summaryrefslogtreecommitdiffstats
path: root/netwerk
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk')
-rw-r--r--netwerk/base/ARefBase.h31
-rw-r--r--netwerk/base/ArrayBufferInputStream.cpp139
-rw-r--r--netwerk/base/ArrayBufferInputStream.h40
-rw-r--r--netwerk/base/AutoClose.h62
-rw-r--r--netwerk/base/BackgroundFileSaver.cpp1121
-rw-r--r--netwerk/base/BackgroundFileSaver.h397
-rw-r--r--netwerk/base/CacheInfoIPCTypes.h24
-rw-r--r--netwerk/base/CaptivePortalService.cpp428
-rw-r--r--netwerk/base/CaptivePortalService.h79
-rw-r--r--netwerk/base/Dashboard.cpp1182
-rw-r--r--netwerk/base/Dashboard.h91
-rw-r--r--netwerk/base/DashboardTypes.h175
-rw-r--r--netwerk/base/DefaultURI.cpp572
-rw-r--r--netwerk/base/DefaultURI.h59
-rw-r--r--netwerk/base/EventTokenBucket.cpp410
-rw-r--r--netwerk/base/EventTokenBucket.h155
-rw-r--r--netwerk/base/FuzzyLayer.cpp407
-rw-r--r--netwerk/base/FuzzyLayer.h29
-rw-r--r--netwerk/base/FuzzySecurityInfo.cpp177
-rw-r--r--netwerk/base/FuzzySecurityInfo.h30
-rw-r--r--netwerk/base/FuzzySocketControl.cpp192
-rw-r--r--netwerk/base/FuzzySocketControl.h29
-rw-r--r--netwerk/base/IOActivityMonitor.cpp495
-rw-r--r--netwerk/base/IOActivityMonitor.h83
-rw-r--r--netwerk/base/IPv6Utils.h50
-rw-r--r--netwerk/base/InterceptionInfo.cpp63
-rw-r--r--netwerk/base/InterceptionInfo.h44
-rw-r--r--netwerk/base/LoadContextInfo.cpp168
-rw-r--r--netwerk/base/LoadContextInfo.h54
-rw-r--r--netwerk/base/LoadInfo.cpp2372
-rw-r--r--netwerk/base/LoadInfo.h413
-rw-r--r--netwerk/base/LoadTainting.h31
-rw-r--r--netwerk/base/MemoryDownloader.cpp71
-rw-r--r--netwerk/base/MemoryDownloader.h59
-rw-r--r--netwerk/base/NetUtil.sys.mjs453
-rw-r--r--netwerk/base/NetworkConnectivityService.cpp554
-rw-r--r--netwerk/base/NetworkConnectivityService.h76
-rw-r--r--netwerk/base/NetworkDataCountLayer.cpp138
-rw-r--r--netwerk/base/NetworkDataCountLayer.h28
-rw-r--r--netwerk/base/NetworkInfoServiceImpl.h18
-rw-r--r--netwerk/base/PollableEvent.cpp399
-rw-r--r--netwerk/base/PollableEvent.h67
-rw-r--r--netwerk/base/Predictor.cpp2438
-rw-r--r--netwerk/base/Predictor.h458
-rw-r--r--netwerk/base/PrivateBrowsingChannel.h118
-rw-r--r--netwerk/base/ProtocolHandlerInfo.cpp86
-rw-r--r--netwerk/base/ProtocolHandlerInfo.h67
-rw-r--r--netwerk/base/ProxyAutoConfig.cpp943
-rw-r--r--netwerk/base/ProxyAutoConfig.h155
-rw-r--r--netwerk/base/ProxyConfig.h199
-rw-r--r--netwerk/base/RedirectChannelRegistrar.cpp86
-rw-r--r--netwerk/base/RedirectChannelRegistrar.h47
-rw-r--r--netwerk/base/RequestContextService.cpp622
-rw-r--r--netwerk/base/RequestContextService.h44
-rw-r--r--netwerk/base/SSLTokensCache.cpp568
-rw-r--r--netwerk/base/SSLTokensCache.h123
-rw-r--r--netwerk/base/ShutdownLayer.cpp76
-rw-r--r--netwerk/base/ShutdownLayer.h23
-rw-r--r--netwerk/base/SimpleBuffer.cpp85
-rw-r--r--netwerk/base/SimpleBuffer.h57
-rw-r--r--netwerk/base/SimpleChannel.cpp137
-rw-r--r--netwerk/base/SimpleChannel.h152
-rw-r--r--netwerk/base/SimpleChannelParent.cpp97
-rw-r--r--netwerk/base/SimpleChannelParent.h40
-rw-r--r--netwerk/base/TLSServerSocket.cpp446
-rw-r--r--netwerk/base/TLSServerSocket.h81
-rw-r--r--netwerk/base/TRRLoadInfo.cpp865
-rw-r--r--netwerk/base/TRRLoadInfo.h54
-rw-r--r--netwerk/base/ThrottleQueue.cpp410
-rw-r--r--netwerk/base/ThrottleQueue.h66
-rw-r--r--netwerk/base/Tickler.cpp249
-rw-r--r--netwerk/base/Tickler.h132
-rw-r--r--netwerk/base/ascii_pac_utils.js256
-rw-r--r--netwerk/base/http-sfv/Cargo.toml15
-rw-r--r--netwerk/base/http-sfv/SFVService.cpp39
-rw-r--r--netwerk/base/http-sfv/SFVService.h14
-rw-r--r--netwerk/base/http-sfv/moz.build17
-rw-r--r--netwerk/base/http-sfv/nsIStructuredFieldValues.idl290
-rw-r--r--netwerk/base/http-sfv/src/lib.rs873
-rw-r--r--netwerk/base/makecppstring.py17
-rw-r--r--netwerk/base/moz.build319
-rw-r--r--netwerk/base/mozIThirdPartyUtil.idl231
-rw-r--r--netwerk/base/mozurl/.gitignore2
-rw-r--r--netwerk/base/mozurl/Cargo.toml12
-rw-r--r--netwerk/base/mozurl/MozURL.cpp12
-rw-r--r--netwerk/base/mozurl/MozURL.h233
-rw-r--r--netwerk/base/mozurl/cbindgen.toml61
-rw-r--r--netwerk/base/mozurl/moz.build22
-rw-r--r--netwerk/base/mozurl/src/lib.rs574
-rw-r--r--netwerk/base/netCore.h14
-rw-r--r--netwerk/base/nsASocketHandler.h102
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.cpp279
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.h121
-rw-r--r--netwerk/base/nsAsyncStreamCopier.cpp405
-rw-r--r--netwerk/base/nsAsyncStreamCopier.h76
-rw-r--r--netwerk/base/nsAuthInformationHolder.cpp61
-rw-r--r--netwerk/base/nsAuthInformationHolder.h44
-rw-r--r--netwerk/base/nsBase64Encoder.cpp54
-rw-r--r--netwerk/base/nsBase64Encoder.h33
-rw-r--r--netwerk/base/nsBaseChannel.cpp1024
-rw-r--r--netwerk/base/nsBaseChannel.h348
-rw-r--r--netwerk/base/nsBaseContentStream.cpp127
-rw-r--r--netwerk/base/nsBaseContentStream.h79
-rw-r--r--netwerk/base/nsBufferedStreams.cpp1197
-rw-r--r--netwerk/base/nsBufferedStreams.h165
-rw-r--r--netwerk/base/nsDNSPrefetch.cpp164
-rw-r--r--netwerk/base/nsDNSPrefetch.h72
-rw-r--r--netwerk/base/nsDirectoryIndexStream.cpp296
-rw-r--r--netwerk/base/nsDirectoryIndexStream.h46
-rw-r--r--netwerk/base/nsDownloader.cpp98
-rw-r--r--netwerk/base/nsDownloader.h36
-rw-r--r--netwerk/base/nsFileStreams.cpp1031
-rw-r--r--netwerk/base/nsFileStreams.h292
-rw-r--r--netwerk/base/nsIArrayBufferInputStream.idl40
-rw-r--r--netwerk/base/nsIAsyncStreamCopier.idl64
-rw-r--r--netwerk/base/nsIAsyncStreamCopier2.idl59
-rw-r--r--netwerk/base/nsIAsyncVerifyRedirectCallback.idl19
-rw-r--r--netwerk/base/nsIAuthInformation.idl117
-rw-r--r--netwerk/base/nsIAuthModule.idl146
-rw-r--r--netwerk/base/nsIAuthPrompt.idl121
-rw-r--r--netwerk/base/nsIAuthPrompt2.idl104
-rw-r--r--netwerk/base/nsIAuthPromptAdapterFactory.idl22
-rw-r--r--netwerk/base/nsIAuthPromptCallback.idl43
-rw-r--r--netwerk/base/nsIAuthPromptProvider.idl34
-rw-r--r--netwerk/base/nsIBackgroundFileSaver.idl183
-rw-r--r--netwerk/base/nsIBufferedStreams.idl47
-rw-r--r--netwerk/base/nsIByteRangeRequest.idl27
-rw-r--r--netwerk/base/nsICacheInfoChannel.idl207
-rw-r--r--netwerk/base/nsICachingChannel.idl110
-rw-r--r--netwerk/base/nsICancelable.idl24
-rw-r--r--netwerk/base/nsICaptivePortalService.idl83
-rw-r--r--netwerk/base/nsIChannel.idl405
-rw-r--r--netwerk/base/nsIChannelEventSink.idl112
-rw-r--r--netwerk/base/nsIChildChannel.idl34
-rw-r--r--netwerk/base/nsIClassOfService.idl103
-rw-r--r--netwerk/base/nsIClassifiedChannel.idl201
-rw-r--r--netwerk/base/nsIContentSniffer.idl35
-rw-r--r--netwerk/base/nsIDHCPClient.idl19
-rw-r--r--netwerk/base/nsIDashboard.idl64
-rw-r--r--netwerk/base/nsIDashboardEventNotifier.idl23
-rw-r--r--netwerk/base/nsIDownloader.idl50
-rw-r--r--netwerk/base/nsIEncodedChannel.idl60
-rw-r--r--netwerk/base/nsIExternalProtocolHandler.idl17
-rw-r--r--netwerk/base/nsIFileStreams.idl239
-rw-r--r--netwerk/base/nsIFileURL.idl42
-rw-r--r--netwerk/base/nsIForcePendingChannel.idl22
-rw-r--r--netwerk/base/nsIFormPOSTActionChannel.idl17
-rw-r--r--netwerk/base/nsIHttpAuthenticatorCallback.idl30
-rw-r--r--netwerk/base/nsIHttpPushListener.idl36
-rw-r--r--netwerk/base/nsIIOService.idl362
-rw-r--r--netwerk/base/nsIIncrementalDownload.idl109
-rw-r--r--netwerk/base/nsIIncrementalStreamLoader.idl100
-rw-r--r--netwerk/base/nsIInputStreamChannel.idl64
-rw-r--r--netwerk/base/nsIInputStreamPump.idl66
-rw-r--r--netwerk/base/nsIInterceptionInfo.idl73
-rw-r--r--netwerk/base/nsILoadContextInfo.idl86
-rw-r--r--netwerk/base/nsILoadGroup.idl105
-rw-r--r--netwerk/base/nsILoadGroupChild.idl41
-rw-r--r--netwerk/base/nsILoadInfo.idl1535
-rw-r--r--netwerk/base/nsIMIMEInputStream.idl48
-rw-r--r--netwerk/base/nsIMultiPartChannel.idl51
-rw-r--r--netwerk/base/nsINestedURI.idl75
-rw-r--r--netwerk/base/nsINetAddr.idl88
-rw-r--r--netwerk/base/nsINetUtil.idl223
-rw-r--r--netwerk/base/nsINetworkConnectivityService.idl44
-rw-r--r--netwerk/base/nsINetworkInfoService.idl56
-rw-r--r--netwerk/base/nsINetworkInterceptController.idl251
-rw-r--r--netwerk/base/nsINetworkLinkService.idl154
-rw-r--r--netwerk/base/nsINetworkPredictor.idl194
-rw-r--r--netwerk/base/nsINetworkPredictorVerifier.idl40
-rw-r--r--netwerk/base/nsINullChannel.idl16
-rw-r--r--netwerk/base/nsIOService.cpp2194
-rw-r--r--netwerk/base/nsIOService.h279
-rw-r--r--netwerk/base/nsIParentChannel.idl76
-rw-r--r--netwerk/base/nsIParentRedirectingChannel.idl68
-rw-r--r--netwerk/base/nsIPermission.idl82
-rw-r--r--netwerk/base/nsIPermissionManager.idl242
-rw-r--r--netwerk/base/nsIPrivateBrowsingChannel.idl58
-rw-r--r--netwerk/base/nsIProgressEventSink.idl72
-rw-r--r--netwerk/base/nsIPrompt.idl103
-rw-r--r--netwerk/base/nsIProtocolHandler.idl317
-rw-r--r--netwerk/base/nsIProtocolProxyCallback.idl42
-rw-r--r--netwerk/base/nsIProtocolProxyFilter.idl97
-rw-r--r--netwerk/base/nsIProtocolProxyService.idl330
-rw-r--r--netwerk/base/nsIProtocolProxyService2.idl32
-rw-r--r--netwerk/base/nsIProxiedChannel.idl36
-rw-r--r--netwerk/base/nsIProxiedProtocolHandler.idl36
-rw-r--r--netwerk/base/nsIProxyInfo.idl106
-rw-r--r--netwerk/base/nsIRandomGenerator.idl46
-rw-r--r--netwerk/base/nsIRedirectChannelRegistrar.idl71
-rw-r--r--netwerk/base/nsIRedirectHistoryEntry.idl34
-rw-r--r--netwerk/base/nsIRedirectResultListener.idl22
-rw-r--r--netwerk/base/nsIRequest.idl332
-rw-r--r--netwerk/base/nsIRequestContext.idl158
-rw-r--r--netwerk/base/nsIRequestObserver.idl43
-rw-r--r--netwerk/base/nsIRequestObserverProxy.idl31
-rw-r--r--netwerk/base/nsIResumableChannel.idl39
-rw-r--r--netwerk/base/nsISecCheckWrapChannel.idl24
-rw-r--r--netwerk/base/nsISecureBrowserUI.idl21
-rw-r--r--netwerk/base/nsISensitiveInfoHiddenURI.idl17
-rw-r--r--netwerk/base/nsISerializationHelper.idl29
-rw-r--r--netwerk/base/nsIServerSocket.idl269
-rw-r--r--netwerk/base/nsISimpleStreamListener.idl25
-rw-r--r--netwerk/base/nsISimpleURIMutator.idl15
-rw-r--r--netwerk/base/nsISocketFilter.idl53
-rw-r--r--netwerk/base/nsISocketTransport.idl382
-rw-r--r--netwerk/base/nsISocketTransportService.idl162
-rw-r--r--netwerk/base/nsISpeculativeConnect.idl98
-rw-r--r--netwerk/base/nsIStandardURL.idl85
-rw-r--r--netwerk/base/nsIStreamListener.idl38
-rw-r--r--netwerk/base/nsIStreamListenerTee.idl50
-rw-r--r--netwerk/base/nsIStreamLoader.idl77
-rw-r--r--netwerk/base/nsIStreamTransportService.idl49
-rw-r--r--netwerk/base/nsISyncStreamListener.idl19
-rw-r--r--netwerk/base/nsISystemProxySettings.idl42
-rw-r--r--netwerk/base/nsITLSServerSocket.idl183
-rw-r--r--netwerk/base/nsIThreadRetargetableRequest.idl43
-rw-r--r--netwerk/base/nsIThreadRetargetableStreamListener.idl49
-rw-r--r--netwerk/base/nsIThrottledInputChannel.idl87
-rw-r--r--netwerk/base/nsITimedChannel.idl128
-rw-r--r--netwerk/base/nsITraceableChannel.idl37
-rw-r--r--netwerk/base/nsITransport.idl162
-rw-r--r--netwerk/base/nsIUDPSocket.idl407
-rw-r--r--netwerk/base/nsIURI.idl332
-rw-r--r--netwerk/base/nsIURIMutator.idl540
-rw-r--r--netwerk/base/nsIURIMutatorUtils.cpp27
-rw-r--r--netwerk/base/nsIURIWithSpecialOrigin.idl20
-rw-r--r--netwerk/base/nsIURL.idl130
-rw-r--r--netwerk/base/nsIURLParser.idl98
-rw-r--r--netwerk/base/nsIUploadChannel.idl56
-rw-r--r--netwerk/base/nsIUploadChannel2.idl57
-rw-r--r--netwerk/base/nsIncrementalDownload.cpp878
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.cpp188
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.h52
-rw-r--r--netwerk/base/nsInputStreamChannel.cpp101
-rw-r--r--netwerk/base/nsInputStreamChannel.h43
-rw-r--r--netwerk/base/nsInputStreamPump.cpp795
-rw-r--r--netwerk/base/nsInputStreamPump.h129
-rw-r--r--netwerk/base/nsLoadGroup.cpp1158
-rw-r--r--netwerk/base/nsLoadGroup.h111
-rw-r--r--netwerk/base/nsMIMEInputStream.cpp500
-rw-r--r--netwerk/base/nsMIMEInputStream.h27
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.cpp354
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.h99
-rw-r--r--netwerk/base/nsNetAddr.cpp138
-rw-r--r--netwerk/base/nsNetAddr.h30
-rw-r--r--netwerk/base/nsNetSegmentUtils.h20
-rw-r--r--netwerk/base/nsNetUtil.cpp4088
-rw-r--r--netwerk/base/nsNetUtil.h1105
-rw-r--r--netwerk/base/nsPACMan.cpp1009
-rw-r--r--netwerk/base/nsPACMan.h295
-rw-r--r--netwerk/base/nsPISocketTransportService.idl59
-rw-r--r--netwerk/base/nsPreloadedStream.cpp148
-rw-r--r--netwerk/base/nsPreloadedStream.h57
-rw-r--r--netwerk/base/nsProtocolProxyService.cpp2459
-rw-r--r--netwerk/base/nsProtocolProxyService.h420
-rw-r--r--netwerk/base/nsProxyInfo.cpp216
-rw-r--r--netwerk/base/nsProxyInfo.h100
-rw-r--r--netwerk/base/nsReadLine.h131
-rw-r--r--netwerk/base/nsRedirectHistoryEntry.cpp43
-rw-r--r--netwerk/base/nsRedirectHistoryEntry.h37
-rw-r--r--netwerk/base/nsRequestObserverProxy.cpp175
-rw-r--r--netwerk/base/nsRequestObserverProxy.h56
-rw-r--r--netwerk/base/nsSerializationHelper.cpp54
-rw-r--r--netwerk/base/nsSerializationHelper.h35
-rw-r--r--netwerk/base/nsServerSocket.cpp582
-rw-r--r--netwerk/base/nsServerSocket.h70
-rw-r--r--netwerk/base/nsSimpleNestedURI.cpp228
-rw-r--r--netwerk/base/nsSimpleNestedURI.h112
-rw-r--r--netwerk/base/nsSimpleStreamListener.cpp69
-rw-r--r--netwerk/base/nsSimpleStreamListener.h35
-rw-r--r--netwerk/base/nsSimpleURI.cpp794
-rw-r--r--netwerk/base/nsSimpleURI.h164
-rw-r--r--netwerk/base/nsSocketTransport2.cpp3411
-rw-r--r--netwerk/base/nsSocketTransport2.h510
-rw-r--r--netwerk/base/nsSocketTransportService2.cpp1899
-rw-r--r--netwerk/base/nsSocketTransportService2.h355
-rw-r--r--netwerk/base/nsStandardURL.cpp4034
-rw-r--r--netwerk/base/nsStandardURL.h626
-rw-r--r--netwerk/base/nsStreamListenerTee.cpp185
-rw-r--r--netwerk/base/nsStreamListenerTee.h45
-rw-r--r--netwerk/base/nsStreamListenerWrapper.cpp55
-rw-r--r--netwerk/base/nsStreamListenerWrapper.h65
-rw-r--r--netwerk/base/nsStreamLoader.cpp140
-rw-r--r--netwerk/base/nsStreamLoader.h57
-rw-r--r--netwerk/base/nsStreamTransportService.cpp429
-rw-r--r--netwerk/base/nsStreamTransportService.h47
-rw-r--r--netwerk/base/nsSyncStreamListener.cpp169
-rw-r--r--netwerk/base/nsSyncStreamListener.h43
-rw-r--r--netwerk/base/nsTransportUtils.cpp133
-rw-r--r--netwerk/base/nsTransportUtils.h25
-rw-r--r--netwerk/base/nsUDPSocket.cpp1553
-rw-r--r--netwerk/base/nsUDPSocket.h118
-rw-r--r--netwerk/base/nsURIHashKey.h71
-rw-r--r--netwerk/base/nsURLHelper.cpp1356
-rw-r--r--netwerk/base/nsURLHelper.h372
-rw-r--r--netwerk/base/nsURLHelperOSX.cpp205
-rw-r--r--netwerk/base/nsURLHelperUnix.cpp109
-rw-r--r--netwerk/base/nsURLHelperWin.cpp111
-rw-r--r--netwerk/base/nsURLParsers.cpp644
-rw-r--r--netwerk/base/nsURLParsers.h119
-rw-r--r--netwerk/base/rust-helper/Cargo.toml10
-rw-r--r--netwerk/base/rust-helper/cbindgen.toml18
-rw-r--r--netwerk/base/rust-helper/moz.build12
-rw-r--r--netwerk/base/rust-helper/src/lib.rs360
-rw-r--r--netwerk/build/components.conf808
-rw-r--r--netwerk/build/moz.build53
-rw-r--r--netwerk/build/nsNetCID.h849
-rw-r--r--netwerk/build/nsNetModule.cpp249
-rw-r--r--netwerk/build/nsNetModule.h33
-rw-r--r--netwerk/cache2/CacheEntry.cpp1952
-rw-r--r--netwerk/cache2/CacheEntry.h587
-rw-r--r--netwerk/cache2/CacheFile.cpp2589
-rw-r--r--netwerk/cache2/CacheFile.h288
-rw-r--r--netwerk/cache2/CacheFileChunk.cpp840
-rw-r--r--netwerk/cache2/CacheFileChunk.h238
-rw-r--r--netwerk/cache2/CacheFileContextEvictor.cpp741
-rw-r--r--netwerk/cache2/CacheFileContextEvictor.h100
-rw-r--r--netwerk/cache2/CacheFileIOManager.cpp4499
-rw-r--r--netwerk/cache2/CacheFileIOManager.h491
-rw-r--r--netwerk/cache2/CacheFileInputStream.cpp734
-rw-r--r--netwerk/cache2/CacheFileInputStream.h80
-rw-r--r--netwerk/cache2/CacheFileMetadata.cpp1041
-rw-r--r--netwerk/cache2/CacheFileMetadata.h245
-rw-r--r--netwerk/cache2/CacheFileOutputStream.cpp481
-rw-r--r--netwerk/cache2/CacheFileOutputStream.h70
-rw-r--r--netwerk/cache2/CacheFileUtils.cpp668
-rw-r--r--netwerk/cache2/CacheFileUtils.h238
-rw-r--r--netwerk/cache2/CacheHashUtils.cpp226
-rw-r--r--netwerk/cache2/CacheHashUtils.h69
-rw-r--r--netwerk/cache2/CacheIOThread.cpp590
-rw-r--r--netwerk/cache2/CacheIOThread.h149
-rw-r--r--netwerk/cache2/CacheIndex.cpp3921
-rw-r--r--netwerk/cache2/CacheIndex.h1311
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.cpp24
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.h31
-rw-r--r--netwerk/cache2/CacheIndexIterator.cpp110
-rw-r--r--netwerk/cache2/CacheIndexIterator.h62
-rw-r--r--netwerk/cache2/CacheLog.cpp20
-rw-r--r--netwerk/cache2/CacheLog.h20
-rw-r--r--netwerk/cache2/CacheObserver.cpp261
-rw-r--r--netwerk/cache2/CacheObserver.h109
-rw-r--r--netwerk/cache2/CachePurgeLock.cpp115
-rw-r--r--netwerk/cache2/CachePurgeLock.h24
-rw-r--r--netwerk/cache2/CacheStorage.cpp196
-rw-r--r--netwerk/cache2/CacheStorage.h63
-rw-r--r--netwerk/cache2/CacheStorageService.cpp2397
-rw-r--r--netwerk/cache2/CacheStorageService.h448
-rw-r--r--netwerk/cache2/moz.build66
-rw-r--r--netwerk/cache2/nsICacheEntry.idl369
-rw-r--r--netwerk/cache2/nsICacheEntryDoomCallback.idl15
-rw-r--r--netwerk/cache2/nsICacheEntryOpenCallback.idl79
-rw-r--r--netwerk/cache2/nsICachePurgeLock.idl40
-rw-r--r--netwerk/cache2/nsICacheStorage.idl145
-rw-r--r--netwerk/cache2/nsICacheStorageService.idl138
-rw-r--r--netwerk/cache2/nsICacheStorageVisitor.idl36
-rw-r--r--netwerk/cache2/nsICacheTesting.idl20
-rw-r--r--netwerk/cookie/Cookie.cpp289
-rw-r--r--netwerk/cookie/Cookie.h165
-rw-r--r--netwerk/cookie/CookieCommons.cpp749
-rw-r--r--netwerk/cookie/CookieCommons.h142
-rw-r--r--netwerk/cookie/CookieJarSettings.cpp733
-rw-r--r--netwerk/cookie/CookieJarSettings.h267
-rw-r--r--netwerk/cookie/CookieKey.h61
-rw-r--r--netwerk/cookie/CookieLogging.cpp185
-rw-r--r--netwerk/cookie/CookieLogging.h65
-rw-r--r--netwerk/cookie/CookieNotification.cpp60
-rw-r--r--netwerk/cookie/CookieNotification.h46
-rw-r--r--netwerk/cookie/CookiePersistentStorage.cpp2134
-rw-r--r--netwerk/cookie/CookiePersistentStorage.h160
-rw-r--r--netwerk/cookie/CookiePrivateStorage.cpp44
-rw-r--r--netwerk/cookie/CookiePrivateStorage.h61
-rw-r--r--netwerk/cookie/CookieService.cpp2586
-rw-r--r--netwerk/cookie/CookieService.h163
-rw-r--r--netwerk/cookie/CookieServiceChild.cpp653
-rw-r--r--netwerk/cookie/CookieServiceChild.h82
-rw-r--r--netwerk/cookie/CookieServiceParent.cpp268
-rw-r--r--netwerk/cookie/CookieServiceParent.h88
-rw-r--r--netwerk/cookie/CookieStorage.cpp910
-rw-r--r--netwerk/cookie/CookieStorage.h216
-rw-r--r--netwerk/cookie/CookieXPCShellUtils.sys.mjs53
-rw-r--r--netwerk/cookie/PCookieService.ipdl74
-rw-r--r--netwerk/cookie/moz.build77
-rw-r--r--netwerk/cookie/nsICookie.idl166
-rw-r--r--netwerk/cookie/nsICookieJarSettings.idl86
-rw-r--r--netwerk/cookie/nsICookieManager.idl278
-rw-r--r--netwerk/cookie/nsICookieNotification.idl70
-rw-r--r--netwerk/cookie/nsICookiePermission.idl35
-rw-r--r--netwerk/cookie/nsICookieService.idl144
-rw-r--r--netwerk/cookie/test/browser/browser.toml42
-rw-r--r--netwerk/cookie/test/browser/browser_broadcastChannel.js80
-rw-r--r--netwerk/cookie/test/browser/browser_cookie_insecure_overwrites_secure.js128
-rw-r--r--netwerk/cookie/test/browser/browser_cookie_purge_sync.js141
-rw-r--r--netwerk/cookie/test/browser/browser_cookies.js53
-rw-r--r--netwerk/cookie/test/browser/browser_cookies_ipv6.js57
-rw-r--r--netwerk/cookie/test/browser/browser_domCache.js25
-rw-r--r--netwerk/cookie/test/browser/browser_indexedDB.js84
-rw-r--r--netwerk/cookie/test/browser/browser_originattributes.js121
-rw-r--r--netwerk/cookie/test/browser/browser_oversize.js96
-rw-r--r--netwerk/cookie/test/browser/browser_partitionedConsole.js201
-rw-r--r--netwerk/cookie/test/browser/browser_partitioned_telemetry.js134
-rw-r--r--netwerk/cookie/test/browser/browser_sameSiteConsole.js133
-rw-r--r--netwerk/cookie/test/browser/browser_serviceWorker.js45
-rw-r--r--netwerk/cookie/test/browser/browser_sharedWorker.js18
-rw-r--r--netwerk/cookie/test/browser/browser_storage.js43
-rw-r--r--netwerk/cookie/test/browser/file_empty.html2
-rw-r--r--netwerk/cookie/test/browser/file_empty.js1
-rw-r--r--netwerk/cookie/test/browser/head.js201
-rw-r--r--netwerk/cookie/test/browser/oversize.sjs17
-rw-r--r--netwerk/cookie/test/browser/partitioned.sjs32
-rw-r--r--netwerk/cookie/test/browser/sameSite.sjs7
-rw-r--r--netwerk/cookie/test/browser/server.sjs9
-rw-r--r--netwerk/cookie/test/mochitest/cookie.sjs166
-rw-r--r--netwerk/cookie/test/mochitest/cookiesHelper.js63
-rw-r--r--netwerk/cookie/test/mochitest/empty.html1
-rw-r--r--netwerk/cookie/test/mochitest/mochitest.toml27
-rw-r--r--netwerk/cookie/test/mochitest/test_document_cookie.html20
-rw-r--r--netwerk/cookie/test/mochitest/test_document_cookie_notification.html32
-rw-r--r--netwerk/cookie/test/mochitest/test_fetch.html20
-rw-r--r--netwerk/cookie/test/mochitest/test_image.html24
-rw-r--r--netwerk/cookie/test/mochitest/test_metaTag.html24
-rw-r--r--netwerk/cookie/test/mochitest/test_script.html25
-rw-r--r--netwerk/cookie/test/mochitest/test_sharedWorker.html50
-rw-r--r--netwerk/cookie/test/mochitest/test_worker.html31
-rw-r--r--netwerk/cookie/test/mochitest/test_xhr.html25
-rw-r--r--netwerk/cookie/test/mochitest/test_xmlDocument.html37
-rw-r--r--netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js105
-rw-r--r--netwerk/cookie/test/unit/test_bug1155169.js96
-rw-r--r--netwerk/cookie/test/unit/test_bug1321912.js99
-rw-r--r--netwerk/cookie/test/unit/test_bug643051.js43
-rw-r--r--netwerk/cookie/test/unit/test_eviction.js199
-rw-r--r--netwerk/cookie/test/unit/test_getCookieSince.js72
-rw-r--r--netwerk/cookie/test/unit/test_migrateCookieLifetimePref.js65
-rw-r--r--netwerk/cookie/test/unit/test_parser_0001.js32
-rw-r--r--netwerk/cookie/test/unit/test_parser_0019.js32
-rw-r--r--netwerk/cookie/test/unit/test_rawSameSite.js125
-rw-r--r--netwerk/cookie/test/unit/test_schemeMap.js216
-rw-r--r--netwerk/cookie/test/unit/test_timestamp_fixup.js130
-rw-r--r--netwerk/cookie/test/unit/xpcshell.toml26
-rw-r--r--netwerk/dns/ChildDNSService.cpp514
-rw-r--r--netwerk/dns/ChildDNSService.h78
-rw-r--r--netwerk/dns/DNS.cpp445
-rw-r--r--netwerk/dns/DNS.h260
-rw-r--r--netwerk/dns/DNSAdditionalInfo.cpp25
-rw-r--r--netwerk/dns/DNSAdditionalInfo.h44
-rw-r--r--netwerk/dns/DNSByTypeRecord.h259
-rw-r--r--netwerk/dns/DNSListenerProxy.cpp37
-rw-r--r--netwerk/dns/DNSListenerProxy.h62
-rw-r--r--netwerk/dns/DNSLogging.h26
-rw-r--r--netwerk/dns/DNSPacket.cpp1100
-rw-r--r--netwerk/dns/DNSPacket.h105
-rw-r--r--netwerk/dns/DNSRequestBase.h152
-rw-r--r--netwerk/dns/DNSRequestChild.cpp574
-rw-r--r--netwerk/dns/DNSRequestChild.h42
-rw-r--r--netwerk/dns/DNSRequestParent.cpp183
-rw-r--r--netwerk/dns/DNSRequestParent.h45
-rw-r--r--netwerk/dns/DNSServiceBase.cpp64
-rw-r--r--netwerk/dns/DNSServiceBase.h36
-rw-r--r--netwerk/dns/DNSUtils.cpp81
-rw-r--r--netwerk/dns/DNSUtils.h32
-rw-r--r--netwerk/dns/GetAddrInfo.cpp584
-rw-r--r--netwerk/dns/GetAddrInfo.h111
-rw-r--r--netwerk/dns/HTTPSSVC.cpp523
-rw-r--r--netwerk/dns/HTTPSSVC.h164
-rw-r--r--netwerk/dns/HostRecordQueue.cpp223
-rw-r--r--netwerk/dns/HostRecordQueue.h72
-rw-r--r--netwerk/dns/IDNBlocklistUtils.cpp86
-rw-r--r--netwerk/dns/IDNBlocklistUtils.h65
-rw-r--r--netwerk/dns/IDNCharacterBlocklist.inc63
-rw-r--r--netwerk/dns/NativeDNSResolverOverrideChild.cpp49
-rw-r--r--netwerk/dns/NativeDNSResolverOverrideChild.h40
-rw-r--r--netwerk/dns/NativeDNSResolverOverrideParent.cpp111
-rw-r--r--netwerk/dns/NativeDNSResolverOverrideParent.h32
-rw-r--r--netwerk/dns/PDNSRequest.ipdl40
-rw-r--r--netwerk/dns/PDNSRequestParams.ipdlh39
-rw-r--r--netwerk/dns/PNativeDNSResolverOverride.ipdl26
-rw-r--r--netwerk/dns/PTRRService.ipdl35
-rw-r--r--netwerk/dns/PlatformDNSAndroid.cpp154
-rw-r--r--netwerk/dns/PlatformDNSUnix.cpp63
-rw-r--r--netwerk/dns/PlatformDNSWin.cpp140
-rw-r--r--netwerk/dns/PublicSuffixList.sys.mjs104
-rw-r--r--netwerk/dns/TRR.cpp1139
-rw-r--r--netwerk/dns/TRR.h159
-rw-r--r--netwerk/dns/TRRQuery.cpp387
-rw-r--r--netwerk/dns/TRRQuery.h111
-rw-r--r--netwerk/dns/TRRService.cpp1411
-rw-r--r--netwerk/dns/TRRService.h384
-rw-r--r--netwerk/dns/TRRServiceBase.cpp399
-rw-r--r--netwerk/dns/TRRServiceBase.h90
-rw-r--r--netwerk/dns/TRRServiceChild.cpp121
-rw-r--r--netwerk/dns/TRRServiceChild.h53
-rw-r--r--netwerk/dns/TRRServiceParent.cpp239
-rw-r--r--netwerk/dns/TRRServiceParent.h52
-rw-r--r--netwerk/dns/effective_tld_names.dat15582
-rw-r--r--netwerk/dns/moz.build122
-rw-r--r--netwerk/dns/nsDNSService2.cpp1792
-rw-r--r--netwerk/dns/nsDNSService2.h135
-rw-r--r--netwerk/dns/nsEffectiveTLDService.cpp518
-rw-r--r--netwerk/dns/nsEffectiveTLDService.h94
-rw-r--r--netwerk/dns/nsHostRecord.cpp598
-rw-r--r--netwerk/dns/nsHostRecord.h416
-rw-r--r--netwerk/dns/nsHostResolver.cpp1965
-rw-r--r--netwerk/dns/nsHostResolver.h344
-rw-r--r--netwerk/dns/nsIDNKitInterface.h195
-rw-r--r--netwerk/dns/nsIDNSAdditionalInfo.idl12
-rw-r--r--netwerk/dns/nsIDNSByTypeRecord.idl133
-rw-r--r--netwerk/dns/nsIDNSListener.idl34
-rw-r--r--netwerk/dns/nsIDNSRecord.idl154
-rw-r--r--netwerk/dns/nsIDNSService.idl383
-rw-r--r--netwerk/dns/nsIDNService.cpp855
-rw-r--r--netwerk/dns/nsIDNService.h191
-rw-r--r--netwerk/dns/nsIEffectiveTLDService.idl205
-rw-r--r--netwerk/dns/nsIIDNService.idl58
-rw-r--r--netwerk/dns/nsINativeDNSResolverOverride.idl37
-rw-r--r--netwerk/dns/nsITRRSkipReason.idl121
-rw-r--r--netwerk/dns/nsPIDNSService.idl34
-rw-r--r--netwerk/dns/prepare_tlds.py150
-rw-r--r--netwerk/dns/punycode.c325
-rw-r--r--netwerk/dns/punycode.h106
-rw-r--r--netwerk/dns/tests/moz.build7
-rw-r--r--netwerk/dns/tests/unit/data/fake_public_suffix_list.dat57
-rw-r--r--netwerk/dns/tests/unit/data/moz.build14
-rw-r--r--netwerk/dns/tests/unit/moz.build7
-rw-r--r--netwerk/dns/tests/unit/test_PublicSuffixList.js174
-rw-r--r--netwerk/dns/tests/unit/test_nsEffectiveTLDService_Reload_DAFSA.js32
-rw-r--r--netwerk/dns/tests/unit/test_nsEffectiveTLDService_getKnownPublicSuffix.js44
-rw-r--r--netwerk/dns/tests/unit/test_nsEffectiveTLDService_getSite.js77
-rw-r--r--netwerk/dns/tests/unit/xpcshell.toml17
-rw-r--r--netwerk/docs/cache2/doc.rst569
-rw-r--r--netwerk/docs/captive_portals.md68
-rw-r--r--netwerk/docs/connectivity_checking.md14
-rw-r--r--netwerk/docs/dns/dns-over-https-trr.md156
-rw-r--r--netwerk/docs/dns/trr-skip-reasons.md324
-rw-r--r--netwerk/docs/early_hints.md157
-rw-r--r--netwerk/docs/http/http3.md154
-rw-r--r--netwerk/docs/http/lifecycle.rst220
-rw-r--r--netwerk/docs/http/logging.rst341
-rw-r--r--netwerk/docs/http_server_for_testing.rst482
-rw-r--r--netwerk/docs/index.md61
-rw-r--r--netwerk/docs/mochitest_with_http3.md76
-rw-r--r--netwerk/docs/necko_lingo.md243
-rw-r--r--netwerk/docs/neqo_triage_guideline.md12
-rw-r--r--netwerk/docs/network_test_guidelines.md175
-rw-r--r--netwerk/docs/new_to_necko_resources.rst80
-rw-r--r--netwerk/docs/sec-necko-components.md77
-rw-r--r--netwerk/docs/submitting_networking_bugs.md112
-rw-r--r--netwerk/docs/url_parsers.md143
-rw-r--r--netwerk/docs/webtransport/webtransport.md6
-rw-r--r--netwerk/docs/webtransport/webtransportsessionproxy.md19
-rw-r--r--netwerk/ipc/ChannelEventQueue.cpp227
-rw-r--r--netwerk/ipc/ChannelEventQueue.h383
-rw-r--r--netwerk/ipc/DocumentChannel.cpp484
-rw-r--r--netwerk/ipc/DocumentChannel.h120
-rw-r--r--netwerk/ipc/DocumentChannelChild.cpp483
-rw-r--r--netwerk/ipc/DocumentChannelChild.h76
-rw-r--r--netwerk/ipc/DocumentChannelParent.cpp160
-rw-r--r--netwerk/ipc/DocumentChannelParent.h68
-rw-r--r--netwerk/ipc/DocumentLoadListener.cpp3027
-rw-r--r--netwerk/ipc/DocumentLoadListener.h630
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueChild.cpp30
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueChild.h33
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueParent.cpp125
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueParent.h51
-rw-r--r--netwerk/ipc/NeckoChannelParams.ipdlh623
-rw-r--r--netwerk/ipc/NeckoChild.cpp337
-rw-r--r--netwerk/ipc/NeckoChild.h93
-rw-r--r--netwerk/ipc/NeckoCommon.cpp68
-rw-r--r--netwerk/ipc/NeckoCommon.h144
-rw-r--r--netwerk/ipc/NeckoMessageUtils.h190
-rw-r--r--netwerk/ipc/NeckoParent.cpp889
-rw-r--r--netwerk/ipc/NeckoParent.h217
-rw-r--r--netwerk/ipc/NeckoTargetHolder.cpp38
-rw-r--r--netwerk/ipc/NeckoTargetHolder.h40
-rw-r--r--netwerk/ipc/PDataChannel.ipdl25
-rw-r--r--netwerk/ipc/PDocumentChannel.ipdl66
-rw-r--r--netwerk/ipc/PFileChannel.ipdl28
-rw-r--r--netwerk/ipc/PInputChannelThrottleQueue.ipdl25
-rw-r--r--netwerk/ipc/PNecko.ipdl178
-rw-r--r--netwerk/ipc/PProxyAutoConfig.ipdl21
-rw-r--r--netwerk/ipc/PProxyConfigLookup.ipdl21
-rw-r--r--netwerk/ipc/PSimpleChannel.ipdl25
-rw-r--r--netwerk/ipc/PSocketProcess.ipdl222
-rw-r--r--netwerk/ipc/PSocketProcessBackground.ipdl59
-rw-r--r--netwerk/ipc/PSocketProcessBridge.ipdl48
-rw-r--r--netwerk/ipc/ParentChannelWrapper.cpp101
-rw-r--r--netwerk/ipc/ParentChannelWrapper.h39
-rw-r--r--netwerk/ipc/ParentProcessDocumentChannel.cpp314
-rw-r--r--netwerk/ipc/ParentProcessDocumentChannel.h62
-rw-r--r--netwerk/ipc/ProxyAutoConfigChild.cpp225
-rw-r--r--netwerk/ipc/ProxyAutoConfigChild.h84
-rw-r--r--netwerk/ipc/ProxyAutoConfigParent.cpp21
-rw-r--r--netwerk/ipc/ProxyAutoConfigParent.h28
-rw-r--r--netwerk/ipc/ProxyConfigLookup.cpp99
-rw-r--r--netwerk/ipc/ProxyConfigLookup.h44
-rw-r--r--netwerk/ipc/ProxyConfigLookupChild.cpp43
-rw-r--r--netwerk/ipc/ProxyConfigLookupChild.h39
-rw-r--r--netwerk/ipc/ProxyConfigLookupParent.cpp43
-rw-r--r--netwerk/ipc/ProxyConfigLookupParent.h35
-rw-r--r--netwerk/ipc/SocketProcessBackgroundChild.cpp113
-rw-r--r--netwerk/ipc/SocketProcessBackgroundChild.h45
-rw-r--r--netwerk/ipc/SocketProcessBackgroundParent.cpp169
-rw-r--r--netwerk/ipc/SocketProcessBackgroundParent.h53
-rw-r--r--netwerk/ipc/SocketProcessBridgeChild.cpp186
-rw-r--r--netwerk/ipc/SocketProcessBridgeChild.h50
-rw-r--r--netwerk/ipc/SocketProcessBridgeParent.cpp97
-rw-r--r--netwerk/ipc/SocketProcessBridgeParent.h44
-rw-r--r--netwerk/ipc/SocketProcessChild.cpp833
-rw-r--r--netwerk/ipc/SocketProcessChild.h188
-rw-r--r--netwerk/ipc/SocketProcessHost.cpp292
-rw-r--r--netwerk/ipc/SocketProcessHost.h150
-rw-r--r--netwerk/ipc/SocketProcessImpl.cpp76
-rw-r--r--netwerk/ipc/SocketProcessImpl.h35
-rw-r--r--netwerk/ipc/SocketProcessLogging.h20
-rw-r--r--netwerk/ipc/SocketProcessParent.cpp364
-rw-r--r--netwerk/ipc/SocketProcessParent.h117
-rw-r--r--netwerk/ipc/moz.build117
-rw-r--r--netwerk/locales/en-US/necko.properties108
-rw-r--r--netwerk/locales/jar.mn9
-rw-r--r--netwerk/locales/moz.build7
-rw-r--r--netwerk/metrics.yaml1029
-rw-r--r--netwerk/mime/moz.build23
-rw-r--r--netwerk/mime/nsIMIMEHeaderParam.idl206
-rw-r--r--netwerk/mime/nsIMIMEInfo.idl369
-rw-r--r--netwerk/mime/nsIMIMEService.idl258
-rw-r--r--netwerk/mime/nsMIMEHeaderParamImpl.cpp1360
-rw-r--r--netwerk/mime/nsMIMEHeaderParamImpl.h44
-rw-r--r--netwerk/mime/nsMimeTypes.h281
-rw-r--r--netwerk/moz.build35
-rw-r--r--netwerk/protocol/about/moz.build32
-rw-r--r--netwerk/protocol/about/nsAboutBlank.cpp52
-rw-r--r--netwerk/protocol/about/nsAboutBlank.h32
-rw-r--r--netwerk/protocol/about/nsAboutCache.cpp533
-rw-r--r--netwerk/protocol/about/nsAboutCache.h199
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.cpp559
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.h86
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.cpp428
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.h139
-rw-r--r--netwerk/protocol/about/nsAboutProtocolUtils.h65
-rw-r--r--netwerk/protocol/about/nsIAboutModule.idl113
-rw-r--r--netwerk/protocol/data/DataChannelChild.cpp58
-rw-r--r--netwerk/protocol/data/DataChannelChild.h40
-rw-r--r--netwerk/protocol/data/DataChannelParent.cpp105
-rw-r--r--netwerk/protocol/data/DataChannelParent.h39
-rw-r--r--netwerk/protocol/data/moz.build29
-rw-r--r--netwerk/protocol/data/nsDataChannel.cpp146
-rw-r--r--netwerk/protocol/data/nsDataChannel.h31
-rw-r--r--netwerk/protocol/data/nsDataHandler.cpp272
-rw-r--r--netwerk/protocol/data/nsDataHandler.h56
-rw-r--r--netwerk/protocol/data/nsDataModule.cpp15
-rw-r--r--netwerk/protocol/file/FileChannelChild.cpp52
-rw-r--r--netwerk/protocol/file/FileChannelChild.h35
-rw-r--r--netwerk/protocol/file/FileChannelParent.cpp105
-rw-r--r--netwerk/protocol/file/FileChannelParent.h39
-rw-r--r--netwerk/protocol/file/moz.build40
-rw-r--r--netwerk/protocol/file/nsFileChannel.cpp569
-rw-r--r--netwerk/protocol/file/nsFileChannel.h59
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.cpp266
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.h28
-rw-r--r--netwerk/protocol/file/nsIFileChannel.idl17
-rw-r--r--netwerk/protocol/file/nsIFileProtocolHandler.idl102
-rw-r--r--netwerk/protocol/gio/GIOChannelChild.cpp458
-rw-r--r--netwerk/protocol/gio/GIOChannelChild.h111
-rw-r--r--netwerk/protocol/gio/GIOChannelParent.cpp324
-rw-r--r--netwerk/protocol/gio/GIOChannelParent.h80
-rw-r--r--netwerk/protocol/gio/PGIOChannel.ipdl51
-rw-r--r--netwerk/protocol/gio/components.conf24
-rw-r--r--netwerk/protocol/gio/moz.build42
-rw-r--r--netwerk/protocol/gio/nsGIOProtocolHandler.cpp1027
-rw-r--r--netwerk/protocol/gio/nsGIOProtocolHandler.h38
-rw-r--r--netwerk/protocol/http/ASpdySession.cpp94
-rw-r--r--netwerk/protocol/http/ASpdySession.h115
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.cpp204
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.h51
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.cpp87
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.h52
-rw-r--r--netwerk/protocol/http/AltServiceChild.cpp111
-rw-r--r--netwerk/protocol/http/AltServiceChild.h41
-rw-r--r--netwerk/protocol/http/AltServiceParent.cpp49
-rw-r--r--netwerk/protocol/http/AltServiceParent.h39
-rw-r--r--netwerk/protocol/http/AltSvcTransactionChild.cpp75
-rw-r--r--netwerk/protocol/http/AltSvcTransactionChild.h39
-rw-r--r--netwerk/protocol/http/AltSvcTransactionParent.cpp64
-rw-r--r--netwerk/protocol/http/AltSvcTransactionParent.h52
-rw-r--r--netwerk/protocol/http/AlternateServices.cpp1354
-rw-r--r--netwerk/protocol/http/AlternateServices.h271
-rw-r--r--netwerk/protocol/http/BackgroundChannelRegistrar.cpp99
-rw-r--r--netwerk/protocol/http/BackgroundChannelRegistrar.h55
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeChild.cpp60
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeChild.h41
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeParent.cpp63
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeParent.h36
-rw-r--r--netwerk/protocol/http/BinaryHttpRequest.cpp51
-rw-r--r--netwerk/protocol/http/BinaryHttpRequest.h48
-rw-r--r--netwerk/protocol/http/CacheControlParser.cpp134
-rw-r--r--netwerk/protocol/http/CacheControlParser.h52
-rw-r--r--netwerk/protocol/http/CachePushChecker.cpp248
-rw-r--r--netwerk/protocol/http/CachePushChecker.h45
-rw-r--r--netwerk/protocol/http/ClassOfService.h76
-rw-r--r--netwerk/protocol/http/ConnectionDiagnostics.cpp239
-rw-r--r--netwerk/protocol/http/ConnectionEntry.cpp1102
-rw-r--r--netwerk/protocol/http/ConnectionEntry.h231
-rw-r--r--netwerk/protocol/http/ConnectionHandle.cpp98
-rw-r--r--netwerk/protocol/http/ConnectionHandle.h41
-rw-r--r--netwerk/protocol/http/DnsAndConnectSocket.cpp1391
-rw-r--r--netwerk/protocol/http/DnsAndConnectSocket.h274
-rw-r--r--netwerk/protocol/http/EarlyHintPreconnect.cpp106
-rw-r--r--netwerk/protocol/http/EarlyHintPreconnect.h24
-rw-r--r--netwerk/protocol/http/EarlyHintPreloader.cpp846
-rw-r--r--netwerk/protocol/http/EarlyHintPreloader.h199
-rw-r--r--netwerk/protocol/http/EarlyHintRegistrar.cpp130
-rw-r--r--netwerk/protocol/http/EarlyHintRegistrar.h82
-rw-r--r--netwerk/protocol/http/EarlyHintsService.cpp191
-rw-r--r--netwerk/protocol/http/EarlyHintsService.h59
-rw-r--r--netwerk/protocol/http/HPKEConfigManager.sys.mjs42
-rw-r--r--netwerk/protocol/http/HTTPSRecordResolver.cpp117
-rw-r--r--netwerk/protocol/http/HTTPSRecordResolver.h44
-rw-r--r--netwerk/protocol/http/Http2Compression.cpp1458
-rw-r--r--netwerk/protocol/http/Http2Compression.h207
-rw-r--r--netwerk/protocol/http/Http2HuffmanIncoming.h709
-rw-r--r--netwerk/protocol/http/Http2HuffmanOutgoing.h85
-rw-r--r--netwerk/protocol/http/Http2Push.cpp557
-rw-r--r--netwerk/protocol/http/Http2Push.h182
-rw-r--r--netwerk/protocol/http/Http2Session.cpp4513
-rw-r--r--netwerk/protocol/http/Http2Session.h626
-rw-r--r--netwerk/protocol/http/Http2Stream.cpp298
-rw-r--r--netwerk/protocol/http/Http2Stream.h59
-rw-r--r--netwerk/protocol/http/Http2StreamBase.cpp1324
-rw-r--r--netwerk/protocol/http/Http2StreamBase.h377
-rw-r--r--netwerk/protocol/http/Http2StreamTunnel.cpp764
-rw-r--r--netwerk/protocol/http/Http2StreamTunnel.h151
-rw-r--r--netwerk/protocol/http/Http3Session.cpp2522
-rw-r--r--netwerk/protocol/http/Http3Session.h388
-rw-r--r--netwerk/protocol/http/Http3Stream.cpp533
-rw-r--r--netwerk/protocol/http/Http3Stream.h165
-rw-r--r--netwerk/protocol/http/Http3StreamBase.h70
-rw-r--r--netwerk/protocol/http/Http3WebTransportSession.cpp518
-rw-r--r--netwerk/protocol/http/Http3WebTransportSession.h123
-rw-r--r--netwerk/protocol/http/Http3WebTransportStream.cpp667
-rw-r--r--netwerk/protocol/http/Http3WebTransportStream.h136
-rw-r--r--netwerk/protocol/http/HttpAuthUtils.cpp171
-rw-r--r--netwerk/protocol/http/HttpAuthUtils.h33
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelChild.cpp499
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelChild.h156
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelParent.cpp526
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelParent.h124
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.cpp6479
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.h1194
-rw-r--r--netwerk/protocol/http/HttpChannelChild.cpp3391
-rw-r--r--netwerk/protocol/http/HttpChannelChild.h481
-rw-r--r--netwerk/protocol/http/HttpChannelParams.ipdlh71
-rw-r--r--netwerk/protocol/http/HttpChannelParent.cpp2202
-rw-r--r--netwerk/protocol/http/HttpChannelParent.h333
-rw-r--r--netwerk/protocol/http/HttpConnectionBase.cpp101
-rw-r--r--netwerk/protocol/http/HttpConnectionBase.h246
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrChild.cpp191
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrChild.h52
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrParent.cpp319
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrParent.h43
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrShell.h229
-rw-r--r--netwerk/protocol/http/HttpConnectionUDP.cpp706
-rw-r--r--netwerk/protocol/http/HttpConnectionUDP.h138
-rw-r--r--netwerk/protocol/http/HttpInfo.cpp16
-rw-r--r--netwerk/protocol/http/HttpInfo.h24
-rw-r--r--netwerk/protocol/http/HttpLog.h73
-rw-r--r--netwerk/protocol/http/HttpTrafficAnalyzer.cpp269
-rw-r--r--netwerk/protocol/http/HttpTrafficAnalyzer.h51
-rw-r--r--netwerk/protocol/http/HttpTrafficAnalyzer.inc106
-rw-r--r--netwerk/protocol/http/HttpTransactionChild.cpp665
-rw-r--r--netwerk/protocol/http/HttpTransactionChild.h127
-rw-r--r--netwerk/protocol/http/HttpTransactionParent.cpp924
-rw-r--r--netwerk/protocol/http/HttpTransactionParent.h197
-rw-r--r--netwerk/protocol/http/HttpTransactionShell.h242
-rw-r--r--netwerk/protocol/http/HttpWinUtils.cpp111
-rw-r--r--netwerk/protocol/http/HttpWinUtils.h18
-rw-r--r--netwerk/protocol/http/InterceptedHttpChannel.cpp1714
-rw-r--r--netwerk/protocol/http/InterceptedHttpChannel.h316
-rw-r--r--netwerk/protocol/http/MockHttpAuth.cpp46
-rw-r--r--netwerk/protocol/http/MockHttpAuth.h31
-rw-r--r--netwerk/protocol/http/NetworkMarker.cpp188
-rw-r--r--netwerk/protocol/http/NetworkMarker.h40
-rw-r--r--netwerk/protocol/http/NullHttpChannel.cpp865
-rw-r--r--netwerk/protocol/http/NullHttpChannel.h64
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.cpp212
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.h94
-rw-r--r--netwerk/protocol/http/ObliviousHttpChannel.cpp860
-rw-r--r--netwerk/protocol/http/ObliviousHttpChannel.h72
-rw-r--r--netwerk/protocol/http/ObliviousHttpService.cpp235
-rw-r--r--netwerk/protocol/http/ObliviousHttpService.h46
-rw-r--r--netwerk/protocol/http/OpaqueResponseUtils.cpp679
-rw-r--r--netwerk/protocol/http/OpaqueResponseUtils.h216
-rw-r--r--netwerk/protocol/http/PAltDataOutputStream.ipdl42
-rw-r--r--netwerk/protocol/http/PAltService.ipdl38
-rw-r--r--netwerk/protocol/http/PAltSvcTransaction.ipdl23
-rw-r--r--netwerk/protocol/http/PBackgroundDataBridge.ipdl32
-rw-r--r--netwerk/protocol/http/PHttpBackgroundChannel.ipdl80
-rw-r--r--netwerk/protocol/http/PHttpChannel.ipdl147
-rw-r--r--netwerk/protocol/http/PHttpChannelParams.h290
-rw-r--r--netwerk/protocol/http/PHttpConnectionMgr.ipdl46
-rw-r--r--netwerk/protocol/http/PHttpTransaction.ipdl119
-rw-r--r--netwerk/protocol/http/PSpdyPush.h56
-rw-r--r--netwerk/protocol/http/ParentChannelListener.cpp292
-rw-r--r--netwerk/protocol/http/ParentChannelListener.h86
-rw-r--r--netwerk/protocol/http/PendingTransactionInfo.cpp136
-rw-r--r--netwerk/protocol/http/PendingTransactionInfo.h63
-rw-r--r--netwerk/protocol/http/PendingTransactionQueue.cpp287
-rw-r--r--netwerk/protocol/http/PendingTransactionQueue.h92
-rw-r--r--netwerk/protocol/http/QuicSocketControl.cpp128
-rw-r--r--netwerk/protocol/http/QuicSocketControl.h67
-rw-r--r--netwerk/protocol/http/README119
-rw-r--r--netwerk/protocol/http/SpeculativeTransaction.cpp89
-rw-r--r--netwerk/protocol/http/SpeculativeTransaction.h70
-rw-r--r--netwerk/protocol/http/TLSTransportLayer.cpp866
-rw-r--r--netwerk/protocol/http/TLSTransportLayer.h170
-rw-r--r--netwerk/protocol/http/TRRServiceChannel.cpp1581
-rw-r--r--netwerk/protocol/http/TRRServiceChannel.h177
-rw-r--r--netwerk/protocol/http/TimingStruct.h44
-rw-r--r--netwerk/protocol/http/TlsHandshaker.cpp336
-rw-r--r--netwerk/protocol/http/TlsHandshaker.h95
-rw-r--r--netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs26
-rw-r--r--netwerk/protocol/http/binary_http/Cargo.toml11
-rw-r--r--netwerk/protocol/http/binary_http/src/binary_http.h24
-rw-r--r--netwerk/protocol/http/binary_http/src/lib.rs264
-rw-r--r--netwerk/protocol/http/components.conf33
-rw-r--r--netwerk/protocol/http/http2_huffman_table.txt257
-rw-r--r--netwerk/protocol/http/make_incoming_tables.py202
-rw-r--r--netwerk/protocol/http/make_outgoing_tables.py58
-rw-r--r--netwerk/protocol/http/metrics.yaml275
-rw-r--r--netwerk/protocol/http/moz.build219
-rw-r--r--netwerk/protocol/http/nsAHttpConnection.h292
-rw-r--r--netwerk/protocol/http/nsAHttpTransaction.h307
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.cpp1753
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.h130
-rw-r--r--netwerk/protocol/http/nsHttp.cpp1160
-rw-r--r--netwerk/protocol/http/nsHttp.h527
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.cpp298
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.h40
-rw-r--r--netwerk/protocol/http/nsHttpAtomList.h111
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.cpp349
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.h219
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.cpp105
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.h34
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.cpp101
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.h39
-rw-r--r--netwerk/protocol/http/nsHttpChannel.cpp10430
-rw-r--r--netwerk/protocol/http/nsHttpChannel.h869
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.cpp1913
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.h185
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.cpp170
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.h50
-rw-r--r--netwerk/protocol/http/nsHttpConnection.cpp2609
-rw-r--r--netwerk/protocol/http/nsHttpConnection.h388
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.cpp570
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.h317
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.cpp3863
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.h469
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.cpp743
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.h98
-rw-r--r--netwerk/protocol/http/nsHttpHandler.cpp2812
-rw-r--r--netwerk/protocol/http/nsHttpHandler.h918
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.cpp475
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.h334
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.cpp404
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.h36
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.cpp367
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.h155
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.cpp1220
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.h239
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.cpp3628
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.h599
-rw-r--r--netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl63
-rw-r--r--netwerk/protocol/http/nsIBinaryHttp.idl40
-rw-r--r--netwerk/protocol/http/nsICorsPreflightCallback.h31
-rw-r--r--netwerk/protocol/http/nsIEarlyHintObserver.idl16
-rw-r--r--netwerk/protocol/http/nsIHttpActivityObserver.idl214
-rw-r--r--netwerk/protocol/http/nsIHttpAuthManager.idl115
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticableChannel.idl122
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticator.idl221
-rw-r--r--netwerk/protocol/http/nsIHttpChannel.idl497
-rw-r--r--netwerk/protocol/http/nsIHttpChannelAuthProvider.idl86
-rw-r--r--netwerk/protocol/http/nsIHttpChannelChild.idl38
-rw-r--r--netwerk/protocol/http/nsIHttpChannelInternal.idl522
-rw-r--r--netwerk/protocol/http/nsIHttpHeaderVisitor.idl26
-rw-r--r--netwerk/protocol/http/nsIHttpProtocolHandler.idl215
-rw-r--r--netwerk/protocol/http/nsIObliviousHttp.idl78
-rw-r--r--netwerk/protocol/http/nsIObliviousHttpChannel.idl26
-rw-r--r--netwerk/protocol/http/nsIRaceCacheWithNetwork.idl57
-rw-r--r--netwerk/protocol/http/nsITlsHandshakeListener.idl14
-rw-r--r--netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl23
-rw-r--r--netwerk/protocol/http/nsServerTiming.cpp110
-rw-r--r--netwerk/protocol/http/nsServerTiming.h54
-rw-r--r--netwerk/protocol/http/oblivious_http/Cargo.toml11
-rw-r--r--netwerk/protocol/http/oblivious_http/src/lib.rs188
-rw-r--r--netwerk/protocol/http/oblivious_http/src/oblivious_http.h24
-rw-r--r--netwerk/protocol/moz.build10
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.cpp1039
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.h236
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.cpp361
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.h120
-rw-r--r--netwerk/protocol/res/RemoteStreamGetter.cpp138
-rw-r--r--netwerk/protocol/res/RemoteStreamGetter.h68
-rw-r--r--netwerk/protocol/res/SubstitutingJARURI.h229
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.cpp723
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.h129
-rw-r--r--netwerk/protocol/res/SubstitutingURL.h66
-rw-r--r--netwerk/protocol/res/moz.build42
-rw-r--r--netwerk/protocol/res/nsIResProtocolHandler.idl15
-rw-r--r--netwerk/protocol/res/nsISubstitutingProtocolHandler.idl62
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.cpp195
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.h78
-rw-r--r--netwerk/protocol/viewsource/moz.build29
-rw-r--r--netwerk/protocol/viewsource/nsIViewSourceChannel.idl41
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.cpp1220
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.h98
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.cpp141
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.h48
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.cpp378
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.h137
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.cpp89
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.h89
-rw-r--r--netwerk/protocol/websocket/PTransportProvider.ipdl30
-rw-r--r--netwerk/protocol/websocket/PWebSocket.ipdl67
-rw-r--r--netwerk/protocol/websocket/PWebSocketConnection.ipdl33
-rw-r--r--netwerk/protocol/websocket/PWebSocketEventListener.ipdl53
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.cpp4336
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.h377
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.cpp732
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.h116
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.cpp360
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.h70
-rw-r--r--netwerk/protocol/websocket/WebSocketConnection.cpp261
-rw-r--r--netwerk/protocol/websocket/WebSocketConnection.h79
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionBase.h86
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionChild.cpp221
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionChild.h54
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionListener.h23
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionParent.cpp210
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionParent.h68
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.cpp109
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.h63
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.cpp117
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.h44
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.cpp566
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.h125
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.cpp133
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.h104
-rw-r--r--netwerk/protocol/websocket/WebSocketLog.h24
-rw-r--r--netwerk/protocol/websocket/moz.build67
-rw-r--r--netwerk/protocol/websocket/nsITransportProvider.idl36
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketChannel.idl268
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketEventService.idl87
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketImpl.idl18
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketListener.idl94
-rw-r--r--netwerk/protocol/webtransport/WebTransportHash.cpp24
-rw-r--r--netwerk/protocol/webtransport/WebTransportHash.h30
-rw-r--r--netwerk/protocol/webtransport/WebTransportLog.h21
-rw-r--r--netwerk/protocol/webtransport/WebTransportSessionProxy.cpp1269
-rw-r--r--netwerk/protocol/webtransport/WebTransportSessionProxy.h195
-rw-r--r--netwerk/protocol/webtransport/WebTransportStreamProxy.cpp386
-rw-r--r--netwerk/protocol/webtransport/WebTransportStreamProxy.h84
-rw-r--r--netwerk/protocol/webtransport/moz.build36
-rw-r--r--netwerk/protocol/webtransport/nsIWebTransport.idl132
-rw-r--r--netwerk/protocol/webtransport/nsIWebTransportStream.idl89
-rw-r--r--netwerk/sctp/datachannel/DataChannel.cpp3548
-rw-r--r--netwerk/sctp/datachannel/DataChannel.h670
-rw-r--r--netwerk/sctp/datachannel/DataChannelListener.h45
-rw-r--r--netwerk/sctp/datachannel/DataChannelLog.h32
-rw-r--r--netwerk/sctp/datachannel/DataChannelProtocol.h85
-rw-r--r--netwerk/sctp/datachannel/moz.build36
-rw-r--r--netwerk/sctp/src/LICENSE.md27
-rw-r--r--netwerk/sctp/src/README.md10
-rw-r--r--netwerk/sctp/src/moz.build58
-rw-r--r--netwerk/sctp/src/moz.yaml63
-rw-r--r--netwerk/sctp/src/netinet/sctp.h674
-rw-r--r--netwerk/sctp/src/netinet/sctp_asconf.c3534
-rw-r--r--netwerk/sctp/src/netinet/sctp_asconf.h89
-rw-r--r--netwerk/sctp/src/netinet/sctp_auth.c2293
-rw-r--r--netwerk/sctp/src/netinet/sctp_auth.h208
-rw-r--r--netwerk/sctp/src/netinet/sctp_bsd_addr.c977
-rw-r--r--netwerk/sctp/src/netinet/sctp_bsd_addr.h67
-rw-r--r--netwerk/sctp/src/netinet/sctp_callout.c249
-rw-r--r--netwerk/sctp/src/netinet/sctp_callout.h114
-rw-r--r--netwerk/sctp/src/netinet/sctp_cc_functions.c2494
-rw-r--r--netwerk/sctp/src/netinet/sctp_constants.h1065
-rw-r--r--netwerk/sctp/src/netinet/sctp_crc32.c819
-rw-r--r--netwerk/sctp/src/netinet/sctp_crc32.h51
-rw-r--r--netwerk/sctp/src/netinet/sctp_header.h598
-rw-r--r--netwerk/sctp/src/netinet/sctp_indata.c5791
-rw-r--r--netwerk/sctp/src/netinet/sctp_indata.h116
-rw-r--r--netwerk/sctp/src/netinet/sctp_input.c6436
-rw-r--r--netwerk/sctp/src/netinet/sctp_input.h61
-rw-r--r--netwerk/sctp/src/netinet/sctp_lock_userspace.h239
-rw-r--r--netwerk/sctp/src/netinet/sctp_os.h84
-rw-r--r--netwerk/sctp/src/netinet/sctp_os_userspace.h1153
-rw-r--r--netwerk/sctp/src/netinet/sctp_output.c15083
-rw-r--r--netwerk/sctp/src/netinet/sctp_output.h235
-rw-r--r--netwerk/sctp/src/netinet/sctp_pcb.c8125
-rw-r--r--netwerk/sctp/src/netinet/sctp_pcb.h876
-rw-r--r--netwerk/sctp/src/netinet/sctp_peeloff.c297
-rw-r--r--netwerk/sctp/src/netinet/sctp_peeloff.h65
-rw-r--r--netwerk/sctp/src/netinet/sctp_process_lock.h693
-rw-r--r--netwerk/sctp/src/netinet/sctp_sha1.c329
-rw-r--r--netwerk/sctp/src/netinet/sctp_sha1.h84
-rw-r--r--netwerk/sctp/src/netinet/sctp_ss_functions.c1121
-rw-r--r--netwerk/sctp/src/netinet/sctp_structs.h1282
-rw-r--r--netwerk/sctp/src/netinet/sctp_sysctl.c1664
-rw-r--r--netwerk/sctp/src/netinet/sctp_sysctl.h628
-rw-r--r--netwerk/sctp/src/netinet/sctp_timer.c1634
-rw-r--r--netwerk/sctp/src/netinet/sctp_timer.h98
-rw-r--r--netwerk/sctp/src/netinet/sctp_uio.h1353
-rw-r--r--netwerk/sctp/src/netinet/sctp_userspace.c425
-rw-r--r--netwerk/sctp/src/netinet/sctp_usrreq.c9039
-rw-r--r--netwerk/sctp/src/netinet/sctp_var.h453
-rw-r--r--netwerk/sctp/src/netinet/sctputil.c8724
-rw-r--r--netwerk/sctp/src/netinet/sctputil.h375
-rw-r--r--netwerk/sctp/src/netinet6/sctp6_usrreq.c1745
-rw-r--r--netwerk/sctp/src/netinet6/sctp6_var.h82
-rwxr-xr-xnetwerk/sctp/src/restore_mod.sh2
-rw-r--r--netwerk/sctp/src/user_atomic.h315
-rw-r--r--netwerk/sctp/src/user_environment.c384
-rw-r--r--netwerk/sctp/src/user_environment.h132
-rw-r--r--netwerk/sctp/src/user_inpcb.h373
-rw-r--r--netwerk/sctp/src/user_ip6_var.h124
-rw-r--r--netwerk/sctp/src/user_ip_icmp.h225
-rw-r--r--netwerk/sctp/src/user_malloc.h203
-rw-r--r--netwerk/sctp/src/user_mbuf.c1589
-rw-r--r--netwerk/sctp/src/user_mbuf.h412
-rw-r--r--netwerk/sctp/src/user_queue.h639
-rw-r--r--netwerk/sctp/src/user_recv_thread.c1566
-rw-r--r--netwerk/sctp/src/user_recv_thread.h34
-rw-r--r--netwerk/sctp/src/user_route.h130
-rw-r--r--netwerk/sctp/src/user_socket.c3571
-rw-r--r--netwerk/sctp/src/user_socketvar.h527
-rw-r--r--netwerk/sctp/src/user_uma.h96
-rw-r--r--netwerk/sctp/src/usrsctp.h1329
-rw-r--r--netwerk/sctp/src/win32-free.patch55
-rw-r--r--netwerk/sctp/src/win32-rands.patch36
-rw-r--r--netwerk/socket/moz.build46
-rw-r--r--netwerk/socket/neqo/extra-bindgen-flags.in1
-rw-r--r--netwerk/socket/neqo_glue/Cargo.toml36
-rw-r--r--netwerk/socket/neqo_glue/NeqoHttp3Conn.h167
-rw-r--r--netwerk/socket/neqo_glue/cbindgen.toml27
-rw-r--r--netwerk/socket/neqo_glue/moz.build21
-rw-r--r--netwerk/socket/neqo_glue/src/lib.rs1439
-rw-r--r--netwerk/socket/nsINamedPipeService.idl77
-rw-r--r--netwerk/socket/nsISocketProvider.idl145
-rw-r--r--netwerk/socket/nsISocketProviderService.idl20
-rw-r--r--netwerk/socket/nsNamedPipeIOLayer.cpp864
-rw-r--r--netwerk/socket/nsNamedPipeIOLayer.h24
-rw-r--r--netwerk/socket/nsNamedPipeService.cpp319
-rw-r--r--netwerk/socket/nsNamedPipeService.h67
-rw-r--r--netwerk/socket/nsSOCKSIOLayer.cpp1471
-rw-r--r--netwerk/socket/nsSOCKSIOLayer.h21
-rw-r--r--netwerk/socket/nsSOCKSSocketProvider.cpp98
-rw-r--r--netwerk/socket/nsSOCKSSocketProvider.h28
-rw-r--r--netwerk/socket/nsSocketProviderService.cpp72
-rw-r--r--netwerk/socket/nsSocketProviderService.h26
-rw-r--r--netwerk/socket/nsUDPSocketProvider.cpp39
-rw-r--r--netwerk/socket/nsUDPSocketProvider.h20
-rw-r--r--netwerk/streamconv/converters/moz.build31
-rw-r--r--netwerk/streamconv/converters/mozTXTToHTMLConv.cpp1291
-rw-r--r--netwerk/streamconv/converters/mozTXTToHTMLConv.h286
-rw-r--r--netwerk/streamconv/converters/nsDirIndex.cpp62
-rw-r--r--netwerk/streamconv/converters/nsDirIndex.h32
-rw-r--r--netwerk/streamconv/converters/nsDirIndexParser.cpp266
-rw-r--r--netwerk/streamconv/converters/nsDirIndexParser.h65
-rw-r--r--netwerk/streamconv/converters/nsHTTPCompressConv.cpp767
-rw-r--r--netwerk/streamconv/converters/nsHTTPCompressConv.h109
-rw-r--r--netwerk/streamconv/converters/nsICompressConvStats.idl17
-rw-r--r--netwerk/streamconv/converters/nsIndexedToHTML.cpp825
-rw-r--r--netwerk/streamconv/converters/nsIndexedToHTML.h61
-rw-r--r--netwerk/streamconv/converters/nsMultiMixedConv.cpp1051
-rw-r--r--netwerk/streamconv/converters/nsMultiMixedConv.h259
-rw-r--r--netwerk/streamconv/converters/nsUnknownDecoder.cpp867
-rw-r--r--netwerk/streamconv/converters/nsUnknownDecoder.h150
-rw-r--r--netwerk/streamconv/moz.build24
-rw-r--r--netwerk/streamconv/mozITXTToHTMLConv.idl88
-rw-r--r--netwerk/streamconv/nsIDirIndex.idl59
-rw-r--r--netwerk/streamconv/nsIDirIndexListener.idl36
-rw-r--r--netwerk/streamconv/nsIStreamConverter.idl113
-rw-r--r--netwerk/streamconv/nsIStreamConverterService.idl89
-rw-r--r--netwerk/streamconv/nsITXTToHTMLConv.idl25
-rw-r--r--netwerk/streamconv/nsStreamConverterService.cpp543
-rw-r--r--netwerk/streamconv/nsStreamConverterService.h46
-rw-r--r--netwerk/system/LinkServiceCommon.cpp30
-rw-r--r--netwerk/system/LinkServiceCommon.h17
-rw-r--r--netwerk/system/NetworkLinkServiceDefines.h21
-rw-r--r--netwerk/system/android/moz.build14
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.cpp243
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.h48
-rw-r--r--netwerk/system/linux/moz.build16
-rw-r--r--netwerk/system/linux/nsNetworkLinkService.cpp211
-rw-r--r--netwerk/system/linux/nsNetworkLinkService.h47
-rw-r--r--netwerk/system/mac/moz.build11
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.h88
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.mm993
-rw-r--r--netwerk/system/moz.build23
-rw-r--r--netwerk/system/netlink/NetlinkService.cpp1903
-rw-r--r--netwerk/system/netlink/NetlinkService.h168
-rw-r--r--netwerk/system/netlink/moz.build12
-rw-r--r--netwerk/system/win32/moz.build16
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.cpp736
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.h99
-rw-r--r--netwerk/test/browser/103_preload.html6
-rw-r--r--netwerk/test/browser/103_preload.html^headers^1
-rw-r--r--netwerk/test/browser/103_preload.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_anchor.html6
-rw-r--r--netwerk/test/browser/103_preload_anchor.html^headers^1
-rw-r--r--netwerk/test/browser/103_preload_anchor.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_and_404.html6
-rw-r--r--netwerk/test/browser/103_preload_and_404.html^headers^1
-rw-r--r--netwerk/test/browser/103_preload_and_404.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_csp_imgsrc_none.html6
-rw-r--r--netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^2
-rw-r--r--netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_iframe.html6
-rw-r--r--netwerk/test/browser/103_preload_iframe.html^headers^1
-rw-r--r--netwerk/test/browser/auth_post.sjs37
-rw-r--r--netwerk/test/browser/browser.toml188
-rw-r--r--netwerk/test/browser/browser_103_assets.js169
-rw-r--r--netwerk/test/browser/browser_103_assets_extension.js78
-rw-r--r--netwerk/test/browser/browser_103_cleanup.js47
-rw-r--r--netwerk/test/browser/browser_103_csp.js86
-rw-r--r--netwerk/test/browser/browser_103_csp_images.js170
-rw-r--r--netwerk/test/browser/browser_103_csp_styles.js86
-rw-r--r--netwerk/test/browser/browser_103_error.js121
-rw-r--r--netwerk/test/browser/browser_103_no_cancel_on_error.js67
-rw-r--r--netwerk/test/browser/browser_103_preconnect.js71
-rw-r--r--netwerk/test/browser/browser_103_preload.js138
-rw-r--r--netwerk/test/browser/browser_103_preload_2.js180
-rw-r--r--netwerk/test/browser/browser_103_private_window.js70
-rw-r--r--netwerk/test/browser/browser_103_redirect.js52
-rw-r--r--netwerk/test/browser/browser_103_redirect_from_server.js321
-rw-r--r--netwerk/test/browser/browser_103_referrer_policy.js167
-rw-r--r--netwerk/test/browser/browser_103_telemetry.js107
-rw-r--r--netwerk/test/browser/browser_103_user_load.js85
-rw-r--r--netwerk/test/browser/browser_NetUtil.js111
-rw-r--r--netwerk/test/browser/browser_about_cache.js136
-rw-r--r--netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js40
-rw-r--r--netwerk/test/browser/browser_bug1535877.js15
-rw-r--r--netwerk/test/browser/browser_bug1629307.js81
-rw-r--r--netwerk/test/browser/browser_child_resource.js246
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_basic.js184
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_cross_origin.js146
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_insecure.js106
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_oa.js190
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_subdomain.js175
-rw-r--r--netwerk/test/browser/browser_cookie_sync_across_tabs.js79
-rw-r--r--netwerk/test/browser/browser_fetch_lnk.js23
-rw-r--r--netwerk/test/browser/browser_http_index_format.js48
-rw-r--r--netwerk/test/browser/browser_nsIFormPOSTActionChannel.js273
-rw-r--r--netwerk/test/browser/browser_post_auth.js65
-rw-r--r--netwerk/test/browser/browser_post_file.js71
-rw-r--r--netwerk/test/browser/browser_purgeCache_idle_daily.js86
-rw-r--r--netwerk/test/browser/browser_resource_navigation.js76
-rw-r--r--netwerk/test/browser/browser_speculative_connection_link_header.js57
-rw-r--r--netwerk/test/browser/browser_test_data_channel_observer.js35
-rw-r--r--netwerk/test/browser/browser_test_favicon.js26
-rw-r--r--netwerk/test/browser/browser_test_io_activity.js50
-rw-r--r--netwerk/test/browser/browser_test_offline_tab.js36
-rw-r--r--netwerk/test/browser/cookie_filtering_helper.sys.mjs166
-rw-r--r--netwerk/test/browser/cookie_filtering_resource.sjs35
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_com.html6
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^2
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_org.html6
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^2
-rw-r--r--netwerk/test/browser/cookie_filtering_square.png0
-rw-r--r--netwerk/test/browser/cookie_filtering_square.png^headers^2
-rw-r--r--netwerk/test/browser/damonbowling.jpgbin0 -> 44008 bytes
-rw-r--r--netwerk/test/browser/damonbowling.jpg^headers^2
-rw-r--r--netwerk/test/browser/dummy.html11
-rw-r--r--netwerk/test/browser/early_hint_asset.sjs50
-rw-r--r--netwerk/test/browser/early_hint_asset_html.sjs135
-rw-r--r--netwerk/test/browser/early_hint_csp_options_html.sjs120
-rw-r--r--netwerk/test/browser/early_hint_error.sjs35
-rw-r--r--netwerk/test/browser/early_hint_main_html.sjs62
-rw-r--r--netwerk/test/browser/early_hint_main_redirect.sjs67
-rw-r--r--netwerk/test/browser/early_hint_pixel.sjs37
-rw-r--r--netwerk/test/browser/early_hint_pixel_count.sjs9
-rw-r--r--netwerk/test/browser/early_hint_preconnect_html.sjs32
-rw-r--r--netwerk/test/browser/early_hint_preload_test_helper.sys.mjs159
-rw-r--r--netwerk/test/browser/early_hint_redirect.sjs21
-rw-r--r--netwerk/test/browser/early_hint_redirect_html.sjs24
-rw-r--r--netwerk/test/browser/early_hint_referrer_policy_html.sjs132
-rw-r--r--netwerk/test/browser/file_favicon.html7
-rw-r--r--netwerk/test/browser/file_link_header.sjs24
-rw-r--r--netwerk/test/browser/file_lnk.lnkbin0 -> 1531 bytes
-rw-r--r--netwerk/test/browser/ioactivity.html11
-rw-r--r--netwerk/test/browser/no_103_preload.html6
-rw-r--r--netwerk/test/browser/no_103_preload.html^headers^1
-rw-r--r--netwerk/test/browser/post.html14
-rw-r--r--netwerk/test/browser/redirect.sjs6
-rw-r--r--netwerk/test/browser/res.css4
-rw-r--r--netwerk/test/browser/res.css^headers^1
-rw-r--r--netwerk/test/browser/res.csv1
-rw-r--r--netwerk/test/browser/res.csv^headers^1
-rw-r--r--netwerk/test/browser/res.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res.unknown1
-rw-r--r--netwerk/test/browser/res_206.html11
-rw-r--r--netwerk/test/browser/res_206.html^headers^2
-rw-r--r--netwerk/test/browser/res_206.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res_206.mp3^headers^1
-rw-r--r--netwerk/test/browser/res_empty.zipbin0 -> 166 bytes
-rw-r--r--netwerk/test/browser/res_http_index_format1
-rw-r--r--netwerk/test/browser/res_http_index_format^headers^1
-rw-r--r--netwerk/test/browser/res_img.pngbin0 -> 129497 bytes
-rw-r--r--netwerk/test/browser/res_img_for_unknown_decoderbin0 -> 4421 bytes
-rw-r--r--netwerk/test/browser/res_img_for_unknown_decoder^headers^2
-rw-r--r--netwerk/test/browser/res_img_unknown.png11
-rw-r--r--netwerk/test/browser/res_invalid_partial.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res_invalid_partial.mp3^headers^2
-rw-r--r--netwerk/test/browser/res_nosniff.html11
-rw-r--r--netwerk/test/browser/res_nosniff.html^headers^2
-rw-r--r--netwerk/test/browser/res_nosniff2.html11
-rw-r--r--netwerk/test/browser/res_nosniff2.html^headers^2
-rw-r--r--netwerk/test/browser/res_not_200or206.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res_not_200or206.mp3^headers^1
-rw-r--r--netwerk/test/browser/res_not_ok.html11
-rw-r--r--netwerk/test/browser/res_not_ok.html^headers^1
-rw-r--r--netwerk/test/browser/res_object.html18
-rw-r--r--netwerk/test/browser/res_sub_document.html11
-rw-r--r--netwerk/test/browser/square.png0
-rw-r--r--netwerk/test/browser/test_1629307.html9
-rw-r--r--netwerk/test/browser/x_frame_options.html0
-rw-r--r--netwerk/test/browser/x_frame_options.html^headers^3
-rw-r--r--netwerk/test/crashtests/1274044-1.html7
-rw-r--r--netwerk/test/crashtests/1334468-1.html25
-rw-r--r--netwerk/test/crashtests/1399467-1.html1
-rw-r--r--netwerk/test/crashtests/1787122.html15
-rw-r--r--netwerk/test/crashtests/1793521.html1
-rw-r--r--netwerk/test/crashtests/675518.html21
-rw-r--r--netwerk/test/crashtests/785753-1.html253
-rw-r--r--netwerk/test/crashtests/785753-2.html3
-rw-r--r--netwerk/test/crashtests/crashtests.list8
-rw-r--r--netwerk/test/fuzz/FuzzingStreamListener.cpp44
-rw-r--r--netwerk/test/fuzz/FuzzingStreamListener.h37
-rw-r--r--netwerk/test/fuzz/TestHttpFuzzing.cpp297
-rw-r--r--netwerk/test/fuzz/TestJARFuzzing.cpp187
-rw-r--r--netwerk/test/fuzz/TestURIFuzzing.cpp238
-rw-r--r--netwerk/test/fuzz/TestWebsocketFuzzing.cpp229
-rw-r--r--netwerk/test/fuzz/moz.build32
-rw-r--r--netwerk/test/fuzz/url_tokens.dict51
-rw-r--r--netwerk/test/gtest/TestBase64Stream.cpp123
-rw-r--r--netwerk/test/gtest/TestBind.cpp187
-rw-r--r--netwerk/test/gtest/TestBufferedInputStream.cpp252
-rw-r--r--netwerk/test/gtest/TestCommon.cpp7
-rw-r--r--netwerk/test/gtest/TestCommon.h45
-rw-r--r--netwerk/test/gtest/TestCookie.cpp1126
-rw-r--r--netwerk/test/gtest/TestDNSPacket.cpp69
-rw-r--r--netwerk/test/gtest/TestHeaders.cpp29
-rw-r--r--netwerk/test/gtest/TestHttpAtom.cpp39
-rw-r--r--netwerk/test/gtest/TestHttpAuthUtils.cpp43
-rw-r--r--netwerk/test/gtest/TestHttpChannel.cpp135
-rw-r--r--netwerk/test/gtest/TestHttpResponseHead.cpp183
-rw-r--r--netwerk/test/gtest/TestInputStreamTransport.cpp204
-rw-r--r--netwerk/test/gtest/TestIsValidIp.cpp178
-rw-r--r--netwerk/test/gtest/TestLinkHeader.cpp356
-rw-r--r--netwerk/test/gtest/TestMIMEInputStream.cpp268
-rw-r--r--netwerk/test/gtest/TestMozURL.cpp392
-rw-r--r--netwerk/test/gtest/TestNamedPipeService.cpp281
-rw-r--r--netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp93
-rw-r--r--netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp88
-rw-r--r--netwerk/test/gtest/TestPACMan.cpp246
-rw-r--r--netwerk/test/gtest/TestProtocolProxyService.cpp164
-rw-r--r--netwerk/test/gtest/TestReadStreamToString.cpp190
-rw-r--r--netwerk/test/gtest/TestSSLTokensCache.cpp154
-rw-r--r--netwerk/test/gtest/TestServerTimingHeader.cpp238
-rw-r--r--netwerk/test/gtest/TestSocketTransportService.cpp164
-rw-r--r--netwerk/test/gtest/TestStandardURL.cpp441
-rw-r--r--netwerk/test/gtest/TestUDPSocket.cpp410
-rw-r--r--netwerk/test/gtest/TestURIMutator.cpp163
-rw-r--r--netwerk/test/gtest/moz.build80
-rw-r--r--netwerk/test/gtest/urltestdata-orig.json6148
-rw-r--r--netwerk/test/gtest/urltestdata.json9050
-rw-r--r--netwerk/test/http3server/Cargo.toml35
-rw-r--r--netwerk/test/http3server/moz.build18
-rw-r--r--netwerk/test/http3server/src/main.rs1386
-rw-r--r--netwerk/test/http3serverDB/cert9.dbbin0 -> 229376 bytes
-rw-r--r--netwerk/test/http3serverDB/key4.dbbin0 -> 294912 bytes
-rw-r--r--netwerk/test/http3serverDB/pkcs11.txt4
-rw-r--r--netwerk/test/httpserver/README101
-rw-r--r--netwerk/test/httpserver/TODO17
-rw-r--r--netwerk/test/httpserver/httpd.sys.mjs5576
-rw-r--r--netwerk/test/httpserver/moz.build21
-rw-r--r--netwerk/test/httpserver/nsIHttpServer.idl649
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^3
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_both.html2
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^2
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt9
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override.html9
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/bar.html^^10
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^2
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^1
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^1
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt2
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/foo.html^9
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/normal-file.txt1
-rw-r--r--netwerk/test/httpserver/test/data/ranges/empty.txt0
-rw-r--r--netwerk/test/httpserver/test/data/ranges/headers.txt1
-rw-r--r--netwerk/test/httpserver/test/data/ranges/headers.txt^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/ranges/range.txt1
-rw-r--r--netwerk/test/httpserver/test/data/sjs/cgi.sjs8
-rw-r--r--netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^2
-rw-r--r--netwerk/test/httpserver/test/data/sjs/object-state.sjs74
-rw-r--r--netwerk/test/httpserver/test/data/sjs/qi.sjs45
-rw-r--r--netwerk/test/httpserver/test/data/sjs/range-checker.sjs1
-rw-r--r--netwerk/test/httpserver/test/data/sjs/sjs4
-rw-r--r--netwerk/test/httpserver/test/data/sjs/state1.sjs38
-rw-r--r--netwerk/test/httpserver/test/data/sjs/state2.sjs38
-rw-r--r--netwerk/test/httpserver/test/data/sjs/thrower.sjs6
-rw-r--r--netwerk/test/httpserver/test/head_utils.js605
-rw-r--r--netwerk/test/httpserver/test/test_async_response_sending.js1661
-rw-r--r--netwerk/test/httpserver/test/test_basic_functionality.js182
-rw-r--r--netwerk/test/httpserver/test/test_body_length.js68
-rw-r--r--netwerk/test/httpserver/test/test_byte_range.js272
-rw-r--r--netwerk/test/httpserver/test/test_cern_meta.js79
-rw-r--r--netwerk/test/httpserver/test/test_default_index_handler.js248
-rw-r--r--netwerk/test/httpserver/test/test_empty_body.js59
-rw-r--r--netwerk/test/httpserver/test/test_errorhandler_exception.js95
-rw-r--r--netwerk/test/httpserver/test/test_header_array.js66
-rw-r--r--netwerk/test/httpserver/test/test_headers.js169
-rw-r--r--netwerk/test/httpserver/test/test_host.js608
-rw-r--r--netwerk/test/httpserver/test/test_host_identity.js115
-rw-r--r--netwerk/test/httpserver/test/test_linedata.js22
-rw-r--r--netwerk/test/httpserver/test/test_load_module.js18
-rw-r--r--netwerk/test/httpserver/test/test_name_scheme.js91
-rw-r--r--netwerk/test/httpserver/test/test_processasync.js272
-rw-r--r--netwerk/test/httpserver/test/test_qi.js107
-rw-r--r--netwerk/test/httpserver/test/test_registerdirectory.js278
-rw-r--r--netwerk/test/httpserver/test/test_registerfile.js44
-rw-r--r--netwerk/test/httpserver/test/test_registerprefix.js130
-rw-r--r--netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js137
-rw-r--r--netwerk/test/httpserver/test/test_response_write.js57
-rw-r--r--netwerk/test/httpserver/test/test_seizepower.js180
-rw-r--r--netwerk/test/httpserver/test/test_setindexhandler.js60
-rw-r--r--netwerk/test/httpserver/test/test_setstatusline.js142
-rw-r--r--netwerk/test/httpserver/test/test_sjs.js243
-rw-r--r--netwerk/test/httpserver/test/test_sjs_object_state.js305
-rw-r--r--netwerk/test/httpserver/test/test_sjs_state.js203
-rw-r--r--netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js73
-rw-r--r--netwerk/test/httpserver/test/test_start_stop.js166
-rw-r--r--netwerk/test/httpserver/test/test_start_stop_ipv6.js166
-rw-r--r--netwerk/test/httpserver/test/xpcshell.toml68
-rw-r--r--netwerk/test/marionette/manifest.toml3
-rw-r--r--netwerk/test/marionette/test_purge_http_cache_at_shutdown.py125
-rw-r--r--netwerk/test/mochitests/beltzner.jpgbin0 -> 9995 bytes
-rw-r--r--netwerk/test/mochitests/beltzner.jpg^headers^3
-rw-r--r--netwerk/test/mochitests/empty.html16
-rw-r--r--netwerk/test/mochitests/file_1331680.js24
-rw-r--r--netwerk/test/mochitests/file_1502055.sjs17
-rw-r--r--netwerk/test/mochitests/file_1503201.sjs4
-rw-r--r--netwerk/test/mochitests/file_chromecommon.js15
-rw-r--r--netwerk/test/mochitests/file_documentcookie_maxage_chromescript.js45
-rw-r--r--netwerk/test/mochitests/file_domain_hierarchy_inner.html13
-rw-r--r--netwerk/test/mochitests/file_domain_hierarchy_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html13
-rw-r--r--netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html13
-rw-r--r--netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html^headers^1
-rw-r--r--netwerk/test/mochitests/file_domain_inner.html13
-rw-r--r--netwerk/test/mochitests/file_domain_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_domain_inner_inner.html13
-rw-r--r--netwerk/test/mochitests/file_domain_inner_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_iframe_allow_same_origin.html8
-rw-r--r--netwerk/test/mochitests/file_iframe_allow_scripts.html6
-rw-r--r--netwerk/test/mochitests/file_image_inner.html14
-rw-r--r--netwerk/test/mochitests/file_image_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_image_inner_inner.html19
-rw-r--r--netwerk/test/mochitests/file_image_inner_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_lnk.lnkbin0 -> 1531 bytes
-rw-r--r--netwerk/test/mochitests/file_loadflags_inner.html16
-rw-r--r--netwerk/test/mochitests/file_loadflags_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_loadinfo_redirectchain.sjs109
-rw-r--r--netwerk/test/mochitests/file_localhost_inner.html13
-rw-r--r--netwerk/test/mochitests/file_localhost_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_loopback_inner.html13
-rw-r--r--netwerk/test/mochitests/file_loopback_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_subdomain_inner.html13
-rw-r--r--netwerk/test/mochitests/file_subdomain_inner.html^headers^4
-rw-r--r--netwerk/test/mochitests/file_testcommon.js84
-rw-r--r--netwerk/test/mochitests/file_testloadflags.js130
-rw-r--r--netwerk/test/mochitests/file_testloadflags_chromescript.js144
-rw-r--r--netwerk/test/mochitests/iframe_1502055.html33
-rw-r--r--netwerk/test/mochitests/image1.pngbin0 -> 821 bytes
-rw-r--r--netwerk/test/mochitests/image1.png^headers^3
-rw-r--r--netwerk/test/mochitests/image2.pngbin0 -> 821 bytes
-rw-r--r--netwerk/test/mochitests/image2.png^headers^3
-rw-r--r--netwerk/test/mochitests/method.sjs9
-rw-r--r--netwerk/test/mochitests/mochitest.toml217
-rw-r--r--netwerk/test/mochitests/origin_header.sjs10
-rw-r--r--netwerk/test/mochitests/origin_header_form_post.html19
-rw-r--r--netwerk/test/mochitests/origin_header_form_post_xorigin.html19
-rw-r--r--netwerk/test/mochitests/partial_content.sjs193
-rw-r--r--netwerk/test/mochitests/redirect.sjs4
-rw-r--r--netwerk/test/mochitests/redirect_idn.html0
-rw-r--r--netwerk/test/mochitests/redirect_idn.html^headers^3
-rw-r--r--netwerk/test/mochitests/redirect_to.sjs4
-rw-r--r--netwerk/test/mochitests/rel_preconnect.sjs17
-rw-r--r--netwerk/test/mochitests/reset_cookie_xhr.sjs15
-rw-r--r--netwerk/test/mochitests/set_cookie_xhr.sjs15
-rw-r--r--netwerk/test/mochitests/signed_web_packaged_app.sjs73
-rw-r--r--netwerk/test/mochitests/subResources.sjs78
-rw-r--r--netwerk/test/mochitests/sw_1502055.js1
-rw-r--r--netwerk/test/mochitests/test1.css2
-rw-r--r--netwerk/test/mochitests/test1.css^headers^3
-rw-r--r--netwerk/test/mochitests/test2.css2
-rw-r--r--netwerk/test/mochitests/test2.css^headers^3
-rw-r--r--netwerk/test/mochitests/test_1331680.html87
-rw-r--r--netwerk/test/mochitests/test_1331680_iframe.html68
-rw-r--r--netwerk/test/mochitests/test_1331680_xhr.html90
-rw-r--r--netwerk/test/mochitests/test_1396395.html48
-rw-r--r--netwerk/test/mochitests/test_1421324.html88
-rw-r--r--netwerk/test/mochitests/test_1425031.html74
-rw-r--r--netwerk/test/mochitests/test_1502055.html49
-rw-r--r--netwerk/test/mochitests/test_1503201.html28
-rw-r--r--netwerk/test/mochitests/test_accept_header.html87
-rw-r--r--netwerk/test/mochitests/test_accept_header.sjs62
-rw-r--r--netwerk/test/mochitests/test_arraybufferinputstream.html89
-rw-r--r--netwerk/test/mochitests/test_arraybufferinputstream_large.html45
-rw-r--r--netwerk/test/mochitests/test_different_domain_in_hierarchy.html15
-rw-r--r--netwerk/test/mochitests/test_differentdomain.html15
-rw-r--r--netwerk/test/mochitests/test_documentcookies_maxage.html149
-rw-r--r--netwerk/test/mochitests/test_fetch_lnk.html17
-rw-r--r--netwerk/test/mochitests/test_idn_redirect.html35
-rw-r--r--netwerk/test/mochitests/test_image.html14
-rw-r--r--netwerk/test/mochitests/test_loadflags.html21
-rw-r--r--netwerk/test/mochitests/test_loadinfo_redirectchain.html269
-rw-r--r--netwerk/test/mochitests/test_origin_header.html398
-rw-r--r--netwerk/test/mochitests/test_partially_cached_content.html95
-rw-r--r--netwerk/test/mochitests/test_redirect_ref.html29
-rw-r--r--netwerk/test/mochitests/test_rel_preconnect.html61
-rw-r--r--netwerk/test/mochitests/test_same_base_domain.html15
-rw-r--r--netwerk/test/mochitests/test_same_base_domain_2.html15
-rw-r--r--netwerk/test/mochitests/test_same_base_domain_3.html15
-rw-r--r--netwerk/test/mochitests/test_same_base_domain_4.html15
-rw-r--r--netwerk/test/mochitests/test_same_base_domain_5.html15
-rw-r--r--netwerk/test/mochitests/test_same_base_domain_6.html26
-rw-r--r--netwerk/test/mochitests/test_samedomain.html15
-rw-r--r--netwerk/test/mochitests/test_uri_scheme.html50
-rw-r--r--netwerk/test/mochitests/test_url_perf.html75
-rw-r--r--netwerk/test/mochitests/test_viewsource_unlinkable.html25
-rw-r--r--netwerk/test/mochitests/test_xhr_method_case.html65
-rw-r--r--netwerk/test/mochitests/web_packaged_app.sjs47
-rw-r--r--netwerk/test/moz.build33
-rw-r--r--netwerk/test/perf/.eslintrc.js12
-rw-r--r--netwerk/test/perf/hooks_throttling.py202
-rw-r--r--netwerk/test/perf/perftest.toml17
-rw-r--r--netwerk/test/perf/perftest_http3_cloudflareblog.js56
-rw-r--r--netwerk/test/perf/perftest_http3_controlled.js32
-rw-r--r--netwerk/test/perf/perftest_http3_facebook_scroll.js165
-rw-r--r--netwerk/test/perf/perftest_http3_google_image.js190
-rw-r--r--netwerk/test/perf/perftest_http3_google_search.js73
-rw-r--r--netwerk/test/perf/perftest_http3_lucasquicfetch.js134
-rw-r--r--netwerk/test/perf/perftest_http3_youtube_watch.js74
-rw-r--r--netwerk/test/perf/perftest_http3_youtube_watch_scroll.js86
-rw-r--r--netwerk/test/reftest/658949-1-ref.html1
-rw-r--r--netwerk/test/reftest/658949-1.html1
-rw-r--r--netwerk/test/reftest/bug565432-1-ref.html11
-rw-r--r--netwerk/test/reftest/bug565432-1.html17
-rw-r--r--netwerk/test/reftest/reftest.list2
-rw-r--r--netwerk/test/unit/client-cert.p12bin0 -> 2333 bytes
-rw-r--r--netwerk/test/unit/client-cert.p12.pkcs12spec3
-rw-r--r--netwerk/test/unit/data/cookies_v10.sqlitebin0 -> 131072 bytes
-rw-r--r--netwerk/test/unit/data/image.pngbin0 -> 102591 bytes
-rw-r--r--netwerk/test/unit/data/signed_win.exebin0 -> 61064 bytes
-rw-r--r--netwerk/test/unit/data/system_root.lnkbin0 -> 1677 bytes
-rw-r--r--netwerk/test/unit/data/test_psl.txt98
-rw-r--r--netwerk/test/unit/data/test_readline1.txt0
-rw-r--r--netwerk/test/unit/data/test_readline2.txt1
-rw-r--r--netwerk/test/unit/data/test_readline3.txt3
-rw-r--r--netwerk/test/unit/data/test_readline4.txt3
-rw-r--r--netwerk/test/unit/data/test_readline5.txt1
-rw-r--r--netwerk/test/unit/data/test_readline6.txt1
-rw-r--r--netwerk/test/unit/data/test_readline7.txt2
-rw-r--r--netwerk/test/unit/data/test_readline8.txt1
-rw-r--r--netwerk/test/unit/head_cache.js137
-rw-r--r--netwerk/test/unit/head_cache2.js429
-rw-r--r--netwerk/test/unit/head_channels.js527
-rw-r--r--netwerk/test/unit/head_cookies.js1062
-rw-r--r--netwerk/test/unit/head_http3.js105
-rw-r--r--netwerk/test/unit/head_servers.js925
-rw-r--r--netwerk/test/unit/head_telemetry.js171
-rw-r--r--netwerk/test/unit/head_trr.js550
-rw-r--r--netwerk/test/unit/head_websocket.js71
-rw-r--r--netwerk/test/unit/head_webtransport.js134
-rw-r--r--netwerk/test/unit/http2-ca.pem18
-rw-r--r--netwerk/test/unit/http2-ca.pem.certspec4
-rw-r--r--netwerk/test/unit/http2_test_common.js1504
-rw-r--r--netwerk/test/unit/node_execute/test_node_execute_loop.js22
-rw-r--r--netwerk/test/unit/node_execute/xpcshell.toml5
-rw-r--r--netwerk/test/unit/perftest.toml3
-rw-r--r--netwerk/test/unit/proxy-ca.pem18
-rw-r--r--netwerk/test/unit/proxy-ca.pem.certspec4
-rw-r--r--netwerk/test/unit/socks_client_subprocess.js94
-rw-r--r--netwerk/test/unit/test_1073747.js42
-rw-r--r--netwerk/test/unit/test_304_headers.js91
-rw-r--r--netwerk/test/unit/test_304_responses.js92
-rw-r--r--netwerk/test/unit/test_307_redirect.js95
-rw-r--r--netwerk/test/unit/test_421.js65
-rw-r--r--netwerk/test/unit/test_MIME_params.js798
-rw-r--r--netwerk/test/unit/test_NetUtil.js804
-rw-r--r--netwerk/test/unit/test_SuperfluousAuth.js101
-rw-r--r--netwerk/test/unit/test_URIs.js996
-rw-r--r--netwerk/test/unit/test_URIs2.js881
-rw-r--r--netwerk/test/unit/test_XHR_redirects.js275
-rw-r--r--netwerk/test/unit/test_about_networking.js120
-rw-r--r--netwerk/test/unit/test_about_protocol.js49
-rw-r--r--netwerk/test/unit/test_aboutblank.js32
-rw-r--r--netwerk/test/unit/test_addr_in_use_error.js36
-rw-r--r--netwerk/test/unit/test_alt-data_closeWithStatus.js184
-rw-r--r--netwerk/test/unit/test_alt-data_cross_process.js153
-rw-r--r--netwerk/test/unit/test_alt-data_overwrite.js209
-rw-r--r--netwerk/test/unit/test_alt-data_simple.js212
-rw-r--r--netwerk/test/unit/test_alt-data_stream.js163
-rw-r--r--netwerk/test/unit/test_alt-data_too_big.js113
-rw-r--r--netwerk/test/unit/test_altsvc.js597
-rw-r--r--netwerk/test/unit/test_altsvc_http3.js494
-rw-r--r--netwerk/test/unit/test_altsvc_pref.js136
-rw-r--r--netwerk/test/unit/test_anonymous-coalescing.js179
-rw-r--r--netwerk/test/unit/test_auth_dialog_permission.js280
-rw-r--r--netwerk/test/unit/test_auth_jar.js92
-rw-r--r--netwerk/test/unit/test_auth_multiple.js464
-rw-r--r--netwerk/test/unit/test_auth_proxy.js463
-rw-r--r--netwerk/test/unit/test_authentication.js1400
-rw-r--r--netwerk/test/unit/test_authpromptwrapper.js207
-rw-r--r--netwerk/test/unit/test_backgroundfilesaver.js761
-rw-r--r--netwerk/test/unit/test_be_conservative.js256
-rw-r--r--netwerk/test/unit/test_be_conservative_error_handling.js216
-rw-r--r--netwerk/test/unit/test_bhttp.js220
-rw-r--r--netwerk/test/unit/test_blob_channelname.js42
-rw-r--r--netwerk/test/unit/test_brotli_decoding.js128
-rw-r--r--netwerk/test/unit/test_brotli_http.js122
-rw-r--r--netwerk/test/unit/test_brotli_unknown_content_type.js74
-rw-r--r--netwerk/test/unit/test_bug1064258.js144
-rw-r--r--netwerk/test/unit/test_bug1177909.js251
-rw-r--r--netwerk/test/unit/test_bug1195415.js116
-rw-r--r--netwerk/test/unit/test_bug1218029.js116
-rw-r--r--netwerk/test/unit/test_bug1279246.js100
-rw-r--r--netwerk/test/unit/test_bug1312774_http1.js149
-rw-r--r--netwerk/test/unit/test_bug1312782_http1.js197
-rw-r--r--netwerk/test/unit/test_bug1355539_http1.js206
-rw-r--r--netwerk/test/unit/test_bug1378385_http1.js198
-rw-r--r--netwerk/test/unit/test_bug1411316_http1.js116
-rw-r--r--netwerk/test/unit/test_bug1527293.js94
-rw-r--r--netwerk/test/unit/test_bug1683176.js91
-rw-r--r--netwerk/test/unit/test_bug1725766.js85
-rw-r--r--netwerk/test/unit/test_bug203271.js249
-rw-r--r--netwerk/test/unit/test_bug248970_cache.js144
-rw-r--r--netwerk/test/unit/test_bug248970_cookie.js148
-rw-r--r--netwerk/test/unit/test_bug261425.js29
-rw-r--r--netwerk/test/unit/test_bug263127.js58
-rw-r--r--netwerk/test/unit/test_bug282432.js37
-rw-r--r--netwerk/test/unit/test_bug321706.js10
-rw-r--r--netwerk/test/unit/test_bug331825.js43
-rw-r--r--netwerk/test/unit/test_bug336501.js26
-rw-r--r--netwerk/test/unit/test_bug337744.js126
-rw-r--r--netwerk/test/unit/test_bug368702.js147
-rw-r--r--netwerk/test/unit/test_bug369787.js73
-rw-r--r--netwerk/test/unit/test_bug371473.js36
-rw-r--r--netwerk/test/unit/test_bug376844.js19
-rw-r--r--netwerk/test/unit/test_bug376865.js23
-rw-r--r--netwerk/test/unit/test_bug379034.js19
-rw-r--r--netwerk/test/unit/test_bug380994.js24
-rw-r--r--netwerk/test/unit/test_bug388281.js25
-rw-r--r--netwerk/test/unit/test_bug396389.js63
-rw-r--r--netwerk/test/unit/test_bug401564.js42
-rw-r--r--netwerk/test/unit/test_bug411952.js53
-rw-r--r--netwerk/test/unit/test_bug412457.js79
-rw-r--r--netwerk/test/unit/test_bug412945.js44
-rw-r--r--netwerk/test/unit/test_bug414122.js59
-rw-r--r--netwerk/test/unit/test_bug427957.js100
-rw-r--r--netwerk/test/unit/test_bug429347.js39
-rw-r--r--netwerk/test/unit/test_bug455311.js128
-rw-r--r--netwerk/test/unit/test_bug464591.js94
-rw-r--r--netwerk/test/unit/test_bug468426.js131
-rw-r--r--netwerk/test/unit/test_bug468594.js178
-rw-r--r--netwerk/test/unit/test_bug470716.js171
-rw-r--r--netwerk/test/unit/test_bug477578.js51
-rw-r--r--netwerk/test/unit/test_bug479413.js48
-rw-r--r--netwerk/test/unit/test_bug479485.js65
-rw-r--r--netwerk/test/unit/test_bug482601.js264
-rw-r--r--netwerk/test/unit/test_bug482934.js190
-rw-r--r--netwerk/test/unit/test_bug490095.js160
-rw-r--r--netwerk/test/unit/test_bug504014.js72
-rw-r--r--netwerk/test/unit/test_bug510359.js104
-rw-r--r--netwerk/test/unit/test_bug526789.js289
-rw-r--r--netwerk/test/unit/test_bug528292.js87
-rw-r--r--netwerk/test/unit/test_bug536324_64bit_content_length.js66
-rw-r--r--netwerk/test/unit/test_bug540566.js24
-rw-r--r--netwerk/test/unit/test_bug553970.js50
-rw-r--r--netwerk/test/unit/test_bug561042.js44
-rw-r--r--netwerk/test/unit/test_bug561276.js66
-rw-r--r--netwerk/test/unit/test_bug580508.js31
-rw-r--r--netwerk/test/unit/test_bug586908.js103
-rw-r--r--netwerk/test/unit/test_bug596443.js117
-rw-r--r--netwerk/test/unit/test_bug618835.js124
-rw-r--r--netwerk/test/unit/test_bug633743.js195
-rw-r--r--netwerk/test/unit/test_bug650522.js35
-rw-r--r--netwerk/test/unit/test_bug650995.js187
-rw-r--r--netwerk/test/unit/test_bug652761.js19
-rw-r--r--netwerk/test/unit/test_bug654926.js91
-rw-r--r--netwerk/test/unit/test_bug654926_doom_and_read.js82
-rw-r--r--netwerk/test/unit/test_bug654926_test_seek.js76
-rw-r--r--netwerk/test/unit/test_bug659569.js60
-rw-r--r--netwerk/test/unit/test_bug660066.js53
-rw-r--r--netwerk/test/unit/test_bug667087.js33
-rw-r--r--netwerk/test/unit/test_bug667818.js49
-rw-r--r--netwerk/test/unit/test_bug667907.js88
-rw-r--r--netwerk/test/unit/test_bug669001.js178
-rw-r--r--netwerk/test/unit/test_bug770243.js246
-rw-r--r--netwerk/test/unit/test_bug812167.js143
-rw-r--r--netwerk/test/unit/test_bug826063.js88
-rw-r--r--netwerk/test/unit/test_bug856978.js138
-rw-r--r--netwerk/test/unit/test_bug894586.js135
-rw-r--r--netwerk/test/unit/test_bug935499.js10
-rw-r--r--netwerk/test/unit/test_cache-control_request.js448
-rw-r--r--netwerk/test/unit/test_cache-entry-id.js220
-rw-r--r--netwerk/test/unit/test_cache2-00-service-get.js15
-rw-r--r--netwerk/test/unit/test_cache2-01-basic.js45
-rw-r--r--netwerk/test/unit/test_cache2-01a-basic-readonly.js45
-rw-r--r--netwerk/test/unit/test_cache2-01b-basic-datasize.js51
-rw-r--r--netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js45
-rw-r--r--netwerk/test/unit/test_cache2-01d-basic-not-wanted.js45
-rw-r--r--netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js39
-rw-r--r--netwerk/test/unit/test_cache2-01f-basic-openTruncate.js24
-rw-r--r--netwerk/test/unit/test_cache2-02-open-non-existing.js45
-rw-r--r--netwerk/test/unit/test_cache2-02b-open-non-existing-and-doom.js180
-rw-r--r--netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js36
-rw-r--r--netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js45
-rw-r--r--netwerk/test/unit/test_cache2-05-visit.js113
-rw-r--r--netwerk/test/unit/test_cache2-06-pb-mode.js50
-rw-r--r--netwerk/test/unit/test_cache2-07-visit-memory.js123
-rw-r--r--netwerk/test/unit/test_cache2-07a-open-memory.js81
-rw-r--r--netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js25
-rw-r--r--netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js32
-rw-r--r--netwerk/test/unit/test_cache2-10-evict-direct.js29
-rw-r--r--netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js21
-rw-r--r--netwerk/test/unit/test_cache2-11-evict-memory.js89
-rw-r--r--netwerk/test/unit/test_cache2-12-evict-disk.js81
-rw-r--r--netwerk/test/unit/test_cache2-13-evict-non-existing.js16
-rw-r--r--netwerk/test/unit/test_cache2-14-concurent-readers.js48
-rw-r--r--netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js76
-rw-r--r--netwerk/test/unit/test_cache2-15-conditional-304.js60
-rw-r--r--netwerk/test/unit/test_cache2-16-conditional-200.js76
-rw-r--r--netwerk/test/unit/test_cache2-17-evict-all.js17
-rw-r--r--netwerk/test/unit/test_cache2-18-not-valid.js38
-rw-r--r--netwerk/test/unit/test_cache2-19-range-206.js65
-rw-r--r--netwerk/test/unit/test_cache2-20-range-200.js72
-rw-r--r--netwerk/test/unit/test_cache2-21-anon-storage.js52
-rw-r--r--netwerk/test/unit/test_cache2-22-anon-visit.js43
-rw-r--r--netwerk/test/unit/test_cache2-23-read-over-chunk.js34
-rw-r--r--netwerk/test/unit/test_cache2-24-exists.js43
-rw-r--r--netwerk/test/unit/test_cache2-25-chunk-memory-limit.js53
-rw-r--r--netwerk/test/unit/test_cache2-26-no-outputstream-open.js36
-rw-r--r--netwerk/test/unit/test_cache2-27-force-valid-for.js35
-rw-r--r--netwerk/test/unit/test_cache2-28-last-access-attrs.js46
-rw-r--r--netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js42
-rw-r--r--netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js76
-rw-r--r--netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js80
-rw-r--r--netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js95
-rw-r--r--netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js95
-rw-r--r--netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js90
-rw-r--r--netwerk/test/unit/test_cache2-30a-entry-pinning.js39
-rw-r--r--netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js45
-rw-r--r--netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js185
-rw-r--r--netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js148
-rw-r--r--netwerk/test/unit/test_cache2-31-visit-all.js88
-rw-r--r--netwerk/test/unit/test_cache2-32-clear-origin.js69
-rw-r--r--netwerk/test/unit/test_cache_204_response.js62
-rw-r--r--netwerk/test/unit/test_cache_jar.js105
-rw-r--r--netwerk/test/unit/test_cacheflags.js437
-rw-r--r--netwerk/test/unit/test_captive_portal_service.js325
-rw-r--r--netwerk/test/unit/test_cert_info.js162
-rw-r--r--netwerk/test/unit/test_cert_verification_failure.js66
-rw-r--r--netwerk/test/unit/test_channel_close.js70
-rw-r--r--netwerk/test/unit/test_channel_long_domain.js14
-rw-r--r--netwerk/test/unit/test_channel_priority.js98
-rw-r--r--netwerk/test/unit/test_chunked_responses.js180
-rw-r--r--netwerk/test/unit/test_client_auth_with_proxy.js176
-rw-r--r--netwerk/test/unit/test_coaleasing_h2_and_h3_connection.js109
-rw-r--r--netwerk/test/unit/test_compareURIs.js61
-rw-r--r--netwerk/test/unit/test_compressappend.js99
-rw-r--r--netwerk/test/unit/test_connection_based_auth.js91
-rw-r--r--netwerk/test/unit/test_content_encoding_gzip.js163
-rw-r--r--netwerk/test/unit/test_content_length_underrun.js295
-rw-r--r--netwerk/test/unit/test_content_sniffer.js157
-rw-r--r--netwerk/test/unit/test_cookie_blacklist.js43
-rw-r--r--netwerk/test/unit/test_cookie_header.js112
-rw-r--r--netwerk/test/unit/test_cookie_ipv6.js53
-rw-r--r--netwerk/test/unit/test_cookie_partitioned_attribute.js80
-rw-r--r--netwerk/test/unit/test_cookiejars.js188
-rw-r--r--netwerk/test/unit/test_cookiejars_safebrowsing.js231
-rw-r--r--netwerk/test/unit/test_cookies_async_failure.js514
-rw-r--r--netwerk/test/unit/test_cookies_partition_counting.js183
-rw-r--r--netwerk/test/unit/test_cookies_privatebrowsing.js132
-rw-r--r--netwerk/test/unit/test_cookies_profile_close.js114
-rw-r--r--netwerk/test/unit/test_cookies_purge_counting.js74
-rw-r--r--netwerk/test/unit/test_cookies_purge_counting_per_host.js81
-rw-r--r--netwerk/test/unit/test_cookies_read.js119
-rw-r--r--netwerk/test/unit/test_cookies_sync_failure.js344
-rw-r--r--netwerk/test/unit/test_cookies_thirdparty.js164
-rw-r--r--netwerk/test/unit/test_cookies_thirdparty_session.js77
-rw-r--r--netwerk/test/unit/test_cookies_upgrade_10.js61
-rw-r--r--netwerk/test/unit/test_data_protocol.js90
-rw-r--r--netwerk/test/unit/test_defaultURI.js186
-rw-r--r--netwerk/test/unit/test_dns_by_type_resolve.js83
-rw-r--r--netwerk/test/unit/test_dns_cancel.js119
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv4.js68
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv6.js51
-rw-r--r--netwerk/test/unit/test_dns_disabled.js88
-rw-r--r--netwerk/test/unit/test_dns_localredirect.js59
-rw-r--r--netwerk/test/unit/test_dns_offline.js105
-rw-r--r--netwerk/test/unit/test_dns_onion.js76
-rw-r--r--netwerk/test/unit/test_dns_originAttributes.js93
-rw-r--r--netwerk/test/unit/test_dns_override.js515
-rw-r--r--netwerk/test/unit/test_dns_override_for_localhost.js92
-rw-r--r--netwerk/test/unit/test_dns_proxy_bypass.js95
-rw-r--r--netwerk/test/unit/test_dns_retry.js319
-rw-r--r--netwerk/test/unit/test_dns_service.js123
-rw-r--r--netwerk/test/unit/test_domain_eviction.js182
-rw-r--r--netwerk/test/unit/test_dooh.js354
-rw-r--r--netwerk/test/unit/test_doomentry.js108
-rw-r--r--netwerk/test/unit/test_duplicate_headers.js563
-rw-r--r--netwerk/test/unit/test_early_hint_listener.js170
-rw-r--r--netwerk/test/unit/test_early_hint_listener_http2.js110
-rw-r--r--netwerk/test/unit/test_ech_grease.js270
-rw-r--r--netwerk/test/unit/test_event_sink.js183
-rw-r--r--netwerk/test/unit/test_eviction.js252
-rw-r--r--netwerk/test/unit/test_extract_charset_from_content_type.js238
-rw-r--r--netwerk/test/unit/test_file_protocol.js277
-rw-r--r--netwerk/test/unit/test_filestreams.js300
-rw-r--r--netwerk/test/unit/test_freshconnection.js30
-rw-r--r--netwerk/test/unit/test_getHost.js65
-rw-r--r--netwerk/test/unit/test_gio_protocol.js201
-rw-r--r--netwerk/test/unit/test_gre_resources.js30
-rw-r--r--netwerk/test/unit/test_h2proxy_connection_limit.js77
-rw-r--r--netwerk/test/unit/test_head.js171
-rw-r--r--netwerk/test/unit/test_head_request_no_response_body.js78
-rw-r--r--netwerk/test/unit/test_header_Accept-Language.js99
-rw-r--r--netwerk/test/unit/test_header_Accept-Language_case.js50
-rw-r--r--netwerk/test/unit/test_header_Server_Timing.js64
-rw-r--r--netwerk/test/unit/test_headers.js184
-rw-r--r--netwerk/test/unit/test_hostnameIsLocalIPAddress.js37
-rw-r--r--netwerk/test/unit/test_hostnameIsSharedIPAddress.js17
-rw-r--r--netwerk/test/unit/test_hpke_config_manager.js112
-rw-r--r--netwerk/test/unit/test_http1-proxy.js231
-rw-r--r--netwerk/test/unit/test_http2-proxy-failing.js174
-rw-r--r--netwerk/test/unit/test_http2-proxy.js862
-rw-r--r--netwerk/test/unit/test_http2.js481
-rw-r--r--netwerk/test/unit/test_http2_with_proxy.js425
-rw-r--r--netwerk/test/unit/test_http3.js571
-rw-r--r--netwerk/test/unit/test_http3_0rtt.js96
-rw-r--r--netwerk/test/unit/test_http3_421.js172
-rw-r--r--netwerk/test/unit/test_http3_alt_svc.js136
-rw-r--r--netwerk/test/unit/test_http3_coalescing.js113
-rw-r--r--netwerk/test/unit/test_http3_direct_proxy.js54
-rw-r--r--netwerk/test/unit/test_http3_dns_retry.js357
-rw-r--r--netwerk/test/unit/test_http3_early_hint_listener.js92
-rw-r--r--netwerk/test/unit/test_http3_error_before_connect.js109
-rw-r--r--netwerk/test/unit/test_http3_fast_fallback.js908
-rw-r--r--netwerk/test/unit/test_http3_fatal_stream_error.js135
-rw-r--r--netwerk/test/unit/test_http3_large_post.js165
-rw-r--r--netwerk/test/unit/test_http3_large_post_telemetry.js151
-rw-r--r--netwerk/test/unit/test_http3_perf.js262
-rw-r--r--netwerk/test/unit/test_http3_prio_disabled.js106
-rw-r--r--netwerk/test/unit/test_http3_prio_enabled.js108
-rw-r--r--netwerk/test/unit/test_http3_prio_helpers.js121
-rw-r--r--netwerk/test/unit/test_http3_server.js168
-rw-r--r--netwerk/test/unit/test_http3_server_not_existing.js109
-rw-r--r--netwerk/test/unit/test_http3_trans_close.js84
-rw-r--r--netwerk/test/unit/test_http3_version1.js93
-rw-r--r--netwerk/test/unit/test_httpResponseTimeout.js162
-rw-r--r--netwerk/test/unit/test_http_408_retry.js92
-rw-r--r--netwerk/test/unit/test_http_headers.js75
-rw-r--r--netwerk/test/unit/test_http_server_timing.js97
-rw-r--r--netwerk/test/unit/test_http_sfv.js597
-rw-r--r--netwerk/test/unit/test_httpauth.js204
-rw-r--r--netwerk/test/unit/test_httpcancel.js261
-rw-r--r--netwerk/test/unit/test_https_rr_ech_prefs.js535
-rw-r--r--netwerk/test/unit/test_https_rr_sorted_alpn.js226
-rw-r--r--netwerk/test/unit/test_httpssvc_ech_with_alpn.js246
-rw-r--r--netwerk/test/unit/test_httpssvc_https_upgrade.js352
-rw-r--r--netwerk/test/unit/test_httpssvc_iphint.js350
-rw-r--r--netwerk/test/unit/test_httpssvc_priority.js125
-rw-r--r--netwerk/test/unit/test_httpssvc_retry_with_ech.js511
-rw-r--r--netwerk/test/unit/test_httpssvc_retry_without_ech.js138
-rw-r--r--netwerk/test/unit/test_httpsuspend.js86
-rw-r--r--netwerk/test/unit/test_idn_blacklist.js168
-rw-r--r--netwerk/test/unit/test_idn_spoof.js1052
-rw-r--r--netwerk/test/unit/test_idn_urls.js436
-rw-r--r--netwerk/test/unit/test_idna2008.js65
-rw-r--r--netwerk/test/unit/test_idnservice.js39
-rw-r--r--netwerk/test/unit/test_immutable.js206
-rw-r--r--netwerk/test/unit/test_inhibit_caching.js94
-rw-r--r--netwerk/test/unit/test_ioservice.js19
-rw-r--r--netwerk/test/unit/test_large_port.js65
-rw-r--r--netwerk/test/unit/test_link.desktop3
-rw-r--r--netwerk/test/unit/test_link.lnkbin0 -> 345 bytes
-rw-r--r--netwerk/test/unit/test_link.url5
-rw-r--r--netwerk/test/unit/test_loadgroup_cancel.js96
-rw-r--r--netwerk/test/unit/test_localhost_offline.js77
-rw-r--r--netwerk/test/unit/test_localstreams.js89
-rw-r--r--netwerk/test/unit/test_mismatch_last-modified.js154
-rw-r--r--netwerk/test/unit/test_mozTXTToHTMLConv.js394
-rw-r--r--netwerk/test/unit/test_multipart_byteranges.js143
-rw-r--r--netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js115
-rw-r--r--netwerk/test/unit/test_multipart_streamconv.js100
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_empty.js68
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_missing_boundary_lead_dashes.js92
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js92
-rw-r--r--netwerk/test/unit/test_nestedabout_serialize.js39
-rw-r--r--netwerk/test/unit/test_net_addr.js216
-rw-r--r--netwerk/test/unit/test_network_connectivity_service.js215
-rw-r--r--netwerk/test/unit/test_networking_over_socket_process.js168
-rw-r--r--netwerk/test/unit/test_no_cookies_after_last_pb_exit.js135
-rw-r--r--netwerk/test/unit/test_node_execute.js87
-rw-r--r--netwerk/test/unit/test_nojsredir.js67
-rw-r--r--netwerk/test/unit/test_non_ipv4_hostname_ending_in_number_cookie_db.js128
-rw-r--r--netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js193
-rw-r--r--netwerk/test/unit/test_ntlm_authentication.js268
-rw-r--r--netwerk/test/unit/test_ntlm_proxy_and_web_auth.js362
-rw-r--r--netwerk/test/unit/test_ntlm_proxy_auth.js408
-rw-r--r--netwerk/test/unit/test_ntlm_web_auth.js251
-rw-r--r--netwerk/test/unit/test_oblivious_http.js206
-rw-r--r--netwerk/test/unit/test_obs-fold.js75
-rw-r--r--netwerk/test/unit/test_offline_status.js15
-rw-r--r--netwerk/test/unit/test_ohttp.js41
-rw-r--r--netwerk/test/unit/test_orb_empty_header.js83
-rw-r--r--netwerk/test/unit/test_origin.js323
-rw-r--r--netwerk/test/unit/test_original_sent_received_head.js249
-rw-r--r--netwerk/test/unit/test_pac_reload_after_network_change.js75
-rw-r--r--netwerk/test/unit/test_parse_content_type.js365
-rw-r--r--netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js106
-rw-r--r--netwerk/test/unit/test_permmgr.js125
-rw-r--r--netwerk/test/unit/test_ping_aboutnetworking.js103
-rw-r--r--netwerk/test/unit/test_plaintext_sniff.js211
-rw-r--r--netwerk/test/unit/test_port_remapping.js50
-rw-r--r--netwerk/test/unit/test_post.js141
-rw-r--r--netwerk/test/unit/test_predictor.js852
-rw-r--r--netwerk/test/unit/test_private_cookie_changed.js44
-rw-r--r--netwerk/test/unit/test_private_necko_channel.js60
-rw-r--r--netwerk/test/unit/test_progress.js145
-rw-r--r--netwerk/test/unit/test_progress_no_proxy_and_proxy.js205
-rw-r--r--netwerk/test/unit/test_protocolproxyservice-async-filters.js435
-rw-r--r--netwerk/test/unit/test_protocolproxyservice.js1071
-rw-r--r--netwerk/test/unit/test_proxy-failover_canceled.js57
-rw-r--r--netwerk/test/unit/test_proxy-failover_passing.js45
-rw-r--r--netwerk/test/unit/test_proxy-replace_canceled.js57
-rw-r--r--netwerk/test/unit/test_proxy-replace_passing.js45
-rw-r--r--netwerk/test/unit/test_proxy-slow-upload.js105
-rw-r--r--netwerk/test/unit/test_proxy_cancel.js397
-rw-r--r--netwerk/test/unit/test_proxy_pac.js126
-rw-r--r--netwerk/test/unit/test_proxyconnect.js360
-rw-r--r--netwerk/test/unit/test_psl.js39
-rw-r--r--netwerk/test/unit/test_race_cache_with_network.js273
-rw-r--r--netwerk/test/unit/test_range_requests.js443
-rw-r--r--netwerk/test/unit/test_rcwn_always_cache_new_content.js116
-rw-r--r--netwerk/test/unit/test_rcwn_interrupted.js108
-rw-r--r--netwerk/test/unit/test_readline.js104
-rw-r--r--netwerk/test/unit/test_redirect-caching_canceled.js64
-rw-r--r--netwerk/test/unit/test_redirect-caching_failure.js81
-rw-r--r--netwerk/test/unit/test_redirect-caching_passing.js56
-rw-r--r--netwerk/test/unit/test_redirect_baduri.js44
-rw-r--r--netwerk/test/unit/test_redirect_canceled.js50
-rw-r--r--netwerk/test/unit/test_redirect_different-protocol.js49
-rw-r--r--netwerk/test/unit/test_redirect_failure.js59
-rw-r--r--netwerk/test/unit/test_redirect_from_script.js247
-rw-r--r--netwerk/test/unit/test_redirect_from_script_after-open_passing.js247
-rw-r--r--netwerk/test/unit/test_redirect_history.js75
-rw-r--r--netwerk/test/unit/test_redirect_loop.js88
-rw-r--r--netwerk/test/unit/test_redirect_passing.js55
-rw-r--r--netwerk/test/unit/test_redirect_protocol_telemetry.js65
-rw-r--r--netwerk/test/unit/test_redirect_veto.js102
-rw-r--r--netwerk/test/unit/test_reentrancy.js109
-rw-r--r--netwerk/test/unit/test_referrer.js248
-rw-r--r--netwerk/test/unit/test_referrer_cross_origin.js332
-rw-r--r--netwerk/test/unit/test_referrer_policy.js154
-rw-r--r--netwerk/test/unit/test_reopen.js136
-rw-r--r--netwerk/test/unit/test_reply_without_content_type.js151
-rw-r--r--netwerk/test/unit/test_resumable_channel.js426
-rw-r--r--netwerk/test/unit/test_resumable_truncate.js95
-rw-r--r--netwerk/test/unit/test_retry_0rtt.js123
-rw-r--r--netwerk/test/unit/test_safeoutputstream.js70
-rw-r--r--netwerk/test/unit/test_safeoutputstream_append.js45
-rw-r--r--netwerk/test/unit/test_schema_10_migration.js181
-rw-r--r--netwerk/test/unit/test_schema_12_migration.js181
-rw-r--r--netwerk/test/unit/test_schema_13_db.js87
-rw-r--r--netwerk/test/unit/test_schema_2_migration.js303
-rw-r--r--netwerk/test/unit/test_schema_3_migration.js170
-rw-r--r--netwerk/test/unit/test_separate_connections.js104
-rw-r--r--netwerk/test/unit/test_servers.js324
-rw-r--r--netwerk/test/unit/test_signature_extraction.js203
-rw-r--r--netwerk/test/unit/test_simple.js70
-rw-r--r--netwerk/test/unit/test_sockettransportsvc_available.js11
-rw-r--r--netwerk/test/unit/test_socks.js520
-rw-r--r--netwerk/test/unit/test_speculative_connect.js382
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_loop.js43
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_max-age-0.js113
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_negative.js92
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_positive.js113
-rw-r--r--netwerk/test/unit/test_standardurl.js1053
-rw-r--r--netwerk/test/unit/test_standardurl_default_port.js58
-rw-r--r--netwerk/test/unit/test_standardurl_port.js53
-rw-r--r--netwerk/test/unit/test_streamcopier.js63
-rw-r--r--netwerk/test/unit/test_substituting_protocol_handler.js64
-rw-r--r--netwerk/test/unit/test_suspend_channel_before_connect.js93
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_authRetry.js264
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_examine.js76
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_examine_merged_response.js208
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_modified.js177
-rw-r--r--netwerk/test/unit/test_synthesized_response.js288
-rw-r--r--netwerk/test/unit/test_throttlechannel.js48
-rw-r--r--netwerk/test/unit/test_throttlequeue.js25
-rw-r--r--netwerk/test/unit/test_throttling.js66
-rw-r--r--netwerk/test/unit/test_tldservice_nextsubdomain.js24
-rw-r--r--netwerk/test/unit/test_tls13_disabled.js93
-rw-r--r--netwerk/test/unit/test_tls_flags.js248
-rw-r--r--netwerk/test/unit/test_tls_flags_separate_connections.js117
-rw-r--r--netwerk/test/unit/test_tls_server.js314
-rw-r--r--netwerk/test/unit/test_tls_server_multiple_clients.js130
-rw-r--r--netwerk/test/unit/test_traceable_channel.js145
-rw-r--r--netwerk/test/unit/test_trackingProtection_annotateChannels.js391
-rw-r--r--netwerk/test/unit/test_trr.js946
-rw-r--r--netwerk/test/unit/test_trr_additional_section.js380
-rw-r--r--netwerk/test/unit/test_trr_af_fallback.js117
-rw-r--r--netwerk/test/unit/test_trr_blocklist.js78
-rw-r--r--netwerk/test/unit/test_trr_cancel.js180
-rw-r--r--netwerk/test/unit/test_trr_case_sensitivity.js153
-rw-r--r--netwerk/test/unit/test_trr_cname_chain.js231
-rw-r--r--netwerk/test/unit/test_trr_confirmation.js401
-rw-r--r--netwerk/test/unit/test_trr_decoding.js56
-rw-r--r--netwerk/test/unit/test_trr_domain.js123
-rw-r--r--netwerk/test/unit/test_trr_enterprise_policy.js93
-rw-r--r--netwerk/test/unit/test_trr_extended_error.js319
-rw-r--r--netwerk/test/unit/test_trr_https_fallback.js1105
-rw-r--r--netwerk/test/unit/test_trr_httpssvc.js728
-rw-r--r--netwerk/test/unit/test_trr_nat64.js120
-rw-r--r--netwerk/test/unit/test_trr_noPrefetch.js174
-rw-r--r--netwerk/test/unit/test_trr_proxy.js156
-rw-r--r--netwerk/test/unit/test_trr_proxy_auth.js122
-rw-r--r--netwerk/test/unit/test_trr_strict_mode.js52
-rw-r--r--netwerk/test/unit/test_trr_telemetry.js118
-rw-r--r--netwerk/test/unit/test_trr_ttl.js60
-rw-r--r--netwerk/test/unit/test_trr_with_proxy.js210
-rw-r--r--netwerk/test/unit/test_udp_multicast.js99
-rw-r--r--netwerk/test/unit/test_udpsocket.js89
-rw-r--r--netwerk/test/unit/test_udpsocket_offline.js144
-rw-r--r--netwerk/test/unit/test_unescapestring.js35
-rw-r--r--netwerk/test/unit/test_unix_domain.js699
-rw-r--r--netwerk/test/unit/test_uri_mutator.js48
-rw-r--r--netwerk/test/unit/test_use_httpssvc.js240
-rw-r--r--netwerk/test/unit/test_verify_traffic.js110
-rw-r--r--netwerk/test/unit/test_websocket_500k.js222
-rw-r--r--netwerk/test/unit/test_websocket_fails.js194
-rw-r--r--netwerk/test/unit/test_websocket_fails_2.js57
-rw-r--r--netwerk/test/unit/test_websocket_offline.js51
-rw-r--r--netwerk/test/unit/test_websocket_server.js317
-rw-r--r--netwerk/test/unit/test_websocket_server_multiclient.js141
-rw-r--r--netwerk/test/unit/test_websocket_with_h3_active.js97
-rw-r--r--netwerk/test/unit/test_webtransport_simple.js460
-rw-r--r--netwerk/test/unit/test_xmlhttprequest.js57
-rw-r--r--netwerk/test/unit/trr_common.js1235
-rw-r--r--netwerk/test/unit/xpcshell.toml1270
-rw-r--r--netwerk/test/unit_ipc/child_channel_id.js47
-rw-r--r--netwerk/test/unit_ipc/child_cookie_header.js93
-rw-r--r--netwerk/test/unit_ipc/child_dns_by_type_resolve.js22
-rw-r--r--netwerk/test/unit_ipc/child_is_proxy_used.js24
-rw-r--r--netwerk/test/unit_ipc/child_veto_in_parent.js53
-rw-r--r--netwerk/test/unit_ipc/head_channels_clone.js12
-rw-r--r--netwerk/test/unit_ipc/head_http3_clone.js10
-rw-r--r--netwerk/test/unit_ipc/head_trr_clone.js10
-rw-r--r--netwerk/test/unit_ipc/test_XHR_redirects.js3
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_closeWithStatus_wrap.js17
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js96
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_simple_wrap.js17
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_stream_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_cache-entry-id_wrap.js13
-rw-r--r--netwerk/test/unit_ipc/test_cache_jar_wrap.js4
-rw-r--r--netwerk/test/unit_ipc/test_cacheflags_wrap.js8
-rw-r--r--netwerk/test/unit_ipc/test_channel_close_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_channel_id.js109
-rw-r--r--netwerk/test/unit_ipc/test_channel_priority_wrap.js49
-rw-r--r--netwerk/test/unit_ipc/test_chunked_responses_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_cookie_header_stripped.js94
-rw-r--r--netwerk/test/unit_ipc/test_cookiejars_wrap.js13
-rw-r--r--netwerk/test/unit_ipc/test_dns_by_type_resolve_wrap.js52
-rw-r--r--netwerk/test/unit_ipc/test_dns_cancel_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_dns_service_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_duplicate_headers_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_event_sink_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_getHost_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_gio_protocol_wrap.js21
-rw-r--r--netwerk/test/unit_ipc/test_head_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_headers_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_http3_prio_disabled_wrap.js20
-rw-r--r--netwerk/test/unit_ipc/test_http3_prio_enabled_wrap.js20
-rw-r--r--netwerk/test/unit_ipc/test_httpcancel_wrap.js48
-rw-r--r--netwerk/test/unit_ipc/test_httpsuspend_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_is_proxy_used.js32
-rw-r--r--netwerk/test/unit_ipc/test_multipart_streamconv_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_orb_empty_header_wrap.js8
-rw-r--r--netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_post_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_predictor_wrap.js44
-rw-r--r--netwerk/test/unit_ipc/test_progress_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js11
-rw-r--r--netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_canceled_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_failure_wrap.js8
-rw-r--r--netwerk/test/unit_ipc/test_redirect_from_script_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_redirect_history_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_passing_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_veto_parent.js64
-rw-r--r--netwerk/test/unit_ipc/test_redirect_veto_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_reentrancy_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_resumable_channel_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_simple_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap1.js26
-rw-r--r--netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap2.js26
-rw-r--r--netwerk/test/unit_ipc/test_trr_httpssvc_wrap.js88
-rw-r--r--netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/xpcshell.toml240
-rw-r--r--netwerk/test/useragent/browser_nonsnap.toml4
-rw-r--r--netwerk/test/useragent/browser_snap.toml8
-rw-r--r--netwerk/test/useragent/browser_ua_nonsnap.js10
-rw-r--r--netwerk/test/useragent/browser_ua_snap_ubuntu.js10
-rw-r--r--netwerk/url-classifier/AsyncUrlChannelClassifier.cpp920
-rw-r--r--netwerk/url-classifier/AsyncUrlChannelClassifier.h27
-rw-r--r--netwerk/url-classifier/ChannelClassifierService.cpp271
-rw-r--r--netwerk/url-classifier/ChannelClassifierService.h80
-rw-r--r--netwerk/url-classifier/UrlClassifierCommon.cpp671
-rw-r--r--netwerk/url-classifier/UrlClassifierCommon.h103
-rw-r--r--netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs168
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureBase.cpp182
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureBase.h86
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp170
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp211
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp103
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCustomTables.h35
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp268
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.h45
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp217
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h47
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFactory.cpp384
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFactory.h57
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp178
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp216
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp128
-rw-r--r--netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureResult.cpp49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureResult.h45
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp178
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp213
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp180
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp217
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h49
-rw-r--r--netwerk/url-classifier/components.conf22
-rw-r--r--netwerk/url-classifier/moz.build68
-rw-r--r--netwerk/url-classifier/nsChannelClassifier.cpp483
-rw-r--r--netwerk/url-classifier/nsChannelClassifier.h70
-rw-r--r--netwerk/url-classifier/nsIChannelClassifierService.idl58
-rw-r--r--netwerk/url-classifier/nsIURIClassifier.idl115
-rw-r--r--netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl71
-rw-r--r--netwerk/url-classifier/nsIUrlClassifierFeature.idl120
-rw-r--r--netwerk/wifi/WifiScanner.h33
-rw-r--r--netwerk/wifi/dbus/DbusWifiScanner.cpp161
-rw-r--r--netwerk/wifi/dbus/DbusWifiScanner.h41
-rw-r--r--netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp148
-rw-r--r--netwerk/wifi/freebsd/FreeBsdWifiScanner.h31
-rw-r--r--netwerk/wifi/gtest/TestWifiMonitor.cpp404
-rw-r--r--netwerk/wifi/gtest/moz.build13
-rw-r--r--netwerk/wifi/mac/MacWifiScanner.h28
-rw-r--r--netwerk/wifi/mac/MacWifiScanner.mm109
-rw-r--r--netwerk/wifi/mac/Wifi.h119
-rw-r--r--netwerk/wifi/moz.build63
-rw-r--r--netwerk/wifi/nsIWifiAccessPoint.idl41
-rw-r--r--netwerk/wifi/nsIWifiListener.idl29
-rw-r--r--netwerk/wifi/nsIWifiMonitor.idl27
-rw-r--r--netwerk/wifi/nsWifiAccessPoint.cpp67
-rw-r--r--netwerk/wifi/nsWifiAccessPoint.h78
-rw-r--r--netwerk/wifi/nsWifiMonitor.cpp393
-rw-r--r--netwerk/wifi/nsWifiMonitor.h123
-rw-r--r--netwerk/wifi/solaris/SolarisWifiScanner.cpp121
-rw-r--r--netwerk/wifi/solaris/SolarisWifiScanner.h31
-rw-r--r--netwerk/wifi/win/WinWifiScanner.cpp170
-rw-r--r--netwerk/wifi/win/WinWifiScanner.h36
-rw-r--r--netwerk/wifi/win/WlanLibrary.cpp111
-rw-r--r--netwerk/wifi/win/WlanLibrary.h54
2102 files changed, 565235 insertions, 0 deletions
diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h
new file mode 100644
index 0000000000..207b5fe03c
--- /dev/null
+++ b/netwerk/base/ARefBase.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ARefBase_h
+#define mozilla_net_ARefBase_h
+
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace net {
+
+// This is an abstract class that can be pointed to by either
+// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic
+// objects that need to be reference counted - similiar to nsISupports
+// but it may or may not be xpcom.
+
+class ARefBase {
+ public:
+ ARefBase() = default;
+ virtual ~ARefBase() = default;
+
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp
new file mode 100644
index 0000000000..b8c26d03ef
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "ArrayBufferInputStream.h"
+#include "nsStreamUtils.h"
+#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject}
+#include "js/RootingAPI.h" // JS::{Handle,Rooted}
+#include "js/Value.h" // JS::Value
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using mozilla::dom::RootingCx;
+
+NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream,
+ nsIInputStream);
+
+NS_IMETHODIMP
+ArrayBufferInputStream::SetDataFromJS(JS::Handle<JS::Value> aBuffer,
+ uint64_t aByteOffset, uint64_t aLength) {
+ NS_ASSERT_OWNINGTHREAD(ArrayBufferInputStream);
+
+ if (!aBuffer.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::Rooted<JSObject*> arrayBuffer(RootingCx(), &aBuffer.toObject());
+ if (!JS::IsArrayBufferObject(arrayBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t buflen = JS::GetArrayBufferByteLength(arrayBuffer);
+ uint64_t offset = std::min(buflen, aByteOffset);
+ uint64_t bufferLength = std::min(buflen - offset, aLength);
+
+ // Prevent truncation.
+ if (bufferLength > UINT32_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mArrayBuffer = mozilla::MakeUniqueFallible<uint8_t[]>(bufferLength);
+ if (!mArrayBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mBufferLength = bufferLength;
+
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ uint8_t* src = JS::GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
+ memcpy(&mArrayBuffer[0], src, mBufferLength);
+ return NS_OK;
+}
+
+nsresult ArrayBufferInputStream::SetData(mozilla::UniquePtr<uint8_t[]> aBytes,
+ uint64_t aByteLen) {
+ mArrayBuffer = std::move(aBytes);
+ mBufferLength = aByteLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close() {
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ if (mArrayBuffer) {
+ *aCount = mBufferLength ? mBufferLength - mPos : 0;
+ } else {
+ *aCount = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::StreamStatus() {
+ return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t aCount, uint32_t* result) {
+ NS_ASSERTION(result, "null ptr");
+ NS_ASSERTION(mBufferLength >= mPos, "bad stream state");
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength),
+ "stream inited incorrectly");
+
+ *result = 0;
+ while (mPos < mBufferLength) {
+ uint32_t remaining = mBufferLength - mPos;
+ MOZ_ASSERT(mArrayBuffer);
+
+ uint32_t count = std::min(aCount, remaining);
+ if (count == 0) {
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv = writer(this, closure, (char*)&mArrayBuffer[0] + mPos, *result,
+ count, &written);
+ if (NS_FAILED(rv)) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ NS_ASSERTION(written <= count,
+ "writer should not write more than we asked it to write");
+ mPos += written;
+ *result += written;
+ aCount -= written;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h
new file mode 100644
index 0000000000..e3d4ea62f0
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ArrayBufferInputStream_h
+#define ArrayBufferInputStream_h
+
+#include "nsIArrayBufferInputStream.h"
+#include "js/Value.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsISupportsImpl.h"
+
+#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID \
+ "@mozilla.org/io/arraybuffer-input-stream;1"
+#define NS_ARRAYBUFFERINPUTSTREAM_CID \
+ { /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */ \
+ 0x3014dde6, 0xaa1c, 0x41db, { \
+ 0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6 \
+ } \
+ }
+
+class ArrayBufferInputStream : public nsIArrayBufferInputStream {
+ public:
+ ArrayBufferInputStream() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIARRAYBUFFERINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAM
+
+ private:
+ virtual ~ArrayBufferInputStream() = default;
+ mozilla::UniquePtr<uint8_t[]> mArrayBuffer;
+ uint32_t mBufferLength{0};
+ uint32_t mPos{0};
+ bool mClosed{false};
+};
+
+#endif // ArrayBufferInputStream_h
diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h
new file mode 100644
index 0000000000..a0e3a48e17
--- /dev/null
+++ b/netwerk/base/AutoClose.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AutoClose_h
+#define mozilla_net_AutoClose_h
+
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+// A container for XPCOM streams (e.g. nsIAsyncInputStream) and other
+// refcounted classes that need to have the Close() method called explicitly
+// before they are destroyed.
+template <typename T>
+class AutoClose {
+ public:
+ AutoClose() : mMutex("net::AutoClose.mMutex") {}
+ ~AutoClose() { CloseAndRelease(); }
+
+ explicit operator bool() {
+ MutexAutoLock lock(mMutex);
+ return mPtr;
+ }
+
+ already_AddRefed<T> forget() {
+ MutexAutoLock lock(mMutex);
+ return mPtr.forget();
+ }
+
+ void takeOver(nsCOMPtr<T>& rhs) { TakeOverInternal(rhs.forget()); }
+
+ void CloseAndRelease() { TakeOverInternal(nullptr); }
+
+ private:
+ void TakeOverInternal(already_AddRefed<T>&& aOther) {
+ nsCOMPtr<T> ptr(std::move(aOther));
+ {
+ MutexAutoLock lock(mMutex);
+ ptr.swap(mPtr);
+ }
+
+ if (ptr) {
+ ptr->Close();
+ }
+ }
+
+ void operator=(const AutoClose<T>&) = delete;
+ AutoClose(const AutoClose<T>&) = delete;
+
+ nsCOMPtr<T> mPtr;
+ Mutex mMutex MOZ_UNANNOTATED;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AutoClose_h
diff --git a/netwerk/base/BackgroundFileSaver.cpp b/netwerk/base/BackgroundFileSaver.cpp
new file mode 100644
index 0000000000..3af6b8eb26
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.cpp
@@ -0,0 +1,1121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BackgroundFileSaver.h"
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.h"
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDependentSubstring.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIFile.h"
+#include "nsIMutableArray.h"
+#include "nsIPipe.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "pk11pub.h"
+#include "secoidt.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <softpub.h>
+# include <wintrust.h>
+#endif // XP_WIN
+
+namespace mozilla {
+namespace net {
+
+// MOZ_LOG=BackgroundFileSaver:5
+static LazyLogModule prlog("BackgroundFileSaver");
+#define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+/**
+ * Buffer size for writing to the output file or reading from the input file.
+ */
+#define BUFFERED_IO_SIZE (1024 * 32)
+
+/**
+ * When this upper limit is reached, the original request is suspended.
+ */
+#define REQUEST_SUSPEND_AT (1024 * 1024 * 4)
+
+/**
+ * When this lower limit is reached, the original request is resumed.
+ */
+#define REQUEST_RESUME_AT (1024 * 1024 * 2)
+
+////////////////////////////////////////////////////////////////////////////////
+//// NotifyTargetChangeRunnable
+
+/**
+ * Runnable object used to notify the control thread that file contents will now
+ * be saved to the specified file.
+ */
+class NotifyTargetChangeRunnable final : public Runnable {
+ public:
+ NotifyTargetChangeRunnable(BackgroundFileSaver* aSaver, nsIFile* aTarget)
+ : Runnable("net::NotifyTargetChangeRunnable"),
+ mSaver(aSaver),
+ mTarget(aTarget) {}
+
+ NS_IMETHOD Run() override { return mSaver->NotifyTargetChange(mTarget); }
+
+ private:
+ RefPtr<BackgroundFileSaver> mSaver;
+ nsCOMPtr<nsIFile> mTarget;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+uint32_t BackgroundFileSaver::sThreadCount = 0;
+uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0;
+
+BackgroundFileSaver::BackgroundFileSaver() {
+ LOG(("Created BackgroundFileSaver [this = %p]", this));
+}
+
+BackgroundFileSaver::~BackgroundFileSaver() {
+ LOG(("Destroying BackgroundFileSaver [this = %p]", this));
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::Init() {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ NS_NewPipe2(getter_AddRefs(mPipeInputStream),
+ getter_AddRefs(mPipeOutputStream), true, true, 0,
+ HasInfiniteBuffer() ? UINT32_MAX : 0);
+
+ mControlEventTarget = GetCurrentSerialEventTarget();
+ NS_ENSURE_TRUE(mControlEventTarget, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv = NS_CreateBackgroundTaskQueue("BgFileSaver",
+ getter_AddRefs(mBackgroundET));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sThreadCount++;
+ if (sThreadCount > sTelemetryMaxThreadCount) {
+ sTelemetryMaxThreadCount = sThreadCount;
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver** aObserver) {
+ NS_ENSURE_ARG_POINTER(aObserver);
+ *aObserver = do_AddRef(mObserver).take();
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver* aObserver) {
+ mObserver = aObserver;
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::EnableAppend() {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ MutexAutoLock lock(mLock);
+ mAppend = true;
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetTarget(nsIFile* aTarget, bool aKeepPartial) {
+ NS_ENSURE_ARG(aTarget);
+ {
+ MutexAutoLock lock(mLock);
+ if (!mInitialTarget) {
+ aTarget->Clone(getter_AddRefs(mInitialTarget));
+ mInitialTargetKeepPartial = aKeepPartial;
+ } else {
+ aTarget->Clone(getter_AddRefs(mRenamedTarget));
+ mRenamedTargetKeepPartial = aKeepPartial;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // rename or create the target file as requested, and start copying data.
+ return GetWorkerThreadAttention(true);
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::Finish(nsresult aStatus) {
+ nsresult rv;
+
+ // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
+ // all the data that is still in the pipe, and then finish.
+ rv = mPipeOutputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure that, when we get attention from the worker thread, if no pending
+ // rename operation is waiting, the operation will complete.
+ {
+ MutexAutoLock lock(mLock);
+ mFinishRequested = true;
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // process the completion conditions, detect that completion is requested, and
+ // notify the main thread of the completion. If this function was called with
+ // a success code, we wait for the copy to finish before processing the
+ // completion conditions, otherwise we interrupt the copy immediately.
+ return GetWorkerThreadAttention(NS_FAILED(aStatus));
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSha256() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable sha256 or initialize NSS off the main thread");
+ // Ensure Personal Security Manager is initialized. This is required for
+ // PK11_* operations to work.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MutexAutoLock lock(mLock);
+ mSha256Enabled = true; // this will be read by the worker thread
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSha256Hash(nsACString& aHash) {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
+ // We acquire a lock because mSha256 is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (mSha256.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aHash = mSha256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSignatureInfo() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable signature extraction off the main thread");
+ // Ensure Personal Security Manager is initialized.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MutexAutoLock lock(mLock);
+ mSignatureInfoEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSignatureInfo(
+ nsTArray<nsTArray<nsTArray<uint8_t>>>& aSignatureInfo) {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
+ // We acquire a lock because mSignatureInfo is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (!mComplete || !mSignatureInfoEnabled) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ for (const auto& signatureChain : mSignatureInfo) {
+ aSignatureInfo.AppendElement(TransformIntoNewArray(
+ signatureChain, [](const auto& element) { return element.Clone(); }));
+ }
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::GetWorkerThreadAttention(
+ bool aShouldInterruptCopy) {
+ nsresult rv;
+
+ MutexAutoLock lock(mLock);
+
+ // We only require attention one time. If this function is called two times
+ // before the worker thread wakes up, and the first has aShouldInterruptCopy
+ // false and the second true, we won't forcibly interrupt the copy from the
+ // control thread. However, that never happens, because calling Finish with a
+ // success code is the only case that may result in aShouldInterruptCopy being
+ // false. In that case, we won't call this function again, because consumers
+ // should not invoke other methods on the control thread after calling Finish.
+ // And in any case, Finish already closes one end of the pipe, causing the
+ // copy to finish properly on its own.
+ if (mWorkerThreadAttentionRequested) {
+ return NS_OK;
+ }
+
+ if (!mAsyncCopyContext) {
+ // Background event queues are not shutdown and could be called after
+ // the queue is reset to null. To match the behavior of nsIThread
+ // return NS_ERROR_UNEXPECTED
+ if (!mBackgroundET) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Copy is not in progress, post an event to handle the change manually.
+ rv = mBackgroundET->Dispatch(
+ NewRunnableMethod("net::BackgroundFileSaver::ProcessAttention", this,
+ &BackgroundFileSaver::ProcessAttention),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ } else if (aShouldInterruptCopy) {
+ // Interrupt the copy. The copy will be resumed, if needed, by the
+ // ProcessAttention function, invoked by the AsyncCopyCallback function.
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ }
+
+ // Indicate that attention has been requested successfully, there is no need
+ // to post another event until the worker thread processes the current one.
+ mWorkerThreadAttentionRequested = true;
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void BackgroundFileSaver::AsyncCopyCallback(void* aClosure, nsresult aStatus) {
+ // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object
+ // alive even if other references disappeared. At the end of this method,
+ // we've finished using the object and can safely release our reference.
+ RefPtr<BackgroundFileSaver> self =
+ dont_AddRef((BackgroundFileSaver*)aClosure);
+ {
+ MutexAutoLock lock(self->mLock);
+
+ // Now that the copy was interrupted or terminated, any notification from
+ // the control thread requires an event to be posted to the worker thread.
+ self->mAsyncCopyContext = nullptr;
+
+ // When detecting failures, ignore the status code we use to interrupt.
+ if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT &&
+ NS_SUCCEEDED(self->mStatus)) {
+ self->mStatus = aStatus;
+ }
+ }
+
+ (void)self->ProcessAttention();
+}
+
+// Called on the worker thread.
+nsresult BackgroundFileSaver::ProcessAttention() {
+ nsresult rv;
+
+ // This function is called whenever the attention of the worker thread has
+ // been requested. This may happen in these cases:
+ // * We are about to start the copy for the first time. In this case, we are
+ // called from an event posted on the worker thread from the control thread
+ // by GetWorkerThreadAttention, and mAsyncCopyContext is null.
+ // * We have interrupted the copy for some reason. In this case, we are
+ // called by AsyncCopyCallback, and mAsyncCopyContext is null.
+ // * We are currently executing ProcessStateChange, and attention is requested
+ // by the control thread, for example because SetTarget or Finish have been
+ // called. In this case, we are called from from an event posted through
+ // GetWorkerThreadAttention. While mAsyncCopyContext was always null when
+ // the event was posted, at this point mAsyncCopyContext may not be null
+ // anymore, because ProcessStateChange may have started the copy before the
+ // event that called this function was processed on the worker thread.
+ // If mAsyncCopyContext is not null, we interrupt the copy and re-enter
+ // through AsyncCopyCallback. This allows us to check if, for instance, we
+ // should rename the target file. We will then restart the copy if needed.
+
+ // mAsyncCopyContext is only written on the worker thread (which we are on)
+ MOZ_ASSERT(!NS_IsMainThread());
+ {
+ // Even though we're the only thread that writes this, we have to take the
+ // lock
+ MutexAutoLock lock(mLock);
+ if (mAsyncCopyContext) {
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ return NS_OK;
+ }
+ }
+ // Use the current shared state to determine the next operation to execute.
+ rv = ProcessStateChange();
+ if (NS_FAILED(rv)) {
+ // If something failed while processing, terminate the operation now.
+ {
+ MutexAutoLock lock(mLock);
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ }
+ // Ensure we notify completion now that the operation failed.
+ CheckCompletion();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+nsresult BackgroundFileSaver::ProcessStateChange() {
+ nsresult rv;
+
+ // We might have been notified because the operation is complete, verify.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Get a copy of the current shared state for the worker thread.
+ nsCOMPtr<nsIFile> initialTarget;
+ bool initialTargetKeepPartial;
+ nsCOMPtr<nsIFile> renamedTarget;
+ bool renamedTargetKeepPartial;
+ bool sha256Enabled;
+ bool append;
+ {
+ MutexAutoLock lock(mLock);
+
+ initialTarget = mInitialTarget;
+ initialTargetKeepPartial = mInitialTargetKeepPartial;
+ renamedTarget = mRenamedTarget;
+ renamedTargetKeepPartial = mRenamedTargetKeepPartial;
+ sha256Enabled = mSha256Enabled;
+ append = mAppend;
+
+ // From now on, another attention event needs to be posted if state changes.
+ mWorkerThreadAttentionRequested = false;
+ }
+
+ // The initial target can only be null if it has never been assigned. In this
+ // case, there is nothing to do since we never created any output file.
+ if (!initialTarget) {
+ return NS_OK;
+ }
+
+ // Determine if we are processing the attention request for the first time.
+ bool isContinuation = !!mActualTarget;
+ if (!isContinuation) {
+ // Assign the target file for the first time.
+ mActualTarget = initialTarget;
+ mActualTargetKeepPartial = initialTargetKeepPartial;
+ }
+
+ // Verify whether we have actually been instructed to use a different file.
+ // This may happen the first time this function is executed, if SetTarget was
+ // called two times before the worker thread processed the attention request.
+ bool equalToCurrent = false;
+ if (renamedTarget) {
+ rv = mActualTarget->Equals(renamedTarget, &equalToCurrent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!equalToCurrent) {
+ // If we were asked to rename the file but the initial file did not exist,
+ // we simply create the file in the renamed location. We avoid this check
+ // if we have already started writing the output file ourselves.
+ bool exists = true;
+ if (!isContinuation) {
+ rv = mActualTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (exists) {
+ // We are moving the previous target file to a different location.
+ nsCOMPtr<nsIFile> renamedTargetParentDir;
+ rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString renamedTargetName;
+ rv = renamedTarget->GetLeafName(renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We must delete any existing target file before moving the current
+ // one.
+ rv = renamedTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ rv = renamedTarget->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Move the file. If this fails, we still reference the original file
+ // in mActualTarget, so that it is deleted if requested. If this
+ // succeeds, the nsIFile instance referenced by mActualTarget mutates
+ // and starts pointing to the new file, but we'll discard the reference.
+ rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We should not only update the mActualTarget with renameTarget when
+ // they point to the different files.
+ // In this way, if mActualTarget and renamedTarget point to the same file
+ // with different addresses, "CheckCompletion()" will return false
+ // forever.
+ }
+
+ // Update mActualTarget with renameTarget,
+ // even if they point to the same file.
+ mActualTarget = renamedTarget;
+ mActualTargetKeepPartial = renamedTargetKeepPartial;
+ }
+
+ // Notify if the target file name actually changed.
+ if (!equalToCurrent) {
+ // We must clone the nsIFile instance because mActualTarget is not
+ // immutable, it may change if the target is renamed later.
+ nsCOMPtr<nsIFile> actualTargetToNotify;
+ rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<NotifyTargetChangeRunnable> event =
+ new NotifyTargetChangeRunnable(this, actualTargetToNotify);
+ NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
+
+ rv = mControlEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (isContinuation) {
+ // The pending rename operation might be the last task before finishing. We
+ // may return here only if we have already created the target file.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Even if the operation did not complete, the pipe input stream may be
+ // empty and may have been closed already. We detect this case using the
+ // Available property, because it never returns an error if there is more
+ // data to be consumed. If the pipe input stream is closed, we just exit
+ // and wait for more calls like SetTarget or Finish to be invoked on the
+ // control thread. However, we still truncate the file or create the
+ // initial digest context if we are expected to do that.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ // Create the digest if requested and NSS hasn't been shut down.
+ if (sha256Enabled && mDigest.isNothing()) {
+ mDigest.emplace(Digest());
+ mDigest->Begin(SEC_OID_SHA256);
+ }
+
+ // When we are requested to append to an existing file, we should read the
+ // existing data and ensure we include it as part of the final hash.
+ if (mDigest.isSome() && append && !isContinuation) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mActualTarget,
+ PR_RDONLY | nsIFile::OS_READAHEAD);
+ if (rv != NS_ERROR_FILE_NOT_FOUND) {
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try to clean up the inputStream if an error occurs.
+ auto closeGuard =
+ mozilla::MakeScopeExit([&] { Unused << inputStream->Close(); });
+
+ char buffer[BUFFERED_IO_SIZE];
+ while (true) {
+ uint32_t count;
+ rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count == 0) {
+ // We reached the end of the file.
+ break;
+ }
+
+ rv = mDigest->Update(BitwiseCast<unsigned char*, char*>(buffer), count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The pending resume operation may have been cancelled by the control
+ // thread while the worker thread was reading in the existing file.
+ // Abort reading in the original file in that case, as the digest will
+ // be discarded anyway.
+ MutexAutoLock lock(mLock);
+ if (NS_FAILED(mStatus)) {
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ // Close explicitly to handle any errors.
+ closeGuard.release();
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // We will append to the initial target file only if it was requested by the
+ // caller, but we'll always append on subsequent accesses to the target file.
+ int32_t creationIoFlags;
+ if (isContinuation) {
+ creationIoFlags = PR_APPEND;
+ } else {
+ creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE;
+ }
+
+ // Create the target file, or append to it if we already started writing it.
+ // The 0600 permissions are used while the file is being downloaded, and for
+ // interrupted downloads. Those may be located in the system temporary
+ // directory, as well as the target directory, and generally have a ".part"
+ // extension. Those part files should never be group or world-writable even
+ // if the umask allows it.
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget,
+ PR_WRONLY | creationIoFlags, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> bufferedStream;
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream),
+ outputStream.forget(), BUFFERED_IO_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ outputStream = bufferedStream;
+
+ // Wrap the output stream so that it feeds the digest if needed.
+ if (mDigest.isSome()) {
+ // Constructing the DigestOutputStream cannot fail. Passing mDigest
+ // to DigestOutputStream is safe, because BackgroundFileSaver always
+ // outlives the outputStream. BackgroundFileSaver is reference-counted
+ // before the call to AsyncCopy, and mDigest is never destroyed
+ // before AsyncCopyCallback.
+ outputStream = new DigestOutputStream(outputStream, mDigest.ref());
+ }
+
+ // Start copying our input to the target file. No errors can be raised past
+ // this point if the copy starts, since they should be handled by the thread.
+ {
+ MutexAutoLock lock(mLock);
+
+ rv = NS_AsyncCopy(mPipeInputStream, outputStream, mBackgroundET,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback,
+ this, false, true, getter_AddRefs(mAsyncCopyContext),
+ GetProgressCallback());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("NS_AsyncCopy failed.");
+ mAsyncCopyContext = nullptr;
+ return rv;
+ }
+ }
+
+ // If the operation succeeded, we must ensure that we keep this object alive
+ // for the entire duration of the copy, since only the raw pointer will be
+ // provided as the argument of the AsyncCopyCallback function. We can add the
+ // reference now, after NS_AsyncCopy returned, because it always starts
+ // processing asynchronously, and there is no risk that the callback is
+ // invoked before we reach this point. If the operation failed instead, then
+ // AsyncCopyCallback will never be called.
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+bool BackgroundFileSaver::CheckCompletion() {
+ nsresult rv;
+
+ bool failed = true;
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(!mAsyncCopyContext,
+ "Should not be copying when checking completion conditions.");
+
+ if (mComplete) {
+ return true;
+ }
+
+ // If an error occurred, we don't need to do the checks in this code block,
+ // and the operation can be completed immediately with a failure code.
+ if (NS_SUCCEEDED(mStatus)) {
+ failed = false;
+
+ // We did not incur in an error, so we must determine if we can stop now.
+ // If the Finish method has not been called, we can just continue now.
+ if (!mFinishRequested) {
+ return false;
+ }
+
+ // We can only stop when all the operations requested by the control
+ // thread have been processed. First, we check whether we have processed
+ // the first SetTarget call, if any. Then, we check whether we have
+ // processed any rename requested by subsequent SetTarget calls.
+ if ((mInitialTarget && !mActualTarget) ||
+ (mRenamedTarget && mRenamedTarget != mActualTarget)) {
+ return false;
+ }
+
+ // If we still have data to write to the output file, allow the copy
+ // operation to resume. The Available getter may return an error if one
+ // of the pipe's streams has been already closed.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available != 0) {
+ return false;
+ }
+ }
+
+ mComplete = true;
+ }
+
+ // Ensure we notify completion now that the operation finished.
+ // Do a best-effort attempt to remove the file if required.
+ if (failed && mActualTarget && !mActualTargetKeepPartial) {
+ (void)mActualTarget->Remove(false);
+ }
+
+ // Finish computing the hash
+ if (!failed && mDigest.isSome()) {
+ nsTArray<uint8_t> outArray;
+ rv = mDigest->End(outArray);
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock lock(mLock);
+ mSha256 = nsDependentCSubstring(
+ BitwiseCast<char*, uint8_t*>(outArray.Elements()), outArray.Length());
+ }
+ }
+
+ // Compute the signature of the binary. ExtractSignatureInfo doesn't do
+ // anything on non-Windows platforms except return an empty nsIArray.
+ if (!failed && mActualTarget) {
+ nsString filePath;
+ mActualTarget->GetTarget(filePath);
+ nsresult rv = ExtractSignatureInfo(filePath);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to extract signature information [this = %p].", this));
+ } else {
+ LOG(("Signature extraction success! [this = %p]", this));
+ }
+ }
+
+ // Post an event to notify that the operation completed.
+ if (NS_FAILED(mControlEventTarget->Dispatch(
+ NewRunnableMethod("BackgroundFileSaver::NotifySaveComplete", this,
+ &BackgroundFileSaver::NotifySaveComplete),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post completion event to the control thread.");
+ }
+
+ return true;
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::NotifyTargetChange(nsIFile* aTarget) {
+ if (mObserver) {
+ (void)mObserver->OnTargetChange(this, aTarget);
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::NotifySaveComplete() {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsresult status;
+ {
+ MutexAutoLock lock(mLock);
+ status = mStatus;
+ }
+
+ if (mObserver) {
+ (void)mObserver->OnSaveComplete(this, status);
+ // If mObserver keeps alive an enclosure that captures `this`, we'll have a
+ // cycle that won't be caught by the cycle-collector, so we need to break it
+ // when we're done here (see bug 1444265).
+ mObserver = nullptr;
+ }
+
+ // At this point, the worker thread will not process any more events, and we
+ // can shut it down. Shutting down a thread may re-enter the event loop on
+ // this thread. This is not a problem in this case, since this function is
+ // called by a top-level event itself, and we have already invoked the
+ // completion observer callback. Re-entering the loop can only delay the
+ // final release and destruction of this saver object, since we are keeping a
+ // reference to it through the event object.
+ mBackgroundET = nullptr;
+
+ sThreadCount--;
+
+ // When there are no more active downloads, we consider the download session
+ // finished. We record the maximum number of concurrent downloads reached
+ // during the session in a telemetry histogram, and we reset the maximum
+ // thread counter for the next download session
+ if (sThreadCount == 0) {
+ Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT,
+ sTelemetryMaxThreadCount);
+ sTelemetryMaxThreadCount = 0;
+ }
+
+ return NS_OK;
+}
+
+nsresult BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
+ {
+ MutexAutoLock lock(mLock);
+ if (!mSignatureInfoEnabled) {
+ return NS_OK;
+ }
+ }
+#ifdef XP_WIN
+ // Setup the file to check.
+ WINTRUST_FILE_INFO fileToCheck = {0};
+ fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
+ fileToCheck.pcwszFilePath = filePath.Data();
+ fileToCheck.hFile = nullptr;
+ fileToCheck.pgKnownSubject = nullptr;
+
+ // We want to check it is signed and trusted.
+ WINTRUST_DATA trustData = {0};
+ trustData.cbStruct = sizeof(trustData);
+ trustData.pPolicyCallbackData = nullptr;
+ trustData.pSIPClientData = nullptr;
+ trustData.dwUIChoice = WTD_UI_NONE;
+ trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.dwStateAction = WTD_STATEACTION_VERIFY;
+ trustData.hWVTStateData = nullptr;
+ trustData.pwszURLReference = nullptr;
+ // Disallow revocation checks over the network
+ trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+ // no UI
+ trustData.dwUIContext = 0;
+ trustData.pFile = &fileToCheck;
+
+ // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
+ // chains up to a trusted root CA and has appropriate permissions to sign
+ // code.
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ // Check if the file is signed by something that is trusted. If the file is
+ // not signed, this is a no-op.
+ LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr;
+ // According to the Windows documentation, we should check against 0 instead
+ // of ERROR_SUCCESS, which is an HRESULT.
+ if (ret == 0) {
+ cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
+ }
+ if (cryptoProviderData) {
+ // Lock because signature information is read on the main thread.
+ MutexAutoLock lock(mLock);
+ LOG(("Downloaded trusted and signed file [this = %p].", this));
+ // A binary may have multiple signers. Each signer may have multiple certs
+ // in the chain.
+ for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) {
+ const CERT_CHAIN_CONTEXT* certChainContext =
+ cryptoProviderData->pasSigners[i].pChainContext;
+ if (!certChainContext) {
+ break;
+ }
+ for (DWORD j = 0; j < certChainContext->cChain; ++j) {
+ const CERT_SIMPLE_CHAIN* certSimpleChain =
+ certChainContext->rgpChain[j];
+ if (!certSimpleChain) {
+ break;
+ }
+
+ nsTArray<nsTArray<uint8_t>> certList;
+ bool extractionSuccess = true;
+ for (DWORD k = 0; k < certSimpleChain->cElement; ++k) {
+ CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k];
+ if (certChainElement->pCertContext->dwCertEncodingType !=
+ X509_ASN_ENCODING) {
+ continue;
+ }
+ nsTArray<uint8_t> cert;
+ cert.AppendElements(certChainElement->pCertContext->pbCertEncoded,
+ certChainElement->pCertContext->cbCertEncoded);
+ certList.AppendElement(std::move(cert));
+ }
+ if (extractionSuccess) {
+ mSignatureInfo.AppendElement(std::move(certList));
+ }
+ }
+ }
+ // Free the provider data if cryptoProviderData is not null.
+ trustData.dwStateAction = WTD_STATEACTION_CLOSE;
+ WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ } else {
+ LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
+ }
+#endif
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream, nsIBackgroundFileSaver,
+ nsIOutputStream, nsIAsyncOutputStream,
+ nsIOutputStreamCallback)
+
+BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream()
+ : mAsyncWaitCallback(nullptr) {}
+
+bool BackgroundFileSaverOutputStream::HasInfiniteBuffer() { return false; }
+
+nsAsyncCopyProgressFun BackgroundFileSaverOutputStream::GetProgressCallback() {
+ return nullptr;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Close() { return mPipeOutputStream->Close(); }
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Flush() { return mPipeOutputStream->Flush(); }
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::StreamStatus() {
+ return mPipeOutputStream->StreamStatus();
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ return mPipeOutputStream->Write(aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* _retval) {
+ return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::IsNonBlocking(bool* _retval) {
+ return mPipeOutputStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) {
+ return mPipeOutputStream->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ NS_ENSURE_STATE(!mAsyncWaitCallback);
+
+ mAsyncWaitCallback = aCallback;
+
+ return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::OnOutputStreamReady(
+ nsIAsyncOutputStream* aStream) {
+ NS_ENSURE_STATE(mAsyncWaitCallback);
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr;
+ asyncWaitCallback.swap(mAsyncWaitCallback);
+
+ return asyncWaitCallback->OnOutputStreamReady(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, nsIBackgroundFileSaver,
+ nsIRequestObserver, nsIStreamListener)
+
+bool BackgroundFileSaverStreamListener::HasInfiniteBuffer() { return true; }
+
+nsAsyncCopyProgressFun
+BackgroundFileSaverStreamListener::GetProgressCallback() {
+ return AsyncCopyProgressCallback;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest* aRequest) {
+ NS_ENSURE_ARG(aRequest);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ // If an error occurred, cancel the operation immediately. On success, wait
+ // until the caller has determined whether the file should be renamed.
+ if (NS_FAILED(aStatusCode)) {
+ Finish(aStatusCode);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount) {
+ nsresult rv;
+
+ NS_ENSURE_ARG(aRequest);
+
+ // Read the requested data. Since the pipe has an infinite buffer, we don't
+ // expect any write error to occur here.
+ uint32_t writeCount;
+ rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If reading from the input stream fails for any reason, the pipe will return
+ // a success code, but without reading all the data. Since we should be able
+ // to read the requested data when OnDataAvailable is called, raise an error.
+ if (writeCount < aCount) {
+ NS_WARNING("Reading from the input stream should not have failed.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stateChanged = false;
+ {
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (!mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) {
+ mReceivedTooMuchData = true;
+ mRequest = aRequest;
+ stateChanged = true;
+ }
+ }
+ }
+
+ if (stateChanged) {
+ NotifySuspendOrResume();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(
+ void* aClosure, uint32_t aCount) {
+ BackgroundFileSaverStreamListener* self =
+ (BackgroundFileSaverStreamListener*)aClosure;
+
+ // Wait if the control thread is in the process of suspending or resuming.
+ MutexAutoLock lock(self->mSuspensionLock);
+
+ // This function is called when some bytes are consumed by NS_AsyncCopy. Each
+ // time this happens, verify if a suspended request should be resumed, because
+ // we have now consumed enough data.
+ if (self->mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = self->mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) {
+ self->mReceivedTooMuchData = false;
+
+ // Post an event to verify if the request should be resumed.
+ if (NS_FAILED(self->mControlEventTarget->Dispatch(
+ NewRunnableMethod(
+ "BackgroundFileSaverStreamListener::NotifySuspendOrResume",
+ self,
+ &BackgroundFileSaverStreamListener::NotifySuspendOrResume),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post resume event to the control thread.");
+ }
+ }
+ }
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaverStreamListener::NotifySuspendOrResume() {
+ // Prevent the worker thread from changing state while processing.
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (mReceivedTooMuchData) {
+ if (!mRequestSuspended) {
+ // Try to suspend the request. If this fails, don't try to resume later.
+ if (NS_SUCCEEDED(mRequest->Suspend())) {
+ mRequestSuspended = true;
+ } else {
+ NS_WARNING("Unable to suspend the request.");
+ }
+ }
+ } else {
+ if (mRequestSuspended) {
+ // Resume the request only if we succeeded in suspending it.
+ if (NS_SUCCEEDED(mRequest->Resume())) {
+ mRequestSuspended = false;
+ } else {
+ NS_WARNING("Unable to resume the request.");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// DigestOutputStream
+NS_IMPL_ISUPPORTS(DigestOutputStream, nsIOutputStream)
+
+DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream,
+ Digest& aDigest)
+ : mOutputStream(aStream), mDigest(aDigest) {
+ MOZ_ASSERT(mOutputStream, "Can't have null output stream");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Close() { return mOutputStream->Close(); }
+
+NS_IMETHODIMP
+DigestOutputStream::Flush() { return mOutputStream->Flush(); }
+
+NS_IMETHODIMP
+DigestOutputStream::StreamStatus() { return mOutputStream->StreamStatus(); }
+
+NS_IMETHODIMP
+DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) {
+ nsresult rv = mDigest.Update(
+ BitwiseCast<const unsigned char*, const char*>(aBuf), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOutputStream->Write(aBuf, aCount, retval);
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* retval) {
+ // Not supported. We could read the stream to a buf, call DigestOp on the
+ // result, seek back and pass the stream on, but it's not worth it since our
+ // application (NS_AsyncCopy) doesn't invoke this on the sink.
+ MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* retval) {
+ MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::IsNonBlocking(bool* retval) {
+ return mOutputStream->IsNonBlocking(retval);
+}
+
+#undef LOG_ENABLED
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h
new file mode 100644
index 0000000000..214aa31d10
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.h
@@ -0,0 +1,397 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file defines two implementations of the nsIBackgroundFileSaver
+ * interface. See the "test_backgroundfilesaver.js" file for usage examples.
+ */
+
+#ifndef BackgroundFileSaver_h__
+#define BackgroundFileSaver_h__
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIBackgroundFileSaver.h"
+#include "nsIStreamListener.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+
+class nsIAsyncInputStream;
+class nsISerialEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class DigestOutputStream;
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+class BackgroundFileSaver : public nsIBackgroundFileSaver {
+ public:
+ NS_DECL_NSIBACKGROUNDFILESAVER
+
+ BackgroundFileSaver();
+
+ /**
+ * Initializes the pipe and the worker thread on XPCOM construction.
+ *
+ * This is called automatically by the XPCOM infrastructure, and if this
+ * fails, the factory will delete this object without returning a reference.
+ */
+ nsresult Init();
+
+ /**
+ * Number of worker threads that are currently running.
+ */
+ static uint32_t sThreadCount;
+
+ /**
+ * Maximum number of worker threads reached during the current download
+ * session, used for telemetry.
+ *
+ * When there are no more worker threads running, we consider the download
+ * session finished, and this counter is reset.
+ */
+ static uint32_t sTelemetryMaxThreadCount;
+
+ protected:
+ virtual ~BackgroundFileSaver();
+
+ /**
+ * Thread that constructed this object.
+ */
+ nsCOMPtr<nsIEventTarget> mControlEventTarget;
+
+ /**
+ * Thread to which the actual input/output is delegated.
+ */
+ nsCOMPtr<nsISerialEventTarget> mBackgroundET;
+
+ /**
+ * Stream that receives data from derived classes. The received data will be
+ * available to the worker thread through mPipeInputStream. This is an
+ * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream.
+ */
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOutputStream;
+
+ /**
+ * Used during initialization, determines if the pipe is created with an
+ * infinite buffer. An infinite buffer is required if the derived class
+ * implements nsIStreamListener, because this interface requires all the
+ * provided data to be consumed synchronously.
+ */
+ virtual bool HasInfiniteBuffer() = 0;
+
+ /**
+ * Used by derived classes if they need to be called back while copying.
+ */
+ virtual nsAsyncCopyProgressFun GetProgressCallback() = 0;
+
+ /**
+ * Stream used by the worker thread to read the data to be saved.
+ */
+ nsCOMPtr<nsIAsyncInputStream> mPipeInputStream;
+
+ private:
+ friend class NotifyTargetChangeRunnable;
+
+ /**
+ * Matches the nsIBackgroundFileSaver::observer property.
+ *
+ * @remarks This is a strong reference so that JavaScript callers don't need
+ * to worry about keeping another reference to the observer.
+ */
+ nsCOMPtr<nsIBackgroundFileSaverObserver> mObserver;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Shared state between control and worker threads
+
+ /**
+ * Protects the shared state between control and worker threads. This mutex
+ * is always locked for a very short time, never during input/output.
+ */
+ mozilla::Mutex mLock{"BackgroundFileSaver.mLock"};
+
+ /**
+ * True if the worker thread is already waiting to process a change in state.
+ */
+ bool mWorkerThreadAttentionRequested MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * True if the operation should finish as soon as possibile.
+ */
+ bool mFinishRequested MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * True if the operation completed, with either success or failure.
+ */
+ bool mComplete MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * Holds the current file saver status. This is a success status while the
+ * object is working correctly, and remains such if the operation completes
+ * successfully. This becomes an error status when an error occurs on the
+ * worker thread, or when the operation is canceled.
+ */
+ nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK};
+
+ /**
+ * True if we should append data to the initial target file, instead of
+ * overwriting it.
+ */
+ bool mAppend MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and contains
+ * the target file name that will be used by the worker thread, as soon as it
+ * is possible to update mActualTarget and open the file. This is null if no
+ * target was ever assigned to this object.
+ */
+ nsCOMPtr<nsIFile> mInitialTarget MOZ_GUARDED_BY(mLock);
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and
+ * indicates whether mInitialTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mInitialTargetKeepPartial MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * contains the new target file name to which the worker thread will move the
+ * target file, as soon as it can be done. This is null if SetTarget was
+ * called only once, or no target was ever assigned to this object.
+ *
+ * The target file can be renamed multiple times, though only the most recent
+ * rename is guaranteed to be processed by the worker thread.
+ */
+ nsCOMPtr<nsIFile> mRenamedTarget MOZ_GUARDED_BY(mLock);
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * indicates whether mRenamedTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mRenamedTargetKeepPartial MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise.
+ * This is read by both threads but only written by the worker thread.
+ */
+ nsCOMPtr<nsISupports> mAsyncCopyContext MOZ_GUARDED_BY(mLock);
+
+ /**
+ * The SHA 256 hash in raw bytes of the downloaded file. This is written
+ * by the worker thread but can be read on the main thread.
+ */
+ nsCString mSha256 MOZ_GUARDED_BY(mLock);
+
+ /**
+ * Whether or not to compute the hash. Must be set on the main thread before
+ * setTarget is called.
+ */
+ bool mSha256Enabled MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * Store the signature info.
+ */
+ nsTArray<nsTArray<nsTArray<uint8_t>>> mSignatureInfo MOZ_GUARDED_BY(mLock);
+
+ /**
+ * Whether or not to extract the signature. Must be set on the main thread
+ * before setTarget is called.
+ */
+ bool mSignatureInfoEnabled MOZ_GUARDED_BY(mLock){false};
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// State handled exclusively by the worker thread
+
+ /**
+ * Current target file associated to the input and output streams.
+ */
+ nsCOMPtr<nsIFile> mActualTarget;
+
+ /**
+ * Indicates whether mActualTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mActualTargetKeepPartial{false};
+
+ /**
+ * Used to calculate the file hash. This keeps state across file renames and
+ * is lazily initialized in ProcessStateChange.
+ */
+ Maybe<Digest> mDigest;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Private methods
+
+ /**
+ * Called when NS_AsyncCopy completes.
+ *
+ * @param aClosure
+ * Populated with a raw pointer to the BackgroundFileSaver object.
+ * @param aStatus
+ * Success or failure status specified when the copy was interrupted.
+ */
+ static void AsyncCopyCallback(void* aClosure, nsresult aStatus);
+
+ /**
+ * Called on the control thread after state changes, to ensure that the worker
+ * thread will process the state change appropriately.
+ *
+ * @param aShouldInterruptCopy
+ * If true, the current NS_AsyncCopy, if any, is canceled.
+ */
+ nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy);
+
+ /**
+ * Event called on the worker thread to begin processing a state change.
+ */
+ nsresult ProcessAttention();
+
+ /**
+ * Called by ProcessAttention to execute the operations corresponding to the
+ * state change. If this results in an error, ProcessAttention will force the
+ * entire operation to be aborted.
+ */
+ nsresult ProcessStateChange();
+
+ /**
+ * Returns true if completion conditions are met on the worker thread. The
+ * first time this happens, posts the completion event to the control thread.
+ */
+ bool CheckCompletion();
+
+ /**
+ * Event called on the control thread to indicate that file contents will now
+ * be saved to the specified file.
+ */
+ nsresult NotifyTargetChange(nsIFile* aTarget);
+
+ /**
+ * Event called on the control thread to send the final notification.
+ */
+ nsresult NotifySaveComplete();
+
+ /**
+ * Verifies the signature of the binary at the specified file path and stores
+ * the signature data in mSignatureInfo. We extract only X.509 certificates,
+ * since that is what Google's Safebrowsing protocol specifies.
+ */
+ nsresult ExtractSignatureInfo(const nsAString& filePath);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+class BackgroundFileSaverOutputStream : public BackgroundFileSaver,
+ public nsIAsyncOutputStream,
+ public nsIOutputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ BackgroundFileSaverOutputStream();
+
+ protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+ private:
+ ~BackgroundFileSaverOutputStream() = default;
+
+ /**
+ * Original callback provided to our AsyncWait wrapper.
+ */
+ nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener. This class is instantiated by
+// nsExternalHelperAppService, DownloadCore.sys.mjs, and possibly others.
+
+class BackgroundFileSaverStreamListener final : public BackgroundFileSaver,
+ public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ BackgroundFileSaverStreamListener() = default;
+
+ protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+ private:
+ ~BackgroundFileSaverStreamListener() = default;
+
+ /**
+ * Protects the state related to whether the request should be suspended.
+ */
+ mozilla::Mutex mSuspensionLock{
+ "BackgroundFileSaverStreamListener.mSuspensionLock"};
+
+ /**
+ * Whether we should suspend the request because we received too much data.
+ */
+ bool mReceivedTooMuchData MOZ_GUARDED_BY(mSuspensionLock){false};
+
+ /**
+ * Request for which we received too much data. This is populated when
+ * mReceivedTooMuchData becomes true for the first time.
+ */
+ nsCOMPtr<nsIRequest> mRequest MOZ_GUARDED_BY(mSuspensionLock);
+
+ /**
+ * Whether mRequest is currently suspended.
+ */
+ bool mRequestSuspended MOZ_GUARDED_BY(mSuspensionLock){false};
+
+ /**
+ * Called while NS_AsyncCopy is copying data.
+ */
+ static void AsyncCopyProgressCallback(void* aClosure, uint32_t aCount);
+
+ /**
+ * Called on the control thread to suspend or resume the request.
+ */
+ nsresult NotifySuspendOrResume();
+};
+
+// A wrapper around nsIOutputStream, so that we can compute hashes on the
+// stream without copying and without polluting pristine NSS code with XPCOM
+// interfaces.
+class DigestOutputStream : public nsIOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ // Constructor. Neither parameter may be null. The caller owns both.
+ DigestOutputStream(nsIOutputStream* aStream, Digest& aDigest);
+
+ private:
+ virtual ~DigestOutputStream() = default;
+
+ // Calls to write are passed to this stream.
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ // Digest used to compute the hash, owned by the caller.
+ Digest& mDigest;
+
+ // Don't accidentally copy construct.
+ DigestOutputStream(const DigestOutputStream& d) = delete;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/CacheInfoIPCTypes.h b/netwerk/base/CacheInfoIPCTypes.h
new file mode 100644
index 0000000000..b3d2277335
--- /dev/null
+++ b/netwerk/base/CacheInfoIPCTypes.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_CacheInfoIPCTypes_h_
+#define mozilla_net_CacheInfoIPCTypes_h_
+
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsICacheInfoChannel.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<nsICacheInfoChannel::PreferredAlternativeDataDeliveryType>
+ : public ContiguousEnumSerializerInclusive<
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType,
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::NONE,
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::
+ SERIALIZE> {};
+
+} // namespace IPC
+
+#endif // mozilla_net_CacheInfoIPCTypes_h_
diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp
new file mode 100644
index 0000000000..97a1dde929
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -0,0 +1,428 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+#include "xpcpublic.h"
+#include "xpcprivate.h"
+
+static constexpr auto kInterfaceName = u"captive-portal-inteface"_ns;
+
+static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
+static const char kAbortCaptivePortalLoginEvent[] =
+ "captive-portal-login-abort";
+static const char kCaptivePortalLoginSuccessEvent[] =
+ "captive-portal-login-success";
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gCaptivePortalLog("CaptivePortalService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver,
+ nsISupportsWeakReference, nsITimerCallback,
+ nsICaptivePortalCallback, nsINamed)
+
+static StaticRefPtr<CaptivePortalService> gCPService;
+
+// static
+already_AddRefed<nsICaptivePortalService> CaptivePortalService::GetSingleton() {
+ if (gCPService) {
+ return do_AddRef(gCPService);
+ }
+
+ gCPService = new CaptivePortalService();
+ ClearOnShutdown(&gCPService);
+ return do_AddRef(gCPService);
+}
+
+CaptivePortalService::CaptivePortalService() {
+ mLastChecked = TimeStamp::Now();
+}
+
+CaptivePortalService::~CaptivePortalService() {
+ LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
+ XRE_GetProcessType() == GeckoProcessType_Default));
+}
+
+nsresult CaptivePortalService::PerformCheck() {
+ LOG(
+ ("CaptivePortalService::PerformCheck mRequestInProgress:%d "
+ "mInitialized:%d mStarted:%d\n",
+ mRequestInProgress, mInitialized, mStarted));
+ // Don't issue another request if last one didn't complete
+ if (mRequestInProgress || !mInitialized || !mStarted) {
+ return NS_OK;
+ }
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+ // Instantiating CaptiveDetect.sys.mjs before the JS engine is ready will
+ // lead to a crash (see bug 1800603)
+ // We can remove this restriction when we rewrite the detector in
+ // C++ or rust (bug 1809886).
+ if (!XPCJSRuntime::Get()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ nsresult rv;
+ if (!mCaptivePortalDetector) {
+ mCaptivePortalDetector =
+ do_CreateInstance("@mozilla.org/toolkit/captive-detector;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to get a captive portal detector\n"));
+ return rv;
+ }
+ }
+
+ LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
+ mRequestInProgress = true;
+ mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this);
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::RearmTimer() {
+ LOG(("CaptivePortalService::RearmTimer\n"));
+ // Start a timer to recheck
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ mTimer = nullptr;
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ // If we have successfully determined the state, and we have never detected
+ // a captive portal, we don't need to keep polling, but will rely on events
+ // to trigger detection.
+ if (mState == NOT_CAPTIVE) {
+ return NS_OK;
+ }
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ if (mTimer && mDelay > 0) {
+ LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay));
+ return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::Initialize() {
+ if (mInitialized) {
+ return NS_OK;
+ }
+ mInitialized = true;
+
+ // Only the main process service should actually do anything. The service in
+ // the content process only mirrors the CP state in the main process.
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ }
+
+ LOG(("Initialized CaptivePortalService\n"));
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::Start() {
+ if (!mInitialized) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (xpc::AreNonLocalConnectionsDisabled() &&
+ !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ if (mStarted) {
+ return NS_OK;
+ }
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
+ mStarted = true;
+ mEverBeenCaptive = false;
+
+ // Get the delay prefs
+ Preferences::GetUint("network.captive-portal-service.minInterval",
+ &mMinInterval);
+ Preferences::GetUint("network.captive-portal-service.maxInterval",
+ &mMaxInterval);
+ Preferences::GetFloat("network.captive-portal-service.backoffFactor",
+ &mBackoffFactor);
+
+ LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval,
+ mMaxInterval, mBackoffFactor));
+
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ // When Start is called, perform a check immediately
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::Stop() {
+ LOG(("CaptivePortalService::Stop\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything when called in the content process.
+ return NS_OK;
+ }
+
+ if (!mStarted) {
+ return NS_OK;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mRequestInProgress = false;
+ mStarted = false;
+ mEverBeenCaptive = false;
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->Abort(kInterfaceName);
+ }
+ mCaptivePortalDetector = nullptr;
+
+ // Clear the state in case anyone queries the state while detection is off.
+ mState = UNKNOWN;
+ return NS_OK;
+}
+
+void CaptivePortalService::SetStateInChild(int32_t aState) {
+ // This should only be called in the content process, from ContentChild.cpp
+ // in order to mirror the captive portal state set in the chrome process.
+ MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
+
+ mState = aState;
+ mLastChecked = TimeStamp::Now();
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+CaptivePortalService::GetState(int32_t* aState) {
+ *aState = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::RecheckCaptivePortal() {
+ LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ // This is called for user activity. We need to reset the slack count,
+ // so the checks continue to be quite frequent.
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::GetLastChecked(uint64_t* aLastChecked) {
+ double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds();
+ *aLastChecked = static_cast<uint64_t>(duration);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsITimer
+// This callback gets called every mDelay miliseconds
+// It issues a checkCaptivePortal operation if one isn't already in progress
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Notify(nsITimer* aTimer) {
+ LOG(("CaptivePortalService::Notify\n"));
+ MOZ_ASSERT(aTimer == mTimer);
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ PerformCheck();
+
+ // This is needed because we don't want to always make requests very often.
+ // Every 10 checks, we the delay is increased mBackoffFactor times
+ // to a maximum delay of mMaxInterval
+ mSlackCount++;
+ if (mSlackCount % 10 == 0) {
+ mDelay = mDelay * mBackoffFactor;
+ }
+ if (mDelay > mMaxInterval) {
+ mDelay = mMaxInterval;
+ }
+
+ // Note - if mDelay is 0, the timer will not be rearmed.
+ RearmTimer();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsINamed
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+CaptivePortalService::GetName(nsACString& aName) {
+ aName.AssignLiteral("CaptivePortalService");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsIObserver
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic));
+ if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
+ // A redirect or altered content has been detected.
+ // The user needs to log in. We are in a captive portal.
+ StateTransition(LOCKED_PORTAL);
+ mLastChecked = TimeStamp::Now();
+ mEverBeenCaptive = true;
+ } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) {
+ // The user has successfully logged in. We have connectivity.
+ StateTransition(UNLOCKED_PORTAL);
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ RearmTimer();
+ } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
+ // The login has been aborted
+ StateTransition(UNKNOWN);
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Stop();
+ return NS_OK;
+ }
+
+ // Send notification so that the captive portal state is mirrored in the
+ // content process.
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE,
+ nullptr);
+ }
+
+ return NS_OK;
+}
+
+void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive) {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(cps, NS_CAPTIVE_PORTAL_CONNECTIVITY,
+ aCaptive ? u"captive" : u"clear");
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalCallback
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Prepare() {
+ LOG(("CaptivePortalService::Prepare\n"));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_OK;
+ }
+ // XXX: Finish preparation shouldn't be called until dns and routing is
+ // available.
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->FinishPreparation(kInterfaceName);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::Complete(bool success) {
+ LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success,
+ mState));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ mLastChecked = TimeStamp::Now();
+
+ // Note: this callback gets called when:
+ // 1. the request is completed, and content is valid (success == true)
+ // 2. when the request is aborted or times out (success == false)
+
+ if (success) {
+ if (mEverBeenCaptive) {
+ StateTransition(UNLOCKED_PORTAL);
+ NotifyConnectivityAvailable(true);
+ } else {
+ StateTransition(NOT_CAPTIVE);
+ NotifyConnectivityAvailable(false);
+ }
+ }
+
+ mRequestInProgress = false;
+ return NS_OK;
+}
+
+void CaptivePortalService::StateTransition(int32_t aNewState) {
+ int32_t oldState = mState;
+ mState = aNewState;
+
+ if ((oldState == UNKNOWN && mState == NOT_CAPTIVE) ||
+ (oldState == LOCKED_PORTAL && mState == UNLOCKED_PORTAL)) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(
+ cps, NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED, nullptr);
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h
new file mode 100644
index 0000000000..f0a7200b4b
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.h
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CaptivePortalService_h_
+#define CaptivePortalService_h_
+
+#include "nsICaptivePortalService.h"
+#include "nsICaptivePortalDetector.h"
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsCOMArray.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace net {
+
+class CaptivePortalService : public nsICaptivePortalService,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsITimerCallback,
+ public nsICaptivePortalCallback,
+ public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICAPTIVEPORTALSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSICAPTIVEPORTALCALLBACK
+ NS_DECL_NSINAMED
+
+ nsresult Initialize();
+ nsresult Start();
+ nsresult Stop();
+
+ static already_AddRefed<nsICaptivePortalService> GetSingleton();
+
+ // This method is only called in the content process, in order to mirror
+ // the captive portal state in the parent process.
+ void SetStateInChild(int32_t aState);
+
+ private:
+ static const uint32_t kDefaultInterval = 60 * 1000; // check every 60 seconds
+
+ CaptivePortalService();
+ virtual ~CaptivePortalService();
+ nsresult PerformCheck();
+ nsresult RearmTimer();
+ void NotifyConnectivityAvailable(bool aCaptive);
+
+ nsCOMPtr<nsICaptivePortalDetector> mCaptivePortalDetector;
+ int32_t mState{UNKNOWN};
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mStarted{false};
+ bool mInitialized{false};
+ bool mRequestInProgress{false};
+ bool mEverBeenCaptive{false};
+
+ uint32_t mDelay{kDefaultInterval};
+ int32_t mSlackCount{0};
+
+ uint32_t mMinInterval{kDefaultInterval};
+ uint32_t mMaxInterval{25 * kDefaultInterval};
+ float mBackoffFactor{5.0};
+
+ void StateTransition(int32_t aNewState);
+
+ // This holds a timestamp when the last time when the captive portal check
+ // has changed state.
+ mozilla::TimeStamp mLastChecked;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // CaptivePortalService_h_
diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp
new file mode 100644
index 0000000000..bc1619ee60
--- /dev/null
+++ b/netwerk/base/Dashboard.cpp
@@ -0,0 +1,1182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/NetDashboardBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/net/Dashboard.h"
+#include "mozilla/net/HttpInfo.h"
+#include "mozilla/net/HTTPSSVC.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttp.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIInputStream.h"
+#include "nsINamed.h"
+#include "nsINetAddr.h"
+#include "nsISocketTransport.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/Logging.h"
+#include "nsIOService.h"
+#include "../cache2/CacheFileUtils.h"
+
+using mozilla::AutoSafeJSContext;
+using mozilla::dom::Sequence;
+using mozilla::dom::ToJSValue;
+
+namespace mozilla {
+namespace net {
+
+class SocketData : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SocketData() = default;
+
+ uint64_t mTotalSent{0};
+ uint64_t mTotalRecv{0};
+ nsTArray<SocketInfo> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+
+ private:
+ virtual ~SocketData() = default;
+};
+
+static void GetErrorString(nsresult rv, nsAString& errorString);
+
+NS_IMPL_ISUPPORTS0(SocketData)
+
+class HttpData : public nsISupports {
+ virtual ~HttpData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HttpData() = default;
+
+ nsTArray<HttpRetParams> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(HttpData)
+
+class WebSocketRequest : public nsISupports {
+ virtual ~WebSocketRequest() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WebSocketRequest() = default;
+
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(WebSocketRequest)
+
+class DnsData : public nsISupports {
+ virtual ~DnsData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DnsData() = default;
+
+ nsTArray<DNSCacheEntries> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(DnsData)
+
+class ConnectionData : public nsITransportEventSink,
+ public nsITimerCallback,
+ public nsINamed {
+ virtual ~ConnectionData() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ }
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSITIMERCALLBACK
+
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("net::ConnectionData");
+ return NS_OK;
+ }
+
+ void StartTimer(uint32_t aTimeout);
+ void StopTimer();
+
+ explicit ConnectionData(Dashboard* target) { mDashboard = target; }
+
+ nsCOMPtr<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIInputStream> mStreamIn;
+ nsCOMPtr<nsITimer> mTimer;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+ Dashboard* mDashboard;
+
+ nsCString mHost;
+ uint32_t mPort{0};
+ nsCString mProtocol;
+ uint32_t mTimeout{0};
+
+ nsString mStatus;
+};
+
+NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback,
+ nsINamed)
+
+class RcwnData : public nsISupports {
+ virtual ~RcwnData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ RcwnData() = default;
+
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(RcwnData)
+
+NS_IMETHODIMP
+ConnectionData::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+ int64_t aProgress, int64_t aProgressMax) {
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
+ StopTimer();
+ }
+
+ GetErrorString(aStatus, mStatus);
+ mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>(
+ "net::Dashboard::GetConnectionStatus", mDashboard,
+ &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ConnectionData::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(aTimer == mTimer);
+
+ if (mSocket) {
+ mSocket->Close(NS_ERROR_ABORT);
+ mSocket = nullptr;
+ mStreamIn = nullptr;
+ }
+
+ mTimer = nullptr;
+
+ mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT");
+ mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>(
+ "net::Dashboard::GetConnectionStatus", mDashboard,
+ &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void ConnectionData::StartTimer(uint32_t aTimeout) {
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ mTimer->InitWithCallback(this, aTimeout * 1000, nsITimer::TYPE_ONE_SHOT);
+}
+
+void ConnectionData::StopTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+class LookupHelper;
+
+class LookupArgument : public nsISupports {
+ virtual ~LookupArgument() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ LookupArgument(nsIDNSRecord* aRecord, LookupHelper* aHelper) {
+ mRecord = aRecord;
+ mHelper = aHelper;
+ }
+
+ nsCOMPtr<nsIDNSRecord> mRecord;
+ RefPtr<LookupHelper> mHelper;
+};
+
+NS_IMPL_ISUPPORTS0(LookupArgument)
+
+class LookupHelper final : public nsIDNSListener {
+ virtual ~LookupHelper() {
+ if (mCancel) {
+ mCancel->Cancel(NS_ERROR_ABORT);
+ }
+ }
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ LookupHelper() = default;
+
+ nsresult ConstructAnswer(LookupArgument* aArgument);
+ nsresult ConstructHTTPSRRAnswer(LookupArgument* aArgument);
+
+ public:
+ nsCOMPtr<nsICancelable> mCancel;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+ nsresult mStatus{NS_ERROR_NOT_INITIALIZED};
+};
+
+NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener)
+
+NS_IMETHODIMP
+LookupHelper::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord,
+ nsresult aStatus) {
+ MOZ_ASSERT(aRequest == mCancel);
+ mCancel = nullptr;
+ mStatus = aStatus;
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = do_QueryInterface(aRecord);
+ if (httpsRecord) {
+ RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
+ mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<LookupArgument>>(
+ "net::LookupHelper::ConstructHTTPSRRAnswer", this,
+ &LookupHelper::ConstructHTTPSRRAnswer, arg),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
+ mEventTarget->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>>(
+ "net::LookupHelper::ConstructAnswer", this,
+ &LookupHelper::ConstructAnswer, arg),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult LookupHelper::ConstructAnswer(LookupArgument* aArgument) {
+ nsIDNSRecord* aRecord = aArgument->mRecord;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSLookupDict dict;
+ dict.mAddress.Construct();
+
+ Sequence<nsString>& addresses = dict.mAddress.Value();
+ nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord);
+ if (NS_SUCCEEDED(mStatus) && record) {
+ dict.mAnswer = true;
+ bool hasMore;
+ record->HasMore(&hasMore);
+ while (hasMore) {
+ nsString* nextAddress = addresses.AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString nextAddressASCII;
+ record->GetNextAddrAsString(nextAddressASCII);
+ CopyASCIItoUTF16(nextAddressASCII, *nextAddress);
+ record->HasMore(&hasMore);
+ }
+ } else {
+ dict.mAnswer = false;
+ GetErrorString(mStatus, dict.mError);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ this->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+static void CStringToHexString(const nsACString& aIn, nsAString& aOut) {
+ static const char* const lut = "0123456789ABCDEF";
+
+ size_t len = aIn.Length();
+
+ aOut.SetCapacity(2 * len);
+ for (size_t i = 0; i < aIn.Length(); ++i) {
+ const char c = static_cast<char>(aIn[i]);
+ aOut.Append(lut[(c >> 4) & 0x0F]);
+ aOut.Append(lut[c & 15]);
+ }
+}
+
+nsresult LookupHelper::ConstructHTTPSRRAnswer(LookupArgument* aArgument) {
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord =
+ do_QueryInterface(aArgument->mRecord);
+
+ AutoSafeJSContext cx;
+
+ mozilla::dom::HTTPSRRLookupDict dict;
+ dict.mRecords.Construct();
+
+ Sequence<dom::HTTPSRecord>& records = dict.mRecords.Value();
+ if (NS_SUCCEEDED(mStatus) && httpsRecord) {
+ dict.mAnswer = true;
+ nsTArray<RefPtr<nsISVCBRecord>> svcbRecords;
+ httpsRecord->GetRecords(svcbRecords);
+
+ for (const auto& record : svcbRecords) {
+ dom::HTTPSRecord* nextRecord = records.AppendElement(fallible);
+ if (!nextRecord) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ Unused << record->GetPriority(&nextRecord->mPriority);
+ nsCString name;
+ Unused << record->GetName(name);
+ CopyASCIItoUTF16(name, nextRecord->mTargetName);
+
+ nsTArray<RefPtr<nsISVCParam>> values;
+ Unused << record->GetValues(values);
+ if (values.IsEmpty()) {
+ continue;
+ }
+
+ for (const auto& value : values) {
+ uint16_t type;
+ Unused << value->GetType(&type);
+ switch (type) {
+ case SvcParamKeyAlpn: {
+ nextRecord->mAlpn.Construct();
+ nextRecord->mAlpn.Value().mType = type;
+ nsCOMPtr<nsISVCParamAlpn> alpnParam = do_QueryInterface(value);
+ nsTArray<nsCString> alpn;
+ Unused << alpnParam->GetAlpn(alpn);
+ nsAutoCString alpnStr;
+ for (const auto& str : alpn) {
+ alpnStr.Append(str);
+ alpnStr.Append(',');
+ }
+ CopyASCIItoUTF16(Span(alpnStr.BeginReading(), alpnStr.Length() - 1),
+ nextRecord->mAlpn.Value().mAlpn);
+ break;
+ }
+ case SvcParamKeyNoDefaultAlpn: {
+ nextRecord->mNoDefaultAlpn.Construct();
+ nextRecord->mNoDefaultAlpn.Value().mType = type;
+ break;
+ }
+ case SvcParamKeyPort: {
+ nextRecord->mPort.Construct();
+ nextRecord->mPort.Value().mType = type;
+ nsCOMPtr<nsISVCParamPort> portParam = do_QueryInterface(value);
+ Unused << portParam->GetPort(&nextRecord->mPort.Value().mPort);
+ break;
+ }
+ case SvcParamKeyIpv4Hint: {
+ nextRecord->mIpv4Hint.Construct();
+ nextRecord->mIpv4Hint.Value().mType = type;
+ nsCOMPtr<nsISVCParamIPv4Hint> ipv4Param = do_QueryInterface(value);
+ nsTArray<RefPtr<nsINetAddr>> ipv4Hint;
+ Unused << ipv4Param->GetIpv4Hint(ipv4Hint);
+ if (!ipv4Hint.IsEmpty()) {
+ nextRecord->mIpv4Hint.Value().mAddress.Construct();
+ for (const auto& address : ipv4Hint) {
+ nsString* nextAddress = nextRecord->mIpv4Hint.Value()
+ .mAddress.Value()
+ .AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString addressASCII;
+ Unused << address->GetAddress(addressASCII);
+ CopyASCIItoUTF16(addressASCII, *nextAddress);
+ }
+ }
+ break;
+ }
+ case SvcParamKeyIpv6Hint: {
+ nextRecord->mIpv6Hint.Construct();
+ nextRecord->mIpv6Hint.Value().mType = type;
+ nsCOMPtr<nsISVCParamIPv6Hint> ipv6Param = do_QueryInterface(value);
+ nsTArray<RefPtr<nsINetAddr>> ipv6Hint;
+ Unused << ipv6Param->GetIpv6Hint(ipv6Hint);
+ if (!ipv6Hint.IsEmpty()) {
+ nextRecord->mIpv6Hint.Value().mAddress.Construct();
+ for (const auto& address : ipv6Hint) {
+ nsString* nextAddress = nextRecord->mIpv6Hint.Value()
+ .mAddress.Value()
+ .AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString addressASCII;
+ Unused << address->GetAddress(addressASCII);
+ CopyASCIItoUTF16(addressASCII, *nextAddress);
+ }
+ }
+ break;
+ }
+ case SvcParamKeyEchConfig: {
+ nextRecord->mEchConfig.Construct();
+ nextRecord->mEchConfig.Value().mType = type;
+ nsCOMPtr<nsISVCParamEchConfig> echConfigParam =
+ do_QueryInterface(value);
+ nsCString echConfigStr;
+ Unused << echConfigParam->GetEchconfig(echConfigStr);
+ CStringToHexString(echConfigStr,
+ nextRecord->mEchConfig.Value().mEchConfig);
+ break;
+ }
+ case SvcParamKeyODoHConfig: {
+ nextRecord->mODoHConfig.Construct();
+ nextRecord->mODoHConfig.Value().mType = type;
+ nsCOMPtr<nsISVCParamODoHConfig> ODoHConfigParam =
+ do_QueryInterface(value);
+ nsCString ODoHConfigStr;
+ Unused << ODoHConfigParam->GetODoHConfig(ODoHConfigStr);
+ CStringToHexString(ODoHConfigStr,
+ nextRecord->mODoHConfig.Value().mODoHConfig);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ } else {
+ dict.mAnswer = false;
+ GetErrorString(mStatus, dict.mError);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ this->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier)
+
+Dashboard::Dashboard() { mEnableLogging = false; }
+
+NS_IMETHODIMP
+Dashboard::RequestSockets(nsINetDashboardCallback* aCallback) {
+ RefPtr<SocketData> socketData = new SocketData();
+ socketData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ socketData->mEventTarget = GetCurrentSerialEventTarget();
+
+ if (nsIOService::UseSocketProcess()) {
+ if (!gIOService->SocketProcessReady()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<Dashboard> self(this);
+ SocketProcessParent::GetSingleton()->SendGetSocketData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self{std::move(self)},
+ socketData{std::move(socketData)}](SocketDataArgs&& args) {
+ socketData->mData.Assign(args.info());
+ socketData->mTotalSent = args.totalSent();
+ socketData->mTotalRecv = args.totalRecv();
+ socketData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<SocketData>>(
+ "net::Dashboard::GetSockets", self, &Dashboard::GetSockets,
+ socketData),
+ NS_DISPATCH_NORMAL);
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {});
+ return NS_OK;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<SocketData>>(
+ "net::Dashboard::GetSocketsDispatch", this,
+ &Dashboard::GetSocketsDispatch, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetSocketsDispatch(SocketData* aSocketData) {
+ RefPtr<SocketData> socketData = aSocketData;
+ if (gSocketTransportService) {
+ gSocketTransportService->GetSocketConnections(&socketData->mData);
+ socketData->mTotalSent = gSocketTransportService->GetSentBytes();
+ socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes();
+ }
+ socketData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<SocketData>>("net::Dashboard::GetSockets", this,
+ &Dashboard::GetSockets, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetSockets(SocketData* aSocketData) {
+ RefPtr<SocketData> socketData = aSocketData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::SocketsDict dict;
+ dict.mSockets.Construct();
+ dict.mSent = 0;
+ dict.mReceived = 0;
+
+ Sequence<mozilla::dom::SocketElement>& sockets = dict.mSockets.Value();
+
+ uint32_t length = socketData->mData.Length();
+ if (!sockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < socketData->mData.Length(); i++) {
+ dom::SocketElement& mSocket = *sockets.AppendElement(fallible);
+ CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost);
+ mSocket.mPort = socketData->mData[i].port;
+ mSocket.mActive = socketData->mData[i].active;
+ CopyASCIItoUTF16(socketData->mData[i].type, mSocket.mType);
+ mSocket.mSent = (double)socketData->mData[i].sent;
+ mSocket.mReceived = (double)socketData->mData[i].received;
+ dict.mSent += socketData->mData[i].sent;
+ dict.mReceived += socketData->mData[i].received;
+ }
+
+ dict.mSent += socketData->mTotalSent;
+ dict.mReceived += socketData->mTotalRecv;
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;
+ socketData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestHttpConnections(nsINetDashboardCallback* aCallback) {
+ RefPtr<HttpData> httpData = new HttpData();
+ httpData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ httpData->mEventTarget = GetCurrentSerialEventTarget();
+
+ if (nsIOService::UseSocketProcess()) {
+ if (!gIOService->SocketProcessReady()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<Dashboard> self(this);
+ SocketProcessParent::GetSingleton()->SendGetHttpConnectionData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self{std::move(self)}, httpData](nsTArray<HttpRetParams>&& params) {
+ httpData->mData.Assign(std::move(params));
+ self->GetHttpConnections(httpData);
+ httpData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<HttpData>>(
+ "net::Dashboard::GetHttpConnections", self,
+ &Dashboard::GetHttpConnections, httpData),
+ NS_DISPATCH_NORMAL);
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {});
+ return NS_OK;
+ }
+
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<HttpData>>(
+ "net::Dashboard::GetHttpDispatch", this,
+ &Dashboard::GetHttpDispatch, httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetHttpDispatch(HttpData* aHttpData) {
+ RefPtr<HttpData> httpData = aHttpData;
+ HttpInfo::GetHttpConnectionData(&httpData->mData);
+ httpData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<HttpData>>("net::Dashboard::GetHttpConnections",
+ this, &Dashboard::GetHttpConnections,
+ httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetHttpConnections(HttpData* aHttpData) {
+ RefPtr<HttpData> httpData = aHttpData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::HttpConnDict dict;
+ dict.mConnections.Construct();
+
+ using mozilla::dom::DnsAndSockInfoDict;
+ using mozilla::dom::HttpConnectionElement;
+ using mozilla::dom::HttpConnInfo;
+ Sequence<HttpConnectionElement>& connections = dict.mConnections.Value();
+
+ uint32_t length = httpData->mData.Length();
+ if (!connections.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < httpData->mData.Length(); i++) {
+ HttpConnectionElement& connection = *connections.AppendElement(fallible);
+
+ CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost);
+ connection.mPort = httpData->mData[i].port;
+ CopyASCIItoUTF16(httpData->mData[i].httpVersion, connection.mHttpVersion);
+ connection.mSsl = httpData->mData[i].ssl;
+
+ connection.mActive.Construct();
+ connection.mIdle.Construct();
+ connection.mDnsAndSocks.Construct();
+
+ Sequence<HttpConnInfo>& active = connection.mActive.Value();
+ Sequence<HttpConnInfo>& idle = connection.mIdle.Value();
+ Sequence<DnsAndSockInfoDict>& dnsAndSocks = connection.mDnsAndSocks.Value();
+
+ if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) ||
+ !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) ||
+ !dnsAndSocks.SetCapacity(httpData->mData[i].dnsAndSocks.Length(),
+ fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) {
+ HttpConnInfo& info = *active.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].active[j].rtt;
+ info.mTtl = httpData->mData[i].active[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].active[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) {
+ HttpConnInfo& info = *idle.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].idle[j].rtt;
+ info.mTtl = httpData->mData[i].idle[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].dnsAndSocks.Length(); j++) {
+ DnsAndSockInfoDict& info = *dnsAndSocks.AppendElement(fallible);
+ info.mSpeculative = httpData->mData[i].dnsAndSocks[j].speculative;
+ }
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ httpData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::GetEnableLogging(bool* value) {
+ *value = mEnableLogging;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::SetEnableLogging(const bool value) {
+ mEnableLogging = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ LogData mData(nsCString(aHost), aSerial, aEncrypted);
+ if (mWs.data.Contains(mData)) {
+ return NS_OK;
+ }
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mWs.data.AppendElement(mData);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1) return NS_ERROR_FAILURE;
+ mWs.data.RemoveElementAt(index);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial,
+ uint32_t aLength) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1) return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgSent++;
+ mWs.data[index].mSizeSent += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial,
+ uint32_t aLength) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1) return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgReceived++;
+ mWs.data[index].mSizeReceived += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestWebsocketConnections(nsINetDashboardCallback* aCallback) {
+ RefPtr<WebSocketRequest> wsRequest = new WebSocketRequest();
+ wsRequest->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ wsRequest->mEventTarget = GetCurrentSerialEventTarget();
+
+ wsRequest->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<WebSocketRequest>>(
+ "net::Dashboard::GetWebSocketConnections", this,
+ &Dashboard::GetWebSocketConnections, wsRequest),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetWebSocketConnections(WebSocketRequest* aWsRequest) {
+ RefPtr<WebSocketRequest> wsRequest = aWsRequest;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::WebSocketDict dict;
+ dict.mWebsockets.Construct();
+ Sequence<mozilla::dom::WebSocketElement>& websockets =
+ dict.mWebsockets.Value();
+
+ mozilla::MutexAutoLock lock(mWs.lock);
+ uint32_t length = mWs.data.Length();
+ if (!websockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < mWs.data.Length(); i++) {
+ dom::WebSocketElement& websocket = *websockets.AppendElement(fallible);
+ CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport);
+ websocket.mMsgsent = mWs.data[i].mMsgSent;
+ websocket.mMsgreceived = mWs.data[i].mMsgReceived;
+ websocket.mSentsize = mWs.data[i].mSizeSent;
+ websocket.mReceivedsize = mWs.data[i].mSizeReceived;
+ websocket.mEncrypted = mWs.data[i].mEncrypted;
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ wsRequest->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSInfo(nsINetDashboardCallback* aCallback) {
+ RefPtr<DnsData> dnsData = new DnsData();
+ dnsData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+
+ nsresult rv;
+ dnsData->mData.Clear();
+ dnsData->mEventTarget = GetCurrentSerialEventTarget();
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (nsIOService::UseSocketProcess()) {
+ if (!gIOService->SocketProcessReady()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<Dashboard> self(this);
+ SocketProcessParent::GetSingleton()->SendGetDNSCacheEntries()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self{std::move(self)},
+ dnsData{std::move(dnsData)}](nsTArray<DNSCacheEntries>&& entries) {
+ dnsData->mData.Assign(std::move(entries));
+ dnsData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<DnsData>>(
+ "net::Dashboard::GetDNSCacheEntries", self,
+ &Dashboard::GetDNSCacheEntries, dnsData),
+ NS_DISPATCH_NORMAL);
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {});
+ return NS_OK;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDnsInfoDispatch",
+ this, &Dashboard::GetDnsInfoDispatch,
+ dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetDnsInfoDispatch(DnsData* aDnsData) {
+ RefPtr<DnsData> dnsData = aDnsData;
+ if (mDnsService) {
+ mDnsService->GetDNSCacheEntries(&dnsData->mData);
+ }
+ dnsData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDNSCacheEntries",
+ this, &Dashboard::GetDNSCacheEntries,
+ dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetDNSCacheEntries(DnsData* dnsData) {
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSCacheDict dict;
+ dict.mEntries.Construct();
+ Sequence<mozilla::dom::DnsCacheEntry>& entries = dict.mEntries.Value();
+
+ uint32_t length = dnsData->mData.Length();
+ if (!entries.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < dnsData->mData.Length(); i++) {
+ dom::DnsCacheEntry& entry = *entries.AppendElement(fallible);
+ entry.mHostaddr.Construct();
+
+ Sequence<nsString>& addrs = entry.mHostaddr.Value();
+ if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname);
+ entry.mExpiration = dnsData->mData[i].expiration;
+ entry.mTrr = dnsData->mData[i].TRR;
+
+ for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) {
+ nsString* addr = addrs.AppendElement(fallible);
+ if (!addr) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr);
+ }
+
+ if (dnsData->mData[i].family == PR_AF_INET6) {
+ entry.mFamily.AssignLiteral(u"ipv6");
+ } else {
+ entry.mFamily.AssignLiteral(u"ipv4");
+ }
+
+ entry.mOriginAttributesSuffix =
+ NS_ConvertUTF8toUTF16(dnsData->mData[i].originAttributesSuffix);
+ entry.mFlags = NS_ConvertUTF8toUTF16(dnsData->mData[i].flags);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ dnsData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSLookup(const nsACString& aHost,
+ nsINetDashboardCallback* aCallback) {
+ nsresult rv;
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<LookupHelper> helper = new LookupHelper();
+ helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ helper->mEventTarget = GetCurrentSerialEventTarget();
+ OriginAttributes attrs;
+ rv = mDnsService->AsyncResolveNative(
+ aHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(),
+ NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel));
+ return rv;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSHTTPSRRLookup(const nsACString& aHost,
+ nsINetDashboardCallback* aCallback) {
+ nsresult rv;
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<LookupHelper> helper = new LookupHelper();
+ helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ helper->mEventTarget = GetCurrentSerialEventTarget();
+ OriginAttributes attrs;
+ rv = mDnsService->AsyncResolveNative(
+ aHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(),
+ NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel));
+ return rv;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestRcwnStats(nsINetDashboardCallback* aCallback) {
+ RefPtr<RcwnData> rcwnData = new RcwnData();
+ rcwnData->mEventTarget = GetCurrentSerialEventTarget();
+ rcwnData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+
+ return rcwnData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<RcwnData>>("net::Dashboard::GetRcwnData", this,
+ &Dashboard::GetRcwnData, rcwnData),
+ NS_DISPATCH_NORMAL);
+}
+
+nsresult Dashboard::GetRcwnData(RcwnData* aData) {
+ AutoSafeJSContext cx;
+ mozilla::dom::RcwnStatus dict;
+
+ dict.mTotalNetworkRequests = gIOService->GetTotalRequestNumber();
+ dict.mRcwnCacheWonCount = gIOService->GetCacheWonRequestNumber();
+ dict.mRcwnNetWonCount = gIOService->GetNetWonRequestNumber();
+
+ uint32_t cacheSlow, cacheNotSlow;
+ CacheFileUtils::CachePerfStats::GetSlowStats(&cacheSlow, &cacheNotSlow);
+ dict.mCacheSlowCount = cacheSlow;
+ dict.mCacheNotSlowCount = cacheNotSlow;
+
+ dict.mPerfStats.Construct();
+ Sequence<mozilla::dom::RcwnPerfStats>& perfStats = dict.mPerfStats.Value();
+ uint32_t length = CacheFileUtils::CachePerfStats::LAST;
+ if (!perfStats.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < length; i++) {
+ CacheFileUtils::CachePerfStats::EDataType perfType =
+ static_cast<CacheFileUtils::CachePerfStats::EDataType>(i);
+ dom::RcwnPerfStats& elem = *perfStats.AppendElement(fallible);
+ elem.mAvgShort =
+ CacheFileUtils::CachePerfStats::GetAverage(perfType, false);
+ elem.mAvgLong = CacheFileUtils::CachePerfStats::GetAverage(perfType, true);
+ elem.mStddevLong =
+ CacheFileUtils::CachePerfStats::GetStdDev(perfType, true);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+void HttpConnInfo::SetHTTPProtocolVersion(HttpVersion pv) {
+ switch (pv) {
+ case HttpVersion::v0_9:
+ protocolVersion.AssignLiteral(u"http/0.9");
+ break;
+ case HttpVersion::v1_0:
+ protocolVersion.AssignLiteral(u"http/1.0");
+ break;
+ case HttpVersion::v1_1:
+ protocolVersion.AssignLiteral(u"http/1.1");
+ break;
+ case HttpVersion::v2_0:
+ protocolVersion.AssignLiteral(u"http/2");
+ break;
+ case HttpVersion::v3_0:
+ protocolVersion.AssignLiteral(u"http/3");
+ break;
+ default:
+ protocolVersion.AssignLiteral(u"unknown protocol version");
+ }
+}
+
+NS_IMETHODIMP
+Dashboard::GetLogPath(nsACString& aLogPath) {
+ aLogPath.SetLength(2048);
+ uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048);
+ aLogPath.SetLength(len);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort,
+ const char* aProtocol, uint32_t aTimeout,
+ nsINetDashboardCallback* aCallback) {
+ nsresult rv;
+ RefPtr<ConnectionData> connectionData = new ConnectionData(this);
+ connectionData->mHost = aHost;
+ connectionData->mPort = aPort;
+ connectionData->mProtocol = aProtocol;
+ connectionData->mTimeout = aTimeout;
+
+ connectionData->mCallback =
+ new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ connectionData->mEventTarget = GetCurrentSerialEventTarget();
+
+ rv = TestNewConnection(connectionData);
+ if (NS_FAILED(rv)) {
+ mozilla::net::GetErrorString(rv, connectionData->mStatus);
+ connectionData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<ConnectionData>>(
+ "net::Dashboard::GetConnectionStatus", this,
+ &Dashboard::GetConnectionStatus, connectionData),
+ NS_DISPATCH_NORMAL);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult Dashboard::GetConnectionStatus(ConnectionData* aConnectionData) {
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::ConnStatusDict dict;
+ dict.mStatus = connectionData->mStatus;
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;
+
+ connectionData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+nsresult Dashboard::TestNewConnection(ConnectionData* aConnectionData) {
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+
+ nsresult rv;
+ if (!connectionData->mHost.Length() ||
+ !net_IsValidHostName(connectionData->mHost)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (connectionData->mProtocol.EqualsLiteral("ssl")) {
+ AutoTArray<nsCString, 1> socketTypes = {connectionData->mProtocol};
+ rv = gSocketTransportService->CreateTransport(
+ socketTypes, connectionData->mHost, connectionData->mPort, nullptr,
+ nullptr, getter_AddRefs(connectionData->mSocket));
+ } else {
+ rv = gSocketTransportService->CreateTransport(
+ nsTArray<nsCString>(), connectionData->mHost, connectionData->mPort,
+ nullptr, nullptr, getter_AddRefs(connectionData->mSocket));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->SetEventSink(connectionData,
+ GetCurrentSerialEventTarget());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->OpenInputStream(
+ nsITransport::OPEN_BLOCKING, 0, 0,
+ getter_AddRefs(connectionData->mStreamIn));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ connectionData->StartTimer(connectionData->mTimeout);
+
+ return rv;
+}
+
+using ErrorEntry = struct {
+ nsresult key;
+ const char* error;
+};
+
+#undef ERROR
+#define ERROR(key, val) \
+ { key, #key }
+
+ErrorEntry socketTransportStatuses[] = {
+ ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)),
+ ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)),
+ ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)),
+ ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)),
+ ERROR(NS_NET_STATUS_TLS_HANDSHAKE_STARTING, FAILURE(12)),
+ ERROR(NS_NET_STATUS_TLS_HANDSHAKE_ENDED, FAILURE(13)),
+ ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)),
+ ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)),
+ ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)),
+};
+#undef ERROR
+
+static void GetErrorString(nsresult rv, nsAString& errorString) {
+ for (auto& socketTransportStatus : socketTransportStatuses) {
+ if (socketTransportStatus.key == rv) {
+ errorString.AssignASCII(socketTransportStatus.error);
+ return;
+ }
+ }
+ nsAutoCString errorCString;
+ mozilla::GetErrorName(rv, errorCString);
+ CopyUTF8toUTF16(errorCString, errorString);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Dashboard.h b/netwerk/base/Dashboard.h
new file mode 100644
index 0000000000..1112a013e9
--- /dev/null
+++ b/netwerk/base/Dashboard.h
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDashboard_h__
+#define nsDashboard_h__
+
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsIDashboard.h"
+#include "nsIDashboardEventNotifier.h"
+
+class nsIDNSService;
+
+namespace mozilla {
+namespace net {
+
+class SocketData;
+class HttpData;
+class DnsData;
+class WebSocketRequest;
+class ConnectionData;
+class RcwnData;
+
+class Dashboard final : public nsIDashboard, public nsIDashboardEventNotifier {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDASHBOARD
+ NS_DECL_NSIDASHBOARDEVENTNOTIFIER
+
+ Dashboard();
+ static const char* GetErrorString(nsresult rv);
+ nsresult GetConnectionStatus(ConnectionData* aConnectionData);
+
+ private:
+ struct LogData {
+ LogData(nsCString host, uint32_t serial, bool encryption)
+ : mHost(host),
+ mSerial(serial),
+ mMsgSent(0),
+ mMsgReceived(0),
+ mSizeSent(0),
+ mSizeReceived(0),
+ mEncrypted(encryption) {}
+ nsCString mHost;
+ uint32_t mSerial;
+ uint32_t mMsgSent;
+ uint32_t mMsgReceived;
+ uint64_t mSizeSent;
+ uint64_t mSizeReceived;
+ bool mEncrypted;
+ bool operator==(const LogData& a) const {
+ return mHost.Equals(a.mHost) && (mSerial == a.mSerial);
+ }
+ };
+
+ struct WebSocketData {
+ WebSocketData() : lock("Dashboard.webSocketData") {}
+ uint32_t IndexOf(const nsCString& hostname, uint32_t mSerial) {
+ LogData temp(hostname, mSerial, false);
+ return data.IndexOf(temp);
+ }
+ nsTArray<LogData> data;
+ mozilla::Mutex lock MOZ_UNANNOTATED;
+ };
+
+ bool mEnableLogging;
+ WebSocketData mWs;
+
+ private:
+ virtual ~Dashboard() = default;
+
+ nsresult GetSocketsDispatch(SocketData*);
+ nsresult GetHttpDispatch(HttpData*);
+ nsresult GetDnsInfoDispatch(DnsData*);
+ nsresult TestNewConnection(ConnectionData*);
+
+ /* Helper methods that pass the JSON to the callback function. */
+ nsresult GetSockets(SocketData*);
+ nsresult GetHttpConnections(HttpData*);
+ nsresult GetDNSCacheEntries(DnsData*);
+ nsresult GetWebSocketConnections(WebSocketRequest*);
+ nsresult GetRcwnData(RcwnData*);
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsDashboard_h__
diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h
new file mode 100644
index 0000000000..6dd901d02a
--- /dev/null
+++ b/netwerk/base/DashboardTypes.h
@@ -0,0 +1,175 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_DashboardTypes_h_
+#define mozilla_net_DashboardTypes_h_
+
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsHttp.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+struct SocketInfo {
+ nsCString host;
+ uint64_t sent;
+ uint64_t received;
+ uint16_t port;
+ bool active;
+ nsCString type;
+};
+
+inline bool operator==(const SocketInfo& a, const SocketInfo& b) {
+ return a.host == b.host && a.sent == b.sent && a.received == b.received &&
+ a.port == b.port && a.active == b.active && a.type == b.type;
+}
+
+struct DnsAndConnectSockets {
+ bool speculative;
+};
+
+struct DNSCacheEntries {
+ nsCString hostname;
+ nsTArray<nsCString> hostaddr;
+ uint16_t family;
+ int64_t expiration;
+ nsCString netInterface;
+ bool TRR;
+ nsCString originAttributesSuffix;
+ nsCString flags;
+};
+
+struct HttpConnInfo {
+ uint32_t ttl;
+ uint32_t rtt;
+ nsString protocolVersion;
+
+ void SetHTTPProtocolVersion(HttpVersion pv);
+};
+
+struct HttpRetParams {
+ nsCString host;
+ CopyableTArray<HttpConnInfo> active;
+ CopyableTArray<HttpConnInfo> idle;
+ CopyableTArray<DnsAndConnectSockets> dnsAndSocks;
+ uint32_t counter;
+ uint16_t port;
+ nsCString httpVersion;
+ bool ssl;
+};
+
+} // namespace net
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::net::SocketInfo> {
+ typedef mozilla::net::SocketInfo paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.host);
+ WriteParam(aWriter, aParam.sent);
+ WriteParam(aWriter, aParam.received);
+ WriteParam(aWriter, aParam.port);
+ WriteParam(aWriter, aParam.active);
+ WriteParam(aWriter, aParam.type);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->host) &&
+ ReadParam(aReader, &aResult->sent) &&
+ ReadParam(aReader, &aResult->received) &&
+ ReadParam(aReader, &aResult->port) &&
+ ReadParam(aReader, &aResult->active) &&
+ ReadParam(aReader, &aResult->type);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::DNSCacheEntries> {
+ typedef mozilla::net::DNSCacheEntries paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.hostname);
+ WriteParam(aWriter, aParam.hostaddr);
+ WriteParam(aWriter, aParam.family);
+ WriteParam(aWriter, aParam.expiration);
+ WriteParam(aWriter, aParam.netInterface);
+ WriteParam(aWriter, aParam.TRR);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->hostname) &&
+ ReadParam(aReader, &aResult->hostaddr) &&
+ ReadParam(aReader, &aResult->family) &&
+ ReadParam(aReader, &aResult->expiration) &&
+ ReadParam(aReader, &aResult->netInterface) &&
+ ReadParam(aReader, &aResult->TRR);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::DnsAndConnectSockets> {
+ typedef mozilla::net::DnsAndConnectSockets paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.speculative);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->speculative);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::HttpConnInfo> {
+ typedef mozilla::net::HttpConnInfo paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.ttl);
+ WriteParam(aWriter, aParam.rtt);
+ WriteParam(aWriter, aParam.protocolVersion);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->ttl) &&
+ ReadParam(aReader, &aResult->rtt) &&
+ ReadParam(aReader, &aResult->protocolVersion);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::HttpRetParams> {
+ typedef mozilla::net::HttpRetParams paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.host);
+ WriteParam(aWriter, aParam.active);
+ WriteParam(aWriter, aParam.idle);
+ WriteParam(aWriter, aParam.dnsAndSocks);
+ WriteParam(aWriter, aParam.counter);
+ WriteParam(aWriter, aParam.port);
+ WriteParam(aWriter, aParam.httpVersion);
+ WriteParam(aWriter, aParam.ssl);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->host) &&
+ ReadParam(aReader, &aResult->active) &&
+ ReadParam(aReader, &aResult->idle) &&
+ ReadParam(aReader, &aResult->dnsAndSocks) &&
+ ReadParam(aReader, &aResult->counter) &&
+ ReadParam(aReader, &aResult->port) &&
+ ReadParam(aReader, &aResult->httpVersion) &&
+ ReadParam(aReader, &aResult->ssl);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_DashboardTypes_h_
diff --git a/netwerk/base/DefaultURI.cpp b/netwerk/base/DefaultURI.cpp
new file mode 100644
index 0000000000..0a70218af5
--- /dev/null
+++ b/netwerk/base/DefaultURI.cpp
@@ -0,0 +1,572 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DefaultURI.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsURLHelper.h"
+
+#include "mozilla/ipc/URIParams.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_DEFAULTURI_CID \
+ { /* 04445aa0-fd27-4c99-bd41-6be6318ae92c */ \
+ 0x04445aa0, 0xfd27, 0x4c99, { \
+ 0xbd, 0x41, 0x6b, 0xe6, 0x31, 0x8a, 0xe9, 0x2c \
+ } \
+ }
+
+#define ASSIGN_AND_ADDREF_THIS(ptrToMutator) \
+ do { \
+ if (ptrToMutator) { \
+ *(ptrToMutator) = do_AddRef(this).take(); \
+ } \
+ } while (0)
+
+static NS_DEFINE_CID(kDefaultURICID, NS_DEFAULTURI_CID);
+
+//----------------------------------------------------------------------------
+// nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMPL_CLASSINFO(DefaultURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_DEFAULTURI_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(DefaultURI)
+
+//----------------------------------------------------------------------------
+// nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(DefaultURI)
+NS_IMPL_RELEASE(DefaultURI)
+NS_INTERFACE_TABLE_HEAD(DefaultURI)
+ NS_INTERFACE_TABLE(DefaultURI, nsIURI, nsISerializable)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_IMPL_QUERY_CLASSINFO(DefaultURI)
+ if (aIID.Equals(kDefaultURICID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+//----------------------------------------------------------------------------
+// nsISerializable
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP DefaultURI::Read(nsIObjectInputStream* aInputStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DefaultURI::Write(nsIObjectOutputStream* aOutputStream) {
+ nsAutoCString spec(mURL->Spec());
+ return aOutputStream->WriteStringZ(spec.get());
+}
+
+//----------------------------------------------------------------------------
+// nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t DefaultURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mURL->SizeOf();
+}
+
+size_t DefaultURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+//----------------------------------------------------------------------------
+// nsIURI
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP DefaultURI::GetSpec(nsACString& aSpec) {
+ aSpec = mURL->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPrePath(nsACString& aPrePath) {
+ aPrePath = mURL->PrePath();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetScheme(nsACString& aScheme) {
+ aScheme = mURL->Scheme();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetUserPass(nsACString& aUserPass) {
+ aUserPass = mURL->Username();
+ nsAutoCString pass(mURL->Password());
+ if (pass.IsEmpty()) {
+ return NS_OK;
+ }
+ aUserPass.Append(':');
+ aUserPass.Append(pass);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetUsername(nsACString& aUsername) {
+ aUsername = mURL->Username();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPassword(nsACString& aPassword) {
+ aPassword = mURL->Password();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHostPort(nsACString& aHostPort) {
+ aHostPort = mURL->HostPort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHost(nsACString& aHost) {
+ aHost = mURL->Host();
+
+ // Historically nsIURI.host has always returned an IPv6 address that isn't
+ // enclosed in brackets. Ideally we want to change that, but for the sake of
+ // consitency we'll leave it like that for the moment.
+ // Bug 1603199 should fix this.
+ if (StringBeginsWith(aHost, "["_ns) && StringEndsWith(aHost, "]"_ns) &&
+ aHost.FindChar(':') != kNotFound) {
+ aHost = Substring(aHost, 1, aHost.Length() - 2);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPort(int32_t* aPort) {
+ *aPort = mURL->Port();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPathQueryRef(nsACString& aPathQueryRef) {
+ aPathQueryRef = mURL->Path();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Equals(nsIURI* other, bool* _retval) {
+ if (!other) {
+ *_retval = false;
+ return NS_OK;
+ }
+ RefPtr<DefaultURI> otherUri;
+ nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ *_retval = mURL->Spec() == otherUri->mURL->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::SchemeIs(const char* scheme, bool* _retval) {
+ if (!scheme) {
+ *_retval = false;
+ return NS_OK;
+ }
+ *_retval = mURL->Scheme().Equals(scheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Resolve(const nsACString& aRelativePath,
+ nsACString& aResult) {
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(aRelativePath, scheme);
+ if (NS_SUCCEEDED(rv)) {
+ aResult = aRelativePath;
+ return NS_OK;
+ }
+
+ // We try to create another URL with this one as its base.
+ RefPtr<MozURL> resolvedURL;
+ rv = MozURL::Init(getter_AddRefs(resolvedURL), aRelativePath, mURL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If parsing the relative url fails, we revert to the previous behaviour
+ // and just return the relative path.
+ aResult = aRelativePath;
+ return NS_OK;
+ }
+
+ aResult = resolvedURL->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetAsciiSpec(nsACString& aAsciiSpec) {
+ return GetSpec(aAsciiSpec);
+}
+
+NS_IMETHODIMP DefaultURI::GetAsciiHostPort(nsACString& aAsciiHostPort) {
+ return GetHostPort(aAsciiHostPort);
+}
+
+NS_IMETHODIMP DefaultURI::GetAsciiHost(nsACString& aAsciiHost) {
+ return GetHost(aAsciiHost);
+}
+
+NS_IMETHODIMP DefaultURI::GetRef(nsACString& aRef) {
+ aRef = mURL->Ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::EqualsExceptRef(nsIURI* other, bool* _retval) {
+ if (!_retval || !other) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ RefPtr<DefaultURI> otherUri;
+ nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ *_retval = mURL->SpecNoRef().Equals(otherUri->mURL->SpecNoRef());
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) {
+ aSpecIgnoringRef = mURL->SpecNoRef();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHasRef(bool* aHasRef) {
+ *aHasRef = mURL->HasFragment();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHasUserPass(bool* aHasUserPass) {
+ *aHasUserPass = !mURL->Username().IsEmpty() || !mURL->Password().IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetFilePath(nsACString& aFilePath) {
+ aFilePath = mURL->FilePath();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetQuery(nsACString& aQuery) {
+ aQuery = mURL->Query();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHasQuery(bool* aHasQuery) {
+ *aHasQuery = mURL->HasQuery();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayHost(nsACString& aDisplayHost) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetHost(aDisplayHost);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayHostPort(nsACString& aDisplayHostPort) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetHostPort(aDisplayHostPort);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplaySpec(nsACString& aDisplaySpec) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetSpec(aDisplaySpec);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayPrePath(nsACString& aDisplayPrePath) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetPrePath(aDisplayPrePath);
+}
+
+NS_IMETHODIMP DefaultURI::Mutate(nsIURIMutator** _retval) {
+ RefPtr<DefaultURI::Mutator> mutator = new DefaultURI::Mutator();
+ mutator->Init(this);
+ mutator.forget(_retval);
+ return NS_OK;
+}
+
+void DefaultURI::Serialize(ipc::URIParams& aParams) {
+ ipc::DefaultURIParams params;
+ params.spec() = mURL->Spec();
+ aParams = params;
+}
+
+//----------------------------------------------------------------------------
+// nsIURIMutator
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(DefaultURI::Mutator)
+NS_IMPL_RELEASE(DefaultURI::Mutator)
+NS_IMETHODIMP DefaultURI::Mutator::QueryInterface(REFNSIID aIID,
+ void** aInstancePtr) {
+ NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");
+ nsISupports* foundInterface = nullptr;
+ if (aIID.Equals(NS_GET_IID(nsIURI))) {
+ RefPtr<DefaultURI> defaultURI = new DefaultURI();
+ mMutator->Finalize(getter_AddRefs(defaultURI->mURL));
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURI*>((defaultURI.get())));
+ NS_ADDREF(foundInterface);
+ *aInstancePtr = foundInterface;
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIURIMutator)) ||
+ aIID.Equals(NS_GET_IID(nsISupports))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURIMutator*>(this));
+ } else if (aIID.Equals(NS_GET_IID(nsIURISetters))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURISetters*>(this));
+ } else if (aIID.Equals(NS_GET_IID(nsIURISetSpec))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURISetSpec*>(this));
+ } else if (aIID.Equals(NS_GET_IID(nsISerializable))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsISerializable*>(this));
+ }
+
+ if (foundInterface) {
+ NS_ADDREF(foundInterface);
+ *aInstancePtr = foundInterface;
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::Read(nsIObjectInputStream* aStream) {
+ nsAutoCString spec;
+ nsresult rv = aStream->ReadCString(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return SetSpec(spec, nullptr);
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::Deserialize(
+ const mozilla::ipc::URIParams& aParams) {
+ if (aParams.type() != ipc::URIParams::TDefaultURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return NS_ERROR_FAILURE;
+ }
+
+ const ipc::DefaultURIParams& params = aParams.get_DefaultURIParams();
+ auto result = MozURL::Mutator::FromSpec(params.spec());
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mMutator = Some(result.unwrap());
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::Finalize(nsIURI** aURI) {
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<DefaultURI> uri = new DefaultURI();
+ mMutator->Finalize(getter_AddRefs(uri->mURL));
+ mMutator = Nothing();
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ auto result = MozURL::Mutator::FromSpec(aSpec);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mMutator = Some(result.unwrap());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetScheme(const nsACString& aScheme,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetScheme(aScheme);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetUserPass(const nsACString& aUserPass,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ int32_t index = aUserPass.FindChar(':');
+ if (index == kNotFound) {
+ mMutator->SetUsername(aUserPass);
+ mMutator->SetPassword(""_ns);
+ return mMutator->GetStatus();
+ }
+
+ mMutator->SetUsername(Substring(aUserPass, 0, index));
+ nsresult rv = mMutator->GetStatus();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mMutator->SetPassword(Substring(aUserPass, index + 1));
+ rv = mMutator->GetStatus();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetUsername(const nsACString& aUsername,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetUsername(aUsername);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetPassword(const nsACString& aPassword,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetPassword(aPassword);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetHostPort(const nsACString& aHostPort,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetHostPort(aHostPort);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetHost(const nsACString& aHost,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetHostname(aHost);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetPort(int32_t aPort, nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetPort(aPort);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetPathQueryRef(const nsACString& aPathQueryRef,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aPathQueryRef.IsEmpty()) {
+ mMutator->SetFilePath(""_ns);
+ mMutator->SetQuery(""_ns);
+ mMutator->SetRef(""_ns);
+ return mMutator->GetStatus();
+ }
+
+ RefPtr<MozURL> url;
+ mMutator->Finalize(getter_AddRefs(url));
+ mMutator = Nothing();
+
+ if (!url) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString pathQueryRef(aPathQueryRef);
+ if (url->CannotBeABase()) {
+ // If the base URL cannot be a base, then setting the pathQueryRef
+ // needs to change everything after the scheme.
+ pathQueryRef.Insert(":", 0);
+ pathQueryRef.Insert(url->Scheme(), 0);
+ // Clear the URL to make sure FromSpec creates an absolute URL
+ url = nullptr;
+ } else if (!StringBeginsWith(pathQueryRef, "/"_ns)) {
+ // If the base URL can be a base, make sure the path
+ // begins with a /
+ pathQueryRef.Insert('/', 0);
+ }
+
+ auto result = MozURL::Mutator::FromSpec(pathQueryRef, url);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mMutator = Some(result.unwrap());
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetRef(const nsACString& aRef, nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetRef(aRef);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetFilePath(const nsACString& aFilePath,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetFilePath(aFilePath);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetQuery(const nsACString& aQuery,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetQuery(aQuery);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ // we only support UTF-8 for DefaultURI
+ mMutator->SetQuery(aQuery);
+ return mMutator->GetStatus();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/DefaultURI.h b/netwerk/base/DefaultURI.h
new file mode 100644
index 0000000000..e737067565
--- /dev/null
+++ b/netwerk/base/DefaultURI.h
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DefaultURI_h__
+#define DefaultURI_h__
+
+#include "nsIURI.h"
+#include "nsISerializable.h"
+#include "nsISizeOf.h"
+#include "nsIURIMutator.h"
+#include "mozilla/net/MozURL.h"
+
+namespace mozilla {
+namespace net {
+
+class DefaultURI : public nsIURI, public nsISerializable, public nsISizeOf {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSISERIALIZABLE
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ class Mutator final : public nsIURIMutator, public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURISETSPEC
+ NS_DECL_NSIURISETTERS
+ NS_DECL_NSIURIMUTATOR
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ MOZ_ASSERT_UNREACHABLE("nsIURIMutator.write() should never be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+ void Init(DefaultURI* aFrom) { mMutator = Some(aFrom->mURL->Mutate()); }
+
+ Maybe<MozURL::Mutator> mMutator;
+
+ friend class DefaultURI;
+ };
+
+ private:
+ virtual ~DefaultURI() = default;
+ RefPtr<MozURL> mURL;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DefaultURI_h__
diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp
new file mode 100644
index 0000000000..87179d6732
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.cpp
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventTokenBucket.h"
+
+#include "nsICancelable.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#ifdef DEBUG
+# include "MainThreadUtils.h"
+#endif
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <mmsystem.h>
+#endif
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////
+// EventTokenBucketCancelable
+////////////////////////////////////////////
+
+class TokenBucketCancelable : public nsICancelable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit TokenBucketCancelable(class ATokenBucketEvent* event);
+ void Fire();
+
+ private:
+ virtual ~TokenBucketCancelable() = default;
+
+ friend class EventTokenBucket;
+ ATokenBucketEvent* mEvent;
+};
+
+NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable)
+
+TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent* event)
+ : mEvent(event) {}
+
+NS_IMETHODIMP
+TokenBucketCancelable::Cancel(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mEvent = nullptr;
+ return NS_OK;
+}
+
+void TokenBucketCancelable::Fire() {
+ if (!mEvent) return;
+
+ ATokenBucketEvent* event = mEvent;
+ mEvent = nullptr;
+ event->OnTokenBucketAdmitted();
+}
+
+////////////////////////////////////////////
+// EventTokenBucket
+////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback, nsINamed)
+
+// by default 1hz with no burst
+EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize)
+ : mUnitCost(kUsecPerSec),
+ mMaxCredit(kUsecPerSec),
+ mCredit(kUsecPerSec),
+ mPaused(false),
+ mStopped(false),
+ mTimerArmed(false)
+#ifdef XP_WIN
+ ,
+ mFineGrainTimerInUse(false),
+ mFineGrainResetTimerArmed(false)
+#endif
+{
+ mLastUpdate = TimeStamp::Now();
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ }
+ if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts);
+ SetRate(eventsPerSecond, burstSize);
+}
+
+EventTokenBucket::~EventTokenBucket() {
+ SOCKET_LOG(
+ ("EventTokenBucket::dtor %p events=%zu\n", this, mEvents.GetSize()));
+
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront();
+ cancelable->Fire();
+ }
+}
+
+void EventTokenBucket::CleanupTimers() {
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mTimerArmed = false;
+
+#ifdef XP_WIN
+ NormalTimers();
+ if (mFineGrainResetTimer && mFineGrainResetTimerArmed) {
+ mFineGrainResetTimer->Cancel();
+ }
+ mFineGrainResetTimer = nullptr;
+ mFineGrainResetTimerArmed = false;
+#endif
+}
+
+void EventTokenBucket::SetRate(uint32_t eventsPerSecond, uint32_t burstSize) {
+ SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n", this, eventsPerSecond,
+ burstSize));
+
+ if (eventsPerSecond > kMaxHz) {
+ eventsPerSecond = kMaxHz;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ if (!eventsPerSecond) {
+ eventsPerSecond = 1;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ mUnitCost = kUsecPerSec / eventsPerSecond;
+ mMaxCredit = mUnitCost * burstSize;
+ if (mMaxCredit > kUsecPerSec * 60 * 15) {
+ SOCKET_LOG((" burstSize out of range\n"));
+ mMaxCredit = kUsecPerSec * 60 * 15;
+ }
+ mCredit = mMaxCredit;
+ mLastUpdate = TimeStamp::Now();
+}
+
+void EventTokenBucket::ClearCredits() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this));
+ mCredit = 0;
+}
+
+uint32_t EventTokenBucket::BurstEventsAvailable() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return static_cast<uint32_t>(mCredit / mUnitCost);
+}
+
+uint32_t EventTokenBucket::QueuedEvents() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mEvents.GetSize();
+}
+
+void EventTokenBucket::Pause() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::Pause %p\n", this));
+ if (mPaused || mStopped) return;
+
+ mPaused = true;
+ if (mTimerArmed) {
+ mTimer->Cancel();
+ mTimerArmed = false;
+ }
+}
+
+void EventTokenBucket::UnPause() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this));
+ if (!mPaused || mStopped) return;
+
+ mPaused = false;
+ DispatchEvents();
+ UpdateTimer();
+}
+
+void EventTokenBucket::Stop() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed));
+ mStopped = true;
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront();
+ cancelable->Fire();
+ }
+}
+
+nsresult EventTokenBucket::SubmitEvent(ATokenBucketEvent* event,
+ nsICancelable** cancelable) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this));
+
+ if (mStopped || !mTimer) return NS_ERROR_FAILURE;
+
+ UpdateCredits();
+
+ RefPtr<TokenBucketCancelable> cancelEvent = new TokenBucketCancelable(event);
+ // When this function exits the cancelEvent needs 2 references, one for the
+ // mEvents queue and one for the caller of SubmitEvent()
+
+ *cancelable = do_AddRef(cancelEvent).take();
+
+ if (mPaused || !TryImmediateDispatch(cancelEvent.get())) {
+ // queue it
+ SOCKET_LOG((" queued\n"));
+ mEvents.Push(cancelEvent.forget());
+ UpdateTimer();
+ } else {
+ SOCKET_LOG((" dispatched synchronously\n"));
+ }
+
+ return NS_OK;
+}
+
+bool EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable* cancelable) {
+ if (mCredit < mUnitCost) return false;
+
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ return true;
+}
+
+void EventTokenBucket::DispatchEvents() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused));
+ if (mPaused || mStopped) return;
+
+ while (mEvents.GetSize() && mUnitCost <= mCredit) {
+ RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront();
+ if (cancelable->mEvent) {
+ SOCKET_LOG(
+ ("EventTokenBucket::DispachEvents [%p] "
+ "Dispatching queue token bucket event cost=%" PRIu64
+ " credit=%" PRIu64 "\n",
+ this, mUnitCost, mCredit));
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ }
+ }
+
+#ifdef XP_WIN
+ if (!mEvents.GetSize()) WantNormalTimers();
+#endif
+}
+
+void EventTokenBucket::UpdateTimer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer) {
+ return;
+ }
+
+ if (mCredit >= mUnitCost) return;
+
+ // determine the time needed to wait to accumulate enough credits to admit
+ // one more event and set the timer for that point. Always round it
+ // up because firing early doesn't help.
+ //
+ uint64_t deficit = mUnitCost - mCredit;
+ uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec;
+
+ if (msecWait < 4) { // minimum wait
+ msecWait = 4;
+ } else if (msecWait > 60000) { // maximum wait
+ msecWait = 60000;
+ }
+
+#ifdef XP_WIN
+ FineGrainTimers();
+#endif
+
+ SOCKET_LOG(
+ ("EventTokenBucket::UpdateTimer %p for %" PRIu64 "ms\n", this, msecWait));
+ nsresult rv = mTimer->InitWithCallback(this, static_cast<uint32_t>(msecWait),
+ nsITimer::TYPE_ONE_SHOT);
+ mTimerArmed = NS_SUCCEEDED(rv);
+}
+
+NS_IMETHODIMP
+EventTokenBucket::Notify(nsITimer* timer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+#ifdef XP_WIN
+ if (timer == mFineGrainResetTimer) {
+ FineGrainResetTimerNotify();
+ return NS_OK;
+ }
+#endif
+
+ SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this));
+ mTimerArmed = false;
+ if (mStopped) return NS_OK;
+
+ UpdateCredits();
+ DispatchEvents();
+ UpdateTimer();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventTokenBucket::GetName(nsACString& aName) {
+ aName.AssignLiteral("EventTokenBucket");
+ return NS_OK;
+}
+
+void EventTokenBucket::UpdateCredits() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration elapsed = now - mLastUpdate;
+ mLastUpdate = now;
+
+ mCredit += static_cast<uint64_t>(elapsed.ToMicroseconds());
+ if (mCredit > mMaxCredit) mCredit = mMaxCredit;
+ SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %" PRIu64 " (%" PRIu64
+ " each.. %3.2f)\n",
+ this, mCredit, mUnitCost, (double)mCredit / mUnitCost));
+}
+
+#ifdef XP_WIN
+void EventTokenBucket::FineGrainTimers() {
+ SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n",
+ this, mFineGrainTimerInUse));
+
+ mLastFineGrainTimerUse = TimeStamp::Now();
+
+ if (mFineGrainTimerInUse) return;
+
+ if (mUnitCost > kCostFineGrainThreshold) return;
+
+ SOCKET_LOG(
+ ("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n", this));
+
+ mFineGrainTimerInUse = true;
+ timeBeginPeriod(1);
+}
+
+void EventTokenBucket::NormalTimers() {
+ if (!mFineGrainTimerInUse) return;
+ mFineGrainTimerInUse = false;
+
+ SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this));
+ timeEndPeriod(1);
+}
+
+void EventTokenBucket::WantNormalTimers() {
+ if (!mFineGrainTimerInUse) return;
+ if (mFineGrainResetTimerArmed) return;
+
+ TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse);
+ static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5);
+
+ if (elapsed >= fiveSeconds) {
+ NormalTimers();
+ return;
+ }
+
+ if (!mFineGrainResetTimer) mFineGrainResetTimer = NS_NewTimer();
+
+ // if we can't delay the reset, just do it now
+ if (!mFineGrainResetTimer) {
+ NormalTimers();
+ return;
+ }
+
+ // pad the callback out 100ms to avoid having to round trip this again if the
+ // timer calls back just a tad early.
+ SOCKET_LOG(
+ ("EventTokenBucket::WantNormalTimers %p "
+ "Will reset timer granularity after delay",
+ this));
+
+ mFineGrainResetTimer->InitWithCallback(
+ this,
+ static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100,
+ nsITimer::TYPE_ONE_SHOT);
+ mFineGrainResetTimerArmed = true;
+}
+
+void EventTokenBucket::FineGrainResetTimerNotify() {
+ SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify(%p) events = %zd\n",
+ this, mEvents.GetSize()));
+ mFineGrainResetTimerArmed = false;
+
+ // If we are currently processing events then wait for the queue to drain
+ // before trying to reset back to normal timers again
+ if (!mEvents.GetSize()) WantNormalTimers();
+}
+
+#endif
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h
new file mode 100644
index 0000000000..4206a622f5
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetEventTokenBucket_h__
+#define NetEventTokenBucket_h__
+
+#include "ARefBase.h"
+#include "nsCOMPtr.h"
+#include "nsDeque.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICancelable;
+
+namespace mozilla {
+namespace net {
+
+/* A token bucket is used to govern the maximum rate a series of events
+ can be executed at. For instance if your event was "eat a piece of cake"
+ then a token bucket configured to allow "1 piece per day" would spread
+ the eating of a 8 piece cake over 8 days even if you tried to eat the
+ whole thing up front. In a practical sense it 'costs' 1 token to execute
+ an event and tokens are 'earned' at a particular rate as time goes by.
+
+ The token bucket can be perfectly smooth or allow a configurable amount of
+ burstiness. A bursty token bucket allows you to save up unused credits, while
+ a perfectly smooth one would not. A smooth "1 per day" cake token bucket
+ would require 9 days to eat that cake if you skipped a slice on day 4
+ (use the token or lose it), while a token bucket configured with a burst
+ of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day
+ 5) and finish the cake in the usual 8 days.
+
+ EventTokenBucket(hz=20, burst=5) creates a token bucket with the following
+ properties:
+
+ + events from an infinite stream will be admitted 20 times per second (i.e.
+ hz=20 means 1 event per 50 ms). Timers will be used to space things evenly
+ down to 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than
+ 200hz will admit multiple events with 5ms gaps between them. 10000hz is the
+ maximum rate and 1hz is the minimum rate.
+
+ + The burst size controls the limit of 'credits' that a token bucket can
+ accumulate when idle. For our (20,5) example each event requires 50ms of
+ credit (again, 20hz = 50ms per event). a burst size of 5 means that the
+ token bucket can accumulate a maximum of 250ms (5 * 50ms) for this bucket.
+ If no events have been admitted for the last full second the bucket can
+ still only accumulate 250ms of credit - but that credit means that 5 events
+ can be admitted without delay. A burst size of 1 is the minimum. The
+ EventTokenBucket is created with maximum credits already applied, but they
+ can be cleared with the ClearCredits() method. The maximum burst size is 15
+ minutes worth of events.
+
+ + An event is submitted to the token bucket asynchronously through
+ SubmitEvent(). The OnTokenBucketAdmitted() method of the submitted event
+ is used as a callback when the event is ready to run. A cancelable event is
+ returned to the SubmitEvent() caller for use in the case they do not wish
+ to wait for the callback.
+*/
+
+class EventTokenBucket;
+
+class ATokenBucketEvent {
+ public:
+ virtual void OnTokenBucketAdmitted() = 0;
+};
+
+class TokenBucketCancelable;
+
+class EventTokenBucket : public nsITimerCallback,
+ public nsINamed,
+ public ARefBase {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ // This should be constructed on the main thread
+ EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ // These public methods are all meant to be called from the socket thread
+ void ClearCredits();
+ uint32_t BurstEventsAvailable();
+ uint32_t QueuedEvents();
+
+ // a paused token bucket will not process any events, but it will accumulate
+ // credits. ClearCredits can be used before unpausing if desired.
+ void Pause();
+ void UnPause();
+ void Stop();
+
+ // The returned cancelable event can only be canceled from the socket thread
+ nsresult SubmitEvent(ATokenBucketEvent* event, nsICancelable** cancelable);
+
+ private:
+ virtual ~EventTokenBucket();
+ void CleanupTimers();
+
+ friend class RunNotifyEvent;
+ friend class SetTimerEvent;
+
+ bool TryImmediateDispatch(TokenBucketCancelable* cancelable);
+ void SetRate(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ void DispatchEvents();
+ void UpdateTimer();
+ void UpdateCredits();
+
+ const static uint64_t kUsecPerSec = 1000000;
+ const static uint64_t kUsecPerMsec = 1000;
+ const static uint64_t kMaxHz = 10000;
+
+ uint64_t
+ mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond)
+ uint64_t mMaxCredit; // usec mCredit limit (from busrtSize)
+ uint64_t mCredit; // usec of accumulated credit.
+
+ bool mPaused;
+ bool mStopped;
+ nsRefPtrDeque<TokenBucketCancelable> mEvents;
+ bool mTimerArmed;
+ TimeStamp mLastUpdate;
+
+ // The timer is created on the main thread, but is armed and executes Notify()
+ // callbacks on the socket thread in order to maintain low latency of event
+ // delivery.
+ nsCOMPtr<nsITimer> mTimer;
+
+#ifdef XP_WIN
+ // Windows timers are 15ms granularity by default. When we have active events
+ // that need to be dispatched at 50ms or less granularity we change the OS
+ // granularity to 1ms. 90 seconds after that need has elapsed we will change
+ // it back
+ const static uint64_t kCostFineGrainThreshold = 50 * kUsecPerMsec;
+
+ void FineGrainTimers(); // get 1ms granularity
+ void NormalTimers(); // reset to default granularity
+ void WantNormalTimers(); // reset after 90 seconds if not needed in interim
+ void FineGrainResetTimerNotify(); // delayed callback to reset
+
+ TimeStamp mLastFineGrainTimerUse;
+ bool mFineGrainTimerInUse;
+ bool mFineGrainResetTimerArmed;
+ nsCOMPtr<nsITimer> mFineGrainResetTimer;
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/FuzzyLayer.cpp b/netwerk/base/FuzzyLayer.cpp
new file mode 100644
index 0000000000..8a9ee694dd
--- /dev/null
+++ b/netwerk/base/FuzzyLayer.cpp
@@ -0,0 +1,407 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzyLayer.h"
+#include "nsTHashMap.h"
+#include "nsDeque.h"
+#include "nsIRunnable.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+
+#include "prmem.h"
+#include "prio.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gFuzzingLog("nsFuzzingNecko");
+
+#define FUZZING_LOG(args) \
+ MOZ_LOG(mozilla::net::gFuzzingLog, mozilla::LogLevel::Verbose, args)
+
+// Mutex for modifying our hash tables
+Mutex gConnRecvMutex("ConnectRecvMutex");
+
+// This structure will be created by addNetworkFuzzingBuffer below
+// and then added to the gNetworkFuzzingBuffers structure.
+//
+// It is assigned on connect and associated with the socket it belongs to.
+typedef struct {
+ const uint8_t* buf;
+ size_t size;
+ bool allowRead;
+ bool allowUnused;
+ PRNetAddr* addr;
+} NetworkFuzzingBuffer;
+
+// This holds all connections we have currently open.
+static nsTHashMap<nsPtrHashKey<PRFileDesc>, NetworkFuzzingBuffer*>
+ gConnectedNetworkFuzzingBuffers;
+
+// This holds all buffers for connections we can still open.
+static nsDeque<NetworkFuzzingBuffer> gNetworkFuzzingBuffers;
+
+// This is `true` once all connections are closed and either there are
+// no buffers left to be used or all remaining buffers are marked optional.
+// Used by `signalNetworkFuzzingDone` to tell the main thread if it needs
+// to spin-wait for `gFuzzingConnClosed`.
+static Atomic<bool> fuzzingNoWaitRequired(false);
+
+// Used to memorize if the main thread has indicated that it is done with
+// its iteration and we don't expect more connections now.
+static Atomic<bool> fuzzingMainSignaledDone(false);
+
+/*
+ * The flag `gFuzzingConnClosed` is set by `FuzzyClose` when all connections
+ * are closed *and* there are no more buffers in `gNetworkFuzzingBuffers` that
+ * must be used. The main thread spins until this becomes true to synchronize
+ * the fuzzing iteration between the main thread and the socket thread, if
+ * the prior call to `signalNetworkFuzzingDone` returned `false`.
+ */
+Atomic<bool> gFuzzingConnClosed(true);
+
+void addNetworkFuzzingBuffer(const uint8_t* data, size_t size, bool readFirst,
+ bool useIsOptional) {
+ if (size > INT32_MAX) {
+ MOZ_CRASH("Unsupported buffer size");
+ }
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* buf = new NetworkFuzzingBuffer();
+ buf->buf = data;
+ buf->size = size;
+ buf->allowRead = readFirst;
+ buf->allowUnused = useIsOptional;
+ buf->addr = nullptr;
+
+ gNetworkFuzzingBuffers.Push(buf);
+
+ fuzzingMainSignaledDone = false;
+ fuzzingNoWaitRequired = false;
+}
+
+/*
+ * This method should be called by fuzzing from the main thread to signal to
+ * the layer code that a fuzzing iteration is done. As a result, we can throw
+ * away any optional buffers and signal back once all connections have been
+ * closed. The main thread should synchronize on all connections being closed
+ * after the actual request/action is complete.
+ */
+bool signalNetworkFuzzingDone() {
+ FUZZING_LOG(("[signalNetworkFuzzingDone] Called."));
+ MutexAutoLock lock(gConnRecvMutex);
+ bool rv = false;
+
+ if (fuzzingNoWaitRequired) {
+ FUZZING_LOG(("[signalNetworkFuzzingDone] Purging remaining buffers."));
+ // Easy case, we already have no connections and non-optional buffers left.
+ gNetworkFuzzingBuffers.Erase();
+ gFuzzingConnClosed = true;
+ rv = true;
+ } else {
+ // We still have either connections left open or non-optional buffers left.
+ // In this case, FuzzyClose will handle the tear-down and signaling.
+ fuzzingMainSignaledDone = true;
+ }
+
+ return rv;
+}
+
+static PRDescIdentity sFuzzyLayerIdentity;
+static PRIOMethods sFuzzyLayerMethods;
+static PRIOMethods* sFuzzyLayerMethodsPtr = nullptr;
+
+static PRInt16 PR_CALLBACK FuzzyPoll(PRFileDesc* fd, PRInt16 in_flags,
+ PRInt16* out_flags) {
+ *out_flags = 0;
+
+ FUZZING_LOG(("[FuzzyPoll] Called with in_flags=%X.", in_flags));
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+
+ if (in_flags & PR_POLL_READ && fuzzBuf && fuzzBuf->allowRead) {
+ *out_flags = PR_POLL_READ;
+ return PR_POLL_READ;
+ }
+
+ if (in_flags & PR_POLL_WRITE) {
+ *out_flags = PR_POLL_WRITE;
+ return PR_POLL_WRITE;
+ }
+
+ return in_flags;
+}
+
+static PRStatus FuzzyConnect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront();
+ if (!buf) {
+ FUZZING_LOG(("[FuzzyConnect] Denying additional connection."));
+ return PR_FAILURE;
+ }
+
+ gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf);
+ fuzzingNoWaitRequired = false;
+
+ FUZZING_LOG(("[FuzzyConnect] Successfully opened connection: %p", fd));
+
+ gFuzzingConnClosed = false;
+
+ return PR_SUCCESS;
+}
+
+static PRInt32 FuzzySendTo(PRFileDesc* fd, const void* buf, PRInt32 amount,
+ PRIntn flags, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront();
+ if (!buf) {
+ FUZZING_LOG(("[FuzzySentTo] Denying additional connection."));
+ return 0;
+ }
+
+ gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf);
+
+ // Store connection address
+ buf->addr = new PRNetAddr;
+ memcpy(buf->addr, addr, sizeof(PRNetAddr));
+
+ fuzzingNoWaitRequired = false;
+
+ FUZZING_LOG(("[FuzzySendTo] Successfully opened connection: %p", fd));
+
+ gFuzzingConnClosed = false;
+ }
+
+ // Allow reading once the implementation has written at least some data
+ if (fuzzBuf && !fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzySendTo] Write received, allowing further reads."));
+ fuzzBuf->allowRead = true;
+ }
+
+ FUZZING_LOG(("[FuzzySendTo] Sent %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzySend(PRFileDesc* fd, const void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzySend] Write on socket that is not connected."));
+ amount = 0;
+ }
+
+ // Allow reading once the implementation has written at least some data
+ if (fuzzBuf && !fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzySend] Write received, allowing further reads."));
+ fuzzBuf->allowRead = true;
+ }
+
+ FUZZING_LOG(("[FuzzySend] Sent %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzyWrite(PRFileDesc* fd, const void* buf, PRInt32 amount) {
+ return FuzzySend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRInt32 FuzzyRecv(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed."));
+ return 0;
+ }
+
+ // As long as we haven't written anything, act as if no data was there yet
+ if (!fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, nothing written before."));
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+
+ if (gFuzzingConnClosed) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed."));
+ return 0;
+ }
+
+ // No data left, act as if the connection was closed.
+ if (!fuzzBuf->size) {
+ FUZZING_LOG(("[FuzzyRecv] Read failed, no data left."));
+ return 0;
+ }
+
+ // Use the remains of fuzzing buffer, if too little is left
+ if (fuzzBuf->size < (PRUint32)amount) amount = fuzzBuf->size;
+
+ // Consume buffer, copy data
+ memcpy(buf, fuzzBuf->buf, amount);
+
+ if (!(flags & PR_MSG_PEEK)) {
+ fuzzBuf->buf += amount;
+ fuzzBuf->size -= amount;
+ }
+
+ FUZZING_LOG(("[FuzzyRecv] Read %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzyRecvFrom(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ // Return the same address used for initial SendTo on this fd
+ if (addr) {
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzyRecvFrom] Denying read, connection is closed."));
+ return 0;
+ }
+
+ if (fuzzBuf->addr) {
+ memcpy(addr, fuzzBuf->addr, sizeof(PRNetAddr));
+ } else {
+ FUZZING_LOG(("[FuzzyRecvFrom] No address found for connection"));
+ }
+ }
+ return FuzzyRecv(fd, buf, amount, flags, timeout);
+}
+
+static PRInt32 FuzzyRead(PRFileDesc* fd, void* buf, PRInt32 amount) {
+ return FuzzyRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRStatus FuzzyClose(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+ MOZ_RELEASE_ASSERT(layer && layer->identity == sFuzzyLayerIdentity,
+ "Fuzzy Layer not on top of stack");
+
+ layer->dtor(layer);
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = nullptr;
+ if (gConnectedNetworkFuzzingBuffers.Remove(fd, &fuzzBuf)) {
+ FUZZING_LOG(("[FuzzyClose] Received close on socket %p", fd));
+ if (fuzzBuf->addr) {
+ delete fuzzBuf->addr;
+ }
+ delete fuzzBuf;
+ } else {
+ /* Happens when close is called on a non-connected socket */
+ FUZZING_LOG(("[FuzzyClose] Received close on unknown socket %p.", fd));
+ }
+
+ PRStatus ret = fd->methods->close(fd);
+
+ if (!gConnectedNetworkFuzzingBuffers.Count()) {
+ // At this point, all connections are closed, but we might still have
+ // unused network buffers that were not marked as optional.
+ bool haveRemainingUnusedBuffers = false;
+ for (size_t i = 0; i < gNetworkFuzzingBuffers.GetSize(); ++i) {
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.ObjectAt(i);
+
+ if (!buf->allowUnused) {
+ haveRemainingUnusedBuffers = true;
+ break;
+ }
+ }
+
+ if (haveRemainingUnusedBuffers) {
+ FUZZING_LOG(
+ ("[FuzzyClose] All connections closed, waiting for remaining "
+ "connections."));
+ } else if (!fuzzingMainSignaledDone) {
+ // We have no connections left, but the main thread hasn't signaled us
+ // yet. For now, that means once the main thread signals us, we can tell
+ // it immediately that it won't have to wait for closing connections.
+ FUZZING_LOG(
+ ("[FuzzyClose] All connections closed, waiting for main thread."));
+ fuzzingNoWaitRequired = true;
+ } else {
+ // No connections left and main thread is already done. Perform cleanup
+ // and then signal the main thread to continue.
+ FUZZING_LOG(("[FuzzyClose] All connections closed, cleaning up."));
+
+ gNetworkFuzzingBuffers.Erase();
+ gFuzzingConnClosed = true;
+
+ // We need to dispatch this so the main thread is guaranteed to wake up
+ nsCOMPtr<nsIRunnable> r(new mozilla::Runnable("Dummy"));
+ NS_DispatchToMainThread(r.forget());
+ }
+ } else {
+ FUZZING_LOG(("[FuzzyClose] Connection closed."));
+ }
+
+ return ret;
+}
+
+nsresult AttachFuzzyIOLayer(PRFileDesc* fd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!sFuzzyLayerMethodsPtr) {
+ sFuzzyLayerIdentity = PR_GetUniqueIdentity("Fuzzy Layer");
+ sFuzzyLayerMethods = *PR_GetDefaultIOMethods();
+ sFuzzyLayerMethods.connect = FuzzyConnect;
+ sFuzzyLayerMethods.send = FuzzySend;
+ sFuzzyLayerMethods.sendto = FuzzySendTo;
+ sFuzzyLayerMethods.write = FuzzyWrite;
+ sFuzzyLayerMethods.recv = FuzzyRecv;
+ sFuzzyLayerMethods.recvfrom = FuzzyRecvFrom;
+ sFuzzyLayerMethods.read = FuzzyRead;
+ sFuzzyLayerMethods.close = FuzzyClose;
+ sFuzzyLayerMethods.poll = FuzzyPoll;
+ sFuzzyLayerMethodsPtr = &sFuzzyLayerMethods;
+ }
+
+ PRFileDesc* layer =
+ PR_CreateIOLayerStub(sFuzzyLayerIdentity, sFuzzyLayerMethodsPtr);
+
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRStatus status = PR_PushIOLayer(fd, PR_TOP_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/FuzzyLayer.h b/netwerk/base/FuzzyLayer.h
new file mode 100644
index 0000000000..6b8109f949
--- /dev/null
+++ b/netwerk/base/FuzzyLayer.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FuzzyLayer_h__
+#define FuzzyLayer_h__
+
+#include "prerror.h"
+#include "nsError.h"
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace net {
+
+nsresult AttachFuzzyIOLayer(PRFileDesc* fd);
+
+extern Atomic<bool> gFuzzingConnClosed;
+bool signalNetworkFuzzingDone();
+
+void addNetworkFuzzingBuffer(const uint8_t* data, size_t size,
+ bool readFirst = false,
+ bool useIsOptional = false);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // FuzzyLayer_h__
diff --git a/netwerk/base/FuzzySecurityInfo.cpp b/netwerk/base/FuzzySecurityInfo.cpp
new file mode 100644
index 0000000000..7a2405ab0a
--- /dev/null
+++ b/netwerk/base/FuzzySecurityInfo.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzySecurityInfo.h"
+
+#include "nsIWebProgressListener.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+FuzzySecurityInfo::FuzzySecurityInfo() {}
+
+FuzzySecurityInfo::~FuzzySecurityInfo() {}
+
+NS_IMPL_ISUPPORTS(FuzzySecurityInfo, nsITransportSecurityInfo)
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSecurityState(uint32_t* state) {
+ *state = nsIWebProgressListener::STATE_IS_SECURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetErrorCode(int32_t* state) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetErrorCodeString(nsAString& aErrorString) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetFailedCertChain(
+ nsTArray<RefPtr<nsIX509Cert>>& aFailedCertChain) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetServerCert(nsIX509Cert** aServerCert) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSucceededCertChain(
+ nsTArray<RefPtr<nsIX509Cert>>& aSucceededCertChain) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetCipherName(nsACString& aCipherName) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetKeyLength(uint32_t* aKeyLength) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSecretKeyLength(uint32_t* aSecretKeyLength) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetKeaGroupName(nsACString& aKeaGroup) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSignatureSchemeName(nsACString& aSignatureScheme) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetProtocolVersion(uint16_t* aProtocolVersion) {
+ NS_ENSURE_ARG_POINTER(aProtocolVersion);
+ // Must be >= TLS 1.2 for HTTP2
+ *aProtocolVersion = nsITransportSecurityInfo::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetCertificateTransparencyStatus(
+ uint16_t* aCertificateTransparencyStatus) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsDelegatedCredential(bool* aIsDelegCred) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsAcceptedEch(bool* aIsAcceptedEch) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetOverridableErrorCategory(
+ OverridableErrorCategory* aOverridableErrorCode) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetMadeOCSPRequests(bool* aMadeOCSPRequests) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetUsedPrivateDNS(bool* aUsedPrivateDNS) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsExtendedValidation(bool* aIsEV) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::ToString(nsACString& aResult) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+void FuzzySecurityInfo::SerializeToIPC(IPC::MessageWriter* aWriter) {
+ MOZ_CRASH("Unused");
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN) {
+ aNegotiatedNPN.Assign("h2");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetResumed(bool* aResumed) {
+ *aResumed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP FuzzySecurityInfo::GetIsBuiltCertChainRootBuiltInRoot(
+ bool* aIsBuiltInRoot) {
+ *aIsBuiltInRoot = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetPeerId(nsACString& aResult) {
+ aResult.Assign(""_ns);
+ return NS_OK;
+}
+
+} // namespace net
+
+} // namespace mozilla
diff --git a/netwerk/base/FuzzySecurityInfo.h b/netwerk/base/FuzzySecurityInfo.h
new file mode 100644
index 0000000000..121c6c8ba9
--- /dev/null
+++ b/netwerk/base/FuzzySecurityInfo.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FuzzySecurityInfo_h__
+#define FuzzySecurityInfo_h__
+
+#include "nsCOMPtr.h"
+#include "nsITransportSecurityInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class FuzzySecurityInfo final : public nsITransportSecurityInfo {
+ public:
+ FuzzySecurityInfo();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTSECURITYINFO
+
+ protected:
+ virtual ~FuzzySecurityInfo();
+}; // class FuzzySecurityInfo
+
+} // namespace net
+} // namespace mozilla
+
+#endif // FuzzySecurityInfo_h__
diff --git a/netwerk/base/FuzzySocketControl.cpp b/netwerk/base/FuzzySocketControl.cpp
new file mode 100644
index 0000000000..ff53358417
--- /dev/null
+++ b/netwerk/base/FuzzySocketControl.cpp
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzySocketControl.h"
+
+#include "FuzzySecurityInfo.h"
+#include "ipc/IPCMessageUtils.h"
+#include "nsITlsHandshakeListener.h"
+#include "sslt.h"
+
+namespace mozilla {
+namespace net {
+
+FuzzySocketControl::FuzzySocketControl() {}
+
+FuzzySocketControl::~FuzzySocketControl() {}
+
+NS_IMPL_ISUPPORTS(FuzzySocketControl, nsITLSSocketControl)
+
+NS_IMETHODIMP
+FuzzySocketControl::GetProviderFlags(uint32_t* aProviderFlags) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetKEAUsed(int16_t* aKea) {
+ // Can be ssl_kea_dh or ssl_kea_ecdh for HTTP2
+ *aKea = ssl_kea_ecdh;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetKEAKeyBits(uint32_t* aKeyBits) {
+ // Must be >= 224 for ecdh and >= 2048 for dh when using HTTP2
+ *aKeyBits = 256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetSSLVersionUsed(int16_t* aSSLVersionUsed) {
+ // Must be >= TLS 1.2 for HTTP2
+ *aSSLVersionUsed = nsITLSSocketControl::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) {
+ *aSSLVersionOffered = nsITLSSocketControl::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetMACAlgorithmUsed(int16_t* aMac) {
+ // The only valid choice for HTTP2 is SSL_MAC_AEAD
+ *aMac = nsITLSSocketControl::SSL_MAC_AEAD;
+ return NS_OK;
+}
+
+bool FuzzySocketControl::GetDenyClientCert() { return false; }
+
+void FuzzySocketControl::SetDenyClientCert(bool aDenyClientCert) {
+ // Called by mozilla::net::nsHttpConnection::StartSpdy
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetClientCertSent(bool* arg) {
+ *arg = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetFailedVerification(bool* arg) {
+ *arg = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetAlpnEarlySelection(nsACString& aAlpnSelected) {
+ // TODO: For now we don't support early selection
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetEarlyDataAccepted(bool* aAccepted) {
+ *aAccepted = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::DriveHandshake() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::IsAcceptableForHost(const nsACString& hostname,
+ bool* _retval) {
+ NS_ENSURE_ARG(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::TestJoinConnection(const nsACString& npnProtocol,
+ const nsACString& hostname, int32_t port,
+ bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::JoinConnection(const nsACString& npnProtocol,
+ const nsACString& hostname, int32_t port,
+ bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::ProxyStartSSL() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::StartTLS() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::SetNPNList(nsTArray<nsCString>& protocolArray) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetEsniTxt(nsACString& aEsniTxt) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::SetEsniTxt(const nsACString& aEsniTxt) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetEchConfig(nsACString& aEchConfig) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::SetEchConfig(const nsACString& aEchConfig) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetRetryEchConfig(nsACString& aEchConfig) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::GetPeerId(nsACString& aResult) {
+ aResult.Assign(""_ns);
+ return NS_OK;
+}
+
+NS_IMETHODIMP FuzzySocketControl::SetHandshakeCallbackListener(
+ nsITlsHandshakeCallbackListener* callback) {
+ if (callback) {
+ callback->HandshakeDone();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::DisableEarlyData(void) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP FuzzySocketControl::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(new FuzzySecurityInfo());
+ securityInfo.forget(aSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::AsyncGetSecurityInfo(JSContext* aCx,
+ mozilla::dom::Promise** aPromise) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP FuzzySocketControl::Claim() { return NS_OK; }
+
+NS_IMETHODIMP FuzzySocketControl::SetBrowserId(uint64_t) { return NS_OK; }
+
+NS_IMETHODIMP FuzzySocketControl::GetBrowserId(uint64_t*) {
+ MOZ_CRASH("Unused");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/FuzzySocketControl.h b/netwerk/base/FuzzySocketControl.h
new file mode 100644
index 0000000000..9debbe32e8
--- /dev/null
+++ b/netwerk/base/FuzzySocketControl.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FuzzySocketControl_h__
+#define FuzzySocketControl_h__
+
+#include "nsITLSSocketControl.h"
+
+namespace mozilla {
+namespace net {
+
+class FuzzySocketControl final : public nsITLSSocketControl {
+ public:
+ FuzzySocketControl();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSOCKETCONTROL
+
+ protected:
+ virtual ~FuzzySocketControl();
+}; // class FuzzySocketControl
+
+} // namespace net
+} // namespace mozilla
+
+#endif // FuzzySocketControl_h__
diff --git a/netwerk/base/IOActivityMonitor.cpp b/netwerk/base/IOActivityMonitor.cpp
new file mode 100644
index 0000000000..76047a7a00
--- /dev/null
+++ b/netwerk/base/IOActivityMonitor.cpp
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "IOActivityMonitor.h"
+#include "nsPrintfCString.h"
+#include "nsSocketTransport2.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prmem.h"
+#include <vector>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+mozilla::StaticRefPtr<IOActivityMonitor> gInstance;
+static Atomic<bool> gActivated(false);
+static PRDescIdentity sNetActivityMonitorLayerIdentity;
+static PRIOMethods sNetActivityMonitorLayerMethods;
+static PRIOMethods* sNetActivityMonitorLayerMethodsPtr = nullptr;
+
+// Maximum number of activities entries in the monitoring class
+#define MAX_ACTIVITY_ENTRIES 1000
+
+// ActivityMonitorSecret is stored in the activity monitor layer
+// and provides a method to get the location.
+//
+// A location can be :
+// - a TCP or UDP socket. The form will be socket://ip:port
+// - a File. The form will be file://path
+//
+// For other cases, the location will be fd://number
+class ActivityMonitorSecret final {
+ public:
+ // constructor used for sockets
+ explicit ActivityMonitorSecret(PRFileDesc* aFd) {
+ mFd = aFd;
+ mLocationSet = false;
+ }
+
+ // constructor used for files
+ explicit ActivityMonitorSecret(PRFileDesc* aFd, const char* aLocation) {
+ mFd = aFd;
+ mLocation.AppendPrintf("file://%s", aLocation);
+ mLocationSet = true;
+ }
+
+ nsCString getLocation() {
+ if (!mLocationSet) {
+ LazySetLocation();
+ }
+ return mLocation;
+ }
+
+ private:
+ // Called to set the location using the FD on the first getLocation() usage
+ // which is typically when a socket is opened. If done earlier, at
+ // construction time, the host won't be bound yet.
+ //
+ // If the location is a file, it needs to be initialized in the
+ // constructor.
+ void LazySetLocation() {
+ mLocationSet = true;
+ PRFileDesc* extract = mFd;
+ while (PR_GetDescType(extract) == PR_DESC_LAYERED) {
+ if (!extract->lower) {
+ break;
+ }
+ extract = extract->lower;
+ }
+
+ PRDescType fdType = PR_GetDescType(extract);
+ // we should not use LazySetLocation for files
+ MOZ_ASSERT(fdType != PR_DESC_FILE);
+
+ switch (fdType) {
+ case PR_DESC_SOCKET_TCP:
+ case PR_DESC_SOCKET_UDP: {
+ mLocation.AppendPrintf("socket://");
+ PRNetAddr addr;
+ PRStatus status = PR_GetSockName(mFd, &addr);
+ if (NS_WARN_IF(status == PR_FAILURE)) {
+ mLocation.AppendPrintf("unknown");
+ break;
+ }
+
+ // grabbing the host
+ char netAddr[mozilla::net::kNetAddrMaxCStrBufSize] = {0};
+ status = PR_NetAddrToString(&addr, netAddr, sizeof(netAddr) - 1);
+ if (NS_WARN_IF(status == PR_FAILURE) || netAddr[0] == 0) {
+ mLocation.AppendPrintf("unknown");
+ break;
+ }
+ mLocation.Append(netAddr);
+
+ // adding the port
+ uint16_t port;
+ if (addr.raw.family == PR_AF_INET) {
+ port = addr.inet.port;
+ } else {
+ port = addr.ipv6.port;
+ }
+ mLocation.AppendPrintf(":%d", port);
+ } break;
+
+ // for all other cases, we just send back fd://<value>
+ default: {
+ mLocation.AppendLiteral("fd://");
+ mLocation.AppendInt(PR_FileDesc2NativeHandle(mFd));
+ }
+ } // end switch
+ }
+
+ private:
+ nsCString mLocation;
+ bool mLocationSet;
+ PRFileDesc* mFd;
+};
+
+// FileDesc2Location converts a PRFileDesc into a "location" by
+// grabbing the ActivityMonitorSecret in layer->secret
+static nsAutoCString FileDesc2Location(PRFileDesc* fd) {
+ nsAutoCString location;
+ PRFileDesc* monitorLayer =
+ PR_GetIdentitiesLayer(fd, sNetActivityMonitorLayerIdentity);
+ if (!monitorLayer) {
+ location.AppendPrintf("unknown");
+ return location;
+ }
+
+ ActivityMonitorSecret* secret = (ActivityMonitorSecret*)monitorLayer->secret;
+ location.AppendPrintf("%s", secret->getLocation().get());
+ return location;
+}
+
+//
+// Wrappers around the socket APIS
+//
+static PRStatus nsNetMon_Connect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ return fd->lower->methods->connect(fd->lower, addr, timeout);
+}
+
+static PRStatus nsNetMon_Close(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+ MOZ_RELEASE_ASSERT(
+ layer && layer->identity == sNetActivityMonitorLayerIdentity,
+ "NetActivityMonitor Layer not on top of stack");
+
+ if (layer->secret) {
+ delete (ActivityMonitorSecret*)layer->secret;
+ layer->secret = nullptr;
+ }
+ layer->dtor(layer);
+ return fd->methods->close(fd);
+}
+
+static int32_t nsNetMon_Read(PRFileDesc* fd, void* buf, int32_t len) {
+ int32_t ret = fd->lower->methods->read(fd->lower, buf, len);
+ if (ret >= 0) {
+ IOActivityMonitor::Read(fd, len);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Write(PRFileDesc* fd, const void* buf, int32_t len) {
+ int32_t ret = fd->lower->methods->write(fd->lower, buf, len);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, len);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Writev(PRFileDesc* fd, const PRIOVec* iov, int32_t size,
+ PRIntervalTime timeout) {
+ int32_t ret = fd->lower->methods->writev(fd->lower, iov, size, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, size);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Recv(PRFileDesc* fd, void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout) {
+ int32_t ret =
+ fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Read(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Send(PRFileDesc* fd, const void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout) {
+ int32_t ret =
+ fd->lower->methods->send(fd->lower, buf, amount, flags, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_RecvFrom(PRFileDesc* fd, void* buf, int32_t amount,
+ int flags, PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ int32_t ret = fd->lower->methods->recvfrom(fd->lower, buf, amount, flags,
+ addr, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Read(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_SendTo(PRFileDesc* fd, const void* buf, int32_t amount,
+ int flags, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ int32_t ret =
+ fd->lower->methods->sendto(fd->lower, buf, amount, flags, addr, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_AcceptRead(PRFileDesc* listenSock,
+ PRFileDesc** acceptedSock,
+ PRNetAddr** peerAddr, void* buf,
+ int32_t amount, PRIntervalTime timeout) {
+ int32_t ret = listenSock->lower->methods->acceptread(
+ listenSock->lower, acceptedSock, peerAddr, buf, amount, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Read(listenSock, amount);
+ }
+ return ret;
+}
+
+//
+// Class IOActivityMonitor
+//
+NS_IMPL_ISUPPORTS(IOActivityMonitor, nsINamed)
+
+IOActivityMonitor::IOActivityMonitor() : mLock("IOActivityMonitor::mLock") {
+ RefPtr<IOActivityMonitor> mon(gInstance);
+ MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!");
+}
+
+// static
+void IOActivityMonitor::RequestActivities(dom::Promise* aPromise) {
+ MOZ_ASSERT(aPromise);
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ aPromise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mon->RequestActivitiesInternal(aPromise);
+}
+
+void IOActivityMonitor::RequestActivitiesInternal(dom::Promise* aPromise) {
+ nsresult result = NS_OK;
+ FallibleTArray<dom::IOActivityDataDictionary> activities;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ // Remove inactive activities
+ for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
+ dom::IOActivityDataDictionary* activity = &iter.Data();
+ if (activity->mRx == 0 && activity->mTx == 0) {
+ iter.Remove();
+ } else {
+ if (NS_WARN_IF(!activities.AppendElement(iter.Data(), fallible))) {
+ result = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(result))) {
+ aPromise->MaybeReject(result);
+ return;
+ }
+ aPromise->MaybeResolve(activities);
+}
+
+NS_IMETHODIMP
+IOActivityMonitor::GetName(nsACString& aName) {
+ aName.AssignLiteral("IOActivityMonitor");
+ return NS_OK;
+}
+
+// static
+bool IOActivityMonitor::IsActive() { return gActivated; }
+
+// static
+already_AddRefed<IOActivityMonitor> IOActivityMonitor::Get() {
+ if (!gActivated) {
+ return nullptr;
+ }
+
+ RefPtr<IOActivityMonitor> mon = gInstance;
+ return mon.forget();
+}
+
+nsresult IOActivityMonitor::Init() {
+ if (IsActive()) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ RefPtr<IOActivityMonitor> mon = new IOActivityMonitor();
+ nsresult rv = mon->InitInternal();
+ if (NS_SUCCEEDED(rv)) {
+ gInstance = mon;
+ ClearOnShutdown(&gInstance);
+ gActivated = true;
+ }
+ return rv;
+}
+
+nsresult IOActivityMonitor::InitInternal() {
+ // wraps the socket APIs
+ if (!sNetActivityMonitorLayerMethodsPtr) {
+ sNetActivityMonitorLayerIdentity =
+ PR_GetUniqueIdentity("network activity monitor layer");
+ sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods();
+ sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect;
+ sNetActivityMonitorLayerMethods.read = nsNetMon_Read;
+ sNetActivityMonitorLayerMethods.write = nsNetMon_Write;
+ sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev;
+ sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv;
+ sNetActivityMonitorLayerMethods.send = nsNetMon_Send;
+ sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom;
+ sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo;
+ sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead;
+ sNetActivityMonitorLayerMethods.close = nsNetMon_Close;
+ sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods;
+ }
+
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::Shutdown() {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mon->ShutdownInternal();
+}
+
+nsresult IOActivityMonitor::ShutdownInternal() {
+ mozilla::MutexAutoLock lock(mLock);
+ mActivities.Clear();
+ gActivated = false;
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_OK;
+ }
+ PRFileDesc* layer;
+ PRStatus status;
+ layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
+ sNetActivityMonitorLayerMethodsPtr);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd);
+ layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ delete secret;
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::MonitorFile(PRFileDesc* aFd, const char* aPath) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_OK;
+ }
+ PRFileDesc* layer;
+ PRStatus status;
+ layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
+ sNetActivityMonitorLayerMethodsPtr);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd, aPath);
+ layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
+
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+ if (status == PR_FAILURE) {
+ delete secret;
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool IOActivityMonitor::IncrementActivity(const nsACString& aLocation,
+ uint32_t aRx, uint32_t aTx) {
+ mLock.AssertCurrentThreadOwns();
+ return mActivities.WithEntryHandle(aLocation, fallible, [&](auto&& entry) {
+ if (!entry) return false;
+
+ if (*entry) {
+ // already registered
+ entry->Data().mTx += aTx;
+ entry->Data().mRx += aRx;
+ } else {
+ // Creating a new IOActivity. Notice that mActivities
+ // will grow indefinitely, which is OK since we won't
+ // have but a few hundreds entries at the most, but we
+ // want to assert we have at the most 1000 entries
+ MOZ_ASSERT(mActivities.Count() <= MAX_ACTIVITY_ENTRIES);
+
+ dom::IOActivityDataDictionary activity;
+ activity.mLocation.Assign(aLocation);
+ activity.mTx = aTx;
+ activity.mRx = aRx;
+
+ entry->Insert(std::move(activity));
+ }
+
+ return true;
+ });
+}
+
+nsresult IOActivityMonitor::Write(const nsACString& aLocation,
+ uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->WriteInternal(aLocation, aAmount);
+}
+
+nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->Write(FileDesc2Location(fd), aAmount);
+}
+
+nsresult IOActivityMonitor::WriteInternal(const nsACString& aLocation,
+ uint32_t aAmount) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (!IncrementActivity(aLocation, aAmount, 0)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::Read(PRFileDesc* fd, uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->Read(FileDesc2Location(fd), aAmount);
+}
+
+nsresult IOActivityMonitor::Read(const nsACString& aLocation,
+ uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->ReadInternal(aLocation, aAmount);
+}
+
+nsresult IOActivityMonitor::ReadInternal(const nsACString& aLocation,
+ uint32_t aAmount) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (!IncrementActivity(aLocation, 0, aAmount)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/IOActivityMonitor.h b/netwerk/base/IOActivityMonitor.h
new file mode 100644
index 0000000000..5e46bc9bfc
--- /dev/null
+++ b/netwerk/base/IOActivityMonitor.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef IOActivityMonitor_h___
+#define IOActivityMonitor_h___
+
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsINamed.h"
+#include "nsISupports.h"
+#include "prinrval.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include <stdint.h>
+
+namespace mozilla {
+
+namespace dom {
+class Promise;
+}
+
+namespace net {
+
+#define IO_ACTIVITY_ENABLED_PREF "io.activity.enabled"
+
+using Activities = nsTHashMap<nsCStringHashKey, dom::IOActivityDataDictionary>;
+
+// IOActivityMonitor has several roles:
+// - maintains an IOActivity per resource and updates it
+// - sends a dump of the activities to a promise via RequestActivities
+class IOActivityMonitor final : public nsINamed {
+ public:
+ IOActivityMonitor();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINAMED
+
+ // initializes and destroys the singleton
+ static nsresult Init();
+ static nsresult Shutdown();
+
+ // collect amounts of data that are written/read by location
+ static nsresult Read(const nsACString& location, uint32_t aAmount);
+ static nsresult Write(const nsACString& location, uint32_t aAmount);
+
+ static nsresult MonitorFile(PRFileDesc* aFd, const char* aPath);
+ static nsresult MonitorSocket(PRFileDesc* aFd);
+ static nsresult Read(PRFileDesc* fd, uint32_t aAmount);
+ static nsresult Write(PRFileDesc* fd, uint32_t aAmount);
+
+ static bool IsActive();
+ static void RequestActivities(dom::Promise* aPromise);
+
+ private:
+ ~IOActivityMonitor() = default;
+
+ static already_AddRefed<IOActivityMonitor> Get();
+
+ nsresult InitInternal();
+ nsresult ShutdownInternal();
+ bool IncrementActivity(const nsACString& location, uint32_t aRx,
+ uint32_t aTx);
+ nsresult WriteInternal(const nsACString& location, uint32_t aAmount);
+ nsresult ReadInternal(const nsACString& location, uint32_t aAmount);
+ void RequestActivitiesInternal(dom::Promise* aPromise);
+
+ Activities mActivities;
+ // protects mActivities accesses
+ Mutex mLock MOZ_UNANNOTATED;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* IOActivityMonitor_h___ */
diff --git a/netwerk/base/IPv6Utils.h b/netwerk/base/IPv6Utils.h
new file mode 100644
index 0000000000..437c0befec
--- /dev/null
+++ b/netwerk/base/IPv6Utils.h
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NETWORK_IPV6_UTILS_H_
+#define NETWORK_IPV6_UTILS_H_
+
+namespace mozilla {
+namespace net {
+namespace utils {
+
+// IPv6 address scopes.
+#define IPV6_SCOPE_GLOBAL 0 // Global scope.
+#define IPV6_SCOPE_LINKLOCAL 1 // Link-local scope.
+#define IPV6_SCOPE_SITELOCAL 2 // Site-local scope (deprecated).
+#define IPV6_SCOPE_UNIQUELOCAL 3 // Unique local
+#define IPV6_SCOPE_NODELOCAL 4 // Loopback
+
+// Return the scope of the given address.
+static int ipv6_scope(const unsigned char addr[16]) {
+ const unsigned char* b = addr;
+ unsigned short w = (unsigned short)((b[0] << 8) | b[1]);
+
+ if ((b[0] & 0xFE) == 0xFC) {
+ return IPV6_SCOPE_UNIQUELOCAL;
+ }
+ switch (w & 0xFFC0) {
+ case 0xFE80:
+ return IPV6_SCOPE_LINKLOCAL;
+ case 0xFEC0:
+ return IPV6_SCOPE_SITELOCAL;
+ case 0x0000:
+ w = b[1] | b[2] | b[3] | b[4] | b[5] | b[6] | b[7] | b[8] | b[9] | b[10] |
+ b[11] | b[12] | b[13] | b[14];
+ if (w || b[15] != 0x01) {
+ break;
+ }
+ return IPV6_SCOPE_NODELOCAL;
+ default:
+ break;
+ }
+
+ return IPV6_SCOPE_GLOBAL;
+}
+
+} // namespace utils
+} // namespace net
+} // namespace mozilla
+
+#endif // NETWORK_IPV6_UTILS_H_
diff --git a/netwerk/base/InterceptionInfo.cpp b/netwerk/base/InterceptionInfo.cpp
new file mode 100644
index 0000000000..977ec02e47
--- /dev/null
+++ b/netwerk/base/InterceptionInfo.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/InterceptionInfo.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(InterceptionInfo, nsIInterceptionInfo)
+
+InterceptionInfo::InterceptionInfo(nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType,
+ const RedirectHistoryArray& aRedirectChain,
+ bool aFromThirdParty)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mContentPolicyType(aContentPolicyType),
+ mFromThirdParty(aFromThirdParty) {
+ SetRedirectChain(aRedirectChain);
+}
+
+nsIPrincipal* InterceptionInfo::TriggeringPrincipal() {
+ return mTriggeringPrincipal;
+}
+
+void InterceptionInfo::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) {
+ mTriggeringPrincipal = aPrincipal;
+}
+
+nsContentPolicyType InterceptionInfo::ContentPolicyType() {
+ return mContentPolicyType;
+}
+
+nsContentPolicyType InterceptionInfo::ExternalContentPolicyType() {
+ return static_cast<nsContentPolicyType>(
+ nsContentUtils::InternalContentPolicyTypeToExternal(mContentPolicyType));
+}
+
+void InterceptionInfo::SetContentPolicyType(
+ const nsContentPolicyType aContentPolicyType) {
+ mContentPolicyType = aContentPolicyType;
+}
+
+const RedirectHistoryArray& InterceptionInfo::RedirectChain() {
+ return mRedirectChain;
+}
+
+void InterceptionInfo::SetRedirectChain(
+ const RedirectHistoryArray& aRedirectChain) {
+ for (auto entry : aRedirectChain) {
+ mRedirectChain.AppendElement(entry);
+ }
+}
+
+bool InterceptionInfo::FromThirdParty() { return mFromThirdParty; }
+
+void InterceptionInfo::SetFromThirdParty(bool aFromThirdParty) {
+ mFromThirdParty = aFromThirdParty;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/InterceptionInfo.h b/netwerk/base/InterceptionInfo.h
new file mode 100644
index 0000000000..ad2f2a4499
--- /dev/null
+++ b/netwerk/base/InterceptionInfo.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_InterceptionInfo_h
+#define mozilla_net_InterceptionInfo_h
+
+#include "nsIContentSecurityPolicy.h"
+#include "nsIInterceptionInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIRedirectHistoryEntry.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::net {
+
+using RedirectHistoryArray = nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>;
+
+class InterceptionInfo final : public nsIInterceptionInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERCEPTIONINFO
+
+ InterceptionInfo(nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType,
+ const RedirectHistoryArray& aRedirectChain,
+ bool aFromThirdParty);
+
+ using nsIInterceptionInfo::GetExtContentPolicyType;
+
+ private:
+ ~InterceptionInfo() = default;
+
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsContentPolicyType mContentPolicyType{nsIContentPolicy::TYPE_INVALID};
+ RedirectHistoryArray mRedirectChain;
+ bool mFromThirdParty = false;
+};
+
+} // namespace mozilla::net
+
+#endif
diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp
new file mode 100644
index 0000000000..d544bf7275
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.cpp
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LoadContextInfo.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsDocShell.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::dom;
+namespace mozilla {
+namespace net {
+
+// LoadContextInfo
+
+NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo)
+
+LoadContextInfo::LoadContextInfo(bool aIsAnonymous,
+ OriginAttributes aOriginAttributes)
+ : mIsAnonymous(aIsAnonymous),
+ mOriginAttributes(std::move(aOriginAttributes)) {}
+
+NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool* aIsPrivate) {
+ *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool* aIsAnonymous) {
+ *aIsAnonymous = mIsAnonymous;
+ return NS_OK;
+}
+
+OriginAttributes const* LoadContextInfo::OriginAttributesPtr() {
+ return &mOriginAttributes;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aVal) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// LoadContextInfoFactory
+
+NS_IMPL_ISUPPORTS(LoadContextInfoFactory, nsILoadContextInfoFactory)
+
+NS_IMETHODIMP LoadContextInfoFactory::GetDefault(
+ nsILoadContextInfo** aDefault) {
+ nsCOMPtr<nsILoadContextInfo> info =
+ GetLoadContextInfo(false, OriginAttributes());
+ info.forget(aDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetPrivate(
+ nsILoadContextInfo** aPrivate) {
+ OriginAttributes attrs;
+ attrs.SyncAttributesWithPrivateBrowsing(true);
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(false, attrs);
+ info.forget(aPrivate);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetAnonymous(
+ nsILoadContextInfo** aAnonymous) {
+ nsCOMPtr<nsILoadContextInfo> info =
+ GetLoadContextInfo(true, OriginAttributes());
+ info.forget(aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::Custom(
+ bool aAnonymous, JS::Handle<JS::Value> aOriginAttributes, JSContext* cx,
+ nsILoadContextInfo** _retval) {
+ OriginAttributes attrs;
+ bool status = attrs.Init(cx, aOriginAttributes);
+ NS_ENSURE_TRUE(status, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aAnonymous, attrs);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromLoadContext(
+ nsILoadContext* aLoadContext, bool aAnonymous,
+ nsILoadContextInfo** _retval) {
+ nsCOMPtr<nsILoadContextInfo> info =
+ GetLoadContextInfo(aLoadContext, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromWindow(nsIDOMWindow* aWindow,
+ bool aAnonymous,
+ nsILoadContextInfo** _retval) {
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aWindow, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+// Helper functions
+
+LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel) {
+ nsresult rv;
+
+ DebugOnly<bool> pb = NS_UsePrivateBrowsing(aChannel);
+
+ bool anon = false;
+ nsLoadFlags loadFlags;
+ rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_SUCCEEDED(rv)) {
+ anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS);
+ }
+
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChannel, oa);
+ MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0));
+
+ return new LoadContextInfo(anon, oa);
+}
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext,
+ bool aIsAnonymous) {
+ if (!aLoadContext) {
+ return new LoadContextInfo(aIsAnonymous, OriginAttributes());
+ }
+
+ OriginAttributes oa;
+ aLoadContext->GetOriginAttributes(oa);
+
+#ifdef DEBUG
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aLoadContext);
+ if (!docShell ||
+ nsDocShell::Cast(docShell)->GetBrowsingContext()->IsContent()) {
+ MOZ_ASSERT(aLoadContext->UsePrivateBrowsing() ==
+ (oa.mPrivateBrowsingId > 0));
+ }
+#endif
+
+ return new LoadContextInfo(aIsAnonymous, oa);
+}
+
+LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous) {
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
+
+ return GetLoadContextInfo(loadContext, aIsAnonymous);
+}
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo) {
+ return new LoadContextInfo(aInfo->IsAnonymous(),
+ *aInfo->OriginAttributesPtr());
+}
+
+LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous,
+ OriginAttributes const& aOriginAttributes) {
+ return new LoadContextInfo(aIsAnonymous, aOriginAttributes);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h
new file mode 100644
index 0000000000..efaf912db4
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.h
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadContextInfo_h__
+#define nsLoadContextInfo_h__
+
+#include "nsILoadContextInfo.h"
+
+class nsIChannel;
+class nsILoadContext;
+
+namespace mozilla {
+namespace net {
+
+class LoadContextInfo final : public nsILoadContextInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADCONTEXTINFO
+
+ LoadContextInfo(bool aIsAnonymous, OriginAttributes aOriginAttributes);
+
+ private:
+ virtual ~LoadContextInfo() = default;
+
+ protected:
+ bool mIsAnonymous : 1;
+ OriginAttributes mOriginAttributes;
+};
+
+class LoadContextInfoFactory : public nsILoadContextInfoFactory {
+ virtual ~LoadContextInfoFactory() = default;
+
+ public:
+ NS_DECL_ISUPPORTS // deliberately not thread-safe
+ NS_DECL_NSILOADCONTEXTINFOFACTORY
+};
+
+LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel);
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext,
+ bool aIsAnonymous);
+
+LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous);
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo);
+
+LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous,
+ OriginAttributes const& aOriginAttributes);
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
new file mode 100644
index 0000000000..eb90324c37
--- /dev/null
+++ b/netwerk/base/LoadInfo.cpp
@@ -0,0 +1,2372 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/LoadInfo.h"
+
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineElement
+#include "mozilla/Assertions.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientSource.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozIThirdPartyUtil.h"
+#include "ThirdPartyUtil.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIScriptElement.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsUtils.h"
+#include "nsIXPConnect.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindowInner.h"
+#include "nsMixedContentBlocker.h"
+#include "nsQueryObject.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsSandboxFlags.h"
+#include "nsICookieService.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla::net {
+
+static nsCString CurrentRemoteType() {
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsContentProcess());
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ return nsCString(cc->GetRemoteType());
+ }
+ return NOT_REMOTE_TYPE;
+}
+
+static nsContentPolicyType InternalContentPolicyTypeForFrame(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ const auto& maybeEmbedderElementType =
+ aBrowsingContext->GetEmbedderElementType();
+ MOZ_ASSERT(maybeEmbedderElementType.isSome());
+ auto embedderElementType = maybeEmbedderElementType.value();
+
+ // Assign same type as in nsDocShell::DetermineContentType.
+ // N.B. internal content policy type will never be TYPE_DOCUMENT
+ return embedderElementType.EqualsLiteral("iframe")
+ ? nsIContentPolicy::TYPE_INTERNAL_IFRAME
+ : nsIContentPolicy::TYPE_INTERNAL_FRAME;
+}
+
+/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForDocument(
+ dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(aBrowsingContext, aURI, aTriggeringPrincipal,
+ aTriggeringRemoteType, aOriginAttributes,
+ aSecurityFlags, aSandboxFlags);
+}
+
+/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForFrame(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(aBrowsingContext, aTriggeringPrincipal,
+ aTriggeringRemoteType, aSecurityFlags,
+ aSandboxFlags);
+}
+
+/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForNonDocument(
+ dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(
+ aParentWGP, aTriggeringPrincipal, aParentWGP->GetRemoteType(),
+ aContentPolicyType, aSecurityFlags, aSandboxFlags);
+}
+
+LoadInfo::LoadInfo(
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo,
+ const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ uint32_t aSandboxFlags, bool aSkipCheckForBrokenURLOrZeroSized)
+ : mLoadingPrincipal(aLoadingContext ? aLoadingContext->NodePrincipal()
+ : aLoadingPrincipal),
+ mTriggeringPrincipal(aTriggeringPrincipal ? aTriggeringPrincipal
+ : mLoadingPrincipal.get()),
+ mTriggeringRemoteType(CurrentRemoteType()),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mClientInfo(aLoadingClientInfo),
+ mController(aController),
+ mLoadingContext(do_GetWeakReference(aLoadingContext)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(aContentPolicyType),
+ mSkipCheckForBrokenURLOrZeroSized(aSkipCheckForBrokenURLOrZeroSized) {
+ MOZ_ASSERT(mLoadingPrincipal);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+#ifdef DEBUG
+ // TYPE_DOCUMENT loads initiated by javascript tests will go through
+ // nsIOService and use the wrong constructor. Don't enforce the
+ // !TYPE_DOCUMENT check in those cases
+ bool skipContentTypeCheck = false;
+ skipContentTypeCheck =
+ Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
+ // This constructor shouldn't be used for TYPE_DOCUMENT loads that don't
+ // have a loadingPrincipal
+ MOZ_ASSERT(skipContentTypeCheck || mLoadingPrincipal ||
+ mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT);
+
+ // We should only get an explicit controller for subresource requests.
+ MOZ_DIAGNOSTIC_ASSERT(aController.isNothing() ||
+ !nsContentUtils::IsNonSubresourceInternalPolicyType(
+ mInternalContentPolicyType));
+
+ // TODO(bug 1259873): Above, we initialize mIsThirdPartyContext to false
+ // meaning that consumers of LoadInfo that don't pass a context or pass a
+ // context from which we can't find a window will default to assuming that
+ // they're 1st party. It would be nice if we could default "safe" and assume
+ // that we are 3rd party until proven otherwise.
+
+ // if consumers pass both, aLoadingContext and aLoadingPrincipal
+ // then the loadingPrincipal must be the same as the node's principal
+ MOZ_ASSERT(!aLoadingContext || !aLoadingPrincipal ||
+ aLoadingContext->NodePrincipal() == aLoadingPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ ExtContentPolicyType externalType =
+ nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType);
+
+ if (aLoadingContext) {
+ // Ensure that all network requests for a window client have the ClientInfo
+ // properly set. Workers must currently pass the loading ClientInfo
+ // explicitly. We allow main thread requests to explicitly pass the value as
+ // well.
+ if (mClientInfo.isNothing()) {
+ mClientInfo = aLoadingContext->OwnerDoc()->GetClientInfo();
+ }
+
+ // For subresource loads set the service worker based on the calling
+ // context's controller. Workers must currently pass the controller in
+ // explicitly. We allow main thread requests to explicitly pass the value
+ // as well, but otherwise extract from the loading context here.
+ if (mController.isNothing() &&
+ !nsContentUtils::IsNonSubresourceInternalPolicyType(
+ mInternalContentPolicyType)) {
+ mController = aLoadingContext->OwnerDoc()->GetController();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> contextOuter =
+ aLoadingContext->OwnerDoc()->GetWindow();
+ if (contextOuter) {
+ ComputeIsThirdPartyContext(contextOuter);
+ RefPtr<dom::BrowsingContext> bc = contextOuter->GetBrowsingContext();
+ MOZ_ASSERT(bc);
+ mBrowsingContextID = bc->Id();
+
+ nsGlobalWindowInner* innerWindow =
+ nsGlobalWindowInner::Cast(contextOuter->GetCurrentInnerWindow());
+ if (innerWindow) {
+ mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal();
+
+ if (!mTopLevelPrincipal &&
+ externalType == ExtContentPolicy::TYPE_SUBDOCUMENT && bc->IsTop()) {
+ // If this is the first level iframe, innerWindow is our top-level
+ // principal.
+ mTopLevelPrincipal = innerWindow->GetPrincipal();
+ }
+ }
+
+ // Let's inherit the cookie behavior and permission from the parent
+ // document.
+ mCookieJarSettings = aLoadingContext->OwnerDoc()->CookieJarSettings();
+ }
+
+ mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID();
+ RefPtr<WindowContext> ctx = WindowContext::GetById(mInnerWindowID);
+ if (ctx) {
+ mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy();
+ }
+ mDocumentHasUserInteracted =
+ aLoadingContext->OwnerDoc()->UserHasInteracted();
+
+ // Inherit HTTPS-Only Mode flags from parent document
+ mHttpsOnlyStatus |= aLoadingContext->OwnerDoc()->HttpsOnlyStatus();
+
+ // When the element being loaded is a frame, we choose the frame's window
+ // for the window ID and the frame element's window as the parent
+ // window. This is the behavior that Chrome exposes to add-ons.
+ // NB: If the frameLoaderOwner doesn't have a frame loader, then the load
+ // must be coming from an object (such as a plugin) that's loaded into it
+ // instead of a document being loaded. In that case, treat this object like
+ // any other non-document-loading element.
+ RefPtr<nsFrameLoaderOwner> frameLoaderOwner =
+ do_QueryObject(aLoadingContext);
+ RefPtr<nsFrameLoader> fl =
+ frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr;
+ if (fl) {
+ nsCOMPtr<nsIDocShell> docShell = fl->GetDocShell(IgnoreErrors());
+ if (docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell);
+ if (outerWindow) {
+ RefPtr<dom::BrowsingContext> bc = outerWindow->GetBrowsingContext();
+ mFrameBrowsingContextID = bc ? bc->Id() : 0;
+ }
+ }
+ }
+
+ // if the document forces all mixed content to be blocked, then we
+ // store that bit for all requests on the loadinfo.
+ mBlockAllMixedContent =
+ aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(false) ||
+ (nsContentUtils::IsPreloadType(mInternalContentPolicyType) &&
+ aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(true));
+
+ if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal)
+ ->OverridesCSP(mLoadingPrincipal)) {
+ // if the load is triggered by an addon which potentially overrides the
+ // CSP of the document, then do not force insecure requests to be
+ // upgraded.
+ mUpgradeInsecureRequests = false;
+ } else {
+ // if the document forces all requests to be upgraded from http to https,
+ // then we should do that for all requests. If it only forces preloads to
+ // be upgraded then we should enforce upgrade insecure requests only for
+ // preloads.
+ mUpgradeInsecureRequests =
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(false) ||
+ (nsContentUtils::IsPreloadType(mInternalContentPolicyType) &&
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(true));
+ }
+
+ if (nsMixedContentBlocker::IsUpgradableContentType(
+ mInternalContentPolicyType, /* aConsiderPrefs */ false)) {
+ // Check the load is within a secure context but ignore loopback URLs
+ if (mLoadingPrincipal->GetIsOriginPotentiallyTrustworthy() &&
+ !mLoadingPrincipal->GetIsLoopbackHost()) {
+ if (nsMixedContentBlocker::IsUpgradableContentType(
+ mInternalContentPolicyType, /* aConsiderPrefs */ true)) {
+ mBrowserUpgradeInsecureRequests = true;
+ } else {
+ mBrowserWouldUpgradeInsecureRequests = true;
+ }
+ }
+ }
+ }
+ mOriginAttributes = mLoadingPrincipal->OriginAttributesRef();
+
+ // We need to do this after inheriting the document's origin attributes
+ // above, in case the loading principal ends up being the system principal.
+ if (aLoadingContext) {
+ nsCOMPtr<nsILoadContext> loadContext =
+ aLoadingContext->OwnerDoc()->GetLoadContext();
+ nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell();
+ if (loadContext && docShell &&
+ docShell->GetBrowsingContext()->IsContent()) {
+ bool usePrivateBrowsing;
+ nsresult rv = loadContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+ if (NS_SUCCEEDED(rv)) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing);
+ }
+ }
+
+ if (!loadContext) {
+ // Things like svg documents being used as images don't have a load
+ // context or a docshell, in that case try to inherit private browsing
+ // from the documents channel (which is how we determine which imgLoader
+ // is used).
+ nsCOMPtr<nsIChannel> channel = aLoadingContext->OwnerDoc()->GetChannel();
+ if (channel) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(
+ NS_UsePrivateBrowsing(channel));
+ }
+ }
+
+ // For chrome docshell, the mPrivateBrowsingId remains 0 even its
+ // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in
+ // origin attributes if the type of the docshell is content.
+ MOZ_ASSERT(!docShell || !docShell->GetBrowsingContext()->IsChrome() ||
+ mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+}
+
+/* Constructor takes an outer window, but no loadingNode or loadingPrincipal.
+ * This constructor should only be used for TYPE_DOCUMENT loads, since they
+ * have a null loadingNode and loadingPrincipal.
+ */
+LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsISupports* aContextForTopLevelLoad,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mTriggeringRemoteType(CurrentRemoteType()),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mContextForTopLevelLoad(do_GetWeakReference(aContextForTopLevelLoad)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party
+ // Grab the information we can out of the window.
+ MOZ_ASSERT(aOuterWindow);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ RefPtr<BrowsingContext> bc = aOuterWindow->GetBrowsingContext();
+ mBrowsingContextID = bc ? bc->Id() : 0;
+
+ // This should be removed in bug 1618557
+ nsGlobalWindowInner* innerWindow =
+ nsGlobalWindowInner::Cast(aOuterWindow->GetCurrentInnerWindow());
+ if (innerWindow) {
+ mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal();
+ }
+
+ // get the docshell from the outerwindow, and then get the originattributes
+ nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
+ MOZ_ASSERT(docShell);
+ mOriginAttributes = nsDocShell::Cast(docShell)->GetOriginAttributes();
+
+ // We sometimes use this constructor for security checks for outer windows
+ // that aren't top level.
+ if (aSecurityFlags != nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK) {
+ MOZ_ASSERT(aOuterWindow->GetBrowsingContext()->IsTop());
+ }
+
+#ifdef DEBUG
+ if (docShell->GetBrowsingContext()->IsChrome()) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+#endif
+
+ // Let's take the current cookie behavior and current cookie permission
+ // for the documents' loadInfo. Note that for any other loadInfos,
+ // cookieBehavior will be BEHAVIOR_REJECT for security reasons.
+ bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ aURI, mOriginAttributes,
+ "We are creating CookieJarSettings, so we can't have one already.",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ mCookieJarSettings = CookieJarSettings::Create(
+ isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+}
+
+LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mTriggeringRemoteType(aTriggeringRemoteType),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party
+ // Grab the information we can out of the window.
+ MOZ_ASSERT(aBrowsingContext);
+ MOZ_ASSERT(mTriggeringPrincipal);
+ MOZ_ASSERT(aSecurityFlags !=
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ mBrowsingContextID = aBrowsingContext->Id();
+ mOriginAttributes = aOriginAttributes;
+
+#ifdef DEBUG
+ if (aBrowsingContext->IsChrome()) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+#endif
+
+ // If we think we should not resist fingerprinting, defer to the opener's
+ // RFP bit (if there is an opener.) If the opener is also exempted, it stays
+ // true, otherwise we will put a false into the CJS and that will be respected
+ // on this document.
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ aURI, mOriginAttributes,
+ "We are creating CookieJarSettings, so we can't have one already.",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ RefPtr<BrowsingContext> opener = aBrowsingContext->GetOpener();
+ if (!shouldResistFingerprinting && opener &&
+ opener->GetCurrentWindowContext()) {
+ shouldResistFingerprinting =
+ opener->GetCurrentWindowContext()->ShouldResistFingerprinting();
+ }
+
+ const bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+
+ // Let's take the current cookie behavior and current cookie permission
+ // for the documents' loadInfo. Note that for any other loadInfos,
+ // cookieBehavior will be BEHAVIOR_REJECT for security reasons.
+ mCookieJarSettings = CookieJarSettings::Create(
+ isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+}
+
+LoadInfo::LoadInfo(dom::WindowGlobalParent* aParentWGP,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsContentPolicyType aContentPolicyType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mTriggeringRemoteType(aTriggeringRemoteType),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(aContentPolicyType) {
+ CanonicalBrowsingContext* parentBC = aParentWGP->BrowsingContext();
+ MOZ_ASSERT(parentBC);
+ ComputeAncestors(parentBC, mAncestorPrincipals, mAncestorBrowsingContextIDs);
+
+ RefPtr<WindowGlobalParent> topLevelWGP = aParentWGP->TopWindowContext();
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ // Ensure that all network requests for a window client have the ClientInfo
+ // properly set.
+ mClientInfo = aParentWGP->GetClientInfo();
+ mLoadingPrincipal = aParentWGP->DocumentPrincipal();
+ ComputeIsThirdPartyContext(aParentWGP);
+
+ mBrowsingContextID = parentBC->Id();
+
+ // Let's inherit the cookie behavior and permission from the embedder
+ // document.
+ mCookieJarSettings = aParentWGP->CookieJarSettings();
+ if (topLevelWGP->BrowsingContext()->IsTop()) {
+ if (mCookieJarSettings) {
+ bool stopAtOurLevel = mCookieJarSettings->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER;
+ if (!stopAtOurLevel ||
+ topLevelWGP->OuterWindowId() != aParentWGP->OuterWindowId()) {
+ mTopLevelPrincipal = topLevelWGP->DocumentPrincipal();
+ }
+ }
+ }
+
+ if (!mTopLevelPrincipal && parentBC->IsTop()) {
+ // If this is the first level iframe, embedder WindowGlobalParent's document
+ // principal is our top-level principal.
+ mTopLevelPrincipal = aParentWGP->DocumentPrincipal();
+ }
+
+ mInnerWindowID = aParentWGP->InnerWindowId();
+ mDocumentHasUserInteracted = aParentWGP->DocumentHasUserInteracted();
+
+ // if the document forces all mixed content to be blocked, then we
+ // store that bit for all requests on the loadinfo.
+ mBlockAllMixedContent = aParentWGP->GetDocumentBlockAllMixedContent();
+
+ if (mTopLevelPrincipal && BasePrincipal::Cast(mTriggeringPrincipal)
+ ->OverridesCSP(mTopLevelPrincipal)) {
+ // if the load is triggered by an addon which potentially overrides the
+ // CSP of the document, then do not force insecure requests to be
+ // upgraded.
+ mUpgradeInsecureRequests = false;
+ } else {
+ // if the document forces all requests to be upgraded from http to https,
+ // then we should do that for all requests. If it only forces preloads to
+ // be upgraded then we should enforce upgrade insecure requests only for
+ // preloads.
+ mUpgradeInsecureRequests = aParentWGP->GetDocumentUpgradeInsecureRequests();
+ }
+ mOriginAttributes = mLoadingPrincipal->OriginAttributesRef();
+
+ // We need to do this after inheriting the document's origin attributes
+ // above, in case the loading principal ends up being the system principal.
+ if (parentBC->IsContent()) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(
+ parentBC->UsePrivateBrowsing());
+ }
+
+ mHttpsOnlyStatus |= aParentWGP->HttpsOnlyStatus();
+
+ // For chrome BC, the mPrivateBrowsingId remains 0 even its
+ // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in
+ // origin attributes if the type of the BC is content.
+ if (parentBC->IsChrome()) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+
+ RefPtr<WindowContext> ctx = WindowContext::GetById(mInnerWindowID);
+ if (ctx) {
+ mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy();
+
+ if (Document* document = ctx->GetDocument()) {
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel =
+ document->Trials().IsEnabled(OriginTrial::CoepCredentialless);
+ }
+ }
+}
+
+// Used for TYPE_FRAME or TYPE_IFRAME load.
+LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : LoadInfo(aBrowsingContext->GetParentWindowContext(), aTriggeringPrincipal,
+ aTriggeringRemoteType,
+ InternalContentPolicyTypeForFrame(aBrowsingContext),
+ aSecurityFlags, aSandboxFlags) {
+ mFrameBrowsingContextID = aBrowsingContext->Id();
+}
+
+LoadInfo::LoadInfo(const LoadInfo& rhs)
+ : mLoadingPrincipal(rhs.mLoadingPrincipal),
+ mTriggeringPrincipal(rhs.mTriggeringPrincipal),
+ mPrincipalToInherit(rhs.mPrincipalToInherit),
+ mTopLevelPrincipal(rhs.mTopLevelPrincipal),
+ mResultPrincipalURI(rhs.mResultPrincipalURI),
+ mChannelCreationOriginalURI(rhs.mChannelCreationOriginalURI),
+ mCookieJarSettings(rhs.mCookieJarSettings),
+ mCspToInherit(rhs.mCspToInherit),
+ mTriggeringRemoteType(rhs.mTriggeringRemoteType),
+ mSandboxedNullPrincipalID(rhs.mSandboxedNullPrincipalID),
+ mClientInfo(rhs.mClientInfo),
+ // mReservedClientSource must be handled specially during redirect
+ // mReservedClientInfo must be handled specially during redirect
+ // mInitialClientInfo must be handled specially during redirect
+ mController(rhs.mController),
+ mPerformanceStorage(rhs.mPerformanceStorage),
+ mLoadingContext(rhs.mLoadingContext),
+ mContextForTopLevelLoad(rhs.mContextForTopLevelLoad),
+ mSecurityFlags(rhs.mSecurityFlags),
+ mSandboxFlags(rhs.mSandboxFlags),
+ mTriggeringSandboxFlags(rhs.mTriggeringSandboxFlags),
+ mTriggeringWindowId(rhs.mTriggeringWindowId),
+ mTriggeringStorageAccess(rhs.mTriggeringStorageAccess),
+ mInternalContentPolicyType(rhs.mInternalContentPolicyType),
+ mTainting(rhs.mTainting),
+ mBlockAllMixedContent(rhs.mBlockAllMixedContent),
+ mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests),
+ mBrowserUpgradeInsecureRequests(rhs.mBrowserUpgradeInsecureRequests),
+ mBrowserDidUpgradeInsecureRequests(
+ rhs.mBrowserDidUpgradeInsecureRequests),
+ mBrowserWouldUpgradeInsecureRequests(
+ rhs.mBrowserWouldUpgradeInsecureRequests),
+ mForceAllowDataURI(rhs.mForceAllowDataURI),
+ mAllowInsecureRedirectToDataURI(rhs.mAllowInsecureRedirectToDataURI),
+ mSkipContentPolicyCheckForWebRequest(
+ rhs.mSkipContentPolicyCheckForWebRequest),
+ mOriginalFrameSrcLoad(rhs.mOriginalFrameSrcLoad),
+ mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped),
+ mInnerWindowID(rhs.mInnerWindowID),
+ mBrowsingContextID(rhs.mBrowsingContextID),
+ mWorkerAssociatedBrowsingContextID(
+ rhs.mWorkerAssociatedBrowsingContextID),
+ mFrameBrowsingContextID(rhs.mFrameBrowsingContextID),
+ mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone),
+ mIsThirdPartyContext(rhs.mIsThirdPartyContext),
+ mIsThirdPartyContextToTopWindow(rhs.mIsThirdPartyContextToTopWindow),
+ mIsFormSubmission(rhs.mIsFormSubmission),
+ mSendCSPViolationEvents(rhs.mSendCSPViolationEvents),
+ mOriginAttributes(rhs.mOriginAttributes),
+ mRedirectChainIncludingInternalRedirects(
+ rhs.mRedirectChainIncludingInternalRedirects.Clone()),
+ mRedirectChain(rhs.mRedirectChain.Clone()),
+ mAncestorPrincipals(rhs.mAncestorPrincipals.Clone()),
+ mAncestorBrowsingContextIDs(rhs.mAncestorBrowsingContextIDs.Clone()),
+ mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders.Clone()),
+ mRequestBlockingReason(rhs.mRequestBlockingReason),
+ mForcePreflight(rhs.mForcePreflight),
+ mIsPreflight(rhs.mIsPreflight),
+ mLoadTriggeredFromExternal(rhs.mLoadTriggeredFromExternal),
+ mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted),
+ mAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ rhs.mAllowListFutureDocumentsCreatedFromThisRedirectChain),
+ mNeedForCheckingAntiTrackingHeuristic(
+ rhs.mNeedForCheckingAntiTrackingHeuristic),
+ mCspNonce(rhs.mCspNonce),
+ mIntegrityMetadata(rhs.mIntegrityMetadata),
+ mSkipContentSniffing(rhs.mSkipContentSniffing),
+ mHttpsOnlyStatus(rhs.mHttpsOnlyStatus),
+ mHstsStatus(rhs.mHstsStatus),
+ mHasValidUserGestureActivation(rhs.mHasValidUserGestureActivation),
+ mAllowDeprecatedSystemRequests(rhs.mAllowDeprecatedSystemRequests),
+ mIsInDevToolsContext(rhs.mIsInDevToolsContext),
+ mParserCreatedScript(rhs.mParserCreatedScript),
+ mStoragePermission(rhs.mStoragePermission),
+ mOverriddenFingerprintingSettings(rhs.mOverriddenFingerprintingSettings),
+#ifdef DEBUG
+ mOverriddenFingerprintingSettingsIsSet(
+ rhs.mOverriddenFingerprintingSettingsIsSet),
+#endif
+ mIsMetaRefresh(rhs.mIsMetaRefresh),
+ mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes),
+ mIsMediaRequest(rhs.mIsMediaRequest),
+ mIsMediaInitialRequest(rhs.mIsMediaInitialRequest),
+ mIsFromObjectOrEmbed(rhs.mIsFromObjectOrEmbed),
+ mLoadingEmbedderPolicy(rhs.mLoadingEmbedderPolicy),
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ rhs.mIsOriginTrialCoepCredentiallessEnabledForTopLevel),
+ mUnstrippedURI(rhs.mUnstrippedURI),
+ mInterceptionInfo(rhs.mInterceptionInfo),
+ mHasInjectedCookieForCookieBannerHandling(
+ rhs.mHasInjectedCookieForCookieBannerHandling),
+ mWasSchemelessInput(rhs.mWasSchemelessInput) {
+}
+
+LoadInfo::LoadInfo(
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal,
+ nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings,
+ nsIContentSecurityPolicy* aCspToInherit,
+ const nsACString& aTriggeringRemoteType,
+ const nsID& aSandboxedNullPrincipalID, const Maybe<ClientInfo>& aClientInfo,
+ const Maybe<ClientInfo>& aReservedClientInfo,
+ const Maybe<ClientInfo>& aInitialClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags,
+ uint32_t aTriggeringSandboxFlags, uint64_t aTriggeringWindowId,
+ bool aTriggeringStorageAccess, nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting, bool aBlockAllMixedContent,
+ bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests,
+ bool aBrowserDidUpgradeInsecureRequests,
+ bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI,
+ bool aAllowInsecureRedirectToDataURI,
+ bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad,
+ bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID,
+ uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID,
+ bool aInitialSecurityCheckDone, bool aIsThirdPartyContext,
+ const Maybe<bool>& aIsThirdPartyContextToTopWindow, bool aIsFormSubmission,
+ bool aSendCSPViolationEvents, const OriginAttributes& aOriginAttributes,
+ RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects,
+ RedirectHistoryArray&& aRedirectChain,
+ nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
+ const nsTArray<uint64_t>& aAncestorBrowsingContextIDs,
+ const nsTArray<nsCString>& aCorsUnsafeHeaders, bool aForcePreflight,
+ bool aIsPreflight, bool aLoadTriggeredFromExternal,
+ bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted,
+ bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
+ bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce,
+ const nsAString& aIntegrityMetadata, bool aSkipContentSniffing,
+ uint32_t aHttpsOnlyStatus, bool aHstsStatus,
+ bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests,
+ bool aIsInDevToolsContext, bool aParserCreatedScript,
+ nsILoadInfo::StoragePermissionState aStoragePermission,
+ const Maybe<RFPTarget>& aOverriddenFingerprintingSettings,
+ bool aIsMetaRefresh, uint32_t aRequestBlockingReason,
+ nsINode* aLoadingContext,
+ nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy,
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel,
+ nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo,
+ bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput)
+ : mLoadingPrincipal(aLoadingPrincipal),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPrincipalToInherit(aPrincipalToInherit),
+ mTopLevelPrincipal(aTopLevelPrincipal),
+ mResultPrincipalURI(aResultPrincipalURI),
+ mCookieJarSettings(aCookieJarSettings),
+ mCspToInherit(aCspToInherit),
+ mTriggeringRemoteType(aTriggeringRemoteType),
+ mSandboxedNullPrincipalID(aSandboxedNullPrincipalID),
+ mClientInfo(aClientInfo),
+ mReservedClientInfo(aReservedClientInfo),
+ mInitialClientInfo(aInitialClientInfo),
+ mController(aController),
+ mLoadingContext(do_GetWeakReference(aLoadingContext)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mTriggeringSandboxFlags(aTriggeringSandboxFlags),
+ mTriggeringWindowId(aTriggeringWindowId),
+ mTriggeringStorageAccess(aTriggeringStorageAccess),
+ mInternalContentPolicyType(aContentPolicyType),
+ mTainting(aTainting),
+ mBlockAllMixedContent(aBlockAllMixedContent),
+ mUpgradeInsecureRequests(aUpgradeInsecureRequests),
+ mBrowserUpgradeInsecureRequests(aBrowserUpgradeInsecureRequests),
+ mBrowserDidUpgradeInsecureRequests(aBrowserDidUpgradeInsecureRequests),
+ mBrowserWouldUpgradeInsecureRequests(
+ aBrowserWouldUpgradeInsecureRequests),
+ mForceAllowDataURI(aForceAllowDataURI),
+ mAllowInsecureRedirectToDataURI(aAllowInsecureRedirectToDataURI),
+ mSkipContentPolicyCheckForWebRequest(
+ aSkipContentPolicyCheckForWebRequest),
+ mOriginalFrameSrcLoad(aOriginalFrameSrcLoad),
+ mForceInheritPrincipalDropped(aForceInheritPrincipalDropped),
+ mInnerWindowID(aInnerWindowID),
+ mBrowsingContextID(aBrowsingContextID),
+ mFrameBrowsingContextID(aFrameBrowsingContextID),
+ mInitialSecurityCheckDone(aInitialSecurityCheckDone),
+ mIsThirdPartyContext(aIsThirdPartyContext),
+ mIsThirdPartyContextToTopWindow(aIsThirdPartyContextToTopWindow),
+ mIsFormSubmission(aIsFormSubmission),
+ mSendCSPViolationEvents(aSendCSPViolationEvents),
+ mOriginAttributes(aOriginAttributes),
+ mRedirectChainIncludingInternalRedirects(
+ std::move(aRedirectChainIncludingInternalRedirects)),
+ mRedirectChain(std::move(aRedirectChain)),
+ mAncestorPrincipals(std::move(aAncestorPrincipals)),
+ mAncestorBrowsingContextIDs(aAncestorBrowsingContextIDs.Clone()),
+ mCorsUnsafeHeaders(aCorsUnsafeHeaders.Clone()),
+ mRequestBlockingReason(aRequestBlockingReason),
+ mForcePreflight(aForcePreflight),
+ mIsPreflight(aIsPreflight),
+ mLoadTriggeredFromExternal(aLoadTriggeredFromExternal),
+ mServiceWorkerTaintingSynthesized(aServiceWorkerTaintingSynthesized),
+ mDocumentHasUserInteracted(aDocumentHasUserInteracted),
+ mAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ aAllowListFutureDocumentsCreatedFromThisRedirectChain),
+ mNeedForCheckingAntiTrackingHeuristic(
+ aNeedForCheckingAntiTrackingHeuristic),
+ mCspNonce(aCspNonce),
+ mIntegrityMetadata(aIntegrityMetadata),
+ mSkipContentSniffing(aSkipContentSniffing),
+ mHttpsOnlyStatus(aHttpsOnlyStatus),
+ mHstsStatus(aHstsStatus),
+ mHasValidUserGestureActivation(aHasValidUserGestureActivation),
+ mAllowDeprecatedSystemRequests(aAllowDeprecatedSystemRequests),
+ mIsInDevToolsContext(aIsInDevToolsContext),
+ mParserCreatedScript(aParserCreatedScript),
+ mStoragePermission(aStoragePermission),
+ mOverriddenFingerprintingSettings(aOverriddenFingerprintingSettings),
+ mIsMetaRefresh(aIsMetaRefresh),
+ mLoadingEmbedderPolicy(aLoadingEmbedderPolicy),
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ aIsOriginTrialCoepCredentiallessEnabledForTopLevel),
+ mUnstrippedURI(aUnstrippedURI),
+ mInterceptionInfo(aInterceptionInfo),
+ mHasInjectedCookieForCookieBannerHandling(
+ aHasInjectedCookieForCookieBannerHandling),
+ mWasSchemelessInput(aWasSchemelessInput) {
+ // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
+ MOZ_ASSERT(mLoadingPrincipal ||
+ aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
+ MOZ_ASSERT(mTriggeringPrincipal);
+}
+
+// static
+void LoadInfo::ComputeAncestors(
+ CanonicalBrowsingContext* aBC,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aAncestorPrincipals,
+ nsTArray<uint64_t>& aBrowsingContextIDs) {
+ MOZ_ASSERT(aAncestorPrincipals.IsEmpty());
+ MOZ_ASSERT(aBrowsingContextIDs.IsEmpty());
+ CanonicalBrowsingContext* ancestorBC = aBC;
+ // Iterate over ancestor WindowGlobalParents, collecting principals and outer
+ // window IDs.
+ while (WindowGlobalParent* ancestorWGP =
+ ancestorBC->GetParentWindowContext()) {
+ ancestorBC = ancestorWGP->BrowsingContext();
+
+ nsCOMPtr<nsIPrincipal> parentPrincipal = ancestorWGP->DocumentPrincipal();
+ MOZ_ASSERT(parentPrincipal, "Ancestor principal is null");
+ aAncestorPrincipals.AppendElement(parentPrincipal.forget());
+ aBrowsingContextIDs.AppendElement(ancestorBC->Id());
+ }
+}
+void LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow) {
+ ExtContentPolicyType type =
+ nsContentUtils::InternalContentPolicyTypeToExternal(
+ mInternalContentPolicyType);
+ if (type == ExtContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party.
+ mIsThirdPartyContext = false;
+ return;
+ }
+
+ nsCOMPtr<mozIThirdPartyUtil> util(do_GetService(THIRDPARTYUTIL_CONTRACTID));
+ if (NS_WARN_IF(!util)) {
+ return;
+ }
+
+ util->IsThirdPartyWindow(aOuterWindow, nullptr, &mIsThirdPartyContext);
+}
+
+void LoadInfo::ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal) {
+ if (nsILoadInfo::GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party.
+ mIsThirdPartyContext = false;
+ return;
+ }
+
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ if (!thirdPartyUtil) {
+ return;
+ }
+ thirdPartyUtil->IsThirdPartyGlobal(aGlobal, &mIsThirdPartyContext);
+}
+
+NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo)
+
+LoadInfo::~LoadInfo() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); }
+
+already_AddRefed<nsILoadInfo> LoadInfo::Clone() const {
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo> LoadInfo::CloneWithNewSecFlags(
+ nsSecurityFlags aSecurityFlags) const {
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mSecurityFlags = aSecurityFlags;
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo> LoadInfo::CloneForNewRequest() const {
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mInitialSecurityCheckDone = false;
+ copy->mRedirectChainIncludingInternalRedirects.Clear();
+ copy->mRedirectChain.Clear();
+ copy->mResultPrincipalURI = nullptr;
+ return copy.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) {
+ *aLoadingPrincipal = do_AddRef(mLoadingPrincipal).take();
+ return NS_OK;
+}
+
+nsIPrincipal* LoadInfo::VirtualGetLoadingPrincipal() {
+ return mLoadingPrincipal;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) {
+ *aTriggeringPrincipal = do_AddRef(mTriggeringPrincipal).take();
+ return NS_OK;
+}
+
+nsIPrincipal* LoadInfo::TriggeringPrincipal() { return mTriggeringPrincipal; }
+
+NS_IMETHODIMP
+LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ *aPrincipalToInherit = do_AddRef(mPrincipalToInherit).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit");
+ mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+nsIPrincipal* LoadInfo::PrincipalToInherit() { return mPrincipalToInherit; }
+
+nsIPrincipal* LoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) {
+ if (mPrincipalToInherit) {
+ return mPrincipalToInherit;
+ }
+
+ nsCOMPtr<nsIURI> uri = mResultPrincipalURI;
+ if (!uri) {
+ Unused << aChannel->GetOriginalURI(getter_AddRefs(uri));
+ }
+
+ auto* prin = BasePrincipal::Cast(mTriggeringPrincipal);
+ return prin->PrincipalToInherit(uri);
+}
+
+const nsID& LoadInfo::GetSandboxedNullPrincipalID() {
+ MOZ_ASSERT(!mSandboxedNullPrincipalID.Equals(nsID{}),
+ "mSandboxedNullPrincipalID wasn't initialized?");
+ return mSandboxedNullPrincipalID;
+}
+
+void LoadInfo::ResetSandboxedNullPrincipalID() {
+ mSandboxedNullPrincipalID = nsID::GenerateUUID();
+}
+
+nsIPrincipal* LoadInfo::GetTopLevelPrincipal() { return mTopLevelPrincipal; }
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) {
+ aTriggeringRemoteType = mTriggeringRemoteType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) {
+ mTriggeringRemoteType = aTriggeringRemoteType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingDocument(Document** aResult) {
+ if (nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext)) {
+ RefPtr<Document> context = node->OwnerDoc();
+ context.forget(aResult);
+ }
+ return NS_OK;
+}
+
+nsINode* LoadInfo::LoadingNode() {
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ return node;
+}
+
+already_AddRefed<nsISupports> LoadInfo::ContextForTopLevelLoad() {
+ // Most likely you want to query LoadingNode() instead of
+ // ContextForTopLevelLoad() if this assertion fires.
+ MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "should only query this context for top level document loads");
+ nsCOMPtr<nsISupports> context = do_QueryReferent(mContextForTopLevelLoad);
+ return context.forget();
+}
+
+already_AddRefed<nsISupports> LoadInfo::GetLoadingContext() {
+ nsCOMPtr<nsISupports> context;
+ if (mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
+ context = ContextForTopLevelLoad();
+ } else {
+ context = LoadingNode();
+ }
+ return context.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) {
+ nsCOMPtr<nsISupports> context = GetLoadingContext();
+ context.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) {
+ *aResult = mSecurityFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSandboxFlags(uint32_t* aResult) {
+ *aResult = mSandboxFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) {
+ *aResult = mTriggeringSandboxFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringSandboxFlags(uint32_t aFlags) {
+ mTriggeringSandboxFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringWindowId(uint64_t* aResult) {
+ *aResult = mTriggeringWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringWindowId(uint64_t aFlags) {
+ mTriggeringWindowId = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringStorageAccess(bool* aResult) {
+ *aResult = mTriggeringStorageAccess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringStorageAccess(bool aFlags) {
+ mTriggeringStorageAccess = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityMode(uint32_t* aFlags) {
+ *aFlags = (mSecurityFlags &
+ (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT |
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL |
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) {
+ *aIsInThirdPartyContext = mIsThirdPartyContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) {
+ mIsThirdPartyContext = aIsInThirdPartyContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsThirdPartyContextToTopWindow(
+ bool* aIsThirdPartyContextToTopWindow) {
+ *aIsThirdPartyContextToTopWindow =
+ mIsThirdPartyContextToTopWindow.valueOr(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsThirdPartyContextToTopWindow(
+ bool aIsThirdPartyContextToTopWindow) {
+ mIsThirdPartyContextToTopWindow = Some(aIsThirdPartyContextToTopWindow);
+ return NS_OK;
+}
+
+static const uint32_t sCookiePolicyMask =
+ nsILoadInfo::SEC_COOKIES_DEFAULT | nsILoadInfo::SEC_COOKIES_INCLUDE |
+ nsILoadInfo::SEC_COOKIES_SAME_ORIGIN | nsILoadInfo::SEC_COOKIES_OMIT;
+
+NS_IMETHODIMP
+LoadInfo::GetCookiePolicy(uint32_t* aResult) {
+ uint32_t policy = mSecurityFlags & sCookiePolicyMask;
+ if (policy == nsILoadInfo::SEC_COOKIES_DEFAULT) {
+ policy = (mSecurityFlags & SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT)
+ ? nsILoadInfo::SEC_COOKIES_SAME_ORIGIN
+ : nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+
+ *aResult = policy;
+ return NS_OK;
+}
+
+namespace {
+
+already_AddRefed<nsICookieJarSettings> CreateCookieJarSettings(
+ nsContentPolicyType aContentPolicyType, bool aIsPrivate,
+ bool shouldResistFingerprinting) {
+ if (StaticPrefs::network_cookieJarSettings_unblocked_for_testing()) {
+ return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate,
+ shouldResistFingerprinting)
+ : CookieJarSettings::Create(CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+ }
+
+ // These contentPolictTypes require a real CookieJarSettings because favicon
+ // and save-as requests must send cookies. Anything else should not
+ // send/receive cookies.
+ if (aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON ||
+ aContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate,
+ shouldResistFingerprinting)
+ : CookieJarSettings::Create(CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+ }
+
+ return CookieJarSettings::GetBlockingAll(shouldResistFingerprinting);
+}
+
+} // namespace
+
+NS_IMETHODIMP
+LoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) {
+ if (!mCookieJarSettings) {
+ bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ Unused << this->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ loadingPrincipal,
+ "CookieJarSettings can't exist yet, we're creating it",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ mCookieJarSettings = CreateCookieJarSettings(
+ mInternalContentPolicyType, isPrivate, shouldResistFingerprinting);
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings = mCookieJarSettings;
+ cookieJarSettings.forget(aCookieJarSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) {
+ MOZ_ASSERT(aCookieJarSettings);
+ // We allow the overwrite of CookieJarSettings.
+ mCookieJarSettings = aCookieJarSettings;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetStoragePermission(
+ nsILoadInfo::StoragePermissionState* aStoragePermission) {
+ *aStoragePermission = mStoragePermission;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetStoragePermission(
+ nsILoadInfo::StoragePermissionState aStoragePermission) {
+ mStoragePermission = aStoragePermission;
+ return NS_OK;
+}
+
+const Maybe<RFPTarget>& LoadInfo::GetOverriddenFingerprintingSettings() {
+#ifdef DEBUG
+ RefPtr<BrowsingContext> browsingContext;
+ GetTargetBrowsingContext(getter_AddRefs(browsingContext));
+
+ // Exclude this check if the target browsing context is for the parent
+ // process.
+ MOZ_ASSERT_IF(XRE_IsParentProcess() && browsingContext &&
+ !browsingContext->IsInProcess(),
+ mOverriddenFingerprintingSettingsIsSet);
+#endif
+ return mOverriddenFingerprintingSettings;
+}
+
+void LoadInfo::SetOverriddenFingerprintingSettings(RFPTarget aTargets) {
+ mOverriddenFingerprintingSettings.reset();
+ mOverriddenFingerprintingSettings.emplace(aTargets);
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsMetaRefresh(bool* aIsMetaRefresh) {
+ *aIsMetaRefresh = mIsMetaRefresh;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsMetaRefresh(bool aIsMetaRefresh) {
+ mIsMetaRefresh = aIsMetaRefresh;
+ return NS_OK;
+}
+
+void LoadInfo::SetIncludeCookiesSecFlag() {
+ MOZ_ASSERT((mSecurityFlags & sCookiePolicyMask) ==
+ nsILoadInfo::SEC_COOKIES_DEFAULT);
+ mSecurityFlags =
+ (mSecurityFlags & ~sCookiePolicyMask) | nsILoadInfo::SEC_COOKIES_INCLUDE;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) {
+ *aInheritPrincipal =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) {
+ *aInheritPrincipal =
+ (mSecurityFlags &
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) {
+ *aLoadingSandboxed = (mSandboxFlags & SANDBOXED_ORIGIN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAboutBlankInherits(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_ABOUT_BLANK_INHERITS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowChrome(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDisallowScript(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDontFollowRedirects(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadErrorPage(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_LOAD_ERROR_PAGE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsFormSubmission(bool* aResult) {
+ *aResult = mIsFormSubmission;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsFormSubmission(bool aValue) {
+ mIsFormSubmission = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSendCSPViolationEvents(bool* aResult) {
+ *aResult = mSendCSPViolationEvents;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetSendCSPViolationEvents(bool aValue) {
+ mSendCSPViolationEvents = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) {
+ // We have to use nsContentPolicyType because ExtContentPolicyType is not
+ // visible from xpidl.
+ *aResult = static_cast<nsContentPolicyType>(
+ nsContentUtils::InternalContentPolicyTypeToExternal(
+ mInternalContentPolicyType));
+ return NS_OK;
+}
+
+nsContentPolicyType LoadInfo::InternalContentPolicyType() {
+ return mInternalContentPolicyType;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBlockAllMixedContent(bool* aResult) {
+ *aResult = mBlockAllMixedContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mBrowserUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mBrowserDidUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mBrowserWouldUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) {
+ MOZ_ASSERT(!mForceAllowDataURI ||
+ mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "can only allow data URI navigation for TYPE_DOCUMENT");
+ mForceAllowDataURI = aForceAllowDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) {
+ *aForceAllowDataURI = mForceAllowDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetAllowInsecureRedirectToDataURI(
+ bool aAllowInsecureRedirectToDataURI) {
+ mAllowInsecureRedirectToDataURI = aAllowInsecureRedirectToDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowInsecureRedirectToDataURI(
+ bool* aAllowInsecureRedirectToDataURI) {
+ *aAllowInsecureRedirectToDataURI = mAllowInsecureRedirectToDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) {
+ mSkipContentPolicyCheckForWebRequest = aSkip;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) {
+ *aSkip = mSkipContentPolicyCheckForWebRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) {
+ mOriginalFrameSrcLoad = aOriginalFrameSrcLoad;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) {
+ *aOriginalFrameSrcLoad = mOriginalFrameSrcLoad;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalDropped(bool* aResult) {
+ *aResult = mForceInheritPrincipalDropped;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInnerWindowID(uint64_t* aResult) {
+ *aResult = mInnerWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowsingContextID(uint64_t* aResult) {
+ *aResult = mBrowsingContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) {
+ *aResult = mWorkerAssociatedBrowsingContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aID) {
+ mWorkerAssociatedBrowsingContextID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) {
+ *aResult = mFrameBrowsingContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) {
+ return (nsILoadInfo::GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT)
+ ? GetFrameBrowsingContextID(aResult)
+ : GetBrowsingContextID(aResult);
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) {
+ *aResult = BrowsingContext::Get(mBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetWorkerAssociatedBrowsingContext(dom::BrowsingContext** aResult) {
+ *aResult = BrowsingContext::Get(mWorkerAssociatedBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) {
+ *aResult = BrowsingContext::Get(mFrameBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) {
+ uint64_t targetBrowsingContextID = 0;
+ MOZ_ALWAYS_SUCCEEDS(GetTargetBrowsingContextID(&targetBrowsingContextID));
+ *aResult = BrowsingContext::Get(targetBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::ResetPrincipalToInheritToNullPrincipal() {
+ // take the originAttributes from the LoadInfo and create
+ // a new NullPrincipal using those origin attributes.
+ nsCOMPtr<nsIPrincipal> newNullPrincipal =
+ NullPrincipal::Create(mOriginAttributes);
+
+ mPrincipalToInherit = newNullPrincipal;
+
+ // setting SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER will overrule
+ // any non null owner set on the channel and will return the principal
+ // form the loadinfo instead.
+ mSecurityFlags |= SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult LoadInfo::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult LoadInfo::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) {
+ // Indicates whether the channel was ever evaluated by the
+ // ContentSecurityManager. Once set to true, this flag must
+ // remain true throughout the lifetime of the channel.
+ // Setting it to anything else than true will be discarded.
+ MOZ_ASSERT(aInitialSecurityCheckDone,
+ "aInitialSecurityCheckDone must be true");
+ mInitialSecurityCheckDone =
+ mInitialSecurityCheckDone || aInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInitialSecurityCheckDone(bool* aResult) {
+ *aResult = mInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+// To prevent unintenional credential and information leaks in content
+// processes we can use this function to truncate a Principal's URI as much as
+// possible.
+already_AddRefed<nsIPrincipal> CreateTruncatedPrincipal(
+ nsIPrincipal* aPrincipal) {
+ nsCOMPtr<nsIPrincipal> truncatedPrincipal;
+ // System Principal URIs don't need to be truncated as they don't contain any
+ // sensitive browsing history information.
+ if (aPrincipal->IsSystemPrincipal()) {
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ // Content Principal URIs are the main location of the information we need to
+ // truncate.
+ if (aPrincipal->GetIsContentPrincipal()) {
+ // Certain URIs (chrome, resource, about, jar) don't need to be truncated
+ // as they should be free of any sensitive user browsing history.
+ if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource") ||
+ aPrincipal->SchemeIs("about") || aPrincipal->SchemeIs("jar")) {
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ // Different parts of the URI are preserved due to being vital to the
+ // browser's operation.
+ // Scheme for differentiating between different types of URIs and how to
+ // truncate them and later on utilize them.
+ // Host and Port to retain the redirect chain's core functionality.
+ // Path would ideally be removed but needs to be retained to ensure that
+ // http/https redirect loops can be detected.
+ // The entirety of the Query String, Reference Fragment, and User Info
+ // subcomponents must be stripped to avoid leaking Oauth tokens, user
+ // identifiers, and similar bits of information that these subcomponents may
+ // contain.
+ nsAutoCString scheme;
+ nsAutoCString separator("://");
+ nsAutoCString hostPort;
+ nsAutoCString path;
+ nsAutoCString uriString("");
+ if (aPrincipal->SchemeIs("view-source")) {
+ // The path portion of the view-source URI will be the URI whose source is
+ // being viewed, so we create a new URI object with a truncated form of
+ // the path and append the view-source scheme to the front again.
+ nsAutoCString viewSourcePath;
+ aPrincipal->GetFilePath(viewSourcePath);
+
+ nsCOMPtr<nsIURI> nestedURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(nestedURI), viewSourcePath);
+
+ if (NS_FAILED(rv)) {
+ // Since the path here should be an already validated URI this should
+ // never happen.
+ NS_WARNING(viewSourcePath.get());
+ MOZ_ASSERT(false,
+ "Failed to create truncated form of URI with NS_NewURI.");
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ nestedURI->GetScheme(scheme);
+ nestedURI->GetHostPort(hostPort);
+ nestedURI->GetFilePath(path);
+ uriString += "view-source:";
+ } else {
+ aPrincipal->GetScheme(scheme);
+ aPrincipal->GetHostPort(hostPort);
+ aPrincipal->GetFilePath(path);
+ }
+ uriString += scheme + separator + hostPort + path;
+
+ nsCOMPtr<nsIURI> truncatedURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(truncatedURI), uriString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(uriString.get());
+ MOZ_ASSERT(false,
+ "Failed to create truncated form of URI with NS_NewURI.");
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ return BasePrincipal::CreateContentPrincipal(
+ truncatedURI, aPrincipal->OriginAttributesRef());
+ }
+
+ // Null Principal Precursor URIs can also contain information that needs to
+ // be truncated.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ nsCOMPtr<nsIPrincipal> precursorPrincipal =
+ aPrincipal->GetPrecursorPrincipal();
+ // If there is no precursor then nothing needs to be truncated.
+ if (!precursorPrincipal) {
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ // Otherwise we return a new Null Principal with the original's Origin
+ // Attributes and a truncated version of the original's precursor URI.
+ nsCOMPtr<nsIPrincipal> truncatedPrecursor =
+ CreateTruncatedPrincipal(precursorPrincipal);
+ return NullPrincipal::CreateWithInheritedAttributes(truncatedPrecursor);
+ }
+
+ // Expanded Principals shouldn't contain sensitive information but their
+ // allowlists might so we truncate that information here.
+ if (aPrincipal->GetIsExpandedPrincipal()) {
+ nsTArray<nsCOMPtr<nsIPrincipal>> truncatedAllowList;
+
+ for (const auto& allowedPrincipal : BasePrincipal::Cast(aPrincipal)
+ ->As<ExpandedPrincipal>()
+ ->AllowList()) {
+ nsCOMPtr<nsIPrincipal> truncatedPrincipal =
+ CreateTruncatedPrincipal(allowedPrincipal);
+
+ truncatedAllowList.AppendElement(truncatedPrincipal);
+ }
+
+ return ExpandedPrincipal::Create(truncatedAllowList,
+ aPrincipal->OriginAttributesRef());
+ }
+
+ // If we hit this assertion we need to update this function to add the
+ // Principals and URIs seen as new corner cases to handle.
+ MOZ_ASSERT(false, "Unhandled Principal or URI type encountered.");
+
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannel,
+ bool aIsInternalRedirect) {
+ NS_ENSURE_ARG(aChannel);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIPrincipal> uriPrincipal;
+ nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager();
+ sm->GetChannelURIPrincipal(aChannel, getter_AddRefs(uriPrincipal));
+
+ nsCOMPtr<nsIURI> referrer;
+ nsCString remoteAddress;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ Unused << httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ if (referrerInfo) {
+ referrer = referrerInfo->GetComputedReferrer();
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> intChannel(do_QueryInterface(aChannel));
+ if (intChannel) {
+ Unused << intChannel->GetRemoteAddress(remoteAddress);
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> truncatedPrincipal =
+ CreateTruncatedPrincipal(uriPrincipal);
+
+ nsCOMPtr<nsIRedirectHistoryEntry> entry =
+ new nsRedirectHistoryEntry(truncatedPrincipal, referrer, remoteAddress);
+
+ mRedirectChainIncludingInternalRedirects.AppendElement(entry);
+ if (!aIsInternalRedirect) {
+ mRedirectChain.AppendElement(entry);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aRedirects,
+ const RedirectHistoryArray& aArray) {
+ JS::Rooted<JSObject*> redirects(aCx,
+ JS::NewArrayObject(aCx, aArray.Length()));
+ NS_ENSURE_TRUE(redirects, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+
+ for (size_t idx = 0; idx < aArray.Length(); idx++) {
+ JS::Rooted<JSObject*> jsobj(aCx);
+ nsresult rv =
+ xpc->WrapNative(aCx, global, aArray[idx],
+ NS_GET_IID(nsIRedirectHistoryEntry), jsobj.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(jsobj);
+
+ bool rc = JS_DefineElement(aCx, redirects, idx, jsobj, JSPROP_ENUMERATE);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ }
+
+ aRedirects.setObject(*redirects);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChainIncludingInternalRedirects(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aChain) {
+ return GetRedirects(aCx, aChain, mRedirectChainIncludingInternalRedirects);
+}
+
+const RedirectHistoryArray&
+LoadInfo::RedirectChainIncludingInternalRedirects() {
+ return mRedirectChainIncludingInternalRedirects;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChain(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aChain) {
+ return GetRedirects(aCx, aChain, mRedirectChain);
+}
+
+const RedirectHistoryArray& LoadInfo::RedirectChain() { return mRedirectChain; }
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>& LoadInfo::AncestorPrincipals() {
+ return mAncestorPrincipals;
+}
+
+const nsTArray<uint64_t>& LoadInfo::AncestorBrowsingContextIDs() {
+ return mAncestorBrowsingContextIDs;
+}
+
+void LoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders,
+ bool aForcePreflight) {
+ MOZ_ASSERT(GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mCorsUnsafeHeaders = aHeaders.Clone();
+ mForcePreflight = aForcePreflight;
+}
+
+const nsTArray<nsCString>& LoadInfo::CorsUnsafeHeaders() {
+ return mCorsUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForcePreflight(bool* aForcePreflight) {
+ *aForcePreflight = mForcePreflight;
+ return NS_OK;
+}
+
+void LoadInfo::SetIsPreflight() {
+ MOZ_ASSERT(GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mIsPreflight = true;
+}
+
+void LoadInfo::SetUpgradeInsecureRequests(bool aValue) {
+ mUpgradeInsecureRequests = aValue;
+}
+
+void LoadInfo::SetBrowserUpgradeInsecureRequests() {
+ mBrowserUpgradeInsecureRequests = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetBrowserDidUpgradeInsecureRequests(
+ bool aBrowserDidUpgradeInsecureRequests) {
+ mBrowserDidUpgradeInsecureRequests = aBrowserDidUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+void LoadInfo::SetBrowserWouldUpgradeInsecureRequests() {
+ mBrowserWouldUpgradeInsecureRequests = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsPreflight(bool* aIsPreflight) {
+ *aIsPreflight = mIsPreflight;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) {
+ MOZ_ASSERT(!aLoadTriggeredFromExternal ||
+ mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "can only set load triggered from external for TYPE_DOCUMENT");
+ mLoadTriggeredFromExternal = aLoadTriggeredFromExternal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) {
+ *aLoadTriggeredFromExternal = mLoadTriggeredFromExternal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetServiceWorkerTaintingSynthesized(
+ bool* aServiceWorkerTaintingSynthesized) {
+ MOZ_ASSERT(aServiceWorkerTaintingSynthesized);
+ *aServiceWorkerTaintingSynthesized = mServiceWorkerTaintingSynthesized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTainting(uint32_t* aTaintingOut) {
+ MOZ_ASSERT(aTaintingOut);
+ *aTaintingOut = static_cast<uint32_t>(mTainting);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::MaybeIncreaseTainting(uint32_t aTainting) {
+ NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE);
+
+ // Skip if the tainting has been set by the service worker.
+ if (mServiceWorkerTaintingSynthesized) {
+ return NS_OK;
+ }
+
+ LoadTainting tainting = static_cast<LoadTainting>(aTainting);
+ if (tainting > mTainting) {
+ mTainting = tainting;
+ }
+ return NS_OK;
+}
+
+void LoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) {
+ MOZ_DIAGNOSTIC_ASSERT(aTainting <= LoadTainting::Opaque);
+ mTainting = aTainting;
+
+ // Flag to prevent the tainting from being increased.
+ mServiceWorkerTaintingSynthesized = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) {
+ MOZ_ASSERT(aDocumentHasUserInteracted);
+ *aDocumentHasUserInteracted = mDocumentHasUserInteracted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) {
+ mDocumentHasUserInteracted = aDocumentHasUserInteracted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ bool* aValue) {
+ MOZ_ASSERT(aValue);
+ *aValue = mAllowListFutureDocumentsCreatedFromThisRedirectChain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain(bool aValue) {
+ mAllowListFutureDocumentsCreatedFromThisRedirectChain = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) {
+ MOZ_ASSERT(aValue);
+ *aValue = mNeedForCheckingAntiTrackingHeuristic;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) {
+ mNeedForCheckingAntiTrackingHeuristic = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetCspNonce(nsAString& aCspNonce) {
+ aCspNonce = mCspNonce;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetCspNonce(const nsAString& aCspNonce) {
+ MOZ_ASSERT(!mInitialSecurityCheckDone,
+ "setting the nonce is only allowed before any sec checks");
+ mCspNonce = aCspNonce;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ aIntegrityMetadata = mIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
+ MOZ_ASSERT(!mInitialSecurityCheckDone,
+ "setting the nonce is only allowed before any sec checks");
+ mIntegrityMetadata = aIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) {
+ *aSkipContentSniffing = mSkipContentSniffing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
+ mSkipContentSniffing = aSkipContentSniffing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) {
+ *aHttpsOnlyStatus = mHttpsOnlyStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) {
+ mHttpsOnlyStatus = aHttpsOnlyStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHstsStatus(bool* aHstsStatus) {
+ *aHstsStatus = mHstsStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHstsStatus(bool aHstsStatus) {
+ mHstsStatus = aHstsStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHasValidUserGestureActivation(
+ bool* aHasValidUserGestureActivation) {
+ *aHasValidUserGestureActivation = mHasValidUserGestureActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHasValidUserGestureActivation(
+ bool aHasValidUserGestureActivation) {
+ mHasValidUserGestureActivation = aHasValidUserGestureActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowDeprecatedSystemRequests(
+ bool* aAllowDeprecatedSystemRequests) {
+ *aAllowDeprecatedSystemRequests = mAllowDeprecatedSystemRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetAllowDeprecatedSystemRequests(
+ bool aAllowDeprecatedSystemRequests) {
+ mAllowDeprecatedSystemRequests = aAllowDeprecatedSystemRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) {
+ *aIsUserTriggeredSave =
+ mIsUserTriggeredSave ||
+ mInternalContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) {
+ mIsUserTriggeredSave = aIsUserTriggeredSave;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) {
+ *aIsInDevToolsContext = mIsInDevToolsContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) {
+ mIsInDevToolsContext = aIsInDevToolsContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) {
+ *aParserCreatedScript = mParserCreatedScript;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetParserCreatedScript(bool aParserCreatedScript) {
+ mParserCreatedScript = aParserCreatedScript;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsTopLevelLoad(bool* aResult) {
+ RefPtr<dom::BrowsingContext> bc;
+ GetTargetBrowsingContext(getter_AddRefs(bc));
+ *aResult = !bc || bc->IsTop();
+ return NS_OK;
+}
+
+void LoadInfo::SetIsFromProcessingFrameAttributes() {
+ mIsFromProcessingFrameAttributes = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsFromProcessingFrameAttributes(
+ bool* aIsFromProcessingFrameAttributes) {
+ MOZ_ASSERT(aIsFromProcessingFrameAttributes);
+ *aIsFromProcessingFrameAttributes = mIsFromProcessingFrameAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsMediaRequest(bool aIsMediaRequest) {
+ mIsMediaRequest = aIsMediaRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) {
+ MOZ_ASSERT(aIsMediaRequest);
+ *aIsMediaRequest = mIsMediaRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) {
+ mIsMediaInitialRequest = aIsMediaInitialRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) {
+ MOZ_ASSERT(aIsMediaInitialRequest);
+ *aIsMediaInitialRequest = mIsMediaInitialRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) {
+ mIsFromObjectOrEmbed = aIsFromObjectOrEmbed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) {
+ MOZ_ASSERT(aIsFromObjectOrEmbed);
+ *aIsFromObjectOrEmbed = mIsFromObjectOrEmbed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized(
+ bool* aShouldSkipCheckForBrokenURLOrZeroSized) {
+ MOZ_ASSERT(aShouldSkipCheckForBrokenURLOrZeroSized);
+ *aShouldSkipCheckForBrokenURLOrZeroSized = mSkipCheckForBrokenURLOrZeroSized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetResultPrincipalURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mResultPrincipalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetResultPrincipalURI(nsIURI* aURI) {
+ mResultPrincipalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mChannelCreationOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) {
+ mChannelCreationOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetRequestBlockingReason(uint32_t aReason) {
+ mRequestBlockingReason = aReason;
+ return NS_OK;
+}
+NS_IMETHODIMP
+LoadInfo::GetRequestBlockingReason(uint32_t* aReason) {
+ *aReason = mRequestBlockingReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetUnstrippedURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mUnstrippedURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetUnstrippedURI(nsIURI* aURI) {
+ mUnstrippedURI = aURI;
+ return NS_OK;
+}
+
+void LoadInfo::SetClientInfo(const ClientInfo& aClientInfo) {
+ mClientInfo.emplace(aClientInfo);
+}
+
+const Maybe<ClientInfo>& LoadInfo::GetClientInfo() { return mClientInfo; }
+
+void LoadInfo::GiveReservedClientSource(
+ UniquePtr<ClientSource>&& aClientSource) {
+ MOZ_DIAGNOSTIC_ASSERT(aClientSource);
+ mReservedClientSource = std::move(aClientSource);
+ SetReservedClientInfo(mReservedClientSource->Info());
+}
+
+UniquePtr<ClientSource> LoadInfo::TakeReservedClientSource() {
+ if (mReservedClientSource) {
+ // If the reserved ClientInfo was set due to a ClientSource being present,
+ // then clear that info object when the ClientSource is taken.
+ mReservedClientInfo.reset();
+ }
+ return std::move(mReservedClientSource);
+}
+
+void LoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(mInitialClientInfo.isNothing());
+ // Treat assignments of the same value as a no-op. The emplace below
+ // will normally assert when overwriting an existing value.
+ if (mReservedClientInfo.isSome()) {
+ if (mReservedClientInfo.ref() == aClientInfo) {
+ return;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(false, "mReservedClientInfo already set");
+ mReservedClientInfo.reset();
+ }
+ mReservedClientInfo.emplace(aClientInfo);
+}
+
+void LoadInfo::OverrideReservedClientInfoInParent(
+ const ClientInfo& aClientInfo) {
+ // This should only be called to handle redirects in the parent process.
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ mInitialClientInfo.reset();
+ mReservedClientInfo.reset();
+ mReservedClientInfo.emplace(aClientInfo);
+}
+
+const Maybe<ClientInfo>& LoadInfo::GetReservedClientInfo() {
+ return mReservedClientInfo;
+}
+
+void LoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(!mReservedClientSource);
+ MOZ_DIAGNOSTIC_ASSERT(mReservedClientInfo.isNothing());
+ // Treat assignments of the same value as a no-op. The emplace below
+ // will normally assert when overwriting an existing value.
+ if (mInitialClientInfo.isSome() && mInitialClientInfo.ref() == aClientInfo) {
+ return;
+ }
+ mInitialClientInfo.emplace(aClientInfo);
+}
+
+const Maybe<ClientInfo>& LoadInfo::GetInitialClientInfo() {
+ return mInitialClientInfo;
+}
+
+void LoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) {
+ mController.emplace(aServiceWorker);
+}
+
+void LoadInfo::ClearController() { mController.reset(); }
+
+const Maybe<ServiceWorkerDescriptor>& LoadInfo::GetController() {
+ return mController;
+}
+
+void LoadInfo::SetPerformanceStorage(PerformanceStorage* aPerformanceStorage) {
+ mPerformanceStorage = aPerformanceStorage;
+}
+
+PerformanceStorage* LoadInfo::GetPerformanceStorage() {
+ if (mPerformanceStorage) {
+ return mPerformanceStorage;
+ }
+
+ auto* innerWindow = nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowID);
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ if (!TriggeringPrincipal()->Equals(innerWindow->GetPrincipal())) {
+ return nullptr;
+ }
+
+ if (nsILoadInfo::GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT &&
+ !GetIsFromProcessingFrameAttributes()) {
+ // We only report loads caused by processing the attributes of the
+ // browsing context container.
+ return nullptr;
+ }
+
+ mozilla::dom::Performance* performance = innerWindow->GetPerformance();
+ if (!performance) {
+ return nullptr;
+ }
+
+ return performance->AsPerformanceStorage();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) {
+ *aCSPEventListener = do_AddRef(mCSPEventListener).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) {
+ mCSPEventListener = aCSPEventListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) {
+ *aResult = mInternalContentPolicyType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ *aOutPolicy = mLoadingEmbedderPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
+ mLoadingEmbedderPolicy = aPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ *aIsOriginTrialCoepCredentiallessEnabledForTopLevel =
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel =
+ aIsOriginTrialCoepCredentiallessEnabledForTopLevel;
+ return NS_OK;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetCsp() {
+ // Before querying the CSP from the client we have to check if the
+ // triggeringPrincipal originates from an addon and potentially
+ // overrides the CSP stored within the client.
+ if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal)
+ ->OverridesCSP(mLoadingPrincipal)) {
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(mTriggeringPrincipal);
+ nsCOMPtr<nsIContentSecurityPolicy> addonCSP;
+ if (ep) {
+ addonCSP = ep->GetCsp();
+ }
+ return addonCSP.forget();
+ }
+
+ if (mClientInfo.isNothing()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ RefPtr<Document> doc = node ? node->OwnerDoc() : nullptr;
+
+ // If the client is of type window, then we return the cached CSP
+ // stored on the document instead of having to deserialize the CSP
+ // from the ClientInfo.
+ if (doc && mClientInfo->Type() == ClientType::Window) {
+ nsCOMPtr<nsIContentSecurityPolicy> docCSP = doc->GetCsp();
+ return docCSP.forget();
+ }
+
+ Maybe<mozilla::ipc::CSPInfo> cspInfo = mClientInfo->GetCspInfo();
+ if (cspInfo.isNothing()) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIContentSecurityPolicy> clientCSP =
+ CSPInfoToCSP(cspInfo.ref(), doc);
+ return clientCSP.forget();
+}
+
+already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetPreloadCsp() {
+ if (mClientInfo.isNothing()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ RefPtr<Document> doc = node ? node->OwnerDoc() : nullptr;
+
+ // If the client is of type window, then we return the cached CSP
+ // stored on the document instead of having to deserialize the CSP
+ // from the ClientInfo.
+ if (doc && mClientInfo->Type() == ClientType::Window) {
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = doc->GetPreloadCsp();
+ return preloadCsp.forget();
+ }
+
+ Maybe<mozilla::ipc::CSPInfo> cspInfo = mClientInfo->GetPreloadCspInfo();
+ if (cspInfo.isNothing()) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCSP =
+ CSPInfoToCSP(cspInfo.ref(), doc);
+ return preloadCSP.forget();
+}
+
+already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetCspToInherit() {
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = mCspToInherit;
+ return cspToInherit.forget();
+}
+
+nsIInterceptionInfo* LoadInfo::InterceptionInfo() { return mInterceptionInfo; }
+
+void LoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aInfo) {
+ mInterceptionInfo = aInfo;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHasInjectedCookieForCookieBannerHandling(
+ bool* aHasInjectedCookieForCookieBannerHandling) {
+ *aHasInjectedCookieForCookieBannerHandling =
+ mHasInjectedCookieForCookieBannerHandling;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHasInjectedCookieForCookieBannerHandling(
+ bool aHasInjectedCookieForCookieBannerHandling) {
+ mHasInjectedCookieForCookieBannerHandling =
+ aHasInjectedCookieForCookieBannerHandling;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) {
+ *aWasSchemelessInput = mWasSchemelessInput;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) {
+ mWasSchemelessInput = aWasSchemelessInput;
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
new file mode 100644
index 0000000000..a8631b09b2
--- /dev/null
+++ b/netwerk/base/LoadInfo.h
@@ -0,0 +1,413 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadInfo_h
+#define mozilla_LoadInfo_h
+
+#include "nsIContentSecurityPolicy.h"
+#include "nsIInterceptionInfo.h"
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+
+class nsDocShell;
+class nsICookieJarSettings;
+class nsINode;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+namespace dom {
+class PerformanceStorage;
+class XMLHttpRequestMainThread;
+class CanonicalBrowsingContext;
+class WindowGlobalParent;
+} // namespace dom
+
+namespace net {
+class EarlyHintPreloader;
+class LoadInfoArgs;
+class LoadInfo;
+} // namespace net
+
+namespace ipc {
+// we have to forward declare that function so we can use it as a friend.
+nsresult LoadInfoArgsToLoadInfo(const mozilla::net::LoadInfoArgs& aLoadInfoArgs,
+ const nsACString& aOriginRemoteType,
+ nsINode* aCspToInheritLoadingContext,
+ net::LoadInfo** outLoadInfo);
+} // namespace ipc
+
+namespace net {
+
+using RedirectHistoryArray = nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>;
+
+/**
+ * Class that provides an nsILoadInfo implementation.
+ */
+class LoadInfo final : public nsILoadInfo {
+ template <typename T, typename... Args>
+ friend already_AddRefed<T> mozilla::MakeAndAddRef(Args&&... aArgs);
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSILOADINFO
+
+ // Used for TYPE_DOCUMENT load.
+ static already_AddRefed<LoadInfo> CreateForDocument(
+ dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ // Used for TYPE_FRAME or TYPE_IFRAME load.
+ static already_AddRefed<LoadInfo> CreateForFrame(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ // Use for non-{TYPE_DOCUMENT|TYPE_FRAME|TYPE_IFRAME} load.
+ static already_AddRefed<LoadInfo> CreateForNonDocument(
+ dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ // aLoadingPrincipal MUST NOT BE NULL.
+ LoadInfo(nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo =
+ Maybe<mozilla::dom::ClientInfo>(),
+ const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController =
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(),
+ uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = 0);
+
+ // Constructor used for TYPE_DOCUMENT loads which have a different
+ // loadingContext than other loads. This ContextForTopLevelLoad is
+ // only used for content policy checks.
+ LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsISupports* aContextForTopLevelLoad, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ private:
+ // Use factory function CreateForDocument
+ // Used for TYPE_DOCUMENT load.
+ LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags);
+
+ // Use factory function CreateForFrame
+ // Used for TYPE_FRAME or TYPE_IFRAME load.
+ LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags);
+
+ // Used for loads initiated by DocumentLoadListener that are not TYPE_DOCUMENT
+ // | TYPE_FRAME | TYPE_FRAME.
+ LoadInfo(dom::WindowGlobalParent* aParentWGP,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsContentPolicyType aContentPolicyType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags);
+
+ public:
+ // Compute a list of ancestor principals and BrowsingContext IDs.
+ // See methods AncestorPrincipals and AncestorBrowsingContextIDs
+ // in nsILoadInfo.idl for details.
+ static void ComputeAncestors(
+ dom::CanonicalBrowsingContext* aBC,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aAncestorPrincipals,
+ nsTArray<uint64_t>& aBrowsingContextIDs);
+
+ // create an exact copy of the loadinfo
+ already_AddRefed<nsILoadInfo> Clone() const;
+
+ // hands off!!! don't use CloneWithNewSecFlags unless you know
+ // exactly what you are doing - it should only be used within
+ // nsBaseChannel::Redirect()
+ already_AddRefed<nsILoadInfo> CloneWithNewSecFlags(
+ nsSecurityFlags aSecurityFlags) const;
+ // creates a copy of the loadinfo which is appropriate to use for a
+ // separate request. I.e. not for a redirect or an inner channel, but
+ // when a separate request is made with the same security properties.
+ already_AddRefed<nsILoadInfo> CloneForNewRequest() const;
+
+ // The `nsContentPolicyType GetExternalContentPolicyType()` version in the
+ // base class is hidden by the implementation of
+ // `GetExternalContentPolicyType(nsContentPolicyType* aResult)` in
+ // LoadInfo.cpp. Explicit mark it visible.
+ using nsILoadInfo::GetExternalContentPolicyType;
+
+ void SetIsPreflight();
+ void SetUpgradeInsecureRequests(bool aValue);
+ void SetBrowserUpgradeInsecureRequests();
+ void SetBrowserWouldUpgradeInsecureRequests();
+ void SetIsFromProcessingFrameAttributes();
+
+ // Hands off from the cspToInherit functionality!
+ //
+ // For navigations, GetCSPToInherit returns what the spec calls the
+ // "request's client's global object's CSP list", or more precisely
+ // a snapshot of it taken when the navigation starts. For navigations
+ // that need to inherit their CSP, this is the right CSP to use for
+ // the new document. We need a way to transfer the CSP from the
+ // docshell (where the navigation starts) to the point where the new
+ // document is created and decides whether to inherit its CSP, and
+ // this is the mechanism we use for that.
+ //
+ // For example:
+ // A document with a CSP triggers a new top-level data: URI load.
+ // We pass the CSP of the document that triggered the load all the
+ // way to docshell. Within docshell we call SetCSPToInherit() on the
+ // loadinfo. Within Document::InitCSP() we check if the newly created
+ // document needs to inherit the CSP. If so, we call GetCSPToInherit()
+ // and set the inherited CSP as the CSP for the new document. Please
+ // note that any additonal Meta CSP in that document will be merged
+ // into that CSP. Any subresource loads within that document
+ // subesquently will receive the correct CSP by querying
+ // loadinfo->GetCsp() from that point on.
+ void SetCSPToInherit(nsIContentSecurityPolicy* aCspToInherit) {
+ mCspToInherit = aCspToInherit;
+ }
+
+ bool HasIsThirdPartyContextToTopWindowSet() {
+ return mIsThirdPartyContextToTopWindow.isSome();
+ }
+ void ClearIsThirdPartyContextToTopWindow() {
+ mIsThirdPartyContextToTopWindow.reset();
+ }
+
+#ifdef DEBUG
+ void MarkOverriddenFingerprintingSettingsAsSet() {
+ mOverriddenFingerprintingSettingsIsSet = true;
+ }
+#endif
+
+ private:
+ // private constructor that is only allowed to be called from within
+ // HttpChannelParent and FTPChannelParent declared as friends undeneath.
+ // In e10s we can not serialize nsINode, hence we store the innerWindowID.
+ // Please note that aRedirectChain uses swapElements.
+ LoadInfo(
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal,
+ nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings,
+ nsIContentSecurityPolicy* aCspToInherit,
+ const nsACString& aTriggeringRemoteType,
+ const nsID& aSandboxedNullPrincipalID,
+ const Maybe<mozilla::dom::ClientInfo>& aClientInfo,
+ const Maybe<mozilla::dom::ClientInfo>& aReservedClientInfo,
+ const Maybe<mozilla::dom::ClientInfo>& aInitialClientInfo,
+ const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags,
+ uint32_t aTriggeringSandboxFlags, uint64_t aTriggeringWindowId,
+ bool aTriggeringStorageAccess, nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting, bool aBlockAllMixedContent,
+ bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests,
+ bool aBrowserDidUpgradeInsecureRequests,
+ bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI,
+ bool aAllowInsecureRedirectToDataURI,
+ bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad,
+ bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID,
+ uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID,
+ bool aInitialSecurityCheckDone, bool aIsThirdPartyContext,
+ const Maybe<bool>& aIsThirdPartyContextToTopWindow,
+ bool aIsFormSubmission, bool aSendCSPViolationEvents,
+ const OriginAttributes& aOriginAttributes,
+ RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects,
+ RedirectHistoryArray&& aRedirectChain,
+ nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
+ const nsTArray<uint64_t>& aAncestorBrowsingContextIDs,
+ const nsTArray<nsCString>& aCorsUnsafeHeaders, bool aForcePreflight,
+ bool aIsPreflight, bool aLoadTriggeredFromExternal,
+ bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted,
+ bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
+ bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce,
+ const nsAString& aIntegrityMetadata, bool aSkipContentSniffing,
+ uint32_t aHttpsOnlyStatus, bool aHstsStatus,
+ bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests,
+ bool aIsInDevToolsContext, bool aParserCreatedScript,
+ nsILoadInfo::StoragePermissionState aStoragePermission,
+ const Maybe<RFPTarget>& aOverriddenFingerprintingSettings,
+ bool aIsMetaRefresh, uint32_t aRequestBlockingReason,
+ nsINode* aLoadingContext,
+ nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy,
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel,
+ nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo,
+ bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput);
+ LoadInfo(const LoadInfo& rhs);
+
+ NS_IMETHOD GetRedirects(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRedirects,
+ const RedirectHistoryArray& aArra);
+
+ friend nsresult mozilla::ipc::LoadInfoArgsToLoadInfo(
+ const mozilla::net::LoadInfoArgs& aLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext,
+ net::LoadInfo** outLoadInfo);
+
+ ~LoadInfo();
+
+ void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow);
+ void ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal);
+
+ // This function is the *only* function which can change the securityflags
+ // of a loadinfo. It only exists because of the XHR code. Don't call it
+ // from anywhere else!
+ void SetIncludeCookiesSecFlag();
+ friend class mozilla::dom::XMLHttpRequestMainThread;
+
+ // nsDocShell::OpenInitializedChannel and EarlyHintPreloader::OpenChannel
+ // needs to update the loadInfo with the correct browsingContext.
+ friend class ::nsDocShell;
+ friend class mozilla::net::EarlyHintPreloader;
+ void UpdateBrowsingContextID(uint64_t aBrowsingContextID) {
+ mBrowsingContextID = aBrowsingContextID;
+ }
+ void UpdateFrameBrowsingContextID(uint64_t aFrameBrowsingContextID) {
+ mFrameBrowsingContextID = aFrameBrowsingContextID;
+ }
+ MOZ_NEVER_INLINE void ReleaseMembers();
+
+ // if you add a member, please also update the copy constructor and consider
+ // if it should be merged from parent channel through
+ // ParentLoadInfoForwarderArgs.
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIURI> mChannelCreationOriginalURI;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+ nsCOMPtr<nsIContentSecurityPolicy> mCspToInherit;
+ nsCString mTriggeringRemoteType;
+ nsID mSandboxedNullPrincipalID;
+
+ Maybe<mozilla::dom::ClientInfo> mClientInfo;
+ UniquePtr<mozilla::dom::ClientSource> mReservedClientSource;
+ Maybe<mozilla::dom::ClientInfo> mReservedClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mInitialClientInfo;
+ Maybe<mozilla::dom::ServiceWorkerDescriptor> mController;
+ RefPtr<mozilla::dom::PerformanceStorage> mPerformanceStorage;
+
+ nsWeakPtr mLoadingContext;
+ nsWeakPtr mContextForTopLevelLoad;
+ nsSecurityFlags mSecurityFlags;
+ uint32_t mSandboxFlags;
+ uint32_t mTriggeringSandboxFlags = 0;
+ uint64_t mTriggeringWindowId = 0;
+ bool mTriggeringStorageAccess = false;
+ nsContentPolicyType mInternalContentPolicyType;
+ LoadTainting mTainting = LoadTainting::Basic;
+ bool mBlockAllMixedContent = false;
+ bool mUpgradeInsecureRequests = false;
+ bool mBrowserUpgradeInsecureRequests = false;
+ bool mBrowserDidUpgradeInsecureRequests = false;
+ bool mBrowserWouldUpgradeInsecureRequests = false;
+ bool mForceAllowDataURI = false;
+ bool mAllowInsecureRedirectToDataURI = false;
+ bool mSkipContentPolicyCheckForWebRequest = false;
+ bool mOriginalFrameSrcLoad = false;
+ bool mForceInheritPrincipalDropped = false;
+ uint64_t mInnerWindowID = 0;
+ uint64_t mBrowsingContextID = 0;
+ uint64_t mWorkerAssociatedBrowsingContextID = 0;
+ uint64_t mFrameBrowsingContextID = 0;
+ bool mInitialSecurityCheckDone = false;
+ // NB: TYPE_DOCUMENT implies !third-party.
+ bool mIsThirdPartyContext = false;
+ Maybe<bool> mIsThirdPartyContextToTopWindow;
+ bool mIsFormSubmission = false;
+ bool mSendCSPViolationEvents = true;
+ OriginAttributes mOriginAttributes;
+ RedirectHistoryArray mRedirectChainIncludingInternalRedirects;
+ RedirectHistoryArray mRedirectChain;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
+ nsTArray<uint64_t> mAncestorBrowsingContextIDs;
+ nsTArray<nsCString> mCorsUnsafeHeaders;
+ uint32_t mRequestBlockingReason = BLOCKING_REASON_NONE;
+ bool mForcePreflight = false;
+ bool mIsPreflight = false;
+ bool mLoadTriggeredFromExternal = false;
+ bool mServiceWorkerTaintingSynthesized = false;
+ bool mDocumentHasUserInteracted = false;
+ bool mAllowListFutureDocumentsCreatedFromThisRedirectChain = false;
+ bool mNeedForCheckingAntiTrackingHeuristic = false;
+ nsString mCspNonce;
+ nsString mIntegrityMetadata;
+ bool mSkipContentSniffing = false;
+ uint32_t mHttpsOnlyStatus = nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
+ bool mHstsStatus = false;
+ bool mHasValidUserGestureActivation = false;
+ bool mAllowDeprecatedSystemRequests = false;
+ bool mIsUserTriggeredSave = false;
+ bool mIsInDevToolsContext = false;
+ bool mParserCreatedScript = false;
+ nsILoadInfo::StoragePermissionState mStoragePermission =
+ nsILoadInfo::NoStoragePermission;
+ Maybe<RFPTarget> mOverriddenFingerprintingSettings;
+#ifdef DEBUG
+ // A boolean used to ensure the mOverriddenFingerprintingSettings is set
+ // before use it.
+ bool mOverriddenFingerprintingSettingsIsSet = false;
+#endif
+ bool mIsMetaRefresh = false;
+
+ // Is true if this load was triggered by processing the attributes of the
+ // browsing context container.
+ // See nsILoadInfo.isFromProcessingFrameAttributes
+ bool mIsFromProcessingFrameAttributes = false;
+
+ // See nsILoadInfo.isMediaRequest and nsILoadInfo.isMediaInitialRequest.
+ bool mIsMediaRequest = false;
+ bool mIsMediaInitialRequest = false;
+
+ // See nsILoadInfo.isFromObjectOrEmbed
+ bool mIsFromObjectOrEmbed = false;
+
+ bool mSkipCheckForBrokenURLOrZeroSized = false;
+
+ // The cross origin embedder policy that the loading need to respect.
+ // If the value is nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP, CORP checking
+ // must be performed for the loading.
+ // See https://wicg.github.io/cross-origin-embedder-policy/#corp-check.
+ nsILoadInfo::CrossOriginEmbedderPolicy mLoadingEmbedderPolicy =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+
+ bool mIsOriginTrialCoepCredentiallessEnabledForTopLevel = false;
+
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+
+ nsCOMPtr<nsIInterceptionInfo> mInterceptionInfo;
+
+ bool mHasInjectedCookieForCookieBannerHandling = false;
+ bool mWasSchemelessInput = false;
+};
+
+// This is exposed solely for testing purposes and should not be used outside of
+// LoadInfo
+already_AddRefed<nsIPrincipal> CreateTruncatedPrincipal(nsIPrincipal*);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_LoadInfo_h
diff --git a/netwerk/base/LoadTainting.h b/netwerk/base/LoadTainting.h
new file mode 100644
index 0000000000..11dc8a304f
--- /dev/null
+++ b/netwerk/base/LoadTainting.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadTainting_h
+#define mozilla_LoadTainting_h
+
+#include <stdint.h>
+
+namespace mozilla {
+
+// Define an enumeration to reflect the concept of response tainting from the
+// the fetch spec:
+//
+// https://fetch.spec.whatwg.org/#concept-request-response-tainting
+//
+// Roughly the tainting means:
+//
+// * Basic: the request resulted in a same-origin or non-http load
+// * CORS: the request resulted in a cross-origin load with CORS headers
+// * Opaque: the request resulted in a cross-origin load without CORS headers
+//
+// The enumeration is purposefully designed such that more restrictive tainting
+// corresponds to a higher integral value.
+enum class LoadTainting : uint8_t { Basic = 0, CORS = 1, Opaque = 2 };
+
+} // namespace mozilla
+
+#endif // mozilla_LoadTainting_h
diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp
new file mode 100644
index 0000000000..afb4552cca
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemoryDownloader.h"
+
+#include "mozilla/Assertions.h"
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(MemoryDownloader, nsIStreamListener, nsIRequestObserver)
+
+MemoryDownloader::MemoryDownloader(IObserver* aObserver)
+ : mObserver(aObserver), mStatus(NS_ERROR_NOT_INITIALIZED) {}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(!mData);
+ mData.reset(new FallibleTArray<uint8_t>());
+ mStatus = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ MOZ_ASSERT_IF(NS_FAILED(mStatus), NS_FAILED(aStatus));
+ MOZ_ASSERT(!mData == NS_FAILED(mStatus));
+ Data data;
+ data.swap(mData);
+ RefPtr<IObserver> observer;
+ observer.swap(mObserver);
+ observer->OnDownloadComplete(this, aRequest, aStatus, std::move(data));
+ return NS_OK;
+}
+
+nsresult MemoryDownloader::ConsumeData(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ MemoryDownloader* self = static_cast<MemoryDownloader*>(aClosure);
+ if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) {
+ // The error returned by ConsumeData isn't propagated to the
+ // return of ReadSegments, so it has to be passed as state.
+ self->mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr,
+ uint64_t aSourceOffset, uint32_t aCount) {
+ uint32_t n;
+ MOZ_ASSERT(mData);
+ nsresult rv = aInStr->ReadSegments(ConsumeData, this, aCount, &n);
+ if (NS_SUCCEEDED(mStatus) && NS_FAILED(rv)) {
+ mStatus = rv;
+ }
+ if (NS_WARN_IF(NS_FAILED(mStatus))) {
+ mData.reset(nullptr);
+ return mStatus;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/MemoryDownloader.h b/netwerk/base/MemoryDownloader.h
new file mode 100644
index 0000000000..8b5700f5bf
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_MemoryDownloader_h__
+#define mozilla_net_MemoryDownloader_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsTArray.h"
+
+/**
+ * mozilla::net::MemoryDownloader
+ *
+ * This class is similar to nsIDownloader, but stores the downloaded
+ * stream in memory instead of a file. Ownership of the temporary
+ * memory is transferred to the observer when download is complete;
+ * there is no need to retain a reference to the downloader.
+ */
+
+namespace mozilla {
+namespace net {
+
+class MemoryDownloader final : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ using Data = mozilla::UniquePtr<FallibleTArray<uint8_t>>;
+
+ class IObserver : public nsISupports {
+ public:
+ // Note: aData may be null if (and only if) aStatus indicates failure.
+ virtual void OnDownloadComplete(MemoryDownloader* aDownloader,
+ nsIRequest* aRequest, nsresult aStatus,
+ Data aData) = 0;
+ };
+
+ explicit MemoryDownloader(IObserver* aObserver);
+
+ private:
+ virtual ~MemoryDownloader() = default;
+
+ static nsresult ConsumeData(nsIInputStream* in, void* closure,
+ const char* fromRawSegment, uint32_t toOffset,
+ uint32_t count, uint32_t* writeCount);
+
+ RefPtr<IObserver> mObserver;
+ Data mData;
+ nsresult mStatus;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_MemoryDownloader_h__
diff --git a/netwerk/base/NetUtil.sys.mjs b/netwerk/base/NetUtil.sys.mjs
new file mode 100644
index 0000000000..679c9979a7
--- /dev/null
+++ b/netwerk/base/NetUtil.sys.mjs
@@ -0,0 +1,453 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
+ * vim: sw=4 ts=4 sts=4 et filetype=javascript
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Necko utilities
+ */
+
+// //////////////////////////////////////////////////////////////////////////////
+// // Constants
+
+const PR_UINT32_MAX = 0xffffffff;
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+// //////////////////////////////////////////////////////////////////////////////
+// // NetUtil Object
+
+export var NetUtil = {
+ /**
+ * Function to perform simple async copying from aSource (an input stream)
+ * to aSink (an output stream). The copy will happen on some background
+ * thread. Both streams will be closed when the copy completes.
+ *
+ * @param aSource
+ * The input stream to read from
+ * @param aSink
+ * The output stream to write to
+ * @param aCallback [optional]
+ * A function that will be called at copy completion with a single
+ * argument: the nsresult status code for the copy operation.
+ *
+ * @return An nsIRequest representing the copy operation (for example, this
+ * can be used to cancel the copying). The consumer can ignore the
+ * return value if desired.
+ */
+ asyncCopy: function NetUtil_asyncCopy(aSource, aSink, aCallback = null) {
+ if (!aSource || !aSink) {
+ let exception = new Components.Exception(
+ "Must have a source and a sink",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // make a stream copier
+ var copier = Cc[
+ "@mozilla.org/network/async-stream-copier;1"
+ ].createInstance(Ci.nsIAsyncStreamCopier2);
+ copier.init(
+ aSource,
+ aSink,
+ null /* Default event target */,
+ 0 /* Default length */,
+ true,
+ true /* Auto-close */
+ );
+
+ var observer;
+ if (aCallback) {
+ observer = {
+ onStartRequest(aRequest) {},
+ onStopRequest(aRequest, aStatusCode) {
+ aCallback(aStatusCode);
+ },
+ };
+ } else {
+ observer = null;
+ }
+
+ // start the copying
+ copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
+ return copier;
+ },
+
+ /**
+ * Asynchronously opens a source and fetches the response. While the fetch
+ * is asynchronous, I/O may happen on the main thread. When reading from
+ * a local file, prefer using IOUtils methods instead.
+ *
+ * @param aSource
+ * This argument can be one of the following:
+ * - An options object that will be passed to NetUtil.newChannel.
+ * - An existing nsIChannel.
+ * - An existing nsIInputStream.
+ * Using an nsIURI, nsIFile, or string spec directly is deprecated.
+ * @param aCallback
+ * The callback function that will be notified upon completion. It
+ * will get these arguments:
+ * 1) An nsIInputStream containing the data from aSource, if any.
+ * 2) The status code from opening the source.
+ * 3) Reference to the nsIRequest.
+ */
+ asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) {
+ if (!aSource || !aCallback) {
+ let exception = new Components.Exception(
+ "Must have a source and a callback",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // Create a pipe that will create our output stream that we can use once
+ // we have gotten all the data.
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, PR_UINT32_MAX, null);
+
+ // Create a listener that will give data to the pipe's output stream.
+ let listener = Cc[
+ "@mozilla.org/network/simple-stream-listener;1"
+ ].createInstance(Ci.nsISimpleStreamListener);
+ listener.init(pipe.outputStream, {
+ onStartRequest(aRequest) {},
+ onStopRequest(aRequest, aStatusCode) {
+ pipe.outputStream.close();
+ aCallback(pipe.inputStream, aStatusCode, aRequest);
+ },
+ });
+
+ // Input streams are handled slightly differently from everything else.
+ if (aSource instanceof Ci.nsIInputStream) {
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
+ Ci.nsIInputStreamPump
+ );
+ pump.init(aSource, 0, 0, true);
+ pump.asyncRead(listener, null);
+ return;
+ }
+
+ let channel = aSource;
+ if (!(channel instanceof Ci.nsIChannel)) {
+ channel = this.newChannel(aSource);
+ }
+
+ try {
+ channel.asyncOpen(listener);
+ } catch (e) {
+ let exception = new Components.Exception(
+ "Failed to open input source '" + channel.originalURI.spec + "'",
+ e.result,
+ Components.stack.caller,
+ aSource,
+ e
+ );
+ throw exception;
+ }
+ },
+
+ /**
+ * Constructs a new URI for the given spec, character set, and base URI, or
+ * an nsIFile.
+ *
+ * @param aTarget
+ * The string spec for the desired URI or an nsIFile.
+ * @param aOriginCharset [optional]
+ * The character set for the URI. Only used if aTarget is not an
+ * nsIFile.
+ * @param aBaseURI [optional]
+ * The base URI for the spec. Only used if aTarget is not an
+ * nsIFile.
+ *
+ * @return an nsIURI object.
+ */
+ newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) {
+ if (!aTarget) {
+ let exception = new Components.Exception(
+ "Must have a non-null string spec or nsIFile object",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aTarget instanceof Ci.nsIFile) {
+ return Services.io.newFileURI(aTarget);
+ }
+
+ return Services.io.newURI(aTarget, aOriginCharset, aBaseURI);
+ },
+
+ /**
+ * Constructs a new channel for the given source.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ *
+ * @param aWhatToLoad
+ * This argument used to be a string spec for the desired URI, an
+ * nsIURI, or an nsIFile. Now it should be an options object with
+ * the following properties:
+ * {
+ * uri:
+ * The full URI spec string, nsIURI or nsIFile to create the
+ * channel for.
+ * Note that this cannot be an nsIFile if you have to specify a
+ * non-default charset or base URI. Call NetUtil.newURI first if
+ * you need to construct an URI using those options.
+ * loadingNode:
+ * loadingPrincipal:
+ * triggeringPrincipal:
+ * securityFlags:
+ * contentPolicyType:
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * loadUsingSystemPrincipal:
+ * Set this to true to use the system principal as
+ * loadingPrincipal. This must be omitted if loadingPrincipal or
+ * loadingNode are present.
+ * This should be used with care as it skips security checks.
+ * }
+ * @return an nsIChannel object.
+ */
+ newChannel: function NetUtil_newChannel(aWhatToLoad) {
+ // Make sure the API is called using only the options object.
+ if (typeof aWhatToLoad != "object" || arguments.length != 1) {
+ throw new Components.Exception(
+ "newChannel requires a single object argument",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ let {
+ uri,
+ loadingNode,
+ loadingPrincipal,
+ loadUsingSystemPrincipal,
+ triggeringPrincipal,
+ securityFlags,
+ contentPolicyType,
+ } = aWhatToLoad;
+
+ if (!uri) {
+ throw new Components.Exception(
+ "newChannel requires the 'uri' property on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
+ uri = this.newURI(uri);
+ }
+
+ if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires at least one of the 'loadingNode'," +
+ " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
+ " properties on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (loadUsingSystemPrincipal === true) {
+ if (loadingNode || loadingPrincipal) {
+ throw new Components.Exception(
+ "newChannel does not accept 'loadUsingSystemPrincipal'" +
+ " if the 'loadingNode' or 'loadingPrincipal' properties" +
+ " are present on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ loadingPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ } else if (loadUsingSystemPrincipal !== undefined) {
+ throw new Components.Exception(
+ "newChannel requires the 'loadUsingSystemPrincipal'" +
+ " property on the options object to be 'true' or 'undefined'.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (securityFlags === undefined) {
+ if (!loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires the 'securityFlags' property on" +
+ " the options object unless loading from system principal.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+ }
+
+ if (contentPolicyType === undefined) {
+ if (!loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires the 'contentPolicyType' property on" +
+ " the options object unless loading from system principal.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
+ }
+
+ let channel = Services.io.newChannelFromURI(
+ uri,
+ loadingNode || null,
+ loadingPrincipal || null,
+ triggeringPrincipal || null,
+ securityFlags,
+ contentPolicyType
+ );
+ if (loadUsingSystemPrincipal) {
+ channel.loadInfo.allowDeprecatedSystemRequests = true;
+ }
+ return channel;
+ },
+
+ newWebTransport: function NetUtil_newWebTransport() {
+ return Services.io.newWebTransport();
+ },
+
+ /**
+ * Reads aCount bytes from aInputStream into a string.
+ *
+ * @param aInputStream
+ * The input stream to read from.
+ * @param aCount
+ * The number of bytes to read from the stream.
+ * @param aOptions [optional]
+ * charset
+ * The character encoding of stream data.
+ * replacement
+ * The character to replace unknown byte sequences.
+ * If unset, it causes an exceptions to be thrown.
+ *
+ * @return the bytes from the input stream in string form.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
+ * block the calling thread (non-blocking mode only).
+ * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
+ * aCount amount of data.
+ * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
+ */
+ readInputStreamToString: function NetUtil_readInputStreamToString(
+ aInputStream,
+ aCount,
+ aOptions
+ ) {
+ if (!(aInputStream instanceof Ci.nsIInputStream)) {
+ let exception = new Components.Exception(
+ "First argument should be an nsIInputStream",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (!aCount) {
+ let exception = new Components.Exception(
+ "Non-zero amount of bytes must be specified",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aOptions && "charset" in aOptions) {
+ let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+ Ci.nsIConverterInputStream
+ );
+ try {
+ // When replacement is set, the character that is unknown sequence
+ // replaces with aOptions.replacement character.
+ if (!("replacement" in aOptions)) {
+ // aOptions.replacement isn't set.
+ // If input stream has unknown sequences for aOptions.charset,
+ // throw NS_ERROR_ILLEGAL_INPUT.
+ aOptions.replacement = 0;
+ }
+
+ cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement);
+ let str = {};
+ cis.readString(-1, str);
+ cis.close();
+ return str.value;
+ } catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(
+ e.message,
+ e.result,
+ Components.stack.caller,
+ e.data
+ );
+ }
+ }
+
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sis.init(aInputStream);
+ try {
+ return sis.readBytes(aCount);
+ } catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(
+ e.message,
+ e.result,
+ Components.stack.caller,
+ e.data
+ );
+ }
+ },
+
+ /**
+ * Reads aCount bytes from aInputStream into a string.
+ *
+ * @param {nsIInputStream} aInputStream
+ * The input stream to read from.
+ * @param {integer} [aCount = aInputStream.available()]
+ * The number of bytes to read from the stream.
+ *
+ * @return the bytes from the input stream in string form.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
+ * block the calling thread (non-blocking mode only).
+ * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
+ * aCount amount of data.
+ */
+ readInputStream(aInputStream, aCount) {
+ if (!(aInputStream instanceof Ci.nsIInputStream)) {
+ let exception = new Components.Exception(
+ "First argument should be an nsIInputStream",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (!aCount) {
+ aCount = aInputStream.available();
+ }
+
+ let stream = new BinaryInputStream(aInputStream);
+ let result = new ArrayBuffer(aCount);
+ stream.readArrayBuffer(result.byteLength, result);
+ return result;
+ },
+};
diff --git a/netwerk/base/NetworkConnectivityService.cpp b/netwerk/base/NetworkConnectivityService.cpp
new file mode 100644
index 0000000000..1e126742ce
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.cpp
@@ -0,0 +1,554 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DNSUtils.h"
+#include "NetworkConnectivityService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIOService.h"
+#include "nsICancelable.h"
+#include "xpcpublic.h"
+#include "nsSocketTransport2.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/StaticPrefs_network.h"
+
+static mozilla::LazyLogModule gNCSLog("NetworkConnectivityService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gNCSLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NetworkConnectivityService, nsIDNSListener, nsIObserver,
+ nsINetworkConnectivityService, nsIStreamListener)
+
+static StaticRefPtr<NetworkConnectivityService> gConnService;
+
+NetworkConnectivityService::NetworkConnectivityService()
+ : mDNSv4(UNKNOWN),
+ mDNSv6(UNKNOWN),
+ mIPv4(UNKNOWN),
+ mIPv6(UNKNOWN),
+ mNAT64(UNKNOWN),
+ mLock("nat64prefixes") {}
+
+// static
+already_AddRefed<NetworkConnectivityService>
+NetworkConnectivityService::GetSingleton() {
+ if (gConnService) {
+ return do_AddRef(gConnService);
+ }
+
+ RefPtr<NetworkConnectivityService> service = new NetworkConnectivityService();
+ service->Init();
+
+ gConnService = std::move(service);
+ ClearOnShutdown(&gConnService);
+ return do_AddRef(gConnService);
+}
+
+nsresult NetworkConnectivityService::Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ observerService->AddObserver(this, "network:captive-portal-connectivity",
+ false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetDNSv4(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mDNSv4;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetDNSv6(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mDNSv6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetIPv4(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mIPv4;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetIPv6(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mIPv6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetNAT64(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mNAT64;
+ return NS_OK;
+}
+
+already_AddRefed<AddrInfo> NetworkConnectivityService::MapNAT64IPs(
+ AddrInfo* aNewRRSet) {
+ // Add prefixes only if there are no IPv6 addresses.
+ // Expect that if aNewRRSet has IPv6 addresses, they must come
+ // before IPv4 addresses.
+ if (aNewRRSet->Addresses().IsEmpty() ||
+ aNewRRSet->Addresses()[0].raw.family == PR_AF_INET6) {
+ return do_AddRef(aNewRRSet);
+ }
+
+ // Currently we only add prefixes to the first IP's clones.
+ uint32_t ip = aNewRRSet->Addresses()[0].inet.ip;
+ nsTArray<NetAddr> addresses = aNewRRSet->Addresses().Clone();
+
+ {
+ MutexAutoLock lock(mLock);
+ for (const auto& prefix : mNAT64Prefixes) {
+ NetAddr addr = NetAddr(prefix);
+
+ // Copy the IPv4 address to the end
+ addr.inet6.ip.u32[3] = ip;
+
+ // If we have both IPv4 and NAT64, we be could insourcing NAT64
+ // to avoid double NAT and improve performance. However, this
+ // breaks WebRTC, so we push it to the back.
+ addresses.AppendElement(addr);
+ }
+ }
+
+ auto builder = aNewRRSet->Build();
+ builder.SetAddresses(std::move(addresses));
+ return builder.Finish();
+}
+
+// Returns true if a prefix was read and saved to the argument
+static inline bool NAT64PrefixFromPref(NetAddr* prefix) {
+ nsAutoCString nat64PrefixPref;
+
+ nsresult rv = Preferences::GetCString(
+ "network.connectivity-service.nat64-prefix", nat64PrefixPref);
+ return !(NS_FAILED(rv) || nat64PrefixPref.IsEmpty() ||
+ NS_FAILED(prefix->InitFromString(nat64PrefixPref)) ||
+ prefix->raw.family != PR_AF_INET6);
+}
+
+static inline bool NAT64PrefixCompare(const NetAddr& prefix1,
+ const NetAddr& prefix2) {
+ // Compare the first 96 bits as 64 + 32
+ return prefix1.inet6.ip.u64[0] == prefix2.inet6.ip.u64[0] &&
+ prefix1.inet6.ip.u32[2] == prefix2.inet6.ip.u32[2];
+}
+
+void NetworkConnectivityService::PerformChecks() {
+ mDNSv4 = UNKNOWN;
+ mDNSv6 = UNKNOWN;
+
+ mIPv4 = UNKNOWN;
+ mIPv6 = UNKNOWN;
+
+ mNAT64 = UNKNOWN;
+
+ {
+ MutexAutoLock lock(mLock);
+ mNAT64Prefixes.Clear();
+
+ // NAT64 checks might be disabled.
+ // Since We can't guarantee a DNS response, we should set up
+ // NAT64 manually now if needed.
+
+ NetAddr priorityPrefix{};
+ bool havePrefix = NAT64PrefixFromPref(&priorityPrefix);
+ if (havePrefix) {
+ mNAT64Prefixes.AppendElement(priorityPrefix);
+ mNAT64 = OK;
+ }
+ }
+
+ RecheckDNS();
+ RecheckIPConnectivity();
+}
+
+static inline void NotifyObservers(const char* aTopic) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->NotifyObservers(nullptr, aTopic, nullptr);
+}
+
+void NetworkConnectivityService::SaveNAT64Prefixes(nsIDNSRecord* aRecord) {
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ MutexAutoLock lock(mLock);
+ mNAT64Prefixes.Clear();
+
+ NetAddr priorityPrefix{};
+ bool havePrefix = NAT64PrefixFromPref(&priorityPrefix);
+ if (havePrefix) {
+ mNAT64 = OK;
+ mNAT64Prefixes.AppendElement(priorityPrefix);
+ }
+
+ if (!rec) {
+ if (!havePrefix) {
+ mNAT64 = NOT_AVAILABLE;
+ }
+ return;
+ }
+
+ mNAT64 = UNKNOWN;
+ NetAddr addr{};
+
+ // use port 80 as dummy value for NetAddr
+ while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
+ if (addr.raw.family != AF_INET6 || addr.IsIPAddrV4Mapped()) {
+ // These are not the kind of addresses we are looking for.
+ continue;
+ }
+
+ // RFC 7050 does not require the embedded IPv4 to be
+ // at the end of IPv6. In practice, and as we assume,
+ // it is always at the end.
+ // The embedded IP must be 192.0.0.170 or 192.0.0.171
+
+ // Clear the last bit to compare with the next one.
+ addr.inet6.ip.u8[15] &= ~(uint32_t)1;
+ if ((addr.inet6.ip.u8[12] != 192) || (addr.inet6.ip.u8[13] != 0) ||
+ (addr.inet6.ip.u8[14] != 0) || (addr.inet6.ip.u8[15] != 170)) {
+ continue;
+ }
+
+ mNAT64Prefixes.AppendElement(addr);
+ }
+
+ size_t length = mNAT64Prefixes.Length();
+ if (length == 0) {
+ mNAT64 = NOT_AVAILABLE;
+ return;
+ }
+
+ // Remove duplicates. Typically a DNS64 resolver sends every
+ // prefix twice with address with different last bits. We want
+ // a list of unique prefixes while reordering is not allowed.
+ // We must not handle the case with an element in-between
+ // two identical ones, which is never the case for a properly
+ // configured DNS64 resolver.
+
+ NetAddr prev = mNAT64Prefixes[0];
+
+ for (size_t i = 1; i < length; i++) {
+ if (NAT64PrefixCompare(prev, mNAT64Prefixes[i])) {
+ mNAT64Prefixes.RemoveElementAt(i);
+ i--;
+ length--;
+ } else {
+ prev = mNAT64Prefixes[i];
+ }
+ }
+
+ // The prioritized address might also appear in the record we received.
+
+ if (havePrefix) {
+ for (size_t i = 1; i < length; i++) {
+ if (NAT64PrefixCompare(priorityPrefix, mNAT64Prefixes[i])) {
+ mNAT64Prefixes.RemoveElementAt(i);
+ // It wouldn't appear more than once.
+ break;
+ }
+ }
+ }
+
+ mNAT64 = OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord,
+ nsresult aStatus) {
+ ConnectivityState state = NS_SUCCEEDED(aStatus) ? OK : NOT_AVAILABLE;
+
+ if (aRequest == mDNSv4Request) {
+ mDNSv4 = state;
+ mDNSv4Request = nullptr;
+ } else if (aRequest == mDNSv6Request) {
+ mDNSv6 = state;
+ mDNSv6Request = nullptr;
+ } else if (aRequest == mNAT64Request) {
+ mNAT64Request = nullptr;
+ SaveNAT64Prefixes(aRecord);
+ }
+
+ if (!mDNSv4Request && !mDNSv6Request && !mNAT64Request) {
+ NotifyObservers("network:connectivity-service:dns-checks-complete");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::RecheckDNS() {
+ bool enabled =
+ Preferences::GetBool("network.connectivity-service.enabled", false);
+ if (!enabled) {
+ return NS_OK;
+ }
+
+ if (nsIOService::UseSocketProcess()) {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent) {
+ Unused << parent->SendRecheckDNS();
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ OriginAttributes attrs;
+ nsAutoCString host;
+ Preferences::GetCString("network.connectivity-service.DNSv4.domain", host);
+
+ rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DISABLE_IPV6 |
+ nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
+ nullptr, this, NS_GetCurrentThread(), attrs,
+ getter_AddRefs(mDNSv4Request));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Preferences::GetCString("network.connectivity-service.DNSv6.domain", host);
+ rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DISABLE_IPV4 |
+ nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
+ nullptr, this, NS_GetCurrentThread(), attrs,
+ getter_AddRefs(mDNSv6Request));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (StaticPrefs::network_connectivity_service_nat64_check()) {
+ rv = dns->AsyncResolveNative("ipv4only.arpa"_ns,
+ nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DISABLE_IPV4 |
+ nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
+ nullptr, this, NS_GetCurrentThread(), attrs,
+ getter_AddRefs(mNAT64Request));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "network:captive-portal-connectivity")) {
+ // Captive portal is cleared, so we redo the checks.
+ PerformChecks();
+ } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ if (mDNSv4Request) {
+ mDNSv4Request->Cancel(NS_ERROR_ABORT);
+ mDNSv4Request = nullptr;
+ }
+ if (mDNSv6Request) {
+ mDNSv6Request->Cancel(NS_ERROR_ABORT);
+ mDNSv6Request = nullptr;
+ }
+ if (mNAT64Request) {
+ mNAT64Request->Cancel(NS_ERROR_ABORT);
+ mNAT64Request = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this,
+ "network:captive-portal-connectivity");
+ observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ } else if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) &&
+ !NS_LITERAL_STRING_FROM_CSTRING(NS_NETWORK_LINK_DATA_UNKNOWN)
+ .Equals(aData)) {
+ PerformChecks();
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIChannel> NetworkConnectivityService::SetupIPCheckChannel(
+ bool ipv4) {
+ nsresult rv;
+ nsAutoCString url;
+
+ if (ipv4) {
+ rv = Preferences::GetCString("network.connectivity-service.IPv4.url", url);
+ } else {
+ rv = Preferences::GetCString("network.connectivity-service.IPv6.url", url);
+ }
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), url);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (XRE_IsSocketProcess()) {
+ rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ channel->SetLoadFlags(
+ nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache
+ nsIRequest::INHIBIT_CACHING | // don't write the response to cache
+ nsIRequest::LOAD_ANONYMOUS);
+ } else {
+ rv = NS_NewChannel(
+ getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // aPerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr,
+ nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache
+ nsIRequest::INHIBIT_CACHING | // don't write the response to cache
+ nsIRequest::LOAD_ANONYMOUS); // prevent privacy leaks
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ {
+ // Prevent HTTPS-Only Mode from upgrading the OCSP request.
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
+ httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
+ loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
+
+ // allow deprecated HTTP request from SystemPrincipal
+ loadInfo->SetAllowDeprecatedSystemRequests(true);
+ }
+ }
+
+ rv = channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(internalChan, nullptr);
+
+ if (ipv4) {
+ internalChan->SetIPv6Disabled();
+ } else {
+ internalChan->SetIPv4Disabled();
+ }
+
+ return channel.forget();
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::RecheckIPConnectivity() {
+ bool enabled =
+ Preferences::GetBool("network.connectivity-service.enabled", false);
+ if (!enabled) {
+ return NS_OK;
+ }
+
+ if (nsIOService::UseSocketProcess()) {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent) {
+ Unused << parent->SendRecheckIPConnectivity();
+ }
+ }
+
+ if (xpc::AreNonLocalConnectionsDisabled() &&
+ !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
+ return NS_OK;
+ }
+
+ if (mIPv4Channel) {
+ mIPv4Channel->Cancel(NS_ERROR_ABORT);
+ mIPv4Channel = nullptr;
+ }
+ if (mIPv6Channel) {
+ mIPv6Channel->Cancel(NS_ERROR_ABORT);
+ mIPv6Channel = nullptr;
+ }
+
+ nsresult rv;
+ mHasNetworkId = false;
+ mCheckedNetworkId = false;
+ mIPv4Channel = SetupIPCheckChannel(/* ipv4 = */ true);
+ if (mIPv4Channel) {
+ rv = mIPv4Channel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mIPv6Channel = SetupIPCheckChannel(/* ipv4 = */ false);
+ if (mIPv6Channel) {
+ rv = mIPv6Channel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnStartRequest(nsIRequest* aRequest) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ if (aStatusCode == NS_ERROR_ABORT) {
+ return NS_OK;
+ }
+
+ ConnectivityState status = NS_FAILED(aStatusCode) ? NOT_AVAILABLE : OK;
+
+ if (aRequest == mIPv4Channel) {
+ mIPv4 = status;
+ mIPv4Channel = nullptr;
+
+ if (mIPv4 == nsINetworkConnectivityService::OK) {
+ Telemetry::AccumulateCategorical(
+ mHasNetworkId ? Telemetry::LABELS_NETWORK_ID_ONLINE::present
+ : Telemetry::LABELS_NETWORK_ID_ONLINE::absent);
+ LOG(("mHasNetworkId : %d\n", mHasNetworkId));
+ }
+ } else if (aRequest == mIPv6Channel) {
+ mIPv6 = status;
+ mIPv6Channel = nullptr;
+ }
+
+ if (!mIPv6Channel && !mIPv4Channel) {
+ NotifyObservers("network:connectivity-service:ip-checks-complete");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ nsAutoCString data;
+
+ // We perform this check here, instead of doing it in OnStopRequest in case
+ // a network down event occurs after the data has arrived but before we fire
+ // OnStopRequest. That would cause us to report a missing networkID, even
+ // though it was not empty while receiving data.
+ if (aRequest == mIPv4Channel && !mCheckedNetworkId) {
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
+ nsAutoCString networkId;
+ if (nls) {
+ nls->GetNetworkID(networkId);
+ }
+ mHasNetworkId = !networkId.IsEmpty();
+ mCheckedNetworkId = true;
+ }
+
+ Unused << NS_ReadInputStreamToString(aInputStream, data, aCount);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkConnectivityService.h b/netwerk/base/NetworkConnectivityService.h
new file mode 100644
index 0000000000..6315fb192b
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.h
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkConnectivityService_h_
+#define NetworkConnectivityService_h_
+
+#include "nsINetworkConnectivityService.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIDNSListener.h"
+#include "nsIStreamListener.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class NetworkConnectivityService : public nsINetworkConnectivityService,
+ public nsIObserver,
+ public nsIDNSListener,
+ public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKCONNECTIVITYSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ already_AddRefed<AddrInfo> MapNAT64IPs(AddrInfo* aNewRRSet);
+
+ static already_AddRefed<NetworkConnectivityService> GetSingleton();
+
+ private:
+ NetworkConnectivityService();
+ virtual ~NetworkConnectivityService() = default;
+
+ nsresult Init();
+ // Calls all the check methods
+ void PerformChecks();
+
+ void SaveNAT64Prefixes(nsIDNSRecord* aRecord);
+
+ already_AddRefed<nsIChannel> SetupIPCheckChannel(bool ipv4);
+
+ // Will be set to OK if the DNS request returned in IP of this type,
+ // NOT_AVAILABLE if that type of resolution is not available
+ // UNKNOWN if the check wasn't performed
+ Atomic<ConnectivityState, Relaxed> mDNSv4;
+ Atomic<ConnectivityState, Relaxed> mDNSv6;
+
+ Atomic<ConnectivityState, Relaxed> mIPv4;
+ Atomic<ConnectivityState, Relaxed> mIPv6;
+
+ Atomic<ConnectivityState, Relaxed> mNAT64;
+
+ nsTArray<NetAddr> mNAT64Prefixes;
+
+ nsCOMPtr<nsICancelable> mDNSv4Request;
+ nsCOMPtr<nsICancelable> mDNSv6Request;
+ nsCOMPtr<nsICancelable> mNAT64Request;
+
+ nsCOMPtr<nsIChannel> mIPv4Channel;
+ nsCOMPtr<nsIChannel> mIPv6Channel;
+
+ bool mCheckedNetworkId = false;
+ bool mHasNetworkId = false;
+
+ Mutex mLock MOZ_UNANNOTATED;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkConnectivityService_h_
diff --git a/netwerk/base/NetworkDataCountLayer.cpp b/netwerk/base/NetworkDataCountLayer.cpp
new file mode 100644
index 0000000000..bee8bd1ee5
--- /dev/null
+++ b/netwerk/base/NetworkDataCountLayer.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetworkDataCountLayer.h"
+#include "nsSocketTransportService2.h"
+#include "prmem.h"
+#include "prio.h"
+
+namespace mozilla {
+namespace net {
+
+static PRDescIdentity sNetworkDataCountLayerIdentity;
+static PRIOMethods sNetworkDataCountLayerMethods;
+static PRIOMethods* sNetworkDataCountLayerMethodsPtr = nullptr;
+
+class NetworkDataCountSecret {
+ public:
+ NetworkDataCountSecret() = default;
+
+ uint64_t mSentBytes = 0;
+ uint64_t mReceivedBytes = 0;
+};
+
+static PRInt32 NetworkDataCountSend(PRFileDesc* fd, const void* buf,
+ PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(fd->secret);
+
+ PRInt32 rv =
+ (fd->lower->methods->send)(fd->lower, buf, amount, flags, timeout);
+ if (rv > 0) {
+ secret->mSentBytes += rv;
+ }
+ return rv;
+}
+
+static PRInt32 NetworkDataCountWrite(PRFileDesc* fd, const void* buf,
+ PRInt32 amount) {
+ return NetworkDataCountSend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRInt32 NetworkDataCountRecv(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(fd->secret);
+
+ PRInt32 rv =
+ (fd->lower->methods->recv)(fd->lower, buf, amount, flags, timeout);
+ if (rv > 0) {
+ secret->mReceivedBytes += rv;
+ }
+ return rv;
+}
+
+static PRInt32 NetworkDataCountRead(PRFileDesc* fd, void* buf, PRInt32 amount) {
+ return NetworkDataCountRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRStatus NetworkDataCountClose(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+
+ MOZ_RELEASE_ASSERT(layer && layer->identity == sNetworkDataCountLayerIdentity,
+ "NetworkDataCount Layer not on top of stack");
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(layer->secret);
+ layer->secret = nullptr;
+ layer->dtor(layer);
+ delete secret;
+ return fd->methods->close(fd);
+}
+
+nsresult AttachNetworkDataCountLayer(PRFileDesc* fd) {
+ if (!sNetworkDataCountLayerMethodsPtr) {
+ sNetworkDataCountLayerIdentity =
+ PR_GetUniqueIdentity("NetworkDataCount Layer");
+ sNetworkDataCountLayerMethods = *PR_GetDefaultIOMethods();
+ sNetworkDataCountLayerMethods.send = NetworkDataCountSend;
+ sNetworkDataCountLayerMethods.write = NetworkDataCountWrite;
+ sNetworkDataCountLayerMethods.recv = NetworkDataCountRecv;
+ sNetworkDataCountLayerMethods.read = NetworkDataCountRead;
+ sNetworkDataCountLayerMethods.close = NetworkDataCountClose;
+ sNetworkDataCountLayerMethodsPtr = &sNetworkDataCountLayerMethods;
+ }
+
+ PRFileDesc* layer = PR_CreateIOLayerStub(sNetworkDataCountLayerIdentity,
+ sNetworkDataCountLayerMethodsPtr);
+
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NetworkDataCountSecret* secret = new NetworkDataCountSecret();
+
+ layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
+
+ PRStatus status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ delete secret;
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes) {
+ PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity);
+ MOZ_RELEASE_ASSERT(ndcFd);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(ndcFd->secret);
+ sentBytes = secret->mSentBytes;
+}
+
+void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes) {
+ PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity);
+ MOZ_RELEASE_ASSERT(ndcFd);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(ndcFd->secret);
+ receivedBytes = secret->mReceivedBytes;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkDataCountLayer.h b/netwerk/base/NetworkDataCountLayer.h
new file mode 100644
index 0000000000..a5a0d8f0dc
--- /dev/null
+++ b/netwerk/base/NetworkDataCountLayer.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkDataCountLayer_h__
+#define NetworkDataCountLayer_h__
+
+#include "prerror.h"
+#include "prio.h"
+#include "ErrorList.h"
+
+namespace mozilla {
+namespace net {
+
+nsresult AttachNetworkDataCountLayer(PRFileDesc* fd);
+
+// Get amount of sent bytes.
+void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes);
+
+// Get amount of received bytes.
+void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkDataCountLayer_h__
diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h
new file mode 100644
index 0000000000..218f31cb0c
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceImpl.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsTHashMap.h"
+
+namespace mozilla {
+namespace net {
+
+using AddrMapType = nsTHashMap<nsCStringHashKey, nsCString>;
+
+nsresult DoListAddresses(AddrMapType& aAddrMap);
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp
new file mode 100644
index 0000000000..a99d6d88f5
--- /dev/null
+++ b/netwerk/base/PollableEvent.cpp
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+#include "PollableEvent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/DNS.h"
+#include "prerror.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include "prnetdb.h"
+
+#ifdef XP_WIN
+# include "ShutdownLayer.h"
+#else
+# include <fcntl.h>
+# define USEPIPE 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifndef USEPIPE
+static PRDescIdentity sPollableEventLayerIdentity;
+static PRIOMethods sPollableEventLayerMethods;
+static PRIOMethods* sPollableEventLayerMethodsPtr = nullptr;
+
+static void LazyInitSocket() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (sPollableEventLayerMethodsPtr) {
+ return;
+ }
+ sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer");
+ sPollableEventLayerMethods = *PR_GetDefaultIOMethods();
+ sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods;
+}
+
+static bool NewTCPSocketPair(PRFileDesc* fd[], bool aSetRecvBuff) {
+ // this is a replacement for PR_NewTCPSocketPair that manually
+ // sets the recv buffer to 64K. A windows bug (1248358)
+ // can result in using an incompatible rwin and window
+ // scale option on localhost pipes if not set before connect.
+
+ SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n",
+ aSetRecvBuff ? "with" : "without"));
+
+ PRFileDesc* listener = nullptr;
+ PRFileDesc* writer = nullptr;
+ PRFileDesc* reader = nullptr;
+ PRSocketOptionData recvBufferOpt;
+ recvBufferOpt.option = PR_SockOpt_RecvBufferSize;
+ recvBufferOpt.value.recv_buffer_size = 65535;
+
+ PRSocketOptionData nodelayOpt;
+ nodelayOpt.option = PR_SockOpt_NoDelay;
+ nodelayOpt.value.no_delay = true;
+
+ PRSocketOptionData noblockOpt;
+ noblockOpt.option = PR_SockOpt_Nonblocking;
+ noblockOpt.value.non_blocking = true;
+
+ listener = PR_OpenTCPSocket(PR_AF_INET);
+ if (!listener) {
+ goto failed;
+ }
+
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(listener, &recvBufferOpt);
+ }
+ PR_SetSocketOption(listener, &nodelayOpt);
+
+ PRNetAddr listenAddr;
+ memset(&listenAddr, 0, sizeof(listenAddr));
+ if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) ||
+ (PR_Bind(listener, &listenAddr) == PR_FAILURE) ||
+ (PR_GetSockName(listener, &listenAddr) ==
+ PR_FAILURE) || // learn the dynamic port
+ (PR_Listen(listener, 5) == PR_FAILURE)) {
+ goto failed;
+ }
+
+ writer = PR_OpenTCPSocket(PR_AF_INET);
+ if (!writer) {
+ goto failed;
+ }
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(writer, &recvBufferOpt);
+ }
+ PR_SetSocketOption(writer, &nodelayOpt);
+ PR_SetSocketOption(writer, &noblockOpt);
+ PRNetAddr writerAddr;
+ if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port),
+ &writerAddr) == PR_FAILURE) {
+ goto failed;
+ }
+
+ if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
+ if ((PR_GetError() != PR_IN_PROGRESS_ERROR) ||
+ (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) {
+ goto failed;
+ }
+ }
+ PR_SetFDInheritable(writer, false);
+
+ reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200));
+ if (!reader) {
+ goto failed;
+ }
+ PR_SetFDInheritable(reader, false);
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(reader, &recvBufferOpt);
+ }
+ PR_SetSocketOption(reader, &nodelayOpt);
+ PR_SetSocketOption(reader, &noblockOpt);
+ PR_Close(listener);
+
+ fd[0] = reader;
+ fd[1] = writer;
+ return true;
+
+failed:
+ if (listener) {
+ PR_Close(listener);
+ }
+ if (reader) {
+ PR_Close(reader);
+ }
+ if (writer) {
+ PR_Close(writer);
+ }
+ return false;
+}
+
+#endif
+
+PollableEvent::PollableEvent()
+
+{
+ MOZ_COUNT_CTOR(PollableEvent);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // create pair of prfiledesc that can be used as a poll()ble
+ // signal. on windows use a localhost socket pair, and on
+ // unix use a pipe.
+#ifdef USEPIPE
+ SOCKET_LOG(("PollableEvent() using pipe\n"));
+ if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
+ // make the pipe non blocking. NSPR asserts at
+ // trying to use SockOpt here
+ PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
+ int flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ fd = PR_FileDesc2NativeHandle(mWriteFD);
+ flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ } else {
+ mReadFD = nullptr;
+ mWriteFD = nullptr;
+ SOCKET_LOG(("PollableEvent() pipe failed\n"));
+ }
+#else
+ SOCKET_LOG(("PollableEvent() using socket pair\n"));
+ PRFileDesc* fd[2];
+ LazyInitSocket();
+
+ // Try with a increased recv buffer first (bug 1248358).
+ if (NewTCPSocketPair(fd, true)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If the previous fails try without recv buffer increase (bug 1305436).
+ } else if (NewTCPSocketPair(fd, false)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If both fail, try the old version.
+ } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+
+ PRSocketOptionData socket_opt;
+ DebugOnly<PRStatus> status;
+ socket_opt.option = PR_SockOpt_NoDelay;
+ socket_opt.value.no_delay = true;
+ PR_SetSocketOption(mWriteFD, &socket_opt);
+ PR_SetSocketOption(mReadFD, &socket_opt);
+ socket_opt.option = PR_SockOpt_Nonblocking;
+ socket_opt.value.non_blocking = true;
+ status = PR_SetSocketOption(mWriteFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ status = PR_SetSocketOption(mReadFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ }
+
+ if (mReadFD && mWriteFD) {
+ // compatibility with LSPs such as McAfee that assume a NSPR
+ // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a
+ // nop.
+ PRFileDesc* topLayer = PR_CreateIOLayerStub(sPollableEventLayerIdentity,
+ sPollableEventLayerMethodsPtr);
+ if (topLayer) {
+ if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) {
+ topLayer->dtor(topLayer);
+ } else {
+ SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
+ mReadFD = topLayer;
+ }
+ }
+
+ } else {
+ SOCKET_LOG(("PollableEvent() socketpair failed\n"));
+ }
+#endif
+
+ if (mReadFD && mWriteFD) {
+ // prime the system to deal with races invovled in [dc]tor cycle
+ SOCKET_LOG(("PollableEvent() ctor ok\n"));
+ mSignaled = true;
+ MarkFirstSignalTimestamp();
+ PR_Write(mWriteFD, "I", 1);
+ }
+}
+
+PollableEvent::~PollableEvent() {
+ MOZ_COUNT_DTOR(PollableEvent);
+ if (mWriteFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mWriteFD);
+#endif
+ PR_Close(mWriteFD);
+ }
+ if (mReadFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mReadFD);
+#endif
+ PR_Close(mReadFD);
+ }
+}
+
+// we do not record signals on the socket thread
+// because the socket thread can reliably look at its
+// own runnable queue before selecting a poll time
+// this is the "service the network without blocking" comment in
+// nsSocketTransportService2.cpp
+bool PollableEvent::Signal() {
+ SOCKET_LOG(("PollableEvent::Signal\n"));
+
+ if (!mWriteFD) {
+ SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
+ return false;
+ }
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event. See bug 1292181.
+ if (OnSocketThread()) {
+ SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
+ return true;
+ }
+#endif
+
+#ifndef XP_WIN
+ // To wake up the poll writing once is enough, but for Windows that can cause
+ // hangs so we will write for every event.
+ // For non-Windows systems it is enough to write just once.
+ if (mSignaled) {
+ return true;
+ }
+#endif
+
+ if (!mSignaled) {
+ mSignaled = true;
+ MarkFirstSignalTimestamp();
+ }
+
+ int32_t status = PR_Write(mWriteFD, "M", 1);
+ SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
+ if (status != 1) {
+ NS_WARNING("PollableEvent::Signal Failed\n");
+ SOCKET_LOG(("PollableEvent::Signal Failed\n"));
+ mSignaled = false;
+ mWriteFailed = true;
+ } else {
+ mWriteFailed = false;
+ }
+ return (status == 1);
+}
+
+bool PollableEvent::Clear() {
+ // necessary because of the "dont signal on socket thread" optimization
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ SOCKET_LOG(("PollableEvent::Clear\n"));
+
+ if (!mFirstSignalAfterClear.IsNull()) {
+ SOCKET_LOG(("PollableEvent::Clear time to signal %ums",
+ (uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear)
+ .ToMilliseconds()));
+ }
+
+ mFirstSignalAfterClear = TimeStamp();
+ mSignalTimestampAdjusted = false;
+ mSignaled = false;
+
+ if (!mReadFD) {
+ SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
+ return false;
+ }
+
+ char buf[2048];
+ int32_t status;
+#ifdef XP_WIN
+ // On Windows we are writing to the socket for each event, to be sure that we
+ // do not have any deadlock read from the socket as much as we can.
+ while (true) {
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ } else {
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+ }
+ }
+ }
+#else
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
+
+ if (status == 1) {
+ return true;
+ }
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status > 1) {
+ MOZ_ASSERT(false);
+ SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
+ Clear();
+ return true;
+ }
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ }
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+#endif // XP_WIN
+}
+
+void PollableEvent::MarkFirstSignalTimestamp() {
+ if (mFirstSignalAfterClear.IsNull()) {
+ SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp"));
+ mFirstSignalAfterClear = TimeStamp::NowLoRes();
+ }
+}
+
+void PollableEvent::AdjustFirstSignalTimestamp() {
+ if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) {
+ SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp"));
+ mFirstSignalAfterClear = TimeStamp::NowLoRes();
+ mSignalTimestampAdjusted = true;
+ }
+}
+
+bool PollableEvent::IsSignallingAlive(TimeDuration const& timeout) {
+ if (mWriteFailed) {
+ return false;
+ }
+
+#ifdef DEBUG
+ // The timeout would be just a disturbance in a debug build.
+ return true;
+#else
+ if (!mSignaled || mFirstSignalAfterClear.IsNull() ||
+ timeout == TimeDuration()) {
+ return true;
+ }
+
+ TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear);
+ bool timedOut = delay > timeout;
+
+ return !timedOut;
+#endif // DEBUG
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h
new file mode 100644
index 0000000000..6a17cd909d
--- /dev/null
+++ b/netwerk/base/PollableEvent.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PollableEvent_h__
+#define PollableEvent_h__
+
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+
+struct PRFileDesc;
+
+namespace mozilla {
+namespace net {
+
+// class must be called locked
+class PollableEvent {
+ public:
+ PollableEvent();
+ ~PollableEvent();
+
+ // Signal/Clear return false only if they fail
+ bool Signal();
+ // This is called only when we get non-null out_flags for the socket pair
+ bool Clear();
+ bool Valid() { return mWriteFD && mReadFD; }
+
+ // We want to detect if writing to one of the socket pair sockets takes
+ // too long to be received by the other socket from the pair.
+ // Hence, we remember the timestamp of the earliest write by a call to
+ // MarkFirstSignalTimestamp() from Signal(). After waking up from poll()
+ // we check how long it took get the 'readable' signal on the socket pair.
+ void MarkFirstSignalTimestamp();
+ // Called right before we enter poll() to exclude any possible delay between
+ // the earlist call to Signal() and entering poll() caused by processing
+ // of events dispatched to the socket transport thread.
+ void AdjustFirstSignalTimestamp();
+ // This returns false on following conditions:
+ // - PR_Write has failed
+ // - no out_flags were signalled on the socket pair for too long after
+ // the earliest Signal()
+ bool IsSignallingAlive(TimeDuration const& timeout);
+
+ PRFileDesc* PollableFD() { return mReadFD; }
+
+ private:
+ PRFileDesc* mWriteFD{nullptr};
+ PRFileDesc* mReadFD{nullptr};
+ bool mSignaled{false};
+ // true when PR_Write to the socket pair has failed (status < 1)
+ bool mWriteFailed{false};
+ // Set true after AdjustFirstSignalTimestamp() was called
+ // Set false after Clear() was called
+ // Ensures shifting the timestamp before entering poll() only once
+ // between Clear()'ings.
+ bool mSignalTimestampAdjusted{false};
+ // Timestamp of the first call to Signal() (or time we enter poll())
+ // that happened after the last Clear() call
+ TimeStamp mFirstSignalAfterClear;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp
new file mode 100644
index 0000000000..de19d0e06e
--- /dev/null
+++ b/netwerk/base/Predictor.cpp
@@ -0,0 +1,2438 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "Predictor.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICacheStorage.h"
+#include "nsICachingChannel.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsIDNSService.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFile.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsILoadContext.h"
+#include "nsILoadContextInfo.h"
+#include "nsILoadGroup.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsIObserverService.h"
+#include "nsISpeculativeConnect.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+
+#include "LoadContextInfo.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoChild.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include "CacheControlParser.h"
+#include "ReferrerInfo.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace net {
+
+Predictor* Predictor::sSelf = nullptr;
+
+static LazyLogModule gPredictorLog("NetworkPredictor");
+
+#define PREDICTOR_LOG(args) \
+ MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
+
+#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
+
+// All these time values are in sec
+static const uint32_t ONE_DAY = 86400U;
+static const uint32_t ONE_WEEK = 7U * ONE_DAY;
+static const uint32_t ONE_MONTH = 30U * ONE_DAY;
+static const uint32_t ONE_YEAR = 365U * ONE_DAY;
+
+// Version of metadata entries we expect
+static const uint32_t METADATA_VERSION = 1;
+
+// Flags available in entries
+// FLAG_PREFETCHABLE - we have determined that this item is eligible for
+// prefetch
+static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
+
+// We save 12 bits in the "flags" section of our metadata for actual flags, the
+// rest are to keep track of a rolling count of which loads a resource has been
+// used on to determine if we can prefetch that resource or not;
+static const uint8_t kRollingLoadOffset = 12;
+static const int32_t kMaxPrefetchRollingLoadCount = 20;
+static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
+
+// ID Extensions for cache entries
+#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
+
+// Get the full origin (scheme, host, port) out of a URI (maybe should be part
+// of nsIURI instead?)
+static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) {
+ nsAutoCString s;
+ nsresult rv = nsContentUtils::GetWebExposedOriginSerialization(uri, s);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewURI(originUri, s);
+}
+
+// All URIs we get passed *must* be http or https if they're not null. This
+// helps ensure that.
+static bool IsNullOrHttp(nsIURI* uri) {
+ if (!uri) {
+ return true;
+ }
+
+ return uri->SchemeIs("http") || uri->SchemeIs("https");
+}
+
+// Listener for the speculative DNS requests we'll fire off, which just ignores
+// the result (since we're just trying to warm the cache). This also exists to
+// reduce round-trips to the main thread, by being something threadsafe the
+// Predictor can use.
+
+NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
+
+NS_IMETHODIMP
+Predictor::DNSListener::OnLookupComplete(nsICancelable* request,
+ nsIDNSRecord* rec, nsresult status) {
+ return NS_OK;
+}
+
+// Class to proxy important information from the initial predictor call through
+// the cache API and back into the internals of the predictor. We can't use the
+// predictor itself, as it may have multiple actions in-flight, and each action
+// has different parameters.
+NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
+
+Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
+ nsIURI* targetURI, nsIURI* sourceURI,
+ nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor)
+ : mFullUri(fullUri),
+ mPredict(predict),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mVerifier(verifier),
+ mStackCount(0),
+ mPredictor(predictor) {
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
+ nsIURI* targetURI, nsIURI* sourceURI,
+ nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor, uint8_t stackCount)
+ : mFullUri(fullUri),
+ mPredict(predict),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mVerifier(verifier),
+ mStackCount(stackCount),
+ mPredictor(predictor) {
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
+
+ nsAutoCString targetURI, sourceURI;
+ mTargetURI->GetAsciiSpec(targetURI);
+ if (mSourceURI) {
+ mSourceURI->GetAsciiSpec(sourceURI);
+ }
+ PREDICTOR_LOG(
+ ("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
+ "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
+ "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32,
+ this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
+ targetURI.get(), sourceURI.get(), mStackCount, isNew,
+ static_cast<uint32_t>(result)));
+ if (NS_FAILED(result)) {
+ PREDICTOR_LOG(
+ ("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
+ "). Aborting.",
+ this, static_cast<uint32_t>(result)));
+ return NS_OK;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, mStartTime);
+ if (mPredict) {
+ bool predicted =
+ mPredictor->PredictInternal(mPredictReason, entry, isNew, mFullUri,
+ mTargetURI, mVerifier, mStackCount);
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME,
+ mStartTime);
+ if (predicted) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
+ }
+ } else {
+ mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
+ mSourceURI);
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME,
+ mStartTime);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor, nsINetworkPredictor, nsIObserver,
+ nsISpeculativeConnectionOverrider, nsIInterfaceRequestor,
+ nsICacheEntryMetaDataVisitor, nsINetworkPredictorVerifier)
+
+Predictor::Predictor()
+
+{
+ MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
+ sSelf = this;
+}
+
+Predictor::~Predictor() {
+ if (mInitialized) Shutdown();
+
+ sSelf = nullptr;
+}
+
+// Predictor::nsIObserver
+
+nsresult Predictor::InstallObserver() {
+ MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+void Predictor::RemoveObserver() {
+ MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+}
+
+NS_IMETHODIMP
+Predictor::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data_unicode) {
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor observing something off main thread!");
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ }
+
+ return rv;
+}
+
+// Predictor::nsISpeculativeConnectionOverrider
+
+NS_IMETHODIMP
+Predictor::GetIgnoreIdle(bool* ignoreIdle) {
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetParallelSpeculativeConnectLimit(
+ uint32_t* parallelSpeculativeConnectLimit) {
+ *parallelSpeculativeConnectLimit = 6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetIsFromPredictor(bool* isFromPredictor) {
+ *isFromPredictor = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetAllow1918(bool* allow1918) {
+ *allow1918 = false;
+ return NS_OK;
+}
+
+// Predictor::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+Predictor::GetInterface(const nsIID& iid, void** result) {
+ return QueryInterface(iid, result);
+}
+
+// Predictor::nsICacheEntryMetaDataVisitor
+
+#define SEEN_META_DATA "predictor::seen"
+#define RESOURCE_META_DATA "predictor::resource-count"
+#define META_DATA_PREFIX "predictor::"
+
+static bool IsURIMetadataElement(const char* key) {
+ return StringBeginsWith(nsDependentCString(key),
+ nsLiteralCString(META_DATA_PREFIX)) &&
+ !nsLiteralCString(SEEN_META_DATA).Equals(key) &&
+ !nsLiteralCString(RESOURCE_META_DATA).Equals(key);
+}
+
+nsresult Predictor::OnMetaDataElement(const char* asciiKey,
+ const char* asciiValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToOperateOn.AppendElement(key);
+ mValuesToOperateOn.AppendElement(value);
+
+ return NS_OK;
+}
+
+// Predictor::nsINetworkPredictor
+
+nsresult Predictor::Init() {
+ MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+
+ rv = InstallObserver();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
+
+ if (!mDNSListener) {
+ mDNSListener = new DNSListener();
+ }
+
+ mCacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewURI(getter_AddRefs(mStartupURI), "predictor://startup");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitialized = true;
+
+ return rv;
+}
+
+namespace {
+class PredictorLearnRunnable final : public Runnable {
+ public:
+ PredictorLearnRunnable(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& oa)
+ : Runnable("PredictorLearnRunnable"),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mReason(reason),
+ mOA(oa) {
+ MOZ_DIAGNOSTIC_ASSERT(targetURI, "Must have a target URI");
+ }
+
+ ~PredictorLearnRunnable() = default;
+
+ NS_IMETHOD Run() override {
+ if (!gNeckoChild) {
+ // This may have gone away between when this runnable was dispatched and
+ // when it actually runs, so let's be safe here, even though we asserted
+ // earlier.
+ PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away"));
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG(("predictor::learn (async) forwarding to parent"));
+ gNeckoChild->SendPredLearn(mTargetURI, mSourceURI, mReason, mOA);
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsCOMPtr<nsIURI> mSourceURI;
+ PredictorLearnReason mReason;
+ const OriginAttributes mOA;
+};
+
+} // namespace
+
+void Predictor::Shutdown() {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
+ return;
+ }
+
+ RemoveObserver();
+
+ mInitialized = false;
+}
+
+nsresult Predictor::Create(const nsIID& aIID, void** aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ RefPtr<Predictor> svc = new Predictor();
+ if (IsNeckoChild()) {
+ NeckoChild::InitNeckoChild();
+
+ // Child threads only need to be call into the public interface methods
+ // so we don't bother with initialization
+ return svc->QueryInterface(aIID, aResult);
+ }
+
+ rv = svc->Init();
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
+ }
+
+ // We treat init failure the same as the service being disabled, since this
+ // is all an optimization anyway. No need to freak people out. That's why we
+ // gladly continue on QI'ing here.
+ rv = svc->QueryInterface(aIID, aResult);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Predictor::Predict(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorPredictReason reason,
+ JS::Handle<JS::Value> originAttributes,
+ nsINetworkPredictorVerifier* verifier, JSContext* aCx) {
+ OriginAttributes attrs;
+
+ if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return PredictNative(targetURI, sourceURI, reason, attrs, verifier);
+}
+
+// Called from the main thread to initiate predictive actions
+NS_IMETHODIMP
+Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorPredictReason reason,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Predict"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+ // If two different threads are predicting concurently, this will be
+ // overwritten. Thankfully, we only use this in tests, which will
+ // overwrite mVerifier perhaps multiple times for each individual test;
+ // however, within each test, the multiple predict calls should have the
+ // same verifier.
+ if (verifier) {
+ PREDICTOR_LOG((" was given a verifier"));
+ mChildVerifier = verifier;
+ }
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredPredict(targetURI, sourceURI, reason, originAttributes,
+ verifier);
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (originAttributes.mPrivateBrowsingId > 0) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ // Nothing we can do for non-HTTP[S] schemes
+ PREDICTOR_LOG((" got non-http[s] URI"));
+ return NS_OK;
+ }
+
+ // Ensure we've been given the appropriate arguments for the kind of
+ // prediction we're being asked to do
+ nsCOMPtr<nsIURI> uriKey = targetURI;
+ nsCOMPtr<nsIURI> originKey;
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LINK:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" link invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ // Link hover is a special case where we can predict without hitting the
+ // db, so let's go ahead and fire off that prediction here.
+ PredictForLink(targetURI, sourceURI, originAttributes, verifier);
+ return NS_OK;
+ case nsINetworkPredictor::PREDICT_LOAD:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ if (targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Predictor::Reason argReason{};
+ argReason.mPredict = reason;
+
+ // First we open the regular cache entry, to ensure we don't gum up the works
+ // waiting on the less-important predictor-only cache entry
+ RefPtr<Predictor::Action> uriAction = new Predictor::Action(
+ Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason,
+ targetURI, nullptr, verifier, this);
+ nsAutoCString uriKeyStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
+ reason, uriAction.get()));
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
+
+ nsresult rv = mCacheStorageService->DiskCacheStorage(
+ lci, getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t openFlags =
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
+ cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, openFlags, uriAction);
+
+ // Now we do the origin-only (and therefore predictor-only) entry
+ nsCOMPtr<nsIURI> targetOrigin;
+ rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!originKey) {
+ originKey = targetOrigin;
+ }
+
+ RefPtr<Predictor::Action> originAction = new Predictor::Action(
+ Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason,
+ targetOrigin, nullptr, verifier, this);
+ nsAutoCString originKeyStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p",
+ originKeyStr.get(), reason, originAction.get()));
+ openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ cacheDiskStorage->AsyncOpenURI(originKey,
+ nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
+ openFlags, originAction);
+
+ PREDICTOR_LOG((" predict returning"));
+ return NS_OK;
+}
+
+bool Predictor::PredictInternal(PredictorPredictReason reason,
+ nsICacheEntry* entry, bool isNew, bool fullUri,
+ nsIURI* targetURI,
+ nsINetworkPredictorVerifier* verifier,
+ uint8_t stackCount) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictInternal"));
+ bool rv = false;
+
+ nsCOMPtr<nsILoadContextInfo> lci;
+ entry->GetLoadContextInfo(getter_AddRefs(lci));
+
+ if (!lci) {
+ return rv;
+ }
+
+ if (reason == nsINetworkPredictor::PREDICT_LOAD) {
+ MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr());
+ }
+
+ if (isNew) {
+ // nothing else we can do here
+ PREDICTOR_LOG((" new entry"));
+ return rv;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LOAD:
+ rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ rv = PredictForStartup(entry, fullUri, verifier);
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ MOZ_ASSERT(false, "Got unexpected value for prediction reason");
+ }
+
+ return rv;
+}
+
+void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForLink"));
+ if (!mSpeculativeService) {
+ PREDICTOR_LOG((" missing speculative service"));
+ return;
+ }
+
+ if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
+ if (sourceURI->SchemeIs("https")) {
+ // We don't want to predict from an HTTPS page, to avoid info leakage
+ PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(targetURI, originAttributes);
+
+ mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr, false);
+ if (verifier) {
+ PREDICTOR_LOG((" sending verification"));
+ verifier->OnPredictPreconnect(targetURI);
+ }
+}
+
+// This is the driver for prediction based on a new pageload.
+static const uint8_t MAX_PAGELOAD_DEPTH = 10;
+bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
+ uint8_t stackCount, bool fullUri,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForPageload"));
+
+ if (stackCount > MAX_PAGELOAD_DEPTH) {
+ PREDICTOR_LOG((" exceeded recursion depth!"));
+ return false;
+ }
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
+ PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
+
+ uint32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsILoadContextInfo> lci;
+
+ rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> redirectURI;
+ if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
+ getter_AddRefs(redirectURI))) {
+ mPreconnects.AppendElement(redirectURI);
+ Predictor::Reason reason{};
+ reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
+ RefPtr<Predictor::Action> redirectAction = new Predictor::Action(
+ Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason,
+ redirectURI, nullptr, verifier, this, stackCount + 1);
+ nsAutoCString redirectUriString;
+ redirectURI->GetAsciiSpec(redirectUriString);
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ rv = mCacheStorageService->DiskCacheStorage(
+ lci, getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ PREDICTOR_LOG((" Predict redirect uri=%s action=%p",
+ redirectUriString.get(), redirectAction.get()));
+ uint32_t openFlags =
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
+ cacheDiskStorage->AsyncOpenURI(redirectURI, ""_ns, openFlags,
+ redirectAction);
+ return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
+ }
+
+ CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation,
+ fullUri);
+
+ return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier);
+}
+
+// This is the driver for predicting at browser startup time based on pages that
+// have previously been loaded close to startup.
+bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForStartup"));
+
+ nsCOMPtr<nsILoadContextInfo> lci;
+
+ nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
+ CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
+ globalDegradation, fullUri);
+ return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
+}
+
+// This calculates how much to degrade our confidence in our data based on
+// the last time this top-level resource was loaded. This "global degradation"
+// applies to *all* subresources we have associated with the top-level
+// resource. This will be in addition to any reduction in confidence we have
+// associated with a particular subresource.
+int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int32_t globalDegradation;
+ uint32_t delta = NOW_IN_SECONDS() - lastLoad;
+ if (delta < ONE_DAY) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_day();
+ } else if (delta < ONE_WEEK) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_week();
+ } else if (delta < ONE_MONTH) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_month();
+ } else if (delta < ONE_YEAR) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_year();
+ } else {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_max();
+ }
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
+ globalDegradation);
+ return globalDegradation;
+}
+
+// This calculates our overall confidence that a particular subresource will be
+// loaded as part of a top-level load.
+// @param hitCount - the number of times we have loaded this subresource as part
+// of this top-level load
+// @param hitsPossible - the number of times we have performed this top-level
+// load
+// @param lastHit - the timestamp of the last time we loaded this subresource as
+// part of this top-level load
+// @param lastPossible - the timestamp of the last time we performed this
+// top-level load
+// @param globalDegradation - the degradation for this top-level load as
+// determined by CalculateGlobalDegradation
+int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED>
+ predictionsCalculated;
+ ++predictionsCalculated;
+
+ if (!hitsPossible) {
+ return 0;
+ }
+
+ int32_t baseConfidence = (hitCount * 100) / hitsPossible;
+ int32_t maxConfidence = 100;
+ int32_t confidenceDegradation = 0;
+
+ if (lastHit < lastPossible) {
+ // We didn't load this subresource the last time this top-level load was
+ // performed, so let's not bother preconnecting (at the very least).
+ maxConfidence =
+ StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
+
+ // Now calculate how much we want to degrade our confidence based on how
+ // long it's been between the last time we did this top-level load and the
+ // last time this top-level load included this subresource.
+ PRTime delta = lastPossible - lastHit;
+ if (delta == 0) {
+ confidenceDegradation = 0;
+ } else if (delta < ONE_DAY) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_day();
+ } else if (delta < ONE_WEEK) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_week();
+ } else if (delta < ONE_MONTH) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_month();
+ } else if (delta < ONE_YEAR) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_year();
+ } else {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_max();
+ maxConfidence = 0;
+ }
+ }
+
+ // Calculate our confidence and clamp it to between 0 and maxConfidence
+ // (<= 100)
+ int32_t confidence =
+ baseConfidence - confidenceDegradation - globalDegradation;
+ confidence = std::max(confidence, 0);
+ confidence = std::min(confidence, maxConfidence);
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
+ confidenceDegradation);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
+ return confidence;
+}
+
+static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
+ const uint32_t flags, nsCString& newValue) {
+ newValue.Truncate();
+ newValue.AppendInt(METADATA_VERSION);
+ newValue.Append(',');
+ newValue.AppendInt(hitCount);
+ newValue.Append(',');
+ newValue.AppendInt(lastHit);
+ newValue.Append(',');
+ newValue.AppendInt(flags);
+}
+
+// On every page load, the rolling window gets shifted by one bit, leaving the
+// lowest bit at 0, to indicate that the subresource in question has not been
+// seen on the most recent page load. If, at some point later during the page
+// load, the subresource is seen again, we will then set the lowest bit to 1.
+// This is how we keep track of how many of the last n pageloads (for n <= 20) a
+// particular subresource has been seen. The rolling window is kept in the upper
+// 20 bits of the flags element of the metadata. This saves 12 bits for regular
+// old flags.
+void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry,
+ const uint32_t flags, const char* key,
+ const uint32_t hitCount,
+ const uint32_t lastHit) {
+ // Extract just the rolling load count from the flags, shift it to clear the
+ // lowest bit, and put the new value with the existing flags.
+ uint32_t rollingLoadCount = flags & ~kFlagsMask;
+ rollingLoadCount <<= 1;
+ uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
+
+ // Finally, update the metadata on the cache entry.
+ nsAutoCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+}
+
+uint32_t Predictor::ClampedPrefetchRollingLoadCount() {
+ int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count();
+ if (n < 0) {
+ return 0;
+ }
+ if (n > kMaxPrefetchRollingLoadCount) {
+ return kMaxPrefetchRollingLoadCount;
+ }
+ return n;
+}
+
+void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Since the visitor gets called under a cache lock, all we do there is get
+ // copies of the keys/values we care about, and then do the real work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
+ valuesToOperateOn = std::move(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char* key = keysToOperateOn[i].BeginReading();
+ const char* value = valuesToOperateOn[i].BeginReading();
+
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+
+ int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
+ lastLoad, globalDegradation);
+ if (fullUri) {
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key,
+ value, confidence));
+ PrefetchIgnoreReason reason = PREFETCH_OK;
+ if (!fullUri) {
+ // Not full URI - don't prefetch! No sense in it!
+ PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
+ if (flags & FLAG_PREFETCHABLE) {
+ // This only applies if we had somehow otherwise marked this
+ // prefetchable.
+ reason = NOT_FULL_URI;
+ }
+ flags &= ~FLAG_PREFETCHABLE;
+ } else if (!referrer) {
+ // No referrer means we can't prefetch, so pretend it's non-cacheable,
+ // no matter what.
+ PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
+ if (flags & FLAG_PREFETCHABLE) {
+ // This only applies if we had somehow otherwise marked this
+ // prefetchable.
+ reason = NO_REFERRER;
+ }
+ flags &= ~FLAG_PREFETCHABLE;
+ } else {
+ uint32_t expectedRollingLoadCount =
+ (1 << ClampedPrefetchRollingLoadCount()) - 1;
+ expectedRollingLoadCount <<= kRollingLoadOffset;
+ if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
+ PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
+ if (flags & FLAG_PREFETCHABLE) {
+ // This only applies if we had somehow otherwise marked this
+ // prefetchable.
+ reason = MISSED_A_LOAD;
+ }
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ }
+
+ PREDICTOR_LOG((" setting up prediction"));
+ SetupPrediction(confidence, flags, uri, reason);
+ }
+}
+
+// (Maybe) adds a predictive action to the prediction runner, based on our
+// calculated confidence for the subresource in question.
+void Predictor::SetupPrediction(int32_t confidence, uint32_t flags,
+ const nsCString& uri,
+ PrefetchIgnoreReason earlyReason) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = NS_OK;
+ PREDICTOR_LOG(
+ ("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
+ "preconnect-min-confidence=%d preresolve-min-confidence=%d "
+ "flags=%d confidence=%d uri=%s",
+ StaticPrefs::network_predictor_enable_prefetch(),
+ StaticPrefs::network_predictor_prefetch_min_confidence(),
+ StaticPrefs::network_predictor_preconnect_min_confidence(),
+ StaticPrefs::network_predictor_preresolve_min_confidence(), flags,
+ confidence, uri.get()));
+
+ bool prefetchOk = !!(flags & FLAG_PREFETCHABLE);
+ PrefetchIgnoreReason reason = earlyReason;
+ if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) {
+ prefetchOk = false;
+ reason = PREFETCH_DISABLED;
+ } else if (prefetchOk && !ClampedPrefetchRollingLoadCount() &&
+ confidence <
+ StaticPrefs::network_predictor_prefetch_min_confidence()) {
+ prefetchOk = false;
+ if (!ClampedPrefetchRollingLoadCount()) {
+ reason = PREFETCH_DISABLED_VIA_COUNT;
+ } else {
+ reason = CONFIDENCE_TOO_LOW;
+ }
+ }
+
+ // prefetchOk == false and reason == PREFETCH_OK indicates that the reason
+ // we aren't prefetching this item is because it was marked un-prefetchable in
+ // our metadata. We already have separate telemetry on that decision, so we
+ // aren't going to accumulate more here. Right now we only care about why
+ // something we had marked prefetchable isn't being prefetched.
+ if (!prefetchOk && reason != PREFETCH_OK) {
+ Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason);
+ }
+
+ if (prefetchOk) {
+ nsCOMPtr<nsIURI> prefetchURI;
+ rv = NS_NewURI(getter_AddRefs(prefetchURI), uri);
+ if (NS_SUCCEEDED(rv)) {
+ mPrefetches.AppendElement(prefetchURI);
+ }
+ } else if (confidence >=
+ StaticPrefs::network_predictor_preconnect_min_confidence()) {
+ nsCOMPtr<nsIURI> preconnectURI;
+ rv = NS_NewURI(getter_AddRefs(preconnectURI), uri);
+ if (NS_SUCCEEDED(rv)) {
+ mPreconnects.AppendElement(preconnectURI);
+ }
+ } else if (confidence >=
+ StaticPrefs::network_predictor_preresolve_min_confidence()) {
+ nsCOMPtr<nsIURI> preresolveURI;
+ rv = NS_NewURI(getter_AddRefs(preresolveURI), uri);
+ if (NS_SUCCEEDED(rv)) {
+ mPreresolves.AppendElement(preresolveURI);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv)));
+ }
+}
+
+nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ nsAutoCString strUri, strReferrer;
+ uri->GetAsciiSpec(strUri);
+ referrer->GetAsciiSpec(strReferrer);
+ PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
+ strUri.get(), strReferrer.get(), verifier));
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(
+ getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
+ nullptr, /* aPerformanceStorage */
+ nullptr, /* aLoadGroup */
+ nullptr, /* aCallbacks */
+ nsIRequest::LOAD_BACKGROUND);
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ rv = loadInfo->SetOriginAttributes(originAttributes);
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" Set originAttributes into loadInfo failed rv=0x%" PRIX32,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrer);
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // XXX - set a header here to indicate this is a prefetch?
+
+ nsCOMPtr<nsIStreamListener> listener =
+ new PrefetchListener(verifier, uri, this);
+ PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(),
+ channel.get()));
+ rv = channel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" AsyncOpen failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
+ }
+
+ return rv;
+}
+
+// Runs predictions that have been set up.
+bool Predictor::RunPredictions(nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
+
+ PREDICTOR_LOG(("Predictor::RunPredictions"));
+
+ bool predicted = false;
+ uint32_t len, i;
+
+ nsTArray<nsCOMPtr<nsIURI>> prefetches = std::move(mPrefetches),
+ preconnects = std::move(mPreconnects),
+ preresolves = std::move(mPreresolves);
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS>
+ totalPredictions;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS>
+ totalPreconnects;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES>
+ totalPreresolves;
+
+ len = prefetches.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing prefetch"));
+ nsCOMPtr<nsIURI> uri = prefetches[i];
+ if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) {
+ ++totalPredictions;
+ ++totalPrefetches;
+ predicted = true;
+ }
+ }
+
+ len = preconnects.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing preconnect"));
+ nsCOMPtr<nsIURI> uri = preconnects[i];
+ ++totalPredictions;
+ ++totalPreconnects;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, originAttributes);
+ mSpeculativeService->SpeculativeConnect(uri, principal, this, false);
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preconnect verification"));
+ verifier->OnPredictPreconnect(uri);
+ }
+ }
+
+ len = preresolves.Length();
+ for (i = 0; i < len; ++i) {
+ nsCOMPtr<nsIURI> uri = preresolves[i];
+ ++totalPredictions;
+ ++totalPreresolves;
+ nsAutoCString hostname;
+ uri->GetAsciiHost(hostname);
+ PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
+ nsCOMPtr<nsICancelable> tmpCancelable;
+ mDnsService->AsyncResolveNative(
+ hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ nullptr, mDNSListener, nullptr, originAttributes,
+ getter_AddRefs(tmpCancelable));
+
+ // Fetch HTTPS RR if needed.
+ if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
+ StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
+ mDnsService->AsyncResolveNative(
+ hostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ nullptr, mDNSListener, nullptr, originAttributes,
+ getter_AddRefs(tmpCancelable));
+ }
+
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preresolve verification"));
+ verifier->OnPredictDNS(uri);
+ }
+ }
+
+ return predicted;
+}
+
+// Find out if a top-level page is likely to redirect.
+bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI** redirectURI) {
+ // TODO - not doing redirects for first go around
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return false;
+}
+
+NS_IMETHODIMP
+Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ JS::Handle<JS::Value> originAttributes, JSContext* aCx) {
+ OriginAttributes attrs;
+
+ if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return LearnNative(targetURI, sourceURI, reason, attrs);
+}
+
+// Called from the main thread to update the database
+NS_IMETHODIMP
+Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Learn"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+
+ RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable(
+ targetURI, sourceURI, reason, originAttributes);
+ SchedulerGroup::Dispatch(runnable.forget());
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (originAttributes.mPrivateBrowsingId > 0) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ PREDICTOR_LOG((" got non-HTTP[S] URI"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIURI> targetOrigin;
+ nsCOMPtr<nsIURI> sourceOrigin;
+ nsCOMPtr<nsIURI> uriKey;
+ nsCOMPtr<nsIURI> originKey;
+ nsresult rv;
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load toplevel invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = targetURI;
+ originKey = targetOrigin;
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" redirect/subresource invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = sourceURI;
+ originKey = sourceOrigin;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
+ ++learnAttempts;
+
+ Predictor::Reason argReason{};
+ argReason.mLearn = reason;
+
+ // We always open the full uri (general cache) entry first, so we don't gum up
+ // the works waiting on predictor-only entries to open
+ RefPtr<Predictor::Action> uriAction = new Predictor::Action(
+ Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason,
+ targetURI, sourceURI, nullptr, this);
+ nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ targetURI->GetAsciiSpec(targetUriStr);
+ if (sourceURI) {
+ sourceURI->GetAsciiSpec(sourceUriStr);
+ }
+ PREDICTOR_LOG(
+ (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
+ "action=%p",
+ uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason,
+ uriAction.get()));
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
+
+ rv = mCacheStorageService->DiskCacheStorage(lci,
+ getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For learning full URI things, we *always* open readonly and secretly, as we
+ // rely on actual pageloads to update the entry's metadata for us.
+ uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // Learning for toplevel we want to open the full uri entry priority, since
+ // it's likely this entry will be used soon anyway, and we want this to be
+ // opened ASAP.
+ uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
+ }
+ cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, uriOpenFlags, uriAction);
+
+ // Now we open the origin-only (and therefore predictor-only) entry
+ RefPtr<Predictor::Action> originAction = new Predictor::Action(
+ Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason,
+ targetOrigin, sourceOrigin, nullptr, this);
+ nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ targetOrigin->GetAsciiSpec(targetOriginStr);
+ if (sourceOrigin) {
+ sourceOrigin->GetAsciiSpec(sourceOriginStr);
+ }
+ PREDICTOR_LOG(
+ (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
+ "action=%p",
+ originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason,
+ originAction.get()));
+ uint32_t originOpenFlags;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // This is the only case when we want to update the 'last used' metadata on
+ // the cache entry we're getting. This only applies to predictor-specific
+ // entries.
+ originOpenFlags =
+ nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
+ } else {
+ originOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ }
+ cacheDiskStorage->AsyncOpenURI(originKey,
+ nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
+ originOpenFlags, originAction);
+
+ PREDICTOR_LOG(("Predictor::Learn returning"));
+ return NS_OK;
+}
+
+void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
+ bool isNew, bool fullUri, nsIURI* targetURI,
+ nsIURI* sourceURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnInternal"));
+
+ nsCString junk;
+ if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
+ NS_FAILED(
+ entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
+ // This is an origin-only entry that we haven't seen before. Let's mark it
+ // as seen.
+ PREDICTOR_LOG((" marking new origin entry as seen"));
+ nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to mark origin entry seen"));
+ return;
+ }
+
+ // Need to ensure someone else can get to the entry if necessary
+ entry->MetaDataReady();
+ return;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ // This case only exists to be used during tests - code outside the
+ // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
+ // The predictor xpcshell test needs this branch, however, because we
+ // have no real page loads in xpcshell, and this is how we fake it up
+ // so that all the work that normally happens behind the scenes in a
+ // page load can be done for testing purposes.
+ if (fullUri && StaticPrefs::network_predictor_doing_tests()) {
+ PREDICTOR_LOG(
+ (" WARNING - updating rolling load count. "
+ "If you see this outside tests, you did it wrong"));
+
+ // Since the visitor gets called under a cache lock, all we do there is
+ // get copies of the keys/values we care about, and then do the real
+ // work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
+ valuesToOperateOn = std::move(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char* key = keysToOperateOn[i].BeginReading();
+ const char* value = valuesToOperateOn[i].BeginReading();
+
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ } else {
+ PREDICTOR_LOG((" nothing to do for toplevel"));
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ if (fullUri) {
+ LearnForRedirect(entry, targetURI);
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ LearnForSubresource(entry, targetURI);
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ LearnForStartup(entry, targetURI);
+ break;
+ default:
+ PREDICTOR_LOG((" unexpected reason value"));
+ MOZ_ASSERT(false, "Got unexpected value for learn reason!");
+ }
+}
+
+NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
+
+NS_IMETHODIMP
+Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(key)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+ bool ok =
+ mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags);
+
+ if (!ok) {
+ // Couldn't parse this one, just get rid of it
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ uint32_t uriLength = uri.Length();
+ if (uriLength > StaticPrefs::network_predictor_max_uri_length()) {
+ // Default to getting rid of URIs that are too long and were put in before
+ // we had our limit on URI length, in order to free up some space.
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
+ mLRUKeyToDelete = key;
+ mLRUStamp = lastHit;
+ }
+
+ return NS_OK;
+}
+
+void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLRUKeyToDelete) {
+ entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
+ }
+
+ for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
+ entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
+ }
+}
+
+// Called when a subresource has been hit from a top-level load. Uses the two
+// helper functions above to update the database appropriately.
+void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnForSubresource"));
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ uint32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString key;
+ key.AssignLiteral(META_DATA_PREFIX);
+ nsCString uri;
+ targetURI->GetAsciiSpec(uri);
+ key.Append(uri);
+ if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) {
+ // We do this to conserve space/prevent OOMs
+ PREDICTOR_LOG((" uri too long!"));
+ entry->SetMetaDataElement(key.BeginReading(), nullptr);
+ return;
+ }
+
+ nsCString value;
+ rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
+
+ uint32_t hitCount, lastHit, flags;
+ bool isNewResource =
+ (NS_FAILED(rv) ||
+ !ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri,
+ hitCount, lastHit, flags));
+
+ int32_t resourceCount = 0;
+ if (isNewResource) {
+ // This is a new addition
+ PREDICTOR_LOG((" new resource"));
+ nsCString s;
+ rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
+ if (NS_SUCCEEDED(rv)) {
+ resourceCount = atoi(s.BeginReading());
+ }
+ if (resourceCount >=
+ StaticPrefs::network_predictor_max_resources_per_entry()) {
+ RefPtr<Predictor::SpaceCleaner> cleaner =
+ new Predictor::SpaceCleaner(this);
+ entry->VisitMetaData(cleaner);
+ cleaner->Finalize(entry);
+ } else {
+ ++resourceCount;
+ }
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to update resource count"));
+ return;
+ }
+ hitCount = 1;
+ flags = 0;
+ } else {
+ PREDICTOR_LOG((" existing resource"));
+ hitCount = std::min(hitCount + 1, loadCount);
+ }
+
+ // Update the rolling load count to mark this sub-resource as seen on the
+ // most-recent pageload so it can be eligible for prefetch (assuming all
+ // the other stars align).
+ flags |= (1 << kRollingLoadOffset);
+
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
+ rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
+ PREDICTOR_LOG(
+ (" SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv) && isNewResource) {
+ // Roll back the increment to the resource count we made above.
+ PREDICTOR_LOG((" rolling back resource count update"));
+ --resourceCount;
+ if (resourceCount == 0) {
+ entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
+ } else {
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ }
+ }
+}
+
+// This is called when a top-level loaded ended up redirecting to a different
+// URI so we can keep track of that fact.
+void Predictor::LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing redirects for first go around
+ PREDICTOR_LOG(("Predictor::LearnForRedirect"));
+}
+
+// This will add a page to our list of startup pages if it's being loaded
+// before our startup window has expired.
+void Predictor::MaybeLearnForStartup(nsIURI* uri, bool fullUri,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing startup for first go around
+ PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
+}
+
+// Add information about a top-level load to our list of startup pages
+void Predictor::LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // These actually do the same set of work, just on different entries, so we
+ // can pass through to get the real work done here
+ PREDICTOR_LOG(("Predictor::LearnForStartup"));
+ LearnForSubresource(entry, targetURI);
+}
+
+bool Predictor::ParseMetaDataEntry(const char* key, const char* value,
+ nsCString& uri, uint32_t& hitCount,
+ uint32_t& lastHit, uint32_t& flags) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(
+ ("Predictor::ParseMetaDataEntry key=%s value=%s", key ? key : "", value));
+
+ const char* comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find first comma"));
+ return false;
+ }
+
+ uint32_t version = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" version -> %u", version));
+
+ if (version != METADATA_VERSION) {
+ PREDICTOR_LOG(
+ (" metadata version mismatch %u != %u", version, METADATA_VERSION));
+ return false;
+ }
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find second comma"));
+ return false;
+ }
+
+ hitCount = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" hitCount -> %u", hitCount));
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find third comma"));
+ return false;
+ }
+
+ lastHit = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" lastHit -> %u", lastHit));
+
+ value = comma + 1;
+ flags = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" flags -> %u", flags));
+
+ if (key) {
+ const char* uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
+ uri.AssignASCII(uriStart);
+ PREDICTOR_LOG((" uri -> %s", uriStart));
+ } else {
+ uri.Truncate();
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+Predictor::Reset() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Reset"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredReset();
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
+ PREDICTOR_LOG((" created a resetter"));
+ mCacheStorageService->AsyncVisitAllStorages(reset, true);
+ PREDICTOR_LOG((" Cache async launched, returning now"));
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor::Resetter, nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor, nsICacheStorageVisitor);
+
+Predictor::Resetter::Resetter(Predictor* predictor)
+ : mEntriesToVisit(0), mPredictor(predictor) {}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(result)) {
+ // This can happen when we've tried to open an entry that doesn't exist for
+ // some non-reset operation, and then get reset shortly thereafter (as
+ // happens in some of our tests).
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+ return NS_OK;
+ }
+
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToDelete = std::move(mKeysToDelete);
+
+ for (size_t i = 0; i < keysToDelete.Length(); ++i) {
+ const char* key = keysToDelete[i].BeginReading();
+ entry->SetMetaDataElement(key, nullptr);
+ }
+
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnMetaDataElement(const char* asciiKey,
+ const char* asciiValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StringBeginsWith(nsDependentCString(asciiKey),
+ nsLiteralCString(META_DATA_PREFIX))) {
+ // Not a metadata entry we care about, carry on
+ return NS_OK;
+ }
+
+ nsCString key;
+ key.AssignASCII(asciiKey);
+ mKeysToDelete.AppendElement(key);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount,
+ uint64_t consumption, uint64_t capacity,
+ nsIFile* diskDirectory) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryInfo(nsIURI* uri, const nsACString& idEnhance,
+ int64_t dataSize, int64_t altDataSize,
+ uint32_t fetchCount,
+ uint32_t lastModifiedTime,
+ uint32_t expirationTime, bool aPinned,
+ nsILoadContextInfo* aInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ // The predictor will only ever touch entries with no idEnhance ("") or an
+ // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
+ // don't match that to avoid doing extra work.
+ if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
+ // This is an entry we own, so we can just doom it entirely
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ rv = mPredictor->mCacheStorageService->DiskCacheStorage(
+ aInfo, getter_AddRefs(cacheDiskStorage));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
+ } else if (idEnhance.IsEmpty()) {
+ // This is an entry we don't own, so we have to be a little more careful and
+ // just get rid of our own metadata entries. Append it to an array of things
+ // to operate on and then do the operations later so we don't end up calling
+ // Complete() multiple times/too soon.
+ ++mEntriesToVisit;
+ mURIsToVisit.AppendElement(uri);
+ mInfosToVisit.AppendElement(aInfo);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryVisitCompleted() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ nsTArray<nsCOMPtr<nsIURI>> urisToVisit = std::move(mURIsToVisit);
+
+ MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
+
+ nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit =
+ std::move(mInfosToVisit);
+
+ MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length());
+
+ if (!mEntriesToVisit) {
+ Complete();
+ return NS_OK;
+ }
+
+ uint32_t entriesToVisit = urisToVisit.Length();
+ for (uint32_t i = 0; i < entriesToVisit; ++i) {
+ nsCString u;
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ rv = mPredictor->mCacheStorageService->DiskCacheStorage(
+ infosToVisit[i], getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ urisToVisit[i]->GetAsciiSpec(u);
+ rv = cacheDiskStorage->AsyncOpenURI(
+ urisToVisit[i], ""_ns,
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+ if (NS_FAILED(rv)) {
+ mEntriesToVisit--;
+ if (!mEntriesToVisit) {
+ Complete();
+ return NS_OK;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void Predictor::Resetter::Complete() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
+ return;
+ }
+
+ obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
+}
+
+// Helper functions to make using the predictor easier from native code
+
+static StaticRefPtr<nsINetworkPredictor> sPredictor;
+
+static nsresult EnsureGlobalPredictor(nsINetworkPredictor** aPredictor) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sPredictor) {
+ nsresult rv;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ sPredictor = predictor;
+ ClearOnShutdown(&sPredictor);
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get();
+ predictor.forget(aPredictor);
+ return NS_OK;
+}
+
+nsresult PredictorPredict(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorPredictReason reason,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->PredictNative(targetURI, sourceURI, reason,
+ originAttributes, verifier);
+}
+
+nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
+}
+
+nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason, nsILoadGroup* loadGroup) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ OriginAttributes originAttributes;
+
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ loadContext = do_GetInterface(callbacks);
+
+ if (loadContext) {
+ loadContext->GetOriginAttributes(originAttributes);
+ }
+ }
+ }
+
+ return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
+}
+
+nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason, dom::Document* document) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes originAttributes;
+
+ if (document) {
+ nsCOMPtr<nsIPrincipal> docPrincipal = document->NodePrincipal();
+
+ if (docPrincipal) {
+ originAttributes = docPrincipal->OriginAttributesRef();
+ }
+ }
+
+ return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
+}
+
+nsresult PredictorLearnRedirect(nsIURI* targetURI, nsIChannel* channel,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> sourceURI;
+ nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sameUri;
+ rv = targetURI->Equals(sourceURI, &sameUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (sameUri) {
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->LearnNative(targetURI, sourceURI,
+ nsINetworkPredictor::LEARN_LOAD_REDIRECT,
+ originAttributes);
+}
+
+// nsINetworkPredictorVerifier
+
+/**
+ * Call through to the child's verifier (only during tests)
+ */
+NS_IMETHODIMP
+Predictor::OnPredictPrefetch(nsIURI* aURI, uint32_t httpStatus) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child
+ // process will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
+ }
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
+
+ for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPrefetch(aURI, httpStatus)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictPreconnect(nsIURI* aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child
+ // process will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPreconnect(aURI);
+ }
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
+
+ for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPreconnect(aURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictDNS(nsIURI* aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child
+ // process will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictDNS(aURI);
+ }
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
+
+ for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictDNS(aURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Predictor::PrefetchListener
+// nsISupports
+NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, nsIStreamListener,
+ nsIRequestObserver)
+
+// nsIRequestObserver
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStartRequest(nsIRequest* aRequest) {
+ mStartTime = TimeStamp::Now();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32, this,
+ static_cast<uint32_t>(aStatusCode)));
+ NS_ENSURE_ARG(aRequest);
+ if (NS_FAILED(aStatusCode)) {
+ return aStatusCode;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME,
+ mStartTime);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
+ if (!cachingChannel) {
+ PREDICTOR_LOG((" Could not get caching channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (NS_SUCCEEDED(rv) && httpStatus == 200) {
+ rv = cachingChannel->ForceCacheEntryValidFor(
+ StaticPrefs::network_predictor_prefetch_force_valid_for());
+ PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32,
+ StaticPrefs::network_predictor_prefetch_force_valid_for(),
+ static_cast<uint32_t>(rv)));
+ } else {
+ rv = cachingChannel->ForceCacheEntryValidFor(0);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200);
+ PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32,
+ static_cast<uint32_t>(rv)));
+ }
+
+ nsAutoCString reqName;
+ rv = aRequest->GetName(reqName);
+ if (NS_FAILED(rv)) {
+ reqName.AssignLiteral("<unknown>");
+ }
+
+ PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus));
+
+ if (mVerifier) {
+ mVerifier->OnPredictPrefetch(mURI, httpStatus);
+ }
+
+ return rv;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ const uint32_t aCount) {
+ uint32_t result;
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
+ &result);
+}
+
+// Miscellaneous Predictor
+
+void Predictor::UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead& requestHead,
+ nsHttpResponseHead* responseHead,
+ nsILoadContextInfo* lci, bool isTracking) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (lci && lci->IsPrivate()) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
+ return;
+ }
+
+ if (!sourceURI || !targetURI) {
+ PREDICTOR_LOG(
+ ("Predictor::UpdateCacheability missing source or target uri"));
+ return;
+ }
+
+ if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
+ return;
+ }
+
+ RefPtr<Predictor> self = sSelf;
+ if (self) {
+ nsAutoCString method;
+ requestHead.Method(method);
+
+ nsAutoCString vary;
+ Unused << responseHead->GetHeader(nsHttp::Vary, vary);
+
+ nsAutoCString cacheControlHeader;
+ Unused << responseHead->GetHeader(nsHttp::Cache_Control,
+ cacheControlHeader);
+ CacheControlParser cacheControl(cacheControlHeader);
+
+ self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method,
+ *lci->OriginAttributesPtr(), isTracking,
+ !vary.IsEmpty(), cacheControl.NoStore());
+ }
+}
+
+void Predictor::UpdateCacheabilityInternal(
+ nsIURI* sourceURI, nsIURI* targetURI, uint32_t httpStatus,
+ const nsCString& method, const OriginAttributes& originAttributes,
+ bool isTracking, bool couldVary, bool isNoStore) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
+
+ nsresult rv;
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
+
+ rv = mCacheStorageService->DiskCacheStorage(lci,
+ getter_AddRefs(cacheDiskStorage));
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" cannot get disk cache storage"));
+ return;
+ }
+
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ RefPtr<Predictor::CacheabilityAction> action =
+ new Predictor::CacheabilityAction(targetURI, httpStatus, method,
+ isTracking, couldVary, isNoStore, this);
+ nsAutoCString uri;
+ targetURI->GetAsciiSpec(uri);
+ PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get()));
+ cacheDiskStorage->AsyncOpenURI(sourceURI, ""_ns, openFlags, action);
+}
+
+NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor);
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry,
+ uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+namespace {
+enum PrefetchDecisionReason {
+ PREFETCHABLE,
+ STATUS_NOT_200,
+ METHOD_NOT_GET,
+ URL_HAS_QUERY_STRING,
+ RESOURCE_IS_TRACKING,
+ RESOURCE_COULD_VARY,
+ RESOURCE_IS_NO_STORE
+};
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry* entry,
+ bool isNew,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // This is being opened read-only, so isNew should always be false
+ MOZ_ASSERT(!isNew);
+
+ PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
+ if (NS_FAILED(result)) {
+ // Nothing to do
+ PREDICTOR_LOG((" nothing to do result=%" PRIX32 " isNew=%d",
+ static_cast<uint32_t>(result), isNew));
+ return NS_OK;
+ }
+
+ nsCString strTargetURI;
+ nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" GetAsciiSpec returned %" PRIx32, static_cast<uint32_t>(rv)));
+ return NS_OK;
+ }
+
+ rv = entry->VisitMetaData(this);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" VisitMetaData returned %" PRIx32, static_cast<uint32_t>(rv)));
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> keysToCheck = std::move(mKeysToCheck),
+ valuesToCheck = std::move(mValuesToCheck);
+
+ bool hasQueryString = false;
+ nsAutoCString query;
+ if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) {
+ hasQueryString = true;
+ }
+
+ MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
+ for (size_t i = 0; i < keysToCheck.Length(); ++i) {
+ const char* key = keysToCheck[i].BeginReading();
+ const char* value = valuesToCheck[i].BeginReading();
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+
+ if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit,
+ flags)) {
+ PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value));
+ continue;
+ }
+
+ if (strTargetURI.Equals(uri)) {
+ bool prefetchable = true;
+ PrefetchDecisionReason reason = PREFETCHABLE;
+
+ if (mHttpStatus != 200) {
+ prefetchable = false;
+ reason = STATUS_NOT_200;
+ } else if (!mMethod.EqualsLiteral("GET")) {
+ prefetchable = false;
+ reason = METHOD_NOT_GET;
+ } else if (hasQueryString) {
+ prefetchable = false;
+ reason = URL_HAS_QUERY_STRING;
+ } else if (mIsTracking) {
+ prefetchable = false;
+ reason = RESOURCE_IS_TRACKING;
+ } else if (mCouldVary) {
+ prefetchable = false;
+ reason = RESOURCE_COULD_VARY;
+ } else if (mIsNoStore) {
+ // We don't set prefetchable = false yet, because we just want to know
+ // what kind of effect this would have on prefetching.
+ reason = RESOURCE_IS_NO_STORE;
+ }
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON,
+ reason);
+
+ if (prefetchable) {
+ PREDICTOR_LOG((" marking %s cacheable", key));
+ flags |= FLAG_PREFETCHABLE;
+ } else {
+ PREDICTOR_LOG((" marking %s uncacheable", key));
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, flags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey,
+ const char* asciiValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToCheck.AppendElement(key);
+ mValuesToCheck.AppendElement(value);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h
new file mode 100644
index 0000000000..a16e09adad
--- /dev/null
+++ b/netwerk/base/Predictor.h
@@ -0,0 +1,458 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Predictor_h
+#define mozilla_net_Predictor_h
+
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+
+#include "nsCOMPtr.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsIDNSListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIStreamListener.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICacheStorage;
+class nsIDNSService;
+class nsIIOService;
+class nsILoadContextInfo;
+class nsITimer;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+class Predictor final : public nsINetworkPredictor,
+ public nsIObserver,
+ public nsISpeculativeConnectionOverrider,
+ public nsIInterfaceRequestor,
+ public nsICacheEntryMetaDataVisitor,
+ public nsINetworkPredictorVerifier {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKPREDICTOR
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSINETWORKPREDICTORVERIFIER
+
+ Predictor();
+
+ nsresult Init();
+ void Shutdown();
+ static nsresult Create(const nsIID& iid, void** result);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ static void UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead& requestHead,
+ nsHttpResponseHead* responseHead,
+ nsILoadContextInfo* lci, bool isTracking);
+
+ private:
+ virtual ~Predictor();
+
+ // Stores callbacks for a child process predictor (for test purposes)
+ nsCOMPtr<nsINetworkPredictorVerifier> mChildVerifier;
+
+ union Reason {
+ PredictorLearnReason mLearn;
+ PredictorPredictReason mPredict;
+ };
+
+ class DNSListener : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ DNSListener() = default;
+
+ private:
+ virtual ~DNSListener() = default;
+ };
+
+ class Action : public nsICacheEntryOpenCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI,
+ nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor);
+ Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI,
+ nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor, uint8_t stackCount);
+
+ static const bool IS_FULL_URI = true;
+ static const bool IS_ORIGIN = false;
+
+ static const bool DO_PREDICT = true;
+ static const bool DO_LEARN = false;
+
+ private:
+ virtual ~Action() = default;
+
+ bool mFullUri : 1;
+ bool mPredict : 1;
+ union {
+ PredictorPredictReason mPredictReason;
+ PredictorLearnReason mLearnReason;
+ };
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsCOMPtr<nsIURI> mSourceURI;
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ TimeStamp mStartTime;
+ uint8_t mStackCount;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class CacheabilityAction : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ CacheabilityAction(nsIURI* targetURI, uint32_t httpStatus,
+ const nsCString& method, bool isTracking, bool couldVary,
+ bool isNoStore, Predictor* predictor)
+ : mTargetURI(targetURI),
+ mHttpStatus(httpStatus),
+ mMethod(method),
+ mIsTracking(isTracking),
+ mCouldVary(couldVary),
+ mIsNoStore(isNoStore),
+ mPredictor(predictor) {}
+
+ private:
+ virtual ~CacheabilityAction() = default;
+
+ nsCOMPtr<nsIURI> mTargetURI;
+ uint32_t mHttpStatus;
+ nsCString mMethod;
+ bool mIsTracking;
+ bool mCouldVary;
+ bool mIsNoStore;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCString> mKeysToCheck;
+ nsTArray<nsCString> mValuesToCheck;
+ };
+
+ class Resetter : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor,
+ public nsICacheStorageVisitor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSICACHESTORAGEVISITOR
+
+ explicit Resetter(Predictor* predictor);
+
+ private:
+ virtual ~Resetter() = default;
+
+ void Complete();
+
+ uint32_t mEntriesToVisit;
+ nsTArray<nsCString> mKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCOMPtr<nsIURI>> mURIsToVisit;
+ nsTArray<nsCOMPtr<nsILoadContextInfo>> mInfosToVisit;
+ };
+
+ class SpaceCleaner : public nsICacheEntryMetaDataVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ explicit SpaceCleaner(Predictor* predictor)
+ : mLRUStamp(0), mLRUKeyToDelete(nullptr), mPredictor(predictor) {}
+
+ void Finalize(nsICacheEntry* entry);
+
+ private:
+ virtual ~SpaceCleaner() = default;
+ uint32_t mLRUStamp;
+ const char* mLRUKeyToDelete;
+ nsTArray<nsCString> mLongKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class PrefetchListener : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ PrefetchListener(nsINetworkPredictorVerifier* verifier, nsIURI* uri,
+ Predictor* predictor)
+ : mVerifier(verifier), mURI(uri), mPredictor(predictor) {}
+
+ private:
+ virtual ~PrefetchListener() = default;
+
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<Predictor> mPredictor;
+ TimeStamp mStartTime;
+ };
+
+ // Observer-related stuff
+ nsresult InstallObserver();
+ void RemoveObserver();
+
+ // Service startup utilities
+ void MaybeCleanupOldDBFiles();
+
+ // The guts of prediction
+
+ // This is the top-level driver for doing any prediction that needs
+ // information from the cache. Returns true if any predictions were queued up
+ // * reason - What kind of prediction this is/why this prediction is
+ // happening (pageload, startup)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are predicting based upon - IOW, the URI
+ // that is being loaded or being redirected to
+ // * verifier - used for testing to verify the expected predictions happen
+ // * stackCount - used to ensure we don't recurse too far trying to find the
+ // final redirection in a redirect chain
+ bool PredictInternal(PredictorPredictReason reason, nsICacheEntry* entry,
+ bool isNew, bool fullUri, nsIURI* targetURI,
+ nsINetworkPredictorVerifier* verifier,
+ uint8_t stackCount);
+
+ // Used when predicting because the user's mouse hovered over a link
+ // * targetURI - the URI target of the link
+ // * sourceURI - the URI of the page on which the link appears
+ // * originAttributes - the originAttributes for this prediction
+ // * verifier - used for testing to verify the expected predictions happen
+ void PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used when predicting because a page is being loaded (which may include
+ // being the target of a redirect). All arguments are the same as for
+ // PredictInternal. Returns true if any predictions were queued up.
+ bool PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
+ uint8_t stackCount, bool fullUri,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used when predicting pages that will be used near browser startup. All
+ // arguments are the same as for PredictInternal. Returns true if any
+ // predictions were queued up.
+ bool PredictForStartup(nsICacheEntry* entry, bool fullUri,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Utilities related to prediction
+
+ // Used to update our rolling load count (how many of the last n loads was a
+ // partular resource loaded on?)
+ // * entry - cache entry of page we're loading
+ // * flags - value that contains our rolling count as the top 20 bits (but
+ // we may use fewer than those 20 bits for calculations)
+ // * key - metadata key that we will update on entry
+ // * hitCount - part of the metadata we need to preserve
+ // * lastHit - part of the metadata we need to preserve
+ void UpdateRollingLoadCount(nsICacheEntry* entry, const uint32_t flags,
+ const char* key, const uint32_t hitCount,
+ const uint32_t lastHit);
+
+ // Used to calculate how much to degrade our confidence for all resources
+ // on a particular page, because of how long ago the most recent load of that
+ // page was. Returns a value between 0 (very recent most recent load) and 100
+ // (very distant most recent load)
+ // * lastLoad - time stamp of most recent load of a page
+ int32_t CalculateGlobalDegradation(uint32_t lastLoad);
+
+ // Used to calculate how confident we are that a particular resource will be
+ // used. Returns a value between 0 (no confidence) and 100 (very confident)
+ // * hitCount - number of times this resource has been seen when loading
+ // this page
+ // * hitsPossible - number of times this page has been loaded
+ // * lastHit - timestamp of the last time this resource was seen when
+ // loading this page
+ // * lastPossible - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ int32_t CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation);
+
+ // Used to calculate all confidence values for all resources associated with a
+ // page.
+ // * entry - the cache entry with all necessary information about this page
+ // * referrer - the URI that we are loading (may be null)
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * loadCount - number of times this page has been loaded
+ // * gloablDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * fullUri - whether we're predicting for a full URI or origin-only
+ void CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri);
+
+ enum PrefetchIgnoreReason {
+ PREFETCH_OK,
+ NOT_FULL_URI,
+ NO_REFERRER,
+ MISSED_A_LOAD,
+ PREFETCH_DISABLED,
+ PREFETCH_DISABLED_VIA_COUNT,
+ CONFIDENCE_TOO_LOW
+ };
+
+ // Used to prepare any necessary prediction for a resource on a page
+ // * confidence - value calculated by CalculateConfidence for this resource
+ // * flags - the flags taken from the resource
+ // * uri - the ascii spec of the URI of the resource
+ void SetupPrediction(int32_t confidence, uint32_t flags, const nsCString& uri,
+ PrefetchIgnoreReason reason);
+
+ // Used to kick off a prefetch from RunPredictions if necessary
+ // * uri - the URI to prefetch
+ // * referrer - the URI of the referring page
+ // * originAttributes - the originAttributes of this prefetch
+ // * verifier - used for testing to ensure the expected prefetch happens
+ nsresult Prefetch(nsIURI* uri, nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used to actually perform any predictions set up via SetupPrediction.
+ // Returns true if any predictions were performed.
+ // * referrer - the URI we are predicting from
+ // * originAttributs - the originAttributes we are predicting from
+ // * verifier - used for testing to ensure the expected predictions happen
+ bool RunPredictions(nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used to guess whether a page will redirect to another page or not. Returns
+ // true if a redirection is likely.
+ // * entry - cache entry with all necessary information about this page
+ // * loadCount - number of times this page has been loaded
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * redirectURI - if this returns true, the URI that is likely to be
+ // redirected to, otherwise null
+ bool WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI** redirectURI);
+
+ // The guts of learning information
+
+ // This is the top-level driver for doing any updating of our information in
+ // the cache
+ // * reason - why this learn is happening (pageload, startup, redirect)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are adding to our data - most often a
+ // resource loaded by a page the user navigated to
+ // * sourceURI - the URI that caused targetURI to be loaded, usually the
+ // page the user navigated to
+ void LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
+ bool isNew, bool fullUri, nsIURI* targetURI,
+ nsIURI* sourceURI);
+
+ // Used when learning about a resource loaded by a page
+ // * entry - the cache entry with information that needs updating
+ // * targetURI - the URI of the resource that was loaded by the page
+ void LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI);
+
+ // Used when learning about a redirect from one page to another
+ // * entry - the cache entry of the page that was redirected from
+ // * targetURI - the URI of the redirect target
+ void LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI);
+
+ // Used to learn about pages loaded close to browser startup. This results in
+ // LearnForStartup being called if we are, in fact, near browser startup
+ // * uri - the URI of a page that has been loaded (may not have been near
+ // browser startup)
+ // * fullUri - true if this is a full page uri, false if it's an origin
+ // * originAttributes - the originAttributes for this learning.
+ void MaybeLearnForStartup(nsIURI* uri, bool fullUri,
+ const OriginAttributes& originAttributes);
+
+ // Used in conjunction with MaybeLearnForStartup to learn about pages loaded
+ // close to browser startup
+ // * entry - the cache entry that stores the startup page list
+ // * targetURI - the URI of a page that was loaded near browser startup
+ void LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI);
+
+ // Used to parse the data we store in cache metadata
+ // * key - the cache metadata key
+ // * value - the cache metadata value
+ // * uri - (out) the ascii spec of the URI this metadata entry was about
+ // * hitCount - (out) the number of times this URI has been seen
+ // * lastHit - (out) timestamp of the last time this URI was seen
+ // * flags - (out) flags for this metadata entry
+ bool ParseMetaDataEntry(const char* key, const char* value, nsCString& uri,
+ uint32_t& hitCount, uint32_t& lastHit,
+ uint32_t& flags);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ void UpdateCacheabilityInternal(nsIURI* sourceURI, nsIURI* targetURI,
+ uint32_t httpStatus, const nsCString& method,
+ const OriginAttributes& originAttributes,
+ bool isTracking, bool couldVary,
+ bool isNoStore);
+
+ // Gets the pref value and clamps it within the acceptable range.
+ uint32_t ClampedPrefetchRollingLoadCount();
+
+ // Our state
+ bool mInitialized{false};
+
+ nsTArray<nsCString> mKeysToOperateOn;
+ nsTArray<nsCString> mValuesToOperateOn;
+
+ nsCOMPtr<nsICacheStorageService> mCacheStorageService;
+
+ nsCOMPtr<nsISpeculativeConnect> mSpeculativeService;
+
+ nsCOMPtr<nsIURI> mStartupURI;
+ uint32_t mStartupTime{0};
+ uint32_t mLastStartupTime{0};
+ int32_t mStartupCount{1};
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+
+ RefPtr<DNSListener> mDNSListener;
+
+ nsTArray<nsCOMPtr<nsIURI>> mPrefetches;
+ nsTArray<nsCOMPtr<nsIURI>> mPreconnects;
+ nsTArray<nsCOMPtr<nsIURI>> mPreresolves;
+
+ static Predictor* sSelf;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Predictor_h
diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h
new file mode 100644
index 0000000000..a2e224f092
--- /dev/null
+++ b/netwerk/base/PrivateBrowsingChannel.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_PrivateBrowsingChannel_h__
+#define mozilla_net_PrivateBrowsingChannel_h__
+
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsCOMPtr.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsNetUtil.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+template <class Channel>
+class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel {
+ public:
+ PrivateBrowsingChannel()
+ : mPrivateBrowsingOverriden(false), mPrivateBrowsing(false) {}
+
+ NS_IMETHOD SetPrivate(bool aPrivate) override {
+ // Make sure that we don't have a load context
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(static_cast<Channel*>(this), loadContext);
+ MOZ_ASSERT(!loadContext);
+ if (loadContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPrivateBrowsingOverriden = true;
+ mPrivateBrowsing = aPrivate;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetIsChannelPrivate(bool* aResult) override {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsing;
+ return NS_OK;
+ }
+
+ NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool* aResult) override {
+ NS_ENSURE_ARG_POINTER(aValue);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsingOverriden;
+ if (mPrivateBrowsingOverriden) {
+ *aValue = mPrivateBrowsing;
+ }
+ return NS_OK;
+ }
+
+ // Must be called every time the channel's callbacks or loadGroup is updated
+ void UpdatePrivateBrowsing() {
+ // once marked as private we never go un-private
+ if (mPrivateBrowsing) {
+ return;
+ }
+
+ auto channel = static_cast<Channel*>(this);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(channel, loadContext);
+ if (loadContext) {
+ mPrivateBrowsing = loadContext->UsePrivateBrowsing();
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ mPrivateBrowsing = attrs.mPrivateBrowsingId > 0;
+ }
+
+ bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aCallbacks) {
+ return true;
+ }
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ if (!loadContext) {
+ return true;
+ }
+ MOZ_ASSERT(!mPrivateBrowsingOverriden);
+ return !mPrivateBrowsingOverriden;
+ }
+
+ bool CanSetLoadGroup(nsILoadGroup* aLoadGroup) const {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aLoadGroup) {
+ return true;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ // From this point on, we just hand off the work to CanSetCallbacks,
+ // because the logic is exactly the same.
+ return CanSetCallbacks(callbacks);
+ }
+
+ protected:
+ bool mPrivateBrowsingOverriden;
+ bool mPrivateBrowsing;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ProtocolHandlerInfo.cpp b/netwerk/base/ProtocolHandlerInfo.cpp
new file mode 100644
index 0000000000..432ff965ce
--- /dev/null
+++ b/netwerk/base/ProtocolHandlerInfo.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProtocolHandlerInfo.h"
+#include "StaticComponents.h"
+#include "nsIProtocolHandler.h"
+
+namespace mozilla::net {
+
+uint32_t ProtocolHandlerInfo::StaticProtocolFlags() const {
+ uint32_t flags = mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ return handler->mProtocolFlags;
+ },
+ [&](const RuntimeProtocolHandler& handler) {
+ return handler.mProtocolFlags;
+ });
+#if !IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ MOZ_RELEASE_ASSERT(!(flags & nsIProtocolHandler::ORIGIN_IS_FULL_SPEC),
+ "ORIGIN_IS_FULL_SPEC is unsupported but used");
+#endif
+ return flags;
+}
+
+int32_t ProtocolHandlerInfo::DefaultPort() const {
+ return mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ return handler->mDefaultPort;
+ },
+ [&](const RuntimeProtocolHandler& handler) {
+ return handler.mDefaultPort;
+ });
+}
+
+nsresult ProtocolHandlerInfo::DynamicProtocolFlags(nsIURI* aURI,
+ uint32_t* aFlags) const {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ // If we're querying dynamic flags, we'll need to fetch the actual xpcom
+ // component in order to check them.
+ if (HasDynamicFlags()) {
+ nsCOMPtr<nsIProtocolHandler> handler = Handler();
+ if (nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dynamic =
+ do_QueryInterface(handler)) {
+ nsresult rv = dynamic->GetFlagsForURI(aURI, aFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_DIAGNOSTIC_ASSERT(
+ (StaticProtocolFlags() & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS) ==
+ (*aFlags & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS),
+ "only DYNAMIC_URI_FLAGS may be changed by a "
+ "nsIProtocolHandlerWithDynamicFlags implementation");
+ return NS_OK;
+ }
+ }
+
+ // Otherwise, just check against static flags.
+ *aFlags = StaticProtocolFlags();
+ return NS_OK;
+}
+
+bool ProtocolHandlerInfo::HasDynamicFlags() const {
+ return mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ return handler->mHasDynamicFlags;
+ },
+ [&](const RuntimeProtocolHandler&) { return false; });
+}
+
+already_AddRefed<nsIProtocolHandler> ProtocolHandlerInfo::Handler() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIProtocolHandler> retval;
+ mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ retval = handler->Module().GetService();
+ },
+ [&](const RuntimeProtocolHandler& handler) {
+ retval = handler.mHandler.get();
+ });
+ return retval.forget();
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/ProtocolHandlerInfo.h b/netwerk/base/ProtocolHandlerInfo.h
new file mode 100644
index 0000000000..337dbddcfc
--- /dev/null
+++ b/netwerk/base/ProtocolHandlerInfo.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ProtocolHandlerInfo_h
+#define mozilla_net_ProtocolHandlerInfo_h
+
+#include "mozilla/Variant.h"
+#include "nsProxyRelease.h"
+#include "nsIProtocolHandler.h"
+
+namespace mozilla {
+namespace xpcom {
+struct StaticProtocolHandler;
+}
+
+namespace net {
+
+struct RuntimeProtocolHandler {
+ nsMainThreadPtrHandle<nsIProtocolHandler> mHandler;
+ uint32_t mProtocolFlags;
+ int32_t mDefaultPort;
+};
+
+// Information about a specific protocol handler.
+class ProtocolHandlerInfo {
+ public:
+ explicit ProtocolHandlerInfo(const xpcom::StaticProtocolHandler& aStatic)
+ : mInner(AsVariant(&aStatic)) {}
+ explicit ProtocolHandlerInfo(RuntimeProtocolHandler aDynamic)
+ : mInner(AsVariant(std::move(aDynamic))) {}
+
+ // Returns the statically known protocol-specific flags.
+ // See `nsIProtocolHandler` for valid values.
+ uint32_t StaticProtocolFlags() const;
+
+ // The port that this protocol normally uses.
+ // If a port does not make sense for the protocol (e.g., "about:") then -1
+ // will be returned.
+ int32_t DefaultPort() const;
+
+ // If true, `DynamicProtocolFlags()` may return a different value than
+ // `StaticProtocolFlags()` for flags in `DYNAMIC_URI_FLAGS`, due to a
+ // `nsIProtocolHandlerWithDynamicFlags` implementation.
+ bool HasDynamicFlags() const;
+
+ // Like `StaticProtocolFlags()` but also checks
+ // `nsIProtocolHandlerWithDynamicFlags` for uri-specific flags.
+ //
+ // NOTE: Only safe to call from the main thread.
+ nsresult DynamicProtocolFlags(nsIURI* aURI, uint32_t* aFlags) const
+ MOZ_REQUIRES(sMainThreadCapability);
+
+ // Get the main-thread-only nsIProtocolHandler instance.
+ already_AddRefed<nsIProtocolHandler> Handler() const
+ MOZ_REQUIRES(sMainThreadCapability);
+
+ private:
+ Variant<const xpcom::StaticProtocolHandler*, RuntimeProtocolHandler> mInner;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ProtocolHandlerInfo_h
diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp
new file mode 100644
index 0000000000..a93d040a14
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -0,0 +1,943 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyAutoConfig.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsINamed.h"
+#include "nsThreadUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIURLParser.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+#include "js/CallAndConstruct.h" // JS_CallFunctionName
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/ContextOptions.h"
+#include "js/Initialization.h"
+#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_GetProperty
+#include "js/PropertySpec.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "js/Utility.h"
+#include "js/Warnings.h" // JS::SetWarningReporter
+#include "prnetdb.h"
+#include "nsITimer.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/ProxyAutoConfigChild.h"
+#include "mozilla/net/ProxyAutoConfigParent.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+#include "XPCSelfHostedShmem.h"
+
+namespace mozilla {
+namespace net {
+
+// These are some global helper symbols the PAC format requires that we provide
+// that are initialized as part of the global javascript context used for PAC
+// evaluations. Additionally dnsResolve(host) and myIpAddress() are supplied in
+// the same context but are implemented as c++ helpers. alert(msg) is similarly
+// defined.
+//
+// Per ProxyAutoConfig::Init, this data must be ASCII.
+
+static const char sAsciiPacUtils[] =
+#include "ascii_pac_utils.inc"
+ ;
+
+// sRunning is defined for the helper functions only while the
+// Javascript engine is running and the PAC object cannot be deleted
+// or reset.
+static Atomic<uint32_t, Relaxed>& RunningIndex() {
+ static Atomic<uint32_t, Relaxed> sRunningIndex(0xdeadbeef);
+ return sRunningIndex;
+}
+static ProxyAutoConfig* GetRunning() {
+ MOZ_ASSERT(RunningIndex() != 0xdeadbeef);
+ return static_cast<ProxyAutoConfig*>(PR_GetThreadPrivate(RunningIndex()));
+}
+
+static void SetRunning(ProxyAutoConfig* arg) {
+ MOZ_ASSERT(RunningIndex() != 0xdeadbeef);
+ MOZ_DIAGNOSTIC_ASSERT_IF(!arg, GetRunning() != nullptr);
+ MOZ_DIAGNOSTIC_ASSERT_IF(arg, GetRunning() == nullptr);
+ PR_SetThreadPrivate(RunningIndex(), arg);
+}
+
+// The PACResolver is used for dnsResolve()
+class PACResolver final : public nsIDNSListener,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit PACResolver(nsIEventTarget* aTarget)
+ : mStatus(NS_ERROR_FAILURE),
+ mMainThreadEventTarget(aTarget),
+ mMutex("PACResolver::Mutex") {}
+
+ // nsIDNSListener
+ NS_IMETHOD OnLookupComplete(nsICancelable* request, nsIDNSRecord* record,
+ nsresult status) override {
+ nsCOMPtr<nsITimer> timer;
+ {
+ MutexAutoLock lock(mMutex);
+ timer.swap(mTimer);
+ mRequest = nullptr;
+ }
+
+ if (timer) {
+ timer->Cancel();
+ }
+
+ mStatus = status;
+ mResponse = record;
+ return NS_OK;
+ }
+
+ // nsITimerCallback
+ NS_IMETHOD Notify(nsITimer* timer) override {
+ nsCOMPtr<nsICancelable> request;
+ {
+ MutexAutoLock lock(mMutex);
+ request.swap(mRequest);
+ mTimer = nullptr;
+ }
+ if (request) {
+ request->Cancel(NS_ERROR_NET_TIMEOUT);
+ }
+ return NS_OK;
+ }
+
+ // nsINamed
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("PACResolver");
+ return NS_OK;
+ }
+
+ nsresult mStatus;
+ nsCOMPtr<nsICancelable> mRequest;
+ nsCOMPtr<nsIDNSRecord> mResponse;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+ Mutex mMutex MOZ_UNANNOTATED;
+
+ private:
+ ~PACResolver() = default;
+};
+NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback, nsINamed)
+
+static void PACLogToConsole(nsString& aMessage) {
+ if (XRE_IsSocketProcess()) {
+ auto task = [message(aMessage)]() {
+ SocketProcessChild* child = SocketProcessChild::GetSingleton();
+ if (child) {
+ Unused << child->SendOnConsoleMessage(message);
+ }
+ };
+ if (NS_IsMainThread()) {
+ task();
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction("PACLogToConsole", task));
+ }
+ return;
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService) return;
+
+ consoleService->LogStringMessage(aMessage.get());
+}
+
+// Javascript errors and warnings are logged to the main error console
+static void PACLogErrorOrWarning(const nsAString& aKind,
+ JSErrorReport* aReport) {
+ nsString formattedMessage(u"PAC Execution "_ns);
+ formattedMessage += aKind;
+ formattedMessage += u": "_ns;
+ if (aReport->message()) {
+ formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
+ }
+ formattedMessage += u" ["_ns;
+ formattedMessage.Append(aReport->linebuf(), aReport->linebufLength());
+ formattedMessage += u"]"_ns;
+ PACLogToConsole(formattedMessage);
+}
+
+static void PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) {
+ MOZ_ASSERT(aReport);
+ MOZ_ASSERT(aReport->isWarning());
+
+ PACLogErrorOrWarning(u"Warning"_ns, aReport);
+}
+
+class MOZ_STACK_CLASS AutoPACErrorReporter {
+ JSContext* mCx;
+
+ public:
+ explicit AutoPACErrorReporter(JSContext* aCx) : mCx(aCx) {}
+ ~AutoPACErrorReporter() {
+ if (!JS_IsExceptionPending(mCx)) {
+ return;
+ }
+ JS::ExceptionStack exnStack(mCx);
+ if (!JS::StealPendingExceptionStack(mCx, &exnStack)) {
+ return;
+ }
+
+ JS::ErrorReportBuilder report(mCx);
+ if (!report.init(mCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
+ JS_ClearPendingException(mCx);
+ return;
+ }
+
+ PACLogErrorOrWarning(u"Error"_ns, report.report());
+ }
+};
+
+// timeout of 0 means the normal necko timeout strategy, otherwise the dns
+// request will be canceled after aTimeout milliseconds
+static bool PACResolve(const nsACString& aHostName, NetAddr* aNetAddr,
+ unsigned int aTimeout) {
+ if (!GetRunning()) {
+ NS_WARNING("PACResolve without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout);
+}
+
+ProxyAutoConfig::ProxyAutoConfig()
+
+{
+ MOZ_COUNT_CTOR(ProxyAutoConfig);
+}
+
+bool ProxyAutoConfig::ResolveAddress(const nsACString& aHostName,
+ NetAddr* aNetAddr, unsigned int aTimeout) {
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) return false;
+
+ RefPtr<PACResolver> helper = new PACResolver(mMainThreadEventTarget);
+ OriginAttributes attrs;
+
+ // When the PAC script attempts to resolve a domain, we must make sure we
+ // don't use TRR, otherwise the TRR channel might also attempt to resolve
+ // a name and we'll have a deadlock.
+ nsIDNSService::DNSFlags flags =
+ nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::GetFlagsFromTRRMode(nsIRequest::TRR_DISABLED_MODE);
+
+ if (NS_FAILED(dns->AsyncResolveNative(
+ aHostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, nullptr,
+ helper, GetCurrentSerialEventTarget(), attrs,
+ getter_AddRefs(helper->mRequest)))) {
+ return false;
+ }
+
+ if (aTimeout && helper->mRequest) {
+ if (!mTimer) mTimer = NS_NewTimer();
+ if (mTimer) {
+ mTimer->SetTarget(mMainThreadEventTarget);
+ mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
+ helper->mTimer = mTimer;
+ }
+ }
+
+ // Spin the event loop of the pac thread until lookup is complete.
+ // nsPACman is responsible for keeping a queue and only allowing
+ // one PAC execution at a time even when it is called re-entrantly.
+ SpinEventLoopUntil("ProxyAutoConfig::ResolveAddress"_ns, [&, helper, this]() {
+ if (!helper->mRequest) {
+ return true;
+ }
+ if (this->mShutdown) {
+ NS_WARNING("mShutdown set with PAC request not cancelled");
+ MOZ_ASSERT(NS_FAILED(helper->mStatus));
+ return true;
+ }
+ return false;
+ });
+
+ if (NS_FAILED(helper->mStatus)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(helper->mResponse);
+ return !(!rec || NS_FAILED(rec->GetNextAddr(0, aNetAddr)));
+}
+
+static bool PACResolveToString(const nsACString& aHostName,
+ nsCString& aDottedDecimal,
+ unsigned int aTimeout) {
+ NetAddr netAddr;
+ if (!PACResolve(aHostName, &netAddr, aTimeout)) return false;
+
+ char dottedDecimal[128];
+ if (!netAddr.ToStringBuffer(dottedDecimal, sizeof(dottedDecimal))) {
+ return false;
+ }
+
+ aDottedDecimal.Assign(dottedDecimal);
+ return true;
+}
+
+// dnsResolve(host) javascript implementation
+static bool PACDnsResolve(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!args.requireAtLeast(cx, "dnsResolve", 1)) return false;
+
+ // Previously we didn't check the type of the argument, so just converted it
+ // to string. A badly written PAC file oculd pass null or undefined here
+ // which could lead to odd results if there are any hosts called "null"
+ // on the network. See bug 1724345 comment 6.
+ if (!args[0].isString()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ JS::Rooted<JSString*> arg1(cx);
+ arg1 = args[0].toString();
+
+ nsAutoJSString hostName;
+ nsAutoCString dottedDecimal;
+
+ if (!hostName.init(cx, arg1)) return false;
+ if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) {
+ JSString* dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ args.rval().setString(dottedDecimalString);
+ } else {
+ args.rval().setNull();
+ }
+
+ return true;
+}
+
+// myIpAddress() javascript implementation
+static bool PACMyIpAddress(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!GetRunning()) {
+ NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->MyIPAddress(args);
+}
+
+// proxyAlert(msg) javascript implementation
+static bool PACProxyAlert(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "alert", 1)) return false;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1) return false;
+
+ nsAutoJSString message;
+ if (!message.init(cx, arg1)) return false;
+
+ nsAutoString alertMessage;
+ alertMessage.AssignLiteral(u"PAC-alert: ");
+ alertMessage.Append(message);
+ PACLogToConsole(alertMessage);
+
+ args.rval().setUndefined(); /* return undefined */
+ return true;
+}
+
+static const JSFunctionSpec PACGlobalFunctions[] = {
+ JS_FN("dnsResolve", PACDnsResolve, 1, 0),
+
+ // a global "var pacUseMultihomedDNS = true;" will change behavior
+ // of myIpAddress to actively use DNS
+ JS_FN("myIpAddress", PACMyIpAddress, 0, 0),
+ JS_FN("alert", PACProxyAlert, 1, 0), JS_FS_END};
+
+// JSContextWrapper is a c++ object that manages the context for the JS engine
+// used on the PAC thread. It is initialized and destroyed on the PAC thread.
+class JSContextWrapper {
+ public:
+ static JSContextWrapper* Create(uint32_t aExtraHeapSize) {
+ JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes + aExtraHeapSize);
+ if (NS_WARN_IF(!cx)) return nullptr;
+
+ JS::ContextOptionsRef(cx).setDisableIon().setDisableEvalSecurityChecks();
+
+ JSContextWrapper* entry = new JSContextWrapper(cx);
+ if (NS_FAILED(entry->Init())) {
+ delete entry;
+ return nullptr;
+ }
+
+ return entry;
+ }
+
+ JSContext* Context() const { return mContext; }
+
+ JSObject* Global() const { return mGlobal; }
+
+ ~JSContextWrapper() {
+ mGlobal = nullptr;
+
+ MOZ_COUNT_DTOR(JSContextWrapper);
+
+ if (mContext) {
+ JS_DestroyContext(mContext);
+ }
+ }
+
+ void SetOK() { mOK = true; }
+
+ bool IsOK() { return mOK; }
+
+ private:
+ JSContext* mContext;
+ JS::PersistentRooted<JSObject*> mGlobal;
+ bool mOK;
+
+ static const JSClass sGlobalClass;
+
+ explicit JSContextWrapper(JSContext* cx)
+ : mContext(cx), mGlobal(cx, nullptr), mOK(false) {
+ MOZ_COUNT_CTOR(JSContextWrapper);
+ }
+
+ nsresult Init() {
+ /*
+ * Not setting this will cause JS_CHECK_RECURSION to report false
+ * positives
+ */
+ JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
+
+ JS::SetWarningReporter(mContext, PACWarningReporter);
+
+ // When available, set the self-hosted shared memory to be read, so that
+ // we can decode the self-hosted content instead of parsing it.
+ {
+ auto& shm = xpc::SelfHostedShmem::GetSingleton();
+ JS::SelfHostedCache selfHostedContent = shm.Content();
+
+ if (!JS::InitSelfHostedCode(mContext, selfHostedContent)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ JS::RealmOptions options;
+ options.creationOptions().setNewCompartmentInSystemZone();
+ options.behaviors()
+ .setClampAndJitterTime(false)
+ .setReduceTimerPrecisionCallerType(
+ RTPCallerTypeToToken(RTPCallerType::Normal));
+ mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
+ JS::DontFireOnNewGlobalHook, options);
+ if (!mGlobal) {
+ JS_ClearPendingException(mContext);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ JS::Rooted<JSObject*> global(mContext, mGlobal);
+
+ JSAutoRealm ar(mContext, global);
+ AutoPACErrorReporter aper(mContext);
+ if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS_FireOnNewGlobalObject(mContext, global);
+
+ return NS_OK;
+ }
+};
+
+const JSClass JSContextWrapper::sGlobalClass = {"PACResolutionThreadGlobal",
+ JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+
+void ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) {
+ RunningIndex() = index;
+}
+
+nsresult ProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData,
+ bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) {
+ mShutdown = false; // Shutdown needs to be called prior to destruction
+
+ mPACURI = aPACURI;
+
+ // The full PAC script data is the concatenation of 1) the various functions
+ // exposed to PAC scripts in |sAsciiPacUtils| and 2) the user-provided PAC
+ // script data. Historically this was single-byte Latin-1 text (usually just
+ // ASCII, but bug 296163 has a real-world Latin-1 example). We now support
+ // UTF-8 if the full data validates as UTF-8, before falling back to Latin-1.
+ // (Technically this is a breaking change: intentional Latin-1 scripts that
+ // happen to be valid UTF-8 may have different behavior. We assume such cases
+ // are vanishingly rare.)
+ //
+ // Supporting both UTF-8 and Latin-1 requires that the functions exposed to
+ // PAC scripts be both UTF-8- and Latin-1-compatible: that is, they must be
+ // ASCII.
+ mConcatenatedPACData = sAsciiPacUtils;
+ if (!mConcatenatedPACData.Append(aPACScriptData, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mIncludePath = aIncludePath;
+ mExtraHeapSize = aExtraHeapSize;
+ mMainThreadEventTarget = aEventTarget;
+
+ if (!GetRunning()) return SetupJS();
+
+ mJSNeedsSetup = true;
+ return NS_OK;
+}
+
+nsresult ProxyAutoConfig::SetupJS() {
+ mJSNeedsSetup = false;
+ MOZ_DIAGNOSTIC_ASSERT(!GetRunning(), "JIT is running");
+ if (GetRunning()) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+#if defined(XP_MACOSX)
+ nsMacUtilsImpl::EnableTCSMIfAvailable();
+#endif
+
+ delete mJSContext;
+ mJSContext = nullptr;
+
+ if (mConcatenatedPACData.IsEmpty()) return NS_ERROR_FAILURE;
+
+ NS_GetCurrentThread()->SetCanInvokeJS(true);
+
+ mJSContext = JSContextWrapper::Create(mExtraHeapSize);
+ if (!mJSContext) return NS_ERROR_FAILURE;
+
+ JSContext* cx = mJSContext->Context();
+ JSAutoRealm ar(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // check if this is a data: uri so that we don't spam the js console with
+ // huge meaningless strings. this is not on the main thread, so it can't
+ // use nsIURI scheme methods
+ bool isDataURI =
+ nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5);
+
+ SetRunning(this);
+
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+
+ auto CompilePACScript = [this](JSContext* cx) -> JSScript* {
+ JS::CompileOptions options(cx);
+ options.setSkipFilenameValidation(true);
+ options.setFileAndLine(this->mPACURI.get(), 1);
+
+ // Per ProxyAutoConfig::Init, compile as UTF-8 if the full data is UTF-8,
+ // and otherwise inflate Latin-1 to UTF-16 and compile that.
+ const char* scriptData = this->mConcatenatedPACData.get();
+ size_t scriptLength = this->mConcatenatedPACData.Length();
+ if (mozilla::IsUtf8(mozilla::Span(scriptData, scriptLength))) {
+ JS::SourceText<Utf8Unit> srcBuf;
+ if (!srcBuf.init(cx, scriptData, scriptLength,
+ JS::SourceOwnership::Borrowed)) {
+ return nullptr;
+ }
+
+ return JS::Compile(cx, options, srcBuf);
+ }
+
+ // nsReadableUtils.h says that "ASCII" is a misnomer "for legacy reasons",
+ // and this handles not just ASCII but Latin-1 too.
+ NS_ConvertASCIItoUTF16 inflated(this->mConcatenatedPACData);
+
+ JS::SourceText<char16_t> source;
+ if (!source.init(cx, inflated.get(), inflated.Length(),
+ JS::SourceOwnership::Borrowed)) {
+ return nullptr;
+ }
+
+ return JS::Compile(cx, options, source);
+ };
+
+ JS::Rooted<JSScript*> script(cx, CompilePACScript(cx));
+ if (!script || !JS_ExecuteScript(cx, script)) {
+ nsString alertMessage(u"PAC file failed to install from "_ns);
+ if (isDataURI) {
+ alertMessage += u"data: URI"_ns;
+ } else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+ SetRunning(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ SetRunning(nullptr);
+
+ mJSContext->SetOK();
+ nsString alertMessage(u"PAC file installed from "_ns);
+ if (isDataURI) {
+ alertMessage += u"data: URI"_ns;
+ } else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+
+ // we don't need these now
+ mConcatenatedPACData.Truncate();
+ mPACURI.Truncate();
+
+ return NS_OK;
+}
+
+void ProxyAutoConfig::GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) {
+ nsAutoCString result;
+ nsresult status = GetProxyForURI(aTestURI, aTestHost, result);
+ aCallback(status, result);
+}
+
+nsresult ProxyAutoConfig::GetProxyForURI(const nsACString& aTestURI,
+ const nsACString& aTestHost,
+ nsACString& result) {
+ if (mJSNeedsSetup) SetupJS();
+
+ if (!mJSContext || !mJSContext->IsOK()) return NS_ERROR_NOT_AVAILABLE;
+
+ JSContext* cx = mJSContext->Context();
+ JSAutoRealm ar(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // the sRunning flag keeps a new PAC file from being installed
+ // while the event loop is spinning on a DNS function. Don't early return.
+ SetRunning(this);
+ mRunningHost = aTestHost;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCString clensedURI(aTestURI);
+
+ if (!mIncludePath) {
+ nsCOMPtr<nsIURLParser> urlParser =
+ do_GetService(NS_STDURLPARSER_CONTRACTID);
+ int32_t pathLen = 0;
+ if (urlParser) {
+ uint32_t schemePos;
+ int32_t schemeLen;
+ uint32_t authorityPos;
+ int32_t authorityLen;
+ uint32_t pathPos;
+ rv = urlParser->ParseURL(aTestURI.BeginReading(), aTestURI.Length(),
+ &schemePos, &schemeLen, &authorityPos,
+ &authorityLen, &pathPos, &pathLen);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ if (pathLen) {
+ // cut off the path but leave the initial slash
+ pathLen--;
+ }
+ aTestURI.Left(clensedURI, aTestURI.Length() - pathLen);
+ }
+ }
+
+ JS::Rooted<JSString*> uriString(
+ cx,
+ JS_NewStringCopyN(cx, clensedURI.BeginReading(), clensedURI.Length()));
+ JS::Rooted<JSString*> hostString(
+ cx, JS_NewStringCopyN(cx, aTestHost.BeginReading(), aTestHost.Length()));
+
+ if (uriString && hostString) {
+ JS::RootedValueArray<2> args(cx);
+ args[0].setString(uriString);
+ args[1].setString(hostString);
+
+ JS::Rooted<JS::Value> rval(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+ bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval);
+
+ if (ok && rval.isString()) {
+ nsAutoJSString pacString;
+ if (pacString.init(cx, rval.toString())) {
+ CopyUTF16toUTF8(pacString, result);
+ rv = NS_OK;
+ }
+ }
+ }
+
+ mRunningHost.Truncate();
+ SetRunning(nullptr);
+ return rv;
+}
+
+void ProxyAutoConfig::GC() {
+ if (!mJSContext || !mJSContext->IsOK()) return;
+
+ JSAutoRealm ar(mJSContext->Context(), mJSContext->Global());
+ JS_MaybeGC(mJSContext->Context());
+}
+
+ProxyAutoConfig::~ProxyAutoConfig() {
+ MOZ_COUNT_DTOR(ProxyAutoConfig);
+ MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor.");
+ NS_ASSERTION(!mJSContext,
+ "~ProxyAutoConfig leaking JS context that "
+ "should have been deleted on pac thread");
+}
+
+void ProxyAutoConfig::Shutdown() {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown");
+
+ if (NS_WARN_IF(GetRunning()) || mShutdown) {
+ return;
+ }
+
+ mShutdown = true;
+ delete mJSContext;
+ mJSContext = nullptr;
+}
+
+bool ProxyAutoConfig::SrcAddress(const NetAddr* remoteAddress,
+ nsCString& localAddress) {
+ PRFileDesc* fd;
+ fd = PR_OpenUDPSocket(remoteAddress->raw.family);
+ if (!fd) return false;
+
+ PRNetAddr prRemoteAddress;
+ NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress);
+ if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PRNetAddr localName;
+ if (PR_GetSockName(fd, &localName) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PR_Close(fd);
+
+ char dottedDecimal[128];
+ if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) !=
+ PR_SUCCESS) {
+ return false;
+ }
+
+ localAddress.Assign(dottedDecimal);
+
+ return true;
+}
+
+// hostName is run through a dns lookup and then a udp socket is connected
+// to the result. If that all works, the local IP address of the socket is
+// returned to the javascript caller and |*aResult| is set to true. Otherwise
+// |*aResult| is set to false.
+bool ProxyAutoConfig::MyIPAddressTryHost(const nsACString& hostName,
+ unsigned int timeout,
+ const JS::CallArgs& aArgs,
+ bool* aResult) {
+ *aResult = false;
+
+ NetAddr remoteAddress;
+ nsAutoCString localDottedDecimal;
+ JSContext* cx = mJSContext->Context();
+
+ if (PACResolve(hostName, &remoteAddress, timeout) &&
+ SrcAddress(&remoteAddress, localDottedDecimal)) {
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ *aResult = true;
+ aArgs.rval().setString(dottedDecimalString);
+ }
+ return true;
+}
+
+bool ProxyAutoConfig::MyIPAddress(const JS::CallArgs& aArgs) {
+ nsAutoCString remoteDottedDecimal;
+ nsAutoCString localDottedDecimal;
+ JSContext* cx = mJSContext->Context();
+ JS::Rooted<JS::Value> v(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+
+ bool useMultihomedDNS =
+ JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) &&
+ !v.isUndefined() && ToBoolean(v);
+
+ // first, lookup the local address of a socket connected
+ // to the host of uri being resolved by the pac file. This is
+ // v6 safe.. but is the last step like that
+ bool rvalAssigned = false;
+ if (useMultihomedDNS) {
+ if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+ } else {
+ // we can still do the fancy multi homing thing if the host is a literal
+ if (HostIsIPLiteral(mRunningHost) &&
+ (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned)) {
+ return rvalAssigned;
+ }
+ }
+
+ // next, look for a route to a public internet address that doesn't need DNS.
+ // This is the google anycast dns address, but it doesn't matter if it
+ // remains operable (as we don't contact it) as long as the address stays
+ // in commonly routed IP address space.
+ remoteDottedDecimal.AssignLiteral("8.8.8.8");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // finally, use the old algorithm based on the local hostname
+ nsAutoCString hostName;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ // without multihomedDNS use such a short timeout that we are basically
+ // just looking at the cache for raw dotted decimals
+ uint32_t timeout = useMultihomedDNS ? kTimeout : 1;
+ if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
+ PACResolveToString(hostName, localDottedDecimal, timeout)) {
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+ }
+
+ // next try a couple RFC 1918 variants.. maybe there is a
+ // local route
+ remoteDottedDecimal.AssignLiteral("192.168.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // more RFC 1918
+ remoteDottedDecimal.AssignLiteral("10.0.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // who knows? let's fallback to localhost
+ localDottedDecimal.AssignLiteral("127.0.0.1");
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+}
+
+RemoteProxyAutoConfig::RemoteProxyAutoConfig() = default;
+
+RemoteProxyAutoConfig::~RemoteProxyAutoConfig() = default;
+
+nsresult RemoteProxyAutoConfig::Init(nsIThread* aPACThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ SocketProcessParent* socketProcessParent =
+ SocketProcessParent::GetSingleton();
+ if (!socketProcessParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ ipc::Endpoint<PProxyAutoConfigParent> parent;
+ ipc::Endpoint<PProxyAutoConfigChild> child;
+ nsresult rv = PProxyAutoConfig::CreateEndpoints(
+ base::GetCurrentProcId(), socketProcessParent->OtherPid(), &parent,
+ &child);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Unused << socketProcessParent->SendInitProxyAutoConfigChild(std::move(child));
+ mProxyAutoConfigParent = new ProxyAutoConfigParent();
+ return aPACThread->Dispatch(
+ NS_NewRunnableFunction("ProxyAutoConfigParent::ProxyAutoConfigParent",
+ [proxyAutoConfigParent(mProxyAutoConfigParent),
+ endpoint{std::move(parent)}]() mutable {
+ proxyAutoConfigParent->Init(std::move(endpoint));
+ }));
+}
+
+nsresult RemoteProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData,
+ bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget*) {
+ Unused << mProxyAutoConfigParent->SendConfigurePAC(
+ aPACURI, aPACScriptData, aIncludePath, aExtraHeapSize);
+ return NS_OK;
+}
+
+void RemoteProxyAutoConfig::Shutdown() { mProxyAutoConfigParent->Close(); }
+
+void RemoteProxyAutoConfig::GC() {
+ // Do nothing. GC would be performed when there is not pending query in socket
+ // process.
+}
+
+void RemoteProxyAutoConfig::GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) {
+ if (!mProxyAutoConfigParent->CanSend()) {
+ return;
+ }
+
+ mProxyAutoConfigParent->SendGetProxyForURI(
+ aTestURI, aTestHost,
+ [aCallback](std::tuple<nsresult, nsCString>&& aResult) {
+ auto [status, result] = aResult;
+ aCallback(status, result);
+ },
+ [aCallback](mozilla::ipc::ResponseRejectReason&& aReason) {
+ aCallback(NS_ERROR_FAILURE, ""_ns);
+ });
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h
new file mode 100644
index 0000000000..e01450eadd
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ProxyAutoConfig_h__
+#define ProxyAutoConfig_h__
+
+#include <functional>
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+class nsITimer;
+class nsIThread;
+namespace JS {
+class CallArgs;
+} // namespace JS
+
+namespace mozilla {
+namespace net {
+
+class JSContextWrapper;
+class ProxyAutoConfigParent;
+union NetAddr;
+
+class ProxyAutoConfigBase {
+ public:
+ virtual ~ProxyAutoConfigBase() = default;
+ virtual nsresult Init(nsIThread* aPACThread) { return NS_OK; }
+ virtual nsresult ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData,
+ bool aIncludePath, uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) = 0;
+ virtual void SetThreadLocalIndex(uint32_t index) {}
+ virtual void Shutdown() = 0;
+ virtual void GC() = 0;
+ virtual void GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) = 0;
+};
+
+// The ProxyAutoConfig class is meant to be created and run on a
+// non main thread. It synchronously resolves PAC files by blocking that
+// thread and running nested event loops. GetProxyForURI is not re-entrant.
+
+class ProxyAutoConfig : public ProxyAutoConfigBase {
+ public:
+ ProxyAutoConfig();
+ virtual ~ProxyAutoConfig();
+
+ nsresult ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData, bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) override;
+ void SetThreadLocalIndex(uint32_t index) override;
+ void Shutdown() override;
+ void GC() override;
+ bool MyIPAddress(const JS::CallArgs& aArgs);
+ bool ResolveAddress(const nsACString& aHostName, NetAddr* aNetAddr,
+ unsigned int aTimeout);
+
+ /**
+ * Get the proxy string for the specified URI. The proxy string is
+ * given by the following:
+ *
+ * result = proxy-spec *( proxy-sep proxy-spec )
+ * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port]
+ * direct-type = "DIRECT"
+ * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5"
+ * proxy-sep = ";" LWS
+ * proxy-host = hostname | ipv4-address-literal
+ * proxy-port = <any 16-bit unsigned integer>
+ * LWS = *( SP | HT )
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ *
+ * NOTE: direct-type and proxy-type are case insensitive
+ * NOTE: SOCKS implies SOCKS4
+ *
+ * Examples:
+ * "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT"
+ * "SOCKS socksproxy"
+ * "DIRECT"
+ *
+ * XXX add support for IPv6 address literals.
+ * XXX quote whatever the official standard is for PAC.
+ *
+ * @param aTestURI
+ * The URI as an ASCII string to test.
+ * @param aTestHost
+ * The ASCII hostname to test.
+ *
+ * @param result
+ * result string as defined above.
+ */
+ nsresult GetProxyForURI(const nsACString& aTestURI,
+ const nsACString& aTestHost, nsACString& result);
+
+ void GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) override;
+
+ private:
+ // allow 665ms for myipaddress dns queries. That's 95th percentile.
+ const static unsigned int kTimeout = 665;
+
+ // used to compile the PAC file and setup the execution context
+ nsresult SetupJS();
+
+ bool SrcAddress(const NetAddr* remoteAddress, nsCString& localAddress);
+ bool MyIPAddressTryHost(const nsACString& hostName, unsigned int timeout,
+ const JS::CallArgs& aArgs, bool* aResult);
+
+ JSContextWrapper* mJSContext{nullptr};
+ bool mJSNeedsSetup{false};
+ bool mShutdown{true};
+ nsCString mConcatenatedPACData;
+ nsCString mPACURI;
+ bool mIncludePath{false};
+ uint32_t mExtraHeapSize{0};
+ nsCString mRunningHost;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+};
+
+class RemoteProxyAutoConfig : public ProxyAutoConfigBase {
+ public:
+ RemoteProxyAutoConfig();
+ virtual ~RemoteProxyAutoConfig();
+
+ nsresult Init(nsIThread* aPACThread) override;
+ nsresult ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData, bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) override;
+ void Shutdown() override;
+ void GC() override;
+ void GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) override;
+
+ private:
+ RefPtr<ProxyAutoConfigParent> mProxyAutoConfigParent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ProxyAutoConfig_h__
diff --git a/netwerk/base/ProxyConfig.h b/netwerk/base/ProxyConfig.h
new file mode 100644
index 0000000000..2deb1dd2a1
--- /dev/null
+++ b/netwerk/base/ProxyConfig.h
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_netwerk_base_proxy_config_h
+#define mozilla_netwerk_base_proxy_config_h
+
+#include <map>
+
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+// NOTE: This file is inspired by Chromium's code.
+// https://source.chromium.org/chromium/chromium/src/+/main:net/proxy_resolution/proxy_config.h.
+
+namespace mozilla {
+namespace net {
+
+// ProxyServer stores the {type, host, port} of a proxy server.
+// ProxyServer is immutable.
+class ProxyServer final {
+ public:
+ enum class ProxyType {
+ DIRECT = 0,
+ HTTP,
+ HTTPS,
+ SOCKS,
+ SOCKS4,
+ SOCKS5,
+ FTP,
+ // DEFAULT is a special type used on windows only.
+ DEFAULT
+ };
+
+ ProxyServer() = default;
+
+ ProxyServer(ProxyType aType, const nsACString& aHost, int32_t aPort)
+ : mType(aType), mHost(aHost), mPort(aPort) {}
+
+ const nsCString& Host() const { return mHost; }
+
+ int32_t Port() const { return mPort; }
+
+ ProxyType Type() const { return mType; }
+
+ void ToHostAndPortStr(nsACString& aOutput) {
+ aOutput.Truncate();
+ if (mType == ProxyType::DIRECT) {
+ return;
+ }
+
+ aOutput.Assign(mHost);
+ if (mPort != -1) {
+ aOutput.Append(':');
+ aOutput.AppendInt(mPort);
+ }
+ }
+
+ bool operator==(const ProxyServer& aOther) const {
+ return mType == aOther.mType && mHost == aOther.mHost &&
+ mPort == aOther.mPort;
+ }
+
+ bool operator!=(const ProxyServer& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ ProxyType mType{ProxyType::DIRECT};
+ nsCString mHost;
+ int32_t mPort{-1};
+};
+
+// This class includes the information about proxy configuration.
+// It contains enabled proxy servers, exception list, and the url of PAC
+// script.
+class ProxyConfig {
+ public:
+ struct ProxyRules {
+ ProxyRules() = default;
+ ~ProxyRules() = default;
+
+ std::map<ProxyServer::ProxyType, ProxyServer> mProxyServers;
+ };
+
+ struct ProxyBypassRules {
+ ProxyBypassRules() = default;
+ ~ProxyBypassRules() = default;
+
+ CopyableTArray<nsCString> mExceptions;
+ };
+
+ ProxyConfig() = default;
+ ProxyConfig(const ProxyConfig& config);
+ ~ProxyConfig() = default;
+
+ ProxyRules& Rules() { return mRules; }
+
+ const ProxyRules& Rules() const { return mRules; }
+
+ ProxyBypassRules& ByPassRules() { return mBypassRules; }
+
+ const ProxyBypassRules& ByPassRules() const { return mBypassRules; }
+
+ void SetPACUrl(const nsACString& aUrl) { mPACUrl = aUrl; }
+
+ const nsCString& PACUrl() const { return mPACUrl; }
+
+ static ProxyServer::ProxyType ToProxyType(const char* aType) {
+ if (!aType) {
+ return ProxyServer::ProxyType::DIRECT;
+ }
+
+ if (nsCRT::strcasecmp(aType, "http") == 0) {
+ return ProxyServer::ProxyType::HTTP;
+ }
+ if (nsCRT::strcasecmp(aType, "https") == 0) {
+ return ProxyServer::ProxyType::HTTPS;
+ }
+ if (nsCRT::strcasecmp(aType, "socks") == 0) {
+ return ProxyServer::ProxyType::SOCKS;
+ }
+ if (nsCRT::strcasecmp(aType, "socks4") == 0) {
+ return ProxyServer::ProxyType::SOCKS4;
+ }
+ if (nsCRT::strcasecmp(aType, "socks5") == 0) {
+ return ProxyServer::ProxyType::SOCKS5;
+ }
+ if (nsCRT::strcasecmp(aType, "ftp") == 0) {
+ return ProxyServer::ProxyType::FTP;
+ }
+
+ return ProxyServer::ProxyType::DIRECT;
+ }
+
+ static void SetProxyResult(const char* aType, const nsACString& aHostPort,
+ nsACString& aResult) {
+ aResult.AssignASCII(aType);
+ aResult.Append(' ');
+ aResult.Append(aHostPort);
+ }
+
+ static void SetProxyResultDirect(nsACString& aResult) {
+ // For whatever reason, a proxy is not to be used.
+ aResult.AssignLiteral("DIRECT");
+ }
+
+ static void ProxyStrToResult(const nsACString& aSpecificProxy,
+ const nsACString& aDefaultProxy,
+ const nsACString& aSocksProxy,
+ nsACString& aResult) {
+ if (!aSpecificProxy.IsEmpty()) {
+ SetProxyResult("PROXY", aSpecificProxy,
+ aResult); // Protocol-specific proxy.
+ } else if (!aDefaultProxy.IsEmpty()) {
+ SetProxyResult("PROXY", aDefaultProxy, aResult); // Default proxy.
+ } else if (!aSocksProxy.IsEmpty()) {
+ SetProxyResult("SOCKS", aSocksProxy, aResult); // SOCKS proxy.
+ } else {
+ SetProxyResultDirect(aResult); // Direct connection.
+ }
+ }
+
+ void GetProxyString(const nsACString& aScheme, nsACString& aResult) {
+ SetProxyResultDirect(aResult);
+
+ nsAutoCString specificProxy;
+ nsAutoCString defaultProxy;
+ nsAutoCString socksProxy;
+ nsAutoCString prefix;
+ ToLowerCase(aScheme, prefix);
+ ProxyServer::ProxyType type = ProxyConfig::ToProxyType(prefix.get());
+ for (auto& [key, value] : mRules.mProxyServers) {
+ // Break the loop if we found a specific proxy.
+ if (key == type) {
+ value.ToHostAndPortStr(specificProxy);
+ break;
+ } else if (key == ProxyServer::ProxyType::DEFAULT) {
+ value.ToHostAndPortStr(defaultProxy);
+ } else if (key == ProxyServer::ProxyType::SOCKS) {
+ value.ToHostAndPortStr(socksProxy);
+ }
+ }
+
+ ProxyStrToResult(specificProxy, defaultProxy, socksProxy, aResult);
+ }
+
+ private:
+ nsCString mPACUrl;
+ ProxyRules mRules;
+ ProxyBypassRules mBypassRules;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_base_proxy_config_h
diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp
new file mode 100644
index 0000000000..9aa687ed88
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.cpp
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RedirectChannelRegistrar.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<RedirectChannelRegistrar> RedirectChannelRegistrar::gSingleton;
+
+NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar)
+
+RedirectChannelRegistrar::RedirectChannelRegistrar()
+ : mRealChannels(32),
+ mParentChannels(32),
+ mLock("RedirectChannelRegistrar") {
+ MOZ_ASSERT(!gSingleton);
+}
+
+// static
+already_AddRefed<nsIRedirectChannelRegistrar>
+RedirectChannelRegistrar::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gSingleton) {
+ gSingleton = new RedirectChannelRegistrar();
+ ClearOnShutdown(&gSingleton);
+ }
+ return do_AddRef(gSingleton);
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::RegisterChannel(nsIChannel* channel, uint64_t id) {
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.InsertOrUpdate(id, channel);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetRegisteredChannel(uint64_t id,
+ nsIChannel** _retval) {
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::LinkChannels(uint64_t id, nsIParentChannel* channel,
+ nsIChannel** _retval) {
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE;
+
+ mParentChannels.InsertOrUpdate(id, channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetParentChannel(uint64_t id,
+ nsIParentChannel** _retval) {
+ MutexAutoLock lock(mLock);
+
+ if (!mParentChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::DeregisterChannels(uint64_t id) {
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.Remove(id);
+ mParentChannels.Remove(id);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h
new file mode 100644
index 0000000000..5041726043
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.h
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RedirectChannelRegistrar_h__
+#define RedirectChannelRegistrar_h__
+
+#include "nsIRedirectChannelRegistrar.h"
+
+#include "nsIChannel.h"
+#include "nsIParentChannel.h"
+#include "nsInterfaceHashtable.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREDIRECTCHANNELREGISTRAR
+
+ RedirectChannelRegistrar();
+
+ private:
+ ~RedirectChannelRegistrar() = default;
+
+ public:
+ // Singleton accessor
+ static already_AddRefed<nsIRedirectChannelRegistrar> GetOrCreate();
+
+ protected:
+ using ChannelHashtable = nsInterfaceHashtable<nsUint64HashKey, nsIChannel>;
+ using ParentChannelHashtable =
+ nsInterfaceHashtable<nsUint64HashKey, nsIParentChannel>;
+
+ ChannelHashtable mRealChannels;
+ ParentChannelHashtable mParentChannels;
+ Mutex mLock MOZ_UNANNOTATED;
+
+ static StaticRefPtr<RedirectChannelRegistrar> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp
new file mode 100644
index 0000000000..72331bdc1e
--- /dev/null
+++ b/netwerk/base/RequestContextService.cpp
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIDocumentLoader.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIXULRuntime.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "RequestContextService.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PSpdyPush.h"
+
+#include "../protocol/http/nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gRequestContextLog("RequestContext");
+#undef LOG
+#define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args)
+
+static StaticRefPtr<RequestContextService> gSingleton;
+
+// This is used to prevent adding tail pending requests after shutdown
+static bool sShutdown = false;
+
+// nsIRequestContext
+class RequestContext final : public nsIRequestContext,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXT
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit RequestContext(const uint64_t id);
+
+ private:
+ virtual ~RequestContext();
+
+ void ProcessTailQueue(nsresult aResult);
+ // Reschedules the timer if needed
+ void ScheduleUnblock();
+ // Hard-reschedules the timer
+ void RescheduleUntailTimer(TimeStamp const& now);
+
+ uint64_t mID;
+ Atomic<uint32_t> mBlockingTransactionCount;
+ UniquePtr<SpdyPushCache> mSpdyCache;
+
+ using PendingTailRequest = nsCOMPtr<nsIRequestTailUnblockCallback>;
+ // Number of known opened non-tailed requets
+ uint32_t mNonTailRequests;
+ // Queue of requests that have been tailed, when conditions are met
+ // we call each of them to unblock and drop the reference
+ nsTArray<PendingTailRequest> mTailQueue;
+ // Loosly scheduled timer, never scheduled further to the future than
+ // mUntailAt time
+ nsCOMPtr<nsITimer> mUntailTimer;
+ // Timestamp when the timer is expected to fire,
+ // always less than or equal to mUntailAt
+ TimeStamp mTimerScheduledAt;
+ // Timestamp when we want to actually untail queued requets based on
+ // the number of request count change in the past; iff this timestamp
+ // is set, we tail requests
+ TimeStamp mUntailAt;
+
+ // Timestamp of the navigation start time, set to Now() in BeginLoad().
+ // This is used to progressively lower the maximum delay time so that
+ // we can't get to a situation when a number of repetitive requests
+ // on the page causes forever tailing.
+ TimeStamp mBeginLoadTime;
+
+ // This member is true only between DOMContentLoaded notification and
+ // next document load beginning for this request context.
+ // Top level request contexts are recycled.
+ bool mAfterDOMContentLoaded;
+};
+
+NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback, nsINamed)
+
+RequestContext::RequestContext(const uint64_t aID)
+ : mID(aID),
+ mBlockingTransactionCount(0),
+ mNonTailRequests(0),
+ mAfterDOMContentLoaded(false) {
+ LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID));
+}
+
+RequestContext::~RequestContext() {
+ MOZ_ASSERT(mTailQueue.Length() == 0);
+
+ LOG(("RequestContext::~RequestContext this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+}
+
+NS_IMETHODIMP
+RequestContext::BeginLoad() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::BeginLoad %p", this));
+
+ if (IsNeckoChild()) {
+ // Tailing is not supported on the child process
+ if (gNeckoChild) {
+ gNeckoChild->SendRequestContextLoadBegin(mID);
+ }
+ return NS_OK;
+ }
+
+ mAfterDOMContentLoaded = false;
+ mBeginLoadTime = TimeStamp::NowLoRes();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::DOMContentLoaded() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::DOMContentLoaded %p", this));
+
+ if (IsNeckoChild()) {
+ // Tailing is not supported on the child process
+ if (gNeckoChild) {
+ gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID);
+ }
+ return NS_OK;
+ }
+
+ if (mAfterDOMContentLoaded) {
+ // There is a possibility of a duplicate notification
+ return NS_OK;
+ }
+
+ mAfterDOMContentLoaded = true;
+
+ // Conditions for the delay calculation has changed.
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetBlockingTransactionCount(
+ uint32_t* aBlockingTransactionCount) {
+ NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
+ *aBlockingTransactionCount = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::AddBlockingTransaction() {
+ mBlockingTransactionCount++;
+ LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveBlockingTransaction(uint32_t* outval) {
+ NS_ENSURE_ARG_POINTER(outval);
+ mBlockingTransactionCount--;
+ LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+ *outval = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+SpdyPushCache* RequestContext::GetSpdyPushCache() { return mSpdyCache.get(); }
+
+void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) {
+ mSpdyCache = WrapUnique(aSpdyPushCache);
+}
+
+uint64_t RequestContext::GetID() { return mID; }
+
+NS_IMETHODIMP
+RequestContext::AddNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ++mNonTailRequests;
+ LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this,
+ mNonTailRequests));
+
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mNonTailRequests > 0);
+
+ LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this,
+ mNonTailRequests - 1));
+
+ --mNonTailRequests;
+
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+void RequestContext::ScheduleUnblock() {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHttpHandler) {
+ return;
+ }
+
+ uint32_t quantum =
+ gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded);
+ uint32_t delayMax = gHttpHandler->TailBlockingDelayMax();
+ uint32_t totalMax = gHttpHandler->TailBlockingTotalMax();
+
+ if (!mBeginLoadTime.IsNull()) {
+ // We decrease the maximum delay progressively with the time since the page
+ // load begin. This seems like a reasonable and clear heuristic allowing us
+ // to start loading tailed requests in a deterministic time after the load
+ // has started.
+
+ uint32_t sinceBeginLoad = static_cast<uint32_t>(
+ (TimeStamp::NowLoRes() - mBeginLoadTime).ToMilliseconds());
+ uint32_t tillTotal = totalMax - std::min(sinceBeginLoad, totalMax);
+ uint32_t proportion = totalMax // values clamped between 0 and 60'000
+ ? (delayMax * tillTotal) / totalMax
+ : 0;
+ delayMax = std::min(delayMax, proportion);
+ }
+
+ CheckedInt<uint32_t> delay = quantum * mNonTailRequests;
+
+ if (!mAfterDOMContentLoaded) {
+ // Before DOMContentLoaded notification we want to make sure that tailed
+ // requests don't start when there is a short delay during which we may
+ // not have any active requests on the page happening.
+ delay += quantum;
+ }
+
+ if (!delay.isValid() || delay.value() > delayMax) {
+ delay = delayMax;
+ }
+
+ LOG(
+ ("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu "
+ "delay=%u after-DCL=%d",
+ this, mNonTailRequests, mTailQueue.Length(), delay.value(),
+ mAfterDOMContentLoaded));
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ mUntailAt = now + TimeDuration::FromMilliseconds(delay.value());
+
+ if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) {
+ LOG(("RequestContext %p timer would fire too late, rescheduling", this));
+ RescheduleUntailTimer(now);
+ }
+}
+
+void RequestContext::RescheduleUntailTimer(TimeStamp const& now) {
+ MOZ_ASSERT(mUntailAt >= now);
+
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ }
+
+ if (!mTailQueue.Length()) {
+ mUntailTimer = nullptr;
+ mTimerScheduledAt = TimeStamp();
+ return;
+ }
+
+ TimeDuration interval = mUntailAt - now;
+ if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) {
+ // When the number of untailed requests goes down,
+ // let's half the interval, since it's likely we would
+ // reschedule for a shorter time again very soon.
+ // This will likely save rescheduling this timer.
+ interval = interval / int64_t(2);
+ mTimerScheduledAt = mUntailAt - interval;
+ } else {
+ mTimerScheduledAt = mUntailAt;
+ }
+
+ uint32_t delay = interval.ToMilliseconds();
+ nsresult rv =
+ NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer), this, delay,
+ nsITimer::TYPE_ONE_SHOT, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not reschedule untail timer");
+ }
+
+ LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay));
+}
+
+NS_IMETHODIMP
+RequestContext::Notify(nsITimer* timer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(timer == mUntailTimer);
+ MOZ_ASSERT(!mTimerScheduledAt.IsNull());
+ MOZ_ASSERT(mTailQueue.Length());
+
+ mUntailTimer = nullptr;
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ if (mUntailAt > mTimerScheduledAt && mUntailAt > now) {
+ LOG(("RequestContext %p timer fired too soon, rescheduling", this));
+ RescheduleUntailTimer(now);
+ return NS_OK;
+ }
+
+ // Must drop to allow re-engage of the timer
+ mTimerScheduledAt = TimeStamp();
+
+ ProcessTailQueue(NS_OK);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetName(nsACString& aName) {
+ aName.AssignLiteral("RequestContext");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest,
+ bool* aBlocked) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
+ this, aRequest, mTailQueue.Length()));
+
+ *aBlocked = false;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (mUntailAt.IsNull()) {
+ LOG((" untail time passed"));
+ return NS_OK;
+ }
+
+ if (mAfterDOMContentLoaded && !mNonTailRequests) {
+ LOG((" after DOMContentLoaded and no untailed requests"));
+ return NS_OK;
+ }
+
+ if (!gHttpHandler) {
+ // Xpcshell tests may not have http handler
+ LOG((" missing gHttpHandler?"));
+ return NS_OK;
+ }
+
+ *aBlocked = true;
+ mTailQueue.AppendElement(aRequest);
+
+ LOG((" request queued"));
+
+ if (!mUntailTimer) {
+ ScheduleUnblock();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool removed = mTailQueue.RemoveElement(aRequest);
+
+ LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this,
+ aRequest, removed));
+
+ // Stop untail timer if all tail requests are canceled.
+ if (removed && mTailQueue.IsEmpty()) {
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ mUntailTimer = nullptr;
+ }
+
+ // Must drop to allow re-engage of the timer
+ mTimerScheduledAt = TimeStamp();
+ }
+
+ return NS_OK;
+}
+
+void RequestContext::ProcessTailQueue(nsresult aResult) {
+ LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32,
+ this, mTailQueue.Length(), static_cast<uint32_t>(aResult)));
+
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ mUntailTimer = nullptr;
+ }
+
+ // Must drop to stop tailing requests
+ mUntailAt = TimeStamp();
+
+ nsTArray<PendingTailRequest> queue = std::move(mTailQueue);
+
+ for (const auto& request : queue) {
+ LOG((" untailing %p", request.get()));
+ request->OnTailUnblock(aResult);
+ }
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailPendingRequests(nsresult aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NS_FAILED(aResult));
+
+ ProcessTailQueue(aResult);
+ return NS_OK;
+}
+
+// nsIRequestContextService
+RequestContextService* RequestContextService::sSelf = nullptr;
+
+NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
+
+RequestContextService::RequestContextService() {
+ MOZ_ASSERT(!sSelf, "multiple rcs instances!");
+ MOZ_ASSERT(NS_IsMainThread());
+ sSelf = this;
+
+ nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+ runtime->GetProcessID(&mRCIDNamespace);
+}
+
+RequestContextService::~RequestContextService() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Shutdown();
+ sSelf = nullptr;
+}
+
+nsresult RequestContextService::Init() {
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ obs->AddObserver(this, "content-document-interactive", false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void RequestContextService::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // We need to do this to prevent the requests from being scheduled after
+ // shutdown.
+ for (const auto& data : mTable.Values()) {
+ data->CancelTailPendingRequests(NS_ERROR_ABORT);
+ }
+ mTable.Clear();
+ sShutdown = true;
+}
+
+/* static */
+already_AddRefed<nsIRequestContextService>
+RequestContextService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sShutdown) {
+ return nullptr;
+ }
+
+ RefPtr<RequestContextService> svc;
+ if (gSingleton) {
+ svc = gSingleton;
+ } else {
+ svc = new RequestContextService();
+ nsresult rv = svc->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ gSingleton = svc;
+ ClearOnShutdown(&gSingleton);
+ }
+
+ return svc.forget();
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContext(const uint64_t rcID,
+ nsIRequestContext** rc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (!rcID) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *rc = do_AddRef(mTable.LookupOrInsertWith(rcID, [&] {
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ return newSC;
+ })).take();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup,
+ nsIRequestContext** rc) {
+ nsresult rv;
+
+ uint64_t rcID;
+ rv = aLoadGroup->GetRequestContextID(&rcID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetRequestContext(rcID, rc);
+}
+
+NS_IMETHODIMP
+RequestContextService::NewRequestContext(nsIRequestContext** rc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ uint64_t rcID =
+ ((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) |
+ mNextRCID++;
+
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ mTable.InsertOrUpdate(rcID, newSC);
+ newSC.swap(*rc);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::RemoveRequestContext(const uint64_t rcID) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mTable.Remove(rcID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data_unicode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp("content-document-interactive", topic)) {
+ nsCOMPtr<dom::Document> document(do_QueryInterface(subject));
+ MOZ_ASSERT(document);
+ // We want this be triggered also for iframes, since those track their
+ // own request context ids.
+ if (!document) {
+ return NS_OK;
+ }
+ nsIDocShell* ds = document->GetDocShell();
+ // XML documents don't always have a docshell assigned
+ if (!ds) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds));
+ if (!dl) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsILoadGroup> lg;
+ dl->GetLoadGroup(getter_AddRefs(lg));
+ if (!lg) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIRequestContext> rc;
+ GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc));
+ if (rc) {
+ rc->DOMContentLoaded();
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false, "Unexpected observer topic");
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h
new file mode 100644
index 0000000000..4aafdb8ed6
--- /dev/null
+++ b/netwerk/base/RequestContextService.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__RequestContextService_h
+#define mozilla__net__RequestContextService_h
+
+#include "nsCOMPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIObserver.h"
+#include "nsIRequestContext.h"
+
+namespace mozilla {
+namespace net {
+
+class RequestContextService final : public nsIRequestContextService,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXTSERVICE
+ NS_DECL_NSIOBSERVER
+
+ static already_AddRefed<nsIRequestContextService> GetOrCreate();
+
+ private:
+ RequestContextService();
+ virtual ~RequestContextService();
+
+ nsresult Init();
+ void Shutdown();
+
+ static RequestContextService* sSelf;
+
+ nsInterfaceHashtable<nsUint64HashKey, nsIRequestContext> mTable;
+ uint32_t mRCIDNamespace{0};
+ uint32_t mNextRCID{1};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__RequestContextService_h
diff --git a/netwerk/base/SSLTokensCache.cpp b/netwerk/base/SSLTokensCache.cpp
new file mode 100644
index 0000000000..cf739814f5
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.cpp
@@ -0,0 +1,568 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SSLTokensCache.h"
+
+#include "CertVerifier.h"
+#include "CommonSocketControl.h"
+#include "TransportSecurityInfo.h"
+#include "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "nsIOService.h"
+#include "ssl.h"
+#include "sslexp.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gSSLTokensCacheLog("SSLTokensCache");
+#undef LOG
+#define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args)
+#undef LOG5_ENABLED
+#define LOG5_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gSSLTokensCacheLog, mozilla::LogLevel::Verbose)
+
+class ExpirationComparator {
+ public:
+ bool Equals(SSLTokensCache::TokenCacheRecord* a,
+ SSLTokensCache::TokenCacheRecord* b) const {
+ return a->mExpirationTime == b->mExpirationTime;
+ }
+ bool LessThan(SSLTokensCache::TokenCacheRecord* a,
+ SSLTokensCache::TokenCacheRecord* b) const {
+ return a->mExpirationTime < b->mExpirationTime;
+ }
+};
+
+SessionCacheInfo SessionCacheInfo::Clone() const {
+ SessionCacheInfo result;
+ result.mEVStatus = mEVStatus;
+ result.mCertificateTransparencyStatus = mCertificateTransparencyStatus;
+ result.mServerCertBytes = mServerCertBytes.Clone();
+ result.mSucceededCertChainBytes =
+ mSucceededCertChainBytes
+ ? Some(TransformIntoNewArray(
+ *mSucceededCertChainBytes,
+ [](const auto& element) { return element.Clone(); }))
+ : Nothing();
+ result.mIsBuiltCertChainRootBuiltInRoot = mIsBuiltCertChainRootBuiltInRoot;
+ result.mOverridableErrorCategory = mOverridableErrorCategory;
+ result.mFailedCertChainBytes =
+ mFailedCertChainBytes
+ ? Some(TransformIntoNewArray(
+ *mFailedCertChainBytes,
+ [](const auto& element) { return element.Clone(); }))
+ : Nothing();
+ return result;
+}
+
+StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
+StaticMutex SSLTokensCache::sLock;
+uint64_t SSLTokensCache::sRecordId = 0;
+
+SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() {
+ if (!gInstance) {
+ return;
+ }
+
+ gInstance->OnRecordDestroyed(this);
+}
+
+uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
+ uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) +
+ sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) +
+ mSessionCacheInfo.mServerCertBytes.Length() +
+ sizeof(mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot) +
+ sizeof(mSessionCacheInfo.mOverridableErrorCategory);
+ if (mSessionCacheInfo.mSucceededCertChainBytes) {
+ for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.ref()) {
+ size += cert.Length();
+ }
+ }
+ if (mSessionCacheInfo.mFailedCertChainBytes) {
+ for (const auto& cert : mSessionCacheInfo.mFailedCertChainBytes.ref()) {
+ size += cert.Length();
+ }
+ }
+ return size;
+}
+
+void SSLTokensCache::TokenCacheRecord::Reset() {
+ mToken.Clear();
+ mExpirationTime = 0;
+ mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV;
+ mSessionCacheInfo.mCertificateTransparencyStatus =
+ nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
+ mSessionCacheInfo.mServerCertBytes.Clear();
+ mSessionCacheInfo.mSucceededCertChainBytes.reset();
+ mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot.reset();
+ mSessionCacheInfo.mOverridableErrorCategory =
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET;
+ mSessionCacheInfo.mFailedCertChainBytes.reset();
+}
+
+uint32_t SSLTokensCache::TokenCacheEntry::Size() const {
+ uint32_t size = 0;
+ for (const auto& rec : mRecords) {
+ size += rec->Size();
+ }
+ return size;
+}
+
+void SSLTokensCache::TokenCacheEntry::AddRecord(
+ UniquePtr<SSLTokensCache::TokenCacheRecord>&& aRecord,
+ nsTArray<TokenCacheRecord*>& aExpirationArray) {
+ if (mRecords.Length() ==
+ StaticPrefs::network_ssl_tokens_cache_records_per_entry()) {
+ aExpirationArray.RemoveElement(mRecords[0].get());
+ mRecords.RemoveElementAt(0);
+ }
+
+ aExpirationArray.AppendElement(aRecord.get());
+ for (int32_t i = mRecords.Length() - 1; i >= 0; --i) {
+ if (aRecord->mExpirationTime > mRecords[i]->mExpirationTime) {
+ mRecords.InsertElementAt(i + 1, std::move(aRecord));
+ return;
+ }
+ }
+ mRecords.InsertElementAt(0, std::move(aRecord));
+}
+
+UniquePtr<SSLTokensCache::TokenCacheRecord>
+SSLTokensCache::TokenCacheEntry::RemoveWithId(uint64_t aId) {
+ for (int32_t i = mRecords.Length() - 1; i >= 0; --i) {
+ if (mRecords[i]->mId == aId) {
+ UniquePtr<TokenCacheRecord> record = std::move(mRecords[i]);
+ mRecords.RemoveElementAt(i);
+ return record;
+ }
+ }
+ return nullptr;
+}
+
+const UniquePtr<SSLTokensCache::TokenCacheRecord>&
+SSLTokensCache::TokenCacheEntry::Get() {
+ return mRecords[0];
+}
+
+NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
+
+// static
+nsresult SSLTokensCache::Init() {
+ StaticMutexAutoLock lock(sLock);
+
+ // SSLTokensCache should be only used in parent process and socket process.
+ // Ideally, parent process should not use this when socket process is enabled.
+ // However, some xpcsehll tests may need to create and use sockets directly,
+ // so we still allow to use this in parent process no matter socket process is
+ // enabled or not.
+ if (!(XRE_IsSocketProcess() || XRE_IsParentProcess())) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!gInstance);
+
+ gInstance = new SSLTokensCache();
+
+ RegisterWeakMemoryReporter(gInstance);
+
+ return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::Shutdown() {
+ StaticMutexAutoLock lock(sLock);
+
+ if (!gInstance) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ UnregisterWeakMemoryReporter(gInstance);
+
+ gInstance = nullptr;
+
+ return NS_OK;
+}
+
+SSLTokensCache::SSLTokensCache() { LOG(("SSLTokensCache::SSLTokensCache")); }
+
+SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
+
+// static
+nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen,
+ CommonSocketControl* aSocketControl) {
+ PRUint32 expirationTime;
+ SSLResumptionTokenInfo tokenInfo;
+ if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo,
+ sizeof(tokenInfo)) != SECSuccess) {
+ LOG((" cannot get expiration time from the token, NSS error %d",
+ PORT_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ expirationTime = tokenInfo.expirationTime;
+ SSL_DestroyResumptionTokenInfo(&tokenInfo);
+
+ return Put(aKey, aToken, aTokenLen, aSocketControl, expirationTime);
+}
+
+// static
+nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen,
+ CommonSocketControl* aSocketControl,
+ PRUint32 aExpirationTime) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]",
+ PromiseFlatCString(aKey).get(), aTokenLen));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!aSocketControl) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ nsresult rv = aSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ securityInfo->GetServerCert(getter_AddRefs(cert));
+ if (!cert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<uint8_t> certBytes;
+ rv = cert->GetRawDER(certBytes);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes;
+ nsTArray<RefPtr<nsIX509Cert>> succeededCertArray;
+ rv = securityInfo->GetSucceededCertChain(succeededCertArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<bool> isBuiltCertChainRootBuiltInRoot;
+ if (!succeededCertArray.IsEmpty()) {
+ succeededCertChainBytes.emplace();
+ for (const auto& cert : succeededCertArray) {
+ nsTArray<uint8_t> rawCert;
+ nsresult rv = cert->GetRawDER(rawCert);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ succeededCertChainBytes->AppendElement(std::move(rawCert));
+ }
+
+ bool builtInRoot = false;
+ rv = securityInfo->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ isBuiltCertChainRootBuiltInRoot.emplace(builtInRoot);
+ }
+
+ bool isEV;
+ rv = securityInfo->GetIsExtendedValidation(&isEV);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint16_t certificateTransparencyStatus;
+ rv = securityInfo->GetCertificateTransparencyStatus(
+ &certificateTransparencyStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory;
+ rv = securityInfo->GetOverridableErrorCategory(&overridableErrorCategory);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> failedCertChainBytes;
+ nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
+ rv = securityInfo->GetFailedCertChain(failedCertArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!failedCertArray.IsEmpty()) {
+ failedCertChainBytes.emplace();
+ for (const auto& cert : failedCertArray) {
+ nsTArray<uint8_t> rawCert;
+ nsresult rv = cert->GetRawDER(rawCert);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ failedCertChainBytes->AppendElement(std::move(rawCert));
+ }
+ }
+
+ auto makeUniqueRecord = [&]() {
+ auto rec = MakeUnique<TokenCacheRecord>();
+ rec->mKey = aKey;
+ rec->mExpirationTime = aExpirationTime;
+ MOZ_ASSERT(rec->mToken.IsEmpty());
+ rec->mToken.AppendElements(aToken, aTokenLen);
+ rec->mId = ++sRecordId;
+ rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
+ rec->mSessionCacheInfo.mSucceededCertChainBytes =
+ std::move(succeededCertChainBytes);
+ if (isEV) {
+ rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
+ }
+ rec->mSessionCacheInfo.mCertificateTransparencyStatus =
+ certificateTransparencyStatus;
+ rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot =
+ std::move(isBuiltCertChainRootBuiltInRoot);
+ rec->mSessionCacheInfo.mOverridableErrorCategory = overridableErrorCategory;
+ rec->mSessionCacheInfo.mFailedCertChainBytes =
+ std::move(failedCertChainBytes);
+ return rec;
+ };
+
+ TokenCacheEntry* const cacheEntry =
+ gInstance->mTokenCacheRecords.WithEntryHandle(aKey, [&](auto&& entry) {
+ if (!entry) {
+ auto rec = makeUniqueRecord();
+ auto cacheEntry = MakeUnique<TokenCacheEntry>();
+ cacheEntry->AddRecord(std::move(rec), gInstance->mExpirationArray);
+ entry.Insert(std::move(cacheEntry));
+ } else {
+ // To make sure the cache size is synced, we take away the size of
+ // whole entry and add it back later.
+ gInstance->mCacheSize -= entry.Data()->Size();
+ entry.Data()->AddRecord(makeUniqueRecord(),
+ gInstance->mExpirationArray);
+ }
+
+ return entry->get();
+ });
+
+ gInstance->mCacheSize += cacheEntry->Size();
+
+ gInstance->LogStats();
+
+ gInstance->EvictIfNecessary();
+
+ return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::Get(const nsACString& aKey, nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult, uint64_t* aTokenId) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return gInstance->GetLocked(aKey, aToken, aResult, aTokenId);
+}
+
+nsresult SSLTokensCache::GetLocked(const nsACString& aKey,
+ nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult,
+ uint64_t* aTokenId) {
+ sLock.AssertCurrentThreadOwns();
+
+ TokenCacheEntry* cacheEntry = nullptr;
+
+ if (mTokenCacheRecords.Get(aKey, &cacheEntry)) {
+ if (cacheEntry->RecordCount() == 0) {
+ MOZ_ASSERT(false, "Found a cacheEntry with no records");
+ mTokenCacheRecords.Remove(aKey);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ const UniquePtr<TokenCacheRecord>& rec = cacheEntry->Get();
+ aToken = rec->mToken.Clone();
+ aResult = rec->mSessionCacheInfo.Clone();
+ if (aTokenId) {
+ *aTokenId = rec->mId;
+ }
+ mCacheSize -= rec->Size();
+ cacheEntry->RemoveWithId(rec->mId);
+ if (cacheEntry->RecordCount() == 0) {
+ mTokenCacheRecords.Remove(aKey);
+ }
+ return NS_OK;
+ }
+
+ LOG((" token not found"));
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// static
+nsresult SSLTokensCache::Remove(const nsACString& aKey, uint64_t aId) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return gInstance->RemoveLocked(aKey, aId);
+}
+
+nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey, uint64_t aId) {
+ sLock.AssertCurrentThreadOwns();
+
+ LOG(("SSLTokensCache::RemoveLocked [key=%s, id=%" PRIu64 "]",
+ PromiseFlatCString(aKey).get(), aId));
+
+ TokenCacheEntry* cacheEntry;
+ if (!mTokenCacheRecords.Get(aKey, &cacheEntry)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UniquePtr<TokenCacheRecord> rec = cacheEntry->RemoveWithId(aId);
+ if (!rec) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mCacheSize -= rec->Size();
+ if (cacheEntry->RecordCount() == 0) {
+ mTokenCacheRecords.Remove(aKey);
+ }
+
+ // Release the record immediately, so mExpirationArray can be also updated.
+ rec = nullptr;
+
+ LogStats();
+
+ return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::RemoveAll(const nsACString& aKey) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return gInstance->RemovAllLocked(aKey);
+}
+
+nsresult SSLTokensCache::RemovAllLocked(const nsACString& aKey) {
+ sLock.AssertCurrentThreadOwns();
+
+ LOG(("SSLTokensCache::RemovAllLocked [key=%s]",
+ PromiseFlatCString(aKey).get()));
+
+ UniquePtr<TokenCacheEntry> cacheEntry;
+ if (!mTokenCacheRecords.Remove(aKey, &cacheEntry)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mCacheSize -= cacheEntry->Size();
+ cacheEntry = nullptr;
+
+ LogStats();
+
+ return NS_OK;
+}
+
+void SSLTokensCache::OnRecordDestroyed(TokenCacheRecord* aRec) {
+ mExpirationArray.RemoveElement(aRec);
+}
+
+void SSLTokensCache::EvictIfNecessary() {
+ // kilobytes to bytes
+ uint32_t capacity = StaticPrefs::network_ssl_tokens_cache_capacity() << 10;
+ if (mCacheSize <= capacity) {
+ return;
+ }
+
+ LOG(("SSLTokensCache::EvictIfNecessary - evicting"));
+
+ mExpirationArray.Sort(ExpirationComparator());
+
+ while (mCacheSize > capacity && mExpirationArray.Length() > 0) {
+ DebugOnly<nsresult> rv =
+ RemoveLocked(mExpirationArray[0]->mKey, mExpirationArray[0]->mId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "mExpirationArray and mTokenCacheRecords are out of sync!");
+ }
+}
+
+void SSLTokensCache::LogStats() {
+ if (!LOG5_ENABLED()) {
+ return;
+ }
+ LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]",
+ mExpirationArray.Length(), mCacheSize));
+ for (const auto& ent : mTokenCacheRecords.Values()) {
+ const UniquePtr<TokenCacheRecord>& rec = ent->Get();
+ LOG(("key=%s count=%d", rec->mKey.get(), ent->RecordCount()));
+ }
+}
+
+size_t SSLTokensCache::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+
+ n += mTokenCacheRecords.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ for (uint32_t i = 0; i < mExpirationArray.Length(); ++i) {
+ n += mallocSizeOf(mExpirationArray[i]);
+ n += mExpirationArray[i]->mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mExpirationArray[i]->mToken.ShallowSizeOfExcludingThis(mallocSizeOf);
+ }
+
+ return n;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf)
+
+NS_IMETHODIMP
+SSLTokensCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ StaticMutexAutoLock lock(sLock);
+
+ MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP,
+ UNITS_BYTES,
+ SizeOfIncludingThis(SSLTokensCacheMallocSizeOf),
+ "Memory used for the SSL tokens cache.");
+
+ return NS_OK;
+}
+
+// static
+void SSLTokensCache::Clear() {
+ LOG(("SSLTokensCache::Clear"));
+
+ StaticMutexAutoLock lock(sLock);
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return;
+ }
+
+ gInstance->mExpirationArray.Clear();
+ gInstance->mTokenCacheRecords.Clear();
+ gInstance->mCacheSize = 0;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SSLTokensCache.h b/netwerk/base/SSLTokensCache.h
new file mode 100644
index 0000000000..b335457526
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.h
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SSLTokensCache_h_
+#define SSLTokensCache_h_
+
+#include "CertVerifier.h" // For EVStatus
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPtr.h"
+#include "nsClassHashtable.h"
+#include "nsIMemoryReporter.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsTArray.h"
+#include "nsTHashMap.h"
+#include "nsXULAppAPI.h"
+
+class CommonSocketControl;
+
+namespace mozilla {
+namespace net {
+
+struct SessionCacheInfo {
+ SessionCacheInfo Clone() const;
+
+ psm::EVStatus mEVStatus = psm::EVStatus::NotEV;
+ uint16_t mCertificateTransparencyStatus =
+ nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
+ nsTArray<uint8_t> mServerCertBytes;
+ Maybe<nsTArray<nsTArray<uint8_t>>> mSucceededCertChainBytes;
+ Maybe<bool> mIsBuiltCertChainRootBuiltInRoot;
+ nsITransportSecurityInfo::OverridableErrorCategory mOverridableErrorCategory;
+ Maybe<nsTArray<nsTArray<uint8_t>>> mFailedCertChainBytes;
+};
+
+class SSLTokensCache : public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ friend class ExpirationComparator;
+
+ static nsresult Init();
+ static nsresult Shutdown();
+
+ static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen, CommonSocketControl* aSocketControl);
+ static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen, CommonSocketControl* aSocketControl,
+ PRUint32 aExpirationTime);
+ static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult, uint64_t* aTokenId = nullptr);
+ static nsresult Remove(const nsACString& aKey, uint64_t aId);
+ static nsresult RemoveAll(const nsACString& aKey);
+ static void Clear();
+
+ private:
+ SSLTokensCache();
+ virtual ~SSLTokensCache();
+
+ nsresult RemoveLocked(const nsACString& aKey, uint64_t aId);
+ nsresult RemovAllLocked(const nsACString& aKey);
+ nsresult GetLocked(const nsACString& aKey, nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult, uint64_t* aTokenId);
+
+ void EvictIfNecessary();
+ void LogStats();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ static mozilla::StaticRefPtr<SSLTokensCache> gInstance;
+ static StaticMutex sLock MOZ_UNANNOTATED;
+ static uint64_t sRecordId;
+
+ uint32_t mCacheSize{0}; // Actual cache size in bytes
+
+ class TokenCacheRecord {
+ public:
+ ~TokenCacheRecord();
+
+ uint32_t Size() const;
+ void Reset();
+
+ nsCString mKey;
+ PRUint32 mExpirationTime = 0;
+ nsTArray<uint8_t> mToken;
+ SessionCacheInfo mSessionCacheInfo;
+ // An unique id to identify the record. Mostly used when we want to remove a
+ // record from TokenCacheEntry.
+ uint64_t mId = 0;
+ };
+
+ class TokenCacheEntry {
+ public:
+ uint32_t Size() const;
+ // Add a record into |mRecords|. To make sure |mRecords| is sorted, we
+ // iterate |mRecords| everytime to find a right place to insert the new
+ // record.
+ void AddRecord(UniquePtr<TokenCacheRecord>&& aRecord,
+ nsTArray<TokenCacheRecord*>& aExpirationArray);
+ // This function returns the first record in |mRecords|.
+ const UniquePtr<TokenCacheRecord>& Get();
+ UniquePtr<TokenCacheRecord> RemoveWithId(uint64_t aId);
+ uint32_t RecordCount() const { return mRecords.Length(); }
+ const nsTArray<UniquePtr<TokenCacheRecord>>& Records() { return mRecords; }
+
+ private:
+ // The records in this array are ordered by the expiration time.
+ nsTArray<UniquePtr<TokenCacheRecord>> mRecords;
+ };
+
+ void OnRecordDestroyed(TokenCacheRecord* aRec);
+
+ nsClassHashtable<nsCStringHashKey, TokenCacheEntry> mTokenCacheRecords;
+ nsTArray<TokenCacheRecord*> mExpirationArray;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // SSLTokensCache_h_
diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp
new file mode 100644
index 0000000000..db62ea691d
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "ShutdownLayer.h"
+#include "prerror.h"
+#include "private/pprio.h"
+#include "prmem.h"
+#include <winsock2.h>
+
+static PRDescIdentity sWinSockShutdownLayerIdentity;
+static PRIOMethods sWinSockShutdownLayerMethods;
+static PRIOMethods* sWinSockShutdownLayerMethodsPtr = nullptr;
+
+namespace mozilla {
+namespace net {
+
+extern PRDescIdentity nsNamedPipeLayerIdentity;
+
+} // namespace net
+} // namespace mozilla
+
+PRStatus WinSockClose(PRFileDesc* aFd) {
+ MOZ_RELEASE_ASSERT(aFd->identity == sWinSockShutdownLayerIdentity,
+ "Windows shutdown layer not on the top of the stack");
+
+ PROsfd osfd = PR_FileDesc2NativeHandle(aFd);
+ if (osfd != -1) {
+ shutdown(osfd, SD_BOTH);
+ }
+
+ aFd->identity = PR_INVALID_IO_LAYER;
+
+ if (aFd->lower) {
+ return aFd->lower->methods->close(aFd->lower);
+ } else {
+ return PR_SUCCESS;
+ }
+}
+
+nsresult mozilla::net::AttachShutdownLayer(PRFileDesc* aFd) {
+ if (!sWinSockShutdownLayerMethodsPtr) {
+ sWinSockShutdownLayerIdentity =
+ PR_GetUniqueIdentity("windows shutdown call layer");
+ sWinSockShutdownLayerMethods = *PR_GetDefaultIOMethods();
+ sWinSockShutdownLayerMethods.close = WinSockClose;
+ sWinSockShutdownLayerMethodsPtr = &sWinSockShutdownLayerMethods;
+ }
+
+ if (mozilla::net::nsNamedPipeLayerIdentity &&
+ PR_GetIdentitiesLayer(aFd, mozilla::net::nsNamedPipeLayerIdentity)) {
+ // Do not attach shutdown layer on named pipe layer,
+ // it is for PR_NSPR_IO_LAYER only.
+ return NS_OK;
+ }
+
+ PRFileDesc* layer;
+ PRStatus status;
+
+ layer = PR_CreateIOLayerStub(sWinSockShutdownLayerIdentity,
+ sWinSockShutdownLayerMethodsPtr);
+ if (!layer) {
+ return NS_OK;
+ }
+
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h
new file mode 100644
index 0000000000..2bf68fdcdc
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SHUTDOWNLAYER_H___
+#define SHUTDOWNLAYER_H___
+
+#include "nscore.h"
+#include "prio.h"
+
+namespace mozilla {
+namespace net {
+
+// This is only for windows. This layer will be attached jus before PR_CLose
+// is call and it will only call shutdown(sock).
+extern nsresult AttachShutdownLayer(PRFileDesc* fd);
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SHUTDOWN_H___ */
diff --git a/netwerk/base/SimpleBuffer.cpp b/netwerk/base/SimpleBuffer.cpp
new file mode 100644
index 0000000000..46b8654efa
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleBuffer.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+nsresult SimpleBuffer::Write(char* src, size_t len) {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ while (len > 0) {
+ SimpleBufferPage* p = mBufferList.getLast();
+ if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) {
+ // no room.. make a new page
+ p = nullptr;
+ }
+ if (!p) {
+ p = new (fallible) SimpleBufferPage();
+ if (!p) {
+ mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return mStatus;
+ }
+ mBufferList.insertBack(p);
+ }
+ size_t roomOnPage =
+ SimpleBufferPage::kSimpleBufferPageSize - p->mWriteOffset;
+ size_t toWrite = std::min(roomOnPage, len);
+ memcpy(p->mBuffer + p->mWriteOffset, src, toWrite);
+ src += toWrite;
+ len -= toWrite;
+ p->mWriteOffset += toWrite;
+ mAvailable += toWrite;
+ }
+ return NS_OK;
+}
+
+size_t SimpleBuffer::Read(char* dest, size_t maxLen) {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ if (NS_FAILED(mStatus)) {
+ return 0;
+ }
+
+ size_t rv = 0;
+ for (SimpleBufferPage* p = mBufferList.getFirst(); p && (rv < maxLen);
+ p = mBufferList.getFirst()) {
+ size_t avail = p->mWriteOffset - p->mReadOffset;
+ size_t toRead = std::min(avail, (maxLen - rv));
+ memcpy(dest + rv, p->mBuffer + p->mReadOffset, toRead);
+ rv += toRead;
+ p->mReadOffset += toRead;
+ if (p->mReadOffset == p->mWriteOffset) {
+ p->remove();
+ delete p;
+ }
+ }
+
+ MOZ_ASSERT(mAvailable >= rv);
+ mAvailable -= rv;
+ return rv;
+}
+
+size_t SimpleBuffer::Available() {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ return NS_SUCCEEDED(mStatus) ? mAvailable : 0;
+}
+
+void SimpleBuffer::Clear() {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ SimpleBufferPage* p;
+ while ((p = mBufferList.popFirst())) {
+ delete p;
+ }
+ mAvailable = 0;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleBuffer.h b/netwerk/base/SimpleBuffer.h
new file mode 100644
index 0000000000..031f59afe5
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SimpleBuffer_h__
+#define SimpleBuffer_h__
+
+/*
+ This class is similar to a nsPipe except it does not have any locking, stores
+ an unbounded amount of data, can only be used on one thread, and has much
+ simpler result code semantics to deal with.
+*/
+
+#include "prtypes.h"
+#include "ErrorList.h"
+#include "mozilla/LinkedList.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace net {
+
+class SimpleBufferPage : public LinkedListElement<SimpleBufferPage> {
+ public:
+ SimpleBufferPage() = default;
+ static const size_t kSimpleBufferPageSize = 32000;
+
+ private:
+ friend class SimpleBuffer;
+ char mBuffer[kSimpleBufferPageSize]{0};
+ size_t mReadOffset{0};
+ size_t mWriteOffset{0};
+};
+
+class SimpleBuffer {
+ public:
+ SimpleBuffer() = default;
+ ~SimpleBuffer() = default;
+
+ nsresult Write(char* src, size_t len); // return OK or OUT_OF_MEMORY
+ size_t Read(char* dest, size_t maxLen); // return bytes read
+ size_t Available();
+ void Clear();
+
+ private:
+ NS_DECL_OWNINGTHREAD
+
+ nsresult mStatus{NS_OK};
+ AutoCleanLinkedList<SimpleBufferPage> mBufferList;
+ size_t mAvailable{0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/SimpleChannel.cpp b/netwerk/base/SimpleChannel.cpp
new file mode 100644
index 0000000000..6297331135
--- /dev/null
+++ b/netwerk/base/SimpleChannel.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleChannel.h"
+
+#include "nsBaseChannel.h"
+#include "nsIChannel.h"
+#include "nsIChildChannel.h"
+#include "nsICancelable.h"
+#include "nsIInputStream.h"
+#include "nsIRequest.h"
+#include "nsISupportsImpl.h"
+#include "nsNetUtil.h"
+
+#include "mozilla/Try.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/PSimpleChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+SimpleChannel::SimpleChannel(UniquePtr<SimpleChannelCallbacks>&& aCallbacks)
+ : mCallbacks(std::move(aCallbacks)) {
+ EnableSynthesizedProgressEvents(true);
+}
+
+nsresult SimpleChannel::OpenContentStream(bool async,
+ nsIInputStream** streamOut,
+ nsIChannel** channel) {
+ NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIInputStream> stream;
+ MOZ_TRY_VAR(stream, mCallbacks->OpenContentStream(async, this));
+ MOZ_ASSERT(stream);
+
+ mCallbacks = nullptr;
+
+ *channel = nullptr;
+ stream.forget(streamOut);
+ return NS_OK;
+}
+
+nsresult SimpleChannel::BeginAsyncRead(nsIStreamListener* listener,
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) {
+ NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED);
+
+ RequestOrReason res = mCallbacks->StartAsyncRead(listener, this);
+
+ if (res.isErr()) {
+ return res.propagateErr();
+ }
+
+ mCallbacks = nullptr;
+
+ RequestOrCancelable value = res.unwrap();
+
+ if (value.is<NotNullRequest>()) {
+ nsCOMPtr<nsIRequest> req = value.as<NotNullRequest>().get();
+ req.forget(request);
+ } else if (value.is<NotNullCancelable>()) {
+ nsCOMPtr<nsICancelable> cancelable = value.as<NotNullCancelable>().get();
+ cancelable.forget(cancelableRequest);
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "StartAsyncRead didn't return a NotNull nsIRequest or nsICancelable.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(SimpleChannelChild, SimpleChannel, nsIChildChannel)
+
+SimpleChannelChild::SimpleChannelChild(
+ UniquePtr<SimpleChannelCallbacks>&& aCallbacks)
+ : SimpleChannel(std::move(aCallbacks)) {}
+
+NS_IMETHODIMP
+SimpleChannelChild::ConnectParent(uint32_t aId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Reference freed in DeallocPSimpleChannelChild.
+ if (!gNeckoChild->SendPSimpleChannelConstructor(do_AddRef(this).take(),
+ aId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ if (CanSend()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ nsresult rv;
+ rv = AsyncOpen(aListener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (CanSend()) {
+ Unused << Send__delete__(this);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsIChannel> NS_NewSimpleChannelInternal(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ UniquePtr<SimpleChannelCallbacks>&& aCallbacks) {
+ RefPtr<SimpleChannel> chan;
+ if (IsNeckoChild()) {
+ chan = new SimpleChannelChild(std::move(aCallbacks));
+ } else {
+ chan = new SimpleChannel(std::move(aCallbacks));
+ }
+
+ chan->SetURI(aURI);
+
+ MOZ_ALWAYS_SUCCEEDS(chan->SetLoadInfo(aLoadInfo));
+
+ return chan.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleChannel.h b/netwerk/base/SimpleChannel.h
new file mode 100644
index 0000000000..d469adf65d
--- /dev/null
+++ b/netwerk/base/SimpleChannel.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SimpleChannel_h
+#define SimpleChannel_h
+
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/UniquePtr.h"
+#include "nsBaseChannel.h"
+#include "nsIChildChannel.h"
+#include "mozilla/net/PSimpleChannelChild.h"
+#include "nsCOMPtr.h"
+
+class nsIChannel;
+class nsIInputStream;
+class nsILoadInfo;
+class nsIRequest;
+class nsIStreamListener;
+class nsIURI;
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+
+using InputStreamOrReason = Result<nsCOMPtr<nsIInputStream>, nsresult>;
+using NotNullRequest = NotNull<nsCOMPtr<nsIRequest>>;
+using NotNullCancelable = NotNull<nsCOMPtr<nsICancelable>>;
+using RequestOrCancelable = Variant<NotNullRequest, NotNullCancelable>;
+using RequestOrReason = Result<RequestOrCancelable, nsresult>;
+
+namespace net {
+
+class SimpleChannelCallbacks {
+ public:
+ virtual InputStreamOrReason OpenContentStream(bool async,
+ nsIChannel* channel) = 0;
+
+ virtual RequestOrReason StartAsyncRead(nsIStreamListener* stream,
+ nsIChannel* channel) = 0;
+
+ virtual ~SimpleChannelCallbacks() = default;
+};
+
+template <typename F1, typename F2, typename T>
+class SimpleChannelCallbacksImpl final : public SimpleChannelCallbacks {
+ public:
+ SimpleChannelCallbacksImpl(F1&& aStartAsyncRead, F2&& aOpenContentStream,
+ T* context)
+ : mStartAsyncRead(aStartAsyncRead),
+ mOpenContentStream(aOpenContentStream),
+ mContext(context) {}
+
+ virtual ~SimpleChannelCallbacksImpl() = default;
+
+ virtual InputStreamOrReason OpenContentStream(bool async,
+ nsIChannel* channel) override {
+ return mOpenContentStream(async, channel, mContext);
+ }
+
+ virtual RequestOrReason StartAsyncRead(nsIStreamListener* listener,
+ nsIChannel* channel) override {
+ return mStartAsyncRead(listener, channel, mContext);
+ }
+
+ private:
+ F1 mStartAsyncRead;
+ F2 mOpenContentStream;
+ RefPtr<T> mContext;
+};
+
+class SimpleChannel : public nsBaseChannel {
+ public:
+ explicit SimpleChannel(UniquePtr<SimpleChannelCallbacks>&& aCallbacks);
+
+ protected:
+ virtual ~SimpleChannel() = default;
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** streamOut,
+ nsIChannel** channel) override;
+
+ virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) override;
+
+ private:
+ UniquePtr<SimpleChannelCallbacks> mCallbacks;
+};
+
+class SimpleChannelChild final : public SimpleChannel,
+ public nsIChildChannel,
+ public PSimpleChannelChild {
+ public:
+ explicit SimpleChannelChild(UniquePtr<SimpleChannelCallbacks>&& aCallbacks);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ private:
+ virtual ~SimpleChannelChild() = default;
+};
+
+already_AddRefed<nsIChannel> NS_NewSimpleChannelInternal(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ UniquePtr<SimpleChannelCallbacks>&& aCallbacks);
+
+} // namespace net
+} // namespace mozilla
+
+/**
+ * Creates a simple channel which wraps an input stream created by the given
+ * callbacks. The callbacks are not called until the underlying AsyncOpen or
+ * Open methods are called, and correspond to the nsBaseChannel::StartAsyncRead
+ * and nsBaseChannel::OpenContentStream methods of the same names.
+ *
+ * The last two arguments of each callback are the created channel instance,
+ * and the ref-counted context object passed to NS_NewSimpleChannel. A strong
+ * reference to that object is guaranteed to be kept alive until after a
+ * callback successfully completes.
+ */
+template <typename T, typename F1, typename F2>
+inline already_AddRefed<nsIChannel> NS_NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, T* context, F1&& aStartAsyncRead,
+ F2&& aOpenContentStream) {
+ using namespace mozilla;
+
+ auto callbacks = MakeUnique<net::SimpleChannelCallbacksImpl<F1, F2, T>>(
+ std::forward<F1>(aStartAsyncRead), std::forward<F2>(aOpenContentStream),
+ context);
+
+ return net::NS_NewSimpleChannelInternal(aURI, aLoadInfo,
+ std::move(callbacks));
+}
+
+template <typename T, typename F1>
+inline already_AddRefed<nsIChannel> NS_NewSimpleChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ T* context,
+ F1&& aStartAsyncRead) {
+ using namespace mozilla;
+
+ auto openContentStream = [](bool async, nsIChannel* channel, T* context) {
+ return Err(NS_ERROR_NOT_IMPLEMENTED);
+ };
+
+ return NS_NewSimpleChannel(aURI, aLoadInfo, context,
+ std::forward<F1>(aStartAsyncRead),
+ std::move(openContentStream));
+}
+
+#endif // SimpleChannel_h
diff --git a/netwerk/base/SimpleChannelParent.cpp b/netwerk/base/SimpleChannelParent.cpp
new file mode 100644
index 0000000000..3a58e14843
--- /dev/null
+++ b/netwerk/base/SimpleChannelParent.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+#ifdef FUZZING_SNAPSHOT
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__
+#else
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(SimpleChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool SimpleChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+
+ MOZ_ALWAYS_SUCCEEDS_FUZZING(
+ NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aPrefix) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::Delete() {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void SimpleChannelParent::ActorDestroy(ActorDestroyReason aWhy) {}
+
+NS_IMETHODIMP
+SimpleChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsSimpleChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleChannelParent.h b/netwerk/base/SimpleChannelParent.h
new file mode 100644
index 0000000000..6a8f2c867b
--- /dev/null
+++ b/netwerk/base/SimpleChannelParent.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_SIMPLECHANNELPARENT_H
+#define NS_SIMPLECHANNELPARENT_H
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PSimpleChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class SimpleChannelParent : public nsIParentChannel,
+ public PSimpleChannelParent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER; // semicolon for clang-format bug 1629756
+
+ [[nodiscard]] bool Init(const uint64_t& aChannelId);
+
+ private:
+ ~SimpleChannelParent() = default;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_SIMPLECHANNELPARENT_H */
diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp
new file mode 100644
index 0000000000..131ea50573
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.cpp
@@ -0,0 +1,446 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TLSServerSocket.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDependentSubstring.h"
+#include "nsIServerSocket.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsThreadUtils.h"
+#include "ScopedNSSTypes.h"
+#include "ssl.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket, nsServerSocket, nsITLSServerSocket)
+
+nsresult TLSServerSocket::SetSocketDefaults() {
+ // Set TLS options on the listening socket
+ mFD = SSL_ImportFD(nullptr, mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSL_OptionSet(mFD, SSL_SECURITY, true);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true);
+ SSL_OptionSet(mFD, SSL_NO_CACHE, true);
+
+ // We don't currently notify the server API consumer of renegotiation events
+ // (to revalidate peer certs, etc.), so disable it for now.
+ SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER);
+
+ SetSessionTickets(true);
+ SetRequestClientCertificate(REQUEST_NEVER);
+
+ return NS_OK;
+}
+
+void TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv;
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ RefPtr<TLSServerConnectionInfo> info = new TLSServerConnectionInfo();
+ info->mServerSocket = this;
+ info->mTransport = trans;
+ nsCOMPtr<nsIInterfaceRequestor> infoInterfaceRequestor(info);
+ rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr,
+ infoInterfaceRequestor);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ // Override the default peer certificate validation, so that server consumers
+ // can make their own choice after the handshake completes.
+ SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr);
+ // Once the TLS handshake has completed, the server consumer is notified and
+ // has access to various TLS state details.
+ // It's safe to pass info here because the socket transport holds it as
+ // |mSecInfo| which keeps it alive for the lifetime of the socket.
+ SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback,
+ info);
+
+ // Notify the consumer of the new client so it can manage the streams.
+ // Security details aren't known yet. The security observer will be notified
+ // later when they are ready.
+ nsCOMPtr<nsIServerSocket> serverSocket =
+ do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this));
+ mListener->OnSocketAccepted(serverSocket, trans);
+}
+
+nsresult TLSServerSocket::OnSocketListen() {
+ if (NS_WARN_IF(!mServerCert)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ UniqueCERTCertificate cert(mServerCert->GetCert());
+ if (NS_WARN_IF(!cert)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr));
+ if (NS_WARN_IF(!key)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
+
+ nsresult rv =
+ MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(), certKEA));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+SECStatus TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig,
+ PRBool isServer) {
+ // Allow any client cert here, server consumer code can decide whether it's
+ // okay after being notified of the new client socket.
+ return SECSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket::nsITLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+TLSServerSocket::GetServerCert(nsIX509Cert** aCert) {
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = do_AddRef(mServerCert).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetServerCert(nsIX509Cert* aCert) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ mServerCert = aCert;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionTickets(bool aEnabled) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetRequestClientCertificate(uint32_t aMode) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER);
+
+ switch (aMode) {
+ case REQUEST_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR);
+ break;
+ case REQUIRE_FIRST_HANDSHAKE:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE);
+ break;
+ case REQUIRE_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS);
+ break;
+ default:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetVersionRange(uint16_t aMinVersion, uint16_t aMaxVersion) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ SSLVersionRange range = {aMinVersion, aMaxVersion};
+ if (SSL_VersionRangeSet(mFD, &range) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerConnectionInfo
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class TLSServerSecurityObserverProxy final
+ : public nsITLSServerSecurityObserver {
+ ~TLSServerSecurityObserverProxy() = default;
+
+ public:
+ explicit TLSServerSecurityObserverProxy(
+ nsITLSServerSecurityObserver* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsITLSServerSecurityObserver>(
+ "TLSServerSecurityObserverProxy::mListener", aListener)) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERSECURITYOBSERVER
+
+ class OnHandshakeDoneRunnable : public Runnable {
+ public:
+ OnHandshakeDoneRunnable(
+ const nsMainThreadPtrHandle<nsITLSServerSecurityObserver>& aListener,
+ nsITLSServerSocket* aServer, nsITLSClientStatus* aStatus)
+ : Runnable(
+ "net::TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable"),
+ mListener(aListener),
+ mServer(aServer),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+ nsCOMPtr<nsITLSServerSocket> mServer;
+ nsCOMPtr<nsITLSClientStatus> mStatus;
+ };
+
+ private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+};
+
+NS_IMPL_ISUPPORTS(TLSServerSecurityObserverProxy, nsITLSServerSecurityObserver)
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDone(nsITLSServerSocket* aServer,
+ nsITLSClientStatus* aStatus) {
+ RefPtr<OnHandshakeDoneRunnable> r =
+ new OnHandshakeDoneRunnable(mListener, aServer, aStatus);
+ return NS_DispatchToMainThread(r);
+}
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable::Run() {
+ mListener->OnHandshakeDone(mServer, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(TLSServerConnectionInfo, nsITLSServerConnectionInfo,
+ nsITLSClientStatus, nsIInterfaceRequestor)
+
+TLSServerConnectionInfo::~TLSServerConnectionInfo() {
+ RefPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ observer = ToRefPtr(std::move(mSecurityObserver));
+ }
+
+ if (observer) {
+ NS_ReleaseOnMainThread("TLSServerConnectionInfo::mSecurityObserver",
+ observer.forget());
+ }
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::SetSecurityObserver(
+ nsITLSServerSecurityObserver* aObserver) {
+ {
+ MutexAutoLock lock(mLock);
+ if (!aObserver) {
+ mSecurityObserver = nullptr;
+ return NS_OK;
+ }
+
+ mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver);
+ // Call `OnHandshakeDone` if TLS handshake is already completed.
+ if (mTlsVersionUsed != TLS_VERSION_UNKNOWN) {
+ nsCOMPtr<nsITLSServerSocket> serverSocket;
+ GetServerSocket(getter_AddRefs(serverSocket));
+ mSecurityObserver->OnHandshakeDone(serverSocket, this);
+ mSecurityObserver = nullptr;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket) {
+ if (NS_WARN_IF(!aSocket)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aSocket = do_AddRef(mServerSocket).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus) {
+ if (NS_WARN_IF(!aStatus)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aStatus = do_AddRef(this).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert) {
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = do_AddRef(mPeerCert).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed) {
+ if (NS_WARN_IF(!aTlsVersionUsed)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aTlsVersionUsed = mTlsVersionUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName) {
+ aCipherName.Assign(mCipherName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength) {
+ if (NS_WARN_IF(!aKeyLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aKeyLength = mKeyLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength) {
+ if (NS_WARN_IF(!aMacLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aMacLength = mMacLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetInterface(const nsIID& aIID, void** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsITLSServerConnectionInfo))) {
+ *aResult = static_cast<nsITLSServerConnectionInfo*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+// static
+void TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg) {
+ RefPtr<TLSServerConnectionInfo> info =
+ static_cast<TLSServerConnectionInfo*>(aArg);
+ nsISocketTransport* transport = info->mTransport;
+ // No longer needed outside this function, so clear the weak ref
+ info->mTransport = nullptr;
+ nsresult rv = info->HandshakeCallback(aFD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ transport->Close(rv);
+ }
+}
+
+nsresult TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD) {
+ nsresult rv;
+
+ UniqueCERTCertificate clientCert(SSL_PeerCertificate(aFD));
+ if (clientCert) {
+ nsCOMPtr<nsIX509CertDB> certDB =
+ do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509Cert> clientCertPSM;
+ nsTArray<uint8_t> clientCertBytes;
+ clientCertBytes.AppendElements(clientCert->derCert.data,
+ clientCert->derCert.len);
+ rv = certDB->ConstructX509(clientCertBytes, getter_AddRefs(clientCertPSM));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPeerCert = clientCertPSM;
+ }
+
+ SSLChannelInfo channelInfo;
+ rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ SSLCipherSuiteInfo cipherInfo;
+ rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof(cipherInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCipherName.Assign(cipherInfo.cipherSuiteName);
+ mKeyLength = cipherInfo.effectiveKeyBits;
+ mMacLength = cipherInfo.macBits;
+
+ // Notify consumer code that handshake is complete
+ nsCOMPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ mTlsVersionUsed = channelInfo.protocolVersion;
+ if (!mSecurityObserver) {
+ return NS_OK;
+ }
+ mSecurityObserver.swap(observer);
+ }
+ nsCOMPtr<nsITLSServerSocket> serverSocket;
+ GetServerSocket(getter_AddRefs(serverSocket));
+ observer->OnHandshakeDone(serverSocket, this);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TLSServerSocket.h b/netwerk/base/TLSServerSocket.h
new file mode 100644
index 0000000000..c8f4380d46
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.h
@@ -0,0 +1,81 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_TLSServerSocket_h
+#define mozilla_net_TLSServerSocket_h
+
+#include "nsIInterfaceRequestor.h"
+#include "nsITLSServerSocket.h"
+#include "nsServerSocket.h"
+#include "nsString.h"
+#include "mozilla/Mutex.h"
+#include "seccomon.h"
+
+namespace mozilla {
+namespace net {
+
+class TLSServerSocket final : public nsServerSocket, public nsITLSServerSocket {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSISERVERSOCKET(nsServerSocket::)
+ NS_DECL_NSITLSSERVERSOCKET
+
+ // Override methods from nsServerSocket
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const NetAddr& clientAddr) override;
+ virtual nsresult SetSocketDefaults() override;
+ virtual nsresult OnSocketListen() override;
+
+ TLSServerSocket() = default;
+
+ private:
+ virtual ~TLSServerSocket() = default;
+
+ static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig, PRBool isServer);
+
+ nsCOMPtr<nsIX509Cert> mServerCert;
+};
+
+class TLSServerConnectionInfo : public nsITLSServerConnectionInfo,
+ public nsITLSClientStatus,
+ public nsIInterfaceRequestor {
+ friend class TLSServerSocket;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERCONNECTIONINFO
+ NS_DECL_NSITLSCLIENTSTATUS
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ TLSServerConnectionInfo() = default;
+
+ private:
+ virtual ~TLSServerConnectionInfo();
+
+ static void HandshakeCallback(PRFileDesc* aFD, void* aArg);
+ nsresult HandshakeCallback(PRFileDesc* aFD);
+
+ RefPtr<TLSServerSocket> mServerSocket;
+ // Weak ref to the transport, to avoid cycles since the transport holds a
+ // reference to the TLSServerConnectionInfo object. This is not handed out to
+ // anyone, and is only used in HandshakeCallback to close the transport in
+ // case of an error. After this, it's set to nullptr.
+ nsISocketTransport* mTransport{nullptr};
+ nsCOMPtr<nsIX509Cert> mPeerCert;
+ int16_t mTlsVersionUsed{TLS_VERSION_UNKNOWN};
+ nsCString mCipherName;
+ uint32_t mKeyLength{0};
+ uint32_t mMacLength{0};
+ // lock protects access to mSecurityObserver
+ mozilla::Mutex mLock{"TLSServerConnectionInfo.mLock"};
+ nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver
+ MOZ_GUARDED_BY(mLock);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSServerSocket_h
diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp
new file mode 100644
index 0000000000..920e7623a7
--- /dev/null
+++ b/netwerk/base/TRRLoadInfo.cpp
@@ -0,0 +1,865 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TRRLoadInfo.h"
+#include "mozilla/dom/ClientSource.h"
+#include "nsContentUtils.h"
+#include "nsIRedirectHistoryEntry.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(TRRLoadInfo, nsILoadInfo)
+
+TRRLoadInfo::TRRLoadInfo(nsIURI* aResultPrincipalURI,
+ nsContentPolicyType aContentPolicyType)
+ : mResultPrincipalURI(aResultPrincipalURI),
+ mInternalContentPolicyType(aContentPolicyType) {}
+
+already_AddRefed<nsILoadInfo> TRRLoadInfo::Clone() const {
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new TRRLoadInfo(mResultPrincipalURI, mInternalContentPolicyType);
+
+ return loadInfo.forget();
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIPrincipal* TRRLoadInfo::VirtualGetLoadingPrincipal() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIPrincipal* TRRLoadInfo::TriggeringPrincipal() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIPrincipal* TRRLoadInfo::PrincipalToInherit() { return nullptr; }
+
+nsIPrincipal* TRRLoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) {
+ return nullptr;
+}
+
+const nsID& TRRLoadInfo::GetSandboxedNullPrincipalID() {
+ return mSandboxedNullPrincipalID;
+}
+
+void TRRLoadInfo::ResetSandboxedNullPrincipalID() {}
+
+nsIPrincipal* TRRLoadInfo::GetTopLevelPrincipal() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingDocument(Document** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsINode* TRRLoadInfo::LoadingNode() { return nullptr; }
+
+already_AddRefed<nsISupports> TRRLoadInfo::ContextForTopLevelLoad() {
+ return nullptr;
+}
+
+already_AddRefed<nsISupports> TRRLoadInfo::GetLoadingContext() {
+ return nullptr;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSandboxFlags(uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringSandboxFlags(uint32_t aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringWindowId(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringWindowId(uint64_t aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringStorageAccess(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringStorageAccess(bool aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSecurityMode(uint32_t* aFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsThirdPartyContextToTopWindow(
+ bool* aIsThirdPartyContextToTopWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsThirdPartyContextToTopWindow(
+ bool aIsThirdPartyContextToTopWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCookiePolicy(uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetStoragePermission(
+ nsILoadInfo::StoragePermissionState* aHasStoragePermission) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetStoragePermission(
+ nsILoadInfo::StoragePermissionState aHasStoragePermission) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+const Maybe<RFPTarget>& TRRLoadInfo::GetOverriddenFingerprintingSettings() {
+ return mOverriddenFingerprintingSettings;
+}
+
+void TRRLoadInfo::SetOverriddenFingerprintingSettings(RFPTarget aTargets) {}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsMetaRefresh(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsMetaRefresh(bool aResult) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAboutBlankInherits(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowChrome(bool* aResult) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetDisallowScript(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetDontFollowRedirects(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadErrorPage(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsFormSubmission(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsFormSubmission(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSendCSPViolationEvents(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetSendCSPViolationEvents(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) {
+ // We have to use nsContentPolicyType because ExtContentPolicyType is not
+ // visible from xpidl.
+ *aResult = static_cast<nsContentPolicyType>(
+ nsContentUtils::InternalContentPolicyTypeToExternal(
+ mInternalContentPolicyType));
+ return NS_OK;
+}
+
+nsContentPolicyType TRRLoadInfo::InternalContentPolicyType() {
+ return mInternalContentPolicyType;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBlockAllMixedContent(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetBrowserDidUpgradeInsecureRequests(
+ bool aBrowserDidUpgradeInsecureRequests) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetAllowInsecureRedirectToDataURI(
+ bool aAllowInsecureRedirectToDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowInsecureRedirectToDataURI(
+ bool* aAllowInsecureRedirectToDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceInheritPrincipalDropped(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetInnerWindowID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetWorkerAssociatedBrowsingContext(
+ dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::ResetPrincipalToInheritToNullPrincipal() {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult TRRLoadInfo::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult TRRLoadInfo::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetInitialSecurityCheckDone(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannelToDeriveFrom,
+ bool aIsInternalRedirect) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetRedirectChainIncludingInternalRedirects(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aChain) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>&
+TRRLoadInfo::RedirectChainIncludingInternalRedirects() {
+ return mEmptyRedirectChain;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetRedirectChain(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aChain) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>&
+TRRLoadInfo::RedirectChain() {
+ return mEmptyRedirectChain;
+}
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>& TRRLoadInfo::AncestorPrincipals() {
+ return mEmptyPrincipals;
+}
+
+const nsTArray<uint64_t>& TRRLoadInfo::AncestorBrowsingContextIDs() {
+ return mEmptyBrowsingContextIDs;
+}
+
+void TRRLoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders,
+ bool aForcePreflight) {}
+
+const nsTArray<nsCString>& TRRLoadInfo::CorsUnsafeHeaders() {
+ return mCorsUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForcePreflight(bool* aForcePreflight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsPreflight(bool* aIsPreflight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetServiceWorkerTaintingSynthesized(
+ bool* aServiceWorkerTaintingSynthesized) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTainting(uint32_t* aTaintingOut) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::MaybeIncreaseTainting(uint32_t aTainting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRLoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) {}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ bool* aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCspNonce(nsAString& aCspNonce) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetCspNonce(const nsAString& aCspNonce) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsTopLevelLoad(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsFromProcessingFrameAttributes(
+ bool* aIsFromProcessingFrameAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsMediaRequest(bool aIsMediaRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized(
+ bool* aShouldSkipCheckForBrokenURLOrZeroSized) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetResultPrincipalURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri = mResultPrincipalURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetResultPrincipalURI(nsIURI* aURI) {
+ mResultPrincipalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetRequestBlockingReason(uint32_t aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::GetRequestBlockingReason(uint32_t* aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRLoadInfo::SetClientInfo(const ClientInfo& aClientInfo) {}
+
+const Maybe<ClientInfo>& TRRLoadInfo::GetClientInfo() { return mClientInfo; }
+
+void TRRLoadInfo::GiveReservedClientSource(
+ UniquePtr<ClientSource>&& aClientSource) {}
+
+UniquePtr<ClientSource> TRRLoadInfo::TakeReservedClientSource() {
+ return nullptr;
+}
+
+void TRRLoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) {}
+
+void TRRLoadInfo::OverrideReservedClientInfoInParent(
+ const ClientInfo& aClientInfo) {}
+
+const Maybe<ClientInfo>& TRRLoadInfo::GetReservedClientInfo() {
+ return mReservedClientInfo;
+}
+
+void TRRLoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) {}
+
+const Maybe<ClientInfo>& TRRLoadInfo::GetInitialClientInfo() {
+ return mInitialClientInfo;
+}
+
+void TRRLoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) {
+}
+
+void TRRLoadInfo::ClearController() {}
+
+const Maybe<ServiceWorkerDescriptor>& TRRLoadInfo::GetController() {
+ return mController;
+}
+
+void TRRLoadInfo::SetPerformanceStorage(
+ PerformanceStorage* aPerformanceStorage) {}
+
+PerformanceStorage* TRRLoadInfo::GetPerformanceStorage() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetCsp() {
+ return nullptr;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetPreloadCsp() {
+ return nullptr;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetCspToInherit() {
+ return nullptr;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHstsStatus(bool* aHstsStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHstsStatus(bool aHstsStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHasValidUserGestureActivation(
+ bool* aHasValidUserGestureActivation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHasValidUserGestureActivation(
+ bool aHasValidUserGestureActivation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) {
+ *aResult = mInternalContentPolicyType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowDeprecatedSystemRequests(
+ bool* aAllowDeprecatedSystemRequests) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetAllowDeprecatedSystemRequests(
+ bool aAllowDeprecatedSystemRequests) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetParserCreatedScript(bool aParserCreatedScript) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetUnstrippedURI(nsIURI** aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetUnstrippedURI(nsIURI* aURI) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+nsIInterceptionInfo* TRRLoadInfo::InterceptionInfo() { return nullptr; }
+void TRRLoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aPrincipla) {}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHasInjectedCookieForCookieBannerHandling(
+ bool* aHasInjectedCookieForCookieBannerHandling) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHasInjectedCookieForCookieBannerHandling(
+ bool aHasInjectedCookieForCookieBannerHandling) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TRRLoadInfo.h b/netwerk/base/TRRLoadInfo.h
new file mode 100644
index 0000000000..2e08abe100
--- /dev/null
+++ b/netwerk/base/TRRLoadInfo.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TRRLoadInfo_h
+#define mozilla_TRRLoadInfo_h
+
+#include "nsILoadInfo.h"
+#include "nsIURI.h"
+#include "nsTArray.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/OriginAttributes.h"
+
+namespace mozilla {
+namespace net {
+
+// TRRLoadInfo is designed to be used by TRRServiceChannel only. Most of
+// nsILoadInfo functions are not implemented since TRRLoadInfo needs to
+// support off main thread.
+class TRRLoadInfo final : public nsILoadInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADINFO
+
+ TRRLoadInfo(nsIURI* aResultPrincipalURI,
+ nsContentPolicyType aContentPolicyType);
+
+ already_AddRefed<nsILoadInfo> Clone() const;
+
+ private:
+ virtual ~TRRLoadInfo() = default;
+
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsContentPolicyType mInternalContentPolicyType;
+ OriginAttributes mOriginAttributes;
+ nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>> mEmptyRedirectChain;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mEmptyPrincipals;
+ nsTArray<uint64_t> mEmptyBrowsingContextIDs;
+ nsTArray<nsCString> mCorsUnsafeHeaders;
+ nsID mSandboxedNullPrincipalID;
+ Maybe<mozilla::dom::ClientInfo> mClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mReservedClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mInitialClientInfo;
+ Maybe<mozilla::dom::ServiceWorkerDescriptor> mController;
+ Maybe<RFPTarget> mOverriddenFingerprintingSettings;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_TRRLoadInfo_h
diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp
new file mode 100644
index 0000000000..4313a6ecb3
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.cpp
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ThrottleQueue.h"
+#include "mozilla/net/InputChannelThrottleQueueParent.h"
+#include "nsISeekableStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIOService.h"
+#include "nsSocketTransportService2.h"
+#include "nsStreamUtils.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+class ThrottleInputStream final : public nsIAsyncInputStream,
+ public nsISeekableStream {
+ public:
+ ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ void AllowInput();
+
+ private:
+ ~ThrottleInputStream();
+
+ nsCOMPtr<nsIInputStream> mStream;
+ RefPtr<ThrottleQueue> mQueue;
+ nsresult mClosedStatus;
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream,
+ nsITellableStream, nsISeekableStream)
+
+ThrottleInputStream::ThrottleInputStream(nsIInputStream* aStream,
+ ThrottleQueue* aQueue)
+ : mStream(aStream), mQueue(aQueue), mClosedStatus(NS_OK) {
+ MOZ_ASSERT(aQueue != nullptr);
+}
+
+ThrottleInputStream::~ThrottleInputStream() { Close(); }
+
+NS_IMETHODIMP
+ThrottleInputStream::Close() {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ if (mQueue) {
+ mQueue->DequeueStream(this);
+ mQueue = nullptr;
+ mClosedStatus = NS_BASE_STREAM_CLOSED;
+ }
+ return mStream->Close();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Available(uint64_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ return mStream->Available(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::StreamStatus() {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->Read(aBuf, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MOZ_ASSERT(realCount <= aCount);
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Tell(int64_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsITellableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Tell(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::SetEOF() {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->SetEOF();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::CloseWithStatus(nsresult aStatus) {
+ if (NS_FAILED(mClosedStatus)) {
+ // Already closed, ignore.
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ mClosedStatus = Close();
+ if (NS_SUCCEEDED(mClosedStatus)) {
+ mClosedStatus = aStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ if (aFlags != 0) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mCallback = aCallback;
+ mEventTarget = aEventTarget;
+ if (mCallback) {
+ mQueue->QueueStream(this);
+ } else {
+ mQueue->DequeueStream(this);
+ }
+ return NS_OK;
+}
+
+void ThrottleInputStream::AllowInput() {
+ MOZ_ASSERT(mCallback);
+ nsCOMPtr<nsIInputStreamCallback> callbackEvent = NS_NewInputStreamReadyEvent(
+ "ThrottleInputStream::AllowInput", mCallback, mEventTarget);
+ mCallback = nullptr;
+ mEventTarget = nullptr;
+ callbackEvent->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+already_AddRefed<nsIInputChannelThrottleQueue> ThrottleQueue::Create() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIInputChannelThrottleQueue> tq;
+ if (nsIOService::UseSocketProcess()) {
+ tq = new InputChannelThrottleQueueParent();
+ } else {
+ tq = new ThrottleQueue();
+ }
+
+ return tq.forget();
+}
+
+NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback,
+ nsINamed)
+
+ThrottleQueue::ThrottleQueue()
+
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ }
+ if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts);
+}
+
+ThrottleQueue::~ThrottleQueue() {
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::RecordRead(uint32_t aBytesRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ ThrottleEntry entry;
+ entry.mTime = TimeStamp::Now();
+ entry.mBytesRead = aBytesRead;
+ mReadEvents.AppendElement(entry);
+ mBytesProcessed += aBytesRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1);
+ size_t i;
+
+ // Remove all stale events.
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ if (mReadEvents[i].mTime >= oneSecondAgo) {
+ break;
+ }
+ }
+ mReadEvents.RemoveElementsAt(0, i);
+
+ uint32_t totalBytes = 0;
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ totalBytes += mReadEvents[i].mBytesRead;
+ }
+
+ uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond;
+ double prob = static_cast<double>(rand()) / RAND_MAX;
+ uint32_t thisSliceBytes =
+ mMeanBytesPerSecond - spread + static_cast<uint32_t>(2 * spread * prob);
+
+ if (totalBytes >= thisSliceBytes) {
+ *aAvailable = 0;
+ } else {
+ *aAvailable = std::min(thisSliceBytes, aRemaining);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond) {
+ // Can be called on any thread.
+ if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 ||
+ aMaxBytesPerSecond < aMeanBytesPerSecond) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mMeanBytesPerSecond = aMeanBytesPerSecond;
+ mMaxBytesPerSecond = aMaxBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::BytesProcessed(uint64_t* aResult) {
+ *aResult = mBytesProcessed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::WrapStream(nsIInputStream* aInputStream,
+ nsIAsyncInputStream** aResult) {
+ nsCOMPtr<nsIAsyncInputStream> result =
+ new ThrottleInputStream(aInputStream, this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // A notified reader may need to push itself back on the queue.
+ // Swap out the list of readers so that this works properly.
+ nsTArray<RefPtr<ThrottleInputStream>> events = std::move(mAsyncEvents);
+
+ // Optimistically notify all the waiting readers, and then let them
+ // requeue if there isn't enough bandwidth.
+ for (size_t i = 0; i < events.Length(); ++i) {
+ events[i]->AllowInput();
+ }
+
+ mTimerArmed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::GetName(nsACString& aName) {
+ aName.AssignLiteral("net::ThrottleQueue");
+ return NS_OK;
+}
+
+void ThrottleQueue::QueueStream(ThrottleInputStream* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mAsyncEvents.IndexOf(aStream) ==
+ nsTArray<RefPtr<mozilla::net::ThrottleInputStream>>::NoIndex) {
+ mAsyncEvents.AppendElement(aStream);
+
+ if (!mTimerArmed) {
+ uint32_t ms = 1000;
+ if (mReadEvents.Length() > 0) {
+ TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1);
+ TimeStamp now = TimeStamp::Now();
+
+ if (t > now) {
+ ms = static_cast<uint32_t>((t - now).ToMilliseconds());
+ } else {
+ ms = 1;
+ }
+ }
+
+ if (NS_SUCCEEDED(
+ mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) {
+ mTimerArmed = true;
+ }
+ }
+ }
+}
+
+void ThrottleQueue::DequeueStream(ThrottleInputStream* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mAsyncEvents.RemoveElement(aStream);
+}
+
+NS_IMETHODIMP
+ThrottleQueue::GetMeanBytesPerSecond(uint32_t* aMeanBytesPerSecond) {
+ NS_ENSURE_ARG(aMeanBytesPerSecond);
+
+ *aMeanBytesPerSecond = mMeanBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::GetMaxBytesPerSecond(uint32_t* aMaxBytesPerSecond) {
+ NS_ENSURE_ARG(aMaxBytesPerSecond);
+
+ *aMaxBytesPerSecond = mMaxBytesPerSecond;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h
new file mode 100644
index 0000000000..4d06af8e64
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ThrottleQueue_h
+#define mozilla_net_ThrottleQueue_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsINamed.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+class ThrottleInputStream;
+
+/**
+ * An implementation of nsIInputChannelThrottleQueue that can be used
+ * to throttle uploads. This class is not thread-safe.
+ * Initialization and calls to WrapStream may be done on any thread;
+ * but otherwise, after creation, it can only be used on the socket
+ * thread. It currently throttles with a one second granularity, so
+ * may be a bit choppy.
+ */
+
+class ThrottleQueue : public nsIInputChannelThrottleQueue,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ static already_AddRefed<nsIInputChannelThrottleQueue> Create();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ void QueueStream(ThrottleInputStream* aStream);
+ void DequeueStream(ThrottleInputStream* aStream);
+
+ protected:
+ ThrottleQueue();
+ virtual ~ThrottleQueue();
+
+ struct ThrottleEntry {
+ TimeStamp mTime;
+ uint32_t mBytesRead = 0;
+ };
+
+ nsTArray<ThrottleEntry> mReadEvents;
+ uint32_t mMeanBytesPerSecond{0};
+ uint32_t mMaxBytesPerSecond{0};
+ uint64_t mBytesProcessed{0};
+
+ nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents;
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerArmed{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ThrottleQueue_h
diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp
new file mode 100644
index 0000000000..3030b24653
--- /dev/null
+++ b/netwerk/base/Tickler.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Tickler.h"
+
+#ifdef MOZ_USE_WIFI_TICKLER
+# include "nsComponentManagerUtils.h"
+# include "nsINamed.h"
+# include "nsServiceManagerUtils.h"
+# include "nsThreadUtils.h"
+# include "prnetdb.h"
+
+# include "mozilla/java/GeckoAppShellWrappers.h"
+# include "mozilla/jni/Utils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler)
+
+Tickler::Tickler()
+ : mLock("Tickler::mLock"),
+ mActive(false),
+ mCanceled(false),
+ mEnabled(false),
+ mDelay(16),
+ mDuration(TimeDuration::FromMilliseconds(400)),
+ mFD(nullptr) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+Tickler::~Tickler() {
+ // non main thread uses of the tickler should hold weak
+ // references to it if they must hold a reference at all
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mThread) {
+ mThread->AsyncShutdown();
+ mThread = nullptr;
+ }
+
+ if (mTimer) mTimer->Cancel();
+ if (mFD) PR_Close(mFD);
+}
+
+nsresult Tickler::Init() {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mTimer);
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(!mThread);
+ MOZ_ASSERT(!mFD);
+
+ if (jni::IsAvailable()) {
+ java::GeckoAppShell::EnableNetworkNotifications();
+ }
+
+ mFD = PR_OpenUDPSocket(PR_AF_INET);
+ if (!mFD) return NS_ERROR_FAILURE;
+
+ // make sure new socket has a ttl of 1
+ // failure is not fatal.
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_IpTimeToLive;
+ opt.value.ip_ttl = 1;
+ PR_SetSocketOption(mFD, &opt);
+
+ nsresult rv = NS_NewNamedThread("wifi tickler", getter_AddRefs(mThread));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITimer> tmpTimer = NS_NewTimer(mThread);
+ if (!tmpTimer) return NS_ERROR_OUT_OF_MEMORY;
+
+ mTimer.swap(tmpTimer);
+
+ mAddr.inet.family = PR_AF_INET;
+ mAddr.inet.port = PR_htons(4886);
+ mAddr.inet.ip = 0;
+
+ return NS_OK;
+}
+
+void Tickler::Tickle() {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread);
+ mLastTickle = TimeStamp::Now();
+ if (!mActive) MaybeStartTickler();
+}
+
+void Tickler::PostCheckTickler() {
+ mLock.AssertCurrentThreadOwns();
+ mThread->Dispatch(NewRunnableMethod("net::Tickler::CheckTickler", this,
+ &Tickler::CheckTickler),
+ NS_DISPATCH_NORMAL);
+ return;
+}
+
+void Tickler::MaybeStartTicklerUnlocked() {
+ MutexAutoLock lock(mLock);
+ MaybeStartTickler();
+}
+
+void Tickler::MaybeStartTickler() {
+ mLock.AssertCurrentThreadOwns();
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod("net::Tickler::MaybeStartTicklerUnlocked", this,
+ &Tickler::MaybeStartTicklerUnlocked));
+ return;
+ }
+
+ if (!mPrefs) mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (mPrefs) {
+ int32_t val;
+ bool boolVal;
+
+ if (NS_SUCCEEDED(
+ mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal)))
+ mEnabled = boolVal;
+
+ if (NS_SUCCEEDED(
+ mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) {
+ if (val < 1) val = 1;
+ if (val > 100000) val = 100000;
+ mDuration = TimeDuration::FromMilliseconds(val);
+ }
+
+ if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) {
+ if (val < 1) val = 1;
+ if (val > 1000) val = 1000;
+ mDelay = static_cast<uint32_t>(val);
+ }
+ }
+
+ PostCheckTickler();
+}
+
+void Tickler::CheckTickler() {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+
+ bool shouldRun =
+ (!mCanceled) && ((TimeStamp::Now() - mLastTickle) <= mDuration);
+
+ if ((shouldRun && mActive) || (!shouldRun && !mActive))
+ return; // no change in state
+
+ if (mActive)
+ StopTickler();
+ else
+ StartTickler();
+}
+
+void Tickler::Cancel() {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(NS_IsMainThread());
+ mCanceled = true;
+ if (mThread) PostCheckTickler();
+}
+
+void Tickler::StopTickler() {
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(mTimer);
+ MOZ_ASSERT(mActive);
+
+ mTimer->Cancel();
+ mActive = false;
+}
+
+class TicklerTimer final : public nsITimerCallback, public nsINamed {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ explicit TicklerTimer(Tickler* aTickler) {
+ mTickler = do_GetWeakReference(aTickler);
+ }
+
+ // nsINamed
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("TicklerTimer");
+ return NS_OK;
+ }
+
+ private:
+ ~TicklerTimer() {}
+
+ nsWeakPtr mTickler;
+};
+
+void Tickler::StartTickler() {
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(mTimer);
+
+ if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this),
+ mEnabled ? mDelay : 1000,
+ nsITimer::TYPE_REPEATING_SLACK)))
+ mActive = true;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Address(uint32_t address) { mAddr.inet.ip = address; }
+
+// argument should be in network byte order
+void Tickler::SetIPV4Port(uint16_t port) { mAddr.inet.port = port; }
+
+NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP TicklerTimer::Notify(nsITimer* timer) {
+ RefPtr<Tickler> tickler = do_QueryReferent(mTickler);
+ if (!tickler) return NS_ERROR_FAILURE;
+ MutexAutoLock lock(tickler->mLock);
+
+ if (!tickler->mFD) {
+ tickler->StopTickler();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (tickler->mCanceled ||
+ ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) {
+ tickler->StopTickler();
+ return NS_OK;
+ }
+
+ if (!tickler->mEnabled) return NS_OK;
+
+ PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+namespace mozilla {
+namespace net {
+NS_IMPL_ISUPPORTS0(Tickler)
+} // namespace net
+} // namespace mozilla
+
+#endif // defined MOZ_USE_WIFI_TICKLER
diff --git a/netwerk/base/Tickler.h b/netwerk/base/Tickler.h
new file mode 100644
index 0000000000..264ecefebb
--- /dev/null
+++ b/netwerk/base/Tickler.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Tickler_h
+#define mozilla_net_Tickler_h
+
+// The tickler sends a regular 0 byte UDP heartbeat out to a
+// particular address for a short time after it has been touched. This
+// is used on some mobile wifi chipsets to mitigate Power Save Polling
+// (PSP) Mode when we are anticipating a response packet
+// soon. Typically PSP adds 100ms of latency to a read event because
+// the packet delivery is not triggered until the 802.11 beacon is
+// delivered to the host (100ms is the standard Access Point
+// configuration for the beacon interval.) Requesting a frequent
+// transmission and getting a CTS frame from the AP at least that
+// frequently allows for low latency receives when we have reason to
+// expect them (e.g a SYN-ACK).
+//
+// The tickler is used to allow RTT based phases of web transport to
+// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL
+// handshake, HTTP headers, and the TCP slow start phase. The
+// transaction is given up to 400 miliseconds by default to get
+// through those phases before the tickler is disabled.
+//
+// The tickler only applies to wifi on mobile right now. Hopefully it
+// can also be restricted to particular handset models in the future.
+
+#if defined(ANDROID) && !defined(MOZ_PROXY_BYPASS_PROTECTION)
+# define MOZ_USE_WIFI_TICKLER
+#endif
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include <stdint.h>
+
+#ifdef MOZ_USE_WIFI_TICKLER
+# include "mozilla/Mutex.h"
+# include "mozilla/TimeStamp.h"
+# include "nsISupports.h"
+# include "nsIThread.h"
+# include "nsITimer.h"
+# include "nsWeakReference.h"
+# include "prio.h"
+
+class nsIPrefBranch;
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifdef MOZ_USE_WIFI_TICKLER
+
+// 8f769ed6-207c-4af9-9f7e-9e832da3754e
+# define NS_TICKLER_IID \
+ { \
+ 0x8f769ed6, 0x207c, 0x4af9, { \
+ 0x9f, 0x7e, 0x9e, 0x83, 0x2d, 0xa3, 0x75, 0x4e \
+ } \
+ }
+
+class Tickler final : public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TICKLER_IID)
+
+ // These methods are main thread only
+ Tickler();
+ void Cancel();
+ nsresult Init();
+ void SetIPV4Address(uint32_t address);
+ void SetIPV4Port(uint16_t port);
+
+ // Tickle the tickler to (re-)start the activity.
+ // May call from any thread
+ void Tickle();
+
+ private:
+ ~Tickler();
+
+ friend class TicklerTimer;
+ Mutex mLock MOZ_UNANNOTATED;
+ nsCOMPtr<nsIThread> mThread;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIPrefBranch> mPrefs;
+
+ bool mActive;
+ bool mCanceled;
+ bool mEnabled;
+ uint32_t mDelay;
+ TimeDuration mDuration;
+ PRFileDesc* mFD;
+
+ TimeStamp mLastTickle;
+ PRNetAddr mAddr;
+
+ // These functions may be called from any thread
+ void PostCheckTickler();
+ void MaybeStartTickler();
+ void MaybeStartTicklerUnlocked();
+
+ // Tickler thread only
+ void CheckTickler();
+ void StartTickler();
+ void StopTickler();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Tickler, NS_TICKLER_IID)
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+class Tickler final : public nsISupports {
+ ~Tickler() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Tickler() = default;
+ nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; }
+ void Cancel() {}
+ void SetIPV4Address(uint32_t){};
+ void SetIPV4Port(uint16_t) {}
+ void Tickle() {}
+};
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Tickler_h
diff --git a/netwerk/base/ascii_pac_utils.js b/netwerk/base/ascii_pac_utils.js
new file mode 100644
index 0000000000..cedb1a0f21
--- /dev/null
+++ b/netwerk/base/ascii_pac_utils.js
@@ -0,0 +1,256 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global dnsResolve */
+
+function dnsDomainIs(host, domain) {
+ return (
+ host.length >= domain.length &&
+ host.substring(host.length - domain.length) == domain
+ );
+}
+
+function dnsDomainLevels(host) {
+ return host.split(".").length - 1;
+}
+
+function isValidIpAddress(ipchars) {
+ var matches = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ipchars);
+ if (matches == null) {
+ return false;
+ } else if (
+ matches[1] > 255 ||
+ matches[2] > 255 ||
+ matches[3] > 255 ||
+ matches[4] > 255
+ ) {
+ return false;
+ }
+ return true;
+}
+
+function convert_addr(ipchars) {
+ var bytes = ipchars.split(".");
+ var result =
+ ((bytes[0] & 0xff) << 24) |
+ ((bytes[1] & 0xff) << 16) |
+ ((bytes[2] & 0xff) << 8) |
+ (bytes[3] & 0xff);
+ return result;
+}
+
+function isInNet(ipaddr, pattern, maskstr) {
+ if (!isValidIpAddress(pattern) || !isValidIpAddress(maskstr)) {
+ return false;
+ }
+ if (!isValidIpAddress(ipaddr)) {
+ ipaddr = dnsResolve(ipaddr);
+ if (ipaddr == null) {
+ return false;
+ }
+ }
+ var host = convert_addr(ipaddr);
+ var pat = convert_addr(pattern);
+ var mask = convert_addr(maskstr);
+ return (host & mask) == (pat & mask);
+}
+
+function isPlainHostName(host) {
+ return host.search("(\\.)|:") == -1;
+}
+
+function isResolvable(host) {
+ var ip = dnsResolve(host);
+ return ip != null;
+}
+
+function localHostOrDomainIs(host, hostdom) {
+ return host == hostdom || hostdom.lastIndexOf(host + ".", 0) == 0;
+}
+
+function shExpMatch(url, pattern) {
+ pattern = pattern.replace(/\./g, "\\.");
+ pattern = pattern.replace(/\*/g, ".*");
+ pattern = pattern.replace(/\?/g, ".");
+ var newRe = new RegExp("^" + pattern + "$");
+ return newRe.test(url);
+}
+
+var wdays = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 };
+var months = {
+ JAN: 0,
+ FEB: 1,
+ MAR: 2,
+ APR: 3,
+ MAY: 4,
+ JUN: 5,
+ JUL: 6,
+ AUG: 7,
+ SEP: 8,
+ OCT: 9,
+ NOV: 10,
+ DEC: 11,
+};
+
+function weekdayRange() {
+ function getDay(weekday) {
+ if (weekday in wdays) {
+ return wdays[weekday];
+ }
+ return -1;
+ }
+ var date = new Date();
+ var argc = arguments.length;
+ var wday;
+ if (argc < 1) {
+ return false;
+ }
+ if (arguments[argc - 1] == "GMT") {
+ argc--;
+ wday = date.getUTCDay();
+ } else {
+ wday = date.getDay();
+ }
+ var wd1 = getDay(arguments[0]);
+ var wd2 = argc == 2 ? getDay(arguments[1]) : wd1;
+ if (wd1 == -1 || wd2 == -1) {
+ return false;
+ }
+
+ if (wd1 <= wd2) {
+ return wd1 <= wday && wday <= wd2;
+ }
+
+ return wd2 >= wday || wday >= wd1;
+}
+
+function dateRange() {
+ function getMonth(name) {
+ if (name in months) {
+ return months[name];
+ }
+ return -1;
+ }
+ var date = new Date();
+ var argc = arguments.length;
+ if (argc < 1) {
+ return false;
+ }
+ var isGMT = arguments[argc - 1] == "GMT";
+
+ if (isGMT) {
+ argc--;
+ }
+ // function will work even without explict handling of this case
+ if (argc == 1) {
+ let tmp = parseInt(arguments[0]);
+ if (isNaN(tmp)) {
+ return (
+ (isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0])
+ );
+ } else if (tmp < 32) {
+ return (isGMT ? date.getUTCDate() : date.getDate()) == tmp;
+ }
+ return (isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp;
+ }
+ var year = date.getFullYear();
+ var date1, date2;
+ date1 = new Date(year, 0, 1, 0, 0, 0);
+ date2 = new Date(year, 11, 31, 23, 59, 59);
+ var adjustMonth = false;
+ for (let i = 0; i < argc >> 1; i++) {
+ let tmp = parseInt(arguments[i]);
+ if (isNaN(tmp)) {
+ let mon = getMonth(arguments[i]);
+ date1.setMonth(mon);
+ } else if (tmp < 32) {
+ adjustMonth = argc <= 2;
+ date1.setDate(tmp);
+ } else {
+ date1.setFullYear(tmp);
+ }
+ }
+ for (let i = argc >> 1; i < argc; i++) {
+ let tmp = parseInt(arguments[i]);
+ if (isNaN(tmp)) {
+ let mon = getMonth(arguments[i]);
+ date2.setMonth(mon);
+ } else if (tmp < 32) {
+ date2.setDate(tmp);
+ } else {
+ date2.setFullYear(tmp);
+ }
+ }
+ if (adjustMonth) {
+ date1.setMonth(date.getMonth());
+ date2.setMonth(date.getMonth());
+ }
+ if (isGMT) {
+ let tmp = date;
+ tmp.setFullYear(date.getUTCFullYear());
+ tmp.setMonth(date.getUTCMonth());
+ tmp.setDate(date.getUTCDate());
+ tmp.setHours(date.getUTCHours());
+ tmp.setMinutes(date.getUTCMinutes());
+ tmp.setSeconds(date.getUTCSeconds());
+ date = tmp;
+ }
+ return date1 <= date2
+ ? date1 <= date && date <= date2
+ : date2 >= date || date >= date1;
+}
+
+function timeRange() {
+ var argc = arguments.length;
+ var date = new Date();
+ var isGMT = false;
+ if (argc < 1) {
+ return false;
+ }
+ if (arguments[argc - 1] == "GMT") {
+ isGMT = true;
+ argc--;
+ }
+
+ var hour = isGMT ? date.getUTCHours() : date.getHours();
+ var date1, date2;
+ date1 = new Date();
+ date2 = new Date();
+
+ if (argc == 1) {
+ return hour == arguments[0];
+ } else if (argc == 2) {
+ return arguments[0] <= hour && hour <= arguments[1];
+ }
+ switch (argc) {
+ case 6:
+ date1.setSeconds(arguments[2]);
+ date2.setSeconds(arguments[5]);
+ // falls through
+ case 4:
+ var middle = argc >> 1;
+ date1.setHours(arguments[0]);
+ date1.setMinutes(arguments[1]);
+ date2.setHours(arguments[middle]);
+ date2.setMinutes(arguments[middle + 1]);
+ if (middle == 2) {
+ date2.setSeconds(59);
+ }
+ break;
+ default:
+ throw new Error("timeRange: bad number of arguments");
+ }
+
+ if (isGMT) {
+ date.setFullYear(date.getUTCFullYear());
+ date.setMonth(date.getUTCMonth());
+ date.setDate(date.getUTCDate());
+ date.setHours(date.getUTCHours());
+ date.setMinutes(date.getUTCMinutes());
+ date.setSeconds(date.getUTCSeconds());
+ }
+ return date1 <= date2
+ ? date1 <= date && date <= date2
+ : date2 >= date || date >= date1;
+}
diff --git a/netwerk/base/http-sfv/Cargo.toml b/netwerk/base/http-sfv/Cargo.toml
new file mode 100644
index 0000000000..4625cdf974
--- /dev/null
+++ b/netwerk/base/http-sfv/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "http_sfv"
+version = "0.1.0"
+authors = ["barabass <yalyna.ts@gmail.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+sfv = "0.9.1"
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
diff --git a/netwerk/base/http-sfv/SFVService.cpp b/netwerk/base/http-sfv/SFVService.cpp
new file mode 100644
index 0000000000..9759993e7f
--- /dev/null
+++ b/netwerk/base/http-sfv/SFVService.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCOMPtr.h"
+#include "SFVService.h"
+
+// This anonymous namespace prevents outside C++ code from improperly accessing
+// these implementation details.
+namespace {
+extern "C" {
+// Implemented in Rust.
+void new_sfv_service(nsISFVService** result);
+}
+
+static mozilla::StaticRefPtr<nsISFVService> sService;
+} // namespace
+
+namespace mozilla::net {
+
+already_AddRefed<nsISFVService> GetSFVService() {
+ nsCOMPtr<nsISFVService> service;
+
+ if (sService) {
+ service = sService;
+ } else {
+ new_sfv_service(getter_AddRefs(service));
+ sService = service;
+ mozilla::ClearOnShutdown(&sService);
+ }
+
+ return service.forget();
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/http-sfv/SFVService.h b/netwerk/base/http-sfv/SFVService.h
new file mode 100644
index 0000000000..4016951609
--- /dev/null
+++ b/netwerk/base/http-sfv/SFVService.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStructuredFieldValues.h"
+namespace mozilla {
+namespace net {
+
+already_AddRefed<nsISFVService> GetSFVService();
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/http-sfv/moz.build b/netwerk/base/http-sfv/moz.build
new file mode 100644
index 0000000000..ed857c91a8
--- /dev/null
+++ b/netwerk/base/http-sfv/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIStructuredFieldValues.idl",
+]
+
+XPIDL_MODULE = "http-sfv"
+
+EXPORTS.mozilla.net += ["SFVService.h"]
+
+SOURCES += ["SFVService.cpp"]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/base/http-sfv/nsIStructuredFieldValues.idl b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl
new file mode 100644
index 0000000000..3f02b33953
--- /dev/null
+++ b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl
@@ -0,0 +1,290 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Conceptually, there are three types of structured field (header) values:
+ *
+ * Item - can be an Integer, Decimal, String, Token, Byte Sequence, or Boolean.
+ * It can have associated Parameters.
+ * List - array of zero or more members, each of which can be an Item or an InnerList,
+ * both of which can be Parameterized.
+ * Dictionary - ordered map of name-value pairs, where the names are short textual strings
+ * and the values are Items or arrays of Items (represented with InnerList),
+ * both of which can be Parameterized. There can be zero or more members,
+ * and their names are unique in the scope of the Dictionary they occur within.
+ *
+ *
+ * There's also a few primitive types used to construct structured field values:
+ * - BareItem used as Item's value or as a parameter value in Parameters.
+ * - Parameters are an ordered map of key-value pairs that are associated with an Item or InnerList.
+ * The keys are unique within the scope the Parameters they occur within, and the values are BareItem.
+ * - InnerList is an array of zero or more Items. Can have Parameters.
+ * - ListEntry represents either Item or InnerList as a member of List or as member-value in Dictionary.
+ */
+
+
+
+/**
+ * nsISFVBareItem is a building block for Item header value (nsISFVItem) and Parameters (nsISFVParams).
+ * It can be of type BOOL, STRING, DECIMAL, INTEGER, TOKEN, BYTE_SEQUENCE.
+ * Each type is represented by its own interface which is used to create
+ * a bare item of that type.
+ */
+[scriptable, builtinclass, uuid(7072853f-215b-4a8a-92e5-9732bccc377b)]
+interface nsISFVBareItem : nsISupports {
+ const long BOOL = 1;
+ const long STRING = 2;
+ const long DECIMAL = 3;
+ const long INTEGER = 4;
+ const long TOKEN = 5;
+ const long BYTE_SEQUENCE = 6;
+
+ /**
+ * Returns value associated with type of bare item.
+ * Used to identify type of bare item without querying for interface
+ * (like nsISFVString, etc).
+ */
+ readonly attribute long type;
+};
+
+[scriptable, builtinclass, uuid(843eea44-990a-422c-bbf2-2aa4ba9ee4d2)]
+interface nsISFVInteger : nsISFVBareItem {
+ attribute long long value;
+};
+
+[scriptable, builtinclass, uuid(df6a0787-7caa-4fef-b145-08c1104c2fde)]
+interface nsISFVString : nsISFVBareItem {
+ attribute ACString value;
+};
+
+[scriptable, builtinclass, uuid(d263c6d7-4123-4c39-a121-ccf874a19012)]
+interface nsISFVBool : nsISFVBareItem {
+ attribute boolean value;
+};
+
+[scriptable, builtinclass, uuid(1098da8b-b4df-4526-b985-53dbd4160ad2)]
+interface nsISFVDecimal : nsISFVBareItem {
+ attribute double value;
+};
+
+[scriptable, builtinclass, uuid(8ad33d52-b9b2-4a17-8aa8-991250fc1214)]
+interface nsISFVToken : nsISFVBareItem {
+ attribute ACString value;
+};
+
+[scriptable, builtinclass, uuid(887eaef0-19fe-42bc-9a42-9ff773aa8fea)]
+interface nsISFVByteSeq : nsISFVBareItem {
+ attribute ACString value;
+};
+
+
+/**
+ * nsISFVParams represents parameters, key-value pairs of ACString and nsISFVBareItem,
+ * which parametrize Item type header or InnerList type withing List type header.
+ */
+[scriptable, builtinclass, uuid(b1a397d7-3333-43e7-993a-fbe8ab90ee96)]
+interface nsISFVParams : nsISupports {
+ /**
+ * Return value (nsISFVBareItem) stored for key, if it is present
+ *
+ * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters.
+ */
+ nsISFVBareItem get(in ACString key);
+
+ /**
+ * Insert a new key-value pair.
+ * If an equivalent key already exists: the key remains and retains in its place in the order,
+ * its corresponding value is updated with the new value.
+ *
+ * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVBareItem interface.
+ */
+ void set(in ACString key, in nsISFVBareItem item);
+
+ /**
+ * Remove the key-value pair equivalent to key.
+ *
+ * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters.
+ */
+ void delete(in ACString key);
+
+ /**
+ * Returns array of keys.
+ */
+ Array<ACString> keys();
+};
+
+/**
+ * nsISFVParametrizable is implemented for types that
+ * can be parametrized with nsISFVParams
+ */
+[scriptable, builtinclass, uuid(6c0399f8-01de-4b25-b339-68e35e8d2e49)]
+interface nsISFVParametrizable : nsISupports {
+ readonly attribute nsISFVParams params;
+};
+
+/**
+ * nsISFVItemOrInnerList represents member in nsISFVList
+ * or member-value in nsISFVDictionary.
+ * nsISFVItemOrInnerList is implemented for
+ * nsISFVItem or nsISFVInnerList, both of which are used
+ * to create nsISFVList and nsISFVDictionary.
+ */
+[scriptable, builtinclass, uuid(99ac1b56-b5b3-44e7-ad96-db7444aae4b2)]
+interface nsISFVItemOrInnerList : nsISFVParametrizable {
+};
+
+/**
+ * nsISFVSerialize indicates that object can be serialized into ACString.
+ */
+[scriptable, builtinclass, uuid(28b9215d-c131-413c-9482-0004a371a5ec)]
+interface nsISFVSerialize : nsISupports {
+ ACString serialize();
+};
+
+/**
+ * nsISFVItem represents Item structured header value.
+ */
+[scriptable, builtinclass, uuid(abe8826b-6af7-4e54-bd2c-46ab231700ce)]
+interface nsISFVItem : nsISFVItemOrInnerList {
+ readonly attribute nsISFVBareItem value;
+ ACString serialize();
+};
+
+/**
+ * nsISFVInnerList can be used as a member of nsISFVList
+ * or a member-value of nsISFVDictionary.
+ */
+[scriptable, builtinclass, uuid(b2e52be2-8488-41b2-9ee2-3c48d92d095c)]
+interface nsISFVInnerList : nsISFVItemOrInnerList {
+ attribute Array<nsISFVItem> items;
+};
+
+/**
+ * nsISFVList represents List structured header value.
+ */
+[scriptable, builtinclass, uuid(02bb92a6-d1de-449c-b54f-d137f30c613d)]
+interface nsISFVList : nsISFVSerialize {
+ /**
+ * Returns array of members.
+ * QueryInterface can be used on a member to get more specific type.
+ */
+ attribute Array<nsISFVItemOrInnerList> members;
+
+ /**
+ * In case when header value is split across lines, it's possible
+ * this method parses supplied line and merges it with members of existing object.
+ */
+ void parseMore(in ACString header);
+};
+
+/**
+ * nsISFVDictionary represents nsISFVDictionary structured header value.
+ */
+[scriptable, builtinclass, uuid(6642a7fe-7026-4eba-b730-05e230ee3437)]
+interface nsISFVDictionary : nsISFVSerialize {
+
+ /**
+ * Return value (nsISFVItemOrInnerList) stored for key, if it is present.
+ * QueryInterface can be used on a value to get more specific type.
+ *
+ * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters.
+ */
+ nsISFVItemOrInnerList get(in ACString key);
+
+ /**
+ * Insert a new key-value pair.
+ * If an equivalent key already exists: the key remains and retains in its place in the order,
+ * its corresponding value is updated with the new value.
+ *
+ * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVItemOrInnerList interface.
+ */
+ void set(in ACString key, in nsISFVItemOrInnerList member_value);
+
+ /**
+ * Remove the key-value pair equivalent to key.
+ *
+ * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters.
+ */
+ void delete(in ACString key);
+
+ /**
+ * Returns array of keys.
+ */
+ Array<ACString> keys();
+
+ /**
+ * In case when header value is split across lines, it's possible
+ * this method parses supplied line and merges it with members of existing object.
+ */
+ void parseMore(in ACString header);
+};
+
+
+/**
+ * nsISFVService provides a set of functions for working with HTTP header value as an object.
+ * It exposes functions for creating object from string containing header value,
+ * as well as individual components for manual structured header object creation.
+ */
+[scriptable, builtinclass, uuid(049f4be1-2f22-4438-a8da-518552ed390c)]
+interface nsISFVService: nsISupports
+{
+ /**
+ * Parses provided string into Dictionary header value (nsISFVDictionary).
+ *
+ * @throws NS_ERROR_FAILURE if parsing fails.
+ */
+ nsISFVDictionary parseDictionary(in ACString header);
+
+ /**
+ * Parses provided string into List header value (nsISFVList).
+ *
+ * @throws NS_ERROR_FAILURE if parsing fails.
+ */
+ nsISFVList parseList(in ACString header);
+
+ /**
+ * Parses provided string into Item header value (nsISFVItem).
+ *
+ * @throws NS_ERROR_FAILURE if parsing fails.
+ */
+ nsISFVItem parseItem(in ACString header);
+
+ /**
+ * The following functions create bare item of specific type.
+ */
+ nsISFVInteger newInteger(in long long value);
+ nsISFVBool newBool(in bool value);
+ nsISFVDecimal newDecimal(in double value);
+ nsISFVString newString(in ACString value);
+ nsISFVByteSeq newByteSequence(in ACString value);
+ nsISFVToken newToken(in ACString value);
+
+ /**
+ * Creates nsISFVParams with no parameters. In other words, it's an empty map byt default.
+ */
+ nsISFVParams newParameters();
+
+ /**
+ * Creates nsISFVInnerList from nsISFVItem array and nsISFVParams.
+ */
+ nsISFVInnerList newInnerList(in Array<nsISFVItem> items, in nsISFVParams params);
+
+ /**
+ * Creates nsISFVItem, which represents Item header value, from nsISFVBareItem and associated nsISFVParams.
+ */
+ nsISFVItem newItem(in nsISFVBareItem value, in nsISFVParams params);
+
+ /**
+ * Creates nsISFVList, which represents List header value, from array of nsISFVItemOrInnerList.
+ * nsISFVItemOrInnerList represens either Item (nsISFVItem) or Inner List (nsISFVInnerList).
+ */
+ nsISFVList newList(in Array<nsISFVItemOrInnerList> members);
+
+ /**
+ * Creates nsISFVDictionary representing Dictionary header value. It is empty by default.
+ */
+ nsISFVDictionary newDictionary();
+};
diff --git a/netwerk/base/http-sfv/src/lib.rs b/netwerk/base/http-sfv/src/lib.rs
new file mode 100644
index 0000000000..fe669f5f3f
--- /dev/null
+++ b/netwerk/base/http-sfv/src/lib.rs
@@ -0,0 +1,873 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NULL_POINTER, NS_ERROR_UNEXPECTED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use sfv::Parser;
+use sfv::SerializeValue;
+use sfv::{
+ BareItem, Decimal, Dictionary, FromPrimitive, InnerList, Item, List, ListEntry, Parameters,
+ ParseMore,
+};
+use std::cell::RefCell;
+use std::ops::Deref;
+use thin_vec::ThinVec;
+use xpcom::interfaces::{
+ nsISFVBareItem, nsISFVBool, nsISFVByteSeq, nsISFVDecimal, nsISFVDictionary, nsISFVInnerList,
+ nsISFVInteger, nsISFVItem, nsISFVItemOrInnerList, nsISFVList, nsISFVParams, nsISFVService,
+ nsISFVString, nsISFVToken,
+};
+use xpcom::{xpcom, xpcom_method, RefPtr, XpCom};
+
+#[no_mangle]
+pub unsafe extern "C" fn new_sfv_service(result: *mut *const nsISFVService) {
+ let service: RefPtr<SFVService> = SFVService::new();
+ RefPtr::new(service.coerce::<nsISFVService>()).forget(&mut *result);
+}
+
+#[xpcom(implement(nsISFVService), atomic)]
+struct SFVService {}
+
+impl SFVService {
+ fn new() -> RefPtr<SFVService> {
+ SFVService::allocate(InitSFVService {})
+ }
+
+ xpcom_method!(parse_dictionary => ParseDictionary(header: *const nsACString) -> *const nsISFVDictionary);
+ fn parse_dictionary(&self, header: &nsACString) -> Result<RefPtr<nsISFVDictionary>, nsresult> {
+ let parsed_dict = Parser::parse_dictionary(&header).map_err(|_| NS_ERROR_FAILURE)?;
+ let sfv_dict = SFVDictionary::new();
+ sfv_dict.value.replace(parsed_dict);
+ Ok(RefPtr::new(sfv_dict.coerce::<nsISFVDictionary>()))
+ }
+
+ xpcom_method!(parse_list => ParseList(field_value: *const nsACString) -> *const nsISFVList);
+ fn parse_list(&self, header: &nsACString) -> Result<RefPtr<nsISFVList>, nsresult> {
+ let parsed_list = Parser::parse_list(&header).map_err(|_| NS_ERROR_FAILURE)?;
+
+ let mut nsi_members = Vec::new();
+ for item_or_inner_list in parsed_list.iter() {
+ nsi_members.push(interface_from_list_entry(item_or_inner_list)?)
+ }
+ let sfv_list = SFVList::allocate(InitSFVList {
+ members: RefCell::new(nsi_members),
+ });
+ Ok(RefPtr::new(sfv_list.coerce::<nsISFVList>()))
+ }
+
+ xpcom_method!(parse_item => ParseItem(header: *const nsACString) -> *const nsISFVItem);
+ fn parse_item(&self, header: &nsACString) -> Result<RefPtr<nsISFVItem>, nsresult> {
+ let parsed_item = Parser::parse_item(&header).map_err(|_| NS_ERROR_FAILURE)?;
+ interface_from_item(&parsed_item)
+ }
+
+ xpcom_method!(new_integer => NewInteger(value: i64) -> *const nsISFVInteger);
+ fn new_integer(&self, value: i64) -> Result<RefPtr<nsISFVInteger>, nsresult> {
+ Ok(RefPtr::new(
+ SFVInteger::new(value).coerce::<nsISFVInteger>(),
+ ))
+ }
+
+ xpcom_method!(new_decimal => NewDecimal(value: f64) -> *const nsISFVDecimal);
+ fn new_decimal(&self, value: f64) -> Result<RefPtr<nsISFVDecimal>, nsresult> {
+ Ok(RefPtr::new(
+ SFVDecimal::new(value).coerce::<nsISFVDecimal>(),
+ ))
+ }
+
+ xpcom_method!(new_bool => NewBool(value: bool) -> *const nsISFVBool);
+ fn new_bool(&self, value: bool) -> Result<RefPtr<nsISFVBool>, nsresult> {
+ Ok(RefPtr::new(SFVBool::new(value).coerce::<nsISFVBool>()))
+ }
+
+ xpcom_method!(new_string => NewString(value: *const nsACString) -> *const nsISFVString);
+ fn new_string(&self, value: &nsACString) -> Result<RefPtr<nsISFVString>, nsresult> {
+ Ok(RefPtr::new(SFVString::new(value).coerce::<nsISFVString>()))
+ }
+
+ xpcom_method!(new_token => NewToken(value: *const nsACString) -> *const nsISFVToken);
+ fn new_token(&self, value: &nsACString) -> Result<RefPtr<nsISFVToken>, nsresult> {
+ Ok(RefPtr::new(SFVToken::new(value).coerce::<nsISFVToken>()))
+ }
+
+ xpcom_method!(new_byte_sequence => NewByteSequence(value: *const nsACString) -> *const nsISFVByteSeq);
+ fn new_byte_sequence(&self, value: &nsACString) -> Result<RefPtr<nsISFVByteSeq>, nsresult> {
+ Ok(RefPtr::new(
+ SFVByteSeq::new(value).coerce::<nsISFVByteSeq>(),
+ ))
+ }
+
+ xpcom_method!(new_parameters => NewParameters() -> *const nsISFVParams);
+ fn new_parameters(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ Ok(RefPtr::new(SFVParams::new().coerce::<nsISFVParams>()))
+ }
+
+ xpcom_method!(new_item => NewItem(value: *const nsISFVBareItem, params: *const nsISFVParams) -> *const nsISFVItem);
+ fn new_item(
+ &self,
+ value: &nsISFVBareItem,
+ params: &nsISFVParams,
+ ) -> Result<RefPtr<nsISFVItem>, nsresult> {
+ Ok(RefPtr::new(
+ SFVItem::new(value, params).coerce::<nsISFVItem>(),
+ ))
+ }
+
+ xpcom_method!(new_inner_list => NewInnerList(items: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, params: *const nsISFVParams) -> *const nsISFVInnerList);
+ fn new_inner_list(
+ &self,
+ items: &thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>,
+ params: &nsISFVParams,
+ ) -> Result<RefPtr<nsISFVInnerList>, nsresult> {
+ let items = items
+ .iter()
+ .cloned()
+ .map(|item| item.ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()?;
+ Ok(RefPtr::new(
+ SFVInnerList::new(items, params).coerce::<nsISFVInnerList>(),
+ ))
+ }
+
+ xpcom_method!(new_list => NewList(members: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>) -> *const nsISFVList);
+ fn new_list(
+ &self,
+ members: &thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>,
+ ) -> Result<RefPtr<nsISFVList>, nsresult> {
+ let members = members
+ .iter()
+ .cloned()
+ .map(|item| item.ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()?;
+ Ok(RefPtr::new(SFVList::new(members).coerce::<nsISFVList>()))
+ }
+
+ xpcom_method!(new_dictionary => NewDictionary() -> *const nsISFVDictionary);
+ fn new_dictionary(&self) -> Result<RefPtr<nsISFVDictionary>, nsresult> {
+ Ok(RefPtr::new(
+ SFVDictionary::new().coerce::<nsISFVDictionary>(),
+ ))
+ }
+}
+
+#[xpcom(implement(nsISFVInteger, nsISFVBareItem), nonatomic)]
+struct SFVInteger {
+ value: RefCell<i64>,
+}
+
+impl SFVInteger {
+ fn new(value: i64) -> RefPtr<SFVInteger> {
+ SFVInteger::allocate(InitSFVInteger {
+ value: RefCell::new(value),
+ })
+ }
+
+ xpcom_method!(get_value => GetValue() -> i64);
+ fn get_value(&self) -> Result<i64, nsresult> {
+ Ok(*self.value.borrow())
+ }
+
+ xpcom_method!(set_value => SetValue(value: i64));
+ fn set_value(&self, value: i64) -> Result<(), nsresult> {
+ self.value.replace(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::INTEGER)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVBool, nsISFVBareItem), nonatomic)]
+struct SFVBool {
+ value: RefCell<bool>,
+}
+
+impl SFVBool {
+ fn new(value: bool) -> RefPtr<SFVBool> {
+ SFVBool::allocate(InitSFVBool {
+ value: RefCell::new(value),
+ })
+ }
+
+ xpcom_method!(get_value => GetValue() -> bool);
+ fn get_value(&self) -> Result<bool, nsresult> {
+ Ok(*self.value.borrow())
+ }
+
+ xpcom_method!(set_value => SetValue(value: bool));
+ fn set_value(&self, value: bool) -> Result<(), nsresult> {
+ self.value.replace(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::BOOL)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVString, nsISFVBareItem), nonatomic)]
+struct SFVString {
+ value: RefCell<nsCString>,
+}
+
+impl SFVString {
+ fn new(value: &nsACString) -> RefPtr<SFVString> {
+ SFVString::allocate(InitSFVString {
+ value: RefCell::new(nsCString::from(value)),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> nsACString
+ );
+
+ fn get_value(&self) -> Result<nsCString, nsresult> {
+ Ok(self.value.borrow().clone())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: *const nsACString)
+ );
+
+ fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+ self.value.borrow_mut().assign(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::STRING)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVToken, nsISFVBareItem), nonatomic)]
+struct SFVToken {
+ value: RefCell<nsCString>,
+}
+
+impl SFVToken {
+ fn new(value: &nsACString) -> RefPtr<SFVToken> {
+ SFVToken::allocate(InitSFVToken {
+ value: RefCell::new(nsCString::from(value)),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> nsACString
+ );
+
+ fn get_value(&self) -> Result<nsCString, nsresult> {
+ Ok(self.value.borrow().clone())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: *const nsACString)
+ );
+
+ fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+ self.value.borrow_mut().assign(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::TOKEN)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVByteSeq, nsISFVBareItem), nonatomic)]
+struct SFVByteSeq {
+ value: RefCell<nsCString>,
+}
+
+impl SFVByteSeq {
+ fn new(value: &nsACString) -> RefPtr<SFVByteSeq> {
+ SFVByteSeq::allocate(InitSFVByteSeq {
+ value: RefCell::new(nsCString::from(value)),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> nsACString
+ );
+
+ fn get_value(&self) -> Result<nsCString, nsresult> {
+ Ok(self.value.borrow().clone())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: *const nsACString)
+ );
+
+ fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+ self.value.borrow_mut().assign(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::BYTE_SEQUENCE)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVDecimal, nsISFVBareItem), nonatomic)]
+struct SFVDecimal {
+ value: RefCell<f64>,
+}
+
+impl SFVDecimal {
+ fn new(value: f64) -> RefPtr<SFVDecimal> {
+ SFVDecimal::allocate(InitSFVDecimal {
+ value: RefCell::new(value),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> f64
+ );
+
+ fn get_value(&self) -> Result<f64, nsresult> {
+ Ok(*self.value.borrow())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: f64)
+ );
+
+ fn set_value(&self, value: f64) -> Result<(), nsresult> {
+ self.value.replace(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::DECIMAL)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVParams), nonatomic)]
+struct SFVParams {
+ params: RefCell<Parameters>,
+}
+
+impl SFVParams {
+ fn new() -> RefPtr<SFVParams> {
+ SFVParams::allocate(InitSFVParams {
+ params: RefCell::new(Parameters::new()),
+ })
+ }
+
+ xpcom_method!(
+ get => Get(key: *const nsACString) -> *const nsISFVBareItem
+ );
+
+ fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+ let key = key.to_utf8();
+ let params = self.params.borrow();
+ let param_val = params.get(key.as_ref());
+
+ match param_val {
+ Some(val) => interface_from_bare_item(val),
+ None => return Err(NS_ERROR_UNEXPECTED),
+ }
+ }
+
+ xpcom_method!(
+ set => Set(key: *const nsACString, item: *const nsISFVBareItem)
+ );
+
+ fn set(&self, key: &nsACString, item: &nsISFVBareItem) -> Result<(), nsresult> {
+ let key = key.to_utf8().into_owned();
+ let bare_item = bare_item_from_interface(item)?;
+ self.params.borrow_mut().insert(key, bare_item);
+ Ok(())
+ }
+
+ xpcom_method!(
+ delete => Delete(key: *const nsACString)
+ );
+ fn delete(&self, key: &nsACString) -> Result<(), nsresult> {
+ let key = key.to_utf8();
+ let mut params = self.params.borrow_mut();
+
+ if !params.contains_key(key.as_ref()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ // Keeps only entries that don't match key
+ params.retain(|k, _| k != key.as_ref());
+ Ok(())
+ }
+
+ xpcom_method!(
+ keys => Keys() -> thin_vec::ThinVec<nsCString>
+ );
+ fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> {
+ let keys = self.params.borrow();
+ let keys = keys
+ .keys()
+ .map(nsCString::from)
+ .collect::<ThinVec<nsCString>>();
+ Ok(keys)
+ }
+
+ fn from_interface(obj: &nsISFVParams) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVItem, nsISFVItemOrInnerList), nonatomic)]
+struct SFVItem {
+ value: RefPtr<nsISFVBareItem>,
+ params: RefPtr<nsISFVParams>,
+}
+
+impl SFVItem {
+ fn new(value: &nsISFVBareItem, params: &nsISFVParams) -> RefPtr<SFVItem> {
+ SFVItem::allocate(InitSFVItem {
+ value: RefPtr::new(value),
+ params: RefPtr::new(params),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> *const nsISFVBareItem
+ );
+
+ fn get_value(&self) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+ Ok(self.value.clone())
+ }
+
+ xpcom_method!(
+ get_params => GetParams(
+ ) -> *const nsISFVParams
+ );
+ fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ Ok(self.params.clone())
+ }
+
+ xpcom_method!(
+ serialize => Serialize() -> nsACString
+ );
+ fn serialize(&self) -> Result<nsCString, nsresult> {
+ let bare_item = bare_item_from_interface(self.value.deref())?;
+ let params = params_from_interface(self.params.deref())?;
+ let serialized = Item::with_params(bare_item, params)
+ .serialize_value()
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(nsCString::from(serialized))
+ }
+
+ fn from_interface(obj: &nsISFVItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVInnerList, nsISFVItemOrInnerList), nonatomic)]
+struct SFVInnerList {
+ items: RefCell<Vec<RefPtr<nsISFVItem>>>,
+ params: RefPtr<nsISFVParams>,
+}
+
+impl SFVInnerList {
+ fn new(items: Vec<RefPtr<nsISFVItem>>, params: &nsISFVParams) -> RefPtr<SFVInnerList> {
+ SFVInnerList::allocate(InitSFVInnerList {
+ items: RefCell::new(items),
+ params: RefPtr::new(params),
+ })
+ }
+
+ xpcom_method!(
+ get_items => GetItems() -> thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>
+ );
+
+ fn get_items(&self) -> Result<thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, nsresult> {
+ let items = self.items.borrow().iter().cloned().map(Some).collect();
+ Ok(items)
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn SetItems(
+ &self,
+ value: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>,
+ ) -> nsresult {
+ if value.is_null() {
+ return NS_ERROR_NULL_POINTER;
+ }
+ match (*value)
+ .iter()
+ .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()
+ {
+ Ok(value) => *self.items.borrow_mut() = value,
+ Err(rv) => return rv,
+ }
+ NS_OK
+ }
+
+ xpcom_method!(
+ get_params => GetParams(
+ ) -> *const nsISFVParams
+ );
+ fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ Ok(self.params.clone())
+ }
+
+ fn from_interface(obj: &nsISFVInnerList) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVList, nsISFVSerialize), nonatomic)]
+struct SFVList {
+ members: RefCell<Vec<RefPtr<nsISFVItemOrInnerList>>>,
+}
+
+impl SFVList {
+ fn new(members: Vec<RefPtr<nsISFVItemOrInnerList>>) -> RefPtr<SFVList> {
+ SFVList::allocate(InitSFVList {
+ members: RefCell::new(members),
+ })
+ }
+
+ xpcom_method!(
+ get_members => GetMembers() -> thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>
+ );
+
+ fn get_members(
+ &self,
+ ) -> Result<thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>, nsresult> {
+ Ok(self.members.borrow().iter().cloned().map(Some).collect())
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn SetMembers(
+ &self,
+ value: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>,
+ ) -> nsresult {
+ if value.is_null() {
+ return NS_ERROR_NULL_POINTER;
+ }
+ match (*value)
+ .iter()
+ .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()
+ {
+ Ok(value) => *self.members.borrow_mut() = value,
+ Err(rv) => return rv,
+ }
+ NS_OK
+ }
+
+ xpcom_method!(
+ parse_more => ParseMore(header: *const nsACString)
+ );
+ fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> {
+ // create List from SFVList to call parse_more on it
+ let mut list = List::new();
+ let members = self.members.borrow().clone();
+ for interface_entry in members.iter() {
+ let item_or_inner_list = list_entry_from_interface(interface_entry)?;
+ list.push(item_or_inner_list);
+ }
+
+ let _ = list.parse_more(&header).map_err(|_| NS_ERROR_FAILURE)?;
+
+ // replace SFVList's members with new_members
+ let mut new_members = Vec::new();
+ for item_or_inner_list in list.iter() {
+ new_members.push(interface_from_list_entry(item_or_inner_list)?)
+ }
+ self.members.replace(new_members);
+ Ok(())
+ }
+
+ xpcom_method!(
+ serialize => Serialize() -> nsACString
+ );
+ fn serialize(&self) -> Result<nsCString, nsresult> {
+ let mut list = List::new();
+ let members = self.members.borrow().clone();
+ for interface_entry in members.iter() {
+ let item_or_inner_list = list_entry_from_interface(interface_entry)?;
+ list.push(item_or_inner_list);
+ }
+
+ let serialized = list.serialize_value().map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(nsCString::from(serialized))
+ }
+}
+
+#[xpcom(implement(nsISFVDictionary, nsISFVSerialize), nonatomic)]
+struct SFVDictionary {
+ value: RefCell<Dictionary>,
+}
+
+impl SFVDictionary {
+ fn new() -> RefPtr<SFVDictionary> {
+ SFVDictionary::allocate(InitSFVDictionary {
+ value: RefCell::new(Dictionary::new()),
+ })
+ }
+
+ xpcom_method!(
+ get => Get(key: *const nsACString) -> *const nsISFVItemOrInnerList
+ );
+
+ fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> {
+ let key = key.to_utf8();
+ let value = self.value.borrow();
+ let member_value = value.get(key.as_ref());
+
+ match member_value {
+ Some(member) => interface_from_list_entry(member),
+ None => return Err(NS_ERROR_UNEXPECTED),
+ }
+ }
+
+ xpcom_method!(
+ set => Set(key: *const nsACString, item: *const nsISFVItemOrInnerList)
+ );
+
+ fn set(&self, key: &nsACString, member_value: &nsISFVItemOrInnerList) -> Result<(), nsresult> {
+ let key = key.to_utf8().into_owned();
+ let value = list_entry_from_interface(member_value)?;
+ self.value.borrow_mut().insert(key, value);
+ Ok(())
+ }
+
+ xpcom_method!(
+ delete => Delete(key: *const nsACString)
+ );
+
+ fn delete(&self, key: &nsACString) -> Result<(), nsresult> {
+ let key = key.to_utf8();
+ let mut params = self.value.borrow_mut();
+
+ if !params.contains_key(key.as_ref()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ // Keeps only entries that don't match key
+ params.retain(|k, _| k != key.as_ref());
+ Ok(())
+ }
+
+ xpcom_method!(
+ keys => Keys() -> thin_vec::ThinVec<nsCString>
+ );
+ fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> {
+ let members = self.value.borrow();
+ let keys = members
+ .keys()
+ .map(nsCString::from)
+ .collect::<ThinVec<nsCString>>();
+ Ok(keys)
+ }
+
+ xpcom_method!(
+ parse_more => ParseMore(header: *const nsACString)
+ );
+ fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> {
+ let _ = self
+ .value
+ .borrow_mut()
+ .parse_more(&header)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(())
+ }
+
+ xpcom_method!(
+ serialize => Serialize() -> nsACString
+ );
+ fn serialize(&self) -> Result<nsCString, nsresult> {
+ let serialized = self
+ .value
+ .borrow()
+ .serialize_value()
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(nsCString::from(serialized))
+ }
+}
+
+fn bare_item_from_interface(obj: &nsISFVBareItem) -> Result<BareItem, nsresult> {
+ let obj = obj
+ .query_interface::<nsISFVBareItem>()
+ .ok_or(NS_ERROR_UNEXPECTED)?;
+ let mut obj_type: i32 = -1;
+ unsafe {
+ obj.deref().GetType(&mut obj_type);
+ }
+
+ match obj_type {
+ nsISFVBareItem::BOOL => {
+ let item_value = SFVBool::from_bare_item_interface(obj.deref()).get_value()?;
+ Ok(BareItem::Boolean(item_value))
+ }
+ nsISFVBareItem::STRING => {
+ let string_itm = SFVString::from_bare_item_interface(obj.deref()).get_value()?;
+ let item_value = (*string_itm.to_utf8()).to_string();
+ Ok(BareItem::String(item_value))
+ }
+ nsISFVBareItem::TOKEN => {
+ let token_itm = SFVToken::from_bare_item_interface(obj.deref()).get_value()?;
+ let item_value = (*token_itm.to_utf8()).to_string();
+ Ok(BareItem::Token(item_value))
+ }
+ nsISFVBareItem::INTEGER => {
+ let item_value = SFVInteger::from_bare_item_interface(obj.deref()).get_value()?;
+ Ok(BareItem::Integer(item_value))
+ }
+ nsISFVBareItem::DECIMAL => {
+ let item_value = SFVDecimal::from_bare_item_interface(obj.deref()).get_value()?;
+ let decimal: Decimal = Decimal::from_f64(item_value).ok_or(NS_ERROR_UNEXPECTED)?;
+ Ok(BareItem::Decimal(decimal))
+ }
+ nsISFVBareItem::BYTE_SEQUENCE => {
+ let token_itm = SFVByteSeq::from_bare_item_interface(obj.deref()).get_value()?;
+ let item_value: String = (*token_itm.to_utf8()).to_string();
+ Ok(BareItem::ByteSeq(item_value.into_bytes()))
+ }
+ _ => return Err(NS_ERROR_UNEXPECTED),
+ }
+}
+
+fn params_from_interface(obj: &nsISFVParams) -> Result<Parameters, nsresult> {
+ let params = SFVParams::from_interface(obj).params.borrow();
+ Ok(params.clone())
+}
+
+fn item_from_interface(obj: &nsISFVItem) -> Result<Item, nsresult> {
+ let sfv_item = SFVItem::from_interface(obj);
+ let bare_item = bare_item_from_interface(sfv_item.value.deref())?;
+ let parameters = params_from_interface(sfv_item.params.deref())?;
+ Ok(Item::with_params(bare_item, parameters))
+}
+
+fn inner_list_from_interface(obj: &nsISFVInnerList) -> Result<InnerList, nsresult> {
+ let sfv_inner_list = SFVInnerList::from_interface(obj);
+
+ let mut inner_list_items: Vec<Item> = vec![];
+ for item in sfv_inner_list.items.borrow().iter() {
+ let item = item_from_interface(item)?;
+ inner_list_items.push(item);
+ }
+ let inner_list_params = params_from_interface(sfv_inner_list.params.deref())?;
+ Ok(InnerList::with_params(inner_list_items, inner_list_params))
+}
+
+fn list_entry_from_interface(obj: &nsISFVItemOrInnerList) -> Result<ListEntry, nsresult> {
+ if let Some(nsi_item) = obj.query_interface::<nsISFVItem>() {
+ let item = item_from_interface(nsi_item.deref())?;
+ Ok(ListEntry::Item(item))
+ } else if let Some(nsi_inner_list) = obj.query_interface::<nsISFVInnerList>() {
+ let inner_list = inner_list_from_interface(nsi_inner_list.deref())?;
+ Ok(ListEntry::InnerList(inner_list))
+ } else {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+}
+
+fn interface_from_bare_item(bare_item: &BareItem) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+ let bare_item = match bare_item {
+ BareItem::Boolean(val) => RefPtr::new(SFVBool::new(*val).coerce::<nsISFVBareItem>()),
+ BareItem::String(val) => {
+ RefPtr::new(SFVString::new(&nsCString::from(val)).coerce::<nsISFVBareItem>())
+ }
+ BareItem::Token(val) => {
+ RefPtr::new(SFVToken::new(&nsCString::from(val)).coerce::<nsISFVBareItem>())
+ }
+ BareItem::ByteSeq(val) => RefPtr::new(
+ SFVByteSeq::new(&nsCString::from(String::from_utf8(val.to_vec()).unwrap()))
+ .coerce::<nsISFVBareItem>(),
+ ),
+ BareItem::Decimal(val) => {
+ let val = val
+ .to_string()
+ .parse::<f64>()
+ .map_err(|_| NS_ERROR_UNEXPECTED)?;
+ RefPtr::new(SFVDecimal::new(val).coerce::<nsISFVBareItem>())
+ }
+ BareItem::Integer(val) => RefPtr::new(SFVInteger::new(*val).coerce::<nsISFVBareItem>()),
+ };
+
+ Ok(bare_item)
+}
+
+fn interface_from_item(item: &Item) -> Result<RefPtr<nsISFVItem>, nsresult> {
+ let nsi_bare_item = interface_from_bare_item(&item.bare_item)?;
+ let nsi_params = interface_from_params(&item.params)?;
+ Ok(RefPtr::new(
+ SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItem>(),
+ ))
+}
+
+fn interface_from_params(params: &Parameters) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ let sfv_params = SFVParams::new();
+ for (key, value) in params.iter() {
+ sfv_params
+ .params
+ .borrow_mut()
+ .insert(key.clone(), value.clone());
+ }
+ Ok(RefPtr::new(sfv_params.coerce::<nsISFVParams>()))
+}
+
+fn interface_from_list_entry(
+ member: &ListEntry,
+) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> {
+ match member {
+ ListEntry::Item(item) => {
+ let nsi_bare_item = interface_from_bare_item(&item.bare_item)?;
+ let nsi_params = interface_from_params(&item.params)?;
+ Ok(RefPtr::new(
+ SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItemOrInnerList>(),
+ ))
+ }
+ ListEntry::InnerList(inner_list) => {
+ let mut nsi_inner_list = Vec::new();
+ for item in inner_list.items.iter() {
+ let nsi_item = interface_from_item(item)?;
+ nsi_inner_list.push(nsi_item);
+ }
+
+ let nsi_params = interface_from_params(&inner_list.params)?;
+ Ok(RefPtr::new(
+ SFVInnerList::new(nsi_inner_list, &nsi_params).coerce::<nsISFVItemOrInnerList>(),
+ ))
+ }
+ }
+}
diff --git a/netwerk/base/makecppstring.py b/netwerk/base/makecppstring.py
new file mode 100644
index 0000000000..330fcfb04c
--- /dev/null
+++ b/netwerk/base/makecppstring.py
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+
+
+def main(output, filename):
+ file = open(filename, "r")
+ output.write('R"(') # insert literal start
+ for line in file:
+ output.write(line)
+ output.write(')"') # insert literal end
+
+
+if __name__ == "__main__":
+ main(sys.stdout, sys.argv[1])
diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build
new file mode 100644
index 0000000000..e069a5f296
--- /dev/null
+++ b/netwerk/base/moz.build
@@ -0,0 +1,319 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "mozIThirdPartyUtil.idl",
+ "nsIArrayBufferInputStream.idl",
+ "nsIAsyncStreamCopier.idl",
+ "nsIAsyncStreamCopier2.idl",
+ "nsIAsyncVerifyRedirectCallback.idl",
+ "nsIAuthInformation.idl",
+ "nsIAuthModule.idl",
+ "nsIAuthPrompt.idl",
+ "nsIAuthPrompt2.idl",
+ "nsIAuthPromptAdapterFactory.idl",
+ "nsIAuthPromptCallback.idl",
+ "nsIAuthPromptProvider.idl",
+ "nsIBackgroundFileSaver.idl",
+ "nsIBufferedStreams.idl",
+ "nsIByteRangeRequest.idl",
+ "nsICacheInfoChannel.idl",
+ "nsICachingChannel.idl",
+ "nsICancelable.idl",
+ "nsICaptivePortalService.idl",
+ "nsIChannel.idl",
+ "nsIChannelEventSink.idl",
+ "nsIChildChannel.idl",
+ "nsIClassifiedChannel.idl",
+ "nsIClassOfService.idl",
+ "nsIContentSniffer.idl",
+ "nsIDashboard.idl",
+ "nsIDashboardEventNotifier.idl",
+ "nsIDHCPClient.idl",
+ "nsIDownloader.idl",
+ "nsIEncodedChannel.idl",
+ "nsIExternalProtocolHandler.idl",
+ "nsIFileStreams.idl",
+ "nsIFileURL.idl",
+ "nsIForcePendingChannel.idl",
+ "nsIFormPOSTActionChannel.idl",
+ "nsIHttpAuthenticatorCallback.idl",
+ "nsIHttpPushListener.idl",
+ "nsIIncrementalDownload.idl",
+ "nsIIncrementalStreamLoader.idl",
+ "nsIInputStreamChannel.idl",
+ "nsIInputStreamPump.idl",
+ "nsIInterceptionInfo.idl",
+ "nsIIOService.idl",
+ "nsILoadContextInfo.idl",
+ "nsILoadGroup.idl",
+ "nsILoadGroupChild.idl",
+ "nsILoadInfo.idl",
+ "nsIMIMEInputStream.idl",
+ "nsIMultiPartChannel.idl",
+ "nsINestedURI.idl",
+ "nsINetAddr.idl",
+ "nsINetUtil.idl",
+ "nsINetworkConnectivityService.idl",
+ "nsINetworkInfoService.idl",
+ "nsINetworkInterceptController.idl",
+ "nsINetworkLinkService.idl",
+ "nsINetworkPredictor.idl",
+ "nsINetworkPredictorVerifier.idl",
+ "nsINullChannel.idl",
+ "nsIParentChannel.idl",
+ "nsIParentRedirectingChannel.idl",
+ "nsIPermission.idl",
+ "nsIPermissionManager.idl",
+ "nsIPrivateBrowsingChannel.idl",
+ "nsIProgressEventSink.idl",
+ "nsIPrompt.idl",
+ "nsIProtocolHandler.idl",
+ "nsIProtocolProxyCallback.idl",
+ "nsIProtocolProxyFilter.idl",
+ "nsIProtocolProxyService.idl",
+ "nsIProtocolProxyService2.idl",
+ "nsIProxiedChannel.idl",
+ "nsIProxiedProtocolHandler.idl",
+ "nsIProxyInfo.idl",
+ "nsIRandomGenerator.idl",
+ "nsIRedirectChannelRegistrar.idl",
+ "nsIRedirectHistoryEntry.idl",
+ "nsIRedirectResultListener.idl",
+ "nsIRequest.idl",
+ "nsIRequestContext.idl",
+ "nsIRequestObserver.idl",
+ "nsIRequestObserverProxy.idl",
+ "nsIResumableChannel.idl",
+ "nsISecCheckWrapChannel.idl",
+ "nsISecureBrowserUI.idl",
+ "nsISensitiveInfoHiddenURI.idl",
+ "nsISerializationHelper.idl",
+ "nsIServerSocket.idl",
+ "nsISimpleStreamListener.idl",
+ "nsISimpleURIMutator.idl",
+ "nsISocketFilter.idl",
+ "nsISocketTransport.idl",
+ "nsISocketTransportService.idl",
+ "nsISpeculativeConnect.idl",
+ "nsIStandardURL.idl",
+ "nsIStreamListener.idl",
+ "nsIStreamListenerTee.idl",
+ "nsIStreamLoader.idl",
+ "nsIStreamTransportService.idl",
+ "nsISyncStreamListener.idl",
+ "nsISystemProxySettings.idl",
+ "nsIThreadRetargetableRequest.idl",
+ "nsIThreadRetargetableStreamListener.idl",
+ "nsIThrottledInputChannel.idl",
+ "nsITimedChannel.idl",
+ "nsITLSServerSocket.idl",
+ "nsITraceableChannel.idl",
+ "nsITransport.idl",
+ "nsIUDPSocket.idl",
+ "nsIUploadChannel.idl",
+ "nsIUploadChannel2.idl",
+ "nsIURI.idl",
+ "nsIURIMutator.idl",
+ "nsIURIWithSpecialOrigin.idl",
+ "nsIURL.idl",
+ "nsIURLParser.idl",
+ "nsPISocketTransportService.idl",
+]
+
+XPIDL_MODULE = "necko"
+
+EXPORTS += [
+ "netCore.h",
+ "nsASocketHandler.h",
+ "nsAsyncRedirectVerifyHelper.h",
+ "nsBaseChannel.h",
+ "nsFileStreams.h",
+ "nsInputStreamPump.h",
+ "nsMIMEInputStream.h",
+ "nsNetUtil.h",
+ "nsReadLine.h",
+ "nsSerializationHelper.h",
+ "nsSimpleNestedURI.h",
+ "nsSimpleURI.h",
+ "nsStandardURL.h",
+ "nsStreamListenerWrapper.h",
+ "nsURIHashKey.h",
+ "nsURLHelper.h",
+ "nsURLParsers.h",
+ "SimpleChannel.h",
+]
+
+EXPORTS.mozilla += [
+ "LoadContextInfo.h",
+ "LoadInfo.h",
+ "LoadTainting.h",
+ "nsRedirectHistoryEntry.h",
+]
+
+EXPORTS.mozilla.net += [
+ "CacheInfoIPCTypes.h",
+ "CaptivePortalService.h",
+ "Dashboard.h",
+ "DashboardTypes.h",
+ "DefaultURI.h",
+ "InterceptionInfo.h",
+ "IOActivityMonitor.h",
+ "MemoryDownloader.h",
+ "NetworkConnectivityService.h",
+ "Predictor.h",
+ "PrivateBrowsingChannel.h",
+ "ProtocolHandlerInfo.h",
+ "RedirectChannelRegistrar.h",
+ "RequestContextService.h",
+ "SimpleChannelParent.h",
+ "SSLTokensCache.h",
+ "ThrottleQueue.h",
+]
+
+UNIFIED_SOURCES += [
+ "ArrayBufferInputStream.cpp",
+ "BackgroundFileSaver.cpp",
+ "CaptivePortalService.cpp",
+ "Dashboard.cpp",
+ "DefaultURI.cpp",
+ "EventTokenBucket.cpp",
+ "InterceptionInfo.cpp",
+ "IOActivityMonitor.cpp",
+ "LoadContextInfo.cpp",
+ "LoadInfo.cpp",
+ "MemoryDownloader.cpp",
+ "NetworkConnectivityService.cpp",
+ "NetworkDataCountLayer.cpp",
+ "nsAsyncRedirectVerifyHelper.cpp",
+ "nsAsyncStreamCopier.cpp",
+ "nsAuthInformationHolder.cpp",
+ "nsBase64Encoder.cpp",
+ "nsBaseChannel.cpp",
+ "nsBaseContentStream.cpp",
+ "nsBufferedStreams.cpp",
+ "nsDirectoryIndexStream.cpp",
+ "nsDNSPrefetch.cpp",
+ "nsDownloader.cpp",
+ "nsFileStreams.cpp",
+ "nsIncrementalDownload.cpp",
+ "nsIncrementalStreamLoader.cpp",
+ "nsInputStreamChannel.cpp",
+ "nsInputStreamPump.cpp",
+ "nsIOService.cpp",
+ "nsIURIMutatorUtils.cpp",
+ "nsLoadGroup.cpp",
+ "nsMIMEInputStream.cpp",
+ "nsNetAddr.cpp",
+ "nsNetUtil.cpp",
+ "nsPACMan.cpp",
+ "nsPreloadedStream.cpp",
+ "nsProtocolProxyService.cpp",
+ "nsProxyInfo.cpp",
+ "nsRedirectHistoryEntry.cpp",
+ "nsRequestObserverProxy.cpp",
+ "nsSerializationHelper.cpp",
+ "nsServerSocket.cpp",
+ "nsSimpleNestedURI.cpp",
+ "nsSimpleStreamListener.cpp",
+ "nsSimpleURI.cpp",
+ "nsSocketTransport2.cpp",
+ "nsSocketTransportService2.cpp",
+ "nsStandardURL.cpp",
+ "nsStreamListenerTee.cpp",
+ "nsStreamListenerWrapper.cpp",
+ "nsStreamLoader.cpp",
+ "nsStreamTransportService.cpp",
+ "nsSyncStreamListener.cpp",
+ "nsTransportUtils.cpp",
+ "nsUDPSocket.cpp",
+ "PollableEvent.cpp",
+ "Predictor.cpp",
+ "ProtocolHandlerInfo.cpp",
+ "ProxyAutoConfig.cpp",
+ "RedirectChannelRegistrar.cpp",
+ "RequestContextService.cpp",
+ "SimpleBuffer.cpp",
+ "SimpleChannel.cpp",
+ "SimpleChannelParent.cpp",
+ "SSLTokensCache.cpp",
+ "ThrottleQueue.cpp",
+ "Tickler.cpp",
+ "TLSServerSocket.cpp",
+ "TRRLoadInfo.cpp",
+]
+
+if CONFIG["FUZZING"]:
+ SOURCES += [
+ "FuzzyLayer.cpp",
+ "FuzzySecurityInfo.cpp",
+ "FuzzySocketControl.cpp",
+ ]
+
+if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]:
+ include("/tools/fuzzing/libfuzzer-flags.mozbuild")
+ SOURCES += [
+ "nsMediaFragmentURIParser.cpp",
+ "nsURLHelper.cpp",
+ "nsURLParsers.cpp",
+ ]
+ SOURCES["nsMediaFragmentURIParser.cpp"].flags += libfuzzer_flags
+ SOURCES["nsURLHelper.cpp"].flags += libfuzzer_flags
+ SOURCES["nsURLParsers.cpp"].flags += libfuzzer_flags
+else:
+ UNIFIED_SOURCES += [
+ "nsMediaFragmentURIParser.cpp",
+ "nsURLHelper.cpp",
+ "nsURLParsers.cpp",
+ ]
+
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ SOURCES += [
+ "nsURLHelperWin.cpp",
+ "ShutdownLayer.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsURLHelperOSX.cpp",
+ ]
+else:
+ SOURCES += [
+ "nsURLHelperUnix.cpp",
+ ]
+
+EXTRA_JS_MODULES += [
+ "NetUtil.sys.mjs",
+]
+
+DIRS += ["mozurl", "rust-helper", "http-sfv"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "!/xpcom/components",
+ "/docshell/base",
+ "/dom/base",
+ "/js/xpconnect/src",
+ "/netwerk/dns",
+ "/netwerk/protocol/http",
+ "/netwerk/protocol/webtransport",
+ "/netwerk/socket",
+ "/netwerk/url-classifier",
+ "/security/manager/ssl",
+ "/xpcom/components",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/xpcom/base",
+ ]
+
+GeneratedFile(
+ "ascii_pac_utils.inc", script="makecppstring.py", inputs=["ascii_pac_utils.js"]
+)
diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl
new file mode 100644
index 0000000000..0c029942d8
--- /dev/null
+++ b/netwerk/base/mozIThirdPartyUtil.idl
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface mozIDOMWindowProxy;
+interface nsIChannel;
+interface nsIPrincipal;
+interface nsILoadInfo;
+
+%{C++
+
+#include "mozilla/EnumSet.h"
+
+enum class ThirdPartyAnalysis {
+ IsForeign,
+ IsThirdPartyTrackingResource,
+ IsThirdPartySocialTrackingResource,
+ IsStorageAccessPermissionGranted,
+};
+
+using ThirdPartyAnalysisResult = mozilla::EnumSet<ThirdPartyAnalysis>;
+
+typedef bool (*RequireThirdPartyCheck)(nsILoadInfo*);
+
+%}
+
+native ThirdPartyAnalysisResult(ThirdPartyAnalysisResult);
+native RequireThirdPartyCheck(RequireThirdPartyCheck);
+
+/**
+ * Utility functions for determining whether a given URI, channel, or window
+ * hierarchy is third party with respect to a known URI.
+ */
+[scriptable, builtinclass, uuid(fd82700e-ffb4-4932-b7d6-08f0b5697dda)]
+interface mozIThirdPartyUtil : nsISupports
+{
+ /**
+ * isThirdPartyURI
+ *
+ * Determine whether two URIs are third party with respect to each other.
+ * This is determined by computing the base domain for both URIs. If they can
+ * be determined, and the base domains match, the request is defined as first
+ * party. If it cannot be determined because one or both URIs do not have a
+ * base domain (for instance, in the case of IP addresses, host aliases such
+ * as 'localhost', or a file:// URI), an exact string comparison on host is
+ * performed.
+ *
+ * For example, the URI "http://mail.google.com/" is not third party with
+ * respect to "http://images.google.com/", but "http://mail.yahoo.com/" and
+ * "http://192.168.1.1/" are.
+ *
+ * @return true if aFirstURI is third party with respect to aSecondURI.
+ *
+ * @throws if either URI is null, has a malformed host, or has an empty host
+ * and is not a file:// URI.
+ */
+ boolean isThirdPartyURI(in nsIURI aFirstURI, in nsIURI aSecondURI);
+
+ /**
+ * isThirdPartyWindow
+ *
+ * Determine whether the given window hierarchy is third party. This is done
+ * as follows:
+ *
+ * 1) Obtain the URI of the principal associated with 'aWindow'. Call this the
+ * 'bottom URI'.
+ * 2) If 'aURI' is provided, determine if it is third party with respect to
+ * the bottom URI. If so, return.
+ * 3) Find the same-type parent window, if there is one, and its URI.
+ * Determine whether it is third party with respect to the bottom URI. If
+ * so, return.
+ *
+ * Therefore, each level in the window hierarchy is tested. (This means that
+ * nested iframes with different base domains, even though the bottommost and
+ * topmost URIs might be equal, will be considered third party.)
+ *
+ * @param aWindow
+ * The bottommost window in the hierarchy.
+ * @param aURI
+ * A URI to test against. If null, the URI of the principal
+ * associated with 'aWindow' will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aWindow' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if 'aURI' is third party with respect to any of the URIs
+ * associated with aWindow and its same-type parents.
+ *
+ * @throws if aWindow is null; the same-type parent of any window in the
+ * hierarchy cannot be determined; or the URI associated with any
+ * window in the hierarchy is null, has a malformed host, or has an
+ * empty host and is not a file:// URI.
+ *
+ * @see isThirdPartyURI
+ */
+ boolean isThirdPartyWindow(in mozIDOMWindowProxy aWindow, [optional] in nsIURI aURI);
+
+ /**
+ * isThirdPartyChannel
+ *
+ * Determine whether the given channel and its content window hierarchy is
+ * third party. This is done as follows:
+ *
+ * 1) If 'aChannel' is an nsIHttpChannel and has the
+ * 'forceAllowThirdPartyCookie' property set, then:
+ * a) If 'aURI' is null, return false.
+ * b) Otherwise, find the URI of the channel, determine whether it is
+ * foreign with respect to 'aURI', and return.
+ * 2) Find the URI of the channel and determine whether it is third party with
+ * respect to the URI of the channel. If so, return.
+ * 3) Obtain the bottommost nsIDOMWindow, and its same-type parent if it
+ * exists, from the channel's notification callbacks. Then:
+ * a) If the parent is the same as the bottommost window, and the channel
+ * has the LOAD_DOCUMENT_URI flag set, return false. This represents the
+ * case where a toplevel load is occurring and the window's URI has not
+ * yet been updated. (We have already checked that 'aURI' is not foreign
+ * with respect to the channel URI.)
+ * b) Otherwise, return the result of isThirdPartyWindow with arguments
+ * of the channel's bottommost window and the channel URI, respectively.
+ *
+ * Therefore, both the channel's URI and each level in the window hierarchy
+ * associated with the channel is tested.
+ *
+ * @param aChannel
+ * The channel associated with the load.
+ * @param aURI
+ * A URI to test against. If null, the URI of the channel will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aChannel' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if aURI is third party with respect to the channel URI or any
+ * of the URIs associated with the same-type window hierarchy of the
+ * channel.
+ *
+ * @throws if 'aChannel' is null; the channel has no notification callbacks or
+ * an associated window; or isThirdPartyWindow throws.
+ *
+ * @see isThirdPartyWindow
+ */
+ boolean isThirdPartyChannel(in nsIChannel aChannel, [optional] in nsIURI aURI);
+
+ /**
+ * getBaseDomain
+ *
+ * Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
+ * "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
+ * dot may be present. If aHostURI is an IP address, an alias such as
+ * 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
+ * be the exact host. The result of this function should only be used in exact
+ * string comparisons, since substring comparisons will not be valid for the
+ * special cases elided above.
+ *
+ * @param aHostURI
+ * The URI to analyze.
+ *
+ * @return the base domain.
+ */
+ AUTF8String getBaseDomain(in nsIURI aHostURI);
+
+ /**
+ * NOTE: Long term, this method won't be needed once bug 922464 is fixed which
+ * will make it possible to parse all URI's off the main thread.
+ *
+ * getBaseDomainFromSchemeHost
+ *
+ * Get the base domain for aScheme and aHost. Otherwise identical to
+ * getBaseDomain().
+ *
+ * @param aScheme
+ * The scheme to analyze.
+ *
+ * @param aAsciiHost
+ * The host to analyze.
+ *
+ * @return the base domain.
+ */
+ AUTF8String getBaseDomainFromSchemeHost(in AUTF8String aScheme,
+ in AUTF8String aAsciiHost);
+
+ /**
+ * getURIFromWindow
+ *
+ * Returns the URI associated with the script object principal for the
+ * window.
+ */
+ nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ * getPrincipalFromWindow
+ *
+ * Returns the script object principal for the window.
+ */
+ nsIPrincipal getPrincipalFromWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ * getTopWindowForChannel
+ *
+ * Returns the top-level window associated with the given channel.
+ */
+ [noscript]
+ mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel, [optional] in nsIURI aURIBeingLoaded);
+
+ /*
+ * Performs a full analysis of a channel.
+ *
+ * aChannel the input channel
+ * aNotify whether to send content blocking notifications if access control checks fail
+ * aURI optional URI to check for (the channel URI will be used instead if not provided)
+ * aRequireThirdPartyCheck a functor used to determine whether the load info requires third-party checks
+ */
+ [noscript, notxpcom]
+ ThirdPartyAnalysisResult analyzeChannel(in nsIChannel aChannel,
+ in boolean aNotify,
+ [optional] in nsIURI aURI,
+ [optional] in RequireThirdPartyCheck aRequireThirdPartyCheck,
+ out uint32_t aRejectedReason);
+};
+
+%{ C++
+/**
+ * The mozIThirdPartyUtil implementation is an XPCOM service registered
+ * under the ContractID:
+ */
+#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1"
+%}
diff --git a/netwerk/base/mozurl/.gitignore b/netwerk/base/mozurl/.gitignore
new file mode 100644
index 0000000000..4fffb2f89c
--- /dev/null
+++ b/netwerk/base/mozurl/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/netwerk/base/mozurl/Cargo.toml b/netwerk/base/mozurl/Cargo.toml
new file mode 100644
index 0000000000..1166bd2bfa
--- /dev/null
+++ b/netwerk/base/mozurl/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "mozurl"
+version = "0.0.1"
+authors = ["Nika Layzell <nika@thelayzells.com>"]
+license = "MPL-2.0"
+
+[dependencies]
+url = "2.4"
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+uuid = { version = "1.0", features = ["v4"] }
diff --git a/netwerk/base/mozurl/MozURL.cpp b/netwerk/base/mozurl/MozURL.cpp
new file mode 100644
index 0000000000..47a679164a
--- /dev/null
+++ b/netwerk/base/mozurl/MozURL.cpp
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/StaticPrefs_security.h"
+
+extern "C" {
+
+bool Gecko_StrictFileOriginPolicy() {
+ return mozilla::StaticPrefs::security_fileuri_strict_origin_policy();
+}
+}
diff --git a/netwerk/base/mozurl/MozURL.h b/netwerk/base/mozurl/MozURL.h
new file mode 100644
index 0000000000..60e8c189ae
--- /dev/null
+++ b/netwerk/base/mozurl/MozURL.h
@@ -0,0 +1,233 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozURL_h__
+#define mozURL_h__
+
+#include "mozilla/net/MozURL_ffi.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+
+namespace mozilla {
+namespace net {
+
+// This class provides a thread-safe, immutable URL parser.
+// As long as there is RefPtr to the object, you may use it on any thread.
+// The constructor is private. One can instantiate the object by
+// calling the Init() method as such:
+//
+// RefPtr<MozURL> url;
+// nsAutoCString href("http://example.com/path?query#ref");
+// nsresult rv = MozURL::Init(getter_AddRefs(url), href);
+// if (NS_SUCCEEDED(rv)) { /* use url */ }
+//
+// When changing the URL is needed, you need to call the Mutate() method.
+// This gives you a Mutator object, on which you can perform setter operations.
+// Calling Finalize() on the Mutator will result in a new MozURL and a status
+// code. If any of the setter operations failed, it will be reflected in the
+// status code, and a null MozURL.
+//
+// Note: In the case of a domain name containing non-ascii characters,
+// GetSpec and GetHostname will return the IDNA(punycode) version of the host.
+// Also note that for now, MozURL only supports the UTF-8 charset.
+//
+// Implementor Note: This type is only a holder for methods in C++, and does not
+// reflect the actual layout of the type.
+class MozURL final {
+ public:
+ static nsresult Init(MozURL** aURL, const nsACString& aSpec,
+ const MozURL* aBaseURL = nullptr) {
+ return mozurl_new(aURL, &aSpec, aBaseURL);
+ }
+
+ nsDependentCSubstring Spec() const { return mozurl_spec(this); }
+ nsDependentCSubstring Scheme() const { return mozurl_scheme(this); }
+ nsDependentCSubstring Username() const { return mozurl_username(this); }
+ nsDependentCSubstring Password() const { return mozurl_password(this); }
+ // Will return the hostname of URL. If the hostname is an IPv6 address,
+ // it will be enclosed in square brackets, such as `[::1]`
+ nsDependentCSubstring Host() const { return mozurl_host(this); }
+ // Will return the port number, if specified, or -1
+ int32_t Port() const { return mozurl_port(this); }
+ int32_t RealPort() const { return mozurl_real_port(this); }
+ // If the URL's port number is equal to the default port, will only return the
+ // hostname, otherwise it will return a string of the form `{host}:{port}`
+ // See: https://url.spec.whatwg.org/#default-port
+ nsDependentCSubstring HostPort() const { return mozurl_host_port(this); }
+ nsDependentCSubstring FilePath() const { return mozurl_filepath(this); }
+ nsDependentCSubstring Path() const { return mozurl_path(this); }
+ nsDependentCSubstring Query() const { return mozurl_query(this); }
+ bool HasQuery() const { return mozurl_has_query(this); }
+ nsDependentCSubstring Ref() const { return mozurl_fragment(this); }
+ bool HasFragment() const { return mozurl_has_fragment(this); }
+ nsDependentCSubstring Directory() const { return mozurl_directory(this); }
+ nsDependentCSubstring PrePath() const { return mozurl_prepath(this); }
+ nsDependentCSubstring SpecNoRef() const { return mozurl_spec_no_ref(this); }
+
+ // This matches the definition of origins and base domains in nsIPrincipal for
+ // almost all URIs (some rare file:// URIs don't match and it would be hard to
+ // fix them). It definitely matches nsIPrincipal for URIs used in quota
+ // manager and there are checks in quota manager and its clients that prevent
+ // different definitions (see QuotaManager::IsPrincipalInfoValid).
+ // See also TestMozURL.cpp which enumerates a huge pile of URIs and checks
+ // that origin and base domain definitions are in sync.
+ void Origin(nsACString& aOrigin) const { mozurl_origin(this, &aOrigin); }
+ nsresult BaseDomain(nsACString& aBaseDomain) const {
+ return mozurl_base_domain(this, &aBaseDomain);
+ }
+ bool CannotBeABase() { return mozurl_cannot_be_a_base(this); }
+
+ nsresult GetCommonBase(const MozURL* aOther, MozURL** aCommon) const {
+ return mozurl_common_base(this, aOther, aCommon);
+ }
+ nsresult GetRelative(const MozURL* aOther, nsACString* aRelative) const {
+ return mozurl_relative(this, aOther, aRelative);
+ }
+
+ size_t SizeOf() { return mozurl_sizeof(this); }
+
+ class Mutator {
+ public:
+ // Calling this method will result in the creation of a new MozURL that
+ // adopts the mutator's mURL.
+ // If any of the setters failed with an error code, that error code will be
+ // returned here. It will also return an error code if Finalize is called
+ // more than once on the Mutator.
+ nsresult Finalize(MozURL** aURL) {
+ nsresult rv = GetStatus();
+ if (NS_SUCCEEDED(rv)) {
+ mURL.forget(aURL);
+ } else {
+ *aURL = nullptr;
+ }
+ return rv;
+ }
+
+ // These setter methods will return a reference to `this` so that you may
+ // chain setter operations as such:
+ //
+ // RefPtr<MozURL> url2;
+ // nsresult rv = url->Mutate().SetHostname("newhost"_ns)
+ // .SetFilePath("new/file/path"_ns)
+ // .Finalize(getter_AddRefs(url2));
+ // if (NS_SUCCEEDED(rv)) { /* use url2 */ }
+ Mutator& SetScheme(const nsACString& aScheme) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_scheme(mURL, &aScheme);
+ }
+ return *this;
+ }
+ Mutator& SetUsername(const nsACString& aUser) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_username(mURL, &aUser);
+ }
+ return *this;
+ }
+ Mutator& SetPassword(const nsACString& aPassword) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_password(mURL, &aPassword);
+ }
+ return *this;
+ }
+ Mutator& SetHostname(const nsACString& aHost) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_hostname(mURL, &aHost);
+ }
+ return *this;
+ }
+ Mutator& SetHostPort(const nsACString& aHostPort) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_host_port(mURL, &aHostPort);
+ }
+ return *this;
+ }
+ Mutator& SetFilePath(const nsACString& aPath) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_pathname(mURL, &aPath);
+ }
+ return *this;
+ }
+ Mutator& SetQuery(const nsACString& aQuery) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_query(mURL, &aQuery);
+ }
+ return *this;
+ }
+ Mutator& SetRef(const nsACString& aRef) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_fragment(mURL, &aRef);
+ }
+ return *this;
+ }
+ Mutator& SetPort(int32_t aPort) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_port_no(mURL, aPort);
+ }
+ return *this;
+ }
+
+ // This method returns the status code of the setter operations.
+ // If any of the setters failed, it will return the code of the first error
+ // that occured. If none of the setters failed, it will return NS_OK.
+ // This method is useful to avoid doing expensive operations when the result
+ // would not be used because an error occurred. For example:
+ //
+ // RefPtr<MozURL> url2;
+ // MozURL::Mutator mut = url->Mutate();
+ // mut.SetScheme("!@#$"); // this would fail
+ // if (NS_SUCCEDED(mut.GetStatus())) {
+ // nsAutoCString host(ExpensiveComputing());
+ // rv = mut.SetHostname(host).Finalize(getter_AddRefs(url2));
+ // }
+ // if (NS_SUCCEEDED(rv)) { /* use url2 */ }
+ nsresult GetStatus() { return mURL ? mStatus : NS_ERROR_NOT_AVAILABLE; }
+
+ static Result<Mutator, nsresult> FromSpec(
+ const nsACString& aSpec, const MozURL* aBaseURL = nullptr) {
+ Mutator m = Mutator(aSpec, aBaseURL);
+ if (m.mURL) {
+ MOZ_ASSERT(NS_SUCCEEDED(m.mStatus));
+ return m;
+ }
+
+ MOZ_ASSERT(NS_FAILED(m.mStatus));
+ return Err(m.mStatus);
+ }
+
+ private:
+ explicit Mutator(MozURL* aUrl) : mStatus(NS_OK) {
+ mozurl_clone(aUrl, getter_AddRefs(mURL));
+ }
+
+ // This is only used to create a mutator from a string without cloning it
+ // so we avoid a pointless copy in FromSpec. It is important that we
+ // check the value of mURL afterwards.
+ explicit Mutator(const nsACString& aSpec,
+ const MozURL* aBaseURL = nullptr) {
+ mStatus = mozurl_new(getter_AddRefs(mURL), &aSpec, aBaseURL);
+ }
+ RefPtr<MozURL> mURL;
+ nsresult mStatus;
+ friend class MozURL;
+ };
+
+ Mutator Mutate() { return Mutator(this); }
+
+ // AddRef and Release are non-virtual on this type, and always call into rust.
+ void AddRef() { mozurl_addref(this); }
+ void Release() { mozurl_release(this); }
+
+ private:
+ // Make it a compile time error for C++ code to ever create, destruct, or copy
+ // MozURL objects. All of these operations will be performed by rust.
+ MozURL(); /* never defined */
+ ~MozURL(); /* never defined */
+ MozURL(const MozURL&) = delete;
+ MozURL& operator=(const MozURL&) = delete;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozURL_h__
diff --git a/netwerk/base/mozurl/cbindgen.toml b/netwerk/base/mozurl/cbindgen.toml
new file mode 100644
index 0000000000..bb5fdbd08f
--- /dev/null
+++ b/netwerk/base/mozurl/cbindgen.toml
@@ -0,0 +1,61 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The MozURL type is implemented in Rust code, and uses extern "C" FFI calls to
+ * operate on the internal data structure. This file contains C declarations of
+ * these files.
+
+ * WARNING: DO NOT CALL ANY OF THESE FUNCTIONS. USE |MozURL| INSTEAD! */
+ """
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */
+
+namespace mozilla {
+namespace net {
+class MozURL;
+} // namespace net
+} // namespace mozilla
+
+extern "C" {
+
+// FFI-compatible string slice struct used internally by MozURL.
+// Coerces to nsDependentCSubstring.
+struct MozURLSpecSlice {
+ char* mData;
+ uint32_t mLen;
+
+ operator nsDependentCSubstring() {
+ return nsDependentCSubstring(mData, mLen);
+ }
+};
+
+nsresult mozurl_new(mozilla::net::MozURL** aResult, const nsACString* aSpec,
+ /* optional */ const mozilla::net::MozURL* aBase);
+
+void mozurl_clone(const mozilla::net::MozURL* aThis,
+ mozilla::net::MozURL** aResult);
+
+nsresult mozurl_common_base(const mozilla::net::MozURL* aUrl1,
+ const mozilla::net::MozURL* aUrl2,
+ mozilla::net::MozURL** aResult);
+}
+
+"""
+
+include_guard = "mozilla_net_MozURL_ffi_h"
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = []
+includes = ["nsError.h", "nsString.h"]
+
+[export]
+exclude = ["Gecko_StrictFileOriginPolicy", "MozURL", "SpecSlice", "mozurl_new", "mozurl_clone", "mozurl_common_base"]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+
+[export.rename]
+"SpecSlice" = "MozURLSpecSlice"
+"MozURL" = "mozilla::net::MozURL"
diff --git a/netwerk/base/mozurl/moz.build b/netwerk/base/mozurl/moz.build
new file mode 100644
index 0000000000..0a4e522b5a
--- /dev/null
+++ b/netwerk/base/mozurl/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "MozURL.h",
+]
+
+SOURCES += [
+ "MozURL.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ CbindgenHeader("MozURL_ffi.h", inputs=["/netwerk/base/mozurl"])
+
+ EXPORTS.mozilla.net += [
+ "!MozURL_ffi.h",
+ ]
diff --git a/netwerk/base/mozurl/src/lib.rs b/netwerk/base/mozurl/src/lib.rs
new file mode 100644
index 0000000000..21be5f7e8b
--- /dev/null
+++ b/netwerk/base/mozurl/src/lib.rs
@@ -0,0 +1,574 @@
+/* -*- Mode: rust; rust-indent-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate url;
+use url::quirks;
+use url::{ParseOptions, Position, Url};
+
+extern crate nsstring;
+use nsstring::{nsACString, nsCString};
+
+extern crate nserror;
+use nserror::*;
+
+extern crate xpcom;
+use xpcom::interfaces::mozIThirdPartyUtil;
+use xpcom::{AtomicRefcnt, RefCounted, RefPtr};
+
+extern crate uuid;
+use uuid::Uuid;
+
+use std::fmt::Write;
+use std::marker::PhantomData;
+use std::mem;
+use std::ops;
+use std::ptr;
+use std::str;
+
+extern "C" {
+ fn Gecko_StrictFileOriginPolicy() -> bool;
+}
+
+/// Helper macro. If the expression $e is Ok(t) evaluates to t, otherwise,
+/// returns NS_ERROR_MALFORMED_URI.
+macro_rules! try_or_malformed {
+ ($e:expr) => {
+ match $e {
+ Ok(v) => v,
+ Err(_) => return NS_ERROR_MALFORMED_URI,
+ }
+ };
+}
+
+fn parser<'a>() -> ParseOptions<'a> {
+ Url::options()
+}
+
+fn default_port(scheme: &str) -> Option<u16> {
+ match scheme {
+ "ftp" => Some(21),
+ "gopher" => Some(70),
+ "http" => Some(80),
+ "https" => Some(443),
+ "ws" => Some(80),
+ "wss" => Some(443),
+ "rtsp" => Some(443),
+ "android" => Some(443),
+ _ => None,
+ }
+}
+
+/// A slice into the backing string. This type is only valid as long as the
+/// MozURL which it was pulled from is valid. In C++, this type implicitly
+/// converts to a nsDependentCString, and is an implementation detail.
+///
+/// This type exists because, unlike &str, this type is safe to return over FFI.
+#[repr(C)]
+pub struct SpecSlice<'a> {
+ data: *const u8,
+ len: u32,
+ _marker: PhantomData<&'a [u8]>,
+}
+
+impl<'a> From<&'a str> for SpecSlice<'a> {
+ fn from(s: &'a str) -> SpecSlice<'a> {
+ assert!(s.len() < u32::max_value() as usize);
+ SpecSlice {
+ data: s.as_ptr(),
+ len: s.len() as u32,
+ _marker: PhantomData,
+ }
+ }
+}
+
+/// The MozURL reference-counted threadsafe URL type. This type intentionally
+/// implements no XPCOM interfaces, and all method calls are non-virtual.
+#[repr(C)]
+pub struct MozURL {
+ pub url: Url,
+ refcnt: AtomicRefcnt,
+}
+
+impl MozURL {
+ pub fn from_url(url: Url) -> RefPtr<MozURL> {
+ // Actually allocate the URL on the heap. This is the only place we actually
+ // create a MozURL, other than in clone().
+ unsafe {
+ RefPtr::from_raw(Box::into_raw(Box::new(MozURL {
+ url: url,
+ refcnt: AtomicRefcnt::new(),
+ })))
+ .unwrap()
+ }
+ }
+}
+
+impl ops::Deref for MozURL {
+ type Target = Url;
+ fn deref(&self) -> &Url {
+ &self.url
+ }
+}
+impl ops::DerefMut for MozURL {
+ fn deref_mut(&mut self) -> &mut Url {
+ &mut self.url
+ }
+}
+
+// Memory Management for MozURL
+#[no_mangle]
+pub unsafe extern "C" fn mozurl_addref(url: &MozURL) {
+ url.refcnt.inc();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn mozurl_release(url: &MozURL) {
+ let rc = url.refcnt.dec();
+ if rc == 0 {
+ mem::drop(Box::from_raw(url as *const MozURL as *mut MozURL));
+ }
+}
+
+// xpcom::RefPtr support
+unsafe impl RefCounted for MozURL {
+ unsafe fn addref(&self) {
+ mozurl_addref(self);
+ }
+ unsafe fn release(&self) {
+ mozurl_release(self);
+ }
+}
+
+// Allocate a new MozURL object with a RefCnt of 1, and store a pointer to it
+// into url.
+#[no_mangle]
+pub extern "C" fn mozurl_new(
+ result: &mut *const MozURL,
+ spec: &nsACString,
+ base: Option<&MozURL>,
+) -> nsresult {
+ *result = ptr::null_mut();
+
+ let spec = try_or_malformed!(str::from_utf8(spec));
+ let url = if let Some(base) = base {
+ try_or_malformed!(base.url.join(spec))
+ } else {
+ try_or_malformed!(parser().parse(spec))
+ };
+
+ MozURL::from_url(url).forget(result);
+ NS_OK
+}
+
+/// Allocate a new MozURL object which is a clone of the original, and store a
+/// pointer to it into newurl.
+#[no_mangle]
+pub extern "C" fn mozurl_clone(url: &MozURL, newurl: &mut *const MozURL) {
+ MozURL::from_url(url.url.clone()).forget(newurl);
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_spec(url: &MozURL) -> SpecSlice {
+ url.as_ref().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_scheme(url: &MozURL) -> SpecSlice {
+ url.scheme().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_username(url: &MozURL) -> SpecSlice {
+ if url.cannot_be_a_base() {
+ "".into()
+ } else {
+ url.username().into()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_password(url: &MozURL) -> SpecSlice {
+ url.password().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_host(url: &MozURL) -> SpecSlice {
+ url.host_str().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_port(url: &MozURL) -> i32 {
+ // NOTE: Gecko uses -1 to represent the default port.
+ url.port().map(|p| p as i32).unwrap_or(-1)
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_real_port(url: &MozURL) -> i32 {
+ url.port()
+ .or_else(|| default_port(url.scheme()))
+ .map(|p| p as i32)
+ .unwrap_or(-1)
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_host_port(url: &MozURL) -> SpecSlice {
+ (&url[Position::BeforeHost..Position::BeforePath]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_filepath(url: &MozURL) -> SpecSlice {
+ url.path().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_path(url: &MozURL) -> SpecSlice {
+ (&url[Position::BeforePath..]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_query(url: &MozURL) -> SpecSlice {
+ url.query().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_fragment(url: &MozURL) -> SpecSlice {
+ url.fragment().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_spec_no_ref(url: &MozURL) -> SpecSlice {
+ (&url[..Position::AfterQuery]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_has_fragment(url: &MozURL) -> bool {
+ url.fragment().is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_has_query(url: &MozURL) -> bool {
+ url.query().is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_directory(url: &MozURL) -> SpecSlice {
+ if let Some(position) = url.path().rfind('/') {
+ url.path()[..position + 1].into()
+ } else {
+ url.path().into()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_prepath(url: &MozURL) -> SpecSlice {
+ (&url[..Position::BeforePath]).into()
+}
+
+fn get_origin(url: &MozURL) -> Option<String> {
+ match url.scheme() {
+ "blob" | "ftp" | "http" | "https" | "ws" | "wss" => {
+ Some(url.origin().ascii_serialization())
+ }
+ "indexeddb" | "moz-extension" | "resource" => {
+ let host = url.host_str().unwrap_or("");
+
+ let port = url.port().or_else(|| default_port(url.scheme()));
+
+ if port == default_port(url.scheme()) {
+ Some(format!("{}://{}", url.scheme(), host))
+ } else {
+ Some(format!("{}://{}:{}", url.scheme(), host, port.unwrap()))
+ }
+ }
+ "file" => {
+ if unsafe { Gecko_StrictFileOriginPolicy() } {
+ Some(url[..Position::AfterPath].to_owned())
+ } else {
+ Some("file://UNIVERSAL_FILE_URI_ORIGIN".to_owned())
+ }
+ }
+ "about" | "moz-safe-about" => Some(url[..Position::AfterPath].to_owned()),
+ _ => None,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_origin(url: &MozURL, origin: &mut nsACString) {
+ let origin_str = if !url.as_ref().starts_with("about:blank") {
+ get_origin(url)
+ } else {
+ None
+ };
+
+ let origin_str = origin_str.unwrap_or_else(|| {
+ // nsIPrincipal stores the uuid, so the same uuid is returned everytime.
+ // We can't do that for MozURL because it can be used across threads.
+ // Storing uuid would mutate the object which would cause races between
+ // threads.
+ format!("moz-nullprincipal:{{{}}}", Uuid::new_v4())
+ });
+
+ // NOTE: Try to re-use the allocation we got from rust-url, and transfer
+ // ownership of the buffer to C++.
+ let mut o = nsCString::from(origin_str);
+ origin.take_from(&mut o);
+}
+
+fn get_base_domain(url: &MozURL) -> Result<Option<String>, nsresult> {
+ match url.scheme() {
+ "ftp" | "http" | "https" | "moz-extension" | "resource" => {
+ let third_party_util: RefPtr<mozIThirdPartyUtil> =
+ xpcom::components::ThirdPartyUtil::service()
+ .map_err(|_| NS_ERROR_ILLEGAL_DURING_SHUTDOWN)?;
+
+ let scheme = nsCString::from(url.scheme());
+
+ let mut host_str = url.host_str().unwrap_or("");
+
+ if host_str.starts_with('[') && host_str.ends_with(']') {
+ host_str = &host_str[1..host_str.len() - 1];
+ }
+
+ let host = nsCString::from(host_str);
+
+ unsafe {
+ let mut string = nsCString::new();
+ third_party_util
+ .GetBaseDomainFromSchemeHost(&*scheme, &*host, &mut *string)
+ .to_result()?;
+
+ // We know that GetBaseDomainFromSchemeHost returns AUTF8String, so just
+ // use unwrap().
+ Ok(Some(String::from_utf8(string.to_vec()).unwrap()))
+ }
+ }
+ "ws" | "wss" => Ok(Some(url.as_ref().to_owned())),
+ "file" => {
+ if unsafe { Gecko_StrictFileOriginPolicy() } {
+ Ok(Some(url.path().to_owned()))
+ } else {
+ Ok(Some("UNIVERSAL_FILE_URI_ORIGIN".to_owned()))
+ }
+ }
+ "about" | "moz-safe-about" | "indexeddb" => Ok(Some(url.as_ref().to_owned())),
+ _ => Ok(None),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_base_domain(url: &MozURL, base_domain: &mut nsACString) -> nsresult {
+ let base_domain_str = if !url.as_ref().starts_with("about:blank") {
+ match get_base_domain(url) {
+ Ok(domain) => domain,
+ Err(rv) => return rv,
+ }
+ } else {
+ None
+ };
+
+ let base_domain_str = base_domain_str.unwrap_or_else(|| {
+ // See the comment in mozurl_origin about why we return a new uuid for
+ // "moz-nullprincipals".
+ format!("{{{}}}", Uuid::new_v4())
+ });
+
+ let mut bd = nsCString::from(base_domain_str);
+ base_domain.take_from(&mut bd);
+
+ NS_OK
+}
+
+// Helper macro for debug asserting that we're the only reference to MozURL.
+macro_rules! debug_assert_mut {
+ ($e:expr) => {
+ debug_assert_eq!($e.refcnt.get(), 1, "Cannot mutate an aliased MozURL!");
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_cannot_be_a_base(url: &mut MozURL) -> bool {
+ debug_assert_mut!(url);
+ url.cannot_be_a_base()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_scheme(url: &mut MozURL, scheme: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let scheme = try_or_malformed!(str::from_utf8(scheme));
+ try_or_malformed!(quirks::set_protocol(url, scheme));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_username(url: &mut MozURL, username: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let username = try_or_malformed!(str::from_utf8(username));
+ try_or_malformed!(quirks::set_username(url, username));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_password(url: &mut MozURL, password: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let password = try_or_malformed!(str::from_utf8(password));
+ try_or_malformed!(quirks::set_password(url, password));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_host_port(url: &mut MozURL, hostport: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let hostport = try_or_malformed!(str::from_utf8(hostport));
+ try_or_malformed!(quirks::set_host(url, hostport));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_hostname(url: &mut MozURL, host: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let host = try_or_malformed!(str::from_utf8(host));
+ try_or_malformed!(quirks::set_hostname(url, host));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_port_no(url: &mut MozURL, new_port: i32) -> nsresult {
+ debug_assert_mut!(url);
+ if url.cannot_be_a_base() {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ let port = match new_port {
+ new if new < 0 || u16::max_value() as i32 <= new => None,
+ new if Some(new as u16) == default_port(url.scheme()) => None,
+ new => Some(new as u16),
+ };
+ try_or_malformed!(url.set_port(port));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_pathname(url: &mut MozURL, path: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let path = try_or_malformed!(str::from_utf8(path));
+ quirks::set_pathname(url, path);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_query(url: &mut MozURL, query: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let query = try_or_malformed!(str::from_utf8(query));
+ quirks::set_search(url, query);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_fragment(url: &mut MozURL, fragment: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let fragment = try_or_malformed!(str::from_utf8(fragment));
+ quirks::set_hash(url, fragment);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_sizeof(url: &MozURL) -> usize {
+ debug_assert_mut!(url);
+ mem::size_of::<MozURL>() + url.as_str().len()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_common_base(
+ url1: &MozURL,
+ url2: &MozURL,
+ result: &mut *const MozURL,
+) -> nsresult {
+ *result = ptr::null();
+ if url1.url == url2.url {
+ RefPtr::new(url1).forget(result);
+ return NS_OK;
+ }
+
+ if url1.scheme() != url2.scheme()
+ || url1.host() != url2.host()
+ || url1.username() != url2.username()
+ || url1.password() != url2.password()
+ || url1.port() != url2.port()
+ {
+ return NS_OK;
+ }
+
+ match (url1.path_segments(), url2.path_segments()) {
+ (Some(path1), Some(path2)) => {
+ // Take the shared prefix of path segments
+ let mut url = url1.url.clone();
+ if let Ok(mut segs) = url.path_segments_mut() {
+ segs.clear();
+ segs.extend(path1.zip(path2).take_while(|&(a, b)| a == b).map(|p| p.0));
+ } else {
+ return NS_OK;
+ }
+
+ MozURL::from_url(url).forget(result);
+ NS_OK
+ }
+ _ => NS_OK,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_relative(
+ url1: &MozURL,
+ url2: &MozURL,
+ result: &mut nsACString,
+) -> nsresult {
+ if url1.url == url2.url {
+ result.truncate();
+ return NS_OK;
+ }
+
+ if url1.scheme() != url2.scheme()
+ || url1.host() != url2.host()
+ || url1.username() != url2.username()
+ || url1.password() != url2.password()
+ || url1.port() != url2.port()
+ {
+ result.assign(url2.as_ref());
+ return NS_OK;
+ }
+
+ match (url1.path_segments(), url2.path_segments()) {
+ (Some(mut path1), Some(mut path2)) => {
+ // Exhaust the part of the iterators that match
+ while let (Some(ref p1), Some(ref p2)) = (path1.next(), path2.next()) {
+ if p1 != p2 {
+ break;
+ }
+ }
+
+ result.truncate();
+ for _ in path1 {
+ result.append("../");
+ }
+ for p2 in path2 {
+ result.append(p2);
+ result.append("/");
+ }
+ }
+ _ => {
+ result.assign(url2.as_ref());
+ }
+ }
+ NS_OK
+}
+
+/// This type is used by nsStandardURL
+#[no_mangle]
+pub extern "C" fn rusturl_parse_ipv6addr(input: &nsACString, addr: &mut nsACString) -> nsresult {
+ let ip6 = try_or_malformed!(str::from_utf8(input));
+ let host = try_or_malformed!(url::Host::parse(ip6));
+ let _ = write!(addr, "{}", host);
+ NS_OK
+}
diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h
new file mode 100644
index 0000000000..258cc5cb58
--- /dev/null
+++ b/netwerk/base/netCore.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __netCore_h__
+#define __netCore_h__
+
+#include "nsError.h"
+
+// Where most necko status messages come from:
+#define NECKO_MSGS_URL "chrome://necko/locale/necko.properties"
+
+#endif // __netCore_h__
diff --git a/netwerk/base/nsASocketHandler.h b/netwerk/base/nsASocketHandler.h
new file mode 100644
index 0000000000..c68da7c359
--- /dev/null
+++ b/netwerk/base/nsASocketHandler.h
@@ -0,0 +1,102 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsASocketHandler_h__
+#define nsASocketHandler_h__
+
+#include "nsError.h"
+#include "nsINetAddr.h"
+#include "nsISupports.h"
+#include "prio.h"
+
+// socket handler used by nsISocketTransportService.
+// methods are only called on the socket thread.
+
+class nsASocketHandler : public nsISupports {
+ public:
+ nsASocketHandler() = default;
+
+ //
+ // this condition variable will be checked to determine if the socket
+ // handler should be detached. it must only be accessed on the socket
+ // thread.
+ //
+ nsresult mCondition{NS_OK};
+
+ //
+ // these flags can only be modified on the socket transport thread.
+ // the socket transport service will check these flags before calling
+ // PR_Poll.
+ //
+ uint16_t mPollFlags{0};
+
+ //
+ // this value specifies the maximum amount of time in seconds that may be
+ // spent waiting for activity on this socket. if this timeout is reached,
+ // then OnSocketReady will be called with outFlags = -1.
+ //
+ // the default value for this member is UINT16_MAX, which disables the
+ // timeout error checking. (i.e., a timeout value of UINT16_MAX is
+ // never reached.)
+ //
+ uint16_t mPollTimeout{UINT16_MAX};
+
+ bool mIsPrivate{false};
+
+ //
+ // called to service a socket
+ //
+ // params:
+ // socketRef - socket identifier
+ // fd - socket file descriptor
+ // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns
+ // or -1 if a timeout occurred
+ //
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) = 0;
+
+ //
+ // called when a socket is no longer under the control of the socket
+ // transport service. the socket handler may close the socket at this
+ // point. after this call returns, the handler will no longer be owned
+ // by the socket transport service.
+ //
+ virtual void OnSocketDetached(PRFileDesc* fd) = 0;
+
+ //
+ // called to determine if the socket is for a local peer.
+ // when used for server sockets, indicates if it only accepts local
+ // connections.
+ //
+ virtual void IsLocal(bool* aIsLocal) = 0;
+
+ //
+ // called to determine if this socket should not be terminated when Gecko
+ // is turned offline. This is mostly useful for the debugging server
+ // socket.
+ //
+ virtual void KeepWhenOffline(bool* aKeepWhenOffline) {
+ *aKeepWhenOffline = false;
+ }
+
+ //
+ // called when global pref for keepalive has changed.
+ //
+ virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) {}
+
+ //
+ // called to determine the NetAddr. addr can only be assumed to be initialized
+ // when NS_OK is returned
+ //
+ virtual nsresult GetRemoteAddr(mozilla::net::NetAddr* addr) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ //
+ // returns the number of bytes sent/transmitted over the socket
+ //
+ virtual uint64_t ByteCountSent() = 0;
+ virtual uint64_t ByteCountReceived() = 0;
+};
+
+#endif // !nsASocketHandler_h__
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
new file mode 100644
index 0000000000..7a10d4d3f0
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+
+#include "nsIOService.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsILoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRedirectLog("nsRedirect");
+#undef LOG
+#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, nsIAsyncVerifyRedirectCallback,
+ nsIRunnable, nsINamed)
+
+class nsAsyncVerifyRedirectCallbackEvent : public Runnable {
+ public:
+ nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback* cb,
+ nsresult result)
+ : Runnable("nsAsyncVerifyRedirectCallbackEvent"),
+ mCallback(cb),
+ mResult(result) {}
+
+ NS_IMETHOD Run() override {
+ LOG(
+ ("nsAsyncVerifyRedirectCallbackEvent::Run() "
+ "callback to %p with result %" PRIx32,
+ mCallback.get(), static_cast<uint32_t>(mResult)));
+ (void)mCallback->OnRedirectVerifyCallback(mResult);
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
+ nsresult mResult;
+};
+
+nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() {
+ NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0,
+ "Did not receive all required callbacks!");
+}
+
+nsresult nsAsyncRedirectVerifyHelper::Init(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget, bool synchronize) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::Init() "
+ "oldChan=%p newChan=%p",
+ oldChan, newChan));
+ mOldChan = oldChan;
+ mNewChan = newChan;
+ mFlags = flags;
+ mCallbackEventTarget = NS_IsMainThread() && mainThreadEventTarget
+ ? mainThreadEventTarget
+ : GetCurrentSerialEventTarget();
+
+ if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
+ nsCOMPtr<nsILoadInfo> loadInfo = oldChan->LoadInfo();
+ if (loadInfo->GetDontFollowRedirects()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+ }
+
+ if (synchronize) mWaitingForRedirectCallback = true;
+
+ nsCOMPtr<nsIRunnable> runnable = this;
+ nsresult rv;
+ rv = mainThreadEventTarget
+ ? mainThreadEventTarget->Dispatch(runnable.forget())
+ : GetMainThreadSerialEventTarget()->Dispatch(runnable.forget());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (synchronize) {
+ if (!SpinEventLoopUntil("nsAsyncRedirectVerifyHelper::Init"_ns,
+ [&]() { return !mWaitingForRedirectCallback; })) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
+ "result=%" PRIx32 " expectedCBs=%u mResult=%" PRIx32,
+ static_cast<uint32_t>(result), mExpectedCallbacks,
+ static_cast<uint32_t>(mResult)));
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ mExpectedCallbacks > 0,
+ "OnRedirectVerifyCallback called more times than expected");
+ if (mExpectedCallbacks <= 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ --mExpectedCallbacks;
+
+ // If response indicates failure we may call back immediately
+ if (NS_FAILED(result)) {
+ // We chose to store the first failure-value (as opposed to the last)
+ if (NS_SUCCEEDED(mResult)) mResult = result;
+
+ // If InitCallback() has been called, just invoke the callback and
+ // return. Otherwise it will be invoked from InitCallback()
+ if (mCallbackInitiated) {
+ ExplicitCallback(mResult);
+ return NS_OK;
+ }
+ }
+
+ // If the expected-counter is in balance and InitCallback() was called, all
+ // sinks have agreed that the redirect is ok and we can invoke our callback
+ if (mCallbackInitiated && mExpectedCallbacks == 0) {
+ ExplicitCallback(mResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(
+ nsIChannelEventSink* sink, nsIChannel* oldChannel, nsIChannel* newChannel,
+ uint32_t flags) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
+ "sink=%p expectedCBs=%u mResult=%" PRIx32,
+ sink, mExpectedCallbacks, static_cast<uint32_t>(mResult)));
+
+ ++mExpectedCallbacks;
+
+ if (IsOldChannelCanceled()) {
+ LOG(
+ (" old channel has been canceled, cancel the redirect by "
+ "emulating OnRedirectVerifyCallback..."));
+ (void)OnRedirectVerifyCallback(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ nsresult rv =
+ sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+
+ LOG((" result=%" PRIx32 " expectedCBs=%u", static_cast<uint32_t>(rv),
+ mExpectedCallbacks));
+
+ // If the sink returns failure from this call the redirect is vetoed. We
+ // emulate a callback from the sink in this case in order to perform all
+ // the necessary logic.
+ if (NS_FAILED(rv)) {
+ LOG((" emulating OnRedirectVerifyCallback..."));
+ (void)OnRedirectVerifyCallback(rv);
+ }
+
+ return rv; // Return the actual status since our caller may need it
+}
+
+void nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "result=%" PRIx32
+ " expectedCBs=%u mCallbackInitiated=%u mResult=%" PRIx32,
+ static_cast<uint32_t>(result), mExpectedCallbacks, mCallbackInitiated,
+ static_cast<uint32_t>(mResult)));
+
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> callback(
+ do_QueryInterface(mOldChan));
+
+ if (!callback || !mCallbackEventTarget) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "callback=%p mCallbackEventTarget=%p",
+ callback.get(), mCallbackEventTarget.get()));
+ return;
+ }
+
+ mCallbackInitiated = false; // reset to ensure only one callback
+ mWaitingForRedirectCallback = false;
+
+ // Now, dispatch the callback on the event-target which called Init()
+ nsCOMPtr<nsIRunnable> event =
+ new nsAsyncVerifyRedirectCallbackEvent(callback, result);
+ if (!event) {
+ NS_WARNING(
+ "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed creating callback event!");
+ return;
+ }
+ nsresult rv = mCallbackEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed dispatching callback event!");
+ } else {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "dispatched callback event=%p",
+ event.get()));
+ }
+}
+
+void nsAsyncRedirectVerifyHelper::InitCallback() {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::InitCallback() "
+ "expectedCBs=%d mResult=%" PRIx32,
+ mExpectedCallbacks, static_cast<uint32_t>(mResult)));
+
+ mCallbackInitiated = true;
+
+ // Invoke the callback if we are done
+ if (mExpectedCallbacks == 0) ExplicitCallback(mResult);
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsAsyncRedirectVerifyHelper");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::Run() {
+ /* If the channel got canceled after it fired AsyncOnChannelRedirect
+ * and before we got here, mostly because docloader load has been canceled,
+ * we must completely ignore this notification and prevent any further
+ * notification.
+ */
+ if (IsOldChannelCanceled()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+
+ // First, the global observer
+ NS_ASSERTION(gIOService, "Must have an IO service at this point");
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
+ nsresult rv =
+ gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, mFlags, this);
+ if (NS_FAILED(rv)) {
+ ExplicitCallback(rv);
+ return NS_OK;
+ }
+
+ // Now, the per-channel observers
+ nsCOMPtr<nsIChannelEventSink> sink;
+ NS_QueryNotificationCallbacks(mOldChan, sink);
+ if (sink) {
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
+ rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags);
+ }
+
+ // All invocations to AsyncOnChannelRedirect has been done - call
+ // InitCallback() to flag this
+ InitCallback();
+ return NS_OK;
+}
+
+bool nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() {
+ if (!mOldChan) {
+ return false;
+ }
+ bool canceled;
+ nsresult rv = mOldChan->GetCanceled(&canceled);
+ return NS_SUCCEEDED(rv) && canceled;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h
new file mode 100644
index 0000000000..3e1afa8c69
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncRedirectVerifyHelper_h
+#define nsAsyncRedirectVerifyHelper_h
+
+#include "nsIRunnable.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsINamed.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * This class simplifies call of OnChannelRedirect of IOService and
+ * the sink bound with the channel being redirected while the result of
+ * redirect decision is returned through the callback.
+ */
+class nsAsyncRedirectVerifyHelper final
+ : public nsIRunnable,
+ public nsINamed,
+ public nsIAsyncVerifyRedirectCallback {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSINAMED
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ public:
+ nsAsyncRedirectVerifyHelper() = default;
+
+ /*
+ * Calls AsyncOnChannelRedirect() on the given sink with the given
+ * channels and flags. Keeps track of number of async callbacks to expect.
+ */
+ nsresult DelegateOnChannelRedirect(nsIChannelEventSink* sink,
+ nsIChannel* oldChannel,
+ nsIChannel* newChannel, uint32_t flags);
+
+ /**
+ * Initialize and run the chain of AsyncOnChannelRedirect calls. OldChannel
+ * is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect
+ * decision is passed through this interface back to the oldChannel.
+ *
+ * @param oldChan
+ * channel being redirected, MUST implement
+ * nsIAsyncVerifyRedirectCallback
+ * @param newChan
+ * target of the redirect channel
+ * @param flags
+ * redirect flags
+ * @param mainThreadEventTarget
+ * a labeled event target for dispatching runnables
+ * @param synchronize
+ * set to TRUE if you want the Init method wait synchronously for
+ * all redirect callbacks
+ */
+ nsresult Init(nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget,
+ bool synchronize = false);
+
+ protected:
+ nsCOMPtr<nsIChannel> mOldChan;
+ nsCOMPtr<nsIChannel> mNewChan;
+ uint32_t mFlags{0};
+ bool mWaitingForRedirectCallback{false};
+ nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+ bool mCallbackInitiated{false};
+ int32_t mExpectedCallbacks{0};
+ nsresult mResult{NS_OK}; // value passed to callback
+
+ void InitCallback();
+
+ /**
+ * Calls back to |oldChan| as described in Init()
+ */
+ void ExplicitCallback(nsresult result);
+
+ private:
+ ~nsAsyncRedirectVerifyHelper();
+
+ bool IsOldChannelCanceled();
+};
+
+/*
+ * Helper to make the call-stack handle some control-flow for us
+ */
+class nsAsyncRedirectAutoCallback {
+ public:
+ explicit nsAsyncRedirectAutoCallback(
+ nsIAsyncVerifyRedirectCallback* aCallback)
+ : mCallback(aCallback) {
+ mResult = NS_OK;
+ }
+ ~nsAsyncRedirectAutoCallback() {
+ if (mCallback) mCallback->OnRedirectVerifyCallback(mResult);
+ }
+ /*
+ * Call this is you want it to call back with a different result-code
+ */
+ void SetResult(nsresult aRes) { mResult = aRes; }
+ /*
+ * Call this is you want to avoid the callback
+ */
+ void DontCallback() { mCallback = nullptr; }
+
+ private:
+ nsIAsyncVerifyRedirectCallback* mCallback;
+ nsresult mResult;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp
new file mode 100644
index 0000000000..de8363d17d
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.cpp
@@ -0,0 +1,405 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAsyncStreamCopier.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIOService.h"
+#include "nsIEventTarget.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIBufferedStreams.h"
+#include "nsIRequestObserver.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+#undef LOG
+//
+// MOZ_LOG=nsStreamCopier:5
+//
+static LazyLogModule gStreamCopierLog("nsStreamCopier");
+#define LOG(args) MOZ_LOG(gStreamCopierLog, mozilla::LogLevel::Debug, args)
+
+/**
+ * An event used to perform initialization off the main thread.
+ */
+class AsyncApplyBufferingPolicyEvent final : public Runnable {
+ public:
+ /**
+ * @param aCopier
+ * The nsAsyncStreamCopier requesting the information.
+ */
+ explicit AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier)
+ : mozilla::Runnable("AsyncApplyBufferingPolicyEvent"),
+ mCopier(aCopier),
+ mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_IMETHOD Run() override {
+ nsresult rv = mCopier->ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ return NS_OK;
+ }
+
+ rv = mTarget->Dispatch(
+ NewRunnableMethod("nsAsyncStreamCopier::AsyncCopyInternal", mCopier,
+ &nsAsyncStreamCopier::AsyncCopyInternal),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsAsyncStreamCopier> mCopier;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+//-----------------------------------------------------------------------------
+
+nsAsyncStreamCopier::nsAsyncStreamCopier()
+ : mChunkSize(nsIOService::gDefaultSegmentSize) {
+ LOG(("Creating nsAsyncStreamCopier @%p\n", this));
+}
+
+nsAsyncStreamCopier::~nsAsyncStreamCopier() {
+ LOG(("Destroying nsAsyncStreamCopier @%p\n", this));
+}
+
+bool nsAsyncStreamCopier::IsComplete(nsresult* status) {
+ MutexAutoLock lock(mLock);
+ if (status) *status = mStatus;
+ return !mIsPending;
+}
+
+nsIRequest* nsAsyncStreamCopier::AsRequest() {
+ return static_cast<nsIRequest*>(static_cast<nsIAsyncStreamCopier*>(this));
+}
+
+void nsAsyncStreamCopier::Complete(nsresult status) {
+ LOG(("nsAsyncStreamCopier::Complete [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+
+ nsCOMPtr<nsIRequestObserver> observer;
+ nsCOMPtr<nsISupports> ctx;
+ {
+ MutexAutoLock lock(mLock);
+ mCopierCtx = nullptr;
+
+ if (mIsPending) {
+ mIsPending = false;
+ mStatus = status;
+
+ // setup OnStopRequest callback and release references...
+ observer = mObserver;
+ mObserver = nullptr;
+ }
+ }
+
+ if (observer) {
+ LOG((" calling OnStopRequest [status=%" PRIx32 "]\n",
+ static_cast<uint32_t>(status)));
+ observer->OnStopRequest(AsRequest(), status);
+ }
+}
+
+void nsAsyncStreamCopier::OnAsyncCopyComplete(void* closure, nsresult status) {
+ // AddRef'd in AsyncCopy. Will be released at the end of the method.
+ RefPtr<nsAsyncStreamCopier> self = dont_AddRef((nsAsyncStreamCopier*)closure);
+ self->Complete(status);
+}
+
+//-----------------------------------------------------------------------------
+// nsISupports
+
+// We cannot use simply NS_IMPL_ISUPPORTSx as both
+// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest
+
+NS_IMPL_ADDREF(nsAsyncStreamCopier)
+NS_IMPL_RELEASE(nsAsyncStreamCopier)
+NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier)
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier)
+ NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest,
+ nsIAsyncStreamCopier)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports,
+ nsIAsyncStreamCopier)
+ NS_INTERFACE_TABLE_END
+NS_INTERFACE_TABLE_TAIL
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetName(nsACString& name) {
+ name.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::IsPending(bool* result) {
+ *result = !IsComplete();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetStatus(nsresult* status) {
+ IsComplete(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAsyncStreamCopier::SetCanceledReason(
+ const nsACString& aReason) {
+ return nsIAsyncStreamCopier::SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsAsyncStreamCopier::GetCanceledReason(nsACString& aReason) {
+ return nsIAsyncStreamCopier::GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsAsyncStreamCopier::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return nsIAsyncStreamCopier::CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Cancel(nsresult status) {
+ nsCOMPtr<nsISupports> copierCtx;
+ {
+ MutexAutoLock lock(mLock);
+ if (!mIsPending) {
+ return NS_OK;
+ }
+ copierCtx.swap(mCopierCtx);
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ NS_WARNING("cancel with non-failure status code");
+ status = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (copierCtx) NS_CancelAsyncCopy(copierCtx, status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Suspend() {
+ MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Resume() {
+ MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return nsIAsyncStreamCopier::GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return nsIAsyncStreamCopier::SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
+
+// Can't be accessed by multiple threads yet
+nsresult nsAsyncStreamCopier::InitInternal(
+ nsIInputStream* source, nsIOutputStream* sink, nsIEventTarget* target,
+ uint32_t chunkSize, bool closeSource,
+ bool closeSink) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ NS_ASSERTION(!mSource && !mSink, "Init() called more than once");
+ if (chunkSize == 0) {
+ chunkSize = nsIOService::gDefaultSegmentSize;
+ }
+ mChunkSize = chunkSize;
+
+ mSource = source;
+ mSink = sink;
+ mCloseSource = closeSource;
+ mCloseSink = closeSink;
+
+ if (target) {
+ mTarget = target;
+ } else {
+ nsresult rv;
+ mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, bool sourceBuffered,
+ bool sinkBuffered, uint32_t chunkSize,
+ bool closeSource, bool closeSink) {
+ NS_ASSERTION(sourceBuffered || sinkBuffered,
+ "at least one stream must be buffered");
+ mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS
+ : NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, uint32_t chunkSize,
+ bool closeSource, bool closeSink) {
+ mShouldSniffBuffering = true;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+/**
+ * Detect whether the input or the output stream is buffered,
+ * bufferize one of them if neither is buffered.
+ */
+nsresult nsAsyncStreamCopier::ApplyBufferingPolicy() {
+ // This function causes I/O, it must not be executed on the main
+ // thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (NS_OutputStreamIsBuffered(mSink)) {
+ // Sink is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ return NS_OK;
+ }
+ if (NS_InputStreamIsBuffered(mSource)) {
+ // Source is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_READSEGMENTS;
+ return NS_OK;
+ }
+
+ // No buffering, let's buffer the sink
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> sink =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = sink->Init(mSink, mChunkSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ mSink = sink;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver* observer, nsISupports* ctx) {
+ LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%p]\n", this,
+ observer));
+
+ NS_ASSERTION(mSource && mSink, "not initialized");
+ nsresult rv;
+
+ if (observer) {
+ // build proxy for observer events
+ rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), observer, ctx);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // from this point forward, AsyncCopy is going to return NS_OK. any errors
+ // will be reported via OnStopRequest.
+ {
+ MutexAutoLock lock(mLock);
+ mIsPending = true;
+ }
+
+ if (mObserver) {
+ rv = mObserver->OnStartRequest(AsRequest());
+ if (NS_FAILED(rv)) Cancel(rv);
+ }
+
+ if (!mShouldSniffBuffering) {
+ // No buffer sniffing required, let's proceed
+ AsyncCopyInternal();
+ return NS_OK;
+ }
+
+ if (NS_IsMainThread()) {
+ // Don't perform buffer sniffing on the main thread
+ nsCOMPtr<nsIRunnable> event = new AsyncApplyBufferingPolicyEvent(this);
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ return NS_OK;
+ }
+
+ // We're not going to block the main thread, so let's sniff here
+ rv = ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ AsyncCopyInternal();
+ return NS_OK;
+}
+
+// Launch async copy.
+// All errors are reported through the observer.
+void nsAsyncStreamCopier::AsyncCopyInternal() {
+ MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS ||
+ mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+
+ nsresult rv;
+ // We want to receive progress notifications; release happens in
+ // OnAsyncCopyComplete.
+ RefPtr<nsAsyncStreamCopier> self = this;
+ {
+ MutexAutoLock lock(mLock);
+ rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize,
+ OnAsyncCopyComplete, this, mCloseSource, mCloseSink,
+ getter_AddRefs(mCopierCtx));
+ }
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return; // release self
+ }
+
+ Unused << self.forget(); // Will be released in OnAsyncCopyComplete
+}
diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h
new file mode 100644
index 0000000000..120218c3c7
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.h
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncStreamCopier_h__
+#define nsAsyncStreamCopier_h__
+
+#include "nsIAsyncStreamCopier.h"
+#include "nsIAsyncStreamCopier2.h"
+#include "mozilla/Mutex.h"
+#include "nsStreamUtils.h"
+#include "nsCOMPtr.h"
+
+class nsIRequestObserver;
+
+//-----------------------------------------------------------------------------
+
+class nsAsyncStreamCopier final : public nsIAsyncStreamCopier,
+ nsIAsyncStreamCopier2 {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIASYNCSTREAMCOPIER
+
+ // nsIAsyncStreamCopier2
+ // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2
+ // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier
+ NS_IMETHOD Init(nsIInputStream* aSource, nsIOutputStream* aSink,
+ nsIEventTarget* aTarget, uint32_t aChunkSize,
+ bool aCloseSource, bool aCloseSink) override;
+
+ nsAsyncStreamCopier();
+
+ //-------------------------------------------------------------------------
+ // these methods may be called on any thread
+
+ bool IsComplete(nsresult* status = nullptr);
+ void Complete(nsresult status);
+
+ private:
+ virtual ~nsAsyncStreamCopier();
+
+ nsresult InitInternal(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, uint32_t chunkSize,
+ bool closeSource, bool closeSink);
+
+ static void OnAsyncCopyComplete(void*, nsresult);
+
+ void AsyncCopyInternal();
+ nsresult ApplyBufferingPolicy();
+ nsIRequest* AsRequest();
+
+ nsCOMPtr<nsIInputStream> mSource;
+ nsCOMPtr<nsIOutputStream> mSink;
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+
+ nsCOMPtr<nsISupports> mCopierCtx MOZ_GUARDED_BY(mLock);
+
+ mozilla::Mutex mLock{"nsAsyncStreamCopier.mLock"};
+
+ nsAsyncCopyMode mMode{NS_ASYNCCOPY_VIA_READSEGMENTS};
+ uint32_t mChunkSize; // only modified in Init
+ nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK};
+ bool mIsPending MOZ_GUARDED_BY(mLock){false};
+ bool mCloseSource MOZ_GUARDED_BY(mLock){false};
+ bool mCloseSink MOZ_GUARDED_BY(mLock){false};
+ bool mShouldSniffBuffering{false}; // only modified in Init
+
+ friend class ProceedWithAsyncCopy;
+ friend class AsyncApplyBufferingPolicyEvent;
+};
+
+#endif // !nsAsyncStreamCopier_h__
diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp
new file mode 100644
index 0000000000..11cf7626ce
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.cpp
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAuthInformationHolder.h"
+
+NS_IMPL_ISUPPORTS(nsAuthInformationHolder, nsIAuthInformation)
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetFlags(uint32_t* aFlags) {
+ *aFlags = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetRealm(nsAString& aRealm) {
+ aRealm = mRealm;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetAuthenticationScheme(nsACString& aScheme) {
+ aScheme = mAuthType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetUsername(nsAString& aUserName) {
+ aUserName = mUser;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetUsername(const nsAString& aUserName) {
+ if (!(mFlags & ONLY_PASSWORD)) mUser = aUserName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetPassword(nsAString& aPassword) {
+ aPassword = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetPassword(const nsAString& aPassword) {
+ mPassword = aPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetDomain(nsAString& aDomain) {
+ aDomain = mDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetDomain(const nsAString& aDomain) {
+ if (mFlags & NEED_DOMAIN) mDomain = aDomain;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsAuthInformationHolder.h b/netwerk/base/nsAuthInformationHolder.h
new file mode 100644
index 0000000000..48054d9413
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.h
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSAUTHINFORMATIONHOLDER_H_
+#define NSAUTHINFORMATIONHOLDER_H_
+
+#include "nsIAuthInformation.h"
+#include "nsString.h"
+
+class nsAuthInformationHolder : public nsIAuthInformation {
+ protected:
+ virtual ~nsAuthInformationHolder() = default;
+
+ public:
+ // aAuthType must be ASCII
+ nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm,
+ const nsACString& aAuthType)
+ : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTHINFORMATION
+
+ const nsString& User() const { return mUser; }
+ const nsString& Password() const { return mPassword; }
+ const nsString& Domain() const { return mDomain; }
+
+ /**
+ * This method can be used to initialize the username when the
+ * ONLY_PASSWORD flag is set.
+ */
+ void SetUserInternal(const nsString& aUsername) { mUser = aUsername; }
+
+ private:
+ nsString mUser;
+ nsString mPassword;
+ nsString mDomain;
+
+ uint32_t mFlags;
+ nsString mRealm;
+ nsCString mAuthType;
+};
+
+#endif
diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp
new file mode 100644
index 0000000000..621dd2c4ec
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.cpp
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBase64Encoder.h"
+
+#include "mozilla/Base64.h"
+
+NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream)
+
+NS_IMETHODIMP
+nsBase64Encoder::Close() { return NS_OK; }
+
+NS_IMETHODIMP
+nsBase64Encoder::Flush() { return NS_OK; }
+
+NS_IMETHODIMP
+nsBase64Encoder::StreamStatus() { return NS_OK; }
+
+NS_IMETHODIMP
+nsBase64Encoder::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ mData.Append(aBuf, aCount);
+ *_retval = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteFrom(nsIInputStream* aStream, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult nsBase64Encoder::Finish(nsACString& result) {
+ nsresult rv = mozilla::Base64Encode(mData, result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Free unneeded memory and allow reusing the object
+ mData.Truncate();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBase64Encoder.h b/netwerk/base/nsBase64Encoder.h
new file mode 100644
index 0000000000..b74d3c9b5a
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSBASE64ENCODER_H_
+#define NSBASE64ENCODER_H_
+
+#include "nsIOutputStream.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * A base64 encoder. Usage: Instantiate class, write to it using
+ * Write(), then call Finish() to get the base64-encoded data.
+ */
+class nsBase64Encoder final : public nsIOutputStream {
+ public:
+ nsBase64Encoder() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult Finish(nsACString& _result);
+
+ private:
+ ~nsBase64Encoder() = default;
+
+ /// The data written to this stream. nsCString can deal fine with
+ /// binary data.
+ nsCString mData;
+};
+
+#endif
diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp
new file mode 100644
index 0000000000..df8aa23db4
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -0,0 +1,1024 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseChannel.h"
+#include "nsContentUtils.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsMimeTypes.h"
+#include "nsUnknownDecoder.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsMimeTypes.h"
+#include "nsICancelable.h"
+#include "nsIChannelEventSink.h"
+#include "nsIStreamConverterService.h"
+#include "nsChannelClassifier.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsProxyRelease.h"
+#include "nsXULAppAPI.h"
+#include "nsContentSecurityManager.h"
+#include "LoadInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsRedirectHistoryEntry.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+
+// This class is used to suspend a request across a function scope.
+class ScopedRequestSuspender {
+ public:
+ explicit ScopedRequestSuspender(nsIRequest* request) : mRequest(request) {
+ if (mRequest && NS_FAILED(mRequest->Suspend())) {
+ NS_WARNING("Couldn't suspend pump");
+ mRequest = nullptr;
+ }
+ }
+ ~ScopedRequestSuspender() {
+ if (mRequest) mRequest->Resume();
+ }
+
+ private:
+ nsIRequest* mRequest;
+};
+
+// Used to suspend data events from mRequest within a function scope. This is
+// usually needed when a function makes callbacks that could process events.
+#define SUSPEND_PUMP_FOR_SCOPE() \
+ ScopedRequestSuspender pump_suspender__(mRequest)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel
+
+nsBaseChannel::nsBaseChannel() : NeckoTargetHolder(nullptr) {
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsBaseChannel::~nsBaseChannel() {
+ NS_ReleaseOnMainThread("nsBaseChannel::mLoadInfo", mLoadInfo.forget());
+}
+
+nsresult nsBaseChannel::Redirect(nsIChannel* newChannel, uint32_t redirectFlags,
+ bool openNewChannel) {
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Transfer properties
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE);
+
+ // make a copy of the loadinfo, append to the redirectchain
+ // and set it on the new channel
+ nsSecurityFlags secFlags =
+ mLoadInfo->GetSecurityFlags() & ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<net::LoadInfo*>(mLoadInfo.get())
+ ->CloneWithNewSecFlags(secFlags);
+
+ bool isInternalRedirect =
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect);
+
+ // Ensure the channel's loadInfo's result principal URI so that it's
+ // either non-null or updated to the redirect target URI.
+ // We must do this because in case the loadInfo's result principal URI
+ // is null, it would be taken from OriginalURI of the channel. But we
+ // overwrite it with the whole redirect chain first URI before opening
+ // the target channel, hence the information would be lost.
+ // If the protocol handler that created the channel wants to use
+ // the originalURI of the channel as the principal URI, it has left
+ // the result principal URI on the load info null.
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+
+ nsCOMPtr<nsILoadInfo> existingLoadInfo = newChannel->LoadInfo();
+ if (existingLoadInfo) {
+ existingLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ }
+ if (!resultPrincipalURI) {
+ newChannel->GetOriginalURI(getter_AddRefs(resultPrincipalURI));
+ }
+
+ newLoadInfo->SetResultPrincipalURI(resultPrincipalURI);
+
+ newChannel->SetLoadInfo(newLoadInfo);
+
+ // Preserve the privacy bit if it has been overridden
+ if (mPrivateBrowsingOverriden) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(mPrivateBrowsing);
+ }
+ }
+
+ if (nsCOMPtr<nsIWritablePropertyBag> bag = ::do_QueryInterface(newChannel)) {
+ nsHashPropertyBag::CopyFrom(bag, static_cast<nsIPropertyBag2*>(this));
+ }
+
+ // Notify consumer, giving chance to cancel redirect.
+
+ auto redirectCallbackHelper = MakeRefPtr<net::nsAsyncRedirectVerifyHelper>();
+
+ bool checkRedirectSynchronously = !openNewChannel;
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+
+ mRedirectChannel = newChannel;
+ mRedirectFlags = redirectFlags;
+ mOpenRedirectChannel = openNewChannel;
+ nsresult rv = redirectCallbackHelper->Init(
+ this, newChannel, redirectFlags, target, checkRedirectSynchronously);
+ if (NS_FAILED(rv)) return rv;
+
+ if (checkRedirectSynchronously && NS_FAILED(mStatus)) return mStatus;
+
+ return NS_OK;
+}
+
+nsresult nsBaseChannel::ContinueRedirect() {
+ // Make sure to do this _after_ making all the OnChannelRedirect calls
+ mRedirectChannel->SetOriginalURI(OriginalURI());
+
+ // If we fail to open the new channel, then we want to leave this channel
+ // unaffected, so we defer tearing down our channel until we have succeeded
+ // with the redirect.
+
+ if (mOpenRedirectChannel) {
+ nsresult rv = NS_OK;
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mRedirectChannel = nullptr;
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+ ChannelDone();
+
+ return NS_OK;
+}
+
+bool nsBaseChannel::HasContentTypeHint() const {
+ NS_ASSERTION(!Pending(), "HasContentTypeHint called too late");
+ return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsresult nsBaseChannel::BeginPumpingData() {
+ nsresult rv;
+
+ rv = BeginAsyncRead(this, getter_AddRefs(mRequest),
+ getter_AddRefs(mCancelableAsyncRequest));
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(mRequest || mCancelableAsyncRequest,
+ "should have got a request or cancelable");
+ mPumpingData = true;
+ return NS_OK;
+ }
+ if (rv != NS_ERROR_NOT_IMPLEMENTED) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsCOMPtr<nsIChannel> channel;
+ rv = OpenContentStream(true, getter_AddRefs(stream), getter_AddRefs(channel));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?");
+
+ if (channel) {
+ nsCOMPtr<nsIRunnable> runnable = new RedirectRunnable(this, channel);
+ rv = Dispatch(runnable.forget());
+ if (NS_SUCCEEDED(rv)) mWaitingOnAsyncRedirect = true;
+ return rv;
+ }
+
+ // By assigning mPump, we flag this channel as pending (see Pending). It's
+ // important that the pending flag is set when we call into the stream (the
+ // call to AsyncRead results in the stream's AsyncWait method being called)
+ // and especially when we call into the loadgroup. Our caller takes care to
+ // release mPump if we return an error.
+
+ nsCOMPtr<nsISerialEventTarget> target = GetNeckoTarget();
+ rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, 0, 0, true,
+ target);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPumpingData = true;
+ mRequest = mPump;
+ rv = mPump->AsyncRead(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<BlockingPromise> promise;
+ rv = ListenerBlockingPromise(getter_AddRefs(promise));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (promise) {
+ mPump->Suspend();
+
+ RefPtr<nsBaseChannel> self(this);
+
+ promise->Then(
+ target, __func__,
+ [self, this](nsresult rv) {
+ MOZ_ASSERT(mPump);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mPump->Resume();
+ },
+ [self, this](nsresult rv) {
+ MOZ_ASSERT(mPump);
+ MOZ_ASSERT(NS_FAILED(rv));
+ Cancel(rv);
+ mPump->Resume();
+ });
+ }
+
+ return NS_OK;
+}
+
+void nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel) {
+ NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here");
+
+ nsresult rv = mStatus;
+ if (NS_SUCCEEDED(mStatus)) {
+ rv = Redirect(newChannel, nsIChannelEventSink::REDIRECT_TEMPORARY, true);
+ if (NS_SUCCEEDED(rv)) {
+ // OnRedirectVerifyCallback will be called asynchronously
+ return;
+ }
+ }
+
+ ContinueHandleAsyncRedirect(rv);
+}
+
+void nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result) {
+ mWaitingOnAsyncRedirect = false;
+
+ if (NS_FAILED(result)) Cancel(result);
+
+ if (NS_FAILED(result) && mListener) {
+ // Notify our consumer ourselves
+ mListener->OnStartRequest(this);
+ mListener->OnStopRequest(this, mStatus);
+ ChannelDone();
+ }
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+}
+
+void nsBaseChannel::ClassifyURI() {
+ // For channels created in the child process, delegate to the parent to
+ // classify URIs.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (NS_ShouldClassifyChannel(this)) {
+ auto classifier = MakeRefPtr<net::nsChannelClassifier>(this);
+ classifier->Start();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsISupports
+
+NS_IMPL_ADDREF(nsBaseChannel)
+NS_IMPL_RELEASE(nsBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsBaseChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
+NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequest
+
+NS_IMETHODIMP
+nsBaseChannel::GetName(nsACString& result) {
+ if (!mURI) {
+ result.Truncate();
+ return NS_OK;
+ }
+ return mURI->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::IsPending(bool* result) {
+ *result = Pending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetStatus(nsresult* status) {
+ if (mRequest && NS_SUCCEEDED(mStatus)) {
+ mRequest->GetStatus(status);
+ } else {
+ *status = mStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsBaseChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsBaseChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Cancel(nsresult status) {
+ // Ignore redundant cancelation
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = status;
+
+ if (mCancelableAsyncRequest) {
+ mCancelableAsyncRequest->Cancel(status);
+ }
+
+ if (mRequest) {
+ mRequest->Cancel(status);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Suspend() {
+ NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
+ return mRequest->Suspend();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Resume() {
+ NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
+ return mRequest->Resume();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ nsCOMPtr<nsILoadGroup> loadGroup(mLoadGroup);
+ loadGroup.forget(aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIChannel
+
+NS_IMETHODIMP
+nsBaseChannel::GetOriginalURI(nsIURI** aURI) {
+ RefPtr<nsIURI> uri = OriginalURI();
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOriginalURI(nsIURI* aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri(mURI);
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetOwner(nsISupports** aOwner) {
+ nsCOMPtr<nsISupports> owner(mOwner);
+ owner.forget(aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOwner(nsISupports* aOwner) {
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+
+ // Need to update |mNeckoTarget| when load info has changed.
+ SetupNeckoTarget();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ nsCOMPtr<nsILoadInfo> loadInfo(mLoadInfo);
+ loadInfo.forget(aLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks(mCallbacks);
+ callbacks.forget(aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ *aSecurityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentType(nsACString& aContentType) {
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentType(const nsACString& aContentType) {
+ // mContentCharset is unchanged if not parsed
+ bool dummy;
+ net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentCharset(nsACString& aContentCharset) {
+ aContentCharset = mContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentCharset(const nsACString& aContentCharset) {
+ mContentCharset = aContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ // preserve old behavior, fail unless explicitly set.
+ if (mContentDispositionHint == UINT32_MAX) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ if (!mContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ mContentDispositionFilename =
+ MakeUnique<nsString>(aContentDispositionFilename);
+
+ // For safety reasons ensure the filename doesn't contain null characters and
+ // replace them with underscores. We may later pass the extension to system
+ // MIME APIs that expect null terminated strings.
+ mContentDispositionFilename->ReplaceChar(char16_t(0), '_');
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentLength(int64_t* aContentLength) {
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentLength(int64_t aContentLength) {
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Open(nsIInputStream** aStream) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = OpenContentStream(false, aStream, getter_AddRefs(chan));
+ NS_ASSERTION(!chan || !*aStream, "Got both a channel and a stream?");
+ if (NS_SUCCEEDED(rv) && chan) {
+ rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false);
+ if (NS_FAILED(rv)) return rv;
+ rv = chan->Open(aStream);
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ return NS_ImplementChannelOpen(this, aStream);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mWasOpened = true;
+ ClassifyURI();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+ NS_ENSURE_ARG(listener);
+
+ SetupNeckoTarget();
+
+ // Skip checking for chrome:// sub-resources.
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("file")) {
+ NS_CompareLoadInfoAndLoadContext(this);
+ }
+
+ // Ensure that this is an allowed port before proceeding.
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(this);
+
+ // Store the listener and context early so that OpenContentStream and the
+ // stream's AsyncWait method (called by AsyncRead) can have access to them
+ // via the StreamListener methods. However, since
+ // this typically introduces a reference cycle between this and the listener,
+ // we need to be sure to break the reference if this method does not succeed.
+ mListener = listener;
+
+ // This method assigns mPump as a side-effect. We need to clear mPump if
+ // this method fails.
+ rv = BeginPumpingData();
+ if (NS_FAILED(rv)) {
+ mPump = nullptr;
+ mRequest = nullptr;
+ mPumpingData = false;
+ ChannelDone();
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // At this point, we are going to return success no matter what.
+
+ mWasOpened = true;
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ ClassifyURI();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsITransportEventSink
+
+NS_IMETHODIMP
+nsBaseChannel::OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ // In some cases, we may wish to suppress transport-layer status events.
+
+ if (!mPumpingData || NS_FAILED(mStatus)) {
+ return NS_OK;
+ }
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Lazily fetch mProgressSink
+ if (!mProgressSink) {
+ if (mQueriedProgressSink) {
+ return NS_OK;
+ }
+ GetCallback(mProgressSink);
+ mQueriedProgressSink = true;
+ if (!mProgressSink) {
+ return NS_OK;
+ }
+ }
+
+ if (!HasLoadFlag(LOAD_BACKGROUND)) {
+ nsAutoString statusArg;
+ if (GetStatusArg(status, statusArg)) {
+ mProgressSink->OnStatus(this, status, statusArg.get());
+ }
+ }
+
+ if (progress) {
+ mProgressSink->OnProgress(this, progress, progressMax);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsBaseChannel::GetInterface(const nsIID& iid, void** result) {
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result);
+ return *result ? NS_OK : NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequestObserver
+
+static void CallTypeSniffers(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsIChannel* chan = static_cast<nsIChannel*>(aClosure);
+
+ nsAutoCString newType;
+ NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+static void CallUnknownTypeSniffer(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsIChannel* chan = static_cast<nsIChannel*>(aClosure);
+
+ RefPtr<nsUnknownDecoder> sniffer = new nsUnknownDecoder();
+
+ nsAutoCString detected;
+ nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected);
+ if (NS_SUCCEEDED(rv)) chan->SetContentType(detected);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStartRequest(nsIRequest* request) {
+ MOZ_ASSERT_IF(mRequest, request == mRequest);
+ MOZ_ASSERT_IF(mCancelableAsyncRequest, !mRequest);
+
+ if (mPump) {
+ // If our content type is unknown, use the content type
+ // sniffer. If the sniffer is not available for some reason, then we just
+ // keep going as-is.
+ if (NS_SUCCEEDED(mStatus) &&
+ mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this));
+ }
+
+ // Now, the general type sniffers. Skip this if we have none.
+ if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
+ }
+ }
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mListener) { // null in case of redirect
+ return mListener->OnStartRequest(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStopRequest(nsIRequest* request, nsresult status) {
+ // If both mStatus and status are failure codes, we keep mStatus as-is since
+ // that is consistent with our GetStatus and Cancel methods.
+ if (NS_SUCCEEDED(mStatus)) mStatus = status;
+
+ // Cause Pending to return false.
+ mPump = nullptr;
+ mRequest = nullptr;
+ mCancelableAsyncRequest = nullptr;
+ mPumpingData = false;
+
+ if (mListener) { // null in case of redirect
+ mListener->OnStopRequest(this, mStatus);
+ }
+ ChannelDone();
+
+ // No need to suspend pump in this scope since we will not be receiving
+ // any more events from it.
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIStreamListener
+
+NS_IMETHODIMP
+nsBaseChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* stream,
+ uint64_t offset, uint32_t count) {
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ nsresult rv = mListener->OnDataAvailable(this, stream, offset, count);
+ if (mSynthProgressEvents && NS_SUCCEEDED(rv)) {
+ int64_t prog = offset + count;
+ if (NS_IsMainThread()) {
+ OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength);
+ } else {
+ class OnTransportStatusAsyncEvent : public Runnable {
+ RefPtr<nsBaseChannel> mChannel;
+ int64_t mProgress;
+ int64_t mContentLength;
+
+ public:
+ OnTransportStatusAsyncEvent(nsBaseChannel* aChannel, int64_t aProgress,
+ int64_t aContentLength)
+ : Runnable("OnTransportStatusAsyncEvent"),
+ mChannel(aChannel),
+ mProgress(aProgress),
+ mContentLength(aContentLength) {}
+
+ NS_IMETHOD Run() override {
+ return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING,
+ mProgress, mContentLength);
+ }
+ };
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new OnTransportStatusAsyncEvent(this, prog, mContentLength);
+ Dispatch(runnable.forget());
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnRedirectVerifyCallback(nsresult result) {
+ if (NS_SUCCEEDED(result)) result = ContinueRedirect();
+
+ if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) {
+ if (NS_SUCCEEDED(mStatus)) mStatus = result;
+ return NS_OK;
+ }
+
+ if (mWaitingOnAsyncRedirect) ContinueHandleAsyncRedirect(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::RetargetDeliveryTo(nsISerialEventTarget* aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mRequest) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableRequest> req;
+ if (mAllowThreadRetargeting) {
+ req = do_QueryInterface(mRequest);
+ }
+
+ NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED);
+
+ return req->RetargetDeliveryTo(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIThreadRetargetableRequest> req;
+ req = do_QueryInterface(mRequest);
+
+ NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED);
+ return req->GetDeliveryTarget(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnDataFinished(nsresult aStatus) {
+ if (!mListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseChannel::GetCanceled(bool* aCanceled) {
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+void nsBaseChannel::SetupNeckoTarget() {
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+nsBaseChannel::ContentRange::ContentRange(const nsACString& aRangeHeader,
+ uint64_t aSize)
+ : mStart(0), mEnd(0), mSize(0) {
+ auto parsed = nsContentUtils::ParseSingleRangeRequest(aRangeHeader, true);
+ // https://fetch.spec.whatwg.org/#ref-for-simple-range-header-value%E2%91%A1
+ // If rangeValue is failure, then return a network error.
+ if (!parsed) {
+ return;
+ }
+
+ // Sanity check: ParseSingleRangeRequest should handle these two cases.
+ // If rangeEndValue and rangeStartValue are null, then return failure.
+ MOZ_ASSERT(parsed->Start().isSome() || parsed->End().isSome());
+ // If rangeStartValue and rangeEndValue are numbers, and rangeStartValue
+ // is greater than rangeEndValue, then return failure.
+ MOZ_ASSERT(parsed->Start().isNothing() || parsed->End().isNothing() ||
+ *parsed->Start() <= *parsed->End());
+
+ // https://fetch.spec.whatwg.org/#ref-for-simple-range-header-value%E2%91%A1
+ // If rangeStart is null:
+ if (parsed->Start().isNothing()) {
+ // Set rangeStart to fullLength − rangeEnd.
+ mStart = aSize - *parsed->End();
+
+ // Set rangeEnd to rangeStart + rangeEnd − 1.
+ mEnd = mStart + *parsed->End() - 1;
+
+ // Otherwise:
+ } else {
+ // If rangeStart is greater than or equal to fullLength, then return a
+ // network error.
+ if (*parsed->Start() >= aSize) {
+ return;
+ }
+ mStart = *parsed->Start();
+
+ // If rangeEnd is null or rangeEnd is greater than or equal to fullLength,
+ // then set rangeEnd to fullLength − 1.
+ if (parsed->End().isNothing() || *parsed->End() >= aSize) {
+ mEnd = aSize - 1;
+ } else {
+ mEnd = *parsed->End();
+ }
+ }
+ mSize = aSize;
+}
+
+void nsBaseChannel::ContentRange::AsHeader(nsACString& aOutString) const {
+ aOutString.Assign("bytes "_ns);
+ aOutString.AppendInt(mStart);
+ aOutString.AppendLiteral("-");
+ aOutString.AppendInt(mEnd);
+ aOutString.AppendLiteral("/");
+ aOutString.AppendInt(mSize);
+}
diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h
new file mode 100644
index 0000000000..179a24bf45
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.h
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseChannel_h__
+#define nsBaseChannel_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PrivateBrowsingChannel.h"
+#include "nsHashPropertyBag.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsITransport.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURI.h"
+#include "nsInputStreamPump.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+
+class nsIInputStream;
+class nsICancelable;
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel is designed to be subclassed. The subclass is responsible for
+// implementing the OpenContentStream method, which will be called by the
+// nsIChannel::AsyncOpen and nsIChannel::Open implementations.
+//
+// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way
+// for subclasses to query both the nsIChannel::notificationCallbacks and
+// nsILoadGroup::notificationCallbacks for supported interfaces.
+//
+// nsBaseChannel implements nsITransportEventSink to support progress & status
+// notifications generated by the transport layer.
+
+class nsBaseChannel
+ : public nsHashPropertyBag,
+ public nsIChannel,
+ public nsIThreadRetargetableRequest,
+ public nsIInterfaceRequestor,
+ public nsITransportEventSink,
+ public nsIAsyncVerifyRedirectCallback,
+ public mozilla::net::PrivateBrowsingChannel<nsBaseChannel>,
+ public mozilla::net::NeckoTargetHolder,
+ protected nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsBaseChannel();
+
+ protected:
+ // -----------------------------------------------
+ // Methods to be implemented by the derived class:
+
+ virtual ~nsBaseChannel();
+
+ using BlockingPromise = mozilla::MozPromise<nsresult, nsresult, true>;
+
+ private:
+ // Implemented by subclass to supply data stream. The parameter, async, is
+ // true when called from nsIChannel::AsyncOpen and false otherwise. When
+ // async is true, the resulting stream will be used with a nsIInputStreamPump
+ // instance. This means that if it is a non-blocking stream that supports
+ // nsIAsyncInputStream that it will be read entirely on the main application
+ // thread, and its AsyncWait method will be called whenever ReadSegments
+ // returns NS_BASE_STREAM_WOULD_BLOCK. Otherwise, if the stream is blocking,
+ // then it will be read on one of the background I/O threads, and it does not
+ // need to implement ReadSegments. If async is false, this method may return
+ // NS_ERROR_NOT_IMPLEMENTED to cause the basechannel to implement Open in
+ // terms of AsyncOpen (see NS_ImplementChannelOpen).
+ // A callee is allowed to return an nsIChannel instead of an nsIInputStream.
+ // That case will be treated as a redirect to the new channel. By default
+ // *channel will be set to null by the caller, so callees who don't want to
+ // return one an just not touch it.
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** stream,
+ nsIChannel** channel) = 0;
+
+ // Implemented by subclass to begin pumping data for an async channel, in
+ // lieu of returning a stream. If implemented, OpenContentStream will never
+ // be called for async channels. If not implemented, AsyncOpen will fall
+ // back to OpenContentStream.
+ //
+ // On success, the callee must begin pumping data to the stream listener,
+ // and at some point call OnStartRequest followed by OnStopRequest.
+ //
+ // Additionally, when a successful nsresult is returned, then the subclass
+ // should be setting through its two out params either:
+ // - a request object, which may be used to suspend, resume, and cancel
+ // the underlying request.
+ // - or a cancelable object (e.g. when a request can't be returned right away
+ // due to some async work needed to retrieve it). which may be used to
+ // cancel the underlying request (e.g. because the channel has been
+ // canceled)
+ //
+ // Not returning a request or cancelable leads to potentially leaking the
+ // an underling stream pump (which would keep to be pumping data even after
+ // the channel has been canceled and nothing is going to handle the data
+ // available, e.g. see Bug 1706594).
+ virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // This method may return a promise that will keep the input stream pump
+ // suspended until the promise is resolved or rejected. On resolution the
+ // pump is resumed. On rejection the channel is canceled with the resulting
+ // error and then the pump is also resumed to propagate the error to the
+ // channel listener. Use it to do any asynchronous/background tasks you need
+ // to finish prior calling OnStartRequest of the listener. This method is
+ // called right after OpenContentStream() with async == true, after the input
+ // stream pump has already been called asyncRead().
+ virtual nsresult ListenerBlockingPromise(BlockingPromise** aPromise) {
+ NS_ENSURE_ARG(aPromise);
+ *aPromise = nullptr;
+ return NS_OK;
+ }
+
+ // The basechannel calls this method from its OnTransportStatus method to
+ // determine whether to call nsIProgressEventSink::OnStatus in addition to
+ // nsIProgressEventSink::OnProgress. This method may be overriden by the
+ // subclass to enable nsIProgressEventSink::OnStatus events. If this method
+ // returns true, then the statusArg out param specifies the "statusArg" value
+ // to pass to the OnStatus method. By default, OnStatus messages are
+ // suppressed. The status parameter passed to this method is the status value
+ // from the OnTransportStatus method.
+ virtual bool GetStatusArg(nsresult status, nsString& statusArg) {
+ return false;
+ }
+
+ // Called when the callbacks available to this channel may have changed.
+ virtual void OnCallbacksChanged() {}
+
+ // Called when our channel is done, to allow subclasses to drop resources.
+ virtual void OnChannelDone() {}
+
+ public:
+ // ----------------------------------------------
+ // Methods provided for use by the derived class:
+
+ // Redirect to another channel. This method takes care of notifying
+ // observers of this redirect as well as of opening the new channel, if asked
+ // to do so. It also cancels |this| with the status code
+ // NS_BINDING_REDIRECTED. A failure return from this method means that the
+ // redirect could not be performed (no channel was opened; this channel
+ // wasn't canceled.) The redirectFlags parameter consists of the flag values
+ // defined on nsIChannelEventSink.
+ nsresult Redirect(nsIChannel* newChannel, uint32_t redirectFlags,
+ bool openNewChannel);
+
+ // Tests whether a type hint was set. Subclasses can use this to decide
+ // whether to call SetContentType.
+ // NOTE: This is only reliable if the subclass didn't itself call
+ // SetContentType, and should also not be called after OpenContentStream.
+ bool HasContentTypeHint() const;
+
+ // The URI member should be initialized before the channel is used, and then
+ // it should never be changed again until the channel is destroyed.
+ nsIURI* URI() { return mURI; }
+ void SetURI(nsIURI* uri) {
+ NS_ASSERTION(uri, "must specify a non-null URI");
+ NS_ASSERTION(!mURI, "must not modify URI");
+ NS_ASSERTION(!mOriginalURI, "how did that get set so early?");
+ mURI = uri;
+ mOriginalURI = uri;
+ }
+ nsIURI* OriginalURI() { return mOriginalURI; }
+
+ // The security info is a property of the transport-layer, which should be
+ // assigned by the subclass.
+ nsITransportSecurityInfo* SecurityInfo() { return mSecurityInfo; }
+ void SetSecurityInfo(nsITransportSecurityInfo* info) { mSecurityInfo = info; }
+
+ // Test the load flags
+ bool HasLoadFlag(uint32_t flag) { return (mLoadFlags & flag) != 0; }
+
+ // This is a short-cut to calling nsIRequest::IsPending()
+ virtual bool Pending() const {
+ return mPumpingData || mWaitingOnAsyncRedirect;
+ }
+
+ // Blob requests may specify a range header. We must parse, validate, and
+ // store that info in a place where BlobURLInputStream::StoreBlobImplStream
+ // can access it. This class helps to encapsulate that logic.
+ class ContentRange {
+ private:
+ uint64_t mStart;
+ uint64_t mEnd;
+ uint64_t mSize;
+
+ public:
+ uint64_t Start() const { return mStart; }
+ uint64_t End() const { return mEnd; }
+ uint64_t Size() const { return mSize; }
+ bool IsValid() const { return mStart < mSize; }
+ ContentRange() : mStart(0), mEnd(0), mSize(0) {}
+ ContentRange(uint64_t aStart, uint64_t aEnd, uint64_t aSize)
+ : mStart(aStart), mEnd(aEnd), mSize(aSize) {}
+ ContentRange(const nsACString& aRangeHeader, uint64_t aSize);
+ void AsHeader(nsACString& aOutString) const;
+ };
+
+ const mozilla::Maybe<ContentRange>& GetContentRange() const {
+ return mContentRange;
+ }
+
+ void SetContentRange(uint64_t aStart, uint64_t aEnd, uint64_t aSize) {
+ mContentRange.emplace(ContentRange(aStart, aEnd, aSize));
+ }
+
+ bool SetContentRange(const nsACString& aRangeHeader, uint64_t aSize) {
+ auto range = ContentRange(aRangeHeader, aSize);
+ if (!range.IsValid()) {
+ return false;
+ }
+ mContentRange.emplace(range);
+ return true;
+ }
+
+ // Helper function for querying the channel's notification callbacks.
+ template <class T>
+ void GetCallback(nsCOMPtr<T>& result) {
+ GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result));
+ }
+
+ // If a subclass does not want to feed transport-layer progress events to the
+ // base channel via nsITransportEventSink, then it may set this flag to cause
+ // the base channel to synthesize progress events when it receives data from
+ // the content stream. By default, progress events are not synthesized.
+ void EnableSynthesizedProgressEvents(bool enable) {
+ mSynthProgressEvents = enable;
+ }
+
+ // Some subclasses may wish to manually insert a stream listener between this
+ // and the channel's listener. The following methods make that possible.
+ void SetStreamListener(nsIStreamListener* listener) { mListener = listener; }
+ nsIStreamListener* StreamListener() { return mListener; }
+
+ protected:
+ void DisallowThreadRetargeting() { mAllowThreadRetargeting = false; }
+
+ virtual void SetupNeckoTarget();
+
+ private:
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // Called to setup mPump and call AsyncRead on it.
+ nsresult BeginPumpingData();
+
+ // Called when the callbacks available to this channel may have changed.
+ void CallbacksChanged() {
+ mProgressSink = nullptr;
+ mQueriedProgressSink = false;
+ OnCallbacksChanged();
+ }
+
+ // Called when our channel is done. This should drop no-longer-needed
+ // pointers.
+ void ChannelDone() {
+ mListener = nullptr;
+ OnChannelDone();
+ }
+
+ // Handle an async redirect callback. This will only be called if we
+ // returned success from AsyncOpen while posting a redirect runnable.
+ void HandleAsyncRedirect(nsIChannel* newChannel);
+ void ContinueHandleAsyncRedirect(nsresult result);
+ nsresult ContinueRedirect();
+
+ // start URI classifier if requested
+ void ClassifyURI();
+
+ class RedirectRunnable : public mozilla::Runnable {
+ public:
+ RedirectRunnable(nsBaseChannel* chan, nsIChannel* newChannel)
+ : mozilla::Runnable("nsBaseChannel::RedirectRunnable"),
+ mChannel(chan),
+ mNewChannel(newChannel) {
+ MOZ_ASSERT(newChannel, "Must have channel to redirect to");
+ }
+
+ NS_IMETHOD Run() override {
+ mChannel->HandleAsyncRedirect(mNewChannel);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsBaseChannel> mChannel;
+ nsCOMPtr<nsIChannel> mNewChannel;
+ };
+ friend class RedirectRunnable;
+
+ RefPtr<nsInputStreamPump> mPump;
+ RefPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsICancelable> mCancelableAsyncRequest;
+ bool mPumpingData{false};
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ uint32_t mLoadFlags{LOAD_NORMAL};
+ bool mQueriedProgressSink{true};
+ bool mSynthProgressEvents{false};
+ bool mAllowThreadRetargeting{true};
+ bool mWaitingOnAsyncRedirect{false};
+ bool mOpenRedirectChannel{false};
+ uint32_t mRedirectFlags{0};
+ mozilla::Maybe<ContentRange> mContentRange;
+
+ protected:
+ nsCString mContentType;
+ nsCString mContentCharset;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsresult mStatus{NS_OK};
+ uint32_t mContentDispositionHint{UINT32_MAX};
+ mozilla::UniquePtr<nsString> mContentDispositionFilename;
+ int64_t mContentLength{-1};
+ bool mWasOpened{false};
+ bool mCanceled{false};
+
+ friend class mozilla::net::PrivateBrowsingChannel<nsBaseChannel>;
+};
+
+#endif // !nsBaseChannel_h__
diff --git a/netwerk/base/nsBaseContentStream.cpp b/netwerk/base/nsBaseContentStream.cpp
new file mode 100644
index 0000000000..d59145f463
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseContentStream.h"
+#include "nsStreamUtils.h"
+
+//-----------------------------------------------------------------------------
+
+void nsBaseContentStream::DispatchCallback(bool async) {
+ if (!mCallback) return;
+
+ // It's important to clear mCallback and mCallbackTarget up-front because the
+ // OnInputStreamReady implementation may call our AsyncWait method.
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ if (async) {
+ callback = NS_NewInputStreamReadyEvent(
+ "nsBaseContentStream::DispatchCallback", mCallback, mCallbackTarget);
+ mCallback = nullptr;
+ } else {
+ callback.swap(mCallback);
+ }
+ mCallbackTarget = nullptr;
+
+ callback->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsISupports
+
+NS_IMPL_ADDREF(nsBaseContentStream)
+NS_IMPL_RELEASE(nsBaseContentStream)
+
+// We only support nsIAsyncInputStream when we are in non-blocking mode.
+NS_INTERFACE_MAP_BEGIN(nsBaseContentStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mNonBlocking)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::Close() {
+ return IsClosed() ? NS_OK : CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::Available(uint64_t* result) {
+ *result = 0;
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::StreamStatus() { return mStatus; }
+
+NS_IMETHODIMP
+nsBaseContentStream::Read(char* buf, uint32_t count, uint32_t* result) {
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::ReadSegments(nsWriteSegmentFun fun, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0;
+
+ if (mStatus == NS_BASE_STREAM_CLOSED) return NS_OK;
+
+ // No data yet
+ if (!IsClosed() && IsNonBlocking()) return NS_BASE_STREAM_WOULD_BLOCK;
+
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::IsNonBlocking(bool* result) {
+ *result = mNonBlocking;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIAsyncInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::CloseWithStatus(nsresult status) {
+ if (IsClosed()) return NS_OK;
+
+ NS_ENSURE_ARG(NS_FAILED(status));
+ mStatus = status;
+
+ DispatchCallback();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t requestedCount,
+ nsIEventTarget* target) {
+ // Our _only_ consumer is nsInputStreamPump, so we simplify things here by
+ // making assumptions about how we will be called.
+ NS_ASSERTION(target, "unexpected parameter");
+ NS_ASSERTION(flags == 0, "unexpected parameter");
+ NS_ASSERTION(requestedCount == 0, "unexpected parameter");
+
+#ifdef DEBUG
+ bool correctThread;
+ target->IsOnCurrentThread(&correctThread);
+ NS_ASSERTION(correctThread, "event target must be on the current thread");
+#endif
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ if (!mCallback) return NS_OK;
+
+ // If we're already closed, then dispatch this callback immediately.
+ if (IsClosed()) {
+ DispatchCallback();
+ return NS_OK;
+ }
+
+ OnCallbackPending();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBaseContentStream.h b/netwerk/base/nsBaseContentStream.h
new file mode 100644
index 0000000000..13a3766857
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseContentStream_h__
+#define nsBaseContentStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream is designed to be subclassed with the intention of being
+// used to satisfy the nsBaseChannel::OpenContentStream method.
+//
+// The subclass typically overrides the default Available, ReadSegments and
+// CloseWithStatus methods. By default, Read is implemented in terms of
+// ReadSegments, and Close is implemented in terms of CloseWithStatus. If
+// CloseWithStatus is overriden, then the subclass will usually want to call
+// the base class' CloseWithStatus method before returning.
+//
+// If the stream is non-blocking, then readSegments may return the exception
+// NS_BASE_STREAM_WOULD_BLOCK if there is no data available and the stream is
+// not at the "end-of-file" or already closed. This error code must not be
+// returned from the Available implementation. When the caller receives this
+// error code, he may choose to call the stream's AsyncWait method, in which
+// case the base stream will have a non-null PendingCallback. When the stream
+// has data or encounters an error, it should be sure to dispatch a pending
+// callback if one exists (see DispatchCallback). The implementation of the
+// base stream's CloseWithStatus (and Close) method will ensure that any
+// pending callback is dispatched. It is the responsibility of the subclass
+// to ensure that the pending callback is dispatched when it wants to have its
+// ReadSegments method called again.
+
+class nsBaseContentStream : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsBaseContentStream(bool nonBlocking)
+ : mStatus(NS_OK), mNonBlocking(nonBlocking) {}
+
+ nsresult Status() { return mStatus; }
+ bool IsNonBlocking() { return mNonBlocking; }
+ bool IsClosed() { return NS_FAILED(mStatus); }
+
+ // Called to test if the stream has a pending callback.
+ bool HasPendingCallback() { return mCallback != nullptr; }
+
+ // The current dispatch target (may be null) for the pending callback if any.
+ nsIEventTarget* CallbackTarget() { return mCallbackTarget; }
+
+ // Called to dispatch a pending callback. If there is no pending callback,
+ // then this function does nothing. Pass true to this function to cause the
+ // callback to occur asynchronously; otherwise, the callback will happen
+ // before this function returns.
+ void DispatchCallback(bool async = true);
+
+ // Helper function to make code more self-documenting.
+ void DispatchCallbackSync() { DispatchCallback(false); }
+
+ protected:
+ virtual ~nsBaseContentStream() = default;
+
+ private:
+ // Called from the base stream's AsyncWait method when a pending callback
+ // is installed on the stream.
+ virtual void OnCallbackPending() {}
+
+ private:
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsresult mStatus;
+ bool mNonBlocking;
+};
+
+#endif // nsBaseContentStream_h__
diff --git a/netwerk/base/nsBufferedStreams.cpp b/netwerk/base/nsBufferedStreams.cpp
new file mode 100644
index 0000000000..2606352cc4
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.cpp
@@ -0,0 +1,1197 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBufferedStreams.h"
+#include "nsStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIEventTarget.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include <algorithm>
+
+#ifdef DEBUG_brendan
+# define METERING
+#endif
+
+#ifdef METERING
+# include <stdio.h>
+# define METER(x) x
+# define MAX_BIG_SEEKS 20
+
+static struct {
+ uint32_t mSeeksWithinBuffer;
+ uint32_t mSeeksOutsideBuffer;
+ uint32_t mBufferReadUponSeek;
+ uint32_t mBufferUnreadUponSeek;
+ uint32_t mBytesReadFromBuffer;
+ uint32_t mBigSeekIndex;
+ struct {
+ int64_t mOldOffset;
+ int64_t mNewOffset;
+ } mBigSeek[MAX_BIG_SEEKS];
+} bufstats;
+#else
+# define METER(x) /* nothing */
+#endif
+
+using namespace mozilla::ipc;
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedStream
+
+nsBufferedStream::~nsBufferedStream() { Close(); }
+
+NS_IMPL_ADDREF(nsBufferedStream)
+NS_IMPL_RELEASE(nsBufferedStream)
+
+NS_INTERFACE_MAP_BEGIN(nsBufferedStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsITellableStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable)
+NS_INTERFACE_MAP_END
+
+nsresult nsBufferedStream::Init(nsISupports* aStream, uint32_t bufferSize) {
+ NS_ASSERTION(aStream, "need to supply a stream");
+ NS_ASSERTION(mStream == nullptr, "already inited");
+ mStream = aStream; // we keep a reference until nsBufferedStream::Close
+ mBufferSize = bufferSize;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ mSeekable = seekable;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ mBuffer = new (mozilla::fallible) char[bufferSize];
+ if (mBuffer == nullptr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+void nsBufferedStream::Close() {
+ // Drop the reference from nsBufferedStream::Init()
+ mStream = nullptr;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ if (mBuffer) {
+ delete[] mBuffer;
+ mBuffer = nullptr;
+ mBufferSize = 0;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ mFillPoint = 0;
+ }
+#ifdef METERING
+ {
+ static FILE* tfp;
+ if (!tfp) {
+ tfp = fopen("/tmp/bufstats", "w");
+ if (tfp) {
+ setvbuf(tfp, nullptr, _IOLBF, 0);
+ }
+ }
+ if (tfp) {
+ fprintf(tfp, "seeks within buffer: %u\n", bufstats.mSeeksWithinBuffer);
+ fprintf(tfp, "seeks outside buffer: %u\n",
+ bufstats.mSeeksOutsideBuffer);
+ fprintf(tfp, "buffer read on seek: %u\n",
+ bufstats.mBufferReadUponSeek);
+ fprintf(tfp, "buffer unread on seek: %u\n",
+ bufstats.mBufferUnreadUponSeek);
+ fprintf(tfp, "bytes read from buffer: %u\n",
+ bufstats.mBytesReadFromBuffer);
+ for (uint32_t i = 0; i < bufstats.mBigSeekIndex; i++) {
+ fprintf(tfp, "bigseek[%u] = {old: %u, new: %u}\n", i,
+ bufstats.mBigSeek[i].mOldOffset,
+ bufstats.mBigSeek[i].mNewOffset);
+ }
+ }
+ }
+#endif
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Seek(int32_t whence, int64_t offset) {
+ if (mStream == nullptr) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ // If the underlying stream isn't a random access store, then fail early.
+ // We could possibly succeed for the case where the seek position denotes
+ // something that happens to be read into the buffer, but that would make
+ // the failure data-dependent.
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("mStream doesn't QI to nsISeekableStream");
+ return rv;
+ }
+
+ int64_t absPos = 0;
+ switch (whence) {
+ case nsISeekableStream::NS_SEEK_SET:
+ absPos = offset;
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ absPos = mBufferStartOffset;
+ absPos += mCursor;
+ absPos += offset;
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ absPos = -1;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bogus seek whence parameter");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Let mCursor point into the existing buffer if the new position is
+ // between the current cursor and the mFillPoint "fencepost" -- the
+ // client may never get around to a Read or Write after this Seek.
+ // Read and Write worry about flushing and filling in that event.
+ // But if we're at EOF, make sure to pass the seek through to the
+ // underlying stream, because it may have auto-closed itself and
+ // needs to reopen.
+ uint32_t offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint && !mEOF) {
+ METER(bufstats.mSeeksWithinBuffer++);
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(bufstats.mSeeksOutsideBuffer++);
+ METER(bufstats.mBufferReadUponSeek += mCursor);
+ METER(bufstats.mBufferUnreadUponSeek += mFillPoint - mCursor);
+ rv = Flush();
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ NS_WARNING(
+ "(debug) Flush returned error within nsBufferedStream::Seek, so we "
+ "exit early.");
+#endif
+ return rv;
+ }
+
+ rv = ras->Seek(whence, offset);
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ NS_WARNING(
+ "(debug) Error: ras->Seek() returned error within "
+ "nsBufferedStream::Seek, so we exit early.");
+#endif
+ return rv;
+ }
+
+ mEOF = false;
+
+ // Recompute whether the offset we're seeking to is in our buffer.
+ // Note that we need to recompute because Flush() might have
+ // changed mBufferStartOffset.
+ offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint) {
+ // It's safe to just set mCursor to offsetInBuffer. In particular, we
+ // want to avoid calling Fill() here since we already have the data that
+ // was seeked to and calling Fill() might auto-close our underlying
+ // stream in some cases.
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex]
+ .mOldOffset = mBufferStartOffset + int64_t(mCursor));
+ const int64_t minus1 = -1;
+ if (absPos == minus1) {
+ // then we had the SEEK_END case, above
+ int64_t tellPos;
+ rv = ras->Tell(&tellPos);
+ mBufferStartOffset = tellPos;
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ mBufferStartOffset = absPos;
+ }
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex++]
+ .mNewOffset = mBufferStartOffset);
+
+ mFillPoint = mCursor = 0;
+
+ // If we seeked back to the start, then don't fill the buffer
+ // right now in case this is a lazily-opened file stream.
+ // We'll fill on the first read, like we did initially.
+ if (whence == nsISeekableStream::NS_SEEK_SET && offset == 0) {
+ return NS_OK;
+ }
+ return Fill();
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Tell(int64_t* result) {
+ if (mStream == nullptr) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ int64_t result64 = mBufferStartOffset;
+ result64 += mCursor;
+ *result = result64;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedStream::SetEOF() {
+ if (mStream == nullptr) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ras->SetEOF();
+ if (NS_SUCCEEDED(rv)) {
+ mEOF = true;
+ }
+
+ return rv;
+}
+
+nsresult nsBufferedStream::GetData(nsISupports** aResult) {
+ nsCOMPtr<nsISupports> stream(mStream);
+ stream.forget(aResult);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream)
+
+NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_BUFFEREDINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream)
+ // Unfortunately there isn't a macro that combines ambiguous and conditional,
+ // and as far as I can tell, no other class would need such a macro.
+ if (mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) {
+ foundInterface =
+ static_cast<nsIInputStream*>(static_cast<nsIAsyncInputStream*>(this));
+ } else if (!mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) {
+ foundInterface = static_cast<nsIInputStream*>(
+ static_cast<nsIBufferedInputStream*>(this));
+ } else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
+ mIsIPCSerializable)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mIsAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
+ mIsAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
+ mIsCloneableInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, mIsInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength,
+ mIsAsyncInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback,
+ mIsAsyncInputStreamLength)
+ NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream, nsIInputStream,
+ nsIBufferedInputStream, nsISeekableStream,
+ nsITellableStream, nsIStreamBufferAccess)
+
+nsresult nsBufferedInputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsBufferedInputStream> stream = new nsBufferedInputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize) {
+ nsresult rv = nsBufferedStream::Init(stream, bufferSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStream);
+ mIsIPCSerializable = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream);
+ mIsAsyncInputStream = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ mIsCloneableInputStream = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ mIsInputStreamLength = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ mIsAsyncInputStreamLength = !!stream;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIInputStream> nsBufferedInputStream::GetInputStream() {
+ // A non-null mStream implies Init() has been called.
+ MOZ_ASSERT(mStream);
+
+ nsIInputStream* out = nullptr;
+ DebugOnly<nsresult> rv = QueryInterface(NS_GET_IID(nsIInputStream),
+ reinterpret_cast<void**>(&out));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(out);
+
+ return already_AddRefed<nsIInputStream>(out);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Close() {
+ nsresult rv = NS_OK;
+ if (mStream) {
+ rv = Source()->Close();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "(debug) Error: Source()->Close() returned error in "
+ "bsBuffedInputStream::Close().");
+ }
+ }
+
+ nsBufferedStream::Close();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Available(uint64_t* result) {
+ *result = 0;
+
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ uint64_t avail = mFillPoint - mCursor;
+
+ uint64_t tmp;
+ nsresult rv = Source()->Available(&tmp);
+ if (NS_SUCCEEDED(rv)) {
+ avail += tmp;
+ }
+
+ if (avail) {
+ *result = avail;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::StreamStatus() {
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ if (mFillPoint - mCursor) {
+ return NS_OK;
+ }
+
+ return Source()->StreamStatus();
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Read(char* buf, uint32_t count, uint32_t* result) {
+ if (mBufferDisabled) {
+ if (!mStream) {
+ *result = 0;
+ return NS_OK;
+ }
+ nsresult rv = Source()->Read(buf, count, result);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferStartOffset += *result; // so nsBufferedStream::Tell works
+ if (*result == 0) {
+ mEOF = true;
+ }
+ }
+ return rv;
+ }
+
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0;
+
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ while (count > 0) {
+ uint32_t amt = std::min(count, mFillPoint - mCursor);
+ if (amt > 0) {
+ uint32_t read = 0;
+ rv = writer(static_cast<nsIBufferedInputStream*>(this), closure,
+ mBuffer + mCursor, *result, amt, &read);
+ if (NS_FAILED(rv)) {
+ // errors returned from the writer end here!
+ rv = NS_OK;
+ break;
+ }
+ *result += read;
+ count -= read;
+ mCursor += read;
+ } else {
+ rv = Fill();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mFillPoint == mCursor) {
+ break;
+ }
+ }
+ }
+ return (*result > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::IsNonBlocking(bool* aNonBlocking) {
+ if (mStream) {
+ return Source()->IsNonBlocking(aNonBlocking);
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Fill() {
+ if (mBufferDisabled) {
+ return NS_OK;
+ }
+ NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED);
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+
+ nsresult rv;
+ int32_t rem = int32_t(mFillPoint - mCursor);
+ if (rem > 0) {
+ // slide the remainder down to the start of the buffer
+ // |<------------->|<--rem-->|<--->|
+ // b c f s
+ memcpy(mBuffer, mBuffer + mCursor, rem);
+ }
+ mBufferStartOffset += mCursor;
+ mFillPoint = rem;
+ mCursor = 0;
+
+ uint32_t amt;
+ rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (amt == 0) {
+ mEOF = true;
+ }
+
+ mFillPoint += amt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) {
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0) {
+ return nullptr;
+ }
+
+ if (mBufferDisabled) {
+ return nullptr;
+ }
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mFillPoint - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Fill())) {
+ return nullptr;
+ }
+ buf = mBuffer + mCursor;
+ rem = mFillPoint - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem) {
+ return nullptr;
+ }
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem) {
+ return nullptr;
+ }
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength) {
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0) {
+ return;
+ }
+
+ NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch");
+ mCursor += aLength;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::DisableBuffering() {
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::EnableBuffering() {
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetUnbufferedStream(nsISupports** aStream) {
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+
+ nsCOMPtr<nsISupports> stream = mStream;
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+void nsBufferedInputStream::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ if (mStream) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+
+ InputStreamHelper::SerializedComplexity(stream, aMaxSize, aSizeUsed, aPipes,
+ aTransferables);
+ }
+}
+
+void nsBufferedInputStream::Serialize(InputStreamParams& aParams,
+ uint32_t aMaxSize, uint32_t* aSizeUsed) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ BufferedInputStreamParams params;
+
+ if (mStream) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+
+ InputStreamParams wrappedParams;
+ InputStreamHelper::SerializeInputStream(stream, wrappedParams, aMaxSize,
+ aSizeUsed);
+
+ params.optionalStream().emplace(wrappedParams);
+ }
+
+ params.bufferSize() = mBufferSize;
+
+ aParams = params;
+}
+
+bool nsBufferedInputStream::Deserialize(const InputStreamParams& aParams) {
+ if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const BufferedInputStreamParams& params =
+ aParams.get_BufferedInputStreamParams();
+ const Maybe<InputStreamParams>& wrappedParams = params.optionalStream();
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (wrappedParams.isSome()) {
+ stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref());
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+ }
+
+ nsresult rv = Init(stream, params.bufferSize());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::CloseWithStatus(nsresult aStatus) { return Close(); }
+
+NS_IMETHODIMP
+nsBufferedInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream);
+ if (!stream) {
+ // Stream is probably closed. Callback, if not nullptr, can be executed
+ // immediately
+ if (!aCallback) {
+ return NS_OK;
+ }
+
+ if (aEventTarget) {
+ nsCOMPtr<nsIInputStreamCallback> callable = NS_NewInputStreamReadyEvent(
+ "nsBufferedInputStream::OnInputStreamReady", aCallback, aEventTarget);
+ return callable->OnInputStreamReady(this);
+ }
+
+ aCallback->OnInputStreamReady(this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (NS_WARN_IF(mAsyncWaitCallback && aCallback &&
+ mAsyncWaitCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+ }
+
+ return stream->AsyncWait(callback, aFlags, aRequestedCount, aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mMutex);
+
+ // We have been canceled in the meanwhile.
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncWaitCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamReady(this);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetData(nsIInputStream** aResult) {
+ nsCOMPtr<nsISupports> stream;
+ nsBufferedStream::GetData(getter_AddRefs(stream));
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(stream);
+ inputStream.forget(aResult);
+ return NS_OK;
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetCloneable(bool* aCloneable) {
+ *aCloneable = false;
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+
+ // If we don't have the buffer, the inputStream has been already closed.
+ // If mBufferStartOffset is not 0, the stream has been seeked or read.
+ // In both case the cloning is not supported.
+ if (!mBuffer || mBufferStartOffset) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+
+ // GetCloneable is infallible.
+ NS_ENSURE_TRUE(stream, NS_OK);
+
+ return stream->GetCloneable(aCloneable);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Clone(nsIInputStream** aResult) {
+ RecursiveMutexAutoLock lock(mBufferMutex);
+
+ if (!mBuffer || mBufferStartOffset) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv = stream->Clone(getter_AddRefs(clonedStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIBufferedInputStream> bis = new nsBufferedInputStream();
+ rv = bis->Init(clonedStream, mBufferSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult =
+ static_cast<nsBufferedInputStream*>(bis.get())->GetInputStream().take();
+
+ return NS_OK;
+}
+
+// nsIInputStreamLength
+
+NS_IMETHODIMP
+nsBufferedInputStream::Length(int64_t* aLength) {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
+
+ return stream->Length(aLength);
+}
+
+// nsIAsyncInputStreamLength
+
+NS_IMETHODIMP
+nsBufferedInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback,
+ nsIEventTarget* aEventTarget) {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ if (!stream) {
+ // Stream is probably closed. Callback, if not nullptr, can be executed
+ // immediately
+ if (aCallback) {
+ const RefPtr<nsBufferedInputStream> self = this;
+ const nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback;
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "nsBufferedInputStream::OnInputStreamLengthReady",
+ [self, callback] { callback->OnInputStreamLengthReady(self, -1); });
+
+ if (aEventTarget) {
+ aEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ } else {
+ runnable->Run();
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ mAsyncInputStreamLengthCallback = aCallback;
+ }
+
+ MOZ_ASSERT(stream);
+ return stream->AsyncLengthWait(callback, aEventTarget);
+}
+
+// nsIInputStreamLengthCallback
+
+NS_IMETHODIMP
+nsBufferedInputStream::OnInputStreamLengthReady(
+ nsIAsyncInputStreamLength* aStream, int64_t aLength) {
+ nsCOMPtr<nsIInputStreamLengthCallback> callback;
+ {
+ MutexAutoLock lock(mMutex);
+ // We have been canceled in the meanwhile.
+ if (!mAsyncInputStreamLengthCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncInputStreamLengthCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamLengthReady(this, aLength);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedOutputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+// This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
+// non-nullness of mSafeStream.
+NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream, mSafeStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+nsresult nsBufferedOutputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsBufferedOutputStream> stream = new nsBufferedOutputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize) {
+ // QI stream to an nsISafeOutputStream, to see if we should support it
+ mSafeStream = do_QueryInterface(stream);
+
+ return nsBufferedStream::Init(stream, bufferSize);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Close() {
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ nsresult rv1, rv2 = NS_OK;
+
+ rv1 = Flush();
+
+#ifdef DEBUG
+ if (NS_FAILED(rv1)) {
+ NS_WARNING(
+ "(debug) Flush() inside nsBufferedOutputStream::Close() returned error "
+ "(rv1).");
+ }
+#endif
+
+ // If we fail to Flush all the data, then we close anyway and drop the
+ // remaining data in the buffer. We do this because it's what Unix does
+ // for fclose and close. However, we report the error from Flush anyway.
+ if (mStream) {
+ rv2 = Sink()->Close();
+#ifdef DEBUG
+ if (NS_FAILED(rv2)) {
+ NS_WARNING(
+ "(debug) Sink->Close() inside nsBufferedOutputStream::Close() "
+ "returned error (rv2).");
+ }
+#endif
+ }
+ nsBufferedStream::Close();
+
+ if (NS_FAILED(rv1)) {
+ return rv1;
+ }
+ if (NS_FAILED(rv2)) {
+ return rv2;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::StreamStatus() {
+ return mStream ? Sink()->StreamStatus() : NS_BASE_STREAM_CLOSED;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* result) {
+ nsresult rv = NS_OK;
+ uint32_t written = 0;
+ *result = 0;
+ if (!mStream) {
+ // We special case this situation.
+ // We should catch the failure, NS_BASE_STREAM_CLOSED ASAP, here.
+ // If we don't, eventually Flush() is called in the while loop below
+ // after so many writes.
+ // However, Flush() returns NS_OK when mStream is null (!!),
+ // and we don't get a meaningful error, NS_BASE_STREAM_CLOSED,
+ // soon enough when we use buffered output.
+#ifdef DEBUG
+ NS_WARNING(
+ "(info) nsBufferedOutputStream::Write returns NS_BASE_STREAM_CLOSED "
+ "immediately (mStream==null).");
+#endif
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ while (count > 0) {
+ uint32_t amt = std::min(count, mBufferSize - mCursor);
+ if (amt > 0) {
+ memcpy(mBuffer + mCursor, buf + written, amt);
+ written += amt;
+ count -= amt;
+ mCursor += amt;
+ if (mFillPoint < mCursor) mFillPoint = mCursor;
+ } else {
+ NS_ASSERTION(mFillPoint, "loop in nsBufferedOutputStream::Write!");
+ rv = Flush();
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ NS_WARNING(
+ "(debug) Flush() returned error in nsBufferedOutputStream::Write.");
+#endif
+ break;
+ }
+ }
+ }
+ *result = written;
+ return (written > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Flush() {
+ nsresult rv;
+ uint32_t amt;
+ if (!mStream) {
+ // Stream already cancelled/flushed; probably because of previous error.
+ return NS_OK;
+ }
+ // optimize : some code within C-C needs to call Seek -> Flush() often.
+ if (mFillPoint == 0) {
+ return NS_OK;
+ }
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ rv = Sink()->Write(mBuffer, mFillPoint, &amt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mBufferStartOffset += amt;
+ if (amt == mFillPoint) {
+ mFillPoint = mCursor = 0;
+ return NS_OK; // flushed everything
+ }
+
+ // slide the remainder down to the start of the buffer
+ // |<-------------->|<---|----->|
+ // b a c s
+ uint32_t rem = mFillPoint - amt;
+ memmove(mBuffer, mBuffer + amt, rem);
+ mFillPoint = mCursor = rem;
+ return NS_ERROR_FAILURE; // didn't flush all
+}
+
+// nsISafeOutputStream
+NS_IMETHODIMP
+nsBufferedOutputStream::Finish() {
+ // flush the stream, to write out any buffered data...
+ nsresult rv1 = nsBufferedOutputStream::Flush();
+ nsresult rv2 = NS_OK;
+
+ if (NS_FAILED(rv1)) {
+ NS_WARNING(
+ "(debug) nsBufferedOutputStream::Flush() failed in "
+ "nsBufferedOutputStream::Finish()! Possible dataloss.");
+
+ rv2 = Sink()->Close();
+ if (NS_FAILED(rv2)) {
+ NS_WARNING(
+ "(debug) Sink()->Close() failed in nsBufferedOutputStream::Finish()! "
+ "Possible dataloss.");
+ }
+ } else {
+ rv2 = mSafeStream->Finish();
+ if (NS_FAILED(rv2)) {
+ NS_WARNING(
+ "(debug) mSafeStream->Finish() failed within "
+ "nsBufferedOutputStream::Flush()! Possible dataloss.");
+ }
+ }
+
+ // ... and close the buffered stream, so any further attempts to flush/close
+ // the buffered stream won't cause errors.
+ nsBufferedStream::Close();
+
+ // We want to return the errors precisely from Finish()
+ // and mimick the existing error handling in
+ // nsBufferedOutputStream::Close() as reference.
+
+ if (NS_FAILED(rv1)) {
+ return rv1;
+ }
+ if (NS_FAILED(rv2)) {
+ return rv2;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteFrom(nsIInputStream* inStr, uint32_t count,
+ uint32_t* _retval) {
+ return WriteSegments(NS_CopyStreamToSegment, inStr, count, _retval);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ *_retval = 0;
+ nsresult rv;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ while (count > 0) {
+ uint32_t left = std::min(count, mBufferSize - mCursor);
+ if (left == 0) {
+ rv = Flush();
+ if (NS_FAILED(rv)) {
+ return (*_retval > 0) ? NS_OK : rv;
+ }
+
+ continue;
+ }
+
+ uint32_t read = 0;
+ rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read);
+
+ if (NS_FAILED(rv)) { // If we have read some data, return ok
+ return (*_retval > 0) ? NS_OK : rv;
+ }
+ mCursor += read;
+ *_retval += read;
+ count -= read;
+ mFillPoint = std::max(mFillPoint, mCursor);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::IsNonBlocking(bool* aNonBlocking) {
+ if (mStream) {
+ return Sink()->IsNonBlocking(aNonBlocking);
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) {
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0) {
+ return nullptr;
+ }
+
+ if (mBufferDisabled) {
+ return nullptr;
+ }
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mBufferSize - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Flush())) {
+ return nullptr;
+ }
+ buf = mBuffer + mCursor;
+ rem = mBufferSize - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem) {
+ return nullptr;
+ }
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem) {
+ return nullptr;
+ }
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) {
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0) {
+ return;
+ }
+
+ NS_ASSERTION(mCursor + aLength <= mBufferSize, "PutBuffer botch");
+ mCursor += aLength;
+ if (mFillPoint < mCursor) {
+ mFillPoint = mCursor;
+ }
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::DisableBuffering() {
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ nsresult rv = Flush();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::EnableBuffering() {
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::GetUnbufferedStream(nsISupports** aStream) {
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ if (mFillPoint) {
+ nsresult rv = Flush();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsISupports> stream = mStream;
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::GetData(nsIOutputStream** aResult) {
+ nsCOMPtr<nsISupports> stream;
+ nsBufferedStream::GetData(getter_AddRefs(stream));
+ nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(stream);
+ outputStream.forget(aResult);
+ return NS_OK;
+}
+#undef METER
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h
new file mode 100644
index 0000000000..929c544209
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBufferedStreams_h__
+#define nsBufferedStreams_h__
+
+#include "nsIBufferedStreams.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIStreamBufferAccess.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStreamLength.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RecursiveMutex.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedStream : public nsISeekableStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+
+ nsBufferedStream() = default;
+
+ void Close();
+
+ protected:
+ virtual ~nsBufferedStream();
+
+ nsresult Init(nsISupports* stream, uint32_t bufferSize);
+ nsresult GetData(nsISupports** aResult);
+ NS_IMETHOD Fill() = 0;
+ NS_IMETHOD Flush() = 0;
+
+ uint32_t mBufferSize{0};
+ char* mBuffer MOZ_GUARDED_BY(mBufferMutex){nullptr};
+
+ mozilla::RecursiveMutex mBufferMutex{"nsBufferedStream::mBufferMutex"};
+
+ // mBufferStartOffset is the offset relative to the start of mStream.
+ int64_t mBufferStartOffset{0};
+
+ // mCursor is the read cursor for input streams, or write cursor for
+ // output streams, and is relative to mBufferStartOffset.
+ uint32_t mCursor{0};
+
+ // mFillPoint is the amount available in the buffer for input streams,
+ // or the high watermark of bytes written into the buffer, and therefore
+ // is relative to mBufferStartOffset.
+ uint32_t mFillPoint{0};
+
+ nsCOMPtr<nsISupports> mStream; // cast to appropriate subclass
+
+ bool mBufferDisabled{false};
+ bool mEOF{false}; // True if mStream is at EOF
+ bool mSeekable{true};
+ uint8_t mGetBufferCount{0};
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedInputStream final : public nsBufferedStream,
+ public nsIBufferedInputStream,
+ public nsIStreamBufferAccess,
+ public nsIIPCSerializableInputStream,
+ public nsIAsyncInputStream,
+ public nsIInputStreamCallback,
+ public nsICloneableInputStream,
+ public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength,
+ public nsIInputStreamLengthCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIBUFFEREDINPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMLENGTH
+ NS_DECL_NSIASYNCINPUTSTREAMLENGTH
+ NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
+
+ nsBufferedInputStream() = default;
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ nsIInputStream* Source() { return (nsIInputStream*)mStream.get(); }
+
+ /**
+ * If there's a reference/pointer to an nsBufferedInputStream BEFORE calling
+ * Init() AND the intent is to ultimately convert/assign that
+ * reference/pointer to an nsIInputStream, DO NOT use that initial
+ * reference/pointer. Instead, use the value of QueryInterface-ing to an
+ * nsIInputStream (and, again, the QueryInterface must be performed after
+ * Init()). This is because nsBufferedInputStream has multiple underlying
+ * nsIInputStreams (one from nsIBufferedInputStream and one from
+ * nsIAsyncInputStream), and the correct base nsIInputStream to use will be
+ * unknown until the final value of mIsAsyncInputStream is set in Init().
+ *
+ * This method, however, does just that but also hides the QI details and
+ * will assert if called before Init().
+ */
+ already_AddRefed<nsIInputStream> GetInputStream();
+
+ protected:
+ virtual ~nsBufferedInputStream() = default;
+
+ NS_IMETHOD Fill() override;
+ NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED{"nsBufferedInputStream::mMutex"};
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback;
+
+ bool mIsIPCSerializable{true};
+ bool mIsAsyncInputStream{false};
+ bool mIsCloneableInputStream{false};
+ bool mIsInputStreamLength{false};
+ bool mIsAsyncInputStreamLength{false};
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedOutputStream : public nsBufferedStream,
+ public nsISafeOutputStream,
+ public nsIBufferedOutputStream,
+ public nsIStreamBufferAccess {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISAFEOUTPUTSTREAM
+ NS_DECL_NSIBUFFEREDOUTPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+
+ nsBufferedOutputStream() = default;
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ nsIOutputStream* Sink() { return (nsIOutputStream*)mStream.get(); }
+
+ protected:
+ virtual ~nsBufferedOutputStream() { nsBufferedOutputStream::Close(); }
+
+ NS_IMETHOD Fill() override { return NS_OK; } // no-op for output streams
+
+ nsCOMPtr<nsISafeOutputStream> mSafeStream; // QI'd from mStream
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsBufferedStreams_h__
diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp
new file mode 100644
index 0000000000..4c5a3a0fd6
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.cpp
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDNSPrefetch.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+#include "nsIDNSAdditionalInfo.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsICancelable.h"
+#include "nsIURI.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Preferences.h"
+
+static mozilla::StaticRefPtr<nsIDNSService> sDNSService;
+
+nsresult nsDNSPrefetch::Initialize(nsIDNSService* aDNSService) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sDNSService = aDNSService;
+ return NS_OK;
+}
+
+nsresult nsDNSPrefetch::Shutdown() {
+ sDNSService = nullptr;
+ return NS_OK;
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI,
+ mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode,
+ nsIDNSListener* aListener, bool storeTiming)
+ : mOriginAttributes(aOriginAttributes),
+ mStoreTiming(storeTiming),
+ mTRRMode(aTRRMode),
+ mListener(do_GetWeakReference(aListener)) {
+ aURI->GetAsciiHost(mHostname);
+ aURI->GetPort(&mPort);
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI,
+ mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode)
+ : mOriginAttributes(aOriginAttributes),
+ mStoreTiming(false),
+ mTRRMode(aTRRMode),
+ mListener(nullptr) {
+ aURI->GetAsciiHost(mHostname);
+}
+
+nsresult nsDNSPrefetch::Prefetch(nsIDNSService::DNSFlags flags) {
+ if (mHostname.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ if (!sDNSService) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ if (mStoreTiming) mStartTimestamp = mozilla::TimeStamp::Now();
+ // If AsyncResolve fails, for example because prefetching is disabled,
+ // then our timing will be useless. However, in such a case,
+ // mEndTimestamp will be a null timestamp and callers should check
+ // TimingsValid() before using the timing.
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
+
+ flags |= nsIDNSService::GetFlagsFromTRRMode(mTRRMode);
+
+ return sDNSService->AsyncResolveNative(
+ mHostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, this, target,
+ mOriginAttributes, getter_AddRefs(tmpOutstanding));
+}
+
+nsresult nsDNSPrefetch::PrefetchLow(nsIDNSService::DNSFlags aFlags) {
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW | aFlags);
+}
+
+nsresult nsDNSPrefetch::PrefetchMedium(nsIDNSService::DNSFlags aFlags) {
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM | aFlags);
+}
+
+nsresult nsDNSPrefetch::PrefetchHigh(nsIDNSService::DNSFlags aFlags) {
+ return Prefetch(aFlags);
+}
+
+namespace {
+
+class HTTPSRRListener final : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ explicit HTTPSRRListener(
+ std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback)
+ : mResultCallback(std::move(aCallback)) {}
+
+ private:
+ ~HTTPSRRListener() = default;
+ std::function<void(nsIDNSHTTPSSVCRecord*)> mResultCallback;
+};
+
+NS_IMPL_ISUPPORTS(HTTPSRRListener, nsIDNSListener)
+
+NS_IMETHODIMP
+HTTPSRRListener::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec,
+ nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ mResultCallback(nullptr);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = do_QueryInterface(aRec);
+ mResultCallback(httpsRecord);
+ return NS_OK;
+}
+
+}; // namespace
+
+nsresult nsDNSPrefetch::FetchHTTPSSVC(
+ bool aRefreshDNS, bool aPrefetch,
+ std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback) {
+ if (!sDNSService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
+ nsIDNSService::DNSFlags flags = nsIDNSService::GetFlagsFromTRRMode(mTRRMode);
+ if (aRefreshDNS) {
+ flags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ if (aPrefetch) {
+ flags |= nsIDNSService::RESOLVE_SPECULATE;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ nsCOMPtr<nsIDNSListener> listener = new HTTPSRRListener(std::move(aCallback));
+ nsCOMPtr<nsIDNSAdditionalInfo> info;
+ if (mPort != -1) {
+ sDNSService->NewAdditionalInfo(""_ns, mPort, getter_AddRefs(info));
+ }
+ return sDNSService->AsyncResolveNative(
+ mHostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags, info, listener,
+ target, mOriginAttributes, getter_AddRefs(tmpOutstanding));
+}
+
+NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener)
+
+NS_IMETHODIMP
+nsDNSPrefetch::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ if (mStoreTiming) {
+ mEndTimestamp = mozilla::TimeStamp::Now();
+ }
+ nsCOMPtr<nsIDNSListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ listener->OnLookupComplete(request, rec, status);
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDNSPrefetch.h b/netwerk/base/nsDNSPrefetch.h
new file mode 100644
index 0000000000..7cad00b588
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDNSPrefetch_h___
+#define nsDNSPrefetch_h___
+
+#include <functional>
+
+#include "nsIWeakReferenceUtils.h"
+#include "nsString.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+
+#include "nsIDNSListener.h"
+#include "nsIRequest.h"
+#include "nsIDNSService.h"
+
+class nsIURI;
+class nsIDNSHTTPSSVCRecord;
+
+class nsDNSPrefetch final : public nsIDNSListener {
+ ~nsDNSPrefetch() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode, nsIDNSListener* aListener,
+ bool storeTiming);
+ // For fetching HTTPS RR.
+ nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode);
+ bool TimingsValid() const {
+ return !mStartTimestamp.IsNull() && !mEndTimestamp.IsNull();
+ }
+ // Only use the two timings if TimingsValid() returns true
+ const mozilla::TimeStamp& StartTimestamp() const { return mStartTimestamp; }
+ const mozilla::TimeStamp& EndTimestamp() const { return mEndTimestamp; }
+
+ static nsresult Initialize(nsIDNSService* aDNSService);
+ static nsresult Shutdown();
+
+ // Call one of the following methods to start the Prefetch.
+ nsresult PrefetchHigh(
+ nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS);
+ nsresult PrefetchMedium(
+ nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS);
+ nsresult PrefetchLow(
+ nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS);
+
+ nsresult FetchHTTPSSVC(
+ bool aRefreshDNS, bool aPrefetch,
+ std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback);
+
+ private:
+ nsCString mHostname;
+ int32_t mPort{-1};
+ mozilla::OriginAttributes mOriginAttributes;
+ bool mStoreTiming;
+ nsIRequest::TRRMode mTRRMode;
+ mozilla::TimeStamp mStartTimestamp;
+ mozilla::TimeStamp mEndTimestamp;
+ nsWeakPtr mListener;
+
+ nsresult Prefetch(nsIDNSService::DNSFlags flags);
+};
+
+#endif
diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp
new file mode 100644
index 0000000000..b9b840172e
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.cpp
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ The converts a filesystem directory into an "HTTP index" stream per
+ Lou Montulli's original spec:
+
+ http://www.mozilla.org/projects/netlib/dirindexformat.html
+
+ */
+
+#include "nsEscape.h"
+#include "nsDirectoryIndexStream.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "nsIFile.h"
+#include "nsNativeCharsetUtils.h"
+
+// NOTE: This runs on the _file transport_ thread.
+// The problem is that now that we're actually doing something with the data,
+// we want to do stuff like i18n sorting. However, none of the collation stuff
+// is threadsafe.
+// So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
+// behaviour, though. See bug 99382.
+
+using namespace mozilla;
+static LazyLogModule gLog("nsDirectoryIndexStream");
+
+nsDirectoryIndexStream::nsDirectoryIndexStream() {
+ MOZ_LOG(gLog, LogLevel::Debug, ("nsDirectoryIndexStream[%p]: created", this));
+}
+
+static int compare(nsIFile* aElement1, nsIFile* aElement2) {
+ if (!NS_IsNativeUTF8()) {
+ // don't check for errors, because we can't report them anyway
+ nsAutoString name1, name2;
+ aElement1->GetLeafName(name1);
+ aElement2->GetLeafName(name2);
+
+ // Note - we should do the collation to do sorting. Why don't we?
+ // Because that is _slow_. Using TestProtocols to list file:///dev/
+ // goes from 3 seconds to 22. (This may be why nsXULSortService is
+ // so slow as well).
+ // Does this have bad effects? Probably, but since nsXULTree appears
+ // to use the raw RDF literal value as the sort key (which ammounts to an
+ // strcmp), it won't be any worse, I think.
+ // This could be made faster, by creating the keys once,
+ // but CompareString could still be smarter - see bug 99383 - bbaetz
+ // NB - 99393 has been WONTFIXed. So if the I18N code is ever made
+ // threadsafe so that this matters, we'd have to pass through a
+ // struct { nsIFile*, uint8_t* } with the pre-calculated key.
+ return Compare(name1, name2);
+ }
+
+ nsAutoCString name1, name2;
+ aElement1->GetNativeLeafName(name1);
+ aElement2->GetNativeLeafName(name2);
+
+ return Compare(name1, name2);
+}
+
+nsresult nsDirectoryIndexStream::Init(nsIFile* aDir) {
+ nsresult rv;
+ bool isDir;
+ rv = aDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ MOZ_ASSERT(isDir, "not a directory");
+ if (!isDir) return NS_ERROR_ILLEGAL_VALUE;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: initialized on %s", this,
+ aDir->HumanReadablePath().get()));
+ }
+
+ // Sigh. We have to allocate on the heap because there are no
+ // assignment operators defined.
+ nsCOMPtr<nsIDirectoryEnumerator> iter;
+ rv = aDir->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) return rv;
+
+ // Now lets sort, because clients expect it that way
+ // XXX - should we do so here, or when the first item is requested?
+ // XXX - use insertion sort instead?
+
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) {
+ mArray.AppendObject(file); // addrefs
+ }
+
+ mArray.Sort(compare);
+
+ mBuf.AppendLiteral("200: filename content-length last-modified file-type\n");
+
+ return NS_OK;
+}
+
+nsDirectoryIndexStream::~nsDirectoryIndexStream() {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: destroyed", this));
+}
+
+nsresult nsDirectoryIndexStream::Create(nsIFile* aDir,
+ nsIInputStream** aResult) {
+ RefPtr<nsDirectoryIndexStream> result = new nsDirectoryIndexStream();
+ if (!result) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = result->Init(aDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream)
+
+// The below routines are proxied to the UI thread!
+NS_IMETHODIMP
+nsDirectoryIndexStream::Close() {
+ mStatus = NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Available(uint64_t* aLength) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ // If there's data in our buffer, use that
+ if (mOffset < (int32_t)mBuf.Length()) {
+ *aLength = mBuf.Length() - mOffset;
+ return NS_OK;
+ }
+
+ // Returning one byte is not ideal, but good enough
+ *aLength = (mPos < mArray.Count()) ? 1 : 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::StreamStatus() { return mStatus; }
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ uint32_t nread = 0;
+
+ // If anything is enqueued (or left-over) in mBuf, then feed it to
+ // the reader first.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+
+ // Room left?
+ if (aCount > 0) {
+ mOffset = 0;
+ mBuf.Truncate();
+
+ // Okay, now we'll suck stuff off of our iterator into the mBuf...
+ while (uint32_t(mBuf.Length()) < aCount) {
+ bool more = mPos < mArray.Count();
+ if (!more) break;
+
+ // don't addref, for speed - an addref happened when it
+ // was placed in the array, so it's not going to go stale
+ nsIFile* current = mArray.ObjectAt(mPos);
+ ++mPos;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: iterated %s", this,
+ current->HumanReadablePath().get()));
+ }
+
+ // rjc: don't return hidden files/directories!
+ // bbaetz: why not?
+ nsresult rv;
+#ifndef XP_UNIX
+ bool hidden = false;
+ current->IsHidden(&hidden);
+ if (hidden) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: skipping hidden file/directory",
+ this));
+ continue;
+ }
+#endif
+
+ int64_t fileSize = 0;
+ current->GetFileSize(&fileSize);
+
+ PRTime fileInfoModifyTime = 0;
+ current->GetLastModifiedTime(&fileInfoModifyTime);
+ fileInfoModifyTime *= PR_USEC_PER_MSEC;
+
+ mBuf.AppendLiteral("201: ");
+
+ // The "filename" field
+ if (!NS_IsNativeUTF8()) {
+ nsAutoString leafname;
+ rv = current->GetLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() &&
+ NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ } else {
+ nsAutoCString leafname;
+ rv = current->GetNativeLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() && NS_Escape(leafname, escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ }
+
+ // The "content-length" field
+ mBuf.AppendInt(fileSize, 10);
+ mBuf.Append(' ');
+
+ // The "last-modified" field
+ PRExplodedTime tm;
+ PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm);
+ {
+ char buf[64];
+ PR_FormatTimeUSEnglish(
+ buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
+ mBuf.Append(buf);
+ }
+
+ // The "file-type" field
+ bool isFile = true;
+ current->IsFile(&isFile);
+ if (isFile) {
+ mBuf.AppendLiteral("FILE ");
+ } else {
+ bool isDir;
+ rv = current->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ if (isDir) {
+ mBuf.AppendLiteral("DIRECTORY ");
+ } else {
+ bool isLink;
+ rv = current->IsSymlink(&isLink);
+ if (NS_FAILED(rv)) return rv;
+ if (isLink) {
+ mBuf.AppendLiteral("SYMBOLIC-LINK ");
+ }
+ }
+ }
+
+ mBuf.Append('\n');
+ }
+
+ // ...and once we've either run out of directory entries, or
+ // filled up the buffer, then we'll push it to the reader.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+ }
+
+ *aReadCount = nread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDirectoryIndexStream.h b/netwerk/base/nsDirectoryIndexStream.h
new file mode 100644
index 0000000000..6bdebc7cfe
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDirectoryIndexStream_h__
+#define nsDirectoryIndexStream_h__
+
+#include "mozilla/Attributes.h"
+
+#include "nsString.h"
+#include "nsIInputStream.h"
+#include "nsCOMArray.h"
+
+class nsIFile;
+
+class nsDirectoryIndexStream final : public nsIInputStream {
+ private:
+ nsCString mBuf;
+ int32_t mOffset{0};
+ nsresult mStatus{NS_OK};
+
+ int32_t mPos{0}; // position within mArray
+ nsCOMArray<nsIFile> mArray; // file objects within the directory
+
+ nsDirectoryIndexStream();
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ nsresult Init(nsIFile* aDir);
+ ~nsDirectoryIndexStream();
+
+ public:
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ static nsresult Create(nsIFile* aDir, nsIInputStream** aResult);
+
+ // nsISupportsInterface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIInputStream interface
+ NS_DECL_NSIINPUTSTREAM
+};
+
+#endif // nsDirectoryIndexStream_h__
diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp
new file mode 100644
index 0000000000..b6d3240226
--- /dev/null
+++ b/netwerk/base/nsDownloader.cpp
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDownloader.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "nsCRTGlue.h"
+
+nsDownloader::~nsDownloader() {
+ if (mLocation && mLocationIsTemp) {
+ // release the sink first since it may still hold an open file
+ // descriptor to mLocation. this needs to happen before the
+ // file can be removed otherwise the Remove call will fail.
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ nsresult rv = mLocation->Remove(false);
+ if (NS_FAILED(rv)) NS_ERROR("unable to remove temp file");
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDownloader, nsIDownloader, nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+nsDownloader::Init(nsIDownloadObserver* observer, nsIFile* location) {
+ mObserver = observer;
+ mLocation = location;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStartRequest(nsIRequest* request) {
+ nsresult rv;
+ if (!mLocation) {
+ nsCOMPtr<nsIFile> location;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(location));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[13];
+ NS_MakeRandomString(buf, 8);
+ memcpy(buf + 8, ".tmp", 5);
+ rv = location->AppendNative(nsDependentCString(buf, 12));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = location->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) return rv;
+
+ location.swap(mLocation);
+ mLocationIsTemp = true;
+ }
+
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(mSink), mLocation);
+ if (NS_FAILED(rv)) return rv;
+
+ // we could wrap this output stream with a buffered output stream,
+ // but it shouldn't be necessary since we will be writing large
+ // chunks given to us via OnDataAvailable.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStopRequest(nsIRequest* request, nsresult status) {
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ mObserver->OnDownloadComplete(this, request, status, mLocation);
+ mObserver = nullptr;
+
+ return NS_OK;
+}
+
+nsresult nsDownloader::ConsumeData(nsIInputStream* in, void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset, uint32_t count,
+ uint32_t* writeCount) {
+ nsDownloader* self = (nsDownloader*)closure;
+ if (self->mSink) return self->mSink->Write(fromRawSegment, count, writeCount);
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ uint32_t n;
+ return inStr->ReadSegments(ConsumeData, this, count, &n);
+}
diff --git a/netwerk/base/nsDownloader.h b/netwerk/base/nsDownloader.h
new file mode 100644
index 0000000000..fe95dd6ac2
--- /dev/null
+++ b/netwerk/base/nsDownloader.h
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDownloader_h__
+#define nsDownloader_h__
+
+#include "nsIDownloader.h"
+#include "nsCOMPtr.h"
+
+class nsIFile;
+class nsIOutputStream;
+
+class nsDownloader : public nsIDownloader {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOWNLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsDownloader() = default;
+
+ protected:
+ virtual ~nsDownloader();
+
+ static nsresult ConsumeData(nsIInputStream* in, void* closure,
+ const char* fromRawSegment, uint32_t toOffset,
+ uint32_t count, uint32_t* writeCount);
+
+ nsCOMPtr<nsIDownloadObserver> mObserver;
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIOutputStream> mSink;
+ bool mLocationIsTemp{false};
+};
+
+#endif // nsDownloader_h__
diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp
new file mode 100644
index 0000000000..32281f6d02
--- /dev/null
+++ b/netwerk/base/nsFileStreams.cpp
@@ -0,0 +1,1031 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#if defined(XP_UNIX)
+# include <unistd.h>
+#elif defined(XP_WIN)
+# include <windows.h>
+# include "nsILocalFileWin.h"
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+#include "private/pprio.h"
+#include "prerror.h"
+
+#include "IOActivityMonitor.h"
+#include "nsFileStreams.h"
+#include "nsIFile.h"
+#include "nsReadLine.h"
+#include "nsIClassInfoImpl.h"
+#include "nsLiteralString.h"
+#include "nsSocketTransport2.h" // for ErrorAccordingToNSPR()
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/RandomAccessStreamParams.h"
+#include "mozilla/Unused.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "nsNetCID.h"
+#include "nsXULAppAPI.h"
+
+using FileHandleType = mozilla::ipc::FileDescriptor::PlatformHandleType;
+
+using namespace mozilla::ipc;
+using namespace mozilla::net;
+
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStreamBase
+
+nsFileStreamBase::~nsFileStreamBase() {
+ // We don't want to try to rewrind the stream when shutting down.
+ mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
+
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsFileStreamBase, nsISeekableStream, nsITellableStream,
+ nsIFileMetadata)
+
+NS_IMETHODIMP
+nsFileStreamBase::Seek(int32_t whence, int64_t offset) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::Tell(int64_t* result) {
+ if (mState == eDeferredOpen && !(mOpenParams.ioFlags & PR_APPEND)) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::SetEOF() {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#if defined(XP_UNIX)
+ // Some system calls require an EOF offset.
+ int64_t offset;
+ rv = Tell(&offset);
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+#if defined(XP_UNIX)
+ if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+#else
+ // XXX not implemented
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetSize(int64_t* _retval) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ *_retval = int64_t(info.size);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetLastModified(int64_t* _retval) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ int64_t modTime = int64_t(info.modifyTime);
+ if (modTime == 0) {
+ *_retval = 0;
+ } else {
+ *_retval = modTime / int64_t(PR_USEC_PER_MSEC);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = mFD;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::Close() {
+ if (mState == eClosed) {
+ return NS_OK;
+ }
+
+ CleanUpOpen();
+
+ nsresult rv = NS_OK;
+ if (mFD) {
+ if (PR_Close(mFD) == PR_FAILURE) rv = NS_BASE_STREAM_OSERROR;
+ mFD = nullptr;
+ mState = eClosed;
+ }
+ return rv;
+}
+
+nsresult nsFileStreamBase::Available(uint64_t* aResult) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // PR_Available with files over 4GB returns an error, so we have to
+ // use the 64-bit version of PR_Available.
+ int64_t avail = PR_Available64(mFD);
+ if (avail == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ // If available is greater than 4GB, return 4GB
+ *aResult = (uint64_t)avail;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aResult) {
+ nsresult rv = DoPendingOpen();
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t bytesRead = PR_Read(mFD, aBuf, aCount);
+ if (bytesRead == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ *aResult = bytesRead;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ // ReadSegments is not implemented because it would be inefficient when
+ // the writer does not consume all data. If you want to call ReadSegments,
+ // wrap a BufferedInputStream around the file stream. That will call
+ // Read().
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsFileStreamBase::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::Flush(void) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t cnt = PR_Sync(mFD);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::StreamStatus() {
+ switch (mState) {
+ case eUnitialized:
+ MOZ_CRASH("This should not happen.");
+ return NS_ERROR_FAILURE;
+
+ case eDeferredOpen:
+ return NS_OK;
+
+ case eOpened:
+ MOZ_ASSERT(mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+
+ case eClosed:
+ MOZ_ASSERT(!mFD);
+ return NS_BASE_STREAM_CLOSED;
+
+ case eError:
+ return mErrorValue;
+ }
+
+ MOZ_CRASH("Invalid mState value.");
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsFileStreamBase::Write(const char* buf, uint32_t count,
+ uint32_t* result) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t cnt = PR_Write(mFD, buf, count);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::WriteFrom(nsIInputStream* inStr, uint32_t count,
+ uint32_t* _retval) {
+ MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags,
+ int32_t aPerm, bool aDeferred) {
+ NS_ENSURE_STATE(aFile);
+
+ mOpenParams.ioFlags = aIoFlags;
+ mOpenParams.perm = aPerm;
+
+ if (aDeferred) {
+ // Clone the file, as it may change between now and the deferred open
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOpenParams.localFile = std::move(file);
+ NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED);
+
+ mState = eDeferredOpen;
+ return NS_OK;
+ }
+
+ mOpenParams.localFile = aFile;
+
+ // Following call open() at main thread.
+ // Main thread might be blocked, while open a remote file.
+ return DoOpen();
+}
+
+void nsFileStreamBase::CleanUpOpen() { mOpenParams.localFile = nullptr; }
+
+nsresult nsFileStreamBase::DoOpen() {
+ MOZ_ASSERT(mState == eDeferredOpen || mState == eUnitialized ||
+ mState == eClosed);
+ NS_ASSERTION(!mFD, "Already have a file descriptor!");
+ NS_ASSERTION(mOpenParams.localFile, "Must have a file to open");
+
+ PRFileDesc* fd;
+ nsresult rv;
+
+ if (mOpenParams.ioFlags & PR_CREATE_FILE) {
+ nsCOMPtr<nsIFile> parent;
+ mOpenParams.localFile->GetParent(getter_AddRefs(parent));
+
+ // Result doesn't need to be checked. If the file's parent path does not
+ // exist, make it. If it does exist, do nothing.
+ if (parent) {
+ mozilla::Unused << parent->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ }
+ }
+
+#ifdef XP_WIN
+ if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) {
+ nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile);
+ MOZ_ASSERT(file);
+
+ rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags,
+ mOpenParams.perm, &fd);
+ } else
+#endif // XP_WIN
+ {
+ rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags,
+ mOpenParams.perm, &fd);
+ }
+
+ if (rv == NS_OK && IOActivityMonitor::IsActive()) {
+ auto nativePath = mOpenParams.localFile->NativePath();
+ if (!nativePath.IsEmpty()) {
+// registering the file to the activity monitor
+#ifdef XP_WIN
+ // 16 bits unicode
+ IOActivityMonitor::MonitorFile(
+ fd, NS_ConvertUTF16toUTF8(nativePath.get()).get());
+#else
+ // 8 bit unicode
+ IOActivityMonitor::MonitorFile(fd, nativePath.get());
+#endif
+ }
+ }
+
+ CleanUpOpen();
+
+ if (NS_FAILED(rv)) {
+ mState = eError;
+ mErrorValue = rv;
+ return rv;
+ }
+
+ mFD = fd;
+ mState = eOpened;
+
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::DoPendingOpen() {
+ switch (mState) {
+ case eUnitialized:
+ MOZ_CRASH("This should not happen.");
+ return NS_ERROR_FAILURE;
+
+ case eDeferredOpen:
+ return DoOpen();
+
+ case eOpened:
+ MOZ_ASSERT(mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+
+ case eClosed:
+ MOZ_ASSERT(!mFD);
+ return NS_BASE_STREAM_CLOSED;
+
+ case eError:
+ return mErrorValue;
+ }
+
+ MOZ_CRASH("Invalid mState value.");
+ return NS_ERROR_FAILURE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase)
+NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase)
+
+NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_LOCALFILEINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable())
+NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, nsIInputStream,
+ nsIFileInputStream, nsISeekableStream,
+ nsITellableStream, nsILineInputStream)
+
+nsresult nsFileInputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsFileInputStream> stream = new nsFileInputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+nsresult nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags,
+ int32_t aPerm) {
+ nsresult rv = NS_OK;
+
+ // If the previous file is open, close it
+ if (mFD) {
+ rv = Close();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Open the file
+ if (aIOFlags == -1) aIOFlags = PR_RDONLY;
+ if (aPerm == -1) aPerm = 0;
+
+ return MaybeOpen(aFile, aIOFlags, aPerm,
+ mBehaviorFlags & nsIFileInputStream::DEFER_OPEN);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
+ int32_t aBehaviorFlags) {
+ NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = aBehaviorFlags;
+ mState = eUnitialized;
+
+ mFile = aFile;
+ mIOFlags = aIOFlags;
+ mPerm = aPerm;
+
+ return Open(aFile, aIOFlags, aPerm);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Close() {
+ // If this stream has already been closed, do nothing.
+ if (mState == eClosed) {
+ return NS_OK;
+ }
+
+ // Get the cache position at the time the file was close. This allows
+ // NS_SEEK_CUR on a closed file that has been opened with
+ // REOPEN_ON_REWIND.
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ // Get actual position. Not one modified by subclasses
+ nsFileStreamBase::Tell(&mCachedPosition);
+ }
+
+ // explicitly clear mLineBuffer in case this stream is reopened
+ mLineBuffer = nullptr;
+ return nsFileStreamBase::Close();
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ // Don't warn if this is a deffered file not found.
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Check if we're at the end of file and need to close
+ if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) {
+ if (!mLineBuffer) {
+ mLineBuffer = mozilla::MakeUnique<nsLineBuffer<char>>();
+ }
+ return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) {
+ return SeekInternal(aWhence, aOffset);
+}
+
+nsresult nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset,
+ bool aClearBuf) {
+ nsresult rv = DoPendingOpen();
+ if (rv != NS_OK && rv != NS_BASE_STREAM_CLOSED) {
+ return rv;
+ }
+
+ if (aClearBuf) {
+ mLineBuffer = nullptr;
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ rv = Open(mFile, mIOFlags, mPerm);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the file was closed, and we do a relative seek, use the
+ // position we cached when we closed the file to seek to the right
+ // location.
+ if (aWhence == NS_SEEK_CUR) {
+ aWhence = NS_SEEK_SET;
+ aOffset += mCachedPosition;
+ }
+ // If we're trying to seek to the start then we're done, so
+ // return early to avoid Seek from calling DoPendingOpen and
+ // opening the underlying file earlier than necessary.
+ if (aWhence == NS_SEEK_SET && aOffset == 0) {
+ return NS_OK;
+ }
+ } else {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ }
+
+ return nsFileStreamBase::Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Tell(int64_t* aResult) {
+ return nsFileStreamBase::Tell(aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Available(uint64_t* aResult) {
+ return nsFileStreamBase::Available(aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::StreamStatus() { return nsFileStreamBase::StreamStatus(); }
+
+void nsFileInputStream::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ *aTransferables = 1;
+}
+
+void nsFileInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize,
+ uint32_t* aSizeUsed) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ FileInputStreamParams params;
+
+ if (NS_SUCCEEDED(DoPendingOpen())) {
+ MOZ_ASSERT(mFD);
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
+ NS_ASSERTION(fd, "This should never be null!");
+
+ params.fileDescriptor() = FileDescriptor(fd);
+
+ Close();
+ } else {
+ NS_WARNING(
+ "This file has not been opened (or could not be opened). "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptor() = FileDescriptor();
+ }
+
+ int32_t behaviorFlags = mBehaviorFlags;
+
+ // The receiving process (or thread) is going to have an open file
+ // descriptor automatically so transferring this flag is meaningless.
+ behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
+
+ params.behaviorFlags() = behaviorFlags;
+ params.ioFlags() = mIOFlags;
+
+ aParams = params;
+}
+
+bool nsFileInputStream::Deserialize(const InputStreamParams& aParams) {
+ NS_ASSERTION(!mFD, "Already have a file descriptor?!");
+ NS_ASSERTION(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
+ NS_ASSERTION(!mFile, "Should never have a file here!");
+ NS_ASSERTION(!mPerm, "This should always be 0!");
+
+ if (aParams.type() != InputStreamParams::TFileInputStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const FileInputStreamParams& params = aParams.get_FileInputStreamParams();
+
+ const FileDescriptor& fd = params.fileDescriptor();
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFD = fileDesc;
+ mState = eOpened;
+ } else {
+ NS_WARNING("Received an invalid file descriptor!");
+ mState = eError;
+ mErrorValue = NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ mBehaviorFlags = params.behaviorFlags();
+
+ if (!XRE_IsParentProcess()) {
+ // A child process shouldn't close when it reads the end because it will
+ // not be able to reopen the file later.
+ mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF;
+
+ // A child process will not be able to reopen the file so this flag is
+ // meaningless.
+ mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
+ }
+
+ mIOFlags = params.ioFlags();
+
+ return true;
+}
+
+bool nsFileInputStream::IsCloneable() const {
+ // This inputStream is cloneable only if has been created using Init() and
+ // it owns a nsIFile. This is not true when it is deserialized from IPC.
+ return XRE_IsParentProcess() && mFile;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::GetCloneable(bool* aCloneable) {
+ *aCloneable = IsCloneable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Clone(nsIInputStream** aResult) {
+ MOZ_ASSERT(IsCloneable());
+ return NS_NewLocalFileInputStream(aResult, mFile, mIOFlags, mPerm,
+ mBehaviorFlags);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, nsFileStreamBase,
+ nsIOutputStream, nsIFileOutputStream)
+
+nsresult nsFileOutputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsFileOutputStream> stream = new nsFileOutputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+ mState = eUnitialized;
+
+ if (ioFlags == -1) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
+ if (perm <= 0) perm = 0664;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN);
+}
+
+nsresult nsFileOutputStream::InitWithFileDescriptor(
+ const mozilla::ipc::FileDescriptor& aFd) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ if (aFd.IsValid()) {
+ auto rawFD = aFd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return NS_ERROR_FAILURE;
+ }
+ mFD = fileDesc;
+ mState = eOpened;
+ } else {
+ mState = eError;
+ mErrorValue = NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Preallocate(int64_t aLength) {
+ if (!mFD) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mozilla::fallocate(mFD, aLength)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAtomicFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, nsFileOutputStream,
+ nsISafeOutputStream, nsIOutputStream,
+ nsIFileOutputStream)
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) {
+ // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter
+ // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending
+ // to existing file. So, throw an exception only if `PR_APPEND` is
+ // explicitly specified without `PR_TRUNCATE`.
+ if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
+}
+
+nsresult nsAtomicFileOutputStream::DoOpen() {
+ // Make sure mOpenParams.localFile will be empty if we bail somewhere in
+ // this function
+ nsCOMPtr<nsIFile> file;
+ file.swap(mOpenParams.localFile);
+
+ if (!file) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ nsresult rv = file->Exists(&mTargetFileExists);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Can't tell if target file exists");
+ mTargetFileExists =
+ true; // Safer to assume it exists - we just do more work.
+ }
+
+ // follow symlinks, for two reasons:
+ // 1) if a user has deliberately set up a profile file as a symlink, we
+ // honor it
+ // 2) to make the MoveToNative() in Finish() an atomic operation (which may
+ // not be the case if moving across directories on different
+ // filesystems).
+ nsCOMPtr<nsIFile> tempResult;
+ rv = file->Clone(getter_AddRefs(tempResult));
+ if (NS_SUCCEEDED(rv) && mTargetFileExists) {
+ tempResult->Normalize();
+ }
+
+ if (NS_SUCCEEDED(rv) && mTargetFileExists) {
+ // Abort if |file| is not writable; it won't work as an output stream.
+ bool isWritable;
+ if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ uint32_t origPerm;
+ if (NS_FAILED(file->GetPermissions(&origPerm))) {
+ NS_ERROR("Can't get permissions of target file");
+ origPerm = mOpenParams.perm;
+ }
+
+ // XXX What if |perm| is more restrictive then |origPerm|?
+ // This leaves the user supplied permissions as they were.
+ rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // nsFileOutputStream::DoOpen will work on the temporary file, so we
+ // prepare it and place it in mOpenParams.localFile.
+ mOpenParams.localFile = tempResult;
+ mTempFile = tempResult;
+ mTargetFile = file;
+ rv = nsFileOutputStream::DoOpen();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Close() {
+ nsresult rv = nsFileOutputStream::Close();
+
+ // the consumer doesn't want the original file overwritten -
+ // so clean up by removing the temp file.
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Finish() {
+ nsresult rv = nsFileOutputStream::Close();
+
+ // if there is no temp file, don't try to move it over the original target.
+ // It would destroy the targetfile if close() is called twice.
+ if (!mTempFile) return rv;
+
+ // Only overwrite if everything was ok, and the temp file could be closed.
+ if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
+ NS_ENSURE_STATE(mTargetFile);
+
+ if (!mTargetFileExists) {
+ // If the target file did not exist when we were initialized, then the
+ // temp file we gave out was actually a reference to the target file.
+ // since we succeeded in writing to the temp file (and hence succeeded
+ // in writing to the target file), there is nothing more to do.
+#ifdef DEBUG
+ bool equal;
+ if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) {
+ NS_WARNING("mTempFile not equal to mTargetFile");
+ }
+#endif
+ } else {
+ nsAutoString targetFilename;
+ rv = mTargetFile->GetLeafName(targetFilename);
+ if (NS_SUCCEEDED(rv)) {
+ // This will replace target.
+ rv = mTempFile->MoveTo(nullptr, targetFilename);
+ if (NS_FAILED(rv)) mTempFile->Remove(false);
+ }
+ }
+ } else {
+ mTempFile->Remove(false);
+
+ // if writing failed, propagate the failure code to the caller.
+ if (NS_FAILED(mWriteResult)) rv = mWriteResult;
+ }
+ mTempFile = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* result) {
+ nsresult rv = nsFileOutputStream::Write(buf, count, result);
+ if (NS_SUCCEEDED(mWriteResult)) {
+ if (NS_FAILED(rv)) {
+ mWriteResult = rv;
+ } else if (count != *result) {
+ mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+ }
+
+ if (NS_FAILED(mWriteResult) && count > 0) {
+ NS_WARNING("writing to output stream failed! data may be lost");
+ }
+ }
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSafeFileOutputStream
+
+NS_IMETHODIMP
+nsSafeFileOutputStream::Finish() {
+ (void)Flush();
+ return nsAtomicFileOutputStream::Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileRandomAccessStream
+
+nsresult nsFileRandomAccessStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileRandomAccessStream, nsFileStreamBase,
+ nsIRandomAccessStream, nsIFileRandomAccessStream,
+ nsIInputStream, nsIOutputStream)
+
+NS_IMETHODIMP
+nsFileRandomAccessStream::GetInputStream(nsIInputStream** aInputStream) {
+ nsCOMPtr<nsIInputStream> inputStream(this);
+
+ inputStream.forget(aInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileRandomAccessStream::GetOutputStream(nsIOutputStream** aOutputStream) {
+ nsCOMPtr<nsIOutputStream> outputStream(this);
+
+ outputStream.forget(aOutputStream);
+ return NS_OK;
+}
+
+nsIInputStream* nsFileRandomAccessStream::InputStream() { return this; }
+
+nsIOutputStream* nsFileRandomAccessStream::OutputStream() { return this; }
+
+RandomAccessStreamParams nsFileRandomAccessStream::Serialize(
+ nsIInterfaceRequestor* aCallbacks) {
+ FileRandomAccessStreamParams params;
+
+ if (NS_SUCCEEDED(DoPendingOpen())) {
+ MOZ_ASSERT(mFD);
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
+ MOZ_ASSERT(fd, "This should never be null!");
+
+ params.fileDescriptor() = FileDescriptor(fd);
+
+ Close();
+ } else {
+ NS_WARNING(
+ "This file has not been opened (or could not be opened). "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptor() = FileDescriptor();
+ }
+
+ int32_t behaviorFlags = mBehaviorFlags;
+
+ // The receiving process (or thread) is going to have an open file
+ // descriptor automatically so transferring this flag is meaningless.
+ behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
+
+ params.behaviorFlags() = behaviorFlags;
+
+ return params;
+}
+
+bool nsFileRandomAccessStream::Deserialize(
+ RandomAccessStreamParams& aStreamParams) {
+ MOZ_ASSERT(!mFD, "Already have a file descriptor?!");
+ MOZ_ASSERT(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
+
+ if (aStreamParams.type() !=
+ RandomAccessStreamParams::TFileRandomAccessStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const FileRandomAccessStreamParams& params =
+ aStreamParams.get_FileRandomAccessStreamParams();
+
+ const FileDescriptor& fd = params.fileDescriptor();
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFD = fileDesc;
+ mState = eOpened;
+ } else {
+ NS_WARNING("Received an invalid file descriptor!");
+ mState = eError;
+ mErrorValue = NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ mBehaviorFlags = params.behaviorFlags();
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsFileRandomAccessStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+ mState = eUnitialized;
+
+ if (ioFlags == -1) ioFlags = PR_RDWR;
+ if (perm <= 0) perm = 0;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileRandomAccessStream::DEFER_OPEN);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h
new file mode 100644
index 0000000000..d402bffb24
--- /dev/null
+++ b/netwerk/base/nsFileStreams.h
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileStreams_h__
+#define nsFileStreams_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRandomAccessStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsILineInputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsReadLine.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+} // namespace mozilla
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileStreamBase : public nsISeekableStream, public nsIFileMetadata {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSIFILEMETADATA
+
+ nsFileStreamBase() = default;
+
+ protected:
+ virtual ~nsFileStreamBase();
+
+ nsresult Close();
+ nsresult Available(uint64_t* aResult);
+ nsresult Read(char* aBuf, uint32_t aCount, uint32_t* aResult);
+ nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+ nsresult IsNonBlocking(bool* aNonBlocking);
+ nsresult Flush();
+ nsresult StreamStatus();
+ nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* result);
+ nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval);
+ nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+
+ PRFileDesc* mFD{nullptr};
+
+ /**
+ * Flags describing our behavior. See the IDL file for possible values.
+ */
+ int32_t mBehaviorFlags{0};
+
+ enum {
+ // This is the default value. It will be changed by Deserialize or Init.
+ eUnitialized,
+ // The opening has been deferred. See DEFER_OPEN.
+ eDeferredOpen,
+ // The file has been opened. mFD is not null.
+ eOpened,
+ // The file has been closed. mFD is null.
+ eClosed,
+ // Something bad happen in the Open() or in Deserialize(). The actual
+ // error value is stored in mErrorValue.
+ eError
+ } mState{eUnitialized};
+
+ struct OpenParams {
+ nsCOMPtr<nsIFile> localFile;
+ int32_t ioFlags = 0;
+ int32_t perm = 0;
+ };
+
+ /**
+ * Data we need to do an open.
+ */
+ OpenParams mOpenParams;
+
+ nsresult mErrorValue{NS_ERROR_FAILURE};
+
+ /**
+ * Prepares the data we need to open the file, and either does the open now
+ * by calling DoOpen(), or leaves it to be opened later by a call to
+ * DoPendingOpen().
+ */
+ nsresult MaybeOpen(nsIFile* aFile, int32_t aIoFlags, int32_t aPerm,
+ bool aDeferred);
+
+ /**
+ * Cleans up data prepared in MaybeOpen.
+ */
+ void CleanUpOpen();
+
+ /**
+ * Open the file. This is called either from MaybeOpen (during Init)
+ * or from DoPendingOpen (if DEFER_OPEN is used when initializing this
+ * stream). The default behavior of DoOpen is to open the file and save the
+ * file descriptor.
+ */
+ virtual nsresult DoOpen();
+
+ /**
+ * Based on mState, this method does the opening, return an error, or do
+ * nothing. If the return value is not NS_OK, please, return it back to the
+ * callee.
+ */
+ inline nsresult DoPendingOpen();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// nsFileInputStream is cloneable only on the parent process because only there
+// it can open the same file multiple times.
+
+class nsFileInputStream : public nsFileStreamBase,
+ public nsIFileInputStream,
+ public nsILineInputStream,
+ public nsIIPCSerializableInputStream,
+ public nsICloneableInputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEINPUTSTREAM
+ NS_DECL_NSILINEINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Tell(int64_t* aResult) override;
+ NS_IMETHOD Available(uint64_t* _retval) override;
+ NS_IMETHOD StreamStatus() override;
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount, _retval);
+ }
+ NS_IMETHOD IsNonBlocking(bool* _retval) override {
+ return nsFileStreamBase::IsNonBlocking(_retval);
+ }
+
+ // Overrided from nsFileStreamBase
+ NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override;
+
+ nsFileInputStream() : mLineBuffer(nullptr) {}
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ virtual ~nsFileInputStream() = default;
+
+ nsresult SeekInternal(int32_t aWhence, int64_t aOffset,
+ bool aClearBuf = true);
+
+ mozilla::UniquePtr<nsLineBuffer<char>> mLineBuffer;
+
+ /**
+ * The file being opened.
+ */
+ nsCOMPtr<nsIFile> mFile;
+ /**
+ * The IO flags passed to Init() for the file open.
+ */
+ int32_t mIOFlags{0};
+ /**
+ * The permissions passed to Init() for the file open.
+ */
+ int32_t mPerm{0};
+
+ /**
+ * Cached position for Tell for automatically reopening streams.
+ */
+ int64_t mCachedPosition{0};
+
+ protected:
+ /**
+ * Internal, called to open a file. Parameters are the same as their
+ * Init() analogues.
+ */
+ nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm);
+
+ bool IsCloneable() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileOutputStream : public nsFileStreamBase, public nsIFileOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEOUTPUTSTREAM
+ NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::)
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+ nsresult InitWithFileDescriptor(const mozilla::ipc::FileDescriptor& aFd);
+
+ protected:
+ virtual ~nsFileOutputStream() = default;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing is complete. This protects against incomplete writes
+ * due to the process or the thread being interrupted or crashed.
+ */
+class nsAtomicFileOutputStream : public nsFileOutputStream,
+ public nsISafeOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISAFEOUTPUTSTREAM
+
+ nsAtomicFileOutputStream() = default;
+
+ virtual nsresult DoOpen() override;
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t* result) override;
+ NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) override;
+
+ protected:
+ virtual ~nsAtomicFileOutputStream() = default;
+
+ nsCOMPtr<nsIFile> mTargetFile;
+ nsCOMPtr<nsIFile> mTempFile;
+
+ bool mTargetFileExists{true};
+ nsresult mWriteResult{NS_OK}; // Internally set in Write()
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing + flushing is complete. This protects against more
+ * classes of software/hardware errors than nsAtomicFileOutputStream,
+ * at the expense of being more costly to the disk, OS and battery.
+ */
+class nsSafeFileOutputStream : public nsAtomicFileOutputStream {
+ public:
+ NS_IMETHOD Finish() override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileRandomAccessStream : public nsFileStreamBase,
+ public nsIFileRandomAccessStream,
+ public nsIInputStream,
+ public nsIOutputStream {
+ public:
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSITELLABLESTREAM(nsFileStreamBase::)
+ NS_FORWARD_NSISEEKABLESTREAM(nsFileStreamBase::)
+ NS_DECL_NSIRANDOMACCESSSTREAM
+ NS_DECL_NSIFILERANDOMACCESSSTREAM
+ NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::)
+
+ // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods
+ // Close() and IsNonBlocking()
+ NS_IMETHOD Flush() override { return nsFileStreamBase::Flush(); }
+ NS_IMETHOD Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) override {
+ return nsFileStreamBase::Write(aBuf, aCount, _retval);
+ }
+ NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) override {
+ return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval);
+ }
+ NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount, _retval);
+ }
+
+ protected:
+ virtual ~nsFileRandomAccessStream() = default;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsFileStreams_h__
diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl
new file mode 100644
index 0000000000..9ba1dc73fa
--- /dev/null
+++ b/netwerk/base/nsIArrayBufferInputStream.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+%{C++
+#include "mozilla/UniquePtr.h"
+%}
+native Bytes(mozilla::UniquePtr<uint8_t[]>);
+
+/**
+ * nsIArrayBufferInputStream
+ *
+ * Provides scriptable methods for initializing a nsIInputStream
+ * implementation with an ArrayBuffer.
+ */
+[scriptable, builtinclass, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)]
+interface nsIArrayBufferInputStream : nsIInputStream
+{
+ /**
+ * SetData - assign an ArrayBuffer to the input stream.
+ *
+ * @param buffer - stream data
+ * @param byteOffset - stream data offset
+ * @param byteLen - stream data length
+ */
+ [binaryname(SetDataFromJS)]
+ void setData(in jsval buffer, in uint64_t byteOffset, in uint64_t byteLen);
+
+ /**
+ * SetData - assign data to the input stream.
+ *
+ * @param aBytes - stream data
+ * @param byteLen - stream data length
+ */
+ [noscript, nostdcall, binaryname(SetData)]
+ void setDataNative(in Bytes bytes, in uint64_t byteLen);
+};
diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl
new file mode 100644
index 0000000000..633fe72b6e
--- /dev/null
+++ b/netwerk/base/nsIAsyncStreamCopier.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+// You should prefer nsIAsyncStreamCopier2
+[scriptable, uuid(5a19ca27-e041-4aca-8287-eb248d4c50c0)]
+interface nsIAsyncStreamCopier : nsIRequest
+{
+ /**
+ * Initialize the stream copier.
+ *
+ * @param aSource
+ * contains the data to be copied.
+ * @param aSink
+ * specifies the destination for the data.
+ * @param aTarget
+ * specifies the thread on which the copy will occur. a null value
+ * is permitted and will cause the copy to occur on an unspecified
+ * background thread.
+ * @param aSourceBuffered
+ * true if aSource implements ReadSegments.
+ * @param aSinkBuffered
+ * true if aSink implements WriteSegments.
+ * @param aChunkSize
+ * specifies how many bytes to read/write at a time. this controls
+ * the granularity of the copying. it should match the segment size
+ * of the "buffered" streams involved.
+ * @param aCloseSource
+ * true if aSource should be closed after copying.
+ * @param aCloseSink
+ * true if aSink should be closed after copying.
+ *
+ * NOTE: at least one of the streams must be buffered. If you do not know
+ * whether your streams are buffered, you should use nsIAsyncStreamCopier2
+ * instead.
+ */
+ void init(in nsIInputStream aSource,
+ in nsIOutputStream aSink,
+ in nsIEventTarget aTarget,
+ in boolean aSourceBuffered,
+ in boolean aSinkBuffered,
+ in unsigned long aChunkSize,
+ in boolean aCloseSource,
+ in boolean aCloseSink);
+
+ /**
+ * asyncCopy triggers the start of the copy. The observer will be notified
+ * when the copy completes.
+ *
+ * @param aObserver
+ * receives notifications.
+ * @param aObserverContext
+ * passed to observer methods.
+ */
+ void asyncCopy(in nsIRequestObserver aObserver,
+ in nsISupports aObserverContext);
+};
diff --git a/netwerk/base/nsIAsyncStreamCopier2.idl b/netwerk/base/nsIAsyncStreamCopier2.idl
new file mode 100644
index 0000000000..7de793f51e
--- /dev/null
+++ b/netwerk/base/nsIAsyncStreamCopier2.idl
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+[scriptable, uuid(a5b2decf-4ede-4801-8b38-e5fe5db46bf2)]
+interface nsIAsyncStreamCopier2 : nsIRequest
+{
+ /**
+ * Initialize the stream copier.
+ *
+ * If neither the source nor the sink are buffered, buffering will
+ * be automatically added to the sink.
+ *
+ *
+ * @param aSource
+ * contains the data to be copied.
+ * @param aSink
+ * specifies the destination for the data.
+ * @param aTarget
+ * specifies the thread on which the copy will occur. a null value
+ * is permitted and will cause the copy to occur on an unspecified
+ * background thread.
+ * @param aChunkSize
+ * specifies how many bytes to read/write at a time. this controls
+ * the granularity of the copying. it should match the segment size
+ * of the "buffered" streams involved.
+ * @param aCloseSource
+ * true if aSource should be closed after copying (this is generally
+ * the desired behavior).
+ * @param aCloseSink
+ * true if aSink should be closed after copying (this is generally
+ * the desired behavior).
+ */
+ void init(in nsIInputStream aSource,
+ in nsIOutputStream aSink,
+ in nsIEventTarget aTarget,
+ in unsigned long aChunkSize,
+ in boolean aCloseSource,
+ in boolean aCloseSink);
+
+ /**
+ * asyncCopy triggers the start of the copy. The observer will be notified
+ * when the copy completes.
+ *
+ * @param aObserver
+ * receives notifications.
+ * @param aObserverContext
+ * passed to observer methods.
+ */
+ void asyncCopy(in nsIRequestObserver aObserver,
+ in nsISupports aObserverContext);
+};
diff --git a/netwerk/base/nsIAsyncVerifyRedirectCallback.idl b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl
new file mode 100644
index 0000000000..8c81a142f8
--- /dev/null
+++ b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(8d171460-a716-41f1-92be-8c659db39b45)]
+interface nsIAsyncVerifyRedirectCallback : nsISupports
+{
+ /**
+ * Complement to nsIChannelEventSink asynchronous callback. The result of
+ * the redirect decision is passed through this callback.
+ *
+ * @param result
+ * Result of the redirect veto decision. If FAILED the redirect has been
+ * vetoed. If SUCCEEDED the redirect has been allowed by all consumers.
+ */
+ void onRedirectVerifyCallback(in nsresult result);
+};
diff --git a/netwerk/base/nsIAuthInformation.idl b/netwerk/base/nsIAuthInformation.idl
new file mode 100644
index 0000000000..a9a74891b1
--- /dev/null
+++ b/netwerk/base/nsIAuthInformation.idl
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * A object that hold authentication information. The caller of
+ * nsIAuthPrompt2::promptUsernameAndPassword or
+ * nsIAuthPrompt2::promptPasswordAsync provides an object implementing this
+ * interface; the prompt implementation can then read the values here to prefill
+ * the dialog. After the user entered the authentication information, it should
+ * set the attributes of this object to indicate to the caller what was entered
+ * by the user.
+ */
+[scriptable, uuid(0d73639c-2a92-4518-9f92-28f71fea5f20)]
+interface nsIAuthInformation : nsISupports
+{
+ /** @name Flags */
+ /* @{ */
+ /**
+ * This dialog belongs to a network host.
+ */
+ const uint32_t AUTH_HOST = 1;
+
+ /**
+ * This dialog belongs to a proxy.
+ */
+ const uint32_t AUTH_PROXY = 2;
+
+ /**
+ * This dialog needs domain information. The user interface should show a
+ * domain field, prefilled with the domain attribute's value.
+ */
+ const uint32_t NEED_DOMAIN = 4;
+
+ /**
+ * This dialog only asks for password information. Authentication prompts
+ * SHOULD NOT show a username field. Attempts to change the username field
+ * will have no effect. nsIAuthPrompt2 implementations should, however, show
+ * its initial value to the user in some form. For example, a paragraph in
+ * the dialog might say "Please enter your password for user jsmith at
+ * server intranet".
+ *
+ * This flag is mutually exclusive with #NEED_DOMAIN.
+ */
+ const uint32_t ONLY_PASSWORD = 8;
+
+ /**
+ * We have already tried to log in for this channel
+ * (with auth values from a previous promptAuth call),
+ * but it failed, so we now ask the user to provide a new, correct login.
+ *
+ * @see also RFC 2616, Section 10.4.2
+ */
+ const uint32_t PREVIOUS_FAILED = 16;
+
+ /**
+ * A cross-origin sub-resource requests an authentication.
+ * The message presented to users must reflect that.
+ */
+ const uint32_t CROSS_ORIGIN_SUB_RESOURCE = 32;
+ /* @} */
+
+ /**
+ * Flags describing this dialog. A bitwise OR of the flag values
+ * above.
+ *
+ * It is possible that neither #AUTH_HOST nor #AUTH_PROXY are set.
+ *
+ * Auth prompts should ignore flags they don't understand; especially, they
+ * should not throw an exception because of an unsupported flag.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * The server-supplied realm of the authentication as defined in RFC 2617.
+ * Can be the empty string if the protocol does not support realms.
+ * Otherwise, this is a human-readable string like "Secret files".
+ */
+ readonly attribute AString realm;
+
+ /**
+ * The authentication scheme used for this request, if applicable. If the
+ * protocol for this authentication does not support schemes, this will be
+ * the empty string. Otherwise, this will be a string such as "basic" or
+ * "digest". This string will always be in lowercase.
+ */
+ readonly attribute AUTF8String authenticationScheme;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the username entered by
+ * the user.
+ * This field can only be changed if the #ONLY_PASSWORD flag is not set.
+ */
+ attribute AString username;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * The password should not be shown in clear.
+ * On return, this parameter should contain the password entered by
+ * the user.
+ */
+ attribute AString password;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the domain entered by
+ * the user.
+ * This attribute is only used if flags include #NEED_DOMAIN.
+ */
+ attribute AString domain;
+};
diff --git a/netwerk/base/nsIAuthModule.idl b/netwerk/base/nsIAuthModule.idl
new file mode 100644
index 0000000000..87985f2a57
--- /dev/null
+++ b/netwerk/base/nsIAuthModule.idl
@@ -0,0 +1,146 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+[uuid(6e35dbc0-49ef-4e2c-b1ea-b72ec64450a2)]
+interface nsIAuthModule : nsISupports
+{
+ /**
+ * Default behavior.
+ */
+ const unsigned long REQ_DEFAULT = 0;
+
+ /**
+ * Client and server will be authenticated.
+ */
+ const unsigned long REQ_MUTUAL_AUTH = (1 << 0);
+
+ /**
+ * The server is allowed to impersonate the client. The REQ_MUTUAL_AUTH
+ * flag may also need to be specified in order for this flag to take
+ * effect.
+ */
+ const unsigned long REQ_DELEGATE = (1 << 1);
+
+ /**
+ * The authentication is required for a proxy connection.
+ */
+ const unsigned long REQ_PROXY_AUTH = (1 << 2);
+
+ /**
+ * Flags used for telemetry.
+ */
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_PROXY = 0;
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_DIRECT = 1;
+ const unsigned long NTLM_MODULE_WIN_API_PROXY = 2;
+ const unsigned long NTLM_MODULE_WIN_API_DIRECT = 3;
+ const unsigned long NTLM_MODULE_GENERIC_PROXY = 4;
+ const unsigned long NTLM_MODULE_GENERIC_DIRECT = 5;
+ const unsigned long NTLM_MODULE_KERBEROS_PROXY = 6;
+ const unsigned long NTLM_MODULE_KERBEROS_DIRECT = 7;
+
+ /** Other flags may be defined in the future */
+
+ /**
+ * Called to initialize an auth module. The other methods cannot be called
+ * unless this method succeeds.
+ *
+ * @param aServiceName
+ * the service name, which may be null if not applicable (e.g., for
+ * NTLM, this parameter should be null).
+ * @param aServiceFlags
+ * a bitwise-or of the REQ_ flags defined above (pass REQ_DEFAULT
+ * for default behavior).
+ * @param aDomain
+ * the authentication domain, which may be null if not applicable.
+ * @param aUsername
+ * the user's login name
+ * @param aPassword
+ * the user's password
+ */
+ void init(in ACString aServiceName,
+ in unsigned long aServiceFlags,
+ in AString aDomain,
+ in AString aUsername,
+ in AString aPassword);
+
+ /**
+ * Called to get the next token in a sequence of authentication steps.
+ *
+ * @param aInToken
+ * A buffer containing the input token (e.g., a challenge from a
+ * server). This may be null.
+ * @param aInTokenLength
+ * The length of the input token.
+ * @param aOutToken
+ * If getNextToken succeeds, then aOutToken will point to a buffer
+ * to be sent in response to the server challenge. The length of
+ * this buffer is given by aOutTokenLength. The buffer at aOutToken
+ * must be recycled with a call to free.
+ * @param aOutTokenLength
+ * If getNextToken succeeds, then aOutTokenLength contains the
+ * length of the buffer (number of bytes) pointed to by aOutToken.
+ */
+ void getNextToken([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+ /**
+ * Once a security context has been established through calls to GetNextToken()
+ * it may be used to protect data exchanged between client and server. Calls
+ * to Wrap() are used to protect items of data to be sent to the server.
+ *
+ * @param aInToken
+ * A buffer containing the data to be sent to the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param confidential
+ * If set to true, Wrap() will encrypt the data, otherwise data will
+ * just be integrity protected (checksummed)
+ * @param aOutToken
+ * A buffer containing the resulting data to be sent to the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication
+ * mechanism does not support security layers.
+ */
+ void wrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ in boolean confidential,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+ /**
+ * Unwrap() is used to unpack, decrypt, and verify the checksums on data
+ * returned by a server when security layers are in use.
+ *
+ * @param aInToken
+ * A buffer containing the data received from the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param aOutToken
+ * A buffer containing the plaintext data from the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying
+ * authentication mechanism does not support security layers.
+ */
+ void unwrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+%{C++
+ /**
+ * Create a new instance of an auth module.
+ *
+ * @param aType
+ * The type of the auth module to be constructed.
+ */
+ static already_AddRefed<nsIAuthModule> CreateInstance(const char* aType);
+%}
+};
diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl
new file mode 100644
index 0000000000..fbbac6ee9f
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt.idl
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrompt;
+
+[scriptable, uuid(358089f9-ee4b-4711-82fd-bcd07fc62061)]
+interface nsIAuthPrompt : nsISupports
+{
+ const uint32_t SAVE_PASSWORD_NEVER = 0;
+ const uint32_t SAVE_PASSWORD_FOR_SESSION = 1;
+ const uint32_t SAVE_PASSWORD_PERMANENTLY = 2;
+
+ /**
+ * Puts up a text input dialog with OK and Cancel buttons.
+ * Note: prompt uses separate args for the "in" and "out" values of the
+ * input field, whereas the other functions use a single inout arg.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param defaultText The default text to display in the text input box.
+ * @param result The value entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ in wstring defaultText,
+ out wstring result);
+
+ /**
+ * Puts up a username/password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param user The username entered in the dialog.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring user,
+ inout wstring pwd);
+
+ /**
+ * Puts up an async username/password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param user The username entered in the dialog.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return promise resolving to true for OK, false for Cancel
+ */
+ Promise asyncPromptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring user,
+ inout wstring pwd);
+
+ /**
+ * Puts up a password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test. If a username is
+ * specified (http://user@site.com) it will be used
+ * when matching existing logins or saving new ones.
+ * If no username is specified, only password-only
+ * logins will be matched or saved.
+ * Note: if a username is specified, the username
+ * should be escaped.
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring pwd);
+
+ /**
+ * Puts up an async password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test. If a username is
+ * specified (http://user@site.com) it will be used
+ * when matching existing logins or saving new ones.
+ * If no username is specified, only password-only
+ * logins will be matched or saved.
+ * Note: if a username is specified, the username
+ * should be escaped.
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return promise resolving to true for OK, false for Cancel
+ */
+ Promise asyncPromptPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring pwd);
+};
diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl
new file mode 100644
index 0000000000..d94d50dfcc
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt2.idl
@@ -0,0 +1,104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthPromptCallback;
+interface nsIChannel;
+interface nsICancelable;
+interface nsIAuthInformation;
+
+/**
+ * An interface allowing to prompt for a username and password. This interface
+ * is usually acquired using getInterface on notification callbacks or similar.
+ * It can be used to prompt users for authentication information, either
+ * synchronously or asynchronously.
+ */
+[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)]
+interface nsIAuthPrompt2 : nsISupports
+{
+ /** @name Security Levels */
+ /* @{ */
+ /**
+ * The password will be sent unencrypted. No security provided.
+ */
+ const uint32_t LEVEL_NONE = 0;
+ /**
+ * Password will be sent encrypted, but the connection is otherwise
+ * insecure.
+ */
+ const uint32_t LEVEL_PW_ENCRYPTED = 1;
+ /**
+ * The connection, both for password and data, is secure.
+ */
+ const uint32_t LEVEL_SECURE = 2;
+ /* @} */
+
+ /**
+ * Requests a username and a password. Implementations will commonly show a
+ * dialog with a username and password field, depending on flags also a
+ * domain field.
+ *
+ * @param aChannel
+ * The channel that requires authentication.
+ * @param level
+ * One of the level constants from above. See there for descriptions
+ * of the levels.
+ * @param authInfo
+ * Authentication information object. The implementation should fill in
+ * this object with the information entered by the user before
+ * returning.
+ *
+ * @retval true
+ * Authentication can proceed using the values in the authInfo
+ * object.
+ * @retval false
+ * Authentication should be cancelled, usually because the user did
+ * not provide username/password.
+ *
+ * @note Exceptions thrown from this function will be treated like a
+ * return value of false.
+ * @deprecated use asyncPromptAuth
+ */
+ boolean promptAuth(in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+
+ /**
+ * Asynchronously prompt the user for a username and password.
+ * This has largely the same semantics as promptUsernameAndPassword(),
+ * but must return immediately after calling and return the entered
+ * data in a callback.
+ *
+ * If the user closes the dialog using a cancel button or similar,
+ * the callback's nsIAuthPromptCallback::onAuthCancelled method must be
+ * called.
+ * Calling nsICancelable::cancel on the returned object SHOULD close the
+ * dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided
+ * callback.
+ *
+ * This implementation may:
+ *
+ * 1) Coalesce identical prompts. This means prompts that are guaranteed to
+ * want the same auth information from the user. A single prompt will be
+ * shown; then the callbacks for all the coalesced prompts will be notified
+ * with the resulting auth information.
+ * 2) Serialize prompts that are all in the same "context" (this might mean
+ * application-wide, for a given window, or something else depending on
+ * the user interface) so that the user is not deluged with prompts.
+ *
+ * @throw
+ * This method may throw any exception when the prompt fails to queue e.g
+ * because of out-of-memory error. It must not throw when the prompt
+ * could already be potentially shown to the user. In that case information
+ * about the failure has to come through the callback. This way we
+ * prevent multiple dialogs shown to the user because consumer may fall
+ * back to synchronous prompt on synchronous failure of this method.
+ */
+ nsICancelable asyncPromptAuth(in nsIChannel aChannel,
+ in nsIAuthPromptCallback aCallback,
+ in nsISupports aContext,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+};
diff --git a/netwerk/base/nsIAuthPromptAdapterFactory.idl b/netwerk/base/nsIAuthPromptAdapterFactory.idl
new file mode 100644
index 0000000000..e763d47146
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptAdapterFactory.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthPrompt;
+interface nsIAuthPrompt2;
+
+/**
+ * An interface for wrapping nsIAuthPrompt interfaces to make
+ * them usable via an nsIAuthPrompt2 interface.
+ */
+[scriptable, uuid(60e46383-bb9a-4860-8962-80d9c5c05ddc)]
+interface nsIAuthPromptAdapterFactory : nsISupports
+{
+ /**
+ * Wrap an object implementing nsIAuthPrompt so that it's usable via
+ * nsIAuthPrompt2.
+ */
+ nsIAuthPrompt2 createAdapter(in nsIAuthPrompt aPrompt);
+};
diff --git a/netwerk/base/nsIAuthPromptCallback.idl b/netwerk/base/nsIAuthPromptCallback.idl
new file mode 100644
index 0000000000..27f89f04c0
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptCallback.idl
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthInformation;
+
+/**
+ * Interface for callback methods for the asynchronous nsIAuthPrompt2 method.
+ * Callers MUST call exactly one method if nsIAuthPrompt2::promptPasswordAsync
+ * returns successfully. They MUST NOT call any method on this interface before
+ * promptPasswordAsync returns.
+ */
+[scriptable, uuid(bdc387d7-2d29-4cac-92f1-dd75d786631d)]
+interface nsIAuthPromptCallback : nsISupports
+{
+ /**
+ * Authentication information is available.
+ *
+ * @param aContext
+ * The context as passed to promptPasswordAsync
+ * @param aAuthInfo
+ * Authentication information. Must be the same object that was passed
+ * to promptPasswordAsync.
+ *
+ * @note Any exceptions thrown from this method should be ignored.
+ */
+ void onAuthAvailable(in nsISupports aContext,
+ in nsIAuthInformation aAuthInfo);
+
+ /**
+ * Notification that the prompt was cancelled.
+ *
+ * @param aContext
+ * The context that was passed to promptPasswordAsync.
+ * @param userCancel
+ * If false, this prompt was cancelled by calling the
+ * the cancel method on the nsICancelable; otherwise,
+ * it was cancelled by the user.
+ */
+ void onAuthCancelled(in nsISupports aContext, in boolean userCancel);
+};
diff --git a/netwerk/base/nsIAuthPromptProvider.idl b/netwerk/base/nsIAuthPromptProvider.idl
new file mode 100644
index 0000000000..e8ff122ec2
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptProvider.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(bd9dc0fa-68ce-47d0-8859-6418c2ae8576)]
+interface nsIAuthPromptProvider : nsISupports
+{
+ /**
+ * Normal (non-proxy) prompt request.
+ */
+ const uint32_t PROMPT_NORMAL = 0;
+
+ /**
+ * Proxy auth request.
+ */
+ const uint32_t PROMPT_PROXY = 1;
+
+ /**
+ * Request a prompt interface for the given prompt reason;
+ * @throws NS_ERROR_NOT_AVAILABLE if no prompt is allowed or
+ * available for the given reason.
+ *
+ * @param aPromptReason The reason for the auth prompt;
+ * one of #PROMPT_NORMAL or #PROMPT_PROXY
+ * @param iid The desired interface, e.g.
+ * NS_GET_IID(nsIAuthPrompt2).
+ * @returns an nsIAuthPrompt2 interface, or throws NS_ERROR_NOT_AVAILABLE
+ */
+ void getAuthPrompt(in uint32_t aPromptReason, in nsIIDRef iid,
+ [iid_is(iid),retval] out nsQIResult result);
+};
diff --git a/netwerk/base/nsIBackgroundFileSaver.idl b/netwerk/base/nsIBackgroundFileSaver.idl
new file mode 100644
index 0000000000..0b26852c28
--- /dev/null
+++ b/netwerk/base/nsIBackgroundFileSaver.idl
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIBackgroundFileSaverObserver;
+interface nsIFile;
+
+/**
+ * Allows saving data to a file, while handling all the input/output on a
+ * background thread, including the initial file name assignment and any
+ * subsequent renaming of the target file.
+ *
+ * This interface is designed for file downloads. Generally, they start in the
+ * temporary directory, while the user is selecting the final name. Then, they
+ * are moved to the chosen target directory with a ".part" extension appended to
+ * the file name. Finally, they are renamed when the download is completed.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIStreamListener
+ * allow data to be fed using an implementation of OnDataAvailable that never
+ * blocks the calling thread. They suspend the request that drives the stream
+ * listener in case too much data is being fed, and resume it when the data has
+ * been written. Calling OnStopRequest does not necessarily close the target
+ * file, and the Finish method must be called to complete the operation.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIAsyncOutputStream
+ * allow data to be fed directly to the non-blocking output stream, that however
+ * may return NS_BASE_STREAM_WOULD_BLOCK in case too much data is being fed.
+ * Closing the output stream does not necessarily close the target file, and the
+ * Finish method must be called to complete the operation.
+ *
+ * @remarks Implementations may require the consumer to always call Finish. If
+ * the object reference is released without calling Finish, a memory
+ * leak may occur, and the target file might be kept locked. All
+ * public methods of the interface may only be called from the main
+ * thread.
+ */
+[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)]
+interface nsIBackgroundFileSaver : nsISupports
+{
+ /**
+ * This observer receives notifications when the target file name changes and
+ * when the operation completes, successfully or not.
+ *
+ * @remarks A strong reference to the observer is held. Notification events
+ * are dispatched to the thread that created the object that
+ * implements nsIBackgroundFileSaver.
+ */
+ attribute nsIBackgroundFileSaverObserver observer;
+
+ /**
+ * An Array of Array of Array of bytes, representing a chain of
+ * X.509 certificates, the last of which signed the downloaded file.
+ * Each list may belong to a different signer and contain certificates
+ * all the way up to the root.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case this is called before the onSaveComplete method has been
+ * called to notify success, or enableSignatureInfo has not been
+ * called.
+ */
+ readonly attribute Array<Array<Array<uint8_t> > > signatureInfo;
+
+ /**
+ * The SHA-256 hash, in raw bytes, associated with the data that was saved.
+ *
+ * In case the enableAppend method has been called, the hash computation
+ * includes the contents of the existing file, if any.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case the enableSha256 method has not been called, or before the
+ * onSaveComplete method has been called to notify success.
+ */
+ readonly attribute ACString sha256Hash;
+
+ /**
+ * Instructs the component to compute the signatureInfo of the target file,
+ * and make it available in the signatureInfo property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSignatureInfo();
+
+ /**
+ * Instructs the component to compute the SHA-256 hash of the target file, and
+ * make it available in the sha256Hash property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSha256();
+
+ /**
+ * Instructs the component to append data to the initial target file, that
+ * will be specified by the first call to the setTarget method, instead of
+ * overwriting the file.
+ *
+ * If the initial target file does not exist, this method has no effect.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableAppend();
+
+ /**
+ * Sets the name of the output file to be written. The target can be changed
+ * after data has already been fed, in which case the existing file will be
+ * moved to the new destination.
+ *
+ * In case the specified file already exists, and this method is called for
+ * the first time, the file may be either overwritten or appended to, based on
+ * whether the enableAppend method was called. Subsequent calls always
+ * overwrite the specified target file with the previously saved data.
+ *
+ * No file will be written until this function is called at least once. It's
+ * recommended not to feed any data until the output file is set.
+ *
+ * If an input/output error occurs with the specified file, the save operation
+ * fails. Failure is notified asynchronously through the observer.
+ *
+ * @param aTarget
+ * New output file to be written.
+ * @param aKeepPartial
+ * Indicates whether aFile should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled. This is
+ * generally set for downloads that use temporary ".part" files.
+ */
+ void setTarget(in nsIFile aTarget, in bool aKeepPartial);
+
+ /**
+ * Terminates access to the output file, then notifies the observer with the
+ * specified status code. A failure code will force the operation to be
+ * canceled, in which case the output file will be deleted if requested.
+ *
+ * This forces the involved streams to be closed, thus no more data should be
+ * fed to the component after this method has been called.
+ *
+ * This is the last method that should be called on this object, and the
+ * target file name cannot be changed anymore after this method has been
+ * called. Conversely, before calling this method, the file can still be
+ * renamed even if all the data has been fed.
+ *
+ * @param aStatus
+ * Result code that determines whether the operation should succeed or
+ * be canceled, and is notified to the observer. If the operation
+ * fails meanwhile for other reasons, or the observer has been already
+ * notified of completion, this status code is ignored.
+ */
+ void finish(in nsresult aStatus);
+};
+
+[scriptable, uuid(ee7058c3-6e54-4411-b76b-3ce87b76fcb6)]
+interface nsIBackgroundFileSaverObserver : nsISupports
+{
+ /**
+ * Called when the name of the output file has been determined. This function
+ * may be called more than once if the target file is renamed while saving.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aTarget
+ * Name of the file that is being written.
+ */
+ void onTargetChange(in nsIBackgroundFileSaver aSaver, in nsIFile aTarget);
+
+ /**
+ * Called when the operation completed, and the target file has been closed.
+ * If the operation succeeded, the target file is ready to be used, otherwise
+ * it might have been already deleted.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aStatus
+ * Result code that determines whether the operation succeeded or
+ * failed, as well as the failure reason.
+ */
+ void onSaveComplete(in nsIBackgroundFileSaver aSaver, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl
new file mode 100644
index 0000000000..46f0112900
--- /dev/null
+++ b/netwerk/base/nsIBufferedStreams.idl
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+
+/**
+ * An input stream that reads ahead and keeps a buffer coming from another input
+ * stream so that fewer accesses to the underlying stream are necessary.
+ */
+[scriptable, builtinclass, uuid(616f5b48-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedInputStream : nsIInputStream
+{
+ /**
+ * @param fillFromStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIInputStream fillFromStream,
+ in unsigned long bufferSize);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIInputStream data;
+};
+
+/**
+ * An output stream that stores up data to write out to another output stream
+ * and does the entire write only when the buffer is full, so that fewer writes
+ * to the underlying output stream are necessary.
+ */
+[scriptable, builtinclass, uuid(6476378a-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedOutputStream : nsIOutputStream
+{
+ /**
+ * @param sinkToStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIOutputStream sinkToStream,
+ in unsigned long bufferSize);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIOutputStream data;
+};
diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl
new file mode 100644
index 0000000000..4a10126d69
--- /dev/null
+++ b/netwerk/base/nsIByteRangeRequest.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(C1B1F426-7E83-4759-9F88-0E1B17F49366)]
+interface nsIByteRangeRequest : nsISupports
+{
+ /**
+ * Returns true IFF this request is a byte range request, otherwise it
+ * returns false (This is effectively the same as checking to see if
+ * |startRequest| is zero and |endRange| is the content length.)
+ */
+ readonly attribute boolean isByteRangeRequest;
+
+ /**
+ * Absolute start position in remote file for this request.
+ */
+ readonly attribute long long startRange;
+
+ /**
+ * Absolute end postion in remote file for this request
+ */
+ readonly attribute long long endRange;
+};
diff --git a/netwerk/base/nsICacheInfoChannel.idl b/netwerk/base/nsICacheInfoChannel.idl
new file mode 100644
index 0000000000..b7bb44093c
--- /dev/null
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -0,0 +1,207 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAsyncOutputStream;
+interface nsIInputStream;
+
+%{C++
+#include "nsTArray.h"
+namespace mozilla {
+namespace net {
+class PreferredAlternativeDataTypeParams;
+}
+} // namespace mozilla
+%}
+
+[ref] native ConstPreferredAlternativeDataTypeArray(const nsTArray<mozilla::net::PreferredAlternativeDataTypeParams>);
+
+[scriptable, uuid(1fb8ccf2-5fa5-45ec-bc57-8c8022a5d0d3)]
+interface nsIInputStreamReceiver : nsISupports
+{
+ void onInputStreamReady(in nsIInputStream aStream);
+};
+
+[scriptable, builtinclass, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)]
+interface nsICacheInfoChannel : nsISupports
+{
+ /**
+ * Get the number of times the cache entry has been opened. This attribute is
+ * equivalent to nsICachingChannel.cacheToken.fetchCount.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the cache entry or the alternate data
+ * cache entry cannot be read.
+ */
+ readonly attribute uint32_t cacheTokenFetchCount;
+
+ /**
+ * Get expiration time from cache token. This attribute is equivalent to
+ * nsICachingChannel.cacheToken.expirationTime.
+ */
+ readonly attribute uint32_t cacheTokenExpirationTime;
+
+ /**
+ * TRUE if this channel's data is being loaded from the cache. This value
+ * is undefined before the channel fires its OnStartRequest notification
+ * and after the channel fires its OnStopRequest notification.
+ */
+ boolean isFromCache();
+
+ /**
+ * Returns true if the channel raced the cache and network requests.
+ * In order to determine if the response is coming from the cache or the
+ * network, the consumer can check isFromCache().
+ * The method can only be called after the channel fires its OnStartRequest
+ * notification.
+ */
+ boolean isRacing();
+
+ /**
+ * The unique ID of the corresponding nsICacheEntry from which the response is
+ * retrieved. By comparing the returned value, we can judge whether the data
+ * of two distinct nsICacheInfoChannels is from the same nsICacheEntry. This
+ * scenario could be useful when verifying whether the alternative data from
+ * one nsICacheInfochannel matches the main data from another one.
+ *
+ * Note: NS_ERROR_NOT_AVAILABLE is thrown when a nsICacheInfoChannel has no
+ * valid corresponding nsICacheEntry.
+ */
+ uint64_t getCacheEntryId();
+
+ /**
+ * Set/get the cache key. This integer uniquely identifies the data in
+ * the cache for this channel.
+ *
+ * A cache key retrieved from a particular instance of nsICacheInfoChannel
+ * could be set on another instance of nsICacheInfoChannel provided the
+ * underlying implementations are compatible and provided the new
+ * channel instance was created with the same URI. The implementation of
+ * nsICacheInfoChannel would be expected to use the cache entry identified
+ * by the cache token. Depending on the value of nsIRequest::loadFlags,
+ * the cache entry may be validated, overwritten, or simply read.
+ *
+ * The cache key may be 0 indicating that the URI of the channel is
+ * sufficient to locate the same cache entry. Setting a 0 cache key
+ * is likewise valid.
+ */
+ attribute unsigned long cacheKey;
+
+ /**
+ * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set,
+ * but without affecting the loads for the entire loadGroup in case of this
+ * channel being the default load group's channel.
+ */
+ attribute boolean allowStaleCacheContent;
+
+ /**
+ * Tells the priority for LOAD_CACHE is raised over LOAD_BYPASS_CACHE or
+ * LOAD_BYPASS_LOCAL_CACHE in case those flags are set at the same time.
+ */
+ attribute boolean preferCacheLoadOverBypass;
+
+ cenum PreferredAlternativeDataDeliveryType : 8 {
+ /**
+ * Do not deliver alternative data stream.
+ */
+ NONE = 0,
+
+ /**
+ * Deliver alternative data stream upon additional request.
+ */
+ ASYNC = 1,
+
+ /**
+ * Deliver alternative data stream during IPC parent/child serialization.
+ */
+ SERIALIZE = 2,
+ };
+
+ /**
+ * Tells the channel to be force validated during soft reload.
+ */
+ attribute boolean forceValidateCacheContent;
+
+ /**
+ * Calling this method instructs the channel to serve the alternative data
+ * if that was previously saved in the cache, otherwise it will serve the
+ * real data.
+ * @param type
+ * a string identifying the alt-data format
+ * @param contentType
+ * the contentType for which the preference applies.
+ * an empty contentType means the preference applies for ANY contentType
+ * @param deliverAltData
+ * if false, also if alt-data is available, the channel will deliver
+ * the original data.
+ *
+ * The method may be called several times, with different type and contentType.
+ *
+ * Must be called before AsyncOpen.
+ */
+ void preferAlternativeDataType(in ACString type, in ACString contentType,
+ in nsICacheInfoChannel_PreferredAlternativeDataDeliveryType deliverAltData);
+
+ /**
+ * Get the preferred alternative data type set by preferAlternativeDataType().
+ * The returned types stand for the desired data type instead of the type of the
+ * information retrieved from the network stack.
+ */
+ [noscript, notxpcom, nostdcall]
+ ConstPreferredAlternativeDataTypeArray preferredAlternativeDataTypes();
+
+ /**
+ * Holds the type of the alternative data representation that the channel
+ * is returning.
+ * Is empty string if no alternative data representation was requested, or
+ * if the requested representation wasn't found in the cache.
+ * Can only be called during or after OnStartRequest.
+ */
+ readonly attribute ACString alternativeDataType;
+
+ /**
+ * If preferAlternativeDataType() has been called passing deliverAltData
+ * equal to false, this attribute will expose the alt-data inputStream if
+ * avaiable.
+ */
+ readonly attribute nsIInputStream alternativeDataInputStream;
+
+ /**
+ * Sometimes when the channel is delivering alt-data, we may want to somehow
+ * access the original content too. This method asynchronously opens the
+ * input stream and delivers it to the receiver.
+ */
+ void getOriginalInputStream(in nsIInputStreamReceiver aReceiver);
+
+ /**
+ * Opens and returns an output stream that a consumer may use to save an
+ * alternate representation of the data.
+ * Must be called after the OnStopRequest that delivered the real data.
+ * The consumer may choose to replace the saved alt representation.
+ * Opening the output stream will fail if there are any open input streams
+ * reading the already saved alt representation. After successfully opening
+ * an output stream, if there is an error before the entire alt data can be
+ * written successfully, the client must signal failure by passing an error
+ * code to CloseWithStatus().
+ *
+ * @param type
+ * type of the alternative data representation
+ * @param predictedSize
+ * Predicted size of the data that will be written. It's used to decide
+ * whether the resulting entry would exceed size limit, in which case
+ * an error is thrown. If the size isn't known in advance, -1 should be
+ * passed.
+ */
+ nsIAsyncOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize);
+};
+
+%{ C++
+namespace mozilla {
+namespace net {
+
+using PreferredAlternativeDataDeliveryTypeIPC = nsICacheInfoChannel::PreferredAlternativeDataDeliveryType;
+
+}
+} // namespace mozilla
+%}
diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl
new file mode 100644
index 0000000000..3623423ff7
--- /dev/null
+++ b/netwerk/base/nsICachingChannel.idl
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICacheInfoChannel.idl"
+
+interface nsIFile;
+
+/**
+ * A channel may optionally implement this interface to allow clients
+ * to affect its behavior with respect to how it uses the cache service.
+ *
+ * This interface provides:
+ * 1) Support for "stream as file" semantics (for JAR and plugins).
+ * 2) Support for "pinning" cached data in the cache (for printing and save-as).
+ * 3) Support for uniquely identifying cached data in cases when the URL
+ * is insufficient (e.g., HTTP form submission).
+ */
+[scriptable, builtinclass, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)]
+interface nsICachingChannel : nsICacheInfoChannel
+{
+ /**
+ * Set/get the cache token... uniquely identifies the data in the cache.
+ * Holding a reference to this token prevents the cached data from being
+ * removed.
+ *
+ * A cache token retrieved from a particular instance of nsICachingChannel
+ * could be set on another instance of nsICachingChannel provided the
+ * underlying implementations are compatible. The implementation of
+ * nsICachingChannel would be expected to only read from the cache entry
+ * identified by the cache token and not try to validate it.
+ *
+ * The cache token can be QI'd to a nsICacheEntryInfo if more detail
+ * about the cache entry is needed (e.g., expiration time).
+ */
+ attribute nsISupports cacheToken;
+
+ /**
+ * Instructs the channel to only store the metadata of the entry, and not
+ * the content. When reading an existing entry, this automatically sets
+ * LOAD_ONLY_IF_MODIFIED flag.
+ * Must be called before asyncOpen().
+ */
+ attribute boolean cacheOnlyMetadata;
+
+ /**
+ * Tells the channel to use the pinning storage.
+ */
+ attribute boolean pin;
+
+ /**
+ * Overrides cache validation for a time specified in seconds.
+ *
+ * @param aSecondsToTheFuture
+ *
+ */
+ void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture);
+
+ /**************************************************************************
+ * Caching channel specific load flags:
+ */
+
+ /**
+ * This load flag inhibits fetching from the net. An error of
+ * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's
+ * onStopRequest if network IO is necessary to complete the request.
+ *
+ * This flag can be used to find out whether fetching this URL would
+ * cause validation of the cache entry via the network.
+ *
+ * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all
+ * loads to fail.
+ */
+ const unsigned long LOAD_NO_NETWORK_IO = 1 << 26;
+
+ /**
+ * This load flag causes the local cache to be skipped when fetching a
+ * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load
+ * (i.e., it does not affect proxy caches).
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28;
+
+ /**
+ * This load flag causes the local cache to be skipped if the request
+ * would otherwise block waiting to access the cache.
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29;
+
+ /**
+ * This load flag inhibits fetching from the net if the data in the cache
+ * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent
+ * to the listener's onStopRequest in this case. This flag is set
+ * automatically when the application is offline.
+ */
+ const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30;
+
+ /**
+ * This load flag controls what happens when a document would be loaded
+ * from the cache to satisfy a call to AsyncOpen. If this attribute is
+ * set to TRUE, then the document will not be loaded from the cache. A
+ * stream listener can check nsICachingChannel::isFromCache to determine
+ * if the AsyncOpen will actually result in data being streamed.
+ *
+ * If this flag has been set, and the request can be satisfied via the
+ * cache, then the OnDataAvailable events will be skipped. The listener
+ * will only see OnStartRequest followed by OnStopRequest.
+ */
+ const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31;
+};
diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl
new file mode 100644
index 0000000000..c558dc6e27
--- /dev/null
+++ b/netwerk/base/nsICancelable.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface provides a means to cancel an operation that is in progress.
+ */
+[scriptable, uuid(d94ac0a0-bb18-46b8-844e-84159064b0bd)]
+interface nsICancelable : nsISupports
+{
+ /**
+ * Call this method to request that this object abort whatever operation it
+ * may be performing.
+ *
+ * @param aReason
+ * Pass a failure code to indicate the reason why this operation is
+ * being canceled. It is an error to pass a success code.
+ */
+ void cancel(in nsresult aReason);
+};
diff --git a/netwerk/base/nsICaptivePortalService.idl b/netwerk/base/nsICaptivePortalService.idl
new file mode 100644
index 0000000000..e4867765d7
--- /dev/null
+++ b/netwerk/base/nsICaptivePortalService.idl
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)]
+interface nsICaptivePortalServiceCallback : nsISupports
+{
+ /**
+ * Invoke callbacks after captive portal detection finished.
+ */
+ void complete(in bool success, in nsresult error);
+};
+
+/**
+ * Service used for captive portal detection.
+ * The service is only active in the main process. It is also available in the
+ * content process, but only to mirror the captive portal state from the main
+ * process.
+ */
+[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)]
+interface nsICaptivePortalService : nsISupports
+{
+ const long UNKNOWN = 0;
+ const long NOT_CAPTIVE = 1;
+ const long UNLOCKED_PORTAL = 2;
+ const long LOCKED_PORTAL = 3;
+
+ /**
+ * Called from XPCOM to trigger a captive portal recheck.
+ * A network request will only be performed if no other checks are currently
+ * ongoing.
+ * Will not do anything if called in the content process.
+ */
+ void recheckCaptivePortal();
+
+ /**
+ * Returns the state of the captive portal.
+ */
+ readonly attribute long state;
+
+%{C++
+ int32_t State() {
+ int32_t result = nsICaptivePortalService::UNKNOWN;
+ GetState(&result);
+ return result;
+ }
+%}
+
+ /**
+ * Returns the time difference between NOW and the last time a request was
+ * completed in milliseconds.
+ */
+ readonly attribute unsigned long long lastChecked;
+};
+
+%{C++
+/**
+ * This observer notification will be emitted when the captive portal state
+ * changes. After receiving it, the ContentParent will send an IPC message
+ * to the ContentChild, which will set the state in the captive portal service
+ * in the child.
+ */
+#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state"
+
+/**
+ * This notification will be emitted when the captive portal service has
+ * determined that we can connect to the internet.
+ * The service will pass either "captive" if there is an unlocked captive portal
+ * present, or "clear" if no captive portal was detected.
+ * Note: this notification only gets sent in the parent process.
+ */
+#define NS_CAPTIVE_PORTAL_CONNECTIVITY "network:captive-portal-connectivity"
+
+/**
+ * Similar to NS_CAPTIVE_PORTAL_CONNECTIVITY but only gets dispatched when
+ * the connectivity changes from UNKNOWN to NOT_CAPTIVE or from LOCKED_PORTAL
+ * to UNLOCKED_PORTAL.
+ */
+#define NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED "network:captive-portal-connectivity-changed"
+
+%}
diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl
new file mode 100644
index 0000000000..2269c4faa8
--- /dev/null
+++ b/netwerk/base/nsIChannel.idl
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+#include "nsILoadInfo.idl"
+
+interface nsIInputStream;
+interface nsIInterfaceRequestor;
+interface nsIStreamListener;
+interface nsITransportSecurityInfo;
+interface nsIURI;
+
+%{C++
+#include "nsCOMPtr.h"
+%}
+
+/**
+ * The nsIChannel interface allows clients to construct "GET" requests for
+ * specific protocols, and manage them in a uniform way. Once a channel is
+ * created (via nsIIOService::newChannel), parameters for that request may
+ * be set by using the channel attributes, or by QI'ing to a subclass of
+ * nsIChannel for protocol-specific parameters. Then, the URI can be fetched
+ * by calling nsIChannel::open or nsIChannel::asyncOpen.
+ *
+ * After a request has been completed, the channel is still valid for accessing
+ * protocol-specific results. For example, QI'ing to nsIHttpChannel allows
+ * response headers to be retrieved for the corresponding http transaction.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)]
+interface nsIChannel : nsIRequest
+{
+ /**
+ * The original URI used to construct the channel. This is used in
+ * the case of a redirect or URI "resolution" (e.g. resolving a
+ * resource: URI to a file: URI) so that the original pre-redirect
+ * URI can still be obtained. This is never null. Attempts to
+ * set it to null must throw.
+ *
+ * NOTE: this is distinctly different from the http Referer (referring URI),
+ * which is typically the page that contained the original URI (accessible
+ * from nsIHttpChannel).
+ *
+ * NOTE: originalURI isn't yet set on the new channel when
+ * asyncOnChannelRedirect is called.
+ */
+ attribute nsIURI originalURI;
+
+ /**
+ * The URI corresponding to the channel. Its value is immutable.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The owner, corresponding to the entity that is responsible for this
+ * channel. Used by the security manager to grant or deny privileges to
+ * mobile code loaded from this channel.
+ *
+ * NOTE: this is a strong reference to the owner, so if the owner is also
+ * holding a strong reference to the channel, care must be taken to
+ * explicitly drop its reference to the channel.
+ */
+ attribute nsISupports owner;
+
+ /**
+ * The notification callbacks for the channel. This is set by clients, who
+ * wish to provide a means to receive progress, status and protocol-specific
+ * notifications. If this value is NULL, the channel implementation may use
+ * the notification callbacks from its load group. The channel may also
+ * query the notification callbacks from its load group if its notification
+ * callbacks do not supply the requested interface.
+ *
+ * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt,
+ * and nsIAuthPrompt/nsIAuthPrompt2.
+ *
+ * When the channel is done, it must not continue holding references to
+ * this object.
+ *
+ * NOTE: A channel implementation should take care when "caching" an
+ * interface pointer queried from its notification callbacks. If the
+ * notification callbacks are changed, then a cached interface pointer may
+ * become invalid and may therefore need to be re-queried.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any) corresponding to the
+ * channel.
+ *
+ * NOTE: In some circumstances TLS information is propagated onto
+ * non-nsIHttpChannel objects to indicate that their contents were likely
+ * delivered over TLS all the same.
+ *
+ * FIXME(bz, bug 1528449) is that still true now that
+ * document.open() doesn't do this?
+ */
+ readonly attribute nsITransportSecurityInfo securityInfo;
+
+ /**
+ * The MIME type of the channel's content if available.
+ *
+ * NOTE: the content type can often be wrongly specified (e.g., wrong file
+ * extension, wrong MIME type, wrong document type stored on a server, etc.),
+ * and the caller most likely wants to verify with the actual data.
+ *
+ * Setting contentType before the channel has been opened provides a hint
+ * to the channel as to what the MIME type is. The channel may ignore this
+ * hint in deciding on the actual MIME type that it will report.
+ *
+ * Setting contentType after onStartRequest has been fired or after open()
+ * is called will override the type determined by the channel.
+ *
+ * Setting contentType between the time that asyncOpen() is called and the
+ * time when onStartRequest is fired has undefined behavior at this time.
+ *
+ * The value of the contentType attribute is a lowercase string. A value
+ * assigned to this attribute will be parsed and normalized as follows:
+ * 1- any parameters (delimited with a ';') will be stripped.
+ * 2- if a charset parameter is given, then its value will replace the
+ * the contentCharset attribute of the channel.
+ * 3- the stripped contentType will be lowercased.
+ * Any implementation of nsIChannel must follow these rules.
+ */
+ attribute ACString contentType;
+
+ /**
+ * The character set of the channel's content if available and if applicable.
+ * This attribute only applies to textual data.
+ *
+ * The value of the contentCharset attribute is a mixedcase string.
+ */
+ attribute ACString contentCharset;
+
+ /**
+ * The length of the data associated with the channel if available. A value
+ * of -1 indicates that the content length is unknown. Note that this is a
+ * 64-bit value and obsoletes the "content-length" property used on some
+ * channels.
+ */
+ attribute int64_t contentLength;
+
+ /**
+ * Synchronously open the channel.
+ *
+ * @return blocking input stream to the channel's data.
+ *
+ * NOTE: nsIChannel implementations are not required to implement this
+ * method. Moreover, since this method may block the calling thread, it
+ * should not be called on a thread that processes UI events. Like any
+ * other nsIChannel method it must not be called on any thread other
+ * than the XPCOM main thread.
+ *
+ * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel
+ * is reopened.
+ */
+ nsIInputStream open();
+
+ /**
+ * Asynchronously open this channel. Data is fed to the specified stream
+ * listener as it becomes available. The stream listener's methods are
+ * called on the thread that calls asyncOpen and are not called until
+ * after asyncOpen returns. If asyncOpen returns successfully, the
+ * channel promises to call at least onStartRequest and onStopRequest.
+ *
+ * If the nsIRequest object passed to the stream listener's methods is not
+ * this channel, an appropriate onChannelRedirect notification needs to be
+ * sent to the notification callbacks before onStartRequest is called.
+ * Once onStartRequest is called, all following method calls on aListener
+ * will get the request that was passed to onStartRequest.
+ *
+ * If the channel's and loadgroup's notification callbacks do not provide
+ * an nsIChannelEventSink when onChannelRedirect would be called, that's
+ * equivalent to having called onChannelRedirect.
+ *
+ * If asyncOpen returns successfully, the channel is responsible for
+ * keeping itself alive until it has called onStopRequest on aListener or
+ * called onChannelRedirect.
+ *
+ * Implementations are allowed to synchronously add themselves to the
+ * associated load group (if any).
+ *
+ * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the
+ * channel is reopened.
+ * NOTE: Implementations should throw an error if the channel has been
+ * cancelled prior asyncOpen being called.
+ *
+ * @param aListener the nsIStreamListener implementation
+ * @see nsIChannelEventSink for onChannelRedirect
+ */
+ void asyncOpen(in nsIStreamListener aListener);
+
+ /**
+ * True if the channel has been canceled.
+ */
+ [must_use] readonly attribute boolean canceled;
+
+ /**************************************************************************
+ * Channel specific load flags:
+ *
+ * Bits 16-31 are reserved for future use by this interface or one of its
+ * derivatives (e.g., see nsICachingChannel).
+ */
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to a document URI.
+ * While setting this flag is sufficient to mark a channel as a document
+ * load, _checking_ whether the channel is a document load requires the use
+ * of the new channel.isDocument
+ */
+ const unsigned long LOAD_DOCUMENT_URI = 1 << 16;
+
+ /**
+ * If the end consumer for this load has been retargeted after discovering
+ * its content, this flag will be set:
+ */
+ const unsigned long LOAD_RETARGETED_DOCUMENT_URI = 1 << 17;
+
+ /**
+ * This flag is set to indicate that this channel is replacing another
+ * channel. This means that:
+ *
+ * 1) the stream listener this channel will be notifying was initially
+ * passed to the asyncOpen method of some other channel
+ *
+ * and
+ *
+ * 2) this channel's URI is a better identifier of the resource being
+ * accessed than this channel's originalURI.
+ *
+ * This flag can be set, for example, for redirects or for cases when a
+ * single channel has multiple parts to it (and thus can follow
+ * onStopRequest with another onStartRequest/onStopRequest pair, each pair
+ * for a different request).
+ */
+ const unsigned long LOAD_REPLACE = 1 << 18;
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to an initial document URI load (e.g., link click).
+ */
+ const unsigned long LOAD_INITIAL_DOCUMENT_URI = 1 << 19;
+
+ /**
+ * Set (e.g., by the URILoader) to indicate whether or not the end consumer
+ * for this load has been determined.
+ */
+ const unsigned long LOAD_TARGETED = 1 << 20;
+
+ /**
+ * If this flag is set, the channel should call the content sniffers as
+ * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY.
+ *
+ * Note: Channels may ignore this flag; however, new channel implementations
+ * should only do so with good reason.
+ */
+ const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21;
+
+ /**
+ * This flag tells the channel to bypass URL classifier service check
+ * when opening the channel.
+ */
+ const unsigned long LOAD_BYPASS_URL_CLASSIFIER = 1 << 22;
+
+ /**
+ * If this flag is set, the media-type content sniffer will be allowed
+ * to override any server-set content-type. Otherwise it will only
+ * be allowed to override "no content type" and application/octet-stream.
+ */
+ const unsigned long LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE = 1 << 23;
+
+ /**
+ * Set to let explicitely provided credentials be used over credentials
+ * we have cached previously. In some situations like form login using HTTP
+ * auth via XMLHttpRequest we need to let consumers override the cached
+ * credentials explicitely. For form login 403 response instead of 401 is
+ * usually used to prevent an auth dialog. But any code other then 401/7
+ * will leave original credentials in the cache and there is then no way
+ * to override them for the same user name.
+ */
+ const unsigned long LOAD_EXPLICIT_CREDENTIALS = 1 << 24;
+
+ /**
+ * Set to force bypass of any service worker interception of the channel.
+ */
+ const unsigned long LOAD_BYPASS_SERVICE_WORKER = 1 << 25;
+
+ // nsICachingChannel load flags begin at bit 26.
+
+ /**
+ * Access to the type implied or stated by the Content-Disposition header
+ * if available and if applicable. This allows determining inline versus
+ * attachment.
+ *
+ * Setting contentDisposition provides a hint to the channel about the
+ * disposition. If the hint is DISPOSITION_ATTACHMENT and a normal
+ * Content-Disposition header is present, the hinted value will always be
+ * used. If the hint is DISPOSITION_FORCE_INLINE then the disposition is
+ * inline and the header is not used. The value from Content-Disposition
+ * header is only used when the hinted value is not DISPOSITION_INLINE or
+ * DISPOSITION_FORCE_INLINE.
+ * If the header is missing the hinted value will be used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty, and return
+ * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present.
+ */
+ attribute unsigned long contentDisposition;
+ const unsigned long DISPOSITION_INLINE = 0;
+ const unsigned long DISPOSITION_ATTACHMENT = 1;
+ const unsigned long DISPOSITION_FORCE_INLINE = 2;
+
+ /**
+ * Access to the filename portion of the Content-Disposition header if
+ * available and if applicable. This allows getting the preferred filename
+ * without having to parse it out yourself.
+ *
+ * Setting contentDispositionFilename provides a hint to the channel about
+ * the disposition. If a normal Content-Disposition header is present its
+ * value will always be used. If it is missing the hinted value will be
+ * used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't
+ * exist for this type of channel, if the header is empty, if the header
+ * doesn't contain a filename portion, or the value of the filename
+ * attribute is empty/missing.
+ */
+ attribute AString contentDispositionFilename;
+
+ /**
+ * Access to the raw Content-Disposition header if available and applicable.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty.
+ *
+ * @deprecated Use contentDisposition/contentDispositionFilename instead.
+ */
+ readonly attribute ACString contentDispositionHeader;
+
+ /**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+ attribute nsILoadInfo loadInfo;
+
+ /**
+ * Returns true if the channel is used to create a document.
+ * It returns true if the loadFlags have LOAD_DOCUMENT_URI set, or if
+ * LOAD_HTML_OBJECT_DATA is set and the channel has the appropriate
+ * MIME type.
+ * Note: May have the wrong value if called before OnStartRequest as we
+ * don't know the MIME type yet.
+ */
+ readonly attribute bool isDocument;
+
+%{ C++
+ inline bool IsDocument()
+ {
+ bool isDocument = false;
+ if (NS_SUCCEEDED(GetIsDocument(&isDocument)) && isDocument) {
+ return true;
+ }
+ return false;
+ }
+
+ inline already_AddRefed<nsILoadInfo> LoadInfo()
+ {
+ nsCOMPtr<nsILoadInfo> result;
+ mozilla::DebugOnly<nsresult> rv = GetLoadInfo(getter_AddRefs(result));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && result);
+ return result.forget();
+ }
+%}
+
+};
+
+[builtinclass, scriptable, uuid(1ebbff64-d742-4f4a-aad5-4df2d1eb937a)]
+interface nsIIdentChannel : nsIChannel
+{
+ /**
+ * Unique ID of the channel, shared between parent and child. Needed if
+ * the channel activity needs to be monitored across process boundaries,
+ * like in devtools net monitor. See bug 1274556.
+ */
+ [must_use] attribute uint64_t channelId;
+
+%{ C++
+ inline uint64_t ChannelId()
+ {
+ uint64_t value = 0;
+ if (NS_SUCCEEDED(GetChannelId(&value))) {
+ return value;
+ }
+ return 0;
+ }
+%}
+};
diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl
new file mode 100644
index 0000000000..56484e933f
--- /dev/null
+++ b/netwerk/base/nsIChannelEventSink.idl
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+/**
+ * Implement this interface to receive control over various channel events.
+ * Channels will try to get this interface from a channel's
+ * notificationCallbacks or, if not available there, from the loadGroup's
+ * notificationCallbacks.
+ *
+ * These methods are called before onStartRequest.
+ */
+[scriptable, uuid(0197720d-37ed-4e75-8956-d0d296e4d8a6)]
+interface nsIChannelEventSink : nsISupports
+{
+ /**
+ * This is a temporary redirect. New requests for this resource should
+ * continue to use the URI of the old channel.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_TEMPORARY = 1 << 0;
+
+ /**
+ * This is a permanent redirect. New requests for this resource should use
+ * the URI of the new channel (This might be an HTTP 301 reponse).
+ * If this flag is not set, this is a temporary redirect.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_PERMANENT = 1 << 1;
+
+ /**
+ * This is an internal redirect, i.e. it was not initiated by the remote
+ * server, but is specific to the channel implementation.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_INTERNAL = 1 << 2;
+
+ /**
+ * This is a special-cased redirect coming from hitting HSTS upgrade
+ * redirect from http to https only. In some cases this type of redirect
+ * may be considered as safe despite not being the-same-origin redirect.
+ */
+ const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
+
+ /**
+ * This is a internal redirect used to handle http authentication retries.
+ * Upon receiving a 401 or 407 the channel gets redirected to a new channel
+ * (same URL) that performs the request with the appropriate credentials.
+ * Auth retry to the server must be made after redirecting to a new channel
+ */
+ const unsigned long REDIRECT_AUTH_RETRY = 1 << 4;
+
+ /**
+ * Called when a redirect occurs. This may happen due to an HTTP 3xx status
+ * code. The purpose of this method is to notify the sink that a redirect
+ * is about to happen, but also to give the sink the right to veto the
+ * redirect by throwing or passing a failure-code in the callback.
+ *
+ * Note that vetoing the redirect simply means that |newChannel| will not
+ * be opened. It is important to understand that |oldChannel| will continue
+ * loading as if it received a HTTP 200, which includes notifying observers
+ * and possibly display or process content attached to the HTTP response.
+ * If the sink wants to prevent this loading it must explicitly deal with
+ * it, e.g. by calling |oldChannel->Cancel()|
+ *
+ * There is a certain freedom in implementing this method:
+ *
+ * If the return-value indicates success, a callback on |callback| is
+ * required. This callback can be done from within asyncOnChannelRedirect
+ * (effectively making the call synchronous) or at some point later
+ * (making the call asynchronous). Repeat: A callback must be done
+ * if this method returns successfully.
+ *
+ * If the return value indicates error (method throws an exception)
+ * the redirect is vetoed and no callback must be done. Repeat: No
+ * callback must be done if this method throws!
+ *
+ * NOTE: originalURI isn't yet set on the new channel when
+ * asyncOnChannelRedirect is called.
+ *
+ * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback()
+ *
+ * @param oldChannel
+ * The channel that's being redirected.
+ * @param newChannel
+ * The new channel. This channel is not opened yet.
+ * @param flags
+ * Flags indicating the type of redirect. A bitmask consisting
+ * of flags from above.
+ * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be
+ * set.
+ * @param callback
+ * Object to inform about the async result of this method
+ *
+ * @throw <any> Throwing an exception will cause the redirect to be
+ * cancelled
+ */
+ void asyncOnChannelRedirect(in nsIChannel oldChannel,
+ in nsIChannel newChannel,
+ in unsigned long flags,
+ in nsIAsyncVerifyRedirectCallback callback);
+};
diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl
new file mode 100644
index 0000000000..3b57013536
--- /dev/null
+++ b/netwerk/base/nsIChildChannel.idl
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * Implemented by content side of IPC protocols.
+ */
+
+[scriptable, uuid(c45b92ae-4f07-41dd-b0ef-aa044eeabb1e)]
+interface nsIChildChannel : nsISupports
+{
+ /**
+ * Create the chrome side of the IPC protocol and join an existing 'real'
+ * channel on the parent process. The id is provided by
+ * nsIRedirectChannelRegistrar on the chrome process and pushed to the child
+ * protocol as an argument to event starting a redirect.
+ *
+ * Primarilly used in HttpChannelChild::Redirect1Begin on a newly created
+ * child channel, where the new channel is intended to be created on the
+ * child process.
+ */
+ void connectParent(in uint32_t registrarId);
+
+ /**
+ * As AsyncOpen is called on the chrome process for redirect target channels,
+ * we have to inform the child side of the protocol of that fact by a special
+ * method.
+ */
+ void completeRedirectSetup(in nsIStreamListener aListener);
+};
diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl
new file mode 100644
index 0000000000..5426eabbcc
--- /dev/null
+++ b/netwerk/base/nsIClassOfService.idl
@@ -0,0 +1,103 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIClassOfService.idl
+ *
+ * Used to express class dependencies and characteristics - complimentary to
+ * nsISupportsPriority which is used to express weight
+ *
+ * Channels that implement this interface may make use of this
+ * information in different ways.
+ */
+
+// convenience class for passing around the class of service
+%{C++
+namespace mozilla::net {
+class ClassOfService;
+}
+%}
+native ClassOfService(mozilla::net::ClassOfService);
+
+[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)]
+interface nsIClassOfService : nsISupports
+{
+ attribute unsigned long classFlags;
+ attribute bool incremental;
+
+ void clearClassFlags(in unsigned long flags);
+ void addClassFlags(in unsigned long flags);
+ void setClassOfService(in ClassOfService s);
+
+ // All these flags have a (de)prioritization effect.
+
+ // In the HTTP/1 world the priority is considered for all requests inside a so
+ // called 'Request Context' which is a context common to all sub-resources
+ // belonging to a single top level window (RequestContextService). Requests
+ // marked with the Leader flag are blocking (preventing from being sent to the
+ // server) all other resource loads except those marked with the Unblocked
+ // flag. Other classes run in parallel - neither being blocked no ;r blocking.
+ // The Leader flag is used only for <head> blocking resources (sync and
+ // defer javascript resources and stylesheets.) Purpose is to deliver these
+ // first-paint and domcontentloaded blocking resources as soon as possbile.
+
+ // In the HTTP/2 world it's different. Priorities are done only per HTTP/2
+ // session, normally we have one session per one origin (including origin
+ // attributes!) Requests are dispatched (sent) immediately on an HTTP/2
+ // session. Each session has artificial streams (groups) relating to the class
+ // of service flags (Leader, Other, Background, Speculative, Follower,
+ // UrgentStart), each such a stream is given a different weight (only way to
+ // give a stream a priority in HTTP/2) reflecting the desired request group
+ // priority. Actual request streams are then dependent on these artificial
+ // streams (groups). nsISupportsPriority of each request is passed as a weight
+ // on the HTTP/2 stream to prioritize streams in the same group. A stream can
+ // also be dependent on other stream. We have dependency of Followers on
+ // Leaders, hence everything set the Follower flag should be processed by the
+ // server after Leaders. Same for Speculative being dependent on Background. The
+ // tree is created and used here:
+ // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Session.cpp#1053-1070
+ // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Stream.cpp#1338
+ // For detailed description of how HTTP/2 server should handle the priorities
+ // and dependencies see:
+ // https://developers.google.com/web/fundamentals/performance/http2/#stream_prioritization
+ // Please note that the dependecies and weights we are sending to the server
+ // are only suggestions, the server may completely ignore it.
+
+ // Leaders (should) block all other resources except Unblocked. This flag
+ // also priortizes HTTP cache reading queue by blocking all other cache
+ // requests.
+ const unsigned long Leader = 1 << 0;
+ // The Follower flag is currently unused!
+ const unsigned long Follower = 1 << 1;
+ // The Speculative flag is currently unused!
+ const unsigned long Speculative = 1 << 2;
+ // The Background flag is currently only used for Beacon.
+ const unsigned long Background = 1 << 3;
+ // Requests marked with this flag are not blocked by Leaders. This is mainly
+ // used for probing-like XMLHttpRequests that may block delivery of head
+ // blocking resources, e.g. CSS files tailored for the UA.
+ const unsigned long Unblocked = 1 << 4;
+ // Throttleable flag allows response throttling of the resource load. Note
+ // that this functionality is currently disabled.
+ const unsigned long Throttleable = 1 << 5;
+ // UrgentStart makes the request temporarily extend HTTP/1 connection
+ // parallelism limits. Used mainly for navigational requests (top level html)
+ // and any request considered coming from a user interaction to make reaction
+ // of the browser as fast as possible and not blocked.
+ const unsigned long UrgentStart = 1 << 6;
+ // Specifically disables throttling under any circumstances, used for media
+ // responses mainly.
+ const unsigned long DontThrottle = 1 << 7;
+ // Enforce tailing on this load; any of Leader, Unblocked, UrgentStart,
+ // TailForbidden overrule this flag (disable tailing.)
+ const unsigned long Tail = 1 << 8;
+ // Tailing may be engaged regardless if the load is marked Unblocked when some
+ // other conditions are met later, like when the load is found to be a
+ // tracker.
+ const unsigned long TailAllowed = 1 << 9;
+ // Tailing not allowed under any circumstances or combination of flags.
+ const unsigned long TailForbidden = 1 << 10;
+};
diff --git a/netwerk/base/nsIClassifiedChannel.idl b/netwerk/base/nsIClassifiedChannel.idl
new file mode 100644
index 0000000000..482c524cbf
--- /dev/null
+++ b/netwerk/base/nsIClassifiedChannel.idl
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIClassifiedChannel
+ *
+ * A channel may optionally implement this interface if it carries classified
+ * result information of channel classifier. The information contains, for
+ * example, the name of matched table and the name of matched provider.
+ */
+[builtinclass, scriptable, uuid(70cf6091-a1de-4aa8-8224-058f8964be31)]
+interface nsIClassifiedChannel : nsISupports
+{
+ /**
+ * Sets matched info of the classified channel.
+ *
+ * @param aList
+ * Name of the Safe Browsing list that matched (e.g. goog-phish-shavar).
+ * @param aProvider
+ * Name of the Safe Browsing provider that matched (e.g. google)
+ * @param aFullHash
+ * Full hash of URL that matched Safe Browsing list.
+ */
+ void setMatchedInfo(in ACString aList,
+ in ACString aProvider,
+ in ACString aFullHash);
+
+ /**
+ * Name of the list that matched
+ */
+ readonly attribute ACString matchedList;
+
+ /**
+ * Name of provider that matched
+ */
+ readonly attribute ACString matchedProvider;
+
+ /**
+ * Full hash of URL that matched
+ */
+ readonly attribute ACString matchedFullHash;
+
+ /**
+ * Sets matched tracking info of the classified channel.
+ *
+ * @param aLists
+ * Name of the Tracking Protection list that matched (e.g. content-track-digest256).
+ * @param aFullHash
+ * Full hash of URLs that matched Tracking Protection list.
+ */
+ void setMatchedTrackingInfo(in Array<ACString> aLists,
+ in Array<ACString> aFullHashes);
+
+ /**
+ * Name of the lists that matched
+ */
+ readonly attribute Array<ACString> matchedTrackingLists;
+
+ /**
+ * Full hash of URLs that matched
+ */
+ readonly attribute Array<ACString> matchedTrackingFullHashes;
+
+ /**
+ * Returns the classification flags if the channel has been processed by
+ * URL-Classifier features and is considered first-party.
+ */
+ [infallible] readonly attribute unsigned long firstPartyClassificationFlags;
+
+ /**
+ * Returns the classification flags if the channel has been processed by
+ * URL-Classifier features and is considered third-party with the top
+ * window URI.
+ */
+ [infallible] readonly attribute unsigned long thirdPartyClassificationFlags;
+
+ /*
+ * Returns the classification flags if the channel has been processed by
+ * URL-Classifier features. This value is equal to
+ * "firstPartyClassificationFlags || thirdPartyClassificationFlags".
+ *
+ * Note that top-level channels could be classified as well.
+ * In order to identify third-party resources specifically, use
+ * classificationThirdPartyFlags;
+ */
+ [infallible] readonly attribute unsigned long classificationFlags;
+
+ cenum ClassificationFlags : 32 {
+ /**
+ * The resource is on the fingerprinting list.
+ */
+ CLASSIFIED_FINGERPRINTING = 0x0001,
+ CLASSIFIED_FINGERPRINTING_CONTENT = 0x0080,
+
+ /**
+ * The resource is on the cryptomining list.
+ */
+ CLASSIFIED_CRYPTOMINING = 0x0002,
+ CLASSIFIED_CRYPTOMINING_CONTENT = 0x0100,
+
+ /**
+ * The following are about tracking annotation and are available only
+ * if the privacy.trackingprotection.annotate_channels pref.
+ * CLASSIFIED_TRACKING is set if we are not able to identify the
+ * type of classification.
+ */
+ CLASSIFIED_TRACKING = 0x0004,
+ CLASSIFIED_TRACKING_AD = 0x0008,
+ CLASSIFIED_TRACKING_ANALYTICS = 0x0010,
+ CLASSIFIED_TRACKING_SOCIAL = 0x0020,
+ CLASSIFIED_TRACKING_CONTENT = 0x0040,
+
+ /**
+ * The following are about social tracking.
+ */
+ CLASSIFIED_SOCIALTRACKING = 0x0200,
+ CLASSIFIED_SOCIALTRACKING_FACEBOOK = 0x0400,
+ CLASSIFIED_SOCIALTRACKING_LINKEDIN = 0x0800,
+ CLASSIFIED_SOCIALTRACKING_TWITTER = 0x1000,
+
+ /**
+ * The following are about email tracking.
+ */
+ CLASSIFIED_EMAILTRACKING = 0x2000,
+ CLASSIFIED_EMAILTRACKING_CONTENT = 0x4000,
+
+ /**
+ * This is exposed to help to identify tracking classification using the
+ * basic lists.
+ */
+ CLASSIFIED_ANY_BASIC_TRACKING = CLASSIFIED_TRACKING |
+ CLASSIFIED_TRACKING_AD | CLASSIFIED_TRACKING_ANALYTICS |
+ CLASSIFIED_TRACKING_SOCIAL | CLASSIFIED_FINGERPRINTING,
+
+ /**
+ * This is exposed to help to identify tracking classification using the
+ * strict lists.
+ */
+ CLASSIFIED_ANY_STRICT_TRACKING = CLASSIFIED_ANY_BASIC_TRACKING |
+ CLASSIFIED_TRACKING_CONTENT | CLASSIFIED_FINGERPRINTING_CONTENT,
+
+ /**
+ * This is exposed to help to identify social tracking classification
+ * flags.
+ */
+ CLASSIFIED_ANY_SOCIAL_TRACKING = CLASSIFIED_SOCIALTRACKING |
+ CLASSIFIED_SOCIALTRACKING_FACEBOOK |
+ CLASSIFIED_SOCIALTRACKING_LINKEDIN | CLASSIFIED_SOCIALTRACKING_TWITTER,
+ };
+
+ /**
+ * Returns true if the channel has been processed by URL-Classifier features
+ * and is considered third-party with the top window URI, and if it has loaded
+ * a resource that is classified as a tracker.
+ *
+ * This is a helper attribute which returns the same value of
+ * (thirdPartyClassificationFlags & CLASSIFIED_ANY_BASIC_TRACKING) or
+ * (thirdPartyClassificationFlags & CLASSIFIED_ANY_STRICT_TRACKING) or
+ * (thirdPartyClassificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING)
+ */
+ boolean isThirdPartyTrackingResource();
+
+%{ C++
+ inline bool IsThirdPartyTrackingResource()
+ {
+ bool value = false;
+ if (NS_SUCCEEDED(IsThirdPartyTrackingResource(&value)) && value) {
+ return true;
+ }
+ return false;
+ }
+%}
+
+ /**
+ * Returns true if the channel has loaded a 3rd party resource that is
+ * classified as a social tracker.
+ *
+ * This is a helper attribute which returns the same value of
+ * (classificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING)
+ *
+ * Note that top-level channels could be marked as tracking
+ * resources. In order to identify third-party social tracking resources
+ * specifically, check the flags manually or add a new helper here.
+ */
+ boolean isThirdPartySocialTrackingResource();
+
+%{ C++
+ inline bool IsThirdPartySocialTrackingResource()
+ {
+ bool value = false;
+ if (NS_SUCCEEDED(IsThirdPartySocialTrackingResource(&value)) && value) {
+ return true;
+ }
+ return false;
+ }
+%}
+};
diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl
new file mode 100644
index 0000000000..f9052b8e60
--- /dev/null
+++ b/netwerk/base/nsIContentSniffer.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+
+/**
+ * Content sniffer interface. Components implementing this interface can
+ * determine a MIME type from a chunk of bytes.
+ */
+[scriptable, uuid(a5772d1b-fc63-495e-a169-96e8d3311af0)]
+interface nsIContentSniffer : nsISupports
+{
+ /**
+ * Given a chunk of data, determines a MIME type. Information from the given
+ * request may be used in order to make a better decision.
+ *
+ * @param aRequest The request where this data came from. May be null.
+ * @param aData Data to check
+ * @param aLength Length of the data
+ *
+ * @return The content type
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if no MIME type could be determined.
+ *
+ * @note Implementations should consider the request read-only. Especially,
+ * they should not attempt to set the content type property that subclasses of
+ * nsIRequest might offer.
+ */
+ ACString getMIMETypeFromContent(in nsIRequest aRequest,
+ [const,array,size_is(aLength)] in octet aData,
+ in unsigned long aLength);
+};
diff --git a/netwerk/base/nsIDHCPClient.idl b/netwerk/base/nsIDHCPClient.idl
new file mode 100644
index 0000000000..1d986ebd52
--- /dev/null
+++ b/netwerk/base/nsIDHCPClient.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface allows the proxy code to access the DHCP Options in a platform-specific way
+ */
+[scriptable, uuid(aee75dc0-be1a-46b9-9e0c-31a6899c175c)]
+interface nsIDHCPClient : nsISupports
+{
+
+ /**
+ * returns the DHCP Option designated by the option parameter
+ */
+ ACString getOption(in uint8_t option);
+};
diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl
new file mode 100644
index 0000000000..ae80a9e458
--- /dev/null
+++ b/netwerk/base/nsIDashboard.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/* A JavaScript callback function that takes a JSON as its parameter.
+ * The returned JSON contains arrays with data
+ */
+[scriptable, function, uuid(19d7f24f-a95a-4fd9-87e2-d96e9e4b1f6d)]
+interface nsINetDashboardCallback : nsISupports
+{
+ void onDashboardDataAvailable(in jsval data);
+};
+
+/* The dashboard service.
+ * The async API returns JSONs, which hold arrays with the required info.
+ * Only one request of each type may be pending at any time.
+ */
+[scriptable, uuid(c79eb3c6-091a-45a6-8544-5a8d1ab79537)]
+interface nsIDashboard : nsISupports
+{
+ /* Arrays: host, port, tcp, active, socksent, sockreceived
+ * Values: sent, received */
+ void requestSockets(in nsINetDashboardCallback cb);
+
+ /* Arrays: host, port, spdy, ssl
+ * Array of arrays: active, idle */
+ void requestHttpConnections(in nsINetDashboardCallback cb);
+
+ /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */
+ void requestWebsocketConnections(in nsINetDashboardCallback cb);
+
+ /* Arrays: hostname, family, hostaddr, expiration */
+ void requestDNSInfo(in nsINetDashboardCallback cb);
+
+ /* aProtocol: a transport layer protocol:
+ * ex: "ssl", "tcp", default is "tcp".
+ * aHost: the host's name
+ * aPort: the port which the connection will open on
+ * aTimeout: the timespan before the connection will be timed out */
+ void requestConnection(in ACString aHost, in unsigned long aPort,
+ in string aProtocol, in unsigned long aTimeout,
+ in nsINetDashboardCallback cb);
+
+ /* When true, the service will log websocket events */
+ attribute boolean enableLogging;
+
+ /* DNS resolver for host name
+ * aHost: host name */
+ void requestDNSLookup(in ACString aHost, in nsINetDashboardCallback cb);
+
+ /* Resolve HTTPS RRs for host name
+ * aHost: host name */
+ void requestDNSHTTPSRRLookup(in ACString aHost,
+ in nsINetDashboardCallback cb);
+
+ /**
+ * Asyncly returns stats regarding the "Race Cache With Network" feature.
+ */
+ void requestRcwnStats(in nsINetDashboardCallback cb);
+
+ AUTF8String getLogPath();
+};
diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl
new file mode 100644
index 0000000000..933caf4371
--- /dev/null
+++ b/netwerk/base/nsIDashboardEventNotifier.idl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[uuid(24fdfcbe-54cb-4997-8392-3c476126ea3b)]
+interface nsIDashboardEventNotifier : nsISupports
+{
+ /* These methods are called to register a websocket event with the dashboard
+ *
+ * A host is identified by the (aHost, aSerial) pair.
+ * aHost: the host's name: example.com
+ * aSerial: a number that uniquely identifies the websocket
+ *
+ * aEncrypted: if the connection is encrypted
+ * aLength: the length of the message in bytes
+ */
+ void addHost(in ACString aHost, in unsigned long aSerial, in boolean aEncrypted);
+ void removeHost(in ACString aHost, in unsigned long aSerial);
+ void newMsgSent(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+ void newMsgReceived(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+};
diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl
new file mode 100644
index 0000000000..c887c32c74
--- /dev/null
+++ b/netwerk/base/nsIDownloader.idl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIFile;
+interface nsIDownloadObserver;
+
+/**
+ * nsIDownloader
+ *
+ * A downloader is a special implementation of a nsIStreamListener that will
+ * make the contents of the stream available as a file. This may utilize the
+ * disk cache as an optimization to avoid an extra copy of the data on disk.
+ * The resulting file is valid from the time the downloader completes until
+ * the last reference to the downloader is released.
+ */
+[scriptable, uuid(fafe41a9-a531-4d6d-89bc-588a6522fb4e)]
+interface nsIDownloader : nsIStreamListener
+{
+ /**
+ * Initialize this downloader
+ *
+ * @param observer
+ * the observer to be notified when the download completes.
+ * @param downloadLocation
+ * the location where the stream contents should be written.
+ * if null, the downloader will select a location and the
+ * resulting file will be deleted (or otherwise made invalid)
+ * when the downloader object is destroyed. if an explicit
+ * download location is specified then the resulting file will
+ * not be deleted, and it will be the callers responsibility
+ * to keep track of the file, etc.
+ */
+ void init(in nsIDownloadObserver observer,
+ in nsIFile downloadLocation);
+};
+
+[scriptable, uuid(44b3153e-a54e-4077-a527-b0325e40924e)]
+interface nsIDownloadObserver : nsISupports
+{
+ /**
+ * Called to signal a download that has completed.
+ */
+ void onDownloadComplete(in nsIDownloader downloader,
+ in nsIRequest request,
+ in nsresult status,
+ in nsIFile result);
+};
diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl
new file mode 100644
index 0000000000..e1db3993c7
--- /dev/null
+++ b/netwerk/base/nsIEncodedChannel.idl
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIUTF8StringEnumerator;
+interface nsIStreamListener;
+interface nsISupports;
+
+/**
+ * A channel interface which allows special handling of encoded content
+ */
+
+[scriptable, uuid(29c29ce6-8ce4-45e6-8d60-36c8fa3e255b)]
+interface nsIEncodedChannel : nsISupports
+{
+ /**
+ * This attribute holds the MIME types corresponding to the content
+ * encodings on the channel. The enumerator returns nsISupportsCString
+ * objects. The first one corresponds to the outermost encoding on the
+ * channel and then we work our way inward. "identity" is skipped and not
+ * represented on the list. Unknown encodings make the enumeration stop.
+ * If you want the actual Content-Encoding value, use
+ * getResponseHeader("Content-Encoding").
+ *
+ * When there is no Content-Encoding header, this property is null.
+ *
+ * Modifying the Content-Encoding header on the channel will cause
+ * this enumerator to have undefined behavior. Don't do it.
+ *
+ * Also note that contentEncodings only exist during or after OnStartRequest.
+ * Calling contentEncodings before OnStartRequest is an error.
+ */
+ readonly attribute nsIUTF8StringEnumerator contentEncodings;
+
+ /**
+ * This attribute controls whether or not content conversion should be
+ * done per the Content-Encoding response header. applyConversion can only
+ * be set before or during OnStartRequest. Calling this during
+ * OnDataAvailable is an error.
+ *
+ * TRUE by default.
+ */
+ attribute boolean applyConversion;
+
+ /**
+ * This attribute indicates the content has been decompressed in
+ * the parent process.
+ */
+ attribute boolean hasContentDecompressed;
+ /**
+ * This function will start converters if they are available.
+ * aNewNextListener will be nullptr if no converter is available.
+ */
+ void doApplyContentConversions(in nsIStreamListener aNextListener,
+ out nsIStreamListener aNewNextListener,
+ in nsISupports aCtxt);
+};
diff --git a/netwerk/base/nsIExternalProtocolHandler.idl b/netwerk/base/nsIExternalProtocolHandler.idl
new file mode 100644
index 0000000000..6f86dbb05a
--- /dev/null
+++ b/netwerk/base/nsIExternalProtocolHandler.idl
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+[scriptable, uuid(0e61f3b2-34d7-4c79-bfdc-4860bc7341b7)]
+interface nsIExternalProtocolHandler: nsIProtocolHandler
+{
+ /**
+ * This method checks if the external handler exists for a given scheme.
+ *
+ * @param scheme external scheme.
+ * @return TRUE if the external handler exists for the input scheme, FALSE otherwise.
+ */
+ boolean externalAppExistsForScheme(in ACString scheme);
+};
diff --git a/netwerk/base/nsIFileStreams.idl b/netwerk/base/nsIFileStreams.idl
new file mode 100644
index 0000000000..026fa41bcf
--- /dev/null
+++ b/netwerk/base/nsIFileStreams.idl
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+#include "nsIRandomAccessStream.idl"
+
+interface nsIEventTarget;
+interface nsIFile;
+interface nsIFileMetadataCallback;
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+
+/**
+ * An input stream that allows you to read from a file.
+ */
+[scriptable, builtinclass, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)]
+interface nsIFileInputStream : nsIInputStream
+{
+ /**
+ * @param file file to read from
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDONLY).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * If this is set, the file will close automatically when the end of the
+ * file is reached.
+ */
+ const long CLOSE_ON_EOF = 1<<2;
+
+ /**
+ * If this is set, the file will be reopened whenever we reach the start of
+ * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative
+ * seek that happen to reach the beginning of the file. If the file is
+ * already open and the seek occurs, it will happen naturally. (The file
+ * will only be reopened if it is closed for some reason.)
+ */
+ const long REOPEN_ON_REWIND = 1<<3;
+
+ /**
+ * If this is set, the file will be opened (i.e., a call to
+ * PR_Open done) only when we do an actual operation on the stream,
+ * or more specifically, when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - ReadLine
+ *
+ * DEFER_OPEN is useful if we use the stream on a background
+ * thread, so that the opening and possible |stat|ing of the file
+ * happens there as well.
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read. Also, the file is not locked when Init is called,
+ * so it might be deleted before we try to read from it.
+ */
+ const long DEFER_OPEN = 1<<4;
+
+ /**
+ * This flag has no effect and is totally ignored on any platform except
+ * Windows since this is the default behavior on POSIX systems. On Windows
+ * if this flag is set then the stream is opened in a special mode that
+ * allows the OS to delete the file from disk just like POSIX.
+ */
+ const long SHARE_DELETE = 1<<5;
+};
+
+/**
+ * An output stream that lets you stream to a file.
+ */
+[scriptable, builtinclass, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)]
+interface nsIFileOutputStream : nsIOutputStream
+{
+ /**
+ * @param file file to write to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_WRONLY |
+ * PR_CREATE_FILE | PR_TRUNCATE)
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default permissions (0664)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (currently none supported)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * @param length asks the operating system to allocate storage for
+ * this file of at least |length| bytes long, and
+ * set the file length to the corresponding size.
+ * @throws NS_ERROR_FAILURE if the preallocation fails.
+ * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened.
+ */
+ [noscript] void preallocate(in long long length);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Write
+ * - Flush
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first write, and if the file is to be created, then it will not
+ * appear on the disk until the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * A stream that allows you to read from a file or stream to a file.
+ */
+[scriptable, builtinclass, uuid(82cf605a-8393-4550-83ab-43cd5578e006)]
+interface nsIFileRandomAccessStream : nsIRandomAccessStream
+{
+ /**
+ * @param file file to read from or stream to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDWR).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - Flush
+ * - Write
+ * - GetSize
+ * - GetLastModified
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read or write. The file is not locked when Init is called,
+ * so it might be deleted before we try to read from it and if the
+ * file is to be created, then it will not appear on the disk until
+ * the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * An interface that allows you to get some metadata like file size and
+ * file last modified time. These methods and attributes can throw
+ * NS_BASE_STREAM_WOULD_BLOCK in case the informations are not available yet.
+ * If this happens, consider the use of nsIAsyncFileMetadata.
+ *
+ * If using nsIAsyncFileMetadata, you should retrieve any data via this
+ * interface before taking any action that might consume the underlying stream.
+ * For example, once Available(), Read(), or nsIAsyncInputStream::AsyncWait()
+ * are invoked, these methods may return NS_BASE_STREAM_CLOSED. This will
+ * happen when using RemoteLazyInputStream with an underlying file stream, for
+ * example.
+ */
+[scriptable, builtinclass, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)]
+interface nsIFileMetadata : nsISupports
+{
+ /**
+ * File size in bytes.
+ */
+ readonly attribute long long size;
+
+ /**
+ * File last modified time in milliseconds from midnight (00:00:00),
+ * January 1, 1970 Greenwich Mean Time (GMT).
+ */
+ readonly attribute long long lastModified;
+
+ /**
+ * The internal file descriptor. It can be used for memory mapping of the
+ * underlying file. Please use carefully! If this returns
+ * NS_BASE_STREAM_WOULD_BLOCK, consider the use of nsIAsyncFileMetadata.
+ */
+ [noscript] PRFileDescPtr getFileDescriptor();
+};
+
+[scriptable, builtinclass, uuid(de15b80b-29ba-4b7f-9220-a3d75b17ae8c)]
+interface nsIAsyncFileMetadata : nsIFileMetadata
+{
+ /**
+ * Asynchronously wait for the object to be ready.
+ *
+ * @param aCallback The callback will be used when the stream is ready to
+ * return File metadata. Use a nullptr to cancel a
+ * previous operation.
+ *
+ * @param aEventTarget The event target where aCallback will be executed.
+ * If aCallback is passed, aEventTarget cannot be null.
+ */
+ void asyncFileMetadataWait(in nsIFileMetadataCallback aCallback,
+ in nsIEventTarget aEventTarget);
+};
+
+/**
+ * This is a companion interface for
+ * nsIAsyncFileMetadata::asyncFileMetadataWait.
+ */
+[function, scriptable, uuid(d01c7ead-7ba3-4726-b399-618ec8ec7057)]
+interface nsIFileMetadataCallback : nsISupports
+{
+ /**
+ * Called to indicate that the nsIFileMetadata object is ready.
+ */
+ void onFileMetadataReady(in nsIAsyncFileMetadata aObject);
+};
diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl
new file mode 100644
index 0000000000..3c97a2e3af
--- /dev/null
+++ b/netwerk/base/nsIFileURL.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURL.idl"
+
+interface nsIFile;
+interface nsIURIMutator;
+
+/**
+ * nsIFileURL provides access to the underlying nsIFile object corresponding to
+ * an URL. The URL scheme need not be file:, since other local protocols may
+ * map URLs to files (e.g., resource:).
+ */
+[scriptable, builtinclass, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)]
+interface nsIFileURL : nsIURL
+{
+ /**
+ * Get the nsIFile corresponding to this URL.
+ *
+ * - Returns a reference to an immutable object. Callers must clone
+ * before attempting to modify the returned nsIFile object. NOTE: this
+ * constraint might not be enforced at runtime, so beware!!
+ */
+ readonly attribute nsIFile file;
+};
+
+[scriptable, builtinclass, uuid(a588b6f2-d2b9-4024-84c7-be3368546b57)]
+interface nsIFileURLMutator : nsISupports
+{
+ /*
+ * - Marks the inner URI implementation as one that supports nsIFileURL.
+ */
+ [must_use, noscript] void markFileURL();
+
+ /*
+ * - Setter clones the nsIFile object (allowing the caller to safely modify
+ * the nsIFile object after setting it on this interface).
+ */
+ [must_use, noscript] void setFile(in nsIFile aFile);
+};
diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl
new file mode 100644
index 0000000000..714c83f52d
--- /dev/null
+++ b/netwerk/base/nsIForcePendingChannel.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIForcePending interface exposes a function that enables overwriting of the normal
+ * behavior for the channel's IsPending(), forcing 'true' to be returned.
+ */
+
+[uuid(2ac3e1ca-049f-44c3-a519-f0681f51e9b1)]
+interface nsIForcePendingChannel : nsISupports
+{
+
+ /**
+ * forcePending(true) overrides the normal behavior for the
+ * channel's IsPending(), forcing 'true' to be returned. A call to
+ * forcePending(false) reverts IsPending() back to normal behavior.
+ */
+ void forcePending(in boolean aForcePending);
+};
diff --git a/netwerk/base/nsIFormPOSTActionChannel.idl b/netwerk/base/nsIFormPOSTActionChannel.idl
new file mode 100644
index 0000000000..870886390c
--- /dev/null
+++ b/netwerk/base/nsIFormPOSTActionChannel.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIUploadChannel.idl"
+
+/**
+ * nsIFormPOSTActionChannel
+ *
+ * Channel classes that want to be allowed for HTML form POST action must
+ * implement this interface.
+ */
+[scriptable, uuid(fc826b53-0db8-42b4-aa6a-5dd2cfca52a4)]
+interface nsIFormPOSTActionChannel : nsIUploadChannel
+{
+};
diff --git a/netwerk/base/nsIHttpAuthenticatorCallback.idl b/netwerk/base/nsIHttpAuthenticatorCallback.idl
new file mode 100644
index 0000000000..74fd3c97ab
--- /dev/null
+++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)]
+interface nsIHttpAuthenticatorCallback : nsISupports
+{
+ /**
+ * Authentication data for a header is available.
+ *
+ * @param aCreds
+ * Credentials which were obtained asynchonously.
+ * @param aFlags
+ * Flags set by asynchronous call.
+ * @param aResult
+ * Result status of credentials generation
+ * @param aSessionState
+ * Modified session state to be passed to caller
+ * @param aContinuationState
+ * Modified continuation state to be passed to caller
+ */
+ void onCredsGenerated(in ACString aCreds,
+ in unsigned long aFlags,
+ in nsresult aResult,
+ in nsISupports aSessionsState,
+ in nsISupports aContinuationState);
+
+};
diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl
new file mode 100644
index 0000000000..f44605c000
--- /dev/null
+++ b/netwerk/base/nsIHttpPushListener.idl
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIHttpChannel;
+
+/**
+ * nsIHttpPushListener
+ *
+ * Used for triggering when a HTTP/2 push is received.
+ *
+ */
+[scriptable, uuid(0d6ce59c-ad5d-4520-b4d3-09664868f279)]
+interface nsIHttpPushListener : nsISupports
+{
+ /**
+ * When provided as a notificationCallback to an httpChannel, this.onPush()
+ * will be invoked when there is a >= Http2 push to that
+ * channel. The push may be in progress.
+ *
+ * The consumer must start the new channel in the usual way by calling
+ * pushChannel.AsyncOpen with a nsIStreamListener object that
+ * will receive the normal sequence of OnStartRequest(),
+ * 0 to N OnDataAvailable(), and onStopRequest().
+ *
+ * The new channel can be canceled after the AsyncOpen if it is not wanted.
+ *
+ * @param associatedChannel
+ * the monitor channel that was recieved on
+ * @param pushChannel
+ * a channel to the resource which is being pushed
+ */
+ void onPush(in nsIHttpChannel associatedChannel,
+ in nsIHttpChannel pushChannel);
+};
diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl
new file mode 100644
index 0000000000..5bc3e41181
--- /dev/null
+++ b/netwerk/base/nsIIOService.idl
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIProtocolHandler;
+interface nsIChannel;
+interface nsIURI;
+interface nsIFile;
+interface nsIPrincipal;
+interface nsILoadInfo;
+interface nsIWebTransport;
+
+webidl Node;
+
+%{C++
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+namespace dom {
+class ClientInfo;
+class ServiceWorkerDescriptor;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>);
+[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>);
+
+/**
+ * nsIIOService provides a set of network utility functions. This interface
+ * duplicates many of the nsIProtocolHandler methods in a protocol handler
+ * independent way (e.g., NewURI inspects the scheme in order to delegate
+ * creation of the new URI to the appropriate protocol handler). nsIIOService
+ * also provides a set of URL parsing utility functions. These are provided
+ * as a convenience to the programmer and in some cases to improve performance
+ * by eliminating intermediate data structures and interfaces.
+ */
+[scriptable, builtinclass, uuid(4286de5a-b2ea-446f-8f70-e2a461f42694)]
+interface nsIIOService : nsISupports
+{
+ /**
+ * Returns a protocol handler for a given URI scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return reference to corresponding nsIProtocolHandler
+ */
+ nsIProtocolHandler getProtocolHandler(in string aScheme);
+
+ /**
+ * Returns the protocol flags for a given scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return protocol flags for the corresponding protocol
+ */
+ unsigned long getProtocolFlags(in string aScheme);
+
+ /**
+ * Returns the dynamic protocol flags for a given URI.
+ *
+ * @param aURI the URI to get all dynamic flags for
+ * @return protocol flags for that URI
+ */
+ unsigned long getDynamicProtocolFlags(in nsIURI aURI);
+
+ /**
+ * Returns the default port for a given scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return default port for the corresponding protocol
+ */
+ long getDefaultPort(in string aScheme);
+
+ /**
+ * This method constructs a new URI based on the scheme of the URI spec.
+ * QueryInterface can be used on the resulting URI object to obtain a more
+ * specific type of URI.
+ */
+ nsIURI newURI(in AUTF8String aSpec,
+ [optional] in string aOriginCharset,
+ [optional] in nsIURI aBaseURI);
+
+ /**
+ * This method constructs a new URI from a nsIFile.
+ *
+ * @param aFile specifies the file path
+ * @return reference to a new nsIURI object
+ *
+ * Note: in the future, for perf reasons we should allow
+ * callers to specify whether this is a file or directory by
+ * splitting this into newDirURI() and newActualFileURI().
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * Converts an internal URI (e.g. one that has a username and password in
+ * it) into one which we can expose to the user, for example on the URL bar.
+ *
+ * @param aURI The URI to be converted.
+ * @return nsIURI The converted, exposable URI.
+ */
+ nsIURI createExposableURI(in nsIURI aURI);
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ */
+ nsIChannel newChannelFromURI(in nsIURI aURI,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ [noscript, nostdcall, notxpcom]
+ nsresult NewChannelFromURIWithClientAndController(in nsIURI aURI,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in const_MaybeClientInfoRef aLoadingClientInfo,
+ in const_MaybeServiceWorkerDescriptorRef aController,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType,
+ in unsigned long aSandboxFlags,
+ in boolean aSkipCheckForBrokenURLOrZeroSized,
+ out nsIChannel aResult);
+
+ /**
+ * Equivalent to newChannelFromURI(aURI, aLoadingNode, ...)
+ */
+ nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI,
+ in nsILoadInfo aLoadInfo);
+
+ /**
+ * Equivalent to newChannelFromURI(newURI(...))
+ */
+ nsIChannel newChannel(in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Creates a WebTransport.
+ */
+ nsIWebTransport newWebTransport();
+
+ /**
+ * Returns true if networking is in "offline" mode. When in offline mode,
+ * attempts to access the network will fail (although this does not
+ * necessarily correlate with whether there is actually a network
+ * available -- that's hard to detect without causing the dialer to
+ * come up).
+ *
+ * Changing this fires observer notifications ... see below.
+ */
+ attribute boolean offline;
+
+ /**
+ * Returns false if there are no interfaces for a network request
+ */
+ readonly attribute boolean connectivity;
+
+ /**
+ * Checks if a port number is banned. This involves consulting a list of
+ * unsafe ports, corresponding to network services that may be easily
+ * exploitable. If the given port is considered unsafe, then the protocol
+ * handler (corresponding to aScheme) will be asked whether it wishes to
+ * override the IO service's decision to block the port. This gives the
+ * protocol handler ultimate control over its own security policy while
+ * ensuring reasonable, default protection.
+ *
+ * @see nsIProtocolHandler::allowPort
+ */
+ boolean allowPort(in long aPort, in string aScheme);
+
+ /**
+ * Utility to extract the scheme from a URL string, consistently and
+ * according to spec (see RFC 2396).
+ *
+ * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme
+ * can also be extracted from a URL string via nsIURI. This method
+ * is provided purely as an optimization.
+ *
+ * @param aSpec the URL string to parse
+ * @return URL scheme, lowercase
+ *
+ * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form.
+ */
+ ACString extractScheme(in AUTF8String urlString);
+
+ /**
+ * Checks if a URI host is a local IPv4 or IPv6 address literal.
+ *
+ * @param nsIURI the URI that contains the hostname to check
+ * @return true if the URI hostname is a local IP address
+ */
+ boolean hostnameIsLocalIPAddress(in nsIURI aURI);
+
+ /**
+ * Checks if a URI host is a shared IPv4 address literal.
+ *
+ * @param nsIURI the URI that contains the hostname to check
+ * @return true if the URI hostname is a shared IP address
+ */
+ boolean hostnameIsSharedIPAddress(in nsIURI aURI);
+
+ /**
+ * Checks if a hostname is valid.
+ *
+ * @param AUTF8String hostname is the hostname to validate
+ * @return true if the hostname is valid, else false
+ */
+ boolean isValidHostname(in AUTF8String hostname);
+
+ /**
+ * While this is set, IOService will monitor an nsINetworkLinkService
+ * (if available) and set its offline status to "true" whenever
+ * isLinkUp is false.
+ *
+ * Applications that want to control changes to the IOService's offline
+ * status should set this to false, watch for network:link-status-changed
+ * broadcasts, and change nsIIOService::offline as they see fit. Note
+ * that this means during application startup, IOService may be offline
+ * if there is no link, until application code runs and can turn off
+ * this management.
+ */
+ attribute boolean manageOfflineStatus;
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aProxyURI
+ * nsIURI to use for proxy resolution. Can be null in which
+ * case aURI is used
+ * @param aProxyFlags flags from nsIProtocolProxyService to use
+ * when resolving proxies for this new channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ */
+ nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI,
+ in nsIURI aProxyURI,
+ in unsigned long aProxyFlags,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Return true if socket process is launched.
+ */
+ readonly attribute boolean socketProcessLaunched;
+
+ /**
+ * The pid for socket process.
+ */
+ readonly attribute unsigned long long socketProcessId;
+
+ /**
+ * Register a protocol handler at runtime, given protocol flags and a
+ * default port.
+ *
+ * Statically registered protocol handlers cannot be overridden, and an
+ * error will be returned if that is attempted.
+ *
+ * Runtime registered protocol handlers are never QueryInterface-ed into
+ * `nsIProtocolHandlerWithDynamicFlags`, so that interface will be ignored.
+ *
+ * @param aScheme the scheme handled by the protocol handler.
+ * @param aHandler the protocol handler instance.
+ * @param aProtocolFlags protocol flags for this protocol, see
+ * nsIProtocolHandler for values.
+ * @param aDefaultPort default port for this scheme, or -1.
+ */
+ void registerProtocolHandler(in ACString aScheme,
+ in nsIProtocolHandler aHandler,
+ in unsigned long aProtocolFlags,
+ in long aDefaultPort);
+
+ /**
+ * Unregister a protocol handler which was previously registered using
+ * registerProtocolHandler.
+ *
+ * @param aScheme the scheme to unregister a handler for.
+ */
+ void unregisterProtocolHandler(in ACString aScheme);
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE
+ * when 'offline' has changed from false to true, and we are about
+ * to shut down network services such as DNS. When those
+ * services have been shut down, we send a notification with
+ * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_OFFLINE.
+ *
+ * When 'offline' changes from true to false, then after
+ * network services have been restarted, we send a notification
+ * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_ONLINE.
+ */
+#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline"
+#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed"
+#define NS_IOSERVICE_OFFLINE "offline"
+#define NS_IOSERVICE_ONLINE "online"
+
+%}
+
+[uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)]
+interface nsIIOServiceInternal : nsISupports
+{
+ /**
+ * This is an internal method that should only be called from ContentChild
+ * in order to pass the connectivity state from the chrome process to the
+ * content process. It throws if called outside the content process.
+ */
+ void SetConnectivity(in boolean connectivity);
+
+ /**
+ * An internal method to asynchronously run our notifications that happen
+ * when we wake from sleep
+ */
+ void NotifyWakeup();
+};
diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl
new file mode 100644
index 0000000000..3ae363c488
--- /dev/null
+++ b/netwerk/base/nsIIncrementalDownload.idl
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIRequestObserver;
+
+/**
+ * An incremental download object attempts to fetch a file piecemeal over time
+ * in an effort to minimize network bandwidth usage.
+ *
+ * Canceling a background download does not cause the file on disk to be
+ * deleted.
+ */
+[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)]
+interface nsIIncrementalDownload : nsIRequest
+{
+ /**
+ * Initialize the incremental download object. If the destination file
+ * already exists, then only the remaining portion of the file will be
+ * fetched.
+ *
+ * NOTE: The downloader will create the destination file if it does not
+ * already exist. It will create the file with the permissions 0600 if
+ * needed. To affect the permissions of the file, consumers of this
+ * interface may create an empty file at the specified destination prior to
+ * starting the incremental download.
+ *
+ * NOTE: Since this class may create a temporary file at the specified
+ * destination, it is advisable for the consumer of this interface to specify
+ * a file name for the destination that would not tempt the user into
+ * double-clicking it. For example, it might be wise to append a file
+ * extension like ".part" to the end of the destination to protect users from
+ * accidentally running "blah.exe" before it is a complete file.
+ *
+ * @param uri
+ * The URI to fetch.
+ * @param destination
+ * The location where the file is to be stored.
+ * @param chunkSize
+ * The size of the chunks to fetch. A non-positive value results in
+ * the default chunk size being used.
+ * @param intervalInSeconds
+ * The amount of time to wait between fetching chunks. Pass a
+ * negative to use the default interval, or 0 to fetch the remaining
+ * part of the file in one chunk.
+ */
+ void init(in nsIURI uri, in nsIFile destination, in long chunkSize,
+ in long intervalInSeconds);
+
+ /**
+ * The URI being fetched.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The URI being fetched after any redirects have been followed. This
+ * attribute is set just prior to calling OnStartRequest on the observer
+ * passed to the start method.
+ */
+ readonly attribute nsIURI finalURI;
+
+ /**
+ * The file where the download is being written.
+ */
+ readonly attribute nsIFile destination;
+
+ /**
+ * The total number of bytes for the requested file. This attribute is set
+ * just prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the total size is unknown.
+ */
+ readonly attribute long long totalSize;
+
+ /**
+ * The current number of bytes downloaded so far. This attribute is set just
+ * prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the current size is unknown.
+ */
+ readonly attribute long long currentSize;
+
+ /**
+ * Start the incremental download.
+ *
+ * @param observer
+ * An observer to be notified of various events. OnStartRequest is
+ * called when finalURI and totalSize have been determined or when an
+ * error occurs. OnStopRequest is called when the file is completely
+ * downloaded or when an error occurs. If this object implements
+ * nsIProgressEventSink, then its OnProgress method will be called as
+ * data is written to the destination file. If this object implements
+ * nsIInterfaceRequestor, then it will be assigned as the underlying
+ * channel's notification callbacks, which allows it to provide a
+ * nsIAuthPrompt implementation if needed by the channel, for example.
+ * @param ctxt
+ * User defined object forwarded to the observer's methods.
+ */
+ void start(in nsIRequestObserver observer,
+ in nsISupports ctxt);
+};
diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl
new file mode 100644
index 0000000000..aef56ed41f
--- /dev/null
+++ b/netwerk/base/nsIIncrementalStreamLoader.idl
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThreadRetargetableStreamListener.idl"
+
+interface nsIRequest;
+interface nsIIncrementalStreamLoader;
+
+[scriptable, uuid(07c3d2cc-5454-4618-9f4f-cd93de9504a4)]
+interface nsIIncrementalStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when new data has arrived on the stream.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param dataLength the length of the new data received
+ * @param data the contents of the new data received.
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to not accumulate all or portional of the data in
+ * the internal buffer, the consumedLength shall be set to the value of
+ * the dataLength or less. By default the consumedLength value is assumed 0.
+ * The data and dataLength reflect the non-consumed data and will be
+ * accumulated if consumedLength is not set.
+ *
+ * In comparison with onStreamComplete(), the data buffer cannot be
+ * adopted if this method returns NS_SUCCESS_ADOPTED_DATA.
+ */
+ void onIncrementalData(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in unsigned long dataLength,
+ [const,array,size_is(dataLength)] in octet data,
+ inout unsigned long consumedLength);
+
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIIncrementalStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIIncrementalStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(a023b060-ba23-431a-b449-2dd63e220554)]
+interface nsIIncrementalStreamLoader : nsIThreadRetargetableStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aObserver
+ * An observer that will be notified when the data is complete.
+ */
+ void init(in nsIIncrementalStreamLoaderObserver aObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIInputStreamChannel.idl b/netwerk/base/nsIInputStreamChannel.idl
new file mode 100644
index 0000000000..9172a2b283
--- /dev/null
+++ b/netwerk/base/nsIInputStreamChannel.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIURI;
+
+/**
+ * nsIInputStreamChannel
+ *
+ * This interface provides methods to initialize an input stream channel.
+ * The input stream channel serves as a data pump for an input stream.
+ */
+[scriptable, uuid(ea730238-4bfd-4015-8489-8f264d05b343)]
+interface nsIInputStreamChannel : nsISupports
+{
+ /**
+ * Sets the URI for this channel. This must be called before the
+ * channel is opened, and it may only be called once.
+ */
+ void setURI(in nsIURI aURI);
+
+ /**
+ * Get/set the content stream
+ *
+ * This stream contains the data that will be pushed to the channel's
+ * stream listener. If the stream is non-blocking and supports the
+ * nsIAsyncInputStream interface, then the stream will be read directly.
+ * Otherwise, the stream will be read on a background thread.
+ *
+ * This attribute must be set before the channel is opened, and it may
+ * only be set once.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if the setter is called after the channel
+ * has been opened.
+ */
+ attribute nsIInputStream contentStream;
+
+ /**
+ * Get/set the srcdoc data string. When the input stream channel is
+ * created to load a srcdoc iframe, this is set to hold the value of the
+ * srcdoc attribute.
+ *
+ * This should be the same value used to create contentStream, but this is
+ * not checked.
+ *
+ * Changing the value of this attribute will not otherwise affect the
+ * functionality of the channel or input stream.
+ */
+ attribute AString srcdocData;
+
+ /**
+ * Returns true if srcdocData has been set within the channel.
+ */
+ readonly attribute boolean isSrcdocChannel;
+
+ /**
+ * The base URI to be used for the channel. Used when the base URI cannot
+ * be inferred by other means, for example when this is a srcdoc channel.
+ */
+ attribute nsIURI baseURI;
+};
diff --git a/netwerk/base/nsIInputStreamPump.idl b/netwerk/base/nsIInputStreamPump.idl
new file mode 100644
index 0000000000..2bd1ee1683
--- /dev/null
+++ b/netwerk/base/nsIInputStreamPump.idl
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsISerialEventTarget;
+interface nsIStreamListener;
+
+/**
+ * nsIInputStreamPump
+ *
+ * This interface provides a means to configure and use a input stream pump
+ * instance. The input stream pump will asynchronously read from an input
+ * stream, and push data to an nsIStreamListener instance. It utilizes the
+ * current thread's nsIEventTarget in order to make reading from the stream
+ * asynchronous. A different thread can be used if the pump also implements
+ * nsIThreadRetargetableRequest.
+ *
+ * If the given stream supports nsIAsyncInputStream, then the stream pump will
+ * call the stream's AsyncWait method to drive the stream listener. Otherwise,
+ * the stream will be read on a background thread utilizing the stream
+ * transport service. More details are provided below.
+ */
+[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)]
+interface nsIInputStreamPump : nsIRequest
+{
+ /**
+ * Initialize the input stream pump.
+ *
+ * @param aStream
+ * contains the data to be read. if the input stream is non-blocking,
+ * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds
+ * then the stream will be read directly. otherwise, it will be read
+ * on a background thread using the stream transport service.
+ * @param aSegmentSize
+ * if the stream transport service is used, then this parameter
+ * specifies the segment size for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aSegmentCount
+ * if the stream transport service is used, then this parameter
+ * specifies the segment count for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aCloseWhenDone
+ * if true, the input stream will be closed after it has been read.
+ * @param aMainThreadTarget
+ * a labeled main therad event target.
+ */
+ void init(in nsIInputStream aStream,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount,
+ in boolean aCloseWhenDone,
+ [optional] in nsISerialEventTarget aMainThreadTarget);
+
+ /**
+ * asyncRead causes the input stream to be read in chunks and delivered
+ * asynchronously to the listener via OnDataAvailable.
+ *
+ * @param aListener
+ * receives notifications.
+ * @param aListenerContext
+ * passed to listener methods.
+ */
+ void asyncRead(in nsIStreamListener aListener);
+};
diff --git a/netwerk/base/nsIInterceptionInfo.idl b/netwerk/base/nsIInterceptionInfo.idl
new file mode 100644
index 0000000000..d3c1b030ac
--- /dev/null
+++ b/netwerk/base/nsIInterceptionInfo.idl
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIPrincipal;
+interface nsIRedirectHistoryEntry;
+
+%{C++
+#include "nsTArray.h"
+%}
+
+[ref] native nsIRedirectHistoryEntryArray(const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>);
+/**
+ * nsIInterceptionInfo is used to record the needed information of the
+ * InterceptedHttpChannel.
+ * This infomration need to be propagated to the new channel which created by
+ * FetchEvent.request or ServiceWorker NavigationPreload.
+ */
+[scriptable, builtinclass, uuid(8b9cd81f-3cd1-4f6a-9086-92a9bbf055f4)]
+interface nsIInterceptionInfo : nsISupports
+{
+ /**
+ * InterceptedHttpChannel's triggering principal
+ */
+ [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)]
+ nsIPrincipal binaryTriggeringPrincipal();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetTriggeringPrincipal)]
+ void binarySetTriggeringPrincipal(in nsIPrincipal aPrincipal);
+
+ /**
+ * InterceptedHttpChannel's content policy type
+ */
+ [noscript, notxpcom, nostdcall, binaryname(ContentPolicyType)]
+ nsContentPolicyType binaryContentPolicyType();
+
+ [noscript, notxpcom, nostdcall, binaryname(ExternalContentPolicyType)]
+ nsContentPolicyType binaryExternalContentPolicyType();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetContentPolicyType)]
+ void binarySetContentPolicyType(in nsContentPolicyType aContentPolicyType);
+
+%{ C++
+ inline ExtContentPolicyType GetExtContentPolicyType()
+ {
+ return static_cast<ExtContentPolicyType>(ExternalContentPolicyType());
+ }
+%}
+
+ /**
+ * The InterceptedHttpChannel's redirect chain
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChain)]
+ nsIRedirectHistoryEntryArray binaryRedirectChain();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetRedirectChain)]
+ void binarySetRedirectChain(
+ in nsIRedirectHistoryEntryArray aRedirectChain);
+
+ /**
+ * The InterceptedHttpChannel is a third party channel or not.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(FromThirdParty)]
+ bool binaryFromThirdParty();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetFromThirdParty)]
+ void binarySetFromThirdParty(in bool aFromThirdParty);
+};
diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl
new file mode 100644
index 0000000000..bbf2b5d48a
--- /dev/null
+++ b/netwerk/base/nsILoadContextInfo.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+%}
+native OriginAttributesNativePtr(const mozilla::OriginAttributes*);
+
+interface nsILoadContext;
+interface nsIDOMWindow;
+
+/**
+ * Helper interface to carry informatin about the load context
+ * encapsulating origin attributes and IsAnonymous, IsPrivite properties.
+ * It shall be used where nsILoadContext cannot be used or is not
+ * available.
+ */
+
+[scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)]
+interface nsILoadContextInfo : nsISupports
+{
+ /**
+ * Whether the context is in a Private Browsing mode
+ */
+ readonly attribute boolean isPrivate;
+
+ /**
+ * Whether the load is initiated as anonymous
+ */
+ readonly attribute boolean isAnonymous;
+
+ /**
+ * OriginAttributes hiding all the security context attributes
+ */
+ [implicit_jscontext]
+ readonly attribute jsval originAttributes;
+ [noscript, notxpcom, nostdcall, binaryname(OriginAttributesPtr)]
+ OriginAttributesNativePtr binaryOriginAttributesPtr();
+
+%{C++
+ /**
+ * De-XPCOMed getters
+ */
+ bool IsPrivate()
+ {
+ bool pb;
+ GetIsPrivate(&pb);
+ return pb;
+ }
+
+ bool IsAnonymous()
+ {
+ bool anon;
+ GetIsAnonymous(&anon);
+ return anon;
+ }
+
+ bool Equals(nsILoadContextInfo *aOther)
+ {
+ return IsAnonymous() == aOther->IsAnonymous() &&
+ *OriginAttributesPtr() == *aOther->OriginAttributesPtr();
+ }
+%}
+};
+
+/**
+ * Since OriginAttributes struct limits the implementation of
+ * nsILoadContextInfo (that needs to be thread safe) to C++,
+ * we need a scriptable factory to create instances of that
+ * interface from JS.
+ */
+[scriptable, uuid(c1c7023d-4318-4f99-8307-b5ccf0558793)]
+interface nsILoadContextInfoFactory : nsISupports
+{
+ readonly attribute nsILoadContextInfo default;
+ readonly attribute nsILoadContextInfo private;
+ readonly attribute nsILoadContextInfo anonymous;
+ [implicit_jscontext]
+ nsILoadContextInfo custom(in boolean aAnonymous, in jsval aOriginAttributes);
+ nsILoadContextInfo fromLoadContext(in nsILoadContext aLoadContext, in boolean aAnonymous);
+ nsILoadContextInfo fromWindow(in nsIDOMWindow aWindow, in boolean aAnonymous);
+};
diff --git a/netwerk/base/nsILoadGroup.idl b/netwerk/base/nsILoadGroup.idl
new file mode 100644
index 0000000000..06e51e442b
--- /dev/null
+++ b/netwerk/base/nsILoadGroup.idl
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsISimpleEnumerator;
+interface nsIRequestObserver;
+interface nsIInterfaceRequestor;
+interface nsIRequestContext;
+
+/**
+ * A load group maintains a collection of nsIRequest objects.
+ * This is used in lots of places where groups of requests need to be tracked.
+ * For example, Document::mDocumentLoadGroup is used to track all requests
+ * made for subdocuments in order to track page load progress and allow all
+ * requests made on behalf of the document to be stopped, etc.
+ */
+[builtinclass, scriptable, uuid(f0c87725-7a35-463c-9ceb-2c07f23406cc)]
+interface nsILoadGroup : nsIRequest
+{
+ /**
+ * The group observer is notified when requests are added to and removed
+ * from this load group. The groupObserver is weak referenced.
+ */
+ attribute nsIRequestObserver groupObserver;
+
+ /**
+ * Accesses the default load request for the group. Each time a number
+ * of requests are added to a group, the defaultLoadRequest may be set
+ * to indicate that all of the requests are related to a base request.
+ *
+ * The load group inherits its load flags from the default load request.
+ * If the default load request is NULL, then the group's load flags are
+ * not changed.
+ */
+ attribute nsIRequest defaultLoadRequest;
+
+ /**
+ * Adds a new request to the group. This will cause the default load
+ * flags to be applied to the request. If this is a foreground
+ * request then the groupObserver's onStartRequest will be called.
+ *
+ * If the request is the default load request or if the default load
+ * request is null, then the load group will inherit its load flags from
+ * the request.
+ */
+ void addRequest(in nsIRequest aRequest,
+ in nsISupports aContext);
+
+ /**
+ * Removes a request from the group. If this is a foreground request
+ * then the groupObserver's onStopRequest will be called.
+ *
+ * By the time this call ends, aRequest will have been removed from the
+ * loadgroup, even if this function throws an exception.
+ */
+ void removeRequest(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatus);
+
+ /**
+ * Returns the requests contained directly in this group.
+ * Enumerator element type: nsIRequest.
+ */
+ readonly attribute nsISimpleEnumerator requests;
+
+ /**
+ * Returns the count of "active" requests (ie. requests without the
+ * LOAD_BACKGROUND bit set).
+ */
+ readonly attribute unsigned long activeCount;
+
+ /**
+ * Notification callbacks for the load group.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Context for managing things like js/css connection blocking,
+ * and per-tab connection grouping.
+ */
+ readonly attribute unsigned long long requestContextID;
+
+ /**
+ * The set of load flags that will be added to all new requests added to
+ * this group. Any existing requests in the load group are not modified,
+ * so it is expected these flags will be added before requests are added
+ * to the group - typically via nsIDocShell::defaultLoadFlags on a new
+ * docShell.
+ * Note that these flags are *not* added to the default request for the
+ * load group; it is expected the default request will already have these
+ * flags (again, courtesy of setting nsIDocShell::defaultLoadFlags before
+ * the docShell has created the default request.)
+ */
+ attribute nsLoadFlags defaultLoadFlags;
+
+ /**
+ * Returns true if the loadGroup belongs to a discarded context, such as, a
+ * terminated private browsing session.
+ */
+ [infallible]
+ readonly attribute boolean isBrowsingContextDiscarded;
+};
diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl
new file mode 100644
index 0000000000..959913a474
--- /dev/null
+++ b/netwerk/base/nsILoadGroupChild.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+/**
+ * nsILoadGroupChild provides a hierarchy of load groups so that the
+ * root load group can be used to conceptually tie a series of loading
+ * operations into a logical whole while still leaving them separate
+ * for the purposes of cancellation and status events.
+ */
+
+[builtinclass, scriptable, uuid(02efe8e2-fbbc-4718-a299-b8a09c60bf6b)]
+interface nsILoadGroupChild : nsISupports
+{
+ /**
+ * The parent of this load group. It is stored with
+ * a nsIWeakReference/nsWeakPtr so there is no requirement for the
+ * parentLoadGroup to out live the child, nor will the child keep a
+ * reference count on the parent.
+ */
+ attribute nsILoadGroup parentLoadGroup;
+
+ /**
+ * The nsILoadGroup associated with this nsILoadGroupChild
+ */
+ readonly attribute nsILoadGroup childLoadGroup;
+
+ /**
+ * The rootLoadGroup is the recursive parent of this
+ * load group where parent is defined as parentlLoadGroup if set
+ * or childLoadGroup.loadGroup as a backup. (i.e. parentLoadGroup takes
+ * precedence.) The nsILoadGroup child is the root if neither parent
+ * nor loadgroup attribute is specified.
+ */
+ readonly attribute nsILoadGroup rootLoadGroup;
+};
diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
new file mode 100644
index 0000000000..ddfcb223e6
--- /dev/null
+++ b/netwerk/base/nsILoadInfo.idl
@@ -0,0 +1,1535 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+#include "nsIScriptSecurityManager.idl"
+#include "nsIInterceptionInfo.idl"
+
+interface nsIChannel;
+interface nsIContentSecurityPolicy;
+interface nsICookieJarSettings;
+interface nsICSPEventListener;
+interface nsINode;
+interface nsIPrincipal;
+interface nsIRedirectHistoryEntry;
+interface nsIURI;
+webidl Document;
+webidl BrowsingContext;
+native LoadContextRef(already_AddRefed<nsISupports>);
+%{C++
+#include "nsTArray.h"
+#include "mozilla/LoadTainting.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsRFPService.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace dom {
+class ClientInfo;
+class ClientSource;
+class PerformanceStorage;
+class ServiceWorkerDescriptor;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native nsIRedirectHistoryEntryArray(const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>);
+native OriginAttributes(mozilla::OriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
+[ref] native CStringArrayRef(const nsTArray<nsCString>);
+[ref] native StringArrayRef(const nsTArray<nsString>);
+[ref] native Uint64ArrayRef(const nsTArray<uint64_t>);
+[ref] native PrincipalArrayRef(const nsTArray<nsCOMPtr<nsIPrincipal>>);
+[ref] native const_ClientInfoRef(const mozilla::dom::ClientInfo);
+ native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>);
+ native UniqueClientSourceMove(mozilla::UniquePtr<mozilla::dom::ClientSource>&&);
+[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>);
+[ref] native const_ServiceWorkerDescriptorRef(const mozilla::dom::ServiceWorkerDescriptor);
+[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>);
+[ref] native const_MaybeRFPTarget(const mozilla::Maybe<mozilla::RFPTarget>);
+ native RFPTarget(mozilla::RFPTarget);
+[ptr] native PerformanceStoragePtr(mozilla::dom::PerformanceStorage);
+ native LoadTainting(mozilla::LoadTainting);
+ native CSPRef(already_AddRefed<nsIContentSecurityPolicy>);
+
+typedef unsigned long nsSecurityFlags;
+
+/**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+[scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)]
+interface nsILoadInfo : nsISupports
+{
+ /**
+ * The following five flags determine the security mode and hence what kind of
+ * security checks should be performed throughout the lifetime of the channel.
+ *
+ * * SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT
+ * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ * * SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
+ * * SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
+ * * SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
+ *
+ * Exactly one of these flags are required to be set in order to allow
+ * the channel to perform the correct security checks (SOP, CORS, ...) and
+ * return the correct result principal. If none or more than one of these
+ * flags are set AsyncOpen will fail.
+ */
+
+ /**
+ * Warning: Never use this flag when creating a new channel!
+ * Only use this flag if you have to create a temporary LoadInfo
+ * for performing an explicit nsIContentPolicy check, like e.g.
+ * when loading something from the cache that needs an explicit
+ * nsIContentPolicy check. In all other cases pick one of the
+ * security flags underneath.
+ */
+ const unsigned long SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK = 0;
+
+ /*
+ * Enforce the same origin policy where loads inherit the principal.
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT = (1<<0);
+
+ /*
+ * Enforce the same origin policy and data: loads are blocked.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1);
+
+ /**
+ * Allow loads from other origins. Loads which inherit the principal should
+ * see the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ *
+ * Commonly used by plain <img>, <video>, <link rel=stylesheet> etc.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT = (1 << 2);
+
+ /**
+ * Allow loads from other origins. Loads from data: will be allowed,
+ * but the resulting resource will get a null principal.
+ * Used in blink/webkit for <iframe>s. Likely also the mode
+ * that should be used by most Chrome code.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL = (1<<3);
+
+ /**
+ * Allow loads from any origin, but require CORS for cross-origin loads.
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ *
+ * Commonly used by <img crossorigin>, <video crossorigin>,
+ * XHR, fetch(), etc.
+ */
+ const unsigned long SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT = (1<<4);
+
+ /**
+ * Choose cookie policy. The default policy is equivalent to "INCLUDE" for
+ * SEC_REQUIRE_SAME_ORIGIN_* and SEC_ALLOW_CROSS_ORIGIN_* modes, and
+ * equivalent to "SAME_ORIGIN" for SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT mode.
+ *
+ * This means that if you want to perform a CORS load with credentials, pass
+ * SEC_COOKIES_INCLUDE.
+ *
+ * Note that these flags are still subject to the user's cookie policies.
+ * For example, if the user is blocking 3rd party cookies, those cookies
+ * will be blocked no matter which of these flags are set.
+ */
+ const unsigned long SEC_COOKIES_DEFAULT = (0 << 5);
+ const unsigned long SEC_COOKIES_INCLUDE = (1 << 5);
+ const unsigned long SEC_COOKIES_SAME_ORIGIN = (2 << 5);
+ const unsigned long SEC_COOKIES_OMIT = (3 << 5);
+
+ /**
+ * Force inheriting of the principal. See the documentation for
+ * principalToInherit, which describes exactly what principal is inherited.
+ *
+ * Setting this flag will cause GetChannelResultPrincipal to return the
+ * principal to be inherited as the channel principal.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ *
+ * So if the principal that gets inherited is "http://a.com/", and the channel
+ * is loading the URI "http://b.com/whatever", GetChannelResultPrincipal
+ * will return a principal from "http://a.com/".
+ *
+ * This flag can not be used together with SANDBOXED_ORIGIN sandbox flag. If
+ * both are passed to the LoadInfo constructor then this flag will be dropped.
+ * If you need to know whether this flag would have been present but was dropped
+ * due to sandboxing, check for the forceInheritPrincipalDropped flag.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL = (1<<7);
+
+ /**
+ * Inherit the Principal for about:blank.
+ */
+ const unsigned long SEC_ABOUT_BLANK_INHERITS = (1<<9);
+
+ /**
+ * Allow access to chrome: packages that are content accessible.
+ */
+ const unsigned long SEC_ALLOW_CHROME = (1<<10);
+
+ /**
+ * Disallow access to javascript: uris.
+ */
+ const unsigned long SEC_DISALLOW_SCRIPT = (1<<11);
+
+ /**
+ * Don't follow redirects. Instead the redirect response is returned
+ * as a successful response for the channel.
+ *
+ * Redirects not initiated by a server response, i.e. REDIRECT_INTERNAL and
+ * REDIRECT_STS_UPGRADE, are still followed.
+ *
+ * Note: If this flag is set and the channel response is a redirect, then
+ * the response body might not be available.
+ * This can happen if the redirect was cached.
+ */
+ const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<12);
+
+ /**
+ * Load an error page, it should be one of following : about:neterror,
+ * about:certerror, about:blocked, about:tabcrashed or about:restartrequired.
+ */
+ const unsigned long SEC_LOAD_ERROR_PAGE = (1<<13);
+
+ /**
+ * Force inheriting of the principal, overruling any owner that might be set
+ * on the channel. (Please note that channel.owner is deprecated and will be
+ * removed within Bug 1286838). See the documentation for principalToInherit,
+ * which describes exactly what principal is inherited.
+ *
+ * Setting this flag will cause GetChannelResultPrincipal to return the
+ * principal to be inherited as the channel principal.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER = (1<<14);
+
+ /**
+ * This is the principal of the network request's caller/requester where
+ * the resulting resource will be used. I.e. it is the principal which
+ * will get access to the result of the request. (Where "get access to"
+ * might simply mean "embed" depending on the type of resource that is
+ * loaded).
+ *
+ * For example for an image, it is the principal of the document where
+ * the image is rendered. For a stylesheet it is the principal of the
+ * document where the stylesheet will be applied.
+ *
+ * So if document at http://a.com/page.html loads an image from
+ * http://b.com/pic.jpg, then loadingPrincipal will be
+ * http://a.com/page.html.
+ *
+ * For <iframe> and <frame> loads, the LoadingPrincipal is the
+ * principal of the parent document. For top-level loads, the
+ * LoadingPrincipal is null. For all loads except top-level loads
+ * the LoadingPrincipal is never null.
+ *
+ * If the loadingPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the loadingPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * content principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal loadingPrincipal;
+
+ /**
+ * A C++-friendly version of triggeringPrincipal.
+ *
+ * This is a bit awkward because we can't use
+ * binaryname(GetLoadingPrincipal).
+ */
+ [noscript, notxpcom, nostdcall]
+ nsIPrincipal virtualGetLoadingPrincipal();
+
+%{C++
+ nsIPrincipal* GetLoadingPrincipal() {
+ return VirtualGetLoadingPrincipal();
+ }
+%}
+
+ /**
+ * This is the principal which caused the network load to start. I.e.
+ * this is the principal which provided the URL to be loaded. This is
+ * often the same as the LoadingPrincipal, but there are a few cases
+ * where that's not true.
+ *
+ * For example for loads into an <iframe>, the LoadingPrincipal is always
+ * the principal of the parent document. However the triggeringPrincipal
+ * is the principal of the document which provided the URL that the
+ * <iframe> is navigating to. This could be the previous document inside
+ * the <iframe> which set document.location. Or a document elsewhere in
+ * the frame tree which contained a <a target="..."> which targetted the
+ * <iframe>.
+ *
+ * If a stylesheet links to a sub-resource, like an @imported stylesheet,
+ * or a background image, then the triggeringPrincipal is the principal
+ * of the stylesheet, while the LoadingPrincipal is the principal of the
+ * document being styled.
+ *
+ * The triggeringPrincipal is never null.
+ *
+ * If the triggeringPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the triggeringPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * content principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * A C++-friendly version of triggeringPrincipal.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)]
+ nsIPrincipal binaryTriggeringPrincipal();
+
+ /**
+ * The remote type of the process which caused the network load to start. I.e.
+ * this is the remote type of the process which provided the URL to be loaded.
+ *
+ * For subresource loads, this should be the same as the process which will
+ * handle the response, however for document loads this may both be different
+ * than the final process, as well as different from the process which starts
+ * the navigation.
+ *
+ * This field is intentionally not perfectly preserved over IPC, and will be
+ * reset to the remote type of the sending process when sent from a content
+ * process to the parent process.
+ */
+ attribute AUTF8String triggeringRemoteType;
+
+ /**
+ * For non-document loads the principalToInherit is always null. For
+ * loads of type TYPE_DOCUMENT or TYPE_SUBDOCUMENT the principalToInherit
+ * might be null. If it's non null, then this is the principal that is
+ * inherited if a principal needs to be inherited. If the principalToInherit
+ * is null but the inherit flag is set, then the triggeringPrincipal is
+ * the principal that is inherited.
+ */
+ attribute nsIPrincipal principalToInherit;
+
+ /**
+ * A C++-friendly version of principalToInherit.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(PrincipalToInherit)]
+ nsIPrincipal binaryPrincipalToInherit();
+
+ /**
+ * Finds the correct principal to inherit for the given channel, based on
+ * the values of PrincipalToInherit and TriggeringPrincipal.
+ */
+ [noscript, notxpcom, nostdcall]
+ nsIPrincipal FindPrincipalToInherit(in nsIChannel aChannel);
+
+ /**
+ * This is the ownerDocument of the LoadingNode. Unless the LoadingNode
+ * is a Document, in which case the LoadingDocument is the same as the
+ * LoadingNode.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingDocument is null. When the LoadingDocument is not null, the
+ * LoadingPrincipal is set to the principal of the LoadingDocument.
+ */
+ readonly attribute Document loadingDocument;
+
+ /**
+ * A C++-friendly version of loadingDocument (loadingNode).
+ * This is the Node where the resulting resource will be used. I.e. it is
+ * the Node which will get access to the result of the request. (Where
+ * "get access to" might simply mean "embed" depending on the type of
+ * resource that is loaded).
+ *
+ * For example for an <img>/<video> it is the image/video element. For
+ * document loads inside <iframe> and <frame>s, the LoadingNode is the
+ * <iframe>/<frame> element. For an XMLHttpRequest, it is the Document
+ * which contained the JS which initiated the XHR. For a stylesheet, it
+ * is the Document that contains <link rel=stylesheet>.
+ *
+ * For loads triggered by the HTML pre-parser, the LoadingNode is the
+ * Document which is currently being parsed.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingNode is null. If the LoadingNode is non-null, then the
+ * LoadingPrincipal is the principal of the LoadingNode.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(LoadingNode)]
+ nsINode binaryLoadingNode();
+
+ /**
+ * A C++ friendly version of the loadingContext for toplevel loads.
+ * Most likely you want to query the ownerDocument or LoadingNode
+ * and not this context only available for TYPE_DOCUMENT loads.
+ * Please note that except for loads of TYPE_DOCUMENT, this
+ * ContextForTopLevelLoad will always return null.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(ContextForTopLevelLoad)]
+ LoadContextRef binaryContextForTopLevelLoad();
+
+ /**
+ * For all loads except loads of TYPE_DOCUMENT, the loadingContext
+ * simply returns the loadingNode. For loads of TYPE_DOCUMENT this
+ * will return the context available for top-level loads which
+ * do not have a loadingNode.
+ */
+ [binaryname(LoadingContextXPCOM)]
+ readonly attribute nsISupports loadingContext;
+
+ /**
+ * A C++ friendly version of the loadingContext.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(GetLoadingContext)]
+ LoadContextRef binaryGetLoadingContext();
+
+ /**
+ * The securityFlags of that channel.
+ */
+ readonly attribute nsSecurityFlags securityFlags;
+
+%{C++
+ inline nsSecurityFlags GetSecurityFlags()
+ {
+ nsSecurityFlags result;
+ mozilla::DebugOnly<nsresult> rv = GetSecurityFlags(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * The sandboxFlags of that channel.
+ */
+ [infallible] readonly attribute unsigned long sandboxFlags;
+
+ /**
+ * The TriggingSandboxFlags are the SandboxFlags of the entity
+ * responsible for causing the load to occur.
+ */
+ [infallible] attribute unsigned long triggeringSandboxFlags;
+
+
+ /**
+ * The window id and storage access status of the window of the
+ * context that triggered the load. This is used to allow self-initiated
+ * same-origin navigations to propogate their "has storage access" bit
+ * to the next Document.
+ */
+ [infallible] attribute unsigned long long triggeringWindowId;
+ [infallible] attribute boolean triggeringStorageAccess;
+
+ /**
+ * Allows to query only the security mode bits from above.
+ */
+ [infallible] readonly attribute unsigned long securityMode;
+
+ /**
+ * This flag is used for any browsing context where we should not sniff
+ * the content type. E.g if an iframe has the XCTO nosniff header, then
+ * that flag is set to true so we skip content sniffing for that browsing
+ * context.
+ */
+ [infallible] attribute boolean skipContentSniffing;
+
+ /**
+ * (default) If this flag is set, it has not yet been determined if the
+ * HTTPS-Only mode will upgrade the request.
+ */
+ const unsigned long HTTPS_ONLY_UNINITIALIZED = (1 << 0);
+
+ /**
+ * Indicates that this is the first time the request gets upgraded, and thus
+ * the HTTPS-Only StreamListener hasn't been registered yet. Even though there
+ * might be multiple channels per request that have to be upgraded (e.g.,
+ * because of redirects), the StreamListener only has to be attached to one
+ * channel.
+ */
+ const unsigned long HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED = (1 << 1);
+
+ /**
+ * Indicates that the request will get upgraded, and the HTTPS-Only
+ * StreamListener got registered.
+ */
+ const unsigned long HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED = (1 << 2);
+
+ /**
+ * This flag can be manually set if the HTTPS-Only mode should exempt the
+ * request and not upgrade it. (e.g in the case of OCSP.
+ */
+ const unsigned long HTTPS_ONLY_EXEMPT = (1 << 3);
+
+ /**
+ * This flag can only ever be set on top-level loads. It indicates
+ * that the top-level https connection succeeded. This flag is mostly
+ * used to counter time-outs which allows to cancel the channel
+ * if the https load has not started.
+ */
+ const unsigned long HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS = (1 << 4);
+
+ /**
+ * This flag can only ever be set on downloads. It indicates
+ * that the download https connection succeeded. This flag is mostly
+ * used to counter time-outs which allows to cancel the channel
+ * if the https load has not started.
+ */
+ const unsigned long HTTPS_ONLY_DOWNLOAD_IN_PROGRESS = (1 << 5);
+
+ /**
+ * This flag indicates that the request should not be logged to the
+ * console.
+ */
+ const unsigned long HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE = (1 << 6);
+
+ /**
+ * This flag indicates that the request was upgraded by https-first mode.
+ */
+ const unsigned long HTTPS_ONLY_UPGRADED_HTTPS_FIRST = (1 << 7);
+
+ /**
+ * This flag indicates that the request should not be blocked by ORB.
+ */
+ const unsigned long HTTPS_ONLY_BYPASS_ORB = (1 << 8);
+
+ /**
+ * This flag indicates that HTTPS_ONLY_EXEMPT should be
+ * set the next time HTTPS-Only exemptions are checked
+ * and HTTPS-First is enabled.
+ */
+ const unsigned long HTTPS_FIRST_EXEMPT_NEXT_LOAD = (1 << 9);
+
+ /**
+ * Upgrade state of HTTPS-Only Mode. The flag HTTPS_ONLY_EXEMPT can get
+ * set on requests that should be excempt from an upgrade.
+ */
+ [infallible] attribute unsigned long httpsOnlyStatus;
+
+ /**
+ * Reflects whetehr this is an HTTP Strict Transport Security host
+ */
+[infallible] attribute boolean hstsStatus;
+
+ /**
+ * Returns true if at the time of the loadinfo construction the document
+ * that triggered this load has the bit hasValidTransientUserGestureActivation
+ * set or the load was triggered from External. (Mostly this bool is used
+ * in the context of Sec-Fetch-User.)
+ */
+ [infallible] attribute boolean hasValidUserGestureActivation;
+
+ /**
+ * We disallow the SystemPrincipal to initiate requests to
+ * the public web. This flag is to allow exceptions.
+ */
+ [infallible] attribute boolean allowDeprecatedSystemRequests;
+
+ /**
+ * Only ever returns true if the loadinfo is of TYPE_SCRIPT and
+ * the script was created by the HTML parser.
+ */
+ [infallible] attribute boolean parserCreatedScript;
+
+ /**
+ * True if this request is known to have been triggered by a user
+ * manually requesting the URI to be saved.
+ */
+ [infallible] attribute boolean isUserTriggeredSave;
+
+ /**
+ * True if this request is from DevTools.
+ */
+ [infallible] attribute boolean isInDevToolsContext;
+
+ /**
+ * True if this request is embedded in a context that can't be third-party
+ * (i.e. an iframe embedded in a cross-origin parent window). If this is
+ * false, then this request may be third-party if it's a third-party to
+ * loadingPrincipal.
+ */
+ [infallible] attribute boolean isInThirdPartyContext;
+
+ /**
+ * True if this request is a third party in respect to the top-level window.
+ *
+ * Note that this doesn't consider the parent window. I.e. It will still
+ * return false even in the case that the parent is cross-origin but the
+ * top-level is same-origin.
+ *
+ * This value would be set during opening the channel in parent and propagate
+ * to the channel in the content.
+ */
+ [infallible] attribute boolean isThirdPartyContextToTopWindow;
+
+ /**
+ * See the SEC_COOKIES_* flags above. This attribute will never return
+ * SEC_COOKIES_DEFAULT, but will instead return what the policy resolves to.
+ * I.e. SEC_COOKIES_SAME_ORIGIN for CORS mode, and SEC_COOKIES_INCLUDE
+ * otherwise.
+ */
+ [infallible] readonly attribute unsigned long cookiePolicy;
+
+ /**
+ * The cookie jar settings inherited from the top-level document's loadInfo.
+ * It cannot be null.
+ */
+ attribute nsICookieJarSettings cookieJarSettings;
+
+ cenum StoragePermissionState : 8 {
+ NoStoragePermission = 0,
+ HasStoragePermission = 1,
+ StoragePermissionAllowListed = 2,
+ };
+
+ /**
+ * The result of the storage permission check of the loading document. This
+ * value would be set during opening the channel.
+ */
+ [infallible] attribute nsILoadInfo_StoragePermissionState
+ storagePermission;
+
+ /**
+ * Get the granular overrides of fingerprinting protections associated to the
+ * channel, the value will override the default fingerprinting protection
+ * settings. This field will only get populated if these is one that comes
+ * from the local granular overrides pref or WebCompat. Otherwise, a value of
+ * Nothing() indicates no granular overrides are present for this channel.
+ *
+ * The RFPTarget defined in the RFPTargets.inc.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeRFPTarget GetOverriddenFingerprintingSettings();
+
+ /**
+ * Set the granular overrides of fingerprinting protections for the channel.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetOverriddenFingerprintingSettings(in RFPTarget aTargets);
+
+ /**
+ * True if the load was triggered by a meta refresh.
+ */
+ [infallible] attribute boolean isMetaRefresh;
+
+ /**
+ * If forceInheritPrincipal is true, the data coming from the channel should
+ * inherit its principal, even when the data is loaded over http:// or another
+ * protocol that would normally use a URI-based principal.
+ *
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ *
+ * This attribute will never be true when loadingSandboxed is true.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipal;
+
+ /**
+ * If forceInheritPrincipalOverruleOwner is true, the data coming from the
+ * channel should inherit the principal, even when the data is loaded over
+ * http:// or another protocol that would normally use a URI-based principal
+ * and even if the channel's .owner is not null. This last is the difference
+ * between forceInheritPrincipalOverruleOwner and forceInheritPrincipal: the
+ * latter does _not_ overrule the .owner setting.
+ *
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalOverruleOwner;
+
+ /**
+ * If loadingSandboxed is true, the data coming from the channel is
+ * being loaded sandboxed, so it should have a nonce origin and
+ * hence should use a NullPrincipal.
+ */
+ [infallible] readonly attribute boolean loadingSandboxed;
+
+ /**
+ * If aboutBlankInherits is true, then about:blank should inherit
+ * the principal.
+ */
+ [infallible] readonly attribute boolean aboutBlankInherits;
+
+ /**
+ * If allowChrome is true, then use nsIScriptSecurityManager::ALLOW_CHROME
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean allowChrome;
+
+ /**
+ * If disallowScript is true, then use nsIScriptSecurityManager::DISALLOW_SCRIPT
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean disallowScript;
+
+%{C++
+ uint32_t CheckLoadURIFlags() {
+ uint32_t flags = nsIScriptSecurityManager::STANDARD;
+ if (GetAllowChrome()) {
+ flags |= nsIScriptSecurityManager::ALLOW_CHROME;
+ }
+ if (GetDisallowScript()) {
+ flags |= nsIScriptSecurityManager::DISALLOW_SCRIPT;
+ }
+ return flags;
+ }
+%}
+
+ /**
+ * Returns true if SEC_DONT_FOLLOW_REDIRECTS is set.
+ */
+ [infallible] readonly attribute boolean dontFollowRedirects;
+
+ /**
+ * Returns true if SEC_LOAD_ERROR_PAGE is set.
+ */
+ [infallible] readonly attribute boolean loadErrorPage;
+
+ /**
+ * True if the load was initiated by a form request.
+ */
+ [infallible] attribute boolean isFormSubmission;
+
+ /**
+ * The external contentPolicyType of the channel, used for security checks
+ * like Mixed Content Blocking and Content Security Policy.
+ *
+ * Specifically, content policy types with _INTERNAL_ in their name will
+ * never get returned from this attribute.
+ */
+ readonly attribute nsContentPolicyType externalContentPolicyType;
+
+ /**
+ * CSP uses this parameter to send or not CSP violation events.
+ * Default value: true.
+ */
+ [infallible] attribute boolean sendCSPViolationEvents;
+
+%{ C++
+ inline ExtContentPolicyType GetExternalContentPolicyType()
+ {
+ nsContentPolicyType result;
+ mozilla::DebugOnly<nsresult> rv = GetExternalContentPolicyType(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return static_cast<ExtContentPolicyType>(result);
+ }
+
+%}
+
+
+ /**
+ * The internal contentPolicyType of the channel, used for constructing
+ * RequestContext values when creating a fetch event for an intercepted
+ * channel.
+ *
+ * This should not be used for the purposes of security checks, since
+ * the content policy implementations cannot be expected to deal with
+ * _INTERNAL_ values. Please use the contentPolicyType attribute above
+ * for that purpose.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(InternalContentPolicyType)]
+ nsContentPolicyType binaryInternalContentPolicyType();
+
+ readonly attribute nsContentPolicyType internalContentPolicyType;
+
+ /**
+ * Returns true if document or any of the documents ancestors
+ * up to the toplevel document make use of the CSP directive
+ * 'block-all-mixed-content'.
+ *
+ * Warning: If the loadingDocument is null, then the
+ * blockAllMixedContent is false.
+ */
+ [infallible] readonly attribute boolean blockAllMixedContent;
+
+ /**
+ * Returns true if document or any of the documents ancestors
+ * up to the toplevel document make use of the CSP directive
+ * 'upgrade-insecure-requests'.
+ *
+ * Warning: If the loadingDocument is null, then the
+ * upgradeInsecureRequests is false.
+ */
+ [infallible] readonly attribute boolean upgradeInsecureRequests;
+
+ /**
+ * Returns true if the the page is https and the content is upgradable from http
+ * requires 'security.mixed_content.upgrade_display_content' pref to be true.
+ * Currently this only upgrades display content but might be expanded to other loads.
+ * This is very similar in implementation to upgradeInsecureRequests but browser set.
+ */
+ [infallible] readonly attribute boolean browserUpgradeInsecureRequests;
+
+ /**
+ * Returns true if the display content was or will get upgraded from http to https.
+ * Requires 'security.mixed_content.upgrade_display_content' pref to be true.
+ * Flag is set purely to collect telemetry.
+ */
+ [infallible] attribute boolean browserDidUpgradeInsecureRequests;
+
+ /**
+ * Returns true if the the page is https and the content is upgradable from http
+ * requires 'security.mixed_content.upgrade_display_content' pref to be false.
+ * See browserUpgradeInsecureRequests for more details, this only happens
+ * when *not* upgrading purely for telemetry.
+ */
+ [infallible] readonly attribute boolean browserWouldUpgradeInsecureRequests;
+
+ /**
+ * If true, toplevel data: URI navigation is allowed
+ */
+ [infallible] attribute boolean forceAllowDataURI;
+
+ /**
+ * If true, insecure redirects to a data: URI are allowed.
+ */
+ [infallible] attribute boolean allowInsecureRedirectToDataURI;
+
+ /**
+ * If true, the content policy security check is excluded from web requests.
+ */
+ [infallible] attribute boolean skipContentPolicyCheckForWebRequest;
+
+ /**
+ * If true, this is the load of a frame's original src attribute
+ */
+ [infallible] attribute boolean originalFrameSrcLoad;
+
+ /**
+ * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info
+ * object is created. Specifically, it will be dropped if the SANDBOXED_ORIGIN
+ * sandbox flag is also present. This flag is set if SEC_FORCE_INHERIT_PRINCIPAL
+ * was dropped.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalDropped;
+
+ /**
+ * This is the inner window ID of the window in which the element being
+ * loaded lives.
+ *
+ * Note that this window ID can be 0 if the window is not
+ * available.
+ */
+ [infallible] readonly attribute unsigned long long innerWindowID;
+
+ /**
+ * The BrowsingContext performing the load for this nsILoadInfo object.
+ */
+ [infallible] readonly attribute unsigned long long browsingContextID;
+ [infallible] readonly attribute BrowsingContext browsingContext;
+
+ /**
+ * The BrowsingContext which the worker is associated.
+ *
+ * Note that this could be 0 if the load is not triggered in a WorkerScope.
+ * This value is only set and used in the parent process for some sitautions
+ * the channel is created in the parent process for Workers. Such as fetch().
+ * In content process, it is always 0.
+ * This value would not be propagated through IPC.
+ */
+ [infallible] attribute unsigned long long workerAssociatedBrowsingContextID;
+ [infallible] readonly attribute BrowsingContext workerAssociatedBrowsingContext;
+
+ /**
+ * Only when the element being loaded is <frame src="foo.html">
+ * (or, more generally, if the element QIs to nsFrameLoaderOwner),
+ * the frameBrowsingContext is the browsing context containing the
+ * foo.html document.
+ *
+ * Note: For other cases, frameBrowsingContextID is 0.
+ */
+ [infallible] readonly attribute unsigned long long frameBrowsingContextID;
+ [infallible] readonly attribute BrowsingContext frameBrowsingContext;
+
+ /**
+ * If the element being loaded is a nsFrameLoaderOwner,
+ * `targetBrowsingContext` is the Browsing Context which will contain the
+ * loading document (see `frameBrowsingContext`). Otherwise, it is the
+ * Browsing Context performing the load (see `browsingContext`).
+ */
+ [infallible] readonly attribute unsigned long long targetBrowsingContextID;
+ [infallible] readonly attribute BrowsingContext targetBrowsingContext;
+
+ /**
+ * Resets the PrincipalToInherit to a freshly created NullPrincipal
+ * which inherits the origin attributes from the loadInfo.
+ *
+ * WARNING: Please only use that function if you know exactly what
+ * you are doing!!!
+ */
+ void resetPrincipalToInheritToNullPrincipal();
+
+ /**
+ * Customized OriginAttributes within LoadInfo to allow overwriting of the
+ * default originAttributes from the loadingPrincipal.
+ *
+ * In chrome side, originAttributes.privateBrowsingId will always be 0 even if
+ * the usePrivateBrowsing is true, because chrome docshell won't set
+ * privateBrowsingId on origin attributes (See bug 1278664). This is to make
+ * sure nsILoadInfo and nsILoadContext have the same origin attributes.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ OriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+%{ C++
+ inline mozilla::OriginAttributes GetOriginAttributes()
+ {
+ mozilla::OriginAttributes result;
+ mozilla::DebugOnly<nsresult> rv = GetOriginAttributes(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * Whenever a channel is evaluated by the ContentSecurityManager
+ * the first time, we set this flag to true to indicate that
+ * subsequent calls of AsyncOpen() do not have to enforce all
+ * security checks again. E.g., after a redirect there is no
+ * need to set up CORS again. We need this separate flag
+ * because the redirectChain might also contain internal
+ * redirects which might pollute the redirectChain so we can't
+ * rely on the size of the redirectChain-array to query whether
+ * a channel got redirected or not.
+ *
+ * Please note, once the flag is set to true it must remain true
+ * throughout the lifetime of the channel. Trying to set it
+ * to anything else than true will be discarded.
+ *
+ */
+ [infallible] attribute boolean initialSecurityCheckDone;
+
+ /**
+ * Returns true if the load was triggered from an external application
+ * (e.g. Thunderbird). Please note that this flag will only ever be true
+ * if the load is of TYPE_DOCUMENT.
+ */
+ [infallible] attribute boolean loadTriggeredFromExternal;
+
+ /**
+ * True if the tainting has been set by the service worker.
+ */
+ [noscript, infallible] readonly attribute boolean serviceWorkerTaintingSynthesized;
+
+ /**
+ * Whenever a channel gets redirected, append the redirect history entry of
+ * the channel which contains principal referrer and remote address [before
+ * the channels got redirected] to the loadinfo, so that at every point this
+ * array provides us information about all the redirects this channel went
+ * through.
+ * @param channelToDeriveFrom the channel being redirected
+ * @param aIsInternalRedirect should be true if the channel is going
+ * through an internal redirect, otherwise false.
+ */
+ void appendRedirectHistoryEntry(in nsIChannel channelToDeriveFrom,
+ in boolean isInternalRedirect);
+
+ /**
+ * An array of nsIRedirectHistoryEntry which stores redirects associated
+ * with this channel. This array is filled whether or not the channel has
+ * ever been opened. The last element of the array is associated with the
+ * most recent redirect. Please note, that this array *includes* internal
+ * redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChainIncludingInternalRedirects;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChainIncludingInternalRedirects)]
+ nsIRedirectHistoryEntryArray binaryRedirectChainIncludingInternalRedirects();
+
+ /**
+ * Same as RedirectChain but does *not* include internal redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChain;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChain)]
+ nsIRedirectHistoryEntryArray binaryRedirectChain();
+
+ /**
+ * This array is only filled out when we are in the parent process and we are
+ * creating a loadInfo object or deserializing LoadInfoArgs into LoadInfo,
+ * as we ever only need in the parent process.
+ *
+ * The array is meant to be a list of principals of the documents that the
+ * browsing context, corresponding to this loadInfo object, is "nested through" in
+ * the sense of
+ * <https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-nested-through>.
+ * Note that the array does not include the principal corresponding to the frame
+ * loading this request. The closest ancestor is at index zero and the top level
+ * ancestor is at the last index.
+ *
+ * If this is a toplevel content browsing context (i.e. toplevel document in spec
+ * terms), the list is empty.
+ *
+ * Otherwise the array is a list for the document we're nested through (again in
+ * the spec sense), with the principal of that document prepended. The
+ * ancestorPrincipals[0] entry for an iframe load will be the principal of the
+ * iframe element's owner document. The ancestorPrincipals[0] entry for an image
+ * loaded in an iframe will be the principal of the iframe element's owner
+ * document. This matches the ordering specified for Location.ancestorOrigins.
+ *
+ * Please note that this array has the same lifetime as the loadInfo object - use
+ * with caution!
+ */
+ [noscript, notxpcom, nostdcall]
+ PrincipalArrayRef AncestorPrincipals();
+
+ /**
+ * An array of BrowsingContext IDs which correspond to nsILoadInfo::AncestorPrincipals
+ * above. AncestorBrowsingContextIDs[0] is the BrowsingContext ID of the frame
+ * associated with the principal at ancestorPrincipals[0], and so forth.
+ *
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall]
+ Uint64ArrayRef AncestorBrowsingContextIDs();
+
+ /**
+ * Sets the list of unsafe headers according to CORS spec, as well as
+ * potentially forces a preflight.
+ * Note that you do not need to set the Content-Type header. That will be
+ * automatically detected as needed.
+ *
+ * Only call this function when using the SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT mode.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setCorsPreflightInfo(in CStringArrayRef unsafeHeaders,
+ in boolean forcePreflight);
+
+ /**
+ * A C++-friendly getter for the list of cors-unsafe headers.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(CorsUnsafeHeaders)]
+ CStringArrayRef corsUnsafeHeaders();
+
+ /**
+ * Returns value set through setCorsPreflightInfo.
+ */
+ [infallible] readonly attribute boolean forcePreflight;
+
+ /**
+ * A C++ friendly getter for the forcePreflight flag.
+ */
+ [infallible] readonly attribute boolean isPreflight;
+
+ /**
+ * Constants reflecting the channel tainting. These are mainly defined here
+ * for script. Internal C++ code should use the enum defined in LoadTainting.h.
+ * See LoadTainting.h for documentation.
+ */
+ const unsigned long TAINTING_BASIC = 0;
+ const unsigned long TAINTING_CORS = 1;
+ const unsigned long TAINTING_OPAQUE = 2;
+
+ /**
+ * Determine the associated channel's current tainting. Note, this can
+ * change due to a service worker intercept, so it should be checked after
+ * OnStartRequest() fires.
+ */
+ readonly attribute unsigned long tainting;
+
+ /**
+ * Note a new tainting level and possibly increase the current tainting
+ * to match. If the tainting level is already greater than the given
+ * value, then there is no effect. It is not possible to reduce the tainting
+ * level on an existing channel/loadinfo.
+ */
+ void maybeIncreaseTainting(in unsigned long aTainting);
+
+ /**
+ * Various helper code to provide more convenient C++ access to the tainting
+ * attribute and maybeIncreaseTainting().
+ */
+%{C++
+ static_assert(TAINTING_BASIC == static_cast<uint32_t>(mozilla::LoadTainting::Basic),
+ "basic tainting enums should match");
+ static_assert(TAINTING_CORS == static_cast<uint32_t>(mozilla::LoadTainting::CORS),
+ "cors tainting enums should match");
+ static_assert(TAINTING_OPAQUE == static_cast<uint32_t>(mozilla::LoadTainting::Opaque),
+ "opaque tainting enums should match");
+
+ mozilla::LoadTainting GetTainting()
+ {
+ uint32_t tainting = TAINTING_BASIC;
+ MOZ_ALWAYS_SUCCEEDS(GetTainting(&tainting));
+ return static_cast<mozilla::LoadTainting>(tainting);
+ }
+
+ void MaybeIncreaseTainting(mozilla::LoadTainting aTainting)
+ {
+ uint32_t tainting = static_cast<uint32_t>(aTainting);
+ MOZ_ALWAYS_SUCCEEDS(MaybeIncreaseTainting(tainting));
+ }
+%}
+
+ /**
+ * Returns true if this load is for top level document.
+ * Note that the load for a sub-frame's document will return false here.
+ */
+ [infallible] readonly attribute boolean isTopLevelLoad;
+
+ /**
+ * If this is non-null, this property represents two things: (1) the
+ * URI to be used for the principal if the channel with this loadinfo
+ * gets a principal based on URI and (2) the URI to use for a document
+ * created from the channel with this loadinfo.
+ */
+ attribute nsIURI resultPrincipalURI;
+
+ /**
+ * This is the URI used to create the most recent channel in the load's
+ * redirect chain, if it's different from channel's `originalURI`.
+ * This is always null for loads not handled by DocumentLoadListener. If
+ * non-null, channelCreationOriginalURI will be used instead of channel's
+ * originalURI to re-create the channel in the final content process selected
+ * to perform the load.
+ */
+ attribute nsIURI channelCreationOriginalURI;
+
+ /**
+ * Returns a unique nsID used to construct the null principal for the
+ * resulting resource if the SANDBOXED_ORIGIN flag is set. This is used by
+ * GetChannelResultPrincipal() to ensure that the same null principal is
+ * returned every time.
+ */
+ [noscript, nostdcall, notxpcom]
+ nsIDRef GetSandboxedNullPrincipalID();
+
+ /**
+ * Generates a new nsID to be returned by a future call to
+ * `GetSandboxedNullPrincipalID()`.
+ */
+ [noscript, nostdcall, notxpcom]
+ void ResetSandboxedNullPrincipalID();
+
+ /**
+ * Return the top-level principal, which is the principal of the top-level
+ * window.
+ */
+ [notxpcom, nostdcall] readonly attribute nsIPrincipal topLevelPrincipal;
+
+ /**
+ * Note which client (i.e. global) initiated this network request. All
+ * nsGlobalWindow and WorkerPrivate can be converted to a ClientInfo to
+ * be set here. While this is being added to support service worker
+ * FetchEvent, it can also be used to communicate other information about
+ * the source global context in the future.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetClientInfo(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * Get the ClientInfo for the global that initiated the network request,
+ * if it has been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeClientInfoRef GetClientInfo();
+
+ /**
+ * Give a pre-allocated ClientSource to the channel LoadInfo. This is
+ * intended to be used by docshell when loading windows without an
+ * initial about:blank document. The docshell will allocate the ClientSource
+ * to represent the client that will be created as a result of the navigation
+ * network request. If the channel succeeds and remains same-origin, then
+ * the result nsGlobalWindow will take ownership of the reserved ClientSource.
+ *
+ * This method is also called when a cross-origin redirect occurs. A new
+ * ClientSource with a different UUID must be created in this case.
+ *
+ * This method automatically calls SetReservedClientInfo() with the
+ * ClientSource::Info().
+ */
+ [noscript, nostdcall, notxpcom]
+ void GiveReservedClientSource(in UniqueClientSourceMove aClientSource);
+
+ /**
+ * This method takes ownership of the reserved ClientSource previously
+ * provided in GiveReservedClientSource(). It may return nullptr if the
+ * nsILoadInfo does not own a ClientSource object.
+ */
+ [noscript, nostdcall, notxpcom]
+ UniqueClientSource TakeReservedClientSource();
+
+ /**
+ * Note the reserved client that be created if this non-subresource
+ * network request succeeds. Depending on the type of client this
+ * may be called directly or indirectly via GiveReservedClientSource().
+ * For example, web workers do not call give their ClientSource to
+ * the nsILoadInfo, but must still call this method to indicate the
+ * reserved client for their main script load.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetReservedClientInfo(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * This will clear any existing reserved or initial client and override
+ * it with the given reserved client. This is similar to calling
+ * TakeReservedClientSource() and then GiveReservedClientSource() with
+ * a new client as ClientChannelHelper does. This method is needed,
+ * though, to perform this operation in the parent process where
+ * the LoadInfo does not have direct access to a ClientSource.
+ *
+ * If in doubt, do not call this method. Its really only needed for
+ * a specific redirect case where the child has created a new client on
+ * redirect and we need to override the parent side's reserved client
+ * to match.
+ */
+ [noscript, nostdcall, notxpcom]
+ void OverrideReservedClientInfoInParent(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * Return the reserved ClientInfo for this load, if one has been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeClientInfoRef GetReservedClientInfo();
+
+ /**
+ * Note that this non-subresource network request will result in
+ * re-using an existing "initial" active client. This mainly only
+ * happens when an initial about:blank document is replaced with
+ * a real load in a window. In these cases we need to track this
+ * initial client so that we may report its existence in a FetchEvent.
+ *
+ * Note, an nsILoadInfo may only have a reserved client or an
+ * initial client. It should never have both.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetInitialClientInfo(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * Return the initial ClientInfo for this load, if one has been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeClientInfoRef GetInitialClientInfo();
+
+ /**
+ * Note that this network request should be controlled by a service worker.
+ * For non-subresource requests this may be set during the load when
+ * the first service worker interception occurs. For subresource requests
+ * it may be set by the source client if its already controlled by a
+ * service worker.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetController(in const_ServiceWorkerDescriptorRef aServiceWorker);
+
+ /**
+ * Clear the service worker controller for this channel. This should only
+ * be used for window navigation redirects. By default we want to keep
+ * the controller in all other cases.
+ */
+ [noscript, nostdcall, notxpcom]
+ void ClearController();
+
+ /**
+ * Get the service worker controlling this network request, if one has
+ * been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeServiceWorkerDescriptorRef GetController();
+
+ /**
+ * Set a custom performance storage. This is meant to be executed only for
+ * workers. If a PerformanceStorage is not set, the loadingDocument->Window
+ * Performance object will be returned instead.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetPerformanceStorage(in PerformanceStoragePtr aPerformanceStorage);
+
+ /**
+ * Get the custom PerformanceStorage if set by SetPerformanceStorage.
+ * Otherwise the loadingDocument->Window Performance object will be returned
+ * instead if all the following conditions are met:
+ * - the triggeringPrincipal is the same as the loadingDocument's principal.
+ * - if the external content policy type is TYPE_SUBDOCUMENT then loading
+ * wasn't caused by processing the attributes of the browsing context
+ * container.
+ */
+ [noscript, nostdcall, notxpcom]
+ PerformanceStoragePtr GetPerformanceStorage();
+
+ /**
+ * Returns the CSP (or Preload CSP for preloads) which should be enforced
+ * when fetching the resource this loadinfo belongs to.
+ *
+ * a) Non-navigations:
+ * For non-navigation loads, GetCsp() returns what the spec refers to as the
+ * "request's client's global object's CSP list". In practice, if this is the
+ * loadinfo of a subresource load (e.g an image load), then GetCsp() or
+ * GetPreloadCSP() returns the CSP of the document which embeds the image.
+ * The returned CSP includes any policy delivered through the HTTP header or
+ * also through the meta tag (modulo the difference for preloads, e.g. image
+ * preloads have to query GetPreloadCsp() because at the time of preloading
+ * we are not entirely sure if the Meta CSP will be applied to the document
+ * in the end or not). Please note that GetCSPToInherit() called on a
+ * loadinfo for any non-navigation always returns null.
+ *
+ * b) Navigations:
+ * * Top-level loads:
+ * For top-level loads (navigations) GetCsp() will return null, unless
+ * the navigation is started by a WebExtension, in which case it will
+ * return the CSP of the webextension, if any.
+ * If you need to query the CSP that potentially should apply to the
+ * new top-level load, you have to query GetCspToInherit(), which is
+ * the CSP of the request's client's global object, just like GetCsp()
+ * is for non-navigation requests.
+ *
+ * * Iframe-loads:
+ * For iframe-loads (navigations) GetCsp() will return the CSP of the
+ * parent document, unless the navigation is started by a WebExtension,
+ * in which case it will return the CSP of the webextension, if any.
+ *
+ * If you need to query the CSP that should potentially be inherited
+ * into the new document, you have to query GetCSPToInherit().
+ *
+ * TODO Bug 1557114:
+ * After evaluating what CSP to use for frame navigations we should
+ * update the above documentation to match the outcome of Bug 1557114.
+ */
+ [notxpcom,nostdcall] CSPRef GetCsp();
+ [notxpcom,nostdcall] CSPRef GetPreloadCsp();
+ [notxpcom,nostdcall] CSPRef GetCspToInherit();
+
+ /**
+ * The service worker and fetch specifications require returning the
+ * exact tainting level of the Response passed to FetchEvent.respondWith().
+ * This method allows us to override the tainting level in that case.
+ *
+ * NOTE: This should not be used outside of service worker code! Use
+ * nsILoadInfo::MaybeIncreaseTainting() instead.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SynthesizeServiceWorkerTainting(in LoadTainting aTainting);
+
+ /**
+ * The top-level document has been user-interacted.
+ */
+ [infallible] attribute boolean documentHasUserInteracted;
+
+ /**
+ * During a top-level document channel redirect from tracking to
+ * non-tracking resources, our anti-tracking heuristic, grants the storage
+ * access permission for a short amount of seconds (See
+ * privacy.restrict3rdpartystorage.expiration_redirect pref).
+ * We use this flag to remember this decision even if this channel is part
+ * of a chain of redirects.
+ */
+ [infallible] attribute boolean allowListFutureDocumentsCreatedFromThisRedirectChain;
+
+ /**
+ * Indicates that we need to check if we should apply the anti-tracking
+ * heuristic after the channel has been classified.
+ */
+ [infallible] attribute boolean needForCheckingAntiTrackingHeuristic;
+
+ /**
+ * A snapshot of the nonce at load start time which is used for CSP
+ * checks and only set for:
+ * * TYPE_SCRIPT and
+ * * TYPE_STYLESHEET
+ */
+ attribute AString cspNonce;
+
+ // Subresource Integrity (SRI) metadata.
+ attribute AString integrityMetadata;
+
+ /**
+ * List of possible reasons the request associated with this load info
+ * may have been blocked, set by various content blocking checkers.
+ */
+ const uint32_t BLOCKING_REASON_NONE = 0;
+ const uint32_t BLOCKING_REASON_CORSDISABLED = 1001;
+ const uint32_t BLOCKING_REASON_CORSDIDNOTSUCCEED = 1002;
+ const uint32_t BLOCKING_REASON_CORSREQUESTNOTHTTP = 1003;
+ const uint32_t BLOCKING_REASON_CORSMULTIPLEALLOWORIGINNOTALLOWED = 1004;
+ const uint32_t BLOCKING_REASON_CORSMISSINGALLOWORIGIN = 1005;
+ const uint32_t BLOCKING_REASON_CORSNOTSUPPORTINGCREDENTIALS = 1006;
+ const uint32_t BLOCKING_REASON_CORSALLOWORIGINNOTMATCHINGORIGIN = 1007;
+ const uint32_t BLOCKING_REASON_CORSMISSINGALLOWCREDENTIALS = 1008;
+ const uint32_t BLOCKING_REASON_CORSORIGINHEADERNOTADDED = 1009;
+ const uint32_t BLOCKING_REASON_CORSEXTERNALREDIRECTNOTALLOWED = 1010;
+ const uint32_t BLOCKING_REASON_CORSPREFLIGHTDIDNOTSUCCEED = 1011;
+ const uint32_t BLOCKING_REASON_CORSINVALIDALLOWMETHOD = 1012;
+ const uint32_t BLOCKING_REASON_CORSMETHODNOTFOUND = 1013;
+ const uint32_t BLOCKING_REASON_CORSINVALIDALLOWHEADER = 1014;
+ const uint32_t BLOCKING_REASON_CORSMISSINGALLOWHEADERFROMPREFLIGHT = 1015;
+ const uint32_t BLOCKING_REASON_CLASSIFY_MALWARE_URI = 2001;
+ const uint32_t BLOCKING_REASON_CLASSIFY_PHISHING_URI = 2002;
+ const uint32_t BLOCKING_REASON_CLASSIFY_UNWANTED_URI = 2003;
+ const uint32_t BLOCKING_REASON_CLASSIFY_TRACKING_URI = 2004;
+ const uint32_t BLOCKING_REASON_CLASSIFY_BLOCKED_URI = 2005;
+ const uint32_t BLOCKING_REASON_CLASSIFY_HARMFUL_URI = 2006;
+ const uint32_t BLOCKING_REASON_CLASSIFY_CRYPTOMINING_URI = 2007;
+ const uint32_t BLOCKING_REASON_CLASSIFY_FINGERPRINTING_URI = 2008;
+ const uint32_t BLOCKING_REASON_CLASSIFY_SOCIALTRACKING_URI = 2009;
+ const uint32_t BLOCKING_REASON_CLASSIFY_EMAILTRACKING_URI = 2010;
+ const uint32_t BLOCKING_REASON_MIXED_BLOCKED = 3001;
+ // The general reason comes from nsCSPContext::permitsInternal(),
+ // which is way too generic to distinguish an exact reason.
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_GENERAL = 4000;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_NO_DATA_PROTOCOL = 4001;
+ // removed 4002
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_CONTENT_BLOCKED = 4003;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_DATA_DOCUMENT = 4004;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_WEB_BROWSER = 4005;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_PRELOAD = 4006;
+ // The reason used when SEC_REQUIRE_SAME_ORIGIN_* is set and not satisifed.
+ const uint32_t BLOCKING_REASON_NOT_SAME_ORIGIN = 5000;
+ // The reason used when an extension cancels the request via the WebRequest api.
+ const uint32_t BLOCKING_REASON_EXTENSION_WEBREQUEST = 6000;
+ // The reason used when a request is cancelled via WebDriver BiDi network interception.
+ const uint32_t BLOCKING_REASON_WEBDRIVER_BIDI = 7000;
+
+ /**
+ * If the request associated with this load info was blocked by some of
+ * our content or load blockers, the reason can be found here.
+ * Note that setting this attribute has NO EFFECT on blocking the request.
+ * This attribute is only informative!
+ *
+ * By default the value is '0' - NONE.
+ * Each write rewrites the last value.
+ * Can be accessed only on a single thread.
+ */
+ [infallible] attribute unsigned long requestBlockingReason;
+
+ /**
+ * The object in charged to receive CSP violation events. It can be null.
+ * This attribute will be merged into the CSP object eventually.
+ * See bug 1500908.
+ */
+ attribute nsICSPEventListener cspEventListener;
+
+ /**
+ * This attribute will be true if this is a load triggered by
+ * https://html.spec.whatwg.org/multipage/iframe-embed-object.html#process-the-iframe-attributes
+ * or https://html.spec.whatwg.org/multipage/obsolete.html#process-the-frame-attributes
+ */
+ [infallible] readonly attribute boolean isFromProcessingFrameAttributes;
+
+ cenum CrossOriginOpenerPolicy : 8 {
+ OPENER_POLICY_UNSAFE_NONE = 0,
+ OPENER_POLICY_SAME_ORIGIN = 1,
+ OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS = 2,
+ OPENER_POLICY_EMBEDDER_POLICY_REQUIRE_CORP_FLAG = 0x10,
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP =
+ OPENER_POLICY_SAME_ORIGIN |
+ OPENER_POLICY_EMBEDDER_POLICY_REQUIRE_CORP_FLAG
+ };
+
+ cenum CrossOriginEmbedderPolicy : 8 {
+ EMBEDDER_POLICY_NULL = 0,
+ EMBEDDER_POLICY_REQUIRE_CORP = 1,
+ EMBEDDER_POLICY_CREDENTIALLESS = 2,
+ };
+
+ /**
+ * This attribute is the loading context's cross origin embedder policy.
+ * The value is initialized with corresponding WindowContext which get by
+ * innerWindowIID in the nsILoadInfo.
+ * It also could be set by workers when fetch is called under
+ * the workers' scope.
+ */
+ [infallible] attribute nsILoadInfo_CrossOriginEmbedderPolicy
+ loadingEmbedderPolicy;
+
+ /**
+ * This attribute will be true if the top level document has COEP:
+ * credentialless enabled in Origin Trial.
+ */
+ [infallible] attribute boolean isOriginTrialCoepCredentiallessEnabledForTopLevel;
+ /**
+ * This attribute will be true if this is a load triggered by a media
+ * element.
+ */
+ [infallible] attribute boolean isMediaRequest;
+
+ /**
+ * This attribute will be true if this is a load triggered by a media
+ * element and it's an initial request.
+ */
+ [infallible] attribute boolean isMediaInitialRequest;
+
+ /**
+ * This attribute will be true if the fetch request is from object or embed
+ * elements
+ */
+ [infallible] attribute boolean isFromObjectOrEmbed;
+
+ /**
+ * This attribute will be true if the URL is known to be possibly broken and
+ * CheckForBrokenChromeURL and RecordZeroLengthEvent should be skipped.
+ */
+ [infallible] readonly attribute boolean shouldSkipCheckForBrokenURLOrZeroSized;
+
+ /**
+ * If this is non-null, this property holds the URI as it was before query
+ * stripping was performed.
+ */
+ attribute nsIURI unstrippedURI;
+
+ /**
+ * Propagated information from InterceptedHttpChannel
+ * It should be null when the channel is not created from FetchEvent.request
+ * or ServiceWorker NavigationPreload.
+ * nsIFetchEventInfo is C++ only, so it is not an attribute.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(InterceptionInfo)]
+ nsIInterceptionInfo binaryInterceptionInfo();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetInterceptionInfo)]
+ void binarySetInterceptionInfo(in nsIInterceptionInfo info);
+
+ /**
+ * Whether nsICookieInjector has injected a cookie for this request to
+ * handle a cookie banner. This is only done for top-level requests.
+ */
+ [infallible] attribute boolean hasInjectedCookieForCookieBannerHandling;
+
+ /**
+ * Whether the load has gone through the URL bar, where the fixup had to add * the protocol scheme.
+ */
+ [infallible] attribute boolean wasSchemelessInput;
+};
diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl
new file mode 100644
index 0000000000..1842cdb40c
--- /dev/null
+++ b/netwerk/base/nsIMIMEInputStream.idl
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIHttpHeaderVisitor.idl"
+#include "nsIInputStream.idl"
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+[scriptable, builtinclass, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)]
+interface nsIMIMEInputStream : nsIInputStream
+{
+ /**
+ * Adds an additional header to the stream on the form "name: value". May
+ * not be called once the stream has been started to be read.
+ * @param name name of the header
+ * @param value value of the header
+ */
+ void addHeader(in string name, in string value);
+
+ /**
+ * Visits all headers which have been added via addHeader. Calling
+ * addHeader while visiting request headers has undefined behavior.
+ *
+ * @param aVisitor
+ * The header visitor instance.
+ */
+ void visitHeaders(in nsIHttpHeaderVisitor visitor);
+
+ /**
+ * Sets data-stream. May not be called once the stream has been started
+ * to be read.
+ * The cursor of the new stream should be located at the beginning of the
+ * stream if the implementation of the nsIMIMEInputStream also is used as
+ * an nsISeekableStream.
+ * @param stream stream containing the data for the stream
+ */
+ void setData(in nsIInputStream stream);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIInputStream data;
+};
diff --git a/netwerk/base/nsIMultiPartChannel.idl b/netwerk/base/nsIMultiPartChannel.idl
new file mode 100644
index 0000000000..3e4bf58812
--- /dev/null
+++ b/netwerk/base/nsIMultiPartChannel.idl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * An interface to access the the base channel
+ * associated with a MultiPartChannel.
+ */
+
+[scriptable, builtinclass, uuid(4fefb490-5567-11e5-a837-0800200c9a66)]
+interface nsIMultiPartChannel : nsISupports
+{
+ /**
+ * readonly attribute to access the underlying channel
+ */
+ readonly attribute nsIChannel baseChannel;
+
+ /**
+ * Attribute guaranteed to be different for different parts of
+ * the same multipart document.
+ */
+ readonly attribute uint32_t partID;
+
+ [noscript] readonly attribute boolean isFirstPart;
+
+ /**
+ * Set to true when onStopRequest is received from the base channel.
+ * The listener can check this from its onStopRequest to determine
+ * whether more data can be expected.
+ */
+ readonly attribute boolean isLastPart;
+};
+
+/**
+ * An interface that listeners can implement to receive a notification
+ * when the last part of the multi-part channel has finished, and the
+ * final OnStopRequest has been sent.
+ */
+[scriptable, uuid(b084959a-4fb9-41a5-88a0-d0f045ce75cf)]
+interface nsIMultiPartChannelListener : nsISupports
+{
+ /**
+ * Sent when all parts have finished and sent OnStopRequest.
+ */
+ void onAfterLastPart(in nsresult status);
+};
diff --git a/netwerk/base/nsINestedURI.idl b/netwerk/base/nsINestedURI.idl
new file mode 100644
index 0000000000..df124348af
--- /dev/null
+++ b/netwerk/base/nsINestedURI.idl
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsINestedURI is an interface that must be implemented by any nsIURI
+ * implementation which has an "inner" URI that it actually gets data
+ * from.
+ *
+ * For example, if URIs for the scheme "sanitize" have the structure:
+ *
+ * sanitize:http://example.com
+ *
+ * and opening a channel on such a sanitize: URI gets the data from
+ * http://example.com, sanitizes it, and returns it, then the sanitize: URI
+ * should implement nsINestedURI and return the http://example.com URI as its
+ * inner URI.
+ */
+[scriptable, builtinclass, uuid(6de2c874-796c-46bf-b57f-0d7bd7d6cab0)]
+interface nsINestedURI : nsISupports
+{
+ /**
+ * The inner URI for this nested URI. This must not return null if the
+ * getter succeeds; URIs that have no inner must not QI to this interface.
+ * Dynamically changing whether there is an inner URI is not allowed.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innerURI;
+
+ /**
+ * The innermost URI for this nested URI. This must not return null if the
+ * getter succeeds. This is equivalent to repeatedly calling innerURI while
+ * the returned URI QIs to nsINestedURI.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innermostURI;
+};
+
+[scriptable, builtinclass, uuid(ca3d6c03-4eee-4271-a97a-d16c0a0b2c5c)]
+interface nsINestedURIMutator : nsISupports
+{
+ /*
+ * - Creates a new URI with the given innerURI.
+ */
+ [must_use, noscript] void init(in nsIURI innerURI);
+};
+
+[scriptable, builtinclass, uuid(c6357a3b-c2bb-4b4b-9278-513377398a38)]
+interface nsINestedAboutURIMutator : nsISupports
+{
+ /*
+ * - Creates a new URI with the given innerURI and base.
+ */
+ [must_use, noscript] void initWithBase(in nsIURI innerURI, in nsIURI baseURI);
+};
+
+[scriptable, builtinclass, uuid(3bd44535-08ea-478f-99b9-85fa1084e820)]
+interface nsIJSURIMutator : nsISupports
+{
+ /*
+ * - Inits the URI by setting the base URI
+ * - It is the caller's responsibility to also call SetSpec afterwards,
+ * otherwise we will return an incomplete URI (with only a base)
+ */
+ [must_use, noscript] void setBase(in nsIURI aBaseURI);
+};
diff --git a/netwerk/base/nsINetAddr.idl b/netwerk/base/nsINetAddr.idl
new file mode 100644
index 0000000000..bbbcd28c0e
--- /dev/null
+++ b/netwerk/base/nsINetAddr.idl
@@ -0,0 +1,88 @@
+/* vim: et ts=4 sw=4 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+
+/**
+ * nsINetAddr
+ *
+ * This interface represents a native NetAddr struct in a readonly
+ * interface.
+ */
+[scriptable, uuid(652B9EC5-D159-45D7-9127-50BB559486CD)]
+interface nsINetAddr : nsISupports
+{
+ /**
+ * @return the address family of the network address, which corresponds to
+ * one of the FAMILY_ constants.
+ */
+ readonly attribute unsigned short family;
+
+ /**
+ * @return Either the IP address (FAMILY_INET, FAMILY_INET6) or the path
+ * (FAMILY_LOCAL) in string form. IP addresses are in the format produced by
+ * mozilla::net::NetAddr::ToStringBuffer.
+ *
+ * Note: Paths for FAMILY_LOCAL may have length limitations which are
+ * implementation dependent and not documented as part of this interface.
+ */
+ readonly attribute AUTF8String address;
+
+ /**
+ * @return the port number for a FAMILY_INET or FAMILY_INET6 address.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET or
+ * FAMILY_INET6.
+ */
+ readonly attribute unsigned short port;
+
+ /**
+ * @return the flow label for a FAMILY_INET6 address.
+ *
+ * @see http://www.ietf.org/rfc/rfc3697.txt
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long flow;
+
+ /**
+ * @return the address scope of a FAMILY_INET6 address.
+ *
+ * @see http://tools.ietf.org/html/rfc4007
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long scope;
+
+ /**
+ * @return whether a FAMILY_INET6 address is mapped from FAMILY_INET.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute boolean isV4Mapped;
+
+ /**
+ * Network address families. These correspond to all the network address
+ * families supported by the NetAddr struct.
+ */
+ const unsigned long FAMILY_INET = 1;
+ const unsigned long FAMILY_INET6 = 2;
+ const unsigned long FAMILY_LOCAL = 3;
+
+ /**
+ * @return the underlying NetAddr struct.
+ */
+ [noscript] NetAddr getNetAddr();
+};
diff --git a/netwerk/base/nsINetUtil.idl b/netwerk/base/nsINetUtil.idl
new file mode 100644
index 0000000000..67e6c54f2b
--- /dev/null
+++ b/netwerk/base/nsINetUtil.idl
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIPrefBranch;
+
+/**
+ * nsINetUtil provides various network-related utility methods.
+ */
+[scriptable, uuid(fe2625ec-b884-4df1-b39c-9e830e47aa94)]
+interface nsINetUtil : nsISupports
+{
+ /**
+ * Parse a Content-Type header value in strict mode. This is a more
+ * conservative parser that reject things that violate RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * that are used for HTTP requests, and those that are used to make security
+ * decisions.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseRequestContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Parse a Content-Type header value in relaxed mode. This is a more
+ * permissive parser that ignores things that go against RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * received from web servers where we want to make a best effort attempt
+ * at extracting a useful MIME type and charset.
+ *
+ * NOTE: DO NOT USE THIS if you're going to make security decisions
+ * based on the result.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseResponseContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Test whether the given URI's handler has the given protocol flags.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether the protocol handler for aURI has all the flags
+ * in aFlags.
+ */
+ boolean protocolHasFlags(in nsIURI aURI, in unsigned long aFlag);
+
+ /**
+ * Test whether the protocol handler for this URI or that for any of
+ * its inner URIs has the given protocol flags. This will QI aURI to
+ * nsINestedURI and walk the nested URI chain.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether any of the protocol handlers involved have all the flags
+ * in aFlags.
+ */
+ boolean URIChainHasFlags(in nsIURI aURI, in unsigned long aFlags);
+
+ /** Escape every character with its %XX-escaped equivalent */
+ const unsigned long ESCAPE_ALL = 0;
+
+ /** Leave alphanumeric characters intact and %XX-escape all others */
+ const unsigned long ESCAPE_XALPHAS = 1;
+
+ /** Leave alphanumeric characters intact, convert spaces to '+',
+ %XX-escape all others */
+ const unsigned long ESCAPE_XPALPHAS = 2;
+
+ /** Leave alphanumeric characters and forward slashes intact,
+ %XX-escape all others */
+ const unsigned long ESCAPE_URL_PATH = 4;
+
+ /** Additional encoding for Apple's NSURL compatibility.
+ Like XALPHAS, but leave '%' to avoid double encoding, '+', and '/'.
+ %XX-escape all others */
+ const unsigned long ESCAPE_URL_APPLE_EXTRA = 8;
+
+ /**
+ * escape a string with %00-style escaping
+ */
+ ACString escapeString(in ACString aString, in unsigned long aEscapeType);
+
+ /** %XX-escape URL scheme */
+ const unsigned long ESCAPE_URL_SCHEME = 1;
+
+ /** %XX-escape username in the URL */
+ const unsigned long ESCAPE_URL_USERNAME = 1 << 1;
+
+ /** %XX-escape password in the URL */
+ const unsigned long ESCAPE_URL_PASSWORD = 1 << 2;
+
+ /** %XX-escape URL host */
+ const unsigned long ESCAPE_URL_HOST = 1 << 3;
+
+ /** %XX-escape URL directory */
+ const unsigned long ESCAPE_URL_DIRECTORY = 1 << 4;
+
+ /** %XX-escape file basename in the URL */
+ const unsigned long ESCAPE_URL_FILE_BASENAME = 1 << 5;
+
+ /** %XX-escape file extension in the URL */
+ const unsigned long ESCAPE_URL_FILE_EXTENSION = 1 << 6;
+
+ /** %XX-escape URL parameters */
+ const unsigned long ESCAPE_URL_PARAM = 1 << 7;
+
+ /** %XX-escape URL query */
+ const unsigned long ESCAPE_URL_QUERY = 1 << 8;
+
+ /** %XX-escape URL ref */
+ const unsigned long ESCAPE_URL_REF = 1 << 9;
+
+ /** %XX-escape URL path - same as escaping directory, basename and extension */
+ const unsigned long ESCAPE_URL_FILEPATH =
+ ESCAPE_URL_DIRECTORY | ESCAPE_URL_FILE_BASENAME | ESCAPE_URL_FILE_EXTENSION;
+
+ /** %XX-escape scheme, username, password, host, path, params, query and ref */
+ const unsigned long ESCAPE_URL_MINIMAL =
+ ESCAPE_URL_SCHEME | ESCAPE_URL_USERNAME | ESCAPE_URL_PASSWORD |
+ ESCAPE_URL_HOST | ESCAPE_URL_FILEPATH | ESCAPE_URL_PARAM |
+ ESCAPE_URL_QUERY | ESCAPE_URL_REF;
+
+ /** Force %XX-escaping of already escaped sequences */
+ const unsigned long ESCAPE_URL_FORCED = 1 << 10;
+
+ /** Skip non-ascii octets, %XX-escape all others */
+ const unsigned long ESCAPE_URL_ONLY_ASCII = 1 << 11;
+
+ /**
+ * Skip graphic octets (0x20-0x7E) when escaping
+ * Skips all ASCII octets (0x00-0x7F) when unescaping
+ */
+ const unsigned long ESCAPE_URL_ONLY_NONASCII = 1 << 12;
+
+ /** Force %XX-escape of colon */
+ const unsigned long ESCAPE_URL_COLON = 1 << 14;
+
+ /** Skip C0 and DEL from unescaping */
+ const unsigned long ESCAPE_URL_SKIP_CONTROL = 1 << 15;
+
+ /** %XX-escape external protocol handler URL */
+ const unsigned long ESCAPE_URL_EXT_HANDLER = 1 << 17;
+
+ /**
+ * %XX-Escape invalid chars in a URL segment.
+ *
+ * @param aStr the URL to be escaped
+ * @param aFlags the URL segment type flags
+ *
+ * @return the escaped string (the string itself if escaping did not happen)
+ *
+ */
+ ACString escapeURL(in ACString aStr, in unsigned long aFlags);
+
+ /**
+ * Expands URL escape sequences
+ *
+ * @param aStr the URL to be unescaped
+ * @param aFlags only ESCAPE_URL_ONLY_NONASCII and ESCAPE_URL_SKIP_CONTROL
+ * are recognized. If |aFlags| is 0 all escape sequences are
+ * unescaped
+ * @return unescaped string
+ */
+ ACString unescapeString(in AUTF8String aStr, in unsigned long aFlags);
+
+ /**
+ * Extract the charset parameter location and value from a content-type
+ * header.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aCharsetStart index of the start of the charset parameter
+ * (the ';' separating it from what came before) in aTypeHeader.
+ * If this function returns false, this argument will still be
+ * set, to the index of the location where a new charset should
+ * be inserted.
+ * @param [out] aCharsetEnd index of the end of the charset parameter (the
+ * ';' separating it from what comes after, or the end
+ * of the string) in aTypeHeader. If this function returns
+ * false, this argument will still be set, to the index of the
+ * location where a new charset should be inserted.
+ *
+ * @return whether a charset parameter was found. This can be false even in
+ * cases when parseContentType would claim to have a charset, if the type
+ * that won out does not have a charset parameter specified.
+ */
+ boolean extractCharsetFromContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out long aCharsetStart,
+ out long aCharsetEnd);
+
+ /**
+ * This is test-only. Send an IPC message to let socket process send a
+ * telemetry.
+ */
+ void socketProcessTelemetryPing();
+
+ /**
+ * This is a void method that is C++ implemented and always
+ * returns NS_ERROR_NOT_IMPLEMENTED. To be used for testing.
+ */
+ void notImplemented();
+};
diff --git a/netwerk/base/nsINetworkConnectivityService.idl b/netwerk/base/nsINetworkConnectivityService.idl
new file mode 100644
index 0000000000..482eaf45ee
--- /dev/null
+++ b/netwerk/base/nsINetworkConnectivityService.idl
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(2693457e-3ba5-4455-991f-5350946adb12)]
+interface nsINetworkConnectivityService : nsISupports
+{
+ /**
+ * Each tested feature may be in one of 3 states:
+ * UNKNOWN, if a check hasn't been performed.
+ * OK, if the feature was successfully tested
+ * NOT_AVAILABLE, if the feature is blocked by the network.
+ * Note that the endpoints are guaranteed to support the features.
+ */
+ cenum ConnectivityState: 32 {
+ UNKNOWN = 0,
+ OK = 1,
+ NOT_AVAILABLE = 2
+ };
+
+ /* If DNS v4/v6 queries actually work on the current network */
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState DNSv4;
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState DNSv6;
+
+ /* If connecting to IPv4/v6 works on the current network */
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState IPv4;
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState IPv6;
+
+ /* If a NAT64 gateway was detected on the current network */
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState NAT64;
+
+ /* Starts the DNS request to check for DNS v4/v6 availability */
+ void recheckDNS();
+
+ /* Starts HTTP requests over IPv4 and IPv6, and checks if they work */
+ void recheckIPConnectivity();
+};
diff --git a/netwerk/base/nsINetworkInfoService.idl b/netwerk/base/nsINetworkInfoService.idl
new file mode 100644
index 0000000000..9349abe13f
--- /dev/null
+++ b/netwerk/base/nsINetworkInfoService.idl
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Listener for getting list of addresses.
+ */
+[scriptable, uuid(c4bdaac1-3ab1-4fdb-9a16-17cbed794603)]
+interface nsIListNetworkAddressesListener : nsISupports
+{
+ /**
+ * Callback function that gets called by nsINetworkInfoService.listNetworkAddresses.
+ * Each address in the array is a string IP address in canonical form,
+ * e.g. 192.168.1.10, or an IPV6 address in string form.
+ */
+ void onListedNetworkAddresses(in Array<ACString> aAddressArray);
+ void onListNetworkAddressesFailed();
+};
+
+/**
+ * Listener for getting hostname.
+ */
+[scriptable, uuid(3ebdcb62-2df4-4042-8864-3fa81abd4693)]
+interface nsIGetHostnameListener : nsISupports
+{
+ void onGotHostname(in AUTF8String aHostname);
+ void onGetHostnameFailed();
+};
+
+/**
+ * Service information
+ */
+[scriptable, uuid(55fc8dae-4a58-4e0f-a49b-901cbabae809)]
+interface nsINetworkInfoService : nsISupports
+{
+ /**
+ * Obtain a list of local machine network addresses. The listener object's
+ * onListedNetworkAddresses will be called with the obtained addresses.
+ * On failure, the listener object's onListNetworkAddressesFailed() will be called.
+ */
+ void listNetworkAddresses(in nsIListNetworkAddressesListener aListener);
+
+ /**
+ * Obtain the hostname of the local machine. The listener object's
+ * onGotHostname will be called with the obtained hostname.
+ * On failure, the listener object's onGetHostnameFailed() will be called.
+ */
+ void getHostname(in nsIGetHostnameListener aListener);
+};
+
+%{ C++
+#define NETWORKINFOSERVICE_CONTRACT_ID \
+ "@mozilla.org/network-info-service;1"
+%}
diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl
new file mode 100644
index 0000000000..155daa5cd5
--- /dev/null
+++ b/netwerk/base/nsINetworkInterceptController.idl
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsICacheInfoChannel;
+interface nsIChannel;
+interface nsIConsoleReportCollector;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIURI;
+
+%{C++
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsILoadInfo.h"
+namespace mozilla {
+class TimeStamp;
+
+namespace dom {
+class ChannelInfo;
+}
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+[ptr] native ChannelInfo(mozilla::dom::ChannelInfo);
+
+/**
+ * Interface allowing the nsIInterceptedChannel to callback when it is
+ * done reading from the body stream.
+ */
+[scriptable, uuid(51039eb6-bea0-40c7-b523-ccab56cc4fde)]
+interface nsIInterceptedBodyCallback : nsISupports
+{
+ void bodyComplete(in nsresult aRv);
+};
+
+/**
+ * Interface to allow implementors of nsINetworkInterceptController to control the behaviour
+ * of intercepted channels without tying implementation details of the interception to
+ * the actual channel. nsIInterceptedChannel is expected to be implemented by objects
+ * which do not implement nsIChannel.
+ */
+
+[scriptable, uuid(f4b82975-6a86-4cc4-87fe-9a1fd430c86d)]
+interface nsIInterceptedChannel : nsISupports
+{
+ /**
+ * Instruct a channel that has been intercepted to continue with the original
+ * network request.
+ *
+ * For our mitigations, we support this reset triggering "bypass" mode which
+ * results in the resulting client not being controlled.
+ */
+ void resetInterception(in boolean bypass);
+
+ /**
+ * Set the status and reason for the forthcoming synthesized response.
+ * Multiple calls overwrite existing values.
+ */
+ void synthesizeStatus(in uint16_t status, in ACString reason);
+
+ /**
+ * Attach a header name/value pair to the forthcoming synthesized response.
+ * Overwrites any existing header value.
+ */
+ void synthesizeHeader(in ACString name, in ACString value);
+
+ /**
+ * Instruct a channel that has been intercepted that a response is
+ * starting to be synthesized. No further header modification is allowed
+ * after this point. There are a few parameters:
+ * - A body stream may be optionally passed. If nullptr, then an
+ * empty body is assumed.
+ * - A callback may be optionally passed. It will be invoked
+ * when the body is complete. For a nullptr body this may be
+ * synchronously on the current thread. Otherwise it will be invoked
+ * asynchronously on the current thread.
+ * - A cacheInfoChannel may be optionally passed. If the body stream is
+ * from alternative data cache, this cacheInfoChannel provides needed
+ * cache information.
+ * - The caller may optionally pass a spec for a URL that this response
+ * originates from; an empty string will cause the original
+ * intercepted request's URL to be used instead.
+ * - The responseRedirected flag is false will cause the channel do an
+ * internal redirect when the original intercepted reauest's URL is
+ * different from the response's URL. The flag is true will cause the
+ * chaanel do a non-internal redirect when the URLs are different.
+ */
+ void startSynthesizedResponse(in nsIInputStream body,
+ in nsIInterceptedBodyCallback callback,
+ in nsICacheInfoChannel channel,
+ in ACString finalURLSpec,
+ in bool responseRedirected);
+
+ /**
+ * Instruct a channel that has been intercepted that response synthesis
+ * has completed and all outstanding resources can be closed.
+ */
+ void finishSynthesizedResponse();
+
+ /**
+ * Cancel the pending intercepted request.
+ * @return NS_ERROR_FAILURE if the response has already been synthesized or
+ * the original request has been instructed to continue.
+ */
+ void cancelInterception(in nsresult status);
+
+ /**
+ * The underlying channel object that was intercepted.
+ */
+ readonly attribute nsIChannel channel;
+
+ /**
+ * The URL of the underlying channel object, corrected for a potential
+ * secure upgrade.
+ */
+ readonly attribute nsIURI secureUpgradedChannelURI;
+
+ /**
+ * This method allows to override the channel info for the channel.
+ */
+ [noscript]
+ void setChannelInfo(in ChannelInfo channelInfo);
+
+ /**
+ * Get the internal load type from the underlying channel.
+ */
+ [noscript]
+ readonly attribute nsContentPolicyType internalContentPolicyType;
+
+ [noscript]
+ readonly attribute nsIConsoleReportCollector consoleReportCollector;
+
+ [noscript]
+ void SetFetchHandlerStart(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetFetchHandlerFinish(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetRemoteWorkerLaunchStart(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetRemoteWorkerLaunchEnd(in TimeStamp aTimeStamp);
+
+ /**
+ * This method indicates if the ServiceWorker Interception is reset to
+ * network or not.
+ */
+ [noscript]
+ bool GetIsReset();
+
+%{C++
+ already_AddRefed<nsIConsoleReportCollector>
+ GetConsoleReportCollector()
+ {
+ nsCOMPtr<nsIConsoleReportCollector> reporter;
+ GetConsoleReportCollector(getter_AddRefs(reporter));
+ return reporter.forget();
+ }
+
+ void
+ GetSubresourceTimeStampKey(nsIChannel* aChannel, nsACString& aKey)
+ {
+ if (!nsContentUtils::IsNonSubresourceRequest(aChannel)) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ switch(loadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE: {
+ aKey = "subresource-script"_ns;
+ break;
+ }
+ case nsIContentPolicy::TYPE_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON: {
+ aKey = "subresource-image"_ns;
+ break;
+ }
+ case nsIContentPolicy::TYPE_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: {
+ aKey = "subresource-stylesheet"_ns;
+ break;
+ }
+ default: {
+ aKey = "subresource-other"_ns;
+ break;
+ }
+ }
+ }
+ }
+
+ bool
+ IsReset()
+ {
+ bool result;
+ GetIsReset(&result);
+ return result;
+ }
+%}
+
+ /**
+ * Allow the ServiceWorkerManager to set an RAII-style object on the
+ * intercepted channel that should be released once the channel is
+ * torn down.
+ */
+ [noscript]
+ void setReleaseHandle(in nsISupports aHandle);
+};
+
+/**
+ * Interface to allow consumers to attach themselves to a channel's
+ * notification callbacks/loadgroup and determine if a given channel
+ * request should be intercepted before any network request is initiated.
+ */
+
+[scriptable, uuid(70d2b4fe-a552-48cd-8d93-1d8437a56b53)]
+interface nsINetworkInterceptController : nsISupports
+{
+ /**
+ * Returns true if a channel should avoid initiating any network
+ * requests until specifically instructed to do so.
+ *
+ * @param aURI The URI to be loaded. Note, this may differ from
+ * the channel's current URL in some cases.
+ * @param aChannel The channel that may be intercepted. It will
+ * be in the state prior to calling OnStartRequest().
+ */
+ bool shouldPrepareForIntercept(in nsIURI aURI, in nsIChannel aChannel);
+
+ /**
+ * Notification when a given intercepted channel is prepared to accept a synthesized
+ * response via the provided stream.
+ *
+ * @param aChannel the controlling interface for a channel that has been intercepted
+ */
+ void channelIntercepted(in nsIInterceptedChannel aChannel);
+};
diff --git a/netwerk/base/nsINetworkLinkService.idl b/netwerk/base/nsINetworkLinkService.idl
new file mode 100644
index 0000000000..29c7fab836
--- /dev/null
+++ b/netwerk/base/nsINetworkLinkService.idl
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsINetAddr;
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+
+/**
+ * Network link status monitoring service.
+ */
+[scriptable, uuid(103e5293-77b3-4b70-af59-6e9e4a1f994a)]
+interface nsINetworkLinkService : nsISupports
+{
+ /* Link type constants */
+ const unsigned long LINK_TYPE_UNKNOWN = 0;
+ const unsigned long LINK_TYPE_ETHERNET = 1;
+ const unsigned long LINK_TYPE_USB = 2;
+ const unsigned long LINK_TYPE_WIFI = 3;
+ const unsigned long LINK_TYPE_WIMAX = 4;
+ const unsigned long LINK_TYPE_MOBILE = 9;
+
+ /**
+ * This is set to true when the system is believed to have a usable
+ * network connection.
+ *
+ * The link is only up when network connections can be established. For
+ * example, the link is down during DHCP configuration (unless there
+ * is another usable interface already configured).
+ *
+ * If the link status is not currently known, we generally assume that
+ * it is up.
+ */
+ readonly attribute boolean isLinkUp;
+
+ /**
+ * This is set to true when we believe that isLinkUp is accurate.
+ */
+ readonly attribute boolean linkStatusKnown;
+
+ /**
+ * The type of network connection.
+ */
+ readonly attribute unsigned long linkType;
+
+ /**
+ * A string uniquely identifying the current active network interfaces.
+ * Empty when there are no active network interfaces.
+ */
+ readonly attribute ACString networkID;
+
+ /**
+ * The list of DNS suffixes for the currently active network interfaces.
+ */
+ readonly attribute Array<ACString> dnsSuffixList;
+
+ /**
+ * The IPs of the DNS resolvers currently used by the platform.
+ */
+ [noscript] readonly attribute Array<NetAddr> nativeResolvers;
+
+ /**
+ * Same as previous - returns the IPs of DNS resolvers but this time as
+ * XPCOM objects usable by extensions.
+ */
+ readonly attribute Array<nsINetAddr> resolvers;
+
+ const unsigned long NONE_DETECTED = 0;
+ const unsigned long VPN_DETECTED = 1 << 0;
+ const unsigned long PROXY_DETECTED = 1 << 1;
+ const unsigned long NRPT_DETECTED = 1 << 2;
+
+ /**
+ * A bitfield that encodes the platform attributes we detected which
+ * indicate that we should only use DNS, not TRR.
+ */
+ readonly attribute unsigned long platformDNSIndications;
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TOPIC whenever one of isLinkUp or linkStatusKnown
+ * changes. We pass one of the NS_NETWORK_LINK_DATA_ constants below
+ * as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TOPIC "network:link-status-changed"
+
+/**
+ * isLinkUp is now true, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_UP "up"
+/**
+ * isLinkUp is now false, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_DOWN "down"
+/**
+ * isLinkUp is still true, but the network setup is modified.
+ * linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_CHANGED "changed"
+/**
+ * linkStatusKnown is now false.
+ */
+#define NS_NETWORK_LINK_DATA_UNKNOWN "unknown"
+
+/**
+ * network ID has changed.
+ */
+#define NS_NETWORK_ID_CHANGED_TOPIC "network:networkid-changed"
+
+/**
+ * DNS suffix list has updated.
+ */
+#define NS_DNS_SUFFIX_LIST_UPDATED_TOPIC "network:dns-suffix-list-updated"
+
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TYPE_TOPIC whenever the network connection type
+ * changes. We pass one of the valid connection type constants
+ * below as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TYPE_TOPIC "network:link-type-changed"
+
+/** We were unable to determine the network connection type */
+#define NS_NETWORK_LINK_TYPE_UNKNOWN "unknown"
+
+/** A standard wired ethernet connection */
+#define NS_NETWORK_LINK_TYPE_ETHERNET "ethernet"
+
+/** A connection via a USB port */
+#define NS_NETWORK_LINK_TYPE_USB "usb"
+
+/** A connection via a WiFi access point (IEEE802.11) */
+#define NS_NETWORK_LINK_TYPE_WIFI "wifi"
+
+/** A connection via WiMax (IEEE802.16) */
+#define NS_NETWORK_LINK_TYPE_WIMAX "wimax"
+
+/** A mobile connection (e.g. 2G, 3G, etc) */
+#define NS_NETWORK_LINK_TYPE_MOBILE "mobile"
+%}
diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl
new file mode 100644
index 0000000000..07f88cba8d
--- /dev/null
+++ b/netwerk/base/nsINetworkPredictor.idl
@@ -0,0 +1,194 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsINetworkPredictorVerifier;
+
+webidl Document;
+
+typedef unsigned long PredictorPredictReason;
+typedef unsigned long PredictorLearnReason;
+
+%{C++
+namespace mozilla {
+
+class OriginAttributes;
+
+}
+%}
+
+[ref] native OriginAttributes(const mozilla::OriginAttributes);
+
+/**
+ * nsINetworkPredictor - learn about pages users visit, and allow us to take
+ * predictive actions upon future visits.
+ * NOTE: nsINetworkPredictor should only
+ * be used on the main thread.
+ */
+[scriptable, builtinclass, uuid(acc88e7c-3f39-42c7-ac31-6377c2c3d73e)]
+interface nsINetworkPredictor : nsISupports
+{
+ /**
+ * Prediction reasons
+ *
+ * PREDICT_LINK - we are being asked to take predictive action because
+ * the user is hovering over a link.
+ *
+ * PREDICT_LOAD - we are being asked to take predictive action because
+ * the user has initiated a pageload.
+ *
+ * PREDICT_STARTUP - we are being asked to take predictive action
+ * because the browser is starting up.
+ */
+ const PredictorPredictReason PREDICT_LINK = 0;
+ const PredictorPredictReason PREDICT_LOAD = 1;
+ const PredictorPredictReason PREDICT_STARTUP = 2;
+
+ /**
+ * Start taking predictive actions
+ *
+ * Calling this will cause the predictor to (possibly) start
+ * taking actions such as DNS prefetch and/or TCP preconnect based on
+ * (1) the host name that we are given, and (2) the reason we are being
+ * asked to take actions.
+ *
+ * @param targetURI - The URI we are being asked to take actions based on.
+ * @param sourceURI - The URI that is currently loaded. This is so we can
+ * avoid doing predictive actions for link hover on an HTTPS page (for
+ * example).
+ * @param reason - The reason we are being asked to take actions. Can be
+ * any of the PREDICT_* values above.
+ * In the case of PREDICT_LINK, targetURI should be the URI of the link
+ * that is being hovered over, and sourceURI should be the URI of the page
+ * on which the link appears.
+ * In the case of PREDICT_LOAD, targetURI should be the URI of the page that
+ * is being loaded and sourceURI should be null.
+ * In the case of PREDICT_STARTUP, both targetURI and sourceURI should be
+ * null.
+ * @param originAttributes - The originAttributes of the page load we are
+ * predicting about.
+ * @param verifier - An nsINetworkPredictorVerifier used in testing to ensure
+ * we're predicting the way we expect to. Not necessary (or desired) for
+ * normal operation.
+ */
+ [implicit_jscontext]
+ void predict(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorPredictReason reason,
+ in jsval originAttributes,
+ in nsINetworkPredictorVerifier verifier);
+
+ [notxpcom]
+ nsresult predictNative(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorPredictReason reason,
+ in OriginAttributes originAttributes,
+ in nsINetworkPredictorVerifier verifier);
+
+
+ /*
+ * Reasons we are learning something
+ *
+ * LEARN_LOAD_TOPLEVEL - we are learning about the toplevel resource of a
+ * pageload (NOTE: this should ONLY be used by tests)
+ *
+ * LEARN_LOAD_SUBRESOURCE - we are learning a subresource from a pageload
+ *
+ * LEARN_LOAD_REDIRECT - we are learning about the re-direct of a URI
+ *
+ * LEARN_STARTUP - we are learning about a page loaded during startup
+ */
+ const PredictorLearnReason LEARN_LOAD_TOPLEVEL = 0;
+ const PredictorLearnReason LEARN_LOAD_SUBRESOURCE = 1;
+ const PredictorLearnReason LEARN_LOAD_REDIRECT = 2;
+ const PredictorLearnReason LEARN_STARTUP = 3;
+
+ /**
+ * Add to our compendium of knowledge
+ *
+ * This adds to our prediction database to make things (hopefully)
+ * smarter next time we predict something.
+ *
+ * @param targetURI - The URI that was loaded that we are keeping track of.
+ * @param sourceURI - The URI that caused targetURI to be loaded (for page
+ * loads). This means the DOCUMENT URI.
+ * @param reason - The reason we are learning this bit of knowledge.
+ * Reason can be any of the LEARN_* values.
+ * In the case of LEARN_LOAD_SUBRESOURCE, targetURI should be the URI of a
+ * subresource of a page, and sourceURI should be the top-level URI.
+ * In the case of LEARN_LOAD_REDIRECT, targetURI is the NEW URI of a
+ * top-level resource that was redirected to, and sourceURI is the
+ * ORIGINAL URI of said top-level resource.
+ * In the case of LEARN_STARTUP, targetURI should be the URI of a page
+ * that was loaded immediately after browser startup, and sourceURI should
+ * be null.
+ * @param originAttributes - The originAttributes for the page load that we
+ * are learning about.
+ */
+ [implicit_jscontext]
+ void learn(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorLearnReason reason,
+ in jsval originAttributes);
+
+ [notxpcom]
+ nsresult learnNative(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorLearnReason reason,
+ in OriginAttributes originAttributes);
+
+ /**
+ * Clear out all our learned knowledge
+ *
+ * This removes everything from our database so that any predictions begun
+ * after this completes will start from a blank slate.
+ */
+ void reset();
+};
+
+%{C++
+// Wrapper functions to make use of the predictor easier and less invasive
+class nsIChannel;
+
+class nsILoadContext;
+class nsILoadGroup;
+class nsINetworkPredictorVerifier;
+
+namespace mozilla {
+
+class OriginAttributes;
+
+namespace net {
+
+nsresult PredictorPredict(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorPredictReason reason,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier *verifier);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& originAttributes);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadGroup *loadGroup);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ dom::Document *document);
+
+nsresult PredictorLearnRedirect(nsIURI *targetURI,
+ nsIChannel *channel,
+ const OriginAttributes& originAttributes);
+
+} // mozilla::net
+} // mozilla
+%}
diff --git a/netwerk/base/nsINetworkPredictorVerifier.idl b/netwerk/base/nsINetworkPredictorVerifier.idl
new file mode 100644
index 0000000000..b00aecc076
--- /dev/null
+++ b/netwerk/base/nsINetworkPredictorVerifier.idl
@@ -0,0 +1,40 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsINetworkPredictorVerifier - used for testing the network predictor to
+ * ensure it does what we expect it to do.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(2e43bb32-dabf-4494-9f90-2b3195b1c73d)]
+interface nsINetworkPredictorVerifier : nsISupports
+{
+ /**
+ * Callback for when we do a predictive prefetch
+ *
+ * @param uri - The URI that was prefetched
+ * @param status - The request status code returned by the
+ * prefetch attempt e.g. 200 (OK):w
+ */
+ void onPredictPrefetch(in nsIURI uri, in uint32_t status);
+
+ /**
+ * Callback for when we do a predictive preconnect
+ *
+ * @param uri - The URI that was preconnected to
+ */
+ void onPredictPreconnect(in nsIURI uri);
+
+ /**
+ * Callback for when we do a predictive DNS lookup
+ *
+ * @param uri - The URI that was looked up
+ */
+ void onPredictDNS(in nsIURI uri);
+};
diff --git a/netwerk/base/nsINullChannel.idl b/netwerk/base/nsINullChannel.idl
new file mode 100644
index 0000000000..6c03a2743b
--- /dev/null
+++ b/netwerk/base/nsINullChannel.idl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is only used in order to mark the fact that
+ * an object isn't a complete implementation of its interfaces.
+ * For example, a consumer can QI NullHttpChannel to nsINullChannel,
+ * to determine if the object is just a dummy implementation of nsIHttpChannel.
+ */
+[scriptable, builtinclass, uuid(4610b901-df41-4bb4-bd3f-fd4d6b6d8d68)]
+interface nsINullChannel : nsISupports
+{
+};
diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp
new file mode 100644
index 0000000000..8450a59b4a
--- /dev/null
+++ b/netwerk/base/nsIOService.cpp
@@ -0,0 +1,2194 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 cindent et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsIOService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIFileProtocolHandler.h"
+#include "nscore.h"
+#include "nsIURI.h"
+#include "prprf.h"
+#include "netCore.h"
+#include "nsIObserverService.h"
+#include "nsXPCOM.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsDNSService2.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsSimpleNestedURI.h"
+#include "nsSocketTransport2.h"
+#include "nsTArray.h"
+#include "nsIConsoleService.h"
+#include "nsIUploadChannel2.h"
+#include "nsXULAppAPI.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsICancelable.h"
+#include "nsINetworkLinkService.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsURLHelper.h"
+#include "nsIProtocolProxyService2.h"
+#include "MainThreadUtils.h"
+#include "nsINode.h"
+#include "nsIWebTransport.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "WebTransportSessionProxy.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/net/NetworkConnectivityService.h"
+#include "mozilla/net/SocketProcessHost.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "mozilla/Unused.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "nsNSSComponent.h"
+#include "ssl.h"
+#include "StaticComponents.h"
+
+namespace mozilla {
+namespace net {
+
+using mozilla::Maybe;
+using mozilla::dom::ClientInfo;
+using mozilla::dom::ServiceWorkerDescriptor;
+
+#define PORT_PREF_PREFIX "network.security.ports."
+#define PORT_PREF(x) PORT_PREF_PREFIX x
+#define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
+
+// Nb: these have been misnomers since bug 715770 removed the buffer cache.
+// "network.segment.count" and "network.segment.size" would be better names,
+// but the old names are still used to preserve backward compatibility.
+#define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count"
+#define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size"
+#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled"
+#define WEBRTC_PREF_PREFIX "media.peerconnection."
+#define NETWORK_DNS_PREF "network.dns."
+#define FORCE_EXTERNAL_PREF_PREFIX "network.protocol-handler.external."
+
+nsIOService* gIOService;
+static bool gHasWarnedUploadChannel2;
+static bool gCaptivePortalEnabled = false;
+static LazyLogModule gIOServiceLog("nsIOService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gIOServiceLog, LogLevel::Debug, args)
+
+// A general port blacklist. Connections to these ports will not be allowed
+// unless the protocol overrides.
+//
+// This list is to be kept in sync with "bad ports" as defined in the
+// WHATWG Fetch standard at <https://fetch.spec.whatwg.org/#port-blocking>
+
+int16_t gBadPortList[] = {
+ 1, // tcpmux
+ 7, // echo
+ 9, // discard
+ 11, // systat
+ 13, // daytime
+ 15, // netstat
+ 17, // qotd
+ 19, // chargen
+ 20, // ftp-data
+ 21, // ftp
+ 22, // ssh
+ 23, // telnet
+ 25, // smtp
+ 37, // time
+ 42, // name
+ 43, // nicname
+ 53, // domain
+ 69, // tftp
+ 77, // priv-rjs
+ 79, // finger
+ 87, // ttylink
+ 95, // supdup
+ 101, // hostriame
+ 102, // iso-tsap
+ 103, // gppitnp
+ 104, // acr-nema
+ 109, // pop2
+ 110, // pop3
+ 111, // sunrpc
+ 113, // auth
+ 115, // sftp
+ 117, // uucp-path
+ 119, // nntp
+ 123, // ntp
+ 135, // loc-srv / epmap
+ 137, // netbios
+ 139, // netbios
+ 143, // imap2
+ 161, // snmp
+ 179, // bgp
+ 389, // ldap
+ 427, // afp (alternate)
+ 465, // smtp (alternate)
+ 512, // print / exec
+ 513, // login
+ 514, // shell
+ 515, // printer
+ 526, // tempo
+ 530, // courier
+ 531, // chat
+ 532, // netnews
+ 540, // uucp
+ 548, // afp
+ 554, // rtsp
+ 556, // remotefs
+ 563, // nntp+ssl
+ 587, // smtp (outgoing)
+ 601, // syslog-conn
+ 636, // ldap+ssl
+ 989, // ftps-data
+ 990, // ftps
+ 993, // imap+ssl
+ 995, // pop3+ssl
+ 1719, // h323gatestat
+ 1720, // h323hostcall
+ 1723, // pptp
+ 2049, // nfs
+ 3659, // apple-sasl
+ 4045, // lockd
+ 5060, // sip
+ 5061, // sips
+ 6000, // x11
+ 6566, // sane-port
+ 6665, // irc (alternate)
+ 6666, // irc (alternate)
+ 6667, // irc (default)
+ 6668, // irc (alternate)
+ 6669, // irc (alternate)
+ 6697, // irc+tls
+ 10080, // amanda
+ 0, // Sentinel value: This MUST be zero
+};
+
+static const char kProfileChangeNetTeardownTopic[] =
+ "profile-change-net-teardown";
+static const char kProfileChangeNetRestoreTopic[] =
+ "profile-change-net-restore";
+static const char kProfileDoChange[] = "profile-do-change";
+
+// Necko buffer defaults
+uint32_t nsIOService::gDefaultSegmentSize = 4096;
+uint32_t nsIOService::gDefaultSegmentCount = 24;
+
+uint32_t nsIOService::sSocketProcessCrashedCount = 0;
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsIOService::nsIOService()
+ : mLastOfflineStateChange(PR_IntervalNow()),
+ mLastConnectivityChange(PR_IntervalNow()),
+ mLastNetworkLinkChange(PR_IntervalNow()) {}
+
+static const char* gCallbackPrefs[] = {
+ PORT_PREF_PREFIX,
+ MANAGE_OFFLINE_STATUS_PREF,
+ NECKO_BUFFER_CACHE_COUNT_PREF,
+ NECKO_BUFFER_CACHE_SIZE_PREF,
+ NETWORK_CAPTIVE_PORTAL_PREF,
+ FORCE_EXTERNAL_PREF_PREFIX,
+ nullptr,
+};
+
+static const char* gCallbackPrefsForSocketProcess[] = {
+ WEBRTC_PREF_PREFIX,
+ NETWORK_DNS_PREF,
+ "network.send_ODA_to_content_directly",
+ "network.trr.",
+ "doh-rollout.",
+ "network.dns.disableIPv6",
+ "network.offline-mirrors-connectivity",
+ "network.disable-localhost-when-offline",
+ "network.proxy.parse_pac_on_socket_process",
+ "network.proxy.allow_hijacking_localhost",
+ "network.connectivity-service.",
+ "network.captive-portal-service.testMode",
+ nullptr,
+};
+
+static const char* gCallbackSecurityPrefs[] = {
+ // Note the prefs listed below should be in sync with the code in
+ // HandleTLSPrefChange().
+ "security.tls.version.min",
+ "security.tls.version.max",
+ "security.tls.version.enable-deprecated",
+ "security.tls.hello_downgrade_check",
+ "security.ssl.require_safe_negotiation",
+ "security.ssl.enable_false_start",
+ "security.ssl.enable_alpn",
+ "security.tls.enable_0rtt_data",
+ "security.ssl.disable_session_identifiers",
+ "security.tls.enable_post_handshake_auth",
+ "security.tls.enable_delegated_credentials",
+ // Note the prefs listed below should be in sync with the code in
+ // SetValidationOptionsCommon().
+ "security.ssl.enable_ocsp_stapling",
+ "security.ssl.enable_ocsp_must_staple",
+ "security.pki.certificate_transparency.mode",
+ nullptr,
+};
+
+nsresult nsIOService::Init() {
+ SSLTokensCache::Init();
+
+ InitializeCaptivePortalService();
+
+ // setup our bad port list stuff
+ for (int i = 0; gBadPortList[i]; i++) {
+ // We can't be accessed by another thread yet
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ mRestrictedPortList.AppendElement(gBadPortList[i]);
+ MOZ_POP_THREAD_SAFETY
+ }
+
+ // Further modifications to the port list come from prefs
+ Preferences::RegisterPrefixCallbacks(nsIOService::PrefsChanged,
+ gCallbackPrefs, this);
+ PrefsChanged();
+
+ mSocketProcessTopicBlockedList.Insert(
+ nsLiteralCString(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID));
+ mSocketProcessTopicBlockedList.Insert(
+ nsLiteralCString(NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+ mSocketProcessTopicBlockedList.Insert("xpcom-shutdown-threads"_ns);
+ mSocketProcessTopicBlockedList.Insert("profile-do-change"_ns);
+ mSocketProcessTopicBlockedList.Insert("network:socket-process-crashed"_ns);
+
+ // Register for profile change notifications
+ mObserverService = services::GetObserverService();
+ AddObserver(this, kProfileChangeNetTeardownTopic, true);
+ AddObserver(this, kProfileChangeNetRestoreTopic, true);
+ AddObserver(this, kProfileDoChange, true);
+ AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ AddObserver(this, NS_NETWORK_ID_CHANGED_TOPIC, true);
+ AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+
+ // Register observers for sending notifications to nsSocketTransportService
+ if (XRE_IsParentProcess()) {
+ AddObserver(this, "profile-initial-state", true);
+ AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
+ }
+
+ if (IsSocketProcessChild()) {
+ Preferences::RegisterCallbacks(nsIOService::OnTLSPrefChange,
+ gCallbackSecurityPrefs, this);
+ }
+
+ gIOService = this;
+
+ InitializeNetworkLinkService();
+ InitializeProtocolProxyService();
+
+ SetOffline(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::AddObserver(nsIObserver* aObserver, const char* aTopic,
+ bool aOwnsWeak) {
+ if (!mObserverService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Register for the origional observer.
+ nsresult rv = mObserverService->AddObserver(aObserver, aTopic, aOwnsWeak);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ return NS_OK;
+ }
+
+ nsAutoCString topic(aTopic);
+ // This happens when AddObserver() is called by nsIOService::Init(). We don't
+ // want to add nsIOService again.
+ if (SameCOMIdentity(aObserver, static_cast<nsIObserver*>(this))) {
+ mIOServiceTopicList.Insert(topic);
+ return NS_OK;
+ }
+
+ if (!UseSocketProcess()) {
+ return NS_OK;
+ }
+
+ if (mSocketProcessTopicBlockedList.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Avoid registering duplicate topics.
+ if (mObserverTopicForSocketProcess.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserverTopicForSocketProcess.Insert(topic);
+
+ // Avoid registering duplicate topics.
+ if (mIOServiceTopicList.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mObserverService->AddObserver(this, aTopic, true);
+}
+
+NS_IMETHODIMP
+nsIOService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIOService::EnumerateObservers(const char* aTopic,
+ nsISimpleEnumerator** anEnumerator) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsIOService::NotifyObservers(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIOService::~nsIOService() {
+ if (gIOService) {
+ MOZ_ASSERT(gIOService == this);
+ gIOService = nullptr;
+ }
+}
+
+// static
+void nsIOService::OnTLSPrefChange(const char* aPref, void* aSelf) {
+ MOZ_ASSERT(IsSocketProcessChild());
+
+ if (!EnsureNSSInitializedChromeOrContent()) {
+ LOG(("NSS not initialized."));
+ return;
+ }
+
+ nsAutoCString pref(aPref);
+ // The preferences listed in gCallbackSecurityPrefs need to be in sync with
+ // the code in HandleTLSPrefChange() and SetValidationOptionsCommon().
+ if (HandleTLSPrefChange(pref)) {
+ LOG(("HandleTLSPrefChange done"));
+ } else if (pref.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
+ pref.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
+ pref.EqualsLiteral("security.pki.certificate_transparency.mode")) {
+ SetValidationOptionsCommon();
+ }
+}
+
+nsresult nsIOService::InitializeCaptivePortalService() {
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // We only initalize a captive portal service in the main process
+ return NS_OK;
+ }
+
+ mCaptivePortalService = do_GetService(NS_CAPTIVEPORTAL_CID);
+ if (mCaptivePortalService) {
+ return static_cast<CaptivePortalService*>(mCaptivePortalService.get())
+ ->Initialize();
+ }
+
+ // Instantiate and initialize the service
+ RefPtr<NetworkConnectivityService> ncs =
+ NetworkConnectivityService::GetSingleton();
+
+ return NS_OK;
+}
+
+nsresult nsIOService::InitializeSocketTransportService() {
+ nsresult rv = NS_OK;
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ LOG(
+ ("nsIOService aborting InitializeSocketTransportService because of app "
+ "shutdown"));
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (!mSocketTransportService) {
+ mSocketTransportService =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get socket transport service");
+ }
+ }
+
+ if (mSocketTransportService) {
+ rv = mSocketTransportService->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service init failed");
+ mSocketTransportService->SetOffline(false);
+ }
+
+ return rv;
+}
+
+nsresult nsIOService::InitializeNetworkLinkService() {
+ nsresult rv = NS_OK;
+
+ if (mNetworkLinkServiceInitialized) return rv;
+
+ if (!NS_IsMainThread()) {
+ NS_WARNING("Network link service should be created on main thread");
+ return NS_ERROR_FAILURE;
+ }
+
+ // go into managed mode if we can, and chrome process
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetworkLinkService = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+
+ if (mNetworkLinkService) {
+ mNetworkLinkServiceInitialized = true;
+ }
+
+ // After initializing the networkLinkService, query the connectivity state
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+
+ return rv;
+}
+
+nsresult nsIOService::InitializeProtocolProxyService() {
+ nsresult rv = NS_OK;
+
+ if (XRE_IsParentProcess()) {
+ // for early-initialization
+ Unused << do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ }
+
+ return rv;
+}
+
+already_AddRefed<nsIOService> nsIOService::GetInstance() {
+ if (!gIOService) {
+ RefPtr<nsIOService> ios = new nsIOService();
+ if (NS_SUCCEEDED(ios->Init())) {
+ MOZ_ASSERT(gIOService == ios.get());
+ return ios.forget();
+ }
+ }
+ return do_AddRef(gIOService);
+}
+
+class SocketProcessListenerProxy : public SocketProcessHost::Listener {
+ public:
+ SocketProcessListenerProxy() = default;
+ void OnProcessLaunchComplete(SocketProcessHost* aHost, bool aSucceeded) {
+ if (!gIOService) {
+ return;
+ }
+
+ gIOService->OnProcessLaunchComplete(aHost, aSucceeded);
+ }
+
+ void OnProcessUnexpectedShutdown(SocketProcessHost* aHost) {
+ if (!gIOService) {
+ return;
+ }
+
+ gIOService->OnProcessUnexpectedShutdown(aHost);
+ }
+};
+
+// static
+bool nsIOService::TooManySocketProcessCrash() {
+ return sSocketProcessCrashedCount >=
+ StaticPrefs::network_max_socket_process_failed_count();
+}
+
+// static
+void nsIOService::IncreaseSocketProcessCrashCount() {
+ MOZ_ASSERT(IsNeckoChild());
+ sSocketProcessCrashedCount++;
+}
+
+nsresult nsIOService::LaunchSocketProcess() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return NS_OK;
+ }
+
+ // We shouldn't launch socket prcess when shutdown begins.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_OK;
+ }
+
+ if (mSocketProcess) {
+ return NS_OK;
+ }
+
+ if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) {
+ LOG(("nsIOService skipping LaunchSocketProcess because of the env"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_process_enabled()) {
+ LOG(("nsIOService skipping LaunchSocketProcess because of the pref"));
+ return NS_OK;
+ }
+
+ Preferences::RegisterPrefixCallbacks(
+ nsIOService::NotifySocketProcessPrefsChanged,
+ gCallbackPrefsForSocketProcess, this);
+
+ // The subprocess is launched asynchronously, so we wait for a callback to
+ // acquire the IPDL actor.
+ mSocketProcess = new SocketProcessHost(new SocketProcessListenerProxy());
+ LOG(("nsIOService::LaunchSocketProcess"));
+ if (!mSocketProcess->Launch()) {
+ NS_WARNING("Failed to launch socket process!!");
+ DestroySocketProcess();
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void nsIOService::DestroySocketProcess() {
+ LOG(("nsIOService::DestroySocketProcess"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default || !mSocketProcess) {
+ return;
+ }
+
+ Preferences::UnregisterPrefixCallbacks(
+ nsIOService::NotifySocketProcessPrefsChanged,
+ gCallbackPrefsForSocketProcess, this);
+
+ mSocketProcess->Shutdown();
+ mSocketProcess = nullptr;
+}
+
+bool nsIOService::SocketProcessReady() {
+ return mSocketProcess && mSocketProcess->IsConnected();
+}
+
+static bool sUseSocketProcess = false;
+static bool sUseSocketProcessChecked = false;
+
+// static
+bool nsIOService::UseSocketProcess(bool aCheckAgain) {
+ if (sUseSocketProcessChecked && !aCheckAgain) {
+ return sUseSocketProcess;
+ }
+
+ sUseSocketProcessChecked = true;
+ sUseSocketProcess = false;
+
+ if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) {
+ return sUseSocketProcess;
+ }
+
+ if (TooManySocketProcessCrash()) {
+ LOG(("TooManySocketProcessCrash"));
+ return sUseSocketProcess;
+ }
+
+ if (PR_GetEnv("MOZ_FORCE_USE_SOCKET_PROCESS")) {
+ sUseSocketProcess = true;
+ return sUseSocketProcess;
+ }
+
+ if (StaticPrefs::network_process_enabled()) {
+ sUseSocketProcess =
+ StaticPrefs::network_http_network_access_on_socket_process_enabled();
+ }
+ return sUseSocketProcess;
+}
+
+// static
+void nsIOService::NotifySocketProcessPrefsChanged(const char* aName,
+ void* aSelf) {
+ static_cast<nsIOService*>(aSelf)->NotifySocketProcessPrefsChanged(aName);
+}
+
+void nsIOService::NotifySocketProcessPrefsChanged(const char* aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (!StaticPrefs::network_process_enabled()) {
+ return;
+ }
+
+ dom::Pref pref(nsCString(aName), /* isLocked */ false,
+ /* isSanitized */ false, Nothing(), Nothing());
+
+ Preferences::GetPreference(&pref, GeckoProcessType_Socket,
+ /* remoteType */ ""_ns);
+ auto sendPrefUpdate = [pref]() {
+ Unused << gIOService->mSocketProcess->GetActor()->SendPreferenceUpdate(
+ pref);
+ };
+ CallOrWaitForSocketProcess(sendPrefUpdate);
+}
+
+void nsIOService::OnProcessLaunchComplete(SocketProcessHost* aHost,
+ bool aSucceeded) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsIOService::OnProcessLaunchComplete aSucceeded=%d\n", aSucceeded));
+
+ mSocketProcessLaunchComplete = aSucceeded;
+
+ if (mShutdown || !SocketProcessReady() || !aSucceeded) {
+ mPendingEvents.Clear();
+ return;
+ }
+
+ if (!mPendingEvents.IsEmpty()) {
+ nsTArray<std::function<void()>> pendingEvents = std::move(mPendingEvents);
+ for (auto& func : pendingEvents) {
+ func();
+ }
+ }
+}
+
+void nsIOService::CallOrWaitForSocketProcess(
+ const std::function<void()>& aFunc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsSocketProcessLaunchComplete() && SocketProcessReady()) {
+ aFunc();
+ } else {
+ mPendingEvents.AppendElement(aFunc); // infallible
+ LaunchSocketProcess();
+ }
+}
+
+int32_t nsIOService::SocketProcessPid() {
+ if (!mSocketProcess) {
+ return 0;
+ }
+ if (SocketProcessParent* actor = mSocketProcess->GetActor()) {
+ return (int32_t)actor->OtherPid();
+ }
+ return 0;
+}
+
+bool nsIOService::IsSocketProcessLaunchComplete() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mSocketProcessLaunchComplete;
+}
+
+void nsIOService::OnProcessUnexpectedShutdown(SocketProcessHost* aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsIOService::OnProcessUnexpectedShutdown\n"));
+ DestroySocketProcess();
+ mPendingEvents.Clear();
+
+ // Nothing to do if socket process was not used before.
+ if (!UseSocketProcess()) {
+ return;
+ }
+
+ sSocketProcessCrashedCount++;
+ if (TooManySocketProcessCrash()) {
+ sUseSocketProcessChecked = false;
+ DNSServiceWrapper::SwitchToBackupDNSService();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ (void)observerService->NotifyObservers(
+ nullptr, "network:socket-process-crashed", nullptr);
+ }
+
+ // UseSocketProcess() could return false if we have too many crashes, so we
+ // should call it again.
+ if (UseSocketProcess()) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+ NewRunnableMethod("nsIOService::LaunchSocketProcess", this,
+ &nsIOService::LaunchSocketProcess)));
+ }
+}
+
+RefPtr<MemoryReportingProcess> nsIOService::GetSocketProcessMemoryReporter() {
+ // Check the prefs here again, since we don't want to create
+ // SocketProcessMemoryReporter for some tests.
+ if (!StaticPrefs::network_process_enabled() || !SocketProcessReady()) {
+ return nullptr;
+ }
+
+ return new SocketProcessMemoryReporter();
+}
+
+NS_IMETHODIMP
+nsIOService::SocketProcessTelemetryPing() {
+ CallOrWaitForSocketProcess([]() {
+ Unused << gIOService->mSocketProcess->GetActor()
+ ->SendSocketProcessTelemetryPing();
+ });
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsIOService, nsIIOService, nsINetUtil, nsISpeculativeConnect,
+ nsIObserver, nsIIOServiceInternal, nsISupportsWeakReference,
+ nsIObserverService)
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsIOService::RecheckCaptivePortal() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ if (!mCaptivePortalService) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIRunnable> task = NewRunnableMethod(
+ "nsIOService::RecheckCaptivePortal", mCaptivePortalService,
+ &nsICaptivePortalService::RecheckCaptivePortal);
+ return NS_DispatchToMainThread(task);
+}
+
+nsresult nsIOService::RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan) {
+ nsresult rv;
+
+ if (!mCaptivePortalService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = newChan->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NetAddr addr;
+ // If the redirect wasn't to an IP literal, so there's probably no need
+ // to trigger the captive portal detection right now. It can wait.
+ if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) {
+ RecheckCaptivePortal();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsIOService::AsyncOnChannelRedirect(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsAsyncRedirectVerifyHelper* helper) {
+ // If a redirect to a local network address occurs, then chances are we
+ // are in a captive portal, so we trigger a recheck.
+ RecheckCaptivePortalIfLocalRedirect(newChan);
+
+ // This is silly. I wish there was a simpler way to get at the global
+ // reference of the contentSecurityManager. But it lives in the XPCOM
+ // service registry.
+ nsCOMPtr<nsIChannelEventSink> sink =
+ do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
+ if (sink) {
+ nsresult rv =
+ helper->DelegateOnChannelRedirect(sink, oldChan, newChan, flags);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Finally, our category
+ nsCOMArray<nsIChannelEventSink> entries;
+ mChannelEventSinks.GetEntries(entries);
+ int32_t len = entries.Count();
+ for (int32_t i = 0; i < len; ++i) {
+ nsresult rv =
+ helper->DelegateOnChannelRedirect(entries[i], oldChan, newChan, flags);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(oldChan));
+
+ // Collect the redirection from HTTP(S) only.
+ if (httpChan) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIURI> newURI;
+ newChan->GetURI(getter_AddRefs(newURI));
+ MOZ_ASSERT(newURI);
+
+ nsAutoCString scheme;
+ newURI->GetScheme(scheme);
+ MOZ_ASSERT(!scheme.IsEmpty());
+
+ Telemetry::AccumulateCategoricalKeyed(
+ scheme,
+ oldChan->IsDocument()
+ ? Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::topLevel
+ : Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::subresource);
+ }
+
+ return NS_OK;
+}
+
+bool nsIOService::UsesExternalProtocolHandler(const nsACString& aScheme) {
+ if (aScheme == "file"_ns || aScheme == "chrome"_ns ||
+ aScheme == "resource"_ns) {
+ // Don't allow file:, chrome: or resource: URIs to be handled with
+ // nsExternalProtocolHandler, since internally we rely on being able to
+ // use and read from these URIs.
+ return false;
+ }
+
+ if (aScheme == "place"_ns || aScheme == "fake-favicon-uri"_ns ||
+ aScheme == "favicon"_ns || aScheme == "moz-nullprincipal"_ns) {
+ // Force place: fake-favicon-uri: favicon: and moz-nullprincipal: URIs to be
+ // handled with nsExternalProtocolHandler, and not with a dynamically
+ // registered handler.
+ return true;
+ }
+
+ // If prefs configure the URI to be handled externally, do so.
+ for (const auto& scheme : mForceExternalSchemes) {
+ if (aScheme == scheme) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ProtocolHandlerInfo nsIOService::LookupProtocolHandler(
+ const nsACString& aScheme) {
+ // Look-ups are ASCII-case-insensitive, so lower-case the string before
+ // continuing.
+ nsAutoCString scheme(aScheme);
+ ToLowerCase(scheme);
+
+ // NOTE: If we could get rid of mForceExternalSchemes (or prevent them from
+ // disabling static protocols), we could avoid locking mLock until we need to
+ // check `mRuntimeProtocolHandlers.
+ AutoReadLock lock(mLock);
+ if (!UsesExternalProtocolHandler(scheme)) {
+ // Try the static protocol handler first - they cannot be overridden by
+ // dynamic protocols.
+ if (const xpcom::StaticProtocolHandler* handler =
+ xpcom::StaticProtocolHandler::Lookup(scheme)) {
+ return ProtocolHandlerInfo(*handler);
+ }
+ if (auto handler = mRuntimeProtocolHandlers.Lookup(scheme)) {
+ return ProtocolHandlerInfo(handler.Data());
+ }
+ }
+ return ProtocolHandlerInfo(xpcom::StaticProtocolHandler::Default());
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolHandler(const char* scheme,
+ nsIProtocolHandler** result) {
+ AssertIsOnMainThread();
+ NS_ENSURE_ARG_POINTER(scheme);
+
+ *result = LookupProtocolHandler(nsDependentCString(scheme)).Handler().take();
+ return *result ? NS_OK : NS_ERROR_UNKNOWN_PROTOCOL;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractScheme(const nsACString& inURI, nsACString& scheme) {
+ return net_ExtractURLScheme(inURI, scheme);
+}
+
+NS_IMETHODIMP
+nsIOService::HostnameIsLocalIPAddress(nsIURI* aURI, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_ARG_POINTER(innerURI);
+
+ nsAutoCString host;
+ nsresult rv = innerURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = false;
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) {
+ *aResult = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::HostnameIsSharedIPAddress(nsIURI* aURI, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_ARG_POINTER(innerURI);
+
+ nsAutoCString host;
+ nsresult rv = innerURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = false;
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrShared()) {
+ *aResult = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::IsValidHostname(const nsACString& inHostname, bool* aResult) {
+ *aResult = net_IsValidHostName(inHostname);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolFlags(const char* scheme, uint32_t* flags) {
+ NS_ENSURE_ARG_POINTER(scheme);
+
+ *flags =
+ LookupProtocolHandler(nsDependentCString(scheme)).StaticProtocolFlags();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetDynamicProtocolFlags(nsIURI* uri, uint32_t* flags) {
+ AssertIsOnMainThread();
+ NS_ENSURE_ARG(uri);
+
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return LookupProtocolHandler(scheme).DynamicProtocolFlags(uri, flags);
+}
+
+NS_IMETHODIMP
+nsIOService::GetDefaultPort(const char* scheme, int32_t* defaultPort) {
+ NS_ENSURE_ARG_POINTER(scheme);
+
+ *defaultPort =
+ LookupProtocolHandler(nsDependentCString(scheme)).DefaultPort();
+ return NS_OK;
+}
+
+nsresult nsIOService::NewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result) {
+ return NS_NewURI(result, aSpec, aCharset, aBaseURI);
+}
+
+NS_IMETHODIMP
+nsIOService::NewFileURI(nsIFile* file, nsIURI** result) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+
+ rv = GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler(do_QueryInterface(handler, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return fileHandler->NewFileURI(file, result);
+}
+
+// static
+already_AddRefed<nsIURI> nsIOService::CreateExposableURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ nsCOMPtr<nsIURI> uri = aURI;
+ bool hasUserPass;
+ if (NS_SUCCEEDED(aURI->GetHasUserPass(&hasUserPass)) && hasUserPass) {
+ DebugOnly<nsresult> rv = NS_MutateURI(uri).SetUserPass(""_ns).Finalize(uri);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && uri, "Mutating URI should never fail");
+ }
+ return uri.forget();
+}
+
+NS_IMETHODIMP
+nsIOService::CreateExposableURI(nsIURI* aURI, nsIURI** _result) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(_result);
+ nsCOMPtr<nsIURI> exposableURI = CreateExposableURI(aURI);
+ exposableURI.forget(_result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURI(nsIURI* aURI, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsIChannel** result) {
+ return NewChannelFromURIWithProxyFlags(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, aSecurityFlags,
+ aContentPolicyType, result);
+}
+nsresult nsIOService::NewChannelFromURIWithClientAndController(
+ nsIURI* aURI, nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const Maybe<ClientInfo>& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags,
+ bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** aResult) {
+ return NewChannelFromURIWithProxyFlagsInternal(
+ aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, aLoadingClientInfo,
+ aController, aSecurityFlags, aContentPolicyType, aSandboxFlags,
+ aSkipCheckForBrokenURLOrZeroSized, aResult);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithLoadInfo(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ return NewChannelFromURIWithProxyFlagsInternal(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadInfo, result);
+}
+
+nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const Maybe<ClientInfo>& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags,
+ bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** result) {
+ nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType, aLoadingClientInfo, aController, aSandboxFlags,
+ aSkipCheckForBrokenURLOrZeroSized);
+ return NewChannelFromURIWithProxyFlagsInternal(aURI, aProxyURI, aProxyFlags,
+ loadInfo, result);
+}
+
+nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo, nsIChannel** result) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aURI);
+ // all channel creations must provide a valid loadinfo
+ MOZ_ASSERT(aLoadInfo, "can not create channel without aLoadInfo");
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler);
+ if (pph) {
+ rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI,
+ aLoadInfo, getter_AddRefs(channel));
+ } else {
+ rv = handler->NewChannel(aURI, aLoadInfo, getter_AddRefs(channel));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // Make sure that all the individual protocolhandlers attach a loadInfo.
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (aLoadInfo != loadInfo) {
+ MOZ_ASSERT(false, "newly created channel must have a loadinfo attached");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (loadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+
+ // Some extensions override the http protocol handler and provide their own
+ // implementation. The channels returned from that implementation doesn't
+ // seem to always implement the nsIUploadChannel2 interface, presumably
+ // because it's a new interface.
+ // Eventually we should remove this and simply require that http channels
+ // implement the new interface.
+ // See bug 529041
+ if (!gHasWarnedUploadChannel2 && scheme.EqualsLiteral("http")) {
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(channel);
+ if (!uploadChannel2) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ consoleService->LogStringMessage(
+ u"Http channel implementation "
+ "doesn't support nsIUploadChannel2. An extension has "
+ "supplied a non-functional http protocol handler. This will "
+ "break behavior and in future releases not work at all.");
+ }
+ gHasWarnedUploadChannel2 = true;
+ }
+ }
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithProxyFlags(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, nsIChannel** result) {
+ return NewChannelFromURIWithProxyFlagsInternal(
+ aURI, aProxyURI, aProxyFlags, aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, Maybe<ClientInfo>(),
+ Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType, 0,
+ /* aSkipCheckForBrokenURLOrZeroSized = */ false, result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannel(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsIChannel** result) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ return NewChannelFromURI(uri, aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, aSecurityFlags,
+ aContentPolicyType, result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewWebTransport(nsIWebTransport** result) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIWebTransport> webTransport = new WebTransportSessionProxy();
+
+ webTransport.forget(result);
+ return NS_OK;
+}
+
+bool nsIOService::IsLinkUp() {
+ InitializeNetworkLinkService();
+
+ if (!mNetworkLinkService) {
+ // We cannot decide, assume the link is up
+ return true;
+ }
+
+ bool isLinkUp;
+ nsresult rv;
+ rv = mNetworkLinkService->GetIsLinkUp(&isLinkUp);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ return isLinkUp;
+}
+
+NS_IMETHODIMP
+nsIOService::GetOffline(bool* offline) {
+ if (StaticPrefs::network_offline_mirrors_connectivity()) {
+ *offline = mOffline || !mConnectivity;
+ } else {
+ *offline = mOffline;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetOffline(bool offline) { return SetOfflineInternal(offline); }
+
+nsresult nsIOService::SetOfflineInternal(bool offline,
+ bool notifySocketProcess) {
+ LOG(("nsIOService::SetOffline offline=%d\n", offline));
+ // When someone wants to go online (!offline) after we got XPCOM shutdown
+ // throw ERROR_NOT_AVAILABLE to prevent return to online state.
+ if ((mShutdown || mOfflineForProfileChange) && !offline) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // SetOffline() may re-enter while it's shutting down services.
+ // If that happens, save the most recent value and it will be
+ // processed when the first SetOffline() call is done bringing
+ // down the service.
+ mSetOfflineValue = offline;
+ if (mSettingOffline) {
+ return NS_OK;
+ }
+
+ mSettingOffline = true;
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (XRE_IsParentProcess()) {
+ if (observerService) {
+ (void)observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC,
+ offline ? u"true" : u"false");
+ }
+ if (SocketProcessReady() && notifySocketProcess) {
+ Unused << mSocketProcess->GetActor()->SendSetOffline(offline);
+ }
+ }
+
+ nsIIOService* subject = static_cast<nsIIOService*>(this);
+ while (mSetOfflineValue != mOffline) {
+ offline = mSetOfflineValue;
+
+ if (offline && !mOffline) {
+ mOffline = true; // indicate we're trying to shutdown
+
+ // don't care if notifications fail
+ if (observerService) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ }
+
+ if (mSocketTransportService) mSocketTransportService->SetOffline(true);
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ if (observerService) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ }
+ } else if (!offline && mOffline) {
+ // go online
+ InitializeSocketTransportService();
+ mOffline = false; // indicate success only AFTER we've
+ // brought up the services
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ // don't care if notification fails
+ // Only send the ONLINE notification if there is connectivity
+ if (observerService && mConnectivity) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ }
+ }
+ }
+
+ // Don't notify here, as the above notifications (if used) suffice.
+ if ((mShutdown || mOfflineForProfileChange) && mOffline) {
+ if (mSocketTransportService) {
+ DebugOnly<nsresult> rv = mSocketTransportService->Shutdown(mShutdown);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "socket transport service shutdown failed");
+ }
+ }
+
+ mSettingOffline = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetConnectivity(bool* aConnectivity) {
+ *aConnectivity = mConnectivity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetConnectivity(bool aConnectivity) {
+ LOG(("nsIOService::SetConnectivity aConnectivity=%d\n", aConnectivity));
+ // This should only be called from ContentChild to pass the connectivity
+ // value from the chrome process to the content process.
+ if (XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SetConnectivityInternal(aConnectivity);
+}
+
+nsresult nsIOService::SetConnectivityInternal(bool aConnectivity) {
+ LOG(("nsIOService::SetConnectivityInternal aConnectivity=%d\n",
+ aConnectivity));
+ if (mConnectivity == aConnectivity) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+ mConnectivity = aConnectivity;
+
+ // This is used for PR_Connect PR_Close telemetry so it is important that
+ // we have statistic about network change event even if we are offline.
+ mLastConnectivityChange = PR_IntervalNow();
+
+ if (mCaptivePortalService) {
+ if (aConnectivity && gCaptivePortalEnabled) {
+ // This will also trigger a captive portal check for the new network
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ return NS_OK;
+ }
+ // This notification sends the connectivity to the child processes
+ if (XRE_IsParentProcess()) {
+ observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC,
+ aConnectivity ? u"true" : u"false");
+ if (SocketProcessReady()) {
+ Unused << mSocketProcess->GetActor()->SendSetConnectivity(aConnectivity);
+ }
+ }
+
+ if (mOffline) {
+ // We don't need to send any notifications if we're offline
+ return NS_OK;
+ }
+
+ if (aConnectivity) {
+ // If we were previously offline due to connectivity=false,
+ // send the ONLINE notification
+ observerService->NotifyObservers(static_cast<nsIIOService*>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ } else {
+ // If we were previously online and lost connectivity
+ // send the OFFLINE notification
+ observerService->NotifyObservers(static_cast<nsIIOService*>(this),
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ observerService->NotifyObservers(static_cast<nsIIOService*>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::AllowPort(int32_t inPort, const char* scheme, bool* _retval) {
+ int32_t port = inPort;
+ if (port == -1) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (port <= 0 || port > std::numeric_limits<uint16_t>::max()) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ nsTArray<int32_t> restrictedPortList;
+ {
+ AutoReadLock lock(mLock);
+ restrictedPortList.Assign(mRestrictedPortList);
+ }
+ // first check to see if the port is in our blacklist:
+ int32_t badPortListCnt = restrictedPortList.Length();
+ for (int i = 0; i < badPortListCnt; i++) {
+ if (port == restrictedPortList[i]) {
+ *_retval = false;
+
+ // check to see if the protocol wants to override
+ if (!scheme) return NS_OK;
+
+ // We don't support get protocol handler off main thread.
+ if (!NS_IsMainThread()) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // let the protocol handler decide
+ return handler->AllowPort(port, scheme, _retval);
+ }
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// static
+void nsIOService::PrefsChanged(const char* pref, void* self) {
+ static_cast<nsIOService*>(self)->PrefsChanged(pref);
+}
+
+void nsIOService::PrefsChanged(const char* pref) {
+ // Look for extra ports to block
+ if (!pref || strcmp(pref, PORT_PREF("banned")) == 0) {
+ ParsePortList(PORT_PREF("banned"), false);
+ }
+
+ // ...as well as previous blocks to remove.
+ if (!pref || strcmp(pref, PORT_PREF("banned.override")) == 0) {
+ ParsePortList(PORT_PREF("banned.override"), true);
+ }
+
+ if (!pref || strcmp(pref, MANAGE_OFFLINE_STATUS_PREF) == 0) {
+ bool manage;
+ if (mNetworkLinkServiceInitialized &&
+ NS_SUCCEEDED(
+ Preferences::GetBool(MANAGE_OFFLINE_STATUS_PREF, &manage))) {
+ LOG(("nsIOService::PrefsChanged ManageOfflineStatus manage=%d\n",
+ manage));
+ SetManageOfflineStatus(manage);
+ }
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_COUNT_PREF) == 0) {
+ int32_t count;
+ if (NS_SUCCEEDED(
+ Preferences::GetInt(NECKO_BUFFER_CACHE_COUNT_PREF, &count))) {
+ /* check for bogus values and default if we find such a value */
+ if (count > 0) gDefaultSegmentCount = count;
+ }
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_SIZE_PREF) == 0) {
+ int32_t size;
+ if (NS_SUCCEEDED(
+ Preferences::GetInt(NECKO_BUFFER_CACHE_SIZE_PREF, &size))) {
+ /* check for bogus values and default if we find such a value
+ * the upper limit here is arbitrary. having a 1mb segment size
+ * is pretty crazy. if you remove this, consider adding some
+ * integer rollover test.
+ */
+ if (size > 0 && size < 1024 * 1024) gDefaultSegmentSize = size;
+ }
+ NS_WARNING_ASSERTION(!(size & (size - 1)),
+ "network segment size is not a power of 2!");
+ }
+
+ if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) {
+ nsresult rv = Preferences::GetBool(NETWORK_CAPTIVE_PORTAL_PREF,
+ &gCaptivePortalEnabled);
+ if (NS_SUCCEEDED(rv) && mCaptivePortalService) {
+ if (gCaptivePortalEnabled) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())
+ ->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+ }
+
+ if (!pref || strncmp(pref, FORCE_EXTERNAL_PREF_PREFIX,
+ strlen(FORCE_EXTERNAL_PREF_PREFIX)) == 0) {
+ nsTArray<nsCString> prefs;
+ if (nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch()) {
+ prefRootBranch->GetChildList(FORCE_EXTERNAL_PREF_PREFIX, prefs);
+ }
+ nsTArray<nsCString> forceExternalSchemes;
+ for (const auto& pref : prefs) {
+ if (Preferences::GetBool(pref.get(), false)) {
+ forceExternalSchemes.AppendElement(
+ Substring(pref, strlen(FORCE_EXTERNAL_PREF_PREFIX)));
+ }
+ }
+ AutoWriteLock lock(mLock);
+ mForceExternalSchemes = std::move(forceExternalSchemes);
+ }
+}
+
+void nsIOService::ParsePortList(const char* pref, bool remove) {
+ nsAutoCString portList;
+ nsTArray<int32_t> restrictedPortList;
+ {
+ AutoWriteLock lock(mLock);
+ restrictedPortList.Assign(std::move(mRestrictedPortList));
+ }
+ // Get a pref string and chop it up into a list of ports.
+ Preferences::GetCString(pref, portList);
+ if (!portList.IsVoid()) {
+ nsTArray<nsCString> portListArray;
+ ParseString(portList, ',', portListArray);
+ uint32_t index;
+ for (index = 0; index < portListArray.Length(); index++) {
+ portListArray[index].StripWhitespace();
+ int32_t portBegin, portEnd;
+
+ if (PR_sscanf(portListArray[index].get(), "%d-%d", &portBegin,
+ &portEnd) == 2) {
+ if ((portBegin < 65536) && (portEnd < 65536)) {
+ int32_t curPort;
+ if (remove) {
+ for (curPort = portBegin; curPort <= portEnd; curPort++) {
+ restrictedPortList.RemoveElement(curPort);
+ }
+ } else {
+ for (curPort = portBegin; curPort <= portEnd; curPort++) {
+ restrictedPortList.AppendElement(curPort);
+ }
+ }
+ }
+ } else {
+ nsresult aErrorCode;
+ int32_t port = portListArray[index].ToInteger(&aErrorCode);
+ if (NS_SUCCEEDED(aErrorCode) && port < 65536) {
+ if (remove) {
+ restrictedPortList.RemoveElement(port);
+ } else {
+ restrictedPortList.AppendElement(port);
+ }
+ }
+ }
+ }
+ }
+
+ AutoWriteLock lock(mLock);
+ mRestrictedPortList.Assign(std::move(restrictedPortList));
+}
+
+class nsWakeupNotifier : public Runnable {
+ public:
+ explicit nsWakeupNotifier(nsIIOServiceInternal* ioService)
+ : Runnable("net::nsWakeupNotifier"), mIOService(ioService) {}
+
+ NS_IMETHOD Run() override { return mIOService->NotifyWakeup(); }
+
+ private:
+ virtual ~nsWakeupNotifier() = default;
+ nsCOMPtr<nsIIOServiceInternal> mIOService;
+};
+
+NS_IMETHODIMP
+nsIOService::NotifyWakeup() {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (observerService && StaticPrefs::network_notify_changed()) {
+ (void)observerService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
+ (u"" NS_NETWORK_LINK_DATA_CHANGED));
+ }
+
+ RecheckCaptivePortal();
+
+ return NS_OK;
+}
+
+void nsIOService::SetHttpHandlerAlreadyShutingDown() {
+ if (!mShutdown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ mHttpHandlerAlreadyShutingDown = true;
+ }
+}
+
+// nsIObserver interface
+NS_IMETHODIMP
+nsIOService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (UseSocketProcess() && SocketProcessReady() &&
+ mObserverTopicForSocketProcess.Contains(nsDependentCString(topic))) {
+ nsCString topicStr(topic);
+ nsString dataStr(data);
+ Unused << mSocketProcess->GetActor()->SendNotifyObserver(topicStr, dataStr);
+ }
+
+ if (!strcmp(topic, kProfileChangeNetTeardownTopic)) {
+ if (!mHttpHandlerAlreadyShutingDown) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+ if (!mOffline) {
+ mOfflineForProfileChange = true;
+ SetOfflineInternal(true, false);
+ }
+ } else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) {
+ if (mOfflineForProfileChange) {
+ mOfflineForProfileChange = false;
+ SetOfflineInternal(false, false);
+ }
+ } else if (!strcmp(topic, kProfileDoChange)) {
+ if (data && u"startup"_ns.Equals(data)) {
+ // Lazy initialization of network link service (see bug 620472)
+ InitializeNetworkLinkService();
+ // Set up the initilization flag regardless the actuall result.
+ // If we fail here, we will fail always on.
+ mNetworkLinkServiceInitialized = true;
+
+ // And now reflect the preference setting
+ PrefsChanged(MANAGE_OFFLINE_STATUS_PREF);
+
+ // Bug 870460 - Read cookie database at an early-as-possible time
+ // off main thread. Hence, we have more chance to finish db query
+ // before something calls into the cookie service.
+ nsCOMPtr<nsISupports> cookieServ =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ }
+ } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ // Remember we passed XPCOM shutdown notification to prevent any
+ // changes of the offline status from now. We must not allow going
+ // online after this point.
+ mShutdown = true;
+
+ if (!mHttpHandlerAlreadyShutingDown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+
+ SetOfflineInternal(true, false);
+
+ if (mCaptivePortalService) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ mCaptivePortalService = nullptr;
+ }
+
+ SSLTokensCache::Shutdown();
+
+ DestroySocketProcess();
+
+ if (IsSocketProcessChild()) {
+ Preferences::UnregisterCallbacks(nsIOService::OnTLSPrefChange,
+ gCallbackSecurityPrefs, this);
+ PrepareForShutdownInSocketProcess();
+ }
+
+ // We're in XPCOM shutdown now. Unregister any dynamic protocol handlers
+ // after this point to avoid leaks.
+ {
+ AutoWriteLock lock(mLock);
+ mRuntimeProtocolHandlers.Clear();
+ }
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, NS_NETWORK_ID_CHANGED_TOPIC)) {
+ LOG(("nsIOService::OnNetworkLinkEvent Network id changed"));
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ // coming back alive from sleep
+ // this indirection brought to you by:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1152048#c19
+ nsCOMPtr<nsIRunnable> wakeupNotifier = new nsWakeupNotifier(this);
+ NS_DispatchToMainThread(wakeupNotifier);
+ }
+
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseRequestContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset, bool* aHadCharset,
+ nsACString& aContentType) {
+ net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseResponseContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset, bool* aHadCharset,
+ nsACString& aContentType) {
+ net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ProtocolHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ NS_ENSURE_ARG(uri);
+
+ *result = false;
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto handler = LookupProtocolHandler(scheme);
+
+ uint32_t protocolFlags;
+ if (flags & nsIProtocolHandler::DYNAMIC_URI_FLAGS) {
+ AssertIsOnMainThread();
+ rv = handler.DynamicProtocolFlags(uri, &protocolFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ protocolFlags = handler.StaticProtocolFlags();
+ }
+
+ *result = (protocolFlags & flags) == flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ nsresult rv = ProtocolHasFlags(uri, flags, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*result) {
+ return rv;
+ }
+
+ // Dig deeper into the chain. Note that this is not a do/while loop to
+ // avoid the extra addref/release on |uri| in the common (non-nested) case.
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> innerURI;
+ rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProtocolHasFlags(innerURI, flags, result);
+
+ if (*result) {
+ return rv;
+ }
+
+ nestedURI = do_QueryInterface(innerURI);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::SetManageOfflineStatus(bool aManage) {
+ LOG(("nsIOService::SetManageOfflineStatus aManage=%d\n", aManage));
+ mManageLinkStatus = aManage;
+
+ // When detection is not activated, the default connectivity state is true.
+ if (!mManageLinkStatus) {
+ SetConnectivityInternal(true);
+ return NS_OK;
+ }
+
+ InitializeNetworkLinkService();
+ // If the NetworkLinkService is already initialized, it does not call
+ // OnNetworkLinkEvent. This is needed, when mManageLinkStatus goes from
+ // false to true.
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetManageOfflineStatus(bool* aManage) {
+ *aManage = mManageLinkStatus;
+ return NS_OK;
+}
+
+// input argument 'data' is already UTF8'ed
+nsresult nsIOService::OnNetworkLinkEvent(const char* data) {
+ if (IsNeckoChild() || IsSocketProcessChild()) {
+ // There is nothing IO service could do on the child process
+ // with this at the moment. Feel free to add functionality
+ // here at will, though.
+ return NS_OK;
+ }
+
+ if (mShutdown) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCString dataAsString(data);
+ for (auto* cp : mozilla::dom::ContentParent::AllProcesses(
+ mozilla::dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ Unused << neckoParent->SendNetworkChangeNotification(dataAsString);
+ }
+
+ LOG(("nsIOService::OnNetworkLinkEvent data:%s\n", data));
+ if (!mNetworkLinkService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mManageLinkStatus) {
+ LOG(("nsIOService::OnNetworkLinkEvent mManageLinkStatus=false\n"));
+ return NS_OK;
+ }
+
+ bool isUp = true;
+ if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) {
+ mLastNetworkLinkChange = PR_IntervalNow();
+ // CHANGED means UP/DOWN didn't change
+ // but the status of the captive portal may have changed.
+ RecheckCaptivePortal();
+ return NS_OK;
+ }
+ if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) {
+ isUp = false;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) {
+ isUp = true;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) {
+ nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_WARNING("Unhandled network event!");
+ return NS_OK;
+ }
+
+ return SetConnectivityInternal(isUp);
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeString(const nsACString& aString, uint32_t aEscapeType,
+ nsACString& aResult) {
+ NS_ENSURE_ARG_MAX(aEscapeType, 4);
+
+ nsAutoCString stringCopy(aString);
+ nsCString result;
+
+ if (!NS_Escape(stringCopy, result, (nsEscapeMask)aEscapeType)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aResult.Assign(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeURL(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult) {
+ aResult.Truncate();
+ NS_EscapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy,
+ aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::UnescapeString(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult) {
+ aResult.Truncate();
+ NS_UnescapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy,
+ aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractCharsetFromContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset,
+ int32_t* aCharsetStart,
+ int32_t* aCharsetEnd,
+ bool* aHadCharset) {
+ nsAutoCString ignored;
+ net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
+ aCharsetStart, aCharsetEnd);
+ if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
+ *aHadCharset = false;
+ }
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+class IOServiceProxyCallback final : public nsIProtocolProxyCallback {
+ ~IOServiceProxyCallback() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ IOServiceProxyCallback(nsIInterfaceRequestor* aCallbacks,
+ nsIOService* aIOService,
+ Maybe<OriginAttributes>&& aOriginAttributes)
+ : mCallbacks(aCallbacks),
+ mIOService(aIOService),
+ mOriginAttributes(std::move(aOriginAttributes)) {}
+
+ private:
+ RefPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsIOService> mIOService;
+ Maybe<OriginAttributes> mOriginAttributes;
+};
+
+NS_IMPL_ISUPPORTS(IOServiceProxyCallback, nsIProtocolProxyCallback)
+
+NS_IMETHODIMP
+IOServiceProxyCallback::OnProxyAvailable(nsICancelable* request,
+ nsIChannel* channel, nsIProxyInfo* pi,
+ nsresult status) {
+ // Checking proxy status for speculative connect
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ // proxies dont do speculative connect
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = mIOService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsCOMPtr<nsISpeculativeConnect> speculativeHandler =
+ do_QueryInterface(handler);
+ if (!speculativeHandler) return NS_OK;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ bool anonymous = !!(loadFlags & nsIRequest::LOAD_ANONYMOUS);
+ if (mOriginAttributes) {
+ speculativeHandler->SpeculativeConnectWithOriginAttributesNative(
+ uri, std::move(mOriginAttributes.ref()), mCallbacks, anonymous);
+ } else {
+ speculativeHandler->SpeculativeConnect(uri, principal, mCallbacks,
+ anonymous);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsIOService::SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) {
+ NS_ENSURE_ARG(aURI);
+
+ if (!aURI->SchemeIs("http") && !aURI->SchemeIs("https")) {
+ // We don't speculatively connect to non-HTTP[S] URIs.
+ return NS_OK;
+ }
+
+ if (IsNeckoChild()) {
+ gNeckoChild->SendSpeculativeConnect(
+ aURI, aPrincipal, std::move(aOriginAttributes), aAnonymous);
+ return NS_OK;
+ }
+
+ // Check for proxy information. If there is a proxy configured then a
+ // speculative connect should not be performed because the potential
+ // reward is slim with tcp peers closely located to the browser.
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aPrincipal;
+
+ MOZ_ASSERT(aPrincipal || aOriginAttributes,
+ "We expect passing a principal or OriginAttributes here.");
+
+ if (!aPrincipal && !aOriginAttributes) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aOriginAttributes) {
+ loadingPrincipal =
+ BasePrincipal::CreateContentPrincipal(aURI, aOriginAttributes.ref());
+ }
+
+ // XXX Bug 1724080: Avoid TCP connections on port 80 when https-only
+ // or https-first is enabled. Let's create a dummy loadinfo which we
+ // only use to determine whether we need ot upgrade the speculative
+ // connection from http to https.
+ nsCOMPtr<nsIURI> httpsURI;
+ if (aURI->SchemeIs("http")) {
+ nsCOMPtr<nsILoadInfo> httpsOnlyCheckLoadInfo =
+ new LoadInfo(loadingPrincipal, loadingPrincipal, nullptr,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_SPECULATIVE);
+
+ // Check if https-only, or https-first would upgrade the request
+ if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, httpsOnlyCheckLoadInfo) ||
+ nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(
+ aURI, httpsOnlyCheckLoadInfo)) {
+ rv = NS_GetSecureUpgradedURI(aURI, getter_AddRefs(httpsURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ aURI = httpsURI.get();
+ }
+ }
+
+ // dummy channel used to create a TCP connection.
+ // we perform security checks on the *real* channel, responsible
+ // for any network loads. this real channel just checks the TCP
+ // pool if there is an available connection created by the
+ // channel we create underneath - hence it's safe to use
+ // the systemPrincipal as the loadingPrincipal for this channel.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NewChannelFromURI(
+ aURI,
+ nullptr, // aLoadingNode,
+ loadingPrincipal,
+ nullptr, // aTriggeringPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_SPECULATIVE, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aAnonymous) {
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ loadFlags |= nsIRequest::LOAD_ANONYMOUS;
+ channel->SetLoadFlags(loadFlags);
+ }
+
+ nsCOMPtr<nsICancelable> cancelable;
+ RefPtr<IOServiceProxyCallback> callback = new IOServiceProxyCallback(
+ aCallbacks, this, std::move(aOriginAttributes));
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ return pps2->AsyncResolve2(channel, 0, callback, nullptr,
+ getter_AddRefs(cancelable));
+ }
+ return pps->AsyncResolve(channel, 0, callback, nullptr,
+ getter_AddRefs(cancelable));
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aAnonymous) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, Nothing(), aCallbacks,
+ aAnonymous);
+}
+
+NS_IMETHODIMP nsIOService::SpeculativeConnectWithOriginAttributes(
+ nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SpeculativeConnectWithOriginAttributesNative(aURI, std::move(attrs),
+ aCallbacks, aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsIOService::SpeculativeConnectWithOriginAttributesNative(
+ nsIURI* aURI, OriginAttributes&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) {
+ Maybe<OriginAttributes> originAttributes;
+ originAttributes.emplace(aOriginAttributes);
+ Unused << SpeculativeConnectInternal(
+ aURI, nullptr, std::move(originAttributes), aCallbacks, aAnonymous);
+}
+
+NS_IMETHODIMP
+nsIOService::NotImplemented() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIOService::GetSocketProcessLaunched(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = SocketProcessReady();
+ return NS_OK;
+}
+
+bool nsIOService::HasObservers(const char* aTopic) {
+ MOZ_ASSERT(false, "Calling this method is unexpected");
+ return false;
+}
+
+NS_IMETHODIMP
+nsIOService::GetSocketProcessId(uint64_t* aPid) {
+ NS_ENSURE_ARG_POINTER(aPid);
+
+ *aPid = 0;
+ if (!mSocketProcess) {
+ return NS_OK;
+ }
+
+ if (SocketProcessParent* actor = mSocketProcess->GetActor()) {
+ *aPid = (uint64_t)actor->OtherPid();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::RegisterProtocolHandler(const nsACString& aScheme,
+ nsIProtocolHandler* aHandler,
+ uint32_t aProtocolFlags,
+ int32_t aDefaultPort) {
+ if (mShutdown) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (aScheme.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString scheme(aScheme);
+ ToLowerCase(scheme);
+
+ AutoWriteLock lock(mLock);
+ return mRuntimeProtocolHandlers.WithEntryHandle(scheme, [&](auto&& entry) {
+ if (entry) {
+ NS_WARNING("Cannot override an existing dynamic protocol handler");
+ return NS_ERROR_FACTORY_EXISTS;
+ }
+ if (xpcom::StaticProtocolHandler::Lookup(scheme)) {
+ NS_WARNING("Cannot override an existing static protocol handler");
+ return NS_ERROR_FACTORY_EXISTS;
+ }
+ nsMainThreadPtrHandle<nsIProtocolHandler> handler(
+ new nsMainThreadPtrHolder<nsIProtocolHandler>("RuntimeProtocolHandler",
+ aHandler));
+ entry.Insert(RuntimeProtocolHandler{
+ .mHandler = std::move(handler),
+ .mProtocolFlags = aProtocolFlags,
+ .mDefaultPort = aDefaultPort,
+ });
+ return NS_OK;
+ });
+}
+
+NS_IMETHODIMP
+nsIOService::UnregisterProtocolHandler(const nsACString& aScheme) {
+ if (mShutdown) {
+ return NS_OK;
+ }
+ if (aScheme.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString scheme(aScheme);
+ ToLowerCase(scheme);
+
+ AutoWriteLock lock(mLock);
+ return mRuntimeProtocolHandlers.Remove(scheme)
+ ? NS_OK
+ : NS_ERROR_FACTORY_NOT_REGISTERED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h
new file mode 100644
index 0000000000..52c8b48e7f
--- /dev/null
+++ b/netwerk/base/nsIOService.h
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIOService_h__
+#define nsIOService_h__
+
+#include "nsStringFwd.h"
+#include "nsIIOService.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsINetUtil.h"
+#include "nsIChannelEventSink.h"
+#include "nsCategoryCache.h"
+#include "nsISpeculativeConnect.h"
+#include "nsWeakReference.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/net/ProtocolHandlerInfo.h"
+#include "prtime.h"
+#include "nsICaptivePortalService.h"
+#include "nsIObserverService.h"
+#include "nsTHashSet.h"
+#include "nsWeakReference.h"
+#include "nsNetCID.h"
+
+// We don't want to expose this observer topic.
+// Intended internal use only for remoting offline/inline events.
+// See Bug 552829
+#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
+#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
+
+class nsINetworkLinkService;
+class nsIPrefBranch;
+class nsIProtocolProxyService2;
+class nsIProxyInfo;
+class nsPISocketTransportService;
+
+namespace mozilla {
+class MemoryReportingProcess;
+namespace net {
+class NeckoChild;
+class nsAsyncRedirectVerifyHelper;
+class SocketProcessHost;
+class SocketProcessMemoryReporter;
+
+class nsIOService final : public nsIIOService,
+ public nsIObserver,
+ public nsINetUtil,
+ public nsISpeculativeConnect,
+ public nsSupportsWeakReference,
+ public nsIIOServiceInternal,
+ public nsIObserverService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIOSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINETUTIL
+ NS_DECL_NSISPECULATIVECONNECT
+ NS_DECL_NSIIOSERVICEINTERNAL
+ NS_DECL_NSIOBSERVERSERVICE
+
+ // Gets the singleton instance of the IO Service, creating it as needed
+ // Returns nullptr on out of memory or failure to initialize.
+ static already_AddRefed<nsIOService> GetInstance();
+
+ nsresult Init();
+ nsresult NewURI(const char* aSpec, nsIURI* aBaseURI, nsIURI** result,
+ nsIProtocolHandler** hdlrResult);
+
+ // Called by channels before a redirect happens. This notifies the global
+ // redirect observers.
+ nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags,
+ nsAsyncRedirectVerifyHelper* helper);
+
+ bool IsOffline() { return mOffline; }
+ PRIntervalTime LastOfflineStateChange() { return mLastOfflineStateChange; }
+ PRIntervalTime LastConnectivityChange() { return mLastConnectivityChange; }
+ PRIntervalTime LastNetworkLinkChange() { return mLastNetworkLinkChange; }
+ bool IsNetTearingDown() {
+ return mShutdown || mOfflineForProfileChange ||
+ mHttpHandlerAlreadyShutingDown;
+ }
+ PRIntervalTime NetTearingDownStarted() { return mNetTearingDownStarted; }
+
+ // nsHttpHandler is going to call this function to inform nsIOService that
+ // network is in process of tearing down. Moving nsHttpConnectionMgr::Shutdown
+ // to nsIOService caused problems (bug 1242755) so we doing it in this way. As
+ // soon as nsIOService gets notification that it is shutdown it is going to
+ // reset mHttpHandlerAlreadyShutingDown.
+ void SetHttpHandlerAlreadyShutingDown();
+
+ bool IsLinkUp();
+
+ // Converts an internal URI (e.g. one that has a username and password in
+ // it) into one which we can expose to the user, for example on the URL bar.
+ static already_AddRefed<nsIURI> CreateExposableURI(nsIURI*);
+
+ // Used to count the total number of HTTP requests made
+ void IncrementRequestNumber() { mTotalRequests++; }
+ uint32_t GetTotalRequestNumber() { return mTotalRequests; }
+ // Used to keep "race cache with network" stats
+ void IncrementCacheWonRequestNumber() { mCacheWon++; }
+ uint32_t GetCacheWonRequestNumber() { return mCacheWon; }
+ void IncrementNetWonRequestNumber() { mNetWon++; }
+ uint32_t GetNetWonRequestNumber() { return mNetWon; }
+
+ // Used to trigger a recheck of the captive portal status
+ nsresult RecheckCaptivePortal();
+
+ void OnProcessLaunchComplete(SocketProcessHost* aHost, bool aSucceeded);
+ void OnProcessUnexpectedShutdown(SocketProcessHost* aHost);
+ bool SocketProcessReady();
+ static void NotifySocketProcessPrefsChanged(const char* aName, void* aSelf);
+ void NotifySocketProcessPrefsChanged(const char* aName);
+ static bool UseSocketProcess(bool aCheckAgain = false);
+
+ bool IsSocketProcessLaunchComplete();
+
+ // Call func immediately if socket process is launched completely. Otherwise,
+ // |func| will be queued and then executed in the *main thread* once socket
+ // process is launced.
+ void CallOrWaitForSocketProcess(const std::function<void()>& aFunc);
+
+ int32_t SocketProcessPid();
+ SocketProcessHost* SocketProcess() { return mSocketProcess; }
+
+ friend SocketProcessMemoryReporter;
+ RefPtr<MemoryReportingProcess> GetSocketProcessMemoryReporter();
+
+ // Lookup the ProtocolHandlerInfo based on a given scheme.
+ // Safe to call from any thread.
+ ProtocolHandlerInfo LookupProtocolHandler(const nsACString& aScheme);
+
+ static void OnTLSPrefChange(const char* aPref, void* aSelf);
+
+ nsresult LaunchSocketProcess();
+
+ static bool TooManySocketProcessCrash();
+ static void IncreaseSocketProcessCrashCount();
+
+ private:
+ // These shouldn't be called directly:
+ // - construct using GetInstance
+ // - destroy using Release
+ nsIOService();
+ ~nsIOService();
+ nsresult SetConnectivityInternal(bool aConnectivity);
+
+ nsresult OnNetworkLinkEvent(const char* data);
+
+ nsresult InitializeCaptivePortalService();
+ nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan);
+
+ // Prefs wrangling
+ static void PrefsChanged(const char* pref, void* self);
+ void PrefsChanged(const char* pref = nullptr);
+ void ParsePortList(const char* pref, bool remove);
+
+ nsresult InitializeSocketTransportService();
+ nsresult InitializeNetworkLinkService();
+ nsresult InitializeProtocolProxyService();
+
+ // consolidated helper function
+ void LookupProxyInfo(nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsCString* aScheme, nsIProxyInfo** outPI);
+
+ nsresult NewChannelFromURIWithProxyFlagsInternal(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const mozilla::Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ uint32_t aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ uint32_t aSandboxFlags, bool aSkipCheckForBrokenURLOrZeroSized,
+ nsIChannel** result);
+
+ nsresult NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result);
+
+ nsresult SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous);
+
+ void DestroySocketProcess();
+
+ nsresult SetOfflineInternal(bool offline, bool notifySocketProcess = true);
+
+ bool UsesExternalProtocolHandler(const nsACString& aScheme)
+ MOZ_REQUIRES_SHARED(mLock);
+
+ private:
+ mozilla::Atomic<bool, mozilla::Relaxed> mOffline{true};
+ mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange{false};
+ bool mManageLinkStatus{false};
+ mozilla::Atomic<bool, mozilla::Relaxed> mConnectivity{true};
+
+ // Used to handle SetOffline() reentrancy. See the comment in
+ // SetOffline() for more details.
+ bool mSettingOffline{false};
+ bool mSetOfflineValue{false};
+
+ bool mSocketProcessLaunchComplete{false};
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mShutdown{false};
+ mozilla::Atomic<bool, mozilla::Relaxed> mHttpHandlerAlreadyShutingDown{false};
+
+ nsCOMPtr<nsPISocketTransportService> mSocketTransportService;
+ nsCOMPtr<nsICaptivePortalService> mCaptivePortalService;
+ nsCOMPtr<nsINetworkLinkService> mNetworkLinkService;
+ bool mNetworkLinkServiceInitialized{false};
+
+ // cached categories
+ nsCategoryCache<nsIChannelEventSink> mChannelEventSinks{
+ NS_CHANNEL_EVENT_SINK_CATEGORY};
+
+ RWLock mLock{"nsIOService::mLock"};
+ nsTArray<int32_t> mRestrictedPortList MOZ_GUARDED_BY(mLock);
+ nsTArray<nsCString> mForceExternalSchemes MOZ_GUARDED_BY(mLock);
+ nsTHashMap<nsCString, RuntimeProtocolHandler> mRuntimeProtocolHandlers
+ MOZ_GUARDED_BY(mLock);
+
+ uint32_t mTotalRequests{0};
+ uint32_t mCacheWon{0};
+ uint32_t mNetWon{0};
+ static uint32_t sSocketProcessCrashedCount;
+
+ // These timestamps are needed for collecting telemetry on PR_Connect,
+ // PR_ConnectContinue and PR_Close blocking time. If we spend very long
+ // time in any of these functions we want to know if and what network
+ // change has happened shortly before.
+ mozilla::Atomic<PRIntervalTime> mLastOfflineStateChange;
+ mozilla::Atomic<PRIntervalTime> mLastConnectivityChange;
+ mozilla::Atomic<PRIntervalTime> mLastNetworkLinkChange;
+
+ // Time a network tearing down started.
+ mozilla::Atomic<PRIntervalTime> mNetTearingDownStarted{0};
+
+ SocketProcessHost* mSocketProcess{nullptr};
+
+ // Events should be executed after the socket process is launched. Will
+ // dispatch these events while socket process fires OnProcessLaunchComplete.
+ // Note: this array is accessed only on the main thread.
+ nsTArray<std::function<void()>> mPendingEvents;
+
+ // The observer notifications need to be forwarded to socket process.
+ nsTHashSet<nsCString> mObserverTopicForSocketProcess;
+ // Some noticications (e.g., NS_XPCOM_SHUTDOWN_OBSERVER_ID) are triggered in
+ // socket process, so we should not send the notifications again.
+ nsTHashSet<nsCString> mSocketProcessTopicBlockedList;
+ // Used to store the topics that are already observed by IOService.
+ nsTHashSet<nsCString> mIOServiceTopicList;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+
+ public:
+ // Used for all default buffer sizes that necko allocates.
+ static uint32_t gDefaultSegmentSize;
+ static uint32_t gDefaultSegmentCount;
+};
+
+/**
+ * Reference to the IO service singleton. May be null.
+ */
+extern nsIOService* gIOService;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsIOService_h__
diff --git a/netwerk/base/nsIParentChannel.idl b/netwerk/base/nsIParentChannel.idl
new file mode 100644
index 0000000000..a351019d7d
--- /dev/null
+++ b/netwerk/base/nsIParentChannel.idl
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+#include "nsIHttpChannel.idl"
+
+interface nsIRemoteTab;
+
+%{C++
+namespace mozilla {
+namespace net {
+class ParentChannelListener;
+}
+}
+%}
+
+[ptr] native ParentChannelListener(mozilla::net::ParentChannelListener);
+
+/**
+ * Implemented by chrome side of IPC protocols.
+ */
+
+[scriptable, uuid(e0fc4801-6030-4653-a59f-1fb282bd1a04)]
+interface nsIParentChannel : nsIStreamListener
+{
+ /**
+ * Called to set the ParentChannelListener object (optional).
+ */
+ [noscript] void setParentListener(in ParentChannelListener listener);
+
+ /**
+ * Called to set matched information when URL matches SafeBrowsing list.
+ * @param aList
+ * Name of the list that matched
+ * @param aProvider
+ * Name of provider that matched
+ * @param aFullHash
+ * String represents full hash that matched
+ */
+ [noscript] void setClassifierMatchedInfo(in ACString aList,
+ in ACString aProvider,
+ in ACString aFullHash);
+
+ /**
+ * Called to set matched tracking information when URL matches tracking annotation list.
+ * @param aList
+ * Comma-separated list of tables that matched
+ * @param aFullHashes
+ * Comma-separated list of base64 encoded full hashes that matched
+ */
+ [noscript] void setClassifierMatchedTrackingInfo(in ACString aLists,
+ in ACString aFullHashes);
+
+ /**
+ * Called to notify the HttpChannelChild that the resource being loaded
+ * has been classified.
+ * @param aClassificationFlags
+ * What classifier identifies this channel.
+ * @param aIsThirdParty
+ * Whether or not the resourced is considered first-party
+ * with the URI of the window.
+ */
+ [noscript] void notifyClassificationFlags(in uint32_t aClassificationFlags,
+ in bool aIsThirdParty);
+
+ /**
+ * Called to invoke deletion of the IPC protocol.
+ */
+ void delete();
+
+ /**
+ * The remote type of the target process for this load.
+ */
+ readonly attribute AUTF8String remoteType;
+};
diff --git a/netwerk/base/nsIParentRedirectingChannel.idl b/netwerk/base/nsIParentRedirectingChannel.idl
new file mode 100644
index 0000000000..6302775c51
--- /dev/null
+++ b/netwerk/base/nsIParentRedirectingChannel.idl
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIParentChannel.idl"
+
+interface nsIRemoteTab;
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+[uuid(01987690-48cf-45de-bae3-e143c2adc2a8)]
+interface nsIAsyncVerifyRedirectReadyCallback : nsISupports
+{
+ /**
+ * Asynchronous callback when redirected channel finishes the preparation for
+ * completing the verification procedure.
+ *
+ * @param result
+ * SUCCEEDED if preparation for redirection verification succceed.
+ * If FAILED the redirection must be aborted.
+ */
+ void readyToVerify(in nsresult result);
+};
+
+/**
+ * Implemented by chrome side of IPC protocols that support redirect responses.
+ */
+
+[scriptable, uuid(3ed1d288-5324-46ee-8a98-33ac37d1080b)]
+interface nsIParentRedirectingChannel : nsIParentChannel
+{
+ /**
+ * Called when the channel got a response that redirects it to a different
+ * URI. The implementation is responsible for calling the redirect observers
+ * on the child process and provide the decision result to the callback.
+ *
+ * @param newURI
+ * the URI we redirect to
+ * @param callback
+ * redirect result callback, usage is compatible with how
+ * nsIChannelEventSink defines it
+ */
+ void startRedirect(in nsIChannel newChannel,
+ in uint32_t redirectFlags,
+ in nsIAsyncVerifyRedirectCallback callback);
+
+ /**
+ * Called to new channel when the original channel got Redirect2Verify
+ * response from child. Callback will be invoked when the new channel
+ * finishes the preparation for Redirect2Verify and can be called immediately.
+ *
+ * @param callback
+ * redirect ready callback, will be called when redirect verification
+ * procedure can proceed.
+ */
+ void continueVerification(in nsIAsyncVerifyRedirectReadyCallback callback);
+
+ /**
+ * Called after we are done with redirecting process and we know if to
+ * redirect or not. Forward the redirect result to the child process. From
+ * that moment the nsIParentChannel implementation expects it will be
+ * forwarded all notifications from the 'real' channel.
+ *
+ * Primarilly used by HttpChannelParent::OnRedirectResult and kept as
+ * mActiveChannel and mRedirectChannel in that class.
+ */
+ void completeRedirect(in nsresult succeeded);
+};
diff --git a/netwerk/base/nsIPermission.idl b/netwerk/base/nsIPermission.idl
new file mode 100644
index 0000000000..029f0d8395
--- /dev/null
+++ b/netwerk/base/nsIPermission.idl
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * This interface defines a "permission" object,
+ * used to specify allowed/blocked objects from
+ * user-specified sites (cookies, images etc).
+ */
+[scriptable, uuid(bb409a51-2371-4fea-9dc9-b7286a458b8c)]
+interface nsIPermission : nsISupports
+{
+ /**
+ * The principal for which this permission applies.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * a case-sensitive ASCII string, indicating the type of permission
+ * (e.g., "cookie", "image", etc).
+ * This string is specified by the consumer when adding a permission
+ * via nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute ACString type;
+
+ /**
+ * The permission (see nsIPermissionManager.idl for allowed values)
+ */
+ readonly attribute uint32_t capability;
+
+ /**
+ * The expiration type of the permission (session, time-based or none).
+ * Constants are EXPIRE_*, defined in nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute uint32_t expireType;
+
+ /**
+ * The expiration time of the permission (milliseconds since Jan 1 1970
+ * 0:00:00).
+ */
+ readonly attribute int64_t expireTime;
+
+ /**
+ * The last modification time of the permission (milliseconds since Jan 1 1970
+ * 0:00:00).
+ */
+ readonly attribute int64_t modificationTime;
+
+ /**
+ * Test whether a principal would be affected by this permission.
+ *
+ * @param principal the principal to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matches(in nsIPrincipal principal,
+ in boolean exactHost);
+
+ /**
+ * Test whether a URI would be affected by this permission.
+ * NOTE: This performs matches with default origin attribute values.
+ *
+ * @param uri the uri to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matchesURI(in nsIURI uri,
+ in boolean exactHost);
+};
diff --git a/netwerk/base/nsIPermissionManager.idl b/netwerk/base/nsIPermissionManager.idl
new file mode 100644
index 0000000000..f234958010
--- /dev/null
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains an interface to the Permission Manager,
+ * used to persistenly store permissions for different object types (cookies,
+ * images etc) on a site-by-site basis.
+ *
+ * This service broadcasts the following notification when the permission list
+ * is changed:
+ *
+ * topic : "perm-changed" (PERM_CHANGE_NOTIFICATION)
+ * broadcast whenever the permission list changes in some way. there
+ * are four possible data strings for this notification; one
+ * notification will be broadcast for each change, and will involve
+ * a single permission.
+ * subject: an nsIPermission interface pointer representing the permission object
+ * that changed.
+ * data : "deleted"
+ * a permission was deleted. the subject is the deleted permission.
+ * "added"
+ * a permission was added. the subject is the added permission.
+ * "changed"
+ * a permission was changed. the subject is the new permission.
+ * "cleared"
+ * the entire permission list was cleared. the subject is null.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIPermission;
+
+[scriptable, builtinclass, uuid(4dcb3851-eba2-4e42-b236-82d2596fca22)]
+interface nsIPermissionManager : nsISupports
+{
+ /**
+ * Predefined return values for the testPermission method and for
+ * the permission param of the add method
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ */
+ const uint32_t UNKNOWN_ACTION = 0;
+ const uint32_t ALLOW_ACTION = 1;
+ const uint32_t DENY_ACTION = 2;
+ const uint32_t PROMPT_ACTION = 3;
+
+ /**
+ * Predefined expiration types for permissions. Permissions can be permanent
+ * (never expire), expire at the end of the session, or expire at a specified
+ * time. Permissions that expire at the end of a session may also have a
+ * specified expiration time.
+ *
+ * EXPIRE_POLICY is a special expiration status. It is set when the permission
+ * is set by reading an enterprise policy. These permissions cannot be overridden.
+ */
+ const uint32_t EXPIRE_NEVER = 0;
+ const uint32_t EXPIRE_SESSION = 1;
+ const uint32_t EXPIRE_TIME = 2;
+ const uint32_t EXPIRE_POLICY = 3;
+
+
+ /**
+ * Get all custom permissions for a given nsIPrincipal. This will return an
+ * enumerator of all permissions which are not set to default and which
+ * belong to the matching principal of the given nsIPrincipal.
+ *
+ * @param principal the URI to get all permissions for
+ */
+ Array<nsIPermission> getAllForPrincipal(in nsIPrincipal principal);
+
+ /**
+ * Get all custom permissions of a specific type, specified with a prefix
+ * string. This will return an array of all permissions which are not set to
+ * default. Also the passed type argument is either equal to or a prefix of
+ * the type of the returned permissions.
+ *
+ * @param prefix the type prefix string
+ */
+ Array<nsIPermission> getAllWithTypePrefix(in ACString prefix);
+
+
+ /**
+ * Get all custom permissions whose type exactly match one of the types defined
+ * in the passed array argument.
+ * This will return an array of all permissions which are not set to default.
+ *
+ * @param types an array of case-sensitive ASCII strings, identifying the
+ * permissions to be matched.
+ */
+ Array<nsIPermission> getAllByTypes(in Array<ACString> types);
+
+ /**
+ * Get all custom permissions of a specific type and that were modified after
+ * the specified date. This will return an array of all permissions which are
+ * not set to default.
+ *
+ * @param type a case-sensitive ASCII string, identifying the permission.
+ * @param since a unix timestamp representing the number of milliseconds from
+ * Jan 1, 1970 00:00:00 UTC.
+ */
+ Array<nsIPermission> getAllByTypeSince(in ACString type, in int64_t since);
+
+ /**
+ * Add permission information for a given principal.
+ * It is internally calling the other add() method using the nsIURI from the
+ * principal.
+ * Passing a system principal will be a no-op because they will always be
+ * granted permissions.
+ */
+ void addFromPrincipal(in nsIPrincipal principal, in ACString type,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Add permanent permission information for a given principal in private
+ * browsing.
+ *
+ * Normally permissions in private browsing are cleared at the end of the
+ * session, this method allows you to override this behavior and set
+ * permanent permissions.
+ *
+ * WARNING: setting permanent permissions _will_ leak data in private
+ * browsing. Only use if you understand the consequences and trade-offs. If
+ * you are unsure, |addFromPrincipal| is very likely what you want to use
+ * instead.
+ */
+ void addFromPrincipalAndPersistInPrivateBrowsing(in nsIPrincipal principal,
+ in ACString type,
+ in uint32_t permission);
+
+ /**
+ * Remove permission information for a given principal.
+ * This is internally calling remove() with the host from the principal's URI.
+ * Passing system principal will be a no-op because we never add them to the
+ * database.
+ */
+ void removeFromPrincipal(in nsIPrincipal principal, in ACString type);
+
+ /**
+ * Remove the given permission from the permission manager.
+ *
+ * @param perm a permission obtained from the permission manager.
+ */
+ void removePermission(in nsIPermission perm);
+
+ /**
+ * Clear permission information for all websites.
+ */
+ void removeAll();
+
+ /**
+ * Clear all permission information added since the specified time.
+ */
+ void removeAllSince(in int64_t since);
+
+ /**
+ * Clear all permissions of the passed type.
+ */
+ void removeByType(in ACString type);
+
+ /**
+ * Clear all permissions of the passed type added since the specified time.
+ * @param type a case-sensitive ASCII string, identifying the permission.
+ * @param since a unix timestamp representing the number of milliseconds from
+ * Jan 1, 1970 00:00:00 UTC.
+ */
+ void removeByTypeSince(in ACString type, in int64_t since);
+
+ /**
+ * Test whether the principal has the permission to perform a given action.
+ * System principals will always have permissions granted.
+ * This function will perform a pref lookup to permissions.default.<type>
+ * if the specific permission type is part of the whitelist for that functionality.
+ */
+ uint32_t testPermissionFromPrincipal(in nsIPrincipal principal,
+ in ACString type);
+
+ /**
+ * Test whether the principal has the permission to perform a given action.
+ * This requires an exact hostname match. Subdomain principals do not match
+ * permissions of base domains.
+ * System principals will always have permissions granted.
+ * This function will perform a pref lookup to permissions.default.<type>
+ * if the specific permission type is part of the whitelist for that functionality.
+ */
+ uint32_t testExactPermissionFromPrincipal(in nsIPrincipal principal,
+ in ACString type);
+
+ /**
+ * Test whether a website has permission to perform the given action
+ * ignoring active sessions.
+ * System principals will always have permissions granted.
+ * This function will perform a pref lookup to permissions.default.<type>
+ * if the specific permission type is part of the whitelist for that functionality.
+ *
+ * @param principal the principal
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermanentPermission(in nsIPrincipal principal,
+ in ACString type);
+
+ /**
+ * Get the permission object associated with the given principal and action.
+ * @param principal The principal
+ * @param type A case-sensitive ASCII string identifying the consumer
+ * @param exactHost If true, only the specific host will be matched.
+ * If false, base domains of the principal will also
+ * be searched.
+ * @returns The matching permission object, or null if no matching object
+ * was found. No matching object is equivalent to UNKNOWN_ACTION.
+ * @note Clients in general should prefer the test* methods unless they
+ * need to know the specific stored details.
+ * @note This method will always return null for the system principal.
+ */
+ nsIPermission getPermissionObject(in nsIPrincipal principal,
+ in ACString type,
+ in boolean exactHost);
+
+ /**
+ * Returns all stored permissions.
+ * @return an array of nsIPermission objects
+ */
+ readonly attribute Array<nsIPermission> all;
+
+ /**
+ * Remove all permissions that will match the origin pattern.
+ */
+ void removePermissionsWithAttributes(in AString patternAsJSON);
+};
+
+%{ C++
+#define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1"
+
+#define PERM_CHANGE_NOTIFICATION "perm-changed"
+%}
diff --git a/netwerk/base/nsIPrivateBrowsingChannel.idl b/netwerk/base/nsIPrivateBrowsingChannel.idl
new file mode 100644
index 0000000000..3bc822f018
--- /dev/null
+++ b/netwerk/base/nsIPrivateBrowsingChannel.idl
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is implemented by channels which support overriding the
+ * privacy state of the channel.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(df702bb0-55b8-11e2-bcfd-0800200c9a66)]
+interface nsIPrivateBrowsingChannel : nsISupports
+{
+ /**
+ * Determine whether the channel is tied to a private browsing window.
+ *
+ * This value can be set only before the channel is opened. Setting it
+ * after that does not have any effect. This value overrides the privacy
+ * state of the channel, which means that if you call this method, then
+ * the loadGroup and load context will no longer be consulted when we
+ * need to know the private mode status for a channel.
+ *
+ * Note that this value is only meant to be used when the channel's privacy
+ * status cannot be obtained from the loadGroup or load context (for
+ * example, when the channel is not associated with any loadGroup or load
+ * context.) Setting this value directly should be avoided if possible.
+ *
+ * Implementations must enforce the ordering semantics of this function by
+ * raising errors if setPrivate is called on a channel which has a loadGroup
+ * and/or callbacks that implement nsILoadContext, or if the loadGroup
+ * or notificationCallbacks are set after setPrivate has been called.
+ *
+ * @param aPrivate whether the channel should be opened in private mode.
+ */
+ void setPrivate(in boolean aPrivate);
+
+ /**
+ * States whether the channel is in private browsing mode. This may either
+ * happen because the channel is opened from a private mode context or
+ * when the mode is explicitly set with ::setPrivate().
+ *
+ * This attribute is equivalent to NS_UsePrivateBrowsing(), but scriptable.
+ */
+ readonly attribute boolean isChannelPrivate;
+
+ /*
+ * This function is used to determine whether the channel's private mode
+ * has been overridden by a call to setPrivate. It is intended to be used
+ * by NS_UsePrivateBrowsing(), and you should not call it directly.
+ *
+ * @param aValue the overridden value. This will only be set if the function
+ * returns true.
+ */
+ [noscript] boolean isPrivateModeOverriden(out boolean aValue);
+};
diff --git a/netwerk/base/nsIProgressEventSink.idl b/netwerk/base/nsIProgressEventSink.idl
new file mode 100644
index 0000000000..5420239e4c
--- /dev/null
+++ b/netwerk/base/nsIProgressEventSink.idl
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIRequest;
+
+/**
+ * nsIProgressEventSink
+ *
+ * This interface is used to asynchronously convey channel status and progress
+ * information that is generally not critical to the processing of the channel.
+ * The information is intended to be displayed to the user in some meaningful
+ * way.
+ *
+ * An implementation of this interface can be passed to a channel via the
+ * channel's notificationCallbacks attribute. See nsIChannel for more info.
+ *
+ * The channel will begin passing notifications to the progress event sink
+ * after its asyncOpen method has been called. Notifications will cease once
+ * the channel calls its listener's onStopRequest method or once the channel
+ * is canceled (via nsIRequest::cancel).
+ *
+ * NOTE: This interface is actually not specific to channels and may be used
+ * with other implementations of nsIRequest.
+ */
+[scriptable, uuid(87d55fba-cb7e-4f38-84c1-5c6c2b2a55e9)]
+interface nsIProgressEventSink : nsISupports
+{
+ /**
+ * Called to notify the event sink that progress has occurred for the
+ * given request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aProgress
+ * numeric value in the range 0 to aProgressMax indicating the
+ * number of bytes transfered thus far.
+ * @param aProgressMax
+ * numeric value indicating maximum number of bytes that will be
+ * transfered (or -1 if total is unknown).
+ */
+ void onProgress(in nsIRequest aRequest,
+ in long long aProgress,
+ in long long aProgressMax);
+
+ /**
+ * Called to notify the event sink with a status message for the given
+ * request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aStatus
+ * status code (not necessarily an error code) indicating the
+ * state of the channel (usually the state of the underlying
+ * transport). see nsISocketTransport for socket specific status
+ * codes.
+ * @param aStatusArg
+ * status code argument to be used with the string bundle service
+ * to convert the status message into localized, human readable
+ * text. the meaning of this parameter is specific to the value
+ * of the status code. for socket status codes, this parameter
+ * indicates the host:port associated with the status code.
+ */
+ void onStatus(in nsIRequest aRequest,
+ in nsresult aStatus,
+ in wstring aStatusArg);
+
+};
diff --git a/netwerk/base/nsIPrompt.idl b/netwerk/base/nsIPrompt.idl
new file mode 100644
index 0000000000..6fbed3355d
--- /dev/null
+++ b/netwerk/base/nsIPrompt.idl
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is the prompt interface which can be used without knowlege of a
+ * parent window. The parentage is hidden by the GetInterface though
+ * which it is gotten. This interface is identical to nsIPromptService
+ * but without the parent nsIDOMWindow parameter. See nsIPromptService
+ * for all documentation.
+ *
+ * Accesskeys can be attached to buttons and checkboxes by inserting
+ * an & before the accesskey character. For a real &, use && instead.
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a63f70c0-148b-11d3-9333-00104ba0fd40)]
+interface nsIPrompt : nsISupports
+{
+ void alert(in wstring dialogTitle,
+ in wstring text);
+
+ void alertCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean confirm(in wstring dialogTitle,
+ in wstring text);
+
+ boolean confirmCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ const unsigned long BUTTON_POS_0 = 1;
+ const unsigned long BUTTON_POS_1 = 1 << 8;
+ const unsigned long BUTTON_POS_2 = 1 << 16;
+
+ const unsigned long BUTTON_TITLE_OK = 1;
+ const unsigned long BUTTON_TITLE_CANCEL = 2;
+ const unsigned long BUTTON_TITLE_YES = 3;
+ const unsigned long BUTTON_TITLE_NO = 4;
+ const unsigned long BUTTON_TITLE_SAVE = 5;
+ const unsigned long BUTTON_TITLE_DONT_SAVE = 6;
+ const unsigned long BUTTON_TITLE_REVERT = 7;
+
+ const unsigned long BUTTON_TITLE_IS_STRING = 127;
+
+ const unsigned long BUTTON_POS_0_DEFAULT = 0 << 24;
+ const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24;
+ const unsigned long BUTTON_POS_2_DEFAULT = 2 << 24;
+
+ /* used for security dialogs, buttons are initially disabled */
+ const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
+
+ const unsigned long SHOW_SPINNER = 1 << 27;
+
+ const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) +
+ (BUTTON_TITLE_CANCEL * BUTTON_POS_1);
+ const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
+ (BUTTON_TITLE_NO * BUTTON_POS_1);
+
+
+ // Indicates whether a prompt should be shown in-content, on tab level or as a separate window
+ const unsigned long MODAL_TYPE_CONTENT = 1;
+ const unsigned long MODAL_TYPE_TAB = 2;
+ const unsigned long MODAL_TYPE_WINDOW = 3;
+ // Like MODAL_TYPE_WINDOW, but shown inside a parent window (with similar
+ // styles as _TAB and _CONTENT types) rather than as a new window:
+ const unsigned long MODAL_TYPE_INTERNAL_WINDOW = 4;
+
+ int32_t confirmEx(in wstring dialogTitle,
+ in wstring text,
+ in unsigned long buttonFlags,
+ in wstring button0Title,
+ in wstring button1Title,
+ in wstring button2Title,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ inout wstring value,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring password);
+
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring username,
+ inout wstring password);
+
+ boolean select(in wstring dialogTitle,
+ in wstring text,
+ in Array<AString> selectList,
+ out long outSelection);
+};
diff --git a/netwerk/base/nsIProtocolHandler.idl b/netwerk/base/nsIProtocolHandler.idl
new file mode 100644
index 0000000000..890f3ef02f
--- /dev/null
+++ b/netwerk/base/nsIProtocolHandler.idl
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsCOMPtr.h"
+
+/**
+ * Protocol handlers are registered with XPCOM under the following CONTRACTID prefix:
+ */
+#define NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "@mozilla.org/network/protocol;1?name="
+/**
+ * For example, "@mozilla.org/network/protocol;1?name=http"
+ */
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+#define IS_ORIGIN_IS_FULL_SPEC_DEFINED 1
+#endif
+%}
+
+interface nsIURI;
+interface nsIChannel;
+interface nsILoadInfo;
+
+/**
+ * nsIProtocolHandlerWithDynamicFlags
+ *
+ * Protocols that wish to return different flags depending on the URI should
+ * implement this interface.
+ */
+[scriptable, builtinclass, uuid(65a8e823-0591-4fc0-a56a-03265e0a4ce8)]
+interface nsIProtocolHandlerWithDynamicFlags : nsISupports
+{
+ /*
+ * Returns protocol flags for the given URI, which may be different from the
+ * flags for another URI of the same scheme.
+ *
+ * Only DYNAMIC_URI_FLAGS may be different from the registered flags for the
+ * protocol handler.
+ */
+ unsigned long getFlagsForURI(in nsIURI aURI);
+};
+
+/**
+ * nsIProtocolHandler
+ */
+[scriptable, uuid(a87210e6-7c8c-41f7-864d-df809015193e)]
+interface nsIProtocolHandler : nsISupports
+{
+ /**
+ * The scheme of this protocol (e.g., "file").
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * Constructs a new channel from the given URI for this protocol handler and
+ * sets the loadInfo for the constructed channel.
+ */
+ nsIChannel newChannel(in nsIURI aURI, in nsILoadInfo aLoadinfo);
+
+ /**
+ * Allows a protocol to override blacklisted ports.
+ *
+ * This method will be called when there is an attempt to connect to a port
+ * that is blacklisted. For example, for most protocols, port 25 (Simple Mail
+ * Transfer) is banned. When a URI containing this "known-to-do-bad-things"
+ * port number is encountered, this function will be called to ask if the
+ * protocol handler wants to override the ban.
+ */
+ boolean allowPort(in long port, in string scheme);
+
+
+ /**************************************************************************
+ * Constants for the protocol flags (the first is the default mask, the
+ * others are deviations):
+ *
+ * NOTE: Protocol flags are provided when the protocol handler is
+ * registered, either through a static component or dynamically with
+ * `nsIIOService.registerProtocolHandler`.
+ *
+ * NOTE: Implementation must ignore any flags they do not understand.
+ */
+
+ /**
+ * standard full URI with authority component and concept of relative
+ * URIs (http, ...)
+ */
+ const unsigned long URI_STD = 0;
+
+ /**
+ * no concept of relative URIs (about, javascript, finger, ...)
+ */
+ const unsigned long URI_NORELATIVE = (1<<0);
+
+ /**
+ * no authority component (file, ...)
+ */
+ const unsigned long URI_NOAUTH = (1<<1);
+
+ /**
+ * This protocol handler can be proxied via a proxy (socks or http)
+ * (e.g., irc, smtp, http, etc.). If the protocol supports transparent
+ * proxying, the handler should implement nsIProxiedProtocolHandler.
+ *
+ * If it supports only HTTP proxying, then it need not support
+ * nsIProxiedProtocolHandler, but should instead set the ALLOWS_PROXY_HTTP
+ * flag (see below).
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+ const unsigned long ALLOWS_PROXY = (1<<2);
+
+ /**
+ * This protocol handler can be proxied using a http proxy (e.g., http,
+ * etc.). nsIIOService::newChannelFromURI will feed URIs from this
+ * protocol handler to the HTTP protocol handler instead. This flag is
+ * ignored if ALLOWS_PROXY is not set.
+ */
+ const unsigned long ALLOWS_PROXY_HTTP = (1<<3);
+
+ /**
+ * The URIs for this protocol have no inherent security context, so
+ * documents loaded via this protocol should inherit the security context
+ * from the document that loads them.
+ */
+ const unsigned long URI_INHERITS_SECURITY_CONTEXT = (1<<4);
+
+ /**
+ * "Automatic" loads that would replace the document (e.g. <meta> refresh,
+ * certain types of XLinks, possibly other loads that the application
+ * decides are not user triggered) are not allowed if the originating (NOT
+ * the target) URI has this protocol flag. Note that the decision as to
+ * what constitutes an "automatic" load is made externally, by the caller
+ * of nsIScriptSecurityManager::CheckLoadURI. See documentation for that
+ * method for more information.
+ *
+ * A typical protocol that might want to set this flag is a protocol that
+ * shows highly untrusted content in a viewing area that the user expects
+ * to have a lot of control over, such as an e-mail reader.
+ */
+ const unsigned long URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT = (1<<5);
+
+ /**
+ * +-------------------------------------------------------------------+
+ * | |
+ * | ALL PROTOCOL HANDLERS MUST SET ONE OF THE FOLLOWING FIVE FLAGS. |
+ * | |
+ * +-------------------------------------------------------------------+
+ *
+ * * URI_LOADABLE_BY_ANYONE
+ * * URI_DANGEROUS_TO_LOAD
+ * * URI_IS_UI_RESOURCE
+ * * URI_IS_LOCAL_FILE
+ * * URI_LOADABLE_BY_SUBSUMERS
+ *
+ * These flags are used to determine who is allowed to load URIs for this
+ * protocol. Note that if a URI is nested, only the flags for the
+ * innermost URI matter. See nsINestedURI.
+ *
+ * If none of these five flags are set, the ContentSecurityManager will
+ * deny the load.
+ */
+
+ /**
+ * The URIs for this protocol can be loaded by anyone. For example, any
+ * website should be allowed to trigger a load of a URI for this protocol.
+ * Web-safe protocols like "http" should set this flag.
+ */
+ const unsigned long URI_LOADABLE_BY_ANYONE = (1<<6);
+
+ /**
+ * The URIs for this protocol are UNSAFE if loaded by untrusted (web)
+ * content and may only be loaded by privileged code (for example, code
+ * which has the system principal). Various internal protocols should set
+ * this flag.
+ */
+ const unsigned long URI_DANGEROUS_TO_LOAD = (1<<7);
+
+ /**
+ * The URIs for this protocol point to resources that are part of the
+ * application's user interface. There are cases when such resources may
+ * be made accessible to untrusted content such as web pages, so this is
+ * less restrictive than URI_DANGEROUS_TO_LOAD but more restrictive than
+ * URI_LOADABLE_BY_ANYONE. See the documentation for
+ * nsIScriptSecurityManager::CheckLoadURI.
+ */
+ const unsigned long URI_IS_UI_RESOURCE = (1<<8);
+
+ /**
+ * Loading of URIs for this protocol from other origins should only be
+ * allowed if those origins should have access to the local filesystem.
+ * It's up to the application to decide what origins should have such
+ * access. Protocols like "file" that point to local data should set this
+ * flag.
+ */
+ const unsigned long URI_IS_LOCAL_FILE = (1<<9);
+
+ /**
+ * The URIs for this protocol can be loaded only by callers with a
+ * principal that subsumes this uri. For example, privileged code and
+ * websites that are same origin as this uri.
+ */
+ const unsigned long URI_LOADABLE_BY_SUBSUMERS = (1<<10);
+
+ /**
+ * Channels using this protocol never call OnDataAvailable
+ * on the listener passed to AsyncOpen and they therefore
+ * do not return any data that we can use.
+ */
+ const unsigned long URI_DOES_NOT_RETURN_DATA = (1<<11);
+
+ /**
+ * URIs for this protocol are considered to be local resources. This could
+ * be a local file (URI_IS_LOCAL_FILE), a UI resource (URI_IS_UI_RESOURCE),
+ * or something else that would not hit the network.
+ */
+ const unsigned long URI_IS_LOCAL_RESOURCE = (1<<12);
+
+ /**
+ * URIs for this protocol execute script when they are opened.
+ */
+ const unsigned long URI_OPENING_EXECUTES_SCRIPT = (1<<13);
+
+ /**
+ * Loading channels from this protocol has side-effects that make
+ * it unsuitable for saving to a local file.
+ */
+ const unsigned long URI_NON_PERSISTABLE = (1<<14);
+
+ /**
+ * URIs for this protocol require the webapps permission on the principal
+ * when opening URIs for a different domain. See bug#773886
+ */
+ const unsigned long URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM = (1<<15);
+
+ /**
+ * Channels for this protocol don't need to spin the event loop to handle
+ * Open() and reads on the resulting stream.
+ */
+ const unsigned long URI_SYNC_LOAD_IS_OK = (1<<16);
+
+ /**
+ * All the origins whose URI has this scheme are considered potentially
+ * trustworthy.
+ * Per the SecureContext spec, https: and wss: should be considered
+ * a priori secure, and implementations may consider other,
+ * implementation-specific URI schemes as secure.
+ */
+ const unsigned long URI_IS_POTENTIALLY_TRUSTWORTHY = (1<<17);
+
+ /**
+ * This URI may be fetched and the contents are visible to anyone. This is
+ * semantically equivalent to the resource being served with all-access CORS
+ * headers. This is only used in MV2 Extensions and should not otherwise
+ * be used.
+ */
+ const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 18);
+
+ /**
+ * If this flag is set, then the origin for this protocol is the full URI
+ * spec, not just the scheme + host + port.
+ *
+ * Note: this is not supported in Firefox. It is currently only available
+ * in Thunderbird and SeaMonkey.
+ */
+ const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 19);
+
+ /**
+ * If this flag is set, the URI does not always allow content using the same
+ * protocol to link to it.
+ */
+ const unsigned long URI_SCHEME_NOT_SELF_LINKABLE = (1 << 20);
+
+ /**
+ * The URIs for this protocol can be loaded by extensions.
+ */
+ const unsigned long URI_LOADABLE_BY_EXTENSIONS = (1 << 21);
+
+ /**
+ * The URIs for this protocol can not be loaded into private contexts.
+ */
+ const unsigned long URI_DISALLOW_IN_PRIVATE_CONTEXT = (1 << 22);
+
+ /**
+ * This protocol handler forbids accessing cookies e.g. for mail related
+ * protocols. Only used in Mailnews (comm-central).
+ */
+ const unsigned long URI_FORBIDS_COOKIE_ACCESS = (1 << 23);
+
+ /**
+ * This is an extension web accessible uri that is loadable if checked
+ * against an allowlist using ExtensionPolicyService::SourceMayLoadExtensionURI.
+ */
+ const unsigned long WEBEXT_URI_WEB_ACCESSIBLE = (1 << 24);
+
+ /**
+ * This URI has a webexposed origin, meaning the URI has a non-null origin
+ * See https://url.spec.whatwg.org/#origin
+ */
+ const unsigned long URI_HAS_WEB_EXPOSED_ORIGIN = (1 << 25);
+
+ /**
+ * Flags which are allowed to be different from the static flags when
+ * returned from `nsIProtocolHandlerWithDynamicFlags::getFlagsForURI`.
+ *
+ * All other flags must match the flags provided when the protocol handler
+ * was registered.
+ */
+ const unsigned long DYNAMIC_URI_FLAGS =
+ URI_LOADABLE_BY_ANYONE | URI_DANGEROUS_TO_LOAD |
+ URI_IS_POTENTIALLY_TRUSTWORTHY | URI_FETCHABLE_BY_ANYONE |
+ URI_LOADABLE_BY_EXTENSIONS | URI_DISALLOW_IN_PRIVATE_CONTEXT |
+ WEBEXT_URI_WEB_ACCESSIBLE | URI_HAS_WEB_EXPOSED_ORIGIN;
+};
diff --git a/netwerk/base/nsIProtocolProxyCallback.idl b/netwerk/base/nsIProtocolProxyCallback.idl
new file mode 100644
index 0000000000..96c2181eca
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyCallback.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIProxyInfo;
+interface nsICancelable;
+
+/**
+ * This interface serves as a closure for nsIProtocolProxyService's
+ * asyncResolve method.
+ */
+[scriptable, uuid(fbb6eff6-0cc2-4d99-8d6f-0a12b462bdeb)]
+interface nsIProtocolProxyCallback : nsISupports
+{
+ /**
+ * This method is called when proxy info is available or when an error
+ * in the proxy resolution occurs.
+ *
+ * @param aRequest
+ * The value returned from asyncResolve.
+ * @param aChannel
+ * The channel passed to asyncResolve.
+ * @param aProxyInfo
+ * The resulting proxy info or null if there is no associated proxy
+ * info for aURI. As with the result of nsIProtocolProxyService's
+ * resolve method, a null result implies that a direct connection
+ * should be used.
+ * @param aStatus
+ * The status of the callback. This is a failure code if the request
+ * could not be satisfied, in which case the value of aStatus
+ * indicates the reason for the failure and aProxyInfo will be null.
+ */
+ void onProxyAvailable(in nsICancelable aRequest,
+ in nsIChannel aChannel,
+ in nsIProxyInfo aProxyInfo,
+ in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIProtocolProxyFilter.idl b/netwerk/base/nsIProtocolProxyFilter.idl
new file mode 100644
index 0000000000..a771af5f43
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyFilter.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIProtocolProxyService;
+interface nsIProxyInfo;
+interface nsIURI;
+
+/**
+ * Recipient of the result of implementers of nsIProtocolProxy(Channel)Filter
+ * allowing the proxyinfo be provided asynchronously.
+ */
+[scriptable, uuid(009E6C3F-FB64-40C5-8093-F1495C64773E)]
+interface nsIProxyProtocolFilterResult : nsISupports
+{
+ /**
+ * It's mandatory to call this method exactly once when the applyFilter()
+ * implementation doesn't throw and to not call it when applyFilter() does
+ * throw.
+ *
+ * It's mandatory to call this method on the same thread as the call to
+ * applyFilter() has been made on.
+ *
+ * Following the above conditions, can be called either from within
+ * applyFilter() or asynchronouly any time later.
+ */
+ void onProxyFilterResult(in nsIProxyInfo aProxy);
+};
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * URI. Use nsIProtocolProxyService::registerFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyChannelFilter.
+ */
+[scriptable, uuid(f424abd3-32b4-456c-9f45-b7e3376cb0d1)]
+interface nsIProtocolProxyFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given URI
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aURI
+ * The URI for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given URI. This may be null.
+ *
+ * @param aCallback
+ * An object that the implementer is obligated to call on with
+ * the result (from within applyFilter() or asynchronously) when
+ * applyFilter didn't throw. The argument passed to onProxyFilterResult
+ * is the proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use nsIProtocolProxyService.newProxyInfo
+ * to construct nsIProxyInfo objects.
+ */
+ void applyFilter(in nsIURI aURI, in nsIProxyInfo aProxy,
+ in nsIProxyProtocolFilterResult aCallback);
+};
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * channel. Use nsIProtocolProxyService::registerChannelFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyFilter.
+ */
+[scriptable, uuid(245b0880-82c5-4e6e-be6d-bc586aa55a90)]
+interface nsIProtocolProxyChannelFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given channel
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aChannel
+ * The channel for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given channel. This may be null.
+ *
+ * @param aCallback
+ * An object that the implementer is obligated to call on with
+ * the result (from within applyFilter() or asynchronously) when
+ * applyFilter didn't throw. The argument passed to onProxyFilterResult
+ * is the proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use nsIProtocolProxyService.newProxyInfo
+ * to construct nsIProxyInfo objects.
+ */
+ void applyFilter(in nsIChannel aChannel, in nsIProxyInfo aProxy,
+ in nsIProxyProtocolFilterResult aCallback);
+};
diff --git a/netwerk/base/nsIProtocolProxyService.idl b/netwerk/base/nsIProtocolProxyService.idl
new file mode 100644
index 0000000000..9bd65f5084
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService.idl
@@ -0,0 +1,330 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIProtocolProxyCallback;
+interface nsIProtocolProxyFilter;
+interface nsIProtocolProxyChannelFilter;
+interface nsIProxyInfo;
+interface nsIChannel;
+interface nsIURI;
+interface nsISerialEventTarget;
+
+[scriptable, uuid(77984234-aad5-47fc-a412-03398c2134a5)]
+interface nsIProxyConfigChangedCallback : nsISupports
+{
+ /**
+ * Called when one of the following conditions are changed.
+ * 1. System proxy settings changed.
+ * 2. A proxy filter is registered or unregistered.
+ * 3. Proxy related prefs changed.
+ */
+ void onProxyConfigChanged();
+};
+
+/**
+ * nsIProtocolProxyService provides methods to access information about
+ * various network proxies.
+ */
+[scriptable, builtinclass, uuid(ef57c8b6-e09d-4cd4-9222-2a5d2402e15d)]
+interface nsIProtocolProxyService : nsISupports
+{
+ /** Flag 1 << 0 is unused **/
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the SOCKS proxy
+ * to HTTP ones.
+ */
+ const unsigned long RESOLVE_PREFER_SOCKS_PROXY = 1 << 1;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to not analyze the uri's
+ * scheme specific proxy. When this flag is set the main HTTP proxy is the
+ * preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_SOCKS_PROXY is set then the SOCKS proxy is
+ * the preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_HTTPS_PROXY is set then the HTTPS proxy
+ * is the preferred one.
+ */
+ const unsigned long RESOLVE_IGNORE_URI_SCHEME = 1 << 2;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the HTTPS proxy
+ * to the others HTTP ones.
+ *
+ * NOTE: RESOLVE_PREFER_SOCKS_PROXY takes precedence over this flag.
+ *
+ * NOTE: This flag implies RESOLVE_IGNORE_URI_SCHEME.
+ */
+ const unsigned long RESOLVE_PREFER_HTTPS_PROXY =
+ (1 << 3) | RESOLVE_IGNORE_URI_SCHEME;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to that all methods will be tunneled via
+ * CONNECT through the http proxy.
+ */
+ const unsigned long RESOLVE_ALWAYS_TUNNEL = (1 << 4);
+
+ /**
+ * This method returns via callback a nsIProxyInfo instance that identifies
+ * a proxy to be used for the given channel. Otherwise, this method returns
+ * null indicating that a direct connection should be used.
+ *
+ * @param aChannelOrURI
+ * The channel for which a proxy is to be found, or, if no channel is
+ * available, a URI indicating the same. This method will return
+ * NS_ERROR_NOINTERFACE if this argument isn't either an nsIURI or an
+ * nsIChannel.
+ * @param aFlags
+ * A bit-wise combination of the RESOLVE_ flags defined above. Pass
+ * 0 to specify the default behavior. Any additional bits that do
+ * not correspond to a RESOLVE_ flag are reserved for future use.
+ * @param aCallback
+ * The object to be notified when the result is available.
+ * @param aMainThreadTarget
+ * A labelled event target for dispatching runnables to main thread.
+ *
+ * @return An object that can be used to cancel the asychronous operation.
+ * If canceled, the cancelation status (aReason) will be forwarded
+ * to the callback's onProxyAvailable method via the aStatus param.
+ *
+ * NOTE: If this proxy is unavailable, getFailoverForProxy may be called
+ * to determine the correct secondary proxy to be used.
+ *
+ * NOTE: If the protocol handler for the given URI supports
+ * nsIProxiedProtocolHandler, then the nsIProxyInfo instance returned from
+ * resolve may be passed to the newProxiedChannel method to create a
+ * nsIChannel to the given URI that uses the specified proxy.
+ *
+ * NOTE: However, if the nsIProxyInfo type is "http", then it means that
+ * the given URI should be loaded using the HTTP protocol handler, which
+ * also supports nsIProxiedProtocolHandler.
+ *
+ * @see nsIProxiedProtocolHandler::newProxiedChannel
+ */
+ nsICancelable asyncResolve(
+ in nsISupports aChannelOrURI, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback,
+ [optional] in nsISerialEventTarget aMainThreadTarget);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance from
+ * the given parameters. This method may be useful in conjunction with
+ * nsISocketTransportService::createTransport for creating, for example,
+ * a SOCKS connection.
+ *
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "http" - specifies a HTTP proxy
+ * "https" - specifies HTTP proxying over TLS connection to proxy
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * "direct" - specifies a direct connection (useful for failover)
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfo(in ACString aType, in AUTF8String aHost,
+ in long aPort,
+ in ACString aProxyAuthorizationHeader,
+ in ACString aConnectionIsolationKey,
+ in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance for
+ * with the specified username and password.
+ * Currently implemented for SOCKS proxies only.
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aUsername
+ * The proxy username
+ * @param aPassword
+ * The proxy password
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfoWithAuth(in ACString aType, in AUTF8String aHost,
+ in long aPort,
+ in AUTF8String aUsername, in AUTF8String aPassword,
+ in ACString aProxyAuthorizationHeader,
+ in ACString aConnectionIsolationKey,
+ in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * If the proxy identified by aProxyInfo is unavailable for some reason,
+ * this method may be called to access an alternate proxy that may be used
+ * instead. As a side-effect, this method may affect future result values
+ * from resolve/asyncResolve as well as from getFailoverForProxy.
+ *
+ * @param aProxyInfo
+ * The proxy that was unavailable.
+ * @param aURI
+ * The URI that was originally passed to resolve/asyncResolve.
+ * @param aReason
+ * The error code corresponding to the proxy failure. This value
+ * may be used to tune the delay before this proxy is used again.
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if there is no alternate proxy available.
+ */
+ nsIProxyInfo getFailoverForProxy(in nsIProxyInfo aProxyInfo,
+ in nsIURI aURI,
+ in nsresult aReason);
+
+ /**
+ * This method may be used to register a proxy filter instance. Each proxy
+ * filter is registered with an associated position that determines the
+ * order in which the filters are applied (starting from position 0). When
+ * resolve/asyncResolve is called, it generates a list of proxies for the
+ * given URI, and then it applies the proxy filters. The filters have the
+ * opportunity to modify the list of proxies.
+ *
+ * If two filters register for the same position, then the filters will be
+ * visited in the order in which they were registered.
+ *
+ * If the filter is already registered, then its position will be updated.
+ *
+ * After filters have been run, any disabled or disallowed proxies will be
+ * removed from the list. A proxy is disabled if it had previously failed-
+ * over to another proxy (see getFailoverForProxy). A proxy is disallowed,
+ * for example, if it is a HTTP proxy and the nsIProtocolHandler for the
+ * queried URI does not permit proxying via HTTP.
+ *
+ * If a nsIProtocolHandler disallows all proxying, then filters will never
+ * have a chance to intercept proxy requests for such URLs.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ *
+ * NOTE: It is possible to construct filters that compete with one another
+ * in undesirable ways. This API does not attempt to protect against such
+ * problems. It is recommended that any extensions that choose to call
+ * this method make their position value configurable at runtime (perhaps
+ * via the preferences service).
+ */
+ void registerFilter(in nsIProtocolProxyFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * Similar to registerFilter, but accepts an nsIProtocolProxyChannelFilter,
+ * which selects proxies according to channel rather than URI.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ */
+ void registerChannelFilter(in nsIProtocolProxyChannelFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * This method may be used to unregister a proxy filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be unregistered.
+ */
+ void unregisterFilter(in nsIProtocolProxyFilter aFilter);
+
+ /**
+ * This method may be used to unregister a proxy channel filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be unregistered.
+ */
+ void unregisterChannelFilter(in nsIProtocolProxyChannelFilter aFilter);
+
+ /**
+ * This method is used to register a nsIProxyConfigChangedCallback.
+ *
+ * @param aCallback
+ * The aCallback instance to be registered.
+ */
+ void addProxyConfigCallback(in nsIProxyConfigChangedCallback aCallback);
+
+ /**
+ * This method is used to unregister a nsIProxyConfigChangedCallback.
+ *
+ * @param aCallback
+ * The aCallback instance to be unregistered.
+ */
+ void removeProxyConfigCallback(in nsIProxyConfigChangedCallback aCallback);
+
+
+ /**
+ * This method is used internal only. Called when proxy config is changed.
+ */
+ void notifyProxyConfigChangedInternal();
+
+ /**
+ * These values correspond to the possible integer values for the
+ * network.proxy.type preference.
+ */
+ const unsigned long PROXYCONFIG_DIRECT = 0;
+ const unsigned long PROXYCONFIG_MANUAL = 1;
+ const unsigned long PROXYCONFIG_PAC = 2;
+ const unsigned long PROXYCONFIG_WPAD = 4;
+ const unsigned long PROXYCONFIG_SYSTEM = 5;
+
+ /**
+ * This attribute specifies the current type of proxy configuration.
+ */
+ readonly attribute unsigned long proxyConfigType;
+
+ /**
+ * True if there is a PAC download in progress.
+ */
+ [notxpcom, nostdcall] readonly attribute boolean isPACLoading;
+};
diff --git a/netwerk/base/nsIProtocolProxyService2.idl b/netwerk/base/nsIProtocolProxyService2.idl
new file mode 100644
index 0000000000..fe031bac40
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService2.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolProxyService.idl"
+
+/**
+ * An extension of nsIProtocolProxyService
+ */
+[scriptable, builtinclass, uuid(b2e5b2c0-e21e-4845-b336-be6d60a38951)]
+interface nsIProtocolProxyService2 : nsIProtocolProxyService
+{
+ /**
+ * Call this method to cause the PAC file (if any is configured) to be
+ * reloaded. The PAC file is loaded asynchronously.
+ */
+ void reloadPAC();
+
+ /**
+ * This method is identical to asyncResolve() except:
+ * - it only accepts an nsIChannel, not an nsIURI;
+ * - it may execute the callback function immediately (i.e from the stack
+ * of asyncResolve2()) if it is immediately ready to run.
+ * The nsICancelable return value will be null in that case.
+ */
+ nsICancelable asyncResolve2(
+ in nsIChannel aChannel, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback,
+ [optional] in nsISerialEventTarget aMainThreadTarget);
+};
diff --git a/netwerk/base/nsIProxiedChannel.idl b/netwerk/base/nsIProxiedChannel.idl
new file mode 100644
index 0000000000..c6796b9f9d
--- /dev/null
+++ b/netwerk/base/nsIProxiedChannel.idl
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIProxyInfo;
+
+/**
+ * An interface for accessing the proxy info that a channel was
+ * constructed with.
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+[scriptable, uuid(6238f134-8c3f-4354-958f-dfd9d54a4446)]
+interface nsIProxiedChannel : nsISupports
+{
+ /**
+ * Gets the proxy info the channel was constructed with. null or a
+ * proxyInfo with type "direct" mean no proxy.
+ *
+ * The returned proxy info must not be modified.
+ */
+ readonly attribute nsIProxyInfo proxyInfo;
+
+ /**
+ * The HTTP response code returned from the proxy to the CONNECT method.
+ * The response code is only available when we get the response from
+ * the proxy server, so this value is known in and after OnStartRequest.
+ *
+ * If CONNECT method is not used, httpProxyConnectResponseCode is always -1.
+ * After OnStartRequest, httpProxyConnectResponseCode is the real HTTP
+ * response code or 0 if we can't reach to the proxy.
+ */
+ readonly attribute int32_t httpProxyConnectResponseCode;
+};
diff --git a/netwerk/base/nsIProxiedProtocolHandler.idl b/netwerk/base/nsIProxiedProtocolHandler.idl
new file mode 100644
index 0000000000..d9436e94a7
--- /dev/null
+++ b/netwerk/base/nsIProxiedProtocolHandler.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIChannel;
+interface nsIURI;
+interface nsIProxyInfo;
+interface nsILoadInfo;
+
+[scriptable, builtinclass, uuid(3756047a-fa2b-4b45-9948-3b5f8fc375e7)]
+interface nsIProxiedProtocolHandler : nsIProtocolHandler
+{
+ /** Create a new channel with the given proxyInfo
+ *
+ * @param uri the channel uri
+ * @param proxyInfo any proxy information that has already been determined,
+ * or null if channel should later determine the proxy on its own using
+ * proxyResolveFlags/proxyURI
+ * @param proxyResolveFlags used if the proxy is later determined
+ * from nsIProtocolProxyService::asyncResolve
+ * @param proxyURI used if the proxy is later determined from
+ * nsIProtocolProxyService::asyncResolve with this as the proxyURI name.
+ * Generally this is the same as uri (or null which has the same
+ * effect), except in the case of websockets which wants to bootstrap
+ * to an http:// channel but make its proxy determination based on
+ * a ws:// uri.
+ * @param aLoadInfo used to evaluate who initated the resource request.
+ */
+ nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo,
+ in unsigned long proxyResolveFlags,
+ in nsIURI proxyURI,
+ in nsILoadInfo aLoadInfo);
+};
diff --git a/netwerk/base/nsIProxyInfo.idl b/netwerk/base/nsIProxyInfo.idl
new file mode 100644
index 0000000000..dc762fbd2d
--- /dev/null
+++ b/netwerk/base/nsIProxyInfo.idl
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface identifies a proxy server.
+ */
+[scriptable, uuid(63fff172-2564-4138-96c6-3ae7d245fbed)]
+interface nsIProxyInfo : nsISupports
+{
+ /**
+ * This attribute specifies the hostname of the proxy server.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * This attribute specifies the port number of the proxy server.
+ */
+ readonly attribute long port;
+
+ /**
+ * This attribute specifies the type of the proxy server as an ASCII string.
+ *
+ * Some special values for this attribute include (but are not limited to)
+ * the following:
+ * "http" HTTP proxy (or SSL CONNECT for HTTPS)
+ * "https" HTTP proxying over TLS connection to proxy
+ * "socks" SOCKS v5 proxy
+ * "socks4" SOCKS v4 proxy
+ * "direct" no proxy
+ * "unknown" unknown proxy (see nsIProtocolProxyService::resolve)
+ *
+ * A future version of this interface may define additional types.
+ */
+ readonly attribute ACString type;
+
+ /**
+ * This attribute specifies flags that modify the proxy type. The value of
+ * this attribute is the bit-wise combination of the Proxy Flags defined
+ * below. Any undefined bits are reserved for future use.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * This attribute specifies flags that were used by nsIProxyProtocolService when
+ * creating this ProxyInfo element.
+ */
+ readonly attribute unsigned long resolveFlags;
+
+ /**
+ * Specifies a proxy username.
+ */
+ readonly attribute ACString username;
+
+ /**
+ * Specifies a proxy password.
+ */
+ readonly attribute ACString password;
+
+ /**
+ * This attribute specifies the failover timeout in seconds for this proxy.
+ * If a nsIProxyInfo is reported as failed via nsIProtocolProxyService::
+ * getFailoverForProxy, then the failed proxy will not be used again for this
+ * many seconds.
+ */
+ readonly attribute unsigned long failoverTimeout;
+
+ /**
+ * This attribute specifies the proxy to failover to when this proxy fails.
+ */
+ attribute nsIProxyInfo failoverProxy;
+
+ /**
+ * Specifies an ID related to the source of this proxy configuration. If
+ * it is created in response to an extension API, it will be the extension ID.
+ */
+ attribute ACString sourceId;
+
+ /**
+ * Any non-empty value will be passed directly as Proxy-Authorization header
+ * value for the CONNECT request attempt. However, this header set on the
+ * resource request itself takes precedence.
+ */
+ readonly attribute ACString proxyAuthorizationHeader;
+
+ /**
+ * An optional key used for additional isolation of this proxy connection.
+ */
+ readonly attribute ACString connectionIsolationKey;
+
+ /****************************************************************************
+ * The following "Proxy Flags" may be bit-wise combined to construct the
+ * flags attribute defined on this interface. All unspecified bits are
+ * reserved for future use.
+ */
+
+ /**
+ * This flag is set if the proxy is to perform name resolution itself. If
+ * this is the case, the hostname is used in some fashion, and we shouldn't
+ * do any form of DNS lookup ourselves.
+ */
+ const unsigned short TRANSPARENT_PROXY_RESOLVES_HOST = 1 << 0;
+};
diff --git a/netwerk/base/nsIRandomGenerator.idl b/netwerk/base/nsIRandomGenerator.idl
new file mode 100644
index 0000000000..576483b549
--- /dev/null
+++ b/netwerk/base/nsIRandomGenerator.idl
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include <type_traits>
+%}
+
+/**
+ * Interface used to generate random data.
+ *
+ * @threadsafe
+ */
+[scriptable, uuid(2362d97a-747a-4576-8863-697667309209)]
+interface nsIRandomGenerator : nsISupports {
+ /**
+ * Generates the specified amount of random bytes.
+ *
+ * @param aLength
+ * The length of the data to generate.
+ * @param aBuffer
+ * A buffer that contains random bytes of size aLength.
+ */
+ void generateRandomBytes(in unsigned long aLength,
+ [retval, array, size_is(aLength)] out octet aBuffer);
+
+ /**
+ * Fills aBuffer with random bytes.
+ *
+ * @param aBuffer
+ * A buffer to fill with random bytes.
+ * @param aLength
+ * Length of aBuffer.
+ */
+ void generateRandomBytesInto([array, size_is(aLength)] in octet aBuffer,
+ in unsigned long aLength);
+
+%{C++
+ template<typename T>
+ std::enable_if_t<!std::is_pointer_v<T>, nsresult> GenerateRandomBytesInto(T& aResult) {
+ return GenerateRandomBytesInto(reinterpret_cast<uint8_t*>(&aResult), sizeof(T));
+ }
+%}
+};
diff --git a/netwerk/base/nsIRedirectChannelRegistrar.idl b/netwerk/base/nsIRedirectChannelRegistrar.idl
new file mode 100644
index 0000000000..26dfdf3c6f
--- /dev/null
+++ b/netwerk/base/nsIRedirectChannelRegistrar.idl
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIParentChannel;
+
+/**
+ * Used on the chrome process as a service to join channel implementation
+ * and parent IPC protocol side under a unique id. Provides this way a generic
+ * communication while redirecting to various protocols.
+ *
+ * See also nsIChildChannel and nsIParentChannel.
+ */
+
+[scriptable, uuid (efa36ea2-5b07-46fc-9534-a5acb8b77b72)]
+interface nsIRedirectChannelRegistrar : nsISupports
+{
+ /**
+ * Register the redirect target channel. The passed id needs to be a
+ * unique ID for that channel (see `nsContentUtils::GenerateLoadIdentifier`).
+ *
+ * Primarily used in ParentChannelListener::AsyncOnChannelRedirect to get
+ * a channel id sent to the HttpChannelChild being redirected.
+ */
+ void registerChannel(in nsIChannel channel, in uint64_t id);
+
+ /**
+ * First, search for the channel registered under the id. If found return
+ * it. Then, register under the same id the parent side of IPC protocol
+ * to let it be later grabbed back by the originator of the redirect and
+ * notifications from the real channel could be forwarded to this parent
+ * channel.
+ *
+ * Primarily used in parent side of an IPC protocol implementation
+ * in reaction to nsIChildChannel.connectParent(id) called from the child
+ * process.
+ */
+ nsIChannel linkChannels(in uint64_t id, in nsIParentChannel channel);
+
+ /**
+ * Returns back the channel previously registered under the ID with
+ * registerChannel method.
+ *
+ * Primarilly used in chrome IPC side of protocols when attaching a redirect
+ * target channel to an existing 'real' channel implementation.
+ */
+ nsIChannel getRegisteredChannel(in uint64_t id);
+
+ /**
+ * Returns the stream listener that shall be attached to the redirect target
+ * channel, all notification from the redirect target channel will be
+ * forwarded to this stream listener.
+ *
+ * Primarilly used in HttpChannelParent::OnRedirectResult callback to grab
+ * the created parent side of the channel and forward notifications to it.
+ */
+ nsIParentChannel getParentChannel(in uint64_t id);
+
+ /**
+ * To not force all channel implementations to support weak reference
+ * consumers of this service must ensure release of registered channels them
+ * self. This releases both the real and parent channel registered under
+ * the id.
+ *
+ * Primarilly used in HttpChannelParent::OnRedirectResult callback.
+ */
+ void deregisterChannels(in uint64_t id);
+};
diff --git a/netwerk/base/nsIRedirectHistoryEntry.idl b/netwerk/base/nsIRedirectHistoryEntry.idl
new file mode 100644
index 0000000000..50086574f8
--- /dev/null
+++ b/netwerk/base/nsIRedirectHistoryEntry.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * This nsIRedirectHistoryEntry defines an interface for specifying channel
+ * redirect information
+ */
+
+[scriptable, uuid(133b2905-0eba-411c-a8bb-f59787142aa2)]
+interface nsIRedirectHistoryEntry : nsISupports
+{
+ /**
+ * The principal of this redirect entry
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * The referring URI of this redirect entry. This may be null.
+ */
+ readonly attribute nsIURI referrerURI;
+
+ /**
+ * The remote address of this redirect entry.
+ */
+ readonly attribute ACString remoteAddress;
+
+};
diff --git a/netwerk/base/nsIRedirectResultListener.idl b/netwerk/base/nsIRedirectResultListener.idl
new file mode 100644
index 0000000000..57241350a4
--- /dev/null
+++ b/netwerk/base/nsIRedirectResultListener.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(85cd2640-e91e-41ac-bdca-1dbf10dc131e)]
+interface nsIRedirectResultListener : nsISupports
+{
+ /**
+ * When an HTTP redirect has been processed (either successfully or not)
+ * nsIHttpChannel will call this function if its callbacks implement this
+ * interface.
+ *
+ * @param proceeding
+ * Indicated whether the redirect will be proceeding, or not (i.e.
+ * has been canceled, or failed).
+ */
+ void onRedirectResult(in nsresult status);
+};
diff --git a/netwerk/base/nsIRequest.idl b/netwerk/base/nsIRequest.idl
new file mode 100644
index 0000000000..8fa9462136
--- /dev/null
+++ b/netwerk/base/nsIRequest.idl
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+#include "nsString.h"
+%}
+
+interface nsILoadGroup;
+
+typedef unsigned long nsLoadFlags;
+
+/**
+ * nsIRequest
+ */
+[scriptable, uuid(ef6bfbd2-fd46-48d8-96b7-9f8f0fd387fe)]
+interface nsIRequest : nsISupports
+{
+ /**
+ * The name of the request. Often this is the URI of the request.
+ */
+ readonly attribute AUTF8String name;
+
+ /**
+ * Indicates whether the request is pending. nsIRequest::isPending is
+ * true when there is an outstanding asynchronous event that will make
+ * the request no longer be pending. Requests do not necessarily start
+ * out pending; in some cases, requests have to be explicitly initiated
+ * (e.g. nsIChannel implementations are only pending once asyncOpen
+ * returns successfully).
+ *
+ * Requests can become pending multiple times during their lifetime.
+ *
+ * @return TRUE if the request has yet to reach completion.
+ * @return FALSE if the request has reached completion (e.g., after
+ * OnStopRequest has fired).
+ * @note Suspended requests are still considered pending.
+ */
+ boolean isPending();
+
+ /**
+ * The error status associated with the request.
+ */
+ readonly attribute nsresult status;
+
+ /**
+ * Cancels the current request. This will close any open input or
+ * output streams and terminate any async requests. Users should
+ * normally pass NS_BINDING_ABORTED, although other errors may also
+ * be passed. The error passed in will become the value of the
+ * status attribute.
+ *
+ * Implementations must not send any notifications (e.g. via
+ * nsIRequestObserver) synchronously from this function. Similarly,
+ * removal from the load group (if any) must also happen asynchronously.
+ *
+ * Requests that use nsIStreamListener must not call onDataAvailable
+ * anymore after cancel has been called.
+ *
+ * @param aStatus the reason for canceling this request.
+ *
+ * NOTE: most nsIRequest implementations expect aStatus to be a
+ * failure code; however, some implementations may allow aStatus to
+ * be a success code such as NS_OK. In general, aStatus should be
+ * a failure code.
+ */
+ void cancel(in nsresult aStatus);
+
+ /**
+ * Suspends the current request. This may have the effect of closing
+ * any underlying transport (in order to free up resources), although
+ * any open streams remain logically opened and will continue delivering
+ * data when the transport is resumed.
+ *
+ * Calling cancel() on a suspended request must not send any
+ * notifications (such as onstopRequest) until the request is resumed.
+ *
+ * NOTE: some implementations are unable to immediately suspend, and
+ * may continue to deliver events already posted to an event queue. In
+ * general, callers should be capable of handling events even after
+ * suspending a request.
+ */
+ void suspend();
+
+ /**
+ * Resumes the current request. This may have the effect of re-opening
+ * any underlying transport and will resume the delivery of data to
+ * any open streams.
+ */
+ void resume();
+
+ /**
+ * The load group of this request. While pending, the request is a
+ * member of the load group. It is the responsibility of the request
+ * to implement this policy.
+ */
+ attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load flags of this request. Bits 0-15 are reserved.
+ *
+ * When added to a load group, this request's load flags are merged with
+ * the load flags of the load group.
+ */
+ attribute nsLoadFlags loadFlags;
+
+ /**
+ * Mask defining the bits reserved for nsIRequest LoadFlags
+ */
+ const unsigned long LOAD_REQUESTMASK = 0xFFFF;
+
+ /**************************************************************************
+ * Listed below are the various load flags which may be or'd together.
+ */
+
+ /**
+ * No special load flags:
+ */
+ const unsigned long LOAD_NORMAL = 0;
+
+ /**
+ * Do not deliver status notifications to the nsIProgressEventSink and
+ * do not block the loadgroup from completing (should this load belong to one).
+ * Note: Progress notifications will still be delivered.
+ */
+ const unsigned long LOAD_BACKGROUND = 1 << 0;
+
+ /**
+ * This flag marks the request as being made to load the data for an html
+ * <object> tag. This means that the LOAD_DOCUMENT_URI flag may be set after
+ * the channel has been provided with the MIME type.
+ */
+ const unsigned long LOAD_HTML_OBJECT_DATA = 1 << 1;
+
+ /**
+ * This flag marks the request as belonging to a document that requires access
+ * to the document.cookies API.
+ */
+ const unsigned long LOAD_DOCUMENT_NEEDS_COOKIE = 1 << 2;
+
+ cenum TRRMode : 32 {
+ TRR_DEFAULT_MODE = 0,
+ TRR_DISABLED_MODE = 1,
+ TRR_FIRST_MODE = 2,
+ TRR_ONLY_MODE = 3
+ };
+
+ /**
+ * These methods encode/decode the TRR mode to/from the loadFlags.
+ * Helper methods Get/SetTRRModeImpl are provided so implementations don't
+ * need to duplicate code.
+ *
+ * Requests with TRR_DEFAULT_MODE will use the mode indicated by the pref
+ * - see network.trr.mode in all.js
+ * Requests with TRR_DISABLED_MODE will always use native DNS, even if the
+ * pref is set to mode3 (TRR-only).
+ * Requests with TRR_FIRST_MODE will first use TRR then fallback to regular
+ * DNS, unless TRR is disabled by setting the pref to mode5, parental
+ * control being enabled, or the domain being in the exclusion list.
+ * Requests with TRR_ONLY_MODE will only use TRR, unless not allowed by
+ * the same conditions mentioned above.
+ */
+ nsIRequest_TRRMode getTRRMode();
+ void setTRRMode(in nsIRequest_TRRMode mode);
+
+ %{C++
+ inline TRRMode GetTRRMode() {
+ TRRMode mode = TRR_DEFAULT_MODE;
+ GetTRRMode(&mode);
+ return mode;
+ }
+
+ inline nsresult GetTRRModeImpl(nsIRequest::TRRMode* aTRRMode) {
+ NS_ENSURE_ARG_POINTER(aTRRMode);
+ nsLoadFlags flags = nsIRequest::LOAD_NORMAL;
+ nsresult rv = GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aTRRMode = static_cast<nsIRequest::TRRMode>(
+ (flags & nsIRequest::LOAD_TRR_MASK) >> 3);
+ return NS_OK;
+ }
+
+ inline nsresult SetTRRModeImpl(nsIRequest::TRRMode aTRRMode) {
+ MOZ_ASSERT(aTRRMode <= 3, "invalid value");
+ nsLoadFlags flags = nsIRequest::LOAD_NORMAL;
+ nsresult rv = GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ flags = (flags & ~nsIRequest::LOAD_TRR_MASK) | (aTRRMode << 3);
+ return SetLoadFlags(flags);
+ }
+ %}
+
+ /**
+ * These two bits encode the TRR mode.
+ * Do not get/set manually, rather use the getTRRMode/setTRRMode methods.
+ */
+ const unsigned long LOAD_TRR_MASK = (1 << 3) | (1 << 4);
+ const unsigned long LOAD_TRR_DISABLED_MODE = 1 << 3;
+ const unsigned long LOAD_TRR_FIRST_MODE = 1 << 4;
+ const unsigned long LOAD_TRR_ONLY_MODE = (1 << 3) | (1 << 4);
+
+ void cancelWithReason(in nsresult aStatus, in ACString aReason);
+ attribute ACString canceledReason;
+
+ %{C++
+ protected:
+ nsCString mCanceledReason;
+
+ public:
+ inline nsresult SetCanceledReasonImpl(const nsACString& aReason) {
+ if (mCanceledReason.IsEmpty()) {
+ mCanceledReason.Assign(aReason);
+ }
+
+ return NS_OK;
+ }
+
+ inline nsresult CancelWithReasonImpl(nsresult aStatus,
+ const nsACString& aReason) {
+ SetCanceledReasonImpl(aReason);
+ return Cancel(aStatus);
+ }
+
+ inline nsresult GetCanceledReasonImpl(nsACString& aReason) {
+ aReason.Assign(mCanceledReason);
+ return NS_OK;
+ }
+ %}
+
+ /**
+ * This is used for a temporary workaround for a web-compat issue. The flag is
+ * only set on CORS preflight request to allowed sending client certificates
+ * on a connection for an anonymous request.
+ */
+ const long LOAD_ANONYMOUS_ALLOW_CLIENT_CERT = 1 << 5;
+
+ /**************************************************************************
+ * The following flags control the flow of data into the cache.
+ */
+
+ /**
+ * This flag prevents caching of any kind. It does not, however, prevent
+ * cached content from being used to satisfy this request.
+ */
+ const unsigned long INHIBIT_CACHING = 1 << 7;
+
+ /**
+ * This flag prevents caching on disk (or other persistent media), which
+ * may be needed to preserve privacy.
+ */
+ const unsigned long INHIBIT_PERSISTENT_CACHING = 1 << 8;
+
+ /**************************************************************************
+ * The following flags control what happens when the cache contains data
+ * that could perhaps satisfy this request. They are listed in descending
+ * order of precidence.
+ */
+
+ /**
+ * Force an end-to-end download of content data from the origin server.
+ * This flag is used for a shift-reload.
+ */
+ const unsigned long LOAD_BYPASS_CACHE = 1 << 9;
+
+ /**
+ * Attempt to force a load from the cache, bypassing ALL validation logic
+ * (note: this is stronger than VALIDATE_NEVER, which still validates for
+ * certain conditions).
+ *
+ * If the resource is not present in cache, it will be loaded from the
+ * network. Combine this flag with LOAD_ONLY_FROM_CACHE if you wish to
+ * perform cache-only loads without validation checks.
+ *
+ * This flag is used when browsing via history. It is not recommended for
+ * normal browsing as it may likely violate reasonable assumptions made by
+ * the server and confuse users.
+ */
+ const unsigned long LOAD_FROM_CACHE = 1 << 10;
+
+ /**
+ * The following flags control the frequency of cached content validation
+ * when neither LOAD_BYPASS_CACHE or LOAD_FROM_CACHE are set. By default,
+ * cached content is automatically validated if necessary before reuse.
+ *
+ * VALIDATE_ALWAYS forces validation of any cached content independent of
+ * its expiration time (unless it is https with Cache-Control: immutable)
+ *
+ * VALIDATE_NEVER disables validation of cached content, unless it arrived
+ * with the "Cache: no-store" header, or arrived via HTTPS with the
+ * "Cache: no-cache" header.
+ *
+ * VALIDATE_ONCE_PER_SESSION disables validation of expired content,
+ * provided it has already been validated (at least once) since the start
+ * of this session.
+ *
+ * NOTE TO IMPLEMENTORS:
+ * These flags are intended for normal browsing, and they should therefore
+ * not apply to content that must be validated before each use. Consider,
+ * for example, a HTTP response with a "Cache-control: no-cache" header.
+ * According to RFC2616, this response must be validated before it can
+ * be taken from a cache. Breaking this requirement could result in
+ * incorrect and potentially undesirable side-effects.
+ */
+ const unsigned long VALIDATE_ALWAYS = 1 << 11;
+ const unsigned long VALIDATE_NEVER = 1 << 12;
+ const unsigned long VALIDATE_ONCE_PER_SESSION = 1 << 13;
+
+ /**
+ * When set, this flag indicates that no user-specific data should be added
+ * to the request when opened. This means that things like authorization
+ * tokens or cookie headers should not be added.
+ */
+ const unsigned long LOAD_ANONYMOUS = 1 << 14;
+
+ /**
+ * When set, this flag indicates that caches of network connections,
+ * particularly HTTP persistent connections, should not be used.
+ * Use this together with LOAD_INITIAL_DOCUMENT_URI as otherwise it has no
+ * effect.
+ */
+ const unsigned long LOAD_FRESH_CONNECTION = 1 << 15;
+
+ // Note that all flags are taken, the rest of the flags
+ // are located in nsIChannel and nsICachingChannel
+};
diff --git a/netwerk/base/nsIRequestContext.idl b/netwerk/base/nsIRequestContext.idl
new file mode 100644
index 0000000000..6c9f265af2
--- /dev/null
+++ b/netwerk/base/nsIRequestContext.idl
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+// Forward-declare mozilla::net::SpdyPushCache
+namespace mozilla {
+namespace net {
+class SpdyPushCache;
+}
+}
+%}
+
+interface nsILoadGroup;
+interface nsIChannel;
+interface nsIStreamListener;
+
+[ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache);
+
+/**
+ * Requests capable of tail-blocking must implement this
+ * interfaces (typically channels).
+ * If the request is tail-blocked, it will be held in its request
+ * context queue until unblocked.
+ */
+[uuid(7EB361D4-37A5-42C9-AFAE-F6C88FE7C394)]
+interface nsIRequestTailUnblockCallback : nsISupports
+{
+ /**
+ * Called when the requests is unblocked and proceed.
+ * @param result
+ * NS_OK - the request is OK to go, unblocking is not
+ * caused by cancelation of the request.
+ * any error - the request must behave as it were canceled
+ * with the result as status.
+ */
+ void onTailUnblock(in nsresult aResult);
+};
+
+/**
+ * The nsIRequestContext is used to maintain state about connections
+ * that are in some way associated with each other (often by being part
+ * of the same load group) and how they interact with blocking items like
+ * HEAD css/js loads.
+ *
+ * This used to be known as nsILoadGroupConnectionInfo and nsISchedulingContext.
+ */
+[uuid(658e3e6e-8633-4b1a-8d66-fa9f72293e63)]
+interface nsIRequestContext : nsISupports
+{
+ /**
+ * A unique identifier for this request context
+ */
+ [notxpcom, nostdcall] readonly attribute unsigned long long ID;
+
+ /**
+ * Called by the associated document when its load starts. This resets
+ * context's internal states.
+ */
+ void beginLoad();
+
+ /**
+ * Called when the associated document notified the DOMContentLoaded event.
+ */
+ void DOMContentLoaded();
+
+ /**
+ * Number of active blocking transactions associated with this context
+ */
+ readonly attribute unsigned long blockingTransactionCount;
+
+ /**
+ * Increase the number of active blocking transactions associated
+ * with this context by one.
+ */
+ void addBlockingTransaction();
+
+ /**
+ * Decrease the number of active blocking transactions associated
+ * with this context by one. The return value is the number of remaining
+ * blockers.
+ */
+ unsigned long removeBlockingTransaction();
+
+ /**
+ * This gives out a weak pointer to the push cache.
+ * The nsIRequestContext implementation owns the cache
+ * and will destroy it when overwritten or when the context
+ * ends.
+ */
+ [notxpcom,nostdcall] attribute SpdyPushCachePtr spdyPushCache;
+
+ /**
+ * Increases/decrease the number of non-tailed requests in this context.
+ * If the count drops to zero, all tail-blocked callbacks are notified
+ * shortly after that to be unblocked.
+ */
+ void addNonTailRequest();
+ void removeNonTailRequest();
+
+ /**
+ * If the request context is in tail-blocked state, the callback
+ * is queued and result is true. The callback will be notified
+ * about tail-unblocking or when the request context is canceled.
+ */
+ [must_use] boolean isContextTailBlocked(in nsIRequestTailUnblockCallback callback);
+
+ /**
+ * Called when the request is sitting in the tail queue but has been
+ * canceled before untailing. This just removes the request from the
+ * queue so that it is not notified on untail and not referenced.
+ */
+ void cancelTailedRequest(in nsIRequestTailUnblockCallback request);
+
+ /**
+ * This notifies all queued tail-blocked requests, they will be notified
+ * aResult and released afterwards. Called by the load group when
+ * it's canceled.
+ */
+ void cancelTailPendingRequests(in nsresult aResult);
+};
+
+/**
+ * The nsIRequestContextService is how anyone gets access to a request
+ * context when they haven't been explicitly given a strong reference to an
+ * existing one. It is responsible for creating and handing out strong
+ * references to nsIRequestContexts, but only keeps weak references itself.
+ * The shared request context will go away once no one else is keeping a
+ * reference to it. If you ask for a request context that has no one else
+ * holding a reference to it, you'll get a brand new request context. Anyone
+ * who asks for the same request context while you're holding a reference
+ * will get a reference to the same request context you have.
+ */
+[uuid(7fcbf4da-d828-4acc-b144-e5435198f727)]
+interface nsIRequestContextService : nsISupports
+{
+ /**
+ * Get an existing request context from its ID
+ */
+ nsIRequestContext getRequestContext(in unsigned long long id);
+ /**
+ * Shorthand to get request context from a load group
+ */
+ nsIRequestContext getRequestContextFromLoadGroup(in nsILoadGroup lg);
+
+ /**
+ * Create a new request context
+ */
+ nsIRequestContext newRequestContext();
+
+ /**
+ * Remove an existing request context from its ID
+ */
+ void removeRequestContext(in unsigned long long id);
+};
diff --git a/netwerk/base/nsIRequestObserver.idl b/netwerk/base/nsIRequestObserver.idl
new file mode 100644
index 0000000000..674e923a32
--- /dev/null
+++ b/netwerk/base/nsIRequestObserver.idl
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+
+/**
+ * nsIRequestObserver
+ */
+[scriptable, uuid(fd91e2e0-1481-11d3-9333-00104ba0fd40)]
+interface nsIRequestObserver : nsISupports
+{
+ /**
+ * Called to signify the beginning of an asynchronous request.
+ *
+ * @param aRequest request being observed
+ *
+ * An exception thrown from onStartRequest has the side-effect of
+ * causing the request to be canceled.
+ *
+ * Note: if this request is an nsIMultiPartChannelListener then
+ * OnStartRequest may be called multiple times.
+ */
+ void onStartRequest(in nsIRequest aRequest);
+
+ /**
+ * Called to signify the end of an asynchronous request. This
+ * call is always preceded by a call to onStartRequest.
+ *
+ * @param aRequest request being observed
+ * @param aStatusCode reason for stopping (NS_OK if completed successfully)
+ *
+ * An exception thrown from onStopRequest is generally ignored.
+ *
+ * Note: if this request is an nsIMultiPartChannelListener then
+ * OnStopRequest may be called multiple times.
+ */
+ void onStopRequest(in nsIRequest aRequest,
+ in nsresult aStatusCode);
+};
diff --git a/netwerk/base/nsIRequestObserverProxy.idl b/netwerk/base/nsIRequestObserverProxy.idl
new file mode 100644
index 0000000000..7b79f53427
--- /dev/null
+++ b/netwerk/base/nsIRequestObserverProxy.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequestObserver.idl"
+
+interface nsIEventTarget;
+
+/**
+ * A request observer proxy is used to ship data over to another thread
+ * specified by the thread's dispatch target. The "true" request observer's
+ * methods are invoked on the other thread.
+ *
+ * This interface only provides the initialization needed after construction.
+ * Otherwise, these objects are used simply as nsIRequestObserver's.
+ */
+[scriptable, uuid(c2b06151-1bf8-4eef-aea9-1532f12f5a10)]
+interface nsIRequestObserverProxy : nsIRequestObserver
+{
+ /**
+ * Initializes an nsIRequestObserverProxy.
+ *
+ * @param observer - receives observer notifications on the main thread
+ * @param context - the context argument that will be passed to OnStopRequest
+ * and OnStartRequest. This has to be stored permanently on
+ * initialization because it sometimes can't be
+ * AddRef/Release'd off-main-thread.
+ */
+ void init(in nsIRequestObserver observer, in nsISupports context);
+};
diff --git a/netwerk/base/nsIResumableChannel.idl b/netwerk/base/nsIResumableChannel.idl
new file mode 100644
index 0000000000..6737e8bf9d
--- /dev/null
+++ b/netwerk/base/nsIResumableChannel.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+[scriptable, uuid(4ad136fa-83af-4a22-a76e-503642c0f4a8)]
+interface nsIResumableChannel : nsISupports {
+ /**
+ * Prepare this channel for resuming. The request will not start until
+ * asyncOpen or open is called. Calling resumeAt after open or asyncOpen
+ * has been called has undefined behaviour.
+ *
+ * @param startPos the starting offset, in bytes, to use to download
+ * @param entityID information about the file, to match before obtaining
+ * the file. Pass an empty string to use anything.
+ *
+ * During OnStartRequest, this channel will have a status of
+ * NS_ERROR_NOT_RESUMABLE if the file cannot be resumed, eg because the
+ * server doesn't support this. This error may occur even if startPos
+ * is 0, so that the front end can warn the user.
+ * Similarly, the status of this channel during OnStartRequest may be
+ * NS_ERROR_ENTITY_CHANGED, which indicates that the entity has changed,
+ * as indicated by a changed entityID.
+ * In both of these cases, no OnDataAvailable will be called, and
+ * OnStopRequest will immediately follow with the same status code.
+ */
+ void resumeAt(in unsigned long long startPos,
+ in ACString entityID);
+
+ /**
+ * The entity id for this URI. Available after OnStartRequest.
+ * @throw NS_ERROR_NOT_RESUMABLE if this load is not resumable.
+ */
+ readonly attribute ACString entityID;
+};
diff --git a/netwerk/base/nsISecCheckWrapChannel.idl b/netwerk/base/nsISecCheckWrapChannel.idl
new file mode 100644
index 0000000000..21f4d0c290
--- /dev/null
+++ b/netwerk/base/nsISecCheckWrapChannel.idl
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * nsISecCheckWrapChannel
+ * Describes an XPCOM component used to wrap channels for performing
+ * security checks. Channels wrapped inside this class can use
+ * this interface to query the wrapped inner channel.
+ */
+
+[scriptable, uuid(9446c5d5-c9fb-4a6e-acf9-ca4fc666efe0)]
+interface nsISecCheckWrapChannel : nsISupports
+{
+ /**
+ * Returns the wrapped channel inside this class.
+ */
+ readonly attribute nsIChannel innerChannel;
+
+};
diff --git a/netwerk/base/nsISecureBrowserUI.idl b/netwerk/base/nsISecureBrowserUI.idl
new file mode 100644
index 0000000000..3984310d93
--- /dev/null
+++ b/netwerk/base/nsISecureBrowserUI.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITransportSecurityInfo;
+
+[scriptable, builtinclass, uuid(718c662a-f810-4a80-a6c9-0b1810ecade2)]
+interface nsISecureBrowserUI : nsISupports
+{
+ readonly attribute unsigned long state;
+ readonly attribute bool isSecureContext;
+ readonly attribute nsITransportSecurityInfo secInfo;
+};
+
+%{C++
+#define NS_SECURE_BROWSER_UI_CONTRACTID "@mozilla.org/secure_browser_ui;1"
+%}
diff --git a/netwerk/base/nsISensitiveInfoHiddenURI.idl b/netwerk/base/nsISensitiveInfoHiddenURI.idl
new file mode 100644
index 0000000000..abb3f082b9
--- /dev/null
+++ b/netwerk/base/nsISensitiveInfoHiddenURI.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a5761968-6e1a-4f2d-8191-ec749602b178)]
+interface nsISensitiveInfoHiddenURI : nsISupports
+{
+ /**
+ * Returns the spec attribute with sensitive information hidden. This will
+ * only affect uri with password. The password part of uri will be
+ * transformed into "****".
+ */
+ AUTF8String getSensitiveInfoHiddenSpec();
+};
diff --git a/netwerk/base/nsISerializationHelper.idl b/netwerk/base/nsISerializationHelper.idl
new file mode 100644
index 0000000000..740927f407
--- /dev/null
+++ b/netwerk/base/nsISerializationHelper.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Simple scriptable serialization helper. Can be used as a service.
+ */
+
+interface nsISerializable;
+
+[scriptable, uuid(31654c0f-35f3-44c6-b31e-37a11516e6bc)]
+interface nsISerializationHelper : nsISupports
+{
+ /**
+ * Serialize the object to a base64 string. This string can be later passed
+ * as an input to deserializeObject method.
+ */
+ ACString serializeToString(in nsISerializable serializable);
+
+ /**
+ * Takes base64 encoded string that cointains serialization of a single
+ * object. Most commonly, input is result of previous call to
+ * serializeToString.
+ */
+ nsISupports deserializeObject(in ACString input);
+};
diff --git a/netwerk/base/nsIServerSocket.idl b/netwerk/base/nsIServerSocket.idl
new file mode 100644
index 0000000000..d6fd348778
--- /dev/null
+++ b/netwerk/base/nsIServerSocket.idl
@@ -0,0 +1,269 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIServerSocketListener;
+interface nsISocketTransport;
+
+native PRNetAddr(union PRNetAddr);
+[ptr] native PRNetAddrPtr(union PRNetAddr);
+
+typedef unsigned long nsServerSocketFlag;
+
+/**
+ * nsIServerSocket
+ *
+ * An interface to a server socket that can accept incoming connections.
+ */
+[scriptable, uuid(7a9c39cb-a13f-4eef-9bdf-a74301628742)]
+interface nsIServerSocket : nsISupports
+{
+ /**
+ * @name Server Socket Flags
+ * These flags define various socket options.
+ * @{
+ */
+ /// The server socket will only respond to connections on the
+ /// local loopback interface. Otherwise, it will accept connections
+ /// from any interface. To specify a particular network interface,
+ /// use initWithAddress.
+ const nsServerSocketFlag LoopbackOnly = 0x00000001;
+ /// The server socket will not be closed when Gecko is set
+ /// offline.
+ const nsServerSocketFlag KeepWhenOffline = 0x00000002;
+ /** @} */
+
+ /**
+ * init
+ *
+ * This method initializes a server socket.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the server socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in long aBackLog);
+
+ /**
+ * the same as init(), but initializes an IPv6 server socket
+ */
+ void initIPv6(in long aPort,
+ in boolean aLoopbackOnly,
+ in long aBackLog);
+
+ /**
+ * Similar to init(), but initializes a server socket that supports
+ * both IPv4 and IPv6.
+ */
+ void initDualStack(in long aPort,
+ in long aBackLog);
+
+ /**
+ * initSpecialConnection
+ *
+ * This method initializes a server socket and offers the ability to have
+ * that socket not get terminated if Gecko is set offline.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aFlags
+ * Flags for the socket.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void initSpecialConnection(in long aPort,
+ in nsServerSocketFlag aFlags,
+ in long aBackLog);
+
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a server socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this server socket should be bound.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ [noscript] void initWithAddress([const] in PRNetAddrPtr aAddr, in long aBackLog);
+
+ /**
+ * initWithFilename
+ *
+ * This method initializes a Unix domain or "local" server socket. Such
+ * a socket has a name in the filesystem, like an ordinary file. To
+ * connect, a client supplies the socket's filename, and the usual
+ * permission checks on socket apply.
+ *
+ * This makes Unix domain sockets useful for communication between the
+ * programs being run by a specific user on a single machine: the
+ * operating system takes care of authentication, and the user's home
+ * directory or profile directory provide natural per-user rendezvous
+ * points.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * All components of the path prefix of |aPath| must name directories;
+ * otherwise, this returns NS_ERROR_FILE_NOT_DIRECTORY.
+ *
+ * This call requires execute permission on all directories containing
+ * the one in which the socket is to be created, and write and execute
+ * permission on the directory itself. Otherwise, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * This call creates the socket's directory entry. There must not be
+ * any existing entry with the given name. If there is, this returns
+ * NS_ERROR_SOCKET_ADDRESS_IN_USE.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aPath nsIFile
+ * The file name at which the socket should be created.
+ *
+ * @param aPermissions unsigned long
+ * Unix-style permission bits to be applied to the new socket.
+ *
+ * Note about permissions: Linux's unix(7) man page claims that some
+ * BSD-derived systems ignore permissions on UNIX-domain sockets;
+ * NetBSD's bind(2) man page agrees, but says it does check now (dated
+ * 2005). POSIX has required 'connect' to fail if write permission on
+ * the socket itself is not granted since 2003 (Issue 6). NetBSD says
+ * that the permissions on the containing directory (execute) have
+ * always applied, so creating sockets in appropriately protected
+ * directories should be secure on both old and new systems.
+ */
+ void initWithFilename(in nsIFile aPath, in unsigned long aPermissions,
+ in long aBacklog);
+
+ /**
+ * initWithAbstractAddress
+ *
+ * This mehtod is a flavor of initWithFilename method. This initializes
+ * a UNIX domain socket that uses abstract socket address.
+ * This socket type is only supported on Linux and Android.
+ *
+ * On systems that don't support this type's UNIX domain sockets at all,
+ * this returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aName
+ * The abstract socket address which the socket should be created.
+ * @param aBacklog
+ * The maximum length the queue of pending connections may grow to.
+ */
+ void initWithAbstractAddress(in AUTF8String aName,
+ in long aBacklog);
+
+ /**
+ * close
+ *
+ * This method closes a server socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this server socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the server socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIServerSocketListener aListener);
+
+ /**
+ * Returns the port of this server socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this server socket is bound. Since a
+ * server socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a server socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] PRNetAddr getAddress();
+};
+
+/**
+ * nsIServerSocketListener
+ *
+ * This interface is notified whenever a server socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(836d98ec-fee2-4bde-b609-abd5e966eabd)]
+interface nsIServerSocketListener : nsISupports
+{
+ /**
+ * onSocketAccepted
+ *
+ * This method is called when a client connection is accepted.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aTransport
+ * The connected socket transport.
+ */
+ void onSocketAccepted(in nsIServerSocket aServ,
+ in nsISocketTransport aTransport);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The server socket is effectively dead after this notification.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aStatus
+ * The reason why the server socket stopped listening. If the
+ * server socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIServerSocket aServ, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsISimpleStreamListener.idl b/netwerk/base/nsISimpleStreamListener.idl
new file mode 100644
index 0000000000..99169481f5
--- /dev/null
+++ b/netwerk/base/nsISimpleStreamListener.idl
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIOutputStream;
+
+/**
+ * A simple stream listener can be used with AsyncRead to supply data to
+ * a output stream.
+ */
+[scriptable, uuid(a9b84f6a-0824-4278-bae6-bfca0570a26e)]
+interface nsISimpleStreamListener : nsIStreamListener
+{
+ /**
+ * Initialize the simple stream listener.
+ *
+ * @param aSink data will be read from the channel to this output stream.
+ * Must implement writeFrom.
+ * @param aObserver optional stream observer (can be NULL)
+ */
+ void init(in nsIOutputStream aSink,
+ in nsIRequestObserver aObserver);
+};
diff --git a/netwerk/base/nsISimpleURIMutator.idl b/netwerk/base/nsISimpleURIMutator.idl
new file mode 100644
index 0000000000..786c8701d2
--- /dev/null
+++ b/netwerk/base/nsISimpleURIMutator.idl
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURIMutator;
+
+[scriptable, builtinclass, uuid(e055bddd-f3c2-404b-adec-db9304e93be2)]
+interface nsISimpleURIMutator : nsISupports
+{
+ /**
+ * Same behaviour as nsIURISetSpec.setSpec() but filters whitespace.
+ */
+ nsIURIMutator setSpecAndFilterWhitespace(in AUTF8String aSpec);
+};
diff --git a/netwerk/base/nsISocketFilter.idl b/netwerk/base/nsISocketFilter.idl
new file mode 100644
index 0000000000..0846fa2eda
--- /dev/null
+++ b/netwerk/base/nsISocketFilter.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsINetAddr.idl"
+
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+
+
+/**
+ * Filters are created and run on the parent, and filter all packets, both
+ * ingoing and outgoing. The child must specify the name of a recognized filter
+ * in order to create a socket.
+ */
+[uuid(afe2c40c-b9b9-4207-b898-e5cde18c6139)]
+interface nsISocketFilter : nsISupports
+{
+ const long SF_INCOMING = 0;
+ const long SF_OUTGOING = 1;
+
+ bool filterPacket([const]in NetAddrPtr remote_addr,
+ [const, array, size_is(len)]in uint8_t data,
+ in unsigned long len,
+ in long direction);
+};
+
+/**
+ * Factory of a specified filter.
+ */
+[uuid(81ee76c6-4753-4125-9c8c-290ed9ba62fb)]
+interface nsISocketFilterHandler : nsISupports
+{
+ nsISocketFilter newFilter();
+};
+
+%{C++
+/**
+ * Filter handlers are registered with XPCOM under the following CONTRACTID prefix:
+ */
+#define NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/udp-filter-handler;1?name="
+#define NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/tcp-filter-handler;1?name="
+
+#define NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX "stun"
+
+#define NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX
+
+
+#define NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX
+%}
diff --git a/netwerk/base/nsISocketTransport.idl b/netwerk/base/nsISocketTransport.idl
new file mode 100644
index 0000000000..58b869203e
--- /dev/null
+++ b/netwerk/base/nsISocketTransport.idl
@@ -0,0 +1,382 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsITransport.idl"
+#include "nsIRequest.idl"
+#include "nsITRRSkipReason.idl"
+
+interface nsIInterfaceRequestor;
+interface nsINetAddr;
+interface nsITLSSocketControl;
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+native OriginAttributes(mozilla::OriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
+
+/**
+ * nsISocketTransport
+ *
+ * NOTE: Connection setup is triggered by opening an input or output stream,
+ * it does not start on its own. Completion of the connection setup is
+ * indicated by a STATUS_CONNECTED_TO notification to the event sink (if set).
+ *
+ * NOTE: This is a free-threaded interface, meaning that the methods on
+ * this interface may be called from any thread.
+ */
+[scriptable, builtinclass, uuid(79221831-85e2-43a8-8152-05d77d6fde31)]
+interface nsISocketTransport : nsITransport
+{
+ /**
+ * Get the peer's host for the underlying socket connection.
+ * For Unix domain sockets, this is a pathname, or the empty string for
+ * unnamed and abstract socket addresses.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * Get the port for the underlying socket connection.
+ * For Unix domain sockets, this is zero.
+ */
+ readonly attribute long port;
+
+ /**
+ * The origin attributes are used to create sockets. The first party domain
+ * will eventually be used to isolate OCSP cache and is only non-empty when
+ * "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ * carry origin attributes down to NSPR layers which are final consumers.
+ * It must be set before the socket transport is built.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ OriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+ /**
+ * Returns the IP address of the socket connection peer. This
+ * attribute is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getPeerAddr();
+
+ /**
+ * Returns the IP address of the initiating end. This attribute
+ * is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getSelfAddr();
+
+ /**
+ * Bind to a specific local address.
+ */
+ [noscript] void bind(in NetAddrPtr aLocalAddr);
+
+ /**
+ * Returns a scriptable version of getPeerAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptablePeerAddr();
+
+ /**
+ * Returns a scriptable version of getSelfAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptableSelfAddr();
+
+ /**
+ * TLS socket control object. This attribute is only available once the
+ * socket is connected.
+ */
+ readonly attribute nsITLSSocketControl tlsSocketControl;
+
+ /**
+ * Security notification callbacks passed to the secure socket provider
+ * via nsITLSSocketControl at socket creation time.
+ *
+ * NOTE: this attribute cannot be changed once a stream has been opened.
+ */
+ attribute nsIInterfaceRequestor securityCallbacks;
+
+ /**
+ * Test if this socket transport is (still) connected.
+ */
+ boolean isAlive();
+
+ /**
+ * Socket timeouts in seconds. To specify no timeout, pass UINT32_MAX
+ * as aValue to setTimeout. The implementation may truncate timeout values
+ * to a smaller range of values (e.g., 0 to 0xFFFF).
+ */
+ unsigned long getTimeout(in unsigned long aType);
+ void setTimeout(in unsigned long aType, in unsigned long aValue);
+
+ /**
+ * Sets the SO_LINGER option with the specified values for the l_onoff and
+ * l_linger parameters. This applies PR_SockOpt_Linger before PR_Close and
+ * can be used with a timeout of zero to send an RST packet when closing.
+ */
+ void setLinger(in boolean aPolarity, in short aTimeout);
+
+ /**
+ * True to set addr and port reuse socket options.
+ */
+ void setReuseAddrPort(in bool reuseAddrPort);
+
+ /**
+ * Values for the aType parameter passed to get/setTimeout.
+ */
+ const unsigned long TIMEOUT_CONNECT = 0;
+ const unsigned long TIMEOUT_READ_WRITE = 1;
+
+ /**
+ * nsITransportEventSink status codes.
+ *
+ * Although these look like XPCOM error codes and are passed in an nsresult
+ * variable, they are *not* error codes. Note that while they *do* overlap
+ * with existing error codes in Necko, these status codes are confined
+ * within a very limited context where no error codes may appear, so there
+ * is no ambiguity.
+ *
+ * The values of these status codes must never change.
+ *
+ * The status codes appear in near-chronological order (not in numeric
+ * order). STATUS_RESOLVING may be skipped if the host does not need to be
+ * resolved. STATUS_WAITING_FOR is an optional status code, which the impl
+ * of this interface may choose not to generate.
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_RESOLVING = 0x4b0003;
+ const unsigned long STATUS_RESOLVED = 0x4b000b;
+ const unsigned long STATUS_CONNECTING_TO = 0x4b0007;
+ const unsigned long STATUS_CONNECTED_TO = 0x4b0004;
+ const unsigned long STATUS_SENDING_TO = 0x4b0005;
+ const unsigned long STATUS_WAITING_FOR = 0x4b000a;
+ const unsigned long STATUS_RECEIVING_FROM = 0x4b0006;
+ const unsigned long STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c;
+ const unsigned long STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d;
+
+ /**
+ * connectionFlags is a bitmask that can be used to modify underlying
+ * behavior of the socket connection. See the flags below.
+ */
+ attribute unsigned long connectionFlags;
+
+ /**
+ * Values for the connectionFlags
+ *
+ * When making a new connection BYPASS_CACHE will force the Necko DNS
+ * cache entry to be refreshed with a new call to NSPR if it is set before
+ * opening the new stream.
+ */
+ const unsigned long BYPASS_CACHE = (1 << 0);
+
+ /**
+ * When setting this flag, the socket will not apply any
+ * credentials when establishing a connection. For example,
+ * an SSL connection would not send any client-certificates
+ * if this flag is set.
+ */
+ const unsigned long ANONYMOUS_CONNECT = (1 << 1);
+
+ /**
+ * If set, we will skip all IPv6 addresses the host may have and only
+ * connect to IPv4 ones.
+ */
+ const unsigned long DISABLE_IPV6 = (1 << 2);
+
+ /**
+ * If set, indicates that the connection was initiated from a source
+ * defined as being private in the sense of Private Browsing. Generally,
+ * there should be no state shared between connections that are private
+ * and those that are not; it is OK for multiple private connections
+ * to share state with each other, and it is OK for multiple non-private
+ * connections to share state with each other.
+ */
+ const unsigned long NO_PERMANENT_STORAGE = (1 << 3);
+
+ /**
+ * If set, we will skip all IPv4 addresses the host may have and only
+ * connect to IPv6 ones.
+ */
+ const unsigned long DISABLE_IPV4 = (1 << 4);
+
+ /**
+ * If set, indicates that the socket should not connect if the hostname
+ * resolves to an RFC1918 address or IPv6 equivalent.
+ */
+ const unsigned long DISABLE_RFC1918 = (1 << 5);
+
+ /**
+ * If set, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ const unsigned long BE_CONSERVATIVE = (1 << 6);
+
+ /**
+ * If set, do not use TRR for resolving the host name. Intended only for
+ * retries or other scenarios when TRR is deemed likely to have returned a
+ * wrong adddress.
+ */
+ const unsigned long DISABLE_TRR = (1 << 7);
+
+ /**
+ * Values for the connectionFlags
+ *
+ * When using BYPASS_CACHE, setting this bit will invalidate the existing
+ * cached entry immediately while the new resolve is being done to avoid
+ * other users from using stale content in the mean time.
+ */
+ const unsigned long REFRESH_CACHE = (1 << 8);
+
+ /**
+ * If this flag is set then it means that if connecting the preferred ip
+ * family has failed, retry with the oppsite one once more.
+ */
+ const unsigned long RETRY_WITH_DIFFERENT_IP_FAMILY = (1 << 9);
+
+ /**
+ * If we know that a server speaks only tls <1.3 there is no need to try
+ * to use ech.
+ */
+ const unsigned long DONT_TRY_ECH = (1 << 10);
+
+ /**
+ * These two bits encode the TRR mode of the request.
+ * Use the static helper methods convert between the TRR mode and flags.
+ */
+ const unsigned long TRR_MODE_FLAGS = (1 << 11) | (1 << 12);
+
+%{C++
+
+ static uint32_t GetFlagsFromTRRMode(nsIRequest::TRRMode aMode) {
+ return static_cast<uint32_t>(aMode) << 11;
+ }
+
+ static nsIRequest::TRRMode GetTRRModeFromFlags(uint32_t aFlags) {
+ return static_cast<nsIRequest::TRRMode>((aFlags & TRR_MODE_FLAGS) >> 11);
+ }
+%}
+
+ /**
+ * If set, we will use IP hint addresses to connect to the host.
+ */
+ const unsigned long USE_IP_HINT_ADDRESS = (1 << 13);
+
+ /**
+ * This is used for a temporary workaround for a web-compat issue. The flag is
+ * only set on CORS preflight request to allowed sending client certificates
+ * on a connection for an anonymous request.
+ */
+ const unsigned long ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT = (1 << 14);
+
+ /**
+ * If set, we've retrying after a failed connection attempt.
+ */
+ const unsigned long IS_RETRY = (1 << 15);
+
+ /**
+ * If set, this is a speculative connection.
+ */
+ const unsigned long IS_SPECULATIVE_CONNECTION = (1 << 16);
+
+ /**
+ * An opaque flags for non-standard behavior of the TLS system.
+ * It is unlikely this will need to be set outside of telemetry studies
+ * relating to the TLS implementation.
+ */
+ attribute unsigned long tlsFlags;
+
+ /**
+ * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
+ * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported
+ * services require expedited-forwarding).
+ * Not setting this value will leave the socket with the default
+ * ToS value, which on most systems if IPTOS_CLASS_CS0 (formerly
+ * IPTOS_PREC_ROUTINE).
+ */
+ attribute octet QoSBits;
+
+ /**
+ * TCP send and receive buffer sizes. A value of 0 means OS level
+ * auto-tuning is in effect.
+ */
+ attribute unsigned long recvBufferSize;
+ attribute unsigned long sendBufferSize;
+
+ /**
+ * TCP keepalive configuration (support varies by platform).
+ * Note that the attribute as well as the setter can only accessed
+ * in the socket thread.
+ */
+ attribute boolean keepaliveEnabled;
+ void setKeepaliveVals(in long keepaliveIdleTime,
+ in long keepaliveRetryInterval);
+
+ /**
+ * If true, this socket transport has found out the prefered family
+ * according it's connection flags could not be used to establish
+ * connections any more. Hence, the preference should be reset.
+ */
+ readonly attribute boolean resetIPFamilyPreference;
+
+ /**
+ * This attribute holds information whether echConfig has been used.
+ * The value is set after PR_Connect is called.
+ */
+ readonly attribute boolean echConfigUsed;
+
+ /**
+ * Called to set the echConfig to the securityInfo object.
+ */
+ void setEchConfig(in ACString echConfig);
+
+ /**
+ * IP address resolved using TRR.
+ */
+ bool resolvedByTRR();
+
+ /**
+ * Returns the effectiveTRRMode used for the DNS resolution.
+ */
+ readonly attribute nsIRequest_TRRMode effectiveTRRMode;
+
+ /**
+ * Returns the TRR skip reason used for the DNS resolution.
+ */
+ readonly attribute nsITRRSkipReason_value trrSkipReason;
+
+ /**
+ * Indicate whether this socket is created from a private window. If yes,
+ * this socket will be closed when the last private window is closed.
+ */
+ [noscript] void setIsPrivate(in boolean isPrivate);
+
+ /**
+ * If DNS is performed externally, this flag informs the caller that it may
+ * retry connecting with a different DNS configuration (e.g. different IP
+ * family preference). The flag is set only if a network error is encounder,
+ * e.g. NS_ERROR_CONNECTION_REFUSED, NS_ERROR_RESET, etc.
+ */
+ readonly attribute boolean retryDnsIfPossible;
+
+ /**
+ * Return the current status of the socket.
+ */
+ [noscript] readonly attribute nsresult status;
+};
diff --git a/netwerk/base/nsISocketTransportService.idl b/netwerk/base/nsISocketTransportService.idl
new file mode 100644
index 0000000000..64891fcfa0
--- /dev/null
+++ b/netwerk/base/nsISocketTransportService.idl
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDNSRecord;
+interface nsIFile;
+interface nsISocketTransport;
+interface nsIProxyInfo;
+interface nsIRunnable;
+
+%{C++
+class nsASocketHandler;
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+[ptr] native nsASocketHandlerPtr(nsASocketHandler);
+
+[scriptable, function, uuid(338947df-2f3b-4d24-9ce4-ecf161c1b7df)]
+interface nsISTSShutdownObserver : nsISupports {
+
+ /**
+ * Observe will be called when the SocketTransportService is shutting down,
+ * before threads are stopped.
+ */
+ void observe();
+};
+
+[builtinclass, scriptable, uuid(ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1)]
+interface nsISocketTransportService : nsISupports
+{
+ /**
+ * Creates a transport for a specified host and port.
+ *
+ * @param aSocketTypes
+ * array of socket type strings. Empty array if using default
+ * socket type.
+ * @param aHost
+ * specifies the target hostname or IP address literal of the peer
+ * for this socket.
+ * @param aPort
+ * specifies the target port of the peer for this socket.
+ * @param aProxyInfo
+ * specifies the transport-layer proxy type to use. null if no
+ * proxy. used for communicating information about proxies like
+ * SOCKS (which are transparent to upper protocols).
+ * @param aDnsRecord
+ * the dns record to be used for the connection
+ *
+ * @see nsIProxiedProtocolHandler
+ * @see nsIProtocolProxyService::GetProxyInfo
+ *
+ * NOTE: this function can be called from any thread
+ */
+ nsISocketTransport createTransport(in Array<ACString> aSocketTypes,
+ in AUTF8String aHost,
+ in long aPort,
+ in nsIProxyInfo aProxyInfo,
+ in nsIDNSRecord dnsRecord);
+
+ /**
+ * Create a transport built on a Unix domain socket, connecting to the
+ * given filename.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * The |aPath| parameter must specify an existing directory entry.
+ * Otherwise, this returns NS_ERROR_FILE_NOT_FOUND.
+ *
+ * The program must have search permission on all components of the
+ * path prefix of |aPath|, and read and write permission on |aPath|
+ * itself. Without such permission, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * The |aPath| parameter must refer to a unix-domain socket. Otherwise,
+ * this returns NS_ERROR_CONNECTION_REFUSED. (POSIX specifies
+ * ECONNREFUSED when "the target address was not listening for
+ * connections", and this is what Linux returns.)
+ *
+ * @param aPath
+ * The file name of the Unix domain socket to which we should
+ * connect.
+ */
+ nsISocketTransport createUnixDomainTransport(in nsIFile aPath);
+
+ /**
+ * Create a transport built on a Unix domain socket that uses abstract
+ * address name.
+ *
+ * If abstract socket address isn't supported on System, this returns
+ * NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aName
+ * The name of abstract socket adress of the Unix domain socket to
+ * which we should connect.
+ */
+ nsISocketTransport
+ createUnixDomainAbstractAddressTransport(in ACString aName);
+
+ /**
+ * Adds a new socket to the list of controlled sockets.
+ *
+ * This will fail with the error code NS_ERROR_NOT_AVAILABLE if the maximum
+ * number of sockets is already reached.
+ * In this case, the notifyWhenCanAttachSocket method should be used.
+ *
+ * @param aFd
+ * Open file descriptor of the socket to control.
+ * @param aHandler
+ * Socket handler that will receive notifications when the socket is
+ * ready or detached.
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void attachSocket(in PRFileDescPtr aFd,
+ in nsASocketHandlerPtr aHandler);
+
+ /**
+ * if the number of sockets reaches the limit, then consumers can be
+ * notified when the number of sockets becomes less than the limit. the
+ * notification is asynchronous, delivered via the given nsIRunnable
+ * instance on the socket transport thread.
+ *
+ * @param aEvent
+ * Event that will receive the notification when a new socket can
+ * be attached
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void notifyWhenCanAttachSocket(in nsIRunnable aEvent);
+
+ [noscript] void addShutdownObserver(in nsISTSShutdownObserver aObserver);
+ [noscript] void removeShutdownObserver(in nsISTSShutdownObserver aObserver);
+};
+
+[builtinclass, scriptable, uuid(c5204623-5b58-4a16-8b2e-67c34dd02e3f)]
+interface nsIRoutedSocketTransportService : nsISocketTransportService
+{
+ // use this instead of createTransport when you have a transport
+ // that distinguishes between origin and route (aka connection)
+ nsISocketTransport createRoutedTransport(in Array<ACString> aSocketTypes,
+ in AUTF8String aHost, // origin
+ in long aPort, // origin
+ in AUTF8String aHostRoute,
+ in long aPortRoute,
+ in nsIProxyInfo aProxyInfo,
+ in nsIDNSRecord aDnsRecord);
+};
diff --git a/netwerk/base/nsISpeculativeConnect.idl b/netwerk/base/nsISpeculativeConnect.idl
new file mode 100644
index 0000000000..e1f890a04b
--- /dev/null
+++ b/netwerk/base/nsISpeculativeConnect.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+%{C++
+namespace mozilla {
+
+class OriginAttributes;
+
+}
+%}
+
+native OriginAttributes(mozilla::OriginAttributes&&);
+
+[scriptable, builtinclass, uuid(d74a17ac-5b8a-4824-a309-b1f04a3c4aed)]
+interface nsISpeculativeConnect : nsISupports
+{
+ /**
+ * Called as a hint to indicate a new transaction for the URI is likely coming
+ * soon. The implementer may use this information to start a TCP
+ * and/or SSL level handshake for that resource immediately so that it is
+ * ready and/or progressed when the transaction is actually submitted.
+ *
+ * No obligation is taken on by the implementer, nor is the submitter obligated
+ * to actually open the new channel.
+ *
+ * @param aURI the URI of the hinted transaction
+ * @param aPrincipal the principal that will be used for opening the
+ * channel of the hinted transaction.
+ * @param aCallbacks any security callbacks for use with SSL for interfaces.
+ * May be null.
+ * @param aAnonymous indicates if this is an anonymous connection.
+ *
+ */
+ void speculativeConnect(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks,
+ in boolean aAnonymous);
+
+ /**
+ * This method is similar to speculativeConnect, but it use
+ * the partition key of the originAttributes directly to create the
+ * connection.
+ */
+ [implicit_jscontext]
+ void speculativeConnectWithOriginAttributes(
+ in nsIURI aURI,
+ in jsval originAttributes,
+ in nsIInterfaceRequestor aCallbacks,
+ in boolean aAnonymous);
+
+ [notxpcom]
+ void speculativeConnectWithOriginAttributesNative(
+ in nsIURI aURI,
+ in OriginAttributes originAttributes,
+ in nsIInterfaceRequestor aCallbacks,
+ in boolean aAnonymous);
+};
+
+/**
+ * This is used to override the default values for various values (documented
+ * inline) to determine whether or not to actually make a speculative
+ * connection.
+ */
+[uuid(1040ebe3-6ed1-45a6-8587-995e082518d7)]
+interface nsISpeculativeConnectionOverrider : nsISupports
+{
+ /**
+ * Used to determine the maximum number of unused speculative connections
+ * we will have open for a host at any one time
+ */
+ [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit;
+
+ /**
+ * Used to determine if we will ignore the existence of any currently idle
+ * connections when we decide whether or not to make a speculative
+ * connection.
+ */
+ [infallible] readonly attribute boolean ignoreIdle;
+
+ /*
+ * Used by the Predictor to gather telemetry data on speculative connection
+ * usage.
+ */
+ [infallible] readonly attribute boolean isFromPredictor;
+
+ /**
+ * by default speculative connections are not made to RFC 1918 addresses
+ */
+ [infallible] readonly attribute boolean allow1918;
+};
diff --git a/netwerk/base/nsIStandardURL.idl b/netwerk/base/nsIStandardURL.idl
new file mode 100644
index 0000000000..dae8ecc13c
--- /dev/null
+++ b/netwerk/base/nsIStandardURL.idl
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIURIMutator;
+
+/**
+ * nsIStandardURL defines the interface to an URL with the standard
+ * file path format common to protocols like http, ftp, and file.
+ * It supports initialization from a relative path and provides
+ * some customization on how URLs are normalized.
+ */
+[scriptable, builtinclass, uuid(babd6cca-ebe7-4329-967c-d6b9e33caa81)]
+interface nsIStandardURL : nsISupports
+{
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_STANDARD = 1;
+
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah://foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah://foo/bar
+ */
+ const unsigned long URLTYPE_AUTHORITY = 2;
+
+ /**
+ * blah:foo/bar => blah:///foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_NO_AUTHORITY = 3;
+};
+
+[scriptable, builtinclass, uuid(fc894e98-23a1-43cd-a7fe-72876f8ea2ee)]
+interface nsIStandardURLMutator : nsISupports
+{
+ /**
+ * Initialize a standard URL.
+ *
+ * @param aUrlType - one of the URLTYPE_ flags listed above.
+ * @param aDefaultPort - if the port parsed from the URL string matches
+ * this port, then the port will be removed from the
+ * canonical form of the URL.
+ * @param aSpec - URL string.
+ * @param aOriginCharset - the charset from which this URI string
+ * originated. this corresponds to the charset
+ * that should be used when communicating this
+ * URI to an origin server, for example. if
+ * null, then provide aBaseURI implements this
+ * interface, the origin charset of aBaseURI will
+ * be assumed, otherwise defaulting to UTF-8 (i.e.,
+ * no charset transformation from aSpec).
+ * @param aBaseURI - if null, aSpec must specify an absolute URI.
+ * otherwise, aSpec will be resolved relative
+ * to aBaseURI.
+ */
+ nsIURIMutator init(in unsigned long aUrlType,
+ in long aDefaultPort,
+ in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI);
+
+ /**
+ * Set the default port.
+ *
+ * Note: If this object is already using its default port (i.e. if it has
+ * mPort == -1), then it will now implicitly be using the new default port.
+ *
+ * @param aNewDefaultPort - if the URI has (or is later given) a port that
+ * matches this default, then we won't include a
+ * port number in the canonical form of the URL.
+ */
+ nsIURIMutator setDefaultPort(in long aNewDefaultPort);
+};
diff --git a/netwerk/base/nsIStreamListener.idl b/netwerk/base/nsIStreamListener.idl
new file mode 100644
index 0000000000..68ade60cd1
--- /dev/null
+++ b/netwerk/base/nsIStreamListener.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequestObserver.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIStreamListener
+ */
+[scriptable, uuid(3b4c8a77-76ba-4610-b316-678c73a3b88c)]
+interface nsIStreamListener : nsIRequestObserver
+{
+ /**
+ * Called when the next chunk of data (corresponding to the request) may
+ * be read without blocking the calling thread. The onDataAvailable impl
+ * must read exactly |aCount| bytes of data before returning.
+ *
+ * @param aRequest request corresponding to the source of the data
+ * @param aInputStream input stream containing the data chunk
+ * @param aOffset
+ * Number of bytes that were sent in previous onDataAvailable calls
+ * for this request. In other words, the sum of all previous count
+ * parameters.
+ * @param aCount number of bytes available in the stream
+ *
+ * NOTE: The aInputStream parameter must implement readSegments.
+ *
+ * An exception thrown from onDataAvailable has the side-effect of
+ * causing the request to be canceled.
+ */
+ void onDataAvailable(in nsIRequest aRequest,
+ in nsIInputStream aInputStream,
+ in unsigned long long aOffset,
+ in unsigned long aCount);
+};
diff --git a/netwerk/base/nsIStreamListenerTee.idl b/netwerk/base/nsIStreamListenerTee.idl
new file mode 100644
index 0000000000..2aa9c34877
--- /dev/null
+++ b/netwerk/base/nsIStreamListenerTee.idl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThreadRetargetableStreamListener.idl"
+
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+/**
+ * As data "flows" into a stream listener tee, it is copied to the output stream
+ * and then forwarded to the real listener.
+ */
+[scriptable, uuid(62b27fc1-6e8c-4225-8ad0-b9d44252973a)]
+interface nsIStreamListenerTee : nsIThreadRetargetableStreamListener
+{
+ /**
+ * Initalize the tee.
+ *
+ * @param listener
+ * the original listener the tee will propagate onStartRequest,
+ * onDataAvailable and onStopRequest notifications to, exceptions from
+ * the listener will be propagated back to the channel
+ * @param sink
+ * the stream the data coming from the channel will be written to,
+ * should be blocking
+ * @param requestObserver
+ * optional parameter, listener that gets only onStartRequest and
+ * onStopRequest notifications; exceptions threw within this optional
+ * observer are also propagated to the channel, but exceptions from
+ * the original listener (listener parameter) are privileged
+ */
+ void init(in nsIStreamListener listener,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+ /**
+ * Initalize the tee like above, but with the extra parameter to make it
+ * possible to copy the output asynchronously
+ * @param anEventTarget
+ * if set, this event-target is used to copy data to the output stream,
+ * giving an asynchronous tee
+ */
+ void initAsync(in nsIStreamListener listener,
+ in nsIEventTarget eventTarget,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+};
diff --git a/netwerk/base/nsIStreamLoader.idl b/netwerk/base/nsIStreamLoader.idl
new file mode 100644
index 0000000000..1b54f59074
--- /dev/null
+++ b/netwerk/base/nsIStreamLoader.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThreadRetargetableStreamListener.idl"
+
+interface nsIRequest;
+interface nsIStreamLoader;
+
+[scriptable, uuid(359F7990-D4E9-11d3-A1A5-0050041CAF44)]
+interface nsIStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(323bcff1-7513-4e1f-a541-1c9213c2ed1b)]
+interface nsIStreamLoader : nsIThreadRetargetableStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aStreamObserver
+ * An observer that will be notified when the data is complete.
+ * @param aRequestObserver
+ * An optional observer that will be notified when the request
+ * has started or stopped.
+ */
+ void init(in nsIStreamLoaderObserver aStreamObserver,
+ [optional] in nsIRequestObserver aRequestObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIStreamTransportService.idl b/netwerk/base/nsIStreamTransportService.idl
new file mode 100644
index 0000000000..3a1c5a4239
--- /dev/null
+++ b/netwerk/base/nsIStreamTransportService.idl
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITransport;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIInputAvailableCallback;
+
+/**
+ * This service read/writes a stream on a background thread.
+ *
+ * Note: instead of using this interface, probably you want to use
+ * NS_MakeAsyncNonBlockingInputStream.
+ *
+ * Use this service to transform any blocking stream (e.g., file stream)
+ * into a fully asynchronous stream that can be read/written without
+ * blocking the main thread.
+ */
+[builtinclass, scriptable, uuid(5e0adf7d-9785-45c3-a193-04f25a75da8f)]
+interface nsIStreamTransportService : nsISupports
+{
+ /**
+ * CreateInputTransport
+ *
+ * @param aStream
+ * The input stream that will be read on a background thread.
+ * This stream must implement "blocking" stream semantics.
+ * @param aCloseWhenDone
+ * Specify this flag to have the input stream closed once its
+ * contents have been completely read.
+ *
+ * @return nsITransport instance.
+ */
+ nsITransport createInputTransport(in nsIInputStream aStream,
+ in boolean aCloseWhenDone);
+
+ void InputAvailable(in nsIInputStream aStream,
+ in nsIInputAvailableCallback aCallback);
+};
+
+[uuid(ff2da731-44d0-4dd9-8236-c99387fec721)]
+interface nsIInputAvailableCallback : nsISupports
+{
+ void onInputAvailableComplete(in unsigned long long available,
+ in nsresult available_return_code);
+};
diff --git a/netwerk/base/nsISyncStreamListener.idl b/netwerk/base/nsISyncStreamListener.idl
new file mode 100644
index 0000000000..9a46dda787
--- /dev/null
+++ b/netwerk/base/nsISyncStreamListener.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+[scriptable, uuid(7e1aa658-6e3f-4521-9946-9685a169f764)]
+interface nsISyncStreamListener : nsIStreamListener
+{
+ /**
+ * Returns an input stream that when read will fetch data delivered to the
+ * sync stream listener. The nsIInputStream implementation will wait for
+ * OnDataAvailable events before returning from Read.
+ *
+ * NOTE: Reading from the returned nsIInputStream may spin the current
+ * thread's event queue, which could result in any event being processed.
+ */
+ readonly attribute nsIInputStream inputStream;
+};
diff --git a/netwerk/base/nsISystemProxySettings.idl b/netwerk/base/nsISystemProxySettings.idl
new file mode 100644
index 0000000000..9cd561fb64
--- /dev/null
+++ b/netwerk/base/nsISystemProxySettings.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface allows the proxy code to use platform-specific proxy
+ * settings when the proxy preference is set to "automatic discovery". This service
+ * acts like a PAC parser to netwerk, but it will actually read the system settings and
+ * either return the proper proxy data from the autoconfig URL specified in the system proxy,
+ * or generate proxy data based on the system's manual proxy settings.
+ */
+[scriptable, uuid(971591cd-277e-409a-bbf6-0a79879cd307)]
+interface nsISystemProxySettings : nsISupports
+{
+ /**
+ * Whether or not it is appropriate to execute getProxyForURI off the main thread.
+ * If that method can block (e.g. for WPAD as windows does) then it must be
+ * not mainThreadOnly to avoid creating main thread jank. The main thread only option is
+ * provided for implementations that do not block but use other main thread only
+ * functions such as dbus.
+ */
+ readonly attribute bool mainThreadOnly;
+
+ /**
+ * If non-empty, use this PAC file. If empty, call getProxyForURI instead.
+ */
+ readonly attribute AUTF8String PACURI;
+
+ /**
+ * See ProxyAutoConfig::getProxyForURI; this function behaves similarly except
+ * a more relaxed return string is allowed that includes full urls instead of just
+ * host:port syntax. e.g. "PROXY http://www.foo.com:8080" instead of
+ * "PROXY www.foo.com:8080"
+ */
+ AUTF8String getProxyForURI(in AUTF8String testSpec,
+ in AUTF8String testScheme,
+ in AUTF8String testHost,
+ in int32_t testPort);
+};
diff --git a/netwerk/base/nsITLSServerSocket.idl b/netwerk/base/nsITLSServerSocket.idl
new file mode 100644
index 0000000000..e944f23af7
--- /dev/null
+++ b/netwerk/base/nsITLSServerSocket.idl
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServerSocket.idl"
+
+interface nsIX509Cert;
+interface nsITLSServerSecurityObserver;
+interface nsISocketTransport;
+
+[scriptable, uuid(cc2c30f9-cfaa-4b8a-bd44-c24881981b74)]
+interface nsITLSServerSocket : nsIServerSocket
+{
+ /**
+ * serverCert
+ *
+ * The server's certificate that is presented to the client during the TLS
+ * handshake. This is required to be set before calling |asyncListen|.
+ */
+ attribute nsIX509Cert serverCert;
+
+ /**
+ * setSessionTickets
+ *
+ * Whether the server should support session tickets. Defaults to true. This
+ * should be set before calling |asyncListen| if you wish to change the
+ * default.
+ */
+ void setSessionTickets(in boolean aSessionTickets);
+
+ /**
+ * Values for setRequestClientCertificate
+ */
+ // Never request
+ const unsigned long REQUEST_NEVER = 0;
+ // Request (but do not require) during the first handshake only
+ const unsigned long REQUEST_FIRST_HANDSHAKE = 1;
+ // Request (but do not require) during each handshake
+ const unsigned long REQUEST_ALWAYS = 2;
+ // Require during the first handshake only
+ const unsigned long REQUIRE_FIRST_HANDSHAKE = 3;
+ // Require during each handshake
+ const unsigned long REQUIRE_ALWAYS = 4;
+
+ /**
+ * setRequestClientCertificate
+ *
+ * Whether the server should request and/or require a client auth certificate
+ * from the client. Defaults to REQUEST_NEVER. See the possible options
+ * above. This should be set before calling |asyncListen| if you wish to
+ * change the default.
+ */
+ void setRequestClientCertificate(in unsigned long aRequestClientCert);
+
+ /**
+ * setVersionRange
+ *
+ * The server's TLS versions that is used by the TLS handshake.
+ * This is required to be set before calling |asyncListen|.
+ *
+ * aMinVersion and aMaxVersion is a TLS version value from
+ * |nsITLSClientStatus| constants.
+ */
+ void setVersionRange(in unsigned short aMinVersion,
+ in unsigned short aMaxVersion);
+};
+
+/**
+ * Security summary for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server.
+ *
+ * This is accessible through the security info object on the transport, which
+ * will be an instance of |nsITLSServerConnectionInfo| (see below).
+ *
+ * The values of these attributes are available once the |onHandshakeDone|
+ * method of the security observer has been called (see
+ * |nsITLSServerSecurityObserver| below).
+ */
+[scriptable, uuid(19668ea4-e5ad-4182-9698-7e890d48f327)]
+interface nsITLSClientStatus : nsISupports
+{
+ /**
+ * peerCert
+ *
+ * The client's certificate, if one was requested via |requestCertificate|
+ * above and supplied by the client.
+ */
+ readonly attribute nsIX509Cert peerCert;
+
+ /**
+ * Values for tlsVersionUsed, as defined by TLS
+ */
+ const short SSL_VERSION_3 = 0x0300;
+ const short TLS_VERSION_1 = 0x0301;
+ const short TLS_VERSION_1_1 = 0x0302;
+ const short TLS_VERSION_1_2 = 0x0303;
+ const short TLS_VERSION_1_3 = 0x0304;
+ const short TLS_VERSION_UNKNOWN = -1;
+
+ /**
+ * tlsVersionUsed
+ *
+ * The version of TLS used by the connection. See values above.
+ */
+ readonly attribute short tlsVersionUsed;
+
+ /**
+ * cipherName
+ *
+ * Name of the cipher suite used, such as
+ * "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".
+ * See security/nss/lib/ssl/sslinfo.c for the possible values.
+ */
+ readonly attribute ACString cipherName;
+
+ /**
+ * keyLength
+ *
+ * The "effective" key size of the symmetric key in bits.
+ */
+ readonly attribute unsigned long keyLength;
+
+ /**
+ * macLength
+ *
+ * The size of the MAC in bits.
+ */
+ readonly attribute unsigned long macLength;
+};
+
+/**
+ * Connection info for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server. This object is thread-safe.
+ *
+ * This is exposed as the security info object on the transport, so it can be
+ * accessed via |transport.securityInfo|.
+ *
+ * This interface is available by the time the |onSocketAttached| is called,
+ * which is the first time the TLS server consumer is notified of a new client.
+ */
+[scriptable, uuid(8a93f5d5-eddd-4c62-a4bd-bfd297653184)]
+interface nsITLSServerConnectionInfo : nsISupports
+{
+ /**
+ * setSecurityObserver
+ *
+ * Set the security observer to be notified when the TLS handshake has
+ * completed.
+ */
+ void setSecurityObserver(in nsITLSServerSecurityObserver observer);
+
+ /**
+ * serverSocket
+ *
+ * The nsITLSServerSocket instance that accepted this client connection.
+ */
+ readonly attribute nsITLSServerSocket serverSocket;
+
+ /**
+ * status
+ *
+ * Security summary for this TLS client connection. Note that the values of
+ * this interface are not available until the TLS handshake has completed.
+ * See |nsITLSClientStatus| above for more details.
+ */
+ readonly attribute nsITLSClientStatus status;
+};
+
+[scriptable, uuid(1f62e1ae-e546-4a38-8917-d428472ed736)]
+interface nsITLSServerSecurityObserver : nsISupports
+{
+ /**
+ * onHandsakeDone
+ *
+ * This method is called once the TLS handshake is completed. This takes
+ * place after |onSocketAccepted| has been called, which typically opens the
+ * streams to keep things moving along. It's important to be aware that the
+ * handshake has not completed at the point that |onSocketAccepted| is called,
+ * so no security verification can be done until this method is called.
+ */
+ void onHandshakeDone(in nsITLSServerSocket aServer,
+ in nsITLSClientStatus aStatus);
+};
diff --git a/netwerk/base/nsIThreadRetargetableRequest.idl b/netwerk/base/nsIThreadRetargetableRequest.idl
new file mode 100644
index 0000000000..5087987567
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableRequest.idl
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISerialEventTarget;
+
+/**
+ * nsIThreadRetargetableRequest
+ *
+ * Should be implemented by requests that support retargeting delivery of
+ * data off the main thread.
+ */
+[uuid(27b84c48-5a73-4ba4-a8a4-8b5e649a145e)]
+interface nsIThreadRetargetableRequest : nsISupports
+{
+ /**
+ * Called to retarget delivery of OnDataAvailable to another thread. Should
+ * only be called before AsyncOpen for nsIWebsocketChannels, or during
+ * OnStartRequest for nsIChannels.
+ * Note: For nsIChannels, OnStartRequest and OnStopRequest will still be
+ * delivered on the main thread.
+ *
+ * @param aNewTarget New event target, e.g. thread or threadpool.
+ *
+ * Note: no return value is given. If the retargeting cannot be handled,
+ * normal delivery to the main thread will continue. As such, listeners
+ * should be ready to deal with OnDataAvailable on either the main thread or
+ * the new target thread.
+ */
+ void retargetDeliveryTo(in nsISerialEventTarget aNewTarget);
+
+ /**
+ * Returns the event target where OnDataAvailable events will be dispatched.
+ *
+ * This is only valid after OnStartRequest has been called. Any time before
+ * that point, the value may be changed by `retargetDeliveryTo` calls.
+ */
+ readonly attribute nsISerialEventTarget deliveryTarget;
+};
diff --git a/netwerk/base/nsIThreadRetargetableStreamListener.idl b/netwerk/base/nsIThreadRetargetableStreamListener.idl
new file mode 100644
index 0000000000..8d4ce396fa
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableStreamListener.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIStreamListener.idl"
+/**
+ * nsIThreadRetargetableStreamListener
+ *
+ * To be used by classes which implement nsIStreamListener and whose
+ * OnDataAvailable callback may be retargeted for delivery off the main thread.
+ */
+[scriptable, uuid(fb2304b8-f82f-4433-af68-d874a2ebbdc1)]
+interface nsIThreadRetargetableStreamListener : nsIStreamListener
+{
+ /**
+ * Checks this listener and any next listeners it may have to verify that
+ * they can receive OnDataAvailable off the main thread. It is the
+ * responsibility of the implementing class to decide on the criteria to
+ * determine if retargeted delivery of these methods is possible, but it must
+ * check any and all nsIStreamListener objects that might be called in the
+ * listener chain.
+ *
+ * An exception should be thrown if a listener in the chain does not
+ * support retargeted delivery, i.e. if the next listener does not implement
+ * nsIThreadRetargetableStreamListener, or a call to its checkListenerChain()
+ * fails.
+ */
+ void checkListenerChain();
+
+ /**
+ * Used for notifying listeners about data stop.
+ * After this notification, the listeners could potentially start processing
+ * the data. Note that onDataFinished can be called on or off the main thread.
+ * It is the responsibility of the listeners to handle this correctly.
+ *
+ * The ChannelEventQueue implementation ensures that the OnDataFinished is
+ * run on the ODA target thread after the last OnDataAvailable is executed on
+ * the ODA target thread and before OnStopRequest is called.
+ * Hence, the following order is guaranteed for the listeners, even with ODA/ODF running off MainThread.
+ * 1. OnStartRequest
+ * 2. OnDataAvailable
+ * 3. OnDataFinished
+ * 4. OnStopRequest
+ */
+ void onDataFinished(in nsresult aStatusCode);
+};
diff --git a/netwerk/base/nsIThrottledInputChannel.idl b/netwerk/base/nsIThrottledInputChannel.idl
new file mode 100644
index 0000000000..ae8d7321db
--- /dev/null
+++ b/netwerk/base/nsIThrottledInputChannel.idl
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIAsyncInputStream;
+
+/**
+ * An instance of this interface can be used to throttle the uploads
+ * of a group of associated channels.
+ */
+[scriptable, uuid(6b4b96fe-3c67-4587-af7b-58b6b17da411)]
+interface nsIInputChannelThrottleQueue : nsISupports
+{
+ /**
+ * Initialize this object with the mean and maximum bytes per
+ * second that will be allowed. Neither value may be zero, and
+ * the maximum must not be less than the mean.
+ *
+ * @param aMeanBytesPerSecond
+ * Mean number of bytes per second.
+ * @param aMaxBytesPerSecond
+ * Maximum number of bytes per second.
+ */
+ void init(in unsigned long aMeanBytesPerSecond, in unsigned long aMaxBytesPerSecond);
+
+ /**
+ * Internal use only. Get the values set by init method.
+ */
+ [noscript] readonly attribute unsigned long meanBytesPerSecond;
+ [noscript] readonly attribute unsigned long maxBytesPerSecond;
+
+
+ /**
+ * Return the number of bytes that are available to the caller in
+ * this time slice.
+ *
+ * @param aRemaining
+ * The number of bytes available to be processed
+ * @return the number of bytes allowed to be processed during this
+ * time slice; this will never be greater than aRemaining.
+ */
+ unsigned long available(in unsigned long aRemaining);
+
+ /**
+ * Record a successful read.
+ *
+ * @param aBytesRead
+ * The number of bytes actually read.
+ */
+ void recordRead(in unsigned long aBytesRead);
+
+ /**
+ * Return the number of bytes allowed through this queue. This is
+ * the sum of all the values passed to recordRead. This method is
+ * primarily useful for testing.
+ */
+ unsigned long long bytesProcessed();
+
+ /**
+ * Wrap the given input stream in a new input stream which
+ * throttles the incoming data.
+ *
+ * @param aInputStream the input stream to wrap
+ * @return a new input stream that throttles the data.
+ */
+ nsIAsyncInputStream wrapStream(in nsIInputStream aInputStream);
+};
+
+/**
+ * A throttled input channel can be managed by an
+ * nsIInputChannelThrottleQueue to limit how much data is sent during
+ * a given time slice.
+ */
+[scriptable, uuid(0a32a100-c031-45b6-9e8b-0444c7d4a143)]
+interface nsIThrottledInputChannel : nsISupports
+{
+ /**
+ * The queue that manages this channel. Multiple channels can
+ * share a single queue. A null value means that no throttling
+ * will be done.
+ */
+ attribute nsIInputChannelThrottleQueue throttleQueue;
+};
diff --git a/netwerk/base/nsITimedChannel.idl b/netwerk/base/nsITimedChannel.idl
new file mode 100644
index 0000000000..4707bf1b7a
--- /dev/null
+++ b/netwerk/base/nsITimedChannel.idl
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIArray;
+interface nsIPrincipal;
+%{C++
+namespace mozilla {
+class TimeStamp;
+}
+#include "nsTArrayForwardDeclare.h"
+#include "nsCOMPtr.h"
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+[scriptable, uuid(c2d9e95b-9cc9-4f47-9ef6-1de0cf7ebc75)]
+interface nsIServerTiming : nsISupports {
+ [must_use] readonly attribute ACString name;
+ [must_use] readonly attribute double duration;
+ [must_use] readonly attribute ACString description;
+};
+
+[ref] native nsServerTimingArrayRef(nsTArray<nsCOMPtr<nsIServerTiming>>);
+
+// All properties return zero if the value is not available
+[scriptable, uuid(ca63784d-959c-4c3a-9a59-234a2a520de0)]
+interface nsITimedChannel : nsISupports {
+ // Set this attribute to true to enable collection of timing data.
+ // channelCreationTime will be available even with this attribute set to
+ // false.
+ attribute boolean timingEnabled;
+
+ // The number of redirects
+ attribute uint8_t redirectCount;
+ attribute uint8_t internalRedirectCount;
+
+ // These properties should only be written externally when they must be
+ // propagated across an internal redirect. For example, when a service
+ // worker interception falls back to network we need to copy the original
+ // timing values to the new nsHttpChannel.
+ [noscript] attribute TimeStamp channelCreation;
+ [noscript] attribute TimeStamp asyncOpen;
+
+ // The following are only set when the request is intercepted by a service
+ // worker no matter the response is synthesized.
+ [noscript] attribute TimeStamp launchServiceWorkerStart;
+ [noscript] attribute TimeStamp launchServiceWorkerEnd;
+ [noscript] attribute TimeStamp dispatchFetchEventStart;
+ [noscript] attribute TimeStamp dispatchFetchEventEnd;
+ [noscript] attribute TimeStamp handleFetchEventStart;
+ [noscript] attribute TimeStamp handleFetchEventEnd;
+
+ // The following are only set when the document is not (only) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp domainLookupStart;
+ [noscript] readonly attribute TimeStamp domainLookupEnd;
+ [noscript] readonly attribute TimeStamp connectStart;
+ [noscript] readonly attribute TimeStamp tcpConnectEnd;
+ [noscript] readonly attribute TimeStamp secureConnectionStart;
+ [noscript] readonly attribute TimeStamp connectEnd;
+ [noscript] readonly attribute TimeStamp requestStart;
+ [noscript] readonly attribute TimeStamp responseStart;
+ [noscript] readonly attribute TimeStamp responseEnd;
+
+ // The redirect attributes timings must be writeble, se we can transfer
+ // the data from one channel to the redirected channel.
+ [noscript] attribute TimeStamp redirectStart;
+ [noscript] attribute TimeStamp redirectEnd;
+
+ // The initiator type
+ [noscript] attribute AString initiatorType;
+
+ // This flag should be set to false only if a cross-domain redirect occurred
+ [noscript] attribute boolean allRedirectsSameOrigin;
+ // This flag is set to false if the timing allow check fails
+ [noscript] attribute boolean allRedirectsPassTimingAllowCheck;
+ // Implements the timing-allow-check to determine if we should report
+ // timing info for the resourceTiming object.
+ [noscript] boolean timingAllowCheck(in nsIPrincipal origin);
+ %{C++
+ inline bool TimingAllowCheck(nsIPrincipal* aOrigin) {
+ bool allowed = false;
+ return NS_SUCCEEDED(TimingAllowCheck(aOrigin, &allowed)) && allowed;
+ }
+ %}
+
+ // The following are only set if the document is (partially) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp cacheReadStart;
+ [noscript] readonly attribute TimeStamp cacheReadEnd;
+
+ // The time when the transaction was submitted to the Connection Manager.
+ // Not reported to resource/navigation timing, only for performance telemetry.
+ [noscript] readonly attribute TimeStamp transactionPending;
+
+ // All following are PRTime versions of the above.
+ readonly attribute PRTime channelCreationTime;
+ readonly attribute PRTime asyncOpenTime;
+ readonly attribute PRTime launchServiceWorkerStartTime;
+ readonly attribute PRTime launchServiceWorkerEndTime;
+ readonly attribute PRTime dispatchFetchEventStartTime;
+ readonly attribute PRTime dispatchFetchEventEndTime;
+ readonly attribute PRTime handleFetchEventStartTime;
+ readonly attribute PRTime handleFetchEventEndTime;
+ readonly attribute PRTime domainLookupStartTime;
+ readonly attribute PRTime domainLookupEndTime;
+ readonly attribute PRTime connectStartTime;
+ readonly attribute PRTime tcpConnectEndTime;
+ readonly attribute PRTime secureConnectionStartTime;
+ readonly attribute PRTime connectEndTime;
+ readonly attribute PRTime requestStartTime;
+ readonly attribute PRTime responseStartTime;
+ readonly attribute PRTime responseEndTime;
+ readonly attribute PRTime cacheReadStartTime;
+ readonly attribute PRTime cacheReadEndTime;
+ readonly attribute PRTime redirectStartTime;
+ readonly attribute PRTime redirectEndTime;
+ // Not reported to resource/navigation timing, only for performance telemetry.
+ readonly attribute PRTime transactionPendingTime;
+
+ // If this attribute is false, this resource MUST NOT be reported in resource timing.
+ [noscript] attribute boolean reportResourceTiming;
+
+ readonly attribute nsIArray serverTiming;
+ nsServerTimingArrayRef getNativeServerTiming();
+};
diff --git a/netwerk/base/nsITraceableChannel.idl b/netwerk/base/nsITraceableChannel.idl
new file mode 100644
index 0000000000..4d325d4fcb
--- /dev/null
+++ b/netwerk/base/nsITraceableChannel.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * A channel implementing this interface allows one to intercept its data by
+ * inserting intermediate stream listeners.
+ */
+[scriptable, uuid(68167b0b-ef34-4d79-a09a-8045f7c5140e)]
+interface nsITraceableChannel : nsISupports
+{
+ /*
+ * Replace the channel's listener with a new one, and return the listener
+ * the channel used to have. The new listener intercepts OnStartRequest,
+ * OnDataAvailable and OnStopRequest calls and must pass them to
+ * the original listener after examination. If multiple callers replace
+ * the channel's listener, a chain of listeners is created.
+ * The caller of setNewListener has no way to control at which place
+ * in the chain its listener is placed.
+ *
+ * @param aMustApplyContentConversion Pass true if the new listener requires
+ * content conversion to already be applied by the channel.
+ *
+ * Note: The caller of setNewListener must not delay passing
+ * OnStartRequest to the original listener.
+ *
+ * Note2: A channel may restrict when the listener can be replaced.
+ * It is not recommended to allow listener replacement after OnStartRequest
+ * has been called.
+ */
+ nsIStreamListener setNewListener(in nsIStreamListener aListener, [optional] in boolean aMustApplyContentConversion);
+};
diff --git a/netwerk/base/nsITransport.idl b/netwerk/base/nsITransport.idl
new file mode 100644
index 0000000000..856c4e2000
--- /dev/null
+++ b/netwerk/base/nsITransport.idl
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsITransportEventSink;
+interface nsIEventTarget;
+
+/**
+ * nsITransport
+ *
+ * This interface provides a common way of accessing i/o streams connected
+ * to some resource. This interface does not in any way specify the resource.
+ * It provides methods to open blocking or non-blocking, buffered or unbuffered
+ * streams to the resource. The name "transport" is meant to connote the
+ * inherent data transfer implied by this interface (i.e., data is being
+ * transfered in some fashion via the streams exposed by this interface).
+ *
+ * A transport can have an event sink associated with it. The event sink
+ * receives transport-specific events as the transfer is occuring. For a
+ * socket transport, these events can include status about the connection.
+ * See nsISocketTransport for more info about socket transport specifics.
+ */
+[scriptable, uuid(2a8c6334-a5e6-4ec3-9865-1256541446fb)]
+interface nsITransport : nsISupports
+{
+ /**
+ * Open flags.
+ */
+ const unsigned long OPEN_BLOCKING = 1<<0;
+ const unsigned long OPEN_UNBUFFERED = 1<<1;
+
+ /**
+ * Open an input stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream has no data and is not
+ * closed, then reading from it will block the calling thread until
+ * at least one byte is available or until the stream is closed.
+ * If this flag is NOT specified, then the stream has non-blocking
+ * stream semantics. This means that if the stream has no data and is
+ * not closed, then reading from it returns NS_BASE_STREAM_WOULD_BLOCK.
+ * In addition, in non-blocking mode, the stream is guaranteed to
+ * support nsIAsyncInputStream. This interface allows the consumer of
+ * the stream to be notified when the stream can again be read.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support ReadSegments.
+ * ReadSegments is only gauranteed to be implemented when this flag is
+ * NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIInputStream openInputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Open an output stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream is full and is not closed,
+ * then writing to it will block the calling thread until ALL of the
+ * data can be written or until the stream is closed. If this flag is
+ * NOT specified, then the stream has non-blocking stream semantics.
+ * This means that if the stream is full and is not closed, then writing
+ * to it returns NS_BASE_STREAM_WOULD_BLOCK. In addition, in non-
+ * blocking mode, the stream is guaranteed to support
+ * nsIAsyncOutputStream. This interface allows the consumer of the
+ * stream to be notified when the stream can again accept more data.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support WriteSegments and
+ * WriteFrom. WriteSegments and WriteFrom are only guaranteed to be
+ * implemented when this flag is NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIOutputStream openOutputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Close the transport and any open streams.
+ *
+ * @param aReason
+ * the reason for closing the stream.
+ */
+ void close(in nsresult aReason);
+
+ /**
+ * Set the transport event sink.
+ *
+ * @param aSink
+ * receives transport layer notifications
+ * @param aEventTarget
+ * indicates the event target to which the notifications should
+ * be delivered. if NULL, then the notifications may occur on
+ * any thread.
+ */
+ void setEventSink(in nsITransportEventSink aSink,
+ in nsIEventTarget aEventTarget);
+
+ /**
+ * Generic nsITransportEventSink status codes. nsITransport
+ * implementations may override these status codes with their own more
+ * specific status codes (e.g., see nsISocketTransport).
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_READING = 0x4b0008;
+ const unsigned long STATUS_WRITING = 0x4b0009;
+};
+
+[scriptable, uuid(EDA4F520-67F7-484b-A691-8C3226A5B0A6)]
+interface nsITransportEventSink : nsISupports
+{
+ /**
+ * Transport status notification.
+ *
+ * @param aTransport
+ * the transport sending this status notification.
+ * @param aStatus
+ * the transport status. See nsISocketTransport for socket specific
+ * status codes and more comments.
+ * @param aProgress
+ * the amount of data either read or written depending on the value
+ * of the status code. this value is relative to aProgressMax.
+ * @param aProgressMax
+ * the maximum amount of data that will be read or written. if
+ * unknown, -1 will be passed.
+ */
+ void onTransportStatus(in nsITransport aTransport,
+ in nsresult aStatus,
+ in long long aProgress,
+ in long long aProgressMax);
+};
diff --git a/netwerk/base/nsIUDPSocket.idl b/netwerk/base/nsIUDPSocket.idl
new file mode 100644
index 0000000000..41bd0ebc17
--- /dev/null
+++ b/netwerk/base/nsIUDPSocket.idl
@@ -0,0 +1,407 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetAddr;
+interface nsIUDPSocketListener;
+interface nsIUDPSocketSyncListener;
+interface nsIUDPMessage;
+interface nsISocketTransport;
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIPrincipal;
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+[ref] native Uint8TArrayRef(FallibleTArray<uint8_t>);
+
+/**
+ * nsIUDPSocket
+ *
+ * An interface to a UDP socket that can accept incoming connections.
+ */
+[scriptable, uuid(d423bf4e-4499-40cf-bc03-153e2bf206d1)]
+interface nsIUDPSocket : nsISupports
+{
+ /**
+ * init
+ *
+ * This method initializes a UDP socket.
+ *
+ * @param aPort
+ * The port of the UDP socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the UDP socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [optional_argc] void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ [optional_argc] void init2(in AUTF8String aAddr,
+ in long aPort,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a UDP socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this UDP socket should be bound.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [noscript, optional_argc] void initWithAddress([const] in NetAddrPtr aAddr,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * close
+ *
+ * This method closes a UDP socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this UDP socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the UDP socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIUDPSocketListener aListener);
+
+ /**
+ * This adds a nsIUDPSocketSyncListener listener (defined below).
+ * When data is available onPacketReceived is called and the lisener uses
+ * recvWithAddr to actually retrive data from the socket.
+ * The listener can be use only if it runs on the socket thread.
+ * If it is used off the socket thread there is a risk of triggering a bug
+ * in OS thatcan cause a crash.
+ */
+ void syncListen(in nsIUDPSocketSyncListener aListener);
+
+ /**
+ * connect
+ *
+ * This method connects the UDP socket to a remote UDP address.
+ *
+ * @param aRemoteAddr
+ * The remote address to connect to
+ */
+ void connect([const] in NetAddrPtr aAddr);
+
+ /**
+ * Returns the local address of this UDP socket
+ */
+ readonly attribute nsINetAddr localAddr;
+
+ /**
+ * Returns the port of this UDP socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this UDP socket is bound. Since a
+ * UDP socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a UDP socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] NetAddr getAddress();
+
+ /**
+ * send
+ *
+ * Send out the datagram to specified remote host and port.
+ * DNS lookup will be triggered.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param data The buffer containing the data to be written.
+ * @return number of bytes written. (0 or length of data)
+ */
+ unsigned long send(in AUTF8String host, in unsigned short port,
+ in Array<uint8_t> data);
+
+ /**
+ * sendWithAddr
+ *
+ * Send out the datagram to specified remote host and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @return number of bytes written. (0 or length of data)
+ */
+ unsigned long sendWithAddr(in nsINetAddr addr,
+ in Array<uint8_t> data);
+
+
+ /**
+ * Receive a datagram.
+ * @param addr The remote host address.
+ * @param data The buffer to store received datagram.
+ */
+ [noscript] void recvWithAddr(out NetAddr addr,
+ out Array<uint8_t> data);
+
+ /**
+ * sendWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @return number of bytes written. (0 or length of data)
+ */
+ [noscript] unsigned long sendWithAddress([const] in NetAddrPtr addr,
+ [array, size_is(length), const] in uint8_t data,
+ in unsigned long length);
+
+ /**
+ * sendBinaryStream
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStream(in AUTF8String host, in unsigned short port,
+ in nsIInputStream stream);
+
+ /**
+ * sendBinaryStreamWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStreamWithAddress([const] in NetAddrPtr addr,
+ in nsIInputStream stream);
+
+ /**
+ * joinMulticast
+ *
+ * Join the multicast group specified by |addr|. You are then able to
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to join the group. If
+ * this is not specified, the OS may join the group on all interfaces
+ * or only the primary interface.
+ */
+ void joinMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void joinMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * leaveMulticast
+ *
+ * Leave the multicast group specified by |addr|. You will no longer
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to leave the group.
+ * If this is not specified, the OS may leave the group on all
+ * interfaces or only the primary interface.
+ */
+ void leaveMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void leaveMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * multicastLoopback
+ *
+ * Whether multicast datagrams sent via this socket should be looped back to
+ * this host (assuming this host has joined the relevant group). Defaults
+ * to true.
+ * Note: This is currently write-only.
+ */
+ attribute boolean multicastLoopback;
+
+ /**
+ * multicastInterface
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ attribute AUTF8String multicastInterface;
+
+ /**
+ * multicastInterfaceAddr
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ [noscript] attribute NetAddr multicastInterfaceAddr;
+
+ /**
+ * recvBufferSize
+ *
+ * The size of the receive buffer. Default depends on the OS.
+ */
+ [noscript] attribute long recvBufferSize;
+
+ /**
+ * sendBufferSize
+ *
+ * The size of the send buffer. Default depends on the OS.
+ */
+ [noscript] attribute long sendBufferSize;
+
+ /**
+ * dontFragment
+ *
+ * The don't fragment flag.
+ * The socket must be initialized before calling this function.
+ */
+ [noscript] attribute boolean dontFragment;
+};
+
+/**
+ * nsIUDPSocketListener
+ *
+ * This interface is notified whenever a UDP socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(2E4B5DD3-7358-4281-B81F-10C62EF39CB5)]
+interface nsIUDPSocketListener : nsISupports
+{
+ /**
+ * onPacketReceived
+ *
+ * This method is called when a client sends an UDP packet.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aMessage
+ * The message.
+ */
+ void onPacketReceived(in nsIUDPSocket aSocket,
+ in nsIUDPMessage aMessage);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The UDP socket is effectively dead after this notification.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aStatus
+ * The reason why the UDP socket stopped listening. If the
+ * UDP socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus);
+};
+
+/**
+ * nsIUDPMessage
+ *
+ * This interface is used to encapsulate an incomming UDP message
+ */
+[scriptable, builtinclass, uuid(afdc743f-9cc0-40d8-b442-695dc54bbb74)]
+interface nsIUDPMessage : nsISupports
+{
+ /**
+ * Address of the source of the message
+ */
+ readonly attribute nsINetAddr fromAddr;
+
+ /**
+ * Data of the message
+ */
+ readonly attribute ACString data;
+
+ /**
+ * Stream to send a response
+ */
+ readonly attribute nsIOutputStream outputStream;
+
+ /**
+ * Raw Data of the message
+ */
+ [implicit_jscontext] readonly attribute jsval rawData;
+ [noscript, notxpcom, nostdcall] Uint8TArrayRef getDataAsTArray();
+};
+
+[uuid(99f3d085-3d69-45da-a2c2-a6176af617cb)]
+interface nsIUDPSocketSyncListener : nsISupports
+{
+ /**
+ * onPacketReceived
+ *
+ * This method is called when a client sends an UDP packet.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aMessage
+ * The message.
+ */
+ void onPacketReceived(in nsIUDPSocket aSocket);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The UDP socket is effectively dead after this notification.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aStatus
+ * The reason why the UDP socket stopped listening. If the
+ * UDP socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIURI.idl b/netwerk/base/nsIURI.idl
new file mode 100644
index 0000000000..993e0374a8
--- /dev/null
+++ b/netwerk/base/nsIURI.idl
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * URIs are essentially structured names for things -- anything. This interface
+ * provides accessors to get the most basic components of an URI.
+ * If you need to change some parts of the URI use nsIURIMutator.
+ * Subclasses, including nsIURL, impose greater structure on the URI.
+ *
+ * This interface follows Tim Berners-Lee's URI spec (RFC3986) [1], where the
+ * basic URI components are defined as such:
+ * <pre>
+ * ftp://username:password@hostname:portnumber/pathname?query#ref
+ * \ / \ / \ / \ /\ / \ / \ /
+ * - --------------- ------ -------- ------- --- -
+ * | | | | | | |
+ * | | | | FilePath Query Ref
+ * | | | Port \ /
+ * | | Host / ------------
+ * | UserPass / |
+ * Scheme / Path
+ * \ /
+ * --------------------------------
+ * |
+ * PrePath
+ * </pre>
+ * The definition of the URI components has been extended to allow for
+ * internationalized domain names [2] and the more generic IRI structure [3].
+ *
+ * [1] https://tools.ietf.org/html/rfc3986
+ * [2] https://tools.ietf.org/html/rfc5890
+ * [3] https://tools.ietf.org/html/rfc3987
+ */
+
+%{C++
+#include "nsString.h"
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+
+namespace mozilla {
+class Encoding;
+namespace ipc {
+class URIParams;
+} // namespace ipc
+} // namespace mozilla
+%}
+
+[ptr] native Encoding(const mozilla::Encoding);
+[ref] native URIParams(mozilla::ipc::URIParams);
+interface nsIURIMutator;
+
+/**
+ * nsIURI - interface for an uniform resource identifier w/ i18n support.
+ *
+ * AUTF8String attributes may contain unescaped UTF-8 characters.
+ * Consumers should be careful to escape the UTF-8 strings as necessary, but
+ * should always try to "display" the UTF-8 version as provided by this
+ * interface.
+ *
+ * AUTF8String attributes may also contain escaped characters.
+ *
+ * Unescaping URI segments is unadvised unless there is intimate
+ * knowledge of the underlying charset or there is no plan to display (or
+ * otherwise enforce a charset on) the resulting URI substring.
+ *
+ * The correct way to create an nsIURI from a string is via
+ * nsIIOService.newURI.
+ *
+ * NOTE: nsBinaryInputStream::ReadObject contains a hackaround to intercept the
+ * old (pre-gecko6) nsIURI IID and swap in the current IID instead, in order
+ * for sessionstore to work after an upgrade. If this IID is revved further,
+ * we will need to add additional checks there for all intermediate IIDs, until
+ * ContentPrincipal is fixed to serialize its URIs as nsISupports (bug 662693).
+ */
+[scriptable, builtinclass, uuid(92073a54-6d78-4f30-913a-b871813208c6)]
+interface nsIURI : nsISupports
+{
+ /************************************************************************
+ * The URI is broken down into the following principal components:
+ */
+
+ /**
+ * Returns a string representation of the URI.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String spec;
+
+%{ C++
+ // An infallible wrapper for GetSpec() that returns a failure indication
+ // string if GetSpec() fails. It is most useful for creating
+ // logging/warning/error messages produced for human consumption, and when
+ // matching a URI spec against a fixed spec such as about:blank.
+ nsCString GetSpecOrDefault()
+ {
+ nsCString spec;
+ nsresult rv = GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ spec.AssignLiteral("[nsIURI::GetSpec failed]");
+ }
+ return spec;
+ }
+%}
+
+ /**
+ * The prePath (eg. scheme://user:password@host:port) returns the string
+ * before the path. This is useful for authentication or managing sessions.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String prePath;
+
+ /**
+ * The Scheme is the protocol to which this URI refers. The scheme is
+ * restricted to the US-ASCII charset per RFC3986.
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * The username:password (or username only if value doesn't contain a ':')
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String userPass;
+
+ /**
+ * The optional username and password, assuming the preHost consists of
+ * username:password.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String username;
+ readonly attribute AUTF8String password;
+
+ /**
+ * The host:port (or simply the host, if port == -1).
+ */
+ readonly attribute AUTF8String hostPort;
+
+ /**
+ * The host is the internet domain name to which this URI refers. It could
+ * be an IPv4 (or IPv6) address literal. Otherwise it is an ASCII or punycode
+ * encoded string.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * A port value of -1 corresponds to the protocol's default port (eg. -1
+ * implies port 80 for http URIs).
+ */
+ readonly attribute long port;
+
+ /**
+ * The path, typically including at least a leading '/' (but may also be
+ * empty, depending on the protocol).
+ *
+ * Some characters may be escaped.
+ *
+ * This attribute contains query and ref parts for historical reasons.
+ * Use the 'filePath' attribute if you do not want those parts included.
+ */
+ readonly attribute AUTF8String pathQueryRef;
+
+
+ /************************************************************************
+ * An URI supports the following methods:
+ */
+
+ /**
+ * URI equivalence test (not a strict string comparison).
+ *
+ * eg. http://foo.com:80/ == http://foo.com/
+ */
+ boolean equals(in nsIURI other);
+
+ /**
+ * An optimization to do scheme checks without requiring the users of nsIURI
+ * to GetScheme, thereby saving extra allocating and freeing. Returns true if
+ * the schemes match (case ignored).
+ */
+ [infallible] boolean schemeIs(in string scheme);
+
+ /**
+ * This method resolves a relative string into an absolute URI string,
+ * using this URI as the base.
+ *
+ * NOTE: some implementations may have no concept of a relative URI.
+ */
+ AUTF8String resolve(in AUTF8String relativePath);
+
+
+ /************************************************************************
+ * Additional attributes:
+ */
+
+ /**
+ * The URI spec with an ASCII compatible encoding. Host portion follows
+ * the IDNA draft spec. Other parts are URL-escaped per the rules of
+ * RFC2396. The result is strictly ASCII.
+ */
+ readonly attribute ACString asciiSpec;
+
+ /**
+ * The host:port (or simply the host, if port == -1), with an ASCII compatible
+ * encoding. Host portion follows the IDNA draft spec. The result is strictly
+ * ASCII.
+ */
+ readonly attribute ACString asciiHostPort;
+
+ /**
+ * The URI host with an ASCII compatible encoding. Follows the IDNA
+ * draft spec for converting internationalized domain names (UTF-8) to
+ * ASCII for compatibility with existing internet infrasture.
+ */
+ readonly attribute ACString asciiHost;
+
+ /************************************************************************
+ * Additional attribute & methods added for .ref support:
+ */
+
+ /**
+ * Returns the reference portion (the part after the "#") of the URI.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String ref;
+
+ /**
+ * URI equivalence test (not a strict string comparison), ignoring
+ * the value of the .ref member.
+ *
+ * eg. http://foo.com/# == http://foo.com/
+ * http://foo.com/#aaa == http://foo.com/#bbb
+ */
+ boolean equalsExceptRef(in nsIURI other);
+
+ /**
+ * returns a string for the current URI with the ref element cleared.
+ */
+ readonly attribute AUTF8String specIgnoringRef;
+
+ /**
+ * Returns if there is a reference portion (the part after the "#") of the URI.
+ */
+ readonly attribute boolean hasRef;
+
+ /**
+ * Returns if there is user and pass in the URI.
+ */
+ readonly attribute boolean hasUserPass;
+
+ /************************************************************************
+ * Additional attributes added for .query support:
+ */
+
+ /**
+ * Returns a path including the directory and file portions of a
+ * URL. For example, the filePath of "http://host/foo/bar.html#baz"
+ * is "/foo/bar.html".
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String filePath;
+
+ /**
+ * Returns the query portion (the part after the "?") of the URL.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String query;
+
+ /**
+ * Returns if there is a query portion (the part after the "?") of the URI.
+ */
+ readonly attribute boolean hasQuery;
+
+ /**
+ * If the URI has a punycode encoded hostname, this will hold the UTF8
+ * representation of that hostname (if that representation doesn't contain
+ * blacklisted characters, and the network.IDN_show_punycode pref is false)
+ * Otherwise, if the hostname is ASCII, it will return the same as .asciiHost
+ */
+ readonly attribute AUTF8String displayHost;
+
+ /**
+ * The displayHost:port (or simply the displayHost, if port == -1).
+ */
+ readonly attribute AUTF8String displayHostPort;
+
+ /**
+ * Returns the same as calling .spec, only with a UTF8 encoded hostname
+ * (if that hostname doesn't contain blacklisted characters, and
+ * the network.IDN_show_punycode pref is false)
+ */
+ readonly attribute AUTF8String displaySpec;
+
+ /**
+ * Returns the same as calling .prePath, only with a UTF8 encoded hostname
+ * (if that hostname doesn't contain blacklisted characters, and
+ * the network.IDN_show_punycode pref is false)
+ */
+ readonly attribute AUTF8String displayPrePath;
+
+ /**
+ * Returns an nsIURIMutator that can be used to make changes to the URI.
+ * After performing the setter operations on the mutator, one may call
+ * mutator.finalize() to get a new immutable URI with the desired
+ * properties.
+ */
+ nsIURIMutator mutate();
+
+ /**
+ * Serializes a URI object to a URIParams data structure in order for being
+ * passed over IPC. For deserialization, see nsIURIMutator.
+ */
+ [noscript, notxpcom] void serialize(in URIParams aParams);
+
+ %{C++
+ // MOZ_DBG support
+ friend std::ostream& operator<<(std::ostream& aOut, const nsIURI& aURI) {
+ nsIURI* uri = const_cast<nsIURI*>(&aURI);
+ return aOut << "nsIURI { " << uri->GetSpecOrDefault() << " }";
+ }
+ %}
+};
diff --git a/netwerk/base/nsIURIMutator.idl b/netwerk/base/nsIURIMutator.idl
new file mode 100644
index 0000000000..c4d9293313
--- /dev/null
+++ b/netwerk/base/nsIURIMutator.idl
@@ -0,0 +1,540 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURI;
+interface nsIObjectInputStream;
+interface nsIURIMutator;
+
+%{C++
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include <utility>
+
+#undef SetPort // XXX Windows!
+
+namespace mozilla {
+class Encoding;
+}
+
+namespace mozilla {
+namespace ipc {
+class URIParams;
+} // namespace ipc
+} // namespace mozilla
+
+template <class T>
+class BaseURIMutator
+{
+// This is the base class that can be extended by implementors of nsIURIMutator
+// in order to avoid code duplication
+// Class type T should be the type of the class that implements nsIURI
+protected:
+ virtual T* Create()
+ {
+ return new T();
+ }
+
+ [[nodiscard]] nsresult InitFromURI(T* aURI)
+ {
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv = aURI->Clone(getter_AddRefs(clone));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = static_cast<T*>(clone.get());
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult InitFromInputStream(nsIObjectInputStream* aStream)
+ {
+ RefPtr<T> uri = Create();
+ nsresult rv = uri->ReadPrivate(aStream);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult InitFromIPCParams(const mozilla::ipc::URIParams& aParams)
+ {
+ RefPtr<T> uri = Create();
+ bool ret = uri->Deserialize(aParams);
+ if (!ret) {
+ return NS_ERROR_FAILURE;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult InitFromSpec(const nsACString& aSpec)
+ {
+ nsresult rv = NS_OK;
+ RefPtr<T> uri;
+ if (mURI) {
+ // This only works because all other Init methods create a new object
+ mURI.swap(uri);
+ } else {
+ uri = Create();
+ }
+
+ rv = uri->SetSpecInternal(aSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ RefPtr<T> mURI;
+};
+
+// Since most implementations of nsIURIMutator would extend BaseURIMutator,
+// some methods would have the same implementation. We provide a useful macro
+// to avoid code duplication.
+#define NS_DEFINE_NSIMUTATOR_COMMON \
+ [[nodiscard]] NS_IMETHOD \
+ Deserialize(const mozilla::ipc::URIParams& aParams) override \
+ { \
+ return InitFromIPCParams(aParams); \
+ } \
+ \
+ [[nodiscard]] NS_IMETHOD \
+ Finalize(nsIURI** aURI) override \
+ { \
+ mURI.forget(aURI); return NS_OK; \
+ } \
+ \
+ [[nodiscard]] NS_IMETHOD \
+ SetSpec(const nsACString& aSpec, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return InitFromSpec(aSpec); \
+ } \
+
+// Implements AddRef, Release and QueryInterface for the mutator
+#define NS_IMPL_NSIURIMUTATOR_ISUPPORTS(aClass, ...) \
+ NS_IMPL_ADDREF(aClass) \
+ NS_IMPL_RELEASE(aClass) \
+ NS_IMPL_NSIURIMUTATOR_QUERY_INTERFACE(aClass, __VA_ARGS__) \
+
+// The list of interfaces is queried and an AddRef-ed pointer is returned if
+// there is a match. Otherwise, we call QueryInterface on mURI and return.
+// The reason for this specialized QueryInterface implementation is that we
+// we want to be able to instantiate the mutator for a given CID of a
+// nsIURI implementation, call nsISerializable.Read() on the mutator to
+// deserialize the URI then QueryInterface the mutator to an nsIURI interface.
+// See bug 1442239.
+// If you QueryInterface a mutator to an interface of the URI
+// implementation this is similar to calling Finalize.
+#define NS_IMPL_NSIURIMUTATOR_QUERY_INTERFACE(aClass, ...) \
+ static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \
+ "Need more arguments"); \
+ NS_INTERFACE_MAP_BEGIN(aClass) \
+ nsCOMPtr<nsIURI> uri; \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIMutator) \
+ MOZ_FOR_EACH(NS_INTERFACE_MAP_ENTRY, (), (__VA_ARGS__)) \
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { \
+ foundInterface = nullptr; \
+ } else \
+ if (mURI && \
+ NS_SUCCEEDED(mURI->QueryInterface(aIID, getter_AddRefs(uri)))) { \
+ mURI = nullptr; \
+ foundInterface = uri.get(); \
+ } else \
+ NS_INTERFACE_MAP_END \
+
+%}
+
+[ptr] native Encoding(const mozilla::Encoding);
+[ref] native const_URIParams_ref(const mozilla::ipc::URIParams);
+
+[scriptable, builtinclass, uuid(1fc53257-898b-4c5e-b69c-05bc84b4cd8f)]
+interface nsIURISetSpec : nsISupports
+{
+ /**
+ * This setter is different from all other setters because it may be used to
+ * initialize the object. We define it separately allowing mutator implementors
+ * to define it separately, while the rest of the setters may be simply
+ * forwarded to the mutable URI.
+ */
+ [must_use] nsIURIMutator setSpec(in AUTF8String aSpec);
+};
+
+/**
+ * These methods allow the mutator to change various parts of the URI.
+ * They return the same nsIURIMutator so that we may chain setter operations:
+ * Example:
+ * let newURI = uri.mutate()
+ * .setSpec("http://example.com")
+ * .setQuery("hello")
+ * .finalize();
+ */
+[scriptable, builtinclass, uuid(5403a6ec-99d7-405e-8b45-9f805bbdfcef)]
+interface nsIURISetters : nsIURISetSpec
+{
+ /**
+ * Setting the scheme outside of a protocol handler implementation is highly
+ * discouraged since that will generally lead to incorrect results.
+ */
+ [must_use] nsIURIMutator setScheme(in AUTF8String aScheme);
+ [must_use] nsIURIMutator setUserPass(in AUTF8String aUserPass);
+ [must_use] nsIURIMutator setUsername(in AUTF8String aUsername);
+ [must_use] nsIURIMutator setPassword(in AUTF8String aPassword);
+
+ /**
+ * If you setHostPort to a value that only has a host part, the port
+ * will not be reset. To reset the port set it to -1 beforehand.
+ * If setting the host succeeds, this method will return NS_OK, even if
+ * setting the port fails (error in parsing the port, or value out of range)
+ */
+ [must_use] nsIURIMutator setHostPort(in AUTF8String aHostPort);
+ [must_use] nsIURIMutator setHost(in AUTF8String aHost);
+ [must_use] nsIURIMutator setPort(in long aPort);
+ [must_use] nsIURIMutator setPathQueryRef(in AUTF8String aPathQueryRef);
+ [must_use] nsIURIMutator setRef(in AUTF8String aRef);
+ [must_use] nsIURIMutator setFilePath(in AUTF8String aFilePath);
+ [must_use] nsIURIMutator setQuery(in AUTF8String aQuery);
+ [must_use, noscript] nsIURIMutator setQueryWithEncoding(in AUTF8String query, in Encoding encoding);
+};
+
+%{C++
+
+// Using this macro instead of NS_FORWARD_SAFE_NSIURISETTERS makes chaining
+// setter operations possible.
+#define NS_FORWARD_SAFE_NSIURISETTERS_RET(_to) \
+ [[nodiscard]] NS_IMETHOD \
+ SetScheme(const nsACString& aScheme, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetScheme(aScheme); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetUserPass(const nsACString& aUserPass, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetUserPass(aUserPass); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetUsername(const nsACString& aUsername, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetUsername(aUsername); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetPassword(const nsACString& aPassword, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetPassword(aPassword); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetHostPort(const nsACString& aHostPort, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetHostPort(aHostPort); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetHost(const nsACString& aHost, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetHost(aHost); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetPort(int32_t aPort, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetPort(aPort); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetPathQueryRef(const nsACString& aPathQueryRef, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetPathQueryRef(aPathQueryRef); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetRef(const nsACString& aRef, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetRef(aRef); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetFilePath(const nsACString& aFilePath, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetFilePath(aFilePath); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetQuery(const nsACString& aQuery, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetQuery(aQuery); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetQueryWithEncoding(const nsACString& query, const mozilla::Encoding *encoding, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetQueryWithEncoding(query, encoding); \
+ } \
+
+%}
+
+[scriptable, builtinclass, uuid(4d1f3103-1c44-4dcd-b717-5d22a697a7d9)]
+interface nsIURIMutator : nsIURISetters
+{
+ /**
+ * Initalizes the URI by reading IPC URIParams.
+ * See nsIURI.
+ */
+ [noscript, notxpcom, must_use]
+ nsresult deserialize(in const_URIParams_ref aParams);
+
+ /**
+ * Finishes changing or constructing the URI and returns an immutable URI.
+ */
+ [must_use]
+ nsIURI finalize();
+};
+
+%{C++
+
+// This templated struct is used to extract the class type of the method
+template <typename Method>
+struct nsMethodTypeTraits;
+
+template <class C, typename R, typename... As>
+struct nsMethodTypeTraits<R(C::*)(As...)>
+{
+ typedef C class_type;
+};
+
+#ifdef NS_HAVE_STDCALL
+template <class C, typename R, typename... As>
+struct nsMethodTypeTraits<R(__stdcall C::*)(As...)>
+{
+ typedef C class_type;
+};
+#endif
+
+
+// This class provides a useful helper that allows chaining of setter operations
+class MOZ_STACK_CLASS NS_MutateURI
+{
+public:
+ explicit NS_MutateURI(nsIURI* aURI);
+ explicit NS_MutateURI(const char * aContractID);
+
+ explicit NS_MutateURI(nsIURIMutator* m)
+ {
+ mStatus = m ? NS_OK : NS_ERROR_NULL_POINTER;
+ mMutator = m;
+ NS_ENSURE_SUCCESS_VOID(mStatus);
+ }
+
+ NS_MutateURI& SetSpec(const nsACString& aSpec)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetSpec(aSpec, nullptr);
+ return *this;
+ }
+ NS_MutateURI& SetScheme(const nsACString& aScheme)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetScheme(aScheme, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetUserPass(const nsACString& aUserPass)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetUserPass(aUserPass, nullptr);
+ return *this;
+ }
+ NS_MutateURI& SetUsername(const nsACString& aUsername)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetUsername(aUsername, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetPassword(const nsACString& aPassword)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetPassword(aPassword, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetHostPort(const nsACString& aHostPort)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetHostPort(aHostPort, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetHost(const nsACString& aHost)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetHost(aHost, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetPort(int32_t aPort)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetPort(aPort, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetPathQueryRef(const nsACString& aPathQueryRef)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetPathQueryRef(aPathQueryRef, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetRef(const nsACString& aRef)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetRef(aRef, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetFilePath(const nsACString& aFilePath)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetFilePath(aFilePath, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetQuery(const nsACString& aQuery)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetQuery(aQuery, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetQueryWithEncoding(const nsACString& query, const mozilla::Encoding *encoding)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetQueryWithEncoding(query, encoding, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+
+ /**
+ * This method allows consumers to call the methods declared in other
+ * interfaces implemented by the mutator object.
+ *
+ * Example:
+ * nsCOMPtr<nsIURI> uri;
+ * nsresult rv = NS_MutateURI(new URIClass::Mutator())
+ * .SetSpec(aSpec)
+ * .Apply(&SomeInterface::Method, arg1, arg2)
+ * .Finalize(uri);
+ *
+ * If mMutator does not implement SomeInterface, do_QueryInterface will fail
+ * and the method will not be called.
+ * If aMethod does not exist, or if there is a mismatch between argument
+ * types, or the number of arguments, then there will be a compile error.
+ */
+ template <typename Method, typename... Args>
+ NS_MutateURI& Apply(Method aMethod, Args&&... aArgs)
+ {
+ typedef typename nsMethodTypeTraits<Method>::class_type Interface;
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ nsCOMPtr<Interface> target = do_QueryInterface(mMutator, &mStatus);
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus), "URL object must implement interface");
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ mStatus = (target->*aMethod)(std::forward<Args>(aArgs)...);
+ return *this;
+ }
+
+ template <class C>
+ [[nodiscard]] nsresult Finalize(nsCOMPtr<C>& aURI)
+ {
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ nsCOMPtr<nsIURI> uri;
+ mStatus = mMutator->Finalize(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ aURI = do_QueryInterface(uri, &mStatus);
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ // Overload for nsIURI to avoid query interface.
+ [[nodiscard]] nsresult Finalize(nsCOMPtr<nsIURI>& aURI)
+ {
+ if (NS_FAILED(mStatus)) return mStatus;
+ mStatus = mMutator->Finalize(getter_AddRefs(aURI));
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ template <class C>
+ [[nodiscard]] nsresult Finalize(C** aURI)
+ {
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ nsCOMPtr<nsIURI> uri;
+ mStatus = mMutator->Finalize(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ nsCOMPtr<C> result = do_QueryInterface(uri, &mStatus);
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ result.forget(aURI);
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult Finalize(nsIURI** aURI)
+ {
+ if (NS_FAILED(mStatus)) return mStatus;
+ mStatus = mMutator->Finalize(aURI);
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ nsresult GetStatus() { return mStatus; }
+private:
+ nsresult mStatus;
+ nsCOMPtr<nsIURIMutator> mMutator;
+};
+
+%}
diff --git a/netwerk/base/nsIURIMutatorUtils.cpp b/netwerk/base/nsIURIMutatorUtils.cpp
new file mode 100644
index 0000000000..6c7fd7c92b
--- /dev/null
+++ b/netwerk/base/nsIURIMutatorUtils.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURIMutator.h"
+#include "nsIURI.h"
+#include "nsComponentManagerUtils.h"
+
+static nsresult GetURIMutator(nsIURI* aURI, nsIURIMutator** aMutator) {
+ if (NS_WARN_IF(!aURI)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return aURI->Mutate(aMutator);
+}
+
+NS_MutateURI::NS_MutateURI(nsIURI* aURI) {
+ mStatus = GetURIMutator(aURI, getter_AddRefs(mMutator));
+ NS_ENSURE_SUCCESS_VOID(mStatus);
+}
+
+NS_MutateURI::NS_MutateURI(const char* aContractID)
+ : mStatus(NS_ERROR_NOT_INITIALIZED) {
+ mMutator = do_CreateInstance(aContractID, &mStatus);
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus), "Called with wrong aContractID");
+}
diff --git a/netwerk/base/nsIURIWithSpecialOrigin.idl b/netwerk/base/nsIURIWithSpecialOrigin.idl
new file mode 100644
index 0000000000..3b8e2e0c97
--- /dev/null
+++ b/netwerk/base/nsIURIWithSpecialOrigin.idl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsIURIWithSpecialOrigin is implemented by URIs need to supply an origin that
+ * does not match the spec. This is exclusively used in comm-central's Mailnews module.
+ */
+[scriptable, builtinclass, uuid(4f65569b-d6fc-4580-94d9-21e775658a2a)]
+interface nsIURIWithSpecialOrigin : nsISupports
+{
+ /**
+ * Special origin.
+ */
+ readonly attribute nsIURI origin;
+};
diff --git a/netwerk/base/nsIURL.idl b/netwerk/base/nsIURL.idl
new file mode 100644
index 0000000000..11a9fa0f47
--- /dev/null
+++ b/netwerk/base/nsIURL.idl
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.idl"
+interface nsIURIMutator;
+
+/**
+ * The nsIURL interface provides convenience methods that further
+ * break down the path portion of nsIURI:
+ *
+ * http://host/directory/fileBaseName.fileExtension?query
+ * http://host/directory/fileBaseName.fileExtension#ref
+ * \ \ /
+ * \ -----------------------
+ * \ | /
+ * \ fileName /
+ * ----------------------------
+ * |
+ * filePath
+ */
+[scriptable, builtinclass, uuid(86adcd89-0b70-47a2-b0fe-5bb2c5f37e31)]
+interface nsIURL : nsIURI
+{
+ /*************************************************************************
+ * The URL path is broken down into the following principal components:
+ *
+ * attribute AUTF8String filePath;
+ * attribute AUTF8String query;
+ *
+ * These are inherited from nsIURI.
+ */
+
+ /*************************************************************************
+ * The URL filepath is broken down into the following sub-components:
+ */
+
+ /**
+ * Returns the directory portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. If the trailing slash is omitted, then the Directory
+ * is /foo/ and the file is bar (i.e. this is a syntactic, not a semantic
+ * breakdown of the Path). And hence don't rely on this for something to
+ * be a definitely be a file. But you can get just the leading directory
+ * portion for sure.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String directory;
+
+ /**
+ * Returns the file name portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. Note that this is purely based on searching for the
+ * last trailing slash. And hence don't rely on this to be a definite file.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String fileName;
+
+ /*************************************************************************
+ * The URL filename is broken down even further:
+ */
+
+ /**
+ * Returns the file basename portion of a filename in a url.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String fileBaseName;
+
+ /**
+ * Returns the file extension portion of a filename in a url. If a file
+ * extension does not exist, the empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String fileExtension;
+
+ /**
+ * This method takes a uri and compares the two. The common uri portion
+ * is returned as a string. The minimum common uri portion is the
+ * protocol, and any of these if present: login, password, host and port
+ * If no commonality is found, "" is returned. If they are identical, the
+ * whole path with file/ref/etc. is returned. For file uris, it is
+ * expected that the common spec would be at least "file:///" since '/' is
+ * a shared common root.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ ""
+ * 2) http://foo.com/bar/ ftp://foo.com/bar/ ""
+ * 3) http://foo.com:8080/ http://foo.com/bar/ ""
+ * 4) ftp://user@foo.com/ ftp://user:pw@foo.com/ ""
+ * 5) ftp://foo.com/bar/ ftp://foo.com/bar ftp://foo.com/
+ * 6) ftp://foo.com/bar/ ftp://foo.com/bar/b.html ftp://foo.com/bar/
+ * 7) http://foo.com/a.htm#i http://foo.com/b.htm http://foo.com/
+ * 8) ftp://foo.com/c.htm#i ftp://foo.com/c.htm ftp://foo.com/c.htm
+ * 9) file:///a/b/c.html file:///d/e/c.html file:///
+ */
+ AUTF8String getCommonBaseSpec(in nsIURI aURIToCompare);
+
+ /**
+ * This method tries to create a string which specifies the location of the
+ * argument relative to |this|. If the argument and |this| are equal, the
+ * method returns "". If any of the URIs' scheme, host, userpass, or port
+ * don't match, the method returns the full spec of the argument.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ http://www.mozilla.org/
+ * 2) http://mozilla.org/ http://www.mozilla.org http://www.mozilla.org/
+ * 3) http://foo.com/bar/ http://foo.com:80/bar/ ""
+ * 4) http://foo.com/ http://foo.com/a.htm#b a.html#b
+ * 5) http://foo.com/a/b/ http://foo.com/c ../../c
+ */
+ AUTF8String getRelativeSpec(in nsIURI aURIToCompare);
+
+};
+
+[scriptable, builtinclass, uuid(25072eb8-f1e6-482f-9ca9-eddd3d65169a)]
+interface nsIURLMutator : nsISupports
+{
+ [must_use] nsIURIMutator setFileName(in AUTF8String aFileName);
+ [must_use] nsIURIMutator setFileBaseName(in AUTF8String aFileBaseName);
+ [must_use] nsIURIMutator setFileExtension(in AUTF8String aFileExtension);
+};
diff --git a/netwerk/base/nsIURLParser.idl b/netwerk/base/nsIURLParser.idl
new file mode 100644
index 0000000000..3d6ac19b8c
--- /dev/null
+++ b/netwerk/base/nsIURLParser.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIURLParser specifies the interface to an URL parser that attempts to
+ * follow the definitions of RFC 2396.
+ */
+[scriptable, uuid(78c5d19f-f5d2-4732-8d3d-d5a7d7133bc0)]
+interface nsIURLParser : nsISupports
+{
+ /**
+ * The string to parse in the following methods may be given as a null
+ * terminated string, in which case the length argument should be -1.
+ *
+ * Out parameters of the following methods are all optional (ie. the caller
+ * may pass-in a NULL value if the corresponding results are not needed).
+ * Signed out parameters may hold a value of -1 if the corresponding result
+ * is not part of the string being parsed.
+ *
+ * The parsing routines attempt to be as forgiving as possible.
+ */
+
+ /**
+ * ParseSpec breaks the URL string up into its 3 major components: a scheme,
+ * an authority section (hostname, etc.), and a path.
+ *
+ * spec = <scheme>://<authority><path>
+ */
+ void parseURL (in string spec, in long specLen,
+ out unsigned long schemePos, out long schemeLen,
+ out unsigned long authorityPos, out long authorityLen,
+ out unsigned long pathPos, out long pathLen);
+
+ /**
+ * ParseAuthority breaks the authority string up into its 4 components:
+ * username, password, hostname, and hostport.
+ *
+ * auth = <username>:<password>@<hostname>:<port>
+ */
+ void parseAuthority (in string authority, in long authorityLen,
+ out unsigned long usernamePos, out long usernameLen,
+ out unsigned long passwordPos, out long passwordLen,
+ out unsigned long hostnamePos, out long hostnameLen,
+ out long port);
+
+ /**
+ * userinfo = <username>:<password>
+ */
+ void parseUserInfo (in string userinfo, in long userinfoLen,
+ out unsigned long usernamePos, out long usernameLen,
+ out unsigned long passwordPos, out long passwordLen);
+
+ /**
+ * serverinfo = <hostname>:<port>
+ */
+ void parseServerInfo (in string serverinfo, in long serverinfoLen,
+ out unsigned long hostnamePos, out long hostnameLen,
+ out long port);
+
+ /**
+ * ParsePath breaks the path string up into its 3 major components: a file path,
+ * a query string, and a reference string.
+ *
+ * path = <filepath>?<query>#<ref>
+ */
+ void parsePath (in string path, in long pathLen,
+ out unsigned long filepathPos, out long filepathLen,
+ out unsigned long queryPos, out long queryLen,
+ out unsigned long refPos, out long refLen);
+
+ /**
+ * ParseFilePath breaks the file path string up into: the directory portion,
+ * file base name, and file extension.
+ *
+ * filepath = <directory><basename>.<extension>
+ */
+ void parseFilePath (in string filepath, in long filepathLen,
+ out unsigned long directoryPos, out long directoryLen,
+ out unsigned long basenamePos, out long basenameLen,
+ out unsigned long extensionPos, out long extensionLen);
+
+ /**
+ * filename = <basename>.<extension>
+ */
+ void parseFileName (in string filename, in long filenameLen,
+ out unsigned long basenamePos, out long basenameLen,
+ out unsigned long extensionPos, out long extensionLen);
+};
+
+%{C++
+// url parser key for use with the category manager
+// mapping from scheme to url parser.
+#define NS_IURLPARSER_KEY "@mozilla.org/urlparser;1"
+%}
diff --git a/netwerk/base/nsIUploadChannel.idl b/netwerk/base/nsIUploadChannel.idl
new file mode 100644
index 0000000000..1c059e8869
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIUploadChannel
+ *
+ * A channel may optionally implement this interface if it supports the
+ * notion of uploading a data stream. The upload stream may only be set
+ * prior to the invocation of asyncOpen on the channel.
+ */
+[scriptable, uuid(5cfe15bd-5adb-4a7f-9e55-4f5a67d15794)]
+interface nsIUploadChannel : nsISupports
+{
+ /**
+ * Sets a stream to be uploaded by this channel.
+ *
+ * Most implementations of this interface require that the stream:
+ * (1) implement threadsafe addRef and release
+ * (2) implement nsIInputStream::readSegments
+ * (3) implement nsISeekableStream::seek
+ *
+ * History here is that we need to support both streams that already have
+ * headers (e.g., Content-Type and Content-Length) information prepended to
+ * the stream (by plugins) as well as clients (composer, uploading
+ * application) that want to upload data streams without any knowledge of
+ * protocol specifications. For this reason, we have a special meaning
+ * for the aContentType parameter (see below).
+ *
+ * @param aStream
+ * The stream to be uploaded by this channel.
+ * @param aContentType
+ * If aContentType is empty, the protocol will assume that no
+ * content headers are to be added to the uploaded stream and that
+ * any required headers are already encoded in the stream. In the
+ * case of HTTP, if this parameter is non-empty, then its value will
+ * replace any existing Content-Type header on the HTTP request.
+ * In the case of FTP and FILE, this parameter is ignored.
+ * @param aContentLength
+ * A value of -1 indicates that the length of the stream should be
+ * determined by calling the stream's |available| method.
+ */
+ void setUploadStream(in nsIInputStream aStream,
+ in ACString aContentType,
+ in long long aContentLength);
+
+ /**
+ * Get the stream (to be) uploaded by this channel.
+ */
+ readonly attribute nsIInputStream uploadStream;
+};
diff --git a/netwerk/base/nsIUploadChannel2.idl b/netwerk/base/nsIUploadChannel2.idl
new file mode 100644
index 0000000000..45f1d76682
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel2.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIRunnable;
+
+[scriptable, uuid(2f712b52-19c5-4e0c-9e8f-b5c7c3b67049)]
+interface nsIUploadChannel2 : nsISupports
+{
+ /**
+ * Sets a stream to be uploaded by this channel with the specified
+ * Content-Type and Content-Length header values.
+ *
+ * Most implementations of this interface require that the stream:
+ * (1) implement threadsafe addRef and release
+ * (2) implement nsIInputStream::readSegments
+ * (3) implement nsISeekableStream::seek
+ *
+ * @param aStream
+ * The stream to be uploaded by this channel.
+ * @param aContentType
+ * This value will replace any existing Content-Type
+ * header on the HTTP request, regardless of whether
+ * or not its empty.
+ * @param aContentLength
+ * A value of -1 indicates that the length of the stream should be
+ * determined by calling the stream's |available| method.
+ * @param aMethod
+ * The HTTP request method to set on the stream.
+ * @param aStreamHasHeaders
+ * True if the stream already contains headers for the HTTP request.
+ */
+ void explicitSetUploadStream(in nsIInputStream aStream,
+ in ACString aContentType,
+ in long long aContentLength,
+ in ACString aMethod,
+ in boolean aStreamHasHeaders);
+
+ /**
+ * Value of aStreamHasHeaders from the last successful call to
+ * explicitSetUploadStream. TRUE indicates the attached upload stream
+ * contains request headers.
+ */
+ readonly attribute boolean uploadStreamHasHeaders;
+
+ /**
+ * Clones the upload stream. May only be called in the parent process.
+ * aContentLength could be -1 in case the size of the stream is unknown,
+ * otherwise it will contain the known size of the stream.
+ */
+ [noscript]
+ nsIInputStream cloneUploadStream(out long long aContentLength);
+};
diff --git a/netwerk/base/nsIncrementalDownload.cpp b/netwerk/base/nsIncrementalDownload.cpp
new file mode 100644
index 0000000000..d7cb658092
--- /dev/null
+++ b/netwerk/base/nsIncrementalDownload.cpp
@@ -0,0 +1,878 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsIIncrementalDownload.h"
+#include "nsIRequestObserver.h"
+#include "nsIProgressEventSink.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsIHttpChannel.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsWeakReference.h"
+#include "prio.h"
+#include "prprf.h"
+#include <algorithm>
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/UniquePtr.h"
+
+// Default values used to initialize a nsIncrementalDownload object.
+#define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
+#define DEFAULT_INTERVAL 60 // seconds
+
+#define UPDATE_PROGRESS_INTERVAL PRTime(100 * PR_USEC_PER_MSEC) // 100ms
+
+// Number of times to retry a failed byte-range request.
+#define MAX_RETRY_COUNT 20
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+static LazyLogModule gIDLog("IncrementalDownload");
+#undef LOG
+#define LOG(args) MOZ_LOG(gIDLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+
+static nsresult WriteToFile(nsIFile* lf, const char* data, uint32_t len,
+ int32_t flags) {
+ PRFileDesc* fd;
+ int32_t mode = 0600;
+ nsresult rv;
+ rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
+ if (NS_FAILED(rv)) return rv;
+
+ if (len) {
+ rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ PR_Close(fd);
+ return rv;
+}
+
+static nsresult AppendToFile(nsIFile* lf, const char* data, uint32_t len) {
+ int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
+ return WriteToFile(lf, data, len, flags);
+}
+
+// maxSize may be -1 if unknown
+static void MakeRangeSpec(const int64_t& size, const int64_t& maxSize,
+ int32_t chunkSize, bool fetchRemaining,
+ nsCString& rangeSpec) {
+ rangeSpec.AssignLiteral("bytes=");
+ rangeSpec.AppendInt(int64_t(size));
+ rangeSpec.Append('-');
+
+ if (fetchRemaining) return;
+
+ int64_t end = size + int64_t(chunkSize);
+ if (maxSize != int64_t(-1) && end > maxSize) end = maxSize;
+ end -= 1;
+
+ rangeSpec.AppendInt(int64_t(end));
+}
+
+//-----------------------------------------------------------------------------
+
+class nsIncrementalDownload final : public nsIIncrementalDownload,
+ public nsIStreamListener,
+ public nsIObserver,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsSupportsWeakReference,
+ public nsIAsyncVerifyRedirectCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINCREMENTALDOWNLOAD
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ nsIncrementalDownload() = default;
+
+ private:
+ ~nsIncrementalDownload() = default;
+ nsresult FlushChunk();
+ void UpdateProgress();
+ nsresult CallOnStartRequest();
+ void CallOnStopRequest();
+ nsresult StartTimer(int32_t interval);
+ nsresult ProcessTimeout();
+ nsresult ReadCurrentSize();
+ nsresult ClearRequestHeader(nsIHttpChannel* channel);
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mFinalURI;
+ nsCOMPtr<nsIFile> mDest;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsITimer> mTimer;
+ mozilla::UniquePtr<char[]> mChunk;
+ int32_t mChunkLen{0};
+ int32_t mChunkSize{DEFAULT_CHUNK_SIZE};
+ int32_t mInterval{DEFAULT_INTERVAL};
+ int64_t mTotalSize{-1};
+ int64_t mCurrentSize{-1};
+ uint32_t mLoadFlags{LOAD_NORMAL};
+ int32_t mNonPartialCount{0};
+ nsresult mStatus{NS_OK};
+ bool mIsPending{false};
+ bool mDidOnStartRequest{false};
+ PRTime mLastProgressUpdate{0};
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIChannel> mNewRedirectChannel;
+ nsCString mPartialValidator;
+ bool mCacheBust{false};
+
+ // nsITimerCallback is implemented on a subclass so that the name attribute
+ // doesn't conflict with the name attribute of the nsIRequest interface.
+ class TimerCallback final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit TimerCallback(nsIncrementalDownload* aIncrementalDownload);
+
+ private:
+ ~TimerCallback() = default;
+
+ RefPtr<nsIncrementalDownload> mIncrementalDownload;
+ };
+};
+
+nsresult nsIncrementalDownload::FlushChunk() {
+ NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
+
+ if (mChunkLen == 0) return NS_OK;
+
+ nsresult rv = AppendToFile(mDest, mChunk.get(), mChunkLen);
+ if (NS_FAILED(rv)) return rv;
+
+ mCurrentSize += int64_t(mChunkLen);
+ mChunkLen = 0;
+
+ return NS_OK;
+}
+
+void nsIncrementalDownload::UpdateProgress() {
+ mLastProgressUpdate = PR_Now();
+
+ if (mProgressSink) {
+ mProgressSink->OnProgress(this, mCurrentSize + mChunkLen, mTotalSize);
+ }
+}
+
+nsresult nsIncrementalDownload::CallOnStartRequest() {
+ if (!mObserver || mDidOnStartRequest) return NS_OK;
+
+ mDidOnStartRequest = true;
+ return mObserver->OnStartRequest(this);
+}
+
+void nsIncrementalDownload::CallOnStopRequest() {
+ if (!mObserver) return;
+
+ // Ensure that OnStartRequest is always called once before OnStopRequest.
+ nsresult rv = CallOnStartRequest();
+ if (NS_SUCCEEDED(mStatus)) mStatus = rv;
+
+ mIsPending = false;
+
+ mObserver->OnStopRequest(this, mStatus);
+ mObserver = nullptr;
+}
+
+nsresult nsIncrementalDownload::StartTimer(int32_t interval) {
+ auto callback = MakeRefPtr<TimerCallback>(this);
+ return NS_NewTimerWithCallback(getter_AddRefs(mTimer), callback,
+ interval * 1000, nsITimer::TYPE_ONE_SHOT);
+}
+
+nsresult nsIncrementalDownload::ProcessTimeout() {
+ NS_ASSERTION(!mChannel, "how can we have a channel?");
+
+ // Handle existing error conditions
+ if (NS_FAILED(mStatus)) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ // Fetch next chunk
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(
+ getter_AddRefs(channel), mFinalURI, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ this, // aCallbacks
+ mLoadFlags);
+
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(mCurrentSize != int64_t(-1),
+ "we should know the current file size by now");
+
+ rv = ClearRequestHeader(http);
+ if (NS_FAILED(rv)) return rv;
+
+ // Don't bother making a range request if we are just going to fetch the
+ // entire document.
+ if (mInterval || mCurrentSize != int64_t(0)) {
+ nsAutoCString range;
+ MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
+
+ rv = http->SetRequestHeader("Range"_ns, range, false);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mPartialValidator.IsEmpty()) {
+ rv = http->SetRequestHeader("If-Range"_ns, mPartialValidator, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::ProcessTimeout\n"
+ " failed to set request header: If-Range\n"));
+ }
+ }
+
+ if (mCacheBust) {
+ rv = http->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::ProcessTimeout\n"
+ " failed to set request header: If-Range\n"));
+ }
+ rv = http->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::ProcessTimeout\n"
+ " failed to set request header: If-Range\n"));
+ }
+ }
+ }
+
+ rv = channel->AsyncOpen(this);
+ if (NS_FAILED(rv)) return rv;
+
+ // Wait to assign mChannel when we know we are going to succeed. This is
+ // important because we don't want to introduce a reference cycle between
+ // mChannel and this until we know for a fact that AsyncOpen has succeeded,
+ // thus ensuring that our stream listener methods will be invoked.
+ mChannel = channel;
+ return NS_OK;
+}
+
+// Reads the current file size and validates it.
+nsresult nsIncrementalDownload::ReadCurrentSize() {
+ int64_t size;
+ nsresult rv = mDest->GetFileSize((int64_t*)&size);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ mCurrentSize = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ mCurrentSize = size;
+ return NS_OK;
+}
+
+// nsISupports
+
+NS_IMPL_ISUPPORTS(nsIncrementalDownload, nsIIncrementalDownload, nsIRequest,
+ nsIStreamListener, nsIRequestObserver, nsIObserver,
+ nsIInterfaceRequestor, nsIChannelEventSink,
+ nsISupportsWeakReference, nsIAsyncVerifyRedirectCallback)
+
+// nsIRequest
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetName(nsACString& name) {
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+
+ return mURI->GetSpec(name);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::IsPending(bool* isPending) {
+ *isPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetStatus(nsresult* status) {
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIncrementalDownload::SetCanceledReason(
+ const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIncrementalDownload::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIncrementalDownload::CancelWithReason(
+ nsresult aStatus, const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Cancel(nsresult status) {
+ NS_ENSURE_ARG(NS_FAILED(status));
+
+ // Ignore this cancelation if we're already canceled.
+ if (NS_FAILED(mStatus)) return NS_OK;
+
+ mStatus = status;
+
+ // Nothing more to do if callbacks aren't pending.
+ if (!mIsPending) return NS_OK;
+
+ if (mChannel) {
+ mChannel->Cancel(mStatus);
+ NS_ASSERTION(!mTimer, "what is this timer object doing here?");
+ } else {
+ // dispatch a timer callback event to drive invoking our listener's
+ // OnStopRequest.
+ if (mTimer) mTimer->Cancel();
+ StartTimer(0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadFlags(nsLoadFlags* loadFlags) {
+ *loadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) {
+ mLoadFlags = loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadGroup(nsILoadGroup** loadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadGroup(nsILoadGroup* loadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIIncrementalDownload
+
+NS_IMETHODIMP
+nsIncrementalDownload::Init(nsIURI* uri, nsIFile* dest, int32_t chunkSize,
+ int32_t interval) {
+ // Keep it simple: only allow initialization once
+ NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
+
+ mDest = dest;
+ NS_ENSURE_ARG(mDest);
+
+ mURI = uri;
+ mFinalURI = uri;
+
+ if (chunkSize > 0) mChunkSize = chunkSize;
+ if (interval >= 0) mInterval = interval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetURI(nsIURI** result) {
+ nsCOMPtr<nsIURI> uri = mURI;
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetFinalURI(nsIURI** result) {
+ nsCOMPtr<nsIURI> uri = mFinalURI;
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetDestination(nsIFile** result) {
+ if (!mDest) {
+ *result = nullptr;
+ return NS_OK;
+ }
+ // Return a clone of mDest so that callers may modify the resulting nsIFile
+ // without corrupting our internal object. This also works around the fact
+ // that some nsIFile impls may cache the result of stat'ing the filesystem.
+ return mDest->Clone(result);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetTotalSize(int64_t* result) {
+ *result = mTotalSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetCurrentSize(int64_t* result) {
+ *result = mCurrentSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Start(nsIRequestObserver* observer,
+ nsISupports* context) {
+ NS_ENSURE_ARG(observer);
+ NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
+
+ // Observe system shutdown so we can be sure to release any reference held
+ // between ourselves and the timer. We have the observer service hold a weak
+ // reference to us, so that we don't have to worry about calling
+ // RemoveObserver. XXX(darin): The timer code should do this for us.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+
+ nsresult rv = ReadCurrentSize();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = StartTimer(0);
+ if (NS_FAILED(rv)) return rv;
+
+ mObserver = observer;
+ mProgressSink = do_QueryInterface(observer); // ok if null
+
+ mIsPending = true;
+ return NS_OK;
+}
+
+// nsIRequestObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStartRequest(nsIRequest* request) {
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Ensure that we are receiving a 206 response.
+ uint32_t code;
+ rv = http->GetResponseStatus(&code);
+ if (NS_FAILED(rv)) return rv;
+ if (code != 206) {
+ // We may already have the entire file downloaded, in which case
+ // our request for a range beyond the end of the file would have
+ // been met with an error response code.
+ if (code == 416 && mTotalSize == int64_t(-1)) {
+ mTotalSize = mCurrentSize;
+ // Return an error code here to suppress OnDataAvailable.
+ return NS_ERROR_DOWNLOAD_COMPLETE;
+ }
+ // The server may have decided to give us all of the data in one chunk. If
+ // we requested a partial range, then we don't want to download all of the
+ // data at once. So, we'll just try again, but if this keeps happening then
+ // we'll eventually give up.
+ if (code == 200) {
+ if (mInterval) {
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ // Since we have been asked to download the rest of the file, we can deal
+ // with a 200 response. This may result in downloading the beginning of
+ // the file again, but that can't really be helped.
+ } else {
+ NS_WARNING("server response was unexpected");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // We got a partial response, so clear this counter in case the next chunk
+ // results in a 200 response.
+ mNonPartialCount = 0;
+
+ // confirm that the content-range response header is consistent with
+ // expectations on each 206. If it is not then drop this response and
+ // retry with no-cache set.
+ if (!mCacheBust) {
+ nsAutoCString buf;
+ int64_t startByte = 0;
+ bool confirmedOK = false;
+
+ rv = http->GetResponseHeader("Content-Range"_ns, buf);
+ if (NS_FAILED(rv)) {
+ return rv; // it isn't a useful 206 without a CONTENT-RANGE of some
+ }
+ // sort
+
+ // Content-Range: bytes 0-299999/25604694
+ int32_t p = buf.Find("bytes ");
+
+ // first look for the starting point of the content-range
+ // to make sure it is what we expect
+ if (p != -1) {
+ char* endptr = nullptr;
+ const char* s = buf.get() + p + 6;
+ while (*s && *s == ' ') s++;
+ startByte = strtol(s, &endptr, 10);
+
+ if (*s && endptr && (endptr != s) && (mCurrentSize == startByte)) {
+ // ok the starting point is confirmed. We still need to check the
+ // total size of the range for consistency if this isn't
+ // the first chunk
+ if (mTotalSize == int64_t(-1)) {
+ // first chunk
+ confirmedOK = true;
+ } else {
+ int32_t slash = buf.FindChar('/');
+ int64_t rangeSize = 0;
+ if (slash != kNotFound &&
+ (PR_sscanf(buf.get() + slash + 1, "%lld",
+ (int64_t*)&rangeSize) == 1) &&
+ rangeSize == mTotalSize) {
+ confirmedOK = true;
+ }
+ }
+ }
+ }
+
+ if (!confirmedOK) {
+ NS_WARNING("unexpected content-range");
+ mCacheBust = true;
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ }
+ }
+
+ // Do special processing after the first response.
+ if (mTotalSize == int64_t(-1)) {
+ // Update knowledge of mFinalURI
+ rv = http->GetURI(getter_AddRefs(mFinalURI));
+ if (NS_FAILED(rv)) return rv;
+ Unused << http->GetResponseHeader("Etag"_ns, mPartialValidator);
+ if (StringBeginsWith(mPartialValidator, "W/"_ns)) {
+ mPartialValidator.Truncate(); // don't use weak validators
+ }
+ if (mPartialValidator.IsEmpty()) {
+ rv = http->GetResponseHeader("Last-Modified"_ns, mPartialValidator);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::OnStartRequest\n"
+ " empty validator\n"));
+ }
+ }
+
+ if (code == 206) {
+ // OK, read the Content-Range header to determine the total size of this
+ // download file.
+ nsAutoCString buf;
+ rv = http->GetResponseHeader("Content-Range"_ns, buf);
+ if (NS_FAILED(rv)) return rv;
+ int32_t slash = buf.FindChar('/');
+ if (slash == kNotFound) {
+ NS_WARNING("server returned invalid Content-Range header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t*)&mTotalSize) !=
+ 1) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ rv = http->GetContentLength(&mTotalSize);
+ if (NS_FAILED(rv)) return rv;
+ // We need to know the total size of the thing we're trying to download.
+ if (mTotalSize == int64_t(-1)) {
+ NS_WARNING("server returned no content-length header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Need to truncate (or create, if it doesn't exist) the file since we
+ // are downloading the whole thing.
+ WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
+ mCurrentSize = 0;
+ }
+
+ // Notify observer that we are starting...
+ rv = CallOnStartRequest();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
+ int64_t diff = mTotalSize - mCurrentSize;
+ if (diff <= int64_t(0)) {
+ NS_WARNING("about to set a bogus chunk size; giving up");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (diff < int64_t(mChunkSize)) mChunkSize = uint32_t(diff);
+
+ mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize);
+ if (!mChunk) rv = NS_ERROR_OUT_OF_MEMORY;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStopRequest(nsIRequest* request, nsresult status) {
+ // Not a real error; just a trick to kill off the channel without our
+ // listener having to care.
+ if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL) return NS_OK;
+
+ // Not a real error; just a trick used to suppress OnDataAvailable calls.
+ if (status == NS_ERROR_DOWNLOAD_COMPLETE) status = NS_OK;
+
+ if (NS_SUCCEEDED(mStatus)) mStatus = status;
+
+ if (mChunk) {
+ if (NS_SUCCEEDED(mStatus)) mStatus = FlushChunk();
+
+ mChunk = nullptr; // deletes memory
+ mChunkLen = 0;
+ UpdateProgress();
+ }
+
+ mChannel = nullptr;
+
+ // Notify listener if we hit an error or finished
+ if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ return StartTimer(mInterval); // Do next chunk
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* input, uint64_t offset,
+ uint32_t count) {
+ while (count) {
+ uint32_t space = mChunkSize - mChunkLen;
+ uint32_t n, len = std::min(space, count);
+
+ nsresult rv = input->Read(&mChunk[mChunkLen], len, &n);
+ if (NS_FAILED(rv)) return rv;
+ if (n != len) return NS_ERROR_UNEXPECTED;
+
+ count -= n;
+ mChunkLen += n;
+
+ if (mChunkLen == mChunkSize) {
+ rv = FlushChunk();
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL) {
+ UpdateProgress();
+ }
+
+ return NS_OK;
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ Cancel(NS_ERROR_ABORT);
+
+ // Since the app is shutting down, we need to go ahead and notify our
+ // observer here. Otherwise, we would notify them after XPCOM has been
+ // shutdown or not at all.
+ CallOnStopRequest();
+ }
+ return NS_OK;
+}
+
+// nsITimerCallback
+
+nsIncrementalDownload::TimerCallback::TimerCallback(
+ nsIncrementalDownload* aIncrementalDownload)
+ : mIncrementalDownload(aIncrementalDownload) {}
+
+NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback, nsITimerCallback,
+ nsINamed)
+
+NS_IMETHODIMP
+nsIncrementalDownload::TimerCallback::Notify(nsITimer* aTimer) {
+ mIncrementalDownload->mTimer = nullptr;
+
+ nsresult rv = mIncrementalDownload->ProcessTimeout();
+ if (NS_FAILED(rv)) mIncrementalDownload->Cancel(rv);
+
+ return NS_OK;
+}
+
+// nsINamed
+
+NS_IMETHODIMP
+nsIncrementalDownload::TimerCallback::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsIncrementalDownload");
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetInterface(const nsIID& iid, void** result) {
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
+ if (ir) return ir->GetInterface(iid, result);
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+nsresult nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel* channel) {
+ NS_ENSURE_ARG(channel);
+
+ // We don't support encodings -- they make the Content-Length not equal
+ // to the actual size of the data.
+ return channel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false);
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+nsIncrementalDownload::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* cb) {
+ // In response to a redirect, we need to propagate the Range header. See bug
+ // 311595. Any failure code returned from this function aborts the redirect.
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
+ NS_ENSURE_STATE(http);
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
+ NS_ENSURE_STATE(newHttpChannel);
+
+ constexpr auto rangeHdr = "Range"_ns;
+
+ nsresult rv = ClearRequestHeader(newHttpChannel);
+ if (NS_FAILED(rv)) return rv;
+
+ // If we didn't have a Range header, then we must be doing a full download.
+ nsAutoCString rangeVal;
+ Unused << http->GetRequestHeader(rangeHdr, rangeVal);
+ if (!rangeVal.IsEmpty()) {
+ rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // A redirection changes the validator
+ mPartialValidator.Truncate();
+
+ if (mCacheBust) {
+ rv = newHttpChannel->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns,
+ false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
+ " failed to set request header: Cache-Control\n"));
+ }
+ rv = newHttpChannel->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
+ " failed to set request header: Pragma\n"));
+ }
+ }
+
+ // Prepare to receive callback
+ mRedirectCallback = cb;
+ mNewRedirectChannel = newChannel;
+
+ // Give the observer a chance to see this redirect notification.
+ nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
+ if (sink) {
+ rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+ if (NS_FAILED(rv)) {
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ }
+ return rv;
+ }
+ (void)OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result) {
+ NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
+ NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
+
+ // Update mChannel, so we can Cancel the new channel.
+ if (NS_SUCCEEDED(result)) mChannel = mNewRedirectChannel;
+
+ mRedirectCallback->OnRedirectVerifyCallback(result);
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+extern nsresult net_NewIncrementalDownload(const nsIID& iid, void** result) {
+ RefPtr<nsIncrementalDownload> d = new nsIncrementalDownload();
+ return d->QueryInterface(iid, result);
+}
diff --git a/netwerk/base/nsIncrementalStreamLoader.cpp b/netwerk/base/nsIncrementalStreamLoader.cpp
new file mode 100644
index 0000000000..f4437b5269
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIncrementalStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "mozilla/ProfilerLabels.h"
+
+#include <limits>
+
+nsIncrementalStreamLoader::nsIncrementalStreamLoader() = default;
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::Init(nsIIncrementalStreamLoaderObserver* observer) {
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = observer;
+ return NS_OK;
+}
+
+nsresult nsIncrementalStreamLoader::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsIncrementalStreamLoader> it = new nsIncrementalStreamLoader();
+ return it->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsIncrementalStreamLoader, nsIIncrementalStreamLoader,
+ nsIRequestObserver, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) {
+ *aNumBytes = mBytesRead;
+ return NS_OK;
+}
+
+/* readonly attribute nsIRequest request; */
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetRequest(nsIRequest** aRequest) {
+ *aRequest = do_AddRef(mRequest).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStartRequest(nsIRequest* request) {
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(request));
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ // On 64bit platforms size of uint64_t coincides with the size of size_t,
+ // so we want to compare with the minimum from size_t and int64_t.
+ if (static_cast<uint64_t>(contentLength) >
+ std::min(std::numeric_limits<size_t>::max(),
+ static_cast<size_t>(std::numeric_limits<int64_t>::max()))) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStopRequest(nsIRequest* request,
+ nsresult aStatus) {
+ AUTO_PROFILER_LABEL("nsIncrementalStreamLoader::OnStopRequest", NETWORK);
+
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to
+ // OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv =
+ mObserver->OnStreamComplete(this, mContext, aStatus, length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsIncrementalStreamLoader::WriteSegmentFun(
+ nsIInputStream* inStr, void* closure, const char* fromSegment,
+ uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
+ nsIncrementalStreamLoader* self = (nsIncrementalStreamLoader*)closure;
+
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(fromSegment);
+ uint32_t consumedCount = 0;
+ nsresult rv;
+ if (self->mData.empty()) {
+ // Shortcut when observer wants to keep the listener's buffer empty.
+ rv = self->mObserver->OnIncrementalData(self, self->mContext, count, data,
+ &consumedCount);
+
+ if (rv != NS_OK) {
+ return rv;
+ }
+
+ if (consumedCount > count) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount < count) {
+ if (!self->mData.append(fromSegment + consumedCount,
+ count - consumedCount)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ } else {
+ // We have some non-consumed data from previous OnIncrementalData call,
+ // appending new data and reporting combined data.
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ size_t length = self->mData.length();
+ uint32_t reportCount = length > UINT32_MAX ? UINT32_MAX : (uint32_t)length;
+ uint8_t* elems = self->mData.extractOrCopyRawBuffer();
+
+ rv = self->mObserver->OnIncrementalData(self, self->mContext, reportCount,
+ elems, &consumedCount);
+
+ // We still own elems, freeing its memory when exiting scope.
+ if (rv != NS_OK) {
+ free(elems);
+ return rv;
+ }
+
+ if (consumedCount > reportCount) {
+ free(elems);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount == length) {
+ free(elems); // good case -- fully consumed data
+ } else {
+ // Adopting elems back (at least its portion).
+ self->mData.replaceRawBuffer(elems, length);
+ if (consumedCount > 0) {
+ self->mData.erase(self->mData.begin() + consumedCount);
+ }
+ }
+ }
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to
+ // OnStreamComplete
+ mRequest = request;
+ }
+ uint32_t countRead;
+ nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+ mRequest = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+ mBytesRead += countRead;
+ return rv;
+}
+
+void nsIncrementalStreamLoader::ReleaseData() { mData.clearAndFree(); }
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::CheckListenerChain() { return NS_OK; }
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnDataFinished(nsresult aStatus) { return NS_OK; }
diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h
new file mode 100644
index 0000000000..97d4277c28
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIncrementalStreamLoader_h__
+#define nsIncrementalStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+class nsIncrementalStreamLoader final : public nsIIncrementalStreamLoader {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsIncrementalStreamLoader();
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ ~nsIncrementalStreamLoader() = default;
+
+ static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t,
+ uint32_t, uint32_t*);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIIncrementalStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+
+ // Number of bytes read, which may not match the number of bytes in mData at
+ // all, as we incrementally remove from there.
+ mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> mBytesRead;
+};
+
+#endif // nsIncrementalStreamLoader_h__
diff --git a/netwerk/base/nsInputStreamChannel.cpp b/netwerk/base/nsInputStreamChannel.cpp
new file mode 100644
index 0000000000..e00bc85ad5
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel
+
+namespace mozilla {
+namespace net {
+
+nsresult nsInputStreamChannel::OpenContentStream(bool async,
+ nsIInputStream** result,
+ nsIChannel** channel) {
+ NS_ENSURE_TRUE(mContentStream, NS_ERROR_NOT_INITIALIZED);
+
+ // If content length is unknown, then we must guess. In this case, we assume
+ // the stream can tell us. If the stream is a pipe, then this will not work.
+
+ if (mContentLength < 0) {
+ uint64_t avail;
+ nsresult rv = mContentStream->Available(&avail);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ // This just means there's nothing in the stream
+ avail = 0;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mContentLength = avail;
+ }
+
+ EnableSynthesizedProgressEvents(true);
+
+ *result = do_AddRef(mContentStream).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamChannel, nsBaseChannel,
+ nsIInputStreamChannel)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsIInputStreamChannel
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetURI(nsIURI* uri) {
+ NS_ENSURE_TRUE(!URI(), NS_ERROR_ALREADY_INITIALIZED);
+ nsBaseChannel::SetURI(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetContentStream(nsIInputStream** stream) {
+ *stream = do_AddRef(mContentStream).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetContentStream(nsIInputStream* stream) {
+ NS_ENSURE_TRUE(!mContentStream, NS_ERROR_ALREADY_INITIALIZED);
+ mContentStream = stream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetSrcdocData(nsAString& aSrcdocData) {
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocChannel = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetIsSrcdocChannel(bool* aIsSrcdocChannel) {
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetBaseURI(nsIURI** aBaseURI) {
+ *aBaseURI = do_AddRef(mBaseURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetBaseURI(nsIURI* aBaseURI) {
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsInputStreamChannel.h b/netwerk/base/nsInputStreamChannel.h
new file mode 100644
index 0000000000..c8c926020b
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamChannel_h__
+#define nsInputStreamChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsInputStreamChannel : public nsBaseChannel,
+ public nsIInputStreamChannel {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCHANNEL
+
+ nsInputStreamChannel() = default;
+
+ protected:
+ virtual ~nsInputStreamChannel() = default;
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) override;
+
+ virtual void OnChannelDone() override { mContentStream = nullptr; }
+
+ private:
+ nsCOMPtr<nsIInputStream> mContentStream;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsString mSrcdocData;
+ bool mIsSrcdocChannel{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsInputStreamPump.cpp b/netwerk/base/nsInputStreamPump.cpp
new file mode 100644
index 0000000000..dd74aed23a
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -0,0 +1,795 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsInputStreamPump.h"
+#include "nsIStreamTransportService.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Logging.h"
+#include "mozilla/NonBlockingAsyncInputStream.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/SlicedInputStream.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIStreamListener.h"
+#include "nsILoadGroup.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include <algorithm>
+
+//
+// MOZ_LOG=nsStreamPump:5
+//
+static mozilla::LazyLogModule gStreamPumpLog("nsStreamPump");
+#undef LOG
+#define LOG(args) MOZ_LOG(gStreamPumpLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump methods
+//-----------------------------------------------------------------------------
+
+nsInputStreamPump::nsInputStreamPump() : mOffMainThread(!NS_IsMainThread()) {}
+
+nsresult nsInputStreamPump::Create(nsInputStreamPump** result,
+ nsIInputStream* stream, uint32_t segsize,
+ uint32_t segcount, bool closeWhenDone,
+ nsISerialEventTarget* mainThreadTarget) {
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ RefPtr<nsInputStreamPump> pump = new nsInputStreamPump();
+ if (pump) {
+ rv = pump->Init(stream, segsize, segcount, closeWhenDone, mainThreadTarget);
+ if (NS_SUCCEEDED(rv)) {
+ pump.forget(result);
+ }
+ }
+ return rv;
+}
+
+struct PeekData {
+ PeekData(nsInputStreamPump::PeekSegmentFun fun, void* closure)
+ : mFunc(fun), mClosure(closure) {}
+
+ nsInputStreamPump::PeekSegmentFun mFunc;
+ void* mClosure;
+};
+
+static nsresult CallPeekFunc(nsIInputStream* aInStream, void* aClosure,
+ const char* aFromSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount) {
+ NS_ASSERTION(aToOffset == 0, "Called more than once?");
+ NS_ASSERTION(aCount > 0, "Called without data?");
+
+ PeekData* data = static_cast<PeekData*>(aClosure);
+ data->mFunc(data->mClosure, reinterpret_cast<const uint8_t*>(aFromSegment),
+ aCount);
+ return NS_BINDING_ABORTED;
+}
+
+nsresult nsInputStreamPump::PeekStream(PeekSegmentFun callback, void* closure) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mAsyncStream, "PeekStream called without stream");
+
+ nsresult rv = CreateBufferedStreamIfNeeded();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // See if the pipe is closed by checking the return of Available.
+ uint64_t dummy64;
+ rv = mAsyncStream->Available(&dummy64);
+ if (NS_FAILED(rv)) return rv;
+ uint32_t dummy = (uint32_t)std::min(dummy64, (uint64_t)UINT32_MAX);
+
+ PeekData data(callback, closure);
+ return mAsyncStream->ReadSegments(
+ CallPeekFunc, &data, mozilla::net::nsIOService::gDefaultSegmentSize,
+ &dummy);
+}
+
+nsresult nsInputStreamPump::EnsureWaiting() {
+ mMutex.AssertCurrentThreadIn();
+
+ // no need to worry about multiple threads... an input stream pump lives
+ // on only one thread at a time.
+ MOZ_ASSERT(mAsyncStream);
+ if (!mWaitingForInputStreamReady && !mProcessingCallbacks) {
+ // Ensure OnStateStop is called on the main thread only when this pump is
+ // created on main thread.
+ if (mState == STATE_STOP && !mOffMainThread) {
+ nsCOMPtr<nsISerialEventTarget> mainThread =
+ mLabeledMainThreadTarget
+ ? mLabeledMainThreadTarget
+ : do_AddRef(mozilla::GetMainThreadSerialEventTarget());
+ if (mTargetThread != mainThread) {
+ mTargetThread = mainThread;
+ }
+ }
+ MOZ_ASSERT(mTargetThread);
+ nsresult rv = mAsyncStream->AsyncWait(this, 0, 0, mTargetThread);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("AsyncWait failed");
+ return rv;
+ }
+ // Any retargeting during STATE_START or START_TRANSFER is complete
+ // after the call to AsyncWait; next callback will be on mTargetThread.
+ mRetargeting = false;
+ mWaitingForInputStreamReady = true;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsISupports
+//-----------------------------------------------------------------------------
+
+// although this class can only be accessed from one thread at a time, we do
+// allow its ownership to move from thread to thread, assuming the consumer
+// understands the limitations of this.
+NS_IMPL_ADDREF(nsInputStreamPump)
+NS_IMPL_RELEASE(nsInputStreamPump)
+NS_INTERFACE_MAP_BEGIN(nsInputStreamPump)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamPump)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsInputStreamPump)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamPump)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::GetName(nsACString& result) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::IsPending(bool* result) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *result = (mState != STATE_IDLE && mState != STATE_DEAD);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetStatus(nsresult* status) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsInputStreamPump::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsInputStreamPump::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsInputStreamPump::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Cancel(nsresult status) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ AssertOnThread();
+
+ LOG(("nsInputStreamPump::Cancel [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+
+ if (NS_FAILED(mStatus)) {
+ LOG((" already canceled\n"));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(NS_FAILED(status), "cancel with non-failure status code");
+ mStatus = status;
+
+ // close input stream
+ if (mAsyncStream) {
+ // If mSuspendCount != 0, EnsureWaiting will be called by Resume().
+ // Note that while suspended, OnInputStreamReady will
+ // not do anything, and also note that calling asyncWait
+ // on a closed stream works and will dispatch an event immediately.
+
+ nsCOMPtr<nsIEventTarget> currentTarget = NS_GetCurrentThread();
+ if (mTargetThread && currentTarget != mTargetThread) {
+ nsresult rv = mTargetThread->Dispatch(NS_NewRunnableFunction(
+ "nsInputStreamPump::Cancel", [self = RefPtr{this}, status] {
+ RecursiveMutexAutoLock lock(self->mMutex);
+ if (!self->mAsyncStream) {
+ return;
+ }
+ self->mAsyncStream->CloseWithStatus(status);
+ if (self->mSuspendCount == 0) {
+ self->EnsureWaiting();
+ }
+ }));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mAsyncStream->CloseWithStatus(status);
+ if (mSuspendCount == 0) {
+ EnsureWaiting();
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Suspend() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ LOG(("nsInputStreamPump::Suspend [this=%p]\n", this));
+ NS_ENSURE_TRUE(mState != STATE_IDLE && mState != STATE_DEAD,
+ NS_ERROR_UNEXPECTED);
+ ++mSuspendCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Resume() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ LOG(("nsInputStreamPump::Resume [this=%p]\n", this));
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(mState != STATE_IDLE && mState != STATE_DEAD,
+ NS_ERROR_UNEXPECTED);
+
+ // There is a brief in-between state when we null out mAsyncStream in
+ // OnStateStop() before calling OnStopRequest, and only afterwards set
+ // STATE_DEAD, which we need to handle gracefully.
+ if (--mSuspendCount == 0 && mAsyncStream) {
+ EnsureWaiting();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamPump implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::Init(nsIInputStream* stream, uint32_t segsize,
+ uint32_t segcount, bool closeWhenDone,
+ nsISerialEventTarget* mainThreadTarget) {
+ // probably we can't be multithread-accessed yet
+ RecursiveMutexAutoLock lock(mMutex);
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+
+ mStream = stream;
+ mSegSize = segsize;
+ mSegCount = segcount;
+ mCloseWhenDone = closeWhenDone;
+ mLabeledMainThreadTarget = mainThreadTarget;
+ if (mOffMainThread && mLabeledMainThreadTarget) {
+ MOZ_ASSERT(
+ false,
+ "Init stream pump off main thread with a main thread event target.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::AsyncRead(nsIStreamListener* listener) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ // This ensures only one thread can interact with a pump at a time
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_ARG_POINTER(listener);
+ MOZ_ASSERT(NS_IsMainThread() || mOffMainThread,
+ "nsInputStreamPump should be read from the "
+ "main thread only.");
+
+ nsresult rv = NS_MakeAsyncNonBlockingInputStream(
+ mStream.forget(), getter_AddRefs(mAsyncStream), mCloseWhenDone, mSegSize,
+ mSegCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(mAsyncStream);
+
+ // mStreamOffset now holds the number of bytes currently read.
+ mStreamOffset = 0;
+
+ // grab event queue (we must do this here by contract, since all notifications
+ // must go to the thread which called AsyncRead)
+ if (NS_IsMainThread() && mLabeledMainThreadTarget) {
+ mTargetThread = mLabeledMainThreadTarget;
+ } else {
+ mTargetThread = mozilla::GetCurrentSerialEventTarget();
+ }
+ NS_ENSURE_STATE(mTargetThread);
+
+ rv = EnsureWaiting();
+ if (NS_FAILED(rv)) return rv;
+
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ mState = STATE_START;
+ mListener = listener;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream* stream) {
+ LOG(("nsInputStreamPump::OnInputStreamReady [this=%p]\n", this));
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnInputStreamReady", NETWORK);
+
+ // this function has been called from a PLEvent, so we can safely call
+ // any listener or progress sink methods directly from here.
+
+ for (;;) {
+ // There should only be one iteration of this loop happening at a time.
+ // To prevent AsyncWait() (called during callbacks or on other threads)
+ // from creating a parallel OnInputStreamReady(), we use:
+ // -- a mutex; and
+ // -- a boolean mProcessingCallbacks to detect parallel loops
+ // when exiting the mutex for callbacks.
+ RecursiveMutexAutoLock lock(mMutex);
+
+ // Prevent parallel execution during callbacks, while out of mutex.
+ if (mProcessingCallbacks) {
+ MOZ_ASSERT(!mProcessingCallbacks);
+ break;
+ }
+ mProcessingCallbacks = true;
+ if (mSuspendCount || mState == STATE_IDLE || mState == STATE_DEAD) {
+ mWaitingForInputStreamReady = false;
+ mProcessingCallbacks = false;
+ break;
+ }
+
+ uint32_t nextState;
+ switch (mState) {
+ case STATE_START:
+ nextState = OnStateStart();
+ break;
+ case STATE_TRANSFER:
+ nextState = OnStateTransfer();
+ break;
+ case STATE_STOP:
+ mRetargeting = false;
+ nextState = OnStateStop();
+ break;
+ default:
+ nextState = 0;
+ MOZ_ASSERT_UNREACHABLE("Unknown enum value.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stillTransferring =
+ (mState == STATE_TRANSFER && nextState == STATE_TRANSFER);
+ if (stillTransferring) {
+ NS_ASSERTION(NS_SUCCEEDED(mStatus),
+ "Should not have failed status for ongoing transfer");
+ } else {
+ NS_ASSERTION(mState != nextState,
+ "Only OnStateTransfer can be called more than once.");
+ }
+ if (mRetargeting) {
+ NS_ASSERTION(mState != STATE_STOP,
+ "Retargeting should not happen during OnStateStop.");
+ }
+
+ // Set mRetargeting so EnsureWaiting will be called. It ensures that
+ // OnStateStop is called on the main thread.
+ if (nextState == STATE_STOP && !NS_IsMainThread() && !mOffMainThread) {
+ mRetargeting = true;
+ }
+
+ // Unset mProcessingCallbacks here (while we have lock) so our own call to
+ // EnsureWaiting isn't blocked by it.
+ mProcessingCallbacks = false;
+
+ // We must break the loop if suspended during one of the previous
+ // operation.
+ if (mSuspendCount) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ break;
+ }
+
+ // Wait asynchronously if there is still data to transfer, or we're
+ // switching event delivery to another thread.
+ if (stillTransferring || mRetargeting) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ nsresult rv = EnsureWaiting();
+ if (NS_SUCCEEDED(rv)) break;
+
+ // Failure to start asynchronous wait: stop transfer.
+ // Do not set mStatus if it was previously set to report a failure.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ nextState = STATE_STOP;
+ }
+
+ mState = nextState;
+ }
+ return NS_OK;
+}
+
+uint32_t nsInputStreamPump::OnStateStart() {
+ mMutex.AssertCurrentThreadIn();
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateStart", NETWORK);
+
+ LOG((" OnStateStart [this=%p]\n", this));
+
+ nsresult rv;
+
+ // need to check the reason why the stream is ready. this is required
+ // so our listener can check our status from OnStartRequest.
+ // XXX async streams should have a GetStatus method!
+ if (NS_SUCCEEDED(mStatus)) {
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_CLOSED) mStatus = rv;
+ }
+
+ {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ // We're on the writing thread
+ AssertOnThread();
+
+ // Note: Must exit mutex for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ RecursiveMutexAutoUnlock unlock(mMutex);
+ rv = listener->OnStartRequest(this);
+ }
+
+ // an error returned from OnStartRequest should cause us to abort; however,
+ // we must not stomp on mStatus if already canceled.
+ if (NS_FAILED(rv) && NS_SUCCEEDED(mStatus)) mStatus = rv;
+
+ return NS_SUCCEEDED(mStatus) ? STATE_TRANSFER : STATE_STOP;
+}
+
+uint32_t nsInputStreamPump::OnStateTransfer() {
+ mMutex.AssertCurrentThreadIn();
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateTransfer", NETWORK);
+
+ LOG((" OnStateTransfer [this=%p]\n", this));
+
+ // if canceled, go directly to STATE_STOP...
+ if (NS_FAILED(mStatus)) return STATE_STOP;
+
+ nsresult rv = CreateBufferedStreamIfNeeded();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return STATE_STOP;
+ }
+
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ LOG((" Available returned [stream=%p rv=%" PRIx32 " avail=%" PRIu64 "]\n",
+ mAsyncStream.get(), static_cast<uint32_t>(rv), avail));
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ avail = 0;
+ } else if (NS_SUCCEEDED(rv) && avail) {
+ // we used to limit avail to 16K - we were afraid some ODA handlers
+ // might assume they wouldn't get more than 16K at once
+ // we're removing that limit since it speeds up local file access.
+ // Now there's an implicit 64K limit of 4 16K segments
+ // NOTE: ok, so the story is as follows. OnDataAvailable impls
+ // are by contract supposed to consume exactly |avail| bytes.
+ // however, many do not... mailnews... stream converters...
+ // cough, cough. the input stream pump is fairly tolerant
+ // in this regard; however, if an ODA does not consume any
+ // data from the stream, then we could potentially end up in
+ // an infinite loop. we do our best here to try to catch
+ // such an error. (see bug 189672)
+
+ // in most cases this QI will succeed (mAsyncStream is almost always
+ // a nsPipeInputStream, which implements nsITellableStream::Tell).
+ int64_t offsetBefore;
+ nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mAsyncStream);
+ if (tellable && NS_FAILED(tellable->Tell(&offsetBefore))) {
+ MOZ_ASSERT_UNREACHABLE("Tell failed on readable stream");
+ offsetBefore = 0;
+ }
+
+ uint32_t odaAvail = avail > UINT32_MAX ? UINT32_MAX : uint32_t(avail);
+
+ LOG((" calling OnDataAvailable [offset=%" PRIu64 " count=%" PRIu64
+ "(%u)]\n",
+ mStreamOffset, avail, odaAvail));
+
+ {
+ // We may be called on non-MainThread even if mOffMainThread is
+ // false, due to RetargetDeliveryTo(), so don't use AssertOnThread()
+ if (mTargetThread) {
+ MOZ_ASSERT(mTargetThread->IsOnCurrentThread());
+ } else {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ // Note: Must exit mutex for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ RecursiveMutexAutoUnlock unlock(mMutex);
+ // We're on the writing thread for mListener and mAsyncStream.
+ // mStreamOffset is only touched in OnStateTransfer, and AsyncRead
+ // shouldn't be called during OnDataAvailable()
+
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ rv = listener->OnDataAvailable(this, mAsyncStream, mStreamOffset,
+ odaAvail);
+ MOZ_POP_THREAD_SAFETY
+ }
+
+ // don't enter this code if ODA failed or called Cancel
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mStatus)) {
+ // test to see if this ODA failed to consume data
+ if (tellable) {
+ // NOTE: if Tell fails, which can happen if the stream is
+ // now closed, then we assume that everything was read.
+ int64_t offsetAfter;
+ if (NS_FAILED(tellable->Tell(&offsetAfter))) {
+ offsetAfter = offsetBefore + odaAvail;
+ }
+ if (offsetAfter > offsetBefore) {
+ mStreamOffset += (offsetAfter - offsetBefore);
+ } else if (mSuspendCount == 0) {
+ //
+ // possible infinite loop if we continue pumping data!
+ //
+ // NOTE: although not allowed by nsIStreamListener, we
+ // will allow the ODA impl to Suspend the pump. IMAP
+ // does this :-(
+ //
+ NS_ERROR("OnDataAvailable implementation consumed no data");
+ mStatus = NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ mStreamOffset += odaAvail; // assume ODA behaved well
+ }
+ }
+ }
+
+ // an error returned from Available or OnDataAvailable should cause us to
+ // abort; however, we must not stop on mStatus if already canceled.
+
+ if (NS_SUCCEEDED(mStatus)) {
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ } else if (avail) {
+ // if stream is now closed, advance to STATE_STOP right away.
+ // Available may return 0 bytes available at the moment; that
+ // would not mean that we are done.
+ // XXX async streams should have a GetStatus method!
+ rv = mAsyncStream->Available(&avail);
+ if (NS_SUCCEEDED(rv)) return STATE_TRANSFER;
+ if (rv != NS_BASE_STREAM_CLOSED) mStatus = rv;
+ }
+ }
+ return STATE_STOP;
+}
+
+nsresult nsInputStreamPump::CallOnStateStop() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "CallOnStateStop should only be called on the main thread.");
+
+ mState = OnStateStop();
+ return NS_OK;
+}
+
+uint32_t nsInputStreamPump::OnStateStop() {
+ mMutex.AssertCurrentThreadIn();
+
+ if (!NS_IsMainThread() && !mOffMainThread) {
+ // This method can be called on a different thread if nsInputStreamPump
+ // is used off the main-thread.
+ if (NS_SUCCEEDED(mStatus) && mListener &&
+ mozilla::StaticPrefs::network_send_OnDataFinished_nsInputStreamPump()) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener);
+ if (retargetableListener) {
+ retargetableListener->OnDataFinished(mStatus);
+ }
+ }
+ nsresult rv = mLabeledMainThreadTarget->Dispatch(
+ mozilla::NewRunnableMethod("nsInputStreamPump::CallOnStateStop", this,
+ &nsInputStreamPump::CallOnStateStop));
+ NS_ENSURE_SUCCESS(rv, STATE_DEAD);
+ return STATE_DEAD;
+ }
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateStop", NETWORK);
+
+ LOG((" OnStateStop [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(mStatus)));
+
+ // if an error occurred, we must be sure to pass the error onto the async
+ // stream. in some cases, this is redundant, but since close is idempotent,
+ // this is OK. otherwise, be sure to honor the "close-when-done" option.
+
+ if (!mAsyncStream || !mListener) {
+ MOZ_ASSERT(mAsyncStream, "null mAsyncStream: OnStateStop called twice?");
+ MOZ_ASSERT(mListener, "null mListener: OnStateStop called twice?");
+ return STATE_DEAD;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ mAsyncStream->CloseWithStatus(mStatus);
+ } else if (mCloseWhenDone) {
+ mAsyncStream->Close();
+ }
+
+ mAsyncStream = nullptr;
+ mIsPending = false;
+ {
+ // We're on the writing thread.
+ // We believe that mStatus can't be changed on us here.
+ AssertOnThread();
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ nsresult status = mStatus;
+ // Note: Must exit mutex for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ RecursiveMutexAutoUnlock unlock(mMutex);
+
+ listener->OnStopRequest(this, status);
+ }
+ mTargetThread = nullptr;
+ mListener = nullptr;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return STATE_DEAD;
+}
+
+nsresult nsInputStreamPump::CreateBufferedStreamIfNeeded() {
+ if (mAsyncStreamIsBuffered) {
+ return NS_OK;
+ }
+
+ // ReadSegments is not available for any nsIAsyncInputStream. In order to use
+ // it, we wrap a nsIBufferedInputStream around it, if needed.
+
+ if (NS_InputStreamIsBuffered(mAsyncStream)) {
+ mAsyncStreamIsBuffered = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(stream),
+ mAsyncStream.forget(), 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // A buffered inputStream must implement nsIAsyncInputStream.
+ mAsyncStream = do_QueryInterface(stream);
+ MOZ_DIAGNOSTIC_ASSERT(mAsyncStream);
+ mAsyncStreamIsBuffered = true;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ NS_ENSURE_ARG(aNewTarget);
+ NS_ENSURE_TRUE(mState == STATE_START || mState == STATE_TRANSFER,
+ NS_ERROR_UNEXPECTED);
+
+ // If canceled, do not retarget. Return with canceled status.
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (aNewTarget == mTargetThread) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+
+ if (mOffMainThread) {
+ // Don't support retargeting if this pump is already used off the main
+ // thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure that |mListener| and any subsequent listeners can be retargeted
+ // to another thread.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (NS_SUCCEEDED(rv) && retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_SUCCEEDED(rv)) {
+ mTargetThread = aNewTarget;
+ mRetargeting = true;
+ }
+ }
+ LOG(
+ ("nsInputStreamPump::RetargetDeliveryTo [this=%p aNewTarget=%p] "
+ "%s listener [%p] rv[%" PRIx32 "]",
+ this, aNewTarget, (mTargetThread == aNewTarget ? "success" : "failure"),
+ (nsIStreamListener*)mListener, static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetDeliveryTarget(nsISerialEventTarget** aNewTarget) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mTargetThread;
+ target.forget(aNewTarget);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsInputStreamPump.h b/netwerk/base/nsInputStreamPump.h
new file mode 100644
index 0000000000..21e75c0276
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamPump_h__
+#define nsInputStreamPump_h__
+
+#include "nsIInputStreamPump.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RecursiveMutex.h"
+
+#ifdef DEBUG
+# include "MainThreadUtils.h"
+# include "nsISerialEventTarget.h"
+#endif
+
+class nsIInputStream;
+class nsILoadGroup;
+class nsIStreamListener;
+
+#define NS_INPUT_STREAM_PUMP_IID \
+ { \
+ 0x42f1cc9b, 0xdf5f, 0x4c9b, { \
+ 0xbd, 0x71, 0x8d, 0x4a, 0xe2, 0x27, 0xc1, 0x8a \
+ } \
+ }
+
+class nsInputStreamPump final : public nsIInputStreamPump,
+ public nsIInputStreamCallback,
+ public nsIThreadRetargetableRequest {
+ ~nsInputStreamPump() = default;
+
+ public:
+ using RecursiveMutexAutoLock = mozilla::RecursiveMutexAutoLock;
+ using RecursiveMutexAutoUnlock = mozilla::RecursiveMutexAutoUnlock;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINPUTSTREAMPUMP
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_STREAM_PUMP_IID)
+
+ nsInputStreamPump();
+
+ static nsresult Create(nsInputStreamPump** result, nsIInputStream* stream,
+ uint32_t segsize = 0, uint32_t segcount = 0,
+ bool closeWhenDone = false,
+ nsISerialEventTarget* mainThreadTarget = nullptr);
+
+ using PeekSegmentFun = void (*)(void*, const uint8_t*, uint32_t);
+ /**
+ * Peek into the first chunk of data that's in the stream. Note that this
+ * method will not call the callback when there is no data in the stream.
+ * The callback will be called at most once.
+ *
+ * The data from the stream will not be consumed, i.e. the pump's listener
+ * can still read all the data
+ *
+ * Do not call before asyncRead. Do not call after onStopRequest.
+ */
+ nsresult PeekStream(PeekSegmentFun callback, void* closure);
+
+ /**
+ * Dispatched (to the main thread) by OnStateStop if it's called off main
+ * thread. Updates mState based on return value of OnStateStop.
+ */
+ nsresult CallOnStateStop();
+
+ protected:
+ enum { STATE_IDLE, STATE_START, STATE_TRANSFER, STATE_STOP, STATE_DEAD };
+
+ nsresult EnsureWaiting();
+ uint32_t OnStateStart();
+ uint32_t OnStateTransfer();
+ uint32_t OnStateStop();
+ nsresult CreateBufferedStreamIfNeeded() MOZ_REQUIRES(mMutex);
+
+ // This should optimize away in non-DEBUG builds
+ MOZ_ALWAYS_INLINE void AssertOnThread() const MOZ_REQUIRES(mMutex) {
+ if (mOffMainThread) {
+ MOZ_ASSERT(mTargetThread->IsOnCurrentThread());
+ } else {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ }
+
+ uint32_t mState MOZ_GUARDED_BY(mMutex){STATE_IDLE};
+ nsCOMPtr<nsILoadGroup> mLoadGroup MOZ_GUARDED_BY(mMutex);
+ // mListener is written on a single thread (either MainThread or an
+ // off-MainThread thread), read from that thread and perhaps others (in
+ // RetargetDeliveryTo)
+ nsCOMPtr<nsIStreamListener> mListener MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsISerialEventTarget> mTargetThread MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsISerialEventTarget> mLabeledMainThreadTarget
+ MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIInputStream> mStream MOZ_GUARDED_BY(mMutex);
+ // mAsyncStream is written on a single thread (either MainThread or an
+ // off-MainThread thread), and lives from AsyncRead() to OnStateStop().
+ nsCOMPtr<nsIAsyncInputStream> mAsyncStream MOZ_GUARDED_BY(mMutex);
+ uint64_t mStreamOffset MOZ_GUARDED_BY(mMutex){0};
+ uint64_t mStreamLength MOZ_GUARDED_BY(mMutex){0};
+ uint32_t mSegSize MOZ_GUARDED_BY(mMutex){0};
+ uint32_t mSegCount MOZ_GUARDED_BY(mMutex){0};
+ nsresult mStatus MOZ_GUARDED_BY(mMutex){NS_OK};
+ uint32_t mSuspendCount MOZ_GUARDED_BY(mMutex){0};
+ uint32_t mLoadFlags MOZ_GUARDED_BY(mMutex){LOAD_NORMAL};
+ bool mIsPending MOZ_GUARDED_BY(mMutex){false};
+ // True while in OnInputStreamReady, calling OnStateStart, OnStateTransfer
+ // and OnStateStop. Used to prevent calls to AsyncWait during callbacks.
+ bool mProcessingCallbacks MOZ_GUARDED_BY(mMutex){false};
+ // True if waiting on the "input stream ready" callback.
+ bool mWaitingForInputStreamReady MOZ_GUARDED_BY(mMutex){false};
+ bool mCloseWhenDone MOZ_GUARDED_BY(mMutex){false};
+ bool mRetargeting MOZ_GUARDED_BY(mMutex){false};
+ bool mAsyncStreamIsBuffered MOZ_GUARDED_BY(mMutex){false};
+ // Indicate whether nsInputStreamPump is used completely off main thread.
+ // If true, OnStateStop() is executed off main thread. Set at creation.
+ const bool mOffMainThread;
+ // Protects state/member var accesses across multiple threads.
+ mozilla::RecursiveMutex mMutex{"nsInputStreamPump"};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsInputStreamPump, NS_INPUT_STREAM_PUMP_IID)
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp
new file mode 100644
index 0000000000..3e2445b6ae
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -0,0 +1,1158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=4 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsLoadGroup.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Telemetry.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsITimedChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRequestObserver.h"
+#include "CacheObserver.h"
+#include "MainThreadUtils.h"
+#include "RequestContextService.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla {
+namespace net {
+
+//
+// Log module for nsILoadGroup logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=LoadGroup:5
+// set MOZ_LOG_FILE=network.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file network.log.
+//
+static LazyLogModule gLoadGroupLog("LoadGroup");
+#undef LOG
+#define LOG(args) MOZ_LOG(gLoadGroupLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class RequestMapEntry : public PLDHashEntryHdr {
+ public:
+ explicit RequestMapEntry(nsIRequest* aRequest) : mKey(aRequest) {}
+
+ nsCOMPtr<nsIRequest> mKey;
+};
+
+static bool RequestHashMatchEntry(const PLDHashEntryHdr* entry,
+ const void* key) {
+ const RequestMapEntry* e = static_cast<const RequestMapEntry*>(entry);
+ const nsIRequest* request = static_cast<const nsIRequest*>(key);
+
+ return e->mKey == request;
+}
+
+static void RequestHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
+ RequestMapEntry* e = static_cast<RequestMapEntry*>(entry);
+
+ // An entry is being cleared, let the entry do its own cleanup.
+ e->~RequestMapEntry();
+}
+
+static void RequestHashInitEntry(PLDHashEntryHdr* entry, const void* key) {
+ const nsIRequest* const_request = static_cast<const nsIRequest*>(key);
+ nsIRequest* request = const_cast<nsIRequest*>(const_request);
+
+ // Initialize the entry with placement new
+ new (entry) RequestMapEntry(request);
+}
+
+static const PLDHashTableOps sRequestHashOps = {
+ PLDHashTable::HashVoidPtrKeyStub, RequestHashMatchEntry,
+ PLDHashTable::MoveEntryStub, RequestHashClearEntry, RequestHashInitEntry};
+
+static void RescheduleRequest(nsIRequest* aRequest, int32_t delta) {
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aRequest);
+ if (p) p->AdjustPriority(delta);
+}
+
+nsLoadGroup::nsLoadGroup()
+ : mRequests(&sRequestHashOps, sizeof(RequestMapEntry)) {
+ LOG(("LOADGROUP [%p]: Created.\n", this));
+}
+
+nsLoadGroup::~nsLoadGroup() {
+ DebugOnly<nsresult> rv =
+ CancelWithReason(NS_BINDING_ABORTED, "nsLoadGroup::~nsLoadGroup"_ns);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed");
+
+ mDefaultLoadRequest = nullptr;
+
+ if (mRequestContext && !mExternalRequestContext) {
+ mRequestContextService->RemoveRequestContext(mRequestContext->GetID());
+ if (IsNeckoChild() && gNeckoChild) {
+ gNeckoChild->SendRemoveRequestContext(mRequestContext->GetID());
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ Unused << os->RemoveObserver(this, "last-pb-context-exited");
+ }
+
+ LOG(("LOADGROUP [%p]: Destroyed.\n", this));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports methods:
+
+NS_IMPL_ISUPPORTS(nsLoadGroup, nsILoadGroup, nsILoadGroupChild, nsIRequest,
+ nsISupportsPriority, nsISupportsWeakReference, nsIObserver)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetName(nsACString& result) {
+ // XXX is this the right "name" for a load group?
+
+ if (!mDefaultLoadRequest) {
+ result.Truncate();
+ return NS_OK;
+ }
+
+ return mDefaultLoadRequest->GetName(result);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::IsPending(bool* aResult) {
+ *aResult = mForegroundCount > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetStatus(nsresult* status) {
+ if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest) {
+ return mDefaultLoadRequest->GetStatus(status);
+ }
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+static bool AppendRequestsToArray(PLDHashTable* aTable,
+ nsTArray<nsIRequest*>* aArray) {
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto* e = static_cast<RequestMapEntry*>(iter.Get());
+ nsIRequest* request = e->mKey;
+ MOZ_DIAGNOSTIC_ASSERT(request, "Null key in mRequests PLDHashTable entry");
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aArray->AppendElement(request);
+ NS_ADDREF(request);
+ }
+
+ if (aArray->Length() != aTable->EntryCount()) {
+ for (uint32_t i = 0, len = aArray->Length(); i < len; ++i) {
+ NS_RELEASE((*aArray)[i]);
+ }
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsLoadGroup::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsLoadGroup::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsLoadGroup::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Cancel(nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
+ nsresult rv;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // set the load group status to our cancel status while we cancel
+ // all our requests...once the cancel is done, we'll reset it...
+ //
+ mStatus = status;
+
+ // Set the flag indicating that the loadgroup is being canceled... This
+ // prevents any new channels from being added during the operation.
+ //
+ mIsCanceling = true;
+
+ nsresult firstError = NS_OK;
+ while (count > 0) {
+ nsCOMPtr<nsIRequest> request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+
+ if (!mRequests.Search(request)) {
+ // |request| was removed already
+ // We need to null out the entry in the request array so we don't try
+ // to notify the observers for this request.
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count));
+ requests.ElementAt(count) = nullptr;
+
+ continue;
+ }
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Canceling request %p %s.\n", this, request.get(),
+ nameStr.get()));
+ }
+
+ // Cancel the request...
+ rv = request->CancelWithReason(status, mCanceledReason);
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
+
+ if (NS_FAILED(RemoveRequestFromHashtable(request, status))) {
+ // It's possible that request->Cancel causes the request to be removed
+ // from the loadgroup causing RemoveRequestFromHashtable to fail.
+ // In that case we shouldn't call NotifyRemovalObservers or decrement
+ // mForegroundCount since that has already happened.
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count));
+ requests.ElementAt(count) = nullptr;
+
+ continue;
+ }
+ }
+
+ for (count = requests.Length(); count > 0;) {
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
+ (void)NotifyRemovalObservers(request, status);
+ }
+
+ if (mRequestContext) {
+ Unused << mRequestContext->CancelTailPendingRequests(status);
+ }
+
+#if defined(DEBUG)
+ NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty.");
+ NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active.");
+#endif
+
+ mStatus = NS_OK;
+ mIsCanceling = false;
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Suspend() {
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request) continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Suspending request %p %s.\n", this, request.get(),
+ nameStr.get()));
+ }
+
+ // Suspend the request...
+ rv = request->Suspend();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
+ }
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Resume() {
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request) continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Resuming request %p %s.\n", this, request.get(),
+ nameStr.get()));
+ }
+
+ // Resume the request...
+ rv = request->Resume();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
+ }
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadFlags(uint32_t* aLoadFlags) {
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadGroup(nsILoadGroup** loadGroup) {
+ nsCOMPtr<nsILoadGroup> result = mLoadGroup;
+ result.forget(loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadGroup(nsILoadGroup* loadGroup) {
+ mLoadGroup = loadGroup;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroup methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadRequest(nsIRequest** aRequest) {
+ nsCOMPtr<nsIRequest> result = mDefaultLoadRequest;
+ result.forget(aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadRequest(nsIRequest* aRequest) {
+ LOG(("nsLoadGroup::SetDefaultLoadRequest this=%p default-request=%p", this,
+ aRequest));
+
+ mDefaultLoadRequest = aRequest;
+ // Inherit the group load flags from the default load request
+ if (mDefaultLoadRequest) {
+ mDefaultLoadRequest->GetLoadFlags(&mLoadFlags);
+ //
+ // Mask off any bits that are not part of the nsIRequest flags.
+ // in particular, nsIChannel::LOAD_DOCUMENT_URI...
+ //
+ mLoadFlags &= nsIRequest::LOAD_REQUESTMASK;
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aRequest);
+ mDefaultLoadIsTimed = timedChannel != nullptr;
+ if (mDefaultLoadIsTimed) {
+ timedChannel->GetChannelCreation(&mDefaultRequestCreationTime);
+ timedChannel->SetTimingEnabled(true);
+ }
+ }
+ // Else, do not change the group's load flags (see bug 95981)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AddRequest(nsIRequest* request, nsISupports* ctxt) {
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Adding request %p %s (count=%d).\n", this, request,
+ nameStr.get(), mRequests.EntryCount()));
+ }
+
+ NS_ASSERTION(!mRequests.Search(request),
+ "Entry added to loadgroup twice, don't do that");
+
+ //
+ // Do not add the channel, if the loadgroup is being canceled...
+ //
+ if (mIsCanceling) {
+ LOG(
+ ("LOADGROUP [%p]: AddChannel() ABORTED because LoadGroup is"
+ " being canceled!!\n",
+ this));
+
+ return NS_BINDING_ABORTED;
+ }
+
+ nsLoadFlags flags;
+ // if the request is the default load request or if the default load
+ // request is null, then the load group should inherit its load flags from
+ // the request, but also we need to enforce defaultLoadFlags.
+ if (mDefaultLoadRequest == request || !mDefaultLoadRequest) {
+ rv = MergeDefaultLoadFlags(request, flags);
+ } else {
+ rv = MergeLoadFlags(request, flags);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ //
+ // Add the request to the list of active requests...
+ //
+
+ auto* entry = static_cast<RequestMapEntry*>(mRequests.Add(request, fallible));
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mPriority != 0) RescheduleRequest(request, mPriority);
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel) timedChannel->SetTimingEnabled(true);
+
+ bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND);
+ if (foreground) {
+ // Update the count of foreground URIs..
+ mForegroundCount += 1;
+ }
+
+ if (foreground || mNotifyObserverAboutBackgroundRequests) {
+ //
+ // Fire the OnStartRequest notification out to the observer...
+ //
+ // If the notification fails then DO NOT add the request to
+ // the load group.
+ //
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(
+ ("LOADGROUP [%p]: Firing OnStartRequest for request %p."
+ "(foreground count=%d).\n",
+ this, request, mForegroundCount));
+
+ rv = observer->OnStartRequest(request);
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%p]: OnStartRequest for request %p FAILED.\n", this,
+ request));
+ //
+ // The URI load has been canceled by the observer. Clean up
+ // the damage...
+ //
+
+ mRequests.Remove(request);
+
+ rv = NS_OK;
+
+ if (foreground) {
+ mForegroundCount -= 1;
+ }
+ }
+ }
+
+ // Ensure that we're part of our loadgroup while pending
+ if (foreground && mForegroundCount == 1 && mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::RemoveRequest(nsIRequest* request, nsISupports* ctxt,
+ nsresult aStatus) {
+ // Make sure we have a owning reference to the request we're about
+ // to remove.
+ nsCOMPtr<nsIRequest> kungFuDeathGrip(request);
+
+ nsresult rv = RemoveRequestFromHashtable(request, aStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NotifyRemovalObservers(request, aStatus);
+}
+
+nsresult nsLoadGroup::RemoveRequestFromHashtable(nsIRequest* request,
+ nsresult aStatus) {
+ NS_ENSURE_ARG_POINTER(request);
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Removing request %p %s status %" PRIx32
+ " (count=%d).\n",
+ this, request, nameStr.get(), static_cast<uint32_t>(aStatus),
+ mRequests.EntryCount() - 1));
+ }
+
+ //
+ // Remove the request from the group. If this fails, it means that
+ // the request was *not* in the group so do not update the foreground
+ // count or it will get messed up...
+ //
+ auto* entry = static_cast<RequestMapEntry*>(mRequests.Search(request));
+
+ if (!entry) {
+ LOG(("LOADGROUP [%p]: Unable to remove request %p. Not in group!\n", this,
+ request));
+
+ return NS_ERROR_FAILURE;
+ }
+
+ mRequests.RemoveEntry(entry);
+
+ // Collect telemetry stats only when default request is a timed channel.
+ // Don't include failed requests in the timing statistics.
+ if (mDefaultLoadIsTimed && NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel) {
+ // Figure out if this request was served from the cache
+ ++mTimedRequests;
+ TimeStamp timeStamp;
+ rv = timedChannel->GetCacheReadStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ ++mCachedRequests;
+ }
+
+ rv = timedChannel->GetAsyncOpen(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_OPEN_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ rv = timedChannel->GetResponseStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ TelemetryReportChannel(timedChannel, false);
+ }
+ }
+
+ if (mRequests.EntryCount() == 0) {
+ TelemetryReport();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsLoadGroup::NotifyRemovalObservers(nsIRequest* request,
+ nsresult aStatus) {
+ NS_ENSURE_ARG_POINTER(request);
+ // Undo any group priority delta...
+ if (mPriority != 0) RescheduleRequest(request, -mPriority);
+
+ nsLoadFlags flags;
+ nsresult rv = request->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) return rv;
+
+ bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND);
+ if (foreground) {
+ NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up");
+ mForegroundCount -= 1;
+ }
+
+ if (foreground || mNotifyObserverAboutBackgroundRequests) {
+ // Fire the OnStopRequest out to the observer...
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(
+ ("LOADGROUP [%p]: Firing OnStopRequest for request %p."
+ "(foreground count=%d).\n",
+ this, request, mForegroundCount));
+
+ rv = observer->OnStopRequest(request, aStatus);
+
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%p]: OnStopRequest for request %p FAILED.\n", this,
+ request));
+ }
+ }
+
+ // If that was the last request -> remove ourselves from loadgroup
+ if (foreground && mForegroundCount == 0 && mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequests(nsISimpleEnumerator** aRequests) {
+ nsCOMArray<nsIRequest> requests;
+ requests.SetCapacity(mRequests.EntryCount());
+
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto* e = static_cast<RequestMapEntry*>(iter.Get());
+ requests.AppendObject(e->mKey);
+ }
+
+ return NS_NewArrayEnumerator(aRequests, requests, NS_GET_IID(nsIRequest));
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver) {
+ SetGroupObserver(aObserver, false);
+ return NS_OK;
+}
+
+void nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver,
+ bool aIncludeBackgroundRequests) {
+ mObserver = do_GetWeakReference(aObserver);
+ mNotifyObserverAboutBackgroundRequests = aIncludeBackgroundRequests;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetGroupObserver(nsIRequestObserver** aResult) {
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ observer.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetActiveCount(uint32_t* aResult) {
+ *aResult = mForegroundCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = mCallbacks;
+ callbacks.forget(aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequestContextID(uint64_t* aRCID) {
+ if (!mRequestContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aRCID = mRequestContext->GetID();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroupChild methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetParentLoadGroup(nsILoadGroup** aParentLoadGroup) {
+ *aParentLoadGroup = nullptr;
+ nsCOMPtr<nsILoadGroup> parent = do_QueryReferent(mParentLoadGroup);
+ if (!parent) return NS_OK;
+ parent.forget(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetParentLoadGroup(nsILoadGroup* aParentLoadGroup) {
+ mParentLoadGroup = do_GetWeakReference(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetChildLoadGroup(nsILoadGroup** aChildLoadGroup) {
+ *aChildLoadGroup = do_AddRef(this).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRootLoadGroup(nsILoadGroup** aRootLoadGroup) {
+ // first recursively try the root load group of our parent
+ nsCOMPtr<nsILoadGroupChild> ancestor = do_QueryReferent(mParentLoadGroup);
+ if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // next recursively try the root load group of our own load grop
+ ancestor = do_QueryInterface(mLoadGroup);
+ if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // finally just return this
+ *aRootLoadGroup = do_AddRef(this).take();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupportsPriority methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetPriority(int32_t* aValue) {
+ *aValue = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetPriority(int32_t aValue) {
+ return AdjustPriority(aValue - mPriority);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AdjustPriority(int32_t aDelta) {
+ // Update the priority for each request that supports nsISupportsPriority
+ if (aDelta != 0) {
+ mPriority += aDelta;
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto* e = static_cast<RequestMapEntry*>(iter.Get());
+ RescheduleRequest(e->mKey, aDelta);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadFlags(uint32_t* aFlags) {
+ *aFlags = mDefaultLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags) {
+ mDefaultLoadFlags = aFlags;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void nsLoadGroup::TelemetryReport() {
+ nsresult defaultStatus = NS_ERROR_INVALID_ARG;
+ // We should only report HTTP_PAGE_* telemetry if the defaultRequest was
+ // actually successful.
+ if (mDefaultLoadRequest) {
+ mDefaultLoadRequest->GetStatus(&defaultStatus);
+ }
+ if (mDefaultLoadIsTimed && NS_SUCCEEDED(defaultStatus)) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE, mTimedRequests);
+ if (mTimedRequests) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE_FROM_CACHE,
+ mCachedRequests * 100 / mTimedRequests);
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel =
+ do_QueryInterface(mDefaultLoadRequest);
+ if (timedChannel) TelemetryReportChannel(timedChannel, true);
+ }
+
+ mTimedRequests = 0;
+ mCachedRequests = 0;
+ mDefaultLoadIsTimed = false;
+}
+
+void nsLoadGroup::TelemetryReportChannel(nsITimedChannel* aTimedChannel,
+ bool aDefaultRequest) {
+ nsresult rv;
+ bool timingEnabled;
+ rv = aTimedChannel->GetTimingEnabled(&timingEnabled);
+ if (NS_FAILED(rv) || !timingEnabled) return;
+
+ TimeStamp asyncOpen;
+ rv = aTimedChannel->GetAsyncOpen(&asyncOpen);
+ // We do not check !asyncOpen.IsNull() bellow, prevent ASSERTIONs this way
+ if (NS_FAILED(rv) || asyncOpen.IsNull()) return;
+
+ TimeStamp cacheReadStart;
+ rv = aTimedChannel->GetCacheReadStart(&cacheReadStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp cacheReadEnd;
+ rv = aTimedChannel->GetCacheReadEnd(&cacheReadEnd);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp domainLookupStart;
+ rv = aTimedChannel->GetDomainLookupStart(&domainLookupStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp domainLookupEnd;
+ rv = aTimedChannel->GetDomainLookupEnd(&domainLookupEnd);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp connectStart;
+ rv = aTimedChannel->GetConnectStart(&connectStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp secureConnectionStart;
+ rv = aTimedChannel->GetSecureConnectionStart(&secureConnectionStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp connectEnd;
+ rv = aTimedChannel->GetConnectEnd(&connectEnd);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp requestStart;
+ rv = aTimedChannel->GetRequestStart(&requestStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp responseStart;
+ rv = aTimedChannel->GetResponseStart(&responseStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp responseEnd;
+ rv = aTimedChannel->GetResponseEnd(&responseEnd);
+ if (NS_FAILED(rv)) return;
+
+ bool useHttp3 = false;
+ bool supportHttp3 = false;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel =
+ do_QueryInterface(aTimedChannel);
+ if (httpChannel) {
+ uint32_t major;
+ uint32_t minor;
+ if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
+ useHttp3 = major == 3;
+ if (major == 2) {
+ if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
+ supportHttp3 = false;
+ }
+ }
+ }
+ }
+
+#define HTTP_REQUEST_HISTOGRAMS(prefix) \
+ if (!domainLookupStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_ISSUE_TIME, \
+ asyncOpen, domainLookupStart); \
+ } \
+ \
+ if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME, \
+ domainLookupStart, domainLookupEnd); \
+ } \
+ \
+ if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_TLS_HANDSHAKE, \
+ secureConnectionStart, connectEnd); \
+ } \
+ \
+ if (!connectStart.IsNull() && !connectEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_TCP_CONNECTION_2, connectStart, \
+ connectEnd); \
+ } \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT, asyncOpen, \
+ requestStart); \
+ \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED, requestStart, \
+ responseEnd); \
+ \
+ if (cacheReadStart.IsNull() && !responseStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_RECEIVED, asyncOpen, \
+ responseStart); \
+ } \
+ } \
+ \
+ if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, asyncOpen, \
+ cacheReadStart); \
+ \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_CACHE_READ_TIME_V2, cacheReadStart, \
+ cacheReadEnd); \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_REVALIDATION, \
+ requestStart, responseEnd); \
+ } \
+ } \
+ \
+ if (!cacheReadEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, cacheReadEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, asyncOpen, \
+ cacheReadEnd); \
+ } else if (!responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, responseEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, asyncOpen, \
+ responseEnd); \
+ }
+
+ // Glean instrumentation of metrics previously collected via Geckoview
+ // Streaming.
+ if (aDefaultRequest) {
+ if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) {
+ mozilla::glean::network::first_from_cache.AccumulateRawDuration(
+ cacheReadStart - asyncOpen);
+ }
+ if (!connectEnd.IsNull()) {
+ if (!connectStart.IsNull()) {
+ mozilla::glean::network::tcp_connection.AccumulateRawDuration(
+ connectEnd - connectStart);
+ }
+ if (!secureConnectionStart.IsNull()) {
+ mozilla::glean::network::tls_handshake.AccumulateRawDuration(
+ connectEnd - secureConnectionStart);
+ }
+ }
+ if (!domainLookupStart.IsNull()) {
+ mozilla::glean::network::dns_start.AccumulateRawDuration(
+ domainLookupStart - asyncOpen);
+ if (!domainLookupEnd.IsNull()) {
+ mozilla::glean::network::dns_end.AccumulateRawDuration(
+ domainLookupEnd - domainLookupStart);
+ }
+ }
+ }
+
+ if (aDefaultRequest) {
+ HTTP_REQUEST_HISTOGRAMS(PAGE)
+ } else {
+ HTTP_REQUEST_HISTOGRAMS(SUB)
+ }
+
+ if ((useHttp3 || supportHttp3) && cacheReadStart.IsNull() &&
+ cacheReadEnd.IsNull()) {
+ nsCString key = (useHttp3) ? ((aDefaultRequest) ? "uses_http3_page"_ns
+ : "uses_http3_sub"_ns)
+ : ((aDefaultRequest) ? "supports_http3_page"_ns
+ : "supports_http3_sub"_ns);
+
+ if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_TLS_HANDSHAKE, key,
+ secureConnectionStart, connectEnd);
+ }
+
+ if (supportHttp3 && !connectStart.IsNull() && !connectEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::SUP_HTTP3_TCP_CONNECTION, key,
+ connectStart, connectEnd);
+ }
+
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_SENT, key,
+ asyncOpen, requestStart);
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP3_FIRST_SENT_TO_LAST_RECEIVED, key, requestStart,
+ responseEnd);
+
+ if (!responseStart.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_RECEIVED,
+ key, asyncOpen, responseStart);
+ }
+
+ if (!responseEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_COMPLETE_LOAD, key,
+ asyncOpen, responseEnd);
+ }
+ }
+ }
+
+ bool hasHTTPSRR = false;
+ if (httpChannel && NS_SUCCEEDED(httpChannel->GetHasHTTPSRR(&hasHTTPSRR)) &&
+ cacheReadStart.IsNull() && cacheReadEnd.IsNull() &&
+ !requestStart.IsNull()) {
+ nsCString key = (hasHTTPSRR) ? ((aDefaultRequest) ? "uses_https_rr_page"_ns
+ : "uses_https_rr_sub"_ns)
+ : ((aDefaultRequest) ? "no_https_rr_page"_ns
+ : "no_https_rr_sub"_ns);
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTPS_RR_OPEN_TO_FIRST_SENT, key,
+ asyncOpen, requestStart);
+ TimeDuration elapsed = requestStart - asyncOpen;
+ if (hasHTTPSRR) {
+ if (aDefaultRequest) {
+ glean::networking::http_channel_page_open_to_first_sent_https_rr
+ .AccumulateRawDuration(elapsed);
+ } else {
+ glean::networking::http_channel_sub_open_to_first_sent_https_rr
+ .AccumulateRawDuration(elapsed);
+ }
+ } else {
+ if (aDefaultRequest) {
+ glean::networking::http_channel_page_open_to_first_sent
+ .AccumulateRawDuration(elapsed);
+ } else {
+ glean::networking::http_channel_sub_open_to_first_sent
+ .AccumulateRawDuration(elapsed);
+ }
+ }
+ }
+
+#undef HTTP_REQUEST_HISTOGRAMS
+}
+
+nsresult nsLoadGroup::MergeLoadFlags(nsIRequest* aRequest,
+ nsLoadFlags& outFlags) {
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+
+ // Inherit the following bits...
+ flags |= (mLoadFlags &
+ (LOAD_BACKGROUND | LOAD_BYPASS_CACHE | LOAD_FROM_CACHE |
+ VALIDATE_ALWAYS | VALIDATE_ONCE_PER_SESSION | VALIDATE_NEVER));
+
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::MergeDefaultLoadFlags(nsIRequest* aRequest,
+ nsLoadFlags& outFlags) {
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::Init() {
+ mRequestContextService = RequestContextService::GetOrCreate();
+ if (mRequestContextService) {
+ Unused << mRequestContextService->NewRequestContext(
+ getter_AddRefs(mRequestContext));
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ Unused << os->AddObserver(this, "last-pb-context-exited", true);
+
+ return NS_OK;
+}
+
+nsresult nsLoadGroup::InitWithRequestContextId(
+ const uint64_t& aRequestContextId) {
+ mRequestContextService = RequestContextService::GetOrCreate();
+ if (mRequestContextService) {
+ Unused << mRequestContextService->GetRequestContext(
+ aRequestContextId, getter_AddRefs(mRequestContext));
+ }
+ mExternalRequestContext = true;
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ Unused << os->AddObserver(this, "last-pb-context-exited", true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(!strcmp(aTopic, "last-pb-context-exited"));
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
+ if (attrs.mPrivateBrowsingId == 0) {
+ return NS_OK;
+ }
+
+ mBrowsingContextDiscarded = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetIsBrowsingContextDiscarded(bool* aIsBrowsingContextDiscarded) {
+ *aIsBrowsingContextDiscarded = mBrowsingContextDiscarded;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/base/nsLoadGroup.h b/netwerk/base/nsLoadGroup.h
new file mode 100644
index 0000000000..055e1804d6
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadGroup_h__
+#define nsLoadGroup_h__
+
+#include "nsILoadGroup.h"
+#include "nsILoadGroupChild.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include "nsISupportsPriority.h"
+#include "PLDHashTable.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIRequestContext;
+class nsIRequestContextService;
+class nsITimedChannel;
+
+namespace mozilla {
+namespace net {
+
+class nsLoadGroup : public nsILoadGroup,
+ public nsILoadGroupChild,
+ public nsIObserver,
+ public nsISupportsPriority,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsIRequest methods:
+ NS_DECL_NSIREQUEST
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroup methods:
+ NS_DECL_NSILOADGROUP
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroupChild methods:
+ NS_DECL_NSILOADGROUPCHILD
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsISupportsPriority methods:
+ NS_DECL_NSISUPPORTSPRIORITY
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsIObserver methods:
+ NS_DECL_NSIOBSERVER
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsLoadGroup methods:
+
+ nsLoadGroup();
+
+ nsresult Init();
+ nsresult InitWithRequestContextId(const uint64_t& aRequestContextId);
+
+ void SetGroupObserver(nsIRequestObserver* aObserver,
+ bool aIncludeBackgroundRequests);
+
+ protected:
+ virtual ~nsLoadGroup();
+
+ nsresult MergeLoadFlags(nsIRequest* aRequest, nsLoadFlags& flags);
+ nsresult MergeDefaultLoadFlags(nsIRequest* aRequest, nsLoadFlags& flags);
+
+ private:
+ void TelemetryReport();
+ void TelemetryReportChannel(nsITimedChannel* timedChannel,
+ bool defaultRequest);
+
+ nsresult RemoveRequestFromHashtable(nsIRequest* aRequest, nsresult aStatus);
+ nsresult NotifyRemovalObservers(nsIRequest* aRequest, nsresult aStatus);
+
+ protected:
+ uint32_t mForegroundCount{0};
+ uint32_t mLoadFlags{LOAD_NORMAL};
+ uint32_t mDefaultLoadFlags{0};
+ int32_t mPriority{PRIORITY_NORMAL};
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup; // load groups can contain load groups
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ nsCOMPtr<nsIRequest> mDefaultLoadRequest;
+ PLDHashTable mRequests;
+
+ nsWeakPtr mObserver;
+ nsWeakPtr mParentLoadGroup;
+
+ nsresult mStatus{NS_OK};
+ bool mIsCanceling{false};
+ bool mDefaultLoadIsTimed{false};
+ bool mBrowsingContextDiscarded{false};
+ bool mExternalRequestContext{false};
+ bool mNotifyObserverAboutBackgroundRequests{false};
+
+ /* Telemetry */
+ mozilla::TimeStamp mDefaultRequestCreationTime;
+ uint32_t mTimedRequests{0};
+ uint32_t mCachedRequests{0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsLoadGroup_h__
diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp
new file mode 100644
index 0000000000..d0d9fe09a5
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -0,0 +1,500 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#include "nsMIMEInputStream.h"
+
+#include <utility>
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIInputStreamLength.h"
+#include "nsIMIMEInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsString.h"
+
+using namespace mozilla::ipc;
+using mozilla::Maybe;
+
+class nsMIMEInputStream : public nsIMIMEInputStream,
+ public nsISeekableStream,
+ public nsIIPCSerializableInputStream,
+ public nsIAsyncInputStream,
+ public nsIInputStreamCallback,
+ public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength,
+ public nsIInputStreamLengthCallback,
+ public nsICloneableInputStream {
+ virtual ~nsMIMEInputStream() = default;
+
+ public:
+ nsMIMEInputStream() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIMIMEINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIINPUTSTREAMLENGTH
+ NS_DECL_NSIASYNCINPUTSTREAMLENGTH
+ NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+ private:
+ struct MOZ_STACK_CLASS ReadSegmentsState {
+ nsCOMPtr<nsIInputStream> mThisStream;
+ nsWriteSegmentFun mWriter{nullptr};
+ void* mClosure{nullptr};
+ };
+ static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+ bool IsSeekableInputStream() const;
+ bool IsAsyncInputStream() const;
+ bool IsInputStreamLength() const;
+ bool IsAsyncInputStreamLength() const;
+ bool IsCloneableInputStream() const;
+
+ nsTArray<HeaderEntry> mHeaders;
+
+ nsCOMPtr<nsIInputStream> mStream;
+ mozilla::Atomic<bool, mozilla::Relaxed> mStartedReading{false};
+
+ mozilla::Mutex mMutex{"nsMIMEInputStream::mMutex"};
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mMutex);
+
+ // This is protected by mutex.
+ nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback;
+};
+
+NS_IMPL_ADDREF(nsMIMEInputStream)
+NS_IMPL_RELEASE(nsMIMEInputStream)
+
+NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_MIMEINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsITellableStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, IsSeekableInputStream())
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, IsAsyncInputStream())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
+ IsAsyncInputStream())
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength,
+ IsInputStreamLength())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength,
+ IsAsyncInputStreamLength())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback,
+ IsAsyncInputStreamLength())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
+ IsCloneableInputStream())
+ NS_IMPL_QUERY_CLASSINFO(nsMIMEInputStream)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream, nsIMIMEInputStream,
+ nsIAsyncInputStream, nsIInputStream,
+ nsISeekableStream, nsITellableStream)
+
+NS_IMETHODIMP
+nsMIMEInputStream::AddHeader(const char* aName, const char* aValue) {
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+
+ HeaderEntry* entry = mHeaders.AppendElement();
+ entry->name().Append(aName);
+ entry->value().Append(aValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor* visitor) {
+ nsresult rv;
+
+ for (auto& header : mHeaders) {
+ rv = visitor->VisitHeader(header.name(), header.value());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::SetData(nsIInputStream* aStream) {
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+
+ mStream = aStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetData(nsIInputStream** aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+ *aStream = do_AddRef(mStream).take();
+ return NS_OK;
+}
+
+#define INITSTREAMS \
+ if (!mStartedReading) { \
+ NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \
+ mStartedReading = true; \
+ }
+
+// Reset mStartedReading when Seek-ing to start
+NS_IMETHODIMP
+nsMIMEInputStream::Seek(int32_t whence, int64_t offset) {
+ NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+
+ if (whence == NS_SEEK_SET && offset == 0) {
+ rv = stream->Seek(whence, offset);
+ if (NS_SUCCEEDED(rv)) mStartedReading = false;
+ } else {
+ INITSTREAMS;
+ rv = stream->Seek(whence, offset);
+ }
+
+ return rv;
+}
+
+// Proxy ReadSegments since we need to be a good little nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ INITSTREAMS;
+ ReadSegmentsState state;
+ // Disambiguate ambiguous nsIInputStream.
+ state.mThisStream =
+ static_cast<nsIInputStream*>(static_cast<nsIMIMEInputStream*>(this));
+ state.mWriter = aWriter;
+ state.mClosure = aClosure;
+ return mStream->ReadSegments(ReadSegCb, &state, aCount, _retval);
+}
+
+nsresult nsMIMEInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ ReadSegmentsState* state = (ReadSegmentsState*)aClosure;
+ return (state->mWriter)(state->mThisStream, state->mClosure, aFromRawSegment,
+ aToOffset, aCount, aWriteCount);
+}
+
+/**
+ * Forward everything else to the mStream after calling INITSTREAMS
+ */
+
+// nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::Close(void) {
+ INITSTREAMS;
+ return mStream->Close();
+}
+NS_IMETHODIMP nsMIMEInputStream::Available(uint64_t* _retval) {
+ INITSTREAMS;
+ return mStream->Available(_retval);
+}
+NS_IMETHODIMP nsMIMEInputStream::StreamStatus() {
+ INITSTREAMS;
+ return mStream->StreamStatus();
+}
+NS_IMETHODIMP nsMIMEInputStream::Read(char* buf, uint32_t count,
+ uint32_t* _retval) {
+ INITSTREAMS;
+ return mStream->Read(buf, count, _retval);
+}
+NS_IMETHODIMP nsMIMEInputStream::IsNonBlocking(bool* aNonBlocking) {
+ INITSTREAMS;
+ return mStream->IsNonBlocking(aNonBlocking);
+}
+
+// nsIAsyncInputStream
+NS_IMETHODIMP
+nsMIMEInputStream::CloseWithStatus(nsresult aStatus) {
+ INITSTREAMS;
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
+ return asyncStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ INITSTREAMS;
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
+ if (NS_WARN_IF(!asyncStream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ if (NS_WARN_IF(mAsyncWaitCallback && aCallback &&
+ mAsyncWaitCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+ }
+
+ return asyncStream->AsyncWait(callback, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+nsMIMEInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+
+ // We have been canceled in the meanwhile.
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncWaitCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamReady(this);
+}
+
+// nsITellableStream
+NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t* _retval) {
+ INITSTREAMS;
+ nsCOMPtr<nsITellableStream> stream = do_QueryInterface(mStream);
+ return stream->Tell(_retval);
+}
+
+// nsISeekableStream
+NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) {
+ INITSTREAMS;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ return stream->SetEOF();
+}
+
+/**
+ * Factory method used by do_CreateInstance
+ */
+
+nsresult nsMIMEInputStreamConstructor(REFNSIID iid, void** result) {
+ *result = nullptr;
+
+ RefPtr<nsMIMEInputStream> inst = new nsMIMEInputStream();
+ if (!inst) return NS_ERROR_OUT_OF_MEMORY;
+
+ return inst->QueryInterface(iid, result);
+}
+
+void nsMIMEInputStream::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ if (nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(mStream)) {
+ InputStreamHelper::SerializedComplexity(mStream, aMaxSize, aSizeUsed,
+ aPipes, aTransferables);
+ } else {
+ *aPipes = 1;
+ }
+}
+
+void nsMIMEInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize,
+ uint32_t* aSizeUsed) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ MIMEInputStreamParams params;
+ params.headers() = mHeaders.Clone();
+ params.startedReading() = mStartedReading;
+
+ if (!mStream) {
+ aParams = params;
+ return;
+ }
+
+ InputStreamParams wrappedParams;
+
+ if (nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(mStream)) {
+ InputStreamHelper::SerializeInputStream(mStream, wrappedParams, aMaxSize,
+ aSizeUsed);
+ } else {
+ // Falling back to sending the underlying stream over a pipe when
+ // sending an nsMIMEInputStream over IPC is potentially wasteful
+ // if it is sent several times. This can possibly happen with
+ // fission. There are two ways to improve this, see bug 1648369
+ // and bug 1648370.
+ InputStreamHelper::SerializeInputStreamAsPipe(mStream, wrappedParams);
+ }
+
+ NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
+ "Wrapped stream failed to serialize!");
+
+ params.optionalStream().emplace(wrappedParams);
+ aParams = params;
+}
+
+bool nsMIMEInputStream::Deserialize(const InputStreamParams& aParams) {
+ if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const MIMEInputStreamParams& params = aParams.get_MIMEInputStreamParams();
+ const Maybe<InputStreamParams>& wrappedParams = params.optionalStream();
+
+ if (wrappedParams.isSome()) {
+ nsCOMPtr<nsIInputStream> stream;
+ stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref());
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(SetData(stream));
+ }
+
+ mHeaders = params.headers().Clone();
+ mStartedReading = params.startedReading();
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::Length(int64_t* aLength) {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return stream->Length(aLength);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback,
+ nsIEventTarget* aEventTarget) {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ mAsyncInputStreamLengthCallback = aCallback;
+ }
+
+ return stream->AsyncLengthWait(callback, aEventTarget);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream,
+ int64_t aLength) {
+ nsCOMPtr<nsIInputStreamLengthCallback> callback;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ // We have been canceled in the meanwhile.
+ if (!mAsyncInputStreamLengthCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncInputStreamLengthCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamLengthReady(this, aLength);
+}
+
+bool nsMIMEInputStream::IsSeekableInputStream() const {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ return !!seekable;
+}
+
+bool nsMIMEInputStream::IsAsyncInputStream() const {
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
+ return !!asyncStream;
+}
+
+bool nsMIMEInputStream::IsInputStreamLength() const {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ return !!stream;
+}
+
+bool nsMIMEInputStream::IsAsyncInputStreamLength() const {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ return !!stream;
+}
+
+bool nsMIMEInputStream::IsCloneableInputStream() const {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ return !!stream;
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetCloneable(bool* aCloneable) {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return stream->GetCloneable(aCloneable);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::Clone(nsIInputStream** aResult) {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv = stream->Clone(getter_AddRefs(clonedStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIMIMEInputStream> mimeStream = new nsMIMEInputStream();
+
+ rv = mimeStream->SetData(clonedStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (const HeaderEntry& entry : mHeaders) {
+ rv = mimeStream->AddHeader(entry.name().get(), entry.value().get());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ static_cast<nsMIMEInputStream*>(mimeStream.get())->mStartedReading =
+ static_cast<bool>(mStartedReading);
+
+ mimeStream.forget(aResult);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsMIMEInputStream.h b/netwerk/base/nsMIMEInputStream.h
new file mode 100644
index 0000000000..adfc5532b1
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#ifndef _nsMIMEInputStream_h_
+#define _nsMIMEInputStream_h_
+
+#include "ErrorList.h"
+#include "nsID.h"
+
+#define NS_MIMEINPUTSTREAM_CONTRACTID "@mozilla.org/network/mime-input-stream;1"
+#define NS_MIMEINPUTSTREAM_CID \
+ { /* 58a1c31c-1dd2-11b2-a3f6-d36949d48268 */ \
+ 0x58a1c31c, 0x1dd2, 0x11b2, { \
+ 0xa3, 0xf6, 0xd3, 0x69, 0x49, 0xd4, 0x82, 0x68 \
+ } \
+ }
+
+extern nsresult nsMIMEInputStreamConstructor(REFNSIID iid, void** result);
+
+#endif // _nsMIMEInputStream_h_
diff --git a/netwerk/base/nsMediaFragmentURIParser.cpp b/netwerk/base/nsMediaFragmentURIParser.cpp
new file mode 100644
index 0000000000..bfa82530c6
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.cpp
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTArray.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsEscape.h"
+#include "nsIURI.h"
+#include <utility>
+
+#include "nsMediaFragmentURIParser.h"
+
+using std::make_pair;
+using std::pair;
+
+namespace mozilla {
+namespace net {
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsIURI* aURI)
+ : mClipUnit(eClipUnit_Pixel) {
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+ Parse(ref);
+}
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsCString& aRef)
+ : mClipUnit(eClipUnit_Pixel) {
+ Parse(aRef);
+}
+
+bool nsMediaFragmentURIParser::ParseNPT(nsDependentSubstring aString) {
+ nsDependentSubstring original(aString);
+ if (aString.Length() > 4 && aString[0] == 'n' && aString[1] == 'p' &&
+ aString[2] == 't' && aString[3] == ':') {
+ aString.Rebind(aString, 4);
+ }
+
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ double start = -1.0;
+ double end = -1.0;
+
+ ParseNPTTime(aString, start);
+
+ if (aString.Length() == 0) {
+ mStart.emplace(start);
+ return true;
+ }
+
+ if (aString[0] != ',') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+
+ if (aString.Length() == 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ ParseNPTTime(aString, end);
+
+ if (end <= start || aString.Length() != 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ mStart.emplace(start);
+ mEnd.emplace(end);
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTTime(nsDependentSubstring& aString,
+ double& aTime) {
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ return ParseNPTHHMMSS(aString, aTime) || ParseNPTMMSS(aString, aTime) ||
+ ParseNPTSec(aString, aTime);
+}
+
+// Return true if the given character is a numeric character
+static bool IsDigit(nsDependentSubstring::char_type aChar) {
+ return (aChar >= '0' && aChar <= '9');
+}
+
+// Return the index of the first character in the string that is not
+// a numerical digit, starting from 'aStart'.
+static uint32_t FirstNonDigit(nsDependentSubstring& aString, uint32_t aStart) {
+ while (aStart < aString.Length() && IsDigit(aString[aStart])) {
+ ++aStart;
+ }
+ return aStart;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSec(nsDependentSubstring& aString,
+ double& aSec) {
+ nsDependentSubstring original(aString);
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ double fraction = 0.0;
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aSec = s + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMMSS(nsDependentSubstring& aString,
+ double& aTime) {
+ nsDependentSubstring original(aString);
+ uint32_t mm = 0;
+ uint32_t ss = 0;
+ double fraction = 0.0;
+ if (!ParseNPTMM(aString, mm)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTSS(aString, ss)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+ aTime = mm * 60 + ss + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTFraction(nsDependentSubstring& aString,
+ double& aFraction) {
+ double fraction = 0.0;
+
+ if (aString.Length() > 0 && aString[0] == '.') {
+ uint32_t index = FirstNonDigit(aString, 1);
+
+ if (index > 1) {
+ nsDependentSubstring number(aString, 0, index);
+ nsresult ec;
+ fraction = PromiseFlatString(number).ToDouble(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+ }
+ aString.Rebind(aString, index);
+ }
+
+ aFraction = fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHHMMSS(nsDependentSubstring& aString,
+ double& aTime) {
+ nsDependentSubstring original(aString);
+ uint32_t hh = 0;
+ double seconds = 0.0;
+ if (!ParseNPTHH(aString, hh)) {
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTMMSS(aString, seconds)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aTime = hh * 3600 + seconds;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHH(nsDependentSubstring& aString,
+ uint32_t& aHour) {
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t u = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aHour = u;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMM(nsDependentSubstring& aString,
+ uint32_t& aMinute) {
+ return ParseNPTSS(aString, aMinute);
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSS(nsDependentSubstring& aString,
+ uint32_t& aSecond) {
+ if (aString.Length() < 2) {
+ return false;
+ }
+
+ if (IsDigit(aString[0]) && IsDigit(aString[1])) {
+ nsDependentSubstring n(aString, 0, 2);
+ nsresult ec;
+ int32_t u = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, 2);
+ if (u >= 60) return false;
+
+ aSecond = u;
+ return true;
+ }
+
+ return false;
+}
+
+static bool ParseInteger(nsDependentSubstring& aString, int32_t& aResult) {
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aResult = s;
+ return true;
+}
+
+static bool ParseCommaSeparator(nsDependentSubstring& aString) {
+ if (aString.Length() > 1 && aString[0] == ',') {
+ aString.Rebind(aString, 1);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsMediaFragmentURIParser::ParseXYWH(nsDependentSubstring aString) {
+ int32_t x, y, w, h;
+ ClipUnit clipUnit;
+
+ // Determine units.
+ if (StringBeginsWith(aString, u"pixel:"_ns)) {
+ clipUnit = eClipUnit_Pixel;
+ aString.Rebind(aString, 6);
+ } else if (StringBeginsWith(aString, u"percent:"_ns)) {
+ clipUnit = eClipUnit_Percent;
+ aString.Rebind(aString, 8);
+ } else {
+ clipUnit = eClipUnit_Pixel;
+ }
+
+ // Read and validate coordinates.
+ if (ParseInteger(aString, x) && x >= 0 && ParseCommaSeparator(aString) &&
+ ParseInteger(aString, y) && y >= 0 && ParseCommaSeparator(aString) &&
+ ParseInteger(aString, w) && w > 0 && ParseCommaSeparator(aString) &&
+ ParseInteger(aString, h) && h > 0 && aString.Length() == 0) {
+ // Reject invalid percentage coordinates.
+ if (clipUnit == eClipUnit_Percent && (x + w > 100 || y + h > 100)) {
+ return false;
+ }
+
+ mClip.emplace(x, y, w, h);
+ mClipUnit = clipUnit;
+ return true;
+ }
+
+ return false;
+}
+
+void nsMediaFragmentURIParser::Parse(nsACString& aRef) {
+ // Create an array of possibly-invalid media fragments.
+ nsTArray<std::pair<nsCString, nsCString> > fragments;
+
+ for (const nsACString& nv : nsCCharSeparatedTokenizer(aRef, '&').ToRange()) {
+ int32_t index = nv.FindChar('=');
+ if (index >= 0) {
+ nsAutoCString name;
+ nsAutoCString value;
+ NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name);
+ NS_UnescapeURL(Substring(nv, index + 1, nv.Length()),
+ esc_Ref | esc_AlwaysCopy, value);
+ fragments.AppendElement(make_pair(name, value));
+ }
+ }
+
+ // Parse the media fragment values.
+ bool gotTemporal = false, gotSpatial = false;
+ for (int i = fragments.Length() - 1; i >= 0; --i) {
+ if (gotTemporal && gotSpatial) {
+ // We've got one of each possible type. No need to look at the rest.
+ break;
+ }
+ if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotTemporal = ParseNPT(nsDependentSubstring(value, 0));
+ } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotSpatial = ParseXYWH(nsDependentSubstring(value, 0));
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsMediaFragmentURIParser.h b/netwerk/base/nsMediaFragmentURIParser.h
new file mode 100644
index 0000000000..4c277fead8
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(nsMediaFragmentURIParser_h__)
+# define nsMediaFragmentURIParser_h__
+
+# include "mozilla/Maybe.h"
+# include "nsStringFwd.h"
+# include "nsRect.h"
+
+class nsIURI;
+
+// Class to handle parsing of a W3C media fragment URI as per
+// spec at: http://www.w3.org/TR/media-frags/
+// Only the temporaral URI portion of the spec is implemented.
+// To use:
+// a) Construct an instance with the URI containing the fragment
+// b) Check for the validity of the values you are interested in
+// using e.g. HasStartTime().
+// c) If the values are valid, obtain them using e.g. GetStartTime().
+
+namespace mozilla {
+namespace net {
+
+enum ClipUnit {
+ eClipUnit_Pixel,
+ eClipUnit_Percent,
+};
+
+class nsMediaFragmentURIParser {
+ public:
+ // Create a parser with the provided URI.
+ explicit nsMediaFragmentURIParser(nsIURI* aURI);
+
+ // Create a parser with the provided URI reference portion.
+ explicit nsMediaFragmentURIParser(nsCString& aRef);
+
+ // True if a valid temporal media fragment indicated a start time.
+ bool HasStartTime() const { return mStart.isSome(); }
+
+ // If a valid temporal media fragment indicated a start time, returns
+ // it in units of seconds. If not, defaults to 0.
+ double GetStartTime() const { return *mStart; }
+
+ // True if a valid temporal media fragment indicated an end time.
+ bool HasEndTime() const { return mEnd.isSome(); }
+
+ // If a valid temporal media fragment indicated an end time, returns
+ // it in units of seconds. If not, defaults to -1.
+ double GetEndTime() const { return *mEnd; }
+
+ // True if a valid spatial media fragment indicated a clipping region.
+ bool HasClip() const { return mClip.isSome(); }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the region. If not, returns an empty region. The unit
+ // used depends on the value returned by GetClipUnit().
+ nsIntRect GetClip() const { return *mClip; }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the unit used.
+ ClipUnit GetClipUnit() const { return mClipUnit; }
+
+ private:
+ // Parse the URI ref provided, looking for media fragments. This is
+ // the top-level parser the invokes the others below.
+ void Parse(nsACString& aRef);
+
+ // The following methods parse the fragment as per the media
+ // fragments specification. 'aString' contains the remaining
+ // fragment data to be parsed. The method returns true
+ // if the parse was successful and leaves the remaining unparsed
+ // data in 'aString'. If the parse fails then false is returned
+ // and 'aString' is left as it was when called.
+ bool ParseNPT(nsDependentSubstring aString);
+ bool ParseNPTTime(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTSec(nsDependentSubstring& aString, double& aSec);
+ bool ParseNPTFraction(nsDependentSubstring& aString, double& aFraction);
+ bool ParseNPTMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour);
+ bool ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute);
+ bool ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond);
+ bool ParseXYWH(nsDependentSubstring aString);
+ bool ParseMozResolution(nsDependentSubstring aString);
+
+ // Media fragment information.
+ Maybe<double> mStart;
+ Maybe<double> mEnd;
+ Maybe<nsIntRect> mClip;
+ ClipUnit mClipUnit;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsNetAddr.cpp b/netwerk/base/nsNetAddr.cpp
new file mode 100644
index 0000000000..d0cbe69bae
--- /dev/null
+++ b/netwerk/base/nsNetAddr.cpp
@@ -0,0 +1,138 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNetAddr.h"
+#include "nsString.h"
+#include "mozilla/net/DNS.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsNetAddr, nsINetAddr)
+
+NS_IMETHODIMP nsNetAddr::GetFamily(uint16_t* aFamily) {
+ switch (mAddr.raw.family) {
+ case AF_INET:
+ *aFamily = nsINetAddr::FAMILY_INET;
+ break;
+ case AF_INET6:
+ *aFamily = nsINetAddr::FAMILY_INET6;
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ *aFamily = nsINetAddr::FAMILY_LOCAL;
+ break;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetAddress(nsACString& aAddress) {
+ switch (mAddr.raw.family) {
+ /* PR_NetAddrToString can handle INET and INET6, but not LOCAL. */
+ case AF_INET:
+ aAddress.SetLength(kIPv4CStrBufSize);
+ mAddr.ToStringBuffer(aAddress.BeginWriting(), kIPv4CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+ case AF_INET6:
+ aAddress.SetLength(kIPv6CStrBufSize);
+ mAddr.ToStringBuffer(aAddress.BeginWriting(), kIPv6CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ aAddress.Assign(mAddr.local.path);
+ break;
+#endif
+ // PR_AF_LOCAL falls through to default when not XP_UNIX
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetPort(uint16_t* aPort) {
+ switch (mAddr.raw.family) {
+ case AF_INET:
+ *aPort = ntohs(mAddr.inet.port);
+ break;
+ case AF_INET6:
+ *aPort = ntohs(mAddr.inet6.port);
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ // There is no port number for local / connections.
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetFlow(uint32_t* aFlow) {
+ switch (mAddr.raw.family) {
+ case AF_INET6:
+ *aFlow = ntohl(mAddr.inet6.flowinfo);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetScope(uint32_t* aScope) {
+ switch (mAddr.raw.family) {
+ case AF_INET6:
+ *aScope = ntohl(mAddr.inet6.scope_id);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetIsV4Mapped(bool* aIsV4Mapped) {
+ switch (mAddr.raw.family) {
+ case AF_INET6:
+ *aIsV4Mapped = IPv6ADDR_IS_V4MAPPED(&mAddr.inet6.ip);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetNetAddr(NetAddr* aResult) {
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
diff --git a/netwerk/base/nsNetAddr.h b/netwerk/base/nsNetAddr.h
new file mode 100644
index 0000000000..49d43ccaee
--- /dev/null
+++ b/netwerk/base/nsNetAddr.h
@@ -0,0 +1,30 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetAddr_h__
+#define nsNetAddr_h__
+
+#include "nsINetAddr.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Attributes.h"
+
+class nsNetAddr final : public nsINetAddr {
+ ~nsNetAddr() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETADDR
+
+ explicit nsNetAddr(const mozilla::net::NetAddr* addr) : mAddr(*addr) {}
+
+ private:
+ mozilla::net::NetAddr mAddr;
+
+ protected:
+ /* additional members */
+};
+
+#endif // !nsNetAddr_h__
diff --git a/netwerk/base/nsNetSegmentUtils.h b/netwerk/base/nsNetSegmentUtils.h
new file mode 100644
index 0000000000..a65defdb50
--- /dev/null
+++ b/netwerk/base/nsNetSegmentUtils.h
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetSegmentUtils_h__
+#define nsNetSegmentUtils_h__
+
+#include "nsIOService.h"
+
+/**
+ * applies defaults to segment params in a consistent way.
+ */
+static inline void net_ResolveSegmentParams(uint32_t& segsize,
+ uint32_t& segcount) {
+ if (!segsize) segsize = mozilla::net::nsIOService::gDefaultSegmentSize;
+
+ if (!segcount) segcount = mozilla::net::nsIOService::gDefaultSegmentCount;
+}
+
+#endif // !nsNetSegmentUtils_h__
diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp
new file mode 100644
index 0000000000..4c03cf63c3
--- /dev/null
+++ b/netwerk/base/nsNetUtil.cpp
@@ -0,0 +1,4088 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "DecoderDoctorDiagnostics.h"
+#include "HttpLog.h"
+
+#include "nsNetUtil.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Components.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Telemetry.h"
+#include "nsBufferedStreams.h"
+#include "nsCategoryCache.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsFileStreams.h"
+#include "nsHashKeys.h"
+#include "nsHttp.h"
+#include "nsMimeTypes.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptAdapterFactory.h"
+#include "nsIBufferedStreams.h"
+#include "nsBufferedStreams.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentSniffer.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDownloader.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIIDNService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsINode.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsPersistentProperties.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIPropertyBag2.h"
+#include "nsIProtocolProxyService.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "nsRequestObserverProxy.h"
+#include "nsISensitiveInfoHiddenURI.h"
+#include "nsISimpleStreamListener.h"
+#include "nsISocketProvider.h"
+#include "nsIStandardURL.h"
+#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsStringStream.h"
+#include "nsSyncStreamListener.h"
+#include "nsITextToSubURI.h"
+#include "nsIURIWithSpecialOrigin.h"
+#include "nsIViewSourceChannel.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsINestedURI.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "nsIScriptError.h"
+#include "nsISiteSecurityService.h"
+#include "nsHttpHandler.h"
+#include "nsNSSComponent.h"
+#include "nsIRedirectHistoryEntry.h"
+#include "nsICertStorage.h"
+#include "nsICertOverrideService.h"
+#include "nsQueryObject.h"
+#include "mozIThirdPartyUtil.h"
+#include "../mime/nsMIMEHeaderParamImpl.h"
+#include "nsStandardURL.h"
+#include "DefaultURI.h"
+#include "nsChromeProtocolHandler.h"
+#include "nsJSProtocolHandler.h"
+#include "nsDataHandler.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "nsStreamUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsViewSourceHandler.h"
+#include "nsJARURI.h"
+#include "nsIconURI.h"
+#include "nsAboutProtocolHandler.h"
+#include "nsResProtocolHandler.h"
+#include "mozilla/net/ExtensionProtocolHandler.h"
+#include "mozilla/net/PageThumbProtocolHandler.h"
+#include "mozilla/net/SFVService.h"
+#include <limits>
+#include "nsIXPConnect.h"
+#include "nsParserConstants.h"
+#include "nsCRT.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/MediaList.h"
+#include "MediaContainerType.h"
+#include "DecoderTraits.h"
+#include "imgLoader.h"
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+# include "nsNewMailnewsURI.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::net;
+using mozilla::dom::BlobURLProtocolHandler;
+using mozilla::dom::ClientInfo;
+using mozilla::dom::PerformanceStorage;
+using mozilla::dom::ServiceWorkerDescriptor;
+
+#define MAX_RECURSION_COUNT 50
+
+already_AddRefed<nsIIOService> do_GetIOService(nsresult* error /* = 0 */) {
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service();
+ if (error) *error = io ? NS_OK : NS_ERROR_FAILURE;
+ return io.forget();
+}
+
+nsresult NS_NewLocalFileInputStream(nsIInputStream** result, nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> in =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) in.forget(result);
+ }
+ return rv;
+}
+
+Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewLocalFileInputStream(
+ nsIFile* file, int32_t ioFlags /* = -1 */, int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIInputStream> stream;
+ const nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file,
+ ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) out.forget(result);
+ }
+ return rv;
+}
+
+Result<nsCOMPtr<nsIOutputStream>, nsresult> NS_NewLocalFileOutputStream(
+ nsIFile* file, int32_t ioFlags /* = -1 */, int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIOutputStream> stream;
+ const nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), file,
+ ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result,
+ const mozilla::ipc::FileDescriptor& fd) {
+ nsCOMPtr<nsIFileOutputStream> out;
+ nsFileOutputStream::Create(NS_GET_IID(nsIFileOutputStream),
+ getter_AddRefs(out));
+
+ nsresult rv =
+ static_cast<nsFileOutputStream*>(out.get())->InitWithFileDescriptor(fd);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ out.forget(result);
+ return NS_OK;
+}
+
+nsresult net_EnsureIOService(nsIIOService** ios, nsCOMPtr<nsIIOService>& grip) {
+ nsresult rv = NS_OK;
+ if (!*ios) {
+ grip = do_GetIOService(&rv);
+ *ios = grip;
+ }
+ return rv;
+}
+
+nsresult NS_NewFileURI(
+ nsIURI** result, nsIFile* spec,
+ nsIIOService*
+ ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) rv = ioService->NewFileURI(spec, result);
+ return rv;
+}
+
+nsresult NS_GetURIWithNewRef(nsIURI* aInput, const nsACString& aRef,
+ nsIURI** aOutput) {
+ MOZ_DIAGNOSTIC_ASSERT(aRef.IsEmpty() || aRef[0] == '#');
+
+ if (NS_WARN_IF(!aInput || !aOutput)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool hasRef;
+ nsresult rv = aInput->GetHasRef(&hasRef);
+
+ nsAutoCString ref;
+ if (NS_SUCCEEDED(rv)) {
+ rv = aInput->GetRef(ref);
+ }
+
+ // If the ref is already equal to the new ref, we do not need to do anything.
+ // Also, if the GetRef failed (it could return NS_ERROR_NOT_IMPLEMENTED)
+ // we can assume SetRef would fail as well, so returning the original
+ // URI is OK.
+ //
+ // Note that aRef contains the hash, but ref doesn't, so need to account for
+ // that in the equality check.
+ if (NS_FAILED(rv) || (!hasRef && aRef.IsEmpty()) ||
+ (!aRef.IsEmpty() && hasRef &&
+ Substring(aRef.Data() + 1, aRef.Length() - 1) == ref)) {
+ nsCOMPtr<nsIURI> uri = aInput;
+ uri.forget(aOutput);
+ return NS_OK;
+ }
+
+ return NS_MutateURI(aInput).SetRef(aRef).Finalize(aOutput);
+}
+
+nsresult NS_GetURIWithoutRef(nsIURI* aInput, nsIURI** aOutput) {
+ return NS_GetURIWithNewRef(aInput, ""_ns, aOutput);
+}
+
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsILoadInfo* aLoadInfo,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ // NS_NewChannelInternal is mostly called for channel redirects. We should
+ // allow the creation of a channel even if the original channel did not have a
+ // loadinfo attached.
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURIWithLoadInfo(aUri, aLoadInfo,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ nsLoadFlags channelLoadFlags = 0;
+ channel->GetLoadFlags(&channelLoadFlags);
+ // Will be removed when we remove LOAD_REPLACE altogether
+ // This check is trying to catch protocol handlers that still
+ // try to set the LOAD_REPLACE flag.
+ MOZ_DIAGNOSTIC_ASSERT(!(channelLoadFlags & nsIChannel::LOAD_REPLACE));
+#endif
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ rv = channel->SetLoadFlags(aLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aPerformanceStorage) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetPerformanceStorage(aPerformanceStorage);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+namespace {
+
+void AssertLoadingPrincipalAndClientInfoMatch(
+ nsIPrincipal* aLoadingPrincipal, const ClientInfo& aLoadingClientInfo,
+ nsContentPolicyType aType) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // Verify that the provided loading ClientInfo matches the loading
+ // principal. Unfortunately we can't just use nsIPrincipal::Equals() here
+ // because of some corner cases:
+ //
+ // 1. Worker debugger scripts want to use a system loading principal for
+ // worker scripts with a content principal. We exempt these from this
+ // check.
+ // 2. Null principals currently require exact object identity for
+ // nsIPrincipal::Equals() to return true. This doesn't work here because
+ // ClientInfo::GetPrincipal() uses PrincipalInfoToPrincipal() to allocate
+ // a new object. To work around this we compare the principal origin
+ // string itself. If bug 1431771 is fixed then we could switch to
+ // Equals().
+
+ // Allow worker debugger to load with a system principal.
+ if (aLoadingPrincipal->IsSystemPrincipal() &&
+ (aType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE)) {
+ return;
+ }
+
+ // Perform a fast comparison for most principal checks.
+ auto clientPrincipalOrErr(aLoadingClientInfo.GetPrincipal());
+ if (clientPrincipalOrErr.isOk()) {
+ nsCOMPtr<nsIPrincipal> clientPrincipal = clientPrincipalOrErr.unwrap();
+ if (aLoadingPrincipal->Equals(clientPrincipal)) {
+ return;
+ }
+ // Fall back to a slower origin equality test to support null principals.
+ nsAutoCString loadingOriginNoSuffix;
+ MOZ_ALWAYS_SUCCEEDS(
+ aLoadingPrincipal->GetOriginNoSuffix(loadingOriginNoSuffix));
+
+ nsAutoCString clientOriginNoSuffix;
+ MOZ_ALWAYS_SUCCEEDS(
+ clientPrincipal->GetOriginNoSuffix(clientOriginNoSuffix));
+
+ // The client principal will have the partitionKey set if it's in a third
+ // party context, but the loading principal won't. So, we ignore he
+ // partitionKey when doing the verification here.
+ MOZ_DIAGNOSTIC_ASSERT(loadingOriginNoSuffix == clientOriginNoSuffix);
+ MOZ_DIAGNOSTIC_ASSERT(
+ aLoadingPrincipal->OriginAttributesRef().EqualsIgnoringPartitionKey(
+ clientPrincipal->OriginAttributesRef()));
+ }
+#endif
+}
+
+} // namespace
+
+nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */,
+ uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags,
+ aContentPolicyType, aCookieJarSettings, aPerformanceStorage, aLoadGroup,
+ aCallbacks, aLoadFlags, aIoService, aSandboxFlags,
+ aSkipCheckForBrokenURLOrZeroSized);
+}
+
+nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri,
+ nsIPrincipal* aLoadingPrincipal,
+ const ClientInfo& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */,
+ uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ AssertLoadingPrincipalAndClientInfoMatch(
+ aLoadingPrincipal, aLoadingClientInfo, aContentPolicyType);
+
+ Maybe<ClientInfo> loadingClientInfo;
+ loadingClientInfo.emplace(aLoadingClientInfo);
+
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ loadingClientInfo, aController, aSecurityFlags, aContentPolicyType,
+ aCookieJarSettings, aPerformanceStorage, aLoadGroup, aCallbacks,
+ aLoadFlags, aIoService, aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized);
+}
+
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ const Maybe<ClientInfo>& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */, uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURIWithClientAndController(
+ aUri, aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal,
+ aLoadingClientInfo, aController, aSecurityFlags, aContentPolicyType,
+ aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized,
+ getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ nsLoadFlags channelLoadFlags = 0;
+ channel->GetLoadFlags(&channelLoadFlags);
+ // Will be removed when we remove LOAD_REPLACE altogether
+ // This check is trying to catch protocol handlers that still
+ // try to set the LOAD_REPLACE flag.
+ MOZ_DIAGNOSTIC_ASSERT(!(channelLoadFlags & nsIChannel::LOAD_REPLACE));
+#endif
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ rv = channel->SetLoadFlags(aLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aPerformanceStorage || aCookieJarSettings) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ if (aPerformanceStorage) {
+ loadInfo->SetPerformanceStorage(aPerformanceStorage);
+ }
+
+ if (aCookieJarSettings) {
+ loadInfo->SetCookieJarSettings(aCookieJarSettings);
+ }
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ MOZ_ASSERT(aLoadingNode);
+ NS_ASSERTION(aTriggeringPrincipal,
+ "Can not create channel without a triggering Principal!");
+ return NS_NewChannelInternal(
+ outChannel, aUri, aLoadingNode, aLoadingNode->NodePrincipal(),
+ aTriggeringPrincipal, Maybe<ClientInfo>(),
+ Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType,
+ aLoadingNode->OwnerDoc()->CookieJarSettings(), aPerformanceStorage,
+ aLoadGroup, aCallbacks, aLoadFlags, aIoService);
+}
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ NS_ASSERTION(aLoadingPrincipal,
+ "Can not create channel without a loading Principal!");
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal, aTriggeringPrincipal, Maybe<ClientInfo>(),
+ Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType,
+ aCookieJarSettings, aPerformanceStorage, aLoadGroup, aCallbacks,
+ aLoadFlags, aIoService);
+}
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, const ClientInfo& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ AssertLoadingPrincipalAndClientInfoMatch(
+ aLoadingPrincipal, aLoadingClientInfo, aContentPolicyType);
+
+ Maybe<ClientInfo> loadingClientInfo;
+ loadingClientInfo.emplace(aLoadingClientInfo);
+
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal, aTriggeringPrincipal, loadingClientInfo, aController,
+ aSecurityFlags, aContentPolicyType, aCookieJarSettings,
+ aPerformanceStorage, aLoadGroup, aCallbacks, aLoadFlags, aIoService);
+}
+
+nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri,
+ nsINode* aLoadingNode, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */,
+ uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ NS_ASSERTION(aLoadingNode, "Can not create channel without a loading Node!");
+ return NS_NewChannelInternal(
+ outChannel, aUri, aLoadingNode, aLoadingNode->NodePrincipal(),
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags,
+ aContentPolicyType, aLoadingNode->OwnerDoc()->CookieJarSettings(),
+ aPerformanceStorage, aLoadGroup, aCallbacks, aLoadFlags, aIoService,
+ aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized);
+}
+
+nsresult NS_GetIsDocumentChannel(nsIChannel* aChannel, bool* aIsDocument) {
+ // Check if this channel is going to be used to create a document. If it has
+ // LOAD_DOCUMENT_URI set it is trivially creating a document. If
+ // LOAD_HTML_OBJECT_DATA is set it may or may not be used to create a
+ // document, depending on its MIME type.
+
+ if (!aChannel || !aIsDocument) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aIsDocument = false;
+ nsLoadFlags loadFlags;
+ nsresult rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ *aIsDocument = true;
+ return NS_OK;
+ }
+ if (!(loadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA)) {
+ *aIsDocument = false;
+ return NS_OK;
+ }
+ nsAutoCString mimeType;
+ rv = aChannel->GetContentType(mimeType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (nsContentUtils::HtmlObjectContentTypeForMIMEType(mimeType) ==
+ nsIObjectLoadingContent::TYPE_DOCUMENT) {
+ *aIsDocument = true;
+ return NS_OK;
+ }
+ *aIsDocument = false;
+ return NS_OK;
+}
+
+nsresult NS_MakeAbsoluteURI(nsACString& result, const nsACString& spec,
+ nsIURI* baseURI) {
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ } else if (spec.IsEmpty()) {
+ rv = baseURI->GetSpec(result);
+ } else {
+ rv = baseURI->Resolve(spec, result);
+ }
+ return rv;
+}
+
+nsresult NS_MakeAbsoluteURI(char** result, const char* spec, nsIURI* baseURI) {
+ nsresult rv;
+ nsAutoCString resultBuf;
+ rv = NS_MakeAbsoluteURI(resultBuf, nsDependentCString(spec), baseURI);
+ if (NS_SUCCEEDED(rv)) {
+ *result = ToNewCString(resultBuf, mozilla::fallible);
+ if (!*result) rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return rv;
+}
+
+nsresult NS_MakeAbsoluteURI(nsAString& result, const nsAString& spec,
+ nsIURI* baseURI) {
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ } else {
+ nsAutoCString resultBuf;
+ if (spec.IsEmpty()) {
+ rv = baseURI->GetSpec(resultBuf);
+ } else {
+ rv = baseURI->Resolve(NS_ConvertUTF16toUTF8(spec), resultBuf);
+ }
+ if (NS_SUCCEEDED(rv)) CopyUTF8toUTF16(resultBuf, result);
+ }
+ return rv;
+}
+
+int32_t NS_GetDefaultPort(const char* scheme,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+
+ // Getting the default port through the protocol handler previously had a lot
+ // of XPCOM overhead involved. We optimize the protocols that matter for Web
+ // pages (HTTP and HTTPS) by hardcoding their default ports here.
+ //
+ // XXX: This might not be necessary for performance anymore.
+ if (strncmp(scheme, "http", 4) == 0) {
+ if (scheme[4] == 's' && scheme[5] == '\0') {
+ return 443;
+ }
+ if (scheme[4] == '\0') {
+ return 80;
+ }
+ }
+
+ nsCOMPtr<nsIIOService> grip;
+ net_EnsureIOService(&ioService, grip);
+ if (!ioService) return -1;
+
+ int32_t port;
+ rv = ioService->GetDefaultPort(scheme, &port);
+ return NS_SUCCEEDED(rv) ? port : -1;
+}
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool NS_StringToACE(const nsACString& idn, nsACString& result) {
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv) return false;
+ nsresult rv = idnSrv->ConvertUTF8toACE(idn, result);
+ return NS_SUCCEEDED(rv);
+}
+
+int32_t NS_GetRealPort(nsIURI* aURI) {
+ int32_t port;
+ nsresult rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) return -1;
+
+ if (port != -1) return port; // explicitly specified
+
+ // Otherwise, we have to get the default port from the protocol handler
+
+ // Need the scheme first
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return -1;
+
+ return NS_GetDefaultPort(scheme.get());
+}
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamChannel> isc =
+ do_CreateInstance(NS_INPUTSTREAMCHANNEL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = isc->SetURI(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+ rv = isc->SetContentStream(stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(isc, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aContentType.IsEmpty()) {
+ rv = channel->SetContentType(aContentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aContentCharset.IsEmpty()) {
+ rv = channel->SetContentCharset(aContentCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ASSERT(aLoadInfo, "need a loadinfo to create a inputstreamchannel");
+ channel->SetLoadInfo(aLoadInfo);
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (aLoadInfo && aLoadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType) {
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType);
+ if (!loadInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, stream.forget(),
+ aContentType, aContentCharset,
+ loadInfo);
+}
+
+nsresult NS_NewInputStreamChannel(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ const nsACString& aContentType /* = ""_ns */,
+ const nsACString& aContentCharset /* = ""_ns */) {
+ nsCOMPtr<nsIInputStream> stream = aStream;
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, stream.forget(),
+ aContentType, aContentCharset,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags, aContentPolicyType);
+}
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsILoadInfo* aLoadInfo,
+ bool aIsSrcdocChannel /* = false */) {
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> stream;
+ stream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t len;
+ char* utf8Bytes = ToNewUTF8String(aData, &len);
+ rv = stream->AdoptData(utf8Bytes, len);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aUri,
+ stream.forget(), aContentType,
+ "UTF-8"_ns, aLoadInfo);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIsSrcdocChannel) {
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(inStrmChan, NS_ERROR_FAILURE);
+ inStrmChan->SetSrcdocData(aData);
+ }
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, const nsAString& aData,
+ const nsACString& aContentType, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */) {
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType);
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType,
+ loadInfo, aIsSrcdocChannel);
+}
+
+nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */) {
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags, aContentPolicyType,
+ aIsSrcdocChannel);
+}
+
+nsresult NS_NewInputStreamPump(
+ nsIInputStreamPump** aResult, already_AddRefed<nsIInputStream> aStream,
+ uint32_t aSegsize /* = 0 */, uint32_t aSegcount /* = 0 */,
+ bool aCloseWhenDone /* = false */,
+ nsISerialEventTarget* aMainThreadTarget /* = nullptr */) {
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamPump> pump =
+ do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = pump->Init(stream, aSegsize, aSegcount, aCloseWhenDone,
+ aMainThreadTarget);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = nullptr;
+ pump.swap(*aResult);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewLoadGroup(nsILoadGroup** result, nsIRequestObserver* obs) {
+ nsresult rv;
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = group->SetGroupObserver(obs);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ group.swap(*result);
+ }
+ }
+ return rv;
+}
+
+bool NS_IsReasonableHTTPHeaderValue(const nsACString& aValue) {
+ return mozilla::net::nsHttp::IsReasonableHeaderValue(aValue);
+}
+
+bool NS_IsValidHTTPToken(const nsACString& aToken) {
+ return mozilla::net::nsHttp::IsValidToken(aToken);
+}
+
+void NS_TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest) {
+ mozilla::net::nsHttp::TrimHTTPWhitespace(aSource, aDest);
+}
+
+nsresult NS_NewLoadGroup(nsILoadGroup** aResult, nsIPrincipal* aPrincipal) {
+ using mozilla::LoadContext;
+ nsresult rv;
+
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<LoadContext> loadContext = new LoadContext(aPrincipal);
+ rv = group->SetNotificationCallbacks(loadContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ group.forget(aResult);
+ return rv;
+}
+
+bool NS_LoadGroupMatchesPrincipal(nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal) {
+ if (!aPrincipal) {
+ return false;
+ }
+
+ // If this is a null principal then the load group doesn't really matter.
+ // The principal will not be allowed to perform any actions that actually
+ // use the load group. Unconditionally treat null principals as a match.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return true;
+ }
+
+ if (!aLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(nullptr, aLoadGroup, NS_GET_IID(nsILoadContext),
+ getter_AddRefs(loadContext));
+ NS_ENSURE_TRUE(loadContext, false);
+
+ return true;
+}
+
+nsresult NS_NewDownloader(nsIStreamListener** result,
+ nsIDownloadObserver* observer,
+ nsIFile* downloadLocation /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIDownloader> downloader =
+ do_CreateInstance(NS_DOWNLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = downloader->Init(observer, downloadLocation);
+ if (NS_SUCCEEDED(rv)) {
+ downloader.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewIncrementalStreamLoader(
+ nsIIncrementalStreamLoader** result,
+ nsIIncrementalStreamLoaderObserver* observer) {
+ nsresult rv;
+ nsCOMPtr<nsIIncrementalStreamLoader> loader =
+ do_CreateInstance(NS_INCREMENTALSTREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewStreamLoader(
+ nsIStreamLoader** result, nsIStreamLoaderObserver* observer,
+ nsIRequestObserver* requestObserver /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer, requestObserver);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewStreamLoaderInternal(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) {
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannelInternal(
+ getter_AddRefs(channel), aUri, aLoadingNode, aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags,
+ aContentPolicyType,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ aLoadGroup, aCallbacks, aLoadFlags);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewStreamLoader(outStream, aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return channel->AsyncOpen(*outStream);
+}
+
+nsresult NS_NewStreamLoader(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) {
+ NS_ASSERTION(aLoadingNode,
+ "Can not create stream loader without a loading Node!");
+ return NS_NewStreamLoaderInternal(
+ outStream, aUri, aObserver, aLoadingNode, aLoadingNode->NodePrincipal(),
+ aSecurityFlags, aContentPolicyType, aLoadGroup, aCallbacks, aLoadFlags);
+}
+
+nsresult NS_NewStreamLoader(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) {
+ return NS_NewStreamLoaderInternal(outStream, aUri, aObserver,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal, aSecurityFlags,
+ aContentPolicyType, aLoadGroup, aCallbacks,
+ aLoadFlags);
+}
+
+nsresult NS_NewSyncStreamListener(nsIStreamListener** result,
+ nsIInputStream** stream) {
+ nsCOMPtr<nsISyncStreamListener> listener = new nsSyncStreamListener();
+ nsresult rv = listener->GetInputStream(stream);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_ImplementChannelOpen(nsIChannel* channel, nsIInputStream** result) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener),
+ getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->AsyncOpen(listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t n;
+ // block until the initial response is received or an error occurs.
+ rv = stream->Available(&n);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = nullptr;
+ stream.swap(*result);
+
+ return NS_OK;
+}
+
+nsresult NS_NewRequestObserverProxy(nsIRequestObserver** result,
+ nsIRequestObserver* observer,
+ nsISupports* context) {
+ nsCOMPtr<nsIRequestObserverProxy> proxy = new nsRequestObserverProxy();
+ nsresult rv = proxy->Init(observer, context);
+ if (NS_SUCCEEDED(rv)) {
+ proxy.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_NewSimpleStreamListener(
+ nsIStreamListener** result, nsIOutputStream* sink,
+ nsIRequestObserver* observer /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsISimpleStreamListener> listener =
+ do_CreateInstance(NS_SIMPLESTREAMLISTENER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = listener->Init(sink, observer);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_CheckPortSafety(int32_t port, const char* scheme,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ bool allow;
+ rv = ioService->AllowPort(port, scheme, &allow);
+ if (NS_SUCCEEDED(rv) && !allow) {
+ NS_WARNING("port blocked");
+ rv = NS_ERROR_PORT_ACCESS_NOT_ALLOWED;
+ }
+ }
+ return rv;
+}
+
+nsresult NS_CheckPortSafety(nsIURI* uri) {
+ int32_t port;
+ nsresult rv = uri->GetPort(&port);
+ if (NS_FAILED(rv) || port == -1) { // port undefined or default-valued
+ return NS_OK;
+ }
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ return NS_CheckPortSafety(port, scheme.get());
+}
+
+nsresult NS_NewProxyInfo(const nsACString& type, const nsACString& host,
+ int32_t port, uint32_t flags, nsIProxyInfo** result) {
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = pps->NewProxyInfo(type, host, port, ""_ns, ""_ns, flags, UINT32_MAX,
+ nullptr, result);
+ }
+ return rv;
+}
+
+nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler** result,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_SUCCEEDED(rv)) rv = CallQueryInterface(handler, result);
+ }
+ return rv;
+}
+
+nsresult NS_GetFileFromURLSpec(const nsACString& inURL, nsIFile** result,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetFileFromURLSpec(inURL, result);
+ return rv;
+}
+
+nsresult NS_GetURLSpecFromFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromFile(file, url);
+ return rv;
+}
+
+nsresult NS_GetURLSpecFromActualFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromActualFile(file, url);
+ return rv;
+}
+
+nsresult NS_GetURLSpecFromDir(nsIFile* file, nsACString& url,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromDir(file, url);
+ return rv;
+}
+
+void NS_GetReferrerFromChannel(nsIChannel* channel, nsIURI** referrer) {
+ *referrer = nullptr;
+
+ if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(channel)) {
+ // We have to check for a property on a property bag because the
+ // referrer may be empty for security reasons (for example, when loading
+ // an http page with an https referrer).
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri(
+ do_GetProperty(props, u"docshell.internalReferrer"_ns, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ uri.forget(referrer);
+ return;
+ }
+ }
+
+ // if that didn't work, we can still try to get the referrer from the
+ // nsIHttpChannel (if we can QI to it)
+ nsCOMPtr<nsIHttpChannel> chan(do_QueryInterface(channel));
+ if (!chan) {
+ return;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = chan->GetReferrerInfo();
+ if (!referrerInfo) {
+ return;
+ }
+
+ referrerInfo->GetOriginalReferrer(referrer);
+}
+
+already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult* error /* = 0 */) {
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service();
+ nsCOMPtr<nsINetUtil> util;
+ if (io) util = do_QueryInterface(io);
+
+ if (error) *error = !!util ? NS_OK : NS_ERROR_FAILURE;
+ return util.forget();
+}
+
+nsresult NS_ParseRequestContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset) {
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseRequestContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset) contentCharset = charset;
+ return rv;
+}
+
+nsresult NS_ParseResponseContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset) {
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseResponseContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset) contentCharset = charset;
+ return rv;
+}
+
+nsresult NS_ExtractCharsetFromContentType(const nsACString& rawContentType,
+ nsCString& contentCharset,
+ bool* hadCharset,
+ int32_t* charsetStart,
+ int32_t* charsetEnd) {
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->ExtractCharsetFromContentType(
+ rawContentType, contentCharset, charsetStart, charsetEnd, hadCharset);
+}
+
+nsresult NS_NewAtomicFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) out.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream** result,
+ nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) out.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_NewLocalFileRandomAccessStream(nsIRandomAccessStream** result,
+ nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIFileRandomAccessStream> stream = new nsFileRandomAccessStream();
+ nsresult rv = stream->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ stream.forget(result);
+ }
+ return rv;
+}
+
+mozilla::Result<nsCOMPtr<nsIRandomAccessStream>, nsresult>
+NS_NewLocalFileRandomAccessStream(nsIFile* file, int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIRandomAccessStream> stream;
+ const nsresult rv = NS_NewLocalFileRandomAccessStream(
+ getter_AddRefs(stream), file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+nsresult NS_NewBufferedOutputStream(
+ nsIOutputStream** aResult, already_AddRefed<nsIOutputStream> aOutputStream,
+ uint32_t aBufferSize) {
+ nsCOMPtr<nsIOutputStream> outputStream = std::move(aOutputStream);
+
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> out =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(outputStream, aBufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ out.forget(aResult);
+ }
+ }
+ return rv;
+}
+
+[[nodiscard]] nsresult NS_NewBufferedInputStream(
+ nsIInputStream** aResult, already_AddRefed<nsIInputStream> aInputStream,
+ uint32_t aBufferSize) {
+ nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream);
+
+ nsCOMPtr<nsIBufferedInputStream> in;
+ nsresult rv = nsBufferedInputStream::Create(
+ NS_GET_IID(nsIBufferedInputStream), getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(inputStream, aBufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = static_cast<nsBufferedInputStream*>(in.get())
+ ->GetInputStream()
+ .take();
+ }
+ }
+ return rv;
+}
+
+Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewBufferedInputStream(
+ already_AddRefed<nsIInputStream> aInputStream, uint32_t aBufferSize) {
+ nsCOMPtr<nsIInputStream> stream;
+ const nsresult rv = NS_NewBufferedInputStream(
+ getter_AddRefs(stream), std::move(aInputStream), aBufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+namespace {
+
+#define BUFFER_SIZE 8192
+
+class BufferWriter final : public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ BufferWriter(nsIInputStream* aInputStream, void* aBuffer, int64_t aCount)
+ : mMonitor("BufferWriter.mMonitor"),
+ mInputStream(aInputStream),
+ mBuffer(aBuffer),
+ mCount(aCount),
+ mWrittenData(0),
+ mBufferType(aBuffer ? eExternal : eInternal),
+ mBufferSize(0) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aCount == -1 || aCount > 0);
+ MOZ_ASSERT_IF(mBuffer, aCount > 0);
+ }
+
+ nsresult Write() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ // Let's make the inputStream buffered if it's not.
+ if (!NS_InputStreamIsBuffered(mInputStream)) {
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ nsresult rv = NS_NewBufferedInputStream(
+ getter_AddRefs(bufferedStream), mInputStream.forget(), BUFFER_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInputStream = bufferedStream;
+ }
+
+ mAsyncInputStream = do_QueryInterface(mInputStream);
+
+ if (!mAsyncInputStream) {
+ return WriteSync();
+ }
+
+ // Let's use mAsyncInputStream only.
+ mInputStream = nullptr;
+
+ return WriteAsync();
+ }
+
+ uint64_t WrittenData() const {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+ return mWrittenData;
+ }
+
+ void* StealBuffer() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+ MOZ_ASSERT(mBufferType == eInternal);
+
+ void* buffer = mBuffer;
+
+ mBuffer = nullptr;
+ mBufferSize = 0;
+
+ return buffer;
+ }
+
+ private:
+ ~BufferWriter() {
+ if (mBuffer && mBufferType == eInternal) {
+ free(mBuffer);
+ }
+
+ if (mTaskQueue) {
+ mTaskQueue->BeginShutdown();
+ }
+ }
+
+ nsresult WriteSync() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ uint64_t length = (uint64_t)mCount;
+
+ if (mCount == -1) {
+ nsresult rv = mInputStream->Available(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (length == 0) {
+ // nothing to read.
+ return NS_OK;
+ }
+ }
+
+ if (mBufferType == eInternal) {
+ mBuffer = malloc(length);
+ if (NS_WARN_IF(!mBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ uint32_t writtenData;
+ nsresult rv = mInputStream->ReadSegments(NS_CopySegmentToBuffer, mBuffer,
+ length, &writtenData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mWrittenData = writtenData;
+ return NS_OK;
+ }
+
+ nsresult WriteAsync() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ if (mCount > 0 && mBufferType == eInternal) {
+ mBuffer = malloc(mCount);
+ if (NS_WARN_IF(!mBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ while (true) {
+ if (mCount == -1 && !MaybeExpandBufferSize()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint64_t offset = mWrittenData;
+ uint64_t length = mCount == -1 ? BUFFER_SIZE : mCount;
+
+ // Let's try to read data directly.
+ uint32_t writtenData;
+ nsresult rv = mAsyncInputStream->ReadSegments(
+ NS_CopySegmentToBuffer, static_cast<char*>(mBuffer) + offset, length,
+ &writtenData);
+
+ // Operation completed. Nothing more to read.
+ if (NS_SUCCEEDED(rv) && writtenData == 0) {
+ return NS_OK;
+ }
+
+ // If we succeeded, let's try to read again.
+ if (NS_SUCCEEDED(rv)) {
+ mWrittenData += writtenData;
+ if (mCount != -1) {
+ MOZ_ASSERT(mCount >= writtenData);
+ mCount -= writtenData;
+
+ // Is this the end of the reading?
+ if (mCount == 0) {
+ return NS_OK;
+ }
+ }
+
+ continue;
+ }
+
+ // Async wait...
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = MaybeCreateTaskQueue();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+
+ rv = mAsyncInputStream->AsyncWait(this, 0, length, mTaskQueue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ lock.Wait();
+ continue;
+ }
+
+ // Otherwise, let's propagate the error.
+ return rv;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should not be here");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult MaybeCreateTaskQueue() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ if (!mTaskQueue) {
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!target) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTaskQueue = TaskQueue::Create(target.forget(), "nsNetUtil:BufferWriter");
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnInputStreamReady(nsIAsyncInputStream* aStream) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // We have something to read. Let's unlock the main-thread.
+ MonitorAutoLock lock(mMonitor);
+ lock.Notify();
+ return NS_OK;
+ }
+
+ bool MaybeExpandBufferSize() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ MOZ_ASSERT(mCount == -1);
+
+ if (mBufferSize >= mWrittenData + BUFFER_SIZE) {
+ // The buffer is big enough.
+ return true;
+ }
+
+ CheckedUint32 bufferSize =
+ std::max<uint32_t>(static_cast<uint32_t>(mWrittenData), BUFFER_SIZE);
+ while (bufferSize.isValid() &&
+ bufferSize.value() < mWrittenData + BUFFER_SIZE) {
+ bufferSize *= 2;
+ }
+
+ if (!bufferSize.isValid()) {
+ return false;
+ }
+
+ void* buffer = realloc(mBuffer, bufferSize.value());
+ if (!buffer) {
+ return false;
+ }
+
+ mBuffer = buffer;
+ mBufferSize = bufferSize.value();
+ return true;
+ }
+
+ // All the members of this class are touched on the owning thread only. The
+ // monitor is only used to communicate when there is more data to read.
+ Monitor mMonitor MOZ_UNANNOTATED;
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+ nsCOMPtr<nsIAsyncInputStream> mAsyncInputStream;
+
+ RefPtr<TaskQueue> mTaskQueue;
+
+ void* mBuffer;
+ int64_t mCount;
+ uint64_t mWrittenData;
+
+ enum {
+ // The buffer is allocated internally and this object must release it
+ // in the DTOR if not stolen. The buffer can be reallocated.
+ eInternal,
+
+ // The buffer is not owned by this object and it cannot be reallocated.
+ eExternal,
+ } mBufferType;
+
+ // The following set if needed for the async read.
+ uint64_t mBufferSize;
+};
+
+NS_IMPL_ISUPPORTS(BufferWriter, nsIInputStreamCallback)
+
+} // anonymous namespace
+
+nsresult NS_ReadInputStreamToBuffer(nsIInputStream* aInputStream, void** aDest,
+ int64_t aCount, uint64_t* aWritten) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aCount >= -1);
+
+ uint64_t dummyWritten;
+ if (!aWritten) {
+ aWritten = &dummyWritten;
+ }
+
+ if (aCount == 0) {
+ *aWritten = 0;
+ return NS_OK;
+ }
+
+ // This will take care of allocating and reallocating aDest.
+ RefPtr<BufferWriter> writer = new BufferWriter(aInputStream, *aDest, aCount);
+
+ nsresult rv = writer->Write();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aWritten = writer->WrittenData();
+
+ if (!*aDest) {
+ *aDest = writer->StealBuffer();
+ }
+
+ return NS_OK;
+}
+
+nsresult NS_ReadInputStreamToString(nsIInputStream* aInputStream,
+ nsACString& aDest, int64_t aCount,
+ uint64_t* aWritten) {
+ uint64_t dummyWritten;
+ if (!aWritten) {
+ aWritten = &dummyWritten;
+ }
+
+ // Nothing to do if aCount is 0.
+ if (aCount == 0) {
+ aDest.Truncate();
+ *aWritten = 0;
+ return NS_OK;
+ }
+
+ // If we have the size, we can pre-allocate the buffer.
+ if (aCount > 0) {
+ if (NS_WARN_IF(aCount >= INT32_MAX) ||
+ NS_WARN_IF(!aDest.SetLength(aCount, mozilla::fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ void* dest = aDest.BeginWriting();
+ nsresult rv =
+ NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount, aWritten);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((uint64_t)aCount > *aWritten) {
+ aDest.Truncate(*aWritten);
+ }
+
+ return NS_OK;
+ }
+
+ // If the size is unknown, BufferWriter will allocate the buffer.
+ void* dest = nullptr;
+ nsresult rv =
+ NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount, aWritten);
+ MOZ_ASSERT_IF(NS_FAILED(rv), dest == nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!dest) {
+ MOZ_ASSERT(*aWritten == 0);
+ aDest.Truncate();
+ return NS_OK;
+ }
+
+ aDest.Adopt(reinterpret_cast<char*>(dest), *aWritten);
+ return NS_OK;
+}
+
+nsresult NS_NewURI(nsIURI** result, const nsACString& spec,
+ NotNull<const Encoding*> encoding,
+ nsIURI* baseURI /* = nullptr */) {
+ nsAutoCString charset;
+ encoding->Name(charset);
+ return NS_NewURI(result, spec, charset.get(), baseURI);
+}
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& aSpec,
+ const char* charset /* = nullptr */,
+ nsIURI* baseURI /* = nullptr */) {
+ nsAutoCString spec;
+ if (!AppendUTF16toUTF8(aSpec, spec, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_NewURI(result, spec, charset, baseURI);
+}
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& aSpec,
+ NotNull<const Encoding*> encoding,
+ nsIURI* baseURI /* = nullptr */) {
+ nsAutoCString spec;
+ if (!AppendUTF16toUTF8(aSpec, spec, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_NewURI(result, spec, encoding, baseURI);
+}
+
+nsresult NS_NewURI(nsIURI** result, const char* spec,
+ nsIURI* baseURI /* = nullptr */) {
+ return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI);
+}
+
+static nsresult NewStandardURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, int32_t aDefaultPort,
+ nsIURI** aURI) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY,
+ aDefaultPort, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+}
+
+nsresult NS_GetSpecWithNSURLEncoding(nsACString& aResult,
+ const nsACString& aSpec) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURIWithNSURLEncoding(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return uri->GetAsciiSpec(aResult);
+}
+
+nsresult NS_NewURIWithNSURLEncoding(nsIURI** aResult, const nsACString& aSpec) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Escape the ref portion of the URL. NSURL is more strict about which
+ // characters in the URL must be % encoded. For example, an unescaped '#'
+ // to indicate the beginning of the ref component is accepted by NSURL, but
+ // '#' characters in the ref must be escaped. Also adds encoding for other
+ // characters not accepted by NSURL in the ref such as '{', '|', '}', and '^'.
+ // The ref returned from GetRef() does not include the leading '#'.
+ nsAutoCString ref, escapedRef;
+ if (NS_SUCCEEDED(uri->GetRef(ref)) && !ref.IsEmpty()) {
+ if (!NS_Escape(ref, escapedRef, url_NSURLRef)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = NS_MutateURI(uri).SetRef(escapedRef).Finalize(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uri.forget(aResult);
+ return NS_OK;
+}
+
+extern MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount;
+
+template <typename T>
+class TlsAutoIncrement {
+ public:
+ explicit TlsAutoIncrement(T& var) : mVar(var) {
+ mValue = mVar.get();
+ mVar.set(mValue + 1);
+ }
+ ~TlsAutoIncrement() {
+ typename T::Type value = mVar.get();
+ MOZ_ASSERT(value == mValue + 1);
+ mVar.set(value - 1);
+ }
+
+ typename T::Type value() { return mValue; }
+
+ private:
+ typename T::Type mValue;
+ T& mVar;
+};
+
+nsresult NS_NewURI(nsIURI** aURI, const nsACString& aSpec,
+ const char* aCharset /* = nullptr */,
+ nsIURI* aBaseURI /* = nullptr */) {
+ TlsAutoIncrement<decltype(gTlsURLRecursionCount)> inc(gTlsURLRecursionCount);
+ if (inc.value() >= MAX_RECURSION_COUNT) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+ if (!ioService) {
+ // Individual protocol handlers unfortunately rely on the ioservice, let's
+ // return an error here instead of causing unpredictable crashes later.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (StaticPrefs::network_url_max_length() &&
+ aSpec.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(aSpec, scheme);
+ if (NS_FAILED(rv)) {
+ // then aSpec is relative
+ if (!aBaseURI) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (!aSpec.IsEmpty() && aSpec[0] == '#') {
+ // Looks like a reference instead of a fully-specified URI.
+ // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
+ return NS_GetURIWithNewRef(aBaseURI, aSpec, aURI);
+ }
+
+ rv = aBaseURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("ws")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTP_DEFAULT_PORT,
+ aURI);
+ }
+ if (scheme.EqualsLiteral("https") || scheme.EqualsLiteral("wss")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTPS_DEFAULT_PORT,
+ aURI);
+ }
+ if (scheme.EqualsLiteral("ftp")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, 21, aURI);
+ }
+
+ if (scheme.EqualsLiteral("file")) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIFileURLMutator::MarkFileURL)
+ .Apply(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, aSpec, aCharset,
+ aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("data")) {
+ return nsDataHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-safe-about") ||
+ scheme.EqualsLiteral("page-icon") || scheme.EqualsLiteral("moz") ||
+ scheme.EqualsLiteral("cached-favicon")) {
+ return NS_MutateURI(new nsSimpleURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("chrome")) {
+ return nsChromeProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+ aURI);
+ }
+
+ if (scheme.EqualsLiteral("javascript")) {
+ return nsJSProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("blob")) {
+ return BlobURLProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+ aURI);
+ }
+
+ if (scheme.EqualsLiteral("view-source")) {
+ return nsViewSourceHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("resource")) {
+ RefPtr<nsResProtocolHandler> handler = nsResProtocolHandler::GetSingleton();
+ if (!handler) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return handler->NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("indexeddb") || scheme.EqualsLiteral("uuid")) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY,
+ 0, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-extension")) {
+ RefPtr<mozilla::net::ExtensionProtocolHandler> handler =
+ mozilla::net::ExtensionProtocolHandler::GetSingleton();
+ if (!handler) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return handler->NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-page-thumb")) {
+ // The moz-page-thumb service runs JS to resolve a URI to a
+ // storage location, so this should only ever run on the main
+ // thread.
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<mozilla::net::PageThumbProtocolHandler> handler =
+ mozilla::net::PageThumbProtocolHandler::GetSingleton();
+ if (!handler) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return handler->NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("about")) {
+ return nsAboutProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+ aURI);
+ }
+
+ if (scheme.EqualsLiteral("jar")) {
+ return NS_MutateURI(new nsJARURI::Mutator())
+ .Apply(&nsIJARURIMutator::SetSpecBaseCharset, aSpec, aBaseURI, aCharset)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-icon")) {
+ return NS_MutateURI(new nsMozIconURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ if (scheme.EqualsLiteral("smb") || scheme.EqualsLiteral("sftp")) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ -1, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+#endif
+
+ if (scheme.EqualsLiteral("android")) {
+ return NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ -1, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+
+ // web-extensions can add custom protocol implementations with standard URLs
+ // that have notion of hostname, authority and relative URLs. Below we
+ // manually check agains set of known protocols schemes until more general
+ // solution is in place (See Bug 1569733)
+ if (!StaticPrefs::network_url_useDefaultURI()) {
+ if (scheme.EqualsLiteral("ssh")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, 22, aURI);
+ }
+
+ if (scheme.EqualsLiteral("dweb") || scheme.EqualsLiteral("dat") ||
+ scheme.EqualsLiteral("ipfs") || scheme.EqualsLiteral("ipns") ||
+ scheme.EqualsLiteral("ssb") || scheme.EqualsLiteral("wtp")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, -1, aURI);
+ }
+ }
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ rv = NS_NewMailnewsURI(aURI, aSpec, aCharset, aBaseURI);
+ if (rv != NS_ERROR_UNKNOWN_PROTOCOL) {
+ return rv;
+ }
+#endif
+
+ if (aBaseURI) {
+ nsAutoCString newSpec;
+ rv = aBaseURI->Resolve(aSpec, newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newScheme;
+ rv = net_ExtractURLScheme(newSpec, newScheme);
+ if (NS_SUCCEEDED(rv)) {
+ // The scheme shouldn't really change at this point.
+ MOZ_DIAGNOSTIC_ASSERT(newScheme == scheme);
+ }
+
+ if (StaticPrefs::network_url_useDefaultURI()) {
+ return NS_MutateURI(new DefaultURI::Mutator())
+ .SetSpec(newSpec)
+ .Finalize(aURI);
+ }
+
+ return NS_MutateURI(new nsSimpleURI::Mutator())
+ .SetSpec(newSpec)
+ .Finalize(aURI);
+ }
+
+ if (StaticPrefs::network_url_useDefaultURI()) {
+ return NS_MutateURI(new DefaultURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+
+ // Falls back to external protocol handler.
+ return NS_MutateURI(new nsSimpleURI::Mutator()).SetSpec(aSpec).Finalize(aURI);
+}
+
+nsresult NS_GetSanitizedURIStringFromURI(nsIURI* aUri,
+ nsAString& aSanitizedSpec) {
+ aSanitizedSpec.Truncate();
+
+ nsCOMPtr<nsISensitiveInfoHiddenURI> safeUri = do_QueryInterface(aUri);
+ nsAutoCString cSpec;
+ nsresult rv;
+ if (safeUri) {
+ rv = safeUri->GetSensitiveInfoHiddenSpec(cSpec);
+ } else {
+ rv = aUri->GetSpec(cSpec);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ aSanitizedSpec.Assign(NS_ConvertUTF8toUTF16(cSpec));
+ }
+ return rv;
+}
+
+nsresult NS_LoadPersistentPropertiesFromURISpec(
+ nsIPersistentProperties** outResult, const nsACString& aSpec) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPersistentProperties> properties = new nsPersistentProperties();
+ rv = properties->Load(in);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ properties.swap(*outResult);
+ return NS_OK;
+}
+
+bool NS_UsePrivateBrowsing(nsIChannel* channel) {
+ OriginAttributes attrs;
+ bool result = StoragePrincipalHelper::GetOriginAttributes(
+ channel, attrs, StoragePrincipalHelper::eRegularPrincipal);
+ NS_ENSURE_TRUE(result, result);
+ return attrs.mPrivateBrowsingId > 0;
+}
+
+bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ // TYPE_DOCUMENT loads have a null LoadingPrincipal and can not be cross
+ // origin.
+ if (!loadInfo->GetLoadingPrincipal()) {
+ return false;
+ }
+
+ // Always treat tainted channels as cross-origin.
+ if (loadInfo->GetTainting() != LoadTainting::Basic) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->GetLoadingPrincipal();
+ uint32_t mode = loadInfo->GetSecurityMode();
+ bool dataInherits =
+ mode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT ||
+ mode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT ||
+ mode == nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+
+ bool aboutBlankInherits = dataInherits && loadInfo->GetAboutBlankInherits();
+
+ uint64_t innerWindowID = loadInfo->GetInnerWindowID();
+
+ for (nsIRedirectHistoryEntry* redirectHistoryEntry :
+ loadInfo->RedirectChain()) {
+ nsCOMPtr<nsIPrincipal> principal;
+ redirectHistoryEntry->GetPrincipal(getter_AddRefs(principal));
+ if (!principal) {
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ auto* basePrin = BasePrincipal::Cast(principal);
+ basePrin->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ continue;
+ }
+
+ nsresult res;
+ if (aReport) {
+ res = loadingPrincipal->CheckMayLoadWithReporting(uri, dataInherits,
+ innerWindowID);
+ } else {
+ res = loadingPrincipal->CheckMayLoad(uri, dataInherits);
+ }
+ if (NS_FAILED(res)) {
+ return true;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ return false;
+ }
+
+ nsresult res;
+ if (aReport) {
+ res = loadingPrincipal->CheckMayLoadWithReporting(uri, dataInherits,
+ innerWindowID);
+ } else {
+ res = loadingPrincipal->CheckMayLoad(uri, dataInherits);
+ }
+
+ return NS_FAILED(res);
+}
+
+bool NS_IsSafeMethodNav(nsIChannel* aChannel) {
+ RefPtr<HttpBaseChannel> baseChan = do_QueryObject(aChannel);
+ if (!baseChan) {
+ return false;
+ }
+ nsHttpRequestHead* requestHead = baseChan->GetRequestHead();
+ if (!requestHead) {
+ return false;
+ }
+ return requestHead->IsSafeMethod();
+}
+
+void NS_WrapAuthPrompt(nsIAuthPrompt* aAuthPrompt,
+ nsIAuthPrompt2** aAuthPrompt2) {
+ nsCOMPtr<nsIAuthPromptAdapterFactory> factory =
+ do_GetService(NS_AUTHPROMPT_ADAPTER_FACTORY_CONTRACTID);
+ if (!factory) return;
+
+ NS_WARNING("Using deprecated nsIAuthPrompt");
+ factory->CreateAdapter(aAuthPrompt, aAuthPrompt2);
+}
+
+void NS_QueryAuthPrompt2(nsIInterfaceRequestor* aCallbacks,
+ nsIAuthPrompt2** aAuthPrompt) {
+ CallGetInterface(aCallbacks, aAuthPrompt);
+ if (*aAuthPrompt) return;
+
+ // Maybe only nsIAuthPrompt is provided and we have to wrap it.
+ nsCOMPtr<nsIAuthPrompt> prompt(do_GetInterface(aCallbacks));
+ if (!prompt) return;
+
+ NS_WrapAuthPrompt(prompt, aAuthPrompt);
+}
+
+void NS_QueryAuthPrompt2(nsIChannel* aChannel, nsIAuthPrompt2** aAuthPrompt) {
+ *aAuthPrompt = nullptr;
+
+ // We want to use any auth prompt we can find on the channel's callbacks,
+ // and if that fails use the loadgroup's prompt (if any)
+ // Therefore, we can't just use NS_QueryNotificationCallbacks, because
+ // that would prefer a loadgroup's nsIAuthPrompt2 over a channel's
+ // nsIAuthPrompt.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+ if (*aAuthPrompt) return;
+ }
+
+ nsCOMPtr<nsILoadGroup> group;
+ aChannel->GetLoadGroup(getter_AddRefs(group));
+ if (!group) return;
+
+ group->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks) return;
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+}
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIEventTarget* target, nsIInterfaceRequestor** result) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ if (loadGroup) loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ return NS_NewInterfaceRequestorAggregation(callbacks, cbs, target, result);
+}
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIInterfaceRequestor** result) {
+ return NS_NewNotificationCallbacksAggregation(callbacks, loadGroup, nullptr,
+ result);
+}
+
+nsresult NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result) {
+ MOZ_ASSERT(nestedURI, "Must have a nested URI!");
+ MOZ_ASSERT(!*result, "Must have null *result");
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = nestedURI->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We may need to loop here until we reach the innermost
+ // URI.
+ nsCOMPtr<nsINestedURI> nestedInner(do_QueryInterface(inner));
+ while (nestedInner) {
+ rv = nestedInner->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nestedInner = do_QueryInterface(inner);
+ }
+
+ // Found the innermost one if we reach here.
+ inner.swap(*result);
+
+ return rv;
+}
+
+nsresult NS_ImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result) {
+ // Make it safe to use swap()
+ *result = nullptr;
+
+ return NS_DoImplGetInnermostURI(nestedURI, result);
+}
+
+already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI, "Must have URI");
+
+ nsCOMPtr<nsIURI> uri = aURI;
+
+ nsCOMPtr<nsINestedURI> nestedURI(do_QueryInterface(uri));
+ if (!nestedURI) {
+ return uri.forget();
+ }
+
+ nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+nsresult NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri) {
+ *uri = nullptr;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ if (resultPrincipalURI) {
+ resultPrincipalURI.forget(uri);
+ return NS_OK;
+ }
+ return channel->GetOriginalURI(uri);
+}
+
+nsresult NS_URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->URIChainHasFlags(uri, flags, result);
+}
+
+uint32_t NS_SecurityHashURI(nsIURI* aURI) {
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+
+ nsAutoCString scheme;
+ uint32_t schemeHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetScheme(scheme))) {
+ schemeHash = mozilla::HashString(scheme);
+ }
+
+ // TODO figure out how to hash file:// URIs
+ if (scheme.EqualsLiteral("file")) return schemeHash; // sad face
+
+#if IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(
+ baseURI, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag) {
+ nsAutoCString spec;
+ uint32_t specHash;
+ nsresult res = baseURI->GetSpec(spec);
+ if (NS_SUCCEEDED(res))
+ specHash = mozilla::HashString(spec);
+ else
+ specHash = static_cast<uint32_t>(res);
+ return specHash;
+ }
+#endif
+
+ nsAutoCString host;
+ uint32_t hostHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetAsciiHost(host))) {
+ hostHash = mozilla::HashString(host);
+ }
+
+ return mozilla::AddToHash(schemeHash, hostHash, NS_GetRealPort(baseURI));
+}
+
+bool NS_SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI,
+ bool aStrictFileOriginPolicy) {
+ nsresult rv;
+
+ // Note that this is not an Equals() test on purpose -- for URIs that don't
+ // support host/port, we want equality to basically be object identity, for
+ // security purposes. Otherwise, for example, two javascript: URIs that
+ // are otherwise unrelated could end up "same origin", which would be
+ // unfortunate.
+ if (aSourceURI && aSourceURI == aTargetURI) {
+ return true;
+ }
+
+ if (!aTargetURI || !aSourceURI) {
+ return false;
+ }
+
+ // If either URI is a nested URI, get the base URI
+ nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(aSourceURI);
+ nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Check if either URI has a special origin.
+ nsCOMPtr<nsIURI> origin;
+ nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin =
+ do_QueryInterface(sourceBaseURI);
+ if (uriWithSpecialOrigin) {
+ rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ MOZ_ASSERT(origin);
+ sourceBaseURI = origin;
+ }
+ uriWithSpecialOrigin = do_QueryInterface(targetBaseURI);
+ if (uriWithSpecialOrigin) {
+ rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ MOZ_ASSERT(origin);
+ targetBaseURI = origin;
+ }
+#endif
+
+ nsCOMPtr<nsIPrincipal> sourceBlobPrincipal;
+ if (BlobURLProtocolHandler::GetBlobURLPrincipal(
+ sourceBaseURI, getter_AddRefs(sourceBlobPrincipal))) {
+ nsCOMPtr<nsIURI> sourceBlobOwnerURI;
+ auto* basePrin = BasePrincipal::Cast(sourceBlobPrincipal);
+ rv = basePrin->GetURI(getter_AddRefs(sourceBlobOwnerURI));
+ if (NS_SUCCEEDED(rv)) {
+ sourceBaseURI = sourceBlobOwnerURI;
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> targetBlobPrincipal;
+ if (BlobURLProtocolHandler::GetBlobURLPrincipal(
+ targetBaseURI, getter_AddRefs(targetBlobPrincipal))) {
+ nsCOMPtr<nsIURI> targetBlobOwnerURI;
+ auto* basePrin = BasePrincipal::Cast(targetBlobPrincipal);
+ rv = basePrin->GetURI(getter_AddRefs(targetBlobOwnerURI));
+ if (NS_SUCCEEDED(rv)) {
+ targetBaseURI = targetBlobOwnerURI;
+ }
+ }
+
+ if (!sourceBaseURI || !targetBaseURI) return false;
+
+ // Compare schemes
+ nsAutoCString targetScheme;
+ bool sameScheme = false;
+ if (NS_FAILED(targetBaseURI->GetScheme(targetScheme)) ||
+ NS_FAILED(sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme)) ||
+ !sameScheme) {
+ // Not same-origin if schemes differ
+ return false;
+ }
+
+ // For file scheme, reject unless the files are identical. See
+ // NS_RelaxStrictFileOriginPolicy for enforcing file same-origin checking
+ if (targetScheme.EqualsLiteral("file")) {
+ // in traditional unsafe behavior all files are the same origin
+ if (!aStrictFileOriginPolicy) return true;
+
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(sourceBaseURI));
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(targetBaseURI));
+
+ if (!sourceFileURL || !targetFileURL) return false;
+
+ nsCOMPtr<nsIFile> sourceFile, targetFile;
+
+ sourceFileURL->GetFile(getter_AddRefs(sourceFile));
+ targetFileURL->GetFile(getter_AddRefs(targetFile));
+
+ if (!sourceFile || !targetFile) return false;
+
+ // Otherwise they had better match
+ bool filesAreEqual = false;
+ rv = sourceFile->Equals(targetFile, &filesAreEqual);
+ return NS_SUCCEEDED(rv) && filesAreEqual;
+ }
+
+#if IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(
+ targetBaseURI, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag) {
+ // URIs with this flag have the whole spec as a distinct trust
+ // domain; use the whole spec for comparison
+ nsAutoCString targetSpec;
+ nsAutoCString sourceSpec;
+ return (NS_SUCCEEDED(targetBaseURI->GetSpec(targetSpec)) &&
+ NS_SUCCEEDED(sourceBaseURI->GetSpec(sourceSpec)) &&
+ targetSpec.Equals(sourceSpec));
+ }
+#endif
+
+ // Compare hosts
+ nsAutoCString targetHost;
+ nsAutoCString sourceHost;
+ if (NS_FAILED(targetBaseURI->GetAsciiHost(targetHost)) ||
+ NS_FAILED(sourceBaseURI->GetAsciiHost(sourceHost))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStandardURL> targetURL(do_QueryInterface(targetBaseURI));
+ nsCOMPtr<nsIStandardURL> sourceURL(do_QueryInterface(sourceBaseURI));
+ if (!targetURL || !sourceURL) {
+ return false;
+ }
+
+ if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator)) {
+ return false;
+ }
+
+ return NS_GetRealPort(targetBaseURI) == NS_GetRealPort(sourceBaseURI);
+}
+
+bool NS_URIIsLocalFile(nsIURI* aURI) {
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil();
+
+ bool isFile;
+ return util &&
+ NS_SUCCEEDED(util->ProtocolHasFlags(
+ aURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, &isFile)) &&
+ isFile;
+}
+
+bool NS_RelaxStrictFileOriginPolicy(nsIURI* aTargetURI, nsIURI* aSourceURI,
+ bool aAllowDirectoryTarget /* = false */) {
+ if (!NS_URIIsLocalFile(aTargetURI)) {
+ // This is probably not what the caller intended
+ MOZ_ASSERT_UNREACHABLE(
+ "NS_RelaxStrictFileOriginPolicy called with non-file URI");
+ return false;
+ }
+
+ if (!NS_URIIsLocalFile(aSourceURI)) {
+ // If the source is not also a file: uri then forget it
+ // (don't want resource: principals in a file: doc)
+ //
+ // note: we're not de-nesting jar: uris here, we want to
+ // keep archive content bottled up in its own little island
+ return false;
+ }
+
+ //
+ // pull out the internal files
+ //
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(aTargetURI));
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(aSourceURI));
+ nsCOMPtr<nsIFile> targetFile;
+ nsCOMPtr<nsIFile> sourceFile;
+ bool targetIsDir;
+
+ // Make sure targetFile is not a directory (bug 209234)
+ // and that it exists w/out unescaping (bug 395343)
+ if (!sourceFileURL || !targetFileURL ||
+ NS_FAILED(targetFileURL->GetFile(getter_AddRefs(targetFile))) ||
+ NS_FAILED(sourceFileURL->GetFile(getter_AddRefs(sourceFile))) ||
+ !targetFile || !sourceFile || NS_FAILED(targetFile->Normalize()) ||
+#ifndef MOZ_WIDGET_ANDROID
+ NS_FAILED(sourceFile->Normalize()) ||
+#endif
+ (!aAllowDirectoryTarget &&
+ (NS_FAILED(targetFile->IsDirectory(&targetIsDir)) || targetIsDir))) {
+ return false;
+ }
+
+ return false;
+}
+
+bool NS_IsInternalSameURIRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel, uint32_t aFlags) {
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(oldURI->Equals(newURI, &res)) && res;
+}
+
+bool NS_IsHSTSUpgradeRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags) {
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ if (!oldURI->SchemeIs("http")) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ nsresult rv = NS_GetSecureUpgradedURI(oldURI, getter_AddRefs(upgradedURI));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(upgradedURI->Equals(newURI, &res)) && res;
+}
+
+bool NS_ShouldRemoveAuthHeaderOnRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags) {
+ // we need to strip Authentication headers for external cross-origin redirects
+ // Howerver, we should NOT strip auth headers for
+ // - internal redirects/HSTS upgrades
+ // - same origin redirects
+ // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
+ if ((aFlags & (nsIChannelEventSink::REDIRECT_STS_UPGRADE |
+ nsIChannelEventSink::REDIRECT_INTERNAL))) {
+ // this is an internal redirect do not strip auth header
+ return false;
+ }
+ nsCOMPtr<nsIURI> oldUri;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldUri)));
+
+ nsCOMPtr<nsIURI> newUri;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newUri)));
+
+ nsresult rv = nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
+ newUri, oldUri, false, false);
+
+ return NS_FAILED(rv);
+}
+
+nsresult NS_LinkRedirectChannels(uint64_t channelId,
+ nsIParentChannel* parentChannel,
+ nsIChannel** _result) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ return registrar->LinkChannels(channelId, parentChannel, _result);
+}
+
+nsILoadInfo::CrossOriginEmbedderPolicy
+NS_GetCrossOriginEmbedderPolicyFromHeader(
+ const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled) {
+ nsCOMPtr<nsISFVService> sfv = GetSFVService();
+
+ nsCOMPtr<nsISFVItem> item;
+ nsresult rv = sfv->ParseItem(aHeader, getter_AddRefs(item));
+ if (NS_FAILED(rv)) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ nsCOMPtr<nsISFVBareItem> value;
+ rv = item->GetValue(getter_AddRefs(value));
+ if (NS_FAILED(rv)) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ nsCOMPtr<nsISFVToken> token = do_QueryInterface(value);
+ if (!token) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ nsAutoCString embedderPolicy;
+ rv = token->GetValue(embedderPolicy);
+ if (NS_FAILED(rv)) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ if (embedderPolicy.EqualsLiteral("require-corp")) {
+ return nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP;
+ } else if (embedderPolicy.EqualsLiteral("credentialless") &&
+ IsCoepCredentiallessEnabled(
+ aIsOriginTrialCoepCredentiallessEnabled)) {
+ return nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS;
+ }
+
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+}
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t NS_GetContentDispositionFromToken(const nsAString& aDispToken) {
+ // RFC 2183, section 2.8 says that an unknown disposition
+ // value should be treated as "attachment"
+ // If all of these tests eval to false, then we have a content-disposition of
+ // "attachment" or unknown
+ if (aDispToken.IsEmpty() || aDispToken.LowerCaseEqualsLiteral("inline") ||
+ // Broken sites just send
+ // Content-Disposition: filename="file"
+ // without a disposition token... screen those out.
+ StringHead(aDispToken, 8).LowerCaseEqualsLiteral("filename")) {
+ return nsIChannel::DISPOSITION_INLINE;
+ }
+
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+}
+
+uint32_t NS_GetContentDispositionFromHeader(const nsACString& aHeader,
+ nsIChannel* aChan /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return nsIChannel::DISPOSITION_ATTACHMENT;
+
+ nsAutoString dispToken;
+ rv = mimehdrpar->GetParameterHTTP(aHeader, "", ""_ns, true, nullptr,
+ dispToken);
+
+ if (NS_FAILED(rv)) {
+ // special case (see bug 272541): empty disposition type handled as "inline"
+ if (rv == NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY) {
+ return nsIChannel::DISPOSITION_INLINE;
+ }
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+ }
+
+ return NS_GetContentDispositionFromToken(dispToken);
+}
+
+nsresult NS_GetFilenameFromDisposition(nsAString& aFilename,
+ const nsACString& aDisposition) {
+ aFilename.Truncate();
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Get the value of 'filename' parameter
+ rv = mimehdrpar->GetParameterHTTP(aDisposition, "filename", ""_ns, true,
+ nullptr, aFilename);
+
+ if (NS_FAILED(rv)) {
+ aFilename.Truncate();
+ return rv;
+ }
+
+ if (aFilename.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ // Filename may still be percent-encoded. Fix:
+ if (aFilename.FindChar('%') != -1) {
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString unescaped;
+ textToSubURI->UnEscapeURIForUI(NS_ConvertUTF16toUTF8(aFilename),
+ /* dontEscape = */ true, unescaped);
+ aFilename.Assign(unescaped);
+ }
+ }
+
+ return NS_OK;
+}
+
+void net_EnsurePSMInit() {
+ if (XRE_IsSocketProcess()) {
+ EnsureNSSInitializedChromeOrContent();
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DebugOnly<bool> rv = EnsureNSSInitializedChromeOrContent();
+ MOZ_ASSERT(rv);
+}
+
+bool NS_IsAboutBlank(nsIURI* uri) {
+ // GetSpec can be expensive for some URIs, so check the scheme first.
+ if (!uri->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString spec;
+ if (NS_FAILED(uri->GetSpec(spec))) {
+ return false;
+ }
+
+ return spec.EqualsLiteral("about:blank");
+}
+
+bool NS_IsAboutSrcdoc(nsIURI* uri) {
+ // GetSpec can be expensive for some URIs, so check the scheme first.
+ if (!uri->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString spec;
+ if (NS_FAILED(uri->GetSpec(spec))) {
+ return false;
+ }
+
+ return spec.EqualsLiteral("about:srcdoc");
+}
+
+nsresult NS_GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine) {
+ if (strchr(host.get(), ':')) {
+ // host is an IPv6 address literal and must be encapsulated in []'s
+ hostLine.Assign('[');
+ // scope id is not needed for Host header.
+ int scopeIdPos = host.FindChar('%');
+ if (scopeIdPos == -1) {
+ hostLine.Append(host);
+ } else if (scopeIdPos > 0) {
+ hostLine.Append(Substring(host, 0, scopeIdPos));
+ } else {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ hostLine.Append(']');
+ } else {
+ hostLine.Assign(host);
+ }
+ if (port != -1) {
+ hostLine.Append(':');
+ hostLine.AppendInt(port);
+ }
+ return NS_OK;
+}
+
+void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest,
+ const uint8_t* aData, uint32_t aLength,
+ nsACString& aSniffedType) {
+ using ContentSnifferCache = nsCategoryCache<nsIContentSniffer>;
+ extern ContentSnifferCache* gNetSniffers;
+ extern ContentSnifferCache* gDataSniffers;
+ extern ContentSnifferCache* gORBSniffers;
+ extern ContentSnifferCache* gNetAndORBSniffers;
+ ContentSnifferCache* cache = nullptr;
+ if (!strcmp(aSnifferType, NS_CONTENT_SNIFFER_CATEGORY)) {
+ if (!gNetSniffers) {
+ gNetSniffers = new ContentSnifferCache(NS_CONTENT_SNIFFER_CATEGORY);
+ }
+ cache = gNetSniffers;
+ } else if (!strcmp(aSnifferType, NS_DATA_SNIFFER_CATEGORY)) {
+ if (!gDataSniffers) {
+ gDataSniffers = new ContentSnifferCache(NS_DATA_SNIFFER_CATEGORY);
+ }
+ cache = gDataSniffers;
+ } else if (!strcmp(aSnifferType, NS_ORB_SNIFFER_CATEGORY)) {
+ if (!gORBSniffers) {
+ gORBSniffers = new ContentSnifferCache(NS_ORB_SNIFFER_CATEGORY);
+ }
+ cache = gORBSniffers;
+ } else if (!strcmp(aSnifferType, NS_CONTENT_AND_ORB_SNIFFER_CATEGORY)) {
+ if (!gNetAndORBSniffers) {
+ gNetAndORBSniffers =
+ new ContentSnifferCache(NS_CONTENT_AND_ORB_SNIFFER_CATEGORY);
+ }
+ cache = gNetAndORBSniffers;
+ } else {
+ // Invalid content sniffer type was requested
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ // In case XCTO nosniff was present, we could just skip sniffing here
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (loadInfo->GetSkipContentSniffing()) {
+ /* Bug 1571742
+ * We cannot skip snffing if the current MIME-Type might be a JSON.
+ * The JSON-Viewer relies on its own sniffer to determine, if it can
+ * render the page, so we need to make an exception if the Server provides
+ * a application/ mime, as it might be json.
+ */
+ nsAutoCString currentContentType;
+ channel->GetContentType(currentContentType);
+ if (!StringBeginsWith(currentContentType, "application/"_ns)) {
+ return;
+ }
+ }
+ }
+ nsCOMArray<nsIContentSniffer> sniffers;
+ cache->GetEntries(sniffers);
+ for (int32_t i = 0; i < sniffers.Count(); ++i) {
+ nsresult rv = sniffers[i]->GetMIMETypeFromContent(aRequest, aData, aLength,
+ aSniffedType);
+ if (NS_SUCCEEDED(rv) && !aSniffedType.IsEmpty()) {
+ return;
+ }
+ }
+
+ aSniffedType.Truncate();
+}
+
+bool NS_IsSrcdocChannel(nsIChannel* aChannel) {
+ bool isSrcdoc;
+ nsCOMPtr<nsIInputStreamChannel> isr = do_QueryInterface(aChannel);
+ if (isr) {
+ isr->GetIsSrcdocChannel(&isSrcdoc);
+ return isSrcdoc;
+ }
+ nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(aChannel);
+ if (vsc) {
+ nsresult rv = vsc->GetIsSrcdocChannel(&isSrcdoc);
+ if (NS_SUCCEEDED(rv)) {
+ return isSrcdoc;
+ }
+ }
+ return false;
+}
+
+// helper function for NS_ShouldSecureUpgrade for checking HSTS
+bool handleResultFunc(bool aAllowSTS, bool aIsStsHost) {
+ if (aIsStsHost) {
+ LOG(("nsHttpChannel::Connect() STS permissions found\n"));
+ if (aAllowSTS) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::STS);
+ return true;
+ }
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::PrefBlockedSTS);
+ } else {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::NoReasonToUpgrade);
+ }
+ return false;
+};
+// That function is a helper function of NS_ShouldSecureUpgrade to check if
+// CSP upgrade-insecure-requests, Mixed content auto upgrading or HTTPs-Only/-
+// First should upgrade the given request.
+static bool ShouldSecureUpgradeNoHSTS(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
+ // 2. CSP upgrade-insecure-requests
+ if (aLoadInfo->GetUpgradeInsecureRequests()) {
+ // let's log a message to the console that we are upgrading a request
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ // append the additional 's' for security to the scheme :-)
+ scheme.AppendLiteral("s");
+ NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 reportScheme(scheme);
+ AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
+ uint64_t innerWindowId = aLoadInfo->GetInnerWindowID();
+ CSP_LogLocalizedStr("upgradeInsecureRequest", params,
+ u""_ns, // aSourceFile
+ u""_ns, // aScriptSample
+ 0, // aLineNumber
+ 1, // aColumnNumber
+ nsIScriptError::warningFlag,
+ "upgradeInsecureRequest"_ns, innerWindowId,
+ !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::CSP);
+ return true;
+ }
+ // 3. Mixed content auto upgrading
+ if (aLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ // let's log a message to the console that we are upgrading a request
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ // append the additional 's' for security to the scheme :-)
+ scheme.AppendLiteral("s");
+ NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 reportScheme(scheme);
+ AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
+
+ nsAutoString localizedMsg;
+ nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
+ "MixedContentAutoUpgrade", params,
+ localizedMsg);
+
+ // Prepending ixed Content to the outgoing console message
+ nsString message;
+ message.AppendLiteral(u"Mixed Content: ");
+ message.Append(localizedMsg);
+
+ uint64_t innerWindowId = aLoadInfo->GetInnerWindowID();
+ nsContentUtils::ReportToConsoleByWindowID(
+ message, nsIScriptError::warningFlag, "Mixed Content Message"_ns,
+ innerWindowId, aURI);
+
+ // Set this flag so we know we'll upgrade because of
+ // 'security.mixed_content.upgrade_display_content'.
+ aLoadInfo->SetBrowserDidUpgradeInsecureRequests(true);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::BrowserDisplay);
+
+ return true;
+ }
+
+ // 4. Https-Only
+ if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, aLoadInfo)) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::HTTPSOnly);
+ return true;
+ }
+ // 4.a Https-First
+ if (nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(aURI, aLoadInfo)) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::HTTPSFirst);
+ return true;
+ }
+ return false;
+}
+
+// Check if channel should be upgraded. check in the following order:
+// 1. HSTS
+// 2. CSP upgrade-insecure-requests
+// 3. Mixed content auto upgrading
+// 4. Https-Only / first
+// (5. Https RR - will be checked in nsHttpChannel)
+nsresult NS_ShouldSecureUpgrade(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIPrincipal* aChannelResultPrincipal,
+ bool aAllowSTS, const OriginAttributes& aOriginAttributes,
+ bool& aShouldUpgrade, std::function<void(bool, nsresult)>&& aResultCallback,
+ bool& aWillCallback) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aWillCallback = false;
+ aShouldUpgrade = false;
+
+ // Even if we're in private browsing mode, we still enforce existing STS
+ // data (it is read-only).
+ // if the connection is not using SSL and either the exact host matches or
+ // a superdomain wants to force HTTPS, do it.
+ bool isHttps = aURI->SchemeIs("https");
+
+ // If request is https, then there is nothing to do here.
+ if (isHttps) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::AlreadyHTTPS);
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+ // If it is a mixed content trustworthy loopback, then we shouldn't upgrade
+ // it.
+ if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI)) {
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+ // If no loadInfo exist there is nothing to upgrade here.
+ if (!aLoadInfo) {
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+ MOZ_ASSERT(!aURI->SchemeIs("https"));
+
+ // enforce Strict-Transport-Security
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+
+ bool isStsHost = false;
+ // Calling |IsSecureURI| before the storage is ready to read will
+ // block the main thread. Once the storage is ready, we can call it
+ // from main thread.
+ static Atomic<bool, Relaxed> storageReady(false);
+ if (!storageReady && gSocketTransportService && aResultCallback) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aLoadInfo;
+ nsCOMPtr<nsIURI> uri = aURI;
+ auto callbackWrapper = [resultCallback{std::move(aResultCallback)}, uri,
+ loadInfo](bool aShouldUpgrade, nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // 1. HSTS upgrade
+ if (aShouldUpgrade || NS_FAILED(aStatus)) {
+ resultCallback(aShouldUpgrade, aStatus);
+ return;
+ }
+ // Check if we need to upgrade because of other reasons.
+ // 2. CSP upgrade-insecure-requests
+ // 3. Mixed content auto upgrading
+ // 4. Https-Only / first
+ bool shouldUpgrade = ShouldSecureUpgradeNoHSTS(uri, loadInfo);
+ resultCallback(shouldUpgrade, aStatus);
+ };
+ nsCOMPtr<nsISiteSecurityService> service = sss;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "net::NS_ShouldSecureUpgrade",
+ [service{std::move(service)}, uri{std::move(uri)},
+ originAttributes(aOriginAttributes),
+ handleResultFunc{std::move(handleResultFunc)},
+ callbackWrapper{std::move(callbackWrapper)},
+ allowSTS{std::move(aAllowSTS)}]() mutable {
+ bool isStsHost = false;
+ nsresult rv =
+ service->IsSecureURI(uri, originAttributes, &isStsHost);
+
+ // Successfully get the result from |IsSecureURI| implies that
+ // the storage is ready to read.
+ storageReady = NS_SUCCEEDED(rv);
+ bool shouldUpgrade = handleResultFunc(allowSTS, isStsHost);
+ // Check if request should be upgraded.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::NS_ShouldSecureUpgrade::ResultCallback",
+ [rv, shouldUpgrade,
+ callbackWrapper{std::move(callbackWrapper)}]() {
+ callbackWrapper(shouldUpgrade, rv);
+ }));
+ }),
+ NS_DISPATCH_NORMAL);
+ aWillCallback = NS_SUCCEEDED(rv);
+ return rv;
+ }
+
+ nsresult rv = sss->IsSecureURI(aURI, aOriginAttributes, &isStsHost);
+
+ // if the SSS check fails, it's likely because this load is on a
+ // malformed URI or something else in the setup is wrong, so any error
+ // should be reported.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aShouldUpgrade = handleResultFunc(aAllowSTS, isStsHost);
+ if (!aShouldUpgrade) {
+ // Check for CSP upgrade-insecure-requests, Mixed content auto upgrading
+ // and Https-Only / -First.
+ aShouldUpgrade = ShouldSecureUpgradeNoHSTS(aURI, aLoadInfo);
+ }
+ return rv;
+}
+
+nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI) {
+ NS_MutateURI mutator(aURI);
+ mutator.SetScheme("https"_ns); // Change the scheme to HTTPS:
+
+ // Change the default port to 443:
+ nsCOMPtr<nsIStandardURL> stdURL = do_QueryInterface(aURI);
+ if (stdURL) {
+ mutator.Apply(&nsIStandardURLMutator::SetDefaultPort, 443, nullptr);
+ } else {
+ // If we don't have a nsStandardURL, fall back to using GetPort/SetPort.
+ // XXXdholbert Is this function even called with a non-nsStandardURL arg,
+ // in practice?
+ NS_WARNING("Calling NS_GetSecureUpgradedURI for non nsStandardURL");
+ int32_t oldPort = -1;
+ nsresult rv = aURI->GetPort(&oldPort);
+ if (NS_FAILED(rv)) return rv;
+
+ // Keep any nonstandard ports so only the scheme is changed.
+ // For example:
+ // http://foo.com:80 -> https://foo.com:443
+ // http://foo.com:81 -> https://foo.com:81
+
+ if (oldPort == 80 || oldPort == -1) {
+ mutator.SetPort(-1);
+ } else {
+ mutator.SetPort(oldPort);
+ }
+ }
+
+ return mutator.Finalize(aUpgradedURI);
+}
+
+nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel* aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadContext) {
+ return NS_OK;
+ }
+
+ // We try to skip about:newtab.
+ // about:newtab will use SystemPrincipal to download thumbnails through
+ // https:// and blob URLs.
+ bool isAboutPage = false;
+ nsINode* node = loadInfo->LoadingNode();
+ if (node) {
+ nsIURI* uri = node->OwnerDoc()->GetDocumentURI();
+ isAboutPage = uri->SchemeIs("about");
+ }
+
+ if (isAboutPage) {
+ return NS_OK;
+ }
+
+ // We skip the favicon loading here. The favicon loading might be
+ // triggered by the XUL image. For that case, the loadContext will have
+ // default originAttributes since the XUL image uses SystemPrincipal, but
+ // the loadInfo will use originAttributes from the content. Thus, the
+ // originAttributes between loadInfo and loadContext will be different.
+ // That's why we have to skip the comparison for the favicon loading.
+ if (loadInfo->GetLoadingPrincipal() &&
+ loadInfo->GetLoadingPrincipal()->IsSystemPrincipal() &&
+ loadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return NS_OK;
+ }
+
+ OriginAttributes originAttrsLoadInfo = loadInfo->GetOriginAttributes();
+ OriginAttributes originAttrsLoadContext;
+ loadContext->GetOriginAttributes(originAttrsLoadContext);
+
+ LOG(
+ ("NS_CompareLoadInfoAndLoadContext - loadInfo: %d, %d; "
+ "loadContext: %d, %d. [channel=%p]",
+ originAttrsLoadInfo.mUserContextId,
+ originAttrsLoadInfo.mPrivateBrowsingId,
+ originAttrsLoadContext.mUserContextId,
+ originAttrsLoadContext.mPrivateBrowsingId, aChannel));
+
+ MOZ_ASSERT(originAttrsLoadInfo.mUserContextId ==
+ originAttrsLoadContext.mUserContextId,
+ "The value of mUserContextId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mPrivateBrowsingId ==
+ originAttrsLoadContext.mPrivateBrowsingId,
+ "The value of mPrivateBrowsingId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ return NS_OK;
+}
+
+nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason) {
+ NS_ENSURE_ARG(channel);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ return NS_SetRequestBlockingReason(loadInfo, reason);
+}
+
+nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason) {
+ NS_ENSURE_ARG(loadInfo);
+
+ return loadInfo->SetRequestBlockingReason(reason);
+}
+
+nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo,
+ uint32_t reason) {
+ NS_ENSURE_ARG(loadInfo);
+
+ uint32_t existingReason;
+ if (NS_SUCCEEDED(loadInfo->GetRequestBlockingReason(&existingReason)) &&
+ existingReason != nsILoadInfo::BLOCKING_REASON_NONE) {
+ return NS_OK;
+ }
+
+ return loadInfo->SetRequestBlockingReason(reason);
+}
+
+bool NS_IsOffline() {
+ bool offline = true;
+ bool connectivity = true;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ ios->GetOffline(&offline);
+ ios->GetConnectivity(&connectivity);
+ }
+ return offline || !connectivity;
+}
+
+/**
+ * This function returns true if this channel should be classified by
+ * the URL Classifier, false otherwise.
+ *
+ * The idea of the algorithm to determine if a channel should be
+ * classified is based on:
+ * 1. Channels created by non-privileged code should be classified.
+ * 2. Top-level document’s channels, if loaded by privileged code
+ * (system principal), should be classified.
+ * 3. Any other channel, created by privileged code, is considered safe.
+ *
+ * A bad/hacked/corrupted safebrowsing database, plus a mistakenly
+ * classified critical channel (this may result from a bug in the exemption
+ * rules or incorrect information being passed into) can cause serious
+ * problems. For example, if the updater channel is classified and blocked
+ * by the Safe Browsing, Firefox can't update itself, and there is no way to
+ * recover from that.
+ *
+ * So two safeguards are added to ensure critical channels are never
+ * automatically classified either because there is a bug in the algorithm
+ * or the data in loadinfo is wrong.
+ * 1. beConservative, this is set by ServiceRequest and we treat
+ * channel created for ServiceRequest as critical channels.
+ * 2. nsIChannel::LOAD_BYPASS_URL_CLASSIFIER, channel's opener can use this
+ * flag to enforce bypassing the URL classifier check.
+ */
+bool NS_ShouldClassifyChannel(nsIChannel* aChannel) {
+ nsLoadFlags loadFlags;
+ Unused << aChannel->GetLoadFlags(&loadFlags);
+ // If our load flags dictate that we must let this channel through without
+ // URL classification, obey that here without performing more checks.
+ if (loadFlags & nsIChannel::LOAD_BYPASS_URL_CLASSIFIER) {
+ return false;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(aChannel));
+ if (httpChannel) {
+ bool beConservative;
+ nsresult rv = httpChannel->GetBeConservative(&beConservative);
+
+ // beConservative flag, set by ServiceRequest to ensure channels that
+ // fetch update use conservative TLS setting, are used here to identify
+ // channels are critical to bypass classification. for channels don't
+ // support beConservative, continue to apply the exemption rules.
+ if (NS_SUCCEEDED(rv) && beConservative) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ ExtContentPolicyType type = loadInfo->GetExternalContentPolicyType();
+ // Skip classifying channel triggered by system unless it is a top-level
+ // load.
+ return !(loadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
+ ExtContentPolicy::TYPE_DOCUMENT != type);
+}
+
+namespace mozilla {
+namespace net {
+
+bool InScriptableRange(int64_t val) {
+ return (val <= kJS_MAX_SAFE_INTEGER) && (val >= kJS_MIN_SAFE_INTEGER);
+}
+
+bool InScriptableRange(uint64_t val) { return val <= kJS_MAX_SAFE_UINTEGER; }
+
+nsresult GetParameterHTTP(const nsACString& aHeaderVal, const char* aParamName,
+ nsAString& aResult) {
+ return nsMIMEHeaderParamImpl::GetParameterHTTP(aHeaderVal, aParamName,
+ aResult);
+}
+
+bool ChannelIsPost(nsIChannel* aChannel) {
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
+ nsAutoCString method;
+ Unused << httpChannel->GetRequestMethod(method);
+ return method.EqualsLiteral("POST");
+ }
+ return false;
+}
+
+bool SchemeIsHTTP(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("http");
+}
+
+bool SchemeIsHTTPS(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("https");
+}
+
+bool SchemeIsJavascript(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("javascript");
+}
+
+bool SchemeIsChrome(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("chrome");
+}
+
+bool SchemeIsAbout(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("about");
+}
+
+bool SchemeIsBlob(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("blob");
+}
+
+bool SchemeIsFile(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("file");
+}
+
+bool SchemeIsData(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("data");
+}
+
+bool SchemeIsViewSource(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("view-source");
+}
+
+bool SchemeIsResource(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("resource");
+}
+
+bool SchemeIsFTP(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("ftp");
+}
+
+bool SchemeIsSpecial(const nsACString& aScheme) {
+ // See https://url.spec.whatwg.org/#special-scheme
+ return aScheme.EqualsIgnoreCase("ftp") || aScheme.EqualsIgnoreCase("file") ||
+ aScheme.EqualsIgnoreCase("http") ||
+ aScheme.EqualsIgnoreCase("https") || aScheme.EqualsIgnoreCase("ws") ||
+ aScheme.EqualsIgnoreCase("wss");
+}
+
+bool IsSchemeChangePermitted(nsIURI* aOldURI, const nsACString& newScheme) {
+ // See step 2.1 in https://url.spec.whatwg.org/#special-scheme
+ // Note: The spec text uses "buffer" instead of newScheme, and "url"
+ MOZ_ASSERT(aOldURI);
+
+ nsAutoCString tmp;
+ nsresult rv = aOldURI->GetScheme(tmp);
+ // If url's scheme is a special scheme and buffer is not a
+ // special scheme, then return.
+ // If url's scheme is not a special scheme and buffer is a
+ // special scheme, then return.
+ if (NS_FAILED(rv) || SchemeIsSpecial(tmp) != SchemeIsSpecial(newScheme)) {
+ return false;
+ }
+
+ // If url's scheme is "file" and its host is an empty host, then return.
+ if (aOldURI->SchemeIs("file")) {
+ rv = aOldURI->GetHost(tmp);
+ if (NS_FAILED(rv) || tmp.IsEmpty()) {
+ return false;
+ }
+ }
+
+ // URL Spec: If url includes credentials or has a non-null port, and
+ // buffer is "file", then return.
+ if (newScheme.EqualsIgnoreCase("file")) {
+ bool hasUserPass;
+ if (NS_FAILED(aOldURI->GetHasUserPass(&hasUserPass)) || hasUserPass) {
+ return false;
+ }
+ int32_t port;
+ rv = aOldURI->GetPort(&port);
+ if (NS_FAILED(rv) || port != -1) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+already_AddRefed<nsIURI> TryChangeProtocol(nsIURI* aURI,
+ const nsAString& aProtocol) {
+ MOZ_ASSERT(aURI);
+
+ nsAString::const_iterator start;
+ aProtocol.BeginReading(start);
+
+ nsAString::const_iterator end;
+ aProtocol.EndReading(end);
+
+ nsAString::const_iterator iter(start);
+ FindCharInReadable(':', iter, end);
+
+ // Changing the protocol of a URL, changes the "nature" of the URI
+ // implementation. In order to do this properly, we have to serialize the
+ // existing URL and reparse it in a new object.
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv = NS_MutateURI(aURI)
+ .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)))
+ .Finalize(clone);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ if (StaticPrefs::network_url_strict_protocol_setter()) {
+ nsAutoCString newScheme;
+ rv = clone->GetScheme(newScheme);
+ if (NS_FAILED(rv) || !net::IsSchemeChangePermitted(aURI, newScheme)) {
+ nsAutoCString url;
+ Unused << clone->GetSpec(url);
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(url));
+ params.AppendElement(NS_ConvertUTF8toUTF16(newScheme));
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Strict Url Protocol Setter"_ns, nullptr,
+ nsContentUtils::eNECKO_PROPERTIES, "StrictUrlProtocolSetter", params);
+ return nullptr;
+ }
+ }
+
+ nsAutoCString href;
+ rv = clone->GetSpec(href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ RefPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return uri.forget();
+}
+
+// Decode a parameter value using the encoding defined in RFC 5987 (in place)
+//
+// charset "'" [ language ] "'" value-chars
+//
+// returns true when decoding happened successfully (otherwise leaves
+// passed value alone)
+static bool Decode5987Format(nsAString& aEncoded) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return false;
+
+ nsAutoCString asciiValue;
+
+ const char16_t* encstart = aEncoded.BeginReading();
+ const char16_t* encend = aEncoded.EndReading();
+
+ // create a plain ASCII string, aborting if we can't do that
+ // converted form is always shorter than input
+ while (encstart != encend) {
+ if (*encstart > 0 && *encstart < 128) {
+ asciiValue.Append((char)*encstart);
+ } else {
+ return false;
+ }
+ encstart++;
+ }
+
+ nsAutoString decoded;
+ nsAutoCString language;
+
+ rv = mimehdrpar->DecodeRFC5987Param(asciiValue, language, decoded);
+ if (NS_FAILED(rv)) return false;
+
+ aEncoded = decoded;
+ return true;
+}
+
+LinkHeader::LinkHeader() { mCrossOrigin.SetIsVoid(true); }
+
+void LinkHeader::Reset() {
+ mHref.Truncate();
+ mRel.Truncate();
+ mTitle.Truncate();
+ mNonce.Truncate();
+ mIntegrity.Truncate();
+ mSrcset.Truncate();
+ mSizes.Truncate();
+ mType.Truncate();
+ mMedia.Truncate();
+ mAnchor.Truncate();
+ mCrossOrigin.Truncate();
+ mReferrerPolicy.Truncate();
+ mAs.Truncate();
+ mCrossOrigin.SetIsVoid(true);
+ mFetchPriority.Truncate();
+}
+
+nsresult LinkHeader::NewResolveHref(nsIURI** aOutURI, nsIURI* aBaseURI) const {
+ if (mAnchor.IsEmpty()) {
+ // use the base uri
+ return NS_NewURI(aOutURI, mHref, nullptr, aBaseURI);
+ }
+
+ // compute the anchored URI
+ nsCOMPtr<nsIURI> anchoredURI;
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(anchoredURI), mAnchor, nullptr, aBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewURI(aOutURI, mHref, nullptr, anchoredURI);
+}
+
+bool LinkHeader::operator==(const LinkHeader& rhs) const {
+ return mHref == rhs.mHref && mRel == rhs.mRel && mTitle == rhs.mTitle &&
+ mNonce == rhs.mNonce && mIntegrity == rhs.mIntegrity &&
+ mSrcset == rhs.mSrcset && mSizes == rhs.mSizes && mType == rhs.mType &&
+ mMedia == rhs.mMedia && mAnchor == rhs.mAnchor &&
+ mCrossOrigin == rhs.mCrossOrigin &&
+ mReferrerPolicy == rhs.mReferrerPolicy && mAs == rhs.mAs &&
+ mFetchPriority == rhs.mFetchPriority;
+}
+
+constexpr auto kTitleStar = "title*"_ns;
+
+nsTArray<LinkHeader> ParseLinkHeader(const nsAString& aLinkData) {
+ nsTArray<LinkHeader> linkHeaders;
+
+ // keep track where we are within the header field
+ bool seenParameters = false;
+
+ // parse link content and add to array
+ LinkHeader header;
+ nsAutoString titleStar;
+
+ // copy to work buffer
+ nsAutoString stringList(aLinkData);
+
+ // put an extra null at the end
+ stringList.Append(kNullCh);
+
+ char16_t* start = stringList.BeginWriting();
+
+ while (*start != kNullCh) {
+ // parse link content and call process style link
+
+ // skip leading space
+ while ((*start != kNullCh) && nsCRT::IsAsciiSpace(*start)) {
+ ++start;
+ }
+
+ char16_t* end = start;
+ char16_t* last = end - 1;
+
+ bool wasQuotedString = false;
+
+ // look for semicolon or comma
+ while (*end != kNullCh && *end != kSemicolon && *end != kComma) {
+ char16_t ch = *end;
+
+ if (ch == kQuote || ch == kLessThan) {
+ // quoted string
+
+ char16_t quote = ch;
+ if (quote == kLessThan) {
+ quote = kGreaterThan;
+ }
+
+ wasQuotedString = (ch == kQuote);
+
+ char16_t* closeQuote = (end + 1);
+
+ // seek closing quote
+ while (*closeQuote != kNullCh && quote != *closeQuote) {
+ // in quoted-string, "\" is an escape character
+ if (wasQuotedString && *closeQuote == kBackSlash &&
+ *(closeQuote + 1) != kNullCh) {
+ ++closeQuote;
+ }
+
+ ++closeQuote;
+ }
+
+ if (quote == *closeQuote) {
+ // found closer
+
+ // skip to close quote
+ end = closeQuote;
+
+ last = end - 1;
+
+ ch = *(end + 1);
+
+ if (ch != kNullCh && ch != kSemicolon && ch != kComma) {
+ // end string here
+ *(++end) = kNullCh;
+
+ ch = *(end + 1);
+
+ // keep going until semi or comma
+ while (ch != kNullCh && ch != kSemicolon && ch != kComma) {
+ ++end;
+
+ ch = *(end + 1);
+ }
+ }
+ }
+ }
+
+ ++end;
+ ++last;
+ }
+
+ char16_t endCh = *end;
+
+ // end string here
+ *end = kNullCh;
+
+ if (start < end) {
+ if ((*start == kLessThan) && (*last == kGreaterThan)) {
+ *last = kNullCh;
+
+ // first instance of <...> wins
+ // also, do not allow hrefs after the first param was seen
+ if (header.mHref.IsEmpty() && !seenParameters) {
+ header.mHref = (start + 1);
+ header.mHref.StripWhitespace();
+ }
+ } else {
+ char16_t* equals = start;
+ seenParameters = true;
+
+ while ((*equals != kNullCh) && (*equals != kEqual)) {
+ equals++;
+ }
+
+ const bool hadEquals = *equals != kNullCh;
+ *equals = kNullCh;
+ nsAutoString attr(start);
+ attr.StripWhitespace();
+
+ char16_t* value = hadEquals ? ++equals : equals;
+ while (nsCRT::IsAsciiSpace(*value)) {
+ value++;
+ }
+
+ if ((*value == kQuote) && (*value == *last)) {
+ *last = kNullCh;
+ value++;
+ }
+
+ if (wasQuotedString) {
+ // unescape in-place
+ char16_t* unescaped = value;
+ char16_t* src = value;
+
+ while (*src != kNullCh) {
+ if (*src == kBackSlash && *(src + 1) != kNullCh) {
+ src++;
+ }
+ *unescaped++ = *src++;
+ }
+
+ *unescaped = kNullCh;
+ }
+
+ if (attr.LowerCaseEqualsASCII(kTitleStar.get())) {
+ if (titleStar.IsEmpty() && !wasQuotedString) {
+ // RFC 5987 encoding; uses token format only, so skip if we get
+ // here with a quoted-string
+ nsAutoString tmp;
+ tmp = value;
+ if (Decode5987Format(tmp)) {
+ titleStar = tmp;
+ titleStar.CompressWhitespace();
+ } else {
+ // header value did not parse, throw it away
+ titleStar.Truncate();
+ }
+ }
+ } else {
+ header.MaybeUpdateAttribute(attr, value);
+ }
+ }
+ }
+
+ if (endCh == kComma) {
+ // hit a comma, process what we've got so far
+
+ header.mHref.Trim(" \t\n\r\f"); // trim HTML5 whitespace
+ if (!header.mHref.IsEmpty() && !header.mRel.IsEmpty()) {
+ if (!titleStar.IsEmpty()) {
+ // prefer RFC 5987 variant over non-I18zed version
+ header.mTitle = titleStar;
+ }
+ linkHeaders.AppendElement(header);
+ }
+
+ titleStar.Truncate();
+ header.Reset();
+
+ seenParameters = false;
+ }
+
+ start = ++end;
+ }
+
+ header.mHref.Trim(" \t\n\r\f"); // trim HTML5 whitespace
+ if (!header.mHref.IsEmpty() && !header.mRel.IsEmpty()) {
+ if (!titleStar.IsEmpty()) {
+ // prefer RFC 5987 variant over non-I18zed version
+ header.mTitle = titleStar;
+ }
+ linkHeaders.AppendElement(header);
+ }
+
+ return linkHeaders;
+}
+
+void LinkHeader::MaybeUpdateAttribute(const nsAString& aAttribute,
+ const char16_t* aValue) {
+ MOZ_ASSERT(!aAttribute.LowerCaseEqualsASCII(kTitleStar.get()));
+
+ if (aAttribute.LowerCaseEqualsLiteral("rel")) {
+ if (mRel.IsEmpty()) {
+ mRel = aValue;
+ mRel.CompressWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("title")) {
+ if (mTitle.IsEmpty()) {
+ mTitle = aValue;
+ mTitle.CompressWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("type")) {
+ if (mType.IsEmpty()) {
+ mType = aValue;
+ mType.StripWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("media")) {
+ if (mMedia.IsEmpty()) {
+ mMedia = aValue;
+
+ // The HTML5 spec is formulated in terms of the CSS3 spec,
+ // which specifies that media queries are case insensitive.
+ nsContentUtils::ASCIIToLower(mMedia);
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("anchor")) {
+ if (mAnchor.IsEmpty()) {
+ mAnchor = aValue;
+ mAnchor.StripWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("crossorigin")) {
+ if (mCrossOrigin.IsVoid()) {
+ mCrossOrigin.SetIsVoid(false);
+ mCrossOrigin = aValue;
+ mCrossOrigin.StripWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("as")) {
+ if (mAs.IsEmpty()) {
+ mAs = aValue;
+ mAs.CompressWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("referrerpolicy")) {
+ // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#referrer-policy-attribute
+ // Specs says referrer policy attribute is an enumerated attribute,
+ // case insensitive and includes the empty string
+ // We will parse the aValue with AttributeReferrerPolicyFromString
+ // later, which will handle parsing it as an enumerated attribute.
+ if (mReferrerPolicy.IsEmpty()) {
+ mReferrerPolicy = aValue;
+ }
+
+ } else if (aAttribute.LowerCaseEqualsLiteral("nonce")) {
+ if (mNonce.IsEmpty()) {
+ mNonce = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("integrity")) {
+ if (mIntegrity.IsEmpty()) {
+ mIntegrity = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("imagesrcset")) {
+ if (mSrcset.IsEmpty()) {
+ mSrcset = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("imagesizes")) {
+ if (mSizes.IsEmpty()) {
+ mSizes = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("fetchpriority")) {
+ if (mFetchPriority.IsEmpty()) {
+ LOG(("Update fetchPriority to \"%s\"",
+ NS_ConvertUTF16toUTF8(aValue).get()));
+ mFetchPriority = aValue;
+ }
+ }
+}
+
+// We will use official mime-types from:
+// https://www.iana.org/assignments/media-types/media-types.xhtml#font
+// We do not support old deprecated mime-types for preload feature.
+// (We currectly do not support font/collection)
+static uint32_t StyleLinkElementFontMimeTypesNum = 5;
+static const char* StyleLinkElementFontMimeTypes[] = {
+ "font/otf", "font/sfnt", "font/ttf", "font/woff", "font/woff2"};
+
+bool IsFontMimeType(const nsAString& aType) {
+ if (aType.IsEmpty()) {
+ return true;
+ }
+ for (uint32_t i = 0; i < StyleLinkElementFontMimeTypesNum; i++) {
+ if (aType.EqualsASCII(StyleLinkElementFontMimeTypes[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static const nsAttrValue::EnumTable kAsAttributeTable[] = {
+ {"", DESTINATION_INVALID}, {"audio", DESTINATION_AUDIO},
+ {"font", DESTINATION_FONT}, {"image", DESTINATION_IMAGE},
+ {"script", DESTINATION_SCRIPT}, {"style", DESTINATION_STYLE},
+ {"track", DESTINATION_TRACK}, {"video", DESTINATION_VIDEO},
+ {"fetch", DESTINATION_FETCH}, {nullptr, 0}};
+
+void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult) {
+ DebugOnly<bool> success =
+ aResult.ParseEnumValue(aValue, kAsAttributeTable, false,
+ // default value is a empty string
+ // if aValue is not a value we
+ // understand
+ &kAsAttributeTable[0]);
+ MOZ_ASSERT(success);
+}
+
+nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue) {
+ switch (aValue.GetEnumValue()) {
+ case DESTINATION_INVALID:
+ return nsIContentPolicy::TYPE_INVALID;
+ case DESTINATION_AUDIO:
+ return nsIContentPolicy::TYPE_INTERNAL_AUDIO;
+ case DESTINATION_TRACK:
+ return nsIContentPolicy::TYPE_INTERNAL_TRACK;
+ case DESTINATION_VIDEO:
+ return nsIContentPolicy::TYPE_INTERNAL_VIDEO;
+ case DESTINATION_FONT:
+ return nsIContentPolicy::TYPE_FONT;
+ case DESTINATION_IMAGE:
+ return nsIContentPolicy::TYPE_IMAGE;
+ case DESTINATION_SCRIPT:
+ return nsIContentPolicy::TYPE_SCRIPT;
+ case DESTINATION_STYLE:
+ return nsIContentPolicy::TYPE_STYLESHEET;
+ case DESTINATION_FETCH:
+ return nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD;
+ }
+ return nsIContentPolicy::TYPE_INVALID;
+}
+
+// TODO: implement this using nsAttrValue's destination enums when support for
+// the new destinations is added; see this diff for a possible start:
+// https://phabricator.services.mozilla.com/D172368?vs=705114&id=708720
+bool IsScriptLikeOrInvalid(const nsAString& aAs) {
+ return !(
+ aAs.LowerCaseEqualsASCII("fetch") || aAs.LowerCaseEqualsASCII("audio") ||
+ aAs.LowerCaseEqualsASCII("document") ||
+ aAs.LowerCaseEqualsASCII("embed") || aAs.LowerCaseEqualsASCII("font") ||
+ aAs.LowerCaseEqualsASCII("frame") || aAs.LowerCaseEqualsASCII("iframe") ||
+ aAs.LowerCaseEqualsASCII("image") ||
+ aAs.LowerCaseEqualsASCII("manifest") ||
+ aAs.LowerCaseEqualsASCII("object") ||
+ aAs.LowerCaseEqualsASCII("report") || aAs.LowerCaseEqualsASCII("style") ||
+ aAs.LowerCaseEqualsASCII("track") || aAs.LowerCaseEqualsASCII("video") ||
+ aAs.LowerCaseEqualsASCII("webidentity") ||
+ aAs.LowerCaseEqualsASCII("xslt"));
+}
+
+bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType,
+ const nsAString& aMedia,
+ mozilla::dom::Document* aDocument) {
+ nsContentPolicyType policyType = AsValueToContentPolicy(aAs);
+ if (policyType == nsIContentPolicy::TYPE_INVALID) {
+ return false;
+ }
+
+ // Check if media attribute is valid.
+ if (!aMedia.IsEmpty()) {
+ RefPtr<mozilla::dom::MediaList> mediaList =
+ mozilla::dom::MediaList::Create(NS_ConvertUTF16toUTF8(aMedia));
+ if (!mediaList->Matches(*aDocument)) {
+ return false;
+ }
+ }
+
+ if (aType.IsEmpty()) {
+ return true;
+ }
+
+ if (policyType == nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD) {
+ return true;
+ }
+
+ nsAutoString type(aType);
+ ToLowerCase(type);
+ if (policyType == nsIContentPolicy::TYPE_MEDIA) {
+ if (aAs.GetEnumValue() == DESTINATION_TRACK) {
+ return type.EqualsASCII("text/vtt");
+ }
+ Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aType);
+ if (!mimeType) {
+ return false;
+ }
+ DecoderDoctorDiagnostics diagnostics;
+ CanPlayStatus status =
+ DecoderTraits::CanHandleContainerType(*mimeType, &diagnostics);
+ // Preload if this return CANPLAY_YES and CANPLAY_MAYBE.
+ return status != CANPLAY_NO;
+ }
+ if (policyType == nsIContentPolicy::TYPE_FONT) {
+ return IsFontMimeType(type);
+ }
+ if (policyType == nsIContentPolicy::TYPE_IMAGE) {
+ return imgLoader::SupportImageWithMimeType(
+ NS_ConvertUTF16toUTF8(type), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
+ }
+ if (policyType == nsIContentPolicy::TYPE_SCRIPT) {
+ return nsContentUtils::IsJavascriptMIMEType(type);
+ }
+ if (policyType == nsIContentPolicy::TYPE_STYLESHEET) {
+ return type.EqualsASCII("text/css");
+ }
+ return false;
+}
+
+void WarnIgnoredPreload(const mozilla::dom::Document& aDoc, nsIURI& aURI) {
+ AutoTArray<nsString, 1> params;
+ {
+ nsCString uri = nsContentUtils::TruncatedURLForDisplay(&aURI);
+ AppendUTF8toUTF16(uri, *params.AppendElement());
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, &aDoc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "PreloadIgnoredInvalidAttr", params);
+}
+
+nsresult HasRootDomain(const nsACString& aInput, const nsACString& aHost,
+ bool* aResult) {
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = false;
+
+ // If the strings are the same, we obviously have a match.
+ if (aInput == aHost) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ // If aHost is not found, we know we do not have it as a root domain.
+ int32_t index = nsAutoCString(aInput).Find(aHost);
+ if (index == kNotFound) {
+ return NS_OK;
+ }
+
+ // Otherwise, we have aHost as our root domain iff the index of aHost is
+ // aHost.length subtracted from our length and (since we do not have an
+ // exact match) the character before the index is a dot or slash.
+ *aResult = index > 0 && (uint32_t)index == aInput.Length() - aHost.Length() &&
+ (aInput[index - 1] == '.' || aInput[index - 1] == '/');
+ return NS_OK;
+}
+
+void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI) {
+ if (!aURI) {
+ return;
+ }
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("resource")) {
+ return;
+ }
+ nsAutoCString host;
+ aURI->GetHost(host);
+ // Ignore test hits.
+ if (host.EqualsLiteral("mochitests") || host.EqualsLiteral("reftest")) {
+ return;
+ }
+
+ nsAutoCString filePath;
+ aURI->GetFilePath(filePath);
+ // Fluent likes checking for files everywhere and expects failure.
+ if (StringEndsWith(filePath, ".ftl"_ns)) {
+ return;
+ }
+
+ // Ignore fetches/xhrs, as they are frequently used in a way where
+ // non-existence is OK (ie with fallbacks). This risks false negatives (ie
+ // files that *should* be there but aren't) - which we accept for now.
+ ExtContentPolicy policy = aLoadInfo
+ ? aLoadInfo->GetExternalContentPolicyType()
+ : ExtContentPolicy::TYPE_OTHER;
+ if (policy == ExtContentPolicy::TYPE_FETCH ||
+ policy == ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ return;
+ }
+
+ if (aLoadInfo) {
+ bool shouldSkipCheckForBrokenURLOrZeroSized;
+ MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetShouldSkipCheckForBrokenURLOrZeroSized(
+ &shouldSkipCheckForBrokenURLOrZeroSized));
+ if (shouldSkipCheckForBrokenURLOrZeroSized) {
+ return;
+ }
+ }
+
+ nsCString spec;
+ aURI->GetSpec(spec);
+
+#ifdef ANDROID
+ // Various toolkit files use this and are shipped on android, but
+ // info-pages.css and aboutLicense.css are not - bug 1808987
+ if (StringEndsWith(spec, "info-pages.css"_ns) ||
+ StringEndsWith(spec, "aboutLicense.css"_ns) ||
+ // Error page CSS is also missing: bug 1810039
+ StringEndsWith(spec, "aboutNetError.css"_ns) ||
+ StringEndsWith(spec, "aboutHttpsOnlyError.css"_ns) ||
+ StringEndsWith(spec, "error-pages.css"_ns) ||
+ // popup.css is used in a single mochitest: bug 1810577
+ StringEndsWith(spec, "/popup.css"_ns) ||
+ // Used by an extension installation test - bug 1809650
+ StringBeginsWith(spec, "resource://android/assets/web_extensions/"_ns)) {
+ return;
+ }
+#endif
+
+ // DTD files from gre may not exist when requested by tests.
+ if (StringBeginsWith(spec, "resource://gre/res/dtd/"_ns)) {
+ return;
+ }
+
+ // The background task machinery allows the caller to specify a JSM on the
+ // command line, which is then looked up in both app-specific and toolkit-wide
+ // locations.
+ if (spec.Find("backgroundtasks") != kNotFound) {
+ return;
+ }
+
+ if (xpc::IsInAutomation()) {
+#ifdef DEBUG
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+ Unused << xpc->DebugDumpJSStack(false, false, false);
+ }
+#endif
+ MOZ_CRASH_UNSAFE_PRINTF("Missing chrome or resource URLs: %s", spec.get());
+ } else {
+ printf_stderr("Missing chrome or resource URL: %s\n", spec.get());
+ }
+}
+
+bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled) {
+ return StaticPrefs::
+ browser_tabs_remote_coep_credentialless_DoNotUseDirectly() ||
+ aIsOriginTrialCoepCredentiallessEnabled;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h
new file mode 100644
index 0000000000..c1f30a56ed
--- /dev/null
+++ b/netwerk/base/nsNetUtil.h
@@ -0,0 +1,1105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetUtil_h__
+#define nsNetUtil_h__
+
+#include <functional>
+#include "mozilla/Maybe.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsAttrValue.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadGroup.h"
+#include "nsINestedURI.h"
+#include "nsINetUtil.h"
+#include "nsIRequest.h"
+#include "nsILoadInfo.h"
+#include "nsIIOService.h"
+#include "nsIURI.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsNetCID.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIPrincipal;
+class nsIAsyncStreamCopier;
+class nsIAuthPrompt;
+class nsIAuthPrompt2;
+class nsIChannel;
+class nsIChannelPolicy;
+class nsICookieJarSettings;
+class nsIDownloadObserver;
+class nsIEventTarget;
+class nsIFileProtocolHandler;
+class nsIFileRandomAccessStream;
+class nsIHttpChannel;
+class nsIInputStream;
+class nsIInputStreamPump;
+class nsIInterfaceRequestor;
+class nsIOutputStream;
+class nsIParentChannel;
+class nsIPersistentProperties;
+class nsIProxyInfo;
+class nsIRandomAccessStream;
+class nsIRequestObserver;
+class nsISerialEventTarget;
+class nsIStreamListener;
+class nsIStreamLoader;
+class nsIStreamLoaderObserver;
+class nsIIncrementalStreamLoader;
+class nsIIncrementalStreamLoaderObserver;
+
+namespace mozilla {
+class Encoding;
+class OriginAttributes;
+class OriginTrials;
+namespace dom {
+class ClientInfo;
+class PerformanceStorage;
+class ServiceWorkerDescriptor;
+} // namespace dom
+
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+
+} // namespace mozilla
+
+template <class>
+class nsCOMPtr;
+template <typename>
+struct already_AddRefed;
+
+already_AddRefed<nsIIOService> do_GetIOService(nsresult* error = nullptr);
+
+already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult* error = nullptr);
+
+// private little helper function... don't call this directly!
+nsresult net_EnsureIOService(nsIIOService** ios, nsCOMPtr<nsIIOService>& grip);
+
+nsresult NS_NewURI(nsIURI** aURI, const nsACString& spec,
+ const char* charset = nullptr, nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const nsACString& spec,
+ mozilla::NotNull<const mozilla::Encoding*> encoding,
+ nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& spec,
+ const char* charset = nullptr, nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& spec,
+ mozilla::NotNull<const mozilla::Encoding*> encoding,
+ nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const char* spec,
+ nsIURI* baseURI = nullptr);
+
+nsresult NS_NewFileURI(
+ nsIURI** result, nsIFile* spec,
+ nsIIOService* ioService =
+ nullptr); // pass in nsIIOService to optimize callers
+
+// Functions for adding additional encoding to a URL for compatibility with
+// Apple's NSURL class URLWithString method.
+//
+// @param aResult
+// Out parameter for the encoded URL spec
+// @param aSpec
+// The spec for the URL to be encoded
+nsresult NS_GetSpecWithNSURLEncoding(nsACString& aResult,
+ const nsACString& aSpec);
+// @param aResult
+// Out parameter for the encoded URI
+// @param aSpec
+// The spec for the URL to be encoded
+nsresult NS_NewURIWithNSURLEncoding(nsIURI** aResult, const nsACString& aSpec);
+
+// These methods will only mutate the URI if the ref of aInput doesn't already
+// match the ref we are trying to set.
+// If aInput has no ref, and we are calling NS_GetURIWithoutRef, or
+// NS_GetURIWithNewRef with an empty string, then aOutput will be the same
+// as aInput. The same is true if aRef is already equal to the ref of aInput.
+// This is OK because URIs are immutable and threadsafe.
+// If the URI doesn't support ref fragments aOutput will be the same as aInput.
+nsresult NS_GetURIWithNewRef(nsIURI* aInput, const nsACString& aRef,
+ nsIURI** aOutput);
+nsresult NS_GetURIWithoutRef(nsIURI* aInput, nsIURI** aOutput);
+
+nsresult NS_GetSanitizedURIStringFromURI(nsIURI* aUri,
+ nsAString& aSanitizedSpec);
+
+/*
+ * How to create a new Channel, using NS_NewChannel,
+ * NS_NewChannelWithTriggeringPrincipal,
+ * NS_NewInputStreamChannel, NS_NewChannelInternal
+ * and it's variations:
+ *
+ * What specific API function to use:
+ * * The NS_NewChannelInternal functions should almost never be directly
+ * called outside of necko code.
+ * * If possible, use NS_NewChannel() providing a loading *nsINode*
+ * * If no loading *nsINode* is available, try calling NS_NewChannel() providing
+ * a loading *ClientInfo*.
+ * * If no loading *nsINode* or *ClientInfo* are available, call NS_NewChannel()
+ * providing a loading *nsIPrincipal*.
+ * * Call NS_NewChannelWithTriggeringPrincipal if the triggeringPrincipal
+ * is different from the loadingPrincipal.
+ * * Call NS_NewChannelInternal() providing aLoadInfo object in cases where
+ * you already have loadInfo object, e.g in case of a channel redirect.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ *
+ * Note, if you provide a loading ClientInfo its principal must match the
+ * loading principal. Currently you must pass both as the loading principal
+ * may have additional mutable values like CSP on it. In the future these
+ * will be removed from nsIPrincipal and the API can be changed to take just
+ * the loading ClientInfo.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ */
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ const mozilla::Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsILoadInfo* aLoadInfo,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const mozilla::dom::ClientInfo& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannel(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannel(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannel(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ const mozilla::dom::ClientInfo& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+nsresult NS_GetIsDocumentChannel(nsIChannel* aChannel, bool* aIsDocument);
+
+nsresult NS_MakeAbsoluteURI(nsACString& result, const nsACString& spec,
+ nsIURI* baseURI);
+
+nsresult NS_MakeAbsoluteURI(char** result, const char* spec, nsIURI* baseURI);
+
+nsresult NS_MakeAbsoluteURI(nsAString& result, const nsAString& spec,
+ nsIURI* baseURI);
+
+/**
+ * This function is a helper function to get a scheme's default port.
+ */
+int32_t NS_GetDefaultPort(const char* scheme,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool NS_StringToACE(const nsACString& idn, nsACString& result);
+
+/**
+ * This function is a helper function to get a protocol's default port if the
+ * URI does not specify a port explicitly. Returns -1 if this protocol has no
+ * concept of ports or if there was an error getting the port.
+ */
+int32_t NS_GetRealPort(nsIURI* aURI);
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsILoadInfo* aLoadInfo);
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType);
+
+nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const nsACString& aContentType = ""_ns,
+ const nsACString& aContentCharset = ""_ns);
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, const nsAString& aData,
+ const nsACString& aContentType, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsILoadInfo* aLoadInfo,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamPump(
+ nsIInputStreamPump** aResult, already_AddRefed<nsIInputStream> aStream,
+ uint32_t aSegsize = 0, uint32_t aSegcount = 0, bool aCloseWhenDone = false,
+ nsISerialEventTarget* aMainThreadTarget = nullptr);
+
+nsresult NS_NewLoadGroup(nsILoadGroup** result, nsIRequestObserver* obs);
+
+// Create a new nsILoadGroup that will match the given principal.
+nsresult NS_NewLoadGroup(nsILoadGroup** aResult, nsIPrincipal* aPrincipal);
+
+// Determine if the given loadGroup/principal pair will produce a principal
+// with similar permissions when passed to NS_NewChannel(). This checks for
+// things like making sure the browser element flag matches. Without
+// an appropriate load group these values can be lost when getting the result
+// principal back out of the channel. Null principals are also always allowed
+// as they do not have permissions to actually use the load group.
+bool NS_LoadGroupMatchesPrincipal(nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal);
+
+nsresult NS_NewDownloader(nsIStreamListener** result,
+ nsIDownloadObserver* observer,
+ nsIFile* downloadLocation = nullptr);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader** result,
+ nsIStreamLoaderObserver* observer,
+ nsIRequestObserver* requestObserver = nullptr);
+
+nsresult NS_NewIncrementalStreamLoader(
+ nsIIncrementalStreamLoader** result,
+ nsIIncrementalStreamLoaderObserver* observer);
+
+nsresult NS_NewStreamLoaderInternal(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver,
+ nsINode* aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
+
+nsresult NS_NewSyncStreamListener(nsIStreamListener** result,
+ nsIInputStream** stream);
+
+/**
+ * Implement the nsIChannel::Open(nsIInputStream**) method using the channel's
+ * AsyncOpen method.
+ *
+ * NOTE: Reading from the returned nsIInputStream may spin the current
+ * thread's event queue, which could result in any event being processed.
+ */
+nsresult NS_ImplementChannelOpen(nsIChannel* channel, nsIInputStream** result);
+
+nsresult NS_NewRequestObserverProxy(nsIRequestObserver** result,
+ nsIRequestObserver* observer,
+ nsISupports* context);
+
+nsresult NS_NewSimpleStreamListener(nsIStreamListener** result,
+ nsIOutputStream* sink,
+ nsIRequestObserver* observer = nullptr);
+
+nsresult NS_CheckPortSafety(int32_t port, const char* scheme,
+ nsIIOService* ioService = nullptr);
+
+// Determine if this URI is using a safe port.
+nsresult NS_CheckPortSafety(nsIURI* uri);
+
+nsresult NS_NewProxyInfo(const nsACString& type, const nsACString& host,
+ int32_t port, uint32_t flags, nsIProxyInfo** result);
+
+nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler** result,
+ nsIIOService* ioService = nullptr);
+
+nsresult NS_GetFileFromURLSpec(const nsACString& inURL, nsIFile** result,
+ nsIIOService* ioService = nullptr);
+
+nsresult NS_GetURLSpecFromFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are not directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromActualFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromDir(nsIFile* file, nsACString& url,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * Obtains the referrer for a given channel. This first tries to obtain the
+ * referrer from the property docshell.internalReferrer, and if that doesn't
+ * work and the channel is an nsIHTTPChannel, we check it's referrer property.
+ *
+ */
+void NS_GetReferrerFromChannel(nsIChannel* channel, nsIURI** referrer);
+
+nsresult NS_ParseRequestContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset);
+
+nsresult NS_ParseResponseContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset);
+
+nsresult NS_ExtractCharsetFromContentType(const nsACString& rawContentType,
+ nsCString& contentCharset,
+ bool* hadCharset,
+ int32_t* charsetStart,
+ int32_t* charsetEnd);
+
+nsresult NS_NewLocalFileInputStream(nsIInputStream** result, nsIFile* file,
+ int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewLocalFileInputStream(
+ nsIFile* file, int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+mozilla::Result<nsCOMPtr<nsIOutputStream>, nsresult>
+NS_NewLocalFileOutputStream(nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1, int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result,
+ const mozilla::ipc::FileDescriptor& fd);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewAtomicFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream** result,
+ nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileRandomAccessStream(nsIRandomAccessStream** result,
+ nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+mozilla::Result<nsCOMPtr<nsIRandomAccessStream>, nsresult>
+NS_NewLocalFileRandomAccessStream(nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1, int32_t behaviorFlags = 0);
+
+[[nodiscard]] nsresult NS_NewBufferedInputStream(
+ nsIInputStream** aResult, already_AddRefed<nsIInputStream> aInputStream,
+ uint32_t aBufferSize);
+
+mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewBufferedInputStream(
+ already_AddRefed<nsIInputStream> aInputStream, uint32_t aBufferSize);
+
+// note: the resulting stream can be QI'ed to nsISafeOutputStream iff the
+// provided stream supports it.
+nsresult NS_NewBufferedOutputStream(
+ nsIOutputStream** aResult, already_AddRefed<nsIOutputStream> aOutputStream,
+ uint32_t aBufferSize);
+
+/**
+ * This function reads an inputStream and stores its content into a buffer. In
+ * general, you should avoid using this function because, it blocks the current
+ * thread until the operation is done.
+ * If the inputStream is async, the reading happens on an I/O thread.
+ *
+ * @param aInputStream the inputStream.
+ * @param aDest the destination buffer. if *aDest is null, it will be allocated
+ * with the size of the written data. if aDest is not null, aCount
+ * must greater than 0.
+ * @param aCount the amount of data to read. Use -1 if you want that all the
+ * stream is read.
+ * @param aWritten this pointer will be used to store the number of data
+ * written in the buffer. If you don't need, pass nullptr.
+ */
+nsresult NS_ReadInputStreamToBuffer(nsIInputStream* aInputStream, void** aDest,
+ int64_t aCount,
+ uint64_t* aWritten = nullptr);
+
+/**
+ * See the comment for NS_ReadInputStreamToBuffer
+ */
+nsresult NS_ReadInputStreamToString(nsIInputStream* aInputStream,
+ nsACString& aDest, int64_t aCount,
+ uint64_t* aWritten = nullptr);
+
+nsresult NS_LoadPersistentPropertiesFromURISpec(
+ nsIPersistentProperties** outResult, const nsACString& aSpec);
+
+/**
+ * NS_QueryNotificationCallbacks implements the canonical algorithm for
+ * querying interfaces from a channel's notification callbacks. It first
+ * searches the channel's notificationCallbacks attribute, and if the interface
+ * is not found there, then it inspects the notificationCallbacks attribute of
+ * the channel's loadGroup.
+ *
+ * Note: templatized only because nsIWebSocketChannel is currently not an
+ * nsIChannel.
+ */
+template <class T>
+inline void NS_QueryNotificationCallbacks(T* channel, const nsIID& iid,
+ void** result) {
+ MOZ_ASSERT(channel, "null channel");
+ *result = nullptr;
+
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ mozilla::Unused << channel->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs) cbs->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mozilla::Unused << channel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs) cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+// template helper:
+// Note: "class C" templatized only because nsIWebSocketChannel is currently not
+// an nsIChannel.
+
+template <class C, class T>
+inline void NS_QueryNotificationCallbacks(C* channel, nsCOMPtr<T>& result) {
+ NS_QueryNotificationCallbacks(channel, NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/**
+ * Alternate form of NS_QueryNotificationCallbacks designed for use by
+ * nsIChannel implementations.
+ */
+inline void NS_QueryNotificationCallbacks(nsIInterfaceRequestor* callbacks,
+ nsILoadGroup* loadGroup,
+ const nsIID& iid, void** result) {
+ *result = nullptr;
+
+ if (callbacks) callbacks->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs) cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+/**
+ * Returns true if channel is using Private Browsing, or false if not.
+ * Returns false if channel's callbacks don't implement nsILoadContext.
+ */
+bool NS_UsePrivateBrowsing(nsIChannel* channel);
+
+/**
+ * Returns true if the channel has visited any cross-origin URLs on any
+ * URLs that it was redirected through.
+ */
+bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport = false);
+
+/**
+ * Returns true if the channel has a safe method.
+ */
+bool NS_IsSafeMethodNav(nsIChannel* aChannel);
+
+// Unique first-party domain for separating the safebrowsing cookie.
+// Note if this value is changed, code in test_cookiejars_safebrowsing.js and
+// nsUrlClassifierHashCompleter.js should also be changed.
+#define NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN \
+ "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla"
+
+// Unique first-party domain for separating about uri.
+#define ABOUT_URI_FIRST_PARTY_DOMAIN \
+ "about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla"
+
+/**
+ * Wraps an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2. This
+ * method is provided mainly for use by other methods in this file.
+ *
+ * *aAuthPrompt2 should be set to null before calling this function.
+ */
+void NS_WrapAuthPrompt(nsIAuthPrompt* aAuthPrompt,
+ nsIAuthPrompt2** aAuthPrompt2);
+
+/**
+ * Gets an auth prompt from an interface requestor. This takes care of wrapping
+ * an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2.
+ */
+void NS_QueryAuthPrompt2(nsIInterfaceRequestor* aCallbacks,
+ nsIAuthPrompt2** aAuthPrompt);
+
+/**
+ * Gets an nsIAuthPrompt2 from a channel. Use this instead of
+ * NS_QueryNotificationCallbacks for better backwards compatibility.
+ */
+void NS_QueryAuthPrompt2(nsIChannel* aChannel, nsIAuthPrompt2** aAuthPrompt);
+
+/* template helper */
+template <class T>
+inline void NS_QueryNotificationCallbacks(nsIInterfaceRequestor* callbacks,
+ nsILoadGroup* loadGroup,
+ nsCOMPtr<T>& result) {
+ NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/* template helper */
+template <class T>
+inline void NS_QueryNotificationCallbacks(
+ const nsCOMPtr<nsIInterfaceRequestor>& aCallbacks,
+ const nsCOMPtr<nsILoadGroup>& aLoadGroup, nsCOMPtr<T>& aResult) {
+ NS_QueryNotificationCallbacks(aCallbacks.get(), aLoadGroup.get(), aResult);
+}
+
+/* template helper */
+template <class T>
+inline void NS_QueryNotificationCallbacks(const nsCOMPtr<nsIChannel>& aChannel,
+ nsCOMPtr<T>& aResult) {
+ NS_QueryNotificationCallbacks(aChannel.get(), aResult);
+}
+
+/**
+ * This function returns a nsIInterfaceRequestor instance that returns the
+ * same result as NS_QueryNotificationCallbacks when queried. It is useful
+ * as the value for nsISocketTransport::securityCallbacks.
+ */
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIEventTarget* target, nsIInterfaceRequestor** result);
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIInterfaceRequestor** result);
+
+/**
+ * Helper function for testing online/offline state of the browser.
+ */
+bool NS_IsOffline();
+
+/**
+ * Helper functions for implementing nsINestedURI::innermostURI.
+ *
+ * Note that NS_DoImplGetInnermostURI is "private" -- call
+ * NS_ImplGetInnermostURI instead.
+ */
+nsresult NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result);
+
+nsresult NS_ImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result);
+
+/**
+ * Helper function for testing whether the given URI, or any of its
+ * inner URIs, has all the given protocol flags.
+ */
+nsresult NS_URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result);
+
+/**
+ * Helper function for getting the innermost URI for a given URI. The return
+ * value could be just the object passed in if it's not a nested URI.
+ */
+already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI* aURI);
+
+/**
+ * Helper function for getting the host name of the innermost URI for a given
+ * URI. The return value could be the host name of the URI passed in if it's
+ * not a nested URI.
+ */
+inline nsresult NS_GetInnermostURIHost(nsIURI* aURI, nsACString& aHost) {
+ aHost.Truncate();
+
+ // This block is optimized in order to avoid the overhead of calling
+ // NS_GetInnermostURI() which incurs a lot of overhead in terms of
+ // AddRef/Release calls.
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(aURI);
+ if (nestedURI) {
+ // We have a nested URI!
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = uri->GetAsciiHost(aHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ // We have a non-nested URI!
+ nsresult rv = aURI->GetAsciiHost(aHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Get the "final" URI for a channel. This is either channel's load info
+ * resultPrincipalURI, if set, or GetOriginalURI. In most cases (but not all)
+ * load info resultPrincipalURI, if set, corresponds to URI of the channel if
+ * it's required to represent the actual principal for the channel.
+ */
+nsresult NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri);
+
+// NS_SecurityHashURI must return the same hash value for any two URIs that
+// compare equal according to NS_SecurityCompareURIs. Unfortunately, in the
+// case of files, it's not clear we can do anything better than returning
+// the schemeHash, so hashing files degenerates to storing them in a list.
+uint32_t NS_SecurityHashURI(nsIURI* aURI);
+
+bool NS_SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI,
+ bool aStrictFileOriginPolicy);
+
+bool NS_URIIsLocalFile(nsIURI* aURI);
+
+// When strict file origin policy is enabled, SecurityCompareURIs will fail for
+// file URIs that do not point to the same local file. This call provides an
+// alternate file-specific origin check that allows target files that are
+// contained in the same directory as the source.
+//
+// https://developer.mozilla.org/en-US/docs/Same-origin_policy_for_file:_URIs
+bool NS_RelaxStrictFileOriginPolicy(nsIURI* aTargetURI, nsIURI* aSourceURI,
+ bool aAllowDirectoryTarget = false);
+
+bool NS_IsInternalSameURIRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel, uint32_t aFlags);
+
+bool NS_IsHSTSUpgradeRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags);
+
+bool NS_ShouldRemoveAuthHeaderOnRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags);
+
+nsresult NS_LinkRedirectChannels(uint64_t channelId,
+ nsIParentChannel* parentChannel,
+ nsIChannel** _result);
+
+/**
+ * Returns nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP if `aHeader` is
+ * "require-corp" and nsILoadInfo::EMBEDDER_POLICY_NULL otherwise.
+ *
+ * See: https://mikewest.github.io/corpp/#parsing
+ */
+nsILoadInfo::CrossOriginEmbedderPolicy
+NS_GetCrossOriginEmbedderPolicyFromHeader(
+ const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled);
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t NS_GetContentDispositionFromToken(const nsAString& aDispToken);
+
+/** Determine the disposition (inline/attachment) of the content based on the
+ * Content-Disposition header
+ * @param aHeader the content-disposition header (full value)
+ * @param aChan the channel the header came from
+ */
+uint32_t NS_GetContentDispositionFromHeader(const nsACString& aHeader,
+ nsIChannel* aChan = nullptr);
+
+/** Extracts the filename out of a content-disposition header
+ * @param aFilename [out] The filename. Can be empty on error.
+ * @param aDisposition Value of a Content-Disposition header
+
+ */
+nsresult NS_GetFilenameFromDisposition(nsAString& aFilename,
+ const nsACString& aDisposition);
+
+/**
+ * Make sure Personal Security Manager is initialized
+ */
+void net_EnsurePSMInit();
+
+/**
+ * Test whether a URI is "about:blank". |uri| must not be null
+ */
+bool NS_IsAboutBlank(nsIURI* uri);
+
+/**
+ * Test whether a URI is "about:srcdoc". |uri| must not be null
+ */
+bool NS_IsAboutSrcdoc(nsIURI* uri);
+
+nsresult NS_GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine);
+
+/**
+ * Sniff the content type for a given request or a given buffer.
+ *
+ * aSnifferType can be either NS_CONTENT_SNIFFER_CATEGORY or
+ * NS_DATA_SNIFFER_CATEGORY. The function returns the sniffed content type
+ * in the aSniffedType argument. This argument will not be modified if the
+ * content type could not be sniffed.
+ */
+void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest,
+ const uint8_t* aData, uint32_t aLength,
+ nsACString& aSniffedType);
+
+/**
+ * Whether the channel was created to load a srcdoc document.
+ * Note that view-source:about:srcdoc is classified as a srcdoc document by
+ * this function, which may not be applicable everywhere.
+ */
+bool NS_IsSrcdocChannel(nsIChannel* aChannel);
+
+/**
+ * Return true if the given string is a reasonable HTTP header value given the
+ * definition in RFC 2616 section 4.2. Currently we don't pay the cost to do
+ * full, sctrict validation here since it would require fulling parsing the
+ * value.
+ */
+bool NS_IsReasonableHTTPHeaderValue(const nsACString& aValue);
+
+/**
+ * Return true if the given string is a valid HTTP token per RFC 2616 section
+ * 2.2.
+ */
+bool NS_IsValidHTTPToken(const nsACString& aToken);
+
+/**
+ * Strip the leading or trailing HTTP whitespace per fetch spec section 2.2.
+ */
+void NS_TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest);
+
+template <typename Char>
+constexpr bool NS_IsHTTPTokenPoint(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
+ c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
+ c == '^' || c == '_' || c == '`' || c == '|' || c == '~' ||
+ mozilla::IsAsciiAlphanumeric(c);
+}
+
+template <typename Char>
+constexpr bool NS_IsHTTPQuotedStringTokenPoint(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == 0x9 || (c >= ' ' && c <= '~') || mozilla::IsNonAsciiLatin1(c);
+}
+
+template <typename Char>
+constexpr bool NS_IsHTTPWhitespace(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == 0x9 || c == 0xA || c == 0xD || c == 0x20;
+}
+
+/**
+ * Return true if the given request must be upgraded to HTTPS.
+ * If |aResultCallback| is provided and the storage is not ready to read, the
+ * result will be sent back through the callback and |aWillCallback| will be
+ * true. Otherwiew, the result will be set to |aShouldUpgrade| and
+ * |aWillCallback| is false.
+ */
+nsresult NS_ShouldSecureUpgrade(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIPrincipal* aChannelResultPrincipal,
+ bool aAllowSTS, const mozilla::OriginAttributes& aOriginAttributes,
+ bool& aShouldUpgrade, std::function<void(bool, nsresult)>&& aResultCallback,
+ bool& aWillCallback);
+
+/**
+ * Returns an https URI for channels that need to go through secure upgrades.
+ */
+nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI);
+
+nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel* aChannel);
+
+/**
+ * Return true if this channel should be classified by the URL classifier.
+ */
+bool NS_ShouldClassifyChannel(nsIChannel* aChannel);
+
+/**
+ * Helper to set the blocking reason on loadinfo of the channel.
+ */
+nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason);
+nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason);
+nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo,
+ uint32_t reason);
+
+namespace mozilla {
+namespace net {
+
+const static uint64_t kJS_MAX_SAFE_UINTEGER = +9007199254740991ULL;
+const static int64_t kJS_MIN_SAFE_INTEGER = -9007199254740991LL;
+const static int64_t kJS_MAX_SAFE_INTEGER = +9007199254740991LL;
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(int64_t val);
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(uint64_t val);
+
+/**
+ * Given the value of a single header field (such as
+ * Content-Disposition and Content-Type) and the name of a parameter
+ * (e.g. filename, name, charset), returns the value of the parameter.
+ * See nsIMIMEHeaderParam.idl for more information.
+ *
+ * @param aHeaderVal a header string to get the value of a parameter
+ * from.
+ * @param aParamName the name of a MIME header parameter (e.g.
+ * filename, name, charset). If empty or nullptr,
+ * returns the first (possibly) _unnamed_ 'parameter'.
+ * @return the value of <code>aParamName</code> in Unichar(UTF-16).
+ */
+nsresult GetParameterHTTP(const nsACString& aHeaderVal, const char* aParamName,
+ nsAString& aResult);
+
+/**
+ * Helper function that determines if channel is an HTTP POST.
+ *
+ * @param aChannel
+ * The channel to test
+ *
+ * @return True if channel is an HTTP post.
+ */
+bool ChannelIsPost(nsIChannel* aChannel);
+
+/**
+ * Convenience functions for verifying nsIURI schemes. These functions simply
+ * wrap aURI->SchemeIs(), but specify the protocol as part of the function name.
+ */
+
+bool SchemeIsHTTP(nsIURI* aURI);
+bool SchemeIsHTTPS(nsIURI* aURI);
+bool SchemeIsJavascript(nsIURI* aURI);
+bool SchemeIsChrome(nsIURI* aURI);
+bool SchemeIsAbout(nsIURI* aURI);
+bool SchemeIsBlob(nsIURI* aURI);
+bool SchemeIsFile(nsIURI* aURI);
+bool SchemeIsData(nsIURI* aURI);
+bool SchemeIsViewSource(nsIURI* aURI);
+bool SchemeIsResource(nsIURI* aURI);
+bool SchemeIsFTP(nsIURI* aURI);
+
+// Helper functions for SetProtocol methods to follow
+// step 2.1 in https://url.spec.whatwg.org/#scheme-state
+bool SchemeIsSpecial(const nsACString&);
+bool IsSchemeChangePermitted(nsIURI*, const nsACString&);
+already_AddRefed<nsIURI> TryChangeProtocol(nsIURI*, const nsAString&);
+
+struct LinkHeader {
+ nsString mHref;
+ nsString mRel;
+ nsString mTitle;
+ nsString mNonce;
+ nsString mIntegrity;
+ nsString mSrcset;
+ nsString mSizes;
+ nsString mType;
+ nsString mMedia;
+ nsString mAnchor;
+ nsString mCrossOrigin;
+ nsString mReferrerPolicy;
+ nsString mAs;
+ nsString mFetchPriority;
+
+ LinkHeader();
+ void Reset();
+
+ nsresult NewResolveHref(nsIURI** aOutURI, nsIURI* aBaseURI) const;
+
+ bool operator==(const LinkHeader& rhs) const;
+
+ void MaybeUpdateAttribute(const nsAString& aAttribute,
+ const char16_t* aValue);
+};
+
+// Implements roughly step 2 to 4 of
+// <https://httpwg.org/specs/rfc8288.html#parse-set>.
+nsTArray<LinkHeader> ParseLinkHeader(const nsAString& aLinkData);
+
+enum ASDestination : uint8_t {
+ DESTINATION_INVALID,
+ DESTINATION_AUDIO,
+ DESTINATION_DOCUMENT,
+ DESTINATION_EMBED,
+ DESTINATION_FONT,
+ DESTINATION_IMAGE,
+ DESTINATION_MANIFEST,
+ DESTINATION_OBJECT,
+ DESTINATION_REPORT,
+ DESTINATION_SCRIPT,
+ DESTINATION_SERVICEWORKER,
+ DESTINATION_SHAREDWORKER,
+ DESTINATION_STYLE,
+ DESTINATION_TRACK,
+ DESTINATION_VIDEO,
+ DESTINATION_WORKER,
+ DESTINATION_XSLT,
+ DESTINATION_FETCH
+};
+
+void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult);
+nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue);
+bool IsScriptLikeOrInvalid(const nsAString& aAs);
+
+bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType,
+ const nsAString& aMedia,
+ mozilla::dom::Document* aDocument);
+void WarnIgnoredPreload(const mozilla::dom::Document&, nsIURI&);
+
+/**
+ * Returns true if the |aInput| in is part of the root domain of |aHost|.
+ * For example, if |aInput| is "www.mozilla.org", and we pass in
+ * "mozilla.org" as |aHost|, this will return true. It would return false
+ * the other way around.
+ *
+ * @param aInput The host to be analyzed.
+ * @param aHost The host to compare to.
+ */
+nsresult HasRootDomain(const nsACString& aInput, const nsACString& aHost,
+ bool* aResult);
+
+void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI);
+
+bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsNetUtil_h__
diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp
new file mode 100644
index 0000000000..0cf66c8ee6
--- /dev/null
+++ b/netwerk/base/nsPACMan.cpp
@@ -0,0 +1,1009 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPACMan.h"
+
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIAuthPrompt.h"
+#include "nsIDHCPClient.h"
+#include "nsIHttpChannel.h"
+#include "nsIPrefBranch.h"
+#include "nsIPromptFactory.h"
+#include "nsIProtocolProxyService.h"
+#include "nsISystemProxySettings.h"
+#include "nsIOService.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Try.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gProxyLog("proxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+#define MOZ_WPAD_URL "http://wpad/wpad.dat"
+#define MOZ_DHCP_WPAD_OPTION 252
+
+// These pointers are declared in nsProtocolProxyService.cpp
+extern const char kProxyType_HTTPS[];
+extern const char kProxyType_DIRECT[];
+
+// The PAC thread does evaluations of both PAC files and
+// nsISystemProxySettings because they can both block the calling thread and we
+// don't want that on the main thread
+
+// Check to see if the underlying request was not an error page in the case of
+// a HTTP request. For other types of channels, just return true.
+static bool HttpRequestSucceeded(nsIStreamLoader* loader) {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+
+ bool result = true; // default to assuming success
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ if (httpChannel) {
+ // failsafe
+ Unused << httpChannel->GetRequestSucceeded(&result);
+ }
+
+ return result;
+}
+
+// Read preference setting of extra JavaScript context heap size.
+// PrefService tends to be run on main thread, where ProxyAutoConfig runs on
+// ProxyResolution thread, so it's read here and passed to ProxyAutoConfig.
+static uint32_t GetExtraJSContextHeapSize() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ static int32_t extraSize = -1;
+
+ if (extraSize < 0) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ int32_t value;
+
+ if (prefs &&
+ NS_SUCCEEDED(prefs->GetIntPref(
+ "network.proxy.autoconfig_extra_jscontext_heap_size", &value))) {
+ LOG(("autoconfig_extra_jscontext_heap_size: %d\n", value));
+
+ extraSize = value;
+ }
+ }
+
+ return extraSize < 0 ? 0 : extraSize;
+}
+
+// Read network proxy type from preference
+// Used to verify that the preference is WPAD in nsPACMan::ConfigureWPAD
+nsresult GetNetworkProxyTypeFromPref(int32_t* type) {
+ *type = 0;
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ if (!prefs) {
+ LOG(("Failed to get a preference service object"));
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+ nsresult rv = prefs->GetIntPref("network.proxy.type", type);
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to retrieve network.proxy.type from prefs"));
+ return rv;
+ }
+ LOG(("network.proxy.type pref retrieved: %d\n", *type));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+// The ExecuteCallback runnable is triggered by
+// nsPACManCallback::OnQueryComplete on the Main thread when its completion is
+// discovered on the pac thread
+
+class ExecuteCallback final : public Runnable {
+ public:
+ ExecuteCallback(nsPACManCallback* aCallback, nsresult status)
+ : Runnable("net::ExecuteCallback"),
+ mCallback(aCallback),
+ mStatus(status) {}
+
+ void SetPACString(const nsACString& pacString) { mPACString = pacString; }
+
+ void SetPACURL(const nsACString& pacURL) { mPACURL = pacURL; }
+
+ NS_IMETHOD Run() override {
+ mCallback->OnQueryComplete(mStatus, mPACString, mPACURL);
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACManCallback> mCallback;
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+};
+
+//-----------------------------------------------------------------------------
+
+// The PAC thread must be deleted from the main thread, this class
+// acts as a proxy to do that, as the PACMan is reference counted
+// and might be destroyed on either thread
+
+class ShutdownThread final : public Runnable {
+ public:
+ explicit ShutdownThread(nsIThread* thread)
+ : Runnable("net::ShutdownThread"), mThread(thread) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mThread->Shutdown();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+// Dispatch this to wait until the PAC thread shuts down.
+
+class WaitForThreadShutdown final : public Runnable {
+ public:
+ explicit WaitForThreadShutdown(nsPACMan* aPACMan)
+ : Runnable("net::WaitForThreadShutdown"), mPACMan(aPACMan) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mPACMan->mPACThread) {
+ mPACMan->mPACThread->Shutdown();
+ mPACMan->mPACThread = nullptr;
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// PACLoadComplete allows the PAC thread to tell the main thread that
+// the javascript PAC file has been installed (perhaps unsuccessfully)
+// and that there is no reason to queue executions anymore
+
+class PACLoadComplete final : public Runnable {
+ public:
+ explicit PACLoadComplete(nsPACMan* aPACMan)
+ : Runnable("net::PACLoadComplete"), mPACMan(aPACMan) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ {
+ auto loader = mPACMan->mLoader.Lock();
+ loader.ref() = nullptr;
+ }
+ mPACMan->PostProcessPendingQ();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// ConfigureWPADComplete allows the PAC thread to tell the main thread that
+// the URL for the PAC file has been found
+class ConfigureWPADComplete final : public Runnable {
+ public:
+ ConfigureWPADComplete(nsPACMan* aPACMan, const nsACString& aPACURISpec)
+ : Runnable("net::ConfigureWPADComplete"),
+ mPACMan(aPACMan),
+ mPACURISpec(aPACURISpec) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mPACMan->AssignPACURISpec(mPACURISpec);
+ mPACMan->ContinueLoadingAfterPACUriKnown();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+ nsCString mPACURISpec;
+};
+
+//-----------------------------------------------------------------------------
+
+// ExecutePACThreadAction is used to proxy actions from the main
+// thread onto the PAC thread. There are 4 options: process the queue,
+// cancel the queue, query DHCP for the PAC option
+// and setup the javascript context with a new PAC file
+
+class ExecutePACThreadAction final : public Runnable {
+ public:
+ // by default we just process the queue
+ explicit ExecutePACThreadAction(nsPACMan* aPACMan)
+ : Runnable("net::ExecutePACThreadAction"),
+ mPACMan(aPACMan),
+ mCancel(false),
+ mCancelStatus(NS_OK),
+ mSetupPAC(false),
+ mExtraHeapSize(0),
+ mConfigureWPAD(false),
+ mShutdown(false) {}
+
+ void CancelQueue(nsresult status, bool aShutdown) {
+ mCancel = true;
+ mCancelStatus = status;
+ mShutdown = aShutdown;
+ }
+
+ void SetupPAC(const char* data, uint32_t dataLen, const nsACString& pacURI,
+ uint32_t extraHeapSize) {
+ mSetupPAC = true;
+ mSetupPACData.Assign(data, dataLen);
+ mSetupPACURI = pacURI;
+ mExtraHeapSize = extraHeapSize;
+ }
+
+ void ConfigureWPAD() { mConfigureWPAD = true; }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ if (mCancel) {
+ mPACMan->CancelPendingQ(mCancelStatus, mShutdown);
+ mCancel = false;
+ return NS_OK;
+ }
+
+ if (mSetupPAC) {
+ mSetupPAC = false;
+
+ nsCOMPtr<nsISerialEventTarget> target = mPACMan->GetNeckoTarget();
+ mPACMan->mPAC->ConfigurePAC(mSetupPACURI, mSetupPACData,
+ mPACMan->mIncludePath, mExtraHeapSize,
+ target);
+
+ RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan);
+ mPACMan->Dispatch(runnable.forget());
+ return NS_OK;
+ }
+
+ if (mConfigureWPAD) {
+ nsAutoCString spec;
+ mConfigureWPAD = false;
+ mPACMan->ConfigureWPAD(spec);
+ RefPtr<ConfigureWPADComplete> runnable =
+ new ConfigureWPADComplete(mPACMan, spec);
+ mPACMan->Dispatch(runnable.forget());
+ return NS_OK;
+ }
+
+ mPACMan->ProcessPendingQ();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+
+ bool mCancel;
+ nsresult mCancelStatus;
+
+ bool mSetupPAC;
+ uint32_t mExtraHeapSize;
+ nsCString mSetupPACData;
+ nsCString mSetupPACURI;
+ bool mConfigureWPAD;
+ bool mShutdown;
+};
+
+//-----------------------------------------------------------------------------
+
+PendingPACQuery::PendingPACQuery(nsPACMan* pacMan, nsIURI* uri,
+ nsPACManCallback* callback, uint32_t flags,
+ bool mainThreadResponse)
+ : Runnable("net::PendingPACQuery"),
+ mPort(0),
+ mFlags(flags),
+ mPACMan(pacMan),
+ mCallback(callback),
+ mOnMainThreadOnly(mainThreadResponse) {
+ uri->GetAsciiSpec(mSpec);
+ uri->GetAsciiHost(mHost);
+ uri->GetScheme(mScheme);
+ uri->GetPort(&mPort);
+}
+
+void PendingPACQuery::Complete(nsresult status, const nsACString& pacString) {
+ if (!mCallback) return;
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status);
+ runnable->SetPACString(pacString);
+ if (mOnMainThreadOnly) {
+ mPACMan->Dispatch(runnable.forget());
+ } else {
+ runnable->Run();
+ }
+}
+
+void PendingPACQuery::UseAlternatePACFile(const nsACString& pacURL) {
+ if (!mCallback) return;
+
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK);
+ runnable->SetPACURL(pacURL);
+ if (mOnMainThreadOnly) {
+ mPACMan->Dispatch(runnable.forget());
+ } else {
+ runnable->Run();
+ }
+}
+
+NS_IMETHODIMP
+PendingPACQuery::Run() {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ mPACMan->PostQuery(this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+static bool sThreadLocalSetup = false;
+static uint32_t sThreadLocalIndex = 0xdeadbeef; // out of range
+
+static const char* kPACIncludePath =
+ "network.proxy.autoconfig_url.include_path";
+
+nsPACMan::nsPACMan(nsISerialEventTarget* mainThreadEventTarget)
+ : NeckoTargetHolder(mainThreadEventTarget),
+ mLoader("nsPACMan::mLoader"),
+ mLoadPending(false),
+ mShutdown(false),
+ mLoadFailureCount(0),
+ mInProgress(false),
+ mAutoDetect(false),
+ mWPADOverDHCPEnabled(false),
+ mProxyConfigType(0) {
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread");
+ mIncludePath = Preferences::GetBool(kPACIncludePath, false);
+ if (StaticPrefs::network_proxy_parse_pac_on_socket_process() &&
+ gIOService->SocketProcessReady()) {
+ mPAC = MakeUnique<RemoteProxyAutoConfig>();
+ } else {
+ mPAC = MakeUnique<ProxyAutoConfig>();
+ if (!sThreadLocalSetup) {
+ sThreadLocalSetup = true;
+ PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
+ }
+ mPAC->SetThreadLocalIndex(sThreadLocalIndex);
+ }
+}
+
+nsPACMan::~nsPACMan() {
+ MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor.");
+
+ if (mPACThread) {
+ if (NS_IsMainThread()) {
+ mPACThread->Shutdown();
+ mPACThread = nullptr;
+ } else {
+ RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread);
+ Dispatch(runnable.forget());
+ }
+ }
+
+#ifdef DEBUG
+ {
+ auto loader = mLoader.Lock();
+ NS_ASSERTION(loader.ref() == nullptr, "pac man not shutdown properly");
+ }
+#endif
+
+ NS_ASSERTION(mPendingQ.isEmpty(), "pac man not shutdown properly");
+}
+
+void nsPACMan::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be shutdown on main thread");
+ if (mShutdown) {
+ return;
+ }
+
+ CancelExistingLoad();
+
+ if (mPACThread) {
+ PostCancelPendingQ(NS_ERROR_ABORT, /*aShutdown =*/true);
+
+ // Shutdown is initiated from an observer. We don't want to block the
+ // observer service on thread shutdown so we post a shutdown runnable that
+ // will run after we return instead.
+ RefPtr<WaitForThreadShutdown> runnable = new WaitForThreadShutdown(this);
+ Dispatch(runnable.forget());
+ }
+
+ mShutdown = true;
+}
+
+nsresult nsPACMan::DispatchToPAC(already_AddRefed<nsIRunnable> aEvent,
+ bool aSync) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsCOMPtr<nsIRunnable> e(aEvent);
+
+ if (mShutdown) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Lazily create the PAC thread. This method is main-thread only so we don't
+ // have to worry about threading issues here.
+ if (!mPACThread) {
+ MOZ_TRY(NS_NewNamedThread("ProxyResolution", getter_AddRefs(mPACThread)));
+ nsresult rv = mPAC->Init(mPACThread);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (aSync) {
+ return NS_DispatchAndSpinEventLoopUntilComplete(
+ "nsPACMan::DispatchToPAC"_ns, mPACThread, e.forget());
+ } else {
+ return mPACThread->Dispatch(e.forget());
+ }
+}
+
+nsresult nsPACMan::AsyncGetProxyForURI(nsIURI* uri, nsPACManCallback* callback,
+ uint32_t flags,
+ bool mainThreadResponse) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mShutdown) return NS_ERROR_NOT_AVAILABLE;
+
+ // Maybe Reload PAC
+ if (!mPACURISpec.IsEmpty() && !mScheduledReload.IsNull() &&
+ TimeStamp::Now() > mScheduledReload) {
+ LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n"));
+
+ LoadPACFromURI(mAutoDetect ? ""_ns : mPACURISpec, false);
+ }
+
+ RefPtr<PendingPACQuery> query =
+ new PendingPACQuery(this, uri, callback, flags, mainThreadResponse);
+
+ if (IsPACURI(uri)) {
+ // deal with this directly instead of queueing it
+ query->Complete(NS_OK, ""_ns);
+ return NS_OK;
+ }
+
+ return DispatchToPAC(query.forget());
+}
+
+nsresult nsPACMan::PostQuery(PendingPACQuery* query) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+
+ if (mShutdown) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns);
+ return NS_OK;
+ }
+
+ // add a reference to the query while it is in the pending list
+ RefPtr<PendingPACQuery> addref(query);
+ mPendingQ.insertBack(addref.forget().take());
+ ProcessPendingQ();
+ return NS_OK;
+}
+
+nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec) {
+ return LoadPACFromURI(aSpec, true);
+}
+
+nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec,
+ bool aResetLoadFailureCount) {
+ NS_ENSURE_STATE(!mShutdown);
+
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
+ NS_ENSURE_STATE(loader);
+
+ LOG(("nsPACMan::LoadPACFromURI aSpec: %s, aResetLoadFailureCount: %s\n",
+ aSpec.BeginReading(), aResetLoadFailureCount ? "true" : "false"));
+
+ CancelExistingLoad();
+
+ {
+ auto locked = mLoader.Lock();
+ locked.ref() = loader.forget();
+ }
+ mPACURIRedirectSpec.Truncate();
+ mNormalPACURISpec.Truncate(); // set at load time
+ if (aResetLoadFailureCount) {
+ mLoadFailureCount = 0;
+ }
+ mAutoDetect = aSpec.IsEmpty();
+ mPACURISpec.Assign(aSpec);
+
+ // reset to Null
+ mScheduledReload = TimeStamp();
+
+ // if we're on the main thread here so we can get hold of prefs,
+ // we check that we have WPAD preffed on if we're auto-detecting
+ if (mAutoDetect && NS_IsMainThread()) {
+ nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mProxyConfigType != nsIProtocolProxyService::PROXYCONFIG_WPAD) {
+ LOG(
+ ("LoadPACFromURI - Aborting WPAD autodetection because the pref "
+ "doesn't match anymore"));
+ return NS_BINDING_ABORTED;
+ }
+ }
+ // Since we might get called from nsProtocolProxyService::Init, we need to
+ // post an event back to the main thread before we try to use the IO service.
+ //
+ // But, we need to flag ourselves as loading, so that we queue up any PAC
+ // queries the enter between now and when we actually load the PAC file.
+
+ if (!mLoadPending) {
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
+ "nsPACMan::StartLoading", this, &nsPACMan::StartLoading);
+ nsresult rv =
+ NS_IsMainThread()
+ ? Dispatch(runnable.forget())
+ : GetCurrentSerialEventTarget()->Dispatch(runnable.forget());
+ if (NS_FAILED(rv)) return rv;
+ mLoadPending = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsPACMan::GetPACFromDHCP(nsACString& aSpec) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ if (!mDHCPClient) {
+ LOG(
+ ("nsPACMan::GetPACFromDHCP DHCP option %d query failed because there "
+ "is no DHCP client available\n",
+ MOZ_DHCP_WPAD_OPTION));
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult rv;
+ rv = mDHCPClient->GetOption(MOZ_DHCP_WPAD_OPTION, aSpec);
+ if (NS_FAILED(rv)) {
+ LOG((
+ "nsPACMan::GetPACFromDHCP DHCP option %d query failed with result %d\n",
+ MOZ_DHCP_WPAD_OPTION, (uint32_t)rv));
+ } else {
+ LOG(
+ ("nsPACMan::GetPACFromDHCP DHCP option %d query succeeded, finding PAC "
+ "URL %s\n",
+ MOZ_DHCP_WPAD_OPTION, aSpec.BeginReading()));
+ }
+ return rv;
+}
+
+nsresult nsPACMan::ConfigureWPAD(nsACString& aSpec) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+
+ if (mProxyConfigType != nsIProtocolProxyService::PROXYCONFIG_WPAD) {
+ LOG(
+ ("ConfigureWPAD - Aborting WPAD autodetection because the pref "
+ "doesn't match anymore"));
+ return NS_BINDING_ABORTED;
+ }
+
+ aSpec.Truncate();
+ if (mWPADOverDHCPEnabled) {
+ GetPACFromDHCP(aSpec);
+ }
+
+ if (aSpec.IsEmpty()) {
+ // We diverge from the WPAD spec here in that we don't walk the
+ // hosts's FQDN, stripping components until we hit a TLD. Doing so
+ // is dangerous in the face of an incomplete list of TLDs, and TLDs
+ // get added over time. We could consider doing only a single
+ // substitution of the first component, if that proves to help
+ // compatibility.
+ aSpec.AssignLiteral(MOZ_WPAD_URL);
+ }
+ return NS_OK;
+}
+
+void nsPACMan::AssignPACURISpec(const nsACString& aSpec) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mPACURISpec.Assign(aSpec);
+}
+
+void nsPACMan::StartLoading() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mLoadPending = false;
+
+ {
+ // CancelExistingLoad was called...
+ nsCOMPtr<nsIStreamLoader> loader;
+ {
+ auto locked = mLoader.Lock();
+ loader = locked.ref();
+ }
+ if (!loader) {
+ PostCancelPendingQ(NS_ERROR_ABORT);
+ return;
+ }
+ }
+
+ if (mAutoDetect) {
+ nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Could not retrieve Network Proxy Type pref when auto-detecting "
+ "proxy. Halting.");
+ return;
+ }
+ RefPtr<ExecutePACThreadAction> wpadConfigurer =
+ new ExecutePACThreadAction(this);
+ wpadConfigurer->ConfigureWPAD();
+ DispatchToPAC(wpadConfigurer.forget());
+ } else {
+ ContinueLoadingAfterPACUriKnown();
+ }
+}
+
+void nsPACMan::ContinueLoadingAfterPACUriKnown() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ {
+ auto locked = mLoader.Lock();
+ loader = locked.ref();
+ }
+
+ // CancelExistingLoad was called...
+ if (!loader) {
+ PostCancelPendingQ(NS_ERROR_ABORT);
+ return;
+ }
+ if (NS_SUCCEEDED(loader->Init(this, nullptr))) {
+ // Always hit the origin server when loading PAC.
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIURI> pacURI;
+ NS_NewURI(getter_AddRefs(pacURI), mPACURISpec);
+
+ // NOTE: This results in GetProxyForURI being called
+ if (pacURI) {
+ nsresult rv = pacURI->GetSpec(mNormalPACURISpec);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ NS_NewChannel(getter_AddRefs(channel), pacURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ios);
+ } else {
+ LOG(("nsPACMan::StartLoading Failed pacspec uri conversion %s\n",
+ mPACURISpec.get()));
+ }
+
+ if (channel) {
+ // allow deprecated HTTP request from SystemPrincipal
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetAllowDeprecatedSystemRequests(true);
+ loadInfo->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT);
+
+ channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
+ channel->SetNotificationCallbacks(this);
+ if (NS_SUCCEEDED(channel->AsyncOpen(loader))) return;
+ }
+ }
+ }
+
+ CancelExistingLoad();
+ PostCancelPendingQ(NS_ERROR_UNEXPECTED);
+}
+
+void nsPACMan::OnLoadFailure() {
+ int32_t minInterval = 5; // 5 seconds
+ int32_t maxInterval = 300; // 5 minutes
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
+ &minInterval);
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
+ &maxInterval);
+ }
+
+ int32_t interval = minInterval << mLoadFailureCount++; // seconds
+ if (!interval || interval > maxInterval) interval = maxInterval;
+
+ mScheduledReload = TimeStamp::Now() + TimeDuration::FromSeconds(interval);
+
+ LOG(("OnLoadFailure: retry in %d seconds (%d fails)\n", interval,
+ (uint32_t)mLoadFailureCount));
+
+ // while we wait for the retry queued members should try direct
+ // even if that means fast failure.
+ PostCancelPendingQ(NS_ERROR_NOT_AVAILABLE);
+}
+
+void nsPACMan::CancelExistingLoad() {
+ nsCOMPtr<nsIStreamLoader> loader;
+ {
+ auto locked = mLoader.Lock();
+ loader.swap(*locked);
+ }
+ if (loader) {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ if (request) {
+ request->Cancel(NS_ERROR_ABORT);
+ }
+ }
+}
+
+void nsPACMan::PostProcessPendingQ() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
+ DispatchToPAC(pending.forget());
+}
+
+void nsPACMan::PostCancelPendingQ(nsresult status, bool aShutdown) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
+ pending->CancelQueue(status, aShutdown);
+ DispatchToPAC(pending.forget());
+}
+
+void nsPACMan::CancelPendingQ(nsresult status, bool aShutdown) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ RefPtr<PendingPACQuery> query;
+
+ while (!mPendingQ.isEmpty()) {
+ query = dont_AddRef(mPendingQ.popLast());
+ query->Complete(status, ""_ns);
+ }
+
+ if (aShutdown) {
+ mPAC->Shutdown();
+ }
+}
+
+void nsPACMan::ProcessPendingQ() {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ while (ProcessPending()) {
+ ;
+ }
+
+ if (mShutdown) {
+ mPAC->Shutdown();
+ } else {
+ // do GC while the thread has nothing pending
+ mPAC->GC();
+ }
+}
+
+// returns true if progress was made by shortening the queue
+bool nsPACMan::ProcessPending() {
+ if (mPendingQ.isEmpty()) return false;
+
+ // queue during normal load, but if we are retrying a failed load then
+ // fast fail the queries
+ if (mInProgress || (IsLoading() && !mLoadFailureCount)) return false;
+
+ RefPtr<PendingPACQuery> query(dont_AddRef(mPendingQ.popFirst()));
+
+ // Having |mLoadFailureCount > 0| means we haven't had a sucessful PAC load
+ // yet. We should use DIRECT instead.
+ if (mShutdown || IsLoading() || mLoadFailureCount > 0) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns);
+ return true;
+ }
+
+ nsAutoCString pacString;
+ bool completed = false;
+ mInProgress = true;
+ nsAutoCString PACURI;
+
+ // first we need to consider the system proxy changing the pac url
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty() && !PACURI.Equals(mPACURISpec)) {
+ query->UseAlternatePACFile(PACURI);
+ LOG(("Use PAC from system settings: %s\n", PACURI.get()));
+ completed = true;
+ }
+
+ // now try the system proxy settings for this particular url if
+ // PAC was not specified
+ if (!completed && mSystemProxySettings && PACURI.IsEmpty() &&
+ NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
+ query->mSpec, query->mScheme, query->mHost, query->mPort,
+ pacString))) {
+ if (query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY &&
+ query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY) {
+ if (StringBeginsWith(pacString, nsDependentCString(kProxyType_DIRECT),
+ nsCaseInsensitiveUTF8StringComparator)) {
+ // DIRECT indicates that system proxy settings are not configured to use
+ // SOCKS proxy. Try https proxy as a secondary preferrable proxy. This
+ // is mainly for websocket whose precedence is SOCKS > HTTPS > DIRECT.
+ NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
+ query->mSpec, nsDependentCString(kProxyType_HTTPS), query->mHost,
+ query->mPort, pacString));
+ }
+ }
+ LOG(("Use proxy from system settings: %s\n", pacString.get()));
+ query->Complete(NS_OK, pacString);
+ completed = true;
+ }
+
+ // the systemproxysettings didn't complete the resolution. try via PAC
+ if (!completed) {
+ auto callback = [query(query)](nsresult aStatus,
+ const nsACString& aResult) {
+ LOG(("Use proxy from PAC: %s\n", PromiseFlatCString(aResult).get()));
+ query->Complete(aStatus, aResult);
+ };
+ mPAC->GetProxyForURIWithCallback(query->mSpec, query->mHost,
+ std::move(callback));
+ }
+
+ mInProgress = false;
+ return true;
+}
+
+NS_IMPL_ISUPPORTS(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+NS_IMETHODIMP
+nsPACMan::OnStreamComplete(nsIStreamLoader* loader, nsISupports* context,
+ nsresult status, uint32_t dataLen,
+ const uint8_t* data) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ bool loadSucceeded = NS_SUCCEEDED(status) && HttpRequestSucceeded(loader);
+ {
+ auto locked = mLoader.Lock();
+ if (locked.ref() != loader) {
+ // If this happens, then it means that LoadPACFromURI was called more
+ // than once before the initial call completed. In this case, status
+ // should be NS_ERROR_ABORT, and if so, then we know that we can and
+ // should delay any processing.
+ LOG(("OnStreamComplete: called more than once\n"));
+ if (status == NS_ERROR_ABORT) {
+ return NS_OK;
+ }
+ } else if (!loadSucceeded) {
+ // We have to clear the loader to indicate that we are not loading PAC
+ // currently.
+ // Note that we can only clear the loader when |loader| and |mLoader| are
+ // the same one.
+ locked.ref() = nullptr;
+ }
+ }
+
+ LOG(("OnStreamComplete: entry\n"));
+
+ if (loadSucceeded) {
+ // Get the URI spec used to load this PAC script.
+ nsAutoCString pacURI;
+ {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) uri->GetAsciiSpec(pacURI);
+ }
+ }
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+ MOZ_ASSERT(pps);
+ if (pps) {
+ pps->NotifyProxyConfigChangedInternal();
+ }
+
+ // We succeeded in loading the pac file using a bunch of interfaces that are
+ // main thread only. Unfortunately, we have to initialize the instance of
+ // the PAC evaluator (NS_PROXYAUTOCONFIG_CONTRACTID) on the PAC thread,
+ // because that's where it will be used.
+ RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
+ pending->SetupPAC(reinterpret_cast<const char*>(data), dataLen, pacURI,
+ GetExtraJSContextHeapSize());
+ DispatchToPAC(pending.forget());
+
+ LOG(("OnStreamComplete: process the PAC contents\n"));
+
+ // Even if the PAC file could not be parsed, we did succeed in loading the
+ // data for it.
+ mLoadFailureCount = 0;
+ } else {
+ // We were unable to load the PAC file (presumably because of a network
+ // failure). Try again a little later.
+ LOG(("OnStreamComplete: unable to load PAC, retry later\n"));
+ OnLoadFailure();
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ PostProcessPendingQ();
+ } else {
+ PostCancelPendingQ(status);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPACMan::GetInterface(const nsIID& iid, void** result) {
+ // In case loading the PAC file requires authentication.
+ if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ nsCOMPtr<nsIPromptFactory> promptFac =
+ do_GetService("@mozilla.org/prompter;1");
+ NS_ENSURE_TRUE(promptFac, NS_ERROR_NO_INTERFACE);
+ nsresult rv =
+ promptFac->GetPrompt(nullptr, iid, reinterpret_cast<void**>(result));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ // In case loading the PAC file results in a redirect.
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsPACMan::AsyncOnChannelRedirect(nsIChannel* oldChannel, nsIChannel* newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> pacURI;
+ if (NS_FAILED((rv = newChannel->GetURI(getter_AddRefs(pacURI))))) return rv;
+
+ rv = pacURI->GetSpec(mPACURIRedirectSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("nsPACMan redirect from original %s to redirected %s\n",
+ mPACURISpec.get(), mPACURIRedirectSpec.get()));
+
+ // do not update mPACURISpec - that needs to stay as the
+ // configured URI so that we can determine when the config changes.
+ // However do track the most recent URI in the redirect change
+ // as mPACURIRedirectSpec so that URI can be allowed to bypass
+ // the proxy and actually fetch the pac file.
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult nsPACMan::Init(nsISystemProxySettings* systemProxySettings) {
+ mSystemProxySettings = systemProxySettings;
+ mDHCPClient = do_GetService(NS_DHCPCLIENT_CONTRACTID);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPACMan.h b/netwerk/base/nsPACMan.h
new file mode 100644
index 0000000000..dd7d6edb8c
--- /dev/null
+++ b/netwerk/base/nsPACMan.h
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPACMan_h__
+#define nsPACMan_h__
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamLoader.h"
+#include "nsThreadUtils.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "ProxyAutoConfig.h"
+
+class nsISystemProxySettings;
+class nsIDHCPClient;
+class nsIThread;
+
+namespace mozilla {
+namespace net {
+
+class nsPACMan;
+class WaitForThreadShutdown;
+
+/**
+ * This class defines a callback interface used by AsyncGetProxyForURI.
+ */
+class NS_NO_VTABLE nsPACManCallback : public nsISupports {
+ public:
+ /**
+ * This method is invoked on the same thread that called AsyncGetProxyForURI.
+ *
+ * @param status
+ * This parameter indicates whether or not the PAC query succeeded.
+ * @param pacString
+ * This parameter holds the value of the PAC string. It is empty when
+ * status is a failure code.
+ * @param newPACURL
+ * This parameter holds the URL of a new PAC file that should be loaded
+ * before the query is evaluated again. At least one of pacString and
+ * newPACURL should be 0 length.
+ */
+ virtual void OnQueryComplete(nsresult status, const nsACString& pacString,
+ const nsACString& newPACURL) = 0;
+};
+
+class PendingPACQuery final : public Runnable,
+ public LinkedListElement<PendingPACQuery> {
+ public:
+ PendingPACQuery(nsPACMan* pacMan, nsIURI* uri, nsPACManCallback* callback,
+ uint32_t flags, bool mainThreadResponse);
+
+ // can be called from either thread
+ void Complete(nsresult status, const nsACString& pacString);
+ void UseAlternatePACFile(const nsACString& pacURL);
+
+ nsCString mSpec;
+ nsCString mScheme;
+ nsCString mHost;
+ int32_t mPort;
+ uint32_t mFlags;
+
+ NS_IMETHOD Run(void) override; /* Runnable */
+
+ private:
+ nsPACMan* mPACMan; // weak reference
+
+ private:
+ RefPtr<nsPACManCallback> mCallback;
+ bool mOnMainThreadOnly;
+};
+
+/**
+ * This class provides an abstraction layer above the PAC thread. The methods
+ * defined on this class are intended to be called on the main thread only.
+ */
+
+class nsPACMan final : public nsIStreamLoaderObserver,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public NeckoTargetHolder {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit nsPACMan(nsISerialEventTarget* mainThreadEventTarget);
+
+ /**
+ * This method may be called to shutdown the PAC manager. Any async queries
+ * that have not yet completed will either finish normally or be canceled by
+ * the time this method returns.
+ */
+ void Shutdown();
+
+ /**
+ * This method queries a PAC result asynchronously. The callback runs on the
+ * calling thread. If the PAC file has not yet been loaded, then this method
+ * will queue up the request, and complete it once the PAC file has been
+ * loaded.
+ *
+ * @param uri
+ * The URI to query.
+ * @param callback
+ * The callback to run once the PAC result is available.
+ * @param flags
+ * A bit-wise combination of the RESOLVE_ flags defined above. Pass
+ * 0 to specify the default behavior.
+ * @param mustCallbackOnMainThread
+ * If set to false the callback can be made from the PAC thread
+ */
+ nsresult AsyncGetProxyForURI(nsIURI* uri, nsPACManCallback* callback,
+ uint32_t flags, bool mainThreadResponse);
+
+ /**
+ * This method may be called to reload the PAC file. While we are loading
+ * the PAC file, any asynchronous PAC queries will be queued up to be
+ * processed once the PAC file finishes loading.
+ *
+ * @param aSpec
+ * The non normalized uri spec of this URI used for comparison with
+ * system proxy settings to determine if the PAC uri has changed.
+ */
+ nsresult LoadPACFromURI(const nsACString& aSpec);
+
+ /**
+ * Returns true if we are currently loading the PAC file.
+ */
+ bool IsLoading() {
+ auto loader = mLoader.Lock();
+ return loader.ref() != nullptr;
+ }
+
+ /**
+ * Returns true if the given URI matches the URI of our PAC file or the
+ * URI it has been redirected to. In the case of a chain of redirections
+ * only the current one being followed and the original are considered
+ * becuase this information is used, respectively, to determine if we
+ * should bypass the proxy (to fetch the pac file) or if the pac
+ * configuration has changed (and we should reload the pac file)
+ */
+ bool IsPACURI(const nsACString& spec) {
+ return mPACURISpec.Equals(spec) || mPACURIRedirectSpec.Equals(spec) ||
+ mNormalPACURISpec.Equals(spec);
+ }
+
+ bool IsPACURI(nsIURI* uri) {
+ if (mPACURISpec.IsEmpty() && mPACURIRedirectSpec.IsEmpty()) {
+ return false;
+ }
+
+ nsAutoCString tmp;
+ nsresult rv = uri->GetSpec(tmp);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return IsPACURI(tmp);
+ }
+
+ bool IsUsingWPAD() { return mAutoDetect; }
+
+ nsresult Init(nsISystemProxySettings*);
+ static nsPACMan* sInstance;
+
+ // PAC thread operations only
+ void ProcessPendingQ();
+ void CancelPendingQ(nsresult, bool aShutdown);
+
+ void SetWPADOverDHCPEnabled(bool aValue) { mWPADOverDHCPEnabled = aValue; }
+
+ private:
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ friend class PendingPACQuery;
+ friend class PACLoadComplete;
+ friend class ConfigureWPADComplete;
+ friend class ExecutePACThreadAction;
+ friend class WaitForThreadShutdown;
+ friend class TestPACMan;
+
+ ~nsPACMan();
+
+ /**
+ * Cancel any existing load if any.
+ */
+ void CancelExistingLoad();
+
+ /**
+ * Start loading the PAC file.
+ */
+ void StartLoading();
+
+ /**
+ * Continue loading the PAC file.
+ */
+ void ContinueLoadingAfterPACUriKnown();
+
+ /**
+ * This method may be called to reload the PAC file. While we are loading
+ * the PAC file, any asynchronous PAC queries will be queued up to be
+ * processed once the PAC file finishes loading.
+ *
+ * @param aSpec
+ * The non normalized uri spec of this URI used for comparison with
+ * system proxy settings to determine if the PAC uri has changed.
+ * @param aResetLoadFailureCount
+ * A flag saying whether the exponential back-off for attempting to
+ * reload the PAC should be reset.
+ */
+ nsresult LoadPACFromURI(const nsACString& aSpec, bool aResetLoadFailureCount);
+
+ /**
+ * Reload the PAC file if there is reason to.
+ */
+ void MaybeReloadPAC();
+
+ /**
+ * Called when we fail to load the PAC file.
+ */
+ void OnLoadFailure();
+
+ /**
+ * PostQuery() only runs on the PAC thread and it is used to
+ * place a pendingPACQuery into the queue and potentially
+ * execute the queue if it was otherwise empty
+ */
+ nsresult PostQuery(PendingPACQuery* query);
+
+ // Having found the PAC URI on the PAC thread, copy it to a string which
+ // can be altered on the main thread.
+ void AssignPACURISpec(const nsACString& aSpec);
+
+ // PAC thread operations only
+ void PostProcessPendingQ();
+ void PostCancelPendingQ(nsresult, bool aShutdown = false);
+ bool ProcessPending();
+ nsresult GetPACFromDHCP(nsACString& aSpec);
+ nsresult ConfigureWPAD(nsACString& aSpec);
+
+ private:
+ /**
+ * Dispatches a runnable to the PAC processing thread. Handles lazy
+ * instantiation of the thread.
+ *
+ * @param aEvent The event to disptach.
+ * @param aSync Whether or not this should be synchronous dispatch.
+ */
+ nsresult DispatchToPAC(already_AddRefed<nsIRunnable> aEvent,
+ bool aSync = false);
+
+ UniquePtr<ProxyAutoConfigBase> mPAC;
+ nsCOMPtr<nsIThread> mPACThread;
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+ nsCOMPtr<nsIDHCPClient> mDHCPClient;
+
+ LinkedList<PendingPACQuery> mPendingQ; /* pac thread only */
+
+ // These specs are not nsIURI so that they can be used off the main thread.
+ // The non-normalized versions are directly from the configuration, the
+ // normalized version has been extracted from an nsIURI
+ nsCString mPACURISpec;
+ nsCString mPACURIRedirectSpec;
+ nsCString mNormalPACURISpec;
+
+ DataMutex<nsCOMPtr<nsIStreamLoader>> mLoader;
+ bool mLoadPending;
+ Atomic<bool, Relaxed> mShutdown;
+ TimeStamp mScheduledReload;
+ Atomic<uint32_t, Relaxed> mLoadFailureCount;
+
+ bool mInProgress;
+ bool mIncludePath;
+ bool mAutoDetect;
+ bool mWPADOverDHCPEnabled;
+ int32_t mProxyConfigType;
+};
+
+extern LazyLogModule gProxyLog;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsPACMan_h__
diff --git a/netwerk/base/nsPISocketTransportService.idl b/netwerk/base/nsPISocketTransportService.idl
new file mode 100644
index 0000000000..e7c8ac5a60
--- /dev/null
+++ b/netwerk/base/nsPISocketTransportService.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISocketTransportService.idl"
+
+/**
+ * This is a private interface used by the internals of the networking library.
+ * It will never be frozen. Do not use it in external code.
+ */
+[builtinclass, scriptable, uuid(18f73bf1-b35b-4b7b-aa9a-11bcbdbc389c)]
+interface nsPISocketTransportService : nsIRoutedSocketTransportService
+{
+ /**
+ * init/shutdown routines.
+ */
+ void init();
+ void shutdown(in bool aXpcomShutdown);
+
+ /**
+ * controls the TCP sender window clamp
+ */
+ readonly attribute long sendBufferSize;
+
+ /**
+ * Controls whether the socket transport service is offline.
+ * Setting it offline will cause non-local socket detachment.
+ */
+ attribute boolean offline;
+
+ /**
+ * Controls the default timeout (in seconds) for sending keepalive probes.
+ */
+ readonly attribute long keepaliveIdleTime;
+
+ /**
+ * Controls the default interval (in seconds) between retrying keepalive probes.
+ */
+ readonly attribute long keepaliveRetryInterval;
+
+ /**
+ * Controls the default retransmission count for keepalive probes.
+ */
+ readonly attribute long keepaliveProbeCount;
+};
+
+%{C++
+/*
+ * I/O activity observer topic. Sends out information about the
+ * amount of data we're sending/receiving via sockets and disk files.
+ *
+ * Activated via the "io.activity.enabled" preference.
+ */
+#define NS_IO_ACTIVITY "io-activity"
+
+
+%}
diff --git a/netwerk/base/nsPreloadedStream.cpp b/netwerk/base/nsPreloadedStream.cpp
new file mode 100644
index 0000000000..cd7a7e79d2
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.cpp
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPreloadedStream.h"
+#include "nsIRunnable.h"
+
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsPreloadedStream, nsIInputStream, nsIAsyncInputStream,
+ nsIInputStreamCallback)
+
+nsPreloadedStream::nsPreloadedStream(nsIAsyncInputStream* aStream,
+ const char* data, uint32_t datalen)
+ : mStream(aStream),
+ mOffset(0),
+ mLen(datalen),
+ mCallback("nsPreloadedStream") {
+ mBuf = (char*)moz_xmalloc(datalen);
+ memcpy(mBuf, data, datalen);
+}
+
+nsPreloadedStream::~nsPreloadedStream() { free(mBuf); }
+
+NS_IMETHODIMP
+nsPreloadedStream::Close() {
+ mLen = 0;
+ return mStream->Close();
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Available(uint64_t* _retval) {
+ uint64_t avail = 0;
+
+ nsresult rv = mStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ *_retval = avail + mLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::StreamStatus() { return mStream->StreamStatus(); }
+
+NS_IMETHODIMP
+nsPreloadedStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ if (!mLen) return mStream->Read(aBuf, aCount, _retval);
+
+ uint32_t toRead = std::min(mLen, aCount);
+ memcpy(aBuf, mBuf + mOffset, toRead);
+ mOffset += toRead;
+ mLen -= toRead;
+ *_retval = toRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* result) {
+ if (!mLen) return mStream->ReadSegments(aWriter, aClosure, aCount, result);
+
+ *result = 0;
+ while (mLen > 0 && aCount > 0) {
+ uint32_t toRead = std::min(mLen, aCount);
+ uint32_t didRead = 0;
+ nsresult rv;
+
+ rv = aWriter(this, aClosure, mBuf + mOffset, *result, toRead, &didRead);
+
+ if (NS_FAILED(rv)) return NS_OK;
+
+ *result += didRead;
+ mOffset += didRead;
+ mLen -= didRead;
+ aCount -= didRead;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::IsNonBlocking(bool* _retval) {
+ return mStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::CloseWithStatus(nsresult aStatus) {
+ mLen = 0;
+ return mStream->CloseWithStatus(aStatus);
+}
+
+class RunOnThread : public Runnable {
+ public:
+ RunOnThread(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback)
+ : Runnable("net::RunOnThread"), mStream(aStream), mCallback(aCallback) {}
+
+ virtual ~RunOnThread() = default;
+
+ NS_IMETHOD Run() override {
+ mCallback->OnInputStreamReady(mStream);
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+};
+
+NS_IMETHODIMP
+nsPreloadedStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ if (!mLen) {
+ {
+ auto lock = mCallback.Lock();
+ *lock = aCallback;
+ }
+ return mStream->AsyncWait(aCallback ? this : nullptr, aFlags,
+ aRequestedCount, aEventTarget);
+ }
+
+ if (!aCallback) return NS_OK;
+
+ if (!aEventTarget) return aCallback->OnInputStreamReady(this);
+
+ nsCOMPtr<nsIRunnable> event = new RunOnThread(this, aCallback);
+ return aEventTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ auto lock = mCallback.Lock();
+ callback = lock->forget();
+ }
+ if (callback) {
+ return callback->OnInputStreamReady(this);
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPreloadedStream.h b/netwerk/base/nsPreloadedStream.h
new file mode 100644
index 0000000000..5c550b43e7
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This class allows you to prefix an existing nsIAsyncInputStream
+ * with a preloaded block of data known at construction time by wrapping the
+ * two data sources into a new nsIAsyncInputStream. Readers of the new
+ * stream initially see the preloaded data and when that has been exhausted
+ * they automatically read from the wrapped stream.
+ *
+ * It is used by nsHttpConnection when it has over buffered while reading from
+ * the HTTP input socket and accidentally consumed data that belongs to
+ * a different protocol via the HTTP Upgrade mechanism. That over-buffered
+ * data is preloaded together with the input socket to form the new input socket
+ * given to the new protocol handler.
+ */
+
+#ifndef nsPreloadedStream_h__
+#define nsPreloadedStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DataMutex.h"
+
+namespace mozilla {
+namespace net {
+
+class nsPreloadedStream final : public nsIAsyncInputStream,
+ public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ nsPreloadedStream(nsIAsyncInputStream* aStream, const char* data,
+ uint32_t datalen);
+
+ private:
+ ~nsPreloadedStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+
+ char* mBuf;
+ uint32_t mOffset;
+ uint32_t mLen;
+
+ DataMutex<nsCOMPtr<nsIInputStreamCallback>> mCallback;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp
new file mode 100644
index 0000000000..9f721f94e8
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -0,0 +1,2459 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+
+#include "nsProtocolProxyService.h"
+#include "nsProxyInfo.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIOService.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannel.h"
+#include "nsICancelable.h"
+#include "nsDNSService2.h"
+#include "nsPIDNSService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsQueryObject.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "prnetdb.h"
+#include "nsPACMan.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+#include "nsISystemProxySettings.h"
+#include "nsINetworkLinkService.h"
+#include "nsIHttpChannelInternal.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Unused.h"
+
+//----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+extern const char kProxyType_HTTP[];
+extern const char kProxyType_HTTPS[];
+extern const char kProxyType_SOCKS[];
+extern const char kProxyType_SOCKS4[];
+extern const char kProxyType_SOCKS5[];
+extern const char kProxyType_DIRECT[];
+extern const char kProxyType_PROXY[];
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+
+//----------------------------------------------------------------------------
+
+#define PROXY_PREF_BRANCH "network.proxy"
+#define PROXY_PREF(x) PROXY_PREF_BRANCH "." x
+
+//----------------------------------------------------------------------------
+
+// This structure is intended to be allocated on the stack
+struct nsProtocolInfo {
+ nsAutoCString scheme;
+ uint32_t flags = 0;
+ int32_t defaultPort = 0;
+};
+
+//----------------------------------------------------------------------------
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+static nsresult GetProxyURI(nsIChannel* channel, nsIURI** aOut) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> proxyURI;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->GetProxyURI(getter_AddRefs(proxyURI));
+ }
+ if (!proxyURI) {
+ rv = channel->GetURI(getter_AddRefs(proxyURI));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ proxyURI.forget(aOut);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsProtocolProxyService::FilterLink::FilterLink(uint32_t p,
+ nsIProtocolProxyFilter* f)
+ : position(p), filter(f), channelFilter(nullptr) {
+ LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, filter=%p", this,
+ f));
+}
+nsProtocolProxyService::FilterLink::FilterLink(
+ uint32_t p, nsIProtocolProxyChannelFilter* cf)
+ : position(p), filter(nullptr), channelFilter(cf) {
+ LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, channel-filter=%p",
+ this, cf));
+}
+
+nsProtocolProxyService::FilterLink::~FilterLink() {
+ LOG(("nsProtocolProxyService::FilterLink::~FilterLink %p", this));
+}
+
+//-----------------------------------------------------------------------------
+
+// The nsPACManCallback portion of this implementation should be run
+// on the main thread - so call nsPACMan::AsyncGetProxyForURI() with
+// a true mainThreadResponse parameter.
+class nsAsyncResolveRequest final : public nsIRunnable,
+ public nsPACManCallback,
+ public nsICancelable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncResolveRequest(nsProtocolProxyService* pps, nsIChannel* channel,
+ uint32_t aResolveFlags,
+ nsIProtocolProxyCallback* callback)
+ : mResolveFlags(aResolveFlags),
+ mPPS(pps),
+ mXPComPPS(pps),
+ mChannel(channel),
+ mCallback(callback) {
+ NS_ASSERTION(mCallback, "null callback");
+ }
+
+ private:
+ ~nsAsyncResolveRequest() {
+ if (!NS_IsMainThread()) {
+ // these xpcom pointers might need to be proxied back to the
+ // main thread to delete safely, but if this request had its
+ // callbacks called normally they will all be null and this is a nop
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mChannel",
+ mChannel.forget());
+ }
+
+ if (mCallback) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mCallback",
+ mCallback.forget());
+ }
+
+ if (mProxyInfo) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mProxyInfo",
+ mProxyInfo.forget());
+ }
+
+ if (mXPComPPS) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mXPComPPS",
+ mXPComPPS.forget());
+ }
+ }
+ }
+
+ // Helper class to loop over all registered asynchronous filters.
+ // There is a cycle between nsAsyncResolveRequest and this class that
+ // is broken after the last filter has called back on this object.
+ class AsyncApplyFilters final : public nsIProxyProtocolFilterResult,
+ public nsIRunnable,
+ public nsICancelable {
+ // The reference counter is thread-safe, but the processing logic is
+ // considered single thread only. We want the counter be thread safe,
+ // since this class can be released on a background thread.
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROXYPROTOCOLFILTERRESULT
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSICANCELABLE
+
+ using Callback =
+ std::function<nsresult(nsAsyncResolveRequest*, nsIProxyInfo*, bool)>;
+
+ explicit AsyncApplyFilters(nsProtocolInfo& aInfo,
+ Callback const& aCallback);
+ // This method starts the processing or filters. If all of them
+ // answer synchronously (call back from within applyFilters) this method
+ // will return immediately and the returning result will carry return
+ // result of the callback given in constructor.
+ // This method is looping the registered filters (that have been copied
+ // locally) as long as an answer from a filter is obtained synchronously.
+ // Note that filters are processed serially to let them build a list
+ // of proxy info.
+ nsresult AsyncProcess(nsAsyncResolveRequest* aRequest);
+
+ private:
+ using FilterLink = nsProtocolProxyService::FilterLink;
+
+ virtual ~AsyncApplyFilters();
+ // Processes the next filter and loops until a filter is successfully
+ // called on or it has called back to us.
+ nsresult ProcessNextFilter();
+ // Called after the last filter has been processed (=called back or failed
+ // to be called on)
+ nsresult Finish();
+
+ nsProtocolInfo mInfo;
+ // This is nullified before we call back on the request or when
+ // Cancel() on this object has been called to break the cycle
+ // and signal to stop.
+ RefPtr<nsAsyncResolveRequest> mRequest;
+ Callback mCallback;
+ // A shallow snapshot of filters as they were registered at the moment
+ // we started to process filters for the given resolve request.
+ nsTArray<RefPtr<FilterLink>> mFiltersCopy;
+
+ nsTArray<RefPtr<FilterLink>>::index_type mNextFilterIndex;
+ // true when we are calling ProcessNextFilter() from inside AsyncProcess(),
+ // false otherwise.
+ bool mProcessingInLoop;
+ // true after a filter called back to us with a result, dropped to false
+ // just before we call a filter.
+ bool mFilterCalledBack;
+
+ // This keeps the initial value we pass to the first filter in line and also
+ // collects the result from each filter call.
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+
+ // The logic is written as non-thread safe, assert single-thread usage.
+ nsCOMPtr<nsISerialEventTarget> mProcessingThread;
+ };
+
+ void EnsureResolveFlagsMatch() {
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(mProxyInfo);
+ if (!pi || pi->ResolveFlags() == mResolveFlags) {
+ return;
+ }
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo =
+ pi->CloneProxyInfoWithNewResolveFlags(mResolveFlags);
+ mProxyInfo.swap(proxyInfo);
+ }
+
+ public:
+ nsresult ProcessLocally(nsProtocolInfo& info, nsIProxyInfo* pi,
+ bool isSyncOK) {
+ SetResult(NS_OK, pi);
+
+ auto consumeFiltersResult = [isSyncOK](nsAsyncResolveRequest* ctx,
+ nsIProxyInfo* pi,
+ bool aCalledAsync) -> nsresult {
+ ctx->SetResult(NS_OK, pi);
+ if (isSyncOK || aCalledAsync) {
+ ctx->Run();
+ return NS_OK;
+ }
+
+ return ctx->DispatchCallback();
+ };
+
+ mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult);
+ // may call consumeFiltersResult() directly
+ return mAsyncFilterApplier->AsyncProcess(this);
+ }
+
+ void SetResult(nsresult status, nsIProxyInfo* pi) {
+ mStatus = status;
+ mProxyInfo = pi;
+ }
+
+ NS_IMETHOD Run() override {
+ if (mCallback) DoCallback();
+ return NS_OK;
+ }
+
+ NS_IMETHOD Cancel(nsresult reason) override {
+ NS_ENSURE_ARG(NS_FAILED(reason));
+
+ if (mAsyncFilterApplier) {
+ mAsyncFilterApplier->Cancel(reason);
+ }
+
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback) return NS_OK;
+
+ SetResult(reason, nullptr);
+ return DispatchCallback();
+ }
+
+ nsresult DispatchCallback() {
+ if (mDispatched) { // Only need to dispatch once
+ return NS_OK;
+ }
+
+ nsresult rv = NS_DispatchToCurrentThread(this);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to dispatch callback event");
+ } else {
+ mDispatched = true;
+ return NS_OK;
+ }
+
+ mCallback = nullptr; // break possible reference cycle
+ return rv;
+ }
+
+ private:
+ // Called asynchronously, so we do not need to post another PLEvent
+ // before calling DoCallback.
+ void OnQueryComplete(nsresult status, const nsACString& pacString,
+ const nsACString& newPACURL) override {
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback) return;
+
+ // Provided we haven't been canceled...
+ if (mStatus == NS_OK) {
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ }
+
+ // In the cancelation case, we may still have another PLEvent in
+ // the queue that wants to call DoCallback. No need to wait for
+ // it, just run the callback now.
+ DoCallback();
+ }
+
+ void DoCallback() {
+ bool pacAvailable = true;
+ if (mStatus == NS_ERROR_NOT_AVAILABLE && !mProxyInfo) {
+ // If the PAC service is not avail (e.g. failed pac load
+ // or shutdown) then we will be going direct. Make that
+ // mapping now so that any filters are still applied.
+ mPACString = "DIRECT;"_ns;
+ mStatus = NS_OK;
+
+ LOG(("pac not available, use DIRECT\n"));
+ pacAvailable = false;
+ }
+
+ // Generate proxy info from the PAC string if appropriate
+ if (NS_SUCCEEDED(mStatus) && !mProxyInfo && !mPACString.IsEmpty()) {
+ mPPS->ProcessPACString(mPACString, mResolveFlags,
+ getter_AddRefs(mProxyInfo));
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // Now apply proxy filters
+ nsProtocolInfo info;
+ mStatus = mPPS->GetProtocolInfo(proxyURI, &info);
+
+ auto consumeFiltersResult = [pacAvailable](nsAsyncResolveRequest* self,
+ nsIProxyInfo* pi,
+ bool async) -> nsresult {
+ LOG(("DoCallback::consumeFiltersResult this=%p, pi=%p, async=%d", self,
+ pi, async));
+
+ self->mProxyInfo = pi;
+
+ if (pacAvailable) {
+ // if !pacAvailable, it was already logged above
+ LOG(("pac thread callback %s\n", self->mPACString.get()));
+ }
+
+ if (NS_SUCCEEDED(self->mStatus)) {
+ self->mPPS->MaybeDisableDNSPrefetch(self->mProxyInfo);
+ }
+
+ self->EnsureResolveFlagsMatch();
+ self->mCallback->OnProxyAvailable(self, self->mChannel,
+ self->mProxyInfo, self->mStatus);
+
+ return NS_OK;
+ };
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult);
+ // This may call consumeFiltersResult() directly.
+ mAsyncFilterApplier->AsyncProcess(this);
+ return;
+ }
+
+ consumeFiltersResult(this, nullptr, false);
+ } else if (NS_SUCCEEDED(mStatus) && !mPACURL.IsEmpty()) {
+ LOG(("pac thread callback indicates new pac file load\n"));
+
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // trigger load of new pac url
+ nsresult rv = mPPS->ConfigureFromPAC(mPACURL, false);
+ if (NS_SUCCEEDED(rv)) {
+ // now that the load is triggered, we can resubmit the query
+ RefPtr<nsAsyncResolveRequest> newRequest =
+ new nsAsyncResolveRequest(mPPS, mChannel, mResolveFlags, mCallback);
+ rv = mPPS->mPACMan->AsyncGetProxyForURI(proxyURI, newRequest,
+ mResolveFlags, true);
+ }
+
+ if (NS_FAILED(rv)) {
+ mCallback->OnProxyAvailable(this, mChannel, nullptr, rv);
+ }
+
+ // do not call onproxyavailable() in SUCCESS case - the newRequest will
+ // take care of that
+ } else {
+ LOG(("pac thread callback did not provide information %" PRIX32 "\n",
+ static_cast<uint32_t>(mStatus)));
+ if (NS_SUCCEEDED(mStatus)) mPPS->MaybeDisableDNSPrefetch(mProxyInfo);
+ EnsureResolveFlagsMatch();
+ mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus);
+ }
+
+ // We are on the main thread now and don't need these any more so
+ // release them to avoid having to proxy them back to the main thread
+ // in the dtor
+ mCallback = nullptr; // in case the callback holds an owning ref to us
+ mPPS = nullptr;
+ mXPComPPS = nullptr;
+ mChannel = nullptr;
+ mProxyInfo = nullptr;
+ }
+
+ private:
+ nsresult mStatus{NS_OK};
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mDispatched{false};
+ uint32_t mResolveFlags;
+
+ nsProtocolProxyService* mPPS;
+ nsCOMPtr<nsIProtocolProxyService> mXPComPPS;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIProtocolProxyCallback> mCallback;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+
+ RefPtr<AsyncApplyFilters> mAsyncFilterApplier;
+};
+
+NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable)
+
+NS_IMPL_ISUPPORTS(nsAsyncResolveRequest::AsyncApplyFilters,
+ nsIProxyProtocolFilterResult, nsICancelable, nsIRunnable)
+
+nsAsyncResolveRequest::AsyncApplyFilters::AsyncApplyFilters(
+ nsProtocolInfo& aInfo, Callback const& aCallback)
+ : mInfo(aInfo),
+ mCallback(aCallback),
+ mNextFilterIndex(0),
+ mProcessingInLoop(false),
+ mFilterCalledBack(false) {
+ LOG(("AsyncApplyFilters %p", this));
+}
+
+nsAsyncResolveRequest::AsyncApplyFilters::~AsyncApplyFilters() {
+ LOG(("~AsyncApplyFilters %p", this));
+
+ MOZ_ASSERT(!mRequest);
+ MOZ_ASSERT(!mProxyInfo);
+ MOZ_ASSERT(!mFiltersCopy.Length());
+}
+
+nsresult nsAsyncResolveRequest::AsyncApplyFilters::AsyncProcess(
+ nsAsyncResolveRequest* aRequest) {
+ LOG(("AsyncApplyFilters::AsyncProcess %p for req %p", this, aRequest));
+
+ MOZ_ASSERT(!mRequest, "AsyncApplyFilters started more than once!");
+
+ if (!(mInfo.flags & nsIProtocolHandler::ALLOWS_PROXY)) {
+ // Calling the callback directly (not via Finish()) since we
+ // don't want to prune.
+ return mCallback(aRequest, aRequest->mProxyInfo, false);
+ }
+
+ mProcessingThread = NS_GetCurrentThread();
+
+ mRequest = aRequest;
+ mProxyInfo = aRequest->mProxyInfo;
+
+ aRequest->mPPS->CopyFilters(mFiltersCopy);
+
+ // We want to give filters a chance to process in a single loop to prevent
+ // any current-thread dispatch delays when those are not needed.
+ // This code is rather "loopy" than "recursive" to prevent long stack traces.
+ do {
+ MOZ_ASSERT(!mProcessingInLoop);
+
+ mozilla::AutoRestore<bool> restore(mProcessingInLoop);
+ mProcessingInLoop = true;
+
+ nsresult rv = ProcessNextFilter();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } while (mFilterCalledBack);
+
+ return NS_OK;
+}
+
+nsresult nsAsyncResolveRequest::AsyncApplyFilters::ProcessNextFilter() {
+ LOG(("AsyncApplyFilters::ProcessNextFilter %p ENTER pi=%p", this,
+ mProxyInfo.get()));
+
+ RefPtr<FilterLink> filter;
+ do {
+ mFilterCalledBack = false;
+
+ if (!mRequest) {
+ // We got canceled
+ LOG((" canceled"));
+ return NS_OK; // should we let the consumer know?
+ }
+
+ if (mNextFilterIndex == mFiltersCopy.Length()) {
+ return Finish();
+ }
+
+ filter = mFiltersCopy[mNextFilterIndex++];
+
+ // Loop until a call to a filter succeeded. Other option is to recurse
+ // but that would waste stack trace when a number of filters gets registered
+ // and all from some reason tend to fail.
+ // The !mFilterCalledBack part of the condition is there to protect us from
+ // calling on another filter when the current one managed to call back and
+ // then threw. We already have the result so take it and use it since
+ // the next filter will be processed by the root loop or a call to
+ // ProcessNextFilter has already been dispatched to this thread.
+ LOG((" calling filter %p pi=%p", filter.get(), mProxyInfo.get()));
+ } while (!mRequest->mPPS->ApplyFilter(filter, mRequest->mChannel, mInfo,
+ mProxyInfo, this) &&
+ !mFilterCalledBack);
+
+ LOG(("AsyncApplyFilters::ProcessNextFilter %p LEAVE pi=%p", this,
+ mProxyInfo.get()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncResolveRequest::AsyncApplyFilters::OnProxyFilterResult(
+ nsIProxyInfo* aProxyInfo) {
+ LOG(("AsyncApplyFilters::OnProxyFilterResult %p pi=%p", this, aProxyInfo));
+
+ MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mFilterCalledBack);
+
+ if (mFilterCalledBack) {
+ LOG((" duplicate notification?"));
+ return NS_OK;
+ }
+
+ mFilterCalledBack = true;
+
+ if (!mRequest) {
+ // We got canceled
+ LOG((" canceled"));
+ return NS_OK;
+ }
+
+ mProxyInfo = aProxyInfo;
+
+ if (mProcessingInLoop) {
+ // No need to call/dispatch ProcessNextFilter(), we are in a control
+ // loop that will do this for us and save recursion/dispatching.
+ LOG((" in a root loop"));
+ return NS_OK;
+ }
+
+ if (mNextFilterIndex == mFiltersCopy.Length()) {
+ // We are done, all filters have been called on!
+ Finish();
+ return NS_OK;
+ }
+
+ // Redispatch, since we don't want long stacks when filters respond
+ // synchronously.
+ LOG((" redispatching"));
+ NS_DispatchToCurrentThread(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncResolveRequest::AsyncApplyFilters::Run() {
+ LOG(("AsyncApplyFilters::Run %p", this));
+
+ MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread());
+
+ ProcessNextFilter();
+ return NS_OK;
+}
+
+nsresult nsAsyncResolveRequest::AsyncApplyFilters::Finish() {
+ LOG(("AsyncApplyFilters::Finish %p pi=%p", this, mProxyInfo.get()));
+
+ MOZ_ASSERT(mRequest);
+
+ mFiltersCopy.Clear();
+
+ RefPtr<nsAsyncResolveRequest> request;
+ request.swap(mRequest);
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ pi.swap(mProxyInfo);
+
+ request->mPPS->PruneProxyInfo(mInfo, pi);
+ return mCallback(request, pi, !mProcessingInLoop);
+}
+
+NS_IMETHODIMP
+nsAsyncResolveRequest::AsyncApplyFilters::Cancel(nsresult reason) {
+ LOG(("AsyncApplyFilters::Cancel %p", this));
+
+ MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread());
+
+ // This will be called only from inside the request, so don't call
+ // its's callback. Dropping the members means we simply break the cycle.
+ mFiltersCopy.Clear();
+ mProxyInfo = nullptr;
+ mRequest = nullptr;
+
+ return NS_OK;
+}
+
+// Bug 1366133: make GetPACURI off-main-thread since it may hang on Windows
+// platform
+class AsyncGetPACURIRequest final : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ using CallbackFunc = nsresult (nsProtocolProxyService::*)(bool, bool,
+ nsresult,
+ const nsACString&);
+
+ AsyncGetPACURIRequest(nsProtocolProxyService* aService,
+ CallbackFunc aCallback,
+ nsISystemProxySettings* aSystemProxySettings,
+ bool aMainThreadOnly, bool aForceReload,
+ bool aResetPACThread)
+ : mIsMainThreadOnly(aMainThreadOnly),
+ mService(aService),
+ mServiceHolder(do_QueryObject(aService)),
+ mCallback(aCallback),
+ mSystemProxySettings(aSystemProxySettings),
+ mForceReload(aForceReload),
+ mResetPACThread(aResetPACThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+ Unused << mIsMainThreadOnly;
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThreadOnly);
+
+ nsCString pacUri;
+ nsresult rv = mSystemProxySettings->GetPACURI(pacUri);
+
+ nsCOMPtr<nsIRunnable> event =
+ NewNonOwningCancelableRunnableMethod<bool, bool, nsresult, nsCString>(
+ "AsyncGetPACURIRequestCallback", mService, mCallback, mForceReload,
+ mResetPACThread, rv, pacUri);
+
+ return NS_DispatchToMainThread(event);
+ }
+
+ private:
+ ~AsyncGetPACURIRequest() {
+ NS_ReleaseOnMainThread("AsyncGetPACURIRequest::mServiceHolder",
+ mServiceHolder.forget());
+ }
+
+ bool mIsMainThreadOnly;
+
+ nsProtocolProxyService* mService; // ref-count is hold by mServiceHolder
+ nsCOMPtr<nsIProtocolProxyService2> mServiceHolder;
+ CallbackFunc mCallback;
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ bool mForceReload;
+ bool mResetPACThread;
+};
+
+NS_IMPL_ISUPPORTS(AsyncGetPACURIRequest, nsIRunnable)
+
+//----------------------------------------------------------------------------
+
+//
+// apply mask to address (zeros out excluded bits).
+//
+// NOTE: we do the byte swapping here to minimize overall swapping.
+//
+static void proxy_MaskIPv6Addr(PRIPv6Addr& addr, uint16_t mask_len) {
+ if (mask_len == 128) return;
+
+ if (mask_len > 96) {
+ addr.pr_s6_addr32[3] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[3]) & (~0uL << (128 - mask_len)));
+ } else if (mask_len > 64) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[2]) & (~0uL << (96 - mask_len)));
+ } else if (mask_len > 32) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[1]) & (~0uL << (64 - mask_len)));
+ } else {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = 0;
+ addr.pr_s6_addr32[0] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[0]) & (~0uL << (32 - mask_len)));
+ }
+}
+
+static void proxy_GetStringPref(nsIPrefBranch* aPrefBranch, const char* aPref,
+ nsCString& aResult) {
+ nsAutoCString temp;
+ nsresult rv = aPrefBranch->GetCharPref(aPref, temp);
+ if (NS_FAILED(rv)) {
+ aResult.Truncate();
+ } else {
+ aResult.Assign(temp);
+ // all of our string prefs are hostnames, so we should remove any
+ // whitespace characters that the user might have unknowingly entered.
+ aResult.StripWhitespace();
+ }
+}
+
+static void proxy_GetIntPref(nsIPrefBranch* aPrefBranch, const char* aPref,
+ int32_t& aResult) {
+ int32_t temp;
+ nsresult rv = aPrefBranch->GetIntPref(aPref, &temp);
+ if (NS_FAILED(rv)) {
+ aResult = -1;
+ } else {
+ aResult = temp;
+ }
+}
+
+static void proxy_GetBoolPref(nsIPrefBranch* aPrefBranch, const char* aPref,
+ bool& aResult) {
+ bool temp;
+ nsresult rv = aPrefBranch->GetBoolPref(aPref, &temp);
+ if (NS_FAILED(rv)) {
+ aResult = false;
+ } else {
+ aResult = temp;
+ }
+}
+
+//----------------------------------------------------------------------------
+
+static const int32_t PROXYCONFIG_DIRECT4X = 3;
+static const int32_t PROXYCONFIG_COUNT = 6;
+
+NS_IMPL_ADDREF(nsProtocolProxyService)
+NS_IMPL_RELEASE(nsProtocolProxyService)
+NS_IMPL_CLASSINFO(nsProtocolProxyService, nullptr, nsIClassInfo::SINGLETON,
+ NS_PROTOCOLPROXYSERVICE_CID)
+
+// NS_IMPL_QUERY_INTERFACE_CI with the nsProtocolProxyService QI change
+NS_INTERFACE_MAP_BEGIN(nsProtocolProxyService)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService2)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsProtocolProxyService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolProxyService)
+ NS_IMPL_QUERY_CLASSINFO(nsProtocolProxyService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(nsProtocolProxyService, nsIProtocolProxyService,
+ nsIProtocolProxyService2)
+
+nsProtocolProxyService::nsProtocolProxyService() : mSessionStart(PR_Now()) {}
+
+nsProtocolProxyService::~nsProtocolProxyService() {
+ // These should have been cleaned up in our Observe method.
+ NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters.Length() == 0 &&
+ mPACMan == nullptr,
+ "what happened to xpcom-shutdown?");
+}
+
+// nsProtocolProxyService methods
+nsresult nsProtocolProxyService::Init() {
+ // failure to access prefs is non-fatal
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ // monitor proxy prefs
+ prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false);
+
+ // read all prefs
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ // register for shutdown notification so we can clean ourselves up
+ // properly.
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ return NS_OK;
+}
+
+// ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids
+// to call ReloadPAC()
+nsresult nsProtocolProxyService::ReloadNetworkPAC() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return NS_OK;
+ }
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ if (type == PROXYCONFIG_PAC) {
+ nsAutoCString pacSpec;
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec);
+ if (!pacSpec.IsEmpty()) {
+ nsCOMPtr<nsIURI> pacURI;
+ rv = NS_NewURI(getter_AddRefs(pacURI), pacSpec);
+ if (!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ nsProtocolInfo pac;
+ rv = GetProtocolInfo(pacURI, &pac);
+ if (!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ if (!pac.scheme.EqualsLiteral("file") &&
+ !pac.scheme.EqualsLiteral("data")) {
+ LOG((": received network changed event, reload PAC"));
+ ReloadPAC();
+ }
+ }
+ } else if ((type == PROXYCONFIG_WPAD) || (type == PROXYCONFIG_SYSTEM)) {
+ ReloadPAC();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsProtocolProxyService::AsyncConfigureFromPAC(bool aForceReload,
+ bool aResetPACThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool mainThreadOnly;
+ nsresult rv = mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIRunnable> req = new AsyncGetPACURIRequest(
+ this, &nsProtocolProxyService::OnAsyncGetPACURI, mSystemProxySettings,
+ mainThreadOnly, aForceReload, aResetPACThread);
+
+ if (mainThreadOnly) {
+ return req->Run();
+ }
+
+ return NS_DispatchBackgroundTask(req.forget(),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+nsresult nsProtocolProxyService::OnAsyncGetPACURI(bool aForceReload,
+ bool aResetPACThread,
+ nsresult aResult,
+ const nsACString& aUri) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aResetPACThread) {
+ ResetPACThread();
+ }
+
+ if (NS_SUCCEEDED(aResult) && !aUri.IsEmpty()) {
+ ConfigureFromPAC(PromiseFlatCString(aUri), aForceReload);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ mIsShutdown = true;
+ // cleanup
+ mHostFiltersArray.Clear();
+ mFilters.Clear();
+
+ if (mPACMan) {
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ }
+
+ if (mReloadPACTimer) {
+ mReloadPACTimer->Cancel();
+ mReloadPACTimer = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ } else if (strcmp(aTopic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(aData);
+ const char* state = converted.get();
+ if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) {
+ uint32_t delay = StaticPrefs::network_proxy_reload_pac_delay();
+ LOG(("nsProtocolProxyService::Observe call ReloadNetworkPAC() delay=%u",
+ delay));
+
+ if (delay) {
+ if (mReloadPACTimer) {
+ mReloadPACTimer->Cancel();
+ mReloadPACTimer = nullptr;
+ }
+ NS_NewTimerWithCallback(getter_AddRefs(mReloadPACTimer), this, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ ReloadNetworkPAC();
+ }
+ }
+ } else {
+ NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
+ "what is this random observer event?");
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ if (prefs) PrefsChanged(prefs, NS_LossyConvertUTF16toASCII(aData).get());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(aTimer == mReloadPACTimer);
+ ReloadNetworkPAC();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsProtocolProxyService");
+ return NS_OK;
+}
+
+void nsProtocolProxyService::PrefsChanged(nsIPrefBranch* prefBranch,
+ const char* pref) {
+ nsresult rv = NS_OK;
+ bool reloadPAC = false;
+ nsAutoCString tempString;
+ auto invokeCallback =
+ MakeScopeExit([&] { NotifyProxyConfigChangedInternal(); });
+
+ if (!pref || !strcmp(pref, PROXY_PREF("type"))) {
+ int32_t type = -1;
+ rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_SUCCEEDED(rv)) {
+ // bug 115720 - for ns4.x backwards compatibility
+ if (type == PROXYCONFIG_DIRECT4X) {
+ type = PROXYCONFIG_DIRECT;
+ // Reset the type so that the dialog looks correct, and we
+ // don't have to handle this case everywhere else
+ // I'm paranoid about a loop of some sort - only do this
+ // if we're enumerating all prefs, and ignore any error
+ if (!pref) prefBranch->SetIntPref(PROXY_PREF("type"), type);
+ } else if (type >= PROXYCONFIG_COUNT) {
+ LOG(("unknown proxy type: %" PRId32 "; assuming direct\n", type));
+ type = PROXYCONFIG_DIRECT;
+ }
+ mProxyConfig = type;
+ reloadPAC = true;
+ }
+
+ if (mProxyConfig == PROXYCONFIG_SYSTEM) {
+ mSystemProxySettings = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID);
+ if (!mSystemProxySettings) mProxyConfig = PROXYCONFIG_DIRECT;
+ ResetPACThread();
+ } else {
+ if (mSystemProxySettings) {
+ mSystemProxySettings = nullptr;
+ ResetPACThread();
+ }
+ }
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http"))) {
+ proxy_GetStringPref(prefBranch, PROXY_PREF("http"), mHTTPProxyHost);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http_port"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("http_port"), mHTTPProxyPort);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl"))) {
+ proxy_GetStringPref(prefBranch, PROXY_PREF("ssl"), mHTTPSProxyHost);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl_port"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("ssl_port"), mHTTPSProxyPort);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks"))) {
+ proxy_GetStringPref(prefBranch, PROXY_PREF("socks"), mSOCKSProxyTarget);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_port"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_port"), mSOCKSProxyPort);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_version"))) {
+ int32_t version;
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_version"), version);
+ // make sure this preference value remains sane
+ if (version == 5) {
+ mSOCKSProxyVersion = 5;
+ } else {
+ mSOCKSProxyVersion = 4;
+ }
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"),
+ mSOCKSProxyRemoteDNS);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"), mProxyOverTLS);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"),
+ mWPADOverDHCPEnabled);
+ reloadPAC = reloadPAC || mProxyConfig == PROXYCONFIG_WPAD;
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
+ mFailedProxyTimeout);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) {
+ rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"), tempString);
+ if (NS_SUCCEEDED(rv)) LoadHostFilters(tempString);
+ }
+
+ // We're done if not using something that could give us a PAC URL
+ // (PAC, WPAD or System)
+ if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
+ mProxyConfig != PROXYCONFIG_SYSTEM) {
+ return;
+ }
+
+ // OK, we need to reload the PAC file if:
+ // 1) network.proxy.type changed, or
+ // 2) network.proxy.autoconfig_url changed and PAC is configured
+
+ if (!pref || !strcmp(pref, PROXY_PREF("autoconfig_url"))) reloadPAC = true;
+
+ if (reloadPAC) {
+ tempString.Truncate();
+ if (mProxyConfig == PROXYCONFIG_PAC) {
+ prefBranch->GetCharPref(PROXY_PREF("autoconfig_url"), tempString);
+ if (mPACMan && !mPACMan->IsPACURI(tempString)) {
+ LOG(("PAC Thread URI Changed - Reset Pac Thread"));
+ ResetPACThread();
+ }
+ } else if (mProxyConfig == PROXYCONFIG_WPAD) {
+ LOG(("Auto-detecting proxy - Reset Pac Thread"));
+ ResetPACThread();
+ } else if (mSystemProxySettings) {
+ // Get System Proxy settings if available
+ AsyncConfigureFromPAC(false, false);
+ }
+ if (!tempString.IsEmpty() || mProxyConfig == PROXYCONFIG_WPAD) {
+ ConfigureFromPAC(tempString, false);
+ }
+ }
+}
+
+bool nsProtocolProxyService::CanUseProxy(nsIURI* aURI, int32_t defaultPort) {
+ int32_t port;
+ nsAutoCString host;
+
+ nsresult rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty()) return false;
+
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) return false;
+ if (port == -1) port = defaultPort;
+
+ PRNetAddr addr;
+ bool is_ipaddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS);
+
+ PRIPv6Addr ipv6;
+ if (is_ipaddr) {
+ // convert parsed address to IPv6
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &ipv6);
+ } else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&ipv6, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ } else {
+ NS_WARNING("unknown address family");
+ return true; // allow proxying
+ }
+ }
+
+ // Don't use proxy for local hosts (plain hostname, no dots)
+ if ((!is_ipaddr && mFilterLocalHosts && !host.Contains('.')) ||
+ // This method detects if we have network.proxy.allow_hijacking_localhost
+ // pref enabled. If it's true then this method will always return false
+ // otherwise it returns true if the host matches an address that's
+ // hardcoded to the loopback address.
+ (!StaticPrefs::network_proxy_allow_hijacking_localhost() &&
+ nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host))) {
+ LOG(("Not using proxy for this local host [%s]!\n", host.get()));
+ return false; // don't allow proxying
+ }
+
+ int32_t index = -1;
+ while (++index < int32_t(mHostFiltersArray.Length())) {
+ const auto& hinfo = mHostFiltersArray[index];
+
+ if (is_ipaddr != hinfo->is_ipaddr) continue;
+ if (hinfo->port && hinfo->port != port) continue;
+
+ if (is_ipaddr) {
+ // generate masked version of target IPv6 address
+ PRIPv6Addr masked;
+ memcpy(&masked, &ipv6, sizeof(PRIPv6Addr));
+ proxy_MaskIPv6Addr(masked, hinfo->ip.mask_len);
+
+ // check for a match
+ if (memcmp(&masked, &hinfo->ip.addr, sizeof(PRIPv6Addr)) == 0) {
+ return false; // proxy disallowed
+ }
+ } else {
+ uint32_t host_len = host.Length();
+ uint32_t filter_host_len = hinfo->name.host_len;
+
+ if (host_len >= filter_host_len) {
+ //
+ // compare last |filter_host_len| bytes of target hostname.
+ //
+ const char* host_tail = host.get() + host_len - filter_host_len;
+ if (!nsCRT::strncasecmp(host_tail, hinfo->name.host, filter_host_len)) {
+ // If the tail of the host string matches the filter
+
+ if (filter_host_len > 0 && hinfo->name.host[0] == '.') {
+ // If the filter was of the form .foo.bar.tld, all such
+ // matches are correct
+ return false; // proxy disallowed
+ }
+
+ // abc-def.example.org should not match def.example.org
+ // however, *.def.example.org should match .def.example.org
+ // We check that the filter doesn't start with a `.`. If it does,
+ // then the strncasecmp above should suffice. If it doesn't,
+ // then we should only consider it a match if the strncasecmp happened
+ // at a subdomain boundary
+ if (host_len > filter_host_len && *(host_tail - 1) == '.') {
+ // If the host was something.foo.bar.tld and the filter
+ // was foo.bar.tld, it's still a match.
+ // the character right before the tail must be a
+ // `.` for this to work
+ return false; // proxy disallowed
+ }
+
+ if (host_len == filter_host_len) {
+ // If the host and filter are of the same length,
+ // they should match
+ return false; // proxy disallowed
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+// kProxyType\* may be referred to externally in
+// nsProxyInfo in order to compare by string pointer
+const char kProxyType_HTTP[] = "http";
+const char kProxyType_HTTPS[] = "https";
+const char kProxyType_PROXY[] = "proxy";
+const char kProxyType_SOCKS[] = "socks";
+const char kProxyType_SOCKS4[] = "socks4";
+const char kProxyType_SOCKS5[] = "socks5";
+const char kProxyType_DIRECT[] = "direct";
+
+const char* nsProtocolProxyService::ExtractProxyInfo(const char* start,
+ uint32_t aResolveFlags,
+ nsProxyInfo** result) {
+ *result = nullptr;
+ uint32_t flags = 0;
+
+ // see BNF in ProxyAutoConfig.h and notes in nsISystemProxySettings.idl
+
+ // find end of proxy info delimiter
+ const char* end = start;
+ while (*end && *end != ';') ++end;
+
+ // find end of proxy type delimiter
+ const char* sp = start;
+ while (sp < end && *sp != ' ' && *sp != '\t') ++sp;
+
+ uint32_t len = sp - start;
+ const char* type = nullptr;
+ switch (len) {
+ case 4:
+ if (nsCRT::strncasecmp(start, kProxyType_HTTP, 4) == 0) {
+ type = kProxyType_HTTP;
+ }
+ break;
+ case 5:
+ if (nsCRT::strncasecmp(start, kProxyType_PROXY, 5) == 0) {
+ type = kProxyType_HTTP;
+ } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS, 5) == 0) {
+ type = kProxyType_SOCKS4; // assume v4 for 4x compat
+ if (StaticPrefs::network_proxy_default_pac_script_socks_version() ==
+ 5) {
+ type = kProxyType_SOCKS;
+ }
+ } else if (nsCRT::strncasecmp(start, kProxyType_HTTPS, 5) == 0) {
+ type = kProxyType_HTTPS;
+ }
+ break;
+ case 6:
+ if (nsCRT::strncasecmp(start, kProxyType_DIRECT, 6) == 0) {
+ type = kProxyType_DIRECT;
+ } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS4, 6) == 0) {
+ type = kProxyType_SOCKS4;
+ } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS5, 6) == 0) {
+ // map "SOCKS5" to "socks" to match contract-id of registered
+ // SOCKS-v5 socket provider.
+ type = kProxyType_SOCKS;
+ }
+ break;
+ }
+ if (type) {
+ int32_t port = -1;
+
+ // If it's a SOCKS5 proxy, do name resolution on the server side.
+ // We could use this with SOCKS4a servers too, but they might not
+ // support it.
+ if (type == kProxyType_SOCKS || mSOCKSProxyRemoteDNS) {
+ flags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+
+ // extract host:port
+ start = sp;
+ while ((*start == ' ' || *start == '\t') && start < end) start++;
+
+ // port defaults
+ if (type == kProxyType_HTTP) {
+ port = 80;
+ } else if (type == kProxyType_HTTPS) {
+ port = 443;
+ } else {
+ port = 1080;
+ }
+
+ RefPtr<nsProxyInfo> pi = new nsProxyInfo();
+ pi->mType = type;
+ pi->mFlags = flags;
+ pi->mResolveFlags = aResolveFlags;
+ pi->mTimeout = mFailedProxyTimeout;
+
+ // www.foo.com:8080 and http://www.foo.com:8080
+ nsDependentCSubstring maybeURL(start, end - start);
+ nsCOMPtr<nsIURI> pacURI;
+
+ nsAutoCString urlHost;
+ // First assume the scheme is present, e.g. http://www.example.com:8080
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) ||
+ NS_FAILED(pacURI->GetAsciiHost(urlHost)) || urlHost.IsEmpty()) {
+ // It isn't, assume www.example.com:8080
+ maybeURL.Insert("http://", 0);
+
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL))) {
+ pacURI->GetAsciiHost(urlHost);
+ }
+ }
+
+ if (!urlHost.IsEmpty()) {
+ pi->mHost = urlHost;
+
+ int32_t tPort;
+ if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) {
+ port = tPort;
+ }
+ pi->mPort = port;
+ }
+
+ pi.forget(result);
+ }
+
+ while (*end == ';' || *end == ' ' || *end == '\t') ++end;
+ return end;
+}
+
+void nsProtocolProxyService::GetProxyKey(nsProxyInfo* pi, nsCString& key) {
+ key.AssignASCII(pi->mType);
+ if (!pi->mHost.IsEmpty()) {
+ key.Append(' ');
+ key.Append(pi->mHost);
+ key.Append(':');
+ key.AppendInt(pi->mPort);
+ }
+}
+
+uint32_t nsProtocolProxyService::SecondsSinceSessionStart() {
+ PRTime now = PR_Now();
+
+ // get time elapsed since session start
+ int64_t diff = now - mSessionStart;
+
+ // convert microseconds to seconds
+ diff /= PR_USEC_PER_SEC;
+
+ // return converted 32 bit value
+ return uint32_t(diff);
+}
+
+void nsProtocolProxyService::EnableProxy(nsProxyInfo* pi) {
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+ mFailedProxies.Remove(key);
+}
+
+void nsProtocolProxyService::DisableProxy(nsProxyInfo* pi) {
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // Add timeout to interval (this is the time when the proxy can
+ // be tried again).
+ dsec += pi->mTimeout;
+
+ // NOTE: The classic codebase would increase the timeout value
+ // incrementally each time a subsequent failure occurred.
+ // We could do the same, but it would require that we not
+ // remove proxy entries in IsProxyDisabled or otherwise
+ // change the way we are recording disabled proxies.
+ // Simpler is probably better for now, and at least the
+ // user can tune the timeout setting via preferences.
+
+ LOG(("DisableProxy %s %d\n", key.get(), dsec));
+
+ // If this fails, oh well... means we don't have enough memory
+ // to remember the failed proxy.
+ mFailedProxies.InsertOrUpdate(key, dsec);
+}
+
+bool nsProtocolProxyService::IsProxyDisabled(nsProxyInfo* pi) {
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t val;
+ if (!mFailedProxies.Get(key, &val)) return false;
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // if time passed has exceeded interval, then try proxy again.
+ if (dsec > val) {
+ mFailedProxies.Remove(key);
+ return false;
+ }
+
+ return true;
+}
+
+nsresult nsProtocolProxyService::SetupPACThread(
+ nsISerialEventTarget* mainThreadEventTarget) {
+ if (mIsShutdown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mPACMan) return NS_OK;
+
+ mPACMan = new nsPACMan(mainThreadEventTarget);
+
+ bool mainThreadOnly;
+ nsresult rv;
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ rv = mPACMan->Init(mSystemProxySettings);
+ } else {
+ rv = mPACMan->Init(nullptr);
+ }
+ if (NS_FAILED(rv)) {
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ }
+ return rv;
+}
+
+nsresult nsProtocolProxyService::ResetPACThread() {
+ if (!mPACMan) return NS_OK;
+
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ return SetupPACThread();
+}
+
+nsresult nsProtocolProxyService::ConfigureFromPAC(const nsCString& spec,
+ bool forceReload) {
+ nsresult rv = SetupPACThread();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool autodetect = spec.IsEmpty();
+ if (!forceReload && ((!autodetect && mPACMan->IsPACURI(spec)) ||
+ (autodetect && mPACMan->IsUsingWPAD()))) {
+ return NS_OK;
+ }
+
+ mFailedProxies.Clear();
+
+ mPACMan->SetWPADOverDHCPEnabled(mWPADOverDHCPEnabled);
+ return mPACMan->LoadPACFromURI(spec);
+}
+
+void nsProtocolProxyService::ProcessPACString(const nsCString& pacString,
+ uint32_t aResolveFlags,
+ nsIProxyInfo** result) {
+ if (pacString.IsEmpty()) {
+ *result = nullptr;
+ return;
+ }
+
+ const char* proxies = pacString.get();
+
+ nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr;
+ while (*proxies) {
+ proxies = ExtractProxyInfo(proxies, aResolveFlags, &pi);
+ if (pi && (pi->mType == kProxyType_HTTPS) && !mProxyOverTLS) {
+ delete pi;
+ pi = nullptr;
+ }
+
+ if (pi) {
+ if (last) {
+ NS_ASSERTION(last->mNext == nullptr, "leaking nsProxyInfo");
+ last->mNext = pi;
+ } else {
+ first = pi;
+ }
+ last = pi;
+ }
+ }
+ *result = first;
+}
+
+// nsIProtocolProxyService2
+NS_IMETHODIMP
+nsProtocolProxyService::ReloadPAC() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) return NS_OK;
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsAutoCString pacSpec;
+ if (type == PROXYCONFIG_PAC) {
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec);
+ } else if (type == PROXYCONFIG_SYSTEM) {
+ if (mSystemProxySettings) {
+ AsyncConfigureFromPAC(true, true);
+ } else {
+ ResetPACThread();
+ }
+ }
+
+ if (!pacSpec.IsEmpty() || type == PROXYCONFIG_WPAD) {
+ ConfigureFromPAC(pacSpec, true);
+ }
+ return NS_OK;
+}
+
+// When sync interface is removed this can go away too
+// The nsPACManCallback portion of this implementation should be run
+// off the main thread, because it uses a condvar for signaling and
+// the main thread is blocking on that condvar -
+// so call nsPACMan::AsyncGetProxyForURI() with
+// a false mainThreadResponse parameter.
+class nsAsyncBridgeRequest final : public nsPACManCallback {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncBridgeRequest()
+ : mMutex("nsDeprecatedCallback"),
+ mCondVar(mMutex, "nsDeprecatedCallback") {}
+
+ void OnQueryComplete(nsresult status, const nsACString& pacString,
+ const nsACString& newPACURL) override {
+ MutexAutoLock lock(mMutex);
+ mCompleted = true;
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ mCondVar.Notify();
+ }
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE(mMutex) { mMutex.Lock(); }
+ void Unlock() MOZ_CAPABILITY_RELEASE(mMutex) { mMutex.Unlock(); }
+ void Wait() { mCondVar.Wait(TimeDuration::FromSeconds(3)); }
+
+ private:
+ ~nsAsyncBridgeRequest() = default;
+
+ friend class nsProtocolProxyService;
+
+ Mutex mMutex;
+ CondVar mCondVar;
+
+ nsresult mStatus MOZ_GUARDED_BY(mMutex){NS_OK};
+ nsCString mPACString MOZ_GUARDED_BY(mMutex);
+ nsCString mPACURL MOZ_GUARDED_BY(mMutex);
+ bool mCompleted MOZ_GUARDED_BY(mMutex){false};
+};
+NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest)
+
+nsresult nsProtocolProxyService::AsyncResolveInternal(
+ nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback,
+ nsICancelable** result, bool isSyncOK,
+ nsISerialEventTarget* mainThreadEventTarget) {
+ NS_ENSURE_ARG_POINTER(channel);
+ NS_ENSURE_ARG_POINTER(callback);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ *result = nullptr;
+ RefPtr<nsAsyncResolveRequest> ctx =
+ new nsAsyncResolveRequest(this, channel, flags, callback);
+
+ nsProtocolInfo info;
+ rv = GetProtocolInfo(uri, &info);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ bool usePACThread;
+
+ // adapt to realtime changes in the system proxy service
+ if (mProxyConfig == PROXYCONFIG_SYSTEM) {
+ nsCOMPtr<nsISystemProxySettings> sp2 =
+ do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID);
+ if (sp2 != mSystemProxySettings) {
+ mSystemProxySettings = sp2;
+ ResetPACThread();
+ }
+ }
+
+ rv = SetupPACThread(mainThreadEventTarget);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // SystemProxySettings and PAC files can block the main thread
+ // but if neither of them are in use, we can just do the work
+ // right here and directly invoke the callback
+
+ rv =
+ Resolve_Internal(channel, info, flags, &usePACThread, getter_AddRefs(pi));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!usePACThread || !mPACMan) {
+ // we can do it locally
+ rv = ctx->ProcessLocally(info, pi, isSyncOK);
+ if (NS_SUCCEEDED(rv) && !isSyncOK) {
+ ctx.forget(result);
+ }
+ return rv;
+ }
+
+ // else kick off a PAC thread query
+ rv = mPACMan->AsyncGetProxyForURI(uri, ctx, flags, true);
+ if (NS_SUCCEEDED(rv)) ctx.forget(result);
+ return rv;
+}
+
+// nsIProtocolProxyService
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve2(
+ nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback,
+ nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) {
+ return AsyncResolveInternal(channel, flags, callback, result, true,
+ mainThreadEventTarget);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve(
+ nsISupports* channelOrURI, uint32_t flags,
+ nsIProtocolProxyCallback* callback,
+ nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) {
+ nsresult rv;
+ // Check if we got a channel:
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(channelOrURI);
+ if (!channel) {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(channelOrURI);
+ if (!uri) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // creating a temporary channel from the URI which is not
+ // used to perform any network loads, hence its safe to
+ // use systemPrincipal as the loadingPrincipal.
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AsyncResolveInternal(channel, flags, callback, result, false,
+ mainThreadEventTarget);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfo(
+ const nsACString& aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey, uint32_t aFlags,
+ uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
+ nsIProxyInfo** aResult) {
+ return NewProxyInfoWithAuth(aType, aHost, aPort, ""_ns, ""_ns,
+ aProxyAuthorizationHeader,
+ aConnectionIsolationKey, aFlags, aFailoverTimeout,
+ aFailoverProxy, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfoWithAuth(
+ const nsACString& aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aUsername, const nsACString& aPassword,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey, uint32_t aFlags,
+ uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
+ nsIProxyInfo** aResult) {
+ static const char* types[] = {kProxyType_HTTP, kProxyType_HTTPS,
+ kProxyType_SOCKS, kProxyType_SOCKS4,
+ kProxyType_DIRECT};
+
+ // resolve type; this allows us to avoid copying the type string into each
+ // proxy info instance. we just reference the string literals directly :)
+ const char* type = nullptr;
+ for (auto& t : types) {
+ if (aType.LowerCaseEqualsASCII(t)) {
+ type = t;
+ break;
+ }
+ }
+ NS_ENSURE_TRUE(type, NS_ERROR_INVALID_ARG);
+
+ // We have only implemented username/password for SOCKS proxies.
+ if ((!aUsername.IsEmpty() || !aPassword.IsEmpty()) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS4)) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NewProxyInfo_Internal(type, aHost, aPort, aUsername, aPassword,
+ aProxyAuthorizationHeader,
+ aConnectionIsolationKey, aFlags,
+ aFailoverTimeout, aFailoverProxy, 0, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo* aProxy, nsIURI* aURI,
+ nsresult aStatus,
+ nsIProxyInfo** aResult) {
+ // Failover is supported through a variety of methods including:
+ // * PAC scripts (PROXYCONFIG_PAC and PROXYCONFIG_WPAD)
+ // * System proxy
+ // * Extensions
+ // With extensions the mProxyConfig can be any type and the extension
+ // is still involved in the proxy filtering. It may have also supplied
+ // any number of failover proxies. We cannot determine what the mix is
+ // here, so we will attempt to get a failover regardless of the config
+ // type. MANUAL configuration will not disable a proxy.
+
+ // Verify that |aProxy| is one of our nsProxyInfo objects.
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ NS_ENSURE_ARG(pi);
+ // OK, the QI checked out. We can proceed.
+
+ // Remember that this proxy is down. If the user has manually configured some
+ // proxies we do not want to disable them.
+ if (mProxyConfig != PROXYCONFIG_MANUAL) {
+ DisableProxy(pi);
+ }
+
+ // NOTE: At this point, we might want to prompt the user if we have
+ // not already tried going DIRECT. This is something that the
+ // classic codebase supported; however, IE6 does not prompt.
+
+ if (!pi->mNext) return NS_ERROR_NOT_AVAILABLE;
+
+ LOG(("PAC failover from %s %s:%d to %s %s:%d\n", pi->mType, pi->mHost.get(),
+ pi->mPort, pi->mNext->mType, pi->mNext->mHost.get(), pi->mNext->mPort));
+
+ *aResult = do_AddRef(pi->mNext).take();
+ return NS_OK;
+}
+
+namespace { // anon
+
+class ProxyFilterPositionComparator {
+ using FilterLinkRef = RefPtr<nsProtocolProxyService::FilterLink>;
+
+ public:
+ bool Equals(const FilterLinkRef& a, const FilterLinkRef& b) const {
+ return a->position == b->position;
+ }
+ bool LessThan(const FilterLinkRef& a, const FilterLinkRef& b) const {
+ return a->position < b->position;
+ }
+};
+
+class ProxyFilterObjectComparator {
+ using FilterLinkRef = RefPtr<nsProtocolProxyService::FilterLink>;
+
+ public:
+ bool Equals(const FilterLinkRef& link, const nsISupports* obj) const {
+ return obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->filter)) ||
+ obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->channelFilter));
+ }
+};
+
+} // namespace
+
+nsresult nsProtocolProxyService::InsertFilterLink(RefPtr<FilterLink>&& link) {
+ LOG(("nsProtocolProxyService::InsertFilterLink filter=%p", link.get()));
+
+ if (mIsShutdown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we add a new element with the same position as an existing one, we want
+ // to preserve the insertion order to avoid surprises.
+ mFilters.InsertElementSorted(link, ProxyFilterPositionComparator());
+
+ NotifyProxyConfigChangedInternal();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter* filter,
+ uint32_t position) {
+ UnregisterFilter(filter); // remove this filter if we already have it
+
+ RefPtr<FilterLink> link = new FilterLink(position, filter);
+ return InsertFilterLink(std::move(link));
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterChannelFilter(
+ nsIProtocolProxyChannelFilter* channelFilter, uint32_t position) {
+ UnregisterChannelFilter(
+ channelFilter); // remove this filter if we already have it
+
+ RefPtr<FilterLink> link = new FilterLink(position, channelFilter);
+ return InsertFilterLink(std::move(link));
+}
+
+nsresult nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject) {
+ LOG(("nsProtocolProxyService::RemoveFilterLink target=%p", givenObject));
+
+ nsresult rv =
+ mFilters.RemoveElement(givenObject, ProxyFilterObjectComparator())
+ ? NS_OK
+ : NS_ERROR_UNEXPECTED;
+ if (NS_SUCCEEDED(rv)) {
+ NotifyProxyConfigChangedInternal();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterFilter(nsIProtocolProxyFilter* filter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(filter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterChannelFilter(
+ nsIProtocolProxyChannelFilter* channelFilter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(channelFilter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetProxyConfigType(uint32_t* aProxyConfigType) {
+ *aProxyConfigType = mProxyConfig;
+ return NS_OK;
+}
+
+void nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters) {
+ if (mIsShutdown) {
+ return;
+ }
+
+ // check to see the owners flag? /!?/ TODO
+ if (mHostFiltersArray.Length() > 0) {
+ mHostFiltersArray.Clear();
+ }
+
+ // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref
+ // string
+ mFilterLocalHosts = false;
+
+ if (aFilters.IsEmpty()) {
+ return;
+ }
+
+ //
+ // filter = ( host | domain | ipaddr ["/" mask] ) [":" port]
+ // filters = filter *( "," LWS filter)
+ //
+ mozilla::Tokenizer t(aFilters);
+ mozilla::Tokenizer::Token token;
+ bool eof = false;
+ // while (*filters) {
+ while (!eof) {
+ // skip over spaces and ,
+ t.SkipWhites();
+ while (t.CheckChar(',')) {
+ t.SkipWhites();
+ }
+
+ nsAutoCString portStr;
+ nsAutoCString hostStr;
+ nsAutoCString maskStr;
+ t.Record();
+
+ bool parsingIPv6 = false;
+ bool parsingPort = false;
+ bool parsingMask = false;
+ while (t.Next(token)) {
+ if (token.Equals(mozilla::Tokenizer::Token::EndOfFile())) {
+ eof = true;
+ break;
+ }
+ if (token.Equals(mozilla::Tokenizer::Token::Char(',')) ||
+ token.Type() == mozilla::Tokenizer::TOKEN_WS) {
+ break;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char('['))) {
+ parsingIPv6 = true;
+ continue;
+ }
+
+ if (!parsingIPv6 && token.Equals(mozilla::Tokenizer::Token::Char(':'))) {
+ // Port is starting. Claim the previous as host.
+ if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ t.Claim(hostStr);
+ }
+ t.Record();
+ parsingPort = true;
+ continue;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) {
+ t.Claim(hostStr);
+ t.Record();
+ parsingMask = true;
+ continue;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char(']'))) {
+ parsingIPv6 = false;
+ continue;
+ }
+ }
+ if (!parsingPort && !parsingMask) {
+ t.Claim(hostStr);
+ } else if (parsingPort) {
+ t.Claim(portStr);
+ } else if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ NS_WARNING("Could not parse this rule");
+ continue;
+ }
+
+ if (hostStr.IsEmpty()) {
+ continue;
+ }
+
+ // If the current host filter is "<local>", then all local (i.e.
+ // no dots in the hostname) hosts should bypass the proxy
+ if (hostStr.EqualsIgnoreCase("<local>")) {
+ mFilterLocalHosts = true;
+ LOG(
+ ("loaded filter for local hosts "
+ "(plain host names, no dots)\n"));
+ // Continue to next host filter;
+ continue;
+ }
+
+ // For all other host filters, create HostInfo object and add to list
+ HostInfo* hinfo = new HostInfo();
+ nsresult rv = NS_OK;
+
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ port = 0;
+ }
+ hinfo->port = port;
+
+ int32_t maskLen = maskStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ maskLen = 128;
+ }
+
+ // PR_StringToNetAddr can't parse brackets enclosed IPv6
+ nsAutoCString addrString = hostStr;
+ if (hostStr.First() == '[' && hostStr.Last() == ']') {
+ addrString = Substring(hostStr, 1, hostStr.Length() - 2);
+ }
+
+ PRNetAddr addr;
+ if (PR_StringToNetAddr(addrString.get(), &addr) == PR_SUCCESS) {
+ hinfo->is_ipaddr = true;
+ hinfo->ip.family = PR_AF_INET6; // we always store address as IPv6
+ hinfo->ip.mask_len = maskLen;
+
+ if (hinfo->ip.mask_len == 0) {
+ NS_WARNING("invalid mask");
+ goto loser;
+ }
+
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &hinfo->ip.addr);
+ // adjust mask_len accordingly
+ if (hinfo->ip.mask_len <= 32) hinfo->ip.mask_len += 96;
+ } else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&hinfo->ip.addr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ } else {
+ NS_WARNING("unknown address family");
+ goto loser;
+ }
+
+ // apply mask to IPv6 address
+ proxy_MaskIPv6Addr(hinfo->ip.addr, hinfo->ip.mask_len);
+ } else {
+ nsAutoCString host;
+ if (hostStr.First() == '*') {
+ host = Substring(hostStr, 1);
+ } else {
+ host = hostStr;
+ }
+
+ if (host.IsEmpty()) {
+ hinfo->name.host = nullptr;
+ goto loser;
+ }
+
+ hinfo->name.host_len = host.Length();
+
+ hinfo->is_ipaddr = false;
+ hinfo->name.host = ToNewCString(host, mozilla::fallible);
+
+ if (!hinfo->name.host) goto loser;
+ }
+
+// #define DEBUG_DUMP_FILTERS
+#ifdef DEBUG_DUMP_FILTERS
+ printf("loaded filter[%zu]:\n", mHostFiltersArray.Length());
+ printf(" is_ipaddr = %u\n", hinfo->is_ipaddr);
+ printf(" port = %u\n", hinfo->port);
+ printf(" host = %s\n", hostStr.get());
+ if (hinfo->is_ipaddr) {
+ printf(" ip.family = %x\n", hinfo->ip.family);
+ printf(" ip.mask_len = %u\n", hinfo->ip.mask_len);
+
+ PRNetAddr netAddr;
+ PR_SetNetAddr(PR_IpAddrNull, PR_AF_INET6, 0, &netAddr);
+ memcpy(&netAddr.ipv6.ip, &hinfo->ip.addr, sizeof(hinfo->ip.addr));
+
+ char buf[256];
+ PR_NetAddrToString(&netAddr, buf, sizeof(buf));
+
+ printf(" ip.addr = %s\n", buf);
+ } else {
+ printf(" name.host = %s\n", hinfo->name.host);
+ }
+#endif
+
+ mHostFiltersArray.AppendElement(hinfo);
+ hinfo = nullptr;
+ loser:
+ delete hinfo;
+ }
+}
+
+nsresult nsProtocolProxyService::GetProtocolInfo(nsIURI* uri,
+ nsProtocolInfo* info) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(uri, "URI is null");
+ MOZ_ASSERT(info, "info is null");
+
+ nsresult rv;
+
+ rv = uri->GetScheme(info->scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ios->GetDynamicProtocolFlags(uri, &info->flags);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ios->GetDefaultPort(info->scheme.get(), &info->defaultPort);
+ return rv;
+}
+
+nsresult nsProtocolProxyService::NewProxyInfo_Internal(
+ const char* aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aUsername, const nsACString& aPassword,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey, uint32_t aFlags,
+ uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
+ uint32_t aResolveFlags, nsIProxyInfo** aResult) {
+ if (aPort <= 0) aPort = -1;
+
+ nsCOMPtr<nsProxyInfo> failover;
+ if (aFailoverProxy) {
+ failover = do_QueryInterface(aFailoverProxy);
+ NS_ENSURE_ARG(failover);
+ }
+
+ RefPtr<nsProxyInfo> proxyInfo = new nsProxyInfo();
+
+ proxyInfo->mType = aType;
+ proxyInfo->mHost = aHost;
+ proxyInfo->mPort = aPort;
+ proxyInfo->mUsername = aUsername;
+ proxyInfo->mPassword = aPassword;
+ proxyInfo->mFlags = aFlags;
+ proxyInfo->mResolveFlags = aResolveFlags;
+ proxyInfo->mTimeout =
+ aFailoverTimeout == UINT32_MAX ? mFailedProxyTimeout : aFailoverTimeout;
+ proxyInfo->mProxyAuthorizationHeader = aProxyAuthorizationHeader;
+ proxyInfo->mConnectionIsolationKey = aConnectionIsolationKey;
+ failover.swap(proxyInfo->mNext);
+
+ proxyInfo.forget(aResult);
+ return NS_OK;
+}
+
+nsresult nsProtocolProxyService::Resolve_Internal(nsIChannel* channel,
+ const nsProtocolInfo& info,
+ uint32_t flags,
+ bool* usePACThread,
+ nsIProxyInfo** result) {
+ NS_ENSURE_ARG_POINTER(channel);
+
+ *usePACThread = false;
+ *result = nullptr;
+
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY)) {
+ return NS_OK; // Can't proxy this (filters may not override)
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ // See bug #586908.
+ // Avoid endless loop if |uri| is the current PAC-URI. Returning OK
+ // here means that we will not use a proxy for this connection.
+ if (mPACMan && mPACMan->IsPACURI(uri)) return NS_OK;
+
+ // if proxies are enabled and this host:port combo is supposed to use a
+ // proxy, check for a proxy.
+ if ((mProxyConfig == PROXYCONFIG_DIRECT) ||
+ !CanUseProxy(uri, info.defaultPort)) {
+ return NS_OK;
+ }
+
+ bool mainThreadOnly;
+ if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM) {
+ // If the system proxy setting implementation is not threadsafe (e.g
+ // linux gconf), we'll do it inline here. Such implementations promise
+ // not to block
+ // bug 1366133: this block uses GetPACURI & GetProxyForURI, which may
+ // hang on Windows platform. Fortunately, current implementation on
+ // Windows is not main thread only, so we are safe here.
+
+ nsAutoCString PACURI;
+ nsAutoCString pacString;
+
+ if (NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty()) {
+ // There is a PAC URI configured. If it is unchanged, then
+ // just execute the PAC thread. If it is changed then load
+ // the new value
+
+ if (mPACMan && mPACMan->IsPACURI(PACURI)) {
+ // unchanged
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ ConfigureFromPAC(PACURI, false);
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+
+ uri->GetAsciiSpec(spec);
+ uri->GetAsciiHost(host);
+ uri->GetScheme(scheme);
+ uri->GetPort(&port);
+
+ if (flags & RESOLVE_PREFER_SOCKS_PROXY) {
+ LOG(("Ignoring RESOLVE_PREFER_SOCKS_PROXY for system proxy setting\n"));
+ } else if (flags & RESOLVE_PREFER_HTTPS_PROXY) {
+ scheme.AssignLiteral("https");
+ } else if (flags & RESOLVE_IGNORE_URI_SCHEME) {
+ scheme.AssignLiteral("http");
+ }
+
+ // now try the system proxy settings for this particular url
+ if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(spec, scheme, host,
+ port, pacString))) {
+ nsCOMPtr<nsIProxyInfo> pi;
+ ProcessPACString(pacString, 0, getter_AddRefs(pi));
+
+ if (flags & RESOLVE_PREFER_SOCKS_PROXY &&
+ flags & RESOLVE_PREFER_HTTPS_PROXY) {
+ nsAutoCString type;
+ pi->GetType(type);
+ // DIRECT from ProcessPACString indicates that system proxy settings
+ // are not configured to use SOCKS proxy. Try https proxy as a
+ // secondary preferrable proxy. This is mainly for websocket whose
+ // proxy precedence is SOCKS > HTTPS > DIRECT.
+ if (type.EqualsLiteral(kProxyType_DIRECT)) {
+ scheme.AssignLiteral(kProxyType_HTTPS);
+ if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
+ spec, scheme, host, port, pacString))) {
+ ProcessPACString(pacString, 0, getter_AddRefs(pi));
+ }
+ }
+ }
+ pi.forget(result);
+ return NS_OK;
+ }
+ }
+
+ // if proxies are enabled and this host:port combo is supposed to use a
+ // proxy, check for a proxy.
+ if (mProxyConfig == PROXYCONFIG_DIRECT ||
+ (mProxyConfig == PROXYCONFIG_MANUAL &&
+ !CanUseProxy(uri, info.defaultPort))) {
+ return NS_OK;
+ }
+
+ // Proxy auto config magic...
+ if (mProxyConfig == PROXYCONFIG_PAC || mProxyConfig == PROXYCONFIG_WPAD) {
+ // Do not query PAC now.
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ // If we aren't in manual proxy configuration mode then we don't
+ // want to honor any manual specific prefs that might be still set
+ if (mProxyConfig != PROXYCONFIG_MANUAL) return NS_OK;
+
+ // proxy info values for manual configuration mode
+ const char* type = nullptr;
+ const nsACString* host = nullptr;
+ int32_t port = -1;
+
+ uint32_t proxyFlags = 0;
+
+ if ((flags & RESOLVE_PREFER_SOCKS_PROXY) && !mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4) {
+ type = kProxyType_SOCKS4;
+ } else {
+ type = kProxyType_SOCKS;
+ }
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS) {
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+ } else if ((flags & RESOLVE_PREFER_HTTPS_PROXY) &&
+ !mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ } else if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 &&
+ ((flags & RESOLVE_IGNORE_URI_SCHEME) ||
+ info.scheme.EqualsLiteral("http"))) {
+ host = &mHTTPProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPProxyPort;
+ } else if (!mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0 &&
+ !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
+ info.scheme.EqualsLiteral("https")) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ } else if (!mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4) {
+ type = kProxyType_SOCKS4;
+ } else {
+ type = kProxyType_SOCKS;
+ }
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS) {
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+ }
+
+ if (type) {
+ rv = NewProxyInfo_Internal(type, *host, port, ""_ns, ""_ns, ""_ns, ""_ns,
+ proxyFlags, UINT32_MAX, nullptr, flags, result);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+void nsProtocolProxyService::MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy) {
+ // Disable Prefetch in the DNS service if a proxy is in use.
+ if (!aProxy) return;
+
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ if (!pi || !pi->mType || pi->mType == kProxyType_DIRECT) return;
+
+ // To avoid getting DNS service recursively, we directly use
+ // GetXPCOMSingleton().
+ nsCOMPtr<nsIDNSService> dns = nsDNSService::GetXPCOMSingleton();
+ if (!dns) return;
+ nsCOMPtr<nsPIDNSService> pdns = do_QueryInterface(dns);
+ if (!pdns) return;
+
+ // We lose the prefetch optimization for the life of the dns service.
+ pdns->SetPrefetchEnabled(false);
+}
+
+void nsProtocolProxyService::CopyFilters(nsTArray<RefPtr<FilterLink>>& aCopy) {
+ MOZ_ASSERT(aCopy.Length() == 0);
+ aCopy.AppendElements(mFilters);
+}
+
+bool nsProtocolProxyService::ApplyFilter(
+ FilterLink const* filterLink, nsIChannel* channel,
+ const nsProtocolInfo& info, nsCOMPtr<nsIProxyInfo> list,
+ nsIProxyProtocolFilterResult* callback) {
+ nsresult rv;
+
+ // We prune the proxy list prior to invoking each filter. This may be
+ // somewhat inefficient, but it seems like a good idea since we want each
+ // filter to "see" a valid proxy list.
+ PruneProxyInfo(info, list);
+
+ if (filterLink->filter) {
+ nsCOMPtr<nsIURI> uri;
+ Unused << GetProxyURI(channel, getter_AddRefs(uri));
+ if (!uri) {
+ return false;
+ }
+
+ rv = filterLink->filter->ApplyFilter(uri, list, callback);
+ return NS_SUCCEEDED(rv);
+ }
+
+ if (filterLink->channelFilter) {
+ rv = filterLink->channelFilter->ApplyFilter(channel, list, callback);
+ return NS_SUCCEEDED(rv);
+ }
+
+ return false;
+}
+
+void nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo& info,
+ nsIProxyInfo** list) {
+ if (!*list) return;
+
+ LOG(("nsProtocolProxyService::PruneProxyInfo ENTER list=%p", *list));
+
+ nsProxyInfo* head = nullptr;
+ CallQueryInterface(*list, &head);
+ if (!head) {
+ MOZ_ASSERT_UNREACHABLE("nsIProxyInfo must QI to nsProxyInfo");
+ return;
+ }
+ NS_RELEASE(*list);
+
+ // Pruning of disabled proxies works like this:
+ // - If all proxies are disabled, return the full list
+ // - Otherwise, remove the disabled proxies.
+ //
+ // Pruning of disallowed proxies works like this:
+ // - If the protocol handler disallows the proxy, then we disallow it.
+
+ // Start by removing all disallowed proxies if required:
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY_HTTP)) {
+ nsProxyInfo *last = nullptr, *iter = head;
+ while (iter) {
+ if ((iter->Type() == kProxyType_HTTP) ||
+ (iter->Type() == kProxyType_HTTPS)) {
+ // reject!
+ if (last) {
+ last->mNext = iter->mNext;
+ } else {
+ head = iter->mNext;
+ }
+ nsProxyInfo* next = iter->mNext;
+ iter->mNext = nullptr;
+ iter->Release();
+ iter = next;
+ } else {
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+ if (!head) {
+ return;
+ }
+ }
+
+ // Scan to see if all remaining non-direct proxies are disabled. If so, then
+ // we'll just bail and return them all. Otherwise, we'll go and prune the
+ // disabled ones.
+
+ bool allNonDirectProxiesDisabled = true;
+
+ nsProxyInfo* iter;
+ for (iter = head; iter; iter = iter->mNext) {
+ if (!IsProxyDisabled(iter) && iter->mType != kProxyType_DIRECT) {
+ allNonDirectProxiesDisabled = false;
+ break;
+ }
+ }
+
+ if (allNonDirectProxiesDisabled &&
+ StaticPrefs::network_proxy_retry_failed_proxies()) {
+ LOG(("All proxies are disabled, so trying all again"));
+ } else {
+ // remove any disabled proxies.
+ nsProxyInfo* last = nullptr;
+ for (iter = head; iter;) {
+ if (IsProxyDisabled(iter)) {
+ // reject!
+ nsProxyInfo* reject = iter;
+
+ iter = iter->mNext;
+ if (last) {
+ last->mNext = iter;
+ } else {
+ head = iter;
+ }
+
+ reject->mNext = nullptr;
+ NS_RELEASE(reject);
+ continue;
+ }
+
+ // since we are about to use this proxy, make sure it is not on
+ // the disabled proxy list. we'll add it back to that list if
+ // we have to (in GetFailoverForProxy).
+ //
+ // XXX(darin): It might be better to do this as a final pass.
+ //
+ EnableProxy(iter);
+
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+
+ // if only DIRECT was specified then return no proxy info, and we're done.
+ if (head && !head->mNext && head->mType == kProxyType_DIRECT) {
+ NS_RELEASE(head);
+ }
+
+ *list = head; // Transfer ownership
+
+ LOG(("nsProtocolProxyService::PruneProxyInfo LEAVE list=%p", *list));
+}
+
+bool nsProtocolProxyService::GetIsPACLoading() {
+ return mPACMan && mPACMan->IsLoading();
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::AddProxyConfigCallback(
+ nsIProxyConfigChangedCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aCallback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mProxyConfigChangedCallbacks.AppendElement(aCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RemoveProxyConfigCallback(
+ nsIProxyConfigChangedCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mProxyConfigChangedCallbacks.RemoveElement(aCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NotifyProxyConfigChangedInternal() {
+ LOG(("nsProtocolProxyService::NotifyProxyConfigChangedInternal"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (const auto& callback : mProxyConfigChangedCallbacks) {
+ callback->OnProxyConfigChanged();
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h
new file mode 100644
index 0000000000..394450ca6e
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -0,0 +1,420 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProtocolProxyService_h__
+#define nsProtocolProxyService_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIProtocolProxyFilter.h"
+#include "nsIProxyInfo.h"
+#include "nsIObserver.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsITimer.h"
+#include "prio.h"
+#include "mozilla/Attributes.h"
+
+class nsIPrefBranch;
+class nsISystemProxySettings;
+
+namespace mozilla {
+namespace net {
+
+using nsFailedProxyTable = nsTHashMap<nsCStringHashKey, uint32_t>;
+
+class nsPACMan;
+class nsProxyInfo;
+struct nsProtocolInfo;
+
+// CID for the nsProtocolProxyService class
+// 091eedd8-8bae-4fe3-ad62-0c87351e640d
+#define NS_PROTOCOL_PROXY_SERVICE_IMPL_CID \
+ { \
+ 0x091eedd8, 0x8bae, 0x4fe3, { \
+ 0xad, 0x62, 0x0c, 0x87, 0x35, 0x1e, 0x64, 0x0d \
+ } \
+ }
+
+class nsProtocolProxyService final : public nsIProtocolProxyService2,
+ public nsIObserver,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYSERVICE2
+ NS_DECL_NSIPROTOCOLPROXYSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+ nsProtocolProxyService();
+
+ nsresult Init();
+
+ public:
+ // An instance of this struct is allocated for each registered
+ // nsIProtocolProxyFilter and each nsIProtocolProxyChannelFilter.
+ class FilterLink {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(FilterLink)
+
+ uint32_t position;
+ nsCOMPtr<nsIProtocolProxyFilter> filter;
+ nsCOMPtr<nsIProtocolProxyChannelFilter> channelFilter;
+
+ FilterLink(uint32_t p, nsIProtocolProxyFilter* f);
+ FilterLink(uint32_t p, nsIProtocolProxyChannelFilter* cf);
+
+ private:
+ ~FilterLink();
+ };
+
+ protected:
+ friend class nsAsyncResolveRequest;
+ friend class TestProtocolProxyService_LoadHostFilters_Test; // for gtest
+
+ ~nsProtocolProxyService();
+
+ /**
+ * This method is called whenever a preference may have changed or
+ * to initialize all preferences.
+ *
+ * @param prefs
+ * This must be a pointer to the root pref branch.
+ * @param name
+ * This can be the name of a fully-qualified preference, or it can
+ * be null, in which case all preferences will be initialized.
+ */
+ void PrefsChanged(nsIPrefBranch* prefBranch, const char* pref);
+
+ /**
+ * This method is called to create a nsProxyInfo instance from the given
+ * PAC-style proxy string. It parses up to the end of the string, or to
+ * the next ';' character.
+ *
+ * @param proxy
+ * The PAC-style proxy string to parse. This must not be null.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * Upon return this points to a newly allocated nsProxyInfo or null
+ * if the proxy string was invalid.
+ *
+ * @return A pointer beyond the parsed proxy string (never null).
+ */
+ const char* ExtractProxyInfo(const char* start, uint32_t aResolveFlags,
+ nsProxyInfo** result);
+
+ /**
+ * Load the specified PAC file.
+ *
+ * @param pacURI
+ * The URI spec of the PAC file to load.
+ */
+ nsresult ConfigureFromPAC(const nsCString& spec, bool forceReload);
+
+ /**
+ * This method builds a list of nsProxyInfo objects from the given PAC-
+ * style string.
+ *
+ * @param pacString
+ * The PAC-style proxy string to parse. This may be empty.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * The resulting list of proxy info objects.
+ */
+ void ProcessPACString(const nsCString& pacString, uint32_t aResolveFlags,
+ nsIProxyInfo** result);
+
+ /**
+ * This method generates a string valued identifier for the given
+ * nsProxyInfo object.
+ *
+ * @param pi
+ * The nsProxyInfo object from which to generate the key.
+ * @param result
+ * Upon return, this parameter holds the generated key.
+ */
+ void GetProxyKey(nsProxyInfo* pi, nsCString& key);
+
+ /**
+ * @return Seconds since start of session.
+ */
+ uint32_t SecondsSinceSessionStart();
+
+ /**
+ * This method removes the specified proxy from the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to enable.
+ */
+ void EnableProxy(nsProxyInfo* pi);
+
+ /**
+ * This method adds the specified proxy to the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to disable.
+ */
+ void DisableProxy(nsProxyInfo* pi);
+
+ /**
+ * This method tests to see if the given proxy is disabled.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to test.
+ *
+ * @return True if the specified proxy is disabled.
+ */
+ bool IsProxyDisabled(nsProxyInfo* pi);
+
+ /**
+ * This method queries the protocol handler for the given scheme to check
+ * for the protocol flags and default port.
+ *
+ * @param uri
+ * The URI to query.
+ * @param info
+ * Holds information about the protocol upon return. Pass address
+ * of structure when you call this method. This parameter must not
+ * be null.
+ */
+ nsresult GetProtocolInfo(nsIURI* uri, nsProtocolInfo* info);
+
+ /**
+ * This method is an internal version nsIProtocolProxyService::newProxyInfo
+ * that expects a string literal for the type.
+ *
+ * @param type
+ * The proxy type.
+ * @param host
+ * The proxy host name (UTF-8 ok).
+ * @param port
+ * The proxy port number.
+ * @param username
+ * The username for the proxy (ASCII). May be "", but not null.
+ * @param password
+ * The password for the proxy (ASCII). May be "", but not null.
+ * @param flags
+ * The proxy flags (nsIProxyInfo::flags).
+ * @param timeout
+ * The failover timeout for this proxy.
+ * @param next
+ * The next proxy to try if this one fails.
+ * @param aResolveFlags
+ * The flags passed to resolve (from nsIProtocolProxyService).
+ * @param result
+ * The resulting nsIProxyInfo object.
+ */
+ nsresult NewProxyInfo_Internal(const char* type, const nsACString& host,
+ int32_t port, const nsACString& username,
+ const nsACString& password,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey,
+ uint32_t flags, uint32_t timeout,
+ nsIProxyInfo* aFailoverProxy,
+ uint32_t aResolveFlags, nsIProxyInfo** result);
+
+ /**
+ * This method is an internal version of Resolve that does not query PAC.
+ * It performs all of the built-in processing, and reports back to the
+ * caller with either the proxy info result or a flag to instruct the
+ * caller to use PAC instead.
+ *
+ * @param channel
+ * The channel to test.
+ * @param info
+ * Information about the URI's protocol.
+ * @param flags
+ * The flags passed to either the resolve or the asyncResolve method.
+ * @param usePAC
+ * If this flag is set upon return, then PAC should be queried to
+ * resolve the proxy info.
+ * @param result
+ * The resulting proxy info or null.
+ */
+ nsresult Resolve_Internal(nsIChannel* channel, const nsProtocolInfo& info,
+ uint32_t flags, bool* usePAC,
+ nsIProxyInfo** result);
+
+ /**
+ * Shallow copy of the current list of registered filters so that
+ * we can safely let them asynchronously process a single proxy
+ * resolution request.
+ */
+ void CopyFilters(nsTArray<RefPtr<FilterLink>>& aCopy);
+
+ /**
+ * This method applies the provided filter to the given proxy info
+ * list, and expects |callback| be called on (synchronously or
+ * asynchronously) to provide the updated proxyinfo list.
+ */
+ bool ApplyFilter(FilterLink const* filterLink, nsIChannel* channel,
+ const nsProtocolInfo& info, nsCOMPtr<nsIProxyInfo> list,
+ nsIProxyProtocolFilterResult* callback);
+
+ /**
+ * This method prunes out disabled and disallowed proxies from a given
+ * proxy info list.
+ *
+ * @param info
+ * Information about the URI's protocol.
+ * @param proxyInfo
+ * The proxy info list to be modified. This is an inout param.
+ */
+ void PruneProxyInfo(const nsProtocolInfo& info, nsIProxyInfo** list);
+
+ /**
+ * This method is a simple wrapper around PruneProxyInfo that takes the
+ * proxy info list inout param as a nsCOMPtr.
+ */
+ void PruneProxyInfo(const nsProtocolInfo& info,
+ nsCOMPtr<nsIProxyInfo>& proxyInfo) {
+ nsIProxyInfo* pi = nullptr;
+ proxyInfo.swap(pi);
+ PruneProxyInfo(info, &pi);
+ proxyInfo.swap(pi);
+ }
+
+ /**
+ * This method populates mHostFiltersArray from the given string.
+ *
+ * @param hostFilters
+ * A "no-proxy-for" exclusion list.
+ */
+ void LoadHostFilters(const nsACString& aFilters);
+
+ /**
+ * This method checks the given URI against mHostFiltersArray.
+ *
+ * @param uri
+ * The URI to test.
+ * @param defaultPort
+ * The default port for the given URI.
+ *
+ * @return True if the URI can use the specified proxy.
+ */
+ bool CanUseProxy(nsIURI* uri, int32_t defaultPort);
+
+ /**
+ * Disable Prefetch in the DNS service if a proxy is in use.
+ *
+ * @param aProxy
+ * The proxy information
+ */
+ void MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy);
+
+ private:
+ nsresult SetupPACThread(
+ nsISerialEventTarget* mainThreadEventTarget = nullptr);
+ nsresult ResetPACThread();
+ nsresult ReloadNetworkPAC();
+
+ nsresult AsyncConfigureFromPAC(bool aForceReload, bool aResetPACThread);
+ nsresult OnAsyncGetPACURI(bool aForceReload, bool aResetPACThread,
+ nsresult aResult, const nsACString& aUri);
+
+ public:
+ // The Sun Forte compiler and others implement older versions of the
+ // C++ standard's rules on access and nested classes. These structs
+ // need to be public in order to deal with those compilers.
+
+ struct HostInfoIP {
+ uint16_t family;
+ uint16_t mask_len;
+ PRIPv6Addr addr; // possibly IPv4-mapped address
+ };
+
+ struct HostInfoName {
+ char* host;
+ uint32_t host_len;
+ };
+
+ protected:
+ // simplified array of filters defined by this struct
+ struct HostInfo {
+ bool is_ipaddr{false};
+ int32_t port{0};
+ // other members intentionally uninitialized
+ union {
+ HostInfoIP ip;
+ HostInfoName name;
+ };
+
+ HostInfo() = default;
+ ~HostInfo() {
+ if (!is_ipaddr && name.host) {
+ free(name.host);
+ }
+ }
+ };
+
+ private:
+ // Private methods to insert and remove FilterLinks from the FilterLink chain.
+ nsresult InsertFilterLink(RefPtr<FilterLink>&& link);
+ nsresult RemoveFilterLink(nsISupports* givenObject);
+
+ protected:
+ // Indicates if local hosts (plain hostnames, no dots) should use the proxy
+ bool mFilterLocalHosts{false};
+
+ // Holds an array of HostInfo objects
+ nsTArray<UniquePtr<HostInfo>> mHostFiltersArray;
+
+ // Filters, always sorted by the position.
+ nsTArray<RefPtr<FilterLink>> mFilters;
+
+ nsTArray<nsCOMPtr<nsIProxyConfigChangedCallback>>
+ mProxyConfigChangedCallbacks;
+
+ uint32_t mProxyConfig{PROXYCONFIG_DIRECT};
+
+ nsCString mHTTPProxyHost;
+ int32_t mHTTPProxyPort{-1};
+
+ nsCString mHTTPSProxyHost;
+ int32_t mHTTPSProxyPort{-1};
+
+ // mSOCKSProxyTarget could be a host, a domain socket path,
+ // or a named-pipe name.
+ nsCString mSOCKSProxyTarget;
+ int32_t mSOCKSProxyPort{-1};
+ int32_t mSOCKSProxyVersion{4};
+ bool mSOCKSProxyRemoteDNS{false};
+ bool mProxyOverTLS{true};
+ bool mWPADOverDHCPEnabled{false};
+
+ RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ PRTime mSessionStart;
+ nsFailedProxyTable mFailedProxies;
+ // 30 minute default
+ int32_t mFailedProxyTimeout{30 * 60};
+
+ private:
+ nsresult AsyncResolveInternal(nsIChannel* channel, uint32_t flags,
+ nsIProtocolProxyCallback* callback,
+ nsICancelable** result, bool isSyncOK,
+ nsISerialEventTarget* mainThreadEventTarget);
+ bool mIsShutdown{false};
+ nsCOMPtr<nsITimer> mReloadPACTimer;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProtocolProxyService,
+ NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsProtocolProxyService_h__
diff --git a/netwerk/base/nsProxyInfo.cpp b/netwerk/base/nsProxyInfo.cpp
new file mode 100644
index 0000000000..6859845739
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsProxyInfo.h"
+
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+// Yes, we support QI to nsProxyInfo
+NS_IMPL_ISUPPORTS(nsProxyInfo, nsProxyInfo, nsIProxyInfo)
+
+// These pointers are declared in nsProtocolProxyService.cpp and
+// comparison of mType by string pointer is valid within necko
+extern const char kProxyType_HTTP[];
+extern const char kProxyType_HTTPS[];
+extern const char kProxyType_SOCKS[];
+extern const char kProxyType_SOCKS4[];
+extern const char kProxyType_SOCKS5[];
+extern const char kProxyType_DIRECT[];
+extern const char kProxyType_PROXY[];
+
+nsProxyInfo::nsProxyInfo(const nsACString& aType, const nsACString& aHost,
+ int32_t aPort, const nsACString& aUsername,
+ const nsACString& aPassword, uint32_t aFlags,
+ uint32_t aTimeout, uint32_t aResolveFlags,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey)
+ : mHost(aHost),
+ mUsername(aUsername),
+ mPassword(aPassword),
+ mProxyAuthorizationHeader(aProxyAuthorizationHeader),
+ mConnectionIsolationKey(aConnectionIsolationKey),
+ mPort(aPort),
+ mFlags(aFlags),
+ mResolveFlags(aResolveFlags),
+ mTimeout(aTimeout),
+ mNext(nullptr) {
+ if (aType.EqualsASCII(kProxyType_HTTP)) {
+ mType = kProxyType_HTTP;
+ } else if (aType.EqualsASCII(kProxyType_HTTPS)) {
+ mType = kProxyType_HTTPS;
+ } else if (aType.EqualsASCII(kProxyType_SOCKS)) {
+ mType = kProxyType_SOCKS;
+ } else if (aType.EqualsASCII(kProxyType_SOCKS4)) {
+ mType = kProxyType_SOCKS4;
+ } else if (aType.EqualsASCII(kProxyType_SOCKS5)) {
+ mType = kProxyType_SOCKS5;
+ } else if (aType.EqualsASCII(kProxyType_PROXY)) {
+ mType = kProxyType_PROXY;
+ } else {
+ mType = kProxyType_DIRECT;
+ }
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetHost(nsACString& result) {
+ result = mHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPort(int32_t* result) {
+ *result = mPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetType(nsACString& result) {
+ result = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFlags(uint32_t* result) {
+ *result = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetResolveFlags(uint32_t* result) {
+ *result = mResolveFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetUsername(nsACString& result) {
+ result = mUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPassword(nsACString& result) {
+ result = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetProxyAuthorizationHeader(nsACString& result) {
+ result = mProxyAuthorizationHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetConnectionIsolationKey(nsACString& result) {
+ result = mConnectionIsolationKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverTimeout(uint32_t* result) {
+ *result = mTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverProxy(nsIProxyInfo** result) {
+ NS_IF_ADDREF(*result = mNext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::SetFailoverProxy(nsIProxyInfo* proxy) {
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(proxy);
+ NS_ENSURE_ARG(pi);
+
+ pi.swap(mNext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetSourceId(nsACString& result) {
+ result = mSourceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::SetSourceId(const nsACString& sourceId) {
+ mSourceId = sourceId;
+ return NS_OK;
+}
+
+bool nsProxyInfo::IsDirect() {
+ if (!mType) return true;
+ return mType == kProxyType_DIRECT;
+}
+
+bool nsProxyInfo::IsHTTP() { return mType == kProxyType_HTTP; }
+
+bool nsProxyInfo::IsHTTPS() { return mType == kProxyType_HTTPS; }
+
+bool nsProxyInfo::IsSOCKS() {
+ return mType == kProxyType_SOCKS || mType == kProxyType_SOCKS4 ||
+ mType == kProxyType_SOCKS5;
+}
+
+/* static */
+void nsProxyInfo::SerializeProxyInfo(nsProxyInfo* aProxyInfo,
+ nsTArray<ProxyInfoCloneArgs>& aResult) {
+ for (nsProxyInfo* iter = aProxyInfo; iter; iter = iter->mNext) {
+ ProxyInfoCloneArgs* arg = aResult.AppendElement();
+ arg->type() = nsCString(iter->Type());
+ arg->host() = iter->Host();
+ arg->port() = iter->Port();
+ arg->username() = iter->Username();
+ arg->password() = iter->Password();
+ arg->proxyAuthorizationHeader() = iter->ProxyAuthorizationHeader();
+ arg->connectionIsolationKey() = iter->ConnectionIsolationKey();
+ arg->flags() = iter->Flags();
+ arg->timeout() = iter->Timeout();
+ arg->resolveFlags() = iter->ResolveFlags();
+ }
+}
+
+/* static */
+nsProxyInfo* nsProxyInfo::DeserializeProxyInfo(
+ const nsTArray<ProxyInfoCloneArgs>& aArgs) {
+ nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr;
+ for (const ProxyInfoCloneArgs& info : aArgs) {
+ pi = new nsProxyInfo(info.type(), info.host(), info.port(), info.username(),
+ info.password(), info.flags(), info.timeout(),
+ info.resolveFlags(), info.proxyAuthorizationHeader(),
+ info.connectionIsolationKey());
+ if (last) {
+ last->mNext = pi;
+ // |mNext| will be released in |last|'s destructor.
+ NS_IF_ADDREF(last->mNext);
+ } else {
+ first = pi;
+ }
+ last = pi;
+ }
+
+ return first;
+}
+
+already_AddRefed<nsProxyInfo> nsProxyInfo::CloneProxyInfoWithNewResolveFlags(
+ uint32_t aResolveFlags) {
+ nsTArray<ProxyInfoCloneArgs> args;
+ SerializeProxyInfo(this, args);
+
+ for (auto& arg : args) {
+ arg.resolveFlags() = aResolveFlags;
+ }
+
+ RefPtr<nsProxyInfo> result = DeserializeProxyInfo(args);
+ return result.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProxyInfo.h b/netwerk/base/nsProxyInfo.h
new file mode 100644
index 0000000000..ca8602edc0
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProxyInfo_h__
+#define nsProxyInfo_h__
+
+#include "nsIProxyInfo.h"
+#include "nsString.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+
+// Use to support QI nsIProxyInfo to nsProxyInfo
+#define NS_PROXYINFO_IID \
+ { /* ed42f751-825e-4cc2-abeb-3670711a8b85 */ \
+ 0xed42f751, 0x825e, 0x4cc2, { \
+ 0xab, 0xeb, 0x36, 0x70, 0x71, 0x1a, 0x8b, 0x85 \
+ } \
+ }
+
+namespace mozilla {
+namespace net {
+
+class ProxyInfoCloneArgs;
+
+// This class is exposed to other classes inside Necko for fast access
+// to the nsIProxyInfo attributes.
+class nsProxyInfo final : public nsIProxyInfo {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROXYINFO_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROXYINFO
+
+ // Cheap accessors for use within Necko
+ const nsCString& Host() const { return mHost; }
+ int32_t Port() const { return mPort; }
+ const char* Type() const { return mType; }
+ uint32_t Flags() const { return mFlags; }
+ const nsCString& Username() const { return mUsername; }
+ const nsCString& Password() const { return mPassword; }
+ uint32_t Timeout() { return mTimeout; }
+ uint32_t ResolveFlags() { return mResolveFlags; }
+ const nsCString& ProxyAuthorizationHeader() const {
+ return mProxyAuthorizationHeader;
+ }
+ const nsCString& ConnectionIsolationKey() const {
+ return mConnectionIsolationKey;
+ }
+
+ bool IsDirect();
+ bool IsHTTP();
+ bool IsHTTPS();
+ bool IsSOCKS();
+
+ static void SerializeProxyInfo(nsProxyInfo* aProxyInfo,
+ nsTArray<ProxyInfoCloneArgs>& aResult);
+ static nsProxyInfo* DeserializeProxyInfo(
+ const nsTArray<ProxyInfoCloneArgs>& aArgs);
+
+ already_AddRefed<nsProxyInfo> CloneProxyInfoWithNewResolveFlags(
+ uint32_t aResolveFlags);
+
+ private:
+ friend class nsProtocolProxyService;
+
+ explicit nsProxyInfo(const char* type = nullptr) : mType(type) {}
+
+ nsProxyInfo(const nsACString& aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aUsername, const nsACString& aPassword,
+ uint32_t aFlags, uint32_t aTimeout, uint32_t aResolveFlags,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey);
+
+ ~nsProxyInfo() { NS_IF_RELEASE(mNext); }
+
+ const char* mType; // pointer to statically allocated value
+ nsCString mHost;
+ nsCString mUsername;
+ nsCString mPassword;
+ nsCString mProxyAuthorizationHeader;
+ nsCString mConnectionIsolationKey;
+ nsCString mSourceId;
+ int32_t mPort{-1};
+ uint32_t mFlags{0};
+ // We need to read on multiple threads, but don't need to sync on anything
+ // else
+ Atomic<uint32_t, Relaxed> mResolveFlags{0};
+ uint32_t mTimeout{UINT32_MAX};
+ nsProxyInfo* mNext{nullptr};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProxyInfo, NS_PROXYINFO_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsProxyInfo_h__
diff --git a/netwerk/base/nsReadLine.h b/netwerk/base/nsReadLine.h
new file mode 100644
index 0000000000..f1e4693f92
--- /dev/null
+++ b/netwerk/base/nsReadLine.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsReadLine_h__
+#define nsReadLine_h__
+
+#include "nsIInputStream.h"
+#include "mozilla/Likely.h"
+
+/**
+ * @file
+ * Functions to read complete lines from an input stream.
+ *
+ * To properly use the helper function in here (NS_ReadLine) the caller should
+ * create a nsLineBuffer<T> with new, and pass it to NS_ReadLine every time it
+ * wants a line out.
+ *
+ * When done, the object should be deleted.
+ */
+
+/**
+ * @internal
+ * Buffer size. This many bytes will be buffered. If a line is longer than this,
+ * the partial line will be appended to the out parameter of NS_ReadLine and the
+ * buffer will be emptied.
+ * Note: if you change this constant, please update the regression test in
+ * netwerk/test/unit/test_readline.js accordingly (bug 397850).
+ */
+#define kLineBufferSize 4096
+
+/**
+ * @internal
+ * Line buffer structure, buffers data from an input stream.
+ * The buffer is empty when |start| == |end|.
+ * Invariant: |start| <= |end|
+ */
+template <typename CharT>
+class nsLineBuffer {
+ public:
+ nsLineBuffer() : start(buf), end(buf) {}
+
+ CharT buf[kLineBufferSize + 1];
+ CharT* start;
+ CharT* end;
+};
+
+/**
+ * Read a line from an input stream. Lines are separated by '\r' (0x0D) or '\n'
+ * (0x0A), or "\r\n" or "\n\r".
+ *
+ * @param aStream
+ * The stream to read from
+ * @param aBuffer
+ * The line buffer to use. A single line buffer must not be used with
+ * different input streams.
+ * @param aLine [out]
+ * The string where the line will be stored.
+ * @param more [out]
+ * Whether more data is available in the buffer. If true, NS_ReadLine may
+ * be called again to read further lines. Otherwise, further calls to
+ * NS_ReadLine will return an error.
+ *
+ * @retval NS_OK
+ * Read successful
+ * @retval error
+ * Input stream returned an error upon read. See
+ * nsIInputStream::read.
+ */
+template <typename CharT, class StreamType, class StringType>
+nsresult NS_ReadLine(StreamType* aStream, nsLineBuffer<CharT>* aBuffer,
+ StringType& aLine, bool* more) {
+ CharT eolchar = 0; // the first eol char or 1 after \r\n or \n\r is found
+
+ aLine.Truncate();
+
+ while (true) { // will be returning out of this loop on eol or eof
+ if (aBuffer->start == aBuffer->end) { // buffer is empty. Read into it.
+ uint32_t bytesRead;
+ nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead);
+ if (NS_FAILED(rv) || MOZ_UNLIKELY(bytesRead == 0)) {
+ *more = false;
+ return rv;
+ }
+ aBuffer->start = aBuffer->buf;
+ aBuffer->end = aBuffer->buf + bytesRead;
+ *(aBuffer->end) = '\0';
+ }
+
+ /*
+ * Walk the buffer looking for an end-of-line.
+ * There are 3 cases to consider:
+ * 1. the eol char is the last char in the buffer
+ * 2. the eol char + one more char at the end of the buffer
+ * 3. the eol char + two or more chars at the end of the buffer
+ * we need at least one char after the first eol char to determine if
+ * it's a \r\n or \n\r sequence (and skip over it), and we need one
+ * more char after the end-of-line to set |more| correctly.
+ */
+ CharT* current = aBuffer->start;
+ if (MOZ_LIKELY(eolchar == 0)) {
+ for (; current < aBuffer->end; ++current) {
+ if (*current == '\n' || *current == '\r') {
+ eolchar = *current;
+ *current++ = '\0';
+ aLine.Append(aBuffer->start);
+ break;
+ }
+ }
+ }
+ if (MOZ_LIKELY(eolchar != 0)) {
+ for (; current < aBuffer->end; ++current) {
+ if ((eolchar == '\r' && *current == '\n') ||
+ (eolchar == '\n' && *current == '\r')) {
+ eolchar = 1;
+ continue;
+ }
+ aBuffer->start = current;
+ *more = true;
+ return NS_OK;
+ }
+ }
+
+ if (eolchar == 0) aLine.Append(aBuffer->start);
+ aBuffer->start = aBuffer->end; // mark the buffer empty
+ }
+}
+
+#endif // nsReadLine_h__
diff --git a/netwerk/base/nsRedirectHistoryEntry.cpp b/netwerk/base/nsRedirectHistoryEntry.cpp
new file mode 100644
index 0000000000..da1a18cb95
--- /dev/null
+++ b/netwerk/base/nsRedirectHistoryEntry.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRedirectHistoryEntry.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsRedirectHistoryEntry, nsIRedirectHistoryEntry)
+
+nsRedirectHistoryEntry::nsRedirectHistoryEntry(nsIPrincipal* aPrincipal,
+ nsIURI* aReferrer,
+ const nsACString& aRemoteAddress)
+ : mPrincipal(aPrincipal),
+ mReferrer(aReferrer),
+ mRemoteAddress(aRemoteAddress) {}
+
+NS_IMETHODIMP
+nsRedirectHistoryEntry::GetRemoteAddress(nsACString& result) {
+ result = mRemoteAddress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRedirectHistoryEntry::GetReferrerURI(nsIURI** referrer) {
+ *referrer = do_AddRef(mReferrer).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRedirectHistoryEntry::GetPrincipal(nsIPrincipal** principal) {
+ *principal = do_AddRef(mPrincipal).take();
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsRedirectHistoryEntry.h b/netwerk/base/nsRedirectHistoryEntry.h
new file mode 100644
index 0000000000..bc916062a8
--- /dev/null
+++ b/netwerk/base/nsRedirectHistoryEntry.h
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRedirectHistoryEntry_h__
+#define nsRedirectHistoryEntry_h__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIRedirectHistoryEntry.h"
+
+class nsIURI;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace net {
+
+class nsRedirectHistoryEntry final : public nsIRedirectHistoryEntry {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREDIRECTHISTORYENTRY
+
+ nsRedirectHistoryEntry(nsIPrincipal* aPrincipal, nsIURI* aReferrer,
+ const nsACString& aRemoteAddress);
+
+ private:
+ ~nsRedirectHistoryEntry() = default;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIURI> mReferrer;
+ nsCString mRemoteAddress;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsRedirectHistoryEntry_h__
diff --git a/netwerk/base/nsRequestObserverProxy.cpp b/netwerk/base/nsRequestObserverProxy.cpp
new file mode 100644
index 0000000000..fda235488a
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nscore.h"
+#include "nsRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "mozilla/Logging.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRequestObserverProxyLog("nsRequestObserverProxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gRequestObserverProxyLog, LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsARequestObserverEvent internal class...
+//-----------------------------------------------------------------------------
+
+nsARequestObserverEvent::nsARequestObserverEvent(nsIRequest* request)
+ : Runnable("net::nsARequestObserverEvent"), mRequest(request) {
+ MOZ_ASSERT(mRequest, "null pointer");
+}
+
+//-----------------------------------------------------------------------------
+// nsOnStartRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStartRequestEvent : public nsARequestObserverEvent {
+ RefPtr<nsRequestObserverProxy> mProxy;
+
+ public:
+ nsOnStartRequestEvent(nsRequestObserverProxy* proxy, nsIRequest* request)
+ : nsARequestObserverEvent(request), mProxy(proxy) {
+ MOZ_ASSERT(mProxy, "null pointer");
+ }
+
+ NS_IMETHOD Run() override {
+ LOG(("nsOnStartRequestEvent::HandleEvent [req=%p]\n", mRequest.get()));
+
+ if (!mProxy->mObserver) {
+ MOZ_ASSERT_UNREACHABLE(
+ "already handled onStopRequest event "
+ "(observer is null)");
+ return NS_OK;
+ }
+
+ LOG(("handle startevent=%p\n", this));
+ nsresult rv = mProxy->mObserver->OnStartRequest(mRequest);
+ if (NS_FAILED(rv)) {
+ LOG(("OnStartRequest failed [rv=%" PRIx32 "] canceling request!\n",
+ static_cast<uint32_t>(rv)));
+ rv = mRequest->Cancel(rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed for request!");
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ virtual ~nsOnStartRequestEvent() = default;
+};
+
+//-----------------------------------------------------------------------------
+// nsOnStopRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStopRequestEvent : public nsARequestObserverEvent {
+ RefPtr<nsRequestObserverProxy> mProxy;
+
+ public:
+ nsOnStopRequestEvent(nsRequestObserverProxy* proxy, nsIRequest* request)
+ : nsARequestObserverEvent(request), mProxy(proxy) {
+ MOZ_ASSERT(mProxy, "null pointer");
+ }
+
+ NS_IMETHOD Run() override {
+ LOG(("nsOnStopRequestEvent::HandleEvent [req=%p]\n", mRequest.get()));
+
+ nsMainThreadPtrHandle<nsIRequestObserver> observer = mProxy->mObserver;
+ if (!observer) {
+ MOZ_ASSERT_UNREACHABLE(
+ "already handled onStopRequest event "
+ "(observer is null)");
+ return NS_OK;
+ }
+ // Do not allow any more events to be handled after OnStopRequest
+ mProxy->mObserver = nullptr;
+
+ nsresult status = NS_OK;
+ DebugOnly<nsresult> rv = mRequest->GetStatus(&status);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetStatus failed for request!");
+
+ LOG(("handle stopevent=%p\n", this));
+ (void)observer->OnStopRequest(mRequest, status);
+
+ return NS_OK;
+ }
+
+ private:
+ virtual ~nsOnStopRequestEvent() = default;
+};
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsISupports implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsRequestObserverProxy, nsIRequestObserver,
+ nsIRequestObserverProxy)
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserver implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStartRequest(nsIRequest* request) {
+ LOG(("nsRequestObserverProxy::OnStartRequest [this=%p req=%p]\n", this,
+ request));
+
+ RefPtr<nsOnStartRequestEvent> ev = new nsOnStartRequestEvent(this, request);
+
+ LOG(("post startevent=%p\n", ev.get()));
+ return FireEvent(ev);
+}
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStopRequest(nsIRequest* request, nsresult status) {
+ LOG(("nsRequestObserverProxy: OnStopRequest [this=%p req=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(status)));
+
+ // The status argument is ignored because, by the time the OnStopRequestEvent
+ // is actually processed, the status of the request may have changed :-(
+ // To make sure that an accurate status code is always used, GetStatus() is
+ // called when the OnStopRequestEvent is actually processed (see above).
+
+ RefPtr<nsOnStopRequestEvent> ev = new nsOnStopRequestEvent(this, request);
+
+ LOG(("post stopevent=%p\n", ev.get()));
+ return FireEvent(ev);
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::Init(nsIRequestObserver* observer,
+ nsISupports* context) {
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = new nsMainThreadPtrHolder<nsIRequestObserver>(
+ "nsRequestObserverProxy::mObserver", observer);
+ mContext = new nsMainThreadPtrHolder<nsISupports>(
+ "nsRequestObserverProxy::mContext", context);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+nsresult nsRequestObserverProxy::FireEvent(nsARequestObserverEvent* event) {
+ nsCOMPtr<nsIEventTarget> mainThread(GetMainThreadSerialEventTarget());
+ return mainThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsRequestObserverProxy.h b/netwerk/base/nsRequestObserverProxy.h
new file mode 100644
index 0000000000..e1ccec0c4c
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRequestObserverProxy_h__
+#define nsRequestObserverProxy_h__
+
+#include "nsIRequestObserver.h"
+#include "nsIRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+class nsARequestObserverEvent;
+
+class nsRequestObserverProxy final : public nsIRequestObserverProxy {
+ ~nsRequestObserverProxy() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIREQUESTOBSERVERPROXY
+
+ nsRequestObserverProxy() = default;
+
+ nsIRequestObserver* Observer() { return mObserver; }
+
+ nsresult FireEvent(nsARequestObserverEvent*);
+
+ protected:
+ nsMainThreadPtrHandle<nsIRequestObserver> mObserver;
+ nsMainThreadPtrHandle<nsISupports> mContext;
+
+ friend class nsOnStartRequestEvent;
+ friend class nsOnStopRequestEvent;
+};
+
+class nsARequestObserverEvent : public Runnable {
+ public:
+ explicit nsARequestObserverEvent(nsIRequest*);
+
+ protected:
+ virtual ~nsARequestObserverEvent() = default;
+
+ nsCOMPtr<nsIRequest> mRequest;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsRequestObserverProxy_h__
diff --git a/netwerk/base/nsSerializationHelper.cpp b/netwerk/base/nsSerializationHelper.cpp
new file mode 100644
index 0000000000..6e2efb7c57
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.cpp
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSerializationHelper.h"
+
+#include "mozilla/Base64.h"
+#include "nsISerializable.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsString.h"
+#include "nsBase64Encoder.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringStream.h"
+
+using namespace mozilla;
+
+nsresult NS_SerializeToString(nsISerializable* obj, nsACString& str) {
+ RefPtr<nsBase64Encoder> stream(new nsBase64Encoder());
+ if (!stream) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIObjectOutputStream> objstream = NS_NewObjectOutputStream(stream);
+ nsresult rv =
+ objstream->WriteCompoundObject(obj, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return stream->Finish(str);
+}
+
+nsresult NS_DeserializeObject(const nsACString& str, nsISupports** obj) {
+ nsCString decodedData;
+ nsresult rv = Base64Decode(str, decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stream), std::move(decodedData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObjectInputStream> objstream = NS_NewObjectInputStream(stream);
+ return objstream->ReadObject(true, obj);
+}
+
+NS_IMPL_ISUPPORTS(nsSerializationHelper, nsISerializationHelper)
+
+NS_IMETHODIMP
+nsSerializationHelper::SerializeToString(nsISerializable* serializable,
+ nsACString& _retval) {
+ return NS_SerializeToString(serializable, _retval);
+}
+
+NS_IMETHODIMP
+nsSerializationHelper::DeserializeObject(const nsACString& input,
+ nsISupports** _retval) {
+ return NS_DeserializeObject(input, _retval);
+}
diff --git a/netwerk/base/nsSerializationHelper.h b/netwerk/base/nsSerializationHelper.h
new file mode 100644
index 0000000000..4e21a4c2df
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.h
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** @file
+ * Helper functions for (de)serializing objects to/from ASCII strings.
+ */
+
+#ifndef NSSERIALIZATIONHELPER_H_
+#define NSSERIALIZATIONHELPER_H_
+
+#include "nsStringFwd.h"
+#include "nsISerializationHelper.h"
+#include "mozilla/Attributes.h"
+
+class nsISerializable;
+
+/**
+ * Serialize an object to an ASCII string.
+ */
+nsresult NS_SerializeToString(nsISerializable* obj, nsACString& str);
+
+/**
+ * Deserialize an object.
+ */
+nsresult NS_DeserializeObject(const nsACString& str, nsISupports** obj);
+
+class nsSerializationHelper final : public nsISerializationHelper {
+ ~nsSerializationHelper() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERIALIZATIONHELPER
+};
+
+#endif
diff --git a/netwerk/base/nsServerSocket.cpp b/netwerk/base/nsServerSocket.cpp
new file mode 100644
index 0000000000..cf8fc7b619
--- /dev/null
+++ b/netwerk/base/nsServerSocket.cpp
@@ -0,0 +1,582 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransport2.h"
+#include "nsServerSocket.h"
+#include "nsProxyRelease.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Unused.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#if defined(XP_WIN)
+# include "private/pprio.h"
+# include <winsock2.h>
+# include <mstcpip.h>
+
+# ifndef IPV6_V6ONLY
+# define IPV6_V6ONLY 27
+# endif
+
+#endif
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+using nsServerSocketFunc = void (nsServerSocket::*)();
+
+static nsresult PostEvent(nsServerSocket* s, nsServerSocketFunc func) {
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod("net::PostEvent", s, func);
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket
+//-----------------------------------------------------------------------------
+
+nsServerSocket::nsServerSocket() {
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService) {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ }
+ // make sure the STS sticks around as long as we do
+ NS_IF_ADDREF(gSocketTransportService);
+}
+
+nsServerSocket::~nsServerSocket() {
+ Close(); // just in case :)
+
+ // release our reference to the STS
+ nsSocketTransportService* serv = gSocketTransportService;
+ NS_IF_RELEASE(serv);
+}
+
+void nsServerSocket::OnMsgClose() {
+ SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then we'll close the socket in our OnSocketDetached.
+ // otherwise, call OnSocketDetached from here.
+ if (!mAttached) OnSocketDetached(mFD);
+}
+
+void nsServerSocket::OnMsgAttach() {
+ SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition)) {
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult nsServerSocket::TryAttach() {
+ nsresult rv;
+
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "net::nsServerSocket::OnMsgAttach", this, &nsServerSocket::OnMsgAttach);
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv)) return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+void nsServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr) {
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ nsresult rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ mListener->OnSocketAccepted(this, trans);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void nsServerSocket::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ PRFileDesc* clientFD;
+ PRNetAddr prClientAddr;
+
+ // NSPR doesn't tell us the peer address's length (as provided by the
+ // 'accept' system call), so we can't distinguish between named,
+ // unnamed, and abstract peer addresses. Clear prClientAddr first, so
+ // that the path will at least be reliably empty for unnamed and
+ // abstract addresses, and not garbage when the peer is unnamed.
+ memset(&prClientAddr, 0, sizeof(prClientAddr));
+
+ clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT);
+ if (!clientFD) {
+ NS_WARNING("PR_Accept failed");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+ PR_SetFDInheritable(clientFD, false);
+
+ NetAddr clientAddr(&prClientAddr);
+ // Accept succeeded, create socket transport and notify consumer
+ CreateClientTransport(clientFD, clientAddr);
+}
+
+void nsServerSocket::OnSocketDetached(PRFileDesc* fd) {
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition)) mCondition = NS_ERROR_ABORT;
+
+ if (mFD) {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+
+ if (mListener) {
+ mListener->OnStopListening(this, mCondition);
+
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIServerSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = ToRefPtr(std::move(mListener));
+ }
+
+ // XXX we need to proxy the release to the listener's target thread to work
+ // around bug 337492.
+ if (listener) {
+ NS_ProxyRelease("nsServerSocket::mListener", mListenerTarget,
+ listener.forget());
+ }
+ }
+}
+
+void nsServerSocket::IsLocal(bool* aIsLocal) {
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mAddr.raw.family == PR_AF_LOCAL) {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ // If bound to loopback, this server socket only accepts local connections.
+ *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback);
+}
+
+void nsServerSocket::KeepWhenOffline(bool* aKeepWhenOffline) {
+ *aKeepWhenOffline = mKeepWhenOffline;
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket)
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsIServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) {
+ return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0,
+ aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitIPv6(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) {
+ PRNetAddrValue val;
+ PRNetAddr addr;
+
+ if (aPort < 0) {
+ aPort = 0;
+ }
+ if (aLoopbackOnly) {
+ val = PR_IpAddrLoopback;
+ } else {
+ val = PR_IpAddrAny;
+ }
+ PR_SetNetAddr(val, PR_AF_INET6, aPort, &addr);
+
+ mKeepWhenOffline = false;
+ return InitWithAddress(&addr, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitDualStack(int32_t aPort, int32_t aBackLog) {
+ if (aPort < 0) {
+ aPort = 0;
+ }
+ PRNetAddr addr;
+ PR_SetNetAddr(PR_IpAddrAny, PR_AF_INET6, aPort, &addr);
+ return InitWithAddressInternal(&addr, aBackLog, true);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithFilename(nsIFile* aPath, uint32_t aPermissions,
+ int32_t aBacklog) {
+#if defined(XP_UNIX)
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // Create a Unix domain PRNetAddr referring to the given path.
+ PRNetAddr addr;
+ if (path.Length() > sizeof(addr.local.path) - 1) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+ addr.local.family = PR_AF_LOCAL;
+ memcpy(addr.local.path, path.get(), path.Length());
+ addr.local.path[path.Length()] = '\0';
+
+ rv = InitWithAddress(&addr, aBacklog);
+ if (NS_FAILED(rv)) return rv;
+
+ return aPath->SetPermissions(aPermissions);
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithAbstractAddress(const nsACString& aName,
+ int32_t aBacklog) {
+ // Abstract socket address is supported on Linux and Android only.
+ // If not Linux, we should return error.
+#if defined(XP_LINUX)
+ // Create an abstract socket address PRNetAddr referring to the name
+ PRNetAddr addr;
+ if (aName.Length() > sizeof(addr.local.path) - 2) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+ addr.local.family = PR_AF_LOCAL;
+ addr.local.path[0] = 0;
+ memcpy(addr.local.path + 1, aName.BeginReading(), aName.Length());
+ addr.local.path[aName.Length() + 1] = 0;
+
+ return InitWithAddress(&addr, aBacklog);
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags,
+ int32_t aBackLog) {
+ PRNetAddrValue val;
+ PRNetAddr addr;
+
+ if (aPort < 0) aPort = 0;
+ if (aFlags & nsIServerSocket::LoopbackOnly) {
+ val = PR_IpAddrLoopback;
+ } else {
+ val = PR_IpAddrAny;
+ }
+ PR_SetNetAddr(val, PR_AF_INET, aPort, &addr);
+
+ mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0);
+ return InitWithAddress(&addr, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithAddress(const PRNetAddr* aAddr, int32_t aBackLog) {
+ return InitWithAddressInternal(aAddr, aBackLog);
+}
+
+nsresult nsServerSocket::InitWithAddressInternal(const PRNetAddr* aAddr,
+ int32_t aBackLog,
+ bool aDualStack) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ nsresult rv;
+
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenTCPSocket(aAddr->raw.family);
+ if (!mFD) {
+ NS_WARNING("unable to create server socket");
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+
+#if defined(XP_WIN)
+ // https://docs.microsoft.com/en-us/windows/win32/winsock/dual-stack-sockets
+ // To create a Dual-Stack Socket, we have to disable IPV6_V6ONLY.
+ if (aDualStack) {
+ PROsfd osfd = PR_FileDesc2NativeHandle(mFD);
+ if (osfd != -1) {
+ int disable = 0;
+ setsockopt(osfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&disable,
+ sizeof(disable));
+ }
+ }
+#else
+ mozilla::Unused << aDualStack;
+#endif
+
+ PR_SetFDInheritable(mFD, false);
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ if (PR_Bind(mFD, aAddr) != PR_SUCCESS) {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ if (aBackLog < 0) aBackLog = 5; // seems like a reasonable default
+
+ if (PR_Listen(mFD, aBackLog) != PR_SUCCESS) {
+ NS_WARNING("cannot listen on socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS) {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ // Set any additional socket defaults needed by child classes
+ rv = SetSocketDefaults();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ goto fail;
+ }
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ rv = ErrorAccordingToNSPR(PR_GetError());
+ Close();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsServerSocket::Close() {
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener) {
+ if (mFD) {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsServerSocket::OnMsgClose);
+}
+
+namespace {
+
+class ServerSocketListenerProxy final : public nsIServerSocketListener {
+ ~ServerSocketListenerProxy() = default;
+
+ public:
+ explicit ServerSocketListenerProxy(nsIServerSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIServerSocketListener>(
+ "ServerSocketListenerProxy::mListener", aListener)),
+ mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ class OnSocketAcceptedRunnable : public Runnable {
+ public:
+ OnSocketAcceptedRunnable(
+ const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ, nsISocketTransport* aTransport)
+ : Runnable("net::ServerSocketListenerProxy::OnSocketAcceptedRunnable"),
+ mListener(aListener),
+ mServ(aServ),
+ mTransport(aTransport) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ };
+
+ class OnStopListeningRunnable : public Runnable {
+ public:
+ OnStopListeningRunnable(
+ const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ, nsresult aStatus)
+ : Runnable("net::ServerSocketListenerProxy::OnStopListeningRunnable"),
+ mListener(aListener),
+ mServ(aServ),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsresult mStatus;
+ };
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(ServerSocketListenerProxy, nsIServerSocketListener)
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ,
+ nsISocketTransport* aTransport) {
+ RefPtr<OnSocketAcceptedRunnable> r =
+ new OnSocketAcceptedRunnable(mListener, aServ, aTransport);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ,
+ nsresult aStatus) {
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aServ, aStatus);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run() {
+ mListener->OnSocketAccepted(mServ, mTransport);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListeningRunnable::Run() {
+ mListener->OnStopListening(mServ, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsServerSocket::AsyncListen(nsIServerSocketListener* aListener) {
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListener = new ServerSocketListenerProxy(aListener);
+ mListenerTarget = GetCurrentSerialEventTarget();
+ }
+
+ // Child classes may need to do additional setup just before listening begins
+ nsresult rv = OnSocketListen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return PostEvent(this, &nsServerSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetPort(int32_t* aResult) {
+ // no need to enter the lock here
+ uint16_t port;
+ if (mAddr.raw.family == PR_AF_INET) {
+ port = mAddr.inet.port;
+ } else if (mAddr.raw.family == PR_AF_INET6) {
+ port = mAddr.ipv6.port;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = static_cast<int32_t>(NetworkEndian::readUint16(&port));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetAddress(PRNetAddr* aResult) {
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsServerSocket.h b/netwerk/base/nsServerSocket.h
new file mode 100644
index 0000000000..76908697fe
--- /dev/null
+++ b/netwerk/base/nsServerSocket.h
@@ -0,0 +1,70 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsServerSocket_h__
+#define nsServerSocket_h__
+
+#include "prio.h"
+#include "nsASocketHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIServerSocket.h"
+#include "mozilla/Mutex.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIEventTarget;
+namespace mozilla {
+namespace net {
+union NetAddr;
+
+class nsServerSocket : public nsASocketHandler, public nsIServerSocket {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+ virtual void KeepWhenOffline(bool* aKeepWhenOffline) override;
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+ nsServerSocket();
+
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const mozilla::net::NetAddr& clientAddr);
+ virtual nsresult SetSocketDefaults() { return NS_OK; }
+ virtual nsresult OnSocketListen() { return NS_OK; }
+
+ protected:
+ virtual ~nsServerSocket();
+ PRFileDesc* mFD{nullptr};
+ nsCOMPtr<nsIServerSocketListener> mListener;
+
+ private:
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ nsresult InitWithAddressInternal(const PRNetAddr* aAddr, int32_t aBackLog,
+ bool aDualStack = false);
+
+ // lock protects access to mListener; so it is not cleared while being used.
+ mozilla::Mutex mLock MOZ_UNANNOTATED{"nsServerSocket.mLock"};
+ PRNetAddr mAddr = {.raw = {0, {0}}};
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached{false};
+ bool mKeepWhenOffline{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+//-----------------------------------------------------------------------------
+
+#endif // nsServerSocket_h__
diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp
new file mode 100644
index 0000000000..54f57b4625
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIClassInfoImpl.h"
+#include "nsSimpleNestedURI.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+
+#include "mozilla/ipc/URIUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_CLASSINFO(nsSimpleNestedURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_SIMPLENESTEDURI_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleNestedURI)
+
+NS_IMPL_ADDREF_INHERITED(nsSimpleNestedURI, nsSimpleURI)
+NS_IMPL_RELEASE_INHERITED(nsSimpleNestedURI, nsSimpleURI)
+NS_IMPL_QUERY_INTERFACE_CI_INHERITED(nsSimpleNestedURI, nsSimpleURI,
+ nsINestedURI)
+
+nsSimpleNestedURI::nsSimpleNestedURI(nsIURI* innerURI) : mInnerURI(innerURI) {
+ NS_ASSERTION(innerURI, "Must have inner URI");
+}
+
+nsresult nsSimpleNestedURI::SetPathQueryRef(const nsACString& aPathQueryRef) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv =
+ NS_MutateURI(mInnerURI).SetPathQueryRef(aPathQueryRef).Finalize(inner);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsSimpleURI::SetPathQueryRef(aPathQueryRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the regular SetPathQueryRef worked, also set it on the inner URI
+ mInnerURI = inner;
+ return NS_OK;
+}
+
+nsresult nsSimpleNestedURI::SetQuery(const nsACString& aQuery) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = NS_MutateURI(mInnerURI).SetQuery(aQuery).Finalize(inner);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsSimpleURI::SetQuery(aQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the regular SetQuery worked, also set it on the inner URI
+ mInnerURI = inner;
+ return NS_OK;
+}
+
+nsresult nsSimpleNestedURI::SetRef(const nsACString& aRef) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = NS_MutateURI(mInnerURI).SetRef(aRef).Finalize(inner);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsSimpleURI::SetRef(aRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the regular SetRef worked, also set it on the inner URI
+ mInnerURI = inner;
+ return NS_OK;
+}
+
+// nsISerializable
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsSimpleNestedURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsresult rv = nsSimpleURI::ReadPrivate(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) return rv;
+
+ mInnerURI = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Write(nsIObjectOutputStream* aStream) {
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(mInnerURI);
+ if (!serializable) {
+ // We can't serialize ourselves
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = nsSimpleURI::Write(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteCompoundObject(mInnerURI, NS_GET_IID(nsIURI), true);
+ return rv;
+}
+
+NS_IMETHODIMP_(void)
+nsSimpleNestedURI::Serialize(mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ SimpleNestedURIParams params;
+ URIParams simpleParams;
+
+ nsSimpleURI::Serialize(simpleParams);
+ params.simpleParams() = simpleParams;
+
+ SerializeURI(mInnerURI, params.innerURI());
+
+ aParams = params;
+}
+
+bool nsSimpleNestedURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TSimpleNestedURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleNestedURIParams& params = aParams.get_SimpleNestedURIParams();
+ if (!nsSimpleURI::Deserialize(params.simpleParams())) return false;
+
+ mInnerURI = DeserializeURI(params.innerURI());
+ return true;
+}
+
+// nsINestedURI
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnerURI(nsIURI** aURI) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> uri = mInnerURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnermostURI(nsIURI** uri) {
+ return NS_ImplGetInnermostURI(this, uri);
+}
+
+// nsSimpleURI overrides
+/* virtual */
+nsresult nsSimpleNestedURI::EqualsInternal(
+ nsIURI* other, nsSimpleURI::RefHandlingEnum refHandlingMode, bool* result) {
+ *result = false;
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ if (other) {
+ bool correctScheme;
+ nsresult rv = other->SchemeIs(mScheme.get(), &correctScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (correctScheme) {
+ nsCOMPtr<nsINestedURI> nest = do_QueryInterface(other);
+ if (nest) {
+ nsCOMPtr<nsIURI> otherInner;
+ rv = nest->GetInnerURI(getter_AddRefs(otherInner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return (refHandlingMode == eHonorRef)
+ ? otherInner->Equals(mInnerURI, result)
+ : otherInner->EqualsExceptRef(mInnerURI, result);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/* virtual */
+nsSimpleURI* nsSimpleNestedURI::StartClone(
+ nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) {
+ NS_ENSURE_TRUE(mInnerURI, nullptr);
+
+ nsCOMPtr<nsIURI> innerClone;
+ nsresult rv = NS_OK;
+ if (refHandlingMode == eHonorRef) {
+ innerClone = mInnerURI;
+ } else if (refHandlingMode == eReplaceRef) {
+ rv = NS_GetURIWithNewRef(mInnerURI, newRef, getter_AddRefs(innerClone));
+ } else {
+ rv = NS_GetURIWithoutRef(mInnerURI, getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsSimpleNestedURI* url = new nsSimpleNestedURI(innerClone);
+ SetRefOnClone(url, refHandlingMode, newRef);
+
+ return url;
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleNestedURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable,
+ nsINestedURIMutator)
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsSimpleNestedURI::Mutator> mutator = new nsSimpleNestedURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleNestedURI.h b/netwerk/base/nsSimpleNestedURI.h
new file mode 100644
index 0000000000..0887431421
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * URI class to be used for cases when a simple URI actually resolves to some
+ * other sort of URI, with the latter being what's loaded when the load
+ * happens.
+ */
+
+#ifndef nsSimpleNestedURI_h__
+#define nsSimpleNestedURI_h__
+
+#include "nsCOMPtr.h"
+#include "nsSimpleURI.h"
+#include "nsINestedURI.h"
+#include "nsIURIMutator.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleNestedURI : public nsSimpleURI, public nsINestedURI {
+ protected:
+ nsSimpleNestedURI() = default;
+ explicit nsSimpleNestedURI(nsIURI* innerURI);
+
+ ~nsSimpleNestedURI() = default;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINESTEDURI
+
+ // Overrides for various methods nsSimpleURI implements follow.
+
+ // nsSimpleURI overrides
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result) override;
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) override;
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override;
+ NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override;
+
+ // nsISerializable overrides
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;
+
+ protected:
+ nsCOMPtr<nsIURI> mInnerURI;
+
+ nsresult SetPathQueryRef(const nsACString& aPathQueryRef) override;
+ nsresult SetQuery(const nsACString& aQuery) override;
+ nsresult SetRef(const nsACString& aRef) override;
+ bool Deserialize(const mozilla::ipc::URIParams&);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsSimpleNestedURI>,
+ public nsISerializable,
+ public nsINestedURIMutator {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ [[nodiscard]] NS_IMETHOD Deserialize(
+ const mozilla::ipc::URIParams& aParams) override {
+ return InitFromIPCParams(aParams);
+ }
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ NS_ADDREF(*aMutator = this);
+ }
+ return InitFromSpec(aSpec);
+ }
+
+ [[nodiscard]] NS_IMETHOD Init(nsIURI* innerURI) override {
+ mURI = new nsSimpleNestedURI(innerURI);
+ return NS_OK;
+ }
+
+ friend class nsSimpleNestedURI;
+ };
+
+ friend BaseURIMutator<nsSimpleNestedURI>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsSimpleNestedURI_h__ */
diff --git a/netwerk/base/nsSimpleStreamListener.cpp b/netwerk/base/nsSimpleStreamListener.cpp
new file mode 100644
index 0000000000..beed8a9f40
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSimpleStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+//
+//----------------------------------------------------------------------------
+// nsISupports implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMPL_ISUPPORTS(nsSimpleStreamListener, nsISimpleStreamListener,
+ nsIStreamListener, nsIRequestObserver)
+
+//
+//----------------------------------------------------------------------------
+// nsIRequestObserver implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStartRequest(nsIRequest* aRequest) {
+ return mObserver ? mObserver->OnStartRequest(aRequest) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ return mObserver ? mObserver->OnStopRequest(request, aStatus) : NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsIStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aSource,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t writeCount;
+ nsresult rv = mSink->WriteFrom(aSource, aCount, &writeCount);
+ //
+ // Equate zero bytes read and NS_SUCCEEDED to stopping the read.
+ //
+ if (NS_SUCCEEDED(rv) && (writeCount == 0)) return NS_BASE_STREAM_CLOSED;
+ return rv;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsISimpleStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::Init(nsIOutputStream* aSink,
+ nsIRequestObserver* aObserver) {
+ MOZ_ASSERT(aSink, "null output stream");
+
+ mSink = aSink;
+ mObserver = aObserver;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleStreamListener.h b/netwerk/base/nsSimpleStreamListener.h
new file mode 100644
index 0000000000..890b49f61b
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleStreamListener_h__
+#define nsSimpleStreamListener_h__
+
+#include "nsISimpleStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleStreamListener : public nsISimpleStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISIMPLESTREAMLISTENER
+
+ nsSimpleStreamListener() = default;
+
+ protected:
+ virtual ~nsSimpleStreamListener() = default;
+
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsSimpleURI.cpp b/netwerk/base/nsSimpleURI.cpp
new file mode 100644
index 0000000000..56e3300ffe
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.cpp
@@ -0,0 +1,794 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#undef LOG
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsSimpleURI.h"
+#include "nscore.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsEscape.h"
+#include "nsError.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIURIMutator.h"
+#include "mozilla/net/MozURL.h"
+#include "mozilla/StaticPrefs_network.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
+ NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
+
+/* static */
+already_AddRefed<nsSimpleURI> nsSimpleURI::From(nsIURI* aURI) {
+ RefPtr<nsSimpleURI> uri;
+ nsresult rv = aURI->QueryInterface(kThisSimpleURIImplementationCID,
+ getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+NS_IMPL_CLASSINFO(nsSimpleURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_SIMPLEURI_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleURI)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSimpleURI methods:
+
+NS_IMPL_ADDREF(nsSimpleURI)
+NS_IMPL_RELEASE(nsSimpleURI)
+NS_INTERFACE_TABLE_HEAD(nsSimpleURI)
+ NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsISerializable)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_IMPL_QUERY_CLASSINFO(nsSimpleURI)
+ if (aIID.Equals(kThisSimpleURIImplementationCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISerializable methods:
+
+NS_IMETHODIMP
+nsSimpleURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsSimpleURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsresult rv;
+
+ bool isMutable;
+ rv = aStream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) return rv;
+ Unused << isMutable;
+
+ rv = aStream->ReadCString(mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->ReadCString(mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ bool isRefValid;
+ rv = aStream->ReadBoolean(&isRefValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsRefValid = isRefValid;
+
+ if (isRefValid) {
+ rv = aStream->ReadCString(mRef);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+ }
+
+ bool isQueryValid;
+ rv = aStream->ReadBoolean(&isQueryValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsQueryValid = isQueryValid;
+
+ if (isQueryValid) {
+ rv = aStream->ReadCString(mQuery);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Write(nsIObjectOutputStream* aStream) {
+ nsresult rv;
+
+ rv = aStream->WriteBoolean(false); // former mMutable
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mScheme.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mPath.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteBoolean(mIsRefValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsRefValid) {
+ rv = aStream->WriteStringZ(mRef.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = aStream->WriteBoolean(mIsQueryValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsQueryValid) {
+ rv = aStream->WriteStringZ(mQuery.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+void nsSimpleURI::Serialize(URIParams& aParams) {
+ SimpleURIParams params;
+
+ params.scheme() = mScheme;
+ params.path() = mPath;
+
+ if (mIsRefValid) {
+ params.ref() = mRef;
+ } else {
+ params.ref().SetIsVoid(true);
+ }
+
+ if (mIsQueryValid) {
+ params.query() = mQuery;
+ } else {
+ params.query().SetIsVoid(true);
+ }
+
+ aParams = params;
+}
+
+bool nsSimpleURI::Deserialize(const URIParams& aParams) {
+ if (aParams.type() != URIParams::TSimpleURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleURIParams& params = aParams.get_SimpleURIParams();
+
+ mScheme = params.scheme();
+ mPath = params.path();
+
+ if (params.ref().IsVoid()) {
+ mRef.Truncate();
+ mIsRefValid = false;
+ } else {
+ mRef = params.ref();
+ mIsRefValid = true;
+ }
+
+ if (params.query().IsVoid()) {
+ mQuery.Truncate();
+ mIsQueryValid = false;
+ } else {
+ mQuery = params.query();
+ mIsQueryValid = true;
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURI methods:
+
+NS_IMETHODIMP
+nsSimpleURI::GetSpec(nsACString& result) {
+ if (!result.Assign(mScheme, fallible) || !result.Append(":"_ns, fallible) ||
+ !result.Append(mPath, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mIsQueryValid) {
+ if (!result.Append("?"_ns, fallible) || !result.Append(mQuery, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ }
+
+ if (mIsRefValid) {
+ if (!result.Append("#"_ns, fallible) || !result.Append(mRef, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ }
+
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsSimpleURI::GetSpecIgnoringRef(nsACString& result) {
+ result = mScheme + ":"_ns + mPath;
+ if (mIsQueryValid) {
+ result += "?"_ns + mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ return GetSpec(aUnicodeSpec);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
+ return GetHostPort(aUnicodeHostPort);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplayHost(nsACString& aUnicodeHost) {
+ return GetHost(aUnicodeHost);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplayPrePath(nsACString& aPrePath) {
+ return GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasRef(bool* result) {
+ *result = mIsRefValid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasUserPass(bool* result) {
+ *result = false;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetSpecInternal(const nsACString& aSpec,
+ bool aStripWhitespace) {
+ if (StaticPrefs::network_url_max_length() &&
+ aSpec.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsresult rv = net_ExtractURLScheme(aSpec, mScheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString spec;
+ rv = net_FilterAndEscapeURI(
+ aSpec, esc_OnlyNonASCII,
+ aStripWhitespace ? ASCIIMask::MaskWhitespace() : ASCIIMask::MaskCRLFTab(),
+ spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t colonPos = spec.FindChar(':');
+ MOZ_ASSERT(colonPos != kNotFound, "A colon should be in this string");
+ // This sets mPath, mQuery and mRef.
+ return SetPathQueryRefInternal(Substring(spec, colonPos + 1));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetScheme(nsACString& result) {
+ result = mScheme;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetScheme(const nsACString& input) {
+ // Strip tabs, newlines, carriage returns from input
+ nsAutoCString scheme(input);
+ scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ if (!net_IsValidScheme(scheme)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ mScheme = scheme;
+ ToLowerCase(mScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPrePath(nsACString& result) {
+ result = mScheme + ":"_ns;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUserPass(nsACString& result) { return NS_ERROR_FAILURE; }
+
+nsresult nsSimpleURI::SetUserPass(const nsACString& userPass) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUsername(nsACString& result) { return NS_ERROR_FAILURE; }
+
+nsresult nsSimpleURI::SetUsername(const nsACString& userName) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPassword(nsACString& result) { return NS_ERROR_FAILURE; }
+
+nsresult nsSimpleURI::SetPassword(const nsACString& password) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHostPort(nsACString& result) {
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ // Note: If this is changed, change GetAsciiHostPort as well.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSimpleURI::SetHostPort(const nsACString& aValue) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHost(nsACString& result) {
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSimpleURI::SetHost(const nsACString& host) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPort(int32_t* result) {
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSimpleURI::SetPort(int32_t port) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+nsSimpleURI::GetPathQueryRef(nsACString& result) {
+ result = mPath;
+ if (mIsQueryValid) {
+ result += "?"_ns + mQuery;
+ }
+ if (mIsRefValid) {
+ result += "#"_ns + mRef;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetPathQueryRef(const nsACString& aPath) {
+ if (StaticPrefs::network_url_max_length() &&
+ aPath.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString path;
+ nsresult rv = NS_EscapeURL(aPath, esc_OnlyNonASCII, path, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return SetPathQueryRefInternal(path);
+}
+
+nsresult nsSimpleURI::SetPathQueryRefInternal(const nsACString& aPath) {
+ nsresult rv;
+ const auto* start = aPath.BeginReading();
+ const auto* end = aPath.EndReading();
+
+ // Find the first instance of ? or # that marks the end of the path.
+ auto hashOrQueryFilter = [](char c) { return c == '?' || c == '#'; };
+ const auto* pathEnd = std::find_if(start, end, hashOrQueryFilter);
+
+ mIsQueryValid = false;
+ mQuery.Truncate();
+
+ mIsRefValid = false;
+ mRef.Truncate();
+
+ // The path
+ if (!mPath.Assign(Substring(start, pathEnd), fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (pathEnd == end) {
+ return NS_OK;
+ }
+
+ const auto* queryEnd =
+ std::find_if(pathEnd, end, [](char c) { return c == '#'; });
+
+ rv = SetQuery(Substring(pathEnd, queryEnd));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (queryEnd == end) {
+ return NS_OK;
+ }
+
+ return SetRef(Substring(queryEnd, end));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetRef(nsACString& result) {
+ if (!mIsRefValid) {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ result.Truncate();
+ } else {
+ result = mRef;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
+// string (and will result in .spec and .path having a terminal #).
+nsresult nsSimpleURI::SetRef(const nsACString& aRef) {
+ if (StaticPrefs::network_url_max_length() &&
+ aRef.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString ref;
+ nsresult rv =
+ NS_EscapeURL(aRef, esc_OnlyNonASCII | esc_Spaces, ref, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (ref.IsEmpty()) {
+ // Empty string means to remove ref completely.
+ mIsRefValid = false;
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+
+ // Trim trailing invalid chars when ref and query are removed
+ if (mScheme != "javascript" && mRef.IsEmpty() && mQuery.IsEmpty()) {
+ TrimTrailingCharactersFromPath();
+ }
+
+ return NS_OK;
+ }
+
+ mIsRefValid = true;
+
+ // Gracefully skip initial hash
+ if (ref[0] == '#') {
+ mRef = Substring(ref, 1);
+ } else {
+ mRef = ref;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Equals(nsIURI* other, bool* result) {
+ return EqualsInternal(other, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::EqualsExceptRef(nsIURI* other, bool* result) {
+ return EqualsInternal(other, eIgnoreRef, result);
+}
+
+/* virtual */
+nsresult nsSimpleURI::EqualsInternal(
+ nsIURI* other, nsSimpleURI::RefHandlingEnum refHandlingMode, bool* result) {
+ NS_ENSURE_ARG_POINTER(other);
+ MOZ_ASSERT(result, "null pointer");
+
+ RefPtr<nsSimpleURI> otherUri;
+ nsresult rv = other->QueryInterface(kThisSimpleURIImplementationCID,
+ getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ *result = EqualsInternal(otherUri, refHandlingMode);
+ return NS_OK;
+}
+
+bool nsSimpleURI::EqualsInternal(nsSimpleURI* otherUri,
+ RefHandlingEnum refHandlingMode) {
+ bool result = (mScheme == otherUri->mScheme && mPath == otherUri->mPath);
+
+ if (result) {
+ result = (mIsQueryValid == otherUri->mIsQueryValid &&
+ (!mIsQueryValid || mQuery == otherUri->mQuery));
+ }
+
+ if (result && refHandlingMode == eHonorRef) {
+ result = (mIsRefValid == otherUri->mIsRefValid &&
+ (!mIsRefValid || mRef == otherUri->mRef));
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SchemeIs(const char* i_Scheme, bool* o_Equals) {
+ MOZ_ASSERT(o_Equals, "null pointer");
+ if (!i_Scheme) {
+ *o_Equals = false;
+ return NS_OK;
+ }
+
+ const char* this_scheme = mScheme.get();
+
+ // mScheme is guaranteed to be lower case.
+ if (*i_Scheme == *this_scheme || *i_Scheme == (*this_scheme - ('a' - 'A'))) {
+ *o_Equals = nsCRT::strcasecmp(this_scheme, i_Scheme) == 0;
+ } else {
+ *o_Equals = false;
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsSimpleURI* nsSimpleURI::StartClone(
+ nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) {
+ nsSimpleURI* url = new nsSimpleURI();
+ SetRefOnClone(url, refHandlingMode, newRef);
+ return url;
+}
+
+/* virtual */
+void nsSimpleURI::SetRefOnClone(nsSimpleURI* url,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) {
+ if (refHandlingMode == eHonorRef) {
+ url->mRef = mRef;
+ url->mIsRefValid = mIsRefValid;
+ } else if (refHandlingMode == eReplaceRef) {
+ url->SetRef(newRef);
+ }
+}
+
+nsresult nsSimpleURI::Clone(nsIURI** result) {
+ return CloneInternal(eHonorRef, ""_ns, result);
+}
+
+nsresult nsSimpleURI::CloneInternal(
+ nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef,
+ nsIURI** result) {
+ RefPtr<nsSimpleURI> url = StartClone(refHandlingMode, newRef);
+ if (!url) return NS_ERROR_OUT_OF_MEMORY;
+
+ url->mScheme = mScheme;
+ url->mPath = mPath;
+
+ url->mIsQueryValid = mIsQueryValid;
+ if (url->mIsQueryValid) {
+ url->mQuery = mQuery;
+ }
+
+ url.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Resolve(const nsACString& relativePath, nsACString& result) {
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(relativePath, scheme);
+ if (NS_SUCCEEDED(rv)) {
+ result = relativePath;
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = GetAsciiSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If getting the spec fails for some reason, preserve behaviour and just
+ // return the relative path.
+ result = relativePath;
+ return NS_OK;
+ }
+
+ RefPtr<MozURL> url;
+ rv = MozURL::Init(getter_AddRefs(url), spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If parsing the current url fails, we revert to the previous behaviour
+ // and just return the relative path.
+ result = relativePath;
+ return NS_OK;
+ }
+
+ RefPtr<MozURL> url2;
+ rv = MozURL::Init(getter_AddRefs(url2), relativePath, url);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If parsing the relative url fails, we revert to the previous behaviour
+ // and just return the relative path.
+ result = relativePath;
+ return NS_OK;
+ }
+
+ result = url2->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiSpec(nsACString& aResult) {
+ nsresult rv = GetSpec(aResult);
+ if (NS_FAILED(rv)) return rv;
+ MOZ_ASSERT(IsAscii(aResult), "The spec should be ASCII");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHostPort(nsACString& result) {
+ // XXX This behavior mimics GetHostPort.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHost(nsACString& result) {
+ result.Truncate();
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t nsSimpleURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mQuery.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mRef.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetFilePath(nsACString& aFilePath) {
+ aFilePath = mPath;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetFilePath(const nsACString& aFilePath) {
+ if (mPath.IsEmpty() || mPath.First() != '/') {
+ // cannot-be-a-base
+ return NS_ERROR_MALFORMED_URI;
+ }
+ const char* current = aFilePath.BeginReading();
+ const char* end = aFilePath.EndReading();
+
+ // Only go up to the first ? or # symbol
+ for (; current < end; ++current) {
+ if (*current == '?' || *current == '#') {
+ break;
+ }
+ }
+ return SetPathQueryRef(
+ nsDependentCSubstring(aFilePath.BeginReading(), current));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetQuery(nsACString& aQuery) {
+ if (!mIsQueryValid) {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ aQuery.Truncate();
+ } else {
+ aQuery = mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasQuery(bool* result) {
+ *result = mIsQueryValid;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetQuery(const nsACString& aQuery) {
+ if (StaticPrefs::network_url_max_length() &&
+ aQuery.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ nsAutoCString query;
+ nsresult rv = NS_EscapeURL(aQuery, esc_OnlyNonASCII, query, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (query.IsEmpty()) {
+ // Empty string means to remove query completely.
+ mIsQueryValid = false;
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+
+ // Trim trailing invalid chars when ref and query are removed
+ if (mScheme != "javascript" && mRef.IsEmpty() && mQuery.IsEmpty()) {
+ TrimTrailingCharactersFromPath();
+ }
+
+ return NS_OK;
+ }
+
+ mIsQueryValid = true;
+
+ // Gracefully skip initial question mark
+ if (query[0] == '?') {
+ mQuery = Substring(query, 1);
+ } else {
+ mQuery = query;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetQueryWithEncoding(const nsACString& aQuery,
+ const Encoding* aEncoding) {
+ return SetQuery(aQuery);
+}
+
+void nsSimpleURI::TrimTrailingCharactersFromPath() {
+ const auto* start = mPath.BeginReading();
+ const auto* end = mPath.EndReading();
+
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ const auto* newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(start)>(start), charFilter)
+ .base();
+
+ auto trailCount = std::distance(newEnd, end);
+ if (trailCount) {
+ mPath.Truncate(mPath.Length() - trailCount);
+ }
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable,
+ nsISimpleURIMutator)
+
+NS_IMETHODIMP
+nsSimpleURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsSimpleURI::Mutator> mutator = new nsSimpleURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleURI.h b/netwerk/base/nsSimpleURI.h
new file mode 100644
index 0000000000..560dd56040
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleURI_h__
+#define nsSimpleURI_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIURI.h"
+#include "nsISerializable.h"
+#include "nsString.h"
+#include "nsIClassInfo.h"
+#include "nsISizeOf.h"
+#include "nsIURIMutator.h"
+#include "nsISimpleURIMutator.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_THIS_SIMPLEURI_IMPLEMENTATION_CID \
+ { /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */ \
+ 0x0b9bb0c2, 0xfee6, 0x470b, { \
+ 0xb9, 0xb9, 0x9f, 0xd9, 0x46, 0x2b, 0x5e, 0x19 \
+ } \
+ }
+
+class nsSimpleURI : public nsIURI, public nsISerializable, public nsISizeOf {
+ protected:
+ nsSimpleURI() = default;
+ virtual ~nsSimpleURI() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSISERIALIZABLE
+
+ static already_AddRefed<nsSimpleURI> From(nsIURI* aURI);
+
+ // nsSimpleURI methods:
+
+ bool Equals(nsSimpleURI* aOther) { return EqualsInternal(aOther, eHonorRef); }
+
+ // nsISizeOf
+ // Among the sub-classes that inherit (directly or indirectly) from
+ // nsSimpleURI, measurement of the following members may be added later if
+ // DMD finds it is worthwhile:
+ // - nsJSURI: mBaseURI
+ // - nsSimpleNestedURI: mInnerURI
+ // - nsBlobURI: mPrincipal
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum { eIgnoreRef, eHonorRef, eReplaceRef };
+
+ virtual nsresult Clone(nsIURI** result);
+ virtual nsresult SetSpecInternal(const nsACString& aSpec,
+ bool aStripWhitespace = false);
+ virtual nsresult SetScheme(const nsACString& input);
+ virtual nsresult SetUserPass(const nsACString& input);
+ nsresult SetUsername(const nsACString& input);
+ virtual nsresult SetPassword(const nsACString& input);
+ virtual nsresult SetHostPort(const nsACString& aValue);
+ virtual nsresult SetHost(const nsACString& input);
+ virtual nsresult SetPort(int32_t port);
+ virtual nsresult SetPathQueryRef(const nsACString& aPath);
+ virtual nsresult SetRef(const nsACString& aRef);
+ virtual nsresult SetFilePath(const nsACString& aFilePath);
+ virtual nsresult SetQuery(const nsACString& aQuery);
+ virtual nsresult SetQueryWithEncoding(const nsACString& aQuery,
+ const Encoding* encoding);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ // Helper to be used by inherited classes who want to test
+ // equality given an assumed nsSimpleURI. This must NOT check
+ // the passed-in other for QI to our CID.
+ bool EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode);
+
+ // Used by StartClone (and versions of StartClone in subclasses) to
+ // handle the ref in the right way for clones.
+ void SetRefOnClone(nsSimpleURI* url, RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // NOTE: This takes the refHandlingMode as an argument because
+ // nsSimpleNestedURI's specialized version needs to know how to clone
+ // its inner URI.
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // Helper to share code between Clone methods.
+ virtual nsresult CloneInternal(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef, nsIURI** result);
+
+ void TrimTrailingCharactersFromPath();
+ nsresult EscapeAndSetPathQueryRef(const nsACString& aPath);
+ nsresult SetPathQueryRefInternal(const nsACString& aPath);
+
+ bool Deserialize(const mozilla::ipc::URIParams&);
+
+ nsCString mScheme;
+ nsCString mPath; // NOTE: mPath does not include ref, as an optimization
+ nsCString mRef; // so that URIs with different refs can share string data.
+ nsCString
+ mQuery; // so that URLs with different querys can share string data.
+ bool mIsRefValid{false}; // To distinguish between empty-ref and no-ref.
+ // To distinguish between empty-query and no-query.
+ bool mIsQueryValid{false};
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsSimpleURI>,
+ public nsISimpleURIMutator,
+ public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+ NS_DEFINE_NSIMUTATOR_COMMON
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpecAndFilterWhitespace(
+ const nsACString& aSpec, nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ *aMutator = do_AddRef(this).take();
+ }
+
+ nsresult rv = NS_OK;
+ RefPtr<nsSimpleURI> uri = new nsSimpleURI();
+ rv = uri->SetSpecInternal(aSpec, /* filterWhitespace */ true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ friend class nsSimpleURI;
+ };
+
+ friend BaseURIMutator<nsSimpleURI>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsSimpleURI_h__
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
new file mode 100644
index 0000000000..e12a376788
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -0,0 +1,3411 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "nsSocketTransport2.h"
+
+#include "IOActivityMonitor.h"
+#include "NSSErrorsService.h"
+#include "NetworkDataCountLayer.h"
+#include "QuicSocketControl.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "mozilla/ProfilerBandwidthCounter.h"
+#include "nsCOMPtr.h"
+#include "nsICancelable.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsISocketProvider.h"
+#include "nsITLSSocketControl.h"
+#include "nsNetAddr.h"
+#include "nsNetCID.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsProxyInfo.h"
+#include "nsSocketProviderService.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+#include "nsURLHelper.h"
+#include "prerr.h"
+#include "sslexp.h"
+#include "xpcpublic.h"
+
+#if defined(FUZZING)
+# include "FuzzyLayer.h"
+# include "FuzzySocketControl.h"
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+#if defined(XP_WIN)
+# include "ShutdownLayer.h"
+#endif
+
+/* Following inclusions required for keepalive config not supported by NSPR. */
+#include "private/pprio.h"
+#if defined(XP_WIN)
+# include <winsock2.h>
+# include <mstcpip.h>
+#elif defined(XP_UNIX)
+# include <errno.h>
+# include <netinet/tcp.h>
+#endif
+/* End keepalive config inclusions. */
+
+#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
+#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
+#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
+#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3
+
+//-----------------------------------------------------------------------------
+
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsSocketEvent : public Runnable {
+ public:
+ nsSocketEvent(nsSocketTransport* transport, uint32_t type,
+ nsresult status = NS_OK, nsISupports* param = nullptr,
+ std::function<void()>&& task = nullptr)
+ : Runnable("net::nsSocketEvent"),
+ mTransport(transport),
+ mType(type),
+ mStatus(status),
+ mParam(param),
+ mTask(std::move(task)) {}
+
+ NS_IMETHOD Run() override {
+ mTransport->OnSocketEvent(mType, mStatus, mParam, std::move(mTask));
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsSocketTransport> mTransport;
+
+ uint32_t mType;
+ nsresult mStatus;
+ nsCOMPtr<nsISupports> mParam;
+ std::function<void()> mTask;
+};
+
+//-----------------------------------------------------------------------------
+
+// #define TEST_CONNECT_ERRORS
+#ifdef TEST_CONNECT_ERRORS
+# include <stdlib.h>
+static PRErrorCode RandomizeConnectError(PRErrorCode code) {
+ //
+ // To test out these errors, load http://www.yahoo.com/. It should load
+ // correctly despite the random occurrence of these errors.
+ //
+ int n = rand();
+ if (n > RAND_MAX / 2) {
+ struct {
+ PRErrorCode err_code;
+ const char* err_name;
+ } errors[] = {
+ //
+ // These errors should be recoverable provided there is another
+ // IP address in mDNSRecord.
+ //
+ {PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"},
+ {PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"},
+ //
+ // This error will cause this socket transport to error out;
+ // however, if the consumer is HTTP, then the HTTP transaction
+ // should be restarted when this error occurs.
+ //
+ {PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"},
+ };
+ n = n % (sizeof(errors) / sizeof(errors[0]));
+ code = errors[n].err_code;
+ SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
+ }
+ return code;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
+ nsresult rv = NS_ERROR_FAILURE;
+ switch (errorCode) {
+ case PR_WOULD_BLOCK_ERROR:
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ case PR_CONNECT_ABORTED_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ rv = NS_ERROR_NET_RESET;
+ break;
+ case PR_END_OF_FILE_ERROR: // XXX document this correlation
+ rv = NS_ERROR_NET_INTERRUPT;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
+ // could get better diagnostics by adding distinct XPCOM error codes for
+ // each of these, but there are a lot of places in Gecko that check
+ // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
+ // be checked.
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_HOST_UNREACHABLE_ERROR:
+ case PR_ADDRESS_NOT_AVAILABLE_ERROR:
+ // Treat EACCES as a soft error since (at least on Linux) connect() returns
+ // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case PR_ADDRESS_NOT_SUPPORTED_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+ break;
+ case PR_IO_TIMEOUT_ERROR:
+ case PR_CONNECT_TIMEOUT_ERROR:
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ case PR_OUT_OF_MEMORY_ERROR:
+ // These really indicate that the descriptor table filled up, or that the
+ // kernel ran out of network buffers - but nobody really cares which part of
+ // the system ran out of memory.
+ case PR_PROC_DESC_TABLE_FULL_ERROR:
+ case PR_SYS_DESC_TABLE_FULL_ERROR:
+ case PR_INSUFFICIENT_RESOURCES_ERROR:
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ case PR_ADDRESS_IN_USE_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
+ break;
+ // These filename-related errors can arise when using Unix-domain sockets.
+ case PR_FILE_NOT_FOUND_ERROR:
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ break;
+ case PR_IS_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_IS_DIRECTORY;
+ break;
+ case PR_LOOP_ERROR:
+ rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ break;
+ case PR_NAME_TOO_LONG_ERROR:
+ rv = NS_ERROR_FILE_NAME_TOO_LONG;
+ break;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ case PR_NOT_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_NOT_DIRECTORY;
+ break;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ rv = NS_ERROR_FILE_READ_ONLY;
+ break;
+ case PR_BAD_ADDRESS_ERROR:
+ rv = NS_ERROR_UNKNOWN_HOST;
+ break;
+ default:
+ if (psm::IsNSSErrorCode(errorCode)) {
+ rv = psm::GetXPCOMFromNSSError(errorCode);
+ }
+ break;
+
+ // NSPR's socket code can return these, but they're not worth breaking out
+ // into their own error codes, distinct from NS_ERROR_FAILURE:
+ //
+ // PR_BAD_DESCRIPTOR_ERROR
+ // PR_INVALID_ARGUMENT_ERROR
+ // PR_NOT_SOCKET_ERROR
+ // PR_NOT_TCP_SOCKET_ERROR
+ // These would indicate a bug internal to the component.
+ //
+ // PR_PROTOCOL_NOT_SUPPORTED_ERROR
+ // This means that we can't use the given "protocol" (like
+ // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
+ // above, this indicates an internal bug.
+ //
+ // PR_IS_CONNECTED_ERROR
+ // This indicates that we've applied a system call like 'bind' or
+ // 'connect' to a socket that is already connected. The socket
+ // components manage each file descriptor's state, and in some cases
+ // handle this error result internally. We shouldn't be returning
+ // this to our callers.
+ //
+ // PR_IO_ERROR
+ // This is so vague that NS_ERROR_FAILURE is just as good.
+ }
+ SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%" PRIx32 "]\n", errorCode,
+ static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// socket input stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketInputStream::nsSocketInputStream(nsSocketTransport* trans)
+ : mTransport(trans) {}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void nsSocketInputStream::OnSocketReady(nsresult condition) {
+ SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = std::move(mCallback);
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback) callback->OnInputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, nsIInputStream,
+ nsIAsyncInputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::AddRef() {
+ ++mReaderRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::Release() {
+ if (--mReaderRefCnt == 0) Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+nsSocketInputStream::Available(uint64_t* avail) {
+ SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this));
+
+ *avail = 0;
+
+ PRFileDesc* fd;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_OK;
+ }
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Available(fd);
+
+ // PSM does not implement PR_Available() so do a best approximation of it
+ // with MSG_PEEK
+ if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
+ char c;
+
+ n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+ SOCKET_LOG(
+ ("nsSocketInputStream::Available [this=%p] "
+ "using PEEK backup n=%d]\n",
+ this, n));
+ }
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n >= 0) {
+ *avail = n;
+ } else {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_OK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::StreamStatus() {
+ SOCKET_LOG(("nsSocketInputStream::StreamStatus [this=%p]\n", this));
+
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Read(char* buf, uint32_t count, uint32_t* countRead) {
+ SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) {
+ return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
+ }
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Read(fd, buf, count);
+
+ SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceInBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0) {
+ mByteCount += (*countRead = n);
+ profiler_count_bandwidth_read_bytes(n);
+ } else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+
+ // only send this notification if we have indeed read some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::CloseWithStatus(nsresult reason) {
+ SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition)) {
+ rv = mCondition = reason;
+ } else {
+ rv = NS_OK;
+ }
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t amount, nsIEventTarget* target) {
+ SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this));
+
+ bool hasError = false;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewInputStreamReadyEvent("nsSocketInputStream::AsyncWait",
+ callback, target);
+ } else {
+ mCallback = callback;
+ }
+ mCallbackFlags = flags;
+
+ hasError = NS_FAILED(mCondition);
+ } // unlock mTransport->mLock
+
+ if (hasError) {
+ // OnSocketEvent will call OnInputStreamReady with an error code after
+ // going through the event loop. We do this because most socket callers
+ // do not expect AsyncWait() to synchronously execute the OnInputStreamReady
+ // callback.
+ mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING);
+ } else {
+ mTransport->OnInputPending();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket output stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport* trans)
+ : mTransport(trans) {}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void nsSocketOutputStream::OnSocketReady(nsresult condition) {
+ SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIOutputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = std::move(mCallback);
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback) callback->OnOutputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::AddRef() {
+ ++mWriterRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::Release() {
+ if (--mWriterRefCnt == 0) Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+nsSocketOutputStream::Flush() { return NS_OK; }
+
+NS_IMETHODIMP
+nsSocketOutputStream::StreamStatus() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count));
+
+ *countWritten = 0;
+
+ // A write of 0 bytes can be used to force the initial SSL handshake, so do
+ // not reject that.
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Write [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Write(fd, buf, count);
+
+ SOCKET_LOG((" PR_Write returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceOutBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0) {
+ mByteCount += (*countWritten = n);
+ profiler_count_bandwidth_written_bytes(n);
+ } else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
+
+ // only send this notification if we have indeed written some data.
+ // see bug 196827 for an example of why this is important.
+ if ((n > 0)) {
+ mTransport->SendStatus(NS_NET_STATUS_SENDING_TO);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsSocketOutputStream::WriteFromSegments(
+ nsIInputStream* input, void* closure, const char* fromSegment,
+ uint32_t offset, uint32_t count, uint32_t* countRead) {
+ nsSocketOutputStream* self = (nsSocketOutputStream*)closure;
+ return self->Write(fromSegment, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteFrom(nsIInputStream* stream, uint32_t count,
+ uint32_t* countRead) {
+ return stream->ReadSegments(WriteFromSegments, this, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::CloseWithStatus(nsresult reason) {
+ SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition)) {
+ rv = mCondition = reason;
+ } else {
+ rv = NS_OK;
+ }
+ }
+ if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback* callback,
+ uint32_t flags, uint32_t amount,
+ nsIEventTarget* target) {
+ SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n", this));
+
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewOutputStreamReadyEvent(callback, target);
+ } else {
+ mCallback = callback;
+ }
+
+ mCallbackFlags = flags;
+ }
+ mTransport->OnOutputPending();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket transport impl
+//-----------------------------------------------------------------------------
+
+nsSocketTransport::nsSocketTransport()
+ : mFD(this),
+ mSocketTransportService(gSocketTransportService),
+ mInput(new nsSocketInputStream(this)),
+ mOutput(new nsSocketOutputStream(this)) {
+ SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
+
+ mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout
+ mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
+}
+
+nsSocketTransport::~nsSocketTransport() {
+ MOZ_RELEASE_ASSERT(!mAttached);
+ SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
+}
+
+nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types,
+ const nsACString& host, uint16_t port,
+ const nsACString& hostRoute,
+ uint16_t portRoute,
+ nsIProxyInfo* givenProxyInfo,
+ nsIDNSRecord* dnsRecord) {
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ if (dnsRecord) {
+ mExternalDNSResolution = true;
+ mDNSRecord = do_QueryInterface(dnsRecord);
+ mDNSRecord->IsTRR(&mResolvedByTRR);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+
+ // init socket type info
+
+ mOriginHost = host;
+ mOriginPort = port;
+ if (!hostRoute.IsEmpty()) {
+ mHost = hostRoute;
+ mPort = portRoute;
+ } else {
+ mHost = host;
+ mPort = port;
+ }
+
+ // A subtle check we don't enter this method more than once for the socket
+ // transport lifetime. Disable on TSan builds to prevent race checking, we
+ // don't want an atomic here for perf reasons!
+#ifndef MOZ_TSAN
+ MOZ_ASSERT(!mPortRemappingApplied);
+#endif // !MOZ_TSAN
+
+ if (proxyInfo) {
+ mHttpsProxy = proxyInfo->IsHTTPS();
+ }
+
+ const char* proxyType = nullptr;
+ mProxyInfo = proxyInfo;
+ if (proxyInfo) {
+ mProxyPort = proxyInfo->Port();
+ mProxyHost = proxyInfo->Host();
+ // grab proxy type (looking for "socks" for example)
+ proxyType = proxyInfo->Type();
+ if (proxyType && (proxyInfo->IsHTTP() || proxyInfo->IsHTTPS() ||
+ proxyInfo->IsDirect() || !strcmp(proxyType, "unknown"))) {
+ proxyType = nullptr;
+ }
+ }
+
+ SOCKET_LOG1(
+ ("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d "
+ "proxy=%s:%hu]\n",
+ this, mHost.get(), mPort, mOriginHost.get(), mOriginPort,
+ mProxyHost.get(), mProxyPort));
+
+ // include proxy type as a socket type if proxy type is not "http"
+ uint32_t typeCount = types.Length() + (proxyType != nullptr);
+ if (!typeCount) return NS_OK;
+
+ // if we have socket types, then the socket provider service had
+ // better exist!
+ nsresult rv;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+
+ if (!mTypes.SetCapacity(typeCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // now verify that each socket type has a registered socket provider.
+ for (uint32_t i = 0, type = 0; i < typeCount; ++i) {
+ // store socket types
+ if (i == 0 && proxyType) {
+ mTypes.AppendElement(proxyType);
+ } else {
+ mTypes.AppendElement(types[type++]);
+ }
+
+ nsCOMPtr<nsISocketProvider> provider;
+ rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("no registered socket provider");
+ return rv;
+ }
+
+ // note if socket type corresponds to a transparent proxy
+ // XXX don't hardcode SOCKS here (use proxy info's flags instead).
+ if (mTypes[i].EqualsLiteral("socks") || mTypes[i].EqualsLiteral("socks4")) {
+ mProxyTransparent = true;
+
+ if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ // we want the SOCKS layer to send the hostname
+ // and port to the proxy and let it do the DNS.
+ mProxyTransparentResolvesHost = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+#if defined(XP_UNIX)
+nsresult nsSocketTransport::InitWithFilename(const char* filename) {
+ return InitWithName(filename, strlen(filename));
+}
+
+nsresult nsSocketTransport::InitWithName(const char* name, size_t length) {
+ if (length > sizeof(mNetAddr.local.path) - 1) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+
+ if (!name[0] && length > 1) {
+ // name is abstract address name that is supported on Linux only
+# if defined(XP_LINUX)
+ mHost.Assign(name + 1, length - 1);
+# else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+# endif
+ } else {
+ // The name isn't abstract socket address. So this is Unix domain
+ // socket that has file path.
+ mHost.Assign(name, length);
+ }
+ mPort = 0;
+
+ mNetAddr.local.family = AF_LOCAL;
+ memcpy(mNetAddr.local.path, name, length);
+ mNetAddr.local.path[length] = '\0';
+ mNetAddrIsSet = true;
+
+ return NS_OK;
+}
+#endif
+
+nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* fd,
+ const NetAddr* addr) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ char buf[kNetAddrMaxCStrBufSize];
+ addr->ToStringBuffer(buf, sizeof(buf));
+ mHost.Assign(buf);
+
+ uint16_t port;
+ if (addr->raw.family == AF_INET) {
+ port = addr->inet.port;
+ } else if (addr->raw.family == AF_INET6) {
+ port = addr->inet6.port;
+ } else {
+ port = 0;
+ }
+ mPort = ntohs(port);
+
+ memcpy(&mNetAddr, addr, sizeof(NetAddr));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mState = STATE_TRANSFERRING;
+ SetSocketName(fd);
+ mNetAddrIsSet = true;
+
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = true;
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+
+ // make sure new socket is non-blocking
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(fd, &opt);
+
+ SOCKET_LOG(
+ ("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
+ this, mHost.get(), mPort));
+
+ // jump to InitiateSocket to get ourselves attached to the STS poll list.
+ return PostEvent(MSG_RETRY_INIT_SOCKET);
+}
+
+nsresult nsSocketTransport::InitWithConnectedSocket(
+ PRFileDesc* aFD, const NetAddr* aAddr, nsIInterfaceRequestor* aCallbacks) {
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = aCallbacks;
+ }
+ return InitWithConnectedSocket(aFD, aAddr);
+}
+
+nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status,
+ nsISupports* param,
+ std::function<void()>&& task) {
+ SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%" PRIx32
+ " param=%p]\n",
+ this, type, static_cast<uint32_t>(status), param));
+
+ nsCOMPtr<nsIRunnable> event =
+ new nsSocketEvent(this, type, status, param, std::move(task));
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+
+ return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void nsSocketTransport::SendStatus(nsresult status) {
+ SOCKET_LOG1(("nsSocketTransport::SendStatus [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(status)));
+
+ nsCOMPtr<nsITransportEventSink> sink;
+ uint64_t progress;
+ {
+ MutexAutoLock lock(mLock);
+ sink = mEventSink;
+ switch (status) {
+ case NS_NET_STATUS_SENDING_TO:
+ progress = mOutput->ByteCount(lock);
+ break;
+ case NS_NET_STATUS_RECEIVING_FROM:
+ progress = mInput->ByteCount(lock);
+ break;
+ default:
+ progress = 0;
+ break;
+ }
+ }
+ if (sink) {
+ sink->OnTransportStatus(this, status, progress, -1);
+ }
+}
+
+nsresult nsSocketTransport::ResolveHost() {
+ SOCKET_LOG((
+ "nsSocketTransport::ResolveHost [this=%p %s:%d%s] "
+ "mProxyTransparentResolvesHost=%d\n",
+ this, SocketHost().get(), SocketPort(),
+ mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "",
+ mProxyTransparentResolvesHost));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+
+ if (!mProxyHost.IsEmpty()) {
+ if (!mProxyTransparent || mProxyTransparentResolvesHost) {
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with proxies");
+#endif
+ // When not resolving mHost locally, we still want to ensure that
+ // it only contains valid characters. See bug 304904 for details.
+ // Sometimes the end host is not yet known and mHost is *
+ if (!net_IsValidHostName(mHost) && !mHost.EqualsLiteral("*")) {
+ SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+ if (mProxyTransparentResolvesHost) {
+ // Name resolution is done on the server side. Just pretend
+ // client resolution is complete, this will get picked up later.
+ // since we don't need to do DNS now, we bypass the resolving
+ // step by initializing mNetAddr to an empty address, but we
+ // must keep the port. The SOCKS IO layer will use the hostname
+ // we send it when it's created, rather than the empty address
+ // we send with the connect call.
+ mState = STATE_RESOLVING;
+ mNetAddr.raw.family = AF_INET;
+ mNetAddr.inet.port = htons(SocketPort());
+ mNetAddr.inet.ip = htonl(INADDR_ANY);
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+ }
+
+ if (mExternalDNSResolution) {
+ MOZ_ASSERT(mDNSRecord);
+ mState = STATE_RESOLVING;
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+
+ nsCOMPtr<nsIDNSService> dns = nullptr;
+ auto initTask = [&dns]() { dns = do_GetService(kDNSServiceCID); };
+ if (!NS_IsMainThread()) {
+ // Forward to the main thread synchronously.
+ RefPtr<nsIThread> mainThread = do_GetMainThread();
+ if (!mainThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SyncRunnable::DispatchToThread(
+ mainThread,
+ NS_NewRunnableFunction("nsSocketTransport::ResolveHost->GetDNSService",
+ initTask));
+ } else {
+ initTask();
+ }
+ if (!dns) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mResolving = true;
+
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) {
+ dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ if (mConnectionFlags & nsSocketTransport::REFRESH_CACHE) {
+ dnsFlags = nsIDNSService::RESOLVE_REFRESH_CACHE;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_TRR) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR;
+ }
+
+ if (mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) {
+ dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
+ }
+
+ dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(
+ nsISocketTransport::GetTRRModeFromFlags(mConnectionFlags));
+
+ // When we get here, we are not resolving using any configured proxy likely
+ // because of individual proxy setting on the request or because the host is
+ // excluded from proxying. Hence, force resolution despite global proxy-DNS
+ // configuration.
+ dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
+
+ NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+
+ SendStatus(NS_NET_STATUS_RESOLVING_HOST);
+
+ if (!SocketHost().Equals(mOriginHost)) {
+ SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n", this,
+ mOriginHost.get(), SocketHost().get()));
+ }
+ rv =
+ dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ dnsFlags, nullptr, this, mSocketTransportService,
+ mOriginAttributes, getter_AddRefs(mDNSRequest));
+
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
+ mState = STATE_RESOLVING;
+ }
+ return rv;
+}
+
+nsresult nsSocketTransport::BuildSocket(PRFileDesc*& fd, bool& proxyTransparent,
+ bool& usingSSL) {
+ SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this));
+
+ nsresult rv = NS_OK;
+
+ proxyTransparent = false;
+ usingSSL = false;
+
+ if (mTypes.IsEmpty()) {
+ fd = PR_OpenTCPSocket(mNetAddr.raw.family);
+ if (!fd) {
+ SOCKET_LOG((" error creating TCP nspr socket [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with socket types");
+#endif
+
+ fd = nullptr;
+
+ uint32_t controlFlags = 0;
+ if (mProxyTransparentResolvesHost) {
+ controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) {
+ controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) {
+ controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::DONT_TRY_ECH) {
+ controlFlags |= nsISocketProvider::DONT_TRY_ECH;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::IS_RETRY) {
+ controlFlags |= nsISocketProvider::IS_RETRY;
+ }
+
+ if (mConnectionFlags &
+ nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::IS_SPECULATIVE_CONNECTION) {
+ controlFlags |= nsISocketProvider::IS_SPECULATIVE_CONNECTION;
+ }
+
+ if (mResolvedByTRR) {
+ controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
+ }
+
+ // by setting host to mOriginHost, instead of mHost we send the
+ // SocketProvider (e.g. PSM) the origin hostname but can still do DNS
+ // on an explicit alternate service host name
+ const char* host = mOriginHost.get();
+ int32_t port = (int32_t)mOriginPort;
+
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+ nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
+
+ uint32_t i;
+ for (i = 0; i < mTypes.Length(); ++i) {
+ nsCOMPtr<nsISocketProvider> provider;
+
+ SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get()));
+
+ rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (i == 0) {
+ // if this is the first type, we'll want the
+ // service to allocate a new socket
+
+ // Most layers _ESPECIALLY_ PSM want the origin name here as they
+ // will use it for secure checks, etc.. and any connection management
+ // differences between the origin name and the routed name can be
+ // taken care of via DNS. However, SOCKS is a special case as there is
+ // no DNS. in the case of SOCKS and PSM the PSM is a separate layer
+ // and receives the origin name.
+ const char* socketProviderHost = host;
+ int32_t socketProviderPort = port;
+ if (mProxyTransparentResolvesHost &&
+ (mTypes[0].EqualsLiteral("socks") ||
+ mTypes[0].EqualsLiteral("socks4"))) {
+ SOCKET_LOG(("SOCKS %d Host/Route override: %s:%d -> %s:%d\n",
+ mHttpsProxy, socketProviderHost, socketProviderPort,
+ mHost.get(), mPort));
+ socketProviderHost = mHost.get();
+ socketProviderPort = mPort;
+ }
+
+ // when https proxying we want to just connect to the proxy as if
+ // it were the end host (i.e. expect the proxy's cert)
+
+ rv = provider->NewSocket(
+ mNetAddr.raw.family,
+ mHttpsProxy ? mProxyHost.get() : socketProviderHost,
+ mHttpsProxy ? mProxyPort : socketProviderPort, proxyInfo,
+ mOriginAttributes, controlFlags, mTlsFlags, &fd,
+ getter_AddRefs(tlsSocketControl));
+
+ if (NS_SUCCEEDED(rv) && !fd) {
+ MOZ_ASSERT_UNREACHABLE(
+ "NewSocket succeeded but failed to "
+ "create a PRFileDesc");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // the socket has already been allocated,
+ // so we just want the service to add itself
+ // to the stack (such as pushing an io layer)
+ rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo,
+ mOriginAttributes, controlFlags, mTlsFlags, fd,
+ getter_AddRefs(tlsSocketControl));
+ }
+
+ // controlFlags = 0; not used below this point...
+ if (NS_FAILED(rv)) break;
+
+ // if the service was ssl or starttls, we want to hold onto the socket
+ // info
+ bool isSSL = mTypes[i].EqualsLiteral("ssl");
+ if (isSSL || mTypes[i].EqualsLiteral("starttls")) {
+ // remember security info
+ {
+ MutexAutoLock lock(mLock);
+ mTLSSocketControl = tlsSocketControl;
+ SOCKET_LOG((" [tlsSocketControl=%p callbacks=%p]\n",
+ mTLSSocketControl.get(), mCallbacks.get()));
+ }
+ // remember if socket type is SSL so we can ProxyStartSSL if need be.
+ usingSSL = isSSL;
+ } else if (mTypes[i].EqualsLiteral("socks") ||
+ mTypes[i].EqualsLiteral("socks4")) {
+ // since socks is transparent, any layers above
+ // it do not have to worry about proxy stuff
+ proxyInfo = nullptr;
+ proxyTransparent = true;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG((" error pushing io layer [%u:%s rv=%" PRIx32 "]\n", i,
+ mTypes[i].get(), static_cast<uint32_t>(rv)));
+ if (fd) {
+ CloseSocket(
+ fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ }
+ }
+ return rv;
+}
+
+nsresult nsSocketTransport::InitiateSocket() {
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+ bool isLocal;
+ IsLocal(&isLocal);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_ABORT;
+ }
+ if (gIOService->IsOffline()) {
+ if (StaticPrefs::network_disable_localhost_when_offline() || !isLocal) {
+ return NS_ERROR_OFFLINE;
+ }
+ } else if (!isLocal) {
+#ifdef DEBUG
+ // all IP networking has to be done from the parent
+ if (NS_SUCCEEDED(mCondition) && ((mNetAddr.raw.family == AF_INET) ||
+ (mNetAddr.raw.family == AF_INET6))) {
+ MOZ_ASSERT(!IsNeckoChild());
+ }
+#endif
+
+ if (NS_SUCCEEDED(mCondition) && xpc::AreNonLocalConnectionsDisabled() &&
+ !(mNetAddr.IsIPAddrAny() || mNetAddr.IsIPAddrLocal() ||
+ mNetAddr.IsIPAddrShared())) {
+ nsAutoCString ipaddr;
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
+ netaddr->GetAddress(ipaddr);
+ fprintf_stderr(
+ stderr,
+ "FATAL ERROR: Non-local network connections are disabled and a "
+ "connection "
+ "attempt to %s (%s) was made.\nYou should only access hostnames "
+ "available via the test networking proxy (if running mochitests) "
+ "or from a test-specific httpd.js server (if running xpcshell "
+ "tests). "
+ "Browser services should be disabled or redirected to a local "
+ "server.\n",
+ mHost.get(), ipaddr.get());
+ return NS_ERROR_NON_LOCAL_CONNECTION_REFUSED;
+ }
+ }
+
+ // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
+ mNetAddr.IsIPAddrLocal()) {
+ if (SOCKET_LOG_ENABLED()) {
+ nsAutoCString netAddrCString;
+ netAddrCString.SetLength(kIPv6CStrBufSize);
+ if (!mNetAddr.ToStringBuffer(netAddrCString.BeginWriting(),
+ kIPv6CStrBufSize)) {
+ netAddrCString = "<IP-to-string failed>"_ns;
+ }
+ SOCKET_LOG(
+ ("nsSocketTransport::InitiateSocket skipping "
+ "speculative connection for host [%s:%d] proxy "
+ "[%s:%d] with Local IP address [%s]",
+ mHost.get(), mPort, mProxyHost.get(), mProxyPort,
+ netAddrCString.get()));
+ }
+ mCondition = NS_ERROR_CONNECTION_REFUSED;
+ OnSocketDetached(nullptr);
+ return mCondition;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!mSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET);
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+ return mSocketTransportService->NotifyWhenCanAttachSocket(event);
+ }
+
+ //
+ // if we already have a connected socket, then just attach and return.
+ //
+ {
+ MutexAutoLock lock(mLock);
+ if (mFD.IsInitialized()) {
+ rv = mSocketTransportService->AttachSocket(mFD, this);
+ if (NS_SUCCEEDED(rv)) mAttached = true;
+ return rv;
+ }
+ }
+
+ //
+ // create new socket fd, push io layers, etc.
+ //
+ PRFileDesc* fd;
+ bool proxyTransparent;
+ bool usingSSL;
+
+ rv = BuildSocket(fd, proxyTransparent, usingSSL);
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG(
+ (" BuildSocket failed [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ // create proxy via IOActivityMonitor
+ IOActivityMonitor::MonitorSocket(fd);
+
+#ifdef FUZZING
+ if (StaticPrefs::fuzzing_necko_enabled()) {
+ rv = AttachFuzzyIOLayer(fd);
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ SOCKET_LOG(("Successfully attached fuzzing IOLayer.\n"));
+
+ if (usingSSL) {
+ mTLSSocketControl = new FuzzySocketControl();
+ }
+ }
+#endif
+
+ PRStatus status;
+
+ // Make the socket non-blocking...
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ status = PR_SetSocketOption(fd, &opt);
+ NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
+
+ if (mReuseAddrPort) {
+ SOCKET_LOG((" Setting port/addr reuse socket options\n"));
+
+ // Set ReuseAddr for TCP sockets to enable having several
+ // sockets bound to same local IP and port
+ PRSocketOptionData opt_reuseaddr;
+ opt_reuseaddr.option = PR_SockOpt_Reuseaddr;
+ opt_reuseaddr.value.reuse_addr = PR_TRUE;
+ status = PR_SetSocketOption(fd, &opt_reuseaddr);
+ if (status != PR_SUCCESS) {
+ SOCKET_LOG((" Couldn't set reuse addr socket option: %d\n", status));
+ }
+
+ // And also set ReusePort for platforms supporting this socket option
+ PRSocketOptionData opt_reuseport;
+ opt_reuseport.option = PR_SockOpt_Reuseport;
+ opt_reuseport.value.reuse_port = PR_TRUE;
+ status = PR_SetSocketOption(fd, &opt_reuseport);
+ if (status != PR_SUCCESS &&
+ PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) {
+ SOCKET_LOG((" Couldn't set reuse port socket option: %d\n", status));
+ }
+ }
+
+ // disable the nagle algorithm - if we rely on it to coalesce writes into
+ // full packets the final packet of a multi segment POST/PUT or pipeline
+ // sequence is delayed a full rtt
+ opt.option = PR_SockOpt_NoDelay;
+ opt.value.no_delay = true;
+ PR_SetSocketOption(fd, &opt);
+
+ // if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF
+ // The Windows default of 8KB is too small and as of vista sp1, autotuning
+ // only applies to receive window
+ int32_t sndBufferSize;
+ mSocketTransportService->GetSendBufferSize(&sndBufferSize);
+ if (sndBufferSize > 0) {
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = sndBufferSize;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+ if (mQoSBits) {
+ opt.option = PR_SockOpt_IpTypeOfService;
+ opt.value.tos = mQoSBits;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+#if defined(XP_WIN)
+ // The linger is turned off by default. This is not a hard close, but
+ // closesocket should return immediately and operating system tries to send
+ // remaining data for certain, implementation specific, amount of time.
+ // https://msdn.microsoft.com/en-us/library/ms739165.aspx
+ //
+ // Turn the linger option on an set the interval to 0. This will cause hard
+ // close of the socket.
+ opt.option = PR_SockOpt_Linger;
+ opt.value.linger.polarity = 1;
+ opt.value.linger.linger = 0;
+ PR_SetSocketOption(fd, &opt);
+#endif
+
+ // up to here, mFD will only be accessed by us
+
+ // assign mFD so that we can properly handle OnSocketDetached before we've
+ // established a connection.
+ {
+ MutexAutoLock lock(mLock);
+ // inform socket transport about this newly created socket...
+ rv = mSocketTransportService->AttachSocket(fd, this);
+ if (NS_FAILED(rv)) {
+ CloseSocket(
+ fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return rv;
+ }
+ mAttached = true;
+
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = false;
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ }
+
+ SOCKET_LOG((" advancing to STATE_CONNECTING\n"));
+ mState = STATE_CONNECTING;
+ SendStatus(NS_NET_STATUS_CONNECTING_TO);
+
+ if (SOCKET_LOG_ENABLED()) {
+ char buf[kNetAddrMaxCStrBufSize];
+ mNetAddr.ToStringBuffer(buf, sizeof(buf));
+ SOCKET_LOG((" trying address: %s\n", buf));
+ }
+
+ //
+ // Initiate the connect() to the host...
+ //
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ {
+ if (mBindAddr) {
+ MutexAutoLock lock(mLock);
+ NetAddrToPRNetAddr(mBindAddr.get(), &prAddr);
+ status = PR_Bind(fd, &prAddr);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ mBindAddr = nullptr;
+ }
+ }
+
+ NetAddrToPRNetAddr(&mNetAddr, &prAddr);
+
+#ifdef XP_WIN
+ // Find the real tcp socket and set non-blocking once again!
+ // Bug 1158189.
+ PRFileDesc* bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
+ if (bottom) {
+ PROsfd osfd = PR_FileDesc2NativeHandle(bottom);
+ u_long nonblocking = 1;
+ if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
+ NS_WARNING("Socket could not be set non-blocking!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+#endif
+
+ if (mTLSSocketControl) {
+ if (!mEchConfig.IsEmpty() &&
+ !(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) {
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
+ rv = mTLSSocketControl->SetEchConfig(mEchConfig);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mEchConfigUsed = true;
+ }
+ }
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ if (Telemetry::CanRecordPrereleaseData() ||
+ Telemetry::CanRecordReleaseData()) {
+ if (NS_FAILED(AttachNetworkDataCountLayer(fd))) {
+ SOCKET_LOG(
+ ("nsSocketTransport::InitiateSocket "
+ "AttachNetworkDataCountLayer failed [this=%p]\n",
+ this));
+ }
+ }
+
+ bool connectCalled = true; // This is only needed for telemetry.
+ status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT);
+ PRErrorCode code = PR_GetError();
+ if (status == PR_SUCCESS) {
+ PR_SetFDInheritable(fd, false);
+ }
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted && connectCalled) {
+ SendPRBlockingTelemetry(
+ connectStarted, Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+ } else {
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the PR_Connect(...) would block, then poll for a connection.
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ //
+ // If the socket is already connected, then return success...
+ //
+ } else if (PR_IS_CONNECTED_ERROR == code) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mTLSSocketControl && !mProxyHost.IsEmpty() && proxyTransparent &&
+ usingSSL) {
+ // if the connection phase is finished, and the ssl layer has
+ // been pushed, and we were proxying (transparently; ie. nothing
+ // has to happen in the protocol layer above us), it's time for
+ // the ssl to start doing it's thing.
+ SOCKET_LOG((" calling ProxyStartSSL()\n"));
+ mTLSSocketControl->ProxyStartSSL();
+ // XXX what if we were forced to poll on the socket for a successful
+ // connection... wouldn't we need to call ProxyStartSSL after a call
+ // to PR_ConnectContinue indicates that we are connected?
+ //
+ // XXX this appears to be what the old socket transport did. why
+ // isn't this broken?
+ }
+ }
+ //
+ // A SOCKS request was rejected; get the actual error code from
+ // the OS error
+ //
+ else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ rv = ErrorAccordingToNSPR(code);
+ }
+ //
+ // The connection was refused...
+ //
+ else {
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted && connectCalled) {
+ SendPRBlockingTelemetry(
+ connectStarted, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE);
+ }
+
+ rv = ErrorAccordingToNSPR(code);
+ if ((rv == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty()) {
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ }
+ }
+ }
+ return rv;
+}
+
+bool nsSocketTransport::RecoverFromError() {
+ NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong");
+
+ SOCKET_LOG(
+ ("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%" PRIx32
+ "]\n",
+ this, mState, static_cast<uint32_t>(mCondition)));
+
+ if (mDoNotRetryToConnect) {
+ SOCKET_LOG(
+ ("nsSocketTransport::RecoverFromError do not retry because "
+ "mDoNotRetryToConnect is set [this=%p]\n",
+ this));
+ return false;
+ }
+
+#if defined(XP_UNIX)
+ // Unix domain connections don't have multiple addresses to try,
+ // so the recovery techniques here don't apply.
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) return false;
+#endif
+
+ if ((mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) &&
+ mCondition == NS_ERROR_UNKNOWN_HOST &&
+ (mState == MSG_DNS_LOOKUP_COMPLETE || mState == MSG_ENSURE_CONNECT)) {
+ SOCKET_LOG((" try again without USE_IP_HINT_ADDRESS"));
+ mConnectionFlags &= ~nsSocketTransport::USE_IP_HINT_ADDRESS;
+ mState = STATE_CLOSED;
+ return NS_SUCCEEDED(PostEvent(MSG_ENSURE_CONNECT, NS_OK));
+ }
+
+ // can only recover from errors in these states
+ if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) {
+ SOCKET_LOG((" not in a recoverable state"));
+ return false;
+ }
+
+ nsresult rv;
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(!mFDconnected, "socket should not be connected");
+ }
+#endif
+
+ // all connection failures need to be reported to DNS so that the next
+ // time we will use a different address if available.
+ // NS_BASE_STREAM_CLOSED is not an actual connection failure, so don't report
+ // to DNS.
+ if (mState == STATE_CONNECTING && mDNSRecord &&
+ mCondition != NS_BASE_STREAM_CLOSED) {
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+ if (mCondition != NS_ERROR_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_NET_TIMEOUT &&
+ mCondition != NS_ERROR_UNKNOWN_HOST &&
+ mCondition != NS_ERROR_UNKNOWN_PROXY_HOST) {
+ SOCKET_LOG((" not a recoverable error %" PRIx32,
+ static_cast<uint32_t>(mCondition)));
+ return false;
+ }
+
+ bool tryAgain = false;
+
+ if ((mState == STATE_CONNECTING) && mDNSRecord) {
+ if (mNetAddr.raw.family == AF_INET) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ }
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+ }
+
+ if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY &&
+ mCondition == NS_ERROR_UNKNOWN_HOST && mState == STATE_RESOLVING &&
+ !mProxyTransparentResolvesHost) {
+ SOCKET_LOG((" trying lookup again with opposite ip family\n"));
+ mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
+ mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
+ // This will tell the consuming half-open to reset preference on the
+ // connection entry
+ mResetFamilyPreference = true;
+ tryAgain = true;
+ }
+
+ // try next ip address only if past the resolver stage...
+ if (mState == STATE_CONNECTING && mDNSRecord) {
+ nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ mDNSRecord->IsTRR(&mResolvedByTRR);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" trying again with next ip address\n"));
+ tryAgain = true;
+ } else if (mExternalDNSResolution) {
+ mRetryDnsIfPossible = true;
+ bool trrEnabled;
+ mDNSRecord->IsTRR(&trrEnabled);
+ // Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we
+ // should intentionally not fallback to regular DNS.
+ if (trrEnabled && !StaticPrefs::network_trr_fallback_on_zero_response() &&
+ ((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
+ (mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 &&
+ mNetAddr.inet6.ip.u64[1] == 0))) {
+ SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
+ mRetryDnsIfPossible = false;
+ }
+ } else if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY) {
+ SOCKET_LOG((" failed to connect, trying with opposite ip family\n"));
+ // Drop state to closed. This will trigger new round of DNS
+ // resolving bellow.
+ mState = STATE_CLOSED;
+ mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
+ mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
+ // This will tell the consuming half-open to reset preference on the
+ // connection entry
+ mResetFamilyPreference = true;
+ tryAgain = true;
+ } else if (!(mConnectionFlags & DISABLE_TRR)) {
+ bool trrEnabled;
+ mDNSRecord->IsTRR(&trrEnabled);
+
+ // Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we
+ // should intentionally not fallback to regular DNS.
+ if (!StaticPrefs::network_trr_fallback_on_zero_response() &&
+ ((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
+ (mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 &&
+ mNetAddr.inet6.ip.u64[1] == 0))) {
+ SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
+ } else if (trrEnabled) {
+ nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
+ mDNSRecord->GetEffectiveTRRMode(&trrMode);
+ // If current trr mode is trr only, we should not retry.
+ if (trrMode != nsIRequest::TRR_ONLY_MODE) {
+ // Drop state to closed. This will trigger a new round of
+ // DNS resolving. Bypass the cache this time since the
+ // cached data came from TRR and failed already!
+ SOCKET_LOG((" failed to connect with TRR enabled, try w/o\n"));
+ mState = STATE_CLOSED;
+ mConnectionFlags |= DISABLE_TRR | BYPASS_CACHE | REFRESH_CACHE;
+ tryAgain = true;
+ }
+ }
+ }
+ }
+
+ // prepare to try again.
+ if (tryAgain) {
+ uint32_t msg;
+
+ if (mState == STATE_CONNECTING) {
+ mState = STATE_RESOLVING;
+ msg = MSG_DNS_LOOKUP_COMPLETE;
+ } else {
+ mState = STATE_CLOSED;
+ msg = MSG_ENSURE_CONNECT;
+ }
+
+ rv = PostEvent(msg, NS_OK);
+ if (NS_FAILED(rv)) tryAgain = false;
+ }
+
+ return tryAgain;
+}
+
+// called on the socket thread only
+void nsSocketTransport::OnMsgInputClosed(nsresult reason) {
+ SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mInputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) {
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ } else if (mOutputClosed) {
+ mCondition =
+ NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ } else {
+ if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_READ;
+ mInput->OnSocketReady(reason);
+ }
+}
+
+// called on the socket thread only
+void nsSocketTransport::OnMsgOutputClosed(nsresult reason) {
+ SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mOutputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) {
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ } else if (mInputClosed) {
+ mCondition =
+ NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ } else {
+ if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_WRITE;
+ mOutput->OnSocketReady(reason);
+ }
+}
+
+void nsSocketTransport::OnSocketConnected() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG((" advancing to STATE_TRANSFERRING\n"));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mState = STATE_TRANSFERRING;
+
+ // Set the m*AddrIsSet flags only when state has reached TRANSFERRING
+ // because we need to make sure its value does not change due to failover
+ mNetAddrIsSet = true;
+
+ // assign mFD (must do this within the transport lock), but take care not
+ // to trample over mFDref if mFD is already set.
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(mFD.IsInitialized(), "no socket");
+ NS_ASSERTION(mFDref == 1, "wrong socket ref count");
+ SetSocketName(mFD);
+ mFDconnected = true;
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+
+ // Ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ SendStatus(NS_NET_STATUS_CONNECTED_TO);
+}
+
+void nsSocketTransport::SetSocketName(PRFileDesc* fd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mSelfAddrIsSet) {
+ return;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ if (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) {
+ PRNetAddrToNetAddr(&prAddr, &mSelfAddr);
+ mSelfAddrIsSet = true;
+ }
+}
+
+PRFileDesc* nsSocketTransport::GetFD_Locked() {
+ mLock.AssertCurrentThreadOwns();
+
+ // mFD is not available to the streams while disconnected.
+ if (!mFDconnected) return nullptr;
+
+ if (mFD.IsInitialized()) mFDref++;
+
+ return mFD;
+}
+
+class ThunkPRClose : public Runnable {
+ public:
+ explicit ThunkPRClose(PRFileDesc* fd)
+ : Runnable("net::ThunkPRClose"), mFD(fd) {}
+
+ NS_IMETHOD Run() override {
+ nsSocketTransport::CloseSocket(
+ mFD, gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return NS_OK;
+ }
+
+ private:
+ PRFileDesc* mFD;
+};
+
+void STS_PRCloseOnSocketTransport(PRFileDesc* fd, bool lingerPolarity,
+ int16_t lingerTimeout) {
+ if (gSocketTransportService) {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL);
+ } else {
+ // something horrible has happened
+ NS_ASSERTION(gSocketTransportService, "No STS service");
+ }
+}
+
+void nsSocketTransport::ReleaseFD_Locked(PRFileDesc* fd) {
+ mLock.AssertCurrentThreadOwns();
+
+ NS_ASSERTION(mFD == fd, "wrong fd");
+
+ if (--mFDref == 0) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ SOCKET_LOG(("Intentional leak"));
+ } else {
+ if (mLingerPolarity || mLingerTimeout) {
+ PRSocketOptionData socket_linger;
+ socket_linger.option = PR_SockOpt_Linger;
+ socket_linger.value.linger.polarity = mLingerPolarity;
+ socket_linger.value.linger.linger = mLingerTimeout;
+ PR_SetSocketOption(mFD, &socket_linger);
+ }
+ if (OnSocketThread()) {
+ SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this));
+ CloseSocket(
+ mFD, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ } else {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ STS_PRCloseOnSocketTransport(mFD, mLingerPolarity, mLingerTimeout);
+ }
+ }
+ mFD = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// socket event handler impl
+
+void nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status,
+ nsISupports* param,
+ std::function<void()>&& task) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(
+ ("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%" PRIx32
+ " param=%p]\n",
+ this, type, static_cast<uint32_t>(status), param));
+
+ if (NS_FAILED(mCondition)) {
+ // block event since we're apparently already dead.
+ SOCKET_LOG((" blocking event [condition=%" PRIx32 "]\n",
+ static_cast<uint32_t>(mCondition)));
+ //
+ // notify input/output streams in case either has a pending notify.
+ //
+ mInput->OnSocketReady(mCondition);
+ mOutput->OnSocketReady(mCondition);
+ return;
+ }
+
+ switch (type) {
+ case MSG_ENSURE_CONNECT:
+ SOCKET_LOG((" MSG_ENSURE_CONNECT\n"));
+ if (task) {
+ task();
+ }
+
+ // Apply port remapping here so that we do it on the socket thread and
+ // before we process the resolved DNS name or create the socket the first
+ // time.
+ if (!mPortRemappingApplied) {
+ mPortRemappingApplied = true;
+
+ mSocketTransportService->ApplyPortRemap(&mPort);
+ mSocketTransportService->ApplyPortRemap(&mOriginPort);
+ }
+
+ //
+ // ensure that we have created a socket, attached it, and have a
+ // connection.
+ //
+ if (mState == STATE_CLOSED) {
+ // Unix domain sockets are ready to connect; mNetAddr is all we
+ // need. Internet address families require a DNS lookup (or possibly
+ // several) before we can connect.
+#if defined(XP_UNIX)
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) {
+ mCondition = InitiateSocket();
+ } else {
+#else
+ {
+#endif
+ mCondition = ResolveHost();
+ }
+
+ } else {
+ SOCKET_LOG((" ignoring redundant event\n"));
+ }
+ break;
+
+ case MSG_DNS_LOOKUP_COMPLETE:
+ if (mDNSRequest) { // only send this if we actually resolved anything
+ SendStatus(NS_NET_STATUS_RESOLVED_HOST);
+ }
+
+ SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n"));
+ mDNSRequest = nullptr;
+
+ if (mDNSRecord) {
+ mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ mDNSRecord->IsTRR(&mResolvedByTRR);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+ // status contains DNS lookup status
+ if (NS_FAILED(status)) {
+ // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
+ // proxy host is not found, so we fixup the error code.
+ // For SOCKS proxies (mProxyTransparent == true), the socket
+ // transport resolves the real host here, so there's no fixup
+ // (see bug 226943).
+ if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
+ } else {
+ mCondition = status;
+ }
+ } else if (mState == STATE_RESOLVING) {
+ mCondition = InitiateSocket();
+ }
+ break;
+
+ case MSG_RETRY_INIT_SOCKET:
+ mCondition = InitiateSocket();
+ break;
+
+ case MSG_INPUT_CLOSED:
+ SOCKET_LOG((" MSG_INPUT_CLOSED\n"));
+ OnMsgInputClosed(status);
+ break;
+
+ case MSG_INPUT_PENDING:
+ SOCKET_LOG((" MSG_INPUT_PENDING\n"));
+ OnMsgInputPending();
+ break;
+
+ case MSG_OUTPUT_CLOSED:
+ SOCKET_LOG((" MSG_OUTPUT_CLOSED\n"));
+ OnMsgOutputClosed(status);
+ break;
+
+ case MSG_OUTPUT_PENDING:
+ SOCKET_LOG((" MSG_OUTPUT_PENDING\n"));
+ OnMsgOutputPending();
+ break;
+ case MSG_TIMEOUT_CHANGED:
+ SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n"));
+ {
+ MutexAutoLock lock(mLock);
+ mPollTimeout =
+ mTimeouts[(mState == STATE_TRANSFERRING) ? TIMEOUT_READ_WRITE
+ : TIMEOUT_CONNECT];
+ }
+ break;
+ default:
+ SOCKET_LOG((" unhandled event!\n"));
+ }
+
+ if (NS_FAILED(mCondition)) {
+ SOCKET_LOG((" after event [this=%p cond=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(mCondition)));
+ if (!mAttached) { // need to process this error ourselves...
+ OnSocketDetached(nullptr);
+ }
+ } else if (mPollFlags == PR_POLL_EXCEPT) {
+ mPollFlags = 0; // make idle
+ }
+}
+
+uint64_t nsSocketTransport::ByteCountReceived() { return mInput->ByteCount(); }
+
+uint64_t nsSocketTransport::ByteCountSent() { return mOutput->ByteCount(); }
+
+//-----------------------------------------------------------------------------
+// socket handler impl
+
+void nsSocketTransport::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG1(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n",
+ this, outFlags));
+
+ if (outFlags == -1) {
+ SOCKET_LOG(("socket timeout expired\n"));
+ mCondition = NS_ERROR_NET_TIMEOUT;
+ return;
+ }
+
+ if (mState == STATE_TRANSFERRING) {
+ // if waiting to write and socket is writable or hit an exception.
+ if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_WRITE;
+ mOutput->OnSocketReady(NS_OK);
+ }
+ // if waiting to read and socket is readable or hit an exception.
+ if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_READ;
+ mInput->OnSocketReady(NS_OK);
+ }
+ // Update poll timeout in case it was changed
+ {
+ MutexAutoLock lock(mLock);
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+ } else if ((mState == STATE_CONNECTING) && !gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ PRStatus status = PR_ConnectContinue(fd, outFlags);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(
+ connectStarted, Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mNetAddr.raw.family == AF_INET) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ }
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+ } else {
+ PRErrorCode code = PR_GetError();
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the connect is still not ready, then continue polling...
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
+ // Set up the select flags for connect...
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ // Update poll timeout in case it was changed
+ {
+ MutexAutoLock lock(mLock);
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ }
+ }
+ //
+ // The SOCKS proxy rejected our request. Find out why.
+ //
+ else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ mCondition = ErrorAccordingToNSPR(code);
+ } else {
+ //
+ // else, the connection failed...
+ //
+ mCondition = ErrorAccordingToNSPR(code);
+ if ((mCondition == NS_ERROR_CONNECTION_REFUSED) &&
+ !mProxyHost.IsEmpty()) {
+ mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ }
+ SOCKET_LOG((" connection failed! [reason=%" PRIx32 "]\n",
+ static_cast<uint32_t>(mCondition)));
+ }
+ }
+ } else if ((mState == STATE_CONNECTING) && gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+ SOCKET_LOG(
+ ("We are in shutdown so skip PR_ConnectContinue and set "
+ "and error.\n"));
+ mCondition = NS_ERROR_ABORT;
+ } else {
+ NS_ERROR("unexpected socket state");
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+
+ if (mPollFlags == PR_POLL_EXCEPT) mPollFlags = 0; // make idle
+}
+
+// called on the socket thread only
+void nsSocketTransport::OnSocketDetached(PRFileDesc* fd) {
+ SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(mCondition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mAttached = false;
+
+ // if we didn't initiate this detach, then be sure to pass an error
+ // condition up to our consumers. (e.g., STS is shutting down.)
+ if (NS_SUCCEEDED(mCondition)) {
+ if (gIOService->IsOffline()) {
+ mCondition = NS_ERROR_OFFLINE;
+ } else {
+ mCondition = NS_ERROR_ABORT;
+ }
+ }
+
+ // If we are not shutting down try again.
+ if (!gIOService->IsNetTearingDown() && RecoverFromError()) {
+ mCondition = NS_OK;
+ } else {
+ mState = STATE_CLOSED;
+
+ // make sure there isn't any pending DNS request
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ //
+ // notify input/output streams
+ //
+ mInput->OnSocketReady(mCondition);
+ mOutput->OnSocketReady(mCondition);
+ if (gIOService->IsNetTearingDown()) {
+ if (mInputCopyContext) {
+ NS_CancelAsyncCopy(mInputCopyContext, mCondition);
+ }
+ if (mOutputCopyContext) {
+ NS_CancelAsyncCopy(mOutputCopyContext, mCondition);
+ }
+ }
+ }
+
+ if (mCondition == NS_ERROR_NET_RESET && mDNSRecord &&
+ mOutput->ByteCount() == 0) {
+ // If we are here, it's likely that we are retrying a transaction. Blocking
+ // the already used address could increase the successful rate of the retry.
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+ // finally, release our reference to the socket (must do this within
+ // the transport lock) possibly closing the socket. Also release our
+ // listeners to break potential refcount cycles.
+
+ // We should be careful not to release mEventSink and mCallbacks while
+ // we're locked, because releasing it might require acquiring the lock
+ // again, so we just null out mEventSink and mCallbacks while we're
+ // holding the lock, and let the stack based objects' destuctors take
+ // care of destroying it if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
+ nsCOMPtr<nsITransportEventSink> ourEventSink;
+ {
+ MutexAutoLock lock(mLock);
+ if (mFD.IsInitialized()) {
+ ReleaseFD_Locked(mFD);
+ // flag mFD as unusable; this prevents other consumers from
+ // acquiring a reference to mFD.
+ mFDconnected = false;
+ }
+
+ // We must release mCallbacks and mEventSink to avoid memory leak
+ // but only when RecoverFromError() above failed. Otherwise we lose
+ // link with UI and security callbacks on next connection attempt
+ // round. That would lead e.g. to a broken certificate exception page.
+ if (NS_FAILED(mCondition)) {
+ mCallbacks.swap(ourCallbacks);
+ mEventSink.swap(ourEventSink);
+ }
+ }
+}
+
+void nsSocketTransport::IsLocal(bool* aIsLocal) {
+ {
+ MutexAutoLock lock(mLock);
+
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mNetAddr.raw.family == PR_AF_LOCAL) {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ *aIsLocal = mNetAddr.IsLoopbackAddr();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransport, nsISocketTransport, nsITransport,
+ nsIDNSListener, nsIClassInfo, nsIInterfaceRequestor)
+NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport, nsISocketTransport, nsITransport,
+ nsIDNSListener, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsSocketTransport::OpenInputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream** aResult) {
+ SOCKET_LOG(
+ ("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags));
+
+ NS_ENSURE_TRUE(!mInput->IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIInputStream> result;
+ nsCOMPtr<nsISupports> inputCopyContext;
+
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ // bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), !openBlocking,
+ true, segsize, segcount);
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(mInput.get(), pipeOut, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize, nullptr, nullptr,
+ true, true, getter_AddRefs(inputCopyContext));
+ if (NS_FAILED(rv)) return rv;
+
+ result = pipeIn;
+ } else {
+ result = mInput.get();
+ }
+
+ // flag input stream as open
+ mInputClosed = false;
+ // mInputCopyContext can be only touched on socket thread
+ auto task = [self = RefPtr{this}, inputCopyContext(inputCopyContext)]() {
+ MOZ_ASSERT(OnSocketThread());
+ self->mInputCopyContext = inputCopyContext;
+ };
+ rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream** aResult) {
+ SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this,
+ flags));
+
+ NS_ENSURE_TRUE(!mOutput->IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ nsCOMPtr<nsIOutputStream> result;
+ nsCOMPtr<nsISupports> outputCopyContext;
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ // bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true,
+ !openBlocking, segsize, segcount);
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(pipeIn, mOutput.get(), mSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize, nullptr, nullptr,
+ true, true, getter_AddRefs(outputCopyContext));
+ if (NS_FAILED(rv)) return rv;
+
+ result = pipeOut;
+ } else {
+ result = mOutput.get();
+ }
+
+ // flag output stream as open
+ mOutputClosed = false;
+
+ // mOutputCopyContext can be only touched on socket thread
+ auto task = [self = RefPtr{this}, outputCopyContext(outputCopyContext)]() {
+ MOZ_ASSERT(OnSocketThread());
+ self->mOutputCopyContext = outputCopyContext;
+ };
+ rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
+ if (NS_FAILED(rv)) return rv;
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Close(nsresult reason) {
+ SOCKET_LOG(("nsSocketTransport::Close %p reason=%" PRIx32, this,
+ static_cast<uint32_t>(reason)));
+
+ if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;
+
+ mDoNotRetryToConnect = true;
+
+ mInput->CloseWithStatus(reason);
+ mOutput->CloseWithStatus(reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTlsSocketControl(nsITLSSocketControl** tlsSocketControl) {
+ MutexAutoLock lock(mLock);
+ *tlsSocketControl = do_AddRef(mTLSSocketControl).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) {
+ MutexAutoLock lock(mLock);
+ *callbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
+ NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
+ GetCurrentSerialEventTarget(),
+ getter_AddRefs(threadsafeCallbacks));
+ MutexAutoLock lock(mLock);
+ mCallbacks = threadsafeCallbacks;
+ SOCKET_LOG(("Reset callbacks for tlsSocketInfo=%p callbacks=%p\n",
+ mTLSSocketControl.get(), mCallbacks.get()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetEventSink(nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ nsCOMPtr<nsITransportEventSink> temp;
+ if (target) {
+ nsresult rv =
+ net_NewTransportEventSinkProxy(getter_AddRefs(temp), sink, target);
+ if (NS_FAILED(rv)) return rv;
+ sink = temp.get();
+ }
+
+ MutexAutoLock lock(mLock);
+ mEventSink = sink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::IsAlive(bool* result) {
+ *result = false;
+
+ nsresult conditionWhileLocked = NS_OK;
+ PRFileDescAutoLock fd(this, &conditionWhileLocked);
+ if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) {
+ return NS_OK;
+ }
+
+ // XXX do some idle-time based checks??
+
+ char c;
+ int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+
+ if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR)) {
+ *result = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetHost(nsACString& host) {
+ host = SocketHost();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPort(int32_t* port) {
+ *port = (int32_t)SocketPort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult nsSocketTransport::GetOriginAttributes(
+ OriginAttributes* aOriginAttributes) {
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult nsSocketTransport::SetOriginAttributes(
+ const OriginAttributes& aOriginAttributes) {
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPeerAddr(NetAddr* addr) {
+ // once we are in the connected state, mNetAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mNetAddr from any thread without being
+ // inside a critical section.
+
+ if (!mNetAddrIsSet) {
+ SOCKET_LOG(
+ ("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.",
+ this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mNetAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSelfAddr(NetAddr* addr) {
+ // once we are in the connected state, mSelfAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mSelfAddr from any thread without being
+ // inside a critical section.
+
+ if (!mSelfAddrIsSet) {
+ SOCKET_LOG(
+ ("nsSocketTransport::GetSelfAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.",
+ this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mSelfAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Bind(NetAddr* aLocalAddr) {
+ NS_ENSURE_ARG(aLocalAddr);
+
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mAttached) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBindAddr = MakeUnique<NetAddr>();
+ memcpy(mBindAddr.get(), aLocalAddr, sizeof(NetAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptablePeerAddr(nsINetAddr** addr) {
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetPeerAddr(&rawAddr);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
+ netaddr.forget(addr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableSelfAddr(nsINetAddr** addr) {
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetSelfAddr(&rawAddr);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
+ netaddr.forget(addr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTimeout(uint32_t type, uint32_t* value) {
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+ MutexAutoLock lock(mLock);
+ *value = (uint32_t)mTimeouts[type];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetTimeout(uint32_t type, uint32_t value) {
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+
+ SOCKET_LOG(("nsSocketTransport::SetTimeout %p type=%u, value=%u", this, type,
+ value));
+
+ // truncate overly large timeout values.
+ {
+ MutexAutoLock lock(mLock);
+ mTimeouts[type] = (uint16_t)std::min<uint32_t>(value, UINT16_MAX);
+ }
+ PostEvent(MSG_TIMEOUT_CHANGED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetReuseAddrPort(bool reuseAddrPort) {
+ mReuseAddrPort = reuseAddrPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetLinger(bool aPolarity, int16_t aTimeout) {
+ MutexAutoLock lock(mLock);
+
+ mLingerPolarity = aPolarity;
+ mLingerTimeout = aTimeout;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetQoSBits(uint8_t aQoSBits) {
+ // Don't do any checking here of bits. Why? Because as of RFC-4594
+ // several different Class Selector and Assured Forwarding values
+ // have been defined, but that isn't to say more won't be added later.
+ // In that case, any checking would be an impediment to interoperating
+ // with newer QoS definitions.
+
+ mQoSBits = aQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetQoSBits(uint8_t* aQoSBits) {
+ *aQoSBits = mQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetRecvBufferSize(uint32_t* aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
+ *aSize = opt.value.recv_buffer_size;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSendBufferSize(uint32_t* aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
+ *aSize = opt.value.send_buffer_size;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetRecvBufferSize(uint32_t aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSendBufferSize(uint32_t aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ SOCKET_LOG(("nsSocketTransport::OnLookupComplete: this=%p status %" PRIx32
+ ".",
+ this, static_cast<uint32_t>(status)));
+
+ if (NS_SUCCEEDED(status)) {
+ mDNSRecord = do_QueryInterface(rec);
+ MOZ_ASSERT(mDNSRecord);
+ }
+
+ if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface(rec)) {
+ addrRecord->IsTRR(&mResolvedByTRR);
+ addrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ addrRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+
+ // flag host lookup complete for the benefit of the ResolveHost method.
+ mResolving = false;
+ nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);
+
+ // if posting a message fails, then we should assume that the socket
+ // transport has been shutdown. this should never happen! if it does
+ // it means that the socket transport service was shutdown before the
+ // DNS service.
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post DNS lookup complete message");
+ }
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+NS_IMETHODIMP
+nsSocketTransport::GetInterface(const nsIID& iid, void** result) {
+ if (iid.Equals(NS_GET_IID(nsIDNSRecord)) ||
+ iid.Equals(NS_GET_IID(nsIDNSAddrRecord))) {
+ return mDNSRecord ? mDNSRecord->QueryInterface(iid, result)
+ : NS_ERROR_NO_INTERFACE;
+ }
+ return this->QueryInterface(iid, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetInterfaces(nsTArray<nsIID>& array) {
+ return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(array);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableHelper(nsIXPCScriptable** _retval) {
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetContractID(nsACString& aContractID) {
+ aContractID.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassDescription(nsACString& aClassDescription) {
+ aClassDescription.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassID(nsCID** aClassID) {
+ *aClassID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetFlags(uint32_t* aFlags) {
+ *aFlags = nsIClassInfo::THREADSAFE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetConnectionFlags(uint32_t* value) {
+ *value = mConnectionFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetConnectionFlags(uint32_t value) {
+ SOCKET_LOG(
+ ("nsSocketTransport::SetConnectionFlags %p flags=%u", this, value));
+
+ mConnectionFlags = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetIsPrivate(bool aIsPrivate) {
+ mIsPrivate = aIsPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTlsFlags(uint32_t* value) {
+ *value = mTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetTlsFlags(uint32_t value) {
+ mTlsFlags = value;
+ return NS_OK;
+}
+
+void nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // The global pref toggles keepalive as a system feature; it only affects
+ // an individual socket if keepalive has been specifically enabled for it.
+ // So, ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(aEnabled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%" PRIx32 "]",
+ aEnabled ? "enable" : "disable", static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+nsresult nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable) {
+ MOZ_ASSERT(mKeepaliveIdleTimeS > 0 && mKeepaliveIdleTimeS <= kMaxTCPKeepIdle);
+ MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 &&
+ mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl);
+ MOZ_ASSERT(mKeepaliveProbeCount > 0 &&
+ mKeepaliveProbeCount <= kMaxTCPKeepCount);
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Only enable if keepalives are globally enabled, but ensure other
+ // options are set correctly on the fd.
+ bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled();
+ nsresult rv =
+ fd.SetKeepaliveVals(enable, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveVals failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ rv = fd.SetKeepaliveEnabled(enable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetKeepaliveEnabled(bool* aResult) {
+ MOZ_ASSERT(aResult);
+
+ *aResult = mKeepaliveEnabled;
+ return NS_OK;
+}
+
+nsresult nsSocketTransport::EnsureKeepaliveValsAreInitialized() {
+ nsresult rv = NS_OK;
+ int32_t val = -1;
+ if (mKeepaliveIdleTimeS == -1) {
+ rv = mSocketTransportService->GetKeepaliveIdleTime(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveIdleTimeS = val;
+ }
+ if (mKeepaliveRetryIntervalS == -1) {
+ rv = mSocketTransportService->GetKeepaliveRetryInterval(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveRetryIntervalS = val;
+ }
+ if (mKeepaliveProbeCount == -1) {
+ rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveEnabled(bool aEnable) {
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (aEnable == mKeepaliveEnabled) {
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.", this,
+ aEnable ? "enabled" : "disabled"));
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ if (aEnable) {
+ rv = EnsureKeepaliveValsAreInitialized();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG(
+ (" SetKeepaliveEnabled [%p] "
+ "error [0x%" PRIx32 "] initializing keepalive vals",
+ this, static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+ SOCKET_LOG(
+ ("nsSocketTransport::SetKeepaliveEnabled [%p] "
+ "%s, idle time[%ds] retry interval[%ds] packet count[%d]: "
+ "globally %s.",
+ this, aEnable ? "enabled" : "disabled", mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS, mKeepaliveProbeCount,
+ mSocketTransportService->IsKeepaliveEnabled() ? "enabled" : "disabled"));
+
+ // Set mKeepaliveEnabled here so that state is maintained; it is possible
+ // that we're in between fds, e.g. the 1st IP address failed, so we're about
+ // to retry on a 2nd from the DNS record.
+ mKeepaliveEnabled = aEnable;
+
+ rv = SetKeepaliveEnabledInternal(aEnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ return NS_OK;
+#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime, int32_t aRetryInterval) {
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aIdleTime == mKeepaliveIdleTimeS &&
+ aRetryInterval == mKeepaliveRetryIntervalS) {
+ SOCKET_LOG(
+ ("nsSocketTransport::SetKeepaliveVals [%p] idle time "
+ "already %ds and retry interval already %ds.",
+ this, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS));
+ return NS_OK;
+ }
+ mKeepaliveIdleTimeS = aIdleTime;
+ mKeepaliveRetryIntervalS = aRetryInterval;
+
+ nsresult rv = NS_OK;
+ if (mKeepaliveProbeCount == -1) {
+ int32_t val = -1;
+ nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+
+ SOCKET_LOG(
+ ("nsSocketTransport::SetKeepaliveVals [%p] "
+ "keepalive %s, idle time[%ds] retry interval[%ds] "
+ "packet count[%d]",
+ this, mKeepaliveEnabled ? "enabled" : "disabled", mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS, mKeepaliveProbeCount));
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ rv = fd.SetKeepaliveVals(mKeepaliveEnabled, mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS, mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+#else
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+#ifdef ENABLE_SOCKET_TRACING
+
+# include <stdio.h>
+# include <ctype.h>
+# include "prenv.h"
+
+static void DumpBytesToFile(const char* path, const char* header,
+ const char* buf, int32_t n) {
+ FILE* fp = fopen(path, "a");
+
+ fprintf(fp, "\n%s [%d bytes]\n", header, n);
+
+ const unsigned char* p;
+ while (n) {
+ p = (const unsigned char*)buf;
+
+ int32_t i, row_max = std::min(16, n);
+
+ for (i = 0; i < row_max; ++i) fprintf(fp, "%02x ", *p++);
+ for (i = row_max; i < 16; ++i) fprintf(fp, " ");
+
+ p = (const unsigned char*)buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ if (isprint(*p))
+ fprintf(fp, "%c", *p);
+ else
+ fprintf(fp, ".");
+ }
+
+ fprintf(fp, "\n");
+ buf += row_max;
+ n -= row_max;
+ }
+
+ fprintf(fp, "\n");
+ fclose(fp);
+}
+
+void nsSocketTransport::TraceInBuf(const char* buf, int32_t n) {
+ char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val) return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Reading from: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+void nsSocketTransport::TraceOutBuf(const char* buf, int32_t n) {
+ char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val) return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Writing to: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+#endif
+
+static void LogNSPRError(const char* aPrefix, const void* aObjPtr) {
+#if defined(DEBUG)
+ PRErrorCode errCode = PR_GetError();
+ int errLen = PR_GetErrorTextLength();
+ nsAutoCString errStr;
+ if (errLen > 0) {
+ errStr.SetLength(errLen);
+ PR_GetErrorText(errStr.BeginWriting());
+ }
+ NS_WARNING(
+ nsPrintfCString("%s [%p] NSPR error[0x%x] %s.",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
+ errLen > 0 ? errStr.BeginReading() : "<no error text>")
+ .get());
+#endif
+}
+
+nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(
+ bool aEnable) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()),
+ "Cannot enable keepalive if global pref is disabled!");
+ if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Keepalive;
+ opt.value.keep_alive = aEnable;
+ PRStatus status = PR_SetSocketOption(mFd, &opt);
+ if (NS_WARN_IF(status != PR_SUCCESS)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+ return NS_OK;
+}
+
+static void LogOSError(const char* aPrefix, const void* aObjPtr) {
+#if defined(DEBUG)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+# ifdef XP_WIN
+ DWORD errCode = WSAGetLastError();
+ char* errMessage;
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&errMessage, 0, NULL);
+ NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%lx] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
+ errCode,
+ errMessage ? errMessage : "<no error text>")
+ .get());
+ LocalFree(errMessage);
+# else
+ int errCode = errno;
+ char* errMessage = strerror(errno);
+ NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
+ errCode,
+ errMessage ? errMessage : "<no error text>")
+ .get());
+# endif
+#endif
+}
+
+/* XXX PR_SetSockOpt does not support setting keepalive values, so native
+ * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
+ * file. Requires inclusion of NSPR private/pprio.h, and platform headers.
+ */
+
+nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(
+ bool aEnabled, int aIdleTime, int aRetryInterval, int aProbeCount) {
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROsfd sock = PR_FileDesc2NativeHandle(mFd);
+ if (NS_WARN_IF(sock == -1)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+#endif
+
+#if defined(XP_WIN)
+ // Windows allows idle time and retry interval to be set; NOT ping count.
+ struct tcp_keepalive keepalive_vals = {(u_long)aEnabled,
+ // Windows uses msec.
+ (u_long)(aIdleTime * 1000UL),
+ (u_long)(aRetryInterval * 1000UL)};
+ DWORD bytes_returned;
+ int err =
+ WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals,
+ sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, NULL);
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_DARWIN)
+ // Darwin uses sec; only supports idle time being set.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &aIdleTime,
+ sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_UNIX)
+ // Not all *nix OSes support the following setsockopt() options
+ // ... but we assume they are supported in the Android kernel;
+ // build errors will tell us if they are not.
+# if defined(ANDROID) || defined(TCP_KEEPIDLE)
+ // Idle time until first keepalive probe; interval between ack'd probes;
+ // seconds.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &aIdleTime,
+ sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+# endif
+# if defined(ANDROID) || defined(TCP_KEEPINTVL)
+ // Interval between unack'd keepalive probes; seconds.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &aRetryInterval,
+ sizeof(aRetryInterval));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+# endif
+# if defined(ANDROID) || defined(TCP_KEEPCNT)
+ // Number of unack'd keepalive probes before connection times out.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &aProbeCount,
+ sizeof(aProbeCount));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+# endif
+ return NS_OK;
+#else
+ MOZ_ASSERT(false,
+ "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals "
+ "called on unsupported platform!");
+ return NS_ERROR_UNEXPECTED;
+#endif
+}
+
+void nsSocketTransport::CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(aFd);
+#endif
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime closeStarted;
+ if (aTelemetryEnabled) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(aFd);
+
+ if (aTelemetryEnabled) {
+ SendPRBlockingTelemetry(
+ closeStarted, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
+ }
+}
+
+void nsSocketTransport::SendPRBlockingTelemetry(
+ PRIntervalTime aStart, Telemetry::HistogramID aIDNormal,
+ Telemetry::HistogramID aIDShutdown,
+ Telemetry::HistogramID aIDConnectivityChange,
+ Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline) {
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(aIDShutdown, PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange()) <
+ 60) {
+ Telemetry::Accumulate(aIDConnectivityChange,
+ PR_IntervalToMilliseconds(now - aStart));
+ } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange()) <
+ 60) {
+ Telemetry::Accumulate(aIDLinkChange,
+ PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange()) <
+ 60) {
+ Telemetry::Accumulate(aIDOffline, PR_IntervalToMilliseconds(now - aStart));
+ } else {
+ Telemetry::Accumulate(aIDNormal, PR_IntervalToMilliseconds(now - aStart));
+ }
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetResetIPFamilyPreference(bool* aReset) {
+ *aReset = mResetFamilyPreference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetEchConfigUsed(bool* aEchConfigUsed) {
+ *aEchConfigUsed = mEchConfigUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetEchConfig(const nsACString& aEchConfig) {
+ mEchConfig = aEchConfig;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::ResolvedByTRR(bool* aResolvedByTRR) {
+ *aResolvedByTRR = mResolvedByTRR;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSocketTransport::GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) {
+ *aEffectiveTRRMode = mEffectiveTRRMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSocketTransport::GetTrrSkipReason(
+ nsITRRSkipReason::value* aSkipReason) {
+ *aSkipReason = mTRRSkipReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetRetryDnsIfPossible(bool* aRetryDns) {
+ *aRetryDns = mRetryDnsIfPossible;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetStatus(nsresult* aStatus) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ *aStatus = mCondition;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h
new file mode 100644
index 0000000000..7dd94abac7
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.h
@@ -0,0 +1,510 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransport2_h__
+#define nsSocketTransport2_h__
+
+#ifdef DEBUG_darinf
+# define ENABLE_SOCKET_TRACING
+#endif
+
+#include <functional>
+
+#include "mozilla/Mutex.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIClassInfo.h"
+#include "mozilla/net/DNS.h"
+#include "nsASocketHandler.h"
+#include "mozilla/Telemetry.h"
+
+#include "prerror.h"
+#include "ssl.h"
+
+class nsICancelable;
+class nsIDNSRecord;
+class nsIInterfaceRequestor;
+
+//-----------------------------------------------------------------------------
+
+// after this short interval, we will return to PR_Poll
+#define NS_SOCKET_CONNECT_TIMEOUT PR_MillisecondsToInterval(20)
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsresult ErrorAccordingToNSPR(PRErrorCode errorCode);
+
+class nsSocketInputStream;
+class nsSocketOutputStream;
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransport final : public nsASocketHandler,
+ public nsISocketTransport,
+ public nsIDNSListener,
+ public nsIClassInfo,
+ public nsIInterfaceRequestor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsSocketTransport();
+
+ // this method instructs the socket transport to open a socket of the
+ // given type(s) to the given host or proxy.
+ nsresult Init(const nsTArray<nsCString>& socketTypes, const nsACString& host,
+ uint16_t port, const nsACString& hostRoute, uint16_t portRoute,
+ nsIProxyInfo* proxyInfo, nsIDNSRecord* dnsRecord);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address.
+ nsresult InitWithConnectedSocket(PRFileDesc* socketFD, const NetAddr* addr);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address, and additionally supplies the security
+ // callbacks interface requestor.
+ nsresult InitWithConnectedSocket(PRFileDesc* aFD, const NetAddr* aAddr,
+ nsIInterfaceRequestor* aCallbacks);
+
+#ifdef XP_UNIX
+ // This method instructs the socket transport to open a socket
+ // connected to the given Unix domain address. We can only create
+ // unlayered, simple, stream sockets.
+ nsresult InitWithFilename(const char* filename);
+
+ // This method instructs the socket transport to open a socket
+ // connected to the given Unix domain address that includes abstract
+ // socket address. If using abstract socket address, first character of
+ // name parameter has to be \0.
+ // We can only create unlayered, simple, stream sockets.
+ nsresult InitWithName(const char* name, size_t len);
+#endif
+
+ // nsASocketHandler methods:
+ void OnSocketReady(PRFileDesc*, int16_t outFlags) override;
+ void OnSocketDetached(PRFileDesc*) override;
+ void IsLocal(bool* aIsLocal) override;
+ void OnKeepaliveEnabledPrefChange(bool aEnabled) final;
+
+ // called when a socket event is handled
+ void OnSocketEvent(uint32_t type, nsresult status, nsISupports* param,
+ std::function<void()>&& task);
+
+ uint64_t ByteCountReceived() override;
+ uint64_t ByteCountSent() override;
+ static void CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled);
+ static void SendPRBlockingTelemetry(
+ PRIntervalTime aStart, Telemetry::HistogramID aIDNormal,
+ Telemetry::HistogramID aIDShutdown,
+ Telemetry::HistogramID aIDConnectivityChange,
+ Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline);
+
+ protected:
+ virtual ~nsSocketTransport();
+
+ private:
+ // event types
+ enum {
+ MSG_ENSURE_CONNECT,
+ MSG_DNS_LOOKUP_COMPLETE,
+ MSG_RETRY_INIT_SOCKET,
+ MSG_TIMEOUT_CHANGED,
+ MSG_INPUT_CLOSED,
+ MSG_INPUT_PENDING,
+ MSG_OUTPUT_CLOSED,
+ MSG_OUTPUT_PENDING
+ };
+ nsresult PostEvent(uint32_t type, nsresult status = NS_OK,
+ nsISupports* param = nullptr,
+ std::function<void()>&& task = nullptr);
+
+ enum {
+ STATE_CLOSED,
+ STATE_IDLE,
+ STATE_RESOLVING,
+ STATE_CONNECTING,
+ STATE_TRANSFERRING
+ };
+
+ // Safer way to get and automatically release PRFileDesc objects.
+ class MOZ_STACK_CLASS PRFileDescAutoLock {
+ public:
+ explicit PRFileDescAutoLock(nsSocketTransport* aSocketTransport,
+ nsresult* aConditionWhileLocked = nullptr)
+ : mSocketTransport(aSocketTransport), mFd(nullptr) {
+ MOZ_ASSERT(aSocketTransport);
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (aConditionWhileLocked) {
+ *aConditionWhileLocked = mSocketTransport->mCondition;
+ if (NS_FAILED(mSocketTransport->mCondition)) {
+ return;
+ }
+ }
+ mFd = mSocketTransport->GetFD_Locked();
+ }
+ ~PRFileDescAutoLock() {
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (mFd) {
+ mSocketTransport->ReleaseFD_Locked(mFd);
+ }
+ }
+ bool IsInitialized() { return mFd; }
+ operator PRFileDesc*() { return mFd; }
+ nsresult SetKeepaliveEnabled(bool aEnable);
+ nsresult SetKeepaliveVals(bool aEnabled, int aIdleTime, int aRetryInterval,
+ int aProbeCount);
+
+ private:
+ operator PRFileDescAutoLock*() { return nullptr; }
+
+ // Weak ptr to nsSocketTransport since this is a stack class only.
+ nsSocketTransport* mSocketTransport;
+ PRFileDesc* mFd;
+ };
+ friend class PRFileDescAutoLock;
+
+ class LockedPRFileDesc {
+ public:
+ explicit LockedPRFileDesc(nsSocketTransport* aSocketTransport)
+ : mSocketTransport(aSocketTransport), mFd(nullptr) {
+ MOZ_ASSERT(aSocketTransport);
+ }
+ ~LockedPRFileDesc() = default;
+ bool IsInitialized() { return mFd; }
+ LockedPRFileDesc& operator=(PRFileDesc* aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ mFd = aFd;
+ return *this;
+ }
+ operator PRFileDesc*() {
+ if (mSocketTransport->mAttached) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ }
+ return mFd;
+ }
+ bool operator==(PRFileDesc* aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ return mFd == aFd;
+ }
+
+ private:
+ operator LockedPRFileDesc*() { return nullptr; }
+ // Weak ptr to nsSocketTransport since it owns this class.
+ nsSocketTransport* mSocketTransport;
+ PRFileDesc* mFd;
+ };
+ friend class LockedPRFileDesc;
+
+ //-------------------------------------------------------------------------
+ // these members are "set" at initialization time and are never modified
+ // afterwards. this allows them to be safely accessed from any thread.
+ //-------------------------------------------------------------------------
+
+ // socket type info:
+ nsTArray<nsCString> mTypes;
+ nsCString mHost;
+ nsCString mProxyHost;
+ nsCString mOriginHost;
+ uint16_t mPort{0};
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ uint16_t mProxyPort{0};
+ uint16_t mOriginPort{0};
+ bool mProxyTransparent{false};
+ bool mProxyTransparentResolvesHost{false};
+ bool mHttpsProxy{false};
+ uint32_t mConnectionFlags{0};
+ // When we fail to connect using a prefered IP family, we tell the consumer to
+ // reset the IP family preference on the connection entry.
+ bool mResetFamilyPreference{false};
+ uint32_t mTlsFlags{0};
+ bool mReuseAddrPort{false};
+
+ // The origin attributes are used to create sockets. The first party domain
+ // will eventually be used to isolate OCSP cache and is only non-empty when
+ // "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ // carry origin attributes down to NSPR layers which are final consumers.
+ // It must be set before the socket transport is built.
+ OriginAttributes mOriginAttributes;
+
+ uint16_t SocketPort() {
+ return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort;
+ }
+ const nsCString& SocketHost() {
+ return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost;
+ }
+
+ Atomic<bool> mInputClosed{true};
+ Atomic<bool> mOutputClosed{true};
+
+ //-------------------------------------------------------------------------
+ // members accessible only on the socket transport thread:
+ // (the exception being initialization/shutdown time)
+ //-------------------------------------------------------------------------
+
+ // socket state vars:
+ uint32_t mState{STATE_CLOSED}; // STATE_??? flags
+ bool mAttached{false};
+
+ // this flag is used to determine if the results of a host lookup arrive
+ // recursively or not. this flag is not protected by any lock.
+ bool mResolving{false};
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ nsCOMPtr<nsIDNSAddrRecord> mDNSRecord;
+
+ nsCString mEchConfig;
+ bool mEchConfigUsed = false;
+ bool mResolvedByTRR{false};
+ nsIRequest::TRRMode mEffectiveTRRMode{nsIRequest::TRR_DEFAULT_MODE};
+ nsITRRSkipReason::value mTRRSkipReason{nsITRRSkipReason::TRR_UNSET};
+
+ nsCOMPtr<nsISupports> mInputCopyContext;
+ nsCOMPtr<nsISupports> mOutputCopyContext;
+
+ // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have
+ // reached STATE_TRANSFERRING. It must not change after that.
+ void SetSocketName(PRFileDesc* fd);
+ NetAddr mNetAddr;
+ NetAddr mSelfAddr; // getsockname()
+ Atomic<bool, Relaxed> mNetAddrIsSet{false};
+ Atomic<bool, Relaxed> mSelfAddrIsSet{false};
+
+ UniquePtr<NetAddr> mBindAddr;
+
+ // socket methods (these can only be called on the socket thread):
+
+ void SendStatus(nsresult status);
+ nsresult ResolveHost();
+ nsresult BuildSocket(PRFileDesc*&, bool&, bool&);
+ nsresult InitiateSocket();
+ bool RecoverFromError();
+
+ void OnMsgInputPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mState == STATE_TRANSFERRING) {
+ mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT);
+ }
+ }
+ void OnMsgOutputPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mState == STATE_TRANSFERRING) {
+ mPollFlags |= (PR_POLL_WRITE | PR_POLL_EXCEPT);
+ }
+ }
+ void OnMsgInputClosed(nsresult reason);
+ void OnMsgOutputClosed(nsresult reason);
+
+ // called when the socket is connected
+ void OnSocketConnected();
+
+ //-------------------------------------------------------------------------
+ // socket input/output objects. these may be accessed on any thread with
+ // the exception of some specific methods (XXX).
+
+ // protects members in this section.
+ Mutex mLock{"nsSocketTransport.mLock"};
+ LockedPRFileDesc mFD MOZ_GUARDED_BY(mLock);
+ // mFD is closed when mFDref goes to zero.
+ nsrefcnt mFDref MOZ_GUARDED_BY(mLock){0};
+ // mFD is available to consumer when TRUE.
+ bool mFDconnected MOZ_GUARDED_BY(mLock){false};
+
+ // A delete protector reference to gSocketTransportService held for lifetime
+ // of 'this'. Sometimes used interchangably with gSocketTransportService due
+ // to scoping.
+ RefPtr<nsSocketTransportService> mSocketTransportService;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsITLSSocketControl> mTLSSocketControl;
+
+ UniquePtr<nsSocketInputStream> mInput;
+ UniquePtr<nsSocketOutputStream> mOutput;
+
+ friend class nsSocketInputStream;
+ friend class nsSocketOutputStream;
+
+ // socket timeouts are protected by mLock.
+ uint16_t mTimeouts[2]{0};
+
+ // linger options to use when closing
+ bool mLingerPolarity{false};
+ int16_t mLingerTimeout{0};
+
+ // QoS setting for socket
+ uint8_t mQoSBits{0x00};
+
+ //
+ // mFD access methods: called with mLock held.
+ //
+ PRFileDesc* GetFD_Locked() MOZ_REQUIRES(mLock);
+ void ReleaseFD_Locked(PRFileDesc* fd) MOZ_REQUIRES(mLock);
+
+ //
+ // stream state changes (called outside mLock):
+ //
+ void OnInputClosed(nsresult reason) {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgInputClosed(reason);
+ } else {
+ PostEvent(MSG_INPUT_CLOSED, reason);
+ }
+ }
+ void OnInputPending() {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgInputPending();
+ } else {
+ PostEvent(MSG_INPUT_PENDING);
+ }
+ }
+ void OnOutputClosed(nsresult reason) {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgOutputClosed(reason); // XXX need to not be inside lock!
+ } else {
+ PostEvent(MSG_OUTPUT_CLOSED, reason);
+ }
+ }
+ void OnOutputPending() {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgOutputPending();
+ } else {
+ PostEvent(MSG_OUTPUT_PENDING);
+ }
+ }
+
+#ifdef ENABLE_SOCKET_TRACING
+ void TraceInBuf(const char* buf, int32_t n);
+ void TraceOutBuf(const char* buf, int32_t n);
+#endif
+
+ // Reads prefs to get default keepalive config.
+ nsresult EnsureKeepaliveValsAreInitialized();
+
+ // Groups calls to fd.SetKeepaliveEnabled and fd.SetKeepaliveVals.
+ nsresult SetKeepaliveEnabledInternal(bool aEnable);
+
+ // True if keepalive has been enabled by the socket owner. Note: Keepalive
+ // must also be enabled globally for it to be enabled in TCP.
+ bool mKeepaliveEnabled{false};
+
+ // Keepalive config (support varies by platform).
+ int32_t mKeepaliveIdleTimeS{-1};
+ int32_t mKeepaliveRetryIntervalS{-1};
+ int32_t mKeepaliveProbeCount{-1};
+
+ Atomic<bool> mDoNotRetryToConnect{false};
+
+ // Whether the port remapping has already been applied. We definitely want to
+ // prevent duplicate calls in case of chaining remapping.
+ bool mPortRemappingApplied = false;
+
+ bool mExternalDNSResolution = false;
+ bool mRetryDnsIfPossible = false;
+};
+
+class nsSocketInputStream : public nsIAsyncInputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsSocketInputStream(nsSocketTransport*);
+ virtual ~nsSocketInputStream() = default;
+
+ // nsSocketTransport holds a ref to us
+ bool IsReferenced() { return mReaderRefCnt > 0; }
+ nsresult Condition() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+ }
+ uint64_t ByteCount() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mByteCount;
+ }
+ uint64_t ByteCount(MutexAutoLock&) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ return mByteCount;
+ }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+ private:
+ nsSocketTransport* mTransport;
+ ThreadSafeAutoRefCnt mReaderRefCnt{0};
+
+ // access to these should be protected by mTransport->mLock but we
+ // can't order things to allow using MOZ_GUARDED_BY().
+ nsresult mCondition MOZ_GUARDED_BY(mTransport->mLock){NS_OK};
+ nsCOMPtr<nsIInputStreamCallback> mCallback MOZ_GUARDED_BY(mTransport->mLock);
+ uint32_t mCallbackFlags MOZ_GUARDED_BY(mTransport->mLock){0};
+ uint64_t mByteCount MOZ_GUARDED_BY(mTransport->mLock){0};
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketOutputStream : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit nsSocketOutputStream(nsSocketTransport*);
+ virtual ~nsSocketOutputStream() = default;
+
+ // nsSocketTransport holds a ref to us
+ bool IsReferenced() { return mWriterRefCnt > 0; }
+ nsresult Condition() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+ }
+ uint64_t ByteCount() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mByteCount;
+ }
+ uint64_t ByteCount(MutexAutoLock&) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ return mByteCount;
+ }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+ private:
+ static nsresult WriteFromSegments(nsIInputStream*, void*, const char*,
+ uint32_t offset, uint32_t count,
+ uint32_t* countRead);
+
+ nsSocketTransport* mTransport;
+ ThreadSafeAutoRefCnt mWriterRefCnt{0};
+
+ // access to these should be protected by mTransport->mLock but we
+ // can't order things to allow using MOZ_GUARDED_BY().
+ nsresult mCondition MOZ_GUARDED_BY(mTransport->mLock){NS_OK};
+ nsCOMPtr<nsIOutputStreamCallback> mCallback MOZ_GUARDED_BY(mTransport->mLock);
+ uint32_t mCallbackFlags MOZ_GUARDED_BY(mTransport->mLock){0};
+ uint64_t mByteCount MOZ_GUARDED_BY(mTransport->mLock){0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransport_h__
diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp
new file mode 100644
index 0000000000..a6aa249465
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -0,0 +1,1899 @@
+// vim:set sw=2 sts=2 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+
+#include "IOActivityMonitor.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/PublicSSL.h"
+#include "mozilla/ReverseIterator.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Telemetry.h"
+#include "nsASocketHandler.h"
+#include "nsError.h"
+#include "nsIFile.h"
+#include "nsINetworkLinkService.h"
+#include "nsIOService.h"
+#include "nsIObserverService.h"
+#include "nsIWidget.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsThreadUtils.h"
+#include "prerror.h"
+#include "prnetdb.h"
+
+namespace mozilla {
+namespace net {
+
+#define SOCKET_THREAD_LONGTASK_MS 3
+
+LazyLogModule gSocketTransportLog("nsSocketTransport");
+LazyLogModule gUDPSocketLog("UDPSocket");
+LazyLogModule gTCPSocketLog("TCPSocket");
+
+nsSocketTransportService* gSocketTransportService = nullptr;
+static Atomic<PRThread*, Relaxed> gSocketThread(nullptr);
+
+#define SEND_BUFFER_PREF "network.tcp.sendbuffer"
+#define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled"
+#define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time"
+#define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval"
+#define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count"
+#define SOCKET_LIMIT_TARGET 1000U
+#define MAX_TIME_BETWEEN_TWO_POLLS \
+ "network.sts.max_time_for_events_between_two_polls"
+#define POLL_BUSY_WAIT_PERIOD "network.sts.poll_busy_wait_period"
+#define POLL_BUSY_WAIT_PERIOD_TIMEOUT \
+ "network.sts.poll_busy_wait_period_timeout"
+#define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN \
+ "network.sts.max_time_for_pr_close_during_shutdown"
+#define POLLABLE_EVENT_TIMEOUT "network.sts.pollable_event_timeout"
+
+#define REPAIR_POLLABLE_EVENT_TIME 10
+
+uint32_t nsSocketTransportService::gMaxCount;
+PRCallOnceType nsSocketTransportService::gMaxCountInitOnce;
+
+// Utility functions
+bool OnSocketThread() { return PR_GetCurrentThread() == gSocketThread; }
+
+//-----------------------------------------------------------------------------
+
+bool nsSocketTransportService::SocketContext::IsTimedOut(
+ PRIntervalTime now) const {
+ return TimeoutIn(now) == 0;
+}
+
+void nsSocketTransportService::SocketContext::EnsureTimeout(
+ PRIntervalTime now) {
+ SOCKET_LOG(("SocketContext::EnsureTimeout socket=%p", mHandler.get()));
+ if (!mPollStartEpoch) {
+ SOCKET_LOG((" engaging"));
+ mPollStartEpoch = now;
+ }
+}
+
+void nsSocketTransportService::SocketContext::DisengageTimeout() {
+ SOCKET_LOG(("SocketContext::DisengageTimeout socket=%p", mHandler.get()));
+ mPollStartEpoch = 0;
+}
+
+PRIntervalTime nsSocketTransportService::SocketContext::TimeoutIn(
+ PRIntervalTime now) const {
+ SOCKET_LOG(("SocketContext::TimeoutIn socket=%p, timeout=%us", mHandler.get(),
+ mHandler->mPollTimeout));
+
+ if (mHandler->mPollTimeout == UINT16_MAX || !mPollStartEpoch) {
+ SOCKET_LOG((" not engaged"));
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+
+ PRIntervalTime elapsed = (now - mPollStartEpoch);
+ PRIntervalTime timeout = PR_SecondsToInterval(mHandler->mPollTimeout);
+
+ if (elapsed >= timeout) {
+ SOCKET_LOG((" timed out!"));
+ return 0;
+ }
+ SOCKET_LOG((" remains %us", PR_IntervalToSeconds(timeout - elapsed)));
+ return timeout - elapsed;
+}
+
+void nsSocketTransportService::SocketContext::MaybeResetEpoch() {
+ if (mPollStartEpoch && mHandler->mPollTimeout == UINT16_MAX) {
+ mPollStartEpoch = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ctor/dtor (called on the main/UI thread by the service manager)
+
+nsSocketTransportService::nsSocketTransportService()
+ : mPollableEventTimeout(TimeDuration::FromSeconds(6)),
+ mMaxTimeForPrClosePref(PR_SecondsToInterval(5)),
+ mNetworkLinkChangeBusyWaitPeriod(PR_SecondsToInterval(50)),
+ mNetworkLinkChangeBusyWaitTimeout(PR_SecondsToInterval(7)) {
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount);
+
+ NS_ASSERTION(!gSocketTransportService, "must not instantiate twice");
+ gSocketTransportService = this;
+
+ // The Poll list always has an entry at [0]. The rest of the
+ // list is a duplicate of the Active list's PRFileDesc file descriptors.
+ PRPollDesc entry = {nullptr, PR_POLL_READ | PR_POLL_EXCEPT, 0};
+ mPollList.InsertElementAt(0, entry);
+}
+
+void nsSocketTransportService::ApplyPortRemap(uint16_t* aPort) {
+ MOZ_ASSERT(IsOnCurrentThreadInfallible());
+
+ if (!mPortRemapping) {
+ return;
+ }
+
+ // Reverse the array to make later rules override earlier rules.
+ for (auto const& portMapping : Reversed(*mPortRemapping)) {
+ if (*aPort < std::get<0>(portMapping)) {
+ continue;
+ }
+ if (*aPort > std::get<1>(portMapping)) {
+ continue;
+ }
+
+ *aPort = std::get<2>(portMapping);
+ return;
+ }
+}
+
+bool nsSocketTransportService::UpdatePortRemapPreference(
+ nsACString const& aPortMappingPref) {
+ TPortRemapping portRemapping;
+
+ auto consumePreference = [&]() -> bool {
+ Tokenizer tokenizer(aPortMappingPref);
+
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckEOF()) {
+ return true;
+ }
+
+ nsTArray<std::tuple<uint16_t, uint16_t>> ranges(2);
+ while (true) {
+ uint16_t loPort;
+ tokenizer.SkipWhites();
+ if (!tokenizer.ReadInteger(&loPort)) {
+ break;
+ }
+
+ uint16_t hiPort;
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckChar('-')) {
+ tokenizer.SkipWhites();
+ if (!tokenizer.ReadInteger(&hiPort)) {
+ break;
+ }
+ } else {
+ hiPort = loPort;
+ }
+
+ ranges.AppendElement(std::make_tuple(loPort, hiPort));
+
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckChar(',')) {
+ continue; // another port or port range is expected
+ }
+
+ if (tokenizer.CheckChar('=')) {
+ uint16_t targetPort;
+ tokenizer.SkipWhites();
+ if (!tokenizer.ReadInteger(&targetPort)) {
+ break;
+ }
+
+ // Storing reversed, because the most common cases (like 443) will very
+ // likely be listed as first, less common cases will be added to the end
+ // of the list mapping to the same port. As we iterate the whole
+ // remapping array from the end, this may have a small perf win by
+ // hitting the most common cases earlier.
+ for (auto const& range : Reversed(ranges)) {
+ portRemapping.AppendElement(std::make_tuple(
+ std::get<0>(range), std::get<1>(range), targetPort));
+ }
+ ranges.Clear();
+
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckChar(';')) {
+ continue; // more mappings (or EOF) expected
+ }
+ if (tokenizer.CheckEOF()) {
+ return true;
+ }
+ }
+
+ // Anything else is unexpected.
+ break;
+ }
+
+ // 'break' from the parsing loop means ill-formed preference
+ portRemapping.Clear();
+ return false;
+ };
+
+ bool rv = consumePreference();
+
+ if (!IsOnCurrentThread()) {
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ if (!thread) {
+ // Init hasn't been called yet. Could probably just assert.
+ // If shutdown, the dispatch below will just silently fail.
+ NS_ASSERTION(false, "ApplyPortRemapPreference before STS::Init");
+ return false;
+ }
+ thread->Dispatch(NewRunnableMethod<TPortRemapping>(
+ "net::ApplyPortRemapping", this,
+ &nsSocketTransportService::ApplyPortRemapPreference, portRemapping));
+ } else {
+ ApplyPortRemapPreference(portRemapping);
+ }
+
+ return rv;
+}
+
+nsSocketTransportService::~nsSocketTransportService() {
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+ NS_ASSERTION(!mInitialized, "not shutdown properly");
+
+ gSocketTransportService = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// event queue (any thread)
+
+already_AddRefed<nsIThread> nsSocketTransportService::GetThreadSafely() {
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIThread> result = mThread;
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DispatchFromScript(nsIRunnable* event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ return Dispatch(event_ref.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Dispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ SOCKET_LOG(("STS dispatch [%p]\n", event_ref.get()));
+
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ nsresult rv;
+ rv = thread ? thread->Dispatch(event_ref.forget(), flags)
+ : NS_ERROR_NOT_INITIALIZED;
+ if (rv == NS_ERROR_UNEXPECTED) {
+ // Thread is no longer accepting events. We must have just shut it
+ // down on the main thread. Pretend we never saw it.
+ rv = NS_ERROR_NOT_INITIALIZED;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>,
+ uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::RegisterShutdownTask(nsITargetShutdownTask* task) {
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ return thread ? thread->RegisterShutdownTask(task) : NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::UnregisterShutdownTask(nsITargetShutdownTask* task) {
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ return thread ? thread->UnregisterShutdownTask(task) : NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::IsOnCurrentThread(bool* result) {
+ *result = OnSocketThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsSocketTransportService::IsOnCurrentThreadInfallible() {
+ return OnSocketThread();
+}
+
+//-----------------------------------------------------------------------------
+// nsIDirectTaskDispatcher
+
+already_AddRefed<nsIDirectTaskDispatcher>
+nsSocketTransportService::GetDirectTaskDispatcherSafely() {
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIDirectTaskDispatcher> result = mDirectTaskDispatcher;
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DispatchDirectTask(
+ already_AddRefed<nsIRunnable> aEvent) {
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ GetDirectTaskDispatcherSafely();
+ NS_ENSURE_TRUE(dispatcher, NS_ERROR_NOT_INITIALIZED);
+ return dispatcher->DispatchDirectTask(std::move(aEvent));
+}
+
+NS_IMETHODIMP nsSocketTransportService::DrainDirectTasks() {
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ GetDirectTaskDispatcherSafely();
+ if (!dispatcher) {
+ // nothing to drain.
+ return NS_OK;
+ }
+ return dispatcher->DrainDirectTasks();
+}
+
+NS_IMETHODIMP nsSocketTransportService::HaveDirectTasks(bool* aValue) {
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ GetDirectTaskDispatcherSafely();
+ if (!dispatcher) {
+ *aValue = false;
+ return NS_OK;
+ }
+ return dispatcher->HaveDirectTasks(aValue);
+}
+
+//-----------------------------------------------------------------------------
+// socket api (socket thread only)
+
+NS_IMETHODIMP
+nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable* event) {
+ SOCKET_LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n"));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (CanAttachSocket()) {
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ auto* runnable = new LinkedRunnableEvent(event);
+ mPendingSocketQueue.insertBack(runnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AttachSocket(PRFileDesc* fd,
+ nsASocketHandler* handler) {
+ SOCKET_LOG(
+ ("nsSocketTransportService::AttachSocket [handler=%p]\n", handler));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!CanAttachSocket()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SocketContext sock{fd, handler, 0};
+
+ AddToIdleList(&sock);
+ return NS_OK;
+}
+
+// the number of sockets that can be attached at any given time is
+// limited. this is done because some operating systems (e.g., Win9x)
+// limit the number of sockets that can be created by an application.
+// AttachSocket will fail if the limit is exceeded. consumers should
+// call CanAttachSocket and check the result before creating a socket.
+
+bool nsSocketTransportService::CanAttachSocket() {
+ MOZ_ASSERT(!mShuttingDown);
+ uint32_t total = mActiveList.Length() + mIdleList.Length();
+ bool rv = total < gMaxCount;
+
+ MOZ_ASSERT(mInitialized);
+ return rv;
+}
+
+nsresult nsSocketTransportService::DetachSocket(SocketContextList& listHead,
+ SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n",
+ sock->mHandler.get()));
+ MOZ_ASSERT((&listHead == &mActiveList) || (&listHead == &mIdleList),
+ "DetachSocket invalid head");
+
+ {
+ // inform the handler that this socket is going away
+ sock->mHandler->OnSocketDetached(sock->mFD);
+ }
+ mSentBytesCount += sock->mHandler->ByteCountSent();
+ mReceivedBytesCount += sock->mHandler->ByteCountReceived();
+
+ // cleanup
+ sock->mFD = nullptr;
+
+ if (&listHead == &mActiveList) {
+ RemoveFromPollList(sock);
+ } else {
+ RemoveFromIdleList(sock);
+ }
+
+ // NOTE: sock is now an invalid pointer
+
+ //
+ // notify the first element on the pending socket queue...
+ //
+ nsCOMPtr<nsIRunnable> event;
+ LinkedRunnableEvent* runnable = mPendingSocketQueue.getFirst();
+ if (runnable) {
+ event = runnable->TakeEvent();
+ runnable->remove();
+ delete runnable;
+ }
+ if (event) {
+ // move event from pending queue to dispatch queue
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+// Returns the index of a SocketContext within a list, or -1 if it's
+// not a pointer to a list element
+// NOTE: this could be supplied by nsTArray<>
+int64_t nsSocketTransportService::SockIndex(SocketContextList& aList,
+ SocketContext* aSock) {
+ ptrdiff_t index = -1;
+ if (!aList.IsEmpty()) {
+ index = aSock - &aList[0];
+ if (index < 0 || (size_t)index + 1 > aList.Length()) {
+ index = -1;
+ }
+ }
+ return (int64_t)index;
+}
+
+void nsSocketTransportService::AddToPollList(SocketContext* sock) {
+ MOZ_ASSERT(SockIndex(mActiveList, sock) == -1,
+ "AddToPollList Socket Already Active");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToPollList %p [handler=%p]\n", sock,
+ sock->mHandler.get()));
+
+ sock->EnsureTimeout(PR_IntervalNow());
+ PRPollDesc poll;
+ poll.fd = sock->mFD;
+ poll.in_flags = sock->mHandler->mPollFlags;
+ poll.out_flags = 0;
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ auto newSocketIndex = mActiveList.Length();
+ newSocketIndex = ChaosMode::randomUint32LessThan(newSocketIndex + 1);
+ mActiveList.InsertElementAt(newSocketIndex, *sock);
+ // mPollList is offset by 1
+ mPollList.InsertElementAt(newSocketIndex + 1, poll);
+ } else {
+ // Avoid refcount bump/decrease
+ mActiveList.EmplaceBack(sock->mFD, sock->mHandler.forget(),
+ sock->mPollStartEpoch);
+ mPollList.AppendElement(poll);
+ }
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::RemoveFromPollList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList %p [handler=%p]\n",
+ sock, sock->mHandler.get()));
+
+ auto index = SockIndex(mActiveList, sock);
+ MOZ_RELEASE_ASSERT(index != -1, "invalid index");
+
+ SOCKET_LOG((" index=%" PRId64 " mActiveList.Length()=%zu\n", index,
+ mActiveList.Length()));
+ mActiveList.UnorderedRemoveElementAt(index);
+ // mPollList is offset by 1
+ mPollList.UnorderedRemoveElementAt(index + 1);
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::AddToIdleList(SocketContext* sock) {
+ MOZ_ASSERT(SockIndex(mIdleList, sock) == -1,
+ "AddToIdleList Socket Already Idle");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToIdleList %p [handler=%p]\n", sock,
+ sock->mHandler.get()));
+
+ // Avoid refcount bump/decrease
+ mIdleList.EmplaceBack(sock->mFD, sock->mHandler.forget(),
+ sock->mPollStartEpoch);
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::RemoveFromIdleList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n",
+ sock->mHandler.get()));
+ auto index = SockIndex(mIdleList, sock);
+ MOZ_RELEASE_ASSERT(index != -1);
+ mIdleList.UnorderedRemoveElementAt(index);
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::MoveToIdleList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::MoveToIdleList %p [handler=%p]\n",
+ sock, sock->mHandler.get()));
+ MOZ_ASSERT(SockIndex(mIdleList, sock) == -1);
+ MOZ_ASSERT(SockIndex(mActiveList, sock) != -1);
+ AddToIdleList(sock);
+ RemoveFromPollList(sock);
+}
+
+void nsSocketTransportService::MoveToPollList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::MoveToPollList %p [handler=%p]\n",
+ sock, sock->mHandler.get()));
+ MOZ_ASSERT(SockIndex(mIdleList, sock) != -1);
+ MOZ_ASSERT(SockIndex(mActiveList, sock) == -1);
+ AddToPollList(sock);
+ RemoveFromIdleList(sock);
+}
+
+void nsSocketTransportService::ApplyPortRemapPreference(
+ TPortRemapping const& portRemapping) {
+ MOZ_ASSERT(IsOnCurrentThreadInfallible());
+
+ mPortRemapping.reset();
+ if (!portRemapping.IsEmpty()) {
+ mPortRemapping.emplace(portRemapping);
+ }
+}
+
+PRIntervalTime nsSocketTransportService::PollTimeout(PRIntervalTime now) {
+ if (mActiveList.IsEmpty()) {
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+
+ // compute minimum time before any socket timeout expires.
+ PRIntervalTime minR = NS_SOCKET_POLL_TIMEOUT;
+ for (uint32_t i = 0; i < mActiveList.Length(); ++i) {
+ const SocketContext& s = mActiveList[i];
+ PRIntervalTime r = s.TimeoutIn(now);
+ if (r < minR) {
+ minR = r;
+ }
+ }
+ if (minR == NS_SOCKET_POLL_TIMEOUT) {
+ SOCKET_LOG(("poll timeout: none\n"));
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+ SOCKET_LOG(("poll timeout: %" PRIu32 "\n", PR_IntervalToSeconds(minR)));
+ return minR;
+}
+
+int32_t nsSocketTransportService::Poll(TimeDuration* pollDuration,
+ PRIntervalTime ts) {
+ MOZ_ASSERT(IsOnCurrentThread());
+ PRPollDesc* firstPollEntry;
+ uint32_t pollCount;
+ PRIntervalTime pollTimeout;
+ *pollDuration = nullptr;
+
+ // If there are pending events for this thread then
+ // DoPollIteration() should service the network without blocking.
+ bool pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+
+ if (mPollList[0].fd) {
+ mPollList[0].out_flags = 0;
+ firstPollEntry = &mPollList[0];
+ pollCount = mPollList.Length();
+ pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout(ts);
+ } else {
+ // no pollable event, so busy wait...
+ pollCount = mActiveList.Length();
+ if (pollCount) {
+ firstPollEntry = &mPollList[1];
+ } else {
+ firstPollEntry = nullptr;
+ }
+ pollTimeout =
+ pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25);
+ }
+
+ if ((ts - mLastNetworkLinkChangeTime) < mNetworkLinkChangeBusyWaitPeriod) {
+ // Being here means we are few seconds after a network change has
+ // been detected.
+ PRIntervalTime to = mNetworkLinkChangeBusyWaitTimeout;
+ if (to) {
+ pollTimeout = std::min(to, pollTimeout);
+ SOCKET_LOG((" timeout shorthened after network change event"));
+ }
+ }
+
+ TimeStamp pollStart;
+ if (Telemetry::CanRecordPrereleaseData()) {
+ pollStart = TimeStamp::NowLoRes();
+ }
+
+ SOCKET_LOG((" timeout = %i milliseconds\n",
+ PR_IntervalToMilliseconds(pollTimeout)));
+
+ int32_t n;
+ {
+#ifdef MOZ_GECKO_PROFILER
+ TimeStamp startTime = TimeStamp::Now();
+ if (pollTimeout != PR_INTERVAL_NO_WAIT) {
+ // There will be an actual non-zero wait, let the profiler know about it
+ // by marking thread as sleeping around the polling call.
+ profiler_thread_sleep();
+ }
+#endif
+
+ n = PR_Poll(firstPollEntry, pollCount, pollTimeout);
+
+#ifdef MOZ_GECKO_PROFILER
+ if (pollTimeout != PR_INTERVAL_NO_WAIT) {
+ profiler_thread_wake();
+ }
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ PROFILER_MARKER_TEXT(
+ "SocketTransportService::Poll", NETWORK,
+ MarkerTiming::IntervalUntilNowFrom(startTime),
+ pollTimeout == PR_INTERVAL_NO_TIMEOUT
+ ? nsPrintfCString("Poll count: %u, Poll timeout: NO_TIMEOUT",
+ pollCount)
+ : pollTimeout == PR_INTERVAL_NO_WAIT
+ ? nsPrintfCString("Poll count: %u, Poll timeout: NO_WAIT",
+ pollCount)
+ : nsPrintfCString("Poll count: %u, Poll timeout: %ums", pollCount,
+ PR_IntervalToMilliseconds(pollTimeout)));
+ }
+#endif
+ }
+
+ if (Telemetry::CanRecordPrereleaseData() && !pollStart.IsNull()) {
+ *pollDuration = TimeStamp::NowLoRes() - pollStart;
+ }
+
+ SOCKET_LOG((" ...returned after %i milliseconds\n",
+ PR_IntervalToMilliseconds(PR_IntervalNow() - ts)));
+
+ return n;
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransportService, nsISocketTransportService,
+ nsIRoutedSocketTransportService, nsIEventTarget,
+ nsISerialEventTarget, nsIThreadObserver, nsIRunnable,
+ nsPISocketTransportService, nsIObserver, nsINamed,
+ nsIDirectTaskDispatcher)
+
+static const char* gCallbackPrefs[] = {
+ SEND_BUFFER_PREF,
+ KEEPALIVE_ENABLED_PREF,
+ KEEPALIVE_IDLE_TIME_PREF,
+ KEEPALIVE_RETRY_INTERVAL_PREF,
+ KEEPALIVE_PROBE_COUNT_PREF,
+ MAX_TIME_BETWEEN_TWO_POLLS,
+ MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
+ POLLABLE_EVENT_TIMEOUT,
+ "network.socket.forcePort",
+ nullptr,
+};
+
+/* static */
+void nsSocketTransportService::UpdatePrefs(const char* aPref, void* aSelf) {
+ static_cast<nsSocketTransportService*>(aSelf)->UpdatePrefs();
+}
+
+static uint32_t GetThreadStackSize() {
+#ifdef XP_WIN
+ if (!StaticPrefs::network_allow_large_stack_size_for_socket_thread()) {
+ return nsIThreadManager::DEFAULT_STACK_SIZE;
+ }
+
+ const uint32_t kWindowsThreadStackSize = 512 * 1024;
+ // We can remove this custom stack size when DEFAULT_STACK_SIZE is increased.
+ static_assert(kWindowsThreadStackSize > nsIThreadManager::DEFAULT_STACK_SIZE);
+ return kWindowsThreadStackSize;
+#else
+ return nsIThreadManager::DEFAULT_STACK_SIZE;
+#endif
+}
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Init() {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("wrong thread");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mInitialized) {
+ return NS_OK;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+
+ if (!XRE_IsContentProcess() ||
+ StaticPrefs::network_allow_raw_sockets_in_content_processes_AtStartup()) {
+ // Since we Poll, we can't use normal LongTask support in Main Process
+ nsresult rv = NS_NewNamedThread(
+ "Socket Thread", getter_AddRefs(thread), this,
+ {GetThreadStackSize(), false, false, Some(SOCKET_THREAD_LONGTASK_MS)});
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // In the child process, we just want a regular nsThread with no socket
+ // polling. So we don't want to run the nsSocketTransportService runnable on
+ // it.
+ nsresult rv =
+ NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), nullptr,
+ {nsIThreadManager::DEFAULT_STACK_SIZE, false, false,
+ Some(SOCKET_THREAD_LONGTASK_MS)});
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set up some of the state that nsSocketTransportService::Run would set.
+ PRThread* prthread = nullptr;
+ thread->GetPRThread(&prthread);
+ gSocketThread = prthread;
+ mRawThread = thread;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ // Install our mThread, protecting against concurrent readers
+ thread.swap(mThread);
+ mDirectTaskDispatcher = do_QueryInterface(mThread);
+ MOZ_DIAGNOSTIC_ASSERT(
+ mDirectTaskDispatcher,
+ "Underlying thread must support direct task dispatching");
+ }
+
+ Preferences::RegisterCallbacks(UpdatePrefs, gCallbackPrefs, this);
+ UpdatePrefs();
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ // Note that the observr notifications are forwarded from parent process to
+ // socket process. We have to make sure the topics registered below are also
+ // registered in nsIObserver::Init().
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "profile-initial-state", false);
+ obsSvc->AddObserver(this, "last-pb-context-exited", false);
+ obsSvc->AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ // We can now dispatch tasks to the socket thread.
+ mInitialized = true;
+ return NS_OK;
+}
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Shutdown(bool aXpcomShutdown) {
+ SOCKET_LOG(("nsSocketTransportService::Shutdown\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized || mShuttingDown) {
+ // We never inited, or shutdown has already started
+ return NS_OK;
+ }
+
+ {
+ auto observersCopy = mShutdownObservers;
+ for (auto& observer : observersCopy) {
+ observer->Observe();
+ }
+ }
+
+ mShuttingDown = true;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ }
+
+ // If we're shutting down due to going offline (rather than due to XPCOM
+ // shutdown), also tear down the thread. The thread will be shutdown during
+ // xpcom-shutdown-threads if during xpcom-shutdown proper.
+ if (!aXpcomShutdown) {
+ ShutdownThread();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSocketTransportService::ShutdownThread() {
+ SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized) {
+ return NS_OK;
+ }
+
+ // join with thread
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ thread->Shutdown();
+ {
+ MutexAutoLock lock(mLock);
+ // Drop our reference to mThread and make sure that any concurrent readers
+ // are excluded
+ mThread = nullptr;
+ mDirectTaskDispatcher = nullptr;
+ }
+
+ Preferences::UnregisterCallbacks(UpdatePrefs, gCallbackPrefs, this);
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(this, "profile-initial-state");
+ obsSvc->RemoveObserver(this, "last-pb-context-exited");
+ obsSvc->RemoveObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, "xpcom-shutdown-threads");
+ obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ }
+
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+
+ IOActivityMonitor::Shutdown();
+
+ mInitialized = false;
+ mShuttingDown = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetOffline(bool* offline) {
+ MutexAutoLock lock(mLock);
+ *offline = mOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::SetOffline(bool offline) {
+ MutexAutoLock lock(mLock);
+ if (!mOffline && offline) {
+ // signal the socket thread to go offline, so it will detach sockets
+ mGoingOffline = true;
+ mOffline = true;
+ } else if (mOffline && !offline) {
+ mOffline = false;
+ }
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveIdleTime(int32_t* aKeepaliveIdleTimeS) {
+ MOZ_ASSERT(aKeepaliveIdleTimeS);
+ if (NS_WARN_IF(!aKeepaliveIdleTimeS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveIdleTimeS = mKeepaliveIdleTimeS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveRetryInterval(
+ int32_t* aKeepaliveRetryIntervalS) {
+ MOZ_ASSERT(aKeepaliveRetryIntervalS);
+ if (NS_WARN_IF(!aKeepaliveRetryIntervalS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveRetryIntervalS = mKeepaliveRetryIntervalS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveProbeCount(
+ int32_t* aKeepaliveProbeCount) {
+ MOZ_ASSERT(aKeepaliveProbeCount);
+ if (NS_WARN_IF(!aKeepaliveProbeCount)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveProbeCount = mKeepaliveProbeCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateTransport(const nsTArray<nsCString>& types,
+ const nsACString& host, int32_t port,
+ nsIProxyInfo* proxyInfo,
+ nsIDNSRecord* dnsRecord,
+ nsISocketTransport** result) {
+ return CreateRoutedTransport(types, host, port, ""_ns, 0, proxyInfo,
+ dnsRecord, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateRoutedTransport(
+ const nsTArray<nsCString>& types, const nsACString& host, int32_t port,
+ const nsACString& hostRoute, int32_t portRoute, nsIProxyInfo* proxyInfo,
+ nsIDNSRecord* dnsRecord, nsISocketTransport** result) {
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE);
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+ nsresult rv = trans->Init(types, host, port, hostRoute, portRoute, proxyInfo,
+ dnsRecord);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateUnixDomainTransport(
+ nsIFile* aPath, nsISocketTransport** result) {
+#ifdef XP_UNIX
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+
+ rv = trans->InitWithFilename(path.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ trans.forget(result);
+ return NS_OK;
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateUnixDomainAbstractAddressTransport(
+ const nsACString& aName, nsISocketTransport** result) {
+ // Abstract socket address is supported on Linux only
+#ifdef XP_LINUX
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+ // First character of Abstract socket address is null
+ UniquePtr<char[]> name(new char[aName.Length() + 1]);
+ *(name.get()) = 0;
+ memcpy(name.get() + 1, aName.BeginReading(), aName.Length());
+ nsresult rv = trans->InitWithName(name.get(), aName.Length() + 1);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ trans.forget(result);
+ return NS_OK;
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnDispatchedEvent() {
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event.
+ if (OnSocketThread()) {
+ // this check is redundant to one done inside ::Signal(), but
+ // we can do it here and skip obtaining the lock - given that
+ // this is a relatively common occurance its worth the
+ // redundant code
+ SOCKET_LOG(("OnDispatchedEvent Same Thread Skip Signal\n"));
+ return NS_OK;
+ }
+#else
+ if (gIOService->IsNetTearingDown()) {
+ // Poll can hang sometimes. If we are in shutdown, we are going to
+ // start a watchdog. If we do not exit poll within
+ // REPAIR_POLLABLE_EVENT_TIME signal a pollable event again.
+ StartPollWatchdog();
+ }
+#endif
+
+ MutexAutoLock lock(mLock);
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal* thread,
+ bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
+
+void nsSocketTransportService::MarkTheLastElementOfPendingQueue() {
+ mServingPendingQueue = false;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Run() {
+ SOCKET_LOG(("STS thread init %d sockets\n", gMaxCount));
+
+#if defined(XP_WIN)
+ // see bug 1361495, gethostname() triggers winsock initialization.
+ // so do it here (on parent and child) to protect against it being done first
+ // accidentally on the main thread.. especially via PR_GetSystemInfo(). This
+ // will also improve latency of first real winsock operation
+ // ..
+ // If STS-thread is no longer needed this should still be run before exiting
+
+ char ignoredStackBuffer[255];
+ Unused << gethostname(ignoredStackBuffer, 255);
+#endif
+
+ psm::InitializeSSLServerCertVerificationThreads();
+
+ gSocketThread = PR_GetCurrentThread();
+
+ {
+ // See bug 1843384:
+ // Avoid blocking the main thread by allocating the PollableEvent outside
+ // the mutex. Still has the potential to hang the socket thread, but the
+ // main thread remains responsive.
+ PollableEvent* pollable = new PollableEvent();
+ MutexAutoLock lock(mLock);
+ mPollableEvent.reset(pollable);
+
+ //
+ // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
+ // or similar software.
+ //
+ // NOTE: per bug 191739, this failure could also be caused by lack
+ // of a loopback device on Windows and OS/2 platforms (it creates
+ // a loopback socket pair on these platforms to implement a pollable
+ // event object). if we can't create a pollable event, then we'll
+ // have to "busy wait" to implement the socket event queue :-(
+ //
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ NS_WARNING("running socket transport thread without a pollable event");
+ SOCKET_LOG(("running socket transport thread without a pollable event"));
+ }
+
+ PRPollDesc entry = {mPollableEvent ? mPollableEvent->PollableFD() : nullptr,
+ PR_POLL_READ | PR_POLL_EXCEPT, 0};
+ SOCKET_LOG(("Setting entry 0"));
+ mPollList[0] = entry;
+ }
+
+ mRawThread = NS_GetCurrentThread();
+
+ // Ensure a call to GetCurrentSerialEventTarget() returns this event target.
+ SerialEventTargetGuard guard(this);
+
+ // hook ourselves up to observe event processing for this thread
+ nsCOMPtr<nsIThreadInternal> threadInt = do_QueryInterface(mRawThread);
+ threadInt->SetObserver(this);
+
+ // make sure the pseudo random number generator is seeded on this thread
+ srand(static_cast<unsigned>(PR_Now()));
+
+ // For the calculation of the duration of the last cycle (i.e. the last
+ // for-loop iteration before shutdown).
+ TimeStamp startOfCycleForLastCycleCalc;
+
+ // For measuring of the poll iteration duration without time spent blocked
+ // in poll().
+ TimeStamp pollCycleStart;
+ // Time blocked in poll().
+ TimeDuration singlePollDuration;
+
+ // For calculating the time needed for a new element to run.
+ TimeStamp startOfIteration;
+ TimeStamp startOfNextIteration;
+
+ // If there is too many pending events queued, we will run some poll()
+ // between them and the following variable is cumulative time spent
+ // blocking in poll().
+ TimeDuration pollDuration;
+
+ for (;;) {
+ bool pendingEvents = false;
+ if (Telemetry::CanRecordPrereleaseData()) {
+ startOfCycleForLastCycleCalc = TimeStamp::NowLoRes();
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ pollDuration = nullptr;
+ // We pop out to this loop when there are no pending events.
+ // If we don't reset these, we may not re-enter ProcessNextEvent()
+ // until we have events to process, and it may seem like we have
+ // an event running for a very long time.
+ mRawThread->SetRunningEventDelay(TimeDuration(), TimeStamp());
+
+ do {
+ if (Telemetry::CanRecordPrereleaseData()) {
+ pollCycleStart = TimeStamp::NowLoRes();
+ }
+
+ DoPollIteration(&singlePollDuration);
+
+ if (Telemetry::CanRecordPrereleaseData() && !pollCycleStart.IsNull()) {
+ Telemetry::Accumulate(Telemetry::STS_POLL_BLOCK_TIME,
+ singlePollDuration.ToMilliseconds());
+ Telemetry::AccumulateTimeDelta(Telemetry::STS_POLL_CYCLE,
+ pollCycleStart + singlePollDuration,
+ TimeStamp::NowLoRes());
+ pollDuration += singlePollDuration;
+ }
+
+ mRawThread->HasPendingEvents(&pendingEvents);
+ if (pendingEvents) {
+ if (!mServingPendingQueue) {
+ nsresult rv = Dispatch(
+ NewRunnableMethod(
+ "net::nsSocketTransportService::"
+ "MarkTheLastElementOfPendingQueue",
+ this,
+ &nsSocketTransportService::MarkTheLastElementOfPendingQueue),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Could not dispatch a new event on the "
+ "socket thread.");
+ } else {
+ mServingPendingQueue = true;
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ startOfIteration = startOfNextIteration;
+ // Everything that comes after this point will
+ // be served in the next iteration. If no even
+ // arrives, startOfNextIteration will be reset at the
+ // beginning of each for-loop.
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ }
+ TimeStamp eventQueueStart = TimeStamp::NowLoRes();
+ do {
+ NS_ProcessNextEvent(mRawThread);
+ pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+ } while (pendingEvents && mServingPendingQueue &&
+ ((TimeStamp::NowLoRes() - eventQueueStart).ToMilliseconds() <
+ mMaxTimePerPollIter));
+
+ if (Telemetry::CanRecordPrereleaseData() && !mServingPendingQueue &&
+ !startOfIteration.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::STS_POLL_AND_EVENTS_CYCLE,
+ startOfIteration + pollDuration,
+ TimeStamp::NowLoRes());
+ pollDuration = nullptr;
+ }
+ }
+ } while (pendingEvents);
+
+ bool goingOffline = false;
+ // now that our event queue is empty, check to see if we should exit
+ if (mShuttingDown) {
+ if (Telemetry::CanRecordPrereleaseData() &&
+ !startOfCycleForLastCycleCalc.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_AND_EVENT_THE_LAST_CYCLE,
+ startOfCycleForLastCycleCalc, TimeStamp::NowLoRes());
+ }
+ break;
+ }
+ {
+ MutexAutoLock lock(mLock);
+ if (mGoingOffline) {
+ mGoingOffline = false;
+ goingOffline = true;
+ }
+ }
+ // Avoid potential deadlock
+ if (goingOffline) {
+ Reset(true);
+ }
+ }
+
+ SOCKET_LOG(("STS shutting down thread\n"));
+
+ // detach all sockets, including locals
+ Reset(false);
+
+ // We don't clear gSocketThread so that OnSocketThread() won't be a false
+ // alarm for events generated by stopping the SSL threads during shutdown.
+ psm::StopSSLServerCertVerificationThreads();
+
+ // Final pass over the event queue. This makes sure that events posted by
+ // socket detach handlers get processed.
+ NS_ProcessPendingEvents(mRawThread);
+
+ SOCKET_LOG(("STS thread exit\n"));
+ MOZ_ASSERT(mPollList.Length() == 1);
+ MOZ_ASSERT(mActiveList.IsEmpty());
+ MOZ_ASSERT(mIdleList.IsEmpty());
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::DetachSocketWithGuard(
+ bool aGuardLocals, SocketContextList& socketList, int32_t index) {
+ bool isGuarded = false;
+ if (aGuardLocals) {
+ socketList[index].mHandler->IsLocal(&isGuarded);
+ if (!isGuarded) {
+ socketList[index].mHandler->KeepWhenOffline(&isGuarded);
+ }
+ }
+ if (!isGuarded) {
+ DetachSocket(socketList, &socketList[index]);
+ }
+}
+
+void nsSocketTransportService::Reset(bool aGuardLocals) {
+ // detach any sockets
+ int32_t i;
+ for (i = mActiveList.Length() - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mActiveList, i);
+ }
+ for (i = mIdleList.Length() - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mIdleList, i);
+ }
+}
+
+nsresult nsSocketTransportService::DoPollIteration(TimeDuration* pollDuration) {
+ SOCKET_LOG(("STS poll iter\n"));
+
+ PRIntervalTime now = PR_IntervalNow();
+
+ // We can't have more than int32_max sockets in use
+ int32_t i, count;
+ //
+ // poll loop
+ //
+ // walk active list backwards to see if any sockets should actually be
+ // idle, then walk the idle list backwards to see if any idle sockets
+ // should become active. take care to check only idle sockets that
+ // were idle to begin with ;-)
+ //
+ count = mIdleList.Length();
+ for (i = mActiveList.Length() - 1; i >= 0; --i) {
+ //---
+ SOCKET_LOG((" active [%u] { handler=%p condition=%" PRIx32
+ " pollflags=%hu }\n",
+ i, mActiveList[i].mHandler.get(),
+ static_cast<uint32_t>(mActiveList[i].mHandler->mCondition),
+ mActiveList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition)) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ } else {
+ uint16_t in_flags = mActiveList[i].mHandler->mPollFlags;
+ if (in_flags == 0) {
+ MoveToIdleList(&mActiveList[i]);
+ } else {
+ // update poll flags
+ mPollList[i + 1].in_flags = in_flags;
+ mPollList[i + 1].out_flags = 0;
+ mActiveList[i].EnsureTimeout(now);
+ }
+ }
+ }
+ for (i = count - 1; i >= 0; --i) {
+ //---
+ SOCKET_LOG((" idle [%u] { handler=%p condition=%" PRIx32
+ " pollflags=%hu }\n",
+ i, mIdleList[i].mHandler.get(),
+ static_cast<uint32_t>(mIdleList[i].mHandler->mCondition),
+ mIdleList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mIdleList[i].mHandler->mCondition)) {
+ DetachSocket(mIdleList, &mIdleList[i]);
+ } else if (mIdleList[i].mHandler->mPollFlags != 0) {
+ MoveToPollList(&mIdleList[i]);
+ }
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ if (mPollableEvent) {
+ // we want to make sure the timeout is measured from the time
+ // we enter poll(). This method resets the timestamp to 'now',
+ // if we were first signalled between leaving poll() and here.
+ // If we didn't do this and processing events took longer than
+ // the allowed signal timeout, we would detect it as a
+ // false-positive. AdjustFirstSignalTimestamp is then a no-op
+ // until mPollableEvent->Clear() is called.
+ mPollableEvent->AdjustFirstSignalTimestamp();
+ }
+ }
+
+ SOCKET_LOG((" calling PR_Poll [active=%zu idle=%zu]\n", mActiveList.Length(),
+ mIdleList.Length()));
+
+ // Measures seconds spent while blocked on PR_Poll
+ int32_t n = 0;
+ *pollDuration = nullptr;
+
+ if (!gIOService->IsNetTearingDown()) {
+ // Let's not do polling during shutdown.
+#if defined(XP_WIN)
+ StartPolling();
+#endif
+ n = Poll(pollDuration, now);
+#if defined(XP_WIN)
+ EndPolling();
+#endif
+ }
+
+ now = PR_IntervalNow();
+#ifdef MOZ_GECKO_PROFILER
+ TimeStamp startTime;
+ bool profiling = profiler_thread_is_being_profiled_for_markers();
+ if (profiling) {
+ startTime = TimeStamp::Now();
+ }
+#endif
+
+ if (n < 0) {
+ SOCKET_LOG((" PR_Poll error [%d] os error [%d]\n", PR_GetError(),
+ PR_GetOSError()));
+ } else {
+ //
+ // service "active" sockets...
+ //
+ for (i = 0; i < int32_t(mActiveList.Length()); ++i) {
+ PRPollDesc& desc = mPollList[i + 1];
+ SocketContext& s = mActiveList[i];
+ if (n > 0 && desc.out_flags != 0) {
+ s.DisengageTimeout();
+ s.mHandler->OnSocketReady(desc.fd, desc.out_flags);
+ } else if (s.IsTimedOut(now)) {
+ SOCKET_LOG(("socket %p timed out", s.mHandler.get()));
+ s.DisengageTimeout();
+ s.mHandler->OnSocketReady(desc.fd, -1);
+ } else {
+ s.MaybeResetEpoch();
+ }
+ }
+ //
+ // check for "dead" sockets and remove them (need to do this in
+ // reverse order obviously).
+ //
+ for (i = mActiveList.Length() - 1; i >= 0; --i) {
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition)) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ // acknowledge pollable event (should not block)
+ if (n != 0 &&
+ (mPollList[0].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT)) &&
+ mPollableEvent &&
+ ((mPollList[0].out_flags & PR_POLL_EXCEPT) ||
+ !mPollableEvent->Clear())) {
+ // On Windows, the TCP loopback connection in the
+ // pollable event may become broken when a laptop
+ // switches between wired and wireless networks or
+ // wakes up from hibernation. We try to create a
+ // new pollable event. If that fails, we fall back
+ // on "busy wait".
+ TryRepairPollableEvent();
+ }
+
+ if (mPollableEvent &&
+ !mPollableEvent->IsSignallingAlive(mPollableEventTimeout)) {
+ SOCKET_LOG(("Pollable event signalling failed/timed out"));
+ TryRepairPollableEvent();
+ }
+ }
+ }
+#ifdef MOZ_GECKO_PROFILER
+ if (profiling) {
+ TimeStamp endTime = TimeStamp::Now();
+ if ((endTime - startTime).ToMilliseconds() >= SOCKET_THREAD_LONGTASK_MS) {
+ struct LongTaskMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("SocketThreadLongTask");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter) {
+ aWriter.StringProperty("category", "LongTask");
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyLabelFormatSearchable("category", "Type",
+ MS::Format::String,
+ MS::Searchable::Searchable);
+ return schema;
+ }
+ };
+
+ profiler_add_marker(ProfilerString8View("LongTaskSocketProcessing"),
+ geckoprofiler::category::OTHER,
+ MarkerTiming::Interval(startTime, endTime),
+ LongTaskMarker{});
+ }
+ }
+
+#endif
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::UpdateSendBufferPref() {
+ int32_t bufferSize;
+
+ // If the pref is set, honor it. 0 means use OS defaults.
+ nsresult rv = Preferences::GetInt(SEND_BUFFER_PREF, &bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ mSendBufferSize = bufferSize;
+ return;
+ }
+
+#if defined(XP_WIN)
+ mSendBufferSize = 131072 * 4;
+#endif
+}
+
+nsresult nsSocketTransportService::UpdatePrefs() {
+ mSendBufferSize = 0;
+
+ UpdateSendBufferPref();
+
+ // Default TCP Keepalive Values.
+ int32_t keepaliveIdleTimeS;
+ nsresult rv =
+ Preferences::GetInt(KEEPALIVE_IDLE_TIME_PREF, &keepaliveIdleTimeS);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepaliveIdleTimeS = clamped(keepaliveIdleTimeS, 1, kMaxTCPKeepIdle);
+ }
+
+ int32_t keepaliveRetryIntervalS;
+ rv = Preferences::GetInt(KEEPALIVE_RETRY_INTERVAL_PREF,
+ &keepaliveRetryIntervalS);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepaliveRetryIntervalS =
+ clamped(keepaliveRetryIntervalS, 1, kMaxTCPKeepIntvl);
+ }
+
+ int32_t keepaliveProbeCount;
+ rv = Preferences::GetInt(KEEPALIVE_PROBE_COUNT_PREF, &keepaliveProbeCount);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepaliveProbeCount = clamped(keepaliveProbeCount, 1, kMaxTCPKeepCount);
+ }
+ bool keepaliveEnabled = false;
+ rv = Preferences::GetBool(KEEPALIVE_ENABLED_PREF, &keepaliveEnabled);
+ if (NS_SUCCEEDED(rv) && keepaliveEnabled != mKeepaliveEnabledPref) {
+ mKeepaliveEnabledPref = keepaliveEnabled;
+ OnKeepaliveEnabledPrefChange();
+ }
+
+ int32_t maxTimePref;
+ rv = Preferences::GetInt(MAX_TIME_BETWEEN_TWO_POLLS, &maxTimePref);
+ if (NS_SUCCEEDED(rv) && maxTimePref >= 0) {
+ mMaxTimePerPollIter = maxTimePref;
+ }
+
+ int32_t pollBusyWaitPeriod;
+ rv = Preferences::GetInt(POLL_BUSY_WAIT_PERIOD, &pollBusyWaitPeriod);
+ if (NS_SUCCEEDED(rv) && pollBusyWaitPeriod > 0) {
+ mNetworkLinkChangeBusyWaitPeriod = PR_SecondsToInterval(pollBusyWaitPeriod);
+ }
+
+ int32_t pollBusyWaitPeriodTimeout;
+ rv = Preferences::GetInt(POLL_BUSY_WAIT_PERIOD_TIMEOUT,
+ &pollBusyWaitPeriodTimeout);
+ if (NS_SUCCEEDED(rv) && pollBusyWaitPeriodTimeout > 0) {
+ mNetworkLinkChangeBusyWaitTimeout =
+ PR_SecondsToInterval(pollBusyWaitPeriodTimeout);
+ }
+
+ int32_t maxTimeForPrClosePref;
+ rv = Preferences::GetInt(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
+ &maxTimeForPrClosePref);
+ if (NS_SUCCEEDED(rv) && maxTimeForPrClosePref >= 0) {
+ mMaxTimeForPrClosePref = PR_MillisecondsToInterval(maxTimeForPrClosePref);
+ }
+
+ int32_t pollableEventTimeout;
+ rv = Preferences::GetInt(POLLABLE_EVENT_TIMEOUT, &pollableEventTimeout);
+ if (NS_SUCCEEDED(rv) && pollableEventTimeout >= 0) {
+ MutexAutoLock lock(mLock);
+ mPollableEventTimeout = TimeDuration::FromSeconds(pollableEventTimeout);
+ }
+
+ nsAutoCString portMappingPref;
+ rv = Preferences::GetCString("network.socket.forcePort", portMappingPref);
+ if (NS_SUCCEEDED(rv)) {
+ bool rv = UpdatePortRemapPreference(portMappingPref);
+ if (!rv) {
+ NS_ERROR(
+ "network.socket.forcePort preference is ill-formed, this will likely "
+ "make everything unexpectedly fail!");
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::OnKeepaliveEnabledPrefChange() {
+ // Dispatch to socket thread if we're not executing there.
+ if (!OnSocketThread()) {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod(
+ "net::nsSocketTransportService::OnKeepaliveEnabledPrefChange", this,
+ &nsSocketTransportService::OnKeepaliveEnabledPrefChange),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SOCKET_LOG(("nsSocketTransportService::OnKeepaliveEnabledPrefChange %s",
+ mKeepaliveEnabledPref ? "enabled" : "disabled"));
+
+ // Notify each socket that keepalive has been en/disabled globally.
+ for (int32_t i = mActiveList.Length() - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mActiveList[i]);
+ }
+ for (int32_t i = mIdleList.Length() - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mIdleList[i]);
+ }
+}
+
+void nsSocketTransportService::NotifyKeepaliveEnabledPrefChange(
+ SocketContext* sock) {
+ MOZ_ASSERT(sock, "SocketContext cannot be null!");
+ MOZ_ASSERT(sock->mHandler, "SocketContext does not have a handler!");
+
+ if (!sock || !sock->mHandler) {
+ return;
+ }
+
+ sock->mHandler->OnKeepaliveEnabledPrefChange(mKeepaliveEnabledPref);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsSocketTransportService");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ SOCKET_LOG(("nsSocketTransportService::Observe topic=%s", topic));
+
+ if (!strcmp(topic, "profile-initial-state")) {
+ if (!Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false)) {
+ return NS_OK;
+ }
+ return net::IOActivityMonitor::Init();
+ }
+
+ if (!strcmp(topic, "last-pb-context-exited")) {
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
+ "net::nsSocketTransportService::ClosePrivateConnections", this,
+ &nsSocketTransportService::ClosePrivateConnections);
+ nsresult rv = Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mAfterWakeUpTimer) {
+ mAfterWakeUpTimer = nullptr;
+ mSleepPhase = false;
+ }
+
+#if defined(XP_WIN)
+ if (timer == mPollRepairTimer) {
+ DoPollRepair();
+ }
+#endif
+
+ } else if (!strcmp(topic, NS_WIDGET_SLEEP_OBSERVER_TOPIC)) {
+ mSleepPhase = true;
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ if (mSleepPhase && !mAfterWakeUpTimer) {
+ NS_NewTimerWithObserver(getter_AddRefs(mAfterWakeUpTimer), this, 2000,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else if (!strcmp(topic, "xpcom-shutdown-threads")) {
+ ShutdownThread();
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ mLastNetworkLinkChangeTime = PR_IntervalNow();
+ }
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::ClosePrivateConnections() {
+ MOZ_ASSERT(IsOnCurrentThread(), "Must be called on the socket thread");
+
+ for (int32_t i = mActiveList.Length() - 1; i >= 0; --i) {
+ if (mActiveList[i].mHandler->mIsPrivate) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+ }
+ for (int32_t i = mIdleList.Length() - 1; i >= 0; --i) {
+ if (mIdleList[i].mHandler->mIsPrivate) {
+ DetachSocket(mIdleList, &mIdleList[i]);
+ }
+ }
+
+ ClearPrivateSSLState();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetSendBufferSize(int32_t* value) {
+ *value = mSendBufferSize;
+ return NS_OK;
+}
+
+/// ugly OS specific includes are placed at the bottom of the src for clarity
+
+#if defined(XP_WIN)
+# include <windows.h>
+#elif defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+# include <sys/resource.h>
+#endif
+
+PRStatus nsSocketTransportService::DiscoverMaxCount() {
+ gMaxCount = SOCKET_LIMIT_MIN;
+
+#if defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+ // On unix and os x network sockets and file
+ // descriptors are the same. OS X comes defaulted at 256,
+ // most linux at 1000. We can reliably use [sg]rlimit to
+ // query that and raise it if needed.
+
+ struct rlimit rlimitData {};
+ if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1) { // rlimit broken - use min
+ return PR_SUCCESS;
+ }
+
+ if (rlimitData.rlim_cur >= SOCKET_LIMIT_TARGET) { // larger than target!
+ gMaxCount = SOCKET_LIMIT_TARGET;
+ return PR_SUCCESS;
+ }
+
+ int32_t maxallowed = rlimitData.rlim_max;
+ if ((uint32_t)maxallowed <= SOCKET_LIMIT_MIN) {
+ return PR_SUCCESS; // so small treat as if rlimit is broken
+ }
+
+ if ((maxallowed == -1) || // no hard cap - ok to set target
+ ((uint32_t)maxallowed >= SOCKET_LIMIT_TARGET)) {
+ maxallowed = SOCKET_LIMIT_TARGET;
+ }
+
+ rlimitData.rlim_cur = maxallowed;
+ setrlimit(RLIMIT_NOFILE, &rlimitData);
+ if ((getrlimit(RLIMIT_NOFILE, &rlimitData) != -1) &&
+ (rlimitData.rlim_cur > SOCKET_LIMIT_MIN)) {
+ gMaxCount = rlimitData.rlim_cur;
+ }
+
+#elif defined(XP_WIN) && !defined(WIN_CE)
+ // >= XP is confirmed to have at least 1000
+ static_assert(SOCKET_LIMIT_TARGET <= 1000,
+ "SOCKET_LIMIT_TARGET max value is 1000");
+ gMaxCount = SOCKET_LIMIT_TARGET;
+#else
+ // other platforms are harder to test - so leave at safe legacy value
+#endif
+
+ return PR_SUCCESS;
+}
+
+// Used to return connection info to Dashboard.cpp
+void nsSocketTransportService::AnalyzeConnection(nsTArray<SocketInfo>* data,
+ SocketContext* context,
+ bool aActive) {
+ if (context->mHandler->mIsPrivate) {
+ return;
+ }
+ PRFileDesc* aFD = context->mFD;
+
+ PRFileDesc* idLayer = PR_GetIdentitiesLayer(aFD, PR_NSPR_IO_LAYER);
+
+ NS_ENSURE_TRUE_VOID(idLayer);
+
+ PRDescType type = PR_GetDescType(idLayer);
+ char host[64] = {0};
+ uint16_t port;
+ const char* type_desc;
+
+ if (type == PR_DESC_SOCKET_TCP) {
+ type_desc = "TCP";
+ PRNetAddr peer_addr;
+ PodZero(&peer_addr);
+
+ PRStatus rv = PR_GetPeerName(aFD, &peer_addr);
+ if (rv != PR_SUCCESS) {
+ return;
+ }
+
+ rv = PR_NetAddrToString(&peer_addr, host, sizeof(host));
+ if (rv != PR_SUCCESS) {
+ return;
+ }
+
+ if (peer_addr.raw.family == PR_AF_INET) {
+ port = peer_addr.inet.port;
+ } else {
+ port = peer_addr.ipv6.port;
+ }
+ port = PR_ntohs(port);
+ } else {
+ if (type == PR_DESC_SOCKET_UDP) {
+ type_desc = "UDP";
+ } else {
+ type_desc = "other";
+ }
+ NetAddr addr;
+ if (context->mHandler->GetRemoteAddr(&addr) != NS_OK) {
+ return;
+ }
+ if (!addr.ToStringBuffer(host, sizeof(host))) {
+ return;
+ }
+ if (addr.GetPort(&port) != NS_OK) {
+ return;
+ }
+ }
+
+ uint64_t sent = context->mHandler->ByteCountSent();
+ uint64_t received = context->mHandler->ByteCountReceived();
+ SocketInfo info = {nsCString(host), sent, received, port, aActive,
+ nsCString(type_desc)};
+
+ data->AppendElement(info);
+}
+
+void nsSocketTransportService::GetSocketConnections(
+ nsTArray<SocketInfo>* data) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ for (uint32_t i = 0; i < mActiveList.Length(); i++) {
+ AnalyzeConnection(data, &mActiveList[i], true);
+ }
+ for (uint32_t i = 0; i < mIdleList.Length(); i++) {
+ AnalyzeConnection(data, &mIdleList[i], false);
+ }
+}
+
+bool nsSocketTransportService::IsTelemetryEnabledAndNotSleepPhase() {
+ return Telemetry::CanRecordPrereleaseData() && !mSleepPhase;
+}
+
+#if defined(XP_WIN)
+void nsSocketTransportService::StartPollWatchdog() {
+ // Start off the timer from a runnable off of the main thread in order to
+ // avoid a deadlock, see bug 1370448.
+ RefPtr<nsSocketTransportService> self(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsSocketTransportService::StartPollWatchdog", [self] {
+ MutexAutoLock lock(self->mLock);
+
+ // Poll can hang sometimes. If we are in shutdown, we are going to start
+ // a watchdog. If we do not exit poll within REPAIR_POLLABLE_EVENT_TIME
+ // signal a pollable event again.
+ if (gIOService->IsNetTearingDown() && self->mPolling &&
+ !self->mPollRepairTimer) {
+ NS_NewTimerWithObserver(getter_AddRefs(self->mPollRepairTimer), self,
+ REPAIR_POLLABLE_EVENT_TIME,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ }));
+}
+
+void nsSocketTransportService::DoPollRepair() {
+ MutexAutoLock lock(mLock);
+ if (mPolling && mPollableEvent) {
+ mPollableEvent->Signal();
+ } else if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+
+void nsSocketTransportService::StartPolling() {
+ MutexAutoLock lock(mLock);
+ mPolling = true;
+}
+
+void nsSocketTransportService::EndPolling() {
+ MutexAutoLock lock(mLock);
+ mPolling = false;
+ if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+
+#endif
+
+void nsSocketTransportService::TryRepairPollableEvent() {
+ mLock.AssertCurrentThreadOwns();
+
+ NS_WARNING("Trying to repair mPollableEvent");
+ mPollableEvent.reset(new PollableEvent());
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ }
+ SOCKET_LOG(
+ ("running socket transport thread without "
+ "a pollable event now valid=%d",
+ !!mPollableEvent));
+ mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+ mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ mPollList[0].out_flags = 0;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AddShutdownObserver(
+ nsISTSShutdownObserver* aObserver) {
+ mShutdownObservers.AppendElement(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::RemoveShutdownObserver(
+ nsISTSShutdownObserver* aObserver) {
+ mShutdownObservers.RemoveElement(aObserver);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransportService2.h b/netwerk/base/nsSocketTransportService2.h
new file mode 100644
index 0000000000..4c4848044b
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -0,0 +1,355 @@
+/* vim:set ts=4 sw=2 sts=2 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransportService2_h__
+#define nsSocketTransportService2_h__
+
+#include "PollableEvent.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsCOMPtr.h"
+#include "nsASocketHandler.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "nsIObserver.h"
+#include "nsIRunnable.h"
+#include "nsIThreadInternal.h"
+#include "nsITimer.h"
+#include "nsPISocketTransportService.h"
+#include "prinit.h"
+#include "prinrval.h"
+
+struct PRPollDesc;
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+//
+// set MOZ_LOG=nsSocketTransport:5
+//
+extern LazyLogModule gSocketTransportLog;
+#define SOCKET_LOG(args) MOZ_LOG(gSocketTransportLog, LogLevel::Debug, args)
+#define SOCKET_LOG1(args) MOZ_LOG(gSocketTransportLog, LogLevel::Error, args)
+#define SOCKET_LOG_ENABLED() MOZ_LOG_TEST(gSocketTransportLog, LogLevel::Debug)
+
+//
+// set MOZ_LOG=UDPSocket:5
+//
+extern LazyLogModule gUDPSocketLog;
+#define UDPSOCKET_LOG(args) MOZ_LOG(gUDPSocketLog, LogLevel::Debug, args)
+#define UDPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gUDPSocketLog, LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+
+#define NS_SOCKET_POLL_TIMEOUT PR_INTERVAL_NO_TIMEOUT
+
+//-----------------------------------------------------------------------------
+
+// These maximums are borrowed from the linux kernel.
+static const int32_t kMaxTCPKeepIdle = 32767; // ~9 hours.
+static const int32_t kMaxTCPKeepIntvl = 32767;
+static const int32_t kMaxTCPKeepCount = 127;
+static const int32_t kDefaultTCPKeepCount =
+#if defined(XP_WIN)
+ 10; // Hardcoded in Windows.
+#elif defined(XP_MACOSX)
+ 8; // Hardcoded in OSX.
+#else
+ 4; // Specifiable in Linux.
+#endif
+
+class LinkedRunnableEvent final
+ : public LinkedListElement<LinkedRunnableEvent> {
+ public:
+ explicit LinkedRunnableEvent(nsIRunnable* event) : mEvent(event) {}
+ ~LinkedRunnableEvent() = default;
+
+ already_AddRefed<nsIRunnable> TakeEvent() { return mEvent.forget(); }
+
+ private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransportService final : public nsPISocketTransportService,
+ public nsISerialEventTarget,
+ public nsIThreadObserver,
+ public nsIRunnable,
+ public nsIObserver,
+ public nsINamed,
+ public nsIDirectTaskDispatcher {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSPISOCKETTRANSPORTSERVICE
+ NS_DECL_NSISOCKETTRANSPORTSERVICE
+ NS_DECL_NSIROUTEDSOCKETTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+ NS_DECL_NSIDIRECTTASKDISPATCHER
+
+ static const uint32_t SOCKET_LIMIT_MIN = 50U;
+
+ nsSocketTransportService();
+
+ // Max Socket count may need to get initialized/used by nsHttpHandler
+ // before this class is initialized.
+ static uint32_t gMaxCount;
+ static PRCallOnceType gMaxCountInitOnce;
+ static PRStatus DiscoverMaxCount();
+
+ bool CanAttachSocket();
+
+ // Called by the networking dashboard on the socket thread only
+ // Fills the passed array with socket information
+ void GetSocketConnections(nsTArray<SocketInfo>*);
+ uint64_t GetSentBytes() { return mSentBytesCount; }
+ uint64_t GetReceivedBytes() { return mReceivedBytesCount; }
+
+ // Returns true if keepalives are enabled in prefs.
+ bool IsKeepaliveEnabled() { return mKeepaliveEnabledPref; }
+
+ bool IsTelemetryEnabledAndNotSleepPhase();
+ PRIntervalTime MaxTimeForPrClosePref() { return mMaxTimeForPrClosePref; }
+
+ // According the preference value of `network.socket.forcePort` this method
+ // possibly remaps the port number passed as the arg.
+ void ApplyPortRemap(uint16_t* aPort);
+
+ // Reads the preference string and updates (rewrites) the mPortRemapping
+ // array on the socket thread. Returns true if the whole pref string was
+ // correctly formed.
+ bool UpdatePortRemapPreference(nsACString const& aPortMappingPref);
+
+ protected:
+ virtual ~nsSocketTransportService();
+
+ private:
+ //-------------------------------------------------------------------------
+ // misc (any thread)
+ //-------------------------------------------------------------------------
+
+ // The value is guaranteed to be valid and not dangling while on the socket
+ // thread as mThread is only ever reset after it's been shutdown.
+ // This member should only ever be read on the socket thread.
+ nsIThread* mRawThread{nullptr};
+
+ // Returns mThread in a thread-safe manner.
+ already_AddRefed<nsIThread> GetThreadSafely();
+ // Same as above, but return mThread as a nsIDirectTaskDispatcher
+ already_AddRefed<nsIDirectTaskDispatcher> GetDirectTaskDispatcherSafely();
+
+ //-------------------------------------------------------------------------
+ // initialization and shutdown (any thread)
+ //-------------------------------------------------------------------------
+
+ Atomic<bool> mInitialized{false};
+ // indicates whether we are currently in the process of shutting down
+ Atomic<bool> mShuttingDown{false};
+
+ Mutex mLock{"nsSocketTransportService::mLock"};
+ // Variables in the next section protected by mLock
+
+ // mThread and mDirectTaskDispatcher are only ever modified on the main
+ // thread. Will be set on Init and set to null after shutdown. You must access
+ // mThread and mDirectTaskDispatcher outside the main thread via respectively
+ // GetThreadSafely and GetDirectTaskDispatchedSafely().
+ nsCOMPtr<nsIThread> mThread MOZ_GUARDED_BY(mLock);
+ // We store a pointer to mThread as a direct task dispatcher to avoid having
+ // to do do_QueryInterface whenever we need to access the interface.
+ nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher MOZ_GUARDED_BY(mLock);
+ UniquePtr<PollableEvent> mPollableEvent MOZ_GUARDED_BY(mLock);
+ bool mOffline MOZ_GUARDED_BY(mLock) = false;
+ bool mGoingOffline MOZ_GUARDED_BY(mLock) = false;
+
+ // Detaches all sockets.
+ void Reset(bool aGuardLocals);
+
+ nsresult ShutdownThread();
+
+ //-------------------------------------------------------------------------
+ // socket lists (socket thread only)
+ //
+ // only "active" sockets are on the poll list. the active list is kept
+ // in sync with the poll list such that:
+ //
+ // mActiveList[k].mFD == mPollList[k+1].fd
+ //
+ // where k=0,1,2,...
+ //-------------------------------------------------------------------------
+
+ class SocketContext {
+ public:
+ SocketContext(PRFileDesc* aFD,
+ already_AddRefed<nsASocketHandler>&& aHandler,
+ PRIntervalTime aPollStartEpoch)
+ : mFD(aFD), mHandler(aHandler), mPollStartEpoch(aPollStartEpoch) {}
+ SocketContext(PRFileDesc* aFD, nsASocketHandler* aHandler,
+ PRIntervalTime aPollStartEpoch)
+ : mFD(aFD), mHandler(aHandler), mPollStartEpoch(aPollStartEpoch) {}
+ ~SocketContext() = default;
+
+ // Returns true iff the socket has not been signalled longer than
+ // the desired timeout (mHandler->mPollTimeout).
+ bool IsTimedOut(PRIntervalTime now) const;
+ // Engages the timeout by marking the epoch we start polling this socket.
+ // If epoch is already marked this does nothing, hence, this method can be
+ // called everytime we put this socket to poll() list with in-flags set.
+ void EnsureTimeout(PRIntervalTime now);
+ // Called after an event on a socket has been signalled to turn of the
+ // timeout calculation.
+ void DisengageTimeout();
+ // Returns the number of intervals this socket is about to timeout in,
+ // or 0 (zero) when it has already timed out. Returns
+ // NS_SOCKET_POLL_TIMEOUT when there is no timeout set on the socket.
+ PRIntervalTime TimeoutIn(PRIntervalTime now) const;
+ // When a socket timeout is reset and later set again, it may happen
+ // that mPollStartEpoch is not reset in between. We have to manually
+ // call this on every iteration over sockets to ensure the epoch reset.
+ void MaybeResetEpoch();
+
+ PRFileDesc* mFD;
+ RefPtr<nsASocketHandler> mHandler;
+ PRIntervalTime mPollStartEpoch; // time we started to poll this socket
+ };
+
+ using SocketContextList = AutoTArray<SocketContext, SOCKET_LIMIT_MIN>;
+ int64_t SockIndex(SocketContextList& aList, SocketContext* aSock);
+
+ SocketContextList mActiveList;
+ SocketContextList mIdleList;
+
+ nsresult DetachSocket(SocketContextList& listHead, SocketContext*);
+ void AddToIdleList(SocketContext* sock);
+ void AddToPollList(SocketContext* sock);
+ void RemoveFromIdleList(SocketContext* sock);
+ void RemoveFromPollList(SocketContext* sock);
+ void MoveToIdleList(SocketContext* sock);
+ void MoveToPollList(SocketContext* sock);
+
+ void InitMaxCount();
+
+ // Total bytes number transfered through all the sockets except active ones
+ uint64_t mSentBytesCount{0};
+ uint64_t mReceivedBytesCount{0};
+ //-------------------------------------------------------------------------
+ // poll list (socket thread only)
+ //
+ // first element of the poll list is mPollableEvent (or null if the pollable
+ // event cannot be created).
+ //-------------------------------------------------------------------------
+
+ nsTArray<PRPollDesc> mPollList;
+
+ PRIntervalTime PollTimeout(
+ PRIntervalTime now); // computes ideal poll timeout
+ nsresult DoPollIteration(TimeDuration* pollDuration);
+ // perfoms a single poll iteration
+ int32_t Poll(TimeDuration* pollDuration, PRIntervalTime ts);
+ // calls PR_Poll. the out param
+ // interval indicates the poll
+ // duration in seconds.
+ // pollDuration is used only for
+ // telemetry
+
+ //-------------------------------------------------------------------------
+ // pending socket queue - see NotifyWhenCanAttachSocket
+ //-------------------------------------------------------------------------
+ AutoCleanLinkedList<LinkedRunnableEvent> mPendingSocketQueue;
+
+ // Preference Monitor for SendBufferSize and Keepalive prefs.
+ nsresult UpdatePrefs();
+ static void UpdatePrefs(const char* aPref, void* aSelf);
+ void UpdateSendBufferPref();
+ int32_t mSendBufferSize{0};
+ // Number of seconds of connection is idle before first keepalive ping.
+ int32_t mKeepaliveIdleTimeS{600};
+ // Number of seconds between retries should keepalive pings fail.
+ int32_t mKeepaliveRetryIntervalS{1};
+ // Number of keepalive probes to send.
+ int32_t mKeepaliveProbeCount{kDefaultTCPKeepCount};
+ // True if TCP keepalive is enabled globally.
+ bool mKeepaliveEnabledPref{false};
+ // Timeout of pollable event signalling.
+ TimeDuration mPollableEventTimeout MOZ_GUARDED_BY(mLock);
+
+ Atomic<bool> mServingPendingQueue{false};
+ Atomic<int32_t, Relaxed> mMaxTimePerPollIter{100};
+ Atomic<PRIntervalTime, Relaxed> mMaxTimeForPrClosePref;
+ // Timestamp of the last network link change event, tracked
+ // also on child processes.
+ Atomic<PRIntervalTime, Relaxed> mLastNetworkLinkChangeTime{0};
+ // Preference for how long we do busy wait after network link
+ // change has been detected.
+ Atomic<PRIntervalTime, Relaxed> mNetworkLinkChangeBusyWaitPeriod;
+ // Preference for the value of timeout for poll() we use during
+ // the network link change event period.
+ Atomic<PRIntervalTime, Relaxed> mNetworkLinkChangeBusyWaitTimeout;
+
+ // Between a computer going to sleep and waking up the PR_*** telemetry
+ // will be corrupted - so do not record it.
+ Atomic<bool, Relaxed> mSleepPhase{false};
+ nsCOMPtr<nsITimer> mAfterWakeUpTimer;
+
+ // Lazily created array of forced port remappings. The tuple members meaning
+ // is exactly:
+ // <0> the greater-or-equal port number of the range to remap
+ // <1> the less-or-equal port number of the range to remap
+ // <2> the port number to remap to, when the given port number falls to the
+ // range
+ using TPortRemapping =
+ CopyableTArray<std::tuple<uint16_t, uint16_t, uint16_t>>;
+ Maybe<TPortRemapping> mPortRemapping;
+
+ // Called on the socket thread to apply the mapping build on the main thread
+ // from the preference.
+ void ApplyPortRemapPreference(TPortRemapping const& portRemapping);
+
+ void OnKeepaliveEnabledPrefChange();
+ void NotifyKeepaliveEnabledPrefChange(SocketContext* sock);
+
+ // Report socket status to about:networking
+ void AnalyzeConnection(nsTArray<SocketInfo>* data, SocketContext* context,
+ bool aActive);
+
+ void ClosePrivateConnections();
+ void DetachSocketWithGuard(bool aGuardLocals, SocketContextList& socketList,
+ int32_t index);
+
+ void MarkTheLastElementOfPendingQueue();
+
+#if defined(XP_WIN)
+ Atomic<bool> mPolling{false};
+ nsCOMPtr<nsITimer> mPollRepairTimer;
+ void StartPollWatchdog();
+ void DoPollRepair();
+ void StartPolling();
+ void EndPolling();
+#endif
+
+ void TryRepairPollableEvent();
+
+ CopyableTArray<nsCOMPtr<nsISTSShutdownObserver>> mShutdownObservers;
+};
+
+extern nsSocketTransportService* gSocketTransportService;
+bool OnSocketThread();
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransportService_h__
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
new file mode 100644
index 0000000000..de3578c439
--- /dev/null
+++ b/netwerk/base/nsStandardURL.cpp
@@ -0,0 +1,4034 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsASCIIMask.h"
+#include "nsStandardURL.h"
+#include "nsCRT.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIIDNService.h"
+#include "mozilla/Logging.h"
+#include "nsIURLParser.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/TextUtils.h"
+#include <algorithm>
+#include "nsContentUtils.h"
+#include "prprf.h"
+#include "nsReadableUtils.h"
+#include "mozilla/net/MozURL_ffi.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+#include "nsIClassInfoImpl.h"
+#include <string.h>
+
+//
+// setenv MOZ_LOG nsStandardURL:5
+//
+static mozilla::LazyLogModule gStandardURLLog("nsStandardURL");
+
+// The Chromium code defines its own LOG macro which we don't want
+#undef LOG
+#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
+
+// This will always be initialized and destroyed on the main thread, but
+// can be safely used on other threads.
+StaticRefPtr<nsIIDNService> nsStandardURL::gIDN;
+
+// This value will only be updated on the main thread once.
+static Atomic<bool, Relaxed> gInitialized{false};
+
+const char nsStandardURL::gHostLimitDigits[] = {'/', '\\', '?', '#', 0};
+
+// Invalid host characters
+// Note that the array below will be initialized at compile time,
+// so we do not need to "optimize" TestForInvalidHostCharacters.
+//
+constexpr bool TestForInvalidHostCharacters(char c) {
+ // Testing for these:
+ // CONTROL_CHARACTERS " #/:?@[\\]*<>|\"";
+ return (c > 0 && c < 32) || // The control characters are [1, 31]
+ c == 0x7F || // // DEL (delete)
+ c == ' ' || c == '#' || c == '/' || c == ':' || c == '?' || c == '@' ||
+ c == '[' || c == '\\' || c == ']' || c == '*' || c == '<' ||
+ c == '^' ||
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Mailnews %-escapes file paths into URLs.
+ c == '>' || c == '|' || c == '"';
+#else
+ c == '>' || c == '|' || c == '"' || c == '%';
+#endif
+}
+constexpr ASCIIMaskArray sInvalidHostChars =
+ CreateASCIIMask(TestForInvalidHostCharacters);
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsSegmentEncoder
+//----------------------------------------------------------------------------
+
+nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding* encoding)
+ : mEncoding(encoding) {
+ if (mEncoding == UTF_8_ENCODING) {
+ mEncoding = nullptr;
+ }
+}
+
+int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount(
+ const char* aStr, const URLSegment& aSeg, int16_t aMask, nsCString& aOut,
+ bool& aAppended, uint32_t aExtraLen) {
+ // aExtraLen is characters outside the segment that will be
+ // added when the segment is not empty (like the @ following
+ // a username).
+ if (!aStr || aSeg.mLen <= 0) {
+ // Empty segment, so aExtraLen not added per above.
+ aAppended = false;
+ return 0;
+ }
+
+ uint32_t origLen = aOut.Length();
+
+ Span<const char> span = Span(aStr + aSeg.mPos, aSeg.mLen);
+
+ // first honor the origin charset if appropriate. as an optimization,
+ // only do this if the segment is non-ASCII. Further, if mEncoding is
+ // null, then the origin charset is UTF-8 and there is nothing to do.
+ if (mEncoding) {
+ size_t upTo;
+ if (MOZ_UNLIKELY(mEncoding == ISO_2022_JP_ENCODING)) {
+ upTo = Encoding::ISO2022JPASCIIValidUpTo(AsBytes(span));
+ } else {
+ upTo = Encoding::ASCIIValidUpTo(AsBytes(span));
+ }
+ if (upTo != span.Length()) {
+ // we have to encode this segment
+ char bufferArr[512];
+ Span<char> buffer = Span(bufferArr);
+
+ auto encoder = mEncoding->NewEncoder();
+
+ nsAutoCString valid; // has to be declared in this scope
+ if (MOZ_UNLIKELY(!IsUtf8(span.From(upTo)))) {
+ MOZ_ASSERT_UNREACHABLE("Invalid UTF-8 passed to nsStandardURL.");
+ // It's UB to pass invalid UTF-8 to
+ // EncodeFromUTF8WithoutReplacement(), so let's make our input valid
+ // UTF-8 by replacing invalid sequences with the REPLACEMENT
+ // CHARACTER.
+ UTF_8_ENCODING->Decode(
+ nsDependentCSubstring(span.Elements(), span.Length()), valid);
+ // This assigment is OK. `span` can't be used after `valid` has
+ // been destroyed because the only way out of the scope that `valid`
+ // was declared in is via return inside the loop below. Specifically,
+ // the return is the only way out of the loop.
+ span = valid;
+ }
+
+ size_t totalRead = 0;
+ for (;;) {
+ auto [encoderResult, read, written] =
+ encoder->EncodeFromUTF8WithoutReplacement(
+ AsBytes(span.From(totalRead)), AsWritableBytes(buffer), true);
+ totalRead += read;
+ auto bufferWritten = buffer.To(written);
+ if (!NS_EscapeURLSpan(bufferWritten, aMask, aOut)) {
+ aOut.Append(bufferWritten);
+ }
+ if (encoderResult == kInputEmpty) {
+ aAppended = true;
+ // Difference between original and current output
+ // string lengths plus extra length
+ return aOut.Length() - origLen + aExtraLen;
+ }
+ if (encoderResult == kOutputFull) {
+ continue;
+ }
+ aOut.AppendLiteral("%26%23");
+ aOut.AppendInt(encoderResult);
+ aOut.AppendLiteral("%3B");
+ }
+ MOZ_RELEASE_ASSERT(
+ false,
+ "There's supposed to be no way out of the above loop except return.");
+ }
+ }
+
+ if (NS_EscapeURLSpan(span, aMask, aOut)) {
+ aAppended = true;
+ // Difference between original and current output
+ // string lengths plus extra length
+ return aOut.Length() - origLen + aExtraLen;
+ }
+ aAppended = false;
+ // Original segment length plus extra length
+ return span.Length() + aExtraLen;
+}
+
+const nsACString& nsStandardURL::nsSegmentEncoder::EncodeSegment(
+ const nsACString& str, int16_t mask, nsCString& result) {
+ const char* text;
+ bool encoded;
+ EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask,
+ result, encoded);
+ if (encoded) {
+ return result;
+ }
+ return str;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL <public>
+//----------------------------------------------------------------------------
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+static StaticMutex gAllURLsMutex MOZ_UNANNOTATED;
+static LinkedList<nsStandardURL> gAllURLs;
+#endif
+
+nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
+ : mURLType(URLTYPE_STANDARD),
+ mSupportsFileURL(aSupportsFileURL),
+ mCheckedIfHostA(false) {
+ LOG(("Creating nsStandardURL @%p\n", this));
+
+ // gInitialized changes value only once (false->true) on the main thread.
+ // It's OK to race here because in the worst case we'll just
+ // dispatch a noop runnable to the main thread.
+ MOZ_ASSERT(gInitialized);
+
+ // default parser in case nsIStandardURL::Init is never called
+ mParser = net_GetStdURLParser();
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (aTrackURL) {
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ gAllURLs.insertBack(this);
+ }
+#endif
+}
+
+bool nsStandardURL::IsValid() {
+ auto checkSegment = [&](const nsStandardURL::URLSegment& aSeg) {
+#ifdef EARLY_BETA_OR_EARLIER
+ // If the parity is not the same, we assume that this is caused by a memory
+ // error. In this case, we think this URLSegment is valid.
+ if ((aSeg.mPos.Parity() != aSeg.mPos.CalculateParity()) ||
+ (aSeg.mLen.Parity() != aSeg.mLen.CalculateParity())) {
+ MOZ_ASSERT(false);
+ return true;
+ }
+#endif
+ // Bad value
+ if (NS_WARN_IF(aSeg.mLen < -1)) {
+ return false;
+ }
+ if (aSeg.mLen == -1) {
+ return true;
+ }
+
+ // Points out of string
+ if (NS_WARN_IF(aSeg.mPos + aSeg.mLen > mSpec.Length())) {
+ return false;
+ }
+
+ // Overflow
+ if (NS_WARN_IF(aSeg.mPos + aSeg.mLen < aSeg.mPos)) {
+ return false;
+ }
+
+ return true;
+ };
+
+ bool allSegmentsValid = checkSegment(mScheme) && checkSegment(mAuthority) &&
+ checkSegment(mUsername) && checkSegment(mPassword) &&
+ checkSegment(mHost) && checkSegment(mPath) &&
+ checkSegment(mFilepath) && checkSegment(mDirectory) &&
+ checkSegment(mBasename) && checkSegment(mExtension) &&
+ checkSegment(mQuery) && checkSegment(mRef);
+ if (!allSegmentsValid) {
+ return false;
+ }
+
+ if (mScheme.mPos != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsStandardURL::SanityCheck() {
+ if (!IsValid()) {
+ nsPrintfCString msg(
+ "mLen:%zX, mScheme (%X,%X), mAuthority (%X,%X), mUsername (%X,%X), "
+ "mPassword (%X,%X), mHost (%X,%X), mPath (%X,%X), mFilepath (%X,%X), "
+ "mDirectory (%X,%X), mBasename (%X,%X), mExtension (%X,%X), mQuery "
+ "(%X,%X), mRef (%X,%X)",
+ mSpec.Length(), (uint32_t)mScheme.mPos, (int32_t)mScheme.mLen,
+ (uint32_t)mAuthority.mPos, (int32_t)mAuthority.mLen,
+ (uint32_t)mUsername.mPos, (int32_t)mUsername.mLen,
+ (uint32_t)mPassword.mPos, (int32_t)mPassword.mLen, (uint32_t)mHost.mPos,
+ (int32_t)mHost.mLen, (uint32_t)mPath.mPos, (int32_t)mPath.mLen,
+ (uint32_t)mFilepath.mPos, (int32_t)mFilepath.mLen,
+ (uint32_t)mDirectory.mPos, (int32_t)mDirectory.mLen,
+ (uint32_t)mBasename.mPos, (int32_t)mBasename.mLen,
+ (uint32_t)mExtension.mPos, (int32_t)mExtension.mLen,
+ (uint32_t)mQuery.mPos, (int32_t)mQuery.mLen, (uint32_t)mRef.mPos,
+ (int32_t)mRef.mLen);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URLSegments,
+ msg);
+
+ MOZ_CRASH("nsStandardURL::SanityCheck failed");
+ }
+}
+
+nsStandardURL::~nsStandardURL() {
+ LOG(("Destroying nsStandardURL @%p\n", this));
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ {
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ if (isInList()) {
+ remove();
+ }
+ }
+#endif
+}
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+struct DumpLeakedURLs {
+ DumpLeakedURLs() = default;
+ ~DumpLeakedURLs();
+};
+
+DumpLeakedURLs::~DumpLeakedURLs() {
+ MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ if (!gAllURLs.isEmpty()) {
+ printf("Leaked URLs:\n");
+ for (auto* url : gAllURLs) {
+ url->PrintSpec();
+ }
+ gAllURLs.clear();
+ }
+}
+#endif
+
+void nsStandardURL::InitGlobalObjects() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (gInitialized) {
+ return;
+ }
+
+ gInitialized = true;
+
+ nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
+ if (serv) {
+ gIDN = serv;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(gIDN);
+
+ // Make sure nsURLHelper::InitGlobals() gets called on the main thread
+ nsCOMPtr<nsIURLParser> parser = net_GetStdURLParser();
+ MOZ_DIAGNOSTIC_ASSERT(parser);
+ Unused << parser;
+}
+
+void nsStandardURL::ShutdownGlobalObjects() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ gIDN = nullptr;
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (gInitialized) {
+ // This instanciates a dummy class, and will trigger the class
+ // destructor when libxul is unloaded. This is equivalent to atexit(),
+ // but gracefully handles dlclose().
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ static DumpLeakedURLs d;
+ }
+#endif
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL <private>
+//----------------------------------------------------------------------------
+
+void nsStandardURL::Clear() {
+ mSpec.Truncate();
+
+ mPort = -1;
+
+ mScheme.Reset();
+ mAuthority.Reset();
+ mUsername.Reset();
+ mPassword.Reset();
+ mHost.Reset();
+
+ mPath.Reset();
+ mFilepath.Reset();
+ mDirectory.Reset();
+ mBasename.Reset();
+
+ mExtension.Reset();
+ mQuery.Reset();
+ mRef.Reset();
+
+ InvalidateCache();
+}
+
+void nsStandardURL::InvalidateCache(bool invalidateCachedFile) {
+ if (invalidateCachedFile) {
+ mFile = nullptr;
+ }
+}
+
+// Return the number of "dots" in the string, or -1 if invalid. Note that the
+// number of relevant entries in the bases/starts/ends arrays is number of
+// dots + 1.
+//
+// length is assumed to be <= host.Length(); the caller is responsible for that
+//
+// Note that the value returned is guaranteed to be in [-1, 3] range.
+inline int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length, bool trailingDot) {
+ MOZ_ASSERT(length <= (int32_t)host.Length());
+ if (length <= 0) {
+ return -1;
+ }
+
+ bool lastWasNumber = false; // We count on this being false for i == 0
+ int32_t dotCount = 0;
+ onlyBase10 = true;
+
+ for (int32_t i = 0; i < length; i++) {
+ char current = host[i];
+ if (current == '.') {
+ // A dot should not follow a dot, or be first - it can follow an x though.
+ if (!(lastWasNumber ||
+ (i >= 2 && (host[i - 1] == 'X' || host[i - 1] == 'x') &&
+ host[i - 2] == '0')) ||
+ (i == (length - 1) && trailingDot)) {
+ return -1;
+ }
+
+ if (dotCount > 2) {
+ return -1;
+ }
+ lastWasNumber = false;
+ dotIndex[dotCount] = i;
+ dotCount++;
+ } else if (current == 'X' || current == 'x') {
+ if (!lastWasNumber || // An X should not follow an X or a dot or be first
+ i == (length - 1) || // No trailing Xs allowed
+ (dotCount == 0 &&
+ i != 1) || // If we had no dots, an X should be second
+ host[i - 1] != '0' || // X should always follow a 0. Guaranteed i >
+ // 0 as lastWasNumber is true
+ (dotCount > 0 &&
+ host[i - 2] != '.')) { // And that zero follows a dot if it exists
+ return -1;
+ }
+ lastWasNumber = false;
+ bases[dotCount] = 16;
+ onlyBase10 = false;
+
+ } else if (current == '0') {
+ if (i < length - 1 && // Trailing zero doesn't signal octal
+ host[i + 1] != '.' && // Lone zero is not octal
+ (i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot
+ // is a candidate for octal
+ bases[dotCount] = 8; // This will turn to 16 above if X shows up
+ onlyBase10 = false;
+ }
+ lastWasNumber = true;
+
+ } else if (current >= '1' && current <= '7') {
+ lastWasNumber = true;
+
+ } else if (current >= '8' && current <= '9') {
+ if (bases[dotCount] == 8) {
+ return -1;
+ }
+ lastWasNumber = true;
+
+ } else if ((current >= 'a' && current <= 'f') ||
+ (current >= 'A' && current <= 'F')) {
+ if (bases[dotCount] != 16) {
+ return -1;
+ }
+ lastWasNumber = true;
+
+ } else {
+ return -1;
+ }
+ }
+
+ return dotCount;
+}
+
+inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
+ uint32_t maxNumber) {
+ uint64_t value = 0;
+ const char* current = input.BeginReading();
+ const char* end = input.EndReading();
+ for (; current < end; ++current) {
+ char c = *current;
+ MOZ_ASSERT(c >= '0' && c <= '9');
+ value *= 10;
+ value += c - '0';
+ }
+ if (value <= maxNumber) {
+ number = value;
+ return NS_OK;
+ }
+
+ // The error case
+ number = 0;
+ return NS_ERROR_FAILURE;
+}
+
+inline nsresult ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber) {
+ // Accumulate in the 64-bit value
+ uint64_t value = 0;
+ const char* current = input.BeginReading();
+ const char* end = input.EndReading();
+ switch (base) {
+ case 16:
+ ++current;
+ [[fallthrough]];
+ case 8:
+ ++current;
+ break;
+ case 10:
+ default:
+ break;
+ }
+ for (; current < end; ++current) {
+ value *= base;
+ char c = *current;
+ MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
+ (base == 8 && c >= '0' && c <= '7') ||
+ (base == 16 && IsAsciiHexDigit(c)));
+ if (IsAsciiDigit(c)) {
+ value += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ value += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ value += c - 'A' + 10;
+ }
+ }
+
+ if (value <= maxNumber) {
+ number = value;
+ return NS_OK;
+ }
+
+ // The error case
+ number = 0;
+ return NS_ERROR_FAILURE;
+}
+
+// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
+/* static */
+nsresult nsStandardURL::NormalizeIPv4(const nsACString& host,
+ nsCString& result) {
+ int32_t bases[4] = {10, 10, 10, 10};
+ bool onlyBase10 = true; // Track this as a special case
+ int32_t dotIndex[3]; // The positions of the dots in the string
+
+ // Use "length" rather than host.Length() after call to
+ // ValidateIPv4Number because of potential trailing period.
+ nsDependentCSubstring filteredHost;
+ bool trailingDot = false;
+ if (host.Length() > 0 && host.Last() == '.') {
+ trailingDot = true;
+ filteredHost.Rebind(host.BeginReading(), host.Length() - 1);
+ } else {
+ filteredHost.Rebind(host.BeginReading(), host.Length());
+ }
+
+ int32_t length = static_cast<int32_t>(filteredHost.Length());
+ int32_t dotCount = ValidateIPv4Number(filteredHost, bases, dotIndex,
+ onlyBase10, length, trailingDot);
+ if (dotCount < 0 || length <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Max values specified by the spec
+ static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu,
+ 0xffu};
+ uint32_t ipv4;
+ int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
+
+ // parse the last part first
+ nsresult res;
+ // Doing a special case for all items being base 10 gives ~35% speedup
+ res = (onlyBase10
+ ? ParseIPv4Number10(Substring(host, start, length - start), ipv4,
+ upperBounds[dotCount])
+ : ParseIPv4Number(Substring(host, start, length - start),
+ bases[dotCount], ipv4, upperBounds[dotCount]));
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // parse remaining parts starting from first part
+ int32_t lastUsed = -1;
+ for (int32_t i = 0; i < dotCount; i++) {
+ uint32_t number;
+ start = lastUsed + 1;
+ lastUsed = dotIndex[i];
+ res =
+ (onlyBase10 ? ParseIPv4Number10(
+ Substring(host, start, lastUsed - start), number, 255)
+ : ParseIPv4Number(Substring(host, start, lastUsed - start),
+ bases[i], number, 255));
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+ ipv4 += number << (8 * (3 - i));
+ }
+
+ // A special case for ipv4 URL like "127." should have the same result as
+ // "127".
+ if (dotCount == 1 && dotIndex[0] == length - 1) {
+ ipv4 = (ipv4 & 0xff000000) >> 24;
+ }
+
+ uint8_t ipSegments[4];
+ NetworkEndian::writeUint32(ipSegments, ipv4);
+ result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
+ ipSegments[2], ipSegments[3]);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::NormalizeIDN(const nsCString& host, nsCString& result) {
+ result.Truncate();
+ mDisplayHost.Truncate();
+ nsresult rv;
+
+ if (!gIDN) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Even if it's already ACE, we must still call ConvertUTF8toACE in order
+ // for the input normalization to take place.
+ rv = gIDN->ConvertUTF8toACE(host, result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the ASCII representation doesn't contain the xn-- token then we don't
+ // need to call ConvertToDisplayIDN as that would not change anything.
+ if (!StringBeginsWith(result, "xn--"_ns) &&
+ result.Find(".xn--"_ns) == kNotFound) {
+ mCheckedIfHostA = true;
+ return NS_OK;
+ }
+
+ bool isAscii = true;
+ nsAutoCString displayHost;
+ rv = gIDN->ConvertToDisplayIDN(result, &isAscii, displayHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mCheckedIfHostA = true;
+ if (!isAscii) {
+ mDisplayHost = displayHost;
+ }
+ return NS_OK;
+}
+
+bool nsStandardURL::ValidIPv6orHostname(const char* host, uint32_t length) {
+ if (!host || !*host) {
+ // Should not be NULL or empty string
+ return false;
+ }
+
+ if (length != strlen(host)) {
+ // Embedded null
+ return false;
+ }
+
+ bool openBracket = host[0] == '[';
+ bool closeBracket = host[length - 1] == ']';
+
+ if (openBracket && closeBracket) {
+ return net_IsValidIPv6Addr(Substring(host + 1, length - 2));
+ }
+
+ if (openBracket || closeBracket) {
+ // Fail if only one of the brackets is present
+ return false;
+ }
+
+ const char* end = host + length;
+ const char* iter = host;
+ for (; iter != end && *iter; ++iter) {
+ if (ASCIIMask::IsMasked(sInvalidHostChars, *iter)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char* path) {
+ net_CoalesceDirs(coalesceFlag, path);
+ int32_t newLen = strlen(path);
+ if (newLen < mPath.mLen) {
+ int32_t diff = newLen - mPath.mLen;
+ mPath.mLen = newLen;
+ mDirectory.mLen += diff;
+ mFilepath.mLen += diff;
+ ShiftFromBasename(diff);
+ }
+}
+
+uint32_t nsStandardURL::AppendSegmentToBuf(char* buf, uint32_t i,
+ const char* str,
+ const URLSegment& segInput,
+ URLSegment& segOutput,
+ const nsCString* escapedStr,
+ bool useEscaped, int32_t* diff) {
+ MOZ_ASSERT(segInput.mLen == segOutput.mLen);
+
+ if (diff) {
+ *diff = 0;
+ }
+
+ if (segInput.mLen > 0) {
+ if (useEscaped) {
+ MOZ_ASSERT(diff);
+ segOutput.mLen = escapedStr->Length();
+ *diff = segOutput.mLen - segInput.mLen;
+ memcpy(buf + i, escapedStr->get(), segOutput.mLen);
+ } else {
+ memcpy(buf + i, str + segInput.mPos, segInput.mLen);
+ }
+ segOutput.mPos = i;
+ i += segOutput.mLen;
+ } else {
+ segOutput.mPos = i;
+ }
+ return i;
+}
+
+uint32_t nsStandardURL::AppendToBuf(char* buf, uint32_t i, const char* str,
+ uint32_t len) {
+ memcpy(buf + i, str, len);
+ return i + len;
+}
+
+static bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiDigit(*c)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiHexDigit(*c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// https://url.spec.whatwg.org/#ends-in-a-number-checker
+static bool EndsInANumber(const nsCString& input) {
+ // 1. Let parts be the result of strictly splitting input on U+002E (.).
+ nsTArray<nsDependentCSubstring> parts;
+ for (const nsDependentCSubstring& part : input.Split('.')) {
+ parts.AppendElement(part);
+ }
+
+ if (parts.Length() == 0) {
+ return false;
+ }
+
+ // 2.If the last item in parts is the empty string, then:
+ // 1. If parts’s size is 1, then return false.
+ // 2. Remove the last item from parts.
+ if (parts.LastElement().IsEmpty()) {
+ if (parts.Length() == 1) {
+ return false;
+ }
+ Unused << parts.PopLastElement();
+ }
+
+ // 3. Let last be the last item in parts.
+ const nsDependentCSubstring& last = parts.LastElement();
+
+ // 4. If last is non-empty and contains only ASCII digits, then return true.
+ // The erroneous input "09" will be caught by the IPv4 parser at a later
+ // stage.
+ if (!last.IsEmpty()) {
+ if (ContainsOnlyAsciiDigits(last)) {
+ return true;
+ }
+ }
+
+ // 5. If parsing last as an IPv4 number does not return failure, then return
+ // true. This is equivalent to checking that last is "0X" or "0x", followed by
+ // zero or more ASCII hex digits.
+ if (StringBeginsWith(last, "0x"_ns) || StringBeginsWith(last, "0X"_ns)) {
+ if (ContainsOnlyAsciiHexDigits(Substring(last, 2))) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// basic algorithm:
+// 1- escape url segments (for improved GetSpec efficiency)
+// 2- allocate spec buffer
+// 3- write url segments
+// 4- update url segment positions and lengths
+nsresult nsStandardURL::BuildNormalizedSpec(const char* spec,
+ const Encoding* encoding) {
+ // Assumptions: all member URLSegments must be relative the |spec| argument
+ // passed to this function.
+
+ // buffers for holding escaped url segments (these will remain empty unless
+ // escaping is required).
+ nsAutoCString encUsername, encPassword, encHost, encDirectory, encBasename,
+ encExtension, encQuery, encRef;
+ bool useEncUsername, useEncPassword, useEncHost = false, useEncDirectory,
+ useEncBasename, useEncExtension,
+ useEncQuery, useEncRef;
+ nsAutoCString portbuf;
+
+ //
+ // escape each URL segment, if necessary, and calculate approximate normalized
+ // spec length.
+ //
+ // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]
+
+ uint32_t approxLen = 0;
+
+ // the scheme is already ASCII
+ if (mScheme.mLen > 0) {
+ approxLen +=
+ mScheme.mLen + 3; // includes room for "://", which we insert always
+ }
+
+ // encode URL segments; convert UTF-8 to origin charset and possibly escape.
+ // results written to encXXX variables only if |spec| is not already in the
+ // appropriate encoding.
+ {
+ nsSegmentEncoder encoder;
+ nsSegmentEncoder queryEncoder(encoding);
+ // Username@
+ approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username,
+ encUsername, useEncUsername, 0);
+ approxLen += 1; // reserve length for @
+ // :password - we insert the ':' even if there's no actual password if
+ // "user:@" was in the spec
+ if (mPassword.mLen > 0) {
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password,
+ encPassword, useEncPassword);
+ }
+ // mHost is handled differently below due to encoding differences
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort) {
+ // :port
+ portbuf.AppendInt(mPort);
+ approxLen += portbuf.Length() + 1;
+ }
+
+ approxLen +=
+ 1; // reserve space for possible leading '/' - may not be needed
+ // Should just use mPath? These are pessimistic, and thus waste space
+ approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory,
+ encDirectory, useEncDirectory, 1);
+ approxLen += encoder.EncodeSegmentCount(spec, mBasename, esc_FileBaseName,
+ encBasename, useEncBasename);
+ approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension,
+ encExtension, useEncExtension, 1);
+
+ // These next ones *always* add their leading character even if length is 0
+ // Handles items like "http://#"
+ // ?query
+ if (mQuery.mLen >= 0) {
+ approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query,
+ encQuery, useEncQuery);
+ }
+ // #ref
+
+ if (mRef.mLen >= 0) {
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, encRef,
+ useEncRef);
+ }
+ }
+
+ // do not escape the hostname, if IPv6 address literal, mHost will
+ // already point to a [ ] delimited IPv6 address literal.
+ // However, perform Unicode normalization on it, as IDN does.
+ // Note that we don't disallow URLs without a host - file:, etc
+ if (mHost.mLen > 0) {
+ nsAutoCString tempHost;
+ NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host,
+ tempHost);
+ if (tempHost.Contains('\0')) {
+ return NS_ERROR_MALFORMED_URI; // null embedded in hostname
+ }
+ if (tempHost.Contains(' ')) {
+ return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname
+ }
+ nsresult rv = NormalizeIDN(tempHost, encHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!SegmentIs(spec, mScheme, "resource") &&
+ !SegmentIs(spec, mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (encHost.Length() > 0 && encHost.First() == '[' &&
+ encHost.Last() == ']' &&
+ ValidIPv6orHostname(encHost.get(), encHost.Length())) {
+ rv = (nsresult)rusturl_parse_ipv6addr(&encHost, &ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ encHost = ipString;
+ } else {
+ if (EndsInANumber(encHost)) {
+ rv = NormalizeIPv4(encHost, ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ encHost = ipString;
+ }
+ }
+ }
+
+ // NormalizeIDN always copies, if the call was successful.
+ useEncHost = true;
+ approxLen += encHost.Length();
+
+ if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ } else {
+ // empty host means empty mDisplayHost
+ mDisplayHost.Truncate();
+ mCheckedIfHostA = true;
+ }
+
+ // We must take a copy of every single segment because they are pointing to
+ // the |spec| while we are changing their value, in case we must use
+ // encoded strings.
+ URLSegment username(mUsername);
+ URLSegment password(mPassword);
+ URLSegment host(mHost);
+ URLSegment path(mPath);
+ URLSegment directory(mDirectory);
+ URLSegment basename(mBasename);
+ URLSegment extension(mExtension);
+ URLSegment query(mQuery);
+ URLSegment ref(mRef);
+
+ // The encoded string could be longer than the original input, so we need
+ // to check the final URI isn't longer than the max length.
+ if (approxLen + 1 > StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // generate the normalized URL string
+ //
+ // approxLen should be correct or 1 high
+ if (!mSpec.SetLength(approxLen + 1,
+ fallible)) { // buf needs a trailing '\0' below
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ char* buf = mSpec.BeginWriting();
+ uint32_t i = 0;
+ int32_t diff = 0;
+
+ if (mScheme.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme);
+ net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen);
+ i = AppendToBuf(buf, i, "://", 3);
+ }
+
+ // record authority starting position
+ mAuthority.mPos = i;
+
+ // append authority
+ if (mUsername.mLen > 0 || mPassword.mLen > 0) {
+ if (mUsername.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, username, mUsername, &encUsername,
+ useEncUsername, &diff);
+ ShiftFromPassword(diff);
+ } else {
+ mUsername.mLen = -1;
+ }
+ if (password.mLen > 0) {
+ buf[i++] = ':';
+ i = AppendSegmentToBuf(buf, i, spec, password, mPassword, &encPassword,
+ useEncPassword, &diff);
+ ShiftFromHost(diff);
+ } else {
+ mPassword.mLen = -1;
+ }
+ buf[i++] = '@';
+ } else {
+ mUsername.mLen = -1;
+ mPassword.mLen = -1;
+ }
+ if (host.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
+ &diff);
+ ShiftFromPath(diff);
+
+ net_ToLowerCase(buf + mHost.mPos, mHost.mLen);
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort) {
+ buf[i++] = ':';
+ // Already formatted while building approxLen
+ i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length());
+ }
+ }
+
+ // record authority length
+ mAuthority.mLen = i - mAuthority.mPos;
+
+ // path must always start with a "/"
+ if (mPath.mLen <= 0) {
+ LOG(("setting path=/"));
+ mDirectory.mPos = mFilepath.mPos = mPath.mPos = i;
+ mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1;
+ // basename must exist, even if empty (bug 113508)
+ mBasename.mPos = i + 1;
+ mBasename.mLen = 0;
+ buf[i++] = '/';
+ } else {
+ uint32_t leadingSlash = 0;
+ if (spec[path.mPos] != '/') {
+ LOG(("adding leading slash to path\n"));
+ leadingSlash = 1;
+ buf[i++] = '/';
+ // basename must exist, even if empty (bugs 113508, 429347)
+ if (mBasename.mLen == -1) {
+ mBasename.mPos = basename.mPos = i;
+ mBasename.mLen = basename.mLen = 0;
+ }
+ }
+
+ // record corrected (file)path starting position
+ mPath.mPos = mFilepath.mPos = i - leadingSlash;
+
+ i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory, &encDirectory,
+ useEncDirectory, &diff);
+ ShiftFromBasename(diff);
+
+ // the directory must end with a '/'
+ if (buf[i - 1] != '/') {
+ buf[i++] = '/';
+ mDirectory.mLen++;
+ }
+
+ i = AppendSegmentToBuf(buf, i, spec, basename, mBasename, &encBasename,
+ useEncBasename, &diff);
+ ShiftFromExtension(diff);
+
+ // make corrections to directory segment if leadingSlash
+ if (leadingSlash) {
+ mDirectory.mPos = mPath.mPos;
+ if (mDirectory.mLen >= 0) {
+ mDirectory.mLen += leadingSlash;
+ } else {
+ mDirectory.mLen = 1;
+ }
+ }
+
+ if (mExtension.mLen >= 0) {
+ buf[i++] = '.';
+ i = AppendSegmentToBuf(buf, i, spec, extension, mExtension, &encExtension,
+ useEncExtension, &diff);
+ ShiftFromQuery(diff);
+ }
+ // calculate corrected filepath length
+ mFilepath.mLen = i - mFilepath.mPos;
+
+ if (mQuery.mLen >= 0) {
+ buf[i++] = '?';
+ i = AppendSegmentToBuf(buf, i, spec, query, mQuery, &encQuery,
+ useEncQuery, &diff);
+ ShiftFromRef(diff);
+ }
+ if (mRef.mLen >= 0) {
+ buf[i++] = '#';
+ i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
+ &diff);
+ }
+ // calculate corrected path length
+ mPath.mLen = i - mPath.mPos;
+ }
+
+ buf[i] = '\0';
+
+ // https://url.spec.whatwg.org/#path-state (1.4.1.2)
+ // https://url.spec.whatwg.org/#windows-drive-letter
+ if (SegmentIs(buf, mScheme, "file")) {
+ char* path = &buf[mPath.mPos];
+ if (mPath.mLen >= 3 && path[0] == '/' && IsAsciiAlpha(path[1]) &&
+ path[2] == '|') {
+ buf[mPath.mPos + 2] = ':';
+ }
+ }
+
+ if (mDirectory.mLen > 1) {
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+ if (SegmentIs(buf, mScheme, "ftp")) {
+ coalesceFlag =
+ (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ CoalescePath(coalesceFlag, buf + mDirectory.mPos);
+ }
+ mSpec.Truncate(strlen(buf));
+ NS_ASSERTION(mSpec.Length() <= approxLen,
+ "We've overflowed the mSpec buffer!");
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+bool nsStandardURL::SegmentIs(const URLSegment& seg, const char* val,
+ bool ignoreCase) {
+ // one or both may be null
+ if (!val || mSpec.IsEmpty()) {
+ return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
+ }
+ if (seg.mLen < 0) {
+ return false;
+ }
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase) {
+ return !nsCRT::strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+ }
+
+ return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+}
+
+bool nsStandardURL::SegmentIs(const char* spec, const URLSegment& seg,
+ const char* val, bool ignoreCase) {
+ // one or both may be null
+ if (!val || !spec) {
+ return (!val && (!spec || seg.mLen < 0));
+ }
+ if (seg.mLen < 0) {
+ return false;
+ }
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase) {
+ return !nsCRT::strncasecmp(spec + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+ }
+
+ return !strncmp(spec + seg.mPos, val, seg.mLen) && (val[seg.mLen] == '\0');
+}
+
+bool nsStandardURL::SegmentIs(const URLSegment& seg1, const char* val,
+ const URLSegment& seg2, bool ignoreCase) {
+ if (seg1.mLen != seg2.mLen) {
+ return false;
+ }
+ if (seg1.mLen == -1 || (!val && mSpec.IsEmpty())) {
+ return true; // both are empty
+ }
+ if (!val) {
+ return false;
+ }
+ if (ignoreCase) {
+ return !nsCRT::strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos,
+ seg1.mLen);
+ }
+
+ return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
+}
+
+int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
+ const char* val, uint32_t valLen) {
+ if (val && valLen) {
+ if (len == 0) {
+ mSpec.Insert(val, pos, valLen);
+ } else {
+ mSpec.Replace(pos, len, nsDependentCString(val, valLen));
+ }
+ return valLen - len;
+ }
+
+ // else remove the specified segment
+ mSpec.Cut(pos, len);
+ return -int32_t(len);
+}
+
+int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
+ const nsACString& val) {
+ if (len == 0) {
+ mSpec.Insert(val, pos);
+ } else {
+ mSpec.Replace(pos, len, val);
+ }
+ return val.Length() - len;
+}
+
+nsresult nsStandardURL::ParseURL(const char* spec, int32_t specLen) {
+ nsresult rv;
+
+ if (specLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // parse given URL string
+ //
+ uint32_t schemePos = mScheme.mPos;
+ int32_t schemeLen = mScheme.mLen;
+ uint32_t authorityPos = mAuthority.mPos;
+ int32_t authorityLen = mAuthority.mLen;
+ uint32_t pathPos = mPath.mPos;
+ int32_t pathLen = mPath.mLen;
+ rv = mParser->ParseURL(spec, specLen, &schemePos, &schemeLen, &authorityPos,
+ &authorityLen, &pathPos, &pathLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mScheme.mPos = schemePos;
+ mScheme.mLen = schemeLen;
+ mAuthority.mPos = authorityPos;
+ mAuthority.mLen = authorityLen;
+ mPath.mPos = pathPos;
+ mPath.mLen = pathLen;
+
+#ifdef DEBUG
+ if (mScheme.mLen <= 0) {
+ printf("spec=%s\n", spec);
+ NS_WARNING("malformed url: no scheme");
+ }
+#endif
+
+ if (mAuthority.mLen > 0) {
+ uint32_t usernamePos = mUsername.mPos;
+ int32_t usernameLen = mUsername.mLen;
+ uint32_t passwordPos = mPassword.mPos;
+ int32_t passwordLen = mPassword.mLen;
+ uint32_t hostPos = mHost.mPos;
+ int32_t hostLen = mHost.mLen;
+ rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
+ &usernamePos, &usernameLen, &passwordPos,
+ &passwordLen, &hostPos, &hostLen, &mPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mUsername.mPos = usernamePos;
+ mUsername.mLen = usernameLen;
+ mPassword.mPos = passwordPos;
+ mPassword.mLen = passwordLen;
+ mHost.mPos = hostPos;
+ mHost.mLen = hostLen;
+
+ // Don't allow mPort to be set to this URI's default port
+ if (mPort == mDefaultPort) {
+ mPort = -1;
+ }
+
+ mUsername.mPos += mAuthority.mPos;
+ mPassword.mPos += mAuthority.mPos;
+ mHost.mPos += mAuthority.mPos;
+ }
+
+ if (mPath.mLen > 0) {
+ rv = ParsePath(spec, mPath.mPos, mPath.mLen);
+ }
+
+ return rv;
+}
+
+nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos,
+ int32_t pathLen) {
+ LOG(("ParsePath: %s pathpos %d len %d\n", spec, pathPos, pathLen));
+
+ if (pathLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ uint32_t filePathPos = mFilepath.mPos;
+ int32_t filePathLen = mFilepath.mLen;
+ uint32_t queryPos = mQuery.mPos;
+ int32_t queryLen = mQuery.mLen;
+ uint32_t refPos = mRef.mPos;
+ int32_t refLen = mRef.mLen;
+ nsresult rv =
+ mParser->ParsePath(spec + pathPos, pathLen, &filePathPos, &filePathLen,
+ &queryPos, &queryLen, &refPos, &refLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mFilepath.mPos = filePathPos;
+ mFilepath.mLen = filePathLen;
+ mQuery.mPos = queryPos;
+ mQuery.mLen = queryLen;
+ mRef.mPos = refPos;
+ mRef.mLen = refLen;
+
+ mFilepath.mPos += pathPos;
+ mQuery.mPos += pathPos;
+ mRef.mPos += pathPos;
+
+ if (mFilepath.mLen > 0) {
+ uint32_t directoryPos = mDirectory.mPos;
+ int32_t directoryLen = mDirectory.mLen;
+ uint32_t basenamePos = mBasename.mPos;
+ int32_t basenameLen = mBasename.mLen;
+ uint32_t extensionPos = mExtension.mPos;
+ int32_t extensionLen = mExtension.mLen;
+ rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
+ &directoryPos, &directoryLen, &basenamePos,
+ &basenameLen, &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDirectory.mPos = directoryPos;
+ mDirectory.mLen = directoryLen;
+ mBasename.mPos = basenamePos;
+ mBasename.mLen = basenameLen;
+ mExtension.mPos = extensionPos;
+ mExtension.mLen = extensionLen;
+
+ mDirectory.mPos += mFilepath.mPos;
+ mBasename.mPos += mFilepath.mPos;
+ mExtension.mPos += mFilepath.mPos;
+ }
+ return NS_OK;
+}
+
+char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len,
+ const char* tail) {
+ // Verify pos and length are within boundaries
+ if (pos > mSpec.Length()) {
+ return nullptr;
+ }
+ if (len < 0) {
+ return nullptr;
+ }
+ if ((uint32_t)len > (mSpec.Length() - pos)) {
+ return nullptr;
+ }
+ if (!tail) {
+ return nullptr;
+ }
+
+ uint32_t tailLen = strlen(tail);
+
+ // Check for int overflow for proposed length of combined string
+ if (UINT32_MAX - ((uint32_t)len + 1) < tailLen) {
+ return nullptr;
+ }
+
+ char* result = (char*)moz_xmalloc(len + tailLen + 1);
+ memcpy(result, mSpec.get() + pos, len);
+ memcpy(result + len, tail, tailLen);
+ result[len + tailLen] = '\0';
+ return result;
+}
+
+nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream,
+ URLSegment& seg) {
+ nsresult rv;
+
+ uint32_t pos = seg.mPos;
+ rv = stream->Read32(&pos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ seg.mPos = pos;
+
+ uint32_t len = seg.mLen;
+ rv = stream->Read32(&len);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CheckedInt<int32_t> checkedLen(len);
+ if (!checkedLen.isValid()) {
+ seg.mLen = -1;
+ } else {
+ seg.mLen = len;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream,
+ const URLSegment& seg) {
+ nsresult rv;
+
+ rv = stream->Write32(seg.mPos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Write32(uint32_t(seg.mLen));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+#define SHIFT_FROM(name, what) \
+ void nsStandardURL::name(int32_t diff) { \
+ if (!diff) return; \
+ if ((what).mLen >= 0) { \
+ CheckedInt<int32_t> pos = (uint32_t)(what).mPos; \
+ pos += diff; \
+ MOZ_ASSERT(pos.isValid()); \
+ (what).mPos = pos.value(); \
+ } else { \
+ MOZ_RELEASE_ASSERT((what).mLen == -1); \
+ }
+
+#define SHIFT_FROM_NEXT(name, what, next) \
+ SHIFT_FROM(name, what) \
+ next(diff); \
+ }
+
+#define SHIFT_FROM_LAST(name, what) \
+ SHIFT_FROM(name, what) \
+ }
+
+SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
+SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
+SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
+SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
+SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
+SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
+SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
+SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
+SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
+SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
+SHIFT_FROM_LAST(ShiftFromRef, mRef)
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMPL_CLASSINFO(nsStandardURL, nullptr, nsIClassInfo::THREADSAFE,
+ NS_STANDARDURL_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsStandardURL)
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsStandardURL)
+NS_IMPL_RELEASE(nsStandardURL)
+
+NS_INTERFACE_MAP_BEGIN(nsStandardURL)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
+ NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_IMPL_QUERY_CLASSINFO(nsStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
+ // see nsStandardURL::Equals
+ if (aIID.Equals(kThisImplCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURI
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpec(nsACString& result) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+ result = mSpec;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString& result) {
+ nsresult rv = GetSpec(result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mPassword.mLen > 0) {
+ result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****");
+ }
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpecIgnoringRef(nsACString& result) {
+ // URI without ref is 0 to one char before ref
+ if (mRef.mLen < 0) {
+ return GetSpec(result);
+ }
+
+ URLSegment noRef(0, mRef.mPos - 1);
+ result = Segment(noRef);
+ MOZ_ASSERT(mCheckedIfHostA);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::CheckIfHostIsAscii() {
+ nsresult rv;
+ if (mCheckedIfHostA) {
+ return NS_OK;
+ }
+
+ mCheckedIfHostA = true;
+
+ if (!gIDN) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsAutoCString displayHost;
+ bool isAscii;
+ rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost);
+ if (NS_FAILED(rv)) {
+ mDisplayHost.Truncate();
+ mCheckedIfHostA = false;
+ return rv;
+ }
+
+ if (!isAscii) {
+ mDisplayHost = displayHost;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ aUnicodeSpec.Assign(mSpec);
+ MOZ_ASSERT(mCheckedIfHostA);
+ if (!mDisplayHost.IsEmpty()) {
+ aUnicodeSpec.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
+ nsAutoCString unicodeHostPort;
+
+ nsresult rv = GetDisplayHost(unicodeHostPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (StringBeginsWith(Hostport(), "["_ns)) {
+ aUnicodeHostPort.AssignLiteral("[");
+ aUnicodeHostPort.Append(unicodeHostPort);
+ aUnicodeHostPort.AppendLiteral("]");
+ } else {
+ aUnicodeHostPort.Assign(unicodeHostPort);
+ }
+
+ uint32_t pos = mHost.mPos + mHost.mLen;
+ if (pos < mPath.mPos) {
+ aUnicodeHostPort += Substring(mSpec, pos, mPath.mPos - pos);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetDisplayHost(nsACString& aUnicodeHost) {
+ MOZ_ASSERT(mCheckedIfHostA);
+ if (mDisplayHost.IsEmpty()) {
+ return GetAsciiHost(aUnicodeHost);
+ }
+
+ aUnicodeHost = mDisplayHost;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPrePath(nsACString& result) {
+ result = Prepath();
+ MOZ_ASSERT(mCheckedIfHostA);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDisplayPrePath(nsACString& result) {
+ result = Prepath();
+ MOZ_ASSERT(mCheckedIfHostA);
+ if (!mDisplayHost.IsEmpty()) {
+ result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
+ }
+ return NS_OK;
+}
+
+// result is strictly US-ASCII
+NS_IMETHODIMP
+nsStandardURL::GetScheme(nsACString& result) {
+ result = Scheme();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUserPass(nsACString& result) {
+ result = Userpass();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUsername(nsACString& result) {
+ result = Username();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPassword(nsACString& result) {
+ result = Password();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHostPort(nsACString& result) {
+ return GetAsciiHostPort(result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHost(nsACString& result) { return GetAsciiHost(result); }
+
+NS_IMETHODIMP
+nsStandardURL::GetPort(int32_t* result) {
+ // should never be more than 16 bit
+ MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
+ *result = mPort;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPathQueryRef(nsACString& result) {
+ result = Path();
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiSpec(nsACString& result) {
+ result = mSpec;
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHostPort(nsACString& result) {
+ result = Hostport();
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHost(nsACString& result) {
+ result = Host();
+ return NS_OK;
+}
+
+static bool IsSpecialProtocol(const nsACString& input) {
+ nsACString::const_iterator start, end;
+ input.BeginReading(start);
+ nsACString::const_iterator iterator(start);
+ input.EndReading(end);
+
+ while (iterator != end && *iterator != ':') {
+ iterator++;
+ }
+
+ nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get()));
+
+ return protocol.LowerCaseEqualsLiteral("http") ||
+ protocol.LowerCaseEqualsLiteral("https") ||
+ protocol.LowerCaseEqualsLiteral("ftp") ||
+ protocol.LowerCaseEqualsLiteral("ws") ||
+ protocol.LowerCaseEqualsLiteral("wss") ||
+ protocol.LowerCaseEqualsLiteral("file") ||
+ protocol.LowerCaseEqualsLiteral("gopher");
+}
+
+nsresult nsStandardURL::SetSpecInternal(const nsACString& input) {
+ return SetSpecWithEncoding(input, nullptr);
+}
+
+nsresult nsStandardURL::SetSpecWithEncoding(const nsACString& input,
+ const Encoding* encoding) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
+
+ if (input.Length() > StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI;
+ net_FilterURIString(flat, filteredURI);
+
+ if (filteredURI.Length() == 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Make a backup of the current URL
+ nsStandardURL prevURL(false, false);
+ prevURL.CopyMembers(this, eHonorRef, ""_ns);
+ Clear();
+
+ if (IsSpecialProtocol(filteredURI)) {
+ // Bug 652186: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ auto* start = filteredURI.BeginWriting();
+ auto* end = filteredURI.EndWriting();
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ const char* spec = filteredURI.get();
+ int32_t specLength = filteredURI.Length();
+
+ // parse the given URL...
+ nsresult rv = ParseURL(spec, specLength);
+ if (mScheme.mLen <= 0) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // finally, use the URLSegment member variables to build a normalized
+ // copy of |spec|
+ rv = BuildNormalizedSpec(spec, encoding);
+ }
+
+ // Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
+ if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+
+ if (NS_FAILED(rv)) {
+ Clear();
+ // If parsing the spec has failed, restore the old URL
+ // so we don't end up with an empty URL.
+ CopyMembers(&prevURL, eHonorRef, ""_ns);
+ return rv;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG((" spec = %s\n", mSpec.get()));
+ LOG((" port = %d\n", mPort));
+ LOG((" scheme = (%u,%d)\n", (uint32_t)mScheme.mPos,
+ (int32_t)mScheme.mLen));
+ LOG((" authority = (%u,%d)\n", (uint32_t)mAuthority.mPos,
+ (int32_t)mAuthority.mLen));
+ LOG((" username = (%u,%d)\n", (uint32_t)mUsername.mPos,
+ (int32_t)mUsername.mLen));
+ LOG((" password = (%u,%d)\n", (uint32_t)mPassword.mPos,
+ (int32_t)mPassword.mLen));
+ LOG((" hostname = (%u,%d)\n", (uint32_t)mHost.mPos, (int32_t)mHost.mLen));
+ LOG((" path = (%u,%d)\n", (uint32_t)mPath.mPos, (int32_t)mPath.mLen));
+ LOG((" filepath = (%u,%d)\n", (uint32_t)mFilepath.mPos,
+ (int32_t)mFilepath.mLen));
+ LOG((" directory = (%u,%d)\n", (uint32_t)mDirectory.mPos,
+ (int32_t)mDirectory.mLen));
+ LOG((" basename = (%u,%d)\n", (uint32_t)mBasename.mPos,
+ (int32_t)mBasename.mLen));
+ LOG((" extension = (%u,%d)\n", (uint32_t)mExtension.mPos,
+ (int32_t)mExtension.mLen));
+ LOG((" query = (%u,%d)\n", (uint32_t)mQuery.mPos,
+ (int32_t)mQuery.mLen));
+ LOG((" ref = (%u,%d)\n", (uint32_t)mRef.mPos, (int32_t)mRef.mLen));
+ }
+
+ SanityCheck();
+ return rv;
+}
+
+nsresult nsStandardURL::SetScheme(const nsACString& input) {
+ // Strip tabs, newlines, carriage returns from input
+ nsAutoCString scheme(input);
+ scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
+
+ if (scheme.IsEmpty()) {
+ NS_WARNING("cannot remove the scheme from an url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mScheme.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!net_IsValidScheme(scheme)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Scheme().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
+
+ if (shift) {
+ mScheme.mLen = scheme.Length();
+ ShiftFromAuthority(shift);
+ }
+
+ // ensure new scheme is lowercase
+ //
+ // XXX the string code unfortunately doesn't provide a ToLowerCase
+ // that operates on a substring.
+ net_ToLowerCase((char*)mSpec.get(), mScheme.mLen);
+
+ // If the scheme changes the default port also changes.
+ if (Scheme() == "http"_ns || Scheme() == "ws"_ns) {
+ mDefaultPort = 80;
+ } else if (Scheme() == "https"_ns || Scheme() == "wss"_ns) {
+ mDefaultPort = 443;
+ }
+ if (mPort == mDefaultPort) {
+ MOZ_ALWAYS_SUCCEEDS(SetPort(-1));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetUserPass(const nsACString& input) {
+ const nsPromiseFlatCString& userpass = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (userpass.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set user:pass on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mAuthority.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mSpec.Length() + input.Length() - Userpass(true).Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ InvalidateCache();
+
+ NS_ASSERTION(mHost.mLen >= 0, "uninitialized");
+
+ nsresult rv;
+ uint32_t usernamePos, passwordPos;
+ int32_t usernameLen, passwordLen;
+
+ rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(), &usernamePos,
+ &usernameLen, &passwordPos, &passwordLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // build new user:pass in |buf|
+ nsAutoCString buf;
+ if (usernameLen > 0 || passwordLen > 0) {
+ nsSegmentEncoder encoder;
+ bool ignoredOut;
+ usernameLen = encoder.EncodeSegmentCount(
+ userpass.get(), URLSegment(usernamePos, usernameLen),
+ esc_Username | esc_AlwaysCopy, buf, ignoredOut);
+ if (passwordLen > 0) {
+ buf.Append(':');
+ passwordLen = encoder.EncodeSegmentCount(
+ userpass.get(), URLSegment(passwordPos, passwordLen),
+ esc_Password | esc_AlwaysCopy, buf, ignoredOut);
+ } else {
+ passwordLen = -1;
+ }
+ if (mUsername.mLen < 0 && mPassword.mLen < 0) {
+ buf.Append('@');
+ }
+ }
+
+ int32_t shift = 0;
+
+ if (mUsername.mLen < 0 && mPassword.mLen < 0) {
+ // no existing user:pass
+ if (!buf.IsEmpty()) {
+ mSpec.Insert(buf, mHost.mPos);
+ mUsername.mPos = mHost.mPos;
+ shift = buf.Length();
+ }
+ } else {
+ // replace existing user:pass
+ uint32_t userpassLen = 0;
+ if (mUsername.mLen > 0) {
+ userpassLen += mUsername.mLen;
+ }
+ if (mPassword.mLen > 0) {
+ userpassLen += (mPassword.mLen + 1);
+ }
+ if (buf.IsEmpty()) {
+ // remove `@` character too
+ userpassLen++;
+ }
+ mSpec.Replace(mAuthority.mPos, userpassLen, buf);
+ shift = buf.Length() - userpassLen;
+ }
+ if (shift) {
+ ShiftFromHost(shift);
+ MOZ_DIAGNOSTIC_ASSERT(mAuthority.mLen >= -shift);
+ mAuthority.mLen += shift;
+ }
+ // update positions and lengths
+ mUsername.mLen = usernameLen > 0 ? usernameLen : -1;
+ mUsername.mPos = mAuthority.mPos;
+ mPassword.mLen = passwordLen > 0 ? passwordLen : -1;
+ if (passwordLen > 0) {
+ if (mUsername.mLen > 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ } else {
+ mPassword.mPos = mAuthority.mPos + 1;
+ }
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetUsername(const nsACString& input) {
+ const nsPromiseFlatCString& username = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (username.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set username on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Username().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ // escape username if necessary
+ nsAutoCString buf;
+ nsSegmentEncoder encoder;
+ const nsACString& escUsername =
+ encoder.EncodeSegment(username, esc_Username, buf);
+
+ int32_t shift = 0;
+
+ if (mUsername.mLen < 0 && escUsername.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (mUsername.mLen < 0 && mPassword.mLen < 0) {
+ MOZ_ASSERT(!escUsername.IsEmpty(), "Should not be empty at this point");
+ mUsername.mPos = mAuthority.mPos;
+ mSpec.Insert(escUsername + "@"_ns, mUsername.mPos);
+ shift = escUsername.Length() + 1;
+ mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
+ } else {
+ uint32_t pos = mUsername.mLen < 0 ? mAuthority.mPos : mUsername.mPos;
+ int32_t len = mUsername.mLen < 0 ? 0 : mUsername.mLen;
+
+ if (mPassword.mLen < 0 && escUsername.IsEmpty()) {
+ len++; // remove the @ character too
+ }
+ shift = ReplaceSegment(pos, len, escUsername);
+ mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
+ mUsername.mPos = pos;
+ }
+
+ if (shift) {
+ mAuthority.mLen += shift;
+ ShiftFromPassword(shift);
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetPassword(const nsACString& input) {
+ const nsPromiseFlatCString& password = PromiseFlatCString(input);
+
+ auto clearedPassword = MakeScopeExit([&password, this]() {
+ // Check that if this method is called with the empty string then the
+ // password is definitely cleared when exiting this method.
+ if (password.IsEmpty()) {
+ MOZ_DIAGNOSTIC_ASSERT(this->Password().IsEmpty());
+ }
+ Unused << this; // silence compiler -Wunused-lambda-capture
+ });
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ LOG(("nsStandardURL::SetPassword [password=%s]\n", password.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (password.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set password on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Password().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (password.IsEmpty()) {
+ if (mPassword.mLen > 0) {
+ // cut(":password")
+ int32_t len = mPassword.mLen;
+ if (mUsername.mLen < 0) {
+ len++; // also cut the @ character
+ }
+ len++; // for the : character
+ mSpec.Cut(mPassword.mPos - 1, len);
+ ShiftFromHost(-len);
+ mAuthority.mLen -= len;
+ mPassword.mLen = -1;
+ }
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+ }
+
+ // escape password if necessary
+ nsAutoCString buf;
+ nsSegmentEncoder encoder;
+ const nsACString& escPassword =
+ encoder.EncodeSegment(password, esc_Password, buf);
+
+ int32_t shift;
+
+ if (mPassword.mLen < 0) {
+ if (mUsername.mLen > 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ mSpec.Insert(":"_ns + escPassword, mPassword.mPos - 1);
+ shift = escPassword.Length() + 1;
+ } else {
+ mPassword.mPos = mAuthority.mPos + 1;
+ mSpec.Insert(":"_ns + escPassword + "@"_ns, mPassword.mPos - 1);
+ shift = escPassword.Length() + 2;
+ }
+ } else {
+ shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);
+ }
+
+ if (shift) {
+ mPassword.mLen = escPassword.Length();
+ mAuthority.mLen += shift;
+ ShiftFromHost(shift);
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+void nsStandardURL::FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd) {
+ for (int32_t i = 0; gHostLimitDigits[i]; ++i) {
+ nsACString::const_iterator c(aStart);
+ if (FindCharInReadable(gHostLimitDigits[i], c, aEnd)) {
+ aEnd = c;
+ }
+ }
+}
+
+// If aValue only has a host part and no port number, the port
+// will not be reset!!!
+nsresult nsStandardURL::SetHostPort(const nsACString& aValue) {
+ // We cannot simply call nsIURI::SetHost because that would treat the name as
+ // an IPv6 address (like http:://[server:443]/). We also cannot call
+ // nsIURI::SetHostPort because that isn't implemented. Sadfaces.
+
+ nsACString::const_iterator start, end;
+ aValue.BeginReading(start);
+ aValue.EndReading(end);
+ nsACString::const_iterator iter(start);
+ bool isIPv6 = false;
+
+ FindHostLimit(start, end);
+
+ if (*start == '[') { // IPv6 address
+ if (!FindCharInReadable(']', iter, end)) {
+ // the ] character is missing
+ return NS_ERROR_MALFORMED_URI;
+ }
+ // iter now at the ']' character
+ isIPv6 = true;
+ } else {
+ nsACString::const_iterator iter2(start);
+ if (FindCharInReadable(']', iter2, end)) {
+ // if the first char isn't [ then there should be no ] character
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ FindCharInReadable(':', iter, end);
+
+ if (!isIPv6 && iter != end) {
+ nsACString::const_iterator iter2(iter);
+ iter2++; // Skip over the first ':' character
+ if (FindCharInReadable(':', iter2, end)) {
+ // If there is more than one ':' character it suggests an IPv6
+ // The format should be [2001::1]:80 where the port is optional
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ nsresult rv = SetHost(Substring(start, iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (iter == end) {
+ // does not end in colon
+ return NS_OK;
+ }
+
+ iter++; // advance over the colon
+ if (iter == end) {
+ // port number is missing
+ return NS_OK;
+ }
+
+ nsCString portStr(Substring(iter, end));
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ // Failure parsing the port number
+ return NS_OK;
+ }
+
+ Unused << SetPort(port);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetHost(const nsACString& input) {
+ nsAutoCString hostname(input);
+ hostname.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ nsACString::const_iterator start, end;
+ hostname.BeginReading(start);
+ hostname.EndReading(end);
+
+ FindHostLimit(start, end);
+
+ // Do percent decoding on the the input.
+ nsAutoCString flat;
+ NS_UnescapeURL(hostname.BeginReading(), end - start,
+ esc_AlwaysCopy | esc_Host, flat);
+ const char* host = flat.get();
+
+ LOG(("nsStandardURL::SetHost [host=%s]\n", host));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (flat.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set host on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (flat.IsEmpty()) {
+ // Setting an empty hostname is not allowed for
+ // URLTYPE_STANDARD and URLTYPE_AUTHORITY.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (strlen(host) < flat.Length()) {
+ return NS_ERROR_MALFORMED_URI; // found embedded null
+ }
+
+ // For consistency with SetSpec/nsURLParsers, don't allow spaces
+ // in the hostname.
+ if (strchr(host, ' ')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mSpec.Length() + strlen(host) - Host().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ InvalidateCache();
+
+ uint32_t len;
+ nsAutoCString hostBuf;
+ nsresult rv = NormalizeIDN(flat, hostBuf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!SegmentIs(mScheme, "resource") && !SegmentIs(mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (hostBuf.Length() > 0 && hostBuf.First() == '[' &&
+ hostBuf.Last() == ']' &&
+ ValidIPv6orHostname(hostBuf.get(), hostBuf.Length())) {
+ rv = (nsresult)rusturl_parse_ipv6addr(&hostBuf, &ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ hostBuf = ipString;
+ } else {
+ if (EndsInANumber(hostBuf)) {
+ rv = NormalizeIPv4(hostBuf, ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ hostBuf = ipString;
+ }
+ }
+ }
+
+ // NormalizeIDN always copies if the call was successful
+ host = hostBuf.get();
+ len = hostBuf.Length();
+
+ if (!ValidIPv6orHostname(host, len)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mHost.mLen < 0) {
+ int port_length = 0;
+ if (mPort != -1) {
+ nsAutoCString buf;
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ port_length = buf.Length();
+ }
+ if (mAuthority.mLen > 0) {
+ mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
+ mHost.mLen = 0;
+ } else if (mScheme.mLen > 0) {
+ mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
+ mHost.mLen = 0;
+ }
+ }
+
+ int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);
+
+ if (shift) {
+ mHost.mLen = len;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+ }
+
+ // Now canonicalize the host to lowercase
+ net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetPort(int32_t port) {
+ LOG(("nsStandardURL::SetPort [port=%d]\n", port));
+
+ if ((port == mPort) || (mPort == -1 && port == mDefaultPort)) {
+ return NS_OK;
+ }
+
+ // ports must be >= 0 and 16 bit
+ // -1 == use default
+ if (port < -1 || port > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ NS_WARNING("cannot set port on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+ if (port == mDefaultPort) {
+ port = -1;
+ }
+
+ ReplacePortInSpec(port);
+
+ mPort = port;
+ return NS_OK;
+}
+
+/**
+ * Replaces the existing port in mSpec with aNewPort.
+ *
+ * The caller is responsible for:
+ * - Calling InvalidateCache (since our mSpec is changing).
+ * - Checking whether aNewPort is mDefaultPort (in which case the
+ * caller should pass aNewPort=-1).
+ */
+void nsStandardURL::ReplacePortInSpec(int32_t aNewPort) {
+ NS_ASSERTION(aNewPort != mDefaultPort || mDefaultPort == -1,
+ "Caller should check its passed-in value and pass -1 instead of "
+ "mDefaultPort, to avoid encoding default port into mSpec");
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ // Create the (possibly empty) string that we're planning to replace:
+ nsAutoCString buf;
+ if (mPort != -1) {
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ }
+ // Find the position & length of that string:
+ const uint32_t replacedLen = buf.Length();
+ const uint32_t replacedStart =
+ mAuthority.mPos + mAuthority.mLen - replacedLen;
+
+ // Create the (possibly empty) replacement string:
+ if (aNewPort == -1) {
+ buf.Truncate();
+ } else {
+ buf.Assign(':');
+ buf.AppendInt(aNewPort);
+ }
+ // Perform the replacement:
+ mSpec.Replace(replacedStart, replacedLen, buf);
+
+ // Bookkeeping to reflect the new length:
+ int32_t shift = buf.Length() - replacedLen;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+}
+
+nsresult nsStandardURL::SetPathQueryRef(const nsACString& input) {
+ const nsPromiseFlatCString& path = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetPathQueryRef [path=%s]\n", path.get()));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ if (!path.IsEmpty()) {
+ nsAutoCString spec;
+
+ spec.Assign(mSpec.get(), mPath.mPos);
+ if (path.First() != '/') {
+ spec.Append('/');
+ }
+ spec.Append(path);
+
+ return SetSpecInternal(spec);
+ }
+ if (mPath.mLen >= 1) {
+ mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1);
+ // these contain only a '/'
+ mPath.mLen = 1;
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ mQuery.mLen = -1;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+}
+
+// When updating this also update SubstitutingURL::Mutator
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsStandardURL::Mutator, nsIURISetters,
+ nsIURIMutator, nsIStandardURLMutator,
+ nsIURLMutator, nsIFileURLMutator,
+ nsISerializable)
+
+NS_IMETHODIMP
+nsStandardURL::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsStandardURL::Mutator> mutator = new nsStandardURL::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Equals(nsIURI* unknownOther, bool* result) {
+ return EqualsInternal(unknownOther, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::EqualsExceptRef(nsIURI* unknownOther, bool* result) {
+ return EqualsInternal(unknownOther, eIgnoreRef, result);
+}
+
+nsresult nsStandardURL::EqualsInternal(
+ nsIURI* unknownOther, nsStandardURL::RefHandlingEnum refHandlingMode,
+ bool* result) {
+ NS_ENSURE_ARG_POINTER(unknownOther);
+ MOZ_ASSERT(result, "null pointer");
+
+ RefPtr<nsStandardURL> other;
+ nsresult rv =
+ unknownOther->QueryInterface(kThisImplCID, getter_AddRefs(other));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // First, check whether one URIs is an nsIFileURL while the other
+ // is not. If that's the case, they're different.
+ if (mSupportsFileURL != other->mSupportsFileURL) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Next check parts of a URI that, if different, automatically make the
+ // URIs different
+ if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
+ // Check for host manually, since conversion to file will
+ // ignore the host!
+ !SegmentIs(mHost, other->mSpec.get(), other->mHost) ||
+ !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) ||
+ !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) ||
+ !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) ||
+ Port() != other->Port()) {
+ // No need to compare files or other URI parts -- these are different
+ // beasties
+ *result = false;
+ return NS_OK;
+ }
+
+ if (refHandlingMode == eHonorRef &&
+ !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Then check for exact identity of URIs. If we have it, they're equal
+ if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) &&
+ SegmentIs(mBasename, other->mSpec.get(), other->mBasename) &&
+ SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) {
+ *result = true;
+ return NS_OK;
+ }
+
+ // At this point, the URIs are not identical, but they only differ in the
+ // directory/filename/extension. If these are file URLs, then get the
+ // corresponding file objects and compare those, since two filenames that
+ // differ, eg, only in case could still be equal.
+ if (mSupportsFileURL) {
+ // Assume not equal for failure cases... but failures in GetFile are
+ // really failures, more or less, so propagate them to caller.
+ *result = false;
+
+ rv = EnsureFile();
+ nsresult rv2 = other->EnsureFile();
+ // special case for resource:// urls that don't resolve to files
+ if (rv == NS_ERROR_NO_INTERFACE && rv == rv2) {
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
+ this, mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(mFile, "EnsureFile() lied!");
+ rv = rv2;
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure "
+ "file",
+ other.get(), other->mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(other->mFile, "EnsureFile() lied!");
+ return mFile->Equals(other->mFile, result);
+ }
+
+ // The URLs are not identical, and they do not correspond to the
+ // same file, so they are different.
+ *result = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SchemeIs(const char* scheme, bool* result) {
+ MOZ_ASSERT(result, "null pointer");
+ if (!scheme) {
+ *result = false;
+ return NS_OK;
+ }
+
+ *result = SegmentIs(mScheme, scheme);
+ return NS_OK;
+}
+
+/* virtual */ nsStandardURL* nsStandardURL::StartClone() {
+ nsStandardURL* clone = new nsStandardURL();
+ return clone;
+}
+
+nsresult nsStandardURL::Clone(nsIURI** aURI) {
+ return CloneInternal(eHonorRef, ""_ns, aURI);
+}
+
+nsresult nsStandardURL::CloneInternal(
+ nsStandardURL::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef,
+ nsIURI** aClone)
+
+{
+ RefPtr<nsStandardURL> clone = StartClone();
+ if (!clone) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Copy local members into clone.
+ // Also copies the cached members mFile, mDisplayHost
+ clone->CopyMembers(this, aRefHandlingMode, aNewRef, true);
+
+ clone.forget(aClone);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::CopyMembers(
+ nsStandardURL* source, nsStandardURL::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef, bool copyCached) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ mSpec = source->mSpec;
+ mDefaultPort = source->mDefaultPort;
+ mPort = source->mPort;
+ mScheme = source->mScheme;
+ mAuthority = source->mAuthority;
+ mUsername = source->mUsername;
+ mPassword = source->mPassword;
+ mHost = source->mHost;
+ mPath = source->mPath;
+ mFilepath = source->mFilepath;
+ mDirectory = source->mDirectory;
+ mBasename = source->mBasename;
+ mExtension = source->mExtension;
+ mQuery = source->mQuery;
+ mRef = source->mRef;
+ mURLType = source->mURLType;
+ mParser = source->mParser;
+ mSupportsFileURL = source->mSupportsFileURL;
+ mCheckedIfHostA = source->mCheckedIfHostA;
+ mDisplayHost = source->mDisplayHost;
+
+ if (copyCached) {
+ mFile = source->mFile;
+ } else {
+ InvalidateCache(true);
+ }
+
+ if (refHandlingMode == eIgnoreRef) {
+ SetRef(""_ns);
+ } else if (refHandlingMode == eReplaceRef) {
+ SetRef(newRef);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Resolve(const nsACString& in, nsACString& out) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(in);
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString buf;
+ net_FilterURIString(flat, buf);
+
+ const char* relpath = buf.get();
+ int32_t relpathLen = buf.Length();
+
+ char* result = nullptr;
+
+ LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n", this,
+ mSpec.get(), relpath));
+
+ NS_ASSERTION(mParser, "no parser: unitialized");
+
+ // NOTE: there is no need for this function to produce normalized
+ // output. normalization will occur when the result is used to
+ // initialize a nsStandardURL object.
+
+ if (mScheme.mLen < 0) {
+ NS_WARNING("unable to Resolve URL: this URL not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+ URLSegment scheme;
+ char* resultPath = nullptr;
+ bool relative = false;
+ uint32_t offset = 0;
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+
+ nsAutoCString baseProtocol(Scheme());
+ nsAutoCString protocol;
+ rv = net_ExtractURLScheme(buf, protocol);
+
+ // Normally, if we parse a scheme, then it's an absolute URI. But because
+ // we still support a deprecated form of relative URIs such as: http:file or
+ // http:/path/file we can't do that for all protocols.
+ // So we just make sure that if there a protocol, it's the same as the
+ // current one, otherwise we treat it as an absolute URI.
+ if (NS_SUCCEEDED(rv) && protocol != baseProtocol) {
+ out = buf;
+ return NS_OK;
+ }
+
+ // relative urls should never contain a host, so we always want to use
+ // the noauth url parser.
+ // use it to extract a possible scheme
+ uint32_t schemePos = scheme.mPos;
+ int32_t schemeLen = scheme.mLen;
+ rv = mParser->ParseURL(relpath, relpathLen, &schemePos, &schemeLen, nullptr,
+ nullptr, nullptr, nullptr);
+
+ // if the parser fails (for example because there is no valid scheme)
+ // reset the scheme and assume a relative url
+ if (NS_FAILED(rv)) {
+ scheme.Reset();
+ }
+
+ scheme.mPos = schemePos;
+ scheme.mLen = schemeLen;
+
+ protocol.Assign(Segment(scheme));
+
+ // We need to do backslash replacement for the following cases:
+ // 1. The input is an absolute path with a http/https/ftp scheme
+ // 2. The input is a relative path, and the base URL has a http/https/ftp
+ // scheme
+ if ((protocol.IsEmpty() && IsSpecialProtocol(baseProtocol)) ||
+ IsSpecialProtocol(protocol)) {
+ auto* start = buf.BeginWriting();
+ auto* end = buf.EndWriting();
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ if (scheme.mLen >= 0) {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(relpath, scheme, "ftp", true)) {
+ coalesceFlag =
+ (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ // this URL appears to be absolute
+ // but try to find out more
+ if (SegmentIs(mScheme, relpath, scheme, true)) {
+ // mScheme and Scheme are the same
+ // but this can still be relative
+ if (strncmp(relpath + scheme.mPos + scheme.mLen, "://", 3) == 0) {
+ // now this is really absolute
+ // because a :// follows the scheme
+ result = NS_xstrdup(relpath);
+ } else {
+ // This is a deprecated form of relative urls like
+ // http:file or http:/path/file
+ // we will support it for now ...
+ relative = true;
+ offset = scheme.mLen + 1;
+ }
+ } else {
+ // the schemes are not the same, we are also done
+ // because we have to assume this is absolute
+ result = NS_xstrdup(relpath);
+ }
+ } else {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(mScheme, "ftp")) {
+ coalesceFlag =
+ (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ if (relpath[0] == '/' && relpath[1] == '/') {
+ // this URL //host/path is almost absolute
+ result = AppendToSubstring(mScheme.mPos, mScheme.mLen + 1, relpath);
+ } else {
+ // then it must be relative
+ relative = true;
+ }
+ }
+ if (relative) {
+ uint32_t len = 0;
+ const char* realrelpath = relpath + offset;
+ switch (*realrelpath) {
+ case '/':
+ // overwrite everything after the authority
+ len = mAuthority.mPos + mAuthority.mLen;
+ break;
+ case '?':
+ // overwrite the existing ?query and #ref
+ if (mQuery.mLen >= 0) {
+ len = mQuery.mPos - 1;
+ } else if (mRef.mLen >= 0) {
+ len = mRef.mPos - 1;
+ } else {
+ len = mPath.mPos + mPath.mLen;
+ }
+ break;
+ case '#':
+ case '\0':
+ // overwrite the existing #ref
+ if (mRef.mLen < 0) {
+ len = mPath.mPos + mPath.mLen;
+ } else {
+ len = mRef.mPos - 1;
+ }
+ break;
+ default:
+ if (coalesceFlag & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
+ if (Filename().Equals("%2F"_ns, nsCaseInsensitiveCStringComparator)) {
+ // if ftp URL ends with %2F then simply
+ // append relative part because %2F also
+ // marks the root directory with ftp-urls
+ len = mFilepath.mPos + mFilepath.mLen;
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ }
+ result = AppendToSubstring(0, len, realrelpath);
+ // locate result path
+ resultPath = result + mPath.mPos;
+ }
+ if (!result) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (resultPath) {
+ net_CoalesceDirs(coalesceFlag, resultPath);
+ } else {
+ // locate result path
+ resultPath = strstr(result, "://");
+ if (resultPath) {
+ // If there are multiple slashes after :// we must ignore them
+ // otherwise net_CoalesceDirs may think the host is a part of the path.
+ resultPath += 3;
+ if (protocol.IsEmpty() && Scheme() != "file") {
+ while (*resultPath == '/') {
+ resultPath++;
+ }
+ }
+ resultPath = strchr(resultPath, '/');
+ if (resultPath) {
+ net_CoalesceDirs(coalesceFlag, resultPath);
+ }
+ }
+ }
+ out.Adopt(result);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetCommonBaseSpec(nsIURI* uri2, nsACString& aResult) {
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ // if uri's are equal, then return uri as is
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
+ return GetSpec(aResult);
+ }
+
+ aResult.Truncate();
+
+ // check pre-path; if they don't match, then return empty string
+ RefPtr<nsStandardURL> stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2));
+ isEquals = NS_SUCCEEDED(rv) &&
+ SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) &&
+ SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) &&
+ SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) &&
+ SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) &&
+ (Port() == stdurl2->Port());
+ if (!isEquals) {
+ return NS_OK;
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+ while ((*thisIndex == *thatIndex) && *thisIndex) {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches which include '?' and '#'
+ while ((thisIndex != startCharPos) && (*(thisIndex - 1) != '/')) {
+ thisIndex--;
+ }
+
+ // grab spec from beginning to thisIndex
+ aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get());
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetRelativeSpec(nsIURI* uri2, nsACString& aResult) {
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ aResult.Truncate();
+
+ // if uri's are equal, then return empty string
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
+ return NS_OK;
+ }
+
+ RefPtr<nsStandardURL> stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2));
+ isEquals = NS_SUCCEEDED(rv) &&
+ SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) &&
+ SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) &&
+ SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) &&
+ SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) &&
+ (Port() == stdurl2->Port());
+ if (!isEquals) {
+ return uri2->GetSpec(aResult);
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+
+#ifdef XP_WIN
+ bool isFileScheme = SegmentIs(mScheme, "file");
+ if (isFileScheme) {
+ // on windows, we need to match the first segment of the path
+ // if these don't match then we need to return an absolute path
+ // skip over any leading '/' in path
+ while ((*thisIndex == *thatIndex) && (*thisIndex == '/')) {
+ thisIndex++;
+ thatIndex++;
+ }
+ // look for end of first segment
+ while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/')) {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // if we didn't match through the first segment, return absolute path
+ if ((*thisIndex != '/') || (*thatIndex != '/')) {
+ return uri2->GetSpec(aResult);
+ }
+ }
+#endif
+
+ while ((*thisIndex == *thatIndex) && *thisIndex) {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches with '#' and '?'
+ while ((*(thatIndex - 1) != '/') && (thatIndex != startCharPos)) {
+ thatIndex--;
+ }
+
+ const char* limit = mSpec.get() + mFilepath.mPos + mFilepath.mLen;
+
+ // need to account for slashes and add corresponding "../"
+ for (; thisIndex <= limit && *thisIndex; ++thisIndex) {
+ if (*thisIndex == '/') {
+ aResult.AppendLiteral("../");
+ }
+ }
+
+ // grab spec from thisIndex to end
+ uint32_t startPos = stdurl2->mScheme.mPos + thatIndex - stdurl2->mSpec.get();
+ aResult.Append(
+ Substring(stdurl2->mSpec, startPos, stdurl2->mSpec.Length() - startPos));
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURL
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFilePath(nsACString& result) {
+ result = Filepath();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetQuery(nsACString& result) {
+ result = Query();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasQuery(bool* result) {
+ *result = (mQuery.mLen >= 0);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetRef(nsACString& result) {
+ result = Ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasRef(bool* result) {
+ *result = (mRef.mLen >= 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasUserPass(bool* result) {
+ *result = (mUsername.mLen >= 0) || (mPassword.mLen >= 0);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDirectory(nsACString& result) {
+ result = Directory();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileName(nsACString& result) {
+ result = Filename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileBaseName(nsACString& result) {
+ result = Basename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileExtension(nsACString& result) {
+ result = Extension();
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetFilePath(const nsACString& input) {
+ nsAutoCString str(input);
+ str.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+ const char* filepath = str.get();
+
+ LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ // if there isn't a filepath, then there can't be anything
+ // after the path either. this url is likely uninitialized.
+ if (mFilepath.mLen < 0) {
+ return SetPathQueryRef(str);
+ }
+
+ if (!str.IsEmpty()) {
+ nsAutoCString spec;
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+ nsresult rv;
+
+ if (IsSpecialProtocol(mSpec)) {
+ // Bug 1873955: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ auto* start = str.BeginWriting();
+ auto* end = str.EndWriting();
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ rv = mParser->ParseFilePath(filepath, str.Length(), &dirPos, &dirLen,
+ &basePos, &baseLen, &extPos, &extLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // build up new candidate spec
+ spec.Assign(mSpec.get(), mPath.mPos);
+
+ // ensure leading '/'
+ if (filepath[dirPos] != '/') {
+ spec.Append('/');
+ }
+
+ nsSegmentEncoder encoder;
+
+ // append encoded filepath components
+ if (dirLen > 0) {
+ encoder.EncodeSegment(
+ Substring(filepath + dirPos, filepath + dirPos + dirLen),
+ esc_Directory | esc_AlwaysCopy, spec);
+ }
+ if (baseLen > 0) {
+ encoder.EncodeSegment(
+ Substring(filepath + basePos, filepath + basePos + baseLen),
+ esc_FileBaseName | esc_AlwaysCopy, spec);
+ }
+ if (extLen >= 0) {
+ spec.Append('.');
+ if (extLen > 0) {
+ encoder.EncodeSegment(
+ Substring(filepath + extPos, filepath + extPos + extLen),
+ esc_FileExtension | esc_AlwaysCopy, spec);
+ }
+ }
+
+ // compute the ending position of the current filepath
+ if (mFilepath.mLen >= 0) {
+ uint32_t end = mFilepath.mPos + mFilepath.mLen;
+ if (mSpec.Length() > end) {
+ spec.Append(mSpec.get() + end, mSpec.Length() - end);
+ }
+ }
+
+ return SetSpecInternal(spec);
+ }
+ if (mPath.mLen > 1) {
+ mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
+ // left shift query, and ref
+ ShiftFromQuery(1 - mFilepath.mLen);
+ // One character for '/', and if we have a query or ref we add their
+ // length and one extra for each '?' or '#' characters
+ mPath.mLen = 1 + (mQuery.mLen >= 0 ? (mQuery.mLen + 1) : 0) +
+ (mRef.mLen >= 0 ? (mRef.mLen + 1) : 0);
+ // these contain only a '/'
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ }
+ return NS_OK;
+}
+
+inline bool IsUTFEncoding(const Encoding* aEncoding) {
+ return aEncoding == UTF_8_ENCODING || aEncoding == UTF_16BE_ENCODING ||
+ aEncoding == UTF_16LE_ENCODING;
+}
+
+nsresult nsStandardURL::SetQuery(const nsACString& input) {
+ return SetQueryWithEncoding(input, nullptr);
+}
+
+nsresult nsStandardURL::SetQueryWithEncoding(const nsACString& input,
+ const Encoding* encoding) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* query = flat.get();
+
+ LOG(("nsStandardURL::SetQuery [query=%s]\n", query));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (IsUTFEncoding(encoding)) {
+ encoding = nullptr;
+ }
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Query().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (flat.IsEmpty()) {
+ // remove existing query
+ if (mQuery.mLen >= 0) {
+ // remove query and leading '?'
+ mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1);
+ ShiftFromRef(-(mQuery.mLen + 1));
+ mPath.mLen -= (mQuery.mLen + 1);
+ mQuery.mPos = 0;
+ mQuery.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ query = filteredURI.get();
+ int32_t queryLen = filteredURI.Length();
+ if (query[0] == '?') {
+ query++;
+ queryLen--;
+ }
+
+ if (mQuery.mLen < 0) {
+ if (mRef.mLen < 0) {
+ mQuery.mPos = mSpec.Length();
+ } else {
+ mQuery.mPos = mRef.mPos - 1;
+ }
+ mSpec.Insert('?', mQuery.mPos);
+ mQuery.mPos++;
+ mQuery.mLen = 0;
+ // the insertion pushes these out by 1
+ mPath.mLen++;
+ mRef.mPos++;
+ }
+
+ // encode query if necessary
+ nsAutoCString buf;
+ bool encoded;
+ nsSegmentEncoder encoder(encoding);
+ encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query, buf,
+ encoded);
+ if (encoded) {
+ query = buf.get();
+ queryLen = buf.Length();
+ }
+
+ int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen);
+
+ if (shift) {
+ mQuery.mLen = queryLen;
+ mPath.mLen += shift;
+ ShiftFromRef(shift);
+ }
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetRef(const nsACString& input) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* ref = flat.get();
+
+ LOG(("nsStandardURL::SetRef [ref=%s]\n", ref));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Ref().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (input.IsEmpty()) {
+ // remove existing ref
+ if (mRef.mLen >= 0) {
+ // remove ref and leading '#'
+ mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1);
+ mPath.mLen -= (mRef.mLen + 1);
+ mRef.mPos = 0;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ ref = filteredURI.get();
+ int32_t refLen = filteredURI.Length();
+ if (ref[0] == '#') {
+ ref++;
+ refLen--;
+ }
+
+ if (mRef.mLen < 0) {
+ mSpec.Append('#');
+ ++mPath.mLen; // Include the # in the path.
+ mRef.mPos = mSpec.Length();
+ mRef.mLen = 0;
+ }
+
+ // If precent encoding is necessary, `ref` will point to `buf`'s content.
+ // `buf` needs to outlive any use of the `ref` pointer.
+ nsAutoCString buf;
+ // encode ref if necessary
+ bool encoded;
+ nsSegmentEncoder encoder;
+ encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref, buf, encoded);
+ if (encoded) {
+ ref = buf.get();
+ refLen = buf.Length();
+ }
+
+ int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen);
+ mPath.mLen += shift;
+ mRef.mLen = refLen;
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetFileNameInternal(const nsACString& input) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* filename = flat.get();
+
+ LOG(("nsStandardURL::SetFileNameInternal [filename=%s]\n", filename));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Filename().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ int32_t shift = 0;
+
+ if (!(filename && *filename)) {
+ // remove the filename
+ if (mBasename.mLen > 0) {
+ if (mExtension.mLen >= 0) {
+ mBasename.mLen += (mExtension.mLen + 1);
+ }
+ mSpec.Cut(mBasename.mPos, mBasename.mLen);
+ shift = -mBasename.mLen;
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ } else {
+ nsresult rv;
+ uint32_t basenamePos = 0;
+ int32_t basenameLen = -1;
+ uint32_t extensionPos = 0;
+ int32_t extensionLen = -1;
+ // let the parser locate the basename and extension
+ rv = mParser->ParseFileName(filename, flat.Length(), &basenamePos,
+ &basenameLen, &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ URLSegment basename(basenamePos, basenameLen);
+ URLSegment extension(extensionPos, extensionLen);
+
+ if (basename.mLen < 0) {
+ // remove existing filename
+ if (mBasename.mLen >= 0) {
+ uint32_t len = mBasename.mLen;
+ if (mExtension.mLen >= 0) {
+ len += (mExtension.mLen + 1);
+ }
+ mSpec.Cut(mBasename.mPos, len);
+ shift = -int32_t(len);
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ } else {
+ nsAutoCString newFilename;
+ bool ignoredOut;
+ nsSegmentEncoder encoder;
+ basename.mLen = encoder.EncodeSegmentCount(
+ filename, basename, esc_FileBaseName | esc_AlwaysCopy, newFilename,
+ ignoredOut);
+ if (extension.mLen >= 0) {
+ newFilename.Append('.');
+ extension.mLen = encoder.EncodeSegmentCount(
+ filename, extension, esc_FileExtension | esc_AlwaysCopy,
+ newFilename, ignoredOut);
+ }
+
+ if (mBasename.mLen < 0) {
+ // insert new filename
+ mBasename.mPos = mDirectory.mPos + mDirectory.mLen;
+ mSpec.Insert(newFilename, mBasename.mPos);
+ shift = newFilename.Length();
+ } else {
+ // replace existing filename
+ uint32_t oldLen = uint32_t(mBasename.mLen);
+ if (mExtension.mLen >= 0) {
+ oldLen += (mExtension.mLen + 1);
+ }
+ mSpec.Replace(mBasename.mPos, oldLen, newFilename);
+ shift = newFilename.Length() - oldLen;
+ }
+
+ mBasename.mLen = basename.mLen;
+ mExtension.mLen = extension.mLen;
+ if (mExtension.mLen >= 0) {
+ mExtension.mPos = mBasename.mPos + mBasename.mLen + 1;
+ }
+ }
+ }
+ if (shift) {
+ ShiftFromQuery(shift);
+ mFilepath.mLen += shift;
+ mPath.mLen += shift;
+ }
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetFileBaseNameInternal(const nsACString& input) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ nsAutoCString extension;
+ nsresult rv = GetFileExtension(extension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newFileName(input);
+
+ if (!extension.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(extension);
+ }
+
+ return SetFileNameInternal(newFileName);
+}
+
+nsresult nsStandardURL::SetFileExtensionInternal(const nsACString& input) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ nsAutoCString newFileName;
+ nsresult rv = GetFileBaseName(newFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!input.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(input);
+ }
+
+ return SetFileNameInternal(newFileName);
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIFileURL
+//----------------------------------------------------------------------------
+
+nsresult nsStandardURL::EnsureFile() {
+ MOZ_ASSERT(mSupportsFileURL,
+ "EnsureFile() called on a URL that doesn't support files!");
+
+ if (mFile) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ // Parse the spec if we don't have a cached result
+ if (mSpec.IsEmpty()) {
+ NS_WARNING("url not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!SegmentIs(mScheme, "file")) {
+ NS_WARNING("not a file URL");
+ return NS_ERROR_FAILURE;
+ }
+
+ return net_GetFileFromURLSpec(mSpec, getter_AddRefs(mFile));
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetFile(nsIFile** result) {
+ MOZ_ASSERT(mSupportsFileURL,
+ "GetFile() called on a URL that doesn't support files!");
+
+ nsresult rv = EnsureFile();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n", this,
+ mSpec.get(), mFile->HumanReadablePath().get()));
+ }
+
+ // clone the file, so the caller can modify it.
+ // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the
+ // nsIFile returned from this method; but it seems that some folks do
+ // (see bug 161921). until we can be sure that all the consumers are
+ // behaving themselves, we'll stay on the safe side and clone the file.
+ // see bug 212724 about fixing the consumers.
+ return mFile->Clone(result);
+}
+
+nsresult nsStandardURL::SetFile(nsIFile* file) {
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsresult rv;
+ nsAutoCString url;
+
+ rv = net_GetURLSpecFromFile(file, url);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t oldURLType = mURLType;
+ uint32_t oldDefaultPort = mDefaultPort;
+ rv = Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, url, nullptr, nullptr);
+
+ if (NS_FAILED(rv)) {
+ // Restore the old url type and default port if the call to Init fails.
+ mURLType = oldURLType;
+ mDefaultPort = oldDefaultPort;
+ return rv;
+ }
+
+ // must clone |file| since its value is not guaranteed to remain constant
+ InvalidateCache();
+ if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
+ NS_WARNING("nsIFile::Clone failed");
+ // failure to clone is not fatal (GetFile will generate mFile)
+ mFile = nullptr;
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIStandardURL
+//----------------------------------------------------------------------------
+
+nsresult nsStandardURL::Init(uint32_t urlType, int32_t defaultPort,
+ const nsACString& spec, const char* charset,
+ nsIURI* baseURI) {
+ if (spec.Length() > StaticPrefs::network_standard_url_max_length() ||
+ defaultPort > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ switch (urlType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return NS_ERROR_INVALID_ARG;
+ }
+ mDefaultPort = defaultPort;
+ mURLType = urlType;
+
+ const auto* encoding =
+ charset ? Encoding::ForLabelNoReplacement(MakeStringSpan(charset))
+ : nullptr;
+ // URI can't be encoded in UTF-16BE or UTF-16LE. Truncate encoding
+ // if it is one of utf encodings (since a null encoding implies
+ // UTF-8, this is safe even if encoding is UTF-8).
+ if (IsUTFEncoding(encoding)) {
+ encoding = nullptr;
+ }
+
+ if (baseURI && net_IsAbsoluteURL(spec)) {
+ baseURI = nullptr;
+ }
+
+ if (!baseURI) {
+ return SetSpecWithEncoding(spec, encoding);
+ }
+
+ nsAutoCString buf;
+ nsresult rv = baseURI->Resolve(spec, buf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return SetSpecWithEncoding(buf, encoding);
+}
+
+nsresult nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort) {
+ InvalidateCache();
+
+ // should never be more than 16 bit
+ if (aNewDefaultPort >= std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // If we're already using the new default-port as a custom port, then clear
+ // it off of our mSpec & set mPort to -1, to indicate that we'll be using
+ // the default from now on (which happens to match what we already had).
+ if (mPort == aNewDefaultPort) {
+ ReplacePortInSpec(-1);
+ mPort = -1;
+ }
+ mDefaultPort = aNewDefaultPort;
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISerializable
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsStandardURL::Read(nsIObjectInputStream* stream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsStandardURL::ReadPrivate(nsIObjectInputStream* stream) {
+ MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
+
+ // If we exit early, make sure to clear the URL so we don't fail the sanity
+ // check in the destructor
+ auto clearOnExit = MakeScopeExit([&] { Clear(); });
+
+ nsresult rv;
+
+ uint32_t urlType;
+ rv = stream->Read32(&urlType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURLType = urlType;
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = stream->Read32((uint32_t*)&mPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Read32((uint32_t*)&mDefaultPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_ReadOptionalCString(stream, mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mScheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mUsername);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mPassword);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mBasename);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mExtension);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // handle forward compatibility from older serializations that included mParam
+ URLSegment old_param;
+ rv = ReadSegment(stream, old_param);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mQuery);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString oldOriginCharset;
+ rv = NS_ReadOptionalCString(stream, oldOriginCharset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isMutable;
+ rv = stream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ Unused << isMutable;
+
+ bool supportsFileURL;
+ rv = stream->ReadBoolean(&supportsFileURL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mSupportsFileURL = supportsFileURL;
+
+ // wait until object is set up, then modify path to include the param
+ if (old_param.mLen >= 0) { // note that mLen=0 is ";"
+ // If this wasn't empty, it marks characters between the end of the
+ // file and start of the query - mPath should include the param,
+ // query and ref already. Bump the mFilePath and
+ // directory/basename/extension components to include this.
+ mFilepath.Merge(mSpec, ';', old_param);
+ mDirectory.Merge(mSpec, ';', old_param);
+ mBasename.Merge(mSpec, ';', old_param);
+ mExtension.Merge(mSpec, ';', old_param);
+ }
+
+ rv = CheckIfHostIsAscii();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsValid()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ clearOnExit.release();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Write(nsIObjectOutputStream* stream) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+ nsresult rv;
+
+ rv = stream->Write32(mURLType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Write32(uint32_t(mPort));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Write32(uint32_t(mDefaultPort));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_WriteOptionalStringZ(stream, mSpec.get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mScheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mUsername);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mPassword);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mBasename);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mExtension);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // for backwards compatibility since we removed mParam. Note that this will
+ // mean that an older browser will read "" for mParam, and the param(s) will
+ // be part of mPath (as they after the removal of special handling). It only
+ // matters if you downgrade a browser to before the patch.
+ URLSegment empty;
+ rv = WriteSegment(stream, empty);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mQuery);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // former origin charset
+ rv = NS_WriteOptionalStringZ(stream, "");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // former mMutable
+ rv = stream->WriteBoolean(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->WriteBoolean(mSupportsFileURL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // mDisplayHost is just a cache that can be recovered as needed.
+
+ return NS_OK;
+}
+
+inline ipc::StandardURLSegment ToIPCSegment(
+ const nsStandardURL::URLSegment& aSegment) {
+ return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen);
+}
+
+[[nodiscard]] inline bool FromIPCSegment(
+ const nsACString& aSpec, const ipc::StandardURLSegment& aSegment,
+ nsStandardURL::URLSegment& aTarget) {
+ // This seems to be just an empty segment.
+ if (aSegment.length() == -1) {
+ aTarget = nsStandardURL::URLSegment();
+ return true;
+ }
+
+ // A value of -1 means an empty segment, but < -1 is undefined.
+ if (NS_WARN_IF(aSegment.length() < -1)) {
+ return false;
+ }
+
+ CheckedInt<uint32_t> segmentLen = aSegment.position();
+ segmentLen += aSegment.length();
+ // Make sure the segment does not extend beyond the spec.
+ if (NS_WARN_IF(!segmentLen.isValid() ||
+ segmentLen.value() > aSpec.Length())) {
+ return false;
+ }
+
+ aTarget.mPos = aSegment.position();
+ aTarget.mLen = aSegment.length();
+
+ return true;
+}
+
+void nsStandardURL::Serialize(URIParams& aParams) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+ StandardURLParams params;
+
+ params.urlType() = mURLType;
+ params.port() = mPort;
+ params.defaultPort() = mDefaultPort;
+ params.spec() = mSpec;
+ params.scheme() = ToIPCSegment(mScheme);
+ params.authority() = ToIPCSegment(mAuthority);
+ params.username() = ToIPCSegment(mUsername);
+ params.password() = ToIPCSegment(mPassword);
+ params.host() = ToIPCSegment(mHost);
+ params.path() = ToIPCSegment(mPath);
+ params.filePath() = ToIPCSegment(mFilepath);
+ params.directory() = ToIPCSegment(mDirectory);
+ params.baseName() = ToIPCSegment(mBasename);
+ params.extension() = ToIPCSegment(mExtension);
+ params.query() = ToIPCSegment(mQuery);
+ params.ref() = ToIPCSegment(mRef);
+ params.supportsFileURL() = !!mSupportsFileURL;
+ params.isSubstituting() = false;
+ // mDisplayHost is just a cache that can be recovered as needed.
+
+ aParams = params;
+}
+
+bool nsStandardURL::Deserialize(const URIParams& aParams) {
+ MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
+ MOZ_ASSERT(!mFile, "Shouldn't have cached file");
+
+ if (aParams.type() != URIParams::TStandardURLParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ // If we exit early, make sure to clear the URL so we don't fail the sanity
+ // check in the destructor
+ auto clearOnExit = MakeScopeExit([&] { Clear(); });
+
+ const StandardURLParams& params = aParams.get_StandardURLParams();
+
+ mURLType = params.urlType();
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return false;
+ }
+
+ mPort = params.port();
+ mDefaultPort = params.defaultPort();
+ mSpec = params.spec();
+ NS_ENSURE_TRUE(
+ mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.scheme(), mScheme), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.authority(), mAuthority), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.username(), mUsername), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.password(), mPassword), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.host(), mHost), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.path(), mPath), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.filePath(), mFilepath), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.directory(), mDirectory), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.baseName(), mBasename), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.extension(), mExtension), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.query(), mQuery), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.ref(), mRef), false);
+
+ mSupportsFileURL = params.supportsFileURL();
+
+ nsresult rv = CheckIfHostIsAscii();
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // Some sanity checks
+ NS_ENSURE_TRUE(mScheme.mPos == 0, false);
+ NS_ENSURE_TRUE(mScheme.mLen > 0, false);
+ // Make sure scheme is followed by :// (3 characters)
+ NS_ENSURE_TRUE(mScheme.mLen < INT32_MAX - 3, false); // avoid overflow
+ NS_ENSURE_TRUE(mSpec.Length() >= (uint32_t)mScheme.mLen + 3, false);
+ NS_ENSURE_TRUE(
+ nsDependentCSubstring(mSpec, mScheme.mLen, 3).EqualsLiteral("://"),
+ false);
+ NS_ENSURE_TRUE(mPath.mLen != -1 && mSpec.CharAt(mPath.mPos) == '/', false);
+ NS_ENSURE_TRUE(mPath.mPos == mFilepath.mPos, false);
+ NS_ENSURE_TRUE(mQuery.mLen == -1 ||
+ (mQuery.mPos > 0 && mSpec.CharAt(mQuery.mPos - 1) == '?'),
+ false);
+ NS_ENSURE_TRUE(
+ mRef.mLen == -1 || (mRef.mPos > 0 && mSpec.CharAt(mRef.mPos - 1) == '#'),
+ false);
+
+ if (!IsValid()) {
+ return false;
+ }
+
+ clearOnExit.release();
+
+ return true;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mDisplayHost.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mParser
+ // - mFile
+}
+
+size_t nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace net
+} // namespace mozilla
+
+// For unit tests. Including nsStandardURL.h seems to cause problems
+nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result) {
+ return mozilla::net::nsStandardURL::NormalizeIPv4(host, result);
+}
+
+// For unit tests. Including nsStandardURL.h seems to cause problems
+nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber) {
+ return mozilla::net::ParseIPv4Number(input, base, number, maxNumber);
+}
+
+int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length) {
+ return mozilla::net::ValidateIPv4Number(host, bases, dotIndex, onlyBase10,
+ length, false);
+}
diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h
new file mode 100644
index 0000000000..a4f644e722
--- /dev/null
+++ b/netwerk/base/nsStandardURL.h
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStandardURL_h__
+#define nsStandardURL_h__
+
+#include <bitset>
+
+#include "nsString.h"
+#include "nsISerializable.h"
+#include "nsIFileURL.h"
+#include "nsIStandardURL.h"
+#include "mozilla/Encoding.h"
+#include "nsCOMPtr.h"
+#include "nsURLHelper.h"
+#include "nsISizeOf.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsISensitiveInfoHiddenURI.h"
+#include "nsIURIMutator.h"
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+# define DEBUG_DUMP_URLS_AT_SHUTDOWN
+#endif
+
+class nsIBinaryInputStream;
+class nsIBinaryOutputStream;
+class nsIIDNService;
+class nsIPrefBranch;
+class nsIFile;
+class nsIURLParser;
+
+namespace mozilla {
+class Encoding;
+namespace net {
+
+template <typename T>
+class URLSegmentNumber {
+ T mData{0};
+ bool mParity{false};
+
+ public:
+ URLSegmentNumber() = default;
+ explicit URLSegmentNumber(T data) : mData(data) {
+ mParity = CalculateParity();
+ }
+ bool operator==(URLSegmentNumber value) const { return mData == value.mData; }
+ bool operator!=(URLSegmentNumber value) const { return mData != value.mData; }
+ bool operator>(URLSegmentNumber value) const { return mData > value.mData; }
+ URLSegmentNumber operator+(int32_t value) const {
+ return URLSegmentNumber(mData + value);
+ }
+ URLSegmentNumber operator+(uint32_t value) const {
+ return URLSegmentNumber(mData + value);
+ }
+ URLSegmentNumber operator-(int32_t value) const {
+ return URLSegmentNumber(mData - value);
+ }
+ URLSegmentNumber operator-(uint32_t value) const {
+ return URLSegmentNumber(mData - value);
+ }
+ URLSegmentNumber operator+=(URLSegmentNumber value) {
+ mData += value.mData;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator+=(T value) {
+ mData += value;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator-=(URLSegmentNumber value) {
+ mData -= value.mData;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator-=(T value) {
+ mData -= value;
+ mParity = CalculateParity();
+ return *this;
+ }
+ operator T() const { return mData; }
+ URLSegmentNumber& operator=(T value) {
+ mData = value;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber& operator++() {
+ ++mData;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator++(int) {
+ URLSegmentNumber value = *this;
+ *this += 1;
+ return value;
+ }
+ bool CalculateParity() const {
+ std::bitset<32> bits((uint32_t)mData);
+ return bits.count() % 2 == 0 ? false : true;
+ }
+ bool Parity() const { return mParity; }
+};
+
+//-----------------------------------------------------------------------------
+// standard URL implementation
+//-----------------------------------------------------------------------------
+
+class nsStandardURL : public nsIFileURL,
+ public nsIStandardURL,
+ public nsISerializable,
+ public nsISizeOf,
+ public nsISensitiveInfoHiddenURI
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ ,
+ public LinkedListElement<nsStandardURL>
+#endif
+{
+ protected:
+ virtual ~nsStandardURL();
+ explicit nsStandardURL(bool aSupportsFileURL = false, bool aTrackURL = true);
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURL
+ NS_DECL_NSIFILEURL
+ NS_DECL_NSISTANDARDURL
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSISENSITIVEINFOHIDDENURI
+
+ // nsISizeOf
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ static void InitGlobalObjects();
+ static void ShutdownGlobalObjects();
+
+ public: /* internal -- HPUX compiler can't handle this being private */
+ //
+ // location and length of an url segment relative to mSpec
+ //
+ struct URLSegment {
+#ifdef EARLY_BETA_OR_EARLIER
+ URLSegmentNumber<uint32_t> mPos{0};
+ URLSegmentNumber<int32_t> mLen{-1};
+#else
+ uint32_t mPos{0};
+ int32_t mLen{-1};
+#endif
+
+ URLSegment() = default;
+ URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {}
+ URLSegment(const URLSegment& aCopy) = default;
+ void Reset() {
+ mPos = 0;
+ mLen = -1;
+ }
+ // Merge another segment following this one to it if they're contiguous
+ // Assumes we have something like "foo;bar" where this object is 'foo' and
+ // right is 'bar'.
+ void Merge(const nsCString& spec, const char separator,
+ const URLSegment& right) {
+ if (mLen >= 0 && *(spec.get() + mPos + mLen) == separator &&
+ mPos + mLen + 1 == right.mPos) {
+ mLen += 1 + right.mLen;
+ }
+ }
+ };
+
+ //
+ // URL segment encoder : performs charset conversion and URL escaping.
+ //
+ class nsSegmentEncoder {
+ public:
+ explicit nsSegmentEncoder(const Encoding* encoding = nullptr);
+
+ // Encode the given segment if necessary, and return the length of
+ // the encoded segment. The encoded segment is appended to |aOut|
+ // if and only if encoding is required.
+ int32_t EncodeSegmentCount(const char* str, const URLSegment& aSeg,
+ int16_t mask, nsCString& aOut, bool& appended,
+ uint32_t extraLen = 0);
+
+ // Encode the given string if necessary, and return a reference to
+ // the encoded string. Returns a reference to |result| if encoding
+ // is required. Otherwise, a reference to |str| is returned.
+ const nsACString& EncodeSegment(const nsACString& str, int16_t mask,
+ nsCString& result);
+
+ private:
+ const Encoding* mEncoding;
+ };
+ friend class nsSegmentEncoder;
+
+ static nsresult NormalizeIPv4(const nsACString& host, nsCString& result);
+
+ protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum { eIgnoreRef, eHonorRef, eReplaceRef };
+
+ // Helper to share code between Equals and EqualsExceptRef
+ // NOTE: *not* virtual, because no one needs to override this so far...
+ nsresult EqualsInternal(nsIURI* unknownOther, RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ virtual nsStandardURL* StartClone();
+
+ // Helper to share code between Clone methods.
+ nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& aNewRef, nsIURI** aClone);
+ // Helper method that copies member variables from the source StandardURL
+ // if copyCached = true, it will also copy mFile and mDisplayHost
+ nsresult CopyMembers(nsStandardURL* source, RefHandlingEnum mode,
+ const nsACString& newRef, bool copyCached = false);
+
+ // Helper for subclass implementation of GetFile(). Subclasses that map
+ // URIs to files in a special way should implement this method. It should
+ // ensure that our mFile is initialized, if it's possible.
+ // returns NS_ERROR_NO_INTERFACE if the url does not map to a file
+ virtual nsresult EnsureFile();
+
+ virtual nsresult Clone(nsIURI** aURI);
+ virtual nsresult SetSpecInternal(const nsACString& input);
+ virtual nsresult SetScheme(const nsACString& input);
+ virtual nsresult SetUserPass(const nsACString& input);
+ virtual nsresult SetUsername(const nsACString& input);
+ virtual nsresult SetPassword(const nsACString& input);
+ virtual nsresult SetHostPort(const nsACString& aValue);
+ virtual nsresult SetHost(const nsACString& input);
+ virtual nsresult SetPort(int32_t port);
+ virtual nsresult SetPathQueryRef(const nsACString& input);
+ virtual nsresult SetRef(const nsACString& input);
+ virtual nsresult SetFilePath(const nsACString& input);
+ virtual nsresult SetQuery(const nsACString& input);
+ virtual nsresult SetQueryWithEncoding(const nsACString& input,
+ const Encoding* encoding);
+ bool Deserialize(const mozilla::ipc::URIParams&);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ private:
+ nsresult Init(uint32_t urlType, int32_t defaultPort, const nsACString& spec,
+ const char* charset, nsIURI* baseURI);
+ nsresult SetDefaultPort(int32_t aNewDefaultPort);
+ nsresult SetFile(nsIFile* file);
+
+ nsresult SetFileNameInternal(const nsACString& input);
+ nsresult SetFileBaseNameInternal(const nsACString& input);
+ nsresult SetFileExtensionInternal(const nsACString& input);
+
+ int32_t Port() { return mPort == -1 ? mDefaultPort : mPort; }
+
+ void ReplacePortInSpec(int32_t aNewPort);
+ void Clear();
+ void InvalidateCache(bool invalidateCachedFile = true);
+
+ bool ValidIPv6orHostname(const char* host, uint32_t length);
+ static bool IsValidOfBase(unsigned char c, const uint32_t base);
+ nsresult NormalizeIDN(const nsCString& host, nsCString& result);
+ nsresult CheckIfHostIsAscii();
+ void CoalescePath(netCoalesceFlags coalesceFlag, char* path);
+
+ uint32_t AppendSegmentToBuf(char*, uint32_t, const char*,
+ const URLSegment& input, URLSegment& output,
+ const nsCString* esc = nullptr,
+ bool useEsc = false, int32_t* diff = nullptr);
+ uint32_t AppendToBuf(char*, uint32_t, const char*, uint32_t);
+
+ nsresult BuildNormalizedSpec(const char* spec, const Encoding* encoding);
+ nsresult SetSpecWithEncoding(const nsACString& input,
+ const Encoding* encoding);
+
+ bool SegmentIs(const URLSegment& seg, const char* val,
+ bool ignoreCase = false);
+ bool SegmentIs(const char* spec, const URLSegment& seg, const char* val,
+ bool ignoreCase = false);
+ bool SegmentIs(const URLSegment& seg1, const char* val,
+ const URLSegment& seg2, bool ignoreCase = false);
+
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const char* val,
+ uint32_t valLen);
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const nsACString& val);
+
+ nsresult ParseURL(const char* spec, int32_t specLen);
+ nsresult ParsePath(const char* spec, uint32_t pathPos, int32_t pathLen = -1);
+
+ char* AppendToSubstring(uint32_t pos, int32_t len, const char* tail);
+
+ // dependent substring helpers
+ nsDependentCSubstring Segment(uint32_t pos, int32_t len); // see below
+ nsDependentCSubstring Segment(const URLSegment& s) {
+ return Segment(s.mPos, s.mLen);
+ }
+
+ // dependent substring getters
+ nsDependentCSubstring Prepath(); // see below
+ nsDependentCSubstring Scheme() { return Segment(mScheme); }
+ nsDependentCSubstring Userpass(bool includeDelim = false); // see below
+ nsDependentCSubstring Username() { return Segment(mUsername); }
+ nsDependentCSubstring Password() { return Segment(mPassword); }
+ nsDependentCSubstring Hostport(); // see below
+ nsDependentCSubstring Host(); // see below
+ nsDependentCSubstring Path() { return Segment(mPath); }
+ nsDependentCSubstring Filepath() { return Segment(mFilepath); }
+ nsDependentCSubstring Directory() { return Segment(mDirectory); }
+ nsDependentCSubstring Filename(); // see below
+ nsDependentCSubstring Basename() { return Segment(mBasename); }
+ nsDependentCSubstring Extension() { return Segment(mExtension); }
+ nsDependentCSubstring Query() { return Segment(mQuery); }
+ nsDependentCSubstring Ref() { return Segment(mRef); }
+
+ // shift the URLSegments to the right by diff
+ void ShiftFromAuthority(int32_t diff);
+ void ShiftFromUsername(int32_t diff);
+ void ShiftFromPassword(int32_t diff);
+ void ShiftFromHost(int32_t diff);
+ void ShiftFromPath(int32_t diff);
+ void ShiftFromFilepath(int32_t diff);
+ void ShiftFromDirectory(int32_t diff);
+ void ShiftFromBasename(int32_t diff);
+ void ShiftFromExtension(int32_t diff);
+ void ShiftFromQuery(int32_t diff);
+ void ShiftFromRef(int32_t diff);
+
+ // fastload helper functions
+ nsresult ReadSegment(nsIBinaryInputStream*, URLSegment&);
+ nsresult WriteSegment(nsIBinaryOutputStream*, const URLSegment&);
+
+ void FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd);
+
+ // Asserts that the URL has sane values
+ void SanityCheck();
+
+ // Checks if the URL has a valid representation.
+ bool IsValid();
+
+ // mSpec contains the normalized version of the URL spec (UTF-8 encoded).
+ nsCString mSpec;
+ int32_t mDefaultPort{-1};
+ int32_t mPort{-1};
+
+ // url parts (relative to mSpec)
+ URLSegment mScheme;
+ URLSegment mAuthority;
+ URLSegment mUsername;
+ URLSegment mPassword;
+ URLSegment mHost;
+ URLSegment mPath;
+ URLSegment mFilepath;
+ URLSegment mDirectory;
+ URLSegment mBasename;
+ URLSegment mExtension;
+ URLSegment mQuery;
+ URLSegment mRef;
+
+ nsCOMPtr<nsIURLParser> mParser;
+
+ // mFile is protected so subclasses can access it directly
+ protected:
+ nsCOMPtr<nsIFile> mFile; // cached result for nsIFileURL::GetFile
+
+ private:
+ // cached result for nsIURI::GetDisplayHost
+ nsCString mDisplayHost;
+
+ enum { eEncoding_Unknown, eEncoding_ASCII, eEncoding_UTF8 };
+
+ uint32_t mURLType : 2; // nsIStandardURL::URLTYPE_xxx
+ uint32_t mSupportsFileURL : 1; // QI to nsIFileURL?
+ uint32_t mCheckedIfHostA : 1; // If set to true, it means either that
+ // mDisplayHost has a been initialized, or
+ // that the hostname is not punycode
+
+ // global objects.
+ static StaticRefPtr<nsIIDNService> gIDN;
+ static const char gHostLimitDigits[];
+
+ public:
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ void PrintSpec() const { printf(" %s\n", mSpec.get()); }
+#endif
+
+ public:
+ // We make this implementation a template so that we can avoid writing
+ // the same code for SubstitutingURL (which extends nsStandardURL)
+ template <class T>
+ class TemplatedMutator : public nsIURIMutator,
+ public BaseURIMutator<T>,
+ public nsIStandardURLMutator,
+ public nsIURLMutator,
+ public nsIFileURLMutator,
+ public nsISerializable {
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(BaseURIMutator<T>::mURI)
+
+ [[nodiscard]] NS_IMETHOD Deserialize(
+ const mozilla::ipc::URIParams& aParams) override {
+ return BaseURIMutator<T>::InitFromIPCParams(aParams);
+ }
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return BaseURIMutator<T>::InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override {
+ BaseURIMutator<T>::mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::InitFromSpec(aSpec);
+ }
+
+ [[nodiscard]] NS_IMETHOD Init(uint32_t aURLType, int32_t aDefaultPort,
+ const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ RefPtr<T> uri;
+ if (BaseURIMutator<T>::mURI) {
+ // We don't need a new URI object if we already have one
+ BaseURIMutator<T>::mURI.swap(uri);
+ } else {
+ uri = Create();
+ }
+ nsresult rv =
+ uri->Init(aURLType, aDefaultPort, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ BaseURIMutator<T>::mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHODIMP SetDefaultPort(
+ int32_t aNewDefaultPort, nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetDefaultPort(aNewDefaultPort);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFileName(const nsACString& aFileName,
+ nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetFileNameInternal(aFileName);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFileBaseName(
+ const nsACString& aFileBaseName, nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetFileBaseNameInternal(aFileBaseName);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFileExtension(
+ const nsACString& aFileExtension, nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetFileExtensionInternal(aFileExtension);
+ }
+
+ T* Create() override { return new T(mMarkedFileURL); }
+
+ [[nodiscard]] NS_IMETHOD MarkFileURL() override {
+ mMarkedFileURL = true;
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFile(nsIFile* aFile) override {
+ RefPtr<T> uri;
+ if (BaseURIMutator<T>::mURI) {
+ // We don't need a new URI object if we already have one
+ BaseURIMutator<T>::mURI.swap(uri);
+ } else {
+ uri = new T(/* aSupportsFileURL = */ true);
+ }
+
+ nsresult rv = uri->SetFile(aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ BaseURIMutator<T>::mURI.swap(uri);
+ return NS_OK;
+ }
+
+ explicit TemplatedMutator() = default;
+
+ private:
+ virtual ~TemplatedMutator() = default;
+
+ bool mMarkedFileURL = false;
+
+ friend T;
+ };
+
+ class Mutator final : public TemplatedMutator<nsStandardURL> {
+ NS_DECL_ISUPPORTS
+ public:
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+ };
+
+ friend BaseURIMutator<nsStandardURL>;
+};
+
+#define NS_THIS_STANDARDURL_IMPL_CID \
+ { /* b8e3e97b-1ccd-4b45-af5a-79596770f5d7 */ \
+ 0xb8e3e97b, 0x1ccd, 0x4b45, { \
+ 0xaf, 0x5a, 0x79, 0x59, 0x67, 0x70, 0xf5, 0xd7 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// Dependent substring getters
+//-----------------------------------------------------------------------------
+
+inline nsDependentCSubstring nsStandardURL::Segment(uint32_t pos, int32_t len) {
+ if (len < 0) {
+ pos = 0;
+ len = 0;
+ }
+ return Substring(mSpec, pos, uint32_t(len));
+}
+
+inline nsDependentCSubstring nsStandardURL::Prepath() {
+ uint32_t len = 0;
+ if (mAuthority.mLen >= 0) len = mAuthority.mPos + mAuthority.mLen;
+ return Substring(mSpec, 0, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Userpass(bool includeDelim) {
+ uint32_t pos = 0, len = 0;
+ if (mUsername.mLen > 0 || mPassword.mLen > 0) {
+ if (mUsername.mLen > 0) {
+ pos = mUsername.mPos;
+ len = mUsername.mLen;
+ if (mPassword.mLen >= 0) {
+ len += (mPassword.mLen + 1);
+ }
+ } else {
+ pos = mPassword.mPos - 1;
+ len = mPassword.mLen + 1;
+ }
+
+ if (includeDelim) len++;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Hostport() {
+ uint32_t pos = 0, len = 0;
+ if (mAuthority.mLen > 0) {
+ pos = mHost.mPos;
+ len = mAuthority.mPos + mAuthority.mLen - pos;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Host() {
+ uint32_t pos = 0, len = 0;
+ if (mHost.mLen > 0) {
+ pos = mHost.mPos;
+ len = mHost.mLen;
+ if (mSpec.CharAt(pos) == '[' && mSpec.CharAt(pos + len - 1) == ']') {
+ pos++;
+ len -= 2;
+ }
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Filename() {
+ uint32_t pos = 0, len = 0;
+ // if there is no basename, then there can be no extension
+ if (mBasename.mLen > 0) {
+ pos = mBasename.mPos;
+ len = mBasename.mLen;
+ if (mExtension.mLen >= 0) len += (mExtension.mLen + 1);
+ }
+ return Substring(mSpec, pos, len);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStandardURL_h__
diff --git a/netwerk/base/nsStreamListenerTee.cpp b/netwerk/base/nsStreamListenerTee.cpp
new file mode 100644
index 0000000000..4de5db56f5
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.cpp
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerTee.h"
+#include "nsProxyRelease.h"
+#include "nsIRequest.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerTee, nsIStreamListener, nsIRequestObserver,
+ nsIStreamListenerTee, nsIThreadRetargetableStreamListener,
+ nsIMultiPartChannelListener)
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStartRequest(nsIRequest* request) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(request);
+ if (multiPartChannel) {
+ mIsMultiPart = true;
+ }
+
+ nsresult rv1 = mListener->OnStartRequest(request);
+ nsresult rv2 = NS_OK;
+ if (mObserver) rv2 = mObserver->OnStartRequest(request);
+
+ // Preserve NS_SUCCESS_XXX in rv1 in case mObserver didn't throw
+ return (NS_FAILED(rv2) && NS_SUCCEEDED(rv1)) ? rv2 : rv1;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStopRequest(nsIRequest* request, nsresult status) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ // it is critical that we close out the input stream tee
+ if (mInputTee) {
+ mInputTee->SetSink(nullptr);
+ mInputTee = nullptr;
+ }
+
+ if (!mIsMultiPart) {
+ // release sink on the same thread where the data was written (bug 716293)
+ if (mEventTarget) {
+ NS_ProxyRelease("nsStreamListenerTee::mSink", mEventTarget,
+ mSink.forget());
+ } else {
+ mSink = nullptr;
+ }
+ }
+
+ nsresult rv = mListener->OnStopRequest(request, status);
+ if (!mIsMultiPart) {
+ mListener = nullptr;
+ }
+ if (mObserver) {
+ mObserver->OnStopRequest(request, status);
+ if (!mIsMultiPart) {
+ mObserver = nullptr;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnDataAvailable(nsIRequest* request, nsIInputStream* input,
+ uint64_t offset, uint32_t count) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mSink, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIInputStream> tee;
+ nsresult rv;
+
+ if (!mInputTee) {
+ if (mEventTarget) {
+ rv = NS_NewInputStreamTeeAsync(getter_AddRefs(tee), input, mSink,
+ mEventTarget);
+ } else {
+ rv = NS_NewInputStreamTee(getter_AddRefs(tee), input, mSink);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ mInputTee = do_QueryInterface(tee, &rv);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // re-initialize the input tee since the input stream may have changed.
+ rv = mInputTee->SetSource(input);
+ if (NS_FAILED(rv)) return rv;
+
+ tee = mInputTee;
+ }
+
+ return mListener->OnDataAvailable(request, tee, offset, count);
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnAfterLastPart(nsresult aStatus) {
+ // release sink on the same thread where the data was written (bug 716293)
+ if (mEventTarget) {
+ NS_ProxyRelease("nsStreamListenerTee::mSink", mEventTarget, mSink.forget());
+ } else {
+ mSink = nullptr;
+ }
+
+ if (nsCOMPtr<nsIMultiPartChannelListener> multi =
+ do_QueryInterface(mListener)) {
+ multi->OnAfterLastPart(aStatus);
+ }
+ if (!SameCOMIdentity(mListener, mObserver)) {
+ if (nsCOMPtr<nsIMultiPartChannelListener> multi =
+ do_QueryInterface(mObserver)) {
+ multi->OnAfterLastPart(aStatus);
+ }
+ }
+
+ mObserver = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::CheckListenerChain() {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!mObserver) {
+ return rv;
+ }
+ retargetableListener = do_QueryInterface(mObserver, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnDataFinished(nsresult aStatus) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->OnDataFinished(aStatus);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!mObserver) {
+ return rv;
+ }
+ retargetableListener = do_QueryInterface(mObserver, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->OnDataFinished(aStatus);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::Init(nsIStreamListener* listener, nsIOutputStream* sink,
+ nsIRequestObserver* requestObserver) {
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_ARG_POINTER(sink);
+ mListener = listener;
+ mSink = sink;
+ mObserver = requestObserver;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::InitAsync(nsIStreamListener* listener,
+ nsIEventTarget* eventTarget,
+ nsIOutputStream* sink,
+ nsIRequestObserver* requestObserver) {
+ NS_ENSURE_ARG_POINTER(eventTarget);
+ mEventTarget = eventTarget;
+ return Init(listener, sink, requestObserver);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerTee.h b/netwerk/base/nsStreamListenerTee.h
new file mode 100644
index 0000000000..87328e02cd
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerTee_h__
+#define nsStreamListenerTee_h__
+
+#include "nsIStreamListenerTee.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIInputStreamTee.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+#include "nsIMultiPartChannel.h"
+
+namespace mozilla {
+namespace net {
+
+class nsStreamListenerTee : public nsIStreamListenerTee,
+ public nsIMultiPartChannelListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSISTREAMLISTENERTEE
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+
+ nsStreamListenerTee() = default;
+
+ private:
+ virtual ~nsStreamListenerTee() = default;
+
+ nsCOMPtr<nsIInputStreamTee> mInputTee;
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+ bool mIsMultiPart = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsStreamListenerWrapper.cpp b/netwerk/base/nsStreamListenerWrapper.cpp
new file mode 100644
index 0000000000..3502f3ada3
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.cpp
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerWrapper.h"
+#ifdef DEBUG
+# include "MainThreadUtils.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerWrapper, nsIStreamListener,
+ nsIRequestObserver, nsIMultiPartChannelListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::OnAfterLastPart(nsresult aStatus) {
+ if (nsCOMPtr<nsIMultiPartChannelListener> listener =
+ do_QueryInterface(mListener)) {
+ nsresult rv = NS_OK;
+ if (nsCOMPtr<nsIMultiPartChannelListener> listener =
+ do_QueryInterface(mListener)) {
+ rv = listener->OnAfterLastPart(aStatus);
+ }
+ mListener = nullptr;
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::CheckListenerChain() {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener);
+ if (retargetableListener) {
+ return retargetableListener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerWrapper.h b/netwerk/base/nsStreamListenerWrapper.h
new file mode 100644
index 0000000000..a950837297
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerWrapper_h__
+#define nsStreamListenerWrapper_h__
+
+#include "nsCOMPtr.h"
+#include "nsIRequest.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIMultiPartChannel.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+// Wrapper class to make replacement of nsHttpChannel's listener
+// from JavaScript possible. It is workaround for bug 433711 and 682305.
+class nsStreamListenerWrapper final
+ : public nsIMultiPartChannelListener,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ explicit nsStreamListenerWrapper(nsIStreamListener* listener)
+ : mListener(listener) {
+ MOZ_ASSERT(mListener, "no stream listener specified");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_SAFE_NSISTREAMLISTENER(mListener)
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ // Don't use NS_FORWARD_NSIREQUESTOBSERVER(mListener->) here, because we need
+ // to release mListener in OnStopRequest, and IDL-generated function doesn't.
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
+ // OnStartRequest can come after OnStopRequest in certain cases (multipart
+ // listeners)
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
+ do_QueryInterface(aRequest);
+ if (multiPartChannel) {
+ mIsMulti = true;
+ }
+ return mListener->OnStartRequest(aRequest);
+ }
+ NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override {
+ nsresult rv = mListener->OnStopRequest(aRequest, aStatusCode);
+ if (!mIsMulti) {
+ // Multipart channels can call OnStartRequest again
+ mListener = nullptr;
+ }
+ return rv;
+ }
+
+ private:
+ bool mIsMulti{false};
+ ~nsStreamListenerWrapper() = default;
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamListenerWrapper_h__
diff --git a/netwerk/base/nsStreamLoader.cpp b/netwerk/base/nsStreamLoader.cpp
new file mode 100644
index 0000000000..f73b260de3
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "mozilla/ProfilerLabels.h"
+
+#include <limits>
+
+namespace mozilla {
+namespace net {
+
+nsStreamLoader::nsStreamLoader() = default;
+
+NS_IMETHODIMP
+nsStreamLoader::Init(nsIStreamLoaderObserver* aStreamObserver,
+ nsIRequestObserver* aRequestObserver) {
+ NS_ENSURE_ARG_POINTER(aStreamObserver);
+ mObserver = aStreamObserver;
+ mRequestObserver = aRequestObserver;
+ return NS_OK;
+}
+
+nsresult nsStreamLoader::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsStreamLoader> it = new nsStreamLoader();
+ return it->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsStreamLoader, nsIStreamLoader, nsIRequestObserver,
+ nsIStreamListener, nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) {
+ *aNumBytes = mBytesRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::GetRequest(nsIRequest** aRequest) {
+ nsCOMPtr<nsIRequest> req = mRequest;
+ req.forget(aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStartRequest(nsIRequest* request) {
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(request));
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ // On 64bit platforms size of uint64_t coincides with the size of size_t,
+ // so we want to compare with the minimum from size_t and int64_t.
+ if (static_cast<uint64_t>(contentLength) >
+ std::min(std::numeric_limits<size_t>::max(),
+ static_cast<size_t>(std::numeric_limits<int64_t>::max()))) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ if (mRequestObserver) {
+ mRequestObserver->OnStartRequest(request);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ AUTO_PROFILER_LABEL("nsStreamLoader::OnStopRequest", NETWORK);
+
+ if (mObserver) {
+ // provide nsIStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv =
+ mObserver->OnStreamComplete(this, mContext, aStatus, length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ }
+
+ if (mRequestObserver) {
+ mRequestObserver->OnStopRequest(request, aStatus);
+ mRequestObserver = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStreamLoader::WriteSegmentFun(nsIInputStream* inStr, void* closure,
+ const char* fromSegment,
+ uint32_t toOffset, uint32_t count,
+ uint32_t* writeCount) {
+ nsStreamLoader* self = (nsStreamLoader*)closure;
+
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ uint32_t countRead;
+ nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mBytesRead += countRead;
+ return NS_OK;
+}
+
+void nsStreamLoader::ReleaseData() { mData.clearAndFree(); }
+
+NS_IMETHODIMP
+nsStreamLoader::CheckListenerChain() { return NS_OK; }
+
+NS_IMETHODIMP
+nsStreamLoader::OnDataFinished(nsresult) { return NS_OK; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamLoader.h b/netwerk/base/nsStreamLoader.h
new file mode 100644
index 0000000000..1960c41db3
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamLoader_h__
+#define nsStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamLoader final : public nsIStreamLoader {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsStreamLoader();
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ ~nsStreamLoader() = default;
+
+ static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t,
+ uint32_t, uint32_t*);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIRequestObserver> mRequestObserver;
+
+ mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> mBytesRead;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamLoader_h__
diff --git a/netwerk/base/nsStreamTransportService.cpp b/netwerk/base/nsStreamTransportService.cpp
new file mode 100644
index 0000000000..e1369bbcb5
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.cpp
@@ -0,0 +1,429 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamTransportService.h"
+#include "ErrorList.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsNetSegmentUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIPipe.h"
+#include "nsITransport.h"
+#include "nsIObserverService.h"
+#include "nsThreadPool.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsInputStreamTransport
+//
+// Implements nsIInputStream as a wrapper around the real input stream. This
+// allows the transport to support seeking, range-limiting, progress reporting,
+// and close-when-done semantics while utilizing NS_AsyncCopy.
+//-----------------------------------------------------------------------------
+
+class nsInputStreamTransport : public nsITransport,
+ public nsIAsyncInputStream,
+ public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ nsInputStreamTransport(nsIInputStream* source, bool closeWhenDone)
+ : mSource(source), mCloseWhenDone(closeWhenDone) {
+ mAsyncSource = do_QueryInterface(mSource);
+ }
+
+ private:
+ virtual ~nsInputStreamTransport() = default;
+
+ Mutex mMutex MOZ_UNANNOTATED{"nsInputStreamTransport::mMutex"};
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+
+ // while the copy is active, these members may only be accessed from the
+ // nsIInputStream implementation.
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsIInputStream> mSource;
+
+ // It can be null.
+ nsCOMPtr<nsIAsyncInputStream> mAsyncSource;
+
+ int64_t mOffset{0};
+ const bool mCloseWhenDone;
+
+ // this variable serves as a lock to prevent the state of the transport
+ // from being modified once the copy is in progress.
+ bool mInProgress{false};
+};
+
+NS_IMPL_ADDREF(nsInputStreamTransport);
+NS_IMPL_RELEASE(nsInputStreamTransport);
+
+NS_INTERFACE_MAP_BEGIN(nsInputStreamTransport)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, !!mAsyncSource)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, !!mAsyncSource)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransport)
+NS_INTERFACE_MAP_END
+
+/** nsITransport **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenInputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream** result) {
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX if the caller requests an unbuffered stream, then perhaps
+ // we'd want to simply return mSource; however, then we would
+ // not be reading mSource on a background thread. is this ok?
+
+ bool nonblocking = !(flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(pipeOut), nonblocking,
+ true, segsize, segcount);
+
+ mInProgress = true;
+
+ // startup async copy process...
+ rv = NS_AsyncCopy(this, pipeOut, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+ segsize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *result = do_AddRef(mPipeIn).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream** result) {
+ // this transport only supports reading!
+ MOZ_ASSERT_UNREACHABLE("nsInputStreamTransport::OpenOutputStream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close(nsresult reason) {
+ if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;
+
+ return mPipeIn->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::SetEventSink(nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ if (target) {
+ return net_NewTransportEventSinkProxy(getter_AddRefs(mEventSink), sink,
+ target);
+ }
+
+ mEventSink = sink;
+ return NS_OK;
+}
+
+/** nsIInputStream **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close() {
+ if (mCloseWhenDone) mSource->Close();
+
+ // make additional reads return early...
+ mOffset = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Available(uint64_t* result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::StreamStatus() { return mSource->StreamStatus(); }
+
+NS_IMETHODIMP
+nsInputStreamTransport::Read(char* buf, uint32_t count, uint32_t* result) {
+ nsresult rv = mSource->Read(buf, count, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOffset += *result;
+ if (mEventSink) {
+ mEventSink->OnTransportStatus(this, NS_NET_STATUS_READING, mOffset, -1);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::IsNonBlocking(bool* result) {
+ *result = false;
+ return NS_OK;
+}
+
+// nsIAsyncInputStream interface
+
+NS_IMETHODIMP
+nsInputStreamTransport::CloseWithStatus(nsresult aStatus) { return Close(); }
+
+NS_IMETHODIMP
+nsInputStreamTransport::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ NS_ENSURE_STATE(!!mAsyncSource);
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (NS_WARN_IF(mAsyncWaitCallback && aCallback &&
+ mAsyncWaitCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+ }
+
+ return mAsyncSource->AsyncWait(callback, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+nsInputStreamTransport::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mMutex);
+
+ // We have been canceled in the meanwhile.
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncWaitCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsStreamTransportService
+//-----------------------------------------------------------------------------
+
+nsStreamTransportService::nsStreamTransportService() = default;
+
+nsStreamTransportService::~nsStreamTransportService() {
+ NS_ASSERTION(!mPool, "thread pool wasn't shutdown");
+}
+
+nsresult nsStreamTransportService::Init() {
+ // Can't be used multithreaded before this
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ MOZ_ASSERT(!mPool);
+ mPool = new nsThreadPool();
+
+ // Configure the pool
+ mPool->SetName("StreamTrans"_ns);
+ mPool->SetThreadLimit(25);
+ mPool->SetIdleThreadLimit(5);
+ mPool->SetIdleThreadTimeoutRegressive(true);
+ mPool->SetIdleThreadTimeout(PR_SecondsToInterval(30));
+ MOZ_POP_THREAD_SAFETY
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsStreamTransportService, nsIStreamTransportService,
+ nsIEventTarget, nsIObserver)
+
+NS_IMETHODIMP
+nsStreamTransportService::DispatchFromScript(nsIRunnable* task,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event(task);
+ return Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Dispatch(already_AddRefed<nsIRunnable> task,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event(task); // so it gets released on failure paths
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::RegisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::UnregisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(bool)
+nsStreamTransportService::IsOnCurrentThreadInfallible() {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ pool = mPool;
+ }
+ if (!pool) {
+ return false;
+ }
+ return pool->IsOnCurrentThread();
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::IsOnCurrentThread(bool* result) {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->IsOnCurrentThread(result);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::CreateInputTransport(nsIInputStream* stream,
+ bool closeWhenDone,
+ nsITransport** result) {
+ RefPtr<nsInputStreamTransport> trans =
+ new nsInputStreamTransport(stream, closeWhenDone);
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops");
+
+ {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ mIsShutdown = true;
+ pool = mPool.forget();
+ }
+
+ if (pool) {
+ pool->Shutdown();
+ }
+ }
+ return NS_OK;
+}
+
+class AvailableEvent final : public Runnable {
+ public:
+ AvailableEvent(nsIInputStream* stream, nsIInputAvailableCallback* callback)
+ : Runnable("net::AvailableEvent"),
+ mStream(stream),
+ mCallback(callback),
+ mDoingCallback(false),
+ mSize(0),
+ mResultForCallback(NS_OK) {
+ mCallbackTarget = GetCurrentSerialEventTarget();
+ }
+
+ NS_IMETHOD Run() override {
+ if (mDoingCallback) {
+ // pong
+ mCallback->OnInputAvailableComplete(mSize, mResultForCallback);
+ mCallback = nullptr;
+ } else {
+ // ping
+ mResultForCallback = mStream->Available(&mSize);
+ mStream = nullptr;
+ mDoingCallback = true;
+
+ nsCOMPtr<nsIRunnable> event(this); // overly cute
+ mCallbackTarget->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ mCallbackTarget = nullptr;
+ }
+ return NS_OK;
+ }
+
+ private:
+ virtual ~AvailableEvent() = default;
+
+ nsCOMPtr<nsIInputStream> mStream;
+ nsCOMPtr<nsIInputAvailableCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ bool mDoingCallback;
+ uint64_t mSize;
+ nsresult mResultForCallback;
+};
+
+NS_IMETHODIMP
+nsStreamTransportService::InputAvailable(nsIInputStream* stream,
+ nsIInputAvailableCallback* callback) {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ nsCOMPtr<nsIRunnable> event = new AvailableEvent(stream, callback);
+ return pool->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h
new file mode 100644
index 0000000000..1229c0ca44
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.h
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamTransportService_h__
+#define nsStreamTransportService_h__
+
+#include "nsIStreamTransportService.h"
+#include "nsIEventTarget.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/Mutex.h"
+
+class nsIThreadPool;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamTransportService final : public nsIStreamTransportService,
+ public nsIEventTarget,
+ public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ nsStreamTransportService();
+
+ private:
+ ~nsStreamTransportService();
+
+ nsCOMPtr<nsIThreadPool> mPool MOZ_GUARDED_BY(mShutdownLock);
+
+ mozilla::Mutex mShutdownLock{"nsStreamTransportService.mShutdownLock"};
+ bool mIsShutdown MOZ_GUARDED_BY(mShutdownLock){false};
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsSyncStreamListener.cpp b/netwerk/base/nsSyncStreamListener.cpp
new file mode 100644
index 0000000000..7e5ec94231
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.cpp
@@ -0,0 +1,169 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsSyncStreamListener.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+using namespace mozilla::net;
+
+nsSyncStreamListener::nsSyncStreamListener() {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_NewPipe(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut),
+ mozilla::net::nsIOService::gDefaultSegmentSize,
+ UINT32_MAX, // no size limit
+ false, false);
+}
+
+nsresult nsSyncStreamListener::WaitForData() {
+ mKeepWaiting = true;
+
+ if (!mozilla::SpinEventLoopUntil("nsSyncStreamListener::Create"_ns,
+ [&]() { return !mKeepWaiting; })) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsSyncStreamListener, nsIStreamListener, nsIRequestObserver,
+ nsIInputStream, nsISyncStreamListener)
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISyncStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::GetInputStream(nsIInputStream** result) {
+ *result = do_AddRef(this).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStartRequest(nsIRequest* request) { return NS_OK; }
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* stream, uint64_t offset,
+ uint32_t count) {
+ uint32_t bytesWritten;
+
+ nsresult rv = mPipeOut->WriteFrom(stream, count, &bytesWritten);
+
+ // if we get an error, then return failure. this will cause the
+ // channel to be canceled, and as a result our OnStopRequest method
+ // will be called immediately. because of this we do not need to
+ // set mStatus or mKeepWaiting here.
+ if (NS_FAILED(rv)) return rv;
+
+ // we expect that all data will be written to the pipe because
+ // the pipe was created to have "infinite" room.
+ NS_ASSERTION(bytesWritten == count, "did not write all data");
+
+ mKeepWaiting = false; // unblock Read
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStopRequest(nsIRequest* request, nsresult status) {
+ mStatus = status;
+ mKeepWaiting = false; // unblock Read
+ mDone = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIInputStream
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::Close() {
+ mStatus = NS_BASE_STREAM_CLOSED;
+ mDone = true;
+
+ // It'd be nice if we could explicitly cancel the request at this point,
+ // but we don't have a reference to it, so the best we can do is close the
+ // pipe so that the next OnDataAvailable event will fail.
+ if (mPipeIn) {
+ mPipeIn->Close();
+ mPipeIn = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Available(uint64_t* result) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ mStatus = mPipeIn->Available(result);
+ if (NS_SUCCEEDED(mStatus) && (*result == 0) && !mDone) {
+ nsresult rv = WaitForData();
+ if (NS_FAILED(rv)) {
+ // Note that `WaitForData` could fail `mStatus`. Do not overwrite if it's
+ // the case.
+ mStatus = NS_SUCCEEDED(mStatus) ? rv : mStatus;
+ } else if (NS_SUCCEEDED(mStatus)) {
+ mStatus = mPipeIn->Available(result);
+ }
+ }
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::StreamStatus() {
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ mStatus = mPipeIn->StreamStatus();
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Read(char* buf, uint32_t bufLen, uint32_t* result) {
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64))) return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)bufLen);
+ mStatus = mPipeIn->Read(buf, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64))) return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)count);
+ mStatus = mPipeIn->ReadSegments(writer, closure, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::IsNonBlocking(bool* result) {
+ *result = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsSyncStreamListener.h b/netwerk/base/nsSyncStreamListener.h
new file mode 100644
index 0000000000..9bd9908535
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSyncStreamListener_h__
+#define nsSyncStreamListener_h__
+
+#include "nsISyncStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+//-----------------------------------------------------------------------------
+
+class nsSyncStreamListener final : public nsISyncStreamListener,
+ public nsIInputStream {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISYNCSTREAMLISTENER
+ NS_DECL_NSIINPUTSTREAM
+
+ private:
+ // Factory method:
+ friend nsresult NS_NewSyncStreamListener(nsIStreamListener** result,
+ nsIInputStream** stream);
+ nsSyncStreamListener();
+ ~nsSyncStreamListener() = default;
+
+ nsresult Init();
+
+ nsresult WaitForData();
+
+ nsCOMPtr<nsIInputStream> mPipeIn;
+ nsCOMPtr<nsIOutputStream> mPipeOut;
+ nsresult mStatus{NS_OK};
+ bool mKeepWaiting{false};
+ bool mDone{false};
+};
+
+#endif // nsSyncStreamListener_h__
diff --git a/netwerk/base/nsTransportUtils.cpp b/netwerk/base/nsTransportUtils.cpp
new file mode 100644
index 0000000000..df53ead198
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.cpp
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsITransport.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+class nsTransportStatusEvent;
+
+class nsTransportEventSinkProxy : public nsITransportEventSink {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ nsTransportEventSinkProxy(nsITransportEventSink* sink, nsIEventTarget* target)
+ : mSink(sink),
+ mTarget(target),
+ mLock("nsTransportEventSinkProxy.mLock"),
+ mLastEvent(nullptr) {
+ NS_ADDREF(mSink);
+ }
+
+ private:
+ virtual ~nsTransportEventSinkProxy() {
+ // our reference to mSink could be the last, so be sure to release
+ // it on the target thread. otherwise, we could get into trouble.
+ NS_ProxyRelease("nsTransportEventSinkProxy::mSink", mTarget,
+ dont_AddRef(mSink));
+ }
+
+ public:
+ nsITransportEventSink* mSink;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Mutex mLock MOZ_UNANNOTATED;
+ nsTransportStatusEvent* mLastEvent;
+};
+
+class nsTransportStatusEvent : public Runnable {
+ public:
+ nsTransportStatusEvent(nsTransportEventSinkProxy* proxy,
+ nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+ : Runnable("nsTransportStatusEvent"),
+ mProxy(proxy),
+ mTransport(transport),
+ mStatus(status),
+ mProgress(progress),
+ mProgressMax(progressMax) {}
+
+ ~nsTransportStatusEvent() {
+ auto ReleaseTransport = [transport(std::move(mTransport))]() mutable {};
+ if (!net::OnSocketThread()) {
+ net::gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnection::~nsHttpConnection", std::move(ReleaseTransport)));
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ // since this event is being handled, we need to clear the proxy's ref.
+ // if not coalescing all, then last event may not equal self!
+ {
+ MutexAutoLock lock(mProxy->mLock);
+ if (mProxy->mLastEvent == this) mProxy->mLastEvent = nullptr;
+ }
+
+ mProxy->mSink->OnTransportStatus(mTransport, mStatus, mProgress,
+ mProgressMax);
+ return NS_OK;
+ }
+
+ RefPtr<nsTransportEventSinkProxy> mProxy;
+
+ // parameters to OnTransportStatus
+ nsCOMPtr<nsITransport> mTransport;
+ nsresult mStatus;
+ int64_t mProgress;
+ int64_t mProgressMax;
+};
+
+NS_IMPL_ISUPPORTS(nsTransportEventSinkProxy, nsITransportEventSink)
+
+NS_IMETHODIMP
+nsTransportEventSinkProxy::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress,
+ int64_t progressMax) {
+ nsresult rv = NS_OK;
+ RefPtr<nsTransportStatusEvent> event;
+ {
+ MutexAutoLock lock(mLock);
+
+ // try to coalesce events! ;-)
+ if (mLastEvent && (mLastEvent->mStatus == status)) {
+ mLastEvent->mStatus = status;
+ mLastEvent->mProgress = progress;
+ mLastEvent->mProgressMax = progressMax;
+ } else {
+ event = new nsTransportStatusEvent(this, transport, status, progress,
+ progressMax);
+ if (!event) rv = NS_ERROR_OUT_OF_MEMORY;
+ mLastEvent = event; // weak ref
+ }
+ }
+ if (event) {
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post transport status event");
+
+ MutexAutoLock lock(mLock); // cleanup.. don't reference anymore!
+ mLastEvent = nullptr;
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult net_NewTransportEventSinkProxy(nsITransportEventSink** result,
+ nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ RefPtr<nsTransportEventSinkProxy> res =
+ new nsTransportEventSinkProxy(sink, target);
+ res.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsTransportUtils.h b/netwerk/base/nsTransportUtils.h
new file mode 100644
index 0000000000..141b9e4eda
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.h
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTransportUtils_h__
+#define nsTransportUtils_h__
+
+#include "nsITransport.h"
+
+/**
+ * This function returns a proxy object for a transport event sink instance.
+ * The transport event sink will be called on the thread indicated by the
+ * given event target. Like events are automatically coalesced. This means
+ * that for example if the status value is the same from event to event, and
+ * the previous event has not yet been delivered, then only one event will
+ * be delivered. The progress reported will be that from the second event.
+
+ * Coalescing events can help prevent a backlog of unprocessed transport
+ * events in the case that the target thread is overworked.
+ */
+nsresult net_NewTransportEventSinkProxy(nsITransportEventSink** aResult,
+ nsITransportEventSink* aSink,
+ nsIEventTarget* aTarget);
+
+#endif // nsTransportUtils_h__
diff --git a/netwerk/base/nsUDPSocket.cpp b/netwerk/base/nsUDPSocket.cpp
new file mode 100644
index 0000000000..991c28acf8
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -0,0 +1,1553 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsQueryObject.h"
+#include "nsSocketTransport2.h"
+#include "nsUDPSocket.h"
+#include "nsProxyRelease.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIOService.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsNetAddr.h"
+#include "nsNetSegmentUtils.h"
+#include "IOActivityMonitor.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "prerror.h"
+#include "nsThreadUtils.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+#include "nsIPipe.h"
+#include "nsWrapperCacheInlines.h"
+#include "HttpConnectionUDP.h"
+#include "mozilla/ProfilerBandwidthCounter.h"
+#include "mozilla/StaticPrefs_network.h"
+
+#if defined(FUZZING)
+# include "FuzzyLayer.h"
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+static const uint32_t UDP_PACKET_CHUNK_SIZE = 1400;
+
+//-----------------------------------------------------------------------------
+
+using nsUDPSocketFunc = void (nsUDPSocket::*)();
+
+static nsresult PostEvent(nsUDPSocket* s, nsUDPSocketFunc func) {
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(
+ NewRunnableMethod("net::PostEvent", s, func), NS_DISPATCH_NORMAL);
+}
+
+static nsresult ResolveHost(const nsACString& host,
+ const OriginAttributes& aOriginAttributes,
+ nsIDNSListener* listener) {
+ nsresult rv;
+
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ return dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr,
+ listener, nullptr, aOriginAttributes,
+ getter_AddRefs(tmpOutstanding));
+}
+
+static nsresult CheckIOStatus(const NetAddr* aAddr) {
+ MOZ_ASSERT(gIOService);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (gIOService->IsOffline() &&
+ (StaticPrefs::network_disable_localhost_when_offline() ||
+ !aAddr->IsLoopbackAddr())) {
+ return NS_ERROR_OFFLINE;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class SetSocketOptionRunnable : public Runnable {
+ public:
+ SetSocketOptionRunnable(nsUDPSocket* aSocket, const PRSocketOptionData& aOpt)
+ : Runnable("net::SetSocketOptionRunnable"),
+ mSocket(aSocket),
+ mOpt(aOpt) {}
+
+ NS_IMETHOD Run() override { return mSocket->SetSocketOption(mOpt); }
+
+ private:
+ RefPtr<nsUDPSocket> mSocket;
+ PRSocketOptionData mOpt;
+};
+
+//-----------------------------------------------------------------------------
+// nsUDPOutputStream impl
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsUDPOutputStream, nsIOutputStream)
+
+nsUDPOutputStream::nsUDPOutputStream(nsUDPSocket* aSocket, PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr)
+ : mSocket(aSocket),
+ mFD(aFD),
+ mPrClientAddr(aPrClientAddr),
+ mIsClosed(false) {}
+
+NS_IMETHODIMP nsUDPOutputStream::Close() {
+ if (mIsClosed) return NS_BASE_STREAM_CLOSED;
+
+ mIsClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Flush() { return NS_OK; }
+
+NS_IMETHODIMP nsUDPOutputStream::StreamStatus() {
+ return mIsClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ if (mIsClosed) return NS_BASE_STREAM_CLOSED;
+
+ *_retval = 0;
+ int32_t count =
+ PR_SendTo(mFD, aBuf, aCount, 0, &mPrClientAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+
+ *_retval = count;
+
+ mSocket->AddOutputBytes(count);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsUDPMessage impl
+//-----------------------------------------------------------------------------
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsUDPMessage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsUDPMessage)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsUDPMessage)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsUDPMessage)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPMessage)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsUDPMessage)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsobj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsUDPMessage)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsUDPMessage)
+ tmp->mJsobj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+nsUDPMessage::nsUDPMessage(NetAddr* aAddr, nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>&& aData)
+ : mOutputStream(aOutputStream), mData(std::move(aData)) {
+ memcpy(&mAddr, aAddr, sizeof(NetAddr));
+}
+
+nsUDPMessage::~nsUDPMessage() { DropJSObjects(this); }
+
+NS_IMETHODIMP
+nsUDPMessage::GetFromAddr(nsINetAddr** aFromAddr) {
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetData(nsACString& aData) {
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetOutputStream(nsIOutputStream** aOutputStream) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ *aOutputStream = do_AddRef(mOutputStream).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetRawData(JSContext* cx, JS::MutableHandle<JS::Value> aRawData) {
+ if (!mJsobj) {
+ ErrorResult error;
+ mJsobj = dom::Uint8Array::Create(cx, nullptr, mData, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ HoldJSObjects(this);
+ }
+ aRawData.setObject(*mJsobj);
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>& nsUDPMessage::GetDataAsTArray() { return mData; }
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket
+//-----------------------------------------------------------------------------
+
+nsUDPSocket::nsUDPSocket() {
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService) {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ }
+
+ mSts = gSocketTransportService;
+}
+
+nsUDPSocket::~nsUDPSocket() { CloseSocket(); }
+
+void nsUDPSocket::AddOutputBytes(int32_t aBytes) {
+ mByteWriteCount += aBytes;
+ profiler_count_bandwidth_written_bytes(aBytes);
+}
+
+void nsUDPSocket::OnMsgClose() {
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then socket transport service will call our
+ // OnSocketDetached method automatically. Otherwise, we have to call it
+ // (and thus close the socket) manually.
+ if (!mAttached) OnSocketDetached(mFD);
+}
+
+void nsUDPSocket::OnMsgAttach() {
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition)) {
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach: TryAttach FAILED err=0x%" PRIx32
+ " [this=%p]\n",
+ static_cast<uint32_t>(mCondition), this));
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult nsUDPSocket::TryAttach() {
+ nsresult rv;
+
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ rv = CheckIOStatus(&mAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "net::nsUDPSocket::OnMsgAttach", this, &nsUDPSocket::OnMsgAttach);
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv)) return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// UDPMessageProxy
+//-----------------------------------------------------------------------------
+class UDPMessageProxy final : public nsIUDPMessage {
+ public:
+ UDPMessageProxy(NetAddr* aAddr, nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>&& aData)
+ : mOutputStream(aOutputStream), mData(std::move(aData)) {
+ memcpy(&mAddr, aAddr, sizeof(mAddr));
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPMESSAGE
+
+ private:
+ ~UDPMessageProxy() = default;
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(UDPMessageProxy, nsIUDPMessage)
+
+NS_IMETHODIMP
+UDPMessageProxy::GetFromAddr(nsINetAddr** aFromAddr) {
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetData(nsACString& aData) {
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>& UDPMessageProxy::GetDataAsTArray() { return mData; }
+
+NS_IMETHODIMP
+UDPMessageProxy::GetRawData(JSContext* cx,
+ JS::MutableHandle<JS::Value> aRawData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetOutputStream(nsIOutputStream** aOutputStream) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ *aOutputStream = do_AddRef(mOutputStream).take();
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void nsUDPSocket::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::OnSocketReady: flags=%d [this=%p]\n", outFlags, this));
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_HUP | PR_POLL_NVAL)) {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ if (mSyncListener) {
+ mSyncListener->OnPacketReceived(this);
+ return;
+ }
+
+ PRNetAddr prClientAddr;
+ int32_t count;
+ // Bug 1252755 - use 9216 bytes to allign with nICEr and transportlayer to
+ // support the maximum size of jumbo frames
+ char buff[9216];
+ count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prClientAddr,
+ PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::OnSocketReady: PR_RecvFrom failed [this=%p]\n", this));
+ return;
+ }
+ mByteReadCount += count;
+ profiler_count_bandwidth_read_bytes(count);
+
+ FallibleTArray<uint8_t> data;
+ if (!data.AppendElements(buff, count, fallible)) {
+ UDPSOCKET_LOG((
+ "nsUDPSocket::OnSocketReady: AppendElements FAILED [this=%p]\n", this));
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+
+ uint32_t segsize = UDP_PACKET_CHUNK_SIZE;
+ uint32_t segcount = 0;
+ net_ResolveSegmentParams(segsize, segcount);
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true,
+ segsize, segcount);
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prClientAddr);
+ nsresult rv = NS_AsyncCopy(pipeIn, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ UDP_PACKET_CHUNK_SIZE);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ NetAddr netAddr(&prClientAddr);
+ nsCOMPtr<nsIUDPMessage> message =
+ new UDPMessageProxy(&netAddr, pipeOut, std::move(data));
+ mListener->OnPacketReceived(this, message);
+}
+
+void nsUDPSocket::OnSocketDetached(PRFileDesc* fd) {
+ UDPSOCKET_LOG(("nsUDPSocket::OnSocketDetached [this=%p]\n", this));
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition)) mCondition = NS_ERROR_ABORT;
+
+ if (mFD) {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ CloseSocket();
+ }
+
+ if (mSyncListener) {
+ mSyncListener->OnStopListening(this, mCondition);
+ mSyncListener = nullptr;
+ } else if (mListener) {
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIUDPSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = ToRefPtr(std::move(mListener));
+ }
+
+ if (listener) {
+ listener->OnStopListening(this, mCondition);
+ NS_ProxyRelease("nsUDPSocket::mListener", mListenerTarget,
+ listener.forget());
+ }
+ }
+}
+
+void nsUDPSocket::IsLocal(bool* aIsLocal) {
+ // If bound to loopback, this UDP socket only accepts local connections.
+ *aIsLocal = mAddr.IsLoopbackAddr();
+}
+
+nsresult nsUDPSocket::GetRemoteAddr(NetAddr* addr) {
+ if (!mSyncListener) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(mSyncListener);
+ if (!connUDP) {
+ return NS_ERROR_FAILURE;
+ }
+ return connUDP->GetPeerAddr(addr);
+}
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsUDPSocket, nsIUDPSocket)
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsUDPSocket::Init(int32_t aPort, bool aLoopbackOnly, nsIPrincipal* aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc) {
+ NetAddr addr;
+
+ if (aPort < 0) aPort = 0;
+
+ addr.raw.family = AF_INET;
+ addr.inet.port = htons(aPort);
+
+ if (aLoopbackOnly) {
+ addr.inet.ip = htonl(INADDR_LOOPBACK);
+ } else {
+ addr.inet.ip = htonl(INADDR_ANY);
+ }
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Init2(const nsACString& aAddr, int32_t aPort,
+ nsIPrincipal* aPrincipal, bool aAddressReuse,
+ uint8_t aOptionalArgc) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aPort < 0) {
+ aPort = 0;
+ }
+
+ NetAddr addr;
+ if (NS_FAILED(addr.InitFromString(aAddr, uint16_t(aPort)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (addr.raw.family != PR_AF_INET && addr.raw.family != PR_AF_INET6) {
+ MOZ_ASSERT_UNREACHABLE("Dont accept address other than IPv4 and IPv6");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::InitWithAddress(const NetAddr* aAddr, nsIPrincipal* aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ rv = CheckIOStatus(aAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true;
+
+ if (aPrincipal) {
+ mOriginAttributes = aPrincipal->OriginAttributesRef();
+ }
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenUDPSocket(aAddr->raw.family);
+ if (!mFD) {
+ NS_WARNING("unable to create UDP socket");
+ return NS_ERROR_FAILURE;
+ }
+
+#ifdef FUZZING
+ if (StaticPrefs::fuzzing_necko_enabled()) {
+ rv = AttachFuzzyIOLayer(mFD);
+ if (NS_FAILED(rv)) {
+ UDPSOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ UDPSOCKET_LOG(("Successfully attached fuzzing IOLayer.\n"));
+ }
+#endif
+
+ uint16_t port;
+ if (NS_FAILED(aAddr->GetPort(&port))) {
+ NS_WARNING("invalid bind address");
+ goto fail;
+ }
+
+ PRSocketOptionData opt;
+
+ // Linux kernel will sometimes hand out a used port if we bind
+ // to port 0 with SO_REUSEADDR
+ if (port) {
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = addressReuse;
+ PR_SetSocketOption(mFD, &opt);
+ }
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ PRNetAddr addr;
+ // Temporary work around for IPv6 until bug 1330490 is fixed
+ memset(&addr, 0, sizeof(addr));
+ NetAddrToPRNetAddr(aAddr, &addr);
+
+ if (PR_Bind(mFD, &addr) != PR_SUCCESS) {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS) {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ // create proxy via IOActivityMonitor
+ IOActivityMonitor::MonitorSocket(mFD);
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ Close();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Connect(const NetAddr* aAddr) {
+ UDPSOCKET_LOG(("nsUDPSocket::Connect [this=%p]\n", this));
+
+ NS_ENSURE_ARG(aAddr);
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+
+ rv = CheckIOStatus(aAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+ NS_ASSERTION(onSTSThread, "NOT ON STS THREAD");
+ if (!onSTSThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ if (PR_Connect(mFD, &prAddr, PR_INTERVAL_NO_WAIT) != PR_SUCCESS) {
+ NS_WARNING("Cannot PR_Connect");
+ return NS_ERROR_FAILURE;
+ }
+ PR_SetFDInheritable(mFD, false);
+
+ // get the resulting socket address, which may have been updated.
+ PRNetAddr addr;
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS) {
+ NS_WARNING("cannot get socket name");
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Close() {
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener && !mSyncListener) {
+ // Here we want to go directly with closing the socket since some tests
+ // expects this happen synchronously.
+ CloseSocket();
+
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgClose);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetPort(int32_t* aResult) {
+ // no need to enter the lock here
+ uint16_t result;
+ nsresult rv = mAddr.GetPort(&result);
+ *aResult = static_cast<int32_t>(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetLocalAddr(nsINetAddr** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aResult);
+
+ return NS_OK;
+}
+
+void nsUDPSocket::CloseSocket() {
+ if (mFD) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ UDPSOCKET_LOG(("Intentional leak"));
+ } else {
+ PRIntervalTime closeStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(mFD);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(
+ now - gIOService->LastConnectivityChange()) < 60) {
+ Telemetry::Accumulate(
+ Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(
+ now - gIOService->LastNetworkLinkChange()) < 60) {
+ Telemetry::Accumulate(
+ Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(
+ now - gIOService->LastOfflineStateChange()) < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL,
+ PR_IntervalToMilliseconds(now - closeStarted));
+ }
+ }
+ }
+ mFD = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetAddress(NetAddr* aResult) {
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// SocketListenerProxy
+//-----------------------------------------------------------------------------
+class SocketListenerProxy final : public nsIUDPSocketListener {
+ ~SocketListenerProxy() = default;
+
+ public:
+ explicit SocketListenerProxy(nsIUDPSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIUDPSocketListener>(
+ "SocketListenerProxy::mListener", aListener)),
+ mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable {
+ public:
+ OnPacketReceivedRunnable(
+ const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsIUDPMessage* aMessage)
+ : Runnable("net::SocketListenerProxy::OnPacketReceivedRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mMessage(aMessage) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable {
+ public:
+ OnStopListeningRunnable(
+ const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsresult aStatus)
+ : Runnable("net::SocketListenerProxy::OnStopListeningRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxy, nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage) {
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus) {
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceivedRunnable::Run() {
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ nsCOMPtr<nsIUDPMessage> message =
+ new nsUDPMessage(&netAddr, outputStream, std::move(data));
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListeningRunnable::Run() {
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+class SocketListenerProxyBackground final : public nsIUDPSocketListener {
+ ~SocketListenerProxyBackground() = default;
+
+ public:
+ explicit SocketListenerProxyBackground(nsIUDPSocketListener* aListener)
+ : mListener(aListener), mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable {
+ public:
+ OnPacketReceivedRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsIUDPMessage* aMessage)
+ : Runnable(
+ "net::SocketListenerProxyBackground::OnPacketReceivedRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mMessage(aMessage) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable {
+ public:
+ OnStopListeningRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsresult aStatus)
+ : Runnable(
+ "net::SocketListenerProxyBackground::OnStopListeningRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxyBackground, nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage) {
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus) {
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceivedRunnable::Run() {
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ UDPSOCKET_LOG(("%s [this=%p], len %zu", __FUNCTION__, this, data.Length()));
+ nsCOMPtr<nsIUDPMessage> message =
+ new UDPMessageProxy(&netAddr, outputStream, std::move(data));
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListeningRunnable::Run() {
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+class PendingSend : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSend(nsUDPSocket* aSocket, uint16_t aPort,
+ FallibleTArray<uint8_t>&& aData)
+ : mSocket(aSocket), mPort(aPort), mData(std::move(aData)) {}
+
+ private:
+ virtual ~PendingSend() = default;
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(PendingSend, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSend::OnLookupComplete(nsICancelable* request, nsIDNSRecord* aRecord,
+ nsresult status) {
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ MOZ_ASSERT(rec);
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ uint32_t count;
+ nsresult rv = mSocket->SendWithAddress(&addr, mData.Elements(),
+ mData.Length(), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class PendingSendStream : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSendStream(nsUDPSocket* aSocket, uint16_t aPort,
+ nsIInputStream* aStream)
+ : mSocket(aSocket), mPort(aPort), mStream(aStream) {}
+
+ private:
+ virtual ~PendingSendStream() = default;
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ nsCOMPtr<nsIInputStream> mStream;
+};
+
+NS_IMPL_ISUPPORTS(PendingSendStream, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSendStream::OnLookupComplete(nsICancelable* request,
+ nsIDNSRecord* aRecord, nsresult status) {
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ MOZ_ASSERT(rec);
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ nsresult rv = mSocket->SendBinaryStreamWithAddress(&addr, mStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class SendRequestRunnable : public Runnable {
+ public:
+ SendRequestRunnable(nsUDPSocket* aSocket, const NetAddr& aAddr,
+ FallibleTArray<uint8_t>&& aData)
+ : Runnable("net::SendRequestRunnable"),
+ mSocket(aSocket),
+ mAddr(aAddr),
+ mData(std::move(aData)) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ RefPtr<nsUDPSocket> mSocket;
+ const NetAddr mAddr;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMETHODIMP
+SendRequestRunnable::Run() {
+ uint32_t count;
+ mSocket->SendWithAddress(&mAddr, mData.Elements(), mData.Length(), &count);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsUDPSocket::AsyncListen(nsIUDPSocketListener* aListener) {
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(mSyncListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListenerTarget = GetCurrentSerialEventTarget();
+ if (NS_IsMainThread()) {
+ // PNecko usage
+ mListener = new SocketListenerProxy(aListener);
+ } else {
+ // PBackground usage from dom/media/webrtc/transport
+ mListener = new SocketListenerProxyBackground(aListener);
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SyncListen(nsIUDPSocketSyncListener* aListener) {
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(mSyncListener == nullptr, NS_ERROR_IN_PROGRESS);
+
+ mSyncListener = aListener;
+
+ return PostEvent(this, &nsUDPSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Send(const nsACString& aHost, uint16_t aPort,
+ const nsTArray<uint8_t>& aData, uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = 0;
+
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCOMPtr<nsIDNSListener> listener =
+ new PendingSend(this, aPort, std::move(fallibleArray));
+
+ nsresult rv = ResolveHost(aHost, mOriginAttributes, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = aData.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddr(nsINetAddr* aAddr, const nsTArray<uint8_t>& aData,
+ uint32_t* _retval) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ NetAddr netAddr;
+ aAddr->GetNetAddr(&netAddr);
+ return SendWithAddress(&netAddr, aData.Elements(), aData.Length(), _retval);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddress(const NetAddr* aAddr, const uint8_t* aData,
+ uint32_t aLength, uint32_t* _retval) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (StaticPrefs::network_http_http3_block_loopback_ipv6_addr() &&
+ aAddr->raw.family == AF_INET6 && aAddr->IsLoopbackAddr()) {
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ *_retval = 0;
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (onSTSThread) {
+ MutexAutoLock lock(mLock);
+ if (!mFD) {
+ // socket is not initialized or has been closed
+ return NS_ERROR_FAILURE;
+ }
+ int32_t count =
+ PR_SendTo(mFD, aData, aLength, 0, &prAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+ this->AddOutputBytes(count);
+ *_retval = count;
+ } else {
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.AppendElements(aData, aLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = mSts->Dispatch(
+ new SendRequestRunnable(this, *aAddr, std::move(fallibleArray)),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *_retval = aLength;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStream(const nsACString& aHost, uint16_t aPort,
+ nsIInputStream* aStream) {
+ NS_ENSURE_ARG(aStream);
+
+ nsCOMPtr<nsIDNSListener> listener =
+ new PendingSendStream(this, aPort, aStream);
+
+ return ResolveHost(aHost, mOriginAttributes, listener);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStreamWithAddress(const NetAddr* aAddr,
+ nsIInputStream* aStream) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aStream);
+
+ PRNetAddr prAddr;
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prAddr);
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prAddr);
+ return NS_AsyncCopy(aStream, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ UDP_PACKET_CHUNK_SIZE);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::RecvWithAddr(NetAddr* addr, nsTArray<uint8_t>& aData) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ PRNetAddr prAddr;
+ int32_t count;
+ char buff[9216];
+ count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::RecvWithAddr: PR_RecvFrom failed [this=%p]\n", this));
+ return NS_OK;
+ }
+ mByteReadCount += count;
+ profiler_count_bandwidth_read_bytes(count);
+ PRNetAddrToNetAddr(&prAddr, addr);
+
+ if (!aData.AppendElements(buff, count, fallible)) {
+ UDPSOCKET_LOG((
+ "nsUDPSocket::OnSocketReady: AppendElements FAILED [this=%p]\n", this));
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult nsUDPSocket::SetSocketOption(const PRSocketOptionData& aOpt) {
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (!onSTSThread) {
+ // Dispatch to STS thread and re-enter this method there
+ nsCOMPtr<nsIRunnable> runnable = new SetSocketOptionRunnable(this, aOpt);
+ nsresult rv = mSts->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (PR_SetSocketOption(mFD, &aOpt) != PR_SUCCESS) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::SetSocketOption [this=%p] failed for type %d, "
+ "error %d\n",
+ this, aOpt.option, PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticast(const nsACString& aAddr, const nsACString& aIface) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+nsresult nsUDPSocket::JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_AddMember;
+ opt.value.add_member.mcaddr = aAddr;
+ opt.value.add_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticast(const nsACString& aAddr, const nsACString& aIface) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+nsresult nsUDPSocket::LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_DropMember;
+ opt.value.drop_member.mcaddr = aAddr;
+ opt.value.drop_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastLoopback(bool* aLoopback) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastLoopback(bool aLoopback) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastLoopback;
+ opt.value.mcast_loopback = aLoopback;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetRecvBufferSize(int* size) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetRecvBufferSize(int size) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetDontFragment(bool* dontFragment) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetDontFragment(bool dontFragment) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_DontFrag;
+ opt.value.dont_fragment = dontFragment;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetSendBufferSize(int* size) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetSendBufferSize(int size) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterface(nsACString& aIface) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterfaceAddr(NetAddr* aIface) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterface(const nsACString& aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterfaceAddr(NetAddr aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ NetAddrToPRNetAddr(&aIface, &prIface);
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+nsresult nsUDPSocket::SetMulticastInterfaceInternal(const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastInterface;
+ opt.value.mcast_if = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsUDPSocket.h b/netwerk/base/nsUDPSocket.h
new file mode 100644
index 0000000000..bd616d5206
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.h
@@ -0,0 +1,118 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUDPSocket_h__
+#define nsUDPSocket_h__
+
+#include "nsIUDPSocket.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DNS.h"
+#include "nsIOutputStream.h"
+#include "nsASocketHandler.h"
+#include "nsCycleCollectionParticipant.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsSocketTransportService;
+
+class nsUDPSocket final : public nsASocketHandler, public nsIUDPSocket {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+ virtual nsresult GetRemoteAddr(NetAddr* addr) override;
+
+ uint64_t ByteCountSent() override { return mByteWriteCount; }
+ uint64_t ByteCountReceived() override { return mByteReadCount; }
+
+ void AddOutputBytes(int32_t aBytes);
+
+ nsUDPSocket();
+
+ private:
+ virtual ~nsUDPSocket();
+
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ friend class SetSocketOptionRunnable;
+ nsresult SetSocketOption(const PRSocketOptionData& aOpt);
+ nsresult JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult SetMulticastInterfaceInternal(const PRNetAddr& aIface);
+
+ void CloseSocket();
+
+ // lock protects access to mListener;
+ // so mListener is not cleared while being used/locked.
+ Mutex mLock MOZ_UNANNOTATED{"nsUDPSocket.mLock"};
+ PRFileDesc* mFD{nullptr};
+ NetAddr mAddr;
+ OriginAttributes mOriginAttributes;
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocketSyncListener> mSyncListener;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached{false};
+ RefPtr<nsSocketTransportService> mSts;
+
+ uint64_t mByteReadCount{0};
+ uint64_t mByteWriteCount{0};
+};
+
+//-----------------------------------------------------------------------------
+
+class nsUDPMessage : public nsIUDPMessage {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsUDPMessage)
+ NS_DECL_NSIUDPMESSAGE
+
+ nsUDPMessage(NetAddr* aAddr, nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>&& aData);
+
+ private:
+ virtual ~nsUDPMessage();
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+ JS::Heap<JSObject*> mJsobj;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsUDPOutputStream : public nsIOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsUDPOutputStream(nsUDPSocket* aSocket, PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr);
+
+ private:
+ virtual ~nsUDPOutputStream() = default;
+
+ RefPtr<nsUDPSocket> mSocket;
+ PRFileDesc* mFD;
+ PRNetAddr mPrClientAddr;
+ bool mIsClosed;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsUDPSocket_h__
diff --git a/netwerk/base/nsURIHashKey.h b/netwerk/base/nsURIHashKey.h
new file mode 100644
index 0000000000..fb98ae0d21
--- /dev/null
+++ b/netwerk/base/nsURIHashKey.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsURIHashKey_h__
+#define nsURIHashKey_h__
+
+#include <utility>
+
+#include "PLDHashTable.h"
+#include "mozilla/Unused.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIURI.h"
+
+/**
+ * Hashtable key class to use with nsTHashtable/nsBaseHashtable
+ */
+class nsURIHashKey : public PLDHashEntryHdr {
+ public:
+ typedef nsIURI* KeyType;
+ typedef const nsIURI* KeyTypePointer;
+
+ nsURIHashKey() { MOZ_COUNT_CTOR(nsURIHashKey); }
+ explicit nsURIHashKey(const nsIURI* aKey) : mKey(const_cast<nsIURI*>(aKey)) {
+ MOZ_COUNT_CTOR(nsURIHashKey);
+ }
+ nsURIHashKey(nsURIHashKey&& toMove)
+ : PLDHashEntryHdr(std::move(toMove)), mKey(std::move(toMove.mKey)) {
+ MOZ_COUNT_CTOR(nsURIHashKey);
+ }
+ MOZ_COUNTED_DTOR(nsURIHashKey)
+
+ nsURIHashKey& operator=(const nsURIHashKey& aOther) {
+ mKey = aOther.mKey;
+ return *this;
+ }
+
+ nsIURI* GetKey() const { return mKey; }
+
+ bool KeyEquals(const nsIURI* aKey) const {
+ bool eq;
+ if (!mKey) {
+ return !aKey;
+ }
+ if (NS_SUCCEEDED(mKey->Equals(const_cast<nsIURI*>(aKey), &eq))) {
+ return eq;
+ }
+ return false;
+ }
+
+ static const nsIURI* KeyToPointer(nsIURI* aKey) { return aKey; }
+ static PLDHashNumber HashKey(const nsIURI* aKey) {
+ if (!aKey) {
+ // If the key is null, return hash for empty string.
+ return mozilla::HashString(""_ns);
+ }
+ nsAutoCString spec;
+ // If GetSpec() fails, ignoring the failure and proceeding with an
+ // empty |spec| seems like the best thing to do.
+ mozilla::Unused << const_cast<nsIURI*>(aKey)->GetSpec(spec);
+ return mozilla::HashString(spec);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ nsCOMPtr<nsIURI> mKey;
+};
+
+#endif // nsURIHashKey_h__
diff --git a/netwerk/base/nsURLHelper.cpp b/netwerk/base/nsURLHelper.cpp
new file mode 100644
index 0000000000..3850c6865a
--- /dev/null
+++ b/netwerk/base/nsURLHelper.cpp
@@ -0,0 +1,1356 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsURLHelper.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/TextUtils.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "nsASCIIMask.h"
+#include "nsIFile.h"
+#include "nsIURLParser.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "mozilla/Preferences.h"
+#include "prnetdb.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Tokenizer.h"
+#include "nsEscape.h"
+#include "nsDOMString.h"
+#include "mozilla/net/rust_helper.h"
+#include "mozilla/net/DNS.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+// Init/Shutdown
+//----------------------------------------------------------------------------
+
+static bool gInitialized = false;
+static StaticRefPtr<nsIURLParser> gNoAuthURLParser;
+static StaticRefPtr<nsIURLParser> gAuthURLParser;
+static StaticRefPtr<nsIURLParser> gStdURLParser;
+
+static void InitGlobals() {
+ nsCOMPtr<nsIURLParser> parser;
+
+ parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'noauth' url parser");
+ if (parser) {
+ gNoAuthURLParser = parser;
+ }
+
+ parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'auth' url parser");
+ if (parser) {
+ gAuthURLParser = parser;
+ }
+
+ parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'std' url parser");
+ if (parser) {
+ gStdURLParser = parser;
+ }
+
+ gInitialized = true;
+}
+
+void net_ShutdownURLHelper() {
+ if (gInitialized) {
+ gInitialized = false;
+ }
+ gNoAuthURLParser = nullptr;
+ gAuthURLParser = nullptr;
+ gStdURLParser = nullptr;
+}
+
+//----------------------------------------------------------------------------
+// nsIURLParser getters
+//----------------------------------------------------------------------------
+
+nsIURLParser* net_GetAuthURLParser() {
+ if (!gInitialized) InitGlobals();
+ return gAuthURLParser;
+}
+
+nsIURLParser* net_GetNoAuthURLParser() {
+ if (!gInitialized) InitGlobals();
+ return gNoAuthURLParser;
+}
+
+nsIURLParser* net_GetStdURLParser() {
+ if (!gInitialized) InitGlobals();
+ return gStdURLParser;
+}
+
+//---------------------------------------------------------------------------
+// GetFileFromURLSpec implementations
+//---------------------------------------------------------------------------
+nsresult net_GetURLSpecFromDir(nsIFile* aFile, nsACString& result) {
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv)) return rv;
+
+ if (escPath.Last() != '/') {
+ escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetURLSpecFromFile(nsIFile* aFile, nsACString& result) {
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv)) return rv;
+
+ // if this file references a directory, then we need to ensure that the
+ // URL ends with a slash. this is important since it affects the rules
+ // for relative URL resolution when this URL is used as a base URL.
+ // if the file does not exist, then we make no assumption about its type,
+ // and simply leave the URL unmodified.
+ if (escPath.Last() != '/') {
+ bool dir;
+ rv = aFile->IsDirectory(&dir);
+ if (NS_SUCCEEDED(rv) && dir) escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// file:// URL parsing
+//----------------------------------------------------------------------------
+
+nsresult net_ParseFileURL(const nsACString& inURL, nsACString& outDirectory,
+ nsACString& outFileBaseName,
+ nsACString& outFileExtension) {
+ nsresult rv;
+
+ if (inURL.Length() >
+ (uint32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ outDirectory.Truncate();
+ outFileBaseName.Truncate();
+ outFileExtension.Truncate();
+
+ const nsPromiseFlatCString& flatURL = PromiseFlatCString(inURL);
+ const char* url = flatURL.get();
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(flatURL, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!scheme.EqualsLiteral("file")) {
+ NS_ERROR("must be a file:// url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsIURLParser* parser = net_GetNoAuthURLParser();
+ NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
+
+ uint32_t pathPos, filepathPos, directoryPos, basenamePos, extensionPos;
+ int32_t pathLen, filepathLen, directoryLen, basenameLen, extensionLen;
+
+ // invoke the parser to extract the URL path
+ rv = parser->ParseURL(url, flatURL.Length(), nullptr,
+ nullptr, // don't care about scheme
+ nullptr, nullptr, // don't care about authority
+ &pathPos, &pathLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // invoke the parser to extract filepath from the path
+ rv = parser->ParsePath(url + pathPos, pathLen, &filepathPos, &filepathLen,
+ nullptr, nullptr, // don't care about query
+ nullptr, nullptr); // don't care about ref
+ if (NS_FAILED(rv)) return rv;
+
+ filepathPos += pathPos;
+
+ // invoke the parser to extract the directory and filename from filepath
+ rv = parser->ParseFilePath(url + filepathPos, filepathLen, &directoryPos,
+ &directoryLen, &basenamePos, &basenameLen,
+ &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) return rv;
+
+ if (directoryLen > 0) {
+ outDirectory = Substring(inURL, filepathPos + directoryPos, directoryLen);
+ }
+ if (basenameLen > 0) {
+ outFileBaseName = Substring(inURL, filepathPos + basenamePos, basenameLen);
+ }
+ if (extensionLen > 0) {
+ outFileExtension =
+ Substring(inURL, filepathPos + extensionPos, extensionLen);
+ }
+ // since we are using a no-auth url parser, there will never be a host
+ // XXX not strictly true... file://localhost/foo/bar.html is a valid URL
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// path manipulation functions
+//----------------------------------------------------------------------------
+
+// Replace all /./ with a / while resolving URLs
+// But only till #?
+void net_CoalesceDirs(netCoalesceFlags flags, char* path) {
+ /* Stolen from the old netlib's mkparse.c.
+ *
+ * modifies a url of the form /foo/../foo1 -> /foo1
+ * and /foo/./foo1 -> /foo/foo1
+ * and /foo/foo1/.. -> /foo/
+ */
+ char* fwdPtr = path;
+ char* urlPtr = path;
+ char* lastslash = path;
+ uint32_t traversal = 0;
+ uint32_t special_ftp_len = 0;
+
+ /* Remember if this url is a special ftp one: */
+ if (flags & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
+ /* some schemes (for example ftp) have the speciality that
+ the path can begin // or /%2F to mark the root of the
+ servers filesystem, a simple / only marks the root relative
+ to the user loging in. We remember the length of the marker */
+ if (nsCRT::strncasecmp(path, "/%2F", 4) == 0) {
+ special_ftp_len = 4;
+ } else if (strncmp(path, "//", 2) == 0) {
+ special_ftp_len = 2;
+ }
+ }
+
+ /* find the last slash before # or ? */
+ for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) {
+ }
+
+ /* found nothing, but go back one only */
+ /* if there is something to go back to */
+ if (fwdPtr != path && *fwdPtr == '\0') {
+ --fwdPtr;
+ }
+
+ /* search the slash */
+ for (; (fwdPtr != path) && (*fwdPtr != '/'); --fwdPtr) {
+ }
+ lastslash = fwdPtr;
+ fwdPtr = path;
+
+ /* replace all %2E or %2e with . in the path */
+ /* but stop at lastchar if non null */
+ for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#') &&
+ (*lastslash == '\0' || fwdPtr != lastslash);
+ ++fwdPtr) {
+ if (*fwdPtr == '%' && *(fwdPtr + 1) == '2' &&
+ (*(fwdPtr + 2) == 'E' || *(fwdPtr + 2) == 'e')) {
+ *urlPtr++ = '.';
+ ++fwdPtr;
+ ++fwdPtr;
+ } else {
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr) {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+
+ // start again, this time for real
+ fwdPtr = path;
+ urlPtr = path;
+
+ for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) {
+ if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '/') {
+ // remove . followed by slash
+ ++fwdPtr;
+ } else if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '.' &&
+ (*(fwdPtr + 3) == '/' ||
+ *(fwdPtr + 3) == '\0' || // This will take care of
+ *(fwdPtr + 3) == '?' || // something like foo/bar/..#sometag
+ *(fwdPtr + 3) == '#')) {
+ // remove foo/..
+ // reverse the urlPtr to the previous slash if possible
+ // if url does not allow relative root then drop .. above root
+ // otherwise retain them in the path
+ if (traversal > 0 || !(flags & NET_COALESCE_ALLOW_RELATIVE_ROOT)) {
+ if (urlPtr != path) urlPtr--; // we must be going back at least by one
+ for (; *urlPtr != '/' && urlPtr != path; urlPtr--) {
+ ; // null body
+ }
+ --traversal; // count back
+ // forward the fwdPtr past the ../
+ fwdPtr += 2;
+ // if we have reached the beginning of the path
+ // while searching for the previous / and we remember
+ // that it is an url that begins with /%2F then
+ // advance urlPtr again by 3 chars because /%2F already
+ // marks the root of the path
+ if (urlPtr == path && special_ftp_len > 3) {
+ ++urlPtr;
+ ++urlPtr;
+ ++urlPtr;
+ }
+ // special case if we have reached the end
+ // to preserve the last /
+ if (*fwdPtr == '.' && *(fwdPtr + 1) == '\0') ++urlPtr;
+ } else {
+ // there are to much /.. in this path, just copy them instead.
+ // forward the urlPtr past the /.. and copying it
+
+ // However if we remember it is an url that starts with
+ // /%2F and urlPtr just points at the "F" of "/%2F" then do
+ // not overwrite it with the /, just copy .. and move forward
+ // urlPtr.
+ if (special_ftp_len > 3 && urlPtr == path + special_ftp_len - 1) {
+ ++urlPtr;
+ } else {
+ *urlPtr++ = *fwdPtr;
+ }
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ }
+ } else {
+ // count the hierachie, but only if we do not have reached
+ // the root of some special urls with a special root marker
+ if (*fwdPtr == '/' && *(fwdPtr + 1) != '.' &&
+ (special_ftp_len != 2 || *(fwdPtr + 1) != '/')) {
+ traversal++;
+ }
+ // copy the url incrementaly
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+
+ /*
+ * Now lets remove trailing . case
+ * /foo/foo1/. -> /foo/foo1/
+ */
+
+ if ((urlPtr > (path + 1)) && (*(urlPtr - 1) == '.') &&
+ (*(urlPtr - 2) == '/')) {
+ urlPtr--;
+ }
+
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr) {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+}
+
+//----------------------------------------------------------------------------
+// scheme fu
+//----------------------------------------------------------------------------
+
+static bool net_IsValidSchemeChar(const char aChar) {
+ return mozilla::net::rust_net_is_valid_scheme_char(aChar);
+}
+
+/* Extract URI-Scheme if possible */
+nsresult net_ExtractURLScheme(const nsACString& inURI, nsACString& scheme) {
+ nsACString::const_iterator start, end;
+ inURI.BeginReading(start);
+ inURI.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t)*start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+ p.Record();
+ if (!p.CheckChar(IsAsciiAlpha)) {
+ // First char must be alpha
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+
+ if (!p.CheckChar(':')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ p.Claim(scheme);
+ scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+ ToLowerCase(scheme);
+ return NS_OK;
+}
+
+bool net_IsValidScheme(const nsACString& scheme) {
+ return mozilla::net::rust_net_is_valid_scheme(&scheme);
+}
+
+bool net_IsAbsoluteURL(const nsACString& uri) {
+ nsACString::const_iterator start, end;
+ uri.BeginReading(start);
+ uri.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t)*start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+
+ // First char must be alpha
+ if (!p.CheckChar(IsAsciiAlpha)) {
+ return false;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+ if (!p.CheckChar(':')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (!p.CheckChar('/')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (p.CheckChar('/')) {
+ // aSpec is really absolute. Ignore aBaseURI in this case
+ return true;
+ }
+ return false;
+}
+
+void net_FilterURIString(const nsACString& input, nsACString& result) {
+ result.Truncate();
+
+ const auto* start = input.BeginReading();
+ const auto* end = input.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ const auto* newStart = std::find_if(start, end, charFilter);
+ const auto* newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter)
+ .base();
+
+ // Check if chars need to be stripped.
+ bool needsStrip = false;
+ const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
+ for (const auto* itr = start; itr != end; ++itr) {
+ if (ASCIIMask::IsMasked(mask, *itr)) {
+ needsStrip = true;
+ break;
+ }
+ }
+
+ // Just use the passed in string rather than creating new copies if no
+ // changes are necessary.
+ if (newStart == start && newEnd == end && !needsStrip) {
+ result = input;
+ return;
+ }
+
+ result.Assign(Substring(newStart, newEnd));
+ if (needsStrip) {
+ result.StripTaggedASCII(mask);
+ }
+}
+
+nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags,
+ const ASCIIMaskArray& aFilterMask,
+ nsACString& aResult) {
+ aResult.Truncate();
+
+ const auto* start = aInput.BeginReading();
+ const auto* end = aInput.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ const auto* newStart = std::find_if(start, end, charFilter);
+ const auto* newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter)
+ .base();
+
+ return NS_EscapeAndFilterURL(Substring(newStart, newEnd), aFlags,
+ &aFilterMask, aResult, fallible);
+}
+
+#if defined(XP_WIN)
+bool net_NormalizeFileURL(const nsACString& aURL, nsCString& aResultBuf) {
+ bool writing = false;
+
+ nsACString::const_iterator beginIter, endIter;
+ aURL.BeginReading(beginIter);
+ aURL.EndReading(endIter);
+
+ const char *s, *begin = beginIter.get();
+
+ for (s = begin; s != endIter.get(); ++s) {
+ if (*s == '\\') {
+ writing = true;
+ if (s > begin) aResultBuf.Append(begin, s - begin);
+ aResultBuf += '/';
+ begin = s + 1;
+ }
+ if (*s == '#') {
+ // Don't normalize any backslashes following the hash.
+ s = endIter.get();
+ break;
+ }
+ }
+ if (writing && s > begin) aResultBuf.Append(begin, s - begin);
+
+ return writing;
+}
+#endif
+
+//----------------------------------------------------------------------------
+// miscellaneous (i.e., stuff that should really be elsewhere)
+//----------------------------------------------------------------------------
+
+static inline void ToLower(char& c) {
+ if ((unsigned)(c - 'A') <= (unsigned)('Z' - 'A')) c += 'a' - 'A';
+}
+
+void net_ToLowerCase(char* str, uint32_t length) {
+ for (char* end = str + length; str < end; ++str) ToLower(*str);
+}
+
+void net_ToLowerCase(char* str) {
+ for (; *str; ++str) ToLower(*str);
+}
+
+char* net_FindCharInSet(const char* iter, const char* stop, const char* set) {
+ for (; iter != stop && *iter; ++iter) {
+ for (const char* s = set; *s; ++s) {
+ if (*iter == *s) return (char*)iter;
+ }
+ }
+ return (char*)iter;
+}
+
+char* net_FindCharNotInSet(const char* iter, const char* stop,
+ const char* set) {
+repeat:
+ for (const char* s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (++iter == stop) break;
+ goto repeat;
+ }
+ }
+ return (char*)iter;
+}
+
+char* net_RFindCharNotInSet(const char* stop, const char* iter,
+ const char* set) {
+ --iter;
+ --stop;
+
+ if (iter == stop) return (char*)iter;
+
+repeat:
+ for (const char* s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (--iter == stop) break;
+ goto repeat;
+ }
+ }
+ return (char*)iter;
+}
+
+#define HTTP_LWS " \t"
+
+// Return the index of the closing quote of the string, if any
+static uint32_t net_FindStringEnd(const nsCString& flatStr,
+ uint32_t stringStart, char stringDelim) {
+ NS_ASSERTION(stringStart < flatStr.Length() &&
+ flatStr.CharAt(stringStart) == stringDelim &&
+ (stringDelim == '"' || stringDelim == '\''),
+ "Invalid stringStart");
+
+ const char set[] = {stringDelim, '\\', '\0'};
+ do {
+ // stringStart points to either the start quote or the last
+ // escaped char (the char following a '\\')
+
+ // Write to searchStart here, so that when we get back to the
+ // top of the loop right outside this one we search from the
+ // right place.
+ uint32_t stringEnd = flatStr.FindCharInSet(set, stringStart + 1);
+ if (stringEnd == uint32_t(kNotFound)) return flatStr.Length();
+
+ if (flatStr.CharAt(stringEnd) == '\\') {
+ // Hit a backslash-escaped char. Need to skip over it.
+ stringStart = stringEnd + 1;
+ if (stringStart == flatStr.Length()) return stringStart;
+
+ // Go back to looking for the next escape or the string end
+ continue;
+ }
+
+ return stringEnd;
+
+ } while (true);
+
+ MOZ_ASSERT_UNREACHABLE("How did we get here?");
+ return flatStr.Length();
+}
+
+static uint32_t net_FindMediaDelimiter(const nsCString& flatStr,
+ uint32_t searchStart, char delimiter) {
+ do {
+ // searchStart points to the spot from which we should start looking
+ // for the delimiter.
+ const char delimStr[] = {delimiter, '"', '\0'};
+ uint32_t curDelimPos = flatStr.FindCharInSet(delimStr, searchStart);
+ if (curDelimPos == uint32_t(kNotFound)) return flatStr.Length();
+
+ char ch = flatStr.CharAt(curDelimPos);
+ if (ch == delimiter) {
+ // Found delimiter
+ return curDelimPos;
+ }
+
+ // We hit the start of a quoted string. Look for its end.
+ searchStart = net_FindStringEnd(flatStr, curDelimPos, ch);
+ if (searchStart == flatStr.Length()) return searchStart;
+
+ ++searchStart;
+
+ // searchStart now points to the first char after the end of the
+ // string, so just go back to the top of the loop and look for
+ // |delimiter| again.
+ } while (true);
+
+ MOZ_ASSERT_UNREACHABLE("How did we get here?");
+ return flatStr.Length();
+}
+
+// aOffset should be added to aCharsetStart and aCharsetEnd if this
+// function sets them.
+static void net_ParseMediaType(const nsACString& aMediaTypeStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset, int32_t aOffset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd, bool aStrict) {
+ const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr);
+ const char* start = flatStr.get();
+ const char* end = start + flatStr.Length();
+
+ // Trim LWS leading and trailing whitespace from type.
+ const char* type = net_FindCharNotInSet(start, end, HTTP_LWS);
+ const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";");
+
+ const char* charset = "";
+ const char* charsetEnd = charset;
+ int32_t charsetParamStart = 0;
+ int32_t charsetParamEnd = 0;
+
+ uint32_t consumed = typeEnd - type;
+
+ // Iterate over parameters
+ bool typeHasCharset = false;
+ uint32_t paramStart = flatStr.FindChar(';', typeEnd - start);
+ if (paramStart != uint32_t(kNotFound)) {
+ // We have parameters. Iterate over them.
+ uint32_t curParamStart = paramStart + 1;
+ do {
+ uint32_t curParamEnd =
+ net_FindMediaDelimiter(flatStr, curParamStart, ';');
+
+ const char* paramName = net_FindCharNotInSet(
+ start + curParamStart, start + curParamEnd, HTTP_LWS);
+ static const char charsetStr[] = "charset=";
+ if (nsCRT::strncasecmp(paramName, charsetStr, sizeof(charsetStr) - 1) ==
+ 0) {
+ charset = paramName + sizeof(charsetStr) - 1;
+ charsetEnd = start + curParamEnd;
+ typeHasCharset = true;
+ charsetParamStart = curParamStart - 1;
+ charsetParamEnd = curParamEnd;
+ }
+
+ consumed = curParamEnd;
+ curParamStart = curParamEnd + 1;
+ } while (curParamStart < flatStr.Length());
+ }
+
+ bool charsetNeedsQuotedStringUnescaping = false;
+ if (typeHasCharset) {
+ // Trim LWS leading and trailing whitespace from charset.
+ charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS);
+ if (*charset == '"') {
+ charsetNeedsQuotedStringUnescaping = true;
+ charsetEnd =
+ start + net_FindStringEnd(flatStr, charset - start, *charset);
+ charset++;
+ NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing");
+ } else {
+ charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";");
+ }
+ }
+
+ // if the server sent "*/*", it is meaningless, so do not store it.
+ // also, if type is the same as aContentType, then just update the
+ // charset. however, if charset is empty and aContentType hasn't
+ // changed, then don't wipe-out an existing aContentCharset. We
+ // also want to reject a mime-type if it does not include a slash.
+ // some servers give junk after the charset parameter, which may
+ // include a comma, so this check makes us a bit more tolerant.
+
+ if (type != typeEnd && memchr(type, '/', typeEnd - type) != nullptr &&
+ (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end)
+ : (strncmp(type, "*/*", typeEnd - type) != 0))) {
+ // Common case here is that aContentType is empty
+ bool eq = !aContentType.IsEmpty() &&
+ aContentType.Equals(Substring(type, typeEnd),
+ nsCaseInsensitiveCStringComparator);
+ if (!eq) {
+ aContentType.Assign(type, typeEnd - type);
+ ToLowerCase(aContentType);
+ }
+
+ if ((!eq && *aHadCharset) || typeHasCharset) {
+ *aHadCharset = true;
+ if (charsetNeedsQuotedStringUnescaping) {
+ // parameters using the "quoted-string" syntax need
+ // backslash-escapes to be unescaped (see RFC 2616 Section 2.2)
+ aContentCharset.Truncate();
+ for (const char* c = charset; c != charsetEnd; c++) {
+ if (*c == '\\' && c + 1 != charsetEnd) {
+ // eat escape
+ c++;
+ }
+ aContentCharset.Append(*c);
+ }
+ } else {
+ aContentCharset.Assign(charset, charsetEnd - charset);
+ }
+ if (typeHasCharset) {
+ *aCharsetStart = charsetParamStart + aOffset;
+ *aCharsetEnd = charsetParamEnd + aOffset;
+ }
+ }
+ // Only set a new charset position if this is a different type
+ // from the last one we had and it doesn't already have a
+ // charset param. If this is the same type, we probably want
+ // to leave the charset position on its first occurrence.
+ if (!eq && !typeHasCharset) {
+ int32_t charsetStart = int32_t(paramStart);
+ if (charsetStart == kNotFound) charsetStart = flatStr.Length();
+
+ *aCharsetEnd = *aCharsetStart = charsetStart + aOffset;
+ }
+ }
+}
+
+#undef HTTP_LWS
+
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset) {
+ int32_t dummy1, dummy2;
+ net_ParseContentType(aHeaderStr, aContentType, aContentCharset, aHadCharset,
+ &dummy1, &dummy2);
+}
+
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd) {
+ //
+ // Augmented BNF (from RFC 2616 section 3.7):
+ //
+ // header-value = media-type *( LWS "," LWS media-type )
+ // media-type = type "/" subtype *( LWS ";" LWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = attribute "=" value
+ // attribute = token
+ // value = token | quoted-string
+ //
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html, text/html
+ // text/html,text/html; charset=ISO-8859-1
+ // text/html,text/html; charset="ISO-8859-1"
+ // text/html;charset=ISO-8859-1, text/html
+ // text/html;charset='ISO-8859-1', text/html
+ // application/octet-stream
+ //
+
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // iterate over media-types. Note that ',' characters can happen
+ // inside quoted strings, so we need to watch out for that.
+ uint32_t curTypeStart = 0;
+ do {
+ // curTypeStart points to the start of the current media-type. We want
+ // to look for its end.
+ uint32_t curTypeEnd = net_FindMediaDelimiter(flatStr, curTypeStart, ',');
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ net_ParseMediaType(
+ Substring(flatStr, curTypeStart, curTypeEnd - curTypeStart),
+ aContentType, aContentCharset, curTypeStart, aHadCharset, aCharsetStart,
+ aCharsetEnd, false);
+
+ // And let's move on to the next media-type
+ curTypeStart = curTypeEnd + 1;
+ } while (curTypeStart < flatStr.Length());
+}
+
+void net_ParseRequestContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset,
+ bool* aHadCharset) {
+ //
+ // Augmented BNF (from RFC 7231 section 3.1.1.1):
+ //
+ // media-type = type "/" subtype *( OWS ";" OWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = token "=" ( token / quoted-string )
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html; charset=ISO-8859-1
+ // text/html; charset="ISO-8859-1"
+ // application/octet-stream
+ //
+
+ aContentType.Truncate();
+ aContentCharset.Truncate();
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ nsAutoCString contentType, contentCharset;
+ bool hadCharset = false;
+ int32_t dummy1, dummy2;
+ uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ',');
+ if (typeEnd != flatStr.Length()) {
+ // We have some stuff left at the end, so this is not a valid
+ // request Content-Type header.
+ return;
+ }
+ net_ParseMediaType(flatStr, contentType, contentCharset, 0, &hadCharset,
+ &dummy1, &dummy2, true);
+
+ aContentType = contentType;
+ aContentCharset = contentCharset;
+ *aHadCharset = hadCharset;
+}
+
+bool net_IsValidHostName(const nsACString& host) {
+ // The host name is limited to 253 ascii characters.
+ if (host.Length() > 253) {
+ return false;
+ }
+
+ const char* end = host.EndReading();
+ // Use explicit whitelists to select which characters we are
+ // willing to send to lower-level DNS logic. This is more
+ // self-documenting, and can also be slightly faster than the
+ // blacklist approach, since DNS names are the common case, and
+ // the commonest characters will tend to be near the start of
+ // the list.
+
+ // Whitelist for DNS names (RFC 1035) with extra characters added
+ // for pragmatic reasons "$+_"
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2
+ if (net_FindCharNotInSet(host.BeginReading(), end,
+ "abcdefghijklmnopqrstuvwxyz"
+ ".-0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end) {
+ return true;
+ }
+
+ // Might be a valid IPv6 link-local address containing a percent sign
+ return mozilla::net::HostIsIPLiteral(host);
+}
+
+bool net_IsValidIPv4Addr(const nsACString& aAddr) {
+ return mozilla::net::rust_net_is_valid_ipv4_addr(&aAddr);
+}
+
+bool net_IsValidIPv6Addr(const nsACString& aAddr) {
+ return mozilla::net::rust_net_is_valid_ipv6_addr(&aAddr);
+}
+
+bool net_GetDefaultStatusTextForCode(uint16_t aCode, nsACString& aOutText) {
+ switch (aCode) {
+ // start with the most common
+ case 200:
+ aOutText.AssignLiteral("OK");
+ break;
+ case 404:
+ aOutText.AssignLiteral("Not Found");
+ break;
+ case 301:
+ aOutText.AssignLiteral("Moved Permanently");
+ break;
+ case 304:
+ aOutText.AssignLiteral("Not Modified");
+ break;
+ case 307:
+ aOutText.AssignLiteral("Temporary Redirect");
+ break;
+ case 500:
+ aOutText.AssignLiteral("Internal Server Error");
+ break;
+
+ // also well known
+ case 100:
+ aOutText.AssignLiteral("Continue");
+ break;
+ case 101:
+ aOutText.AssignLiteral("Switching Protocols");
+ break;
+ case 201:
+ aOutText.AssignLiteral("Created");
+ break;
+ case 202:
+ aOutText.AssignLiteral("Accepted");
+ break;
+ case 203:
+ aOutText.AssignLiteral("Non Authoritative");
+ break;
+ case 204:
+ aOutText.AssignLiteral("No Content");
+ break;
+ case 205:
+ aOutText.AssignLiteral("Reset Content");
+ break;
+ case 206:
+ aOutText.AssignLiteral("Partial Content");
+ break;
+ case 207:
+ aOutText.AssignLiteral("Multi-Status");
+ break;
+ case 208:
+ aOutText.AssignLiteral("Already Reported");
+ break;
+ case 300:
+ aOutText.AssignLiteral("Multiple Choices");
+ break;
+ case 302:
+ aOutText.AssignLiteral("Found");
+ break;
+ case 303:
+ aOutText.AssignLiteral("See Other");
+ break;
+ case 305:
+ aOutText.AssignLiteral("Use Proxy");
+ break;
+ case 308:
+ aOutText.AssignLiteral("Permanent Redirect");
+ break;
+ case 400:
+ aOutText.AssignLiteral("Bad Request");
+ break;
+ case 401:
+ aOutText.AssignLiteral("Unauthorized");
+ break;
+ case 402:
+ aOutText.AssignLiteral("Payment Required");
+ break;
+ case 403:
+ aOutText.AssignLiteral("Forbidden");
+ break;
+ case 405:
+ aOutText.AssignLiteral("Method Not Allowed");
+ break;
+ case 406:
+ aOutText.AssignLiteral("Not Acceptable");
+ break;
+ case 407:
+ aOutText.AssignLiteral("Proxy Authentication Required");
+ break;
+ case 408:
+ aOutText.AssignLiteral("Request Timeout");
+ break;
+ case 409:
+ aOutText.AssignLiteral("Conflict");
+ break;
+ case 410:
+ aOutText.AssignLiteral("Gone");
+ break;
+ case 411:
+ aOutText.AssignLiteral("Length Required");
+ break;
+ case 412:
+ aOutText.AssignLiteral("Precondition Failed");
+ break;
+ case 413:
+ aOutText.AssignLiteral("Request Entity Too Large");
+ break;
+ case 414:
+ aOutText.AssignLiteral("Request URI Too Long");
+ break;
+ case 415:
+ aOutText.AssignLiteral("Unsupported Media Type");
+ break;
+ case 416:
+ aOutText.AssignLiteral("Requested Range Not Satisfiable");
+ break;
+ case 417:
+ aOutText.AssignLiteral("Expectation Failed");
+ break;
+ case 418:
+ aOutText.AssignLiteral("I'm a teapot");
+ break;
+ case 421:
+ aOutText.AssignLiteral("Misdirected Request");
+ break;
+ case 422:
+ aOutText.AssignLiteral("Unprocessable Entity");
+ break;
+ case 423:
+ aOutText.AssignLiteral("Locked");
+ break;
+ case 424:
+ aOutText.AssignLiteral("Failed Dependency");
+ break;
+ case 425:
+ aOutText.AssignLiteral("Too Early");
+ break;
+ case 426:
+ aOutText.AssignLiteral("Upgrade Required");
+ break;
+ case 428:
+ aOutText.AssignLiteral("Precondition Required");
+ break;
+ case 429:
+ aOutText.AssignLiteral("Too Many Requests");
+ break;
+ case 431:
+ aOutText.AssignLiteral("Request Header Fields Too Large");
+ break;
+ case 451:
+ aOutText.AssignLiteral("Unavailable For Legal Reasons");
+ break;
+ case 501:
+ aOutText.AssignLiteral("Not Implemented");
+ break;
+ case 502:
+ aOutText.AssignLiteral("Bad Gateway");
+ break;
+ case 503:
+ aOutText.AssignLiteral("Service Unavailable");
+ break;
+ case 504:
+ aOutText.AssignLiteral("Gateway Timeout");
+ break;
+ case 505:
+ aOutText.AssignLiteral("HTTP Version Unsupported");
+ break;
+ case 506:
+ aOutText.AssignLiteral("Variant Also Negotiates");
+ break;
+ case 507:
+ aOutText.AssignLiteral("Insufficient Storage ");
+ break;
+ case 508:
+ aOutText.AssignLiteral("Loop Detected");
+ break;
+ case 510:
+ aOutText.AssignLiteral("Not Extended");
+ break;
+ case 511:
+ aOutText.AssignLiteral("Network Authentication Required");
+ break;
+ default:
+ aOutText.AssignLiteral("No Reason Phrase");
+ return false;
+ }
+ return true;
+}
+
+namespace mozilla {
+static auto MakeNameMatcher(const nsAString& aName) {
+ return [&aName](const auto& param) { return param.mKey.Equals(aName); };
+}
+
+bool URLParams::Has(const nsAString& aName) {
+ return std::any_of(mParams.cbegin(), mParams.cend(), MakeNameMatcher(aName));
+}
+
+bool URLParams::Has(const nsAString& aName, const nsAString& aValue) {
+ return std::any_of(
+ mParams.cbegin(), mParams.cend(), [&aName, &aValue](const auto& param) {
+ return param.mKey.Equals(aName) && param.mValue.Equals(aValue);
+ });
+}
+
+void URLParams::Get(const nsAString& aName, nsString& aRetval) {
+ SetDOMStringToNull(aRetval);
+
+ const auto end = mParams.cend();
+ const auto it = std::find_if(mParams.cbegin(), end, MakeNameMatcher(aName));
+ if (it != end) {
+ aRetval.Assign(it->mValue);
+ }
+}
+
+void URLParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval) {
+ aRetval.Clear();
+
+ for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
+ if (mParams[i].mKey.Equals(aName)) {
+ aRetval.AppendElement(mParams[i].mValue);
+ }
+ }
+}
+
+void URLParams::Append(const nsAString& aName, const nsAString& aValue) {
+ Param* param = mParams.AppendElement();
+ param->mKey = aName;
+ param->mValue = aValue;
+}
+
+void URLParams::Set(const nsAString& aName, const nsAString& aValue) {
+ Param* param = nullptr;
+ for (uint32_t i = 0, len = mParams.Length(); i < len;) {
+ if (!mParams[i].mKey.Equals(aName)) {
+ ++i;
+ continue;
+ }
+ if (!param) {
+ param = &mParams[i];
+ ++i;
+ continue;
+ }
+ // Remove duplicates.
+ mParams.RemoveElementAt(i);
+ --len;
+ }
+
+ if (!param) {
+ param = mParams.AppendElement();
+ param->mKey = aName;
+ }
+
+ param->mValue = aValue;
+}
+
+void URLParams::Delete(const nsAString& aName) {
+ mParams.RemoveElementsBy(
+ [&aName](const auto& param) { return param.mKey.Equals(aName); });
+}
+
+void URLParams::Delete(const nsAString& aName, const nsAString& aValue) {
+ mParams.RemoveElementsBy([&aName, &aValue](const auto& param) {
+ return param.mKey.Equals(aName) && param.mValue.Equals(aValue);
+ });
+}
+
+/* static */
+void URLParams::ConvertString(const nsACString& aInput, nsAString& aOutput) {
+ if (NS_FAILED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aInput, aOutput))) {
+ MOZ_CRASH("Out of memory when converting URL params.");
+ }
+}
+
+/* static */
+void URLParams::DecodeString(const nsACString& aInput, nsAString& aOutput) {
+ const char* const end = aInput.EndReading();
+
+ nsAutoCString unescaped;
+
+ for (const char* iter = aInput.BeginReading(); iter != end;) {
+ // replace '+' with U+0020
+ if (*iter == '+') {
+ unescaped.Append(' ');
+ ++iter;
+ continue;
+ }
+
+ // Percent decode algorithm
+ if (*iter == '%') {
+ const char* const first = iter + 1;
+ const char* const second = first + 1;
+
+ const auto asciiHexDigit = [](char x) {
+ return (x >= 0x41 && x <= 0x46) || (x >= 0x61 && x <= 0x66) ||
+ (x >= 0x30 && x <= 0x39);
+ };
+
+ const auto hexDigit = [](char x) {
+ return x >= 0x30 && x <= 0x39
+ ? x - 0x30
+ : (x >= 0x41 && x <= 0x46 ? x - 0x37 : x - 0x57);
+ };
+
+ if (first != end && second != end && asciiHexDigit(*first) &&
+ asciiHexDigit(*second)) {
+ unescaped.Append(hexDigit(*first) * 16 + hexDigit(*second));
+ iter = second + 1;
+ } else {
+ unescaped.Append('%');
+ ++iter;
+ }
+
+ continue;
+ }
+
+ unescaped.Append(*iter);
+ ++iter;
+ }
+
+ // XXX It seems rather wasteful to first decode into a UTF-8 nsCString and
+ // then convert the whole string to UTF-16, at least if we exceed the inline
+ // storage size.
+ ConvertString(unescaped, aOutput);
+}
+
+/* static */
+bool URLParams::ParseNextInternal(const char*& aStart, const char* const aEnd,
+ nsAString* aOutDecodedName,
+ nsAString* aOutDecodedValue) {
+ nsDependentCSubstring string;
+
+ const char* const iter = std::find(aStart, aEnd, '&');
+ if (iter != aEnd) {
+ string.Rebind(aStart, iter);
+ aStart = iter + 1;
+ } else {
+ string.Rebind(aStart, aEnd);
+ aStart = aEnd;
+ }
+
+ if (string.IsEmpty()) {
+ return false;
+ }
+
+ const auto* const eqStart = string.BeginReading();
+ const auto* const eqEnd = string.EndReading();
+ const auto* const eqIter = std::find(eqStart, eqEnd, '=');
+
+ nsDependentCSubstring name;
+ nsDependentCSubstring value;
+
+ if (eqIter != eqEnd) {
+ name.Rebind(eqStart, eqIter);
+ value.Rebind(eqIter + 1, eqEnd);
+ } else {
+ name.Rebind(string, 0);
+ }
+
+ DecodeString(name, *aOutDecodedName);
+ DecodeString(value, *aOutDecodedValue);
+
+ return true;
+}
+
+/* static */
+bool URLParams::Extract(const nsACString& aInput, const nsAString& aName,
+ nsAString& aValue) {
+ aValue.SetIsVoid(true);
+ return !URLParams::Parse(
+ aInput, [&aName, &aValue](const nsAString& name, nsString&& value) {
+ if (aName == name) {
+ aValue = std::move(value);
+ return false;
+ }
+ return true;
+ });
+}
+
+void URLParams::ParseInput(const nsACString& aInput) {
+ // Remove all the existing data before parsing a new input.
+ DeleteAll();
+
+ URLParams::Parse(aInput, [this](nsString&& name, nsString&& value) {
+ mParams.AppendElement(Param{std::move(name), std::move(value)});
+ return true;
+ });
+}
+
+namespace {
+
+void SerializeString(const nsCString& aInput, nsAString& aValue) {
+ const unsigned char* p = (const unsigned char*)aInput.get();
+ const unsigned char* end = p + aInput.Length();
+
+ while (p != end) {
+ // ' ' to '+'
+ if (*p == 0x20) {
+ aValue.Append(0x2B);
+ // Percent Encode algorithm
+ } else if (*p == 0x2A || *p == 0x2D || *p == 0x2E ||
+ (*p >= 0x30 && *p <= 0x39) || (*p >= 0x41 && *p <= 0x5A) ||
+ *p == 0x5F || (*p >= 0x61 && *p <= 0x7A)) {
+ aValue.Append(*p);
+ } else {
+ aValue.AppendPrintf("%%%.2X", *p);
+ }
+
+ ++p;
+ }
+}
+
+} // namespace
+
+void URLParams::Serialize(nsAString& aValue, bool aEncode) const {
+ aValue.Truncate();
+ bool first = true;
+
+ for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
+ if (first) {
+ first = false;
+ } else {
+ aValue.Append('&');
+ }
+
+ // XXX Actually, it's not necessary to build a new string object. Generally,
+ // such cases could just convert each codepoint one-by-one.
+ if (aEncode) {
+ SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mKey), aValue);
+ aValue.Append('=');
+ SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mValue), aValue);
+ } else {
+ aValue.Append(mParams[i].mKey);
+ aValue.Append('=');
+ aValue.Append(mParams[i].mValue);
+ }
+ }
+}
+
+void URLParams::Sort() {
+ mParams.StableSort([](const Param& lhs, const Param& rhs) {
+ return Compare(lhs.mKey, rhs.mKey);
+ });
+}
+
+} // namespace mozilla
diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h
new file mode 100644
index 0000000000..f5ccc8bac6
--- /dev/null
+++ b/netwerk/base/nsURLHelper.h
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLHelper_h__
+#define nsURLHelper_h__
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsASCIIMask.h"
+
+class nsIFile;
+class nsIURLParser;
+
+enum netCoalesceFlags {
+ NET_COALESCE_NORMAL = 0,
+
+ /**
+ * retains /../ that reach above dir root (useful for FTP
+ * servers in which the root of the FTP URL is not necessarily
+ * the root of the FTP filesystem).
+ */
+ NET_COALESCE_ALLOW_RELATIVE_ROOT = 1 << 0,
+
+ /**
+ * recognizes /%2F and // as markers for the root directory
+ * and handles them properly.
+ */
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT = 1 << 1
+};
+
+//----------------------------------------------------------------------------
+// This module contains some private helper functions related to URL parsing.
+//----------------------------------------------------------------------------
+
+/* shutdown frees URL parser */
+void net_ShutdownURLHelper();
+#ifdef XP_MACOSX
+void net_ShutdownURLHelperOSX();
+#endif
+
+/* access URL parsers */
+nsIURLParser* net_GetAuthURLParser();
+nsIURLParser* net_GetNoAuthURLParser();
+nsIURLParser* net_GetStdURLParser();
+
+/* convert between nsIFile and file:// URL spec
+ * net_GetURLSpecFromFile does an extra stat, so callers should
+ * avoid it if possible in favor of net_GetURLSpecFromActualFile
+ * and net_GetURLSpecFromDir */
+nsresult net_GetURLSpecFromFile(nsIFile*, nsACString&);
+nsresult net_GetURLSpecFromDir(nsIFile*, nsACString&);
+nsresult net_GetURLSpecFromActualFile(nsIFile*, nsACString&);
+nsresult net_GetFileFromURLSpec(const nsACString&, nsIFile**);
+
+/* extract file path components from file:// URL */
+nsresult net_ParseFileURL(const nsACString& inURL, nsACString& outDirectory,
+ nsACString& outFileBaseName,
+ nsACString& outFileExtension);
+
+/* handle .. in dirs while resolving URLs (path is UTF-8) */
+void net_CoalesceDirs(netCoalesceFlags flags, char* path);
+
+/**
+ * Check if a URL is absolute
+ *
+ * @param inURL URL spec
+ * @return true if the given spec represents an absolute URL
+ */
+bool net_IsAbsoluteURL(const nsACString& uri);
+
+/**
+ * Extract URI-Scheme if possible
+ *
+ * @param inURI URI spec
+ * @param scheme scheme copied to this buffer on return. Is lowercase.
+ */
+nsresult net_ExtractURLScheme(const nsACString& inURI, nsACString& scheme);
+
+/* check that the given scheme conforms to RFC 2396 */
+bool net_IsValidScheme(const nsACString& scheme);
+
+/**
+ * This function strips out all C0 controls and space at the beginning and end
+ * of the URL and filters out \r, \n, \t from the middle of the URL. This makes
+ * it safe to call on things like javascript: urls or data: urls, where we may
+ * in fact run into whitespace that is not properly encoded.
+ *
+ * @param input the URL spec we want to filter
+ * @param result the out param to write to if filtering happens
+ */
+void net_FilterURIString(const nsACString& input, nsACString& result);
+
+/**
+ * This function performs character stripping just like net_FilterURIString,
+ * with the added benefit of also performing percent escaping of dissallowed
+ * characters, all in one pass. Saving one pass is very important when operating
+ * on really large strings.
+ *
+ * @param aInput the URL spec we want to filter
+ * @param aFlags the flags which control which characters we escape
+ * @param aFilterMask a mask of characters that should excluded from the result
+ * @param aResult the out param to write to if filtering happens
+ */
+nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags,
+ const ASCIIMaskArray& aFilterMask,
+ nsACString& aResult);
+
+#if defined(XP_WIN)
+/**
+ * On Win32 and OS/2 system's a back-slash in a file:// URL is equivalent to a
+ * forward-slash. This function maps any back-slashes to forward-slashes.
+ *
+ * @param aURL
+ * The URL string to normalize (UTF-8 encoded). This can be a
+ * relative URL segment.
+ * @param aResultBuf
+ * The resulting string is appended to this string. If the input URL
+ * is already normalized, then aResultBuf is unchanged.
+ *
+ * @returns false if aURL is already normalized. Otherwise, returns true.
+ */
+bool net_NormalizeFileURL(const nsACString& aURL, nsCString& aResultBuf);
+#endif
+
+/*****************************************************************************
+ * generic string routines follow (XXX move to someplace more generic).
+ */
+
+/* convert to lower case */
+void net_ToLowerCase(char* str, uint32_t length);
+void net_ToLowerCase(char* str);
+
+/**
+ * returns pointer to first character of |str| in the given set. if not found,
+ * then |end| is returned. stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char* net_FindCharInSet(const char* iter, const char* stop, const char* set);
+
+/**
+ * returns pointer to first character of |str| NOT in the given set. if all
+ * characters are in the given set, then |end| is returned. if '\0' is not
+ * included in |set|, then stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char* net_FindCharNotInSet(const char* iter, const char* stop, const char* set);
+
+/**
+ * returns pointer to last character of |str| NOT in the given set. if all
+ * characters are in the given set, then |str - 1| is returned.
+ */
+char* net_RFindCharNotInSet(const char* stop, const char* iter,
+ const char* set);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ *
+ * This parsing is suitable for HTTP request. Use net_ParseContentType
+ * for parsing this header in HTTP responses.
+ */
+void net_ParseRequestContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset,
+ bool* aHadCharset);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ */
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset);
+/**
+ * As above, but also returns the start and end indexes for the charset
+ * parameter in aHeaderStr. These are indices for the entire parameter, NOT
+ * just the value. If there is "effectively" no charset parameter (e.g. if an
+ * earlier type with one is overridden by a later type without one),
+ * *aHadCharset will be true but *aCharsetStart will be set to -1. Note that
+ * it's possible to have aContentCharset empty and *aHadCharset true when
+ * *aCharsetStart is nonnegative; this corresponds to charset="".
+ */
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd);
+
+/* inline versions */
+
+/* remember the 64-bit platforms ;-) */
+#define NET_MAX_ADDRESS ((char*)UINTPTR_MAX)
+
+inline char* net_FindCharInSet(const char* str, const char* set) {
+ return net_FindCharInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char* net_FindCharNotInSet(const char* str, const char* set) {
+ return net_FindCharNotInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char* net_RFindCharNotInSet(const char* str, const char* set) {
+ return net_RFindCharNotInSet(str, str + strlen(str), set);
+}
+
+/**
+ * This function returns true if the given hostname does not include any
+ * restricted characters. Otherwise, false is returned.
+ */
+bool net_IsValidHostName(const nsACString& host);
+
+/**
+ * Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv4Addr(const nsACString& aAddr);
+
+/**
+ * Checks whether the IPv6 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv6Addr(const nsACString& aAddr);
+
+/**
+ * Returns the default status text for a given HTTP status code (useful if HTTP2
+ * does not provide one, for instance).
+ */
+bool net_GetDefaultStatusTextForCode(uint16_t aCode, nsACString& aOutText);
+
+namespace mozilla {
+/**
+ * A class for handling form-urlencoded query strings.
+ *
+ * Manages an ordered list of name-value pairs, and allows conversion from and
+ * to the string representation.
+ *
+ * In addition, there are static functions for handling one-shot use cases.
+ */
+class URLParams final {
+ public:
+ /**
+ * \brief Parses a query string and calls a parameter handler for each
+ * name/value pair. The parameter handler can stop processing early by
+ * returning false.
+ *
+ * \param aInput the query string to parse
+ * \param aParamHandler the parameter handler as desribed above
+ * \tparam ParamHandler a function type compatible with signature
+ * bool(nsString, nsString)
+ *
+ * \return false if the parameter handler returned false for any parameter,
+ * true otherwise
+ */
+ template <typename ParamHandler>
+ static bool Parse(const nsACString& aInput, ParamHandler aParamHandler) {
+ const char* start = aInput.BeginReading();
+ const char* const end = aInput.EndReading();
+
+ while (start != end) {
+ nsAutoString decodedName;
+ nsAutoString decodedValue;
+
+ if (!ParseNextInternal(start, end, &decodedName, &decodedValue)) {
+ continue;
+ }
+
+ if (!aParamHandler(std::move(decodedName), std::move(decodedValue))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * \brief Parses a query string and returns the value of a single parameter
+ * specified by name.
+ *
+ * If there are multiple parameters with the same name, the value of the first
+ * is returned.
+ *
+ * \param aInput the query string to parse
+ * \param aName the name of the parameter to extract
+ * \param[out] aValue will be assigned the parameter value, set to void if
+ * there is no match \return true iff there was a parameter with with name
+ * \paramref aName
+ */
+ static bool Extract(const nsACString& aInput, const nsAString& aName,
+ nsAString& aValue);
+
+ /**
+ * \brief Resets the state of this instance and parses a new query string.
+ *
+ * \param aInput the query string to parse
+ */
+ void ParseInput(const nsACString& aInput);
+
+ /**
+ * Serializes the current state to a query string.
+ *
+ * \param[out] aValue will be assigned the result of the serialization
+ * \param aEncode If this is true, the serialization will encode the string.
+ */
+ void Serialize(nsAString& aValue, bool aEncode) const;
+
+ void Get(const nsAString& aName, nsString& aRetval);
+
+ void GetAll(const nsAString& aName, nsTArray<nsString>& aRetval);
+
+ /**
+ * \brief Sets the value of a given parameter.
+ *
+ * If one or more parameters of the name exist, the value of the first is
+ * replaced, and all further parameters of the name are deleted. Otherwise,
+ * the behaviour is the same as \ref Append.
+ */
+ void Set(const nsAString& aName, const nsAString& aValue);
+
+ void Append(const nsAString& aName, const nsAString& aValue);
+
+ bool Has(const nsAString& aName);
+
+ bool Has(const nsAString& aName, const nsAString& aValue);
+
+ /**
+ * \brief Deletes all parameters with the given name.
+ */
+ void Delete(const nsAString& aName);
+
+ void Delete(const nsAString& aName, const nsAString& aValue);
+
+ void DeleteAll() { mParams.Clear(); }
+
+ uint32_t Length() const { return mParams.Length(); }
+
+ const nsAString& GetKeyAtIndex(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mParams.Length());
+ return mParams[aIndex].mKey;
+ }
+
+ const nsAString& GetValueAtIndex(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mParams.Length());
+ return mParams[aIndex].mValue;
+ }
+
+ /**
+ * \brief Performs a stable sort of the parameters, maintaining the order of
+ * multiple parameters with the same name.
+ */
+ void Sort();
+
+ private:
+ static void DecodeString(const nsACString& aInput, nsAString& aOutput);
+ static void ConvertString(const nsACString& aInput, nsAString& aOutput);
+ static bool ParseNextInternal(const char*& aStart, const char* aEnd,
+ nsAString* aOutDecodedName,
+ nsAString* aOutDecodedValue);
+
+ struct Param {
+ nsString mKey;
+ nsString mValue;
+ };
+
+ nsTArray<Param> mParams;
+};
+} // namespace mozilla
+
+#endif // !nsURLHelper_h__
diff --git a/netwerk/base/nsURLHelperOSX.cpp b/netwerk/base/nsURLHelperOSX.cpp
new file mode 100644
index 0000000000..9a8fbad9a2
--- /dev/null
+++ b/netwerk/base/nsURLHelperOSX.cpp
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Mac OS X-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include <Carbon/Carbon.h>
+
+static nsTArray<nsCString>* gVolumeList = nullptr;
+
+static bool pathBeginsWithVolName(const nsACString& path,
+ nsACString& firstPathComponent) {
+ // Return whether the 1st path component in path (escaped) is equal to the
+ // name of a mounted volume. Return the 1st path component (unescaped) in any
+ // case. This needs to be done as quickly as possible, so we cache a list of
+ // volume names.
+ // XXX Register an event handler to detect drives being mounted/unmounted?
+
+ if (!gVolumeList) {
+ gVolumeList = new nsTArray<nsCString>;
+ if (!gVolumeList) {
+ return false; // out of memory
+ }
+ }
+
+ // Cache a list of volume names
+ if (!gVolumeList->Length()) {
+ OSErr err;
+ ItemCount volumeIndex = 1;
+
+ do {
+ HFSUniStr255 volName;
+ FSRef rootDirectory;
+ err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr,
+ &volName, &rootDirectory);
+ if (err == noErr) {
+ NS_ConvertUTF16toUTF8 volNameStr(
+ Substring((char16_t*)volName.unicode,
+ (char16_t*)volName.unicode + volName.length));
+ gVolumeList->AppendElement(volNameStr);
+ volumeIndex++;
+ }
+ } while (err == noErr);
+ }
+
+ // Extract the first component of the path
+ nsACString::const_iterator start;
+ path.BeginReading(start);
+ start.advance(1); // path begins with '/'
+ nsACString::const_iterator directory_end;
+ path.EndReading(directory_end);
+ nsACString::const_iterator component_end(start);
+ FindCharInReadable('/', component_end, directory_end);
+
+ nsAutoCString flatComponent((Substring(start, component_end)));
+ NS_UnescapeURL(flatComponent);
+ int32_t foundIndex = gVolumeList->IndexOf(flatComponent);
+ firstPathComponent = flatComponent;
+ return (foundIndex != -1);
+}
+
+void net_ShutdownURLHelperOSX() {
+ delete gVolumeList;
+ gVolumeList = nullptr;
+}
+
+static nsresult convertHFSPathtoPOSIX(const nsACString& hfsPath,
+ nsACString& posixPath) {
+ // Use CFURL to do the conversion. We don't want to do this by simply
+ // using SwapSlashColon - we need the charset mapped from MacRoman
+ // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject
+ // to change) prepended if the path is not on the boot drive.
+
+ CFStringRef pathStrRef = CFStringCreateWithCString(
+ nullptr, PromiseFlatCString(hfsPath).get(), kCFStringEncodingMacRoman);
+ if (!pathStrRef) return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr, pathStrRef,
+ kCFURLHFSPathStyle, true);
+ if (urlRef) {
+ UInt8 pathBuf[PATH_MAX];
+ if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf,
+ sizeof(pathBuf))) {
+ posixPath = (char*)pathBuf;
+ rv = NS_OK;
+ }
+ }
+ CFRelease(pathStrRef);
+ if (urlRef) CFRelease(urlRef);
+ return rv;
+}
+
+static void SwapSlashColon(char* s) {
+ while (*s) {
+ if (*s == '/')
+ *s = ':';
+ else if (*s == ':')
+ *s = '/';
+ s++;
+ }
+}
+
+nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) {
+ // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp
+
+ nsresult rv;
+ nsAutoCString ePath;
+
+ // construct URL spec from native file path
+ rv = aFile->GetNativePath(ePath);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escPath;
+ constexpr auto prefix = "file://"_ns;
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory + esc_Forced,
+ escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) {
+ // NOTE: See also the implementation in nsURLHelperUnix.cpp
+ // This matches it except for the HFS path handling.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+ bool bHFSPath = false;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path);
+
+ // The canonical form of file URLs on OSX use POSIX paths:
+ // file:///path-name.
+ // But, we still encounter file URLs that use HFS paths:
+ // file:///volume-name/path-name
+ // Determine that here and normalize HFS paths to POSIX.
+ nsAutoCString possibleVolName;
+ if (pathBeginsWithVolName(directory, possibleVolName)) {
+ // Though we know it begins with a volume name, it could still
+ // be a valid POSIX path if the boot drive is named "Mac HD"
+ // and there is a directory "Mac HD" at its root. If such a
+ // directory doesn't exist, we'll assume this is an HFS path.
+ FSRef testRef;
+ possibleVolName.InsertLiteral("/", 0);
+ if (::FSPathMakeRef((UInt8*)possibleVolName.get(), &testRef, nullptr) !=
+ noErr)
+ bHFSPath = true;
+ }
+
+ if (bHFSPath) {
+ // "%2F"s need to become slashes, while all other slashes need to
+ // become colons. If we start out by changing "%2F"s to colons, we
+ // can reply on SwapSlashColon() to do what we need
+ path.ReplaceSubstring("%2F", ":");
+ path.Cut(0, 1); // directory begins with '/'
+ SwapSlashColon((char*)path.get());
+ // At this point, path is an HFS path made using the same
+ // algorithm as nsURLHelperMac. We'll convert to POSIX below.
+ }
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH;
+
+ if (bHFSPath) convertHFSPathtoPOSIX(path, path);
+
+ // assuming path is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperUnix.cpp b/netwerk/base/nsURLHelperUnix.cpp
new file mode 100644
index 0000000000..abb8b0cfeb
--- /dev/null
+++ b/netwerk/base/nsURLHelperUnix.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Unix-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Utf8.h"
+
+using mozilla::IsUtf8;
+
+nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) {
+ nsresult rv;
+ nsAutoCString nativePath, ePath;
+ nsAutoString path;
+
+ rv = aFile->GetNativePath(nativePath);
+ if (NS_FAILED(rv)) return rv;
+
+ // Convert to unicode and back to check correct conversion to native charset
+ NS_CopyNativeToUnicode(nativePath, path);
+ NS_CopyUnicodeToNative(path, ePath);
+
+ // Use UTF8 version if conversion was successful
+ if (nativePath == ePath) {
+ CopyUTF16toUTF8(path, ePath);
+ } else {
+ ePath = nativePath;
+ }
+
+ nsAutoCString escPath;
+ constexpr auto prefix = "file://"_ns;
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory + esc_Forced, escPath)) {
+ escPath.Insert(prefix, 0);
+ } else {
+ escPath.Assign(prefix + ePath);
+ }
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) {
+ // NOTE: See also the implementation in nsURLHelperOSX.cpp,
+ // which is based on this.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!directory.IsEmpty()) {
+ rv = NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!fileBaseName.IsEmpty()) {
+ rv = NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ rv = NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH;
+
+ if (IsUtf8(path)) {
+ // speed up the start-up where UTF-8 is the native charset
+ // (e.g. on recent Linux distributions)
+ if (NS_IsNativeUTF8()) {
+ rv = localFile->InitWithNativePath(path);
+ } else {
+ rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ }
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ } else {
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperWin.cpp b/netwerk/base/nsURLHelperWin.cpp
new file mode 100644
index 0000000000..7a4f53978e
--- /dev/null
+++ b/netwerk/base/nsURLHelperWin.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Windows-specific local file uri parsing */
+#include "nsComponentManagerUtils.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include <windows.h>
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Utf8.h"
+
+using namespace mozilla;
+
+nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) {
+ nsresult rv;
+ nsAutoString path;
+
+ // construct URL spec from file path
+ rv = aFile->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // Replace \ with / to convert to an url
+ path.ReplaceChar(char16_t(0x5Cu), char16_t(0x2Fu));
+
+ nsAutoCString escPath;
+
+ // Windows Desktop paths begin with a drive letter, so need an 'extra'
+ // slash at the begining
+ // C:\Windows => file:///C:/Windows
+ constexpr auto prefix = "file:///"_ns;
+
+ // Escape the path with the directory mask
+ NS_ConvertUTF16toUTF8 ePath(path);
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory + esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) {
+ nsresult rv;
+
+ if (aURL.Length() > StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsCOMPtr<nsIFile> localFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Only nsIFile supported right now");
+ return rv;
+ }
+
+ const nsACString* specPtr;
+
+ nsAutoCString buf;
+ if (net_NormalizeFileURL(aURL, buf))
+ specPtr = &buf;
+ else
+ specPtr = &aURL;
+
+ nsAutoCString directory, fileBaseName, fileExtension;
+
+ rv = net_ParseFileURL(*specPtr, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString path;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path);
+ if (path.Length() > 2 && path.CharAt(2) == '|') path.SetCharAt(':', 2);
+ path.ReplaceChar('/', '\\');
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH;
+
+ // remove leading '\'
+ if (path.CharAt(0) == '\\') path.Cut(0, 1);
+
+ if (IsUtf8(path)) rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ else
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLParsers.cpp b/netwerk/base/nsURLParsers.cpp
new file mode 100644
index 0000000000..7dc6567d84
--- /dev/null
+++ b/netwerk/base/nsURLParsers.cpp
@@ -0,0 +1,644 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "mozilla/RangedPtr.h"
+#include "mozilla/TextUtils.h"
+
+#include "nsCRTGlue.h"
+#include "nsURLParsers.h"
+#include "nsURLHelper.h"
+#include "nsString.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+
+static uint32_t CountConsecutiveSlashes(const char* str, int32_t len) {
+ RangedPtr<const char> p(str, len);
+ uint32_t count = 0;
+ while (len-- && *p++ == '/') ++count;
+ return count;
+}
+
+//----------------------------------------------------------------------------
+// nsBaseURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsAuthURLParser, nsIURLParser)
+NS_IMPL_ISUPPORTS(nsNoAuthURLParser, nsIURLParser)
+
+#define SET_RESULT(component, pos, len) \
+ PR_BEGIN_MACRO \
+ if (component##Pos) *component##Pos = uint32_t(pos); \
+ if (component##Len) *component##Len = int32_t(len); \
+ PR_END_MACRO
+
+#define OFFSET_RESULT(component, offset) \
+ PR_BEGIN_MACRO \
+ if (component##Pos) *component##Pos += (offset); \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseURL(const char* spec, int32_t specLen,
+ uint32_t* schemePos, int32_t* schemeLen,
+ uint32_t* authorityPos, int32_t* authorityLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ if (NS_WARN_IF(!spec)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (specLen < 0) specLen = strlen(spec);
+
+ const char* stop = nullptr;
+ const char* colon = nullptr;
+ const char* slash = nullptr;
+ const char* p = spec;
+ uint32_t offset = 0;
+ int32_t len = specLen;
+
+ // skip leading whitespace
+ while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') {
+ spec++;
+ specLen--;
+ offset++;
+
+ p++;
+ len--;
+ }
+
+ for (; len && *p && !colon && !slash; ++p, --len) {
+ switch (*p) {
+ case ':':
+ if (!colon) colon = p;
+ break;
+ case '/': // start of filepath
+ case '?': // start of query
+ case '#': // start of ref
+ if (!slash) slash = p;
+ break;
+ case '@': // username@hostname
+ case '[': // start of IPv6 address literal
+ if (!stop) stop = p;
+ break;
+ }
+ }
+ // disregard the first colon if it follows an '@' or a '['
+ if (colon && stop && colon > stop) colon = nullptr;
+
+ // if the spec only contained whitespace ...
+ if (specLen == 0) {
+ SET_RESULT(scheme, 0, -1);
+ SET_RESULT(authority, 0, 0);
+ SET_RESULT(path, 0, 0);
+ return NS_OK;
+ }
+
+ // ignore trailing whitespace and control characters
+ for (p = spec + specLen - 1; ((unsigned char)*p <= ' ') && (p != spec); --p) {
+ ;
+ }
+
+ specLen = p - spec + 1;
+
+ if (colon && (colon < slash || !slash)) {
+ //
+ // spec = <scheme>:/<the-rest>
+ //
+ // or
+ //
+ // spec = <scheme>:<authority>
+ // spec = <scheme>:<path-no-slashes>
+ //
+ if (!net_IsValidScheme(nsDependentCSubstring(spec, colon - spec))) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ SET_RESULT(scheme, offset, colon - spec);
+ if (authorityLen || pathLen) {
+ uint32_t schemeLen = colon + 1 - spec;
+ offset += schemeLen;
+ ParseAfterScheme(colon + 1, specLen - schemeLen, authorityPos,
+ authorityLen, pathPos, pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ } else {
+ //
+ // spec = <authority-no-port-or-password>/<path>
+ // spec = <path>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>/<path-with-colon>
+ // spec = <path-with-colon>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>
+ // spec = <path-no-slashes-or-colon>
+ //
+ SET_RESULT(scheme, 0, -1);
+ if (authorityLen || pathLen) {
+ ParseAfterScheme(spec, specLen, authorityPos, authorityLen, pathPos,
+ pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0) authLen = strlen(auth);
+
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, authLen);
+ if (port) *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseUserInfo(const char* userinfo, int32_t userinfoLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseServerInfo(const char* serverinfo, int32_t serverinfoLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ SET_RESULT(hostname, 0, -1);
+ if (port) *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParsePath(const char* path, int32_t pathLen,
+ uint32_t* filepathPos, int32_t* filepathLen,
+ uint32_t* queryPos, int32_t* queryLen,
+ uint32_t* refPos, int32_t* refLen) {
+ if (NS_WARN_IF(!path)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (pathLen < 0) pathLen = strlen(path);
+
+ // path = [/]<segment1>/<segment2>/<...>/<segmentN>?<query>#<ref>
+
+ // XXX PL_strnpbrk would be nice, but it's buggy
+
+ // search for first occurrence of either ? or #
+ const char *query_beg = nullptr, *query_end = nullptr;
+ const char* ref_beg = nullptr;
+ const char* p = nullptr;
+ for (p = path; p < path + pathLen; ++p) {
+ // only match the query string if it precedes the reference fragment
+ if (!ref_beg && !query_beg && *p == '?') {
+ query_beg = p + 1;
+ } else if (*p == '#') {
+ ref_beg = p + 1;
+ if (query_beg) query_end = p;
+ break;
+ }
+ }
+
+ if (query_beg) {
+ if (query_end) {
+ SET_RESULT(query, query_beg - path, query_end - query_beg);
+ } else {
+ SET_RESULT(query, query_beg - path, pathLen - (query_beg - path));
+ }
+ } else {
+ SET_RESULT(query, 0, -1);
+ }
+
+ if (ref_beg) {
+ SET_RESULT(ref, ref_beg - path, pathLen - (ref_beg - path));
+ } else {
+ SET_RESULT(ref, 0, -1);
+ }
+
+ const char* end;
+ if (query_beg) {
+ end = query_beg - 1;
+ } else if (ref_beg) {
+ end = ref_beg - 1;
+ } else {
+ end = path + pathLen;
+ }
+
+ // an empty file path is no file path
+ if (end != path) {
+ SET_RESULT(filepath, 0, end - path);
+ } else {
+ SET_RESULT(filepath, 0, -1);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseFilePath(const char* filepath, int32_t filepathLen,
+ uint32_t* directoryPos, int32_t* directoryLen,
+ uint32_t* basenamePos, int32_t* basenameLen,
+ uint32_t* extensionPos, int32_t* extensionLen) {
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0) filepathLen = strlen(filepath);
+
+ if (filepathLen == 0) {
+ SET_RESULT(directory, 0, -1);
+ SET_RESULT(basename, 0, 0); // assume a zero length file basename
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+
+ const char* p;
+ const char* end = filepath + filepathLen;
+
+ // search backwards for filename
+ for (p = end - 1; *p != '/' && p > filepath; --p) {
+ ;
+ }
+ if (*p == '/') {
+ // catch /.. and /.
+ if ((p + 1 < end && *(p + 1) == '.') &&
+ (p + 2 == end || (*(p + 2) == '.' && p + 3 == end))) {
+ p = end - 1;
+ }
+ // filepath = <directory><filename>.<extension>
+ SET_RESULT(directory, 0, p - filepath + 1);
+ ParseFileName(p + 1, end - (p + 1), basenamePos, basenameLen, extensionPos,
+ extensionLen);
+ OFFSET_RESULT(basename, p + 1 - filepath);
+ OFFSET_RESULT(extension, p + 1 - filepath);
+ } else {
+ // filepath = <filename>.<extension>
+ SET_RESULT(directory, 0, -1);
+ ParseFileName(filepath, filepathLen, basenamePos, basenameLen, extensionPos,
+ extensionLen);
+ }
+ return NS_OK;
+}
+
+nsresult nsBaseURLParser::ParseFileName(
+ const char* filename, int32_t filenameLen, uint32_t* basenamePos,
+ int32_t* basenameLen, uint32_t* extensionPos, int32_t* extensionLen) {
+ if (NS_WARN_IF(!filename)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filenameLen < 0) filenameLen = strlen(filename);
+
+ // no extension if filename ends with a '.'
+ if (filename[filenameLen - 1] != '.') {
+ // ignore '.' at the beginning
+ for (const char* p = filename + filenameLen - 1; p > filename; --p) {
+ if (*p == '.') {
+ // filename = <basename.extension>
+ SET_RESULT(basename, 0, p - filename);
+ SET_RESULT(extension, p + 1 - filename,
+ filenameLen - (p - filename + 1));
+ return NS_OK;
+ }
+ }
+ }
+ // filename = <basename>
+ SET_RESULT(basename, 0, filenameLen);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsNoAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ MOZ_ASSERT_UNREACHABLE("Shouldn't parse auth in a NoAuthURL!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsNoAuthURLParser::ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ MOZ_ASSERT(specLen >= 0, "unexpected");
+
+ // everything is the path
+ uint32_t pos = 0;
+ switch (CountConsecutiveSlashes(spec, specLen)) {
+ case 0:
+ case 1:
+ break;
+ case 2: {
+ const char* p = nullptr;
+ if (specLen > 2) {
+ // looks like there is an authority section
+
+ // if the authority looks like a drive number then we
+ // really want to treat it as part of the path
+ // [a-zA-Z][:|]{/\}
+ // i.e one of: c: c:\foo c:/foo c| c|\foo c|/foo
+ if ((specLen > 3) && (spec[3] == ':' || spec[3] == '|') &&
+ IsAsciiAlpha(spec[2]) &&
+ ((specLen == 4) || (spec[4] == '/') || (spec[4] == '\\'))) {
+ pos = 1;
+ break;
+ }
+ // Ignore apparent authority; path is everything after it
+ for (p = spec + 2; p < spec + specLen; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#') break;
+ }
+ }
+ SET_RESULT(auth, 0, -1);
+ if (p && p != spec + specLen) {
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ } else {
+ SET_RESULT(path, 0, -1);
+ }
+ return;
+ }
+ default:
+ pos = 2;
+ break;
+ }
+ SET_RESULT(auth, pos, 0);
+ SET_RESULT(path, pos, specLen - pos);
+}
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseFilePath(const char* filepath, int32_t filepathLen,
+ uint32_t* directoryPos, int32_t* directoryLen,
+ uint32_t* basenamePos, int32_t* basenameLen,
+ uint32_t* extensionPos,
+ int32_t* extensionLen) {
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0) filepathLen = strlen(filepath);
+
+ // look for a filepath consisting of only a drive number, which may or
+ // may not have a leading slash.
+ if (filepathLen > 1 && filepathLen < 4) {
+ const char* end = filepath + filepathLen;
+ const char* p = filepath;
+ if (*p == '/') p++;
+ if ((end - p == 2) && (p[1] == ':' || p[1] == '|') && IsAsciiAlpha(*p)) {
+ // filepath = <drive-number>:
+ SET_RESULT(directory, 0, filepathLen);
+ SET_RESULT(basename, 0, -1);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+ }
+
+ // otherwise fallback on common implementation
+ return nsBaseURLParser::ParseFilePath(filepath, filepathLen, directoryPos,
+ directoryLen, basenamePos, basenameLen,
+ extensionPos, extensionLen);
+}
+#endif
+
+//----------------------------------------------------------------------------
+// nsAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ nsresult rv;
+
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0) authLen = strlen(auth);
+
+ if (authLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, 0);
+ if (port) *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for @
+ const char* p = auth + authLen - 1;
+ for (; (*p != '@') && (p > auth); --p) {
+ }
+ if (*p == '@') {
+ // auth = <user-info@server-info>
+ rv = ParseUserInfo(auth, p - auth, usernamePos, usernameLen, passwordPos,
+ passwordLen);
+ if (NS_FAILED(rv)) return rv;
+ rv = ParseServerInfo(p + 1, authLen - (p - auth + 1), hostnamePos,
+ hostnameLen, port);
+ if (NS_FAILED(rv)) return rv;
+ OFFSET_RESULT(hostname, p + 1 - auth);
+
+ // malformed if has a username or password
+ // but no host info, such as: http://u:p@/
+ if ((usernamePos || passwordPos) && (!hostnamePos || !*hostnameLen)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ } else {
+ // auth = <server-info>
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ rv = ParseServerInfo(auth, authLen, hostnamePos, hostnameLen, port);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseUserInfo(const char* userinfo, int32_t userinfoLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen) {
+ if (NS_WARN_IF(!userinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (userinfoLen < 0) userinfoLen = strlen(userinfo);
+
+ if (userinfoLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+ }
+
+ const char* p = (const char*)memchr(userinfo, ':', userinfoLen);
+ if (p) {
+ // userinfo = <username:password>
+ SET_RESULT(username, 0, p - userinfo);
+ SET_RESULT(password, p - userinfo + 1, userinfoLen - (p - userinfo + 1));
+ } else {
+ // userinfo = <username>
+ SET_RESULT(username, 0, userinfoLen);
+ SET_RESULT(password, 0, -1);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseServerInfo(const char* serverinfo, int32_t serverinfoLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ if (NS_WARN_IF(!serverinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (serverinfoLen < 0) serverinfoLen = strlen(serverinfo);
+
+ if (serverinfoLen == 0) {
+ SET_RESULT(hostname, 0, 0);
+ if (port) *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for a ':' but stop on ']' (IPv6 address literal
+ // delimiter). check for illegal characters in the hostname.
+ const char* p = serverinfo + serverinfoLen - 1;
+ const char *colon = nullptr, *bracket = nullptr;
+ for (; p > serverinfo; --p) {
+ switch (*p) {
+ case ']':
+ bracket = p;
+ break;
+ case ':':
+ if (bracket == nullptr) colon = p;
+ break;
+ case ' ':
+ // hostname must not contain a space
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ if (colon) {
+ // serverinfo = <hostname:port>
+ SET_RESULT(hostname, 0, colon - serverinfo);
+ if (port) {
+ // XXX unfortunately ToInteger is not defined for substrings
+ nsAutoCString buf(colon + 1, serverinfoLen - (colon + 1 - serverinfo));
+ if (buf.Length() == 0) {
+ *port = -1;
+ } else {
+ const char* nondigit = NS_strspnp("0123456789", buf.get());
+ if (nondigit && *nondigit) return NS_ERROR_MALFORMED_URI;
+
+ nsresult err;
+ *port = buf.ToInteger(&err);
+ if (NS_FAILED(err) || *port < 0 ||
+ *port > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+ }
+ } else {
+ // serverinfo = <hostname>
+ SET_RESULT(hostname, 0, serverinfoLen);
+ if (port) *port = -1;
+ }
+
+ // In case of IPv6 address check its validity
+ if (*hostnameLen > 1 && *(serverinfo + *hostnamePos) == '[' &&
+ *(serverinfo + *hostnamePos + *hostnameLen - 1) == ']' &&
+ !net_IsValidIPv6Addr(
+ Substring(serverinfo + *hostnamePos + 1, *hostnameLen - 2))) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+void nsAuthURLParser::ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ MOZ_ASSERT(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char* end = spec + specLen;
+ const char* p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#') break;
+ }
+ if (p < end) {
+ // spec = [/]<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ } else {
+ // spec = [/]<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+}
+
+//----------------------------------------------------------------------------
+// nsStdURLParser implementation
+//----------------------------------------------------------------------------
+
+void nsStdURLParser::ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ MOZ_ASSERT(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char* end = spec + specLen;
+ const char* p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (strchr("/?#;", *p)) break;
+ }
+ switch (nslash) {
+ case 0:
+ case 2:
+ if (p < end) {
+ // spec = (//)<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ } else {
+ // spec = (//)<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+ break;
+ case 1:
+ // spec = /<path>
+ SET_RESULT(auth, 0, -1);
+ SET_RESULT(path, 0, specLen);
+ break;
+ default:
+ // spec = ///[/]<path>
+ SET_RESULT(auth, 2, 0);
+ SET_RESULT(path, 2, specLen - 2);
+ }
+}
diff --git a/netwerk/base/nsURLParsers.h b/netwerk/base/nsURLParsers.h
new file mode 100644
index 0000000000..3143022b90
--- /dev/null
+++ b/netwerk/base/nsURLParsers.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLParsers_h__
+#define nsURLParsers_h__
+
+#include "nsIURLParser.h"
+#include "mozilla/Attributes.h"
+
+//----------------------------------------------------------------------------
+// base class for url parsers
+//----------------------------------------------------------------------------
+
+class nsBaseURLParser : public nsIURLParser {
+ public:
+ NS_DECL_NSIURLPARSER
+
+ nsBaseURLParser() = default;
+
+ protected:
+ // implemented by subclasses
+ virtual void ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) = 0;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that do not have an authority section
+//
+// eg. file:foo/bar.txt
+// file:/foo/bar.txt (treated equivalently)
+// file:///foo/bar.txt
+//
+// eg. file:////foo/bar.txt (UNC-filepath = \\foo\bar.txt)
+//
+// XXX except in this case:
+// file://foo/bar.txt (the authority "foo" is ignored)
+//----------------------------------------------------------------------------
+
+class nsNoAuthURLParser final : public nsBaseURLParser {
+ ~nsNoAuthURLParser() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+#if defined(XP_WIN)
+ NS_IMETHOD ParseFilePath(const char*, int32_t, uint32_t*, int32_t*, uint32_t*,
+ int32_t*, uint32_t*, int32_t*) override;
+#endif
+
+ NS_IMETHOD ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) override;
+
+ void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos,
+ int32_t* authLen, uint32_t* pathPos,
+ int32_t* pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that must have an authority section
+//
+// eg. http:www.foo.com/bar.html
+// http:/www.foo.com/bar.html
+// http://www.foo.com/bar.html (treated equivalently)
+// http:///www.foo.com/bar.html
+//----------------------------------------------------------------------------
+
+class nsAuthURLParser : public nsBaseURLParser {
+ protected:
+ virtual ~nsAuthURLParser() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) override;
+
+ NS_IMETHOD ParseUserInfo(const char* userinfo, int32_t userinfoLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos,
+ int32_t* passwordLen) override;
+
+ NS_IMETHOD ParseServerInfo(const char* serverinfo, int32_t serverinfoLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) override;
+
+ void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos,
+ int32_t* authLen, uint32_t* pathPos,
+ int32_t* pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that may or may not have an authority section
+//
+// eg. http:www.foo.com (www.foo.com is authority)
+// http:www.foo.com/bar.html (www.foo.com is authority)
+// http:/www.foo.com/bar.html (www.foo.com is part of file path)
+// http://www.foo.com/bar.html (www.foo.com is authority)
+// http:///www.foo.com/bar.html (www.foo.com is part of file path)
+//----------------------------------------------------------------------------
+
+class nsStdURLParser : public nsAuthURLParser {
+ virtual ~nsStdURLParser() = default;
+
+ public:
+ void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos,
+ int32_t* authLen, uint32_t* pathPos,
+ int32_t* pathLen) override;
+};
+
+#endif // nsURLParsers_h__
diff --git a/netwerk/base/rust-helper/Cargo.toml b/netwerk/base/rust-helper/Cargo.toml
new file mode 100644
index 0000000000..370d420182
--- /dev/null
+++ b/netwerk/base/rust-helper/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "netwerk_helper"
+version = "0.0.1"
+authors = ["Jeff Hemphill <jthemphill@mozilla.com>"]
+license = "MPL-2.0"
+
+[dependencies]
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
diff --git a/netwerk/base/rust-helper/cbindgen.toml b/netwerk/base/rust-helper/cbindgen.toml
new file mode 100644
index 0000000000..1e5e235576
--- /dev/null
+++ b/netwerk/base/rust-helper/cbindgen.toml
@@ -0,0 +1,18 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_guard = "mozilla_net_rustHelper_h"
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "net"]
+includes = ["nsError.h", "nsString.h"]
+
+[export]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[export.rename]
+"ThinVec" = "nsTArray"
diff --git a/netwerk/base/rust-helper/moz.build b/netwerk/base/rust-helper/moz.build
new file mode 100644
index 0000000000..1f7512ecf9
--- /dev/null
+++ b/netwerk/base/rust-helper/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ CbindgenHeader("rust_helper.h", inputs=["/netwerk/base/rust-helper"])
+
+ EXPORTS.mozilla.net += [
+ "!rust_helper.h",
+ ]
diff --git a/netwerk/base/rust-helper/src/lib.rs b/netwerk/base/rust-helper/src/lib.rs
new file mode 100644
index 0000000000..7a97736e50
--- /dev/null
+++ b/netwerk/base/rust-helper/src/lib.rs
@@ -0,0 +1,360 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate nserror;
+use self::nserror::*;
+
+extern crate nsstring;
+use self::nsstring::{nsACString, nsCString};
+
+extern crate thin_vec;
+use self::thin_vec::ThinVec;
+
+use std::fs::File;
+use std::io::{self, BufRead};
+use std::net::Ipv4Addr;
+
+/// HTTP leading whitespace, defined in netwerk/protocol/http/nsHttp.h
+static HTTP_LWS: &'static [u8] = &[' ' as u8, '\t' as u8];
+
+/// Trim leading whitespace, trailing whitespace, and quality-value
+/// from a token.
+fn trim_token(token: &[u8]) -> &[u8] {
+ // Trim left whitespace
+ let ltrim = token
+ .iter()
+ .take_while(|c| HTTP_LWS.iter().any(|ws| &ws == c))
+ .count();
+
+ // Trim right whitespace
+ // remove "; q=..." if present
+ let rtrim = token[ltrim..]
+ .iter()
+ .take_while(|c| **c != (';' as u8) && HTTP_LWS.iter().all(|ws| ws != *c))
+ .count();
+
+ &token[ltrim..ltrim + rtrim]
+}
+
+#[no_mangle]
+/// Allocates an nsACString that contains a ISO 639 language list
+/// notated with HTTP "q" values for output with an HTTP Accept-Language
+/// header. Previous q values will be stripped because the order of
+/// the langs implies the q value. The q values are calculated by dividing
+/// 1.0 amongst the number of languages present.
+///
+/// Ex: passing: "en, ja"
+/// returns: "en,ja;q=0.5"
+///
+/// passing: "en, ja, fr_CA"
+/// returns: "en,ja;q=0.7,fr_CA;q=0.3"
+pub extern "C" fn rust_prepare_accept_languages<'a, 'b>(
+ i_accept_languages: &'a nsACString,
+ o_accept_languages: &'b mut nsACString,
+) -> nsresult {
+ if i_accept_languages.is_empty() {
+ return NS_OK;
+ }
+
+ let make_tokens = || {
+ i_accept_languages
+ .split(|c| *c == (',' as u8))
+ .map(|token| trim_token(token))
+ .filter(|token| token.len() != 0)
+ };
+
+ let n = make_tokens().count();
+
+ for (count_n, i_token) in make_tokens().enumerate() {
+ // delimiter if not first item
+ if count_n != 0 {
+ o_accept_languages.append(",");
+ }
+
+ let token_pos = o_accept_languages.len();
+ o_accept_languages.append(&i_token as &[u8]);
+
+ {
+ let o_token = o_accept_languages.to_mut();
+ canonicalize_language_tag(&mut o_token[token_pos..]);
+ }
+
+ // Divide the quality-values evenly among the languages.
+ let q = 1.0 - count_n as f32 / n as f32;
+
+ let u: u32 = ((q + 0.005) * 100.0) as u32;
+ // Only display q-value if less than 1.00.
+ if u < 100 {
+ // With a small number of languages, one decimal place is
+ // enough to prevent duplicate q-values.
+ // Also, trailing zeroes do not add any information, so
+ // they can be removed.
+ if n < 10 || u % 10 == 0 {
+ let u = (u + 5) / 10;
+ o_accept_languages.append(&format!(";q=0.{}", u));
+ } else {
+ // Values below 10 require zero padding.
+ o_accept_languages.append(&format!(";q=0.{:02}", u));
+ }
+ }
+ }
+
+ NS_OK
+}
+
+/// Defines a consistent capitalization for a given language string.
+///
+/// # Arguments
+/// * `token` - a narrow char slice describing a language.
+///
+/// Valid language tags are of the form
+/// "*", "fr", "en-US", "es-419", "az-Arab", "x-pig-latin", "man-Nkoo-GN"
+///
+/// Language tags are defined in the
+/// [rfc5646](https://tools.ietf.org/html/rfc5646) spec. According to
+/// the spec:
+///
+/// > At all times, language tags and their subtags, including private
+/// > use and extensions, are to be treated as case insensitive: there
+/// > exist conventions for the capitalization of some of the subtags,
+/// > but these MUST NOT be taken to carry meaning.
+///
+/// So why is this code even here? See bug 1108183, I guess.
+fn canonicalize_language_tag(token: &mut [u8]) {
+ for c in token.iter_mut() {
+ *c = c.to_ascii_lowercase();
+ }
+
+ let sub_tags = token.split_mut(|c| *c == ('-' as u8));
+ for (i, sub_tag) in sub_tags.enumerate() {
+ if i == 0 {
+ // ISO 639-1 language code, like the "en" in "en-US"
+ continue;
+ }
+
+ match sub_tag.len() {
+ // Singleton tag, like "x" or "i". These signify a
+ // non-standard language, so we stop capitalizing after
+ // these.
+ 1 => break,
+ // ISO 3166-1 Country code, like "US"
+ 2 => {
+ sub_tag[0] = sub_tag[0].to_ascii_uppercase();
+ sub_tag[1] = sub_tag[1].to_ascii_uppercase();
+ }
+ // ISO 15924 script code, like "Nkoo"
+ 4 => {
+ sub_tag[0] = sub_tag[0].to_ascii_uppercase();
+ }
+ _ => {}
+ };
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_ipv4_addr<'a>(addr: &'a nsACString) -> bool {
+ is_valid_ipv4_addr(addr)
+}
+
+#[inline]
+fn try_apply_digit(current_octet: u8, digit_to_apply: u8) -> Option<u8> {
+ current_octet.checked_mul(10)?.checked_add(digit_to_apply)
+}
+
+pub fn is_valid_ipv4_addr<'a>(addr: &'a [u8]) -> bool {
+ let mut current_octet: Option<u8> = None;
+ let mut dots: u8 = 0;
+ for c in addr {
+ let c = *c as char;
+ match c {
+ '.' => {
+ match current_octet {
+ None => {
+ // starting an octet with a . is not allowed
+ return false;
+ }
+ Some(_) => {
+ dots = dots + 1;
+ current_octet = None;
+ }
+ }
+ }
+ // The character is not a digit
+ no_digit if no_digit.to_digit(10).is_none() => {
+ return false;
+ }
+ digit => {
+ match current_octet {
+ None => {
+ // Unwrap is sound because it has been checked in the previous arm
+ current_octet = Some(digit.to_digit(10).unwrap() as u8);
+ }
+ Some(octet) => {
+ if let Some(0) = current_octet {
+ // Leading 0 is not allowed
+ return false;
+ }
+ if let Some(applied) =
+ try_apply_digit(octet, digit.to_digit(10).unwrap() as u8)
+ {
+ current_octet = Some(applied);
+ } else {
+ // Multiplication or Addition overflowed
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+ dots == 3 && current_octet.is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_ipv6_addr<'a>(addr: &'a nsACString) -> bool {
+ is_valid_ipv6_addr(addr)
+}
+
+#[inline(always)]
+fn fast_is_hex_digit(c: u8) -> bool {
+ match c {
+ b'0'..=b'9' => true,
+ b'a'..=b'f' => true,
+ b'A'..=b'F' => true,
+ _ => false,
+ }
+}
+
+pub fn is_valid_ipv6_addr<'a>(addr: &'a [u8]) -> bool {
+ let mut double_colon = false;
+ let mut colon_before = false;
+ let mut digits: u8 = 0;
+ let mut blocks: u8 = 0;
+
+ // The smallest ipv6 is unspecified (::)
+ // The IP starts with a single colon
+ if addr.len() < 2 || addr[0] == b':' && addr[1] != b':' {
+ return false;
+ }
+ //Enumerate with an u8 for cache locality
+ for (i, c) in (0u8..).zip(addr) {
+ match c {
+ maybe_digit if fast_is_hex_digit(*maybe_digit) => {
+ // Too many digits in the block
+ if digits == 4 {
+ return false;
+ }
+ colon_before = false;
+ digits += 1;
+ }
+ b':' => {
+ // Too many columns
+ if double_colon && colon_before || blocks == 8 {
+ return false;
+ }
+ if !colon_before {
+ if digits != 0 {
+ blocks += 1;
+ }
+ digits = 0;
+ colon_before = true;
+ } else if !double_colon {
+ double_colon = true;
+ }
+ }
+ b'.' => {
+ // IPv4 from the last block
+ if is_valid_ipv4_addr(&addr[(i - digits) as usize..]) {
+ return double_colon && blocks < 6 || !double_colon && blocks == 6;
+ }
+ return false;
+ }
+ _ => {
+ // Invalid character
+ return false;
+ }
+ }
+ }
+ if colon_before && !double_colon {
+ // The IP ends with a single colon
+ return false;
+ }
+ if digits != 0 {
+ blocks += 1;
+ }
+
+ double_colon && blocks < 8 || !double_colon && blocks == 8
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_scheme_char(a_char: u8) -> bool {
+ is_valid_scheme_char(a_char)
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_scheme<'a>(scheme: &'a nsACString) -> bool {
+ if scheme.is_empty() {
+ return false;
+ }
+
+ // first char must be alpha
+ if !scheme[0].is_ascii_alphabetic() {
+ return false;
+ }
+
+ scheme[1..]
+ .iter()
+ .all(|a_char| is_valid_scheme_char(*a_char))
+}
+
+fn is_valid_scheme_char(a_char: u8) -> bool {
+ a_char.is_ascii_alphanumeric() || a_char == b'+' || a_char == b'.' || a_char == b'-'
+}
+
+pub type ParsingCallback = extern "C" fn(&ThinVec<nsCString>) -> bool;
+
+#[no_mangle]
+pub extern "C" fn rust_parse_etc_hosts<'a>(path: &'a nsACString, callback: ParsingCallback) {
+ let file = match File::open(&*path.to_utf8()) {
+ Ok(file) => io::BufReader::new(file),
+ Err(..) => return,
+ };
+
+ let mut array = ThinVec::new();
+ for line in file.lines() {
+ let line = match line {
+ Ok(l) => l,
+ Err(..) => continue,
+ };
+
+ let mut iter = line.split('#').next().unwrap().split_whitespace();
+ iter.next(); // skip the IP
+
+ array.extend(
+ iter.filter(|host| {
+ // Make sure it's a valid domain
+ let invalid = [
+ '\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']',
+ ];
+ host.parse::<Ipv4Addr>().is_err() && !host.contains(&invalid[..])
+ })
+ .map(nsCString::from),
+ );
+
+ // /etc/hosts files can be huge. To make sure we don't block shutdown
+ // for every 100 domains that we parse we call the callback passing the
+ // domains and see if we should keep parsing.
+ if array.len() > 100 {
+ let keep_going = callback(&array);
+ array.clear();
+ if !keep_going {
+ break;
+ }
+ }
+ }
+
+ if !array.is_empty() {
+ callback(&array);
+ }
+}
diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf
new file mode 100644
index 0000000000..68d7be7ec3
--- /dev/null
+++ b/netwerk/build/components.conf
@@ -0,0 +1,808 @@
+# -*- 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/.
+
+Headers = [
+ '/netwerk/build/nsNetModule.h'
+]
+
+InitFunc = 'nsNetStartup'
+UnloadFunc = 'nsNetShutdown'
+
+Categories = {
+ '@mozilla.org/streamconv;1': {
+ '?from=application/http-index-format&to=text/html': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=multipart/x-mixed-replace&to=*/*': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=multipart/mixed&to=*/*': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=multipart/byteranges&to=*/*': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=application/x-unknown-content-type&to=*/*': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=gzip&to=uncompressed': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=x-gzip&to=uncompressed': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=br&to=uncompressed': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=compress&to=uncompressed': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=x-compress&to=uncompressed': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ '?from=deflate&to=uncompressed': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
+ },
+}
+
+Classes = [
+ {
+ 'cid': '{3014dde6-aa1c-41db-87d0-48764a3710f6}',
+ 'contract_ids': ['@mozilla.org/io/arraybuffer-input-stream;1'],
+ 'type': 'ArrayBufferInputStream',
+ 'headers': ['/netwerk/base/ArrayBufferInputStream.h'],
+ },
+ {
+ 'js_name': 'loadContextInfo',
+ 'cid': '{62d4b190-3642-4450-b019-d1c1fba56025}',
+ 'contract_ids': ['@mozilla.org/load-context-info-factory;1'],
+ 'interfaces': ['nsILoadContextInfoFactory'],
+ 'type': 'mozilla::net::LoadContextInfoFactory',
+ 'headers': ['mozilla/LoadContextInfo.h'],
+ },
+ {
+ 'name': 'CacheStorage',
+ 'js_name': 'cache2',
+ 'cid': '{ea70b098-5014-4e21-aee1-75e6b2c4b8e0}',
+ 'contract_ids': [
+ '@mozilla.org/netwerk/cache-storage-service;1',
+ '@mozilla.org/network/cache-storage-service;1',
+ ],
+ 'interfaces': ['nsICacheStorageService'],
+ 'singleton': True,
+ 'type': 'mozilla::net::CacheStorageService',
+ 'headers': ['CacheStorageService.h'],
+ },
+ {
+ 'cid': '{e746a8b1-c97a-4fc5-baa4-66607521bd08}',
+ 'contract_ids': ['@mozilla.org/network/async-stream-copier;1'],
+ 'type': 'nsAsyncStreamCopier',
+ 'headers': ['/netwerk/base/nsAsyncStreamCopier.h'],
+ },
+ {
+ 'cid': '{6eae857e-4ba9-11e3-9b39-b4036188709b}',
+ 'contract_ids': ['@mozilla.org/network/atomic-file-output-stream;1'],
+ 'type': 'nsAtomicFileOutputStream',
+ 'headers': ['nsFileStreams.h'],
+ },
+ {
+ 'cid': '{62147d1e-ef6a-40e8-aaf8-d039f5caaa81}',
+ 'contract_ids': ['@mozilla.org/network/background-file-saver;1?mode=outputstream'],
+ 'type': 'mozilla::net::BackgroundFileSaverOutputStream',
+ 'headers': ['/netwerk/base/BackgroundFileSaver.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{208de7fc-a781-4031-bbae-cc0de539f61a}',
+ 'contract_ids': ['@mozilla.org/network/background-file-saver;1?mode=streamlistener'],
+ 'type': 'mozilla::net::BackgroundFileSaverStreamListener',
+ 'headers': ['/netwerk/base/BackgroundFileSaver.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{a2027ec6-ba0d-4c72-805d-148233f5f33c}',
+ 'contract_ids': ['@mozilla.org/network/binary-detector;1'],
+ 'legacy_constructor': 'CreateNewBinaryDetectorFactory',
+ 'categories': {'net-content-sniffers': 'Binary Detector'}
+ },
+ {
+ 'cid': '{9226888e-da08-11d3-8cda-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/buffered-input-stream;1'],
+ 'legacy_constructor': 'nsBufferedInputStream::Create',
+ 'headers': ['/netwerk/base/nsBufferedStreams.h'],
+ },
+ {
+ 'cid': '{9868b4ce-da08-11d3-8cda-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/buffered-output-stream;1'],
+ 'legacy_constructor': 'nsBufferedOutputStream::Create',
+ 'headers': ['/netwerk/base/nsBufferedStreams.h'],
+ },
+ {
+ 'cid': '{bdbe0555-fc3d-4f7b-9205-c309ceb2d641}',
+ 'contract_ids': ['@mozilla.org/network/captive-portal-service;1'],
+ 'singleton': True,
+ 'type': 'nsICaptivePortalService',
+ 'constructor': 'mozilla::net::CaptivePortalService::GetSingleton',
+ 'headers': ['mozilla/net/CaptivePortalService.h'],
+ },
+ {
+ 'cid': '{c79eb3c6-091a-45a6-8544-5a8d1ab79537}',
+ 'contract_ids': ['@mozilla.org/network/dashboard;1'],
+ 'type': 'mozilla::net::Dashboard',
+ 'headers': ['mozilla/net/Dashboard.h'],
+ },
+ {
+ 'name': 'DNS',
+ 'js_name': 'dns',
+ 'cid': '{b0ff4572-dae4-4bef-a092-83c1b88f6be9}',
+ 'contract_ids': ['@mozilla.org/network/dns-service;1'],
+ 'interfaces': ['nsIDNSService'],
+ 'singleton': True,
+ 'type': 'nsIDNSService',
+ 'constructor': 'nsDNSService::GetXPCOMSingleton',
+ 'headers': ['/netwerk/dns/nsDNSService2.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{4ffae79e-57bd-4d7a-a0c9-0045a17b3615}',
+ 'contract_ids': ['@mozilla.org/network/native-dns-override;1'],
+ 'singleton': True,
+ 'type': 'nsINativeDNSResolverOverride',
+ 'constructor': 'mozilla::net::NativeDNSResolverOverride::GetSingleton',
+ 'headers': ['/netwerk/dns/GetAddrInfo.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{510a86bb-6019-4ed1-bb4f-965cffd23ece}',
+ 'contract_ids': ['@mozilla.org/network/downloader;1'],
+ 'type': 'nsDownloader',
+ 'headers': ['/netwerk/base/nsDownloader.h'],
+ },
+ {
+ 'js_name': 'eTLD',
+ 'cid': '{cb9abbae-66b6-4609-8594-5c4ff300888e}',
+ 'contract_ids': ['@mozilla.org/network/effective-tld-service;1'],
+ 'interfaces': ['nsIEffectiveTLDService'],
+ 'singleton': True,
+ 'type': 'nsEffectiveTLDService',
+ 'headers': ['/netwerk/dns/nsEffectiveTLDService.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{be9a53ae-c7e9-11d3-8cda-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/file-input-stream;1'],
+ 'legacy_constructor': 'nsFileInputStream::Create',
+ 'headers': ['nsFileStreams.h'],
+ },
+ {
+ 'cid': '{c272fee0-c7e9-11d3-8cda-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/file-output-stream;1'],
+ 'legacy_constructor': 'nsFileOutputStream::Create',
+ 'headers': ['nsFileStreams.h'],
+ },
+ {
+ 'cid': '{648705e9-757a-4d4b-a5bF-0248e512c309}',
+ 'contract_ids': ['@mozilla.org/network/file-random-access-stream;1'],
+ 'legacy_constructor': 'nsFileRandomAccessStream::Create',
+ 'headers': ['nsFileStreams.h'],
+ },
+ {
+ 'name': 'HttpActivityDistributor',
+ 'cid': '{15629ada-a41c-4a09-961f-6553cd60b1a2}',
+ 'contract_ids': ['@mozilla.org/network/http-activity-distributor;1'],
+ 'type': 'mozilla::net::nsHttpActivityDistributor',
+ 'headers': ['/netwerk/protocol/http/nsHttpActivityDistributor.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{36b63ef3-e0fa-4c49-9fd4-e065e85568f4}',
+ 'contract_ids': ['@mozilla.org/network/http-auth-manager;1'],
+ 'type': 'mozilla::net::nsHttpAuthManager',
+ 'headers': ['/netwerk/protocol/http/nsHttpAuthManager.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{62b778a6-bce3-456b-8c31-2865fbb68c91}',
+ 'contract_ids': ['@mozilla.org/network/idn-service;1'],
+ 'type': 'nsIDNService',
+ 'headers': ['/netwerk/dns/nsIDNService.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{a62af1ba-79b3-4896-8aaf-b148bfce4280}',
+ 'contract_ids': ['@mozilla.org/network/incremental-download;1'],
+ 'legacy_constructor': 'net_NewIncrementalDownload',
+ },
+ {
+ 'cid': '{5d6352a3-b9c3-4fa3-87aa-b2a3c6e5a501}',
+ 'contract_ids': ['@mozilla.org/network/incremental-stream-loader;1'],
+ 'legacy_constructor': 'nsIncrementalStreamLoader::Create',
+ 'headers': ['/netwerk/base/nsIncrementalStreamLoader.h'],
+ },
+ {
+ 'cid': '{6ddb050c-0d04-11d4-986e-00c04fa0cf4a}',
+ 'contract_ids': ['@mozilla.org/network/input-stream-channel;1'],
+ 'type': 'mozilla::net::nsInputStreamChannel',
+ 'headers': ['/netwerk/base/nsInputStreamChannel.h'],
+ },
+ {
+ 'cid': '{ccd0e960-7947-4635-b70e-4c661b63d675}',
+ 'contract_ids': ['@mozilla.org/network/input-stream-pump;1'],
+ 'type': 'nsInputStreamPump',
+ 'headers': ['nsInputStreamPump.h'],
+ },
+ {
+ 'name': 'IO',
+ 'js_name': 'io',
+ 'cid': '{9ac9e770-18bc-11d3-9337-00104ba0fd40}',
+ 'contract_ids': [
+ '@mozilla.org/network/io-service;1',
+ '@mozilla.org/network/util;1',
+ ],
+ 'interfaces': ['nsIIOService', 'nsISpeculativeConnect', 'nsINetUtil'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsIOService',
+ 'headers': ['/netwerk/base/nsIOService.h'],
+ 'constructor': 'mozilla::net::nsIOService::GetInstance',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{e1c61582-2a84-11d3-8cce-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/load-group;1'],
+ 'legacy_constructor': 'nsLoadGroupConstructor',
+ },
+ {
+ 'cid': '{1f4dbcf7-245c-4c8c-943d-8a1da0495e8a}',
+ 'contract_ids': ['@mozilla.org/network/mime-hdrparam;1'],
+ 'type': 'nsMIMEHeaderParamImpl',
+ 'headers': ['/netwerk/mime/nsMIMEHeaderParamImpl.h'],
+ },
+ {
+ 'cid': '{58a1c31c-1dd2-11b2-a3f6-d36949d48268}',
+ 'contract_ids': ['@mozilla.org/network/mime-input-stream;1'],
+ 'legacy_constructor': 'nsMIMEInputStreamConstructor',
+ 'headers': ['nsMIMEInputStream.h'],
+ },
+ {
+ 'cid': '{2693457e-3ba5-4455-991f-5350946adb12}',
+ 'contract_ids': ['@mozilla.org/network/network-connectivity-service;1'],
+ 'singleton': True,
+ 'type': 'nsINetworkConnectivityService',
+ 'constructor': 'mozilla::net::NetworkConnectivityService::GetSingleton',
+ 'headers': ['mozilla/net/NetworkConnectivityService.h'],
+ },
+ {
+ 'cid': '{969adfdf-7221-4419-aecf-05f8faf00c9b}',
+ 'contract_ids': ['@mozilla.org/network/predictor;1'],
+ 'singleton': True,
+ 'legacy_constructor': 'mozilla::net::Predictor::Create',
+ 'headers': ['mozilla/net/Predictor.h'],
+ },
+ {
+ 'cid': '{e9b301c0-e0e4-11d3-a1a8-0050041caf44}',
+ 'contract_ids': ['@mozilla.org/network/protocol-proxy-service;1'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsProtocolProxyService',
+ 'headers': ['/netwerk/base/nsProtocolProxyService.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{3decd6c8-30ef-11d3-8cd0-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/protocol/about;1?what=blank'],
+ 'legacy_constructor': 'nsAboutBlank::Create',
+ 'headers': ['/netwerk/protocol/about/nsAboutBlank.h'],
+ },
+ {
+ 'cid': '{9158c470-86e4-11d4-9be2-00e09872a416}',
+ 'contract_ids': ['@mozilla.org/network/protocol/about;1?what=cache'],
+ 'legacy_constructor': 'nsAboutCache::Create',
+ 'headers': ['/netwerk/protocol/about/nsAboutCache.h'],
+ },
+ {
+ 'cid': '{7fa5237d-b0eb-438f-9e50-ca0166e63788}',
+ 'contract_ids': ['@mozilla.org/network/protocol/about;1?what=cache-entry'],
+ 'type': 'nsAboutCacheEntry',
+ 'headers': ['/netwerk/protocol/about/nsAboutCacheEntry.h'],
+ },
+ {
+ 'cid': '{9e3b6c90-2f75-11d3-8cd0-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=about'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsAboutProtocolHandler',
+ 'headers': ['/netwerk/protocol/about/nsAboutProtocolHandler.h'],
+ 'protocol_config': {
+ 'scheme': 'about',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ 'URI_DANGEROUS_TO_LOAD',
+ 'URI_SCHEME_NOT_SELF_LINKABLE',
+ ],
+ 'has_dynamic_flags': True,
+ },
+ },
+ {
+ 'cid': '{b6ed3030-6183-11d3-a178-0050041caf44}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=data'],
+ 'singleton': True,
+ 'legacy_constructor': 'nsDataHandler::Create',
+ 'headers': ['/netwerk/protocol/data/nsDataHandler.h'],
+ 'protocol_config': {
+ 'scheme': 'data',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ 'URI_INHERITS_SECURITY_CONTEXT',
+ 'URI_LOADABLE_BY_ANYONE',
+ 'URI_NON_PERSISTABLE',
+ 'URI_IS_LOCAL_RESOURCE',
+ 'URI_SYNC_LOAD_IS_OK',
+ ],
+ },
+ },
+ {
+ 'cid': '{fbc81170-1f69-11d3-9344-00104ba0fd40}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=file'],
+ 'singleton': True,
+ 'type': 'nsFileProtocolHandler',
+ 'headers': ['mozilla/net/nsFileProtocolHandler.h'],
+ 'init_method': 'Init',
+ 'protocol_config': {
+ 'scheme': 'file',
+ 'flags': [
+ 'URI_NOAUTH',
+ 'URI_IS_LOCAL_FILE',
+ 'URI_IS_LOCAL_RESOURCE',
+ 'URI_IS_POTENTIALLY_TRUSTWORTHY',
+ ],
+ },
+ },
+ {
+ 'cid': '{4f47e42e-4d23-4dd3-bfda-eb29255e9ea3}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=http'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsHttpHandler',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ 'protocol_config': {
+ 'scheme': 'http',
+ 'flags': [
+ 'URI_STD',
+ 'ALLOWS_PROXY',
+ 'ALLOWS_PROXY_HTTP',
+ 'URI_LOADABLE_BY_ANYONE',
+ 'URI_HAS_WEB_EXPOSED_ORIGIN',
+ ],
+ 'default_port': 80,
+ },
+ },
+ {
+ 'cid': '{dccbe7e4-7750-466b-a557-5ea36c8ff24e}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=https'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsHttpsHandler',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ 'protocol_config': {
+ 'scheme': 'https',
+ 'flags': [
+ 'URI_STD',
+ 'ALLOWS_PROXY',
+ 'ALLOWS_PROXY_HTTP',
+ 'URI_LOADABLE_BY_ANYONE',
+ 'URI_IS_POTENTIALLY_TRUSTWORTHY',
+ 'URI_HAS_WEB_EXPOSED_ORIGIN',
+ ],
+ 'default_port': 443,
+ },
+ },
+ {
+ 'cid': '{aea16cd0-f020-4138-b068-0716c4a15b5a}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-extension'],
+ 'singleton': True,
+ 'type': 'mozilla::net::ExtensionProtocolHandler',
+ 'headers': ['mozilla/net/ExtensionProtocolHandler.h'],
+ 'constructor': 'mozilla::net::ExtensionProtocolHandler::GetSingleton',
+ 'protocol_config': {
+ 'scheme': 'moz-extension',
+ 'flags': [
+ 'URI_STD',
+ 'URI_IS_LOCAL_RESOURCE',
+ 'URI_IS_POTENTIALLY_TRUSTWORTHY',
+ 'URI_HAS_WEB_EXPOSED_ORIGIN',
+ ],
+ 'has_dynamic_flags': True,
+ },
+ },
+ {
+ 'cid': '{450a2b55-620a-44b3-9f67-839b3b0c329c}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-page-thumb'],
+ 'singleton': True,
+ 'type': 'mozilla::net::PageThumbProtocolHandler',
+ 'headers': ['mozilla/net/PageThumbProtocolHandler.h'],
+ 'constructor': 'mozilla::net::PageThumbProtocolHandler::GetSingleton',
+ 'protocol_config': {
+ 'scheme': 'moz-page-thumb',
+ 'flags': [
+ 'URI_STD',
+ 'URI_IS_UI_RESOURCE',
+ 'URI_IS_LOCAL_RESOURCE',
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ ],
+ },
+ },
+ {
+ 'cid': '{1423e739-782c-4081-b5d8-fe6fba68c0ef}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-safe-about'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsSafeAboutProtocolHandler',
+ 'headers': ['/netwerk/protocol/about/nsAboutProtocolHandler.h'],
+ 'protocol_config': {
+ 'scheme': 'moz-safe-about',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ 'URI_LOADABLE_BY_ANYONE',
+ 'URI_IS_POTENTIALLY_TRUSTWORTHY',
+ ],
+ },
+ },
+ {
+ 'cid': '{e64f152a-9f07-11d3-8cda-0060b0fc14a3}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=resource'],
+ 'singleton': True,
+ 'type': 'nsResProtocolHandler',
+ 'headers': ['/netwerk/protocol/res/nsResProtocolHandler.h'],
+ 'constructor': 'nsResProtocolHandler::GetSingleton',
+ 'protocol_config': {
+ 'scheme': 'resource',
+ 'flags': [
+ 'URI_STD',
+ 'URI_IS_UI_RESOURCE',
+ 'URI_IS_LOCAL_RESOURCE',
+ 'URI_IS_POTENTIALLY_TRUSTWORTHY',
+ 'URI_HAS_WEB_EXPOSED_ORIGIN',
+ ],
+ },
+ },
+ {
+ 'cid': '{9c7ec5d1-23f9-11d5-aea8-8fcc0793e97f}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=view-source'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsViewSourceHandler',
+ 'headers': ['/netwerk/protocol/viewsource/nsViewSourceHandler.h'],
+ 'protocol_config': {
+ 'scheme': 'view-source',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ 'URI_DANGEROUS_TO_LOAD',
+ 'URI_NON_PERSISTABLE',
+ ],
+ 'has_dynamic_flags': True,
+ },
+ },
+ {
+ 'cid': '{dc01db59-a513-4c90-824b-085cce06c0aa}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=ws'],
+ 'singleton': True,
+ 'legacy_constructor': 'mozilla::net::WebSocketChannelConstructor',
+ 'protocol_config': {
+ 'scheme': 'ws',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NON_PERSISTABLE',
+ 'ALLOWS_PROXY',
+ 'ALLOWS_PROXY_HTTP',
+ 'URI_DOES_NOT_RETURN_DATA',
+ 'URI_DANGEROUS_TO_LOAD',
+ 'URI_HAS_WEB_EXPOSED_ORIGIN',
+ ],
+ 'default_port': 80,
+ },
+ },
+ {
+ 'cid': '{dc01dbbb-a5bb-4cbb-82bb-085cce06c0bb}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=wss'],
+ 'singleton': True,
+ 'legacy_constructor': 'mozilla::net::WebSocketSSLChannelConstructor',
+ 'protocol_config': {
+ 'scheme': 'wss',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NON_PERSISTABLE',
+ 'ALLOWS_PROXY',
+ 'ALLOWS_PROXY_HTTP',
+ 'URI_DOES_NOT_RETURN_DATA',
+ 'URI_DANGEROUS_TO_LOAD',
+ 'URI_IS_POTENTIALLY_TRUSTWORTHY',
+ 'URI_HAS_WEB_EXPOSED_ORIGIN',
+ ],
+ 'default_port': 443,
+ },
+ },
+ {
+ 'cid': '{a181af0d-68b8-4308-94db-d4f859058215}',
+ 'contract_ids': ['@mozilla.org/network/safe-file-output-stream;1'],
+ 'type': 'nsSafeFileOutputStream',
+ 'headers': ['nsFileStreams.h'],
+ },
+ {
+ 'cid': '{d6ef593d-a429-4b14-a887-d9e2f765d9ed}',
+ 'contract_ids': ['@mozilla.org/network/serialization-helper;1'],
+ 'type': 'nsSerializationHelper',
+ 'headers': ['nsSerializationHelper.h'],
+ },
+ {
+ 'cid': '{2ec62893-3b35-48fa-ab1d-5e68a9f45f08}',
+ 'contract_ids': ['@mozilla.org/network/server-socket;1'],
+ 'type': 'mozilla::net::nsServerSocket',
+ 'headers': ['/netwerk/base/nsServerSocket.h'],
+ },
+ {
+ 'cid': '{fb8cbf4e-4701-4ba1-b1d6-5388e041fb67}',
+ 'contract_ids': ['@mozilla.org/network/simple-stream-listener;1'],
+ 'type': 'mozilla::net::nsSimpleStreamListener',
+ 'headers': ['/netwerk/base/nsSimpleStreamListener.h'],
+ },
+ {
+ 'cid': '{2be14592-28d4-4a83-8fe9-08e778849f6e}',
+ 'contract_ids': ['@mozilla.org/network/simple-uri-mutator;1'],
+ 'type': 'mozilla::net::nsSimpleURI::Mutator',
+ 'headers': ['nsSimpleURI.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{04445aa0-fd27-4c99-bd41-6be6318ae92c}',
+ 'contract_ids': ['@mozilla.org/network/default-uri-mutator;1'],
+ 'type': 'mozilla::net::DefaultURI::Mutator',
+ 'headers': ['/netwerk/base/DefaultURI.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'name': 'SocketTransport',
+ 'cid': '{ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1}',
+ 'contract_ids': ['@mozilla.org/network/socket-transport-service;1'],
+ 'singleton': True,
+ 'type': 'mozilla::net::nsSocketTransportService',
+ 'headers': ['/netwerk/base/nsSocketTransportService2.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{ce7d7da0-fb28-44a3-8c7b-000c165918f4}',
+ 'contract_ids': ['@mozilla.org/network/standard-url-mutator;1'],
+ 'type': 'mozilla::net::nsStandardURL::Mutator',
+ 'headers': ['/netwerk/base/nsStandardURL.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{831f8f13-7aa8-485f-b02e-77c881cc5773}',
+ 'contract_ids': ['@mozilla.org/network/stream-listener-tee;1'],
+ 'type': 'mozilla::net::nsStreamListenerTee',
+ 'headers': ['/netwerk/base/nsStreamListenerTee.h'],
+ },
+ {
+ 'cid': '{9879908a-2972-40c0-890b-a91dd7dfb954}',
+ 'contract_ids': ['@mozilla.org/network/stream-loader;1'],
+ 'legacy_constructor': 'mozilla::net::nsStreamLoader::Create',
+ 'headers': ['/netwerk/base/nsStreamLoader.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'name': 'StreamTransport',
+ 'cid': '{0885d4f8-f7b8-4cda-902e-94ba38bc256e}',
+ 'contract_ids': ['@mozilla.org/network/stream-transport-service;1'],
+ 'type': 'mozilla::net::nsStreamTransportService',
+ 'headers': ['/netwerk/base/nsStreamTransportService.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{4c39159c-cd90-4dd3-97a7-06af5e6d84c4}',
+ 'contract_ids': ['@mozilla.org/network/throttlequeue;1'],
+ 'type': 'nsIInputChannelThrottleQueue',
+ 'constructor': 'mozilla::net::ThrottleQueue::Create',
+ 'headers': ['/netwerk/base/ThrottleQueue.h'],
+ },
+ {
+ 'cid': '{1813cbb4-c98e-4622-8c7d-839167f3f272}',
+ 'contract_ids': ['@mozilla.org/network/tls-server-socket;1'],
+ 'type': 'mozilla::net::TLSServerSocket',
+ 'headers': ['/netwerk/base/TLSServerSocket.h'],
+ },
+ {
+ 'cid': '{c9f74572-7b8e-4fec-bb4a-03c0d3021bd6}',
+ 'contract_ids': ['@mozilla.org/network/udp-socket;1'],
+ 'type': 'mozilla::net::nsUDPSocket',
+ 'headers': ['/netwerk/base/nsUDPSocket.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{ff41913b-546a-4bff-9201-dc9b2c032eba}',
+ 'contract_ids': ['@mozilla.org/network/url-parser;1?auth=maybe'],
+ 'type': 'nsStdURLParser',
+ 'headers': ['nsURLParsers.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{78804a84-8173-42b6-bb94-789f0816a810}',
+ 'contract_ids': ['@mozilla.org/network/url-parser;1?auth=no'],
+ 'type': 'nsNoAuthURLParser',
+ 'headers': ['nsURLParsers.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{275d800e-3f60-4896-adb7-d7f390ce0e42}',
+ 'contract_ids': ['@mozilla.org/network/url-parser;1?auth=yes'],
+ 'type': 'nsAuthURLParser',
+ 'headers': ['nsURLParsers.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{892ffeb0-3f80-11d3-a16c-0050041caf44}',
+ 'contract_ids': ['@mozilla.org/streamConverters;1'],
+ 'legacy_constructor': 'CreateNewStreamConvServiceFactory',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{cf0f71fd-fafd-4e2b-9fdc-134d972e16e2}',
+ 'contract_ids': ['@mozilla.org/streamconv;1?from=application/http-index-format&to=text/html'],
+ 'legacy_constructor': 'nsIndexedToHTML::Create',
+ 'headers': ['/netwerk/streamconv/converters/nsIndexedToHTML.h'],
+ },
+ {
+ 'cid': '{7d7008a0-c49a-11d3-9b22-0080c7cb1080}',
+ 'contract_ids': ['@mozilla.org/streamconv;1?from=application/x-unknown-content-type&to=*/*'],
+ 'legacy_constructor': 'CreateNewUnknownDecoderFactory',
+ },
+ {
+ 'cid': '{66230b2b-17fa-4bd3-abf4-07986151022d}',
+ 'contract_ids': [
+ '@mozilla.org/streamconv;1?from=br&to=uncompressed',
+ '@mozilla.org/streamconv;1?from=compress&to=uncompressed',
+ '@mozilla.org/streamconv;1?from=deflate&to=uncompressed',
+ '@mozilla.org/streamconv;1?from=gzip&to=uncompressed',
+ '@mozilla.org/streamconv;1?from=x-compress&to=uncompressed',
+ '@mozilla.org/streamconv;1?from=x-gzip&to=uncompressed',
+ ],
+ 'legacy_constructor': 'CreateNewHTTPCompressConvFactory',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{7584ce90-5b25-11d3-a175-0050041caf44}',
+ 'contract_ids': [
+ '@mozilla.org/streamconv;1?from=multipart/byteranges&to=*/*',
+ '@mozilla.org/streamconv;1?from=multipart/mixed&to=*/*',
+ '@mozilla.org/streamconv;1?from=multipart/x-mixed-replace&to=*/*',
+ ],
+ 'legacy_constructor': 'CreateNewMultiMixedConvFactory',
+ },
+ {
+ 'cid': '{77c0e42a-1dd2-11b2-8ebf-edc6606f2f4b}',
+ 'contract_ids': ['@mozilla.org/txttohtmlconv;1'],
+ 'legacy_constructor': 'CreateNewTXTToHTMLConvFactory',
+ },
+ {
+ 'cid': '{2f277c00-0eaf-4ddb-b936-41326ba48aae}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::nsNestedAboutURI::Mutator',
+ 'headers': ['/netwerk/protocol/about/nsAboutProtocolHandler.h'],
+ },
+ {
+ 'cid': '{56388dad-287b-4240-a785-85c394012503}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::nsSimpleNestedURI::Mutator',
+ 'headers': ['nsSimpleNestedURI.h'],
+ },
+ {
+ 'cid': '{9c4e9d49-ce64-4ca3-acef-3075c5e5aba7}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::nsSimpleNestedURI::Mutator',
+ 'headers': ['nsSimpleNestedURI.h'],
+ },
+ {
+ 'cid': '{b0054ef3-b096-483d-8242-4ee36b7b2115}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::nsNestedAboutURI::Mutator',
+ 'headers': ['/netwerk/protocol/about/nsAboutProtocolHandler.h'],
+ },
+ {
+ 'cid': '{b3cfeb91-332a-46c9-ad97-93ff39841494}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::SubstitutingURL::Mutator',
+ 'headers': ['mozilla/net/SubstitutingURL.h'],
+ },
+ {
+ 'cid': '{de9472d0-8034-11d3-9399-00104ba0fd40}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::nsStandardURL::Mutator',
+ 'headers': ['/netwerk/base/nsStandardURL.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{dea9657c-18cf-4984-bde9-ccef5d8ab473}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::SubstitutingURL::Mutator',
+ 'headers': ['mozilla/net/SubstitutingURL.h'],
+ },
+ {
+ 'cid': '{50d50ddf-f16a-4652-8705-936b19c3763b}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::SubstitutingJARURI',
+ 'headers': ['mozilla/net/SubstitutingJARURI.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{e0da1d70-2f7b-11d3-8cd0-0060b0fc14a3}',
+ 'contract_ids': [],
+ 'type': 'mozilla::net::nsSimpleURI::Mutator',
+ 'headers': ['nsSimpleURI.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'js_name': 'cookies',
+ 'cid': '{c375fa80-150f-11d6-a618-0010a401eb10}',
+ 'contract_ids': [
+ '@mozilla.org/cookieService;1',
+ '@mozilla.org/cookiemanager;1',
+ ],
+ 'interfaces': ['nsICookieService', 'nsICookieManager'],
+ 'singleton': True,
+ 'type': 'nsICookieService',
+ 'constructor': 'mozilla::net::CookieService::GetXPCOMSingleton',
+ 'headers': ['/netwerk/cookie/CookieService.h'],
+ },
+ {
+ 'cid': '{e1676f84-e6e5-45d0-a4bf-d9905efc5b2e}',
+ 'contract_ids': ['@mozilla.org/http-sfv-service;1'],
+ 'singleton': True,
+ 'constructor': 'mozilla::net::GetSFVService',
+ 'headers': ['mozilla/net/SFVService.h'],
+ },
+ {
+ 'cid': '{4ce234f1-52e8-47a9-8c8d-b02f815733c7}',
+ 'contract_ids': ['@mozilla.org/cookieJarSettings;1'],
+ 'type': 'nsICookieJarSettings',
+ 'constructor': 'mozilla::net::CookieJarSettings::CreateForXPCOM',
+ 'headers': ['mozilla/net/CookieJarSettings.h'],
+ },
+]
+
+if defined('NECKO_WIFI'):
+ Classes += [
+ {
+ 'cid': '{3ff8fb9f-ee63-48df-89f0-dace0242fd82}',
+ 'contract_ids': ['@mozilla.org/wifi/monitor;1'],
+ 'singleton': True,
+ 'type': 'nsWifiMonitor',
+ 'headers': ['/netwerk/wifi/nsWifiMonitor.h'],
+ },
+ ]
+
+toolkit = buildconfig.substs['MOZ_WIDGET_TOOLKIT']
+link_service = None
+if toolkit == 'windows':
+ link_service = {
+ 'type': 'nsNotifyAddrListener',
+ 'headers': ['/netwerk/system/win32/nsNotifyAddrListener.h'],
+ 'init_method': 'Init',
+ }
+elif toolkit == 'cocoa':
+ link_service = {
+ 'type': 'nsNetworkLinkService',
+ 'headers': ['/netwerk/system/mac/nsNetworkLinkService.h'],
+ 'init_method': 'Init',
+ }
+elif toolkit == 'android':
+ link_service = {
+ 'type': 'nsAndroidNetworkLinkService',
+ 'headers': ['/netwerk/system/android/nsAndroidNetworkLinkService.h'],
+ 'init_method': 'Init',
+ }
+elif buildconfig.substs['OS_ARCH'] == 'Linux':
+ link_service = {
+ 'type': 'nsNetworkLinkService',
+ 'headers': ['/netwerk/system/linux/nsNetworkLinkService.h'],
+ 'init_method': 'Init',
+ }
+
+if link_service:
+ Classes += [
+ dict({
+ 'cid': '{75a500a2-0030-40f7-86f8-63f225b940ae}',
+ 'contract_ids': ['@mozilla.org/network/network-link-service;1'],
+ 'singleton': True,
+ }, **link_service)
+ ]
+
+if toolkit != 'android':
+ Classes += [
+ {
+ 'cid': '{72da39cc-0b9b-4fff-8ff9-d3b9a41d0dc4}',
+ 'contract_ids': ['@mozilla.org/net/CachePurgeLock;1'],
+ 'type': 'mozilla::net::CachePurgeLock',
+ 'headers': ['mozilla/net/CachePurgeLock.h'],
+ },
+ ]
diff --git a/netwerk/build/moz.build b/netwerk/build/moz.build
new file mode 100644
index 0000000000..a3c3fbdd66
--- /dev/null
+++ b/netwerk/build/moz.build
@@ -0,0 +1,53 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ "nsNetCID.h",
+]
+
+SOURCES += [
+ "nsNetModule.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/dns",
+ "/netwerk/mime",
+ "/netwerk/protocol/about",
+ "/netwerk/protocol/data",
+ "/netwerk/protocol/file",
+ "/netwerk/protocol/http",
+ "/netwerk/protocol/res",
+ "/netwerk/protocol/viewsource",
+ "/netwerk/protocol/websocket",
+ "/netwerk/socket",
+ "/netwerk/streamconv",
+ "/netwerk/streamconv/converters",
+]
+
+if CONFIG["MOZ_AUTH_EXTENSION"]:
+ LOCAL_INCLUDES += [
+ "/extensions/auth",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ LOCAL_INCLUDES += [
+ "/netwerk/protocol/gio",
+ ]
+
+
+LOCAL_INCLUDES += [
+ "!/netwerk/dns",
+ "/modules/brotli/dec",
+]
diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h
new file mode 100644
index 0000000000..641fd9e4b3
--- /dev/null
+++ b/netwerk/build/nsNetCID.h
@@ -0,0 +1,849 @@
+/* -*- 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 nsNetCID_h__
+#define nsNetCID_h__
+
+/******************************************************************************
+ * netwerk/base/ classes
+ */
+
+// service implementing nsIIOService
+#define NS_IOSERVICE_CONTRACTID "@mozilla.org/network/io-service;1"
+#define NS_IOSERVICE_CID \
+ { /* 9ac9e770-18bc-11d3-9337-00104ba0fd40 */ \
+ 0x9ac9e770, 0x18bc, 0x11d3, { \
+ 0x93, 0x37, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40 \
+ } \
+ }
+
+// service implementing nsINetUtil
+#define NS_NETUTIL_CONTRACTID "@mozilla.org/network/util;1"
+
+// serialization scriptable helper
+#define NS_SERIALIZATION_HELPER_CONTRACTID \
+ "@mozilla.org/network/serialization-helper;1"
+#define NS_SERIALIZATION_HELPER_CID \
+ { /* D6EF593D-A429-4b14-A887-D9E2F765D9ED */ \
+ 0xd6ef593d, 0xa429, 0x4b14, { \
+ 0xa8, 0x87, 0xd9, 0xe2, 0xf7, 0x65, 0xd9, 0xed \
+ } \
+ }
+
+// service implementing nsIProtocolProxyService and nsPIProtocolProxyService.
+#define NS_PROTOCOLPROXYSERVICE_CONTRACTID \
+ "@mozilla.org/network/protocol-proxy-service;1"
+#define NS_PROTOCOLPROXYSERVICE_CID \
+ { /* E9B301C0-E0E4-11d3-A1A8-0050041CAF44 */ \
+ 0xe9b301c0, 0xe0e4, 0x11d3, { \
+ 0xa1, 0xa8, 0x0, 0x50, 0x4, 0x1c, 0xaf, 0x44 \
+ } \
+ }
+
+// component implementing nsILoadGroup.
+#define NS_LOADGROUP_CONTRACTID "@mozilla.org/network/load-group;1"
+#define NS_LOADGROUP_CID \
+ { /* e1c61582-2a84-11d3-8cce-0060b0fc14a3 */ \
+ 0xe1c61582, 0x2a84, 0x11d3, { \
+ 0x8c, 0xce, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+// component implementing nsIURI, nsISerializable, and nsIClassInfo.
+#define NS_SIMPLEURI_CID \
+ { /* e0da1d70-2f7b-11d3-8cd0-0060b0fc14a3 */ \
+ 0xe0da1d70, 0x2f7b, 0x11d3, { \
+ 0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+// component implementing nsIURI, nsISerializable, and nsIClassInfo.
+#define NS_ICONURI_CID \
+ { /* 1460df3b-774c-4205-8349-838e507c3ef9 */ \
+ 0x1460df3b, 0x774c, 0x4205, { \
+ 0x83, 0x49, 0x83, 0x8e, 0x50, 0x7c, 0x3e, 0xf9 \
+ } \
+ }
+
+#define NS_SIMPLEURIMUTATOR_CONTRACTID \
+ "@mozilla.org/network/simple-uri-mutator;1"
+#define NS_SIMPLEURIMUTATOR_CID \
+ { /* 2be14592-28d4-4a83-8fe9-08e778849f6e */ \
+ 0x2be14592, 0x28d4, 0x4a83, { \
+ 0x8f, 0xe9, 0x08, 0xe7, 0x78, 0x84, 0x9f, 0x6e \
+ } \
+ }
+
+// component inheriting from the simple URI component and also
+// implementing nsINestedURI.
+#define NS_SIMPLENESTEDURI_CID \
+ { /* 56388dad-287b-4240-a785-85c394012503 */ \
+ 0x56388dad, 0x287b, 0x4240, { \
+ 0xa7, 0x85, 0x85, 0xc3, 0x94, 0x01, 0x25, 0x03 \
+ } \
+ }
+
+#define NS_SIMPLENESTEDURIMUTATOR_CID \
+ { /* 9c4e9d49-ce64-4ca3-acef-3075c5e5aba7 */ \
+ 0x9c4e9d49, 0xce64, 0x4ca3, { \
+ 0xac, 0xef, 0x30, 0x75, 0xc5, 0xe5, 0xab, 0xa7 \
+ } \
+ }
+
+// component inheriting from the nested simple URI component and also
+// carrying along its base URI
+#define NS_NESTEDABOUTURI_CID \
+ { /* 2f277c00-0eaf-4ddb-b936-41326ba48aae */ \
+ 0x2f277c00, 0x0eaf, 0x4ddb, { \
+ 0xb9, 0x36, 0x41, 0x32, 0x6b, 0xa4, 0x8a, 0xae \
+ } \
+ }
+
+#define NS_NESTEDABOUTURIMUTATOR_CID \
+ { /* b0054ef3-b096-483d-8242-4ee36b7b2115 */ \
+ 0xb0054ef3, 0xb096, 0x483d, { \
+ 0x82, 0x42, 0x4e, 0xe3, 0x6b, 0x7b, 0x21, 0x15 \
+ } \
+ }
+
+// component implementing nsIStandardURL, nsIURI, nsIURL, nsISerializable,
+// and nsIClassInfo.
+#define NS_STANDARDURL_CID \
+ { /* de9472d0-8034-11d3-9399-00104ba0fd40 */ \
+ 0xde9472d0, 0x8034, 0x11d3, { \
+ 0x93, 0x99, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40 \
+ } \
+ }
+
+#define NS_STANDARDURLMUTATOR_CONTRACTID \
+ "@mozilla.org/network/standard-url-mutator;1"
+#define NS_STANDARDURLMUTATOR_CID \
+ { /* ce7d7da0-fb28-44a3-8c7b-000c165918f4 */ \
+ 0xce7d7da0, 0xfb28, 0x44a3, { \
+ 0x8c, 0x7b, 0x00, 0x0c, 0x16, 0x59, 0x18, 0xf4 \
+ } \
+ }
+
+// service implementing nsIURLParser that assumes the URL will NOT contain an
+// authority section.
+#define NS_NOAUTHURLPARSER_CONTRACTID \
+ "@mozilla.org/network/url-parser;1?auth=no"
+#define NS_NOAUTHURLPARSER_CID \
+ { /* 78804a84-8173-42b6-bb94-789f0816a810 */ \
+ 0x78804a84, 0x8173, 0x42b6, { \
+ 0xbb, 0x94, 0x78, 0x9f, 0x08, 0x16, 0xa8, 0x10 \
+ } \
+ }
+
+// service implementing nsIURLParser that assumes the URL will contain an
+// authority section.
+#define NS_AUTHURLPARSER_CONTRACTID "@mozilla.org/network/url-parser;1?auth=yes"
+#define NS_AUTHURLPARSER_CID \
+ { /* 275d800e-3f60-4896-adb7-d7f390ce0e42 */ \
+ 0x275d800e, 0x3f60, 0x4896, { \
+ 0xad, 0xb7, 0xd7, 0xf3, 0x90, 0xce, 0x0e, 0x42 \
+ } \
+ }
+
+// service implementing nsIURLParser that does not make any assumptions about
+// whether or not the URL contains an authority section.
+#define NS_STDURLPARSER_CONTRACTID \
+ "@mozilla.org/network/url-parser;1?auth=maybe"
+#define NS_STDURLPARSER_CID \
+ { /* ff41913b-546a-4bff-9201-dc9b2c032eba */ \
+ 0xff41913b, 0x546a, 0x4bff, { \
+ 0x92, 0x01, 0xdc, 0x9b, 0x2c, 0x03, 0x2e, 0xba \
+ } \
+ }
+
+// component implementing nsISimpleStreamListener.
+#define NS_SIMPLESTREAMLISTENER_CONTRACTID \
+ "@mozilla.org/network/simple-stream-listener;1"
+#define NS_SIMPLESTREAMLISTENER_CID \
+ { /* fb8cbf4e-4701-4ba1-b1d6-5388e041fb67 */ \
+ 0xfb8cbf4e, 0x4701, 0x4ba1, { \
+ 0xb1, 0xd6, 0x53, 0x88, 0xe0, 0x41, 0xfb, 0x67 \
+ } \
+ }
+
+// component implementing nsIStreamListenerTee.
+#define NS_STREAMLISTENERTEE_CONTRACTID \
+ "@mozilla.org/network/stream-listener-tee;1"
+#define NS_STREAMLISTENERTEE_CID \
+ { /* 831f8f13-7aa8-485f-b02e-77c881cc5773 */ \
+ 0x831f8f13, 0x7aa8, 0x485f, { \
+ 0xb0, 0x2e, 0x77, 0xc8, 0x81, 0xcc, 0x57, 0x73 \
+ } \
+ }
+
+// component implementing nsIAsyncStreamCopier.
+#define NS_ASYNCSTREAMCOPIER_CONTRACTID \
+ "@mozilla.org/network/async-stream-copier;1"
+#define NS_ASYNCSTREAMCOPIER_CID \
+ { /* e746a8b1-c97a-4fc5-baa4-66607521bd08 */ \
+ 0xe746a8b1, 0xc97a, 0x4fc5, { \
+ 0xba, 0xa4, 0x66, 0x60, 0x75, 0x21, 0xbd, 0x08 \
+ } \
+ }
+
+// component implementing nsIInputStreamPump.
+#define NS_INPUTSTREAMPUMP_CONTRACTID "@mozilla.org/network/input-stream-pump;1"
+#define NS_INPUTSTREAMPUMP_CID \
+ { /* ccd0e960-7947-4635-b70e-4c661b63d675 */ \
+ 0xccd0e960, 0x7947, 0x4635, { \
+ 0xb7, 0x0e, 0x4c, 0x66, 0x1b, 0x63, 0xd6, 0x75 \
+ } \
+ }
+
+// component implementing nsIInputStreamChannel.
+#define NS_INPUTSTREAMCHANNEL_CONTRACTID \
+ "@mozilla.org/network/input-stream-channel;1"
+#define NS_INPUTSTREAMCHANNEL_CID \
+ { /* 6ddb050c-0d04-11d4-986e-00c04fa0cf4a */ \
+ 0x6ddb050c, 0x0d04, 0x11d4, { \
+ 0x98, 0x6e, 0x00, 0xc0, 0x4f, 0xa0, 0xcf, 0x4a \
+ } \
+ }
+
+// component implementing nsIStreamLoader.
+#define NS_STREAMLOADER_CONTRACTID "@mozilla.org/network/stream-loader;1"
+#define NS_STREAMLOADER_CID \
+ { /* 9879908a-2972-40c0-890b-a91dd7dfb954 */ \
+ 0x9879908a, 0x2972, 0x40c0, { \
+ 0x89, 0x0b, 0xa9, 0x1d, 0xd7, 0xdf, 0xb9, 0x54 \
+ } \
+ }
+
+// component implementing nsIStreamLoader.
+#define NS_INCREMENTALSTREAMLOADER_CONTRACTID \
+ "@mozilla.org/network/incremental-stream-loader;1"
+#define NS_INCREMENTALSTREAMLOADER_CID \
+ { /* 5d6352a3-b9c3-4fa3-87aa-b2a3c6e5a501 */ \
+ 0x5d6352a3, 0xb9c3, 0x4fa3, { \
+ 0x87, 0xaa, 0xb2, 0xa3, 0xc6, 0xe5, 0xa5, 0x01 \
+ } \
+ }
+
+// component implementing nsIUnicharStreamLoader.
+#define NS_UNICHARSTREAMLOADER_CONTRACTID \
+ "@mozilla.org/network/unichar-stream-loader;1"
+#define NS_UNICHARSTREAMLOADER_CID \
+ { /* 9445791f-fa4c-4669-b174-df5032bb67b3 */ \
+ 0x9445791f, 0xfa4c, 0x4669, { \
+ 0xb1, 0x74, 0xdf, 0x50, 0x32, 0xbb, 0x67, 0xb3 \
+ } \
+ }
+
+// component implementing nsIDownloader.
+#define NS_DOWNLOADER_CONTRACTID "@mozilla.org/network/downloader;1"
+#define NS_DOWNLOADER_CID \
+ { /* 510a86bb-6019-4ed1-bb4f-965cffd23ece */ \
+ 0x510a86bb, 0x6019, 0x4ed1, { \
+ 0xbb, 0x4f, 0x96, 0x5c, 0xff, 0xd2, 0x3e, 0xce \
+ } \
+ }
+
+// component implementing nsIBackgroundFileSaver and
+// nsIOutputStream.
+#define NS_BACKGROUNDFILESAVEROUTPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/background-file-saver;1?mode=outputstream"
+#define NS_BACKGROUNDFILESAVEROUTPUTSTREAM_CID \
+ { /* 62147d1e-ef6a-40e8-aaf8-d039f5caaa81 */ \
+ 0x62147d1e, 0xef6a, 0x40e8, { \
+ 0xaa, 0xf8, 0xd0, 0x39, 0xf5, 0xca, 0xaa, 0x81 \
+ } \
+ }
+
+// component implementing nsIBackgroundFileSaver and
+// nsIStreamListener.
+#define NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID \
+ "@mozilla.org/network/background-file-saver;1?mode=streamlistener"
+#define NS_BACKGROUNDFILESAVERSTREAMLISTENER_CID \
+ { /* 208de7fc-a781-4031-bbae-cc0de539f61a */ \
+ 0x208de7fc, 0xa781, 0x4031, { \
+ 0xbb, 0xae, 0xcc, 0x0d, 0xe5, 0x39, 0xf6, 0x1a \
+ } \
+ }
+
+// component implementing nsIIncrementalDownload.
+#define NS_INCREMENTALDOWNLOAD_CONTRACTID \
+ "@mozilla.org/network/incremental-download;1"
+
+// component implementing nsISystemProxySettings.
+#define NS_SYSTEMPROXYSETTINGS_CONTRACTID "@mozilla.org/system-proxy-settings;1"
+
+// component implementing nsIDHCPClient.
+#define NS_DHCPCLIENT_CONTRACTID "@mozilla.org/dhcp-client;1"
+
+// service implementing nsIStreamTransportService
+#define NS_STREAMTRANSPORTSERVICE_CONTRACTID \
+ "@mozilla.org/network/stream-transport-service;1"
+#define NS_STREAMTRANSPORTSERVICE_CID \
+ { /* 0885d4f8-f7b8-4cda-902e-94ba38bc256e */ \
+ 0x0885d4f8, 0xf7b8, 0x4cda, { \
+ 0x90, 0x2e, 0x94, 0xba, 0x38, 0xbc, 0x25, 0x6e \
+ } \
+ }
+
+// service implementing nsISocketTransportService
+#define NS_SOCKETTRANSPORTSERVICE_CONTRACTID \
+ "@mozilla.org/network/socket-transport-service;1"
+#define NS_SOCKETTRANSPORTSERVICE_CID \
+ { /* ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1 */ \
+ 0xad56b25f, 0xe6bb, 0x4db3, { \
+ 0x9f, 0x7b, 0x5b, 0x7d, 0xb3, 0x3f, 0xd2, 0xb1 \
+ } \
+ }
+
+// component implementing nsIServerSocket
+#define NS_SERVERSOCKET_CONTRACTID "@mozilla.org/network/server-socket;1"
+#define NS_SERVERSOCKET_CID \
+ { /* 2ec62893-3b35-48fa-ab1d-5e68a9f45f08 */ \
+ 0x2ec62893, 0x3b35, 0x48fa, { \
+ 0xab, 0x1d, 0x5e, 0x68, 0xa9, 0xf4, 0x5f, 0x08 \
+ } \
+ }
+
+// component implementing nsITLSServerSocket
+#define NS_TLSSERVERSOCKET_CONTRACTID "@mozilla.org/network/tls-server-socket;1"
+#define NS_TLSSERVERSOCKET_CID \
+ { /* 1813cbb4-c98e-4622-8c7d-839167f3f272 */ \
+ 0x1813cbb4, 0xc98e, 0x4622, { \
+ 0x8c, 0x7d, 0x83, 0x91, 0x67, 0xf3, 0xf2, 0x72 \
+ } \
+ }
+
+// component implementing nsIUDPSocket
+#define NS_UDPSOCKET_CONTRACTID "@mozilla.org/network/udp-socket;1"
+#define NS_UDPSOCKET_CID \
+ { /* c9f74572-7b8e-4fec-bb4a-03c0d3021bd6 */ \
+ 0xc9f74572, 0x7b8e, 0x4fec, { \
+ 0xbb, 0x4a, 0x03, 0xc0, 0xd3, 0x02, 0x1b, 0xd6 \
+ } \
+ }
+
+#define NS_LOCALFILEINPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/file-input-stream;1"
+#define NS_LOCALFILEINPUTSTREAM_CID \
+ { /* be9a53ae-c7e9-11d3-8cda-0060b0fc14a3 */ \
+ 0xbe9a53ae, 0xc7e9, 0x11d3, { \
+ 0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+#define NS_LOCALFILEOUTPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/file-output-stream;1"
+#define NS_LOCALFILEOUTPUTSTREAM_CID \
+ { /* c272fee0-c7e9-11d3-8cda-0060b0fc14a3 */ \
+ 0xc272fee0, 0xc7e9, 0x11d3, { \
+ 0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+#define NS_LOCALFILERANDOMACCESSSTREAM_CONTRACTID \
+ "@mozilla.org/network/file-random-access-stream;1"
+#define NS_LOCALFILERANDOMACCESSSTREAM_CID \
+ { /* 648705e9-757a-4d4b-a5bF-0248e512c309 */ \
+ 0x648705e9, 0x757a, 0x4d4b, { \
+ 0xa5, 0xbF, 0x02, 0x48, 0xe5, 0x12, 0xc3, 0x09 \
+ } \
+ }
+
+#define NS_BUFFEREDINPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/buffered-input-stream;1"
+#define NS_BUFFEREDINPUTSTREAM_CID \
+ { /* 9226888e-da08-11d3-8cda-0060b0fc14a3 */ \
+ 0x9226888e, 0xda08, 0x11d3, { \
+ 0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+#define NS_BUFFEREDOUTPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/buffered-output-stream;1"
+#define NS_BUFFEREDOUTPUTSTREAM_CID \
+ { /* 9868b4ce-da08-11d3-8cda-0060b0fc14a3 */ \
+ 0x9868b4ce, 0xda08, 0x11d3, { \
+ 0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+// components implementing nsISafeOutputStream
+#define NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/atomic-file-output-stream;1"
+#define NS_ATOMICLOCALFILEOUTPUTSTREAM_CID \
+ { /* 6EAE857E-4BA9-11E3-9B39-B4036188709B */ \
+ 0x6EAE857E, 0x4BA9, 0x11E3, { \
+ 0x9b, 0x39, 0xb4, 0x03, 0x61, 0x88, 0x70, 0x9b \
+ } \
+ }
+
+#define NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/safe-file-output-stream;1"
+#define NS_SAFELOCALFILEOUTPUTSTREAM_CID \
+ { /* a181af0d-68b8-4308-94db-d4f859058215 */ \
+ 0xa181af0d, 0x68b8, 0x4308, { \
+ 0x94, 0xdb, 0xd4, 0xf8, 0x59, 0x05, 0x82, 0x15 \
+ } \
+ }
+
+/**
+ * Contract ID for a service implementing nsIURIClassifier that identifies
+ * phishing and malware sites.
+ */
+#define NS_URICLASSIFIERSERVICE_CONTRACTID "@mozilla.org/uriclassifierservice"
+
+// service implementing nsINetworkPredictor
+#define NS_NETWORKPREDICTOR_CONTRACTID "@mozilla.org/network/predictor;1"
+#define NS_NETWORKPREDICTOR_CID \
+ { /* {969adfdf-7221-4419-aecf-05f8faf00c9b} */ \
+ 0x969adfdf, 0x7221, 0x4419, { \
+ 0xae, 0xcf, 0x05, 0xf8, 0xfa, 0xf0, 0x0c, 0x9b \
+ } \
+ }
+
+// captive portal service implementing nsICaptivePortalService
+#define NS_CAPTIVEPORTAL_CONTRACTID \
+ "@mozilla.org/network/captive-portal-service;1"
+#define NS_CAPTIVEPORTAL_CID \
+ { /* bdbe0555-fc3d-4f7b-9205-c309ceb2d641 */ \
+ 0xbdbe0555, 0xfc3d, 0x4f7b, { \
+ 0x92, 0x05, 0xc3, 0x09, 0xce, 0xb2, 0xd6, 0x41 \
+ } \
+ }
+
+#define NS_NETWORKCONNECTIVITYSERVICE_CONTRACTID \
+ "@mozilla.org/network/network-connectivity-service;1"
+#define NS_NETWORKCONNECTIVITYSERVICE_CID \
+ { /* 2693457e-3ba5-4455-991f-5350946adb12 */ \
+ 0x2693457e, 0x3ba5, 0x4455, { \
+ 0x99, 0x1f, 0x53, 0x50, 0x94, 0x6a, 0xdb, 0x12 \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/cache2/ classes
+ */
+
+// service implementing nsICacheService.
+#define NS_CACHESERVICE_CONTRACTID "@mozilla.org/network/cache-service;1"
+#define NS_CACHESERVICE_CID \
+ { /* 6c84aec9-29a5-4264-8fbc-bee8f922ea67 */ \
+ 0x6c84aec9, 0x29a5, 0x4264, { \
+ 0x8f, 0xbc, 0xbe, 0xe8, 0xf9, 0x22, 0xea, 0x67 \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/protocol/http/ classes
+ */
+
+#define NS_HTTPPROTOCOLHANDLER_CID \
+ { /* 4f47e42e-4d23-4dd3-bfda-eb29255e9ea3 */ \
+ 0x4f47e42e, 0x4d23, 0x4dd3, { \
+ 0xbf, 0xda, 0xeb, 0x29, 0x25, 0x5e, 0x9e, 0xa3 \
+ } \
+ }
+
+#define NS_HTTPSPROTOCOLHANDLER_CID \
+ { /* dccbe7e4-7750-466b-a557-5ea36c8ff24e */ \
+ 0xdccbe7e4, 0x7750, 0x466b, { \
+ 0xa5, 0x57, 0x5e, 0xa3, 0x6c, 0x8f, 0xf2, 0x4e \
+ } \
+ }
+
+#define NS_HTTPBASICAUTH_CID \
+ { /* fca3766a-434a-4ae7-83cf-0909e18a093a */ \
+ 0xfca3766a, 0x434a, 0x4ae7, { \
+ 0x83, 0xcf, 0x09, 0x09, 0xe1, 0x8a, 0x09, 0x3a \
+ } \
+ }
+
+#define NS_HTTPDIGESTAUTH_CID \
+ { /* 17491ba4-1dd2-11b2-aae3-de6b92dab620 */ \
+ 0x17491ba4, 0x1dd2, 0x11b2, { \
+ 0xaa, 0xe3, 0xde, 0x6b, 0x92, 0xda, 0xb6, 0x20 \
+ } \
+ }
+
+#define NS_HTTPNTLMAUTH_CID \
+ { /* bbef8185-c628-4cc1-b53e-e61e74c2451a */ \
+ 0xbbef8185, 0xc628, 0x4cc1, { \
+ 0xb5, 0x3e, 0xe6, 0x1e, 0x74, 0xc2, 0x45, 0x1a \
+ } \
+ }
+
+#define NS_HTTPAUTHMANAGER_CONTRACTID "@mozilla.org/network/http-auth-manager;1"
+#define NS_HTTPAUTHMANAGER_CID \
+ { /* 36b63ef3-e0fa-4c49-9fd4-e065e85568f4 */ \
+ 0x36b63ef3, 0xe0fa, 0x4c49, { \
+ 0x9f, 0xd4, 0xe0, 0x65, 0xe8, 0x55, 0x68, 0xf4 \
+ } \
+ }
+
+#define NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID \
+ "@mozilla.org/network/http-activity-distributor;1"
+#define NS_HTTPACTIVITYDISTRIBUTOR_CID \
+ { /* 15629ada-a41c-4a09-961f-6553cd60b1a2 */ \
+ 0x15629ada, 0xa41c, 0x4a09, { \
+ 0x96, 0x1f, 0x65, 0x53, 0xcd, 0x60, 0xb1, 0xa2 \
+ } \
+ }
+
+#define NS_THROTTLEQUEUE_CONTRACTID "@mozilla.org/network/throttlequeue;1"
+#define NS_THROTTLEQUEUE_CID \
+ { /* 4c39159c-cd90-4dd3-97a7-06af5e6d84c4 */ \
+ 0x4c39159c, 0xcd90, 0x4dd3, { \
+ 0x97, 0xa7, 0x06, 0xaf, 0x5e, 0x6d, 0x84, 0xc4 \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/protocol/res/ classes
+ */
+
+#define NS_RESPROTOCOLHANDLER_CID \
+ { /* e64f152a-9f07-11d3-8cda-0060b0fc14a3 */ \
+ 0xe64f152a, 0x9f07, 0x11d3, { \
+ 0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+#define NS_EXTENSIONPROTOCOLHANDLER_CID \
+ { /* aea16cd0-f020-4138-b068-0716c4a15b5a */ \
+ 0xaea16cd0, 0xf020, 0x4138, { \
+ 0xb0, 0x68, 0x07, 0x16, 0xc4, 0xa1, 0x5b, 0x5a \
+ } \
+ }
+
+#define NS_SUBSTITUTINGURL_CID \
+ { \
+ 0xdea9657c, 0x18cf, 0x4984, { \
+ 0xbd, 0xe9, 0xcc, 0xef, 0x5d, 0x8a, 0xb4, 0x73 \
+ } \
+ }
+
+#define NS_SUBSTITUTINGURLMUTATOR_CID \
+ { \
+ 0xb3cfeb91, 0x332a, 0x46c9, { \
+ 0xad, 0x97, 0x93, 0xff, 0x39, 0x84, 0x14, 0x94 \
+ } \
+ }
+
+#define NS_SUBSTITUTINGJARURI_CID \
+ { /* 50d50ddf-f16a-4652-8705-936b19c3763b */ \
+ 0x50d50ddf, 0xf16a, 0x4652, { \
+ 0x87, 0x05, 0x93, 0x6b, 0x19, 0xc3, 0x76, 0x3b \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/protocol/file/ classes
+ */
+
+#define NS_FILEPROTOCOLHANDLER_CID \
+ { /* fbc81170-1f69-11d3-9344-00104ba0fd40 */ \
+ 0xfbc81170, 0x1f69, 0x11d3, { \
+ 0x93, 0x44, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40 \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/protocol/data/ classes
+ */
+
+#define NS_DATAPROTOCOLHANDLER_CID \
+ { /* {B6ED3030-6183-11d3-A178-0050041CAF44} */ \
+ 0xb6ed3030, 0x6183, 0x11d3, { \
+ 0xa1, 0x78, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44 \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/protocol/viewsource/ classes
+ */
+
+// service implementing nsIProtocolHandler
+#define NS_VIEWSOURCEHANDLER_CID \
+ { /* {0x9c7ec5d1-23f9-11d5-aea8-8fcc0793e97f} */ \
+ 0x9c7ec5d1, 0x23f9, 0x11d5, { \
+ 0xae, 0xa8, 0x8f, 0xcc, 0x07, 0x93, 0xe9, 0x7f \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/protocol/websocket/ classes
+ */
+
+#define NS_WEBSOCKETPROTOCOLHANDLER_CID \
+ { /* {dc01db59-a513-4c90-824b-085cce06c0aa} */ \
+ 0xdc01db59, 0xa513, 0x4c90, { \
+ 0x82, 0x4b, 0x08, 0x5c, 0xce, 0x06, 0xc0, 0xaa \
+ } \
+ }
+
+#define NS_WEBSOCKETSSLPROTOCOLHANDLER_CID \
+ { /* {dc01dbbb-a5bb-4cbb-82bb-085cce06c0bb} */ \
+ 0xdc01dbbb, 0xa5bb, 0x4cbb, { \
+ 0x82, 0xbb, 0x08, 0x5c, 0xce, 0x06, 0xc0, 0xbb \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/protocol/about/ classes
+ */
+
+#define NS_ABOUTPROTOCOLHANDLER_CID \
+ { /* 9e3b6c90-2f75-11d3-8cd0-0060b0fc14a3 */ \
+ 0x9e3b6c90, 0x2f75, 0x11d3, { \
+ 0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+#define NS_SAFEABOUTPROTOCOLHANDLER_CID \
+ { /* 1423e739-782c-4081-b5d8-fe6fba68c0ef */ \
+ 0x1423e739, 0x782c, 0x4081, { \
+ 0xb5, 0xd8, 0xfe, 0x6f, 0xba, 0x68, 0xc0, 0xef \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/dns/ classes
+ */
+
+#define NS_DNSSERVICE_CONTRACTID "@mozilla.org/network/dns-service;1"
+#define NS_DNSSERVICE_CID \
+ { /* b0ff4572-dae4-4bef-a092-83c1b88f6be9 */ \
+ 0xb0ff4572, 0xdae4, 0x4bef, { \
+ 0xa0, 0x92, 0x83, 0xc1, 0xb8, 0x8f, 0x6b, 0xe9 \
+ } \
+ }
+
+/* ContractID of the XPCOM package that implements nsIIDNService */
+#define NS_IDNSERVICE_CONTRACTID "@mozilla.org/network/idn-service;1"
+#define NS_IDNSERVICE_CID \
+ { /* 62b778a6-bce3-456b-8c31-2865fbb68c91 */ \
+ 0x62b778a6, 0xbce3, 0x456b, { \
+ 0x8c, 0x31, 0x28, 0x65, 0xfb, 0xb6, 0x8c, 0x91 \
+ } \
+ }
+
+#define NS_EFFECTIVETLDSERVICE_CONTRACTID \
+ "@mozilla.org/network/effective-tld-service;1"
+#define NS_EFFECTIVETLDSERVICE_CID \
+ { /* cb9abbae-66b6-4609-8594-5c4ff300888e */ \
+ 0xcb9abbae, 0x66b6, 0x4609, { \
+ 0x85, 0x94, 0x5c, 0x4f, 0xf3, 0x00, 0x88, 0x8e \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/mime classes
+ */
+
+// {1F4DBCF7-245C-4c8c-943D-8A1DA0495E8A}
+#define NS_MIMEHEADERPARAM_CID \
+ { \
+ 0x1f4dbcf7, 0x245c, 0x4c8c, { \
+ 0x94, 0x3d, 0x8a, 0x1d, 0xa0, 0x49, 0x5e, 0x8a \
+ } \
+ }
+
+#define NS_MIMEHEADERPARAM_CONTRACTID "@mozilla.org/network/mime-hdrparam;1"
+
+/******************************************************************************
+ * netwerk/socket classes
+ */
+
+#define NS_SOCKSSOCKETPROVIDER_CID \
+ { /* 8dbe7246-1dd2-11b2-9b8f-b9a849e4403a */ \
+ 0x8dbe7246, 0x1dd2, 0x11b2, { \
+ 0x9b, 0x8f, 0xb9, 0xa8, 0x49, 0xe4, 0x40, 0x3a \
+ } \
+ }
+
+#define NS_SOCKS4SOCKETPROVIDER_CID \
+ { /* F7C9F5F4-4451-41c3-A28A-5BA2447FBACE */ \
+ 0xf7c9f5f4, 0x4451, 0x41c3, { \
+ 0xa2, 0x8a, 0x5b, 0xa2, 0x44, 0x7f, 0xba, 0xce \
+ } \
+ }
+
+#define NS_UDPSOCKETPROVIDER_CID \
+ { /* 320706D2-2E81-42c6-89C3-8D83B17D3FB4 */ \
+ 0x320706d2, 0x2e81, 0x42c6, { \
+ 0x89, 0xc3, 0x8d, 0x83, 0xb1, 0x7d, 0x3f, 0xb4 \
+ } \
+ }
+
+#define NS_DASHBOARD_CONTRACTID "@mozilla.org/network/dashboard;1"
+#define NS_DASHBOARD_CID \
+ { /*c79eb3c6-091a-45a6-8544-5a8d1ab79537 */ \
+ 0xc79eb3c6, 0x091a, 0x45a6, { \
+ 0x85, 0x44, 0x5a, 0x8d, 0x1a, 0xb7, 0x95, 0x37 \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/cookie classes
+ */
+
+// service implementing nsICookieManager
+#define NS_COOKIEMANAGER_CONTRACTID "@mozilla.org/cookiemanager;1"
+#define NS_COOKIEMANAGER_CID \
+ { /* aaab6710-0f2c-11d5-a53b-0010a401eb10 */ \
+ 0xaaab6710, 0x0f2c, 0x11d5, { \
+ 0xa5, 0x3b, 0x00, 0x10, 0xa4, 0x01, 0xeb, 0x10 \
+ } \
+ }
+
+// service implementing nsICookieService.
+#define NS_COOKIESERVICE_CONTRACTID "@mozilla.org/cookieService;1"
+#define NS_COOKIESERVICE_CID \
+ { /* c375fa80-150f-11d6-a618-0010a401eb10 */ \
+ 0xc375fa80, 0x150f, 0x11d6, { \
+ 0xa6, 0x18, 0x00, 0x10, 0xa4, 0x01, 0xeb, 0x10 \
+ } \
+ }
+
+/******************************************************************************
+ * netwerk/wifi classes
+ */
+#ifdef NECKO_WIFI
+# define NS_WIFI_MONITOR_CONTRACTID "@mozilla.org/wifi/monitor;1"
+
+# define NS_WIFI_MONITOR_COMPONENT_CID \
+ { \
+ 0x3FF8FB9F, 0xEE63, 0x48DF, { \
+ 0x89, 0xF0, 0xDA, 0xCE, 0x02, 0x42, 0xFD, 0x82 \
+ } \
+ }
+#endif
+
+/******************************************************************************
+ * netwerk/streamconv classes
+ */
+
+// service implementing nsIStreamConverterService
+#define NS_STREAMCONVERTERSERVICE_CONTRACTID "@mozilla.org/streamConverters;1"
+#define NS_STREAMCONVERTERSERVICE_CID \
+ { /* 892FFEB0-3F80-11d3-A16C-0050041CAF44 */ \
+ 0x892ffeb0, 0x3f80, 0x11d3, { \
+ 0xa1, 0x6c, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44 \
+ } \
+ }
+
+/**
+ * Detector that can act as either an nsIStreamConverter or an
+ * nsIContentSniffer to decide whether text/plain data is "really" text/plain
+ * or APPLICATION_GUESS_FROM_EXT. Use with CreateInstance.
+ */
+#define NS_BINARYDETECTOR_CONTRACTID "@mozilla.org/network/binary-detector;1"
+
+/******************************************************************************
+ * netwerk/system classes
+ */
+
+// service implementing nsINetworkLinkService
+#define NS_NETWORK_LINK_SERVICE_CID \
+ { \
+ 0x75a500a2, 0x0030, 0x40f7, { \
+ 0x86, 0xf8, 0x63, 0xf2, 0x25, 0xb9, 0x40, 0xae \
+ } \
+ }
+
+/******************************************************************************
+ * Contracts that can be implemented by necko users.
+ */
+
+/**
+ * This contract ID will be gotten as a service implementing
+ * nsINetworkLinkService and monitored by IOService for automatic online/offline
+ * management.
+ *
+ * Must implement nsINetworkLinkService
+ */
+#define NS_NETWORK_LINK_SERVICE_CONTRACTID \
+ "@mozilla.org/network/network-link-service;1"
+
+/**
+ * This contract ID is used when Necko needs to wrap an nsIAuthPrompt as
+ * nsIAuthPrompt2. Implementing it is required for backwards compatibility
+ * with Versions before 1.9.
+ *
+ * Must implement nsIAuthPromptAdapterFactory
+ */
+#define NS_AUTHPROMPT_ADAPTER_FACTORY_CONTRACTID \
+ "@mozilla.org/network/authprompt-adapter-factory;1"
+
+/**
+ * Must implement nsICryptoHash.
+ */
+#define NS_CRYPTO_HASH_CONTRACTID "@mozilla.org/security/hash;1"
+
+/******************************************************************************
+ * Categories
+ */
+/**
+ * Services registered in this category will get notified via
+ * nsIChannelEventSink about all redirects that happen and have the opportunity
+ * to veto them. The value of the category entries is interpreted as the
+ * contract ID of the service.
+ */
+#define NS_CHANNEL_EVENT_SINK_CATEGORY "net-channel-event-sinks"
+
+/**
+ * Services in this category will get told about each load that happens and get
+ * the opportunity to override the detected MIME type via nsIContentSniffer.
+ * Services should not set the MIME type on the channel directly, but return the
+ * new type. If getMIMETypeFromContent throws an exception, the type will remain
+ * unchanged.
+ *
+ * Note that only channels with the LOAD_CALL_CONTENT_SNIFFERS flag will call
+ * content sniffers. Also note that there can be security implications about
+ * changing the MIME type -- proxies filtering responses based on their MIME
+ * type might consider certain types to be safe, which these sniffers can
+ * override.
+ *
+ * Not all channels may implement content sniffing. See also
+ * nsIChannel::LOAD_CALL_CONTENT_SNIFFERS.
+ */
+#define NS_CONTENT_SNIFFER_CATEGORY "net-content-sniffers"
+
+/**
+ * Services in this category can sniff content that is not necessarily loaded
+ * from the network, and they won't be told about each load.
+ */
+#define NS_DATA_SNIFFER_CATEGORY "content-sniffing-services"
+
+/**
+ * Services in this category similar to "net-content-sniffers" but it's
+ * initiated by the opaque response blocking algorithm.
+ */
+#define NS_ORB_SNIFFER_CATEGORY "orb-content-sniffers"
+
+/**
+ * Services in this category are the union of "net-content-sniffers" and
+ * "orb-content-sniffers".
+ */
+#define NS_CONTENT_AND_ORB_SNIFFER_CATEGORY "net-and-orb-content-sniffers"
+
+/**
+ * Must implement nsINSSErrorsService.
+ */
+#define NS_NSS_ERRORS_SERVICE_CONTRACTID "@mozilla.org/nss_errors_service;1"
+
+/**
+ * LoadContextInfo factory
+ */
+#define NS_NSILOADCONTEXTINFOFACTORY_CONTRACTID \
+ "@mozilla.org/load-context-info-factory;1"
+#define NS_NSILOADCONTEXTINFOFACTORY_CID \
+ { /* 62d4b190-3642-4450-b019-d1c1fba56025 */ \
+ 0x62d4b190, 0x3642, 0x4450, { \
+ 0xb0, 0x19, 0xd1, 0xc1, 0xfb, 0xa5, 0x60, 0x25 \
+ } \
+ }
+
+#endif // nsNetCID_h__
diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp
new file mode 100644
index 0000000000..a54275b697
--- /dev/null
+++ b/netwerk/build/nsNetModule.cpp
@@ -0,0 +1,249 @@
+/* -*- 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/. */
+
+#define ALLOW_LATE_HTTPLOG_H_INCLUDE 1
+#include "base/basictypes.h"
+
+#include "nsCOMPtr.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/Components.h"
+#include "mozilla/ModuleUtils.h"
+#include "nscore.h"
+#include "nsSimpleURI.h"
+#include "nsLoadGroup.h"
+#include "nsMimeTypes.h"
+#include "nsDNSPrefetch.h"
+#include "nsXULAppAPI.h"
+#include "nsCategoryCache.h"
+#include "nsIContentSniffer.h"
+#include "nsStandardURL.h"
+#include "mozilla/net/BackgroundChannelRegistrar.h"
+#include "mozilla/net/NeckoChild.h"
+#ifdef MOZ_AUTH_EXTENSION
+# include "nsAuthGSSAPI.h"
+#endif
+
+#include "nsNetCID.h"
+
+#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
+# define BUILD_NETWORK_INFO_SERVICE 1
+#endif
+
+using namespace mozilla;
+
+using ContentSnifferCache = nsCategoryCache<nsIContentSniffer>;
+ContentSnifferCache* gNetSniffers = nullptr;
+ContentSnifferCache* gDataSniffers = nullptr;
+ContentSnifferCache* gORBSniffers = nullptr;
+ContentSnifferCache* gNetAndORBSniffers = nullptr;
+
+#define static
+using nsLoadGroup = mozilla::net::nsLoadGroup;
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsLoadGroup, Init)
+#undef static
+
+///////////////////////////////////////////////////////////////////////////////
+// protocols
+///////////////////////////////////////////////////////////////////////////////
+
+// http/https
+#include "nsHttpHandler.h"
+#include "Http2Compression.h"
+#undef LOG
+#undef LOG_ENABLED
+#include "nsHttpAuthManager.h"
+#include "nsHttpActivityDistributor.h"
+#include "ThrottleQueue.h"
+#undef LOG
+#undef LOG_ENABLED
+
+NS_IMPL_COMPONENT_FACTORY(net::nsHttpHandler) {
+ return net::nsHttpHandler::GetInstance().downcast<nsIHttpProtocolHandler>();
+}
+
+NS_IMPL_COMPONENT_FACTORY(net::nsHttpsHandler) {
+ auto handler = MakeRefPtr<net::nsHttpsHandler>();
+
+ if (NS_FAILED(handler->Init())) {
+ return nullptr;
+ }
+ return handler.forget().downcast<nsIHttpProtocolHandler>();
+}
+
+#include "WebSocketChannel.h"
+#include "WebSocketChannelChild.h"
+namespace mozilla::net {
+static BaseWebSocketChannel* WebSocketChannelConstructor(bool aSecure) {
+ if (IsNeckoChild()) {
+ return new WebSocketChannelChild(aSecure);
+ }
+
+ if (aSecure) {
+ return new WebSocketSSLChannel;
+ }
+ return new WebSocketChannel;
+}
+
+#define WEB_SOCKET_HANDLER_CONSTRUCTOR(type, secure) \
+ nsresult type##Constructor(REFNSIID aIID, void** aResult) { \
+ RefPtr<BaseWebSocketChannel> inst; \
+ \
+ *aResult = nullptr; \
+ inst = WebSocketChannelConstructor(secure); \
+ return inst->QueryInterface(aIID, aResult); \
+ }
+
+WEB_SOCKET_HANDLER_CONSTRUCTOR(WebSocketChannel, false)
+WEB_SOCKET_HANDLER_CONSTRUCTOR(WebSocketSSLChannel, true)
+#undef WEB_SOCKET_HANDLER_CONSTRUCTOR
+} // namespace mozilla::net
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsStreamConverterService.h"
+#include "nsMultiMixedConv.h"
+#include "nsHTTPCompressConv.h"
+#include "mozTXTToHTMLConv.h"
+#include "nsUnknownDecoder.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsIndexedToHTML.h"
+
+nsresult NS_NewMultiMixedConv(nsMultiMixedConv** result);
+nsresult MOZ_NewTXTToHTMLConv(mozTXTToHTMLConv** result);
+nsresult NS_NewHTTPCompressConv(mozilla::net::nsHTTPCompressConv** result);
+nsresult NS_NewStreamConv(nsStreamConverterService** aStreamConv);
+
+nsresult CreateNewStreamConvServiceFactory(REFNSIID aIID, void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ RefPtr<nsStreamConverterService> inst;
+ nsresult rv = NS_NewStreamConv(getter_AddRefs(inst));
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ return rv;
+}
+
+nsresult CreateNewMultiMixedConvFactory(REFNSIID aIID, void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ RefPtr<nsMultiMixedConv> inst;
+ nsresult rv = NS_NewMultiMixedConv(getter_AddRefs(inst));
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ return rv;
+}
+
+nsresult CreateNewTXTToHTMLConvFactory(REFNSIID aIID, void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ RefPtr<mozTXTToHTMLConv> inst;
+ nsresult rv = MOZ_NewTXTToHTMLConv(getter_AddRefs(inst));
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ return rv;
+}
+
+nsresult CreateNewHTTPCompressConvFactory(REFNSIID aIID, void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ RefPtr<mozilla::net::nsHTTPCompressConv> inst;
+ nsresult rv = NS_NewHTTPCompressConv(getter_AddRefs(inst));
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ return rv;
+}
+
+nsresult CreateNewUnknownDecoderFactory(REFNSIID aIID, void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aResult = nullptr;
+
+ RefPtr<nsUnknownDecoder> inst = new nsUnknownDecoder();
+ return inst->QueryInterface(aIID, aResult);
+}
+
+nsresult CreateNewBinaryDetectorFactory(REFNSIID aIID, void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aResult = nullptr;
+
+ RefPtr<nsBinaryDetector> inst = new nsBinaryDetector();
+ return inst->QueryInterface(aIID, aResult);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Module implementation for the net library
+
+// Net module startup hook
+nsresult nsNetStartup() {
+ mozilla::net::nsStandardURL::InitGlobalObjects();
+ return NS_OK;
+}
+
+// Net module shutdown hook
+void nsNetShutdown() {
+ // Release the url parser that the stdurl is holding.
+ mozilla::net::nsStandardURL::ShutdownGlobalObjects();
+
+ // Release global state used by the URL helper module.
+ net_ShutdownURLHelper();
+#ifdef XP_MACOSX
+ net_ShutdownURLHelperOSX();
+#endif
+
+ // Release DNS service reference.
+ nsDNSPrefetch::Shutdown();
+
+ // Release the Websocket Admission Manager
+ mozilla::net::WebSocketChannel::Shutdown();
+
+ mozilla::net::Http2CompressionCleanup();
+
+#ifdef MOZ_AUTH_EXTENSION
+ nsAuthGSSAPI::Shutdown();
+#endif
+
+ delete gNetSniffers;
+ gNetSniffers = nullptr;
+ delete gDataSniffers;
+ gDataSniffers = nullptr;
+ delete gORBSniffers;
+ gORBSniffers = nullptr;
+ delete gNetAndORBSniffers;
+ gNetAndORBSniffers = nullptr;
+}
diff --git a/netwerk/build/nsNetModule.h b/netwerk/build/nsNetModule.h
new file mode 100644
index 0000000000..cb3da83220
--- /dev/null
+++ b/netwerk/build/nsNetModule.h
@@ -0,0 +1,33 @@
+/* -*- 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 nsNetModule_h
+#define nsNetModule_h
+
+#include "nsID.h"
+
+class nsISupports;
+
+nsresult nsNetStartup();
+void nsNetShutdown();
+
+nsresult CreateNewStreamConvServiceFactory(const nsIID& aIID, void** aResult);
+nsresult CreateNewMultiMixedConvFactory(const nsIID& aIID, void** aResult);
+nsresult CreateNewTXTToHTMLConvFactory(const nsIID& aIID, void** aResult);
+nsresult CreateNewHTTPCompressConvFactory(const nsIID& aIID, void** aResult);
+nsresult CreateNewUnknownDecoderFactory(const nsIID& aIID, void** aResult);
+nsresult CreateNewBinaryDetectorFactory(const nsIID& aIID, void** aResult);
+nsresult nsLoadGroupConstructor(const nsIID& aIID, void** aResult);
+
+extern nsresult net_NewIncrementalDownload(const nsIID&, void**);
+
+namespace mozilla {
+namespace net {
+nsresult WebSocketChannelConstructor(const nsIID& aIID, void** aResult);
+nsresult WebSocketSSLChannelConstructor(const nsIID& aIID, void** aResult);
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheEntry.cpp b/netwerk/cache2/CacheEntry.cpp
new file mode 100644
index 0000000000..142d32d274
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -0,0 +1,1952 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include <math.h>
+
+#include "CacheEntry.h"
+
+#include "CacheFileUtils.h"
+#include "CacheIndex.h"
+#include "CacheLog.h"
+#include "CacheObserver.h"
+#include "CacheStorageService.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/psm/TransportSecurityInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheStorage.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsISizeOf.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::net {
+
+static uint32_t const ENTRY_WANTED = nsICacheEntryOpenCallback::ENTRY_WANTED;
+static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
+ nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
+static uint32_t const ENTRY_NEEDS_REVALIDATION =
+ nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
+static uint32_t const ENTRY_NOT_WANTED =
+ nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
+
+NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
+
+// CacheEntryHandle
+
+CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry) : mEntry(aEntry) {
+#ifdef DEBUG
+ if (!mEntry->HandlesCount()) {
+ // CacheEntry.mHandlesCount must go from zero to one only under
+ // the service lock. Can access CacheStorageService::Self() w/o a check
+ // since CacheEntry hrefs it.
+ CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
+ }
+#endif
+
+ mEntry->AddHandleRef();
+
+ LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
+}
+
+NS_IMETHODIMP CacheEntryHandle::Dismiss() {
+ LOG(("CacheEntryHandle::Dismiss %p", this));
+
+ if (mClosed.compareExchange(false, true)) {
+ mEntry->OnHandleClosed(this);
+ return NS_OK;
+ }
+
+ LOG((" already dropped"));
+ return NS_ERROR_UNEXPECTED;
+}
+
+CacheEntryHandle::~CacheEntryHandle() {
+ mEntry->ReleaseHandleRef();
+ Dismiss();
+
+ LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
+}
+
+// CacheEntry::Callback
+
+CacheEntry::Callback::Callback(CacheEntry* aEntry,
+ nsICacheEntryOpenCallback* aCallback,
+ bool aReadOnly, bool aCheckOnAnyThread,
+ bool aSecret)
+ : mEntry(aEntry),
+ mCallback(aCallback),
+ mTarget(GetCurrentSerialEventTarget()),
+ mReadOnly(aReadOnly),
+ mRevalidating(false),
+ mCheckOnAnyThread(aCheckOnAnyThread),
+ mRecheckAfterWrite(false),
+ mNotWanted(false),
+ mSecret(aSecret),
+ mDoomWhenFoundPinned(false),
+ mDoomWhenFoundNonPinned(false) {
+ MOZ_COUNT_CTOR(CacheEntry::Callback);
+
+ // The counter may go from zero to non-null only under the service lock
+ // but here we expect it to be already positive.
+ MOZ_ASSERT(mEntry->HandlesCount());
+ mEntry->AddHandleRef();
+}
+
+CacheEntry::Callback::Callback(CacheEntry* aEntry,
+ bool aDoomWhenFoundInPinStatus)
+ : mEntry(aEntry),
+ mReadOnly(false),
+ mRevalidating(false),
+ mCheckOnAnyThread(true),
+ mRecheckAfterWrite(false),
+ mNotWanted(false),
+ mSecret(false),
+ mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus),
+ mDoomWhenFoundNonPinned(!aDoomWhenFoundInPinStatus) {
+ MOZ_COUNT_CTOR(CacheEntry::Callback);
+ MOZ_ASSERT(mEntry->HandlesCount());
+ mEntry->AddHandleRef();
+}
+
+CacheEntry::Callback::Callback(CacheEntry::Callback const& aThat)
+ : mEntry(aThat.mEntry),
+ mCallback(aThat.mCallback),
+ mTarget(aThat.mTarget),
+ mReadOnly(aThat.mReadOnly),
+ mRevalidating(aThat.mRevalidating),
+ mCheckOnAnyThread(aThat.mCheckOnAnyThread),
+ mRecheckAfterWrite(aThat.mRecheckAfterWrite),
+ mNotWanted(aThat.mNotWanted),
+ mSecret(aThat.mSecret),
+ mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned),
+ mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned) {
+ MOZ_COUNT_CTOR(CacheEntry::Callback);
+
+ // The counter may go from zero to non-null only under the service lock
+ // but here we expect it to be already positive.
+ MOZ_ASSERT(mEntry->HandlesCount());
+ mEntry->AddHandleRef();
+}
+
+CacheEntry::Callback::~Callback() {
+ ProxyRelease("CacheEntry::Callback::mCallback", mCallback, mTarget);
+
+ mEntry->ReleaseHandleRef();
+ MOZ_COUNT_DTOR(CacheEntry::Callback);
+}
+
+// We have locks on both this and aEntry
+void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) {
+ aEntry->mLock.AssertCurrentThreadOwns();
+ mEntry->mLock.AssertCurrentThreadOwns();
+ if (mEntry == aEntry) return;
+
+ // The counter may go from zero to non-null only under the service lock
+ // but here we expect it to be already positive.
+ MOZ_ASSERT(aEntry->HandlesCount());
+ aEntry->AddHandleRef();
+ mEntry->ReleaseHandleRef();
+ mEntry = aEntry;
+}
+
+// This is called on entries in another entry's mCallback array, under the lock
+// of that other entry. No other threads can access this entry at this time.
+bool CacheEntry::Callback::DeferDoom(bool* aDoom) const
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ MOZ_ASSERT(mEntry->mPinningKnown);
+
+ if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) ||
+ MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
+ *aDoom =
+ (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) &&
+ MOZ_LIKELY(!mEntry->mPinned)) ||
+ (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
+
+ return true;
+ }
+
+ return false;
+}
+
+nsresult CacheEntry::Callback::OnCheckThread(bool* aOnCheckThread) const {
+ if (!mCheckOnAnyThread) {
+ // Check we are on the target
+ return mTarget->IsOnCurrentThread(aOnCheckThread);
+ }
+
+ // We can invoke check anywhere
+ *aOnCheckThread = true;
+ return NS_OK;
+}
+
+nsresult CacheEntry::Callback::OnAvailThread(bool* aOnAvailThread) const {
+ return mTarget->IsOnCurrentThread(aOnAvailThread);
+}
+
+// CacheEntry
+
+NS_IMPL_ISUPPORTS(CacheEntry, nsIRunnable, CacheFileListener)
+
+/* static */
+uint64_t CacheEntry::GetNextId() {
+ static Atomic<uint64_t, Relaxed> id(0);
+ return ++id;
+}
+
+CacheEntry::CacheEntry(const nsACString& aStorageID, const nsACString& aURI,
+ const nsACString& aEnhanceID, bool aUseDisk,
+ bool aSkipSizeCheck, bool aPin)
+ : mURI(aURI),
+ mEnhanceID(aEnhanceID),
+ mStorageID(aStorageID),
+ mUseDisk(aUseDisk),
+ mSkipSizeCheck(aSkipSizeCheck),
+ mPinned(aPin),
+ mSecurityInfoLoaded(false),
+ mPreventCallbacks(false),
+ mHasData(false),
+ mPinningKnown(false),
+ mCacheEntryId(GetNextId()) {
+ LOG(("CacheEntry::CacheEntry [this=%p]", this));
+
+ mService = CacheStorageService::Self();
+
+ CacheStorageService::Self()->RecordMemoryOnlyEntry(this, !aUseDisk,
+ true /* overwrite */);
+}
+
+CacheEntry::~CacheEntry() { LOG(("CacheEntry::~CacheEntry [this=%p]", this)); }
+
+char const* CacheEntry::StateString(uint32_t aState) {
+ switch (aState) {
+ case NOTLOADED:
+ return "NOTLOADED";
+ case LOADING:
+ return "LOADING";
+ case EMPTY:
+ return "EMPTY";
+ case WRITING:
+ return "WRITING";
+ case READY:
+ return "READY";
+ case REVALIDATING:
+ return "REVALIDATING";
+ }
+
+ return "?";
+}
+
+nsresult CacheEntry::HashingKeyWithStorage(nsACString& aResult) const {
+ return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
+}
+
+nsresult CacheEntry::HashingKey(nsACString& aResult) const {
+ return HashingKey(""_ns, mEnhanceID, mURI, aResult);
+}
+
+// static
+nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
+ const nsACString& aEnhanceID, nsIURI* aURI,
+ nsACString& aResult) {
+ nsAutoCString spec;
+ nsresult rv = aURI->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return HashingKey(aStorageID, aEnhanceID, spec, aResult);
+}
+
+// static
+nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
+ const nsACString& aEnhanceID,
+ const nsACString& aURISpec,
+ nsACString& aResult) {
+ /**
+ * This key is used to salt hash that is a base for disk file name.
+ * Changing it will cause we will not be able to find files on disk.
+ */
+
+ aResult.Assign(aStorageID);
+
+ if (!aEnhanceID.IsEmpty()) {
+ CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
+ }
+
+ // Appending directly
+ aResult.Append(':');
+ aResult.Append(aURISpec);
+
+ return NS_OK;
+}
+
+void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback,
+ uint32_t aFlags) {
+ bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
+ bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
+ bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
+ bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
+ bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
+ bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY;
+
+ if (MOZ_LOG_TEST(gCache2Log, LogLevel::Debug)) {
+ MutexAutoLock lock(mLock);
+ LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
+ this, StateString(mState), aFlags, aCallback));
+ }
+#ifdef DEBUG
+ {
+ // yes, if logging is on in DEBUG we'll take the lock twice in a row
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
+ MOZ_ASSERT(!(truncate && mState > LOADING),
+ "Must not call truncate on already loaded entry");
+ }
+#endif
+
+ Callback callback(this, aCallback, readonly, multithread, secret);
+
+ if (!Open(callback, truncate, priority, bypassIfBusy)) {
+ // We get here when the callback wants to bypass cache when it's busy.
+ LOG((" writing or revalidating, callback wants to bypass cache"));
+ callback.mNotWanted = true;
+ InvokeAvailableCallback(callback);
+ }
+}
+
+bool CacheEntry::Open(Callback& aCallback, bool aTruncate, bool aPriority,
+ bool aBypassIfBusy) {
+ mozilla::MutexAutoLock lock(mLock);
+
+ // Check state under the lock
+ if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
+ return false;
+ }
+
+ RememberCallback(aCallback);
+
+ // Load() opens the lock
+ if (Load(aTruncate, aPriority)) {
+ // Loading is in progress...
+ return true;
+ }
+
+ InvokeCallbacks();
+
+ return true;
+}
+
+bool CacheEntry::Load(bool aTruncate, bool aPriority) {
+ LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
+
+ mLock.AssertCurrentThreadOwns();
+
+ if (mState > LOADING) {
+ LOG((" already loaded"));
+ return false;
+ }
+
+ if (mState == LOADING) {
+ LOG((" already loading"));
+ return true;
+ }
+
+ mState = LOADING;
+
+ MOZ_ASSERT(!mFile);
+
+ nsresult rv;
+
+ nsAutoCString fileKey;
+ rv = HashingKeyWithStorage(fileKey);
+
+ bool reportMiss = false;
+
+ // Check the index under two conditions for two states and take appropriate
+ // action:
+ // 1. When this is a disk entry and not told to truncate, check there is a
+ // disk file.
+ // If not, set the 'truncate' flag to true so that this entry will open
+ // instantly as a new one.
+ // 2. When this is a memory-only entry, check there is a disk file.
+ // If there is or could be, doom that file.
+ if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
+ // Check the index right now to know we have or have not the entry
+ // as soon as possible.
+ CacheIndex::EntryStatus status;
+ if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
+ switch (status) {
+ case CacheIndex::DOES_NOT_EXIST:
+ // Doesn't apply to memory-only entries, Load() is called only once
+ // for them and never again for their session lifetime.
+ if (!aTruncate && mUseDisk) {
+ LOG(
+ (" entry doesn't exist according information from the index, "
+ "truncating"));
+ reportMiss = true;
+ aTruncate = true;
+ }
+ break;
+ case CacheIndex::EXISTS:
+ case CacheIndex::DO_NOT_KNOW:
+ if (!mUseDisk) {
+ LOG(
+ (" entry open as memory-only, but there is a file, status=%d, "
+ "dooming it",
+ status));
+ CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
+ }
+ break;
+ }
+ }
+ }
+
+ mFile = new CacheFile();
+
+ BackgroundOp(Ops::REGISTER);
+
+ bool directLoad = aTruncate || !mUseDisk;
+ if (directLoad) {
+ // mLoadStart will be used to calculate telemetry of life-time of this
+ // entry. Low resulution is then enough.
+ mLoadStart = TimeStamp::NowLoRes();
+ mPinningKnown = true;
+ } else {
+ mLoadStart = TimeStamp::Now();
+ }
+
+ {
+ mozilla::MutexAutoUnlock unlock(mLock);
+
+ if (reportMiss) {
+ CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
+ CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
+ }
+
+ LOG((" performing load, file=%p", mFile.get()));
+ if (NS_SUCCEEDED(rv)) {
+ rv = mFile->Init(fileKey, aTruncate, !mUseDisk, mSkipSizeCheck, aPriority,
+ mPinned, directLoad ? nullptr : this);
+ }
+
+ if (NS_FAILED(rv)) {
+ mFileStatus = rv;
+ AsyncDoom(nullptr);
+ return false;
+ }
+ }
+
+ if (directLoad) {
+ // Just fake the load has already been done as "new".
+ mFileStatus = NS_OK;
+ mState = EMPTY;
+ }
+
+ return mState == LOADING;
+}
+
+NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew) {
+ LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32 ", new=%d]", this,
+ static_cast<uint32_t>(aResult), aIsNew));
+
+ MOZ_ASSERT(!mLoadStart.IsNull());
+
+ if (NS_SUCCEEDED(aResult)) {
+ if (aIsNew) {
+ CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
+ CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
+ } else {
+ CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
+ CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
+ }
+ }
+
+ // OnFileReady, that is the only code that can transit from LOADING
+ // to any follow-on state and can only be invoked ones on an entry.
+ // Until this moment there is no consumer that could manipulate
+ // the entry state.
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mState == LOADING);
+
+ mState = (aIsNew || NS_FAILED(aResult)) ? EMPTY : READY;
+
+ mFileStatus = aResult;
+
+ mPinned = mFile->IsPinned();
+
+ mPinningKnown = true;
+ LOG((" pinning=%d", (bool)mPinned));
+
+ if (mState == READY) {
+ mHasData = true;
+
+ uint32_t frecency;
+ mFile->GetFrecency(&frecency);
+ // mFrecency is held in a double to increase computance precision.
+ // It is ok to persist frecency only as a uint32 with some math involved.
+ mFrecency = INT2FRECENCY(frecency);
+ }
+
+ InvokeCallbacks();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) {
+ if (mDoomCallback) {
+ RefPtr<DoomCallbackRunnable> event =
+ new DoomCallbackRunnable(this, aResult);
+ NS_DispatchToMainThread(event);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(
+ bool aMemoryOnly, nsICacheEntryOpenCallback* aCallback) {
+ LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
+
+ mLock.AssertCurrentThreadOwns();
+
+ // Hold callbacks invocation, AddStorageEntry would invoke from doom
+ // prematurly
+ mPreventCallbacks = true;
+
+ RefPtr<CacheEntryHandle> handle;
+ RefPtr<CacheEntry> newEntry;
+ {
+ if (mPinned) {
+ MOZ_ASSERT(mUseDisk);
+ // We want to pin even no-store entries (the case we recreate a disk entry
+ // as a memory-only entry.)
+ aMemoryOnly = false;
+ }
+
+ mozilla::MutexAutoUnlock unlock(mLock);
+
+ // The following call dooms this entry (calls DoomAlreadyRemoved on us)
+ nsresult rv = CacheStorageService::Self()->AddStorageEntry(
+ GetStorageID(), GetURI(), GetEnhanceID(), mUseDisk && !aMemoryOnly,
+ mSkipSizeCheck, mPinned,
+ nsICacheStorage::OPEN_TRUNCATE, // truncate existing (this one)
+ getter_AddRefs(handle));
+
+ if (NS_SUCCEEDED(rv)) {
+ newEntry = handle->Entry();
+ LOG((" exchanged entry %p by entry %p, rv=0x%08" PRIx32, this,
+ newEntry.get(), static_cast<uint32_t>(rv)));
+ newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
+ } else {
+ LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ AsyncDoom(nullptr);
+ }
+ }
+
+ mPreventCallbacks = false;
+
+ if (!newEntry) return nullptr;
+
+ newEntry->TransferCallbacks(*this);
+ mCallbacks.Clear();
+
+ // Must return a new write handle, since the consumer is expected to
+ // write to this newly recreated entry. The |handle| is only a common
+ // reference counter and doesn't revert entry state back when write
+ // fails and also doesn't update the entry frecency. Not updating
+ // frecency causes entries to not be purged from our memory pools.
+ RefPtr<CacheEntryHandle> writeHandle = newEntry->NewWriteHandle();
+ return writeHandle.forget();
+}
+
+void CacheEntry::TransferCallbacks(CacheEntry& aFromEntry) {
+ mozilla::MutexAutoLock lock(mLock);
+ aFromEntry.mLock.AssertCurrentThreadOwns();
+
+ LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", this, &aFromEntry));
+
+ if (!mCallbacks.Length()) {
+ mCallbacks.SwapElements(aFromEntry.mCallbacks);
+ } else {
+ mCallbacks.AppendElements(aFromEntry.mCallbacks);
+ }
+
+ uint32_t callbacksLength = mCallbacks.Length();
+ if (callbacksLength) {
+ // Carry the entry reference (unfortunately, needs to be done manually...)
+ for (uint32_t i = 0; i < callbacksLength; ++i) {
+ mCallbacks[i].ExchangeEntry(this);
+ }
+
+ BackgroundOp(Ops::CALLBACKS, true);
+ }
+}
+
+void CacheEntry::RememberCallback(Callback& aCallback) {
+ mLock.AssertCurrentThreadOwns();
+
+ LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]", this,
+ aCallback.mCallback.get(), StateString(mState)));
+
+ mCallbacks.AppendElement(aCallback);
+}
+
+void CacheEntry::InvokeCallbacksLock() {
+ mozilla::MutexAutoLock lock(mLock);
+ InvokeCallbacks();
+}
+
+void CacheEntry::InvokeCallbacks() {
+ mLock.AssertCurrentThreadOwns();
+
+ LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
+
+ // Invoke first all r/w callbacks, then all r/o callbacks.
+ if (InvokeCallbacks(false)) InvokeCallbacks(true);
+
+ LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
+}
+
+bool CacheEntry::InvokeCallbacks(bool aReadOnly) {
+ mLock.AssertCurrentThreadOwns();
+
+ RefPtr<CacheEntryHandle> recreatedHandle;
+
+ uint32_t i = 0;
+ while (i < mCallbacks.Length()) {
+ if (mPreventCallbacks) {
+ LOG((" callbacks prevented!"));
+ return false;
+ }
+
+ if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
+ LOG((" entry is being written/revalidated"));
+ return false;
+ }
+
+ bool recreate;
+ if (mCallbacks[i].DeferDoom(&recreate)) {
+ mCallbacks.RemoveElementAt(i);
+ if (!recreate) {
+ continue;
+ }
+
+ LOG((" defer doom marker callback hit positive, recreating"));
+ recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
+ break;
+ }
+
+ if (mCallbacks[i].mReadOnly != aReadOnly) {
+ // Callback is not r/w or r/o, go to another one in line
+ ++i;
+ continue;
+ }
+
+ bool onCheckThread;
+ nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
+
+ if (NS_SUCCEEDED(rv) && !onCheckThread) {
+ // Redispatch to the target thread
+ rv = mCallbacks[i].mTarget->Dispatch(
+ NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock", this,
+ &CacheEntry::InvokeCallbacksLock),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" re-dispatching to target thread"));
+ return false;
+ }
+ }
+
+ Callback callback = mCallbacks[i];
+ mCallbacks.RemoveElementAt(i);
+
+ if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
+ // Callback didn't fire, put it back and go to another one in line.
+ // Only reason InvokeCallback returns false is that onCacheEntryCheck
+ // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
+ // readers or potential writers would be unnecessarily kept from being
+ // invoked.
+ size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
+ mCallbacks.InsertElementAt(pos, callback);
+ ++i;
+ }
+ }
+
+ if (recreatedHandle) {
+ // Must be released outside of the lock, enters InvokeCallback on the new
+ // entry
+ mozilla::MutexAutoUnlock unlock(mLock);
+ recreatedHandle = nullptr;
+ }
+
+ return true;
+}
+
+bool CacheEntry::InvokeCallback(Callback& aCallback) {
+ mLock.AssertCurrentThreadOwns();
+ LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this,
+ StateString(mState), aCallback.mCallback.get()));
+
+ // When this entry is doomed we want to notify the callback any time
+ if (!mIsDoomed) {
+ // When we are here, the entry must be loaded from disk
+ MOZ_ASSERT(mState > LOADING);
+
+ if (mState == WRITING || mState == REVALIDATING) {
+ // Prevent invoking other callbacks since one of them is now writing
+ // or revalidating this entry. No consumers should get this entry
+ // until metadata are filled with values downloaded from the server
+ // or the entry revalidated and output stream has been opened.
+ LOG((" entry is being written/revalidated, callback bypassed"));
+ return false;
+ }
+
+ // mRecheckAfterWrite flag already set means the callback has already passed
+ // the onCacheEntryCheck call. Until the current write is not finished this
+ // callback will be bypassed.
+ if (!aCallback.mRecheckAfterWrite) {
+ if (!aCallback.mReadOnly) {
+ if (mState == EMPTY) {
+ // Advance to writing state, we expect to invoke the callback and let
+ // it fill content of this entry. Must set and check the state here
+ // to prevent more then one
+ mState = WRITING;
+ LOG((" advancing to WRITING state"));
+ }
+
+ if (!aCallback.mCallback) {
+ // We can be given no callback only in case of recreate, it is ok
+ // to advance to WRITING state since the caller of recreate is
+ // expected to write this entry now.
+ return true;
+ }
+ }
+
+ if (mState == READY) {
+ // Metadata present, validate the entry
+ uint32_t checkResult;
+ {
+ // mayhemer: TODO check and solve any potential races of concurent
+ // OnCacheEntryCheck
+ mozilla::MutexAutoUnlock unlock(mLock);
+
+ RefPtr<CacheEntryHandle> handle = NewHandle();
+
+ nsresult rv =
+ aCallback.mCallback->OnCacheEntryCheck(handle, &checkResult);
+ LOG((" OnCacheEntryCheck: rv=0x%08" PRIx32 ", result=%" PRId32,
+ static_cast<uint32_t>(rv), static_cast<uint32_t>(checkResult)));
+
+ if (NS_FAILED(rv)) checkResult = ENTRY_NOT_WANTED;
+ }
+
+ aCallback.mRevalidating = checkResult == ENTRY_NEEDS_REVALIDATION;
+
+ switch (checkResult) {
+ case ENTRY_WANTED:
+ // Nothing more to do here, the consumer is responsible to handle
+ // the result of OnCacheEntryCheck it self.
+ // Proceed to callback...
+ break;
+
+ case RECHECK_AFTER_WRITE_FINISHED:
+ LOG(
+ (" consumer will check on the entry again after write is "
+ "done"));
+ // The consumer wants the entry to complete first.
+ aCallback.mRecheckAfterWrite = true;
+ break;
+
+ case ENTRY_NEEDS_REVALIDATION:
+ LOG((" will be holding callbacks until entry is revalidated"));
+ // State is READY now and from that state entry cannot transit to
+ // any other state then REVALIDATING for which cocurrency is not an
+ // issue. Potentially no need to lock here.
+ mState = REVALIDATING;
+ break;
+
+ case ENTRY_NOT_WANTED:
+ LOG((" consumer not interested in the entry"));
+ // Do not give this entry to the consumer, it is not interested in
+ // us.
+ aCallback.mNotWanted = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (aCallback.mCallback) {
+ if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
+ // If we don't have data and the callback wants a complete entry,
+ // don't invoke now.
+ bool bypass = !mHasData;
+ if (!bypass && NS_SUCCEEDED(mFileStatus)) {
+ int64_t _unused;
+ bypass = !mFile->DataSize(&_unused);
+ }
+
+ if (bypass) {
+ LOG((" bypassing, entry data still being written"));
+ return false;
+ }
+
+ // Entry is complete now, do the check+avail call again
+ aCallback.mRecheckAfterWrite = false;
+ return InvokeCallback(aCallback);
+ }
+
+ mozilla::MutexAutoUnlock unlock(mLock);
+ InvokeAvailableCallback(aCallback);
+ }
+
+ return true;
+}
+
+void CacheEntry::InvokeAvailableCallback(Callback const& aCallback) {
+ nsresult rv;
+ uint32_t state;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ state = mState;
+ LOG(
+ ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, "
+ "r/o=%d, "
+ "n/w=%d]",
+ this, StateString(mState), aCallback.mCallback.get(),
+ aCallback.mReadOnly, aCallback.mNotWanted));
+
+ // When we are here, the entry must be loaded from disk
+ MOZ_ASSERT(state > LOADING || mIsDoomed);
+ }
+
+ bool onAvailThread;
+ rv = aCallback.OnAvailThread(&onAvailThread);
+ if (NS_FAILED(rv)) {
+ LOG((" target thread dead?"));
+ return;
+ }
+
+ if (!onAvailThread) {
+ // Dispatch to the right thread
+ RefPtr<AvailableCallbackRunnable> event =
+ new AvailableCallbackRunnable(this, aCallback);
+
+ rv = aCallback.mTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+ LOG((" redispatched, (rv = 0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ if (mIsDoomed || aCallback.mNotWanted) {
+ LOG(
+ (" doomed or not wanted, notifying OCEA with "
+ "NS_ERROR_CACHE_KEY_NOT_FOUND"));
+ aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
+ NS_ERROR_CACHE_KEY_NOT_FOUND);
+ return;
+ }
+
+ if (state == READY) {
+ LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
+
+ if (!aCallback.mSecret) {
+ mozilla::MutexAutoLock lock(mLock);
+ BackgroundOp(Ops::FRECENCYUPDATE);
+ }
+
+ OnFetched(aCallback);
+
+ RefPtr<CacheEntryHandle> handle = NewHandle();
+ aCallback.mCallback->OnCacheEntryAvailable(handle, false, NS_OK);
+ return;
+ }
+
+ // R/O callbacks may do revalidation, let them fall through
+ if (aCallback.mReadOnly && !aCallback.mRevalidating) {
+ LOG(
+ (" r/o and not ready, notifying OCEA with "
+ "NS_ERROR_CACHE_KEY_NOT_FOUND"));
+ aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
+ NS_ERROR_CACHE_KEY_NOT_FOUND);
+ return;
+ }
+
+ // This is a new or potentially non-valid entry and needs to be fetched first.
+ // The CacheEntryHandle blocks other consumers until the channel
+ // either releases the entry or marks metadata as filled or whole entry valid,
+ // i.e. until MetaDataReady() or SetValid() on the entry is called
+ // respectively.
+
+ // Consumer will be responsible to fill or validate the entry metadata and
+ // data.
+
+ OnFetched(aCallback);
+
+ RefPtr<CacheEntryHandle> handle = NewWriteHandle();
+ rv = aCallback.mCallback->OnCacheEntryAvailable(handle, state == WRITING,
+ NS_OK);
+
+ if (NS_FAILED(rv)) {
+ LOG((" writing/revalidating failed (0x%08" PRIx32 ")",
+ static_cast<uint32_t>(rv)));
+
+ // Consumer given a new entry failed to take care of the entry.
+ OnHandleClosed(handle);
+ return;
+ }
+
+ LOG((" writing/revalidating"));
+}
+
+void CacheEntry::OnFetched(Callback const& aCallback) {
+ if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) {
+ // Let the last-fetched and fetch-count properties be updated.
+ mFile->OnFetched();
+ }
+}
+
+CacheEntryHandle* CacheEntry::NewHandle() { return new CacheEntryHandle(this); }
+
+CacheEntryHandle* CacheEntry::NewWriteHandle() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
+ // used only along with OPEN_READONLY, but there is no need to enforce that.
+ BackgroundOp(Ops::FRECENCYUPDATE);
+
+ return (mWriter = NewHandle());
+}
+
+void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle) {
+ mozilla::MutexAutoLock lock(mLock);
+ LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this,
+ StateString(mState), aHandle));
+
+ if (mIsDoomed && NS_SUCCEEDED(mFileStatus) &&
+ // Note: mHandlesCount is dropped before this method is called
+ (mHandlesCount == 0 ||
+ (mHandlesCount == 1 && mWriter && mWriter != aHandle))) {
+ // This entry is no longer referenced from outside and is doomed.
+ // We can do this also when there is just reference from the writer,
+ // no one else could ever reach the written data.
+ // Tell the file to kill the handle, i.e. bypass any I/O operations
+ // on it except removing the file.
+ mFile->Kill();
+ }
+
+ if (mWriter != aHandle) {
+ LOG((" not the writer"));
+ return;
+ }
+
+ if (mOutputStream) {
+ LOG((" abandoning phantom output stream"));
+ // No one took our internal output stream, so there are no data
+ // and output stream has to be open symultaneously with input stream
+ // on this entry again.
+ mHasData = false;
+ // This asynchronously ends up invoking callbacks on this entry
+ // through OnOutputClosed() call.
+ mOutputStream->Close();
+ mOutputStream = nullptr;
+ } else {
+ // We must always redispatch, otherwise there is a risk of stack
+ // overflow. This code can recurse deeply. It won't execute sooner
+ // than we release mLock.
+ BackgroundOp(Ops::CALLBACKS, true);
+ }
+
+ mWriter = nullptr;
+
+ if (mState == WRITING) {
+ LOG((" reverting to state EMPTY - write failed"));
+ mState = EMPTY;
+ } else if (mState == REVALIDATING) {
+ LOG((" reverting to state READY - reval failed"));
+ mState = READY;
+ }
+
+ if (mState == READY && !mHasData) {
+ // We may get to this state when following steps happen:
+ // 1. a new entry is given to a consumer
+ // 2. the consumer calls MetaDataReady(), we transit to READY
+ // 3. abandons the entry w/o opening the output stream, mHasData left false
+ //
+ // In this case any following consumer will get a ready entry (with
+ // metadata) but in state like the entry data write was still happening (was
+ // in progress) and will indefinitely wait for the entry data or even the
+ // entry itself when RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
+ LOG(
+ (" we are in READY state, pretend we have data regardless it"
+ " has actully been never touched"));
+ mHasData = true;
+ }
+}
+
+void CacheEntry::OnOutputClosed() {
+ // Called when the file's output stream is closed. Invoke any callbacks
+ // waiting for complete entry.
+
+ mozilla::MutexAutoLock lock(mLock);
+ InvokeCallbacks();
+}
+
+bool CacheEntry::IsReferenced() const {
+ CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
+
+ // Increasing this counter from 0 to non-null and this check both happen only
+ // under the service lock.
+ return mHandlesCount > 0;
+}
+
+bool CacheEntry::IsFileDoomed() {
+ if (NS_SUCCEEDED(mFileStatus)) {
+ return mFile->IsDoomed();
+ }
+
+ return false;
+}
+
+uint32_t CacheEntry::GetMetadataMemoryConsumption() {
+ NS_ENSURE_SUCCESS(mFileStatus, 0);
+
+ uint32_t size;
+ if (NS_FAILED(mFile->ElementsSize(&size))) return 0;
+
+ return size;
+}
+
+// nsICacheEntry
+
+nsresult CacheEntry::GetPersistent(bool* aPersistToDisk) {
+ // No need to sync when only reading.
+ // When consumer needs to be consistent with state of the memory storage
+ // entries table, then let it use GetUseDisk getter that must be called under
+ // the service lock.
+ *aPersistToDisk = mUseDisk;
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetKey(nsACString& aKey) {
+ aKey.Assign(mURI);
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ *aCacheEntryId = mCacheEntryId;
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetFetchCount(uint32_t* aFetchCount) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetFetchCount(aFetchCount);
+}
+
+nsresult CacheEntry::GetLastFetched(uint32_t* aLastFetched) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetLastFetched(aLastFetched);
+}
+
+nsresult CacheEntry::GetLastModified(uint32_t* aLastModified) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetLastModified(aLastModified);
+}
+
+nsresult CacheEntry::GetExpirationTime(uint32_t* aExpirationTime) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetExpirationTime(aExpirationTime);
+}
+
+nsresult CacheEntry::GetOnStartTime(uint64_t* aTime) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+ return mFile->GetOnStartTime(aTime);
+}
+
+nsresult CacheEntry::GetOnStopTime(uint64_t* aTime) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+ return mFile->GetOnStopTime(aTime);
+}
+
+nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime,
+ uint64_t aOnStopTime) {
+ if (NS_SUCCEEDED(mFileStatus)) {
+ return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult CacheEntry::SetContentType(uint8_t aContentType) {
+ NS_ENSURE_ARG_MAX(aContentType, nsICacheEntry::CONTENT_TYPE_LAST - 1);
+
+ if (NS_SUCCEEDED(mFileStatus)) {
+ return mFile->SetContentType(aContentType);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult CacheEntry::GetIsForcedValid(bool* aIsForcedValid) {
+ NS_ENSURE_ARG(aIsForcedValid);
+
+#ifdef DEBUG
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mState > LOADING);
+ }
+#endif
+ if (mPinned) {
+ *aIsForcedValid = true;
+ return NS_OK;
+ }
+
+ nsAutoCString key;
+ nsresult rv = HashingKey(key);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aIsForcedValid =
+ CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
+ LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
+ *aIsForcedValid));
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture) {
+ LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
+ aSecondsToTheFuture));
+
+ nsAutoCString key;
+ nsresult rv = HashingKey(key);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key,
+ aSecondsToTheFuture);
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::MarkForcedValidUse() {
+ LOG(("CacheEntry::MarkForcedValidUse [this=%p, ]", this));
+
+ nsAutoCString key;
+ nsresult rv = HashingKey(key);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CacheStorageService::Self()->MarkForcedValidEntryUse(mStorageID, key);
+ return NS_OK;
+}
+
+nsresult CacheEntry::SetExpirationTime(uint32_t aExpirationTime) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ nsresult rv = mFile->SetExpirationTime(aExpirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Aligned assignment, thus atomic.
+ mSortingExpirationTime = aExpirationTime;
+ return NS_OK;
+}
+
+nsresult CacheEntry::OpenInputStream(int64_t offset, nsIInputStream** _retval) {
+ LOG(("CacheEntry::OpenInputStream [this=%p]", this));
+ return OpenInputStreamInternal(offset, nullptr, _retval);
+}
+
+nsresult CacheEntry::OpenAlternativeInputStream(const nsACString& type,
+ nsIInputStream** _retval) {
+ LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
+ PromiseFlatCString(type).get()));
+ return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval);
+}
+
+nsresult CacheEntry::OpenInputStreamInternal(int64_t offset,
+ const char* aAltDataType,
+ nsIInputStream** _retval) {
+ LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
+
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ nsresult rv;
+
+ RefPtr<CacheEntryHandle> selfHandle = NewHandle();
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (aAltDataType) {
+ rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType,
+ getter_AddRefs(stream));
+ if (NS_FAILED(rv)) {
+ // Failure of this method may be legal when the alternative data requested
+ // is not avaialble or of a different type. Console error logs are
+ // ensured by CacheFile::OpenAlternativeInputStream.
+ return rv;
+ }
+ } else {
+ rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mHasData) {
+ // So far output stream on this new entry not opened, do it now.
+ LOG((" creating phantom output stream"));
+ rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ stream.forget(_retval);
+ return NS_OK;
+}
+
+nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize,
+ nsIOutputStream** _retval) {
+ LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
+
+ nsresult rv;
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mState > EMPTY);
+
+ if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) {
+ LOG((" entry would exceed size limit"));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ if (mOutputStream && !mIsDoomed) {
+ LOG((" giving phantom output stream"));
+ mOutputStream.forget(_retval);
+ } else {
+ rv = OpenOutputStreamInternal(offset, _retval);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Entry considered ready when writer opens output stream.
+ if (mState < READY) mState = READY;
+
+ // Invoke any pending readers now.
+ InvokeCallbacks();
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
+ PromiseFlatCString(type).get()));
+
+ nsresult rv;
+
+ if (type.IsEmpty()) {
+ // The empty string is reserved to mean no alt-data available.
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
+ LOG((" entry not in state to write alt-data"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
+ LOG((" entry would exceed size limit"));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> stream;
+ rv = mFile->OpenAlternativeOutputStream(
+ nullptr, PromiseFlatCString(type).get(), getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stream.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset,
+ nsIOutputStream** _retval) {
+ LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
+
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ mLock.AssertCurrentThreadOwns();
+
+ if (mIsDoomed) {
+ LOG((" doomed..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(mState > LOADING);
+
+ nsresult rv;
+
+ // No need to sync on mUseDisk here, we don't need to be consistent
+ // with content of the memory storage entries hash table.
+ if (!mUseDisk) {
+ rv = mFile->SetMemoryOnly();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ RefPtr<CacheOutputCloseListener> listener =
+ new CacheOutputCloseListener(this);
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Prevent opening output stream again.
+ mHasData = true;
+
+ stream.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ if (mSecurityInfoLoaded) {
+ *aSecurityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+ }
+ }
+
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ nsCString info;
+ nsresult rv = mFile->GetElement("security-info", getter_Copies(info));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ if (!info.IsVoid()) {
+ rv = mozilla::psm::TransportSecurityInfo::Read(
+ info, getter_AddRefs(securityInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (!securityInfo) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ mSecurityInfo.swap(securityInfo);
+ mSecurityInfoLoaded = true;
+
+ *aSecurityInfo = do_AddRef(mSecurityInfo).take();
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo) {
+ nsresult rv;
+
+ NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ mSecurityInfo = aSecurityInfo;
+ mSecurityInfoLoaded = true;
+ }
+
+ nsCString info;
+ if (aSecurityInfo) {
+ rv = aSecurityInfo->ToString(info);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize) {
+ NS_ENSURE_ARG(aStorageDataSize);
+
+ int64_t dataSize;
+ nsresult rv = GetDataSize(&dataSize);
+ if (NS_FAILED(rv)) return rv;
+
+ *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::AsyncDoom(nsICacheEntryDoomCallback* aCallback) {
+ LOG(("CacheEntry::AsyncDoom [this=%p]", this));
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mIsDoomed || mDoomCallback) {
+ return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
+ }
+
+ RemoveForcedValidity();
+
+ mIsDoomed = true;
+ mDoomCallback = aCallback;
+ }
+
+ // This immediately removes the entry from the master hashtable and also
+ // immediately dooms the file. This way we make sure that any consumer
+ // after this point asking for the same entry won't get
+ // a) this entry
+ // b) a new entry with the same file
+ PurgeAndDoom();
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetMetaDataElement(const char* aKey, char** aRetval) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetElement(aKey, aRetval);
+}
+
+nsresult CacheEntry::SetMetaDataElement(const char* aKey, const char* aValue) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->SetElement(aKey, aValue);
+}
+
+nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->VisitMetaData(aVisitor);
+}
+
+nsresult CacheEntry::MetaDataReady() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this,
+ StateString(mState)));
+
+ MOZ_ASSERT(mState > EMPTY);
+
+ if (mState == WRITING) mState = READY;
+
+ InvokeCallbacks();
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::SetValid() {
+ nsCOMPtr<nsIOutputStream> outputStream;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ LOG(("CacheEntry::SetValid [this=%p, state=%s]", this,
+ StateString(mState)));
+
+ MOZ_ASSERT(mState > EMPTY);
+
+ mState = READY;
+ mHasData = true;
+
+ InvokeCallbacks();
+
+ outputStream.swap(mOutputStream);
+ }
+
+ if (outputStream) {
+ LOG((" abandoning phantom output stream"));
+ outputStream->Close();
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheEntry::Recreate(bool aMemoryOnly, nsICacheEntry** _retval) {
+ mozilla::MutexAutoLock lock(mLock);
+ LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
+
+ RefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
+ if (handle) {
+ handle.forget(_retval);
+ return NS_OK;
+ }
+
+ BackgroundOp(Ops::CALLBACKS, true);
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult CacheEntry::GetDataSize(int64_t* aDataSize) {
+ LOG(("CacheEntry::GetDataSize [this=%p]", this));
+ *aDataSize = 0;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mHasData) {
+ LOG((" write in progress (no data)"));
+ return NS_ERROR_IN_PROGRESS;
+ }
+ }
+
+ NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
+
+ // mayhemer: TODO Problem with compression?
+ if (!mFile->DataSize(aDataSize)) {
+ LOG((" write in progress (stream active)"));
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ LOG((" size=%" PRId64, *aDataSize));
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetAltDataSize(int64_t* aDataSize) {
+ LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
+ if (NS_FAILED(mFileStatus)) {
+ return mFileStatus;
+ }
+ return mFile->GetAltDataSize(aDataSize);
+}
+
+nsresult CacheEntry::GetAltDataType(nsACString& aType) {
+ LOG(("CacheEntry::GetAltDataType [this=%p]", this));
+ if (NS_FAILED(mFileStatus)) {
+ return mFileStatus;
+ }
+ return mFile->GetAltDataType(aType);
+}
+
+nsresult CacheEntry::MarkValid() {
+ // NOT IMPLEMENTED ACTUALLY
+ return NS_OK;
+}
+
+nsresult CacheEntry::MaybeMarkValid() {
+ // NOT IMPLEMENTED ACTUALLY
+ return NS_OK;
+}
+
+nsresult CacheEntry::HasWriteAccess(bool aWriteAllowed, bool* aWriteAccess) {
+ *aWriteAccess = aWriteAllowed;
+ return NS_OK;
+}
+
+nsresult CacheEntry::Close() {
+ // NOT IMPLEMENTED ACTUALLY
+ return NS_OK;
+}
+
+nsresult CacheEntry::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) {
+ if (NS_FAILED(mFileStatus)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mFile->GetDiskStorageSizeInKB(aDiskStorageSize);
+}
+
+nsresult CacheEntry::GetLoadContextInfo(nsILoadContextInfo** aInfo) {
+ nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(mStorageID);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ info.forget(aInfo);
+
+ return NS_OK;
+}
+
+// nsIRunnable
+
+NS_IMETHODIMP CacheEntry::Run() {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ BackgroundOp(mBackgroundOperations.Grab());
+ return NS_OK;
+}
+
+// Management methods
+
+double CacheEntry::GetFrecency() const {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+ return mFrecency;
+}
+
+uint32_t CacheEntry::GetExpirationTime() const {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+ return mSortingExpirationTime;
+}
+
+bool CacheEntry::IsRegistered() const {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+ return mRegistration == REGISTERED;
+}
+
+bool CacheEntry::CanRegister() const {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+ return mRegistration == NEVERREGISTERED;
+}
+
+void CacheEntry::SetRegistered(bool aRegistered) {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+ if (aRegistered) {
+ MOZ_ASSERT(mRegistration == NEVERREGISTERED);
+ mRegistration = REGISTERED;
+ } else {
+ MOZ_ASSERT(mRegistration == REGISTERED);
+ mRegistration = DEREGISTERED;
+ }
+}
+
+bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned) {
+ LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
+
+ mozilla::MutexAutoLock lock(mLock);
+ if (mPinningKnown) {
+ LOG((" pinned=%d, caller=%d", (bool)mPinned, aPinned));
+ // Bypass when the pin status of this entry doesn't match the pin status
+ // caller wants to remove
+ return mPinned != aPinned;
+ }
+
+ LOG((" pinning unknown, caller=%d", aPinned));
+ // Oterwise, remember to doom after the status is determined for any
+ // callback opening the entry after this point...
+ Callback c(this, aPinned);
+ RememberCallback(c);
+ // ...and always bypass
+ return true;
+}
+
+bool CacheEntry::Purge(uint32_t aWhat) {
+ LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
+
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+ switch (aWhat) {
+ case PURGE_DATA_ONLY_DISK_BACKED:
+ case PURGE_WHOLE_ONLY_DISK_BACKED:
+ // This is an in-memory only entry, don't purge it
+ if (!mUseDisk) {
+ LOG((" not using disk"));
+ return false;
+ }
+ }
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mState == WRITING || mState == LOADING || mFrecency == 0) {
+ // In-progress (write or load) entries should (at least for consistency
+ // and from the logical point of view) stay in memory. Zero-frecency
+ // entries are those which have never been given to any consumer, those
+ // are actually very fresh and should not go just because frecency had not
+ // been set so far.
+ LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
+ return false;
+ }
+ }
+
+ if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
+ // The file is used when there are open streams or chunks/metadata still
+ // waiting for write. In this case, this entry cannot be purged,
+ // otherwise reopenned entry would may not even find the data on disk -
+ // CacheFile is not shared and cannot be left orphan when its job is not
+ // done, hence keep the whole entry.
+ LOG((" file still under use"));
+ return false;
+ }
+
+ switch (aWhat) {
+ case PURGE_WHOLE_ONLY_DISK_BACKED:
+ case PURGE_WHOLE: {
+ if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
+ LOG((" not purging, still referenced"));
+ return false;
+ }
+
+ CacheStorageService::Self()->UnregisterEntry(this);
+
+ // Entry removed it self from control arrays, return true
+ return true;
+ }
+
+ case PURGE_DATA_ONLY_DISK_BACKED: {
+ NS_ENSURE_SUCCESS(mFileStatus, false);
+
+ mFile->ThrowMemoryCachedData();
+
+ // Entry has been left in control arrays, return false (not purged)
+ return false;
+ }
+ }
+
+ LOG((" ?"));
+ return false;
+}
+
+void CacheEntry::PurgeAndDoom() {
+ LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
+
+ CacheStorageService::Self()->RemoveEntry(this);
+ DoomAlreadyRemoved();
+}
+
+void CacheEntry::DoomAlreadyRemoved() {
+ LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ RemoveForcedValidity();
+
+ mIsDoomed = true;
+
+ // Pretend pinning is know. This entry is now doomed for good, so don't
+ // bother with defering doom because of unknown pinning state any more.
+ mPinningKnown = true;
+
+ // This schedules dooming of the file, dooming is ensured to happen
+ // sooner than demand to open the same file made after this point
+ // so that we don't get this file for any newer opened entry(s).
+ DoomFile();
+
+ // Must force post here since may be indirectly called from
+ // InvokeCallbacks of this entry and we don't want reentrancy here.
+ BackgroundOp(Ops::CALLBACKS, true);
+ // Process immediately when on the management thread.
+ BackgroundOp(Ops::UNREGISTER);
+}
+
+void CacheEntry::DoomFile() {
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ if (NS_SUCCEEDED(mFileStatus)) {
+ if (mHandlesCount == 0 || (mHandlesCount == 1 && mWriter)) {
+ // We kill the file also when there is just reference from the writer,
+ // no one else could ever reach the written data. Obvisouly also
+ // when there is no reference at all (should we ever end up here
+ // in that case.)
+ // Tell the file to kill the handle, i.e. bypass any I/O operations
+ // on it except removing the file.
+ mFile->Kill();
+ }
+
+ // Always calls the callback asynchronously.
+ rv = mFile->Doom(mDoomCallback ? this : nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" file doomed"));
+ return;
+ }
+
+ if (NS_ERROR_FILE_NOT_FOUND == rv) {
+ // File is set to be just memory-only, notify the callbacks
+ // and pretend dooming has succeeded. From point of view of
+ // the entry it actually did - the data is gone and cannot be
+ // reused.
+ rv = NS_OK;
+ }
+ }
+
+ // Always posts to the main thread.
+ OnFileDoomed(rv);
+}
+
+void CacheEntry::RemoveForcedValidity() {
+ mLock.AssertCurrentThreadOwns();
+
+ nsresult rv;
+
+ if (mIsDoomed) {
+ return;
+ }
+
+ nsAutoCString entryKey;
+ rv = HashingKey(entryKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ CacheStorageService::Self()->RemoveEntryForceValid(mStorageID, entryKey);
+}
+
+void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) {
+ mLock.AssertCurrentThreadOwns();
+
+ if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
+ if (mBackgroundOperations.Set(aOperations)) {
+ CacheStorageService::Self()->Dispatch(this);
+ }
+
+ LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
+ return;
+ }
+
+ {
+ mozilla::MutexAutoUnlock unlock(mLock);
+
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+ if (aOperations & Ops::FRECENCYUPDATE) {
+ ++mUseCount;
+
+#ifndef M_LN2
+# define M_LN2 0.69314718055994530942
+#endif
+
+ // Half-life is dynamic, in seconds.
+ static double half_life = CacheObserver::HalfLifeSeconds();
+ // Must convert from seconds to milliseconds since PR_Now() gives usecs.
+ static double const decay =
+ (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
+
+ double now_decay = static_cast<double>(PR_Now()) * decay;
+
+ if (mFrecency == 0) {
+ mFrecency = now_decay;
+ } else {
+ // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n +
+ // 1) but more precise.
+ mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
+ }
+ LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this,
+ mFrecency));
+
+ // Because CacheFile::Set*() are not thread-safe to use (uses
+ // WeakReference that is not thread-safe) we must post to the main
+ // thread...
+ NS_DispatchToMainThread(
+ NewRunnableMethod<double>("net::CacheEntry::StoreFrecency", this,
+ &CacheEntry::StoreFrecency, mFrecency));
+ }
+
+ if (aOperations & Ops::REGISTER) {
+ LOG(("CacheEntry REGISTER [this=%p]", this));
+
+ CacheStorageService::Self()->RegisterEntry(this);
+ }
+
+ if (aOperations & Ops::UNREGISTER) {
+ LOG(("CacheEntry UNREGISTER [this=%p]", this));
+
+ CacheStorageService::Self()->UnregisterEntry(this);
+ }
+ } // unlock
+
+ if (aOperations & Ops::CALLBACKS) {
+ LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
+
+ InvokeCallbacks();
+ }
+}
+
+void CacheEntry::StoreFrecency(double aFrecency) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(mFileStatus)) {
+ mFile->SetFrecency(FRECENCY2INT(aFrecency));
+ }
+}
+
+// CacheOutputCloseListener
+
+CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
+ : Runnable("net::CacheOutputCloseListener"), mEntry(aEntry) {}
+
+void CacheOutputCloseListener::OnOutputClosed() {
+ // We need this class and to redispatch since this callback is invoked
+ // under the file's lock and to do the job we need to enter the entry's
+ // lock too. That would lead to potential deadlocks.
+ // This function may be reached while XPCOM is already shutting down,
+ // and we might be unable to obtain the main thread or the sts. #1826661
+
+ if (NS_IsMainThread()) {
+ // If we're already on the main thread, dispatch to the main thread instead
+ // of the sts. Always dispatching to the sts can cause problems late in
+ // shutdown, when threadpools may no longer be available (bug 1806332).
+ //
+ // This may also avoid some unnecessary thread-hops when invoking callbacks,
+ // which can require that they be called on the main thread.
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_GetMainThread(getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(do_AddRef(this)));
+ }
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_DIAGNOSTIC_ASSERT(sts);
+ if (sts) {
+ MOZ_ALWAYS_SUCCEEDS(sts->Dispatch(do_AddRef(this)));
+ }
+}
+
+NS_IMETHODIMP CacheOutputCloseListener::Run() {
+ mEntry->OnOutputClosed();
+ return NS_OK;
+}
+
+// Memory reporting
+
+size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ size_t n = 0;
+
+ MutexAutoLock lock(mLock);
+ n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
+ if (mFile) {
+ n += mFile->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ n += mURI.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+
+ // mDoomCallback is an arbitrary class that is probably reported elsewhere.
+ // mOutputStream is reported in mFile.
+ // mWriter is one of many handles we create, but (intentionally) not keep
+ // any reference to, so those unfortunately cannot be reported. Handles are
+ // small, though.
+ // mSecurityInfo doesn't impl nsISizeOf.
+
+ return n;
+}
+
+size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheEntry.h b/netwerk/cache2/CacheEntry.h
new file mode 100644
index 0000000000..0e137d306b
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.h
@@ -0,0 +1,587 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheEntry__h__
+#define CacheEntry__h__
+
+#include "mozilla/LinkedList.h"
+#include "nsICacheEntry.h"
+#include "CacheFile.h"
+
+#include "nsIRunnable.h"
+#include "nsIOutputStream.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntryDoomCallback.h"
+#include "nsITransportSecurityInfo.h"
+
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+
+static inline uint32_t PRTimeToSeconds(PRTime t_usec) {
+ return uint32_t(t_usec / PR_USEC_PER_SEC);
+}
+
+#define NowInSeconds() PRTimeToSeconds(PR_Now())
+
+class nsIOutputStream;
+class nsIURI;
+class nsIThread;
+
+namespace mozilla {
+namespace net {
+
+class CacheStorageService;
+class CacheStorage;
+class CacheOutputCloseListener;
+class CacheEntryHandle;
+
+class CacheEntry final : public nsIRunnable,
+ public CacheFileListener,
+ // Used by CacheStorageService::MemoryPool
+ public LinkedListElement<RefPtr<CacheEntry>> {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ static uint64_t GetNextId();
+
+ CacheEntry(const nsACString& aStorageID, const nsACString& aURI,
+ const nsACString& aEnhanceID, bool aUseDisk, bool aSkipSizeCheck,
+ bool aPin);
+
+ void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags);
+
+ CacheEntryHandle* NewHandle();
+ // For a new and recreated entry w/o a callback, we need to wrap it
+ // with a handle to detect writing consumer is gone.
+ CacheEntryHandle* NewWriteHandle();
+
+ // Forwarded to from CacheEntryHandle : nsICacheEntry
+ nsresult GetKey(nsACString& aKey);
+ nsresult GetCacheEntryId(uint64_t* aCacheEntryId);
+ nsresult GetPersistent(bool* aPersistToDisk);
+ nsresult GetFetchCount(uint32_t* aFetchCount);
+ nsresult GetLastFetched(uint32_t* aLastFetched);
+ nsresult GetLastModified(uint32_t* aLastModified);
+ nsresult GetExpirationTime(uint32_t* aExpirationTime);
+ nsresult SetExpirationTime(uint32_t expirationTime);
+ nsresult GetOnStartTime(uint64_t* aTime);
+ nsresult GetOnStopTime(uint64_t* aTime);
+ nsresult SetNetworkTimes(uint64_t onStartTime, uint64_t onStopTime);
+ nsresult SetContentType(uint8_t aContentType);
+ nsresult ForceValidFor(uint32_t aSecondsToTheFuture);
+ nsresult GetIsForcedValid(bool* aIsForcedValid);
+ nsresult MarkForcedValidUse();
+ nsresult OpenInputStream(int64_t offset, nsIInputStream** _retval);
+ nsresult OpenOutputStream(int64_t offset, int64_t predictedSize,
+ nsIOutputStream** _retval);
+ nsresult GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo);
+ nsresult SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo);
+ nsresult GetStorageDataSize(uint32_t* aStorageDataSize);
+ nsresult AsyncDoom(nsICacheEntryDoomCallback* aCallback);
+ nsresult GetMetaDataElement(const char* key, char** aRetval);
+ nsresult SetMetaDataElement(const char* key, const char* value);
+ nsresult VisitMetaData(nsICacheEntryMetaDataVisitor* visitor);
+ nsresult MetaDataReady(void);
+ nsresult SetValid(void);
+ nsresult GetDiskStorageSizeInKB(uint32_t* aDiskStorageSizeInKB);
+ nsresult Recreate(bool aMemoryOnly, nsICacheEntry** _retval);
+ nsresult GetDataSize(int64_t* aDataSize);
+ nsresult GetAltDataSize(int64_t* aDataSize);
+ nsresult GetAltDataType(nsACString& aAltDataType);
+ nsresult OpenAlternativeOutputStream(const nsACString& type,
+ int64_t predictedSize,
+ nsIAsyncOutputStream** _retval);
+ nsresult OpenAlternativeInputStream(const nsACString& type,
+ nsIInputStream** _retval);
+ nsresult GetLoadContextInfo(nsILoadContextInfo** aInfo);
+ nsresult Close(void);
+ nsresult MarkValid(void);
+ nsresult MaybeMarkValid(void);
+ nsresult HasWriteAccess(bool aWriteAllowed, bool* aWriteAccess);
+
+ public:
+ uint32_t GetMetadataMemoryConsumption();
+ nsCString const& GetStorageID() const { return mStorageID; }
+ nsCString const& GetEnhanceID() const { return mEnhanceID; }
+ nsCString const& GetURI() const { return mURI; }
+ // Accessible at any time
+ bool IsUsingDisk() const { return mUseDisk; }
+ bool IsReferenced() const MOZ_NO_THREAD_SAFETY_ANALYSIS;
+ bool IsFileDoomed();
+ bool IsDoomed() const { return mIsDoomed; }
+ bool IsPinned() const { return mPinned; }
+
+ // Methods for entry management (eviction from memory),
+ // called only on the management thread.
+
+ // TODO make these inline
+ double GetFrecency() const;
+ uint32_t GetExpirationTime() const;
+ uint32_t UseCount() const { return mUseCount; }
+
+ bool IsRegistered() const;
+ bool CanRegister() const;
+ void SetRegistered(bool aRegistered);
+
+ TimeStamp const& LoadStart() const { return mLoadStart; }
+
+ enum EPurge {
+ PURGE_DATA_ONLY_DISK_BACKED,
+ PURGE_WHOLE_ONLY_DISK_BACKED,
+ PURGE_WHOLE,
+ };
+
+ bool DeferOrBypassRemovalOnPinStatus(bool aPinned);
+ bool Purge(uint32_t aWhat);
+ void PurgeAndDoom();
+ void DoomAlreadyRemoved();
+
+ nsresult HashingKeyWithStorage(nsACString& aResult) const;
+ nsresult HashingKey(nsACString& aResult) const;
+
+ static nsresult HashingKey(const nsACString& aStorageID,
+ const nsACString& aEnhanceID, nsIURI* aURI,
+ nsACString& aResult);
+
+ static nsresult HashingKey(const nsACString& aStorageID,
+ const nsACString& aEnhanceID,
+ const nsACString& aURISpec, nsACString& aResult);
+
+ // Accessed only on the service management thread
+ double mFrecency{0};
+ ::mozilla::Atomic<uint32_t, ::mozilla::Relaxed> mSortingExpirationTime{
+ uint32_t(-1)};
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ private:
+ virtual ~CacheEntry();
+
+ // CacheFileListener
+ NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew) override;
+ NS_IMETHOD OnFileDoomed(nsresult aResult) override;
+
+ // Keep the service alive during life-time of an entry
+ RefPtr<CacheStorageService> mService;
+
+ // We must monitor when a cache entry whose consumer is responsible
+ // for writing it the first time gets released. We must then invoke
+ // waiting callbacks to not break the chain.
+ class Callback {
+ public:
+ Callback(CacheEntry* aEntry, nsICacheEntryOpenCallback* aCallback,
+ bool aReadOnly, bool aCheckOnAnyThread, bool aSecret);
+ // Special constructor for Callback objects added to the chain
+ // just to ensure proper defer dooming (recreation) of this entry.
+ Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus);
+ Callback(Callback const& aThat);
+ ~Callback();
+
+ // Called when this callback record changes it's owning entry,
+ // mainly during recreation.
+ void ExchangeEntry(CacheEntry* aEntry) MOZ_REQUIRES(aEntry->mLock);
+
+ // Returns true when an entry is about to be "defer" doomed and this is
+ // a "defer" callback. The caller must hold a lock (this entry is in the
+ // caller's mCallback array)
+ bool DeferDoom(bool* aDoom) const;
+
+ // We are raising reference count here to take into account the pending
+ // callback (that virtually holds a ref to this entry before it gets
+ // it's pointer).
+ RefPtr<CacheEntry> mEntry;
+ nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ bool mReadOnly : 1;
+ bool mRevalidating : 1;
+ bool mCheckOnAnyThread : 1;
+ bool mRecheckAfterWrite : 1;
+ bool mNotWanted : 1;
+ bool mSecret : 1;
+
+ // These are set only for the defer-doomer Callback instance inserted
+ // to the callback chain. When any of these is set and also any of
+ // the corressponding flags on the entry is set, this callback will
+ // indicate (via DeferDoom()) the entry have to be recreated/doomed.
+ bool mDoomWhenFoundPinned : 1;
+ bool mDoomWhenFoundNonPinned : 1;
+
+ nsresult OnCheckThread(bool* aOnCheckThread) const;
+ nsresult OnAvailThread(bool* aOnAvailThread) const;
+ };
+
+ // Since OnCacheEntryAvailable must be invoked on the main thread
+ // we need a runnable for it...
+ class AvailableCallbackRunnable : public Runnable {
+ public:
+ AvailableCallbackRunnable(CacheEntry* aEntry, Callback const& aCallback)
+ : Runnable("CacheEntry::AvailableCallbackRunnable"),
+ mEntry(aEntry),
+ mCallback(aCallback) {}
+
+ private:
+ NS_IMETHOD Run() override {
+ mEntry->InvokeAvailableCallback(mCallback);
+ return NS_OK;
+ }
+
+ RefPtr<CacheEntry> mEntry;
+ Callback mCallback;
+ };
+
+ // Since OnCacheEntryDoomed must be invoked on the main thread
+ // we need a runnable for it...
+ class DoomCallbackRunnable : public Runnable {
+ public:
+ DoomCallbackRunnable(CacheEntry* aEntry, nsresult aRv)
+ : Runnable("net::CacheEntry::DoomCallbackRunnable"),
+ mEntry(aEntry),
+ mRv(aRv) {}
+
+ private:
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsICacheEntryDoomCallback> callback;
+ {
+ mozilla::MutexAutoLock lock(mEntry->mLock);
+ mEntry->mDoomCallback.swap(callback);
+ }
+
+ if (callback) callback->OnCacheEntryDoomed(mRv);
+ return NS_OK;
+ }
+
+ RefPtr<CacheEntry> mEntry;
+ nsresult mRv;
+ };
+
+ // Starts the load or just invokes the callback, bypasses (when required)
+ // if busy. Returns true on job done, false on bypass.
+ bool Open(Callback& aCallback, bool aTruncate, bool aPriority,
+ bool aBypassIfBusy);
+ // Loads from disk asynchronously
+ bool Load(bool aTruncate, bool aPriority);
+
+ void RememberCallback(Callback& aCallback) MOZ_REQUIRES(mLock);
+ void InvokeCallbacksLock();
+ void InvokeCallbacks();
+ bool InvokeCallbacks(bool aReadOnly);
+ bool InvokeCallback(Callback& aCallback);
+ void InvokeAvailableCallback(Callback const& aCallback);
+ void OnFetched(Callback const& aCallback);
+
+ nsresult OpenOutputStreamInternal(int64_t offset, nsIOutputStream** _retval);
+ nsresult OpenInputStreamInternal(int64_t offset, const char* aAltDataType,
+ nsIInputStream** _retval);
+
+ void OnHandleClosed(CacheEntryHandle const* aHandle);
+
+ private:
+ friend class CacheEntryHandle;
+ // Increment/decrements the number of handles keeping this entry.
+ void AddHandleRef() MOZ_REQUIRES(mLock) { ++mHandlesCount; }
+ void ReleaseHandleRef() MOZ_REQUIRES(mLock) { --mHandlesCount; }
+ // Current number of handles keeping this entry.
+ uint32_t HandlesCount() const MOZ_REQUIRES(mLock) { return mHandlesCount; }
+
+ private:
+ friend class CacheOutputCloseListener;
+ void OnOutputClosed();
+
+ private:
+ // Schedules a background operation on the management thread.
+ // When executed on the management thread directly, the operation(s)
+ // is (are) executed immediately.
+ void BackgroundOp(uint32_t aOperation, bool aForceAsync = false);
+ void StoreFrecency(double aFrecency);
+
+ // Called only from DoomAlreadyRemoved()
+ void DoomFile() MOZ_REQUIRES(mLock);
+ // When this entry is doomed the first time, this method removes
+ // any force-valid timing info for this entry.
+ void RemoveForcedValidity();
+
+ already_AddRefed<CacheEntryHandle> ReopenTruncated(
+ bool aMemoryOnly, nsICacheEntryOpenCallback* aCallback);
+ void TransferCallbacks(CacheEntry& aFromEntry);
+
+ mozilla::Mutex mLock{"CacheEntry"};
+
+ // Reflects the number of existing handles for this entry
+ ::mozilla::ThreadSafeAutoRefCnt mHandlesCount MOZ_GUARDED_BY(mLock);
+
+ nsTArray<Callback> mCallbacks MOZ_GUARDED_BY(mLock);
+ nsCOMPtr<nsICacheEntryDoomCallback> mDoomCallback;
+
+ // Set in CacheEntry::Load(), only - shouldn't need to be under lock
+ // XXX FIX? is this correct?
+ RefPtr<CacheFile> mFile;
+
+ // Using ReleaseAcquire since we only control access to mFile with this.
+ // When mFileStatus is read and found success it is ensured there is mFile and
+ // that it is after a successful call to Init().
+ Atomic<nsresult, ReleaseAcquire> mFileStatus{NS_ERROR_NOT_INITIALIZED};
+ // Set in constructor
+ nsCString const mURI;
+ nsCString const mEnhanceID;
+ nsCString const mStorageID;
+
+ // mUseDisk, mSkipSizeCheck, mIsDoomed are plain "bool", not "bool:1",
+ // so as to avoid bitfield races with the byte containing
+ // mSecurityInfoLoaded et al. See bug 1278524.
+ //
+ // Whether it's allowed to persist the data to disk
+ bool const mUseDisk;
+ // Whether it should skip max size check.
+ bool const mSkipSizeCheck;
+ // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
+ Atomic<bool, Relaxed> mIsDoomed{false};
+ // The indication of pinning this entry was open with
+ Atomic<bool, Relaxed> mPinned;
+
+ // Following flags are all synchronized with the cache entry lock.
+
+ // Whether security info has already been looked up in metadata.
+ bool mSecurityInfoLoaded : 1 MOZ_GUARDED_BY(mLock);
+ // Prevents any callback invocation
+ bool mPreventCallbacks : 1 MOZ_GUARDED_BY(mLock);
+ // true: after load and an existing file, or after output stream has been
+ // opened.
+ // note - when opening an input stream, and this flag is false, output
+ // stream is open along ; this makes input streams on new entries
+ // behave correctly when EOF is reached (WOULD_BLOCK is returned).
+ // false: after load and a new file, or dropped to back to false when a
+ // writer fails to open an output stream.
+ bool mHasData : 1 MOZ_GUARDED_BY(mLock);
+ // Whether the pinning state of the entry is known (equals to the actual state
+ // of the cache file)
+ bool mPinningKnown : 1 MOZ_GUARDED_BY(mLock);
+
+ static char const* StateString(uint32_t aState);
+
+ enum EState { // transiting to:
+ NOTLOADED = 0, // -> LOADING | EMPTY
+ LOADING = 1, // -> EMPTY | READY
+ EMPTY = 2, // -> WRITING
+ WRITING = 3, // -> EMPTY | READY
+ READY = 4, // -> REVALIDATING
+ REVALIDATING = 5 // -> READY
+ };
+
+ // State of this entry.
+ EState mState MOZ_GUARDED_BY(mLock){NOTLOADED};
+
+ enum ERegistration {
+ NEVERREGISTERED = 0, // The entry has never been registered
+ REGISTERED = 1, // The entry is stored in the memory pool index
+ DEREGISTERED = 2 // The entry has been removed from the pool
+ };
+
+ // Accessed only on the management thread. Records the state of registration
+ // this entry in the memory pool intermediate cache.
+ ERegistration mRegistration{NEVERREGISTERED};
+
+ // If a new (empty) entry is requested to open an input stream before
+ // output stream has been opened, we must open output stream internally
+ // on CacheFile and hold until writer releases the entry or opens the output
+ // stream for read (then we trade him mOutputStream).
+ nsCOMPtr<nsIOutputStream> mOutputStream MOZ_GUARDED_BY(mLock);
+
+ // Weak reference to the current writter. There can be more then one
+ // writer at a time and OnHandleClosed() must be processed only for the
+ // current one.
+ CacheEntryHandle* mWriter MOZ_GUARDED_BY(mLock){nullptr};
+
+ // Background thread scheduled operation. Set (under the lock) one
+ // of this flags to tell the background thread what to do.
+ class Ops {
+ public:
+ static uint32_t const REGISTER = 1 << 0;
+ static uint32_t const FRECENCYUPDATE = 1 << 1;
+ static uint32_t const CALLBACKS = 1 << 2;
+ static uint32_t const UNREGISTER = 1 << 3;
+
+ Ops() = default;
+ uint32_t Grab() {
+ uint32_t flags = mFlags;
+ mFlags = 0;
+ return flags;
+ }
+ bool Set(uint32_t aFlags) {
+ if (mFlags & aFlags) return false;
+ mFlags |= aFlags;
+ return true;
+ }
+
+ private:
+ uint32_t mFlags{0};
+ } mBackgroundOperations;
+
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ mozilla::TimeStamp mLoadStart;
+ uint32_t mUseCount{0};
+
+ const uint64_t mCacheEntryId;
+};
+
+class CacheEntryHandle final : public nsICacheEntry {
+ public:
+ explicit CacheEntryHandle(CacheEntry* aEntry);
+ CacheEntry* Entry() const { return mEntry; }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // Default implementation is simply safely forwarded.
+ NS_IMETHOD GetKey(nsACString& aKey) override { return mEntry->GetKey(aKey); }
+ NS_IMETHOD GetCacheEntryId(uint64_t* aCacheEntryId) override {
+ return mEntry->GetCacheEntryId(aCacheEntryId);
+ }
+ NS_IMETHOD GetPersistent(bool* aPersistent) override {
+ return mEntry->GetPersistent(aPersistent);
+ }
+ NS_IMETHOD GetFetchCount(uint32_t* aFetchCount) override {
+ return mEntry->GetFetchCount(aFetchCount);
+ }
+ NS_IMETHOD GetLastFetched(uint32_t* aLastFetched) override {
+ return mEntry->GetLastFetched(aLastFetched);
+ }
+ NS_IMETHOD GetLastModified(uint32_t* aLastModified) override {
+ return mEntry->GetLastModified(aLastModified);
+ }
+ NS_IMETHOD GetExpirationTime(uint32_t* aExpirationTime) override {
+ return mEntry->GetExpirationTime(aExpirationTime);
+ }
+ NS_IMETHOD SetExpirationTime(uint32_t expirationTime) override {
+ return mEntry->SetExpirationTime(expirationTime);
+ }
+ NS_IMETHOD GetOnStartTime(uint64_t* aOnStartTime) override {
+ return mEntry->GetOnStartTime(aOnStartTime);
+ }
+ NS_IMETHOD GetOnStopTime(uint64_t* aOnStopTime) override {
+ return mEntry->GetOnStopTime(aOnStopTime);
+ }
+ NS_IMETHOD SetNetworkTimes(uint64_t onStartTime,
+ uint64_t onStopTime) override {
+ return mEntry->SetNetworkTimes(onStartTime, onStopTime);
+ }
+ NS_IMETHOD SetContentType(uint8_t contentType) override {
+ return mEntry->SetContentType(contentType);
+ }
+ NS_IMETHOD ForceValidFor(uint32_t aSecondsToTheFuture) override {
+ return mEntry->ForceValidFor(aSecondsToTheFuture);
+ }
+ NS_IMETHOD GetIsForcedValid(bool* aIsForcedValid) override {
+ return mEntry->GetIsForcedValid(aIsForcedValid);
+ }
+ NS_IMETHOD MarkForcedValidUse() override {
+ return mEntry->MarkForcedValidUse();
+ }
+ NS_IMETHOD OpenInputStream(int64_t offset,
+ nsIInputStream** _retval) override {
+ return mEntry->OpenInputStream(offset, _retval);
+ }
+ NS_IMETHOD OpenOutputStream(int64_t offset, int64_t predictedSize,
+ nsIOutputStream** _retval) override {
+ return mEntry->OpenOutputStream(offset, predictedSize, _retval);
+ }
+ NS_IMETHOD GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) override {
+ return mEntry->GetSecurityInfo(aSecurityInfo);
+ }
+ NS_IMETHOD SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo) override {
+ return mEntry->SetSecurityInfo(aSecurityInfo);
+ }
+ NS_IMETHOD GetStorageDataSize(uint32_t* aStorageDataSize) override {
+ return mEntry->GetStorageDataSize(aStorageDataSize);
+ }
+ NS_IMETHOD AsyncDoom(nsICacheEntryDoomCallback* listener) override {
+ return mEntry->AsyncDoom(listener);
+ }
+ NS_IMETHOD GetMetaDataElement(const char* key, char** _retval) override {
+ return mEntry->GetMetaDataElement(key, _retval);
+ }
+ NS_IMETHOD SetMetaDataElement(const char* key, const char* value) override {
+ return mEntry->SetMetaDataElement(key, value);
+ }
+ NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor* visitor) override {
+ return mEntry->VisitMetaData(visitor);
+ }
+ NS_IMETHOD MetaDataReady(void) override { return mEntry->MetaDataReady(); }
+ NS_IMETHOD SetValid(void) override { return mEntry->SetValid(); }
+ NS_IMETHOD GetDiskStorageSizeInKB(uint32_t* aDiskStorageSizeInKB) override {
+ return mEntry->GetDiskStorageSizeInKB(aDiskStorageSizeInKB);
+ }
+ NS_IMETHOD Recreate(bool aMemoryOnly, nsICacheEntry** _retval) override {
+ return mEntry->Recreate(aMemoryOnly, _retval);
+ }
+ NS_IMETHOD GetDataSize(int64_t* aDataSize) override {
+ return mEntry->GetDataSize(aDataSize);
+ }
+ NS_IMETHOD GetAltDataSize(int64_t* aAltDataSize) override {
+ return mEntry->GetAltDataSize(aAltDataSize);
+ }
+ NS_IMETHOD GetAltDataType(nsACString& aType) override {
+ return mEntry->GetAltDataType(aType);
+ }
+ NS_IMETHOD OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) override {
+ return mEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
+ }
+ NS_IMETHOD OpenAlternativeInputStream(const nsACString& type,
+ nsIInputStream** _retval) override {
+ return mEntry->OpenAlternativeInputStream(type, _retval);
+ }
+ NS_IMETHOD GetLoadContextInfo(
+ nsILoadContextInfo** aLoadContextInfo) override {
+ return mEntry->GetLoadContextInfo(aLoadContextInfo);
+ }
+ NS_IMETHOD Close(void) override { return mEntry->Close(); }
+ NS_IMETHOD MarkValid(void) override { return mEntry->MarkValid(); }
+ NS_IMETHOD MaybeMarkValid(void) override { return mEntry->MaybeMarkValid(); }
+ NS_IMETHOD HasWriteAccess(bool aWriteAllowed, bool* _retval) override {
+ return mEntry->HasWriteAccess(aWriteAllowed, _retval);
+ }
+
+ // Specific implementation:
+ NS_IMETHOD Dismiss() override;
+
+ private:
+ virtual ~CacheEntryHandle();
+ RefPtr<CacheEntry> mEntry;
+
+ // This is |false| until Dismiss() was called and prevents OnHandleClosed
+ // being called more than once.
+ Atomic<bool, ReleaseAcquire> mClosed{false};
+};
+
+class CacheOutputCloseListener final : public Runnable {
+ public:
+ void OnOutputClosed();
+
+ private:
+ friend class CacheEntry;
+
+ virtual ~CacheOutputCloseListener() = default;
+
+ NS_DECL_NSIRUNNABLE
+ explicit CacheOutputCloseListener(CacheEntry* aEntry);
+
+ private:
+ RefPtr<CacheEntry> mEntry;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFile.cpp b/netwerk/cache2/CacheFile.cpp
new file mode 100644
index 0000000000..02a9f3bce7
--- /dev/null
+++ b/netwerk/cache2/CacheFile.cpp
@@ -0,0 +1,2589 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheFile.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "CacheFileChunk.h"
+#include "CacheFileInputStream.h"
+#include "CacheFileOutputStream.h"
+#include "CacheFileUtils.h"
+#include "CacheIndex.h"
+#include "CacheLog.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICacheEntry.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+// When CACHE_CHUNKS is defined we always cache unused chunks in mCacheChunks.
+// When it is not defined, we always release the chunks ASAP, i.e. we cache
+// unused chunks only when:
+// - CacheFile is memory-only
+// - CacheFile is still waiting for the handle
+// - the chunk is preloaded
+
+// #define CACHE_CHUNKS
+
+namespace mozilla::net {
+
+using CacheFileUtils::CacheFileLock;
+
+class NotifyCacheFileListenerEvent : public Runnable {
+ public:
+ NotifyCacheFileListenerEvent(CacheFileListener* aCallback, nsresult aResult,
+ bool aIsNew)
+ : Runnable("net::NotifyCacheFileListenerEvent"),
+ mCallback(aCallback),
+ mRV(aResult),
+ mIsNew(aIsNew) {
+ LOG(
+ ("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
+ "[this=%p]",
+ this));
+ }
+
+ protected:
+ ~NotifyCacheFileListenerEvent() {
+ LOG(
+ ("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
+ "[this=%p]",
+ this));
+ }
+
+ public:
+ NS_IMETHOD Run() override {
+ LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this));
+
+ mCallback->OnFileReady(mRV, mIsNew);
+ return NS_OK;
+ }
+
+ protected:
+ nsCOMPtr<CacheFileListener> mCallback;
+ nsresult mRV;
+ bool mIsNew;
+};
+
+class NotifyChunkListenerEvent : public Runnable {
+ public:
+ NotifyChunkListenerEvent(CacheFileChunkListener* aCallback, nsresult aResult,
+ uint32_t aChunkIdx, CacheFileChunk* aChunk)
+ : Runnable("net::NotifyChunkListenerEvent"),
+ mCallback(aCallback),
+ mRV(aResult),
+ mChunkIdx(aChunkIdx),
+ mChunk(aChunk) {
+ LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
+ this));
+ }
+
+ protected:
+ ~NotifyChunkListenerEvent() {
+ LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
+ this));
+ }
+
+ public:
+ NS_IMETHOD Run() override {
+ LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this));
+
+ mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
+ return NS_OK;
+ }
+
+ protected:
+ nsCOMPtr<CacheFileChunkListener> mCallback;
+ nsresult mRV;
+ uint32_t mChunkIdx;
+ RefPtr<CacheFileChunk> mChunk;
+};
+
+class DoomFileHelper : public CacheFileIOListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit DoomFileHelper(CacheFileListener* aListener)
+ : mListener(aListener) {}
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override {
+ if (mListener) mListener->OnFileDoomed(aResult);
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
+ nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnFileRenamed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ private:
+ virtual ~DoomFileHelper() = default;
+
+ nsCOMPtr<CacheFileListener> mListener;
+};
+
+NS_IMPL_ISUPPORTS(DoomFileHelper, CacheFileIOListener)
+
+NS_IMPL_ADDREF(CacheFile)
+NS_IMPL_RELEASE(CacheFile)
+NS_INTERFACE_MAP_BEGIN(CacheFile)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,
+ mozilla::net::CacheFileChunkListener)
+NS_INTERFACE_MAP_END
+
+CacheFile::CacheFile() : mLock(new CacheFileLock()) {
+ LOG(("CacheFile::CacheFile() [this=%p]", this));
+}
+
+CacheFile::~CacheFile() {
+ LOG(("CacheFile::~CacheFile() [this=%p]", this));
+
+ MutexAutoLock lock(mLock->Lock());
+ if (!mMemoryOnly && mReady && !mKill) {
+ // mReady flag indicates we have metadata plus in a valid state.
+ WriteMetadataIfNeededLocked(true);
+ }
+}
+
+nsresult CacheFile::Init(const nsACString& aKey, bool aCreateNew,
+ bool aMemoryOnly, bool aSkipSizeCheck, bool aPriority,
+ bool aPinned, CacheFileListener* aCallback)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ MOZ_ASSERT(!mListener);
+ MOZ_ASSERT(!mHandle);
+
+ MOZ_ASSERT(!(aMemoryOnly && aPinned));
+
+ nsresult rv;
+
+ mKey = aKey;
+ mOpenAsMemoryOnly = mMemoryOnly = aMemoryOnly;
+ mSkipSizeCheck = aSkipSizeCheck;
+ mPriority = aPriority;
+ mPinned = aPinned;
+
+ // Some consumers (at least nsHTTPCompressConv) assume that Read() can read
+ // such amount of data that was announced by Available().
+ // CacheFileInputStream::Available() uses also preloaded chunks to compute
+ // number of available bytes in the input stream, so we have to make sure the
+ // preloadChunkCount won't change during CacheFile's lifetime since otherwise
+ // we could potentially release some cached chunks that was used to calculate
+ // available bytes but would not be available later during call to
+ // CacheFileInputStream::Read().
+ mPreloadChunkCount = CacheObserver::PreloadChunkCount();
+
+ LOG(
+ ("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
+ "priority=%d, listener=%p]",
+ this, mKey.get(), aCreateNew, aMemoryOnly, aPriority, aCallback));
+
+ if (mMemoryOnly) {
+ MOZ_ASSERT(!aCallback);
+
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, false, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+ return NS_OK;
+ }
+ uint32_t flags;
+ if (aCreateNew) {
+ MOZ_ASSERT(!aCallback);
+ flags = CacheFileIOManager::CREATE_NEW;
+
+ // make sure we can use this entry immediately
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+ } else {
+ flags = CacheFileIOManager::CREATE;
+ }
+
+ if (mPriority) {
+ flags |= CacheFileIOManager::PRIORITY;
+ }
+
+ if (mPinned) {
+ flags |= CacheFileIOManager::PINNED;
+ }
+
+ mOpeningFile = true;
+ mListener = aCallback;
+ rv = CacheFileIOManager::OpenFile(mKey, flags, this);
+ if (NS_FAILED(rv)) {
+ mListener = nullptr;
+ mOpeningFile = false;
+
+ if (mPinned) {
+ LOG(
+ ("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+ "but we want to pin, fail the file opening. [this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aCreateNew) {
+ NS_WARNING("Forcing memory-only entry since OpenFile failed");
+ LOG(
+ ("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+ "synchronously. We can continue in memory-only mode since "
+ "aCreateNew == true. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ } else if (rv == NS_ERROR_NOT_INITIALIZED) {
+ NS_WARNING(
+ "Forcing memory-only entry since CacheIOManager isn't "
+ "initialized.");
+ LOG(
+ ("CacheFile::Init() - CacheFileIOManager isn't initialized, "
+ "initializing entry as memory-only. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+
+ RefPtr<NotifyCacheFileListenerEvent> ev;
+ ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true);
+ rv = NS_DispatchToCurrentThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+void CacheFile::Key(nsACString& aKey) {
+ CacheFileAutoLock lock(this);
+ aKey = mKey;
+}
+
+bool CacheFile::IsPinned() {
+ CacheFileAutoLock lock(this);
+ return mPinned;
+}
+
+nsresult CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk* aChunk) {
+ CacheFileAutoLock lock(this);
+
+ nsresult rv;
+
+ uint32_t index = aChunk->Index();
+
+ LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08" PRIx32
+ ", chunk=%p, idx=%u]",
+ this, static_cast<uint32_t>(aResult), aChunk, index));
+
+ if (aChunk->mDiscardedChunk) {
+ // We discard only unused chunks, so it must be still unused when reading
+ // data finishes.
+ MOZ_ASSERT(aChunk->mRefCnt == 2);
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(
+ RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ DebugOnly<bool> removed = mDiscardedChunks.RemoveElement(aChunk);
+ MOZ_ASSERT(removed);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aResult)) {
+ SetError(aResult);
+ }
+
+ if (HaveChunkListeners(index)) {
+ rv = NotifyChunkListeners(index, aResult, aChunk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk* aChunk) {
+ // In case the chunk was reused, made dirty and released between calls to
+ // CacheFileChunk::Write() and CacheFile::OnChunkWritten(), we must write
+ // the chunk to the disk again. When the chunk is unused and is dirty simply
+ // addref and release (outside the lock) the chunk which ensures that
+ // CacheFile::DeactivateChunk() will be called again.
+ RefPtr<CacheFileChunk> deactivateChunkAgain;
+
+ CacheFileAutoLock lock(this);
+
+ nsresult rv;
+
+ LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08" PRIx32
+ ", chunk=%p, idx=%u]",
+ this, static_cast<uint32_t>(aResult), aChunk, aChunk->Index()));
+
+ MOZ_ASSERT(!mMemoryOnly);
+ MOZ_ASSERT(!mOpeningFile);
+ MOZ_ASSERT(mHandle);
+
+ if (aChunk->mDiscardedChunk) {
+ // We discard only unused chunks, so it must be still unused when writing
+ // data finishes.
+ MOZ_ASSERT(aChunk->mRefCnt == 2);
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(
+ RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ DebugOnly<bool> removed = mDiscardedChunks.RemoveElement(aChunk);
+ MOZ_ASSERT(removed);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aResult)) {
+ SetError(aResult);
+ }
+
+ if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) {
+ // update hash value in metadata
+ mMetadata->SetHash(aChunk->Index(), aChunk->Hash());
+ }
+
+ // notify listeners if there is any
+ if (HaveChunkListeners(aChunk->Index())) {
+ // don't release the chunk since there are some listeners queued
+ rv = NotifyChunkListeners(aChunk->Index(), aResult, aChunk);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(aChunk->mRefCnt != 2);
+ return NS_OK;
+ }
+ }
+
+ if (aChunk->mRefCnt != 2) {
+ LOG(
+ ("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p,"
+ " refcnt=%" PRIuPTR "]",
+ this, aChunk, aChunk->mRefCnt.get()));
+
+ return NS_OK;
+ }
+
+ if (aChunk->IsDirty()) {
+ LOG(
+ ("CacheFile::OnChunkWritten() - Unused chunk is dirty. We must go "
+ "through deactivation again. [this=%p, chunk=%p]",
+ this, aChunk));
+
+ deactivateChunkAgain = aChunk;
+ return NS_OK;
+ }
+
+ bool keepChunk = false;
+ if (NS_SUCCEEDED(aResult)) {
+ keepChunk = ShouldCacheChunk(aChunk->Index());
+ LOG(("CacheFile::OnChunkWritten() - %s unused chunk [this=%p, chunk=%p]",
+ keepChunk ? "Caching" : "Releasing", this, aChunk));
+ } else {
+ LOG(
+ ("CacheFile::OnChunkWritten() - Releasing failed chunk [this=%p, "
+ "chunk=%p]",
+ this, aChunk));
+ }
+
+ RemoveChunkInternal(aChunk, keepChunk);
+
+ WriteMetadataIfNeededLocked();
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnChunkUpdated(CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) {
+ // Using an 'auto' class to perform doom or fail the listener
+ // outside the CacheFile's lock.
+ class AutoFailDoomListener {
+ public:
+ explicit AutoFailDoomListener(CacheFileHandle* aHandle)
+ : mHandle(aHandle), mAlreadyDoomed(false) {}
+ ~AutoFailDoomListener() {
+ if (!mListener) return;
+
+ if (mHandle) {
+ if (mAlreadyDoomed) {
+ mListener->OnFileDoomed(mHandle, NS_OK);
+ } else {
+ CacheFileIOManager::DoomFile(mHandle, mListener);
+ }
+ } else {
+ mListener->OnFileDoomed(nullptr, NS_ERROR_NOT_AVAILABLE);
+ }
+ }
+
+ CacheFileHandle* mHandle;
+ nsCOMPtr<CacheFileIOListener> mListener;
+ bool mAlreadyDoomed;
+ } autoDoom(aHandle);
+
+ RefPtr<CacheFileMetadata> metadata;
+ nsCOMPtr<CacheFileListener> listener;
+ bool isNew = false;
+ nsresult retval = NS_OK;
+
+ {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mOpeningFile);
+ MOZ_ASSERT((NS_SUCCEEDED(aResult) && aHandle) ||
+ (NS_FAILED(aResult) && !aHandle));
+ MOZ_ASSERT((mListener && !mMetadata) || // !createNew
+ (!mListener && mMetadata)); // createNew
+ MOZ_ASSERT(!mMemoryOnly || mMetadata); // memory-only was set on new entry
+
+ LOG(("CacheFile::OnFileOpened() [this=%p, rv=0x%08" PRIx32 ", handle=%p]",
+ this, static_cast<uint32_t>(aResult), aHandle));
+
+ mOpeningFile = false;
+
+ autoDoom.mListener.swap(mDoomAfterOpenListener);
+
+ if (mMemoryOnly) {
+ // We can be here only in case the entry was initilized as createNew and
+ // SetMemoryOnly() was called.
+
+ // Just don't store the handle into mHandle and exit
+ autoDoom.mAlreadyDoomed = true;
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aResult)) {
+ if (mMetadata) {
+ // This entry was initialized as createNew, just switch to memory-only
+ // mode.
+ NS_WARNING("Forcing memory-only entry since OpenFile failed");
+ LOG(
+ ("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
+ "failed asynchronously. We can continue in memory-only mode since "
+ "aCreateNew == true. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ return NS_OK;
+ }
+
+ if (aResult == NS_ERROR_FILE_INVALID_PATH) {
+ // CacheFileIOManager doesn't have mCacheDirectory, switch to
+ // memory-only mode.
+ NS_WARNING(
+ "Forcing memory-only entry since CacheFileIOManager doesn't "
+ "have mCacheDirectory.");
+ LOG(
+ ("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
+ "mCacheDirectory, initializing entry as memory-only. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+
+ isNew = true;
+ retval = NS_OK;
+ } else {
+ // CacheFileIOManager::OpenFile() failed for another reason.
+ isNew = false;
+ retval = aResult;
+ }
+
+ mListener.swap(listener);
+ } else {
+ mHandle = aHandle;
+ if (NS_FAILED(mStatus)) {
+ CacheFileIOManager::DoomFile(mHandle, nullptr);
+ }
+
+ if (mMetadata) {
+ InitIndexEntry();
+
+ // The entry was initialized as createNew, don't try to read metadata.
+ mMetadata->SetHandle(mHandle);
+
+ // Write all cached chunks, otherwise they may stay unwritten.
+ for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+ RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ LOG(("CacheFile::OnFileOpened() - write [this=%p, idx=%u, chunk=%p]",
+ this, idx, chunk.get()));
+
+ mChunks.InsertOrUpdate(idx, RefPtr{chunk});
+ chunk->mFile = this;
+ chunk->mActiveChunk = true;
+
+ MOZ_ASSERT(chunk->IsReady());
+
+ // This would be cleaner if we had an nsRefPtr constructor that took
+ // a RefPtr<Derived>.
+ ReleaseOutsideLock(std::move(chunk));
+
+ iter.Remove();
+ }
+
+ return NS_OK;
+ }
+ }
+ if (listener) {
+ lock.Unlock();
+ listener->OnFileReady(retval, isNew);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_SUCCEEDED(aResult));
+ MOZ_ASSERT(!mMetadata);
+ MOZ_ASSERT(mListener);
+
+ // mMetaData is protected by a lock, but ReadMetaData has to be called
+ // without the lock. Alternatively we could make a
+ // "ReadMetaDataLocked", and temporarily unlock to call OnFileReady
+ metadata = mMetadata =
+ new CacheFileMetadata(mHandle, mKey, WrapNotNull(mLock));
+ }
+ metadata->ReadMetadata(this);
+ return NS_OK;
+}
+
+nsresult CacheFile::OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnDataWritten should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnDataRead should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnMetadataRead(nsresult aResult) {
+ nsCOMPtr<CacheFileListener> listener;
+ bool isNew = false;
+ {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mListener);
+
+ LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08" PRIx32 "]", this,
+ static_cast<uint32_t>(aResult)));
+
+ if (NS_SUCCEEDED(aResult)) {
+ mPinned = mMetadata->Pinned();
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+ if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
+ isNew = true;
+ mMetadata->MarkDirty();
+ } else {
+ const char* altData =
+ mMetadata->GetElement(CacheFileUtils::kAltDataKey);
+ if (altData && (NS_FAILED(CacheFileUtils::ParseAlternativeDataInfo(
+ altData, &mAltDataOffset, &mAltDataType)) ||
+ (mAltDataOffset > mDataSize))) {
+ // alt-metadata cannot be parsed or alt-data offset is invalid
+ mMetadata->InitEmptyMetadata();
+ isNew = true;
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ mDataSize = 0;
+ } else {
+ PreloadChunks(0);
+ }
+ }
+
+ InitIndexEntry();
+ }
+
+ mListener.swap(listener);
+ }
+ listener->OnFileReady(aResult, isNew);
+ return NS_OK;
+}
+
+nsresult CacheFile::OnMetadataWritten(nsresult aResult) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::OnMetadataWritten() [this=%p, rv=0x%08" PRIx32 "]", this,
+ static_cast<uint32_t>(aResult)));
+
+ MOZ_ASSERT(mWritingMetadata);
+ mWritingMetadata = false;
+
+ MOZ_ASSERT(!mMemoryOnly);
+ MOZ_ASSERT(!mOpeningFile);
+
+ if (NS_WARN_IF(NS_FAILED(aResult))) {
+ // TODO close streams with an error ???
+ SetError(aResult);
+ }
+
+ if (mOutput || mInputs.Length() || mChunks.Count()) return NS_OK;
+
+ if (IsDirty()) WriteMetadataIfNeededLocked();
+
+ if (!mWritingMetadata) {
+ LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
+ this));
+ CacheFileIOManager::ReleaseNSPRHandle(mHandle);
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) {
+ nsCOMPtr<CacheFileListener> listener;
+
+ {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mListener);
+
+ LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08" PRIx32 ", handle=%p]",
+ this, static_cast<uint32_t>(aResult), aHandle));
+
+ mListener.swap(listener);
+ }
+
+ listener->OnFileDoomed(aResult);
+ return NS_OK;
+}
+
+nsresult CacheFile::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnFileRenamed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+bool CacheFile::IsKilled() {
+ bool killed = mKill;
+ if (killed) {
+ LOG(("CacheFile is killed, this=%p", this));
+ }
+
+ return killed;
+}
+
+nsresult CacheFile::OpenInputStream(nsICacheEntry* aEntryHandle,
+ nsIInputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ if (!mReady) {
+ LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenInputStream() - CacheFile is in a failure state "
+ "[this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // Don't allow opening the input stream when this CacheFile is in
+ // a failed state. This is the only way to protect consumers correctly
+ // from reading a broken entry. When the file is in the failed state,
+ // it's also doomed, so reopening the entry won't make any difference -
+ // data will still be inaccessible anymore. Note that for just doomed
+ // files, we must allow reading the data.
+ return mStatus;
+ }
+
+ // Once we open input stream we no longer allow preloading of chunks without
+ // input stream, i.e. we will no longer keep first few chunks preloaded when
+ // the last input stream is closed.
+ mPreloadWithoutInputStreams = false;
+
+ CacheFileInputStream* input =
+ new CacheFileInputStream(this, aEntryHandle, false);
+ LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
+ input, this));
+
+ mInputs.AppendElement(input);
+ NS_ADDREF(input);
+
+ mDataAccessed = true;
+ *_retval = do_AddRef(input).take();
+ return NS_OK;
+}
+
+nsresult CacheFile::OpenAlternativeInputStream(nsICacheEntry* aEntryHandle,
+ const char* aAltDataType,
+ nsIInputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ if (NS_WARN_IF(!mReady)) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - CacheFile is not ready "
+ "[this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mAltDataOffset == -1) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - Alternative data is not "
+ "available [this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - CacheFile is in a failure "
+ "state [this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // Don't allow opening the input stream when this CacheFile is in
+ // a failed state. This is the only way to protect consumers correctly
+ // from reading a broken entry. When the file is in the failed state,
+ // it's also doomed, so reopening the entry won't make any difference -
+ // data will still be inaccessible anymore. Note that for just doomed
+ // files, we must allow reading the data.
+ return mStatus;
+ }
+
+ if (mAltDataType != aAltDataType) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - Alternative data is of a "
+ "different type than requested [this=%p, availableType=%s, "
+ "requestedType=%s]",
+ this, mAltDataType.get(), aAltDataType));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Once we open input stream we no longer allow preloading of chunks without
+ // input stream, i.e. we will no longer keep first few chunks preloaded when
+ // the last input stream is closed.
+ mPreloadWithoutInputStreams = false;
+
+ CacheFileInputStream* input =
+ new CacheFileInputStream(this, aEntryHandle, true);
+
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - Creating new input stream %p "
+ "[this=%p]",
+ input, this));
+
+ mInputs.AppendElement(input);
+ NS_ADDREF(input);
+
+ mDataAccessed = true;
+ *_retval = do_AddRef(input).take();
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OpenOutputStream(CacheOutputCloseListener* aCloseListener,
+ nsIOutputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ nsresult rv;
+
+ if (!mReady) {
+ LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mOutput) {
+ LOG(
+ ("CacheFile::OpenOutputStream() - We already have output stream %p "
+ "[this=%p]",
+ mOutput, this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenOutputStream() - CacheFile is in a failure state "
+ "[this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // The CacheFile is already doomed. It make no sense to allow to write any
+ // data to such entry.
+ return mStatus;
+ }
+
+ // Fail if there is any input stream opened for alternative data
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ if (mInputs[i]->IsAlternativeData()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ if (mAltDataOffset != -1) {
+ // Remove alt-data
+ rv = Truncate(mAltDataOffset);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::OpenOutputStream() - Truncating alt-data failed "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ SetAltMetadata(nullptr);
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ }
+
+ // Once we open output stream we no longer allow preloading of chunks without
+ // input stream. There is no reason to believe that some input stream will be
+ // opened soon. Otherwise we would cache unused chunks of all newly created
+ // entries until the CacheFile is destroyed.
+ mPreloadWithoutInputStreams = false;
+
+ mOutput = new CacheFileOutputStream(this, aCloseListener, false);
+
+ LOG(
+ ("CacheFile::OpenOutputStream() - Creating new output stream %p "
+ "[this=%p]",
+ mOutput, this));
+
+ mDataAccessed = true;
+ *_retval = do_AddRef(mOutput).take();
+ return NS_OK;
+}
+
+nsresult CacheFile::OpenAlternativeOutputStream(
+ CacheOutputCloseListener* aCloseListener, const char* aAltDataType,
+ nsIAsyncOutputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ if (!mReady) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - CacheFile is not ready "
+ "[this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mOutput) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - We already have output "
+ "stream %p [this=%p]",
+ mOutput, this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - CacheFile is in a failure "
+ "state [this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // The CacheFile is already doomed. It make no sense to allow to write any
+ // data to such entry.
+ return mStatus;
+ }
+
+ // Fail if there is any input stream opened for alternative data
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ if (mInputs[i]->IsAlternativeData()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ nsresult rv;
+
+ if (mAltDataOffset != -1) {
+ // Truncate old alt-data
+ rv = Truncate(mAltDataOffset);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - Truncating old alt-data "
+ "failed [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ } else {
+ mAltDataOffset = mDataSize;
+ }
+
+ nsAutoCString altMetadata;
+ CacheFileUtils::BuildAlternativeDataInfo(aAltDataType, mAltDataOffset,
+ altMetadata);
+ rv = SetAltMetadata(altMetadata.get());
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - Set Metadata for alt-data"
+ "failed [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ // Once we open output stream we no longer allow preloading of chunks without
+ // input stream. There is no reason to believe that some input stream will be
+ // opened soon. Otherwise we would cache unused chunks of all newly created
+ // entries until the CacheFile is destroyed.
+ mPreloadWithoutInputStreams = false;
+
+ mOutput = new CacheFileOutputStream(this, aCloseListener, true);
+
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - Creating new output stream "
+ "%p [this=%p]",
+ mOutput, this));
+
+ mDataAccessed = true;
+ mAltDataType = aAltDataType;
+ *_retval = do_AddRef(mOutput).take();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetMemoryOnly() {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetMemoryOnly() mMemoryOnly=%d [this=%p]", mMemoryOnly,
+ this));
+
+ if (mMemoryOnly) return NS_OK;
+
+ MOZ_ASSERT(mReady);
+
+ if (!mReady) {
+ LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mDataAccessed) {
+ LOG(("CacheFile::SetMemoryOnly() - Data was already accessed [this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // TODO what to do when this isn't a new entry and has an existing metadata???
+ mMemoryOnly = true;
+ return NS_OK;
+}
+
+nsresult CacheFile::Doom(CacheFileListener* aCallback) {
+ LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback));
+
+ CacheFileAutoLock lock(this);
+
+ return DoomLocked(aCallback);
+}
+
+nsresult CacheFile::DoomLocked(CacheFileListener* aCallback) {
+ AssertOwnsLock();
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ LOG(("CacheFile::DoomLocked() [this=%p, listener=%p]", this, aCallback));
+
+ nsresult rv = NS_OK;
+
+ if (mMemoryOnly) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ if (mHandle && mHandle->IsDoomed()) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ nsCOMPtr<CacheFileIOListener> listener;
+ if (aCallback || !mHandle) {
+ listener = new DoomFileHelper(aCallback);
+ }
+ if (mHandle) {
+ rv = CacheFileIOManager::DoomFile(mHandle, listener);
+ } else if (mOpeningFile) {
+ mDoomAfterOpenListener = listener;
+ }
+
+ return rv;
+}
+
+nsresult CacheFile::ThrowMemoryCachedData() {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this));
+
+ if (mMemoryOnly) {
+ // This method should not be called when the CacheFile was initialized as
+ // memory-only, but it can be called when CacheFile end up as memory-only
+ // due to e.g. IO failure since CacheEntry doesn't know it.
+ LOG(
+ ("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
+ "entry is memory-only. [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mOpeningFile) {
+ // mayhemer, note: we shouldn't get here, since CacheEntry prevents loading
+ // entries from being purged.
+
+ LOG(
+ ("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
+ "entry is still opening the file [this=%p]",
+ this));
+
+ return NS_ERROR_ABORT;
+ }
+
+ // We cannot release all cached chunks since we need to keep preloaded chunks
+ // in memory. See initialization of mPreloadChunkCount for explanation.
+ CleanUpCachedChunks();
+
+ return NS_OK;
+}
+
+nsresult CacheFile::GetElement(const char* aKey, char** _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ const char* value;
+ value = mMetadata->GetElement(aKey);
+ if (!value) return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = NS_xstrdup(value);
+ return NS_OK;
+}
+
+nsresult CacheFile::SetElement(const char* aKey, const char* aValue) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetElement() this=%p", this));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ if (!strcmp(aKey, CacheFileUtils::kAltDataKey)) {
+ NS_ERROR(
+ "alt-data element is reserved for internal use and must not be "
+ "changed via CacheFile::SetElement()");
+ return NS_ERROR_FAILURE;
+ }
+
+ PostWriteTimer();
+ return mMetadata->SetElement(aKey, aValue);
+}
+
+nsresult CacheFile::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ MOZ_ASSERT(mReady);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ mMetadata->Visit(aVisitor);
+ return NS_OK;
+}
+
+nsresult CacheFile::ElementsSize(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+
+ if (!mMetadata) return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = mMetadata->ElementsSize();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetExpirationTime(uint32_t aExpirationTime) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetExpirationTime() this=%p, expiration=%u", this,
+ aExpirationTime));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+ mMetadata->SetExpirationTime(aExpirationTime);
+ return NS_OK;
+}
+
+nsresult CacheFile::GetExpirationTime(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ *_retval = mMetadata->GetExpirationTime();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetFrecency(uint32_t aFrecency) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetFrecency() this=%p, frecency=%u", this, aFrecency));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(mHandle, &aFrecency, nullptr, nullptr,
+ nullptr, nullptr);
+ }
+
+ mMetadata->SetFrecency(aFrecency);
+ return NS_OK;
+}
+
+nsresult CacheFile::GetFrecency(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+ *_retval = mMetadata->GetFrecency();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetNetworkTimes(uint64_t aOnStartTime,
+ uint64_t aOnStopTime) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetNetworkTimes() this=%p, aOnStartTime=%" PRIu64
+ ", aOnStopTime=%" PRIu64 "",
+ this, aOnStartTime, aOnStopTime));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ nsAutoCString onStartTime;
+ onStartTime.AppendInt(aOnStartTime);
+ nsresult rv =
+ mMetadata->SetElement("net-response-time-onstart", onStartTime.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString onStopTime;
+ onStopTime.AppendInt(aOnStopTime);
+ rv = mMetadata->SetElement("net-response-time-onstop", onStopTime.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint16_t onStartTime16 = aOnStartTime <= kIndexTimeOutOfBound
+ ? aOnStartTime
+ : kIndexTimeOutOfBound;
+ uint16_t onStopTime16 =
+ aOnStopTime <= kIndexTimeOutOfBound ? aOnStopTime : kIndexTimeOutOfBound;
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(
+ mHandle, nullptr, nullptr, &onStartTime16, &onStopTime16, nullptr);
+ }
+ return NS_OK;
+}
+
+nsresult CacheFile::GetOnStartTime(uint64_t* _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mMetadata);
+ const char* onStartTimeStr =
+ mMetadata->GetElement("net-response-time-onstart");
+ if (!onStartTimeStr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv;
+ *_retval = nsDependentCString(onStartTimeStr).ToInteger64(&rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+}
+
+nsresult CacheFile::GetOnStopTime(uint64_t* _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mMetadata);
+ const char* onStopTimeStr = mMetadata->GetElement("net-response-time-onstop");
+ if (!onStopTimeStr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv;
+ *_retval = nsDependentCString(onStopTimeStr).ToInteger64(&rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+}
+
+nsresult CacheFile::SetContentType(uint8_t aContentType) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetContentType() this=%p, contentType=%u", this,
+ aContentType));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ // Save the content type to metadata for case we need to rebuild the index.
+ nsAutoCString contentType;
+ contentType.AppendInt(aContentType);
+ nsresult rv = mMetadata->SetElement("ctid", contentType.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, nullptr, nullptr,
+ nullptr, &aContentType);
+ }
+ return NS_OK;
+}
+
+nsresult CacheFile::SetAltMetadata(const char* aAltMetadata) {
+ AssertOwnsLock();
+ LOG(("CacheFile::SetAltMetadata() this=%p, aAltMetadata=%s", this,
+ aAltMetadata ? aAltMetadata : ""));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ nsresult rv =
+ mMetadata->SetElement(CacheFileUtils::kAltDataKey, aAltMetadata);
+
+ bool hasAltData = !!aAltMetadata;
+
+ if (NS_FAILED(rv)) {
+ // Removing element shouldn't fail because it doesn't allocate memory.
+ mMetadata->SetElement(CacheFileUtils::kAltDataKey, nullptr);
+
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ hasAltData = false;
+ }
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, &hasAltData, nullptr,
+ nullptr, nullptr);
+ }
+ return rv;
+}
+
+nsresult CacheFile::GetLastModified(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ *_retval = mMetadata->GetLastModified();
+ return NS_OK;
+}
+
+nsresult CacheFile::GetLastFetched(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ *_retval = mMetadata->GetLastFetched();
+ return NS_OK;
+}
+
+nsresult CacheFile::GetFetchCount(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+ *_retval = mMetadata->GetFetchCount();
+ return NS_OK;
+}
+
+nsresult CacheFile::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) {
+ CacheFileAutoLock lock(this);
+ if (!mHandle) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aDiskStorageSize = mHandle->FileSizeInK();
+ return NS_OK;
+}
+
+nsresult CacheFile::OnFetched() {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::OnFetched() this=%p", this));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ mMetadata->OnFetched();
+ return NS_OK;
+}
+
+void CacheFile::ReleaseOutsideLock(RefPtr<nsISupports> aObject) {
+ AssertOwnsLock();
+
+ mObjsToRelease.AppendElement(std::move(aObject));
+}
+
+nsresult CacheFile::GetChunkLocked(uint32_t aIndex, ECallerType aCaller,
+ CacheFileChunkListener* aCallback,
+ CacheFileChunk** _retval) {
+ AssertOwnsLock();
+
+ LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%u, caller=%d, listener=%p]",
+ this, aIndex, aCaller, aCallback));
+
+ MOZ_ASSERT(mReady);
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+ MOZ_ASSERT((aCaller == READER && aCallback) ||
+ (aCaller == WRITER && !aCallback) ||
+ (aCaller == PRELOADER && !aCallback));
+
+ // Preload chunks from disk when this is disk backed entry and the listener
+ // is reader.
+ bool preload = !mMemoryOnly && (aCaller == READER);
+
+ nsresult rv;
+
+ RefPtr<CacheFileChunk> chunk;
+ if (mChunks.Get(aIndex, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
+ chunk.get(), this));
+
+ // Preloader calls this method to preload only non-loaded chunks.
+ MOZ_ASSERT(aCaller != PRELOADER, "Unexpected!");
+
+ // We might get failed chunk between releasing the lock in
+ // CacheFileChunk::OnDataWritten/Read and CacheFile::OnChunkWritten/Read
+ rv = chunk->GetStatus();
+ if (NS_FAILED(rv)) {
+ SetError(rv);
+ LOG(
+ ("CacheFile::GetChunkLocked() - Found failed chunk in mChunks "
+ "[this=%p]",
+ this));
+ return rv;
+ }
+
+ if (chunk->IsReady() || aCaller == WRITER) {
+ chunk.swap(*_retval);
+ } else {
+ QueueChunkListener(aIndex, aCallback);
+ }
+
+ if (preload) {
+ PreloadChunks(aIndex + 1);
+ }
+
+ return NS_OK;
+ }
+
+ if (mCachedChunks.Get(aIndex, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]",
+ chunk.get(), this));
+
+ // Preloader calls this method to preload only non-loaded chunks.
+ MOZ_ASSERT(aCaller != PRELOADER, "Unexpected!");
+
+ mChunks.InsertOrUpdate(aIndex, RefPtr{chunk});
+ mCachedChunks.Remove(aIndex);
+ chunk->mFile = this;
+ chunk->mActiveChunk = true;
+
+ MOZ_ASSERT(chunk->IsReady());
+
+ chunk.swap(*_retval);
+
+ if (preload) {
+ PreloadChunks(aIndex + 1);
+ }
+
+ return NS_OK;
+ }
+
+ int64_t off = aIndex * static_cast<int64_t>(kChunkSize);
+
+ if (off < mDataSize) {
+ // We cannot be here if this is memory only entry since the chunk must exist
+ MOZ_ASSERT(!mMemoryOnly);
+ if (mMemoryOnly) {
+ // If this ever really happen it is better to fail rather than crashing on
+ // a null handle.
+ LOG(
+ ("CacheFile::GetChunkLocked() - Unexpected state! Offset < mDataSize "
+ "for memory-only entry. [this=%p, off=%" PRId64
+ ", mDataSize=%" PRId64 "]",
+ this, off, mDataSize));
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ chunk = new CacheFileChunk(this, aIndex, aCaller == WRITER);
+ mChunks.InsertOrUpdate(aIndex, RefPtr{chunk});
+ chunk->mActiveChunk = true;
+
+ LOG(
+ ("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
+ "the disk [this=%p]",
+ chunk.get(), this));
+
+ // Read the chunk from the disk
+ rv = chunk->Read(mHandle,
+ std::min(static_cast<uint32_t>(mDataSize - off),
+ static_cast<uint32_t>(kChunkSize)),
+ mMetadata->GetHash(aIndex), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RemoveChunkInternal(chunk, false);
+ return rv;
+ }
+
+ if (aCaller == WRITER) {
+ chunk.swap(*_retval);
+ } else if (aCaller != PRELOADER) {
+ QueueChunkListener(aIndex, aCallback);
+ }
+
+ if (preload) {
+ PreloadChunks(aIndex + 1);
+ }
+
+ return NS_OK;
+ }
+ if (off == mDataSize) {
+ if (aCaller == WRITER) {
+ // this listener is going to write to the chunk
+ chunk = new CacheFileChunk(this, aIndex, true);
+ mChunks.InsertOrUpdate(aIndex, RefPtr{chunk});
+ chunk->mActiveChunk = true;
+
+ LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
+ chunk.get(), this));
+
+ chunk->InitNew();
+ mMetadata->SetHash(aIndex, chunk->Hash());
+
+ if (HaveChunkListeners(aIndex)) {
+ rv = NotifyChunkListeners(aIndex, NS_OK, chunk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ chunk.swap(*_retval);
+ return NS_OK;
+ }
+ } else {
+ if (aCaller == WRITER) {
+ // this chunk was requested by writer, but we need to fill the gap first
+
+ // Fill with zero the last chunk if it is incomplete
+ if (mDataSize % kChunkSize) {
+ rv = PadChunkWithZeroes(mDataSize / kChunkSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(!(mDataSize % kChunkSize));
+ }
+
+ uint32_t startChunk = mDataSize / kChunkSize;
+
+ if (mMemoryOnly) {
+ // We need to create all missing CacheFileChunks if this is memory-only
+ // entry
+ for (uint32_t i = startChunk; i < aIndex; i++) {
+ rv = PadChunkWithZeroes(i);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // We don't need to create CacheFileChunk for other empty chunks unless
+ // there is some input stream waiting for this chunk.
+
+ if (startChunk != aIndex) {
+ // Make sure the file contains zeroes at the end of the file
+ rv = CacheFileIOManager::TruncateSeekSetEOF(
+ mHandle, startChunk * kChunkSize, aIndex * kChunkSize, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (uint32_t i = startChunk; i < aIndex; i++) {
+ if (HaveChunkListeners(i)) {
+ rv = PadChunkWithZeroes(i);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mMetadata->SetHash(i, kEmptyChunkHash);
+ mDataSize = (i + 1) * kChunkSize;
+ }
+ }
+ }
+
+ MOZ_ASSERT(mDataSize == off);
+ rv = GetChunkLocked(aIndex, WRITER, nullptr, getter_AddRefs(chunk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ chunk.swap(*_retval);
+ return NS_OK;
+ }
+ }
+
+ // We can be here only if the caller is reader since writer always create a
+ // new chunk above and preloader calls this method to preload only chunks that
+ // are not loaded but that do exist.
+ MOZ_ASSERT(aCaller == READER, "Unexpected!");
+
+ if (mOutput) {
+ // the chunk doesn't exist but mOutput may create it
+ QueueChunkListener(aIndex, aCallback);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+void CacheFile::PreloadChunks(uint32_t aIndex) {
+ AssertOwnsLock();
+
+ uint32_t limit = aIndex + mPreloadChunkCount;
+
+ for (uint32_t i = aIndex; i < limit; ++i) {
+ int64_t off = i * static_cast<int64_t>(kChunkSize);
+
+ if (off >= mDataSize) {
+ // This chunk is beyond EOF.
+ return;
+ }
+
+ if (mChunks.GetWeak(i) || mCachedChunks.GetWeak(i)) {
+ // This chunk is already in memory or is being read right now.
+ continue;
+ }
+
+ LOG(("CacheFile::PreloadChunks() - Preloading chunk [this=%p, idx=%u]",
+ this, i));
+
+ RefPtr<CacheFileChunk> chunk;
+ GetChunkLocked(i, PRELOADER, nullptr, getter_AddRefs(chunk));
+ // We've checked that we don't have this chunk, so no chunk must be
+ // returned.
+ MOZ_ASSERT(!chunk);
+ }
+}
+
+bool CacheFile::ShouldCacheChunk(uint32_t aIndex) {
+ AssertOwnsLock();
+
+#ifdef CACHE_CHUNKS
+ // We cache all chunks.
+ return true;
+#else
+
+ if (mPreloadChunkCount != 0 && mInputs.Length() == 0 &&
+ mPreloadWithoutInputStreams && aIndex < mPreloadChunkCount) {
+ // We don't have any input stream yet, but it is likely that some will be
+ // opened soon. Keep first mPreloadChunkCount chunks in memory. The
+ // condition is here instead of in MustKeepCachedChunk() since these
+ // chunks should be preloaded and can be kept in memory as an optimization,
+ // but they can be released at any time until they are considered as
+ // preloaded chunks for any input stream.
+ return true;
+ }
+
+ // Cache only chunks that we really need to keep.
+ return MustKeepCachedChunk(aIndex);
+#endif
+}
+
+bool CacheFile::MustKeepCachedChunk(uint32_t aIndex) {
+ AssertOwnsLock();
+
+ // We must keep the chunk when this is memory only entry or we don't have
+ // a handle yet.
+ if (mMemoryOnly || mOpeningFile) {
+ return true;
+ }
+
+ if (mPreloadChunkCount == 0) {
+ // Preloading of chunks is disabled
+ return false;
+ }
+
+ // Check whether this chunk should be considered as preloaded chunk for any
+ // existing input stream.
+
+ // maxPos is the position of the last byte in the given chunk
+ int64_t maxPos = static_cast<int64_t>(aIndex + 1) * kChunkSize - 1;
+
+ // minPos is the position of the first byte in a chunk that precedes the given
+ // chunk by mPreloadChunkCount chunks
+ int64_t minPos;
+ if (mPreloadChunkCount >= aIndex) {
+ minPos = 0;
+ } else {
+ minPos = static_cast<int64_t>(aIndex - mPreloadChunkCount) * kChunkSize;
+ }
+
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ int64_t inputPos = mInputs[i]->GetPosition();
+ if (inputPos >= minPos && inputPos <= maxPos) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult CacheFile::DeactivateChunk(CacheFileChunk* aChunk) {
+ nsresult rv;
+
+ // Avoid lock reentrancy by increasing the RefCnt
+ RefPtr<CacheFileChunk> chunk = aChunk;
+
+ {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::DeactivateChunk() [this=%p, chunk=%p, idx=%u]", this,
+ aChunk, aChunk->Index()));
+
+ MOZ_ASSERT(mReady);
+ MOZ_ASSERT((mHandle && !mMemoryOnly && !mOpeningFile) ||
+ (!mHandle && mMemoryOnly && !mOpeningFile) ||
+ (!mHandle && !mMemoryOnly && mOpeningFile));
+
+ if (aChunk->mRefCnt != 2) {
+ LOG(
+ ("CacheFile::DeactivateChunk() - Chunk is still used [this=%p, "
+ "chunk=%p, refcnt=%" PRIuPTR "]",
+ this, aChunk, aChunk->mRefCnt.get()));
+
+ // somebody got the reference before the lock was acquired
+ return NS_OK;
+ }
+
+ if (aChunk->mDiscardedChunk) {
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(
+ RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ DebugOnly<bool> removed = mDiscardedChunks.RemoveElement(aChunk);
+ MOZ_ASSERT(removed);
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ {
+ // We can be here iff the chunk is in the hash table
+ RefPtr<CacheFileChunk> chunkCheck;
+ mChunks.Get(chunk->Index(), getter_AddRefs(chunkCheck));
+ MOZ_ASSERT(chunkCheck == chunk);
+
+ // We also shouldn't have any queued listener for this chunk
+ ChunkListeners* listeners;
+ mChunkListeners.Get(chunk->Index(), &listeners);
+ MOZ_ASSERT(!listeners);
+ }
+#endif
+
+ if (NS_FAILED(chunk->GetStatus())) {
+ SetError(chunk->GetStatus());
+ }
+
+ if (NS_FAILED(mStatus)) {
+ // Don't write any chunk to disk since this entry will be doomed
+ LOG(
+ ("CacheFile::DeactivateChunk() - Releasing chunk because of status "
+ "[this=%p, chunk=%p, mStatus=0x%08" PRIx32 "]",
+ this, chunk.get(), static_cast<uint32_t>(mStatus)));
+
+ RemoveChunkInternal(chunk, false);
+ return mStatus;
+ }
+
+ if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) {
+ LOG(
+ ("CacheFile::DeactivateChunk() - Writing dirty chunk to the disk "
+ "[this=%p]",
+ this));
+
+ mDataIsDirty = true;
+
+ rv = chunk->Write(mHandle, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::DeactivateChunk() - CacheFileChunk::Write() failed "
+ "synchronously. Removing it. [this=%p, chunk=%p, rv=0x%08" PRIx32
+ "]",
+ this, chunk.get(), static_cast<uint32_t>(rv)));
+
+ RemoveChunkInternal(chunk, false);
+
+ SetError(rv);
+ return rv;
+ }
+
+ // Chunk will be removed in OnChunkWritten if it is still unused
+
+ // chunk needs to be released under the lock to be able to rely on
+ // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
+ chunk = nullptr;
+ return NS_OK;
+ }
+
+ bool keepChunk = ShouldCacheChunk(aChunk->Index());
+ LOG(("CacheFile::DeactivateChunk() - %s unused chunk [this=%p, chunk=%p]",
+ keepChunk ? "Caching" : "Releasing", this, chunk.get()));
+
+ RemoveChunkInternal(chunk, keepChunk);
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+ }
+
+ return NS_OK;
+}
+
+void CacheFile::RemoveChunkInternal(CacheFileChunk* aChunk, bool aCacheChunk) {
+ AssertOwnsLock();
+
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ if (aCacheChunk) {
+ mCachedChunks.InsertOrUpdate(aChunk->Index(), RefPtr{aChunk});
+ }
+
+ mChunks.Remove(aChunk->Index());
+}
+
+bool CacheFile::OutputStreamExists(bool aAlternativeData) {
+ AssertOwnsLock();
+
+ if (!mOutput) {
+ return false;
+ }
+
+ return mOutput->IsAlternativeData() == aAlternativeData;
+}
+
+int64_t CacheFile::BytesFromChunk(uint32_t aIndex, bool aAlternativeData) {
+ AssertOwnsLock();
+
+ int64_t dataSize;
+
+ if (mAltDataOffset != -1) {
+ if (aAlternativeData) {
+ dataSize = mDataSize;
+ } else {
+ dataSize = mAltDataOffset;
+ }
+ } else {
+ MOZ_ASSERT(!aAlternativeData);
+ dataSize = mDataSize;
+ }
+
+ if (!dataSize) {
+ return 0;
+ }
+
+ // Index of the last existing chunk.
+ uint32_t lastChunk = (dataSize - 1) / kChunkSize;
+ if (aIndex > lastChunk) {
+ return 0;
+ }
+
+ // We can use only preloaded chunks for the given stream to calculate
+ // available bytes if this is an entry stored on disk, since only those
+ // chunks are guaranteed not to be released.
+ uint32_t maxPreloadedChunk;
+ if (mMemoryOnly) {
+ maxPreloadedChunk = lastChunk;
+ } else {
+ maxPreloadedChunk = std::min(aIndex + mPreloadChunkCount, lastChunk);
+ }
+
+ uint32_t i;
+ for (i = aIndex; i <= maxPreloadedChunk; ++i) {
+ CacheFileChunk* chunk;
+
+ chunk = mChunks.GetWeak(i);
+ if (chunk) {
+ MOZ_ASSERT(i == lastChunk || chunk->DataSize() == kChunkSize);
+ if (chunk->IsReady()) {
+ continue;
+ }
+
+ // don't search this chunk in cached
+ break;
+ }
+
+ chunk = mCachedChunks.GetWeak(i);
+ if (chunk) {
+ MOZ_ASSERT(i == lastChunk || chunk->DataSize() == kChunkSize);
+ continue;
+ }
+
+ break;
+ }
+
+ // theoretic bytes in advance
+ int64_t advance = int64_t(i - aIndex) * kChunkSize;
+ // real bytes till the end of the file
+ int64_t tail = dataSize - (aIndex * kChunkSize);
+
+ return std::min(advance, tail);
+}
+
+nsresult CacheFile::Truncate(int64_t aOffset) {
+ AssertOwnsLock();
+
+ LOG(("CacheFile::Truncate() [this=%p, offset=%" PRId64 "]", this, aOffset));
+
+ nsresult rv;
+
+ // If we ever need to truncate on non alt-data boundary, we need to handle
+ // existing input streams.
+ MOZ_ASSERT(aOffset == mAltDataOffset,
+ "Truncating normal data not implemented");
+ MOZ_ASSERT(mReady);
+ MOZ_ASSERT(!mOutput);
+
+ uint32_t lastChunk = 0;
+ if (mDataSize > 0) {
+ lastChunk = (mDataSize - 1) / kChunkSize;
+ }
+
+ uint32_t newLastChunk = 0;
+ if (aOffset > 0) {
+ newLastChunk = (aOffset - 1) / kChunkSize;
+ }
+
+ uint32_t bytesInNewLastChunk = aOffset - newLastChunk * kChunkSize;
+
+ LOG(
+ ("CacheFileTruncate() - lastChunk=%u, newLastChunk=%u, "
+ "bytesInNewLastChunk=%u",
+ lastChunk, newLastChunk, bytesInNewLastChunk));
+
+ // Remove all truncated chunks from mCachedChunks
+ for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+
+ if (idx > newLastChunk) {
+ // This is unused chunk, simply remove it.
+ LOG(("CacheFile::Truncate() - removing cached chunk [idx=%u]", idx));
+ iter.Remove();
+ }
+ }
+
+ // We need to make sure no input stream holds a reference to a chunk we're
+ // going to discard. In theory, if alt-data begins at chunk boundary, input
+ // stream for normal data can get the chunk containing only alt-data via
+ // EnsureCorrectChunk() call. The input stream won't read the data from such
+ // chunk, but it will keep the reference until the stream is closed and we
+ // cannot simply discard this chunk.
+ int64_t maxInputChunk = -1;
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ int64_t inputChunk = mInputs[i]->GetChunkIdx();
+
+ if (maxInputChunk < inputChunk) {
+ maxInputChunk = inputChunk;
+ }
+
+ MOZ_RELEASE_ASSERT(mInputs[i]->GetPosition() <= aOffset);
+ }
+
+ MOZ_RELEASE_ASSERT(maxInputChunk <= newLastChunk + 1);
+ if (maxInputChunk == newLastChunk + 1) {
+ // Truncating must be done at chunk boundary
+ MOZ_RELEASE_ASSERT(bytesInNewLastChunk == kChunkSize);
+ newLastChunk++;
+ bytesInNewLastChunk = 0;
+ LOG(
+ ("CacheFile::Truncate() - chunk %p is still in use, using "
+ "newLastChunk=%u and bytesInNewLastChunk=%u",
+ mChunks.GetWeak(newLastChunk), newLastChunk, bytesInNewLastChunk));
+ }
+
+ // Discard all truncated chunks in mChunks
+ for (auto iter = mChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+
+ if (idx > newLastChunk) {
+ RefPtr<CacheFileChunk>& chunk = iter.Data();
+ LOG(("CacheFile::Truncate() - discarding chunk [idx=%u, chunk=%p]", idx,
+ chunk.get()));
+
+ if (HaveChunkListeners(idx)) {
+ NotifyChunkListeners(idx, NS_ERROR_NOT_AVAILABLE, chunk);
+ }
+
+ chunk->mDiscardedChunk = true;
+ mDiscardedChunks.AppendElement(chunk);
+ iter.Remove();
+ }
+ }
+
+ // Remove hashes of all removed chunks from the metadata
+ for (uint32_t i = lastChunk; i > newLastChunk; --i) {
+ mMetadata->RemoveHash(i);
+ }
+
+ // Truncate new last chunk
+ if (bytesInNewLastChunk == kChunkSize) {
+ LOG(("CacheFile::Truncate() - not truncating last chunk."));
+ } else {
+ RefPtr<CacheFileChunk> chunk;
+ if (mChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::Truncate() - New last chunk %p got from mChunks.",
+ chunk.get()));
+ } else if (mCachedChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::Truncate() - New last chunk %p got from mCachedChunks.",
+ chunk.get()));
+ } else {
+ // New last chunk isn't loaded but we need to update the hash.
+ MOZ_ASSERT(!mMemoryOnly);
+ MOZ_ASSERT(mHandle);
+
+ rv = GetChunkLocked(newLastChunk, PRELOADER, nullptr,
+ getter_AddRefs(chunk));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // We've checked that we don't have this chunk, so no chunk must be
+ // returned.
+ MOZ_ASSERT(!chunk);
+
+ if (!mChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ LOG(("CacheFile::Truncate() - New last chunk %p got from preloader.",
+ chunk.get()));
+ }
+
+ rv = chunk->GetStatus();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::Truncate() - New last chunk is failed "
+ "[status=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ chunk->Truncate(bytesInNewLastChunk);
+
+ // If the chunk is ready set the new hash now. If it's still being loaded
+ // CacheChunk::Truncate() made the chunk dirty and the hash will be updated
+ // in OnChunkWritten().
+ if (chunk->IsReady()) {
+ mMetadata->SetHash(newLastChunk, chunk->Hash());
+ }
+ }
+
+ if (mHandle) {
+ rv = CacheFileIOManager::TruncateSeekSetEOF(mHandle, aOffset, aOffset,
+ nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mDataSize = aOffset;
+
+ return NS_OK;
+}
+
+static uint32_t StatusToTelemetryEnum(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ return 0;
+ }
+
+ switch (aStatus) {
+ case NS_BASE_STREAM_CLOSED:
+ return 0; // Log this as a success
+ case NS_ERROR_OUT_OF_MEMORY:
+ return 2;
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ return 3;
+ case NS_ERROR_FILE_CORRUPTED:
+ return 4;
+ case NS_ERROR_FILE_NOT_FOUND:
+ return 5;
+ case NS_BINDING_ABORTED:
+ return 6;
+ default:
+ return 1; // other error
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+}
+
+void CacheFile::RemoveInput(CacheFileInputStream* aInput, nsresult aStatus) {
+ AssertOwnsLock();
+
+ LOG(("CacheFile::RemoveInput() [this=%p, input=%p, status=0x%08" PRIx32 "]",
+ this, aInput, static_cast<uint32_t>(aStatus)));
+
+ DebugOnly<bool> found{};
+ found = mInputs.RemoveElement(aInput);
+ MOZ_ASSERT(found);
+
+ ReleaseOutsideLock(
+ already_AddRefed<nsIInputStream>(static_cast<nsIInputStream*>(aInput)));
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+
+ // If the input didn't read all data, there might be left some preloaded
+ // chunks that won't be used anymore.
+ CleanUpCachedChunks();
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_INPUT_STREAM_STATUS,
+ StatusToTelemetryEnum(aStatus));
+}
+
+void CacheFile::RemoveOutput(CacheFileOutputStream* aOutput, nsresult aStatus) {
+ AssertOwnsLock();
+
+ nsresult rv;
+
+ LOG(("CacheFile::RemoveOutput() [this=%p, output=%p, status=0x%08" PRIx32 "]",
+ this, aOutput, static_cast<uint32_t>(aStatus)));
+
+ if (mOutput != aOutput) {
+ LOG(
+ ("CacheFile::RemoveOutput() - This output was already removed, ignoring"
+ " call [this=%p]",
+ this));
+ return;
+ }
+
+ mOutput = nullptr;
+
+ // Cancel all queued chunk and update listeners that cannot be satisfied
+ NotifyListenersAboutOutputRemoval();
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+
+ // Make sure the CacheFile status is set to a failure when the output stream
+ // is closed with a fatal error. This way we propagate correctly and w/o any
+ // windows the failure state of this entry to end consumers.
+ if (NS_SUCCEEDED(mStatus) && NS_FAILED(aStatus) &&
+ aStatus != NS_BASE_STREAM_CLOSED) {
+ if (aOutput->IsAlternativeData()) {
+ MOZ_ASSERT(mAltDataOffset != -1);
+ // If there is no alt-data input stream truncate only alt-data, otherwise
+ // doom the entry.
+ bool altDataInputExists = false;
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ if (mInputs[i]->IsAlternativeData()) {
+ altDataInputExists = true;
+ break;
+ }
+ }
+ if (altDataInputExists) {
+ SetError(aStatus);
+ } else {
+ rv = Truncate(mAltDataOffset);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::RemoveOutput() - Truncating alt-data failed "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ SetError(aStatus);
+ } else {
+ SetAltMetadata(nullptr);
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ }
+ }
+ } else {
+ SetError(aStatus);
+ }
+ }
+
+ // Notify close listener as the last action
+ aOutput->NotifyCloseListener();
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_OUTPUT_STREAM_STATUS,
+ StatusToTelemetryEnum(aStatus));
+}
+
+nsresult CacheFile::NotifyChunkListener(CacheFileChunkListener* aCallback,
+ nsIEventTarget* aTarget,
+ nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) {
+ LOG(
+ ("CacheFile::NotifyChunkListener() [this=%p, listener=%p, target=%p, "
+ "rv=0x%08" PRIx32 ", idx=%u, chunk=%p]",
+ this, aCallback, aTarget, static_cast<uint32_t>(aResult), aChunkIdx,
+ aChunk));
+
+ RefPtr<NotifyChunkListenerEvent> ev;
+ ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk);
+ if (aTarget) {
+ return aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+ }
+ return NS_DispatchToCurrentThread(ev);
+}
+
+void CacheFile::QueueChunkListener(uint32_t aIndex,
+ CacheFileChunkListener* aCallback) {
+ LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%u, listener=%p]", this,
+ aIndex, aCallback));
+
+ AssertOwnsLock();
+
+ MOZ_ASSERT(aCallback);
+
+ ChunkListenerItem* item = new ChunkListenerItem();
+ item->mTarget = CacheFileIOManager::IOTarget();
+ if (!item->mTarget) {
+ LOG(
+ ("CacheFile::QueueChunkListener() - Cannot get Cache I/O thread! Using "
+ "main thread for callback."));
+ item->mTarget = GetMainThreadSerialEventTarget();
+ }
+ item->mCallback = aCallback;
+
+ mChunkListeners.GetOrInsertNew(aIndex)->mItems.AppendElement(item);
+}
+
+nsresult CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+ CacheFileChunk* aChunk) {
+ LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%u, rv=0x%08" PRIx32
+ ", "
+ "chunk=%p]",
+ this, aIndex, static_cast<uint32_t>(aResult), aChunk));
+
+ AssertOwnsLock();
+
+ nsresult rv, rv2;
+
+ ChunkListeners* listeners;
+ mChunkListeners.Get(aIndex, &listeners);
+ MOZ_ASSERT(listeners);
+
+ rv = NS_OK;
+ for (uint32_t i = 0; i < listeners->mItems.Length(); i++) {
+ ChunkListenerItem* item = listeners->mItems[i];
+ rv2 = NotifyChunkListener(item->mCallback, item->mTarget, aResult, aIndex,
+ aChunk);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) rv = rv2;
+ delete item;
+ }
+
+ mChunkListeners.Remove(aIndex);
+
+ return rv;
+}
+
+bool CacheFile::HaveChunkListeners(uint32_t aIndex) {
+ AssertOwnsLock();
+ ChunkListeners* listeners;
+ mChunkListeners.Get(aIndex, &listeners);
+ return !!listeners;
+}
+
+void CacheFile::NotifyListenersAboutOutputRemoval() {
+ LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this));
+
+ AssertOwnsLock();
+
+ // First fail all chunk listeners that wait for non-existent chunk
+ for (auto iter = mChunkListeners.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+ auto* listeners = iter.UserData();
+
+ LOG(
+ ("CacheFile::NotifyListenersAboutOutputRemoval() - fail "
+ "[this=%p, idx=%u]",
+ this, idx));
+
+ RefPtr<CacheFileChunk> chunk;
+ mChunks.Get(idx, getter_AddRefs(chunk));
+ if (chunk) {
+ // Skip these listeners because the chunk is being read. We don't have
+ // assertion here to check its state because it might be already in READY
+ // state while CacheFile::OnChunkRead() is waiting on Cache I/O thread for
+ // a lock so the listeners hasn't been notified yet. In any case, the
+ // listeners will be notified from CacheFile::OnChunkRead().
+ continue;
+ }
+
+ for (uint32_t i = 0; i < listeners->mItems.Length(); i++) {
+ ChunkListenerItem* item = listeners->mItems[i];
+ NotifyChunkListener(item->mCallback, item->mTarget,
+ NS_ERROR_NOT_AVAILABLE, idx, nullptr);
+ delete item;
+ }
+
+ iter.Remove();
+ }
+
+ // Fail all update listeners
+ for (const auto& entry : mChunks) {
+ const RefPtr<CacheFileChunk>& chunk = entry.GetData();
+ LOG(
+ ("CacheFile::NotifyListenersAboutOutputRemoval() - fail2 "
+ "[this=%p, idx=%u]",
+ this, entry.GetKey()));
+
+ if (chunk->IsReady()) {
+ chunk->NotifyUpdateListeners();
+ }
+ }
+}
+
+bool CacheFile::DataSize(int64_t* aSize) {
+ CacheFileAutoLock lock(this);
+
+ if (OutputStreamExists(false)) {
+ return false;
+ }
+
+ if (mAltDataOffset == -1) {
+ *aSize = mDataSize;
+ } else {
+ *aSize = mAltDataOffset;
+ }
+
+ return true;
+}
+
+nsresult CacheFile::GetAltDataSize(int64_t* aSize) {
+ CacheFileAutoLock lock(this);
+ if (mOutput) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ if (mAltDataOffset == -1) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aSize = mDataSize - mAltDataOffset;
+ return NS_OK;
+}
+
+nsresult CacheFile::GetAltDataType(nsACString& aType) {
+ CacheFileAutoLock lock(this);
+
+ if (mAltDataOffset == -1) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aType = mAltDataType;
+ return NS_OK;
+}
+
+bool CacheFile::IsDoomed() {
+ CacheFileAutoLock lock(this);
+
+ if (!mHandle) return false;
+
+ return mHandle->IsDoomed();
+}
+
+bool CacheFile::IsWriteInProgress() {
+ CacheFileAutoLock lock(this);
+
+ bool result = false;
+
+ if (!mMemoryOnly) {
+ result =
+ mDataIsDirty || (mMetadata && mMetadata->IsDirty()) || mWritingMetadata;
+ }
+
+ result = result || mOpeningFile || mOutput || mChunks.Count();
+
+ return result;
+}
+
+bool CacheFile::EntryWouldExceedLimit(int64_t aOffset, int64_t aSize,
+ bool aIsAltData) {
+ CacheFileAutoLock lock(this);
+
+ if (mSkipSizeCheck || aSize < 0) {
+ return false;
+ }
+
+ int64_t totalSize = aOffset + aSize;
+ if (aIsAltData) {
+ totalSize += (mAltDataOffset == -1) ? mDataSize : mAltDataOffset;
+ }
+
+ return CacheObserver::EntryIsTooBig(totalSize, !mMemoryOnly);
+}
+
+bool CacheFile::IsDirty() { return mDataIsDirty || mMetadata->IsDirty(); }
+
+void CacheFile::WriteMetadataIfNeeded() {
+ LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
+
+ CacheFileAutoLock lock(this);
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+}
+
+void CacheFile::WriteMetadataIfNeededLocked(bool aFireAndForget) {
+ // When aFireAndForget is set to true, we are called from dtor.
+ // |this| must not be referenced after this method returns!
+
+ LOG(("CacheFile::WriteMetadataIfNeededLocked() [this=%p]", this));
+
+ nsresult rv;
+
+ AssertOwnsLock();
+ MOZ_ASSERT(!mMemoryOnly);
+
+ if (!mMetadata) {
+ MOZ_CRASH("Must have metadata here");
+ return;
+ }
+
+ if (NS_FAILED(mStatus)) return;
+
+ if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() ||
+ mWritingMetadata || mOpeningFile || mKill) {
+ return;
+ }
+
+ if (!aFireAndForget) {
+ // if aFireAndForget is set, we are called from dtor. Write
+ // scheduler hard-refers CacheFile otherwise, so we cannot be here.
+ CacheFileIOManager::UnscheduleMetadataWrite(this);
+ }
+
+ LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing metadata [this=%p]",
+ this));
+
+ rv = mMetadata->WriteMetadata(mDataSize, aFireAndForget ? nullptr : this);
+ if (NS_SUCCEEDED(rv)) {
+ mWritingMetadata = true;
+ mDataIsDirty = false;
+ } else {
+ LOG(
+ ("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously "
+ "failed [this=%p]",
+ this));
+ // TODO: close streams with error
+ SetError(rv);
+ }
+}
+
+void CacheFile::PostWriteTimer() {
+ if (mMemoryOnly) return;
+ LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
+
+ CacheFileIOManager::ScheduleMetadataWrite(this);
+}
+
+void CacheFile::CleanUpCachedChunks() {
+ for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+ const RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ LOG(("CacheFile::CleanUpCachedChunks() [this=%p, idx=%u, chunk=%p]", this,
+ idx, chunk.get()));
+
+ if (MustKeepCachedChunk(idx)) {
+ LOG(("CacheFile::CleanUpCachedChunks() - Keeping chunk"));
+ continue;
+ }
+
+ LOG(("CacheFile::CleanUpCachedChunks() - Removing chunk"));
+ iter.Remove();
+ }
+}
+
+nsresult CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx) {
+ AssertOwnsLock();
+
+ // This method is used to pad last incomplete chunk with zeroes or create
+ // a new chunk full of zeroes
+ MOZ_ASSERT(mDataSize / kChunkSize == aChunkIdx);
+
+ nsresult rv;
+ RefPtr<CacheFileChunk> chunk;
+ rv = GetChunkLocked(aChunkIdx, WRITER, nullptr, getter_AddRefs(chunk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(
+ ("CacheFile::PadChunkWithZeroes() - Zeroing hole in chunk %d, range %d-%d"
+ " [this=%p]",
+ aChunkIdx, chunk->DataSize(), kChunkSize - 1, this));
+
+ CacheFileChunkWriteHandle hnd = chunk->GetWriteHandle(kChunkSize);
+ if (!hnd.Buf()) {
+ ReleaseOutsideLock(std::move(chunk));
+ SetError(NS_ERROR_OUT_OF_MEMORY);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t offset = hnd.DataSize();
+ memset(hnd.Buf() + offset, 0, kChunkSize - offset);
+ hnd.UpdateDataSize(offset, kChunkSize - offset);
+
+ ReleaseOutsideLock(std::move(chunk));
+
+ return NS_OK;
+}
+
+void CacheFile::SetError(nsresult aStatus) {
+ AssertOwnsLock();
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ if (mHandle) {
+ CacheFileIOManager::DoomFile(mHandle, nullptr);
+ }
+ }
+}
+
+nsresult CacheFile::InitIndexEntry() {
+ AssertOwnsLock();
+ MOZ_ASSERT(mHandle);
+
+ if (mHandle->IsDoomed()) return NS_OK;
+
+ nsresult rv;
+
+ rv = CacheFileIOManager::InitIndexEntry(
+ mHandle, GetOriginAttrsHash(mMetadata->OriginAttributes()),
+ mMetadata->IsAnonymous(), mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t frecency = mMetadata->GetFrecency();
+
+ bool hasAltData =
+ mMetadata->GetElement(CacheFileUtils::kAltDataKey) != nullptr;
+
+ static auto toUint16 = [](const char* s) -> uint16_t {
+ if (s) {
+ nsresult rv;
+ uint64_t n64 = nsDependentCString(s).ToInteger64(&rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return n64 <= kIndexTimeOutOfBound ? n64 : kIndexTimeOutOfBound;
+ }
+ return kIndexTimeNotAvailable;
+ };
+
+ const char* onStartTimeStr =
+ mMetadata->GetElement("net-response-time-onstart");
+ uint16_t onStartTime = toUint16(onStartTimeStr);
+
+ const char* onStopTimeStr = mMetadata->GetElement("net-response-time-onstop");
+ uint16_t onStopTime = toUint16(onStopTimeStr);
+
+ const char* contentTypeStr = mMetadata->GetElement("ctid");
+ uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ if (contentTypeStr) {
+ int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv);
+ if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN ||
+ n64 >= nsICacheEntry::CONTENT_TYPE_LAST) {
+ n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ }
+ contentType = n64;
+ }
+
+ rv = CacheFileIOManager::UpdateIndexEntry(
+ mHandle, &frecency, &hasAltData, &onStartTime, &onStopTime, &contentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+size_t CacheFile::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ CacheFileAutoLock lock(const_cast<CacheFile*>(this));
+
+ size_t n = 0;
+ n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mChunks.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& chunk : mChunks.Values()) {
+ n += chunk->SizeOfIncludingThis(mallocSizeOf);
+ }
+ n += mCachedChunks.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& chunk : mCachedChunks.Values()) {
+ n += chunk->SizeOfIncludingThis(mallocSizeOf);
+ }
+ // Ignore metadata if it's still being read. It's not safe to access buffers
+ // in CacheFileMetadata because they might be reallocated on another thread
+ // outside CacheFile's lock.
+ if (mMetadata && mReady) {
+ n += mMetadata->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // Input streams are not elsewhere reported.
+ n += mInputs.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ n += mInputs[i]->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // Output streams are not elsewhere reported.
+ if (mOutput) {
+ n += mOutput->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // The listeners are usually classes reported just above.
+ n += mChunkListeners.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mObjsToRelease.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ // mHandle reported directly from CacheFileIOManager.
+
+ return n;
+}
+
+size_t CacheFile::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheFile.h b/netwerk/cache2/CacheFile.h
new file mode 100644
index 0000000000..97f986c143
--- /dev/null
+++ b/netwerk/cache2/CacheFile.h
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFile__h__
+#define CacheFile__h__
+
+#include "CacheFileChunk.h"
+#include "CacheFileIOManager.h"
+#include "CacheFileMetadata.h"
+#include "nsRefPtrHashtable.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Mutex.h"
+
+class nsIAsyncOutputStream;
+class nsICacheEntry;
+class nsICacheEntryMetaDataVisitor;
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class CacheFileInputStream;
+class CacheFileOutputStream;
+class CacheOutputCloseListener;
+class MetadataWriteTimer;
+
+namespace CacheFileUtils {
+class CacheFileLock;
+};
+
+#define CACHEFILELISTENER_IID \
+ { /* 95e7f284-84ba-48f9-b1fc-3a7336b4c33c */ \
+ 0x95e7f284, 0x84ba, 0x48f9, { \
+ 0xb1, 0xfc, 0x3a, 0x73, 0x36, 0xb4, 0xc3, 0x3c \
+ } \
+ }
+
+class CacheFileListener : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILELISTENER_IID)
+
+ NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew) = 0;
+ NS_IMETHOD OnFileDoomed(nsresult aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileListener, CACHEFILELISTENER_IID)
+
+class MOZ_CAPABILITY("mutex") CacheFile final
+ : public CacheFileChunkListener,
+ public CacheFileIOListener,
+ public CacheFileMetadataListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CacheFile();
+
+ nsresult Init(const nsACString& aKey, bool aCreateNew, bool aMemoryOnly,
+ bool aSkipSizeCheck, bool aPriority, bool aPinned,
+ CacheFileListener* aCallback);
+
+ NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkUpdated(CacheFileChunk* aChunk) override;
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) override;
+ virtual bool IsKilled() override;
+
+ NS_IMETHOD OnMetadataRead(nsresult aResult) override;
+ NS_IMETHOD OnMetadataWritten(nsresult aResult) override;
+
+ NS_IMETHOD OpenInputStream(nsICacheEntry* aCacheEntryHandle,
+ nsIInputStream** _retval);
+ NS_IMETHOD OpenAlternativeInputStream(nsICacheEntry* aCacheEntryHandle,
+ const char* aAltDataType,
+ nsIInputStream** _retval);
+ NS_IMETHOD OpenOutputStream(CacheOutputCloseListener* aCloseListener,
+ nsIOutputStream** _retval);
+ NS_IMETHOD OpenAlternativeOutputStream(
+ CacheOutputCloseListener* aCloseListener, const char* aAltDataType,
+ nsIAsyncOutputStream** _retval);
+ NS_IMETHOD SetMemoryOnly();
+ NS_IMETHOD Doom(CacheFileListener* aCallback);
+
+ void Kill() { mKill = true; }
+ nsresult ThrowMemoryCachedData();
+
+ nsresult GetAltDataSize(int64_t* aSize);
+ nsresult GetAltDataType(nsACString& aType);
+
+ // metadata forwarders
+ nsresult GetElement(const char* aKey, char** _retval);
+ nsresult SetElement(const char* aKey, const char* aValue);
+ nsresult VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor);
+ nsresult ElementsSize(uint32_t* _retval);
+ nsresult SetExpirationTime(uint32_t aExpirationTime);
+ nsresult GetExpirationTime(uint32_t* _retval);
+ nsresult SetFrecency(uint32_t aFrecency);
+ nsresult GetFrecency(uint32_t* _retval);
+ nsresult SetNetworkTimes(uint64_t aOnStartTime, uint64_t aOnStopTime);
+ nsresult SetContentType(uint8_t aContentType);
+ nsresult GetOnStartTime(uint64_t* _retval);
+ nsresult GetOnStopTime(uint64_t* _retval);
+ nsresult GetLastModified(uint32_t* _retval);
+ nsresult GetLastFetched(uint32_t* _retval);
+ nsresult GetFetchCount(uint32_t* _retval);
+ nsresult GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize);
+ // Called by upper layers to indicated the entry has been fetched,
+ // i.e. delivered to the consumer.
+ nsresult OnFetched();
+
+ bool DataSize(int64_t* aSize);
+ void Key(nsACString& aKey);
+ bool IsDoomed();
+ bool IsPinned();
+ // Returns true when there is a potentially unfinished write operation.
+ bool IsWriteInProgress();
+ bool EntryWouldExceedLimit(int64_t aOffset, int64_t aSize, bool aIsAltData);
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ friend class CacheFileIOManager;
+ friend class CacheFileChunk;
+ friend class CacheFileInputStream;
+ friend class CacheFileOutputStream;
+ friend class CacheFileAutoLock;
+ friend class MetadataWriteTimer;
+
+ virtual ~CacheFile();
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE() { mLock->Lock().Lock(); }
+ void Unlock() MOZ_CAPABILITY_RELEASE() {
+ // move the elements out of mObjsToRelease
+ // so that they can be released after we unlock
+ nsTArray<RefPtr<nsISupports>> objs = std::move(mObjsToRelease);
+
+ mLock->Lock().Unlock();
+ }
+ void AssertOwnsLock() const MOZ_ASSERT_CAPABILITY(this) {
+ mLock->Lock().AssertCurrentThreadOwns();
+ }
+ void ReleaseOutsideLock(RefPtr<nsISupports> aObject);
+
+ enum ECallerType { READER = 0, WRITER = 1, PRELOADER = 2 };
+
+ nsresult DoomLocked(CacheFileListener* aCallback);
+
+ nsresult GetChunkLocked(uint32_t aIndex, ECallerType aCaller,
+ CacheFileChunkListener* aCallback,
+ CacheFileChunk** _retval);
+
+ void PreloadChunks(uint32_t aIndex);
+ bool ShouldCacheChunk(uint32_t aIndex);
+ bool MustKeepCachedChunk(uint32_t aIndex);
+
+ nsresult DeactivateChunk(CacheFileChunk* aChunk);
+ void RemoveChunkInternal(CacheFileChunk* aChunk, bool aCacheChunk);
+
+ bool OutputStreamExists(bool aAlternativeData);
+ // Returns number of bytes that are available and can be read by input stream
+ // without waiting for the data. The amount is counted from the start of
+ // aIndex chunk and it is guaranteed that this data won't be released by
+ // CleanUpCachedChunks().
+ int64_t BytesFromChunk(uint32_t aIndex, bool aAlternativeData);
+ nsresult Truncate(int64_t aOffset);
+
+ void RemoveInput(CacheFileInputStream* aInput, nsresult aStatus);
+ void RemoveOutput(CacheFileOutputStream* aOutput, nsresult aStatus);
+ nsresult NotifyChunkListener(CacheFileChunkListener* aCallback,
+ nsIEventTarget* aTarget, nsresult aResult,
+ uint32_t aChunkIdx, CacheFileChunk* aChunk);
+ void QueueChunkListener(uint32_t aIndex, CacheFileChunkListener* aCallback);
+ nsresult NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+ CacheFileChunk* aChunk);
+ bool HaveChunkListeners(uint32_t aIndex);
+ void NotifyListenersAboutOutputRemoval();
+
+ bool IsDirty() MOZ_REQUIRES(this);
+ void WriteMetadataIfNeeded();
+ void WriteMetadataIfNeededLocked(bool aFireAndForget = false)
+ MOZ_REQUIRES(this);
+ void PostWriteTimer() MOZ_REQUIRES(this);
+
+ void CleanUpCachedChunks() MOZ_REQUIRES(this);
+
+ nsresult PadChunkWithZeroes(uint32_t aChunkIdx);
+
+ void SetError(nsresult aStatus);
+ nsresult SetAltMetadata(const char* aAltMetadata);
+
+ nsresult InitIndexEntry();
+
+ bool mOpeningFile MOZ_GUARDED_BY(this){false};
+ bool mReady MOZ_GUARDED_BY(this){false};
+ bool mMemoryOnly MOZ_GUARDED_BY(this){false};
+ bool mSkipSizeCheck MOZ_GUARDED_BY(this){false};
+ bool mOpenAsMemoryOnly MOZ_GUARDED_BY(this){false};
+ bool mPinned MOZ_GUARDED_BY(this){false};
+ bool mPriority MOZ_GUARDED_BY(this){false};
+ bool mDataAccessed MOZ_GUARDED_BY(this){false};
+ bool mDataIsDirty MOZ_GUARDED_BY(this){false};
+ bool mWritingMetadata MOZ_GUARDED_BY(this){false};
+ bool mPreloadWithoutInputStreams MOZ_GUARDED_BY(this){true};
+ uint32_t mPreloadChunkCount MOZ_GUARDED_BY(this){0};
+ nsresult mStatus MOZ_GUARDED_BY(this){NS_OK};
+ // Size of the whole data including eventual alternative data represenation.
+ int64_t mDataSize MOZ_GUARDED_BY(this){-1};
+
+ // If there is alternative data present, it contains size of the original
+ // data, i.e. offset where alternative data starts. Otherwise it is -1.
+ int64_t mAltDataOffset MOZ_GUARDED_BY(this){-1};
+
+ nsCString mKey MOZ_GUARDED_BY(this);
+ nsCString mAltDataType
+ MOZ_GUARDED_BY(this); // The type of the saved alt-data. May be empty.
+
+ RefPtr<CacheFileHandle> mHandle MOZ_GUARDED_BY(this);
+ RefPtr<CacheFileMetadata> mMetadata MOZ_GUARDED_BY(this);
+ nsCOMPtr<CacheFileListener> mListener MOZ_GUARDED_BY(this);
+ nsCOMPtr<CacheFileIOListener> mDoomAfterOpenListener MOZ_GUARDED_BY(this);
+ Atomic<bool, Relaxed> mKill{false};
+
+ nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks
+ MOZ_GUARDED_BY(this);
+ nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners
+ MOZ_GUARDED_BY(this);
+ nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks
+ MOZ_GUARDED_BY(this);
+ // We can truncate data only if there is no input/output stream beyond the
+ // truncate position, so only unused chunks can be thrown away. But it can
+ // happen that we need to throw away a chunk that is still in mChunks (i.e.
+ // an active chunk) because deactivation happens with a small delay. We cannot
+ // delete such chunk immediately but we need to ensure that such chunk won't
+ // be returned by GetChunkLocked, so we move this chunk into mDiscardedChunks
+ // and mark it as discarded.
+ nsTArray<RefPtr<CacheFileChunk>> mDiscardedChunks MOZ_GUARDED_BY(this);
+
+ nsTArray<CacheFileInputStream*> mInputs MOZ_GUARDED_BY(this);
+ CacheFileOutputStream* mOutput MOZ_GUARDED_BY(this){nullptr};
+
+ nsTArray<RefPtr<nsISupports>> mObjsToRelease MOZ_GUARDED_BY(this);
+ RefPtr<CacheFileUtils::CacheFileLock> mLock;
+};
+
+class MOZ_RAII MOZ_SCOPED_CAPABILITY CacheFileAutoLock {
+ public:
+ explicit CacheFileAutoLock(CacheFile* aFile) MOZ_CAPABILITY_ACQUIRE(aFile)
+ : mFile(aFile), mLocked(true) {
+ mFile->Lock();
+ }
+ ~CacheFileAutoLock() MOZ_CAPABILITY_RELEASE() {
+ if (mLocked) {
+ mFile->Unlock();
+ }
+ }
+ void Lock() MOZ_CAPABILITY_ACQUIRE() {
+ MOZ_ASSERT(!mLocked);
+ mFile->Lock();
+ mLocked = true;
+ }
+ void Unlock() MOZ_CAPABILITY_RELEASE() {
+ MOZ_ASSERT(mLocked);
+ mFile->Unlock();
+ mLocked = false;
+ }
+
+ private:
+ RefPtr<CacheFile> mFile;
+ bool mLocked;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileChunk.cpp b/netwerk/cache2/CacheFileChunk.cpp
new file mode 100644
index 0000000000..7dc0d9078c
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -0,0 +1,840 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheFileChunk.h"
+
+#include "CacheFile.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+
+namespace mozilla::net {
+
+#define kMinBufSize 512
+
+CacheFileChunkBuffer::CacheFileChunkBuffer(CacheFileChunk* aChunk)
+ : mChunk(aChunk),
+ mBuf(nullptr),
+ mBufSize(0),
+ mDataSize(0),
+ mReadHandlesCount(0),
+ mWriteHandleExists(false) {}
+
+CacheFileChunkBuffer::~CacheFileChunkBuffer() {
+ if (mBuf) {
+ CacheFileUtils::FreeBuffer(mBuf);
+ mBuf = nullptr;
+ mChunk->BuffersAllocationChanged(mBufSize, 0);
+ mBufSize = 0;
+ }
+}
+
+void CacheFileChunkBuffer::CopyFrom(CacheFileChunkBuffer* aOther) {
+ MOZ_RELEASE_ASSERT(mBufSize >= aOther->mDataSize);
+ mDataSize = aOther->mDataSize;
+ memcpy(mBuf, aOther->mBuf, mDataSize);
+}
+
+nsresult CacheFileChunkBuffer::FillInvalidRanges(
+ CacheFileChunkBuffer* aOther, CacheFileUtils::ValidityMap* aMap) {
+ nsresult rv;
+
+ rv = EnsureBufSize(aOther->mDataSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t invalidOffset = 0;
+ uint32_t invalidLength;
+
+ for (uint32_t i = 0; i < aMap->Length(); ++i) {
+ uint32_t validOffset = (*aMap)[i].Offset();
+ uint32_t validLength = (*aMap)[i].Len();
+
+ MOZ_RELEASE_ASSERT(invalidOffset <= validOffset);
+ invalidLength = validOffset - invalidOffset;
+ if (invalidLength > 0) {
+ MOZ_RELEASE_ASSERT(invalidOffset + invalidLength <= aOther->mDataSize);
+ memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength);
+ }
+ invalidOffset = validOffset + validLength;
+ }
+
+ if (invalidOffset < aOther->mDataSize) {
+ invalidLength = aOther->mDataSize - invalidOffset;
+ memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength);
+ }
+
+ return NS_OK;
+}
+
+[[nodiscard]] nsresult CacheFileChunkBuffer::EnsureBufSize(uint32_t aBufSize) {
+ AssertOwnsLock();
+
+ if (mBufSize >= aBufSize) {
+ return NS_OK;
+ }
+
+ // find smallest power of 2 greater than or equal to aBufSize
+ aBufSize--;
+ aBufSize |= aBufSize >> 1;
+ aBufSize |= aBufSize >> 2;
+ aBufSize |= aBufSize >> 4;
+ aBufSize |= aBufSize >> 8;
+ aBufSize |= aBufSize >> 16;
+ aBufSize++;
+
+ const uint32_t minBufSize = kMinBufSize;
+ const uint32_t maxBufSize = kChunkSize;
+ aBufSize = clamped(aBufSize, minBufSize, maxBufSize);
+
+ if (!mChunk->CanAllocate(aBufSize - mBufSize)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char* newBuf = static_cast<char*>(realloc(mBuf, aBufSize));
+ if (!newBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mChunk->BuffersAllocationChanged(mBufSize, aBufSize);
+ mBuf = newBuf;
+ mBufSize = aBufSize;
+
+ return NS_OK;
+}
+
+void CacheFileChunkBuffer::SetDataSize(uint32_t aDataSize) {
+ MOZ_RELEASE_ASSERT(
+ // EnsureBufSize must be called before SetDataSize, so the new data size
+ // is guaranteed to be smaller than or equal to mBufSize.
+ aDataSize <= mBufSize ||
+ // The only exception is an optimization when we read the data from the
+ // disk. The data is read to a separate buffer and CacheFileChunk::mBuf is
+ // empty (see CacheFileChunk::Read). We need to set mBuf::mDataSize
+ // accordingly so that DataSize() methods return correct value, but we
+ // don't want to allocate the buffer since it wouldn't be used in most
+ // cases.
+ (mBufSize == 0 && mChunk->mState == CacheFileChunk::READING));
+
+ mDataSize = aDataSize;
+}
+
+void CacheFileChunkBuffer::AssertOwnsLock() const { mChunk->AssertOwnsLock(); }
+
+void CacheFileChunkBuffer::RemoveReadHandle() {
+ AssertOwnsLock();
+ MOZ_RELEASE_ASSERT(mReadHandlesCount);
+ MOZ_RELEASE_ASSERT(!mWriteHandleExists);
+ mReadHandlesCount--;
+
+ if (mReadHandlesCount == 0 && mChunk->mBuf != this) {
+ DebugOnly<bool> removed = mChunk->mOldBufs.RemoveElement(this);
+ MOZ_ASSERT(removed);
+ }
+}
+
+void CacheFileChunkBuffer::RemoveWriteHandle() {
+ AssertOwnsLock();
+ MOZ_RELEASE_ASSERT(mReadHandlesCount == 0);
+ MOZ_RELEASE_ASSERT(mWriteHandleExists);
+ mWriteHandleExists = false;
+}
+
+size_t CacheFileChunkBuffer::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+
+ if (mBuf) {
+ n += mallocSizeOf(mBuf);
+ }
+
+ return n;
+}
+
+uint32_t CacheFileChunkHandle::DataSize() {
+ MOZ_ASSERT(mBuf, "Unexpected call on dummy handle");
+ mBuf->AssertOwnsLock();
+ return mBuf->mDataSize;
+}
+
+uint32_t CacheFileChunkHandle::Offset() {
+ MOZ_ASSERT(mBuf, "Unexpected call on dummy handle");
+ mBuf->AssertOwnsLock();
+ return mBuf->mChunk->Index() * kChunkSize;
+}
+
+CacheFileChunkReadHandle::CacheFileChunkReadHandle(CacheFileChunkBuffer* aBuf) {
+ mBuf = aBuf;
+ mBuf->mReadHandlesCount++;
+}
+
+CacheFileChunkReadHandle::~CacheFileChunkReadHandle() {
+ mBuf->RemoveReadHandle();
+}
+
+const char* CacheFileChunkReadHandle::Buf() { return mBuf->mBuf; }
+
+CacheFileChunkWriteHandle::CacheFileChunkWriteHandle(
+ CacheFileChunkBuffer* aBuf) {
+ mBuf = aBuf;
+ if (mBuf) {
+ MOZ_ASSERT(!mBuf->mWriteHandleExists);
+ mBuf->mWriteHandleExists = true;
+ }
+}
+
+CacheFileChunkWriteHandle::~CacheFileChunkWriteHandle() {
+ if (mBuf) {
+ mBuf->RemoveWriteHandle();
+ }
+}
+
+char* CacheFileChunkWriteHandle::Buf() { return mBuf ? mBuf->mBuf : nullptr; }
+
+void CacheFileChunkWriteHandle::UpdateDataSize(uint32_t aOffset,
+ uint32_t aLen) {
+ MOZ_ASSERT(mBuf, "Write performed on dummy handle?");
+ MOZ_ASSERT(aOffset <= mBuf->mDataSize);
+ MOZ_ASSERT(aOffset + aLen <= mBuf->mBufSize);
+
+ if (aOffset + aLen > mBuf->mDataSize) {
+ mBuf->mDataSize = aOffset + aLen;
+ }
+
+ mBuf->mChunk->UpdateDataSize(aOffset, aLen);
+}
+
+class NotifyUpdateListenerEvent : public Runnable {
+ public:
+ NotifyUpdateListenerEvent(CacheFileChunkListener* aCallback,
+ CacheFileChunk* aChunk)
+ : Runnable("net::NotifyUpdateListenerEvent"),
+ mCallback(aCallback),
+ mChunk(aChunk) {
+ LOG(("NotifyUpdateListenerEvent::NotifyUpdateListenerEvent() [this=%p]",
+ this));
+ }
+
+ protected:
+ ~NotifyUpdateListenerEvent() {
+ LOG(("NotifyUpdateListenerEvent::~NotifyUpdateListenerEvent() [this=%p]",
+ this));
+ }
+
+ public:
+ NS_IMETHOD Run() override {
+ LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this));
+
+ mCallback->OnChunkUpdated(mChunk);
+ return NS_OK;
+ }
+
+ protected:
+ nsCOMPtr<CacheFileChunkListener> mCallback;
+ RefPtr<CacheFileChunk> mChunk;
+};
+
+bool CacheFileChunk::DispatchRelease() {
+ if (NS_IsMainThread()) {
+ return false;
+ }
+
+ NS_DispatchToMainThread(NewNonOwningRunnableMethod(
+ "net::CacheFileChunk::Release", this, &CacheFileChunk::Release));
+
+ return true;
+}
+
+NS_IMPL_ADDREF(CacheFileChunk)
+NS_IMETHODIMP_(MozExternalRefCountType)
+CacheFileChunk::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the main thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "CacheFileChunk");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ // We can safely access this chunk after decreasing mRefCnt since we re-post
+ // all calls to Release() happening off the main thread to the main thread.
+ // I.e. no other Release() that would delete the object could be run before
+ // we call CacheFile::DeactivateChunk().
+ //
+ // NOTE: we don't grab the CacheFile's lock, so the chunk might be addrefed
+ // on another thread before CacheFile::DeactivateChunk() grabs the lock on
+ // this thread. To make sure we won't deactivate chunk that was just returned
+ // to a new consumer we check mRefCnt once again in
+ // CacheFile::DeactivateChunk() after we grab the lock.
+ if (mActiveChunk && count == 1) {
+ mFile->DeactivateChunk(this);
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileChunk)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+CacheFileChunk::CacheFileChunk(CacheFile* aFile, uint32_t aIndex,
+ bool aInitByWriter)
+ : CacheMemoryConsumer(aFile->mOpenAsMemoryOnly ? MEMORY_ONLY : DONT_REPORT),
+ mIndex(aIndex),
+ mState(INITIAL),
+ mStatus(NS_OK),
+ mActiveChunk(false),
+ mIsDirty(false),
+ mDiscardedChunk(false),
+ mBuffersSize(0),
+ mLimitAllocation(!aFile->mOpenAsMemoryOnly && aInitByWriter),
+ mIsPriority(aFile->mPriority),
+ mExpectedHash(0),
+ mFile(aFile) {
+ LOG(("CacheFileChunk::CacheFileChunk() [this=%p, index=%u, initByWriter=%d]",
+ this, aIndex, aInitByWriter));
+ mBuf = new CacheFileChunkBuffer(this);
+}
+
+CacheFileChunk::~CacheFileChunk() {
+ LOG(("CacheFileChunk::~CacheFileChunk() [this=%p]", this));
+}
+
+void CacheFileChunk::AssertOwnsLock() const { mFile->AssertOwnsLock(); }
+
+void CacheFileChunk::InitNew() {
+ AssertOwnsLock();
+
+ LOG(("CacheFileChunk::InitNew() [this=%p]", this));
+
+ MOZ_ASSERT(mState == INITIAL);
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus));
+ MOZ_ASSERT(!mBuf->Buf());
+ MOZ_ASSERT(!mWritingStateHandle);
+ MOZ_ASSERT(!mReadingStateBuf);
+ MOZ_ASSERT(!mIsDirty);
+
+ mBuf = new CacheFileChunkBuffer(this);
+ mState = READY;
+}
+
+nsresult CacheFileChunk::Read(CacheFileHandle* aHandle, uint32_t aLen,
+ CacheHash::Hash16_t aHash,
+ CacheFileChunkListener* aCallback) {
+ AssertOwnsLock();
+
+ LOG(("CacheFileChunk::Read() [this=%p, handle=%p, len=%d, listener=%p]", this,
+ aHandle, aLen, aCallback));
+
+ MOZ_ASSERT(mState == INITIAL);
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus));
+ MOZ_ASSERT(!mBuf->Buf());
+ MOZ_ASSERT(!mWritingStateHandle);
+ MOZ_ASSERT(!mReadingStateBuf);
+ MOZ_ASSERT(aLen);
+
+ nsresult rv;
+
+ mState = READING;
+
+ RefPtr<CacheFileChunkBuffer> tmpBuf = new CacheFileChunkBuffer(this);
+ rv = tmpBuf->EnsureBufSize(aLen);
+ if (NS_FAILED(rv)) {
+ SetError(rv);
+ return mStatus;
+ }
+ tmpBuf->SetDataSize(aLen);
+
+ rv = CacheFileIOManager::Read(aHandle, mIndex * kChunkSize, tmpBuf->Buf(),
+ aLen, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND;
+ SetError(rv);
+ } else {
+ mReadingStateBuf.swap(tmpBuf);
+ mListener = aCallback;
+ // mBuf contains no data but we set datasize to size of the data that will
+ // be read from the disk. No handle is allowed to access the non-existent
+ // data until reading finishes, but data can be appended or overwritten.
+ // These pieces are tracked in mValidityMap and will be merged with the data
+ // read from disk in OnDataRead().
+ mBuf->SetDataSize(aLen);
+ mExpectedHash = aHash;
+ }
+
+ return rv;
+}
+
+nsresult CacheFileChunk::Write(CacheFileHandle* aHandle,
+ CacheFileChunkListener* aCallback) {
+ AssertOwnsLock();
+
+ LOG(("CacheFileChunk::Write() [this=%p, handle=%p, listener=%p]", this,
+ aHandle, aCallback));
+
+ MOZ_ASSERT(mState == READY);
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus));
+ MOZ_ASSERT(!mWritingStateHandle);
+ MOZ_ASSERT(mBuf->DataSize()); // Don't write chunk when it is empty
+ MOZ_ASSERT(mBuf->ReadHandlesCount() == 0);
+ MOZ_ASSERT(!mBuf->WriteHandleExists());
+
+ nsresult rv;
+
+ mState = WRITING;
+ mWritingStateHandle = MakeUnique<CacheFileChunkReadHandle>(mBuf);
+
+ rv = CacheFileIOManager::Write(
+ aHandle, mIndex * kChunkSize, mWritingStateHandle->Buf(),
+ mWritingStateHandle->DataSize(), false, false, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mWritingStateHandle = nullptr;
+ SetError(rv);
+ } else {
+ mListener = aCallback;
+ mIsDirty = false;
+ }
+
+ return rv;
+}
+
+void CacheFileChunk::WaitForUpdate(CacheFileChunkListener* aCallback) {
+ AssertOwnsLock();
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ LOG(("CacheFileChunk::WaitForUpdate() [this=%p, listener=%p]", this,
+ aCallback));
+
+ MOZ_ASSERT(mFile->mOutput);
+ MOZ_ASSERT(IsReady());
+
+#ifdef DEBUG
+ for (uint32_t i = 0; i < mUpdateListeners.Length(); i++) {
+ MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
+ }
+#endif
+
+ ChunkListenerItem* item = new ChunkListenerItem();
+ item->mTarget = CacheFileIOManager::IOTarget();
+ if (!item->mTarget) {
+ LOG(
+ ("CacheFileChunk::WaitForUpdate() - Cannot get Cache I/O thread! Using "
+ "main thread for callback."));
+ item->mTarget = GetMainThreadSerialEventTarget();
+ }
+ item->mCallback = aCallback;
+ MOZ_ASSERT(item->mTarget);
+ item->mCallback = aCallback;
+
+ mUpdateListeners.AppendElement(item);
+}
+
+void CacheFileChunk::CancelWait(CacheFileChunkListener* aCallback) {
+ AssertOwnsLock();
+
+ LOG(("CacheFileChunk::CancelWait() [this=%p, listener=%p]", this, aCallback));
+
+ MOZ_ASSERT(IsReady());
+
+ uint32_t i;
+ for (i = 0; i < mUpdateListeners.Length(); i++) {
+ ChunkListenerItem* item = mUpdateListeners[i];
+
+ if (item->mCallback == aCallback) {
+ mUpdateListeners.RemoveElementAt(i);
+ delete item;
+ break;
+ }
+ }
+
+#ifdef DEBUG
+ for (; i < mUpdateListeners.Length(); i++) {
+ MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
+ }
+#endif
+}
+
+nsresult CacheFileChunk::NotifyUpdateListeners() {
+ AssertOwnsLock();
+
+ LOG(("CacheFileChunk::NotifyUpdateListeners() [this=%p]", this));
+
+ MOZ_ASSERT(IsReady());
+
+ nsresult rv, rv2;
+
+ rv = NS_OK;
+ for (uint32_t i = 0; i < mUpdateListeners.Length(); i++) {
+ ChunkListenerItem* item = mUpdateListeners[i];
+
+ LOG(
+ ("CacheFileChunk::NotifyUpdateListeners() - Notifying listener %p "
+ "[this=%p]",
+ item->mCallback.get(), this));
+
+ RefPtr<NotifyUpdateListenerEvent> ev;
+ ev = new NotifyUpdateListenerEvent(item->mCallback, this);
+ rv2 = item->mTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) rv = rv2;
+ delete item;
+ }
+
+ mUpdateListeners.Clear();
+
+ return rv;
+}
+
+uint32_t CacheFileChunk::Index() const { return mIndex; }
+
+CacheHash::Hash16_t CacheFileChunk::Hash() const {
+ MOZ_ASSERT(IsReady());
+
+ return CacheHash::Hash16(mBuf->Buf(), mBuf->DataSize());
+}
+
+uint32_t CacheFileChunk::DataSize() const { return mBuf->DataSize(); }
+
+void CacheFileChunk::UpdateDataSize(uint32_t aOffset, uint32_t aLen) {
+ AssertOwnsLock();
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ // UpdateDataSize() is called only when we've written some data to the chunk
+ // and we never write data anymore once some error occurs.
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus));
+
+ LOG(("CacheFileChunk::UpdateDataSize() [this=%p, offset=%d, len=%d]", this,
+ aOffset, aLen));
+
+ mIsDirty = true;
+
+ int64_t fileSize = static_cast<int64_t>(kChunkSize) * mIndex + aOffset + aLen;
+ bool notify = false;
+
+ if (fileSize > mFile->mDataSize) {
+ mFile->mDataSize = fileSize;
+ notify = true;
+ }
+
+ if (mState == READY || mState == WRITING) {
+ MOZ_ASSERT(mValidityMap.Length() == 0);
+
+ if (notify) {
+ NotifyUpdateListeners();
+ }
+
+ return;
+ }
+
+ // We're still waiting for data from the disk. This chunk cannot be used by
+ // input stream, so there must be no update listener. We also need to keep
+ // track of where the data is written so that we can correctly merge the new
+ // data with the old one.
+
+ MOZ_ASSERT(mUpdateListeners.Length() == 0);
+ MOZ_ASSERT(mState == READING);
+
+ mValidityMap.AddPair(aOffset, aLen);
+ mValidityMap.Log();
+}
+
+void CacheFileChunk::Truncate(uint32_t aOffset) {
+ MOZ_RELEASE_ASSERT(mState == READY || mState == WRITING || mState == READING);
+
+ if (mState == READING) {
+ mIsDirty = true;
+ }
+
+ mBuf->SetDataSize(aOffset);
+}
+
+nsresult CacheFileChunk::OnFileOpened(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFileChunk::OnFileOpened should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileChunk::OnDataWritten(CacheFileHandle* aHandle,
+ const char* aBuf, nsresult aResult) {
+ LOG((
+ "CacheFileChunk::OnDataWritten() [this=%p, handle=%p, result=0x%08" PRIx32
+ "]",
+ this, aHandle, static_cast<uint32_t>(aResult)));
+
+ nsCOMPtr<CacheFileChunkListener> listener;
+
+ {
+ CacheFileAutoLock lock(mFile);
+
+ MOZ_ASSERT(mState == WRITING);
+ MOZ_ASSERT(mListener);
+
+ mWritingStateHandle = nullptr;
+
+ if (NS_WARN_IF(NS_FAILED(aResult))) {
+ SetError(aResult);
+ }
+
+ mState = READY;
+ mListener.swap(listener);
+ }
+
+ listener->OnChunkWritten(aResult, this);
+
+ return NS_OK;
+}
+
+nsresult CacheFileChunk::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) {
+ LOG(("CacheFileChunk::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32
+ "]",
+ this, aHandle, static_cast<uint32_t>(aResult)));
+
+ nsCOMPtr<CacheFileChunkListener> listener;
+
+ {
+ CacheFileAutoLock lock(mFile);
+
+ MOZ_ASSERT(mState == READING);
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mReadingStateBuf);
+ MOZ_RELEASE_ASSERT(mBuf->ReadHandlesCount() == 0);
+ MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists());
+
+ RefPtr<CacheFileChunkBuffer> tmpBuf;
+ tmpBuf.swap(mReadingStateBuf);
+
+ if (NS_SUCCEEDED(aResult)) {
+ CacheHash::Hash16_t hash =
+ CacheHash::Hash16(tmpBuf->Buf(), tmpBuf->DataSize());
+ if (hash != mExpectedHash) {
+ LOG(
+ ("CacheFileChunk::OnDataRead() - Hash mismatch! Hash of the data is"
+ " %hx, hash in metadata is %hx. [this=%p, idx=%d]",
+ hash, mExpectedHash, this, mIndex));
+ aResult = NS_ERROR_FILE_CORRUPTED;
+ } else {
+ if (mBuf->DataSize() < tmpBuf->DataSize()) {
+ // Truncate() was called while the data was being read.
+ tmpBuf->SetDataSize(mBuf->DataSize());
+ }
+
+ if (!mBuf->Buf()) {
+ // Just swap the buffers if mBuf is still empty
+ mBuf.swap(tmpBuf);
+ } else {
+ LOG(("CacheFileChunk::OnDataRead() - Merging buffers. [this=%p]",
+ this));
+
+ mValidityMap.Log();
+ aResult = mBuf->FillInvalidRanges(tmpBuf, &mValidityMap);
+ mValidityMap.Clear();
+ }
+ }
+ }
+
+ if (NS_FAILED(aResult)) {
+ aResult = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND;
+ SetError(aResult);
+ mBuf->SetDataSize(0);
+ }
+
+ mState = READY;
+ mListener.swap(listener);
+ }
+
+ listener->OnChunkRead(aResult, this);
+
+ return NS_OK;
+}
+
+nsresult CacheFileChunk::OnFileDoomed(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFileChunk::OnFileDoomed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileChunk::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheFileChunk::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileChunk::OnFileRenamed(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFileChunk::OnFileRenamed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+bool CacheFileChunk::IsKilled() { return mFile->IsKilled(); }
+
+bool CacheFileChunk::IsReady() const {
+ return (NS_SUCCEEDED(mStatus) && (mState == READY || mState == WRITING));
+}
+
+bool CacheFileChunk::IsDirty() const {
+ AssertOwnsLock();
+
+ return mIsDirty;
+}
+
+nsresult CacheFileChunk::GetStatus() { return mStatus; }
+
+void CacheFileChunk::SetError(nsresult aStatus) {
+ LOG(("CacheFileChunk::SetError() [this=%p, status=0x%08" PRIx32 "]", this,
+ static_cast<uint32_t>(aStatus)));
+
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ if (NS_FAILED(mStatus)) {
+ // Remember only the first error code.
+ return;
+ }
+
+ mStatus = aStatus;
+}
+
+CacheFileChunkReadHandle CacheFileChunk::GetReadHandle() {
+ LOG(("CacheFileChunk::GetReadHandle() [this=%p]", this));
+
+ AssertOwnsLock();
+
+ MOZ_RELEASE_ASSERT(mState == READY || mState == WRITING);
+ // We don't release the lock when writing the data and CacheFileOutputStream
+ // doesn't get the read handle, so there cannot be a write handle when read
+ // handle is obtained.
+ MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists());
+
+ return CacheFileChunkReadHandle(mBuf);
+}
+
+CacheFileChunkWriteHandle CacheFileChunk::GetWriteHandle(
+ uint32_t aEnsuredBufSize) {
+ LOG(("CacheFileChunk::GetWriteHandle() [this=%p, ensuredBufSize=%u]", this,
+ aEnsuredBufSize));
+
+ AssertOwnsLock();
+
+ if (NS_FAILED(mStatus)) {
+ return CacheFileChunkWriteHandle(nullptr); // dummy handle
+ }
+
+ nsresult rv;
+
+ // We don't support multiple write handles
+ MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists());
+
+ if (mBuf->ReadHandlesCount()) {
+ LOG(
+ ("CacheFileChunk::GetWriteHandle() - cloning buffer because of existing"
+ " read handle"));
+
+ MOZ_RELEASE_ASSERT(mState != READING);
+ RefPtr<CacheFileChunkBuffer> newBuf = new CacheFileChunkBuffer(this);
+ rv = newBuf->EnsureBufSize(std::max(aEnsuredBufSize, mBuf->DataSize()));
+ if (NS_SUCCEEDED(rv)) {
+ newBuf->CopyFrom(mBuf);
+ mOldBufs.AppendElement(mBuf);
+ mBuf = newBuf;
+ }
+ } else {
+ rv = mBuf->EnsureBufSize(aEnsuredBufSize);
+ }
+
+ if (NS_FAILED(rv)) {
+ SetError(NS_ERROR_OUT_OF_MEMORY);
+ return CacheFileChunkWriteHandle(nullptr); // dummy handle
+ }
+
+ return CacheFileChunkWriteHandle(mBuf);
+}
+
+// Memory reporting
+
+size_t CacheFileChunk::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = mBuf->SizeOfIncludingThis(mallocSizeOf);
+
+ if (mReadingStateBuf) {
+ n += mReadingStateBuf->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ for (uint32_t i = 0; i < mOldBufs.Length(); ++i) {
+ n += mOldBufs[i]->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ n += mValidityMap.SizeOfExcludingThis(mallocSizeOf);
+
+ return n;
+}
+
+size_t CacheFileChunk::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+bool CacheFileChunk::CanAllocate(uint32_t aSize) const {
+ if (!mLimitAllocation) {
+ return true;
+ }
+
+ LOG(("CacheFileChunk::CanAllocate() [this=%p, size=%u]", this, aSize));
+
+ int64_t limit = CacheObserver::MaxDiskChunksMemoryUsage(mIsPriority);
+ if (limit == 0) {
+ return true;
+ }
+
+ limit <<= 10;
+ if (limit > UINT32_MAX) {
+ limit = UINT32_MAX;
+ }
+
+ int64_t usage = ChunksMemoryUsage();
+ if (usage + aSize > limit) {
+ LOG(("CacheFileChunk::CanAllocate() - Returning false. [this=%p]", this));
+ return false;
+ }
+
+ return true;
+}
+
+void CacheFileChunk::BuffersAllocationChanged(uint32_t aFreed,
+ uint32_t aAllocated) {
+ uint32_t oldBuffersSize = mBuffersSize;
+ mBuffersSize += aAllocated;
+ mBuffersSize -= aFreed;
+
+ DoMemoryReport(sizeof(CacheFileChunk) + mBuffersSize);
+
+ if (!mLimitAllocation) {
+ return;
+ }
+
+ ChunksMemoryUsage() -= oldBuffersSize;
+ ChunksMemoryUsage() += mBuffersSize;
+ LOG(
+ ("CacheFileChunk::BuffersAllocationChanged() - %s chunks usage %u "
+ "[this=%p]",
+ mIsPriority ? "Priority" : "Normal",
+ static_cast<uint32_t>(ChunksMemoryUsage()), this));
+}
+
+mozilla::Atomic<uint32_t, ReleaseAcquire>& CacheFileChunk::ChunksMemoryUsage()
+ const {
+ static mozilla::Atomic<uint32_t, ReleaseAcquire> chunksMemoryUsage(0);
+ static mozilla::Atomic<uint32_t, ReleaseAcquire> prioChunksMemoryUsage(0);
+ return mIsPriority ? prioChunksMemoryUsage : chunksMemoryUsage;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheFileChunk.h b/netwerk/cache2/CacheFileChunk.h
new file mode 100644
index 0000000000..4d20bf19eb
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -0,0 +1,238 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileChunk__h__
+#define CacheFileChunk__h__
+
+#include "CacheFileIOManager.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "CacheFileUtils.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace net {
+
+constexpr int32_t kChunkSize = 256 * 1024;
+constexpr size_t kEmptyChunkHash = 0x1826;
+
+class CacheFileChunk;
+class CacheFile;
+
+class CacheFileChunkBuffer {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileChunkBuffer)
+
+ explicit CacheFileChunkBuffer(CacheFileChunk* aChunk);
+
+ nsresult EnsureBufSize(uint32_t aBufSize);
+ void CopyFrom(CacheFileChunkBuffer* aOther);
+ nsresult FillInvalidRanges(CacheFileChunkBuffer* aOther,
+ CacheFileUtils::ValidityMap* aMap);
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ char* Buf() const { return mBuf; }
+ void SetDataSize(uint32_t aDataSize);
+ uint32_t DataSize() const { return mDataSize; }
+ uint32_t ReadHandlesCount() const { return mReadHandlesCount; }
+ bool WriteHandleExists() const { return mWriteHandleExists; }
+
+ private:
+ friend class CacheFileChunkHandle;
+ friend class CacheFileChunkReadHandle;
+ friend class CacheFileChunkWriteHandle;
+
+ ~CacheFileChunkBuffer();
+
+ void AssertOwnsLock() const;
+
+ void RemoveReadHandle();
+ void RemoveWriteHandle();
+
+ // We keep a weak reference to the chunk to not create a reference cycle. The
+ // buffer is referenced only by chunk and handles. Handles are always
+ // destroyed before the chunk so it is guaranteed that mChunk is a valid
+ // pointer for the whole buffer's lifetime.
+ CacheFileChunk* mChunk;
+ char* mBuf;
+ uint32_t mBufSize;
+ uint32_t mDataSize;
+ uint32_t mReadHandlesCount;
+ bool mWriteHandleExists;
+};
+
+class CacheFileChunkHandle {
+ public:
+ uint32_t DataSize();
+ uint32_t Offset();
+
+ protected:
+ RefPtr<CacheFileChunkBuffer> mBuf;
+};
+
+class CacheFileChunkReadHandle : public CacheFileChunkHandle {
+ public:
+ explicit CacheFileChunkReadHandle(CacheFileChunkBuffer* aBuf);
+ ~CacheFileChunkReadHandle();
+
+ const char* Buf();
+};
+
+class CacheFileChunkWriteHandle : public CacheFileChunkHandle {
+ public:
+ explicit CacheFileChunkWriteHandle(CacheFileChunkBuffer* aBuf);
+ ~CacheFileChunkWriteHandle();
+
+ char* Buf();
+ void UpdateDataSize(uint32_t aOffset, uint32_t aLen);
+};
+
+#define CACHEFILECHUNKLISTENER_IID \
+ { /* baf16149-2ab5-499c-a9c2-5904eb95c288 */ \
+ 0xbaf16149, 0x2ab5, 0x499c, { \
+ 0xa9, 0xc2, 0x59, 0x04, 0xeb, 0x95, 0xc2, 0x88 \
+ } \
+ }
+
+class CacheFileChunkListener : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILECHUNKLISTENER_IID)
+
+ NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk* aChunk) = 0;
+ NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk* aChunk) = 0;
+ NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) = 0;
+ NS_IMETHOD OnChunkUpdated(CacheFileChunk* aChunk) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileChunkListener,
+ CACHEFILECHUNKLISTENER_IID)
+
+class ChunkListenerItem {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(ChunkListenerItem)
+ MOZ_COUNTED_DTOR(ChunkListenerItem)
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+ nsCOMPtr<CacheFileChunkListener> mCallback;
+};
+
+class ChunkListeners {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(ChunkListeners)
+ MOZ_COUNTED_DTOR(ChunkListeners)
+
+ nsTArray<ChunkListenerItem*> mItems;
+};
+
+class CacheFileChunk final : public CacheFileIOListener,
+ public CacheMemoryConsumer {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ bool DispatchRelease();
+
+ CacheFileChunk(CacheFile* aFile, uint32_t aIndex, bool aInitByWriter);
+
+ void InitNew();
+ nsresult Read(CacheFileHandle* aHandle, uint32_t aLen,
+ CacheHash::Hash16_t aHash, CacheFileChunkListener* aCallback);
+ nsresult Write(CacheFileHandle* aHandle, CacheFileChunkListener* aCallback);
+ void WaitForUpdate(CacheFileChunkListener* aCallback);
+ void CancelWait(CacheFileChunkListener* aCallback);
+ nsresult NotifyUpdateListeners();
+
+ uint32_t Index() const;
+ CacheHash::Hash16_t Hash() const;
+ uint32_t DataSize() const;
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) override;
+ virtual bool IsKilled() override;
+
+ bool IsReady() const;
+ bool IsDirty() const;
+
+ nsresult GetStatus();
+ void SetError(nsresult aStatus);
+
+ CacheFileChunkReadHandle GetReadHandle();
+ CacheFileChunkWriteHandle GetWriteHandle(uint32_t aEnsuredBufSize);
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ friend class CacheFileChunkBuffer;
+ friend class CacheFileChunkWriteHandle;
+ friend class CacheFileInputStream;
+ friend class CacheFileOutputStream;
+ friend class CacheFile;
+
+ virtual ~CacheFileChunk();
+
+ void AssertOwnsLock() const;
+
+ void UpdateDataSize(uint32_t aOffset, uint32_t aLen);
+ void Truncate(uint32_t aOffset);
+
+ bool CanAllocate(uint32_t aSize) const;
+ void BuffersAllocationChanged(uint32_t aFreed, uint32_t aAllocated);
+
+ mozilla::Atomic<uint32_t, ReleaseAcquire>& ChunksMemoryUsage() const;
+
+ enum EState { INITIAL = 0, READING = 1, WRITING = 2, READY = 3 };
+
+ uint32_t mIndex;
+ EState mState;
+ nsresult mStatus;
+
+ Atomic<bool> mActiveChunk; // Is true iff the chunk is in CacheFile::mChunks.
+ // Adding/removing chunk to/from mChunks as well
+ // as changing this member happens under the
+ // CacheFile's lock.
+ bool mIsDirty : 1;
+ bool mDiscardedChunk : 1;
+
+ uint32_t mBuffersSize;
+ bool const mLimitAllocation : 1; // Whether this chunk respects limit for
+ // disk chunks memory usage.
+ bool const mIsPriority : 1;
+
+ // Buffer containing the chunk data. Multiple read handles can access the same
+ // buffer. When write handle is created and some read handle exists a new copy
+ // of the buffer is created. This prevents invalidating the buffer when
+ // CacheFileInputStream::ReadSegments calls the handler outside the lock.
+ RefPtr<CacheFileChunkBuffer> mBuf;
+
+ // We need to keep pointers of the old buffers for memory reporting.
+ nsTArray<RefPtr<CacheFileChunkBuffer>> mOldBufs;
+
+ // Read handle that is used during writing the chunk to the disk.
+ UniquePtr<CacheFileChunkReadHandle> mWritingStateHandle;
+
+ // Buffer that is used to read the chunk from the disk. It is allowed to write
+ // a new data to chunk while we wait for the data from the disk. In this case
+ // this buffer is merged with mBuf in OnDataRead().
+ RefPtr<CacheFileChunkBuffer> mReadingStateBuf;
+ CacheHash::Hash16_t mExpectedHash;
+
+ RefPtr<CacheFile> mFile; // is null if chunk is cached to
+ // prevent reference cycles
+ nsCOMPtr<CacheFileChunkListener> mListener;
+ nsTArray<ChunkListenerItem*> mUpdateListeners;
+ CacheFileUtils::ValidityMap mValidityMap;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileContextEvictor.cpp b/netwerk/cache2/CacheFileContextEvictor.cpp
new file mode 100644
index 0000000000..1f0792759a
--- /dev/null
+++ b/netwerk/cache2/CacheFileContextEvictor.cpp
@@ -0,0 +1,741 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheFileContextEvictor.h"
+#include "CacheFileIOManager.h"
+#include "CacheFileMetadata.h"
+#include "CacheIndex.h"
+#include "CacheIndexIterator.h"
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
+#include "nsIFile.h"
+#include "LoadContextInfo.h"
+#include "nsThreadUtils.h"
+#include "nsString.h"
+#include "nsIDirectoryEnumerator.h"
+#include "mozilla/Base64.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+
+namespace mozilla::net {
+
+#define CONTEXT_EVICTION_PREFIX "ce_"
+const uint32_t kContextEvictionPrefixLength =
+ sizeof(CONTEXT_EVICTION_PREFIX) - 1;
+
+bool CacheFileContextEvictor::sDiskAlreadySearched = false;
+
+CacheFileContextEvictor::CacheFileContextEvictor() {
+ LOG(("CacheFileContextEvictor::CacheFileContextEvictor() [this=%p]", this));
+}
+
+CacheFileContextEvictor::~CacheFileContextEvictor() {
+ LOG(("CacheFileContextEvictor::~CacheFileContextEvictor() [this=%p]", this));
+}
+
+nsresult CacheFileContextEvictor::Init(nsIFile* aCacheDirectory) {
+ LOG(("CacheFileContextEvictor::Init()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ CacheIndex::IsUpToDate(&mIndexIsUpToDate);
+
+ mCacheDirectory = aCacheDirectory;
+
+ rv = aCacheDirectory->Clone(getter_AddRefs(mEntriesDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mEntriesDir->AppendNative(nsLiteralCString(ENTRIES_DIR));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!sDiskAlreadySearched) {
+ LoadEvictInfoFromDisk();
+ if ((mEntries.Length() != 0) && mIndexIsUpToDate) {
+ CreateIterators();
+ StartEvicting();
+ }
+ }
+
+ return NS_OK;
+}
+
+void CacheFileContextEvictor::Shutdown() {
+ LOG(("CacheFileContextEvictor::Shutdown()"));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ CloseIterators();
+}
+
+uint32_t CacheFileContextEvictor::ContextsCount() {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ return mEntries.Length();
+}
+
+nsresult CacheFileContextEvictor::AddContext(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin) {
+ LOG(
+ ("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p, "
+ "pinned=%d]",
+ this, aLoadContextInfo, aPinned));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ CacheFileContextEvictorEntry* entry = nullptr;
+ if (aLoadContextInfo) {
+ for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+ if (mEntries[i]->mInfo && mEntries[i]->mInfo->Equals(aLoadContextInfo) &&
+ mEntries[i]->mPinned == aPinned &&
+ mEntries[i]->mOrigin.Equals(aOrigin)) {
+ entry = mEntries[i].get();
+ break;
+ }
+ }
+ } else {
+ // Not providing load context info means we want to delete everything,
+ // so let's not bother with any currently running context cleanups
+ // for the same pinning state.
+ for (uint32_t i = mEntries.Length(); i > 0;) {
+ --i;
+ if (mEntries[i]->mInfo && mEntries[i]->mPinned == aPinned) {
+ RemoveEvictInfoFromDisk(mEntries[i]->mInfo, mEntries[i]->mPinned,
+ mEntries[i]->mOrigin);
+ mEntries.RemoveElementAt(i);
+ }
+ }
+ }
+
+ if (!entry) {
+ entry = new CacheFileContextEvictorEntry();
+ entry->mInfo = aLoadContextInfo;
+ entry->mPinned = aPinned;
+ entry->mOrigin = aOrigin;
+ mEntries.AppendElement(WrapUnique(entry));
+ }
+
+ entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
+
+ PersistEvictionInfoToDisk(aLoadContextInfo, aPinned, aOrigin);
+
+ if (mIndexIsUpToDate) {
+ // Already existing context could be added again, in this case the iterator
+ // would be recreated. Close the old iterator explicitely.
+ if (entry->mIterator) {
+ entry->mIterator->Close();
+ entry->mIterator = nullptr;
+ }
+
+ rv = CacheIndex::GetIterator(aLoadContextInfo, false,
+ getter_AddRefs(entry->mIterator));
+ if (NS_FAILED(rv)) {
+ // This could probably happen during shutdown. Remove the entry from
+ // the array, but leave the info on the disk. No entry can be opened
+ // during shutdown and we'll load the eviction info on next start.
+ LOG(
+ ("CacheFileContextEvictor::AddContext() - Cannot get an iterator. "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ mEntries.RemoveElement(entry);
+ return rv;
+ }
+
+ StartEvicting();
+ }
+
+ return NS_OK;
+}
+
+void CacheFileContextEvictor::CacheIndexStateChanged() {
+ LOG(("CacheFileContextEvictor::CacheIndexStateChanged() [this=%p]", this));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ bool isUpToDate = false;
+ CacheIndex::IsUpToDate(&isUpToDate);
+ if (mEntries.Length() == 0) {
+ // Just save the state and exit, since there is nothing to do
+ mIndexIsUpToDate = isUpToDate;
+ return;
+ }
+
+ if (!isUpToDate && !mIndexIsUpToDate) {
+ // Index is outdated and status has not changed, nothing to do.
+ return;
+ }
+
+ if (isUpToDate && mIndexIsUpToDate) {
+ // Status has not changed, but make sure the eviction is running.
+ if (mEvicting) {
+ return;
+ }
+
+ // We're not evicting, but we should be evicting?!
+ LOG(
+ ("CacheFileContextEvictor::CacheIndexStateChanged() - Index is up to "
+ "date, we have some context to evict but eviction is not running! "
+ "Starting now."));
+ }
+
+ mIndexIsUpToDate = isUpToDate;
+
+ if (mIndexIsUpToDate) {
+ CreateIterators();
+ StartEvicting();
+ } else {
+ CloseIterators();
+ }
+}
+
+void CacheFileContextEvictor::WasEvicted(const nsACString& aKey, nsIFile* aFile,
+ bool* aEvictedAsPinned,
+ bool* aEvictedAsNonPinned) {
+ LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
+ PromiseFlatCString(aKey).get()));
+
+ *aEvictedAsPinned = false;
+ *aEvictedAsNonPinned = false;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
+ MOZ_ASSERT(info);
+ if (!info) {
+ LOG(("CacheFileContextEvictor::WasEvicted() - Cannot parse key!"));
+ return;
+ }
+
+ for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+ const auto& entry = mEntries[i];
+
+ if (entry->mInfo && !info->Equals(entry->mInfo)) {
+ continue;
+ }
+
+ PRTime lastModifiedTime;
+ if (NS_FAILED(aFile->GetLastModifiedTime(&lastModifiedTime))) {
+ LOG(
+ ("CacheFileContextEvictor::WasEvicted() - Cannot get last modified "
+ "time, returning."));
+ return;
+ }
+
+ if (lastModifiedTime > entry->mTimeStamp) {
+ // File has been modified since context eviction.
+ continue;
+ }
+
+ LOG(
+ ("CacheFileContextEvictor::WasEvicted() - evicted [pinning=%d, "
+ "mTimeStamp=%" PRId64 ", lastModifiedTime=%" PRId64 "]",
+ entry->mPinned, entry->mTimeStamp, lastModifiedTime));
+
+ if (entry->mPinned) {
+ *aEvictedAsPinned = true;
+ } else {
+ *aEvictedAsNonPinned = true;
+ }
+ }
+}
+
+nsresult CacheFileContextEvictor::PersistEvictionInfoToDisk(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin) {
+ LOG(
+ ("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
+ "loadContextInfo=%p]",
+ this, aLoadContextInfo));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetContextFile(aLoadContextInfo, aPinned, aOrigin, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString path = file->HumanReadablePath();
+
+ PRFileDesc* fd;
+ rv =
+ file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Creating file "
+ "failed! [path=%s, rv=0x%08" PRIx32 "]",
+ path.get(), static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ PR_Close(fd);
+
+ LOG(
+ ("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
+ "created file. [path=%s]",
+ path.get()));
+
+ return NS_OK;
+}
+
+nsresult CacheFileContextEvictor::RemoveEvictInfoFromDisk(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin) {
+ LOG(
+ ("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
+ "loadContextInfo=%p]",
+ this, aLoadContextInfo));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetContextFile(aLoadContextInfo, aPinned, aOrigin, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString path = file->HumanReadablePath();
+
+ rv = file->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Removing file"
+ " failed! [path=%s, rv=0x%08" PRIx32 "]",
+ path.get(), static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ LOG(
+ ("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Successfully "
+ "removed file. [path=%s]",
+ path.get()));
+
+ return NS_OK;
+}
+
+nsresult CacheFileContextEvictor::LoadEvictInfoFromDisk() {
+ LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() [this=%p]", this));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ sDiskAlreadySearched = true;
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnum;
+ rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(dirEnum));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ while (true) {
+ nsCOMPtr<nsIFile> file;
+ rv = dirEnum->GetNextFile(getter_AddRefs(file));
+ if (!file) {
+ break;
+ }
+
+ bool isDir = false;
+ file->IsDirectory(&isDir);
+ if (isDir) {
+ continue;
+ }
+
+ nsAutoCString leaf;
+ rv = file->GetNativeLeafName(leaf);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::LoadEvictInfoFromDisk() - "
+ "GetNativeLeafName() failed! Skipping file."));
+ continue;
+ }
+
+ if (leaf.Length() < kContextEvictionPrefixLength) {
+ continue;
+ }
+
+ if (!StringBeginsWith(leaf, nsLiteralCString(CONTEXT_EVICTION_PREFIX))) {
+ continue;
+ }
+
+ nsAutoCString encoded;
+ encoded = Substring(leaf, kContextEvictionPrefixLength);
+ encoded.ReplaceChar('-', '/');
+
+ nsAutoCString decoded;
+ rv = Base64Decode(encoded, decoded);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Base64 decoding "
+ "failed. Removing the file. [file=%s]",
+ leaf.get()));
+ file->Remove(false);
+ continue;
+ }
+
+ bool pinned = decoded[0] == '\t';
+ if (pinned) {
+ decoded = Substring(decoded, 1);
+ }
+
+ // Let's see if we have an origin.
+ nsAutoCString origin;
+ if (decoded.Contains('\t')) {
+ auto split = decoded.Split('\t');
+ MOZ_ASSERT(decoded.CountChar('\t') == 1);
+
+ auto splitIt = split.begin();
+ origin = *splitIt;
+ ++splitIt;
+ decoded = *splitIt;
+ }
+
+ nsCOMPtr<nsILoadContextInfo> info;
+ if (!"*"_ns.Equals(decoded)) {
+ // "*" is indication of 'delete all', info left null will pass
+ // to CacheFileContextEvictor::AddContext and clear all the cache data.
+ info = CacheFileUtils::ParseKey(decoded);
+ if (!info) {
+ LOG(
+ ("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
+ "context key, removing file. [contextKey=%s, file=%s]",
+ decoded.get(), leaf.get()));
+ file->Remove(false);
+ continue;
+ }
+ }
+
+ PRTime lastModifiedTime;
+ rv = file->GetLastModifiedTime(&lastModifiedTime);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ CacheFileContextEvictorEntry* entry = new CacheFileContextEvictorEntry();
+ entry->mInfo = info;
+ entry->mPinned = pinned;
+ CopyUTF8toUTF16(origin, entry->mOrigin);
+ entry->mTimeStamp = lastModifiedTime;
+ mEntries.AppendElement(entry);
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileContextEvictor::GetContextFile(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin, nsIFile** _retval) {
+ nsresult rv;
+
+ nsAutoCString keyPrefix;
+ if (aPinned) {
+ // Mark pinned context files with a tab char at the start.
+ // Tab is chosen because it can never be used as a context key tag.
+ keyPrefix.Append('\t');
+ }
+ if (aLoadContextInfo) {
+ CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
+ } else {
+ keyPrefix.Append('*');
+ }
+ if (!aOrigin.IsEmpty()) {
+ keyPrefix.Append('\t');
+ keyPrefix.Append(NS_ConvertUTF16toUTF8(aOrigin));
+ }
+
+ nsAutoCString leafName;
+ leafName.AssignLiteral(CONTEXT_EVICTION_PREFIX);
+
+ rv = Base64EncodeAppend(keyPrefix, leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Replace '/' with '-' since '/' cannot be part of the filename.
+ leafName.ReplaceChar('/', '-');
+
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->AppendNative(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+void CacheFileContextEvictor::CreateIterators() {
+ LOG(("CacheFileContextEvictor::CreateIterators() [this=%p]", this));
+
+ CloseIterators();
+
+ nsresult rv;
+
+ for (uint32_t i = 0; i < mEntries.Length();) {
+ rv = CacheIndex::GetIterator(mEntries[i]->mInfo, false,
+ getter_AddRefs(mEntries[i]->mIterator));
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::CreateIterators() - Cannot get an iterator"
+ ". [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ mEntries.RemoveElementAt(i);
+ continue;
+ }
+
+ ++i;
+ }
+}
+
+void CacheFileContextEvictor::CloseIterators() {
+ LOG(("CacheFileContextEvictor::CloseIterators() [this=%p]", this));
+
+ for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+ if (mEntries[i]->mIterator) {
+ mEntries[i]->mIterator->Close();
+ mEntries[i]->mIterator = nullptr;
+ }
+ }
+}
+
+void CacheFileContextEvictor::StartEvicting() {
+ LOG(("CacheFileContextEvictor::StartEvicting() [this=%p]", this));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ if (mEvicting) {
+ LOG(("CacheFileContextEvictor::StartEvicting() - already evicting."));
+ return;
+ }
+
+ if (mEntries.Length() == 0) {
+ LOG(("CacheFileContextEvictor::StartEvicting() - no context to evict."));
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod("net::CacheFileContextEvictor::EvictEntries", this,
+ &CacheFileContextEvictor::EvictEntries);
+
+ RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
+
+ nsresult rv = ioThread->Dispatch(ev, CacheIOThread::EVICT);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::StartEvicting() - Cannot dispatch event to "
+ "IO thread. [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+
+ mEvicting = true;
+}
+
+void CacheFileContextEvictor::EvictEntries() {
+ LOG(("CacheFileContextEvictor::EvictEntries()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ mEvicting = false;
+
+ if (!mIndexIsUpToDate) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Stopping evicting due to "
+ "outdated index."));
+ return;
+ }
+
+ while (true) {
+ if (CacheObserver::ShuttingDown()) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Stopping evicting due to "
+ "shutdown."));
+ mEvicting =
+ true; // We don't want to start eviction again during shutdown
+ // process. Setting this flag to true ensures it.
+ return;
+ }
+
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Breaking loop for higher "
+ "level events."));
+ mEvicting = true;
+ return;
+ }
+
+ if (mEntries.Length() == 0) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Stopping evicting, there "
+ "is no context to evict."));
+
+ // Allow index to notify AsyncGetDiskConsumption callbacks. The size is
+ // actual again.
+ CacheIndex::OnAsyncEviction(false);
+ return;
+ }
+
+ SHA1Sum::Hash hash;
+ rv = mEntries[0]->mIterator->GetNextHash(&hash);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - No more entries left in "
+ "iterator. [iterator=%p, info=%p]",
+ mEntries[0]->mIterator.get(), mEntries[0]->mInfo.get()));
+ RemoveEvictInfoFromDisk(mEntries[0]->mInfo, mEntries[0]->mPinned,
+ mEntries[0]->mOrigin);
+ mEntries.RemoveElementAt(0);
+ continue;
+ }
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Iterator failed to "
+ "provide next hash (shutdown?), keeping eviction info on disk."
+ " [iterator=%p, info=%p]",
+ mEntries[0]->mIterator.get(), mEntries[0]->mInfo.get()));
+ mEntries.RemoveElementAt(0);
+ continue;
+ }
+
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Processing hash. "
+ "[hash=%08x%08x%08x%08x%08x, iterator=%p, info=%p]",
+ LOGSHA1(&hash), mEntries[0]->mIterator.get(),
+ mEntries[0]->mInfo.get()));
+
+ RefPtr<CacheFileHandle> handle;
+ CacheFileIOManager::gInstance->mHandles.GetHandle(&hash,
+ getter_AddRefs(handle));
+ if (handle) {
+ // We doom any active handle in CacheFileIOManager::EvictByContext(), so
+ // this must be a new one. Skip it.
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Skipping entry since we "
+ "found an active handle. [handle=%p]",
+ handle.get()));
+ continue;
+ }
+
+ CacheIndex::EntryStatus status;
+ bool pinned = false;
+ auto callback = [&pinned](const CacheIndexEntry* aEntry) {
+ pinned = aEntry->IsPinned();
+ };
+ rv = CacheIndex::HasEntry(hash, &status, callback);
+ // This must never fail, since eviction (this code) happens only when the
+ // index is up-to-date and thus the informatin is known.
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (pinned != mEntries[0]->mPinned) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
+ "pinning "
+ "doesn't match [evicting pinned=%d, entry pinned=%d]",
+ mEntries[0]->mPinned, pinned));
+ continue;
+ }
+
+ if (!mEntries[0]->mOrigin.IsEmpty()) {
+ nsCOMPtr<nsIFile> file;
+ CacheFileIOManager::gInstance->GetFile(&hash, getter_AddRefs(file));
+
+ // Read metadata from the file synchronously
+ RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
+ rv = metadata->SyncReadMetadata(file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ // Now get the context + enhance id + URL from the key.
+ nsAutoCString uriSpec;
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(metadata->GetKey(), nullptr, &uriSpec);
+ MOZ_ASSERT(info);
+ if (!info) {
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
+ "NS_NewURI failed to parse the uriSpec"));
+ continue;
+ }
+
+ nsAutoString urlOrigin;
+ rv = nsContentUtils::GetWebExposedOriginSerialization(uri, urlOrigin);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
+ "We failed to extract an origin"));
+ continue;
+ }
+
+ if (!urlOrigin.Equals(mEntries[0]->mOrigin)) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
+ "origin "
+ "doesn't match"));
+ continue;
+ }
+ }
+
+ nsAutoCString leafName;
+ CacheFileIOManager::HashToStr(&hash, leafName);
+
+ PRTime lastModifiedTime;
+ nsCOMPtr<nsIFile> file;
+ rv = mEntriesDir->Clone(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->AppendNative(leafName);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->GetLastModifiedTime(&lastModifiedTime);
+ }
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Cannot get last modified "
+ "time, skipping entry."));
+ continue;
+ }
+
+ if (lastModifiedTime > mEntries[0]->mTimeStamp) {
+ LOG(
+ ("CacheFileContextEvictor::EvictEntries() - Skipping newer entry. "
+ "[mTimeStamp=%" PRId64 ", lastModifiedTime=%" PRId64 "]",
+ mEntries[0]->mTimeStamp, lastModifiedTime));
+ continue;
+ }
+
+ LOG(("CacheFileContextEvictor::EvictEntries - Removing entry."));
+ file->Remove(false);
+ CacheIndex::RemoveEntry(&hash);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheFileContextEvictor.h b/netwerk/cache2/CacheFileContextEvictor.h
new file mode 100644
index 0000000000..518d3b7d5f
--- /dev/null
+++ b/netwerk/cache2/CacheFileContextEvictor.h
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileContextEvictor__h__
+#define CacheFileContextEvictor__h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIFile;
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheIndexIterator;
+
+struct CacheFileContextEvictorEntry {
+ nsCOMPtr<nsILoadContextInfo> mInfo;
+ bool mPinned = false;
+ nsString mOrigin; // it can be empty
+ PRTime mTimeStamp = 0; // in milliseconds
+ RefPtr<CacheIndexIterator> mIterator;
+};
+
+class CacheFileContextEvictor {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileContextEvictor)
+
+ CacheFileContextEvictor();
+
+ private:
+ virtual ~CacheFileContextEvictor();
+
+ public:
+ nsresult Init(nsIFile* aCacheDirectory);
+ void Shutdown();
+
+ // Returns number of contexts that are being evicted.
+ uint32_t ContextsCount();
+ // Start evicting given context and an origin, if not empty.
+ nsresult AddContext(nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin);
+ // CacheFileIOManager calls this method when CacheIndex's state changes. We
+ // check whether the index is up to date and start or stop evicting according
+ // to index's state.
+ void CacheIndexStateChanged();
+ // CacheFileIOManager calls this method to check whether an entry file should
+ // be considered as evicted. It returns true when there is a matching context
+ // info to the given key and the last modified time of the entry file is
+ // earlier than the time stamp of the time when the context was added to the
+ // evictor.
+ void WasEvicted(const nsACString& aKey, nsIFile* aFile,
+ bool* aEvictedAsPinned, bool* aEvictedAsNonPinned);
+
+ private:
+ // Writes information about eviction of the given context to the disk. This is
+ // done for every context added to the evictor to be able to recover eviction
+ // after a shutdown or crash. When the context file is found after startup, we
+ // restore mTimeStamp from the last modified time of the file.
+ nsresult PersistEvictionInfoToDisk(nsILoadContextInfo* aLoadContextInfo,
+ bool aPinned, const nsAString& aOrigin);
+ // Once we are done with eviction for the given context, the eviction info is
+ // removed from the disk.
+ nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo* aLoadContextInfo,
+ bool aPinned, const nsAString& aOrigin);
+ // Tries to load all contexts from the disk. This method is called just once
+ // after startup.
+ nsresult LoadEvictInfoFromDisk();
+ nsresult GetContextFile(nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin, nsIFile** _retval);
+
+ void CreateIterators();
+ void CloseIterators();
+ void StartEvicting();
+ void EvictEntries();
+
+ // Whether eviction is in progress
+ bool mEvicting{false};
+ // Whether index is up to date. We wait with eviction until the index finishes
+ // update process when it is outdated. NOTE: We also stop eviction in progress
+ // when the index is found outdated, the eviction is restarted again once the
+ // update process finishes.
+ bool mIndexIsUpToDate{false};
+ // Whether we already tried to restore unfinished jobs from previous run after
+ // startup.
+ static bool sDiskAlreadySearched;
+ // Array of contexts being evicted.
+ nsTArray<UniquePtr<CacheFileContextEvictorEntry>> mEntries;
+ nsCOMPtr<nsIFile> mCacheDirectory;
+ nsCOMPtr<nsIFile> mEntriesDir;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileIOManager.cpp b/netwerk/cache2/CacheFileIOManager.cpp
new file mode 100644
index 0000000000..8cff1c1cd9
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -0,0 +1,4499 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits>
+#include "CacheLog.h"
+#include "CacheFileIOManager.h"
+
+#include "CacheHashUtils.h"
+#include "CacheStorageService.h"
+#include "CacheIndex.h"
+#include "CacheFileUtils.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+#include "CacheFile.h"
+#include "CacheObserver.h"
+#include "nsIFile.h"
+#include "CacheFileContextEvictor.h"
+#include "nsITimer.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsEffectiveTLDService.h"
+#include "nsIObserverService.h"
+#include "nsISizeOf.h"
+#include "mozilla/net/MozURL.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "private/pprio.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasksRunner.h"
+# include "nsIBackgroundTasks.h"
+#endif
+
+// include files for ftruncate (or equivalent)
+#if defined(XP_UNIX)
+# include <unistd.h>
+#elif defined(XP_WIN)
+# include <windows.h>
+# undef CreateFile
+# undef CREATE_NEW
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+namespace mozilla::net {
+
+#define kOpenHandlesLimit 128
+#define kMetadataWriteDelay 5000
+#define kRemoveTrashStartDelay 60000 // in milliseconds
+#define kSmartSizeUpdateInterval 60000 // in milliseconds
+
+#ifdef ANDROID
+const uint32_t kMaxCacheSizeKB = 512 * 1024; // 512 MB
+#else
+const uint32_t kMaxCacheSizeKB = 1024 * 1024; // 1 GB
+#endif
+const uint32_t kMaxClearOnShutdownCacheSizeKB = 150 * 1024; // 150 MB
+const auto kPurgeExtension = ".purge.bg_rm"_ns;
+
+bool CacheFileHandle::DispatchRelease() {
+ if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ if (!ioTarget) {
+ return false;
+ }
+
+ nsresult rv = ioTarget->Dispatch(
+ NewNonOwningRunnableMethod("net::CacheFileHandle::Release", this,
+ &CacheFileHandle::Release),
+ nsIEventTarget::DISPATCH_NORMAL);
+ return NS_SUCCEEDED(rv);
+}
+
+NS_IMPL_ADDREF(CacheFileHandle)
+NS_IMETHODIMP_(MozExternalRefCountType)
+CacheFileHandle::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the IO thread.
+ return count;
+ }
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ LOG(("CacheFileHandle::Release() [this=%p, refcnt=%" PRIuPTR "]", this,
+ mRefCnt.get()));
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "CacheFileHandle");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority,
+ PinningStatus aPinning)
+ : mHash(aHash),
+ mIsDoomed(false),
+ mClosed(false),
+ mPriority(aPriority),
+ mSpecialFile(false),
+ mInvalid(false),
+ mFileExists(false),
+ mDoomWhenFoundPinned(false),
+ mDoomWhenFoundNonPinned(false),
+ mKilled(false),
+ mPinning(aPinning),
+ mFileSize(-1),
+ mFD(nullptr) {
+ // If we initialize mDoomed in the initialization list, that initialization is
+ // not guaranteeded to be atomic. Whereas this assignment here is guaranteed
+ // to be atomic. TSan will see this (atomic) assignment and be satisfied
+ // that cross-thread accesses to mIsDoomed are properly synchronized.
+ mIsDoomed = false;
+ LOG((
+ "CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]",
+ this, LOGSHA1(aHash)));
+}
+
+CacheFileHandle::CacheFileHandle(const nsACString& aKey, bool aPriority,
+ PinningStatus aPinning)
+ : mHash(nullptr),
+ mIsDoomed(false),
+ mClosed(false),
+ mPriority(aPriority),
+ mSpecialFile(true),
+ mInvalid(false),
+ mFileExists(false),
+ mDoomWhenFoundPinned(false),
+ mDoomWhenFoundNonPinned(false),
+ mKilled(false),
+ mPinning(aPinning),
+ mFileSize(-1),
+ mFD(nullptr),
+ mKey(aKey) {
+ // See comment above about the initialization of mIsDoomed.
+ mIsDoomed = false;
+ LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
+ PromiseFlatCString(aKey).get()));
+}
+
+CacheFileHandle::~CacheFileHandle() {
+ LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
+ if (!IsClosed() && ioMan) {
+ ioMan->CloseHandleInternal(this);
+ }
+}
+
+void CacheFileHandle::Log() {
+ nsAutoCString leafName;
+ if (mFile) {
+ mFile->GetNativeLeafName(leafName);
+ }
+
+ if (mSpecialFile) {
+ LOG(
+ ("CacheFileHandle::Log() - special file [this=%p, "
+ "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
+ "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
+ ", leafName=%s, key=%s]",
+ this, bool(mIsDoomed), bool(mPriority), bool(mClosed), bool(mInvalid),
+ static_cast<uint32_t>(mPinning), bool(mFileExists), int64_t(mFileSize),
+ leafName.get(), mKey.get()));
+ } else {
+ LOG(
+ ("CacheFileHandle::Log() - entry file [this=%p, "
+ "hash=%08x%08x%08x%08x%08x, "
+ "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
+ "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
+ ", leafName=%s, key=%s]",
+ this, LOGSHA1(mHash), bool(mIsDoomed), bool(mPriority), bool(mClosed),
+ bool(mInvalid), static_cast<uint32_t>(mPinning), bool(mFileExists),
+ int64_t(mFileSize), leafName.get(), mKey.get()));
+ }
+}
+
+uint32_t CacheFileHandle::FileSizeInK() const {
+ MOZ_ASSERT(mFileSize != -1);
+ uint64_t size64 = mFileSize;
+
+ size64 += 0x3FF;
+ size64 >>= 10;
+
+ uint32_t size;
+ if (size64 >> 32) {
+ NS_WARNING(
+ "CacheFileHandle::FileSizeInK() - FileSize is too large, "
+ "truncating to PR_UINT32_MAX");
+ size = PR_UINT32_MAX;
+ } else {
+ size = static_cast<uint32_t>(size64);
+ }
+
+ return size;
+}
+
+bool CacheFileHandle::SetPinned(bool aPinned) {
+ LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ mPinning = aPinned ? PinningStatus::PINNED : PinningStatus::NON_PINNED;
+
+ if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) ||
+ (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) {
+ LOG((" dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
+ bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned));
+
+ mDoomWhenFoundPinned = false;
+ mDoomWhenFoundNonPinned = false;
+
+ return false;
+ }
+
+ return true;
+}
+
+// Memory reporting
+
+size_t CacheFileHandle::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+ nsCOMPtr<nsISizeOf> sizeOf;
+
+ sizeOf = do_QueryInterface(mFile);
+ if (sizeOf) {
+ n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ n += mallocSizeOf(mFD);
+ n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ return n;
+}
+
+size_t CacheFileHandle::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+/******************************************************************************
+ * CacheFileHandles::HandleHashKey
+ *****************************************************************************/
+
+void CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ mHandles.InsertElementAt(0, aHandle);
+}
+
+void CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ DebugOnly<bool> found{};
+ found = mHandles.RemoveElement(aHandle);
+ MOZ_ASSERT(found);
+}
+
+already_AddRefed<CacheFileHandle>
+CacheFileHandles::HandleHashKey::GetNewestHandle() {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ RefPtr<CacheFileHandle> handle;
+ if (mHandles.Length()) {
+ handle = mHandles[0];
+ }
+
+ return handle.forget();
+}
+
+void CacheFileHandles::HandleHashKey::GetHandles(
+ nsTArray<RefPtr<CacheFileHandle>>& aResult) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+ CacheFileHandle* handle = mHandles[i];
+ aResult.AppendElement(handle);
+ }
+}
+
+#ifdef DEBUG
+
+void CacheFileHandles::HandleHashKey::AssertHandlesState() {
+ for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+ CacheFileHandle* handle = mHandles[i];
+ MOZ_ASSERT(handle->IsDoomed());
+ }
+}
+
+#endif
+
+size_t CacheFileHandles::HandleHashKey::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ size_t n = 0;
+ n += mallocSizeOf(mHash.get());
+ for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+ n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ return n;
+}
+
+/******************************************************************************
+ * CacheFileHandles
+ *****************************************************************************/
+
+CacheFileHandles::CacheFileHandles() {
+ LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
+ MOZ_COUNT_CTOR(CacheFileHandles);
+}
+
+CacheFileHandles::~CacheFileHandles() {
+ LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
+ MOZ_COUNT_DTOR(CacheFileHandles);
+}
+
+nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash* aHash,
+ CacheFileHandle** _retval) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHash);
+
+#ifdef DEBUG_HANDLES
+ LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+#endif
+
+ // find hash entry for key
+ HandleHashKey* entry = mTable.GetEntry(*aHash);
+ if (!entry) {
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "no handle entries found",
+ LOGSHA1(aHash)));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#ifdef DEBUG_HANDLES
+ Log(entry);
+#endif
+
+ // Check if the entry is doomed
+ RefPtr<CacheFileHandle> handle = entry->GetNewestHandle();
+ if (!handle) {
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "no handle found %p, entry %p",
+ LOGSHA1(aHash), handle.get(), entry));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (handle->IsDoomed()) {
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "found doomed handle %p, entry %p",
+ LOGSHA1(aHash), handle.get(), entry));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "found handle %p, entry %p",
+ LOGSHA1(aHash), handle.get(), entry));
+
+ handle.forget(_retval);
+ return NS_OK;
+}
+
+already_AddRefed<CacheFileHandle> CacheFileHandles::NewHandle(
+ const SHA1Sum::Hash* aHash, bool aPriority,
+ CacheFileHandle::PinningStatus aPinning) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHash);
+
+#ifdef DEBUG_HANDLES
+ LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+#endif
+
+ // find hash entry for key
+ HandleHashKey* entry = mTable.PutEntry(*aHash);
+
+#ifdef DEBUG_HANDLES
+ Log(entry);
+#endif
+
+#ifdef DEBUG
+ entry->AssertHandlesState();
+#endif
+
+ RefPtr<CacheFileHandle> handle =
+ new CacheFileHandle(entry->Hash(), aPriority, aPinning);
+ entry->AddHandle(handle);
+
+ LOG(
+ ("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
+ "created new handle %p, entry=%p",
+ LOGSHA1(aHash), handle.get(), entry));
+ return handle.forget();
+}
+
+void CacheFileHandles::RemoveHandle(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHandle);
+
+ if (!aHandle) {
+ return;
+ }
+
+#ifdef DEBUG_HANDLES
+ LOG((
+ "CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]",
+ aHandle, LOGSHA1(aHandle->Hash())));
+#endif
+
+ // find hash entry for key
+ HandleHashKey* entry = mTable.GetEntry(*aHandle->Hash());
+ if (!entry) {
+ MOZ_ASSERT(CacheFileIOManager::IsShutdown(),
+ "Should find entry when removing a handle before shutdown");
+
+ LOG(
+ ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+ "no entries found",
+ LOGSHA1(aHandle->Hash())));
+ return;
+ }
+
+#ifdef DEBUG_HANDLES
+ Log(entry);
+#endif
+
+ LOG(
+ ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+ "removing handle %p",
+ LOGSHA1(entry->Hash()), aHandle));
+ entry->RemoveHandle(aHandle);
+
+ if (entry->IsEmpty()) {
+ LOG(
+ ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+ "list is empty, removing entry %p",
+ LOGSHA1(entry->Hash()), entry));
+ mTable.RemoveEntry(entry);
+ }
+}
+
+void CacheFileHandles::GetAllHandles(
+ nsTArray<RefPtr<CacheFileHandle>>* _retval) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetHandles(*_retval);
+ }
+}
+
+void CacheFileHandles::GetActiveHandles(
+ nsTArray<RefPtr<CacheFileHandle>>* _retval) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<CacheFileHandle> handle = iter.Get()->GetNewestHandle();
+ MOZ_ASSERT(handle);
+
+ if (!handle->IsDoomed()) {
+ _retval->AppendElement(handle);
+ }
+ }
+}
+
+void CacheFileHandles::ClearAll() {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ mTable.Clear();
+}
+
+uint32_t CacheFileHandles::HandleCount() { return mTable.Count(); }
+
+#ifdef DEBUG_HANDLES
+void CacheFileHandles::Log(CacheFileHandlesEntry* entry) {
+ LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry));
+
+ nsTArray<RefPtr<CacheFileHandle>> array;
+ aEntry->GetHandles(array);
+
+ for (uint32_t i = 0; i < array.Length(); ++i) {
+ CacheFileHandle* handle = array[i];
+ handle->Log();
+ }
+
+ LOG(("CacheFileHandles::Log() END [entry=%p]", entry));
+}
+#endif
+
+// Memory reporting
+
+size_t CacheFileHandles::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ return mTable.SizeOfExcludingThis(mallocSizeOf);
+}
+
+// Events
+
+class ShutdownEvent : public Runnable, nsITimerCallback {
+ NS_DECL_ISUPPORTS_INHERITED
+ public:
+ ShutdownEvent() : Runnable("net::ShutdownEvent") {}
+
+ protected:
+ ~ShutdownEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ CacheFileIOManager::gInstance->ShutdownInternal();
+
+ mNotified = true;
+
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("CacheFileIOManager::ShutdownEvent::Run", []() {
+ // This empty runnable is dispatched just in case the MT event loop
+ // becomes empty - we need to process a task to break out of
+ // SpinEventLoopUntil.
+ }));
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Notify(nsITimer* timer) override {
+ if (mNotified) {
+ return NS_OK;
+ }
+
+ // If there is any IO blocking on the IO thread, this will
+ // try to cancel it.
+ CacheFileIOManager::gInstance->mIOThread->CancelBlockingIO();
+
+ // After this runs the first time, the browser_cache_max_shutdown_io_lag
+ // time has elapsed. The CacheIO thread may pick up more blocking IO tasks
+ // so we want to block those too if necessary.
+ mTimer->SetDelay(
+ StaticPrefs::browser_cache_shutdown_io_time_between_cancellations_ms());
+ return NS_OK;
+ }
+
+ void PostAndWait() {
+ nsresult rv = CacheFileIOManager::gInstance->mIOThread->Dispatch(
+ this,
+ CacheIOThread::WRITE); // When writes and closing of handles is done
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // If we failed to post the even there's no reason to go into the loop
+ // because mNotified will never be set to true.
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Posting ShutdownEvent task failed");
+ return;
+ }
+
+ rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this,
+ StaticPrefs::browser_cache_max_shutdown_io_lag() * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ mozilla::SpinEventLoopUntil("CacheFileIOManager::ShutdownEvent"_ns,
+ [&]() { return bool(mNotified); });
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ }
+
+ protected:
+ Atomic<bool> mNotified{false};
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(ShutdownEvent, Runnable, nsITimerCallback)
+
+// Class responsible for reporting IO performance stats
+class IOPerfReportEvent {
+ public:
+ explicit IOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataType aType)
+ : mType(aType), mEventCounter(0) {}
+
+ void Start(CacheIOThread* aIOThread) {
+ mStartTime = TimeStamp::Now();
+ mEventCounter = aIOThread->EventCounter();
+ }
+
+ void Report(CacheIOThread* aIOThread) {
+ if (mStartTime.IsNull()) {
+ return;
+ }
+
+ // Single IO operations can take less than 1ms. So we use microseconds to
+ // keep a good resolution of data.
+ uint32_t duration = (TimeStamp::Now() - mStartTime).ToMicroseconds();
+
+ // This is a simple prefiltering of values that might differ a lot from the
+ // average value. Do not add the value to the filtered stats when the event
+ // had to wait in a long queue.
+ uint32_t eventCounter = aIOThread->EventCounter();
+ bool shortOnly = eventCounter - mEventCounter >= 5;
+
+ CacheFileUtils::CachePerfStats::AddValue(mType, duration, shortOnly);
+ }
+
+ protected:
+ CacheFileUtils::CachePerfStats::EDataType mType;
+ TimeStamp mStartTime;
+ uint32_t mEventCounter;
+};
+
+class OpenFileEvent : public Runnable, public IOPerfReportEvent {
+ public:
+ OpenFileEvent(const nsACString& aKey, uint32_t aFlags,
+ CacheFileIOListener* aCallback)
+ : Runnable("net::OpenFileEvent"),
+ IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_OPEN),
+ mFlags(aFlags),
+ mCallback(aCallback),
+ mKey(aKey) {
+ mIOMan = CacheFileIOManager::gInstance;
+ if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
+ Start(mIOMan->mIOThread);
+ }
+ }
+
+ protected:
+ ~OpenFileEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv = NS_OK;
+
+ if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
+ SHA1Sum sum;
+ sum.update(mKey.BeginReading(), mKey.Length());
+ sum.finish(mHash);
+ }
+
+ if (!mIOMan) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ if (mFlags & CacheFileIOManager::SPECIAL_FILE) {
+ rv = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
+ getter_AddRefs(mHandle));
+ } else {
+ rv = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
+ getter_AddRefs(mHandle));
+ if (NS_SUCCEEDED(rv)) {
+ Report(mIOMan->mIOThread);
+ }
+ }
+ mIOMan = nullptr;
+ if (mHandle) {
+ if (mHandle->Key().IsEmpty()) {
+ mHandle->Key() = mKey;
+ }
+ }
+ }
+
+ mCallback->OnFileOpened(mHandle, rv);
+ return NS_OK;
+ }
+
+ protected:
+ SHA1Sum::Hash mHash{};
+ uint32_t mFlags;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+ RefPtr<CacheFileIOManager> mIOMan;
+ RefPtr<CacheFileHandle> mHandle;
+ nsCString mKey;
+};
+
+class ReadEvent : public Runnable, public IOPerfReportEvent {
+ public:
+ ReadEvent(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
+ int32_t aCount, CacheFileIOListener* aCallback)
+ : Runnable("net::ReadEvent"),
+ IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_READ),
+ mHandle(aHandle),
+ mOffset(aOffset),
+ mBuf(aBuf),
+ mCount(aCount),
+ mCallback(aCallback) {
+ if (!mHandle->IsSpecialFile()) {
+ Start(CacheFileIOManager::gInstance->mIOThread);
+ }
+ }
+
+ protected:
+ ~ReadEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->ReadInternal(mHandle, mOffset, mBuf,
+ mCount);
+ if (NS_SUCCEEDED(rv)) {
+ Report(CacheFileIOManager::gInstance->mIOThread);
+ }
+ }
+
+ mCallback->OnDataRead(mHandle, mBuf, rv);
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ int64_t mOffset;
+ char* mBuf;
+ int32_t mCount;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class WriteEvent : public Runnable, public IOPerfReportEvent {
+ public:
+ WriteEvent(CacheFileHandle* aHandle, int64_t aOffset, const char* aBuf,
+ int32_t aCount, bool aValidate, bool aTruncate,
+ CacheFileIOListener* aCallback)
+ : Runnable("net::WriteEvent"),
+ IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_WRITE),
+ mHandle(aHandle),
+ mOffset(aOffset),
+ mBuf(aBuf),
+ mCount(aCount),
+ mValidate(aValidate),
+ mTruncate(aTruncate),
+ mCallback(aCallback) {
+ if (!mHandle->IsSpecialFile()) {
+ Start(CacheFileIOManager::gInstance->mIOThread);
+ }
+ }
+
+ protected:
+ ~WriteEvent() {
+ if (!mCallback && mBuf) {
+ free(const_cast<char*>(mBuf));
+ }
+ }
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
+ // We usually get here only after the internal shutdown
+ // (i.e. mShuttingDown == true). Pretend write has succeeded
+ // to avoid any past-shutdown file dooming.
+ rv = (CacheObserver::IsPastShutdownIOLag() ||
+ CacheFileIOManager::gInstance->mShuttingDown)
+ ? NS_OK
+ : NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->WriteInternal(
+ mHandle, mOffset, mBuf, mCount, mValidate, mTruncate);
+ if (NS_SUCCEEDED(rv)) {
+ Report(CacheFileIOManager::gInstance->mIOThread);
+ }
+ if (NS_FAILED(rv) && !mCallback) {
+ // No listener is going to handle the error, doom the file
+ CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+ }
+ }
+ if (mCallback) {
+ mCallback->OnDataWritten(mHandle, mBuf, rv);
+ } else {
+ free(const_cast<char*>(mBuf));
+ mBuf = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ int64_t mOffset;
+ const char* mBuf;
+ int32_t mCount;
+ bool mValidate : 1;
+ bool mTruncate : 1;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class DoomFileEvent : public Runnable {
+ public:
+ DoomFileEvent(CacheFileHandle* aHandle, CacheFileIOListener* aCallback)
+ : Runnable("net::DoomFileEvent"),
+ mCallback(aCallback),
+ mHandle(aHandle) {}
+
+ protected:
+ ~DoomFileEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed()) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+ }
+
+ if (mCallback) {
+ mCallback->OnFileDoomed(mHandle, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ nsCOMPtr<CacheFileIOListener> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ RefPtr<CacheFileHandle> mHandle;
+};
+
+class DoomFileByKeyEvent : public Runnable {
+ public:
+ DoomFileByKeyEvent(const nsACString& aKey, CacheFileIOListener* aCallback)
+ : Runnable("net::DoomFileByKeyEvent"), mCallback(aCallback) {
+ SHA1Sum sum;
+ sum.update(aKey.BeginReading(), aKey.Length());
+ sum.finish(mHash);
+
+ mIOMan = CacheFileIOManager::gInstance;
+ }
+
+ protected:
+ ~DoomFileByKeyEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (!mIOMan) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = mIOMan->DoomFileByKeyInternal(&mHash);
+ mIOMan = nullptr;
+ }
+
+ if (mCallback) {
+ mCallback->OnFileDoomed(nullptr, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ SHA1Sum::Hash mHash{};
+ nsCOMPtr<CacheFileIOListener> mCallback;
+ RefPtr<CacheFileIOManager> mIOMan;
+};
+
+class ReleaseNSPRHandleEvent : public Runnable {
+ public:
+ explicit ReleaseNSPRHandleEvent(CacheFileHandle* aHandle)
+ : Runnable("net::ReleaseNSPRHandleEvent"), mHandle(aHandle) {}
+
+ protected:
+ ~ReleaseNSPRHandleEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ if (!mHandle->IsClosed()) {
+ CacheFileIOManager::gInstance->MaybeReleaseNSPRHandleInternal(mHandle);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+};
+
+class TruncateSeekSetEOFEvent : public Runnable {
+ public:
+ TruncateSeekSetEOFEvent(CacheFileHandle* aHandle, int64_t aTruncatePos,
+ int64_t aEOFPos, CacheFileIOListener* aCallback)
+ : Runnable("net::TruncateSeekSetEOFEvent"),
+ mHandle(aHandle),
+ mTruncatePos(aTruncatePos),
+ mEOFPos(aEOFPos),
+ mCallback(aCallback) {}
+
+ protected:
+ ~TruncateSeekSetEOFEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
+ mHandle, mTruncatePos, mEOFPos);
+ }
+
+ if (mCallback) {
+ mCallback->OnEOFSet(mHandle, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ int64_t mTruncatePos;
+ int64_t mEOFPos;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class RenameFileEvent : public Runnable {
+ public:
+ RenameFileEvent(CacheFileHandle* aHandle, const nsACString& aNewName,
+ CacheFileIOListener* aCallback)
+ : Runnable("net::RenameFileEvent"),
+ mHandle(aHandle),
+ mNewName(aNewName),
+ mCallback(aCallback) {}
+
+ protected:
+ ~RenameFileEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed()) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->RenameFileInternal(mHandle, mNewName);
+ }
+
+ if (mCallback) {
+ mCallback->OnFileRenamed(mHandle, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ nsCString mNewName;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class InitIndexEntryEvent : public Runnable {
+ public:
+ InitIndexEntryEvent(CacheFileHandle* aHandle,
+ OriginAttrsHash aOriginAttrsHash, bool aAnonymous,
+ bool aPinning)
+ : Runnable("net::InitIndexEntryEvent"),
+ mHandle(aHandle),
+ mOriginAttrsHash(aOriginAttrsHash),
+ mAnonymous(aAnonymous),
+ mPinning(aPinning) {}
+
+ protected:
+ ~InitIndexEntryEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ if (mHandle->IsClosed() || mHandle->IsDoomed()) {
+ return NS_OK;
+ }
+
+ CacheIndex::InitEntry(mHandle->Hash(), mOriginAttrsHash, mAnonymous,
+ mPinning);
+
+ // We cannot set the filesize before we init the entry. If we're opening
+ // an existing entry file, frecency will be set after parsing the entry
+ // file, but we must set the filesize here since nobody is going to set it
+ // if there is no write to the file.
+ uint32_t sizeInK = mHandle->FileSizeInK();
+ CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
+ nullptr, &sizeInK);
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ OriginAttrsHash mOriginAttrsHash;
+ bool mAnonymous;
+ bool mPinning;
+};
+
+class UpdateIndexEntryEvent : public Runnable {
+ public:
+ UpdateIndexEntryEvent(CacheFileHandle* aHandle, const uint32_t* aFrecency,
+ const bool* aHasAltData, const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType)
+ : Runnable("net::UpdateIndexEntryEvent"),
+ mHandle(aHandle),
+ mHasFrecency(false),
+ mHasHasAltData(false),
+ mHasOnStartTime(false),
+ mHasOnStopTime(false),
+ mHasContentType(false),
+ mFrecency(0),
+ mHasAltData(false),
+ mOnStartTime(0),
+ mOnStopTime(0),
+ mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN) {
+ if (aFrecency) {
+ mHasFrecency = true;
+ mFrecency = *aFrecency;
+ }
+ if (aHasAltData) {
+ mHasHasAltData = true;
+ mHasAltData = *aHasAltData;
+ }
+ if (aOnStartTime) {
+ mHasOnStartTime = true;
+ mOnStartTime = *aOnStartTime;
+ }
+ if (aOnStopTime) {
+ mHasOnStopTime = true;
+ mOnStopTime = *aOnStopTime;
+ }
+ if (aContentType) {
+ mHasContentType = true;
+ mContentType = *aContentType;
+ }
+ }
+
+ protected:
+ ~UpdateIndexEntryEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ if (mHandle->IsClosed() || mHandle->IsDoomed()) {
+ return NS_OK;
+ }
+
+ CacheIndex::UpdateEntry(mHandle->Hash(),
+ mHasFrecency ? &mFrecency : nullptr,
+ mHasHasAltData ? &mHasAltData : nullptr,
+ mHasOnStartTime ? &mOnStartTime : nullptr,
+ mHasOnStopTime ? &mOnStopTime : nullptr,
+ mHasContentType ? &mContentType : nullptr, nullptr);
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+
+ bool mHasFrecency;
+ bool mHasHasAltData;
+ bool mHasOnStartTime;
+ bool mHasOnStopTime;
+ bool mHasContentType;
+
+ uint32_t mFrecency;
+ bool mHasAltData;
+ uint16_t mOnStartTime;
+ uint16_t mOnStopTime;
+ uint8_t mContentType;
+};
+
+class MetadataWriteScheduleEvent : public Runnable {
+ public:
+ enum EMode { SCHEDULE, UNSCHEDULE, SHUTDOWN } mMode;
+
+ RefPtr<CacheFile> mFile;
+ RefPtr<CacheFileIOManager> mIOMan;
+
+ MetadataWriteScheduleEvent(CacheFileIOManager* aManager, CacheFile* aFile,
+ EMode aMode)
+ : Runnable("net::MetadataWriteScheduleEvent"),
+ mMode(aMode),
+ mFile(aFile),
+ mIOMan(aManager) {}
+
+ virtual ~MetadataWriteScheduleEvent() = default;
+
+ NS_IMETHOD Run() override {
+ RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
+ if (!ioMan) {
+ NS_WARNING(
+ "CacheFileIOManager already gone in "
+ "MetadataWriteScheduleEvent::Run()");
+ return NS_OK;
+ }
+
+ switch (mMode) {
+ case SCHEDULE:
+ ioMan->ScheduleMetadataWriteInternal(mFile);
+ break;
+ case UNSCHEDULE:
+ ioMan->UnscheduleMetadataWriteInternal(mFile);
+ break;
+ case SHUTDOWN:
+ ioMan->ShutdownMetadataWriteSchedulingInternal();
+ break;
+ }
+ return NS_OK;
+ }
+};
+
+StaticRefPtr<CacheFileIOManager> CacheFileIOManager::gInstance;
+
+NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback, nsINamed)
+
+CacheFileIOManager::CacheFileIOManager()
+
+{
+ LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
+ MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
+}
+
+CacheFileIOManager::~CacheFileIOManager() {
+ LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
+}
+
+// static
+nsresult CacheFileIOManager::Init() {
+ LOG(("CacheFileIOManager::Init()"));
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gInstance) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ RefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
+
+ nsresult rv = ioMan->InitInternal();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gInstance = std::move(ioMan);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::InitInternal() {
+ nsresult rv;
+
+ mIOThread = new CacheIOThread();
+
+ rv = mIOThread->Init();
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStartTime = TimeStamp::NowLoRes();
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::Shutdown() {
+ LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance.get()));
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gInstance) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2> shutdownTimer;
+
+ CacheIndex::PreShutdown();
+
+ ShutdownMetadataWriteScheduling();
+
+ RefPtr<ShutdownEvent> ev = new ShutdownEvent();
+ ev->PostAndWait();
+
+ MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
+ MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
+
+ if (gInstance->mIOThread) {
+ gInstance->mIOThread->Shutdown();
+ }
+
+ CacheIndex::Shutdown();
+
+ if (CacheObserver::ClearCacheOnShutdown()) {
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE>
+ totalTimer;
+ gInstance->SyncRemoveAllCacheFiles();
+ }
+
+ gInstance = nullptr;
+
+ return NS_OK;
+}
+
+void CacheFileIOManager::ShutdownInternal() {
+ LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this));
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ // No new handles can be created after this flag is set
+ mShuttingDown = true;
+
+ if (mTrashTimer) {
+ mTrashTimer->Cancel();
+ mTrashTimer = nullptr;
+ }
+
+ // close all handles and delete all associated files
+ nsTArray<RefPtr<CacheFileHandle>> handles;
+ mHandles.GetAllHandles(&handles);
+ handles.AppendElements(mSpecialHandles);
+
+ for (uint32_t i = 0; i < handles.Length(); i++) {
+ CacheFileHandle* h = handles[i];
+ h->mClosed = true;
+
+ h->Log();
+
+ // Close completely written files.
+ MaybeReleaseNSPRHandleInternal(h);
+ // Don't bother removing invalid and/or doomed files to improve
+ // shutdown perfomrance.
+ // Doomed files are already in the doomed directory from which
+ // we never reuse files and delete the dir on next session startup.
+ // Invalid files don't have metadata and thus won't load anyway
+ // (hashes won't match).
+
+ if (!h->IsSpecialFile() && !h->mIsDoomed && !h->mFileExists) {
+ CacheIndex::RemoveEntry(h->Hash());
+ }
+
+ // Remove the handle from mHandles/mSpecialHandles
+ if (h->IsSpecialFile()) {
+ mSpecialHandles.RemoveElement(h);
+ } else {
+ mHandles.RemoveHandle(h);
+ }
+
+ // Pointer to the hash is no longer valid once the last handle with the
+ // given hash is released. Null out the pointer so that we crash if there
+ // is a bug in this code and we dereference the pointer after this point.
+ if (!h->IsSpecialFile()) {
+ h->mHash = nullptr;
+ }
+ }
+
+ // Assert the table is empty. When we are here, no new handles can be added
+ // and handles will no longer remove them self from this table and we don't
+ // want to keep invalid handles here. Also, there is no lookup after this
+ // point to happen.
+ MOZ_ASSERT(mHandles.HandleCount() == 0);
+
+ // Release trash directory enumerator
+ if (mTrashDirEnumerator) {
+ mTrashDirEnumerator->Close();
+ mTrashDirEnumerator = nullptr;
+ }
+
+ if (mContextEvictor) {
+ mContextEvictor->Shutdown();
+ mContextEvictor = nullptr;
+ }
+}
+
+// static
+nsresult CacheFileIOManager::OnProfile() {
+ LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance.get()));
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan) {
+ // CacheFileIOManager::Init() failed, probably could not create the IO
+ // thread, just go with it...
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> directory;
+
+ CacheObserver::ParentDirOverride(getter_AddRefs(directory));
+
+#if defined(MOZ_WIDGET_ANDROID)
+ nsCOMPtr<nsIFile> profilelessDirectory;
+ char* cachePath = getenv("CACHE_DIRECTORY");
+ if (!directory && cachePath && *cachePath) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), true,
+ getter_AddRefs(directory));
+ if (NS_SUCCEEDED(rv)) {
+ // Save this directory as the profileless path.
+ rv = directory->Clone(getter_AddRefs(profilelessDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add profile leaf name to the directory name to distinguish
+ // multiple profiles Fennec supports.
+ nsCOMPtr<nsIFile> profD;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profD));
+
+ nsAutoCString leafName;
+ if (NS_SUCCEEDED(rv)) {
+ rv = profD->GetNativeLeafName(leafName);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = directory->AppendNative(leafName);
+ }
+ if (NS_FAILED(rv)) {
+ directory = nullptr;
+ }
+ }
+ }
+#endif
+
+ if (!directory) {
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ }
+
+ if (!directory) {
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(directory));
+ }
+
+ if (directory) {
+ rv = directory->Append(u"cache2"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // All functions return a clone.
+ ioMan->mCacheDirectory.swap(directory);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ if (profilelessDirectory) {
+ rv = profilelessDirectory->Append(u"cache2"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory);
+#endif
+
+ if (ioMan->mCacheDirectory) {
+ CacheIndex::Init(ioMan->mCacheDirectory);
+ }
+
+ return NS_OK;
+}
+
+static bool inBackgroundTask() {
+ MOZ_ASSERT(NS_IsMainThread(), "backgroundtasks are main thread only");
+#if defined(MOZ_BACKGROUNDTASKS)
+ nsCOMPtr<nsIBackgroundTasks> backgroundTaskService =
+ do_GetService("@mozilla.org/backgroundtasks;1");
+ if (!backgroundTaskService) {
+ return false;
+ }
+ bool isBackgroundTask = false;
+ backgroundTaskService->GetIsBackgroundTaskMode(&isBackgroundTask);
+ return isBackgroundTask;
+#else
+ return false;
+#endif
+}
+
+// static
+nsresult CacheFileIOManager::OnDelayedStartupFinished() {
+ // If we don't clear the cache at shutdown, or we don't use a
+ // background task then there's no need to dispatch a cleanup task
+ // at startup
+ if (!CacheObserver::ClearCacheOnShutdown()) {
+ return NS_OK;
+ }
+ if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
+ return NS_OK;
+ }
+
+ // If this is a background task already, there's no need to
+ // dispatch another one.
+ if (inBackgroundTask()) {
+ return NS_OK;
+ }
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ nsCOMPtr<nsIEventTarget> target = IOTarget();
+ if (NS_WARN_IF(!ioMan || !target)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return target->Dispatch(
+ NS_NewRunnableFunction("CacheFileIOManager::OnDelayedStartupFinished",
+ [ioMan = std::move(ioMan)] {
+ ioMan->DispatchPurgeTask(""_ns, "0"_ns,
+ kPurgeExtension);
+ }),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+// static
+nsresult CacheFileIOManager::OnIdleDaily() {
+ // In case the background task process fails for some reason (bug 1848542)
+ // We will remove the directories in the main process on a daily idle.
+ if (!CacheObserver::ClearCacheOnShutdown()) {
+ return NS_OK;
+ }
+ if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
+ return NS_OK;
+ }
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ nsCOMPtr<nsIFile> parentDir;
+ nsresult rv = ioMan->mCacheDirectory->GetParent(getter_AddRefs(parentDir));
+ if (NS_FAILED(rv) || !parentDir) {
+ return NS_OK;
+ }
+
+ NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "CacheFileIOManager::CheckLeftoverFolders",
+ [dir = std::move(parentDir)] {
+ nsCOMPtr<nsIDirectoryEnumerator> directories;
+ nsresult rv = dir->GetDirectoryEntries(getter_AddRefs(directories));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ bool hasMoreElements = false;
+ while (
+ NS_SUCCEEDED(directories->HasMoreElements(&hasMoreElements)) &&
+ hasMoreElements) {
+ nsCOMPtr<nsIFile> subdir;
+ rv = directories->GetNextFile(getter_AddRefs(subdir));
+ if (NS_FAILED(rv) || !subdir) {
+ break;
+ }
+ nsAutoCString leafName;
+ rv = subdir->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ if (leafName.Find(kPurgeExtension) != kNotFound) {
+ mozilla::glean::networking::residual_cache_folder_count.Add(1);
+ rv = subdir->Remove(true);
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::glean::networking::residual_cache_folder_removal
+ .Get("success"_ns)
+ .Add(1);
+ } else {
+ mozilla::glean::networking::residual_cache_folder_removal
+ .Get("failure"_ns)
+ .Add(1);
+ }
+ }
+ }
+
+ return;
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ return NS_OK;
+}
+
+// static
+already_AddRefed<nsIEventTarget> CacheFileIOManager::IOTarget() {
+ nsCOMPtr<nsIEventTarget> target;
+ if (gInstance && gInstance->mIOThread) {
+ target = gInstance->mIOThread->Target();
+ }
+
+ return target.forget();
+}
+
+// static
+already_AddRefed<CacheIOThread> CacheFileIOManager::IOThread() {
+ RefPtr<CacheIOThread> thread;
+ if (gInstance) {
+ thread = gInstance->mIOThread;
+ }
+
+ return thread.forget();
+}
+
+// static
+bool CacheFileIOManager::IsOnIOThread() {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (ioMan && ioMan->mIOThread) {
+ return ioMan->mIOThread->IsCurrentThread();
+ }
+
+ return false;
+}
+
+// static
+bool CacheFileIOManager::IsOnIOThreadOrCeased() {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (ioMan && ioMan->mIOThread) {
+ return ioMan->mIOThread->IsCurrentThread();
+ }
+
+ // Ceased...
+ return true;
+}
+
+// static
+bool CacheFileIOManager::IsShutdown() {
+ if (!gInstance) {
+ return true;
+ }
+ return gInstance->mShuttingDown;
+}
+
+// static
+nsresult CacheFileIOManager::ScheduleMetadataWrite(CacheFile* aFile) {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
+
+ RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
+ ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE);
+ nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+nsresult CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile* aFile) {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ nsresult rv;
+
+ if (!mMetadataWritesTimer) {
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mMetadataWritesTimer), this,
+ kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mScheduledMetadataWrites.IndexOf(aFile) !=
+ nsTArray<RefPtr<mozilla::net::CacheFile>>::NoIndex) {
+ return NS_OK;
+ }
+
+ mScheduledMetadataWrites.AppendElement(aFile);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::UnscheduleMetadataWrite(CacheFile* aFile) {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
+
+ RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
+ ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE);
+ nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile* aFile) {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ mScheduledMetadataWrites.RemoveElement(aFile);
+
+ if (mScheduledMetadataWrites.Length() == 0 && mMetadataWritesTimer) {
+ mMetadataWritesTimer->Cancel();
+ mMetadataWritesTimer = nullptr;
+ }
+}
+
+// static
+nsresult CacheFileIOManager::ShutdownMetadataWriteScheduling() {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
+
+ RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
+ ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN);
+ nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ CacheFile* file = files[i];
+ file->WriteMetadataIfNeeded();
+ }
+
+ if (mMetadataWritesTimer) {
+ mMetadataWritesTimer->Cancel();
+ mMetadataWritesTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+CacheFileIOManager::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+ MOZ_ASSERT(mMetadataWritesTimer == aTimer);
+
+ mMetadataWritesTimer = nullptr;
+
+ nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ CacheFile* file = files[i];
+ file->WriteMetadataIfNeeded();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileIOManager::GetName(nsACString& aName) {
+ aName.AssignLiteral("CacheFileIOManager");
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::OpenFile(const nsACString& aKey, uint32_t aFlags,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
+ PromiseFlatCString(aKey).get(), aFlags, aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ bool priority = aFlags & CacheFileIOManager::PRIORITY;
+ RefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
+ rv = ioMan->mIOThread->Dispatch(
+ ev, priority ? CacheIOThread::OPEN_PRIORITY : CacheIOThread::OPEN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash* aHash,
+ const nsACString& aKey,
+ uint32_t aFlags,
+ CacheFileHandle** _retval) {
+ LOG(
+ ("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
+ "key=%s, flags=%d]",
+ LOGSHA1(aHash), PromiseFlatCString(aKey).get(), aFlags));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ CacheIOThread::Cancelable cancelable(
+ true /* never called for special handles */);
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ CacheFileHandle::PinningStatus pinning =
+ aFlags & PINNED ? CacheFileHandle::PinningStatus::PINNED
+ : CacheFileHandle::PinningStatus::NON_PINNED;
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(aHash, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CacheFileHandle> handle;
+ mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+ if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
+ if (handle) {
+ rv = DoomFileInternal(handle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ handle = nullptr;
+ }
+
+ handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ CacheIndex::RemoveEntry(aHash);
+
+ LOG(
+ ("CacheFileIOManager::OpenFileInternal() - Removing old file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove old entry from the disk");
+ LOG(
+ ("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
+ ". [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ CacheIndex::AddEntry(aHash);
+ handle->mFile.swap(file);
+ handle->mFileSize = 0;
+ }
+
+ if (handle) {
+ handle.swap(*_retval);
+ return NS_OK;
+ }
+
+ bool exists, evictedAsPinned = false, evictedAsNonPinned = false;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists && mContextEvictor) {
+ if (mContextEvictor->ContextsCount() == 0) {
+ mContextEvictor = nullptr;
+ } else {
+ mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned,
+ &evictedAsNonPinned);
+ }
+ }
+
+ if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (exists) {
+ // For existing files we determine the pinning status later, after the
+ // metadata gets parsed.
+ pinning = CacheFileHandle::PinningStatus::UNKNOWN;
+ }
+
+ handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);
+ if (exists) {
+ // If this file has been found evicted through the context file evictor
+ // above for any of pinned or non-pinned state, these calls ensure we doom
+ // the handle ASAP we know the real pinning state after metadta has been
+ // parsed. DoomFileInternal on the |handle| doesn't doom right now, since
+ // the pinning state is unknown and we pass down a pinning restriction.
+ if (evictedAsPinned) {
+ rv = DoomFileInternal(handle, DOOM_WHEN_PINNED);
+ MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+ }
+ if (evictedAsNonPinned) {
+ rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED);
+ MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+ }
+
+ int64_t fileSize = -1;
+ rv = file->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ handle->mFileSize = fileSize;
+ handle->mFileExists = true;
+
+ CacheIndex::EnsureEntryExists(aHash);
+ } else {
+ handle->mFileSize = 0;
+
+ CacheIndex::AddEntry(aHash);
+ }
+
+ handle->mFile.swap(file);
+ handle.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OpenSpecialFileInternal(
+ const nsACString& aKey, uint32_t aFlags, CacheFileHandle** _retval) {
+ LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",
+ PromiseFlatCString(aKey).get(), aFlags));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetSpecialFile(aKey, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CacheFileHandle> handle;
+ for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) {
+ if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) {
+ handle = mSpecialHandles[i];
+ break;
+ }
+ }
+
+ if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
+ if (handle) {
+ rv = DoomFileInternal(handle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ handle = nullptr;
+ }
+
+ handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
+ CacheFileHandle::PinningStatus::NON_PINNED);
+ mSpecialHandles.AppendElement(handle);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ LOG(
+ ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove old entry from the disk");
+ LOG(
+ ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file "
+ "failed. [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ handle->mFile.swap(file);
+ handle->mFileSize = 0;
+ }
+
+ if (handle) {
+ handle.swap(*_retval);
+ return NS_OK;
+ }
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
+ CacheFileHandle::PinningStatus::NON_PINNED);
+ mSpecialHandles.AppendElement(handle);
+
+ if (exists) {
+ int64_t fileSize = -1;
+ rv = file->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ handle->mFileSize = fileSize;
+ handle->mFileExists = true;
+ } else {
+ handle->mFileSize = 0;
+ }
+
+ handle->mFile.swap(file);
+ handle.swap(*_retval);
+ return NS_OK;
+}
+
+void CacheFileIOManager::CloseHandleInternal(CacheFileHandle* aHandle) {
+ nsresult rv;
+ LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle));
+
+ MOZ_ASSERT(!aHandle->IsClosed());
+
+ aHandle->Log();
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ // Maybe close file handle (can be legally bypassed after shutdown)
+ rv = MaybeReleaseNSPRHandleInternal(aHandle);
+
+ // Delete the file if the entry was doomed or invalid and
+ // filedesc properly closed
+ if ((aHandle->mIsDoomed || aHandle->mInvalid) && aHandle->mFileExists &&
+ NS_SUCCEEDED(rv)) {
+ LOG(
+ ("CacheFileIOManager::CloseHandleInternal() - Removing file from "
+ "disk"));
+
+ rv = aHandle->mFile->Remove(false);
+ if (NS_SUCCEEDED(rv)) {
+ aHandle->mFileExists = false;
+ } else {
+ LOG((" failed to remove the file [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed &&
+ (aHandle->mInvalid || !aHandle->mFileExists)) {
+ CacheIndex::RemoveEntry(aHandle->Hash());
+ }
+
+ // Don't remove handles after shutdown
+ if (!mShuttingDown) {
+ if (aHandle->IsSpecialFile()) {
+ mSpecialHandles.RemoveElement(aHandle);
+ } else {
+ mHandles.RemoveHandle(aHandle);
+ }
+ }
+}
+
+// static
+nsresult CacheFileIOManager::Read(CacheFileHandle* aHandle, int64_t aOffset,
+ char* aBuf, int32_t aCount,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::Read() [handle=%p, offset=%" PRId64 ", count=%d, "
+ "listener=%p]",
+ aHandle, aOffset, aCount, aCallback));
+
+ if (CacheObserver::ShuttingDown()) {
+ LOG((" no reads after shutdown"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<ReadEvent> ev =
+ new ReadEvent(aHandle, aOffset, aBuf, aCount, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+ ? CacheIOThread::READ_PRIORITY
+ : CacheIOThread::READ);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::ReadInternal(CacheFileHandle* aHandle,
+ int64_t aOffset, char* aBuf,
+ int32_t aCount) {
+ LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%" PRId64
+ ", count=%d]",
+ aHandle, aOffset, aCount));
+
+ nsresult rv;
+
+ if (CacheObserver::ShuttingDown()) {
+ LOG((" no reads after shutdown"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!aHandle->mFileExists) {
+ NS_WARNING("Trying to read from non-existent file");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (!aHandle->mFD) {
+ rv = OpenNSPRHandle(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NSPRHandleUsed(aHandle);
+ }
+
+ // Check again, OpenNSPRHandle could figure out the file was gone.
+ if (!aHandle->mFileExists) {
+ NS_WARNING("Trying to read from non-existent file");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+ if (offset == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
+ if (bytesRead != aCount) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::Write(CacheFileHandle* aHandle, int64_t aOffset,
+ const char* aBuf, int32_t aCount,
+ bool aValidate, bool aTruncate,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::Write() [handle=%p, offset=%" PRId64 ", count=%d, "
+ "validate=%d, truncate=%d, listener=%p]",
+ aHandle, aOffset, aCount, aValidate, aTruncate, aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
+ if (!aCallback) {
+ // When no callback is provided, CacheFileIOManager is responsible for
+ // releasing the buffer. We must release it even in case of failure.
+ free(const_cast<char*>(aBuf));
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
+ aValidate, aTruncate, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+static nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) {
+#if defined(XP_UNIX)
+ if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET);
+ if (cnt == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+#else
+ MOZ_ASSERT(false, "Not implemented!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::WriteInternal(CacheFileHandle* aHandle,
+ int64_t aOffset, const char* aBuf,
+ int32_t aCount, bool aValidate,
+ bool aTruncate) {
+ LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%" PRId64
+ ", count=%d, "
+ "validate=%d, truncate=%d]",
+ aHandle, aOffset, aCount, aValidate, aTruncate));
+
+ nsresult rv;
+
+ if (aHandle->mKilled) {
+ LOG((" handle already killed, nothing written"));
+ return NS_OK;
+ }
+
+ if (CacheObserver::ShuttingDown() && (!aValidate || !aHandle->mFD)) {
+ aHandle->mKilled = true;
+ LOG((" killing the handle, nothing written"));
+ return NS_OK;
+ }
+
+ if (CacheObserver::IsPastShutdownIOLag()) {
+ LOG((" past the shutdown I/O lag, nothing written"));
+ // Pretend the write has succeeded, otherwise upper layers will doom
+ // the file and we end up with I/O anyway.
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (!aHandle->mFileExists) {
+ rv = CreateFile(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aHandle->mFD) {
+ rv = OpenNSPRHandle(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NSPRHandleUsed(aHandle);
+ }
+
+ // Check again, OpenNSPRHandle could figure out the file was gone.
+ if (!aHandle->mFileExists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // When this operation would increase cache size, check whether the cache size
+ // reached the hard limit and whether it would cause critical low disk space.
+ if (aHandle->mFileSize < aOffset + aCount) {
+ if (mOverLimitEvicting && mCacheSizeOnHardLimit) {
+ LOG(
+ ("CacheFileIOManager::WriteInternal() - failing because cache size "
+ "reached hard limit!"));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+ LOG(
+ ("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() "
+ "failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
+ if (freeSpace - aOffset - aCount + aHandle->mFileSize < limit) {
+ LOG(
+ ("CacheFileIOManager::WriteInternal() - Low free space, refusing "
+ "to write! [freeSpace=%" PRId64 "kB, limit=%ukB]",
+ freeSpace, limit));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+ }
+ }
+
+ // Write invalidates the entry by default
+ aHandle->mInvalid = true;
+
+ int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+ if (offset == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);
+
+ if (bytesWritten != -1) {
+ uint32_t oldSizeInK = aHandle->FileSizeInK();
+ int64_t writeEnd = aOffset + bytesWritten;
+
+ if (aTruncate) {
+ rv = TruncFile(aHandle->mFD, writeEnd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFileSize = writeEnd;
+ } else {
+ if (aHandle->mFileSize < writeEnd) {
+ aHandle->mFileSize = writeEnd;
+ }
+ }
+
+ uint32_t newSizeInK = aHandle->FileSizeInK();
+
+ if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
+ !aHandle->IsSpecialFile()) {
+ CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr,
+ nullptr, nullptr, &newSizeInK);
+
+ if (oldSizeInK < newSizeInK) {
+ EvictIfOverLimitInternal();
+ }
+ }
+
+ CacheIndex::UpdateTotalBytesWritten(bytesWritten);
+ }
+
+ if (bytesWritten != aCount) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Write was successful and this write validates the entry (i.e. metadata)
+ if (aValidate) {
+ aHandle->mInvalid = false;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::DoomFile(CacheFileHandle* aHandle,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", aHandle,
+ aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+ ? CacheIOThread::OPEN_PRIORITY
+ : CacheIOThread::OPEN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::DoomFileInternal(
+ CacheFileHandle* aHandle, PinningDoomRestriction aPinningDoomRestriction) {
+ LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
+ aHandle->Log();
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ nsresult rv;
+
+ if (aHandle->IsDoomed()) {
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (aPinningDoomRestriction > NO_RESTRICTION) {
+ switch (aHandle->mPinning) {
+ case CacheFileHandle::PinningStatus::NON_PINNED:
+ if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) {
+ LOG((" not dooming, it's a non-pinned handle"));
+ return NS_OK;
+ }
+ // Doom now
+ break;
+
+ case CacheFileHandle::PinningStatus::PINNED:
+ if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) {
+ LOG((" not dooming, it's a pinned handle"));
+ return NS_OK;
+ }
+ // Doom now
+ break;
+
+ case CacheFileHandle::PinningStatus::UNKNOWN:
+ if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) {
+ LOG((" doom when non-pinned set"));
+ aHandle->mDoomWhenFoundNonPinned = true;
+ } else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) {
+ LOG((" doom when pinned set"));
+ aHandle->mDoomWhenFoundPinned = true;
+ }
+
+ LOG((" pinning status not known, deferring doom decision"));
+ return NS_OK;
+ }
+ }
+
+ if (aHandle->mFileExists) {
+ // we need to move the current file to the doomed directory
+ rv = MaybeReleaseNSPRHandleInternal(aHandle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // find unused filename
+ nsCOMPtr<nsIFile> file;
+ rv = GetDoomedFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> parentDir;
+ rv = file->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString leafName;
+ rv = file->GetNativeLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aHandle->mFile->MoveToNative(parentDir, leafName);
+ if (NS_ERROR_FILE_NOT_FOUND == rv) {
+ LOG((" file already removed under our hands"));
+ aHandle->mFileExists = false;
+ rv = NS_OK;
+ } else {
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHandle->mFile.swap(file);
+ }
+ }
+
+ if (!aHandle->IsSpecialFile()) {
+ CacheIndex::RemoveEntry(aHandle->Hash());
+ }
+
+ aHandle->mIsDoomed = true;
+
+ if (!aHandle->IsSpecialFile()) {
+ RefPtr<CacheStorageService> storageService = CacheStorageService::Self();
+ if (storageService) {
+ nsAutoCString idExtension, url;
+ nsCOMPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url);
+ MOZ_ASSERT(info);
+ if (info) {
+ storageService->CacheFileDoomed(info, idExtension, url);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::DoomFileByKey(const nsACString& aKey,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
+ PromiseFlatCString(aKey).get(), aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
+ rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash* aHash) {
+ LOG((
+ "CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ nsresult rv;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mCacheDirectory) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ // Find active handle
+ RefPtr<CacheFileHandle> handle;
+ mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+ if (handle) {
+ handle->Log();
+
+ return DoomFileInternal(handle);
+ }
+
+ CacheIOThread::Cancelable cancelable(true);
+
+ // There is no handle for this file, delete the file if exists
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(aHash, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG(
+ ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove old entry from the disk");
+ LOG(
+ ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+
+ CacheIndex::RemoveEntry(aHash);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle* aHandle) {
+ LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::MaybeReleaseNSPRHandleInternal(
+ CacheFileHandle* aHandle, bool aIgnoreShutdownLag) {
+ LOG(
+ ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() [handle=%p, "
+ "ignore shutdown=%d]",
+ aHandle, aIgnoreShutdownLag));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ if (aHandle->mFD) {
+ DebugOnly<bool> found{};
+ found = mHandlesByLastUsed.RemoveElement(aHandle);
+ MOZ_ASSERT(found);
+ }
+
+ PRFileDesc* fd = aHandle->mFD;
+ aHandle->mFD = nullptr;
+
+ // Leak invalid (w/o metadata) and doomed handles immediately after shutdown.
+ // Leak other handles when past the shutdown time maximum lag.
+ if (
+#ifndef DEBUG
+ ((aHandle->mInvalid || aHandle->mIsDoomed) &&
+ MOZ_UNLIKELY(CacheObserver::ShuttingDown())) ||
+#endif
+ MOZ_UNLIKELY(!aIgnoreShutdownLag &&
+ CacheObserver::IsPastShutdownIOLag())) {
+ // Don't bother closing this file. Return a failure code from here will
+ // cause any following IO operation on the file (mainly removal) to be
+ // bypassed, which is what we want.
+ // For mInvalid == true the entry will never be used, since it doesn't
+ // have correct metadata, thus we don't need to worry about removing it.
+ // For mIsDoomed == true the file is already in the doomed sub-dir and
+ // will be removed on next session start.
+ LOG((" past the shutdown I/O lag, leaking file handle"));
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (!fd) {
+ // The filedesc has already been closed before, just let go.
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ PRStatus status = PR_Close(fd);
+ if (status != PR_SUCCESS) {
+ LOG(
+ ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() "
+ "failed to close [handle=%p, status=%u]",
+ aHandle, status));
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() DONE"));
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::TruncateSeekSetEOF(
+ CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos,
+ CacheFileIOListener* aCallback) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, "
+ "truncatePos=%" PRId64 ", "
+ "EOFPos=%" PRId64 ", listener=%p]",
+ aHandle, aTruncatePos, aEOFPos, aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<TruncateSeekSetEOFEvent> ev =
+ new TruncateSeekSetEOFEvent(aHandle, aTruncatePos, aEOFPos, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// static
+void CacheFileIOManager::GetCacheDirectory(nsIFile** result) {
+ *result = nullptr;
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan || !ioMan->mCacheDirectory) {
+ return;
+ }
+
+ ioMan->mCacheDirectory->Clone(result);
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+
+// static
+void CacheFileIOManager::GetProfilelessCacheDirectory(nsIFile** result) {
+ *result = nullptr;
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan || !ioMan->mCacheProfilelessDirectory) {
+ return;
+ }
+
+ ioMan->mCacheProfilelessDirectory->Clone(result);
+}
+
+#endif
+
+// static
+nsresult CacheFileIOManager::GetEntryInfo(
+ const SHA1Sum::Hash* aHash,
+ CacheStorageService::EntryInfoCallback* aCallback) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsAutoCString enhanceId;
+ nsAutoCString uriSpec;
+
+ RefPtr<CacheFileHandle> handle;
+ ioMan->mHandles.GetHandle(aHash, getter_AddRefs(handle));
+ if (handle) {
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec);
+
+ MOZ_ASSERT(info);
+ if (!info) {
+ return NS_OK; // ignore
+ }
+
+ RefPtr<CacheStorageService> service = CacheStorageService::Self();
+ if (!service) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Invokes OnCacheEntryInfo when an existing entry is found
+ if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) {
+ return NS_OK;
+ }
+
+ // When we are here, there is no existing entry and we need
+ // to synchrnously load metadata from a disk file.
+ }
+
+ // Locate the actual file
+ nsCOMPtr<nsIFile> file;
+ ioMan->GetFile(aHash, getter_AddRefs(file));
+
+ // Read metadata from the file synchronously
+ RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
+ rv = metadata->SyncReadMetadata(file);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // Now get the context + enhance id + URL from the key.
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(metadata->GetKey(), &enhanceId, &uriSpec);
+ MOZ_ASSERT(info);
+ if (!info) {
+ return NS_OK;
+ }
+
+ // Pick all data to pass to the callback.
+ int64_t dataSize = metadata->Offset();
+ int64_t altDataSize = 0;
+ uint32_t fetchCount = metadata->GetFetchCount();
+ uint32_t expirationTime = metadata->GetExpirationTime();
+ uint32_t lastModified = metadata->GetLastModified();
+
+ const char* altDataElement =
+ metadata->GetElement(CacheFileUtils::kAltDataKey);
+ if (altDataElement) {
+ int64_t altDataOffset = std::numeric_limits<int64_t>::max();
+ if (NS_SUCCEEDED(CacheFileUtils::ParseAlternativeDataInfo(
+ altDataElement, &altDataOffset, nullptr)) &&
+ altDataOffset < dataSize) {
+ dataSize = altDataOffset;
+ altDataSize = metadata->Offset() - altDataOffset;
+ } else {
+ LOG(("CacheFileIOManager::GetEntryInfo() invalid alternative data info"));
+ return NS_OK;
+ }
+ }
+
+ // Call directly on the callback.
+ aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount,
+ lastModified, expirationTime, metadata->Pinned(),
+ info);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::TruncateSeekSetEOFInternal(
+ CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, "
+ "truncatePos=%" PRId64 ", EOFPos=%" PRId64 "]",
+ aHandle, aTruncatePos, aEOFPos));
+
+ nsresult rv;
+
+ if (aHandle->mKilled) {
+ LOG((" handle already killed, file not truncated"));
+ return NS_OK;
+ }
+
+ if (CacheObserver::ShuttingDown() && !aHandle->mFD) {
+ aHandle->mKilled = true;
+ LOG((" killing the handle, file not truncated"));
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (!aHandle->mFileExists) {
+ rv = CreateFile(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aHandle->mFD) {
+ rv = OpenNSPRHandle(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NSPRHandleUsed(aHandle);
+ }
+
+ // Check again, OpenNSPRHandle could figure out the file was gone.
+ if (!aHandle->mFileExists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // When this operation would increase cache size, check whether the cache size
+ // reached the hard limit and whether it would cause critical low disk space.
+ if (aHandle->mFileSize < aEOFPos) {
+ if (mOverLimitEvicting && mCacheSizeOnHardLimit) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() - failing because "
+ "cache size reached hard limit!"));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
+ if (freeSpace - aEOFPos + aHandle->mFileSize < limit) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() - Low free space"
+ ", refusing to write! [freeSpace=%" PRId64 "kB, limit=%ukB]",
+ freeSpace, limit));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+ }
+ }
+
+ // This operation always invalidates the entry
+ aHandle->mInvalid = true;
+
+ rv = TruncFile(aHandle->mFD, aTruncatePos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aTruncatePos != aEOFPos) {
+ rv = TruncFile(aHandle->mFD, aEOFPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t oldSizeInK = aHandle->FileSizeInK();
+ aHandle->mFileSize = aEOFPos;
+ uint32_t newSizeInK = aHandle->FileSizeInK();
+
+ if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
+ !aHandle->IsSpecialFile()) {
+ CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
+ nullptr, &newSizeInK);
+
+ if (oldSizeInK < newSizeInK) {
+ EvictIfOverLimitInternal();
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::RenameFile(CacheFileHandle* aHandle,
+ const nsACString& aNewName,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",
+ aHandle, PromiseFlatCString(aNewName).get(), aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!aHandle->IsSpecialFile()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<RenameFileEvent> ev =
+ new RenameFileEvent(aHandle, aNewName, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::RenameFileInternal(CacheFileHandle* aHandle,
+ const nsACString& aNewName) {
+ LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]",
+ aHandle, PromiseFlatCString(aNewName).get()));
+
+ nsresult rv;
+
+ MOZ_ASSERT(aHandle->IsSpecialFile());
+
+ if (aHandle->IsDoomed()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Doom old handle if it exists and is not doomed
+ for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) {
+ if (!mSpecialHandles[i]->IsDoomed() &&
+ mSpecialHandles[i]->Key() == aNewName) {
+ MOZ_ASSERT(aHandle != mSpecialHandles[i]);
+ rv = DoomFileInternal(mSpecialHandles[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetSpecialFile(aNewName, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ LOG(
+ ("CacheFileIOManager::RenameFileInternal() - Removing old file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove file from the disk");
+ LOG(
+ ("CacheFileIOManager::RenameFileInternal() - Removing old file failed"
+ ". [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ if (!aHandle->FileExists()) {
+ aHandle->mKey = aNewName;
+ return NS_OK;
+ }
+
+ rv = MaybeReleaseNSPRHandleInternal(aHandle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aHandle->mFile->MoveToNative(nullptr, aNewName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mKey = aNewName;
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::EvictIfOverLimit() {
+ LOG(("CacheFileIOManager::EvictIfOverLimit()"));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::EvictIfOverLimitInternal",
+ ioMan, &CacheFileIOManager::EvictIfOverLimitInternal);
+
+ rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::EvictIfOverLimitInternal() {
+ LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mOverLimitEvicting) {
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already "
+ "running."));
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(true);
+
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+
+ // Do not change smart size.
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ UpdateSmartCacheSize(freeSpace);
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cacheLimit = CacheObserver::DiskCacheCapacity();
+ uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
+
+ if (cacheUsage <= cacheLimit &&
+ (freeSpace == -1 || freeSpace >= freeSpaceLimit)) {
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size and free "
+ "space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
+ "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
+ cacheUsage, cacheLimit, freeSpace, freeSpaceLimit));
+ return NS_OK;
+ }
+
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded "
+ "limit. Starting overlimit eviction. [cacheSize=%ukB, limit=%ukB]",
+ cacheUsage, cacheLimit));
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::OverLimitEvictionInternal",
+ this, &CacheFileIOManager::OverLimitEvictionInternal);
+
+ rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOverLimitEvicting = true;
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OverLimitEvictionInternal() {
+ LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
+ // here and set it to true again once we dispatch another event that will
+ // continue with the eviction. The reason why we do so is that we can fail
+ // early anywhere in this method and the variable will contain a correct
+ // value. Otherwise we would need to set it to false on every failing place.
+ mOverLimitEvicting = false;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ while (true) {
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+
+ // Do not change smart size.
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ UpdateSmartCacheSize(freeSpace);
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cacheLimit = CacheObserver::DiskCacheCapacity();
+ uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
+
+ if (cacheUsage > cacheLimit) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
+ "limit. [cacheSize=%ukB, limit=%ukB]",
+ cacheUsage, cacheLimit));
+
+ // We allow cache size to go over the specified limit. Eviction should
+ // keep the size within the limit in a long run, but in case the eviction
+ // is too slow, the cache could go way over the limit. To prevent this we
+ // set flag mCacheSizeOnHardLimit when the size reaches 105% of the limit
+ // and WriteInternal() and TruncateSeekSetEOFInternal() fail to cache
+ // additional data.
+ if ((cacheUsage - cacheLimit) > (cacheLimit / 20)) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size "
+ "reached hard limit."));
+ mCacheSizeOnHardLimit = true;
+ } else {
+ mCacheSizeOnHardLimit = false;
+ }
+ } else if (freeSpace != -1 && freeSpace < freeSpaceLimit) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Free space under "
+ "limit. [freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
+ freeSpace, freeSpaceLimit));
+ } else {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and "
+ "free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
+ "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
+ cacheUsage, cacheLimit, freeSpace, freeSpaceLimit));
+
+ mCacheSizeOnHardLimit = false;
+ return NS_OK;
+ }
+
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
+ "for higher level events."));
+ mOverLimitEvicting = true;
+ return NS_OK;
+ }
+
+ SHA1Sum::Hash hash;
+ uint32_t cnt;
+ static uint32_t consecutiveFailures = 0;
+ rv = CacheIndex::GetEntryForEviction(false, &hash, &cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = DoomFileByKeyInternal(&hash);
+ if (NS_SUCCEEDED(rv)) {
+ consecutiveFailures = 0;
+ } else if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - "
+ "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ // TODO index is outdated, start update
+
+ // Make sure index won't return the same entry again
+ CacheIndex::RemoveEntry(&hash);
+ consecutiveFailures = 0;
+ } else {
+ // This shouldn't normally happen, but the eviction must not fail
+ // completely if we ever encounter this problem.
+ NS_WARNING(
+ "CacheFileIOManager::OverLimitEvictionInternal() - Unexpected "
+ "failure of DoomFileByKeyInternal()");
+
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - "
+ "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+
+ // Normally, CacheIndex::UpdateEntry() is called only to update newly
+ // created/opened entries which are always fresh and UpdateEntry() expects
+ // and checks this flag. The way we use UpdateEntry() here is a kind of
+ // hack and we must make sure the flag is set by calling
+ // EnsureEntryExists().
+ rv = CacheIndex::EnsureEntryExists(&hash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move the entry at the end of both lists to make sure we won't end up
+ // failing on one entry forever.
+ uint32_t frecency = 0;
+ rv = CacheIndex::UpdateEntry(&hash, &frecency, nullptr, nullptr, nullptr,
+ nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ consecutiveFailures++;
+ if (consecutiveFailures >= cnt) {
+ // This doesn't necessarily mean that we've tried to doom every entry
+ // but we've reached a sane number of tries. It is likely that another
+ // eviction will start soon. And as said earlier, this normally doesn't
+ // happen at all.
+ return NS_OK;
+ }
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::EvictAll() {
+ LOG(("CacheFileIOManager::EvictAll()"));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::EvictAllInternal", ioMan,
+ &CacheFileIOManager::EvictAllInternal);
+
+ rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+class EvictionNotifierRunnable : public Runnable {
+ public:
+ EvictionNotifierRunnable() : Runnable("EvictionNotifierRunnable") {}
+ NS_DECL_NSIRUNNABLE
+};
+
+NS_IMETHODIMP
+EvictionNotifierRunnable::Run() {
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr);
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+nsresult CacheFileIOManager::EvictAllInternal() {
+ LOG(("CacheFileIOManager::EvictAllInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+
+ if (!mCacheDirectory) {
+ // This is a kind of hack. Somebody called EvictAll() without a profile.
+ // This happens in xpcshell tests that use cache without profile. We need
+ // to notify observers in this case since the tests are waiting for it.
+ NS_DispatchToMainThread(r);
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // Doom all active handles
+ nsTArray<RefPtr<CacheFileHandle>> handles;
+ mHandles.GetActiveHandles(&handles);
+
+ for (uint32_t i = 0; i < handles.Length(); ++i) {
+ rv = DoomFileInternal(handles[i]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
+ "[handle=%p]",
+ handles[i].get()));
+ }
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Trash current entries directory
+ rv = TrashDirectory(file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Files are now inaccessible in entries directory, notify observers.
+ NS_DispatchToMainThread(r);
+
+ // Create a new empty entries directory
+ rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ CacheIndex::RemoveAll();
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::EvictByContext(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin, const nsAString& aBaseDomain) {
+ LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
+ aLoadContextInfo));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev =
+ NewRunnableMethod<nsCOMPtr<nsILoadContextInfo>, bool, nsString, nsString>(
+ "net::CacheFileIOManager::EvictByContextInternal", ioMan,
+ &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo,
+ aPinned, aOrigin, aBaseDomain);
+
+ rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::EvictByContextInternal(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin, const nsAString& aBaseDomain) {
+ LOG(
+ ("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
+ "pinned=%d]",
+ aLoadContextInfo, aPinned));
+
+ nsresult rv;
+
+ if (aLoadContextInfo) {
+ nsAutoCString suffix;
+ aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
+ LOG((" anonymous=%u, suffix=%s]", aLoadContextInfo->IsAnonymous(),
+ suffix.get()));
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
+ if (aLoadContextInfo->IsPrivate()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (!mCacheDirectory) {
+ // This is a kind of hack. Somebody called EvictAll() without a profile.
+ // This happens in xpcshell tests that use cache without profile. We need
+ // to notify observers in this case since the tests are waiting for it.
+ // Also notify for aPinned == true, those are interested as well.
+ if (!aLoadContextInfo) {
+ RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+ NS_DispatchToMainThread(r);
+ }
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ NS_ConvertUTF16toUTF8 origin(aOrigin);
+ NS_ConvertUTF16toUTF8 baseDomain(aBaseDomain);
+
+ // Doom all active handles that matches the load context
+ nsTArray<RefPtr<CacheFileHandle>> handles;
+ mHandles.GetActiveHandles(&handles);
+
+ for (uint32_t i = 0; i < handles.Length(); ++i) {
+ CacheFileHandle* handle = handles[i];
+
+ const bool shouldRemove = [&] {
+ nsAutoCString uriSpec;
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(handle->Key(), nullptr, &uriSpec);
+ if (!info) {
+ LOG(
+ ("CacheFileIOManager::EvictByContextInternal() - Cannot parse key "
+ "in "
+ "handle! [handle=%p, key=%s]",
+ handle, handle->Key().get()));
+ MOZ_CRASH("Unexpected error!");
+ }
+
+ // Filter by base domain.
+ if (!aBaseDomain.IsEmpty()) {
+ if (StoragePrincipalHelper::PartitionKeyHasBaseDomain(
+ info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) {
+ return true;
+ }
+
+ // If the partitionKey does not match, check the entry URI next.
+
+ // Get host portion of uriSpec.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ // Some entries may not have valid hosts. We can skip them.
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return false;
+ }
+
+ // Clear entry if the host belongs to the given base domain.
+ bool hasRootDomain = false;
+ rv = HasRootDomain(host, baseDomain, &hasRootDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return hasRootDomain;
+ }
+
+ // Filter by LoadContextInfo.
+ if (aLoadContextInfo && !info->Equals(aLoadContextInfo)) {
+ return false;
+ }
+
+ // Filter by origin.
+ if (!origin.IsEmpty()) {
+ RefPtr<MozURL> url;
+ rv = MozURL::Init(getter_AddRefs(url), uriSpec);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString urlOrigin;
+ url->Origin(urlOrigin);
+
+ if (!urlOrigin.Equals(origin)) {
+ return false;
+ }
+ }
+
+ return true;
+ }();
+
+ if (!shouldRemove) {
+ continue;
+ }
+
+ // handle will be doomed only when pinning status is known and equal or
+ // doom decision will be deferred until pinning status is determined.
+ rv = DoomFileInternal(handle,
+ aPinned ? CacheFileIOManager::DOOM_WHEN_PINNED
+ : CacheFileIOManager::DOOM_WHEN_NON_PINNED);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
+ " [handle=%p]",
+ handle));
+ }
+ }
+
+ if (!aLoadContextInfo) {
+ RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+ NS_DispatchToMainThread(r);
+ }
+
+ if (!mContextEvictor) {
+ mContextEvictor = new CacheFileContextEvictor();
+ mContextEvictor->Init(mCacheDirectory);
+ }
+
+ mContextEvictor->AddContext(aLoadContextInfo, aPinned, aOrigin);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::CacheIndexStateChanged() {
+ LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
+
+ nsresult rv;
+
+ // CacheFileIOManager lives longer than CacheIndex so gInstance must be
+ // non-null here.
+ MOZ_ASSERT(gInstance);
+
+ // We have to re-distatch even if we are on IO thread to prevent reentering
+ // the lock in CacheIndex
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
+ "net::CacheFileIOManager::CacheIndexStateChangedInternal",
+ gInstance.get(), &CacheFileIOManager::CacheIndexStateChangedInternal);
+
+ nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void CacheFileIOManager::CacheIndexStateChangedInternal() {
+ if (mShuttingDown) {
+ // ignore notification during shutdown
+ return;
+ }
+
+ if (!mContextEvictor) {
+ return;
+ }
+
+ mContextEvictor->CacheIndexStateChanged();
+}
+
+nsresult CacheFileIOManager::TrashDirectory(nsIFile* aFile) {
+ LOG(("CacheFileIOManager::TrashDirectory() [file=%s]",
+ aFile->HumanReadablePath().get()));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+ MOZ_ASSERT(mCacheDirectory);
+
+ // When the directory is empty, it is cheaper to remove it directly instead of
+ // using the trash mechanism.
+ bool isEmpty;
+ rv = IsEmptyDirectory(aFile, &isEmpty);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isEmpty) {
+ rv = aFile->Remove(false);
+ LOG(
+ ("CacheFileIOManager::TrashDirectory() - Directory removed "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+#ifdef DEBUG
+ nsCOMPtr<nsIFile> dirCheck;
+ rv = aFile->GetParent(getter_AddRefs(dirCheck));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool equals = false;
+ rv = dirCheck->Equals(mCacheDirectory, &equals);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(equals);
+#endif
+
+ nsCOMPtr<nsIFile> dir, trash;
+ nsAutoCString leaf;
+
+ rv = aFile->Clone(getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aFile->Clone(getter_AddRefs(trash));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const int32_t kMaxTries = 16;
+ srand(static_cast<unsigned>(PR_Now()));
+ for (int32_t triesCount = 0;; ++triesCount) {
+ leaf = TRASH_DIR;
+ leaf.AppendInt(rand());
+ rv = trash->SetNativeLeafName(leaf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
+ break;
+ }
+
+ LOG(
+ ("CacheFileIOManager::TrashDirectory() - Trash directory already "
+ "exists [leaf=%s]",
+ leaf.get()));
+
+ if (triesCount == kMaxTries) {
+ LOG(
+ ("CacheFileIOManager::TrashDirectory() - Could not find unused trash "
+ "directory in %d tries.",
+ kMaxTries));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]",
+ leaf.get()));
+
+ rv = dir->MoveToNative(nullptr, leaf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StartRemovingTrash();
+ return NS_OK;
+}
+
+// static
+void CacheFileIOManager::OnTrashTimer(nsITimer* aTimer, void* aClosure) {
+ LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer,
+ aClosure));
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return;
+ }
+
+ ioMan->mTrashTimer = nullptr;
+ ioMan->StartRemovingTrash();
+}
+
+nsresult CacheFileIOManager::StartRemovingTrash() {
+ LOG(("CacheFileIOManager::StartRemovingTrash()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mCacheDirectory) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (mTrashTimer) {
+ LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists."));
+ return NS_OK;
+ }
+
+ if (mRemovingTrashDirs) {
+ LOG(
+ ("CacheFileIOManager::StartRemovingTrash() - Trash removing in "
+ "progress."));
+ return NS_OK;
+ }
+
+ uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
+ if (elapsed < kRemoveTrashStartDelay) {
+ nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTrashTimer), CacheFileIOManager::OnTrashTimer, nullptr,
+ kRemoveTrashStartDelay - elapsed, nsITimer::TYPE_ONE_SHOT,
+ "net::CacheFileIOManager::StartRemovingTrash", ioTarget);
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::RemoveTrashInternal", this,
+ &CacheFileIOManager::RemoveTrashInternal);
+
+ rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRemovingTrashDirs = true;
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::RemoveTrashInternal() {
+ LOG(("CacheFileIOManager::RemoveTrashInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ CacheIOThread::Cancelable cancelable(true);
+
+ MOZ_ASSERT(!mTrashTimer);
+ MOZ_ASSERT(mRemovingTrashDirs);
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag
+ // here and set it again once we dispatch a continuation event. By doing so,
+ // we don't have to drop the flag on any possible early return.
+ mRemovingTrashDirs = false;
+
+ while (true) {
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for "
+ "higher level events."));
+ mRemovingTrashDirs = true;
+ return NS_OK;
+ }
+
+ // Find some trash directory
+ if (!mTrashDir) {
+ MOZ_ASSERT(!mTrashDirEnumerator);
+
+ rv = FindTrashDirToRemove();
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - No trash directory "
+ "found."));
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(mTrashDirEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ continue; // check elapsed time
+ }
+
+ // We null out mTrashDirEnumerator once we remove all files in the
+ // directory, so remove the trash directory if we don't have enumerator.
+ if (!mTrashDirEnumerator) {
+ rv = mTrashDir->Remove(false);
+ if (NS_FAILED(rv)) {
+ // There is no reason why removing an empty directory should fail, but
+ // if it does, we should continue and try to remove all other trash
+ // directories.
+ nsAutoCString leafName;
+ mTrashDir->GetNativeLeafName(leafName);
+ mFailedTrashDirs.AppendElement(leafName);
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - Cannot remove "
+ "trashdir. [name=%s]",
+ leafName.get()));
+ }
+
+ mTrashDir = nullptr;
+ continue; // check elapsed time
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = mTrashDirEnumerator->GetNextFile(getter_AddRefs(file));
+ if (!file) {
+ mTrashDirEnumerator->Close();
+ mTrashDirEnumerator = nullptr;
+ continue; // check elapsed time
+ }
+ bool isDir = false;
+ file->IsDirectory(&isDir);
+ if (isDir) {
+ NS_WARNING(
+ "Found a directory in a trash directory! It will be removed "
+ "recursively, but this can block IO thread for a while!");
+ if (LOG_ENABLED()) {
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - Found a directory in "
+ "a trash "
+ "directory! It will be removed recursively, but this can block IO "
+ "thread for a while! [file=%s]",
+ file->HumanReadablePath().get()));
+ }
+ }
+ file->Remove(isDir);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::FindTrashDirToRemove() {
+ LOG(("CacheFileIOManager::FindTrashDirToRemove()"));
+
+ nsresult rv;
+
+ if (!mCacheDirectory) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // We call this method on the main thread during shutdown when user wants to
+ // remove all cache files.
+ MOZ_ASSERT(mIOThread->IsCurrentThread() || mShuttingDown);
+
+ nsCOMPtr<nsIDirectoryEnumerator> iter;
+ rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) {
+ bool isDir = false;
+ file->IsDirectory(&isDir);
+ if (!isDir) {
+ continue;
+ }
+
+ nsAutoCString leafName;
+ rv = file->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (leafName.Length() < strlen(TRASH_DIR)) {
+ continue;
+ }
+
+ if (!StringBeginsWith(leafName, nsLiteralCString(TRASH_DIR))) {
+ continue;
+ }
+
+ if (mFailedTrashDirs.Contains(leafName)) {
+ continue;
+ }
+
+ LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s",
+ leafName.get()));
+
+ mTrashDir = file;
+ return NS_OK;
+ }
+
+ // When we're here we've tried to delete all trash directories. Clear
+ // mFailedTrashDirs so we will try to delete them again when we start removing
+ // trash directories next time.
+ mFailedTrashDirs.Clear();
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// static
+nsresult CacheFileIOManager::InitIndexEntry(CacheFileHandle* aHandle,
+ OriginAttrsHash aOriginAttrsHash,
+ bool aAnonymous, bool aPinning) {
+ LOG(
+ ("CacheFileIOManager::InitIndexEntry() [handle=%p, "
+ "originAttrsHash=%" PRIx64 ", "
+ "anonymous=%d, pinning=%d]",
+ aHandle, aOriginAttrsHash, aAnonymous, aPinning));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aHandle->IsSpecialFile()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<InitIndexEntryEvent> ev =
+ new InitIndexEntryEvent(aHandle, aOriginAttrsHash, aAnonymous, aPinning);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::UpdateIndexEntry(CacheFileHandle* aHandle,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType) {
+ LOG(
+ ("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
+ "hasAltData=%s, onStartTime=%s, onStopTime=%s, contentType=%s]",
+ aHandle, aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
+ aHasAltData ? (*aHasAltData ? "true" : "false") : "",
+ aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
+ aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
+ aContentType ? nsPrintfCString("%u", *aContentType).get() : ""));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aHandle->IsSpecialFile()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<UpdateIndexEntryEvent> ev = new UpdateIndexEntryEvent(
+ aHandle, aFrecency, aHasAltData, aOnStartTime, aOnStopTime, aContentType);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::CreateFile(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(!aHandle->mFD);
+ MOZ_ASSERT(aHandle->mFile);
+
+ nsresult rv;
+
+ if (aHandle->IsDoomed()) {
+ nsCOMPtr<nsIFile> file;
+
+ rv = GetDoomedFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFile.swap(file);
+ } else {
+ bool exists;
+ if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) {
+ NS_WARNING("Found a file that should not exist!");
+ }
+ }
+
+ rv = OpenNSPRHandle(aHandle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFileSize = 0;
+ return NS_OK;
+}
+
+// static
+void CacheFileIOManager::HashToStr(const SHA1Sum::Hash* aHash,
+ nsACString& _retval) {
+ _retval.Truncate();
+ const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+ for (uint32_t i = 0; i < sizeof(SHA1Sum::Hash); i++) {
+ _retval.Append(hexChars[(*aHash)[i] >> 4]);
+ _retval.Append(hexChars[(*aHash)[i] & 0xF]);
+ }
+}
+
+// static
+nsresult CacheFileIOManager::StrToHash(const nsACString& aHash,
+ SHA1Sum::Hash* _retval) {
+ if (aHash.Length() != 2 * sizeof(SHA1Sum::Hash)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (uint32_t i = 0; i < aHash.Length(); i++) {
+ uint8_t value;
+
+ if (aHash[i] >= '0' && aHash[i] <= '9') {
+ value = aHash[i] - '0';
+ } else if (aHash[i] >= 'A' && aHash[i] <= 'F') {
+ value = aHash[i] - 'A' + 10;
+ } else if (aHash[i] >= 'a' && aHash[i] <= 'f') {
+ value = aHash[i] - 'a' + 10;
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (i % 2 == 0) {
+ (reinterpret_cast<uint8_t*>(_retval))[i / 2] = value << 4;
+ } else {
+ (reinterpret_cast<uint8_t*>(_retval))[i / 2] += value;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::GetFile(const SHA1Sum::Hash* aHash,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString leafName;
+ HashToStr(aHash, leafName);
+
+ rv = file->AppendNative(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::GetSpecialFile(const nsACString& aKey,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(aKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::GetDoomedFile(nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(nsLiteralCString(DOOMED_DIR));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative("dummyleaf"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const int32_t kMaxTries = 64;
+ srand(static_cast<unsigned>(PR_Now()));
+ nsAutoCString leafName;
+ for (int32_t triesCount = 0;; ++triesCount) {
+ leafName.AppendInt(rand());
+ rv = file->SetNativeLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) {
+ break;
+ }
+
+ if (triesCount == kMaxTries) {
+ LOG(
+ ("CacheFileIOManager::GetDoomedFile() - Could not find unused file "
+ "name in %d tries.",
+ kMaxTries));
+ return NS_ERROR_FAILURE;
+ }
+
+ leafName.Truncate();
+ }
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::IsEmptyDirectory(nsIFile* aFile, bool* _retval) {
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDirectoryEnumerator> enumerator;
+ rv = aFile->GetDirectoryEntries(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMoreElements = false;
+ rv = enumerator->HasMoreElements(&hasMoreElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = !hasMoreElements;
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::CheckAndCreateDir(nsIFile* aFile, const char* aDir,
+ bool aEnsureEmptyDir) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> file;
+ if (!aDir) {
+ file = aFile;
+ } else {
+ nsAutoCString dir(aDir);
+ rv = aFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = file->AppendNative(dir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool exists = false;
+ rv = file->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory) {
+ // Try to remove the file
+ rv = file->Remove(false);
+ if (NS_SUCCEEDED(rv)) {
+ exists = false;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aEnsureEmptyDir && NS_SUCCEEDED(rv) && exists) {
+ bool isEmpty;
+ rv = IsEmptyDirectory(file, &isEmpty);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isEmpty) {
+ // Don't check the result, if this fails, it's OK. We do this
+ // only for the doomed directory that doesn't need to be deleted
+ // for the cost of completely disabling the whole browser.
+ TrashDirectory(file);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && !exists) {
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot create directory");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::CreateCacheTree() {
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+ MOZ_ASSERT(!mTreeCreated);
+
+ if (!mCacheDirectory || mTreeCreationFailed) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ nsresult rv;
+
+ // Set the flag here and clear it again below when the tree is created
+ // successfully.
+ mTreeCreationFailed = true;
+
+ // ensure parent directory exists
+ nsCOMPtr<nsIFile> parentDir;
+ rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CheckAndCreateDir(parentDir, nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ensure cache directory exists
+ rv = CheckAndCreateDir(mCacheDirectory, nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ensure entries directory exists
+ rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ensure doomed directory exists
+ rv = CheckAndCreateDir(mCacheDirectory, DOOMED_DIR, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTreeCreated = true;
+ mTreeCreationFailed = false;
+
+ if (!mContextEvictor) {
+ RefPtr<CacheFileContextEvictor> contextEvictor;
+ contextEvictor = new CacheFileContextEvictor();
+
+ // Init() method will try to load unfinished contexts from the disk. Store
+ // the evictor as a member only when there is some unfinished job.
+ contextEvictor->Init(mCacheDirectory);
+ if (contextEvictor->ContextsCount()) {
+ contextEvictor.swap(mContextEvictor);
+ }
+ }
+
+ StartRemovingTrash();
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OpenNSPRHandle(CacheFileHandle* aHandle,
+ bool aCreate) {
+ LOG(("CacheFileIOManager::OpenNSPRHandle BEGIN, handle=%p", aHandle));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(!aHandle->mFD);
+ MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
+ MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
+ MOZ_ASSERT((aCreate && !aHandle->mFileExists) ||
+ (!aCreate && aHandle->mFileExists));
+
+ nsresult rv;
+
+ if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
+ // close handle that hasn't been used for the longest time
+ rv = MaybeReleaseNSPRHandleInternal(mHandlesByLastUsed[0], true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCreate) {
+ rv = aHandle->mFile->OpenNSPRFileDesc(
+ PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS || // error from nsLocalFileWin
+ rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { // error from nsLocalFileUnix
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() - Cannot create a new file, we"
+ " might reached a limit on FAT32. Will evict a single entry and try "
+ "again. [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHandle->Hash())));
+
+ SHA1Sum::Hash hash;
+ uint32_t cnt;
+
+ rv = CacheIndex::GetEntryForEviction(true, &hash, &cnt);
+ if (NS_SUCCEEDED(rv)) {
+ rv = DoomFileByKeyInternal(&hash);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = aHandle->mFile->OpenNSPRFileDesc(
+ PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() - Successfully evicted entry"
+ " with hash %08x%08x%08x%08x%08x. %s to create the new file.",
+ LOGSHA1(&hash), NS_SUCCEEDED(rv) ? "Succeeded" : "Failed"));
+ } else {
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() - Couldn't evict an existing"
+ " entry."));
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+ }
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() Create failed with "
+ "0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFileExists = true;
+ } else {
+ rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD);
+ if (NS_ERROR_FILE_NOT_FOUND == rv) {
+ LOG((" file doesn't exists"));
+ aHandle->mFileExists = false;
+ return DoomFileInternal(aHandle);
+ }
+ if (NS_FAILED(rv)) {
+ LOG(("CacheFileIOManager::OpenNSPRHandle() Open failed with 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mHandlesByLastUsed.AppendElement(aHandle);
+
+ LOG(("CacheFileIOManager::OpenNSPRHandle END, handle=%p", aHandle));
+
+ return NS_OK;
+}
+
+void CacheFileIOManager::NSPRHandleUsed(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHandle->mFD);
+
+ DebugOnly<bool> found{};
+ found = mHandlesByLastUsed.RemoveElement(aHandle);
+ MOZ_ASSERT(found);
+
+ mHandlesByLastUsed.AppendElement(aHandle);
+}
+
+nsresult CacheFileIOManager::SyncRemoveDir(nsIFile* aFile, const char* aDir) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+
+ if (!aFile) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aDir) {
+ file = aFile;
+ } else {
+ rv = aFile->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->AppendNative(nsDependentCString(aDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (LOG_ENABLED()) {
+ LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",
+ file->HumanReadablePath().get()));
+ }
+
+ rv = file->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::SyncRemoveDir() - Removing failed! "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+
+ return rv;
+}
+
+nsresult CacheFileIOManager::DispatchPurgeTask(
+ const nsCString& aCacheDirName, const nsCString& aSecondsToWait,
+ const nsCString& aPurgeExtension) {
+#if !defined(MOZ_BACKGROUNDTASKS)
+ // If background tasks are disabled, then we should just bail out early.
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsCOMPtr<nsIFile> cacheDir;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(cacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> profileDir;
+ rv = cacheDir->GetParent(getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> lf;
+ rv = XRE_GetBinaryPath(getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString path;
+# if !defined(XP_WIN)
+ rv = profileDir->GetNativePath(path);
+# else
+ rv = profileDir->GetNativeTarget(path);
+# endif
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIBackgroundTasksRunner> runner =
+ do_GetService("@mozilla.org/backgroundtasksrunner;1");
+
+ return runner->RemoveDirectoryInDetachedProcess(
+ path, aCacheDirName, aSecondsToWait, aPurgeExtension, "HttpCache"_ns);
+#endif
+}
+
+void CacheFileIOManager::SyncRemoveAllCacheFiles() {
+ LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));
+ nsresult rv;
+
+ // If we are already running in a background task, we
+ // don't want to spawn yet another one at shutdown.
+ if (inBackgroundTask()) {
+ return;
+ }
+
+ if (StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
+ rv = [&]() -> nsresult {
+ nsresult rv;
+
+ // If there is no cache directory, there's nothing to remove.
+ if (!mCacheDirectory) {
+ return NS_OK;
+ }
+
+ nsAutoCString leafName;
+ rv = mCacheDirectory->GetNativeLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ leafName.Append('.');
+
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
+ leafName.Append(nsPrintfCString(
+ "%04d-%02d-%02d-%02d-%02d-%02d", now.tm_year, now.tm_month + 1,
+ now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec));
+ leafName.Append(kPurgeExtension);
+
+ nsAutoCString secondsToWait;
+ secondsToWait.AppendInt(
+ StaticPrefs::network_cache_shutdown_purge_folder_wait_seconds());
+
+ rv = DispatchPurgeTask(leafName, secondsToWait, kPurgeExtension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mCacheDirectory->RenameToNative(nullptr, leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }();
+
+ // Dispatching to the background task has succeeded. This is finished.
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+
+ SyncRemoveDir(mCacheDirectory, ENTRIES_DIR);
+ SyncRemoveDir(mCacheDirectory, DOOMED_DIR);
+
+ // Clear any intermediate state of trash dir enumeration.
+ mFailedTrashDirs.Clear();
+ mTrashDir = nullptr;
+
+ while (true) {
+ // FindTrashDirToRemove() fills mTrashDir if there is any trash directory.
+ rv = FindTrashDirToRemove();
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(
+ ("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory "
+ "found."));
+ break;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::SyncRemoveAllCacheFiles() - "
+ "FindTrashDirToRemove() returned an unexpected error. "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ break;
+ }
+
+ rv = SyncRemoveDir(mTrashDir, nullptr);
+ if (NS_FAILED(rv)) {
+ nsAutoCString leafName;
+ mTrashDir->GetNativeLeafName(leafName);
+ mFailedTrashDirs.AppendElement(leafName);
+ }
+ }
+}
+
+// Returns default ("smart") size (in KB) of cache, given available disk space
+// (also in KB)
+static uint32_t SmartCacheSize(const int64_t availKB) {
+ uint32_t maxSize;
+
+ if (CacheObserver::ClearCacheOnShutdown()) {
+ maxSize = kMaxClearOnShutdownCacheSizeKB;
+ } else {
+ maxSize = kMaxCacheSizeKB;
+ }
+
+ if (availKB > 25 * 1024 * 1024) {
+ return maxSize; // skip computing if we're over 25 GB
+ }
+
+ // Grow/shrink in 10 MB units, deliberately, so that in the common case we
+ // don't shrink cache and evict items every time we startup (it's important
+ // that we don't slow down startup benchmarks).
+ uint32_t sz10MBs = 0;
+ uint32_t avail10MBs = availKB / (1024 * 10);
+
+ // 2.5% of space above 7GB
+ if (avail10MBs > 700) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 700) * .025);
+ avail10MBs = 700;
+ }
+ // 7.5% of space between 500 MB -> 7 GB
+ if (avail10MBs > 50) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 50) * .075);
+ avail10MBs = 50;
+ }
+
+#ifdef ANDROID
+ // On Android, smaller/older devices may have very little storage and
+ // device owners may be sensitive to storage footprint: Use a smaller
+ // percentage of available space and a smaller minimum.
+
+ // 16% of space up to 500 MB (10 MB min)
+ sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .16));
+#else
+ // 30% of space up to 500 MB (50 MB min)
+ sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .3));
+#endif
+
+ return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
+}
+
+nsresult CacheFileIOManager::UpdateSmartCacheSize(int64_t aFreeSpace) {
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ nsresult rv;
+
+ if (!CacheObserver::SmartCacheSizeEnabled()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Wait at least kSmartSizeUpdateInterval before recomputing smart size.
+ static const TimeDuration kUpdateLimit =
+ TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval);
+ if (!mLastSmartSizeTime.IsNull() &&
+ (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) {
+ return NS_OK;
+ }
+
+ // Do not compute smart size when cache size is not reliable.
+ bool isUpToDate = false;
+ CacheIndex::IsUpToDate(&isUpToDate);
+ if (!isUpToDate) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ mLastSmartSizeTime = TimeStamp::NowLoRes();
+
+ uint32_t smartSize = SmartCacheSize(aFreeSpace + cacheUsage);
+
+ if (smartSize == CacheObserver::DiskCacheCapacity()) {
+ // Smart size has not changed.
+ return NS_OK;
+ }
+
+ CacheObserver::SetSmartDiskCacheCapacity(smartSize);
+
+ return NS_OK;
+}
+
+// Memory reporting
+
+namespace {
+
+// A helper class that dispatches and waits for an event that gets result of
+// CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread
+// to safely get handles memory report.
+// We must do this, since the handle list is only accessed and managed w/o
+// locking on the I/O thread. That is by design.
+class SizeOfHandlesRunnable : public Runnable {
+ public:
+ SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf,
+ CacheFileHandles const& handles,
+ nsTArray<CacheFileHandle*> const& specialHandles)
+ : Runnable("net::SizeOfHandlesRunnable"),
+ mMonitor("SizeOfHandlesRunnable.mMonitor"),
+ mMonitorNotified(false),
+ mMallocSizeOf(mallocSizeOf),
+ mHandles(handles),
+ mSpecialHandles(specialHandles),
+ mSize(0) {}
+
+ size_t Get(CacheIOThread* thread) {
+ nsCOMPtr<nsIEventTarget> target = thread->Target();
+ if (!target) {
+ NS_ERROR("If we have the I/O thread we also must have the I/O target");
+ return 0;
+ }
+
+ mozilla::MonitorAutoLock mon(mMonitor);
+ mMonitorNotified = false;
+ nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles");
+ return 0;
+ }
+
+ while (!mMonitorNotified) {
+ mon.Wait();
+ }
+ return mSize;
+ }
+
+ NS_IMETHOD Run() override {
+ mozilla::MonitorAutoLock mon(mMonitor);
+ // Excluding this since the object itself is a member of CacheFileIOManager
+ // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|.
+ mSize = mHandles.SizeOfExcludingThis(mMallocSizeOf);
+ for (uint32_t i = 0; i < mSpecialHandles.Length(); ++i) {
+ mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf);
+ }
+
+ mMonitorNotified = true;
+ mon.Notify();
+ return NS_OK;
+ }
+
+ private:
+ mozilla::Monitor mMonitor MOZ_UNANNOTATED;
+ bool mMonitorNotified;
+ mozilla::MallocSizeOf mMallocSizeOf;
+ CacheFileHandles const& mHandles;
+ nsTArray<CacheFileHandle*> const& mSpecialHandles;
+ size_t mSize;
+};
+
+} // namespace
+
+size_t CacheFileIOManager::SizeOfExcludingThisInternal(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+ nsCOMPtr<nsISizeOf> sizeOf;
+
+ if (mIOThread) {
+ n += mIOThread->SizeOfIncludingThis(mallocSizeOf);
+
+ // mHandles and mSpecialHandles must be accessed only on the I/O thread,
+ // must sync dispatch.
+ RefPtr<SizeOfHandlesRunnable> sizeOfHandlesRunnable =
+ new SizeOfHandlesRunnable(mallocSizeOf, mHandles, mSpecialHandles);
+ n += sizeOfHandlesRunnable->Get(mIOThread);
+ }
+
+ // mHandlesByLastUsed just refers handles reported by mHandles.
+
+ sizeOf = do_QueryInterface(mCacheDirectory);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ sizeOf = do_QueryInterface(mMetadataWritesTimer);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ sizeOf = do_QueryInterface(mTrashTimer);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ sizeOf = do_QueryInterface(mTrashDir);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) {
+ n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ }
+
+ return n;
+}
+
+// static
+size_t CacheFileIOManager::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) {
+ if (!gInstance) return 0;
+
+ return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
+}
+
+// static
+size_t CacheFileIOManager::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) {
+ return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheFileIOManager.h b/netwerk/cache2/CacheFileIOManager.h
new file mode 100644
index 0000000000..5be21c8db8
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -0,0 +1,491 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileIOManager__h__
+#define CacheFileIOManager__h__
+
+#include "CacheIOThread.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "nsIEventTarget.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "prio.h"
+
+// #define DEBUG_HANDLES 1
+
+class nsIFile;
+class nsITimer;
+class nsIDirectoryEnumerator;
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+class CacheFileIOListener;
+
+#ifdef DEBUG_HANDLES
+class CacheFileHandlesEntry;
+#endif
+
+#define ENTRIES_DIR "entries"
+#define DOOMED_DIR "doomed"
+#define TRASH_DIR "trash"
+
+class CacheFileHandle final : public nsISupports {
+ public:
+ enum class PinningStatus : uint32_t { UNKNOWN, NON_PINNED, PINNED };
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ bool DispatchRelease();
+
+ CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority,
+ PinningStatus aPinning);
+ CacheFileHandle(const nsACString& aKey, bool aPriority,
+ PinningStatus aPinning);
+ void Log();
+ bool IsDoomed() const { return mIsDoomed; }
+ const SHA1Sum::Hash* Hash() const { return mHash; }
+ int64_t FileSize() const { return mFileSize; }
+ uint32_t FileSizeInK() const;
+ bool IsPriority() const { return mPriority; }
+ bool FileExists() const { return mFileExists; }
+ bool IsClosed() const { return mClosed; }
+ bool IsSpecialFile() const { return mSpecialFile; }
+ nsCString& Key() { return mKey; }
+
+ // Returns false when this handle has been doomed based on the pinning state
+ // update.
+ bool SetPinned(bool aPinned);
+ void SetInvalid() { mInvalid = true; }
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ friend class CacheFileIOManager;
+ friend class CacheFileHandles;
+ friend class ReleaseNSPRHandleEvent;
+
+ virtual ~CacheFileHandle();
+
+ const SHA1Sum::Hash* mHash;
+ mozilla::Atomic<bool, ReleaseAcquire> mIsDoomed;
+ mozilla::Atomic<bool, ReleaseAcquire> mClosed;
+
+ // mPriority and mSpecialFile are plain "bool", not "bool:1", so as to
+ // avoid bitfield races with the byte containing mInvalid et al. See
+ // bug 1278502.
+ bool const mPriority;
+ bool const mSpecialFile;
+
+ mozilla::Atomic<bool, Relaxed> mInvalid;
+
+ // These bit flags are all accessed only on the IO thread
+ bool mFileExists : 1; // This means that the file should exists,
+ // but it can be still deleted by OS/user
+ // and then a subsequent OpenNSPRFileDesc()
+ // will fail.
+
+ // Both initially false. Can be raised to true only when this handle is to be
+ // doomed during the period when the pinning status is unknown. After the
+ // pinning status determination we check these flags and possibly doom. These
+ // flags are only accessed on the IO thread.
+ bool mDoomWhenFoundPinned : 1;
+ bool mDoomWhenFoundNonPinned : 1;
+ // Set when after shutdown AND:
+ // - when writing: writing data (not metadata) OR the physical file handle is
+ // not currently open
+ // - when truncating: the physical file handle is not currently open
+ // When set it prevents any further writes or truncates on such handles to
+ // happen immediately after shutdown and gives a chance to write metadata of
+ // already open files quickly as possible (only that renders them actually
+ // usable by the cache.)
+ bool mKilled : 1;
+ // For existing files this is always pre-set to UNKNOWN. The status is
+ // udpated accordingly after the matadata has been parsed. For new files the
+ // flag is set according to which storage kind is opening the cache entry and
+ // remains so for the handle's lifetime. The status can only change from
+ // UNKNOWN (if set so initially) to one of PINNED or NON_PINNED and it stays
+ // unchanged afterwards. This status is only accessed on the IO thread.
+ PinningStatus mPinning;
+
+ nsCOMPtr<nsIFile> mFile;
+
+ // file size is atomic because it is used on main thread by
+ // nsHttpChannel::ReportNetVSCacheTelemetry()
+ Atomic<int64_t, Relaxed> mFileSize;
+ PRFileDesc* mFD; // if null then the file doesn't exists on the disk
+ nsCString mKey;
+};
+
+class CacheFileHandles {
+ public:
+ CacheFileHandles();
+ ~CacheFileHandles();
+
+ nsresult GetHandle(const SHA1Sum::Hash* aHash, CacheFileHandle** _retval);
+ already_AddRefed<CacheFileHandle> NewHandle(const SHA1Sum::Hash*,
+ bool aPriority,
+ CacheFileHandle::PinningStatus);
+ void RemoveHandle(CacheFileHandle* aHandle);
+ void GetAllHandles(nsTArray<RefPtr<CacheFileHandle> >* _retval);
+ void GetActiveHandles(nsTArray<RefPtr<CacheFileHandle> >* _retval);
+ void ClearAll();
+ uint32_t HandleCount();
+
+#ifdef DEBUG_HANDLES
+ void Log(CacheFileHandlesEntry* entry);
+#endif
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ class HandleHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const SHA1Sum::Hash&;
+ using KeyTypePointer = const SHA1Sum::Hash*;
+
+ explicit HandleHashKey(KeyTypePointer aKey) {
+ MOZ_COUNT_CTOR(HandleHashKey);
+ mHash = MakeUnique<uint8_t[]>(SHA1Sum::kHashSize);
+ memcpy(mHash.get(), aKey, sizeof(SHA1Sum::Hash));
+ }
+ HandleHashKey(const HandleHashKey& aOther) {
+ MOZ_ASSERT_UNREACHABLE("HandleHashKey copy constructor is forbidden!");
+ }
+ MOZ_COUNTED_DTOR(HandleHashKey)
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return memcmp(mHash.get(), aKey, sizeof(SHA1Sum::Hash)) == 0;
+ }
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return (reinterpret_cast<const uint32_t*>(aKey))[0];
+ }
+
+ void AddHandle(CacheFileHandle* aHandle);
+ void RemoveHandle(CacheFileHandle* aHandle);
+ already_AddRefed<CacheFileHandle> GetNewestHandle();
+ void GetHandles(nsTArray<RefPtr<CacheFileHandle> >& aResult);
+
+ SHA1Sum::Hash* Hash() const {
+ return reinterpret_cast<SHA1Sum::Hash*>(mHash.get());
+ }
+ bool IsEmpty() const { return mHandles.Length() == 0; }
+
+ enum { ALLOW_MEMMOVE = true };
+
+#ifdef DEBUG
+ void AssertHandlesState();
+#endif
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ // We can't make this UniquePtr<SHA1Sum::Hash>, because you can't have
+ // UniquePtrs with known bounds. So we settle for this representation
+ // and using appropriate casts when we need to access it as a
+ // SHA1Sum::Hash.
+ UniquePtr<uint8_t[]> mHash;
+ // Use weak pointers since the hash table access is on a single thread
+ // only and CacheFileHandle removes itself from this table in its dtor
+ // that may only be called on the same thread as we work with the hashtable
+ // since we dispatch its Release() to this thread.
+ nsTArray<CacheFileHandle*> mHandles;
+ };
+
+ private:
+ nsTHashtable<HandleHashKey> mTable;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class OpenFileEvent;
+class ReadEvent;
+class WriteEvent;
+class MetadataWriteScheduleEvent;
+class CacheFileContextEvictor;
+
+#define CACHEFILEIOLISTENER_IID \
+ { /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */ \
+ 0xdcaf2ddc, 0x17cf, 0x4242, { \
+ 0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5 \
+ } \
+ }
+
+class CacheFileIOListener : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEIOLISTENER_IID)
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) = 0;
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) = 0;
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) = 0;
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) = 0;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) = 0;
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) = 0;
+
+ virtual bool IsKilled() { return false; }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID)
+
+class CacheFileIOManager final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ enum {
+ OPEN = 0U,
+ CREATE = 1U,
+ CREATE_NEW = 2U,
+ PRIORITY = 4U,
+ SPECIAL_FILE = 8U,
+ PINNED = 16U
+ };
+
+ CacheFileIOManager();
+
+ static nsresult Init();
+ static nsresult Shutdown();
+ static nsresult OnProfile();
+ static nsresult OnDelayedStartupFinished();
+ static nsresult OnIdleDaily();
+ static already_AddRefed<nsIEventTarget> IOTarget();
+ static already_AddRefed<CacheIOThread> IOThread();
+ static bool IsOnIOThread();
+ static bool IsOnIOThreadOrCeased();
+ static bool IsShutdown();
+
+ // Make aFile's WriteMetadataIfNeeded be called automatically after
+ // a short interval.
+ static nsresult ScheduleMetadataWrite(CacheFile* aFile);
+ // Remove aFile from the scheduling registry array.
+ // WriteMetadataIfNeeded will not be automatically called.
+ static nsresult UnscheduleMetadataWrite(CacheFile* aFile);
+ // Shuts the scheduling off and flushes all pending metadata writes.
+ static nsresult ShutdownMetadataWriteScheduling();
+
+ static nsresult OpenFile(const nsACString& aKey, uint32_t aFlags,
+ CacheFileIOListener* aCallback);
+ static nsresult Read(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
+ int32_t aCount, CacheFileIOListener* aCallback);
+ static nsresult Write(CacheFileHandle* aHandle, int64_t aOffset,
+ const char* aBuf, int32_t aCount, bool aValidate,
+ bool aTruncate, CacheFileIOListener* aCallback);
+ // PinningDoomRestriction:
+ // NO_RESTRICTION
+ // no restriction is checked, the file is simply always doomed
+ // DOOM_WHEN_(NON)_PINNED, we branch based on the pinning status of the
+ // handle:
+ // UNKNOWN: the handle is marked to be doomed when later found (non)pinned
+ // PINNED/NON_PINNED: doom only when the restriction matches the pin status
+ // and the handle has not yet been required to doom during the UNKNOWN
+ // period
+ enum PinningDoomRestriction {
+ NO_RESTRICTION,
+ DOOM_WHEN_NON_PINNED,
+ DOOM_WHEN_PINNED
+ };
+ static nsresult DoomFile(CacheFileHandle* aHandle,
+ CacheFileIOListener* aCallback);
+ static nsresult DoomFileByKey(const nsACString& aKey,
+ CacheFileIOListener* aCallback);
+ static nsresult ReleaseNSPRHandle(CacheFileHandle* aHandle);
+ static nsresult TruncateSeekSetEOF(CacheFileHandle* aHandle,
+ int64_t aTruncatePos, int64_t aEOFPos,
+ CacheFileIOListener* aCallback);
+ static nsresult RenameFile(CacheFileHandle* aHandle,
+ const nsACString& aNewName,
+ CacheFileIOListener* aCallback);
+ static nsresult EvictIfOverLimit();
+ static nsresult EvictAll();
+ static nsresult EvictByContext(nsILoadContextInfo* aLoadContextInfo,
+ bool aPinned, const nsAString& aOrigin,
+ const nsAString& aBaseDomain = u""_ns);
+
+ static nsresult InitIndexEntry(CacheFileHandle* aHandle,
+ OriginAttrsHash aOriginAttrsHash,
+ bool aAnonymous, bool aPinning);
+ static nsresult UpdateIndexEntry(CacheFileHandle* aHandle,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType);
+
+ static nsresult UpdateIndexEntry();
+
+ enum EEnumerateMode { ENTRIES, DOOMED };
+
+ static void GetCacheDirectory(nsIFile** result);
+#if defined(MOZ_WIDGET_ANDROID)
+ static void GetProfilelessCacheDirectory(nsIFile** result);
+#endif
+
+ // Calls synchronously OnEntryInfo for an entry with the given hash.
+ // Tries to find an existing entry in the service hashtables first, if not
+ // found, loads synchronously from disk file.
+ // Callable on the IO thread only.
+ static nsresult GetEntryInfo(
+ const SHA1Sum::Hash* aHash,
+ CacheStorageService::EntryInfoCallback* aCallback);
+
+ // Memory reporting
+ static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+ static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ private:
+ friend class CacheFileHandle;
+ friend class CacheFileChunk;
+ friend class CacheFile;
+ friend class ShutdownEvent;
+ friend class OpenFileEvent;
+ friend class CloseHandleEvent;
+ friend class ReadEvent;
+ friend class WriteEvent;
+ friend class DoomFileEvent;
+ friend class DoomFileByKeyEvent;
+ friend class ReleaseNSPRHandleEvent;
+ friend class TruncateSeekSetEOFEvent;
+ friend class RenameFileEvent;
+ friend class CacheIndex;
+ friend class MetadataWriteScheduleEvent;
+ friend class CacheFileContextEvictor;
+
+ virtual ~CacheFileIOManager();
+
+ nsresult InitInternal();
+ void ShutdownInternal();
+
+ nsresult OpenFileInternal(const SHA1Sum::Hash* aHash, const nsACString& aKey,
+ uint32_t aFlags, CacheFileHandle** _retval);
+ nsresult OpenSpecialFileInternal(const nsACString& aKey, uint32_t aFlags,
+ CacheFileHandle** _retval);
+ void CloseHandleInternal(CacheFileHandle* aHandle);
+ nsresult ReadInternal(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
+ int32_t aCount);
+ nsresult WriteInternal(CacheFileHandle* aHandle, int64_t aOffset,
+ const char* aBuf, int32_t aCount, bool aValidate,
+ bool aTruncate);
+ nsresult DoomFileInternal(
+ CacheFileHandle* aHandle,
+ PinningDoomRestriction aPinningDoomRestriction = NO_RESTRICTION);
+ nsresult DoomFileByKeyInternal(const SHA1Sum::Hash* aHash);
+ nsresult MaybeReleaseNSPRHandleInternal(CacheFileHandle* aHandle,
+ bool aIgnoreShutdownLag = false);
+ nsresult TruncateSeekSetEOFInternal(CacheFileHandle* aHandle,
+ int64_t aTruncatePos, int64_t aEOFPos);
+ nsresult RenameFileInternal(CacheFileHandle* aHandle,
+ const nsACString& aNewName);
+ nsresult EvictIfOverLimitInternal();
+ nsresult OverLimitEvictionInternal();
+ nsresult EvictAllInternal();
+ nsresult EvictByContextInternal(nsILoadContextInfo* aLoadContextInfo,
+ bool aPinned, const nsAString& aOrigin,
+ const nsAString& aBaseDomain = u""_ns);
+
+ nsresult TrashDirectory(nsIFile* aFile);
+ static void OnTrashTimer(nsITimer* aTimer, void* aClosure);
+ nsresult StartRemovingTrash();
+ nsresult RemoveTrashInternal();
+ nsresult FindTrashDirToRemove();
+
+ nsresult CreateFile(CacheFileHandle* aHandle);
+ static void HashToStr(const SHA1Sum::Hash* aHash, nsACString& _retval);
+ static nsresult StrToHash(const nsACString& aHash, SHA1Sum::Hash* _retval);
+ nsresult GetFile(const SHA1Sum::Hash* aHash, nsIFile** _retval);
+ nsresult GetSpecialFile(const nsACString& aKey, nsIFile** _retval);
+ nsresult GetDoomedFile(nsIFile** _retval);
+ nsresult IsEmptyDirectory(nsIFile* aFile, bool* _retval);
+ nsresult CheckAndCreateDir(nsIFile* aFile, const char* aDir,
+ bool aEnsureEmptyDir);
+ nsresult CreateCacheTree();
+ nsresult OpenNSPRHandle(CacheFileHandle* aHandle, bool aCreate = false);
+ void NSPRHandleUsed(CacheFileHandle* aHandle);
+
+ // Removing all cache files during shutdown
+ nsresult SyncRemoveDir(nsIFile* aFile, const char* aDir);
+ void SyncRemoveAllCacheFiles();
+
+ nsresult ScheduleMetadataWriteInternal(CacheFile* aFile);
+ void UnscheduleMetadataWriteInternal(CacheFile* aFile);
+ void ShutdownMetadataWriteSchedulingInternal();
+
+ static nsresult CacheIndexStateChanged();
+ void CacheIndexStateChangedInternal();
+
+ // Dispatches a purgeHTTP background task to delete the cache directoy
+ // indicated by aCacheDirName.
+ // When this feature is enabled, a task will be dispatched at shutdown
+ // or after browser startup (to cleanup potential left-over directories)
+ nsresult DispatchPurgeTask(const nsCString& aCacheDirName,
+ const nsCString& aSecondsToWait,
+ const nsCString& aPurgeExtension);
+
+ // Smart size calculation. UpdateSmartCacheSize() must be called on IO thread.
+ // It is called in EvictIfOverLimitInternal() just before we decide whether to
+ // start overlimit eviction or not and also in OverLimitEvictionInternal()
+ // before we start an eviction loop.
+ nsresult UpdateSmartCacheSize(int64_t aFreeSpace);
+
+ // Memory reporting (private part)
+ size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ static StaticRefPtr<CacheFileIOManager> gInstance;
+
+ TimeStamp mStartTime;
+ // Set true on the IO thread, CLOSE level as part of the internal shutdown
+ // procedure.
+ bool mShuttingDown{false};
+ RefPtr<CacheIOThread> mIOThread;
+ nsCOMPtr<nsIFile> mCacheDirectory;
+#if defined(MOZ_WIDGET_ANDROID)
+ // On Android we add the active profile directory name between the path
+ // and the 'cache2' leaf name. However, to delete any leftover data from
+ // times before we were doing it, we still need to access the directory
+ // w/o the profile name in the path. Here it is stored.
+ nsCOMPtr<nsIFile> mCacheProfilelessDirectory;
+#endif
+ bool mTreeCreated{false};
+ bool mTreeCreationFailed{false};
+ CacheFileHandles mHandles;
+ nsTArray<CacheFileHandle*> mHandlesByLastUsed;
+ nsTArray<CacheFileHandle*> mSpecialHandles;
+ nsTArray<RefPtr<CacheFile> > mScheduledMetadataWrites;
+ nsCOMPtr<nsITimer> mMetadataWritesTimer;
+ bool mOverLimitEvicting{false};
+ // When overlimit eviction is too slow and cache size reaches 105% of the
+ // limit, this flag is set and no other content is cached to prevent
+ // uncontrolled cache growing.
+ bool mCacheSizeOnHardLimit{false};
+ bool mRemovingTrashDirs{false};
+ nsCOMPtr<nsITimer> mTrashTimer;
+ nsCOMPtr<nsIFile> mTrashDir;
+ nsCOMPtr<nsIDirectoryEnumerator> mTrashDirEnumerator;
+ nsTArray<nsCString> mFailedTrashDirs;
+ RefPtr<CacheFileContextEvictor> mContextEvictor;
+ TimeStamp mLastSmartSizeTime;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileInputStream.cpp b/netwerk/cache2/CacheFileInputStream.cpp
new file mode 100644
index 0000000000..99302baf54
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -0,0 +1,734 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheFileInputStream.h"
+
+#include "CacheFile.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(CacheFileInputStream)
+NS_IMETHODIMP_(MozExternalRefCountType)
+CacheFileInputStream::Release() {
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "CacheFileInputStream");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (count == 1) {
+ CacheFileAutoLock lock(mFile);
+ mFile->RemoveInput(this, mStatus);
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+ NS_INTERFACE_MAP_ENTRY(nsITellableStream)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+CacheFileInputStream::CacheFileInputStream(CacheFile* aFile,
+ nsISupports* aEntry,
+ bool aAlternativeData)
+ : mFile(aFile),
+ mPos(0),
+ mStatus(NS_OK),
+ mClosed(false),
+ mInReadSegments(false),
+ mWaitingForUpdate(false),
+ mAlternativeData(aAlternativeData),
+ mListeningForChunk(-1),
+ mCallbackFlags(0),
+ mCacheEntryHandle(aEntry) {
+ LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this));
+
+ if (mAlternativeData) {
+ mPos = mFile->mAltDataOffset;
+ }
+}
+
+CacheFileInputStream::~CacheFileInputStream() {
+ LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
+ MOZ_ASSERT(!mInReadSegments);
+}
+
+// nsIInputStream
+NS_IMETHODIMP
+CacheFileInputStream::Close() {
+ LOG(("CacheFileInputStream::Close() [this=%p]", this));
+ return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Available(uint64_t* _retval) {
+ CacheFileAutoLock lock(mFile);
+
+ if (mClosed) {
+ LOG(
+ ("CacheFileInputStream::Available() - Stream is closed. [this=%p, "
+ "status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+ return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+ }
+
+ EnsureCorrectChunk(false);
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFileInputStream::Available() - EnsureCorrectChunk failed. "
+ "[this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+ return mStatus;
+ }
+
+ nsresult rv = NS_OK;
+ *_retval = 0;
+
+ if (mChunk) {
+ int64_t canRead = mFile->BytesFromChunk(mChunk->Index(), mAlternativeData);
+ canRead -= (mPos % kChunkSize);
+
+ if (canRead > 0) {
+ *_retval = canRead;
+ } else if (canRead == 0 && !mFile->OutputStreamExists(mAlternativeData)) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+ }
+
+ LOG(("CacheFileInputStream::Available() [this=%p, retval=%" PRIu64
+ ", rv=0x%08" PRIx32 "]",
+ this, *_retval, static_cast<uint32_t>(rv)));
+
+ return rv;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::StreamStatus() {
+ CacheFileAutoLock lock(mFile);
+
+ if (mClosed) {
+ LOG(
+ ("CacheFileInputStream::StreamStatus() - Stream is closed. [this=%p, "
+ "status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+ return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ LOG(("CacheFileInputStream::Read() [this=%p, count=%d]", this, aCount));
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) {
+ CacheFileAutoLock lock(mFile);
+
+ LOG(("CacheFileInputStream::ReadSegments() [this=%p, count=%d]", this,
+ aCount));
+
+ nsresult rv = NS_OK;
+
+ *_retval = 0;
+
+ if (mInReadSegments) {
+ LOG(
+ ("CacheFileInputStream::ReadSegments() - Cannot be called while the "
+ "stream is in ReadSegments!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mClosed) {
+ LOG(
+ ("CacheFileInputStream::ReadSegments() - Stream is closed. [this=%p, "
+ "status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ return NS_OK;
+ }
+
+ if (aCount == 0) {
+ return NS_OK;
+ }
+
+ EnsureCorrectChunk(false);
+
+ while (true) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ if (!mChunk) {
+ if (mListeningForChunk == -1) {
+ return NS_OK;
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ CacheFileChunkReadHandle hnd = mChunk->GetReadHandle();
+ int64_t canRead = CanRead(&hnd);
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (canRead < 0) {
+ // file was truncated ???
+ MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+ rv = NS_OK;
+ } else if (canRead > 0) {
+ uint32_t toRead = std::min(static_cast<uint32_t>(canRead), aCount);
+ uint32_t read;
+ const char* buf = hnd.Buf() + (mPos - hnd.Offset());
+
+ mInReadSegments = true;
+ lock.Unlock();
+
+ rv = aWriter(this, aClosure, buf, *_retval, toRead, &read);
+
+ lock.Lock();
+ mInReadSegments = false;
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(read <= toRead,
+ "writer should not write more than we asked it to write");
+
+ *_retval += read;
+ mPos += read;
+ aCount -= read;
+
+ if (!mClosed) {
+ // The last chunk is released after the caller closes this stream.
+ EnsureCorrectChunk(false);
+
+ if (mChunk && aCount) {
+ // Check whether there is more data available to read.
+ continue;
+ }
+ }
+ }
+
+ if (mClosed) {
+ // The stream was closed from aWriter, do the cleanup.
+ CleanUp();
+ }
+
+ rv = NS_OK;
+ } else {
+ if (*_retval == 0 && mFile->OutputStreamExists(mAlternativeData)) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ rv = NS_OK;
+ }
+ }
+
+ break;
+ }
+
+ LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08" PRIx32
+ ", retval=%d]",
+ this, static_cast<uint32_t>(rv), *_retval));
+
+ return rv;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+// nsIAsyncInputStream
+NS_IMETHODIMP
+CacheFileInputStream::CloseWithStatus(nsresult aStatus) {
+ CacheFileAutoLock lock(mFile);
+
+ LOG(("CacheFileInputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32
+ "]",
+ this, static_cast<uint32_t>(aStatus)));
+
+ CloseWithStatusLocked(aStatus);
+ return NS_OK;
+}
+
+void CacheFileInputStream::CloseWithStatusLocked(nsresult aStatus) {
+ LOG(
+ ("CacheFileInputStream::CloseWithStatusLocked() [this=%p, "
+ "aStatus=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(aStatus)));
+
+ if (mClosed) {
+ // We notify listener and null out mCallback immediately after closing
+ // the stream. If we're in ReadSegments we postpone notification until we
+ // step out from ReadSegments. So if the stream is already closed the
+ // following assertion must be true.
+ MOZ_ASSERT(!mCallback || mInReadSegments);
+ return;
+ }
+
+ mClosed = true;
+ mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
+
+ if (!mInReadSegments) {
+ CleanUp();
+ }
+}
+
+void CacheFileInputStream::CleanUp() {
+ MOZ_ASSERT(!mInReadSegments);
+ MOZ_ASSERT(mClosed);
+
+ if (mChunk) {
+ ReleaseChunk();
+ }
+
+ // TODO propagate error from input stream to other streams ???
+
+ MaybeNotifyListener();
+
+ mFile->ReleaseOutsideLock(std::move(mCacheEntryHandle));
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ CacheFileAutoLock lock(mFile);
+
+ LOG(
+ ("CacheFileInputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
+ "requestedCount=%d, eventTarget=%p]",
+ this, aCallback, aFlags, aRequestedCount, aEventTarget));
+
+ if (mInReadSegments) {
+ LOG(
+ ("CacheFileInputStream::AsyncWait() - Cannot be called while the stream"
+ " is in ReadSegments!"));
+ MOZ_ASSERT(false,
+ "Unexpected call. If it's a valid usage implement it. "
+ "Otherwise fix the caller.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mCallback = aCallback;
+ mCallbackFlags = aFlags;
+ mCallbackTarget = aEventTarget;
+
+ if (!mCallback) {
+ if (mWaitingForUpdate) {
+ mChunk->CancelWait(this);
+ mWaitingForUpdate = false;
+ }
+ return NS_OK;
+ }
+
+ if (mClosed) {
+ NotifyListener();
+ return NS_OK;
+ }
+
+ EnsureCorrectChunk(false);
+
+ MaybeNotifyListener();
+
+ return NS_OK;
+}
+
+// nsISeekableStream
+NS_IMETHODIMP
+CacheFileInputStream::Seek(int32_t whence, int64_t offset) {
+ CacheFileAutoLock lock(mFile);
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ LOG(("CacheFileInputStream::Seek() [this=%p, whence=%d, offset=%" PRId64 "]",
+ this, whence, offset));
+
+ if (mInReadSegments) {
+ LOG(
+ ("CacheFileInputStream::Seek() - Cannot be called while the stream is "
+ "in ReadSegments!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mClosed) {
+ LOG(("CacheFileInputStream::Seek() - Stream is closed. [this=%p]", this));
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ int64_t newPos = offset;
+ switch (whence) {
+ case NS_SEEK_SET:
+ if (mAlternativeData) {
+ newPos += mFile->mAltDataOffset;
+ }
+ break;
+ case NS_SEEK_CUR:
+ newPos += mPos;
+ break;
+ case NS_SEEK_END:
+ if (mAlternativeData) {
+ newPos += mFile->mDataSize;
+ } else {
+ newPos += mFile->mAltDataOffset;
+ }
+ break;
+ default:
+ NS_ERROR("invalid whence");
+ return NS_ERROR_INVALID_ARG;
+ }
+ mPos = newPos;
+ EnsureCorrectChunk(false);
+
+ LOG(("CacheFileInputStream::Seek() [this=%p, pos=%" PRId64 "]", this, mPos));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::SetEOF() {
+ MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsITellableStream
+NS_IMETHODIMP
+CacheFileInputStream::Tell(int64_t* _retval) {
+ CacheFileAutoLock lock(mFile);
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ if (mClosed) {
+ LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this));
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *_retval = mPos;
+
+ if (mAlternativeData) {
+ *_retval -= mFile->mAltDataOffset;
+ }
+
+ LOG(("CacheFileInputStream::Tell() [this=%p, retval=%" PRId64 "]", this,
+ *_retval));
+ return NS_OK;
+}
+
+// CacheFileChunkListener
+nsresult CacheFileInputStream::OnChunkRead(nsresult aResult,
+ CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFileInputStream::OnChunkRead should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileInputStream::OnChunkWritten(nsresult aResult,
+ CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFileInputStream::OnChunkWritten should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileInputStream::OnChunkAvailable(nsresult aResult,
+ uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) {
+ CacheFileAutoLock lock(mFile);
+
+ LOG(("CacheFileInputStream::OnChunkAvailable() [this=%p, result=0x%08" PRIx32
+ ", "
+ "idx=%d, chunk=%p]",
+ this, static_cast<uint32_t>(aResult), aChunkIdx, aChunk));
+
+ MOZ_ASSERT(mListeningForChunk != -1);
+
+ if (mListeningForChunk != static_cast<int64_t>(aChunkIdx)) {
+ // This is not a chunk that we're waiting for
+ LOG(
+ ("CacheFileInputStream::OnChunkAvailable() - Notification is for a "
+ "different chunk. [this=%p, listeningForChunk=%" PRId64 "]",
+ this, mListeningForChunk));
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mChunk);
+ MOZ_ASSERT(!mWaitingForUpdate);
+ MOZ_ASSERT(!mInReadSegments);
+ mListeningForChunk = -1;
+
+ if (mClosed) {
+ MOZ_ASSERT(!mCallback);
+
+ LOG(
+ ("CacheFileInputStream::OnChunkAvailable() - Stream is closed, "
+ "ignoring notification. [this=%p]",
+ this));
+
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(aResult)) {
+ mChunk = aChunk;
+ } else if (aResult != NS_ERROR_NOT_AVAILABLE) {
+ // Close the stream with error. The consumer will receive this error later
+ // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
+ // differently since it is returned when the requested chunk is not
+ // available and there is no writer that could create it, i.e. it means that
+ // we've reached the end of the file.
+ CloseWithStatusLocked(aResult);
+
+ return NS_OK;
+ }
+
+ MaybeNotifyListener();
+
+ return NS_OK;
+}
+
+nsresult CacheFileInputStream::OnChunkUpdated(CacheFileChunk* aChunk) {
+ CacheFileAutoLock lock(mFile);
+
+ LOG(("CacheFileInputStream::OnChunkUpdated() [this=%p, idx=%d]", this,
+ aChunk->Index()));
+
+ if (!mWaitingForUpdate) {
+ LOG(
+ ("CacheFileInputStream::OnChunkUpdated() - Ignoring notification since "
+ "mWaitingforUpdate == false. [this=%p]",
+ this));
+
+ return NS_OK;
+ }
+
+ mWaitingForUpdate = false;
+
+ MOZ_ASSERT(mChunk == aChunk);
+
+ MaybeNotifyListener();
+
+ return NS_OK;
+}
+
+void CacheFileInputStream::ReleaseChunk() {
+ mFile->AssertOwnsLock();
+
+ LOG(("CacheFileInputStream::ReleaseChunk() [this=%p, idx=%d]", this,
+ mChunk->Index()));
+
+ MOZ_ASSERT(!mInReadSegments);
+
+ if (mWaitingForUpdate) {
+ LOG(
+ ("CacheFileInputStream::ReleaseChunk() - Canceling waiting for update. "
+ "[this=%p]",
+ this));
+
+ mChunk->CancelWait(this);
+ mWaitingForUpdate = false;
+ }
+
+ mFile->ReleaseOutsideLock(std::move(mChunk));
+}
+
+void CacheFileInputStream::EnsureCorrectChunk(bool aReleaseOnly) {
+ mFile->AssertOwnsLock();
+
+ LOG(("CacheFileInputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
+ this, aReleaseOnly));
+
+ nsresult rv;
+
+ uint32_t chunkIdx = mPos / kChunkSize;
+
+ if (mInReadSegments) {
+ // We must have correct chunk
+ MOZ_ASSERT(mChunk);
+ MOZ_ASSERT(mChunk->Index() == chunkIdx);
+ return;
+ }
+
+ if (mChunk) {
+ if (mChunk->Index() == chunkIdx) {
+ // we have a correct chunk
+ LOG(
+ ("CacheFileInputStream::EnsureCorrectChunk() - Have correct chunk "
+ "[this=%p, idx=%d]",
+ this, chunkIdx));
+
+ return;
+ }
+ ReleaseChunk();
+ }
+
+ MOZ_ASSERT(!mWaitingForUpdate);
+
+ if (aReleaseOnly) return;
+
+ if (mListeningForChunk == static_cast<int64_t>(chunkIdx)) {
+ // We're already waiting for this chunk
+ LOG(
+ ("CacheFileInputStream::EnsureCorrectChunk() - Already listening for "
+ "chunk %" PRId64 " [this=%p]",
+ mListeningForChunk, this));
+
+ return;
+ }
+
+ rv = mFile->GetChunkLocked(chunkIdx, CacheFile::READER, this,
+ getter_AddRefs(mChunk));
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
+ "[this=%p, idx=%d, rv=0x%08" PRIx32 "]",
+ this, chunkIdx, static_cast<uint32_t>(rv)));
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // Close the stream with error. The consumer will receive this error later
+ // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
+ // differently since it is returned when the requested chunk is not
+ // available and there is no writer that could create it, i.e. it means
+ // that we've reached the end of the file.
+ CloseWithStatusLocked(rv);
+
+ return;
+ }
+ } else if (!mChunk) {
+ mListeningForChunk = static_cast<int64_t>(chunkIdx);
+ }
+
+ MaybeNotifyListener();
+}
+
+int64_t CacheFileInputStream::CanRead(CacheFileChunkReadHandle* aHandle) {
+ mFile->AssertOwnsLock();
+
+ MOZ_ASSERT(mChunk);
+ MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+ int64_t retval = aHandle->Offset() + aHandle->DataSize();
+
+ if (!mAlternativeData && mFile->mAltDataOffset != -1 &&
+ mFile->mAltDataOffset < retval) {
+ retval = mFile->mAltDataOffset;
+ }
+
+ retval -= mPos;
+ if (retval <= 0 && NS_FAILED(mChunk->GetStatus())) {
+ CloseWithStatusLocked(mChunk->GetStatus());
+ }
+
+ LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%" PRId64 "]", this,
+ retval));
+
+ return retval;
+}
+
+void CacheFileInputStream::NotifyListener() {
+ mFile->AssertOwnsLock();
+
+ LOG(("CacheFileInputStream::NotifyListener() [this=%p]", this));
+
+ MOZ_ASSERT(mCallback);
+ MOZ_ASSERT(!mInReadSegments);
+
+ if (!mCallbackTarget) {
+ mCallbackTarget = CacheFileIOManager::IOTarget();
+ if (!mCallbackTarget) {
+ LOG(
+ ("CacheFileInputStream::NotifyListener() - Cannot get Cache I/O "
+ "thread! Using main thread for callback."));
+ mCallbackTarget = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> asyncCallback = NS_NewInputStreamReadyEvent(
+ "CacheFileInputStream::NotifyListener", mCallback, mCallbackTarget);
+
+ mCallback = nullptr;
+ mCallbackTarget = nullptr;
+
+ asyncCallback->OnInputStreamReady(this);
+}
+
+void CacheFileInputStream::MaybeNotifyListener() {
+ mFile->AssertOwnsLock();
+
+ LOG(
+ ("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, "
+ "mClosed=%d, mStatus=0x%08" PRIx32
+ ", mChunk=%p, mListeningForChunk=%" PRId64 ", "
+ "mWaitingForUpdate=%d]",
+ this, mCallback.get(), mClosed, static_cast<uint32_t>(mStatus),
+ mChunk.get(), mListeningForChunk, mWaitingForUpdate));
+
+ MOZ_ASSERT(!mInReadSegments);
+
+ if (!mCallback) return;
+
+ if (mClosed || NS_FAILED(mStatus)) {
+ NotifyListener();
+ return;
+ }
+
+ if (!mChunk) {
+ if (mListeningForChunk == -1) {
+ // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ??
+ NotifyListener();
+ }
+ return;
+ }
+
+ MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+ if (mWaitingForUpdate) return;
+
+ CacheFileChunkReadHandle hnd = mChunk->GetReadHandle();
+ int64_t canRead = CanRead(&hnd);
+ if (NS_FAILED(mStatus)) {
+ // CanRead() called CloseWithStatusLocked() which called
+ // MaybeNotifyListener() so the listener was already notified. Stop here.
+ MOZ_ASSERT(!mCallback);
+ return;
+ }
+
+ if (canRead > 0) {
+ if (!(mCallbackFlags & WAIT_CLOSURE_ONLY)) NotifyListener();
+ } else if (canRead == 0) {
+ if (!mFile->OutputStreamExists(mAlternativeData)) {
+ // EOF
+ NotifyListener();
+ } else {
+ mChunk->WaitForUpdate(this);
+ mWaitingForUpdate = true;
+ }
+ } else {
+ // Output have set EOF before mPos?
+ MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+ NotifyListener();
+ }
+}
+
+// Memory reporting
+
+size_t CacheFileInputStream::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ // Everything the stream keeps a reference to is already reported somewhere
+ // else. mFile reports itself. mChunk reported as part of CacheFile. mCallback
+ // is usually CacheFile or a class that is reported elsewhere.
+ return mallocSizeOf(this);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheFileInputStream.h b/netwerk/cache2/CacheFileInputStream.h
new file mode 100644
index 0000000000..8280f9d224
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.h
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileInputStream__h__
+#define CacheFileInputStream__h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "CacheFileChunk.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+
+class CacheFileInputStream : public nsIAsyncInputStream,
+ public nsISeekableStream,
+ public CacheFileChunkListener {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+
+ public:
+ explicit CacheFileInputStream(CacheFile* aFile, nsISupports* aEntry,
+ bool aAlternativeData);
+
+ NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkUpdated(CacheFileChunk* aChunk) override;
+
+ // Memory reporting
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ uint32_t GetPosition() const { return mPos; };
+ bool IsAlternativeData() const { return mAlternativeData; };
+ int64_t GetChunkIdx() const {
+ return mChunk ? static_cast<int64_t>(mChunk->Index()) : -1;
+ };
+
+ private:
+ virtual ~CacheFileInputStream();
+
+ void CloseWithStatusLocked(nsresult aStatus);
+ void CleanUp();
+ void ReleaseChunk();
+ void EnsureCorrectChunk(bool aReleaseOnly);
+
+ // CanRead returns negative value when output stream truncates the data before
+ // the input stream's mPos.
+ int64_t CanRead(CacheFileChunkReadHandle* aHandle);
+ void NotifyListener();
+ void MaybeNotifyListener();
+
+ RefPtr<CacheFile> mFile;
+ RefPtr<CacheFileChunk> mChunk;
+ int64_t mPos;
+ nsresult mStatus;
+ bool mClosed : 1;
+ bool mInReadSegments : 1;
+ bool mWaitingForUpdate : 1;
+ bool const mAlternativeData : 1;
+ int64_t mListeningForChunk;
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ // Held purely for referencing purposes
+ RefPtr<nsISupports> mCacheEntryHandle;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileMetadata.cpp b/netwerk/cache2/CacheFileMetadata.cpp
new file mode 100644
index 0000000000..68d55b1988
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -0,0 +1,1041 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheFileMetadata.h"
+
+#include "CacheFileIOManager.h"
+#include "nsICacheEntry.h"
+#include "CacheHashUtils.h"
+#include "CacheFileChunk.h"
+#include "CacheFileUtils.h"
+#include "nsILoadContextInfo.h"
+#include "nsICacheEntry.h" // for nsICacheEntryMetaDataVisitor
+#include "nsIFile.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "prnetdb.h"
+
+namespace mozilla::net {
+
+#define kMinMetadataRead 1024 // TODO find optimal value from telemetry
+#define kAlignSize 4096
+
+// Most of the cache entries fit into one chunk due to current chunk size. Make
+// sure to tweak this value if kChunkSize is going to change.
+#define kInitialHashArraySize 1
+
+// Initial elements buffer size.
+#define kInitialBufSize 64
+
+// Max size of elements in bytes.
+#define kMaxElementsSize (64 * 1024)
+
+#define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
+
+NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
+
+CacheFileMetadata::CacheFileMetadata(
+ CacheFileHandle* aHandle, const nsACString& aKey,
+ NotNull<CacheFileUtils::CacheFileLock*> aLock)
+ : CacheMemoryConsumer(NORMAL),
+ mHandle(aHandle),
+ mOffset(-1),
+ mIsDirty(false),
+ mAnonymous(false),
+ mAllocExactSize(false),
+ mFirstRead(true),
+ mLock(aLock) {
+ LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
+ this, aHandle, PromiseFlatCString(aKey).get()));
+
+ memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+ mMetaHdr.mVersion = kCacheEntryVersion;
+ mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ mKey = aKey;
+
+ DebugOnly<nsresult> rv{};
+ rv = ParseKey(aKey);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+CacheFileMetadata::CacheFileMetadata(
+ bool aMemoryOnly, bool aPinned, const nsACString& aKey,
+ NotNull<CacheFileUtils::CacheFileLock*> aLock)
+ : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL),
+ mIsDirty(true),
+ mAnonymous(false),
+ mAllocExactSize(false),
+ mFirstRead(true),
+ mLock(aLock) {
+ LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]", this,
+ PromiseFlatCString(aKey).get()));
+
+ memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+ mMetaHdr.mVersion = kCacheEntryVersion;
+ if (aPinned) {
+ AddFlags(kCacheEntryIsPinned);
+ }
+ mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ mKey = aKey;
+ mMetaHdr.mKeySize = mKey.Length();
+
+ DebugOnly<nsresult> rv{};
+ rv = ParseKey(aKey);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+CacheFileMetadata::CacheFileMetadata()
+ : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */),
+ mIsDirty(false),
+ mAnonymous(false),
+ mAllocExactSize(false),
+ mFirstRead(true),
+ mLock(new CacheFileUtils::CacheFileLock()) {
+ LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]", this));
+
+ memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+}
+
+CacheFileMetadata::~CacheFileMetadata() {
+ LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
+
+ MOZ_ASSERT(!mListener);
+
+ if (mHashArray) {
+ CacheFileUtils::FreeBuffer(mHashArray);
+ mHashArray = nullptr;
+ mHashArraySize = 0;
+ }
+
+ if (mBuf) {
+ CacheFileUtils::FreeBuffer(mBuf);
+ mBuf = nullptr;
+ mBufSize = 0;
+ }
+}
+
+void CacheFileMetadata::SetHandle(CacheFileHandle* aHandle) {
+ LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle));
+
+ MOZ_ASSERT(!mHandle);
+
+ mHandle = aHandle;
+}
+
+void CacheFileMetadata::ReadMetadata(CacheFileMetadataListener* aListener) {
+ LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this,
+ aListener));
+
+ MOZ_ASSERT(!mListener);
+ MOZ_ASSERT(!mHashArray);
+ MOZ_ASSERT(!mBuf);
+ MOZ_ASSERT(!mWriteBuf);
+
+ nsresult rv;
+
+ int64_t size = mHandle->FileSize();
+ MOZ_ASSERT(size != -1);
+
+ if (size == 0) {
+ // this is a new entry
+ LOG(
+ ("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
+ "metadata. [this=%p]",
+ this));
+
+ InitEmptyMetadata();
+ aListener->OnMetadataRead(NS_OK);
+ return;
+ }
+
+ if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2 * sizeof(uint32_t))) {
+ // there must be at least checksum, header and offset
+ LOG(
+ ("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
+ "empty metadata. [this=%p, filesize=%" PRId64 "]",
+ this, size));
+
+ InitEmptyMetadata();
+ aListener->OnMetadataRead(NS_OK);
+ return;
+ }
+
+ // Set offset so that we read at least kMinMetadataRead if the file is big
+ // enough.
+ int64_t offset;
+ if (size < kMinMetadataRead) {
+ offset = 0;
+ } else {
+ offset = size - kMinMetadataRead;
+ }
+
+ // round offset to kAlignSize blocks
+ offset = (offset / kAlignSize) * kAlignSize;
+
+ mBufSize = size - offset;
+ mBuf = static_cast<char*>(moz_xmalloc(mBufSize));
+
+ DoMemoryReport(MemoryUsage());
+
+ LOG(
+ ("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
+ "offset=%" PRId64 ", filesize=%" PRId64 " [this=%p]",
+ offset, size, this));
+
+ mReadStart = mozilla::TimeStamp::Now();
+ mListener = aListener;
+ rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
+ " synchronously, creating empty metadata. [this=%p, rv=0x%08" PRIx32
+ "]",
+ this, static_cast<uint32_t>(rv)));
+
+ mListener = nullptr;
+ InitEmptyMetadata();
+ aListener->OnMetadataRead(NS_OK);
+ }
+}
+
+uint32_t CacheFileMetadata::CalcMetadataSize(uint32_t aElementsSize,
+ uint32_t aHashCount) {
+ return sizeof(uint32_t) + // hash of the metadata
+ aHashCount * sizeof(CacheHash::Hash16_t) + // array of chunk hashes
+ sizeof(CacheFileMetadataHeader) + // metadata header
+ mKey.Length() + 1 + // key with trailing null
+ aElementsSize + // elements
+ sizeof(uint32_t); // offset
+}
+
+nsresult CacheFileMetadata::WriteMetadata(
+ uint32_t aOffset, CacheFileMetadataListener* aListener) {
+ LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
+ this, aOffset, aListener));
+
+ MOZ_ASSERT(!mListener);
+ MOZ_ASSERT(!mWriteBuf);
+
+ nsresult rv;
+
+ mIsDirty = false;
+
+ mWriteBuf =
+ static_cast<char*>(malloc(CalcMetadataSize(mElementsSize, mHashCount)));
+ if (!mWriteBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char* p = mWriteBuf + sizeof(uint32_t);
+ if (mHashCount) {
+ memcpy(p, mHashArray, mHashCount * sizeof(CacheHash::Hash16_t));
+ p += mHashCount * sizeof(CacheHash::Hash16_t);
+ }
+ mMetaHdr.WriteToBuf(p);
+ p += sizeof(CacheFileMetadataHeader);
+ memcpy(p, mKey.get(), mKey.Length());
+ p += mKey.Length();
+ *p = 0;
+ p++;
+ if (mElementsSize) {
+ memcpy(p, mBuf, mElementsSize);
+ p += mElementsSize;
+ }
+
+ CacheHash::Hash32_t hash;
+ hash = CacheHash::Hash(mWriteBuf + sizeof(uint32_t),
+ p - mWriteBuf - sizeof(uint32_t));
+ NetworkEndian::writeUint32(mWriteBuf, hash);
+
+ NetworkEndian::writeUint32(p, aOffset);
+ p += sizeof(uint32_t);
+
+ char* writeBuffer = mWriteBuf;
+ if (aListener) {
+ mListener = aListener;
+ } else {
+ // We are not going to pass |this| as a callback so the buffer will be
+ // released by CacheFileIOManager. Just null out mWriteBuf here.
+ mWriteBuf = nullptr;
+ }
+
+ rv = CacheFileIOManager::Write(mHandle, aOffset, writeBuffer, p - writeBuffer,
+ true, true, aListener ? this : nullptr);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
+ "failed synchronously. [this=%p, rv=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+
+ mListener = nullptr;
+ if (mWriteBuf) {
+ CacheFileUtils::FreeBuffer(mWriteBuf);
+ mWriteBuf = nullptr;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ DoMemoryReport(MemoryUsage());
+
+ return NS_OK;
+}
+
+nsresult CacheFileMetadata::SyncReadMetadata(nsIFile* aFile) {
+ LOG(("CacheFileMetadata::SyncReadMetadata() [this=%p]", this));
+
+ MOZ_ASSERT(!mListener);
+ MOZ_ASSERT(!mHandle);
+ MOZ_ASSERT(!mHashArray);
+ MOZ_ASSERT(!mBuf);
+ MOZ_ASSERT(!mWriteBuf);
+ MOZ_ASSERT(mKey.IsEmpty());
+
+ nsresult rv;
+
+ int64_t fileSize;
+ rv = aFile->GetFileSize(&fileSize);
+ if (NS_FAILED(rv)) {
+ // Don't bloat the console
+ return rv;
+ }
+
+ PRFileDesc* fd;
+ rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET);
+ if (offset == -1) {
+ PR_Close(fd);
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t metaOffset;
+ int32_t bytesRead = PR_Read(fd, &metaOffset, sizeof(uint32_t));
+ if (bytesRead != sizeof(uint32_t)) {
+ PR_Close(fd);
+ return NS_ERROR_FAILURE;
+ }
+
+ metaOffset = NetworkEndian::readUint32(&metaOffset);
+ if (metaOffset > fileSize) {
+ PR_Close(fd);
+ return NS_ERROR_FAILURE;
+ }
+
+ mBuf = static_cast<char*>(malloc(fileSize - metaOffset));
+ if (!mBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mBufSize = fileSize - metaOffset;
+
+ DoMemoryReport(MemoryUsage());
+
+ offset = PR_Seek64(fd, metaOffset, PR_SEEK_SET);
+ if (offset == -1) {
+ PR_Close(fd);
+ return NS_ERROR_FAILURE;
+ }
+
+ bytesRead = PR_Read(fd, mBuf, mBufSize);
+ PR_Close(fd);
+ if (bytesRead != static_cast<int32_t>(mBufSize)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = ParseMetadata(metaOffset, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+const char* CacheFileMetadata::GetElement(const char* aKey) {
+ const char* data = mBuf;
+ const char* limit = mBuf + mElementsSize;
+
+ while (data != limit) {
+ size_t maxLen = limit - data;
+ size_t keyLen = strnlen(data, maxLen);
+ MOZ_RELEASE_ASSERT(keyLen != maxLen,
+ "Metadata elements corrupted. Key "
+ "isn't null terminated!");
+ MOZ_RELEASE_ASSERT(keyLen + 1 != maxLen,
+ "Metadata elements corrupted. "
+ "There is no value for the key!");
+
+ const char* value = data + keyLen + 1;
+ maxLen = limit - value;
+ size_t valueLen = strnlen(value, maxLen);
+ MOZ_RELEASE_ASSERT(valueLen != maxLen,
+ "Metadata elements corrupted. Value "
+ "isn't null terminated!");
+
+ if (strcmp(data, aKey) == 0) {
+ LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
+ this, aKey));
+ return value;
+ }
+
+ // point to next pair
+ data += keyLen + valueLen + 2;
+ }
+ LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
+ this, aKey));
+ return nullptr;
+}
+
+nsresult CacheFileMetadata::SetElement(const char* aKey, const char* aValue) {
+ LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]", this,
+ aKey, aValue));
+
+ mLock->Lock().AssertCurrentThreadOwns();
+
+ MarkDirty();
+
+ nsresult rv;
+
+ const uint32_t keySize = strlen(aKey) + 1;
+ char* pos = const_cast<char*>(GetElement(aKey));
+
+ if (!aValue) {
+ // No value means remove the key/value pair completely, if existing
+ if (pos) {
+ uint32_t oldValueSize = strlen(pos) + 1;
+ uint32_t offset = pos - mBuf;
+ uint32_t remainder = mElementsSize - (offset + oldValueSize);
+
+ memmove(pos - keySize, pos + oldValueSize, remainder);
+ mElementsSize -= keySize + oldValueSize;
+ }
+ return NS_OK;
+ }
+
+ const uint32_t valueSize = strlen(aValue) + 1;
+ uint32_t newSize = mElementsSize + valueSize;
+ if (pos) {
+ const uint32_t oldValueSize = strlen(pos) + 1;
+ const uint32_t offset = pos - mBuf;
+ const uint32_t remainder = mElementsSize - (offset + oldValueSize);
+
+ // Update the value in place
+ newSize -= oldValueSize;
+ rv = EnsureBuffer(newSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Move the remainder to the right place
+ pos = mBuf + offset;
+ memmove(pos + valueSize, pos + oldValueSize, remainder);
+ } else {
+ // allocate new meta data element
+ newSize += keySize;
+ rv = EnsureBuffer(newSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Add after last element
+ pos = mBuf + mElementsSize;
+ memcpy(pos, aKey, keySize);
+ pos += keySize;
+ }
+
+ // Update value
+ memcpy(pos, aValue, valueSize);
+ mElementsSize = newSize;
+
+ return NS_OK;
+}
+
+void CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor* aVisitor) {
+ const char* data = mBuf;
+ const char* limit = mBuf + mElementsSize;
+
+ while (data < limit) {
+ // Point to the value part
+ const char* value = data + strlen(data) + 1;
+ MOZ_ASSERT(value < limit, "Metadata elements corrupted");
+
+ aVisitor->OnMetaDataElement(data, value);
+
+ // Skip value part
+ data = value + strlen(value) + 1;
+ }
+
+ MOZ_ASSERT(data == limit, "Metadata elements corrupted");
+}
+
+CacheHash::Hash16_t CacheFileMetadata::GetHash(uint32_t aIndex) {
+ mLock->Lock().AssertCurrentThreadOwns();
+
+ MOZ_ASSERT(aIndex < mHashCount);
+ return NetworkEndian::readUint16(&mHashArray[aIndex]);
+}
+
+nsresult CacheFileMetadata::SetHash(uint32_t aIndex,
+ CacheHash::Hash16_t aHash) {
+ LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]", this, aIndex,
+ aHash));
+
+ mLock->Lock().AssertCurrentThreadOwns();
+
+ MarkDirty();
+
+ MOZ_ASSERT(aIndex <= mHashCount);
+
+ if (aIndex > mHashCount) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (aIndex == mHashCount) {
+ if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) {
+ // reallocate hash array buffer
+ if (mHashArraySize == 0) {
+ mHashArraySize = kInitialHashArraySize * sizeof(CacheHash::Hash16_t);
+ } else {
+ mHashArraySize *= 2;
+ }
+ mHashArray = static_cast<CacheHash::Hash16_t*>(
+ moz_xrealloc(mHashArray, mHashArraySize));
+ }
+
+ mHashCount++;
+ }
+
+ NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
+
+ DoMemoryReport(MemoryUsage());
+
+ return NS_OK;
+}
+
+nsresult CacheFileMetadata::RemoveHash(uint32_t aIndex) {
+ LOG(("CacheFileMetadata::RemoveHash() [this=%p, idx=%d]", this, aIndex));
+
+ mLock->Lock().AssertCurrentThreadOwns();
+
+ MarkDirty();
+
+ MOZ_ASSERT((aIndex + 1) == mHashCount, "Can remove only last hash!");
+
+ if (aIndex + 1 != mHashCount) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mHashCount--;
+ return NS_OK;
+}
+
+void CacheFileMetadata::AddFlags(uint32_t aFlags) {
+ MarkDirty(false);
+ mMetaHdr.mFlags |= aFlags;
+}
+
+void CacheFileMetadata::RemoveFlags(uint32_t aFlags) {
+ MarkDirty(false);
+ mMetaHdr.mFlags &= ~aFlags;
+}
+
+void CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime) {
+ LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
+ this, aExpirationTime));
+
+ MarkDirty(false);
+ mMetaHdr.mExpirationTime = aExpirationTime;
+}
+
+void CacheFileMetadata::SetFrecency(uint32_t aFrecency) {
+ LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]", this,
+ (double)aFrecency));
+
+ MarkDirty(false);
+ mMetaHdr.mFrecency = aFrecency;
+}
+
+void CacheFileMetadata::OnFetched() {
+ MarkDirty(false);
+
+ mMetaHdr.mLastFetched = NOW_SECONDS();
+ ++mMetaHdr.mFetchCount;
+}
+
+void CacheFileMetadata::MarkDirty(bool aUpdateLastModified) {
+ mIsDirty = true;
+ if (aUpdateLastModified) {
+ mMetaHdr.mLastModified = NOW_SECONDS();
+ }
+}
+
+nsresult CacheFileMetadata::OnFileOpened(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileMetadata::OnDataWritten(CacheFileHandle* aHandle,
+ const char* aBuf, nsresult aResult) {
+ LOG(
+ ("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, "
+ "result=0x%08" PRIx32 "]",
+ this, aHandle, static_cast<uint32_t>(aResult)));
+
+ nsCOMPtr<CacheFileMetadataListener> listener;
+ {
+ MutexAutoLock lock(mLock->Lock());
+
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mWriteBuf);
+
+ CacheFileUtils::FreeBuffer(mWriteBuf);
+ mWriteBuf = nullptr;
+
+ mListener.swap(listener);
+ DoMemoryReport(MemoryUsage());
+ }
+
+ listener->OnMetadataWritten(aResult);
+
+ return NS_OK;
+}
+
+nsresult CacheFileMetadata::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) {
+ LOG((
+ "CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32
+ "]",
+ this, aHandle, static_cast<uint32_t>(aResult)));
+
+ MOZ_ASSERT(mListener);
+
+ nsresult rv;
+ nsCOMPtr<CacheFileMetadataListener> listener;
+
+ auto notifyListenerOutsideLock = mozilla::MakeScopeExit([&listener] {
+ if (listener) {
+ listener->OnMetadataRead(NS_OK);
+ }
+ });
+
+ MutexAutoLock lock(mLock->Lock());
+
+ if (NS_FAILED(aResult)) {
+ LOG(
+ ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
+ ", creating empty metadata. [this=%p, rv=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(aResult)));
+
+ InitEmptyMetadata();
+
+ mListener.swap(listener);
+ return NS_OK;
+ }
+
+ if (mFirstRead) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_TIME_MS, mReadStart);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_CACHE_METADATA_SECOND_READ_TIME_MS, mReadStart);
+ }
+
+ // check whether we have read all necessary data
+ uint32_t realOffset =
+ NetworkEndian::readUint32(mBuf + mBufSize - sizeof(uint32_t));
+
+ int64_t size = mHandle->FileSize();
+ MOZ_ASSERT(size != -1);
+
+ if (realOffset >= size) {
+ LOG(
+ ("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating "
+ "empty metadata. [this=%p, realOffset=%u, size=%" PRId64 "]",
+ this, realOffset, size));
+
+ InitEmptyMetadata();
+
+ mListener.swap(listener);
+ return NS_OK;
+ }
+
+ uint32_t maxHashCount = size / kChunkSize;
+ uint32_t maxMetadataSize = CalcMetadataSize(kMaxElementsSize, maxHashCount);
+ if (size - realOffset > maxMetadataSize) {
+ LOG(
+ ("CacheFileMetadata::OnDataRead() - Invalid realOffset, metadata would "
+ "be too big, creating empty metadata. [this=%p, realOffset=%u, "
+ "maxMetadataSize=%u, size=%" PRId64 "]",
+ this, realOffset, maxMetadataSize, size));
+
+ InitEmptyMetadata();
+
+ mListener.swap(listener);
+ return NS_OK;
+ }
+
+ uint32_t usedOffset = size - mBufSize;
+
+ if (realOffset < usedOffset) {
+ uint32_t missing = usedOffset - realOffset;
+ // we need to read more data
+ char* newBuf = static_cast<char*>(realloc(mBuf, mBufSize + missing));
+ if (!newBuf) {
+ LOG(
+ ("CacheFileMetadata::OnDataRead() - Error allocating %d more bytes "
+ "for the missing part of the metadata, creating empty metadata. "
+ "[this=%p]",
+ missing, this));
+
+ InitEmptyMetadata();
+
+ mListener.swap(listener);
+ return NS_OK;
+ }
+
+ mBuf = newBuf;
+ memmove(mBuf + missing, mBuf, mBufSize);
+ mBufSize += missing;
+
+ DoMemoryReport(MemoryUsage());
+
+ LOG(
+ ("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
+ "have full metadata. [this=%p]",
+ missing, this));
+
+ mFirstRead = false;
+ mReadStart = mozilla::TimeStamp::Now();
+ rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() "
+ "failed synchronously, creating empty metadata. [this=%p, "
+ "rv=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+
+ InitEmptyMetadata();
+
+ mListener.swap(listener);
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_METADATA_SIZE_2,
+ size - realOffset);
+
+ // We have all data according to offset information at the end of the entry.
+ // Try to parse it.
+ rv = ParseMetadata(realOffset, realOffset - usedOffset, true);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
+ "empty metadata. [this=%p]",
+ this));
+ InitEmptyMetadata();
+ } else {
+ // Shrink elements buffer.
+ mBuf = static_cast<char*>(moz_xrealloc(mBuf, mElementsSize));
+ mBufSize = mElementsSize;
+
+ // There is usually no or just one call to SetMetadataElement() when the
+ // metadata is parsed from disk. Avoid allocating power of two sized buffer
+ // which we do in case of newly created metadata.
+ mAllocExactSize = true;
+ }
+
+ mListener.swap(listener);
+
+ return NS_OK;
+}
+
+nsresult CacheFileMetadata::OnFileDoomed(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileMetadata::OnEOFSet(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileMetadata::OnFileRenamed(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFileMetadata::OnFileRenamed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void CacheFileMetadata::InitEmptyMetadata() {
+ if (mBuf) {
+ CacheFileUtils::FreeBuffer(mBuf);
+ mBuf = nullptr;
+ mBufSize = 0;
+ }
+ mAllocExactSize = false;
+ mOffset = 0;
+ mMetaHdr.mVersion = kCacheEntryVersion;
+ mMetaHdr.mFetchCount = 0;
+ mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ mMetaHdr.mKeySize = mKey.Length();
+
+ // Deliberately not touching the "kCacheEntryIsPinned" flag.
+
+ DoMemoryReport(MemoryUsage());
+
+ // We're creating a new entry. If there is any old data truncate it.
+ if (mHandle) {
+ mHandle->SetPinned(Pinned());
+ // We can pronounce the handle as invalid now, because it simply
+ // doesn't have the correct metadata. This will cause IO operations
+ // be bypassed during shutdown (mainly dooming it, when a channel
+ // is canceled by closing the window.)
+ mHandle->SetInvalid();
+ if (mHandle->FileExists() && mHandle->FileSize()) {
+ CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
+ }
+ }
+}
+
+nsresult CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset,
+ uint32_t aBufOffset, bool aHaveKey) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
+ "bufOffset=%d, haveKey=%u]",
+ this, aMetaOffset, aBufOffset, aHaveKey));
+
+ nsresult rv;
+
+ uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
+ uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
+ uint32_t hashCount = aMetaOffset / kChunkSize;
+ if (aMetaOffset % kChunkSize) hashCount++;
+ uint32_t hashesLen = hashCount * sizeof(CacheHash::Hash16_t);
+ uint32_t hdrOffset = hashesOffset + hashesLen;
+ uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);
+
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() [this=%p]\n metaposOffset=%d\n "
+ "hashesOffset=%d\n hashCount=%d\n hashesLen=%d\n hdfOffset=%d\n "
+ "keyOffset=%d\n",
+ this, metaposOffset, hashesOffset, hashCount, hashesLen, hdrOffset,
+ keyOffset));
+
+ if (keyOffset > metaposOffset) {
+ LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
+ this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
+
+ if (mMetaHdr.mVersion == 1) {
+ // Backward compatibility before we've added flags to the header
+ keyOffset -= sizeof(uint32_t);
+ } else if (mMetaHdr.mVersion == 2) {
+ // Version 2 just lacks the ability to store alternative data. Nothing to do
+ // here.
+ } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
+ "[version=0x%x, this=%p]",
+ mMetaHdr.mVersion, this));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Update the version stored in the header to make writes
+ // store the header in the current version form.
+ mMetaHdr.mVersion = kCacheEntryVersion;
+
+ uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
+
+ if (elementsOffset > metaposOffset) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
+ "[this=%p]",
+ elementsOffset, this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // check that key ends with \0
+ if (mBuf[elementsOffset - 1] != 0) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
+ "[this=%p]",
+ this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (!aHaveKey) {
+ // get the key form metadata
+ mKey.Assign(mBuf + keyOffset, mMetaHdr.mKeySize);
+
+ rv = ParseKey(mKey);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ if (mMetaHdr.mKeySize != mKey.Length()) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s "
+ "[this=%p]",
+ nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s "
+ "[this=%p]",
+ nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+
+ // check metadata hash (data from hashesOffset to metaposOffset)
+ CacheHash::Hash32_t hashComputed, hashExpected;
+ hashComputed =
+ CacheHash::Hash(mBuf + hashesOffset, metaposOffset - hashesOffset);
+ hashExpected = NetworkEndian::readUint32(mBuf + aBufOffset);
+
+ if (hashComputed != hashExpected) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
+ "the metadata is %x, hash in file is %x [this=%p]",
+ hashComputed, hashExpected, this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // check elements
+ rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mHandle) {
+ if (!mHandle->SetPinned(Pinned())) {
+ LOG(
+ ("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
+ "pinning state, truncate the file [this=%p, pinned=%d]",
+ this, Pinned()));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+
+ mHashArraySize = hashesLen;
+ mHashCount = hashCount;
+ if (mHashArraySize) {
+ mHashArray = static_cast<CacheHash::Hash16_t*>(moz_xmalloc(mHashArraySize));
+ memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
+ }
+
+ MarkDirty();
+
+ mElementsSize = metaposOffset - elementsOffset;
+ memmove(mBuf, mBuf + elementsOffset, mElementsSize);
+ mOffset = aMetaOffset;
+
+ DoMemoryReport(MemoryUsage());
+
+ return NS_OK;
+}
+
+nsresult CacheFileMetadata::CheckElements(const char* aBuf, uint32_t aSize) {
+ if (aSize) {
+ // Check if the metadata ends with a zero byte.
+ if (aBuf[aSize - 1] != 0) {
+ NS_ERROR("Metadata elements are not null terminated");
+ LOG(
+ ("CacheFileMetadata::CheckElements() - Elements are not null "
+ "terminated. [this=%p]",
+ this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ // Check that there are an even number of zero bytes
+ // to match the pattern { key \0 value \0 }
+ bool odd = false;
+ for (uint32_t i = 0; i < aSize; i++) {
+ if (aBuf[i] == 0) odd = !odd;
+ }
+ if (odd) {
+ NS_ERROR("Metadata elements are malformed");
+ LOG(
+ ("CacheFileMetadata::CheckElements() - Elements are malformed. "
+ "[this=%p]",
+ this));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult CacheFileMetadata::EnsureBuffer(uint32_t aSize) {
+ if (aSize > kMaxElementsSize) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mBufSize < aSize) {
+ if (mAllocExactSize) {
+ // If this is not the only allocation, use power of two for following
+ // allocations.
+ mAllocExactSize = false;
+ } else {
+ // find smallest power of 2 greater than or equal to aSize
+ --aSize;
+ aSize |= aSize >> 1;
+ aSize |= aSize >> 2;
+ aSize |= aSize >> 4;
+ aSize |= aSize >> 8;
+ aSize |= aSize >> 16;
+ ++aSize;
+ }
+
+ if (aSize < kInitialBufSize) {
+ aSize = kInitialBufSize;
+ }
+
+ char* newBuf = static_cast<char*>(realloc(mBuf, aSize));
+ if (!newBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mBufSize = aSize;
+ mBuf = newBuf;
+
+ DoMemoryReport(MemoryUsage());
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileMetadata::ParseKey(const nsACString& aKey) {
+ nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
+ NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);
+
+ mAnonymous = info->IsAnonymous();
+ mOriginAttributes = *info->OriginAttributesPtr();
+
+ return NS_OK;
+}
+
+// Memory reporting
+
+size_t CacheFileMetadata::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+ // mHandle reported via CacheFileIOManager.
+ n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mallocSizeOf(mHashArray);
+ n += mallocSizeOf(mBuf);
+ // Ignore mWriteBuf, it's not safe to access it when metadata is being
+ // written and it's null otherwise.
+ // mListener is usually the owning CacheFile.
+
+ return n;
+}
+
+size_t CacheFileMetadata::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheFileMetadata.h b/netwerk/cache2/CacheFileMetadata.h
new file mode 100644
index 0000000000..329a9629e7
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -0,0 +1,245 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileMetadata__h__
+#define CacheFileMetadata__h__
+
+#include "CacheFileIOManager.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "CacheObserver.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/NotNull.h"
+#include "nsString.h"
+
+class nsICacheEntryMetaDataVisitor;
+
+namespace mozilla {
+namespace net {
+
+namespace CacheFileUtils {
+class CacheFileLock;
+};
+
+// Flags stored in CacheFileMetadataHeader.mFlags
+
+// Whether an entry is a pinned entry (created with
+// nsICacheStorageService.pinningCacheStorage.)
+static const uint32_t kCacheEntryIsPinned = 1 << 0;
+
+// By multiplying with the current half-life we convert the frecency
+// to time independent of half-life value. The range fits 32bits.
+// When decay time changes on next run of the browser, we convert
+// the frecency value to a correct internal representation again.
+// It might not be 100% accurate, but for the purpose it suffice.
+#define FRECENCY2INT(aFrecency) \
+ ((uint32_t)((aFrecency) * CacheObserver::HalfLifeSeconds()))
+#define INT2FRECENCY(aInt) \
+ ((double)(aInt) / (double)CacheObserver::HalfLifeSeconds())
+
+#define kCacheEntryVersion 3
+
+#pragma pack(push)
+#pragma pack(1)
+
+class CacheFileMetadataHeader {
+ public:
+ uint32_t mVersion;
+ uint32_t mFetchCount;
+ uint32_t mLastFetched;
+ uint32_t mLastModified;
+ uint32_t mFrecency;
+ uint32_t mExpirationTime;
+ uint32_t mKeySize;
+ uint32_t mFlags;
+
+ void WriteToBuf(void* aBuf) {
+ EnsureCorrectClassSize();
+
+ uint8_t* ptr = static_cast<uint8_t*>(aBuf);
+ MOZ_ASSERT(mVersion == kCacheEntryVersion);
+ NetworkEndian::writeUint32(ptr, mVersion);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint32(ptr, mFetchCount);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint32(ptr, mLastFetched);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint32(ptr, mLastModified);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint32(ptr, mFrecency);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint32(ptr, mExpirationTime);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint32(ptr, mKeySize);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint32(ptr, mFlags);
+ }
+
+ void ReadFromBuf(const void* aBuf) {
+ EnsureCorrectClassSize();
+
+ const uint8_t* ptr = static_cast<const uint8_t*>(aBuf);
+ mVersion = BigEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mFetchCount = BigEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mLastFetched = BigEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mLastModified = BigEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mFrecency = BigEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mExpirationTime = BigEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mKeySize = BigEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ if (mVersion >= 2) {
+ mFlags = BigEndian::readUint32(ptr);
+ } else {
+ mFlags = 0;
+ }
+ }
+
+ inline void EnsureCorrectClassSize() {
+ static_assert(
+ (sizeof(mVersion) + sizeof(mFetchCount) + sizeof(mLastFetched) +
+ sizeof(mLastModified) + sizeof(mFrecency) + sizeof(mExpirationTime) +
+ sizeof(mKeySize)) +
+ sizeof(mFlags) ==
+ sizeof(CacheFileMetadataHeader),
+ "Unexpected sizeof(CacheFileMetadataHeader)!");
+ }
+};
+
+#pragma pack(pop)
+
+#define CACHEFILEMETADATALISTENER_IID \
+ { /* a9e36125-3f01-4020-9540-9dafa8d31ba7 */ \
+ 0xa9e36125, 0x3f01, 0x4020, { \
+ 0x95, 0x40, 0x9d, 0xaf, 0xa8, 0xd3, 0x1b, 0xa7 \
+ } \
+ }
+
+class CacheFileMetadataListener : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEMETADATALISTENER_IID)
+
+ NS_IMETHOD OnMetadataRead(nsresult aResult) = 0;
+ NS_IMETHOD OnMetadataWritten(nsresult aResult) = 0;
+ virtual bool IsKilled() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileMetadataListener,
+ CACHEFILEMETADATALISTENER_IID)
+
+class CacheFileMetadata final : public CacheFileIOListener,
+ public CacheMemoryConsumer {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CacheFileMetadata(CacheFileHandle* aHandle, const nsACString& aKey,
+ NotNull<CacheFileUtils::CacheFileLock*> aLock);
+ CacheFileMetadata(bool aMemoryOnly, bool aPinned, const nsACString& aKey,
+ NotNull<CacheFileUtils::CacheFileLock*> aLock);
+ CacheFileMetadata();
+
+ void SetHandle(CacheFileHandle* aHandle);
+
+ const nsACString& GetKey() const { return mKey; }
+
+ void ReadMetadata(CacheFileMetadataListener* aListener);
+ uint32_t CalcMetadataSize(uint32_t aElementsSize, uint32_t aHashCount);
+ nsresult WriteMetadata(uint32_t aOffset,
+ CacheFileMetadataListener* aListener);
+ nsresult SyncReadMetadata(nsIFile* aFile);
+
+ bool IsAnonymous() const { return mAnonymous; }
+ mozilla::OriginAttributes const& OriginAttributes() const {
+ return mOriginAttributes;
+ }
+ bool Pinned() const { return !!(mMetaHdr.mFlags & kCacheEntryIsPinned); }
+
+ const char* GetElement(const char* aKey);
+ nsresult SetElement(const char* aKey, const char* aValue);
+ void Visit(nsICacheEntryMetaDataVisitor* aVisitor);
+
+ CacheHash::Hash16_t GetHash(uint32_t aIndex);
+ nsresult SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash);
+ nsresult RemoveHash(uint32_t aIndex);
+
+ void AddFlags(uint32_t aFlags);
+ void RemoveFlags(uint32_t aFlags);
+ uint32_t GetFlags() const { return mMetaHdr.mFlags; }
+ void SetExpirationTime(uint32_t aExpirationTime);
+ uint32_t GetExpirationTime() const { return mMetaHdr.mExpirationTime; }
+ void SetFrecency(uint32_t aFrecency);
+ uint32_t GetFrecency() const { return mMetaHdr.mFrecency; }
+ uint32_t GetLastModified() const { return mMetaHdr.mLastModified; }
+ uint32_t GetLastFetched() const { return mMetaHdr.mLastFetched; }
+ uint32_t GetFetchCount() const { return mMetaHdr.mFetchCount; }
+ // Called by upper layers to indicate the entry this metadata belongs
+ // with has been fetched, i.e. delivered to the consumer.
+ void OnFetched();
+
+ int64_t Offset() { return mOffset; }
+ uint32_t ElementsSize() { return mElementsSize; }
+ void MarkDirty(bool aUpdateLastModified = true);
+ bool IsDirty() { return mIsDirty; }
+ uint32_t MemoryUsage() {
+ return sizeof(CacheFileMetadata) + mHashArraySize + mBufSize;
+ }
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) override;
+ virtual bool IsKilled() override {
+ return mListener && mListener->IsKilled();
+ }
+ void InitEmptyMetadata();
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ virtual ~CacheFileMetadata();
+
+ nsresult ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset,
+ bool aHaveKey);
+ nsresult CheckElements(const char* aBuf, uint32_t aSize);
+ nsresult EnsureBuffer(uint32_t aSize);
+ nsresult ParseKey(const nsACString& aKey);
+
+ RefPtr<CacheFileHandle> mHandle;
+ nsCString mKey;
+ CacheHash::Hash16_t* mHashArray{nullptr};
+ uint32_t mHashArraySize{0};
+ uint32_t mHashCount{0};
+ int64_t mOffset{0};
+ // used for parsing, then points to elements
+ char* mBuf{nullptr};
+ uint32_t mBufSize{0};
+ char* mWriteBuf{nullptr};
+ CacheFileMetadataHeader mMetaHdr{0};
+ uint32_t mElementsSize{0};
+ bool mIsDirty : 1;
+ bool mAnonymous : 1;
+ bool mAllocExactSize : 1;
+ bool mFirstRead : 1;
+ mozilla::OriginAttributes mOriginAttributes;
+ mozilla::TimeStamp mReadStart;
+ nsCOMPtr<CacheFileMetadataListener> mListener;
+ RefPtr<CacheFileUtils::CacheFileLock> mLock;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileOutputStream.cpp b/netwerk/cache2/CacheFileOutputStream.cpp
new file mode 100644
index 0000000000..985082efba
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -0,0 +1,481 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheFileOutputStream.h"
+
+#include "CacheFile.h"
+#include "CacheEntry.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include <algorithm>
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(CacheFileOutputStream)
+NS_IMETHODIMP_(MozExternalRefCountType)
+CacheFileOutputStream::Release() {
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "CacheFileOutputStream");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ {
+ CacheFileAutoLock lock(mFile);
+ mFile->RemoveOutput(this, mStatus);
+ }
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+ NS_INTERFACE_MAP_ENTRY(nsITellableStream)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
+NS_INTERFACE_MAP_END
+
+CacheFileOutputStream::CacheFileOutputStream(
+ CacheFile* aFile, CacheOutputCloseListener* aCloseListener,
+ bool aAlternativeData)
+ : mFile(aFile),
+ mCloseListener(aCloseListener),
+ mPos(0),
+ mClosed(false),
+ mAlternativeData(aAlternativeData),
+ mStatus(NS_OK),
+ mCallbackFlags(0) {
+ LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this));
+
+ if (mAlternativeData) {
+ mPos = mFile->mAltDataOffset;
+ }
+}
+
+CacheFileOutputStream::~CacheFileOutputStream() {
+ LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this));
+}
+
+// nsIOutputStream
+NS_IMETHODIMP
+CacheFileOutputStream::Close() {
+ LOG(("CacheFileOutputStream::Close() [this=%p]", this));
+ return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::Flush() {
+ // TODO do we need to implement flush ???
+ LOG(("CacheFileOutputStream::Flush() [this=%p]", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::StreamStatus() {
+ CacheFileAutoLock lock(mFile);
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ LOG(("CacheFileOutputStream::Close() [this=%p]", this));
+ if (mClosed) {
+ return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ CacheFileAutoLock lock(mFile);
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount));
+
+ if (mClosed) {
+ LOG(
+ ("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
+ "status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+ }
+
+ if (!mFile->mSkipSizeCheck &&
+ CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) {
+ LOG(("CacheFileOutputStream::Write() - Entry is too big. [this=%p]", this));
+
+ CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ // We use 64-bit offset when accessing the file, unfortunately we use 32-bit
+ // metadata offset, so we cannot handle data bigger than 4GB.
+ if (mPos + aCount > PR_UINT32_MAX) {
+ LOG(("CacheFileOutputStream::Write() - Entry's size exceeds 4GB. [this=%p]",
+ this));
+
+ CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ *_retval = aCount;
+
+ while (aCount) {
+ EnsureCorrectChunk(false);
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ FillHole();
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
+ uint32_t canWrite = kChunkSize - chunkOffset;
+ uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount);
+
+ CacheFileChunkWriteHandle hnd =
+ mChunk->GetWriteHandle(chunkOffset + thisWrite);
+ if (!hnd.Buf()) {
+ CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy(hnd.Buf() + chunkOffset, aBuf, thisWrite);
+ hnd.UpdateDataSize(chunkOffset, thisWrite);
+
+ mPos += thisWrite;
+ aBuf += thisWrite;
+ aCount -= thisWrite;
+ }
+
+ EnsureCorrectChunk(true);
+
+ LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]", *_retval,
+ this));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) {
+ LOG(
+ ("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p"
+ ", count=%d]",
+ this, aFromStream, aCount));
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) {
+ LOG(
+ ("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, "
+ "count=%d]",
+ this, aCount));
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::IsNonBlocking(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+// nsIAsyncOutputStream
+NS_IMETHODIMP
+CacheFileOutputStream::CloseWithStatus(nsresult aStatus) {
+ CacheFileAutoLock lock(mFile);
+
+ LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32
+ "]",
+ this, static_cast<uint32_t>(aStatus)));
+
+ return CloseWithStatusLocked(aStatus);
+}
+
+nsresult CacheFileOutputStream::CloseWithStatusLocked(nsresult aStatus) {
+ LOG(
+ ("CacheFileOutputStream::CloseWithStatusLocked() [this=%p, "
+ "aStatus=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(aStatus)));
+
+ if (mClosed) {
+ MOZ_ASSERT(!mCallback);
+ return NS_OK;
+ }
+
+ mClosed = true;
+ mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
+
+ if (mChunk) {
+ ReleaseChunk();
+ }
+
+ if (mCallback) {
+ NotifyListener();
+ }
+
+ mFile->RemoveOutput(this, mStatus);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ CacheFileAutoLock lock(mFile);
+
+ LOG(
+ ("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
+ "requestedCount=%d, eventTarget=%p]",
+ this, aCallback, aFlags, aRequestedCount, aEventTarget));
+
+ mCallback = aCallback;
+ mCallbackFlags = aFlags;
+ mCallbackTarget = aEventTarget;
+
+ if (!mCallback) return NS_OK;
+
+ // The stream is blocking so it is writable at any time
+ if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY)) NotifyListener();
+
+ return NS_OK;
+}
+
+// nsISeekableStream
+NS_IMETHODIMP
+CacheFileOutputStream::Seek(int32_t whence, int64_t offset) {
+ CacheFileAutoLock lock(mFile);
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%" PRId64 "]",
+ this, whence, offset));
+
+ if (mClosed) {
+ LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this));
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ int64_t newPos = offset;
+ switch (whence) {
+ case NS_SEEK_SET:
+ if (mAlternativeData) {
+ newPos += mFile->mAltDataOffset;
+ }
+ break;
+ case NS_SEEK_CUR:
+ newPos += mPos;
+ break;
+ case NS_SEEK_END:
+ if (mAlternativeData) {
+ newPos += mFile->mDataSize;
+ } else {
+ newPos += mFile->mAltDataOffset;
+ }
+ break;
+ default:
+ NS_ERROR("invalid whence");
+ return NS_ERROR_INVALID_ARG;
+ }
+ mPos = newPos;
+ EnsureCorrectChunk(true);
+
+ LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%" PRId64 "]", this, mPos));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::SetEOF() {
+ MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented");
+ // Right now we don't use SetEOF(). If we ever need this method, we need
+ // to think about what to do with input streams that already points beyond
+ // new EOF.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsITellableStream
+NS_IMETHODIMP
+CacheFileOutputStream::Tell(int64_t* _retval) {
+ CacheFileAutoLock lock(mFile);
+ mFile->AssertOwnsLock(); // For thread-safety analysis
+
+ if (mClosed) {
+ LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this));
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *_retval = mPos;
+
+ if (mAlternativeData) {
+ *_retval -= mFile->mAltDataOffset;
+ }
+
+ LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%" PRId64 "]", this,
+ *_retval));
+ return NS_OK;
+}
+
+// CacheFileChunkListener
+nsresult CacheFileOutputStream::OnChunkRead(nsresult aResult,
+ CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileOutputStream::OnChunkWritten(nsresult aResult,
+ CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFileOutputStream::OnChunkWritten should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileOutputStream::OnChunkAvailable(nsresult aResult,
+ uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFileOutputStream::OnChunkAvailable should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFileOutputStream::OnChunkUpdated(CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFileOutputStream::OnChunkUpdated should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void CacheFileOutputStream::NotifyCloseListener() {
+ RefPtr<CacheOutputCloseListener> listener;
+ listener.swap(mCloseListener);
+ if (!listener) return;
+
+ listener->OnOutputClosed();
+}
+
+void CacheFileOutputStream::ReleaseChunk() {
+ mFile->AssertOwnsLock();
+
+ LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]", this,
+ mChunk->Index()));
+
+ // If the chunk didn't write any data we need to remove hash for this chunk
+ // that was added when the chunk was created in CacheFile::GetChunkLocked.
+ if (mChunk->DataSize() == 0) {
+ // It must be due to a failure, we don't create a new chunk when we don't
+ // have data to write.
+ MOZ_ASSERT(NS_FAILED(mChunk->GetStatus()));
+ mFile->mMetadata->RemoveHash(mChunk->Index());
+ }
+
+ mFile->ReleaseOutsideLock(std::move(mChunk));
+}
+
+void CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly) {
+ mFile->AssertOwnsLock();
+
+ LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
+ this, aReleaseOnly));
+
+ uint32_t chunkIdx = mPos / kChunkSize;
+
+ if (mChunk) {
+ if (mChunk->Index() == chunkIdx) {
+ // we have a correct chunk
+ LOG(
+ ("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk "
+ "[this=%p, idx=%d]",
+ this, chunkIdx));
+
+ return;
+ }
+ ReleaseChunk();
+ }
+
+ if (aReleaseOnly) return;
+
+ nsresult rv;
+ rv = mFile->GetChunkLocked(chunkIdx, CacheFile::WRITER, nullptr,
+ getter_AddRefs(mChunk));
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileOutputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
+ "[this=%p, idx=%d, rv=0x%08" PRIx32 "]",
+ this, chunkIdx, static_cast<uint32_t>(rv)));
+ CloseWithStatusLocked(rv);
+ }
+}
+
+void CacheFileOutputStream::FillHole() {
+ mFile->AssertOwnsLock();
+
+ MOZ_ASSERT(mChunk);
+ MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+ uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize;
+ if (mChunk->DataSize() >= pos) return;
+
+ LOG(
+ ("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range "
+ "%d-%d [this=%p]",
+ mChunk->Index(), mChunk->DataSize(), pos - 1, this));
+
+ CacheFileChunkWriteHandle hnd = mChunk->GetWriteHandle(pos);
+ if (!hnd.Buf()) {
+ CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ uint32_t offset = hnd.DataSize();
+ memset(hnd.Buf() + offset, 0, pos - offset);
+ hnd.UpdateDataSize(offset, pos - offset);
+}
+
+void CacheFileOutputStream::NotifyListener() {
+ mFile->AssertOwnsLock();
+
+ LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this));
+
+ MOZ_ASSERT(mCallback);
+
+ if (!mCallbackTarget) {
+ mCallbackTarget = CacheFileIOManager::IOTarget();
+ if (!mCallbackTarget) {
+ LOG(
+ ("CacheFileOutputStream::NotifyListener() - Cannot get Cache I/O "
+ "thread! Using main thread for callback."));
+ mCallbackTarget = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
+ NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
+
+ mCallback = nullptr;
+ mCallbackTarget = nullptr;
+
+ asyncCallback->OnOutputStreamReady(this);
+}
+
+// Memory reporting
+
+size_t CacheFileOutputStream::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ // Everything the stream keeps a reference to is already reported somewhere
+ // else.
+ // mFile reports itself.
+ // mChunk reported as part of CacheFile.
+ // mCloseListener is CacheEntry, already reported.
+ // mCallback is usually CacheFile or a class that is reported elsewhere.
+ return mallocSizeOf(this);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheFileOutputStream.h b/netwerk/cache2/CacheFileOutputStream.h
new file mode 100644
index 0000000000..4d1fc5e4b7
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.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 CacheFileOutputStream__h__
+#define CacheFileOutputStream__h__
+
+#include "nsIAsyncOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "CacheFileChunk.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+class CacheOutputCloseListener;
+
+class CacheFileOutputStream : public nsIAsyncOutputStream,
+ public nsISeekableStream,
+ public CacheFileChunkListener {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+
+ public:
+ CacheFileOutputStream(CacheFile* aFile,
+ CacheOutputCloseListener* aCloseListener,
+ bool aAlternativeData);
+
+ NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) override;
+ NS_IMETHOD OnChunkUpdated(CacheFileChunk* aChunk) override;
+
+ void NotifyCloseListener();
+ bool IsAlternativeData() const { return mAlternativeData; };
+
+ // Memory reporting
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ virtual ~CacheFileOutputStream();
+
+ nsresult CloseWithStatusLocked(nsresult aStatus);
+ void ReleaseChunk();
+ void EnsureCorrectChunk(bool aReleaseOnly);
+ void FillHole();
+ void NotifyListener();
+
+ RefPtr<CacheFile> mFile;
+ RefPtr<CacheFileChunk> mChunk;
+ RefPtr<CacheOutputCloseListener> mCloseListener;
+ int64_t mPos;
+ bool mClosed : 1;
+ bool const mAlternativeData : 1;
+ nsresult mStatus;
+
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileUtils.cpp b/netwerk/cache2/CacheFileUtils.cpp
new file mode 100644
index 0000000000..8d68cc82a0
--- /dev/null
+++ b/netwerk/cache2/CacheFileUtils.cpp
@@ -0,0 +1,668 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheIndex.h"
+#include "CacheLog.h"
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
+#include "LoadContextInfo.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Telemetry.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include <algorithm>
+#include "mozilla/Unused.h"
+
+namespace mozilla::net::CacheFileUtils {
+
+// This designates the format for the "alt-data" metadata.
+// When the format changes we need to update the version.
+static uint32_t const kAltDataVersion = 1;
+const char* kAltDataKey = "alt-data";
+
+namespace {
+
+/**
+ * A simple recursive descent parser for the mapping key.
+ */
+class KeyParser : protected Tokenizer {
+ public:
+ explicit KeyParser(nsACString const& aInput)
+ : Tokenizer(aInput),
+ isAnonymous(false)
+ // Initialize the cache key to a zero length by default
+ ,
+ lastTag(0) {}
+
+ private:
+ // Results
+ OriginAttributes originAttribs;
+ bool isAnonymous;
+ nsCString idEnhance;
+ nsDependentCSubstring cacheKey;
+
+ // Keeps the last tag name, used for alphabetical sort checking
+ char lastTag;
+
+ // Classifier for the 'tag' character valid range.
+ // Explicitly using unsigned char as 127 is -1 when signed and it would only
+ // produce a warning.
+ static bool TagChar(const char aChar) {
+ unsigned char c = static_cast<unsigned char>(aChar);
+ return c >= ' ' && c <= '\x7f';
+ }
+
+ bool ParseTags() {
+ // Expects to be at the tag name or at the end
+ if (CheckEOF()) {
+ return true;
+ }
+
+ char tag;
+ if (!ReadChar(&TagChar, &tag)) {
+ return false;
+ }
+
+ // Check the alphabetical order, hard-fail on disobedience
+ if (!(lastTag < tag || tag == ':')) {
+ return false;
+ }
+ lastTag = tag;
+
+ switch (tag) {
+ case ':':
+ // last possible tag, when present there is the cacheKey following,
+ // not terminated with ',' and no need to unescape.
+ cacheKey.Rebind(mCursor, mEnd - mCursor);
+ return true;
+ case 'O': {
+ nsAutoCString originSuffix;
+ if (!ParseValue(&originSuffix) ||
+ !originAttribs.PopulateFromSuffix(originSuffix)) {
+ return false;
+ }
+ break;
+ }
+ case 'p':
+ originAttribs.SyncAttributesWithPrivateBrowsing(true);
+ break;
+ case 'b':
+ // Leaving to be able to read and understand oldformatted entries
+ originAttribs.mInIsolatedMozBrowser = true;
+ break;
+ case 'a':
+ isAnonymous = true;
+ break;
+ case 'i': {
+ // Leaving to be able to read and understand oldformatted entries
+ uint32_t deprecatedAppId = 0;
+ if (!ReadInteger(&deprecatedAppId)) {
+ return false; // not a valid 32-bit integer
+ }
+ break;
+ }
+ case '~':
+ if (!ParseValue(&idEnhance)) {
+ return false;
+ }
+ break;
+ default:
+ if (!ParseValue()) { // skip any tag values, optional
+ return false;
+ }
+ break;
+ }
+
+ // We expect a comma after every tag
+ if (!CheckChar(',')) {
+ return false;
+ }
+
+ // Recurse to the next tag
+ return ParseTags();
+ }
+
+ bool ParseValue(nsACString* result = nullptr) {
+ // If at the end, fail since we expect a comma ; value may be empty tho
+ if (CheckEOF()) {
+ return false;
+ }
+
+ Token t;
+ while (Next(t)) {
+ if (!Token::Char(',').Equals(t)) {
+ if (result) {
+ result->Append(t.Fragment());
+ }
+ continue;
+ }
+
+ if (CheckChar(',')) {
+ // Two commas in a row, escaping
+ if (result) {
+ result->Append(',');
+ }
+ continue;
+ }
+
+ // We must give the comma back since the upper calls expect it
+ Rollback();
+ return true;
+ }
+
+ return false;
+ }
+
+ public:
+ already_AddRefed<LoadContextInfo> Parse() {
+ RefPtr<LoadContextInfo> info;
+ if (ParseTags()) {
+ info = GetLoadContextInfo(isAnonymous, originAttribs);
+ }
+
+ return info.forget();
+ }
+
+ void URISpec(nsACString& result) { result.Assign(cacheKey); }
+
+ void IdEnhance(nsACString& result) { result.Assign(idEnhance); }
+};
+
+} // namespace
+
+already_AddRefed<nsILoadContextInfo> ParseKey(const nsACString& aKey,
+ nsACString* aIdEnhance,
+ nsACString* aURISpec) {
+ KeyParser parser(aKey);
+ RefPtr<LoadContextInfo> info = parser.Parse();
+
+ if (info) {
+ if (aIdEnhance) parser.IdEnhance(*aIdEnhance);
+ if (aURISpec) parser.URISpec(*aURISpec);
+ }
+
+ return info.forget();
+}
+
+void AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString& _retval) {
+ /**
+ * This key is used to salt file hashes. When form of the key is changed
+ * cache entries will fail to find on disk.
+ *
+ * IMPORTANT NOTE:
+ * Keep the attributes list sorted according their ASCII code.
+ */
+
+ if (!aInfo) {
+ return;
+ }
+
+ OriginAttributes const* oa = aInfo->OriginAttributesPtr();
+ nsAutoCString suffix;
+ oa->CreateSuffix(suffix);
+ if (!suffix.IsEmpty()) {
+ AppendTagWithValue(_retval, 'O', suffix);
+ }
+
+ if (aInfo->IsAnonymous()) {
+ _retval.AppendLiteral("a,");
+ }
+
+ if (aInfo->IsPrivate()) {
+ _retval.AppendLiteral("p,");
+ }
+}
+
+void AppendTagWithValue(nsACString& aTarget, char const aTag,
+ const nsACString& aValue) {
+ aTarget.Append(aTag);
+
+ // First check the value string to save some memory copying
+ // for cases we don't need to escape at all (most likely).
+ if (!aValue.IsEmpty()) {
+ if (!aValue.Contains(',')) {
+ // No need to escape
+ aTarget.Append(aValue);
+ } else {
+ nsAutoCString escapedValue(aValue);
+ escapedValue.ReplaceSubstring(","_ns, ",,"_ns);
+ aTarget.Append(escapedValue);
+ }
+ }
+
+ aTarget.Append(',');
+}
+
+nsresult KeyMatchesLoadContextInfo(const nsACString& aKey,
+ nsILoadContextInfo* aInfo, bool* _retval) {
+ nsCOMPtr<nsILoadContextInfo> info = ParseKey(aKey);
+
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *_retval = info->Equals(aInfo);
+ return NS_OK;
+}
+
+ValidityPair::ValidityPair(uint32_t aOffset, uint32_t aLen)
+ : mOffset(aOffset), mLen(aLen) {}
+
+bool ValidityPair::CanBeMerged(const ValidityPair& aOther) const {
+ // The pairs can be merged into a single one if the start of one of the pairs
+ // is placed anywhere in the validity interval of other pair or exactly after
+ // its end.
+ return IsInOrFollows(aOther.mOffset) || aOther.IsInOrFollows(mOffset);
+}
+
+bool ValidityPair::IsInOrFollows(uint32_t aOffset) const {
+ return mOffset <= aOffset && mOffset + mLen >= aOffset;
+}
+
+bool ValidityPair::LessThan(const ValidityPair& aOther) const {
+ if (mOffset < aOther.mOffset) {
+ return true;
+ }
+
+ if (mOffset == aOther.mOffset && mLen < aOther.mLen) {
+ return true;
+ }
+
+ return false;
+}
+
+void ValidityPair::Merge(const ValidityPair& aOther) {
+ MOZ_ASSERT(CanBeMerged(aOther));
+
+ uint32_t offset = std::min(mOffset, aOther.mOffset);
+ uint32_t end = std::max(mOffset + mLen, aOther.mOffset + aOther.mLen);
+
+ mOffset = offset;
+ mLen = end - offset;
+}
+
+void ValidityMap::Log() const {
+ LOG(("ValidityMap::Log() - number of pairs: %zu", mMap.Length()));
+ for (uint32_t i = 0; i < mMap.Length(); i++) {
+ LOG((" (%u, %u)", mMap[i].Offset() + 0, mMap[i].Len() + 0));
+ }
+}
+
+uint32_t ValidityMap::Length() const { return mMap.Length(); }
+
+void ValidityMap::AddPair(uint32_t aOffset, uint32_t aLen) {
+ ValidityPair pair(aOffset, aLen);
+
+ if (mMap.Length() == 0) {
+ mMap.AppendElement(pair);
+ return;
+ }
+
+ // Find out where to place this pair into the map, it can overlap only with
+ // one preceding pair and all subsequent pairs.
+ uint32_t pos = 0;
+ for (pos = mMap.Length(); pos > 0;) {
+ --pos;
+
+ if (mMap[pos].LessThan(pair)) {
+ // The new pair should be either inserted after pos or merged with it.
+ if (mMap[pos].CanBeMerged(pair)) {
+ // Merge with the preceding pair
+ mMap[pos].Merge(pair);
+ } else {
+ // They don't overlap, element must be placed after pos element
+ ++pos;
+ if (pos == mMap.Length()) {
+ mMap.AppendElement(pair);
+ } else {
+ mMap.InsertElementAt(pos, pair);
+ }
+ }
+
+ break;
+ }
+
+ if (pos == 0) {
+ // The new pair should be placed in front of all existing pairs.
+ mMap.InsertElementAt(0, pair);
+ }
+ }
+
+ // pos now points to merged or inserted pair, check whether it overlaps with
+ // subsequent pairs.
+ while (pos + 1 < mMap.Length()) {
+ if (mMap[pos].CanBeMerged(mMap[pos + 1])) {
+ mMap[pos].Merge(mMap[pos + 1]);
+ mMap.RemoveElementAt(pos + 1);
+ } else {
+ break;
+ }
+ }
+}
+
+void ValidityMap::Clear() { mMap.Clear(); }
+
+size_t ValidityMap::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mMap.ShallowSizeOfExcludingThis(mallocSizeOf);
+}
+
+ValidityPair& ValidityMap::operator[](uint32_t aIdx) {
+ return mMap.ElementAt(aIdx);
+}
+
+StaticMutex DetailedCacheHitTelemetry::sLock;
+uint32_t DetailedCacheHitTelemetry::sRecordCnt = 0;
+DetailedCacheHitTelemetry::HitRate
+ DetailedCacheHitTelemetry::sHRStats[kNumOfRanges];
+
+DetailedCacheHitTelemetry::HitRate::HitRate() { Reset(); }
+
+void DetailedCacheHitTelemetry::HitRate::AddRecord(ERecType aType) {
+ if (aType == HIT) {
+ ++mHitCnt;
+ } else {
+ ++mMissCnt;
+ }
+}
+
+uint32_t DetailedCacheHitTelemetry::HitRate::GetHitRateBucket(
+ uint32_t aNumOfBuckets) const {
+ uint32_t bucketIdx = (aNumOfBuckets * mHitCnt) / (mHitCnt + mMissCnt);
+ if (bucketIdx ==
+ aNumOfBuckets) { // make sure 100% falls into the last bucket
+ --bucketIdx;
+ }
+
+ return bucketIdx;
+}
+
+uint32_t DetailedCacheHitTelemetry::HitRate::Count() {
+ return mHitCnt + mMissCnt;
+}
+
+void DetailedCacheHitTelemetry::HitRate::Reset() {
+ mHitCnt = 0;
+ mMissCnt = 0;
+}
+
+// static
+void DetailedCacheHitTelemetry::AddRecord(ERecType aType,
+ TimeStamp aLoadStart) {
+ bool isUpToDate = false;
+ CacheIndex::IsUpToDate(&isUpToDate);
+ if (!isUpToDate) {
+ // Ignore the record when the entry file count might be incorrect
+ return;
+ }
+
+ uint32_t entryCount;
+ nsresult rv = CacheIndex::GetEntryFileCount(&entryCount);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ uint32_t rangeIdx = entryCount / kRangeSize;
+ if (rangeIdx >= kNumOfRanges) { // The last range has no upper limit.
+ rangeIdx = kNumOfRanges - 1;
+ }
+
+ uint32_t hitMissValue = 2 * rangeIdx; // 2 values per range
+ if (aType == MISS) { // The order is HIT, MISS
+ ++hitMissValue;
+ }
+
+ StaticMutexAutoLock lock(sLock);
+
+ if (aType == MISS) {
+ mozilla::Telemetry::AccumulateTimeDelta(
+ mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS, aLoadStart);
+ } else {
+ mozilla::glean::network::cache_hit_time.AccumulateRawDuration(
+ TimeStamp::Now() - aLoadStart);
+ }
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_MISS_STAT_PER_CACHE_SIZE,
+ hitMissValue);
+
+ sHRStats[rangeIdx].AddRecord(aType);
+ ++sRecordCnt;
+
+ if (sRecordCnt < kTotalSamplesReportLimit) {
+ return;
+ }
+
+ sRecordCnt = 0;
+
+ for (uint32_t i = 0; i < kNumOfRanges; ++i) {
+ if (sHRStats[i].Count() >= kHitRateSamplesReportLimit) {
+ // The telemetry enums are grouped by buckets as follows:
+ // Telemetry value : 0,1,2,3, ... ,19,20,21,22, ... ,398,399
+ // Hit rate bucket : 0,0,0,0, ... , 0, 1, 1, 1, ... , 19, 19
+ // Cache size range: 0,1,2,3, ... ,19, 0, 1, 2, ... , 18, 19
+ uint32_t bucketOffset =
+ sHRStats[i].GetHitRateBucket(kHitRateBuckets) * kNumOfRanges;
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_RATE_PER_CACHE_SIZE,
+ bucketOffset + i);
+ sHRStats[i].Reset();
+ }
+ }
+}
+
+StaticMutex CachePerfStats::sLock;
+CachePerfStats::PerfData CachePerfStats::sData[CachePerfStats::LAST];
+uint32_t CachePerfStats::sCacheSlowCnt = 0;
+uint32_t CachePerfStats::sCacheNotSlowCnt = 0;
+
+CachePerfStats::MMA::MMA(uint32_t aTotalWeight, bool aFilter)
+ : mSum(0), mSumSq(0), mCnt(0), mWeight(aTotalWeight), mFilter(aFilter) {}
+
+void CachePerfStats::MMA::AddValue(uint32_t aValue) {
+ if (mFilter) {
+ // Filter high spikes
+ uint32_t avg = GetAverage();
+ uint32_t stddev = GetStdDev();
+ uint32_t maxdiff = avg + (3 * stddev);
+ if (avg && aValue > avg + maxdiff) {
+ return;
+ }
+ }
+
+ if (mCnt < mWeight) {
+ // Compute arithmetic average until we have at least mWeight values
+ CheckedInt<uint64_t> newSumSq = CheckedInt<uint64_t>(aValue) * aValue;
+ newSumSq += mSumSq;
+ if (!newSumSq.isValid()) {
+ return; // ignore this value
+ }
+ mSumSq = newSumSq.value();
+ mSum += aValue;
+ ++mCnt;
+ } else {
+ CheckedInt<uint64_t> newSumSq = mSumSq - mSumSq / mCnt;
+ newSumSq += static_cast<uint64_t>(aValue) * aValue;
+ if (!newSumSq.isValid()) {
+ return; // ignore this value
+ }
+ mSumSq = newSumSq.value();
+
+ // Compute modified moving average for more values:
+ // newAvg = ((weight - 1) * oldAvg + newValue) / weight
+ mSum -= GetAverage();
+ mSum += aValue;
+ }
+}
+
+uint32_t CachePerfStats::MMA::GetAverage() {
+ if (mCnt == 0) {
+ return 0;
+ }
+
+ return mSum / mCnt;
+}
+
+uint32_t CachePerfStats::MMA::GetStdDev() {
+ if (mCnt == 0) {
+ return 0;
+ }
+
+ uint32_t avg = GetAverage();
+ uint64_t avgSq = static_cast<uint64_t>(avg) * avg;
+ uint64_t variance = mSumSq / mCnt;
+ if (variance < avgSq) {
+ // Due to rounding error when using integer data type, it can happen that
+ // average of squares of the values is smaller than square of the average
+ // of the values. In this case fix mSumSq.
+ variance = avgSq;
+ mSumSq = variance * mCnt;
+ }
+
+ variance -= avgSq;
+ return sqrt(static_cast<double>(variance));
+}
+
+CachePerfStats::PerfData::PerfData()
+ : mFilteredAvg(50, true), mShortAvg(3, false) {}
+
+void CachePerfStats::PerfData::AddValue(uint32_t aValue, bool aShortOnly) {
+ if (!aShortOnly) {
+ mFilteredAvg.AddValue(aValue);
+ }
+ mShortAvg.AddValue(aValue);
+}
+
+uint32_t CachePerfStats::PerfData::GetAverage(bool aFiltered) {
+ return aFiltered ? mFilteredAvg.GetAverage() : mShortAvg.GetAverage();
+}
+
+uint32_t CachePerfStats::PerfData::GetStdDev(bool aFiltered) {
+ return aFiltered ? mFilteredAvg.GetStdDev() : mShortAvg.GetStdDev();
+}
+
+// static
+void CachePerfStats::AddValue(EDataType aType, uint32_t aValue,
+ bool aShortOnly) {
+ StaticMutexAutoLock lock(sLock);
+ sData[aType].AddValue(aValue, aShortOnly);
+}
+
+// static
+uint32_t CachePerfStats::GetAverage(EDataType aType, bool aFiltered) {
+ StaticMutexAutoLock lock(sLock);
+ return sData[aType].GetAverage(aFiltered);
+}
+
+// static
+uint32_t CachePerfStats::GetStdDev(EDataType aType, bool aFiltered) {
+ StaticMutexAutoLock lock(sLock);
+ return sData[aType].GetStdDev(aFiltered);
+}
+
+// static
+bool CachePerfStats::IsCacheSlow() {
+ StaticMutexAutoLock lock(sLock);
+
+ // Compare mShortAvg with mFilteredAvg to find out whether cache is getting
+ // slower. Use only data about single IO operations because ENTRY_OPEN can be
+ // affected by more factors than a slow disk.
+ for (uint32_t i = 0; i < ENTRY_OPEN; ++i) {
+ if (i == IO_WRITE) {
+ // Skip this data type. IsCacheSlow is used for determining cache slowness
+ // when opening entries. Writes have low priority and it's normal that
+ // they are delayed a lot, but this doesn't necessarily affect opening
+ // cache entries.
+ continue;
+ }
+
+ uint32_t avgLong = sData[i].GetAverage(true);
+ if (avgLong == 0) {
+ // We have no perf data yet, skip this data type.
+ continue;
+ }
+ uint32_t avgShort = sData[i].GetAverage(false);
+ uint32_t stddevLong = sData[i].GetStdDev(true);
+ uint32_t maxdiff = avgLong + (3 * stddevLong);
+
+ if (avgShort > avgLong + maxdiff) {
+ LOG(
+ ("CachePerfStats::IsCacheSlow() - result is slow based on perf "
+ "type %u [avgShort=%u, avgLong=%u, stddevLong=%u]",
+ i, avgShort, avgLong, stddevLong));
+ ++sCacheSlowCnt;
+ return true;
+ }
+ }
+
+ ++sCacheNotSlowCnt;
+ return false;
+}
+
+// static
+void CachePerfStats::GetSlowStats(uint32_t* aSlow, uint32_t* aNotSlow) {
+ StaticMutexAutoLock lock(sLock);
+ *aSlow = sCacheSlowCnt;
+ *aNotSlow = sCacheNotSlowCnt;
+}
+
+void FreeBuffer(void* aBuf) {
+#ifndef NS_FREE_PERMANENT_DATA
+ if (CacheObserver::ShuttingDown()) {
+ return;
+ }
+#endif
+
+ free(aBuf);
+}
+
+nsresult ParseAlternativeDataInfo(const char* aInfo, int64_t* _offset,
+ nsACString* _type) {
+ // The format is: "1;12345,javascript/binary"
+ // <version>;<offset>,<type>
+ mozilla::Tokenizer p(aInfo, nullptr, "/");
+ uint32_t altDataVersion = 0;
+ int64_t altDataOffset = -1;
+
+ // The metadata format has a wrong version number.
+ if (!p.ReadInteger(&altDataVersion) || altDataVersion != kAltDataVersion) {
+ LOG(
+ ("ParseAlternativeDataInfo() - altDataVersion=%u, "
+ "expectedVersion=%u",
+ altDataVersion, kAltDataVersion));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!p.CheckChar(';') || !p.ReadInteger(&altDataOffset) ||
+ !p.CheckChar(',')) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // The requested alt-data representation is not available
+ if (altDataOffset < 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (_offset) {
+ *_offset = altDataOffset;
+ }
+
+ if (_type) {
+ mozilla::Unused << p.ReadUntil(Tokenizer::Token::EndOfFile(), *_type);
+ }
+
+ return NS_OK;
+}
+
+void BuildAlternativeDataInfo(const char* aInfo, int64_t aOffset,
+ nsACString& _retval) {
+ _retval.Truncate();
+ _retval.AppendInt(kAltDataVersion);
+ _retval.Append(';');
+ _retval.AppendInt(aOffset);
+ _retval.Append(',');
+ _retval.Append(aInfo);
+}
+
+} // namespace mozilla::net::CacheFileUtils
diff --git a/netwerk/cache2/CacheFileUtils.h b/netwerk/cache2/CacheFileUtils.h
new file mode 100644
index 0000000000..aa6fb64312
--- /dev/null
+++ b/netwerk/cache2/CacheFileUtils.h
@@ -0,0 +1,238 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileUtils__h__
+#define CacheFileUtils__h__
+
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/TimeStamp.h"
+
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+namespace CacheFileUtils {
+
+extern const char* kAltDataKey;
+
+already_AddRefed<nsILoadContextInfo> ParseKey(const nsACString& aKey,
+ nsACString* aIdEnhance = nullptr,
+ nsACString* aURISpec = nullptr);
+
+void AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString& _retval);
+
+void AppendTagWithValue(nsACString& aTarget, char const aTag,
+ const nsACString& aValue);
+
+nsresult KeyMatchesLoadContextInfo(const nsACString& aKey,
+ nsILoadContextInfo* aInfo, bool* _retval);
+
+class ValidityPair {
+ public:
+ ValidityPair(uint32_t aOffset, uint32_t aLen);
+
+ ValidityPair& operator=(const ValidityPair& aOther) = default;
+
+ // Returns true when two pairs can be merged, i.e. they do overlap or the one
+ // ends exactly where the other begins.
+ bool CanBeMerged(const ValidityPair& aOther) const;
+
+ // Returns true when aOffset is placed anywhere in the validity interval or
+ // exactly after its end.
+ bool IsInOrFollows(uint32_t aOffset) const;
+
+ // Returns true when this pair has lower offset than the other pair. In case
+ // both pairs have the same offset it returns true when this pair has a
+ // shorter length.
+ bool LessThan(const ValidityPair& aOther) const;
+
+ // Merges two pair into one.
+ void Merge(const ValidityPair& aOther);
+
+ uint32_t Offset() const { return mOffset; }
+ uint32_t Len() const { return mLen; }
+
+ private:
+ uint32_t mOffset;
+ uint32_t mLen;
+};
+
+class ValidityMap {
+ public:
+ // Prints pairs in the map into log.
+ void Log() const;
+
+ // Returns number of pairs in the map.
+ uint32_t Length() const;
+
+ // Adds a new pair to the map. It keeps the pairs ordered and merges pairs
+ // when possible.
+ void AddPair(uint32_t aOffset, uint32_t aLen);
+
+ // Removes all pairs from the map.
+ void Clear();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ ValidityPair& operator[](uint32_t aIdx);
+
+ private:
+ nsTArray<ValidityPair> mMap;
+};
+
+class DetailedCacheHitTelemetry {
+ public:
+ enum ERecType { HIT = 0, MISS = 1 };
+
+ static void AddRecord(ERecType aType, TimeStamp aLoadStart);
+
+ private:
+ class HitRate {
+ public:
+ HitRate();
+
+ void AddRecord(ERecType aType);
+ // Returns the bucket index that the current hit rate falls into according
+ // to the given aNumOfBuckets.
+ uint32_t GetHitRateBucket(uint32_t aNumOfBuckets) const;
+ uint32_t Count();
+ void Reset();
+
+ private:
+ uint32_t mHitCnt = 0;
+ uint32_t mMissCnt = 0;
+ };
+
+ // Group the hits and misses statistics by cache files count ranges (0-5000,
+ // 5001-10000, ... , 95001- )
+ static const uint32_t kRangeSize = 5000;
+ static const uint32_t kNumOfRanges = 20;
+
+ // Use the same ranges to report an average hit rate. Report the hit rates
+ // (and reset the counters) every kTotalSamplesReportLimit samples.
+ static const uint32_t kTotalSamplesReportLimit = 1000;
+
+ // Report hit rate for a given cache size range only if it contains
+ // kHitRateSamplesReportLimit or more samples. This limit should avoid
+ // reporting a biased statistics.
+ static const uint32_t kHitRateSamplesReportLimit = 500;
+
+ // All hit rates are accumulated in a single telemetry probe, so to use
+ // a sane number of enumerated values the hit rate is divided into buckets
+ // instead of using a percent value. This constant defines number of buckets
+ // that we divide the hit rates into. I.e. we'll report ranges 0%-5%, 5%-10%,
+ // 10-%15%, ...
+ static const uint32_t kHitRateBuckets = 20;
+
+ // Protects sRecordCnt, sHRStats and Telemetry::Accumulated() calls.
+ static StaticMutex sLock;
+
+ // Counter of samples that is compared against kTotalSamplesReportLimit.
+ static uint32_t sRecordCnt MOZ_GUARDED_BY(sLock);
+
+ // Hit rate statistics for every cache size range.
+ static HitRate sHRStats[kNumOfRanges] MOZ_GUARDED_BY(sLock);
+};
+
+class CachePerfStats {
+ public:
+ // perfStatTypes in displayRcwnStats() in toolkit/content/aboutNetworking.js
+ // must match EDataType
+ enum EDataType {
+ IO_OPEN = 0,
+ IO_READ = 1,
+ IO_WRITE = 2,
+ ENTRY_OPEN = 3,
+ LAST = 4
+ };
+
+ static void AddValue(EDataType aType, uint32_t aValue, bool aShortOnly);
+ static uint32_t GetAverage(EDataType aType, bool aFiltered);
+ static uint32_t GetStdDev(EDataType aType, bool aFiltered);
+ static bool IsCacheSlow();
+ static void GetSlowStats(uint32_t* aSlow, uint32_t* aNotSlow);
+
+ private:
+ // This class computes average and standard deviation, it returns an
+ // arithmetic avg and stddev until total number of values reaches mWeight.
+ // Then it returns modified moving average computed as follows:
+ //
+ // avg = (1-a)*avg + a*value
+ // avgsq = (1-a)*avgsq + a*value^2
+ // stddev = sqrt(avgsq - avg^2)
+ //
+ // where
+ // avgsq is an average of the square of the values
+ // a = 1 / weight
+ class MMA {
+ public:
+ MMA(uint32_t aTotalWeight, bool aFilter);
+
+ void AddValue(uint32_t aValue);
+ uint32_t GetAverage();
+ uint32_t GetStdDev();
+
+ private:
+ uint64_t mSum;
+ uint64_t mSumSq;
+ uint32_t mCnt;
+ uint32_t mWeight;
+ bool mFilter;
+ };
+
+ class PerfData {
+ public:
+ PerfData();
+
+ void AddValue(uint32_t aValue, bool aShortOnly);
+ uint32_t GetAverage(bool aFiltered);
+ uint32_t GetStdDev(bool aFiltered);
+
+ private:
+ // Contains filtered data (i.e. times when we think the cache and disk was
+ // not busy) for a longer time.
+ MMA mFilteredAvg;
+
+ // Contains unfiltered average of few recent values.
+ MMA mShortAvg;
+ };
+
+ static StaticMutex sLock;
+
+ static PerfData sData[LAST] MOZ_GUARDED_BY(sLock);
+ static uint32_t sCacheSlowCnt MOZ_GUARDED_BY(sLock);
+ static uint32_t sCacheNotSlowCnt MOZ_GUARDED_BY(sLock);
+};
+
+void FreeBuffer(void* aBuf);
+
+nsresult ParseAlternativeDataInfo(const char* aInfo, int64_t* _offset,
+ nsACString* _type);
+
+void BuildAlternativeDataInfo(const char* aInfo, int64_t aOffset,
+ nsACString& _retval);
+
+class CacheFileLock final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileLock)
+ CacheFileLock() = default;
+
+ mozilla::Mutex& Lock() MOZ_RETURN_CAPABILITY(mLock) { return mLock; }
+
+ private:
+ ~CacheFileLock() = default;
+
+ mozilla::Mutex mLock{"CacheFile.mLock"};
+};
+
+} // namespace CacheFileUtils
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheHashUtils.cpp b/netwerk/cache2/CacheHashUtils.cpp
new file mode 100644
index 0000000000..6ac8205931
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.cpp
@@ -0,0 +1,226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheHashUtils.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/SHA1.h"
+
+namespace mozilla::net {
+
+/**
+ * CacheHash::Hash(const char * key, uint32_t initval)
+ *
+ * See http://burtleburtle.net/bob/hash/evahash.html for more information
+ * about this hash function.
+ *
+ * This algorithm is used to check the data integrity.
+ */
+
+static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c) {
+ a -= b;
+ a -= c;
+ a ^= (c >> 13);
+ b -= c;
+ b -= a;
+ b ^= (a << 8);
+ c -= a;
+ c -= b;
+ c ^= (b >> 13);
+ a -= b;
+ a -= c;
+ a ^= (c >> 12);
+ b -= c;
+ b -= a;
+ b ^= (a << 16);
+ c -= a;
+ c -= b;
+ c ^= (b >> 5);
+ a -= b;
+ a -= c;
+ a ^= (c >> 3);
+ b -= c;
+ b -= a;
+ b ^= (a << 10);
+ c -= a;
+ c -= b;
+ c ^= (b >> 15);
+}
+
+CacheHash::Hash32_t CacheHash::Hash(const char* aData, uint32_t aSize,
+ uint32_t aInitval) {
+ const uint8_t* k = reinterpret_cast<const uint8_t*>(aData);
+ uint32_t a, b, c, len;
+
+ /* Set up the internal state */
+ len = aSize;
+ a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
+ c = aInitval; /* variable initialization of internal state */
+
+ /*---------------------------------------- handle most of the key */
+ while (len >= 12) {
+ a += k[0] + (uint32_t(k[1]) << 8) + (uint32_t(k[2]) << 16) +
+ (uint32_t(k[3]) << 24);
+ b += k[4] + (uint32_t(k[5]) << 8) + (uint32_t(k[6]) << 16) +
+ (uint32_t(k[7]) << 24);
+ c += k[8] + (uint32_t(k[9]) << 8) + (uint32_t(k[10]) << 16) +
+ (uint32_t(k[11]) << 24);
+ hashmix(a, b, c);
+ k += 12;
+ len -= 12;
+ }
+
+ /*------------------------------------- handle the last 11 bytes */
+ c += aSize;
+ switch (len) { /* all the case statements fall through */
+ case 11:
+ c += (uint32_t(k[10]) << 24);
+ [[fallthrough]];
+ case 10:
+ c += (uint32_t(k[9]) << 16);
+ [[fallthrough]];
+ case 9:
+ c += (uint32_t(k[8]) << 8);
+ [[fallthrough]];
+ /* the low-order byte of c is reserved for the length */
+ case 8:
+ b += (uint32_t(k[7]) << 24);
+ [[fallthrough]];
+ case 7:
+ b += (uint32_t(k[6]) << 16);
+ [[fallthrough]];
+ case 6:
+ b += (uint32_t(k[5]) << 8);
+ [[fallthrough]];
+ case 5:
+ b += k[4];
+ [[fallthrough]];
+ case 4:
+ a += (uint32_t(k[3]) << 24);
+ [[fallthrough]];
+ case 3:
+ a += (uint32_t(k[2]) << 16);
+ [[fallthrough]];
+ case 2:
+ a += (uint32_t(k[1]) << 8);
+ [[fallthrough]];
+ case 1:
+ a += k[0];
+ /* case 0: nothing left to add */
+ }
+ hashmix(a, b, c);
+
+ return c;
+}
+
+CacheHash::Hash16_t CacheHash::Hash16(const char* aData, uint32_t aSize,
+ uint32_t aInitval) {
+ Hash32_t hash = Hash(aData, aSize, aInitval);
+ return (hash & 0xFFFF);
+}
+
+NS_IMPL_ISUPPORTS0(CacheHash)
+
+CacheHash::CacheHash(uint32_t aInitval) : mC(aInitval) {}
+
+void CacheHash::Feed(uint32_t aVal, uint8_t aLen) {
+ switch (mPos) {
+ case 0:
+ mA += aVal;
+ mPos++;
+ break;
+
+ case 1:
+ mB += aVal;
+ mPos++;
+ break;
+
+ case 2:
+ mPos = 0;
+ if (aLen == 4) {
+ mC += aVal;
+ hashmix(mA, mB, mC);
+ } else {
+ mC += aVal << 8;
+ }
+ }
+
+ mLength += aLen;
+}
+
+void CacheHash::Update(const char* aData, uint32_t aLen) {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(aData);
+
+ MOZ_ASSERT(!mFinalized);
+
+ if (mBufPos) {
+ while (mBufPos != 4 && aLen) {
+ mBuf += uint32_t(*data) << 8 * mBufPos;
+ data++;
+ mBufPos++;
+ aLen--;
+ }
+
+ if (mBufPos == 4) {
+ mBufPos = 0;
+ Feed(mBuf);
+ mBuf = 0;
+ }
+ }
+
+ if (!aLen) return;
+
+ while (aLen >= 4) {
+ Feed(data[0] + (uint32_t(data[1]) << 8) + (uint32_t(data[2]) << 16) +
+ (uint32_t(data[3]) << 24));
+ data += 4;
+ aLen -= 4;
+ }
+
+ switch (aLen) {
+ case 3:
+ mBuf += data[2] << 16;
+ [[fallthrough]];
+ case 2:
+ mBuf += data[1] << 8;
+ [[fallthrough]];
+ case 1:
+ mBuf += data[0];
+ }
+
+ mBufPos = aLen;
+}
+
+CacheHash::Hash32_t CacheHash::GetHash() {
+ if (!mFinalized) {
+ if (mBufPos) {
+ Feed(mBuf, mBufPos);
+ }
+ mC += mLength;
+ hashmix(mA, mB, mC);
+ mFinalized = true;
+ }
+
+ return mC;
+}
+
+CacheHash::Hash16_t CacheHash::GetHash16() {
+ Hash32_t hash = GetHash();
+ return (hash & 0xFFFF);
+}
+
+OriginAttrsHash GetOriginAttrsHash(const mozilla::OriginAttributes& aOA) {
+ nsAutoCString suffix;
+ aOA.CreateSuffix(suffix);
+
+ SHA1Sum sum;
+ SHA1Sum::Hash hash;
+ sum.update(suffix.BeginReading(), suffix.Length());
+ sum.finish(hash);
+
+ return BigEndian::readUint64(&hash);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheHashUtils.h b/netwerk/cache2/CacheHashUtils.h
new file mode 100644
index 0000000000..6ab7df972a
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.h
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheHashUtils__h__
+#define CacheHashUtils__h__
+
+#include "nsISupports.h"
+#include "mozilla/Types.h"
+#include "prnetdb.h"
+#include "nsPrintfCString.h"
+
+#define LOGSHA1(x) \
+ PR_htonl((reinterpret_cast<const uint32_t*>(x))[0]), \
+ PR_htonl((reinterpret_cast<const uint32_t*>(x))[1]), \
+ PR_htonl((reinterpret_cast<const uint32_t*>(x))[2]), \
+ PR_htonl((reinterpret_cast<const uint32_t*>(x))[3]), \
+ PR_htonl((reinterpret_cast<const uint32_t*>(x))[4])
+
+#define SHA1STRING(x) \
+ (nsPrintfCString("%08x%08x%08x%08x%08x", LOGSHA1(x)).get())
+
+namespace mozilla {
+
+class OriginAttributes;
+
+namespace net {
+
+class CacheHash : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ using Hash16_t = uint16_t;
+ using Hash32_t = uint32_t;
+
+ static Hash32_t Hash(const char* aData, uint32_t aSize,
+ uint32_t aInitval = 0);
+ static Hash16_t Hash16(const char* aData, uint32_t aSize,
+ uint32_t aInitval = 0);
+
+ explicit CacheHash(uint32_t aInitval = 0);
+
+ void Update(const char* aData, uint32_t aLen);
+ Hash32_t GetHash();
+ Hash16_t GetHash16();
+
+ private:
+ virtual ~CacheHash() = default;
+
+ void Feed(uint32_t aVal, uint8_t aLen = 4);
+
+ static const uint32_t kGoldenRation = 0x9e3779b9;
+
+ uint32_t mA{kGoldenRation}, mB{kGoldenRation}, mC;
+ uint8_t mPos{0};
+ uint32_t mBuf{0};
+ uint8_t mBufPos{0};
+ uint32_t mLength{0};
+ bool mFinalized{false};
+};
+
+using OriginAttrsHash = uint64_t;
+
+OriginAttrsHash GetOriginAttrsHash(const mozilla::OriginAttributes& aOA);
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheIOThread.cpp b/netwerk/cache2/CacheIOThread.cpp
new file mode 100644
index 0000000000..189e7d76fe
--- /dev/null
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -0,0 +1,590 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheIOThread.h"
+#include "CacheFileIOManager.h"
+#include "CacheLog.h"
+#include "CacheObserver.h"
+
+#include "nsIRunnable.h"
+#include "nsISupportsImpl.h"
+#include "nsPrintfCString.h"
+#include "nsThread.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+namespace mozilla::net {
+
+namespace { // anon
+
+class CacheIOTelemetry {
+ public:
+ using size_type = CacheIOThread::EventQueue::size_type;
+ static size_type mMinLengthToReport[CacheIOThread::LAST_LEVEL];
+ static void Report(uint32_t aLevel, size_type aLength);
+};
+
+static CacheIOTelemetry::size_type const kGranularity = 30;
+
+CacheIOTelemetry::size_type
+ CacheIOTelemetry::mMinLengthToReport[CacheIOThread::LAST_LEVEL] = {
+ kGranularity, kGranularity, kGranularity, kGranularity,
+ kGranularity, kGranularity, kGranularity, kGranularity};
+
+// static
+void CacheIOTelemetry::Report(uint32_t aLevel,
+ CacheIOTelemetry::size_type aLength) {
+ if (mMinLengthToReport[aLevel] > aLength) {
+ return;
+ }
+
+ static Telemetry::HistogramID telemetryID[] = {
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_OPEN_PRIORITY,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_READ_PRIORITY,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_MANAGEMENT,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_OPEN,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_READ,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_WRITE_PRIORITY,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_WRITE,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_INDEX,
+ Telemetry::HTTP_CACHE_IO_QUEUE_2_EVICT};
+
+ // Each bucket is a multiply of kGranularity (30, 60, 90..., 300+)
+ aLength = (aLength / kGranularity);
+ // Next time report only when over the current length + kGranularity
+ mMinLengthToReport[aLevel] = (aLength + 1) * kGranularity;
+
+ // 10 is number of buckets we have in each probe
+ aLength = std::min<size_type>(aLength, 10);
+
+ Telemetry::Accumulate(telemetryID[aLevel], aLength - 1); // counted from 0
+}
+
+} // namespace
+
+namespace detail {
+
+/**
+ * Helper class encapsulating platform-specific code to cancel
+ * any pending IO operation taking too long. Solely used during
+ * shutdown to prevent any IO shutdown hangs.
+ * Mainly designed for using Win32 CancelSynchronousIo function.
+ */
+class NativeThreadHandle {
+#ifdef XP_WIN
+ // The native handle to the thread
+ HANDLE mThread;
+#endif
+
+ public:
+ // Created and destroyed on the main thread only
+ NativeThreadHandle();
+ ~NativeThreadHandle();
+
+ // Called on the IO thread to grab the platform specific
+ // reference to it.
+ void InitThread();
+ // If there is a blocking operation being handled on the IO
+ // thread, this is called on the main thread during shutdown.
+ void CancelBlockingIO(Monitor& aMonitor);
+};
+
+#ifdef XP_WIN
+
+NativeThreadHandle::NativeThreadHandle() : mThread(NULL) {}
+
+NativeThreadHandle::~NativeThreadHandle() {
+ if (mThread) {
+ CloseHandle(mThread);
+ }
+}
+
+void NativeThreadHandle::InitThread() {
+ // GetCurrentThread() only returns a pseudo handle, hence DuplicateHandle
+ ::DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
+ GetCurrentProcess(), &mThread, 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+}
+
+void NativeThreadHandle::CancelBlockingIO(Monitor& aMonitor) {
+ HANDLE thread;
+ {
+ MonitorAutoLock lock(aMonitor);
+ thread = mThread;
+
+ if (!thread) {
+ return;
+ }
+ }
+
+ LOG(("CacheIOThread: Attempting to cancel a long blocking IO operation"));
+ BOOL result = ::CancelSynchronousIo(thread);
+ if (result) {
+ LOG((" cancelation signal succeeded"));
+ } else {
+ DWORD error = GetLastError();
+ LOG((" cancelation signal failed with GetLastError=%lu", error));
+ }
+}
+
+#else // WIN
+
+// Stub code only (we don't implement IO cancelation for this platform)
+
+NativeThreadHandle::NativeThreadHandle() = default;
+NativeThreadHandle::~NativeThreadHandle() = default;
+void NativeThreadHandle::InitThread() {}
+void NativeThreadHandle::CancelBlockingIO(Monitor&) {}
+
+#endif
+
+} // namespace detail
+
+CacheIOThread* CacheIOThread::sSelf = nullptr;
+
+NS_IMPL_ISUPPORTS(CacheIOThread, nsIThreadObserver)
+
+CacheIOThread::CacheIOThread() {
+ for (auto& item : mQueueLength) {
+ item = 0;
+ }
+
+ sSelf = this;
+}
+
+CacheIOThread::~CacheIOThread() {
+ {
+ MonitorAutoLock lock(mMonitor);
+ MOZ_RELEASE_ASSERT(mShutdown);
+ }
+
+ if (mXPCOMThread) {
+ nsIThread* thread = mXPCOMThread;
+ thread->Release();
+ }
+
+ sSelf = nullptr;
+#ifdef DEBUG
+ for (auto& event : mEventQueue) {
+ MOZ_ASSERT(!event.Length());
+ }
+#endif
+}
+
+nsresult CacheIOThread::Init() {
+ {
+ MonitorAutoLock lock(mMonitor);
+ // Yeah, there is not a thread yet, but we want to make sure
+ // the sequencing is correct.
+ mNativeThreadHandle = MakeUnique<detail::NativeThreadHandle>();
+ }
+
+ // Increase the reference count while spawning a new thread.
+ // If PR_CreateThread succeeds, we will forget this reference and the thread
+ // will be responsible to release it when it completes.
+ RefPtr<CacheIOThread> self = this;
+ mThread =
+ PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 128 * 1024);
+ if (!mThread) {
+ // Treat this thread as already shutdown.
+ MonitorAutoLock lock(mMonitor);
+ mShutdown = true;
+ return NS_ERROR_FAILURE;
+ }
+
+ // IMPORTANT: The thread now owns this reference, so it's important that we
+ // leak it here, otherwise we'll end up with a bad refcount.
+ // See the dont_AddRef in ThreadFunc().
+ Unused << self.forget().take();
+
+ return NS_OK;
+}
+
+nsresult CacheIOThread::Dispatch(nsIRunnable* aRunnable, uint32_t aLevel) {
+ return Dispatch(do_AddRef(aRunnable), aLevel);
+}
+
+nsresult CacheIOThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aLevel) {
+ NS_ENSURE_ARG(aLevel < LAST_LEVEL);
+
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+
+ // Runnable is always expected to be non-null, hard null-check bellow.
+ MOZ_ASSERT(runnable);
+
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown && (PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return DispatchInternal(runnable.forget(), aLevel);
+}
+
+nsresult CacheIOThread::DispatchAfterPendingOpens(nsIRunnable* aRunnable) {
+ // Runnable is always expected to be non-null, hard null-check bellow.
+ MOZ_ASSERT(aRunnable);
+
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown && (PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Move everything from later executed OPEN level to the OPEN_PRIORITY level
+ // where we post the (eviction) runnable.
+ mQueueLength[OPEN_PRIORITY] += mEventQueue[OPEN].Length();
+ mQueueLength[OPEN] -= mEventQueue[OPEN].Length();
+ mEventQueue[OPEN_PRIORITY].AppendElements(mEventQueue[OPEN]);
+ mEventQueue[OPEN].Clear();
+
+ return DispatchInternal(do_AddRef(aRunnable), OPEN_PRIORITY);
+}
+
+nsresult CacheIOThread::DispatchInternal(
+ already_AddRefed<nsIRunnable> aRunnable, uint32_t aLevel) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+
+ LogRunnable::LogDispatch(runnable.get());
+
+ if (NS_WARN_IF(!runnable)) return NS_ERROR_NULL_POINTER;
+
+ mMonitor.AssertCurrentThreadOwns();
+
+ ++mQueueLength[aLevel];
+ mEventQueue[aLevel].AppendElement(runnable.forget());
+ if (mLowestLevelWaiting > aLevel) mLowestLevelWaiting = aLevel;
+
+ mMonitor.NotifyAll();
+
+ return NS_OK;
+}
+
+bool CacheIOThread::IsCurrentThread() {
+ return mThread == PR_GetCurrentThread();
+}
+
+uint32_t CacheIOThread::QueueSize(bool highPriority) {
+ MonitorAutoLock lock(mMonitor);
+ if (highPriority) {
+ return mQueueLength[OPEN_PRIORITY] + mQueueLength[READ_PRIORITY];
+ }
+
+ return mQueueLength[OPEN_PRIORITY] + mQueueLength[READ_PRIORITY] +
+ mQueueLength[MANAGEMENT] + mQueueLength[OPEN] + mQueueLength[READ];
+}
+
+bool CacheIOThread::YieldInternal() {
+ if (!IsCurrentThread()) {
+ NS_WARNING(
+ "Trying to yield to priority events on non-cache2 I/O thread? "
+ "You probably do something wrong.");
+ return false;
+ }
+
+ if (mCurrentlyExecutingLevel == XPCOM_LEVEL) {
+ // Doesn't make any sense, since this handler is the one
+ // that would be executed as the next one.
+ return false;
+ }
+
+ if (!EventsPending(mCurrentlyExecutingLevel)) return false;
+
+ mRerunCurrentEvent = true;
+ return true;
+}
+
+void CacheIOThread::Shutdown() {
+ if (!mThread) {
+ return;
+ }
+
+ {
+ MonitorAutoLock lock(mMonitor);
+ mShutdown = true;
+ mMonitor.NotifyAll();
+ }
+
+ PR_JoinThread(mThread);
+ mThread = nullptr;
+}
+
+void CacheIOThread::CancelBlockingIO() {
+ // This is an attempt to cancel any blocking I/O operation taking
+ // too long time.
+ if (!mNativeThreadHandle) {
+ return;
+ }
+
+ if (!mIOCancelableEvents) {
+ LOG(("CacheIOThread::CancelBlockingIO, no blocking operation to cancel"));
+ return;
+ }
+
+ // OK, when we are here, we are processing an IO on the thread that
+ // can be cancelled.
+ mNativeThreadHandle->CancelBlockingIO(mMonitor);
+}
+
+already_AddRefed<nsIEventTarget> CacheIOThread::Target() {
+ nsCOMPtr<nsIEventTarget> target;
+
+ target = mXPCOMThread;
+ if (!target && mThread) {
+ MonitorAutoLock lock(mMonitor);
+ while (!mXPCOMThread) {
+ lock.Wait();
+ }
+
+ target = mXPCOMThread;
+ }
+
+ return target.forget();
+}
+
+// static
+void CacheIOThread::ThreadFunc(void* aClosure) {
+ // XXXmstange We'd like to register this thread with the profiler, but doing
+ // so causes leaks, see bug 1323100.
+ NS_SetCurrentThreadName("Cache2 I/O");
+
+ mozilla::IOInterposer::RegisterCurrentThread();
+ // We hold on to this reference for the duration of the thread.
+ RefPtr<CacheIOThread> thread =
+ dont_AddRef(static_cast<CacheIOThread*>(aClosure));
+ thread->ThreadFunc();
+ mozilla::IOInterposer::UnregisterCurrentThread();
+}
+
+void CacheIOThread::ThreadFunc() {
+ nsCOMPtr<nsIThreadInternal> threadInternal;
+
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ MOZ_ASSERT(mNativeThreadHandle);
+ mNativeThreadHandle->InitThread();
+
+ auto queue =
+ MakeRefPtr<ThreadEventQueue>(MakeUnique<mozilla::EventQueue>());
+ nsCOMPtr<nsIThread> xpcomThread =
+ nsThreadManager::get().CreateCurrentThread(queue);
+
+ threadInternal = do_QueryInterface(xpcomThread);
+ if (threadInternal) threadInternal->SetObserver(this);
+
+ mXPCOMThread = xpcomThread.forget().take();
+ nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
+
+ lock.NotifyAll();
+
+ do {
+ loopStart:
+ // Reset the lowest level now, so that we can detect a new event on
+ // a lower level (i.e. higher priority) has been scheduled while
+ // executing any previously scheduled event.
+ mLowestLevelWaiting = LAST_LEVEL;
+
+ // Process xpcom events first
+ while (mHasXPCOMEvents) {
+ mHasXPCOMEvents = false;
+ mCurrentlyExecutingLevel = XPCOM_LEVEL;
+
+ MonitorAutoUnlock unlock(mMonitor);
+
+ bool processedEvent;
+ nsresult rv;
+ do {
+ rv = thread->ProcessNextEvent(false, &processedEvent);
+
+ ++mEventCounter;
+ MOZ_ASSERT(mNativeThreadHandle);
+ } while (NS_SUCCEEDED(rv) && processedEvent);
+ }
+
+ uint32_t level;
+ for (level = 0; level < LAST_LEVEL; ++level) {
+ if (!mEventQueue[level].Length()) {
+ // no events on this level, go to the next level
+ continue;
+ }
+
+ LoopOneLevel(level);
+
+ // Go to the first (lowest) level again
+ goto loopStart;
+ }
+
+ if (EventsPending()) {
+ continue;
+ }
+
+ if (mShutdown) {
+ break;
+ }
+
+ AUTO_PROFILER_LABEL("CacheIOThread::ThreadFunc::Wait", IDLE);
+ lock.Wait();
+
+ } while (true);
+
+ MOZ_ASSERT(!EventsPending());
+
+#ifdef DEBUG
+ // This is for correct assertion on XPCOM events dispatch.
+ mInsideLoop = false;
+#endif
+ } // lock
+
+ if (threadInternal) threadInternal->SetObserver(nullptr);
+}
+
+void CacheIOThread::LoopOneLevel(uint32_t aLevel) {
+ mMonitor.AssertCurrentThreadOwns();
+ EventQueue events = std::move(mEventQueue[aLevel]);
+ EventQueue::size_type length = events.Length();
+
+ mCurrentlyExecutingLevel = aLevel;
+
+ bool returnEvents = false;
+ bool reportTelemetry = true;
+
+ EventQueue::size_type index;
+ {
+ MonitorAutoUnlock unlock(mMonitor);
+
+ for (index = 0; index < length; ++index) {
+ if (EventsPending(aLevel)) {
+ // Somebody scheduled a new event on a lower level, break and harry
+ // to execute it! Don't forget to return what we haven't exec.
+ returnEvents = true;
+ break;
+ }
+
+ if (reportTelemetry) {
+ reportTelemetry = false;
+ CacheIOTelemetry::Report(aLevel, length);
+ }
+
+ // Drop any previous flagging, only an event on the current level may set
+ // this flag.
+ mRerunCurrentEvent = false;
+
+ LogRunnable::Run log(events[index].get());
+
+ events[index]->Run();
+
+ MOZ_ASSERT(mNativeThreadHandle);
+
+ if (mRerunCurrentEvent) {
+ // The event handler yields to higher priority events and wants to
+ // rerun.
+ log.WillRunAgain();
+ returnEvents = true;
+ break;
+ }
+
+ ++mEventCounter;
+ --mQueueLength[aLevel];
+
+ // Release outside the lock.
+ events[index] = nullptr;
+ }
+ }
+
+ if (returnEvents) {
+ // This code must prevent any AddRef/Release calls on the stored COMPtrs as
+ // it might be exhaustive and block the monitor's lock for an excessive
+ // amout of time.
+
+ // 'index' points at the event that was interrupted and asked for re-run,
+ // all events before have run, been nullified, and can be removed.
+ events.RemoveElementsAt(0, index);
+ // Move events that might have been scheduled on this queue to the tail to
+ // preserve the expected per-queue FIFO order.
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ events.AppendElements(std::move(mEventQueue[aLevel]));
+ // And finally move everything back to the main queue.
+ mEventQueue[aLevel] = std::move(events);
+ }
+}
+
+bool CacheIOThread::EventsPending(uint32_t aLastLevel) {
+ return mLowestLevelWaiting < aLastLevel || mHasXPCOMEvents;
+}
+
+NS_IMETHODIMP CacheIOThread::OnDispatchedEvent() {
+ MonitorAutoLock lock(mMonitor);
+ mHasXPCOMEvents = true;
+ MOZ_ASSERT(mInsideLoop);
+ lock.Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheIOThread::OnProcessNextEvent(nsIThreadInternal* thread,
+ bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheIOThread::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
+
+// Memory reporting
+
+size_t CacheIOThread::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ MonitorAutoLock lock(const_cast<CacheIOThread*>(this)->mMonitor);
+
+ size_t n = 0;
+ for (const auto& event : mEventQueue) {
+ n += event.ShallowSizeOfExcludingThis(mallocSizeOf);
+ // Events referenced by the queues are arbitrary objects we cannot be sure
+ // are reported elsewhere as well as probably not implementing nsISizeOf
+ // interface. Deliberatly omitting them from reporting here.
+ }
+
+ return n;
+}
+
+size_t CacheIOThread::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+CacheIOThread::Cancelable::Cancelable(bool aCancelable)
+ : mCancelable(aCancelable) {
+ // This will only ever be used on the I/O thread,
+ // which is expected to be alive longer than this class.
+ MOZ_ASSERT(CacheIOThread::sSelf);
+ MOZ_ASSERT(CacheIOThread::sSelf->IsCurrentThread());
+
+ if (mCancelable) {
+ ++CacheIOThread::sSelf->mIOCancelableEvents;
+ }
+}
+
+CacheIOThread::Cancelable::~Cancelable() {
+ MOZ_ASSERT(CacheIOThread::sSelf);
+
+ if (mCancelable) {
+ --CacheIOThread::sSelf->mIOCancelableEvents;
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheIOThread.h b/netwerk/cache2/CacheIOThread.h
new file mode 100644
index 0000000000..4cb8a964b8
--- /dev/null
+++ b/netwerk/cache2/CacheIOThread.h
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheIOThread__h__
+#define CacheIOThread__h__
+
+#include "nsIThreadInternal.h"
+#include "nsISupportsImpl.h"
+#include "prthread.h"
+#include "nsTArray.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace net {
+
+namespace detail {
+// A class keeping platform specific information needed to
+// cancel any long blocking synchronous IO. Must be predeclared here
+// since including windows.h breaks stuff with number of macro definition
+// conflicts.
+class NativeThreadHandle;
+} // namespace detail
+
+class CacheIOThread final : public nsIThreadObserver {
+ virtual ~CacheIOThread();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADOBSERVER
+
+ CacheIOThread();
+
+ using EventQueue = nsTArray<nsCOMPtr<nsIRunnable>>;
+
+ enum ELevel : uint32_t {
+ OPEN_PRIORITY,
+ READ_PRIORITY,
+ MANAGEMENT, // Doesn't do any actual I/O
+ OPEN,
+ READ,
+ WRITE_PRIORITY,
+ WRITE,
+ INDEX,
+ EVICT,
+ LAST_LEVEL,
+
+ // This is actually executed as the first level, but we want this enum
+ // value merely as an indicator while other values are used as indexes
+ // to the queue array. Hence put at end and not as the first.
+ XPCOM_LEVEL
+ };
+
+ nsresult Init();
+ nsresult Dispatch(nsIRunnable* aRunnable, uint32_t aLevel);
+ nsresult Dispatch(already_AddRefed<nsIRunnable>, uint32_t aLevel);
+ // Makes sure that any previously posted event to OPEN or OPEN_PRIORITY
+ // levels (such as file opennings and dooms) are executed before aRunnable
+ // that is intended to evict stuff from the cache.
+ nsresult DispatchAfterPendingOpens(nsIRunnable* aRunnable);
+ bool IsCurrentThread();
+
+ uint32_t QueueSize(bool highPriority);
+
+ uint32_t EventCounter() const { return mEventCounter; }
+
+ /**
+ * Callable only on this thread, checks if there is an event waiting in
+ * the event queue with a higher execution priority. If so, the result
+ * is true and the current event handler should break it's work and return
+ * from Run() method immediately. The event handler will be rerun again
+ * when all more priority events are processed. Events pending after this
+ * handler (i.e. the one that called YieldAndRerun()) will not execute sooner
+ * then this handler is executed w/o a call to YieldAndRerun().
+ */
+ static bool YieldAndRerun() { return sSelf ? sSelf->YieldInternal() : false; }
+
+ void Shutdown();
+ // This method checks if there is a long blocking IO on the
+ // IO thread and tries to cancel it. It waits maximum of
+ // two seconds.
+ void CancelBlockingIO();
+ already_AddRefed<nsIEventTarget> Target();
+
+ // A stack class used to annotate running interruptable I/O event
+ class Cancelable {
+ bool mCancelable;
+
+ public:
+ explicit Cancelable(bool aCancelable);
+ ~Cancelable();
+ };
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ static void ThreadFunc(void* aClosure);
+ void ThreadFunc();
+ void LoopOneLevel(uint32_t aLevel) MOZ_REQUIRES(mMonitor);
+ bool EventsPending(uint32_t aLastLevel = LAST_LEVEL);
+ nsresult DispatchInternal(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aLevel);
+ bool YieldInternal();
+
+ static CacheIOThread* sSelf;
+
+ mozilla::Monitor mMonitor{"CacheIOThread"};
+ PRThread* mThread{nullptr};
+ // Only set in Init(), before the thread is started, which reads it but never
+ // writes
+ UniquePtr<detail::NativeThreadHandle> mNativeThreadHandle;
+ Atomic<nsIThread*> mXPCOMThread{nullptr};
+ Atomic<uint32_t, Relaxed> mLowestLevelWaiting{LAST_LEVEL};
+ uint32_t mCurrentlyExecutingLevel{0}; // only accessed on CacheIO Thread
+
+ // Keeps the length of the each event queue, since LoopOneLevel moves all
+ // events into a local array.
+ Atomic<int32_t> mQueueLength[LAST_LEVEL];
+
+ EventQueue mEventQueue[LAST_LEVEL] MOZ_GUARDED_BY(mMonitor);
+ // Raised when nsIEventTarget.Dispatch() is called on this thread
+ Atomic<bool, Relaxed> mHasXPCOMEvents{false};
+ // See YieldAndRerun() above
+ bool mRerunCurrentEvent{false}; // Only accessed on the cache thread
+ // Signal to process all pending events and then shutdown
+ // Synchronized by mMonitor
+ bool mShutdown MOZ_GUARDED_BY(mMonitor){false};
+ // If > 0 there is currently an I/O operation on the thread that
+ // can be canceled when after shutdown, see the Shutdown() method
+ // for usage. Made a counter to allow nesting of the Cancelable class.
+ Atomic<uint32_t, Relaxed> mIOCancelableEvents{0};
+ // Event counter that increases with every event processed.
+ Atomic<uint32_t, Relaxed> mEventCounter{0};
+#ifdef DEBUG
+ bool mInsideLoop MOZ_GUARDED_BY(mMonitor){true};
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheIndex.cpp b/netwerk/cache2/CacheIndex.cpp
new file mode 100644
index 0000000000..c0cb63ef6d
--- /dev/null
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -0,0 +1,3921 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheIndex.h"
+
+#include "CacheLog.h"
+#include "CacheFileIOManager.h"
+#include "CacheFileMetadata.h"
+#include "CacheFileUtils.h"
+#include "CacheIndexIterator.h"
+#include "CacheIndexContextIterator.h"
+#include "nsThreadUtils.h"
+#include "nsISizeOf.h"
+#include "nsPrintfCString.h"
+#include "mozilla/DebugOnly.h"
+#include "prinrval.h"
+#include "nsIFile.h"
+#include "nsITimer.h"
+#include "mozilla/AutoRestore.h"
+#include <algorithm>
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+
+#define kMinUnwrittenChanges 300
+#define kMinDumpInterval 20000 // in milliseconds
+#define kMaxBufSize 16384
+#define kIndexVersion 0x0000000A
+#define kUpdateIndexStartDelay 50000 // in milliseconds
+#define kTelemetryReportBytesLimit (2U * 1024U * 1024U * 1024U) // 2GB
+
+#define INDEX_NAME "index"
+#define TEMP_INDEX_NAME "index.tmp"
+#define JOURNAL_NAME "index.log"
+
+namespace mozilla::net {
+
+namespace {
+
+class FrecencyComparator {
+ public:
+ bool Equals(const RefPtr<CacheIndexRecordWrapper>& a,
+ const RefPtr<CacheIndexRecordWrapper>& b) const {
+ if (!a || !b) {
+ return false;
+ }
+
+ return a->Get()->mFrecency == b->Get()->mFrecency;
+ }
+ bool LessThan(const RefPtr<CacheIndexRecordWrapper>& a,
+ const RefPtr<CacheIndexRecordWrapper>& b) const {
+ // Removed (=null) entries must be at the end of the array.
+ if (!a) {
+ return false;
+ }
+ if (!b) {
+ return true;
+ }
+
+ // Place entries with frecency 0 at the end of the non-removed entries.
+ if (a->Get()->mFrecency == 0) {
+ return false;
+ }
+ if (b->Get()->mFrecency == 0) {
+ return true;
+ }
+
+ return a->Get()->mFrecency < b->Get()->mFrecency;
+ }
+};
+
+} // namespace
+
+// used to dispatch a wrapper deletion the caller's thread
+// cannot be used on IOThread after shutdown begins
+class DeleteCacheIndexRecordWrapper : public Runnable {
+ CacheIndexRecordWrapper* mWrapper;
+
+ public:
+ explicit DeleteCacheIndexRecordWrapper(CacheIndexRecordWrapper* wrapper)
+ : Runnable("net::CacheIndex::DeleteCacheIndexRecordWrapper"),
+ mWrapper(wrapper) {}
+ NS_IMETHOD Run() override {
+ StaticMutexAutoLock lock(CacheIndex::sLock);
+
+ // if somehow the item is still in the frecency array, remove it
+ RefPtr<CacheIndex> index = CacheIndex::gInstance;
+ if (index) {
+ bool found = index->mFrecencyArray.RecordExistedUnlocked(mWrapper);
+ if (found) {
+ LOG(
+ ("DeleteCacheIndexRecordWrapper::Run() - \
+ record wrapper found in frecency array during deletion"));
+ index->mFrecencyArray.RemoveRecord(mWrapper, lock);
+ }
+ }
+
+ delete mWrapper;
+ return NS_OK;
+ }
+};
+
+void CacheIndexRecordWrapper::DispatchDeleteSelfToCurrentThread() {
+ // Dispatch during shutdown will not trigger DeleteCacheIndexRecordWrapper
+ nsCOMPtr<nsIRunnable> event = new DeleteCacheIndexRecordWrapper(this);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(event));
+}
+
+CacheIndexRecordWrapper::~CacheIndexRecordWrapper() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ CacheIndex::sLock.AssertCurrentThreadOwns();
+ RefPtr<CacheIndex> index = CacheIndex::gInstance;
+ if (index) {
+ bool found = index->mFrecencyArray.RecordExistedUnlocked(this);
+ MOZ_DIAGNOSTIC_ASSERT(!found);
+ }
+#endif
+}
+
+/**
+ * This helper class is responsible for keeping CacheIndex::mIndexStats and
+ * CacheIndex::mFrecencyArray up to date.
+ */
+class MOZ_RAII CacheIndexEntryAutoManage {
+ public:
+ CacheIndexEntryAutoManage(const SHA1Sum::Hash* aHash, CacheIndex* aIndex,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(CacheIndex::sLock)
+ : mIndex(aIndex), mProofOfLock(aProofOfLock) {
+ mHash = aHash;
+ const CacheIndexEntry* entry = FindEntry();
+ mIndex->mIndexStats.BeforeChange(entry);
+ if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
+ mOldRecord = entry->mRec;
+ mOldFrecency = entry->mRec->Get()->mFrecency;
+ }
+ }
+
+ ~CacheIndexEntryAutoManage() MOZ_REQUIRES(CacheIndex::sLock) {
+ const CacheIndexEntry* entry = FindEntry();
+ mIndex->mIndexStats.AfterChange(entry);
+ if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
+ entry = nullptr;
+ }
+
+ if (entry && !mOldRecord) {
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
+ mIndex->AddRecordToIterators(entry->mRec, mProofOfLock);
+ } else if (!entry && mOldRecord) {
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
+ mIndex->RemoveRecordFromIterators(mOldRecord, mProofOfLock);
+ } else if (entry && mOldRecord) {
+ if (entry->mRec != mOldRecord) {
+ // record has a different address, we have to replace it
+ mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec, mProofOfLock);
+
+ if (entry->mRec->Get()->mFrecency == mOldFrecency) {
+ // If frecency hasn't changed simply replace the pointer
+ mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, entry->mRec,
+ mProofOfLock);
+ } else {
+ // Remove old pointer and insert the new one at the end of the array
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
+ }
+ } else if (entry->mRec->Get()->mFrecency != mOldFrecency) {
+ // Move the element at the end of the array
+ mIndex->mFrecencyArray.RemoveRecord(entry->mRec, mProofOfLock);
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
+ }
+ } else {
+ // both entries were removed or not initialized, do nothing
+ }
+ }
+
+ // We cannot rely on nsTHashtable::GetEntry() in case we are removing entries
+ // while iterating. Destructor is called before the entry is removed. Caller
+ // must call one of following methods to skip lookup in the hashtable.
+ void DoNotSearchInIndex() { mDoNotSearchInIndex = true; }
+ void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; }
+
+ private:
+ const CacheIndexEntry* FindEntry() MOZ_REQUIRES(CacheIndex::sLock) {
+ const CacheIndexEntry* entry = nullptr;
+
+ switch (mIndex->mState) {
+ case CacheIndex::READING:
+ case CacheIndex::WRITING:
+ if (!mDoNotSearchInUpdates) {
+ entry = mIndex->mPendingUpdates.GetEntry(*mHash);
+ }
+ [[fallthrough]];
+ case CacheIndex::BUILDING:
+ case CacheIndex::UPDATING:
+ case CacheIndex::READY:
+ if (!entry && !mDoNotSearchInIndex) {
+ entry = mIndex->mIndex.GetEntry(*mHash);
+ }
+ break;
+ case CacheIndex::INITIAL:
+ case CacheIndex::SHUTDOWN:
+ default:
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+
+ return entry;
+ }
+
+ const SHA1Sum::Hash* mHash;
+ RefPtr<CacheIndex> mIndex;
+ RefPtr<CacheIndexRecordWrapper> mOldRecord;
+ uint32_t mOldFrecency{0};
+ bool mDoNotSearchInIndex{false};
+ bool mDoNotSearchInUpdates{false};
+ const StaticMutexAutoLock& mProofOfLock;
+};
+
+class FileOpenHelper final : public CacheFileIOListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit FileOpenHelper(CacheIndex* aIndex)
+ : mIndex(aIndex), mCanceled(false) {}
+
+ void Cancel() {
+ CacheIndex::sLock.AssertCurrentThreadOwns();
+ mCanceled = true;
+ }
+
+ private:
+ virtual ~FileOpenHelper() = default;
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override {
+ MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override {
+ MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override {
+ MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
+ MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
+ nsresult aResult) override {
+ MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<CacheIndex> mIndex;
+ bool mCanceled;
+};
+
+NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle* aHandle,
+ nsresult aResult) {
+ StaticMutexAutoLock lock(CacheIndex::sLock);
+
+ if (mCanceled) {
+ if (aHandle) {
+ CacheFileIOManager::DoomFile(aHandle, nullptr);
+ }
+
+ return NS_OK;
+ }
+
+ mIndex->OnFileOpenedInternal(this, aHandle, aResult, lock);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener);
+
+StaticRefPtr<CacheIndex> CacheIndex::gInstance;
+StaticMutex CacheIndex::sLock;
+
+NS_IMPL_ADDREF(CacheIndex)
+NS_IMPL_RELEASE(CacheIndex)
+
+NS_INTERFACE_MAP_BEGIN(CacheIndex)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+NS_INTERFACE_MAP_END
+
+CacheIndex::CacheIndex() {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::CacheIndex [this=%p]", this));
+ MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!");
+}
+
+CacheIndex::~CacheIndex() {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::~CacheIndex [this=%p]", this));
+
+ ReleaseBuffer();
+}
+
+// static
+nsresult CacheIndex::Init(nsIFile* aCacheDirectory) {
+ LOG(("CacheIndex::Init()"));
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ if (gInstance) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ RefPtr<CacheIndex> idx = new CacheIndex();
+
+ nsresult rv = idx->InitInternal(aCacheDirectory, lock);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gInstance = std::move(idx);
+ return NS_OK;
+}
+
+nsresult CacheIndex::InitInternal(nsIFile* aCacheDirectory,
+ const StaticMutexAutoLock& aProofOfLock) {
+ nsresult rv;
+ sLock.AssertCurrentThreadOwns();
+
+ rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStartTime = TimeStamp::NowLoRes();
+
+ ReadIndexFromDisk(aProofOfLock);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::PreShutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance.get()));
+
+ nsresult rv;
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LOG(
+ ("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, "
+ "dontMarkIndexClean=%d]",
+ index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean));
+
+ LOG(("CacheIndex::PreShutdown() - Closing iterators."));
+ for (uint32_t i = 0; i < index->mIterators.Length();) {
+ rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
+ if (NS_FAILED(rv)) {
+ // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
+ // it returns success.
+ LOG(
+ ("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
+ "[rv=0x%08" PRIx32 "]",
+ index->mIterators[i], static_cast<uint32_t>(rv)));
+ i++;
+ }
+ }
+
+ index->mShuttingDown = true;
+
+ if (index->mState == READY) {
+ return NS_OK; // nothing to do
+ }
+
+ nsCOMPtr<nsIRunnable> event;
+ event = NewRunnableMethod("net::CacheIndex::PreShutdownInternal", index,
+ &CacheIndex::PreShutdownInternal);
+
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ // PreShutdownInternal() will be executed before any queued event on INDEX
+ // level. That's OK since we don't want to wait for any operation in progess.
+ rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event");
+ LOG(("CacheIndex::PreShutdown() - Can't dispatch event"));
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void CacheIndex::PreShutdownInternal() {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(
+ ("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, "
+ "dontMarkIndexClean=%d]",
+ mState, mIndexOnDiskIsValid, mDontMarkIndexClean));
+
+ MOZ_ASSERT(mShuttingDown);
+
+ if (mUpdateTimer) {
+ mUpdateTimer->Cancel();
+ mUpdateTimer = nullptr;
+ }
+
+ switch (mState) {
+ case WRITING:
+ FinishWrite(false, lock);
+ break;
+ case READY:
+ // nothing to do, write the journal in Shutdown()
+ break;
+ case READING:
+ FinishRead(false, lock);
+ break;
+ case BUILDING:
+ case UPDATING:
+ FinishUpdate(false, lock);
+ break;
+ default:
+ MOZ_ASSERT(false, "Implement me!");
+ }
+
+ // We should end up in READY state
+ MOZ_ASSERT(mState == READY);
+}
+
+// static
+nsresult CacheIndex::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance.get()));
+
+ RefPtr<CacheIndex> index = gInstance.forget();
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ bool sanitize = CacheObserver::ClearCacheOnShutdown();
+
+ LOG(
+ ("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, "
+ "dontMarkIndexClean=%d, sanitize=%d]",
+ index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean,
+ sanitize));
+
+ MOZ_ASSERT(index->mShuttingDown);
+
+ EState oldState = index->mState;
+ index->ChangeState(SHUTDOWN, lock);
+
+ if (oldState != READY) {
+ LOG(
+ ("CacheIndex::Shutdown() - Unexpected state. Did posting of "
+ "PreShutdownInternal() fail?"));
+ }
+
+ switch (oldState) {
+ case WRITING:
+ index->FinishWrite(false, lock);
+ [[fallthrough]];
+ case READY:
+ if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) {
+ if (!sanitize && NS_FAILED(index->WriteLogToDisk())) {
+ index->RemoveJournalAndTempFile();
+ }
+ } else {
+ index->RemoveJournalAndTempFile();
+ }
+ break;
+ case READING:
+ index->FinishRead(false, lock);
+ break;
+ case BUILDING:
+ case UPDATING:
+ index->FinishUpdate(false, lock);
+ break;
+ default:
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+
+ if (sanitize) {
+ index->RemoveAllIndexFiles();
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::AddEntry(const SHA1Sum::Hash* aHash) {
+ LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Getters in CacheIndexStats assert when mStateLogged is true since the
+ // information is incomplete between calls to BeforeChange() and AfterChange()
+ // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether
+ // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage.
+ bool updateIfNonFreshEntriesExist = false;
+
+ {
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
+
+ CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
+ bool entryRemoved = entry && entry->IsRemoved();
+ CacheIndexEntryUpdate* updated = nullptr;
+
+ if (index->mState == READY || index->mState == UPDATING ||
+ index->mState == BUILDING) {
+ MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
+
+ if (entry && !entryRemoved) {
+ // Found entry in index that shouldn't exist.
+
+ if (entry->IsFresh()) {
+ // Someone removed the file on disk while FF is running. Update
+ // process can fix only non-fresh entries (i.e. entries that were not
+ // added within this session). Start update only if we have such
+ // entries.
+ //
+ // TODO: This should be very rare problem. If it turns out not to be
+ // true, change the update process so that it also iterates all
+ // initialized non-empty entries and checks whether the file exists.
+
+ LOG(
+ ("CacheIndex::AddEntry() - Cache file was removed outside FF "
+ "process!"));
+
+ updateIfNonFreshEntriesExist = true;
+ } else if (index->mState == READY) {
+ // Index is outdated, update it.
+ LOG(
+ ("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
+ "update is needed"));
+ index->mIndexNeedsUpdate = true;
+ } else {
+ // We cannot be here when building index since all entries are fresh
+ // during building.
+ MOZ_ASSERT(index->mState == UPDATING);
+ }
+ }
+
+ if (!entry) {
+ entry = index->mIndex.PutEntry(*aHash);
+ }
+ } else { // WRITING, READING
+ updated = index->mPendingUpdates.GetEntry(*aHash);
+ bool updatedRemoved = updated && updated->IsRemoved();
+
+ if ((updated && !updatedRemoved) ||
+ (!updated && entry && !entryRemoved && entry->IsFresh())) {
+ // Fresh entry found, so the file was removed outside FF
+ LOG(
+ ("CacheIndex::AddEntry() - Cache file was removed outside FF "
+ "process!"));
+
+ updateIfNonFreshEntriesExist = true;
+ } else if (!updated && entry && !entryRemoved) {
+ if (index->mState == WRITING) {
+ LOG(
+ ("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
+ "update is needed"));
+ index->mIndexNeedsUpdate = true;
+ }
+ // Ignore if state is READING since the index information is partial
+ }
+
+ updated = index->mPendingUpdates.PutEntry(*aHash);
+ }
+
+ if (updated) {
+ updated->InitNew();
+ updated->MarkDirty();
+ updated->MarkFresh();
+ } else {
+ entry->InitNew();
+ entry->MarkDirty();
+ entry->MarkFresh();
+ }
+ }
+
+ if (updateIfNonFreshEntriesExist &&
+ index->mIndexStats.Count() != index->mIndexStats.Fresh()) {
+ index->mIndexNeedsUpdate = true;
+ }
+
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::EnsureEntryExists(const SHA1Sum::Hash* aHash) {
+ LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ {
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
+
+ CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
+ bool entryRemoved = entry && entry->IsRemoved();
+
+ if (index->mState == READY || index->mState == UPDATING ||
+ index->mState == BUILDING) {
+ MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
+
+ if (!entry || entryRemoved) {
+ if (entryRemoved && entry->IsFresh()) {
+ // This could happen only if somebody copies files to the entries
+ // directory while FF is running.
+ LOG(
+ ("CacheIndex::EnsureEntryExists() - Cache file was added outside "
+ "FF process! Update is needed."));
+ index->mIndexNeedsUpdate = true;
+ } else if (index->mState == READY ||
+ (entryRemoved && !entry->IsFresh())) {
+ // Removed non-fresh entries can be present as a result of
+ // MergeJournal()
+ LOG(
+ ("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
+ " exist, update is needed"));
+ index->mIndexNeedsUpdate = true;
+ }
+
+ if (!entry) {
+ entry = index->mIndex.PutEntry(*aHash);
+ }
+ entry->InitNew();
+ entry->MarkDirty();
+ }
+ entry->MarkFresh();
+ } else { // WRITING, READING
+ CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
+ bool updatedRemoved = updated && updated->IsRemoved();
+
+ if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) {
+ // Fresh information about missing entry found. This could happen only
+ // if somebody copies files to the entries directory while FF is
+ // running.
+ LOG(
+ ("CacheIndex::EnsureEntryExists() - Cache file was added outside "
+ "FF process! Update is needed."));
+ index->mIndexNeedsUpdate = true;
+ } else if (!updated && (!entry || entryRemoved)) {
+ if (index->mState == WRITING) {
+ LOG(
+ ("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
+ " exist, update is needed"));
+ index->mIndexNeedsUpdate = true;
+ }
+ // Ignore if state is READING since the index information is partial
+ }
+
+ // We don't need entryRemoved and updatedRemoved info anymore
+ if (entryRemoved) entry = nullptr;
+ if (updatedRemoved) updated = nullptr;
+
+ if (updated) {
+ updated->MarkFresh();
+ } else {
+ if (!entry) {
+ // Create a new entry
+ updated = index->mPendingUpdates.PutEntry(*aHash);
+ updated->InitNew();
+ updated->MarkFresh();
+ updated->MarkDirty();
+ } else {
+ if (!entry->IsFresh()) {
+ // To mark the entry fresh we must make a copy of index entry
+ // since the index is read-only.
+ updated = index->mPendingUpdates.PutEntry(*aHash);
+ *updated = *entry;
+ updated->MarkFresh();
+ }
+ }
+ }
+ }
+ }
+
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::InitEntry(const SHA1Sum::Hash* aHash,
+ OriginAttrsHash aOriginAttrsHash,
+ bool aAnonymous, bool aPinned) {
+ LOG(
+ ("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, "
+ "originAttrsHash=%" PRIx64 ", anonymous=%d, pinned=%d]",
+ LOGSHA1(aHash), aOriginAttrsHash, aAnonymous, aPinned));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ {
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
+
+ CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
+ CacheIndexEntryUpdate* updated = nullptr;
+ bool reinitEntry = false;
+
+ if (entry && entry->IsRemoved()) {
+ entry = nullptr;
+ }
+
+ if (index->mState == READY || index->mState == UPDATING ||
+ index->mState == BUILDING) {
+ MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
+ MOZ_ASSERT(entry);
+ MOZ_ASSERT(entry->IsFresh());
+
+ if (!entry) {
+ LOG(("CacheIndex::InitEntry() - Entry was not found in mIndex!"));
+ NS_WARNING(
+ ("CacheIndex::InitEntry() - Entry was not found in mIndex!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) {
+ index->mIndexNeedsUpdate =
+ true; // TODO Does this really help in case of collision?
+ reinitEntry = true;
+ } else {
+ if (entry->IsInitialized()) {
+ return NS_OK;
+ }
+ }
+ } else {
+ updated = index->mPendingUpdates.GetEntry(*aHash);
+ DebugOnly<bool> removed = updated && updated->IsRemoved();
+
+ MOZ_ASSERT(updated || !removed);
+ MOZ_ASSERT(updated || entry);
+
+ if (!updated && !entry) {
+ LOG(
+ ("CacheIndex::InitEntry() - Entry was found neither in mIndex nor "
+ "in mPendingUpdates!"));
+ NS_WARNING(
+ ("CacheIndex::InitEntry() - Entry was found neither in "
+ "mIndex nor in mPendingUpdates!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (updated) {
+ MOZ_ASSERT(updated->IsFresh());
+
+ if (IsCollision(updated, aOriginAttrsHash, aAnonymous)) {
+ index->mIndexNeedsUpdate = true;
+ reinitEntry = true;
+ } else {
+ if (updated->IsInitialized()) {
+ return NS_OK;
+ }
+ }
+ } else {
+ MOZ_ASSERT(entry->IsFresh());
+
+ if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) {
+ index->mIndexNeedsUpdate = true;
+ reinitEntry = true;
+ } else {
+ if (entry->IsInitialized()) {
+ return NS_OK;
+ }
+ }
+
+ // make a copy of a read-only entry
+ updated = index->mPendingUpdates.PutEntry(*aHash);
+ *updated = *entry;
+ }
+ }
+
+ if (reinitEntry) {
+ // There is a collision and we are going to rewrite this entry. Initialize
+ // it as a new entry.
+ if (updated) {
+ updated->InitNew();
+ updated->MarkFresh();
+ } else {
+ entry->InitNew();
+ entry->MarkFresh();
+ }
+ }
+
+ if (updated) {
+ updated->Init(aOriginAttrsHash, aAnonymous, aPinned);
+ updated->MarkDirty();
+ } else {
+ entry->Init(aOriginAttrsHash, aAnonymous, aPinned);
+ entry->MarkDirty();
+ }
+ }
+
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::RemoveEntry(const SHA1Sum::Hash* aHash) {
+ LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ {
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
+
+ CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
+ bool entryRemoved = entry && entry->IsRemoved();
+
+ if (index->mState == READY || index->mState == UPDATING ||
+ index->mState == BUILDING) {
+ MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
+
+ if (!entry || entryRemoved) {
+ if (entryRemoved && entry->IsFresh()) {
+ // This could happen only if somebody copies files to the entries
+ // directory while FF is running.
+ LOG(
+ ("CacheIndex::RemoveEntry() - Cache file was added outside FF "
+ "process! Update is needed."));
+ index->mIndexNeedsUpdate = true;
+ } else if (index->mState == READY ||
+ (entryRemoved && !entry->IsFresh())) {
+ // Removed non-fresh entries can be present as a result of
+ // MergeJournal()
+ LOG(
+ ("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
+ ", update is needed"));
+ index->mIndexNeedsUpdate = true;
+ }
+ } else {
+ if (entry) {
+ if (!entry->IsDirty() && entry->IsFileEmpty()) {
+ index->mIndex.RemoveEntry(entry);
+ entry = nullptr;
+ } else {
+ entry->MarkRemoved();
+ entry->MarkDirty();
+ entry->MarkFresh();
+ }
+ }
+ }
+ } else { // WRITING, READING
+ CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
+ bool updatedRemoved = updated && updated->IsRemoved();
+
+ if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) {
+ // Fresh information about missing entry found. This could happen only
+ // if somebody copies files to the entries directory while FF is
+ // running.
+ LOG(
+ ("CacheIndex::RemoveEntry() - Cache file was added outside FF "
+ "process! Update is needed."));
+ index->mIndexNeedsUpdate = true;
+ } else if (!updated && (!entry || entryRemoved)) {
+ if (index->mState == WRITING) {
+ LOG(
+ ("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
+ ", update is needed"));
+ index->mIndexNeedsUpdate = true;
+ }
+ // Ignore if state is READING since the index information is partial
+ }
+
+ if (!updated) {
+ updated = index->mPendingUpdates.PutEntry(*aHash);
+ updated->InitNew();
+ }
+
+ updated->MarkRemoved();
+ updated->MarkDirty();
+ updated->MarkFresh();
+ }
+ }
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::UpdateEntry(const SHA1Sum::Hash* aHash,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType,
+ const uint32_t* aSize) {
+ LOG(
+ ("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
+ "frecency=%s, hasAltData=%s, onStartTime=%s, onStopTime=%s, "
+ "contentType=%s, size=%s]",
+ LOGSHA1(aHash), aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
+ aHasAltData ? (*aHasAltData ? "true" : "false") : "",
+ aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
+ aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
+ aContentType ? nsPrintfCString("%u", *aContentType).get() : "",
+ aSize ? nsPrintfCString("%u", *aSize).get() : ""));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ {
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
+
+ CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
+
+ if (entry && entry->IsRemoved()) {
+ entry = nullptr;
+ }
+
+ if (index->mState == READY || index->mState == UPDATING ||
+ index->mState == BUILDING) {
+ MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
+ MOZ_ASSERT(entry);
+
+ if (!entry) {
+ LOG(("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
+ NS_WARNING(
+ ("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!HasEntryChanged(entry, aFrecency, aHasAltData, aOnStartTime,
+ aOnStopTime, aContentType, aSize)) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(entry->IsFresh());
+ MOZ_ASSERT(entry->IsInitialized());
+ entry->MarkDirty();
+
+ if (aFrecency) {
+ entry->SetFrecency(*aFrecency);
+ }
+
+ if (aHasAltData) {
+ entry->SetHasAltData(*aHasAltData);
+ }
+
+ if (aOnStartTime) {
+ entry->SetOnStartTime(*aOnStartTime);
+ }
+
+ if (aOnStopTime) {
+ entry->SetOnStopTime(*aOnStopTime);
+ }
+
+ if (aContentType) {
+ entry->SetContentType(*aContentType);
+ }
+
+ if (aSize) {
+ entry->SetFileSize(*aSize);
+ }
+ } else {
+ CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
+ DebugOnly<bool> removed = updated && updated->IsRemoved();
+
+ MOZ_ASSERT(updated || !removed);
+ MOZ_ASSERT(updated || entry);
+
+ if (!updated) {
+ if (!entry) {
+ LOG(
+ ("CacheIndex::UpdateEntry() - Entry was found neither in mIndex "
+ "nor in mPendingUpdates!"));
+ NS_WARNING(
+ ("CacheIndex::UpdateEntry() - Entry was found neither in "
+ "mIndex nor in mPendingUpdates!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // make a copy of a read-only entry
+ updated = index->mPendingUpdates.PutEntry(*aHash);
+ *updated = *entry;
+ }
+
+ MOZ_ASSERT(updated->IsFresh());
+ MOZ_ASSERT(updated->IsInitialized());
+ updated->MarkDirty();
+
+ if (aFrecency) {
+ updated->SetFrecency(*aFrecency);
+ }
+
+ if (aHasAltData) {
+ updated->SetHasAltData(*aHasAltData);
+ }
+
+ if (aOnStartTime) {
+ updated->SetOnStartTime(*aOnStartTime);
+ }
+
+ if (aOnStopTime) {
+ updated->SetOnStopTime(*aOnStopTime);
+ }
+
+ if (aContentType) {
+ updated->SetContentType(*aContentType);
+ }
+
+ if (aSize) {
+ updated->SetFileSize(*aSize);
+ }
+ }
+ }
+
+ index->WriteIndexToDiskIfNeeded(lock);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::RemoveAll() {
+ LOG(("CacheIndex::RemoveAll()"));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsCOMPtr<nsIFile> file;
+
+ {
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ MOZ_ASSERT(!index->mRemovingAll);
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ AutoRestore<bool> saveRemovingAll(index->mRemovingAll);
+ index->mRemovingAll = true;
+
+ // Doom index and journal handles but don't null them out since this will be
+ // done in FinishWrite/FinishRead methods.
+ if (index->mIndexHandle) {
+ CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr);
+ } else {
+ // We don't have a handle to index file, so get the file here, but delete
+ // it outside the lock. Ignore the result since this is not fatal.
+ index->GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(file));
+ }
+
+ if (index->mJournalHandle) {
+ CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr);
+ }
+
+ switch (index->mState) {
+ case WRITING:
+ index->FinishWrite(false, lock);
+ break;
+ case READY:
+ // nothing to do
+ break;
+ case READING:
+ index->FinishRead(false, lock);
+ break;
+ case BUILDING:
+ case UPDATING:
+ index->FinishUpdate(false, lock);
+ break;
+ default:
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+
+ // We should end up in READY state
+ MOZ_ASSERT(index->mState == READY);
+
+ // There should not be any handle
+ MOZ_ASSERT(!index->mIndexHandle);
+ MOZ_ASSERT(!index->mJournalHandle);
+
+ index->mIndexOnDiskIsValid = false;
+ index->mIndexNeedsUpdate = false;
+
+ index->mIndexStats.Clear();
+ index->mFrecencyArray.Clear(lock);
+ index->mIndex.Clear();
+
+ for (uint32_t i = 0; i < index->mIterators.Length();) {
+ nsresult rv = index->mIterators[i]->CloseInternal(NS_ERROR_NOT_AVAILABLE);
+ if (NS_FAILED(rv)) {
+ // CacheIndexIterator::CloseInternal() removes itself from mIterators
+ // iff it returns success.
+ LOG(
+ ("CacheIndex::RemoveAll() - Failed to remove iterator %p. "
+ "[rv=0x%08" PRIx32 "]",
+ index->mIterators[i], static_cast<uint32_t>(rv)));
+ i++;
+ }
+ }
+ }
+
+ if (file) {
+ // Ignore the result. The file might not exist and the failure is not fatal.
+ file->Remove(false);
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::HasEntry(
+ const nsACString& aKey, EntryStatus* _retval,
+ const std::function<void(const CacheIndexEntry*)>& aCB) {
+ LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
+
+ SHA1Sum sum;
+ SHA1Sum::Hash hash;
+ sum.update(aKey.BeginReading(), aKey.Length());
+ sum.finish(hash);
+
+ return HasEntry(hash, _retval, aCB);
+}
+
+// static
+nsresult CacheIndex::HasEntry(
+ const SHA1Sum::Hash& hash, EntryStatus* _retval,
+ const std::function<void(const CacheIndexEntry*)>& aCB) {
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ const CacheIndexEntry* entry = nullptr;
+
+ switch (index->mState) {
+ case READING:
+ case WRITING:
+ entry = index->mPendingUpdates.GetEntry(hash);
+ [[fallthrough]];
+ case BUILDING:
+ case UPDATING:
+ case READY:
+ if (!entry) {
+ entry = index->mIndex.GetEntry(hash);
+ }
+ break;
+ case INITIAL:
+ case SHUTDOWN:
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+
+ if (!entry) {
+ if (index->mState == READY || index->mState == WRITING) {
+ *_retval = DOES_NOT_EXIST;
+ } else {
+ *_retval = DO_NOT_KNOW;
+ }
+ } else {
+ if (entry->IsRemoved()) {
+ if (entry->IsFresh()) {
+ *_retval = DOES_NOT_EXIST;
+ } else {
+ *_retval = DO_NOT_KNOW;
+ }
+ } else {
+ *_retval = EXISTS;
+ if (aCB) {
+ aCB(entry);
+ }
+ }
+ }
+
+ LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::GetEntryForEviction(bool aIgnoreEmptyEntries,
+ SHA1Sum::Hash* aHash, uint32_t* aCnt) {
+ LOG(("CacheIndex::GetEntryForEviction()"));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) return NS_ERROR_NOT_INITIALIZED;
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (index->mIndexStats.Size() == 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ int32_t mediaUsage =
+ round(static_cast<double>(index->mIndexStats.SizeByType(
+ nsICacheEntry::CONTENT_TYPE_MEDIA)) *
+ 100.0 / static_cast<double>(index->mIndexStats.Size()));
+ int32_t mediaUsageLimit =
+ StaticPrefs::browser_cache_disk_content_type_media_limit();
+ bool evictMedia = false;
+ if (mediaUsage > mediaUsageLimit) {
+ LOG(
+ ("CacheIndex::GetEntryForEviction() - media content type is over the "
+ "limit [mediaUsage=%d, mediaUsageLimit=%d]",
+ mediaUsage, mediaUsageLimit));
+ evictMedia = true;
+ }
+
+ SHA1Sum::Hash hash;
+ CacheIndexRecord* foundRecord = nullptr;
+ uint32_t skipped = 0;
+
+ // find first non-forced valid and unpinned entry with the lowest frecency
+ index->mFrecencyArray.SortIfNeeded(lock);
+
+ for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexRecord* rec = iter.Get()->Get();
+
+ memcpy(&hash, rec->mHash, sizeof(SHA1Sum::Hash));
+
+ ++skipped;
+
+ if (evictMedia && CacheIndexEntry::GetContentType(rec) !=
+ nsICacheEntry::CONTENT_TYPE_MEDIA) {
+ continue;
+ }
+
+ if (IsForcedValidEntry(&hash)) {
+ continue;
+ }
+
+ if (CacheIndexEntry::IsPinned(rec)) {
+ continue;
+ }
+
+ if (aIgnoreEmptyEntries && !CacheIndexEntry::GetFileSize(*rec)) {
+ continue;
+ }
+
+ --skipped;
+ foundRecord = rec;
+ break;
+ }
+
+ if (!foundRecord) return NS_ERROR_NOT_AVAILABLE;
+
+ *aCnt = skipped;
+
+ LOG(
+ ("CacheIndex::GetEntryForEviction() - returning entry "
+ "[hash=%08x%08x%08x%08x%08x, cnt=%u, frecency=%u, contentType=%u]",
+ LOGSHA1(&hash), *aCnt, foundRecord->mFrecency,
+ CacheIndexEntry::GetContentType(foundRecord)));
+
+ memcpy(aHash, &hash, sizeof(SHA1Sum::Hash));
+
+ return NS_OK;
+}
+
+// static
+bool CacheIndex::IsForcedValidEntry(const SHA1Sum::Hash* aHash) {
+ RefPtr<CacheFileHandle> handle;
+
+ CacheFileIOManager::gInstance->mHandles.GetHandle(aHash,
+ getter_AddRefs(handle));
+
+ if (!handle) return false;
+
+ nsCString hashKey = handle->Key();
+ return CacheStorageService::Self()->IsForcedValidEntry(hashKey);
+}
+
+// static
+nsresult CacheIndex::GetCacheSize(uint32_t* _retval) {
+ LOG(("CacheIndex::GetCacheSize()"));
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) return NS_ERROR_NOT_INITIALIZED;
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *_retval = index->mIndexStats.Size();
+ LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval));
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::GetEntryFileCount(uint32_t* _retval) {
+ LOG(("CacheIndex::GetEntryFileCount()"));
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *_retval = index->mIndexStats.ActiveEntriesCount();
+ LOG(("CacheIndex::GetEntryFileCount() - returning %u", *_retval));
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::GetCacheStats(nsILoadContextInfo* aInfo, uint32_t* aSize,
+ uint32_t* aCount) {
+ LOG(("CacheIndex::GetCacheStats() [info=%p]", aInfo));
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aSize = 0;
+ *aCount = 0;
+
+ for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
+ if (aInfo &&
+ !CacheIndexEntry::RecordMatchesLoadContextInfo(iter.Get(), aInfo)) {
+ continue;
+ }
+
+ *aSize += CacheIndexEntry::GetFileSize(*(iter.Get()->Get()));
+ ++*aCount;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::AsyncGetDiskConsumption(
+ nsICacheStorageConsumptionObserver* aObserver) {
+ LOG(("CacheIndex::AsyncGetDiskConsumption()"));
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<DiskConsumptionObserver> observer =
+ DiskConsumptionObserver::Init(aObserver);
+
+ NS_ENSURE_ARG(observer);
+
+ if ((index->mState == READY || index->mState == WRITING) &&
+ !index->mAsyncGetDiskConsumptionBlocked) {
+ LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately"));
+ // Safe to call the callback under the lock,
+ // we always post to the main thread.
+ observer->OnDiskConsumption(index->mIndexStats.Size() << 10);
+ return NS_OK;
+ }
+
+ LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback"));
+ // Will be called when the index get to the READY state.
+ index->mDiskConsumptionObservers.AppendElement(observer);
+
+ // Move forward with index re/building if it is pending
+ RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
+ if (ioThread) {
+ ioThread->Dispatch(
+ NS_NewRunnableFunction("net::CacheIndex::AsyncGetDiskConsumption",
+ []() -> void {
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+ if (index && index->mUpdateTimer) {
+ index->mUpdateTimer->Cancel();
+ index->DelayedUpdateLocked(lock);
+ }
+ }),
+ CacheIOThread::INDEX);
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::GetIterator(nsILoadContextInfo* aInfo, bool aAddNew,
+ CacheIndexIterator** _retval) {
+ LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<CacheIndexIterator> idxIter;
+ if (aInfo) {
+ idxIter = new CacheIndexContextIterator(index, aAddNew, aInfo);
+ } else {
+ idxIter = new CacheIndexIterator(index, aAddNew);
+ }
+
+ index->mFrecencyArray.SortIfNeeded(lock);
+
+ for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
+ idxIter->AddRecord(iter.Get(), lock);
+ }
+
+ index->mIterators.AppendElement(idxIter);
+ idxIter.swap(*_retval);
+ return NS_OK;
+}
+
+// static
+nsresult CacheIndex::IsUpToDate(bool* _retval) {
+ LOG(("CacheIndex::IsUpToDate()"));
+
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *_retval = (index->mState == READY || index->mState == WRITING) &&
+ !index->mIndexNeedsUpdate && !index->mShuttingDown;
+
+ LOG(("CacheIndex::IsUpToDate() - returning %d", *_retval));
+ return NS_OK;
+}
+
+bool CacheIndex::IsIndexUsable() {
+ MOZ_ASSERT(mState != INITIAL);
+
+ switch (mState) {
+ case INITIAL:
+ case SHUTDOWN:
+ return false;
+
+ case READING:
+ case WRITING:
+ case BUILDING:
+ case UPDATING:
+ case READY:
+ break;
+ }
+
+ return true;
+}
+
+// static
+bool CacheIndex::IsCollision(CacheIndexEntry* aEntry,
+ OriginAttrsHash aOriginAttrsHash,
+ bool aAnonymous) {
+ if (!aEntry->IsInitialized()) {
+ return false;
+ }
+
+ if (aEntry->Anonymous() != aAnonymous ||
+ aEntry->OriginAttrsHash() != aOriginAttrsHash) {
+ LOG(
+ ("CacheIndex::IsCollision() - Collision detected for entry hash=%08x"
+ "%08x%08x%08x%08x, expected values: originAttrsHash=%" PRIu64 ", "
+ "anonymous=%d; actual values: originAttrsHash=%" PRIu64
+ ", anonymous=%d]",
+ LOGSHA1(aEntry->Hash()), aOriginAttrsHash, aAnonymous,
+ aEntry->OriginAttrsHash(), aEntry->Anonymous()));
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool CacheIndex::HasEntryChanged(
+ CacheIndexEntry* aEntry, const uint32_t* aFrecency, const bool* aHasAltData,
+ const uint16_t* aOnStartTime, const uint16_t* aOnStopTime,
+ const uint8_t* aContentType, const uint32_t* aSize) {
+ if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
+ return true;
+ }
+
+ if (aHasAltData && *aHasAltData != aEntry->GetHasAltData()) {
+ return true;
+ }
+
+ if (aOnStartTime && *aOnStartTime != aEntry->GetOnStartTime()) {
+ return true;
+ }
+
+ if (aOnStopTime && *aOnStopTime != aEntry->GetOnStopTime()) {
+ return true;
+ }
+
+ if (aContentType && *aContentType != aEntry->GetContentType()) {
+ return true;
+ }
+
+ if (aSize &&
+ (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) {
+ return true;
+ }
+
+ return false;
+}
+
+void CacheIndex::ProcessPendingOperations(
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::ProcessPendingOperations()"));
+
+ for (auto iter = mPendingUpdates.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexEntryUpdate* update = iter.Get();
+
+ LOG(("CacheIndex::ProcessPendingOperations() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(update->Hash())));
+
+ MOZ_ASSERT(update->IsFresh());
+
+ CacheIndexEntry* entry = mIndex.GetEntry(*update->Hash());
+ {
+ CacheIndexEntryAutoManage emng(update->Hash(), this, aProofOfLock);
+ emng.DoNotSearchInUpdates();
+
+ if (update->IsRemoved()) {
+ if (entry) {
+ if (entry->IsRemoved()) {
+ MOZ_ASSERT(entry->IsFresh());
+ MOZ_ASSERT(entry->IsDirty());
+ } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
+ // Entries with empty file are not stored in index on disk. Just
+ // remove the entry, but only in case the entry is not dirty, i.e.
+ // the entry file was empty when we wrote the index.
+ mIndex.RemoveEntry(entry);
+ entry = nullptr;
+ } else {
+ entry->MarkRemoved();
+ entry->MarkDirty();
+ entry->MarkFresh();
+ }
+ }
+ } else if (entry) {
+ // Some information in mIndex can be newer than in mPendingUpdates (see
+ // bug 1074832). This will copy just those values that were really
+ // updated.
+ update->ApplyUpdate(entry);
+ } else {
+ // There is no entry in mIndex, copy all information from
+ // mPendingUpdates to mIndex.
+ entry = mIndex.PutEntry(*update->Hash());
+ *entry = *update;
+ }
+ }
+ iter.Remove();
+ }
+
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+
+ EnsureCorrectStats();
+}
+
+bool CacheIndex::WriteIndexToDiskIfNeeded(
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ if (mState != READY || mShuttingDown || mRWPending) {
+ return false;
+ }
+
+ if (!mLastDumpTime.IsNull() &&
+ (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() <
+ kMinDumpInterval) {
+ return false;
+ }
+
+ if (mIndexStats.Dirty() < kMinUnwrittenChanges) {
+ return false;
+ }
+
+ WriteIndexToDisk(aProofOfLock);
+ return true;
+}
+
+void CacheIndex::WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::WriteIndexToDisk()"));
+ mIndexStats.Log();
+
+ nsresult rv;
+
+ MOZ_ASSERT(mState == READY);
+ MOZ_ASSERT(!mRWBuf);
+ MOZ_ASSERT(!mRWHash);
+ MOZ_ASSERT(!mRWPending);
+
+ ChangeState(WRITING, aProofOfLock);
+
+ mProcessEntries = mIndexStats.ActiveEntriesCount();
+
+ mIndexFileOpener = new FileOpenHelper(this);
+ rv = CacheFileIOManager::OpenFile(
+ nsLiteralCString(TEMP_INDEX_NAME),
+ CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::CREATE,
+ mIndexFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08" PRIx32
+ "]",
+ static_cast<uint32_t>(rv)));
+ FinishWrite(false, aProofOfLock);
+ return;
+ }
+
+ // Write index header to a buffer, it will be written to disk together with
+ // records in WriteRecords() once we open the file successfully.
+ AllocBuffer();
+ mRWHash = new CacheHash();
+
+ mRWBufPos = 0;
+ // index version
+ NetworkEndian::writeUint32(mRWBuf + mRWBufPos, kIndexVersion);
+ mRWBufPos += sizeof(uint32_t);
+ // timestamp
+ NetworkEndian::writeUint32(mRWBuf + mRWBufPos,
+ static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
+ mRWBufPos += sizeof(uint32_t);
+ // dirty flag
+ NetworkEndian::writeUint32(mRWBuf + mRWBufPos, 1);
+ mRWBufPos += sizeof(uint32_t);
+ // amount of data written to the cache
+ NetworkEndian::writeUint32(mRWBuf + mRWBufPos,
+ static_cast<uint32_t>(mTotalBytesWritten >> 10));
+ mRWBufPos += sizeof(uint32_t);
+
+ mSkipEntries = 0;
+}
+
+void CacheIndex::WriteRecords(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::WriteRecords()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mState == WRITING);
+ MOZ_ASSERT(!mRWPending);
+
+ int64_t fileOffset;
+
+ if (mSkipEntries) {
+ MOZ_ASSERT(mRWBufPos == 0);
+ fileOffset = sizeof(CacheIndexHeader);
+ fileOffset += sizeof(CacheIndexRecord) * mSkipEntries;
+ } else {
+ MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader));
+ fileOffset = 0;
+ }
+ uint32_t hashOffset = mRWBufPos;
+
+ char* buf = mRWBuf + mRWBufPos;
+ uint32_t skip = mSkipEntries;
+ uint32_t processMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
+ MOZ_ASSERT(processMax != 0 ||
+ mProcessEntries ==
+ 0); // TODO make sure we can write an empty index
+ uint32_t processed = 0;
+#ifdef DEBUG
+ bool hasMore = false;
+#endif
+ for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexEntry* entry = iter.Get();
+ if (entry->IsRemoved() || !entry->IsInitialized() || entry->IsFileEmpty()) {
+ continue;
+ }
+
+ if (skip) {
+ skip--;
+ continue;
+ }
+
+ if (processed == processMax) {
+#ifdef DEBUG
+ hasMore = true;
+#endif
+ break;
+ }
+
+ entry->WriteToBuf(buf);
+ buf += sizeof(CacheIndexRecord);
+ processed++;
+ }
+
+ MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(buf - mRWBuf) ||
+ mProcessEntries == 0);
+ mRWBufPos = buf - mRWBuf;
+ mSkipEntries += processed;
+ MOZ_ASSERT(mSkipEntries <= mProcessEntries);
+
+ mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset);
+
+ if (mSkipEntries == mProcessEntries) {
+ MOZ_ASSERT(!hasMore);
+
+ // We've processed all records
+ if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) {
+ // realloc buffer to spare another write cycle
+ mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t);
+ mRWBuf = static_cast<char*>(moz_xrealloc(mRWBuf, mRWBufSize));
+ }
+
+ NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash());
+ mRWBufPos += sizeof(CacheHash::Hash32_t);
+ } else {
+ MOZ_ASSERT(hasMore);
+ }
+
+ rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos,
+ mSkipEntries == mProcessEntries, false, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
+ "synchronously [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ FinishWrite(false, aProofOfLock);
+ } else {
+ mRWPending = true;
+ }
+
+ mRWBufPos = 0;
+}
+
+void CacheIndex::FinishWrite(bool aSucceeded,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
+
+ MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
+
+ // If there is write operation pending we must be cancelling writing of the
+ // index when shutting down or removing the whole index.
+ MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
+
+ mIndexHandle = nullptr;
+ mRWHash = nullptr;
+ ReleaseBuffer();
+
+ if (aSucceeded) {
+ // Opening of the file must not be in progress if writing succeeded.
+ MOZ_ASSERT(!mIndexFileOpener);
+
+ for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexEntry* entry = iter.Get();
+
+ bool remove = false;
+ {
+ CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
+
+ if (entry->IsRemoved()) {
+ emng.DoNotSearchInIndex();
+ remove = true;
+ } else if (entry->IsDirty()) {
+ entry->ClearDirty();
+ }
+ }
+ if (remove) {
+ iter.Remove();
+ }
+ }
+
+ mIndexOnDiskIsValid = true;
+ } else {
+ if (mIndexFileOpener) {
+ // If opening of the file is still in progress (e.g. WRITE process was
+ // canceled by RemoveAll()) then we need to cancel the opener to make sure
+ // that OnFileOpenedInternal() won't be called.
+ mIndexFileOpener->Cancel();
+ mIndexFileOpener = nullptr;
+ }
+ }
+
+ ProcessPendingOperations(aProofOfLock);
+ mIndexStats.Log();
+
+ if (mState == WRITING) {
+ ChangeState(READY, aProofOfLock);
+ mLastDumpTime = TimeStamp::NowLoRes();
+ }
+}
+
+nsresult CacheIndex::GetFile(const nsACString& aName, nsIFile** _retval) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+void CacheIndex::RemoveFile(const nsACString& aName) {
+ MOZ_ASSERT(mState == SHUTDOWN);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(aName, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = file->Remove(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+ LOG(
+ ("CacheIndex::RemoveFile() - Cannot remove old entry file from disk "
+ "[rv=0x%08" PRIx32 ", name=%s]",
+ static_cast<uint32_t>(rv), PromiseFlatCString(aName).get()));
+ }
+}
+
+void CacheIndex::RemoveAllIndexFiles() {
+ LOG(("CacheIndex::RemoveAllIndexFiles()"));
+ RemoveFile(nsLiteralCString(INDEX_NAME));
+ RemoveJournalAndTempFile();
+}
+
+void CacheIndex::RemoveJournalAndTempFile() {
+ LOG(("CacheIndex::RemoveJournalAndTempFile()"));
+ RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
+ RemoveFile(nsLiteralCString(JOURNAL_NAME));
+}
+
+class WriteLogHelper {
+ public:
+ explicit WriteLogHelper(PRFileDesc* aFD)
+ : mFD(aFD), mBufSize(kMaxBufSize), mBufPos(0) {
+ mHash = new CacheHash();
+ mBuf = static_cast<char*>(moz_xmalloc(mBufSize));
+ }
+
+ ~WriteLogHelper() { free(mBuf); }
+
+ nsresult AddEntry(CacheIndexEntry* aEntry);
+ nsresult Finish();
+
+ private:
+ nsresult FlushBuffer();
+
+ PRFileDesc* mFD;
+ char* mBuf;
+ uint32_t mBufSize;
+ int32_t mBufPos;
+ RefPtr<CacheHash> mHash;
+};
+
+nsresult WriteLogHelper::AddEntry(CacheIndexEntry* aEntry) {
+ nsresult rv;
+
+ if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) {
+ mHash->Update(mBuf, mBufPos);
+
+ rv = FlushBuffer();
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize);
+ }
+
+ aEntry->WriteToBuf(mBuf + mBufPos);
+ mBufPos += sizeof(CacheIndexRecord);
+
+ return NS_OK;
+}
+
+nsresult WriteLogHelper::Finish() {
+ nsresult rv;
+
+ mHash->Update(mBuf, mBufPos);
+ if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) {
+ rv = FlushBuffer();
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize);
+ }
+
+ NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash());
+ mBufPos += sizeof(CacheHash::Hash32_t);
+
+ rv = FlushBuffer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult WriteLogHelper::FlushBuffer() {
+ if (CacheObserver::IsPastShutdownIOLag()) {
+ LOG(("WriteLogHelper::FlushBuffer() - Interrupting writing journal."));
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos);
+
+ if (bytesWritten != mBufPos) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBufPos = 0;
+ return NS_OK;
+}
+
+nsresult CacheIndex::WriteLogToDisk() {
+ LOG(("CacheIndex::WriteLogToDisk()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+ MOZ_ASSERT(mState == SHUTDOWN);
+
+ if (CacheObserver::IsPastShutdownIOLag()) {
+ LOG(("CacheIndex::WriteLogToDisk() - Skipping writing journal."));
+ return NS_ERROR_FAILURE;
+ }
+
+ RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
+
+ nsCOMPtr<nsIFile> indexFile;
+ rv = GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(indexFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> logFile;
+ rv = GetFile(nsLiteralCString(JOURNAL_NAME), getter_AddRefs(logFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIndexStats.Log();
+
+ PRFileDesc* fd = nullptr;
+ rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600,
+ &fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ WriteLogHelper wlh(fd);
+ for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexEntry* entry = iter.Get();
+ if (entry->IsRemoved() || entry->IsDirty()) {
+ rv = wlh.AddEntry(entry);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ rv = wlh.Finish();
+ PR_Close(fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Seek to dirty flag in the index header and clear it.
+ static_assert(2 * sizeof(uint32_t) == offsetof(CacheIndexHeader, mIsDirty),
+ "Unexpected offset of CacheIndexHeader::mIsDirty");
+ int64_t offset = PR_Seek64(fd, 2 * sizeof(uint32_t), PR_SEEK_SET);
+ if (offset == -1) {
+ PR_Close(fd);
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t isDirty = 0;
+ int32_t bytesWritten = PR_Write(fd, &isDirty, sizeof(isDirty));
+ PR_Close(fd);
+ if (bytesWritten != sizeof(isDirty)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void CacheIndex::ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::ReadIndexFromDisk()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mState == INITIAL);
+
+ ChangeState(READING, aProofOfLock);
+
+ mIndexFileOpener = new FileOpenHelper(this);
+ rv = CacheFileIOManager::OpenFile(
+ nsLiteralCString(INDEX_NAME),
+ CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
+ mIndexFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
+ "failed [rv=0x%08" PRIx32 ", file=%s]",
+ static_cast<uint32_t>(rv), INDEX_NAME));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+
+ mJournalFileOpener = new FileOpenHelper(this);
+ rv = CacheFileIOManager::OpenFile(
+ nsLiteralCString(JOURNAL_NAME),
+ CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
+ mJournalFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
+ "failed [rv=0x%08" PRIx32 ", file=%s]",
+ static_cast<uint32_t>(rv), JOURNAL_NAME));
+ FinishRead(false, aProofOfLock);
+ }
+
+ mTmpFileOpener = new FileOpenHelper(this);
+ rv = CacheFileIOManager::OpenFile(
+ nsLiteralCString(TEMP_INDEX_NAME),
+ CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
+ mTmpFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
+ "failed [rv=0x%08" PRIx32 ", file=%s]",
+ static_cast<uint32_t>(rv), TEMP_INDEX_NAME));
+ FinishRead(false, aProofOfLock);
+ }
+}
+
+void CacheIndex::StartReadingIndex(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::StartReadingIndex()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIndexHandle);
+ MOZ_ASSERT(mState == READING);
+ MOZ_ASSERT(!mIndexOnDiskIsValid);
+ MOZ_ASSERT(!mDontMarkIndexClean);
+ MOZ_ASSERT(!mJournalReadSuccessfully);
+ MOZ_ASSERT(mIndexHandle->FileSize() >= 0);
+ MOZ_ASSERT(!mRWPending);
+
+ int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
+ sizeof(CacheHash::Hash32_t);
+
+ if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
+ LOG(("CacheIndex::StartReadingIndex() - Index is corrupted"));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+
+ AllocBuffer();
+ mSkipEntries = 0;
+ mRWHash = new CacheHash();
+
+ mRWBufPos =
+ std::min(mRWBufSize, static_cast<uint32_t>(mIndexHandle->FileSize()));
+
+ rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed "
+ "synchronously [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ FinishRead(false, aProofOfLock);
+ } else {
+ mRWPending = true;
+ }
+}
+
+void CacheIndex::ParseRecords(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::ParseRecords()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(!mRWPending);
+
+ uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
+ sizeof(CacheHash::Hash32_t)) /
+ sizeof(CacheIndexRecord);
+ uint32_t pos = 0;
+
+ if (!mSkipEntries) {
+ if (NetworkEndian::readUint32(mRWBuf + pos) != kIndexVersion) {
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+ pos += sizeof(uint32_t);
+
+ mIndexTimeStamp = NetworkEndian::readUint32(mRWBuf + pos);
+ pos += sizeof(uint32_t);
+
+ if (NetworkEndian::readUint32(mRWBuf + pos)) {
+ if (mJournalHandle) {
+ CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
+ mJournalHandle = nullptr;
+ }
+ } else {
+ uint32_t* isDirty =
+ reinterpret_cast<uint32_t*>(moz_xmalloc(sizeof(uint32_t)));
+ NetworkEndian::writeUint32(isDirty, 1);
+
+ // Mark index dirty. The buffer is freed by CacheFileIOManager when
+ // nullptr is passed as the listener and the call doesn't fail
+ // synchronously.
+ rv = CacheFileIOManager::Write(mIndexHandle, 2 * sizeof(uint32_t),
+ reinterpret_cast<char*>(isDirty),
+ sizeof(uint32_t), true, false, nullptr);
+ if (NS_FAILED(rv)) {
+ // This is not fatal, just free the memory
+ free(isDirty);
+ }
+ }
+ pos += sizeof(uint32_t);
+
+ uint64_t dataWritten = NetworkEndian::readUint32(mRWBuf + pos);
+ pos += sizeof(uint32_t);
+ dataWritten <<= 10;
+ mTotalBytesWritten += dataWritten;
+ }
+
+ uint32_t hashOffset = pos;
+
+ while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
+ mSkipEntries != entryCnt) {
+ CacheIndexRecord* rec = reinterpret_cast<CacheIndexRecord*>(mRWBuf + pos);
+ CacheIndexEntry tmpEntry(&rec->mHash);
+ tmpEntry.ReadFromBuf(mRWBuf + pos);
+
+ if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() ||
+ tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) {
+ LOG(
+ ("CacheIndex::ParseRecords() - Invalid entry found in index, removing"
+ " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, "
+ "removed=%d]",
+ tmpEntry.IsDirty(), tmpEntry.IsInitialized(), tmpEntry.IsFileEmpty(),
+ tmpEntry.IsFresh(), tmpEntry.IsRemoved()));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+
+ CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this, aProofOfLock);
+
+ CacheIndexEntry* entry = mIndex.PutEntry(*tmpEntry.Hash());
+ *entry = tmpEntry;
+
+ pos += sizeof(CacheIndexRecord);
+ mSkipEntries++;
+ }
+
+ mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset);
+
+ if (pos != mRWBufPos) {
+ memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
+ }
+
+ mRWBufPos -= pos;
+ pos = 0;
+
+ int64_t fileOffset = sizeof(CacheIndexHeader) +
+ mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
+
+ MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize());
+ if (fileOffset == mIndexHandle->FileSize()) {
+ uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf);
+ if (mRWHash->GetHash() != expectedHash) {
+ LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]",
+ mRWHash->GetHash(), expectedHash));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+
+ mIndexOnDiskIsValid = true;
+ mJournalReadSuccessfully = false;
+
+ if (mJournalHandle) {
+ StartReadingJournal(aProofOfLock);
+ } else {
+ FinishRead(false, aProofOfLock);
+ }
+
+ return;
+ }
+
+ pos = mRWBufPos;
+ uint32_t toRead =
+ std::min(mRWBufSize - pos,
+ static_cast<uint32_t>(mIndexHandle->FileSize() - fileOffset));
+ mRWBufPos = pos + toRead;
+
+ rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead,
+ this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed "
+ "synchronously [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+ mRWPending = true;
+}
+
+void CacheIndex::StartReadingJournal(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::StartReadingJournal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mJournalHandle);
+ MOZ_ASSERT(mIndexOnDiskIsValid);
+ MOZ_ASSERT(mTmpJournal.Count() == 0);
+ MOZ_ASSERT(mJournalHandle->FileSize() >= 0);
+ MOZ_ASSERT(!mRWPending);
+
+ int64_t entriesSize =
+ mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t);
+
+ if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
+ LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted"));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+
+ mSkipEntries = 0;
+ mRWHash = new CacheHash();
+
+ mRWBufPos =
+ std::min(mRWBufSize, static_cast<uint32_t>(mJournalHandle->FileSize()));
+
+ rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed"
+ " synchronously [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ FinishRead(false, aProofOfLock);
+ } else {
+ mRWPending = true;
+ }
+}
+
+void CacheIndex::ParseJournal(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::ParseJournal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(!mRWPending);
+
+ uint32_t entryCnt =
+ (mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t)) /
+ sizeof(CacheIndexRecord);
+
+ uint32_t pos = 0;
+
+ while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
+ mSkipEntries != entryCnt) {
+ CacheIndexEntry tmpEntry(reinterpret_cast<SHA1Sum::Hash*>(mRWBuf + pos));
+ tmpEntry.ReadFromBuf(mRWBuf + pos);
+
+ CacheIndexEntry* entry = mTmpJournal.PutEntry(*tmpEntry.Hash());
+ *entry = tmpEntry;
+
+ if (entry->IsDirty() || entry->IsFresh()) {
+ LOG(
+ ("CacheIndex::ParseJournal() - Invalid entry found in journal, "
+ "ignoring whole journal [dirty=%d, fresh=%d]",
+ entry->IsDirty(), entry->IsFresh()));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+
+ pos += sizeof(CacheIndexRecord);
+ mSkipEntries++;
+ }
+
+ mRWHash->Update(mRWBuf, pos);
+
+ if (pos != mRWBufPos) {
+ memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
+ }
+
+ mRWBufPos -= pos;
+ pos = 0;
+
+ int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
+
+ MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize());
+ if (fileOffset == mJournalHandle->FileSize()) {
+ uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf);
+ if (mRWHash->GetHash() != expectedHash) {
+ LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]",
+ mRWHash->GetHash(), expectedHash));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+
+ mJournalReadSuccessfully = true;
+ FinishRead(true, aProofOfLock);
+ return;
+ }
+
+ pos = mRWBufPos;
+ uint32_t toRead =
+ std::min(mRWBufSize - pos,
+ static_cast<uint32_t>(mJournalHandle->FileSize() - fileOffset));
+ mRWBufPos = pos + toRead;
+
+ rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos,
+ toRead, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed "
+ "synchronously [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ FinishRead(false, aProofOfLock);
+ return;
+ }
+ mRWPending = true;
+}
+
+void CacheIndex::MergeJournal(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::MergeJournal()"));
+
+ for (auto iter = mTmpJournal.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexEntry* entry = iter.Get();
+
+ LOG(("CacheIndex::MergeJournal() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(entry->Hash())));
+
+ CacheIndexEntry* entry2 = mIndex.GetEntry(*entry->Hash());
+ {
+ CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
+ if (entry->IsRemoved()) {
+ if (entry2) {
+ entry2->MarkRemoved();
+ entry2->MarkDirty();
+ }
+ } else {
+ if (!entry2) {
+ entry2 = mIndex.PutEntry(*entry->Hash());
+ }
+
+ *entry2 = *entry;
+ entry2->MarkDirty();
+ }
+ }
+ iter.Remove();
+ }
+
+ MOZ_ASSERT(mTmpJournal.Count() == 0);
+}
+
+void CacheIndex::EnsureNoFreshEntry() {
+#ifdef DEBUG_STATS
+ CacheIndexStats debugStats;
+ debugStats.DisableLogging();
+ for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
+ debugStats.BeforeChange(nullptr);
+ debugStats.AfterChange(iter.Get());
+ }
+ MOZ_ASSERT(debugStats.Fresh() == 0);
+#endif
+}
+
+void CacheIndex::EnsureCorrectStats() {
+#ifdef DEBUG_STATS
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+ CacheIndexStats debugStats;
+ debugStats.DisableLogging();
+ for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
+ debugStats.BeforeChange(nullptr);
+ debugStats.AfterChange(iter.Get());
+ }
+ MOZ_ASSERT(debugStats == mIndexStats);
+#endif
+}
+
+void CacheIndex::FinishRead(bool aSucceeded,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
+
+ MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
+
+ MOZ_ASSERT(
+ // -> rebuild
+ (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
+ // -> update
+ (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
+ // -> ready
+ (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully));
+
+ // If there is read operation pending we must be cancelling reading of the
+ // index when shutting down or removing the whole index.
+ MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
+
+ if (mState == SHUTDOWN) {
+ RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
+ RemoveFile(nsLiteralCString(JOURNAL_NAME));
+ } else {
+ if (mIndexHandle && !mIndexOnDiskIsValid) {
+ CacheFileIOManager::DoomFile(mIndexHandle, nullptr);
+ }
+
+ if (mJournalHandle) {
+ CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
+ }
+ }
+
+ if (mIndexFileOpener) {
+ mIndexFileOpener->Cancel();
+ mIndexFileOpener = nullptr;
+ }
+ if (mJournalFileOpener) {
+ mJournalFileOpener->Cancel();
+ mJournalFileOpener = nullptr;
+ }
+ if (mTmpFileOpener) {
+ mTmpFileOpener->Cancel();
+ mTmpFileOpener = nullptr;
+ }
+
+ mIndexHandle = nullptr;
+ mJournalHandle = nullptr;
+ mRWHash = nullptr;
+ ReleaseBuffer();
+
+ if (mState == SHUTDOWN) {
+ return;
+ }
+
+ if (!mIndexOnDiskIsValid) {
+ MOZ_ASSERT(mTmpJournal.Count() == 0);
+ EnsureNoFreshEntry();
+ ProcessPendingOperations(aProofOfLock);
+ // Remove all entries that we haven't seen during this session
+ RemoveNonFreshEntries(aProofOfLock);
+ StartUpdatingIndex(true, aProofOfLock);
+ return;
+ }
+
+ if (!mJournalReadSuccessfully) {
+ mTmpJournal.Clear();
+ EnsureNoFreshEntry();
+ ProcessPendingOperations(aProofOfLock);
+ StartUpdatingIndex(false, aProofOfLock);
+ return;
+ }
+
+ MergeJournal(aProofOfLock);
+ EnsureNoFreshEntry();
+ ProcessPendingOperations(aProofOfLock);
+ mIndexStats.Log();
+
+ ChangeState(READY, aProofOfLock);
+ mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
+}
+
+// static
+void CacheIndex::DelayedUpdate(nsITimer* aTimer, void* aClosure) {
+ LOG(("CacheIndex::DelayedUpdate()"));
+
+ StaticMutexAutoLock lock(sLock);
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return;
+ }
+
+ index->DelayedUpdateLocked(lock);
+}
+
+// static
+void CacheIndex::DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::DelayedUpdateLocked()"));
+
+ nsresult rv;
+
+ mUpdateTimer = nullptr;
+
+ if (!IsIndexUsable()) {
+ return;
+ }
+
+ if (mState == READY && mShuttingDown) {
+ return;
+ }
+
+ // mUpdateEventPending must be false here since StartUpdatingIndex() won't
+ // schedule timer if it is true.
+ MOZ_ASSERT(!mUpdateEventPending);
+ if (mState != BUILDING && mState != UPDATING) {
+ LOG(("CacheIndex::DelayedUpdateLocked() - Update was canceled"));
+ return;
+ }
+
+ // We need to redispatch to run with lower priority
+ RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
+ MOZ_ASSERT(ioThread);
+
+ mUpdateEventPending = true;
+ rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
+ if (NS_FAILED(rv)) {
+ mUpdateEventPending = false;
+ NS_WARNING("CacheIndex::DelayedUpdateLocked() - Can't dispatch event");
+ LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event"));
+ FinishUpdate(false, aProofOfLock);
+ }
+}
+
+nsresult CacheIndex::ScheduleUpdateTimer(uint32_t aDelay) {
+ LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay));
+
+ MOZ_ASSERT(!mUpdateTimer);
+
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mUpdateTimer), CacheIndex::DelayedUpdate, nullptr, aDelay,
+ nsITimer::TYPE_ONE_SHOT, "net::CacheIndex::ScheduleUpdateTimer",
+ ioTarget);
+}
+
+nsresult CacheIndex::SetupDirectoryEnumerator() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!mDirEnumerator);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ NS_WARNING(
+ "CacheIndex::SetupDirectoryEnumerator() - Entries directory "
+ "doesn't exist!");
+ LOG(
+ ("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't "
+ "exist!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Do not do IO under the lock.
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator;
+ {
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = file->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ }
+ mDirEnumerator = dirEnumerator.forget();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheIndex::InitEntryFromDiskData(CacheIndexEntry* aEntry,
+ CacheFileMetadata* aMetaData,
+ int64_t aFileSize) {
+ nsresult rv;
+
+ aEntry->InitNew();
+ aEntry->MarkDirty();
+ aEntry->MarkFresh();
+
+ aEntry->Init(GetOriginAttrsHash(aMetaData->OriginAttributes()),
+ aMetaData->IsAnonymous(), aMetaData->Pinned());
+
+ aEntry->SetFrecency(aMetaData->GetFrecency());
+
+ const char* altData = aMetaData->GetElement(CacheFileUtils::kAltDataKey);
+ bool hasAltData = altData != nullptr;
+ if (hasAltData && NS_FAILED(CacheFileUtils::ParseAlternativeDataInfo(
+ altData, nullptr, nullptr))) {
+ return NS_ERROR_FAILURE;
+ }
+ aEntry->SetHasAltData(hasAltData);
+
+ static auto toUint16 = [](const char* aUint16String) -> uint16_t {
+ if (!aUint16String) {
+ return kIndexTimeNotAvailable;
+ }
+ nsresult rv;
+ uint64_t n64 = nsDependentCString(aUint16String).ToInteger64(&rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return n64 <= kIndexTimeOutOfBound ? n64 : kIndexTimeOutOfBound;
+ };
+
+ aEntry->SetOnStartTime(
+ toUint16(aMetaData->GetElement("net-response-time-onstart")));
+ aEntry->SetOnStopTime(
+ toUint16(aMetaData->GetElement("net-response-time-onstop")));
+
+ const char* contentTypeStr = aMetaData->GetElement("ctid");
+ uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ if (contentTypeStr) {
+ int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv);
+ if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN ||
+ n64 >= nsICacheEntry::CONTENT_TYPE_LAST) {
+ n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ }
+ contentType = n64;
+ }
+ aEntry->SetContentType(contentType);
+
+ aEntry->SetFileSize(static_cast<uint32_t>(std::min(
+ static_cast<int64_t>(PR_UINT32_MAX), (aFileSize + 0x3FF) >> 10)));
+ return NS_OK;
+}
+
+bool CacheIndex::IsUpdatePending() {
+ sLock.AssertCurrentThreadOwns();
+
+ return mUpdateTimer || mUpdateEventPending;
+}
+
+void CacheIndex::BuildIndex(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::BuildIndex()"));
+
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+
+ nsresult rv;
+
+ if (!mDirEnumerator) {
+ rv = SetupDirectoryEnumerator();
+ if (mState == SHUTDOWN) {
+ // The index was shut down while we released the lock. FinishUpdate() was
+ // already called from Shutdown(), so just simply return here.
+ return;
+ }
+
+ if (NS_FAILED(rv)) {
+ FinishUpdate(false, aProofOfLock);
+ return;
+ }
+ }
+
+ while (true) {
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG((
+ "CacheIndex::BuildIndex() - Breaking loop for higher level events."));
+ mUpdateEventPending = true;
+ return;
+ }
+
+ bool fileExists = false;
+ nsCOMPtr<nsIFile> file;
+ {
+ // Do not do IO under the lock.
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator);
+ sLock.AssertCurrentThreadOwns();
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = dirEnumerator->GetNextFile(getter_AddRefs(file));
+
+ if (file) {
+ file->Exists(&fileExists);
+ }
+ }
+ if (mState == SHUTDOWN) {
+ return;
+ }
+ if (!file) {
+ FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
+ return;
+ }
+
+ nsAutoCString leaf;
+ rv = file->GetNativeLeafName(leaf);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping "
+ "file."));
+ mDontMarkIndexClean = true;
+ continue;
+ }
+
+ if (!fileExists) {
+ LOG(
+ ("CacheIndex::BuildIndex() - File returned by the iterator was "
+ "removed in the meantime [name=%s]",
+ leaf.get()));
+ continue;
+ }
+
+ SHA1Sum::Hash hash;
+ rv = CacheFileIOManager::StrToHash(leaf, &hash);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::BuildIndex() - Filename is not a hash, removing file. "
+ "[name=%s]",
+ leaf.get()));
+ file->Remove(false);
+ continue;
+ }
+
+ CacheIndexEntry* entry = mIndex.GetEntry(hash);
+ if (entry && entry->IsRemoved()) {
+ LOG(
+ ("CacheIndex::BuildIndex() - Found file that should not exist. "
+ "[name=%s]",
+ leaf.get()));
+ entry->Log();
+ MOZ_ASSERT(entry->IsFresh());
+ entry = nullptr;
+ }
+
+#ifdef DEBUG
+ RefPtr<CacheFileHandle> handle;
+ CacheFileIOManager::gInstance->mHandles.GetHandle(&hash,
+ getter_AddRefs(handle));
+#endif
+
+ if (entry) {
+ // the entry is up to date
+ LOG(
+ ("CacheIndex::BuildIndex() - Skipping file because the entry is up to"
+ " date. [name=%s]",
+ leaf.get()));
+ entry->Log();
+ MOZ_ASSERT(entry->IsFresh()); // The entry must be from this session
+ // there must be an active CacheFile if the entry is not initialized
+ MOZ_ASSERT(entry->IsInitialized() || handle);
+ continue;
+ }
+
+ MOZ_ASSERT(!handle);
+
+ RefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
+ int64_t size = 0;
+
+ {
+ // Do not do IO under the lock.
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = meta->SyncReadMetadata(file);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->GetFileSize(&size);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::BuildIndex() - Cannot get filesize of file that was"
+ " successfully parsed. [name=%s]",
+ leaf.get()));
+ }
+ }
+ }
+ if (mState == SHUTDOWN) {
+ return;
+ }
+
+ // Nobody could add the entry while the lock was released since we modify
+ // the index only on IO thread and this loop is executed on IO thread too.
+ entry = mIndex.GetEntry(hash);
+ MOZ_ASSERT(!entry || entry->IsRemoved());
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() "
+ "failed, removing file. [name=%s]",
+ leaf.get()));
+ file->Remove(false);
+ } else {
+ CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
+ entry = mIndex.PutEntry(hash);
+ if (NS_FAILED(InitEntryFromDiskData(entry, meta, size))) {
+ LOG(
+ ("CacheIndex::BuildIndex() - CacheFile::InitEntryFromDiskData() "
+ "failed, removing file. [name=%s]",
+ leaf.get()));
+ file->Remove(false);
+ entry->MarkRemoved();
+ } else {
+ LOG(("CacheIndex::BuildIndex() - Added entry to index. [name=%s]",
+ leaf.get()));
+ entry->Log();
+ }
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+}
+
+bool CacheIndex::StartUpdatingIndexIfNeeded(
+ const StaticMutexAutoLock& aProofOfLock, bool aSwitchingToReadyState) {
+ sLock.AssertCurrentThreadOwns();
+ // Start updating process when we are in or we are switching to READY state
+ // and index needs update, but not during shutdown or when removing all
+ // entries.
+ if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate &&
+ !mShuttingDown && !mRemovingAll) {
+ LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process"));
+ mIndexNeedsUpdate = false;
+ StartUpdatingIndex(false, aProofOfLock);
+ return true;
+ }
+
+ return false;
+}
+
+void CacheIndex::StartUpdatingIndex(bool aRebuild,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
+
+ nsresult rv;
+
+ mIndexStats.Log();
+
+ ChangeState(aRebuild ? BUILDING : UPDATING, aProofOfLock);
+ mDontMarkIndexClean = false;
+
+ if (mShuttingDown || mRemovingAll) {
+ FinishUpdate(false, aProofOfLock);
+ return;
+ }
+
+ if (IsUpdatePending()) {
+ LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending"));
+ return;
+ }
+
+ uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
+ if (elapsed < kUpdateIndexStartDelay) {
+ LOG(
+ ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
+ "scheduling timer to fire in %u ms.",
+ elapsed, kUpdateIndexStartDelay - elapsed));
+ rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed);
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+
+ LOG(
+ ("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. "
+ "Starting update immediately."));
+ } else {
+ LOG(
+ ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
+ "starting update now.",
+ elapsed));
+ }
+
+ RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
+ MOZ_ASSERT(ioThread);
+
+ // We need to dispatch an event even if we are on IO thread since we need to
+ // update the index with the correct priority.
+ mUpdateEventPending = true;
+ rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
+ if (NS_FAILED(rv)) {
+ mUpdateEventPending = false;
+ NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event");
+ LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event"));
+ FinishUpdate(false, aProofOfLock);
+ }
+}
+
+void CacheIndex::UpdateIndex(const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::UpdateIndex()"));
+
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+ sLock.AssertCurrentThreadOwns();
+
+ nsresult rv;
+
+ if (!mDirEnumerator) {
+ rv = SetupDirectoryEnumerator();
+ if (mState == SHUTDOWN) {
+ // The index was shut down while we released the lock. FinishUpdate() was
+ // already called from Shutdown(), so just simply return here.
+ return;
+ }
+
+ if (NS_FAILED(rv)) {
+ FinishUpdate(false, aProofOfLock);
+ return;
+ }
+ }
+
+ while (true) {
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - Breaking loop for higher level "
+ "events."));
+ mUpdateEventPending = true;
+ return;
+ }
+
+ bool fileExists = false;
+ nsCOMPtr<nsIFile> file;
+ {
+ // Do not do IO under the lock.
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator);
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = dirEnumerator->GetNextFile(getter_AddRefs(file));
+
+ if (file) {
+ file->Exists(&fileExists);
+ }
+ }
+ if (mState == SHUTDOWN) {
+ return;
+ }
+ if (!file) {
+ FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
+ return;
+ }
+
+ nsAutoCString leaf;
+ rv = file->GetNativeLeafName(leaf);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping "
+ "file."));
+ mDontMarkIndexClean = true;
+ continue;
+ }
+
+ if (!fileExists) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - File returned by the iterator was "
+ "removed in the meantime [name=%s]",
+ leaf.get()));
+ continue;
+ }
+
+ SHA1Sum::Hash hash;
+ rv = CacheFileIOManager::StrToHash(leaf, &hash);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. "
+ "[name=%s]",
+ leaf.get()));
+ file->Remove(false);
+ continue;
+ }
+
+ CacheIndexEntry* entry = mIndex.GetEntry(hash);
+ if (entry && entry->IsRemoved()) {
+ if (entry->IsFresh()) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - Found file that should not exist. "
+ "[name=%s]",
+ leaf.get()));
+ entry->Log();
+ }
+ entry = nullptr;
+ }
+
+#ifdef DEBUG
+ RefPtr<CacheFileHandle> handle;
+ CacheFileIOManager::gInstance->mHandles.GetHandle(&hash,
+ getter_AddRefs(handle));
+#endif
+
+ if (entry && entry->IsFresh()) {
+ // the entry is up to date
+ LOG(
+ ("CacheIndex::UpdateIndex() - Skipping file because the entry is up "
+ " to date. [name=%s]",
+ leaf.get()));
+ entry->Log();
+ // there must be an active CacheFile if the entry is not initialized
+ MOZ_ASSERT(entry->IsInitialized() || handle);
+ continue;
+ }
+
+ MOZ_ASSERT(!handle);
+
+ if (entry) {
+ PRTime lastModifiedTime;
+ {
+ // Do not do IO under the lock.
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = file->GetLastModifiedTime(&lastModifiedTime);
+ }
+ if (mState == SHUTDOWN) {
+ return;
+ }
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. "
+ "[name=%s]",
+ leaf.get()));
+ // Assume the file is newer than index
+ } else {
+ if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - Skipping file because of last "
+ "modified time. [name=%s, indexTimeStamp=%" PRIu32 ", "
+ "lastModifiedTime=%" PRId64 "]",
+ leaf.get(), mIndexTimeStamp,
+ lastModifiedTime / PR_MSEC_PER_SEC));
+
+ CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
+ entry->MarkFresh();
+ continue;
+ }
+ }
+ }
+
+ RefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
+ int64_t size = 0;
+
+ {
+ // Do not do IO under the lock.
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = meta->SyncReadMetadata(file);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->GetFileSize(&size);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - Cannot get filesize of file that "
+ "was successfully parsed. [name=%s]",
+ leaf.get()));
+ }
+ }
+ }
+ if (mState == SHUTDOWN) {
+ return;
+ }
+
+ // Nobody could add the entry while the lock was released since we modify
+ // the index only on IO thread and this loop is executed on IO thread too.
+ entry = mIndex.GetEntry(hash);
+ MOZ_ASSERT(!entry || !entry->IsFresh());
+
+ CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() "
+ "failed, removing file. [name=%s]",
+ leaf.get()));
+ } else {
+ entry = mIndex.PutEntry(hash);
+ rv = InitEntryFromDiskData(entry, meta, size);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::UpdateIndex() - CacheIndex::InitEntryFromDiskData "
+ "failed, removing file. [name=%s]",
+ leaf.get()));
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ file->Remove(false);
+ if (entry) {
+ entry->MarkRemoved();
+ entry->MarkFresh();
+ entry->MarkDirty();
+ }
+ } else {
+ LOG(
+ ("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
+ "[name=%s]",
+ leaf.get()));
+ entry->Log();
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+}
+
+void CacheIndex::FinishUpdate(bool aSucceeded,
+ const StaticMutexAutoLock& aProofOfLock) {
+ LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
+
+ MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
+ (!aSucceeded && mState == SHUTDOWN));
+
+ if (mDirEnumerator) {
+ if (NS_IsMainThread()) {
+ LOG(
+ ("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?"
+ " Cannot safely release mDirEnumerator, leaking it!"));
+ NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!"));
+ // This can happen only in case dispatching event to IO thread failed in
+ // CacheIndex::PreShutdown().
+ Unused << mDirEnumerator.forget(); // Leak it since dir enumerator is not
+ // threadsafe
+ } else {
+ mDirEnumerator->Close();
+ mDirEnumerator = nullptr;
+ }
+ }
+
+ if (!aSucceeded) {
+ mDontMarkIndexClean = true;
+ }
+
+ if (mState == SHUTDOWN) {
+ return;
+ }
+
+ if (mState == UPDATING && aSucceeded) {
+ // If we've iterated over all entries successfully then all entries that
+ // really exist on the disk are now marked as fresh. All non-fresh entries
+ // don't exist anymore and must be removed from the index.
+ RemoveNonFreshEntries(aProofOfLock);
+ }
+
+ // Make sure we won't start update. If the build or update failed, there is no
+ // reason to believe that it will succeed next time.
+ mIndexNeedsUpdate = false;
+
+ ChangeState(READY, aProofOfLock);
+ mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
+}
+
+void CacheIndex::RemoveNonFreshEntries(
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexEntry* entry = iter.Get();
+ if (entry->IsFresh()) {
+ continue;
+ }
+
+ LOG(
+ ("CacheIndex::RemoveNonFreshEntries() - Removing entry. "
+ "[hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(entry->Hash())));
+
+ {
+ CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
+ emng.DoNotSearchInIndex();
+ }
+
+ iter.Remove();
+ }
+}
+
+// static
+char const* CacheIndex::StateString(EState aState) {
+ switch (aState) {
+ case INITIAL:
+ return "INITIAL";
+ case READING:
+ return "READING";
+ case WRITING:
+ return "WRITING";
+ case BUILDING:
+ return "BUILDING";
+ case UPDATING:
+ return "UPDATING";
+ case READY:
+ return "READY";
+ case SHUTDOWN:
+ return "SHUTDOWN";
+ }
+
+ MOZ_ASSERT(false, "Unexpected state!");
+ return "?";
+}
+
+void CacheIndex::ChangeState(EState aNewState,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState),
+ StateString(aNewState)));
+
+ // All pending updates should be processed before changing state
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+
+ // PreShutdownInternal() should change the state to READY from every state. It
+ // may go through different states, but once we are in READY state the only
+ // possible transition is to SHUTDOWN state.
+ MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN);
+
+ // Start updating process when switching to READY state if needed
+ if (aNewState == READY && StartUpdatingIndexIfNeeded(aProofOfLock, true)) {
+ return;
+ }
+
+ // Try to evict entries over limit everytime we're leaving state READING,
+ // BUILDING or UPDATING, but not during shutdown or when removing all
+ // entries.
+ if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN &&
+ (mState == READING || mState == BUILDING || mState == UPDATING)) {
+ CacheFileIOManager::EvictIfOverLimit();
+ }
+
+ mState = aNewState;
+
+ if (mState != SHUTDOWN) {
+ CacheFileIOManager::CacheIndexStateChanged();
+ }
+
+ NotifyAsyncGetDiskConsumptionCallbacks();
+}
+
+void CacheIndex::NotifyAsyncGetDiskConsumptionCallbacks() {
+ if ((mState == READY || mState == WRITING) &&
+ !mAsyncGetDiskConsumptionBlocked && mDiskConsumptionObservers.Length()) {
+ for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
+ DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
+ // Safe to call under the lock. We always post to the main thread.
+ o->OnDiskConsumption(mIndexStats.Size() << 10);
+ }
+
+ mDiskConsumptionObservers.Clear();
+ }
+}
+
+void CacheIndex::AllocBuffer() {
+ switch (mState) {
+ case WRITING:
+ mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) +
+ mProcessEntries * sizeof(CacheIndexRecord);
+ if (mRWBufSize > kMaxBufSize) {
+ mRWBufSize = kMaxBufSize;
+ }
+ break;
+ case READING:
+ mRWBufSize = kMaxBufSize;
+ break;
+ default:
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+
+ mRWBuf = static_cast<char*>(moz_xmalloc(mRWBufSize));
+}
+
+void CacheIndex::ReleaseBuffer() {
+ sLock.AssertCurrentThreadOwns();
+
+ if (!mRWBuf || mRWPending) {
+ return;
+ }
+
+ LOG(("CacheIndex::ReleaseBuffer() releasing buffer"));
+
+ free(mRWBuf);
+ mRWBuf = nullptr;
+ mRWBufSize = 0;
+ mRWBufPos = 0;
+}
+
+void CacheIndex::FrecencyArray::AppendRecord(
+ CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(
+ ("CacheIndex::FrecencyArray::AppendRecord() [record=%p, hash=%08x%08x%08x"
+ "%08x%08x]",
+ aRecord, LOGSHA1(aRecord->Get()->mHash)));
+
+ MOZ_DIAGNOSTIC_ASSERT(!mRecs.Contains(aRecord));
+ mRecs.AppendElement(aRecord);
+
+ // If the new frecency is 0, the element should be at the end of the array,
+ // i.e. this change doesn't affect order of the array
+ if (aRecord->Get()->mFrecency != 0) {
+ ++mUnsortedElements;
+ }
+}
+
+void CacheIndex::FrecencyArray::RemoveRecord(
+ CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::FrecencyArray::RemoveRecord() [record=%p]", aRecord));
+
+ decltype(mRecs)::index_type idx;
+ idx = mRecs.IndexOf(aRecord);
+ MOZ_RELEASE_ASSERT(idx != mRecs.NoIndex);
+ // sanity check to ensure correct record removal
+ MOZ_RELEASE_ASSERT(mRecs[idx] == aRecord);
+ mRecs[idx] = nullptr;
+ ++mRemovedElements;
+
+ // Calling SortIfNeeded ensures that we get rid of removed elements in the
+ // array once we hit the limit.
+ SortIfNeeded(aProofOfLock);
+}
+
+void CacheIndex::FrecencyArray::ReplaceRecord(
+ CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(
+ ("CacheIndex::FrecencyArray::ReplaceRecord() [oldRecord=%p, "
+ "newRecord=%p]",
+ aOldRecord, aNewRecord));
+
+ decltype(mRecs)::index_type idx;
+ idx = mRecs.IndexOf(aOldRecord);
+ MOZ_RELEASE_ASSERT(idx != mRecs.NoIndex);
+ // sanity check to ensure correct record replaced
+ MOZ_RELEASE_ASSERT(mRecs[idx] == aOldRecord);
+ mRecs[idx] = aNewRecord;
+}
+
+void CacheIndex::FrecencyArray::SortIfNeeded(
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ const uint32_t kMaxUnsortedCount = 512;
+ const uint32_t kMaxUnsortedPercent = 10;
+ const uint32_t kMaxRemovedCount = 512;
+
+ uint32_t unsortedLimit = std::min<uint32_t>(
+ kMaxUnsortedCount, Length() * kMaxUnsortedPercent / 100);
+
+ if (mUnsortedElements > unsortedLimit ||
+ mRemovedElements > kMaxRemovedCount) {
+ LOG(
+ ("CacheIndex::FrecencyArray::SortIfNeeded() - Sorting array "
+ "[unsortedElements=%u, unsortedLimit=%u, removedElements=%u, "
+ "maxRemovedCount=%u]",
+ mUnsortedElements, unsortedLimit, mRemovedElements, kMaxRemovedCount));
+
+ mRecs.Sort(FrecencyComparator());
+ mUnsortedElements = 0;
+ if (mRemovedElements) {
+#if defined(EARLY_BETA_OR_EARLIER)
+ // validate only null items are removed
+ for (uint32_t i = Length(); i < mRecs.Length(); ++i) {
+ MOZ_DIAGNOSTIC_ASSERT(!mRecs[i]);
+ }
+#endif
+ // Removed elements are at the end after sorting.
+ mRecs.RemoveElementsAt(Length(), mRemovedElements);
+ mRemovedElements = 0;
+ }
+ }
+}
+
+bool CacheIndex::FrecencyArray::RecordExistedUnlocked(
+ CacheIndexRecordWrapper* aRecord) {
+ return mRecs.Contains(aRecord);
+}
+
+void CacheIndex::AddRecordToIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ for (uint32_t i = 0; i < mIterators.Length(); ++i) {
+ // Add a new record only when iterator is supposed to be updated.
+ if (mIterators[i]->ShouldBeNewAdded()) {
+ mIterators[i]->AddRecord(aRecord, aProofOfLock);
+ }
+ }
+}
+
+void CacheIndex::RemoveRecordFromIterators(
+ CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ for (uint32_t i = 0; i < mIterators.Length(); ++i) {
+ // Remove the record from iterator always, it makes no sence to return
+ // non-existing entries. Also the pointer to the record is no longer valid
+ // once the entry is removed from index.
+ mIterators[i]->RemoveRecord(aRecord, aProofOfLock);
+ }
+}
+
+void CacheIndex::ReplaceRecordInIterators(
+ CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ for (uint32_t i = 0; i < mIterators.Length(); ++i) {
+ // We have to replace the record always since the pointer is no longer
+ // valid after this point. NOTE: Replacing the record doesn't mean that
+ // a new entry was added, it just means that the data in the entry was
+ // changed (e.g. a file size) and we had to track this change in
+ // mPendingUpdates since mIndex was read-only.
+ mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord, aProofOfLock);
+ }
+}
+
+nsresult CacheIndex::Run() {
+ LOG(("CacheIndex::Run()"));
+
+ StaticMutexAutoLock lock(sLock);
+
+ if (!IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mState == READY && mShuttingDown) {
+ return NS_OK;
+ }
+
+ mUpdateEventPending = false;
+
+ switch (mState) {
+ case BUILDING:
+ BuildIndex(lock);
+ break;
+ case UPDATING:
+ UpdateIndex(lock);
+ break;
+ default:
+ LOG(("CacheIndex::Run() - Update/Build was canceled"));
+ }
+
+ return NS_OK;
+}
+
+void CacheIndex::OnFileOpenedInternal(FileOpenHelper* aOpener,
+ CacheFileHandle* aHandle,
+ nsresult aResult,
+ const StaticMutexAutoLock& aProofOfLock) {
+ sLock.AssertCurrentThreadOwns();
+ LOG(
+ ("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
+ "result=0x%08" PRIx32 "]",
+ aOpener, aHandle, static_cast<uint32_t>(aResult)));
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ MOZ_RELEASE_ASSERT(IsIndexUsable());
+
+ if (mState == READY && mShuttingDown) {
+ return;
+ }
+
+ switch (mState) {
+ case WRITING:
+ MOZ_ASSERT(aOpener == mIndexFileOpener);
+ mIndexFileOpener = nullptr;
+
+ if (NS_FAILED(aResult)) {
+ LOG(
+ ("CacheIndex::OnFileOpenedInternal() - Can't open index file for "
+ "writing [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(aResult)));
+ FinishWrite(false, aProofOfLock);
+ } else {
+ mIndexHandle = aHandle;
+ WriteRecords(aProofOfLock);
+ }
+ break;
+ case READING:
+ if (aOpener == mIndexFileOpener) {
+ mIndexFileOpener = nullptr;
+
+ if (NS_SUCCEEDED(aResult)) {
+ if (aHandle->FileSize() == 0) {
+ FinishRead(false, aProofOfLock);
+ CacheFileIOManager::DoomFile(aHandle, nullptr);
+ break;
+ }
+ mIndexHandle = aHandle;
+ } else {
+ FinishRead(false, aProofOfLock);
+ break;
+ }
+ } else if (aOpener == mJournalFileOpener) {
+ mJournalFileOpener = nullptr;
+ mJournalHandle = aHandle;
+ } else if (aOpener == mTmpFileOpener) {
+ mTmpFileOpener = nullptr;
+ mTmpHandle = aHandle;
+ } else {
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+
+ if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) {
+ // Some opener still didn't finish
+ break;
+ }
+
+ // We fail and cancel all other openers when we opening index file fails.
+ MOZ_ASSERT(mIndexHandle);
+
+ if (mTmpHandle) {
+ CacheFileIOManager::DoomFile(mTmpHandle, nullptr);
+ mTmpHandle = nullptr;
+
+ if (mJournalHandle) { // this shouldn't normally happen
+ LOG(
+ ("CacheIndex::OnFileOpenedInternal() - Unexpected state, all "
+ "files [%s, %s, %s] should never exist. Removing whole index.",
+ INDEX_NAME, JOURNAL_NAME, TEMP_INDEX_NAME));
+ FinishRead(false, aProofOfLock);
+ break;
+ }
+ }
+
+ if (mJournalHandle) {
+ // Rename journal to make sure we update index on next start in case
+ // firefox crashes
+ rv = CacheFileIOManager::RenameFile(
+ mJournalHandle, nsLiteralCString(TEMP_INDEX_NAME), this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
+ "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ FinishRead(false, aProofOfLock);
+ break;
+ }
+ } else {
+ StartReadingIndex(aProofOfLock);
+ }
+
+ break;
+ default:
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+}
+
+nsresult CacheIndex::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheIndex::OnFileOpened should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheIndex::OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) {
+ LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08" PRIx32 "]",
+ aHandle, static_cast<uint32_t>(aResult)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ StaticMutexAutoLock lock(sLock);
+
+ MOZ_RELEASE_ASSERT(IsIndexUsable());
+ MOZ_RELEASE_ASSERT(mRWPending);
+ mRWPending = false;
+
+ if (mState == READY && mShuttingDown) {
+ return NS_OK;
+ }
+
+ switch (mState) {
+ case WRITING:
+ MOZ_ASSERT(mIndexHandle == aHandle);
+
+ if (NS_FAILED(aResult)) {
+ FinishWrite(false, lock);
+ } else {
+ if (mSkipEntries == mProcessEntries) {
+ rv = CacheFileIOManager::RenameFile(
+ mIndexHandle, nsLiteralCString(INDEX_NAME), this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheIndex::OnDataWritten() - CacheFileIOManager::"
+ "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ FinishWrite(false, lock);
+ }
+ } else {
+ WriteRecords(lock);
+ }
+ }
+ break;
+ default:
+ // Writing was canceled.
+ LOG(
+ ("CacheIndex::OnDataWritten() - ignoring notification since the "
+ "operation was previously canceled [state=%d]",
+ mState));
+ ReleaseBuffer();
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheIndex::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) {
+ LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08" PRIx32 "]", aHandle,
+ static_cast<uint32_t>(aResult)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ MOZ_RELEASE_ASSERT(IsIndexUsable());
+ MOZ_RELEASE_ASSERT(mRWPending);
+ mRWPending = false;
+
+ switch (mState) {
+ case READING:
+ MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle);
+
+ if (NS_FAILED(aResult)) {
+ FinishRead(false, lock);
+ } else {
+ if (!mIndexOnDiskIsValid) {
+ ParseRecords(lock);
+ } else {
+ ParseJournal(lock);
+ }
+ }
+ break;
+ default:
+ // Reading was canceled.
+ LOG(
+ ("CacheIndex::OnDataRead() - ignoring notification since the "
+ "operation was previously canceled [state=%d]",
+ mState));
+ ReleaseBuffer();
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheIndex::OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheIndex::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheIndex::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheIndex::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) {
+ LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08" PRIx32 "]",
+ aHandle, static_cast<uint32_t>(aResult)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ StaticMutexAutoLock lock(sLock);
+
+ MOZ_RELEASE_ASSERT(IsIndexUsable());
+
+ if (mState == READY && mShuttingDown) {
+ return NS_OK;
+ }
+
+ switch (mState) {
+ case WRITING:
+ // This is a result of renaming the new index written to tmpfile to index
+ // file. This is the last step when writing the index and the whole
+ // writing process is successful iff renaming was successful.
+
+ if (mIndexHandle != aHandle) {
+ LOG(
+ ("CacheIndex::OnFileRenamed() - ignoring notification since it "
+ "belongs to previously canceled operation [state=%d]",
+ mState));
+ break;
+ }
+
+ FinishWrite(NS_SUCCEEDED(aResult), lock);
+ break;
+ case READING:
+ // This is a result of renaming journal file to tmpfile. It is renamed
+ // before we start reading index and journal file and it should normally
+ // succeed. If it fails give up reading of index.
+
+ if (mJournalHandle != aHandle) {
+ LOG(
+ ("CacheIndex::OnFileRenamed() - ignoring notification since it "
+ "belongs to previously canceled operation [state=%d]",
+ mState));
+ break;
+ }
+
+ if (NS_FAILED(aResult)) {
+ FinishRead(false, lock);
+ } else {
+ StartReadingIndex(lock);
+ }
+ break;
+ default:
+ // Reading/writing was canceled.
+ LOG(
+ ("CacheIndex::OnFileRenamed() - ignoring notification since the "
+ "operation was previously canceled [state=%d]",
+ mState));
+ }
+
+ return NS_OK;
+}
+
+// Memory reporting
+
+size_t CacheIndex::SizeOfExcludingThisInternal(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ sLock.AssertCurrentThreadOwns();
+
+ size_t n = 0;
+ nsCOMPtr<nsISizeOf> sizeOf;
+
+ // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable
+ // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special
+ // handles array.
+
+ sizeOf = do_QueryInterface(mCacheDirectory);
+ if (sizeOf) {
+ n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ sizeOf = do_QueryInterface(mUpdateTimer);
+ if (sizeOf) {
+ n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ n += mallocSizeOf(mRWBuf);
+ n += mallocSizeOf(mRWHash);
+
+ n += mIndex.SizeOfExcludingThis(mallocSizeOf);
+ n += mPendingUpdates.SizeOfExcludingThis(mallocSizeOf);
+ n += mTmpJournal.SizeOfExcludingThis(mallocSizeOf);
+
+ // mFrecencyArray items are reported by mIndex/mPendingUpdates
+ n += mFrecencyArray.mRecs.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mDiskConsumptionObservers.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ return n;
+}
+
+// static
+size_t CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ StaticMutexAutoLock lock(sLock);
+
+ if (!gInstance) return 0;
+
+ return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
+}
+
+// static
+size_t CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ StaticMutexAutoLock lock(sLock);
+
+ return mallocSizeOf(gInstance) +
+ (gInstance ? gInstance->SizeOfExcludingThisInternal(mallocSizeOf) : 0);
+}
+
+// static
+void CacheIndex::UpdateTotalBytesWritten(uint32_t aBytesWritten) {
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+ if (!index) {
+ return;
+ }
+
+ index->mTotalBytesWritten += aBytesWritten;
+
+ // Do telemetry report if enough data has been written and the index is
+ // in READY state. The data is available also in WRITING state, but we would
+ // need to deal with pending updates.
+ if (index->mTotalBytesWritten >= kTelemetryReportBytesLimit &&
+ index->mState == READY && !index->mIndexNeedsUpdate &&
+ !index->mShuttingDown) {
+ index->DoTelemetryReport();
+ index->mTotalBytesWritten = 0;
+ return;
+ }
+}
+
+void CacheIndex::DoTelemetryReport() {
+ static const nsLiteralCString
+ contentTypeNames[nsICacheEntry::CONTENT_TYPE_LAST] = {
+ "UNKNOWN"_ns, "OTHER"_ns, "JAVASCRIPT"_ns, "IMAGE"_ns,
+ "MEDIA"_ns, "STYLESHEET"_ns, "WASM"_ns};
+
+ for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
+ if (mIndexStats.Size() > 0) {
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_CACHE_SIZE_SHARE, contentTypeNames[i],
+ round(static_cast<double>(mIndexStats.SizeByType(i)) * 100.0 /
+ static_cast<double>(mIndexStats.Size())));
+ }
+
+ if (mIndexStats.Count() > 0) {
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_CACHE_ENTRY_COUNT_SHARE, contentTypeNames[i],
+ round(static_cast<double>(mIndexStats.CountByType(i)) * 100.0 /
+ static_cast<double>(mIndexStats.Count())));
+ }
+ }
+
+ nsCString probeKey;
+ if (CacheObserver::SmartCacheSizeEnabled()) {
+ probeKey = "SMARTSIZE"_ns;
+ } else {
+ probeKey = "USERDEFINEDSIZE"_ns;
+ }
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_ENTRY_COUNT, probeKey,
+ mIndexStats.Count());
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE, probeKey,
+ mIndexStats.Size() >> 10);
+}
+
+// static
+void CacheIndex::OnAsyncEviction(bool aEvicting) {
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+ if (!index) {
+ return;
+ }
+
+ index->mAsyncGetDiskConsumptionBlocked = aEvicting;
+ if (!aEvicting) {
+ index->NotifyAsyncGetDiskConsumptionCallbacks();
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheIndex.h b/netwerk/cache2/CacheIndex.h
new file mode 100644
index 0000000000..053c8ce654
--- /dev/null
+++ b/netwerk/cache2/CacheIndex.h
@@ -0,0 +1,1311 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheIndex__h__
+#define CacheIndex__h__
+
+#include "CacheLog.h"
+#include "CacheFileIOManager.h"
+#include "nsIRunnable.h"
+#include "CacheHashUtils.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheEntry.h"
+#include "nsILoadContextInfo.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTHashtable.h"
+#include "nsThreadUtils.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIFile;
+class nsIDirectoryEnumerator;
+class nsITimer;
+
+#ifdef DEBUG
+# define DEBUG_STATS 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+class CacheFileMetadata;
+class FileOpenHelper;
+class CacheIndexIterator;
+
+const uint16_t kIndexTimeNotAvailable = 0xFFFFU;
+const uint16_t kIndexTimeOutOfBound = 0xFFFEU;
+
+using CacheIndexHeader = struct {
+ // Version of the index. The index must be ignored and deleted when the file
+ // on disk was written with a newer version.
+ uint32_t mVersion;
+
+ // Timestamp of time when the last successful write of the index started.
+ // During update process we use this timestamp for a quick validation of entry
+ // files. If last modified time of the file is lower than this timestamp, we
+ // skip parsing of such file since the information in index should be up to
+ // date.
+ uint32_t mTimeStamp;
+
+ // We set this flag as soon as possible after parsing index during startup
+ // and clean it after we write journal to disk during shutdown. We ignore the
+ // journal and start update process whenever this flag is set during index
+ // parsing.
+ uint32_t mIsDirty;
+
+ // The amount of data written to the cache. When it reaches
+ // kTelemetryReportBytesLimit a telemetry report is sent and the counter is
+ // reset.
+ uint32_t mKBWritten;
+};
+
+static_assert(sizeof(CacheIndexHeader::mVersion) +
+ sizeof(CacheIndexHeader::mTimeStamp) +
+ sizeof(CacheIndexHeader::mIsDirty) +
+ sizeof(CacheIndexHeader::mKBWritten) ==
+ sizeof(CacheIndexHeader),
+ "Unexpected sizeof(CacheIndexHeader)!");
+
+#pragma pack(push, 1)
+struct CacheIndexRecord {
+ SHA1Sum::Hash mHash{};
+ uint32_t mFrecency{0};
+ OriginAttrsHash mOriginAttrsHash{0};
+ uint16_t mOnStartTime{kIndexTimeNotAvailable};
+ uint16_t mOnStopTime{kIndexTimeNotAvailable};
+ uint8_t mContentType{nsICacheEntry::CONTENT_TYPE_UNKNOWN};
+
+ /*
+ * 1000 0000 0000 0000 0000 0000 0000 0000 : initialized
+ * 0100 0000 0000 0000 0000 0000 0000 0000 : anonymous
+ * 0010 0000 0000 0000 0000 0000 0000 0000 : removed
+ * 0001 0000 0000 0000 0000 0000 0000 0000 : dirty
+ * 0000 1000 0000 0000 0000 0000 0000 0000 : fresh
+ * 0000 0100 0000 0000 0000 0000 0000 0000 : pinned
+ * 0000 0010 0000 0000 0000 0000 0000 0000 : has cached alt data
+ * 0000 0001 0000 0000 0000 0000 0000 0000 : reserved
+ * 0000 0000 1111 1111 1111 1111 1111 1111 : file size (in kB)
+ */
+ uint32_t mFlags{0};
+
+ CacheIndexRecord() = default;
+};
+#pragma pack(pop)
+
+static_assert(sizeof(CacheIndexRecord::mHash) +
+ sizeof(CacheIndexRecord::mFrecency) +
+ sizeof(CacheIndexRecord::mOriginAttrsHash) +
+ sizeof(CacheIndexRecord::mOnStartTime) +
+ sizeof(CacheIndexRecord::mOnStopTime) +
+ sizeof(CacheIndexRecord::mContentType) +
+ sizeof(CacheIndexRecord::mFlags) ==
+ sizeof(CacheIndexRecord),
+ "Unexpected sizeof(CacheIndexRecord)!");
+
+class CacheIndexRecordWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(
+ CacheIndexRecordWrapper, DispatchDeleteSelfToCurrentThread());
+
+ CacheIndexRecordWrapper() : mRec(MakeUnique<CacheIndexRecord>()) {}
+ CacheIndexRecord* Get() { return mRec.get(); }
+
+ private:
+ ~CacheIndexRecordWrapper();
+ void DispatchDeleteSelfToCurrentThread();
+ UniquePtr<CacheIndexRecord> mRec;
+ friend class DeleteCacheIndexRecordWrapper;
+};
+
+class CacheIndexEntry : public PLDHashEntryHdr {
+ public:
+ using KeyType = const SHA1Sum::Hash&;
+ using KeyTypePointer = const SHA1Sum::Hash*;
+
+ explicit CacheIndexEntry(KeyTypePointer aKey) {
+ MOZ_COUNT_CTOR(CacheIndexEntry);
+ mRec = new CacheIndexRecordWrapper();
+ LOG(("CacheIndexEntry::CacheIndexEntry() - Created record [rec=%p]",
+ mRec->Get()));
+ memcpy(&mRec->Get()->mHash, aKey, sizeof(SHA1Sum::Hash));
+ }
+ CacheIndexEntry(const CacheIndexEntry& aOther) {
+ MOZ_ASSERT_UNREACHABLE("CacheIndexEntry copy constructor is forbidden!");
+ }
+ ~CacheIndexEntry() {
+ MOZ_COUNT_DTOR(CacheIndexEntry);
+ LOG(("CacheIndexEntry::~CacheIndexEntry() - Deleting record [rec=%p]",
+ mRec->Get()));
+ }
+
+ // KeyEquals(): does this entry match this key?
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return memcmp(&mRec->Get()->mHash, aKey, sizeof(SHA1Sum::Hash)) == 0;
+ }
+
+ // KeyToPointer(): Convert KeyType to KeyTypePointer
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ // HashKey(): calculate the hash number
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return (reinterpret_cast<const uint32_t*>(aKey))[0];
+ }
+
+ // ALLOW_MEMMOVE can we move this class with memmove(), or do we have
+ // to use the copy constructor?
+ enum { ALLOW_MEMMOVE = true };
+
+ bool operator==(const CacheIndexEntry& aOther) const {
+ return KeyEquals(&aOther.mRec->Get()->mHash);
+ }
+
+ CacheIndexEntry& operator=(const CacheIndexEntry& aOther) {
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aOther.mRec->Get()->mHash,
+ sizeof(SHA1Sum::Hash)) == 0);
+ mRec->Get()->mFrecency = aOther.mRec->Get()->mFrecency;
+ mRec->Get()->mOriginAttrsHash = aOther.mRec->Get()->mOriginAttrsHash;
+ mRec->Get()->mOnStartTime = aOther.mRec->Get()->mOnStartTime;
+ mRec->Get()->mOnStopTime = aOther.mRec->Get()->mOnStopTime;
+ mRec->Get()->mContentType = aOther.mRec->Get()->mContentType;
+ mRec->Get()->mFlags = aOther.mRec->Get()->mFlags;
+ return *this;
+ }
+
+ void InitNew() {
+ mRec->Get()->mFrecency = 0;
+ mRec->Get()->mOriginAttrsHash = 0;
+ mRec->Get()->mOnStartTime = kIndexTimeNotAvailable;
+ mRec->Get()->mOnStopTime = kIndexTimeNotAvailable;
+ mRec->Get()->mContentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ mRec->Get()->mFlags = 0;
+ }
+
+ void Init(OriginAttrsHash aOriginAttrsHash, bool aAnonymous, bool aPinned) {
+ MOZ_ASSERT(mRec->Get()->mFrecency == 0);
+ MOZ_ASSERT(mRec->Get()->mOriginAttrsHash == 0);
+ MOZ_ASSERT(mRec->Get()->mOnStartTime == kIndexTimeNotAvailable);
+ MOZ_ASSERT(mRec->Get()->mOnStopTime == kIndexTimeNotAvailable);
+ MOZ_ASSERT(mRec->Get()->mContentType ==
+ nsICacheEntry::CONTENT_TYPE_UNKNOWN);
+ // When we init the entry it must be fresh and may be dirty
+ MOZ_ASSERT((mRec->Get()->mFlags & ~kDirtyMask) == kFreshMask);
+
+ mRec->Get()->mOriginAttrsHash = aOriginAttrsHash;
+ mRec->Get()->mFlags |= kInitializedMask;
+ if (aAnonymous) {
+ mRec->Get()->mFlags |= kAnonymousMask;
+ }
+ if (aPinned) {
+ mRec->Get()->mFlags |= kPinnedMask;
+ }
+ }
+
+ const SHA1Sum::Hash* Hash() const { return &mRec->Get()->mHash; }
+
+ bool IsInitialized() const {
+ return !!(mRec->Get()->mFlags & kInitializedMask);
+ }
+
+ mozilla::net::OriginAttrsHash OriginAttrsHash() const {
+ return mRec->Get()->mOriginAttrsHash;
+ }
+
+ bool Anonymous() const { return !!(mRec->Get()->mFlags & kAnonymousMask); }
+
+ bool IsRemoved() const { return !!(mRec->Get()->mFlags & kRemovedMask); }
+ void MarkRemoved() { mRec->Get()->mFlags |= kRemovedMask; }
+
+ bool IsDirty() const { return !!(mRec->Get()->mFlags & kDirtyMask); }
+ void MarkDirty() { mRec->Get()->mFlags |= kDirtyMask; }
+ void ClearDirty() { mRec->Get()->mFlags &= ~kDirtyMask; }
+
+ bool IsFresh() const { return !!(mRec->Get()->mFlags & kFreshMask); }
+ void MarkFresh() { mRec->Get()->mFlags |= kFreshMask; }
+
+ bool IsPinned() const { return !!(mRec->Get()->mFlags & kPinnedMask); }
+
+ void SetFrecency(uint32_t aFrecency) { mRec->Get()->mFrecency = aFrecency; }
+ uint32_t GetFrecency() const { return mRec->Get()->mFrecency; }
+
+ void SetHasAltData(bool aHasAltData) {
+ aHasAltData ? mRec->Get()->mFlags |= kHasAltDataMask
+ : mRec->Get()->mFlags &= ~kHasAltDataMask;
+ }
+ bool GetHasAltData() const {
+ return !!(mRec->Get()->mFlags & kHasAltDataMask);
+ }
+
+ void SetOnStartTime(uint16_t aTime) { mRec->Get()->mOnStartTime = aTime; }
+ uint16_t GetOnStartTime() const { return mRec->Get()->mOnStartTime; }
+
+ void SetOnStopTime(uint16_t aTime) { mRec->Get()->mOnStopTime = aTime; }
+ uint16_t GetOnStopTime() const { return mRec->Get()->mOnStopTime; }
+
+ void SetContentType(uint8_t aType) { mRec->Get()->mContentType = aType; }
+ uint8_t GetContentType() const { return GetContentType(mRec->Get()); }
+ static uint8_t GetContentType(CacheIndexRecord* aRec) {
+ if (aRec->mContentType >= nsICacheEntry::CONTENT_TYPE_LAST) {
+ LOG(
+ ("CacheIndexEntry::GetContentType() - Found invalid content type "
+ "[hash=%08x%08x%08x%08x%08x, contentType=%u]",
+ LOGSHA1(aRec->mHash), aRec->mContentType));
+ return nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ }
+ return aRec->mContentType;
+ }
+
+ // Sets filesize in kilobytes.
+ void SetFileSize(uint32_t aFileSize) {
+ if (aFileSize > kFileSizeMask) {
+ LOG(
+ ("CacheIndexEntry::SetFileSize() - FileSize is too large, "
+ "truncating to %u",
+ kFileSizeMask));
+ aFileSize = kFileSizeMask;
+ }
+ mRec->Get()->mFlags &= ~kFileSizeMask;
+ mRec->Get()->mFlags |= aFileSize;
+ }
+ // Returns filesize in kilobytes.
+ uint32_t GetFileSize() const { return GetFileSize(*(mRec->Get())); }
+ static uint32_t GetFileSize(const CacheIndexRecord& aRec) {
+ return aRec.mFlags & kFileSizeMask;
+ }
+ static uint32_t IsPinned(CacheIndexRecord* aRec) {
+ return aRec->mFlags & kPinnedMask;
+ }
+ bool IsFileEmpty() const { return GetFileSize() == 0; }
+
+ void WriteToBuf(void* aBuf) {
+ uint8_t* ptr = static_cast<uint8_t*>(aBuf);
+ memcpy(ptr, mRec->Get()->mHash, sizeof(SHA1Sum::Hash));
+ ptr += sizeof(SHA1Sum::Hash);
+ NetworkEndian::writeUint32(ptr, mRec->Get()->mFrecency);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint64(ptr, mRec->Get()->mOriginAttrsHash);
+ ptr += sizeof(uint64_t);
+ NetworkEndian::writeUint16(ptr, mRec->Get()->mOnStartTime);
+ ptr += sizeof(uint16_t);
+ NetworkEndian::writeUint16(ptr, mRec->Get()->mOnStopTime);
+ ptr += sizeof(uint16_t);
+ *ptr = mRec->Get()->mContentType;
+ ptr += sizeof(uint8_t);
+ // Dirty and fresh flags should never go to disk, since they make sense only
+ // during current session.
+ NetworkEndian::writeUint32(
+ ptr, mRec->Get()->mFlags & ~(kDirtyMask | kFreshMask));
+ }
+
+ void ReadFromBuf(void* aBuf) {
+ const uint8_t* ptr = static_cast<const uint8_t*>(aBuf);
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, ptr, sizeof(SHA1Sum::Hash)) == 0);
+ ptr += sizeof(SHA1Sum::Hash);
+ mRec->Get()->mFrecency = NetworkEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mRec->Get()->mOriginAttrsHash = NetworkEndian::readUint64(ptr);
+ ptr += sizeof(uint64_t);
+ mRec->Get()->mOnStartTime = NetworkEndian::readUint16(ptr);
+ ptr += sizeof(uint16_t);
+ mRec->Get()->mOnStopTime = NetworkEndian::readUint16(ptr);
+ ptr += sizeof(uint16_t);
+ mRec->Get()->mContentType = *ptr;
+ ptr += sizeof(uint8_t);
+ mRec->Get()->mFlags = NetworkEndian::readUint32(ptr);
+ }
+
+ void Log() const {
+ LOG(
+ ("CacheIndexEntry::Log() [this=%p, hash=%08x%08x%08x%08x%08x, fresh=%u,"
+ " initialized=%u, removed=%u, dirty=%u, anonymous=%u, "
+ "originAttrsHash=%" PRIx64 ", frecency=%u, hasAltData=%u, "
+ "onStartTime=%u, onStopTime=%u, contentType=%u, size=%u]",
+ this, LOGSHA1(mRec->Get()->mHash), IsFresh(), IsInitialized(),
+ IsRemoved(), IsDirty(), Anonymous(), OriginAttrsHash(), GetFrecency(),
+ GetHasAltData(), GetOnStartTime(), GetOnStopTime(), GetContentType(),
+ GetFileSize()));
+ }
+
+ static bool RecordMatchesLoadContextInfo(CacheIndexRecordWrapper* aRec,
+ nsILoadContextInfo* aInfo) {
+ MOZ_ASSERT(aInfo);
+
+ return !aInfo->IsPrivate() &&
+ GetOriginAttrsHash(*aInfo->OriginAttributesPtr()) ==
+ aRec->Get()->mOriginAttrsHash &&
+ aInfo->IsAnonymous() == !!(aRec->Get()->mFlags & kAnonymousMask);
+ }
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(mRec->Get());
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+ }
+
+ private:
+ friend class CacheIndexEntryUpdate;
+ friend class CacheIndex;
+ friend class CacheIndexEntryAutoManage;
+ friend struct CacheIndexRecord;
+
+ static const uint32_t kInitializedMask = 0x80000000;
+ static const uint32_t kAnonymousMask = 0x40000000;
+
+ // This flag is set when the entry was removed. We need to keep this
+ // information in memory until we write the index file.
+ static const uint32_t kRemovedMask = 0x20000000;
+
+ // This flag is set when the information in memory is not in sync with the
+ // information in index file on disk.
+ static const uint32_t kDirtyMask = 0x10000000;
+
+ // This flag is set when the information about the entry is fresh, i.e.
+ // we've created or opened this entry during this session, or we've seen
+ // this entry during update or build process.
+ static const uint32_t kFreshMask = 0x08000000;
+
+ // Indicates a pinned entry.
+ static const uint32_t kPinnedMask = 0x04000000;
+
+ // Indicates there is cached alternative data in the entry.
+ static const uint32_t kHasAltDataMask = 0x02000000;
+ static const uint32_t kReservedMask = 0x01000000;
+
+ // FileSize in kilobytes
+ static const uint32_t kFileSizeMask = 0x00FFFFFF;
+
+ RefPtr<CacheIndexRecordWrapper> mRec;
+};
+
+class CacheIndexEntryUpdate : public CacheIndexEntry {
+ public:
+ explicit CacheIndexEntryUpdate(CacheIndexEntry::KeyTypePointer aKey)
+ : CacheIndexEntry(aKey), mUpdateFlags(0) {
+ MOZ_COUNT_CTOR(CacheIndexEntryUpdate);
+ LOG(("CacheIndexEntryUpdate::CacheIndexEntryUpdate()"));
+ }
+ ~CacheIndexEntryUpdate() {
+ MOZ_COUNT_DTOR(CacheIndexEntryUpdate);
+ LOG(("CacheIndexEntryUpdate::~CacheIndexEntryUpdate()"));
+ }
+
+ CacheIndexEntryUpdate& operator=(const CacheIndexEntry& aOther) {
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aOther.mRec->Get()->mHash,
+ sizeof(SHA1Sum::Hash)) == 0);
+ mUpdateFlags = 0;
+ *(static_cast<CacheIndexEntry*>(this)) = aOther;
+ return *this;
+ }
+
+ void InitNew() {
+ mUpdateFlags = kFrecencyUpdatedMask | kHasAltDataUpdatedMask |
+ kOnStartTimeUpdatedMask | kOnStopTimeUpdatedMask |
+ kContentTypeUpdatedMask | kFileSizeUpdatedMask;
+ CacheIndexEntry::InitNew();
+ }
+
+ void SetFrecency(uint32_t aFrecency) {
+ mUpdateFlags |= kFrecencyUpdatedMask;
+ CacheIndexEntry::SetFrecency(aFrecency);
+ }
+
+ void SetHasAltData(bool aHasAltData) {
+ mUpdateFlags |= kHasAltDataUpdatedMask;
+ CacheIndexEntry::SetHasAltData(aHasAltData);
+ }
+
+ void SetOnStartTime(uint16_t aTime) {
+ mUpdateFlags |= kOnStartTimeUpdatedMask;
+ CacheIndexEntry::SetOnStartTime(aTime);
+ }
+
+ void SetOnStopTime(uint16_t aTime) {
+ mUpdateFlags |= kOnStopTimeUpdatedMask;
+ CacheIndexEntry::SetOnStopTime(aTime);
+ }
+
+ void SetContentType(uint8_t aType) {
+ mUpdateFlags |= kContentTypeUpdatedMask;
+ CacheIndexEntry::SetContentType(aType);
+ }
+
+ void SetFileSize(uint32_t aFileSize) {
+ mUpdateFlags |= kFileSizeUpdatedMask;
+ CacheIndexEntry::SetFileSize(aFileSize);
+ }
+
+ void ApplyUpdate(CacheIndexEntry* aDst) {
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aDst->mRec->Get()->mHash,
+ sizeof(SHA1Sum::Hash)) == 0);
+ if (mUpdateFlags & kFrecencyUpdatedMask) {
+ aDst->mRec->Get()->mFrecency = mRec->Get()->mFrecency;
+ }
+ aDst->mRec->Get()->mOriginAttrsHash = mRec->Get()->mOriginAttrsHash;
+ if (mUpdateFlags & kOnStartTimeUpdatedMask) {
+ aDst->mRec->Get()->mOnStartTime = mRec->Get()->mOnStartTime;
+ }
+ if (mUpdateFlags & kOnStopTimeUpdatedMask) {
+ aDst->mRec->Get()->mOnStopTime = mRec->Get()->mOnStopTime;
+ }
+ if (mUpdateFlags & kContentTypeUpdatedMask) {
+ aDst->mRec->Get()->mContentType = mRec->Get()->mContentType;
+ }
+ if (mUpdateFlags & kHasAltDataUpdatedMask &&
+ ((aDst->mRec->Get()->mFlags ^ mRec->Get()->mFlags) & kHasAltDataMask)) {
+ // Toggle the bit if we need to.
+ aDst->mRec->Get()->mFlags ^= kHasAltDataMask;
+ }
+
+ if (mUpdateFlags & kFileSizeUpdatedMask) {
+ // Copy all flags except |HasAltData|.
+ aDst->mRec->Get()->mFlags |= (mRec->Get()->mFlags & ~kHasAltDataMask);
+ } else {
+ // Copy all flags except |HasAltData| and file size.
+ aDst->mRec->Get()->mFlags &= kFileSizeMask;
+ aDst->mRec->Get()->mFlags |=
+ (mRec->Get()->mFlags & ~kHasAltDataMask & ~kFileSizeMask);
+ }
+ }
+
+ private:
+ static const uint32_t kFrecencyUpdatedMask = 0x00000001;
+ static const uint32_t kContentTypeUpdatedMask = 0x00000002;
+ static const uint32_t kFileSizeUpdatedMask = 0x00000004;
+ static const uint32_t kHasAltDataUpdatedMask = 0x00000008;
+ static const uint32_t kOnStartTimeUpdatedMask = 0x00000010;
+ static const uint32_t kOnStopTimeUpdatedMask = 0x00000020;
+
+ uint32_t mUpdateFlags;
+};
+
+class CacheIndexStats {
+ public:
+ CacheIndexStats() {
+ for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
+ mCountByType[i] = 0;
+ mSizeByType[i] = 0;
+ }
+ }
+
+ bool operator==(const CacheIndexStats& aOther) const {
+ for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
+ if (mCountByType[i] != aOther.mCountByType[i] ||
+ mSizeByType[i] != aOther.mSizeByType[i]) {
+ return false;
+ }
+ }
+
+ return
+#ifdef DEBUG
+ aOther.mStateLogged == mStateLogged &&
+#endif
+ aOther.mCount == mCount && aOther.mNotInitialized == mNotInitialized &&
+ aOther.mRemoved == mRemoved && aOther.mDirty == mDirty &&
+ aOther.mFresh == mFresh && aOther.mEmpty == mEmpty &&
+ aOther.mSize == mSize;
+ }
+
+#ifdef DEBUG
+ void DisableLogging() { mDisableLogging = true; }
+#endif
+
+ void Log() {
+ LOG(
+ ("CacheIndexStats::Log() [count=%u, notInitialized=%u, removed=%u, "
+ "dirty=%u, fresh=%u, empty=%u, size=%u]",
+ mCount, mNotInitialized, mRemoved, mDirty, mFresh, mEmpty, mSize));
+ }
+
+ void Clear() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Clear() - state logged!");
+
+ mCount = 0;
+ mNotInitialized = 0;
+ mRemoved = 0;
+ mDirty = 0;
+ mFresh = 0;
+ mEmpty = 0;
+ mSize = 0;
+ for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
+ mCountByType[i] = 0;
+ mSizeByType[i] = 0;
+ }
+ }
+
+#ifdef DEBUG
+ bool StateLogged() { return mStateLogged; }
+#endif
+
+ uint32_t Count() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Count() - state logged!");
+ return mCount;
+ }
+
+ uint32_t CountByType(uint8_t aContentType) {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::CountByType() - state logged!");
+ MOZ_RELEASE_ASSERT(aContentType < nsICacheEntry::CONTENT_TYPE_LAST);
+ return mCountByType[aContentType];
+ }
+
+ uint32_t Dirty() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Dirty() - state logged!");
+ return mDirty;
+ }
+
+ uint32_t Fresh() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Fresh() - state logged!");
+ return mFresh;
+ }
+
+ uint32_t ActiveEntriesCount() {
+ MOZ_ASSERT(!mStateLogged,
+ "CacheIndexStats::ActiveEntriesCount() - state "
+ "logged!");
+ return mCount - mRemoved - mNotInitialized - mEmpty;
+ }
+
+ uint32_t Size() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Size() - state logged!");
+ return mSize;
+ }
+
+ uint32_t SizeByType(uint8_t aContentType) {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::SizeByType() - state logged!");
+ MOZ_RELEASE_ASSERT(aContentType < nsICacheEntry::CONTENT_TYPE_LAST);
+ return mSizeByType[aContentType];
+ }
+
+ void BeforeChange(const CacheIndexEntry* aEntry) {
+#ifdef DEBUG_STATS
+ if (!mDisableLogging) {
+ LOG(("CacheIndexStats::BeforeChange()"));
+ Log();
+ }
+#endif
+
+ MOZ_ASSERT(!mStateLogged,
+ "CacheIndexStats::BeforeChange() - state "
+ "logged!");
+#ifdef DEBUG
+ mStateLogged = true;
+#endif
+ if (aEntry) {
+ MOZ_ASSERT(mCount);
+ uint8_t contentType = aEntry->GetContentType();
+ mCount--;
+ mCountByType[contentType]--;
+ if (aEntry->IsDirty()) {
+ MOZ_ASSERT(mDirty);
+ mDirty--;
+ }
+ if (aEntry->IsFresh()) {
+ MOZ_ASSERT(mFresh);
+ mFresh--;
+ }
+ if (aEntry->IsRemoved()) {
+ MOZ_ASSERT(mRemoved);
+ mRemoved--;
+ } else {
+ if (!aEntry->IsInitialized()) {
+ MOZ_ASSERT(mNotInitialized);
+ mNotInitialized--;
+ } else {
+ if (aEntry->IsFileEmpty()) {
+ MOZ_ASSERT(mEmpty);
+ mEmpty--;
+ } else {
+ MOZ_ASSERT(mSize >= aEntry->GetFileSize());
+ mSize -= aEntry->GetFileSize();
+ mSizeByType[contentType] -= aEntry->GetFileSize();
+ }
+ }
+ }
+ }
+ }
+
+ void AfterChange(const CacheIndexEntry* aEntry) {
+ MOZ_ASSERT(mStateLogged,
+ "CacheIndexStats::AfterChange() - state not "
+ "logged!");
+#ifdef DEBUG
+ mStateLogged = false;
+#endif
+ if (aEntry) {
+ uint8_t contentType = aEntry->GetContentType();
+ ++mCount;
+ ++mCountByType[contentType];
+ if (aEntry->IsDirty()) {
+ mDirty++;
+ }
+ if (aEntry->IsFresh()) {
+ mFresh++;
+ }
+ if (aEntry->IsRemoved()) {
+ mRemoved++;
+ } else {
+ if (!aEntry->IsInitialized()) {
+ mNotInitialized++;
+ } else {
+ if (aEntry->IsFileEmpty()) {
+ mEmpty++;
+ } else {
+ mSize += aEntry->GetFileSize();
+ mSizeByType[contentType] += aEntry->GetFileSize();
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG_STATS
+ if (!mDisableLogging) {
+ LOG(("CacheIndexStats::AfterChange()"));
+ Log();
+ }
+#endif
+ }
+
+ private:
+ uint32_t mCount{0};
+ uint32_t mCountByType[nsICacheEntry::CONTENT_TYPE_LAST]{0};
+ uint32_t mNotInitialized{0};
+ uint32_t mRemoved{0};
+ uint32_t mDirty{0};
+ uint32_t mFresh{0};
+ uint32_t mEmpty{0};
+ uint32_t mSize{0};
+ uint32_t mSizeByType[nsICacheEntry::CONTENT_TYPE_LAST]{0};
+#ifdef DEBUG
+ // We completely remove the data about an entry from the stats in
+ // BeforeChange() and set this flag to true. The entry is then modified,
+ // deleted or created and the data is again put into the stats and this flag
+ // set to false. Statistics must not be read during this time since the
+ // information is not correct.
+ bool mStateLogged{false};
+
+ // Disables logging in this instance of CacheIndexStats
+ bool mDisableLogging{false};
+#endif
+};
+
+class CacheIndex final : public CacheFileIOListener, public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ CacheIndex();
+
+ static nsresult Init(nsIFile* aCacheDirectory);
+ static nsresult PreShutdown();
+ static nsresult Shutdown();
+
+ // Following methods can be called only on IO thread.
+
+ // Add entry to the index. The entry shouldn't be present in index. This
+ // method is called whenever a new handle for a new entry file is created. The
+ // newly created entry is not initialized and it must be either initialized
+ // with InitEntry() or removed with RemoveEntry().
+ static nsresult AddEntry(const SHA1Sum::Hash* aHash);
+
+ // Inform index about an existing entry that should be present in index. This
+ // method is called whenever a new handle for an existing entry file is
+ // created. Like in case of AddEntry(), either InitEntry() or RemoveEntry()
+ // must be called on the entry, since the entry is not initizlized if the
+ // index is outdated.
+ static nsresult EnsureEntryExists(const SHA1Sum::Hash* aHash);
+
+ // Initialize the entry. It MUST be present in index. Call to AddEntry() or
+ // EnsureEntryExists() must precede the call to this method.
+ static nsresult InitEntry(const SHA1Sum::Hash* aHash,
+ OriginAttrsHash aOriginAttrsHash, bool aAnonymous,
+ bool aPinned);
+
+ // Remove entry from index. The entry should be present in index.
+ static nsresult RemoveEntry(const SHA1Sum::Hash* aHash);
+
+ // Update some information in entry. The entry MUST be present in index and
+ // MUST be initialized. Call to AddEntry() or EnsureEntryExists() and to
+ // InitEntry() must precede the call to this method.
+ // Pass nullptr if the value didn't change.
+ static nsresult UpdateEntry(const SHA1Sum::Hash* aHash,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType,
+ const uint32_t* aSize);
+
+ // Remove all entries from the index. Called when clearing the whole cache.
+ static nsresult RemoveAll();
+
+ enum EntryStatus { EXISTS = 0, DOES_NOT_EXIST = 1, DO_NOT_KNOW = 2 };
+
+ // Returns status of the entry in index for the given key. It can be called
+ // on any thread.
+ // If the optional aCB callback is given, the it will be called with a
+ // CacheIndexEntry only if _retval is EXISTS when the method returns.
+ static nsresult HasEntry(
+ const nsACString& aKey, EntryStatus* _retval,
+ const std::function<void(const CacheIndexEntry*)>& aCB = nullptr);
+ static nsresult HasEntry(
+ const SHA1Sum::Hash& hash, EntryStatus* _retval,
+ const std::function<void(const CacheIndexEntry*)>& aCB = nullptr);
+
+ // Returns a hash of the least important entry that should be evicted if the
+ // cache size is over limit and also returns a total number of all entries in
+ // the index minus the number of forced valid entries and unpinned entries
+ // that we encounter when searching (see below)
+ static nsresult GetEntryForEviction(bool aIgnoreEmptyEntries,
+ SHA1Sum::Hash* aHash, uint32_t* aCnt);
+
+ // Checks if a cache entry is currently forced valid. Used to prevent an entry
+ // (that has been forced valid) from being evicted when the cache size reaches
+ // its limit.
+ static bool IsForcedValidEntry(const SHA1Sum::Hash* aHash);
+
+ // Returns cache size in kB.
+ static nsresult GetCacheSize(uint32_t* _retval);
+
+ // Returns number of entry files in the cache
+ static nsresult GetEntryFileCount(uint32_t* _retval);
+
+ // Synchronously returns the disk occupation and number of entries
+ // per-context. Callable on any thread. It will ignore loadContextInfo and get
+ // stats for all entries if the aInfo is a nullptr.
+ static nsresult GetCacheStats(nsILoadContextInfo* aInfo, uint32_t* aSize,
+ uint32_t* aCount);
+
+ // Asynchronously gets the disk cache size, used for display in the UI.
+ static nsresult AsyncGetDiskConsumption(
+ nsICacheStorageConsumptionObserver* aObserver);
+
+ // Returns an iterator that returns entries matching a given context that were
+ // present in the index at the time this method was called. If aAddNew is true
+ // then the iterator will also return entries created after this call.
+ // NOTE: When some entry is removed from index it is removed also from the
+ // iterator regardless what aAddNew was passed.
+ static nsresult GetIterator(nsILoadContextInfo* aInfo, bool aAddNew,
+ CacheIndexIterator** _retval);
+
+ // Returns true if we _think_ that the index is up to date. I.e. the state is
+ // READY or WRITING and mIndexNeedsUpdate as well as mShuttingDown is false.
+ static nsresult IsUpToDate(bool* _retval);
+
+ // Called from CacheStorageService::Clear() and
+ // CacheFileContextEvictor::EvictEntries(), sets a flag that blocks
+ // notification to AsyncGetDiskConsumption.
+ static void OnAsyncEviction(bool aEvicting);
+
+ // We keep track of total bytes written to the cache to be able to do
+ // a telemetry report after writting certain amount of data to the cache.
+ static void UpdateTotalBytesWritten(uint32_t aBytesWritten);
+
+ // Memory reporting
+ static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+ static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ private:
+ friend class CacheIndexEntryAutoManage;
+ friend class FileOpenHelper;
+ friend class CacheIndexIterator;
+ friend class CacheIndexRecordWrapper;
+ friend class DeleteCacheIndexRecordWrapper;
+
+ virtual ~CacheIndex();
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
+ void OnFileOpenedInternal(FileOpenHelper* aOpener, CacheFileHandle* aHandle,
+ nsresult aResult,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) override;
+
+ nsresult InitInternal(nsIFile* aCacheDirectory,
+ const StaticMutexAutoLock& aProofOfLock);
+ void PreShutdownInternal();
+
+ // This method returns false when index is not initialized or is shut down.
+ bool IsIndexUsable() MOZ_REQUIRES(sLock);
+
+ // This method checks whether the entry has the same values of
+ // originAttributes and isAnonymous. We don't expect to find a collision
+ // since these values are part of the key that we hash and we use a strong
+ // hash function.
+ static bool IsCollision(CacheIndexEntry* aEntry,
+ OriginAttrsHash aOriginAttrsHash, bool aAnonymous);
+
+ // Checks whether any of the information about the entry has changed.
+ static bool HasEntryChanged(CacheIndexEntry* aEntry,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType,
+ const uint32_t* aSize);
+
+ // Merge all pending operations from mPendingUpdates into mIndex.
+ void ProcessPendingOperations(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Following methods perform writing of the index file.
+ //
+ // The index is written periodically, but not earlier than once in
+ // kMinDumpInterval and there must be at least kMinUnwrittenChanges
+ // differences between index on disk and in memory. Index is always first
+ // written to a temporary file and the old index file is replaced when the
+ // writing process succeeds.
+ //
+ // Starts writing of index when both limits (minimal delay between writes and
+ // minimum number of changes in index) were exceeded.
+ bool WriteIndexToDiskIfNeeded(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Starts writing of index file.
+ void WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Serializes part of mIndex hashtable to the write buffer a writes the buffer
+ // to the file.
+ void WriteRecords(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Finalizes writing process.
+ void FinishWrite(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Following methods perform writing of the journal during shutdown. All these
+ // methods must be called only during shutdown since they write/delete files
+ // directly on the main thread instead of using CacheFileIOManager that does
+ // it asynchronously on IO thread. Journal contains only entries that are
+ // dirty, i.e. changes that are not present in the index file on the disk.
+ // When the log is written successfully, the dirty flag in index file is
+ // cleared.
+ nsresult GetFile(const nsACString& aName, nsIFile** _retval);
+ void RemoveFile(const nsACString& aName) MOZ_REQUIRES(sLock);
+ void RemoveAllIndexFiles() MOZ_REQUIRES(sLock);
+ void RemoveJournalAndTempFile() MOZ_REQUIRES(sLock);
+ // Writes journal to the disk and clears dirty flag in index header.
+ nsresult WriteLogToDisk() MOZ_REQUIRES(sLock);
+
+ // Following methods perform reading of the index from the disk.
+ //
+ // Index is read at startup just after initializing the CacheIndex. There are
+ // 3 files used when manipulating with index: index file, journal file and
+ // a temporary file. All files contain the hash of the data, so we can check
+ // whether the content is valid and complete. Index file contains also a dirty
+ // flag in the index header which is unset on a clean shutdown. During opening
+ // and reading of the files we determine the status of the whole index from
+ // the states of the separate files. Following table shows all possible
+ // combinations:
+ //
+ // index, journal, tmpfile
+ // M * * - index is missing -> BUILD
+ // I * * - index is invalid -> BUILD
+ // D * * - index is dirty -> UPDATE
+ // C M * - index is dirty -> UPDATE
+ // C I * - unexpected state -> UPDATE
+ // C V E - unexpected state -> UPDATE
+ // C V M - index is up to date -> READY
+ //
+ // where the letters mean:
+ // * - any state
+ // E - file exists
+ // M - file is missing
+ // I - data is invalid (parsing failed or hash didn't match)
+ // D - dirty (data in index file is correct, but dirty flag is set)
+ // C - clean (index file is clean)
+ // V - valid (data in journal file is correct)
+ //
+ // Note: We accept the data from journal only when the index is up to date as
+ // a whole (i.e. C,V,M state).
+ //
+ // We rename the journal file to the temporary file as soon as possible after
+ // initial test to ensure that we start update process on the next startup if
+ // FF crashes during parsing of the index.
+ //
+ // Initiates reading index from disk.
+ void ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Starts reading data from index file.
+ void StartReadingIndex(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Parses data read from index file.
+ void ParseRecords(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Starts reading data from journal file.
+ void StartReadingJournal(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Parses data read from journal file.
+ void ParseJournal(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Merges entries from journal into mIndex.
+ void MergeJournal(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // In debug build this method checks that we have no fresh entry in mIndex
+ // after we finish reading index and before we process pending operations.
+ void EnsureNoFreshEntry() MOZ_REQUIRES(sLock);
+ // In debug build this method is called after processing pending operations
+ // to make sure mIndexStats contains correct information.
+ void EnsureCorrectStats() MOZ_REQUIRES(sLock);
+
+ // Finalizes reading process.
+ void FinishRead(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Following methods perform updating and building of the index.
+ // Timer callback that starts update or build process.
+ static void DelayedUpdate(nsITimer* aTimer, void* aClosure);
+ void DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Posts timer event that start update or build process.
+ nsresult ScheduleUpdateTimer(uint32_t aDelay) MOZ_REQUIRES(sLock);
+ nsresult SetupDirectoryEnumerator() MOZ_REQUIRES(sLock);
+ nsresult InitEntryFromDiskData(CacheIndexEntry* aEntry,
+ CacheFileMetadata* aMetaData,
+ int64_t aFileSize);
+ // Returns true when either a timer is scheduled or event is posted.
+ bool IsUpdatePending() MOZ_REQUIRES(sLock);
+ // Iterates through all files in entries directory that we didn't create/open
+ // during this session, parses them and adds the entries to the index.
+ void BuildIndex(const StaticMutexAutoLock& aProofOfLock) MOZ_REQUIRES(sLock);
+
+ bool StartUpdatingIndexIfNeeded(const StaticMutexAutoLock& aProofOfLock,
+ bool aSwitchingToReadyState = false);
+ // Starts update or build process or fires a timer when it is too early after
+ // startup.
+ void StartUpdatingIndex(bool aRebuild,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Iterates through all files in entries directory that we didn't create/open
+ // during this session and theirs last modified time is newer than timestamp
+ // in the index header. Parses the files and adds the entries to the index.
+ void UpdateIndex(const StaticMutexAutoLock& aProofOfLock) MOZ_REQUIRES(sLock);
+ // Finalizes update or build process.
+ void FinishUpdate(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ void RemoveNonFreshEntries(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ enum EState {
+ // Initial state in which the index is not usable
+ // Possible transitions:
+ // -> READING
+ INITIAL = 0,
+
+ // Index is being read from the disk.
+ // Possible transitions:
+ // -> INITIAL - We failed to dispatch a read event.
+ // -> BUILDING - No or corrupted index file was found.
+ // -> UPDATING - No or corrupted journal file was found.
+ // - Dirty flag was set in index header.
+ // -> READY - Index was read successfully or was interrupted by
+ // pre-shutdown.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ READING = 1,
+
+ // Index is being written to the disk.
+ // Possible transitions:
+ // -> READY - Writing of index finished or was interrupted by
+ // pre-shutdown..
+ // -> UPDATING - Writing of index finished, but index was found outdated
+ // during writing.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ WRITING = 2,
+
+ // Index is being build.
+ // Possible transitions:
+ // -> READY - Building of index finished or was interrupted by
+ // pre-shutdown.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ BUILDING = 3,
+
+ // Index is being updated.
+ // Possible transitions:
+ // -> READY - Updating of index finished or was interrupted by
+ // pre-shutdown.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ UPDATING = 4,
+
+ // Index is ready.
+ // Possible transitions:
+ // -> UPDATING - Index was found outdated.
+ // -> SHUTDOWN - Index is shutting down.
+ READY = 5,
+
+ // Index is shutting down.
+ SHUTDOWN = 6
+ };
+
+ static char const* StateString(EState aState);
+ void ChangeState(EState aNewState, const StaticMutexAutoLock& aProofOfLock);
+ void NotifyAsyncGetDiskConsumptionCallbacks() MOZ_REQUIRES(sLock);
+
+ // Allocates and releases buffer used for reading and writing index.
+ void AllocBuffer() MOZ_REQUIRES(sLock);
+ void ReleaseBuffer() MOZ_REQUIRES(sLock);
+
+ // Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
+ void AddRecordToIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ void RemoveRecordFromIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ void ReplaceRecordInIterators(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Memory reporting (private part)
+ size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
+ MOZ_REQUIRES(sLock);
+
+ // Reports telemetry about cache, i.e. size, entry count and content type
+ // stats.
+ void DoTelemetryReport() MOZ_REQUIRES(sLock);
+
+ static mozilla::StaticRefPtr<CacheIndex> gInstance MOZ_GUARDED_BY(sLock);
+
+ // sLock guards almost everything here...
+ // Also guards FileOpenHelper::mCanceled
+ static StaticMutex sLock;
+
+ nsCOMPtr<nsIFile> mCacheDirectory;
+
+ EState mState MOZ_GUARDED_BY(sLock){INITIAL};
+ // Timestamp of time when the index was initialized. We use it to delay
+ // initial update or build of index.
+ TimeStamp mStartTime MOZ_GUARDED_BY(sLock);
+ // Set to true in PreShutdown(), it is checked on variaous places to prevent
+ // starting any process (write, update, etc.) during shutdown.
+ bool mShuttingDown MOZ_GUARDED_BY(sLock){false};
+ // When set to true, update process should start as soon as possible. This
+ // flag is set whenever we find some inconsistency which would be fixed by
+ // update process. The flag is checked always when switching to READY state.
+ // To make sure we start the update process as soon as possible, methods that
+ // set this flag should also call StartUpdatingIndexIfNeeded() to cover the
+ // case when we are currently in READY state.
+ bool mIndexNeedsUpdate MOZ_GUARDED_BY(sLock){false};
+ // Set at the beginning of RemoveAll() which clears the whole index. When
+ // removing all entries we must stop any pending reading, writing, updating or
+ // building operation. This flag is checked at various places and it prevents
+ // we won't start another operation (e.g. canceling reading of the index would
+ // normally start update or build process)
+ bool mRemovingAll MOZ_GUARDED_BY(sLock){false};
+ // Whether the index file on disk exists and is valid.
+ bool mIndexOnDiskIsValid MOZ_GUARDED_BY(sLock){false};
+ // When something goes wrong during updating or building process, we don't
+ // mark index clean (and also don't write journal) to ensure that update or
+ // build will be initiated on the next start.
+ bool mDontMarkIndexClean MOZ_GUARDED_BY(sLock){false};
+ // Timestamp value from index file. It is used during update process to skip
+ // entries that were last modified before this timestamp.
+ uint32_t mIndexTimeStamp MOZ_GUARDED_BY(sLock){0};
+ // Timestamp of last time the index was dumped to disk.
+ // NOTE: The index might not be necessarily dumped at this time. The value
+ // is used to schedule next dump of the index.
+ TimeStamp mLastDumpTime MOZ_GUARDED_BY(sLock);
+
+ // Timer of delayed update/build.
+ nsCOMPtr<nsITimer> mUpdateTimer MOZ_GUARDED_BY(sLock);
+ // True when build or update event is posted
+ bool mUpdateEventPending MOZ_GUARDED_BY(sLock){false};
+
+ // Helper members used when reading/writing index from/to disk.
+ // Contains number of entries that should be skipped:
+ // - in hashtable when writing index because they were already written
+ // - in index file when reading index because they were already read
+ uint32_t mSkipEntries MOZ_GUARDED_BY(sLock){0};
+ // Number of entries that should be written to disk. This is number of entries
+ // in hashtable that are initialized and are not marked as removed when
+ // writing begins.
+ uint32_t mProcessEntries MOZ_GUARDED_BY(sLock){0};
+ char* mRWBuf MOZ_GUARDED_BY(sLock){nullptr};
+ uint32_t mRWBufSize MOZ_GUARDED_BY(sLock){0};
+ uint32_t mRWBufPos MOZ_GUARDED_BY(sLock){0};
+ RefPtr<CacheHash> mRWHash MOZ_GUARDED_BY(sLock);
+
+ // True if read or write operation is pending. It is used to ensure that
+ // mRWBuf is not freed until OnDataRead or OnDataWritten is called.
+ bool mRWPending MOZ_GUARDED_BY(sLock){false};
+
+ // Reading of journal succeeded if true.
+ bool mJournalReadSuccessfully MOZ_GUARDED_BY(sLock){false};
+
+ // Handle used for writing and reading index file.
+ RefPtr<CacheFileHandle> mIndexHandle MOZ_GUARDED_BY(sLock);
+ // Handle used for reading journal file.
+ RefPtr<CacheFileHandle> mJournalHandle MOZ_GUARDED_BY(sLock);
+ // Used to check the existence of the file during reading process.
+ RefPtr<CacheFileHandle> mTmpHandle MOZ_GUARDED_BY(sLock);
+
+ RefPtr<FileOpenHelper> mIndexFileOpener MOZ_GUARDED_BY(sLock);
+ RefPtr<FileOpenHelper> mJournalFileOpener MOZ_GUARDED_BY(sLock);
+ RefPtr<FileOpenHelper> mTmpFileOpener MOZ_GUARDED_BY(sLock);
+
+ // Directory enumerator used when building and updating index.
+ nsCOMPtr<nsIDirectoryEnumerator> mDirEnumerator MOZ_GUARDED_BY(sLock);
+
+ // Main index hashtable.
+ nsTHashtable<CacheIndexEntry> mIndex MOZ_GUARDED_BY(sLock);
+
+ // We cannot add, remove or change any entry in mIndex in states READING and
+ // WRITING. We track all changes in mPendingUpdates during these states.
+ nsTHashtable<CacheIndexEntryUpdate> mPendingUpdates MOZ_GUARDED_BY(sLock);
+
+ // Contains information statistics for mIndex + mPendingUpdates.
+ CacheIndexStats mIndexStats MOZ_GUARDED_BY(sLock);
+
+ // When reading journal, we must first parse the whole file and apply the
+ // changes iff the journal was read successfully. mTmpJournal is used to store
+ // entries from the journal file. We throw away all these entries if parsing
+ // of the journal fails or the hash does not match.
+ nsTHashtable<CacheIndexEntry> mTmpJournal MOZ_GUARDED_BY(sLock);
+
+ // FrecencyArray maintains order of entry records for eviction. Ideally, the
+ // records would be ordered by frecency all the time, but since this would be
+ // quite expensive, we allow certain amount of entries to be out of order.
+ // When the frecency is updated the new value is always bigger than the old
+ // one. Instead of keeping updated entries at the same position, we move them
+ // at the end of the array. This protects recently updated entries from
+ // eviction. The array is sorted once we hit the limit of maximum unsorted
+ // entries.
+ class FrecencyArray {
+ class Iterator {
+ public:
+ explicit Iterator(nsTArray<RefPtr<CacheIndexRecordWrapper>>* aRecs)
+ : mRecs(aRecs), mIdx(0) {
+ while (!Done() && !(*mRecs)[mIdx]) {
+ mIdx++;
+ }
+ }
+
+ bool Done() const { return mIdx == mRecs->Length(); }
+
+ CacheIndexRecordWrapper* Get() const {
+ MOZ_ASSERT(!Done());
+ return (*mRecs)[mIdx];
+ }
+
+ void Next() {
+ MOZ_ASSERT(!Done());
+ ++mIdx;
+ while (!Done() && !(*mRecs)[mIdx]) {
+ mIdx++;
+ }
+ }
+
+ private:
+ nsTArray<RefPtr<CacheIndexRecordWrapper>>* mRecs;
+ uint32_t mIdx;
+ };
+
+ public:
+ Iterator Iter() { return Iterator(&mRecs); }
+
+ FrecencyArray() = default;
+
+ // Methods used by CacheIndexEntryAutoManage to keep the array up to date.
+ void AppendRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void RemoveRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void ReplaceRecord(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void SortIfNeeded(const StaticMutexAutoLock& aProofOfLock);
+ bool RecordExistedUnlocked(CacheIndexRecordWrapper* aRecord);
+
+ size_t Length() const { return mRecs.Length() - mRemovedElements; }
+ void Clear(const StaticMutexAutoLock& aProofOfLock) { mRecs.Clear(); }
+
+ private:
+ friend class CacheIndex;
+
+ nsTArray<RefPtr<CacheIndexRecordWrapper>> mRecs;
+ uint32_t mUnsortedElements{0};
+ // Instead of removing elements from the array immediately, we null them out
+ // and the iterator skips them when accessing the array. The null pointers
+ // are placed at the end during sorting and we strip them out all at once.
+ // This saves moving a lot of memory in nsTArray::RemoveElementsAt.
+ uint32_t mRemovedElements{0};
+ };
+
+ FrecencyArray mFrecencyArray MOZ_GUARDED_BY(sLock);
+
+ nsTArray<CacheIndexIterator*> mIterators MOZ_GUARDED_BY(sLock);
+
+ // This flag is true iff we are between CacheStorageService:Clear() and
+ // processing all contexts to be evicted. It will make UI to show
+ // "calculating" instead of any intermediate cache size.
+ bool mAsyncGetDiskConsumptionBlocked MOZ_GUARDED_BY(sLock){false};
+
+ class DiskConsumptionObserver : public Runnable {
+ public:
+ static DiskConsumptionObserver* Init(
+ nsICacheStorageConsumptionObserver* aObserver) {
+ nsWeakPtr observer = do_GetWeakReference(aObserver);
+ if (!observer) return nullptr;
+
+ return new DiskConsumptionObserver(observer);
+ }
+
+ void OnDiskConsumption(int64_t aSize) {
+ mSize = aSize;
+ NS_DispatchToMainThread(this);
+ }
+
+ private:
+ explicit DiskConsumptionObserver(nsWeakPtr const& aWeakObserver)
+ : Runnable("net::CacheIndex::DiskConsumptionObserver"),
+ mObserver(aWeakObserver),
+ mSize(0) {}
+ virtual ~DiskConsumptionObserver() {
+ if (mObserver && !NS_IsMainThread()) {
+ NS_ReleaseOnMainThread("DiskConsumptionObserver::mObserver",
+ mObserver.forget());
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsICacheStorageConsumptionObserver> observer =
+ do_QueryReferent(mObserver);
+
+ mObserver = nullptr;
+
+ if (observer) {
+ observer->OnNetworkCacheDiskConsumption(mSize);
+ }
+
+ return NS_OK;
+ }
+
+ nsWeakPtr mObserver;
+ int64_t mSize;
+ };
+
+ // List of async observers that want to get disk consumption information
+ nsTArray<RefPtr<DiskConsumptionObserver>> mDiskConsumptionObservers
+ MOZ_GUARDED_BY(sLock);
+
+ // Number of bytes written to the cache since the last telemetry report
+ uint64_t mTotalBytesWritten MOZ_GUARDED_BY(sLock){0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheIndexContextIterator.cpp b/netwerk/cache2/CacheIndexContextIterator.cpp
new file mode 100644
index 0000000000..a523b21915
--- /dev/null
+++ b/netwerk/cache2/CacheIndexContextIterator.cpp
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheIndexContextIterator.h"
+#include "CacheIndex.h"
+#include "nsString.h"
+
+namespace mozilla::net {
+
+CacheIndexContextIterator::CacheIndexContextIterator(CacheIndex* aIndex,
+ bool aAddNew,
+ nsILoadContextInfo* aInfo)
+ : CacheIndexIterator(aIndex, aAddNew), mInfo(aInfo) {}
+
+void CacheIndexContextIterator::AddRecord(
+ CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
+ if (CacheIndexEntry::RecordMatchesLoadContextInfo(aRecord, mInfo)) {
+ CacheIndexIterator::AddRecord(aRecord, aProofOfLock);
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheIndexContextIterator.h b/netwerk/cache2/CacheIndexContextIterator.h
new file mode 100644
index 0000000000..f9c19409e7
--- /dev/null
+++ b/netwerk/cache2/CacheIndexContextIterator.h
@@ -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/. */
+
+#ifndef CacheIndexContextIterator__h__
+#define CacheIndexContextIterator__h__
+
+#include "CacheIndexIterator.h"
+
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheIndexContextIterator : public CacheIndexIterator {
+ public:
+ CacheIndexContextIterator(CacheIndex* aIndex, bool aAddNew,
+ nsILoadContextInfo* aInfo);
+ virtual ~CacheIndexContextIterator() = default;
+
+ private:
+ virtual void AddRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock) override;
+
+ nsCOMPtr<nsILoadContextInfo> mInfo;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheIndexIterator.cpp b/netwerk/cache2/CacheIndexIterator.cpp
new file mode 100644
index 0000000000..5f1a78470c
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.cpp
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheIndexIterator.h"
+#include "CacheIndex.h"
+#include "nsString.h"
+#include "mozilla/DebugOnly.h"
+
+namespace mozilla::net {
+
+CacheIndexIterator::CacheIndexIterator(CacheIndex* aIndex, bool aAddNew)
+ : mStatus(NS_OK), mIndex(aIndex), mAddNew(aAddNew) {
+ LOG(("CacheIndexIterator::CacheIndexIterator() [this=%p]", this));
+}
+
+CacheIndexIterator::~CacheIndexIterator() {
+ LOG(("CacheIndexIterator::~CacheIndexIterator() [this=%p]", this));
+
+ StaticMutexAutoLock lock(CacheIndex::sLock);
+ ClearRecords(lock);
+ CloseInternal(NS_ERROR_NOT_AVAILABLE);
+}
+
+nsresult CacheIndexIterator::GetNextHash(SHA1Sum::Hash* aHash) {
+ LOG(("CacheIndexIterator::GetNextHash() [this=%p]", this));
+
+ StaticMutexAutoLock lock(CacheIndex::sLock);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (!mRecords.Length()) {
+ CloseInternal(NS_ERROR_NOT_AVAILABLE);
+ return mStatus;
+ }
+
+ memcpy(aHash, mRecords.PopLastElement()->Get()->mHash, sizeof(SHA1Sum::Hash));
+
+ return NS_OK;
+}
+
+nsresult CacheIndexIterator::Close() {
+ LOG(("CacheIndexIterator::Close() [this=%p]", this));
+
+ StaticMutexAutoLock lock(CacheIndex::sLock);
+
+ return CloseInternal(NS_ERROR_NOT_AVAILABLE);
+}
+
+nsresult CacheIndexIterator::CloseInternal(nsresult aStatus) {
+ LOG(("CacheIndexIterator::CloseInternal() [this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(aStatus)));
+
+ // Make sure status will be a failure
+ MOZ_ASSERT(NS_FAILED(aStatus));
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = NS_ERROR_UNEXPECTED;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CacheIndex::sLock.AssertCurrentThreadOwns();
+ DebugOnly<bool> removed = mIndex->mIterators.RemoveElement(this);
+ MOZ_ASSERT(removed);
+ mStatus = aStatus;
+
+ return NS_OK;
+}
+
+void CacheIndexIterator::ClearRecords(const StaticMutexAutoLock& aProofOfLock) {
+ mRecords.Clear();
+}
+
+void CacheIndexIterator::AddRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock) {
+ LOG(("CacheIndexIterator::AddRecord() [this=%p, record=%p]", this, aRecord));
+
+ mRecords.AppendElement(aRecord);
+}
+
+bool CacheIndexIterator::RemoveRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock) {
+ LOG(("CacheIndexIterator::RemoveRecord() [this=%p, record=%p]", this,
+ aRecord));
+
+ return mRecords.RemoveElement(aRecord);
+}
+
+bool CacheIndexIterator::ReplaceRecord(
+ CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock) {
+ LOG(
+ ("CacheIndexIterator::ReplaceRecord() [this=%p, oldRecord=%p, "
+ "newRecord=%p]",
+ this, aOldRecord, aNewRecord));
+
+ if (RemoveRecord(aOldRecord, aProofOfLock)) {
+ AddRecord(aNewRecord, aProofOfLock);
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheIndexIterator.h b/netwerk/cache2/CacheIndexIterator.h
new file mode 100644
index 0000000000..2f9aedb659
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.h
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheIndexIterator__h__
+#define CacheIndexIterator__h__
+
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticMutex.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheIndex;
+class CacheIndexRecordWrapper;
+
+class CacheIndexIterator {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheIndexIterator)
+
+ CacheIndexIterator(CacheIndex* aIndex, bool aAddNew);
+
+ protected:
+ virtual ~CacheIndexIterator();
+
+ public:
+ // Returns a hash of a next entry. If there is no entry NS_ERROR_NOT_AVAILABLE
+ // is returned and the iterator is closed. Other error is returned when the
+ // iterator is closed for other reason, e.g. shutdown.
+ nsresult GetNextHash(SHA1Sum::Hash* aHash);
+
+ // Closes the iterator. This means the iterator is removed from the list of
+ // iterators in CacheIndex.
+ nsresult Close();
+
+ protected:
+ friend class CacheIndex;
+
+ nsresult CloseInternal(nsresult aStatus);
+
+ bool ShouldBeNewAdded() { return mAddNew; }
+ virtual void AddRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ bool RemoveRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ bool ReplaceRecord(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void ClearRecords(const StaticMutexAutoLock& aProofOfLock);
+
+ nsresult mStatus;
+ RefPtr<CacheIndex> mIndex;
+ nsTArray<RefPtr<CacheIndexRecordWrapper>> mRecords;
+ bool mAddNew;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheLog.cpp b/netwerk/cache2/CacheLog.cpp
new file mode 100644
index 0000000000..ea81ff2a19
--- /dev/null
+++ b/netwerk/cache2/CacheLog.cpp
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+
+namespace mozilla::net {
+
+// Log module for cache2 (2013) cache implementation logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=cache2:5
+// set MOZ_LOG_FILE=network.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file network.log.
+LazyLogModule gCache2Log("cache2");
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheLog.h b/netwerk/cache2/CacheLog.h
new file mode 100644
index 0000000000..f4117ea4f7
--- /dev/null
+++ b/netwerk/cache2/CacheLog.h
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Cache2Log__h__
+#define Cache2Log__h__
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace net {
+
+extern LazyLogModule gCache2Log;
+#define LOG(x) MOZ_LOG(gCache2Log, mozilla::LogLevel::Debug, x)
+#define LOG_ENABLED() MOZ_LOG_TEST(gCache2Log, mozilla::LogLevel::Debug)
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheObserver.cpp b/netwerk/cache2/CacheObserver.cpp
new file mode 100644
index 0000000000..59a798d151
--- /dev/null
+++ b/netwerk/cache2/CacheObserver.cpp
@@ -0,0 +1,261 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheObserver.h"
+
+#include "CacheStorageService.h"
+#include "CacheFileIOManager.h"
+#include "LoadContextInfo.h"
+#include "nsICacheStorage.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TimeStamp.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "prsystem.h"
+#include <time.h>
+#include <math.h>
+#include "nsIUserIdleService.h"
+
+namespace mozilla::net {
+
+StaticRefPtr<CacheObserver> CacheObserver::sSelf;
+
+static float const kDefaultHalfLifeHours = 24.0F; // 24 hours
+float CacheObserver::sHalfLifeHours = kDefaultHalfLifeHours;
+
+// The default value will be overwritten as soon as the correct smart size is
+// calculated by CacheFileIOManager::UpdateSmartCacheSize(). It's limited to 1GB
+// just for case the size is never calculated which might in theory happen if
+// GetDiskSpaceAvailable() always fails.
+Atomic<uint32_t, Relaxed> CacheObserver::sSmartDiskCacheCapacity(1024 * 1024);
+
+Atomic<PRIntervalTime> CacheObserver::sShutdownDemandedTime(
+ PR_INTERVAL_NO_TIMEOUT);
+
+NS_IMPL_ISUPPORTS(CacheObserver, nsIObserver, nsISupportsWeakReference)
+
+// static
+nsresult CacheObserver::Init() {
+ if (IsNeckoChild()) {
+ return NS_OK;
+ }
+
+ if (sSelf) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ sSelf = new CacheObserver();
+
+ obs->AddObserver(sSelf, "prefservice:after-app-defaults", true);
+ obs->AddObserver(sSelf, "profile-do-change", true);
+ obs->AddObserver(sSelf, "profile-before-change", true);
+ obs->AddObserver(sSelf, "xpcom-shutdown", true);
+ obs->AddObserver(sSelf, "last-pb-context-exited", true);
+ obs->AddObserver(sSelf, "memory-pressure", true);
+ obs->AddObserver(sSelf, "browser-delayed-startup-finished", true);
+ obs->AddObserver(sSelf, OBSERVER_TOPIC_IDLE_DAILY, true);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheObserver::Shutdown() {
+ if (!sSelf) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ sSelf = nullptr;
+ return NS_OK;
+}
+
+void CacheObserver::AttachToPreferences() {
+ mozilla::Preferences::GetComplex(
+ "browser.cache.disk.parent_directory", NS_GET_IID(nsIFile),
+ getter_AddRefs(mCacheParentDirectoryOverride));
+
+ sHalfLifeHours = std::max(
+ 0.01F, std::min(1440.0F, mozilla::Preferences::GetFloat(
+ "browser.cache.frecency_half_life_hours",
+ kDefaultHalfLifeHours)));
+}
+
+// static
+uint32_t CacheObserver::MemoryCacheCapacity() {
+ if (StaticPrefs::browser_cache_memory_capacity() >= 0) {
+ return StaticPrefs::browser_cache_memory_capacity();
+ }
+
+ // Cache of the calculated memory capacity based on the system memory size in
+ // KB (C++11 guarantees local statics will be initialized once and in a
+ // thread-safe way.)
+ static int32_t sAutoMemoryCacheCapacity = ([] {
+ uint64_t bytes = PR_GetPhysicalMemorySize();
+ // If getting the physical memory failed, arbitrarily assume
+ // 32 MB of RAM. We use a low default to have a reasonable
+ // size on all the devices we support.
+ if (bytes == 0) {
+ bytes = 32 * 1024 * 1024;
+ }
+ // Conversion from unsigned int64_t to double doesn't work on all platforms.
+ // We need to truncate the value at INT64_MAX to make sure we don't
+ // overflow.
+ if (bytes > INT64_MAX) {
+ bytes = INT64_MAX;
+ }
+ uint64_t kbytes = bytes >> 10;
+ double kBytesD = double(kbytes);
+ double x = log(kBytesD) / log(2.0) - 14;
+
+ int32_t capacity = 0;
+ if (x > 0) {
+ // 0.1 is added here for rounding
+ capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1);
+ if (capacity > 32) {
+ capacity = 32;
+ }
+ capacity <<= 10;
+ }
+ return capacity;
+ })();
+
+ return sAutoMemoryCacheCapacity;
+}
+
+// static
+void CacheObserver::SetSmartDiskCacheCapacity(uint32_t aCapacity) {
+ sSmartDiskCacheCapacity = aCapacity;
+}
+
+// static
+uint32_t CacheObserver::DiskCacheCapacity() {
+ return SmartCacheSizeEnabled() ? sSmartDiskCacheCapacity
+ : StaticPrefs::browser_cache_disk_capacity();
+}
+
+// static
+void CacheObserver::ParentDirOverride(nsIFile** aDir) {
+ if (NS_WARN_IF(!aDir)) return;
+
+ *aDir = nullptr;
+
+ if (!sSelf) return;
+ if (!sSelf->mCacheParentDirectoryOverride) return;
+
+ sSelf->mCacheParentDirectoryOverride->Clone(aDir);
+}
+
+// static
+bool CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk) {
+ // If custom limit is set, check it.
+ int64_t preferredLimit =
+ aUsingDisk ? MaxDiskEntrySize() : MaxMemoryEntrySize();
+
+ // do not convert to bytes when the limit is -1, which means no limit
+ if (preferredLimit > 0) {
+ preferredLimit <<= 10;
+ }
+
+ if (preferredLimit != -1 && aSize > preferredLimit) return true;
+
+ // Otherwise (or when in the custom limit), check limit based on the global
+ // limit. It's 1/8 of the respective capacity.
+ int64_t derivedLimit =
+ aUsingDisk ? DiskCacheCapacity() : MemoryCacheCapacity();
+ derivedLimit <<= (10 - 3);
+
+ return aSize > derivedLimit;
+}
+
+// static
+bool CacheObserver::IsPastShutdownIOLag() {
+#ifdef DEBUG
+ return false;
+#else
+ if (sShutdownDemandedTime == PR_INTERVAL_NO_TIMEOUT ||
+ MaxShutdownIOLag() == UINT32_MAX) {
+ return false;
+ }
+
+ static const PRIntervalTime kMaxShutdownIOLag =
+ PR_SecondsToInterval(MaxShutdownIOLag());
+
+ if ((PR_IntervalNow() - sShutdownDemandedTime) > kMaxShutdownIOLag) {
+ return true;
+ }
+
+ return false;
+#endif
+}
+
+NS_IMETHODIMP
+CacheObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "prefservice:after-app-defaults")) {
+ CacheFileIOManager::Init();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-do-change")) {
+ AttachToPreferences();
+ CacheFileIOManager::Init();
+ CacheFileIOManager::OnProfile();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-change-net-teardown") ||
+ !strcmp(aTopic, "profile-before-change") ||
+ !strcmp(aTopic, "xpcom-shutdown")) {
+ if (sShutdownDemandedTime == PR_INTERVAL_NO_TIMEOUT) {
+ sShutdownDemandedTime = PR_IntervalNow();
+ }
+
+ RefPtr<CacheStorageService> service = CacheStorageService::Self();
+ if (service) {
+ service->Shutdown();
+ }
+
+ CacheFileIOManager::Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "last-pb-context-exited")) {
+ RefPtr<CacheStorageService> service = CacheStorageService::Self();
+ if (service) {
+ service->DropPrivateBrowsingEntries();
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "memory-pressure")) {
+ RefPtr<CacheStorageService> service = CacheStorageService::Self();
+ if (service) {
+ service->PurgeFromMemory(nsICacheStorageService::PURGE_EVERYTHING);
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "browser-delayed-startup-finished")) {
+ CacheFileIOManager::OnDelayedStartupFinished();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
+ CacheFileIOManager::OnIdleDaily();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false, "Missing observer handler");
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheObserver.h b/netwerk/cache2/CacheObserver.h
new file mode 100644
index 0000000000..633af7eec5
--- /dev/null
+++ b/netwerk/cache2/CacheObserver.h
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheObserver__h__
+#define CacheObserver__h__
+
+#include "nsIObserver.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPtr.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+class CacheObserver : public nsIObserver, public nsSupportsWeakReference {
+ virtual ~CacheObserver() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static nsresult Init();
+ static nsresult Shutdown();
+ static CacheObserver* Self() { return sSelf; }
+
+ // Access to preferences
+ static bool UseDiskCache() {
+ return StaticPrefs::browser_cache_disk_enable();
+ }
+ static bool UseMemoryCache() {
+ return StaticPrefs::browser_cache_memory_enable();
+ }
+ static uint32_t MetadataMemoryLimit() // result in kilobytes.
+ {
+ return StaticPrefs::browser_cache_disk_metadata_memory_limit();
+ }
+ static uint32_t MemoryCacheCapacity(); // result in kilobytes.
+ static uint32_t DiskCacheCapacity(); // result in kilobytes.
+ static void SetSmartDiskCacheCapacity(uint32_t); // parameter in kilobytes.
+ static uint32_t DiskFreeSpaceSoftLimit() // result in kilobytes.
+ {
+ return StaticPrefs::browser_cache_disk_free_space_soft_limit();
+ }
+ static uint32_t DiskFreeSpaceHardLimit() // result in kilobytes.
+ {
+ return StaticPrefs::browser_cache_disk_free_space_hard_limit();
+ }
+ static bool SmartCacheSizeEnabled() {
+ return StaticPrefs::browser_cache_disk_smart_size_enabled();
+ }
+ static uint32_t PreloadChunkCount() {
+ return StaticPrefs::browser_cache_disk_preload_chunk_count();
+ }
+ static uint32_t MaxMemoryEntrySize() // result in kilobytes.
+ {
+ return StaticPrefs::browser_cache_memory_max_entry_size();
+ }
+ static uint32_t MaxDiskEntrySize() // result in kilobytes.
+ {
+ return StaticPrefs::browser_cache_disk_max_entry_size();
+ }
+ static uint32_t MaxDiskChunksMemoryUsage(
+ bool aPriority) // result in kilobytes.
+ {
+ return aPriority
+ ? StaticPrefs::
+ browser_cache_disk_max_priority_chunks_memory_usage()
+ : StaticPrefs::browser_cache_disk_max_chunks_memory_usage();
+ }
+ static uint32_t HalfLifeSeconds() { return sHalfLifeHours * 60.0F * 60.0F; }
+ static bool ClearCacheOnShutdown() {
+ return StaticPrefs::privacy_sanitize_sanitizeOnShutdown() &&
+ StaticPrefs::privacy_clearOnShutdown_cache();
+ }
+ static void ParentDirOverride(nsIFile** aDir);
+
+ static bool EntryIsTooBig(int64_t aSize, bool aUsingDisk);
+
+ static uint32_t MaxShutdownIOLag() {
+ return StaticPrefs::browser_cache_max_shutdown_io_lag();
+ }
+ static bool IsPastShutdownIOLag();
+
+ static bool ShuttingDown() {
+ return sShutdownDemandedTime != PR_INTERVAL_NO_TIMEOUT;
+ }
+
+ private:
+ static StaticRefPtr<CacheObserver> sSelf;
+
+ void AttachToPreferences();
+
+ static int32_t sAutoMemoryCacheCapacity;
+ static Atomic<uint32_t, Relaxed> sSmartDiskCacheCapacity;
+ static float sHalfLifeHours;
+ static Atomic<PRIntervalTime> sShutdownDemandedTime;
+
+ // Non static properties, accessible via sSelf
+ nsCOMPtr<nsIFile> mCacheParentDirectoryOverride;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CachePurgeLock.cpp b/netwerk/cache2/CachePurgeLock.cpp
new file mode 100644
index 0000000000..6328891669
--- /dev/null
+++ b/netwerk/cache2/CachePurgeLock.cpp
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CachePurgeLock.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsAppRunner.h"
+#include "mozilla/MultiInstanceLock.h"
+#include "nsLocalFile.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(CachePurgeLock, nsICachePurgeLock)
+
+static nsresult PrepareLockArguments(const nsACString& profileName,
+ nsCString& lockName,
+ nsString& appDirPath) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> appFile = mozilla::GetNormalizedAppFile(nullptr);
+ if (!appFile) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIFile> appDirFile;
+ rv = appFile->GetParent(getter_AddRefs(appDirFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appDirFile->GetPath(appDirPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ lockName = profileName;
+ lockName.Append("-cachePurge");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CachePurgeLock::GetLockFile(const nsACString& profileName, nsIFile** aResult) {
+ nsresult rv;
+ nsCString lockName;
+ nsString appDirPath;
+ rv = PrepareLockArguments(profileName, lockName, appDirPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString filePath;
+ if (!GetMultiInstanceLockFileName(lockName.get(), appDirPath.get(),
+ filePath)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIFile> lockFile = new nsLocalFile();
+ rv = lockFile->InitWithNativePath(filePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ lockFile.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CachePurgeLock::Lock(const nsACString& profileName) {
+ nsresult rv;
+ if (mLock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
+ // Lock is already open.
+ return NS_OK;
+ }
+
+ nsCString lockName;
+ nsString appDirPath;
+ rv = PrepareLockArguments(profileName, lockName, appDirPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mLock = mozilla::OpenMultiInstanceLock(lockName.get(), appDirPath.get());
+ if (mLock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CachePurgeLock::IsOtherInstanceRunning(bool* aResult) {
+ if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) {
+ return NS_ERROR_SERVICE_NOT_AVAILABLE;
+ }
+
+ if (mLock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ bool rv = mozilla::IsOtherInstanceRunning(mLock, aResult);
+ NS_ENSURE_TRUE(rv, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CachePurgeLock::Unlock() {
+ if (mLock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
+ // Lock is already released.
+ return NS_OK;
+ }
+
+ mozilla::ReleaseMultiInstanceLock(mLock);
+ mLock = MULTI_INSTANCE_LOCK_HANDLE_ERROR;
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CachePurgeLock.h b/netwerk/cache2/CachePurgeLock.h
new file mode 100644
index 0000000000..464b4e681d
--- /dev/null
+++ b/netwerk/cache2/CachePurgeLock.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_CachePurgeLock_h__
+#define mozilla_net_CachePurgeLock_h__
+
+#include "nsICachePurgeLock.h"
+#include "mozilla/MultiInstanceLock.h"
+
+namespace mozilla::net {
+
+class CachePurgeLock : public nsICachePurgeLock {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEPURGELOCK
+ private:
+ virtual ~CachePurgeLock() = default;
+
+ MultiInstLockHandle mLock = MULTI_INSTANCE_LOCK_HANDLE_ERROR;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_CachePurgeLock_h__
diff --git a/netwerk/cache2/CacheStorage.cpp b/netwerk/cache2/CacheStorage.cpp
new file mode 100644
index 0000000000..b4177430a2
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheStorage.h"
+#include "CacheStorageService.h"
+#include "CacheEntry.h"
+#include "CacheObserver.h"
+
+#include "nsICacheEntryDoomCallback.h"
+
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(CacheStorage, nsICacheStorage)
+
+CacheStorage::CacheStorage(nsILoadContextInfo* aInfo, bool aAllowDisk,
+ bool aSkipSizeCheck, bool aPinning)
+ : mLoadContextInfo(aInfo ? GetLoadContextInfo(aInfo) : nullptr),
+ mWriteToDisk(aAllowDisk),
+ mSkipSizeCheck(aSkipSizeCheck),
+ mPinning(aPinning) {}
+
+NS_IMETHODIMP CacheStorage::AsyncOpenURI(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ uint32_t aFlags,
+ nsICacheEntryOpenCallback* aCallback) {
+ if (!CacheStorageService::Self()) return NS_ERROR_NOT_INITIALIZED;
+
+ if (MOZ_UNLIKELY(!CacheObserver::UseDiskCache()) && mWriteToDisk &&
+ !(aFlags & OPEN_INTERCEPTED)) {
+ aCallback->OnCacheEntryAvailable(nullptr, false, NS_ERROR_NOT_AVAILABLE);
+ return NS_OK;
+ }
+
+ if (MOZ_UNLIKELY(!CacheObserver::UseMemoryCache()) && !mWriteToDisk &&
+ !(aFlags & OPEN_INTERCEPTED)) {
+ aCallback->OnCacheEntryAvailable(nullptr, false, NS_ERROR_NOT_AVAILABLE);
+ return NS_OK;
+ }
+
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString asciiSpec;
+ rv = noRefURI->GetAsciiSpec(asciiSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CacheEntryHandle> entry;
+ rv = CacheStorageService::Self()->AddStorageEntry(
+ this, asciiSpec, aIdExtension, aFlags, getter_AddRefs(entry));
+ if (NS_FAILED(rv)) {
+ aCallback->OnCacheEntryAvailable(nullptr, false, rv);
+ return NS_OK;
+ }
+
+ // May invoke the callback synchronously
+ entry->Entry()->AsyncOpen(aCallback, aFlags);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorage::OpenTruncate(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ nsICacheEntry** aCacheEntry) {
+ if (!CacheStorageService::Self()) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString asciiSpec;
+ rv = noRefURI->GetAsciiSpec(asciiSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CacheEntryHandle> handle;
+ rv = CacheStorageService::Self()->AddStorageEntry(
+ this, asciiSpec, aIdExtension,
+ nsICacheStorage::OPEN_TRUNCATE, // replace any existing one
+ getter_AddRefs(handle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Just open w/o callback, similar to nsICacheEntry.recreate().
+ handle->Entry()->AsyncOpen(nullptr, OPEN_TRUNCATE);
+
+ // Return a write handler, consumer is supposed to fill in the entry.
+ RefPtr<CacheEntryHandle> writeHandle = handle->Entry()->NewWriteHandle();
+ writeHandle.forget(aCacheEntry);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorage::Exists(nsIURI* aURI, const nsACString& aIdExtension,
+ bool* aResult) {
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aResult);
+
+ if (!CacheStorageService::Self()) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString asciiSpec;
+ rv = noRefURI->GetAsciiSpec(asciiSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CacheStorageService::Self()->CheckStorageEntry(this, asciiSpec,
+ aIdExtension, aResult);
+}
+
+NS_IMETHODIMP
+CacheStorage::GetCacheIndexEntryAttrs(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ bool* aHasAltData, uint32_t* aSizeInKB) {
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aHasAltData);
+ NS_ENSURE_ARG(aSizeInKB);
+ if (!CacheStorageService::Self()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString asciiSpec;
+ rv = noRefURI->GetAsciiSpec(asciiSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CacheStorageService::Self()->GetCacheIndexEntryAttrs(
+ this, asciiSpec, aIdExtension, aHasAltData, aSizeInKB);
+}
+
+NS_IMETHODIMP CacheStorage::AsyncDoomURI(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ nsICacheEntryDoomCallback* aCallback) {
+ if (!CacheStorageService::Self()) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString asciiSpec;
+ rv = noRefURI->GetAsciiSpec(asciiSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CacheStorageService::Self()->DoomStorageEntry(this, asciiSpec,
+ aIdExtension, aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorage::AsyncEvictStorage(
+ nsICacheEntryDoomCallback* aCallback) {
+ if (!CacheStorageService::Self()) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv =
+ CacheStorageService::Self()->DoomStorageEntries(this, aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
+ bool aVisitEntries) {
+ LOG(("CacheStorage::AsyncVisitStorage [this=%p, cb=%p, disk=%d]", this,
+ aVisitor, (bool)mWriteToDisk));
+ if (!CacheStorageService::Self()) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = CacheStorageService::Self()->WalkStorageEntries(
+ this, aVisitEntries, aVisitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheStorage.h b/netwerk/cache2/CacheStorage.h
new file mode 100644
index 0000000000..f4db3e2a96
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.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 CacheStorage__h__
+#define CacheStorage__h__
+
+#include "nsICacheStorage.h"
+#include "CacheEntry.h"
+#include "LoadContextInfo.h"
+
+#include "nsILoadContextInfo.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+// This dance is needed to make CacheEntryTable declarable-only in headers
+// w/o exporting CacheEntry.h file to make nsNetModule.cpp compilable.
+using TCacheEntryTable = nsRefPtrHashtable<nsCStringHashKey, CacheEntry>;
+class CacheEntryTable : public TCacheEntryTable {
+ public:
+ enum EType { MEMORY_ONLY, ALL_ENTRIES };
+
+ explicit CacheEntryTable(EType aType) : mType(aType) {}
+ EType Type() const { return mType; }
+
+ private:
+ EType const mType;
+ CacheEntryTable() = delete;
+};
+
+class CacheStorage : public nsICacheStorage {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHESTORAGE
+
+ public:
+ CacheStorage(nsILoadContextInfo* aInfo, bool aAllowDisk, bool aSkipSizeCheck,
+ bool aPinning);
+
+ protected:
+ virtual ~CacheStorage() = default;
+
+ RefPtr<LoadContextInfo> mLoadContextInfo;
+ bool mWriteToDisk : 1;
+ bool mSkipSizeCheck : 1;
+ bool mPinning : 1;
+
+ public:
+ nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
+ bool WriteToDisk() const {
+ return mWriteToDisk &&
+ (!mLoadContextInfo || !mLoadContextInfo->IsPrivate());
+ }
+ bool SkipSizeCheck() const { return mSkipSizeCheck; }
+ bool Pinning() const { return mPinning; }
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp
new file mode 100644
index 0000000000..ff5cc51d51
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -0,0 +1,2397 @@
+/* -*- 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 "CacheLog.h"
+#include "CacheStorageService.h"
+#include <iterator>
+#include "CacheFileIOManager.h"
+#include "CacheObserver.h"
+#include "CacheIndex.h"
+#include "CacheIndexIterator.h"
+#include "CacheStorage.h"
+#include "CacheEntry.h"
+#include "CacheFileUtils.h"
+
+#include "ErrorList.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsIObserverService.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsINetworkPredictor.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Services.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla::net {
+
+namespace {
+
+void AppendMemoryStorageTag(nsAutoCString& key) {
+ // Using DEL as the very last ascii-7 character we can use in the list of
+ // attributes
+ key.Append('\x7f');
+ key.Append(',');
+}
+
+} // namespace
+
+// Not defining as static or class member of CacheStorageService since
+// it would otherwise need to include CacheEntry.h and that then would
+// need to be exported to make nsNetModule.cpp compilable.
+using GlobalEntryTables = nsClassHashtable<nsCStringHashKey, CacheEntryTable>;
+
+/**
+ * Keeps tables of entries. There is one entries table for each distinct load
+ * context type. The distinction is based on following load context info
+ * states: <isPrivate|isAnon|inIsolatedMozBrowser> which builds a mapping
+ * key.
+ *
+ * Thread-safe to access, protected by the service mutex.
+ */
+static GlobalEntryTables* sGlobalEntryTables;
+
+CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags) {
+ StoreFlags(aFlags);
+}
+
+void CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) {
+ if (!(LoadFlags() & DONT_REPORT) && CacheStorageService::Self()) {
+ CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
+ }
+}
+
+CacheStorageService::MemoryPool::MemoryPool(EType aType) : mType(aType) {}
+
+CacheStorageService::MemoryPool::~MemoryPool() {
+ if (mMemorySize != 0) {
+ NS_ERROR(
+ "Network cache reported memory consumption is not at 0, probably "
+ "leaking?");
+ }
+}
+
+uint32_t CacheStorageService::MemoryPool::Limit() const {
+ uint32_t limit = 0;
+
+ switch (mType) {
+ case DISK:
+ limit = CacheObserver::MetadataMemoryLimit();
+ break;
+ case MEMORY:
+ limit = CacheObserver::MemoryCacheCapacity();
+ break;
+ default:
+ MOZ_CRASH("Bad pool type");
+ }
+
+ static const uint32_t kMaxLimit = 0x3FFFFF;
+ if (limit > kMaxLimit) {
+ LOG((" a memory limit (%u) is unexpectedly high, clipping to %u", limit,
+ kMaxLimit));
+ limit = kMaxLimit;
+ }
+
+ return limit << 10;
+}
+
+NS_IMPL_ISUPPORTS(CacheStorageService, nsICacheStorageService,
+ nsIMemoryReporter, nsITimerCallback, nsICacheTesting,
+ nsINamed)
+
+CacheStorageService* CacheStorageService::sSelf = nullptr;
+
+CacheStorageService::CacheStorageService() {
+ CacheFileIOManager::Init();
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!sSelf);
+
+ sSelf = this;
+ sGlobalEntryTables = new GlobalEntryTables();
+
+ RegisterStrongMemoryReporter(this);
+}
+
+CacheStorageService::~CacheStorageService() {
+ LOG(("CacheStorageService::~CacheStorageService"));
+ sSelf = nullptr;
+}
+
+void CacheStorageService::Shutdown() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mShutdown) return;
+
+ LOG(("CacheStorageService::Shutdown - start"));
+
+ mShutdown = true;
+
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("net::CacheStorageService::ShutdownBackground", this,
+ &CacheStorageService::ShutdownBackground);
+ Dispatch(event);
+
+#ifdef NS_FREE_PERMANENT_DATA
+ sGlobalEntryTables->Clear();
+ delete sGlobalEntryTables;
+#endif
+ sGlobalEntryTables = nullptr;
+
+ LOG(("CacheStorageService::Shutdown - done"));
+}
+
+void CacheStorageService::ShutdownBackground() {
+ LOG(("CacheStorageService::ShutdownBackground - start"));
+
+ MOZ_ASSERT(IsOnManagementThread());
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ // Cancel purge timer to avoid leaking.
+ if (mPurgeTimer) {
+ LOG((" freeing the timer"));
+ mPurgeTimer->Cancel();
+ }
+ }
+
+#ifdef NS_FREE_PERMANENT_DATA
+ Pool(MemoryPool::EType::DISK).mManagedEntries.clear();
+ Pool(MemoryPool::EType::MEMORY).mManagedEntries.clear();
+#endif
+
+ LOG(("CacheStorageService::ShutdownBackground - done"));
+}
+
+// Internal management methods
+
+namespace {
+
+// WalkCacheRunnable
+// Base class for particular storage entries visiting
+class WalkCacheRunnable : public Runnable,
+ public CacheStorageService::EntryInfoCallback {
+ protected:
+ WalkCacheRunnable(nsICacheStorageVisitor* aVisitor, bool aVisitEntries)
+ : Runnable("net::WalkCacheRunnable"),
+ mService(CacheStorageService::Self()),
+ mCallback(aVisitor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ StoreNotifyStorage(true);
+ StoreVisitEntries(aVisitEntries);
+ }
+
+ virtual ~WalkCacheRunnable() {
+ if (mCallback) {
+ ProxyReleaseMainThread("WalkCacheRunnable::mCallback", mCallback);
+ }
+ }
+
+ RefPtr<CacheStorageService> mService;
+ nsCOMPtr<nsICacheStorageVisitor> mCallback;
+
+ uint64_t mSize{0};
+
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, (
+ (bool, NotifyStorage, 1),
+ (bool, VisitEntries, 1)
+ ))
+ // clang-format on
+
+ Atomic<bool> mCancel{false};
+};
+
+// WalkMemoryCacheRunnable
+// Responsible to visit memory storage and walk
+// all entries on it asynchronously.
+class WalkMemoryCacheRunnable : public WalkCacheRunnable {
+ public:
+ WalkMemoryCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
+ nsICacheStorageVisitor* aVisitor)
+ : WalkCacheRunnable(aVisitor, aVisitEntries) {
+ CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ nsresult Walk() { return mService->Dispatch(this); }
+
+ private:
+ NS_IMETHOD Run() override {
+ if (CacheStorageService::IsOnManagementThread()) {
+ LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this));
+ // First, walk, count and grab all entries from the storage
+
+ mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
+
+ if (!CacheStorageService::IsRunning()) return NS_ERROR_NOT_INITIALIZED;
+
+ // Count the entries to allocate the array memory all at once.
+ size_t numEntries = 0;
+ for (const auto& entries : sGlobalEntryTables->Values()) {
+ if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
+ continue;
+ }
+ numEntries += entries->Values().Count();
+ }
+ mEntryArray.SetCapacity(numEntries);
+
+ // Collect the entries.
+ for (const auto& entries : sGlobalEntryTables->Values()) {
+ if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
+ continue;
+ }
+
+ for (CacheEntry* entry : entries->Values()) {
+ MOZ_ASSERT(!entry->IsUsingDisk());
+
+ mSize += entry->GetMetadataMemoryConsumption();
+
+ int64_t size;
+ if (NS_SUCCEEDED(entry->GetDataSize(&size))) {
+ mSize += size;
+ }
+ mEntryArray.AppendElement(entry);
+ }
+ }
+
+ // Next, we dispatch to the main thread
+ } else if (NS_IsMainThread()) {
+ LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this));
+
+ if (LoadNotifyStorage()) {
+ LOG((" storage"));
+
+ uint64_t capacity = CacheObserver::MemoryCacheCapacity();
+ capacity <<= 10; // kilobytes to bytes
+
+ // Second, notify overall storage info
+ mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize, capacity,
+ nullptr);
+ if (!LoadVisitEntries()) return NS_OK; // done
+
+ StoreNotifyStorage(false);
+
+ } else {
+ LOG((" entry [left=%zu, canceled=%d]", mEntryArray.Length(),
+ (bool)mCancel));
+
+ // Third, notify each entry until depleted or canceled.
+ if (mNextEntryIdx >= mEntryArray.Length() || mCancel) {
+ mCallback->OnCacheEntryVisitCompleted();
+ return NS_OK; // done
+ }
+
+ // Grab the next entry.
+ RefPtr<CacheEntry> entry = std::move(mEntryArray[mNextEntryIdx++]);
+
+ // Invokes this->OnEntryInfo, that calls the callback with all
+ // information of the entry.
+ CacheStorageService::GetCacheEntryInfo(entry, this);
+ }
+ } else {
+ MOZ_CRASH("Bad thread");
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_DispatchToMainThread(this);
+ return NS_OK;
+ }
+
+ virtual ~WalkMemoryCacheRunnable() {
+ if (mCallback) {
+ ProxyReleaseMainThread("WalkMemoryCacheRunnable::mCallback", mCallback);
+ }
+ }
+
+ virtual void OnEntryInfo(const nsACString& aURISpec,
+ const nsACString& aIdEnhance, int64_t aDataSize,
+ int64_t aAltDataSize, uint32_t aFetchCount,
+ uint32_t aLastModifiedTime, uint32_t aExpirationTime,
+ bool aPinned, nsILoadContextInfo* aInfo) override {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), aURISpec);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ rv = mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aAltDataSize,
+ aFetchCount, aLastModifiedTime,
+ aExpirationTime, aPinned, aInfo);
+ if (NS_FAILED(rv)) {
+ LOG((" callback failed, canceling the walk"));
+ mCancel = true;
+ }
+ }
+
+ private:
+ nsCString mContextKey;
+ nsTArray<RefPtr<CacheEntry>> mEntryArray;
+ size_t mNextEntryIdx{0};
+};
+
+// WalkDiskCacheRunnable
+// Using the cache index information to get the list of files per context.
+class WalkDiskCacheRunnable : public WalkCacheRunnable {
+ public:
+ WalkDiskCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
+ nsICacheStorageVisitor* aVisitor)
+ : WalkCacheRunnable(aVisitor, aVisitEntries),
+ mLoadInfo(aLoadInfo),
+ mPass(COLLECT_STATS),
+ mCount(0) {}
+
+ nsresult Walk() {
+ // TODO, bug 998693
+ // Initial index build should be forced here so that about:cache soon
+ // after startup gives some meaningfull results.
+
+ // Dispatch to the INDEX level in hope that very recent cache entries
+ // information gets to the index list before we grab the index iterator
+ // for the first time. This tries to avoid miss of entries that has
+ // been created right before the visit is required.
+ RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+ NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
+
+ return thread->Dispatch(this, CacheIOThread::INDEX);
+ }
+
+ private:
+ // Invokes OnCacheEntryInfo callback for each single found entry.
+ // There is one instance of this class per one entry.
+ class OnCacheEntryInfoRunnable : public Runnable {
+ public:
+ explicit OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker)
+ : Runnable("net::WalkDiskCacheRunnable::OnCacheEntryInfoRunnable"),
+ mWalker(aWalker) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ rv = mWalker->mCallback->OnCacheEntryInfo(
+ uri, mIdEnhance, mDataSize, mAltDataSize, mFetchCount,
+ mLastModifiedTime, mExpirationTime, mPinned, mInfo);
+ if (NS_FAILED(rv)) {
+ mWalker->mCancel = true;
+ }
+
+ return NS_OK;
+ }
+
+ RefPtr<WalkDiskCacheRunnable> mWalker;
+
+ nsCString mURISpec;
+ nsCString mIdEnhance;
+ int64_t mDataSize{0};
+ int64_t mAltDataSize{0};
+ uint32_t mFetchCount{0};
+ uint32_t mLastModifiedTime{0};
+ uint32_t mExpirationTime{0};
+ bool mPinned{false};
+ nsCOMPtr<nsILoadContextInfo> mInfo;
+ };
+
+ NS_IMETHOD Run() override {
+ // The main loop
+ nsresult rv;
+
+ if (CacheStorageService::IsOnManagementThread()) {
+ switch (mPass) {
+ case COLLECT_STATS:
+ // Get quickly the cache stats.
+ uint32_t size;
+ rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount);
+ if (NS_FAILED(rv)) {
+ if (LoadVisitEntries()) {
+ // both onStorageInfo and onCompleted are expected
+ NS_DispatchToMainThread(this);
+ }
+ return NS_DispatchToMainThread(this);
+ }
+
+ mSize = static_cast<uint64_t>(size) << 10;
+
+ // Invoke onCacheStorageInfo with valid information.
+ NS_DispatchToMainThread(this);
+
+ if (!LoadVisitEntries()) {
+ return NS_OK; // done
+ }
+
+ mPass = ITERATE_METADATA;
+ [[fallthrough]];
+
+ case ITERATE_METADATA:
+ // Now grab the context iterator.
+ if (!mIter) {
+ rv =
+ CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter));
+ if (NS_FAILED(rv)) {
+ // Invoke onCacheEntryVisitCompleted now
+ return NS_DispatchToMainThread(this);
+ }
+ }
+
+ while (!mCancel && !CacheObserver::ShuttingDown()) {
+ if (CacheIOThread::YieldAndRerun()) return NS_OK;
+
+ SHA1Sum::Hash hash;
+ rv = mIter->GetNextHash(&hash);
+ if (NS_FAILED(rv)) break; // done (or error?)
+
+ // This synchronously invokes OnEntryInfo on this class where we
+ // redispatch to the main thread for the consumer callback.
+ CacheFileIOManager::GetEntryInfo(&hash, this);
+ }
+
+ // Invoke onCacheEntryVisitCompleted on the main thread
+ NS_DispatchToMainThread(this);
+ }
+ } else if (NS_IsMainThread()) {
+ if (LoadNotifyStorage()) {
+ nsCOMPtr<nsIFile> dir;
+ CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
+ uint64_t capacity = CacheObserver::DiskCacheCapacity();
+ capacity <<= 10; // kilobytes to bytes
+ mCallback->OnCacheStorageInfo(mCount, mSize, capacity, dir);
+ StoreNotifyStorage(false);
+ } else {
+ mCallback->OnCacheEntryVisitCompleted();
+ }
+ } else {
+ MOZ_CRASH("Bad thread");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ virtual void OnEntryInfo(const nsACString& aURISpec,
+ const nsACString& aIdEnhance, int64_t aDataSize,
+ int64_t aAltDataSize, uint32_t aFetchCount,
+ uint32_t aLastModifiedTime, uint32_t aExpirationTime,
+ bool aPinned, nsILoadContextInfo* aInfo) override {
+ // Called directly from CacheFileIOManager::GetEntryInfo.
+
+ // Invoke onCacheEntryInfo on the main thread for this entry.
+ RefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this);
+ info->mURISpec = aURISpec;
+ info->mIdEnhance = aIdEnhance;
+ info->mDataSize = aDataSize;
+ info->mAltDataSize = aAltDataSize;
+ info->mFetchCount = aFetchCount;
+ info->mLastModifiedTime = aLastModifiedTime;
+ info->mExpirationTime = aExpirationTime;
+ info->mPinned = aPinned;
+ info->mInfo = aInfo;
+
+ NS_DispatchToMainThread(info);
+ }
+
+ RefPtr<nsILoadContextInfo> mLoadInfo;
+ enum {
+ // First, we collect stats for the load context.
+ COLLECT_STATS,
+
+ // Second, if demanded, we iterate over the entries gethered
+ // from the iterator and call CacheFileIOManager::GetEntryInfo
+ // for each found entry.
+ ITERATE_METADATA,
+ } mPass;
+
+ RefPtr<CacheIndexIterator> mIter;
+ uint32_t mCount;
+};
+
+} // namespace
+
+void CacheStorageService::DropPrivateBrowsingEntries() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mShutdown) return;
+
+ nsTArray<nsCString> keys;
+ for (const nsACString& key : sGlobalEntryTables->Keys()) {
+ nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(key);
+ if (info && info->IsPrivate()) {
+ keys.AppendElement(key);
+ }
+ }
+
+ for (uint32_t i = 0; i < keys.Length(); ++i) {
+ DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
+ }
+}
+
+// Helper methods
+
+// static
+bool CacheStorageService::IsOnManagementThread() {
+ RefPtr<CacheStorageService> service = Self();
+ if (!service) return false;
+
+ nsCOMPtr<nsIEventTarget> target = service->Thread();
+ if (!target) return false;
+
+ bool currentThread;
+ nsresult rv = target->IsOnCurrentThread(&currentThread);
+ return NS_SUCCEEDED(rv) && currentThread;
+}
+
+already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const {
+ return CacheFileIOManager::IOTarget();
+}
+
+nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent) {
+ RefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread();
+ if (!cacheIOThread) return NS_ERROR_NOT_AVAILABLE;
+
+ return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT);
+}
+
+namespace CacheStorageEvictHelper {
+
+nsresult ClearStorage(bool const aPrivate, bool const aAnonymous,
+ OriginAttributes& aOa) {
+ nsresult rv;
+
+ aOa.SyncAttributesWithPrivateBrowsing(aPrivate);
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(aAnonymous, aOa);
+
+ nsCOMPtr<nsICacheStorage> storage;
+ RefPtr<CacheStorageService> service = CacheStorageService::Self();
+ NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
+
+ // Clear disk storage
+ rv = service->DiskCacheStorage(info, getter_AddRefs(storage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = storage->AsyncEvictStorage(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Clear memory storage
+ rv = service->MemoryCacheStorage(info, getter_AddRefs(storage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = storage->AsyncEvictStorage(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult Run(OriginAttributes& aOa) {
+ nsresult rv;
+
+ // Clear all [private X anonymous] combinations
+ rv = ClearStorage(false, false, aOa);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ClearStorage(false, true, aOa);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ClearStorage(true, false, aOa);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ClearStorage(true, true, aOa);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace CacheStorageEvictHelper
+
+// nsICacheStorageService
+
+NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(
+ nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
+ NS_ENSURE_ARG(_retval);
+
+ nsCOMPtr<nsICacheStorage> storage =
+ new CacheStorage(aLoadContextInfo, false, false, false);
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::DiskCacheStorage(
+ nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
+ NS_ENSURE_ARG(_retval);
+
+ // TODO save some heap granularity - cache commonly used storages.
+
+ // When disk cache is disabled, still provide a storage, but just keep stuff
+ // in memory.
+ bool useDisk = CacheObserver::UseDiskCache();
+
+ nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
+ aLoadContextInfo, useDisk, false /* size limit */, false /* don't pin */);
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::PinningCacheStorage(
+ nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
+ NS_ENSURE_ARG(aLoadContextInfo);
+ NS_ENSURE_ARG(_retval);
+
+ // When disk cache is disabled don't pretend we cache.
+ if (!CacheObserver::UseDiskCache()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsICacheStorage> storage =
+ new CacheStorage(aLoadContextInfo, true /* use disk */,
+ true /* ignore size checks */, true /* pin */);
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::Clear() {
+ nsresult rv;
+
+ // Tell the index to block notification to AsyncGetDiskConsumption.
+ // Will be allowed again from CacheFileContextEvictor::EvictEntries()
+ // when all the context have been removed from disk.
+ CacheIndex::OnAsyncEviction(true);
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ {
+ mozilla::MutexAutoLock forcedValidEntriesLock(mForcedValidEntriesLock);
+ mForcedValidEntries.Clear();
+ }
+
+ NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ const auto keys = ToTArray<nsTArray<nsCString>>(sGlobalEntryTables->Keys());
+ for (const auto& key : keys) {
+ DoomStorageEntries(key, nullptr, true, false, nullptr);
+ }
+
+ // Passing null as a load info means to evict all contexts.
+ // EvictByContext() respects the entry pinning. EvictAll() does not.
+ rv = CacheFileIOManager::EvictByContext(nullptr, false, u""_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::ClearOrigin(nsIPrincipal* aPrincipal) {
+ nsresult rv;
+
+ if (NS_WARN_IF(!aPrincipal)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString origin;
+ rv = nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::ClearOriginAttributes(
+ const nsAString& aOriginAttributes) {
+ nsresult rv;
+
+ if (NS_WARN_IF(aOriginAttributes.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ OriginAttributes oa;
+ if (!oa.Init(aOriginAttributes)) {
+ NS_ERROR("Could not parse the argument for OriginAttributes");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = CacheStorageEvictHelper::Run(oa);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
+ CacheEntry* aEntry, bool aOverwrite) {
+ RefPtr<CacheEntry> existingEntry;
+ if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
+ LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
+ return false; // Already removed...
+ }
+
+ if (!aOverwrite && existingEntry != aEntry) {
+ LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
+ return false; // Already replaced...
+ }
+
+ LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
+ aEntries->Remove(aKey);
+ return true;
+}
+
+NS_IMETHODIMP CacheStorageService::ClearBaseDomain(
+ const nsAString& aBaseDomain) {
+ if (sGlobalEntryTables) {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mShutdown) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCString cBaseDomain = NS_ConvertUTF16toUTF8(aBaseDomain);
+
+ nsTArray<nsCString> keys;
+ for (const auto& globalEntry : *sGlobalEntryTables) {
+ // Match by partitionKey base domain. This should cover most cache entries
+ // because we statically partition the cache. Most first party cache
+ // entries will also have a partitionKey set where the partitionKey base
+ // domain will match the entry URI base domain.
+ const nsACString& key = globalEntry.GetKey();
+ nsCOMPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(globalEntry.GetKey());
+
+ if (info &&
+ StoragePrincipalHelper::PartitionKeyHasBaseDomain(
+ info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) {
+ keys.AppendElement(key);
+ continue;
+ }
+
+ // If we didn't get a partitionKey match, try to match by entry URI. This
+ // requires us to iterate over all entries.
+ CacheEntryTable* table = globalEntry.GetWeak();
+ MOZ_ASSERT(table);
+
+ nsTArray<RefPtr<CacheEntry>> entriesToDelete;
+
+ for (CacheEntry* entry : table->Values()) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ // Some entries may not have valid hosts. We can skip them.
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ continue;
+ }
+
+ bool hasRootDomain = false;
+ rv = HasRootDomain(host, cBaseDomain, &hasRootDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ if (hasRootDomain) {
+ entriesToDelete.AppendElement(entry);
+ }
+ }
+
+ // Clear individual matched entries.
+ for (RefPtr<CacheEntry>& entry : entriesToDelete) {
+ nsAutoCString entryKey;
+ nsresult rv = entry->HashingKey(entryKey);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("aEntry->HashingKey() failed?");
+ return rv;
+ }
+
+ RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
+ }
+ }
+
+ // Clear matched keys.
+ for (uint32_t i = 0; i < keys.Length(); ++i) {
+ DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
+ }
+ }
+
+ return CacheFileIOManager::EvictByContext(nullptr, false /* pinned */, u""_ns,
+ aBaseDomain);
+}
+
+nsresult CacheStorageService::ClearOriginInternal(
+ const nsAString& aOrigin, const OriginAttributes& aOriginAttributes,
+ bool aAnonymous) {
+ nsresult rv;
+
+ RefPtr<LoadContextInfo> info =
+ GetLoadContextInfo(aAnonymous, aOriginAttributes);
+ if (NS_WARN_IF(!info)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (sGlobalEntryTables) {
+ for (const auto& globalEntry : *sGlobalEntryTables) {
+ bool matches = false;
+ rv = CacheFileUtils::KeyMatchesLoadContextInfo(globalEntry.GetKey(), info,
+ &matches);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!matches) {
+ continue;
+ }
+
+ CacheEntryTable* table = globalEntry.GetWeak();
+ MOZ_ASSERT(table);
+
+ nsTArray<RefPtr<CacheEntry>> entriesToDelete;
+
+ for (CacheEntry* entry : table->Values()) {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString origin;
+ rv = nsContentUtils::GetWebExposedOriginSerialization(uri, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (origin != aOrigin) {
+ continue;
+ }
+
+ entriesToDelete.AppendElement(entry);
+ }
+
+ for (RefPtr<CacheEntry>& entry : entriesToDelete) {
+ nsAutoCString entryKey;
+ rv = entry->HashingKey(entryKey);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("aEntry->HashingKey() failed?");
+ return rv;
+ }
+
+ RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
+ }
+ }
+ }
+
+ rv = CacheFileIOManager::EvictByContext(info, false /* pinned */, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) {
+ uint32_t what;
+
+ switch (aWhat) {
+ case PURGE_DISK_DATA_ONLY:
+ what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
+ break;
+
+ case PURGE_DISK_ALL:
+ what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
+ break;
+
+ case PURGE_EVERYTHING:
+ what = CacheEntry::PURGE_WHOLE;
+ break;
+
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new PurgeFromMemoryRunnable(this, what);
+
+ return Dispatch(event);
+}
+
+NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run() {
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(
+ nullptr, "cacheservice:purge-memory-pools", nullptr);
+ }
+
+ return NS_OK;
+ }
+
+ if (mService) {
+ // TODO not all flags apply to both pools
+ mService->Pool(MemoryPool::EType::DISK).PurgeAll(mWhat);
+ mService->Pool(MemoryPool::EType::MEMORY).PurgeAll(mWhat);
+ mService = nullptr;
+ }
+
+ NS_DispatchToMainThread(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
+ nsICacheStorageConsumptionObserver* aObserver) {
+ NS_ENSURE_ARG(aObserver);
+
+ nsresult rv;
+
+ rv = CacheIndex::AsyncGetDiskConsumption(aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget) {
+ NS_ENSURE_ARG(aEventTarget);
+
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ ioTarget.forget(aEventTarget);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::AsyncVisitAllStorages(
+ nsICacheStorageVisitor* aVisitor, bool aVisitEntries) {
+ LOG(("CacheStorageService::AsyncVisitAllStorages [cb=%p]", aVisitor));
+ NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ // Walking the disk cache also walks the memory cache.
+ RefPtr<WalkDiskCacheRunnable> event =
+ new WalkDiskCacheRunnable(nullptr, aVisitEntries, aVisitor);
+ return event->Walk();
+}
+
+// Methods used by CacheEntry for management of in-memory structures.
+
+void CacheStorageService::RegisterEntry(CacheEntry* aEntry) {
+ MOZ_ASSERT(IsOnManagementThread());
+
+ if (mShutdown || !aEntry->CanRegister()) return;
+
+ TelemetryRecordEntryCreation(aEntry);
+
+ LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
+
+ MemoryPool& pool = Pool(aEntry->IsUsingDisk());
+ pool.mManagedEntries.insertBack(aEntry);
+
+ aEntry->SetRegistered(true);
+}
+
+void CacheStorageService::UnregisterEntry(CacheEntry* aEntry) {
+ MOZ_ASSERT(IsOnManagementThread());
+
+ if (!aEntry->IsRegistered()) return;
+
+ TelemetryRecordEntryRemoval(aEntry);
+
+ LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
+
+ MemoryPool& pool = Pool(aEntry->IsUsingDisk());
+ aEntry->removeFrom(pool.mManagedEntries);
+
+ // Note: aEntry->CanRegister() since now returns false
+ aEntry->SetRegistered(false);
+}
+
+static bool AddExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
+ CacheEntry* aEntry, bool aOverwrite) {
+ RefPtr<CacheEntry> existingEntry;
+ if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
+ bool equals = existingEntry == aEntry;
+ LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
+ return equals; // Already there...
+ }
+
+ LOG(("AddExactEntry [entry=%p put]", aEntry));
+ aEntries->InsertOrUpdate(aKey, RefPtr{aEntry});
+ return true;
+}
+
+bool CacheStorageService::RemoveEntry(CacheEntry* aEntry,
+ bool aOnlyUnreferenced) {
+ LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
+
+ nsAutoCString entryKey;
+ nsresult rv = aEntry->HashingKey(entryKey);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("aEntry->HashingKey() failed?");
+ return false;
+ }
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mShutdown) {
+ LOG((" after shutdown"));
+ return false;
+ }
+
+ if (aOnlyUnreferenced) {
+ if (aEntry->IsReferenced()) {
+ LOG((" still referenced, not removing"));
+ return false;
+ }
+
+ if (!aEntry->IsUsingDisk() &&
+ IsForcedValidEntry(aEntry->GetStorageID(), entryKey)) {
+ LOG((" forced valid, not removing"));
+ return false;
+ }
+ }
+
+ CacheEntryTable* entries;
+ if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries)) {
+ RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
+ }
+
+ nsAutoCString memoryStorageID(aEntry->GetStorageID());
+ AppendMemoryStorageTag(memoryStorageID);
+
+ if (sGlobalEntryTables->Get(memoryStorageID, &entries)) {
+ RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
+ }
+
+ return true;
+}
+
+void CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
+ bool aOnlyInMemory,
+ bool aOverwrite) {
+ LOG(
+ ("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, "
+ "overwrite=%d]",
+ aEntry, aOnlyInMemory, aOverwrite));
+ // This method is responsible to put this entry to a special record hashtable
+ // that contains only entries that are stored in memory.
+ // Keep in mind that every entry, regardless of whether is in-memory-only or
+ // not is always recorded in the storage master hash table, the one identified
+ // by CacheEntry.StorageID().
+
+ mLock.AssertCurrentThreadOwns();
+
+ if (mShutdown) {
+ LOG((" after shutdown"));
+ return;
+ }
+
+ nsresult rv;
+
+ nsAutoCString entryKey;
+ rv = aEntry->HashingKey(entryKey);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("aEntry->HashingKey() failed?");
+ return;
+ }
+
+ CacheEntryTable* entries = nullptr;
+ nsAutoCString memoryStorageID(aEntry->GetStorageID());
+ AppendMemoryStorageTag(memoryStorageID);
+
+ if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
+ if (!aOnlyInMemory) {
+ LOG((" not recorded as memory only"));
+ return;
+ }
+
+ entries = sGlobalEntryTables
+ ->InsertOrUpdate(
+ memoryStorageID,
+ MakeUnique<CacheEntryTable>(CacheEntryTable::MEMORY_ONLY))
+ .get();
+ LOG((" new memory-only storage table for %s", memoryStorageID.get()));
+ }
+
+ if (aOnlyInMemory) {
+ AddExactEntry(entries, entryKey, aEntry, aOverwrite);
+ } else {
+ RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
+ }
+}
+
+// Checks if a cache entry is forced valid (will be loaded directly from cache
+// without further validation) - see nsICacheEntry.idl for further details
+bool CacheStorageService::IsForcedValidEntry(nsACString const& aContextKey,
+ nsACString const& aEntryKey) {
+ return IsForcedValidEntry(aContextKey + aEntryKey);
+}
+
+bool CacheStorageService::IsForcedValidEntry(
+ nsACString const& aContextEntryKey) {
+ mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
+
+ ForcedValidData data;
+
+ if (!mForcedValidEntries.Get(aContextEntryKey, &data)) {
+ return false;
+ }
+
+ if (data.validUntil.IsNull()) {
+ MOZ_ASSERT_UNREACHABLE("the timeStamp should never be null");
+ return false;
+ }
+
+ // Entry timeout not reached yet
+ if (TimeStamp::NowLoRes() <= data.validUntil) {
+ return true;
+ }
+
+ // Entry timeout has been reached
+ mForcedValidEntries.Remove(aContextEntryKey);
+
+ if (!data.viewed) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WaitedTooLong);
+ }
+ return false;
+}
+
+void CacheStorageService::MarkForcedValidEntryUse(nsACString const& aContextKey,
+ nsACString const& aEntryKey) {
+ mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
+
+ ForcedValidData data;
+
+ if (!mForcedValidEntries.Get(aContextKey + aEntryKey, &data)) {
+ return;
+ }
+
+ data.viewed = true;
+ mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
+}
+
+// Allows a cache entry to be loaded directly from cache without further
+// validation - see nsICacheEntry.idl for further details
+void CacheStorageService::ForceEntryValidFor(nsACString const& aContextKey,
+ nsACString const& aEntryKey,
+ uint32_t aSecondsToTheFuture) {
+ mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ ForcedValidEntriesPrune(now);
+
+ ForcedValidData data;
+ data.validUntil = now + TimeDuration::FromSeconds(aSecondsToTheFuture);
+ data.viewed = false;
+
+ mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
+}
+
+void CacheStorageService::RemoveEntryForceValid(nsACString const& aContextKey,
+ nsACString const& aEntryKey) {
+ mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
+
+ LOG(("CacheStorageService::RemoveEntryForceValid context='%s' entryKey=%s",
+ aContextKey.BeginReading(), aEntryKey.BeginReading()));
+ ForcedValidData data;
+ bool ok = mForcedValidEntries.Get(aContextKey + aEntryKey, &data);
+ if (ok && !data.viewed) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WaitedTooLong);
+ }
+ mForcedValidEntries.Remove(aContextKey + aEntryKey);
+}
+
+// Cleans out the old entries in mForcedValidEntries
+void CacheStorageService::ForcedValidEntriesPrune(TimeStamp& now) {
+ static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
+ static TimeStamp dontPruneUntil = now + oneMinute;
+ if (now < dontPruneUntil) return;
+
+ for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Data().validUntil < now) {
+ if (!iter.Data().viewed) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WaitedTooLong);
+ }
+ iter.Remove();
+ }
+ }
+ dontPruneUntil = now + oneMinute;
+}
+
+void CacheStorageService::OnMemoryConsumptionChange(
+ CacheMemoryConsumer* aConsumer, uint32_t aCurrentMemoryConsumption) {
+ LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
+ aConsumer, aCurrentMemoryConsumption));
+
+ uint32_t savedMemorySize = aConsumer->LoadReportedMemoryConsumption();
+ if (savedMemorySize == aCurrentMemoryConsumption) return;
+
+ // Exchange saved size with current one.
+ aConsumer->StoreReportedMemoryConsumption(aCurrentMemoryConsumption);
+
+ bool usingDisk = !(aConsumer->LoadFlags() & CacheMemoryConsumer::MEMORY_ONLY);
+ bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange(
+ savedMemorySize, aCurrentMemoryConsumption);
+
+ if (!overLimit) return;
+
+ // It's likely the timer has already been set when we get here,
+ // check outside the lock to save resources.
+#ifdef MOZ_TSAN
+ if (mPurgeTimerActive) {
+#else
+ if (mPurgeTimer) {
+#endif
+ return;
+ }
+
+ // We don't know if this is called under the service lock or not,
+ // hence rather dispatch.
+ RefPtr<nsIEventTarget> cacheIOTarget = Thread();
+ if (!cacheIOTarget) return;
+
+ // Dispatch as a priority task, we want to set the purge timer
+ // ASAP to prevent vain redispatch of this event.
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "net::CacheStorageService::SchedulePurgeOverMemoryLimit", this,
+ &CacheStorageService::SchedulePurgeOverMemoryLimit);
+ cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+bool CacheStorageService::MemoryPool::OnMemoryConsumptionChange(
+ uint32_t aSavedMemorySize, uint32_t aCurrentMemoryConsumption) {
+ mMemorySize -= aSavedMemorySize;
+ mMemorySize += aCurrentMemoryConsumption;
+
+ LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize),
+ aCurrentMemoryConsumption, aSavedMemorySize));
+
+ // Bypass purging when memory has not grew up significantly
+ if (aCurrentMemoryConsumption <= aSavedMemorySize) return false;
+
+ return mMemorySize > Limit();
+}
+
+void CacheStorageService::SchedulePurgeOverMemoryLimit() {
+ LOG(("CacheStorageService::SchedulePurgeOverMemoryLimit"));
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mShutdown) {
+ LOG((" past shutdown"));
+ return;
+ }
+
+ if (mPurgeTimer) {
+ LOG((" timer already up"));
+ return;
+ }
+
+ mPurgeTimer = NS_NewTimer();
+ if (mPurgeTimer) {
+#ifdef MOZ_TSAN
+ mPurgeTimerActive = true;
+#endif
+ nsresult rv;
+ rv = mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
+ LOG((" timer init rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
+ }
+}
+
+NS_IMETHODIMP
+CacheStorageService::Notify(nsITimer* aTimer) {
+ LOG(("CacheStorageService::Notify"));
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (aTimer == mPurgeTimer) {
+#ifdef MOZ_TSAN
+ mPurgeTimerActive = false;
+#endif
+ mPurgeTimer = nullptr;
+
+ if (!mShutdown) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "net::CacheStorageService::PurgeExpiredOrOverMemoryLimit", this,
+ &CacheStorageService::PurgeExpiredOrOverMemoryLimit);
+ Dispatch(event);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheStorageService::GetName(nsACString& aName) {
+ aName.AssignLiteral("CacheStorageService");
+ return NS_OK;
+}
+
+void CacheStorageService::PurgeExpiredOrOverMemoryLimit() {
+ MOZ_ASSERT(IsOnManagementThread());
+
+ LOG(("CacheStorageService::PurgeExpiredOrOverMemoryLimit"));
+
+ if (mShutdown) return;
+
+ static TimeDuration const kFourSeconds = TimeDuration::FromSeconds(4);
+ TimeStamp now = TimeStamp::NowLoRes();
+
+ if (!mLastPurgeTime.IsNull() && now - mLastPurgeTime < kFourSeconds) {
+ LOG((" bypassed, too soon"));
+ return;
+ }
+
+ mLastPurgeTime = now;
+
+ Pool(MemoryPool::EType::DISK).PurgeExpiredOrOverMemoryLimit();
+ Pool(MemoryPool::EType::MEMORY).PurgeExpiredOrOverMemoryLimit();
+}
+
+void CacheStorageService::MemoryPool::PurgeExpiredOrOverMemoryLimit() {
+ TimeStamp start(TimeStamp::Now());
+
+ uint32_t const memoryLimit = Limit();
+
+ // We always purge expired entries, even if under our limit.
+ size_t numExpired = PurgeExpired();
+ if (numExpired > 0) {
+ LOG((" found and purged %zu expired entries", numExpired));
+ }
+
+ // If we are still under pressure, purge LFU entries until we aren't.
+ if (mMemorySize > memoryLimit) {
+ auto r = PurgeByFrecency();
+ if (MOZ_LIKELY(r.isOk())) {
+ size_t numPurged = r.unwrap();
+ LOG((
+ " memory data consumption over the limit, abandoned %zu LFU entries",
+ numPurged));
+ } else {
+ // If we hit an error (OOM), do an emergency PurgeAll.
+ size_t numPurged = PurgeAll(CacheEntry::PURGE_WHOLE);
+ LOG(
+ (" memory data consumption over the limit, emergency purged all %zu "
+ "entries",
+ numPurged));
+ }
+ }
+
+ LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
+}
+
+// This function purges ALL expired entries.
+size_t CacheStorageService::MemoryPool::PurgeExpired() {
+ MOZ_ASSERT(IsOnManagementThread());
+
+ uint32_t now = NowInSeconds();
+
+ size_t numPurged = 0;
+
+ // Scan for items to purge. mManagedEntries is not sorted but comparing just
+ // one integer should be faster than anything else, so go scan.
+ RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
+ while (entry) {
+ // Get the next entry before we may be removed from our list.
+ RefPtr<CacheEntry> nextEntry = entry->getNext();
+
+ if (entry->GetExpirationTime() <= now) {
+ // Purge will modify our mManagedEntries list but we are prepared for it.
+ if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
+ numPurged++;
+ LOG((" purged expired, entry=%p, exptime=%u (now=%u)", entry.get(),
+ entry->GetExpirationTime(), now));
+ }
+ }
+
+ entry = std::move(nextEntry);
+
+ // To have some progress even under load, we do the check only after
+ // purging at least one item if under pressure.
+ if ((numPurged > 0 || mMemorySize <= Limit()) &&
+ CacheIOThread::YieldAndRerun()) {
+ break;
+ }
+ }
+
+ return numPurged;
+}
+
+Result<size_t, nsresult> CacheStorageService::MemoryPool::PurgeByFrecency() {
+ MOZ_ASSERT(IsOnManagementThread());
+
+ // Pretend the limit is 10% lower so that we get rid of more entries at one
+ // shot and save the sorting below.
+ uint32_t const memoryLimit = (uint32_t)(Limit() * 0.9);
+ if (mMemorySize <= memoryLimit) {
+ return 0;
+ }
+
+ LOG(("MemoryPool::PurgeByFrecency, len=%zu", mManagedEntries.length()));
+
+ // We want to have an array snapshot for sorting and iterating.
+ struct mayPurgeEntry {
+ RefPtr<CacheEntry> mEntry;
+ double mFrecency;
+
+ explicit mayPurgeEntry(CacheEntry* aEntry) {
+ mEntry = aEntry;
+ mFrecency = aEntry->GetFrecency();
+ }
+
+ bool operator<(const mayPurgeEntry& aOther) const {
+ return mFrecency < aOther.mFrecency;
+ }
+ };
+
+ nsTArray<mayPurgeEntry> mayPurgeSorted;
+ if (!mayPurgeSorted.SetCapacity(mManagedEntries.length(),
+ mozilla::fallible)) {
+ return Err(NS_ERROR_OUT_OF_MEMORY);
+ }
+ {
+ mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
+
+ for (const auto& entry : mManagedEntries) {
+ // Referenced items cannot be purged and we deliberately want to not look
+ // at '0' frecency entries, these are new entries and can be ignored.
+ if (!entry->IsReferenced() && entry->GetFrecency() > 0.0) {
+ mayPurgeEntry copy(entry);
+ mayPurgeSorted.AppendElement(std::move(copy));
+ }
+ }
+ }
+ if (mayPurgeSorted.Length() == 0) {
+ return 0;
+ }
+ mayPurgeSorted.Sort();
+
+ size_t numPurged = 0;
+
+ // Given that sorting is expensive, let's ensure to interrupt only if we
+ // made at least some progress. We expect purging of memory entries to be
+ // less expensive than disk entries.
+ size_t minprogress =
+ (mType == EType::DISK)
+ ? StaticPrefs::network_cache_purgebyfrecency_minprogress_disk()
+ : StaticPrefs::network_cache_purgebyfrecency_minprogress_memory();
+ for (auto& checkPurge : mayPurgeSorted) {
+ if (mMemorySize <= memoryLimit) {
+ break;
+ }
+
+ RefPtr<CacheEntry> entry = checkPurge.mEntry;
+
+ if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
+ numPurged++;
+ LOG((" abandoned (%d), entry=%p, frecency=%1.10f",
+ CacheEntry::PURGE_WHOLE, entry.get(), entry->GetFrecency()));
+ }
+
+ if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) {
+ LOG(("MemoryPool::PurgeByFrecency interrupted"));
+ return numPurged;
+ }
+ }
+
+ LOG(("MemoryPool::PurgeByFrecency done"));
+
+ return numPurged;
+}
+
+size_t CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat) {
+ LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat));
+ MOZ_ASSERT(IsOnManagementThread());
+
+ size_t numPurged = 0;
+
+ RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
+ while (entry) {
+ if (numPurged > 0 && CacheIOThread::YieldAndRerun()) break;
+
+ // Get the next entry before we may be removed from our list.
+ RefPtr<CacheEntry> nextEntry = entry->getNext();
+
+ if (entry->Purge(aWhat)) {
+ numPurged++;
+ LOG((" abandoned entry=%p", entry.get()));
+ }
+
+ entry = std::move(nextEntry);
+ }
+
+ return numPurged;
+}
+
+// Methods exposed to and used by CacheStorage.
+
+nsresult CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
+ const nsACString& aURI,
+ const nsACString& aIdExtension,
+ uint32_t aFlags,
+ CacheEntryHandle** aResult) {
+ NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(aStorage);
+
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+
+ return AddStorageEntry(contextKey, aURI, aIdExtension,
+ aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
+ aStorage->Pinning(), aFlags, aResult);
+}
+
+nsresult CacheStorageService::AddStorageEntry(
+ const nsACString& aContextKey, const nsACString& aURI,
+ const nsACString& aIdExtension, bool aWriteToDisk, bool aSkipSizeCheck,
+ bool aPin, uint32_t aFlags, CacheEntryHandle** aResult) {
+ nsresult rv;
+
+ nsAutoCString entryKey;
+ rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
+ entryKey.get(), aContextKey.BeginReading()));
+
+ RefPtr<CacheEntry> entry;
+ RefPtr<CacheEntryHandle> handle;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ // Ensure storage table
+ CacheEntryTable* const entries =
+ sGlobalEntryTables
+ ->LookupOrInsertWith(
+ aContextKey,
+ [&aContextKey] {
+ LOG((" new storage entries table for context '%s'",
+ aContextKey.BeginReading()));
+ return MakeUnique<CacheEntryTable>(
+ CacheEntryTable::ALL_ENTRIES);
+ })
+ .get();
+
+ bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
+ if (!entryExists && (aFlags & nsICacheStorage::OPEN_READONLY) &&
+ (aFlags & nsICacheStorage::OPEN_SECRETLY) &&
+ StaticPrefs::network_cache_bug1708673()) {
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+
+ bool replace = aFlags & nsICacheStorage::OPEN_TRUNCATE;
+
+ if (entryExists && !replace) {
+ // check whether we want to turn this entry to a memory-only.
+ if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
+ LOG((" entry is persistent but we want mem-only, replacing it"));
+ replace = true;
+ }
+ }
+
+ // If truncate is demanded, delete and doom the current entry
+ if (entryExists && replace) {
+ entries->Remove(entryKey);
+
+ LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(),
+ entryKey.get()));
+ // On purpose called under the lock to prevent races of doom and open on
+ // I/O thread No need to remove from both memory-only and all-entries
+ // tables. The new entry will overwrite the shadow entry in its ctor.
+ entry->DoomAlreadyRemoved();
+
+ entry = nullptr;
+ entryExists = false;
+
+ // Would only lead to deleting force-valid timestamp again. We don't need
+ // the replace information anymore after this point anyway.
+ replace = false;
+ }
+
+ // Ensure entry for the particular URL
+ if (!entryExists) {
+ // When replacing with a new entry, always remove the current force-valid
+ // timestamp, this is the only place to do it.
+ if (replace) {
+ RemoveEntryForceValid(aContextKey, entryKey);
+ }
+
+ // Entry is not in the hashtable or has just been truncated...
+ entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk,
+ aSkipSizeCheck, aPin);
+ entries->InsertOrUpdate(entryKey, RefPtr{entry});
+ LOG((" new entry %p for %s", entry.get(), entryKey.get()));
+ }
+
+ if (entry) {
+ // Here, if this entry was not for a long time referenced by any consumer,
+ // gets again first 'handles count' reference.
+ handle = entry->NewHandle();
+ }
+ }
+
+ handle.forget(aResult);
+ return NS_OK;
+}
+
+nsresult CacheStorageService::CheckStorageEntry(CacheStorage const* aStorage,
+ const nsACString& aURI,
+ const nsACString& aIdExtension,
+ bool* aResult) {
+ nsresult rv;
+
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+
+ if (!aStorage->WriteToDisk()) {
+ AppendMemoryStorageTag(contextKey);
+ }
+
+ LOG(("CacheStorageService::CheckStorageEntry [uri=%s, eid=%s, contextKey=%s]",
+ aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoCString entryKey;
+ rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CacheEntryTable* entries;
+ if ((*aResult = sGlobalEntryTables->Get(contextKey, &entries)) &&
+ entries->GetWeak(entryKey, aResult)) {
+ LOG((" found in hash tables"));
+ return NS_OK;
+ }
+ }
+
+ if (!aStorage->WriteToDisk()) {
+ // Memory entry, nothing more to do.
+ LOG((" not found in hash tables"));
+ return NS_OK;
+ }
+
+ // Disk entry, not found in the hashtable, check the index.
+ nsAutoCString fileKey;
+ rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
+
+ CacheIndex::EntryStatus status;
+ rv = CacheIndex::HasEntry(fileKey, &status);
+ if (NS_FAILED(rv) || status == CacheIndex::DO_NOT_KNOW) {
+ LOG((" index doesn't know, rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = status == CacheIndex::EXISTS;
+ LOG((" %sfound in index", *aResult ? "" : "not "));
+ return NS_OK;
+}
+
+nsresult CacheStorageService::GetCacheIndexEntryAttrs(
+ CacheStorage const* aStorage, const nsACString& aURI,
+ const nsACString& aIdExtension, bool* aHasAltData, uint32_t* aFileSizeKb) {
+ nsresult rv;
+
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+
+ LOG(
+ ("CacheStorageService::GetCacheIndexEntryAttrs [uri=%s, eid=%s, "
+ "contextKey=%s]",
+ aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
+
+ nsAutoCString fileKey;
+ rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aHasAltData = false;
+ *aFileSizeKb = 0;
+ auto closure = [&aHasAltData, &aFileSizeKb](const CacheIndexEntry* entry) {
+ *aHasAltData = entry->GetHasAltData();
+ *aFileSizeKb = entry->GetFileSize();
+ };
+
+ CacheIndex::EntryStatus status;
+ rv = CacheIndex::HasEntry(fileKey, &status, closure);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (status != CacheIndex::EXISTS) {
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+class CacheEntryDoomByKeyCallback : public CacheFileIOListener,
+ public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
+ : mCallback(aCallback), mResult(NS_ERROR_NOT_INITIALIZED) {}
+
+ private:
+ virtual ~CacheEntryDoomByKeyCallback();
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
+ nsresult aResult) override {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
+ nsresult mResult;
+};
+
+CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() {
+ if (mCallback) {
+ ProxyReleaseMainThread("CacheEntryDoomByKeyCallback::mCallback", mCallback);
+ }
+}
+
+NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(
+ CacheFileHandle* aHandle, nsresult aResult) {
+ if (!mCallback) return NS_OK;
+
+ mResult = aResult;
+ if (NS_IsMainThread()) {
+ Run();
+ } else {
+ NS_DispatchToMainThread(this);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntryDoomByKeyCallback::Run() {
+ mCallback->OnCacheEntryDoomed(mResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener,
+ nsIRunnable);
+
+} // namespace
+
+nsresult CacheStorageService::DoomStorageEntry(
+ CacheStorage const* aStorage, const nsACString& aURI,
+ const nsACString& aIdExtension, nsICacheEntryDoomCallback* aCallback) {
+ LOG(("CacheStorageService::DoomStorageEntry"));
+
+ NS_ENSURE_ARG(aStorage);
+
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+
+ nsAutoCString entryKey;
+ nsresult rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CacheEntry> entry;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ CacheEntryTable* entries;
+ if (sGlobalEntryTables->Get(contextKey, &entries)) {
+ if (entries->Get(entryKey, getter_AddRefs(entry))) {
+ if (aStorage->WriteToDisk() || !entry->IsUsingDisk()) {
+ // When evicting from disk storage, purge
+ // When evicting from memory storage and the entry is memory-only,
+ // purge
+ LOG(
+ (" purging entry %p for %s [storage use disk=%d, entry use "
+ "disk=%d]",
+ entry.get(), entryKey.get(), aStorage->WriteToDisk(),
+ entry->IsUsingDisk()));
+ entries->Remove(entryKey);
+ } else {
+ // Otherwise, leave it
+ LOG(
+ (" leaving entry %p for %s [storage use disk=%d, entry use "
+ "disk=%d]",
+ entry.get(), entryKey.get(), aStorage->WriteToDisk(),
+ entry->IsUsingDisk()));
+ entry = nullptr;
+ }
+ }
+ }
+
+ if (!entry) {
+ RemoveEntryForceValid(contextKey, entryKey);
+ }
+ }
+
+ if (entry) {
+ LOG((" dooming entry %p for %s", entry.get(), entryKey.get()));
+ return entry->AsyncDoom(aCallback);
+ }
+
+ LOG((" no entry loaded for %s", entryKey.get()));
+
+ if (aStorage->WriteToDisk()) {
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+
+ rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG((" dooming file only for %s", entryKey.get()));
+
+ RefPtr<CacheEntryDoomByKeyCallback> callback(
+ new CacheEntryDoomByKeyCallback(aCallback));
+ rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ class Callback : public Runnable {
+ public:
+ explicit Callback(nsICacheEntryDoomCallback* aCallback)
+ : mozilla::Runnable("Callback"), mCallback(aCallback) {}
+ NS_IMETHOD Run() override {
+ mCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
+ return NS_OK;
+ }
+ nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
+ };
+
+ if (aCallback) {
+ RefPtr<Runnable> callback = new Callback(aCallback);
+ return NS_DispatchToMainThread(callback);
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheStorageService::DoomStorageEntries(
+ CacheStorage const* aStorage, nsICacheEntryDoomCallback* aCallback) {
+ LOG(("CacheStorageService::DoomStorageEntries"));
+
+ NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_ARG(aStorage);
+
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
+ aStorage->WriteToDisk(), aStorage->Pinning(),
+ aCallback);
+}
+
+nsresult CacheStorageService::DoomStorageEntries(
+ const nsACString& aContextKey, nsILoadContextInfo* aContext,
+ bool aDiskStorage, bool aPinned, nsICacheEntryDoomCallback* aCallback) {
+ LOG(("CacheStorageService::DoomStorageEntries [context=%s]",
+ aContextKey.BeginReading()));
+
+ mLock.AssertCurrentThreadOwns();
+
+ NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoCString memoryStorageID(aContextKey);
+ AppendMemoryStorageTag(memoryStorageID);
+
+ if (aDiskStorage) {
+ LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading()));
+
+ // Walk one by one and remove entries according their pin status
+ CacheEntryTable *diskEntries, *memoryEntries;
+ if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
+ sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
+
+ for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = iter.Data();
+ if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
+ continue;
+ }
+
+ if (memoryEntries) {
+ RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
+ }
+ iter.Remove();
+ }
+ }
+
+ if (aContext && !aContext->IsPrivate()) {
+ LOG((" dooming disk entries"));
+ CacheFileIOManager::EvictByContext(aContext, aPinned, u""_ns);
+ }
+ } else {
+ LOG((" dooming memory-only storage of %s", aContextKey.BeginReading()));
+
+ // Remove the memory entries table from the global tables.
+ // Since we store memory entries also in the disk entries table
+ // we need to remove the memory entries from the disk table one
+ // by one manually.
+ mozilla::UniquePtr<CacheEntryTable> memoryEntries;
+ sGlobalEntryTables->Remove(memoryStorageID, &memoryEntries);
+
+ CacheEntryTable* diskEntries;
+ if (memoryEntries && sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
+ for (const auto& memoryEntry : *memoryEntries) {
+ const auto& entry = memoryEntry.GetData();
+ RemoveExactEntry(diskEntries, memoryEntry.GetKey(), entry, false);
+ }
+ }
+ }
+
+ {
+ mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
+
+ if (aContext) {
+ for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
+ bool matches;
+ DebugOnly<nsresult> rv = CacheFileUtils::KeyMatchesLoadContextInfo(
+ iter.Key(), aContext, &matches);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (matches) {
+ iter.Remove();
+ }
+ }
+ } else {
+ mForcedValidEntries.Clear();
+ }
+ }
+
+ // An artificial callback. This is a candidate for removal tho. In the new
+ // cache any 'doom' or 'evict' function ensures that the entry or entries
+ // being doomed is/are not accessible after the function returns. So there is
+ // probably no need for a callback - has no meaning. But for compatibility
+ // with the old cache that is still in the tree we keep the API similar to be
+ // able to make tests as well as other consumers work for now.
+ class Callback : public Runnable {
+ public:
+ explicit Callback(nsICacheEntryDoomCallback* aCallback)
+ : mozilla::Runnable("Callback"), mCallback(aCallback) {}
+ NS_IMETHOD Run() override {
+ mCallback->OnCacheEntryDoomed(NS_OK);
+ return NS_OK;
+ }
+ nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
+ };
+
+ if (aCallback) {
+ RefPtr<Runnable> callback = new Callback(aCallback);
+ return NS_DispatchToMainThread(callback);
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheStorageService::WalkStorageEntries(
+ CacheStorage const* aStorage, bool aVisitEntries,
+ nsICacheStorageVisitor* aVisitor) {
+ LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]",
+ aVisitor, aVisitEntries));
+ NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(aStorage);
+
+ if (aStorage->WriteToDisk()) {
+ RefPtr<WalkDiskCacheRunnable> event = new WalkDiskCacheRunnable(
+ aStorage->LoadInfo(), aVisitEntries, aVisitor);
+ return event->Walk();
+ }
+
+ RefPtr<WalkMemoryCacheRunnable> event = new WalkMemoryCacheRunnable(
+ aStorage->LoadInfo(), aVisitEntries, aVisitor);
+ return event->Walk();
+}
+
+void CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
+ const nsACString& aIdExtension,
+ const nsACString& aURISpec) {
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
+
+ nsAutoCString entryKey;
+ CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mShutdown) {
+ return;
+ }
+
+ CacheEntryTable* entries;
+ RefPtr<CacheEntry> entry;
+
+ if (sGlobalEntryTables->Get(contextKey, &entries) &&
+ entries->Get(entryKey, getter_AddRefs(entry))) {
+ if (entry->IsFileDoomed()) {
+ // Need to remove under the lock to avoid possible race leading
+ // to duplication of the entry per its key.
+ RemoveExactEntry(entries, entryKey, entry, false);
+ entry->DoomAlreadyRemoved();
+ }
+
+ // Entry found, but it's not the entry that has been found doomed
+ // by the lower eviction layer. Just leave everything unchanged.
+ return;
+ }
+
+ RemoveEntryForceValid(contextKey, entryKey);
+}
+
+bool CacheStorageService::GetCacheEntryInfo(
+ nsILoadContextInfo* aLoadContextInfo, const nsACString& aIdExtension,
+ const nsACString& aURISpec, EntryInfoCallback* aCallback) {
+ nsAutoCString contextKey;
+ CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
+
+ nsAutoCString entryKey;
+ CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
+
+ RefPtr<CacheEntry> entry;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (mShutdown) {
+ return false;
+ }
+
+ CacheEntryTable* entries;
+ if (!sGlobalEntryTables->Get(contextKey, &entries)) {
+ return false;
+ }
+
+ if (!entries->Get(entryKey, getter_AddRefs(entry))) {
+ return false;
+ }
+ }
+
+ GetCacheEntryInfo(entry, aCallback);
+ return true;
+}
+
+// static
+void CacheStorageService::GetCacheEntryInfo(CacheEntry* aEntry,
+ EntryInfoCallback* aCallback) {
+ nsCString const uriSpec = aEntry->GetURI();
+ nsCString const enhanceId = aEntry->GetEnhanceID();
+
+ nsAutoCString entryKey;
+ aEntry->HashingKeyWithStorage(entryKey);
+
+ nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(entryKey);
+
+ uint32_t dataSize;
+ if (NS_FAILED(aEntry->GetStorageDataSize(&dataSize))) {
+ dataSize = 0;
+ }
+ int64_t altDataSize;
+ if (NS_FAILED(aEntry->GetAltDataSize(&altDataSize))) {
+ altDataSize = 0;
+ }
+ uint32_t fetchCount;
+ if (NS_FAILED(aEntry->GetFetchCount(&fetchCount))) {
+ fetchCount = 0;
+ }
+ uint32_t lastModified;
+ if (NS_FAILED(aEntry->GetLastModified(&lastModified))) {
+ lastModified = 0;
+ }
+ uint32_t expirationTime;
+ if (NS_FAILED(aEntry->GetExpirationTime(&expirationTime))) {
+ expirationTime = 0;
+ }
+
+ aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount,
+ lastModified, expirationTime, aEntry->IsPinned(),
+ info);
+}
+
+// static
+uint32_t CacheStorageService::CacheQueueSize(bool highPriority) {
+ RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+ // The thread will be null at shutdown.
+ if (!thread) {
+ return 0;
+ }
+ return thread->QueueSize(highPriority);
+}
+
+// Telemetry collection
+
+namespace {
+
+bool TelemetryEntryKey(CacheEntry const* entry, nsAutoCString& key) {
+ nsAutoCString entryKey;
+ nsresult rv = entry->HashingKey(entryKey);
+ if (NS_FAILED(rv)) return false;
+
+ if (entry->GetStorageID().IsEmpty()) {
+ // Hopefully this will be const-copied, saves some memory
+ key = entryKey;
+ } else {
+ key.Assign(entry->GetStorageID());
+ key.Append(':');
+ key.Append(entryKey);
+ }
+
+ return true;
+}
+
+} // namespace
+
+void CacheStorageService::TelemetryPrune(TimeStamp& now) {
+ static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
+ static TimeStamp dontPruneUntil = now + oneMinute;
+ if (now < dontPruneUntil) return;
+
+ static TimeDuration const fifteenMinutes = TimeDuration::FromSeconds(900);
+ for (auto iter = mPurgeTimeStamps.Iter(); !iter.Done(); iter.Next()) {
+ if (now - iter.Data() > fifteenMinutes) {
+ // We are not interested in resurrection of entries after 15 minutes
+ // of time. This is also the limit for the telemetry.
+ iter.Remove();
+ }
+ }
+ dontPruneUntil = now + oneMinute;
+}
+
+void CacheStorageService::TelemetryRecordEntryCreation(
+ CacheEntry const* entry) {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+ nsAutoCString key;
+ if (!TelemetryEntryKey(entry, key)) return;
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ TelemetryPrune(now);
+
+ // When an entry is craeted (registered actually) we check if there is
+ // a timestamp marked when this very same cache entry has been removed
+ // (deregistered) because of over-memory-limit purging. If there is such
+ // a timestamp found accumulate telemetry on how long the entry was away.
+ TimeStamp timeStamp;
+ if (!mPurgeTimeStamps.Get(key, &timeStamp)) return;
+
+ mPurgeTimeStamps.Remove(key);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_CACHE_ENTRY_RELOAD_TIME,
+ timeStamp, TimeStamp::NowLoRes());
+}
+
+void CacheStorageService::TelemetryRecordEntryRemoval(CacheEntry* entry) {
+ MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+ // Doomed entries must not be considered, we are only interested in purged
+ // entries. Note that the mIsDoomed flag is always set before deregistration
+ // happens.
+ if (entry->IsDoomed()) return;
+
+ nsAutoCString key;
+ if (!TelemetryEntryKey(entry, key)) return;
+
+ // When an entry is removed (deregistered actually) we put a timestamp for
+ // this entry to the hashtable so that when the entry is created (registered)
+ // again we know how long it was away. Also accumulate number of AsyncOpen
+ // calls on the entry, this tells us how efficiently the pool actually works.
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ TelemetryPrune(now);
+ mPurgeTimeStamps.InsertOrUpdate(key, now);
+
+ Telemetry::Accumulate(Telemetry::HTTP_CACHE_ENTRY_REUSE_COUNT,
+ entry->UseCount());
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_CACHE_ENTRY_ALIVE_TIME,
+ entry->LoadStart(), TimeStamp::NowLoRes());
+}
+
+// nsIMemoryReporter
+
+size_t CacheStorageService::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
+
+ size_t n = 0;
+ // The elemets are referenced by sGlobalEntryTables and are reported from
+ // there.
+
+ // Entries reported manually in CacheStorageService::CollectReports callback
+ if (sGlobalEntryTables) {
+ n += sGlobalEntryTables->ShallowSizeOfIncludingThis(mallocSizeOf);
+ }
+ n += mPurgeTimeStamps.SizeOfExcludingThis(mallocSizeOf);
+
+ return n;
+}
+
+size_t CacheStorageService::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+NS_IMETHODIMP
+CacheStorageService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MutexAutoLock lock(mLock);
+ MOZ_COLLECT_REPORT("explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES,
+ CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf),
+ "Memory used by the cache IO manager.");
+
+ MOZ_COLLECT_REPORT("explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES,
+ CacheIndex::SizeOfIncludingThis(MallocSizeOf),
+ "Memory used by the cache index.");
+
+ // Report the service instance, this doesn't report entries, done lower
+ MOZ_COLLECT_REPORT("explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(MallocSizeOf),
+ "Memory used by the cache storage service.");
+
+ // Report all entries, each storage separately (by the context key)
+ //
+ // References are:
+ // sGlobalEntryTables to N CacheEntryTable
+ // CacheEntryTable to N CacheEntry
+ // CacheEntry to 1 CacheFile
+ // CacheFile to
+ // N CacheFileChunk (keeping the actual data)
+ // 1 CacheFileMetadata (keeping http headers etc.)
+ // 1 CacheFileOutputStream
+ // N CacheFileInputStream
+ if (sGlobalEntryTables) {
+ for (const auto& globalEntry : *sGlobalEntryTables) {
+ CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
+
+ CacheEntryTable* table = globalEntry.GetWeak();
+
+ size_t size = 0;
+ mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf;
+
+ size += table->ShallowSizeOfIncludingThis(mallocSizeOf);
+ for (const auto& tableEntry : *table) {
+ size += tableEntry.GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf);
+
+ // Bypass memory-only entries, those will be reported when iterating the
+ // memory only table. Memory-only entries are stored in both ALL_ENTRIES
+ // and MEMORY_ONLY hashtables.
+ RefPtr<mozilla::net::CacheEntry> const& entry = tableEntry.GetData();
+ if (table->Type() == CacheEntryTable::MEMORY_ONLY ||
+ entry->IsUsingDisk()) {
+ size += entry->SizeOfIncludingThis(mallocSizeOf);
+ }
+ }
+
+ aHandleReport->Callback(
+ ""_ns,
+ nsPrintfCString(
+ "explicit/network/cache2/%s-storage(%s)",
+ table->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk",
+ aAnonymize ? "<anonymized>"
+ : globalEntry.GetKey().BeginReading()),
+ nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, size,
+ "Memory used by the cache storage."_ns, aData);
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsICacheTesting
+
+NS_IMETHODIMP
+CacheStorageService::IOThreadSuspender::Run() {
+ MonitorAutoLock mon(mMon);
+ while (!mSignaled) {
+ mon.Wait();
+ }
+ return NS_OK;
+}
+
+void CacheStorageService::IOThreadSuspender::Notify() {
+ MonitorAutoLock mon(mMon);
+ mSignaled = true;
+ mon.Notify();
+}
+
+NS_IMETHODIMP
+CacheStorageService::SuspendCacheIOThread(uint32_t aLevel) {
+ RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+ if (!thread) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(!mActiveIOSuspender);
+ mActiveIOSuspender = new IOThreadSuspender();
+ return thread->Dispatch(mActiveIOSuspender, aLevel);
+}
+
+NS_IMETHODIMP
+CacheStorageService::ResumeCacheIOThread() {
+ MOZ_ASSERT(mActiveIOSuspender);
+
+ RefPtr<IOThreadSuspender> suspender;
+ suspender.swap(mActiveIOSuspender);
+ suspender->Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheStorageService::Flush(nsIObserver* aObserver) {
+ RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+ if (!thread) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Adding as weak, the consumer is responsible to keep the reference
+ // until notified.
+ observerService->AddObserver(aObserver, "cacheservice:purge-memory-pools",
+ false);
+
+ // This runnable will do the purging and when done, notifies the above
+ // observer. We dispatch it to the CLOSE level, so all data writes scheduled
+ // up to this time will be done before this purging happens.
+ RefPtr<CacheStorageService::PurgeFromMemoryRunnable> r =
+ new CacheStorageService::PurgeFromMemoryRunnable(this,
+ CacheEntry::PURGE_WHOLE);
+
+ return thread->Dispatch(r, CacheIOThread::WRITE);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/CacheStorageService.h b/netwerk/cache2/CacheStorageService.h
new file mode 100644
index 0000000000..84b63438b0
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.h
@@ -0,0 +1,448 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheStorageService__h__
+#define CacheStorageService__h__
+
+#include "mozilla/LinkedList.h"
+#include "nsICacheStorageService.h"
+#include "nsIMemoryReporter.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsICacheTesting.h"
+
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
+
+class nsIURI;
+class nsICacheEntryDoomCallback;
+class nsICacheStorageVisitor;
+class nsIRunnable;
+class nsIThread;
+class nsIEventTarget;
+
+namespace mozilla {
+
+class OriginAttributes;
+
+namespace net {
+
+class CacheStorageService;
+class CacheStorage;
+class CacheEntry;
+class CacheEntryHandle;
+
+class CacheMemoryConsumer {
+ private:
+ friend class CacheStorageService;
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 32, (
+ (uint32_t, ReportedMemoryConsumption, 30),
+ (uint32_t, Flags, 2)
+ ))
+ // clang-format on
+
+ private:
+ CacheMemoryConsumer() = delete;
+
+ protected:
+ enum {
+ // No special treatment, reports always to the disk-entries pool.
+ NORMAL = 0,
+ // This consumer is belonging to a memory-only cache entry, used to decide
+ // which of the two disk and memory pools count this consumption at.
+ MEMORY_ONLY = 1 << 0,
+ // Prevent reports of this consumer at all, used for disk data chunks since
+ // we throw them away as soon as the entry is not used by any consumer and
+ // don't want to make them wipe the whole pool out during their short life.
+ DONT_REPORT = 1 << 1
+ };
+
+ explicit CacheMemoryConsumer(uint32_t aFlags);
+ ~CacheMemoryConsumer() { DoMemoryReport(0); }
+ void DoMemoryReport(uint32_t aCurrentSize);
+};
+
+class CacheStorageService final : public nsICacheStorageService,
+ public nsIMemoryReporter,
+ public nsITimerCallback,
+ public nsICacheTesting,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHESTORAGESERVICE
+ NS_DECL_NSIMEMORYREPORTER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSICACHETESTING
+ NS_DECL_NSINAMED
+
+ CacheStorageService();
+
+ void Shutdown();
+ void DropPrivateBrowsingEntries();
+
+ static CacheStorageService* Self() { return sSelf; }
+ static nsISupports* SelfISupports() {
+ return static_cast<nsICacheStorageService*>(Self());
+ }
+ nsresult Dispatch(nsIRunnable* aEvent);
+ static bool IsRunning() { return sSelf && !sSelf->mShutdown; }
+ static bool IsOnManagementThread();
+ already_AddRefed<nsIEventTarget> Thread() const;
+ mozilla::Mutex& Lock() { return mLock; }
+
+ // Tracks entries that may be forced valid in a pruned hashtable.
+ struct ForcedValidData {
+ // The timestamp is computed when the entry gets inserted into the map.
+ // It should never be null for an entry in the map.
+ TimeStamp validUntil;
+ // viewed gets set to true by a call to MarkForcedValidEntryUse()
+ bool viewed = false;
+ };
+ nsTHashMap<nsCStringHashKey, ForcedValidData> mForcedValidEntries;
+ void ForcedValidEntriesPrune(TimeStamp& now);
+
+ // Helper thread-safe interface to pass entry info, only difference from
+ // nsICacheStorageVisitor is that instead of nsIURI only the uri spec is
+ // passed.
+ class EntryInfoCallback {
+ public:
+ virtual void OnEntryInfo(const nsACString& aURISpec,
+ const nsACString& aIdEnhance, int64_t aDataSize,
+ int64_t aAltDataSize, uint32_t aFetchCount,
+ uint32_t aLastModifiedTime,
+ uint32_t aExpirationTime, bool aPinned,
+ nsILoadContextInfo* aInfo) = 0;
+ };
+
+ // Invokes OnEntryInfo for the given aEntry, synchronously.
+ static void GetCacheEntryInfo(CacheEntry* aEntry,
+ EntryInfoCallback* aCallback);
+
+ nsresult GetCacheIndexEntryAttrs(CacheStorage const* aStorage,
+ const nsACString& aURI,
+ const nsACString& aIdExtension,
+ bool* aHasAltData, uint32_t* aFileSizeKb);
+
+ static uint32_t CacheQueueSize(bool highPriority);
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ private:
+ virtual ~CacheStorageService();
+ void ShutdownBackground();
+
+ private:
+ // The following methods may only be called on the management
+ // thread.
+ friend class CacheEntry;
+
+ /**
+ * Registers the entry into the associated MemoryPool.
+ * Holds a strong reference until it is unregistered.
+ */
+ void RegisterEntry(CacheEntry* aEntry);
+
+ /**
+ * Deregisters the entry from the associated MemoryPool.
+ */
+ void UnregisterEntry(CacheEntry* aEntry);
+
+ /**
+ * Removes the entry from the related entry hash table, if still present.
+ */
+ bool RemoveEntry(CacheEntry* aEntry, bool aOnlyUnreferenced = false);
+
+ /**
+ * Tells the storage service whether this entry is only to be stored in
+ * memory.
+ */
+ void RecordMemoryOnlyEntry(CacheEntry* aEntry, bool aOnlyInMemory,
+ bool aOverwrite);
+
+ /**
+ * Sets a cache entry valid (overrides the default loading behavior by loading
+ * directly from cache) for the given number of seconds
+ * See nsICacheEntry.idl for more details
+ */
+ void ForceEntryValidFor(nsACString const& aContextKey,
+ nsACString const& aEntryKey,
+ uint32_t aSecondsToTheFuture);
+
+ /**
+ * Remove the validity info
+ */
+ void RemoveEntryForceValid(nsACString const& aContextKey,
+ nsACString const& aEntryKey);
+
+ /**
+ * Retrieves the status of the cache entry to see if it has been forced valid
+ * (so it will loaded directly from cache without further validation)
+ */
+ bool IsForcedValidEntry(nsACString const& aContextKey,
+ nsACString const& aEntryKey);
+
+ // Marks the entry as used, so we may properly report when it gets evicted
+ // if the prefetched resource was used or not.
+ void MarkForcedValidEntryUse(nsACString const& aContextKey,
+ nsACString const& aEntryKey);
+
+ private:
+ friend class CacheIndex;
+
+ /**
+ * CacheIndex uses this to prevent a cache entry from being prememptively
+ * thrown away when forced valid
+ * See nsICacheEntry.idl for more details
+ */
+ bool IsForcedValidEntry(nsACString const& aContextEntryKey);
+
+ private:
+ // These are helpers for telemetry monitoring of the memory pools.
+ void TelemetryPrune(TimeStamp& now);
+ void TelemetryRecordEntryCreation(CacheEntry const* entry);
+ void TelemetryRecordEntryRemoval(CacheEntry* entry);
+
+ private:
+ // Following methods are thread safe to call.
+ friend class CacheStorage;
+
+ /**
+ * Get, or create when not existing and demanded, an entry for the storage
+ * and uri+id extension.
+ */
+ nsresult AddStorageEntry(CacheStorage const* aStorage, const nsACString& aURI,
+ const nsACString& aIdExtension, uint32_t aFlags,
+ CacheEntryHandle** aResult);
+
+ /**
+ * Check existance of an entry. This may throw NS_ERROR_NOT_AVAILABLE
+ * when the information cannot be obtained synchronously w/o blocking.
+ */
+ nsresult CheckStorageEntry(CacheStorage const* aStorage,
+ const nsACString& aURI,
+ const nsACString& aIdExtension, bool* aResult);
+
+ /**
+ * Removes the entry from the related entry hash table, if still present
+ * and returns it.
+ */
+ nsresult DoomStorageEntry(CacheStorage const* aStorage,
+ const nsACString& aURI,
+ const nsACString& aIdExtension,
+ nsICacheEntryDoomCallback* aCallback);
+
+ /**
+ * Removes and returns entry table for the storage.
+ */
+ nsresult DoomStorageEntries(CacheStorage const* aStorage,
+ nsICacheEntryDoomCallback* aCallback);
+
+ /**
+ * Walk all entiries beloging to the storage.
+ */
+ nsresult WalkStorageEntries(CacheStorage const* aStorage, bool aVisitEntries,
+ nsICacheStorageVisitor* aVisitor);
+
+ private:
+ friend class CacheFileIOManager;
+
+ /**
+ * CacheFileIOManager uses this method to notify CacheStorageService that
+ * an active entry was removed. This method is called even if the entry
+ * removal was originated by CacheStorageService.
+ */
+ void CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
+ const nsACString& aIdExtension,
+ const nsACString& aURISpec);
+
+ /**
+ * Tries to find an existing entry in the hashtables and synchronously call
+ * OnCacheEntryInfo of the aVisitor callback when found.
+ * @retuns
+ * true, when the entry has been found that also implies the callbacks has
+ * beem invoked
+ * false, when an entry has not been found
+ */
+ bool GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
+ const nsACString& aIdExtension,
+ const nsACString& aURISpec,
+ EntryInfoCallback* aCallback);
+
+ private:
+ friend class CacheMemoryConsumer;
+
+ /**
+ * When memory consumption of this entry radically changes, this method
+ * is called to reflect the size of allocated memory. This call may purge
+ * unspecified number of entries from memory (but not from disk).
+ */
+ void OnMemoryConsumptionChange(CacheMemoryConsumer* aConsumer,
+ uint32_t aCurrentMemoryConsumption);
+
+ /**
+ * If not already pending, it schedules mPurgeTimer that fires after 1 second
+ * and dispatches PurgeOverMemoryLimit().
+ */
+ void SchedulePurgeOverMemoryLimit();
+
+ /**
+ * Called on the management thread, removes all expired and then least used
+ * entries from the memory, first from the disk pool and then from the memory
+ * pool.
+ */
+ void PurgeExpiredOrOverMemoryLimit();
+
+ private:
+ nsresult DoomStorageEntries(const nsACString& aContextKey,
+ nsILoadContextInfo* aContext, bool aDiskStorage,
+ bool aPin, nsICacheEntryDoomCallback* aCallback);
+ nsresult AddStorageEntry(const nsACString& aContextKey,
+ const nsACString& aURI,
+ const nsACString& aIdExtension, bool aWriteToDisk,
+ bool aSkipSizeCheck, bool aPin, uint32_t aFlags,
+ CacheEntryHandle** aResult);
+
+ nsresult ClearOriginInternal(
+ const nsAString& aOrigin,
+ const mozilla::OriginAttributes& aOriginAttributes, bool aAnonymous);
+
+ static CacheStorageService* sSelf;
+
+ mozilla::Mutex mLock MOZ_UNANNOTATED{"CacheStorageService.mLock"};
+ mozilla::Mutex mForcedValidEntriesLock{
+ "CacheStorageService.mForcedValidEntriesLock"};
+
+ Atomic<bool, Relaxed> mShutdown{false};
+
+ // Accessible only on the service thread
+ class MemoryPool {
+ public:
+ enum EType {
+ DISK,
+ MEMORY,
+ } mType;
+
+ explicit MemoryPool(EType aType);
+ ~MemoryPool();
+
+ // We want to have constant O(1) for removal from this list.
+ LinkedList<RefPtr<CacheEntry>> mManagedEntries;
+ Atomic<uint32_t, Relaxed> mMemorySize{0};
+
+ bool OnMemoryConsumptionChange(uint32_t aSavedMemorySize,
+ uint32_t aCurrentMemoryConsumption);
+ /**
+ * Purges entries from memory based on the frecency ordered array.
+ */
+ void PurgeExpiredOrOverMemoryLimit();
+ size_t PurgeExpired();
+ Result<size_t, nsresult> PurgeByFrecency();
+ size_t PurgeAll(uint32_t aWhat);
+
+ private:
+ uint32_t Limit() const;
+ MemoryPool() = delete;
+ };
+
+ MemoryPool mDiskPool{MemoryPool::DISK};
+ MemoryPool mMemoryPool{MemoryPool::MEMORY};
+ TimeStamp mLastPurgeTime;
+ MemoryPool& Pool(bool aUsingDisk) {
+ return aUsingDisk ? mDiskPool : mMemoryPool;
+ }
+ MemoryPool const& Pool(bool aUsingDisk) const {
+ return aUsingDisk ? mDiskPool : mMemoryPool;
+ }
+
+ nsCOMPtr<nsITimer> mPurgeTimer;
+#ifdef MOZ_TSAN
+ // In OnMemoryConsumptionChange() we check whether the timer exists, but we
+ // cannot grab the lock there (see comment 6 in bug 1614637) and TSan reports
+ // a data race. This data race is harmless, so we use this atomic flag only in
+ // TSan build to suppress it.
+ Atomic<bool, Relaxed> mPurgeTimerActive{false};
+#endif
+
+ class PurgeFromMemoryRunnable : public Runnable {
+ public:
+ PurgeFromMemoryRunnable(CacheStorageService* aService, uint32_t aWhat)
+ : Runnable("net::CacheStorageService::PurgeFromMemoryRunnable"),
+ mService(aService),
+ mWhat(aWhat) {}
+
+ private:
+ virtual ~PurgeFromMemoryRunnable() = default;
+
+ NS_IMETHOD Run() override;
+
+ RefPtr<CacheStorageService> mService;
+ uint32_t mWhat;
+ };
+
+ // Used just for telemetry purposes, accessed only on the management thread.
+ // Note: not included in the memory reporter, this is not expected to be huge
+ // and also would be complicated to report since reporting happens on the main
+ // thread but this table is manipulated on the management thread.
+ nsTHashMap<nsCStringHashKey, mozilla::TimeStamp> mPurgeTimeStamps;
+
+ // nsICacheTesting
+ class IOThreadSuspender : public Runnable {
+ public:
+ IOThreadSuspender()
+ : Runnable("net::CacheStorageService::IOThreadSuspender"),
+ mMon("IOThreadSuspender") {}
+ void Notify();
+
+ private:
+ virtual ~IOThreadSuspender() = default;
+ NS_IMETHOD Run() override;
+
+ Monitor mMon MOZ_UNANNOTATED;
+ bool mSignaled{false};
+ };
+
+ RefPtr<IOThreadSuspender> mActiveIOSuspender;
+};
+
+template <class T>
+void ProxyRelease(const char* aName, nsCOMPtr<T>& object,
+ nsIEventTarget* target) {
+ NS_ProxyRelease(aName, target, object.forget());
+}
+
+template <class T>
+void ProxyReleaseMainThread(const char* aName, nsCOMPtr<T>& object) {
+ ProxyRelease(aName, object, GetMainThreadSerialEventTarget());
+}
+
+} // namespace net
+} // namespace mozilla
+
+#define NS_CACHE_STORAGE_SERVICE_CID \
+ { \
+ 0xea70b098, 0x5014, 0x4e21, { \
+ 0xae, 0xe1, 0x75, 0xe6, 0xb2, 0xc4, 0xb8, 0xe0 \
+ } \
+ }
+
+#define NS_CACHE_STORAGE_SERVICE_CONTRACTID \
+ "@mozilla.org/netwerk/cache-storage-service;1"
+
+#define NS_CACHE_STORAGE_SERVICE_CONTRACTID2 \
+ "@mozilla.org/network/cache-storage-service;1"
+
+#endif
diff --git a/netwerk/cache2/moz.build b/netwerk/cache2/moz.build
new file mode 100644
index 0000000000..54afa11a09
--- /dev/null
+++ b/netwerk/cache2/moz.build
@@ -0,0 +1,66 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: Cache")
+
+XPIDL_SOURCES += [
+ "nsICacheEntry.idl",
+ "nsICacheEntryDoomCallback.idl",
+ "nsICacheEntryOpenCallback.idl",
+ "nsICachePurgeLock.idl",
+ "nsICacheStorage.idl",
+ "nsICacheStorageService.idl",
+ "nsICacheStorageVisitor.idl",
+ "nsICacheTesting.idl",
+]
+
+XPIDL_MODULE = "necko_cache2"
+
+EXPORTS += [
+ "CacheObserver.h",
+ "CacheStorageService.h",
+]
+
+EXPORTS.mozilla.net += ["CachePurgeLock.h"]
+
+SOURCES += [
+ "CacheStorage.cpp",
+]
+
+
+UNIFIED_SOURCES += [
+ "CacheEntry.cpp",
+ "CacheFile.cpp",
+ "CacheFileChunk.cpp",
+ "CacheFileContextEvictor.cpp",
+ "CacheFileInputStream.cpp",
+ "CacheFileIOManager.cpp",
+ "CacheFileMetadata.cpp",
+ "CacheFileOutputStream.cpp",
+ "CacheFileUtils.cpp",
+ "CacheHashUtils.cpp",
+ "CacheIndex.cpp",
+ "CacheIndexContextIterator.cpp",
+ "CacheIndexIterator.cpp",
+ "CacheIOThread.cpp",
+ "CacheLog.cpp",
+ "CacheObserver.cpp",
+ "CacheStorageService.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
+ UNIFIED_SOURCES += [
+ "CachePurgeLock.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/cache2/nsICacheEntry.idl b/netwerk/cache2/nsICacheEntry.idl
new file mode 100644
index 0000000000..744c5a014f
--- /dev/null
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -0,0 +1,369 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAsyncOutputStream;
+interface nsICacheEntryDoomCallback;
+interface nsICacheEntryMetaDataVisitor;
+interface nsIInputStream;
+interface nsILoadContextInfo;
+interface nsIOutputStream;
+interface nsITransportSecurityInfo;
+
+[scriptable, uuid(607c2a2c-0a48-40b9-a956-8cf2bb9857cf)]
+interface nsICacheEntry : nsISupports
+{
+ const unsigned long CONTENT_TYPE_UNKNOWN = 0;
+ const unsigned long CONTENT_TYPE_OTHER = 1;
+ const unsigned long CONTENT_TYPE_JAVASCRIPT = 2;
+ const unsigned long CONTENT_TYPE_IMAGE = 3;
+ const unsigned long CONTENT_TYPE_MEDIA = 4;
+ const unsigned long CONTENT_TYPE_STYLESHEET = 5;
+ const unsigned long CONTENT_TYPE_WASM = 6;
+ /**
+ * Content type that is used internally to check whether the value parsed
+ * from disk is within allowed limits. Don't pass CONTENT_TYPE_LAST to
+ * setContentType method.
+ */
+ const unsigned long CONTENT_TYPE_LAST = 7;
+
+ /**
+ * Placeholder for the initial value of expiration time.
+ */
+ const unsigned long NO_EXPIRATION_TIME = 0xFFFFFFFF;
+
+ /**
+ * Get the key identifying the cache entry.
+ */
+ readonly attribute ACString key;
+
+ /**
+ * The unique ID for every nsICacheEntry instance, which can be used to check
+ * whether two pieces of information are from the same nsICacheEntry instance.
+ */
+ readonly attribute uint64_t cacheEntryId;
+
+ /**
+ * Whether the entry is memory/only or persisted to disk.
+ * Note: private browsing entries are reported as persistent for consistency
+ * while are not actually persisted to disk.
+ */
+ readonly attribute boolean persistent;
+
+ /**
+ * Get the number of times the cache entry has been opened.
+ */
+ readonly attribute uint32_t fetchCount;
+
+ /**
+ * Get the last time the cache entry was opened (in seconds since the Epoch).
+ */
+ readonly attribute uint32_t lastFetched;
+
+ /**
+ * Get the last time the cache entry was modified (in seconds since the Epoch).
+ */
+ readonly attribute uint32_t lastModified;
+
+ /**
+ * Get the expiration time of the cache entry (in seconds since the Epoch).
+ */
+ readonly attribute uint32_t expirationTime;
+
+ /**
+ * Set the time at which the cache entry should be considered invalid (in
+ * seconds since the Epoch).
+ */
+ void setExpirationTime(in uint32_t expirationTime);
+
+ /**
+ * Get the last network response times for onStartReqeust/onStopRequest (in ms).
+ * @throws
+ * - NS_ERROR_NOT_AVAILABLE if onStartTime/onStopTime does not exist.
+ */
+ readonly attribute uint64_t onStartTime;
+ readonly attribute uint64_t onStopTime;
+
+ /**
+ * Set the network response times for onStartReqeust/onStopRequest (in ms).
+ */
+ void setNetworkTimes(in uint64_t onStartTime, in uint64_t onStopTime);
+
+ /**
+ * Set content type. Available types are defined at the begining of this file.
+ * The content type is used internally for cache partitioning and telemetry
+ * purposes so there is no getter.
+ */
+ void setContentType(in uint8_t contentType);
+
+ /**
+ * This method is intended to override the per-spec cache validation
+ * decisions for a duration specified in seconds. The current state can
+ * be examined with isForcedValid (see below). This value is not persisted,
+ * so it will not survive session restart. Cache entries that are forced valid
+ * will not be evicted from the cache for the duration of forced validity.
+ * This means that there is a potential problem if the number of forced valid
+ * entries grows to take up more space than the cache size allows.
+ *
+ * NOTE: entries that have been forced valid will STILL be ignored by HTTP
+ * channels if they have expired AND the resource in question requires
+ * validation after expiring. This is to avoid using known-stale content.
+ *
+ * @param aSecondsToTheFuture
+ * the number of seconds the default cache validation behavior will be
+ * overridden before it returns to normal
+ */
+ void forceValidFor(in unsigned long aSecondsToTheFuture);
+
+ /**
+ * The state variable for whether this entry is currently forced valid.
+ * Defaults to false for normal cache validation behavior, and will return
+ * true if the number of seconds set by forceValidFor() has yet to be reached.
+ */
+ readonly attribute boolean isForcedValid;
+
+ /**
+ * This method gets called to mark the actual use of the forced-valid entry.
+ * This is necessary for telemetry, so when the entry eventually gets
+ * evicted we can report whether it was ever used or not.
+ * If the entry was not forced-valid, then this operation has no effect.
+ */
+ void markForcedValidUse();
+
+ /**
+ * Open blocking input stream to cache data. Use the stream transport
+ * service to asynchronously read this stream on a background thread.
+ * The returned stream MAY implement nsISeekableStream.
+ *
+ * @param offset
+ * read starting from this offset into the cached data. an offset
+ * beyond the end of the stream has undefined consequences.
+ *
+ * @return non-blocking, buffered input stream.
+ */
+ nsIInputStream openInputStream(in long long offset);
+
+ /**
+ * Open non-blocking output stream to cache data. The returned stream
+ * MAY implement nsISeekableStream.
+ *
+ * If opening an output stream to existing cached data, the data will be
+ * truncated to the specified offset.
+ *
+ * @param offset
+ * write starting from this offset into the cached data. an offset
+ * beyond the end of the stream has undefined consequences.
+ * @param predictedSize
+ * Predicted size of the data that will be written. It's used to decide
+ * whether the resulting entry would exceed size limit, in which case
+ * an error is thrown. If the size isn't known in advance, -1 should be
+ * passed.
+ *
+ * @return blocking, buffered output stream.
+ */
+ nsIOutputStream openOutputStream(in long long offset, in long long predictedSize);
+
+ /**
+ * Get/set security info on the cache entry for this descriptor.
+ */
+ attribute nsITransportSecurityInfo securityInfo;
+
+ /**
+ * Get the size of the cache entry data, as stored. This may differ
+ * from the entry's dataSize, if the entry is compressed.
+ */
+ readonly attribute unsigned long storageDataSize;
+
+ /**
+ * Asynchronously doom an entry. Listener will be notified about the status
+ * of the operation. Null may be passed if caller doesn't care about the
+ * result.
+ */
+ void asyncDoom(in nsICacheEntryDoomCallback listener);
+
+ /**
+ * Methods for accessing meta data. Meta data is a table of key/value
+ * string pairs. The strings do not have to conform to any particular
+ * charset, but they must be null terminated.
+ */
+ string getMetaDataElement(in string key);
+ void setMetaDataElement(in string key, in string value);
+
+ /**
+ * Obtain the list of metadata keys this entry keeps.
+ *
+ * NOTE: The callback is invoked under the CacheFile's lock. It means
+ * there should not be made any calls to the entry from the visitor and
+ * if the values need to be processed somehow, it's better to cache them
+ * and process outside the callback.
+ */
+ void visitMetaData(in nsICacheEntryMetaDataVisitor visitor);
+
+ /**
+ * Claims that all metadata on this entry are up-to-date and this entry
+ * now can be delivered to other waiting consumers.
+ *
+ * We need such method since metadata must be delivered synchronously.
+ */
+ void metaDataReady();
+
+ /**
+ * Called by consumer upon 304/206 response from the server. This marks
+ * the entry content as positively revalidated.
+ * Consumer uses this method after the consumer has returned ENTRY_NEEDS_REVALIDATION
+ * result from onCacheEntryCheck and after successfull revalidation with the server.
+ */
+ void setValid();
+
+ /**
+ * Explicitly tell the cache backend this consumer is no longer going to modify
+ * this cache entry data or metadata. In case the consumer was responsible to
+ * either of writing the cache entry or revalidating it, calling this method
+ * reverts the state to initial (as never written) or as not-validated and
+ * immediately notifies the next consumer in line waiting for this entry.
+ * This is the way to prevent deadlocks when someone else than the responsible
+ * channel references the cache entry being in a non-written or revalidating
+ * state.
+ */
+ void dismiss();
+
+ /**
+ * Returns the size in kilobytes used to store the cache entry on disk.
+ */
+ readonly attribute uint32_t diskStorageSizeInKB;
+
+ /**
+ * Doom this entry and open a new, empty, entry for write. Consumer has
+ * to exchange the entry this method is called on for the newly created.
+ * Used on 200 responses to conditional requests.
+ *
+ * @param aMemoryOnly
+ * - whether the entry is to be created as memory/only regardless how
+ * the entry being recreated persistence is set
+ * @returns
+ * - an entry that can be used to write to
+ * @throws
+ * - NS_ERROR_NOT_AVAILABLE when the entry cannot be from some reason
+ * recreated for write
+ */
+ nsICacheEntry recreate([optional] in boolean aMemoryOnly);
+
+ /**
+ * Returns the length of data this entry holds.
+ * @throws
+ * NS_ERROR_IN_PROGRESS when the write is still in progress.
+ */
+ readonly attribute long long dataSize;
+
+ /**
+ * Returns the length of data this entry holds.
+ * @throws
+ * - NS_ERROR_IN_PROGRESS when a write is still in progress (either real
+ content or alt data).
+ * - NS_ERROR_NOT_AVAILABLE if alt data does not exist.
+ */
+ readonly attribute long long altDataSize;
+
+ /**
+ * Returns the type of the saved alt data.
+ * @throws
+ * - NS_ERROR_NOT_AVAILABLE if alt data does not exist.
+ */
+ readonly attribute ACString altDataType;
+
+ /**
+ * Opens and returns an output stream that a consumer may use to save an
+ * alternate representation of the data.
+ *
+ * @param type
+ * type of the alternative data representation
+ * @param predictedSize
+ * Predicted size of the data that will be written. It's used to decide
+ * whether the resulting entry would exceed size limit, in which case
+ * an error is thrown. If the size isn't known in advance, -1 should be
+ * passed.
+ *
+ * @throws
+ * - NS_ERROR_NOT_AVAILABLE if the real data hasn't been written.
+ * - NS_ERROR_IN_PROGRESS when the writing regular content or alt-data to
+ * the cache entry is still in progress.
+ *
+ * If there is alt-data already saved, it will be overwritten.
+ */
+ nsIAsyncOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize);
+
+ /**
+ * Opens and returns an input stream that can be used to read the alternative
+ * representation previously saved in the cache.
+ * If this call is made while writing alt-data is still in progress, it is
+ * still possible to read content from the input stream as it's being written.
+ * @throws
+ * - NS_ERROR_NOT_AVAILABLE if the alt-data representation doesn't exist at
+ * all or if alt-data of the given type doesn't exist.
+ */
+ nsIInputStream openAlternativeInputStream(in ACString type);
+
+ /**
+ * Get the nsILoadContextInfo of the cache entry
+ */
+ readonly attribute nsILoadContextInfo loadContextInfo;
+
+ /****************************************************************************
+ * The following methods might be added to some nsICacheEntryInternal
+ * interface since we want to remove them as soon as the old cache backend is
+ * completely removed.
+ */
+
+ /**
+ * @deprecated
+ * FOR BACKWARD COMPATIBILITY ONLY
+ * When the old cache backend is eventually removed, this method
+ * can be removed too.
+ *
+ * In the new backend: this method is no-op
+ * In the old backend: this method delegates to nsICacheEntryDescriptor.close()
+ */
+ void close();
+
+ /**
+ * @deprecated
+ * FOR BACKWARD COMPATIBILITY ONLY
+ * Marks the entry as valid so that others can use it and get only readonly
+ * access when the entry is held by the 1st writer.
+ */
+ void markValid();
+
+ /**
+ * @deprecated
+ * FOR BACKWARD COMPATIBILITY ONLY
+ * Marks the entry as valid when write access is acquired.
+ */
+ void maybeMarkValid();
+
+ /**
+ * @deprecated
+ * FOR BACKWARD COMPATIBILITY ONLY / KINDA HACK
+ * @param aWriteAllowed
+ * Consumer indicates whether write to the entry is allowed for it.
+ * Depends on implementation how the flag is handled.
+ * @returns
+ * true when write access is acquired for this entry,
+ * false otherwise
+ */
+ boolean hasWriteAccess(in boolean aWriteAllowed);
+};
+
+/**
+ * Argument for nsICacheEntry.visitMetaData, provides access to all metadata
+ * keys and values stored on the entry.
+ */
+[scriptable, uuid(fea3e276-6ba5-4ceb-a581-807d1f43f6d0)]
+interface nsICacheEntryMetaDataVisitor : nsISupports
+{
+ /**
+ * Called over each key / value pair.
+ */
+ void onMetaDataElement(in string key, in string value);
+};
diff --git a/netwerk/cache2/nsICacheEntryDoomCallback.idl b/netwerk/cache2/nsICacheEntryDoomCallback.idl
new file mode 100644
index 0000000000..a16a738292
--- /dev/null
+++ b/netwerk/cache2/nsICacheEntryDoomCallback.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(2f8896be-232f-4140-afb3-1faffb56f3c6)]
+interface nsICacheEntryDoomCallback : nsISupports
+{
+ /**
+ * Callback invoked after an entry or entries has/have been
+ * doomed from the cache.
+ */
+ void onCacheEntryDoomed(in nsresult aResult);
+};
diff --git a/netwerk/cache2/nsICacheEntryOpenCallback.idl b/netwerk/cache2/nsICacheEntryOpenCallback.idl
new file mode 100644
index 0000000000..0116d7cc57
--- /dev/null
+++ b/netwerk/cache2/nsICacheEntryOpenCallback.idl
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICacheEntry;
+
+[scriptable, uuid(1fc9fe11-c6ac-4748-94bd-8555a5a12b94)]
+interface nsICacheEntryOpenCallback : nsISupports
+{
+ /**
+ * State of the entry determined by onCacheEntryCheck.
+ *
+ * ENTRY_WANTED - the consumer is interested in the entry, we will pass it.
+ * RECHECK_AFTER_WRITE_FINISHED - the consumer cannot use the entry while data is
+ * still being written and wants to check it again after the current write is
+ * finished. This actually prevents concurrent read/write and is used with
+ * non-resumable HTTP responses.
+ * ENTRY_NEEDS_REVALIDATION - entry needs to be revalidated first with origin server,
+ * this means the loading channel will decide whether to use the entry content
+ * as is after it gets a positive response from the server about validity of the
+ * content ; when a new content needs to be loaded from the server, the loading
+ * channel opens a new entry with OPEN_TRUNCATE flag which dooms the one
+ * this check has been made for.
+ * ENTRY_NOT_WANTED - the consumer is not interested in the entry, we will not pass it.
+ */
+ const unsigned long ENTRY_WANTED = 0;
+ const unsigned long RECHECK_AFTER_WRITE_FINISHED = 1;
+ const unsigned long ENTRY_NEEDS_REVALIDATION = 2;
+ const unsigned long ENTRY_NOT_WANTED = 3;
+
+ /**
+ * Callback to perform any validity checks before the entry should be used.
+ * Called before onCacheEntryAvailable callback, depending on the result it
+ * may be called more then one time.
+ *
+ * This callback is ensured to be called on the same thread on which asyncOpenURI
+ * has been called, unless nsICacheStorage.CHECK_MULTITHREADED flag has been specified.
+ * In that case this callback can be invoked on any thread, usually it is the cache I/O
+ * or cache management thread.
+ *
+ * IMPORTANT NOTE:
+ * This callback may be invoked sooner then respective asyncOpenURI call exits.
+ *
+ * @param aEntry
+ * An entry to examine. Consumer has a chance to decide whether the
+ * entry is valid or not.
+ * @return
+ * State of the entry, see the constants just above.
+ */
+ unsigned long onCacheEntryCheck(in nsICacheEntry aEntry);
+
+ /**
+ * Callback giving actual result of asyncOpenURI. It may give consumer the cache
+ * entry or a failure result when it's not possible to open it from some reason.
+ * This callback is ensured to be called on the same thread on which asyncOpenURI
+ * has been called.
+ *
+ * IMPORTANT NOTE:
+ * This callback may be invoked sooner then respective asyncOpenURI call exits.
+ *
+ * @param aEntry
+ * The entry bound to the originally requested URI.
+ * @param aNew
+ * Whether no data so far has been stored for this entry, i.e. reading
+ * it will just fail. When aNew is true, a server request should be
+ * made and data stored to this new entry.
+ * @param aResult
+ * Result of the request. This may be a failure only when one of these
+ * issues occur:
+ * - the cache storage service could not be started due to some unexpected
+ * faulure
+ * - there is not enough disk space to create new entries
+ */
+ void onCacheEntryAvailable(in nsICacheEntry aEntry,
+ in boolean aNew,
+ in nsresult aResult);
+};
diff --git a/netwerk/cache2/nsICachePurgeLock.idl b/netwerk/cache2/nsICachePurgeLock.idl
new file mode 100644
index 0000000000..a0937a8922
--- /dev/null
+++ b/netwerk/cache2/nsICachePurgeLock.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+interface nsIFile;
+
+/**
+ * This object is a wrapper of MultiInstanceLock.
+ * It's intended to be used to ensure exclusive access to folders being
+ * deleted by the purgeHTTPCache background task.
+ */
+[scriptable,uuid(8abb21e3-c6a0-4b4d-9333-cc0d72f2c23b)]
+interface nsICachePurgeLock : nsISupports {
+ /**
+ * Initializes the lock using the profile name and the current process's
+ * path.
+ * Will throw if a lock was already acquired successfully.
+ */
+ void lock(in AUTF8String profileName);
+
+ /**
+ * Returns true if another instance also holds the lock.
+ * Throws if called before lock was called, or after unlock was called.
+ */
+ bool isOtherInstanceRunning();
+
+ /**
+ * Releases the lock.
+ * This object may be locked again, potentially using a different path
+ * after unlocking.
+ */
+ void unlock();
+
+ /**
+ * Returns the file used to guarantee single access to a resource.
+ * This method is used to remove the lock file when no longer necessary.
+ */
+ nsIFile getLockFile(in AUTF8String profileName);
+};
diff --git a/netwerk/cache2/nsICacheStorage.idl b/netwerk/cache2/nsICacheStorage.idl
new file mode 100644
index 0000000000..8169c9b730
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorage.idl
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsICacheEntry;
+interface nsICacheEntryOpenCallback;
+interface nsICacheEntryDoomCallback;
+interface nsICacheStorageVisitor;
+
+/**
+ * Representation of a cache storage. There can be just-in-mem,
+ * in-mem+on-disk, in-mem+on-disk+app-cache or just a specific
+ * app-cache storage.
+ */
+[scriptable, uuid(35d104a6-d252-4fd4-8a56-3c14657cad3b)]
+interface nsICacheStorage : nsISupports
+{
+ /**
+ * Placeholder for specifying "no special flags" during open.
+ */
+ const uint32_t OPEN_NORMALLY = 0;
+
+ /**
+ * Rewrite any existing data when opening a URL.
+ */
+ const uint32_t OPEN_TRUNCATE = 1 << 0;
+
+ /**
+ * Only open an existing entry. Don't create a new one.
+ */
+ const uint32_t OPEN_READONLY = 1 << 1;
+
+ /**
+ * Use for first-paint blocking loads.
+ */
+ const uint32_t OPEN_PRIORITY = 1 << 2;
+
+ /**
+ * Bypass the cache load when write is still in progress.
+ */
+ const uint32_t OPEN_BYPASS_IF_BUSY = 1 << 3;
+
+ /**
+ * Perform the cache entry check (onCacheEntryCheck invocation) on any thread
+ * for optimal perfomance optimization. If this flag is not specified it is
+ * ensured that onCacheEntryCheck is called on the same thread as respective
+ * asyncOpen has been called.
+ */
+ const uint32_t CHECK_MULTITHREADED = 1 << 4;
+
+ /**
+ * Don't automatically update any 'last used' metadata of the entry.
+ */
+ const uint32_t OPEN_SECRETLY = 1 << 5;
+
+ /**
+ * Entry is being opened as part of a service worker interception. Do not
+ * allow the cache to be disabled in this case.
+ */
+ const uint32_t OPEN_INTERCEPTED = 1 << 6;
+
+ /**
+ * Asynchronously opens a cache entry for the specified URI.
+ * Result is fetched asynchronously via the callback.
+ *
+ * @param aURI
+ * The URI to search in cache or to open for writting.
+ * @param aIdExtension
+ * Any string that will extend (distinguish) the entry. Two entries
+ * with the same aURI but different aIdExtension will be comletely
+ * different entries. If you don't know what aIdExtension should be
+ * leave it empty.
+ * @param aFlags
+ * OPEN_NORMALLY - open cache entry normally for read and write
+ * OPEN_TRUNCATE - delete any existing entry before opening it
+ * OPEN_READONLY - don't create an entry if there is none
+ * OPEN_PRIORITY - give this request a priority over others
+ * OPEN_BYPASS_IF_BUSY - backward compatibility only, LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
+ * CHECK_MULTITHREADED - onCacheEntryCheck may be called on any thread, consumer
+ * implementation is thread-safe
+ * @param aCallback
+ * The consumer that receives the result.
+ * IMPORTANT: The callback may be called sooner the method returns.
+ */
+ void asyncOpenURI(in nsIURI aURI, in ACString aIdExtension,
+ in uint32_t aFlags,
+ in nsICacheEntryOpenCallback aCallback);
+
+ /**
+ * Immediately opens a new and empty cache entry in the storage, any existing
+ * entries are immediately doomed. This is similar to the recreate() method
+ * on nsICacheEntry.
+ *
+ * Storage may not implement this method and throw NS_ERROR_NOT_IMPLEMENTED.
+ * In that case consumer must use asyncOpen with OPEN_TRUNCATE flag and get
+ * the new entry via a callback.
+ *
+ * @param aURI @see asyncOpenURI
+ * @param aIdExtension @see asyncOpenURI
+ */
+ nsICacheEntry openTruncate(in nsIURI aURI,
+ in ACString aIdExtension);
+
+ /**
+ * Synchronously check on existance of an entry. In case of disk entries
+ * this uses information from the cache index. When the index data are not
+ * up to date or index is still building, NS_ERROR_NOT_AVAILABLE is thrown.
+ * The same error may throw any storage implementation that cannot determine
+ * entry state without blocking the caller.
+ */
+ boolean exists(in nsIURI aURI, in ACString aIdExtension);
+
+ /**
+ * Synchronously check on existance of alternative data and size of the
+ * content. When the index data are not up to date or index is still building,
+ * NS_ERROR_NOT_AVAILABLE is thrown. The same error may throw any storage
+ * implementation that cannot determine entry state without blocking the caller.
+ */
+ void getCacheIndexEntryAttrs(in nsIURI aURI,
+ in ACString aIdExtension,
+ out bool aHasAltData,
+ out uint32_t aSizeInKB);
+ /**
+ * Asynchronously removes an entry belonging to the URI from the cache.
+ */
+ void asyncDoomURI(in nsIURI aURI, in ACString aIdExtension,
+ in nsICacheEntryDoomCallback aCallback);
+
+ /**
+ * Asynchronously removes all cached entries under this storage.
+ * NOTE: Disk storage also evicts memory storage.
+ */
+ void asyncEvictStorage(in nsICacheEntryDoomCallback aCallback);
+
+ /**
+ * Visits the storage and its entries.
+ * NOTE: Disk storage also visits memory storage.
+ */
+ void asyncVisitStorage(in nsICacheStorageVisitor aVisitor,
+ in boolean aVisitEntries);
+
+};
diff --git a/netwerk/cache2/nsICacheStorageService.idl b/netwerk/cache2/nsICacheStorageService.idl
new file mode 100644
index 0000000000..f0a4cf5b65
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorageService.idl
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsICacheStorage;
+interface nsILoadContextInfo;
+interface nsIEventTarget;
+interface nsICacheStorageConsumptionObserver;
+interface nsICacheStorageVisitor;
+interface nsIPrincipal;
+
+/**
+ * Provides access to particual cache storages of the network URI cache.
+ */
+[scriptable, uuid(ae29c44b-fbc3-4552-afaf-0a157ce771e7)]
+interface nsICacheStorageService : nsISupports
+{
+ /**
+ * Get storage where entries will only remain in memory, never written
+ * to the disk.
+ *
+ * NOTE: Any existing disk entry for [URL|id-extension] will be doomed
+ * prior opening an entry using this memory-only storage. Result of
+ * AsyncOpenURI will be a new and empty memory-only entry. Using
+ * OPEN_READONLY open flag has no effect on this behavior.
+ *
+ * @param aLoadContextInfo
+ * Information about the loading context, this focuses the storage JAR and
+ * respects separate storage for private browsing.
+ */
+ nsICacheStorage memoryCacheStorage(in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Get storage where entries will be written to disk when not forbidden by
+ * response headers.
+ */
+ nsICacheStorage diskCacheStorage(in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Get storage where entries will be written to disk and marked as pinned.
+ * These pinned entries are immune to over limit eviction and call of clear()
+ * on this service.
+ */
+ nsICacheStorage pinningCacheStorage(in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Evict any cache entry having the same origin of aPrincipal.
+ *
+ * @param aPrincipal
+ * The principal to compare the entries with.
+ */
+ void clearOrigin(in nsIPrincipal aPrincipal);
+
+ /**
+ * Evict any cache entry which belongs to a base domain. This includes entries
+ * partitioned under aBaseDomain and entries which belong to aBaseDomain, but
+ * are partitioned under other top level sites.
+ * @param aBaseDomain
+ * The base domain to clear cache for.
+ */
+ void clearBaseDomain(in AString aBaseDomain);
+
+ /**
+ * Evict any cache entry having the same originAttributes.
+ *
+ * @param aOriginAttributes
+ * The origin attributes in string format to compare the entries with.
+ */
+ void clearOriginAttributes(in AString aOriginAttributes);
+
+ /**
+ * Evict the whole cache.
+ */
+ void clear();
+
+ /**
+ * Purge only data of disk backed entries. Metadata are left for
+ * performance purposes.
+ */
+ const uint32_t PURGE_DISK_DATA_ONLY = 1;
+ /**
+ * Purge whole disk backed entries from memory. Disk files will
+ * be left unattended.
+ */
+ const uint32_t PURGE_DISK_ALL = 2;
+ /**
+ * Purge all entries we keep in memory, including memory-storage
+ * entries. This may be dangerous to use.
+ */
+ const uint32_t PURGE_EVERYTHING = 3;
+ /**
+ * Purges data we keep warmed in memory. Use for tests and for
+ * saving memory.
+ */
+ void purgeFromMemory(in uint32_t aWhat);
+
+ /**
+ * I/O thread target to use for any operations on disk
+ */
+ readonly attribute nsIEventTarget ioTarget;
+
+ /**
+ * Asynchronously determine how many bytes of the disk space the cache takes.
+ * @see nsICacheStorageConsumptionObserver
+ * @param aObserver
+ * A mandatory (weak referred) observer. Documented at
+ * nsICacheStorageConsumptionObserver.
+ * NOTE: the observer MUST implement nsISupportsWeakReference.
+ */
+ void asyncGetDiskConsumption(in nsICacheStorageConsumptionObserver aObserver);
+
+ /**
+ * Asynchronously visits all storages of the disk cache and memory cache.
+ * @see nsICacheStorageVisitor
+ * @param aVisitor
+ * A visitor callback.
+ * @param aVisitEntries
+ * A boolean indicates whether visits entries.
+ */
+ void asyncVisitAllStorages(in nsICacheStorageVisitor aVisitor,
+ in boolean aVisitEntries);
+};
+
+[scriptable, uuid(7728ab5b-4c01-4483-a606-32bf5b8136cb)]
+interface nsICacheStorageConsumptionObserver : nsISupports
+{
+ /**
+ * Callback invoked to answer asyncGetDiskConsumption call. Always triggered
+ * on the main thread.
+ * NOTE: implementers must also implement nsISupportsWeakReference.
+ *
+ * @param aDiskSize
+ * The disk consumption in bytes.
+ */
+ void onNetworkCacheDiskConsumption(in int64_t aDiskSize);
+};
diff --git a/netwerk/cache2/nsICacheStorageVisitor.idl b/netwerk/cache2/nsICacheStorageVisitor.idl
new file mode 100644
index 0000000000..138de2c984
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorageVisitor.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 nsIURI;
+interface nsIFile;
+interface nsILoadContextInfo;
+
+[scriptable, uuid(6cc7c253-93b6-482b-8e9d-1e04d8e9d655)]
+interface nsICacheStorageVisitor : nsISupports
+{
+ /**
+ */
+ void onCacheStorageInfo(in uint32_t aEntryCount,
+ in uint64_t aConsumption,
+ in uint64_t aCapacity,
+ in nsIFile aDiskDirectory);
+
+ /**
+ */
+ void onCacheEntryInfo(in nsIURI aURI,
+ in ACString aIdEnhance,
+ in int64_t aDataSize,
+ in int64_t aAltDataSize,
+ in uint32_t aFetchCount,
+ in uint32_t aLastModifiedTime,
+ in uint32_t aExpirationTime,
+ in boolean aPinned,
+ in nsILoadContextInfo aInfo);
+
+ /**
+ */
+ void onCacheEntryVisitCompleted();
+};
diff --git a/netwerk/cache2/nsICacheTesting.idl b/netwerk/cache2/nsICacheTesting.idl
new file mode 100644
index 0000000000..15704f7caa
--- /dev/null
+++ b/netwerk/cache2/nsICacheTesting.idl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIObserver;
+
+/**
+ * This is an internal interface used only for testing purposes.
+ *
+ * THIS IS NOT AN API TO BE USED BY EXTENSIONS! ONLY USED BY MOZILLA TESTS.
+ */
+[scriptable, builtinclass, uuid(4e8ba935-92e1-4a74-944b-b1a2f02a7480)]
+interface nsICacheTesting : nsISupports
+{
+ void suspendCacheIOThread(in uint32_t aLevel);
+ void resumeCacheIOThread();
+ void flush(in nsIObserver aObserver);
+};
diff --git a/netwerk/cookie/Cookie.cpp b/netwerk/cookie/Cookie.cpp
new file mode 100644
index 0000000000..e9de561f88
--- /dev/null
+++ b/netwerk/cookie/Cookie.cpp
@@ -0,0 +1,289 @@
+/* -*- 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 "Cookie.h"
+#include "CookieStorage.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIURLParser.h"
+#include "nsURLHelper.h"
+#include <cstdlib>
+
+namespace mozilla {
+namespace net {
+
+/******************************************************************************
+ * Cookie:
+ * creation helper
+ ******************************************************************************/
+
+// This is a counter that keeps track of the last used creation time, each time
+// we create a new Cookie. This is nominally the time (in microseconds) the
+// cookie was created, but is guaranteed to be monotonically increasing for
+// cookies added at runtime after the database has been read in. This is
+// necessary to enforce ordering among cookies whose creation times would
+// otherwise overlap, since it's possible two cookies may be created at the
+// same time, or that the system clock isn't monotonic.
+static int64_t gLastCreationTime;
+
+int64_t Cookie::GenerateUniqueCreationTime(int64_t aCreationTime) {
+ // Check if the creation time given to us is greater than the running maximum
+ // (it should always be monotonically increasing).
+ if (aCreationTime > gLastCreationTime) {
+ gLastCreationTime = aCreationTime;
+ return aCreationTime;
+ }
+
+ // Make up our own.
+ return ++gLastCreationTime;
+}
+
+already_AddRefed<Cookie> Cookie::Create(
+ const CookieStruct& aCookieData,
+ const OriginAttributes& aOriginAttributes) {
+ RefPtr<Cookie> cookie =
+ Cookie::FromCookieStruct(aCookieData, aOriginAttributes);
+
+ // If the creationTime given to us is higher than the running maximum,
+ // update our maximum.
+ if (cookie->mData.creationTime() > gLastCreationTime) {
+ gLastCreationTime = cookie->mData.creationTime();
+ }
+
+ return cookie.forget();
+}
+
+already_AddRefed<Cookie> Cookie::FromCookieStruct(
+ const CookieStruct& aCookieData,
+ const OriginAttributes& aOriginAttributes) {
+ RefPtr<Cookie> cookie = new Cookie(aCookieData, aOriginAttributes);
+
+ // Ensure mValue contains a valid UTF-8 sequence. Otherwise XPConnect will
+ // truncate the string after the first invalid octet.
+ UTF_8_ENCODING->DecodeWithoutBOMHandling(aCookieData.value(),
+ cookie->mData.value());
+
+ // If sameSite/rawSameSite values aren't sensible reset to Default
+ // cf. 5.4.7 in draft-ietf-httpbis-rfc6265bis-09
+ if (!Cookie::ValidateSameSite(cookie->mData)) {
+ cookie->mData.sameSite() = nsICookie::SAMESITE_LAX;
+ cookie->mData.rawSameSite() = nsICookie::SAMESITE_NONE;
+ }
+
+ return cookie.forget();
+}
+
+already_AddRefed<Cookie> Cookie::CreateValidated(
+ const CookieStruct& aCookieData,
+ const OriginAttributes& aOriginAttributes) {
+ if (!StaticPrefs::network_cookie_fixup_on_db_load()) {
+ return Cookie::Create(aCookieData, aOriginAttributes);
+ }
+
+ RefPtr<Cookie> cookie =
+ Cookie::FromCookieStruct(aCookieData, aOriginAttributes);
+
+ int64_t currentTimeInUsec = PR_Now();
+ // Assert that the last creation time is not higher than the current time.
+ // The 10000 wiggle room accounts for the fact that calling
+ // GenerateUniqueCreationTime might go over the value of PR_Now(), but we'd
+ // most likely not add 10000 cookies in a row.
+ MOZ_ASSERT(gLastCreationTime < currentTimeInUsec + 10000,
+ "Last creation time must not be higher than NOW");
+
+ // If the creationTime given to us is higher than the current time then
+ // update the creation time to now.
+ if (cookie->mData.creationTime() > currentTimeInUsec) {
+ uint64_t diffInSeconds =
+ (cookie->mData.creationTime() - currentTimeInUsec) / PR_USEC_PER_SEC;
+ mozilla::glean::networking::cookie_creation_fixup_diff.AccumulateSamples(
+ {diffInSeconds});
+ glean::networking::cookie_timestamp_fixed_count.Get("creationTime"_ns)
+ .Add(1);
+
+ cookie->mData.creationTime() =
+ GenerateUniqueCreationTime(currentTimeInUsec);
+ }
+
+ if (cookie->mData.lastAccessed() > currentTimeInUsec) {
+ uint64_t diffInSeconds =
+ (cookie->mData.lastAccessed() - currentTimeInUsec) / PR_USEC_PER_SEC;
+ mozilla::glean::networking::cookie_access_fixup_diff.AccumulateSamples(
+ {diffInSeconds});
+ glean::networking::cookie_timestamp_fixed_count.Get("lastAccessed"_ns)
+ .Add(1);
+
+ cookie->mData.lastAccessed() = currentTimeInUsec;
+ }
+
+ return cookie.forget();
+}
+
+size_t Cookie::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) +
+ mData.name().SizeOfExcludingThisIfUnshared(MallocSizeOf) +
+ mData.value().SizeOfExcludingThisIfUnshared(MallocSizeOf) +
+ mData.host().SizeOfExcludingThisIfUnshared(MallocSizeOf) +
+ mData.path().SizeOfExcludingThisIfUnshared(MallocSizeOf) +
+ mFilePathCache.SizeOfExcludingThisIfUnshared(MallocSizeOf);
+}
+
+bool Cookie::IsStale() const {
+ int64_t currentTimeInUsec = PR_Now();
+
+ return currentTimeInUsec - LastAccessed() >
+ StaticPrefs::network_cookie_staleThreshold() * PR_USEC_PER_SEC;
+}
+
+/******************************************************************************
+ * Cookie:
+ * xpcom impl
+ ******************************************************************************/
+
+// xpcom getters
+NS_IMETHODIMP Cookie::GetName(nsACString& aName) {
+ aName = Name();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetValue(nsACString& aValue) {
+ aValue = Value();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetHost(nsACString& aHost) {
+ aHost = Host();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetRawHost(nsACString& aHost) {
+ aHost = RawHost();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetPath(nsACString& aPath) {
+ aPath = Path();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetExpiry(int64_t* aExpiry) {
+ *aExpiry = Expiry();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetIsSession(bool* aIsSession) {
+ *aIsSession = IsSession();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetIsDomain(bool* aIsDomain) {
+ *aIsDomain = IsDomain();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetIsSecure(bool* aIsSecure) {
+ *aIsSecure = IsSecure();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetIsHttpOnly(bool* aHttpOnly) {
+ *aHttpOnly = IsHttpOnly();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetIsPartitioned(bool* aPartitioned) {
+ *aPartitioned = IsPartitioned();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetCreationTime(int64_t* aCreation) {
+ *aCreation = CreationTime();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetLastAccessed(int64_t* aTime) {
+ *aTime = LastAccessed();
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetSameSite(int32_t* aSameSite) {
+ if (StaticPrefs::network_cookie_sameSite_laxByDefault()) {
+ *aSameSite = SameSite();
+ } else {
+ *aSameSite = RawSameSite();
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP Cookie::GetSchemeMap(nsICookie::schemeType* aSchemeMap) {
+ *aSchemeMap = static_cast<nsICookie::schemeType>(SchemeMap());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Cookie::GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+const OriginAttributes& Cookie::OriginAttributesNative() {
+ return mOriginAttributes;
+}
+
+const Cookie& Cookie::AsCookie() { return *this; }
+
+const nsCString& Cookie::GetFilePath() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (Path().IsEmpty()) {
+ // If we don't have a path, just return the (empty) file path cache.
+ return mFilePathCache;
+ }
+ if (!mFilePathCache.IsEmpty()) {
+ // If we've computed the answer before, just return it.
+ return mFilePathCache;
+ }
+
+ nsIURLParser* parser = net_GetStdURLParser();
+ NS_ENSURE_TRUE(parser, mFilePathCache);
+
+ int32_t pathLen = Path().Length();
+ int32_t filepathLen = 0;
+ uint32_t filepathPos = 0;
+
+ nsresult rv = parser->ParsePath(PromiseFlatCString(Path()).get(), pathLen,
+ &filepathPos, &filepathLen, nullptr,
+ nullptr, // don't care about query
+ nullptr, nullptr); // don't care about ref
+ NS_ENSURE_SUCCESS(rv, mFilePathCache);
+
+ mFilePathCache = Substring(Path(), filepathPos, filepathLen);
+
+ return mFilePathCache;
+}
+
+// compatibility method, for use with the legacy nsICookie interface.
+// here, expires == 0 denotes a session cookie.
+NS_IMETHODIMP
+Cookie::GetExpires(uint64_t* aExpires) {
+ if (IsSession()) {
+ *aExpires = 0;
+ } else {
+ *aExpires = Expiry() > 0 ? Expiry() : 1;
+ }
+ return NS_OK;
+}
+
+// static
+bool Cookie::ValidateSameSite(const CookieStruct& aCookieData) {
+ // For proper migration towards a laxByDefault world,
+ // sameSite is initialized to LAX even though the server
+ // has never sent it.
+ if (aCookieData.rawSameSite() == aCookieData.sameSite()) {
+ return aCookieData.rawSameSite() >= nsICookie::SAMESITE_NONE &&
+ aCookieData.rawSameSite() <= nsICookie::SAMESITE_STRICT;
+ }
+ return aCookieData.rawSameSite() == nsICookie::SAMESITE_NONE &&
+ aCookieData.sameSite() == nsICookie::SAMESITE_LAX;
+}
+
+already_AddRefed<Cookie> Cookie::Clone() const {
+ return Create(mData, OriginAttributesRef());
+}
+
+NS_IMPL_ISUPPORTS(Cookie, nsICookie)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/Cookie.h b/netwerk/cookie/Cookie.h
new file mode 100644
index 0000000000..5fa3479268
--- /dev/null
+++ b/netwerk/cookie/Cookie.h
@@ -0,0 +1,165 @@
+/* -*- 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 mozilla_net_Cookie_h
+#define mozilla_net_Cookie_h
+
+#include "nsICookie.h"
+#include "nsIMemoryReporter.h"
+#include "nsString.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsIMemoryReporter.h"
+
+using mozilla::OriginAttributes;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * The Cookie class is the main cookie storage medium for use within cookie
+ * code.
+ */
+
+/******************************************************************************
+ * Cookie:
+ * implementation
+ ******************************************************************************/
+
+class Cookie final : public nsICookie {
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ public:
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIE
+
+ private:
+ // for internal use only. see Cookie::Create().
+ Cookie(const CookieStruct& aCookieData,
+ const OriginAttributes& aOriginAttributes)
+ : mData(aCookieData), mOriginAttributes(aOriginAttributes) {}
+
+ static already_AddRefed<Cookie> FromCookieStruct(
+ const CookieStruct& aCookieData,
+ const OriginAttributes& aOriginAttributes);
+
+ public:
+ // Returns false if rawSameSite has an invalid value, compared to sameSite.
+ static bool ValidateSameSite(const CookieStruct& aCookieData);
+
+ // Generate a unique and monotonically increasing creation time. See comment
+ // in Cookie.cpp.
+ static int64_t GenerateUniqueCreationTime(int64_t aCreationTime);
+
+ // public helper to create an Cookie object.
+ static already_AddRefed<Cookie> Create(
+ const CookieStruct& aCookieData,
+ const OriginAttributes& aOriginAttributes);
+
+ // Same as Cookie::Create but fixes the lastAccessed and creationDates
+ // if they are set in the future.
+ // Should only get called from CookiePersistentStorage::InitDBConn
+ static already_AddRefed<Cookie> CreateValidated(
+ const CookieStruct& aCookieData,
+ const OriginAttributes& aOriginAttributes);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // fast (inline, non-xpcom) getters
+ inline const nsCString& Name() const { return mData.name(); }
+ inline const nsCString& Value() const { return mData.value(); }
+ inline const nsCString& Host() const { return mData.host(); }
+ inline nsDependentCSubstring RawHost() const {
+ return nsDependentCSubstring(mData.host(), IsDomain() ? 1 : 0);
+ }
+ inline const nsCString& Path() const { return mData.path(); }
+ const nsCString& GetFilePath();
+ inline int64_t Expiry() const { return mData.expiry(); } // in seconds
+ inline int64_t LastAccessed() const {
+ return mData.lastAccessed();
+ } // in microseconds
+ inline int64_t CreationTime() const {
+ return mData.creationTime();
+ } // in microseconds
+ inline bool IsSession() const { return mData.isSession(); }
+ inline bool IsDomain() const { return *mData.host().get() == '.'; }
+ inline bool IsSecure() const { return mData.isSecure(); }
+ inline bool IsHttpOnly() const { return mData.isHttpOnly(); }
+ inline bool IsPartitioned() const {
+ return !mOriginAttributes.mPartitionKey.IsEmpty();
+ }
+ inline bool RawIsPartitioned() const { return mData.isPartitioned(); }
+ inline const OriginAttributes& OriginAttributesRef() const {
+ return mOriginAttributes;
+ }
+ inline int32_t SameSite() const { return mData.sameSite(); }
+ inline int32_t RawSameSite() const { return mData.rawSameSite(); }
+ inline bool IsDefaultSameSite() const {
+ return SameSite() == nsICookie::SAMESITE_LAX &&
+ RawSameSite() == nsICookie::SAMESITE_NONE;
+ }
+ inline uint8_t SchemeMap() const { return mData.schemeMap(); }
+
+ // setters
+ inline void SetExpiry(int64_t aExpiry) { mData.expiry() = aExpiry; }
+ inline void SetLastAccessed(int64_t aTime) { mData.lastAccessed() = aTime; }
+ inline void SetIsSession(bool aIsSession) { mData.isSession() = aIsSession; }
+ inline bool SetIsHttpOnly(bool aIsHttpOnly) {
+ return mData.isHttpOnly() = aIsHttpOnly;
+ }
+ // Set the creation time manually, overriding the monotonicity checks in
+ // Create(). Use with caution!
+ inline void SetCreationTime(int64_t aTime) { mData.creationTime() = aTime; }
+ inline void SetSchemeMap(uint8_t aSchemeMap) {
+ mData.schemeMap() = aSchemeMap;
+ }
+ inline void SetHost(const nsACString& aHost) { mData.host() = aHost; }
+
+ bool IsStale() const;
+
+ const CookieStruct& ToIPC() const { return mData; }
+
+ already_AddRefed<Cookie> Clone() const;
+
+ protected:
+ virtual ~Cookie() = default;
+
+ private:
+ // member variables
+ //
+ // Please update SizeOfIncludingThis if this strategy changes.
+ CookieStruct mData;
+ OriginAttributes mOriginAttributes;
+ nsCString mFilePathCache;
+};
+
+// Comparator class for sorting cookies before sending to a server.
+class CompareCookiesForSending {
+ public:
+ bool Equals(const Cookie* aCookie1, const Cookie* aCookie2) const {
+ return aCookie1->CreationTime() == aCookie2->CreationTime() &&
+ aCookie2->Path().Length() == aCookie1->Path().Length();
+ }
+
+ bool LessThan(const Cookie* aCookie1, const Cookie* aCookie2) const {
+ // compare by cookie path length in accordance with RFC2109
+ int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
+ if (result != 0) return result < 0;
+
+ // when path lengths match, older cookies should be listed first. this is
+ // required for backwards compatibility since some websites erroneously
+ // depend on receiving cookies in the order in which they were sent to the
+ // browser! see bug 236772.
+ return aCookie1->CreationTime() < aCookie2->CreationTime();
+ }
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Cookie_h
diff --git a/netwerk/cookie/CookieCommons.cpp b/netwerk/cookie/CookieCommons.cpp
new file mode 100644
index 0000000000..3708f23daa
--- /dev/null
+++ b/netwerk/cookie/CookieCommons.cpp
@@ -0,0 +1,749 @@
+/* -*- 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 "Cookie.h"
+#include "CookieCommons.h"
+#include "CookieLogging.h"
+#include "CookieService.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsContentUtils.h"
+#include "nsICookiePermission.h"
+#include "nsICookieService.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIRedirectHistoryEntry.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "nsScriptSecurityManager.h"
+#include "ThirdPartyUtil.h"
+
+namespace mozilla {
+
+using dom::Document;
+
+namespace net {
+
+// static
+bool CookieCommons::DomainMatches(Cookie* aCookie, const nsACString& aHost) {
+ // first, check for an exact host or domain cookie match, e.g. "google.com"
+ // or ".google.com"; second a subdomain match, e.g.
+ // host = "mail.google.com", cookie domain = ".google.com".
+ return aCookie->RawHost() == aHost ||
+ (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
+}
+
+// static
+bool CookieCommons::PathMatches(Cookie* aCookie, const nsACString& aPath) {
+ nsCString cookiePath(aCookie->GetFilePath());
+
+ // if our cookie path is empty we can't really perform our prefix check, and
+ // also we can't check the last character of the cookie path, so we would
+ // never return a successful match.
+ if (cookiePath.IsEmpty()) {
+ return false;
+ }
+
+ // if the cookie path and the request path are identical, they match.
+ if (cookiePath.Equals(aPath)) {
+ return true;
+ }
+
+ // if the cookie path is a prefix of the request path, and the last character
+ // of the cookie path is %x2F ("/"), they match.
+ bool isPrefix = StringBeginsWith(aPath, cookiePath);
+ if (isPrefix && cookiePath.Last() == '/') {
+ return true;
+ }
+
+ // if the cookie path is a prefix of the request path, and the first character
+ // of the request path that is not included in the cookie path is a %x2F ("/")
+ // character, they match.
+ uint32_t cookiePathLen = cookiePath.Length();
+ return isPrefix && aPath[cookiePathLen] == '/';
+}
+
+// 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, and aRequireHostMatch will be true to indicate that
+// substring matches should not be performed.
+nsresult CookieCommons::GetBaseDomain(nsIEffectiveTLDService* aTLDService,
+ nsIURI* aHostURI, nsACString& aBaseDomain,
+ bool& aRequireHostMatch) {
+ // get the base domain. this will fail if the host contains a leading dot,
+ // more than one trailing dot, or is otherwise malformed.
+ nsresult rv = aTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
+ aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
+ if (aRequireHostMatch) {
+ // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
+ // such as 'co.uk', or the empty string. use the host as a key in such
+ // cases.
+ rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, aBaseDomain);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
+ if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // block any URIs without a host that aren't file:// URIs.
+ if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+nsresult CookieCommons::GetBaseDomain(nsIPrincipal* aPrincipal,
+ nsACString& aBaseDomain) {
+ MOZ_ASSERT(aPrincipal);
+
+ // for historical reasons we use ascii host for file:// URLs.
+ if (aPrincipal->SchemeIs("file")) {
+ return nsContentUtils::GetHostOrIPv6WithBrackets(aPrincipal, aBaseDomain);
+ }
+
+ nsresult rv = aPrincipal->GetBaseDomain(aBaseDomain);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsContentUtils::MaybeFixIPv6Host(aBaseDomain);
+ return NS_OK;
+}
+
+// Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
+// "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it
+// is assumed that aHost is already normalized, and it may contain a leading dot
+// (indicating that it represents a domain). A trailing dot may be present.
+// If aHost 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, and a
+// leading dot will be treated as an error.
+nsresult CookieCommons::GetBaseDomainFromHost(
+ nsIEffectiveTLDService* aTLDService, const nsACString& aHost,
+ nsCString& aBaseDomain) {
+ // aHost must not be the string '.'.
+ if (aHost.Length() == 1 && aHost.Last() == '.') {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // aHost may contain a leading dot; if so, strip it now.
+ bool domain = !aHost.IsEmpty() && aHost.First() == '.';
+
+ // get the base domain. this will fail if the host contains a leading dot,
+ // more than one trailing dot, or is otherwise malformed.
+ nsresult rv = aTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0,
+ aBaseDomain);
+ if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ // aHost is either an IP address, an alias such as 'localhost', an eTLD
+ // such as 'co.uk', or the empty string. use the host as a key in such
+ // cases; however, we reject any such hosts with a leading dot, since it
+ // doesn't make sense for them to be domain cookies.
+ if (domain) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aBaseDomain = aHost;
+ return NS_OK;
+ }
+ return rv;
+}
+
+namespace {
+
+void NotifyRejectionToObservers(nsIURI* aHostURI, CookieOperation aOperation) {
+ if (aOperation == OPERATION_WRITE) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
+ }
+ } else {
+ MOZ_ASSERT(aOperation == OPERATION_READ);
+ }
+}
+
+} // namespace
+
+// Notify observers that a cookie was rejected due to the users' prefs.
+void CookieCommons::NotifyRejected(nsIURI* aHostURI, nsIChannel* aChannel,
+ uint32_t aRejectedReason,
+ CookieOperation aOperation) {
+ NotifyRejectionToObservers(aHostURI, aOperation);
+
+ ContentBlockingNotifier::OnDecision(
+ aChannel, ContentBlockingNotifier::BlockingDecision::eBlock,
+ aRejectedReason);
+}
+
+bool CookieCommons::CheckPathSize(const CookieStruct& aCookieData) {
+ return aCookieData.path().Length() <= kMaxBytesPerPath;
+}
+
+bool CookieCommons::CheckNameAndValueSize(const CookieStruct& aCookieData) {
+ // reject cookie if it's over the size limit, per RFC2109
+ return (aCookieData.name().Length() + aCookieData.value().Length()) <=
+ kMaxBytesPerCookie;
+}
+
+bool CookieCommons::CheckName(const CookieStruct& aCookieData) {
+ const char illegalNameCharacters[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
+ 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x3B, 0x3D, 0x7F, 0x00};
+
+ const auto* start = aCookieData.name().BeginReading();
+ const auto* end = aCookieData.name().EndReading();
+
+ auto charFilter = [&](unsigned char c) {
+ if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) {
+ return true;
+ }
+ return std::find(std::begin(illegalNameCharacters),
+ std::end(illegalNameCharacters),
+ c) != std::end(illegalNameCharacters);
+ };
+
+ return std::find_if(start, end, charFilter) == end;
+}
+
+bool CookieCommons::CheckValue(const CookieStruct& aCookieData) {
+ // reject cookie if value contains an RFC 6265 disallowed character - see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423
+ // NOTE: this is not the full set of characters disallowed by 6265 - notably
+ // 0x09, 0x20, 0x22, 0x2C, and 0x5C are missing from this list.
+ const char illegalCharacters[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C,
+ 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x3B, 0x7F, 0x00};
+
+ const auto* start = aCookieData.value().BeginReading();
+ const auto* end = aCookieData.value().EndReading();
+
+ auto charFilter = [&](unsigned char c) {
+ if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) {
+ return true;
+ }
+ return std::find(std::begin(illegalCharacters), std::end(illegalCharacters),
+ c) != std::end(illegalCharacters);
+ };
+
+ return std::find_if(start, end, charFilter) == end;
+}
+
+// static
+bool CookieCommons::CheckCookiePermission(nsIChannel* aChannel,
+ CookieStruct& aCookieData) {
+ if (!aChannel) {
+ // No channel, let's assume this is a system-principal request.
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return true;
+ }
+
+ nsIScriptSecurityManager* ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+ MOZ_ASSERT(ssm);
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(channelPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return CheckCookiePermission(channelPrincipal, cookieJarSettings,
+ aCookieData);
+}
+
+// static
+bool CookieCommons::CheckCookiePermission(
+ nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
+ CookieStruct& aCookieData) {
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aCookieJarSettings);
+
+ if (!aPrincipal->GetIsContentPrincipal()) {
+ return true;
+ }
+
+ uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
+ nsresult rv =
+ aCookieJarSettings->CookiePermission(aPrincipal, &cookiePermission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return true;
+ }
+
+ if (cookiePermission == nsICookiePermission::ACCESS_ALLOW) {
+ return true;
+ }
+
+ if (cookiePermission == nsICookiePermission::ACCESS_SESSION) {
+ aCookieData.isSession() = true;
+ return true;
+ }
+
+ if (cookiePermission == nsICookiePermission::ACCESS_DENY) {
+ return false;
+ }
+
+ return true;
+}
+
+namespace {
+
+CookieStatus CookieStatusForWindow(nsPIDOMWindowInner* aWindow,
+ nsIURI* aDocumentURI) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aDocumentURI);
+
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ if (thirdPartyUtil) {
+ bool isThirdParty = true;
+
+ nsresult rv = thirdPartyUtil->IsThirdPartyWindow(
+ aWindow->GetOuterWindow(), aDocumentURI, &isThirdParty);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Third-party window check failed.");
+
+ if (NS_SUCCEEDED(rv) && !isThirdParty) {
+ return STATUS_ACCEPTED;
+ }
+ }
+
+ if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) {
+ return STATUS_ACCEPT_SESSION;
+ }
+
+ if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly() &&
+ !nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aDocumentURI)) {
+ return STATUS_ACCEPT_SESSION;
+ }
+
+ return STATUS_ACCEPTED;
+}
+
+} // namespace
+
+// static
+already_AddRefed<Cookie> CookieCommons::CreateCookieFromDocument(
+ Document* aDocument, const nsACString& aCookieString,
+ int64_t currentTimeInUsec, nsIEffectiveTLDService* aTLDService,
+ mozIThirdPartyUtil* aThirdPartyUtil,
+ std::function<bool(const nsACString&, const OriginAttributes&)>&&
+ aHasExistingCookiesLambda,
+ nsIURI** aDocumentURI, nsACString& aBaseDomain, OriginAttributes& aAttrs) {
+ nsCOMPtr<nsIPrincipal> storagePrincipal =
+ aDocument->EffectiveCookiePrincipal();
+ MOZ_ASSERT(storagePrincipal);
+
+ nsCOMPtr<nsIURI> principalURI;
+ auto* basePrincipal = BasePrincipal::Cast(aDocument->NodePrincipal());
+ basePrincipal->GetURI(getter_AddRefs(principalURI));
+ if (NS_WARN_IF(!principalURI)) {
+ // Document's principal is not a content or null (may be system), so
+ // can't set cookies
+ return nullptr;
+ }
+
+ if (!CookieCommons::IsSchemeSupported(principalURI)) {
+ return nullptr;
+ }
+
+ nsAutoCString baseDomain;
+ bool requireHostMatch = false;
+ nsresult rv = CookieCommons::GetBaseDomain(aTLDService, principalURI,
+ baseDomain, requireHostMatch);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
+ if (NS_WARN_IF(!innerWindow)) {
+ return nullptr;
+ }
+
+ // Check if limit-foreign is required.
+ uint32_t dummyRejectedReason = 0;
+ if (aDocument->CookieJarSettings()->GetLimitForeignContexts() &&
+ !aHasExistingCookiesLambda(baseDomain,
+ storagePrincipal->OriginAttributesRef()) &&
+ !ShouldAllowAccessFor(innerWindow, principalURI, &dummyRejectedReason)) {
+ return nullptr;
+ }
+
+ bool isForeignAndNotAddon = false;
+ if (!BasePrincipal::Cast(aDocument->NodePrincipal())->AddonPolicy()) {
+ rv = aThirdPartyUtil->IsThirdPartyWindow(
+ innerWindow->GetOuterWindow(), principalURI, &isForeignAndNotAddon);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ isForeignAndNotAddon = true;
+ }
+ }
+
+ bool mustBePartitioned =
+ isForeignAndNotAddon &&
+ aDocument->CookieJarSettings()->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
+ !aDocument->UsingStorageAccess();
+
+ // If we are here, we have been already accepted by the anti-tracking.
+ // We just need to check if we have to be in session-only mode.
+ CookieStatus cookieStatus = CookieStatusForWindow(innerWindow, principalURI);
+ MOZ_ASSERT(cookieStatus == STATUS_ACCEPTED ||
+ cookieStatus == STATUS_ACCEPT_SESSION);
+
+ // Console report takes care of the correct reporting at the exit of this
+ // method.
+ RefPtr<ConsoleReportCollector> crc = new ConsoleReportCollector();
+ auto scopeExit = MakeScopeExit([&] { crc->FlushConsoleReports(aDocument); });
+
+ nsCString cookieString(aCookieString);
+
+ CookieStruct cookieData;
+ MOZ_ASSERT(cookieData.creationTime() == 0, "Must be initialized to 0");
+ bool canSetCookie = false;
+ CookieService::CanSetCookie(principalURI, baseDomain, cookieData,
+ requireHostMatch, cookieStatus, cookieString,
+ false, isForeignAndNotAddon, mustBePartitioned,
+ crc, canSetCookie);
+
+ if (!canSetCookie) {
+ return nullptr;
+ }
+
+ // check permissions from site permission list.
+ if (!CookieCommons::CheckCookiePermission(aDocument->NodePrincipal(),
+ aDocument->CookieJarSettings(),
+ cookieData)) {
+ NotifyRejectionToObservers(principalURI, OPERATION_WRITE);
+ ContentBlockingNotifier::OnDecision(
+ innerWindow, ContentBlockingNotifier::BlockingDecision::eBlock,
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION);
+ return nullptr;
+ }
+
+ RefPtr<Cookie> cookie =
+ Cookie::Create(cookieData, storagePrincipal->OriginAttributesRef());
+ MOZ_ASSERT(cookie);
+
+ cookie->SetLastAccessed(currentTimeInUsec);
+ cookie->SetCreationTime(
+ Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
+
+ aBaseDomain = baseDomain;
+ aAttrs = storagePrincipal->OriginAttributesRef();
+ principalURI.forget(aDocumentURI);
+
+ return cookie.forget();
+}
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieCommons::GetCookieJarSettings(
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ bool shouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
+ aChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
+ if (aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsresult rv =
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ cookieJarSettings =
+ CookieJarSettings::GetBlockingAll(shouldResistFingerprinting);
+ }
+ } else {
+ cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+ }
+
+ MOZ_ASSERT(cookieJarSettings);
+ return cookieJarSettings.forget();
+}
+
+// static
+bool CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
+ Cookie* aCookie, dom::Document* aDocument) {
+ MOZ_ASSERT(aCookie);
+ MOZ_ASSERT(aDocument);
+
+ int32_t sameSiteAttr = 0;
+ aCookie->GetSameSite(&sameSiteAttr);
+
+ if (aDocument->CookieJarSettings()->GetPartitionForeign() &&
+ StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
+ return false;
+ }
+
+ return sameSiteAttr == nsICookie::SAMESITE_NONE;
+}
+
+bool CookieCommons::IsSafeTopLevelNav(nsIChannel* aChannel) {
+ if (!aChannel) {
+ return false;
+ }
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo();
+ if ((loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT &&
+ loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) &&
+ !interceptionInfo) {
+ return false;
+ }
+
+ if (interceptionInfo &&
+ interceptionInfo->GetExtContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT &&
+ interceptionInfo->GetExtContentPolicyType() !=
+ ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD &&
+ interceptionInfo->GetExtContentPolicyType() !=
+ ExtContentPolicy::TYPE_INVALID) {
+ return false;
+ }
+
+ return NS_IsSafeMethodNav(aChannel);
+}
+
+// This function determines if two schemes are equal in the context of
+// "Schemeful SameSite cookies".
+//
+// Two schemes are considered equal:
+// - if the "network.cookie.sameSite.schemeful" pref is set to false.
+// OR
+// - if one of the schemes is not http or https.
+// OR
+// - if both schemes are equal AND both are either http or https.
+bool IsSameSiteSchemeEqual(const nsACString& aFirstScheme,
+ const nsACString& aSecondScheme) {
+ if (!StaticPrefs::network_cookie_sameSite_schemeful()) {
+ return true;
+ }
+
+ auto isSchemeHttpOrHttps = [](const nsACString& scheme) -> bool {
+ return scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https");
+ };
+
+ if (!isSchemeHttpOrHttps(aFirstScheme) ||
+ !isSchemeHttpOrHttps(aSecondScheme)) {
+ return true;
+ }
+
+ return aFirstScheme.Equals(aSecondScheme);
+}
+
+bool CookieCommons::IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI,
+ bool* aHadCrossSiteRedirects) {
+ *aHadCrossSiteRedirects = false;
+
+ if (!aChannel) {
+ return false;
+ }
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ // Do not treat loads triggered by web extensions as foreign
+ nsCOMPtr<nsIURI> channelURI;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+
+ nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo();
+
+ RefPtr<BasePrincipal> triggeringPrincipal;
+ ExtContentPolicy contentPolicyType;
+ if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) {
+ triggeringPrincipal =
+ BasePrincipal::Cast(interceptionInfo->TriggeringPrincipal());
+ contentPolicyType = interceptionInfo->GetExtContentPolicyType();
+ } else {
+ triggeringPrincipal = BasePrincipal::Cast(loadInfo->TriggeringPrincipal());
+ contentPolicyType = loadInfo->GetExternalContentPolicyType();
+
+ if (triggeringPrincipal->AddonPolicy() &&
+ triggeringPrincipal->AddonAllowsLoad(channelURI)) {
+ return false;
+ }
+ }
+ const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& redirectChain(
+ interceptionInfo && interceptionInfo->TriggeringPrincipal()
+ ? interceptionInfo->RedirectChain()
+ : loadInfo->RedirectChain());
+
+ nsAutoCString hostScheme, otherScheme;
+ aHostURI->GetScheme(hostScheme);
+
+ bool isForeign = true;
+ nsresult rv;
+ if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ contentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ // for loads of TYPE_DOCUMENT we query the hostURI from the
+ // triggeringPrincipal which returns the URI of the document that caused the
+ // navigation.
+ rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
+
+ triggeringPrincipal->GetScheme(otherScheme);
+ } else {
+ // If the load is caused by FetchEvent.request or NavigationPreload request,
+ // check the original InterceptedHttpChannel is a third-party channel or
+ // not.
+ if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) {
+ isForeign = interceptionInfo->FromThirdParty();
+ if (isForeign) {
+ return true;
+ }
+ }
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ if (!thirdPartyUtil) {
+ return true;
+ }
+ rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
+
+ channelURI->GetScheme(otherScheme);
+ }
+ // if we are dealing with a cross origin request, we can return here
+ // because we already know the request is 'foreign'.
+ if (NS_FAILED(rv) || isForeign) {
+ return true;
+ }
+
+ if (!IsSameSiteSchemeEqual(otherScheme, hostScheme)) {
+ // If the two schemes are not of the same http(s) scheme then we
+ // consider the request as foreign.
+ return true;
+ }
+
+ // for loads of TYPE_SUBDOCUMENT we have to perform an additional test,
+ // because a cross-origin iframe might perform a navigation to a same-origin
+ // iframe which would send same-site cookies. Hence, if the iframe navigation
+ // was triggered by a cross-origin triggeringPrincipal, we treat the load as
+ // foreign.
+ if (contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
+ if (NS_FAILED(rv) || isForeign) {
+ return true;
+ }
+ }
+
+ // for the purpose of same-site cookies we have to treat any cross-origin
+ // redirects as foreign. E.g. cross-site to same-site redirect is a problem
+ // with regards to CSRF.
+
+ nsCOMPtr<nsIPrincipal> redirectPrincipal;
+ for (nsIRedirectHistoryEntry* entry : redirectChain) {
+ entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
+ if (redirectPrincipal) {
+ rv = redirectPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
+ // if at any point we encounter a cross-origin redirect we can return.
+ if (NS_FAILED(rv) || isForeign) {
+ *aHadCrossSiteRedirects = isForeign;
+ return true;
+ }
+
+ nsAutoCString redirectScheme;
+ redirectPrincipal->GetScheme(redirectScheme);
+ if (!IsSameSiteSchemeEqual(redirectScheme, hostScheme)) {
+ // If the two schemes are not of the same http(s) scheme then we
+ // consider the request as foreign.
+ *aHadCrossSiteRedirects = true;
+ return true;
+ }
+ }
+ }
+ return isForeign;
+}
+
+// static
+nsICookie::schemeType CookieCommons::URIToSchemeType(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+ nsAutoCString scheme;
+ nsresult rv = aURI->GetScheme(scheme);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nsICookie::SCHEME_UNSET;
+ }
+
+ return SchemeToSchemeType(scheme);
+}
+
+// static
+nsICookie::schemeType CookieCommons::PrincipalToSchemeType(
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(aPrincipal);
+
+ nsAutoCString scheme;
+ nsresult rv = aPrincipal->GetScheme(scheme);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nsICookie::SCHEME_UNSET;
+ }
+
+ return SchemeToSchemeType(scheme);
+}
+
+// static
+nsICookie::schemeType CookieCommons::SchemeToSchemeType(
+ const nsACString& aScheme) {
+ MOZ_ASSERT(IsSchemeSupported(aScheme));
+
+ if (aScheme.Equals("https")) {
+ return nsICookie::SCHEME_HTTPS;
+ }
+
+ if (aScheme.Equals("http")) {
+ return nsICookie::SCHEME_HTTP;
+ }
+
+ if (aScheme.Equals("file")) {
+ return nsICookie::SCHEME_FILE;
+ }
+
+ MOZ_CRASH("Unsupported scheme type");
+}
+
+// static
+bool CookieCommons::IsSchemeSupported(nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(aPrincipal);
+
+ nsAutoCString scheme;
+ nsresult rv = aPrincipal->GetScheme(scheme);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return IsSchemeSupported(scheme);
+}
+
+// static
+bool CookieCommons::IsSchemeSupported(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+ nsAutoCString scheme;
+ nsresult rv = aURI->GetScheme(scheme);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return IsSchemeSupported(scheme);
+}
+
+// static
+bool CookieCommons::IsSchemeSupported(const nsACString& aScheme) {
+ return aScheme.Equals("https") || aScheme.Equals("http") ||
+ aScheme.Equals("file");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieCommons.h b/netwerk/cookie/CookieCommons.h
new file mode 100644
index 0000000000..f2b9df355a
--- /dev/null
+++ b/netwerk/cookie/CookieCommons.h
@@ -0,0 +1,142 @@
+/* -*- 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_CookieCommons_h
+#define mozilla_net_CookieCommons_h
+
+#include <cstdint>
+#include <functional>
+#include "mozIThirdPartyUtil.h"
+#include "prtime.h"
+#include "nsString.h"
+#include "nsICookie.h"
+#include "mozilla/net/NeckoChannelParams.h"
+
+class nsIChannel;
+class nsIConsoleReportCollector;
+class nsICookieJarSettings;
+class nsIEffectiveTLDService;
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+}
+
+namespace net {
+
+// these constants represent an operation being performed on cookies
+enum CookieOperation { OPERATION_READ, OPERATION_WRITE };
+
+// these constants represent a decision about a cookie based on user prefs.
+enum CookieStatus {
+ STATUS_ACCEPTED,
+ STATUS_ACCEPT_SESSION,
+ STATUS_REJECTED,
+ // STATUS_REJECTED_WITH_ERROR indicates the cookie should be rejected because
+ // of an error (rather than something the user can control). this is used for
+ // notification purposes, since we only want to notify of rejections where
+ // the user can do something about it (e.g. whitelist the site).
+ STATUS_REJECTED_WITH_ERROR
+};
+
+class Cookie;
+
+// pref string constants
+static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
+static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost";
+static const char kPrefCookieQuotaPerHost[] = "network.cookie.quotaPerHost";
+static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge";
+
+// default limits for the cookie list. these can be tuned by the
+// network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
+static const uint32_t kMaxCookiesPerHost = 180;
+static const uint32_t kCookieQuotaPerHost = 150;
+static const uint32_t kMaxNumberOfCookies = 3000;
+static const uint32_t kMaxBytesPerCookie = 4096;
+static const uint32_t kMaxBytesPerPath = 1024;
+
+static const int64_t kCookiePurgeAge =
+ int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
+
+class CookieCommons final {
+ public:
+ static bool DomainMatches(Cookie* aCookie, const nsACString& aHost);
+
+ static bool PathMatches(Cookie* aCookie, const nsACString& aPath);
+
+ static nsresult GetBaseDomain(nsIEffectiveTLDService* aTLDService,
+ nsIURI* aHostURI, nsACString& aBaseDomain,
+ bool& aRequireHostMatch);
+
+ static nsresult GetBaseDomain(nsIPrincipal* aPrincipal,
+ nsACString& aBaseDomain);
+
+ static nsresult GetBaseDomainFromHost(nsIEffectiveTLDService* aTLDService,
+ const nsACString& aHost,
+ nsCString& aBaseDomain);
+
+ static void NotifyRejected(nsIURI* aHostURI, nsIChannel* aChannel,
+ uint32_t aRejectedReason,
+ CookieOperation aOperation);
+
+ static bool CheckPathSize(const CookieStruct& aCookieData);
+
+ static bool CheckNameAndValueSize(const CookieStruct& aCookieData);
+
+ static bool CheckName(const CookieStruct& aCookieData);
+
+ static bool CheckValue(const CookieStruct& aCookieData);
+
+ static bool CheckCookiePermission(nsIChannel* aChannel,
+ CookieStruct& aCookieData);
+
+ static bool CheckCookiePermission(nsIPrincipal* aPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ CookieStruct& aCookieData);
+
+ static already_AddRefed<Cookie> CreateCookieFromDocument(
+ dom::Document* aDocument, const nsACString& aCookieString,
+ int64_t aCurrentTimeInUsec, nsIEffectiveTLDService* aTLDService,
+ mozIThirdPartyUtil* aThirdPartyUtil,
+ std::function<bool(const nsACString&, const OriginAttributes&)>&&
+ aHasExistingCookiesLambda,
+ nsIURI** aDocumentURI, nsACString& aBaseDomain, OriginAttributes& aAttrs);
+
+ static already_AddRefed<nsICookieJarSettings> GetCookieJarSettings(
+ nsIChannel* aChannel);
+
+ static bool ShouldIncludeCrossSiteCookieForDocument(Cookie* aCookie,
+ dom::Document* aDocument);
+
+ static bool IsSchemeSupported(nsIPrincipal* aPrincipal);
+ static bool IsSchemeSupported(nsIURI* aURI);
+ static bool IsSchemeSupported(const nsACString& aScheme);
+
+ static nsICookie::schemeType URIToSchemeType(nsIURI* aURI);
+
+ static nsICookie::schemeType PrincipalToSchemeType(nsIPrincipal* aPrincipal);
+
+ static nsICookie::schemeType SchemeToSchemeType(const nsACString& aScheme);
+
+ // Returns true if the channel is a safe top-level navigation or if it's a
+ // download request
+ static bool IsSafeTopLevelNav(nsIChannel* aChannel);
+
+ // Returns true if the channel is a foreign with respect to the host-uri.
+ // For loads of TYPE_DOCUMENT, this function returns true if it's a cross
+ // site navigation.
+ // `aHadCrossSiteRedirects` will be true iff the channel had a cross-site
+ // redirect before the final URI.
+ static bool IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI,
+ bool* aHadCrossSiteRedirects);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieCommons_h
diff --git a/netwerk/cookie/CookieJarSettings.cpp b/netwerk/cookie/CookieJarSettings.cpp
new file mode 100644
index 0000000000..09a4789d85
--- /dev/null
+++ b/netwerk/cookie/CookieJarSettings.cpp
@@ -0,0 +1,733 @@
+/* -*- 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/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/Permission.h"
+#include "mozilla/PermissionManager.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Unused.h"
+#include "nsIPrincipal.h"
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+# include "nsIProtocolHandler.h"
+#endif
+#include "nsIClassInfoImpl.h"
+#include "nsIChannel.h"
+#include "nsICookieManager.h"
+#include "nsICookieService.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_CLASSINFO(CookieJarSettings, nullptr, nsIClassInfo::THREADSAFE,
+ COOKIEJARSETTINGS_CID)
+
+NS_IMPL_ISUPPORTS_CI(CookieJarSettings, nsICookieJarSettings, nsISerializable)
+
+static StaticRefPtr<CookieJarSettings> sBlockinAll;
+
+namespace {
+
+class PermissionComparator {
+ public:
+ static bool Equals(nsIPermission* aA, nsIPermission* aB) {
+ nsCOMPtr<nsIPrincipal> principalA;
+ nsresult rv = aA->GetPrincipal(getter_AddRefs(principalA));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principalB;
+ rv = aB->GetPrincipal(getter_AddRefs(principalB));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ bool equals = false;
+ rv = principalA->Equals(principalB, &equals);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return equals;
+ }
+};
+
+class ReleaseCookiePermissions final : public Runnable {
+ public:
+ explicit ReleaseCookiePermissions(nsTArray<RefPtr<nsIPermission>>&& aArray)
+ : Runnable("ReleaseCookiePermissions"), mArray(std::move(aArray)) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ mArray.Clear();
+ return NS_OK;
+ }
+
+ private:
+ nsTArray<RefPtr<nsIPermission>> mArray;
+};
+
+} // namespace
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieJarSettings::GetBlockingAll(
+ bool aShouldResistFingerprinting) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sBlockinAll) {
+ return do_AddRef(sBlockinAll);
+ }
+
+ sBlockinAll = new CookieJarSettings(nsICookieService::BEHAVIOR_REJECT,
+ OriginAttributes::IsFirstPartyEnabled(),
+ aShouldResistFingerprinting, eFixed);
+ ClearOnShutdown(&sBlockinAll);
+
+ return do_AddRef(sBlockinAll);
+}
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieJarSettings::Create(
+ CreateMode aMode, bool aShouldResistFingerprinting) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<CookieJarSettings> cookieJarSettings;
+
+ switch (aMode) {
+ case eRegular:
+ case ePrivate:
+ cookieJarSettings = new CookieJarSettings(
+ nsICookieManager::GetCookieBehavior(aMode == ePrivate),
+ OriginAttributes::IsFirstPartyEnabled(), aShouldResistFingerprinting,
+ eProgressive);
+ break;
+
+ default:
+ MOZ_CRASH("Unexpected create mode.");
+ }
+
+ return cookieJarSettings.forget();
+}
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieJarSettings::Create(
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ aPrincipal, "We are constructing CookieJarSettings here.",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+
+ if (aPrincipal && aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0) {
+ return Create(ePrivate, shouldResistFingerprinting);
+ }
+
+ return Create(eRegular, shouldResistFingerprinting);
+}
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieJarSettings::Create(
+ uint32_t aCookieBehavior, const nsAString& aPartitionKey,
+ bool aIsFirstPartyIsolated, bool aIsOnContentBlockingAllowList,
+ bool aShouldResistFingerprinting) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<CookieJarSettings> cookieJarSettings =
+ new CookieJarSettings(aCookieBehavior, aIsFirstPartyIsolated,
+ aShouldResistFingerprinting, eProgressive);
+ cookieJarSettings->mPartitionKey = aPartitionKey;
+ cookieJarSettings->mIsOnContentBlockingAllowList =
+ aIsOnContentBlockingAllowList;
+
+ return cookieJarSettings.forget();
+}
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieJarSettings::CreateForXPCOM() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return Create(eRegular, /* shouldResistFingerprinting */ false);
+}
+
+CookieJarSettings::CookieJarSettings(uint32_t aCookieBehavior,
+ bool aIsFirstPartyIsolated,
+ bool aShouldResistFingerprinting,
+ State aState)
+ : mCookieBehavior(aCookieBehavior),
+ mIsFirstPartyIsolated(aIsFirstPartyIsolated),
+ mIsOnContentBlockingAllowList(false),
+ mIsOnContentBlockingAllowListUpdated(false),
+ mState(aState),
+ mToBeMerged(false),
+ mShouldResistFingerprinting(aShouldResistFingerprinting) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT_IF(
+ mIsFirstPartyIsolated,
+ mCookieBehavior !=
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
+}
+
+CookieJarSettings::~CookieJarSettings() {
+ if (!NS_IsMainThread() && !mCookiePermissions.IsEmpty()) {
+ RefPtr<Runnable> r =
+ new ReleaseCookiePermissions(std::move(mCookiePermissions));
+ MOZ_ASSERT(mCookiePermissions.IsEmpty());
+ SchedulerGroup::Dispatch(r.forget());
+ }
+}
+
+NS_IMETHODIMP
+CookieJarSettings::InitWithURI(nsIURI* aURI, bool aIsPrivate) {
+ NS_ENSURE_ARG(aURI);
+
+ mCookieBehavior = nsICookieManager::GetCookieBehavior(aIsPrivate);
+
+ SetPartitionKey(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetCookieBehavior(uint32_t* aCookieBehavior) {
+ *aCookieBehavior = mCookieBehavior;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetIsFirstPartyIsolated(bool* aIsFirstPartyIsolated) {
+ *aIsFirstPartyIsolated = mIsFirstPartyIsolated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetShouldResistFingerprinting(
+ bool* aShouldResistFingerprinting) {
+ *aShouldResistFingerprinting = mShouldResistFingerprinting;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetRejectThirdPartyContexts(
+ bool* aRejectThirdPartyContexts) {
+ *aRejectThirdPartyContexts =
+ CookieJarSettings::IsRejectThirdPartyContexts(mCookieBehavior);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetLimitForeignContexts(bool* aLimitForeignContexts) {
+ *aLimitForeignContexts =
+ mCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN ||
+ (StaticPrefs::privacy_dynamic_firstparty_limitForeign() &&
+ mCookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetBlockingAllThirdPartyContexts(
+ bool* aBlockingAllThirdPartyContexts) {
+ // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by
+ // simply rejecting the request to use the storage. In the future, if we
+ // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense
+ // for non-cookie storage types, this may change.
+ *aBlockingAllThirdPartyContexts =
+ mCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN ||
+ mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetBlockingAllContexts(bool* aBlockingAllContexts) {
+ *aBlockingAllContexts = mCookieBehavior == nsICookieService::BEHAVIOR_REJECT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetPartitionForeign(bool* aPartitionForeign) {
+ *aPartitionForeign =
+ mCookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::SetPartitionForeign(bool aPartitionForeign) {
+ if (mIsFirstPartyIsolated) {
+ return NS_OK;
+ }
+
+ if (aPartitionForeign) {
+ mCookieBehavior =
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetIsOnContentBlockingAllowList(
+ bool* aIsOnContentBlockingAllowList) {
+ *aIsOnContentBlockingAllowList = mIsOnContentBlockingAllowList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetPartitionKey(nsAString& aPartitionKey) {
+ aPartitionKey = mPartitionKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::GetFingerprintingRandomizationKey(
+ nsTArray<uint8_t>& aFingerprintingRandomizationKey) {
+ if (!mFingerprintingRandomKey) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aFingerprintingRandomizationKey = mFingerprintingRandomKey->Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::CookiePermission(nsIPrincipal* aPrincipal,
+ uint32_t* aCookiePermission) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aCookiePermission);
+
+ *aCookiePermission = nsIPermissionManager::UNKNOWN_ACTION;
+
+ nsresult rv;
+
+ // Let's see if we know this permission.
+ if (!mCookiePermissions.IsEmpty()) {
+ for (const RefPtr<nsIPermission>& permission : mCookiePermissions) {
+ bool match = false;
+ rv = permission->Matches(aPrincipal, false, &match);
+ if (NS_WARN_IF(NS_FAILED(rv)) || !match) {
+ continue;
+ }
+
+ rv = permission->GetCapability(aCookiePermission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+ }
+
+ // Let's ask the permission manager.
+ PermissionManager* pm = PermissionManager::GetInstance();
+ if (NS_WARN_IF(!pm)) {
+ return NS_ERROR_FAILURE;
+ }
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Check if this protocol doesn't allow cookies.
+ bool hasFlags;
+ nsCOMPtr<nsIURI> uri;
+ BasePrincipal::Cast(aPrincipal)->GetURI(getter_AddRefs(uri));
+
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS,
+ &hasFlags);
+ if (NS_FAILED(rv) || hasFlags) {
+ *aCookiePermission = PermissionManager::DENY_ACTION;
+ rv = NS_OK; // Reset, so it's not caught as a bad status after the `else`.
+ } else // Note the tricky `else` which controls the call below.
+#endif
+
+ rv = pm->TestPermissionFromPrincipal(aPrincipal, "cookie"_ns,
+ aCookiePermission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Let's store the permission, also if the result is UNKNOWN in order to avoid
+ // race conditions.
+
+ nsCOMPtr<nsIPermission> permission =
+ Permission::Create(aPrincipal, "cookie"_ns, *aCookiePermission, 0, 0, 0);
+ if (permission) {
+ mCookiePermissions.AppendElement(permission);
+ }
+
+ mToBeMerged = true;
+ return NS_OK;
+}
+
+void CookieJarSettings::Serialize(CookieJarSettingsArgs& aData) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ aData.isFixed() = mState == eFixed;
+ aData.cookieBehavior() = mCookieBehavior;
+ aData.isFirstPartyIsolated() = mIsFirstPartyIsolated;
+ aData.shouldResistFingerprinting() = mShouldResistFingerprinting;
+ aData.isOnContentBlockingAllowList() = mIsOnContentBlockingAllowList;
+ aData.partitionKey() = mPartitionKey;
+ if (mFingerprintingRandomKey) {
+ aData.hasFingerprintingRandomizationKey() = true;
+ aData.fingerprintingRandomizationKey() = mFingerprintingRandomKey->Clone();
+ } else {
+ aData.hasFingerprintingRandomizationKey() = false;
+ }
+
+ for (const RefPtr<nsIPermission>& permission : mCookiePermissions) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = permission->GetPrincipal(getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ ipc::PrincipalInfo principalInfo;
+ rv = PrincipalToPrincipalInfo(principal, &principalInfo,
+ true /* aSkipBaseDomain */);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ uint32_t cookiePermission = 0;
+ rv = permission->GetCapability(&cookiePermission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ aData.cookiePermissions().AppendElement(
+ CookiePermissionData(principalInfo, cookiePermission));
+ }
+
+ mToBeMerged = false;
+}
+
+/* static */ void CookieJarSettings::Deserialize(
+ const CookieJarSettingsArgs& aData,
+ nsICookieJarSettings** aCookieJarSettings) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ CookiePermissionList list;
+ for (const CookiePermissionData& data : aData.cookiePermissions()) {
+ auto principalOrErr = PrincipalInfoToPrincipal(data.principalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ nsCOMPtr<nsIPermission> permission = Permission::Create(
+ principal, "cookie"_ns, data.cookiePermission(), 0, 0, 0);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+
+ list.AppendElement(permission);
+ }
+
+ RefPtr<CookieJarSettings> cookieJarSettings = new CookieJarSettings(
+ aData.cookieBehavior(), aData.isFirstPartyIsolated(),
+ aData.shouldResistFingerprinting(),
+ aData.isFixed() ? eFixed : eProgressive);
+
+ cookieJarSettings->mIsOnContentBlockingAllowList =
+ aData.isOnContentBlockingAllowList();
+ cookieJarSettings->mCookiePermissions = std::move(list);
+ cookieJarSettings->mPartitionKey = aData.partitionKey();
+ cookieJarSettings->mShouldResistFingerprinting =
+ aData.shouldResistFingerprinting();
+
+ if (aData.hasFingerprintingRandomizationKey()) {
+ cookieJarSettings->mFingerprintingRandomKey.emplace(
+ aData.fingerprintingRandomizationKey().Clone());
+ }
+
+ cookieJarSettings.forget(aCookieJarSettings);
+}
+
+void CookieJarSettings::Merge(const CookieJarSettingsArgs& aData) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(
+ mCookieBehavior == aData.cookieBehavior() ||
+ (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
+ aData.cookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) ||
+ (mCookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
+ aData.cookieBehavior() == nsICookieService::BEHAVIOR_REJECT_TRACKER));
+
+ if (mState == eFixed) {
+ return;
+ }
+
+ // Merge cookie behavior pref values
+ if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
+ aData.cookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ // If the other side has decided to partition third-party cookies, update
+ // our side when first-party isolation is disabled.
+ if (!mIsFirstPartyIsolated) {
+ mCookieBehavior =
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
+ }
+ }
+ if (mCookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
+ aData.cookieBehavior() == nsICookieService::BEHAVIOR_REJECT_TRACKER) {
+ // If we've decided to partition third-party cookies, the other side may not
+ // have caught up yet unless it has first-party isolation enabled.
+ if (aData.isFirstPartyIsolated()) {
+ mCookieBehavior = nsICookieService::BEHAVIOR_REJECT_TRACKER;
+ mIsFirstPartyIsolated = true;
+ }
+ }
+ // Ignore all other cases.
+ MOZ_ASSERT_IF(
+ mIsFirstPartyIsolated,
+ mCookieBehavior !=
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
+
+ if (aData.shouldResistFingerprinting()) {
+ mShouldResistFingerprinting = true;
+ }
+
+ PermissionComparator comparator;
+
+ for (const CookiePermissionData& data : aData.cookiePermissions()) {
+ auto principalOrErr = PrincipalInfoToPrincipal(data.principalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+ nsCOMPtr<nsIPermission> permission = Permission::Create(
+ principal, "cookie"_ns, data.cookiePermission(), 0, 0, 0);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+
+ if (!mCookiePermissions.Contains(permission, comparator)) {
+ mCookiePermissions.AppendElement(permission);
+ }
+ }
+}
+
+void CookieJarSettings::SetPartitionKey(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+ OriginAttributes attrs;
+ attrs.SetPartitionKey(aURI);
+ mPartitionKey = std::move(attrs.mPartitionKey);
+}
+
+void CookieJarSettings::UpdateIsOnContentBlockingAllowList(
+ nsIChannel* aChannel) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aChannel);
+
+ // Early return if the flag was updated before.
+ if (mIsOnContentBlockingAllowListUpdated) {
+ return;
+ }
+ mIsOnContentBlockingAllowListUpdated = true;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // We need to recompute the ContentBlockingAllowListPrincipal here for the
+ // top level channel because we might navigate from the the initial
+ // about:blank page or the existing page which may have a different origin
+ // than the URI we are going to load here. Thus, we need to recompute the
+ // prinicpal in order to get the correct ContentBlockingAllowListPrincipal.
+ nsCOMPtr<nsIPrincipal> contentBlockingAllowListPrincipal;
+ OriginAttributes attrs;
+ loadInfo->GetOriginAttributes(&attrs);
+ ContentBlockingAllowList::RecomputePrincipal(
+ uri, attrs, getter_AddRefs(contentBlockingAllowListPrincipal));
+
+ if (!contentBlockingAllowListPrincipal ||
+ !contentBlockingAllowListPrincipal->GetIsContentPrincipal()) {
+ return;
+ }
+
+ Unused << ContentBlockingAllowList::Check(contentBlockingAllowListPrincipal,
+ NS_UsePrivateBrowsing(aChannel),
+ mIsOnContentBlockingAllowList);
+}
+
+// static
+bool CookieJarSettings::IsRejectThirdPartyContexts(uint32_t aCookieBehavior) {
+ return aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ aCookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::Read(nsIObjectInputStream* aStream) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsresult rv = aStream->Read32(&mCookieBehavior);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->ReadBoolean(&mIsFirstPartyIsolated);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->ReadBoolean(&mShouldResistFingerprinting);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isFixed;
+ aStream->ReadBoolean(&isFixed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mState = isFixed ? eFixed : eProgressive;
+
+ rv = aStream->ReadBoolean(&mIsOnContentBlockingAllowList);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->ReadString(mPartitionKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Deserializing the cookie permission list.
+ uint32_t cookiePermissionsLength;
+ rv = aStream->Read32(&cookiePermissionsLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!cookiePermissionsLength) {
+ // Bailing out early because there is no cookie permission.
+ return NS_OK;
+ }
+
+ CookiePermissionList list;
+ mCookiePermissions.SetCapacity(cookiePermissionsLength);
+ for (uint32_t i = 0; i < cookiePermissionsLength; ++i) {
+ nsAutoCString principalJSON;
+ aStream->ReadCString(principalJSON);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(principalJSON);
+
+ if (NS_WARN_IF(!principal)) {
+ continue;
+ }
+
+ uint32_t cookiePermission;
+ aStream->Read32(&cookiePermission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ Permission::Create(principal, "cookie"_ns, cookiePermission, 0, 0, 0);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+
+ list.AppendElement(permission);
+ }
+
+ mCookiePermissions = std::move(list);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieJarSettings::Write(nsIObjectOutputStream* aStream) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsresult rv = aStream->Write32(mCookieBehavior);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteBoolean(mIsFirstPartyIsolated);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteBoolean(mShouldResistFingerprinting);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteBoolean(mState == eFixed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteBoolean(mIsOnContentBlockingAllowList);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteWStringZ(mPartitionKey.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Serializing the cookie permission list. It will first write the length of
+ // the list, and then, write the cookie permission consecutively.
+ uint32_t cookiePermissionsLength = mCookiePermissions.Length();
+ rv = aStream->Write32(cookiePermissionsLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (const RefPtr<nsIPermission>& permission : mCookiePermissions) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = permission->GetPrincipal(getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsAutoCString principalJSON;
+ BasePrincipal::Cast(principal)->ToJSON(principalJSON);
+
+ rv = aStream->WriteStringZ(principalJSON.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t cookiePermission = 0;
+ rv = permission->GetCapability(&cookiePermission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ rv = aStream->Write32(cookiePermission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieJarSettings.h b/netwerk/cookie/CookieJarSettings.h
new file mode 100644
index 0000000000..97f8528a55
--- /dev/null
+++ b/netwerk/cookie/CookieJarSettings.h
@@ -0,0 +1,267 @@
+/* -*- 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_CookieJarSettings_h
+#define mozilla_net_CookieJarSettings_h
+
+#include "mozilla/Maybe.h"
+
+#include "nsICookieJarSettings.h"
+#include "nsTArray.h"
+
+#define COOKIEJARSETTINGS_CONTRACTID "@mozilla.org/cookieJarSettings;1"
+// 4ce234f1-52e8-47a9-8c8d-b02f815733c7
+#define COOKIEJARSETTINGS_CID \
+ { \
+ 0x4ce234f1, 0x52e8, 0x47a9, { \
+ 0x8c, 0x8d, 0xb0, 0x2f, 0x81, 0x57, 0x33, 0xc7 \
+ } \
+ }
+
+class nsIPermission;
+
+namespace mozilla {
+namespace net {
+
+class CookieJarSettingsArgs;
+
+/**
+ * CookieJarSettings
+ * ~~~~~~~~~~~~~~
+ *
+ * CookieJarSettings is a snapshot of the cookie jar's configurations in a
+ * precise moment of time, such as the cookie policy and cookie permissions.
+ * This object is used by top-level documents to have a consistent cookie jar
+ * configuration also in case the user changes it. New configurations will apply
+ * only to new top-level documents.
+ *
+ * CookieJarSettings creation
+ * ~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * CookieJarSettings is created when the top-level document's nsIChannel's
+ * nsILoadInfo is constructed. Any sub-resource and any sub-document inherits it
+ * from that nsILoadInfo. Also dedicated workers and their resources inherit it
+ * from the parent document.
+ *
+ * SharedWorkers and ServiceWorkers have their own CookieJarSettings because
+ * they don't have a single parent document (SharedWorkers could have more than
+ * one, ServiceWorkers have none).
+ *
+ * In Chrome code, we have a new CookieJarSettings when we download resources
+ * via 'Save-as...' and we also have a new CookieJarSettings for favicon
+ * downloading.
+ *
+ * Content-scripts WebExtensions also have their own CookieJarSettings because
+ * they don't have a direct access to the document they are running into.
+ *
+ * Anything else will have a special CookieJarSettings which blocks everything
+ * (CookieJarSettings::GetBlockingAll()) by forcing BEHAVIOR_REJECT as policy.
+ * When this happens, that context will not have access to the cookie jar and no
+ * cookies are sent or received.
+ *
+ * Propagation of CookieJarSettings
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * CookieJarSettings are shared inside the same top-level document via its
+ * nsIChannel's nsILoadInfo. This is done automatically if you pass a nsINode
+ * to NS_NewChannel(), and it must be done manually if you use a different
+ * channel constructor. For instance, this happens for any worker networking
+ * operation.
+ *
+ * We use the same CookieJarSettings for any resource belonging to the top-level
+ * document even if cross-origin. This makes the browser behave consistently a
+ * scenario where A loads B which loads A again, and cookie policy/permission
+ * changes in the meantime.
+ *
+ * Cookie Permissions propagation
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * CookieJarSettings populates the known cookie permissions only when required.
+ * Initially the list is empty, but when CookieJarSettings::CookiePermission()
+ * is called, the requested permission is stored in the internal list if it
+ * doesn't exist yet.
+ *
+ * This is actually nice because it relies on the permission propagation from
+ * parent to content process. No extra IPC is required.
+ *
+ * Note that we store permissions with UNKNOWN_ACTION values too because they
+ * can be set after the loading of the top-level document and we don't want to
+ * return a different value when this happens.
+ *
+ * Use of CookieJarSettings
+ * ~~~~~~~~~~~~~~~~~~~~~
+ *
+ * In theory, there should not be direct access to cookie permissions or
+ * cookieBehavior pref. Everything should pass through CookieJarSettings.
+ *
+ * A reference to CookieJarSettings can be obtained from
+ * nsILoadInfo::GetCookieJarSettings(), from Document::CookieJarSettings() and
+ * from the WorkerPrivate::CookieJarSettings().
+ *
+ * CookieJarSettings is thread-safe, but the permission list must be touched
+ * only on the main-thread.
+ *
+ * Testing
+ * ~~~~~~~
+ *
+ * If you need to test the changing of cookie policy or a cookie permission, you
+ * need to workaround CookieJarSettings. This can be done opening a new window
+ * and running the test into that new global.
+ */
+
+/**
+ * Class that provides an nsICookieJarSettings implementation.
+ */
+class CookieJarSettings final : public nsICookieJarSettings {
+ public:
+ typedef nsTArray<RefPtr<nsIPermission>> CookiePermissionList;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICOOKIEJARSETTINGS
+ NS_DECL_NSISERIALIZABLE
+
+ static already_AddRefed<nsICookieJarSettings> GetBlockingAll(
+ bool aShouldResistFingerprinting);
+
+ enum CreateMode { eRegular, ePrivate };
+
+ static already_AddRefed<nsICookieJarSettings> Create(
+ CreateMode aMode, bool aShouldResistFingerprinting);
+
+ static already_AddRefed<nsICookieJarSettings> Create(
+ nsIPrincipal* aPrincipal);
+
+ // This function should be only called for XPCOM. You should never use this
+ // for other purposes.
+ static already_AddRefed<nsICookieJarSettings> CreateForXPCOM();
+
+ static already_AddRefed<nsICookieJarSettings> Create(
+ uint32_t aCookieBehavior, const nsAString& aPartitionKey,
+ bool aIsFirstPartyIsolated, bool aIsOnContentBlockingAllowList,
+ bool aShouldResistFingerprinting);
+
+ static CookieJarSettings* Cast(nsICookieJarSettings* aCS) {
+ return static_cast<CookieJarSettings*>(aCS);
+ }
+
+ void Serialize(CookieJarSettingsArgs& aData);
+
+ static void Deserialize(const CookieJarSettingsArgs& aData,
+ nsICookieJarSettings** aCookieJarSettings);
+
+ void Merge(const CookieJarSettingsArgs& aData);
+
+ // We don't want to send this object from parent to child process if there are
+ // no reasons. HasBeenChanged() returns true if the object has changed its
+ // internal state and it must be sent beck to the content process.
+ bool HasBeenChanged() const { return mToBeMerged; }
+
+ void UpdateIsOnContentBlockingAllowList(nsIChannel* aChannel);
+
+ void SetPartitionKey(nsIURI* aURI);
+ void SetPartitionKey(const nsAString& aPartitionKey) {
+ mPartitionKey = aPartitionKey;
+ }
+ const nsAString& GetPartitionKey() { return mPartitionKey; };
+
+ void SetFingerprintingRandomizationKey(const nsTArray<uint8_t>& aKey) {
+ mFingerprintingRandomKey.reset();
+
+ mFingerprintingRandomKey.emplace(aKey.Clone());
+ }
+
+ // Utility function to test if the passed cookiebahvior is
+ // BEHAVIOR_REJECT_TRACKER, BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN or
+ // BEHAVIOR_REJECT_FOREIGN when
+ // network.cookie.rejectForeignWithExceptions.enabled pref is set to true.
+ static bool IsRejectThirdPartyContexts(uint32_t aCookieBehavior);
+
+ private:
+ enum State {
+ // No cookie permissions are allowed to be stored in this object.
+ eFixed,
+
+ // Cookie permissions can be stored in case they are unknown when they are
+ // asked or when they are sent from the parent process.
+ eProgressive,
+ };
+
+ CookieJarSettings(uint32_t aCookieBehavior, bool aIsFirstPartyIsolated,
+ bool aShouldResistFingerprinting, State aState);
+ ~CookieJarSettings();
+
+ uint32_t mCookieBehavior;
+ bool mIsFirstPartyIsolated;
+ CookiePermissionList mCookiePermissions;
+ bool mIsOnContentBlockingAllowList;
+ bool mIsOnContentBlockingAllowListUpdated;
+ nsString mPartitionKey;
+
+ State mState;
+
+ bool mToBeMerged;
+
+ // DO NOT USE THIS MEMBER TO CHECK IF YOU SHOULD RESIST FINGERPRINTING.
+ // USE THE nsContentUtils::ShouldResistFingerprinting() METHODS ONLY.
+ //
+ // As we move to fine-grained RFP control, we want to support per-domain
+ // exemptions from ResistFingerprinting. Specifically the behavior should be
+ // as such:
+ //
+ // Top-Level Document is on an Exempted Domain
+ // - RFP is disabled.
+ //
+ // Top-Level Document on an Exempted Domain embedding a non-exempted
+ // cross-origin iframe
+ // - RFP in the iframe is enabled (NOT exempted). (**)
+ //
+ // Top-Level Document on an Exempted Domain embedding an exempted cross-origin
+ // iframe
+ // - RFP in the iframe is disabled (exempted).
+ //
+ // Top-Level Document on a Non-Exempted Domain
+ // - RFP is enabled (NOT exempted).
+ //
+ // Top-Level Document on a Non-Exempted Domain embeds an exempted cross-origin
+ // iframe
+ // - RFP in the iframe is enabled (NOT exempted). (*)
+ //
+ // Exempted Document (top-level or iframe) contacts any cross-origin domain
+ // (exempted or non-exempted)
+ // - RFP is disabled (exempted) for the request
+ //
+ // Non-Exempted Document (top-level or iframe) contacts any cross-origin
+ // domain
+ // (exempted or non-exempted)
+ // - RFP is enabled (NOT exempted) for the request
+ //
+ // This boolean on CookieJarSettings will enable us to apply the most
+ // difficult rule, marked in (*). (It is difficult because the
+ // subdocument's loadinfo will look like it should be exempted.)
+ // However if we trusted this member blindly, it would not correctly apply
+ // the one marked with (**). (Because it would inherit an exemption into
+ // a subdocument that should not be exempted.)
+ // To handle this case, we only trust a CookieJar's ShouldRFP value if it
+ // says we should resist fingerprinting. If it says that we _should not_,
+ // we continue and check the channel's URI or LoadInfo and if
+ // the domain specified there is not an exempted domain, enforce RFP anyway.
+ // This all occurrs in the nscontentUtils::ShouldResistFingerprinting
+ // functions which you should be using.
+ bool mShouldResistFingerprinting;
+
+ // The key used to generate the random noise for randomizing the browser
+ // fingerprint. The key is decided by the session key and the top-level site.
+ // So, the browse fingerprint will look different to the same tracker
+ // under different top-level sites. Also, the fingerprint will change as
+ // browsing session changes. This can prevent trackers to identify individuals
+ // by using browser fingerprints.
+ Maybe<nsTArray<uint8_t>> mFingerprintingRandomKey;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieJarSettings_h
diff --git a/netwerk/cookie/CookieKey.h b/netwerk/cookie/CookieKey.h
new file mode 100644
index 0000000000..86291a187e
--- /dev/null
+++ b/netwerk/cookie/CookieKey.h
@@ -0,0 +1,61 @@
+/* -*- 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_CookieKey_h
+#define mozilla_net_CookieKey_h
+
+#include "mozilla/OriginAttributes.h"
+#include "nsHashKeys.h"
+
+namespace mozilla {
+namespace net {
+
+class CookieKey : public PLDHashEntryHdr {
+ public:
+ typedef const CookieKey& KeyType;
+ typedef const CookieKey* KeyTypePointer;
+
+ CookieKey() = default;
+
+ CookieKey(const nsACString& baseDomain, const OriginAttributes& attrs)
+ : mBaseDomain(baseDomain), mOriginAttributes(attrs) {}
+
+ explicit CookieKey(KeyTypePointer other)
+ : mBaseDomain(other->mBaseDomain),
+ mOriginAttributes(other->mOriginAttributes) {}
+
+ CookieKey(CookieKey&& other) = default;
+ CookieKey& operator=(CookieKey&&) = default;
+
+ bool KeyEquals(KeyTypePointer other) const {
+ return mBaseDomain == other->mBaseDomain &&
+ mOriginAttributes == other->mOriginAttributes;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ nsAutoCString temp(aKey->mBaseDomain);
+ temp.Append('#');
+ nsAutoCString suffix;
+ aKey->mOriginAttributes.CreateSuffix(suffix);
+ temp.Append(suffix);
+ return HashString(temp);
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ nsCString mBaseDomain;
+ OriginAttributes mOriginAttributes;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieKey_h
diff --git a/netwerk/cookie/CookieLogging.cpp b/netwerk/cookie/CookieLogging.cpp
new file mode 100644
index 0000000000..9f5262b2bb
--- /dev/null
+++ b/netwerk/cookie/CookieLogging.cpp
@@ -0,0 +1,185 @@
+/* -*- 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 "CookieLogging.h"
+#include "Cookie.h"
+#include "nsIConsoleReportCollector.h"
+
+constexpr auto TIME_STRING_LENGTH = 40;
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gCookieLog("cookie");
+
+static const char* SameSiteToString(uint32_t aSameSite) {
+ switch (aSameSite) {
+ case nsICookie::SAMESITE_NONE:
+ return "none";
+ case nsICookie::SAMESITE_LAX:
+ return "lax";
+ case nsICookie::SAMESITE_STRICT:
+ return "strict";
+ default:
+ MOZ_CRASH("Invalid nsICookie sameSite value");
+ return "";
+ }
+}
+
+// static
+void CookieLogging::LogSuccess(bool aSetCookie, nsIURI* aHostURI,
+ const nsACString& aCookieString, Cookie* aCookie,
+ bool aReplacing) {
+ // if logging isn't enabled, return now to save cycles
+ if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) {
+ return;
+ }
+
+ nsAutoCString spec;
+ if (aHostURI) {
+ aHostURI->GetAsciiSpec(spec);
+ }
+
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("request URL: %s\n", spec.get()));
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("cookie string: %s\n", aCookieString.BeginReading()));
+ if (aSetCookie) {
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
+ }
+
+ LogCookie(aCookie);
+
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("\n"));
+}
+
+// static
+void CookieLogging::LogFailure(bool aSetCookie, nsIURI* aHostURI,
+ const nsACString& aCookieString,
+ const char* aReason) {
+ // if logging isn't enabled, return now to save cycles
+ if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
+ return;
+ }
+
+ nsAutoCString spec;
+ if (aHostURI) {
+ aHostURI->GetAsciiSpec(spec);
+ }
+
+ MOZ_LOG(gCookieLog, LogLevel::Warning,
+ ("===== %s =====\n",
+ aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
+ MOZ_LOG(gCookieLog, LogLevel::Warning, ("request URL: %s\n", spec.get()));
+ if (aSetCookie) {
+ MOZ_LOG(gCookieLog, LogLevel::Warning,
+ ("cookie string: %s\n", aCookieString.BeginReading()));
+ }
+
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
+ char timeString[TIME_STRING_LENGTH];
+ PR_FormatTimeUSEnglish(timeString, TIME_STRING_LENGTH, "%c GMT",
+ &explodedTime);
+
+ MOZ_LOG(gCookieLog, LogLevel::Warning, ("current time: %s", timeString));
+ MOZ_LOG(gCookieLog, LogLevel::Warning, ("rejected because %s\n", aReason));
+ MOZ_LOG(gCookieLog, LogLevel::Warning, ("\n"));
+}
+
+// static
+void CookieLogging::LogCookie(Cookie* aCookie) {
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
+ char timeString[TIME_STRING_LENGTH];
+ PR_FormatTimeUSEnglish(timeString, TIME_STRING_LENGTH, "%c GMT",
+ &explodedTime);
+
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("current time: %s", timeString));
+
+ if (aCookie) {
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("----------------\n"));
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("name: %s\n", aCookie->Name().get()));
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("value: %s\n", aCookie->Value().get()));
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("%s: %s\n", aCookie->IsDomain() ? "domain" : "host",
+ aCookie->Host().get()));
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("path: %s\n", aCookie->Path().get()));
+
+ PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
+ PR_GMTParameters, &explodedTime);
+ PR_FormatTimeUSEnglish(timeString, TIME_STRING_LENGTH, "%c GMT",
+ &explodedTime);
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("expires: %s%s", timeString,
+ aCookie->IsSession() ? " (at end of session)" : ""));
+
+ PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
+ PR_FormatTimeUSEnglish(timeString, TIME_STRING_LENGTH, "%c GMT",
+ &explodedTime);
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("created: %s", timeString));
+
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("sameSite: %s - rawSameSite: %s\n",
+ SameSiteToString(aCookie->SameSite()),
+ SameSiteToString(aCookie->RawSameSite())));
+ MOZ_LOG(
+ gCookieLog, LogLevel::Debug,
+ ("schemeMap %d (http: %s | https: %s | file: %s)\n",
+ aCookie->SchemeMap(),
+ (aCookie->SchemeMap() & nsICookie::SCHEME_HTTP ? "true" : "false"),
+ (aCookie->SchemeMap() & nsICookie::SCHEME_HTTPS ? "true" : "false"),
+ (aCookie->SchemeMap() & nsICookie::SCHEME_FILE ? "true" : "false")));
+
+ nsAutoCString suffix;
+ aCookie->OriginAttributesRef().CreateSuffix(suffix);
+ MOZ_LOG(gCookieLog, LogLevel::Debug,
+ ("origin attributes: %s\n",
+ suffix.IsEmpty() ? "{empty}" : suffix.get()));
+ }
+}
+
+// static
+void CookieLogging::LogEvicted(Cookie* aCookie, const char* details) {
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("===== COOKIE EVICTED =====\n"));
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("%s\n", details));
+
+ LogCookie(aCookie);
+
+ MOZ_LOG(gCookieLog, LogLevel::Debug, ("\n"));
+}
+
+// static
+void CookieLogging::LogMessageToConsole(nsIConsoleReportCollector* aCRC,
+ nsIURI* aURI, uint32_t aErrorFlags,
+ const nsACString& aCategory,
+ const nsACString& aMsg,
+ const nsTArray<nsString>& aParams) {
+ if (!aCRC) {
+ return;
+ }
+
+ nsAutoCString uri;
+ if (aURI) {
+ nsresult rv = aURI->GetSpec(uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+
+ aCRC->AddConsoleReport(aErrorFlags, aCategory,
+ nsContentUtils::eNECKO_PROPERTIES, uri, 0, 0, aMsg,
+ aParams);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieLogging.h b/netwerk/cookie/CookieLogging.h
new file mode 100644
index 0000000000..19721914c8
--- /dev/null
+++ b/netwerk/cookie/CookieLogging.h
@@ -0,0 +1,65 @@
+/* -*- 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_CookieLogging_h
+#define mozilla_net_CookieLogging_h
+
+#include "mozilla/Logging.h"
+#include "nsString.h"
+
+class nsIConsoleReportCollector;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+// define logging macros for convenience
+#define SET_COOKIE true
+#define GET_COOKIE false
+
+extern LazyLogModule gCookieLog;
+
+#define COOKIE_LOGFAILURE(a, b, c, d) CookieLogging::LogFailure(a, b, c, d)
+#define COOKIE_LOGSUCCESS(a, b, c, d, e) \
+ CookieLogging::LogSuccess(a, b, c, d, e)
+
+#define COOKIE_LOGEVICTED(a, details) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) \
+ CookieLogging::LogEvicted(a, details); \
+ PR_END_MACRO
+
+#define COOKIE_LOGSTRING(lvl, fmt) \
+ PR_BEGIN_MACRO \
+ MOZ_LOG(gCookieLog, lvl, fmt); \
+ MOZ_LOG(gCookieLog, lvl, ("\n")); \
+ PR_END_MACRO
+
+class Cookie;
+
+class CookieLogging final {
+ public:
+ static void LogSuccess(bool aSetCookie, nsIURI* aHostURI,
+ const nsACString& aCookieString, Cookie* aCookie,
+ bool aReplacing);
+
+ static void LogFailure(bool aSetCookie, nsIURI* aHostURI,
+ const nsACString& aCookieString, const char* aReason);
+
+ static void LogCookie(Cookie* aCookie);
+
+ static void LogEvicted(Cookie* aCookie, const char* aDetails);
+
+ static void LogMessageToConsole(nsIConsoleReportCollector* aCRC, nsIURI* aURI,
+ uint32_t aErrorFlags,
+ const nsACString& aCategory,
+ const nsACString& aMsg,
+ const nsTArray<nsString>& aParams);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieLogging_h
diff --git a/netwerk/cookie/CookieNotification.cpp b/netwerk/cookie/CookieNotification.cpp
new file mode 100644
index 0000000000..43e7364a38
--- /dev/null
+++ b/netwerk/cookie/CookieNotification.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CookieNotification.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "nsICookieNotification.h"
+
+namespace mozilla::net {
+
+NS_IMETHODIMP
+CookieNotification::GetAction(nsICookieNotification::Action* aResult) {
+ *aResult = mAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieNotification::GetCookie(nsICookie** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = mCookie;
+ NS_IF_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CookieNotification::GetBaseDomain(nsACString& aBaseDomain) {
+ aBaseDomain = mBaseDomain;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieNotification::GetBatchDeletedCookies(nsIArray** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(mAction == nsICookieNotification::COOKIES_BATCH_DELETED,
+ NS_ERROR_NOT_AVAILABLE);
+
+ *aResult = mBatchDeletedCookies;
+ NS_IF_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieNotification::GetBrowsingContextId(uint64_t* aResult) {
+ *aResult = mBrowsingContextId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieNotification::GetBrowsingContext(dom::BrowsingContext** aResult) {
+ *aResult = dom::BrowsingContext::Get(mBrowsingContextId).take();
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(CookieNotification, nsICookieNotification)
+
+} // namespace mozilla::net
diff --git a/netwerk/cookie/CookieNotification.h b/netwerk/cookie/CookieNotification.h
new file mode 100644
index 0000000000..c619ce56c1
--- /dev/null
+++ b/netwerk/cookie/CookieNotification.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_CookieNotification_h
+#define mozilla_net_CookieNotification_h
+
+#include "nsIArray.h"
+#include "nsICookieNotification.h"
+#include "nsICookie.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla::net {
+
+class CookieNotification final : public nsICookieNotification {
+ public:
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIENOTIFICATION
+
+ explicit CookieNotification(nsICookieNotification::Action aAction,
+ nsICookie* aCookie, const nsACString& aBaseDomain,
+ nsIArray* aBatchDeletedCookies = nullptr,
+ uint64_t aBrowsingContextId = 0)
+ : mAction(aAction),
+ mCookie(aCookie),
+ mBaseDomain(aBaseDomain),
+ mBatchDeletedCookies(aBatchDeletedCookies),
+ mBrowsingContextId(aBrowsingContextId){};
+
+ private:
+ nsICookieNotification::Action mAction;
+ nsCOMPtr<nsICookie> mCookie;
+ nsCString mBaseDomain;
+ nsCOMPtr<nsIArray> mBatchDeletedCookies;
+ uint64_t mBrowsingContextId = 0;
+
+ ~CookieNotification() = default;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_CookieNotification_h
diff --git a/netwerk/cookie/CookiePersistentStorage.cpp b/netwerk/cookie/CookiePersistentStorage.cpp
new file mode 100644
index 0000000000..bb4e64f0f1
--- /dev/null
+++ b/netwerk/cookie/CookiePersistentStorage.cpp
@@ -0,0 +1,2134 @@
+/* -*- 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 "Cookie.h"
+#include "CookieCommons.h"
+#include "CookieLogging.h"
+#include "CookiePersistentStorage.h"
+
+#include "mozilla/FileUtils.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.h"
+#include "mozIStorageAsyncStatement.h"
+#include "mozIStorageError.h"
+#include "mozIStorageFunction.h"
+#include "mozIStorageService.h"
+#include "mozStorageHelper.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICookieNotification.h"
+#include "nsICookieService.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsILineInputStream.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+#include "nsVariant.h"
+#include "prprf.h"
+
+// XXX_hack. See bug 178993.
+// This is a hack to hide HttpOnly cookies from older browsers
+#define HTTP_ONLY_PREFIX "#HttpOnly_"
+
+constexpr auto COOKIES_SCHEMA_VERSION = 13;
+
+// parameter indexes; see |Read|
+constexpr auto IDX_NAME = 0;
+constexpr auto IDX_VALUE = 1;
+constexpr auto IDX_HOST = 2;
+constexpr auto IDX_PATH = 3;
+constexpr auto IDX_EXPIRY = 4;
+constexpr auto IDX_LAST_ACCESSED = 5;
+constexpr auto IDX_CREATION_TIME = 6;
+constexpr auto IDX_SECURE = 7;
+constexpr auto IDX_HTTPONLY = 8;
+constexpr auto IDX_ORIGIN_ATTRIBUTES = 9;
+constexpr auto IDX_SAME_SITE = 10;
+constexpr auto IDX_RAW_SAME_SITE = 11;
+constexpr auto IDX_SCHEME_MAP = 12;
+constexpr auto IDX_PARTITIONED_ATTRIBUTE_SET = 13;
+
+#define COOKIES_FILE "cookies.sqlite"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+void BindCookieParameters(mozIStorageBindingParamsArray* aParamsArray,
+ const CookieKey& aKey, const Cookie* aCookie) {
+ NS_ASSERTION(aParamsArray,
+ "Null params array passed to BindCookieParameters!");
+ NS_ASSERTION(aCookie, "Null cookie passed to BindCookieParameters!");
+
+ // Use the asynchronous binding methods to ensure that we do not acquire the
+ // database lock.
+ nsCOMPtr<mozIStorageBindingParams> params;
+ DebugOnly<nsresult> rv =
+ aParamsArray->NewBindingParams(getter_AddRefs(params));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString suffix;
+ aKey.mOriginAttributes.CreateSuffix(suffix);
+ rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("name"_ns, aCookie->Name());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("value"_ns, aCookie->Value());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("host"_ns, aCookie->Host());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("path"_ns, aCookie->Path());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt64ByName("expiry"_ns, aCookie->Expiry());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt64ByName("lastAccessed"_ns, aCookie->LastAccessed());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt64ByName("creationTime"_ns, aCookie->CreationTime());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt32ByName("isSecure"_ns, aCookie->IsSecure());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt32ByName("isHttpOnly"_ns, aCookie->IsHttpOnly());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt32ByName("sameSite"_ns, aCookie->SameSite());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt32ByName("rawSameSite"_ns, aCookie->RawSameSite());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt32ByName("schemeMap"_ns, aCookie->SchemeMap());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindInt32ByName("isPartitionedAttributeSet"_ns,
+ aCookie->RawIsPartitioned());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Bind the params to the array.
+ rv = aParamsArray->AddParams(params);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction {
+ ~ConvertAppIdToOriginAttrsSQLFunction() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
+
+NS_IMETHODIMP
+ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
+ nsresult rv;
+ int32_t inIsolatedMozBrowser;
+
+ rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create an originAttributes object by inIsolatedMozBrowser.
+ // Then create the originSuffix string from this object.
+ OriginAttributes attrs(inIsolatedMozBrowser != 0);
+ nsAutoCString suffix;
+ attrs.CreateSuffix(suffix);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsAUTF8String(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+class SetAppIdFromOriginAttributesSQLFunction final
+ : public mozIStorageFunction {
+ ~SetAppIdFromOriginAttributesSQLFunction() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
+
+NS_IMETHODIMP
+SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
+ nsresult rv;
+ nsAutoCString suffix;
+ OriginAttributes attrs;
+
+ rv = aFunctionArguments->GetUTF8String(0, suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool success = attrs.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsInt32(0); // deprecated appId!
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+class SetInBrowserFromOriginAttributesSQLFunction final
+ : public mozIStorageFunction {
+ ~SetInBrowserFromOriginAttributesSQLFunction() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
+ mozIStorageFunction);
+
+NS_IMETHODIMP
+SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
+ nsresult rv;
+ nsAutoCString suffix;
+ OriginAttributes attrs;
+
+ rv = aFunctionArguments->GetUTF8String(0, suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool success = attrs.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+/******************************************************************************
+ * DBListenerErrorHandler impl:
+ * Parent class for our async storage listeners that handles the logging of
+ * errors.
+ ******************************************************************************/
+class DBListenerErrorHandler : public mozIStorageStatementCallback {
+ protected:
+ explicit DBListenerErrorHandler(CookiePersistentStorage* dbState)
+ : mStorage(dbState) {}
+ RefPtr<CookiePersistentStorage> mStorage;
+ virtual const char* GetOpType() = 0;
+
+ public:
+ NS_IMETHOD HandleError(mozIStorageError* aError) override {
+ if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
+ int32_t result = -1;
+ aError->GetResult(&result);
+
+ nsAutoCString message;
+ aError->GetMessage(message);
+ COOKIE_LOGSTRING(
+ LogLevel::Warning,
+ ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
+ "performing operation '%s' with message '%s'; rebuilding database.",
+ result, GetOpType(), message.get()));
+ }
+
+ // Rebuild the database.
+ mStorage->HandleCorruptDB();
+
+ return NS_OK;
+ }
+};
+
+/******************************************************************************
+ * InsertCookieDBListener impl:
+ * mozIStorageStatementCallback used to track asynchronous insertion operations.
+ ******************************************************************************/
+class InsertCookieDBListener final : public DBListenerErrorHandler {
+ private:
+ const char* GetOpType() override { return "INSERT"; }
+
+ ~InsertCookieDBListener() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit InsertCookieDBListener(CookiePersistentStorage* dbState)
+ : DBListenerErrorHandler(dbState) {}
+ NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
+ MOZ_ASSERT_UNREACHABLE(
+ "Unexpected call to "
+ "InsertCookieDBListener::HandleResult");
+ return NS_OK;
+ }
+ NS_IMETHOD HandleCompletion(uint16_t aReason) override {
+ // If we were rebuilding the db and we succeeded, make our mCorruptFlag say
+ // so.
+ if (mStorage->GetCorruptFlag() == CookiePersistentStorage::REBUILDING &&
+ aReason == mozIStorageStatementCallback::REASON_FINISHED) {
+ COOKIE_LOGSTRING(
+ LogLevel::Debug,
+ ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
+ mStorage->SetCorruptFlag(CookiePersistentStorage::OK);
+ }
+
+ // This notification is just for testing.
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-saved-on-disk", nullptr);
+ }
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
+
+/******************************************************************************
+ * UpdateCookieDBListener impl:
+ * mozIStorageStatementCallback used to track asynchronous update operations.
+ ******************************************************************************/
+class UpdateCookieDBListener final : public DBListenerErrorHandler {
+ private:
+ const char* GetOpType() override { return "UPDATE"; }
+
+ ~UpdateCookieDBListener() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit UpdateCookieDBListener(CookiePersistentStorage* dbState)
+ : DBListenerErrorHandler(dbState) {}
+ NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
+ MOZ_ASSERT_UNREACHABLE(
+ "Unexpected call to "
+ "UpdateCookieDBListener::HandleResult");
+ return NS_OK;
+ }
+ NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
+};
+
+NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
+
+/******************************************************************************
+ * RemoveCookieDBListener impl:
+ * mozIStorageStatementCallback used to track asynchronous removal operations.
+ ******************************************************************************/
+class RemoveCookieDBListener final : public DBListenerErrorHandler {
+ private:
+ const char* GetOpType() override { return "REMOVE"; }
+
+ ~RemoveCookieDBListener() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit RemoveCookieDBListener(CookiePersistentStorage* dbState)
+ : DBListenerErrorHandler(dbState) {}
+ NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
+ MOZ_ASSERT_UNREACHABLE(
+ "Unexpected call to "
+ "RemoveCookieDBListener::HandleResult");
+ return NS_OK;
+ }
+ NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
+};
+
+NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
+
+/******************************************************************************
+ * CloseCookieDBListener imp:
+ * Static mozIStorageCompletionCallback used to notify when the database is
+ * successfully closed.
+ ******************************************************************************/
+class CloseCookieDBListener final : public mozIStorageCompletionCallback {
+ ~CloseCookieDBListener() = default;
+
+ public:
+ explicit CloseCookieDBListener(CookiePersistentStorage* dbState)
+ : mStorage(dbState) {}
+ RefPtr<CookiePersistentStorage> mStorage;
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Complete(nsresult /*status*/, nsISupports* /*value*/) override {
+ mStorage->HandleDBClosed();
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
+
+} // namespace
+
+// static
+already_AddRefed<CookiePersistentStorage> CookiePersistentStorage::Create() {
+ RefPtr<CookiePersistentStorage> storage = new CookiePersistentStorage();
+ storage->Init();
+ storage->Activate();
+
+ return storage.forget();
+}
+
+CookiePersistentStorage::CookiePersistentStorage()
+ : mMonitor("CookiePersistentStorage"),
+ mInitialized(false),
+ mCorruptFlag(OK) {}
+
+void CookiePersistentStorage::NotifyChangedInternal(
+ nsICookieNotification* aNotification, bool aOldCookieIsSession) {
+ MOZ_ASSERT(aNotification);
+ // Notify for topic "session-cookie-changed" to update the copy of session
+ // cookies in session restore component.
+
+ nsICookieNotification::Action action = aNotification->GetAction();
+
+ // Filter out notifications for individual non-session cookies.
+ if (action == nsICookieNotification::COOKIE_CHANGED ||
+ action == nsICookieNotification::COOKIE_DELETED ||
+ action == nsICookieNotification::COOKIE_ADDED) {
+ nsCOMPtr<nsICookie> xpcCookie;
+ DebugOnly<nsresult> rv =
+ aNotification->GetCookie(getter_AddRefs(xpcCookie));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && xpcCookie);
+ const Cookie& cookie = xpcCookie->AsCookie();
+ if (!cookie.IsSession() && !aOldCookieIsSession) {
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(aNotification, "session-cookie-changed", u"");
+ }
+}
+
+void CookiePersistentStorage::RemoveAllInternal() {
+ // clear the cookie file
+ if (mDBConn) {
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ nsresult rv = mDBConn->CreateAsyncStatement("DELETE FROM moz_cookies"_ns,
+ getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ // Recreate the database.
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("RemoveAll(): corruption detected with rv 0x%" PRIx32,
+ static_cast<uint32_t>(rv)));
+ HandleCorruptDB();
+ }
+ }
+}
+
+void CookiePersistentStorage::HandleCorruptDB() {
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("HandleCorruptDB(): CookieStorage %p has mCorruptFlag %u",
+ this, mCorruptFlag));
+
+ // Mark the database corrupt, so the close listener can begin reconstructing
+ // it.
+ switch (mCorruptFlag) {
+ case OK: {
+ // Move to 'closing' state.
+ mCorruptFlag = CLOSING_FOR_REBUILD;
+
+ CleanupCachedStatements();
+ mDBConn->AsyncClose(mCloseListener);
+ CleanupDBConnection();
+ break;
+ }
+ case CLOSING_FOR_REBUILD: {
+ // We had an error while waiting for close completion. That's OK, just
+ // ignore it -- we're rebuilding anyway.
+ return;
+ }
+ case REBUILDING: {
+ // We had an error while rebuilding the DB. Game over. Close the database
+ // and let the close handler do nothing; then we'll move it out of the
+ // way.
+ CleanupCachedStatements();
+ if (mDBConn) {
+ mDBConn->AsyncClose(mCloseListener);
+ }
+ CleanupDBConnection();
+ break;
+ }
+ }
+}
+
+void CookiePersistentStorage::RemoveCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) {
+ mozStorageTransaction transaction(mDBConn, false);
+
+ // XXX Handle the error, bug 1696130.
+ Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
+
+ CookieStorage::RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
+
+ DebugOnly<nsresult> rv = transaction.Commit();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void CookiePersistentStorage::RemoveCookiesFromExactHost(
+ const nsACString& aHost, const nsACString& aBaseDomain,
+ const OriginAttributesPattern& aPattern) {
+ mozStorageTransaction transaction(mDBConn, false);
+
+ // XXX Handle the error, bug 1696130.
+ Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
+
+ CookieStorage::RemoveCookiesFromExactHost(aHost, aBaseDomain, aPattern);
+
+ DebugOnly<nsresult> rv = transaction.Commit();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void CookiePersistentStorage::RemoveCookieFromDB(const Cookie& aCookie) {
+ // if it's a non-session cookie, remove it from the db
+ if (aCookie.IsSession() || !mDBConn) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
+
+ PrepareCookieRemoval(aCookie, paramsArray);
+
+ DebugOnly<nsresult> rv = mStmtDelete->BindParameters(paramsArray);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void CookiePersistentStorage::PrepareCookieRemoval(
+ const Cookie& aCookie, mozIStorageBindingParamsArray* aParamsArray) {
+ // if it's a non-session cookie, remove it from the db
+ if (aCookie.IsSession() || !mDBConn) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageBindingParams> params;
+ aParamsArray->NewBindingParams(getter_AddRefs(params));
+
+ DebugOnly<nsresult> rv =
+ params->BindUTF8StringByName("name"_ns, aCookie.Name());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("host"_ns, aCookie.Host());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("path"_ns, aCookie.Path());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString suffix;
+ aCookie.OriginAttributesRef().CreateSuffix(suffix);
+ rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = aParamsArray->AddParams(params);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+// Null out the statements.
+// This must be done before closing the connection.
+void CookiePersistentStorage::CleanupCachedStatements() {
+ mStmtInsert = nullptr;
+ mStmtDelete = nullptr;
+ mStmtUpdate = nullptr;
+}
+
+// Null out the listeners, and the database connection itself. This
+// will not null out the statements, cancel a pending read or
+// asynchronously close the connection -- these must be done
+// beforehand if necessary.
+void CookiePersistentStorage::CleanupDBConnection() {
+ MOZ_ASSERT(!mStmtInsert, "mStmtInsert has been cleaned up");
+ MOZ_ASSERT(!mStmtDelete, "mStmtDelete has been cleaned up");
+ MOZ_ASSERT(!mStmtUpdate, "mStmtUpdate has been cleaned up");
+
+ // Null out the database connections. If 'mDBConn' has not been used for any
+ // asynchronous operations yet, this will synchronously close it; otherwise,
+ // it's expected that the caller has performed an AsyncClose prior.
+ mDBConn = nullptr;
+
+ // Manually null out our listeners. This is necessary because they hold a
+ // strong ref to the CookieStorage itself. They'll stay alive until whatever
+ // statements are still executing complete.
+ mInsertListener = nullptr;
+ mUpdateListener = nullptr;
+ mRemoveListener = nullptr;
+ mCloseListener = nullptr;
+}
+
+void CookiePersistentStorage::Close() {
+ if (mThread) {
+ mThread->Shutdown();
+ mThread = nullptr;
+ }
+
+ // Cleanup cached statements before we can close anything.
+ CleanupCachedStatements();
+
+ if (mDBConn) {
+ // Asynchronously close the connection. We will null it below.
+ mDBConn->AsyncClose(mCloseListener);
+ }
+
+ CleanupDBConnection();
+
+ mInitialized = false;
+ mInitializedDBConn = false;
+}
+
+void CookiePersistentStorage::StoreCookie(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie) {
+ // if it's a non-session cookie and hasn't just been read from the db, write
+ // it out.
+ if (aCookie->IsSession() || !mDBConn) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ mStmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
+
+ CookieKey key(aBaseDomain, aOriginAttributes);
+ BindCookieParameters(paramsArray, key, aCookie);
+
+ MaybeStoreCookiesToDB(paramsArray);
+}
+
+void CookiePersistentStorage::MaybeStoreCookiesToDB(
+ mozIStorageBindingParamsArray* aParamsArray) {
+ if (!aParamsArray) {
+ return;
+ }
+
+ uint32_t length;
+ aParamsArray->GetLength(&length);
+ if (!length) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv = mStmtInsert->BindParameters(aParamsArray);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = mStmtInsert->ExecuteAsync(mInsertListener, getter_AddRefs(handle));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void CookiePersistentStorage::StaleCookies(const nsTArray<Cookie*>& aCookieList,
+ int64_t aCurrentTimeInUsec) {
+ // Create an array of parameters to bind to our update statement. Batching
+ // is OK here since we're updating cookies with no interleaved operations.
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ mozIStorageAsyncStatement* stmt = mStmtUpdate;
+ if (mDBConn) {
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ }
+
+ int32_t count = aCookieList.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ Cookie* cookie = aCookieList.ElementAt(i);
+
+ if (cookie->IsStale()) {
+ UpdateCookieInList(cookie, aCurrentTimeInUsec, paramsArray);
+ }
+ }
+ // Update the database now if necessary.
+ if (paramsArray) {
+ uint32_t length;
+ paramsArray->GetLength(&length);
+ if (length) {
+ DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(mUpdateListener, getter_AddRefs(handle));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+}
+
+void CookiePersistentStorage::UpdateCookieInList(
+ Cookie* aCookie, int64_t aLastAccessed,
+ mozIStorageBindingParamsArray* aParamsArray) {
+ MOZ_ASSERT(aCookie);
+
+ // udpate the lastAccessed timestamp
+ aCookie->SetLastAccessed(aLastAccessed);
+
+ // if it's a non-session cookie, update it in the db too
+ if (!aCookie->IsSession() && aParamsArray) {
+ // Create our params holder.
+ nsCOMPtr<mozIStorageBindingParams> params;
+ aParamsArray->NewBindingParams(getter_AddRefs(params));
+
+ // Bind our parameters.
+ DebugOnly<nsresult> rv =
+ params->BindInt64ByName("lastAccessed"_ns, aLastAccessed);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("name"_ns, aCookie->Name());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("host"_ns, aCookie->Host());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("path"_ns, aCookie->Path());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString suffix;
+ aCookie->OriginAttributesRef().CreateSuffix(suffix);
+ rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Add our bound parameters to the array.
+ rv = aParamsArray->AddParams(params);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void CookiePersistentStorage::DeleteFromDB(
+ mozIStorageBindingParamsArray* aParamsArray) {
+ uint32_t length;
+ aParamsArray->GetLength(&length);
+ if (length) {
+ DebugOnly<nsresult> rv = mStmtDelete->BindParameters(aParamsArray);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void CookiePersistentStorage::Activate() {
+ MOZ_ASSERT(!mThread, "already have a cookie thread");
+
+ mStorageService = do_GetService("@mozilla.org/storage/service;1");
+ MOZ_ASSERT(mStorageService);
+
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(mTLDService);
+
+ // Get our cookie file.
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mCookieFile));
+ if (NS_FAILED(rv)) {
+ // We've already set up our CookieStorages appropriately; nothing more to
+ // do.
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("InitCookieStorages(): couldn't get cookie file"));
+
+ mInitializedDBConn = true;
+ mInitialized = true;
+ return;
+ }
+
+ mCookieFile->AppendNative(nsLiteralCString(COOKIES_FILE));
+
+ NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread)));
+
+ RefPtr<CookiePersistentStorage> self = this;
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction("CookiePersistentStorage::Activate", [self] {
+ MonitorAutoLock lock(self->mMonitor);
+
+ // Attempt to open and read the database. If TryInitDB() returns
+ // RESULT_RETRY, do so.
+ OpenDBResult result = self->TryInitDB(false);
+ if (result == RESULT_RETRY) {
+ // Database may be corrupt. Synchronously close the connection, clean
+ // up the default CookieStorage, and try again.
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("InitCookieStorages(): retrying TryInitDB()"));
+ self->CleanupCachedStatements();
+ self->CleanupDBConnection();
+ result = self->TryInitDB(true);
+ if (result == RESULT_RETRY) {
+ // We're done. Change the code to failure so we clean up below.
+ result = RESULT_FAILURE;
+ }
+ }
+
+ if (result == RESULT_FAILURE) {
+ COOKIE_LOGSTRING(
+ LogLevel::Warning,
+ ("InitCookieStorages(): TryInitDB() failed, closing connection"));
+
+ // Connection failure is unrecoverable. Clean up our connection. We
+ // can run fine without persistent storage -- e.g. if there's no
+ // profile.
+ self->CleanupCachedStatements();
+ self->CleanupDBConnection();
+
+ // No need to initialize mDBConn
+ self->mInitializedDBConn = true;
+ }
+
+ self->mInitialized = true;
+
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("CookiePersistentStorage::InitDBConn",
+ [self] { self->InitDBConn(); }));
+ self->mMonitor.Notify();
+ });
+
+ mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+/* Attempt to open and read the database. If 'aRecreateDB' is true, try to
+ * move the existing database file out of the way and create a new one.
+ *
+ * @returns RESULT_OK if opening or creating the database succeeded;
+ * RESULT_RETRY if the database cannot be opened, is corrupt, or some
+ * other failure occurred that might be resolved by recreating the
+ * database; or RESULT_FAILED if there was an unrecoverable error and
+ * we must run without a database.
+ *
+ * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
+ * cleanup of the default CookieStorage.
+ */
+CookiePersistentStorage::OpenDBResult CookiePersistentStorage::TryInitDB(
+ bool aRecreateDB) {
+ NS_ASSERTION(!mDBConn, "nonnull mDBConn");
+ NS_ASSERTION(!mStmtInsert, "nonnull mStmtInsert");
+ NS_ASSERTION(!mInsertListener, "nonnull mInsertListener");
+ NS_ASSERTION(!mSyncConn, "nonnull mSyncConn");
+ NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread");
+
+ // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
+ // want to delete it outright, since it may be useful for debugging purposes,
+ // so we move it out of the way.
+ nsresult rv;
+ if (aRecreateDB) {
+ nsCOMPtr<nsIFile> backupFile;
+ mCookieFile->Clone(getter_AddRefs(backupFile));
+ rv = backupFile->MoveToNative(nullptr,
+ nsLiteralCString(COOKIES_FILE ".bak"));
+ NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
+ }
+
+ // This block provides scope for the Telemetry AutoTimer
+ {
+ Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
+ telemetry;
+ ReadAheadFile(mCookieFile);
+
+ // open a connection to the cookie database, and only cache our connection
+ // and statements upon success. The connection is opened unshared to
+ // eliminate cache contention between the main and background threads.
+ rv = mStorageService->OpenUnsharedDatabase(
+ mCookieFile, mozIStorageService::CONNECTION_DEFAULT,
+ getter_AddRefs(mSyncConn));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+
+ auto guard = MakeScopeExit([&] { mSyncConn = nullptr; });
+
+ bool tableExists = false;
+ mSyncConn->TableExists("moz_cookies"_ns, &tableExists);
+ if (!tableExists) {
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ } else {
+ // table already exists; check the schema version before reading
+ int32_t dbSchemaVersion;
+ rv = mSyncConn->GetSchemaVersion(&dbSchemaVersion);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Start a transaction for the whole migration block.
+ mozStorageTransaction transaction(mSyncConn, true);
+
+ // XXX Handle the error, bug 1696130.
+ Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
+
+ switch (dbSchemaVersion) {
+ // Upgrading.
+ // Every time you increment the database schema, you need to implement
+ // the upgrading code from the previous version to the new one. If
+ // migration fails for any reason, it's a bug -- so we return RESULT_RETRY
+ // such that the original database will be saved, in the hopes that we
+ // might one day see it and fix it.
+ case 1: {
+ // Add the lastAccessed column to the table.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // Fall through to the next upgrade.
+ [[fallthrough]];
+
+ case 2: {
+ // Add the baseDomain column and index to the table.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ "ALTER TABLE moz_cookies ADD baseDomain TEXT"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Compute the baseDomains for the table. This must be done eagerly
+ // otherwise we won't be able to synchronously read in individual
+ // domains on demand.
+ const int64_t SCHEMA2_IDX_ID = 0;
+ const int64_t SCHEMA2_IDX_HOST = 1;
+ nsCOMPtr<mozIStorageStatement> select;
+ rv = mSyncConn->CreateStatement("SELECT id, host FROM moz_cookies"_ns,
+ getter_AddRefs(select));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ nsCOMPtr<mozIStorageStatement> update;
+ rv = mSyncConn->CreateStatement(
+ nsLiteralCString("UPDATE moz_cookies SET baseDomain = "
+ ":baseDomain WHERE id = :id"),
+ getter_AddRefs(update));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ nsCString baseDomain;
+ nsCString host;
+ bool hasResult;
+ while (true) {
+ rv = select->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ if (!hasResult) {
+ break;
+ }
+
+ int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
+ select->GetUTF8String(SCHEMA2_IDX_HOST, host);
+
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host,
+ baseDomain);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ mozStorageStatementScoper scoper(update);
+
+ rv = update->BindUTF8StringByName("baseDomain"_ns, baseDomain);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = update->BindInt64ByName("id"_ns, id);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = update->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+
+ // Create an index on baseDomain.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // Fall through to the next upgrade.
+ [[fallthrough]];
+
+ case 3: {
+ // Add the creationTime column to the table, and create a unique index
+ // on (name, host, path). Before we do this, we have to purge the table
+ // of expired cookies such that we know that the (name, host, path)
+ // index is truly unique -- otherwise we can't create the index. Note
+ // that we can't just execute a statement to delete all rows where the
+ // expiry column is in the past -- doing so would rely on the clock
+ // (both now and when previous cookies were set) being monotonic.
+
+ // Select the whole table, and order by the fields we're interested in.
+ // This means we can simply do a linear traversal of the results and
+ // check for duplicates as we go.
+ const int64_t SCHEMA3_IDX_ID = 0;
+ const int64_t SCHEMA3_IDX_NAME = 1;
+ const int64_t SCHEMA3_IDX_HOST = 2;
+ const int64_t SCHEMA3_IDX_PATH = 3;
+ nsCOMPtr<mozIStorageStatement> select;
+ rv = mSyncConn->CreateStatement(
+ nsLiteralCString(
+ "SELECT id, name, host, path FROM moz_cookies "
+ "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
+ getter_AddRefs(select));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ nsCOMPtr<mozIStorageStatement> deleteExpired;
+ rv = mSyncConn->CreateStatement(
+ "DELETE FROM moz_cookies WHERE id = :id"_ns,
+ getter_AddRefs(deleteExpired));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Read the first row.
+ bool hasResult;
+ rv = select->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ if (hasResult) {
+ nsCString name1;
+ nsCString host1;
+ nsCString path1;
+ int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
+ select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
+ select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
+ select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
+
+ nsCString name2;
+ nsCString host2;
+ nsCString path2;
+ while (true) {
+ // Read the second row.
+ rv = select->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ if (!hasResult) {
+ break;
+ }
+
+ int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
+ select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
+ select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
+ select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
+
+ // If the two rows match in (name, host, path), we know the earlier
+ // row has an earlier expiry time. Delete it.
+ if (name1 == name2 && host1 == host2 && path1 == path2) {
+ mozStorageStatementScoper scoper(deleteExpired);
+
+ rv = deleteExpired->BindInt64ByName("id"_ns, id1);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = deleteExpired->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+
+ // Make the second row the first for the next iteration.
+ name1 = name2;
+ host1 = host2;
+ path1 = path2;
+ id1 = id2;
+ }
+ }
+
+ // Add the creationTime column to the table.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Copy the id of each row into the new creationTime column.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("UPDATE moz_cookies SET creationTime = "
+ "(SELECT id WHERE id = moz_cookies.id)"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Create a unique index on (name, host, path) to allow fast lookup.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE UNIQUE INDEX moz_uniqueid "
+ "ON moz_cookies (name, host, path)"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // Fall through to the next upgrade.
+ [[fallthrough]];
+
+ case 4: {
+ // We need to add appId/inBrowserElement, plus change a constraint on
+ // the table (unique entries now include appId/inBrowserElement):
+ // this requires creating a new table and copying the data to it. We
+ // then rename the new table to the old name.
+ //
+ // Why we made this change: appId/inBrowserElement allow "cookie jars"
+ // for Firefox OS. We create a separate cookie namespace per {appId,
+ // inBrowserElement}. When upgrading, we convert existing cookies
+ // (which imply we're on desktop/mobile) to use {0, false}, as that is
+ // the only namespace used by a non-Firefox-OS implementation.
+
+ // Rename existing table
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop existing index (CreateTable will create new one for new table)
+ rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Create new table (with new fields and new unique constraint)
+ rv = CreateTableForSchemaVersion5();
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Copy data from old table, using appId/inBrowser=0 for existing rows
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "INSERT INTO moz_cookies "
+ "(baseDomain, appId, inBrowserElement, name, value, host, path, "
+ "expiry,"
+ " lastAccessed, creationTime, isSecure, isHttpOnly) "
+ "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
+ " lastAccessed, creationTime, isSecure, isHttpOnly "
+ "FROM moz_cookies_old"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop old table
+ rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 5"));
+ }
+ // Fall through to the next upgrade.
+ [[fallthrough]];
+
+ case 5: {
+ // Change in the version: Replace the columns |appId| and
+ // |inBrowserElement| by a single column |originAttributes|.
+ //
+ // Why we made this change: FxOS new security model (NSec) encapsulates
+ // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to
+ // make it easier to modify the contents of this structure in the
+ // future.
+ //
+ // We do the migration in several steps:
+ // 1. Rename the old table.
+ // 2. Create a new table.
+ // 3. Copy data from the old table to the new table; convert appId and
+ // inBrowserElement to originAttributes in the meantime.
+
+ // Rename existing table.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop existing index (CreateTable will create new one for new table).
+ rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Create new table with new fields and new unique constraint.
+ rv = CreateTableForSchemaVersion6();
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Copy data from old table without the two deprecated columns appId and
+ // inBrowserElement.
+ nsCOMPtr<mozIStorageFunction> convertToOriginAttrs(
+ new ConvertAppIdToOriginAttrsSQLFunction());
+ NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
+
+ constexpr auto convertToOriginAttrsName =
+ "CONVERT_TO_ORIGIN_ATTRIBUTES"_ns;
+
+ rv = mSyncConn->CreateFunction(convertToOriginAttrsName, 2,
+ convertToOriginAttrs);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "INSERT INTO moz_cookies "
+ "(baseDomain, originAttributes, name, value, host, path, expiry,"
+ " lastAccessed, creationTime, isSecure, isHttpOnly) "
+ "SELECT baseDomain, "
+ " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
+ " name, value, host, path, expiry, lastAccessed, creationTime, "
+ " isSecure, isHttpOnly "
+ "FROM moz_cookies_old"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mSyncConn->RemoveFunction(convertToOriginAttrsName);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop old table
+ rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 6"));
+ }
+ [[fallthrough]];
+
+ case 6: {
+ // We made a mistake in schema version 6. We cannot remove expected
+ // columns of any version (checked in the default case) from cookie
+ // database, because doing this would destroy the possibility of
+ // downgrading database.
+ //
+ // This version simply restores appId and inBrowserElement columns in
+ // order to fix downgrading issue even though these two columns are no
+ // longer used in the latest schema.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Compute and populate the values of appId and inBrwoserElement from
+ // originAttributes.
+ nsCOMPtr<mozIStorageFunction> setAppId(
+ new SetAppIdFromOriginAttributesSQLFunction());
+ NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
+
+ constexpr auto setAppIdName = "SET_APP_ID"_ns;
+
+ rv = mSyncConn->CreateFunction(setAppIdName, 1, setAppId);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ nsCOMPtr<mozIStorageFunction> setInBrowser(
+ new SetInBrowserFromOriginAttributesSQLFunction());
+ NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
+
+ constexpr auto setInBrowserName = "SET_IN_BROWSER"_ns;
+
+ rv = mSyncConn->CreateFunction(setInBrowserName, 1, setInBrowser);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
+ "inBrowserElement = SET_IN_BROWSER(originAttributes);"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mSyncConn->RemoveFunction(setAppIdName);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mSyncConn->RemoveFunction(setInBrowserName);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 7"));
+ }
+ [[fallthrough]];
+
+ case 7: {
+ // Remove the appId field from moz_cookies.
+ //
+ // Unfortunately sqlite doesn't support dropping columns using ALTER
+ // TABLE, so we need to go through the procedure documented in
+ // https://www.sqlite.org/lang_altertable.html.
+
+ // Drop existing index
+ rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Create a new_moz_cookies table without the appId field.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE new_moz_cookies("
+ "id INTEGER PRIMARY KEY, "
+ "baseDomain TEXT, "
+ "originAttributes TEXT NOT NULL DEFAULT '', "
+ "name TEXT, "
+ "value TEXT, "
+ "host TEXT, "
+ "path TEXT, "
+ "expiry INTEGER, "
+ "lastAccessed INTEGER, "
+ "creationTime INTEGER, "
+ "isSecure INTEGER, "
+ "isHttpOnly INTEGER, "
+ "inBrowserElement INTEGER DEFAULT 0, "
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, "
+ "path, originAttributes)"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Move the data over.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("INSERT INTO new_moz_cookies ("
+ "id, "
+ "baseDomain, "
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "inBrowserElement "
+ ") SELECT "
+ "id, "
+ "baseDomain, "
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "inBrowserElement "
+ "FROM moz_cookies;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop the old table
+ rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies;"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Rename new_moz_cookies to moz_cookies.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Recreate our index.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE INDEX moz_basedomain ON moz_cookies "
+ "(baseDomain, originAttributes)"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 8"));
+ }
+ [[fallthrough]];
+
+ case 8: {
+ // Add the sameSite column to the table.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ "ALTER TABLE moz_cookies ADD sameSite INTEGER"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 9"));
+ }
+ [[fallthrough]];
+
+ case 9: {
+ // Add the rawSameSite column to the table.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies ADD rawSameSite INTEGER"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Copy the current sameSite value into rawSameSite.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ "UPDATE moz_cookies SET rawSameSite = sameSite"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 10"));
+ }
+ [[fallthrough]];
+
+ case 10: {
+ // Rename existing table
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Create a new moz_cookies table without the baseDomain field.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE moz_cookies("
+ "id INTEGER PRIMARY KEY, "
+ "originAttributes TEXT NOT NULL DEFAULT '', "
+ "name TEXT, "
+ "value TEXT, "
+ "host TEXT, "
+ "path TEXT, "
+ "expiry INTEGER, "
+ "lastAccessed INTEGER, "
+ "creationTime INTEGER, "
+ "isSecure INTEGER, "
+ "isHttpOnly INTEGER, "
+ "inBrowserElement INTEGER DEFAULT 0, "
+ "sameSite INTEGER DEFAULT 0, "
+ "rawSameSite INTEGER DEFAULT 0, "
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, "
+ "path, originAttributes)"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Move the data over.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("INSERT INTO moz_cookies ("
+ "id, "
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "inBrowserElement, "
+ "sameSite, "
+ "rawSameSite "
+ ") SELECT "
+ "id, "
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "inBrowserElement, "
+ "sameSite, "
+ "rawSameSite "
+ "FROM moz_cookies_old "
+ "WHERE baseDomain NOTNULL;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop the old table
+ rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old;"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop the moz_basedomain index from the database (if it hasn't been
+ // removed already by removing the table).
+ rv = mSyncConn->ExecuteSimpleSQL(
+ "DROP INDEX IF EXISTS moz_basedomain;"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 11"));
+ }
+ [[fallthrough]];
+
+ case 11: {
+ // Add the schemeMap column to the table.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE moz_cookies ADD schemeMap INTEGER DEFAULT 0;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 12"));
+
+ // No more upgrades. Update the schema version.
+ rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ [[fallthrough]];
+
+ case 12: {
+ // Add the isPartitionedAttributeSet column to the table.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("ALTER TABLE moz_cookies ADD "
+ "isPartitionedAttributeSet INTEGER DEFAULT 0;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 13"));
+
+ // No more upgrades. Update the schema version.
+ rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ [[fallthrough]];
+ }
+
+ case COOKIES_SCHEMA_VERSION:
+ break;
+
+ case 0: {
+ NS_WARNING("couldn't get schema version!");
+
+ // the table may be usable; someone might've just clobbered the schema
+ // version. we can treat this case like a downgrade using the codepath
+ // below, by verifying the columns we care about are all there. for now,
+ // re-set the schema version in the db, in case the checks succeed (if
+ // they don't, we're dropping the table anyway).
+ rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // fall through to downgrade check
+ [[fallthrough]];
+
+ // downgrading.
+ // if columns have been added to the table, we can still use the ones we
+ // understand safely. if columns have been deleted or altered, just
+ // blow away the table and start from scratch! if you change the way
+ // a column is interpreted, make sure you also change its name so this
+ // check will catch it.
+ default: {
+ // check if all the expected columns exist
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mSyncConn->CreateStatement(
+ nsLiteralCString("SELECT "
+ "id, "
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "sameSite, "
+ "rawSameSite, "
+ "schemeMap, "
+ "isPartitionedAttributeSet "
+ "FROM moz_cookies"),
+ getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv)) {
+ break;
+ }
+
+ // our columns aren't there - drop the table!
+ rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies"_ns);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ } break;
+ }
+ }
+
+ // if we deleted a corrupt db, don't attempt to import - return now
+ if (aRecreateDB) {
+ return RESULT_OK;
+ }
+
+ // check whether to import or just read in the db
+ if (tableExists) {
+ return Read();
+ }
+
+ return RESULT_OK;
+}
+
+void CookiePersistentStorage::RebuildCorruptDB() {
+ NS_ASSERTION(!mDBConn, "shouldn't have an open db connection");
+ NS_ASSERTION(mCorruptFlag == CookiePersistentStorage::CLOSING_FOR_REBUILD,
+ "should be in CLOSING_FOR_REBUILD state");
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+
+ mCorruptFlag = CookiePersistentStorage::REBUILDING;
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("RebuildCorruptDB(): creating new database"));
+
+ RefPtr<CookiePersistentStorage> self = this;
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [self] {
+ // The database has been closed, and we're ready to rebuild. Open a
+ // connection.
+ OpenDBResult result = self->TryInitDB(true);
+
+ nsCOMPtr<nsIRunnable> innerRunnable = NS_NewRunnableFunction(
+ "RebuildCorruptDB.TryInitDBComplete", [self, result] {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (result != RESULT_OK) {
+ // We're done. Reset our DB connection and statements, and
+ // notify of closure.
+ COOKIE_LOGSTRING(
+ LogLevel::Warning,
+ ("RebuildCorruptDB(): TryInitDB() failed with result %u",
+ result));
+ self->CleanupCachedStatements();
+ self->CleanupDBConnection();
+ self->mCorruptFlag = CookiePersistentStorage::OK;
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
+ }
+ return;
+ }
+
+ // Notify observers that we're beginning the rebuild.
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
+ }
+
+ self->InitDBConnInternal();
+
+ // Enumerate the hash, and add cookies to the params array.
+ mozIStorageAsyncStatement* stmt = self->mStmtInsert;
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ for (auto iter = self->mHostTable.Iter(); !iter.Done();
+ iter.Next()) {
+ CookieEntry* entry = iter.Get();
+
+ const CookieEntry::ArrayType& cookies = entry->GetCookies();
+ for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ Cookie* cookie = cookies[i];
+
+ if (!cookie->IsSession()) {
+ BindCookieParameters(paramsArray, CookieKey(entry), cookie);
+ }
+ }
+ }
+
+ // Make sure we've got something to write. If we don't, we're
+ // done.
+ uint32_t length;
+ paramsArray->GetLength(&length);
+ if (length == 0) {
+ COOKIE_LOGSTRING(
+ LogLevel::Debug,
+ ("RebuildCorruptDB(): nothing to write, rebuild complete"));
+ self->mCorruptFlag = CookiePersistentStorage::OK;
+ return;
+ }
+
+ self->MaybeStoreCookiesToDB(paramsArray);
+ });
+ NS_DispatchToMainThread(innerRunnable);
+ });
+ mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+void CookiePersistentStorage::HandleDBClosed() {
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("HandleDBClosed(): CookieStorage %p closed", this));
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+
+ switch (mCorruptFlag) {
+ case CookiePersistentStorage::OK: {
+ // Database is healthy. Notify of closure.
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
+ }
+ break;
+ }
+ case CookiePersistentStorage::CLOSING_FOR_REBUILD: {
+ // Our close finished. Start the rebuild, and notify of db closure later.
+ RebuildCorruptDB();
+ break;
+ }
+ case CookiePersistentStorage::REBUILDING: {
+ // We encountered an error during rebuild, closed the database, and now
+ // here we are. We already have a 'cookies.sqlite.bak' from the original
+ // dead database; we don't want to overwrite it, so let's move this one to
+ // 'cookies.sqlite.bak-rebuild'.
+ nsCOMPtr<nsIFile> backupFile;
+ mCookieFile->Clone(getter_AddRefs(backupFile));
+ nsresult rv = backupFile->MoveToNative(
+ nullptr, nsLiteralCString(COOKIES_FILE ".bak-rebuild"));
+
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("HandleDBClosed(): CookieStorage %p encountered error "
+ "rebuilding db; move to "
+ "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32,
+ this, static_cast<uint32_t>(rv)));
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
+ }
+ break;
+ }
+ }
+}
+
+CookiePersistentStorage::OpenDBResult CookiePersistentStorage::Read() {
+ MOZ_ASSERT(NS_GetCurrentThread() == mThread);
+
+ // Read in the data synchronously.
+ // see IDX_NAME, etc. for parameter indexes
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv =
+ mSyncConn->CreateStatement(nsLiteralCString("SELECT "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "originAttributes, "
+ "sameSite, "
+ "rawSameSite, "
+ "schemeMap, "
+ "isPartitionedAttributeSet "
+ "FROM moz_cookies"),
+ getter_AddRefs(stmt));
+
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ if (NS_WARN_IF(!mReadArray.IsEmpty())) {
+ mReadArray.Clear();
+ }
+ mReadArray.SetCapacity(kMaxNumberOfCookies);
+
+ nsCString baseDomain;
+ nsCString name;
+ nsCString value;
+ nsCString host;
+ nsCString path;
+ bool hasResult;
+ while (true) {
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mReadArray.Clear();
+ return RESULT_RETRY;
+ }
+
+ if (!hasResult) {
+ break;
+ }
+
+ stmt->GetUTF8String(IDX_HOST, host);
+
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ if (NS_FAILED(rv)) {
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Read(): Ignoring invalid host '%s'", host.get()));
+ continue;
+ }
+
+ nsAutoCString suffix;
+ OriginAttributes attrs;
+ stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
+ // If PopulateFromSuffix failed we just ignore the OA attributes
+ // that we don't support
+ Unused << attrs.PopulateFromSuffix(suffix);
+
+ CookieKey key(baseDomain, attrs);
+ CookieDomainTuple* tuple = mReadArray.AppendElement();
+ tuple->key = std::move(key);
+ tuple->originAttributes = attrs;
+ tuple->cookie = GetCookieFromRow(stmt);
+ }
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Read(): %zu cookies read", mReadArray.Length()));
+
+ return RESULT_OK;
+}
+
+// Extract data from a single result row and create an Cookie.
+UniquePtr<CookieStruct> CookiePersistentStorage::GetCookieFromRow(
+ mozIStorageStatement* aRow) {
+ nsCString name;
+ nsCString value;
+ nsCString host;
+ nsCString path;
+ DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aRow->GetUTF8String(IDX_VALUE, value);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aRow->GetUTF8String(IDX_HOST, host);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aRow->GetUTF8String(IDX_PATH, path);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
+ int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
+ int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
+ bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
+ bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
+ int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
+ int32_t rawSameSite = aRow->AsInt32(IDX_RAW_SAME_SITE);
+ int32_t schemeMap = aRow->AsInt32(IDX_SCHEME_MAP);
+ bool isPartitionedAttributeSet =
+ 0 != aRow->AsInt32(IDX_PARTITIONED_ATTRIBUTE_SET);
+
+ // Create a new constCookie and assign the data.
+ return MakeUnique<CookieStruct>(
+ name, value, host, path, expiry, lastAccessed, creationTime, isHttpOnly,
+ false, isSecure, isPartitionedAttributeSet, sameSite, rawSameSite,
+ static_cast<nsICookie::schemeType>(schemeMap));
+}
+
+void CookiePersistentStorage::EnsureInitialized() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool isAccumulated = false;
+
+ if (!mInitialized) {
+ TimeStamp startBlockTime = TimeStamp::Now();
+ MonitorAutoLock lock(mMonitor);
+
+ while (!mInitialized) {
+ mMonitor.Wait();
+ }
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS_V2, startBlockTime);
+ Telemetry::Accumulate(
+ Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
+ isAccumulated = true;
+ } else if (!mEndInitDBConn.IsNull()) {
+ // We didn't block main thread, and here comes the first cookie request.
+ // Collect how close we're going to block main thread.
+ Telemetry::Accumulate(
+ Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS,
+ (TimeStamp::Now() - mEndInitDBConn).ToMilliseconds());
+ // Nullify the timestamp so wo don't accumulate this telemetry probe again.
+ mEndInitDBConn = TimeStamp();
+ isAccumulated = true;
+ } else if (!mInitializedDBConn) {
+ // A request comes while we finished cookie thread task and InitDBConn is
+ // on the way from cookie thread to main thread. We're very close to block
+ // main thread.
+ Telemetry::Accumulate(
+ Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
+ isAccumulated = true;
+ }
+
+ if (!mInitializedDBConn) {
+ InitDBConn();
+ if (isAccumulated) {
+ // Nullify the timestamp so wo don't accumulate this telemetry probe
+ // again.
+ mEndInitDBConn = TimeStamp();
+ }
+ }
+}
+
+void CookiePersistentStorage::InitDBConn() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We should skip InitDBConn if we close profile during initializing
+ // CookieStorages and then InitDBConn is called after we close the
+ // CookieStorages.
+ if (!mInitialized || mInitializedDBConn) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> dummyUri;
+ nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://example.com");
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ nsTArray<RefPtr<Cookie>> cleanupCookies;
+
+ for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
+ CookieDomainTuple& tuple = mReadArray[i];
+ MOZ_ASSERT(!tuple.cookie->isSession());
+
+ // filter invalid non-ipv4 host ending in number from old db values
+ nsCOMPtr<nsIURIMutator> outMut;
+ nsCOMPtr<nsIURIMutator> dummyMut;
+ rv = dummyUri->Mutate(getter_AddRefs(dummyMut));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = dummyMut->SetHost(tuple.cookie->host(), getter_AddRefs(outMut));
+
+ if (NS_FAILED(rv)) {
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Removing cookie from db with "
+ "newly invalid hostname: '%s'",
+ tuple.cookie->host().get()));
+ RefPtr<Cookie> cookie =
+ Cookie::Create(*tuple.cookie, tuple.originAttributes);
+ cleanupCookies.AppendElement(cookie);
+ continue;
+ }
+
+ // CreateValidated fixes up the creation and lastAccessed times.
+ // If the DB is corrupted and the timestaps are far away in the future
+ // we don't want the creation timestamp to update gLastCreationTime
+ // as that would contaminate all the next creation times.
+ // We fix up these dates to not be later than the current time.
+ // The downside is that if the user sets the date far away in the past
+ // then back to the current date, those cookies will be stale,
+ // but if we don't fix their dates, those cookies might never be
+ // evicted.
+ RefPtr<Cookie> cookie =
+ Cookie::CreateValidated(*tuple.cookie, tuple.originAttributes);
+ AddCookieToList(tuple.key.mBaseDomain, tuple.key.mOriginAttributes, cookie);
+ }
+
+ if (NS_FAILED(InitDBConnInternal())) {
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("InitDBConn(): retrying InitDBConnInternal()"));
+ CleanupCachedStatements();
+ CleanupDBConnection();
+ if (NS_FAILED(InitDBConnInternal())) {
+ COOKIE_LOGSTRING(
+ LogLevel::Warning,
+ ("InitDBConn(): InitDBConnInternal() failed, closing connection"));
+
+ // Game over, clean the connections.
+ CleanupCachedStatements();
+ CleanupDBConnection();
+ }
+ }
+ mInitializedDBConn = true;
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("InitDBConn(): mInitializedDBConn = true"));
+ mEndInitDBConn = TimeStamp::Now();
+
+ for (const auto& cookie : cleanupCookies) {
+ RemoveCookieFromDB(*cookie);
+ }
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
+ mReadArray.Clear();
+ }
+}
+
+nsresult CookiePersistentStorage::InitDBConnInternal() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = mStorageService->OpenUnsharedDatabase(
+ mCookieFile, mozIStorageService::CONNECTION_DEFAULT,
+ getter_AddRefs(mDBConn));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set up our listeners.
+ mInsertListener = new InsertCookieDBListener(this);
+ mUpdateListener = new UpdateCookieDBListener(this);
+ mRemoveListener = new RemoveCookieDBListener(this);
+ mCloseListener = new CloseCookieDBListener(this);
+
+ // Grow cookie db in 512KB increments
+ mDBConn->SetGrowthIncrement(512 * 1024, ""_ns);
+
+ // make operations on the table asynchronous, for performance
+ mDBConn->ExecuteSimpleSQL("PRAGMA synchronous = OFF"_ns);
+
+ // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
+ // 16 pages (around 500KB).
+ mDBConn->ExecuteSimpleSQL(nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR
+ "PRAGMA journal_mode = WAL"));
+ mDBConn->ExecuteSimpleSQL("PRAGMA wal_autocheckpoint = 16"_ns);
+
+ // cache frequently used statements (for insertion, deletion, and updating)
+ rv = mDBConn->CreateAsyncStatement(
+ nsLiteralCString("INSERT INTO moz_cookies ("
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "sameSite, "
+ "rawSameSite, "
+ "schemeMap, "
+ "isPartitionedAttributeSet "
+ ") VALUES ("
+ ":originAttributes, "
+ ":name, "
+ ":value, "
+ ":host, "
+ ":path, "
+ ":expiry, "
+ ":lastAccessed, "
+ ":creationTime, "
+ ":isSecure, "
+ ":isHttpOnly, "
+ ":sameSite, "
+ ":rawSameSite, "
+ ":schemeMap, "
+ ":isPartitionedAttributeSet "
+ ")"),
+ getter_AddRefs(mStmtInsert));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(
+ nsLiteralCString("DELETE FROM moz_cookies "
+ "WHERE name = :name AND host = :host AND path = :path "
+ "AND originAttributes = :originAttributes"),
+ getter_AddRefs(mStmtDelete));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(
+ nsLiteralCString("UPDATE moz_cookies SET lastAccessed = :lastAccessed "
+ "WHERE name = :name AND host = :host AND path = :path "
+ "AND originAttributes = :originAttributes"),
+ getter_AddRefs(mStmtUpdate));
+ return rv;
+}
+
+// Sets the schema version and creates the moz_cookies table.
+nsresult CookiePersistentStorage::CreateTableWorker(const char* aName) {
+ // Create the table.
+ // We default originAttributes to empty string: this is so if users revert to
+ // an older Firefox version that doesn't know about this field, any cookies
+ // set will still work once they upgrade back.
+ nsAutoCString command("CREATE TABLE ");
+ command.Append(aName);
+ command.AppendLiteral(
+ " ("
+ "id INTEGER PRIMARY KEY, "
+ "originAttributes TEXT NOT NULL DEFAULT '', "
+ "name TEXT, "
+ "value TEXT, "
+ "host TEXT, "
+ "path TEXT, "
+ "expiry INTEGER, "
+ "lastAccessed INTEGER, "
+ "creationTime INTEGER, "
+ "isSecure INTEGER, "
+ "isHttpOnly INTEGER, "
+ "inBrowserElement INTEGER DEFAULT 0, "
+ "sameSite INTEGER DEFAULT 0, "
+ "rawSameSite INTEGER DEFAULT 0, "
+ "schemeMap INTEGER DEFAULT 0, "
+ "isPartitionedAttributeSet INTEGER DEFAULT 0, "
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
+ ")");
+ return mSyncConn->ExecuteSimpleSQL(command);
+}
+
+// Sets the schema version and creates the moz_cookies table.
+nsresult CookiePersistentStorage::CreateTable() {
+ // Set the schema version, before creating the table.
+ nsresult rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = CreateTableWorker("moz_cookies");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// Sets the schema version and creates the moz_cookies table.
+nsresult CookiePersistentStorage::CreateTableForSchemaVersion6() {
+ // Set the schema version, before creating the table.
+ nsresult rv = mSyncConn->SetSchemaVersion(6);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Create the table.
+ // We default originAttributes to empty string: this is so if users revert to
+ // an older Firefox version that doesn't know about this field, any cookies
+ // set will still work once they upgrade back.
+ rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "CREATE TABLE moz_cookies ("
+ "id INTEGER PRIMARY KEY, "
+ "baseDomain TEXT, "
+ "originAttributes TEXT NOT NULL DEFAULT '', "
+ "name TEXT, "
+ "value TEXT, "
+ "host TEXT, "
+ "path TEXT, "
+ "expiry INTEGER, "
+ "lastAccessed INTEGER, "
+ "creationTime INTEGER, "
+ "isSecure INTEGER, "
+ "isHttpOnly INTEGER, "
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
+ ")"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Create an index on baseDomain.
+ return mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
+ "originAttributes)"));
+}
+
+// Sets the schema version and creates the moz_cookies table.
+nsresult CookiePersistentStorage::CreateTableForSchemaVersion5() {
+ // Set the schema version, before creating the table.
+ nsresult rv = mSyncConn->SetSchemaVersion(5);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Create the table. We default appId/inBrowserElement to 0: this is so if
+ // users revert to an older Firefox version that doesn't know about these
+ // fields, any cookies set will still work once they upgrade back.
+ rv = mSyncConn->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE moz_cookies ("
+ "id INTEGER PRIMARY KEY, "
+ "baseDomain TEXT, "
+ "appId INTEGER DEFAULT 0, "
+ "inBrowserElement INTEGER DEFAULT 0, "
+ "name TEXT, "
+ "value TEXT, "
+ "host TEXT, "
+ "path TEXT, "
+ "expiry INTEGER, "
+ "lastAccessed INTEGER, "
+ "creationTime INTEGER, "
+ "isSecure INTEGER, "
+ "isHttpOnly INTEGER, "
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, "
+ "appId, inBrowserElement)"
+ ")"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Create an index on baseDomain.
+ return mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
+ "appId, "
+ "inBrowserElement)"));
+}
+
+nsresult CookiePersistentStorage::RunInTransaction(
+ nsICookieTransactionCallback* aCallback) {
+ if (NS_WARN_IF(!mDBConn)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mozStorageTransaction transaction(mDBConn, true);
+
+ // XXX Handle the error, bug 1696130.
+ Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
+
+ if (NS_FAILED(aCallback->Callback())) {
+ Unused << transaction.Rollback();
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+// purges expired and old cookies in a batch operation.
+already_AddRefed<nsIArray> CookiePersistentStorage::PurgeCookies(
+ int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
+ int64_t aCookiePurgeAge) {
+ // Create a params array to batch the removals. This is OK here because
+ // all the removals are in order, and there are no interleaved additions.
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ if (mDBConn) {
+ mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ }
+
+ RefPtr<CookiePersistentStorage> self = this;
+
+ return PurgeCookiesWithCallbacks(
+ aCurrentTimeInUsec, aMaxNumberOfCookies, aCookiePurgeAge,
+ [paramsArray, self](const CookieListIter& aIter) {
+ self->PrepareCookieRemoval(*aIter.Cookie(), paramsArray);
+ self->RemoveCookieFromListInternal(aIter);
+ },
+ [paramsArray, self]() {
+ if (paramsArray) {
+ self->DeleteFromDB(paramsArray);
+ }
+ });
+}
+
+void CookiePersistentStorage::CollectCookieJarSizeData() {
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("CookiePersistentStorage::CollectCookieJarSizeData"));
+
+ uint32_t sumPartitioned = 0;
+ uint32_t sumUnpartitioned = 0;
+ for (const auto& cookieEntry : mHostTable) {
+ if (cookieEntry.IsPartitioned()) {
+ uint16_t cePartitioned = cookieEntry.GetCookies().Length();
+ sumPartitioned += cePartitioned;
+ mozilla::glean::networking::cookie_count_part_by_key.AccumulateSamples(
+ {cePartitioned});
+ } else {
+ uint16_t ceUnpartitioned = cookieEntry.GetCookies().Length();
+ sumUnpartitioned += ceUnpartitioned;
+ mozilla::glean::networking::cookie_count_unpart_by_key.AccumulateSamples(
+ {ceUnpartitioned});
+ }
+ }
+
+ mozilla::glean::networking::cookie_count_total.AccumulateSamples(
+ {mCookieCount});
+ mozilla::glean::networking::cookie_count_partitioned.AccumulateSamples(
+ {sumPartitioned});
+ mozilla::glean::networking::cookie_count_unpartitioned.AccumulateSamples(
+ {sumUnpartitioned});
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookiePersistentStorage.h b/netwerk/cookie/CookiePersistentStorage.h
new file mode 100644
index 0000000000..e973c74ab6
--- /dev/null
+++ b/netwerk/cookie/CookiePersistentStorage.h
@@ -0,0 +1,160 @@
+/* -*- 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_CookiePersistentStorage_h
+#define mozilla_net_CookiePersistentStorage_h
+
+#include "CookieStorage.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageCompletionCallback.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageStatementCallback.h"
+
+class mozIStorageAsyncStatement;
+class mozIStorageService;
+class nsICookieTransactionCallback;
+class nsIEffectiveTLDService;
+
+namespace mozilla {
+namespace net {
+
+class CookiePersistentStorage final : public CookieStorage {
+ public:
+ // Result codes for TryInitDB() and Read().
+ enum OpenDBResult { RESULT_OK, RESULT_RETRY, RESULT_FAILURE };
+
+ static already_AddRefed<CookiePersistentStorage> Create();
+
+ void HandleCorruptDB();
+
+ void RemoveCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern,
+ const nsACString& aBaseDomain) override;
+
+ void RemoveCookiesFromExactHost(
+ const nsACString& aHost, const nsACString& aBaseDomain,
+ const OriginAttributesPattern& aPattern) override;
+
+ void StaleCookies(const nsTArray<Cookie*>& aCookieList,
+ int64_t aCurrentTimeInUsec) override;
+
+ void Close() override;
+
+ void EnsureInitialized() override;
+
+ void CleanupCachedStatements();
+ void CleanupDBConnection();
+
+ void Activate();
+
+ void RebuildCorruptDB();
+ void HandleDBClosed();
+
+ nsresult RunInTransaction(nsICookieTransactionCallback* aCallback) override;
+
+ // State of the database connection.
+ enum CorruptFlag {
+ OK, // normal
+ CLOSING_FOR_REBUILD, // corruption detected, connection closing
+ REBUILDING // close complete, rebuilding database from memory
+ };
+
+ CorruptFlag GetCorruptFlag() const { return mCorruptFlag; }
+
+ void SetCorruptFlag(CorruptFlag aFlag) { mCorruptFlag = aFlag; }
+
+ protected:
+ const char* NotificationTopic() const override { return "cookie-changed"; }
+
+ void NotifyChangedInternal(nsICookieNotification* aNotification,
+ bool aOldCookieIsSession) override;
+
+ void RemoveAllInternal() override;
+
+ void RemoveCookieFromDB(const Cookie& aCookie) override;
+
+ void StoreCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie) override;
+
+ private:
+ CookiePersistentStorage();
+
+ static void UpdateCookieInList(Cookie* aCookie, int64_t aLastAccessed,
+ mozIStorageBindingParamsArray* aParamsArray);
+
+ void PrepareCookieRemoval(const Cookie& aCookie,
+ mozIStorageBindingParamsArray* aParamsArray);
+
+ void InitDBConn();
+ nsresult InitDBConnInternal();
+
+ OpenDBResult TryInitDB(bool aRecreateDB);
+ OpenDBResult Read();
+
+ nsresult CreateTableWorker(const char* aName);
+ nsresult CreateTable();
+ nsresult CreateTableForSchemaVersion6();
+ nsresult CreateTableForSchemaVersion5();
+
+ static UniquePtr<CookieStruct> GetCookieFromRow(mozIStorageStatement* aRow);
+
+ already_AddRefed<nsIArray> PurgeCookies(int64_t aCurrentTimeInUsec,
+ uint16_t aMaxNumberOfCookies,
+ int64_t aCookiePurgeAge) override;
+
+ void CollectCookieJarSizeData() override;
+
+ void DeleteFromDB(mozIStorageBindingParamsArray* aParamsArray);
+
+ void MaybeStoreCookiesToDB(mozIStorageBindingParamsArray* aParamsArray);
+
+ nsCOMPtr<nsIThread> mThread;
+ nsCOMPtr<mozIStorageService> mStorageService;
+ nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+
+ // encapsulates a (key, Cookie) tuple for temporary storage purposes.
+ struct CookieDomainTuple {
+ CookieKey key;
+ OriginAttributes originAttributes;
+ UniquePtr<CookieStruct> cookie;
+ };
+
+ // thread
+ TimeStamp mEndInitDBConn;
+ nsTArray<CookieDomainTuple> mReadArray;
+
+ Monitor mMonitor MOZ_UNANNOTATED;
+
+ Atomic<bool> mInitialized;
+ Atomic<bool> mInitializedDBConn;
+
+ nsCOMPtr<nsIFile> mCookieFile;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtInsert;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtDelete;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtUpdate;
+
+ CorruptFlag mCorruptFlag;
+
+ // Various parts representing asynchronous read state. These are useful
+ // while the background read is taking place.
+ nsCOMPtr<mozIStorageConnection> mSyncConn;
+
+ // DB completion handlers.
+ nsCOMPtr<mozIStorageStatementCallback> mInsertListener;
+ nsCOMPtr<mozIStorageStatementCallback> mUpdateListener;
+ nsCOMPtr<mozIStorageStatementCallback> mRemoveListener;
+ nsCOMPtr<mozIStorageCompletionCallback> mCloseListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookiePersistentStorage_h
diff --git a/netwerk/cookie/CookiePrivateStorage.cpp b/netwerk/cookie/CookiePrivateStorage.cpp
new file mode 100644
index 0000000000..e6d8fd67d2
--- /dev/null
+++ b/netwerk/cookie/CookiePrivateStorage.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "CookiePrivateStorage.h"
+#include "Cookie.h"
+
+namespace mozilla {
+namespace net {
+
+// static
+already_AddRefed<CookiePrivateStorage> CookiePrivateStorage::Create() {
+ RefPtr<CookiePrivateStorage> storage = new CookiePrivateStorage();
+ storage->Init();
+
+ return storage.forget();
+}
+
+void CookiePrivateStorage::StaleCookies(const nsTArray<Cookie*>& aCookieList,
+ int64_t aCurrentTimeInUsec) {
+ int32_t count = aCookieList.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ Cookie* cookie = aCookieList.ElementAt(i);
+
+ if (cookie->IsStale()) {
+ cookie->SetLastAccessed(aCurrentTimeInUsec);
+ }
+ }
+}
+
+// purges expired and old cookies in a batch operation.
+already_AddRefed<nsIArray> CookiePrivateStorage::PurgeCookies(
+ int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
+ int64_t aCookiePurgeAge) {
+ RefPtr<CookiePrivateStorage> self = this;
+ return PurgeCookiesWithCallbacks(
+ aCurrentTimeInUsec, aMaxNumberOfCookies, aCookiePurgeAge,
+ [self](const CookieListIter& iter) { self->RemoveCookieFromList(iter); },
+ nullptr);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookiePrivateStorage.h b/netwerk/cookie/CookiePrivateStorage.h
new file mode 100644
index 0000000000..812a7e6764
--- /dev/null
+++ b/netwerk/cookie/CookiePrivateStorage.h
@@ -0,0 +1,61 @@
+/* -*- 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_CookiePrivateStorage_h
+#define mozilla_net_CookiePrivateStorage_h
+
+#include "CookieStorage.h"
+
+class nsICookieTransactionCallback;
+
+namespace mozilla {
+namespace net {
+
+class CookiePrivateStorage final : public CookieStorage {
+ public:
+ static already_AddRefed<CookiePrivateStorage> Create();
+
+ void StaleCookies(const nsTArray<Cookie*>& aCookieList,
+ int64_t aCurrentTimeInUsec) override;
+
+ void Close() override{};
+
+ void EnsureInitialized() override{};
+
+ nsresult RunInTransaction(nsICookieTransactionCallback* aCallback) override {
+ // It might make sense for this to be a no-op, or to return
+ // `NS_ERROR_NOT_AVAILABLE`, or to evalute `aCallback` (in case it has
+ // side-effects), but for now, just crash.
+ MOZ_CRASH("RunInTransaction is not supported for private storage");
+ };
+
+ protected:
+ const char* NotificationTopic() const override {
+ return "private-cookie-changed";
+ }
+
+ void NotifyChangedInternal(nsICookieNotification* aNotification,
+ bool aOldCookieIsSession) override {}
+
+ void RemoveAllInternal() override {}
+
+ void RemoveCookieFromDB(const Cookie& aCookie) override {}
+
+ already_AddRefed<nsIArray> PurgeCookies(int64_t aCurrentTimeInUsec,
+ uint16_t aMaxNumberOfCookies,
+ int64_t aCookiePurgeAge) override;
+
+ void StoreCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie) override {}
+
+ private:
+ void CollectCookieJarSizeData() override{};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookiePrivateStorage_h
diff --git a/netwerk/cookie/CookieService.cpp b/netwerk/cookie/CookieService.cpp
new file mode 100644
index 0000000000..d306203c2f
--- /dev/null
+++ b/netwerk/cookie/CookieService.cpp
@@ -0,0 +1,2586 @@
+/* -*- Mode: C++; tab-width: 2; 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 "CookieCommons.h"
+#include "CookieLogging.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/CookiePersistentStorage.h"
+#include "mozilla/net/CookiePrivateStorage.h"
+#include "mozilla/net/CookieService.h"
+#include "mozilla/net/CookieServiceChild.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Telemetry.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsICookiePermission.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIIDNService.h"
+#include "nsIScriptError.h"
+#include "nsIURL.h"
+#include "nsIURI.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "prprf.h"
+#include "ThirdPartyUtil.h"
+
+using namespace mozilla::dom;
+
+namespace {
+
+uint32_t MakeCookieBehavior(uint32_t aCookieBehavior) {
+ bool isFirstPartyIsolated = OriginAttributes::IsFirstPartyEnabled();
+
+ if (isFirstPartyIsolated &&
+ aCookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ return nsICookieService::BEHAVIOR_REJECT_TRACKER;
+ }
+ return aCookieBehavior;
+}
+
+/*
+ Enables sanitizeOnShutdown cleaning prefs and disables the
+ network.cookie.lifetimePolicy
+*/
+void MigrateCookieLifetimePrefs() {
+ // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY
+ // are not available anymore 2 = ACCEPT_SESSION
+ if (mozilla::Preferences::GetInt("network.cookie.lifetimePolicy") != 2) {
+ return;
+ }
+ if (!mozilla::Preferences::GetBool("privacy.sanitize.sanitizeOnShutdown")) {
+ mozilla::Preferences::SetBool("privacy.sanitize.sanitizeOnShutdown", true);
+ // To avoid clearing categories that the user did not intend to clear
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.history", false);
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.formdata", false);
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.downloads", false);
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.sessions", false);
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.siteSettings",
+ false);
+ }
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.cookies", true);
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.cache", true);
+ mozilla::Preferences::SetBool("privacy.clearOnShutdown.offlineApps", true);
+ mozilla::Preferences::ClearUser("network.cookie.lifetimePolicy");
+}
+
+} // anonymous namespace
+
+// static
+uint32_t nsICookieManager::GetCookieBehavior(bool aIsPrivate) {
+ if (aIsPrivate) {
+ // To sync the cookieBehavior pref between regular and private mode in ETP
+ // custom mode, we will return the regular cookieBehavior pref for private
+ // mode when
+ // 1. The regular cookieBehavior pref has a non-default value.
+ // 2. And the private cookieBehavior pref has a default value.
+ // Also, this can cover the migration case where the user has a non-default
+ // cookieBehavior before the private cookieBehavior was introduced. The
+ // getter here will directly return the regular cookieBehavior, so that the
+ // cookieBehavior for private mode is consistent.
+ if (mozilla::Preferences::HasUserValue(
+ "network.cookie.cookieBehavior.pbmode")) {
+ return MakeCookieBehavior(
+ mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
+ }
+
+ if (mozilla::Preferences::HasUserValue("network.cookie.cookieBehavior")) {
+ return MakeCookieBehavior(
+ mozilla::StaticPrefs::network_cookie_cookieBehavior());
+ }
+
+ return MakeCookieBehavior(
+ mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
+ }
+ return MakeCookieBehavior(
+ mozilla::StaticPrefs::network_cookie_cookieBehavior());
+}
+
+namespace mozilla {
+namespace net {
+
+/******************************************************************************
+ * CookieService impl:
+ * useful types & constants
+ ******************************************************************************/
+
+static StaticRefPtr<CookieService> gCookieService;
+
+constexpr auto CONSOLE_CHIPS_CATEGORY = "cookiesCHIPS"_ns;
+constexpr auto CONSOLE_SAMESITE_CATEGORY = "cookieSameSite"_ns;
+constexpr auto CONSOLE_OVERSIZE_CATEGORY = "cookiesOversize"_ns;
+constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
+constexpr auto SAMESITE_MDN_URL =
+ "https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/"
+ u"SameSite"_ns;
+
+namespace {
+
+void ComposeCookieString(nsTArray<Cookie*>& aCookieList,
+ nsACString& aCookieString) {
+ for (Cookie* cookie : aCookieList) {
+ // check if we have anything to write
+ if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
+ // if we've already added a cookie to the return list, append a "; " so
+ // that subsequent cookies are delimited in the final list.
+ if (!aCookieString.IsEmpty()) {
+ aCookieString.AppendLiteral("; ");
+ }
+
+ if (!cookie->Name().IsEmpty()) {
+ // we have a name and value - write both
+ aCookieString += cookie->Name() + "="_ns + cookie->Value();
+ } else {
+ // just write value
+ aCookieString += cookie->Value();
+ }
+ }
+ }
+}
+
+// Return false if the cookie should be ignored for the current channel.
+bool ProcessSameSiteCookieForForeignRequest(nsIChannel* aChannel,
+ Cookie* aCookie,
+ bool aIsSafeTopLevelNav,
+ bool aHadCrossSiteRedirects,
+ bool aLaxByDefault) {
+ // If it's a cross-site request and the cookie is same site only (strict)
+ // don't send it.
+ if (aCookie->SameSite() == nsICookie::SAMESITE_STRICT) {
+ return false;
+ }
+
+ // Explicit SameSite=None cookies are always processed. When laxByDefault
+ // is OFF then so are default cookies.
+ if (aCookie->SameSite() == nsICookie::SAMESITE_NONE ||
+ (!aLaxByDefault && aCookie->IsDefaultSameSite())) {
+ return true;
+ }
+
+ // Lax-by-default cookies are processed even with an intermediate
+ // cross-site redirect (they are treated like aIsSameSiteForeign = false).
+ if (aLaxByDefault && aCookie->IsDefaultSameSite() && aHadCrossSiteRedirects &&
+ StaticPrefs::
+ network_cookie_sameSite_laxByDefault_allowBoomerangRedirect()) {
+ return true;
+ }
+
+ int64_t currentTimeInUsec = PR_Now();
+
+ // 2 minutes of tolerance for 'SameSite=Lax by default' for cookies set
+ // without a SameSite value when used for unsafe http methods.
+ if (aLaxByDefault && aCookie->IsDefaultSameSite() &&
+ StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() > 0 &&
+ currentTimeInUsec - aCookie->CreationTime() <=
+ (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() *
+ PR_USEC_PER_SEC) &&
+ !NS_IsSafeMethodNav(aChannel)) {
+ return true;
+ }
+
+ MOZ_ASSERT((aLaxByDefault && aCookie->IsDefaultSameSite()) ||
+ aCookie->SameSite() == nsICookie::SAMESITE_LAX);
+ // We only have SameSite=Lax or lax-by-default cookies at this point. These
+ // are processed only if it's a top-level navigation
+ return aIsSafeTopLevelNav;
+}
+
+} // namespace
+
+/******************************************************************************
+ * CookieService impl:
+ * singleton instance ctor/dtor methods
+ ******************************************************************************/
+
+already_AddRefed<nsICookieService> CookieService::GetXPCOMSingleton() {
+ if (IsNeckoChild()) {
+ return CookieServiceChild::GetSingleton();
+ }
+
+ return GetSingleton();
+}
+
+already_AddRefed<CookieService> CookieService::GetSingleton() {
+ NS_ASSERTION(!IsNeckoChild(), "not a parent process");
+
+ if (gCookieService) {
+ return do_AddRef(gCookieService);
+ }
+
+ // Create a new singleton CookieService.
+ // We AddRef only once since XPCOM has rules about the ordering of module
+ // teardowns - by the time our module destructor is called, it's too late to
+ // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
+ // cycles have already been completed and would result in serious leaks.
+ // See bug 209571.
+ // TODO: Verify what is the earliest point in time during shutdown where
+ // we can deny the creation of the CookieService as a whole.
+ gCookieService = new CookieService();
+ if (gCookieService) {
+ if (NS_SUCCEEDED(gCookieService->Init())) {
+ ClearOnShutdown(&gCookieService);
+ } else {
+ gCookieService = nullptr;
+ }
+ }
+
+ return do_AddRef(gCookieService);
+}
+
+/******************************************************************************
+ * CookieService impl:
+ * public methods
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(CookieService, nsICookieService, nsICookieManager,
+ nsIObserver, nsISupportsWeakReference, nsIMemoryReporter)
+
+CookieService::CookieService() = default;
+
+nsresult CookieService::Init() {
+ nsresult rv;
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Init our default, and possibly private CookieStorages.
+ InitCookieStorages();
+
+ // Migrate network.cookie.lifetimePolicy pref to sanitizeOnShutdown prefs
+ MigrateCookieLifetimePrefs();
+
+ RegisterWeakMemoryReporter(this);
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ NS_ENSURE_STATE(os);
+ os->AddObserver(this, "profile-before-change", true);
+ os->AddObserver(this, "profile-do-change", true);
+ os->AddObserver(this, "last-pb-context-exited", true);
+
+ return NS_OK;
+}
+
+void CookieService::InitCookieStorages() {
+ NS_ASSERTION(!mPersistentStorage, "already have a default CookieStorage");
+ NS_ASSERTION(!mPrivateStorage, "already have a private CookieStorage");
+
+ // Create two new CookieStorages. If we are in or beyond our observed
+ // shutdown phase, just be non-persistent.
+ if (MOZ_UNLIKELY(StaticPrefs::network_cookie_noPersistentStorage() ||
+ AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown))) {
+ mPersistentStorage = CookiePrivateStorage::Create();
+ } else {
+ mPersistentStorage = CookiePersistentStorage::Create();
+ }
+
+ mPrivateStorage = CookiePrivateStorage::Create();
+}
+
+void CookieService::CloseCookieStorages() {
+ // return if we already closed
+ if (!mPersistentStorage) {
+ return;
+ }
+
+ // Let's nullify both storages before calling Close().
+ RefPtr<CookieStorage> privateStorage;
+ privateStorage.swap(mPrivateStorage);
+
+ RefPtr<CookieStorage> persistentStorage;
+ persistentStorage.swap(mPersistentStorage);
+
+ privateStorage->Close();
+ persistentStorage->Close();
+}
+
+CookieService::~CookieService() {
+ CloseCookieStorages();
+
+ UnregisterWeakMemoryReporter(this);
+
+ gCookieService = nullptr;
+}
+
+NS_IMETHODIMP
+CookieService::Observe(nsISupports* /*aSubject*/, const char* aTopic,
+ const char16_t* /*aData*/) {
+ // check the topic
+ if (!strcmp(aTopic, "profile-before-change")) {
+ // The profile is about to change,
+ // or is going away because the application is shutting down.
+
+ // Close the default DB connection and null out our CookieStorages before
+ // changing.
+ CloseCookieStorages();
+
+ } else if (!strcmp(aTopic, "profile-do-change")) {
+ NS_ASSERTION(!mPersistentStorage, "shouldn't have a default CookieStorage");
+ NS_ASSERTION(!mPrivateStorage, "shouldn't have a private CookieStorage");
+
+ // the profile has already changed; init the db from the new location.
+ // if we are in the private browsing state, however, we do not want to read
+ // data into it - we should instead put it into the default state, so it's
+ // ready for us if and when we switch back to it.
+ InitCookieStorages();
+
+ } else if (!strcmp(aTopic, "last-pb-context-exited")) {
+ // Flush all the cookies stored by private browsing contexts
+ OriginAttributesPattern pattern;
+ pattern.mPrivateBrowsingId.Construct(1);
+ RemoveCookiesWithOriginAttributes(pattern, ""_ns);
+ mPrivateStorage = CookiePrivateStorage::Create();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::GetCookieBehavior(bool aIsPrivate, uint32_t* aCookieBehavior) {
+ NS_ENSURE_ARG_POINTER(aCookieBehavior);
+ *aCookieBehavior = nsICookieManager::GetCookieBehavior(aIsPrivate);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::GetCookieStringFromDocument(Document* aDocument,
+ nsACString& aCookie) {
+ NS_ENSURE_ARG(aDocument);
+
+ nsresult rv;
+
+ aCookie.Truncate();
+
+ if (!IsInitialized()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
+
+ if (!CookieCommons::IsSchemeSupported(principal)) {
+ return NS_OK;
+ }
+
+ CookieStorage* storage = PickStorage(principal->OriginAttributesRef());
+
+ nsAutoCString baseDomain;
+ rv = CookieCommons::GetBaseDomain(principal, baseDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ nsAutoCString hostFromURI;
+ rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ nsAutoCString pathFromURI;
+ rv = principal->GetFilePath(pathFromURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ int64_t currentTimeInUsec = PR_Now();
+ int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
+
+ const nsTArray<RefPtr<Cookie>>* cookies =
+ storage->GetCookiesFromHost(baseDomain, principal->OriginAttributesRef());
+ if (!cookies) {
+ return NS_OK;
+ }
+
+ // check if the nsIPrincipal is using an https secure protocol.
+ // if it isn't, then we can't send a secure cookie over the connection.
+ bool potentiallyTrustworthy = principal->GetIsOriginPotentiallyTrustworthy();
+
+ bool thirdParty = true;
+ nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
+ // in gtests we don't have a window, let's consider those requests as 3rd
+ // party.
+ if (innerWindow) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+
+ if (thirdPartyUtil) {
+ Unused << thirdPartyUtil->IsThirdPartyWindow(
+ innerWindow->GetOuterWindow(), nullptr, &thirdParty);
+ }
+ }
+
+ bool stale = false;
+ nsTArray<Cookie*> cookieList;
+
+ // iterate the cookies!
+ for (Cookie* cookie : *cookies) {
+ // check the host, since the base domain lookup is conservative.
+ if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
+ continue;
+ }
+
+ // if the cookie is httpOnly and it's not going directly to the HTTP
+ // connection, don't send it
+ if (cookie->IsHttpOnly()) {
+ continue;
+ }
+
+ if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
+ cookie, aDocument)) {
+ continue;
+ }
+
+ // if the cookie is secure and the host scheme isn't, we can't send it
+ if (cookie->IsSecure() && !potentiallyTrustworthy) {
+ continue;
+ }
+
+ // if the nsIURI path doesn't match the cookie path, don't send it back
+ if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
+ continue;
+ }
+
+ // check if the cookie has expired
+ if (cookie->Expiry() <= currentTime) {
+ continue;
+ }
+
+ // all checks passed - add to list and check if lastAccessed stamp needs
+ // updating
+ cookieList.AppendElement(cookie);
+ if (cookie->IsStale()) {
+ stale = true;
+ }
+ }
+
+ if (cookieList.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // update lastAccessed timestamps. we only do this if the timestamp is stale
+ // by a certain amount, to avoid thrashing the db during pageload.
+ if (stale) {
+ storage->StaleCookies(cookieList, currentTimeInUsec);
+ }
+
+ // return cookies in order of path length; longest to shortest.
+ // this is required per RFC2109. if cookies match in length,
+ // then sort by creation time (see bug 236772).
+ cookieList.Sort(CompareCookiesForSending());
+ ComposeCookieString(cookieList, aCookie);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel,
+ nsACString& aCookieString) {
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG(aChannel);
+
+ aCookieString.Truncate();
+
+ if (!CookieCommons::IsSchemeSupported(aHostURI)) {
+ return NS_OK;
+ }
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
+ aChannel, false, aHostURI, nullptr, &rejectedReason);
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributes(
+ aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
+
+ bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
+ bool hadCrossSiteRedirects = false;
+ bool isSameSiteForeign = CookieCommons::IsSameSiteForeign(
+ aChannel, aHostURI, &hadCrossSiteRedirects);
+
+ AutoTArray<Cookie*, 8> foundCookieList;
+ GetCookiesForURI(
+ aHostURI, aChannel, result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ rejectedReason, isSafeTopLevelNav, isSameSiteForeign,
+ hadCrossSiteRedirects, true, false, attrs, foundCookieList);
+
+ ComposeCookieString(foundCookieList, aCookieString);
+
+ if (!aCookieString.IsEmpty()) {
+ COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::SetCookieStringFromDocument(Document* aDocument,
+ const nsACString& aCookieString) {
+ NS_ENSURE_ARG(aDocument);
+
+ if (!IsInitialized()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> documentURI;
+ nsAutoCString baseDomain;
+ OriginAttributes attrs;
+
+ int64_t currentTimeInUsec = PR_Now();
+
+ // This function is executed in this context, I don't need to keep objects
+ // alive.
+ auto hasExistingCookiesLambda = [&](const nsACString& aBaseDomain,
+ const OriginAttributes& aAttrs) {
+ CookieStorage* storage = PickStorage(aAttrs);
+ return !!storage->CountCookiesFromHost(aBaseDomain,
+ aAttrs.mPrivateBrowsingId);
+ };
+
+ RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
+ aDocument, aCookieString, currentTimeInUsec, mTLDService, mThirdPartyUtil,
+ hasExistingCookiesLambda, getter_AddRefs(documentURI), baseDomain, attrs);
+ if (!cookie) {
+ return NS_OK;
+ }
+
+ bool thirdParty = true;
+ nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
+ // in gtests we don't have a window, let's consider those requests as 3rd
+ // party.
+ if (innerWindow) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+
+ if (thirdPartyUtil) {
+ Unused << thirdPartyUtil->IsThirdPartyWindow(
+ innerWindow->GetOuterWindow(), nullptr, &thirdParty);
+ }
+ }
+
+ if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
+ cookie, aDocument)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIConsoleReportCollector> crc =
+ do_QueryInterface(aDocument->GetChannel());
+
+ // add the cookie to the list. AddCookie() takes care of logging.
+ PickStorage(attrs)->AddCookie(crc, baseDomain, attrs, cookie,
+ currentTimeInUsec, documentURI, aCookieString,
+ false, aDocument->GetBrowsingContext());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
+ const nsACString& aCookieHeader,
+ nsIChannel* aChannel) {
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG(aChannel);
+
+ if (!IsInitialized()) {
+ return NS_OK;
+ }
+
+ if (!CookieCommons::IsSchemeSupported(aHostURI)) {
+ return NS_OK;
+ }
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
+ aChannel, false, aHostURI, nullptr, &rejectedReason);
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributes(
+ aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
+
+ // get the base domain for the host URI.
+ // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
+ // file:// URI's (i.e. with an empty host) are allowed, but any other
+ // scheme must have a non-empty host. A trailing dot in the host
+ // is acceptable.
+ bool requireHostMatch;
+ nsAutoCString baseDomain;
+ nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
+ requireHostMatch);
+ if (NS_FAILED(rv)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "couldn't get base domain from URI");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ CookieCommons::GetCookieJarSettings(aChannel);
+
+ nsAutoCString hostFromURI;
+ nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
+
+ nsAutoCString baseDomainFromURI;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, hostFromURI,
+ baseDomainFromURI);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ CookieStorage* storage = PickStorage(attrs);
+
+ // check default prefs
+ uint32_t priorCookieCount = storage->CountCookiesFromHost(
+ baseDomainFromURI, attrs.mPrivateBrowsingId);
+
+ nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
+
+ CookieStatus cookieStatus = CheckPrefs(
+ crc, cookieJarSettings, aHostURI,
+ result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ aCookieHeader, priorCookieCount, attrs, &rejectedReason);
+
+ MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
+
+ // fire a notification if third party or if cookie was rejected
+ // (but not if there was an error)
+ switch (cookieStatus) {
+ case STATUS_REJECTED:
+ CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
+ OPERATION_WRITE);
+ return NS_OK; // Stop here
+ case STATUS_REJECTED_WITH_ERROR:
+ CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
+ OPERATION_WRITE);
+ return NS_OK;
+ case STATUS_ACCEPTED: // Fallthrough
+ case STATUS_ACCEPT_SESSION:
+ NotifyAccepted(aChannel);
+ break;
+ default:
+ break;
+ }
+
+ bool addonAllowsLoad = false;
+ nsCOMPtr<nsIURI> channelURI;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
+ ->AddonAllowsLoad(channelURI);
+
+ bool isForeignAndNotAddon = false;
+ if (!addonAllowsLoad) {
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
+ &isForeignAndNotAddon);
+ }
+
+ bool mustBePartitioned =
+ isForeignAndNotAddon &&
+ cookieJarSettings->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
+ !result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted);
+
+ nsCString cookieHeader(aCookieHeader);
+
+ bool moreCookieToRead = true;
+
+ // process each cookie in the header
+ while (moreCookieToRead) {
+ CookieStruct cookieData;
+ bool canSetCookie = false;
+
+ moreCookieToRead =
+ CanSetCookie(aHostURI, baseDomain, cookieData, requireHostMatch,
+ cookieStatus, cookieHeader, true, isForeignAndNotAddon,
+ mustBePartitioned, crc, canSetCookie);
+
+ if (!canSetCookie) {
+ continue;
+ }
+
+ // check permissions from site permission list.
+ if (!CookieCommons::CheckCookiePermission(aChannel, cookieData)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie rejected by permission manager");
+ CookieCommons::NotifyRejected(
+ aHostURI, aChannel,
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
+ OPERATION_WRITE);
+ CookieLogging::LogMessageToConsole(
+ crc, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(cookieData.name()),
+ });
+ continue;
+ }
+
+ // create a new Cookie
+ RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
+ MOZ_ASSERT(cookie);
+
+ int64_t currentTimeInUsec = PR_Now();
+ cookie->SetLastAccessed(currentTimeInUsec);
+ cookie->SetCreationTime(
+ Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
+
+ RefPtr<BrowsingContext> bc = loadInfo->GetBrowsingContext();
+
+ // add the cookie to the list. AddCookie() takes care of logging.
+ storage->AddCookie(crc, baseDomain, attrs, cookie, currentTimeInUsec,
+ aHostURI, aCookieHeader, true, bc);
+ }
+
+ return NS_OK;
+}
+
+void CookieService::NotifyAccepted(nsIChannel* aChannel) {
+ ContentBlockingNotifier::OnDecision(
+ aChannel, ContentBlockingNotifier::BlockingDecision::eAllow, 0);
+}
+
+/******************************************************************************
+ * CookieService:
+ * public transaction helper impl
+ ******************************************************************************/
+
+NS_IMETHODIMP
+CookieService::RunInTransaction(nsICookieTransactionCallback* aCallback) {
+ NS_ENSURE_ARG(aCallback);
+
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+ return mPersistentStorage->RunInTransaction(aCallback);
+}
+
+/******************************************************************************
+ * nsICookieManager impl:
+ * nsICookieManager
+ ******************************************************************************/
+
+NS_IMETHODIMP
+CookieService::RemoveAll() {
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+ mPersistentStorage->RemoveAll();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+
+ // We expose only non-private cookies.
+ mPersistentStorage->GetCookies(aCookies);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::GetSessionCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+
+ // We expose only non-private cookies.
+ mPersistentStorage->GetSessionCookies(aCookies);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::Add(const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aName, const nsACString& aValue,
+ bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
+ int64_t aExpiry, JS::Handle<JS::Value> aOriginAttributes,
+ int32_t aSameSite, nsICookie::schemeType aSchemeMap,
+ JSContext* aCx) {
+ OriginAttributes attrs;
+
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
+ aIsSession, aExpiry, &attrs, aSameSite, aSchemeMap);
+}
+
+NS_IMETHODIMP_(nsresult)
+CookieService::AddNative(const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aName, const nsACString& aValue,
+ bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
+ int64_t aExpiry, OriginAttributes* aOriginAttributes,
+ int32_t aSameSite, nsICookie::schemeType aSchemeMap) {
+ if (NS_WARN_IF(!aOriginAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // first, normalize the hostname, and fail if it contains illegal characters.
+ nsAutoCString host(aHost);
+ nsresult rv = NormalizeHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the base domain for the host URI.
+ // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
+ nsAutoCString baseDomain;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t currentTimeInUsec = PR_Now();
+ CookieKey key = CookieKey(baseDomain, *aOriginAttributes);
+
+ CookieStruct cookieData(nsCString(aName), nsCString(aValue), nsCString(aHost),
+ nsCString(aPath), aExpiry, currentTimeInUsec,
+ Cookie::GenerateUniqueCreationTime(currentTimeInUsec),
+ aIsHttpOnly, aIsSession, aIsSecure, false, aSameSite,
+ aSameSite, aSchemeMap);
+
+ RefPtr<Cookie> cookie = Cookie::Create(cookieData, key.mOriginAttributes);
+ MOZ_ASSERT(cookie);
+
+ CookieStorage* storage = PickStorage(*aOriginAttributes);
+ storage->AddCookie(nullptr, baseDomain, *aOriginAttributes, cookie,
+ currentTimeInUsec, nullptr, VoidCString(), true, nullptr);
+ return NS_OK;
+}
+
+nsresult CookieService::Remove(const nsACString& aHost,
+ const OriginAttributes& aAttrs,
+ const nsACString& aName,
+ const nsACString& aPath) {
+ // first, normalize the hostname, and fail if it contains illegal characters.
+ nsAutoCString host(aHost);
+ nsresult rv = NormalizeHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDomain;
+ if (!host.IsEmpty()) {
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CookieStorage* storage = PickStorage(aAttrs);
+ storage->RemoveCookie(baseDomain, aAttrs, host, PromiseFlatCString(aName),
+ PromiseFlatCString(aPath));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::Remove(const nsACString& aHost, const nsACString& aName,
+ const nsACString& aPath,
+ JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx) {
+ OriginAttributes attrs;
+
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return RemoveNative(aHost, aName, aPath, &attrs);
+}
+
+NS_IMETHODIMP_(nsresult)
+CookieService::RemoveNative(const nsACString& aHost, const nsACString& aName,
+ const nsACString& aPath,
+ OriginAttributes* aOriginAttributes) {
+ if (NS_WARN_IF(!aOriginAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void CookieService::GetCookiesForURI(
+ nsIURI* aHostURI, nsIChannel* aChannel, bool aIsForeign,
+ bool aIsThirdPartyTrackingResource,
+ bool aIsThirdPartySocialTrackingResource,
+ bool aStorageAccessPermissionGranted, uint32_t aRejectedReason,
+ bool aIsSafeTopLevelNav, bool aIsSameSiteForeign,
+ bool aHadCrossSiteRedirects, bool aHttpBound,
+ bool aAllowSecureCookiesToInsecureOrigin,
+ const OriginAttributes& aOriginAttrs, nsTArray<Cookie*>& aCookieList) {
+ NS_ASSERTION(aHostURI, "null host!");
+
+ if (!CookieCommons::IsSchemeSupported(aHostURI)) {
+ return;
+ }
+
+ if (!IsInitialized()) {
+ return;
+ }
+
+ CookieStorage* storage = PickStorage(aOriginAttrs);
+
+ // get the base domain, host, and path from the URI.
+ // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
+ // file:// URI's (i.e. with an empty host) are allowed, but any other
+ // scheme must have a non-empty host. A trailing dot in the host
+ // is acceptable.
+ bool requireHostMatch;
+ nsAutoCString baseDomain;
+ nsAutoCString hostFromURI;
+ nsAutoCString pathFromURI;
+ nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
+ requireHostMatch);
+ if (NS_SUCCEEDED(rv)) {
+ rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = aHostURI->GetFilePath(pathFromURI);
+ }
+ if (NS_FAILED(rv)) {
+ COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, VoidCString(),
+ "invalid host/path from URI");
+ return;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ CookieCommons::GetCookieJarSettings(aChannel);
+
+ nsAutoCString normalizedHostFromURI(hostFromURI);
+ rv = NormalizeHost(normalizedHostFromURI);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsAutoCString baseDomainFromURI;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, normalizedHostFromURI,
+ baseDomainFromURI);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // check default prefs
+ uint32_t rejectedReason = aRejectedReason;
+ uint32_t priorCookieCount = storage->CountCookiesFromHost(
+ baseDomainFromURI, aOriginAttrs.mPrivateBrowsingId);
+
+ nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
+ CookieStatus cookieStatus = CheckPrefs(
+ crc, cookieJarSettings, aHostURI, aIsForeign,
+ aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource,
+ aStorageAccessPermissionGranted, VoidCString(), priorCookieCount,
+ aOriginAttrs, &rejectedReason);
+
+ MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
+
+ // for GetCookie(), we only fire acceptance/rejection notifications
+ // (but not if there was an error)
+ switch (cookieStatus) {
+ case STATUS_REJECTED:
+ // If we don't have any cookies from this host, fail silently.
+ if (priorCookieCount) {
+ CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
+ OPERATION_READ);
+ }
+ return;
+ default:
+ break;
+ }
+
+ // Note: The following permissions logic is mirrored in
+ // extensions::MatchPattern::MatchesCookie.
+ // If it changes, please update that function, or file a bug for someone
+ // else to do so.
+
+ // check if aHostURI is using an https secure protocol.
+ // if it isn't, then we can't send a secure cookie over the connection.
+ // if SchemeIs fails, assume an insecure connection, to be on the safe side
+ bool potentiallyTrustworthy =
+ nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
+
+ int64_t currentTimeInUsec = PR_Now();
+ int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
+ bool stale = false;
+
+ const nsTArray<RefPtr<Cookie>>* cookies =
+ storage->GetCookiesFromHost(baseDomain, aOriginAttrs);
+ if (!cookies) {
+ return;
+ }
+
+ bool laxByDefault =
+ StaticPrefs::network_cookie_sameSite_laxByDefault() &&
+ !nsContentUtils::IsURIInPrefList(
+ aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
+
+ // iterate the cookies!
+ for (Cookie* cookie : *cookies) {
+ // check the host, since the base domain lookup is conservative.
+ if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
+ continue;
+ }
+
+ // if the cookie is secure and the host scheme isn't, we avoid sending
+ // cookie if possible. But for process synchronization purposes, we may want
+ // the content process to know about the cookie (without it's value). In
+ // which case we will wipe the value before sending
+ if (cookie->IsSecure() && !potentiallyTrustworthy &&
+ !aAllowSecureCookiesToInsecureOrigin) {
+ continue;
+ }
+
+ // if the cookie is httpOnly and it's not going directly to the HTTP
+ // connection, don't send it
+ if (cookie->IsHttpOnly() && !aHttpBound) {
+ continue;
+ }
+
+ // if the nsIURI path doesn't match the cookie path, don't send it back
+ if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
+ continue;
+ }
+
+ // check if the cookie has expired
+ if (cookie->Expiry() <= currentTime) {
+ continue;
+ }
+
+ if (aHttpBound && aIsSameSiteForeign) {
+ bool blockCookie = !ProcessSameSiteCookieForForeignRequest(
+ aChannel, cookie, aIsSafeTopLevelNav, aHadCrossSiteRedirects,
+ laxByDefault);
+
+ if (blockCookie) {
+ if (aHadCrossSiteRedirects) {
+ CookieLogging::LogMessageToConsole(
+ crc, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieBlockedCrossSiteRedirect"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(cookie->Name()),
+ });
+ }
+ continue;
+ }
+ }
+
+ // all checks passed - add to list and check if lastAccessed stamp needs
+ // updating
+ aCookieList.AppendElement(cookie);
+ if (cookie->IsStale()) {
+ stale = true;
+ }
+ }
+
+ if (aCookieList.IsEmpty()) {
+ return;
+ }
+
+ // Send a notification about the acceptance of the cookies now that we found
+ // some.
+ NotifyAccepted(aChannel);
+
+ // update lastAccessed timestamps. we only do this if the timestamp is stale
+ // by a certain amount, to avoid thrashing the db during pageload.
+ if (stale) {
+ storage->StaleCookies(aCookieList, currentTimeInUsec);
+ }
+
+ // return cookies in order of path length; longest to shortest.
+ // this is required per RFC2109. if cookies match in length,
+ // then sort by creation time (see bug 236772).
+ aCookieList.Sort(CompareCookiesForSending());
+}
+
+static bool ContainsUnicodeChars(const nsCString& str) {
+ const auto* start = str.BeginReading();
+ const auto* end = str.EndReading();
+
+ return std::find_if(start, end, [](unsigned char c) { return c >= 0x80; }) !=
+ end;
+}
+
+static void RecordUnicodeTelemetry(const CookieStruct& cookieData) {
+ auto label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::none;
+ if (ContainsUnicodeChars(cookieData.name())) {
+ label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeName;
+ } else if (ContainsUnicodeChars(cookieData.value())) {
+ label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeValue;
+ }
+ Telemetry::AccumulateCategorical(label);
+}
+
+static void RecordPartitionedTelemetry(const CookieStruct& aCookieData,
+ bool aIsForeign) {
+ mozilla::glean::networking::set_cookie.Add(1);
+ if (aCookieData.isPartitioned()) {
+ mozilla::glean::networking::set_cookie_partitioned.AddToNumerator(1);
+ }
+ if (aIsForeign) {
+ mozilla::glean::networking::set_cookie_foreign.AddToNumerator(1);
+ }
+ if (aIsForeign && aCookieData.isPartitioned()) {
+ mozilla::glean::networking::set_cookie_foreign_partitioned.AddToNumerator(
+ 1);
+ }
+}
+
+// processes a single cookie, and returns true if there are more cookies
+// to be processed
+bool CookieService::CanSetCookie(
+ nsIURI* aHostURI, const nsACString& aBaseDomain, CookieStruct& aCookieData,
+ bool aRequireHostMatch, CookieStatus aStatus, nsCString& aCookieHeader,
+ bool aFromHttp, bool aIsForeignAndNotAddon, bool aPartitionedOnly,
+ nsIConsoleReportCollector* aCRC, bool& aSetCookie) {
+ MOZ_ASSERT(aHostURI);
+
+ aSetCookie = false;
+
+ // init expiryTime such that session cookies won't prematurely expire
+ aCookieData.expiry() = INT64_MAX;
+
+ aCookieData.schemeMap() = CookieCommons::URIToSchemeType(aHostURI);
+
+ // aCookieHeader is an in/out param to point to the next cookie, if
+ // there is one. Save the present value for logging purposes
+ nsCString savedCookieHeader(aCookieHeader);
+
+ // newCookie says whether there are multiple cookies in the header;
+ // so we can handle them separately.
+ nsAutoCString expires;
+ nsAutoCString maxage;
+ bool acceptedByParser = false;
+ bool newCookie = ParseAttributes(aCRC, aHostURI, aCookieHeader, aCookieData,
+ expires, maxage, acceptedByParser);
+ if (!acceptedByParser) {
+ return newCookie;
+ }
+
+ // Collect telemetry on how often secure cookies are set from non-secure
+ // origins, and vice-versa.
+ //
+ // 0 = nonsecure and "http:"
+ // 1 = nonsecure and "https:"
+ // 2 = secure and "http:"
+ // 3 = secure and "https:"
+ bool potentiallyTrustworthy =
+ nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
+
+ int64_t currentTimeInUsec = PR_Now();
+
+ // calculate expiry time of cookie.
+ aCookieData.isSession() =
+ GetExpiry(aCookieData, expires, maxage,
+ currentTimeInUsec / PR_USEC_PER_SEC, aFromHttp);
+ if (aStatus == STATUS_ACCEPT_SESSION) {
+ // force lifetime to session. note that the expiration time, if set above,
+ // will still apply.
+ aCookieData.isSession() = true;
+ }
+
+ // reject cookie if it's over the size limit, per RFC2109
+ if (!CookieCommons::CheckNameAndValueSize(aCookieData)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "cookie too big (> 4kb)");
+
+ AutoTArray<nsString, 2> params = {
+ NS_ConvertUTF8toUTF16(aCookieData.name())};
+
+ nsString size;
+ size.AppendInt(kMaxBytesPerCookie);
+ params.AppendElement(size);
+
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
+ "CookieOversize"_ns, params);
+ return newCookie;
+ }
+
+ RecordUnicodeTelemetry(aCookieData);
+
+ // We count SetCookie operations in the parent process only for HTTP set
+ // cookies to prevent double counting.
+ if (XRE_IsParentProcess() || !aFromHttp) {
+ RecordPartitionedTelemetry(aCookieData, aIsForeignAndNotAddon);
+ }
+
+ if (!CookieCommons::CheckName(aCookieData)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "invalid name character");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedInvalidCharName"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ // domain & path checks
+ if (!CheckDomain(aCookieData, aHostURI, aBaseDomain, aRequireHostMatch)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "failed the domain tests");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedInvalidDomain"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ if (!CheckPath(aCookieData, aCRC, aHostURI)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "failed the path tests");
+ return newCookie;
+ }
+
+ if (!CheckHiddenPrefix(aCookieData)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "failed the CheckHiddenPrefix tests");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedInvalidPrefix"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
+ if (!CheckPrefixes(aCookieData, potentiallyTrustworthy)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "failed the prefix tests");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedInvalidPrefix"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ if (!CookieCommons::CheckValue(aCookieData)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "invalid value character");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedInvalidCharValue"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ // if the new cookie is httponly, make sure we're not coming from script
+ if (!aFromHttp && aCookieData.isHttpOnly()) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "cookie is httponly; coming from script");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedHttpOnlyButFromScript"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ // If the new cookie is non-https and wants to set secure flag,
+ // browser have to ignore this new cookie.
+ // (draft-ietf-httpbis-cookie-alone section 3.1)
+ if (aCookieData.isSecure() && !potentiallyTrustworthy) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "non-https cookie can't set secure flag");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedSecureButNonHttps"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ // If the new cookie is same-site but in a cross site context,
+ // browser must ignore the cookie.
+ bool laxByDefault =
+ StaticPrefs::network_cookie_sameSite_laxByDefault() &&
+ !nsContentUtils::IsURIInPrefList(
+ aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
+ auto effectiveSameSite =
+ laxByDefault ? aCookieData.sameSite() : aCookieData.rawSameSite();
+ if ((effectiveSameSite != nsICookie::SAMESITE_NONE) &&
+ aIsForeignAndNotAddon) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "failed the samesite tests");
+
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieRejectedForNonSameSiteness"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+
+ // If the cookie does not have the partitioned attribute,
+ // but is foreign we should give the developer a message.
+ // If CHIPS isn't required yet, we will warn the console
+ // that we have upcoming changes. Otherwise we give a rejection message.
+ if (aPartitionedOnly && !aCookieData.isPartitioned() &&
+ aIsForeignAndNotAddon) {
+ if (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
+ "foreign cookies must be partitioned");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
+ "CookieForeignNoPartitionedError"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ return newCookie;
+ }
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
+ "CookieForeignNoPartitionedWarning"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieData.name()),
+ });
+ }
+
+ aSetCookie = true;
+ return newCookie;
+}
+
+/******************************************************************************
+ * CookieService impl:
+ * private cookie header parsing functions
+ ******************************************************************************/
+
+// clang-format off
+// The following comment block elucidates the function of ParseAttributes.
+/******************************************************************************
+ ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
+ ** please note: this BNF deviates from both specifications, and reflects this
+ ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
+
+ ** Differences from RFC2109/2616 and explanations:
+ 1. implied *LWS
+ The grammar described by this specification is word-based. Except
+ where noted otherwise, linear white space (<LWS>) can be included
+ between any two adjacent words (token or quoted-string), and
+ between adjacent words and separators, without changing the
+ interpretation of a field.
+ <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
+
+ 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
+ common use inside values.
+
+ 3. tokens and values have looser restrictions on allowed characters than
+ spec. This is also due to certain characters being in common use inside
+ values. We allow only '=' to separate token/value pairs, and ';' to
+ terminate tokens or values. <LWS> is allowed within tokens and values
+ (see bug 206022).
+
+ 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
+ reject control chars or non-ASCII chars. This is erring on the loose
+ side, since there's probably no good reason to enforce this strictness.
+
+ 5. Attribute "HttpOnly", not covered in the RFCs, is supported
+ (see bug 178993).
+
+ ** Begin BNF:
+ token = 1*<any allowed-chars except separators>
+ value = 1*<any allowed-chars except value-sep>
+ separators = ";" | "="
+ value-sep = ";"
+ cookie-sep = CR | LF
+ allowed-chars = <any OCTET except NUL or cookie-sep>
+ OCTET = <any 8-bit sequence of data>
+ LWS = SP | HT
+ NUL = <US-ASCII NUL, null control character (0)>
+ CR = <US-ASCII CR, carriage return (13)>
+ LF = <US-ASCII LF, linefeed (10)>
+ SP = <US-ASCII SP, space (32)>
+ HT = <US-ASCII HT, horizontal-tab (9)>
+
+ set-cookie = "Set-Cookie:" cookies
+ cookies = cookie *( cookie-sep cookie )
+ cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
+ NAME = token ; cookie name
+ VALUE = value ; cookie value
+ cookie-av = token ["=" value]
+
+ valid values for cookie-av (checked post-parsing) are:
+ cookie-av = "Path" "=" value
+ | "Domain" "=" value
+ | "Expires" "=" value
+ | "Max-Age" "=" value
+ | "Comment" "=" value
+ | "Version" "=" value
+ | "Secure"
+ | "HttpOnly"
+
+******************************************************************************/
+// clang-format on
+
+// helper functions for GetTokenValue
+static inline bool isnull(char c) { return c == 0; }
+static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; }
+static inline bool isterminator(char c) { return c == '\n' || c == '\r'; }
+static inline bool isvalueseparator(char c) {
+ return isterminator(c) || c == ';';
+}
+static inline bool istokenseparator(char c) {
+ return isvalueseparator(c) || c == '=';
+}
+
+// Parse a single token/value pair.
+// Returns true if a cookie terminator is found, so caller can parse new cookie.
+bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
+ nsACString::const_char_iterator& aEndIter,
+ nsDependentCSubstring& aTokenString,
+ nsDependentCSubstring& aTokenValue,
+ bool& aEqualsFound) {
+ nsACString::const_char_iterator start;
+ nsACString::const_char_iterator lastSpace;
+ // initialize value string to clear garbage
+ aTokenValue.Rebind(aIter, aIter);
+
+ // find <token>, including any <LWS> between the end-of-token and the
+ // token separator. we'll remove trailing <LWS> next
+ while (aIter != aEndIter && iswhitespace(*aIter)) {
+ ++aIter;
+ }
+ start = aIter;
+ while (aIter != aEndIter && !isnull(*aIter) && !istokenseparator(*aIter)) {
+ ++aIter;
+ }
+
+ // remove trailing <LWS>; first check we're not at the beginning
+ lastSpace = aIter;
+ if (lastSpace != start) {
+ while (--lastSpace != start && iswhitespace(*lastSpace)) {
+ }
+ ++lastSpace;
+ }
+ aTokenString.Rebind(start, lastSpace);
+
+ aEqualsFound = (*aIter == '=');
+ if (aEqualsFound) {
+ // find <value>
+ while (++aIter != aEndIter && iswhitespace(*aIter)) {
+ }
+
+ start = aIter;
+
+ // process <token>
+ // just look for ';' to terminate ('=' allowed)
+ while (aIter != aEndIter && !isnull(*aIter) && !isvalueseparator(*aIter)) {
+ ++aIter;
+ }
+
+ // remove trailing <LWS>; first check we're not at the beginning
+ if (aIter != start) {
+ lastSpace = aIter;
+ while (--lastSpace != start && iswhitespace(*lastSpace)) {
+ }
+
+ aTokenValue.Rebind(start, ++lastSpace);
+ }
+ }
+
+ // aIter is on ';', or terminator, or EOS
+ if (aIter != aEndIter) {
+ // if on terminator, increment past & return true to process new cookie
+ if (isterminator(*aIter)) {
+ ++aIter;
+ return true;
+ }
+ // fall-through: aIter is on ';', increment and return false
+ ++aIter;
+ }
+ return false;
+}
+
+static inline void SetSameSiteAttributeDefault(CookieStruct& aCookieData) {
+ // Set cookie with SameSite attribute that is treated as Default
+ // and doesn't requires changing the DB schema.
+ aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
+ aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
+}
+
+static inline void SetSameSiteAttribute(CookieStruct& aCookieData,
+ int32_t aValue) {
+ aCookieData.sameSite() = aValue;
+ aCookieData.rawSameSite() = aValue;
+}
+
+// Parses attributes from cookie header. expires/max-age attributes aren't
+// folded into the cookie struct here, because we don't know which one to use
+// until we've parsed the header.
+bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC,
+ nsIURI* aHostURI, nsCString& aCookieHeader,
+ CookieStruct& aCookieData,
+ nsACString& aExpires, nsACString& aMaxage,
+ bool& aAcceptedByParser) {
+ aAcceptedByParser = false;
+
+ static const char kPath[] = "path";
+ static const char kDomain[] = "domain";
+ static const char kExpires[] = "expires";
+ static const char kMaxage[] = "max-age";
+ static const char kSecure[] = "secure";
+ static const char kHttpOnly[] = "httponly";
+ static const char kSameSite[] = "samesite";
+ static const char kSameSiteLax[] = "lax";
+ static const char kSameSiteNone[] = "none";
+ static const char kSameSiteStrict[] = "strict";
+ static const char kPartitioned[] = "partitioned";
+
+ nsACString::const_char_iterator cookieStart;
+ aCookieHeader.BeginReading(cookieStart);
+
+ nsACString::const_char_iterator cookieEnd;
+ aCookieHeader.EndReading(cookieEnd);
+
+ aCookieData.isSecure() = false;
+ aCookieData.isHttpOnly() = false;
+
+ SetSameSiteAttributeDefault(aCookieData);
+
+ nsDependentCSubstring tokenString(cookieStart, cookieStart);
+ nsDependentCSubstring tokenValue(cookieStart, cookieStart);
+ bool newCookie;
+ bool equalsFound;
+
+ // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
+ // if we find multiple cookies, return for processing
+ // note: if there's no '=', we assume token is <VALUE>. this is required by
+ // some sites (see bug 169091).
+ // XXX fix the parser to parse according to <VALUE> grammar for this case
+ newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
+ equalsFound);
+ if (equalsFound) {
+ aCookieData.name() = tokenString;
+ aCookieData.value() = tokenValue;
+ } else {
+ aCookieData.value() = tokenString;
+ }
+
+ // extract remaining attributes
+ while (cookieStart != cookieEnd && !newCookie) {
+ newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
+ equalsFound);
+
+ // decide which attribute we have, and copy the string
+ if (tokenString.LowerCaseEqualsLiteral(kPath)) {
+ aCookieData.path() = tokenValue;
+
+ } else if (tokenString.LowerCaseEqualsLiteral(kDomain)) {
+ aCookieData.host() = tokenValue;
+
+ } else if (tokenString.LowerCaseEqualsLiteral(kExpires)) {
+ aExpires = tokenValue;
+
+ } else if (tokenString.LowerCaseEqualsLiteral(kMaxage)) {
+ aMaxage = tokenValue;
+
+ // ignore any tokenValue for isSecure; just set the boolean
+ } else if (tokenString.LowerCaseEqualsLiteral(kSecure)) {
+ aCookieData.isSecure() = true;
+
+ // ignore any tokenValue for isPartitioned; just set the boolean
+ } else if (tokenString.LowerCaseEqualsLiteral(kPartitioned)) {
+ aCookieData.isPartitioned() = true;
+
+ // ignore any tokenValue for isHttpOnly (see bug 178993);
+ // just set the boolean
+ } else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) {
+ aCookieData.isHttpOnly() = true;
+
+ } else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
+ if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
+ SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_LAX);
+ } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) {
+ SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_STRICT);
+ } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteNone)) {
+ SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_NONE);
+ } else {
+ // Reset to Default if unknown token value (see Bug 1682450)
+ SetSameSiteAttributeDefault(aCookieData);
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieSameSiteValueInvalid2"_ns,
+ AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
+ }
+ }
+ }
+
+ // re-assign aCookieHeader, in case we need to process another cookie
+ aCookieHeader.Assign(Substring(cookieStart, cookieEnd));
+
+ // If same-site is explicitly set to 'none' but this is not a secure context,
+ // let's abort the parsing.
+ if (!aCookieData.isSecure() &&
+ aCookieData.sameSite() == nsICookie::SAMESITE_NONE) {
+ if (StaticPrefs::network_cookie_sameSite_noneRequiresSecure()) {
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::errorFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieRejectedNonRequiresSecure2"_ns,
+ AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
+ return newCookie;
+ }
+
+ // Still warn about the missing Secure attribute when not enforcing.
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieRejectedNonRequiresSecureForBeta3"_ns,
+ AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
+ SAMESITE_MDN_URL});
+ }
+
+ // Ensure the partitioned cookie is set with the secure attribute.
+ if (aCookieData.isPartitioned() && !aCookieData.isSecure()) {
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::errorFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedPartitionedRequiresSecure"_ns,
+ AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
+
+ // We only drop the cookie if CHIPS is enabled.
+ if (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
+ return newCookie;
+ }
+ }
+
+ if (aCookieData.rawSameSite() == nsICookie::SAMESITE_NONE &&
+ aCookieData.sameSite() == nsICookie::SAMESITE_LAX) {
+ bool laxByDefault =
+ StaticPrefs::network_cookie_sameSite_laxByDefault() &&
+ !nsContentUtils::IsURIInPrefList(
+ aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
+ if (laxByDefault) {
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieLaxForced2"_ns,
+ AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
+ } else {
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_SAMESITE_CATEGORY, "CookieLaxForcedForBeta2"_ns,
+ AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
+ SAMESITE_MDN_URL});
+ }
+ }
+
+ // Cookie accepted.
+ aAcceptedByParser = true;
+
+ MOZ_ASSERT(Cookie::ValidateSameSite(aCookieData));
+ return newCookie;
+}
+
+/******************************************************************************
+ * CookieService impl:
+ * private domain & permission compliance enforcement functions
+ ******************************************************************************/
+
+// Normalizes the given hostname, component by component. ASCII/ACE
+// components are lower-cased, and UTF-8 components are normalized per
+// RFC 3454 and converted to ACE.
+nsresult CookieService::NormalizeHost(nsCString& aHost) {
+ if (!IsAscii(aHost)) {
+ nsAutoCString host;
+ nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aHost = host;
+ }
+
+ ToLowerCase(aHost);
+ return NS_OK;
+}
+
+// returns true if 'a' is equal to or a subdomain of 'b',
+// assuming no leading dots are present.
+static inline bool IsSubdomainOf(const nsACString& a, const nsACString& b) {
+ if (a == b) {
+ return true;
+ }
+ if (a.Length() > b.Length()) {
+ return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
+ }
+ return false;
+}
+
+CookieStatus CookieService::CheckPrefs(
+ nsIConsoleReportCollector* aCRC, nsICookieJarSettings* aCookieJarSettings,
+ nsIURI* aHostURI, bool aIsForeign, bool aIsThirdPartyTrackingResource,
+ bool aIsThirdPartySocialTrackingResource,
+ bool aStorageAccessPermissionGranted, const nsACString& aCookieHeader,
+ const int aNumOfCookies, const OriginAttributes& aOriginAttrs,
+ uint32_t* aRejectedReason) {
+ nsresult rv;
+
+ MOZ_ASSERT(aRejectedReason);
+
+ *aRejectedReason = 0;
+
+ // don't let unsupported scheme sites get/set cookies (could be a security
+ // issue)
+ if (!CookieCommons::IsSchemeSupported(aHostURI)) {
+ COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
+ "non http/https sites cannot read cookies");
+ return STATUS_REJECTED_WITH_ERROR;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(aHostURI, aOriginAttrs);
+
+ if (!principal) {
+ COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
+ "non-content principals cannot get/set cookies");
+ return STATUS_REJECTED_WITH_ERROR;
+ }
+
+ // check the permission list first; if we find an entry, it overrides
+ // default prefs. see bug 184059.
+ uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
+ rv = aCookieJarSettings->CookiePermission(principal, &cookiePermission);
+ if (NS_SUCCEEDED(rv)) {
+ switch (cookiePermission) {
+ case nsICookiePermission::ACCESS_DENY:
+ COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
+ "cookies are blocked for this site");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieHeader),
+ });
+
+ *aRejectedReason =
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION;
+ return STATUS_REJECTED;
+
+ case nsICookiePermission::ACCESS_ALLOW:
+ return STATUS_ACCEPTED;
+ default:
+ break;
+ }
+ }
+
+ // No cookies allowed if this request comes from a resource in a 3rd party
+ // context, when anti-tracking protection is enabled and when we don't have
+ // access to the first-party cookie jar.
+ if (aIsForeign && aIsThirdPartyTrackingResource &&
+ !aStorageAccessPermissionGranted &&
+ aCookieJarSettings->GetRejectThirdPartyContexts()) {
+ uint32_t rejectReason =
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
+ if (StoragePartitioningEnabled(rejectReason, aCookieJarSettings)) {
+ MOZ_ASSERT(!aOriginAttrs.mPartitionKey.IsEmpty(),
+ "We must have a StoragePrincipal here!");
+ return STATUS_ACCEPTED;
+ }
+
+ COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
+ "cookies are disabled in trackers");
+ if (aIsThirdPartySocialTrackingResource) {
+ *aRejectedReason =
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER;
+ } else {
+ *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
+ }
+ return STATUS_REJECTED;
+ }
+
+ // check default prefs.
+ // Check aStorageAccessPermissionGranted when checking aCookieBehavior
+ // so that we take things such as the content blocking allow list into
+ // account.
+ if (aCookieJarSettings->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT &&
+ !aStorageAccessPermissionGranted) {
+ COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
+ "cookies are disabled");
+ *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL;
+ return STATUS_REJECTED;
+ }
+
+ // check if cookie is foreign
+ if (aIsForeign) {
+ if (aCookieJarSettings->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_FOREIGN &&
+ !aStorageAccessPermissionGranted) {
+ COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
+ "context is third party");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieHeader),
+ });
+ *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
+ return STATUS_REJECTED;
+ }
+
+ if (aCookieJarSettings->GetLimitForeignContexts() &&
+ !aStorageAccessPermissionGranted && aNumOfCookies == 0) {
+ COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
+ "context is third party");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookieHeader),
+ });
+ *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
+ return STATUS_REJECTED;
+ }
+
+ if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) {
+ return STATUS_ACCEPT_SESSION;
+ }
+
+ if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly()) {
+ if (!aHostURI->SchemeIs("https")) {
+ return STATUS_ACCEPT_SESSION;
+ }
+ }
+ }
+
+ // if nothing has complained, accept cookie
+ return STATUS_ACCEPTED;
+}
+
+// processes domain attribute, and returns true if host has permission to set
+// for this domain.
+bool CookieService::CheckDomain(CookieStruct& aCookieData, nsIURI* aHostURI,
+ const nsACString& aBaseDomain,
+ bool aRequireHostMatch) {
+ // Note: The logic in this function is mirrored in
+ // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
+ // If it changes, please update that function, or file a bug for someone
+ // else to do so.
+
+ // get host from aHostURI
+ nsAutoCString hostFromURI;
+ nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
+
+ // if a domain is given, check the host has permission
+ if (!aCookieData.host().IsEmpty()) {
+ // Tolerate leading '.' characters, but not if it's otherwise an empty host.
+ if (aCookieData.host().Length() > 1 && aCookieData.host().First() == '.') {
+ aCookieData.host().Cut(0, 1);
+ }
+
+ // switch to lowercase now, to avoid case-insensitive compares everywhere
+ ToLowerCase(aCookieData.host());
+
+ // check whether the host is either an IP address, an alias such as
+ // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
+ // cases, require an exact string match for the domain, and leave the cookie
+ // as a non-domain one. bug 105917 originally noted the requirement to deal
+ // with IP addresses.
+ if (aRequireHostMatch) {
+ return hostFromURI.Equals(aCookieData.host());
+ }
+
+ // ensure the proposed domain is derived from the base domain; and also
+ // that the host domain is derived from the proposed domain (per RFC2109).
+ if (IsSubdomainOf(aCookieData.host(), aBaseDomain) &&
+ IsSubdomainOf(hostFromURI, aCookieData.host())) {
+ // prepend a dot to indicate a domain cookie
+ aCookieData.host().InsertLiteral(".", 0);
+ return true;
+ }
+
+ /*
+ * note: RFC2109 section 4.3.2 requires that we check the following:
+ * that the portion of host not in domain does not contain a dot.
+ * this prevents hosts of the form x.y.co.nz from setting cookies in the
+ * entire .co.nz domain. however, it's only a only a partial solution and
+ * it breaks sites (IE doesn't enforce it), so we don't perform this check.
+ */
+ return false;
+ }
+
+ // no domain specified, use hostFromURI
+ aCookieData.host() = hostFromURI;
+ return true;
+}
+
+// static
+bool CookieService::CheckHiddenPrefix(CookieStruct& aCookieData) {
+ // If a cookie is nameless, then its value must not start with
+ // `__Host-` or `__Secure-`
+ if (!aCookieData.name().IsEmpty()) {
+ return true;
+ }
+
+ if (StringBeginsWith(aCookieData.value(), "__Host-"_ns)) {
+ return false;
+ }
+
+ if (StringBeginsWith(aCookieData.value(), "__Secure-"_ns)) {
+ return false;
+ }
+
+ return true;
+}
+
+namespace {
+nsAutoCString GetPathFromURI(nsIURI* aHostURI) {
+ // strip down everything after the last slash to get the path,
+ // ignoring slashes in the query string part.
+ // if we can QI to nsIURL, that'll take care of the query string portion.
+ // otherwise, it's not an nsIURL and can't have a query string, so just find
+ // the last slash.
+ nsAutoCString path;
+ nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
+ if (hostURL) {
+ hostURL->GetDirectory(path);
+ } else {
+ aHostURI->GetPathQueryRef(path);
+ int32_t slash = path.RFindChar('/');
+ if (slash != kNotFound) {
+ path.Truncate(slash + 1);
+ }
+ }
+
+ // strip the right-most %x2F ("/") if the path doesn't contain only 1 '/'.
+ int32_t lastSlash = path.RFindChar('/');
+ int32_t firstSlash = path.FindChar('/');
+ if (lastSlash != firstSlash && lastSlash != kNotFound &&
+ lastSlash == static_cast<int32_t>(path.Length() - 1)) {
+ path.Truncate(lastSlash);
+ }
+
+ return path;
+}
+
+} // namespace
+
+bool CookieService::CheckPath(CookieStruct& aCookieData,
+ nsIConsoleReportCollector* aCRC,
+ nsIURI* aHostURI) {
+ // if a path is given, check the host has permission
+ if (aCookieData.path().IsEmpty() || aCookieData.path().First() != '/') {
+ aCookieData.path() = GetPathFromURI(aHostURI);
+ }
+
+ if (!CookieCommons::CheckPathSize(aCookieData)) {
+ AutoTArray<nsString, 2> params = {
+ NS_ConvertUTF8toUTF16(aCookieData.name())};
+
+ nsString size;
+ size.AppendInt(kMaxBytesPerPath);
+ params.AppendElement(size);
+
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
+ "CookiePathOversize"_ns, params);
+ return false;
+ }
+
+ return !aCookieData.path().Contains('\t');
+}
+
+// CheckPrefixes
+//
+// Reject cookies whose name starts with the magic prefixes from
+// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis
+// if they do not meet the criteria required by the prefix.
+//
+// Must not be called until after CheckDomain() and CheckPath() have
+// regularized and validated the CookieStruct values!
+bool CookieService::CheckPrefixes(CookieStruct& aCookieData,
+ bool aSecureRequest) {
+ static const char kSecure[] = "__Secure-";
+ static const char kHost[] = "__Host-";
+ static const int kSecureLen = sizeof(kSecure) - 1;
+ static const int kHostLen = sizeof(kHost) - 1;
+
+ bool isSecure = strncmp(aCookieData.name().get(), kSecure, kSecureLen) == 0;
+ bool isHost = strncmp(aCookieData.name().get(), kHost, kHostLen) == 0;
+
+ if (!isSecure && !isHost) {
+ // not one of the magic prefixes: carry on
+ return true;
+ }
+
+ if (!aSecureRequest || !aCookieData.isSecure()) {
+ // the magic prefixes may only be used from a secure request and
+ // the secure attribute must be set on the cookie
+ return false;
+ }
+
+ if (isHost) {
+ // The host prefix requires that the path is "/" and that the cookie
+ // had no domain attribute. CheckDomain() and CheckPath() MUST be run
+ // first to make sure invalid attributes are rejected and to regularlize
+ // them. In particular all explicit domain attributes result in a host
+ // that starts with a dot, and if the host doesn't start with a dot it
+ // correctly matches the true host.
+ if (aCookieData.host()[0] == '.' ||
+ !aCookieData.path().EqualsLiteral("/")) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool CookieService::GetExpiry(CookieStruct& aCookieData,
+ const nsACString& aExpires,
+ const nsACString& aMaxage, int64_t aCurrentTime,
+ bool aFromHttp) {
+ // maxageCap is in seconds.
+ // Disabled for HTTP cookies.
+ int64_t maxageCap =
+ aFromHttp ? 0 : StaticPrefs::privacy_documentCookies_maxage();
+
+ /* Determine when the cookie should expire. This is done by taking the
+ * difference between the server time and the time the server wants the cookie
+ * to expire, and adding that difference to the client time. This localizes
+ * the client time regardless of whether or not the TZ environment variable
+ * was set on the client.
+ *
+ * Note: We need to consider accounting for network lag here, per RFC.
+ */
+ // check for max-age attribute first; this overrides expires attribute
+ if (!aMaxage.IsEmpty()) {
+ // obtain numeric value of maxageAttribute
+ int64_t maxage;
+ int32_t numInts = PR_sscanf(aMaxage.BeginReading(), "%lld", &maxage);
+
+ // default to session cookie if the conversion failed
+ if (numInts != 1) {
+ return true;
+ }
+
+ // if this addition overflows, expiryTime will be less than currentTime
+ // and the cookie will be expired - that's okay.
+ if (maxageCap) {
+ aCookieData.expiry() = aCurrentTime + std::min(maxage, maxageCap);
+ } else {
+ aCookieData.expiry() = aCurrentTime + maxage;
+ }
+
+ // check for expires attribute
+ } else if (!aExpires.IsEmpty()) {
+ PRTime expires;
+
+ // parse expiry time
+ if (PR_ParseTimeString(aExpires.BeginReading(), true, &expires) !=
+ PR_SUCCESS) {
+ return true;
+ }
+
+ // If set-cookie used absolute time to set expiration, and it can't use
+ // client time to set expiration.
+ // Because if current time be set in the future, but the cookie expire
+ // time be set less than current time and more than server time.
+ // The cookie item have to be used to the expired cookie.
+ if (maxageCap) {
+ aCookieData.expiry() = std::min(expires / int64_t(PR_USEC_PER_SEC),
+ aCurrentTime + maxageCap);
+ } else {
+ aCookieData.expiry() = expires / int64_t(PR_USEC_PER_SEC);
+ }
+
+ // default to session cookie if no attributes found. Here we don't need to
+ // enforce the maxage cap, because session cookies are short-lived by
+ // definition.
+ } else {
+ return true;
+ }
+
+ return false;
+}
+
+/******************************************************************************
+ * CookieService impl:
+ * private cookielist management functions
+ ******************************************************************************/
+
+// find whether a given cookie has been previously set. this is provided by the
+// nsICookieManager interface.
+NS_IMETHODIMP
+CookieService::CookieExists(const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aName,
+ JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx, bool* aFoundCookie) {
+ NS_ENSURE_ARG_POINTER(aCx);
+ NS_ENSURE_ARG_POINTER(aFoundCookie);
+
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie);
+}
+
+NS_IMETHODIMP_(nsresult)
+CookieService::CookieExistsNative(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aName,
+ OriginAttributes* aOriginAttributes,
+ bool* aFoundCookie) {
+ nsCOMPtr<nsICookie> cookie;
+ nsresult rv = GetCookieNative(aHost, aPath, aName, aOriginAttributes,
+ getter_AddRefs(cookie));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aFoundCookie = cookie != nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(nsresult)
+CookieService::GetCookieNative(const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aName,
+ OriginAttributes* aOriginAttributes,
+ nsICookie** aCookie) {
+ NS_ENSURE_ARG_POINTER(aOriginAttributes);
+ NS_ENSURE_ARG_POINTER(aCookie);
+
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString baseDomain;
+ nsresult rv =
+ CookieCommons::GetBaseDomainFromHost(mTLDService, aHost, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CookieListIter iter{};
+ CookieStorage* storage = PickStorage(*aOriginAttributes);
+ bool foundCookie = storage->FindCookie(baseDomain, *aOriginAttributes, aHost,
+ aName, aPath, iter);
+
+ if (foundCookie) {
+ RefPtr<Cookie> cookie = iter.Cookie();
+ NS_ENSURE_TRUE(cookie, NS_ERROR_NULL_POINTER);
+
+ cookie.forget(aCookie);
+ }
+
+ return NS_OK;
+}
+
+// count the number of cookies stored by a particular host. this is provided by
+// the nsICookieManager interface.
+NS_IMETHODIMP
+CookieService::CountCookiesFromHost(const nsACString& aHost,
+ uint32_t* aCountFromHost) {
+ // first, normalize the hostname, and fail if it contains illegal characters.
+ nsAutoCString host(aHost);
+ nsresult rv = NormalizeHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDomain;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+
+ *aCountFromHost = mPersistentStorage->CountCookiesFromHost(baseDomain, 0);
+
+ return NS_OK;
+}
+
+// get an enumerator of cookies stored by a particular host. this is provided by
+// the nsICookieManager interface.
+NS_IMETHODIMP
+CookieService::GetCookiesFromHost(const nsACString& aHost,
+ JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx,
+ nsTArray<RefPtr<nsICookie>>& aResult) {
+ // first, normalize the hostname, and fail if it contains illegal characters.
+ nsAutoCString host(aHost);
+ nsresult rv = NormalizeHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDomain;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CookieStorage* storage = PickStorage(attrs);
+
+ const nsTArray<RefPtr<Cookie>>* cookies =
+ storage->GetCookiesFromHost(baseDomain, attrs);
+
+ if (cookies) {
+ aResult.SetCapacity(cookies->Length());
+ for (Cookie* cookie : *cookies) {
+ aResult.AppendElement(cookie);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::GetCookiesWithOriginAttributes(
+ const nsAString& aPattern, const nsACString& aHost,
+ nsTArray<RefPtr<nsICookie>>& aResult) {
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString host(aHost);
+ nsresult rv = NormalizeHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDomain;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetCookiesWithOriginAttributes(pattern, baseDomain, aResult);
+}
+
+nsresult CookieService::GetCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain,
+ nsTArray<RefPtr<nsICookie>>& aResult) {
+ CookieStorage* storage = PickStorage(aPattern);
+ storage->GetCookiesWithOriginAttributes(aPattern, aBaseDomain, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
+ const nsACString& aHost) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString host(aHost);
+ nsresult rv = NormalizeHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDomain;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
+}
+
+nsresult CookieService::RemoveCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain) {
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CookieStorage* storage = PickStorage(aPattern);
+ storage->RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::RemoveCookiesFromExactHost(const nsACString& aHost,
+ const nsAString& aPattern) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return RemoveCookiesFromExactHost(aHost, pattern);
+}
+
+nsresult CookieService::RemoveCookiesFromExactHost(
+ const nsACString& aHost, const OriginAttributesPattern& aPattern) {
+ nsAutoCString host(aHost);
+ nsresult rv = NormalizeHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDomain;
+ rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CookieStorage* storage = PickStorage(aPattern);
+ storage->RemoveCookiesFromExactHost(aHost, baseDomain, aPattern);
+
+ return NS_OK;
+}
+
+namespace {
+
+class RemoveAllSinceRunnable : public Runnable {
+ public:
+ using CookieArray = nsTArray<RefPtr<nsICookie>>;
+ RemoveAllSinceRunnable(Promise* aPromise, CookieService* aSelf,
+ CookieArray&& aCookieArray, int64_t aSinceWhen)
+ : Runnable("RemoveAllSinceRunnable"),
+ mPromise(aPromise),
+ mSelf(aSelf),
+ mList(std::move(aCookieArray)),
+ mIndex(0),
+ mSinceWhen(aSinceWhen) {}
+
+ NS_IMETHODIMP Run() override {
+ RemoveSome();
+
+ if (mIndex < mList.Length()) {
+ return NS_DispatchToCurrentThread(this);
+ }
+ mPromise->MaybeResolveWithUndefined();
+
+ return NS_OK;
+ }
+
+ private:
+ void RemoveSome() {
+ for (CookieArray::size_type iter = 0;
+ iter < kYieldPeriod && mIndex < mList.Length(); ++mIndex, ++iter) {
+ auto* cookie = static_cast<Cookie*>(mList[mIndex].get());
+ if (cookie->CreationTime() > mSinceWhen &&
+ NS_FAILED(mSelf->Remove(cookie->Host(), cookie->OriginAttributesRef(),
+ cookie->Name(), cookie->Path()))) {
+ continue;
+ }
+ }
+ }
+
+ private:
+ RefPtr<Promise> mPromise;
+ RefPtr<CookieService> mSelf;
+ CookieArray mList;
+ CookieArray::size_type mIndex;
+ int64_t mSinceWhen;
+ static const CookieArray::size_type kYieldPeriod = 10;
+};
+
+} // namespace
+
+NS_IMETHODIMP
+CookieService::RemoveAllSince(int64_t aSinceWhen, JSContext* aCx,
+ Promise** aRetVal) {
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ mPersistentStorage->EnsureInitialized();
+
+ nsTArray<RefPtr<nsICookie>> cookieList;
+
+ // We delete only non-private cookies.
+ mPersistentStorage->GetAll(cookieList);
+
+ RefPtr<RemoveAllSinceRunnable> runMe = new RemoveAllSinceRunnable(
+ promise, this, std::move(cookieList), aSinceWhen);
+
+ promise.forget(aRetVal);
+
+ return runMe->Run();
+}
+
+namespace {
+
+class CompareCookiesCreationTime {
+ public:
+ static bool Equals(const nsICookie* aCookie1, const nsICookie* aCookie2) {
+ return static_cast<const Cookie*>(aCookie1)->CreationTime() ==
+ static_cast<const Cookie*>(aCookie2)->CreationTime();
+ }
+
+ static bool LessThan(const nsICookie* aCookie1, const nsICookie* aCookie2) {
+ return static_cast<const Cookie*>(aCookie1)->CreationTime() <
+ static_cast<const Cookie*>(aCookie2)->CreationTime();
+ }
+};
+
+} // namespace
+
+NS_IMETHODIMP
+CookieService::GetCookiesSince(int64_t aSinceWhen,
+ nsTArray<RefPtr<nsICookie>>& aResult) {
+ if (!IsInitialized()) {
+ return NS_OK;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+
+ // We expose only non-private cookies.
+ nsTArray<RefPtr<nsICookie>> cookieList;
+ mPersistentStorage->GetAll(cookieList);
+
+ for (RefPtr<nsICookie>& cookie : cookieList) {
+ if (static_cast<Cookie*>(cookie.get())->CreationTime() >= aSinceWhen) {
+ aResult.AppendElement(cookie);
+ }
+ }
+
+ aResult.Sort(CompareCookiesCreationTime());
+ return NS_OK;
+}
+
+size_t CookieService::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ if (mPersistentStorage) {
+ n += mPersistentStorage->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mPrivateStorage) {
+ n += mPrivateStorage->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
+
+NS_IMETHODIMP
+CookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool /*aAnonymize*/) {
+ MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(CookieServiceMallocSizeOf),
+ "Memory used by the cookie service.");
+
+ return NS_OK;
+}
+
+bool CookieService::IsInitialized() const {
+ if (!mPersistentStorage) {
+ NS_WARNING("No CookieStorage! Profile already close?");
+ return false;
+ }
+
+ MOZ_ASSERT(mPrivateStorage);
+ return true;
+}
+
+CookieStorage* CookieService::PickStorage(const OriginAttributes& aAttrs) {
+ MOZ_ASSERT(IsInitialized());
+
+ if (aAttrs.mPrivateBrowsingId > 0) {
+ return mPrivateStorage;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+ return mPersistentStorage;
+}
+
+CookieStorage* CookieService::PickStorage(
+ const OriginAttributesPattern& aAttrs) {
+ MOZ_ASSERT(IsInitialized());
+
+ if (aAttrs.mPrivateBrowsingId.WasPassed() &&
+ aAttrs.mPrivateBrowsingId.Value() > 0) {
+ return mPrivateStorage;
+ }
+
+ mPersistentStorage->EnsureInitialized();
+ return mPersistentStorage;
+}
+
+bool CookieService::SetCookiesFromIPC(const nsACString& aBaseDomain,
+ const OriginAttributes& aAttrs,
+ nsIURI* aHostURI, bool aFromHttp,
+ const nsTArray<CookieStruct>& aCookies,
+ BrowsingContext* aBrowsingContext) {
+ if (!IsInitialized()) {
+ // If we are probably shutting down, we can ignore this cookie.
+ return true;
+ }
+
+ CookieStorage* storage = PickStorage(aAttrs);
+ int64_t currentTimeInUsec = PR_Now();
+
+ for (const CookieStruct& cookieData : aCookies) {
+ if (!CookieCommons::CheckPathSize(cookieData)) {
+ return false;
+ }
+
+ // reject cookie if it's over the size limit, per RFC2109
+ if (!CookieCommons::CheckNameAndValueSize(cookieData)) {
+ return false;
+ }
+
+ RecordUnicodeTelemetry(cookieData);
+
+ if (!CookieCommons::CheckName(cookieData)) {
+ return false;
+ }
+
+ if (!CookieCommons::CheckValue(cookieData)) {
+ return false;
+ }
+
+ // create a new Cookie and copy attributes
+ RefPtr<Cookie> cookie = Cookie::Create(cookieData, aAttrs);
+ if (!cookie) {
+ continue;
+ }
+
+ cookie->SetLastAccessed(currentTimeInUsec);
+ cookie->SetCreationTime(
+ Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
+
+ storage->AddCookie(nullptr, aBaseDomain, aAttrs, cookie, currentTimeInUsec,
+ aHostURI, ""_ns, aFromHttp, aBrowsingContext);
+ }
+
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieService.h b/netwerk/cookie/CookieService.h
new file mode 100644
index 0000000000..09eb4c1289
--- /dev/null
+++ b/netwerk/cookie/CookieService.h
@@ -0,0 +1,163 @@
+/* -*- 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_CookieService_h
+#define mozilla_net_CookieService_h
+
+#include "nsICookieService.h"
+#include "nsICookieManager.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+#include "Cookie.h"
+#include "CookieCommons.h"
+
+#include "nsString.h"
+#include "nsIMemoryReporter.h"
+#include "mozilla/MemoryReporting.h"
+
+class nsIConsoleReportCollector;
+class nsICookieJarSettings;
+class nsIEffectiveTLDService;
+class nsIIDNService;
+class nsIURI;
+class nsIChannel;
+class mozIThirdPartyUtil;
+
+namespace mozilla {
+namespace net {
+
+class CookiePersistentStorage;
+class CookiePrivateStorage;
+class CookieStorage;
+
+/******************************************************************************
+ * CookieService:
+ * class declaration
+ ******************************************************************************/
+
+class CookieService final : public nsICookieService,
+ public nsICookieManager,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIMemoryReporter {
+ private:
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSICOOKIESERVICE
+ NS_DECL_NSICOOKIEMANAGER
+ NS_DECL_NSIMEMORYREPORTER
+
+ static already_AddRefed<CookieService> GetSingleton();
+
+ CookieService();
+ static already_AddRefed<nsICookieService> GetXPCOMSingleton();
+ nsresult Init();
+
+ /**
+ * Start watching the observer service for messages indicating that an app has
+ * been uninstalled. When an app is uninstalled, we get the cookie service
+ * (thus instantiating it, if necessary) and clear all the cookies for that
+ * app.
+ */
+
+ static bool CanSetCookie(nsIURI* aHostURI, const nsACString& aBaseDomain,
+ CookieStruct& aCookieData, bool aRequireHostMatch,
+ CookieStatus aStatus, nsCString& aCookieHeader,
+ bool aFromHttp, bool aIsForeignAndNotAddon,
+ bool aPartitionedOnly,
+ nsIConsoleReportCollector* aCRC, bool& aSetCookie);
+ static CookieStatus CheckPrefs(
+ nsIConsoleReportCollector* aCRC, nsICookieJarSettings* aCookieJarSettings,
+ nsIURI* aHostURI, bool aIsForeign, bool aIsThirdPartyTrackingResource,
+ bool aIsThirdPartySocialTrackingResource,
+ bool aStorageAccessPermissionGranted, const nsACString& aCookieHeader,
+ const int aNumOfCookies, const OriginAttributes& aOriginAttrs,
+ uint32_t* aRejectedReason);
+
+ void GetCookiesForURI(nsIURI* aHostURI, nsIChannel* aChannel, bool aIsForeign,
+ bool aIsThirdPartyTrackingResource,
+ bool aIsThirdPartySocialTrackingResource,
+ bool aStorageAccessPermissionGranted,
+ uint32_t aRejectedReason, bool aIsSafeTopLevelNav,
+ bool aIsSameSiteForeign, bool aHadCrossSiteRedirects,
+ bool aHttpBound,
+ bool aAllowSecureCookiesToInsecureOrigin,
+ const OriginAttributes& aOriginAttrs,
+ nsTArray<Cookie*>& aCookieList);
+
+ /**
+ * This method is a helper that allows calling nsICookieManager::Remove()
+ * with OriginAttributes parameter.
+ */
+ nsresult Remove(const nsACString& aHost, const OriginAttributes& aAttrs,
+ const nsACString& aName, const nsACString& aPath);
+
+ bool SetCookiesFromIPC(const nsACString& aBaseDomain,
+ const OriginAttributes& aAttrs, nsIURI* aHostURI,
+ bool aFromHttp, const nsTArray<CookieStruct>& aCookies,
+ dom::BrowsingContext* aBrowsingContext);
+
+ protected:
+ virtual ~CookieService();
+
+ bool IsInitialized() const;
+
+ void InitCookieStorages();
+ void CloseCookieStorages();
+
+ nsresult NormalizeHost(nsCString& aHost);
+ static bool GetTokenValue(nsACString::const_char_iterator& aIter,
+ nsACString::const_char_iterator& aEndIter,
+ nsDependentCSubstring& aTokenString,
+ nsDependentCSubstring& aTokenValue,
+ bool& aEqualsFound);
+ static bool ParseAttributes(nsIConsoleReportCollector* aCRC, nsIURI* aHostURI,
+ nsCString& aCookieHeader,
+ CookieStruct& aCookieData, nsACString& aExpires,
+ nsACString& aMaxage, bool& aAcceptedByParser);
+ static bool CheckDomain(CookieStruct& aCookieData, nsIURI* aHostURI,
+ const nsACString& aBaseDomain,
+ bool aRequireHostMatch);
+ static bool CheckHiddenPrefix(CookieStruct& aCookieData);
+ static bool CheckPath(CookieStruct& aCookieData,
+ nsIConsoleReportCollector* aCRC, nsIURI* aHostURI);
+ static bool CheckPrefixes(CookieStruct& aCookieData, bool aSecureRequest);
+ static bool GetExpiry(CookieStruct& aCookieData, const nsACString& aExpires,
+ const nsACString& aMaxage, int64_t aCurrentTime,
+ bool aFromHttp);
+ void NotifyAccepted(nsIChannel* aChannel);
+
+ nsresult GetCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain,
+ nsTArray<RefPtr<nsICookie>>& aResult);
+ nsresult RemoveCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain);
+
+ protected:
+ CookieStorage* PickStorage(const OriginAttributes& aAttrs);
+ CookieStorage* PickStorage(const OriginAttributesPattern& aAttrs);
+
+ nsresult RemoveCookiesFromExactHost(const nsACString& aHost,
+ const OriginAttributesPattern& aPattern);
+
+ // cached members.
+ nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
+ nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+ nsCOMPtr<nsIIDNService> mIDNService;
+
+ // we have two separate Cookie Storages: one for normal browsing and one for
+ // private browsing.
+ RefPtr<CookieStorage> mPersistentStorage;
+ RefPtr<CookieStorage> mPrivateStorage;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieService_h
diff --git a/netwerk/cookie/CookieServiceChild.cpp b/netwerk/cookie/CookieServiceChild.cpp
new file mode 100644
index 0000000000..a005b5dbe7
--- /dev/null
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -0,0 +1,653 @@
+/* -*- 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 "Cookie.h"
+#include "CookieCommons.h"
+#include "CookieLogging.h"
+#include "CookieService.h"
+#include "mozilla/net/CookieServiceChild.h"
+#include "ErrorList.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsICookieJarSettings.h"
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIURI.h"
+#include "nsIPrefBranch.h"
+#include "nsIWebProgressListener.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "ThirdPartyUtil.h"
+#include "nsIConsoleReportCollector.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<CookieServiceChild> gCookieChildService;
+
+already_AddRefed<CookieServiceChild> CookieServiceChild::GetSingleton() {
+ if (!gCookieChildService) {
+ gCookieChildService = new CookieServiceChild();
+ gCookieChildService->Init();
+ ClearOnShutdown(&gCookieChildService);
+ }
+
+ return do_AddRef(gCookieChildService);
+}
+
+NS_IMPL_ISUPPORTS(CookieServiceChild, nsICookieService,
+ nsISupportsWeakReference)
+
+CookieServiceChild::CookieServiceChild() { NeckoChild::InitNeckoChild(); }
+
+CookieServiceChild::~CookieServiceChild() { gCookieChildService = nullptr; }
+
+void CookieServiceChild::Init() {
+ auto* cc = static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return;
+ }
+
+ // This corresponds to Release() in DeallocPCookieService.
+ NS_ADDREF_THIS();
+
+ // Create a child PCookieService actor. Don't do this in the constructor
+ // since it could release 'this' on failure
+ gNeckoChild->SendPCookieServiceConstructor(this);
+
+ mThirdPartyUtil = ThirdPartyUtil::GetInstance();
+ NS_ASSERTION(mThirdPartyUtil, "couldn't get ThirdPartyUtil service");
+
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ NS_ASSERTION(mTLDService, "couldn't get TLDService");
+}
+
+RefPtr<GenericPromise> CookieServiceChild::TrackCookieLoad(
+ nsIChannel* aChannel) {
+ if (!CanSend()) {
+ return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
+ aChannel, true, nullptr, RequireThirdPartyCheck, &rejectedReason);
+
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
+ aChannel, attrs);
+
+ bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
+ bool hadCrossSiteRedirects = false;
+ bool isSameSiteForeign =
+ CookieCommons::IsSameSiteForeign(aChannel, uri, &hadCrossSiteRedirects);
+
+ RefPtr<CookieServiceChild> self(this);
+
+ return SendGetCookieList(
+ uri, result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(
+ ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(
+ ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ rejectedReason, isSafeTopLevelNav, isSameSiteForeign,
+ hadCrossSiteRedirects, attrs)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, uri, attrs](const nsTArray<CookieStruct>& aCookiesList) {
+ for (uint32_t i = 0; i < aCookiesList.Length(); i++) {
+ RefPtr<Cookie> cookie = Cookie::Create(aCookiesList[i], attrs);
+ cookie->SetIsHttpOnly(false);
+ self->RecordDocumentCookie(cookie, attrs);
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ },
+ [](const mozilla::ipc::ResponseRejectReason) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ });
+}
+
+IPCResult CookieServiceChild::RecvRemoveAll() {
+ mCookiesMap.Clear();
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "content-removed-all-cookies",
+ nullptr);
+ }
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvRemoveCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs) {
+ RemoveSingleCookie(aCookie, aAttrs);
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "content-removed-cookie", nullptr);
+ }
+ return IPC_OK();
+}
+
+void CookieServiceChild::RemoveSingleCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs) {
+ nsCString baseDomain;
+ CookieCommons::GetBaseDomainFromHost(mTLDService, aCookie.host(), baseDomain);
+ CookieKey key(baseDomain, aAttrs);
+ CookiesList* cookiesList = nullptr;
+ mCookiesMap.Get(key, &cookiesList);
+
+ if (!cookiesList) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < cookiesList->Length(); i++) {
+ Cookie* cookie = cookiesList->ElementAt(i);
+ // bug 1858366: In the case that we are updating a stale cookie
+ // from the content process: the parent process will signal
+ // a batch deletion for the old cookie.
+ // When received by the content process we should not remove
+ // the new cookie since we have already updated the content
+ // process cookies. So we also check the expiry here.
+ if (cookie->Name().Equals(aCookie.name()) &&
+ cookie->Host().Equals(aCookie.host()) &&
+ cookie->Path().Equals(aCookie.path()) &&
+ cookie->Expiry() <= aCookie.expiry()) {
+ cookiesList->RemoveElementAt(i);
+ break;
+ }
+ }
+}
+
+IPCResult CookieServiceChild::RecvAddCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs) {
+ RefPtr<Cookie> cookie = Cookie::Create(aCookie, aAttrs);
+ RecordDocumentCookie(cookie, aAttrs);
+
+ // signal test code to check their cookie list
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "content-added-cookie", nullptr);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvRemoveBatchDeletedCookies(
+ nsTArray<CookieStruct>&& aCookiesList,
+ nsTArray<OriginAttributes>&& aAttrsList) {
+ MOZ_ASSERT(aCookiesList.Length() == aAttrsList.Length());
+ for (uint32_t i = 0; i < aCookiesList.Length(); i++) {
+ CookieStruct cookieStruct = aCookiesList.ElementAt(i);
+ RemoveSingleCookie(cookieStruct, aAttrsList.ElementAt(i));
+ }
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "content-batch-deleted-cookies",
+ nullptr);
+ }
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvTrackCookiesLoad(
+ nsTArray<CookieStruct>&& aCookiesList, const OriginAttributes& aAttrs) {
+ for (uint32_t i = 0; i < aCookiesList.Length(); i++) {
+ RefPtr<Cookie> cookie = Cookie::Create(aCookiesList[i], aAttrs);
+ cookie->SetIsHttpOnly(false);
+ RecordDocumentCookie(cookie, aAttrs);
+ }
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "content-track-cookies-loaded",
+ nullptr);
+ }
+
+ return IPC_OK();
+}
+
+uint32_t CookieServiceChild::CountCookiesFromHashTable(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttrs) {
+ CookiesList* cookiesList = nullptr;
+
+ nsCString baseDomain;
+ CookieKey key(aBaseDomain, aOriginAttrs);
+ mCookiesMap.Get(key, &cookiesList);
+
+ return cookiesList ? cookiesList->Length() : 0;
+}
+
+/* static */ bool CookieServiceChild::RequireThirdPartyCheck(
+ nsILoadInfo* aLoadInfo) {
+ if (!aLoadInfo) {
+ return false;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ uint32_t cookieBehavior = cookieJarSettings->GetCookieBehavior();
+ return cookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
+ cookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN ||
+ cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ cookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN ||
+ StaticPrefs::network_cookie_thirdparty_sessionOnly() ||
+ StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly();
+}
+
+void CookieServiceChild::RecordDocumentCookie(Cookie* aCookie,
+ const OriginAttributes& aAttrs) {
+ nsAutoCString baseDomain;
+ CookieCommons::GetBaseDomainFromHost(mTLDService, aCookie->Host(),
+ baseDomain);
+
+ CookieKey key(baseDomain, aAttrs);
+ CookiesList* cookiesList = nullptr;
+ mCookiesMap.Get(key, &cookiesList);
+
+ if (!cookiesList) {
+ cookiesList = mCookiesMap.GetOrInsertNew(key);
+ }
+ for (uint32_t i = 0; i < cookiesList->Length(); i++) {
+ Cookie* cookie = cookiesList->ElementAt(i);
+ if (cookie->Name().Equals(aCookie->Name()) &&
+ cookie->Host().Equals(aCookie->Host()) &&
+ cookie->Path().Equals(aCookie->Path())) {
+ if (cookie->Value().Equals(aCookie->Value()) &&
+ cookie->Expiry() == aCookie->Expiry() &&
+ cookie->IsSecure() == aCookie->IsSecure() &&
+ cookie->SameSite() == aCookie->SameSite() &&
+ cookie->RawSameSite() == aCookie->RawSameSite() &&
+ cookie->IsSession() == aCookie->IsSession() &&
+ cookie->IsHttpOnly() == aCookie->IsHttpOnly()) {
+ cookie->SetLastAccessed(aCookie->LastAccessed());
+ return;
+ }
+ cookiesList->RemoveElementAt(i);
+ break;
+ }
+ }
+
+ int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
+ if (aCookie->Expiry() <= currentTime) {
+ return;
+ }
+
+ cookiesList->AppendElement(aCookie);
+}
+
+NS_IMETHODIMP
+CookieServiceChild::GetCookieStringFromDocument(dom::Document* aDocument,
+ nsACString& aCookieString) {
+ NS_ENSURE_ARG(aDocument);
+
+ aCookieString.Truncate();
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
+
+ if (!CookieCommons::IsSchemeSupported(principal)) {
+ return NS_OK;
+ }
+
+ nsAutoCString baseDomain;
+ nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ CookieKey key(baseDomain, principal->OriginAttributesRef());
+ CookiesList* cookiesList = nullptr;
+ mCookiesMap.Get(key, &cookiesList);
+
+ if (!cookiesList) {
+ return NS_OK;
+ }
+
+ nsAutoCString hostFromURI;
+ rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ nsAutoCString pathFromURI;
+ principal->GetFilePath(pathFromURI);
+
+ bool thirdParty = true;
+ nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
+ // in gtests we don't have a window, let's consider those requests as 3rd
+ // party.
+ if (innerWindow) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+
+ if (thirdPartyUtil) {
+ Unused << thirdPartyUtil->IsThirdPartyWindow(
+ innerWindow->GetOuterWindow(), nullptr, &thirdParty);
+ }
+ }
+
+ bool isPotentiallyTrustworthy =
+ principal->GetIsOriginPotentiallyTrustworthy();
+ int64_t currentTimeInUsec = PR_Now();
+ int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
+
+ cookiesList->Sort(CompareCookiesForSending());
+ for (uint32_t i = 0; i < cookiesList->Length(); i++) {
+ Cookie* cookie = cookiesList->ElementAt(i);
+ // check the host, since the base domain lookup is conservative.
+ if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
+ continue;
+ }
+
+ // We don't show HttpOnly cookies in content processes.
+ if (cookie->IsHttpOnly()) {
+ continue;
+ }
+
+ if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
+ cookie, aDocument)) {
+ continue;
+ }
+
+ // do not display the cookie if it is secure and the host scheme isn't
+ if (cookie->IsSecure() && !isPotentiallyTrustworthy) {
+ continue;
+ }
+
+ // if the nsIURI path doesn't match the cookie path, don't send it back
+ if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
+ continue;
+ }
+
+ // check if the cookie has expired
+ if (cookie->Expiry() <= currentTime) {
+ continue;
+ }
+
+ if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
+ if (!aCookieString.IsEmpty()) {
+ aCookieString.AppendLiteral("; ");
+ }
+ if (!cookie->Name().IsEmpty()) {
+ aCookieString.Append(cookie->Name().get());
+ aCookieString.AppendLiteral("=");
+ aCookieString.Append(cookie->Value().get());
+ } else {
+ aCookieString.Append(cookie->Value().get());
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::GetCookieStringFromHttp(nsIURI* /*aHostURI*/,
+ nsIChannel* /*aChannel*/,
+ nsACString& /*aCookieString*/) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::SetCookieStringFromDocument(
+ dom::Document* aDocument, const nsACString& aCookieString) {
+ NS_ENSURE_ARG(aDocument);
+
+ nsCOMPtr<nsIURI> documentURI;
+ nsAutoCString baseDomain;
+ OriginAttributes attrs;
+
+ // This function is executed in this context, I don't need to keep objects
+ // alive.
+ auto hasExistingCookiesLambda = [&](const nsACString& aBaseDomain,
+ const OriginAttributes& aAttrs) {
+ return !!CountCookiesFromHashTable(aBaseDomain, aAttrs);
+ };
+
+ RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
+ aDocument, aCookieString, PR_Now(), mTLDService, mThirdPartyUtil,
+ hasExistingCookiesLambda, getter_AddRefs(documentURI), baseDomain, attrs);
+ if (!cookie) {
+ return NS_OK;
+ }
+
+ bool thirdParty = true;
+ nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
+ // in gtests we don't have a window, let's consider those requests as 3rd
+ // party.
+ if (innerWindow) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+
+ if (thirdPartyUtil) {
+ Unused << thirdPartyUtil->IsThirdPartyWindow(
+ innerWindow->GetOuterWindow(), nullptr, &thirdParty);
+ }
+ }
+
+ if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
+ cookie, aDocument)) {
+ return NS_OK;
+ }
+
+ CookieKey key(baseDomain, attrs);
+ CookiesList* cookies = mCookiesMap.Get(key);
+
+ if (cookies) {
+ // We need to see if the cookie we're setting would overwrite an httponly
+ // or a secure one. This would not affect anything we send over the net
+ // (those come from the parent, which already checks this),
+ // but script could see an inconsistent view of things.
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
+ bool isPotentiallyTrustworthy =
+ principal->GetIsOriginPotentiallyTrustworthy();
+
+ for (uint32_t i = 0; i < cookies->Length(); ++i) {
+ RefPtr<Cookie> existingCookie = cookies->ElementAt(i);
+ if (existingCookie->Name().Equals(cookie->Name()) &&
+ existingCookie->Host().Equals(cookie->Host()) &&
+ existingCookie->Path().Equals(cookie->Path())) {
+ // Can't overwrite an httponly cookie from a script context.
+ if (existingCookie->IsHttpOnly()) {
+ return NS_OK;
+ }
+
+ // prevent insecure cookie from overwriting a secure one in insecure
+ // context.
+ if (existingCookie->IsSecure() && !isPotentiallyTrustworthy) {
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ RecordDocumentCookie(cookie, attrs);
+
+ if (CanSend()) {
+ nsTArray<CookieStruct> cookiesToSend;
+ cookiesToSend.AppendElement(cookie->ToIPC());
+
+ // Asynchronously call the parent.
+ dom::WindowGlobalChild* windowGlobalChild =
+ aDocument->GetWindowGlobalChild();
+
+ // If there is no WindowGlobalChild fall back to PCookieService SetCookies.
+ if (NS_WARN_IF(!windowGlobalChild)) {
+ SendSetCookies(baseDomain, attrs, documentURI, false, cookiesToSend);
+ return NS_OK;
+ }
+ windowGlobalChild->SendSetCookies(baseDomain, attrs, documentURI, false,
+ cookiesToSend);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::SetCookieStringFromHttp(nsIURI* aHostURI,
+ const nsACString& aCookieString,
+ nsIChannel* aChannel) {
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG(aChannel);
+
+ if (!CookieCommons::IsSchemeSupported(aHostURI)) {
+ return NS_OK;
+ }
+
+ // Fast past: don't bother sending IPC messages about nullprincipal'd
+ // documents.
+ nsAutoCString scheme;
+ aHostURI->GetScheme(scheme);
+ if (scheme.EqualsLiteral("moz-nullprincipal")) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
+ aChannel, false, aHostURI, RequireThirdPartyCheck, &rejectedReason);
+
+ nsCString cookieString(aCookieString);
+
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
+ aChannel, attrs);
+
+ bool requireHostMatch;
+ nsCString baseDomain;
+ CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
+ requireHostMatch);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ CookieCommons::GetCookieJarSettings(aChannel);
+
+ nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
+
+ CookieStatus cookieStatus = CookieService::CheckPrefs(
+ crc, cookieJarSettings, aHostURI,
+ result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ aCookieString, CountCookiesFromHashTable(baseDomain, attrs), attrs,
+ &rejectedReason);
+
+ if (cookieStatus != STATUS_ACCEPTED &&
+ cookieStatus != STATUS_ACCEPT_SESSION) {
+ return NS_OK;
+ }
+
+ CookieKey key(baseDomain, attrs);
+
+ nsTArray<CookieStruct> cookiesToSend;
+
+ int64_t currentTimeInUsec = PR_Now();
+
+ bool addonAllowsLoad = false;
+ nsCOMPtr<nsIURI> finalChannelURI;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalChannelURI));
+ addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
+ ->AddonAllowsLoad(finalChannelURI);
+
+ bool isForeignAndNotAddon = false;
+ if (!addonAllowsLoad) {
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
+ &isForeignAndNotAddon);
+ }
+
+ bool mustBePartitioned =
+ isForeignAndNotAddon &&
+ cookieJarSettings->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
+ !result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted);
+
+ bool moreCookies;
+ do {
+ CookieStruct cookieData;
+ bool canSetCookie = false;
+ moreCookies = CookieService::CanSetCookie(
+ aHostURI, baseDomain, cookieData, requireHostMatch, cookieStatus,
+ cookieString, true, isForeignAndNotAddon, mustBePartitioned, crc,
+ canSetCookie);
+ if (!canSetCookie) {
+ continue;
+ }
+
+ // check permissions from site permission list.
+ if (!CookieCommons::CheckCookiePermission(aChannel, cookieData)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieString,
+ "cookie rejected by permission manager");
+ constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
+ CookieLogging::LogMessageToConsole(
+ crc, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(cookieData.name()),
+ });
+ CookieCommons::NotifyRejected(
+ aHostURI, aChannel,
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
+ OPERATION_WRITE);
+ continue;
+ }
+
+ RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
+ MOZ_ASSERT(cookie);
+
+ cookie->SetLastAccessed(currentTimeInUsec);
+ cookie->SetCreationTime(
+ Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
+
+ RecordDocumentCookie(cookie, attrs);
+ cookiesToSend.AppendElement(cookieData);
+ } while (moreCookies);
+
+ // Asynchronously call the parent.
+ if (CanSend() && !cookiesToSend.IsEmpty()) {
+ RefPtr<HttpChannelChild> httpChannelChild = do_QueryObject(aChannel);
+ MOZ_ASSERT(httpChannelChild);
+ httpChannelChild->SendSetCookies(baseDomain, attrs, aHostURI, true,
+ cookiesToSend);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::RunInTransaction(
+ nsICookieTransactionCallback* /*aCallback*/) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieServiceChild.h b/netwerk/cookie/CookieServiceChild.h
new file mode 100644
index 0000000000..b9caa2aaa7
--- /dev/null
+++ b/netwerk/cookie/CookieServiceChild.h
@@ -0,0 +1,82 @@
+/* -*- 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_CookieServiceChild_h__
+#define mozilla_net_CookieServiceChild_h__
+
+#include "CookieKey.h"
+#include "mozilla/net/PCookieServiceChild.h"
+#include "nsClassHashtable.h"
+#include "nsICookieService.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsWeakReference.h"
+#include "nsThreadUtils.h"
+
+class nsIEffectiveTLDService;
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+class Cookie;
+class CookieStruct;
+
+class CookieServiceChild final : public PCookieServiceChild,
+ public nsICookieService,
+ public nsSupportsWeakReference {
+ friend class PCookieServiceChild;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIESERVICE
+
+ typedef nsTArray<RefPtr<Cookie>> CookiesList;
+ typedef nsClassHashtable<CookieKey, CookiesList> CookiesMap;
+
+ CookieServiceChild();
+
+ void Init();
+
+ static already_AddRefed<CookieServiceChild> GetSingleton();
+
+ RefPtr<GenericPromise> TrackCookieLoad(nsIChannel* aChannel);
+
+ private:
+ ~CookieServiceChild();
+
+ void RecordDocumentCookie(Cookie* aCookie, const OriginAttributes& aAttrs);
+
+ uint32_t CountCookiesFromHashTable(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttrs);
+
+ static bool RequireThirdPartyCheck(nsILoadInfo* aLoadInfo);
+
+ mozilla::ipc::IPCResult RecvTrackCookiesLoad(
+ nsTArray<CookieStruct>&& aCookiesList, const OriginAttributes& aAttrs);
+
+ mozilla::ipc::IPCResult RecvRemoveAll();
+
+ mozilla::ipc::IPCResult RecvRemoveBatchDeletedCookies(
+ nsTArray<CookieStruct>&& aCookiesList,
+ nsTArray<OriginAttributes>&& aAttrsList);
+
+ mozilla::ipc::IPCResult RecvRemoveCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs);
+
+ mozilla::ipc::IPCResult RecvAddCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs);
+
+ void RemoveSingleCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs);
+
+ CookiesMap mCookiesMap;
+ nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
+ nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieServiceChild_h__
diff --git a/netwerk/cookie/CookieServiceParent.cpp b/netwerk/cookie/CookieServiceParent.cpp
new file mode 100644
index 0000000000..c78a67513f
--- /dev/null
+++ b/netwerk/cookie/CookieServiceParent.cpp
@@ -0,0 +1,268 @@
+/* -*- 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 "CookieCommons.h"
+#include "CookieLogging.h"
+#include "mozilla/net/CookieService.h"
+#include "mozilla/net/CookieServiceParent.h"
+#include "mozilla/net/NeckoParent.h"
+
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsArrayUtils.h"
+#include "nsIChannel.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsNetCID.h"
+#include "nsMixedContentBlocker.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+CookieServiceParent::CookieServiceParent() {
+ // Instantiate the cookieservice via the service manager, so it sticks around
+ // until shutdown.
+ nsCOMPtr<nsICookieService> cs = do_GetService(NS_COOKIESERVICE_CONTRACTID);
+
+ // Get the CookieService instance directly, so we can call internal methods.
+ mCookieService = CookieService::GetSingleton();
+ NS_ASSERTION(mCookieService, "couldn't get nsICookieService");
+
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ALWAYS_TRUE(mTLDService);
+
+ mProcessingCookie = false;
+}
+
+void CookieServiceParent::RemoveBatchDeletedCookies(nsIArray* aCookieList) {
+ uint32_t len = 0;
+ aCookieList->GetLength(&len);
+ OriginAttributes attrs;
+ CookieStruct cookieStruct;
+ nsTArray<CookieStruct> cookieStructList;
+ nsTArray<OriginAttributes> attrsList;
+ for (uint32_t i = 0; i < len; i++) {
+ nsCOMPtr<nsICookie> xpcCookie = do_QueryElementAt(aCookieList, i);
+ const auto& cookie = xpcCookie->AsCookie();
+ attrs = cookie.OriginAttributesRef();
+ cookieStruct = cookie.ToIPC();
+
+ // Child only needs to know HttpOnly cookies exists, not its value
+ // Same for Secure cookies going to a process for an insecure site.
+ if (cookie.IsHttpOnly() || !InsecureCookieOrSecureOrigin(cookie)) {
+ cookieStruct.value() = "";
+ }
+ cookieStructList.AppendElement(cookieStruct);
+ attrsList.AppendElement(attrs);
+ }
+ Unused << SendRemoveBatchDeletedCookies(cookieStructList, attrsList);
+}
+
+void CookieServiceParent::RemoveAll() { Unused << SendRemoveAll(); }
+
+void CookieServiceParent::RemoveCookie(const Cookie& cookie) {
+ const OriginAttributes& attrs = cookie.OriginAttributesRef();
+ CookieStruct cookieStruct = cookie.ToIPC();
+
+ // Child only needs to know HttpOnly cookies exists, not its value
+ // Same for Secure cookies going to a process for an insecure site.
+ if (cookie.IsHttpOnly() || !InsecureCookieOrSecureOrigin(cookie)) {
+ cookieStruct.value() = "";
+ }
+ Unused << SendRemoveCookie(cookieStruct, attrs);
+}
+
+void CookieServiceParent::AddCookie(const Cookie& cookie) {
+ const OriginAttributes& attrs = cookie.OriginAttributesRef();
+ CookieStruct cookieStruct = cookie.ToIPC();
+
+ // Child only needs to know HttpOnly cookies exists, not its value
+ // Same for Secure cookies going to a process for an insecure site.
+ if (cookie.IsHttpOnly() || !InsecureCookieOrSecureOrigin(cookie)) {
+ cookieStruct.value() = "";
+ }
+ Unused << SendAddCookie(cookieStruct, attrs);
+}
+
+bool CookieServiceParent::ContentProcessHasCookie(const Cookie& cookie) {
+ nsCString baseDomain;
+ if (NS_WARN_IF(NS_FAILED(CookieCommons::GetBaseDomainFromHost(
+ mTLDService, cookie.Host(), baseDomain)))) {
+ return false;
+ }
+
+ CookieKey cookieKey(baseDomain, cookie.OriginAttributesRef());
+ return mCookieKeysInContent.MaybeGet(cookieKey).isSome();
+}
+
+bool CookieServiceParent::InsecureCookieOrSecureOrigin(const Cookie& cookie) {
+ nsCString baseDomain;
+ // CookieStorage notifications triggering this won't fail to get base domain
+ MOZ_ALWAYS_SUCCEEDS(CookieCommons::GetBaseDomainFromHost(
+ mTLDService, cookie.Host(), baseDomain));
+
+ // cookie is insecure or cookie is associated with a secure-origin process
+ CookieKey cookieKey(baseDomain, cookie.OriginAttributesRef());
+ if (Maybe<bool> allowSecure = mCookieKeysInContent.MaybeGet(cookieKey)) {
+ return (!cookie.IsSecure() || *allowSecure);
+ }
+ return false;
+}
+
+void CookieServiceParent::TrackCookieLoad(nsIChannel* aChannel) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
+ bool hadCrossSiteRedirects = false;
+ bool isSameSiteForeign =
+ CookieCommons::IsSameSiteForeign(aChannel, uri, &hadCrossSiteRedirects);
+
+ StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
+ aChannel, attrs);
+
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil;
+ thirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = thirdPartyUtil->AnalyzeChannel(
+ aChannel, false, nullptr, nullptr, &rejectedReason);
+
+ UpdateCookieInContentList(uri, attrs);
+
+ // Send matching cookies to Child.
+ nsTArray<Cookie*> foundCookieList;
+ mCookieService->GetCookiesForURI(
+ uri, aChannel, result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ rejectedReason, isSafeTopLevelNav, isSameSiteForeign,
+ hadCrossSiteRedirects, false, true, attrs, foundCookieList);
+ nsTArray<CookieStruct> matchingCookiesList;
+ SerializeCookieList(foundCookieList, matchingCookiesList, uri);
+ Unused << SendTrackCookiesLoad(matchingCookiesList, attrs);
+}
+
+// we append outgoing cookie info into a list here so the ContentParent can
+// filter cookies passing to unnecessary ContentProcesses
+void CookieServiceParent::UpdateCookieInContentList(
+ nsIURI* uri, const OriginAttributes& originAttrs) {
+ nsCString baseDomain;
+ bool requireAHostMatch = false;
+
+ // prevent malformed urls from being added to the cookie list
+ if (NS_WARN_IF(NS_FAILED(CookieCommons::GetBaseDomain(
+ mTLDService, uri, baseDomain, requireAHostMatch)))) {
+ return;
+ }
+
+ CookieKey cookieKey(baseDomain, originAttrs);
+ bool& allowSecure = mCookieKeysInContent.LookupOrInsert(cookieKey, false);
+ allowSecure =
+ allowSecure || nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri);
+}
+
+// static
+void CookieServiceParent::SerializeCookieList(
+ const nsTArray<Cookie*>& aFoundCookieList,
+ nsTArray<CookieStruct>& aCookiesList, nsIURI* aHostURI) {
+ for (uint32_t i = 0; i < aFoundCookieList.Length(); i++) {
+ Cookie* cookie = aFoundCookieList.ElementAt(i);
+ CookieStruct* cookieStruct = aCookiesList.AppendElement();
+ *cookieStruct = cookie->ToIPC();
+
+ // clear http-only cookie values
+ if (cookie->IsHttpOnly()) {
+ // Value only needs to exist if an HttpOnly cookie exists.
+ cookieStruct->value() = "";
+ }
+
+ // clear secure cookie values in insecure context
+ bool potentiallyTurstworthy =
+ nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
+ if (cookie->IsSecure() && !potentiallyTurstworthy) {
+ cookieStruct->value() = "";
+ }
+ }
+}
+
+IPCResult CookieServiceParent::RecvGetCookieList(
+ nsIURI* aHost, const bool& aIsForeign,
+ const bool& aIsThirdPartyTrackingResource,
+ const bool& aIsThirdPartySocialTrackingResource,
+ const bool& aStorageAccessPermissionGranted,
+ const uint32_t& aRejectedReason, const bool& aIsSafeTopLevelNav,
+ const bool& aIsSameSiteForeign, const bool& aHadCrossSiteRedirects,
+ const OriginAttributes& aAttrs, GetCookieListResolver&& aResolve) {
+ // Send matching cookies to Child.
+ if (!aHost) {
+ return IPC_FAIL(this, "aHost must not be null");
+ }
+
+ // we append outgoing cookie info into a list here so the ContentParent can
+ // filter cookies that do not need to go to certain ContentProcesses
+ UpdateCookieInContentList(aHost, aAttrs);
+
+ nsTArray<Cookie*> foundCookieList;
+ // Note: passing nullptr as aChannel to GetCookiesForURI() here is fine since
+ // this argument is only used for proper reporting of cookie loads, but the
+ // child process already does the necessary reporting in this case for us.
+ mCookieService->GetCookiesForURI(
+ aHost, nullptr, aIsForeign, aIsThirdPartyTrackingResource,
+ aIsThirdPartySocialTrackingResource, aStorageAccessPermissionGranted,
+ aRejectedReason, aIsSafeTopLevelNav, aIsSameSiteForeign,
+ aHadCrossSiteRedirects, false, true, aAttrs, foundCookieList);
+
+ nsTArray<CookieStruct> matchingCookiesList;
+ SerializeCookieList(foundCookieList, matchingCookiesList, aHost);
+
+ aResolve(matchingCookiesList);
+
+ return IPC_OK();
+}
+
+void CookieServiceParent::ActorDestroy(ActorDestroyReason aWhy) {
+ // Nothing needed here. Called right before destructor since this is a
+ // non-refcounted class.
+}
+
+IPCResult CookieServiceParent::RecvSetCookies(
+ const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies) {
+ return SetCookies(aBaseDomain, aOriginAttributes, aHost, aFromHttp, aCookies);
+}
+
+IPCResult CookieServiceParent::SetCookies(
+ const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies,
+ dom::BrowsingContext* aBrowsingContext) {
+ if (!mCookieService) {
+ return IPC_OK();
+ }
+
+ // Deserialize URI. Having a host URI is mandatory and should always be
+ // provided by the child; thus we consider failure fatal.
+ if (!aHost) {
+ return IPC_FAIL(this, "aHost must not be null");
+ }
+
+ // We set this to true while processing this cookie update, to make sure
+ // we don't send it back to the same content process.
+ mProcessingCookie = true;
+
+ bool ok =
+ mCookieService->SetCookiesFromIPC(aBaseDomain, aOriginAttributes, aHost,
+ aFromHttp, aCookies, aBrowsingContext);
+ mProcessingCookie = false;
+ return ok ? IPC_OK() : IPC_FAIL(this, "Invalid cookie received.");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieServiceParent.h b/netwerk/cookie/CookieServiceParent.h
new file mode 100644
index 0000000000..45b46883fb
--- /dev/null
+++ b/netwerk/cookie/CookieServiceParent.h
@@ -0,0 +1,88 @@
+/* -*- 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_CookieServiceParent_h
+#define mozilla_net_CookieServiceParent_h
+
+#include "mozilla/net/PCookieServiceParent.h"
+#include "mozilla/net/CookieKey.h"
+
+class nsIArray;
+class nsICookie;
+namespace mozilla {
+class OriginAttributes;
+}
+
+class nsIEffectiveTLDService;
+
+namespace mozilla {
+namespace net {
+
+class Cookie;
+class CookieService;
+
+class CookieServiceParent : public PCookieServiceParent {
+ friend class PCookieServiceParent;
+
+ public:
+ CookieServiceParent();
+ virtual ~CookieServiceParent() = default;
+
+ void TrackCookieLoad(nsIChannel* aChannel);
+
+ void RemoveBatchDeletedCookies(nsIArray* aCookieList);
+
+ void RemoveAll();
+
+ void RemoveCookie(const Cookie& aCookie);
+
+ void AddCookie(const Cookie& aCookie);
+
+ // This will return true if the CookieServiceParent is currently processing
+ // an update from the content process. This is used in ContentParent to make
+ // sure that we are only forwarding those cookie updates to other content
+ // processes, not the one they originated from.
+ bool ProcessingCookie() { return mProcessingCookie; }
+
+ bool ContentProcessHasCookie(const Cookie& cookie);
+ bool InsecureCookieOrSecureOrigin(const Cookie& cookie);
+ void UpdateCookieInContentList(nsIURI* aHostURI,
+ const OriginAttributes& aOriginAttrs);
+
+ mozilla::ipc::IPCResult SetCookies(
+ const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies,
+ dom::BrowsingContext* aBrowsingContext = nullptr);
+
+ protected:
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvSetCookies(
+ const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies);
+
+ mozilla::ipc::IPCResult RecvGetCookieList(
+ nsIURI* aHost, const bool& aIsForeign,
+ const bool& aIsThirdPartyTrackingResource,
+ const bool& aIsThirdPartySocialTrackingResource,
+ const bool& aStorageAccessPermissionGranted,
+ const uint32_t& aRejectedReason, const bool& aIsSafeTopLevelNav,
+ const bool& aIsSameSiteForeign, const bool& aHadCrossSiteRedirects,
+ const OriginAttributes& aAttrs, GetCookieListResolver&& aResolve);
+
+ static void SerializeCookieList(const nsTArray<Cookie*>& aFoundCookieList,
+ nsTArray<CookieStruct>& aCookiesList,
+ nsIURI* aHostURI);
+
+ nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+ RefPtr<CookieService> mCookieService;
+ bool mProcessingCookie;
+ nsTHashMap<CookieKey, bool> mCookieKeysInContent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieServiceParent_h
diff --git a/netwerk/cookie/CookieStorage.cpp b/netwerk/cookie/CookieStorage.cpp
new file mode 100644
index 0000000000..cc827a2372
--- /dev/null
+++ b/netwerk/cookie/CookieStorage.cpp
@@ -0,0 +1,910 @@
+/* -*- 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 "Cookie.h"
+#include "CookieCommons.h"
+#include "CookieLogging.h"
+#include "CookieNotification.h"
+#include "nsCOMPtr.h"
+#include "nsICookieNotification.h"
+#include "CookieStorage.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "nsIMutableArray.h"
+#include "nsTPriorityQueue.h"
+#include "nsIScriptError.h"
+#include "nsIUserIdleService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "prprf.h"
+#include "nsIPrefService.h"
+
+#undef ADD_TEN_PERCENT
+#define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i) / 10)
+
+#undef LIMIT
+#define LIMIT(x, low, high, default) \
+ ((x) >= (low) && (x) <= (high) ? (x) : (default))
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+// comparator class for lastaccessed times of cookies.
+class CompareCookiesByAge {
+ public:
+ static bool Equals(const CookieListIter& a, const CookieListIter& b) {
+ return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
+ a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
+ }
+
+ static bool LessThan(const CookieListIter& a, const CookieListIter& b) {
+ // compare by lastAccessed time, and tiebreak by creationTime.
+ int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
+ if (result != 0) {
+ return result < 0;
+ }
+
+ return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
+ }
+};
+
+// Cookie comparator for the priority queue used in FindStaleCookies.
+// Note that the expired cookie has the highest priority.
+// Other non-expired cookies are sorted by their age.
+class CookieIterComparator {
+ private:
+ int64_t mCurrentTime;
+
+ public:
+ explicit CookieIterComparator(int64_t aTime) : mCurrentTime(aTime) {}
+
+ bool LessThan(const CookieListIter& lhs, const CookieListIter& rhs) {
+ bool lExpired = lhs.Cookie()->Expiry() <= mCurrentTime;
+ bool rExpired = rhs.Cookie()->Expiry() <= mCurrentTime;
+ if (lExpired && !rExpired) {
+ return true;
+ }
+
+ if (!lExpired && rExpired) {
+ return false;
+ }
+
+ return mozilla::net::CompareCookiesByAge::LessThan(lhs, rhs);
+ }
+};
+
+// comparator class for sorting cookies by entry and index.
+class CompareCookiesByIndex {
+ public:
+ static bool Equals(const CookieListIter& a, const CookieListIter& b) {
+ NS_ASSERTION(a.entry != b.entry || a.index != b.index,
+ "cookie indexes should never be equal");
+ return false;
+ }
+
+ static bool LessThan(const CookieListIter& a, const CookieListIter& b) {
+ // compare by entryclass pointer, then by index.
+ if (a.entry != b.entry) {
+ return a.entry < b.entry;
+ }
+
+ return a.index < b.index;
+ }
+};
+
+} // namespace
+
+// ---------------------------------------------------------------------------
+// CookieEntry
+
+size_t CookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = CookieKey::SizeOfExcludingThis(aMallocSizeOf);
+
+ amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mCookies.Length(); ++i) {
+ amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+bool CookieEntry::IsPartitioned() const {
+ return !mOriginAttributes.mPartitionKey.IsEmpty();
+}
+
+// ---------------------------------------------------------------------------
+// CookieStorage
+
+NS_IMPL_ISUPPORTS(CookieStorage, nsIObserver, nsISupportsWeakReference)
+
+void CookieStorage::Init() {
+ // init our pref and observer
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
+ prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
+ prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
+ PrefChanged(prefBranch);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ NS_ENSURE_TRUE_VOID(observerService);
+
+ nsresult rv =
+ observerService->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, true);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+size_t CookieStorage::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+
+ amount += aMallocSizeOf(this);
+ amount += mHostTable.SizeOfExcludingThis(aMallocSizeOf);
+
+ return amount;
+}
+
+void CookieStorage::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const {
+ aCookies.SetCapacity(mCookieCount);
+ for (const auto& entry : mHostTable) {
+ const CookieEntry::ArrayType& cookies = entry.GetCookies();
+ for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ aCookies.AppendElement(cookies[i]);
+ }
+ }
+}
+
+void CookieStorage::GetSessionCookies(
+ nsTArray<RefPtr<nsICookie>>& aCookies) const {
+ aCookies.SetCapacity(mCookieCount);
+ for (const auto& entry : mHostTable) {
+ const CookieEntry::ArrayType& cookies = entry.GetCookies();
+ for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ Cookie* cookie = cookies[i];
+ // Filter out non-session cookies.
+ if (cookie->IsSession()) {
+ aCookies.AppendElement(cookie);
+ }
+ }
+ }
+}
+
+// find an exact cookie specified by host, name, and path that hasn't expired.
+bool CookieStorage::FindCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aHost, const nsACString& aName,
+ const nsACString& aPath, CookieListIter& aIter) {
+ CookieEntry* entry =
+ mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
+ if (!entry) {
+ return false;
+ }
+
+ const CookieEntry::ArrayType& cookies = entry->GetCookies();
+ for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ Cookie* cookie = cookies[i];
+
+ if (aHost.Equals(cookie->Host()) && aPath.Equals(cookie->Path()) &&
+ aName.Equals(cookie->Name())) {
+ aIter = CookieListIter(entry, i);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// find an secure cookie specified by host and name
+bool CookieStorage::FindSecureCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie) {
+ CookieEntry* entry =
+ mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
+ if (!entry) {
+ return false;
+ }
+
+ const CookieEntry::ArrayType& cookies = entry->GetCookies();
+ for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ Cookie* cookie = cookies[i];
+ // isn't a match if insecure or a different name
+ if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name())) {
+ continue;
+ }
+
+ // The host must "domain-match" an existing cookie or vice-versa
+ if (CookieCommons::DomainMatches(cookie, aCookie->Host()) ||
+ CookieCommons::DomainMatches(aCookie, cookie->Host())) {
+ // If the path of new cookie and the path of existing cookie
+ // aren't "/", then this situation needs to compare paths to
+ // ensure only that a newly-created non-secure cookie does not
+ // overlay an existing secure cookie.
+ if (CookieCommons::PathMatches(cookie, aCookie->GetFilePath())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+uint32_t CookieStorage::CountCookiesFromHost(const nsACString& aBaseDomain,
+ uint32_t aPrivateBrowsingId) {
+ OriginAttributes attrs;
+ attrs.mPrivateBrowsingId = aPrivateBrowsingId;
+
+ // Return a count of all cookies, including expired.
+ CookieEntry* entry = mHostTable.GetEntry(CookieKey(aBaseDomain, attrs));
+ return entry ? entry->GetCookies().Length() : 0;
+}
+
+void CookieStorage::GetAll(nsTArray<RefPtr<nsICookie>>& aResult) const {
+ aResult.SetCapacity(mCookieCount);
+
+ for (const auto& entry : mHostTable) {
+ const CookieEntry::ArrayType& cookies = entry.GetCookies();
+ for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ aResult.AppendElement(cookies[i]);
+ }
+ }
+}
+
+const nsTArray<RefPtr<Cookie>>* CookieStorage::GetCookiesFromHost(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes) {
+ CookieEntry* entry =
+ mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
+ return entry ? &entry->GetCookies() : nullptr;
+}
+
+void CookieStorage::GetCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain,
+ nsTArray<RefPtr<nsICookie>>& aResult) {
+ for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
+ CookieEntry* entry = iter.Get();
+
+ if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(entry->mOriginAttributes)) {
+ continue;
+ }
+
+ const CookieEntry::ArrayType& entryCookies = entry->GetCookies();
+
+ for (CookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
+ aResult.AppendElement(entryCookies[i]);
+ }
+ }
+}
+
+void CookieStorage::RemoveCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aHost,
+ const nsACString& aName,
+ const nsACString& aPath) {
+ CookieListIter matchIter{};
+ RefPtr<Cookie> cookie;
+ if (FindCookie(aBaseDomain, aOriginAttributes, aHost, aName, aPath,
+ matchIter)) {
+ cookie = matchIter.Cookie();
+ RemoveCookieFromList(matchIter);
+ }
+
+ if (cookie) {
+ // Everything's done. Notify observers.
+ NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED, aBaseDomain);
+ }
+}
+
+void CookieStorage::RemoveCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) {
+ // Iterate the hash table of CookieEntry.
+ for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
+ CookieEntry* entry = iter.Get();
+
+ if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(entry->mOriginAttributes)) {
+ continue;
+ }
+
+ // Pattern matches. Delete all cookies within this CookieEntry.
+ uint32_t cookiesCount = entry->GetCookies().Length();
+
+ for (CookieEntry::IndexType i = 0; i < cookiesCount; ++i) {
+ // Remove the first cookie from the list.
+ CookieListIter iter(entry, 0);
+ RefPtr<Cookie> cookie = iter.Cookie();
+
+ // Remove the cookie.
+ RemoveCookieFromList(iter);
+
+ if (cookie) {
+ NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED,
+ aBaseDomain);
+ }
+ }
+ }
+}
+
+void CookieStorage::RemoveCookiesFromExactHost(
+ const nsACString& aHost, const nsACString& aBaseDomain,
+ const OriginAttributesPattern& aPattern) {
+ // Iterate the hash table of CookieEntry.
+ for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
+ CookieEntry* entry = iter.Get();
+
+ if (!aBaseDomain.Equals(entry->mBaseDomain)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(entry->mOriginAttributes)) {
+ continue;
+ }
+
+ uint32_t cookiesCount = entry->GetCookies().Length();
+ for (CookieEntry::IndexType i = cookiesCount; i != 0; --i) {
+ CookieListIter iter(entry, i - 1);
+ RefPtr<Cookie> cookie = iter.Cookie();
+
+ if (!aHost.Equals(cookie->RawHost())) {
+ continue;
+ }
+
+ // Remove the cookie.
+ RemoveCookieFromList(iter);
+
+ if (cookie) {
+ NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED,
+ aBaseDomain);
+ }
+ }
+ }
+}
+
+void CookieStorage::RemoveAll() {
+ // clearing the hashtable will call each CookieEntry's dtor,
+ // which releases all their respective children.
+ mHostTable.Clear();
+ mCookieCount = 0;
+ mCookieOldestTime = INT64_MAX;
+
+ RemoveAllInternal();
+
+ NotifyChanged(nullptr, nsICookieNotification::ALL_COOKIES_CLEARED, ""_ns);
+}
+
+// notify observers that the cookie list changed.
+void CookieStorage::NotifyChanged(nsISupports* aSubject,
+ nsICookieNotification::Action aAction,
+ const nsACString& aBaseDomain,
+ dom::BrowsingContext* aBrowsingContext,
+ bool aOldCookieIsSession) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ return;
+ }
+
+ nsCOMPtr<nsICookie> cookie;
+ nsCOMPtr<nsIArray> batchDeletedCookies;
+
+ if (aAction == nsICookieNotification::COOKIES_BATCH_DELETED) {
+ batchDeletedCookies = do_QueryInterface(aSubject);
+ } else {
+ cookie = do_QueryInterface(aSubject);
+ }
+
+ uint64_t browsingContextId = 0;
+ if (aBrowsingContext) {
+ browsingContextId = aBrowsingContext->Id();
+ }
+
+ nsCOMPtr<nsICookieNotification> notification = new CookieNotification(
+ aAction, cookie, aBaseDomain, batchDeletedCookies, browsingContextId);
+ // Notify for topic "private-cookie-changed" or "cookie-changed"
+ os->NotifyObservers(notification, NotificationTopic(), u"");
+
+ NotifyChangedInternal(notification, aOldCookieIsSession);
+}
+
+// this is a backend function for adding a cookie to the list, via SetCookie.
+// also used in the cookie manager, for profile migration from IE. it either
+// replaces an existing cookie; or adds the cookie to the hashtable, and
+// deletes a cookie (if maximum number of cookies has been reached). also
+// performs list maintenance by removing expired cookies.
+void CookieStorage::AddCookie(nsIConsoleReportCollector* aCRC,
+ const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie, int64_t aCurrentTimeInUsec,
+ nsIURI* aHostURI, const nsACString& aCookieHeader,
+ bool aFromHttp,
+ dom::BrowsingContext* aBrowsingContext) {
+ int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
+
+ CookieListIter exactIter{};
+ bool foundCookie = false;
+ foundCookie = FindCookie(aBaseDomain, aOriginAttributes, aCookie->Host(),
+ aCookie->Name(), aCookie->Path(), exactIter);
+ bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure();
+ bool potentiallyTrustworthy = true;
+ if (aHostURI) {
+ potentiallyTrustworthy =
+ nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
+ }
+ constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
+ bool oldCookieIsSession = false;
+ // Step1, call FindSecureCookie(). FindSecureCookie() would
+ // find the existing cookie with the security flag and has
+ // the same name, host and path of the new cookie, if there is any.
+ // Step2, Confirm new cookie's security setting. If any targeted
+ // cookie had been found in Step1, then confirm whether the
+ // new cookie could modify it. If the new created cookie’s
+ // "secure-only-flag" is not set, and the "scheme" component
+ // of the "request-uri" does not denote a "secure" protocol,
+ // then ignore the new cookie.
+ // (draft-ietf-httpbis-cookie-alone section 3.2)
+ if (!aCookie->IsSecure() &&
+ (foundSecureExact ||
+ FindSecureCookie(aBaseDomain, aOriginAttributes, aCookie)) &&
+ !potentiallyTrustworthy) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie can't save because older cookie is secure "
+ "cookie but newer cookie is non-secure cookie");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedNonsecureOverSecure"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookie->Name()),
+ });
+ return;
+ }
+
+ RefPtr<Cookie> oldCookie;
+ nsCOMPtr<nsIArray> purgedList;
+ if (foundCookie) {
+ oldCookie = exactIter.Cookie();
+ oldCookieIsSession = oldCookie->IsSession();
+
+ // Check if the old cookie is stale (i.e. has already expired). If so, we
+ // need to be careful about the semantics of removing it and adding the new
+ // cookie: we want the behavior wrt adding the new cookie to be the same as
+ // if it didn't exist, but we still want to fire a removal notification.
+ if (oldCookie->Expiry() <= currentTime) {
+ if (aCookie->Expiry() <= currentTime) {
+ // The new cookie has expired and the old one is stale. Nothing to do.
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie has already expired");
+ return;
+ }
+
+ // Remove the stale cookie. We save notification for later, once all list
+ // modifications are complete.
+ RemoveCookieFromList(exactIter);
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "stale cookie was purged");
+ purgedList = CreatePurgeList(oldCookie);
+
+ // We've done all we need to wrt removing and notifying the stale cookie.
+ // From here on out, we pretend pretend it didn't exist, so that we
+ // preserve expected notification semantics when adding the new cookie.
+ foundCookie = false;
+
+ } else {
+ // If the old cookie is httponly, make sure we're not coming from script.
+ if (!aFromHttp && oldCookie->IsHttpOnly()) {
+ COOKIE_LOGFAILURE(
+ SET_COOKIE, aHostURI, aCookieHeader,
+ "previously stored cookie is httponly; coming from script");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedHttpOnlyButFromScript"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookie->Name()),
+ });
+ return;
+ }
+
+ // If the new cookie has the same value, expiry date, isSecure, isSession,
+ // isHttpOnly and SameSite flags then we can just keep the old one.
+ // Only if any of these differ we would want to override the cookie.
+ if (oldCookie->Value().Equals(aCookie->Value()) &&
+ oldCookie->Expiry() == aCookie->Expiry() &&
+ oldCookie->IsSecure() == aCookie->IsSecure() &&
+ oldCookie->IsSession() == aCookie->IsSession() &&
+ oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
+ oldCookie->SameSite() == aCookie->SameSite() &&
+ oldCookie->RawSameSite() == aCookie->RawSameSite() &&
+ oldCookie->SchemeMap() == aCookie->SchemeMap() &&
+ // We don't want to perform this optimization if the cookie is
+ // considered stale, since in this case we would need to update the
+ // database.
+ !oldCookie->IsStale()) {
+ // Update the last access time on the old cookie.
+ oldCookie->SetLastAccessed(aCookie->LastAccessed());
+ UpdateCookieOldestTime(oldCookie);
+ return;
+ }
+
+ // Merge the scheme map in case the old cookie and the new cookie are
+ // used with different schemes.
+ MergeCookieSchemeMap(oldCookie, aCookie);
+
+ // Remove the old cookie.
+ RemoveCookieFromList(exactIter);
+
+ // If the new cookie has expired -- i.e. the intent was simply to delete
+ // the old cookie -- then we're done.
+ if (aCookie->Expiry() <= currentTime) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "previously stored cookie was deleted");
+ NotifyChanged(oldCookie, nsICookieNotification::COOKIE_DELETED,
+ aBaseDomain, aBrowsingContext, oldCookieIsSession);
+ return;
+ }
+
+ // Preserve creation time of cookie for ordering purposes.
+ aCookie->SetCreationTime(oldCookie->CreationTime());
+ }
+
+ } else {
+ // check if cookie has already expired
+ if (aCookie->Expiry() <= currentTime) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie has already expired");
+ return;
+ }
+
+ // check if we have to delete an old cookie.
+ CookieEntry* entry =
+ mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
+ if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
+ nsTArray<CookieListIter> removedIterList;
+ // Prioritize evicting insecure cookies.
+ // (draft-ietf-httpbis-cookie-alone section 3.3)
+ uint32_t limit = mMaxCookiesPerHost - mCookieQuotaPerHost;
+ FindStaleCookies(entry, currentTime, false, removedIterList, limit);
+ if (removedIterList.Length() == 0) {
+ if (aCookie->IsSecure()) {
+ // It's valid to evict a secure cookie for another secure cookie.
+ FindStaleCookies(entry, currentTime, true, removedIterList, limit);
+ } else {
+ COOKIE_LOGEVICTED(aCookie,
+ "Too many cookies for this domain and the new "
+ "cookie is not a secure cookie");
+ return;
+ }
+ }
+
+ MOZ_ASSERT(!removedIterList.IsEmpty());
+ // Sort |removedIterList| by index again, since we have to remove the
+ // cookie in the reverse order.
+ removedIterList.Sort(CompareCookiesByIndex());
+ for (auto it = removedIterList.rbegin(); it != removedIterList.rend();
+ it++) {
+ RefPtr<Cookie> evictedCookie = (*it).Cookie();
+ COOKIE_LOGEVICTED(evictedCookie, "Too many cookies for this domain");
+ RemoveCookieFromList(*it);
+ CreateOrUpdatePurgeList(purgedList, evictedCookie);
+ MOZ_ASSERT((*it).entry);
+ }
+ uint32_t purgedLength = 0;
+ purgedList->GetLength(&purgedLength);
+ mozilla::glean::networking::cookie_purge_entry_max.AccumulateSamples(
+ {purgedLength});
+
+ } else if (mCookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
+ int64_t maxAge = aCurrentTimeInUsec - mCookieOldestTime;
+ int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
+ if (maxAge >= purgeAge) {
+ // we're over both size and age limits by 10%; time to purge the table!
+ // do this by:
+ // 1) removing expired cookies;
+ // 2) evicting the balance of old cookies until we reach the size limit.
+ // note that the mCookieOldestTime indicator can be pessimistic - if
+ // it's older than the actual oldest cookie, we'll just purge more
+ // eagerly.
+ purgedList = PurgeCookies(aCurrentTimeInUsec, mMaxNumberOfCookies,
+ mCookiePurgeAge);
+ uint32_t purgedLength = 0;
+ purgedList->GetLength(&purgedLength);
+ mozilla::glean::networking::cookie_purge_max.AccumulateSamples(
+ {purgedLength});
+ }
+ }
+ }
+
+ // Add the cookie to the db. We do not supply a params array for batching
+ // because this might result in removals and additions being out of order.
+ AddCookieToList(aBaseDomain, aOriginAttributes, aCookie);
+ StoreCookie(aBaseDomain, aOriginAttributes, aCookie);
+
+ COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
+
+ // Now that list mutations are complete, notify observers. We do it here
+ // because observers may themselves attempt to mutate the list.
+ if (purgedList) {
+ NotifyChanged(purgedList, nsICookieNotification::COOKIES_BATCH_DELETED,
+ ""_ns);
+ }
+
+ // Notify for topic "private-cookie-changed" or "cookie-changed"
+ NotifyChanged(aCookie,
+ foundCookie ? nsICookieNotification::COOKIE_CHANGED
+ : nsICookieNotification::COOKIE_ADDED,
+ aBaseDomain, aBrowsingContext, oldCookieIsSession);
+}
+
+void CookieStorage::UpdateCookieOldestTime(Cookie* aCookie) {
+ if (aCookie->LastAccessed() < mCookieOldestTime) {
+ mCookieOldestTime = aCookie->LastAccessed();
+ }
+}
+
+void CookieStorage::MergeCookieSchemeMap(Cookie* aOldCookie,
+ Cookie* aNewCookie) {
+ aNewCookie->SetSchemeMap(aOldCookie->SchemeMap() | aNewCookie->SchemeMap());
+}
+
+void CookieStorage::AddCookieToList(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie) {
+ if (!aCookie) {
+ NS_WARNING("Attempting to AddCookieToList with null cookie");
+ return;
+ }
+
+ CookieKey key(aBaseDomain, aOriginAttributes);
+
+ CookieEntry* entry = mHostTable.PutEntry(key);
+ NS_ASSERTION(entry, "can't insert element into a null entry!");
+
+ entry->GetCookies().AppendElement(aCookie);
+ ++mCookieCount;
+
+ // keep track of the oldest cookie, for when it comes time to purge
+ UpdateCookieOldestTime(aCookie);
+}
+
+// static
+already_AddRefed<nsIArray> CookieStorage::CreatePurgeList(nsICookie* aCookie) {
+ nsCOMPtr<nsIMutableArray> removedList =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ removedList->AppendElement(aCookie);
+ return removedList.forget();
+}
+
+// Given the output iter array and the count limit, find cookies
+// sort by expiry and lastAccessed time.
+// static
+void CookieStorage::FindStaleCookies(CookieEntry* aEntry, int64_t aCurrentTime,
+ bool aIsSecure,
+ nsTArray<CookieListIter>& aOutput,
+ uint32_t aLimit) {
+ MOZ_ASSERT(aLimit);
+
+ const CookieEntry::ArrayType& cookies = aEntry->GetCookies();
+ aOutput.Clear();
+
+ CookieIterComparator comp(aCurrentTime);
+ nsTPriorityQueue<CookieListIter, CookieIterComparator> queue(comp);
+
+ for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ Cookie* cookie = cookies[i];
+
+ if (cookie->Expiry() <= aCurrentTime) {
+ queue.Push(CookieListIter(aEntry, i));
+ continue;
+ }
+
+ if (!aIsSecure) {
+ // We want to look for the non-secure cookie first time through,
+ // then find the secure cookie the second time this function is called.
+ if (cookie->IsSecure()) {
+ continue;
+ }
+ }
+
+ queue.Push(CookieListIter(aEntry, i));
+ }
+
+ uint32_t count = 0;
+ while (!queue.IsEmpty() && count < aLimit) {
+ aOutput.AppendElement(queue.Pop());
+ count++;
+ }
+}
+
+// static
+void CookieStorage::CreateOrUpdatePurgeList(nsCOMPtr<nsIArray>& aPurgedList,
+ nsICookie* aCookie) {
+ if (!aPurgedList) {
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list"));
+ aPurgedList = CreatePurgeList(aCookie);
+ return;
+ }
+
+ nsCOMPtr<nsIMutableArray> purgedList = do_QueryInterface(aPurgedList);
+ if (purgedList) {
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Updating existing purge list"));
+ purgedList->AppendElement(aCookie);
+ } else {
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Could not QI aPurgedList!"));
+ }
+}
+
+// purges expired and old cookies in a batch operation.
+already_AddRefed<nsIArray> CookieStorage::PurgeCookiesWithCallbacks(
+ int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
+ int64_t aCookiePurgeAge,
+ std::function<void(const CookieListIter&)>&& aRemoveCookieCallback,
+ std::function<void()>&& aFinalizeCallback) {
+ NS_ASSERTION(mHostTable.Count() > 0, "table is empty");
+
+ uint32_t initialCookieCount = mCookieCount;
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("PurgeCookies(): beginning purge with %" PRIu32
+ " cookies and %" PRId64 " oldest age",
+ mCookieCount, aCurrentTimeInUsec - mCookieOldestTime));
+
+ using PurgeList = nsTArray<CookieListIter>;
+ PurgeList purgeList(kMaxNumberOfCookies);
+
+ nsCOMPtr<nsIMutableArray> removedList =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
+ int64_t purgeTime = aCurrentTimeInUsec - aCookiePurgeAge;
+ int64_t oldestTime = INT64_MAX;
+
+ for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
+ CookieEntry* entry = iter.Get();
+
+ const CookieEntry::ArrayType& cookies = entry->GetCookies();
+ auto length = cookies.Length();
+ for (CookieEntry::IndexType i = 0; i < length;) {
+ CookieListIter iter(entry, i);
+ Cookie* cookie = cookies[i];
+
+ // check if the cookie has expired
+ if (cookie->Expiry() <= currentTime) {
+ removedList->AppendElement(cookie);
+ COOKIE_LOGEVICTED(cookie, "Cookie expired");
+
+ // remove from list; do not increment our iterator, but stop if we're
+ // done already.
+ aRemoveCookieCallback(iter);
+ if (i == --length) {
+ break;
+ }
+ } else {
+ // check if the cookie is over the age limit
+ if (cookie->LastAccessed() <= purgeTime) {
+ purgeList.AppendElement(iter);
+
+ } else if (cookie->LastAccessed() < oldestTime) {
+ // reset our indicator
+ oldestTime = cookie->LastAccessed();
+ }
+
+ ++i;
+ }
+ MOZ_ASSERT(length == cookies.Length());
+ }
+ }
+
+ uint32_t postExpiryCookieCount = mCookieCount;
+
+ // now we have a list of iterators for cookies over the age limit.
+ // sort them by age, and then we'll see how many to remove...
+ purgeList.Sort(CompareCookiesByAge());
+
+ // only remove old cookies until we reach the max cookie limit, no more.
+ uint32_t excess = mCookieCount > aMaxNumberOfCookies
+ ? mCookieCount - aMaxNumberOfCookies
+ : 0;
+ if (purgeList.Length() > excess) {
+ // We're not purging everything in the list, so update our indicator.
+ oldestTime = purgeList[excess].Cookie()->LastAccessed();
+
+ purgeList.SetLength(excess);
+ }
+
+ // sort the list again, this time grouping cookies with a common entryclass
+ // together, and with ascending index. this allows us to iterate backwards
+ // over the list removing cookies, without having to adjust indexes as we go.
+ purgeList.Sort(CompareCookiesByIndex());
+ for (PurgeList::index_type i = purgeList.Length(); i--;) {
+ Cookie* cookie = purgeList[i].Cookie();
+ removedList->AppendElement(cookie);
+ COOKIE_LOGEVICTED(cookie, "Cookie too old");
+
+ aRemoveCookieCallback(purgeList[i]);
+ }
+
+ // Update the database if we have entries to purge.
+ if (aFinalizeCallback) {
+ aFinalizeCallback();
+ }
+
+ // reset the oldest time indicator
+ mCookieOldestTime = oldestTime;
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32
+ " purged; %" PRIu32 " remain; %" PRId64 " oldest age",
+ initialCookieCount - postExpiryCookieCount,
+ postExpiryCookieCount - mCookieCount, mCookieCount,
+ aCurrentTimeInUsec - mCookieOldestTime));
+
+ return removedList.forget();
+}
+
+// remove a cookie from the hashtable, and update the iterator state.
+void CookieStorage::RemoveCookieFromList(const CookieListIter& aIter) {
+ RemoveCookieFromDB(*aIter.Cookie());
+ RemoveCookieFromListInternal(aIter);
+}
+
+void CookieStorage::RemoveCookieFromListInternal(const CookieListIter& aIter) {
+ if (aIter.entry->GetCookies().Length() == 1) {
+ // we're removing the last element in the array - so just remove the entry
+ // from the hash. note that the entryclass' dtor will take care of
+ // releasing this last element for us!
+ mHostTable.RawRemoveEntry(aIter.entry);
+
+ } else {
+ // just remove the element from the list
+ aIter.entry->GetCookies().RemoveElementAt(aIter.index);
+ }
+
+ --mCookieCount;
+}
+
+void CookieStorage::PrefChanged(nsIPrefBranch* aPrefBranch) {
+ int32_t val;
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val))) {
+ mMaxNumberOfCookies =
+ static_cast<uint16_t> LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
+ }
+
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieQuotaPerHost, &val))) {
+ mCookieQuotaPerHost = static_cast<uint16_t> LIMIT(
+ val, 1, mMaxCookiesPerHost - 1, kCookieQuotaPerHost);
+ }
+
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) {
+ mMaxCookiesPerHost = static_cast<uint16_t> LIMIT(
+ val, mCookieQuotaPerHost + 1, 0xFFFF, kMaxCookiesPerHost);
+ }
+
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
+ mCookiePurgeAge =
+ int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
+ }
+}
+
+NS_IMETHODIMP
+CookieStorage::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* /*aData*/) {
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ if (prefBranch) {
+ PrefChanged(prefBranch);
+ }
+ } else if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
+ CollectCookieJarSizeData();
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieStorage.h b/netwerk/cookie/CookieStorage.h
new file mode 100644
index 0000000000..3836edbb9c
--- /dev/null
+++ b/netwerk/cookie/CookieStorage.h
@@ -0,0 +1,216 @@
+/* -*- 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_CookieStorage_h
+#define mozilla_net_CookieStorage_h
+
+#include "CookieKey.h"
+
+#include "nsICookieNotification.h"
+#include "nsIObserver.h"
+#include "nsTHashtable.h"
+#include "nsWeakReference.h"
+#include <functional>
+#include "CookieCommons.h"
+
+class nsIArray;
+class nsICookie;
+class nsICookieTransactionCallback;
+class nsIPrefBranch;
+
+namespace mozilla {
+namespace net {
+
+class Cookie;
+
+// Inherit from CookieKey so this can be stored in nsTHashTable
+// TODO: why aren't we using nsClassHashTable<CookieKey, ArrayType>?
+class CookieEntry : public CookieKey {
+ public:
+ // Hash methods
+ using ArrayType = nsTArray<RefPtr<Cookie>>;
+ using IndexType = ArrayType::index_type;
+
+ explicit CookieEntry(KeyTypePointer aKey) : CookieKey(aKey) {}
+
+ CookieEntry(const CookieEntry& toCopy) {
+ // if we end up here, things will break. nsTHashtable shouldn't
+ // allow this, since we set ALLOW_MEMMOVE to true.
+ MOZ_ASSERT_UNREACHABLE("CookieEntry copy constructor is forbidden!");
+ }
+
+ ~CookieEntry() = default;
+
+ inline ArrayType& GetCookies() { return mCookies; }
+ inline const ArrayType& GetCookies() const { return mCookies; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ bool IsPartitioned() const;
+
+ private:
+ ArrayType mCookies;
+};
+
+// stores the CookieEntry entryclass and an index into the cookie array within
+// that entryclass, for purposes of storing an iteration state that points to a
+// certain cookie.
+struct CookieListIter {
+ // default (non-initializing) constructor.
+ CookieListIter() = default;
+
+ // explicit constructor to a given iterator state with entryclass 'aEntry'
+ // and index 'aIndex'.
+ explicit CookieListIter(CookieEntry* aEntry, CookieEntry::IndexType aIndex)
+ : entry(aEntry), index(aIndex) {}
+
+ // get the Cookie * the iterator currently points to.
+ mozilla::net::Cookie* Cookie() const { return entry->GetCookies()[index]; }
+
+ CookieEntry* entry;
+ CookieEntry::IndexType index;
+};
+
+class CookieStorage : public nsIObserver, public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ void GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const;
+
+ void GetSessionCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const;
+
+ bool FindCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aHost, const nsACString& aName,
+ const nsACString& aPath, CookieListIter& aIter);
+
+ uint32_t CountCookiesFromHost(const nsACString& aBaseDomain,
+ uint32_t aPrivateBrowsingId);
+
+ void GetAll(nsTArray<RefPtr<nsICookie>>& aResult) const;
+
+ const nsTArray<RefPtr<Cookie>>* GetCookiesFromHost(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes);
+
+ void GetCookiesWithOriginAttributes(const OriginAttributesPattern& aPattern,
+ const nsACString& aBaseDomain,
+ nsTArray<RefPtr<nsICookie>>& aResult);
+
+ void RemoveCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aHost, const nsACString& aName,
+ const nsACString& aPath);
+
+ virtual void RemoveCookiesWithOriginAttributes(
+ const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain);
+
+ virtual void RemoveCookiesFromExactHost(
+ const nsACString& aHost, const nsACString& aBaseDomain,
+ const OriginAttributesPattern& aPattern);
+
+ void RemoveAll();
+
+ void NotifyChanged(nsISupports* aSubject,
+ nsICookieNotification::Action aAction,
+ const nsACString& aBaseDomain,
+ dom::BrowsingContext* aBrowsingContext = nullptr,
+ bool aOldCookieIsSession = false);
+
+ void AddCookie(nsIConsoleReportCollector* aCRC, const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes, Cookie* aCookie,
+ int64_t aCurrentTimeInUsec, nsIURI* aHostURI,
+ const nsACString& aCookieHeader, bool aFromHttp,
+ dom::BrowsingContext* aBrowsingContext);
+
+ static void CreateOrUpdatePurgeList(nsCOMPtr<nsIArray>& aPurgedList,
+ nsICookie* aCookie);
+
+ virtual void StaleCookies(const nsTArray<Cookie*>& aCookieList,
+ int64_t aCurrentTimeInUsec) = 0;
+
+ virtual void Close() = 0;
+
+ virtual void EnsureInitialized() = 0;
+
+ virtual nsresult RunInTransaction(
+ nsICookieTransactionCallback* aCallback) = 0;
+
+ protected:
+ CookieStorage() = default;
+ virtual ~CookieStorage() = default;
+
+ void Init();
+
+ void AddCookieToList(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie);
+
+ virtual void StoreCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie) = 0;
+
+ virtual const char* NotificationTopic() const = 0;
+
+ virtual void NotifyChangedInternal(nsICookieNotification* aSubject,
+ bool aOldCookieIsSession) = 0;
+
+ virtual void RemoveAllInternal() = 0;
+
+ // This method calls RemoveCookieFromDB + RemoveCookieFromListInternal.
+ void RemoveCookieFromList(const CookieListIter& aIter);
+
+ void RemoveCookieFromListInternal(const CookieListIter& aIter);
+
+ virtual void RemoveCookieFromDB(const Cookie& aCookie) = 0;
+
+ already_AddRefed<nsIArray> PurgeCookiesWithCallbacks(
+ int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
+ int64_t aCookiePurgeAge,
+ std::function<void(const CookieListIter&)>&& aRemoveCookieCallback,
+ std::function<void()>&& aFinalizeCallback);
+
+ nsTHashtable<CookieEntry> mHostTable;
+
+ uint32_t mCookieCount{0};
+
+ private:
+ void PrefChanged(nsIPrefBranch* aPrefBranch);
+
+ bool FindSecureCookie(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ Cookie* aCookie);
+
+ static void FindStaleCookies(CookieEntry* aEntry, int64_t aCurrentTime,
+ bool aIsSecure,
+ nsTArray<CookieListIter>& aOutput,
+ uint32_t aLimit);
+
+ void UpdateCookieOldestTime(Cookie* aCookie);
+
+ void MergeCookieSchemeMap(Cookie* aOldCookie, Cookie* aNewCookie);
+
+ static already_AddRefed<nsIArray> CreatePurgeList(nsICookie* aCookie);
+
+ virtual already_AddRefed<nsIArray> PurgeCookies(int64_t aCurrentTimeInUsec,
+ uint16_t aMaxNumberOfCookies,
+ int64_t aCookiePurgeAge) = 0;
+
+ virtual void CollectCookieJarSizeData() = 0;
+
+ int64_t mCookieOldestTime{INT64_MAX};
+
+ uint16_t mMaxNumberOfCookies{kMaxNumberOfCookies};
+ uint16_t mMaxCookiesPerHost{kMaxCookiesPerHost};
+ uint16_t mCookieQuotaPerHost{kCookieQuotaPerHost};
+ int64_t mCookiePurgeAge{kCookiePurgeAge};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieStorage_h
diff --git a/netwerk/cookie/CookieXPCShellUtils.sys.mjs b/netwerk/cookie/CookieXPCShellUtils.sys.mjs
new file mode 100644
index 0000000000..b6d0d3f26b
--- /dev/null
+++ b/netwerk/cookie/CookieXPCShellUtils.sys.mjs
@@ -0,0 +1,53 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { ExtensionTestUtils } from "resource://testing-common/ExtensionXPCShellUtils.sys.mjs";
+
+import { AddonTestUtils } from "resource://testing-common/AddonTestUtils.sys.mjs";
+
+export const CookieXPCShellUtils = {
+ init(scope) {
+ AddonTestUtils.maybeInit(scope);
+ ExtensionTestUtils.init(scope);
+ },
+
+ createServer(args) {
+ const server = AddonTestUtils.createHttpServer(args);
+ server.registerPathHandler("/", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+
+ let body = "<body><h1>Hello world!</h1></body>";
+ response.bodyOutputStream.write(body, body.length);
+ });
+ return server;
+ },
+
+ async loadContentPage(uri, options = {}) {
+ return ExtensionTestUtils.loadContentPage(uri, options);
+ },
+
+ async getCookieStringFromDocument(uri, options = {}) {
+ const contentPage = await this.loadContentPage(uri, options);
+ const cookies = await contentPage.spawn(
+ [],
+ // eslint-disable-next-line no-undef
+ () => content.document.cookie
+ );
+ await contentPage.close();
+ return cookies;
+ },
+
+ async setCookieToDocument(uri, set, options = {}) {
+ const contentPage = await this.loadContentPage(uri, options);
+ await contentPage.spawn(
+ [set],
+ // eslint-disable-next-line no-undef
+ cookies => (content.document.cookie = cookies)
+ );
+ await contentPage.close();
+ },
+};
diff --git a/netwerk/cookie/PCookieService.ipdl b/netwerk/cookie/PCookieService.ipdl
new file mode 100644
index 0000000000..f8ec4c8d0f
--- /dev/null
+++ b/netwerk/cookie/PCookieService.ipdl
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include NeckoChannelParams;
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace net {
+
+/**
+ * PCookieService
+ *
+ * Provides IPDL methods for setting and getting cookies. These are stored on
+ * and managed by the parent; the child process goes through the parent for
+ * all cookie operations. Lower-level programmatic operations (i.e. those
+ * provided by the nsICookieManager interface) are not
+ * currently implemented and requesting these interfaces in the child will fail.
+ *
+ * @see nsICookieService
+ * @see nsICookiePermission
+ */
+
+[ManualDealloc, NestedUpTo=inside_cpow] sync protocol PCookieService
+{
+ manager PNecko;
+
+parent:
+ [Nested=inside_cpow] async SetCookies(nsCString baseDomain,
+ OriginAttributes attrs,
+ nullable nsIURI host,
+ bool fromHttp,
+ CookieStruct[] cookies);
+
+ async GetCookieList(nullable nsIURI host,
+ bool isForeign,
+ bool isThirdPartyTrackingResource,
+ bool isThirdPartySocialTrackingResource,
+ bool firstPartyStorageAccessPermissionGranted,
+ uint32_t rejectedReason,
+ bool isSafeTopLevelNav,
+ bool isSameSiteForeign,
+ bool hadCrossSiteRedirects,
+ OriginAttributes attrs)
+ returns (CookieStruct[] cookies);
+
+ async __delete__();
+
+child:
+ async TrackCookiesLoad(CookieStruct[] cookiesList,
+ OriginAttributes attrs);
+
+ async RemoveCookie(CookieStruct cookie,
+ OriginAttributes attrs);
+
+ async RemoveBatchDeletedCookies(CookieStruct[] cookiesList,
+ OriginAttributes[] attrsList);
+
+ async RemoveAll();
+
+ async AddCookie(CookieStruct cookie,
+ OriginAttributes attrs);
+
+};
+
+}
+}
+
diff --git a/netwerk/cookie/moz.build b/netwerk/cookie/moz.build
new file mode 100644
index 0000000000..1667943a31
--- /dev/null
+++ b/netwerk/cookie/moz.build
@@ -0,0 +1,77 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: Cookies")
+
+# export required interfaces, even if --disable-cookies has been given
+XPIDL_SOURCES += [
+ "nsICookie.idl",
+ "nsICookieJarSettings.idl",
+ "nsICookieManager.idl",
+ "nsICookieNotification.idl",
+ "nsICookiePermission.idl",
+ "nsICookieService.idl",
+]
+
+XPIDL_MODULE = "necko_cookie"
+
+
+EXPORTS.mozilla.net = [
+ "Cookie.h",
+ "CookieJarSettings.h",
+ "CookieKey.h",
+ "CookieNotification.h",
+ "CookiePersistentStorage.h",
+ "CookiePrivateStorage.h",
+ "CookieService.h",
+ "CookieServiceChild.h",
+ "CookieServiceParent.h",
+ "CookieStorage.h",
+]
+UNIFIED_SOURCES += [
+ "Cookie.cpp",
+ "CookieCommons.cpp",
+ "CookieJarSettings.cpp",
+ "CookieLogging.cpp",
+ "CookieNotification.cpp",
+ "CookiePersistentStorage.cpp",
+ "CookiePrivateStorage.cpp",
+ "CookieService.cpp",
+ "CookieServiceChild.cpp",
+ "CookieServiceParent.cpp",
+ "CookieStorage.cpp",
+]
+XPCSHELL_TESTS_MANIFESTS += [
+ "test/unit/xpcshell.toml",
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ "test/browser/browser.toml",
+]
+
+MOCHITEST_MANIFESTS += [
+ "test/mochitest/mochitest.toml",
+]
+
+IPDL_SOURCES = [
+ "PCookieService.ipdl",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/intl/uconv",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+]
+
+TESTING_JS_MODULES += [
+ "CookieXPCShellUtils.sys.mjs",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/cookie/nsICookie.idl b/netwerk/cookie/nsICookie.idl
new file mode 100644
index 0000000000..6d7fedfd21
--- /dev/null
+++ b/netwerk/cookie/nsICookie.idl
@@ -0,0 +1,166 @@
+/* -*- 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"
+
+/**
+ * Main cookie object interface.
+ */
+
+%{C++
+namespace mozilla {
+ class OriginAttributes;
+
+ namespace net {
+ class Cookie;
+ }
+}
+%}
+
+[ref] native const_OriginAttributes(const mozilla::OriginAttributes);
+[ref] native const_Cookie(const mozilla::net::Cookie);
+
+typedef long nsCookieStatus;
+typedef long nsCookiePolicy;
+
+[builtinclass, scriptable, uuid(adf0db5e-211e-45a3-be14-4486ac430a58)]
+interface nsICookie : nsISupports {
+ const uint32_t SAMESITE_NONE = 0;
+ const uint32_t SAMESITE_LAX = 1;
+ const uint32_t SAMESITE_STRICT = 2;
+
+ /**
+ * the name of the cookie
+ */
+ readonly attribute ACString name;
+
+ /**
+ * the cookie value
+ */
+ readonly attribute AUTF8String value;
+
+ /**
+ * true if the cookie is a domain cookie, false otherwise
+ */
+ readonly attribute boolean isDomain;
+
+ /**
+ * the host (possibly fully qualified) of the cookie
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * the host (possibly fully qualified) of the cookie,
+ * without a leading dot to represent if it is a
+ * domain cookie.
+ */
+ readonly attribute AUTF8String rawHost;
+
+ /**
+ * the path pertaining to the cookie
+ */
+ readonly attribute AUTF8String path;
+
+ /**
+ * true if the cookie was transmitted over ssl, false otherwise
+ */
+ readonly attribute boolean isSecure;
+
+ /**
+ * @DEPRECATED use nsICookie.expiry and nsICookie.isSession instead.
+ *
+ * expiration time in seconds since midnight (00:00:00), January 1, 1970 UTC.
+ * expires = 0 represents a session cookie.
+ * expires = 1 represents an expiration time earlier than Jan 1, 1970.
+ */
+ readonly attribute uint64_t expires;
+
+ /**
+ * the actual expiry time of the cookie, in seconds
+ * since midnight (00:00:00), January 1, 1970 UTC.
+ *
+ * this is distinct from nsICookie::expires, which
+ * has different and obsolete semantics.
+ */
+ readonly attribute int64_t expiry;
+
+ /**
+ * The origin attributes for this cookie
+ */
+ [implicit_jscontext]
+ readonly attribute jsval originAttributes;
+
+ /**
+ * Native getter for origin attributes
+ */
+ [noscript, notxpcom, nostdcall, binaryname(OriginAttributesNative)]
+ const_OriginAttributes OriginAttributesNative();
+
+ [noscript, notxpcom, nostdcall, binaryname(AsCookie)]
+ const_Cookie AsCookie();
+
+ /**
+ * true if the cookie is a session cookie.
+ * note that expiry time will also be honored
+ * for session cookies (see below); thus, whichever is
+ * the more restrictive of the two will take effect.
+ */
+ readonly attribute boolean isSession;
+
+ /**
+ * true if the cookie is an http only cookie
+ */
+ readonly attribute boolean isHttpOnly;
+
+ /**
+ * the creation time of the cookie, in microseconds
+ * since midnight (00:00:00), January 1, 1970 UTC.
+ */
+ readonly attribute int64_t creationTime;
+
+ /**
+ * the last time the cookie was accessed (i.e. created,
+ * modified, or read by the server), in microseconds
+ * since midnight (00:00:00), January 1, 1970 UTC.
+ *
+ * note that this time may be approximate.
+ */
+ readonly attribute int64_t lastAccessed;
+
+ /**
+ * the SameSite attribute; this controls the cookie behavior for cross-site
+ * requests as per
+ * https://tools.ietf.org/html/draft-west-first-party-cookies-07
+ *
+ * This should be one of:
+ * - SAMESITE_NONE - the SameSite attribute is not present
+ * - SAMESITE_LAX - the SameSite attribute is present, but not strict
+ * - SAMESITE_STRICT - the SameSite attribute is present and strict
+ */
+ readonly attribute int32_t sameSite;
+
+ /**
+ * The list of possible schemes of cookies. It's a bitmap because a cookie
+ * can be set on HTTP and HTTPS. At the moment, we treat it as the same
+ * cookie.
+ */
+ cenum schemeType : 8 {
+ SCHEME_UNSET = 0x00,
+ SCHEME_HTTP = 0x01,
+ SCHEME_HTTPS = 0x02,
+ SCHEME_FILE = 0x04,
+ };
+
+ /**
+ * Bitmap of schemes.
+ */
+ readonly attribute nsICookie_schemeType schemeMap;
+
+ /**
+ * true if the cookie's OriginAttributes PartitionKey is NOT empty
+ */
+ readonly attribute boolean isPartitioned;
+};
diff --git a/netwerk/cookie/nsICookieJarSettings.idl b/netwerk/cookie/nsICookieJarSettings.idl
new file mode 100644
index 0000000000..d522c96358
--- /dev/null
+++ b/netwerk/cookie/nsICookieJarSettings.idl
@@ -0,0 +1,86 @@
+/* -*- 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 "nsISerializable.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * Cookie jar settings for top-level documents. Please see CookieJarSettings.h
+ * for more details.
+ */
+[scriptable, builtinclass, uuid(3ec40331-7cf0-4b71-ba2a-2265aab8f6bc)]
+interface nsICookieJarSettings : nsISerializable
+{
+ /**
+ * CookieBehavior at the loading of the document. Any other loadInfo
+ * inherits it from its document's loadInfo. If there is not a document
+ * involved, cookieBehavior is reject.
+ */
+ [infallible] readonly attribute unsigned long cookieBehavior;
+
+ /**
+ * First-Party Isolation state at the loading of the document.
+ */
+ [infallible] readonly attribute boolean isFirstPartyIsolated;
+
+ /**
+ * Resist Fingerprinting state at the loading of the document.
+ */
+ [infallible] readonly attribute boolean shouldResistFingerprinting;
+
+ /**
+ * Whether our cookie behavior mandates rejecting third-party contexts.
+ */
+ [infallible] readonly attribute boolean rejectThirdPartyContexts;
+
+ [infallible] readonly attribute boolean limitForeignContexts;
+
+ [infallible] readonly attribute boolean blockingAllThirdPartyContexts;
+
+ [infallible] readonly attribute boolean blockingAllContexts;
+ /**
+ * Whether our cookie behavior mandates partitioning third-party content.
+ */
+ [infallible] attribute boolean partitionForeign;
+
+ /**
+ * Whether the top-level document is on the content blocking allow list.
+ */
+ [infallible] readonly attribute boolean isOnContentBlockingAllowList;
+
+ /**
+ * The key used for partitioning.
+ */
+ readonly attribute AString partitionKey;
+
+ /**
+ * The key used for fingerprinting randomization.
+ */
+ readonly attribute Array<uint8_t> fingerprintingRandomizationKey;
+
+ /**
+ * CookiePermission at the loading of the document for a particular
+ * principal. It returns the same cookiePermission also in case it changes
+ * during the life-time of the top document.
+ */
+ unsigned long cookiePermission(in nsIPrincipal aPrincipal);
+
+ /**
+ * Initiate the cookieJarSettings with a URI. The aURI will be used to build
+ * the partition key for this cookieJarSettings. This function is added for
+ * js code to be able to set the partitionKey from a first-party URI.
+ *
+ * The aIsPrivate indicates if this cookieJarSettings is initiated for the
+ * private browsing mode or not. If aIsPrivate was true, it will get
+ * cookieBehavior from the pref "network.cookie.cookieBehavior" which is for
+ * the regular browsing mode. Otherwise, it will get from the pref
+ * "network.cookie.cookieBehavior.pbmode" for the private browsing mode.
+ */
+ void initWithURI(in nsIURI aURI, in boolean aIsPrivate);
+};
diff --git a/netwerk/cookie/nsICookieManager.idl b/netwerk/cookie/nsICookieManager.idl
new file mode 100644
index 0000000000..ae18ec27c5
--- /dev/null
+++ b/netwerk/cookie/nsICookieManager.idl
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsICookie.idl"
+
+%{ C++
+namespace mozilla {
+class OriginAttributes;
+} // mozilla namespace
+%}
+
+[ptr] native OriginAttributesPtr(mozilla::OriginAttributes);
+
+/**
+ * An optional interface for accessing or removing the cookies
+ * that are in the cookie list
+ */
+
+[scriptable, builtinclass, uuid(AAAB6710-0F2C-11d5-A53B-0010A401EB10)]
+interface nsICookieManager : nsISupports
+{
+
+ /**
+ * Called to remove all cookies from the cookie list
+ */
+ void removeAll();
+
+ /**
+ * Returns an array of cookies in the cookie list.
+ * The objects in the array are of type nsICookie
+ * This array only contains non-private browsing cookies.
+ * To retrieve an array of private browsing cookies, use
+ * getCookiesWithOriginAttributes.
+ */
+ readonly attribute Array<nsICookie> cookies;
+
+ /**
+ * Returns an array of session cookies in the cookie list.
+ * The objects in the array are of type nsICookie
+ * This array only contains non-private browsing cookies.
+ */
+ readonly attribute Array<nsICookie> sessionCookies;
+
+ /**
+ * Returns current effective value of the cookieBehavior. It will return the
+ * different pref according to the aIsPrivate. If aIsPrivate is true, it will
+ * return the pref "network.cookie.cookieBehavior". Otherwise, it will return
+ * the pref "network.cookie.cookieBehavior.pbmode"
+ */
+ uint32_t getCookieBehavior(in boolean aIsPrivate);
+ %{C++
+ static uint32_t GetCookieBehavior(bool aIsPrivate);
+ %}
+
+ /**
+ * Called to remove an individual cookie from the cookie list, specified
+ * by host, name, and path. If the cookie cannot be found, no exception
+ * is thrown. Typically, the arguments to this method will be obtained
+ * directly from the desired nsICookie object.
+ *
+ * @param aHost The host or domain for which the cookie was set. @see
+ * nsICookieManager::add for a description of acceptable host
+ * strings. If the target cookie is a domain cookie, a leading
+ * dot must be present.
+ * @param aName The name specified in the cookie
+ * @param aPath The path for which the cookie was set
+ * @param aOriginAttributes The originAttributes of this cookie.
+ *
+ */
+ [implicit_jscontext]
+ void remove(in AUTF8String aHost,
+ in ACString aName,
+ in AUTF8String aPath,
+ in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult removeNative(in AUTF8String aHost,
+ in ACString aName,
+ in AUTF8String aPath,
+ in OriginAttributesPtr aOriginAttributes);
+
+ /**
+ * Add a cookie. nsICookieService is the normal way to do this. This
+ * method is something of a backdoor.
+ *
+ * @param aHost
+ * the host or domain for which the cookie is set. presence of a
+ * leading dot indicates a domain cookie; otherwise, the cookie
+ * is treated as a non-domain cookie (see RFC2109). The host string
+ * will be normalized to ASCII or ACE; any trailing dot will be
+ * stripped. To be a domain cookie, the host must have at least two
+ * subdomain parts (e.g. '.foo.com', not '.com'), otherwise an
+ * exception will be thrown. An empty string is acceptable
+ * (e.g. file:// URI's).
+ * @param aPath
+ * path within the domain for which the cookie is valid
+ * @param aName
+ * cookie name
+ * @param aValue
+ * cookie data
+ * @param aIsSecure
+ * true if the cookie should only be sent over a secure connection.
+ * @param aIsHttpOnly
+ * true if the cookie should only be sent to, and can only be
+ * modified by, an http connection.
+ * @param aIsSession
+ * true if the cookie should exist for the current session only.
+ * see aExpiry.
+ * @param aExpiry
+ * expiration date, in seconds since midnight (00:00:00), January 1,
+ * 1970 UTC. note that expiry time will also be honored for session cookies;
+ * in this way, the more restrictive of the two will take effect.
+ * @param aOriginAttributes
+ * the originAttributes of this cookie.
+ * @param aSameSite
+ * the SameSite attribute.
+ */
+ [implicit_jscontext]
+ void add(in AUTF8String aHost,
+ in AUTF8String aPath,
+ in ACString aName,
+ in AUTF8String aValue,
+ in boolean aIsSecure,
+ in boolean aIsHttpOnly,
+ in boolean aIsSession,
+ in int64_t aExpiry,
+ in jsval aOriginAttributes,
+ in int32_t aSameSite,
+ in nsICookie_schemeType aSchemeMap);
+
+ [notxpcom]
+ nsresult addNative(in AUTF8String aHost,
+ in AUTF8String aPath,
+ in ACString aName,
+ in AUTF8String aValue,
+ in boolean aIsSecure,
+ in boolean aIsHttpOnly,
+ in boolean aIsSession,
+ in int64_t aExpiry,
+ in OriginAttributesPtr aOriginAttributes,
+ in int32_t aSameSite,
+ in nsICookie_schemeType aSchemeMap);
+
+ /**
+ * Find whether a given cookie already exists.
+ *
+ * @param aHost
+ * the cookie's host to look for
+ * @param aPath
+ * the cookie's path to look for
+ * @param aName
+ * the cookie's name to look for
+ * @param aOriginAttributes
+ * the cookie's originAttributes to look for
+ *
+ * @return true if a cookie was found which matches the host, path, name and
+ * originAttributes fields of aCookie
+ */
+ [implicit_jscontext]
+ boolean cookieExists(in AUTF8String aHost,
+ in AUTF8String aPath,
+ in ACString aName,
+ in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult cookieExistsNative(in AUTF8String aHost,
+ in AUTF8String aPath,
+ in ACString aName,
+ in OriginAttributesPtr aOriginAttributes,
+ out boolean aExists);
+
+ /**
+ * Get a specific cookie by host, path, name and OriginAttributes.
+ *
+ * @param aHost
+ * the cookie's host to look for
+ * @param aPath
+ * the cookie's path to look for
+ * @param aName
+ * the cookie's name to look for
+ * @param aOriginAttributes
+ * the cookie's originAttributes to look for
+ *
+ * @return cookie matching the arguments or nullptr if not existing.
+ */
+ [notxpcom]
+ nsresult getCookieNative(in AUTF8String aHost,
+ in AUTF8String aPath,
+ in ACString aName,
+ in OriginAttributesPtr aOriginAttributes,
+ out nsICookie aCookie);
+
+ /**
+ * Count how many cookies exist within the base domain of 'aHost'.
+ * Thus, for a host "weather.yahoo.com", the base domain would be "yahoo.com",
+ * and any host or domain cookies for "yahoo.com" and its subdomains would be
+ * counted.
+ *
+ * @param aHost
+ * the host string to search for, e.g. "google.com". this should consist
+ * of only the host portion of a URI. see @add for a description of
+ * acceptable host strings.
+ *
+ * @return the number of cookies found.
+ */
+ unsigned long countCookiesFromHost(in AUTF8String aHost);
+
+ /**
+ * Returns an array of cookies that exist within the base domain of
+ * 'aHost'. Thus, for a host "weather.yahoo.com", the base domain would be
+ * "yahoo.com", and any host or domain cookies for "yahoo.com" and its
+ * subdomains would be returned.
+ *
+ * @param aHost
+ * the host string to search for, e.g. "google.com". this should consist
+ * of only the host portion of a URI. see @add for a description of
+ * acceptable host strings.
+ * @param aOriginAttributes The originAttributes of cookies that would be
+ * retrived.
+ *
+ * @return an array of nsICookie objects.
+ *
+ * @see countCookiesFromHost
+ */
+ [implicit_jscontext]
+ Array<nsICookie> getCookiesFromHost(in AUTF8String aHost,
+ in jsval aOriginAttributes);
+
+ /**
+ * Returns an array of all cookies whose origin attributes matches aPattern
+ *
+ * @param aPattern origin attribute pattern in JSON format
+ *
+ * @param aHost
+ * the host string to search for, e.g. "google.com". this should consist
+ * of only the host portion of a URI. see @add for a description of
+ * acceptable host strings. This attribute is optional. It will search
+ * all hosts if this attribute is not given.
+ */
+ Array<nsICookie> getCookiesWithOriginAttributes(in AString aPattern,
+ [optional] in AUTF8String aHost);
+
+ /**
+ * Remove all the cookies whose origin attributes matches aPattern
+ *
+ * @param aPattern origin attribute pattern in JSON format
+ */
+ void removeCookiesWithOriginAttributes(in AString aPattern,
+ [optional] in AUTF8String aHost);
+
+ /**
+ * Remove all the cookies whose origin attributes matches aPattern and the
+ * host is exactly aHost (without subdomain matching).
+ *
+ * @param aHost the host to match
+ * @param aPattern origin attribute pattern in JSON format
+ */
+ void removeCookiesFromExactHost(in AUTF8String aHost, in AString aPattern);
+
+ /**
+ * Removes all cookies that were created on or after aSinceWhen, and returns
+ * a Promise which will be resolved when the last such cookie has been
+ * removed.
+ *
+ * @param aSinceWhen the starting point in time after which no cookies should
+ * be created when the Promise returned from this method is resolved.
+ */
+ [implicit_jscontext]
+ Promise removeAllSince(in int64_t aSinceWhen);
+
+ /**
+ * Retrieves all the cookies that were created on or after aSinceWhen, order
+ * by creation time */
+ Array<nsICookie> getCookiesSince(in int64_t aSinceWhen);
+};
diff --git a/netwerk/cookie/nsICookieNotification.idl b/netwerk/cookie/nsICookieNotification.idl
new file mode 100644
index 0000000000..b37ae65873
--- /dev/null
+++ b/netwerk/cookie/nsICookieNotification.idl
@@ -0,0 +1,70 @@
+/* -*- 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 nsICookie;
+interface nsIArray;
+webidl BrowsingContext;
+
+/**
+ * Meta object dispatched by cookie change notifications.
+ */
+[builtinclass, scriptable, uuid(5b3490f2-75f0-4e36-9f3d-47c857ecdfbb)]
+interface nsICookieNotification : nsISupports {
+
+ cenum Action : 8 {
+ // A cookie was deleted. cookie contains the deleted cookie.
+ COOKIE_DELETED,
+ // A cookie was added. cookie contains the added cookie.
+ COOKIE_ADDED,
+ // A cookie was altered. cookie contains the updated version of the
+ // cookie. Note that host, path, and name are invariant for a given
+ // cookie; other parameters may change.
+ COOKIE_CHANGED,
+ // the entire cookie list was cleared. cookie is null.
+ ALL_COOKIES_CLEARED,
+ // A set of cookies was purged. batchDeletedCookies contains the list of
+ // deleted cookies. cookie is null.
+ // Purging typically affects expired cookies or cases where the cookie
+ // list grows too large.
+ COOKIES_BATCH_DELETED,
+ };
+
+ /**
+ * Describes the cookie operation this notification is for. Cookies may be
+ * deleted, added or changed. See Action enum above for possible values.
+ */
+ [infallible] readonly attribute nsICookieNotification_Action action;
+
+
+ /**
+ * The cookie the notification is for, may be null depending on the action.
+ */
+ [infallible] readonly attribute nsICookie cookie;
+
+ /**
+ * Base domain of the cookie. May be empty if cookie is null.
+ */
+ readonly attribute ACString baseDomain;
+
+ /**
+ * List of cookies purged.
+ * Only set when action == COOKIES_BATCH_DELETED.
+ */
+ readonly attribute nsIArray batchDeletedCookies;
+
+ /**
+ * The id of the BrowsingContext the cookie change was triggered from. Set
+ * to 0 if there is not applicable BrowsingContext.
+ */
+ [infallible] readonly attribute unsigned long long browsingContextId;
+
+ /**
+ * BrowsingContext associated with browsingContextId. May be nullptr.
+ */
+ [infallible] readonly attribute BrowsingContext browsingContext;
+};
diff --git a/netwerk/cookie/nsICookiePermission.idl b/netwerk/cookie/nsICookiePermission.idl
new file mode 100644
index 0000000000..dc9b2b0a1b
--- /dev/null
+++ b/netwerk/cookie/nsICookiePermission.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"
+
+typedef long nsCookieAccess;
+
+/**
+ * An interface to test for cookie permissions
+ */
+[scriptable, uuid(11ddd4ed-8f5b-40b3-b2a0-27c20ea1c88d)]
+interface nsICookiePermission : nsISupports
+{
+ /**
+ * nsCookieAccess values
+ */
+ const nsCookieAccess ACCESS_DEFAULT = 0;
+ const nsCookieAccess ACCESS_ALLOW = 1;
+ const nsCookieAccess ACCESS_DENY = 2;
+
+ /**
+ * additional values for nsCookieAccess which may not match
+ * nsIPermissionManager. Keep 3-7 available to allow nsIPermissionManager to
+ * add values without colliding. ACCESS_SESSION is not directly returned by
+ * any methods on this interface.
+ */
+ const nsCookieAccess ACCESS_SESSION = 8;
+
+ /**
+ * Don't use values 9 and 10! They used to be ACCESS_ALLOW_FIRST_PARTY_ONLY
+ * and ACCESS_LIMIT_THIRD_PARTY, now removed, but maybe still stored in some
+ * ancient user profiles.
+ */
+};
diff --git a/netwerk/cookie/nsICookieService.idl b/netwerk/cookie/nsICookieService.idl
new file mode 100644
index 0000000000..c69453f6fa
--- /dev/null
+++ b/netwerk/cookie/nsICookieService.idl
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIChannel;
+webidl Document;
+
+/**
+ * @see nsICookieService::runInTransaction
+ */
+[scriptable, function, uuid(0fc41ffb-f1b7-42d9-9a42-8dc420c158c1)]
+interface nsICookieTransactionCallback : nsISupports
+{
+ void callback();
+};
+
+/**
+ * nsICookieService
+ *
+ * Provides methods for setting and getting cookies in the context of a
+ * page load. See nsICookieManager for methods to manipulate the cookie
+ * database directly. This separation of interface is mainly historical.
+ *
+ * This service broadcasts the notifications detailed below when the cookie
+ * list is changed, or a cookie is rejected.
+ *
+ * NOTE: observers of these notifications *must* not attempt to change profile
+ * or switch into or out of private browsing mode from within the
+ * observer. Doing so will cause undefined behavior. Mutating the cookie
+ * list (e.g. by calling methods on nsICookieService and friends) is
+ * allowed, but beware that there may be pending notifications you haven't
+ * seen yet -- for instance, a COOKIES_BATCH_DELETED notification will likely be
+ * immediately followed by COOKIE_ADDED. You may check the state of the cookie
+ * list to determine if this is the case.
+ *
+ * topic : "cookie-changed"
+ * broadcast whenever the cookie list changes in some way. see
+ * explanation of data strings below.
+ * subject: The cookie notification. See nsICookieNotification for details.
+ * data : none. For possible actions see nsICookieNotification_Action.
+ *
+ * topic : "cookie-rejected"
+ * broadcast whenever a cookie was rejected from being set as a
+ * result of user prefs.
+ * subject: an nsIURI interface pointer representing the URI that attempted
+ * to set the cookie.
+ * data : none.
+ */
+[scriptable, uuid(1e94e283-2811-4f43-b947-d22b1549d824)]
+interface nsICookieService : nsISupports
+{
+ /*
+ * Possible values for the "network.cookie.cookieBehavior" preference.
+ */
+ const uint32_t BEHAVIOR_ACCEPT = 0; // allow all cookies
+ const uint32_t BEHAVIOR_REJECT_FOREIGN = 1; // reject all third-party cookies
+ const uint32_t BEHAVIOR_REJECT = 2; // reject all cookies
+ const uint32_t BEHAVIOR_LIMIT_FOREIGN = 3; // reject third-party cookies unless the
+ // eTLD already has at least one cookie
+ const uint32_t BEHAVIOR_REJECT_TRACKER = 4; // reject trackers
+ const uint32_t BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN = 5; // reject trackers, partition third-party cookies
+ // When adding a new cookie behavior, please increase this value!
+ const uint32_t BEHAVIOR_LAST = 5;
+
+ /*
+ * Get the complete cookie string associated with the document's principal.
+ * This method is meant to be used for `document.cookie` only. Any security
+ * check about storage-access permission and cookie behavior must be done by
+ * the caller.
+ *
+ * @param aDocument
+ * The document.
+ *
+ * @return the resulting cookie string
+ */
+ ACString getCookieStringFromDocument(in Document aDocument);
+
+ /*
+ * Get the complete cookie string associated with the URI.
+ *
+ * This function is NOT redundant with getCookieString, as the result
+ * will be different based on httponly (see bug 178993)
+ *
+ * @param aURI
+ * The URI of the document for which cookies are being queried.
+ * file:// URIs (i.e. with an empty host) are allowed, but any other
+ * scheme must have a non-empty host. A trailing dot in the host
+ * is acceptable, and will be stripped. This argument must not be null.
+ * @param aChannel
+ * the channel used to load the document.
+ *
+ * @return the resulting cookie string
+ */
+ ACString getCookieStringFromHttp(in nsIURI aURI, in nsIChannel aChannel);
+
+ /*
+ * Set the cookie string associated with a Document. This method is meant to
+ * be used for `document.cookie` only. Any security check about
+ * storage-access permission and cookie behavior must be done by the caller.
+ *
+ * @param aDocument
+ * The document.
+ * @param aCookie
+ * the cookie string to set.
+ */
+ void setCookieStringFromDocument(in Document aDocument, in ACString aCookie);
+
+ /*
+ * Set the cookie string and expires associated with the URI.
+ *
+ * This function is NOT redundant with setCookieString, as the result
+ * will be different based on httponly (see bug 178993)
+ *
+ * @param aURI
+ * The URI of the document for which cookies are being queried.
+ * file:// URIs (i.e. with an empty host) are allowed, but any other
+ * scheme must have a non-empty host. A trailing dot in the host
+ * is acceptable, and will be stripped. This argument must not be null.
+ * @param aCookie
+ * the cookie string to set.
+ * @param aChannel
+ * the channel used to load the document.
+ */
+ void setCookieStringFromHttp(in nsIURI aURI, in ACString aCookie,
+ in nsIChannel aChannel);
+
+ /*
+ * Batch SQLite operations into one transaction. By default each call to
+ * CookieService that affects the underlying SQLite database (add, remove,
+ * setCookieString etc.) runs in a separate transaction. If you do this many
+ * times in a row, it's faster and suggested to wrap them all in a single
+ * transaction by setting all the operations into the callback parameter.
+ * Example: test scripts that need to construct a large cookie database.
+ * @param aCallback
+ * nsICookieTransactionCallback interface to call
+ * @throws NS_ERROR_FAILURE if aCallback() fails.
+ * @throws NS_ERROR_NOT_AVAILABLE if the connection is not established.
+ */
+ void runInTransaction(in nsICookieTransactionCallback aCallback);
+};
diff --git a/netwerk/cookie/test/browser/browser.toml b/netwerk/cookie/test/browser/browser.toml
new file mode 100644
index 0000000000..05a302ddbd
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser.toml
@@ -0,0 +1,42 @@
+[DEFAULT]
+
+support-files = [
+ "file_empty.html",
+ "file_empty.js",
+ "head.js"
+]
+
+["browser_broadcastChannel.js"]
+
+["browser_cookie_insecure_overwrites_secure.js"]
+
+["browser_cookie_purge_sync.js"]
+
+["browser_cookies.js"]
+support-files = ["server.sjs"]
+
+["browser_cookies_ipv6.js"]
+
+["browser_domCache.js"]
+
+["browser_indexedDB.js"]
+
+["browser_originattributes.js"]
+
+["browser_oversize.js"]
+support-files = ["oversize.sjs"]
+
+["browser_partitionedConsole.js"]
+support-files = ["partitioned.sjs"]
+
+["browser_partitioned_telemetry.js"]
+support-files = ["partitioned.sjs"]
+
+["browser_sameSiteConsole.js"]
+support-files = ["sameSite.sjs"]
+
+["browser_serviceWorker.js"]
+
+["browser_sharedWorker.js"]
+
+["browser_storage.js"]
diff --git a/netwerk/cookie/test/browser/browser_broadcastChannel.js b/netwerk/cookie/test/browser/browser_broadcastChannel.js
new file mode 100644
index 0000000000..ee561e6f0c
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_broadcastChannel.js
@@ -0,0 +1,80 @@
+// BroadcastChannel is not considered part of CookieJar. It's not allowed to
+// communicate with other windows with different cookie jar settings.
+"use strict";
+
+CookiePolicyHelper.runTest("BroadcastChannel", {
+ cookieJarAccessAllowed: async w => {
+ new w.BroadcastChannel("hello");
+ ok(true, "BroadcastChannel be used");
+ },
+
+ cookieJarAccessDenied: async w => {
+ try {
+ new w.BroadcastChannel("hello");
+ ok(false, "BroadcastChannel cannot be used!");
+ } catch (e) {
+ ok(true, "BroadcastChannel cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+});
+
+CookiePolicyHelper.runTest("BroadcastChannel in workers", {
+ cookieJarAccessAllowed: async w => {
+ function nonBlockingCode() {
+ new BroadcastChannel("hello");
+ postMessage(true);
+ }
+
+ let blob = new w.Blob([
+ nonBlockingCode.toString() + "; nonBlockingCode();",
+ ]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = w.URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new w.Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new w.Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+ });
+ },
+
+ cookieJarAccessDenied: async w => {
+ function blockingCode() {
+ try {
+ new BroadcastChannel("hello");
+ postMessage(false);
+ } catch (e) {
+ postMessage(e.name == "SecurityError");
+ }
+ }
+
+ let blob = new w.Blob([blockingCode.toString() + "; blockingCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = w.URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new w.Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new w.Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+ });
+ },
+});
diff --git a/netwerk/cookie/test/browser/browser_cookie_insecure_overwrites_secure.js b/netwerk/cookie/test/browser/browser_cookie_insecure_overwrites_secure.js
new file mode 100644
index 0000000000..7461e5cc16
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_cookie_insecure_overwrites_secure.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const urlPath = "/browser/netwerk/cookie/test/browser/file_empty.html";
+const baseDomain = "example.com";
+
+// eslint doesn't like http
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const URL_INSECURE_COM = "http://" + baseDomain + urlPath;
+const URL_SECURE_COM = "https://" + baseDomain + urlPath;
+
+// common cookie strings
+const COOKIE_BASIC = "foo=one";
+const COOKIE_OTHER = "foo=two";
+const COOKIE_THIRD = "foo=three";
+const COOKIE_FORTH = "foo=four";
+
+function securify(cookie) {
+ return cookie + "; Secure";
+}
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("dom.security.https_first");
+ Services.prefs.clearUserPref("network.cookie.cookieBehavior");
+ Services.prefs.clearUserPref(
+ "network.cookieJarSettings.unblocked_for_testing"
+ );
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+ Services.prefs.clearUserPref("network.cookie.sameSite.noneRequiresSecure");
+ Services.prefs.clearUserPref("network.cookie.sameSite.schemeful");
+ info("Cleaning up the test");
+});
+
+async function setup() {
+ // HTTPS-First would interfere with this test.
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+ Services.prefs.setBoolPref(
+ "network.cookie.sameSite.noneRequiresSecure",
+ false
+ );
+ Services.prefs.setBoolPref("network.cookie.sameSite.schemeful", true);
+ Services.cookies.removeAll();
+}
+add_task(setup);
+
+// note:
+// 1. The URL scheme will not matter for insecure cookies, since
+// cookies are not "schemeful" in this sense.
+// So an insecure cookie set anywhere will be visible on http and https sites
+// Secure cookies are different, they will only be visible from https sites
+// and will prevent cookie setting of the same name on insecure sites.
+//
+// 2. The different processes (tabs) shouldn't matter since
+// cookie adds/changes are distributed to other processes on a need-to-know
+// basis.
+
+add_task(async function test_insecure_cant_overwrite_secure_via_doc() {
+ // insecure
+ const tab1 = BrowserTestUtils.addTab(gBrowser, URL_INSECURE_COM);
+ const browser = gBrowser.getBrowserForTab(tab1);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // secure
+ const tab2 = BrowserTestUtils.addTab(gBrowser, URL_SECURE_COM);
+ const browser2 = gBrowser.getBrowserForTab(tab2);
+ await BrowserTestUtils.browserLoaded(browser2);
+
+ // init with insecure cookie on insecure origin child process
+ await SpecialPowers.spawn(
+ browser,
+ [COOKIE_BASIC, COOKIE_BASIC],
+ (cookie, expected) => {
+ content.document.cookie = cookie;
+ is(content.document.cookie, expected);
+ }
+ );
+
+ // insecure cookie visible on secure origin process (sanity check)
+ await SpecialPowers.spawn(browser2, [COOKIE_BASIC], expected => {
+ is(content.document.cookie, expected);
+ });
+
+ // overwrite insecure cookie on secure origin with secure cookie (sanity check)
+ await SpecialPowers.spawn(
+ browser2,
+ [securify(COOKIE_OTHER), COOKIE_OTHER],
+ (cookie, expected) => {
+ content.document.cookie = cookie;
+ is(content.document.cookie, expected);
+ }
+ );
+
+ // insecure cookie will NOT overwrite the secure one on insecure origin
+ // and cookie.document appears blank
+ await SpecialPowers.spawn(browser, [COOKIE_THIRD, ""], (cookie, expected) => {
+ content.document.cookie = cookie; // quiet failure here
+ is(content.document.cookie, expected);
+ });
+
+ // insecure cookie will overwrite secure cookie on secure origin
+ // a bit weird, but this is normal
+ await SpecialPowers.spawn(
+ browser2,
+ [COOKIE_FORTH, COOKIE_FORTH],
+ (cookie, expected) => {
+ content.document.cookie = cookie;
+ is(content.document.cookie, expected);
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+ Services.cookies.removeAll();
+});
diff --git a/netwerk/cookie/test/browser/browser_cookie_purge_sync.js b/netwerk/cookie/test/browser/browser_cookie_purge_sync.js
new file mode 100644
index 0000000000..186797d058
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_cookie_purge_sync.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 test checks that cookie purge broadcast correctly
+// updates content process memory as triggered by:
+// 1. stale-cookie update from content process
+// 2. delete cookie by host from parent process
+// 3. clear all cookies from parent process
+
+const URL_EXAMPLE = "https://example.com";
+const COOKIE_NAMEVALUE = "name=value";
+const COOKIE_NAMEVALUE_2 = COOKIE_NAMEVALUE + "2";
+const COOKIE_STRING = COOKIE_NAMEVALUE + "; Secure; SameSite=None";
+const COOKIE_STRING_2 = COOKIE_NAMEVALUE_2 + "; Secure; SameSite=None";
+const MAX_AGE_OLD = 2; // seconds
+const MAX_AGE_NEW = 100; // 100 sec
+
+registerCleanupFunction(() => {
+ info("Cleaning up the test setup");
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+ Services.cookies.removeAll();
+});
+
+add_setup(async function () {
+ info("Setting up the test");
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+});
+
+function waitForNotificationPromise(notification, expected) {
+ return new Promise(resolve => {
+ function observer(subject, topic, data) {
+ is(content.document.cookie, expected);
+ Services.obs.removeObserver(observer, notification);
+ resolve();
+ }
+ Services.obs.addObserver(observer, notification);
+ });
+}
+
+add_task(async function test_purge_sync_batch_and_deleted() {
+ const tab1 = BrowserTestUtils.addTab(gBrowser, URL_EXAMPLE);
+ const browser = gBrowser.getBrowserForTab(tab1);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ const tab2 = BrowserTestUtils.addTab(gBrowser, URL_EXAMPLE);
+ const browser2 = gBrowser.getBrowserForTab(tab2);
+ await BrowserTestUtils.browserLoaded(browser2);
+
+ let firstCookieAdded = SpecialPowers.spawn(
+ browser2,
+ ["content-added-cookie", COOKIE_NAMEVALUE],
+ waitForNotificationPromise
+ );
+ await TestUtils.waitForTick(); // waiting helps --verify
+
+ // set old cookie in tab 1 and check it in tab 2
+ await SpecialPowers.spawn(
+ browser,
+ [COOKIE_STRING, MAX_AGE_OLD],
+ (cookie, max_age) => {
+ content.document.cookie = cookie + ";Max-Age=" + max_age;
+ }
+ );
+ await firstCookieAdded;
+
+ // wait until the first cookie expires
+ await SpecialPowers.spawn(browser2, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.document.cookie == "",
+ "cookie did not expire in time",
+ 200
+ ).catch(msg => {
+ is(false, "Cookie did not expire in time");
+ });
+ });
+
+ // BATCH_DELETED/BatchDeleted pathway
+ let batchDeletedPromise = SpecialPowers.spawn(
+ browser,
+ ["content-batch-deleted-cookies", COOKIE_NAMEVALUE_2],
+ waitForNotificationPromise
+ );
+ await TestUtils.waitForTick(); // waiting helps --verify
+ await SpecialPowers.spawn(
+ browser,
+ [COOKIE_STRING_2, MAX_AGE_NEW],
+ (cookie, max_age) => {
+ content.document.cookie = cookie + ";Max-Age=" + max_age;
+ }
+ );
+ await batchDeletedPromise;
+
+ // COOKIE_DELETED/RemoveCookie pathway
+ let cookieRemovedPromise = SpecialPowers.spawn(
+ browser,
+ ["content-removed-cookie", ""],
+ waitForNotificationPromise
+ );
+ let cookieRemovedPromise2 = SpecialPowers.spawn(
+ browser2,
+ ["content-removed-cookie", ""],
+ waitForNotificationPromise
+ );
+ await TestUtils.waitForTick();
+ Services.cookies.removeCookiesFromExactHost(
+ "example.com",
+ JSON.stringify({})
+ );
+ await cookieRemovedPromise;
+ await cookieRemovedPromise2;
+
+ // cleanup prep
+ let anotherCookieAdded = SpecialPowers.spawn(
+ browser2,
+ ["content-added-cookie", COOKIE_NAMEVALUE],
+ waitForNotificationPromise
+ );
+ await TestUtils.waitForTick(); // waiting helps --verify
+ await SpecialPowers.spawn(
+ browser,
+ [COOKIE_STRING, MAX_AGE_NEW],
+ (cookie, max_age) => {
+ content.document.cookie = cookie + ";Max-Age=" + max_age;
+ }
+ );
+ await anotherCookieAdded;
+
+ // ALL_COOKIES_CLEARED/RemoveAll pathway
+ let cleanup = SpecialPowers.spawn(
+ browser2,
+ ["content-removed-all-cookies", ""],
+ waitForNotificationPromise
+ );
+ await TestUtils.waitForTick();
+ Services.cookies.removeAll();
+ await cleanup;
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
diff --git a/netwerk/cookie/test/browser/browser_cookies.js b/netwerk/cookie/test/browser/browser_cookies.js
new file mode 100644
index 0000000000..8a0d8332bf
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_cookies.js
@@ -0,0 +1,53 @@
+"use strict";
+
+CookiePolicyHelper.runTest("document.cookies", {
+ cookieJarAccessAllowed: async _ => {
+ let hasCookie = !!content.document.cookie.length;
+
+ await content
+ .fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(
+ text,
+ hasCookie ? "cookie-present" : "cookie-not-present",
+ "document.cookie is consistent with fetch requests"
+ );
+ });
+
+ content.document.cookie = "name=value";
+ ok(content.document.cookie.includes("name=value"), "Some cookies for me");
+ ok(content.document.cookie.includes("foopy=1"), "Some cookies for me");
+
+ await content
+ .fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-present", "We should have cookies");
+ });
+
+ ok(!!content.document.cookie.length, "Some Cookies for me");
+ },
+
+ cookieJarAccessDenied: async _ => {
+ is(content.document.cookie, "", "No cookies for me");
+ content.document.cookie = "name=value";
+ is(content.document.cookie, "", "No cookies for me");
+
+ await content
+ .fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ // Let's do it twice.
+ await content
+ .fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+
+ is(content.document.cookie, "", "Still no cookies for me");
+ },
+});
diff --git a/netwerk/cookie/test/browser/browser_cookies_ipv6.js b/netwerk/cookie/test/browser/browser_cookies_ipv6.js
new file mode 100644
index 0000000000..088a76f4e8
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_cookies_ipv6.js
@@ -0,0 +1,57 @@
+"use strict";
+
+let { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let gHttpServer = null;
+let ip = "[::1]";
+
+function contentHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = `
+ <!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Cookie ipv6 Test</title>
+ </head>
+ <body>
+ </body>
+ </html>`;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+add_task(async _ => {
+ if (!gHttpServer) {
+ gHttpServer = new HttpServer();
+ gHttpServer.registerPathHandler("/content", contentHandler);
+ gHttpServer._start(-1, ip);
+ }
+
+ registerCleanupFunction(() => {
+ gHttpServer.stop(() => {
+ gHttpServer = null;
+ });
+ });
+
+ let serverPort = gHttpServer.identity.primaryPort;
+ let testURL = `http://${ip}:${serverPort}/content`;
+
+ // Let's open our tab.
+ const tab = BrowserTestUtils.addTab(gBrowser, testURL);
+ gBrowser.selectedTab = tab;
+
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Test if we can set and get document.cookie successfully.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.cookie = "foo=bar";
+ is(content.document.cookie, "foo=bar");
+ });
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/cookie/test/browser/browser_domCache.js b/netwerk/cookie/test/browser/browser_domCache.js
new file mode 100644
index 0000000000..5f1aa84d83
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_domCache.js
@@ -0,0 +1,25 @@
+"use strict";
+
+CookiePolicyHelper.runTest("DOM Cache", {
+ cookieJarAccessAllowed: async w => {
+ await w.caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(false, "DOM Cache can be used!");
+ }
+ );
+ },
+
+ cookieJarAccessDenied: async w => {
+ await w.caches.open("wow").then(
+ _ => {
+ ok(false, "DOM Cache cannot be used!");
+ },
+ _ => {
+ ok(true, "DOM Cache cannot be used!");
+ }
+ );
+ },
+});
diff --git a/netwerk/cookie/test/browser/browser_indexedDB.js b/netwerk/cookie/test/browser/browser_indexedDB.js
new file mode 100644
index 0000000000..7f417077eb
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_indexedDB.js
@@ -0,0 +1,84 @@
+"use strict";
+
+CookiePolicyHelper.runTest("IndexedDB", {
+ cookieJarAccessAllowed: async w => {
+ w.indexedDB.open("test", "1");
+ ok(true, "IDB should be allowed");
+ },
+
+ cookieJarAccessDenied: async w => {
+ try {
+ w.indexedDB.open("test", "1");
+ ok(false, "IDB should be blocked");
+ } catch (e) {
+ ok(true, "IDB should be blocked");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+});
+
+CookiePolicyHelper.runTest("IndexedDB in workers", {
+ cookieJarAccessAllowed: async w => {
+ function nonBlockCode() {
+ indexedDB.open("test", "1");
+ postMessage(true);
+ }
+
+ let blob = new w.Blob([nonBlockCode.toString() + "; nonBlockCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = w.URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new w.Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new w.Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+
+ cookieJarAccessDenied: async w => {
+ function blockCode() {
+ try {
+ indexedDB.open("test", "1");
+ postMessage(false);
+ } catch (e) {
+ postMessage(e.name == "SecurityError");
+ }
+ }
+
+ let blob = new w.Blob([blockCode.toString() + "; blockCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = w.URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new w.Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new w.Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+});
diff --git a/netwerk/cookie/test/browser/browser_originattributes.js b/netwerk/cookie/test/browser/browser_originattributes.js
new file mode 100644
index 0000000000..fab7e67b2e
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_originattributes.js
@@ -0,0 +1,121 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const USER_CONTEXTS = ["default", "personal", "work"];
+
+const COOKIE_NAMES = ["cookie0", "cookie1", "cookie2"];
+
+const TEST_URL =
+ "http://example.com/browser/netwerk/cookie/test/browser/file_empty.html";
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+async function openTabInUserContext(uri, userContextId) {
+ // open the tab in the correct userContextId
+ let tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId });
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ // wait for tab load
+ await BrowserTestUtils.browserLoaded(browser);
+
+ return { tab, browser };
+}
+
+add_setup(async function () {
+ // make sure userContext is enabled.
+ await new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { set: [["privacy.userContext.enabled", true]] },
+ resolve
+ );
+ });
+});
+
+add_task(async function test() {
+ // load the page in 3 different contexts and set a cookie
+ // which should only be visible in that context
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // open our tab in the given user context
+ let { tab, browser } = await openTabInUserContext(TEST_URL, userContextId);
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ names: COOKIE_NAMES, value: USER_CONTEXTS[userContextId] }],
+ function (opts) {
+ for (let name of opts.names) {
+ content.document.cookie = name + "=" + opts.value;
+ }
+ }
+ );
+
+ // remove the tab
+ gBrowser.removeTab(tab);
+ }
+
+ let expectedValues = USER_CONTEXTS.slice(0);
+ await checkCookies(expectedValues, "before removal");
+
+ // remove cookies that belongs to user context id #1
+ Services.cookies.removeCookiesWithOriginAttributes(
+ JSON.stringify({ userContextId: 1 })
+ );
+
+ expectedValues[1] = undefined;
+ await checkCookies(expectedValues, "after removal");
+});
+
+async function checkCookies(expectedValues, time) {
+ for (let userContextId of Object.keys(expectedValues)) {
+ let cookiesFromTitle = await getCookiesFromJS(userContextId);
+ let cookiesFromManager = getCookiesFromManager(userContextId);
+
+ let expectedValue = expectedValues[userContextId];
+ for (let name of COOKIE_NAMES) {
+ is(
+ cookiesFromTitle[name],
+ expectedValue,
+ `User context ${userContextId}: ${name} should be correct from title ${time}`
+ );
+ is(
+ cookiesFromManager[name],
+ expectedValue,
+ `User context ${userContextId}: ${name} should be correct from manager ${time}`
+ );
+ }
+ }
+}
+
+function getCookiesFromManager(userContextId) {
+ let cookies = {};
+ let allCookies = Services.cookies.getCookiesWithOriginAttributes(
+ JSON.stringify({ userContextId })
+ );
+ for (let cookie of allCookies) {
+ cookies[cookie.name] = cookie.value;
+ }
+ return cookies;
+}
+
+async function getCookiesFromJS(userContextId) {
+ let { tab, browser } = await openTabInUserContext(TEST_URL, userContextId);
+
+ // get the cookies
+ let cookieString = await SpecialPowers.spawn(browser, [], function () {
+ return content.document.cookie;
+ });
+
+ // check each item in the title and validate it meets expectatations
+ let cookies = {};
+ for (let cookie of cookieString.split(";")) {
+ let [name, value] = cookie.trim().split("=");
+ cookies[name] = value;
+ }
+
+ gBrowser.removeTab(tab);
+ return cookies;
+}
diff --git a/netwerk/cookie/test/browser/browser_oversize.js b/netwerk/cookie/test/browser/browser_oversize.js
new file mode 100644
index 0000000000..f6e1f8a70b
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_oversize.js
@@ -0,0 +1,96 @@
+"use strict";
+
+const OVERSIZE_DOMAIN = "http://example.com/";
+const OVERSIZE_PATH = "browser/netwerk/cookie/test/browser/";
+const OVERSIZE_TOP_PAGE = OVERSIZE_DOMAIN + OVERSIZE_PATH + "oversize.sjs";
+
+add_task(async _ => {
+ const expected = [];
+
+ const consoleListener = {
+ observe(what) {
+ if (!(what instanceof Ci.nsIConsoleMessage)) {
+ return;
+ }
+
+ info("Console Listener: " + what);
+ for (let i = expected.length - 1; i >= 0; --i) {
+ const e = expected[i];
+
+ if (what.message.includes(e.match)) {
+ ok(true, "Message received: " + e.match);
+ expected.splice(i, 1);
+ e.resolve();
+ }
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+
+ registerCleanupFunction(() =>
+ Services.console.unregisterListener(consoleListener)
+ );
+
+ const netPromises = [
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “a” is invalid because its size is too big. Max size is 4096 B.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “b” is invalid because its path size is too big. Max size is 1024 B.",
+ });
+ }),
+ ];
+
+ // Let's open our tab.
+ const tab = BrowserTestUtils.addTab(gBrowser, OVERSIZE_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Let's wait for the first set of console events.
+ await Promise.all(netPromises);
+
+ // the DOM list of events.
+ const domPromises = [
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “d” is invalid because its size is too big. Max size is 4096 B.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “e” is invalid because its path size is too big. Max size is 1024 B.",
+ });
+ }),
+ ];
+
+ // Let's use document.cookie
+ SpecialPowers.spawn(browser, [], () => {
+ const maxBytesPerCookie = 4096;
+ const maxBytesPerCookiePath = 1024;
+ content.document.cookie = "d=" + Array(maxBytesPerCookie + 1).join("x");
+ content.document.cookie =
+ "e=f; path=/" + Array(maxBytesPerCookiePath + 1).join("x");
+ });
+
+ // Let's wait for the dom events.
+ await Promise.all(domPromises);
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/cookie/test/browser/browser_partitionedConsole.js b/netwerk/cookie/test/browser/browser_partitionedConsole.js
new file mode 100644
index 0000000000..ec834bfbcf
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_partitionedConsole.js
@@ -0,0 +1,201 @@
+"use strict";
+
+const DOMAIN = "https://example.com/";
+const PATH = "browser/netwerk/cookie/test/browser/";
+const TOP_PAGE = DOMAIN + PATH + "file_empty.html";
+
+// Run the test with CHIPS disabled, expecting a warning message
+add_task(async _ => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior.optInPartitioning", false]],
+ });
+
+ const expected = [];
+
+ const consoleListener = {
+ observe(what) {
+ if (!(what instanceof Ci.nsIConsoleMessage)) {
+ return;
+ }
+
+ info("Console Listener: " + what);
+ for (let i = expected.length - 1; i >= 0; --i) {
+ const e = expected[i];
+
+ if (what.message.includes(e.match)) {
+ ok(true, "Message received: " + e.match);
+ expected.splice(i, 1);
+ e.resolve();
+ }
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+
+ registerCleanupFunction(() =>
+ Services.console.unregisterListener(consoleListener)
+ );
+
+ const netPromises = [
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “a” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute.",
+ });
+ }),
+ ];
+
+ // Let's open our tab.
+ const tab = BrowserTestUtils.addTab(gBrowser, TOP_PAGE);
+ gBrowser.selectedTab = tab;
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Set cookies with cross-site HTTP
+ await SpecialPowers.spawn(browser, [], async function () {
+ await content.fetch(
+ "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs",
+ { credentials: "include" }
+ );
+ });
+
+ // Let's wait for the first set of console events.
+ await Promise.all(netPromises);
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Run the test with CHIPS enabled, expecting a different warning message
+add_task(async _ => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior.optInPartitioning", true]],
+ });
+
+ const expected = [];
+
+ const consoleListener = {
+ observe(what) {
+ if (!(what instanceof Ci.nsIConsoleMessage)) {
+ return;
+ }
+
+ info("Console Listener: " + what);
+ for (let i = expected.length - 1; i >= 0; --i) {
+ const e = expected[i];
+
+ if (what.message.includes(e.match)) {
+ ok(true, "Message received: " + e.match);
+ expected.splice(i, 1);
+ e.resolve();
+ }
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+
+ registerCleanupFunction(() =>
+ Services.console.unregisterListener(consoleListener)
+ );
+
+ const netPromises = [
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “a” has been rejected because it is foreign and does not have the “Partitioned“ attribute.",
+ });
+ }),
+ ];
+
+ // Let's open our tab.
+ const tab = BrowserTestUtils.addTab(gBrowser, TOP_PAGE);
+ gBrowser.selectedTab = tab;
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Set cookies with cross-site HTTP
+ await SpecialPowers.spawn(browser, [], async function () {
+ await content.fetch(
+ "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs",
+ { credentials: "include" }
+ );
+ });
+
+ // Let's wait for the first set of console events.
+ await Promise.all(netPromises);
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Run the test with CHIPS enabled, ensuring the partitioned cookies require
+// secure context.
+add_task(async function partitionedAttrRequiresSecure() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior.optInPartitioning", true]],
+ });
+
+ // Clear all cookies before testing.
+ Services.cookies.removeAll();
+
+ const expected = [];
+
+ const consoleListener = {
+ observe(what) {
+ if (!(what instanceof Ci.nsIConsoleMessage)) {
+ return;
+ }
+
+ info("Console Listener: " + what);
+ for (let i = expected.length - 1; i >= 0; --i) {
+ const e = expected[i];
+
+ if (what.message.includes(e.match)) {
+ ok(true, "Message received: " + e.match);
+ expected.splice(i, 1);
+ e.resolve();
+ }
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+
+ registerCleanupFunction(() =>
+ Services.console.unregisterListener(consoleListener)
+ );
+
+ const netPromises = [
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “c” has been rejected because it has the “Partitioned” attribute but is missing the “secure” attribute.",
+ });
+ }),
+ ];
+
+ // Let's open our tab.
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TOP_PAGE);
+
+ // Set cookies with cross-site HTTP
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await content.fetch(
+ "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs?nosecure",
+ { credentials: "include" }
+ );
+ });
+
+ // Let's wait for the first set of console events.
+ await Promise.all(netPromises);
+
+ // Ensure no cookie is set.
+ is(Services.cookies.cookies.length, 0, "No cookie is set.");
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/cookie/test/browser/browser_partitioned_telemetry.js b/netwerk/cookie/test/browser/browser_partitioned_telemetry.js
new file mode 100644
index 0000000000..f89bcdd189
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_partitioned_telemetry.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_URL =
+ "https://example.com/browser/netwerk/cookie/test/browser/file_empty.html";
+
+async function validateTelemetryValues(
+ { setCookies, setForeigns, setPartitioneds, setForeignPartitioneds },
+ message
+) {
+ await Services.fog.testFlushAllChildren();
+ let setCookieTelemetry = Glean.networking.setCookie.testGetValue();
+ is(
+ setCookieTelemetry ?? undefined,
+ setCookies,
+ message + " - all set cookies"
+ );
+ let foreignTelemetry = Glean.networking.setCookieForeign.testGetValue();
+ is(
+ foreignTelemetry?.numerator,
+ setForeigns,
+ message + " - foreign set cookies"
+ );
+ is(
+ foreignTelemetry?.denominator,
+ setCookies,
+ message + " - foreign set cookies denominator"
+ );
+ let partitonedTelemetry =
+ Glean.networking.setCookiePartitioned.testGetValue();
+ is(
+ partitonedTelemetry?.numerator,
+ setPartitioneds,
+ message + " - partitioned set cookies"
+ );
+ is(
+ partitonedTelemetry?.denominator,
+ setCookies,
+ message + " - partitioned set cookies denominator"
+ );
+ let foreignPartitonedTelemetry =
+ Glean.networking.setCookieForeignPartitioned.testGetValue();
+ is(
+ foreignPartitonedTelemetry?.numerator,
+ setForeignPartitioneds,
+ message + " - foreign partitioned set cookies"
+ );
+ is(
+ foreignPartitonedTelemetry?.denominator,
+ setCookies,
+ message + " - foreign partitioned set cookies denominator"
+ );
+}
+
+add_task(async () => {
+ await Services.fog.testFlushAllChildren();
+ Services.fog.testResetFOG();
+ await validateTelemetryValues({}, "initially empty");
+
+ // open a browser window for the test
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_URL);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Set cookies with Javascript
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.cookie = "a=1; Partitioned; SameSite=None; Secure";
+ content.document.cookie = "b=2; SameSite=None; Secure";
+ });
+ await validateTelemetryValues(
+ {
+ setCookies: 2,
+ setForeigns: 0,
+ setPartitioneds: 1,
+ setForeignPartitioneds: 0,
+ },
+ "javascript cookie"
+ );
+
+ // Set cookies with HTTP
+ await SpecialPowers.spawn(browser, [], async function () {
+ await content.fetch("partitioned.sjs");
+ });
+ await validateTelemetryValues(
+ {
+ setCookies: 4,
+ setForeigns: 0,
+ setPartitioneds: 2,
+ setForeignPartitioneds: 0,
+ },
+ "same site fetch"
+ );
+
+ // Set cookies with cross-site HTTP
+ await SpecialPowers.spawn(browser, [], async function () {
+ await content.fetch(
+ "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs",
+ { credentials: "include" }
+ );
+ });
+ await validateTelemetryValues(
+ {
+ setCookies: 6,
+ setForeigns: 2,
+ setPartitioneds: 3,
+ setForeignPartitioneds: 1,
+ },
+ "foreign fetch"
+ );
+
+ // Set cookies with cross-site HTTP redirect
+ await SpecialPowers.spawn(browser, [], async function () {
+ await content.fetch(
+ encodeURI(
+ "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs?redirect=https://example.com/browser/netwerk/cookie/test/browser/partitioned.sjs?nocookie"
+ ),
+ { credentials: "include" }
+ );
+ });
+
+ await validateTelemetryValues(
+ {
+ setCookies: 8,
+ setForeigns: 4,
+ setPartitioneds: 4,
+ setForeignPartitioneds: 2,
+ },
+ "foreign fetch redirect"
+ );
+
+ // remove the tab
+ gBrowser.removeTab(tab);
+});
diff --git a/netwerk/cookie/test/browser/browser_sameSiteConsole.js b/netwerk/cookie/test/browser/browser_sameSiteConsole.js
new file mode 100644
index 0000000000..84527296b2
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_sameSiteConsole.js
@@ -0,0 +1,133 @@
+"use strict";
+
+const SAMESITE_DOMAIN = "http://example.com/";
+const SAMESITE_PATH = "browser/netwerk/cookie/test/browser/";
+const SAMESITE_TOP_PAGE = SAMESITE_DOMAIN + SAMESITE_PATH + "sameSite.sjs";
+
+add_task(async _ => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.sameSite.laxByDefault", true],
+ ["network.cookie.sameSite.noneRequiresSecure", true],
+ ],
+ });
+
+ const expected = [];
+
+ const consoleListener = {
+ observe(what) {
+ if (!(what instanceof Ci.nsIConsoleMessage)) {
+ return;
+ }
+
+ info("Console Listener: " + what);
+ for (let i = expected.length - 1; i >= 0; --i) {
+ const e = expected[i];
+
+ if (what.message.includes(e.match)) {
+ ok(true, "Message received: " + e.match);
+ expected.splice(i, 1);
+ e.resolve();
+ }
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+
+ registerCleanupFunction(() =>
+ Services.console.unregisterListener(consoleListener)
+ );
+
+ const netPromises = [
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “a” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “b” rejected because it has the “SameSite=None” attribute but is missing the “secure” attribute.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Invalid “SameSite“ value for cookie “c”. The supported values are: “Lax“, “Strict“, “None“.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “c” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.",
+ });
+ }),
+ ];
+
+ // Let's open our tab.
+ const tab = BrowserTestUtils.addTab(gBrowser, SAMESITE_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Let's wait for the first set of console events.
+ await Promise.all(netPromises);
+
+ // the DOM list of events.
+ const domPromises = [
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “d” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “e” rejected because it has the “SameSite=None” attribute but is missing the “secure” attribute.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Invalid “SameSite“ value for cookie “f”. The supported values are: “Lax“, “Strict“, “None“.",
+ });
+ }),
+
+ new Promise(resolve => {
+ expected.push({
+ resolve,
+ match:
+ "Cookie “f” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.",
+ });
+ }),
+ ];
+
+ // Let's use document.cookie
+ SpecialPowers.spawn(browser, [], () => {
+ content.document.cookie = "d=4";
+ content.document.cookie = "e=5; sameSite=none";
+ content.document.cookie = "f=6; sameSite=batmat";
+ });
+
+ // Let's wait for the dom events.
+ await Promise.all(domPromises);
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/cookie/test/browser/browser_serviceWorker.js b/netwerk/cookie/test/browser/browser_serviceWorker.js
new file mode 100644
index 0000000000..2a5c963535
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_serviceWorker.js
@@ -0,0 +1,45 @@
+"use strict";
+
+CookiePolicyHelper.runTest("ServiceWorker", {
+ prefs: [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+
+ cookieJarAccessAllowed: async w => {
+ await w.navigator.serviceWorker
+ .register("file_empty.js")
+ .then(
+ reg => {
+ ok(true, "ServiceWorker can be used!");
+ return reg;
+ },
+ _ => {
+ ok(false, "ServiceWorker cannot be used! " + _);
+ }
+ )
+ .then(
+ reg => reg.unregister(),
+ _ => {
+ ok(false, "unregister failed");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+
+ cookieJarAccessDenied: async w => {
+ await w.navigator.serviceWorker
+ .register("file_empty.js")
+ .then(
+ _ => {
+ ok(false, "ServiceWorker cannot be used!");
+ },
+ _ => {
+ ok(true, "ServiceWorker cannot be used!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+});
diff --git a/netwerk/cookie/test/browser/browser_sharedWorker.js b/netwerk/cookie/test/browser/browser_sharedWorker.js
new file mode 100644
index 0000000000..88a8b3f0e7
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_sharedWorker.js
@@ -0,0 +1,18 @@
+"use strict";
+
+CookiePolicyHelper.runTest("SharedWorker", {
+ cookieJarAccessAllowed: async w => {
+ new w.SharedWorker("a.js", "foo");
+ ok(true, "SharedWorker is allowed");
+ },
+
+ cookieJarAccessDenied: async w => {
+ try {
+ new w.SharedWorker("a.js", "foo");
+ ok(false, "SharedWorker cannot be used!");
+ } catch (e) {
+ ok(true, "SharedWorker cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+});
diff --git a/netwerk/cookie/test/browser/browser_storage.js b/netwerk/cookie/test/browser/browser_storage.js
new file mode 100644
index 0000000000..1e37b1a367
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_storage.js
@@ -0,0 +1,43 @@
+"use strict";
+
+CookiePolicyHelper.runTest("SessionStorage", {
+ cookieJarAccessAllowed: async w => {
+ try {
+ w.sessionStorage.foo = 42;
+ ok(true, "SessionStorage works");
+ } catch (e) {
+ ok(false, "SessionStorage works");
+ }
+ },
+
+ cookieJarAccessDenied: async w => {
+ try {
+ w.sessionStorage.foo = 42;
+ ok(false, "SessionStorage doesn't work");
+ } catch (e) {
+ ok(true, "SessionStorage doesn't work");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+});
+
+CookiePolicyHelper.runTest("LocalStorage", {
+ cookieJarAccessAllowed: async w => {
+ try {
+ w.localStorage.foo = 42;
+ ok(true, "LocalStorage works");
+ } catch (e) {
+ ok(false, "LocalStorage works");
+ }
+ },
+
+ cookieJarAccessDenied: async w => {
+ try {
+ w.localStorage.foo = 42;
+ ok(false, "LocalStorage doesn't work");
+ } catch (e) {
+ ok(true, "LocalStorage doesn't work");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+});
diff --git a/netwerk/cookie/test/browser/file_empty.html b/netwerk/cookie/test/browser/file_empty.html
new file mode 100644
index 0000000000..78b64149c4
--- /dev/null
+++ b/netwerk/cookie/test/browser/file_empty.html
@@ -0,0 +1,2 @@
+<html><body>
+</body></html>
diff --git a/netwerk/cookie/test/browser/file_empty.js b/netwerk/cookie/test/browser/file_empty.js
new file mode 100644
index 0000000000..3053583c76
--- /dev/null
+++ b/netwerk/cookie/test/browser/file_empty.js
@@ -0,0 +1 @@
+/* nothing here */
diff --git a/netwerk/cookie/test/browser/head.js b/netwerk/cookie/test/browser/head.js
new file mode 100644
index 0000000000..609ad683db
--- /dev/null
+++ b/netwerk/cookie/test/browser/head.js
@@ -0,0 +1,201 @@
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const BEHAVIOR_ACCEPT = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+const BEHAVIOR_REJECT = Ci.nsICookieService.BEHAVIOR_REJECT;
+
+const PERM_DEFAULT = Ci.nsICookiePermission.ACCESS_DEFAULT;
+const PERM_ALLOW = Ci.nsICookiePermission.ACCESS_ALLOW;
+const PERM_DENY = Ci.nsICookiePermission.ACCESS_DENY;
+
+const TEST_DOMAIN = "https://example.com/";
+const TEST_PATH = "browser/netwerk/cookie/test/browser/";
+const TEST_TOP_PAGE = TEST_DOMAIN + TEST_PATH + "file_empty.html";
+
+// Helper to eval() provided cookieJarAccessAllowed and cookieJarAccessDenied
+// toString()ed optionally async function in freshly created tabs with
+// BEHAVIOR_ACCEPT and BEHAVIOR_REJECT configured, respectively, in a number of
+// permutations. This includes verifying that changing the permission while the
+// page is open still results in the state of the permission when the
+// document/global was created still applying. Code will execute in the
+// ContentTask.spawn frame-script context, use content to access the underlying
+// page.
+this.CookiePolicyHelper = {
+ runTest(testName, config) {
+ // Testing allowed to blocked by cookie behavior
+ this._createTest(
+ testName,
+ config.cookieJarAccessAllowed,
+ config.cookieJarAccessDenied,
+ config.prefs,
+ {
+ fromBehavior: BEHAVIOR_ACCEPT,
+ toBehavior: BEHAVIOR_REJECT,
+ fromPermission: PERM_DEFAULT,
+ toPermission: PERM_DEFAULT,
+ }
+ );
+
+ // Testing blocked to allowed by cookie behavior
+ this._createTest(
+ testName,
+ config.cookieJarAccessDenied,
+ config.cookieJarAccessAllowed,
+ config.prefs,
+ {
+ fromBehavior: BEHAVIOR_REJECT,
+ toBehavior: BEHAVIOR_ACCEPT,
+ fromPermission: PERM_DEFAULT,
+ toPermission: PERM_DEFAULT,
+ }
+ );
+
+ // Testing allowed to blocked by cookie permission
+ this._createTest(
+ testName,
+ config.cookieJarAccessAllowed,
+ config.cookieJarAccessDenied,
+ config.prefs,
+ {
+ fromBehavior: BEHAVIOR_REJECT,
+ toBehavior: BEHAVIOR_REJECT,
+ fromPermission: PERM_ALLOW,
+ toPermission: PERM_DEFAULT,
+ }
+ );
+
+ // Testing blocked to allowed by cookie permission
+ this._createTest(
+ testName,
+ config.cookieJarAccessDenied,
+ config.cookieJarAccessAllowed,
+ config.prefs,
+ {
+ fromBehavior: BEHAVIOR_ACCEPT,
+ toBehavior: BEHAVIOR_ACCEPT,
+ fromPermission: PERM_DENY,
+ toPermission: PERM_DEFAULT,
+ }
+ );
+ },
+
+ _createTest(testName, goodCb, badCb, prefs, config) {
+ add_task(async _ => {
+ info("Starting " + testName + ": " + config.toSource());
+
+ await SpecialPowers.flushPrefEnv();
+
+ if (prefs) {
+ await SpecialPowers.pushPrefEnv({ set: prefs });
+ }
+
+ // Let's set the first cookie pref.
+ PermissionTestUtils.add(TEST_DOMAIN, "cookie", config.fromPermission);
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior", config.fromBehavior]],
+ });
+
+ // Let's open a tab and load content.
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Let's create an iframe.
+ await SpecialPowers.spawn(
+ browser,
+ [{ url: TEST_TOP_PAGE }],
+ async obj => {
+ return new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "iframe");
+ ifr.src = obj.url;
+ ifr.onload = () => resolve();
+ content.document.body.appendChild(ifr);
+ });
+ }
+ );
+
+ // Let's exec the "good" callback.
+ info(
+ "Executing the test after setting the cookie behavior to " +
+ config.fromBehavior +
+ " and permission to " +
+ config.fromPermission
+ );
+ await SpecialPowers.spawn(
+ browser,
+ [{ callback: goodCb.toString() }],
+ async obj => {
+ let runnableStr = `(() => {return (${obj.callback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ await runnable(content);
+
+ let ifr = content.document.getElementById("iframe");
+ await runnable(ifr.contentWindow);
+ }
+ );
+
+ // Now, let's change the cookie settings
+ PermissionTestUtils.add(TEST_DOMAIN, "cookie", config.toPermission);
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior", config.toBehavior]],
+ });
+
+ // We still want the good callback to succeed.
+ info(
+ "Executing the test after setting the cookie behavior to " +
+ config.toBehavior +
+ " and permission to " +
+ config.toPermission
+ );
+ await SpecialPowers.spawn(
+ browser,
+ [{ callback: goodCb.toString() }],
+ async obj => {
+ let runnableStr = `(() => {return (${obj.callback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ await runnable(content);
+
+ let ifr = content.document.getElementById("iframe");
+ await runnable(ifr.contentWindow);
+ }
+ );
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+
+ // Let's open a new tab and load content again.
+ tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Let's exec the "bad" callback.
+ info("Executing the test in a new tab");
+ await SpecialPowers.spawn(
+ browser,
+ [{ callback: badCb.toString() }],
+ async obj => {
+ let runnableStr = `(() => {return (${obj.callback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ await runnable(content);
+ }
+ );
+
+ // Let's close the tab.
+ BrowserTestUtils.removeTab(tab);
+
+ // Cleanup.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_ALL,
+ resolve
+ );
+ });
+ });
+ },
+};
diff --git a/netwerk/cookie/test/browser/oversize.sjs b/netwerk/cookie/test/browser/oversize.sjs
new file mode 100644
index 0000000000..dfe2f31645
--- /dev/null
+++ b/netwerk/cookie/test/browser/oversize.sjs
@@ -0,0 +1,17 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ const maxBytesPerCookie = 4096;
+ const maxBytesPerCookiePath = 1024;
+
+ aResponse.setHeader(
+ "Set-Cookie",
+ "a=" + Array(maxBytesPerCookie + 1).join("x"),
+ true
+ );
+ aResponse.setHeader(
+ "Set-Cookie",
+ "b=c; path=/" + Array(maxBytesPerCookiePath + 1).join("x"),
+ true
+ );
+}
diff --git a/netwerk/cookie/test/browser/partitioned.sjs b/netwerk/cookie/test/browser/partitioned.sjs
new file mode 100644
index 0000000000..5649b88f2f
--- /dev/null
+++ b/netwerk/cookie/test/browser/partitioned.sjs
@@ -0,0 +1,32 @@
+function handleRequest(aRequest, aResponse) {
+ if (aRequest.hasHeader("Origin")) {
+ let origin = aRequest.getHeader("Origin");
+ aResponse.setHeader("Access-Control-Allow-Origin", origin);
+ aResponse.setHeader("Access-Control-Allow-Credentials", "true");
+ }
+
+ var params = new URLSearchParams(aRequest.queryString);
+ if (params.has("redirect")) {
+ aResponse.setHeader("Location", params.get("redirect"));
+ aResponse.setStatusLine(aRequest.httpVersion, 302);
+ } else {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ }
+
+ if (params.has("nocookie")) {
+ return;
+ }
+
+ if (params.has("nosecure")) {
+ aResponse.setHeader("Set-Cookie", "c=3; Partitioned;", true);
+
+ return;
+ }
+
+ aResponse.setHeader("Set-Cookie", "a=1; SameSite=None; Secure", true);
+ aResponse.setHeader(
+ "Set-Cookie",
+ "b=2; Partitioned; SameSite=None; Secure",
+ true
+ );
+}
diff --git a/netwerk/cookie/test/browser/sameSite.sjs b/netwerk/cookie/test/browser/sameSite.sjs
new file mode 100644
index 0000000000..a19624d2cb
--- /dev/null
+++ b/netwerk/cookie/test/browser/sameSite.sjs
@@ -0,0 +1,7 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ aResponse.setHeader("Set-Cookie", "a=1", true);
+ aResponse.setHeader("Set-Cookie", "b=2; sameSite=none", true);
+ aResponse.setHeader("Set-Cookie", "c=3; sameSite=batman", true);
+}
diff --git a/netwerk/cookie/test/browser/server.sjs b/netwerk/cookie/test/browser/server.sjs
new file mode 100644
index 0000000000..86835914bb
--- /dev/null
+++ b/netwerk/cookie/test/browser/server.sjs
@@ -0,0 +1,9 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ if (aRequest.hasHeader("Cookie")) {
+ aResponse.write("cookie-present");
+ } else {
+ aResponse.setHeader("Set-Cookie", "foopy=1");
+ aResponse.write("cookie-not-present");
+ }
+}
diff --git a/netwerk/cookie/test/mochitest/cookie.sjs b/netwerk/cookie/test/mochitest/cookie.sjs
new file mode 100644
index 0000000000..75d4e638b4
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/cookie.sjs
@@ -0,0 +1,166 @@
+function handleRequest(aRequest, aResponse) {
+ let parts = aRequest.queryString.split("&");
+ if (parts.includes("window")) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ aResponse.setHeader("Content-Type", "text/html");
+ aResponse.setHeader("Clear-Site-Data", '"cache", "cookies", "storage"');
+ aResponse.write("<body><h1>Welcome</h1></body>");
+ return;
+ }
+
+ if (parts.includes("fetch")) {
+ setState(
+ "data",
+ JSON.stringify({ type: "fetch", hasCookie: aRequest.hasHeader("Cookie") })
+ );
+ aResponse.write("Hello world!");
+ return;
+ }
+
+ if (parts.includes("xhr")) {
+ setState(
+ "data",
+ JSON.stringify({ type: "xhr", hasCookie: aRequest.hasHeader("Cookie") })
+ );
+ aResponse.write("Hello world!");
+ return;
+ }
+
+ if (parts.includes("image")) {
+ setState(
+ "data",
+ JSON.stringify({ type: "image", hasCookie: aRequest.hasHeader("Cookie") })
+ );
+
+ // A 1x1 PNG image.
+ // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+ const IMAGE = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+ );
+
+ aResponse.setHeader("Content-Type", "image/png", false);
+ aResponse.write(IMAGE);
+ return;
+ }
+
+ if (parts.includes("script")) {
+ setState(
+ "data",
+ JSON.stringify({
+ type: "script",
+ hasCookie: aRequest.hasHeader("Cookie"),
+ })
+ );
+
+ aResponse.setHeader("Content-Type", "text/javascript", false);
+ aResponse.write("window.scriptLoaded();");
+ return;
+ }
+
+ if (parts.includes("worker")) {
+ setState(
+ "data",
+ JSON.stringify({
+ type: "worker",
+ hasCookie: aRequest.hasHeader("Cookie"),
+ })
+ );
+
+ function w() {
+ onmessage = e => {
+ if (e.data == "subworker") {
+ importScripts("cookie.sjs?subworker&" + Math.random());
+ postMessage(42);
+ return;
+ }
+
+ if (e.data == "fetch") {
+ fetch("cookie.sjs?fetch&" + Math.random())
+ .then(r => r.text())
+ .then(_ => postMessage(42));
+ return;
+ }
+
+ if (e.data == "xhr") {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "cookie.sjs?xhr&" + Math.random());
+ xhr.send();
+ xhr.onload = _ => postMessage(42);
+ }
+ };
+ postMessage(42);
+ }
+
+ aResponse.setHeader("Content-Type", "text/javascript", false);
+ aResponse.write(w.toString() + "; w();");
+ return;
+ }
+
+ if (parts.includes("subworker")) {
+ setState(
+ "data",
+ JSON.stringify({
+ type: "subworker",
+ hasCookie: aRequest.hasHeader("Cookie"),
+ })
+ );
+ aResponse.setHeader("Content-Type", "text/javascript", false);
+ aResponse.write("42");
+ return;
+ }
+
+ if (parts.includes("sharedworker")) {
+ setState(
+ "data",
+ JSON.stringify({
+ type: "sharedworker",
+ hasCookie: aRequest.hasHeader("Cookie"),
+ })
+ );
+
+ // This function is exported as a string.
+ /* eslint-disable no-undef */
+ function w() {
+ onconnect = e => {
+ e.ports[0].onmessage = evt => {
+ if (evt.data == "subworker") {
+ importScripts("cookie.sjs?subworker&" + Math.random());
+ e.ports[0].postMessage(42);
+ return;
+ }
+
+ if (evt.data == "fetch") {
+ fetch("cookie.sjs?fetch&" + Math.random())
+ .then(r => r.text())
+ .then(_ => e.ports[0].postMessage(42));
+ return;
+ }
+
+ if (evt.data == "xhr") {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "cookie.sjs?xhr&" + Math.random());
+ xhr.send();
+ xhr.onload = _ => e.ports[0].postMessage(42);
+ }
+ };
+ e.ports[0].postMessage(42);
+ };
+ }
+ /* eslint-enable no-undef */
+
+ aResponse.setHeader("Content-Type", "text/javascript", false);
+ aResponse.write(w.toString() + "; w();");
+ return;
+ }
+
+ if (parts.includes("last")) {
+ let data = getState("data");
+ setState("data", "");
+ aResponse.write(data);
+ return;
+ }
+
+ aResponse.setStatusLine(aRequest.httpVersion, 400);
+ aResponse.write("Invalid request");
+}
diff --git a/netwerk/cookie/test/mochitest/cookiesHelper.js b/netwerk/cookie/test/mochitest/cookiesHelper.js
new file mode 100644
index 0000000000..cbff91f2f2
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/cookiesHelper.js
@@ -0,0 +1,63 @@
+const ALLOWED = 0;
+const BLOCKED = 1;
+
+async function cleanupData() {
+ await new Promise(resolve => {
+ const chromeScript = SpecialPowers.loadChromeScript(_ => {
+ /* eslint-env mozilla/chrome-script */
+ addMessageListener("go", __ => {
+ Services.clearData.deleteData(
+ Services.clearData.CLEAR_COOKIES |
+ Services.clearData.CLEAR_ALL_CACHES |
+ Services.clearData.CLEAR_DOM_STORAGES,
+ ___ => {
+ sendAsyncMessage("done");
+ }
+ );
+ });
+ });
+
+ chromeScript.addMessageListener("done", _ => {
+ chromeScript.destroy();
+ resolve();
+ });
+
+ chromeScript.sendAsyncMessage("go");
+ });
+}
+
+async function checkLastRequest(type, state) {
+ let json = await fetch("cookie.sjs?last&" + Math.random()).then(r =>
+ r.json()
+ );
+ is(json.type, type, "Type: " + type);
+ is(json.hasCookie, state == ALLOWED, "Fetch has cookies");
+}
+
+async function runTests(currentTest) {
+ await cleanupData();
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior", 2]],
+ });
+ let windowBlocked = window.open("cookie.sjs?window&" + Math.random());
+ await new Promise(resolve => {
+ windowBlocked.onload = resolve;
+ });
+ await currentTest(windowBlocked, BLOCKED);
+ windowBlocked.close();
+
+ await cleanupData();
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior", 1]],
+ });
+ let windowAllowed = window.open("cookie.sjs?window&" + Math.random());
+ await new Promise(resolve => {
+ windowAllowed.onload = resolve;
+ });
+ await currentTest(windowAllowed, ALLOWED);
+ windowAllowed.close();
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
diff --git a/netwerk/cookie/test/mochitest/empty.html b/netwerk/cookie/test/mochitest/empty.html
new file mode 100644
index 0000000000..cd161cc52d
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/empty.html
@@ -0,0 +1 @@
+<h1>Nothing here</h1>
diff --git a/netwerk/cookie/test/mochitest/mochitest.toml b/netwerk/cookie/test/mochitest/mochitest.toml
new file mode 100644
index 0000000000..fdf26a7e94
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/mochitest.toml
@@ -0,0 +1,27 @@
+[DEFAULT]
+scheme = "https"
+support-files = [
+ "cookie.sjs",
+ "cookiesHelper.js",
+]
+
+["test_document_cookie.html"]
+
+["test_document_cookie_notification.html"]
+
+["test_fetch.html"]
+
+["test_image.html"]
+
+["test_metaTag.html"]
+
+["test_script.html"]
+
+["test_sharedWorker.html"]
+
+["test_worker.html"]
+
+["test_xhr.html"]
+
+["test_xmlDocument.html"]
+support-files = ["empty.html"]
diff --git a/netwerk/cookie/test/mochitest/test_document_cookie.html b/netwerk/cookie/test/mochitest/test_document_cookie.html
new file mode 100644
index 0000000000..86e7c7f661
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_document_cookie.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for document.cookie when the policy changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="cookiesHelper.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+runTests(async (w, state) => {
+ is(w.document.cookie.length, 0, "No cookie to start!");
+ w.document.cookie = "name=value";
+ is(w.document.cookie.includes("name=value"), state == ALLOWED, "Some cookies for me");
+});
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_document_cookie_notification.html b/netwerk/cookie/test/mochitest/test_document_cookie_notification.html
new file mode 100644
index 0000000000..b84b6ed045
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_document_cookie_notification.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for document.cookie setter + notification</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function Listener() {
+ SpecialPowers.addObserver(this, "document-set-cookie");
+}
+
+Listener.prototype = {
+ observe(aSubject, aTopic, aData) {
+ is(aTopic, "document-set-cookie", "Notification received");
+ ok(aData.startsWith("a="), "Right cookie received");
+
+ SpecialPowers.removeObserver(this, "document-set-cookie");
+ SimpleTest.finish();
+ }
+}
+
+const cl = new Listener();
+document.cookie = "a=" + Math.random();
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_fetch.html b/netwerk/cookie/test/mochitest/test_fetch.html
new file mode 100644
index 0000000000..315d0d7624
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_fetch.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for cookies + fetch when the policy changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="cookiesHelper.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+runTests(async (w, state) => {
+ w.document.cookie = "name=value";
+ await w.fetch("cookie.sjs?fetch&" + Math.random()).then(r => r.text());
+ await checkLastRequest("fetch", state);
+});
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_image.html b/netwerk/cookie/test/mochitest/test_image.html
new file mode 100644
index 0000000000..4a49d64169
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_image.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for cookies and image loading when the policy changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="cookiesHelper.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+runTests(async (w, state) => {
+ w.document.cookie = "name=value";
+
+ let image = new w.Image();
+ image.src = "cookie.sjs?image&" + Math.random();
+ w.document.body.appendChild(image);
+ await new w.Promise(resolve => { image.onload = resolve; });
+ await checkLastRequest("image", state);
+});
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_metaTag.html b/netwerk/cookie/test/mochitest/test_metaTag.html
new file mode 100644
index 0000000000..48d360d5b2
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_metaTag.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for meta tag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript">
+
+document.addEventListener("DOMContentLoaded", _ => {
+ try {
+ document.write('<meta content=a http-equiv="set-cookie">');
+ } catch (e) {}
+
+ ok(true, "No crash!");
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_script.html b/netwerk/cookie/test/mochitest/test_script.html
new file mode 100644
index 0000000000..9f4b9f846d
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_script.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for cookies + script loading when the policy changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="cookiesHelper.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+runTests(async (w, state) => {
+ w.document.cookie = "name=value";
+
+ let p = new w.Promise(resolve => { w.scriptLoaded = resolve; });
+ let script = document.createElement("script");
+ script.src = "cookie.sjs?script&" + Math.random();
+ w.document.body.appendChild(script);
+ await p;
+ await checkLastRequest("script", state);
+});
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_sharedWorker.html b/netwerk/cookie/test/mochitest/test_sharedWorker.html
new file mode 100644
index 0000000000..c29bf86a88
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_sharedWorker.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for cookies + SharedWorker loading when the policy changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="cookiesHelper.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+runTests(async (w, state) => {
+ w.document.cookie = "name=value";
+
+ if (state == BLOCKED) {
+ try {
+ new w.SharedWorker("cookie.sjs?sharedworker&" + Math.random());
+ ok(false, "SharedWorker should not be allowed!");
+ } catch (ex) {
+ ok(true, "SharedWorker should not be allowed!");
+ }
+ return;
+ }
+
+ let p = new w.SharedWorker("cookie.sjs?sharedworker&" + Math.random());
+ await new w.Promise(resolve => { p.port.onmessage = resolve; });
+ await checkLastRequest("sharedworker", state);
+
+ await new w.Promise(resolve => {
+ p.port.postMessage("subworker");
+ p.port.onmessage = resolve;
+ });
+ await checkLastRequest("subworker", state);
+
+ await new w.Promise(resolve => {
+ p.port.postMessage("fetch");
+ p.port.onmessage = resolve;
+ });
+ await checkLastRequest("fetch", state);
+
+ await new w.Promise(resolve => {
+ p.port.postMessage("xhr");
+ p.port.onmessage = resolve;
+ });
+ await checkLastRequest("xhr", state);
+});
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_worker.html b/netwerk/cookie/test/mochitest/test_worker.html
new file mode 100644
index 0000000000..37ab222bce
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_worker.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for cookies + worker loading when the policy changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="cookiesHelper.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+runTests(async (w, state) => {
+ w.document.cookie = "name=value";
+
+ let p = new w.Worker("cookie.sjs?worker&" + Math.random());
+ await new w.Promise(resolve => { p.onmessage = resolve; });
+ await checkLastRequest("worker", state);
+
+ await new w.Promise(resolve => { p.postMessage("subworker"); p.onmessage = resolve; });
+ await checkLastRequest("subworker", state);
+
+ await new w.Promise(resolve => { p.postMessage("fetch"); p.onmessage = resolve; });
+ await checkLastRequest("fetch", state);
+
+ await new w.Promise(resolve => { p.postMessage("xhr"); p.onmessage = resolve; });
+ await checkLastRequest("xhr", state);
+});
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_xhr.html b/netwerk/cookie/test/mochitest/test_xhr.html
new file mode 100644
index 0000000000..d00b5690f8
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_xhr.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for cookies + XHR when the policy changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="cookiesHelper.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+runTests(async (w, state) => {
+ w.document.cookie = "name=value";
+ await new w.Promise(resolve => {
+ let xhr = new w.XMLHttpRequest();
+ xhr.open("GET", "cookie.sjs?xhr&" + Math.random());
+ xhr.send();
+ xhr.onload = resolve;
+ });
+ await checkLastRequest("xhr", state);
+});
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/mochitest/test_xmlDocument.html b/netwerk/cookie/test/mochitest/test_xmlDocument.html
new file mode 100644
index 0000000000..91417c98c4
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/test_xmlDocument.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Document constructor</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript">
+
+let w;
+
+SpecialPowers.pushPrefEnv({set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.prompt.testing", true],
+ ["dom.storage_access.prompt.testing.allow", true],
+ ["dom.testing.sync-content-blocking-notifications", true],
+ ["network.cookie.cookieBehavior", 0],
+]}).then(_ => {
+ return new Promise(resolve => {
+ w = window.open("empty.html");
+ w.onload = resolve;
+ });
+}).then(_ => {
+ const doc = new w.Document();
+ return doc.requestStorageAccess().catch(__ => {});
+}).then(___ => {
+ w.close();
+ ok(true, "No crash!");
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js b/netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js
new file mode 100644
index 0000000000..94f01b778e
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js
@@ -0,0 +1,105 @@
+"use strict";
+
+add_task(async () => {
+ const HOST = "www.bbc.co.uk";
+ Assert.equal(
+ Services.eTLD.getBaseDomainFromHost(HOST),
+ "bbc.co.uk",
+ "Sanity check: HOST is an eTLD + 1 with subdomain"
+ );
+
+ const tests = [
+ {
+ // Correct baseDomain: eTLD + 1.
+ baseDomain: "bbc.co.uk",
+ name: "originally_bbc_co_uk",
+ },
+ {
+ // Incorrect baseDomain: Part of public suffix list.
+ baseDomain: "uk",
+ name: "originally_uk",
+ },
+ {
+ // Incorrect baseDomain: Part of public suffix list.
+ baseDomain: "co.uk",
+ name: "originally_co_uk",
+ },
+ {
+ // Incorrect baseDomain: eTLD + 2.
+ baseDomain: "www.bbc.co.uk",
+ name: "originally_www_bbc_co_uk",
+ },
+ ];
+
+ do_get_profile();
+
+ let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dbFile.append("cookies.sqlite");
+ let conn = Services.storage.openDatabase(dbFile);
+
+ conn.schemaVersion = 10;
+ conn.executeSimpleSQL("DROP TABLE IF EXISTS moz_cookies");
+ conn.executeSimpleSQL(
+ "CREATE TABLE moz_cookies (" +
+ "id INTEGER PRIMARY KEY, " +
+ "baseDomain TEXT, " +
+ "originAttributes TEXT NOT NULL DEFAULT '', " +
+ "name TEXT, " +
+ "value TEXT, " +
+ "host TEXT, " +
+ "path TEXT, " +
+ "expiry INTEGER, " +
+ "lastAccessed INTEGER, " +
+ "creationTime INTEGER, " +
+ "isSecure INTEGER, " +
+ "isHttpOnly INTEGER, " +
+ "inBrowserElement INTEGER DEFAULT 0, " +
+ "sameSite INTEGER DEFAULT 0, " +
+ "rawSameSite INTEGER DEFAULT 0, " +
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" +
+ ")"
+ );
+
+ function addCookie(baseDomain, host, name) {
+ conn.executeSimpleSQL(
+ "INSERT INTO moz_cookies(" +
+ "baseDomain, host, name, value, path, expiry, " +
+ "lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (" +
+ `'${baseDomain}', '${host}', '${name}', 'thevalue', '/', ` +
+ (Date.now() + 3600000) +
+ "," +
+ Date.now() +
+ "," +
+ Date.now() +
+ ", 1, 1)"
+ );
+ }
+
+ // Prepare the database.
+ for (let { baseDomain, name } of tests) {
+ addCookie(baseDomain, HOST, name);
+ }
+ // Domain cookies are not supported for IP addresses.
+ addCookie("127.0.0.1", ".127.0.0.1", "invalid_host");
+ conn.close();
+
+ let cs = Services.cookies;
+
+ // Count excludes the invalid_host cookie.
+ Assert.equal(cs.cookies.length, tests.length, "Expected number of cookies");
+
+ // Check whether the database has the expected value,
+ // despite the incorrect baseDomain.
+ for (let { name } of tests) {
+ Assert.ok(
+ cs.cookieExists(HOST, "/", name, {}),
+ "Should find cookie with name: " + name
+ );
+ }
+
+ Assert.equal(
+ cs.cookieExists("127.0.0.1", "/", "invalid_host", {}),
+ false,
+ "Should ignore database row with invalid host name"
+ );
+});
diff --git a/netwerk/cookie/test/unit/test_bug1155169.js b/netwerk/cookie/test/unit/test_bug1155169.js
new file mode 100644
index 0000000000..2bf8bd768d
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug1155169.js
@@ -0,0 +1,96 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+const URI = Services.io.newURI("http://example.org/");
+
+const { COOKIE_CHANGED, COOKIE_ADDED } = Ci.nsICookieNotification;
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // Clear cookies.
+ Services.cookies.removeAll();
+
+ // Add a new cookie.
+ setCookie("foo=bar", {
+ type: COOKIE_ADDED,
+ isSession: true,
+ isSecure: false,
+ isHttpOnly: false,
+ });
+
+ // Update cookie with isHttpOnly=true.
+ setCookie("foo=bar; HttpOnly", {
+ type: COOKIE_CHANGED,
+ isSession: true,
+ isSecure: false,
+ isHttpOnly: true,
+ });
+
+ // Update cookie with isSecure=true.
+ setCookie("foo=bar; Secure", {
+ type: COOKIE_CHANGED,
+ isSession: true,
+ isSecure: true,
+ isHttpOnly: false,
+ });
+
+ // Update cookie with isSession=false.
+ let expiry = new Date();
+ expiry.setUTCFullYear(expiry.getUTCFullYear() + 2);
+ setCookie(`foo=bar; Expires=${expiry.toGMTString()}`, {
+ type: COOKIE_CHANGED,
+ isSession: false,
+ isSecure: false,
+ isHttpOnly: false,
+ });
+
+ // Reset cookie.
+ setCookie("foo=bar", {
+ type: COOKIE_CHANGED,
+ isSession: true,
+ isSecure: false,
+ isHttpOnly: false,
+ });
+}
+
+function setCookie(value, expected) {
+ function setCookieInternal(valueInternal, expectedInternal = null) {
+ function observer(subject) {
+ if (!expectedInternal) {
+ do_throw("no notification expected");
+ return;
+ }
+
+ let notification = subject.QueryInterface(Ci.nsICookieNotification);
+
+ // Check we saw the right notification.
+ Assert.equal(notification.action, expectedInternal.type);
+
+ // Check cookie details.
+ let cookie = notification.cookie.QueryInterface(Ci.nsICookie);
+ Assert.equal(cookie.isSession, expectedInternal.isSession);
+ Assert.equal(cookie.isSecure, expectedInternal.isSecure);
+ Assert.equal(cookie.isHttpOnly, expectedInternal.isHttpOnly);
+ }
+
+ Services.obs.addObserver(observer, "cookie-changed");
+
+ let channel = NetUtil.newChannel({
+ uri: URI,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ Services.cookies.setCookieStringFromHttp(URI, valueInternal, channel);
+ Services.obs.removeObserver(observer, "cookie-changed");
+ }
+
+ // Check that updating/inserting the cookie works.
+ setCookieInternal(value, expected);
+
+ // Check that we ignore identical cookies.
+ setCookieInternal(value);
+}
diff --git a/netwerk/cookie/test/unit/test_bug1321912.js b/netwerk/cookie/test/unit/test_bug1321912.js
new file mode 100644
index 0000000000..fd24f15bbf
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug1321912.js
@@ -0,0 +1,99 @@
+do_get_profile();
+const dirSvc = Services.dirsvc;
+
+let dbFile = dirSvc.get("ProfD", Ci.nsIFile);
+dbFile.append("cookies.sqlite");
+
+let storage = Services.storage;
+let properties = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag
+);
+properties.setProperty("shared", true);
+let conn = storage.openDatabase(dbFile);
+
+// Write the schema v7 to the database.
+conn.schemaVersion = 7;
+conn.executeSimpleSQL(
+ "CREATE TABLE moz_cookies (" +
+ "id INTEGER PRIMARY KEY, " +
+ "baseDomain TEXT, " +
+ "originAttributes TEXT NOT NULL DEFAULT '', " +
+ "name TEXT, " +
+ "value TEXT, " +
+ "host TEXT, " +
+ "path TEXT, " +
+ "expiry INTEGER, " +
+ "lastAccessed INTEGER, " +
+ "creationTime INTEGER, " +
+ "isSecure INTEGER, " +
+ "isHttpOnly INTEGER, " +
+ "appId INTEGER DEFAULT 0, " +
+ "inBrowserElement INTEGER DEFAULT 0, " +
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" +
+ ")"
+);
+conn.executeSimpleSQL(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " +
+ "originAttributes)"
+);
+
+conn.executeSimpleSQL("PRAGMA synchronous = OFF");
+conn.executeSimpleSQL("PRAGMA journal_mode = WAL");
+conn.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+
+let now = Date.now();
+conn.executeSimpleSQL(
+ "INSERT INTO moz_cookies(" +
+ "baseDomain, host, name, value, path, expiry, " +
+ "lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (" +
+ "'foo.com', '.foo.com', 'foo', 'bar=baz', '/', " +
+ now +
+ ", " +
+ now +
+ ", " +
+ now +
+ ", 1, 1)"
+);
+
+// Now start the cookie service, and then check the fields in the table.
+// Get sessionCookies to wait for the initialization in cookie thread
+Services.cookies.sessionCookies;
+
+Assert.equal(conn.schemaVersion, 13);
+let stmt = conn.createStatement(
+ "SELECT sql FROM sqlite_master " +
+ "WHERE type = 'table' AND " +
+ " name = 'moz_cookies'"
+);
+try {
+ Assert.ok(stmt.executeStep());
+ let sql = stmt.getString(0);
+ Assert.equal(sql.indexOf("appId"), -1);
+} finally {
+ stmt.finalize();
+}
+
+stmt = conn.createStatement(
+ "SELECT * FROM moz_cookies " +
+ "WHERE host = '.foo.com' AND " +
+ " name = 'foo' AND " +
+ " value = 'bar=baz' AND " +
+ " path = '/' AND " +
+ " expiry = " +
+ now +
+ " AND " +
+ " lastAccessed = " +
+ now +
+ " AND " +
+ " creationTime = " +
+ now +
+ " AND " +
+ " isSecure = 1 AND " +
+ " isHttpOnly = 1"
+);
+try {
+ Assert.ok(stmt.executeStep());
+} finally {
+ stmt.finalize();
+}
+conn.close();
diff --git a/netwerk/cookie/test/unit/test_bug643051.js b/netwerk/cookie/test/unit/test_bug643051.js
new file mode 100644
index 0000000000..35b37a5889
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug643051.js
@@ -0,0 +1,43 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+CookieXPCShellUtils.init(this);
+CookieXPCShellUtils.createServer({ hosts: ["example.net"] });
+
+add_task(async () => {
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ let uri = NetUtil.newURI("http://example.org/");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ let set = "foo=bar\nbaz=foo";
+ let expected = "foo=bar; baz=foo";
+ Services.cookies.setCookieStringFromHttp(uri, set, channel);
+
+ let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
+ Assert.equal(actual, expected);
+
+ await CookieXPCShellUtils.setCookieToDocument("http://example.net/", set);
+ actual = await CookieXPCShellUtils.getCookieStringFromDocument(
+ "http://example.net/"
+ );
+
+ expected = "foo=bar";
+ Assert.equal(actual, expected);
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/cookie/test/unit/test_eviction.js b/netwerk/cookie/test/unit/test_eviction.js
new file mode 100644
index 0000000000..8c0073a107
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_eviction.js
@@ -0,0 +1,199 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+const BASE_HOST = "example.org";
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+CookieXPCShellUtils.init(this);
+CookieXPCShellUtils.createServer({ hosts: ["example.org"] });
+
+add_task(async function test_basic_eviction() {
+ do_get_profile();
+
+ Services.prefs.setIntPref("network.cookie.staleThreshold", 0);
+ Services.prefs.setIntPref("network.cookie.quotaPerHost", 2);
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 5);
+
+ // We don't want to have CookieJarSettings blocking this test.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ const BASE_URI = Services.io.newURI("http://" + BASE_HOST);
+ const FOO_PATH = Services.io.newURI("http://" + BASE_HOST + "/foo/");
+ const BAR_PATH = Services.io.newURI("http://" + BASE_HOST + "/bar/");
+
+ await setCookie("session_foo_path_1", null, "/foo", null, FOO_PATH);
+ await setCookie("session_foo_path_2", null, "/foo", null, FOO_PATH);
+ await setCookie("session_foo_path_3", null, "/foo", null, FOO_PATH);
+ await setCookie("session_foo_path_4", null, "/foo", null, FOO_PATH);
+ await setCookie("session_foo_path_5", null, "/foo", null, FOO_PATH);
+ verifyCookies(
+ [
+ "session_foo_path_1",
+ "session_foo_path_2",
+ "session_foo_path_3",
+ "session_foo_path_4",
+ "session_foo_path_5",
+ ],
+ BASE_URI
+ );
+
+ // Check if cookies are evicted by creation time.
+ await setCookie("session_foo_path_6", null, "/foo", null, FOO_PATH);
+ verifyCookies(
+ ["session_foo_path_4", "session_foo_path_5", "session_foo_path_6"],
+ BASE_URI
+ );
+
+ await setCookie("session_bar_path_1", null, "/bar", null, BAR_PATH);
+ await setCookie("session_bar_path_2", null, "/bar", null, BAR_PATH);
+
+ verifyCookies(
+ [
+ "session_foo_path_4",
+ "session_foo_path_5",
+ "session_foo_path_6",
+ "session_bar_path_1",
+ "session_bar_path_2",
+ ],
+ BASE_URI
+ );
+
+ // Check if cookies are evicted by last accessed time.
+ await CookieXPCShellUtils.getCookieStringFromDocument(FOO_PATH.spec);
+
+ await setCookie("session_foo_path_7", null, "/foo", null, FOO_PATH);
+ verifyCookies(
+ ["session_foo_path_5", "session_foo_path_6", "session_foo_path_7"],
+ BASE_URI
+ );
+
+ const EXPIRED_TIME = 3;
+
+ await setCookie(
+ "non_session_expired_foo_path_1",
+ null,
+ "/foo",
+ EXPIRED_TIME,
+ FOO_PATH
+ );
+ await setCookie(
+ "non_session_expired_foo_path_2",
+ null,
+ "/foo",
+ EXPIRED_TIME,
+ FOO_PATH
+ );
+ verifyCookies(
+ [
+ "session_foo_path_5",
+ "session_foo_path_6",
+ "session_foo_path_7",
+ "non_session_expired_foo_path_1",
+ "non_session_expired_foo_path_2",
+ ],
+ BASE_URI
+ );
+
+ // Check if expired cookies are evicted first.
+ await new Promise(resolve => do_timeout(EXPIRED_TIME * 1000, resolve));
+ await setCookie("session_foo_path_8", null, "/foo", null, FOO_PATH);
+ verifyCookies(
+ ["session_foo_path_6", "session_foo_path_7", "session_foo_path_8"],
+ BASE_URI
+ );
+
+ Services.cookies.removeAll();
+});
+
+// Verify that the given cookie names exist, and are ordered from least to most recently accessed
+function verifyCookies(names, uri) {
+ Assert.equal(Services.cookies.countCookiesFromHost(uri.host), names.length);
+ let actual_cookies = [];
+ for (let cookie of Services.cookies.getCookiesFromHost(uri.host, {})) {
+ actual_cookies.push(cookie);
+ }
+ if (names.length != actual_cookies.length) {
+ let left = names.filter(function (n) {
+ return (
+ actual_cookies.findIndex(function (c) {
+ return c.name == n;
+ }) == -1
+ );
+ });
+ let right = actual_cookies
+ .filter(function (c) {
+ return (
+ names.findIndex(function (n) {
+ return c.name == n;
+ }) == -1
+ );
+ })
+ .map(function (c) {
+ return c.name;
+ });
+ if (left.length) {
+ info("unexpected cookies: " + left);
+ }
+ if (right.length) {
+ info("expected cookies: " + right);
+ }
+ }
+ Assert.equal(names.length, actual_cookies.length);
+ actual_cookies.sort(function (a, b) {
+ if (a.lastAccessed < b.lastAccessed) {
+ return -1;
+ }
+ if (a.lastAccessed > b.lastAccessed) {
+ return 1;
+ }
+ return 0;
+ });
+ for (var i = 0; i < names.length; i++) {
+ Assert.equal(names[i], actual_cookies[i].name);
+ Assert.equal(names[i].startsWith("session"), actual_cookies[i].isSession);
+ }
+}
+
+var lastValue = 0;
+function setCookie(name, domain, path, maxAge, url) {
+ let value = name + "=" + ++lastValue;
+ var s = "setting cookie " + value;
+ if (domain) {
+ value += "; Domain=" + domain;
+ s += " (d=" + domain + ")";
+ }
+ if (path) {
+ value += "; Path=" + path;
+ s += " (p=" + path + ")";
+ }
+ if (maxAge) {
+ value += "; Max-Age=" + maxAge;
+ s += " (non-session)";
+ } else {
+ s += " (session)";
+ }
+ s += " for " + url.spec;
+ info(s);
+
+ let channel = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ Services.cookies.setCookieStringFromHttp(url, value, channel);
+
+ return new Promise(function (resolve) {
+ // Windows XP has low precision timestamps that cause our cookie eviction
+ // algorithm to produce different results from other platforms. We work around
+ // this by ensuring that there's a clear gap between each cookie update.
+ do_timeout(10, resolve);
+ });
+}
diff --git a/netwerk/cookie/test/unit/test_getCookieSince.js b/netwerk/cookie/test/unit/test_getCookieSince.js
new file mode 100644
index 0000000000..e58624b6a1
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_getCookieSince.js
@@ -0,0 +1,72 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+function setCookie(name, url) {
+ let value = `${name}=${Math.random()}; Path=/; Max-Age=1000; sameSite=none; Secure`;
+ info(`Setting cookie ${value} for ${url.spec}`);
+
+ let channel = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ Services.cookies.setCookieStringFromHttp(url, value, channel);
+}
+
+async function sleep() {
+ await new Promise(resolve => do_timeout(1000, resolve));
+}
+
+function checkSorting(cookies) {
+ for (let i = 1; i < cookies.length; ++i) {
+ Assert.greater(
+ cookies[i].creationTime,
+ cookies[i - 1].creationTime,
+ "Cookie " + cookies[i].name
+ );
+ }
+}
+
+add_task(async function () {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ await setCookie("A", Services.io.newURI("https://example.com/A/"));
+ await sleep();
+
+ await setCookie("B", Services.io.newURI("https://foo.bar/B/"));
+ await sleep();
+
+ await setCookie("C", Services.io.newURI("https://example.org/C/"));
+ await sleep();
+
+ await setCookie("D", Services.io.newURI("https://example.com/D/"));
+ await sleep();
+
+ Assert.equal(Services.cookies.cookies.length, 4, "Cookie check");
+
+ const cookies = Services.cookies.getCookiesSince(0);
+ Assert.equal(cookies.length, 4, "We retrieve all the 4 cookies");
+ checkSorting(cookies);
+
+ let someCookies = Services.cookies.getCookiesSince(
+ cookies[0].creationTime + 1
+ );
+ Assert.equal(someCookies.length, 3, "We retrieve some cookies");
+ checkSorting(someCookies);
+
+ someCookies = Services.cookies.getCookiesSince(cookies[1].creationTime + 1);
+ Assert.equal(someCookies.length, 2, "We retrieve some cookies");
+ checkSorting(someCookies);
+
+ someCookies = Services.cookies.getCookiesSince(cookies[2].creationTime + 1);
+ Assert.equal(someCookies.length, 1, "We retrieve some cookies");
+ checkSorting(someCookies);
+
+ someCookies = Services.cookies.getCookiesSince(cookies[3].creationTime + 1);
+ Assert.equal(someCookies.length, 0, "We retrieve some cookies");
+});
diff --git a/netwerk/cookie/test/unit/test_migrateCookieLifetimePref.js b/netwerk/cookie/test/unit/test_migrateCookieLifetimePref.js
new file mode 100644
index 0000000000..088a909709
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_migrateCookieLifetimePref.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ Tests that
+ - the migration code runs,
+ - the sanitize on shutdown prefs for profiles with the network.cookie.lifetimePolicy enabled are set to true,
+ - the previous settings for clearOnShutdown prefs will not be applied due to sanitizeOnShutdown being disabled
+ - the network.cookie.lifetimePolicy is disabled afterwards.
+*/
+add_task(async function migrateSanitizationPrefsClearCleaningPrefs() {
+ // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY are not available anymore
+ // 2 = ACCEPT_SESSION
+ Services.prefs.setIntPref("network.cookie.lifetimePolicy", 2);
+ Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", false);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", false);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", false);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.offlineApps", false);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.downloads", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.sessions", true);
+
+ // The migration code is called in cookieService::Init
+ Services.cookies;
+
+ // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY are not available anymore
+ // 0 = ACCEPT_NORMALLY
+ Assert.equal(
+ Services.prefs.getIntPref("network.cookie.lifetimePolicy", 0),
+ 0,
+ "Cookie lifetime policy is off"
+ );
+
+ Assert.ok(
+ Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown"),
+ "Sanitize on shutdown is set"
+ );
+
+ Assert.ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"),
+ "Clearing cookies on shutdown is selected"
+ );
+
+ Assert.ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.cache"),
+ "Clearing cache on shutdown is still selected"
+ );
+
+ Assert.ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"),
+ "Clearing offline apps on shutdown is selected"
+ );
+
+ Assert.ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.downloads"),
+ "Clearing downloads on shutdown is not set anymore"
+ );
+ Assert.ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions"),
+ "Clearing active logins on shutdown is not set anymore"
+ );
+
+ Services.prefs.resetPrefs();
+
+ delete Services.cookies;
+});
diff --git a/netwerk/cookie/test/unit/test_parser_0001.js b/netwerk/cookie/test/unit/test_parser_0001.js
new file mode 100644
index 0000000000..acc2e919ef
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_parser_0001.js
@@ -0,0 +1,32 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function run_test() {
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ let uri = NetUtil.newURI("http://example.org/");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ let set = "foo=bar";
+ Services.cookies.setCookieStringFromHttp(uri, set, channel);
+
+ let expected = "foo=bar";
+ let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
+ Assert.equal(actual, expected);
+}
diff --git a/netwerk/cookie/test/unit/test_parser_0019.js b/netwerk/cookie/test/unit/test_parser_0019.js
new file mode 100644
index 0000000000..7ba0d4ef79
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_parser_0019.js
@@ -0,0 +1,32 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function run_test() {
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ let uri = NetUtil.newURI("http://example.org/");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ let set = "foo=b;max-age=3600, c=d;path=/";
+ Services.cookies.setCookieStringFromHttp(uri, set, channel);
+
+ let expected = "foo=b";
+ let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
+ Assert.equal(actual, expected);
+}
diff --git a/netwerk/cookie/test/unit/test_rawSameSite.js b/netwerk/cookie/test/unit/test_rawSameSite.js
new file mode 100644
index 0000000000..dc739ef852
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_rawSameSite.js
@@ -0,0 +1,125 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+add_task(async _ => {
+ do_get_profile();
+
+ let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dbFile.append("cookies.sqlite");
+
+ let storage = Services.storage;
+ let properties = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag
+ );
+ properties.setProperty("shared", true);
+ let conn = storage.openDatabase(dbFile);
+
+ conn.schemaVersion = 9;
+ conn.executeSimpleSQL("DROP TABLE IF EXISTS moz_cookies");
+ conn.executeSimpleSQL(
+ "CREATE TABLE moz_cookies (" +
+ "id INTEGER PRIMARY KEY, " +
+ "baseDomain TEXT, " +
+ "originAttributes TEXT NOT NULL DEFAULT '', " +
+ "name TEXT, " +
+ "value TEXT, " +
+ "host TEXT, " +
+ "path TEXT, " +
+ "expiry INTEGER, " +
+ "lastAccessed INTEGER, " +
+ "creationTime INTEGER, " +
+ "isSecure INTEGER, " +
+ "isHttpOnly INTEGER, " +
+ "inBrowserElement INTEGER DEFAULT 0, " +
+ "sameSite INTEGER DEFAULT 0, " +
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" +
+ ")"
+ );
+ conn.close();
+
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", true);
+ Services.prefs.setBoolPref(
+ "network.cookie.sameSite.noneRequiresSecure",
+ true
+ );
+ }
+
+ let uri = NetUtil.newURI("http://example.org/");
+
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+
+ let channel = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ let tests = [
+ {
+ cookie: "foo=b;max-age=3600, c=d;path=/; sameSite=strict",
+ sameSite: 2,
+ rawSameSite: 2,
+ },
+ {
+ cookie: "foo=b;max-age=3600, c=d;path=/; sameSite=lax",
+ sameSite: 1,
+ rawSameSite: 1,
+ },
+ { cookie: "foo=b;max-age=3600, c=d;path=/", sameSite: 1, rawSameSite: 0 },
+ ];
+
+ for (let i = 0; i < tests.length; ++i) {
+ let test = tests[i];
+
+ let promise = new Promise(resolve => {
+ function observer(subject, topic, data) {
+ Services.obs.removeObserver(observer, "cookie-saved-on-disk");
+ resolve();
+ }
+
+ Services.obs.addObserver(observer, "cookie-saved-on-disk");
+ });
+
+ Services.cookies.setCookieStringFromHttp(uri, test.cookie, channel);
+
+ await promise;
+
+ conn = storage.openDatabase(dbFile);
+ Assert.equal(conn.schemaVersion, 13);
+
+ let stmt = conn.createStatement(
+ "SELECT sameSite, rawSameSite FROM moz_cookies"
+ );
+
+ let success = stmt.executeStep();
+ Assert.ok(success);
+
+ let sameSite = stmt.getInt32(0);
+ let rawSameSite = stmt.getInt32(1);
+ stmt.finalize();
+
+ Assert.equal(sameSite, test.sameSite);
+ Assert.equal(rawSameSite, test.rawSameSite);
+
+ Services.cookies.removeAll();
+
+ stmt.finalize();
+ conn.close();
+ }
+});
diff --git a/netwerk/cookie/test/unit/test_schemeMap.js b/netwerk/cookie/test/unit/test_schemeMap.js
new file mode 100644
index 0000000000..041c24033a
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_schemeMap.js
@@ -0,0 +1,216 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+let CookieXPCShellUtilsInitialized = false;
+function maybeInitializeCookieXPCShellUtils() {
+ if (!CookieXPCShellUtilsInitialized) {
+ CookieXPCShellUtilsInitialized = true;
+ CookieXPCShellUtils.init(this);
+
+ CookieXPCShellUtils.createServer({ hosts: ["example.org"] });
+ }
+}
+
+// Don't pick up default permissions from profile.
+Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
+
+add_task(async _ => {
+ do_get_profile();
+
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ info("Let's set a cookie from HTTP example.org");
+
+ let uri = NetUtil.newURI("http://example.org/");
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ let channel = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ Services.cookies.setCookieStringFromHttp(uri, "a=b; sameSite=lax", channel);
+
+ let cookies = Services.cookies.getCookiesFromHost("example.org", {});
+ Assert.equal(cookies.length, 1, "We expect 1 cookie only");
+
+ Assert.equal(cookies[0].schemeMap, Ci.nsICookie.SCHEME_HTTP, "HTTP Scheme");
+
+ info("Let's set a cookie from HTTPS example.org");
+
+ uri = NetUtil.newURI("https://example.org/");
+ principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
+ channel = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ Services.cookies.setCookieStringFromHttp(uri, "a=b; sameSite=lax", channel);
+
+ cookies = Services.cookies.getCookiesFromHost("example.org", {});
+ Assert.equal(cookies.length, 1, "We expect 1 cookie only");
+
+ Assert.equal(
+ cookies[0].schemeMap,
+ Ci.nsICookie.SCHEME_HTTP | Ci.nsICookie.SCHEME_HTTPS,
+ "HTTP + HTTPS Schemes"
+ );
+
+ Services.cookies.removeAll();
+});
+
+[true, false].forEach(schemefulComparison => {
+ add_task(async () => {
+ do_get_profile();
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ maybeInitializeCookieXPCShellUtils();
+
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setBoolPref(
+ "network.cookie.sameSite.schemeful",
+ schemefulComparison
+ );
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ info(
+ `Testing schemefulSameSite=${schemefulComparison}. Let's set a cookie from HTTPS example.org`
+ );
+
+ let https_uri = NetUtil.newURI("https://example.org/");
+ let https_principal = Services.scriptSecurityManager.createContentPrincipal(
+ https_uri,
+ {}
+ );
+ let same_site_channel = NetUtil.newChannel({
+ uri: https_uri,
+ loadingPrincipal: https_principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ Services.cookies.setCookieStringFromHttp(
+ https_uri,
+ "a=b; sameSite=lax",
+ same_site_channel
+ );
+
+ let cookies = Services.cookies.getCookieStringFromHttp(
+ https_uri,
+ same_site_channel
+ );
+ Assert.equal(cookies, "a=b", "Cookies match");
+
+ let http_uri = NetUtil.newURI("http://example.org/");
+ let http_principal = Services.scriptSecurityManager.createContentPrincipal(
+ http_uri,
+ {}
+ );
+ let cross_site_channel = NetUtil.newChannel({
+ uri: https_uri,
+ loadingPrincipal: http_principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ cookies = Services.cookies.getCookieStringFromHttp(
+ http_uri,
+ cross_site_channel
+ );
+ if (schemefulComparison) {
+ Assert.equal(cookies, "", "No http(s) cookie for different scheme!");
+ } else {
+ Assert.equal(cookies, "a=b", "http(s) Cookie even for differentscheme!");
+ }
+
+ // SameSite cookies are included via document.domain
+ cookies = await CookieXPCShellUtils.getCookieStringFromDocument(
+ http_uri.spec
+ );
+ Assert.equal(cookies, "a=b", "document.cookie even for different scheme!");
+
+ Services.cookies.removeAll();
+ Services.prefs.clearUserPref("dom.security.https_first");
+ });
+});
+
+add_task(async _ => {
+ do_get_profile();
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ info("Let's set a cookie without scheme");
+ Services.cookies.add(
+ "example.org",
+ "/",
+ "a",
+ "b",
+ false,
+ false,
+ false,
+ Math.floor(Date.now() / 1000 + 1000),
+ {},
+ Ci.nsICookie.SAMESITE_LAX,
+ Ci.nsICookie.SCHEME_UNSET
+ );
+
+ let cookies = Services.cookies.getCookiesFromHost("example.org", {});
+ Assert.equal(cookies.length, 1, "We expect 1 cookie only");
+ Assert.equal(cookies[0].schemeMap, Ci.nsICookie.SCHEME_UNSET, "Unset scheme");
+
+ ["https", "http"].forEach(scheme => {
+ let uri = NetUtil.newURI(scheme + "://example.org/");
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ let channel = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ cookies = Services.cookies.getCookieStringFromHttp(uri, channel);
+ Assert.equal(cookies, "a=b", "Cookie for unset scheme");
+ });
+
+ Services.cookies.removeAll();
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/cookie/test/unit/test_timestamp_fixup.js b/netwerk/cookie/test/unit/test_timestamp_fixup.js
new file mode 100644
index 0000000000..a6e9642ad7
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_timestamp_fixup.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const USEC_PER_SEC = 1000 * 1000;
+const ONE_DAY = 60 * 60 * 24 * USEC_PER_SEC;
+const ONE_YEAR = ONE_DAY * 365;
+const LAST_ACCESSED_DIFF = 10 * ONE_YEAR;
+const CREATION_DIFF = 100 * ONE_YEAR;
+
+function initDB(conn, now) {
+ // Write the schema v7 to the database.
+ conn.schemaVersion = 7;
+ conn.executeSimpleSQL(
+ "CREATE TABLE moz_cookies (" +
+ "id INTEGER PRIMARY KEY, " +
+ "baseDomain TEXT, " +
+ "originAttributes TEXT NOT NULL DEFAULT '', " +
+ "name TEXT, " +
+ "value TEXT, " +
+ "host TEXT, " +
+ "path TEXT, " +
+ "expiry INTEGER, " +
+ "lastAccessed INTEGER, " +
+ "creationTime INTEGER, " +
+ "isSecure INTEGER, " +
+ "isHttpOnly INTEGER, " +
+ "appId INTEGER DEFAULT 0, " +
+ "inBrowserElement INTEGER DEFAULT 0, " +
+ "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" +
+ ")"
+ );
+ conn.executeSimpleSQL(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " +
+ "originAttributes)"
+ );
+
+ conn.executeSimpleSQL("PRAGMA synchronous = OFF");
+ conn.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ conn.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+
+ conn.executeSimpleSQL(
+ `INSERT INTO moz_cookies(baseDomain, host, name, value, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly)
+ VALUES ('foo.com', '.foo.com', 'foo', 'bar=baz', '/',
+ ${now + ONE_DAY}, ${now + LAST_ACCESSED_DIFF} , ${
+ now + CREATION_DIFF
+ } , 1, 1)`
+ );
+}
+
+add_task(async function test_timestamp_fixup() {
+ let now = Date.now() * 1000; // date in microseconds
+ Services.prefs.setBoolPref("network.cookie.fixup_on_db_load", true);
+ do_get_profile();
+ let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dbFile.append("cookies.sqlite");
+ let conn = Services.storage.openDatabase(dbFile);
+ initDB(conn, now);
+
+ if (AppConstants.platform != "android") {
+ Services.fog.initializeFOG();
+ }
+ Services.fog.testResetFOG();
+
+ // Now start the cookie service, and then check the fields in the table.
+ // Get sessionCookies to wait for the initialization in cookie thread
+ Assert.lessOrEqual(
+ Math.floor(Services.cookies.cookies[0].creationTime / 1000),
+ now
+ );
+ Assert.equal(conn.schemaVersion, 13);
+
+ Assert.equal(
+ await Glean.networking.cookieTimestampFixedCount.creationTime.testGetValue(),
+ 1,
+ "One fixup of creation time"
+ );
+ Assert.equal(
+ await Glean.networking.cookieTimestampFixedCount.lastAccessed.testGetValue(),
+ 1,
+ "One fixup of lastAccessed"
+ );
+ {
+ let { values } =
+ await Glean.networking.cookieCreationFixupDiff.testGetValue();
+ info(JSON.stringify(values));
+ let keys = Object.keys(values).splice(-2, 2);
+ Assert.equal(keys.length, 2, "There should be two entries in telemetry");
+ Assert.equal(values[keys[0]], 1, "First entry should have value 1");
+ Assert.equal(values[keys[1]], 0, "Second entry should have value 0");
+ const creationDiffInSeconds = CREATION_DIFF / USEC_PER_SEC;
+ Assert.lessOrEqual(
+ parseInt(keys[0]),
+ creationDiffInSeconds,
+ "The bucket should be smaller than time diff"
+ );
+ Assert.lessOrEqual(
+ creationDiffInSeconds,
+ parseInt(keys[1]),
+ "The next bucket should be larger than time diff"
+ );
+ }
+
+ {
+ let { values } =
+ await Glean.networking.cookieAccessFixupDiff.testGetValue();
+ info(JSON.stringify(values));
+ let keys = Object.keys(values).splice(-2, 2);
+ Assert.equal(keys.length, 2, "There should be two entries in telemetry");
+ Assert.equal(values[keys[0]], 1, "First entry should have value 1");
+ Assert.equal(values[keys[1]], 0, "Second entry should have value 0");
+ info(now);
+ const lastAccessedDiffInSeconds = LAST_ACCESSED_DIFF / USEC_PER_SEC;
+ Assert.lessOrEqual(
+ parseInt(keys[0]),
+ lastAccessedDiffInSeconds,
+ "The bucket should be smaller than time diff"
+ );
+ Assert.lessOrEqual(
+ lastAccessedDiffInSeconds,
+ parseInt(keys[1]),
+ "The next bucket should be larger than time diff"
+ );
+ }
+
+ conn.close();
+});
diff --git a/netwerk/cookie/test/unit/xpcshell.toml b/netwerk/cookie/test/unit/xpcshell.toml
new file mode 100644
index 0000000000..694d3cb847
--- /dev/null
+++ b/netwerk/cookie/test/unit/xpcshell.toml
@@ -0,0 +1,26 @@
+[DEFAULT]
+head = ""
+
+["test_baseDomain_publicsuffix.js"]
+
+["test_bug643051.js"]
+
+["test_bug1155169.js"]
+
+["test_bug1321912.js"]
+
+["test_eviction.js"]
+
+["test_getCookieSince.js"]
+
+["test_migrateCookieLifetimePref.js"]
+
+["test_parser_0001.js"]
+
+["test_parser_0019.js"]
+
+["test_rawSameSite.js"]
+
+["test_schemeMap.js"]
+
+["test_timestamp_fixup.js"]
diff --git a/netwerk/dns/ChildDNSService.cpp b/netwerk/dns/ChildDNSService.cpp
new file mode 100644
index 0000000000..653608ab01
--- /dev/null
+++ b/netwerk/dns/ChildDNSService.cpp
@@ -0,0 +1,514 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/ChildDNSService.h"
+#include "nsDNSPrefetch.h"
+#include "nsIDNSListener.h"
+#include "nsIOService.h"
+#include "nsThreadUtils.h"
+#include "nsIXPConnect.h"
+#include "nsIProtocolProxyService.h"
+#include "nsNetCID.h"
+#include "nsQueryObject.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/DNSListenerProxy.h"
+#include "mozilla/net/TRRServiceParent.h"
+#include "nsHostResolver.h"
+#include "nsServiceManagerUtils.h"
+#include "prsystem.h"
+#include "DNSAdditionalInfo.h"
+#include "TRRService.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// ChildDNSService
+//-----------------------------------------------------------------------------
+
+static StaticRefPtr<ChildDNSService> gChildDNSService;
+
+already_AddRefed<ChildDNSService> ChildDNSService::GetSingleton() {
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess(),
+ XRE_IsContentProcess() || XRE_IsParentProcess());
+ MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(),
+ XRE_IsContentProcess() || XRE_IsSocketProcess());
+
+ if (!gChildDNSService) {
+ if (NS_WARN_IF(!NS_IsMainThread())) {
+ return nullptr;
+ }
+ gChildDNSService = new ChildDNSService();
+ gChildDNSService->Init();
+ ClearOnShutdown(&gChildDNSService);
+ }
+
+ return do_AddRef(gChildDNSService);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(ChildDNSService, DNSServiceBase, nsIDNSService,
+ nsPIDNSService)
+
+ChildDNSService::ChildDNSService() {
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess(),
+ XRE_IsContentProcess() || XRE_IsParentProcess());
+ MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(),
+ XRE_IsContentProcess() || XRE_IsSocketProcess());
+ if (XRE_IsParentProcess() && nsIOService::UseSocketProcess()) {
+ nsDNSPrefetch::Initialize(this);
+ mTRRServiceParent = new TRRServiceParent();
+ mTRRServiceParent->Init();
+ }
+}
+
+void ChildDNSService::GetDNSRecordHashKey(
+ const nsACString& aHost, const nsACString& aTrrServer, int32_t aPort,
+ uint16_t aType, const OriginAttributes& aOriginAttributes,
+ nsIDNSService::DNSFlags aFlags, uintptr_t aListenerAddr,
+ nsACString& aHashKey) {
+ aHashKey.Assign(aHost);
+ aHashKey.Assign(aTrrServer);
+ aHashKey.AppendInt(aPort);
+ aHashKey.AppendInt(aType);
+
+ nsAutoCString originSuffix;
+ aOriginAttributes.CreateSuffix(originSuffix);
+ aHashKey.Append(originSuffix);
+
+ aHashKey.AppendInt(aFlags);
+ aHashKey.AppendPrintf("0x%" PRIxPTR, aListenerAddr);
+}
+
+nsresult ChildDNSService::AsyncResolveInternal(
+ const nsACString& hostname, uint16_t type, nsIDNSService::DNSFlags flags,
+ nsIDNSAdditionalInfo* aInfo, nsIDNSListener* listener,
+ nsIEventTarget* target_, const OriginAttributes& aOriginAttributes,
+ nsICancelable** result) {
+ if (XRE_IsContentProcess()) {
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+ }
+
+ if (DNSForbiddenByActiveProxy(hostname, flags)) {
+ // nsHostResolver returns NS_ERROR_UNKNOWN_HOST for lots of reasons.
+ // We use a different error code to differentiate this failure and to make
+ // it clear(er) where this error comes from.
+ return NS_ERROR_UNKNOWN_PROXY_HOST;
+ }
+
+ bool resolveDNSInSocketProcess = false;
+ if (XRE_IsParentProcess() && nsIOService::UseSocketProcess()) {
+ resolveDNSInSocketProcess = true;
+ if (type != nsIDNSService::RESOLVE_TYPE_DEFAULT &&
+ (mTRRServiceParent->Mode() != nsIDNSService::MODE_TRRFIRST &&
+ mTRRServiceParent->Mode() != nsIDNSService::MODE_TRRONLY) &&
+ !StaticPrefs::network_dns_native_https_query()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+
+ if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) {
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+ }
+
+ // We need original listener for the pending requests hash.
+ uintptr_t originalListenerAddr = reinterpret_cast<uintptr_t>(listener);
+
+ // make sure JS callers get notification on the main thread
+ nsCOMPtr<nsIEventTarget> target = target_;
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
+ if (wrappedListener && !target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ if (target) {
+ // Guarantee listener freed on main thread. Not sure we need this in child
+ // (or in parent in nsDNSService.cpp) but doesn't hurt.
+ listener = new DNSListenerProxy(listener, target);
+ }
+
+ RefPtr<DNSRequestSender> sender = new DNSRequestSender(
+ hostname, DNSAdditionalInfo::URL(aInfo), DNSAdditionalInfo::Port(aInfo),
+ type, aOriginAttributes, flags, listener, target);
+ RefPtr<DNSRequestActor> dnsReq;
+ if (resolveDNSInSocketProcess) {
+ dnsReq = new DNSRequestParent(sender);
+ if (!mTRRServiceParent->TRRConnectionInfoInited()) {
+ mTRRServiceParent->InitTRRConnectionInfo();
+ }
+ } else {
+ dnsReq = new DNSRequestChild(sender);
+ }
+
+ {
+ MutexAutoLock lock(mPendingRequestsLock);
+ nsCString key;
+ GetDNSRecordHashKey(hostname, DNSAdditionalInfo::URL(aInfo),
+ DNSAdditionalInfo::Port(aInfo), type, aOriginAttributes,
+ flags, originalListenerAddr, key);
+ mPendingRequests.GetOrInsertNew(key)->AppendElement(sender);
+ }
+
+ sender->StartRequest();
+
+ sender.forget(result);
+ return NS_OK;
+}
+
+nsresult ChildDNSService::CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType, nsIDNSService::DNSFlags aFlags,
+ nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener, nsresult aReason,
+ const OriginAttributes& aOriginAttributes) {
+ if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+ }
+
+ MutexAutoLock lock(mPendingRequestsLock);
+ nsTArray<RefPtr<DNSRequestSender>>* hashEntry;
+ nsCString key;
+ uintptr_t listenerAddr = reinterpret_cast<uintptr_t>(aListener);
+ GetDNSRecordHashKey(aHostname, DNSAdditionalInfo::URL(aInfo),
+ DNSAdditionalInfo::Port(aInfo), aType, aOriginAttributes,
+ aFlags, listenerAddr, key);
+ if (mPendingRequests.Get(key, &hashEntry)) {
+ // We cancel just one.
+ hashEntry->ElementAt(0)->Cancel(aReason);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSService::nsIDNSService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolve(const nsACString& hostname,
+ nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags flags,
+ nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* listener, nsIEventTarget* target_,
+ JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx, uint8_t aArgc,
+ nsICancelable** result) {
+ OriginAttributes attrs;
+
+ if (aArgc == 1) {
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return AsyncResolveInternal(hostname, aType, flags, aInfo, listener, target_,
+ attrs, result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolveNative(
+ const nsACString& hostname, nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags flags, nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* listener, nsIEventTarget* target_,
+ const OriginAttributes& aOriginAttributes, nsICancelable** result) {
+ return AsyncResolveInternal(hostname, aType, flags, aInfo, listener, target_,
+ aOriginAttributes, result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::NewAdditionalInfo(const nsACString& aTrrURL, int32_t aPort,
+ nsIDNSAdditionalInfo** aInfo) {
+ RefPtr<DNSAdditionalInfo> res = new DNSAdditionalInfo(aTrrURL, aPort);
+ res.forget(aInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolve(const nsACString& aHostname,
+ nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags aFlags,
+ nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* aListener, nsresult aReason,
+ JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx, uint8_t aArgc) {
+ OriginAttributes attrs;
+
+ if (aArgc == 1) {
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return CancelAsyncResolveInternal(aHostname, aType, aFlags, aInfo, aListener,
+ aReason, attrs);
+}
+
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolveNative(
+ const nsACString& aHostname, nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags aFlags, nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* aListener, nsresult aReason,
+ const OriginAttributes& aOriginAttributes) {
+ return CancelAsyncResolveInternal(aHostname, aType, aFlags, aInfo, aListener,
+ aReason, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+ChildDNSService::Resolve(const nsACString& hostname,
+ nsIDNSService::DNSFlags flags,
+ JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx, uint8_t aArgc, nsIDNSRecord** result) {
+ // not planning to ever support this, since sync IPDL is evil.
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+ChildDNSService::ResolveNative(const nsACString& hostname,
+ nsIDNSService::DNSFlags flags,
+ const OriginAttributes& aOriginAttributes,
+ nsIDNSRecord** result) {
+ // not planning to ever support this, since sync IPDL is evil.
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetDNSCacheEntries(
+ nsTArray<mozilla::net::DNSCacheEntries>* args) {
+ // Only used by networking dashboard, so may not ever need this in child.
+ // (and would provide a way to spy on what hosts other apps are connecting to,
+ // unless we start keeping per-app DNS caches).
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+ChildDNSService::ClearCache(bool aTrrToo) {
+ if (!mTRRServiceParent || !mTRRServiceParent->CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << mTRRServiceParent->SendClearDNSCache(aTrrToo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::ReloadParentalControlEnabled() {
+ if (!mTRRServiceParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mTRRServiceParent->UpdateParentalControlEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::SetDetectedTrrURI(const nsACString& aURI) {
+ if (!mTRRServiceParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mTRRServiceParent->SetDetectedTrrURI(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::SetHeuristicDetectionResult(nsITRRSkipReason::value aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetHeuristicDetectionResult(nsITRRSkipReason::value* aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetTRRSkipReasonName(nsITRRSkipReason::value aValue,
+ nsACString& aName) {
+ return mozilla::net::GetTRRSkipReasonName(aValue, aName);
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetCurrentTrrURI(nsACString& aURI) {
+ if (!mTRRServiceParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mTRRServiceParent->GetURI(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetCurrentTrrMode(nsIDNSService::ResolverMode* aMode) {
+ if (XRE_IsContentProcess()) {
+ *aMode = mTRRMode;
+ return NS_OK;
+ }
+ if (!mTRRServiceParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aMode = mTRRServiceParent->Mode();
+ return NS_OK;
+}
+
+void ChildDNSService::SetTRRModeInChild(
+ nsIDNSService::ResolverMode mode,
+ nsIDNSService::ResolverMode modeFromPref) {
+ if (!XRE_IsContentProcess()) {
+ MOZ_ASSERT(false, "Why are we calling this?");
+ return;
+ }
+ mTRRMode = mode;
+ TRRService::SetCurrentTRRMode(modeFromPref);
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetCurrentTrrConfirmationState(uint32_t* aConfirmationState) {
+ if (!mTRRServiceParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aConfirmationState = mTRRServiceParent->GetConfirmationState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetMyHostName(nsACString& result) {
+ if (XRE_IsParentProcess()) {
+ char name[100];
+ if (PR_GetSystemInfo(PR_SI_HOSTNAME, name, sizeof(name)) == PR_SUCCESS) {
+ result = name;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+ }
+ // TODO: get value from parent during PNecko construction?
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+void ChildDNSService::NotifyRequestDone(DNSRequestSender* aDnsRequest) {
+ // We need the original flags and listener for the pending requests hash.
+ nsIDNSService::DNSFlags originalFlags =
+ aDnsRequest->mFlags & ~RESOLVE_OFFLINE;
+ uintptr_t originalListenerAddr =
+ reinterpret_cast<uintptr_t>(aDnsRequest->mListener.get());
+ RefPtr<DNSListenerProxy> wrapper = do_QueryObject(aDnsRequest->mListener);
+ if (wrapper) {
+ originalListenerAddr = wrapper->GetOriginalListenerAddress();
+ }
+
+ MutexAutoLock lock(mPendingRequestsLock);
+
+ nsCString key;
+ GetDNSRecordHashKey(aDnsRequest->mHost, aDnsRequest->mTrrServer,
+ aDnsRequest->mPort, aDnsRequest->mType,
+ aDnsRequest->mOriginAttributes, originalFlags,
+ originalListenerAddr, key);
+
+ nsTArray<RefPtr<DNSRequestSender>>* hashEntry;
+
+ if (mPendingRequests.Get(key, &hashEntry)) {
+ auto idx = hashEntry->IndexOf(aDnsRequest);
+ if (idx != nsTArray<RefPtr<DNSRequestSender>>::NoIndex) {
+ hashEntry->RemoveElementAt(idx);
+ if (hashEntry->IsEmpty()) {
+ mPendingRequests.Remove(key);
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSService::nsPIDNSService
+//-----------------------------------------------------------------------------
+
+nsresult ChildDNSService::Init() {
+ ReadPrefs(nullptr);
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ AddPrefObserver(prefs);
+ }
+
+ return NS_OK;
+}
+
+nsresult ChildDNSService::Shutdown() { return NS_OK; }
+
+NS_IMETHODIMP
+ChildDNSService::GetPrefetchEnabled(bool* outVal) {
+ *outVal = !mDisablePrefetch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::SetPrefetchEnabled(bool inVal) {
+ mDisablePrefetch = !inVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::ReportFailedSVCDomainName(const nsACString& aOwnerName,
+ const nsACString& aSVCDomainName) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ChildDNSService::IsSVCDomainNameFailed(const nsACString& aOwnerName,
+ const nsACString& aSVCDomainName,
+ bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ChildDNSService::ResetExcludedSVCDomainName(const nsACString& aOwnerName) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSService::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ChildDNSService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ // Reread prefs
+ ReadPrefs(NS_ConvertUTF16toUTF8(data).get());
+ }
+ return NS_OK;
+}
+
+void ChildDNSService::SetTRRDomain(const nsACString& aTRRDomain) {
+ mTRRDomain = aTRRDomain;
+ TRRService::SetProviderDomain(aTRRDomain);
+}
+
+nsresult ChildDNSService::GetTRRDomainKey(nsACString& aTRRDomain) {
+ aTRRDomain = TRRService::ProviderKey();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetTrrDomain(nsACString& aTRRDomain) {
+ aTRRDomain = mTRRDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetLastConfirmationStatus(nsresult* aConfirmationStatus) {
+ // XXX(valentin): Fix for socket process
+ *aConfirmationStatus = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ChildDNSService::GetLastConfirmationSkipReason(
+ TRRSkippedReason* aSkipReason) {
+ // XXX(valentin): Fix for socket process
+ *aSkipReason = nsITRRSkipReason::TRR_UNSET;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/ChildDNSService.h b/netwerk/dns/ChildDNSService.h
new file mode 100644
index 0000000000..611f8b871f
--- /dev/null
+++ b/netwerk/dns/ChildDNSService.h
@@ -0,0 +1,78 @@
+/* -*- 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_ChildDNSService_h
+#define mozilla_net_ChildDNSService_h
+
+#include "DNSServiceBase.h"
+#include "nsPIDNSService.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "DNSRequestChild.h"
+#include "DNSRequestParent.h"
+#include "nsHashKeys.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+class TRRServiceParent;
+
+class ChildDNSService final : public DNSServiceBase, public nsPIDNSService {
+ public:
+ // AsyncResolve (and CancelAsyncResolve) can be called off-main
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSPIDNSSERVICE
+ NS_DECL_NSIDNSSERVICE
+ NS_DECL_NSIOBSERVER
+
+ ChildDNSService();
+
+ static already_AddRefed<ChildDNSService> GetSingleton();
+
+ void NotifyRequestDone(DNSRequestSender* aDnsRequest);
+
+ void SetTRRDomain(const nsACString& aTRRDomain);
+ void SetTRRModeInChild(nsIDNSService::ResolverMode mode,
+ nsIDNSService::ResolverMode modeFromPref);
+
+ private:
+ virtual ~ChildDNSService() = default;
+
+ void MOZ_ALWAYS_INLINE GetDNSRecordHashKey(
+ const nsACString& aHost, const nsACString& aTrrServer, int32_t aPort,
+ uint16_t aType, const OriginAttributes& aOriginAttributes,
+ nsIDNSService::DNSFlags aFlags, uintptr_t aListenerAddr,
+ nsACString& aHashKey);
+ nsresult AsyncResolveInternal(const nsACString& hostname, uint16_t type,
+ nsIDNSService::DNSFlags flags,
+ nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* listener,
+ nsIEventTarget* target_,
+ const OriginAttributes& aOriginAttributes,
+ nsICancelable** result);
+ nsresult CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType,
+ nsIDNSService::DNSFlags aFlags, nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* aListener, nsresult aReason,
+ const OriginAttributes& aOriginAttributes);
+
+ // We need to remember pending dns requests to be able to cancel them.
+ nsClassHashtable<nsCStringHashKey, nsTArray<RefPtr<DNSRequestSender>>>
+ mPendingRequests;
+ Mutex mPendingRequestsLock MOZ_UNANNOTATED{"DNSPendingRequestsLock"};
+ RefPtr<TRRServiceParent> mTRRServiceParent;
+
+ nsCString mTRRDomain;
+ // Only set in the content process.
+ nsIDNSService::ResolverMode mTRRMode =
+ nsIDNSService::ResolverMode::MODE_NATIVEONLY;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ChildDNSService_h
diff --git a/netwerk/dns/DNS.cpp b/netwerk/dns/DNS.cpp
new file mode 100644
index 0000000000..91730989db
--- /dev/null
+++ b/netwerk/dns/DNS.cpp
@@ -0,0 +1,445 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/DNS.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include <string.h>
+
+#ifdef XP_WIN
+# include "ws2tcpip.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+const char* inet_ntop_internal(int af, const void* src, char* dst,
+ socklen_t size) {
+#ifdef XP_WIN
+ if (af == AF_INET) {
+ struct sockaddr_in s;
+ memset(&s, 0, sizeof(s));
+ s.sin_family = AF_INET;
+ memcpy(&s.sin_addr, src, sizeof(struct in_addr));
+ int result = getnameinfo((struct sockaddr*)&s, sizeof(struct sockaddr_in),
+ dst, size, nullptr, 0, NI_NUMERICHOST);
+ if (result == 0) {
+ return dst;
+ }
+ } else if (af == AF_INET6) {
+ struct sockaddr_in6 s;
+ memset(&s, 0, sizeof(s));
+ s.sin6_family = AF_INET6;
+ memcpy(&s.sin6_addr, src, sizeof(struct in_addr6));
+ int result = getnameinfo((struct sockaddr*)&s, sizeof(struct sockaddr_in6),
+ dst, size, nullptr, 0, NI_NUMERICHOST);
+ if (result == 0) {
+ return dst;
+ }
+ }
+ return nullptr;
+#else
+ return inet_ntop(af, src, dst, size);
+#endif
+}
+
+// Copies the contents of a PRNetAddr to a NetAddr.
+// Does not do a ptr safety check!
+void PRNetAddrToNetAddr(const PRNetAddr* prAddr, NetAddr* addr) {
+ if (prAddr->raw.family == PR_AF_INET) {
+ addr->inet.family = AF_INET;
+ addr->inet.port = prAddr->inet.port;
+ addr->inet.ip = prAddr->inet.ip;
+ } else if (prAddr->raw.family == PR_AF_INET6) {
+ addr->inet6.family = AF_INET6;
+ addr->inet6.port = prAddr->ipv6.port;
+ addr->inet6.flowinfo = prAddr->ipv6.flowinfo;
+ memcpy(&addr->inet6.ip, &prAddr->ipv6.ip, sizeof(addr->inet6.ip.u8));
+ addr->inet6.scope_id = prAddr->ipv6.scope_id;
+ }
+#if defined(XP_UNIX)
+ else if (prAddr->raw.family == PR_AF_LOCAL) {
+ addr->local.family = AF_LOCAL;
+ memcpy(addr->local.path, prAddr->local.path, sizeof(addr->local.path));
+ }
+#endif
+}
+
+extern "C" {
+// Rust bindings
+
+uint16_t moz_netaddr_get_family(const NetAddr* addr) {
+ return addr->raw.family;
+}
+
+uint32_t moz_netaddr_get_network_order_ip(const NetAddr* addr) {
+ return addr->inet.ip;
+}
+
+uint8_t const* moz_netaddr_get_ipv6(const NetAddr* addr) {
+ return addr->inet6.ip.u8;
+}
+
+uint16_t moz_netaddr_get_network_order_port(const NetAddr* addr) {
+ if (addr->raw.family == PR_AF_INET) {
+ return addr->inet.port;
+ }
+ if (addr->raw.family == PR_AF_INET6) {
+ return addr->inet6.port;
+ }
+ return 0;
+}
+
+} // extern "C"
+
+// Copies the contents of a NetAddr to a PRNetAddr.
+// Does not do a ptr safety check!
+void NetAddrToPRNetAddr(const NetAddr* addr, PRNetAddr* prAddr) {
+ if (addr->raw.family == AF_INET) {
+ prAddr->inet.family = PR_AF_INET;
+ prAddr->inet.port = addr->inet.port;
+ prAddr->inet.ip = addr->inet.ip;
+ } else if (addr->raw.family == AF_INET6) {
+ prAddr->ipv6.family = PR_AF_INET6;
+ prAddr->ipv6.port = addr->inet6.port;
+ prAddr->ipv6.flowinfo = addr->inet6.flowinfo;
+ memcpy(&prAddr->ipv6.ip, &addr->inet6.ip, sizeof(addr->inet6.ip.u8));
+ prAddr->ipv6.scope_id = addr->inet6.scope_id;
+ }
+#if defined(XP_UNIX)
+ else if (addr->raw.family == AF_LOCAL) {
+ prAddr->local.family = PR_AF_LOCAL;
+ memcpy(prAddr->local.path, addr->local.path, sizeof(addr->local.path));
+ }
+#elif defined(XP_WIN)
+ else if (addr->raw.family == AF_LOCAL) {
+ prAddr->local.family = PR_AF_LOCAL;
+ memcpy(prAddr->local.path, addr->local.path, sizeof(addr->local.path));
+ }
+#endif
+}
+
+bool NetAddr::ToStringBuffer(char* buf, uint32_t bufSize) const {
+ const NetAddr* addr = this;
+ if (addr->raw.family == AF_INET) {
+ if (bufSize < INET_ADDRSTRLEN) {
+ return false;
+ }
+ struct in_addr nativeAddr = {};
+ nativeAddr.s_addr = addr->inet.ip;
+ return !!inet_ntop_internal(AF_INET, &nativeAddr, buf, bufSize);
+ }
+ if (addr->raw.family == AF_INET6) {
+ if (bufSize < INET6_ADDRSTRLEN) {
+ return false;
+ }
+ struct in6_addr nativeAddr = {};
+ memcpy(&nativeAddr.s6_addr, &addr->inet6.ip, sizeof(addr->inet6.ip.u8));
+ return !!inet_ntop_internal(AF_INET6, &nativeAddr, buf, bufSize);
+ }
+#if defined(XP_UNIX)
+ if (addr->raw.family == AF_LOCAL) {
+ if (bufSize < sizeof(addr->local.path)) {
+ // Many callers don't bother checking our return value, so
+ // null-terminate just in case.
+ if (bufSize > 0) {
+ buf[0] = '\0';
+ }
+ return false;
+ }
+
+ // Usually, the size passed to memcpy should be the size of the
+ // destination. Here, we know that the source is no larger than the
+ // destination, so using the source's size is always safe, whereas
+ // using the destination's size may cause us to read off the end of the
+ // source.
+ memcpy(buf, addr->local.path, sizeof(addr->local.path));
+ return true;
+ }
+#endif
+ return false;
+}
+
+nsCString NetAddr::ToString() const {
+ nsCString out;
+ out.SetLength(kNetAddrMaxCStrBufSize);
+ if (ToStringBuffer(out.BeginWriting(), kNetAddrMaxCStrBufSize)) {
+ out.SetLength(strlen(out.BeginWriting()));
+ return out;
+ }
+ return ""_ns;
+}
+
+bool NetAddr::IsLoopbackAddr() const {
+ if (IsLoopBackAddressWithoutIPv6Mapping()) {
+ return true;
+ }
+ const NetAddr* addr = this;
+ if (addr->raw.family != AF_INET6) {
+ return false;
+ }
+
+ return IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip) &&
+ IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip) == htonl(INADDR_LOOPBACK);
+}
+
+bool NetAddr::IsLoopBackAddressWithoutIPv6Mapping() const {
+ const NetAddr* addr = this;
+ if (addr->raw.family == AF_INET) {
+ // Consider 127.0.0.1/8 as loopback
+ uint32_t ipv4Addr = ntohl(addr->inet.ip);
+ return (ipv4Addr >> 24) == 127;
+ }
+
+ return addr->raw.family == AF_INET6 && IPv6ADDR_IS_LOOPBACK(&addr->inet6.ip);
+}
+
+bool IsLoopbackHostname(const nsACString& aAsciiHost) {
+ // If the user has configured to proxy localhost addresses don't consider them
+ // to be secure
+ if (StaticPrefs::network_proxy_allow_hijacking_localhost() &&
+ !StaticPrefs::network_proxy_testing_localhost_is_secure_when_hijacked()) {
+ return false;
+ }
+
+ nsAutoCString host;
+ nsContentUtils::ASCIIToLower(aAsciiHost, host);
+
+ return host.EqualsLiteral("localhost") || host.EqualsLiteral("localhost.") ||
+ StringEndsWith(host, ".localhost"_ns) ||
+ StringEndsWith(host, ".localhost."_ns);
+}
+
+bool HostIsIPLiteral(const nsACString& aAsciiHost) {
+ NetAddr addr;
+ return NS_SUCCEEDED(addr.InitFromString(aAsciiHost));
+}
+
+bool NetAddr::IsIPAddrAny() const {
+ if (this->raw.family == AF_INET) {
+ if (this->inet.ip == htonl(INADDR_ANY)) {
+ return true;
+ }
+ } else if (this->raw.family == AF_INET6) {
+ if (IPv6ADDR_IS_UNSPECIFIED(&this->inet6.ip)) {
+ return true;
+ }
+ if (IPv6ADDR_IS_V4MAPPED(&this->inet6.ip) &&
+ IPv6ADDR_V4MAPPED_TO_IPADDR(&this->inet6.ip) == htonl(INADDR_ANY)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NetAddr::NetAddr(const PRNetAddr* prAddr) { PRNetAddrToNetAddr(prAddr, this); }
+
+nsresult NetAddr::InitFromString(const nsACString& aString, uint16_t aPort) {
+ PRNetAddr prAddr{};
+ memset(&prAddr, 0, sizeof(PRNetAddr));
+ if (PR_StringToNetAddr(PromiseFlatCString(aString).get(), &prAddr) !=
+ PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddrToNetAddr(&prAddr, this);
+
+ if (this->raw.family == PR_AF_INET) {
+ this->inet.port = PR_htons(aPort);
+ } else if (this->raw.family == PR_AF_INET6) {
+ this->inet6.port = PR_htons(aPort);
+ }
+ return NS_OK;
+}
+
+bool NetAddr::IsIPAddrV4() const { return this->raw.family == AF_INET; }
+
+bool NetAddr::IsIPAddrV4Mapped() const {
+ if (this->raw.family == AF_INET6) {
+ return IPv6ADDR_IS_V4MAPPED(&this->inet6.ip);
+ }
+ return false;
+}
+
+static bool isLocalIPv4(uint32_t networkEndianIP) {
+ uint32_t addr32 = ntohl(networkEndianIP);
+ return addr32 >> 24 == 0x0A || // 10/8 prefix (RFC 1918).
+ addr32 >> 20 == 0xAC1 || // 172.16/12 prefix (RFC 1918).
+ addr32 >> 16 == 0xC0A8 || // 192.168/16 prefix (RFC 1918).
+ addr32 >> 16 == 0xA9FE; // 169.254/16 prefix (Link Local).
+}
+
+bool NetAddr::IsIPAddrLocal() const {
+ const NetAddr* addr = this;
+
+ // IPv4 RFC1918 and Link Local Addresses.
+ if (addr->raw.family == AF_INET) {
+ return isLocalIPv4(addr->inet.ip);
+ }
+ // IPv6 Unique and Link Local Addresses.
+ // or mapped IPv4 addresses
+ if (addr->raw.family == AF_INET6) {
+ uint16_t addr16 = ntohs(addr->inet6.ip.u16[0]);
+ if (addr16 >> 9 == 0xfc >> 1 || // fc00::/7 Unique Local Address.
+ addr16 >> 6 == 0xfe80 >> 6) { // fe80::/10 Link Local Address.
+ return true;
+ }
+ if (IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip)) {
+ return isLocalIPv4(IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip));
+ }
+ }
+
+ // Not an IPv4/6 local address.
+ return false;
+}
+
+bool NetAddr::IsIPAddrShared() const {
+ const NetAddr* addr = this;
+
+ // IPv4 RFC6598.
+ if (addr->raw.family == AF_INET) {
+ uint32_t addr32 = ntohl(addr->inet.ip);
+ if (addr32 >> 22 == 0x644 >> 2) { // 100.64/10 prefix (RFC 6598).
+ return true;
+ }
+ }
+
+ // Not an IPv4 shared address.
+ return false;
+}
+
+nsresult NetAddr::GetPort(uint16_t* aResult) const {
+ uint16_t port;
+ if (this->raw.family == PR_AF_INET) {
+ port = this->inet.port;
+ } else if (this->raw.family == PR_AF_INET6) {
+ port = this->inet6.port;
+ } else {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ *aResult = ntohs(port);
+ return NS_OK;
+}
+
+bool NetAddr::operator==(const NetAddr& other) const {
+ if (this->raw.family != other.raw.family) {
+ return false;
+ }
+ if (this->raw.family == AF_INET) {
+ return (this->inet.port == other.inet.port) &&
+ (this->inet.ip == other.inet.ip);
+ }
+ if (this->raw.family == AF_INET6) {
+ return (this->inet6.port == other.inet6.port) &&
+ (this->inet6.flowinfo == other.inet6.flowinfo) &&
+ (memcmp(&this->inet6.ip, &other.inet6.ip, sizeof(this->inet6.ip)) ==
+ 0) &&
+ (this->inet6.scope_id == other.inet6.scope_id);
+#if defined(XP_UNIX)
+ }
+ if (this->raw.family == AF_LOCAL) {
+ return strncmp(this->local.path, other.local.path,
+ ArrayLength(this->local.path));
+#endif
+ }
+ return false;
+}
+
+bool NetAddr::operator<(const NetAddr& other) const {
+ if (this->raw.family != other.raw.family) {
+ return this->raw.family < other.raw.family;
+ }
+ if (this->raw.family == AF_INET) {
+ if (this->inet.ip == other.inet.ip) {
+ return this->inet.port < other.inet.port;
+ }
+ return this->inet.ip < other.inet.ip;
+ }
+ if (this->raw.family == AF_INET6) {
+ int cmpResult =
+ memcmp(&this->inet6.ip, &other.inet6.ip, sizeof(this->inet6.ip));
+ if (cmpResult) {
+ return cmpResult < 0;
+ }
+ if (this->inet6.port != other.inet6.port) {
+ return this->inet6.port < other.inet6.port;
+ }
+ return this->inet6.flowinfo < other.inet6.flowinfo;
+ }
+ return false;
+}
+
+AddrInfo::AddrInfo(const nsACString& host, const PRAddrInfo* prAddrInfo,
+ bool disableIPv4, bool filterNameCollision,
+ const nsACString& cname)
+ : mHostName(host), mCanonicalName(cname) {
+ MOZ_ASSERT(prAddrInfo,
+ "Cannot construct AddrInfo with a null prAddrInfo pointer!");
+ const uint32_t nameCollisionAddr = htonl(0x7f003535); // 127.0.53.53
+
+ PRNetAddr tmpAddr;
+ void* iter = nullptr;
+ do {
+ iter = PR_EnumerateAddrInfo(iter, prAddrInfo, 0, &tmpAddr);
+ bool addIt = iter && (!disableIPv4 || tmpAddr.raw.family != PR_AF_INET) &&
+ (!filterNameCollision || tmpAddr.raw.family != PR_AF_INET ||
+ (tmpAddr.inet.ip != nameCollisionAddr));
+ if (addIt) {
+ NetAddr elem(&tmpAddr);
+ mAddresses.AppendElement(elem);
+ }
+ } while (iter);
+}
+
+AddrInfo::AddrInfo(const nsACString& host, const nsACString& cname,
+ DNSResolverType aResolverType, unsigned int aTRRType,
+ nsTArray<NetAddr>&& addresses)
+ : mHostName(host),
+ mCanonicalName(cname),
+ mResolverType(aResolverType),
+ mTRRType(aTRRType),
+ mAddresses(std::move(addresses)) {}
+
+AddrInfo::AddrInfo(const nsACString& host, DNSResolverType aResolverType,
+ unsigned int aTRRType, nsTArray<NetAddr>&& addresses,
+ uint32_t aTTL)
+ : ttl(aTTL),
+ mHostName(host),
+ mResolverType(aResolverType),
+ mTRRType(aTRRType),
+ mAddresses(std::move(addresses)) {}
+
+// deep copy constructor
+AddrInfo::AddrInfo(const AddrInfo* src) {
+ mHostName = src->mHostName;
+ mCanonicalName = src->mCanonicalName;
+ ttl = src->ttl;
+ mResolverType = src->mResolverType;
+ mTRRType = src->mTRRType;
+ mTrrFetchDuration = src->mTrrFetchDuration;
+ mTrrFetchDurationNetworkOnly = src->mTrrFetchDurationNetworkOnly;
+
+ mAddresses = src->mAddresses.Clone();
+}
+
+AddrInfo::~AddrInfo() = default;
+
+size_t AddrInfo::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+ n += mHostName.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mCanonicalName.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mAddresses.ShallowSizeOfExcludingThis(mallocSizeOf);
+ return n;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNS.h b/netwerk/dns/DNS.h
new file mode 100644
index 0000000000..a0dbbaa17d
--- /dev/null
+++ b/netwerk/dns/DNS.h
@@ -0,0 +1,260 @@
+/* -*- 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 DNS_h_
+#define DNS_h_
+
+#include "nscore.h"
+#include "nsString.h"
+#include "prio.h"
+#include "prnetdb.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsTArray.h"
+
+#if !defined(XP_WIN)
+# include <arpa/inet.h>
+#endif
+
+#ifdef XP_WIN
+# include "winsock2.h"
+#endif
+
+#ifndef AF_LOCAL
+# define AF_LOCAL 1 // used for named pipe
+#endif
+
+#define IPv6ADDR_IS_LOOPBACK(a) \
+ (((a)->u32[0] == 0) && ((a)->u32[1] == 0) && ((a)->u32[2] == 0) && \
+ ((a)->u8[12] == 0) && ((a)->u8[13] == 0) && ((a)->u8[14] == 0) && \
+ ((a)->u8[15] == 0x1U))
+
+#define IPv6ADDR_IS_V4MAPPED(a) \
+ (((a)->u32[0] == 0) && ((a)->u32[1] == 0) && ((a)->u8[8] == 0) && \
+ ((a)->u8[9] == 0) && ((a)->u8[10] == 0xff) && ((a)->u8[11] == 0xff))
+
+#define IPv6ADDR_V4MAPPED_TO_IPADDR(a) ((a)->u32[3])
+
+#define IPv6ADDR_IS_UNSPECIFIED(a) \
+ (((a)->u32[0] == 0) && ((a)->u32[1] == 0) && ((a)->u32[2] == 0) && \
+ ((a)->u32[3] == 0))
+
+namespace mozilla {
+namespace net {
+
+// IMPORTANT: when adding new values, always add them to the end, otherwise
+// it will mess up telemetry.
+// Stage_0: Receive the record before the http transaction is created.
+// Stage_1: Receive the record after the http transaction is created and the
+// transaction is not dispatched.
+// Stage_2: Receive the record after the http transaction is dispatched.
+enum HTTPSSVC_RECEIVED_STAGE : uint32_t {
+ HTTPSSVC_NOT_PRESENT = 0,
+ HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_0 = 1,
+ HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_0 = 2,
+ HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_1 = 3,
+ HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_1 = 4,
+ HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_2 = 5,
+ HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_2 = 6,
+ HTTPSSVC_NOT_USED = 7,
+ HTTPSSVC_NO_USABLE_RECORD = 8,
+};
+
+#define HTTPS_RR_IS_USED(s) \
+ (s > HTTPSSVC_NOT_PRESENT && s < HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_2)
+
+// Required buffer size for text form of an IP address.
+// Includes space for null termination. We make our own contants
+// because we don't want higher-level code depending on things
+// like INET6_ADDRSTRLEN and having to include the associated
+// platform-specific headers.
+#ifdef XP_WIN
+// Windows requires longer buffers for some reason.
+const int kIPv4CStrBufSize = 22;
+const int kIPv6CStrBufSize = 65;
+const int kNetAddrMaxCStrBufSize = kIPv6CStrBufSize;
+#else
+const int kIPv4CStrBufSize = 16;
+const int kIPv6CStrBufSize = 46;
+const int kLocalCStrBufSize = 108;
+const int kNetAddrMaxCStrBufSize = kLocalCStrBufSize;
+#endif
+
+// This was all created at a time in which we were using NSPR for host
+// resolution and we were propagating NSPR types like "PRAddrInfo" and
+// "PRNetAddr" all over Gecko. This made it hard to use another host
+// resolver -- we were locked into NSPR. The goal here is to get away
+// from that. We'll translate what we get from NSPR or any other host
+// resolution library into the types below and use them in Gecko.
+
+union IPv6Addr {
+ uint8_t u8[16];
+ uint16_t u16[8];
+ uint32_t u32[4];
+ uint64_t u64[2];
+};
+
+// This struct is similar to operating system structs like "sockaddr", used for
+// things like "connect" and "getsockname". When tempted to cast or do dumb
+// copies of this struct to another struct, bear compiler-computed padding
+// in mind. The size of this struct, and the layout of the data in it, may
+// not be what you expect.
+union NetAddr {
+ struct {
+ uint16_t family; /* address family (0x00ff maskable) */
+ char data[14]; /* raw address data */
+ } raw{};
+ struct {
+ uint16_t family; /* address family (AF_INET) */
+ uint16_t port; /* port number */
+ uint32_t ip; /* The actual 32 bits of address */
+ } inet;
+ struct {
+ uint16_t family; /* address family (AF_INET6) */
+ uint16_t port; /* port number */
+ uint32_t flowinfo; /* routing information */
+ IPv6Addr ip; /* the actual 128 bits of address */
+ uint32_t scope_id; /* set of interfaces for a scope */
+ } inet6;
+#if defined(XP_UNIX) || defined(XP_WIN)
+ struct { /* Unix domain socket or
+ Windows Named Pipes address */
+ uint16_t family; /* address family (AF_UNIX) */
+ char path[104]; /* null-terminated pathname */
+ } local;
+#endif
+ // introduced to support nsTArray<NetAddr> comparisons and sorting
+ bool operator==(const NetAddr& other) const;
+ bool operator<(const NetAddr& other) const;
+
+ inline NetAddr& operator=(const NetAddr& other) {
+ memcpy(this, &other, sizeof(NetAddr));
+ return *this;
+ }
+
+ NetAddr() { memset(this, 0, sizeof(NetAddr)); }
+ explicit NetAddr(const PRNetAddr* prAddr);
+
+ // Will parse aString into a NetAddr using PR_StringToNetAddr.
+ // Returns an error code if parsing fails.
+ // If aPort is non-0 will set the NetAddr's port to (the network endian
+ // value of) that.
+ nsresult InitFromString(const nsACString& aString, uint16_t aPort = 0);
+
+ bool IsIPAddrAny() const;
+ bool IsLoopbackAddr() const;
+ bool IsLoopBackAddressWithoutIPv6Mapping() const;
+ bool IsIPAddrV4() const;
+ bool IsIPAddrV4Mapped() const;
+ bool IsIPAddrLocal() const;
+ bool IsIPAddrShared() const;
+ nsresult GetPort(uint16_t* aResult) const;
+ bool ToStringBuffer(char* buf, uint32_t bufSize) const;
+ nsCString ToString() const;
+};
+
+enum class DNSResolverType : uint32_t { Native = 0, TRR };
+
+class AddrInfo {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AddrInfo)
+
+ public:
+ static const uint32_t NO_TTL_DATA = (uint32_t)-1;
+
+ // Creates an AddrInfo object.
+ explicit AddrInfo(const nsACString& host, const PRAddrInfo* prAddrInfo,
+ bool disableIPv4, bool filterNameCollision,
+ const nsACString& cname);
+
+ // Creates a basic AddrInfo object (initialize only the host, cname and TRR
+ // type).
+ explicit AddrInfo(const nsACString& host, const nsACString& cname,
+ DNSResolverType aResolverType, unsigned int aTRRType,
+ nsTArray<NetAddr>&& addresses);
+
+ // Creates a basic AddrInfo object (initialize only the host and TRR status).
+ explicit AddrInfo(const nsACString& host, DNSResolverType aResolverType,
+ unsigned int aTRRType, nsTArray<NetAddr>&& addresses,
+ uint32_t aTTL = NO_TTL_DATA);
+
+ explicit AddrInfo(const AddrInfo* src); // copy
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ bool IsTRR() const { return mResolverType == DNSResolverType::TRR; }
+ DNSResolverType ResolverType() const { return mResolverType; }
+ unsigned int TRRType() { return mTRRType; }
+
+ double GetTrrFetchDuration() { return mTrrFetchDuration; }
+ double GetTrrFetchDurationNetworkOnly() {
+ return mTrrFetchDurationNetworkOnly;
+ }
+
+ const nsTArray<NetAddr>& Addresses() { return mAddresses; }
+ const nsCString& Hostname() { return mHostName; }
+ const nsCString& CanonicalHostname() { return mCanonicalName; }
+ uint32_t TTL() { return ttl; }
+
+ class MOZ_STACK_CLASS AddrInfoBuilder {
+ public:
+ explicit AddrInfoBuilder(AddrInfo* aInfo) {
+ mInfo = new AddrInfo(aInfo); // Clone it
+ }
+
+ void SetTrrFetchDurationNetworkOnly(double aTime) {
+ mInfo->mTrrFetchDurationNetworkOnly = aTime;
+ }
+
+ void SetTrrFetchDuration(double aTime) { mInfo->mTrrFetchDuration = aTime; }
+
+ void SetTTL(uint32_t aTTL) { mInfo->ttl = aTTL; }
+
+ void SetAddresses(nsTArray<NetAddr>&& addresses) {
+ mInfo->mAddresses = std::move(addresses);
+ }
+
+ void SetCanonicalHostname(const nsACString& aCname) {
+ mInfo->mCanonicalName = aCname;
+ }
+
+ already_AddRefed<AddrInfo> Finish() { return mInfo.forget(); }
+
+ private:
+ RefPtr<AddrInfo> mInfo;
+ };
+
+ AddrInfoBuilder Build() { return AddrInfoBuilder(this); }
+
+ private:
+ ~AddrInfo();
+ uint32_t ttl = NO_TTL_DATA;
+
+ nsCString mHostName;
+ nsCString mCanonicalName;
+ DNSResolverType mResolverType = DNSResolverType::Native;
+ unsigned int mTRRType = 0;
+ double mTrrFetchDuration = 0;
+ double mTrrFetchDurationNetworkOnly = 0;
+
+ nsTArray<NetAddr> mAddresses;
+};
+
+// Copies the contents of a PRNetAddr to a NetAddr.
+// Does not do a ptr safety check!
+void PRNetAddrToNetAddr(const PRNetAddr* prAddr, NetAddr* addr);
+
+// Copies the contents of a NetAddr to a PRNetAddr.
+// Does not do a ptr safety check!
+void NetAddrToPRNetAddr(const NetAddr* addr, PRNetAddr* prAddr);
+
+bool IsLoopbackHostname(const nsACString& aAsciiHost);
+
+bool HostIsIPLiteral(const nsACString& aAsciiHost);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DNS_h_
diff --git a/netwerk/dns/DNSAdditionalInfo.cpp b/netwerk/dns/DNSAdditionalInfo.cpp
new file mode 100644
index 0000000000..885e9e10dc
--- /dev/null
+++ b/netwerk/dns/DNSAdditionalInfo.cpp
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DNSAdditionalInfo.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DNSAdditionalInfo, nsIDNSAdditionalInfo)
+
+NS_IMETHODIMP
+DNSAdditionalInfo::GetPort(int32_t* aPort) {
+ *aPort = mPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DNSAdditionalInfo::GetResolverURL(nsACString& aURL) {
+ aURL = mURL;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSAdditionalInfo.h b/netwerk/dns/DNSAdditionalInfo.h
new file mode 100644
index 0000000000..658b9623dd
--- /dev/null
+++ b/netwerk/dns/DNSAdditionalInfo.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 mozilla_net_DNSAdditionalInfo_h__
+#define mozilla_net_DNSAdditionalInfo_h__
+
+#include "nsIDNSAdditionalInfo.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSAdditionalInfo : public nsIDNSAdditionalInfo {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSADDITIONALINFO
+ public:
+ explicit DNSAdditionalInfo(const nsACString& aURL, int32_t aPort)
+ : mURL(aURL), mPort(aPort){};
+ static nsCString URL(nsIDNSAdditionalInfo* aInfo) {
+ nsCString url;
+ if (aInfo) {
+ MOZ_ALWAYS_SUCCEEDS(aInfo->GetResolverURL(url));
+ }
+ return url;
+ }
+ static int32_t Port(nsIDNSAdditionalInfo* aInfo) {
+ int32_t port = -1;
+ if (aInfo) {
+ MOZ_ALWAYS_SUCCEEDS(aInfo->GetPort(&port));
+ }
+ return port;
+ }
+
+ private:
+ virtual ~DNSAdditionalInfo() = default;
+ nsCString mURL;
+ int32_t mPort;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DNSAdditionalInfo_h__
diff --git a/netwerk/dns/DNSByTypeRecord.h b/netwerk/dns/DNSByTypeRecord.h
new file mode 100644
index 0000000000..8b80787698
--- /dev/null
+++ b/netwerk/dns/DNSByTypeRecord.h
@@ -0,0 +1,259 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DNSByTypeRecord_h__
+#define DNSByTypeRecord_h__
+
+#include "mozilla/net/HTTPSSVC.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/net/NeckoMessageUtils.h"
+
+namespace mozilla {
+namespace net {
+
+// The types of nsIDNSByTypeRecord: Nothing, TXT, HTTPSSVC
+using TypeRecordEmpty = Nothing;
+using TypeRecordTxt = CopyableTArray<nsCString>;
+using TypeRecordHTTPSSVC = CopyableTArray<SVCB>;
+
+// This variant reflects the multiple types of data a nsIDNSByTypeRecord
+// can hold.
+using TypeRecordResultType =
+ Variant<TypeRecordEmpty, TypeRecordTxt, TypeRecordHTTPSSVC>;
+
+// TypeRecordResultType is a variant, but since it doesn't have a default
+// constructor it's not a type we can pass directly over IPC.
+struct IPCTypeRecord {
+ bool operator==(const IPCTypeRecord& aOther) const {
+ return mData == aOther.mData;
+ }
+ explicit IPCTypeRecord() : mData(Nothing{}) {}
+ TypeRecordResultType mData;
+ uint32_t mTTL = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+namespace mozilla {
+namespace ipc {
+
+template <>
+struct IPDLParamTraits<mozilla::net::IPCTypeRecord> {
+ typedef mozilla::net::IPCTypeRecord paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mData);
+ WriteIPDLParam(aWriter, aActor, aParam.mTTL);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mData)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mTTL)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::Nothing> {
+ typedef mozilla::Nothing paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ bool isSome = false;
+ WriteIPDLParam(aWriter, aActor, isSome);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ bool isSome;
+ if (!ReadIPDLParam(aReader, aActor, &isSome)) {
+ return false;
+ }
+ *aResult = Nothing();
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SVCB> {
+ typedef mozilla::net::SVCB paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mSvcFieldPriority);
+ WriteIPDLParam(aWriter, aActor, aParam.mSvcDomainName);
+ WriteIPDLParam(aWriter, aActor, aParam.mEchConfig);
+ WriteIPDLParam(aWriter, aActor, aParam.mODoHConfig);
+ WriteIPDLParam(aWriter, aActor, aParam.mHasIPHints);
+ WriteIPDLParam(aWriter, aActor, aParam.mHasEchConfig);
+ WriteIPDLParam(aWriter, aActor, aParam.mSvcFieldValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mSvcFieldPriority)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mSvcDomainName)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mEchConfig)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mODoHConfig)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mHasIPHints)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mHasEchConfig)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mSvcFieldValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamAlpn> {
+ typedef mozilla::net::SvcParamAlpn paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamNoDefaultAlpn> {
+ typedef mozilla::net::SvcParamNoDefaultAlpn paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {}
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamPort> {
+ typedef mozilla::net::SvcParamPort paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamIpv4Hint> {
+ typedef mozilla::net::SvcParamIpv4Hint paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamEchConfig> {
+ typedef mozilla::net::SvcParamEchConfig paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamIpv6Hint> {
+ typedef mozilla::net::SvcParamIpv6Hint paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamODoHConfig> {
+ typedef mozilla::net::SvcParamODoHConfig paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcFieldValue> {
+ typedef mozilla::net::SvcFieldValue paramType;
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // DNSByTypeRecord_h__
diff --git a/netwerk/dns/DNSListenerProxy.cpp b/netwerk/dns/DNSListenerProxy.cpp
new file mode 100644
index 0000000000..8468edca2d
--- /dev/null
+++ b/netwerk/dns/DNSListenerProxy.cpp
@@ -0,0 +1,37 @@
+/* -*- 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 "mozilla/net/DNSListenerProxy.h"
+#include "nsICancelable.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(DNSListenerProxy)
+NS_IMPL_RELEASE(DNSListenerProxy)
+NS_INTERFACE_MAP_BEGIN(DNSListenerProxy)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(DNSListenerProxy)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+DNSListenerProxy::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord, nsresult aStatus) {
+ RefPtr<DNSListenerProxy> self = this;
+ nsCOMPtr<nsICancelable> request = aRequest;
+ nsCOMPtr<nsIDNSRecord> record = aRecord;
+ return mTargetThread->Dispatch(
+ NS_NewRunnableFunction("DNSListenerProxy::OnLookupComplete",
+ [self, request, record, aStatus]() {
+ Unused << self->mListener->OnLookupComplete(
+ request, record, aStatus);
+ self->mListener = nullptr;
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSListenerProxy.h b/netwerk/dns/DNSListenerProxy.h
new file mode 100644
index 0000000000..5baddfd8e7
--- /dev/null
+++ b/netwerk/dns/DNSListenerProxy.h
@@ -0,0 +1,62 @@
+/* -*- 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 DNSListenerProxy_h__
+#define DNSListenerProxy_h__
+
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+class nsIEventTarget;
+class nsICancelable;
+
+namespace mozilla {
+namespace net {
+
+#define DNS_LISTENER_PROXY_IID \
+ { \
+ 0x8f172ca3, 0x7a7f, 0x4941, { \
+ 0xa7, 0x0b, 0xbc, 0x72, 0x80, 0x2e, 0x9d, 0x9b \
+ } \
+ }
+
+class DNSListenerProxy final : public nsIDNSListener {
+ public:
+ DNSListenerProxy(nsIDNSListener* aListener, nsIEventTarget* aTargetThread)
+ // We want to make sure that |aListener| is only accessed on the target
+ // thread.
+ : mListener(aListener),
+ mTargetThread(aTargetThread),
+ mListenerAddress(reinterpret_cast<uintptr_t>(aListener)) {
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mTargetThread);
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+ NS_DECLARE_STATIC_IID_ACCESSOR(DNS_LISTENER_PROXY_IID)
+
+ uintptr_t GetOriginalListenerAddress() const { return mListenerAddress; }
+
+ private:
+ ~DNSListenerProxy() {
+ NS_ProxyRelease("DNSListenerProxy::mListener", mTargetThread,
+ mListener.forget());
+ }
+
+ nsCOMPtr<nsIDNSListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+ uintptr_t mListenerAddress;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DNSListenerProxy, DNS_LISTENER_PROXY_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DNSListenerProxy_h__
diff --git a/netwerk/dns/DNSLogging.h b/netwerk/dns/DNSLogging.h
new file mode 100644
index 0000000000..4fd63730ca
--- /dev/null
+++ b/netwerk/dns/DNSLogging.h
@@ -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/. */
+
+#ifndef mozilla_DNSLogging_h
+#define mozilla_DNSLogging_h
+
+#include "mozilla/Logging.h"
+
+#undef LOG
+
+namespace mozilla {
+namespace net {
+extern LazyLogModule gHostResolverLog;
+} // namespace net
+} // namespace mozilla
+
+#define LOG(msg) \
+ MOZ_LOG(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug, msg)
+#define LOG1(msg) \
+ MOZ_LOG(mozilla::net::gHostResolverLog, mozilla::LogLevel::Error, msg)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug)
+
+#endif // mozilla_DNSLogging_h
diff --git a/netwerk/dns/DNSPacket.cpp b/netwerk/dns/DNSPacket.cpp
new file mode 100644
index 0000000000..6aef801d63
--- /dev/null
+++ b/netwerk/dns/DNSPacket.cpp
@@ -0,0 +1,1100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DNSPacket.h"
+
+#include "DNS.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
+#include "DNSLogging.h"
+
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+static uint16_t get16bit(const unsigned char* aData, unsigned int index) {
+ return ((aData[index] << 8) | aData[index + 1]);
+}
+
+static uint32_t get32bit(const unsigned char* aData, unsigned int index) {
+ return (aData[index] << 24) | (aData[index + 1] << 16) |
+ (aData[index + 2] << 8) | aData[index + 3];
+}
+
+// https://datatracker.ietf.org/doc/html/rfc8914#name-defined-extended-dns-errors
+// This is a list of errors for which we should not fallback to Do53.
+// These are normally explicit filtering performed by the recursive resolver.
+bool hardFail(uint16_t code) {
+ const uint16_t noFallbackErrors[] = {
+ 4, // Forged answer (malware filtering)
+ 17, // Filtered
+ };
+
+ for (const auto& err : noFallbackErrors) {
+ if (code == err) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult DNSPacket::FillBuffer(
+ std::function<int(unsigned char response[MAX_SIZE])>&& aPredicate) {
+ int response_length = aPredicate(mResponse);
+ if (response_length < 0) {
+ LOG(("FillBuffer response len < 0"));
+ mBodySize = 0;
+ mStatus = NS_ERROR_UNEXPECTED;
+ return mStatus;
+ }
+
+ mBodySize = response_length;
+ return NS_OK;
+}
+// static
+nsresult DNSPacket::ParseSvcParam(unsigned int svcbIndex, uint16_t key,
+ SvcFieldValue& field, uint16_t length,
+ const unsigned char* aBuffer) {
+ switch (key) {
+ case SvcParamKeyMandatory: {
+ if (length % 2 != 0) {
+ // This key should encode a list of uint16_t
+ return NS_ERROR_UNEXPECTED;
+ }
+ while (length > 0) {
+ uint16_t mandatoryKey = get16bit(aBuffer, svcbIndex);
+ length -= 2;
+ svcbIndex += 2;
+
+ if (!IsValidSvcParamKey(mandatoryKey)) {
+ LOG(("The mandatory field includes a key we don't support %u",
+ mandatoryKey));
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ break;
+ }
+ case SvcParamKeyAlpn: {
+ field.mValue = AsVariant(SvcParamAlpn());
+ auto& alpnArray = field.mValue.as<SvcParamAlpn>().mValue;
+ while (length > 0) {
+ uint8_t alpnIdLength = aBuffer[svcbIndex++];
+ length -= 1;
+ if (alpnIdLength > length) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ alpnArray.AppendElement(
+ nsCString((const char*)&aBuffer[svcbIndex], alpnIdLength));
+ length -= alpnIdLength;
+ svcbIndex += alpnIdLength;
+ }
+ break;
+ }
+ case SvcParamKeyNoDefaultAlpn: {
+ if (length != 0) {
+ // This key should not contain a value
+ return NS_ERROR_UNEXPECTED;
+ }
+ field.mValue = AsVariant(SvcParamNoDefaultAlpn{});
+ break;
+ }
+ case SvcParamKeyPort: {
+ if (length != 2) {
+ // This key should only encode a uint16_t
+ return NS_ERROR_UNEXPECTED;
+ }
+ field.mValue =
+ AsVariant(SvcParamPort{.mValue = get16bit(aBuffer, svcbIndex)});
+ break;
+ }
+ case SvcParamKeyIpv4Hint: {
+ if (length % 4 != 0) {
+ // This key should only encode IPv4 addresses
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ field.mValue = AsVariant(SvcParamIpv4Hint());
+ auto& ipv4array = field.mValue.as<SvcParamIpv4Hint>().mValue;
+ while (length > 0) {
+ NetAddr addr;
+ addr.inet.family = AF_INET;
+ addr.inet.port = 0;
+ addr.inet.ip = ntohl(get32bit(aBuffer, svcbIndex));
+ ipv4array.AppendElement(addr);
+ length -= 4;
+ svcbIndex += 4;
+ }
+ break;
+ }
+ case SvcParamKeyEchConfig: {
+ field.mValue = AsVariant(SvcParamEchConfig{
+ .mValue = nsCString((const char*)(&aBuffer[svcbIndex]), length)});
+ break;
+ }
+ case SvcParamKeyIpv6Hint: {
+ if (length % 16 != 0) {
+ // This key should only encode IPv6 addresses
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ field.mValue = AsVariant(SvcParamIpv6Hint());
+ auto& ipv6array = field.mValue.as<SvcParamIpv6Hint>().mValue;
+ while (length > 0) {
+ NetAddr addr;
+ addr.inet6.family = AF_INET6;
+ addr.inet6.port = 0; // unknown
+ addr.inet6.flowinfo = 0; // unknown
+ addr.inet6.scope_id = 0; // unknown
+ for (int i = 0; i < 16; i++, svcbIndex++) {
+ addr.inet6.ip.u8[i] = aBuffer[svcbIndex];
+ }
+ ipv6array.AppendElement(addr);
+ length -= 16;
+ // no need to increase svcbIndex - we did it in the for above.
+ }
+ break;
+ }
+ case SvcParamKeyODoHConfig: {
+ field.mValue = AsVariant(SvcParamODoHConfig{
+ .mValue = nsCString((const char*)(&aBuffer[svcbIndex]), length)});
+ break;
+ }
+ default: {
+ // Unespected type. We'll just ignore it.
+ return NS_OK;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult DNSPacket::PassQName(unsigned int& index,
+ const unsigned char* aBuffer) {
+ uint8_t length;
+ do {
+ if (mBodySize < (index + 1)) {
+ LOG(("TRR: PassQName:%d fail at index %d\n", __LINE__, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ length = static_cast<uint8_t>(aBuffer[index]);
+ if ((length & 0xc0) == 0xc0) {
+ // name pointer, advance over it and be done
+ if (mBodySize < (index + 2)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ index += 2;
+ break;
+ }
+ if (length & 0xc0) {
+ LOG(("TRR: illegal label length byte (%x) at index %d\n", length, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // pass label
+ if (mBodySize < (index + 1 + length)) {
+ LOG(("TRR: PassQName:%d fail at index %d\n", __LINE__, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ index += 1 + length;
+ } while (length);
+ return NS_OK;
+}
+
+// GetQname: retrieves the qname (stores in 'aQname') and stores the index
+// after qname was parsed into the 'aIndex'.
+// static
+nsresult DNSPacket::GetQname(nsACString& aQname, unsigned int& aIndex,
+ const unsigned char* aBuffer,
+ unsigned int aBodySize) {
+ uint8_t clength = 0;
+ unsigned int cindex = aIndex;
+ unsigned int loop = 128; // a valid DNS name can never loop this much
+ unsigned int endindex = 0; // index position after this data
+ do {
+ if (cindex >= aBodySize) {
+ LOG(("TRR: bad Qname packet\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ clength = static_cast<uint8_t>(aBuffer[cindex]);
+ if ((clength & 0xc0) == 0xc0) {
+ // name pointer, get the new offset (14 bits)
+ if ((cindex + 1) >= aBodySize) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // extract the new index position for the next label
+ uint16_t newpos = (clength & 0x3f) << 8 | aBuffer[cindex + 1];
+ if (!endindex) {
+ // only update on the first "jump"
+ endindex = cindex + 2;
+ }
+ cindex = newpos;
+ continue;
+ }
+ if (clength & 0xc0) {
+ // any of those bits set individually is an error
+ LOG(("TRR: bad Qname packet\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ cindex++;
+
+ if (clength) {
+ if (!aQname.IsEmpty()) {
+ aQname.Append(".");
+ }
+ if ((cindex + clength) > aBodySize) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aQname.Append((const char*)(&aBuffer[cindex]), clength);
+ cindex += clength; // skip label
+ }
+ } while (clength && --loop);
+
+ if (!loop) {
+ LOG(("DNSPacket::DohDecode pointer loop error\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (!endindex) {
+ // there was no "jump"
+ endindex = cindex;
+ }
+ aIndex = endindex;
+ return NS_OK;
+}
+
+nsresult DOHresp::Add(uint32_t TTL, unsigned char const* dns,
+ unsigned int index, uint16_t len, bool aLocalAllowed) {
+ NetAddr addr;
+ if (4 == len) {
+ // IPv4
+ addr.inet.family = AF_INET;
+ addr.inet.port = 0; // unknown
+ addr.inet.ip = ntohl(get32bit(dns, index));
+ } else if (16 == len) {
+ // IPv6
+ addr.inet6.family = AF_INET6;
+ addr.inet6.port = 0; // unknown
+ addr.inet6.flowinfo = 0; // unknown
+ addr.inet6.scope_id = 0; // unknown
+ for (int i = 0; i < 16; i++, index++) {
+ addr.inet6.ip.u8[i] = dns[index];
+ }
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (addr.IsIPAddrLocal() && !aLocalAllowed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // While the DNS packet might return individual TTLs for each address,
+ // we can only return one value in the AddrInfo class so pick the
+ // lowest number.
+ if (mTtl < TTL) {
+ mTtl = TTL;
+ }
+
+ if (LOG_ENABLED()) {
+ char buf[128];
+ addr.ToStringBuffer(buf, sizeof(buf));
+ LOG(("DOHresp:Add %s\n", buf));
+ }
+ mAddresses.AppendElement(addr);
+ return NS_OK;
+}
+
+nsresult DNSPacket::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, const uint32_t aCount) {
+ if (aCount + mBodySize > MAX_SIZE) {
+ LOG(("DNSPacket::OnDataAvailable:%d fail\n", __LINE__));
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t count;
+ nsresult rv =
+ aInputStream->Read((char*)mResponse + mBodySize, aCount, &count);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MOZ_ASSERT(count == aCount);
+ mBodySize += aCount;
+ return NS_OK;
+}
+
+const uint8_t kDNS_CLASS_IN = 1;
+
+nsresult DNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost,
+ uint16_t aType, bool aDisableECS) {
+ aBody.Truncate();
+ // Header
+ aBody += '\0';
+ aBody += '\0'; // 16 bit id
+ aBody += 0x01; // |QR| Opcode |AA|TC|RD| Set the RD bit
+ aBody += '\0'; // |RA| Z | RCODE |
+ aBody += '\0';
+ aBody += 1; // QDCOUNT (number of entries in the question section)
+ aBody += '\0';
+ aBody += '\0'; // ANCOUNT
+ aBody += '\0';
+ aBody += '\0'; // NSCOUNT
+
+ char additionalRecords =
+ (aDisableECS || StaticPrefs::network_trr_padding()) ? 1 : 0;
+ aBody += '\0'; // ARCOUNT
+ aBody += additionalRecords; // ARCOUNT low byte for EDNS(0)
+
+ // Question
+
+ // The input host name should be converted to a sequence of labels, where
+ // each label consists of a length octet followed by that number of
+ // octets. The domain name terminates with the zero length octet for the
+ // null label of the root.
+ // Followed by 16 bit QTYPE and 16 bit QCLASS
+
+ int32_t index = 0;
+ int32_t offset = 0;
+ do {
+ bool dotFound = false;
+ int32_t labelLength;
+ index = aHost.FindChar('.', offset);
+ if (kNotFound != index) {
+ dotFound = true;
+ labelLength = index - offset;
+ } else {
+ labelLength = aHost.Length() - offset;
+ }
+ if (labelLength > 63) {
+ // too long label!
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (labelLength > 0) {
+ aBody += static_cast<unsigned char>(labelLength);
+ nsDependentCSubstring label = Substring(aHost, offset, labelLength);
+ aBody.Append(label);
+ }
+ if (!dotFound) {
+ aBody += '\0'; // terminate with a final zero
+ break;
+ }
+ offset += labelLength + 1; // move over label and dot
+ } while (true);
+
+ aBody += static_cast<uint8_t>(aType >> 8); // upper 8 bit TYPE
+ aBody += static_cast<uint8_t>(aType);
+ aBody += '\0'; // upper 8 bit CLASS
+ aBody += kDNS_CLASS_IN; // IN - "the Internet"
+
+ if (additionalRecords) {
+ // EDNS(0) is RFC 6891, ECS is RFC 7871
+ aBody += '\0'; // NAME | domain name | MUST be 0 (root domain) |
+ aBody += '\0';
+ aBody += 41; // TYPE | u_int16_t | OPT (41) |
+ aBody += 16; // CLASS | u_int16_t | requestor's UDP payload size |
+ aBody +=
+ '\0'; // advertise 4K (high-byte: 16 | low-byte: 0), ignored by DoH
+ aBody += '\0'; // TTL | u_int32_t | extended RCODE and flags |
+ aBody += '\0';
+ aBody += '\0';
+ aBody += '\0';
+
+ // calculate padding length
+ unsigned int paddingLen = 0;
+ unsigned int rdlen = 0;
+ bool padding = StaticPrefs::network_trr_padding();
+ if (padding) {
+ // always add padding specified in rfc 7830 when this config is enabled
+ // to allow the reponse to be padded as well
+
+ // two bytes RDLEN, 4 bytes padding header
+ unsigned int packetLen = aBody.Length() + 2 + 4;
+ if (aDisableECS) {
+ // 8 bytes for disabling ecs
+ packetLen += 8;
+ }
+
+ // clamp the padding length, because the padding extension only allows up
+ // to 2^16 - 1 bytes padding and adding too much padding wastes resources
+ uint32_t padTo = std::clamp<uint32_t>(
+ StaticPrefs::network_trr_padding_length(), 0, 1024);
+
+ // Calculate number of padding bytes. The second '%'-operator is necessary
+ // because we prefer to add 0 bytes padding rather than padTo bytes
+ if (padTo > 0) {
+ paddingLen = (padTo - (packetLen % padTo)) % padTo;
+ }
+ // padding header + padding length
+ rdlen += 4 + paddingLen;
+ }
+ if (aDisableECS) {
+ rdlen += 8;
+ }
+
+ // RDLEN | u_int16_t | length of all RDATA |
+ aBody += (char)((rdlen >> 8) & 0xff); // upper 8 bit RDLEN
+ aBody += (char)(rdlen & 0xff);
+
+ // RDATA | octet stream | {attribute,value} pairs |
+ // The RDATA is just the ECS option setting zero subnet prefix
+
+ if (aDisableECS) {
+ aBody += '\0'; // upper 8 bit OPTION-CODE ECS
+ aBody += 8; // OPTION-CODE, 2 octets, for ECS is 8
+
+ aBody += '\0'; // upper 8 bit OPTION-LENGTH
+ aBody += 4; // OPTION-LENGTH, 2 octets, contains the length of the
+ // payload after OPTION-LENGTH
+ aBody += '\0'; // upper 8 bit FAMILY. IANA Address Family Numbers
+ // registry, not the AF_* constants!
+ aBody += 1; // FAMILY (Ipv4), 2 octets
+
+ aBody += '\0'; // SOURCE PREFIX-LENGTH | SCOPE PREFIX-LENGTH |
+ aBody += '\0';
+
+ // ADDRESS, minimum number of octets == nothing because zero bits
+ }
+
+ if (padding) {
+ aBody += '\0'; // upper 8 bit option OPTION-CODE PADDING
+ aBody += 12; // OPTION-CODE, 2 octets, for PADDING is 12
+
+ // OPTION-LENGTH, 2 octets
+ aBody += (char)((paddingLen >> 8) & 0xff);
+ aBody += (char)(paddingLen & 0xff);
+ for (unsigned int i = 0; i < paddingLen; i++) {
+ aBody += '\0';
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult DNSPacket::ParseHTTPS(uint16_t aRDLen, struct SVCB& aParsed,
+ unsigned int aIndex,
+ const unsigned char* aBuffer,
+ unsigned int aBodySize,
+ const nsACString& aOriginHost) {
+ int32_t lastSvcParamKey = -1;
+ nsresult rv = NS_OK;
+ unsigned int svcbIndex = aIndex;
+ CheckedInt<uint16_t> available = aRDLen;
+
+ // Should have at least 2 bytes for the priority and one for the
+ // qname length.
+ if (available.value() < 3) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aParsed.mSvcFieldPriority = get16bit(aBuffer, svcbIndex);
+ svcbIndex += 2;
+
+ rv = GetQname(aParsed.mSvcDomainName, svcbIndex, aBuffer, aBodySize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aParsed.mSvcDomainName.IsEmpty()) {
+ if (aParsed.mSvcFieldPriority == 0) {
+ // For AliasMode SVCB RRs, a TargetName of "." indicates that
+ // the service is not available or does not exist.
+ return NS_OK;
+ }
+
+ // For ServiceMode SVCB RRs, if TargetName has the value ".",
+ // then the owner name of this record MUST be used as
+ // the effective TargetName.
+ // When the qname is port prefix name, we need to use the
+ // original host name as TargetName.
+ aParsed.mSvcDomainName = aOriginHost;
+ }
+
+ available -= (svcbIndex - aIndex);
+ if (!available.isValid()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ while (available.value() >= 4) {
+ // Every SvcFieldValues must have at least 4 bytes for the
+ // SvcParamKey (2 bytes) and length of SvcParamValue (2 bytes)
+ // If the length ever goes above the available data, meaning if
+ // available ever underflows, then that is an error.
+ struct SvcFieldValue value;
+ uint16_t key = get16bit(aBuffer, svcbIndex);
+ svcbIndex += 2;
+
+ // 2.2 Clients MUST consider an RR malformed if SvcParamKeys are
+ // not in strictly increasing numeric order.
+ if (key <= lastSvcParamKey) {
+ LOG(("SvcParamKeys not in increasing order"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ lastSvcParamKey = key;
+
+ uint16_t len = get16bit(aBuffer, svcbIndex);
+ svcbIndex += 2;
+
+ available -= 4 + len;
+ if (!available.isValid()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = ParseSvcParam(svcbIndex, key, value, len, aBuffer);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ svcbIndex += len;
+
+ // If this is an unknown key, we will simply ignore it.
+ // We also don't need to record SvcParamKeyMandatory
+ if (key == SvcParamKeyMandatory || !IsValidSvcParamKey(key)) {
+ continue;
+ }
+
+ if (value.mValue.is<SvcParamIpv4Hint>() ||
+ value.mValue.is<SvcParamIpv6Hint>()) {
+ aParsed.mHasIPHints = true;
+ }
+ if (value.mValue.is<SvcParamEchConfig>()) {
+ aParsed.mHasEchConfig = true;
+ aParsed.mEchConfig = value.mValue.as<SvcParamEchConfig>().mValue;
+ }
+ if (value.mValue.is<SvcParamODoHConfig>()) {
+ aParsed.mODoHConfig = value.mValue.as<SvcParamODoHConfig>().mValue;
+ }
+ aParsed.mSvcFieldValue.AppendElement(value);
+ }
+
+ return NS_OK;
+}
+
+Result<uint8_t, nsresult> DNSPacket::GetRCode() const {
+ if (mBodySize < 12) {
+ LOG(("DNSPacket::GetRCode - packet too small"));
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ return mResponse[3] & 0x0F;
+}
+
+Result<bool, nsresult> DNSPacket::RecursionAvailable() const {
+ if (mBodySize < 12) {
+ LOG(("DNSPacket::GetRCode - packet too small"));
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ return mResponse[3] & 0x80;
+}
+
+nsresult DNSPacket::DecodeInternal(
+ nsCString& aHost, enum TrrType aType, nsCString& aCname, bool aAllowRFC1918,
+ DOHresp& aResp, TypeRecordResultType& aTypeResult,
+ nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
+ uint32_t& aTTL, const unsigned char* aBuffer, uint32_t aLen) {
+ // The response has a 12 byte header followed by the question (returned)
+ // and then the answer. The answer section itself contains the name, type
+ // and class again and THEN the record data.
+
+ // www.example.com response:
+ // header:
+ // abcd 8180 0001 0001 0000 0000
+ // the question:
+ // 0377 7777 0765 7861 6d70 6c65 0363 6f6d 0000 0100 01
+ // the answer:
+ // 03 7777 7707 6578 616d 706c 6503 636f 6d00 0001 0001
+ // 0000 0080 0004 5db8 d822
+
+ unsigned int index = 12;
+ uint8_t length;
+ nsAutoCString host;
+ nsresult rv;
+ uint16_t extendedError = UINT16_MAX;
+
+ LOG(("doh decode %s %d bytes\n", aHost.get(), aLen));
+
+ aCname.Truncate();
+
+ if (aLen < 12) {
+ LOG(("TRR bad incoming DOH, eject!\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (!mNativePacket && (aBuffer[0] || aBuffer[1])) {
+ LOG(("Packet ID is unexpectedly non-zero"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ uint8_t rcode = mResponse[3] & 0x0F;
+ LOG(("TRR Decode %s RCODE %d\n", PromiseFlatCString(aHost).get(), rcode));
+
+ uint16_t questionRecords = get16bit(aBuffer, 4); // qdcount
+ // iterate over the single(?) host name in question
+ while (questionRecords) {
+ do {
+ if (aLen < (index + 1)) {
+ LOG(("TRR Decode 1 index: %u size: %u", index, aLen));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ length = static_cast<uint8_t>(aBuffer[index]);
+ if (length) {
+ if (host.Length()) {
+ host.Append(".");
+ }
+ if (aLen < (index + 1 + length)) {
+ LOG(("TRR Decode 2 index: %u size: %u len: %u", index, aLen, length));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ host.Append(((char*)aBuffer) + index + 1, length);
+ }
+ index += 1 + length; // skip length byte + label
+ } while (length);
+ if (aLen < (index + 4)) {
+ LOG(("TRR Decode 3 index: %u size: %u", index, aLen));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ index += 4; // skip question's type, class
+ questionRecords--;
+ }
+
+ // Figure out the number of answer records from ANCOUNT
+ uint16_t answerRecords = get16bit(aBuffer, 6);
+
+ LOG(("TRR Decode: %d answer records (%u bytes body) %s index=%u\n",
+ answerRecords, aLen, host.get(), index));
+
+ while (answerRecords) {
+ nsAutoCString qname;
+ rv = GetQname(qname, index, aBuffer, mBodySize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // 16 bit TYPE
+ if (aLen < (index + 2)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index + 2));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t TYPE = get16bit(aBuffer, index);
+
+ index += 2;
+
+ // 16 bit class
+ if (aLen < (index + 2)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index + 2));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t CLASS = get16bit(aBuffer, index);
+ if (kDNS_CLASS_IN != CLASS) {
+ LOG(("TRR bad CLASS (%u) at index %d\n", CLASS, index));
+ return NS_ERROR_UNEXPECTED;
+ }
+ index += 2;
+
+ // 32 bit TTL (seconds)
+ if (aLen < (index + 4)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint32_t TTL = get32bit(aBuffer, index);
+ index += 4;
+
+ // 16 bit RDLENGTH
+ if (aLen < (index + 2)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t RDLENGTH = get16bit(aBuffer, index);
+ index += 2;
+
+ if (aLen < (index + RDLENGTH)) {
+ LOG(("TRR: Dohdecode:%d fail RDLENGTH=%d at index %d\n", __LINE__,
+ RDLENGTH, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if ((TYPE != TRRTYPE_CNAME) && (TYPE != TRRTYPE_HTTPSSVC) &&
+ (TYPE != static_cast<uint16_t>(aType))) {
+ // Not the same type as was asked for nor CNAME
+ LOG(("TRR: Dohdecode:%d asked for type %d got %d\n", __LINE__, aType,
+ TYPE));
+ index += RDLENGTH;
+ answerRecords--;
+ continue;
+ }
+
+ // We check if the qname is a case-insensitive match for the host or the
+ // FQDN version of the host
+ bool responseMatchesQuestion =
+ (qname.Length() == aHost.Length() ||
+ (aHost.Length() == qname.Length() + 1 && aHost.Last() == '.')) &&
+ StringBeginsWith(aHost, qname, nsCaseInsensitiveCStringComparator);
+
+ if (responseMatchesQuestion) {
+ // RDATA
+ // - A (TYPE 1): 4 bytes
+ // - AAAA (TYPE 28): 16 bytes
+ // - NS (TYPE 2): N bytes
+
+ switch (TYPE) {
+ case TRRTYPE_A:
+ if (RDLENGTH != 4) {
+ LOG(("TRR bad length for A (%u)\n", RDLENGTH));
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = aResp.Add(TTL, aBuffer, index, RDLENGTH, aAllowRFC1918);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("TRR:DohDecode failed: local IP addresses or unknown IP "
+ "family\n"));
+ return rv;
+ }
+ break;
+ case TRRTYPE_AAAA:
+ if (RDLENGTH != 16) {
+ LOG(("TRR bad length for AAAA (%u)\n", RDLENGTH));
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = aResp.Add(TTL, aBuffer, index, RDLENGTH, aAllowRFC1918);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR got unique/local IPv6 address!\n"));
+ return rv;
+ }
+ break;
+
+ case TRRTYPE_NS:
+ break;
+ case TRRTYPE_CNAME:
+ if (aCname.IsEmpty()) {
+ nsAutoCString qname;
+ unsigned int qnameindex = index;
+ rv = GetQname(qname, qnameindex, aBuffer, mBodySize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!qname.IsEmpty()) {
+ ToLowerCase(qname);
+ aCname = qname;
+ LOG(("DNSPacket::DohDecode CNAME host %s => %s\n", host.get(),
+ aCname.get()));
+ } else {
+ LOG(("DNSPacket::DohDecode empty CNAME for host %s!\n",
+ host.get()));
+ }
+ } else {
+ LOG(("DNSPacket::DohDecode CNAME - ignoring another entry\n"));
+ }
+ break;
+ case TRRTYPE_TXT: {
+ // TXT record RRDATA sections are a series of character-strings
+ // each character string is a length byte followed by that many data
+ // bytes
+ nsAutoCString txt;
+ unsigned int txtIndex = index;
+ uint16_t available = RDLENGTH;
+
+ while (available > 0) {
+ uint8_t characterStringLen = aBuffer[txtIndex++];
+ available--;
+ if (characterStringLen > available) {
+ LOG(("DNSPacket::DohDecode MALFORMED TXT RECORD\n"));
+ break;
+ }
+ txt.Append((const char*)(&aBuffer[txtIndex]), characterStringLen);
+ txtIndex += characterStringLen;
+ available -= characterStringLen;
+ }
+
+ if (!aTypeResult.is<TypeRecordTxt>()) {
+ aTypeResult = AsVariant(CopyableTArray<nsCString>());
+ }
+
+ {
+ auto& results = aTypeResult.as<TypeRecordTxt>();
+ results.AppendElement(txt);
+ }
+ if (aTTL > TTL) {
+ aTTL = TTL;
+ }
+ LOG(("DNSPacket::DohDecode TXT host %s => %s\n", host.get(),
+ txt.get()));
+
+ break;
+ }
+ case TRRTYPE_HTTPSSVC: {
+ struct SVCB parsed;
+
+ if (aType != TRRTYPE_HTTPSSVC) {
+ // Ignore the entry that we just parsed if we didn't ask for it.
+ break;
+ }
+
+ rv = ParseHTTPS(RDLENGTH, parsed, index, aBuffer, mBodySize,
+ mOriginHost ? *mOriginHost : qname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (parsed.mSvcDomainName.IsEmpty() &&
+ parsed.mSvcFieldPriority == 0) {
+ // For AliasMode SVCB RRs, a TargetName of "." indicates that the
+ // service is not available or does not exist.
+ continue;
+ }
+
+ // Check for AliasForm
+ if (aCname.IsEmpty() && parsed.mSvcFieldPriority == 0) {
+ // Alias form SvcDomainName must not have the "." value (empty)
+ if (parsed.mSvcDomainName.IsEmpty()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ aCname = parsed.mSvcDomainName;
+ // If aliasForm is present, Service form must be ignored.
+ aTypeResult = mozilla::AsVariant(Nothing());
+ ToLowerCase(aCname);
+ LOG(("DNSPacket::DohDecode HTTPSSVC AliasForm host %s => %s\n",
+ host.get(), aCname.get()));
+ break;
+ }
+
+ aTTL = TTL;
+
+ if (!aTypeResult.is<TypeRecordHTTPSSVC>()) {
+ aTypeResult = mozilla::AsVariant(CopyableTArray<SVCB>());
+ }
+ {
+ auto& results = aTypeResult.as<TypeRecordHTTPSSVC>();
+ results.AppendElement(parsed);
+ }
+
+ break;
+ }
+ default:
+ // skip unknown record types
+ LOG(("TRR unsupported TYPE (%u) RDLENGTH %u\n", TYPE, RDLENGTH));
+ break;
+ }
+ } else {
+ LOG(("TRR asked for %s data but got %s\n", aHost.get(), qname.get()));
+ }
+
+ index += RDLENGTH;
+ LOG(("done with record type %u len %u index now %u of %u\n", TYPE, RDLENGTH,
+ index, aLen));
+ answerRecords--;
+ }
+
+ // NSCOUNT
+ uint16_t nsRecords = get16bit(aBuffer, 8);
+ LOG(("TRR Decode: %d ns records (%u bytes body)\n", nsRecords, aLen));
+ while (nsRecords) {
+ rv = PassQName(index, aBuffer);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aLen < (index + 8)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ index += 2; // type
+ index += 2; // class
+ index += 4; // ttl
+
+ // 16 bit RDLENGTH
+ if (aLen < (index + 2)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t RDLENGTH = get16bit(aBuffer, index);
+ index += 2;
+ if (aLen < (index + RDLENGTH)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ index += RDLENGTH;
+ LOG(("done with nsRecord now %u of %u\n", index, aLen));
+ nsRecords--;
+ }
+
+ // additional resource records
+ uint16_t arRecords = get16bit(aBuffer, 10);
+ LOG(("TRR Decode: %d additional resource records (%u bytes body)\n",
+ arRecords, aLen));
+
+ while (arRecords) {
+ nsAutoCString qname;
+ rv = GetQname(qname, index, aBuffer, mBodySize);
+ if (NS_FAILED(rv)) {
+ LOG(("Bad qname for additional record"));
+ return rv;
+ }
+
+ if (aLen < (index + 8)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t type = get16bit(aBuffer, index);
+ index += 2;
+ // The next two bytes encode class
+ // (or udpPayloadSize when type is TRRTYPE_OPT)
+ uint16_t cls = get16bit(aBuffer, index);
+ index += 2;
+ // The next 4 bytes encode TTL
+ // (or extRCode + ednsVersion + flags when type is TRRTYPE_OPT)
+ uint32_t ttl = get32bit(aBuffer, index);
+ index += 4;
+ // cls and ttl are unused when type is TRRTYPE_OPT
+
+ // 16 bit RDLENGTH
+ if (aLen < (index + 2)) {
+ LOG(("Record too small"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ uint16_t rdlength = get16bit(aBuffer, index);
+ index += 2;
+ if (aLen < (index + rdlength)) {
+ LOG(("rdlength too big"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ auto parseRecord = [&]() {
+ LOG(("Parsing additional record type: %u", type));
+ auto* entry = aAdditionalRecords.GetOrInsertNew(qname);
+
+ switch (type) {
+ case TRRTYPE_A:
+ if (kDNS_CLASS_IN != cls) {
+ LOG(("NOT IN - returning"));
+ return;
+ }
+ if (rdlength != 4) {
+ LOG(("TRR bad length for A (%u)\n", rdlength));
+ return;
+ }
+ rv = entry->Add(ttl, aBuffer, index, rdlength, aAllowRFC1918);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("TRR:DohDecode failed: local IP addresses or unknown IP "
+ "family\n"));
+ return;
+ }
+ break;
+ case TRRTYPE_AAAA:
+ if (kDNS_CLASS_IN != cls) {
+ LOG(("NOT IN - returning"));
+ return;
+ }
+ if (rdlength != 16) {
+ LOG(("TRR bad length for AAAA (%u)\n", rdlength));
+ return;
+ }
+ rv = entry->Add(ttl, aBuffer, index, rdlength, aAllowRFC1918);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR got unique/local IPv6 address!\n"));
+ return;
+ }
+ break;
+ case TRRTYPE_OPT: { // OPT
+ LOG(("Parsing opt rdlen: %u", rdlength));
+ unsigned int offset = 0;
+ while (offset + 2 <= rdlength) {
+ uint16_t optCode = get16bit(aBuffer, index + offset);
+ LOG(("optCode: %u", optCode));
+ offset += 2;
+ if (offset + 2 > rdlength) {
+ break;
+ }
+ uint16_t optLen = get16bit(aBuffer, index + offset);
+ LOG(("optLen: %u", optLen));
+ offset += 2;
+ if (offset + optLen > rdlength) {
+ LOG(("offset: %u, optLen: %u, rdlen: %u", offset, optLen,
+ rdlength));
+ break;
+ }
+
+ LOG(("OPT: code: %u len:%u", optCode, optLen));
+
+ if (optCode != 15) {
+ offset += optLen;
+ continue;
+ }
+
+ // optCode == 15; Extended DNS error
+
+ if (offset + 2 > rdlength || optLen < 2) {
+ break;
+ }
+ extendedError = get16bit(aBuffer, index + offset);
+
+ LOG(("Extended error code: %u message: %s", extendedError,
+ nsAutoCString((char*)aBuffer + index + offset + 2, optLen - 2)
+ .get()));
+ offset += optLen;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ };
+
+ parseRecord();
+
+ index += rdlength;
+ LOG(("done with additional rr now %u of %u\n", index, aLen));
+ arRecords--;
+ }
+
+ if (index != aLen) {
+ LOG(("DohDecode failed to parse entire response body, %u out of %u bytes\n",
+ index, aLen));
+ // failed to parse 100%, do not continue
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (aType == TRRTYPE_NS && rcode != 0) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if ((aType != TRRTYPE_NS) && aCname.IsEmpty() && aResp.mAddresses.IsEmpty() &&
+ aTypeResult.is<TypeRecordEmpty>()) {
+ // no entries were stored!
+ LOG(("TRR: No entries were stored!\n"));
+
+ if (extendedError != UINT16_MAX &&
+ StaticPrefs::network_trr_hard_fail_on_extended_error() &&
+ hardFail(extendedError)) {
+ return NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
+ }
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ // https://tools.ietf.org/html/draft-ietf-dnsop-svcb-httpssvc-03#page-14
+ // If one or more SVCB records of ServiceForm SvcRecordType are returned for
+ // HOST, clients should select the highest-priority option with acceptable
+ // parameters.
+ if (aTypeResult.is<TypeRecordHTTPSSVC>()) {
+ auto& results = aTypeResult.as<TypeRecordHTTPSSVC>();
+ results.Sort();
+ }
+
+ return NS_OK;
+}
+
+//
+// DohDecode() collects the TTL and the IP addresses in the response
+//
+nsresult DNSPacket::Decode(
+ nsCString& aHost, enum TrrType aType, nsCString& aCname, bool aAllowRFC1918,
+ DOHresp& aResp, TypeRecordResultType& aTypeResult,
+ nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
+ uint32_t& aTTL) {
+ nsresult rv =
+ DecodeInternal(aHost, aType, aCname, aAllowRFC1918, aResp, aTypeResult,
+ aAdditionalRecords, aTTL, mResponse, mBodySize);
+ mStatus = rv;
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSPacket.h b/netwerk/dns/DNSPacket.h
new file mode 100644
index 0000000000..943bcfe246
--- /dev/null
+++ b/netwerk/dns/DNSPacket.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 mozilla_net_DNSPacket_h__
+#define mozilla_net_DNSPacket_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsClassHashtable.h"
+#include "nsIDNSService.h"
+#include "DNS.h"
+#include "DNSByTypeRecord.h"
+
+#include <functional>
+
+namespace mozilla {
+namespace net {
+
+class DOHresp {
+ public:
+ nsresult Add(uint32_t TTL, unsigned char const* dns, unsigned int index,
+ uint16_t len, bool aLocalAllowed);
+ nsTArray<NetAddr> mAddresses;
+ uint32_t mTtl = 0;
+};
+
+// the values map to RFC1035 type identifiers
+enum TrrType {
+ TRRTYPE_A = 1,
+ TRRTYPE_NS = 2,
+ TRRTYPE_CNAME = 5,
+ TRRTYPE_AAAA = 28,
+ TRRTYPE_OPT = 41,
+ TRRTYPE_TXT = 16,
+ TRRTYPE_HTTPSSVC = nsIDNSService::RESOLVE_TYPE_HTTPSSVC, // 65
+};
+
+class DNSPacket {
+ public:
+ // Never accept larger DOH responses than this as that would indicate
+ // something is wrong. Typical ones are much smaller.
+ static const unsigned int MAX_SIZE = 3200;
+
+ DNSPacket() = default;
+ virtual ~DNSPacket() = default;
+
+ Result<uint8_t, nsresult> GetRCode() const;
+ Result<bool, nsresult> RecursionAvailable() const;
+
+ // Called in order to feed data into the buffer.
+ nsresult OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, const uint32_t aCount);
+
+ // Encodes the name request into a buffer that represents a DNS packet
+ virtual nsresult EncodeRequest(nsCString& aBody, const nsACString& aHost,
+ uint16_t aType, bool aDisableECS);
+
+ // Decodes the DNS response and extracts the responses, additional records,
+ // etc. XXX: This should probably be refactored to reduce the number of
+ // output parameters and have a common format for different record types.
+ virtual nsresult Decode(
+ nsCString& aHost, enum TrrType aType, nsCString& aCname,
+ bool aAllowRFC1918, DOHresp& aResp, TypeRecordResultType& aTypeResult,
+ nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
+ uint32_t& aTTL);
+
+ void SetOriginHost(const Maybe<nsCString>& aHost) { mOriginHost = aHost; }
+
+ nsresult FillBuffer(std::function<int(unsigned char response[MAX_SIZE])>&&);
+
+ static nsresult ParseHTTPS(uint16_t aRDLen, struct SVCB& aParsed,
+ unsigned int aIndex, const unsigned char* aBuffer,
+ unsigned int aBodySize,
+ const nsACString& aOriginHost);
+ void SetNativePacket(bool aNative) { mNativePacket = aNative; }
+
+ protected:
+ nsresult PassQName(unsigned int& index, const unsigned char* aBuffer);
+ static nsresult GetQname(nsACString& aQname, unsigned int& aIndex,
+ const unsigned char* aBuffer,
+ unsigned int aBodySize);
+ static nsresult ParseSvcParam(unsigned int svcbIndex, uint16_t key,
+ SvcFieldValue& field, uint16_t length,
+ const unsigned char* aBuffer);
+ nsresult DecodeInternal(
+ nsCString& aHost, enum TrrType aType, nsCString& aCname,
+ bool aAllowRFC1918, DOHresp& aResp, TypeRecordResultType& aTypeResult,
+ nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
+ uint32_t& aTTL, const unsigned char* aBuffer, uint32_t aLen);
+
+ // The response buffer.
+ unsigned char mResponse[MAX_SIZE]{};
+ unsigned int mBodySize = 0;
+ // True when decoding a DNS packet received from OS. Decoding will
+ // not panic if packet ID is not zero.
+ bool mNativePacket = false;
+ nsresult mStatus = NS_OK;
+ Maybe<nsCString> mOriginHost;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DNSPacket_h__
diff --git a/netwerk/dns/DNSRequestBase.h b/netwerk/dns/DNSRequestBase.h
new file mode 100644
index 0000000000..7b26846f5e
--- /dev/null
+++ b/netwerk/dns/DNSRequestBase.h
@@ -0,0 +1,152 @@
+/* -*- 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_DNSRequestBase_h
+#define mozilla_net_DNSRequestBase_h
+
+#include "mozilla/net/PDNSRequestParent.h"
+#include "nsICancelable.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIEventTarget.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSRequestActor;
+class DNSRequestChild;
+class DNSRequestHandler;
+class DNSRequestParent;
+class DNSRequestSender;
+
+// A base class for DNSRequestSender and DNSRequestHandler.
+// Provide interfaces for processing DNS requests.
+class DNSRequestBase : public nsISupports {
+ public:
+ explicit DNSRequestBase() = default;
+
+ void SetIPCActor(DNSRequestActor* aActor);
+
+ virtual void OnRecvCancelDNSRequest(const nsCString& hostName,
+ const nsCString& trrServer,
+ const int32_t& port, const uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags,
+ const nsresult& reason) = 0;
+ virtual bool OnRecvLookupCompleted(const DNSRequestResponse& reply) = 0;
+ virtual void OnIPCActorDestroy() = 0;
+
+ virtual DNSRequestSender* AsDNSRequestSender() = 0;
+ virtual DNSRequestHandler* AsDNSRequestHandler() = 0;
+
+ protected:
+ virtual ~DNSRequestBase() = default;
+
+ RefPtr<DNSRequestActor> mIPCActor;
+};
+
+// DNSRequestSender is used to send an IPC request to DNSRequestHandler and
+// deliver the result to nsIDNSListener.
+// Note this class could be used both in content process and parent process.
+class DNSRequestSender final : public DNSRequestBase, public nsICancelable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ DNSRequestSender(const nsACString& aHost, const nsACString& aTrrServer,
+ int32_t aPort, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags,
+ nsIDNSListener* aListener, nsIEventTarget* target);
+
+ void OnRecvCancelDNSRequest(const nsCString& hostName,
+ const nsCString& trrServer, const int32_t& port,
+ const uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags,
+ const nsresult& reason) override;
+ bool OnRecvLookupCompleted(const DNSRequestResponse& reply) override;
+ void OnIPCActorDestroy() override;
+
+ // Sends IPDL request to DNSRequestHandler
+ void StartRequest();
+ void CallOnLookupComplete();
+
+ DNSRequestSender* AsDNSRequestSender() override { return this; }
+ DNSRequestHandler* AsDNSRequestHandler() override { return nullptr; }
+
+ private:
+ friend class ChildDNSService;
+ virtual ~DNSRequestSender() = default;
+
+ nsCOMPtr<nsIDNSListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ nsCOMPtr<nsIDNSRecord> mResultRecord;
+ nsresult mResultStatus = NS_OK;
+ nsCString mHost;
+ nsCString mTrrServer;
+ int32_t mPort;
+ uint16_t mType = 0;
+ const OriginAttributes mOriginAttributes;
+ nsIDNSService::DNSFlags mFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+};
+
+// DNSRequestHandler handles the dns request and sends the result back via IPC.
+// Note this class could be used both in parent process and socket process.
+class DNSRequestHandler final : public DNSRequestBase, public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ DNSRequestHandler() = default;
+
+ void DoAsyncResolve(const nsACString& hostname, const nsACString& trrServer,
+ int32_t port, uint16_t type,
+ const OriginAttributes& originAttributes,
+ nsIDNSService::DNSFlags flags);
+
+ void OnRecvCancelDNSRequest(const nsCString& hostName,
+ const nsCString& trrServer, const int32_t& port,
+ const uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags,
+ const nsresult& reason) override;
+ bool OnRecvLookupCompleted(const DNSRequestResponse& reply) override;
+ void OnIPCActorDestroy() override;
+
+ DNSRequestSender* AsDNSRequestSender() override { return nullptr; }
+ DNSRequestHandler* AsDNSRequestHandler() override { return this; }
+
+ private:
+ virtual ~DNSRequestHandler() = default;
+
+ nsIDNSService::DNSFlags mFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+};
+
+// Provides some common methods for DNSRequestChild and DNSRequestParent.
+class DNSRequestActor {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ explicit DNSRequestActor(DNSRequestBase* aRequest) : mDNSRequest(aRequest) {}
+
+ virtual bool CanSend() const = 0;
+ virtual DNSRequestChild* AsDNSRequestChild() = 0;
+ virtual DNSRequestParent* AsDNSRequestParent() = 0;
+
+ DNSRequestBase* GetDNSRequest() { return mDNSRequest.get(); };
+
+ protected:
+ virtual ~DNSRequestActor() = default;
+
+ RefPtr<DNSRequestBase> mDNSRequest;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DNSRequestBase_h
diff --git a/netwerk/dns/DNSRequestChild.cpp b/netwerk/dns/DNSRequestChild.cpp
new file mode 100644
index 0000000000..ec0f56b3f1
--- /dev/null
+++ b/netwerk/dns/DNSRequestChild.cpp
@@ -0,0 +1,574 @@
+/* -*- 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 "mozilla/dom/ContentChild.h"
+#include "mozilla/net/ChildDNSService.h"
+#include "mozilla/net/DNSByTypeRecord.h"
+#include "mozilla/net/DNSRequestChild.h"
+#include "mozilla/net/DNSRequestParent.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/Unused.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsHostResolver.h"
+#include "nsIOService.h"
+#include "nsTArray.h"
+#include "nsNetAddr.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+void DNSRequestBase::SetIPCActor(DNSRequestActor* aActor) {
+ mIPCActor = aActor;
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSRecord:
+// A simple class to provide nsIDNSRecord on the child
+//-----------------------------------------------------------------------------
+
+class ChildDNSRecord : public nsIDNSAddrRecord {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+ NS_DECL_NSIDNSADDRRECORD
+
+ ChildDNSRecord(const DNSRecord& reply, nsIDNSService::DNSFlags flags);
+
+ private:
+ virtual ~ChildDNSRecord() = default;
+
+ nsCString mCanonicalName;
+ nsTArray<NetAddr> mAddresses;
+ uint32_t mCurrent = 0; // addr iterator
+ nsIDNSService::DNSFlags mFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ double mTrrFetchDuration = 0;
+ double mTrrFetchDurationNetworkOnly = 0;
+ bool mIsTRR = false;
+ bool mResolvedInSocketProcess = false;
+ nsIRequest::TRRMode mEffectiveTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ nsITRRSkipReason::value mTRRSkipReason = nsITRRSkipReason::TRR_UNSET;
+ uint32_t mTTL = 0;
+};
+
+NS_IMPL_ISUPPORTS(ChildDNSRecord, nsIDNSRecord, nsIDNSAddrRecord)
+
+ChildDNSRecord::ChildDNSRecord(const DNSRecord& reply,
+ nsIDNSService::DNSFlags flags)
+ : mFlags(flags) {
+ mCanonicalName = reply.canonicalName();
+ mTrrFetchDuration = reply.trrFetchDuration();
+ mTrrFetchDurationNetworkOnly = reply.trrFetchDurationNetworkOnly();
+ mIsTRR = reply.isTRR();
+ // When ChildDNSRecord is created in parent process, we know this is case that
+ // DNS resolution is done in socket process.
+ mResolvedInSocketProcess = XRE_IsParentProcess();
+ mEffectiveTRRMode = reply.effectiveTRRMode();
+
+ // A shame IPDL gives us no way to grab ownership of array: so copy it.
+ const nsTArray<NetAddr>& addrs = reply.addrs();
+ mAddresses = addrs.Clone();
+ mTTL = reply.ttl();
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSRecord::nsIDNSAddrRecord
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ChildDNSRecord::GetCanonicalName(nsACString& result) {
+ if (!(mFlags & nsHostResolver::RES_CANON_NAME)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ result = mCanonicalName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::IsTRR(bool* retval) {
+ *retval = mIsTRR;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::ResolvedInSocketProcess(bool* retval) {
+ *retval = mResolvedInSocketProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::GetTrrFetchDuration(double* aTime) {
+ *aTime = mTrrFetchDuration;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::GetTrrFetchDurationNetworkOnly(double* aTime) {
+ *aTime = mTrrFetchDurationNetworkOnly;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::GetNextAddr(uint16_t port, NetAddr* addr) {
+ if (mCurrent >= mAddresses.Length()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mAddresses[mCurrent++], sizeof(NetAddr));
+
+ // both Ipv4/6 use same bits for port, so safe to just use ipv4's field
+ addr->inet.port = htons(port);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::GetAddresses(nsTArray<NetAddr>& aAddressArray) {
+ aAddressArray = mAddresses.Clone();
+ return NS_OK;
+}
+
+// shamelessly copied from nsDNSRecord
+NS_IMETHODIMP
+ChildDNSRecord::GetScriptableNextAddr(uint16_t port, nsINetAddr** result) {
+ NetAddr addr;
+ nsresult rv = GetNextAddr(port, &addr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&addr);
+ netaddr.forget(result);
+
+ return NS_OK;
+}
+
+// also copied from nsDNSRecord
+NS_IMETHODIMP
+ChildDNSRecord::GetNextAddrAsString(nsACString& result) {
+ NetAddr addr;
+ nsresult rv = GetNextAddr(0, &addr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ char buf[kIPv6CStrBufSize];
+ if (addr.ToStringBuffer(buf, sizeof(buf))) {
+ result.Assign(buf);
+ return NS_OK;
+ }
+ NS_ERROR("NetAddrToString failed unexpectedly");
+ return NS_ERROR_FAILURE; // conversion failed for some reason
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::HasMore(bool* result) {
+ *result = mCurrent < mAddresses.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::Rewind() {
+ mCurrent = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::ReportUnusable(uint16_t aPort) {
+ // "We thank you for your feedback" == >/dev/null
+ // TODO: we could send info back to parent.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::GetEffectiveTRRMode(nsIRequest::TRRMode* aMode) {
+ *aMode = mEffectiveTRRMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ChildDNSRecord::GetTrrSkipReason(
+ nsITRRSkipReason::value* aTrrSkipReason) {
+ *aTrrSkipReason = mTRRSkipReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSRecord::GetTtl(uint32_t* aTtl) {
+ *aTtl = mTTL;
+ return NS_OK;
+}
+
+class ChildDNSByTypeRecord : public nsIDNSByTypeRecord,
+ public nsIDNSTXTRecord,
+ public nsIDNSHTTPSSVCRecord,
+ public DNSHTTPSSVCRecordBase {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+ NS_DECL_NSIDNSBYTYPERECORD
+ NS_DECL_NSIDNSTXTRECORD
+ NS_DECL_NSIDNSHTTPSSVCRECORD
+
+ explicit ChildDNSByTypeRecord(const TypeRecordResultType& reply,
+ const nsACString& aHost, uint32_t aTTL);
+
+ private:
+ virtual ~ChildDNSByTypeRecord() = default;
+
+ TypeRecordResultType mResults = AsVariant(mozilla::Nothing());
+ bool mAllRecordsExcluded = false;
+ uint32_t mTTL = 0;
+};
+
+NS_IMPL_ISUPPORTS(ChildDNSByTypeRecord, nsIDNSByTypeRecord, nsIDNSRecord,
+ nsIDNSTXTRecord, nsIDNSHTTPSSVCRecord)
+
+ChildDNSByTypeRecord::ChildDNSByTypeRecord(const TypeRecordResultType& reply,
+ const nsACString& aHost,
+ uint32_t aTTL)
+ : DNSHTTPSSVCRecordBase(aHost) {
+ mResults = reply;
+ mTTL = aTTL;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetType(uint32_t* aType) {
+ *aType = mResults.match(
+ [](TypeRecordEmpty&) {
+ MOZ_ASSERT(false, "This should never be the case");
+ return nsIDNSService::RESOLVE_TYPE_DEFAULT;
+ },
+ [](TypeRecordTxt&) { return nsIDNSService::RESOLVE_TYPE_TXT; },
+ [](TypeRecordHTTPSSVC&) { return nsIDNSService::RESOLVE_TYPE_HTTPSSVC; });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetRecords(CopyableTArray<nsCString>& aRecords) {
+ if (!mResults.is<TypeRecordTxt>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aRecords = mResults.as<CopyableTArray<nsCString>>();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetRecordsAsOneString(nsACString& aRecords) {
+ // deep copy
+ if (!mResults.is<TypeRecordTxt>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ auto& results = mResults.as<CopyableTArray<nsCString>>();
+ for (uint32_t i = 0; i < results.Length(); i++) {
+ aRecords.Append(results[i]);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetRecords(nsTArray<RefPtr<nsISVCBRecord>>& aRecords) {
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& results = mResults.as<TypeRecordHTTPSSVC>();
+
+ for (const SVCB& r : results) {
+ RefPtr<nsISVCBRecord> rec = new SVCBRecord(r);
+ aRecords.AppendElement(rec);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetServiceModeRecord(bool aNoHttp2, bool aNoHttp3,
+ nsISVCBRecord** aRecord) {
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& results = mResults.as<TypeRecordHTTPSSVC>();
+ nsCOMPtr<nsISVCBRecord> result = GetServiceModeRecordInternal(
+ aNoHttp2, aNoHttp3, results, mAllRecordsExcluded);
+ if (!result) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ result.forget(aRecord);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetAllRecordsWithEchConfig(
+ bool aNoHttp2, bool aNoHttp3, bool* aAllRecordsHaveEchConfig,
+ bool* aAllRecordsInH3ExcludedList,
+ nsTArray<RefPtr<nsISVCBRecord>>& aResult) {
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& records = mResults.as<TypeRecordHTTPSSVC>();
+ GetAllRecordsWithEchConfigInternal(aNoHttp2, aNoHttp3, records,
+ aAllRecordsHaveEchConfig,
+ aAllRecordsInH3ExcludedList, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetHasIPAddresses(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& results = mResults.as<TypeRecordHTTPSSVC>();
+ *aResult = HasIPAddressesInternal(results);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetAllRecordsExcluded(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = mAllRecordsExcluded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetResults(mozilla::net::TypeRecordResultType* aResults) {
+ *aResults = mResults;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetTtl(uint32_t* aResult) {
+ *aResult = mTTL;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// DNSRequestSender
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(DNSRequestSender, nsICancelable)
+
+DNSRequestSender::DNSRequestSender(const nsACString& aHost,
+ const nsACString& aTrrServer, int32_t aPort,
+ const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags,
+ nsIDNSListener* aListener,
+ nsIEventTarget* target)
+ : mListener(aListener),
+ mTarget(target),
+ mResultStatus(NS_OK),
+ mHost(aHost),
+ mTrrServer(aTrrServer),
+ mPort(aPort),
+ mType(aType),
+ mOriginAttributes(aOriginAttributes),
+ mFlags(aFlags) {}
+
+void DNSRequestSender::OnRecvCancelDNSRequest(
+ const nsCString& hostName, const nsCString& trrServer, const int32_t& port,
+ const uint16_t& type, const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason) {}
+
+NS_IMETHODIMP
+DNSRequestSender::Cancel(nsresult reason) {
+ if (!mIPCActor) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We can only do IPC on the MainThread
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "net::CancelDNSRequestEvent",
+ [actor(mIPCActor), host(mHost), trrServer(mTrrServer), port(mPort),
+ type(mType), originAttributes(mOriginAttributes), flags(mFlags),
+ reason]() {
+ if (!actor->CanSend()) {
+ return;
+ }
+
+ if (DNSRequestChild* child = actor->AsDNSRequestChild()) {
+ Unused << child->SendCancelDNSRequest(
+ host, trrServer, port, type, originAttributes, flags, reason);
+ } else if (DNSRequestParent* parent = actor->AsDNSRequestParent()) {
+ Unused << parent->SendCancelDNSRequest(
+ host, trrServer, port, type, originAttributes, flags, reason);
+ }
+ });
+ SchedulerGroup::Dispatch(runnable.forget());
+ return NS_OK;
+}
+
+void DNSRequestSender::StartRequest() {
+ // we can only do IPC on the MainThread
+ if (!NS_IsMainThread()) {
+ SchedulerGroup::Dispatch(
+ NewRunnableMethod("net::DNSRequestSender::StartRequest", this,
+ &DNSRequestSender::StartRequest));
+ return;
+ }
+
+ if (RefPtr<DNSRequestChild> child = mIPCActor->AsDNSRequestChild()) {
+ if (XRE_IsContentProcess()) {
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return;
+ }
+
+ // Send request to Parent process.
+ gNeckoChild->SendPDNSRequestConstructor(child, mHost, mTrrServer, mPort,
+ mType, mOriginAttributes, mFlags);
+ } else if (XRE_IsSocketProcess()) {
+ // DNS resolution is done in the parent process. Send a DNS request to
+ // parent process.
+ MOZ_ASSERT(!nsIOService::UseSocketProcess());
+
+ SocketProcessChild* socketProcessChild =
+ SocketProcessChild::GetSingleton();
+ if (!socketProcessChild->CanSend()) {
+ return;
+ }
+
+ MOZ_ALWAYS_TRUE(socketProcessChild->SendPDNSRequestConstructor(
+ child, mHost, mTrrServer, mPort, mType, mOriginAttributes, mFlags));
+ } else {
+ MOZ_ASSERT(false, "Wrong process");
+ return;
+ }
+ } else if (DNSRequestParent* parent = mIPCActor->AsDNSRequestParent()) {
+ // DNS resolution is done in the socket process. Send a DNS request to
+ // socket process.
+ MOZ_ASSERT(nsIOService::UseSocketProcess());
+
+ RefPtr<DNSRequestParent> requestParent = parent;
+ RefPtr<DNSRequestSender> self = this;
+ auto task = [requestParent, self]() {
+ Unused << SocketProcessParent::GetSingleton()->SendPDNSRequestConstructor(
+ requestParent, self->mHost, self->mTrrServer, self->mPort,
+ self->mType, self->mOriginAttributes, self->mFlags);
+ };
+ if (!gIOService->SocketProcessReady()) {
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return;
+ }
+
+ task();
+ }
+}
+
+void DNSRequestSender::CallOnLookupComplete() {
+ MOZ_ASSERT(mListener);
+ mListener->OnLookupComplete(this, mResultRecord, mResultStatus);
+}
+
+bool DNSRequestSender::OnRecvLookupCompleted(const DNSRequestResponse& reply) {
+ MOZ_ASSERT(mListener);
+
+ switch (reply.type()) {
+ case DNSRequestResponse::TDNSRecord: {
+ mResultRecord = new ChildDNSRecord(reply.get_DNSRecord(), mFlags);
+ break;
+ }
+ case DNSRequestResponse::Tnsresult: {
+ mResultStatus = reply.get_nsresult();
+ break;
+ }
+ case DNSRequestResponse::TIPCTypeRecord: {
+ MOZ_ASSERT(mType != nsIDNSService::RESOLVE_TYPE_DEFAULT);
+ mResultRecord =
+ new ChildDNSByTypeRecord(reply.get_IPCTypeRecord().mData, mHost,
+ reply.get_IPCTypeRecord().mTTL);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown type");
+ return false;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool targetIsMain = false;
+ if (!mTarget) {
+ targetIsMain = true;
+ } else {
+ mTarget->IsOnCurrentThread(&targetIsMain);
+ }
+
+ if (targetIsMain) {
+ CallOnLookupComplete();
+ } else {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("net::DNSRequestSender::CallOnLookupComplete", this,
+ &DNSRequestSender::CallOnLookupComplete);
+ mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ if (DNSRequestChild* child = mIPCActor->AsDNSRequestChild()) {
+ Unused << mozilla::net::DNSRequestChild::Send__delete__(child);
+ } else if (DNSRequestParent* parent = mIPCActor->AsDNSRequestParent()) {
+ Unused << mozilla::net::DNSRequestParent::Send__delete__(parent);
+ }
+
+ return true;
+}
+
+void DNSRequestSender::OnIPCActorDestroy() {
+ // Request is done or destroyed. Remove it from the hash table.
+ RefPtr<ChildDNSService> dnsServiceChild =
+ dont_AddRef(ChildDNSService::GetSingleton());
+ dnsServiceChild->NotifyRequestDone(this);
+
+ mIPCActor = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// DNSRequestChild
+//-----------------------------------------------------------------------------
+
+DNSRequestChild::DNSRequestChild(DNSRequestBase* aRequest)
+ : DNSRequestActor(aRequest) {
+ aRequest->SetIPCActor(this);
+}
+
+mozilla::ipc::IPCResult DNSRequestChild::RecvCancelDNSRequest(
+ const nsCString& hostName, const nsCString& trrServer, const int32_t& port,
+ const uint16_t& type, const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason) {
+ mDNSRequest->OnRecvCancelDNSRequest(hostName, trrServer, port, type,
+ originAttributes, flags, reason);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DNSRequestChild::RecvLookupCompleted(
+ const DNSRequestResponse& reply) {
+ return mDNSRequest->OnRecvLookupCompleted(reply) ? IPC_OK()
+ : IPC_FAIL_NO_REASON(this);
+}
+
+void DNSRequestChild::ActorDestroy(ActorDestroyReason) {
+ mDNSRequest->OnIPCActorDestroy();
+ mDNSRequest = nullptr;
+}
+
+//------------------------------------------------------------------------------
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSRequestChild.h b/netwerk/dns/DNSRequestChild.h
new file mode 100644
index 0000000000..064c2425e0
--- /dev/null
+++ b/netwerk/dns/DNSRequestChild.h
@@ -0,0 +1,42 @@
+/* -*- 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_DNSRequestChild_h
+#define mozilla_net_DNSRequestChild_h
+
+#include "mozilla/net/DNSRequestBase.h"
+#include "mozilla/net/PDNSRequestChild.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSRequestChild final : public DNSRequestActor, public PDNSRequestChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DNSRequestChild, override)
+ friend class PDNSRequestChild;
+
+ explicit DNSRequestChild(DNSRequestBase* aRequest);
+
+ bool CanSend() const override { return PDNSRequestChild::CanSend(); }
+ DNSRequestChild* AsDNSRequestChild() override { return this; }
+ DNSRequestParent* AsDNSRequestParent() override { return nullptr; }
+
+ private:
+ virtual ~DNSRequestChild() = default;
+
+ mozilla::ipc::IPCResult RecvCancelDNSRequest(
+ const nsCString& hostName, const nsCString& trrServer,
+ const int32_t& port, const uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason);
+ mozilla::ipc::IPCResult RecvLookupCompleted(const DNSRequestResponse& reply);
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DNSRequestChild_h
diff --git a/netwerk/dns/DNSRequestParent.cpp b/netwerk/dns/DNSRequestParent.cpp
new file mode 100644
index 0000000000..09213b59ba
--- /dev/null
+++ b/netwerk/dns/DNSRequestParent.cpp
@@ -0,0 +1,183 @@
+/* -*- 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 "mozilla/net/DNSRequestParent.h"
+#include "mozilla/net/DNSRequestChild.h"
+#include "nsIDNSService.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "nsICancelable.h"
+#include "nsIDNSRecord.h"
+#include "nsHostResolver.h"
+#include "mozilla/Unused.h"
+#include "DNSAdditionalInfo.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DNSRequestHandler::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(DNSRequestHandler, nsIDNSListener)
+
+static void SendLookupCompletedHelper(DNSRequestActor* aActor,
+ const DNSRequestResponse& aReply) {
+ if (DNSRequestParent* parent = aActor->AsDNSRequestParent()) {
+ Unused << parent->SendLookupCompleted(aReply);
+ } else if (DNSRequestChild* child = aActor->AsDNSRequestChild()) {
+ Unused << child->SendLookupCompleted(aReply);
+ }
+}
+
+void DNSRequestHandler::DoAsyncResolve(const nsACString& hostname,
+ const nsACString& trrServer,
+ int32_t port, uint16_t type,
+ const OriginAttributes& originAttributes,
+ nsIDNSService::DNSFlags flags) {
+ nsresult rv;
+ mFlags = flags;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget();
+ nsCOMPtr<nsICancelable> unused;
+ RefPtr<DNSAdditionalInfo> info;
+ if (!trrServer.IsEmpty() || port != -1) {
+ info = new DNSAdditionalInfo(trrServer, port);
+ }
+ rv = dns->AsyncResolveNative(
+ hostname, static_cast<nsIDNSService::ResolveType>(type), flags, info,
+ this, main, originAttributes, getter_AddRefs(unused));
+ }
+
+ if (NS_FAILED(rv) && mIPCActor->CanSend()) {
+ SendLookupCompletedHelper(mIPCActor, DNSRequestResponse(rv));
+ }
+}
+
+void DNSRequestHandler::OnRecvCancelDNSRequest(
+ const nsCString& hostName, const nsCString& aTrrServer, const int32_t& port,
+ const uint16_t& type, const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason) {
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<DNSAdditionalInfo> info;
+ if (!aTrrServer.IsEmpty() || port != -1) {
+ info = new DNSAdditionalInfo(aTrrServer, port);
+ }
+ rv = dns->CancelAsyncResolveNative(
+ hostName, static_cast<nsIDNSService::ResolveType>(type), flags, info,
+ this, reason, originAttributes);
+ }
+}
+
+bool DNSRequestHandler::OnRecvLookupCompleted(const DNSRequestResponse& reply) {
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// nsIDNSListener functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+DNSRequestHandler::OnLookupComplete(nsICancelable* request,
+ nsIDNSRecord* aRecord, nsresult status) {
+ if (!mIPCActor || !mIPCActor->CanSend()) {
+ // nothing to do: child probably crashed
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ MOZ_ASSERT(aRecord);
+
+ nsCOMPtr<nsIDNSByTypeRecord> byTypeRec = do_QueryInterface(aRecord);
+ if (byTypeRec) {
+ IPCTypeRecord result;
+ byTypeRec->GetResults(&result.mData);
+ if (nsCOMPtr<nsIDNSHTTPSSVCRecord> rec = do_QueryInterface(aRecord)) {
+ rec->GetTtl(&result.mTTL);
+ }
+ SendLookupCompletedHelper(mIPCActor, DNSRequestResponse(result));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ MOZ_ASSERT(rec);
+ nsAutoCString cname;
+ if (mFlags & nsHostResolver::RES_CANON_NAME) {
+ rec->GetCanonicalName(cname);
+ }
+
+ // Get IP addresses for hostname (use port 80 as dummy value for NetAddr)
+ nsTArray<NetAddr> array;
+ NetAddr addr;
+ while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
+ array.AppendElement(addr);
+ }
+
+ double trrFetchDuration;
+ rec->GetTrrFetchDuration(&trrFetchDuration);
+
+ double trrFetchDurationNetworkOnly;
+ rec->GetTrrFetchDurationNetworkOnly(&trrFetchDurationNetworkOnly);
+
+ bool isTRR = false;
+ rec->IsTRR(&isTRR);
+
+ nsIRequest::TRRMode effectiveTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ rec->GetEffectiveTRRMode(&effectiveTRRMode);
+
+ uint32_t ttl = 0;
+ rec->GetTtl(&ttl);
+
+ SendLookupCompletedHelper(
+ mIPCActor, DNSRequestResponse(DNSRecord(cname, array, trrFetchDuration,
+ trrFetchDurationNetworkOnly,
+ isTRR, effectiveTRRMode, ttl)));
+ } else {
+ SendLookupCompletedHelper(mIPCActor, DNSRequestResponse(status));
+ }
+
+ return NS_OK;
+}
+
+void DNSRequestHandler::OnIPCActorDestroy() { mIPCActor = nullptr; }
+
+//-----------------------------------------------------------------------------
+// DNSRequestParent functions
+//-----------------------------------------------------------------------------
+
+DNSRequestParent::DNSRequestParent(DNSRequestBase* aRequest)
+ : DNSRequestActor(aRequest) {
+ aRequest->SetIPCActor(this);
+}
+
+mozilla::ipc::IPCResult DNSRequestParent::RecvCancelDNSRequest(
+ const nsCString& hostName, const nsCString& trrServer, const int32_t& port,
+ const uint16_t& type, const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason) {
+ mDNSRequest->OnRecvCancelDNSRequest(hostName, trrServer, port, type,
+ originAttributes, flags, reason);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DNSRequestParent::RecvLookupCompleted(
+ const DNSRequestResponse& reply) {
+ return mDNSRequest->OnRecvLookupCompleted(reply) ? IPC_OK()
+ : IPC_FAIL_NO_REASON(this);
+}
+
+void DNSRequestParent::ActorDestroy(ActorDestroyReason) {
+ mDNSRequest->OnIPCActorDestroy();
+ mDNSRequest = nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSRequestParent.h b/netwerk/dns/DNSRequestParent.h
new file mode 100644
index 0000000000..7583709678
--- /dev/null
+++ b/netwerk/dns/DNSRequestParent.h
@@ -0,0 +1,45 @@
+/* -*- 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_DNSRequestParent_h
+#define mozilla_net_DNSRequestParent_h
+
+#include "mozilla/net/DNSRequestBase.h"
+#include "mozilla/net/PDNSRequestParent.h"
+#include "nsIDNSListener.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSRequestParent : public DNSRequestActor, public PDNSRequestParent {
+ public:
+ friend class PDNSRequestParent;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DNSRequestParent, override)
+
+ explicit DNSRequestParent(DNSRequestBase* aRequest);
+
+ bool CanSend() const override { return PDNSRequestParent::CanSend(); }
+ DNSRequestChild* AsDNSRequestChild() override { return nullptr; }
+ DNSRequestParent* AsDNSRequestParent() override { return this; }
+
+ private:
+ virtual ~DNSRequestParent() = default;
+
+ // Pass args here rather than storing them in the parent; they are only
+ // needed if the request is to be canceled.
+ mozilla::ipc::IPCResult RecvCancelDNSRequest(
+ const nsCString& hostName, const nsCString& trrServer,
+ const int32_t& port, const uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason);
+ mozilla::ipc::IPCResult RecvLookupCompleted(const DNSRequestResponse& reply);
+ void ActorDestroy(ActorDestroyReason) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DNSRequestParent_h
diff --git a/netwerk/dns/DNSServiceBase.cpp b/netwerk/dns/DNSServiceBase.cpp
new file mode 100644
index 0000000000..85b2da37e3
--- /dev/null
+++ b/netwerk/dns/DNSServiceBase.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 "DNSServiceBase.h"
+
+#include "DNS.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIDNSService.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIPrefBranch.h"
+
+namespace mozilla::net {
+
+static const char kPrefProxyType[] = "network.proxy.type";
+static const char kPrefDisablePrefetch[] = "network.dns.disablePrefetch";
+static const char kPrefNetworkProxySOCKS[] = "network.proxy.socks";
+
+NS_IMPL_ISUPPORTS(DNSServiceBase, nsIObserver)
+
+void DNSServiceBase::AddPrefObserver(nsIPrefBranch* aPrefs) {
+ aPrefs->AddObserver(kPrefProxyType, this, false);
+ aPrefs->AddObserver(kPrefDisablePrefetch, this, false);
+ // Monitor these to see if there is a change in proxy configuration
+ aPrefs->AddObserver(kPrefNetworkProxySOCKS, this, false);
+}
+
+void DNSServiceBase::ReadPrefs(const char* aName) {
+ if (!aName || !strcmp(aName, kPrefNetworkProxySOCKS)) {
+ nsAutoCString socks;
+ if (NS_SUCCEEDED(Preferences::GetCString(kPrefNetworkProxySOCKS, socks))) {
+ mHasSocksProxy = !socks.IsEmpty();
+ }
+ }
+ if (!aName || !strcmp(aName, kPrefDisablePrefetch) ||
+ !strcmp(aName, kPrefProxyType)) {
+ mDisablePrefetch = Preferences::GetBool(kPrefDisablePrefetch, false) ||
+ (StaticPrefs::network_proxy_type() ==
+ nsIProtocolProxyService::PROXYCONFIG_MANUAL);
+ }
+}
+
+bool DNSServiceBase::DNSForbiddenByActiveProxy(const nsACString& aHostname,
+ uint32_t aFlags) {
+ if (aFlags & nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS) {
+ return false;
+ }
+
+ // We should avoid doing DNS when a proxy is in use.
+ if (StaticPrefs::network_proxy_type() ==
+ nsIProtocolProxyService::PROXYCONFIG_MANUAL &&
+ mHasSocksProxy && StaticPrefs::network_proxy_socks_remote_dns()) {
+ // Allow IP lookups through, but nothing else.
+ if (!HostIsIPLiteral(aHostname)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/dns/DNSServiceBase.h b/netwerk/dns/DNSServiceBase.h
new file mode 100644
index 0000000000..9657d809f7
--- /dev/null
+++ b/netwerk/dns/DNSServiceBase.h
@@ -0,0 +1,36 @@
+/* -*- 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_DNSServiceBase_h
+#define mozilla_net_DNSServiceBase_h
+
+#include "mozilla/Atomics.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+
+class nsIPrefBranch;
+
+namespace mozilla::net {
+
+class DNSServiceBase : public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DNSServiceBase() = default;
+
+ protected:
+ virtual ~DNSServiceBase() = default;
+ void AddPrefObserver(nsIPrefBranch* aPrefs);
+ virtual void ReadPrefs(const char* aName);
+ bool DNSForbiddenByActiveProxy(const nsACString& aHostname, uint32_t aFlags);
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mDisablePrefetch{false};
+ mozilla::Atomic<bool, mozilla::Relaxed> mHasSocksProxy{false};
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_DNSServiceBase_h
diff --git a/netwerk/dns/DNSUtils.cpp b/netwerk/dns/DNSUtils.cpp
new file mode 100644
index 0000000000..8101f28116
--- /dev/null
+++ b/netwerk/dns/DNSUtils.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DNSUtils.h"
+
+#include "nsContentUtils.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "mozilla/SyncRunnable.h"
+#include "TRRServiceChannel.h"
+#include "TRRLoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+static void InitHttpHandler() {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+}
+
+// static
+nsresult DNSUtils::CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult) {
+ *aResult = nullptr;
+
+ if (NS_IsMainThread() && !XRE_IsSocketProcess()) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewChannel(
+ aResult, aUri, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ios);
+ }
+
+ // Unfortunately, we can only initialize gHttpHandler on main thread.
+ if (!gHttpHandler) {
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget();
+ if (main) {
+ // Forward to the main thread synchronously.
+ SyncRunnable::DispatchToThread(
+ main, NS_NewRunnableFunction("InitHttpHandler",
+ []() { InitHttpHandler(); }));
+ }
+ }
+
+ if (!gHttpHandler) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<TRRLoadInfo> loadInfo =
+ new TRRLoadInfo(aUri, nsIContentPolicy::TYPE_OTHER);
+ return gHttpHandler->CreateTRRServiceChannel(aUri,
+ nullptr, // givenProxyInfo
+ 0, // proxyResolveFlags
+ nullptr, // proxyURI
+ loadInfo, // aLoadInfo
+ aResult);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSUtils.h b/netwerk/dns/DNSUtils.h
new file mode 100644
index 0000000000..f5b53c5d72
--- /dev/null
+++ b/netwerk/dns/DNSUtils.h
@@ -0,0 +1,32 @@
+/* -*- 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 DNSUtils_h_
+#define DNSUtils_h_
+
+#include "nsError.h"
+
+class nsIURI;
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class NetworkConnectivityService;
+class TRR;
+
+class DNSUtils final {
+ private:
+ friend class NetworkConnectivityService;
+ friend class ObliviousHttpService;
+ friend class TRR;
+ static nsresult CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DNSUtils_h_
diff --git a/netwerk/dns/GetAddrInfo.cpp b/netwerk/dns/GetAddrInfo.cpp
new file mode 100644
index 0000000000..0dd1f0099a
--- /dev/null
+++ b/netwerk/dns/GetAddrInfo.cpp
@@ -0,0 +1,584 @@
+/* -*- 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 "GetAddrInfo.h"
+
+#ifdef DNSQUERY_AVAILABLE
+// There is a bug in windns.h where the type of parameter ppQueryResultsSet for
+// DnsQuery_A is dependent on UNICODE being set. It should *always* be
+// PDNS_RECORDA, but if UNICODE is set it is PDNS_RECORDW. To get around this
+// we make sure that UNICODE is unset.
+# undef UNICODE
+# include <ws2tcpip.h>
+# undef GetAddrInfo
+# include <windns.h>
+#endif // DNSQUERY_AVAILABLE
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/DNS.h"
+#include "NativeDNSResolverOverrideParent.h"
+#include "prnetdb.h"
+#include "nsIOService.h"
+#include "nsHostResolver.h"
+#include "nsError.h"
+#include "mozilla/net/DNS.h"
+#include <algorithm>
+#include "prerror.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/net/DNSPacket.h"
+#include "nsIDNSService.h"
+
+namespace mozilla::net {
+
+static StaticRefPtr<NativeDNSResolverOverride> gOverrideService;
+
+LazyLogModule gGetAddrInfoLog("GetAddrInfo");
+#define LOG(msg, ...) \
+ MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
+#define LOG_WARNING(msg, ...) \
+ MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__))
+
+#ifdef DNSQUERY_AVAILABLE
+
+# define COMPUTER_NAME_BUFFER_SIZE 100
+static char sDNSComputerName[COMPUTER_NAME_BUFFER_SIZE];
+static char sNETBIOSComputerName[MAX_COMPUTERNAME_LENGTH + 1];
+
+////////////////////////////
+// WINDOWS IMPLEMENTATION //
+////////////////////////////
+
+// Ensure consistency of PR_* and AF_* constants to allow for legacy usage of
+// PR_* constants with this API.
+static_assert(PR_AF_INET == AF_INET && PR_AF_INET6 == AF_INET6 &&
+ PR_AF_UNSPEC == AF_UNSPEC,
+ "PR_AF_* must match AF_*");
+
+// If successful, returns in aResult a TTL value that is smaller or
+// equal with the one already there. Gets the TTL value by calling
+// to DnsQuery_A and iterating through the returned
+// records to find the one with the smallest TTL value.
+static MOZ_ALWAYS_INLINE nsresult _CallDnsQuery_A_Windows(
+ const nsACString& aHost, uint16_t aAddressFamily, DWORD aFlags,
+ std::function<void(PDNS_RECORDA)> aCallback) {
+ NS_ConvertASCIItoUTF16 name(aHost);
+
+ auto callDnsQuery_A = [&](uint16_t reqFamily) {
+ PDNS_RECORDA dnsData = nullptr;
+ DNS_STATUS status = DnsQuery_A(aHost.BeginReading(), reqFamily, aFlags,
+ nullptr, &dnsData, nullptr);
+ if (status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_RCODE_NAME_ERROR ||
+ !dnsData) {
+ LOG("No DNS records found for %s. status=%lX. reqFamily = %X\n",
+ aHost.BeginReading(), status, reqFamily);
+ return NS_ERROR_FAILURE;
+ } else if (status != NOERROR) {
+ LOG_WARNING("DnsQuery_A failed with status %lX.\n", status);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (PDNS_RECORDA curRecord = dnsData; curRecord;
+ curRecord = curRecord->pNext) {
+ // Only records in the answer section are important
+ if (curRecord->Flags.S.Section != DnsSectionAnswer) {
+ continue;
+ }
+ if (curRecord->wType != reqFamily) {
+ continue;
+ }
+
+ aCallback(curRecord);
+ }
+
+ DnsFree(dnsData, DNS_FREE_TYPE::DnsFreeRecordList);
+ return NS_OK;
+ };
+
+ if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET) {
+ callDnsQuery_A(DNS_TYPE_A);
+ }
+
+ if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET6) {
+ callDnsQuery_A(DNS_TYPE_AAAA);
+ }
+ return NS_OK;
+}
+
+bool recordTypeMatchesRequest(uint16_t wType, uint16_t aAddressFamily) {
+ if (aAddressFamily == PR_AF_UNSPEC) {
+ return wType == DNS_TYPE_A || wType == DNS_TYPE_AAAA;
+ }
+ if (aAddressFamily == PR_AF_INET) {
+ return wType == DNS_TYPE_A;
+ }
+ if (aAddressFamily == PR_AF_INET6) {
+ return wType == DNS_TYPE_AAAA;
+ }
+ return false;
+}
+
+static MOZ_ALWAYS_INLINE nsresult _GetTTLData_Windows(const nsACString& aHost,
+ uint32_t* aResult,
+ uint16_t aAddressFamily) {
+ MOZ_ASSERT(!aHost.IsEmpty());
+ MOZ_ASSERT(aResult);
+ if (aAddressFamily != PR_AF_UNSPEC && aAddressFamily != PR_AF_INET &&
+ aAddressFamily != PR_AF_INET6) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // In order to avoid using ANY records which are not always implemented as a
+ // "Gimme what you have" request in hostname resolvers, we should send A
+ // and/or AAAA requests, based on the address family requested.
+ const DWORD ttlFlags =
+ (DNS_QUERY_STANDARD | DNS_QUERY_NO_NETBT | DNS_QUERY_NO_HOSTS_FILE |
+ DNS_QUERY_NO_MULTICAST | DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE |
+ DNS_QUERY_DONT_RESET_TTL_VALUES);
+ unsigned int ttl = (unsigned int)-1;
+ _CallDnsQuery_A_Windows(
+ aHost, aAddressFamily, ttlFlags,
+ [&ttl, &aHost, aAddressFamily](PDNS_RECORDA curRecord) {
+ if (recordTypeMatchesRequest(curRecord->wType, aAddressFamily)) {
+ ttl = std::min<unsigned int>(ttl, curRecord->dwTtl);
+ } else {
+ LOG("Received unexpected record type %u in response for %s.\n",
+ curRecord->wType, aHost.BeginReading());
+ }
+ });
+
+ if (ttl == (unsigned int)-1) {
+ LOG("No useable TTL found.");
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = ttl;
+ return NS_OK;
+}
+
+static MOZ_ALWAYS_INLINE nsresult
+_DNSQuery_A_SingleLabel(const nsACString& aCanonHost, uint16_t aAddressFamily,
+ uint16_t aFlags, AddrInfo** aAddrInfo) {
+ bool setCanonName = aFlags & nsHostResolver::RES_CANON_NAME;
+ nsAutoCString canonName;
+ const DWORD flags = (DNS_QUERY_STANDARD | DNS_QUERY_NO_MULTICAST |
+ DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE);
+ nsTArray<NetAddr> addresses;
+
+ _CallDnsQuery_A_Windows(
+ aCanonHost, aAddressFamily, flags, [&](PDNS_RECORDA curRecord) {
+ MOZ_DIAGNOSTIC_ASSERT(curRecord->wType == DNS_TYPE_A ||
+ curRecord->wType == DNS_TYPE_AAAA);
+ if (setCanonName) {
+ canonName.Assign(curRecord->pName);
+ }
+ NetAddr addr{};
+ addr.inet.family = AF_INET;
+ addr.inet.ip = curRecord->Data.A.IpAddress;
+ addresses.AppendElement(addr);
+ });
+
+ LOG("Query for: %s has %zu results", aCanonHost.BeginReading(),
+ addresses.Length());
+ if (addresses.IsEmpty()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ RefPtr<AddrInfo> ai(new AddrInfo(
+ aCanonHost, canonName, DNSResolverType::Native, 0, std::move(addresses)));
+ ai.forget(aAddrInfo);
+
+ return NS_OK;
+}
+
+#endif
+
+////////////////////////////////////
+// PORTABLE RUNTIME IMPLEMENTATION//
+////////////////////////////////////
+
+static MOZ_ALWAYS_INLINE nsresult
+_GetAddrInfo_Portable(const nsACString& aCanonHost, uint16_t aAddressFamily,
+ uint16_t aFlags, AddrInfo** aAddrInfo) {
+ MOZ_ASSERT(!aCanonHost.IsEmpty());
+ MOZ_ASSERT(aAddrInfo);
+
+ // We accept the same aFlags that nsHostResolver::ResolveHost accepts, but we
+ // need to translate the aFlags into a form that PR_GetAddrInfoByName
+ // accepts.
+ int prFlags = PR_AI_ADDRCONFIG;
+ if (!(aFlags & nsHostResolver::RES_CANON_NAME)) {
+ prFlags |= PR_AI_NOCANONNAME;
+ }
+
+ // We need to remove IPv4 records manually because PR_GetAddrInfoByName
+ // doesn't support PR_AF_INET6.
+ bool disableIPv4 = aAddressFamily == PR_AF_INET6;
+ if (disableIPv4) {
+ aAddressFamily = PR_AF_UNSPEC;
+ }
+
+#if defined(DNSQUERY_AVAILABLE)
+ if (StaticPrefs::network_dns_dns_query_single_label() &&
+ !aCanonHost.Contains('.') && aCanonHost != "localhost"_ns) {
+ // For some reason we can't use DnsQuery_A to get the computer's IP.
+ if (!aCanonHost.Equals(nsDependentCString(sDNSComputerName),
+ nsCaseInsensitiveCStringComparator) &&
+ !aCanonHost.Equals(nsDependentCString(sNETBIOSComputerName),
+ nsCaseInsensitiveCStringComparator)) {
+ // This is a single label name resolve without a dot.
+ // We use DNSQuery_A for these.
+ LOG("Resolving %s using DnsQuery_A (computername: %s)\n",
+ aCanonHost.BeginReading(), sDNSComputerName);
+ return _DNSQuery_A_SingleLabel(aCanonHost, aAddressFamily, aFlags,
+ aAddrInfo);
+ }
+ }
+#endif
+
+ LOG("Resolving %s using PR_GetAddrInfoByName", aCanonHost.BeginReading());
+ PRAddrInfo* prai =
+ PR_GetAddrInfoByName(aCanonHost.BeginReading(), aAddressFamily, prFlags);
+
+ if (!prai) {
+ LOG("PR_GetAddrInfoByName returned null PR_GetError:%d PR_GetOSErrpr:%d",
+ PR_GetError(), PR_GetOSError());
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ nsAutoCString canonName;
+ if (aFlags & nsHostResolver::RES_CANON_NAME) {
+ canonName.Assign(PR_GetCanonNameFromAddrInfo(prai));
+ }
+
+ bool filterNameCollision =
+ !(aFlags & nsHostResolver::RES_ALLOW_NAME_COLLISION);
+ RefPtr<AddrInfo> ai(new AddrInfo(aCanonHost, prai, disableIPv4,
+ filterNameCollision, canonName));
+ PR_FreeAddrInfo(prai);
+ if (ai->Addresses().IsEmpty()) {
+ LOG("PR_GetAddrInfoByName returned empty address list");
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ ai.forget(aAddrInfo);
+
+ LOG("PR_GetAddrInfoByName resolved successfully");
+ return NS_OK;
+}
+
+//////////////////////////////////////
+// COMMON/PLATFORM INDEPENDENT CODE //
+//////////////////////////////////////
+nsresult GetAddrInfoInit() {
+ LOG("Initializing GetAddrInfo.\n");
+
+#ifdef DNSQUERY_AVAILABLE
+ DWORD namesize = COMPUTER_NAME_BUFFER_SIZE;
+ if (!GetComputerNameExA(ComputerNameDnsHostname, sDNSComputerName,
+ &namesize)) {
+ sDNSComputerName[0] = 0;
+ }
+ namesize = MAX_COMPUTERNAME_LENGTH + 1;
+ if (!GetComputerNameExA(ComputerNameNetBIOS, sNETBIOSComputerName,
+ &namesize)) {
+ sNETBIOSComputerName[0] = 0;
+ }
+#endif
+ return NS_OK;
+}
+
+nsresult GetAddrInfoShutdown() {
+ LOG("Shutting down GetAddrInfo.\n");
+ return NS_OK;
+}
+
+bool FindAddrOverride(const nsACString& aHost, uint16_t aAddressFamily,
+ uint16_t aFlags, AddrInfo** aAddrInfo) {
+ RefPtr<NativeDNSResolverOverride> overrideService = gOverrideService;
+ if (!overrideService) {
+ return false;
+ }
+ AutoReadLock lock(overrideService->mLock);
+ auto overrides = overrideService->mOverrides.Lookup(aHost);
+ if (!overrides) {
+ return false;
+ }
+ nsCString* cname = nullptr;
+ if (aFlags & nsHostResolver::RES_CANON_NAME) {
+ cname = overrideService->mCnames.Lookup(aHost).DataPtrOrNull();
+ }
+
+ RefPtr<AddrInfo> ai;
+
+ nsTArray<NetAddr> addresses;
+ for (const auto& ip : *overrides) {
+ if (aAddressFamily != AF_UNSPEC && ip.raw.family != aAddressFamily) {
+ continue;
+ }
+ addresses.AppendElement(ip);
+ }
+
+ if (!cname) {
+ ai = new AddrInfo(aHost, DNSResolverType::Native, 0, std::move(addresses));
+ } else {
+ ai = new AddrInfo(aHost, *cname, DNSResolverType::Native, 0,
+ std::move(addresses));
+ }
+
+ ai.forget(aAddrInfo);
+ return true;
+}
+
+nsresult GetAddrInfo(const nsACString& aHost, uint16_t aAddressFamily,
+ uint16_t aFlags, AddrInfo** aAddrInfo, bool aGetTtl) {
+ if (NS_WARN_IF(aHost.IsEmpty()) || NS_WARN_IF(!aAddrInfo)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aAddrInfo = nullptr;
+
+ if (StaticPrefs::network_dns_disabled()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+#ifdef DNSQUERY_AVAILABLE
+ // The GetTTLData needs the canonical name to function properly
+ if (aGetTtl) {
+ aFlags |= nsHostResolver::RES_CANON_NAME;
+ }
+#endif
+
+ // If there is an override for this host, then we synthetize a result.
+ if (gOverrideService &&
+ FindAddrOverride(aHost, aAddressFamily, aFlags, aAddrInfo)) {
+ LOG("Returning IP address from NativeDNSResolverOverride");
+ return (*aAddrInfo)->Addresses().Length() ? NS_OK : NS_ERROR_UNKNOWN_HOST;
+ }
+
+ nsAutoCString host;
+ if (StaticPrefs::network_dns_copy_string_before_call()) {
+ host = Substring(aHost.BeginReading(), aHost.Length());
+ MOZ_ASSERT(aHost.BeginReading() != host.BeginReading());
+ } else {
+ host = aHost;
+ }
+
+ if (gNativeIsLocalhost) {
+ // pretend we use the given host but use IPv4 localhost instead!
+ host = "localhost"_ns;
+ aAddressFamily = PR_AF_INET;
+ }
+
+ RefPtr<AddrInfo> info;
+ nsresult rv =
+ _GetAddrInfo_Portable(host, aAddressFamily, aFlags, getter_AddRefs(info));
+
+#ifdef DNSQUERY_AVAILABLE
+ if (aGetTtl && NS_SUCCEEDED(rv)) {
+ // Figure out the canonical name, or if that fails, just use the host name
+ // we have.
+ nsAutoCString name;
+ if (info && !info->CanonicalHostname().IsEmpty()) {
+ name = info->CanonicalHostname();
+ } else {
+ name = host;
+ }
+
+ LOG("Getting TTL for %s (cname = %s).", host.get(), name.get());
+ uint32_t ttl = 0;
+ nsresult ttlRv = _GetTTLData_Windows(name, &ttl, aAddressFamily);
+ if (NS_SUCCEEDED(ttlRv)) {
+ auto builder = info->Build();
+ builder.SetTTL(ttl);
+ info = builder.Finish();
+ LOG("Got TTL %u for %s (name = %s).", ttl, host.get(), name.get());
+ } else {
+ LOG_WARNING("Could not get TTL for %s (cname = %s).", host.get(),
+ name.get());
+ }
+ }
+#endif
+
+ info.forget(aAddrInfo);
+ return rv;
+}
+
+bool FindHTTPSRecordOverride(const nsACString& aHost,
+ TypeRecordResultType& aResult) {
+ LOG("FindHTTPSRecordOverride aHost=%s", nsCString(aHost).get());
+ RefPtr<NativeDNSResolverOverride> overrideService = gOverrideService;
+ if (!overrideService) {
+ return false;
+ }
+
+ AutoReadLock lock(overrideService->mLock);
+ auto overrides = overrideService->mHTTPSRecordOverrides.Lookup(aHost);
+ if (!overrides) {
+ return false;
+ }
+
+ DNSPacket packet;
+ nsAutoCString host(aHost);
+ nsAutoCString cname;
+
+ LOG("resolving %s\n", host.get());
+ // Perform the query
+ nsresult rv = packet.FillBuffer(
+ [&](unsigned char response[DNSPacket::MAX_SIZE]) -> int {
+ if (overrides->Length() > DNSPacket::MAX_SIZE) {
+ return -1;
+ }
+ memcpy(response, overrides->Elements(), overrides->Length());
+ return overrides->Length();
+ });
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ uint32_t ttl = 0;
+ rv = ParseHTTPSRecord(host, packet, aResult, ttl);
+
+ return NS_SUCCEEDED(rv);
+}
+
+nsresult ParseHTTPSRecord(nsCString& aHost, DNSPacket& aDNSPacket,
+ TypeRecordResultType& aResult, uint32_t& aTTL) {
+ nsAutoCString cname;
+ nsresult rv;
+
+ aDNSPacket.SetNativePacket(true);
+
+ int32_t loopCount = 64;
+ while (loopCount > 0 && aResult.is<Nothing>()) {
+ loopCount--;
+ DOHresp resp;
+ nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
+ rv = aDNSPacket.Decode(aHost, TRRTYPE_HTTPSSVC, cname, true, resp, aResult,
+ additionalRecords, aTTL);
+ if (NS_FAILED(rv)) {
+ LOG("Decode failed %x", static_cast<uint32_t>(rv));
+ return rv;
+ }
+ if (!cname.IsEmpty() && aResult.is<Nothing>()) {
+ aHost = cname;
+ cname.Truncate();
+ continue;
+ }
+ }
+
+ if (aResult.is<Nothing>()) {
+ LOG("Result is nothing");
+ // The call succeeded, but no HTTPS records were found.
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ return NS_OK;
+}
+
+nsresult ResolveHTTPSRecord(const nsACString& aHost, uint16_t aFlags,
+ TypeRecordResultType& aResult, uint32_t& aTTL) {
+ if (gOverrideService) {
+ return FindHTTPSRecordOverride(aHost, aResult) ? NS_OK
+ : NS_ERROR_UNKNOWN_HOST;
+ }
+
+ return ResolveHTTPSRecordImpl(aHost, aFlags, aResult, aTTL);
+}
+
+// static
+already_AddRefed<nsINativeDNSResolverOverride>
+NativeDNSResolverOverride::GetSingleton() {
+ if (nsIOService::UseSocketProcess() && XRE_IsParentProcess()) {
+ return NativeDNSResolverOverrideParent::GetSingleton();
+ }
+
+ if (gOverrideService) {
+ return do_AddRef(gOverrideService);
+ }
+
+ gOverrideService = new NativeDNSResolverOverride();
+ ClearOnShutdown(&gOverrideService);
+ return do_AddRef(gOverrideService);
+}
+
+NS_IMPL_ISUPPORTS(NativeDNSResolverOverride, nsINativeDNSResolverOverride)
+
+NS_IMETHODIMP NativeDNSResolverOverride::AddIPOverride(
+ const nsACString& aHost, const nsACString& aIPLiteral) {
+ NetAddr tempAddr;
+
+ if (aIPLiteral.Equals("N/A"_ns)) {
+ AutoWriteLock lock(mLock);
+ auto& overrides = mOverrides.LookupOrInsert(aHost);
+ overrides.Clear();
+ return NS_OK;
+ }
+
+ if (NS_FAILED(tempAddr.InitFromString(aIPLiteral))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ AutoWriteLock lock(mLock);
+ auto& overrides = mOverrides.LookupOrInsert(aHost);
+ overrides.AppendElement(tempAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverride::AddHTTPSRecordOverride(
+ const nsACString& aHost, const uint8_t* aData, uint32_t aLength) {
+ AutoWriteLock lock(mLock);
+ nsTArray<uint8_t> data(aData, aLength);
+ mHTTPSRecordOverrides.InsertOrUpdate(aHost, std::move(data));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverride::SetCnameOverride(
+ const nsACString& aHost, const nsACString& aCNAME) {
+ if (aCNAME.IsEmpty()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ AutoWriteLock lock(mLock);
+ mCnames.InsertOrUpdate(aHost, nsCString(aCNAME));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverride::ClearHostOverride(
+ const nsACString& aHost) {
+ AutoWriteLock lock(mLock);
+ mCnames.Remove(aHost);
+ auto overrides = mOverrides.Extract(aHost);
+ if (!overrides) {
+ return NS_OK;
+ }
+
+ overrides->Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverride::ClearOverrides() {
+ AutoWriteLock lock(mLock);
+ mOverrides.Clear();
+ mCnames.Clear();
+ return NS_OK;
+}
+
+#ifdef MOZ_NO_HTTPS_IMPL
+
+// If there is no platform specific implementation of ResolveHTTPSRecordImpl
+// we link a dummy implementation here.
+// Otherwise this is implemented in GetAddrInfoWin/Linux/etc
+nsresult ResolveHTTPSRecordImpl(const nsACString& aHost, uint16_t aFlags,
+ TypeRecordResultType& aResult, uint32_t& aTTL) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#endif // MOZ_NO_HTTPS_IMPL
+
+} // namespace mozilla::net
diff --git a/netwerk/dns/GetAddrInfo.h b/netwerk/dns/GetAddrInfo.h
new file mode 100644
index 0000000000..e8dc095415
--- /dev/null
+++ b/netwerk/dns/GetAddrInfo.h
@@ -0,0 +1,111 @@
+/* -*- 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 netwerk_dns_GetAddrInfo_h
+#define netwerk_dns_GetAddrInfo_h
+
+#include "nsError.h"
+#include "nscore.h"
+#include "nsINativeDNSResolverOverride.h"
+#include "nsHashKeys.h"
+#include "nsTHashMap.h"
+#include "mozilla/RWLock.h"
+#include "nsTArray.h"
+#include "prio.h"
+#include "mozilla/net/DNS.h"
+#include "nsIDNSByTypeRecord.h"
+#include "mozilla/Logging.h"
+
+#if defined(XP_WIN)
+# define DNSQUERY_AVAILABLE 1
+#else
+# undef DNSQUERY_AVAILABLE
+#endif
+
+namespace mozilla {
+namespace net {
+
+extern LazyLogModule gGetAddrInfoLog;
+class AddrInfo;
+class DNSPacket;
+
+/**
+ * Look up a host by name. Mostly equivalent to getaddrinfo(host, NULL, ...) of
+ * RFC 3493.
+ *
+ * @param aHost[in] Character string defining the host name of interest
+ * @param aAddressFamily[in] May be AF_INET, AF_INET6, or AF_UNSPEC.
+ * @param aFlags[in] May be either PR_AI_ADDRCONFIG or
+ * PR_AI_ADDRCONFIG | PR_AI_NOCANONNAME. Include PR_AI_NOCANONNAME to
+ * suppress the determination of the canonical name corresponding to
+ * hostname (PR_AI_NOCANONNAME will be ignored if the TTL is retrieved).
+ * @param aAddrInfo[out] Will point to the results of the host lookup, or be
+ * null if the lookup failed.
+ * @param aGetTtl[in] If true, the TTL will be retrieved if DNS provides the
+ * answers..
+ */
+nsresult GetAddrInfo(const nsACString& aHost, uint16_t aAddressFamily,
+ uint16_t aFlags, AddrInfo** aAddrInfo, bool aGetTtl);
+
+/**
+ * Initialize the GetAddrInfo module.
+ *
+ * GetAddrInfoShutdown() should be called for every time this function is
+ * called.
+ */
+nsresult GetAddrInfoInit();
+
+/**
+ * Shutdown the GetAddrInfo module.
+ *
+ * This function should be called for every time GetAddrInfoInit() is called.
+ * An assertion may throw (but is not guarenteed) if this function is called
+ * too many times.
+ */
+nsresult GetAddrInfoShutdown();
+
+/**
+ * Resolves a HTTPS record. Will check overrides before calling the
+ * native OS implementation.
+ */
+nsresult ResolveHTTPSRecord(const nsACString& aHost, uint16_t aFlags,
+ TypeRecordResultType& aResult, uint32_t& aTTL);
+
+/**
+ * The platform specific implementation of HTTPS resolution.
+ */
+nsresult ResolveHTTPSRecordImpl(const nsACString& aHost, uint16_t aFlags,
+ TypeRecordResultType& aResult, uint32_t& aTTL);
+
+nsresult ParseHTTPSRecord(nsCString& aHost, DNSPacket& aDNSPacket,
+ TypeRecordResultType& aResult, uint32_t& aTTL);
+
+class NativeDNSResolverOverride : public nsINativeDNSResolverOverride {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINATIVEDNSRESOLVEROVERRIDE
+ public:
+ NativeDNSResolverOverride() = default;
+
+ static already_AddRefed<nsINativeDNSResolverOverride> GetSingleton();
+
+ private:
+ virtual ~NativeDNSResolverOverride() = default;
+ mozilla::RWLock mLock MOZ_UNANNOTATED{"NativeDNSResolverOverride"};
+
+ nsTHashMap<nsCStringHashKey, nsTArray<NetAddr>> mOverrides;
+ nsTHashMap<nsCStringHashKey, nsCString> mCnames;
+ nsTHashMap<nsCStringHashKey, nsTArray<uint8_t>> mHTTPSRecordOverrides;
+
+ friend bool FindAddrOverride(const nsACString& aHost, uint16_t aAddressFamily,
+ uint16_t aFlags, AddrInfo** aAddrInfo);
+ friend bool FindHTTPSRecordOverride(const nsACString& aHost,
+ TypeRecordResultType& aResult);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // netwerk_dns_GetAddrInfo_h
diff --git a/netwerk/dns/HTTPSSVC.cpp b/netwerk/dns/HTTPSSVC.cpp
new file mode 100644
index 0000000000..d36bdf91a2
--- /dev/null
+++ b/netwerk/dns/HTTPSSVC.cpp
@@ -0,0 +1,523 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTTPSSVC.h"
+#include "mozilla/net/DNS.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsNetAddr.h"
+#include "nsNetUtil.h"
+#include "nsIDNSService.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(SVCBRecord, nsISVCBRecord)
+
+class SvcParam : public nsISVCParam,
+ public nsISVCParamAlpn,
+ public nsISVCParamNoDefaultAlpn,
+ public nsISVCParamPort,
+ public nsISVCParamIPv4Hint,
+ public nsISVCParamEchConfig,
+ public nsISVCParamIPv6Hint,
+ public nsISVCParamODoHConfig {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISVCPARAM
+ NS_DECL_NSISVCPARAMALPN
+ NS_DECL_NSISVCPARAMNODEFAULTALPN
+ NS_DECL_NSISVCPARAMPORT
+ NS_DECL_NSISVCPARAMIPV4HINT
+ NS_DECL_NSISVCPARAMECHCONFIG
+ NS_DECL_NSISVCPARAMIPV6HINT
+ NS_DECL_NSISVCPARAMODOHCONFIG
+ public:
+ explicit SvcParam(const SvcParamType& value) : mValue(value){};
+
+ private:
+ virtual ~SvcParam() = default;
+ SvcParamType mValue;
+};
+
+NS_IMPL_ADDREF(SvcParam)
+NS_IMPL_RELEASE(SvcParam)
+NS_INTERFACE_MAP_BEGIN(SvcParam)
+ NS_INTERFACE_MAP_ENTRY(nsISVCParam)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISVCParam)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamAlpn, mValue.is<SvcParamAlpn>())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamNoDefaultAlpn,
+ mValue.is<SvcParamNoDefaultAlpn>())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamPort, mValue.is<SvcParamPort>())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamIPv4Hint,
+ mValue.is<SvcParamIpv4Hint>())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamEchConfig,
+ mValue.is<SvcParamEchConfig>())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamIPv6Hint,
+ mValue.is<SvcParamIpv6Hint>())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamODoHConfig,
+ mValue.is<SvcParamODoHConfig>())
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+SvcParam::GetType(uint16_t* aType) {
+ *aType = mValue.match(
+ [](Nothing&) { return SvcParamKeyMandatory; },
+ [](SvcParamAlpn&) { return SvcParamKeyAlpn; },
+ [](SvcParamNoDefaultAlpn&) { return SvcParamKeyNoDefaultAlpn; },
+ [](SvcParamPort&) { return SvcParamKeyPort; },
+ [](SvcParamIpv4Hint&) { return SvcParamKeyIpv4Hint; },
+ [](SvcParamEchConfig&) { return SvcParamKeyEchConfig; },
+ [](SvcParamIpv6Hint&) { return SvcParamKeyIpv6Hint; },
+ [](SvcParamODoHConfig&) { return SvcParamKeyODoHConfig; });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SvcParam::GetAlpn(nsTArray<nsCString>& aAlpn) {
+ if (!mValue.is<SvcParamAlpn>()) {
+ MOZ_ASSERT(false, "Unexpected type for variant");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aAlpn.AppendElements(mValue.as<SvcParamAlpn>().mValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SvcParam::GetPort(uint16_t* aPort) {
+ if (!mValue.is<SvcParamPort>()) {
+ MOZ_ASSERT(false, "Unexpected type for variant");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aPort = mValue.as<SvcParamPort>().mValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SvcParam::GetEchconfig(nsACString& aEchConfig) {
+ if (!mValue.is<SvcParamEchConfig>()) {
+ MOZ_ASSERT(false, "Unexpected type for variant");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aEchConfig = mValue.as<SvcParamEchConfig>().mValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SvcParam::GetIpv4Hint(nsTArray<RefPtr<nsINetAddr>>& aIpv4Hint) {
+ if (!mValue.is<SvcParamIpv4Hint>()) {
+ MOZ_ASSERT(false, "Unexpected type for variant");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ const auto& results = mValue.as<SvcParamIpv4Hint>().mValue;
+ for (const auto& ip : results) {
+ if (ip.raw.family != AF_INET) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ RefPtr<nsINetAddr> hint = new nsNetAddr(&ip);
+ aIpv4Hint.AppendElement(hint);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SvcParam::GetIpv6Hint(nsTArray<RefPtr<nsINetAddr>>& aIpv6Hint) {
+ if (!mValue.is<SvcParamIpv6Hint>()) {
+ MOZ_ASSERT(false, "Unexpected type for variant");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ const auto& results = mValue.as<SvcParamIpv6Hint>().mValue;
+ for (const auto& ip : results) {
+ if (ip.raw.family != AF_INET6) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ RefPtr<nsINetAddr> hint = new nsNetAddr(&ip);
+ aIpv6Hint.AppendElement(hint);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SvcParam::GetODoHConfig(nsACString& aODoHConfig) {
+ if (!mValue.is<SvcParamODoHConfig>()) {
+ MOZ_ASSERT(false, "Unexpected type for variant");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aODoHConfig = mValue.as<SvcParamODoHConfig>().mValue;
+ return NS_OK;
+}
+
+bool SVCB::operator<(const SVCB& aOther) const {
+ if (gHttpHandler->EchConfigEnabled()) {
+ if (mHasEchConfig && !aOther.mHasEchConfig) {
+ return true;
+ }
+ if (!mHasEchConfig && aOther.mHasEchConfig) {
+ return false;
+ }
+ }
+
+ return mSvcFieldPriority < aOther.mSvcFieldPriority;
+}
+
+Maybe<uint16_t> SVCB::GetPort() const {
+ Maybe<uint16_t> port;
+ for (const auto& value : mSvcFieldValue) {
+ if (value.mValue.is<SvcParamPort>()) {
+ port.emplace(value.mValue.as<SvcParamPort>().mValue);
+ if (NS_FAILED(NS_CheckPortSafety(*port, "https"))) {
+ *port = 0;
+ }
+ return port;
+ }
+ }
+
+ return Nothing();
+}
+
+bool SVCB::NoDefaultAlpn() const {
+ for (const auto& value : mSvcFieldValue) {
+ if (value.mValue.is<SvcParamKeyNoDefaultAlpn>()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void SVCB::GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const {
+ if (mSvcFieldPriority == 0) {
+ return;
+ }
+
+ for (const auto& value : mSvcFieldValue) {
+ if (value.mValue.is<SvcParamIpv4Hint>()) {
+ aAddresses.AppendElements(value.mValue.as<SvcParamIpv4Hint>().mValue);
+ } else if (value.mValue.is<SvcParamIpv6Hint>()) {
+ aAddresses.AppendElements(value.mValue.as<SvcParamIpv6Hint>().mValue);
+ }
+ }
+}
+
+class AlpnComparator {
+ public:
+ bool Equals(const std::tuple<nsCString, SupportedAlpnRank>& aA,
+ const std::tuple<nsCString, SupportedAlpnRank>& aB) const {
+ return std::get<1>(aA) == std::get<1>(aB);
+ }
+ bool LessThan(const std::tuple<nsCString, SupportedAlpnRank>& aA,
+ const std::tuple<nsCString, SupportedAlpnRank>& aB) const {
+ return std::get<1>(aA) > std::get<1>(aB);
+ }
+};
+
+nsTArray<std::tuple<nsCString, SupportedAlpnRank>> SVCB::GetAllAlpn() const {
+ nsTArray<std::tuple<nsCString, SupportedAlpnRank>> alpnList;
+ for (const auto& value : mSvcFieldValue) {
+ if (value.mValue.is<SvcParamAlpn>()) {
+ for (const auto& alpn : value.mValue.as<SvcParamAlpn>().mValue) {
+ alpnList.AppendElement(std::make_tuple(alpn, IsAlpnSupported(alpn)));
+ }
+ }
+ }
+ alpnList.Sort(AlpnComparator());
+ return alpnList;
+}
+
+SVCBRecord::SVCBRecord(const SVCB& data,
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> aAlpn)
+ : mData(data), mAlpn(aAlpn) {
+ mPort = mData.GetPort();
+}
+
+NS_IMETHODIMP SVCBRecord::GetPriority(uint16_t* aPriority) {
+ *aPriority = mData.mSvcFieldPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP SVCBRecord::GetName(nsACString& aName) {
+ aName = mData.mSvcDomainName;
+ return NS_OK;
+}
+
+Maybe<uint16_t> SVCBRecord::GetPort() { return mPort; }
+
+Maybe<std::tuple<nsCString, SupportedAlpnRank>> SVCBRecord::GetAlpn() {
+ return mAlpn;
+}
+
+NS_IMETHODIMP SVCBRecord::GetSelectedAlpn(nsACString& aAlpn) {
+ if (!mAlpn) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aAlpn = std::get<0>(*mAlpn);
+ return NS_OK;
+}
+
+NS_IMETHODIMP SVCBRecord::GetEchConfig(nsACString& aEchConfig) {
+ aEchConfig = mData.mEchConfig;
+ return NS_OK;
+}
+
+NS_IMETHODIMP SVCBRecord::GetODoHConfig(nsACString& aODoHConfig) {
+ aODoHConfig = mData.mODoHConfig;
+ return NS_OK;
+}
+
+NS_IMETHODIMP SVCBRecord::GetValues(nsTArray<RefPtr<nsISVCParam>>& aValues) {
+ for (const auto& v : mData.mSvcFieldValue) {
+ RefPtr<nsISVCParam> param = new SvcParam(v.mValue);
+ aValues.AppendElement(param);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress) {
+ *aHasIPHintAddress = mData.mHasIPHints;
+ return NS_OK;
+}
+
+static bool CheckRecordIsUsable(const SVCB& aRecord, nsIDNSService* aDNSService,
+ const nsACString& aHost,
+ uint32_t& aExcludedCount) {
+ if (!aHost.IsEmpty()) {
+ bool excluded = false;
+ if (NS_SUCCEEDED(aDNSService->IsSVCDomainNameFailed(
+ aHost, aRecord.mSvcDomainName, &excluded)) &&
+ excluded) {
+ // Skip if the domain name of this record was failed to connect before.
+ ++aExcludedCount;
+ return false;
+ }
+ }
+
+ Maybe<uint16_t> port = aRecord.GetPort();
+ if (port && *port == 0) {
+ // Found an unsafe port, skip this record.
+ return false;
+ }
+
+ return true;
+}
+
+static bool CheckAlpnIsUsable(SupportedAlpnRank aAlpnType, bool aNoHttp2,
+ bool aNoHttp3, bool aCheckHttp3ExcludedList,
+ const nsACString& aTargetName,
+ uint32_t& aExcludedCount) {
+ // Skip if this alpn is not supported.
+ if (aAlpnType == SupportedAlpnRank::NOT_SUPPORTED) {
+ return false;
+ }
+
+ // Skip if we don't want to use http2.
+ if (aNoHttp2 && aAlpnType == SupportedAlpnRank::HTTP_2) {
+ return false;
+ }
+
+ if (IsHttp3(aAlpnType)) {
+ if (aCheckHttp3ExcludedList && gHttpHandler->IsHttp3Excluded(aTargetName)) {
+ aExcludedCount++;
+ return false;
+ }
+
+ if (aNoHttp3) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static nsTArray<SVCBWrapper> FlattenRecords(const nsTArray<SVCB>& aRecords) {
+ nsTArray<SVCBWrapper> result;
+ for (const auto& record : aRecords) {
+ nsTArray<std::tuple<nsCString, SupportedAlpnRank>> alpnList =
+ record.GetAllAlpn();
+ if (alpnList.IsEmpty()) {
+ result.AppendElement(SVCBWrapper(record));
+ } else {
+ for (const auto& alpn : alpnList) {
+ SVCBWrapper wrapper(record);
+ wrapper.mAlpn = Some(alpn);
+ result.AppendElement(wrapper);
+ }
+ }
+ }
+ return result;
+}
+
+already_AddRefed<nsISVCBRecord>
+DNSHTTPSSVCRecordBase::GetServiceModeRecordInternal(
+ bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
+ bool& aRecordsAllExcluded, bool aCheckHttp3ExcludedList) {
+ RefPtr<SVCBRecord> selectedRecord;
+ RefPtr<SVCBRecord> h3RecordWithEchConfig;
+ uint32_t recordHasNoDefaultAlpnCount = 0;
+ uint32_t recordExcludedCount = 0;
+ aRecordsAllExcluded = false;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ uint32_t h3ExcludedCount = 0;
+
+ nsTArray<SVCBWrapper> records = FlattenRecords(aRecords);
+ for (const auto& record : records) {
+ if (record.mRecord.mSvcFieldPriority == 0) {
+ // In ServiceMode, the SvcPriority should never be 0.
+ return nullptr;
+ }
+
+ if (record.mRecord.NoDefaultAlpn()) {
+ ++recordHasNoDefaultAlpnCount;
+ }
+
+ if (!CheckRecordIsUsable(record.mRecord, dns, mHost, recordExcludedCount)) {
+ // Skip if this record is not usable.
+ continue;
+ }
+
+ if (record.mAlpn) {
+ if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
+ aCheckHttp3ExcludedList,
+ record.mRecord.mSvcDomainName, h3ExcludedCount)) {
+ continue;
+ }
+
+ if (IsHttp3(std::get<1>(*(record.mAlpn)))) {
+ // If the selected alpn is h3 and ech for h3 is disabled, we want
+ // to find out if there is another non-h3 record that has
+ // echConfig. If yes, we'll use the non-h3 record with echConfig
+ // to connect. If not, we'll use h3 to connect without echConfig.
+ if (record.mRecord.mHasEchConfig &&
+ (gHttpHandler->EchConfigEnabled() &&
+ !gHttpHandler->EchConfigEnabled(true))) {
+ if (!h3RecordWithEchConfig) {
+ // Save this h3 record for later use.
+ h3RecordWithEchConfig =
+ new SVCBRecord(record.mRecord, record.mAlpn);
+ // Make sure the next record is not h3.
+ aNoHttp3 = true;
+ continue;
+ }
+ }
+ }
+ }
+
+ if (!selectedRecord) {
+ selectedRecord = new SVCBRecord(record.mRecord, record.mAlpn);
+ }
+ }
+
+ if (!selectedRecord && !h3RecordWithEchConfig) {
+ // If all records indicate "no-default-alpn", we should not use this RRSet.
+ if (recordHasNoDefaultAlpnCount == records.Length()) {
+ return nullptr;
+ }
+
+ if (recordExcludedCount == records.Length()) {
+ aRecordsAllExcluded = true;
+ return nullptr;
+ }
+
+ // If all records are in http3 excluded list, try again without checking the
+ // excluded list. This is better than returning nothing.
+ if (h3ExcludedCount == records.Length() && aCheckHttp3ExcludedList) {
+ return GetServiceModeRecordInternal(aNoHttp2, aNoHttp3, aRecords,
+ aRecordsAllExcluded, false);
+ }
+ }
+
+ if (h3RecordWithEchConfig) {
+ if (selectedRecord && selectedRecord->mData.mHasEchConfig) {
+ return selectedRecord.forget();
+ }
+
+ return h3RecordWithEchConfig.forget();
+ }
+
+ return selectedRecord.forget();
+}
+
+void DNSHTTPSSVCRecordBase::GetAllRecordsWithEchConfigInternal(
+ bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
+ bool* aAllRecordsHaveEchConfig, bool* aAllRecordsInH3ExcludedList,
+ nsTArray<RefPtr<nsISVCBRecord>>& aResult, bool aCheckHttp3ExcludedList) {
+ if (aRecords.IsEmpty()) {
+ return;
+ }
+
+ *aAllRecordsHaveEchConfig = aRecords[0].mHasEchConfig;
+ *aAllRecordsInH3ExcludedList = false;
+ // The first record should have echConfig.
+ if (!(*aAllRecordsHaveEchConfig)) {
+ return;
+ }
+
+ uint32_t h3ExcludedCount = 0;
+ nsTArray<SVCBWrapper> records = FlattenRecords(aRecords);
+ for (const auto& record : records) {
+ if (record.mRecord.mSvcFieldPriority == 0) {
+ // This should not happen, since GetAllRecordsWithEchConfigInternal()
+ // should be called only if GetServiceModeRecordInternal() returns a
+ // non-null record.
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ // Records with echConfig are in front of records without echConfig, so we
+ // don't have to continue.
+ *aAllRecordsHaveEchConfig &= record.mRecord.mHasEchConfig;
+ if (!(*aAllRecordsHaveEchConfig)) {
+ aResult.Clear();
+ return;
+ }
+
+ Maybe<uint16_t> port = record.mRecord.GetPort();
+ if (port && *port == 0) {
+ // Found an unsafe port, skip this record.
+ continue;
+ }
+
+ if (record.mAlpn) {
+ if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
+ aCheckHttp3ExcludedList,
+ record.mRecord.mSvcDomainName, h3ExcludedCount)) {
+ continue;
+ }
+ }
+
+ RefPtr<nsISVCBRecord> svcbRecord =
+ new SVCBRecord(record.mRecord, record.mAlpn);
+ aResult.AppendElement(svcbRecord);
+ }
+
+ // If all records are in http3 excluded list, try again without checking the
+ // excluded list. This is better than returning nothing.
+ if (h3ExcludedCount == records.Length() && aCheckHttp3ExcludedList) {
+ GetAllRecordsWithEchConfigInternal(
+ aNoHttp2, aNoHttp3, aRecords, aAllRecordsHaveEchConfig,
+ aAllRecordsInH3ExcludedList, aResult, false);
+ *aAllRecordsInH3ExcludedList = true;
+ }
+}
+
+bool DNSHTTPSSVCRecordBase::HasIPAddressesInternal(
+ const nsTArray<SVCB>& aRecords) {
+ for (const SVCB& record : aRecords) {
+ if (record.mSvcFieldPriority != 0) {
+ for (const auto& value : record.mSvcFieldValue) {
+ if (value.mValue.is<SvcParamIpv4Hint>()) {
+ return true;
+ }
+ if (value.mValue.is<SvcParamIpv6Hint>()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/HTTPSSVC.h b/netwerk/dns/HTTPSSVC.h
new file mode 100644
index 0000000000..792f894a02
--- /dev/null
+++ b/netwerk/dns/HTTPSSVC.h
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HTTPSSVC_h__
+#define HTTPSSVC_h__
+
+#include "nsIDNSByTypeRecord.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Variant.h"
+#include "mozilla/Maybe.h"
+#include "nsHttp.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSHTTPSSVCRecordBase;
+
+enum SvcParamKey : uint16_t {
+ SvcParamKeyMandatory = 0,
+ SvcParamKeyAlpn = 1,
+ SvcParamKeyNoDefaultAlpn = 2,
+ SvcParamKeyPort = 3,
+ SvcParamKeyIpv4Hint = 4,
+ SvcParamKeyEchConfig = 5,
+ SvcParamKeyIpv6Hint = 6,
+ SvcParamKeyODoHConfig = 32769,
+};
+
+inline bool IsValidSvcParamKey(uint16_t aKey) {
+ return aKey <= SvcParamKeyIpv6Hint || aKey == SvcParamKeyODoHConfig;
+}
+
+struct SvcParamAlpn {
+ bool operator==(const SvcParamAlpn& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ CopyableTArray<nsCString> mValue;
+};
+
+struct SvcParamNoDefaultAlpn {
+ bool operator==(const SvcParamNoDefaultAlpn& aOther) const { return true; }
+};
+
+struct SvcParamPort {
+ bool operator==(const SvcParamPort& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ uint16_t mValue;
+};
+
+struct SvcParamIpv4Hint {
+ bool operator==(const SvcParamIpv4Hint& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ CopyableTArray<mozilla::net::NetAddr> mValue;
+};
+
+struct SvcParamEchConfig {
+ bool operator==(const SvcParamEchConfig& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ nsCString mValue;
+};
+
+struct SvcParamIpv6Hint {
+ bool operator==(const SvcParamIpv6Hint& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ CopyableTArray<mozilla::net::NetAddr> mValue;
+};
+
+struct SvcParamODoHConfig {
+ bool operator==(const SvcParamODoHConfig& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ nsCString mValue;
+};
+
+using SvcParamType =
+ mozilla::Variant<Nothing, SvcParamAlpn, SvcParamNoDefaultAlpn, SvcParamPort,
+ SvcParamIpv4Hint, SvcParamEchConfig, SvcParamIpv6Hint,
+ SvcParamODoHConfig>;
+
+struct SvcFieldValue {
+ bool operator==(const SvcFieldValue& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ SvcFieldValue() : mValue(AsVariant(Nothing{})) {}
+ SvcParamType mValue;
+};
+
+struct SVCB {
+ bool operator==(const SVCB& aOther) const {
+ return mSvcFieldPriority == aOther.mSvcFieldPriority &&
+ mSvcDomainName == aOther.mSvcDomainName &&
+ mSvcFieldValue == aOther.mSvcFieldValue;
+ }
+ bool operator<(const SVCB& aOther) const;
+ Maybe<uint16_t> GetPort() const;
+ bool NoDefaultAlpn() const;
+ void GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const;
+ nsTArray<std::tuple<nsCString, SupportedAlpnRank>> GetAllAlpn() const;
+ uint16_t mSvcFieldPriority = 0;
+ nsCString mSvcDomainName;
+ nsCString mEchConfig;
+ nsCString mODoHConfig;
+ bool mHasIPHints = false;
+ bool mHasEchConfig = false;
+ CopyableTArray<SvcFieldValue> mSvcFieldValue;
+};
+
+struct SVCBWrapper {
+ explicit SVCBWrapper(const SVCB& aRecord) : mRecord(aRecord) {}
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> mAlpn;
+ const SVCB& mRecord;
+};
+
+class SVCBRecord : public nsISVCBRecord {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISVCBRECORD
+ public:
+ explicit SVCBRecord(const SVCB& data)
+ : mData(data), mPort(Nothing()), mAlpn(Nothing()) {}
+ explicit SVCBRecord(const SVCB& data,
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> aAlpn);
+
+ private:
+ friend class DNSHTTPSSVCRecordBase;
+
+ virtual ~SVCBRecord() = default;
+
+ SVCB mData;
+ Maybe<uint16_t> mPort;
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> mAlpn;
+};
+
+class DNSHTTPSSVCRecordBase {
+ public:
+ explicit DNSHTTPSSVCRecordBase(const nsACString& aHost) : mHost(aHost) {}
+
+ protected:
+ virtual ~DNSHTTPSSVCRecordBase() = default;
+
+ already_AddRefed<nsISVCBRecord> GetServiceModeRecordInternal(
+ bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
+ bool& aRecordsAllExcluded, bool aCheckHttp3ExcludedList = true);
+
+ bool HasIPAddressesInternal(const nsTArray<SVCB>& aRecords);
+
+ void GetAllRecordsWithEchConfigInternal(
+ bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
+ bool* aAllRecordsHaveEchConfig, bool* aAllRecordsInH3ExcludedList,
+ nsTArray<RefPtr<nsISVCBRecord>>& aResult,
+ bool aCheckHttp3ExcludedList = true);
+
+ // The owner name of this HTTPS RR.
+ nsCString mHost;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HTTPSSVC_h__
diff --git a/netwerk/dns/HostRecordQueue.cpp b/netwerk/dns/HostRecordQueue.cpp
new file mode 100644
index 0000000000..6e8fd5488f
--- /dev/null
+++ b/netwerk/dns/HostRecordQueue.cpp
@@ -0,0 +1,223 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HostRecordQueue.h"
+#include "mozilla/Telemetry.h"
+#include "nsQueryObject.h"
+
+namespace mozilla {
+namespace net {
+
+void HostRecordQueue::InsertRecord(nsHostRecord* aRec,
+ nsIDNSService::DNSFlags aFlags,
+ const MutexAutoLock& aProofOfLock) {
+ if (aRec->isInList()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEvictionQ.contains(aRec),
+ "Already in eviction queue");
+ MOZ_DIAGNOSTIC_ASSERT(!mHighQ.contains(aRec), "Already in high queue");
+ MOZ_DIAGNOSTIC_ASSERT(!mMediumQ.contains(aRec), "Already in med queue");
+ MOZ_DIAGNOSTIC_ASSERT(!mLowQ.contains(aRec), "Already in low queue");
+ MOZ_DIAGNOSTIC_ASSERT(false, "Already on some other queue?");
+ }
+
+ switch (AddrHostRecord::GetPriority(aFlags)) {
+ case AddrHostRecord::DNS_PRIORITY_HIGH:
+ mHighQ.insertBack(aRec);
+ break;
+
+ case AddrHostRecord::DNS_PRIORITY_MEDIUM:
+ mMediumQ.insertBack(aRec);
+ break;
+
+ case AddrHostRecord::DNS_PRIORITY_LOW:
+ mLowQ.insertBack(aRec);
+ break;
+ }
+ mPendingCount++;
+}
+
+void HostRecordQueue::AddToEvictionQ(
+ nsHostRecord* aRec, uint32_t aMaxCacheEntries,
+ nsRefPtrHashtable<nsGenericHashKey<nsHostKey>, nsHostRecord>& aDB,
+ const MutexAutoLock& aProofOfLock) {
+ if (aRec->isInList()) {
+ bool inEvictionQ = mEvictionQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(!inEvictionQ, "Already in eviction queue");
+ bool inHighQ = mHighQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(!inHighQ, "Already in high queue");
+ bool inMediumQ = mMediumQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(!inMediumQ, "Already in med queue");
+ bool inLowQ = mLowQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(!inLowQ, "Already in low queue");
+ MOZ_DIAGNOSTIC_ASSERT(false, "Already on some other queue?");
+
+ // Bug 1678117 - it's not clear why this can happen, but let's fix it
+ // for release users.
+ aRec->remove();
+ if (inEvictionQ) {
+ MOZ_DIAGNOSTIC_ASSERT(mEvictionQSize > 0);
+ mEvictionQSize--;
+ } else if (inHighQ || inMediumQ || inLowQ) {
+ MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
+ mPendingCount--;
+ }
+ }
+ mEvictionQ.insertBack(aRec);
+ if (mEvictionQSize < aMaxCacheEntries) {
+ mEvictionQSize++;
+ } else {
+ // remove first element on mEvictionQ
+ RefPtr<nsHostRecord> head = mEvictionQ.popFirst();
+ aDB.Remove(*static_cast<nsHostKey*>(head.get()));
+
+ if (!head->negative) {
+ // record the age of the entry upon eviction.
+ TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
+ if (aRec->IsAddrRecord()) {
+ Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
+ static_cast<uint32_t>(age.ToSeconds() / 60));
+ } else {
+ Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_CLEANUP_AGE,
+ static_cast<uint32_t>(age.ToSeconds() / 60));
+ }
+ if (head->CheckExpiration(TimeStamp::Now()) !=
+ nsHostRecord::EXP_EXPIRED) {
+ if (aRec->IsAddrRecord()) {
+ Telemetry::Accumulate(Telemetry::DNS_PREMATURE_EVICTION,
+ static_cast<uint32_t>(age.ToSeconds() / 60));
+ } else {
+ Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_PREMATURE_EVICTION,
+ static_cast<uint32_t>(age.ToSeconds() / 60));
+ }
+ }
+ }
+ }
+}
+
+void HostRecordQueue::MaybeRenewHostRecord(nsHostRecord* aRec,
+ const MutexAutoLock& aProofOfLock) {
+ if (!aRec->isInList()) {
+ return;
+ }
+
+ bool inEvictionQ = mEvictionQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(inEvictionQ, "Should be in eviction queue");
+ bool inHighQ = mHighQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(!inHighQ, "Already in high queue");
+ bool inMediumQ = mMediumQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(!inMediumQ, "Already in med queue");
+ bool inLowQ = mLowQ.contains(aRec);
+ MOZ_DIAGNOSTIC_ASSERT(!inLowQ, "Already in low queue");
+
+ // we're already on the eviction queue. This is a renewal
+ aRec->remove();
+ if (inEvictionQ) {
+ MOZ_DIAGNOSTIC_ASSERT(mEvictionQSize > 0);
+ mEvictionQSize--;
+ } else if (inHighQ || inMediumQ || inLowQ) {
+ MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
+ mPendingCount--;
+ }
+}
+
+void HostRecordQueue::FlushEvictionQ(
+ nsRefPtrHashtable<nsGenericHashKey<nsHostKey>, nsHostRecord>& aDB,
+ const MutexAutoLock& aProofOfLock) {
+ mEvictionQSize = 0;
+
+ // Clear the evictionQ and remove all its corresponding entries from
+ // the cache first
+ if (!mEvictionQ.isEmpty()) {
+ for (const RefPtr<nsHostRecord>& rec : mEvictionQ) {
+ rec->Cancel();
+ aDB.Remove(*static_cast<nsHostKey*>(rec));
+ }
+ mEvictionQ.clear();
+ }
+}
+
+void HostRecordQueue::MaybeRemoveFromQ(nsHostRecord* aRec,
+ const MutexAutoLock& aProofOfLock) {
+ if (!aRec->isInList()) {
+ return;
+ }
+
+ if (mHighQ.contains(aRec) || mMediumQ.contains(aRec) ||
+ mLowQ.contains(aRec)) {
+ mPendingCount--;
+ } else if (mEvictionQ.contains(aRec)) {
+ mEvictionQSize--;
+ } else {
+ MOZ_ASSERT(false, "record is in other queue");
+ }
+
+ aRec->remove();
+}
+
+void HostRecordQueue::MoveToAnotherPendingQ(nsHostRecord* aRec,
+ nsIDNSService::DNSFlags aFlags,
+ const MutexAutoLock& aProofOfLock) {
+ if (!(mHighQ.contains(aRec) || mMediumQ.contains(aRec) ||
+ mLowQ.contains(aRec))) {
+ MOZ_ASSERT(false, "record is not in the pending queue");
+ return;
+ }
+
+ aRec->remove();
+ InsertRecord(aRec, aFlags, aProofOfLock);
+}
+
+already_AddRefed<nsHostRecord> HostRecordQueue::Dequeue(
+ bool aHighQOnly, const MutexAutoLock& aProofOfLock) {
+ RefPtr<nsHostRecord> rec;
+ if (!mHighQ.isEmpty()) {
+ rec = mHighQ.popFirst();
+ } else if (!mMediumQ.isEmpty() && !aHighQOnly) {
+ rec = mMediumQ.popFirst();
+ } else if (!mLowQ.isEmpty() && !aHighQOnly) {
+ rec = mLowQ.popFirst();
+ }
+
+ if (rec) {
+ mPendingCount--;
+ }
+
+ return rec.forget();
+}
+
+void HostRecordQueue::ClearAll(
+ const std::function<void(nsHostRecord*)>& aCallback,
+ const MutexAutoLock& aProofOfLock) {
+ mPendingCount = 0;
+
+ auto clearPendingQ = [&](LinkedList<RefPtr<nsHostRecord>>& aPendingQ) {
+ if (aPendingQ.isEmpty()) {
+ return;
+ }
+
+ // loop through pending queue, erroring out pending lookups.
+ for (const RefPtr<nsHostRecord>& rec : aPendingQ) {
+ rec->Cancel();
+ aCallback(rec);
+ }
+ aPendingQ.clear();
+ };
+
+ clearPendingQ(mHighQ);
+ clearPendingQ(mMediumQ);
+ clearPendingQ(mLowQ);
+
+ mEvictionQSize = 0;
+ if (!mEvictionQ.isEmpty()) {
+ for (const RefPtr<nsHostRecord>& rec : mEvictionQ) {
+ rec->Cancel();
+ }
+ }
+
+ mEvictionQ.clear();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/HostRecordQueue.h b/netwerk/dns/HostRecordQueue.h
new file mode 100644
index 0000000000..dfa56d1c88
--- /dev/null
+++ b/netwerk/dns/HostRecordQueue.h
@@ -0,0 +1,72 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HostRecordQueue_h__
+#define HostRecordQueue_h__
+
+#include <functional>
+#include "mozilla/Mutex.h"
+#include "nsHostRecord.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+class HostRecordQueue final {
+ public:
+ HostRecordQueue() = default;
+ ~HostRecordQueue() = default;
+ HostRecordQueue(const HostRecordQueue& aCopy) = delete;
+ HostRecordQueue& operator=(const HostRecordQueue& aCopy) = delete;
+
+ uint32_t PendingCount() const { return mPendingCount; }
+ uint32_t EvictionQSize() const { return mEvictionQSize; }
+
+ // Insert the record to mHighQ or mMediumQ or mLowQ based on the record's
+ // priority.
+ void InsertRecord(nsHostRecord* aRec, nsIDNSService::DNSFlags aFlags,
+ const MutexAutoLock& aProofOfLock);
+ // Insert the record to mEvictionQ. In theory, this function should be called
+ // when the record is not in any queue.
+ void AddToEvictionQ(
+ nsHostRecord* aRec, uint32_t aMaxCacheEntries,
+ nsRefPtrHashtable<nsGenericHashKey<nsHostKey>, nsHostRecord>& aDB,
+ const MutexAutoLock& aProofOfLock);
+ // Called for removing the record from mEvictionQ. When this function is
+ // called, the record should be either in mEvictionQ or not in any queue.
+ void MaybeRenewHostRecord(nsHostRecord* aRec,
+ const MutexAutoLock& aProofOfLock);
+ // Called for clearing mEvictionQ.
+ void FlushEvictionQ(
+ nsRefPtrHashtable<nsGenericHashKey<nsHostKey>, nsHostRecord>& aDB,
+ const MutexAutoLock& aProofOfLock);
+ // Remove the record from the queue that contains it.
+ void MaybeRemoveFromQ(nsHostRecord* aRec, const MutexAutoLock& aProofOfLock);
+ // When the record's priority changes, move the record between pending queues.
+ void MoveToAnotherPendingQ(nsHostRecord* aRec, nsIDNSService::DNSFlags aFlags,
+ const MutexAutoLock& aProofOfLock);
+ // Returning the first record from one of the pending queue. When |aHighQOnly|
+ // is true, returning the record from mHighQ only. When false, return the
+ // record from mMediumQ or mLowQ.
+ already_AddRefed<nsHostRecord> Dequeue(bool aHighQOnly,
+ const MutexAutoLock& aProofOfLock);
+ // Clear all queues and is called only during shutdown. |aCallback| is invoked
+ // when a record is removed from a queue.
+ void ClearAll(const std::function<void(nsHostRecord*)>& aCallback,
+ const MutexAutoLock& aProofOfLock);
+
+ private:
+ Atomic<uint32_t> mPendingCount{0};
+ Atomic<uint32_t> mEvictionQSize{0};
+ LinkedList<RefPtr<nsHostRecord>> mHighQ;
+ LinkedList<RefPtr<nsHostRecord>> mMediumQ;
+ LinkedList<RefPtr<nsHostRecord>> mLowQ;
+ LinkedList<RefPtr<nsHostRecord>> mEvictionQ;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HostRecordQueue_h__
diff --git a/netwerk/dns/IDNBlocklistUtils.cpp b/netwerk/dns/IDNBlocklistUtils.cpp
new file mode 100644
index 0000000000..92c73d0997
--- /dev/null
+++ b/netwerk/dns/IDNBlocklistUtils.cpp
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IDNBlocklistUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace net {
+
+static constexpr char16_t sBlocklistPairs[][2] = {
+#include "IDNCharacterBlocklist.inc"
+};
+
+void RemoveCharFromBlocklist(char16_t aChar,
+ nsTArray<BlocklistRange>& aBlocklist) {
+ auto pos = aBlocklist.BinaryIndexOf(aChar, BlocklistPairToCharComparator());
+ if (pos == nsTArray<BlocklistRange>::NoIndex) {
+ return;
+ }
+
+ auto& pair = aBlocklist[pos];
+
+ // If the matched range has a length of one, we can just remove it
+ if (pair.second == pair.first) {
+ aBlocklist.RemoveElementAt(pos);
+ return;
+ }
+
+ // If the character matches the first element in the range, just update
+ // the range.
+ if (aChar == pair.first) {
+ pair.first = pair.first + 1;
+ return;
+ }
+
+ // Also if it matches the last character in the range, we just update it.
+ if (aChar == pair.second) {
+ pair.second = pair.second - 1;
+ return;
+ }
+
+ // Our character is in the middle of the range, splitting it in two.
+ // We update the matched range to reflect the values before the character,
+ // and insert a new range that represents the values after.
+ char16_t lastElement = pair.second;
+ pair.second = aChar - 1;
+ aBlocklist.InsertElementAt(pos + 1,
+ std::make_pair(char16_t(aChar + 1), lastElement));
+}
+
+void InitializeBlocklist(nsTArray<BlocklistRange>& aBlocklist) {
+ aBlocklist.Clear();
+ for (auto const& arr : sBlocklistPairs) {
+ // The hardcoded pairs are already sorted.
+ aBlocklist.AppendElement(std::make_pair(arr[0], arr[1]));
+ }
+
+ nsAutoString extraAllowed;
+ nsresult rv =
+ Preferences::GetString("network.IDN.extra_allowed_chars", extraAllowed);
+ if (NS_SUCCEEDED(rv) && !extraAllowed.IsEmpty()) {
+ const char16_t* cur = extraAllowed.BeginReading();
+ const char16_t* end = extraAllowed.EndReading();
+ // Characters in the allowed list are removed from the blocklist.
+ for (; cur < end; ++cur) {
+ RemoveCharFromBlocklist(*cur, aBlocklist);
+ }
+ }
+
+ nsAutoString extraBlocked;
+ rv = Preferences::GetString("network.IDN.extra_blocked_chars", extraBlocked);
+ // We add each extra blocked character to the blocklist as a separate range.
+ if (NS_SUCCEEDED(rv) && !extraBlocked.IsEmpty()) {
+ for (size_t i = 0; i < extraBlocked.Length(); ++i) {
+ aBlocklist.AppendElement(
+ std::make_pair(extraBlocked[i], extraBlocked[i]));
+ }
+ aBlocklist.Sort(BlocklistEntryComparator());
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/IDNBlocklistUtils.h b/netwerk/dns/IDNBlocklistUtils.h
new file mode 100644
index 0000000000..6e1c6c0a77
--- /dev/null
+++ b/netwerk/dns/IDNBlocklistUtils.h
@@ -0,0 +1,65 @@
+/* -*- 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 IDNBlocklistUtils_h__
+#define IDNBlocklistUtils_h__
+
+#include <utility>
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+// A blocklist range is defined as all of the characters between:
+// { firstCharacterInRange, lastCharacterInRange }
+using BlocklistRange = std::pair<char16_t, char16_t>;
+
+// Used to perform a binary search of the needle in the sorted array of pairs
+class BlocklistPairToCharComparator {
+ public:
+ bool Equals(const BlocklistRange& pair, char16_t needle) const {
+ // If the needle is between pair.first and pair.second it
+ // is part of the range.
+ return pair.first <= needle && needle <= pair.second;
+ }
+
+ bool LessThan(const BlocklistRange& pair, char16_t needle) const {
+ // The needle has to be larger than the second value,
+ // otherwise it may be equal.
+ return pair.second < needle;
+ }
+};
+
+// Used to sort the array of pairs
+class BlocklistEntryComparator {
+ public:
+ bool Equals(const BlocklistRange& a, const BlocklistRange& b) const {
+ return a.first == b.first && a.second == b.second;
+ }
+
+ bool LessThan(const BlocklistRange& a, const BlocklistRange& b) const {
+ return a.first < b.first;
+ }
+};
+
+// Returns true if the char can be found in the blocklist
+inline bool CharInBlocklist(char16_t aChar,
+ const nsTArray<BlocklistRange>& aBlocklist) {
+ return aBlocklist.ContainsSorted(aChar, BlocklistPairToCharComparator());
+}
+
+// Initializes the blocklist based on the statically defined list and the
+// values of the following preferences:
+// - network.IDN.extra_allowed_chars
+// - network.IDN.extra_blocked_chars
+void InitializeBlocklist(nsTArray<BlocklistRange>& aBlocklist);
+
+void RemoveCharFromBlocklist(char16_t aChar,
+ nsTArray<BlocklistRange>& aBlocklist);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // IDNBlocklistUtils_h__
diff --git a/netwerk/dns/IDNCharacterBlocklist.inc b/netwerk/dns/IDNCharacterBlocklist.inc
new file mode 100644
index 0000000000..754c4f9518
--- /dev/null
+++ b/netwerk/dns/IDNCharacterBlocklist.inc
@@ -0,0 +1,63 @@
+// This file contains the IDN character blocklist.
+// Each entry represents a range of blocked characters.
+// Ranges are defined as:
+// { firstCharacterInRange, lastCharacterInRange }
+// IMPORTANT: Make sure this list is sorted in ascending order
+
+
+// ASCII Space
+{ 0x0020, 0x0020 },
+{ 0x00A0, 0x00A0 },
+{ 0x00BC, 0x00BE },
+{ 0x0138, 0x0138 },
+{ 0x01C3, 0x01C3 },
+{ 0x02D0, 0x02D0 },
+{ 0x0337, 0x0338 },
+{ 0x0589, 0x058A },
+{ 0x05C3, 0x05C3 },
+{ 0x05F4, 0x05F4 },
+{ 0x0609, 0x060A },
+{ 0x066A, 0x066A },
+{ 0x06D4, 0x06D4 },
+{ 0x0701, 0x0704 },
+{ 0x115F, 0x1160 },
+{ 0x1735, 0x1735 },
+{ 0x2000, 0x200B },
+{ 0x200E, 0x2010 },
+{ 0x2019, 0x2019 },
+{ 0x2024, 0x2024 },
+{ 0x2027, 0x202F },
+{ 0x2039, 0x203A },
+{ 0x2041, 0x2041 },
+{ 0x2044, 0x2044 },
+{ 0x2052, 0x2052 },
+{ 0x205F, 0x205F },
+{ 0x2153, 0x215F },
+{ 0x2215, 0x2215 },
+{ 0x2236, 0x2236 },
+{ 0x23AE, 0x23AE },
+{ 0x2571, 0x2571 },
+{ 0x29F6, 0x29F6 },
+{ 0x29F8, 0x29F8 },
+{ 0x2AFB, 0x2AFB },
+{ 0x2AFD, 0x2AFD },
+{ 0x2FF0, 0x2FFB },
+// Ideographic Space
+{ 0x3000, 0x3000 },
+{ 0x3002, 0x3002 },
+{ 0x3014, 0x3015 },
+{ 0x3033, 0x3033 },
+{ 0x30A0, 0x30A0 },
+{ 0x3164, 0x3164 },
+{ 0x321D, 0x321E },
+{ 0x33AE, 0x33AF },
+{ 0x33C6, 0x33C6 },
+{ 0x33DF, 0x33DF },
+{ 0xFE14, 0xFE15 },
+{ 0xFE3F, 0xFE3F },
+{ 0xFE5D, 0xFE5E },
+{ 0xFEFF, 0xFEFF },
+{ 0xFF0E, 0xFF0F },
+{ 0xFF61, 0xFF61 },
+{ 0xFFA0, 0xFFA0 },
+{ 0xFFF9, 0xFFFD },
diff --git a/netwerk/dns/NativeDNSResolverOverrideChild.cpp b/netwerk/dns/NativeDNSResolverOverrideChild.cpp
new file mode 100644
index 0000000000..b52e929ab3
--- /dev/null
+++ b/netwerk/dns/NativeDNSResolverOverrideChild.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "NativeDNSResolverOverrideChild.h"
+#include "GetAddrInfo.h"
+
+namespace mozilla {
+namespace net {
+
+NativeDNSResolverOverrideChild::NativeDNSResolverOverrideChild() {
+ mOverrideService = NativeDNSResolverOverride::GetSingleton();
+}
+
+mozilla::ipc::IPCResult NativeDNSResolverOverrideChild::RecvAddIPOverride(
+ const nsCString& aHost, const nsCString& aIPLiteral) {
+ Unused << mOverrideService->AddIPOverride(aHost, aIPLiteral);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+NativeDNSResolverOverrideChild::RecvAddHTTPSRecordOverride(
+ const nsCString& aHost, nsTArray<uint8_t>&& aData) {
+ Unused << mOverrideService->AddHTTPSRecordOverride(aHost, aData.Elements(),
+ aData.Length());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NativeDNSResolverOverrideChild::RecvSetCnameOverride(
+ const nsCString& aHost, const nsCString& aCNAME) {
+ Unused << mOverrideService->SetCnameOverride(aHost, aCNAME);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NativeDNSResolverOverrideChild::RecvClearHostOverride(
+ const nsCString& aHost) {
+ Unused << mOverrideService->ClearHostOverride(aHost);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NativeDNSResolverOverrideChild::RecvClearOverrides() {
+ Unused << mOverrideService->ClearOverrides();
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/NativeDNSResolverOverrideChild.h b/netwerk/dns/NativeDNSResolverOverrideChild.h
new file mode 100644
index 0000000000..42fcc0b7c8
--- /dev/null
+++ b/netwerk/dns/NativeDNSResolverOverrideChild.h
@@ -0,0 +1,40 @@
+/* -*- 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_NativeDNSResolverOverrideChild_h
+#define mozilla_net_NativeDNSResolverOverrideChild_h
+
+#include "mozilla/net/PNativeDNSResolverOverrideChild.h"
+#include "nsINativeDNSResolverOverride.h"
+
+namespace mozilla {
+namespace net {
+
+class NativeDNSResolverOverrideChild : public PNativeDNSResolverOverrideChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(NativeDNSResolverOverrideChild, override)
+
+ NativeDNSResolverOverrideChild();
+
+ mozilla::ipc::IPCResult RecvAddIPOverride(const nsCString& aHost,
+ const nsCString& aIPLiteral);
+ mozilla::ipc::IPCResult RecvAddHTTPSRecordOverride(const nsCString& aHost,
+ nsTArray<uint8_t>&& aData);
+ mozilla::ipc::IPCResult RecvSetCnameOverride(const nsCString& aHost,
+ const nsCString& aCNAME);
+ mozilla::ipc::IPCResult RecvClearHostOverride(const nsCString& aHost);
+ mozilla::ipc::IPCResult RecvClearOverrides();
+
+ private:
+ virtual ~NativeDNSResolverOverrideChild() = default;
+
+ nsCOMPtr<nsINativeDNSResolverOverride> mOverrideService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NativeDNSResolverOverrideChild_h
diff --git a/netwerk/dns/NativeDNSResolverOverrideParent.cpp b/netwerk/dns/NativeDNSResolverOverrideParent.cpp
new file mode 100644
index 0000000000..47ba16918f
--- /dev/null
+++ b/netwerk/dns/NativeDNSResolverOverrideParent.cpp
@@ -0,0 +1,111 @@
+/* -*- 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 "NativeDNSResolverOverrideParent.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsIOService.h"
+#include "DNS.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NativeDNSResolverOverrideParent, nsINativeDNSResolverOverride)
+
+static StaticRefPtr<NativeDNSResolverOverrideParent>
+ gNativeDNSResolverOverrideParent;
+
+// static
+already_AddRefed<nsINativeDNSResolverOverride>
+NativeDNSResolverOverrideParent::GetSingleton() {
+ if (gNativeDNSResolverOverrideParent) {
+ return do_AddRef(gNativeDNSResolverOverrideParent);
+ }
+
+ if (!gIOService) {
+ return nullptr;
+ }
+
+ gNativeDNSResolverOverrideParent = new NativeDNSResolverOverrideParent();
+ ClearOnShutdown(&gNativeDNSResolverOverrideParent);
+
+ auto initTask = []() {
+ Unused << SocketProcessParent::GetSingleton()
+ ->SendPNativeDNSResolverOverrideConstructor(
+ gNativeDNSResolverOverrideParent);
+ };
+ gIOService->CallOrWaitForSocketProcess(initTask);
+ return do_AddRef(gNativeDNSResolverOverrideParent);
+}
+
+NS_IMETHODIMP NativeDNSResolverOverrideParent::AddIPOverride(
+ const nsACString& aHost, const nsACString& aIPLiteral) {
+ NetAddr tempAddr;
+ if (!aIPLiteral.Equals("N/A"_ns) &&
+ NS_FAILED(tempAddr.InitFromString(aIPLiteral))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<NativeDNSResolverOverrideParent> self = this;
+ nsCString host(aHost);
+ nsCString ip(aIPLiteral);
+ auto task = [self{std::move(self)}, host, ip]() {
+ Unused << self->SendAddIPOverride(host, ip);
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverrideParent::AddHTTPSRecordOverride(
+ const nsACString& aHost, const uint8_t* aData, uint32_t aLength) {
+ nsCString host(aHost);
+ CopyableTArray<uint8_t> data(aData, aLength);
+ auto task = [self = RefPtr{this}, host, data = std::move(data)]() {
+ Unused << self->SendAddHTTPSRecordOverride(host, data);
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverrideParent::SetCnameOverride(
+ const nsACString& aHost, const nsACString& aCNAME) {
+ if (aCNAME.IsEmpty()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<NativeDNSResolverOverrideParent> self = this;
+ nsCString host(aHost);
+ nsCString cname(aCNAME);
+ auto task = [self{std::move(self)}, host, cname]() {
+ Unused << self->SendSetCnameOverride(host, cname);
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverrideParent::ClearHostOverride(
+ const nsACString& aHost) {
+ RefPtr<NativeDNSResolverOverrideParent> self = this;
+ nsCString host(aHost);
+ auto task = [self{std::move(self)}, host]() {
+ Unused << self->SendClearHostOverride(host);
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverrideParent::ClearOverrides() {
+ RefPtr<NativeDNSResolverOverrideParent> self = this;
+ auto task = [self{std::move(self)}]() {
+ Unused << self->SendClearOverrides();
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/NativeDNSResolverOverrideParent.h b/netwerk/dns/NativeDNSResolverOverrideParent.h
new file mode 100644
index 0000000000..b0cb5e4d6d
--- /dev/null
+++ b/netwerk/dns/NativeDNSResolverOverrideParent.h
@@ -0,0 +1,32 @@
+/* -*- 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_NativeDNSResolverOverrideParent_h
+#define mozilla_net_NativeDNSResolverOverrideParent_h
+
+#include "mozilla/net/PNativeDNSResolverOverrideParent.h"
+#include "nsINativeDNSResolverOverride.h"
+
+namespace mozilla {
+namespace net {
+
+class NativeDNSResolverOverrideParent : public PNativeDNSResolverOverrideParent,
+ public nsINativeDNSResolverOverride {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINATIVEDNSRESOLVEROVERRIDE
+
+ explicit NativeDNSResolverOverrideParent() = default;
+ static already_AddRefed<nsINativeDNSResolverOverride> GetSingleton();
+
+ private:
+ virtual ~NativeDNSResolverOverrideParent() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NativeDNSResolverOverrideParent_h
diff --git a/netwerk/dns/PDNSRequest.ipdl b/netwerk/dns/PDNSRequest.ipdl
new file mode 100644
index 0000000000..0613ff67b4
--- /dev/null
+++ b/netwerk/dns/PDNSRequest.ipdl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PSocketProcess;
+
+include PDNSRequestParams;
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using nsIDNSService::DNSFlags from "nsIDNSService.h";
+
+namespace mozilla {
+namespace net {
+
+async protocol PDNSRequest
+{
+ manager PNecko or PSocketProcess;
+
+both:
+ // constructor in PNecko takes AsyncResolve args that initialize request
+
+ // Pass args here rather than storing them in the parent; they are only
+ // needed if the request is to be canceled.
+ async CancelDNSRequest(nsCString hostName, nsCString trrServer, int32_t port,
+ uint16_t type, OriginAttributes originAttributes,
+ DNSFlags flags, nsresult reason);
+ async __delete__();
+
+ async LookupCompleted(DNSRequestResponse reply);
+
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/dns/PDNSRequestParams.ipdlh b/netwerk/dns/PDNSRequestParams.ipdlh
new file mode 100644
index 0000000000..de15e91a59
--- /dev/null
+++ b/netwerk/dns/PDNSRequestParams.ipdlh
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using mozilla::net::IPCTypeRecord from "mozilla/net/DNSByTypeRecord.h";
+using nsIRequest::TRRMode from "nsIRequest.h";
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DNS IPDL structs
+//-----------------------------------------------------------------------------
+
+struct DNSRecord
+{
+ nsCString canonicalName;
+ NetAddr[] addrs;
+ double trrFetchDuration;
+ double trrFetchDurationNetworkOnly;
+ bool isTRR;
+ TRRMode effectiveTRRMode;
+ uint32_t ttl;
+};
+
+union DNSRequestResponse
+{
+ DNSRecord;
+ IPCTypeRecord; // The result of a by-type query
+ nsresult; // if error
+};
+
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/netwerk/dns/PNativeDNSResolverOverride.ipdl b/netwerk/dns/PNativeDNSResolverOverride.ipdl
new file mode 100644
index 0000000000..a1cd4c9ff5
--- /dev/null
+++ b/netwerk/dns/PNativeDNSResolverOverride.ipdl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+
+namespace mozilla {
+namespace net {
+
+async protocol PNativeDNSResolverOverride
+{
+ manager PSocketProcess;
+
+child:
+ async __delete__();
+ async AddIPOverride(nsCString aHost, nsCString aIPLiteral);
+ async AddHTTPSRecordOverride(nsCString aHost, uint8_t[] aData);
+ async SetCnameOverride(nsCString aHost, nsCString aCNAME);
+ async ClearHostOverride(nsCString aHost);
+ async ClearOverrides();
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/dns/PTRRService.ipdl b/netwerk/dns/PTRRService.ipdl
new file mode 100644
index 0000000000..14059e033f
--- /dev/null
+++ b/netwerk/dns/PTRRService.ipdl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+
+include NeckoChannelParams;
+include PSMIPCTypes;
+
+namespace mozilla {
+namespace net {
+
+async protocol PTRRService
+{
+ manager PSocketProcess;
+
+parent:
+ async NotifyNetworkConnectivityServiceObservers(nsCString aTopic);
+ async InitTRRConnectionInfo();
+ async SetConfirmationState(uint32_t aNewState);
+
+child:
+ async __delete__();
+ async UpdatePlatformDNSInformation(nsCString[] aSuffixList);
+ async UpdateParentalControlEnabled(bool aEnabled);
+ async ClearDNSCache(bool aTrrToo);
+ async SetDetectedTrrURI(nsCString aURI);
+ async SetDefaultTRRConnectionInfo(HttpConnectionInfoCloneArgs? aConnInfoArgs);
+ async UpdateEtcHosts(nsCString[] aHosts);
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/dns/PlatformDNSAndroid.cpp b/netwerk/dns/PlatformDNSAndroid.cpp
new file mode 100644
index 0000000000..78a944777f
--- /dev/null
+++ b/netwerk/dns/PlatformDNSAndroid.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GetAddrInfo.h"
+#include "mozilla/net/DNSPacket.h"
+#include "nsIDNSService.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/StaticPrefs_network.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <poll.h>
+#include <dlfcn.h>
+#include <android/multinetwork.h>
+
+namespace mozilla::net {
+
+// The first call to ResolveHTTPSRecordImpl will load the library
+// and function pointers.
+static Atomic<bool> sLibLoading{false};
+
+// https://developer.android.com/ndk/reference/group/networking#android_res_nquery
+// The function android_res_nquery is defined in <android/multinetwork.h>
+typedef int (*android_res_nquery_ptr)(net_handle_t network, const char* dname,
+ int ns_class, int ns_type,
+ uint32_t flags);
+static Atomic<android_res_nquery_ptr> sAndroidResNQuery;
+
+#define LOG(msg, ...) \
+ MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
+
+nsresult ResolveHTTPSRecordImpl(const nsACString& aHost, uint16_t aFlags,
+ TypeRecordResultType& aResult, uint32_t& aTTL) {
+ DNSPacket packet;
+ nsAutoCString host(aHost);
+ nsAutoCString cname;
+ nsresult rv;
+
+ if (xpc::IsInAutomation() &&
+ !StaticPrefs::network_dns_native_https_query_in_automation()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (!sLibLoading.exchange(true)) {
+ // We're the first call here, load the library and symbols.
+ void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL);
+ if (!handle) {
+ LOG("Error loading libandroid_net %s", dlerror());
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ auto x = dlsym(handle, "android_res_nquery");
+ if (!x) {
+ LOG("No android_res_nquery symbol");
+ }
+
+ sAndroidResNQuery = (android_res_nquery_ptr)x;
+ }
+
+ if (!sAndroidResNQuery) {
+ LOG("nquery not loaded");
+ // The library hasn't been loaded yet.
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ LOG("resolving %s\n", host.get());
+ // Perform the query
+ rv = packet.FillBuffer(
+ [&](unsigned char response[DNSPacket::MAX_SIZE]) -> int {
+ int fd = 0;
+ auto closeSocket = MakeScopeExit([&] {
+ if (fd > 0) {
+ close(fd);
+ }
+ });
+ uint32_t flags = 0;
+ if (aFlags & nsIDNSService::RESOLVE_BYPASS_CACHE) {
+ flags = ANDROID_RESOLV_NO_CACHE_LOOKUP;
+ }
+ fd = sAndroidResNQuery(0, host.get(), ns_c_in,
+ nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags);
+
+ if (fd < 0) {
+ LOG("DNS query failed");
+ return fd;
+ }
+
+ struct pollfd fds;
+ fds.fd = fd;
+ fds.events = POLLIN; // Wait for read events
+
+ // Wait for an event on the file descriptor
+ int ret = poll(&fds, 1,
+ StaticPrefs::network_dns_native_https_timeout_android());
+ if (ret <= 0) {
+ LOG("poll failed %d", ret);
+ return -1;
+ }
+
+ ssize_t len = recv(fd, response, DNSPacket::MAX_SIZE - 1, 0);
+ if (len <= 8) {
+ LOG("size too small %zd", len);
+ return len < 0 ? len : -1;
+ }
+
+ // The first 8 bytes are UDP header.
+ // XXX: we should consider avoiding this move somehow.
+ for (int i = 0; i < len - 8; i++) {
+ response[i] = response[i + 8];
+ }
+
+ return len - 8;
+ });
+ if (NS_FAILED(rv)) {
+ LOG("failed rv");
+ return rv;
+ }
+ packet.SetNativePacket(true);
+
+ int32_t loopCount = 64;
+ while (loopCount > 0 && aResult.is<Nothing>()) {
+ loopCount--;
+ DOHresp resp;
+ nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
+ rv = packet.Decode(host, TRRTYPE_HTTPSSVC, cname, true, resp, aResult,
+ additionalRecords, aTTL);
+ if (NS_FAILED(rv)) {
+ LOG("Decode failed %x", static_cast<uint32_t>(rv));
+ return rv;
+ }
+ if (!cname.IsEmpty() && aResult.is<Nothing>()) {
+ host = cname;
+ cname.Truncate();
+ continue;
+ }
+ }
+
+ if (aResult.is<Nothing>()) {
+ LOG("Result is nothing");
+ // The call succeeded, but no HTTPS records were found.
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/dns/PlatformDNSUnix.cpp b/netwerk/dns/PlatformDNSUnix.cpp
new file mode 100644
index 0000000000..dedbe337df
--- /dev/null
+++ b/netwerk/dns/PlatformDNSUnix.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GetAddrInfo.h"
+#include "mozilla/net/DNSPacket.h"
+#include "nsIDNSService.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticPrefs_network.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <resolv.h>
+
+namespace mozilla::net {
+
+#define LOG(msg, ...) \
+ MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
+
+nsresult ResolveHTTPSRecordImpl(const nsACString& aHost, uint16_t aFlags,
+ TypeRecordResultType& aResult, uint32_t& aTTL) {
+ DNSPacket packet;
+ nsAutoCString host(aHost);
+ nsAutoCString cname;
+ nsresult rv;
+
+ if (xpc::IsInAutomation() &&
+ !StaticPrefs::network_dns_native_https_query_in_automation()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ LOG("resolving %s\n", host.get());
+ // Perform the query
+ rv = packet.FillBuffer(
+ [&](unsigned char response[DNSPacket::MAX_SIZE]) -> int {
+ int len = 0;
+#if defined(XP_LINUX)
+ len = res_nquery(&_res, host.get(), ns_c_in,
+ nsIDNSService::RESOLVE_TYPE_HTTPSSVC, response,
+ DNSPacket::MAX_SIZE);
+#elif defined(XP_MACOSX)
+ len =
+ res_query(host.get(), ns_c_in, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ response, DNSPacket::MAX_SIZE);
+#endif
+
+ if (len < 0) {
+ LOG("DNS query failed");
+ }
+ return len;
+ });
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return ParseHTTPSRecord(host, packet, aResult, aTTL);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/dns/PlatformDNSWin.cpp b/netwerk/dns/PlatformDNSWin.cpp
new file mode 100644
index 0000000000..8b5539d7de
--- /dev/null
+++ b/netwerk/dns/PlatformDNSWin.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GetAddrInfo.h"
+#include "mozilla/net/DNSPacket.h"
+#include "nsIDNSService.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+
+#ifdef DNSQUERY_AVAILABLE
+// There is a bug in windns.h where the type of parameter ppQueryResultsSet for
+// DnsQuery_A is dependent on UNICODE being set. It should *always* be
+// PDNS_RECORDA, but if UNICODE is set it is PDNS_RECORDW. To get around this
+// we make sure that UNICODE is unset.
+# undef UNICODE
+# include <ws2tcpip.h>
+# undef GetAddrInfo
+# include <windns.h>
+#endif // DNSQUERY_AVAILABLE
+
+namespace mozilla::net {
+
+#define LOG(msg, ...) \
+ MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
+
+nsresult ResolveHTTPSRecordImpl(const nsACString& aHost, uint16_t aFlags,
+ TypeRecordResultType& aResult, uint32_t& aTTL) {
+ nsAutoCString host(aHost);
+ PDNS_RECORD result = nullptr;
+ nsAutoCString cname;
+ aTTL = UINT32_MAX;
+
+ if (xpc::IsInAutomation() &&
+ !StaticPrefs::network_dns_native_https_query_in_automation()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ DNS_STATUS status =
+ DnsQuery_A(host.get(), nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ DNS_QUERY_STANDARD, nullptr, &result, nullptr);
+ if (status != ERROR_SUCCESS) {
+ LOG("DnsQuery_A failed with error: %ld\n", status);
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ // This will free the record if we exit early from this function.
+ auto freeDnsRecord =
+ MakeScopeExit([&]() { DnsRecordListFree(result, DnsFreeRecordList); });
+
+ auto CheckRecords = [&aResult, &cname, &aTTL](
+ PDNS_RECORD result,
+ const nsCString& aHost) -> nsresult {
+ PDNS_RECORD current = result;
+
+ for (current = result; current; current = current->pNext) {
+ if (strcmp(current->pName, aHost.get()) != 0) {
+ continue;
+ }
+ if (current->wType == nsIDNSService::RESOLVE_TYPE_HTTPSSVC) {
+ const unsigned char* ptr = (const unsigned char*)&(current->Data);
+ struct SVCB parsed;
+ nsresult rv = DNSPacket::ParseHTTPS(current->wDataLength, parsed, 0,
+ ptr, current->wDataLength, aHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (parsed.mSvcDomainName.IsEmpty() && parsed.mSvcFieldPriority == 0) {
+ // For AliasMode SVCB RRs, a TargetName of "." indicates that the
+ // service is not available or does not exist.
+ continue;
+ }
+
+ if (parsed.mSvcFieldPriority == 0) {
+ // Alias form SvcDomainName must not have the "." value (empty)
+ if (parsed.mSvcDomainName.IsEmpty()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ cname = parsed.mSvcDomainName;
+ ToLowerCase(cname);
+ break;
+ }
+
+ if (!aResult.is<TypeRecordHTTPSSVC>()) {
+ aResult = mozilla::AsVariant(CopyableTArray<SVCB>());
+ }
+ auto& results = aResult.as<TypeRecordHTTPSSVC>();
+ results.AppendElement(parsed);
+ aTTL = std::min<uint32_t>(aTTL, current->dwTtl);
+ } else if (current->wType == DNS_TYPE_CNAME) {
+ cname = current->Data.Cname.pNameHost;
+ ToLowerCase(cname);
+ aTTL = std::min<uint32_t>(aTTL, current->dwTtl);
+ break;
+ }
+ }
+ return NS_OK;
+ };
+
+ int32_t loopCount = 64;
+ while (loopCount > 0 && aResult.is<Nothing>()) {
+ loopCount--;
+
+ nsresult rv = CheckRecords(result, host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aResult.is<Nothing>() && !cname.IsEmpty()) {
+ host = cname;
+ cname.Truncate();
+ continue;
+ }
+
+ if (aResult.is<Nothing>()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+
+ // CNAME loop
+ if (loopCount == 0) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (aResult.is<Nothing>()) {
+ // The call succeeded, but no HTTPS records were found.
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (aTTL == UINT32_MAX) {
+ aTTL = 60; // Defaults to 60 seconds
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/dns/PublicSuffixList.sys.mjs b/netwerk/dns/PublicSuffixList.sys.mjs
new file mode 100644
index 0000000000..89cec38fea
--- /dev/null
+++ b/netwerk/dns/PublicSuffixList.sys.mjs
@@ -0,0 +1,104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { RemoteSettings } from "resource://services-settings/remote-settings.sys.mjs";
+
+const FileUtils = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+).FileUtils;
+
+const RECORD_ID = "tld-dafsa";
+const SIGNAL = "public-suffix-list-updated";
+
+export const PublicSuffixList = {
+ CLIENT: RemoteSettings("public-suffix-list"),
+
+ init() {
+ // Only initialize once.
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+
+ this.CLIENT.on("sync", this.onUpdate.bind(this));
+ /* We have a single record for this collection. Let's see if we already have it locally.
+ * Note that on startup, we don't need to synchronize immediately on new profiles.
+ */
+ this.CLIENT.get({ syncIfEmpty: false, filters: { id: RECORD_ID } })
+ .then(async records => {
+ if (records.length == 1) {
+ // Get the downloaded file URI (most likely to be a no-op here, since file will exist).
+ const fileURI = await this.CLIENT.attachments.downloadToDisk(
+ records[0]
+ );
+ // Send a signal so that the C++ code loads the updated list on startup.
+ this.notifyUpdate(fileURI);
+ }
+ })
+ .catch(err => console.error(err));
+ },
+
+ /**
+ * This method returns the path to the file based on the file uri received
+ * Example:
+ * On windows "file://C:/Users/AppData/main/public-suffix-list/dafsa.bin"
+ * will be converted to "C:\\Users\\main\\public-suffix-list\\dafsa.bin
+ *
+ * On macOS/linux "file:///home/main/public-suffix-list/dafsa.bin"
+ * will be converted to "/home/main/public-suffix-list/dafsa.bin"
+ */
+ getFilePath(fileURI) {
+ const uri = Services.io.newURI(fileURI);
+ const file = uri.QueryInterface(Ci.nsIFileURL).file;
+ return file.path;
+ },
+
+ notifyUpdate(fileURI) {
+ if (!Services.prefs.getBoolPref("network.psl.onUpdate_notify", false)) {
+ // Updating the PSL while Firefox is running could cause principals to
+ // have a different base domain before/after the update.
+ // See bug 1582647 comment 30
+ return;
+ }
+
+ const filePath = this.getFilePath(fileURI);
+ const nsifile = new FileUtils.File(filePath);
+ /* Send a signal to be read by the C++, the method
+ * ::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+ * at netwerk/dns/nsEffectiveTLDService.cpp
+ */
+ Services.obs.notifyObservers(
+ nsifile, // aSubject
+ SIGNAL, // aTopic
+ filePath // aData
+ );
+ },
+
+ async onUpdate({ data: { created, updated, deleted } }) {
+ // In theory, this will never happen, we will never delete the record.
+ if (deleted.length == 1) {
+ await this.CLIENT.attachments.deleteFromDisk(deleted[0]);
+ }
+ // Handle creation and update the same way
+ const changed = created.concat(updated.map(u => u.new));
+ /* In theory, we should never have more than one record. And if we receive
+ * this event, it's because the single record was updated.
+ */
+ if (changed.length != 1) {
+ console.warn("Unsupported sync event for Public Suffix List");
+ return;
+ }
+ // Download the updated file.
+ let fileURI;
+ try {
+ fileURI = await this.CLIENT.attachments.downloadToDisk(changed[0]);
+ } catch (err) {
+ console.error(err);
+ return;
+ }
+
+ // Notify the C++ part to reload it from disk.
+ this.notifyUpdate(fileURI);
+ },
+};
diff --git a/netwerk/dns/TRR.cpp b/netwerk/dns/TRR.cpp
new file mode 100644
index 0000000000..aad65ab809
--- /dev/null
+++ b/netwerk/dns/TRR.cpp
@@ -0,0 +1,1139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DNS.h"
+#include "DNSUtils.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsHttpHandler.h"
+#include "nsHttpChannel.h"
+#include "nsHostResolver.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIInputStream.h"
+#include "nsIObliviousHttp.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsITimedChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "ObliviousHttpChannel.h"
+#include "TRR.h"
+#include "TRRService.h"
+#include "TRRServiceChannel.h"
+#include "TRRLoadInfo.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/UniquePtr.h"
+// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
+#include "DNSLogging.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(TRR, nsIHttpPushListener, nsIInterfaceRequestor,
+ nsIStreamListener, nsIRunnable, nsITimerCallback)
+
+// when firing off a normal A or AAAA query
+TRR::TRR(AHostResolver* aResolver, nsHostRecord* aRec, enum TrrType aType)
+ : mozilla::Runnable("TRR"),
+ mRec(aRec),
+ mHostResolver(aResolver),
+ mType(aType),
+ mOriginSuffix(aRec->originSuffix) {
+ mHost = aRec->host;
+ mPB = aRec->pb;
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
+ "TRR must be in parent or socket process");
+}
+
+// when following CNAMEs
+TRR::TRR(AHostResolver* aResolver, nsHostRecord* aRec, nsCString& aHost,
+ enum TrrType& aType, unsigned int aLoopCount, bool aPB)
+ : mozilla::Runnable("TRR"),
+ mHost(aHost),
+ mRec(aRec),
+ mHostResolver(aResolver),
+ mType(aType),
+ mPB(aPB),
+ mCnameLoop(aLoopCount),
+ mOriginSuffix(aRec ? aRec->originSuffix : ""_ns) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
+ "TRR must be in parent or socket process");
+}
+
+// used on push
+TRR::TRR(AHostResolver* aResolver, bool aPB)
+ : mozilla::Runnable("TRR"), mHostResolver(aResolver), mPB(aPB) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
+ "TRR must be in parent or socket process");
+}
+
+// to verify a domain
+TRR::TRR(AHostResolver* aResolver, nsACString& aHost, enum TrrType aType,
+ const nsACString& aOriginSuffix, bool aPB, bool aUseFreshConnection)
+ : mozilla::Runnable("TRR"),
+ mHost(aHost),
+ mRec(nullptr),
+ mHostResolver(aResolver),
+ mType(aType),
+ mPB(aPB),
+ mOriginSuffix(aOriginSuffix),
+ mUseFreshConnection(aUseFreshConnection) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
+ "TRR must be in parent or socket process");
+}
+
+void TRR::HandleTimeout() {
+ mTimeout = nullptr;
+ RecordReason(TRRSkippedReason::TRR_TIMEOUT);
+ Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL);
+}
+
+NS_IMETHODIMP
+TRR::Notify(nsITimer* aTimer) {
+ if (aTimer == mTimeout) {
+ HandleTimeout();
+ } else {
+ MOZ_CRASH("Unknown timer");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::Run() {
+ MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(),
+ NS_IsMainThread() || TRRService::Get()->IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+
+ if ((TRRService::Get() == nullptr) || NS_FAILED(SendHTTPRequest())) {
+ RecordReason(TRRSkippedReason::TRR_SEND_FAILED);
+ FailData(NS_ERROR_FAILURE);
+ // The dtor will now be run
+ }
+ return NS_OK;
+}
+
+DNSPacket* TRR::GetOrCreateDNSPacket() {
+ if (!mPacket) {
+ mPacket = MakeUnique<DNSPacket>();
+ }
+
+ return mPacket.get();
+}
+
+nsresult TRR::CreateQueryURI(nsIURI** aOutURI) {
+ nsAutoCString uri;
+ nsCOMPtr<nsIURI> dnsURI;
+ if (UseDefaultServer()) {
+ TRRService::Get()->GetURI(uri);
+ } else {
+ uri = mRec->mTrrServer;
+ }
+
+ nsresult rv = NS_NewURI(getter_AddRefs(dnsURI), uri);
+ if (NS_FAILED(rv)) {
+ RecordReason(TRRSkippedReason::TRR_BAD_URL);
+ return rv;
+ }
+
+ dnsURI.forget(aOutURI);
+ return NS_OK;
+}
+
+bool TRR::MaybeBlockRequest() {
+ if (((mType == TRRTYPE_A) || (mType == TRRTYPE_AAAA)) &&
+ mRec->mEffectiveTRRMode != nsIRequest::TRR_ONLY_MODE) {
+ // let NS resolves skip the blocklist check
+ // we also don't check the blocklist for TRR only requests
+ MOZ_ASSERT(mRec);
+
+ // If TRRService isn't enabled anymore for the req, don't do TRR.
+ if (!TRRService::Get()->Enabled(mRec->mEffectiveTRRMode)) {
+ RecordReason(TRRSkippedReason::TRR_MODE_NOT_ENABLED);
+ return true;
+ }
+
+ if (!StaticPrefs::network_trr_strict_native_fallback() &&
+ UseDefaultServer() &&
+ TRRService::Get()->IsTemporarilyBlocked(mHost, mOriginSuffix, mPB,
+ true)) {
+ if (mType == TRRTYPE_A) {
+ // count only blocklist for A records to avoid double counts
+ Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED3,
+ TRRService::ProviderKey(), true);
+ }
+
+ RecordReason(TRRSkippedReason::TRR_HOST_BLOCKED_TEMPORARY);
+ // not really an error but no TRR is issued
+ return true;
+ }
+
+ if (TRRService::Get()->IsExcludedFromTRR(mHost)) {
+ RecordReason(TRRSkippedReason::TRR_EXCLUDED);
+ return true;
+ }
+
+ if (UseDefaultServer() && (mType == TRRTYPE_A)) {
+ Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED3,
+ TRRService::ProviderKey(), false);
+ }
+ }
+
+ return false;
+}
+
+nsresult TRR::SendHTTPRequest() {
+ // This is essentially the "run" method - created from nsHostResolver
+ if (mCancelled) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if ((mType != TRRTYPE_A) && (mType != TRRTYPE_AAAA) &&
+ (mType != TRRTYPE_NS) && (mType != TRRTYPE_TXT) &&
+ (mType != TRRTYPE_HTTPSSVC)) {
+ // limit the calling interface because nsHostResolver has explicit slots for
+ // these types
+ return NS_ERROR_FAILURE;
+ }
+
+ if (MaybeBlockRequest()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ LOG(("TRR::SendHTTPRequest resolve %s type %u\n", mHost.get(), mType));
+
+ nsAutoCString body;
+ bool disableECS = StaticPrefs::network_trr_disable_ECS();
+ nsresult rv =
+ GetOrCreateDNSPacket()->EncodeRequest(body, mHost, mType, disableECS);
+ if (NS_FAILED(rv)) {
+ HandleEncodeError(rv);
+ return rv;
+ }
+
+ bool useGet = StaticPrefs::network_trr_useGET();
+ nsCOMPtr<nsIURI> dnsURI;
+ rv = CreateQueryURI(getter_AddRefs(dnsURI));
+ if (NS_FAILED(rv)) {
+ LOG(("TRR:SendHTTPRequest: NewURI failed!\n"));
+ return rv;
+ }
+
+ if (useGet) {
+ /* For GET requests, the outgoing packet needs to be Base64url-encoded and
+ then appended to the end of the URI. */
+ nsAutoCString encoded;
+ rv = Base64URLEncode(body.Length(),
+ reinterpret_cast<const unsigned char*>(body.get()),
+ Base64URLEncodePaddingPolicy::Omit, encoded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString query;
+ rv = dnsURI->GetQuery(query);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (query.IsEmpty()) {
+ query.Assign("?dns="_ns);
+ } else {
+ query.Append("&dns="_ns);
+ }
+ query.Append(encoded);
+
+ rv = NS_MutateURI(dnsURI).SetQuery(query).Finalize(dnsURI);
+ LOG(("TRR::SendHTTPRequest GET dns=%s\n", body.get()));
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ bool useOHTTP = StaticPrefs::network_trr_use_ohttp();
+ if (useOHTTP) {
+ nsCOMPtr<nsIObliviousHttpService> ohttpService(
+ do_GetService("@mozilla.org/network/oblivious-http-service;1"));
+ if (!ohttpService) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIURI> relayURI;
+ nsTArray<uint8_t> encodedConfig;
+ rv = ohttpService->GetTRRSettings(getter_AddRefs(relayURI), encodedConfig);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!relayURI) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = ohttpService->NewChannel(relayURI, dnsURI, encodedConfig,
+ getter_AddRefs(channel));
+ } else {
+ rv = DNSUtils::CreateChannelHelper(dnsURI, getter_AddRefs(channel));
+ }
+ if (NS_FAILED(rv) || !channel) {
+ LOG(("TRR:SendHTTPRequest: NewChannel failed!\n"));
+ return rv;
+ }
+
+ auto loadFlags = nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIChannel::LOAD_BYPASS_URL_CLASSIFIER;
+ if (mUseFreshConnection) {
+ // Causes TRRServiceChannel to tell the connection manager
+ // to clear out any connection with the current conn info.
+ loadFlags |= nsIRequest::LOAD_FRESH_CONNECTION;
+ }
+ channel->SetLoadFlags(loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->SetNotificationCallbacks(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // This connection should not use TRR
+ rv = httpChannel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString contentType(ContentType());
+ rv = httpChannel->SetRequestHeader("Accept"_ns, contentType, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString cred;
+ if (UseDefaultServer()) {
+ TRRService::Get()->GetCredentials(cred);
+ }
+ if (!cred.IsEmpty()) {
+ rv = httpChannel->SetRequestHeader("Authorization"_ns, cred, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(channel);
+ if (!internalChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // setting a small stream window means the h2 stack won't pipeline a window
+ // update with each HEADERS or reply to a DATA with a WINDOW UPDATE
+ rv = internalChannel->SetInitialRwin(127 * 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = internalChannel->SetIsTRRServiceChannel(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (UseDefaultServer() && StaticPrefs::network_trr_async_connInfo()) {
+ RefPtr<nsHttpConnectionInfo> trrConnInfo =
+ TRRService::Get()->TRRConnectionInfo();
+ if (trrConnInfo) {
+ nsAutoCString host;
+ dnsURI->GetHost(host);
+ if (host.Equals(trrConnInfo->GetOrigin())) {
+ internalChannel->SetConnectionInfo(trrConnInfo);
+ LOG(("TRR::SendHTTPRequest use conn info:%s\n",
+ trrConnInfo->HashKey().get()));
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(false);
+ }
+ } else {
+ TRRService::Get()->InitTRRConnectionInfo();
+ }
+ }
+
+ if (useGet) {
+ rv = httpChannel->SetRequestMethod("GET"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
+ if (!uploadChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ uint32_t streamLength = body.Length();
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv =
+ NS_NewCStringInputStream(getter_AddRefs(uploadStream), std::move(body));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uploadChannel->ExplicitSetUploadStream(uploadStream, contentType,
+ streamLength, "POST"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = SetupTRRServiceChannelInternal(httpChannel, useGet, contentType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = httpChannel->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the asyncOpen succeeded we can say that we actually attempted to
+ // use the TRR connection.
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(mRec);
+ if (addrRec) {
+ addrRec->mResolverType = ResolverType();
+ }
+
+ NS_NewTimerWithCallback(
+ getter_AddRefs(mTimeout), this,
+ mTimeoutMs ? mTimeoutMs : TRRService::Get()->GetRequestTimeout(),
+ nsITimer::TYPE_ONE_SHOT);
+
+ mChannel = channel;
+ return NS_OK;
+}
+
+// static
+nsresult TRR::SetupTRRServiceChannelInternal(nsIHttpChannel* aChannel,
+ bool aUseGet,
+ const nsACString& aContentType) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = aChannel;
+ MOZ_ASSERT(httpChannel);
+
+ nsresult rv = NS_OK;
+ if (!aUseGet) {
+ rv =
+ httpChannel->SetRequestHeader("Cache-Control"_ns, "no-store"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Sanitize the request by removing the Accept-Language header so we minimize
+ // the amount of fingerprintable information we send to the server.
+ if (!StaticPrefs::network_trr_send_accept_language_headers()) {
+ rv = httpChannel->SetRequestHeader("Accept-Language"_ns, ""_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Sanitize the request by removing the User-Agent
+ if (!StaticPrefs::network_trr_send_user_agent_headers()) {
+ rv = httpChannel->SetRequestHeader("User-Agent"_ns, ""_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (StaticPrefs::network_trr_send_empty_accept_encoding_headers()) {
+ rv = httpChannel->SetEmptyRequestHeader("Accept-Encoding"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // set the *default* response content type
+ if (NS_FAILED(httpChannel->SetContentType(aContentType))) {
+ LOG(("TRR::SetupTRRServiceChannelInternal: couldn't set content-type!\n"));
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChan(do_QueryInterface(httpChannel));
+ if (timedChan) {
+ timedChan->SetTimingEnabled(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::GetInterface(const nsIID& iid, void** result) {
+ if (!iid.Equals(NS_GET_IID(nsIHttpPushListener))) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIHttpPushListener> copy(this);
+ *result = copy.forget().take();
+ return NS_OK;
+}
+
+nsresult TRR::DohDecodeQuery(const nsCString& query, nsCString& host,
+ enum TrrType& type) {
+ FallibleTArray<uint8_t> binary;
+ bool found_dns = false;
+ LOG(("TRR::DohDecodeQuery %s!\n", query.get()));
+
+ // extract "dns=" from the query string
+ nsAutoCString data;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(query, '&').ToRange()) {
+ nsDependentCSubstring dns = Substring(token, 0, 4);
+ nsAutoCString check(dns);
+ if (check.Equals("dns=")) {
+ nsDependentCSubstring q = Substring(token, 4, -1);
+ data = q;
+ found_dns = true;
+ break;
+ }
+ }
+ if (!found_dns) {
+ LOG(("TRR::DohDecodeQuery no dns= in pushed URI query string\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv =
+ Base64URLDecode(data, Base64URLDecodePaddingPolicy::Ignore, binary);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t avail = binary.Length();
+ if (avail < 12) {
+ return NS_ERROR_FAILURE;
+ }
+ // check the query bit and the opcode
+ if ((binary[2] & 0xf8) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t qdcount = (binary[4] << 8) + binary[5];
+ if (!qdcount) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t index = 12;
+ uint32_t length = 0;
+ host.Truncate();
+ do {
+ if (avail < (index + 1)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ length = binary[index];
+ if (length) {
+ if (host.Length()) {
+ host.Append(".");
+ }
+ if (avail < (index + 1 + length)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ host.Append((const char*)(&binary[0]) + index + 1, length);
+ }
+ index += 1 + length; // skip length byte + label
+ } while (length);
+
+ LOG(("TRR::DohDecodeQuery host %s\n", host.get()));
+
+ if (avail < (index + 2)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ uint16_t i16 = 0;
+ i16 += binary[index] << 8;
+ i16 += binary[index + 1];
+ type = (enum TrrType)i16;
+
+ LOG(("TRR::DohDecodeQuery type %d\n", (int)type));
+
+ return NS_OK;
+}
+
+nsresult TRR::ReceivePush(nsIHttpChannel* pushed, nsHostRecord* pushedRec) {
+ if (!mHostResolver) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ LOG(("TRR::ReceivePush: PUSH incoming!\n"));
+
+ nsCOMPtr<nsIURI> uri;
+ pushed->GetURI(getter_AddRefs(uri));
+ nsAutoCString query;
+ if (uri) {
+ uri->GetQuery(query);
+ }
+
+ if (NS_FAILED(DohDecodeQuery(query, mHost, mType)) ||
+ HostIsIPLiteral(mHost)) { // literal
+ LOG(("TRR::ReceivePush failed to decode %s\n", mHost.get()));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if ((mType != TRRTYPE_A) && (mType != TRRTYPE_AAAA) &&
+ (mType != TRRTYPE_TXT) && (mType != TRRTYPE_HTTPSSVC)) {
+ LOG(("TRR::ReceivePush unknown type %d\n", mType));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (TRRService::Get()->IsExcludedFromTRR(mHost)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t type = nsIDNSService::RESOLVE_TYPE_DEFAULT;
+ if (mType == TRRTYPE_TXT) {
+ type = nsIDNSService::RESOLVE_TYPE_TXT;
+ } else if (mType == TRRTYPE_HTTPSSVC) {
+ type = nsIDNSService::RESOLVE_TYPE_HTTPSSVC;
+ }
+
+ RefPtr<nsHostRecord> hostRecord;
+ nsresult rv;
+ rv = mHostResolver->GetHostRecord(
+ mHost, ""_ns, type, pushedRec->flags, pushedRec->af, pushedRec->pb,
+ pushedRec->originSuffix, getter_AddRefs(hostRecord));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Since we don't ever call nsHostResolver::NameLookup for this record,
+ // we need to copy the trr mode from the previous record
+ if (hostRecord->mEffectiveTRRMode == nsIRequest::TRR_DEFAULT_MODE) {
+ hostRecord->mEffectiveTRRMode =
+ static_cast<nsIRequest::TRRMode>(pushedRec->mEffectiveTRRMode);
+ }
+
+ rv = mHostResolver->TrrLookup_unlocked(hostRecord, this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = pushed->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // OK!
+ mChannel = pushed;
+ mRec.swap(hostRecord);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::OnPush(nsIHttpChannel* associated, nsIHttpChannel* pushed) {
+ LOG(("TRR::OnPush entry\n"));
+ MOZ_ASSERT(associated == mChannel);
+ if (!mRec) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!UseDefaultServer()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<TRR> trr = new TRR(mHostResolver, mPB);
+ return trr->ReceivePush(pushed, mRec);
+}
+
+NS_IMETHODIMP
+TRR::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("TRR::OnStartRequest %p %s %d\n", this, mHost.get(), mType));
+
+ nsresult status = NS_OK;
+ aRequest->GetStatus(&status);
+
+ if (NS_FAILED(status)) {
+ if (NS_IsOffline()) {
+ RecordReason(TRRSkippedReason::TRR_IS_OFFLINE);
+ }
+
+ switch (status) {
+ case NS_ERROR_UNKNOWN_HOST:
+ RecordReason(TRRSkippedReason::TRR_CHANNEL_DNS_FAIL);
+ break;
+ case NS_ERROR_OFFLINE:
+ RecordReason(TRRSkippedReason::TRR_IS_OFFLINE);
+ break;
+ case NS_ERROR_NET_RESET:
+ RecordReason(TRRSkippedReason::TRR_NET_RESET);
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ case NS_ERROR_NET_TIMEOUT_EXTERNAL:
+ RecordReason(TRRSkippedReason::TRR_NET_TIMEOUT);
+ break;
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ RecordReason(TRRSkippedReason::TRR_NET_REFUSED);
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ RecordReason(TRRSkippedReason::TRR_NET_INTERRUPT);
+ break;
+ case NS_ERROR_NET_INADEQUATE_SECURITY:
+ RecordReason(TRRSkippedReason::TRR_NET_INADEQ_SEQURITY);
+ break;
+ default:
+ RecordReason(TRRSkippedReason::TRR_UNKNOWN_CHANNEL_FAILURE);
+ }
+ }
+
+ return NS_OK;
+}
+
+void TRR::SaveAdditionalRecords(
+ const nsClassHashtable<nsCStringHashKey, DOHresp>& aRecords) {
+ if (!mRec) {
+ return;
+ }
+ nsresult rv;
+ for (const auto& recordEntry : aRecords) {
+ if (!recordEntry.GetData() || recordEntry.GetData()->mAddresses.IsEmpty()) {
+ // no point in adding empty records.
+ continue;
+ }
+ // If IPv6 is disabled don't add anything else than IPv4.
+ if (StaticPrefs::network_dns_disableIPv6() &&
+ std::find_if(recordEntry.GetData()->mAddresses.begin(),
+ recordEntry.GetData()->mAddresses.end(),
+ [](const NetAddr& addr) { return !addr.IsIPAddrV4(); }) !=
+ recordEntry.GetData()->mAddresses.end()) {
+ continue;
+ }
+ RefPtr<nsHostRecord> hostRecord;
+ rv = mHostResolver->GetHostRecord(
+ recordEntry.GetKey(), EmptyCString(),
+ nsIDNSService::RESOLVE_TYPE_DEFAULT, mRec->flags, AF_UNSPEC, mRec->pb,
+ mRec->originSuffix, getter_AddRefs(hostRecord));
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to get host record for additional record %s",
+ nsCString(recordEntry.GetKey()).get()));
+ continue;
+ }
+ RefPtr<AddrInfo> ai(
+ new AddrInfo(recordEntry.GetKey(), ResolverType(), TRRTYPE_A,
+ std::move(recordEntry.GetData()->mAddresses),
+ recordEntry.GetData()->mTtl));
+ mHostResolver->MaybeRenewHostRecord(hostRecord);
+
+ // Since we're not actually calling NameLookup for this record, we need
+ // to set these fields to avoid assertions in CompleteLookup.
+ // This is quite hacky, and should be fixed.
+ hostRecord->Reset();
+ hostRecord->mResolving++;
+ hostRecord->mEffectiveTRRMode =
+ static_cast<nsIRequest::TRRMode>(mRec->mEffectiveTRRMode);
+ LOG(("Completing lookup for additional: %s",
+ nsCString(recordEntry.GetKey()).get()));
+ (void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB,
+ mOriginSuffix, TRRSkippedReason::TRR_OK,
+ this);
+ }
+}
+
+void TRR::StoreIPHintAsDNSRecord(const struct SVCB& aSVCBRecord) {
+ LOG(("TRR::StoreIPHintAsDNSRecord [%p] [%s]", this,
+ aSVCBRecord.mSvcDomainName.get()));
+ CopyableTArray<NetAddr> addresses;
+ aSVCBRecord.GetIPHints(addresses);
+
+ if (StaticPrefs::network_dns_disableIPv6()) {
+ addresses.RemoveElementsBy(
+ [](const NetAddr& addr) { return !addr.IsIPAddrV4(); });
+ }
+
+ if (addresses.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsHostRecord> hostRecord;
+ nsresult rv = mHostResolver->GetHostRecord(
+ aSVCBRecord.mSvcDomainName, EmptyCString(),
+ nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ mRec->flags | nsIDNSService::RESOLVE_IP_HINT, AF_UNSPEC, mRec->pb,
+ mRec->originSuffix, getter_AddRefs(hostRecord));
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to get host record"));
+ return;
+ }
+
+ mHostResolver->MaybeRenewHostRecord(hostRecord);
+
+ RefPtr<AddrInfo> ai(new AddrInfo(aSVCBRecord.mSvcDomainName, ResolverType(),
+ TRRTYPE_A, std::move(addresses), mTTL));
+
+ // Since we're not actually calling NameLookup for this record, we need
+ // to set these fields to avoid assertions in CompleteLookup.
+ // This is quite hacky, and should be fixed.
+ hostRecord->mResolving++;
+ hostRecord->mEffectiveTRRMode =
+ static_cast<nsIRequest::TRRMode>(mRec->mEffectiveTRRMode);
+ (void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB, mOriginSuffix,
+ TRRSkippedReason::TRR_OK, this);
+}
+
+nsresult TRR::ReturnData(nsIChannel* aChannel) {
+ if (mType != TRRTYPE_TXT && mType != TRRTYPE_HTTPSSVC) {
+ // create and populate an AddrInfo instance to pass on
+ RefPtr<AddrInfo> ai(new AddrInfo(mHost, ResolverType(), mType,
+ nsTArray<NetAddr>(), mDNS.mTtl));
+ auto builder = ai->Build();
+ builder.SetAddresses(std::move(mDNS.mAddresses));
+ builder.SetCanonicalHostname(mCname);
+
+ // Set timings.
+ nsCOMPtr<nsITimedChannel> timedChan = do_QueryInterface(aChannel);
+ if (timedChan) {
+ TimeStamp asyncOpen, start, end;
+ if (NS_SUCCEEDED(timedChan->GetAsyncOpen(&asyncOpen)) &&
+ !asyncOpen.IsNull()) {
+ builder.SetTrrFetchDuration(
+ (TimeStamp::Now() - asyncOpen).ToMilliseconds());
+ }
+ if (NS_SUCCEEDED(timedChan->GetRequestStart(&start)) &&
+ NS_SUCCEEDED(timedChan->GetResponseEnd(&end)) && !start.IsNull() &&
+ !end.IsNull()) {
+ builder.SetTrrFetchDurationNetworkOnly((end - start).ToMilliseconds());
+ }
+ }
+ ai = builder.Finish();
+
+ if (!mHostResolver) {
+ return NS_ERROR_FAILURE;
+ }
+ RecordReason(TRRSkippedReason::TRR_OK);
+ (void)mHostResolver->CompleteLookup(mRec, NS_OK, ai, mPB, mOriginSuffix,
+ mTRRSkippedReason, this);
+ mHostResolver = nullptr;
+ mRec = nullptr;
+ } else {
+ RecordReason(TRRSkippedReason::TRR_OK);
+ (void)mHostResolver->CompleteLookupByType(mRec, NS_OK, mResult,
+ mTRRSkippedReason, mTTL, mPB);
+ }
+ return NS_OK;
+}
+
+nsresult TRR::FailData(nsresult error) {
+ if (!mHostResolver) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we didn't record a reason until now, record a default one.
+ RecordReason(TRRSkippedReason::TRR_FAILED);
+
+ if (mType == TRRTYPE_TXT || mType == TRRTYPE_HTTPSSVC) {
+ TypeRecordResultType empty(Nothing{});
+ (void)mHostResolver->CompleteLookupByType(mRec, error, empty,
+ mTRRSkippedReason, 0, mPB);
+ } else {
+ // create and populate an TRR AddrInfo instance to pass on to signal that
+ // this comes from TRR
+ nsTArray<NetAddr> noAddresses;
+ RefPtr<AddrInfo> ai =
+ new AddrInfo(mHost, ResolverType(), mType, std::move(noAddresses));
+
+ (void)mHostResolver->CompleteLookup(mRec, error, ai, mPB, mOriginSuffix,
+ mTRRSkippedReason, this);
+ }
+
+ mHostResolver = nullptr;
+ mRec = nullptr;
+ return NS_OK;
+}
+
+void TRR::HandleDecodeError(nsresult aStatusCode) {
+ auto rcode = mPacket->GetRCode();
+ if (rcode.isOk() && rcode.unwrap() != 0) {
+ if (rcode.unwrap() == 0x03) {
+ RecordReason(TRRSkippedReason::TRR_NXDOMAIN);
+ } else {
+ RecordReason(TRRSkippedReason::TRR_RCODE_FAIL);
+ }
+ } else if (aStatusCode == NS_ERROR_UNKNOWN_HOST ||
+ aStatusCode == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
+ RecordReason(TRRSkippedReason::TRR_NO_ANSWERS);
+ } else {
+ RecordReason(TRRSkippedReason::TRR_DECODE_FAILED);
+ }
+}
+
+bool TRR::HasUsableResponse() {
+ if (mType == TRRTYPE_A || mType == TRRTYPE_AAAA) {
+ return !mDNS.mAddresses.IsEmpty();
+ }
+ if (mType == TRRTYPE_TXT) {
+ return mResult.is<TypeRecordTxt>();
+ }
+ if (mType == TRRTYPE_HTTPSSVC) {
+ return mResult.is<TypeRecordHTTPSSVC>();
+ }
+ return false;
+}
+
+nsresult TRR::FollowCname(nsIChannel* aChannel) {
+ nsresult rv = NS_OK;
+ nsAutoCString cname;
+ while (NS_SUCCEEDED(rv) && mDNS.mAddresses.IsEmpty() && !mCname.IsEmpty() &&
+ mCnameLoop > 0) {
+ mCnameLoop--;
+ LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(),
+ mCnameLoop));
+ cname = mCname;
+ mCname.Truncate();
+
+ LOG(("TRR: check for CNAME record for %s within previous response\n",
+ cname.get()));
+ nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
+ rv = GetOrCreateDNSPacket()->Decode(
+ cname, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(), mDNS,
+ mResult, additionalRecords, mTTL);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::FollowCname DohDecode %x\n", (int)rv));
+ HandleDecodeError(rv);
+ }
+ }
+
+ // restore mCname as DohDecode() change it
+ mCname = cname;
+ if (NS_SUCCEEDED(rv) && HasUsableResponse()) {
+ ReturnData(aChannel);
+ return NS_OK;
+ }
+
+ bool ra = mPacket && mPacket->RecursionAvailable().unwrapOr(false);
+ LOG(("ra = %d", ra));
+ if (rv == NS_ERROR_UNKNOWN_HOST && ra) {
+ // If recursion is available, but no addresses have been returned,
+ // we can just return a failure here.
+ LOG(("TRR::FollowCname not sending another request as RA flag is set."));
+ FailData(NS_ERROR_UNKNOWN_HOST);
+ return NS_OK;
+ }
+
+ if (!mCnameLoop) {
+ LOG(("TRR::On200Response CNAME loop, eject!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(),
+ mCnameLoop));
+ RefPtr<TRR> trr =
+ new TRR(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB);
+ if (!TRRService::Get()) {
+ return NS_ERROR_FAILURE;
+ }
+ return TRRService::Get()->DispatchTRRRequest(trr);
+}
+
+nsresult TRR::On200Response(nsIChannel* aChannel) {
+ // decode body and create an AddrInfo struct for the response
+ nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
+ RefPtr<TypeHostRecord> typeRec = do_QueryObject(mRec);
+ if (typeRec && typeRec->mOriginHost) {
+ GetOrCreateDNSPacket()->SetOriginHost(typeRec->mOriginHost);
+ }
+ nsresult rv = GetOrCreateDNSPacket()->Decode(
+ mHost, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(), mDNS,
+ mResult, additionalRecords, mTTL);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
+ HandleDecodeError(rv);
+ return rv;
+ }
+ if (StaticPrefs::network_trr_add_additional_records()) {
+ SaveAdditionalRecords(additionalRecords);
+ }
+
+ if (mResult.is<TypeRecordHTTPSSVC>()) {
+ auto& results = mResult.as<TypeRecordHTTPSSVC>();
+ for (const auto& rec : results) {
+ StoreIPHintAsDNSRecord(rec);
+ }
+ }
+
+ if (!mDNS.mAddresses.IsEmpty() || mType == TRRTYPE_TXT || mCname.IsEmpty()) {
+ // pass back the response data
+ ReturnData(aChannel);
+ return NS_OK;
+ }
+
+ LOG(("TRR::On200Response trying CNAME %s", mCname.get()));
+ return FollowCname(aChannel);
+}
+
+void TRR::RecordProcessingTime(nsIChannel* aChannel) {
+ // This method records the time it took from the last received byte of the
+ // DoH response until we've notified the consumer with a host record.
+ nsCOMPtr<nsITimedChannel> timedChan = do_QueryInterface(aChannel);
+ if (!timedChan) {
+ return;
+ }
+ TimeStamp end;
+ if (NS_FAILED(timedChan->GetResponseEnd(&end))) {
+ return;
+ }
+
+ if (end.IsNull()) {
+ return;
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::DNS_TRR_PROCESSING_TIME, end);
+
+ LOG(("Processing DoH response took %f ms",
+ (TimeStamp::Now() - end).ToMilliseconds()));
+}
+
+void TRR::ReportStatus(nsresult aStatusCode) {
+ // If the TRR was cancelled by nsHostResolver, then we don't need to report
+ // it as failed; otherwise it can cause the confirmation to fail.
+ if (UseDefaultServer() && aStatusCode != NS_ERROR_ABORT) {
+ // Bad content is still considered "okay" if the HTTP response is okay
+ TRRService::Get()->RecordTRRStatus(this);
+ }
+}
+
+static void RecordHttpVersion(nsIHttpChannel* aHttpChannel) {
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(aHttpChannel);
+ if (!internalChannel) {
+ LOG(("RecordHttpVersion: Failed to QI nsIHttpChannelInternal"));
+ return;
+ }
+
+ uint32_t major, minor;
+ if (NS_FAILED(internalChannel->GetResponseVersion(&major, &minor))) {
+ LOG(("RecordHttpVersion: Failed to get protocol version"));
+ return;
+ }
+
+ auto label = Telemetry::LABELS_DNS_TRR_HTTP_VERSION2::h_1;
+ if (major == 2) {
+ label = Telemetry::LABELS_DNS_TRR_HTTP_VERSION2::h_2;
+ } else if (major == 3) {
+ label = Telemetry::LABELS_DNS_TRR_HTTP_VERSION2::h_3;
+ }
+
+ Telemetry::AccumulateCategoricalKeyed(TRRService::ProviderKey(), label);
+
+ LOG(("RecordHttpVersion: Provider responded using HTTP version: %d", major));
+}
+
+NS_IMETHODIMP
+TRR::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // The dtor will be run after the function returns
+ LOG(("TRR:OnStopRequest %p %s %d failed=%d code=%X\n", this, mHost.get(),
+ mType, mFailed, (unsigned int)aStatusCode));
+ nsCOMPtr<nsIChannel> channel;
+ channel.swap(mChannel);
+
+ mChannelStatus = aStatusCode;
+ if (NS_SUCCEEDED(aStatusCode)) {
+ nsCString label = "regular"_ns;
+ if (mPB) {
+ label = "private"_ns;
+ }
+ mozilla::glean::networking::trr_request_count.Get(label).Add(1);
+ }
+
+ {
+ // Cancel the timer since we don't need it anymore.
+ nsCOMPtr<nsITimer> timer;
+ mTimeout.swap(timer);
+ if (timer) {
+ timer->Cancel();
+ }
+ }
+
+ auto scopeExit = MakeScopeExit([&] { ReportStatus(aStatusCode); });
+
+ nsresult rv = NS_OK;
+ // if status was "fine", parse the response and pass on the answer
+ if (!mFailed && NS_SUCCEEDED(aStatusCode)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsAutoCString contentType;
+ httpChannel->GetContentType(contentType);
+ if (contentType.Length() &&
+ !contentType.LowerCaseEqualsASCII(ContentType())) {
+ LOG(("TRR:OnStopRequest %p %s %d wrong content type %s\n", this,
+ mHost.get(), mType, contentType.get()));
+ FailData(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (NS_SUCCEEDED(rv) && httpStatus == 200) {
+ rv = On200Response(channel);
+ if (NS_SUCCEEDED(rv) && UseDefaultServer()) {
+ RecordReason(TRRSkippedReason::TRR_OK);
+ RecordProcessingTime(channel);
+ RecordHttpVersion(httpChannel);
+ return rv;
+ }
+ } else {
+ RecordReason(TRRSkippedReason::TRR_SERVER_RESPONSE_ERR);
+ LOG(("TRR:OnStopRequest:%d %p rv %x httpStatus %d\n", __LINE__, this,
+ (int)rv, httpStatus));
+ }
+ }
+
+ LOG(("TRR:OnStopRequest %p status %x mFailed %d\n", this, (int)aStatusCode,
+ mFailed));
+ FailData(NS_SUCCEEDED(rv) ? NS_ERROR_UNKNOWN_HOST : rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, const uint32_t aCount) {
+ LOG(("TRR:OnDataAvailable %p %s %d failed=%d aCount=%u\n", this, mHost.get(),
+ mType, mFailed, (unsigned int)aCount));
+ // receive DNS response into the local buffer
+ if (mFailed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = GetOrCreateDNSPacket()->OnDataAvailable(aRequest, aInputStream,
+ aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::OnDataAvailable:%d fail\n", __LINE__));
+ mFailed = true;
+ return rv;
+ }
+ return NS_OK;
+}
+
+void TRR::Cancel(nsresult aStatus) {
+ bool isTRRServiceChannel = false;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
+ do_QueryInterface(mChannel));
+ if (httpChannelInternal) {
+ nsresult rv =
+ httpChannelInternal->GetIsTRRServiceChannel(&isTRRServiceChannel);
+ if (NS_FAILED(rv)) {
+ isTRRServiceChannel = false;
+ }
+ }
+ // nsHttpChannel can be only canceled on the main thread.
+ RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
+ if (isTRRServiceChannel && !XRE_IsSocketProcess() && !httpChannel) {
+ if (TRRService::Get()) {
+ nsCOMPtr<nsIThread> thread = TRRService::Get()->TRRThread();
+ if (thread && !thread->IsOnCurrentThread()) {
+ thread->Dispatch(NS_NewRunnableFunction(
+ "TRR::Cancel",
+ [self = RefPtr(this), aStatus]() { self->Cancel(aStatus); }));
+ return;
+ }
+ }
+ } else {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "TRR::Cancel",
+ [self = RefPtr(this), aStatus]() { self->Cancel(aStatus); }));
+ return;
+ }
+ }
+
+ if (mCancelled) {
+ return;
+ }
+ mCancelled = true;
+
+ if (mChannel) {
+ RecordReason(TRRSkippedReason::TRR_REQ_CANCELLED);
+ LOG(("TRR: %p canceling Channel %p %s %d status=%" PRIx32 "\n", this,
+ mChannel.get(), mHost.get(), mType, static_cast<uint32_t>(aStatus)));
+ mChannel->Cancel(aStatus);
+ }
+}
+
+bool TRR::UseDefaultServer() { return !mRec || mRec->mTrrServer.IsEmpty(); }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRR.h b/netwerk/dns/TRR.h
new file mode 100644
index 0000000000..82244c97a0
--- /dev/null
+++ b/netwerk/dns/TRR.h
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 2; 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_TRR_h
+#define mozilla_net_TRR_h
+
+#include "mozilla/net/DNSByTypeRecord.h"
+#include "mozilla/Assertions.h"
+#include "nsClassHashtable.h"
+#include "nsIChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "DNSPacket.h"
+#include "nsITRRSkipReason.h"
+
+class AHostResolver;
+class nsHostRecord;
+
+namespace mozilla {
+namespace net {
+
+class TRRService;
+class TRRServiceChannel;
+
+class TRR : public Runnable,
+ public nsITimerCallback,
+ public nsIHttpPushListener,
+ public nsIInterfaceRequestor,
+ public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIHTTPPUSHLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITIMERCALLBACK
+
+ // Number of "steps" we follow CNAME chains
+ static const unsigned int kCnameChaseMax = 64;
+
+ // when firing off a normal A or AAAA query
+ explicit TRR(AHostResolver* aResolver, nsHostRecord* aRec,
+ enum TrrType aType);
+ // when following CNAMEs
+ explicit TRR(AHostResolver* aResolver, nsHostRecord* aRec, nsCString& aHost,
+ enum TrrType& aType, unsigned int aLoopCount, bool aPB);
+ // used on push
+ explicit TRR(AHostResolver* aResolver, bool aPB);
+ // to verify a domain
+ explicit TRR(AHostResolver* aResolver, nsACString& aHost, enum TrrType aType,
+ const nsACString& aOriginSuffix, bool aPB,
+ bool aUseFreshConnection);
+
+ NS_IMETHOD Run() override;
+ void Cancel(nsresult aStatus);
+ enum TrrType Type() { return mType; }
+ nsCString mHost;
+ RefPtr<nsHostRecord> mRec;
+ RefPtr<AHostResolver> mHostResolver;
+
+ void SetTimeout(uint32_t aTimeoutMs) { mTimeoutMs = aTimeoutMs; }
+
+ nsresult ChannelStatus() { return mChannelStatus; }
+
+ enum RequestPurpose {
+ Resolve,
+ Confirmation,
+ Blocklist,
+ };
+
+ RequestPurpose Purpose() { return mPurpose; }
+ void SetPurpose(RequestPurpose aPurpose) { mPurpose = aPurpose; }
+ TRRSkippedReason SkipReason() const { return mTRRSkippedReason; }
+
+ protected:
+ virtual ~TRR() = default;
+ virtual DNSPacket* GetOrCreateDNSPacket();
+ virtual nsresult CreateQueryURI(nsIURI** aOutURI);
+ virtual const char* ContentType() const { return "application/dns-message"; }
+ virtual DNSResolverType ResolverType() const { return DNSResolverType::TRR; }
+ virtual bool MaybeBlockRequest();
+ virtual void RecordProcessingTime(nsIChannel* aChannel);
+ virtual void ReportStatus(nsresult aStatusCode);
+ virtual void HandleTimeout();
+ virtual void HandleEncodeError(nsresult aStatusCode) {}
+ virtual void HandleDecodeError(nsresult aStatusCode);
+ nsresult SendHTTPRequest();
+ nsresult ReturnData(nsIChannel* aChannel);
+
+ // FailData() must be called to signal that the asynch TRR resolve is
+ // completed. For failed name resolves ("no such host"), the 'error' it
+ // passses on in its argument must be NS_ERROR_UNKNOWN_HOST. Other errors
+ // (if host was blocklisted, there as a bad content-type received, etc)
+ // other error codes must be used. This distinction is important for the
+ // subsequent logic to separate the error reasons.
+ nsresult FailData(nsresult error);
+ static nsresult DohDecodeQuery(const nsCString& query, nsCString& host,
+ enum TrrType& type);
+ nsresult ReceivePush(nsIHttpChannel* pushed, nsHostRecord* pushedRec);
+ nsresult On200Response(nsIChannel* aChannel);
+ nsresult FollowCname(nsIChannel* aChannel);
+
+ bool HasUsableResponse();
+
+ bool UseDefaultServer();
+ void SaveAdditionalRecords(
+ const nsClassHashtable<nsCStringHashKey, DOHresp>& aRecords);
+
+ friend class TRRServiceChannel;
+ static nsresult SetupTRRServiceChannelInternal(
+ nsIHttpChannel* aChannel, bool aUseGet, const nsACString& aContentType);
+
+ void StoreIPHintAsDNSRecord(const struct SVCB& aSVCBRecord);
+
+ nsCOMPtr<nsIChannel> mChannel;
+ enum TrrType mType { TRRTYPE_A };
+ UniquePtr<DNSPacket> mPacket;
+ bool mFailed = false;
+ bool mPB = false;
+ DOHresp mDNS;
+ nsresult mChannelStatus = NS_OK;
+
+ RequestPurpose mPurpose = Resolve;
+ Atomic<bool, Relaxed> mCancelled{false};
+
+ // The request timeout in milliseconds. If 0 we will use the default timeout
+ // we get from the prefs.
+ uint32_t mTimeoutMs = 0;
+ nsCOMPtr<nsITimer> mTimeout;
+ nsCString mCname;
+ uint32_t mCnameLoop = kCnameChaseMax; // loop detection counter
+
+ uint32_t mTTL = UINT32_MAX;
+ TypeRecordResultType mResult = mozilla::AsVariant(Nothing());
+
+ TRRSkippedReason mTRRSkippedReason = TRRSkippedReason::TRR_UNSET;
+ void RecordReason(TRRSkippedReason reason) {
+ if (mTRRSkippedReason == TRRSkippedReason::TRR_UNSET) {
+ mTRRSkippedReason = reason;
+ }
+ }
+
+ // keep a copy of the originSuffix for the cases where mRec == nullptr */
+ const nsCString mOriginSuffix;
+
+ // If true, we set LOAD_FRESH_CONNECTION on our channel's load flags.
+ bool mUseFreshConnection = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // include guard
diff --git a/netwerk/dns/TRRQuery.cpp b/netwerk/dns/TRRQuery.cpp
new file mode 100644
index 0000000000..7602f586a7
--- /dev/null
+++ b/netwerk/dns/TRRQuery.cpp
@@ -0,0 +1,387 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TRRQuery.h"
+
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsQueryObject.h"
+#include "TRR.h"
+#include "TRRService.h"
+// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
+#include "DNSLogging.h"
+
+namespace mozilla {
+namespace net {
+
+static already_AddRefed<AddrInfo> merge_rrset(AddrInfo* rrto,
+ AddrInfo* rrfrom) {
+ MOZ_ASSERT(rrto && rrfrom);
+ // Each of the arguments are all-IPv4 or all-IPv6 hence judging
+ // by the first element. This is true only for TRR resolutions.
+ bool isIPv6 = rrfrom->Addresses().Length() > 0 &&
+ rrfrom->Addresses()[0].raw.family == PR_AF_INET6;
+
+ nsTArray<NetAddr> addresses;
+ if (isIPv6) {
+ addresses = rrfrom->Addresses().Clone();
+ addresses.AppendElements(rrto->Addresses());
+ } else {
+ addresses = rrto->Addresses().Clone();
+ addresses.AppendElements(rrfrom->Addresses());
+ }
+ auto builder = rrto->Build();
+ builder.SetAddresses(std::move(addresses));
+ return builder.Finish();
+}
+
+void TRRQuery::Cancel(nsresult aStatus) {
+ MutexAutoLock trrlock(mTrrLock);
+ if (mTrrA) {
+ mTrrA->Cancel(aStatus);
+ }
+ if (mTrrAAAA) {
+ mTrrAAAA->Cancel(aStatus);
+ }
+ if (mTrrByType) {
+ mTrrByType->Cancel(aStatus);
+ }
+}
+
+void TRRQuery::MarkSendingTRR(TRR* trr, enum TrrType rectype, MutexAutoLock&) {
+ if (rectype == TRRTYPE_A) {
+ MOZ_ASSERT(!mTrrA);
+ mTrrA = trr;
+ mTrrAUsed = STARTED;
+ } else if (rectype == TRRTYPE_AAAA) {
+ MOZ_ASSERT(!mTrrAAAA);
+ mTrrAAAA = trr;
+ mTrrAAAAUsed = STARTED;
+ } else {
+ LOG(("TrrLookup called with bad type set: %d\n", rectype));
+ MOZ_ASSERT(0);
+ }
+}
+
+void TRRQuery::PrepareQuery(enum TrrType aRecType,
+ nsTArray<RefPtr<TRR>>& aRequestsToSend) {
+ LOG(("TRR Resolve %s type %d\n", mRecord->host.get(), (int)aRecType));
+ RefPtr<TRR> trr = new TRR(this, mRecord, aRecType);
+
+ {
+ MutexAutoLock trrlock(mTrrLock);
+ MarkSendingTRR(trr, aRecType, trrlock);
+ aRequestsToSend.AppendElement(trr);
+ }
+}
+
+bool TRRQuery::SendQueries(nsTArray<RefPtr<TRR>>& aRequestsToSend) {
+ bool madeQuery = false;
+ mTRRRequestCounter = aRequestsToSend.Length();
+ for (const auto& request : aRequestsToSend) {
+ if (NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(request))) {
+ madeQuery = true;
+ } else {
+ mTRRRequestCounter--;
+ MutexAutoLock trrlock(mTrrLock);
+ if (request == mTrrA) {
+ mTrrA = nullptr;
+ mTrrAUsed = INIT;
+ }
+ if (request == mTrrAAAA) {
+ mTrrAAAA = nullptr;
+ mTrrAAAAUsed = INIT;
+ }
+ }
+ }
+ aRequestsToSend.Clear();
+ return madeQuery;
+}
+
+nsresult TRRQuery::DispatchLookup(TRR* pushedTRR) {
+ mTrrStart = TimeStamp::Now();
+
+ if (!mRecord->IsAddrRecord()) {
+ return DispatchByTypeLookup(pushedTRR);
+ }
+
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(mRecord);
+ MOZ_ASSERT(addrRec);
+ if (!addrRec) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mTrrAUsed = INIT;
+ mTrrAAAAUsed = INIT;
+
+ // Always issue both A and AAAA.
+ // When both are complete we filter out the unneeded results.
+ enum TrrType rectype = (mRecord->af == AF_INET6) ? TRRTYPE_AAAA : TRRTYPE_A;
+
+ if (pushedTRR) {
+ MutexAutoLock trrlock(mTrrLock);
+ rectype = pushedTRR->Type();
+ MarkSendingTRR(pushedTRR, rectype, trrlock);
+ return NS_OK;
+ }
+
+ // Need to dispatch TRR requests after |mTrrA| and |mTrrAAAA| are set
+ // properly so as to avoid the race when CompleteLookup() is called at the
+ // same time.
+ nsTArray<RefPtr<TRR>> requestsToSend;
+ if ((mRecord->af == AF_UNSPEC || mRecord->af == AF_INET6) &&
+ !StaticPrefs::network_dns_disableIPv6()) {
+ PrepareQuery(TRRTYPE_AAAA, requestsToSend);
+ }
+ if (mRecord->af == AF_UNSPEC || mRecord->af == AF_INET) {
+ PrepareQuery(TRRTYPE_A, requestsToSend);
+ }
+
+ if (SendQueries(requestsToSend)) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_UNKNOWN_HOST;
+}
+
+nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR) {
+ RefPtr<TypeHostRecord> typeRec = do_QueryObject(mRecord);
+ MOZ_ASSERT(typeRec);
+ if (!typeRec) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ enum TrrType rectype;
+
+ // XXX this could use a more extensible approach.
+ if (mRecord->type == nsIDNSService::RESOLVE_TYPE_TXT) {
+ rectype = TRRTYPE_TXT;
+ } else if (mRecord->type == nsIDNSService::RESOLVE_TYPE_HTTPSSVC) {
+ rectype = TRRTYPE_HTTPSSVC;
+ } else if (pushedTRR) {
+ rectype = pushedTRR->Type();
+ } else {
+ MOZ_ASSERT(false, "Not an expected request type");
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ LOG(("TRR Resolve %s type %d\n", typeRec->host.get(), (int)rectype));
+ RefPtr<TRR> trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype);
+
+ if (pushedTRR || NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(trr))) {
+ MutexAutoLock trrlock(mTrrLock);
+ MOZ_ASSERT(!mTrrByType);
+ mTrrByType = trr;
+ return NS_OK;
+ }
+
+ return NS_ERROR_UNKNOWN_HOST;
+}
+
+AHostResolver::LookupStatus TRRQuery::CompleteLookup(
+ nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
+ const nsACString& aOriginsuffix, nsHostRecord::TRRSkippedReason aReason,
+ TRR* aTRRRequest) {
+ if (rec != mRecord) {
+ LOG(("TRRQuery::CompleteLookup - Pushed record. Go to resolver"));
+ return mHostResolver->CompleteLookup(rec, status, aNewRRSet, pb,
+ aOriginsuffix, aReason, aTRRRequest);
+ }
+
+ LOG(("TRRQuery::CompleteLookup > host: %s", rec->host.get()));
+
+ RefPtr<AddrInfo> newRRSet(aNewRRSet);
+ DNSResolverType resolverType = newRRSet->ResolverType();
+ {
+ MutexAutoLock trrlock(mTrrLock);
+ if (newRRSet->TRRType() == TRRTYPE_A) {
+ MOZ_ASSERT(mTrrA);
+ mTRRAFailReason = aReason;
+ mTrrA = nullptr;
+ mTrrAUsed = NS_SUCCEEDED(status) ? OK : FAILED;
+ MOZ_ASSERT(!mAddrInfoA);
+ mAddrInfoA = newRRSet;
+ mAResult = status;
+ LOG(("A query status: 0x%x", static_cast<uint32_t>(status)));
+ } else if (newRRSet->TRRType() == TRRTYPE_AAAA) {
+ MOZ_ASSERT(mTrrAAAA);
+ mTRRAAAAFailReason = aReason;
+ mTrrAAAA = nullptr;
+ mTrrAAAAUsed = NS_SUCCEEDED(status) ? OK : FAILED;
+ MOZ_ASSERT(!mAddrInfoAAAA);
+ mAddrInfoAAAA = newRRSet;
+ mAAAAResult = status;
+ LOG(("AAAA query status: 0x%x", static_cast<uint32_t>(status)));
+ } else {
+ MOZ_ASSERT(0);
+ }
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ mTRRSuccess++;
+ if (mTRRSuccess == 1) {
+ // Store the duration on first succesful TRR response. We
+ // don't know that there will be a second response nor can we
+ // tell which of two has useful data.
+ mTrrDuration = TimeStamp::Now() - mTrrStart;
+ }
+ }
+
+ bool pendingRequest = false;
+ if (mTRRRequestCounter) {
+ mTRRRequestCounter--;
+ pendingRequest = (mTRRRequestCounter != 0);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Request counter is messed up");
+ }
+ if (pendingRequest) { // There are other outstanding requests
+ LOG(("CompleteLookup: waiting for all responses!\n"));
+ return LOOKUP_OK;
+ }
+
+ if (mRecord->af == AF_UNSPEC) {
+ // merge successful records
+ if (mTrrAUsed == OK) {
+ LOG(("Have A response"));
+ newRRSet = mAddrInfoA;
+ status = mAResult;
+ if (mTrrAAAAUsed == OK) {
+ LOG(("Merging A and AAAA responses"));
+ newRRSet = merge_rrset(newRRSet, mAddrInfoAAAA);
+ }
+ } else {
+ newRRSet = mAddrInfoAAAA;
+ status = mAAAAResult;
+ }
+
+ if (NS_FAILED(status) && (mAAAAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST ||
+ mAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) {
+ status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
+ }
+ } else {
+ // If this is a failed AAAA request, but the server only has a A record,
+ // then we should not fallback to Do53. Instead we also send a A request
+ // and return NS_ERROR_DEFINITIVE_UNKNOWN_HOST if that succeeds.
+ if (NS_FAILED(status) && status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST &&
+ (mTrrAUsed == INIT || mTrrAAAAUsed == INIT)) {
+ if (newRRSet->TRRType() == TRRTYPE_A) {
+ LOG(("A lookup failed. Checking if AAAA record exists"));
+ nsTArray<RefPtr<TRR>> requestsToSend;
+ PrepareQuery(TRRTYPE_AAAA, requestsToSend);
+ if (SendQueries(requestsToSend)) {
+ LOG(("Sent AAAA request"));
+ return LOOKUP_OK;
+ }
+ } else if (newRRSet->TRRType() == TRRTYPE_AAAA) {
+ LOG(("AAAA lookup failed. Checking if A record exists"));
+ nsTArray<RefPtr<TRR>> requestsToSend;
+ PrepareQuery(TRRTYPE_A, requestsToSend);
+ if (SendQueries(requestsToSend)) {
+ LOG(("Sent A request"));
+ return LOOKUP_OK;
+ }
+ } else {
+ MOZ_ASSERT(false, "Unexpected family");
+ }
+ }
+ bool otherSucceeded =
+ mRecord->af == AF_INET6 ? mTrrAUsed == OK : mTrrAAAAUsed == OK;
+ LOG(("TRRQuery::CompleteLookup other request succeeded"));
+
+ if (mRecord->af == AF_INET) {
+ // return only A record
+ newRRSet = mAddrInfoA;
+ status = mAResult;
+ if (NS_FAILED(status) &&
+ (otherSucceeded || mAAAAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) {
+ LOG(("status set to NS_ERROR_DEFINITIVE_UNKNOWN_HOST"));
+ status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
+ }
+
+ } else if (mRecord->af == AF_INET6) {
+ // return only AAAA record
+ newRRSet = mAddrInfoAAAA;
+ status = mAAAAResult;
+
+ if (NS_FAILED(status) &&
+ (otherSucceeded || mAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) {
+ LOG(("status set to NS_ERROR_DEFINITIVE_UNKNOWN_HOST"));
+ status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
+ }
+
+ } else {
+ MOZ_ASSERT(false, "Unexpected AF");
+ return LOOKUP_OK;
+ }
+
+ // If this record failed, but there is a record for the other AF
+ // we prevent fallback to the native resolver.
+ }
+
+ if (mTRRSuccess && mHostResolver->GetNCS() &&
+ (mHostResolver->GetNCS()->GetNAT64() ==
+ nsINetworkConnectivityService::OK) &&
+ newRRSet) {
+ newRRSet = mHostResolver->GetNCS()->MapNAT64IPs(newRRSet);
+ }
+
+ if (resolverType == DNSResolverType::TRR) {
+ if (mTrrAUsed == OK) {
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAOK);
+ } else if (mTrrAUsed == FAILED) {
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAFail);
+ }
+
+ if (mTrrAAAAUsed == OK) {
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAAAAOK);
+ } else if (mTrrAAAAUsed == FAILED) {
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAAAAFail);
+ }
+ }
+
+ mAddrInfoAAAA = nullptr;
+ mAddrInfoA = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCalledCompleteLookup,
+ "must not call CompleteLookup more than once");
+ mCalledCompleteLookup = true;
+ return mHostResolver->CompleteLookup(rec, status, newRRSet, pb, aOriginsuffix,
+ aReason, aTRRRequest);
+}
+
+AHostResolver::LookupStatus TRRQuery::CompleteLookupByType(
+ nsHostRecord* rec, nsresult status,
+ mozilla::net::TypeRecordResultType& aResult,
+ mozilla::net::TRRSkippedReason aReason, uint32_t aTtl, bool pb) {
+ if (rec != mRecord) {
+ LOG(("TRRQuery::CompleteLookup - Pushed record. Go to resolver"));
+ return mHostResolver->CompleteLookupByType(rec, status, aResult, aReason,
+ aTtl, pb);
+ }
+
+ {
+ MutexAutoLock trrlock(mTrrLock);
+ mTrrByType = nullptr;
+ }
+
+ // Unlike the address record, we store the duration regardless of the status.
+ mTrrDuration = TimeStamp::Now() - mTrrStart;
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCalledCompleteLookup,
+ "must not call CompleteLookup more than once");
+ mCalledCompleteLookup = true;
+ return mHostResolver->CompleteLookupByType(rec, status, aResult, aReason,
+ aTtl, pb);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRQuery.h b/netwerk/dns/TRRQuery.h
new file mode 100644
index 0000000000..211aacbe74
--- /dev/null
+++ b/netwerk/dns/TRRQuery.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; 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_TRRQuery_h
+#define mozilla_net_TRRQuery_h
+
+#include "nsHostResolver.h"
+#include "DNSPacket.h"
+
+namespace mozilla {
+namespace net {
+
+class TRRQuery : public AHostResolver {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TRRQuery, override)
+
+ public:
+ TRRQuery(nsHostResolver* aHostResolver, nsHostRecord* aHostRecord)
+ : mHostResolver(aHostResolver),
+ mRecord(aHostRecord),
+ mTrrLock("TRRQuery.mTrrLock") {}
+
+ nsresult DispatchLookup(TRR* pushedTRR = nullptr);
+
+ void Cancel(nsresult aStatus);
+
+ enum TRRState { INIT, STARTED, OK, FAILED };
+ TRRState mTrrAUsed = INIT;
+ TRRState mTrrAAAAUsed = INIT;
+
+ TRRSkippedReason mTRRAFailReason = TRRSkippedReason::TRR_UNSET;
+ TRRSkippedReason mTRRAAAAFailReason = TRRSkippedReason::TRR_UNSET;
+
+ virtual LookupStatus CompleteLookup(nsHostRecord*, nsresult,
+ mozilla::net::AddrInfo*, bool pb,
+ const nsACString& aOriginsuffix,
+ nsHostRecord::TRRSkippedReason aReason,
+ TRR* aTRRRequest) override;
+ virtual LookupStatus CompleteLookupByType(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ mozilla::net::TRRSkippedReason aReason, uint32_t aTtl, bool pb) override;
+ virtual nsresult GetHostRecord(const nsACString& host,
+ const nsACString& aTrrServer, uint16_t type,
+ nsIDNSService::DNSFlags flags, uint16_t af,
+ bool pb, const nsCString& originSuffix,
+ nsHostRecord** result) override {
+ if (!mHostResolver) {
+ return NS_ERROR_FAILURE;
+ }
+ return mHostResolver->GetHostRecord(host, aTrrServer, type, flags, af, pb,
+ originSuffix, result);
+ }
+ virtual nsresult TrrLookup_unlocked(
+ nsHostRecord* rec, mozilla::net::TRR* pushedTRR = nullptr) override {
+ if (!mHostResolver) {
+ return NS_ERROR_FAILURE;
+ }
+ return mHostResolver->TrrLookup_unlocked(rec, pushedTRR);
+ }
+ virtual void MaybeRenewHostRecord(nsHostRecord* aRec) override {
+ if (!mHostResolver) {
+ return;
+ }
+ mHostResolver->MaybeRenewHostRecord(aRec);
+ }
+
+ mozilla::TimeDuration Duration() { return mTrrDuration; }
+
+ private:
+ nsresult DispatchByTypeLookup(TRR* pushedTRR = nullptr);
+
+ private:
+ ~TRRQuery() = default;
+
+ void MarkSendingTRR(TRR* trr, TrrType rectype, MutexAutoLock&);
+ void PrepareQuery(TrrType aRecType, nsTArray<RefPtr<TRR>>& aRequestsToSend);
+ bool SendQueries(nsTArray<RefPtr<TRR>>& aRequestsToSend);
+
+ RefPtr<nsHostResolver> mHostResolver;
+ RefPtr<nsHostRecord> mRecord;
+
+ Mutex mTrrLock
+ MOZ_UNANNOTATED; // lock when accessing the mTrrA[AAA] pointers
+ RefPtr<mozilla::net::TRR> mTrrA;
+ RefPtr<mozilla::net::TRR> mTrrAAAA;
+ RefPtr<mozilla::net::TRR> mTrrByType;
+ // |mTRRRequestCounter| indicates the number of TRR requests that were
+ // dispatched sucessfully. Generally, this counter is increased to 2 after
+ // mTrrA and mTrrAAAA are dispatched, and is decreased by 1 when
+ // CompleteLookup is called. Note that nsHostResolver::CompleteLookup is only
+ // called when this counter equals to 0.
+ Atomic<uint32_t> mTRRRequestCounter{0};
+
+ uint8_t mTRRSuccess = 0; // number of successful TRR responses
+ bool mCalledCompleteLookup = false;
+
+ mozilla::TimeDuration mTrrDuration;
+ mozilla::TimeStamp mTrrStart;
+
+ RefPtr<mozilla::net::AddrInfo> mAddrInfoA;
+ RefPtr<mozilla::net::AddrInfo> mAddrInfoAAAA;
+ nsresult mAResult = NS_OK;
+ nsresult mAAAAResult = NS_OK;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TRRQuery_h
diff --git a/netwerk/dns/TRRService.cpp b/netwerk/dns/TRRService.cpp
new file mode 100644
index 0000000000..fbaa67ee14
--- /dev/null
+++ b/netwerk/dns/TRRService.cpp
@@ -0,0 +1,1411 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsCharSeparatedTokenizer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsICaptivePortalService.h"
+#include "nsIFile.h"
+#include "nsIParentalControlsService.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
+#include "nsIOService.h"
+#include "nsNetUtil.h"
+#include "nsStandardURL.h"
+#include "TRR.h"
+#include "TRRService.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryComms.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/TRRServiceChild.h"
+// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
+#include "DNSLogging.h"
+
+static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
+static const char kClearPrivateData[] = "clear-private-data";
+static const char kPurge[] = "browser:purge-session-history";
+
+#define TRR_PREF_PREFIX "network.trr."
+#define TRR_PREF(x) TRR_PREF_PREFIX x
+
+namespace mozilla::net {
+
+StaticRefPtr<nsIThread> sTRRBackgroundThread;
+static Atomic<TRRService*> sTRRServicePtr;
+
+static Atomic<size_t, Relaxed> sDomainIndex(0);
+static Atomic<size_t, Relaxed> sCurrentTRRModeIndex(0);
+
+constexpr nsLiteralCString kTRRDomains[3][7] = {
+ // clang-format off
+ {
+ // When mode is 0, the provider key has no postfix.
+ "(other)"_ns,
+ "mozilla.cloudflare-dns.com"_ns,
+ "firefox.dns.nextdns.io"_ns,
+ "private.canadianshield.cira.ca"_ns,
+ "doh.xfinity.com"_ns, // Steered clients
+ "dns.shaw.ca"_ns, // Steered clients
+ "dooh.cloudflare-dns.com"_ns, // DNS over Oblivious HTTP
+ },
+ {
+ "(other)_2"_ns,
+ "mozilla.cloudflare-dns.com_2"_ns,
+ "firefox.dns.nextdns.io_2"_ns,
+ "private.canadianshield.cira.ca_2"_ns,
+ "doh.xfinity.com_2"_ns, // Steered clients
+ "dns.shaw.ca_2"_ns, // Steered clients
+ "dooh.cloudflare-dns.com_2"_ns, // DNS over Oblivious HTTP
+ },
+ {
+ "(other)_3"_ns,
+ "mozilla.cloudflare-dns.com_3"_ns,
+ "firefox.dns.nextdns.io_3"_ns,
+ "private.canadianshield.cira.ca_3"_ns,
+ "doh.xfinity.com_3"_ns, // Steered clients
+ "dns.shaw.ca_3"_ns, // Steered clients
+ "dooh.cloudflare-dns.com_3"_ns, // DNS over Oblivious HTTP
+ },
+ // clang-format on
+};
+
+// static
+void TRRService::SetCurrentTRRMode(nsIDNSService::ResolverMode aMode) {
+ // A table to map ResolverMode to the row of kTRRDomains.
+ // When the aMode is 2, we use kTRRDomains[1] as provider keys. When aMode is
+ // 3, we use kTRRDomains[2]. Otherwise, we kTRRDomains[0] is used.
+ static const uint32_t index[] = {0, 0, 1, 2, 0, 0};
+ if (aMode > nsIDNSService::MODE_TRROFF) {
+ aMode = nsIDNSService::MODE_TRROFF;
+ }
+ sCurrentTRRModeIndex = index[static_cast<size_t>(aMode)];
+}
+
+// static
+void TRRService::SetProviderDomain(const nsACString& aTRRDomain) {
+ sDomainIndex = 0;
+ for (size_t i = 1; i < std::size(kTRRDomains[0]); i++) {
+ if (aTRRDomain.Equals(kTRRDomains[0][i])) {
+ sDomainIndex = i;
+ break;
+ }
+ }
+}
+
+// static
+const nsCString& TRRService::ProviderKey() {
+ return kTRRDomains[sCurrentTRRModeIndex][sDomainIndex];
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(TRRService, TRRServiceBase, nsIObserver,
+ nsISupportsWeakReference)
+
+NS_IMPL_ADDREF_USING_AGGREGATOR(TRRService::ConfirmationContext, OwningObject())
+NS_IMPL_RELEASE_USING_AGGREGATOR(TRRService::ConfirmationContext,
+ OwningObject())
+NS_IMPL_QUERY_INTERFACE(TRRService::ConfirmationContext, nsITimerCallback,
+ nsINamed)
+
+TRRService::TRRService() : mLock("TRRService", this) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+}
+
+// static
+TRRService* TRRService::Get() { return sTRRServicePtr; }
+
+// static
+void TRRService::AddObserver(nsIObserver* aObserver,
+ nsIObserverService* aObserverService) {
+ nsCOMPtr<nsIObserverService> observerService;
+ if (aObserverService) {
+ observerService = aObserverService;
+ } else {
+ observerService = mozilla::services::GetObserverService();
+ }
+
+ if (observerService) {
+ observerService->AddObserver(aObserver, NS_CAPTIVE_PORTAL_CONNECTIVITY,
+ true);
+ observerService->AddObserver(aObserver, kOpenCaptivePortalLoginEvent, true);
+ observerService->AddObserver(aObserver, kClearPrivateData, true);
+ observerService->AddObserver(aObserver, kPurge, true);
+ observerService->AddObserver(aObserver, NS_NETWORK_LINK_TOPIC, true);
+ observerService->AddObserver(aObserver, NS_DNS_SUFFIX_LIST_UPDATED_TOPIC,
+ true);
+ observerService->AddObserver(aObserver, "xpcom-shutdown-threads", true);
+ }
+}
+
+// static
+bool TRRService::CheckCaptivePortalIsPassed() {
+ bool result = false;
+ nsCOMPtr<nsICaptivePortalService> captivePortalService =
+ do_GetService(NS_CAPTIVEPORTAL_CID);
+ if (captivePortalService) {
+ int32_t captiveState;
+ MOZ_ALWAYS_SUCCEEDS(captivePortalService->GetState(&captiveState));
+
+ if ((captiveState == nsICaptivePortalService::UNLOCKED_PORTAL) ||
+ (captiveState == nsICaptivePortalService::NOT_CAPTIVE)) {
+ result = true;
+ }
+ LOG(("TRRService::Init mCaptiveState=%d mCaptiveIsPassed=%d\n",
+ captiveState, (int)result));
+ }
+
+ return result;
+}
+
+static void EventTelemetryPrefChanged(const char* aPref, void* aData) {
+ Telemetry::SetEventRecordingEnabled(
+ "network.dns"_ns,
+ StaticPrefs::network_trr_confirmation_telemetry_enabled());
+}
+
+nsresult TRRService::Init() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mInitialized) {
+ return NS_OK;
+ }
+ mInitialized = true;
+
+ AddObserver(this);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ GetPrefBranch(getter_AddRefs(prefBranch));
+ if (prefBranch) {
+ prefBranch->AddObserver(TRR_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(kRolloutURIPref, this, true);
+ prefBranch->AddObserver(kRolloutModePref, this, true);
+ }
+
+ sTRRServicePtr = this;
+
+ ReadPrefs(nullptr);
+ mConfirmation.HandleEvent(ConfirmationEvent::Init);
+
+ if (XRE_IsParentProcess()) {
+ mCaptiveIsPassed = CheckCaptivePortalIsPassed();
+
+ mParentalControlEnabled = GetParentalControlEnabledInternal();
+
+ mLinkService = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
+ if (mLinkService) {
+ nsTArray<nsCString> suffixList;
+ mLinkService->GetDnsSuffixList(suffixList);
+ RebuildSuffixList(std::move(suffixList));
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ if (NS_FAILED(
+ NS_NewNamedThread("TRR Background", getter_AddRefs(thread)))) {
+ NS_WARNING("NS_NewNamedThread failed!");
+ return NS_ERROR_FAILURE;
+ }
+
+ sTRRBackgroundThread = thread;
+ }
+
+ Preferences::RegisterCallbackAndCall(
+ EventTelemetryPrefChanged,
+ "network.trr.confirmation_telemetry_enabled"_ns);
+
+ LOG(("Initialized TRRService\n"));
+ return NS_OK;
+}
+
+// static
+bool TRRService::GetParentalControlEnabledInternal() {
+ nsCOMPtr<nsIParentalControlsService> pc =
+ do_CreateInstance("@mozilla.org/parental-controls-service;1");
+ if (pc) {
+ bool result = false;
+ pc->GetParentalControlsEnabled(&result);
+ LOG(("TRRService::GetParentalControlEnabledInternal=%d\n", result));
+ return result;
+ }
+
+ return false;
+}
+
+void TRRService::SetDetectedTrrURI(const nsACString& aURI) {
+ LOG(("SetDetectedTrrURI(%s", nsPromiseFlatCString(aURI).get()));
+ // If the user has set a custom URI then we don't want to override that.
+ // If the URI is set via doh-rollout.uri, mURIPref will be empty
+ // (see TRRServiceBase::OnTRRURIChange)
+ if (!mURIPref.IsEmpty()) {
+ LOG(("Already has user value. Not setting URI"));
+ return;
+ }
+
+ if (StaticPrefs::network_trr_use_ohttp()) {
+ LOG(("No autodetection when using OHTTP"));
+ return;
+ }
+
+ mURISetByDetection = MaybeSetPrivateURI(aURI);
+}
+
+bool TRRService::Enabled(nsIRequest::TRRMode aRequestMode) {
+ if (mMode == nsIDNSService::MODE_TRROFF ||
+ aRequestMode == nsIRequest::TRR_DISABLED_MODE) {
+ LOG(("TRR service not enabled - off or disabled"));
+ return false;
+ }
+
+ // If already confirmed, service is enabled.
+ if (mConfirmation.State() == CONFIRM_OK ||
+ aRequestMode == nsIRequest::TRR_ONLY_MODE) {
+ LOG(("TRR service enabled - confirmed or trr_only request"));
+ return true;
+ }
+
+ // If this is a TRR_FIRST request but the resolver has a different mode,
+ // just go ahead and let it try to use TRR.
+ if (aRequestMode == nsIRequest::TRR_FIRST_MODE &&
+ mMode != nsIDNSService::MODE_TRRFIRST) {
+ LOG(("TRR service enabled - trr_first request"));
+ return true;
+ }
+
+ // In TRR_ONLY_MODE / confirmationNS == "skip" we don't try to confirm.
+ if (mConfirmation.State() == CONFIRM_DISABLED) {
+ LOG(("TRRService service enabled - confirmation is disabled"));
+ return true;
+ }
+
+ LOG(("TRRService::Enabled mConfirmation.mState=%d mCaptiveIsPassed=%d\n",
+ mConfirmation.State(), (int)mCaptiveIsPassed));
+
+ if (StaticPrefs::network_trr_wait_for_confirmation()) {
+ return mConfirmation.State() == CONFIRM_OK;
+ }
+
+ if (StaticPrefs::network_trr_attempt_when_retrying_confirmation()) {
+ return mConfirmation.State() == CONFIRM_OK ||
+ mConfirmation.State() == CONFIRM_TRYING_OK ||
+ mConfirmation.State() == CONFIRM_TRYING_FAILED;
+ }
+
+ return mConfirmation.State() == CONFIRM_OK ||
+ mConfirmation.State() == CONFIRM_TRYING_OK;
+}
+
+void TRRService::GetPrefBranch(nsIPrefBranch** result) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ *result = nullptr;
+ CallGetService(NS_PREFSERVICE_CONTRACTID, result);
+}
+
+bool TRRService::MaybeSetPrivateURI(const nsACString& aURI) {
+ bool clearCache = false;
+ nsAutoCString newURI(aURI);
+ LOG(("MaybeSetPrivateURI(%s)", newURI.get()));
+
+ ProcessURITemplate(newURI);
+ {
+ MutexSingleWriterAutoLock lock(mLock);
+ if (mPrivateURI.Equals(newURI)) {
+ return false;
+ }
+
+ if (!mPrivateURI.IsEmpty()) {
+ LOG(("TRRService clearing blocklist because of change in uri service\n"));
+ auto bl = mTRRBLStorage.Lock();
+ bl->Clear();
+ clearCache = true;
+ }
+
+ nsAutoCString host;
+
+ nsCOMPtr<nsIURI> url;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(url), newURI))) {
+ url->GetHost(host);
+ }
+
+ SetProviderDomain(host);
+
+ mPrivateURI = newURI;
+
+ // Notify the content processes of the new TRR
+ for (auto* cp :
+ dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent =
+ SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ Unused << neckoParent->SendSetTRRDomain(host);
+ }
+
+ AsyncCreateTRRConnectionInfo(mPrivateURI);
+
+ // The URI has changed. We should trigger a new confirmation immediately.
+ // We must do this here because the URI could also change because of
+ // steering.
+ mConfirmationTriggered =
+ mConfirmation.HandleEvent(ConfirmationEvent::URIChange, lock);
+ }
+
+ // Clear the cache because we changed the URI
+ if (clearCache) {
+ ClearEntireCache();
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, NS_NETWORK_TRR_URI_CHANGED_TOPIC, nullptr);
+ }
+ return true;
+}
+
+nsresult TRRService::ReadPrefs(const char* name) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ // Whenever a pref change occurs that would cause us to clear the cache
+ // we set this to true then do it at the end of the method.
+ bool clearEntireCache = false;
+
+ if (!name || !strcmp(name, TRR_PREF("mode")) ||
+ !strcmp(name, kRolloutModePref)) {
+ nsIDNSService::ResolverMode prevMode = Mode();
+
+ OnTRRModeChange();
+ // When the TRR service gets disabled we should purge the TRR cache to
+ // make sure we don't use any of the cached entries on a network where
+ // they are invalid - for example after turning on a VPN.
+ if (TRR_DISABLED(Mode()) && !TRR_DISABLED(prevMode)) {
+ clearEntireCache = true;
+ }
+ }
+ if (!name || !strcmp(name, TRR_PREF("uri")) ||
+ !strcmp(name, TRR_PREF("default_provider_uri")) ||
+ !strcmp(name, kRolloutURIPref) || !strcmp(name, TRR_PREF("ohttp.uri")) ||
+ !strcmp(name, TRR_PREF("use_ohttp"))) {
+ OnTRRURIChange();
+ }
+ if (!name || !strcmp(name, TRR_PREF("credentials"))) {
+ MutexSingleWriterAutoLock lock(mLock);
+ Preferences::GetCString(TRR_PREF("credentials"), mPrivateCred);
+ }
+ if (!name || !strcmp(name, TRR_PREF("confirmationNS"))) {
+ MutexSingleWriterAutoLock lock(mLock);
+ Preferences::GetCString(TRR_PREF("confirmationNS"), mConfirmationNS);
+ LOG(("confirmationNS = %s", mConfirmationNS.get()));
+ }
+ if (!name || !strcmp(name, TRR_PREF("bootstrapAddr"))) {
+ MutexSingleWriterAutoLock lock(mLock);
+ Preferences::GetCString(TRR_PREF("bootstrapAddr"), mBootstrapAddr);
+ clearEntireCache = true;
+ }
+ if (!name || !strcmp(name, TRR_PREF("excluded-domains")) ||
+ !strcmp(name, TRR_PREF("builtin-excluded-domains"))) {
+ MutexSingleWriterAutoLock lock(mLock);
+ mExcludedDomains.Clear();
+
+ auto parseExcludedDomains = [this](const char* aPrefName) {
+ nsAutoCString excludedDomains;
+ mLock.AssertCurrentThreadOwns();
+ Preferences::GetCString(aPrefName, excludedDomains);
+ if (excludedDomains.IsEmpty()) {
+ return;
+ }
+
+ for (const nsACString& tokenSubstring :
+ nsCCharSeparatedTokenizerTemplate<
+ NS_IsAsciiWhitespace, nsTokenizerFlags::SeparatorOptional>(
+ excludedDomains, ',')
+ .ToRange()) {
+ nsCString token{tokenSubstring};
+ LOG(("TRRService::ReadPrefs %s host:[%s]\n", aPrefName, token.get()));
+ mExcludedDomains.Insert(token);
+ }
+ };
+
+ parseExcludedDomains(TRR_PREF("excluded-domains"));
+ parseExcludedDomains(TRR_PREF("builtin-excluded-domains"));
+ clearEntireCache = true;
+ }
+
+ // if name is null, then we're just now initializing. In that case we don't
+ // need to clear the cache.
+ if (name && clearEntireCache) {
+ ClearEntireCache();
+ }
+
+ return NS_OK;
+}
+
+void TRRService::ClearEntireCache() {
+ if (!StaticPrefs::network_trr_clear_cache_on_pref_change()) {
+ return;
+ }
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) {
+ return;
+ }
+ dns->ClearCache(true);
+}
+
+void TRRService::AddEtcHosts(const nsTArray<nsCString>& aArray) {
+ MutexSingleWriterAutoLock lock(mLock);
+ for (const auto& item : aArray) {
+ LOG(("Adding %s from /etc/hosts to excluded domains", item.get()));
+ mEtcHostsDomains.Insert(item);
+ }
+}
+
+void TRRService::ReadEtcHostsFile() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ DoReadEtcHostsFile([](const nsTArray<nsCString>* aArray) -> bool {
+ RefPtr<TRRService> service(sTRRServicePtr);
+ if (service && aArray) {
+ service->AddEtcHosts(*aArray);
+ }
+ return !!service;
+ });
+}
+
+void TRRService::GetURI(nsACString& result) {
+ MutexSingleWriterAutoLock lock(mLock);
+ result = mPrivateURI;
+}
+
+nsresult TRRService::GetCredentials(nsCString& result) {
+ MutexSingleWriterAutoLock lock(mLock);
+ result = mPrivateCred;
+ return NS_OK;
+}
+
+uint32_t TRRService::GetRequestTimeout() {
+ if (mMode == nsIDNSService::MODE_TRRONLY) {
+ return StaticPrefs::network_trr_request_timeout_mode_trronly_ms();
+ }
+
+ if (StaticPrefs::network_trr_strict_native_fallback()) {
+ return StaticPrefs::network_trr_strict_fallback_request_timeout_ms();
+ }
+
+ return StaticPrefs::network_trr_request_timeout_ms();
+}
+
+nsresult TRRService::Start() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (!mInitialized) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return NS_OK;
+}
+
+TRRService::~TRRService() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ LOG(("Exiting TRRService\n"));
+}
+
+nsresult TRRService::DispatchTRRRequest(TRR* aTrrRequest) {
+ return DispatchTRRRequestInternal(aTrrRequest, true);
+}
+
+nsresult TRRService::DispatchTRRRequestInternal(TRR* aTrrRequest,
+ bool aWithLock) {
+ NS_ENSURE_ARG_POINTER(aTrrRequest);
+
+ nsCOMPtr<nsIThread> thread = MainThreadOrTRRThread(aWithLock);
+ if (!thread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<TRR> trr = aTrrRequest;
+ return thread->Dispatch(trr.forget());
+}
+
+already_AddRefed<nsIThread> TRRService::MainThreadOrTRRThread(bool aWithLock) {
+ if (!StaticPrefs::network_trr_fetch_off_main_thread() ||
+ XRE_IsSocketProcess() || mDontUseTRRThread) {
+ return do_GetMainThread();
+ }
+
+ nsCOMPtr<nsIThread> thread = aWithLock ? TRRThread() : TRRThread_locked();
+ return thread.forget();
+}
+
+already_AddRefed<nsIThread> TRRService::TRRThread() {
+ MutexSingleWriterAutoLock lock(mLock);
+ return TRRThread_locked();
+}
+
+already_AddRefed<nsIThread> TRRService::TRRThread_locked() {
+ RefPtr<nsIThread> thread = sTRRBackgroundThread;
+ return thread.forget();
+}
+
+bool TRRService::IsOnTRRThread() {
+ nsCOMPtr<nsIThread> thread;
+ {
+ MutexSingleWriterAutoLock lock(mLock);
+ thread = sTRRBackgroundThread;
+ }
+ if (!thread) {
+ return false;
+ }
+
+ return thread->IsOnCurrentThread();
+}
+
+NS_IMETHODIMP
+TRRService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ LOG(("TRR::Observe() topic=%s\n", aTopic));
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ // Reset the state of whether a confirmation is triggered, so we can check
+ // if we create a new one after ReadPrefs().
+ mConfirmationTriggered = false;
+ ReadPrefs(NS_ConvertUTF16toUTF8(aData).get());
+ {
+ MutexSingleWriterAutoLock lock(mLock);
+ mConfirmation.RecordEvent("pref-change", lock);
+ }
+
+ // We should only trigger a new confirmation if reading the prefs didn't
+ // already trigger one.
+ if (!mConfirmationTriggered) {
+ mConfirmation.HandleEvent(ConfirmationEvent::PrefChange);
+ }
+ } else if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
+ // We are in a captive portal
+ LOG(("TRRservice in captive portal\n"));
+ mCaptiveIsPassed = false;
+ mConfirmation.SetCaptivePortalStatus(
+ nsICaptivePortalService::LOCKED_PORTAL);
+ } else if (!strcmp(aTopic, NS_CAPTIVE_PORTAL_CONNECTIVITY)) {
+ nsAutoCString data = NS_ConvertUTF16toUTF8(aData);
+ LOG(("TRRservice captive portal was %s\n", data.get()));
+ nsCOMPtr<nsICaptivePortalService> cps = do_QueryInterface(aSubject);
+ if (cps) {
+ mConfirmation.SetCaptivePortalStatus(cps->State());
+ }
+
+ // If we were previously in a captive portal, this event means we will
+ // need to trigger confirmation again. Otherwise it's just a periodical
+ // captive-portal check that completed and we don't need to react to it.
+ if (!mCaptiveIsPassed) {
+ mConfirmation.HandleEvent(ConfirmationEvent::CaptivePortalConnectivity);
+ }
+
+ mCaptiveIsPassed = true;
+ } else if (!strcmp(aTopic, kClearPrivateData) || !strcmp(aTopic, kPurge)) {
+ // flush the TRR blocklist
+ auto bl = mTRRBLStorage.Lock();
+ bl->Clear();
+ } else if (!strcmp(aTopic, NS_DNS_SUFFIX_LIST_UPDATED_TOPIC) ||
+ !strcmp(aTopic, NS_NETWORK_LINK_TOPIC)) {
+ // nsINetworkLinkService is only available on parent process.
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsINetworkLinkService> link = do_QueryInterface(aSubject);
+ // The network link service notification normally passes itself as the
+ // subject, but some unit tests will sometimes pass a null subject.
+ if (link) {
+ nsTArray<nsCString> suffixList;
+ link->GetDnsSuffixList(suffixList);
+ RebuildSuffixList(std::move(suffixList));
+ }
+ }
+
+ if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC)) {
+ if (NS_ConvertUTF16toUTF8(aData).EqualsLiteral(
+ NS_NETWORK_LINK_DATA_DOWN)) {
+ MutexSingleWriterAutoLock lock(mLock);
+ mConfirmation.RecordEvent("network-change", lock);
+ }
+
+ if (mURISetByDetection) {
+ // If the URI was set via SetDetectedTrrURI we need to restore it to the
+ // default pref when a network link change occurs.
+ CheckURIPrefs();
+ }
+
+ if (NS_ConvertUTF16toUTF8(aData).EqualsLiteral(NS_NETWORK_LINK_DATA_UP)) {
+ mConfirmation.HandleEvent(ConfirmationEvent::NetworkUp);
+ }
+ }
+ } else if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
+ mShutdown = true;
+ // If a confirmation is still in progress we record the event.
+ // Since there should be no more confirmations after this, the shutdown
+ // reason would not really be recorded in telemetry.
+ {
+ MutexSingleWriterAutoLock lock(mLock);
+ mConfirmation.RecordEvent("shutdown", lock);
+ }
+
+ if (sTRRBackgroundThread) {
+ nsCOMPtr<nsIThread> thread;
+ thread = sTRRBackgroundThread.get();
+ sTRRBackgroundThread = nullptr;
+ MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
+ sTRRServicePtr = nullptr;
+ }
+ }
+ return NS_OK;
+}
+
+void TRRService::RebuildSuffixList(nsTArray<nsCString>&& aSuffixList) {
+ if (!StaticPrefs::network_trr_split_horizon_mitigations() || mShutdown) {
+ return;
+ }
+
+ MutexSingleWriterAutoLock lock(mLock);
+ mDNSSuffixDomains.Clear();
+ for (const auto& item : aSuffixList) {
+ LOG(("TRRService adding %s to suffix list", item.get()));
+ mDNSSuffixDomains.Insert(item);
+ }
+}
+
+void TRRService::ConfirmationContext::SetState(
+ enum ConfirmationState aNewState) {
+ mState = aNewState;
+
+ enum ConfirmationState state = mState;
+ if (XRE_IsParentProcess()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "TRRService::ConfirmationContextNotify", [state] {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ auto stateString =
+ [](enum ConfirmationState aState) -> const char16_t* {
+ switch (aState) {
+ case CONFIRM_OFF:
+ return u"CONFIRM_OFF";
+ case CONFIRM_TRYING_OK:
+ return u"CONFIRM_TRYING_OK";
+ case CONFIRM_OK:
+ return u"CONFIRM_OK";
+ case CONFIRM_FAILED:
+ return u"CONFIRM_FAILED";
+ case CONFIRM_TRYING_FAILED:
+ return u"CONFIRM_TRYING_FAILED";
+ case CONFIRM_DISABLED:
+ return u"CONFIRM_DISABLED";
+ }
+ MOZ_ASSERT_UNREACHABLE();
+ return u"";
+ };
+
+ obs->NotifyObservers(nullptr, "network:trr-confirmation",
+ stateString(state));
+ }
+ }));
+ }
+
+ if (XRE_IsParentProcess()) {
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ TRRServiceChild* child = TRRServiceChild::GetSingleton();
+ if (child && child->CanSend()) {
+ LOG(("TRRService::SendSetConfirmationState"));
+ Unused << child->SendSetConfirmationState(mState);
+ }
+}
+
+bool TRRService::ConfirmationContext::HandleEvent(ConfirmationEvent aEvent) {
+ MutexSingleWriterAutoLock lock(OwningObject()->mLock);
+ return HandleEvent(aEvent, lock);
+}
+
+// We're protected by service->mLock
+bool TRRService::ConfirmationContext::HandleEvent(
+ ConfirmationEvent aEvent, const MutexSingleWriterAutoLock&) {
+ auto prevAddr = TaskAddr();
+ TRRService* service = OwningObject();
+ service->mLock.AssertCurrentThreadOwns();
+ nsIDNSService::ResolverMode mode = service->Mode();
+
+ auto resetConfirmation = [&]() {
+ service->mLock.AssertCurrentThreadOwns();
+ mTask = nullptr;
+ nsCOMPtr<nsITimer> timer = std::move(mTimer);
+ if (timer) {
+ timer->Cancel();
+ }
+
+ mRetryInterval = StaticPrefs::network_trr_retry_timeout_ms();
+ mTRRFailures = 0;
+
+ if (TRR_DISABLED(mode)) {
+ LOG(("TRR is disabled. mConfirmation.mState -> CONFIRM_OFF"));
+ SetState(CONFIRM_OFF);
+ return;
+ }
+
+ if (mode == nsIDNSService::MODE_TRRONLY) {
+ LOG(("TRR_ONLY_MODE. mConfirmation.mState -> CONFIRM_DISABLED"));
+ SetState(CONFIRM_DISABLED);
+ return;
+ }
+
+ if (service->mConfirmationNS.Equals("skip"_ns)) {
+ LOG((
+ "mConfirmationNS == skip. mConfirmation.mState -> CONFIRM_DISABLED"));
+ SetState(CONFIRM_DISABLED);
+ return;
+ }
+
+ // The next call to maybeConfirm will transition to CONFIRM_TRYING_OK
+ LOG(("mConfirmation.mState -> CONFIRM_OK"));
+ SetState(CONFIRM_OK);
+ };
+
+ auto maybeConfirm = [&](const char* aReason) {
+ service->mLock.AssertCurrentThreadOwns();
+ if (TRR_DISABLED(mode) || mState == CONFIRM_DISABLED || mTask) {
+ LOG(
+ ("TRRService:MaybeConfirm(%s) mode=%d, mTask=%p "
+ "mState=%d\n",
+ aReason, (int)mode, (void*)mTask, (int)mState));
+ return;
+ }
+
+ MOZ_ASSERT(mode != nsIDNSService::MODE_TRRONLY,
+ "Confirmation should be disabled");
+ MOZ_ASSERT(!service->mConfirmationNS.Equals("skip"),
+ "Confirmation should be disabled");
+
+ LOG(("maybeConfirm(%s) starting confirmation test %s %s\n", aReason,
+ service->mPrivateURI.get(), service->mConfirmationNS.get()));
+
+ MOZ_ASSERT(mState == CONFIRM_OK || mState == CONFIRM_FAILED);
+
+ if (mState == CONFIRM_FAILED) {
+ LOG(("mConfirmation.mState -> CONFIRM_TRYING_FAILED"));
+ SetState(CONFIRM_TRYING_FAILED);
+ } else {
+ LOG(("mConfirmation.mState -> CONFIRM_TRYING_OK"));
+ SetState(CONFIRM_TRYING_OK);
+ }
+
+ nsCOMPtr<nsITimer> timer = std::move(mTimer);
+ if (timer) {
+ timer->Cancel();
+ }
+
+ MOZ_ASSERT(mode == nsIDNSService::MODE_TRRFIRST,
+ "Should only confirm in TRR first mode");
+ // Set aUseFreshConnection if TRR lookups are retried.
+ mTask = new TRR(service, service->mConfirmationNS, TRRTYPE_NS, ""_ns, false,
+ StaticPrefs::network_trr_retry_on_recoverable_errors());
+ mTask->SetTimeout(StaticPrefs::network_trr_confirmation_timeout_ms());
+ mTask->SetPurpose(TRR::Confirmation);
+
+ if (service->mLinkService) {
+ service->mLinkService->GetNetworkID(mNetworkId);
+ }
+
+ if (mFirstRequestTime.IsNull()) {
+ mFirstRequestTime = TimeStamp::Now();
+ }
+ if (mTrigger.IsEmpty()) {
+ mTrigger.Assign(aReason);
+ }
+
+ LOG(("Dispatching confirmation task: %p", mTask.get()));
+ service->DispatchTRRRequestInternal(mTask, false);
+ };
+
+ switch (aEvent) {
+ case ConfirmationEvent::Init:
+ resetConfirmation();
+ maybeConfirm("context-init");
+ break;
+ case ConfirmationEvent::PrefChange:
+ resetConfirmation();
+ maybeConfirm("pref-change");
+ break;
+ case ConfirmationEvent::ConfirmationRetry:
+ MOZ_ASSERT(mState == CONFIRM_FAILED);
+ if (mState == CONFIRM_FAILED) {
+ maybeConfirm("confirmation-retry");
+ }
+ break;
+ case ConfirmationEvent::FailedLookups:
+ MOZ_ASSERT(mState == CONFIRM_OK);
+ mTrigger.Assign("failed-lookups");
+ mFailedLookups = nsDependentCSubstring(
+ mFailureReasons, mTRRFailures % ConfirmationContext::RESULTS_SIZE);
+ maybeConfirm("failed-lookups");
+ break;
+ case ConfirmationEvent::RetryTRR:
+ MOZ_ASSERT(mState == CONFIRM_OK);
+ maybeConfirm("retry-trr");
+ break;
+ case ConfirmationEvent::URIChange:
+ resetConfirmation();
+ maybeConfirm("uri-change");
+ break;
+ case ConfirmationEvent::CaptivePortalConnectivity:
+ // If we area already confirmed then we're fine.
+ // If there is a confirmation in progress, likely it started before
+ // we had full connectivity, so it may be hanging. We reset and try again.
+ if (mState == CONFIRM_FAILED || mState == CONFIRM_TRYING_FAILED ||
+ mState == CONFIRM_TRYING_OK) {
+ resetConfirmation();
+ maybeConfirm("cp-connectivity");
+ }
+ break;
+ case ConfirmationEvent::NetworkUp:
+ if (mState != CONFIRM_OK) {
+ resetConfirmation();
+ maybeConfirm("network-up");
+ }
+ break;
+ case ConfirmationEvent::ConfirmOK:
+ SetState(CONFIRM_OK);
+ mTask = nullptr;
+ break;
+ case ConfirmationEvent::ConfirmFail:
+ MOZ_ASSERT(mState == CONFIRM_TRYING_OK ||
+ mState == CONFIRM_TRYING_FAILED);
+ SetState(CONFIRM_FAILED);
+ mTask = nullptr;
+ // retry failed NS confirmation
+
+ NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mRetryInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ if (mRetryInterval < 64000) {
+ // double the interval up to this point
+ mRetryInterval *= 2;
+ }
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected ConfirmationEvent");
+ }
+
+ return prevAddr != TaskAddr();
+}
+
+bool TRRService::MaybeBootstrap(const nsACString& aPossible,
+ nsACString& aResult) {
+ MutexSingleWriterAutoLock lock(mLock);
+ if (mMode == nsIDNSService::MODE_TRROFF || mBootstrapAddr.IsEmpty()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv =
+ NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ 443, mPrivateURI, nullptr, nullptr, nullptr)
+ .Finalize(url);
+ if (NS_FAILED(rv)) {
+ LOG(("TRRService::MaybeBootstrap failed to create URI!\n"));
+ return false;
+ }
+
+ nsAutoCString host;
+ url->GetHost(host);
+ if (!aPossible.Equals(host)) {
+ return false;
+ }
+ LOG(("TRRService::MaybeBootstrap: use %s instead of %s\n",
+ mBootstrapAddr.get(), host.get()));
+ aResult = mBootstrapAddr;
+ return true;
+}
+
+bool TRRService::IsDomainBlocked(const nsACString& aHost,
+ const nsACString& aOriginSuffix,
+ bool aPrivateBrowsing) {
+ auto bl = mTRRBLStorage.Lock();
+ if (bl->IsEmpty()) {
+ return false;
+ }
+
+ // use a unified casing for the hashkey
+ nsAutoCString hashkey(aHost + aOriginSuffix);
+ if (auto val = bl->Lookup(hashkey)) {
+ int32_t until =
+ *val + int32_t(StaticPrefs::network_trr_temp_blocklist_duration_sec());
+ int32_t expire = NowInSeconds();
+ if (until > expire) {
+ LOG(("Host [%s] is TRR blocklisted\n", nsCString(aHost).get()));
+ return true;
+ }
+
+ // the blocklisted entry has expired
+ val.Remove();
+ }
+ return false;
+}
+
+// When running in TRR-only mode, the blocklist is not used and it will also
+// try resolving the localhost / .local names.
+bool TRRService::IsTemporarilyBlocked(const nsACString& aHost,
+ const nsACString& aOriginSuffix,
+ bool aPrivateBrowsing,
+ bool aParentsToo) // false if domain
+{
+ if (!StaticPrefs::network_trr_temp_blocklist()) {
+ LOG(("TRRService::IsTemporarilyBlocked temp blocklist disabled by pref"));
+ return false;
+ }
+
+ if (mMode == nsIDNSService::MODE_TRRONLY) {
+ return false; // might as well try
+ }
+
+ LOG(("Checking if host [%s] is blocklisted", aHost.BeginReading()));
+
+ int32_t dot = aHost.FindChar('.');
+ if ((dot == kNotFound) && aParentsToo) {
+ // Only if a full host name. Domains can be dotless to be able to
+ // blocklist entire TLDs
+ return true;
+ }
+
+ if (IsDomainBlocked(aHost, aOriginSuffix, aPrivateBrowsing)) {
+ return true;
+ }
+
+ nsDependentCSubstring domain = Substring(aHost, 0);
+ while (dot != kNotFound) {
+ dot++;
+ domain.Rebind(domain, dot, domain.Length() - dot);
+
+ if (IsDomainBlocked(domain, aOriginSuffix, aPrivateBrowsing)) {
+ return true;
+ }
+
+ dot = domain.FindChar('.');
+ }
+
+ return false;
+}
+
+bool TRRService::IsExcludedFromTRR(const nsACString& aHost) {
+ // This method may be called off the main thread. We need to lock so
+ // mExcludedDomains and mDNSSuffixDomains don't change while this code
+ // is running.
+ MutexSingleWriterAutoLock lock(mLock);
+
+ return IsExcludedFromTRR_unlocked(aHost);
+}
+
+bool TRRService::IsExcludedFromTRR_unlocked(const nsACString& aHost) {
+ mLock.AssertOnWritingThreadOrHeld();
+
+ int32_t dot = 0;
+ // iteratively check the sub-domain of |aHost|
+ while (dot < static_cast<int32_t>(aHost.Length())) {
+ nsDependentCSubstring subdomain =
+ Substring(aHost, dot, aHost.Length() - dot);
+
+ if (mExcludedDomains.Contains(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR via pref\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+ if (mDNSSuffixDomains.Contains(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR via pref\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+ if (mEtcHostsDomains.Contains(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR by /etc/hosts\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+
+ dot = aHost.FindChar('.', dot + 1);
+ if (dot == kNotFound) {
+ break;
+ }
+ dot++;
+ }
+
+ return false;
+}
+
+void TRRService::AddToBlocklist(const nsACString& aHost,
+ const nsACString& aOriginSuffix,
+ bool privateBrowsing, bool aParentsToo) {
+ if (!StaticPrefs::network_trr_temp_blocklist()) {
+ LOG(("TRRService::AddToBlocklist temp blocklist disabled by pref"));
+ return;
+ }
+
+ LOG(("TRR blocklist %s\n", nsCString(aHost).get()));
+ nsAutoCString hashkey(aHost + aOriginSuffix);
+
+ // this overwrites any existing entry
+ {
+ auto bl = mTRRBLStorage.Lock();
+ bl->InsertOrUpdate(hashkey, NowInSeconds());
+ }
+
+ // See bug 1700405. Some test expects 15 trr consecutive failures, but the NS
+ // check against the base domain is successful. So, we skip this NS check when
+ // the pref said so in order to pass the test reliably.
+ if (aParentsToo && !StaticPrefs::network_trr_skip_check_for_blocked_host()) {
+ // when given a full host name, verify its domain as well
+ int32_t dot = aHost.FindChar('.');
+ if (dot != kNotFound) {
+ // this has a domain to be checked
+ dot++;
+ nsDependentCSubstring domain =
+ Substring(aHost, dot, aHost.Length() - dot);
+ nsAutoCString check(domain);
+ if (IsTemporarilyBlocked(check, aOriginSuffix, privateBrowsing, false)) {
+ // the domain part is already blocklisted, no need to add this entry
+ return;
+ }
+ // verify 'check' over TRR
+ LOG(("TRR: verify if '%s' resolves as NS\n", check.get()));
+
+ // check if there's an NS entry for this name
+ RefPtr<TRR> trr = new TRR(this, check, TRRTYPE_NS, aOriginSuffix,
+ privateBrowsing, false);
+ trr->SetPurpose(TRR::Blocklist);
+ DispatchTRRRequest(trr);
+ }
+ }
+}
+
+NS_IMETHODIMP
+TRRService::ConfirmationContext::Notify(nsITimer* aTimer) {
+ MutexSingleWriterAutoLock lock(OwningObject()->mLock);
+ if (aTimer == mTimer) {
+ HandleEvent(ConfirmationEvent::ConfirmationRetry, lock);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRService::ConfirmationContext::GetName(nsACString& aName) {
+ aName.AssignLiteral("TRRService::ConfirmationContext");
+ return NS_OK;
+}
+
+static char StatusToChar(nsresult aLookupStatus, nsresult aChannelStatus) {
+ // If the resolution fails in the TRR channel then we'll have a failed
+ // aChannelStatus. Otherwise, we parse the response - if it's not a valid DNS
+ // packet or doesn't contain the correct responses aLookupStatus will be a
+ // failure code.
+ if (aChannelStatus == NS_OK) {
+ // Return + if confirmation was OK, or - if confirmation failed
+ return aLookupStatus == NS_OK ? '+' : '-';
+ }
+
+ if (nsCOMPtr<nsIIOService> ios = do_GetIOService()) {
+ bool hasConnectiviy = true;
+ ios->GetConnectivity(&hasConnectiviy);
+ if (!hasConnectiviy) {
+ // Browser has no active network interfaces = is offline.
+ return 'o';
+ }
+ }
+
+ switch (aChannelStatus) {
+ case NS_ERROR_NET_TIMEOUT_EXTERNAL:
+ // TRR timeout expired
+ return 't';
+ case NS_ERROR_UNKNOWN_HOST:
+ // TRRServiceChannel failed to due to unresolved host
+ return 'd';
+ default:
+ break;
+ }
+
+ // The error is a network error
+ if (NS_ERROR_GET_MODULE(aChannelStatus) == NS_ERROR_MODULE_NETWORK) {
+ return 'n';
+ }
+
+ // Some other kind of failure.
+ return '?';
+}
+
+void TRRService::RetryTRRConfirm() {
+ if (mConfirmation.State() == CONFIRM_OK) {
+ LOG(("TRRService::RetryTRRConfirm triggering confirmation"));
+ mConfirmation.HandleEvent(ConfirmationEvent::RetryTRR);
+ }
+}
+
+void TRRService::RecordTRRStatus(TRR* aTrrRequest) {
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), NS_IsMainThread() || IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+
+ nsresult channelStatus = aTrrRequest->ChannelStatus();
+
+ Telemetry::AccumulateCategoricalKeyed(
+ ProviderKey(), NS_SUCCEEDED(channelStatus)
+ ? Telemetry::LABELS_DNS_TRR_SUCCESS3::Fine
+ : (channelStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL
+ ? Telemetry::LABELS_DNS_TRR_SUCCESS3::Timeout
+ : Telemetry::LABELS_DNS_TRR_SUCCESS3::Bad));
+
+ mConfirmation.RecordTRRStatus(aTrrRequest);
+}
+
+void TRRService::ConfirmationContext::RecordTRRStatus(TRR* aTrrRequest) {
+ nsresult channelStatus = aTrrRequest->ChannelStatus();
+
+ if (OwningObject()->Mode() == nsIDNSService::MODE_TRRONLY) {
+ mLastConfirmationSkipReason = aTrrRequest->SkipReason();
+ mLastConfirmationStatus = channelStatus;
+ }
+
+ if (NS_SUCCEEDED(channelStatus)) {
+ LOG(("TRRService::RecordTRRStatus channel success"));
+ mTRRFailures = 0;
+ return;
+ }
+
+ if (OwningObject()->Mode() != nsIDNSService::MODE_TRRFIRST) {
+ return;
+ }
+
+ // only count failures while in OK state
+ if (State() != CONFIRM_OK) {
+ return;
+ }
+
+ // When TRR retry is enabled, nsHostResolver will trigger Confirmation
+ // immediately upon a lookup failure, so nothing to be done here.
+ // nsHostResolver can assess the success of the lookup considering all the
+ // involved results (A, AAAA) so we let it tell us when to re-Confirm.
+ if (StaticPrefs::network_trr_retry_on_recoverable_errors()) {
+ LOG(("TRRService not counting failures when retry is enabled"));
+ return;
+ }
+
+ mFailureReasons[mTRRFailures % ConfirmationContext::RESULTS_SIZE] =
+ StatusToChar(NS_OK, channelStatus);
+ uint32_t fails = ++mTRRFailures;
+ LOG(("TRRService::RecordTRRStatus fails=%u", fails));
+
+ if (fails >= StaticPrefs::network_trr_max_fails()) {
+ LOG(("TRRService had %u failures in a row\n", fails));
+ // When several failures occur we trigger a confirmation causing
+ // us to transition into the CONFIRM_TRYING_OK state.
+ // Only after the confirmation fails do we finally go into CONFIRM_FAILED
+ // and start skipping TRR.
+
+ // Trigger a confirmation immediately.
+ // If it fails, it will fire off a timer to start retrying again.
+ HandleEvent(ConfirmationEvent::FailedLookups);
+ }
+}
+
+void TRRService::ConfirmationContext::RecordEvent(
+ const char* aReason, const MutexSingleWriterAutoLock&) {
+ // Reset the confirmation context attributes
+ // Only resets the attributes that we keep for telemetry purposes.
+ auto reset = [&]() {
+ mAttemptCount = 0;
+ mNetworkId.Truncate();
+ mFirstRequestTime = TimeStamp();
+ mContextChangeReason.Assign(aReason);
+ mTrigger.Truncate();
+ mFailedLookups.Truncate();
+
+ mRetryInterval = StaticPrefs::network_trr_retry_timeout_ms();
+ };
+
+ if (mAttemptCount == 0) {
+ // XXX: resetting everything might not be the best thing here, even if the
+ // context changes, because there might still be a confirmation pending.
+ // But cancelling and retrying that confirmation might just make the whole
+ // confirmation longer for no reason.
+ reset();
+ return;
+ }
+
+ Telemetry::EventID eventType =
+ Telemetry::EventID::NetworkDns_Trrconfirmation_Context;
+
+ nsAutoCString results;
+ static_assert(RESULTS_SIZE < 64);
+
+ // mResults is a circular buffer ending at mAttemptCount
+ if (mAttemptCount <= RESULTS_SIZE) {
+ // We have fewer attempts than the size of the buffer, so all of the
+ // results are in the buffer.
+ results.Append(nsDependentCSubstring(mResults, mAttemptCount));
+ } else {
+ // More attempts than the buffer size.
+ // That means past RESULTS_SIZE attempts in order are
+ // [posInResults .. end-of-buffer) + [start-of-buffer .. posInResults)
+ uint32_t posInResults = mAttemptCount % RESULTS_SIZE;
+
+ results.Append(nsDependentCSubstring(mResults + posInResults,
+ RESULTS_SIZE - posInResults));
+ results.Append(nsDependentCSubstring(mResults, posInResults));
+ }
+
+ auto extra = Some<nsTArray<mozilla::Telemetry::EventExtraEntry>>({
+ Telemetry::EventExtraEntry{"trigger"_ns, mTrigger},
+ Telemetry::EventExtraEntry{"contextReason"_ns, mContextChangeReason},
+ Telemetry::EventExtraEntry{"attemptCount"_ns,
+ nsPrintfCString("%u", mAttemptCount)},
+ Telemetry::EventExtraEntry{"results"_ns, results},
+ Telemetry::EventExtraEntry{
+ "time"_ns,
+ nsPrintfCString(
+ "%f",
+ !mFirstRequestTime.IsNull()
+ ? (TimeStamp::Now() - mFirstRequestTime).ToMilliseconds()
+ : 0.0)},
+ Telemetry::EventExtraEntry{"networkID"_ns, mNetworkId},
+ Telemetry::EventExtraEntry{"captivePortal"_ns,
+ nsPrintfCString("%i", mCaptivePortalStatus)},
+ });
+
+ if (mTrigger.Equals("failed-lookups"_ns)) {
+ extra.ref().AppendElement(
+ Telemetry::EventExtraEntry{"failedLookups"_ns, mFailedLookups});
+ }
+
+ enum ConfirmationState state = mState;
+ Telemetry::RecordEvent(eventType, mozilla::Some(nsPrintfCString("%u", state)),
+ extra);
+
+ reset();
+}
+
+void TRRService::ConfirmationContext::RequestCompleted(
+ nsresult aLookupStatus, nsresult aChannelStatus) {
+ mResults[mAttemptCount % RESULTS_SIZE] =
+ StatusToChar(aLookupStatus, aChannelStatus);
+ mAttemptCount++;
+}
+
+void TRRService::ConfirmationContext::CompleteConfirmation(nsresult aStatus,
+ TRR* aTRRRequest) {
+ {
+ MutexSingleWriterAutoLock lock(OwningObject()->mLock);
+ // Ignore confirmations that dont match the pending task.
+ if (mTask != aTRRRequest) {
+ return;
+ }
+ MOZ_ASSERT(State() == CONFIRM_TRYING_OK ||
+ State() == CONFIRM_TRYING_FAILED);
+ if (State() != CONFIRM_TRYING_OK && State() != CONFIRM_TRYING_FAILED) {
+ return;
+ }
+
+ RequestCompleted(aStatus, aTRRRequest->ChannelStatus());
+ mLastConfirmationSkipReason = aTRRRequest->SkipReason();
+ mLastConfirmationStatus = aTRRRequest->ChannelStatus();
+
+ MOZ_ASSERT(mTask);
+ if (NS_SUCCEEDED(aStatus)) {
+ HandleEvent(ConfirmationEvent::ConfirmOK, lock);
+ } else {
+ HandleEvent(ConfirmationEvent::ConfirmFail, lock);
+ }
+
+ if (State() == CONFIRM_OK) {
+ // Record event and start new confirmation context
+ RecordEvent("success", lock);
+ }
+ LOG(("TRRService finishing confirmation test %s %d %X\n",
+ OwningObject()->mPrivateURI.get(), State(), (unsigned int)aStatus));
+ }
+
+ if (State() == CONFIRM_OK) {
+ // A fresh confirmation means previous blocked entries might not
+ // be valid anymore.
+ auto bl = OwningObject()->mTRRBLStorage.Lock();
+ bl->Clear();
+ } else {
+ MOZ_ASSERT(State() == CONFIRM_FAILED);
+ }
+
+ Telemetry::Accumulate(Telemetry::DNS_TRR_NS_VERFIFIED3,
+ TRRService::ProviderKey(), (State() == CONFIRM_OK));
+}
+
+AHostResolver::LookupStatus TRRService::CompleteLookup(
+ nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
+ const nsACString& aOriginSuffix, TRRSkippedReason aReason,
+ TRR* aTRRRequest) {
+ // this is an NS check for the TRR blocklist or confirmationNS check
+
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), NS_IsMainThread() || IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+ MOZ_ASSERT(!rec);
+
+ RefPtr<AddrInfo> newRRSet(aNewRRSet);
+ MOZ_ASSERT(newRRSet && newRRSet->TRRType() == TRRTYPE_NS);
+
+ if (aTRRRequest->Purpose() == TRR::Confirmation) {
+ mConfirmation.CompleteConfirmation(status, aTRRRequest);
+ return LOOKUP_OK;
+ }
+
+ if (aTRRRequest->Purpose() == TRR::Blocklist) {
+ if (NS_SUCCEEDED(status)) {
+ LOG(("TRR verified %s to be fine!\n", newRRSet->Hostname().get()));
+ } else {
+ LOG(("TRR says %s doesn't resolve as NS!\n", newRRSet->Hostname().get()));
+ AddToBlocklist(newRRSet->Hostname(), aOriginSuffix, pb, false);
+ }
+ return LOOKUP_OK;
+ }
+
+ MOZ_ASSERT_UNREACHABLE(
+ "TRRService::CompleteLookup called for unexpected request");
+ return LOOKUP_OK;
+}
+
+AHostResolver::LookupStatus TRRService::CompleteLookupByType(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ mozilla::net::TRRSkippedReason aReason, uint32_t aTtl, bool aPb) {
+ return LOOKUP_OK;
+}
+
+NS_IMETHODIMP TRRService::OnProxyConfigChanged() {
+ LOG(("TRRService::OnProxyConfigChanged"));
+
+ nsAutoCString uri;
+ GetURI(uri);
+ AsyncCreateTRRConnectionInfo(uri);
+
+ return NS_OK;
+}
+
+void TRRService::InitTRRConnectionInfo() {
+ if (XRE_IsParentProcess()) {
+ TRRServiceBase::InitTRRConnectionInfo();
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ TRRServiceChild* child = TRRServiceChild::GetSingleton();
+ if (child && child->CanSend()) {
+ LOG(("TRRService::SendInitTRRConnectionInfo"));
+ Unused << child->SendInitTRRConnectionInfo();
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/dns/TRRService.h b/netwerk/dns/TRRService.h
new file mode 100644
index 0000000000..3283a8ea06
--- /dev/null
+++ b/netwerk/dns/TRRService.h
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 8; 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 TRRService_h_
+#define TRRService_h_
+
+#include "mozilla/DataMutex.h"
+#include "nsHostResolver.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+#include "TRRServiceBase.h"
+#include "nsICaptivePortalService.h"
+#include "nsTHashSet.h"
+#include "TRR.h"
+
+class nsDNSService;
+class nsIPrefBranch;
+class nsINetworkLinkService;
+class nsIObserverService;
+
+namespace mozilla {
+namespace net {
+
+class TRRServiceChild;
+class TRRServiceParent;
+
+class TRRService : public TRRServiceBase,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public AHostResolver,
+ public SingleWriterLockOwner {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIPROXYCONFIGCHANGEDCALLBACK
+
+ TRRService();
+ static TRRService* Get();
+
+ bool OnWritingThread() const override { return NS_IsMainThread(); }
+
+ nsresult Init();
+ nsresult Start();
+ bool Enabled(nsIRequest::TRRMode aRequestMode = nsIRequest::TRR_DEFAULT_MODE);
+ bool IsConfirmed() { return mConfirmation.State() == CONFIRM_OK; }
+ uint32_t ConfirmationState() { return mConfirmation.State(); }
+
+ void GetURI(nsACString& result) override;
+ nsresult GetCredentials(nsCString& result);
+ uint32_t GetRequestTimeout();
+ void RetryTRRConfirm();
+
+ LookupStatus CompleteLookup(nsHostRecord*, nsresult, mozilla::net::AddrInfo*,
+ bool pb, const nsACString& aOriginSuffix,
+ TRRSkippedReason aReason,
+ TRR* aTrrRequest) override;
+ LookupStatus CompleteLookupByType(nsHostRecord*, nsresult,
+ mozilla::net::TypeRecordResultType&,
+ TRRSkippedReason, uint32_t,
+ bool pb) override;
+ void AddToBlocklist(const nsACString& host, const nsACString& originSuffix,
+ bool privateBrowsing, bool aParentsToo);
+ bool IsTemporarilyBlocked(const nsACString& aHost,
+ const nsACString& aOriginSuffix,
+ bool aPrivateBrowsing, bool aParentsToo);
+ bool IsExcludedFromTRR(const nsACString& aHost);
+
+ bool MaybeBootstrap(const nsACString& possible, nsACString& result);
+ void RecordTRRStatus(TRR* aTrrRequest);
+ bool ParentalControlEnabled() const { return mParentalControlEnabled; }
+
+ nsresult DispatchTRRRequest(TRR* aTrrRequest);
+ already_AddRefed<nsIThread> TRRThread();
+ bool IsOnTRRThread();
+
+ bool IsUsingAutoDetectedURL() { return mURISetByDetection; }
+
+ void SetHeuristicDetectionResult(TRRSkippedReason aValue) {
+ mHeuristicDetectionValue = aValue;
+ }
+ TRRSkippedReason GetHeuristicDetectionResult() {
+ return mHeuristicDetectionValue;
+ }
+
+ nsresult LastConfirmationStatus() {
+ return mConfirmation.LastConfirmationStatus();
+ }
+ TRRSkippedReason LastConfirmationSkipReason() {
+ return mConfirmation.LastConfirmationSkipReason();
+ }
+
+ // Returns a reference to a static string identifying the current DoH server
+ // If the DoH server is not one of the built-in ones it will return "(other)"
+ static const nsCString& ProviderKey();
+ static void SetProviderDomain(const nsACString& aTRRDomain);
+ // Only called when TRR mode changed.
+ static void SetCurrentTRRMode(nsIDNSService::ResolverMode aMode);
+
+ void InitTRRConnectionInfo() override;
+
+ void DontUseTRRThread() { mDontUseTRRThread = true; }
+
+ private:
+ virtual ~TRRService();
+
+ friend class TRRServiceChild;
+ friend class TRRServiceParent;
+ static void AddObserver(nsIObserver* aObserver,
+ nsIObserverService* aObserverService = nullptr);
+ static bool CheckCaptivePortalIsPassed();
+ static bool GetParentalControlEnabledInternal();
+ static bool CheckPlatformDNSStatus(nsINetworkLinkService* aLinkService);
+
+ nsresult ReadPrefs(const char* name);
+ void GetPrefBranch(nsIPrefBranch** result);
+ friend class ::nsDNSService;
+ void SetDetectedTrrURI(const nsACString& aURI);
+
+ bool IsDomainBlocked(const nsACString& aHost, const nsACString& aOriginSuffix,
+ bool aPrivateBrowsing);
+ bool IsExcludedFromTRR_unlocked(const nsACString& aHost);
+
+ void RebuildSuffixList(nsTArray<nsCString>&& aSuffixList);
+
+ nsresult DispatchTRRRequestInternal(TRR* aTrrRequest, bool aWithLock);
+ already_AddRefed<nsIThread> TRRThread_locked();
+ already_AddRefed<nsIThread> MainThreadOrTRRThread(bool aWithLock = true);
+
+ // This method will process the URI and try to set mPrivateURI to that value.
+ // Will return true if performed the change (if the value was different)
+ // or false if mPrivateURI already had that value.
+ bool MaybeSetPrivateURI(const nsACString& aURI) override;
+ void ClearEntireCache();
+
+ virtual void ReadEtcHostsFile() override;
+ void AddEtcHosts(const nsTArray<nsCString>&);
+
+ bool mInitialized{false};
+ MutexSingleWriter mLock;
+
+ nsCString mPrivateCred; // main thread only
+ nsCString mConfirmationNS MOZ_GUARDED_BY(mLock){"example.com"_ns};
+ nsCString mBootstrapAddr MOZ_GUARDED_BY(mLock);
+
+ Atomic<bool, Relaxed> mCaptiveIsPassed{
+ false}; // set when captive portal check is passed
+ Atomic<bool, Relaxed> mShutdown{false};
+ Atomic<bool, Relaxed> mDontUseTRRThread{false};
+
+ // TRR Blocklist storage
+ // mTRRBLStorage is only modified on the main thread, but we query whether it
+ // is initialized or not off the main thread as well. Therefore we need to
+ // lock while creating it and while accessing it off the main thread.
+ DataMutex<nsTHashMap<nsCStringHashKey, int32_t>> mTRRBLStorage{
+ "DataMutex::TRRBlocklist"};
+
+ // A set of domains that we should not use TRR for.
+ nsTHashSet<nsCString> mExcludedDomains MOZ_GUARDED_BY(mLock);
+ nsTHashSet<nsCString> mDNSSuffixDomains MOZ_GUARDED_BY(mLock);
+ nsTHashSet<nsCString> mEtcHostsDomains MOZ_GUARDED_BY(mLock);
+
+ // The result of the TRR heuristic detection
+ TRRSkippedReason mHeuristicDetectionValue = nsITRRSkipReason::TRR_UNSET;
+
+ enum class ConfirmationEvent {
+ Init,
+ PrefChange,
+ ConfirmationRetry,
+ FailedLookups,
+ RetryTRR,
+ URIChange,
+ CaptivePortalConnectivity,
+ NetworkUp,
+ ConfirmOK,
+ ConfirmFail,
+ };
+
+ // (FailedLookups/RetryTRR/URIChange/NetworkUp)
+ // +---------------------------+
+ // +-----------+ | |
+ // | (Init) | +------v---------+ +-+--+
+ // | | TRR turned on | | (ConfirmOK) | |
+ // | OFF +---------------> TRY-OK +---------------> OK |
+ // | | (PrefChange) | | | |
+ // +-----^-----+ +^-^----+--------+ +-^--+
+ // | (PrefChange/CP) | | | |
+ // TRR + +------------------+ | | |
+ // off | | +----+ |(ConfirmFail) |(ConfirmOK)
+ // (Pref)| | | | |
+ // +---------+-+ | | |
+ // | | (CPConn) | +-------v--------+ +-+---------+
+ // | ANY-STATE | (NetworkUp)| | | timer | |
+ // | | (URIChange)+-+ FAIL +---------------> TRY-FAIL |
+ // +-----+-----+ | | (Confirmation | |
+ // | +------^---------+ Retry) +------+----+
+ // | (PrefChange) | |
+ // | TRR_ONLY mode or +--------------------------------+
+ // | confirmationNS = skip (ConfirmFail)
+ // +-----v-----+
+ // | |
+ // | DISABLED |
+ // | |
+ // +-----------+
+ //
+ enum ConfirmationState {
+ CONFIRM_OFF = 0,
+ CONFIRM_TRYING_OK = 1,
+ CONFIRM_OK = 2,
+ CONFIRM_FAILED = 3,
+ CONFIRM_TRYING_FAILED = 4,
+ CONFIRM_DISABLED = 5,
+ };
+
+ class ConfirmationContext final : public nsITimerCallback, public nsINamed {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ private:
+ static const size_t RESULTS_SIZE = 32;
+
+ RefPtr<TRR> mTask;
+ nsCOMPtr<nsITimer> mTimer;
+ uint32_t mRetryInterval = 125; // milliseconds until retry
+ // The number of TRR requests that failed in a row.
+ Atomic<uint32_t, Relaxed> mTRRFailures{0};
+
+ // This buffer holds consecutive TRR failures reported by calling
+ // RecordTRRStatus(). It is only meant for reporting event telemetry.
+ char mFailureReasons[RESULTS_SIZE] = {0};
+
+ // The number of confirmation retries.
+ uint32_t mAttemptCount = 0;
+
+ // The results of past confirmation attempts.
+ // This is circular buffer ending at mAttemptCount.
+ char mResults[RESULTS_SIZE] = {0};
+
+ // Time when first confirmation started. Needed so we can
+ // record the time from start to confirmed.
+ TimeStamp mFirstRequestTime;
+ // The network ID at the start of the last confirmation attempt
+ nsCString mNetworkId;
+ // Captive portal status at the time of recording.
+ int32_t mCaptivePortalStatus = nsICaptivePortalService::UNKNOWN;
+
+ // The reason the confirmation context changed.
+ nsCString mContextChangeReason;
+
+ // What triggered the confirmation
+ nsCString mTrigger;
+
+ // String representation of consecutive failed lookups that triggered
+ // confirmation.
+ nsCString mFailedLookups;
+
+ Atomic<TRRSkippedReason, Relaxed> mLastConfirmationSkipReason{
+ nsITRRSkipReason::TRR_UNSET};
+ Atomic<nsresult, Relaxed> mLastConfirmationStatus{NS_OK};
+
+ void SetState(enum ConfirmationState aNewState);
+
+ public:
+ // Called when a confirmation completes successfully or when the
+ // confirmation context changes.
+ void RecordEvent(const char* aReason, const MutexSingleWriterAutoLock&);
+
+ // Called when a confirmation request is completed. The status is recorded
+ // in the results.
+ void RequestCompleted(nsresult aLookupStatus, nsresult aChannelStatus);
+
+ enum ConfirmationState State() { return mState; }
+
+ void CompleteConfirmation(nsresult aStatus, TRR* aTrrRequest);
+
+ void RecordTRRStatus(TRR* aTrrRequest);
+
+ // Returns true when handling the event caused a new confirmation task to be
+ // dispatched.
+ bool HandleEvent(ConfirmationEvent aEvent);
+ bool HandleEvent(ConfirmationEvent aEvent,
+ const MutexSingleWriterAutoLock&);
+
+ void SetCaptivePortalStatus(int32_t aStatus) {
+ mCaptivePortalStatus = aStatus;
+ }
+
+ TRRSkippedReason LastConfirmationSkipReason() {
+ return mLastConfirmationSkipReason;
+ }
+ nsresult LastConfirmationStatus() { return mLastConfirmationStatus; }
+
+ uintptr_t TaskAddr() { return uintptr_t(mTask.get()); }
+
+ private:
+ // Since the ConfirmationContext is embedded in the TRRService object
+ // we can easily get a pointer to the TRRService. ConfirmationContext
+ // delegates AddRef/Release calls to the owning object since they are
+ // guaranteed to have the same lifetime.
+ TRRService* OwningObject() {
+ return reinterpret_cast<TRRService*>(
+ reinterpret_cast<uint8_t*>(this) -
+ offsetof(TRRService, mConfirmation) -
+ offsetof(ConfirmationWrapper, mConfirmation));
+ }
+
+ Atomic<enum ConfirmationState, Relaxed> mState{CONFIRM_OFF};
+
+ // TRRService needs to be a friend class because it needs to access the
+ // destructor.
+ friend class TRRService;
+ ~ConfirmationContext() = default;
+ };
+
+ // Because TRRService needs to be a friend class to ConfirmationContext that
+ // means it can access member variables. In order to properly separate logic
+ // and prevent direct access to its member variables we embed it in a wrapper
+ // class.
+ class ConfirmationWrapper {
+ public:
+ // Called when a confirmation completes successfully or when the
+ // confirmation context changes.
+ void RecordEvent(const char* aReason,
+ const MutexSingleWriterAutoLock& aLock) {
+ mConfirmation.RecordEvent(aReason, aLock);
+ }
+
+ // Called when a confirmation request is completed. The status is recorded
+ // in the results.
+ void RequestCompleted(nsresult aLookupStatus, nsresult aChannelStatus) {
+ mConfirmation.RequestCompleted(aLookupStatus, aChannelStatus);
+ }
+
+ enum ConfirmationState State() { return mConfirmation.State(); }
+
+ void CompleteConfirmation(nsresult aStatus, TRR* aTrrRequest) {
+ mConfirmation.CompleteConfirmation(aStatus, aTrrRequest);
+ }
+
+ void RecordTRRStatus(TRR* aTrrRequest) {
+ mConfirmation.RecordTRRStatus(aTrrRequest);
+ }
+
+ bool HandleEvent(ConfirmationEvent aEvent) {
+ return mConfirmation.HandleEvent(aEvent);
+ }
+
+ bool HandleEvent(ConfirmationEvent aEvent,
+ const MutexSingleWriterAutoLock& lock) {
+ return mConfirmation.HandleEvent(aEvent, lock);
+ }
+
+ void SetCaptivePortalStatus(int32_t aStatus) {
+ mConfirmation.SetCaptivePortalStatus(aStatus);
+ }
+
+ TRRSkippedReason LastConfirmationSkipReason() {
+ return mConfirmation.LastConfirmationSkipReason();
+ }
+ nsresult LastConfirmationStatus() {
+ return mConfirmation.LastConfirmationStatus();
+ }
+
+ private:
+ friend TRRService* ConfirmationContext::OwningObject();
+ ConfirmationContext mConfirmation;
+ };
+
+ ConfirmationWrapper mConfirmation;
+
+ bool mParentalControlEnabled{false};
+ // This is used to track whether a confirmation was triggered by a URI change,
+ // so we don't trigger another one just because other prefs have changed.
+ bool mConfirmationTriggered{false};
+ nsCOMPtr<nsINetworkLinkService> mLinkService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // TRRService_h_
diff --git a/netwerk/dns/TRRServiceBase.cpp b/netwerk/dns/TRRServiceBase.cpp
new file mode 100644
index 0000000000..943edc41dd
--- /dev/null
+++ b/netwerk/dns/TRRServiceBase.cpp
@@ -0,0 +1,399 @@
+/* -*- 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 "TRRServiceBase.h"
+
+#include "TRRService.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ScopeExit.h"
+#include "nsHostResolver.h"
+#include "nsNetUtil.h"
+#include "nsIOService.h"
+#include "nsIDNSService.h"
+#include "nsIProxyInfo.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "AlternateServices.h"
+#include "ProxyConfigLookup.h"
+// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
+#include "DNSLogging.h"
+
+#if defined(XP_WIN)
+# include <shlobj.h> // for SHGetSpecialFolderPathA
+#endif // XP_WIN
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(TRRServiceBase, nsIProxyConfigChangedCallback)
+
+TRRServiceBase::TRRServiceBase()
+ : mDefaultTRRConnectionInfo("DataMutex::mDefaultTRRConnectionInfo") {}
+
+TRRServiceBase::~TRRServiceBase() {
+ if (mTRRConnectionInfoInited) {
+ UnregisterProxyChangeListener();
+ }
+}
+
+void TRRServiceBase::ProcessURITemplate(nsACString& aURI) {
+ // URI Template, RFC 6570.
+ if (aURI.IsEmpty()) {
+ return;
+ }
+ nsAutoCString scheme;
+ nsCOMPtr<nsIIOService> ios(do_GetIOService());
+ if (ios) {
+ ios->ExtractScheme(aURI, scheme);
+ }
+ if (!scheme.Equals("https")) {
+ LOG(("TRRService TRR URI %s is not https. Not used.\n",
+ PromiseFlatCString(aURI).get()));
+ aURI.Truncate();
+ return;
+ }
+
+ // cut off everything from "{" to "}" sequences (potentially multiple),
+ // as a crude conversion from template into URI.
+ nsAutoCString uri(aURI);
+
+ do {
+ nsCCharSeparatedTokenizer openBrace(uri, '{');
+ if (openBrace.hasMoreTokens()) {
+ // the 'nextToken' is the left side of the open brace (or full uri)
+ nsAutoCString prefix(openBrace.nextToken());
+
+ // if there is an open brace, there's another token
+ const nsACString& endBrace = openBrace.nextToken();
+ nsCCharSeparatedTokenizer closeBrace(endBrace, '}');
+ if (closeBrace.hasMoreTokens()) {
+ // there is a close brace as well, make a URI out of the prefix
+ // and the suffix
+ closeBrace.nextToken();
+ nsAutoCString suffix(closeBrace.nextToken());
+ uri = prefix + suffix;
+ } else {
+ // no (more) close brace
+ break;
+ }
+ } else {
+ // no (more) open brace
+ break;
+ }
+ } while (true);
+
+ aURI = uri;
+}
+
+void TRRServiceBase::CheckURIPrefs() {
+ mURISetByDetection = false;
+
+ if (StaticPrefs::network_trr_use_ohttp() && !mOHTTPURIPref.IsEmpty()) {
+ MaybeSetPrivateURI(mOHTTPURIPref);
+ return;
+ }
+
+ // The user has set a custom URI so it takes precedence.
+ if (!mURIPref.IsEmpty()) {
+ MaybeSetPrivateURI(mURIPref);
+ return;
+ }
+
+ // Check if the rollout addon has set a pref.
+ if (!mRolloutURIPref.IsEmpty()) {
+ MaybeSetPrivateURI(mRolloutURIPref);
+ return;
+ }
+
+ // Otherwise just use the default value.
+ MaybeSetPrivateURI(mDefaultURIPref);
+}
+
+// static
+nsIDNSService::ResolverMode ModeFromPrefs(
+ nsIDNSService::ResolverMode& aTRRModePrefValue) {
+ // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved,
+ // 5 - explicit off
+
+ auto processPrefValue = [](uint32_t value) -> nsIDNSService::ResolverMode {
+ if (value == nsIDNSService::MODE_RESERVED1 ||
+ value == nsIDNSService::MODE_RESERVED4 ||
+ value > nsIDNSService::MODE_TRROFF) {
+ return nsIDNSService::MODE_TRROFF;
+ }
+ return static_cast<nsIDNSService::ResolverMode>(value);
+ };
+
+ uint32_t tmp;
+ if (NS_FAILED(Preferences::GetUint("network.trr.mode", &tmp))) {
+ tmp = 0;
+ }
+ nsIDNSService::ResolverMode modeFromPref = processPrefValue(tmp);
+ aTRRModePrefValue = modeFromPref;
+
+ if (modeFromPref != nsIDNSService::MODE_NATIVEONLY) {
+ return modeFromPref;
+ }
+
+ if (NS_FAILED(Preferences::GetUint(kRolloutModePref, &tmp))) {
+ tmp = 0;
+ }
+ modeFromPref = processPrefValue(tmp);
+
+ return modeFromPref;
+}
+
+void TRRServiceBase::OnTRRModeChange() {
+ uint32_t oldMode = mMode;
+ // This is the value of the pref "network.trr.mode"
+ nsIDNSService::ResolverMode trrModePrefValue;
+ mMode = ModeFromPrefs(trrModePrefValue);
+ if (mMode != oldMode) {
+ LOG(("TRR Mode changed from %d to %d", oldMode, int(mMode)));
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, NS_NETWORK_TRR_MODE_CHANGED_TOPIC, nullptr);
+ }
+
+ TRRService::SetCurrentTRRMode(trrModePrefValue);
+ }
+
+ static bool readHosts = false;
+ if ((mMode == nsIDNSService::MODE_TRRFIRST ||
+ mMode == nsIDNSService::MODE_TRRONLY) &&
+ !readHosts) {
+ readHosts = true;
+ ReadEtcHostsFile();
+ }
+}
+
+void TRRServiceBase::OnTRRURIChange() {
+ Preferences::GetCString("network.trr.uri", mURIPref);
+ Preferences::GetCString(kRolloutURIPref, mRolloutURIPref);
+ Preferences::GetCString("network.trr.default_provider_uri", mDefaultURIPref);
+ Preferences::GetCString("network.trr.ohttp.uri", mOHTTPURIPref);
+
+ CheckURIPrefs();
+}
+
+static already_AddRefed<nsHttpConnectionInfo> CreateConnInfoHelper(
+ nsIURI* aURI, nsIProxyInfo* aProxyInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoCString host;
+ nsAutoCString scheme;
+ nsAutoCString username;
+ int32_t port = -1;
+ bool isHttps = aURI->SchemeIs("https");
+
+ nsresult rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ // Just a warning here because some nsIURIs do not implement this method.
+ if (NS_WARN_IF(NS_FAILED(aURI->GetUsername(username)))) {
+ LOG(("Failed to get username for aURI(%s)",
+ aURI->GetSpecOrDefault().get()));
+ }
+
+ gHttpHandler->MaybeAddAltSvcForTesting(aURI, username, false, nullptr,
+ OriginAttributes());
+
+ nsCOMPtr<nsProxyInfo> proxyInfo = do_QueryInterface(aProxyInfo);
+ RefPtr<nsHttpConnectionInfo> connInfo = new nsHttpConnectionInfo(
+ host, port, ""_ns, username, proxyInfo, OriginAttributes(), isHttps);
+ bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo);
+ bool http3Allowed = proxyInfo ? proxyInfo->IsDirect() : true;
+
+ RefPtr<AltSvcMapping> mapping;
+ if ((http2Allowed || http3Allowed) &&
+ AltSvcMapping::AcceptableProxy(proxyInfo) &&
+ (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
+ (mapping = gHttpHandler->GetAltServiceMapping(
+ scheme, host, port, false, OriginAttributes(), http2Allowed,
+ http3Allowed))) {
+ mapping->GetConnectionInfo(getter_AddRefs(connInfo), proxyInfo,
+ OriginAttributes());
+ }
+
+ return connInfo.forget();
+}
+
+void TRRServiceBase::InitTRRConnectionInfo() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (mTRRConnectionInfoInited) {
+ return;
+ }
+
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "TRRServiceBase::InitTRRConnectionInfo",
+ [self = RefPtr{this}]() { self->InitTRRConnectionInfo(); }));
+ return;
+ }
+
+ LOG(("TRRServiceBase::InitTRRConnectionInfo"));
+ nsAutoCString uri;
+ GetURI(uri);
+ AsyncCreateTRRConnectionInfoInternal(uri);
+}
+
+void TRRServiceBase::AsyncCreateTRRConnectionInfo(const nsACString& aURI) {
+ LOG(
+ ("TRRServiceBase::AsyncCreateTRRConnectionInfo "
+ "mTRRConnectionInfoInited=%d",
+ bool(mTRRConnectionInfoInited)));
+ if (!mTRRConnectionInfoInited) {
+ return;
+ }
+
+ AsyncCreateTRRConnectionInfoInternal(aURI);
+}
+
+void TRRServiceBase::AsyncCreateTRRConnectionInfoInternal(
+ const nsACString& aURI) {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ SetDefaultTRRConnectionInfo(nullptr);
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> dnsURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(dnsURI), aURI);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ rv = ProxyConfigLookup::Create(
+ [self = RefPtr{this}, uri(dnsURI)](nsIProxyInfo* aProxyInfo,
+ nsresult aStatus) mutable {
+ if (NS_FAILED(aStatus)) {
+ self->SetDefaultTRRConnectionInfo(nullptr);
+ return;
+ }
+
+ RefPtr<nsHttpConnectionInfo> connInfo =
+ CreateConnInfoHelper(uri, aProxyInfo);
+ self->SetDefaultTRRConnectionInfo(connInfo);
+ if (!self->mTRRConnectionInfoInited) {
+ self->mTRRConnectionInfoInited = true;
+ self->RegisterProxyChangeListener();
+ }
+ },
+ dnsURI, 0, nullptr);
+
+ // mDefaultTRRConnectionInfo is set to nullptr at the beginning of this
+ // method, so we don't really care aobut the |rv| here. If it's failed,
+ // mDefaultTRRConnectionInfo stays as nullptr and we'll create a new
+ // connection info in TRRServiceChannel again.
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+already_AddRefed<nsHttpConnectionInfo> TRRServiceBase::TRRConnectionInfo() {
+ RefPtr<nsHttpConnectionInfo> connInfo;
+ {
+ auto lock = mDefaultTRRConnectionInfo.Lock();
+ connInfo = *lock;
+ }
+ return connInfo.forget();
+}
+
+void TRRServiceBase::SetDefaultTRRConnectionInfo(
+ nsHttpConnectionInfo* aConnInfo) {
+ LOG(("TRRService::SetDefaultTRRConnectionInfo aConnInfo=%s",
+ aConnInfo ? aConnInfo->HashKey().get() : "none"));
+ {
+ auto lock = mDefaultTRRConnectionInfo.Lock();
+ lock.ref() = aConnInfo;
+ }
+}
+
+void TRRServiceBase::RegisterProxyChangeListener() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+ if (!pps) {
+ return;
+ }
+
+ pps->AddProxyConfigCallback(this);
+}
+
+void TRRServiceBase::UnregisterProxyChangeListener() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+ if (!pps) {
+ return;
+ }
+
+ pps->RemoveProxyConfigCallback(this);
+}
+
+void TRRServiceBase::DoReadEtcHostsFile(ParsingCallback aCallback) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!StaticPrefs::network_trr_exclude_etc_hosts()) {
+ return;
+ }
+
+ auto readHostsTask = [aCallback]() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not run on the main thread");
+#if defined(XP_WIN)
+ // Inspired by libevent/evdns.c
+ // Windows is a little coy about where it puts its configuration
+ // files. Sure, they're _usually_ in C:\windows\system32, but
+ // there's no reason in principle they couldn't be in
+ // W:\hoboken chicken emergency
+
+ nsCString path;
+ path.SetLength(MAX_PATH + 1);
+ if (!SHGetSpecialFolderPathA(NULL, path.BeginWriting(), CSIDL_SYSTEM,
+ false)) {
+ LOG(("Calling SHGetSpecialFolderPathA failed"));
+ return;
+ }
+
+ path.SetLength(strlen(path.get()));
+ path.Append("\\drivers\\etc\\hosts");
+#else
+ nsAutoCString path("/etc/hosts"_ns);
+#endif
+
+ LOG(("Reading hosts file at %s", path.get()));
+ rust_parse_etc_hosts(&path, aCallback);
+ };
+
+ Unused << NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("Read /etc/hosts file", readHostsTask),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRServiceBase.h b/netwerk/dns/TRRServiceBase.h
new file mode 100644
index 0000000000..a7f85fc95d
--- /dev/null
+++ b/netwerk/dns/TRRServiceBase.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; 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 TRRServiceBase_h_
+#define TRRServiceBase_h_
+
+#include "mozilla/Atomics.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/net/rust_helper.h"
+#include "nsString.h"
+#include "nsIDNSService.h"
+#include "nsIProtocolProxyService2.h"
+
+class nsICancelable;
+class nsIProxyInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnectionInfo;
+
+static const char kRolloutURIPref[] = "doh-rollout.uri";
+static const char kRolloutModePref[] = "doh-rollout.mode";
+
+class TRRServiceBase : public nsIProxyConfigChangedCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ TRRServiceBase();
+ nsIDNSService::ResolverMode Mode() { return mMode; }
+ virtual void GetURI(nsACString& result) = 0;
+ already_AddRefed<nsHttpConnectionInfo> TRRConnectionInfo();
+ // Called to initialize the connection info. Once the connection info is
+ // created first time, mTRRConnectionInfoInited will be set to true.
+ virtual void InitTRRConnectionInfo();
+ bool TRRConnectionInfoInited() const { return mTRRConnectionInfoInited; }
+
+ protected:
+ virtual ~TRRServiceBase();
+
+ virtual bool MaybeSetPrivateURI(const nsACString& aURI) = 0;
+ void ProcessURITemplate(nsACString& aURI);
+ // Checks the network.trr.uri or the doh-rollout.uri prefs and sets the URI
+ // in order of preference:
+ // 1. The value of network.trr.uri if it is not the default one, meaning
+ // is was set by an explicit user action
+ // 2. The value of doh-rollout.uri if it exists
+ // this is set by the rollout addon
+ // 3. The default value of network.trr.uri
+ void CheckURIPrefs();
+
+ void OnTRRModeChange();
+ void OnTRRURIChange();
+
+ void DoReadEtcHostsFile(ParsingCallback aCallback);
+ virtual void ReadEtcHostsFile() = 0;
+ // Called to create a connection info that will be used by TRRServiceChannel.
+ // Note that when this function is called, mDefaultTRRConnectionInfo will be
+ // set to null to invalidate the connection info.
+ // When the connection info is created, SetDefaultTRRConnectionInfo() is
+ // called to set the result to mDefaultTRRConnectionInfo.
+ // Note that this method does nothing when mTRRConnectionInfoInited is false.
+ // We want to starting updating the connection info after it's create first
+ // time.
+ void AsyncCreateTRRConnectionInfo(const nsACString& aURI);
+ void AsyncCreateTRRConnectionInfoInternal(const nsACString& aURI);
+ virtual void SetDefaultTRRConnectionInfo(nsHttpConnectionInfo* aConnInfo);
+ void RegisterProxyChangeListener();
+ void UnregisterProxyChangeListener();
+
+ nsCString mPrivateURI; // protected by mMutex
+ // Pref caches should only be used on the main thread.
+ nsCString mURIPref;
+ nsCString mRolloutURIPref;
+ nsCString mDefaultURIPref;
+ nsCString mOHTTPURIPref;
+
+ Atomic<nsIDNSService::ResolverMode, Relaxed> mMode{
+ nsIDNSService::MODE_NATIVEONLY};
+ Atomic<bool, Relaxed> mURISetByDetection{false};
+ Atomic<bool, Relaxed> mTRRConnectionInfoInited{false};
+ DataMutex<RefPtr<nsHttpConnectionInfo>> mDefaultTRRConnectionInfo;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // TRRServiceBase_h_
diff --git a/netwerk/dns/TRRServiceChild.cpp b/netwerk/dns/TRRServiceChild.cpp
new file mode 100644
index 0000000000..37be446f8f
--- /dev/null
+++ b/netwerk/dns/TRRServiceChild.cpp
@@ -0,0 +1,121 @@
+/* -*- 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 "mozilla/net/TRRServiceChild.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsIDNService.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "TRRService.h"
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<nsIDNSService> sDNSService;
+static Atomic<TRRServiceChild*> sTRRServiceChild;
+
+NS_IMPL_ISUPPORTS(TRRServiceChild, nsIObserver, nsISupportsWeakReference)
+
+TRRServiceChild::TRRServiceChild() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sTRRServiceChild = this;
+}
+
+TRRServiceChild::~TRRServiceChild() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sTRRServiceChild = nullptr;
+}
+
+/* static */
+TRRServiceChild* TRRServiceChild::GetSingleton() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return sTRRServiceChild;
+}
+
+void TRRServiceChild::Init(const bool& aCaptiveIsPassed,
+ const bool& aParentalControlEnabled,
+ nsTArray<nsCString>&& aDNSSuffixList) {
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1");
+ sDNSService = dns;
+ ClearOnShutdown(&sDNSService);
+ MOZ_ASSERT(sDNSService);
+
+ TRRService* trrService = TRRService::Get();
+ MOZ_ASSERT(trrService);
+
+ trrService->mCaptiveIsPassed = aCaptiveIsPassed;
+ trrService->mParentalControlEnabled = aParentalControlEnabled;
+ trrService->RebuildSuffixList(std::move(aDNSSuffixList));
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(this, "network:connectivity-service:dns-checks-complete",
+ true);
+ obs->AddObserver(this, "network:connectivity-service:ip-checks-complete",
+ true);
+}
+
+NS_IMETHODIMP
+TRRServiceChild::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "network:connectivity-service:ip-checks-complete") ||
+ !strcmp(aTopic, "network:connectivity-service:dns-checks-complete")) {
+ Unused << SendNotifyNetworkConnectivityServiceObservers(
+ nsPrintfCString("%s-from-socket-process", aTopic));
+ }
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvUpdatePlatformDNSInformation(
+ nsTArray<nsCString>&& aDNSSuffixList) {
+ TRRService::Get()->RebuildSuffixList(std::move(aDNSSuffixList));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvUpdateParentalControlEnabled(
+ const bool& aEnabled) {
+ TRRService::Get()->mParentalControlEnabled = aEnabled;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvClearDNSCache(
+ const bool& aTrrToo) {
+ Unused << sDNSService->ClearCache(aTrrToo);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvSetDetectedTrrURI(
+ const nsCString& aURI) {
+ TRRService::Get()->SetDetectedTrrURI(aURI);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvSetDefaultTRRConnectionInfo(
+ Maybe<HttpConnectionInfoCloneArgs>&& aArgs) {
+ if (!aArgs) {
+ TRRService::Get()->SetDefaultTRRConnectionInfo(nullptr);
+ return IPC_OK();
+ }
+
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aArgs.ref());
+ TRRService::Get()->SetDefaultTRRConnectionInfo(cinfo);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvUpdateEtcHosts(
+ nsTArray<nsCString>&& aHosts) {
+ TRRService::Get()->AddEtcHosts(aHosts);
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRServiceChild.h b/netwerk/dns/TRRServiceChild.h
new file mode 100644
index 0000000000..f9c6136f70
--- /dev/null
+++ b/netwerk/dns/TRRServiceChild.h
@@ -0,0 +1,53 @@
+/* -*- 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_TRRServiceChild_h
+#define mozilla_net_TRRServiceChild_h
+
+#include "mozilla/net/PTRRServiceChild.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+
+namespace net {
+
+class TRRServiceChild : public PTRRServiceChild,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ TRRServiceChild();
+ static TRRServiceChild* GetSingleton();
+
+ void Init(const bool& aCaptiveIsPassed, const bool& aParentalControlEnabled,
+ nsTArray<nsCString>&& aDNSSuffixList);
+ mozilla::ipc::IPCResult RecvNotifyObserver(const nsCString& aTopic,
+ const nsString& aData);
+ mozilla::ipc::IPCResult RecvUpdatePlatformDNSInformation(
+ nsTArray<nsCString>&& aDNSSuffixList);
+ mozilla::ipc::IPCResult RecvUpdateParentalControlEnabled(
+ const bool& aEnabled);
+ mozilla::ipc::IPCResult RecvClearDNSCache(const bool& aTrrToo);
+ mozilla::ipc::IPCResult RecvSetDetectedTrrURI(const nsCString& aURI);
+ mozilla::ipc::IPCResult RecvSetDefaultTRRConnectionInfo(
+ Maybe<HttpConnectionInfoCloneArgs>&& aArgs);
+ mozilla::ipc::IPCResult RecvUpdateEtcHosts(nsTArray<nsCString>&& aHosts);
+
+ private:
+ virtual ~TRRServiceChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TRRServiceChild_h
diff --git a/netwerk/dns/TRRServiceParent.cpp b/netwerk/dns/TRRServiceParent.cpp
new file mode 100644
index 0000000000..d5c072921b
--- /dev/null
+++ b/netwerk/dns/TRRServiceParent.cpp
@@ -0,0 +1,239 @@
+/* -*- 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 "mozilla/net/TRRServiceParent.h"
+
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/psm/PSMIPCTypes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsICaptivePortalService.h"
+#include "nsIParentalControlsService.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
+#include "nsIOService.h"
+#include "nsNetCID.h"
+#include "TRRService.h"
+
+#include "DNSLogging.h"
+
+namespace mozilla {
+namespace net {
+
+static Atomic<TRRServiceParent*> sTRRServiceParentPtr;
+
+static const char* gTRRUriCallbackPrefs[] = {
+ "network.trr.uri", "network.trr.default_provider_uri",
+ "network.trr.mode", kRolloutURIPref,
+ kRolloutModePref, nullptr,
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(TRRServiceParent, TRRServiceBase, nsIObserver,
+ nsISupportsWeakReference)
+
+TRRServiceParent::~TRRServiceParent() = default;
+
+void TRRServiceParent::Init() {
+ MOZ_ASSERT(gIOService);
+
+ if (!gIOService->SocketProcessReady()) {
+ RefPtr<TRRServiceParent> self = this;
+ gIOService->CallOrWaitForSocketProcess([self]() { self->Init(); });
+ return;
+ }
+
+ SocketProcessParent* socketParent = SocketProcessParent::GetSingleton();
+ if (!socketParent) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs =
+ static_cast<nsIObserverService*>(gIOService);
+ TRRService::AddObserver(this, obs);
+
+ bool captiveIsPassed = TRRService::CheckCaptivePortalIsPassed();
+ bool parentalControlEnabled = TRRService::GetParentalControlEnabledInternal();
+
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
+ nsTArray<nsCString> suffixList;
+ if (nls) {
+ nls->GetDnsSuffixList(suffixList);
+ }
+
+ Preferences::RegisterPrefixCallbacks(TRRServiceParent::PrefsChanged,
+ gTRRUriCallbackPrefs, this);
+ prefsChanged(nullptr);
+
+ if (socketParent->SendPTRRServiceConstructor(
+ this, captiveIsPassed, parentalControlEnabled, suffixList)) {
+ sTRRServiceParentPtr = this;
+ }
+}
+
+NS_IMETHODIMP
+TRRServiceParent::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, NS_DNS_SUFFIX_LIST_UPDATED_TOPIC) ||
+ !strcmp(aTopic, NS_NETWORK_LINK_TOPIC)) {
+ nsCOMPtr<nsINetworkLinkService> link = do_QueryInterface(aSubject);
+ // The network link service notification normally passes itself as the
+ // subject, but some unit tests will sometimes pass a null subject.
+ if (link) {
+ nsTArray<nsCString> suffixList;
+ link->GetDnsSuffixList(suffixList);
+ Unused << SendUpdatePlatformDNSInformation(suffixList);
+ }
+
+ if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) && mURISetByDetection) {
+ CheckURIPrefs();
+ }
+ }
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult
+TRRServiceParent::RecvNotifyNetworkConnectivityServiceObservers(
+ const nsCString& aTopic) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, aTopic.get(), nullptr);
+ }
+ return IPC_OK();
+}
+
+bool TRRServiceParent::MaybeSetPrivateURI(const nsACString& aURI) {
+ nsAutoCString newURI(aURI);
+ ProcessURITemplate(newURI);
+
+ if (mPrivateURI.Equals(newURI)) {
+ return false;
+ }
+
+ mPrivateURI = newURI;
+ AsyncCreateTRRConnectionInfo(mPrivateURI);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, NS_NETWORK_TRR_URI_CHANGED_TOPIC, nullptr);
+ }
+ return true;
+}
+
+void TRRServiceParent::SetDetectedTrrURI(const nsACString& aURI) {
+ if (!mURIPref.IsEmpty()) {
+ return;
+ }
+
+ mURISetByDetection = MaybeSetPrivateURI(aURI);
+ gIOService->CallOrWaitForSocketProcess(
+ [self = RefPtr{this}, uri = nsAutoCString(aURI)]() {
+ Unused << self->SendSetDetectedTrrURI(uri);
+ });
+}
+
+void TRRServiceParent::GetURI(nsACString& aURI) {
+ // We don't need a lock here, since mPrivateURI is only touched on main
+ // thread.
+ MOZ_ASSERT(NS_IsMainThread());
+ aURI = mPrivateURI;
+}
+
+void TRRServiceParent::UpdateParentalControlEnabled() {
+ bool enabled = TRRService::GetParentalControlEnabledInternal();
+ RefPtr<TRRServiceParent> self = this;
+ gIOService->CallOrWaitForSocketProcess([self, enabled]() {
+ Unused << self->SendUpdateParentalControlEnabled(enabled);
+ });
+}
+
+// static
+void TRRServiceParent::PrefsChanged(const char* aName, void* aSelf) {
+ static_cast<TRRServiceParent*>(aSelf)->prefsChanged(aName);
+}
+
+void TRRServiceParent::prefsChanged(const char* aName) {
+ if (!aName || !strcmp(aName, "network.trr.uri") ||
+ !strcmp(aName, "network.trr.default_provider_uri") ||
+ !strcmp(aName, kRolloutURIPref) ||
+ !strcmp(aName, "network.trr.ohttp.uri")) {
+ OnTRRURIChange();
+ }
+
+ if (!aName || !strcmp(aName, "network.trr.mode") ||
+ !strcmp(aName, kRolloutModePref)) {
+ OnTRRModeChange();
+ }
+}
+
+void TRRServiceParent::ActorDestroy(ActorDestroyReason why) {
+ sTRRServiceParentPtr = nullptr;
+ Preferences::UnregisterPrefixCallbacks(TRRServiceParent::PrefsChanged,
+ gTRRUriCallbackPrefs, this);
+}
+
+NS_IMETHODIMP TRRServiceParent::OnProxyConfigChanged() {
+ LOG(("TRRServiceParent::OnProxyConfigChanged"));
+
+ AsyncCreateTRRConnectionInfo(mPrivateURI);
+ return NS_OK;
+}
+
+void TRRServiceParent::SetDefaultTRRConnectionInfo(
+ nsHttpConnectionInfo* aConnInfo) {
+ TRRServiceBase::SetDefaultTRRConnectionInfo(aConnInfo);
+
+ if (!CanSend()) {
+ return;
+ }
+
+ if (!aConnInfo) {
+ Unused << SendSetDefaultTRRConnectionInfo(Nothing());
+ return;
+ }
+
+ HttpConnectionInfoCloneArgs infoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(aConnInfo, infoArgs);
+ Unused << SendSetDefaultTRRConnectionInfo(Some(infoArgs));
+}
+
+mozilla::ipc::IPCResult TRRServiceParent::RecvInitTRRConnectionInfo() {
+ InitTRRConnectionInfo();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TRRServiceParent::RecvSetConfirmationState(
+ uint32_t aNewState) {
+ mConfirmationState = aNewState;
+ return IPC_OK();
+}
+
+void TRRServiceParent::ReadEtcHostsFile() {
+ if (!sTRRServiceParentPtr) {
+ return;
+ }
+
+ DoReadEtcHostsFile([](const nsTArray<nsCString>* aArray) -> bool {
+ RefPtr<TRRServiceParent> service(sTRRServiceParentPtr);
+ if (service && aArray) {
+ nsTArray<nsCString> hosts(aArray->Clone());
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "TRRServiceParent::ReadEtcHostsFile",
+ [service, hosts = std::move(hosts)]() mutable {
+ if (service->CanSend()) {
+ Unused << service->SendUpdateEtcHosts(hosts);
+ }
+ }));
+ }
+ return !!service;
+ });
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRServiceParent.h b/netwerk/dns/TRRServiceParent.h
new file mode 100644
index 0000000000..c61310c029
--- /dev/null
+++ b/netwerk/dns/TRRServiceParent.h
@@ -0,0 +1,52 @@
+/* -*- 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_TRRServiceParent_h
+#define mozilla_net_TRRServiceParent_h
+
+#include "mozilla/net/PTRRServiceParent.h"
+#include "mozilla/net/TRRServiceBase.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+class TRRServiceParent : public TRRServiceBase,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public PTRRServiceParent {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIPROXYCONFIGCHANGEDCALLBACK
+
+ TRRServiceParent() = default;
+ void Init();
+ void UpdateParentalControlEnabled();
+ static void PrefsChanged(const char* aName, void* aSelf);
+ void SetDetectedTrrURI(const nsACString& aURI);
+ bool MaybeSetPrivateURI(const nsACString& aURI) override;
+ void GetURI(nsACString& result) override;
+ mozilla::ipc::IPCResult RecvNotifyNetworkConnectivityServiceObservers(
+ const nsCString& aTopic);
+ mozilla::ipc::IPCResult RecvInitTRRConnectionInfo();
+ mozilla::ipc::IPCResult RecvSetConfirmationState(uint32_t aNewState);
+ uint32_t GetConfirmationState() { return mConfirmationState; }
+ virtual void ReadEtcHostsFile() override;
+
+ private:
+ virtual ~TRRServiceParent();
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+ void prefsChanged(const char* aName);
+ void SetDefaultTRRConnectionInfo(nsHttpConnectionInfo* aConnInfo) override;
+ uint32_t mConfirmationState = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TRRServiceParent_h
diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat
new file mode 100644
index 0000000000..c2b813fa68
--- /dev/null
+++ b/netwerk/dns/effective_tld_names.dat
@@ -0,0 +1,15582 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat,
+// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported.
+
+// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/.
+
+// ===BEGIN ICANN DOMAINS===
+
+// ac : http://nic.ac/rules.htm
+ac
+com.ac
+edu.ac
+gov.ac
+net.ac
+mil.ac
+org.ac
+
+// ad : https://en.wikipedia.org/wiki/.ad
+ad
+nom.ad
+
+// ae : https://tdra.gov.ae/en/aeda/ae-policies
+ae
+co.ae
+net.ae
+org.ae
+sch.ae
+ac.ae
+gov.ae
+mil.ae
+
+// aero : see https://www.information.aero/index.php?id=66
+aero
+accident-investigation.aero
+accident-prevention.aero
+aerobatic.aero
+aeroclub.aero
+aerodrome.aero
+agents.aero
+aircraft.aero
+airline.aero
+airport.aero
+air-surveillance.aero
+airtraffic.aero
+air-traffic-control.aero
+ambulance.aero
+amusement.aero
+association.aero
+author.aero
+ballooning.aero
+broker.aero
+caa.aero
+cargo.aero
+catering.aero
+certification.aero
+championship.aero
+charter.aero
+civilaviation.aero
+club.aero
+conference.aero
+consultant.aero
+consulting.aero
+control.aero
+council.aero
+crew.aero
+design.aero
+dgca.aero
+educator.aero
+emergency.aero
+engine.aero
+engineer.aero
+entertainment.aero
+equipment.aero
+exchange.aero
+express.aero
+federation.aero
+flight.aero
+fuel.aero
+gliding.aero
+government.aero
+groundhandling.aero
+group.aero
+hanggliding.aero
+homebuilt.aero
+insurance.aero
+journal.aero
+journalist.aero
+leasing.aero
+logistics.aero
+magazine.aero
+maintenance.aero
+media.aero
+microlight.aero
+modelling.aero
+navigation.aero
+parachuting.aero
+paragliding.aero
+passenger-association.aero
+pilot.aero
+press.aero
+production.aero
+recreation.aero
+repbody.aero
+res.aero
+research.aero
+rotorcraft.aero
+safety.aero
+scientist.aero
+services.aero
+show.aero
+skydiving.aero
+software.aero
+student.aero
+trader.aero
+trading.aero
+trainer.aero
+union.aero
+workinggroup.aero
+works.aero
+
+// af : http://www.nic.af/help.jsp
+af
+gov.af
+com.af
+org.af
+net.af
+edu.af
+
+// ag : http://www.nic.ag/prices.htm
+ag
+com.ag
+org.ag
+net.ag
+co.ag
+nom.ag
+
+// ai : http://nic.com.ai/
+ai
+off.ai
+com.ai
+net.ai
+org.ai
+
+// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31
+al
+com.al
+edu.al
+gov.al
+mil.al
+net.al
+org.al
+
+// am : https://www.amnic.net/policy/en/Policy_EN.pdf
+am
+co.am
+com.am
+commune.am
+net.am
+org.am
+
+// ao : https://en.wikipedia.org/wiki/.ao
+// http://www.dns.ao/REGISTR.DOC
+ao
+ed.ao
+gv.ao
+og.ao
+co.ao
+pb.ao
+it.ao
+
+// aq : https://en.wikipedia.org/wiki/.aq
+aq
+
+// ar : https://nic.ar/es/nic-argentina/normativa
+ar
+bet.ar
+com.ar
+coop.ar
+edu.ar
+gob.ar
+gov.ar
+int.ar
+mil.ar
+musica.ar
+mutual.ar
+net.ar
+org.ar
+senasa.ar
+tur.ar
+
+// arpa : https://en.wikipedia.org/wiki/.arpa
+// Confirmed by registry <iana-questions@icann.org> 2008-06-18
+arpa
+e164.arpa
+in-addr.arpa
+ip6.arpa
+iris.arpa
+uri.arpa
+urn.arpa
+
+// as : https://en.wikipedia.org/wiki/.as
+as
+gov.as
+
+// asia : https://en.wikipedia.org/wiki/.asia
+asia
+
+// at : https://en.wikipedia.org/wiki/.at
+// Confirmed by registry <it@nic.at> 2008-06-17
+at
+ac.at
+co.at
+gv.at
+or.at
+sth.ac.at
+
+// au : https://en.wikipedia.org/wiki/.au
+// http://www.auda.org.au/
+au
+// 2LDs
+com.au
+net.au
+org.au
+edu.au
+gov.au
+asn.au
+id.au
+// Historic 2LDs (closed to new registration, but sites still exist)
+info.au
+conf.au
+oz.au
+// CGDNs - http://www.cgdn.org.au/
+act.au
+nsw.au
+nt.au
+qld.au
+sa.au
+tas.au
+vic.au
+wa.au
+// 3LDs
+act.edu.au
+catholic.edu.au
+// eq.edu.au - Removed at the request of the Queensland Department of Education
+nsw.edu.au
+nt.edu.au
+qld.edu.au
+sa.edu.au
+tas.edu.au
+vic.edu.au
+wa.edu.au
+// act.gov.au Bug 984824 - Removed at request of Greg Tankard
+// nsw.gov.au Bug 547985 - Removed at request of <Shae.Donelan@services.nsw.gov.au>
+// nt.gov.au Bug 940478 - Removed at request of Greg Connors <Greg.Connors@nt.gov.au>
+qld.gov.au
+sa.gov.au
+tas.gov.au
+vic.gov.au
+wa.gov.au
+// 4LDs
+// education.tas.edu.au - Removed at the request of the Department of Education Tasmania
+schools.nsw.edu.au
+
+// aw : https://en.wikipedia.org/wiki/.aw
+aw
+com.aw
+
+// ax : https://en.wikipedia.org/wiki/.ax
+ax
+
+// az : https://en.wikipedia.org/wiki/.az
+az
+com.az
+net.az
+int.az
+gov.az
+org.az
+edu.az
+info.az
+pp.az
+mil.az
+name.az
+pro.az
+biz.az
+
+// ba : http://nic.ba/users_data/files/pravilnik_o_registraciji.pdf
+ba
+com.ba
+edu.ba
+gov.ba
+mil.ba
+net.ba
+org.ba
+
+// bb : https://en.wikipedia.org/wiki/.bb
+bb
+biz.bb
+co.bb
+com.bb
+edu.bb
+gov.bb
+info.bb
+net.bb
+org.bb
+store.bb
+tv.bb
+
+// bd : https://en.wikipedia.org/wiki/.bd
+*.bd
+
+// be : https://en.wikipedia.org/wiki/.be
+// Confirmed by registry <tech@dns.be> 2008-06-08
+be
+ac.be
+
+// bf : https://en.wikipedia.org/wiki/.bf
+bf
+gov.bf
+
+// bg : https://en.wikipedia.org/wiki/.bg
+// https://www.register.bg/user/static/rules/en/index.html
+bg
+a.bg
+b.bg
+c.bg
+d.bg
+e.bg
+f.bg
+g.bg
+h.bg
+i.bg
+j.bg
+k.bg
+l.bg
+m.bg
+n.bg
+o.bg
+p.bg
+q.bg
+r.bg
+s.bg
+t.bg
+u.bg
+v.bg
+w.bg
+x.bg
+y.bg
+z.bg
+0.bg
+1.bg
+2.bg
+3.bg
+4.bg
+5.bg
+6.bg
+7.bg
+8.bg
+9.bg
+
+// bh : https://en.wikipedia.org/wiki/.bh
+bh
+com.bh
+edu.bh
+net.bh
+org.bh
+gov.bh
+
+// bi : https://en.wikipedia.org/wiki/.bi
+// http://whois.nic.bi/
+bi
+co.bi
+com.bi
+edu.bi
+or.bi
+org.bi
+
+// biz : https://en.wikipedia.org/wiki/.biz
+biz
+
+// bj : https://nic.bj/bj-suffixes.txt
+// submitted by registry <contact@nic.bj>
+bj
+africa.bj
+agro.bj
+architectes.bj
+assur.bj
+avocats.bj
+co.bj
+com.bj
+eco.bj
+econo.bj
+edu.bj
+info.bj
+loisirs.bj
+money.bj
+net.bj
+org.bj
+ote.bj
+resto.bj
+restaurant.bj
+tourism.bj
+univ.bj
+
+// bm : http://www.bermudanic.bm/dnr-text.txt
+bm
+com.bm
+edu.bm
+gov.bm
+net.bm
+org.bm
+
+// bn : http://www.bnnic.bn/faqs
+bn
+com.bn
+edu.bn
+gov.bn
+net.bn
+org.bn
+
+// bo : https://nic.bo/delegacion2015.php#h-1.10
+bo
+com.bo
+edu.bo
+gob.bo
+int.bo
+org.bo
+net.bo
+mil.bo
+tv.bo
+web.bo
+// Social Domains
+academia.bo
+agro.bo
+arte.bo
+blog.bo
+bolivia.bo
+ciencia.bo
+cooperativa.bo
+democracia.bo
+deporte.bo
+ecologia.bo
+economia.bo
+empresa.bo
+indigena.bo
+industria.bo
+info.bo
+medicina.bo
+movimiento.bo
+musica.bo
+natural.bo
+nombre.bo
+noticias.bo
+patria.bo
+politica.bo
+profesional.bo
+plurinacional.bo
+pueblo.bo
+revista.bo
+salud.bo
+tecnologia.bo
+tksat.bo
+transporte.bo
+wiki.bo
+
+// br : http://registro.br/dominio/categoria.html
+// Submitted by registry <fneves@registro.br>
+br
+9guacu.br
+abc.br
+adm.br
+adv.br
+agr.br
+aju.br
+am.br
+anani.br
+aparecida.br
+app.br
+arq.br
+art.br
+ato.br
+b.br
+barueri.br
+belem.br
+bhz.br
+bib.br
+bio.br
+blog.br
+bmd.br
+boavista.br
+bsb.br
+campinagrande.br
+campinas.br
+caxias.br
+cim.br
+cng.br
+cnt.br
+com.br
+contagem.br
+coop.br
+coz.br
+cri.br
+cuiaba.br
+curitiba.br
+def.br
+des.br
+det.br
+dev.br
+ecn.br
+eco.br
+edu.br
+emp.br
+enf.br
+eng.br
+esp.br
+etc.br
+eti.br
+far.br
+feira.br
+flog.br
+floripa.br
+fm.br
+fnd.br
+fortal.br
+fot.br
+foz.br
+fst.br
+g12.br
+geo.br
+ggf.br
+goiania.br
+gov.br
+// gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil
+ac.gov.br
+al.gov.br
+am.gov.br
+ap.gov.br
+ba.gov.br
+ce.gov.br
+df.gov.br
+es.gov.br
+go.gov.br
+ma.gov.br
+mg.gov.br
+ms.gov.br
+mt.gov.br
+pa.gov.br
+pb.gov.br
+pe.gov.br
+pi.gov.br
+pr.gov.br
+rj.gov.br
+rn.gov.br
+ro.gov.br
+rr.gov.br
+rs.gov.br
+sc.gov.br
+se.gov.br
+sp.gov.br
+to.gov.br
+gru.br
+imb.br
+ind.br
+inf.br
+jab.br
+jampa.br
+jdf.br
+joinville.br
+jor.br
+jus.br
+leg.br
+lel.br
+log.br
+londrina.br
+macapa.br
+maceio.br
+manaus.br
+maringa.br
+mat.br
+med.br
+mil.br
+morena.br
+mp.br
+mus.br
+natal.br
+net.br
+niteroi.br
+*.nom.br
+not.br
+ntr.br
+odo.br
+ong.br
+org.br
+osasco.br
+palmas.br
+poa.br
+ppg.br
+pro.br
+psc.br
+psi.br
+pvh.br
+qsl.br
+radio.br
+rec.br
+recife.br
+rep.br
+ribeirao.br
+rio.br
+riobranco.br
+riopreto.br
+salvador.br
+sampa.br
+santamaria.br
+santoandre.br
+saobernardo.br
+saogonca.br
+seg.br
+sjc.br
+slg.br
+slz.br
+sorocaba.br
+srv.br
+taxi.br
+tc.br
+tec.br
+teo.br
+the.br
+tmp.br
+trd.br
+tur.br
+tv.br
+udi.br
+vet.br
+vix.br
+vlog.br
+wiki.br
+zlg.br
+
+// bs : http://www.nic.bs/rules.html
+bs
+com.bs
+net.bs
+org.bs
+edu.bs
+gov.bs
+
+// bt : https://en.wikipedia.org/wiki/.bt
+bt
+com.bt
+edu.bt
+gov.bt
+net.bt
+org.bt
+
+// bv : No registrations at this time.
+// Submitted by registry <jarle@uninett.no>
+bv
+
+// bw : https://en.wikipedia.org/wiki/.bw
+// http://www.gobin.info/domainname/bw.doc
+// list of other 2nd level tlds ?
+bw
+co.bw
+org.bw
+
+// by : https://en.wikipedia.org/wiki/.by
+// http://tld.by/rules_2006_en.html
+// list of other 2nd level tlds ?
+by
+gov.by
+mil.by
+// Official information does not indicate that com.by is a reserved
+// second-level domain, but it's being used as one (see www.google.com.by and
+// www.yahoo.com.by, for example), so we list it here for safety's sake.
+com.by
+
+// http://hoster.by/
+of.by
+
+// bz : https://en.wikipedia.org/wiki/.bz
+// http://www.belizenic.bz/
+bz
+com.bz
+net.bz
+org.bz
+edu.bz
+gov.bz
+
+// ca : https://en.wikipedia.org/wiki/.ca
+ca
+// ca geographical names
+ab.ca
+bc.ca
+mb.ca
+nb.ca
+nf.ca
+nl.ca
+ns.ca
+nt.ca
+nu.ca
+on.ca
+pe.ca
+qc.ca
+sk.ca
+yk.ca
+// gc.ca: https://en.wikipedia.org/wiki/.gc.ca
+// see also: http://registry.gc.ca/en/SubdomainFAQ
+gc.ca
+
+// cat : https://en.wikipedia.org/wiki/.cat
+cat
+
+// cc : https://en.wikipedia.org/wiki/.cc
+cc
+
+// cd : https://en.wikipedia.org/wiki/.cd
+// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1
+cd
+gov.cd
+
+// cf : https://en.wikipedia.org/wiki/.cf
+cf
+
+// cg : https://en.wikipedia.org/wiki/.cg
+cg
+
+// ch : https://en.wikipedia.org/wiki/.ch
+ch
+
+// ci : https://en.wikipedia.org/wiki/.ci
+// http://www.nic.ci/index.php?page=charte
+ci
+org.ci
+or.ci
+com.ci
+co.ci
+edu.ci
+ed.ci
+ac.ci
+net.ci
+go.ci
+asso.ci
+aéroport.ci
+int.ci
+presse.ci
+md.ci
+gouv.ci
+
+// ck : https://en.wikipedia.org/wiki/.ck
+*.ck
+!www.ck
+
+// cl : https://www.nic.cl
+// Confirmed by .CL registry <hsalgado@nic.cl>
+cl
+co.cl
+gob.cl
+gov.cl
+mil.cl
+
+// cm : https://en.wikipedia.org/wiki/.cm plus bug 981927
+cm
+co.cm
+com.cm
+gov.cm
+net.cm
+
+// cn : https://en.wikipedia.org/wiki/.cn
+// Submitted by registry <tanyaling@cnnic.cn>
+cn
+ac.cn
+com.cn
+edu.cn
+gov.cn
+net.cn
+org.cn
+mil.cn
+公司.cn
+网络.cn
+網絡.cn
+// cn geographic names
+ah.cn
+bj.cn
+cq.cn
+fj.cn
+gd.cn
+gs.cn
+gz.cn
+gx.cn
+ha.cn
+hb.cn
+he.cn
+hi.cn
+hl.cn
+hn.cn
+jl.cn
+js.cn
+jx.cn
+ln.cn
+nm.cn
+nx.cn
+qh.cn
+sc.cn
+sd.cn
+sh.cn
+sn.cn
+sx.cn
+tj.cn
+xj.cn
+xz.cn
+yn.cn
+zj.cn
+hk.cn
+mo.cn
+tw.cn
+
+// co : https://en.wikipedia.org/wiki/.co
+// Submitted by registry <tecnico@uniandes.edu.co>
+co
+arts.co
+com.co
+edu.co
+firm.co
+gov.co
+info.co
+int.co
+mil.co
+net.co
+nom.co
+org.co
+rec.co
+web.co
+
+// com : https://en.wikipedia.org/wiki/.com
+com
+
+// coop : https://en.wikipedia.org/wiki/.coop
+coop
+
+// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do
+cr
+ac.cr
+co.cr
+ed.cr
+fi.cr
+go.cr
+or.cr
+sa.cr
+
+// cu : https://en.wikipedia.org/wiki/.cu
+cu
+com.cu
+edu.cu
+org.cu
+net.cu
+gov.cu
+inf.cu
+
+// cv : https://en.wikipedia.org/wiki/.cv
+// cv : http://www.dns.cv/tldcv_portal/do?com=DS;5446457100;111;+PAGE(4000018)+K-CAT-CODIGO(RDOM)+RCNT(100); <- registration rules
+cv
+com.cv
+edu.cv
+int.cv
+nome.cv
+org.cv
+
+// cw : http://www.una.cw/cw_registry/
+// Confirmed by registry <registry@una.net> 2013-03-26
+cw
+com.cw
+edu.cw
+net.cw
+org.cw
+
+// cx : https://en.wikipedia.org/wiki/.cx
+// list of other 2nd level tlds ?
+cx
+gov.cx
+
+// cy : http://www.nic.cy/
+// Submitted by registry Panayiotou Fotia <cydns@ucy.ac.cy>
+// namespace policies URL https://www.nic.cy/portal//sites/default/files/symfonia_gia_eggrafi.pdf
+cy
+ac.cy
+biz.cy
+com.cy
+ekloges.cy
+gov.cy
+ltd.cy
+mil.cy
+net.cy
+org.cy
+press.cy
+pro.cy
+tm.cy
+
+// cz : https://en.wikipedia.org/wiki/.cz
+cz
+
+// de : https://en.wikipedia.org/wiki/.de
+// Confirmed by registry <ops@denic.de> (with technical
+// reservations) 2008-07-01
+de
+
+// dj : https://en.wikipedia.org/wiki/.dj
+dj
+
+// dk : https://en.wikipedia.org/wiki/.dk
+// Confirmed by registry <robert@dk-hostmaster.dk> 2008-06-17
+dk
+
+// dm : https://en.wikipedia.org/wiki/.dm
+dm
+com.dm
+net.dm
+org.dm
+edu.dm
+gov.dm
+
+// do : https://en.wikipedia.org/wiki/.do
+do
+art.do
+com.do
+edu.do
+gob.do
+gov.do
+mil.do
+net.do
+org.do
+sld.do
+web.do
+
+// dz : http://www.nic.dz/images/pdf_nic/charte.pdf
+dz
+art.dz
+asso.dz
+com.dz
+edu.dz
+gov.dz
+org.dz
+net.dz
+pol.dz
+soc.dz
+tm.dz
+
+// ec : http://www.nic.ec/reg/paso1.asp
+// Submitted by registry <vabboud@nic.ec>
+ec
+com.ec
+info.ec
+net.ec
+fin.ec
+k12.ec
+med.ec
+pro.ec
+org.ec
+edu.ec
+gov.ec
+gob.ec
+mil.ec
+
+// edu : https://en.wikipedia.org/wiki/.edu
+edu
+
+// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B
+ee
+edu.ee
+gov.ee
+riik.ee
+lib.ee
+med.ee
+com.ee
+pri.ee
+aip.ee
+org.ee
+fie.ee
+
+// eg : https://en.wikipedia.org/wiki/.eg
+eg
+com.eg
+edu.eg
+eun.eg
+gov.eg
+mil.eg
+name.eg
+net.eg
+org.eg
+sci.eg
+
+// er : https://en.wikipedia.org/wiki/.er
+*.er
+
+// es : https://www.nic.es/site_ingles/ingles/dominios/index.html
+es
+com.es
+nom.es
+org.es
+gob.es
+edu.es
+
+// et : https://en.wikipedia.org/wiki/.et
+et
+com.et
+gov.et
+org.et
+edu.et
+biz.et
+name.et
+info.et
+net.et
+
+// eu : https://en.wikipedia.org/wiki/.eu
+eu
+
+// fi : https://en.wikipedia.org/wiki/.fi
+fi
+// aland.fi : https://en.wikipedia.org/wiki/.ax
+// This domain is being phased out in favor of .ax. As there are still many
+// domains under aland.fi, we still keep it on the list until aland.fi is
+// completely removed.
+// TODO: Check for updates (expected to be phased out around Q1/2009)
+aland.fi
+
+// fj : http://domains.fj/
+// Submitted by registry <garth.miller@cocca.org.nz> 2020-02-11
+fj
+ac.fj
+biz.fj
+com.fj
+gov.fj
+info.fj
+mil.fj
+name.fj
+net.fj
+org.fj
+pro.fj
+
+// fk : https://en.wikipedia.org/wiki/.fk
+*.fk
+
+// fm : https://en.wikipedia.org/wiki/.fm
+com.fm
+edu.fm
+net.fm
+org.fm
+fm
+
+// fo : https://en.wikipedia.org/wiki/.fo
+fo
+
+// fr : https://www.afnic.fr/ https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
+fr
+asso.fr
+com.fr
+gouv.fr
+nom.fr
+prd.fr
+tm.fr
+// Other SLDs now selfmanaged out of AFNIC range. Former "domaines sectoriels", still registration suffixes
+avoues.fr
+cci.fr
+greta.fr
+huissier-justice.fr
+
+// ga : https://en.wikipedia.org/wiki/.ga
+ga
+
+// gb : This registry is effectively dormant
+// Submitted by registry <Damien.Shaw@ja.net>
+gb
+
+// gd : https://en.wikipedia.org/wiki/.gd
+edu.gd
+gov.gd
+gd
+
+// ge : http://www.nic.net.ge/policy_en.pdf
+ge
+com.ge
+edu.ge
+gov.ge
+org.ge
+mil.ge
+net.ge
+pvt.ge
+
+// gf : https://en.wikipedia.org/wiki/.gf
+gf
+
+// gg : http://www.channelisles.net/register-domains/
+// Confirmed by registry <nigel@channelisles.net> 2013-11-28
+gg
+co.gg
+net.gg
+org.gg
+
+// gh : https://en.wikipedia.org/wiki/.gh
+// see also: http://www.nic.gh/reg_now.php
+// Although domains directly at second level are not possible at the moment,
+// they have been possible for some time and may come back.
+gh
+com.gh
+edu.gh
+gov.gh
+org.gh
+mil.gh
+
+// gi : http://www.nic.gi/rules.html
+gi
+com.gi
+ltd.gi
+gov.gi
+mod.gi
+edu.gi
+org.gi
+
+// gl : https://en.wikipedia.org/wiki/.gl
+// http://nic.gl
+gl
+co.gl
+com.gl
+edu.gl
+net.gl
+org.gl
+
+// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm
+gm
+
+// gn : http://psg.com/dns/gn/gn.txt
+// Submitted by registry <randy@psg.com>
+gn
+ac.gn
+com.gn
+edu.gn
+gov.gn
+org.gn
+net.gn
+
+// gov : https://en.wikipedia.org/wiki/.gov
+gov
+
+// gp : http://www.nic.gp/index.php?lang=en
+gp
+com.gp
+net.gp
+mobi.gp
+edu.gp
+org.gp
+asso.gp
+
+// gq : https://en.wikipedia.org/wiki/.gq
+gq
+
+// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html
+// Submitted by registry <segred@ics.forth.gr>
+gr
+com.gr
+edu.gr
+net.gr
+org.gr
+gov.gr
+
+// gs : https://en.wikipedia.org/wiki/.gs
+gs
+
+// gt : https://www.gt/sitio/registration_policy.php?lang=en
+gt
+com.gt
+edu.gt
+gob.gt
+ind.gt
+mil.gt
+net.gt
+org.gt
+
+// gu : http://gadao.gov.gu/register.html
+// University of Guam : https://www.uog.edu
+// Submitted by uognoc@triton.uog.edu
+gu
+com.gu
+edu.gu
+gov.gu
+guam.gu
+info.gu
+net.gu
+org.gu
+web.gu
+
+// gw : https://en.wikipedia.org/wiki/.gw
+// gw : https://nic.gw/regras/
+gw
+
+// gy : https://en.wikipedia.org/wiki/.gy
+// http://registry.gy/
+gy
+co.gy
+com.gy
+edu.gy
+gov.gy
+net.gy
+org.gy
+
+// hk : https://www.hkirc.hk
+// Submitted by registry <hk.tech@hkirc.hk>
+hk
+com.hk
+edu.hk
+gov.hk
+idv.hk
+net.hk
+org.hk
+公司.hk
+教育.hk
+敎育.hk
+政府.hk
+個人.hk
+个人.hk
+箇人.hk
+網络.hk
+网络.hk
+组織.hk
+網絡.hk
+网絡.hk
+组织.hk
+組織.hk
+組织.hk
+
+// hm : https://en.wikipedia.org/wiki/.hm
+hm
+
+// hn : http://www.nic.hn/politicas/ps02,,05.html
+hn
+com.hn
+edu.hn
+org.hn
+net.hn
+mil.hn
+gob.hn
+
+// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf
+hr
+iz.hr
+from.hr
+name.hr
+com.hr
+
+// ht : http://www.nic.ht/info/charte.cfm
+ht
+com.ht
+shop.ht
+firm.ht
+info.ht
+adult.ht
+net.ht
+pro.ht
+org.ht
+med.ht
+art.ht
+coop.ht
+pol.ht
+asso.ht
+edu.ht
+rel.ht
+gouv.ht
+perso.ht
+
+// hu : http://www.domain.hu/domain/English/sld.html
+// Confirmed by registry <pasztor@iszt.hu> 2008-06-12
+hu
+co.hu
+info.hu
+org.hu
+priv.hu
+sport.hu
+tm.hu
+2000.hu
+agrar.hu
+bolt.hu
+casino.hu
+city.hu
+erotica.hu
+erotika.hu
+film.hu
+forum.hu
+games.hu
+hotel.hu
+ingatlan.hu
+jogasz.hu
+konyvelo.hu
+lakas.hu
+media.hu
+news.hu
+reklam.hu
+sex.hu
+shop.hu
+suli.hu
+szex.hu
+tozsde.hu
+utazas.hu
+video.hu
+
+// id : https://pandi.id/en/domain/registration-requirements/
+id
+ac.id
+biz.id
+co.id
+desa.id
+go.id
+mil.id
+my.id
+net.id
+or.id
+ponpes.id
+sch.id
+web.id
+
+// ie : https://en.wikipedia.org/wiki/.ie
+ie
+gov.ie
+
+// il : http://www.isoc.org.il/domains/
+// see also: https://en.isoc.org.il/il-cctld/registration-rules
+// ISOC-IL (operated by .il Registry)
+il
+ac.il
+co.il
+gov.il
+idf.il
+k12.il
+muni.il
+net.il
+org.il
+// xn--4dbrk0ce ("Israel", Hebrew) : IL
+ישראל
+// xn--4dbgdty6c.xn--4dbrk0ce.
+אקדמיה.ישראל
+// xn--5dbhl8d.xn--4dbrk0ce.
+ישוב.ישראל
+// xn--8dbq2a.xn--4dbrk0ce.
+צהל.ישראל
+// xn--hebda8b.xn--4dbrk0ce.
+ממשל.ישראל
+
+// im : https://www.nic.im/
+// Submitted by registry <info@nic.im>
+im
+ac.im
+co.im
+com.im
+ltd.co.im
+net.im
+org.im
+plc.co.im
+tt.im
+tv.im
+
+// in : https://en.wikipedia.org/wiki/.in
+// see also: https://registry.in/policies
+// Please note, that nic.in is not an official eTLD, but used by most
+// government institutions.
+in
+5g.in
+6g.in
+ac.in
+ai.in
+am.in
+bihar.in
+biz.in
+business.in
+ca.in
+cn.in
+co.in
+com.in
+coop.in
+cs.in
+delhi.in
+dr.in
+edu.in
+er.in
+firm.in
+gen.in
+gov.in
+gujarat.in
+ind.in
+info.in
+int.in
+internet.in
+io.in
+me.in
+mil.in
+net.in
+nic.in
+org.in
+pg.in
+post.in
+pro.in
+res.in
+travel.in
+tv.in
+uk.in
+up.in
+us.in
+
+// info : https://en.wikipedia.org/wiki/.info
+info
+
+// int : https://en.wikipedia.org/wiki/.int
+// Confirmed by registry <iana-questions@icann.org> 2008-06-18
+int
+eu.int
+
+// io : http://www.nic.io/rules.htm
+// list of other 2nd level tlds ?
+io
+com.io
+
+// iq : http://www.cmc.iq/english/iq/iqregister1.htm
+iq
+gov.iq
+edu.iq
+mil.iq
+com.iq
+org.iq
+net.iq
+
+// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules
+// Also see http://www.nic.ir/Internationalized_Domain_Names
+// Two <iran>.ir entries added at request of <tech-team@nic.ir>, 2010-04-16
+ir
+ac.ir
+co.ir
+gov.ir
+id.ir
+net.ir
+org.ir
+sch.ir
+// xn--mgba3a4f16a.ir (<iran>.ir, Persian YEH)
+ایران.ir
+// xn--mgba3a4fra.ir (<iran>.ir, Arabic YEH)
+ايران.ir
+
+// is : http://www.isnic.is/domain/rules.php
+// Confirmed by registry <marius@isgate.is> 2008-12-06
+is
+net.is
+com.is
+edu.is
+gov.is
+org.is
+int.is
+
+// it : https://en.wikipedia.org/wiki/.it
+it
+gov.it
+edu.it
+// Reserved geo-names (regions and provinces):
+// https://www.nic.it/sites/default/files/archivio/docs/Regulation_assignation_v7.1.pdf
+// Regions
+abr.it
+abruzzo.it
+aosta-valley.it
+aostavalley.it
+bas.it
+basilicata.it
+cal.it
+calabria.it
+cam.it
+campania.it
+emilia-romagna.it
+emiliaromagna.it
+emr.it
+friuli-v-giulia.it
+friuli-ve-giulia.it
+friuli-vegiulia.it
+friuli-venezia-giulia.it
+friuli-veneziagiulia.it
+friuli-vgiulia.it
+friuliv-giulia.it
+friulive-giulia.it
+friulivegiulia.it
+friulivenezia-giulia.it
+friuliveneziagiulia.it
+friulivgiulia.it
+fvg.it
+laz.it
+lazio.it
+lig.it
+liguria.it
+lom.it
+lombardia.it
+lombardy.it
+lucania.it
+mar.it
+marche.it
+mol.it
+molise.it
+piedmont.it
+piemonte.it
+pmn.it
+pug.it
+puglia.it
+sar.it
+sardegna.it
+sardinia.it
+sic.it
+sicilia.it
+sicily.it
+taa.it
+tos.it
+toscana.it
+trentin-sud-tirol.it
+trentin-süd-tirol.it
+trentin-sudtirol.it
+trentin-südtirol.it
+trentin-sued-tirol.it
+trentin-suedtirol.it
+trentino-a-adige.it
+trentino-aadige.it
+trentino-alto-adige.it
+trentino-altoadige.it
+trentino-s-tirol.it
+trentino-stirol.it
+trentino-sud-tirol.it
+trentino-süd-tirol.it
+trentino-sudtirol.it
+trentino-südtirol.it
+trentino-sued-tirol.it
+trentino-suedtirol.it
+trentino.it
+trentinoa-adige.it
+trentinoaadige.it
+trentinoalto-adige.it
+trentinoaltoadige.it
+trentinos-tirol.it
+trentinostirol.it
+trentinosud-tirol.it
+trentinosüd-tirol.it
+trentinosudtirol.it
+trentinosüdtirol.it
+trentinosued-tirol.it
+trentinosuedtirol.it
+trentinsud-tirol.it
+trentinsüd-tirol.it
+trentinsudtirol.it
+trentinsüdtirol.it
+trentinsued-tirol.it
+trentinsuedtirol.it
+tuscany.it
+umb.it
+umbria.it
+val-d-aosta.it
+val-daosta.it
+vald-aosta.it
+valdaosta.it
+valle-aosta.it
+valle-d-aosta.it
+valle-daosta.it
+valleaosta.it
+valled-aosta.it
+valledaosta.it
+vallee-aoste.it
+vallée-aoste.it
+vallee-d-aoste.it
+vallée-d-aoste.it
+valleeaoste.it
+valléeaoste.it
+valleedaoste.it
+valléedaoste.it
+vao.it
+vda.it
+ven.it
+veneto.it
+// Provinces
+ag.it
+agrigento.it
+al.it
+alessandria.it
+alto-adige.it
+altoadige.it
+an.it
+ancona.it
+andria-barletta-trani.it
+andria-trani-barletta.it
+andriabarlettatrani.it
+andriatranibarletta.it
+ao.it
+aosta.it
+aoste.it
+ap.it
+aq.it
+aquila.it
+ar.it
+arezzo.it
+ascoli-piceno.it
+ascolipiceno.it
+asti.it
+at.it
+av.it
+avellino.it
+ba.it
+balsan-sudtirol.it
+balsan-südtirol.it
+balsan-suedtirol.it
+balsan.it
+bari.it
+barletta-trani-andria.it
+barlettatraniandria.it
+belluno.it
+benevento.it
+bergamo.it
+bg.it
+bi.it
+biella.it
+bl.it
+bn.it
+bo.it
+bologna.it
+bolzano-altoadige.it
+bolzano.it
+bozen-sudtirol.it
+bozen-südtirol.it
+bozen-suedtirol.it
+bozen.it
+br.it
+brescia.it
+brindisi.it
+bs.it
+bt.it
+bulsan-sudtirol.it
+bulsan-südtirol.it
+bulsan-suedtirol.it
+bulsan.it
+bz.it
+ca.it
+cagliari.it
+caltanissetta.it
+campidano-medio.it
+campidanomedio.it
+campobasso.it
+carbonia-iglesias.it
+carboniaiglesias.it
+carrara-massa.it
+carraramassa.it
+caserta.it
+catania.it
+catanzaro.it
+cb.it
+ce.it
+cesena-forli.it
+cesena-forlì.it
+cesenaforli.it
+cesenaforlì.it
+ch.it
+chieti.it
+ci.it
+cl.it
+cn.it
+co.it
+como.it
+cosenza.it
+cr.it
+cremona.it
+crotone.it
+cs.it
+ct.it
+cuneo.it
+cz.it
+dell-ogliastra.it
+dellogliastra.it
+en.it
+enna.it
+fc.it
+fe.it
+fermo.it
+ferrara.it
+fg.it
+fi.it
+firenze.it
+florence.it
+fm.it
+foggia.it
+forli-cesena.it
+forlì-cesena.it
+forlicesena.it
+forlìcesena.it
+fr.it
+frosinone.it
+ge.it
+genoa.it
+genova.it
+go.it
+gorizia.it
+gr.it
+grosseto.it
+iglesias-carbonia.it
+iglesiascarbonia.it
+im.it
+imperia.it
+is.it
+isernia.it
+kr.it
+la-spezia.it
+laquila.it
+laspezia.it
+latina.it
+lc.it
+le.it
+lecce.it
+lecco.it
+li.it
+livorno.it
+lo.it
+lodi.it
+lt.it
+lu.it
+lucca.it
+macerata.it
+mantova.it
+massa-carrara.it
+massacarrara.it
+matera.it
+mb.it
+mc.it
+me.it
+medio-campidano.it
+mediocampidano.it
+messina.it
+mi.it
+milan.it
+milano.it
+mn.it
+mo.it
+modena.it
+monza-brianza.it
+monza-e-della-brianza.it
+monza.it
+monzabrianza.it
+monzaebrianza.it
+monzaedellabrianza.it
+ms.it
+mt.it
+na.it
+naples.it
+napoli.it
+no.it
+novara.it
+nu.it
+nuoro.it
+og.it
+ogliastra.it
+olbia-tempio.it
+olbiatempio.it
+or.it
+oristano.it
+ot.it
+pa.it
+padova.it
+padua.it
+palermo.it
+parma.it
+pavia.it
+pc.it
+pd.it
+pe.it
+perugia.it
+pesaro-urbino.it
+pesarourbino.it
+pescara.it
+pg.it
+pi.it
+piacenza.it
+pisa.it
+pistoia.it
+pn.it
+po.it
+pordenone.it
+potenza.it
+pr.it
+prato.it
+pt.it
+pu.it
+pv.it
+pz.it
+ra.it
+ragusa.it
+ravenna.it
+rc.it
+re.it
+reggio-calabria.it
+reggio-emilia.it
+reggiocalabria.it
+reggioemilia.it
+rg.it
+ri.it
+rieti.it
+rimini.it
+rm.it
+rn.it
+ro.it
+roma.it
+rome.it
+rovigo.it
+sa.it
+salerno.it
+sassari.it
+savona.it
+si.it
+siena.it
+siracusa.it
+so.it
+sondrio.it
+sp.it
+sr.it
+ss.it
+suedtirol.it
+südtirol.it
+sv.it
+ta.it
+taranto.it
+te.it
+tempio-olbia.it
+tempioolbia.it
+teramo.it
+terni.it
+tn.it
+to.it
+torino.it
+tp.it
+tr.it
+trani-andria-barletta.it
+trani-barletta-andria.it
+traniandriabarletta.it
+tranibarlettaandria.it
+trapani.it
+trento.it
+treviso.it
+trieste.it
+ts.it
+turin.it
+tv.it
+ud.it
+udine.it
+urbino-pesaro.it
+urbinopesaro.it
+va.it
+varese.it
+vb.it
+vc.it
+ve.it
+venezia.it
+venice.it
+verbania.it
+vercelli.it
+verona.it
+vi.it
+vibo-valentia.it
+vibovalentia.it
+vicenza.it
+viterbo.it
+vr.it
+vs.it
+vt.it
+vv.it
+
+// je : http://www.channelisles.net/register-domains/
+// Confirmed by registry <nigel@channelisles.net> 2013-11-28
+je
+co.je
+net.je
+org.je
+
+// jm : http://www.com.jm/register.html
+*.jm
+
+// jo : http://www.dns.jo/Registration_policy.aspx
+jo
+com.jo
+org.jo
+net.jo
+edu.jo
+sch.jo
+gov.jo
+mil.jo
+name.jo
+
+// jobs : https://en.wikipedia.org/wiki/.jobs
+jobs
+
+// jp : https://en.wikipedia.org/wiki/.jp
+// http://jprs.co.jp/en/jpdomain.html
+// Submitted by registry <info@jprs.jp>
+jp
+// jp organizational type names
+ac.jp
+ad.jp
+co.jp
+ed.jp
+go.jp
+gr.jp
+lg.jp
+ne.jp
+or.jp
+// jp prefecture type names
+aichi.jp
+akita.jp
+aomori.jp
+chiba.jp
+ehime.jp
+fukui.jp
+fukuoka.jp
+fukushima.jp
+gifu.jp
+gunma.jp
+hiroshima.jp
+hokkaido.jp
+hyogo.jp
+ibaraki.jp
+ishikawa.jp
+iwate.jp
+kagawa.jp
+kagoshima.jp
+kanagawa.jp
+kochi.jp
+kumamoto.jp
+kyoto.jp
+mie.jp
+miyagi.jp
+miyazaki.jp
+nagano.jp
+nagasaki.jp
+nara.jp
+niigata.jp
+oita.jp
+okayama.jp
+okinawa.jp
+osaka.jp
+saga.jp
+saitama.jp
+shiga.jp
+shimane.jp
+shizuoka.jp
+tochigi.jp
+tokushima.jp
+tokyo.jp
+tottori.jp
+toyama.jp
+wakayama.jp
+yamagata.jp
+yamaguchi.jp
+yamanashi.jp
+栃木.jp
+愛知.jp
+愛媛.jp
+兵庫.jp
+熊本.jp
+茨城.jp
+北海道.jp
+千葉.jp
+和歌山.jp
+長崎.jp
+長野.jp
+新潟.jp
+青森.jp
+静岡.jp
+東京.jp
+石川.jp
+埼玉.jp
+三重.jp
+京都.jp
+佐賀.jp
+大分.jp
+大阪.jp
+奈良.jp
+宮城.jp
+宮崎.jp
+富山.jp
+山口.jp
+山形.jp
+山梨.jp
+岩手.jp
+岐阜.jp
+岡山.jp
+島根.jp
+広島.jp
+徳島.jp
+沖縄.jp
+滋賀.jp
+神奈川.jp
+福井.jp
+福岡.jp
+福島.jp
+秋田.jp
+群馬.jp
+香川.jp
+高知.jp
+鳥取.jp
+鹿児島.jp
+// jp geographic type names
+// http://jprs.jp/doc/rule/saisoku-1.html
+*.kawasaki.jp
+*.kitakyushu.jp
+*.kobe.jp
+*.nagoya.jp
+*.sapporo.jp
+*.sendai.jp
+*.yokohama.jp
+!city.kawasaki.jp
+!city.kitakyushu.jp
+!city.kobe.jp
+!city.nagoya.jp
+!city.sapporo.jp
+!city.sendai.jp
+!city.yokohama.jp
+// 4th level registration
+aisai.aichi.jp
+ama.aichi.jp
+anjo.aichi.jp
+asuke.aichi.jp
+chiryu.aichi.jp
+chita.aichi.jp
+fuso.aichi.jp
+gamagori.aichi.jp
+handa.aichi.jp
+hazu.aichi.jp
+hekinan.aichi.jp
+higashiura.aichi.jp
+ichinomiya.aichi.jp
+inazawa.aichi.jp
+inuyama.aichi.jp
+isshiki.aichi.jp
+iwakura.aichi.jp
+kanie.aichi.jp
+kariya.aichi.jp
+kasugai.aichi.jp
+kira.aichi.jp
+kiyosu.aichi.jp
+komaki.aichi.jp
+konan.aichi.jp
+kota.aichi.jp
+mihama.aichi.jp
+miyoshi.aichi.jp
+nishio.aichi.jp
+nisshin.aichi.jp
+obu.aichi.jp
+oguchi.aichi.jp
+oharu.aichi.jp
+okazaki.aichi.jp
+owariasahi.aichi.jp
+seto.aichi.jp
+shikatsu.aichi.jp
+shinshiro.aichi.jp
+shitara.aichi.jp
+tahara.aichi.jp
+takahama.aichi.jp
+tobishima.aichi.jp
+toei.aichi.jp
+togo.aichi.jp
+tokai.aichi.jp
+tokoname.aichi.jp
+toyoake.aichi.jp
+toyohashi.aichi.jp
+toyokawa.aichi.jp
+toyone.aichi.jp
+toyota.aichi.jp
+tsushima.aichi.jp
+yatomi.aichi.jp
+akita.akita.jp
+daisen.akita.jp
+fujisato.akita.jp
+gojome.akita.jp
+hachirogata.akita.jp
+happou.akita.jp
+higashinaruse.akita.jp
+honjo.akita.jp
+honjyo.akita.jp
+ikawa.akita.jp
+kamikoani.akita.jp
+kamioka.akita.jp
+katagami.akita.jp
+kazuno.akita.jp
+kitaakita.akita.jp
+kosaka.akita.jp
+kyowa.akita.jp
+misato.akita.jp
+mitane.akita.jp
+moriyoshi.akita.jp
+nikaho.akita.jp
+noshiro.akita.jp
+odate.akita.jp
+oga.akita.jp
+ogata.akita.jp
+semboku.akita.jp
+yokote.akita.jp
+yurihonjo.akita.jp
+aomori.aomori.jp
+gonohe.aomori.jp
+hachinohe.aomori.jp
+hashikami.aomori.jp
+hiranai.aomori.jp
+hirosaki.aomori.jp
+itayanagi.aomori.jp
+kuroishi.aomori.jp
+misawa.aomori.jp
+mutsu.aomori.jp
+nakadomari.aomori.jp
+noheji.aomori.jp
+oirase.aomori.jp
+owani.aomori.jp
+rokunohe.aomori.jp
+sannohe.aomori.jp
+shichinohe.aomori.jp
+shingo.aomori.jp
+takko.aomori.jp
+towada.aomori.jp
+tsugaru.aomori.jp
+tsuruta.aomori.jp
+abiko.chiba.jp
+asahi.chiba.jp
+chonan.chiba.jp
+chosei.chiba.jp
+choshi.chiba.jp
+chuo.chiba.jp
+funabashi.chiba.jp
+futtsu.chiba.jp
+hanamigawa.chiba.jp
+ichihara.chiba.jp
+ichikawa.chiba.jp
+ichinomiya.chiba.jp
+inzai.chiba.jp
+isumi.chiba.jp
+kamagaya.chiba.jp
+kamogawa.chiba.jp
+kashiwa.chiba.jp
+katori.chiba.jp
+katsuura.chiba.jp
+kimitsu.chiba.jp
+kisarazu.chiba.jp
+kozaki.chiba.jp
+kujukuri.chiba.jp
+kyonan.chiba.jp
+matsudo.chiba.jp
+midori.chiba.jp
+mihama.chiba.jp
+minamiboso.chiba.jp
+mobara.chiba.jp
+mutsuzawa.chiba.jp
+nagara.chiba.jp
+nagareyama.chiba.jp
+narashino.chiba.jp
+narita.chiba.jp
+noda.chiba.jp
+oamishirasato.chiba.jp
+omigawa.chiba.jp
+onjuku.chiba.jp
+otaki.chiba.jp
+sakae.chiba.jp
+sakura.chiba.jp
+shimofusa.chiba.jp
+shirako.chiba.jp
+shiroi.chiba.jp
+shisui.chiba.jp
+sodegaura.chiba.jp
+sosa.chiba.jp
+tako.chiba.jp
+tateyama.chiba.jp
+togane.chiba.jp
+tohnosho.chiba.jp
+tomisato.chiba.jp
+urayasu.chiba.jp
+yachimata.chiba.jp
+yachiyo.chiba.jp
+yokaichiba.chiba.jp
+yokoshibahikari.chiba.jp
+yotsukaido.chiba.jp
+ainan.ehime.jp
+honai.ehime.jp
+ikata.ehime.jp
+imabari.ehime.jp
+iyo.ehime.jp
+kamijima.ehime.jp
+kihoku.ehime.jp
+kumakogen.ehime.jp
+masaki.ehime.jp
+matsuno.ehime.jp
+matsuyama.ehime.jp
+namikata.ehime.jp
+niihama.ehime.jp
+ozu.ehime.jp
+saijo.ehime.jp
+seiyo.ehime.jp
+shikokuchuo.ehime.jp
+tobe.ehime.jp
+toon.ehime.jp
+uchiko.ehime.jp
+uwajima.ehime.jp
+yawatahama.ehime.jp
+echizen.fukui.jp
+eiheiji.fukui.jp
+fukui.fukui.jp
+ikeda.fukui.jp
+katsuyama.fukui.jp
+mihama.fukui.jp
+minamiechizen.fukui.jp
+obama.fukui.jp
+ohi.fukui.jp
+ono.fukui.jp
+sabae.fukui.jp
+sakai.fukui.jp
+takahama.fukui.jp
+tsuruga.fukui.jp
+wakasa.fukui.jp
+ashiya.fukuoka.jp
+buzen.fukuoka.jp
+chikugo.fukuoka.jp
+chikuho.fukuoka.jp
+chikujo.fukuoka.jp
+chikushino.fukuoka.jp
+chikuzen.fukuoka.jp
+chuo.fukuoka.jp
+dazaifu.fukuoka.jp
+fukuchi.fukuoka.jp
+hakata.fukuoka.jp
+higashi.fukuoka.jp
+hirokawa.fukuoka.jp
+hisayama.fukuoka.jp
+iizuka.fukuoka.jp
+inatsuki.fukuoka.jp
+kaho.fukuoka.jp
+kasuga.fukuoka.jp
+kasuya.fukuoka.jp
+kawara.fukuoka.jp
+keisen.fukuoka.jp
+koga.fukuoka.jp
+kurate.fukuoka.jp
+kurogi.fukuoka.jp
+kurume.fukuoka.jp
+minami.fukuoka.jp
+miyako.fukuoka.jp
+miyama.fukuoka.jp
+miyawaka.fukuoka.jp
+mizumaki.fukuoka.jp
+munakata.fukuoka.jp
+nakagawa.fukuoka.jp
+nakama.fukuoka.jp
+nishi.fukuoka.jp
+nogata.fukuoka.jp
+ogori.fukuoka.jp
+okagaki.fukuoka.jp
+okawa.fukuoka.jp
+oki.fukuoka.jp
+omuta.fukuoka.jp
+onga.fukuoka.jp
+onojo.fukuoka.jp
+oto.fukuoka.jp
+saigawa.fukuoka.jp
+sasaguri.fukuoka.jp
+shingu.fukuoka.jp
+shinyoshitomi.fukuoka.jp
+shonai.fukuoka.jp
+soeda.fukuoka.jp
+sue.fukuoka.jp
+tachiarai.fukuoka.jp
+tagawa.fukuoka.jp
+takata.fukuoka.jp
+toho.fukuoka.jp
+toyotsu.fukuoka.jp
+tsuiki.fukuoka.jp
+ukiha.fukuoka.jp
+umi.fukuoka.jp
+usui.fukuoka.jp
+yamada.fukuoka.jp
+yame.fukuoka.jp
+yanagawa.fukuoka.jp
+yukuhashi.fukuoka.jp
+aizubange.fukushima.jp
+aizumisato.fukushima.jp
+aizuwakamatsu.fukushima.jp
+asakawa.fukushima.jp
+bandai.fukushima.jp
+date.fukushima.jp
+fukushima.fukushima.jp
+furudono.fukushima.jp
+futaba.fukushima.jp
+hanawa.fukushima.jp
+higashi.fukushima.jp
+hirata.fukushima.jp
+hirono.fukushima.jp
+iitate.fukushima.jp
+inawashiro.fukushima.jp
+ishikawa.fukushima.jp
+iwaki.fukushima.jp
+izumizaki.fukushima.jp
+kagamiishi.fukushima.jp
+kaneyama.fukushima.jp
+kawamata.fukushima.jp
+kitakata.fukushima.jp
+kitashiobara.fukushima.jp
+koori.fukushima.jp
+koriyama.fukushima.jp
+kunimi.fukushima.jp
+miharu.fukushima.jp
+mishima.fukushima.jp
+namie.fukushima.jp
+nango.fukushima.jp
+nishiaizu.fukushima.jp
+nishigo.fukushima.jp
+okuma.fukushima.jp
+omotego.fukushima.jp
+ono.fukushima.jp
+otama.fukushima.jp
+samegawa.fukushima.jp
+shimogo.fukushima.jp
+shirakawa.fukushima.jp
+showa.fukushima.jp
+soma.fukushima.jp
+sukagawa.fukushima.jp
+taishin.fukushima.jp
+tamakawa.fukushima.jp
+tanagura.fukushima.jp
+tenei.fukushima.jp
+yabuki.fukushima.jp
+yamato.fukushima.jp
+yamatsuri.fukushima.jp
+yanaizu.fukushima.jp
+yugawa.fukushima.jp
+anpachi.gifu.jp
+ena.gifu.jp
+gifu.gifu.jp
+ginan.gifu.jp
+godo.gifu.jp
+gujo.gifu.jp
+hashima.gifu.jp
+hichiso.gifu.jp
+hida.gifu.jp
+higashishirakawa.gifu.jp
+ibigawa.gifu.jp
+ikeda.gifu.jp
+kakamigahara.gifu.jp
+kani.gifu.jp
+kasahara.gifu.jp
+kasamatsu.gifu.jp
+kawaue.gifu.jp
+kitagata.gifu.jp
+mino.gifu.jp
+minokamo.gifu.jp
+mitake.gifu.jp
+mizunami.gifu.jp
+motosu.gifu.jp
+nakatsugawa.gifu.jp
+ogaki.gifu.jp
+sakahogi.gifu.jp
+seki.gifu.jp
+sekigahara.gifu.jp
+shirakawa.gifu.jp
+tajimi.gifu.jp
+takayama.gifu.jp
+tarui.gifu.jp
+toki.gifu.jp
+tomika.gifu.jp
+wanouchi.gifu.jp
+yamagata.gifu.jp
+yaotsu.gifu.jp
+yoro.gifu.jp
+annaka.gunma.jp
+chiyoda.gunma.jp
+fujioka.gunma.jp
+higashiagatsuma.gunma.jp
+isesaki.gunma.jp
+itakura.gunma.jp
+kanna.gunma.jp
+kanra.gunma.jp
+katashina.gunma.jp
+kawaba.gunma.jp
+kiryu.gunma.jp
+kusatsu.gunma.jp
+maebashi.gunma.jp
+meiwa.gunma.jp
+midori.gunma.jp
+minakami.gunma.jp
+naganohara.gunma.jp
+nakanojo.gunma.jp
+nanmoku.gunma.jp
+numata.gunma.jp
+oizumi.gunma.jp
+ora.gunma.jp
+ota.gunma.jp
+shibukawa.gunma.jp
+shimonita.gunma.jp
+shinto.gunma.jp
+showa.gunma.jp
+takasaki.gunma.jp
+takayama.gunma.jp
+tamamura.gunma.jp
+tatebayashi.gunma.jp
+tomioka.gunma.jp
+tsukiyono.gunma.jp
+tsumagoi.gunma.jp
+ueno.gunma.jp
+yoshioka.gunma.jp
+asaminami.hiroshima.jp
+daiwa.hiroshima.jp
+etajima.hiroshima.jp
+fuchu.hiroshima.jp
+fukuyama.hiroshima.jp
+hatsukaichi.hiroshima.jp
+higashihiroshima.hiroshima.jp
+hongo.hiroshima.jp
+jinsekikogen.hiroshima.jp
+kaita.hiroshima.jp
+kui.hiroshima.jp
+kumano.hiroshima.jp
+kure.hiroshima.jp
+mihara.hiroshima.jp
+miyoshi.hiroshima.jp
+naka.hiroshima.jp
+onomichi.hiroshima.jp
+osakikamijima.hiroshima.jp
+otake.hiroshima.jp
+saka.hiroshima.jp
+sera.hiroshima.jp
+seranishi.hiroshima.jp
+shinichi.hiroshima.jp
+shobara.hiroshima.jp
+takehara.hiroshima.jp
+abashiri.hokkaido.jp
+abira.hokkaido.jp
+aibetsu.hokkaido.jp
+akabira.hokkaido.jp
+akkeshi.hokkaido.jp
+asahikawa.hokkaido.jp
+ashibetsu.hokkaido.jp
+ashoro.hokkaido.jp
+assabu.hokkaido.jp
+atsuma.hokkaido.jp
+bibai.hokkaido.jp
+biei.hokkaido.jp
+bifuka.hokkaido.jp
+bihoro.hokkaido.jp
+biratori.hokkaido.jp
+chippubetsu.hokkaido.jp
+chitose.hokkaido.jp
+date.hokkaido.jp
+ebetsu.hokkaido.jp
+embetsu.hokkaido.jp
+eniwa.hokkaido.jp
+erimo.hokkaido.jp
+esan.hokkaido.jp
+esashi.hokkaido.jp
+fukagawa.hokkaido.jp
+fukushima.hokkaido.jp
+furano.hokkaido.jp
+furubira.hokkaido.jp
+haboro.hokkaido.jp
+hakodate.hokkaido.jp
+hamatonbetsu.hokkaido.jp
+hidaka.hokkaido.jp
+higashikagura.hokkaido.jp
+higashikawa.hokkaido.jp
+hiroo.hokkaido.jp
+hokuryu.hokkaido.jp
+hokuto.hokkaido.jp
+honbetsu.hokkaido.jp
+horokanai.hokkaido.jp
+horonobe.hokkaido.jp
+ikeda.hokkaido.jp
+imakane.hokkaido.jp
+ishikari.hokkaido.jp
+iwamizawa.hokkaido.jp
+iwanai.hokkaido.jp
+kamifurano.hokkaido.jp
+kamikawa.hokkaido.jp
+kamishihoro.hokkaido.jp
+kamisunagawa.hokkaido.jp
+kamoenai.hokkaido.jp
+kayabe.hokkaido.jp
+kembuchi.hokkaido.jp
+kikonai.hokkaido.jp
+kimobetsu.hokkaido.jp
+kitahiroshima.hokkaido.jp
+kitami.hokkaido.jp
+kiyosato.hokkaido.jp
+koshimizu.hokkaido.jp
+kunneppu.hokkaido.jp
+kuriyama.hokkaido.jp
+kuromatsunai.hokkaido.jp
+kushiro.hokkaido.jp
+kutchan.hokkaido.jp
+kyowa.hokkaido.jp
+mashike.hokkaido.jp
+matsumae.hokkaido.jp
+mikasa.hokkaido.jp
+minamifurano.hokkaido.jp
+mombetsu.hokkaido.jp
+moseushi.hokkaido.jp
+mukawa.hokkaido.jp
+muroran.hokkaido.jp
+naie.hokkaido.jp
+nakagawa.hokkaido.jp
+nakasatsunai.hokkaido.jp
+nakatombetsu.hokkaido.jp
+nanae.hokkaido.jp
+nanporo.hokkaido.jp
+nayoro.hokkaido.jp
+nemuro.hokkaido.jp
+niikappu.hokkaido.jp
+niki.hokkaido.jp
+nishiokoppe.hokkaido.jp
+noboribetsu.hokkaido.jp
+numata.hokkaido.jp
+obihiro.hokkaido.jp
+obira.hokkaido.jp
+oketo.hokkaido.jp
+okoppe.hokkaido.jp
+otaru.hokkaido.jp
+otobe.hokkaido.jp
+otofuke.hokkaido.jp
+otoineppu.hokkaido.jp
+oumu.hokkaido.jp
+ozora.hokkaido.jp
+pippu.hokkaido.jp
+rankoshi.hokkaido.jp
+rebun.hokkaido.jp
+rikubetsu.hokkaido.jp
+rishiri.hokkaido.jp
+rishirifuji.hokkaido.jp
+saroma.hokkaido.jp
+sarufutsu.hokkaido.jp
+shakotan.hokkaido.jp
+shari.hokkaido.jp
+shibecha.hokkaido.jp
+shibetsu.hokkaido.jp
+shikabe.hokkaido.jp
+shikaoi.hokkaido.jp
+shimamaki.hokkaido.jp
+shimizu.hokkaido.jp
+shimokawa.hokkaido.jp
+shinshinotsu.hokkaido.jp
+shintoku.hokkaido.jp
+shiranuka.hokkaido.jp
+shiraoi.hokkaido.jp
+shiriuchi.hokkaido.jp
+sobetsu.hokkaido.jp
+sunagawa.hokkaido.jp
+taiki.hokkaido.jp
+takasu.hokkaido.jp
+takikawa.hokkaido.jp
+takinoue.hokkaido.jp
+teshikaga.hokkaido.jp
+tobetsu.hokkaido.jp
+tohma.hokkaido.jp
+tomakomai.hokkaido.jp
+tomari.hokkaido.jp
+toya.hokkaido.jp
+toyako.hokkaido.jp
+toyotomi.hokkaido.jp
+toyoura.hokkaido.jp
+tsubetsu.hokkaido.jp
+tsukigata.hokkaido.jp
+urakawa.hokkaido.jp
+urausu.hokkaido.jp
+uryu.hokkaido.jp
+utashinai.hokkaido.jp
+wakkanai.hokkaido.jp
+wassamu.hokkaido.jp
+yakumo.hokkaido.jp
+yoichi.hokkaido.jp
+aioi.hyogo.jp
+akashi.hyogo.jp
+ako.hyogo.jp
+amagasaki.hyogo.jp
+aogaki.hyogo.jp
+asago.hyogo.jp
+ashiya.hyogo.jp
+awaji.hyogo.jp
+fukusaki.hyogo.jp
+goshiki.hyogo.jp
+harima.hyogo.jp
+himeji.hyogo.jp
+ichikawa.hyogo.jp
+inagawa.hyogo.jp
+itami.hyogo.jp
+kakogawa.hyogo.jp
+kamigori.hyogo.jp
+kamikawa.hyogo.jp
+kasai.hyogo.jp
+kasuga.hyogo.jp
+kawanishi.hyogo.jp
+miki.hyogo.jp
+minamiawaji.hyogo.jp
+nishinomiya.hyogo.jp
+nishiwaki.hyogo.jp
+ono.hyogo.jp
+sanda.hyogo.jp
+sannan.hyogo.jp
+sasayama.hyogo.jp
+sayo.hyogo.jp
+shingu.hyogo.jp
+shinonsen.hyogo.jp
+shiso.hyogo.jp
+sumoto.hyogo.jp
+taishi.hyogo.jp
+taka.hyogo.jp
+takarazuka.hyogo.jp
+takasago.hyogo.jp
+takino.hyogo.jp
+tamba.hyogo.jp
+tatsuno.hyogo.jp
+toyooka.hyogo.jp
+yabu.hyogo.jp
+yashiro.hyogo.jp
+yoka.hyogo.jp
+yokawa.hyogo.jp
+ami.ibaraki.jp
+asahi.ibaraki.jp
+bando.ibaraki.jp
+chikusei.ibaraki.jp
+daigo.ibaraki.jp
+fujishiro.ibaraki.jp
+hitachi.ibaraki.jp
+hitachinaka.ibaraki.jp
+hitachiomiya.ibaraki.jp
+hitachiota.ibaraki.jp
+ibaraki.ibaraki.jp
+ina.ibaraki.jp
+inashiki.ibaraki.jp
+itako.ibaraki.jp
+iwama.ibaraki.jp
+joso.ibaraki.jp
+kamisu.ibaraki.jp
+kasama.ibaraki.jp
+kashima.ibaraki.jp
+kasumigaura.ibaraki.jp
+koga.ibaraki.jp
+miho.ibaraki.jp
+mito.ibaraki.jp
+moriya.ibaraki.jp
+naka.ibaraki.jp
+namegata.ibaraki.jp
+oarai.ibaraki.jp
+ogawa.ibaraki.jp
+omitama.ibaraki.jp
+ryugasaki.ibaraki.jp
+sakai.ibaraki.jp
+sakuragawa.ibaraki.jp
+shimodate.ibaraki.jp
+shimotsuma.ibaraki.jp
+shirosato.ibaraki.jp
+sowa.ibaraki.jp
+suifu.ibaraki.jp
+takahagi.ibaraki.jp
+tamatsukuri.ibaraki.jp
+tokai.ibaraki.jp
+tomobe.ibaraki.jp
+tone.ibaraki.jp
+toride.ibaraki.jp
+tsuchiura.ibaraki.jp
+tsukuba.ibaraki.jp
+uchihara.ibaraki.jp
+ushiku.ibaraki.jp
+yachiyo.ibaraki.jp
+yamagata.ibaraki.jp
+yawara.ibaraki.jp
+yuki.ibaraki.jp
+anamizu.ishikawa.jp
+hakui.ishikawa.jp
+hakusan.ishikawa.jp
+kaga.ishikawa.jp
+kahoku.ishikawa.jp
+kanazawa.ishikawa.jp
+kawakita.ishikawa.jp
+komatsu.ishikawa.jp
+nakanoto.ishikawa.jp
+nanao.ishikawa.jp
+nomi.ishikawa.jp
+nonoichi.ishikawa.jp
+noto.ishikawa.jp
+shika.ishikawa.jp
+suzu.ishikawa.jp
+tsubata.ishikawa.jp
+tsurugi.ishikawa.jp
+uchinada.ishikawa.jp
+wajima.ishikawa.jp
+fudai.iwate.jp
+fujisawa.iwate.jp
+hanamaki.iwate.jp
+hiraizumi.iwate.jp
+hirono.iwate.jp
+ichinohe.iwate.jp
+ichinoseki.iwate.jp
+iwaizumi.iwate.jp
+iwate.iwate.jp
+joboji.iwate.jp
+kamaishi.iwate.jp
+kanegasaki.iwate.jp
+karumai.iwate.jp
+kawai.iwate.jp
+kitakami.iwate.jp
+kuji.iwate.jp
+kunohe.iwate.jp
+kuzumaki.iwate.jp
+miyako.iwate.jp
+mizusawa.iwate.jp
+morioka.iwate.jp
+ninohe.iwate.jp
+noda.iwate.jp
+ofunato.iwate.jp
+oshu.iwate.jp
+otsuchi.iwate.jp
+rikuzentakata.iwate.jp
+shiwa.iwate.jp
+shizukuishi.iwate.jp
+sumita.iwate.jp
+tanohata.iwate.jp
+tono.iwate.jp
+yahaba.iwate.jp
+yamada.iwate.jp
+ayagawa.kagawa.jp
+higashikagawa.kagawa.jp
+kanonji.kagawa.jp
+kotohira.kagawa.jp
+manno.kagawa.jp
+marugame.kagawa.jp
+mitoyo.kagawa.jp
+naoshima.kagawa.jp
+sanuki.kagawa.jp
+tadotsu.kagawa.jp
+takamatsu.kagawa.jp
+tonosho.kagawa.jp
+uchinomi.kagawa.jp
+utazu.kagawa.jp
+zentsuji.kagawa.jp
+akune.kagoshima.jp
+amami.kagoshima.jp
+hioki.kagoshima.jp
+isa.kagoshima.jp
+isen.kagoshima.jp
+izumi.kagoshima.jp
+kagoshima.kagoshima.jp
+kanoya.kagoshima.jp
+kawanabe.kagoshima.jp
+kinko.kagoshima.jp
+kouyama.kagoshima.jp
+makurazaki.kagoshima.jp
+matsumoto.kagoshima.jp
+minamitane.kagoshima.jp
+nakatane.kagoshima.jp
+nishinoomote.kagoshima.jp
+satsumasendai.kagoshima.jp
+soo.kagoshima.jp
+tarumizu.kagoshima.jp
+yusui.kagoshima.jp
+aikawa.kanagawa.jp
+atsugi.kanagawa.jp
+ayase.kanagawa.jp
+chigasaki.kanagawa.jp
+ebina.kanagawa.jp
+fujisawa.kanagawa.jp
+hadano.kanagawa.jp
+hakone.kanagawa.jp
+hiratsuka.kanagawa.jp
+isehara.kanagawa.jp
+kaisei.kanagawa.jp
+kamakura.kanagawa.jp
+kiyokawa.kanagawa.jp
+matsuda.kanagawa.jp
+minamiashigara.kanagawa.jp
+miura.kanagawa.jp
+nakai.kanagawa.jp
+ninomiya.kanagawa.jp
+odawara.kanagawa.jp
+oi.kanagawa.jp
+oiso.kanagawa.jp
+sagamihara.kanagawa.jp
+samukawa.kanagawa.jp
+tsukui.kanagawa.jp
+yamakita.kanagawa.jp
+yamato.kanagawa.jp
+yokosuka.kanagawa.jp
+yugawara.kanagawa.jp
+zama.kanagawa.jp
+zushi.kanagawa.jp
+aki.kochi.jp
+geisei.kochi.jp
+hidaka.kochi.jp
+higashitsuno.kochi.jp
+ino.kochi.jp
+kagami.kochi.jp
+kami.kochi.jp
+kitagawa.kochi.jp
+kochi.kochi.jp
+mihara.kochi.jp
+motoyama.kochi.jp
+muroto.kochi.jp
+nahari.kochi.jp
+nakamura.kochi.jp
+nankoku.kochi.jp
+nishitosa.kochi.jp
+niyodogawa.kochi.jp
+ochi.kochi.jp
+okawa.kochi.jp
+otoyo.kochi.jp
+otsuki.kochi.jp
+sakawa.kochi.jp
+sukumo.kochi.jp
+susaki.kochi.jp
+tosa.kochi.jp
+tosashimizu.kochi.jp
+toyo.kochi.jp
+tsuno.kochi.jp
+umaji.kochi.jp
+yasuda.kochi.jp
+yusuhara.kochi.jp
+amakusa.kumamoto.jp
+arao.kumamoto.jp
+aso.kumamoto.jp
+choyo.kumamoto.jp
+gyokuto.kumamoto.jp
+kamiamakusa.kumamoto.jp
+kikuchi.kumamoto.jp
+kumamoto.kumamoto.jp
+mashiki.kumamoto.jp
+mifune.kumamoto.jp
+minamata.kumamoto.jp
+minamioguni.kumamoto.jp
+nagasu.kumamoto.jp
+nishihara.kumamoto.jp
+oguni.kumamoto.jp
+ozu.kumamoto.jp
+sumoto.kumamoto.jp
+takamori.kumamoto.jp
+uki.kumamoto.jp
+uto.kumamoto.jp
+yamaga.kumamoto.jp
+yamato.kumamoto.jp
+yatsushiro.kumamoto.jp
+ayabe.kyoto.jp
+fukuchiyama.kyoto.jp
+higashiyama.kyoto.jp
+ide.kyoto.jp
+ine.kyoto.jp
+joyo.kyoto.jp
+kameoka.kyoto.jp
+kamo.kyoto.jp
+kita.kyoto.jp
+kizu.kyoto.jp
+kumiyama.kyoto.jp
+kyotamba.kyoto.jp
+kyotanabe.kyoto.jp
+kyotango.kyoto.jp
+maizuru.kyoto.jp
+minami.kyoto.jp
+minamiyamashiro.kyoto.jp
+miyazu.kyoto.jp
+muko.kyoto.jp
+nagaokakyo.kyoto.jp
+nakagyo.kyoto.jp
+nantan.kyoto.jp
+oyamazaki.kyoto.jp
+sakyo.kyoto.jp
+seika.kyoto.jp
+tanabe.kyoto.jp
+uji.kyoto.jp
+ujitawara.kyoto.jp
+wazuka.kyoto.jp
+yamashina.kyoto.jp
+yawata.kyoto.jp
+asahi.mie.jp
+inabe.mie.jp
+ise.mie.jp
+kameyama.mie.jp
+kawagoe.mie.jp
+kiho.mie.jp
+kisosaki.mie.jp
+kiwa.mie.jp
+komono.mie.jp
+kumano.mie.jp
+kuwana.mie.jp
+matsusaka.mie.jp
+meiwa.mie.jp
+mihama.mie.jp
+minamiise.mie.jp
+misugi.mie.jp
+miyama.mie.jp
+nabari.mie.jp
+shima.mie.jp
+suzuka.mie.jp
+tado.mie.jp
+taiki.mie.jp
+taki.mie.jp
+tamaki.mie.jp
+toba.mie.jp
+tsu.mie.jp
+udono.mie.jp
+ureshino.mie.jp
+watarai.mie.jp
+yokkaichi.mie.jp
+furukawa.miyagi.jp
+higashimatsushima.miyagi.jp
+ishinomaki.miyagi.jp
+iwanuma.miyagi.jp
+kakuda.miyagi.jp
+kami.miyagi.jp
+kawasaki.miyagi.jp
+marumori.miyagi.jp
+matsushima.miyagi.jp
+minamisanriku.miyagi.jp
+misato.miyagi.jp
+murata.miyagi.jp
+natori.miyagi.jp
+ogawara.miyagi.jp
+ohira.miyagi.jp
+onagawa.miyagi.jp
+osaki.miyagi.jp
+rifu.miyagi.jp
+semine.miyagi.jp
+shibata.miyagi.jp
+shichikashuku.miyagi.jp
+shikama.miyagi.jp
+shiogama.miyagi.jp
+shiroishi.miyagi.jp
+tagajo.miyagi.jp
+taiwa.miyagi.jp
+tome.miyagi.jp
+tomiya.miyagi.jp
+wakuya.miyagi.jp
+watari.miyagi.jp
+yamamoto.miyagi.jp
+zao.miyagi.jp
+aya.miyazaki.jp
+ebino.miyazaki.jp
+gokase.miyazaki.jp
+hyuga.miyazaki.jp
+kadogawa.miyazaki.jp
+kawaminami.miyazaki.jp
+kijo.miyazaki.jp
+kitagawa.miyazaki.jp
+kitakata.miyazaki.jp
+kitaura.miyazaki.jp
+kobayashi.miyazaki.jp
+kunitomi.miyazaki.jp
+kushima.miyazaki.jp
+mimata.miyazaki.jp
+miyakonojo.miyazaki.jp
+miyazaki.miyazaki.jp
+morotsuka.miyazaki.jp
+nichinan.miyazaki.jp
+nishimera.miyazaki.jp
+nobeoka.miyazaki.jp
+saito.miyazaki.jp
+shiiba.miyazaki.jp
+shintomi.miyazaki.jp
+takaharu.miyazaki.jp
+takanabe.miyazaki.jp
+takazaki.miyazaki.jp
+tsuno.miyazaki.jp
+achi.nagano.jp
+agematsu.nagano.jp
+anan.nagano.jp
+aoki.nagano.jp
+asahi.nagano.jp
+azumino.nagano.jp
+chikuhoku.nagano.jp
+chikuma.nagano.jp
+chino.nagano.jp
+fujimi.nagano.jp
+hakuba.nagano.jp
+hara.nagano.jp
+hiraya.nagano.jp
+iida.nagano.jp
+iijima.nagano.jp
+iiyama.nagano.jp
+iizuna.nagano.jp
+ikeda.nagano.jp
+ikusaka.nagano.jp
+ina.nagano.jp
+karuizawa.nagano.jp
+kawakami.nagano.jp
+kiso.nagano.jp
+kisofukushima.nagano.jp
+kitaaiki.nagano.jp
+komagane.nagano.jp
+komoro.nagano.jp
+matsukawa.nagano.jp
+matsumoto.nagano.jp
+miasa.nagano.jp
+minamiaiki.nagano.jp
+minamimaki.nagano.jp
+minamiminowa.nagano.jp
+minowa.nagano.jp
+miyada.nagano.jp
+miyota.nagano.jp
+mochizuki.nagano.jp
+nagano.nagano.jp
+nagawa.nagano.jp
+nagiso.nagano.jp
+nakagawa.nagano.jp
+nakano.nagano.jp
+nozawaonsen.nagano.jp
+obuse.nagano.jp
+ogawa.nagano.jp
+okaya.nagano.jp
+omachi.nagano.jp
+omi.nagano.jp
+ookuwa.nagano.jp
+ooshika.nagano.jp
+otaki.nagano.jp
+otari.nagano.jp
+sakae.nagano.jp
+sakaki.nagano.jp
+saku.nagano.jp
+sakuho.nagano.jp
+shimosuwa.nagano.jp
+shinanomachi.nagano.jp
+shiojiri.nagano.jp
+suwa.nagano.jp
+suzaka.nagano.jp
+takagi.nagano.jp
+takamori.nagano.jp
+takayama.nagano.jp
+tateshina.nagano.jp
+tatsuno.nagano.jp
+togakushi.nagano.jp
+togura.nagano.jp
+tomi.nagano.jp
+ueda.nagano.jp
+wada.nagano.jp
+yamagata.nagano.jp
+yamanouchi.nagano.jp
+yasaka.nagano.jp
+yasuoka.nagano.jp
+chijiwa.nagasaki.jp
+futsu.nagasaki.jp
+goto.nagasaki.jp
+hasami.nagasaki.jp
+hirado.nagasaki.jp
+iki.nagasaki.jp
+isahaya.nagasaki.jp
+kawatana.nagasaki.jp
+kuchinotsu.nagasaki.jp
+matsuura.nagasaki.jp
+nagasaki.nagasaki.jp
+obama.nagasaki.jp
+omura.nagasaki.jp
+oseto.nagasaki.jp
+saikai.nagasaki.jp
+sasebo.nagasaki.jp
+seihi.nagasaki.jp
+shimabara.nagasaki.jp
+shinkamigoto.nagasaki.jp
+togitsu.nagasaki.jp
+tsushima.nagasaki.jp
+unzen.nagasaki.jp
+ando.nara.jp
+gose.nara.jp
+heguri.nara.jp
+higashiyoshino.nara.jp
+ikaruga.nara.jp
+ikoma.nara.jp
+kamikitayama.nara.jp
+kanmaki.nara.jp
+kashiba.nara.jp
+kashihara.nara.jp
+katsuragi.nara.jp
+kawai.nara.jp
+kawakami.nara.jp
+kawanishi.nara.jp
+koryo.nara.jp
+kurotaki.nara.jp
+mitsue.nara.jp
+miyake.nara.jp
+nara.nara.jp
+nosegawa.nara.jp
+oji.nara.jp
+ouda.nara.jp
+oyodo.nara.jp
+sakurai.nara.jp
+sango.nara.jp
+shimoichi.nara.jp
+shimokitayama.nara.jp
+shinjo.nara.jp
+soni.nara.jp
+takatori.nara.jp
+tawaramoto.nara.jp
+tenkawa.nara.jp
+tenri.nara.jp
+uda.nara.jp
+yamatokoriyama.nara.jp
+yamatotakada.nara.jp
+yamazoe.nara.jp
+yoshino.nara.jp
+aga.niigata.jp
+agano.niigata.jp
+gosen.niigata.jp
+itoigawa.niigata.jp
+izumozaki.niigata.jp
+joetsu.niigata.jp
+kamo.niigata.jp
+kariwa.niigata.jp
+kashiwazaki.niigata.jp
+minamiuonuma.niigata.jp
+mitsuke.niigata.jp
+muika.niigata.jp
+murakami.niigata.jp
+myoko.niigata.jp
+nagaoka.niigata.jp
+niigata.niigata.jp
+ojiya.niigata.jp
+omi.niigata.jp
+sado.niigata.jp
+sanjo.niigata.jp
+seiro.niigata.jp
+seirou.niigata.jp
+sekikawa.niigata.jp
+shibata.niigata.jp
+tagami.niigata.jp
+tainai.niigata.jp
+tochio.niigata.jp
+tokamachi.niigata.jp
+tsubame.niigata.jp
+tsunan.niigata.jp
+uonuma.niigata.jp
+yahiko.niigata.jp
+yoita.niigata.jp
+yuzawa.niigata.jp
+beppu.oita.jp
+bungoono.oita.jp
+bungotakada.oita.jp
+hasama.oita.jp
+hiji.oita.jp
+himeshima.oita.jp
+hita.oita.jp
+kamitsue.oita.jp
+kokonoe.oita.jp
+kuju.oita.jp
+kunisaki.oita.jp
+kusu.oita.jp
+oita.oita.jp
+saiki.oita.jp
+taketa.oita.jp
+tsukumi.oita.jp
+usa.oita.jp
+usuki.oita.jp
+yufu.oita.jp
+akaiwa.okayama.jp
+asakuchi.okayama.jp
+bizen.okayama.jp
+hayashima.okayama.jp
+ibara.okayama.jp
+kagamino.okayama.jp
+kasaoka.okayama.jp
+kibichuo.okayama.jp
+kumenan.okayama.jp
+kurashiki.okayama.jp
+maniwa.okayama.jp
+misaki.okayama.jp
+nagi.okayama.jp
+niimi.okayama.jp
+nishiawakura.okayama.jp
+okayama.okayama.jp
+satosho.okayama.jp
+setouchi.okayama.jp
+shinjo.okayama.jp
+shoo.okayama.jp
+soja.okayama.jp
+takahashi.okayama.jp
+tamano.okayama.jp
+tsuyama.okayama.jp
+wake.okayama.jp
+yakage.okayama.jp
+aguni.okinawa.jp
+ginowan.okinawa.jp
+ginoza.okinawa.jp
+gushikami.okinawa.jp
+haebaru.okinawa.jp
+higashi.okinawa.jp
+hirara.okinawa.jp
+iheya.okinawa.jp
+ishigaki.okinawa.jp
+ishikawa.okinawa.jp
+itoman.okinawa.jp
+izena.okinawa.jp
+kadena.okinawa.jp
+kin.okinawa.jp
+kitadaito.okinawa.jp
+kitanakagusuku.okinawa.jp
+kumejima.okinawa.jp
+kunigami.okinawa.jp
+minamidaito.okinawa.jp
+motobu.okinawa.jp
+nago.okinawa.jp
+naha.okinawa.jp
+nakagusuku.okinawa.jp
+nakijin.okinawa.jp
+nanjo.okinawa.jp
+nishihara.okinawa.jp
+ogimi.okinawa.jp
+okinawa.okinawa.jp
+onna.okinawa.jp
+shimoji.okinawa.jp
+taketomi.okinawa.jp
+tarama.okinawa.jp
+tokashiki.okinawa.jp
+tomigusuku.okinawa.jp
+tonaki.okinawa.jp
+urasoe.okinawa.jp
+uruma.okinawa.jp
+yaese.okinawa.jp
+yomitan.okinawa.jp
+yonabaru.okinawa.jp
+yonaguni.okinawa.jp
+zamami.okinawa.jp
+abeno.osaka.jp
+chihayaakasaka.osaka.jp
+chuo.osaka.jp
+daito.osaka.jp
+fujiidera.osaka.jp
+habikino.osaka.jp
+hannan.osaka.jp
+higashiosaka.osaka.jp
+higashisumiyoshi.osaka.jp
+higashiyodogawa.osaka.jp
+hirakata.osaka.jp
+ibaraki.osaka.jp
+ikeda.osaka.jp
+izumi.osaka.jp
+izumiotsu.osaka.jp
+izumisano.osaka.jp
+kadoma.osaka.jp
+kaizuka.osaka.jp
+kanan.osaka.jp
+kashiwara.osaka.jp
+katano.osaka.jp
+kawachinagano.osaka.jp
+kishiwada.osaka.jp
+kita.osaka.jp
+kumatori.osaka.jp
+matsubara.osaka.jp
+minato.osaka.jp
+minoh.osaka.jp
+misaki.osaka.jp
+moriguchi.osaka.jp
+neyagawa.osaka.jp
+nishi.osaka.jp
+nose.osaka.jp
+osakasayama.osaka.jp
+sakai.osaka.jp
+sayama.osaka.jp
+sennan.osaka.jp
+settsu.osaka.jp
+shijonawate.osaka.jp
+shimamoto.osaka.jp
+suita.osaka.jp
+tadaoka.osaka.jp
+taishi.osaka.jp
+tajiri.osaka.jp
+takaishi.osaka.jp
+takatsuki.osaka.jp
+tondabayashi.osaka.jp
+toyonaka.osaka.jp
+toyono.osaka.jp
+yao.osaka.jp
+ariake.saga.jp
+arita.saga.jp
+fukudomi.saga.jp
+genkai.saga.jp
+hamatama.saga.jp
+hizen.saga.jp
+imari.saga.jp
+kamimine.saga.jp
+kanzaki.saga.jp
+karatsu.saga.jp
+kashima.saga.jp
+kitagata.saga.jp
+kitahata.saga.jp
+kiyama.saga.jp
+kouhoku.saga.jp
+kyuragi.saga.jp
+nishiarita.saga.jp
+ogi.saga.jp
+omachi.saga.jp
+ouchi.saga.jp
+saga.saga.jp
+shiroishi.saga.jp
+taku.saga.jp
+tara.saga.jp
+tosu.saga.jp
+yoshinogari.saga.jp
+arakawa.saitama.jp
+asaka.saitama.jp
+chichibu.saitama.jp
+fujimi.saitama.jp
+fujimino.saitama.jp
+fukaya.saitama.jp
+hanno.saitama.jp
+hanyu.saitama.jp
+hasuda.saitama.jp
+hatogaya.saitama.jp
+hatoyama.saitama.jp
+hidaka.saitama.jp
+higashichichibu.saitama.jp
+higashimatsuyama.saitama.jp
+honjo.saitama.jp
+ina.saitama.jp
+iruma.saitama.jp
+iwatsuki.saitama.jp
+kamiizumi.saitama.jp
+kamikawa.saitama.jp
+kamisato.saitama.jp
+kasukabe.saitama.jp
+kawagoe.saitama.jp
+kawaguchi.saitama.jp
+kawajima.saitama.jp
+kazo.saitama.jp
+kitamoto.saitama.jp
+koshigaya.saitama.jp
+kounosu.saitama.jp
+kuki.saitama.jp
+kumagaya.saitama.jp
+matsubushi.saitama.jp
+minano.saitama.jp
+misato.saitama.jp
+miyashiro.saitama.jp
+miyoshi.saitama.jp
+moroyama.saitama.jp
+nagatoro.saitama.jp
+namegawa.saitama.jp
+niiza.saitama.jp
+ogano.saitama.jp
+ogawa.saitama.jp
+ogose.saitama.jp
+okegawa.saitama.jp
+omiya.saitama.jp
+otaki.saitama.jp
+ranzan.saitama.jp
+ryokami.saitama.jp
+saitama.saitama.jp
+sakado.saitama.jp
+satte.saitama.jp
+sayama.saitama.jp
+shiki.saitama.jp
+shiraoka.saitama.jp
+soka.saitama.jp
+sugito.saitama.jp
+toda.saitama.jp
+tokigawa.saitama.jp
+tokorozawa.saitama.jp
+tsurugashima.saitama.jp
+urawa.saitama.jp
+warabi.saitama.jp
+yashio.saitama.jp
+yokoze.saitama.jp
+yono.saitama.jp
+yorii.saitama.jp
+yoshida.saitama.jp
+yoshikawa.saitama.jp
+yoshimi.saitama.jp
+aisho.shiga.jp
+gamo.shiga.jp
+higashiomi.shiga.jp
+hikone.shiga.jp
+koka.shiga.jp
+konan.shiga.jp
+kosei.shiga.jp
+koto.shiga.jp
+kusatsu.shiga.jp
+maibara.shiga.jp
+moriyama.shiga.jp
+nagahama.shiga.jp
+nishiazai.shiga.jp
+notogawa.shiga.jp
+omihachiman.shiga.jp
+otsu.shiga.jp
+ritto.shiga.jp
+ryuoh.shiga.jp
+takashima.shiga.jp
+takatsuki.shiga.jp
+torahime.shiga.jp
+toyosato.shiga.jp
+yasu.shiga.jp
+akagi.shimane.jp
+ama.shimane.jp
+gotsu.shimane.jp
+hamada.shimane.jp
+higashiizumo.shimane.jp
+hikawa.shimane.jp
+hikimi.shimane.jp
+izumo.shimane.jp
+kakinoki.shimane.jp
+masuda.shimane.jp
+matsue.shimane.jp
+misato.shimane.jp
+nishinoshima.shimane.jp
+ohda.shimane.jp
+okinoshima.shimane.jp
+okuizumo.shimane.jp
+shimane.shimane.jp
+tamayu.shimane.jp
+tsuwano.shimane.jp
+unnan.shimane.jp
+yakumo.shimane.jp
+yasugi.shimane.jp
+yatsuka.shimane.jp
+arai.shizuoka.jp
+atami.shizuoka.jp
+fuji.shizuoka.jp
+fujieda.shizuoka.jp
+fujikawa.shizuoka.jp
+fujinomiya.shizuoka.jp
+fukuroi.shizuoka.jp
+gotemba.shizuoka.jp
+haibara.shizuoka.jp
+hamamatsu.shizuoka.jp
+higashiizu.shizuoka.jp
+ito.shizuoka.jp
+iwata.shizuoka.jp
+izu.shizuoka.jp
+izunokuni.shizuoka.jp
+kakegawa.shizuoka.jp
+kannami.shizuoka.jp
+kawanehon.shizuoka.jp
+kawazu.shizuoka.jp
+kikugawa.shizuoka.jp
+kosai.shizuoka.jp
+makinohara.shizuoka.jp
+matsuzaki.shizuoka.jp
+minamiizu.shizuoka.jp
+mishima.shizuoka.jp
+morimachi.shizuoka.jp
+nishiizu.shizuoka.jp
+numazu.shizuoka.jp
+omaezaki.shizuoka.jp
+shimada.shizuoka.jp
+shimizu.shizuoka.jp
+shimoda.shizuoka.jp
+shizuoka.shizuoka.jp
+susono.shizuoka.jp
+yaizu.shizuoka.jp
+yoshida.shizuoka.jp
+ashikaga.tochigi.jp
+bato.tochigi.jp
+haga.tochigi.jp
+ichikai.tochigi.jp
+iwafune.tochigi.jp
+kaminokawa.tochigi.jp
+kanuma.tochigi.jp
+karasuyama.tochigi.jp
+kuroiso.tochigi.jp
+mashiko.tochigi.jp
+mibu.tochigi.jp
+moka.tochigi.jp
+motegi.tochigi.jp
+nasu.tochigi.jp
+nasushiobara.tochigi.jp
+nikko.tochigi.jp
+nishikata.tochigi.jp
+nogi.tochigi.jp
+ohira.tochigi.jp
+ohtawara.tochigi.jp
+oyama.tochigi.jp
+sakura.tochigi.jp
+sano.tochigi.jp
+shimotsuke.tochigi.jp
+shioya.tochigi.jp
+takanezawa.tochigi.jp
+tochigi.tochigi.jp
+tsuga.tochigi.jp
+ujiie.tochigi.jp
+utsunomiya.tochigi.jp
+yaita.tochigi.jp
+aizumi.tokushima.jp
+anan.tokushima.jp
+ichiba.tokushima.jp
+itano.tokushima.jp
+kainan.tokushima.jp
+komatsushima.tokushima.jp
+matsushige.tokushima.jp
+mima.tokushima.jp
+minami.tokushima.jp
+miyoshi.tokushima.jp
+mugi.tokushima.jp
+nakagawa.tokushima.jp
+naruto.tokushima.jp
+sanagochi.tokushima.jp
+shishikui.tokushima.jp
+tokushima.tokushima.jp
+wajiki.tokushima.jp
+adachi.tokyo.jp
+akiruno.tokyo.jp
+akishima.tokyo.jp
+aogashima.tokyo.jp
+arakawa.tokyo.jp
+bunkyo.tokyo.jp
+chiyoda.tokyo.jp
+chofu.tokyo.jp
+chuo.tokyo.jp
+edogawa.tokyo.jp
+fuchu.tokyo.jp
+fussa.tokyo.jp
+hachijo.tokyo.jp
+hachioji.tokyo.jp
+hamura.tokyo.jp
+higashikurume.tokyo.jp
+higashimurayama.tokyo.jp
+higashiyamato.tokyo.jp
+hino.tokyo.jp
+hinode.tokyo.jp
+hinohara.tokyo.jp
+inagi.tokyo.jp
+itabashi.tokyo.jp
+katsushika.tokyo.jp
+kita.tokyo.jp
+kiyose.tokyo.jp
+kodaira.tokyo.jp
+koganei.tokyo.jp
+kokubunji.tokyo.jp
+komae.tokyo.jp
+koto.tokyo.jp
+kouzushima.tokyo.jp
+kunitachi.tokyo.jp
+machida.tokyo.jp
+meguro.tokyo.jp
+minato.tokyo.jp
+mitaka.tokyo.jp
+mizuho.tokyo.jp
+musashimurayama.tokyo.jp
+musashino.tokyo.jp
+nakano.tokyo.jp
+nerima.tokyo.jp
+ogasawara.tokyo.jp
+okutama.tokyo.jp
+ome.tokyo.jp
+oshima.tokyo.jp
+ota.tokyo.jp
+setagaya.tokyo.jp
+shibuya.tokyo.jp
+shinagawa.tokyo.jp
+shinjuku.tokyo.jp
+suginami.tokyo.jp
+sumida.tokyo.jp
+tachikawa.tokyo.jp
+taito.tokyo.jp
+tama.tokyo.jp
+toshima.tokyo.jp
+chizu.tottori.jp
+hino.tottori.jp
+kawahara.tottori.jp
+koge.tottori.jp
+kotoura.tottori.jp
+misasa.tottori.jp
+nanbu.tottori.jp
+nichinan.tottori.jp
+sakaiminato.tottori.jp
+tottori.tottori.jp
+wakasa.tottori.jp
+yazu.tottori.jp
+yonago.tottori.jp
+asahi.toyama.jp
+fuchu.toyama.jp
+fukumitsu.toyama.jp
+funahashi.toyama.jp
+himi.toyama.jp
+imizu.toyama.jp
+inami.toyama.jp
+johana.toyama.jp
+kamiichi.toyama.jp
+kurobe.toyama.jp
+nakaniikawa.toyama.jp
+namerikawa.toyama.jp
+nanto.toyama.jp
+nyuzen.toyama.jp
+oyabe.toyama.jp
+taira.toyama.jp
+takaoka.toyama.jp
+tateyama.toyama.jp
+toga.toyama.jp
+tonami.toyama.jp
+toyama.toyama.jp
+unazuki.toyama.jp
+uozu.toyama.jp
+yamada.toyama.jp
+arida.wakayama.jp
+aridagawa.wakayama.jp
+gobo.wakayama.jp
+hashimoto.wakayama.jp
+hidaka.wakayama.jp
+hirogawa.wakayama.jp
+inami.wakayama.jp
+iwade.wakayama.jp
+kainan.wakayama.jp
+kamitonda.wakayama.jp
+katsuragi.wakayama.jp
+kimino.wakayama.jp
+kinokawa.wakayama.jp
+kitayama.wakayama.jp
+koya.wakayama.jp
+koza.wakayama.jp
+kozagawa.wakayama.jp
+kudoyama.wakayama.jp
+kushimoto.wakayama.jp
+mihama.wakayama.jp
+misato.wakayama.jp
+nachikatsuura.wakayama.jp
+shingu.wakayama.jp
+shirahama.wakayama.jp
+taiji.wakayama.jp
+tanabe.wakayama.jp
+wakayama.wakayama.jp
+yuasa.wakayama.jp
+yura.wakayama.jp
+asahi.yamagata.jp
+funagata.yamagata.jp
+higashine.yamagata.jp
+iide.yamagata.jp
+kahoku.yamagata.jp
+kaminoyama.yamagata.jp
+kaneyama.yamagata.jp
+kawanishi.yamagata.jp
+mamurogawa.yamagata.jp
+mikawa.yamagata.jp
+murayama.yamagata.jp
+nagai.yamagata.jp
+nakayama.yamagata.jp
+nanyo.yamagata.jp
+nishikawa.yamagata.jp
+obanazawa.yamagata.jp
+oe.yamagata.jp
+oguni.yamagata.jp
+ohkura.yamagata.jp
+oishida.yamagata.jp
+sagae.yamagata.jp
+sakata.yamagata.jp
+sakegawa.yamagata.jp
+shinjo.yamagata.jp
+shirataka.yamagata.jp
+shonai.yamagata.jp
+takahata.yamagata.jp
+tendo.yamagata.jp
+tozawa.yamagata.jp
+tsuruoka.yamagata.jp
+yamagata.yamagata.jp
+yamanobe.yamagata.jp
+yonezawa.yamagata.jp
+yuza.yamagata.jp
+abu.yamaguchi.jp
+hagi.yamaguchi.jp
+hikari.yamaguchi.jp
+hofu.yamaguchi.jp
+iwakuni.yamaguchi.jp
+kudamatsu.yamaguchi.jp
+mitou.yamaguchi.jp
+nagato.yamaguchi.jp
+oshima.yamaguchi.jp
+shimonoseki.yamaguchi.jp
+shunan.yamaguchi.jp
+tabuse.yamaguchi.jp
+tokuyama.yamaguchi.jp
+toyota.yamaguchi.jp
+ube.yamaguchi.jp
+yuu.yamaguchi.jp
+chuo.yamanashi.jp
+doshi.yamanashi.jp
+fuefuki.yamanashi.jp
+fujikawa.yamanashi.jp
+fujikawaguchiko.yamanashi.jp
+fujiyoshida.yamanashi.jp
+hayakawa.yamanashi.jp
+hokuto.yamanashi.jp
+ichikawamisato.yamanashi.jp
+kai.yamanashi.jp
+kofu.yamanashi.jp
+koshu.yamanashi.jp
+kosuge.yamanashi.jp
+minami-alps.yamanashi.jp
+minobu.yamanashi.jp
+nakamichi.yamanashi.jp
+nanbu.yamanashi.jp
+narusawa.yamanashi.jp
+nirasaki.yamanashi.jp
+nishikatsura.yamanashi.jp
+oshino.yamanashi.jp
+otsuki.yamanashi.jp
+showa.yamanashi.jp
+tabayama.yamanashi.jp
+tsuru.yamanashi.jp
+uenohara.yamanashi.jp
+yamanakako.yamanashi.jp
+yamanashi.yamanashi.jp
+
+// ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains
+ke
+ac.ke
+co.ke
+go.ke
+info.ke
+me.ke
+mobi.ke
+ne.ke
+or.ke
+sc.ke
+
+// kg : http://www.domain.kg/dmn_n.html
+kg
+org.kg
+net.kg
+com.kg
+edu.kg
+gov.kg
+mil.kg
+
+// kh : http://www.mptc.gov.kh/dns_registration.htm
+*.kh
+
+// ki : http://www.ki/dns/index.html
+ki
+edu.ki
+biz.ki
+net.ki
+org.ki
+gov.ki
+info.ki
+com.ki
+
+// km : https://en.wikipedia.org/wiki/.km
+// http://www.domaine.km/documents/charte.doc
+km
+org.km
+nom.km
+gov.km
+prd.km
+tm.km
+edu.km
+mil.km
+ass.km
+com.km
+// These are only mentioned as proposed suggestions at domaine.km, but
+// https://en.wikipedia.org/wiki/.km says they're available for registration:
+coop.km
+asso.km
+presse.km
+medecin.km
+notaires.km
+pharmaciens.km
+veterinaire.km
+gouv.km
+
+// kn : https://en.wikipedia.org/wiki/.kn
+// http://www.dot.kn/domainRules.html
+kn
+net.kn
+org.kn
+edu.kn
+gov.kn
+
+// kp : http://www.kcce.kp/en_index.php
+kp
+com.kp
+edu.kp
+gov.kp
+org.kp
+rep.kp
+tra.kp
+
+// kr : https://en.wikipedia.org/wiki/.kr
+// see also: http://domain.nida.or.kr/eng/registration.jsp
+kr
+ac.kr
+co.kr
+es.kr
+go.kr
+hs.kr
+kg.kr
+mil.kr
+ms.kr
+ne.kr
+or.kr
+pe.kr
+re.kr
+sc.kr
+// kr geographical names
+busan.kr
+chungbuk.kr
+chungnam.kr
+daegu.kr
+daejeon.kr
+gangwon.kr
+gwangju.kr
+gyeongbuk.kr
+gyeonggi.kr
+gyeongnam.kr
+incheon.kr
+jeju.kr
+jeonbuk.kr
+jeonnam.kr
+seoul.kr
+ulsan.kr
+
+// kw : https://www.nic.kw/policies/
+// Confirmed by registry <nic.tech@citra.gov.kw>
+kw
+com.kw
+edu.kw
+emb.kw
+gov.kw
+ind.kw
+net.kw
+org.kw
+
+// ky : http://www.icta.ky/da_ky_reg_dom.php
+// Confirmed by registry <kysupport@perimeterusa.com> 2008-06-17
+ky
+com.ky
+edu.ky
+net.ky
+org.ky
+
+// kz : https://en.wikipedia.org/wiki/.kz
+// see also: http://www.nic.kz/rules/index.jsp
+kz
+org.kz
+edu.kz
+net.kz
+gov.kz
+mil.kz
+com.kz
+
+// la : https://en.wikipedia.org/wiki/.la
+// Submitted by registry <gavin.brown@nic.la>
+la
+int.la
+net.la
+info.la
+edu.la
+gov.la
+per.la
+com.la
+org.la
+
+// lb : https://en.wikipedia.org/wiki/.lb
+// Submitted by registry <randy@psg.com>
+lb
+com.lb
+edu.lb
+gov.lb
+net.lb
+org.lb
+
+// lc : https://en.wikipedia.org/wiki/.lc
+// see also: http://www.nic.lc/rules.htm
+lc
+com.lc
+net.lc
+co.lc
+org.lc
+edu.lc
+gov.lc
+
+// li : https://en.wikipedia.org/wiki/.li
+li
+
+// lk : https://www.nic.lk/index.php/domain-registration/lk-domain-naming-structure
+lk
+gov.lk
+sch.lk
+net.lk
+int.lk
+com.lk
+org.lk
+edu.lk
+ngo.lk
+soc.lk
+web.lk
+ltd.lk
+assn.lk
+grp.lk
+hotel.lk
+ac.lk
+
+// lr : http://psg.com/dns/lr/lr.txt
+// Submitted by registry <randy@psg.com>
+lr
+com.lr
+edu.lr
+gov.lr
+org.lr
+net.lr
+
+// ls : http://www.nic.ls/
+// Confirmed by registry <lsadmin@nic.ls>
+ls
+ac.ls
+biz.ls
+co.ls
+edu.ls
+gov.ls
+info.ls
+net.ls
+org.ls
+sc.ls
+
+// lt : https://en.wikipedia.org/wiki/.lt
+lt
+// gov.lt : http://www.gov.lt/index_en.php
+gov.lt
+
+// lu : http://www.dns.lu/en/
+lu
+
+// lv : http://www.nic.lv/DNS/En/generic.php
+lv
+com.lv
+edu.lv
+gov.lv
+org.lv
+mil.lv
+id.lv
+net.lv
+asn.lv
+conf.lv
+
+// ly : http://www.nic.ly/regulations.php
+ly
+com.ly
+net.ly
+gov.ly
+plc.ly
+edu.ly
+sch.ly
+med.ly
+org.ly
+id.ly
+
+// ma : https://en.wikipedia.org/wiki/.ma
+// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf
+ma
+co.ma
+net.ma
+gov.ma
+org.ma
+ac.ma
+press.ma
+
+// mc : http://www.nic.mc/
+mc
+tm.mc
+asso.mc
+
+// md : https://en.wikipedia.org/wiki/.md
+md
+
+// me : https://en.wikipedia.org/wiki/.me
+me
+co.me
+net.me
+org.me
+edu.me
+ac.me
+gov.me
+its.me
+priv.me
+
+// mg : http://nic.mg/nicmg/?page_id=39
+mg
+org.mg
+nom.mg
+gov.mg
+prd.mg
+tm.mg
+edu.mg
+mil.mg
+com.mg
+co.mg
+
+// mh : https://en.wikipedia.org/wiki/.mh
+mh
+
+// mil : https://en.wikipedia.org/wiki/.mil
+mil
+
+// mk : https://en.wikipedia.org/wiki/.mk
+// see also: http://dns.marnet.net.mk/postapka.php
+mk
+com.mk
+org.mk
+net.mk
+edu.mk
+gov.mk
+inf.mk
+name.mk
+
+// ml : http://www.gobin.info/domainname/ml-template.doc
+// see also: https://en.wikipedia.org/wiki/.ml
+ml
+com.ml
+edu.ml
+gouv.ml
+gov.ml
+net.ml
+org.ml
+presse.ml
+
+// mm : https://en.wikipedia.org/wiki/.mm
+*.mm
+
+// mn : https://en.wikipedia.org/wiki/.mn
+mn
+gov.mn
+edu.mn
+org.mn
+
+// mo : http://www.monic.net.mo/
+mo
+com.mo
+net.mo
+org.mo
+edu.mo
+gov.mo
+
+// mobi : https://en.wikipedia.org/wiki/.mobi
+mobi
+
+// mp : http://www.dot.mp/
+// Confirmed by registry <dcamacho@saipan.com> 2008-06-17
+mp
+
+// mq : https://en.wikipedia.org/wiki/.mq
+mq
+
+// mr : https://en.wikipedia.org/wiki/.mr
+mr
+gov.mr
+
+// ms : http://www.nic.ms/pdf/MS_Domain_Name_Rules.pdf
+ms
+com.ms
+edu.ms
+gov.ms
+net.ms
+org.ms
+
+// mt : https://www.nic.org.mt/go/policy
+// Submitted by registry <help@nic.org.mt>
+mt
+com.mt
+edu.mt
+net.mt
+org.mt
+
+// mu : https://en.wikipedia.org/wiki/.mu
+mu
+com.mu
+net.mu
+org.mu
+gov.mu
+ac.mu
+co.mu
+or.mu
+
+// museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/
+museum
+
+// mv : https://en.wikipedia.org/wiki/.mv
+// "mv" included because, contra Wikipedia, google.mv exists.
+mv
+aero.mv
+biz.mv
+com.mv
+coop.mv
+edu.mv
+gov.mv
+info.mv
+int.mv
+mil.mv
+museum.mv
+name.mv
+net.mv
+org.mv
+pro.mv
+
+// mw : http://www.registrar.mw/
+mw
+ac.mw
+biz.mw
+co.mw
+com.mw
+coop.mw
+edu.mw
+gov.mw
+int.mw
+museum.mw
+net.mw
+org.mw
+
+// mx : http://www.nic.mx/
+// Submitted by registry <farias@nic.mx>
+mx
+com.mx
+org.mx
+gob.mx
+edu.mx
+net.mx
+
+// my : http://www.mynic.my/
+// Available strings: https://mynic.my/resources/domains/buying-a-domain/
+my
+biz.my
+com.my
+edu.my
+gov.my
+mil.my
+name.my
+net.my
+org.my
+
+// mz : http://www.uem.mz/
+// Submitted by registry <antonio@uem.mz>
+mz
+ac.mz
+adv.mz
+co.mz
+edu.mz
+gov.mz
+mil.mz
+net.mz
+org.mz
+
+// na : http://www.na-nic.com.na/
+// http://www.info.na/domain/
+na
+info.na
+pro.na
+name.na
+school.na
+or.na
+dr.na
+us.na
+mx.na
+ca.na
+in.na
+cc.na
+tv.na
+ws.na
+mobi.na
+co.na
+com.na
+org.na
+
+// name : has 2nd-level tlds, but there's no list of them
+name
+
+// nc : http://www.cctld.nc/
+nc
+asso.nc
+nom.nc
+
+// ne : https://en.wikipedia.org/wiki/.ne
+ne
+
+// net : https://en.wikipedia.org/wiki/.net
+net
+
+// nf : https://en.wikipedia.org/wiki/.nf
+nf
+com.nf
+net.nf
+per.nf
+rec.nf
+web.nf
+arts.nf
+firm.nf
+info.nf
+other.nf
+store.nf
+
+// ng : http://www.nira.org.ng/index.php/join-us/register-ng-domain/189-nira-slds
+ng
+com.ng
+edu.ng
+gov.ng
+i.ng
+mil.ng
+mobi.ng
+name.ng
+net.ng
+org.ng
+sch.ng
+
+// ni : http://www.nic.ni/
+ni
+ac.ni
+biz.ni
+co.ni
+com.ni
+edu.ni
+gob.ni
+in.ni
+info.ni
+int.ni
+mil.ni
+net.ni
+nom.ni
+org.ni
+web.ni
+
+// nl : https://en.wikipedia.org/wiki/.nl
+// https://www.sidn.nl/
+// ccTLD for the Netherlands
+nl
+
+// no : https://www.norid.no/en/om-domenenavn/regelverk-for-no/
+// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/
+// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/
+// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/
+// RSS feed: https://teknisk.norid.no/en/feed/
+no
+// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/
+fhs.no
+vgs.no
+fylkesbibl.no
+folkebibl.no
+museum.no
+idrett.no
+priv.no
+// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/
+mil.no
+stat.no
+dep.no
+kommune.no
+herad.no
+// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/
+// counties
+aa.no
+ah.no
+bu.no
+fm.no
+hl.no
+hm.no
+jan-mayen.no
+mr.no
+nl.no
+nt.no
+of.no
+ol.no
+oslo.no
+rl.no
+sf.no
+st.no
+svalbard.no
+tm.no
+tr.no
+va.no
+vf.no
+// primary and lower secondary schools per county
+gs.aa.no
+gs.ah.no
+gs.bu.no
+gs.fm.no
+gs.hl.no
+gs.hm.no
+gs.jan-mayen.no
+gs.mr.no
+gs.nl.no
+gs.nt.no
+gs.of.no
+gs.ol.no
+gs.oslo.no
+gs.rl.no
+gs.sf.no
+gs.st.no
+gs.svalbard.no
+gs.tm.no
+gs.tr.no
+gs.va.no
+gs.vf.no
+// cities
+akrehamn.no
+åkrehamn.no
+algard.no
+ålgård.no
+arna.no
+brumunddal.no
+bryne.no
+bronnoysund.no
+brønnøysund.no
+drobak.no
+drøbak.no
+egersund.no
+fetsund.no
+floro.no
+florø.no
+fredrikstad.no
+hokksund.no
+honefoss.no
+hønefoss.no
+jessheim.no
+jorpeland.no
+jørpeland.no
+kirkenes.no
+kopervik.no
+krokstadelva.no
+langevag.no
+langevåg.no
+leirvik.no
+mjondalen.no
+mjøndalen.no
+mo-i-rana.no
+mosjoen.no
+mosjøen.no
+nesoddtangen.no
+orkanger.no
+osoyro.no
+osøyro.no
+raholt.no
+råholt.no
+sandnessjoen.no
+sandnessjøen.no
+skedsmokorset.no
+slattum.no
+spjelkavik.no
+stathelle.no
+stavern.no
+stjordalshalsen.no
+stjørdalshalsen.no
+tananger.no
+tranby.no
+vossevangen.no
+// communities
+afjord.no
+åfjord.no
+agdenes.no
+al.no
+ål.no
+alesund.no
+ålesund.no
+alstahaug.no
+alta.no
+áltá.no
+alaheadju.no
+álaheadju.no
+alvdal.no
+amli.no
+åmli.no
+amot.no
+åmot.no
+andebu.no
+andoy.no
+andøy.no
+andasuolo.no
+ardal.no
+årdal.no
+aremark.no
+arendal.no
+ås.no
+aseral.no
+åseral.no
+asker.no
+askim.no
+askvoll.no
+askoy.no
+askøy.no
+asnes.no
+åsnes.no
+audnedaln.no
+aukra.no
+aure.no
+aurland.no
+aurskog-holand.no
+aurskog-høland.no
+austevoll.no
+austrheim.no
+averoy.no
+averøy.no
+balestrand.no
+ballangen.no
+balat.no
+bálát.no
+balsfjord.no
+bahccavuotna.no
+báhccavuotna.no
+bamble.no
+bardu.no
+beardu.no
+beiarn.no
+bajddar.no
+bájddar.no
+baidar.no
+báidár.no
+berg.no
+bergen.no
+berlevag.no
+berlevåg.no
+bearalvahki.no
+bearalváhki.no
+bindal.no
+birkenes.no
+bjarkoy.no
+bjarkøy.no
+bjerkreim.no
+bjugn.no
+bodo.no
+bodø.no
+badaddja.no
+bådåddjå.no
+budejju.no
+bokn.no
+bremanger.no
+bronnoy.no
+brønnøy.no
+bygland.no
+bykle.no
+barum.no
+bærum.no
+bo.telemark.no
+bø.telemark.no
+bo.nordland.no
+bø.nordland.no
+bievat.no
+bievát.no
+bomlo.no
+bømlo.no
+batsfjord.no
+båtsfjord.no
+bahcavuotna.no
+báhcavuotna.no
+dovre.no
+drammen.no
+drangedal.no
+dyroy.no
+dyrøy.no
+donna.no
+dønna.no
+eid.no
+eidfjord.no
+eidsberg.no
+eidskog.no
+eidsvoll.no
+eigersund.no
+elverum.no
+enebakk.no
+engerdal.no
+etne.no
+etnedal.no
+evenes.no
+evenassi.no
+evenášši.no
+evje-og-hornnes.no
+farsund.no
+fauske.no
+fuossko.no
+fuoisku.no
+fedje.no
+fet.no
+finnoy.no
+finnøy.no
+fitjar.no
+fjaler.no
+fjell.no
+flakstad.no
+flatanger.no
+flekkefjord.no
+flesberg.no
+flora.no
+fla.no
+flå.no
+folldal.no
+forsand.no
+fosnes.no
+frei.no
+frogn.no
+froland.no
+frosta.no
+frana.no
+fræna.no
+froya.no
+frøya.no
+fusa.no
+fyresdal.no
+forde.no
+førde.no
+gamvik.no
+gangaviika.no
+gáŋgaviika.no
+gaular.no
+gausdal.no
+gildeskal.no
+gildeskål.no
+giske.no
+gjemnes.no
+gjerdrum.no
+gjerstad.no
+gjesdal.no
+gjovik.no
+gjøvik.no
+gloppen.no
+gol.no
+gran.no
+grane.no
+granvin.no
+gratangen.no
+grimstad.no
+grong.no
+kraanghke.no
+kråanghke.no
+grue.no
+gulen.no
+hadsel.no
+halden.no
+halsa.no
+hamar.no
+hamaroy.no
+habmer.no
+hábmer.no
+hapmir.no
+hápmir.no
+hammerfest.no
+hammarfeasta.no
+hámmárfeasta.no
+haram.no
+hareid.no
+harstad.no
+hasvik.no
+aknoluokta.no
+ákŋoluokta.no
+hattfjelldal.no
+aarborte.no
+haugesund.no
+hemne.no
+hemnes.no
+hemsedal.no
+heroy.more-og-romsdal.no
+herøy.møre-og-romsdal.no
+heroy.nordland.no
+herøy.nordland.no
+hitra.no
+hjartdal.no
+hjelmeland.no
+hobol.no
+hobøl.no
+hof.no
+hol.no
+hole.no
+holmestrand.no
+holtalen.no
+holtålen.no
+hornindal.no
+horten.no
+hurdal.no
+hurum.no
+hvaler.no
+hyllestad.no
+hagebostad.no
+hægebostad.no
+hoyanger.no
+høyanger.no
+hoylandet.no
+høylandet.no
+ha.no
+hå.no
+ibestad.no
+inderoy.no
+inderøy.no
+iveland.no
+jevnaker.no
+jondal.no
+jolster.no
+jølster.no
+karasjok.no
+karasjohka.no
+kárášjohka.no
+karlsoy.no
+galsa.no
+gálsá.no
+karmoy.no
+karmøy.no
+kautokeino.no
+guovdageaidnu.no
+klepp.no
+klabu.no
+klæbu.no
+kongsberg.no
+kongsvinger.no
+kragero.no
+kragerø.no
+kristiansand.no
+kristiansund.no
+krodsherad.no
+krødsherad.no
+kvalsund.no
+rahkkeravju.no
+ráhkkerávju.no
+kvam.no
+kvinesdal.no
+kvinnherad.no
+kviteseid.no
+kvitsoy.no
+kvitsøy.no
+kvafjord.no
+kvæfjord.no
+giehtavuoatna.no
+kvanangen.no
+kvænangen.no
+navuotna.no
+návuotna.no
+kafjord.no
+kåfjord.no
+gaivuotna.no
+gáivuotna.no
+larvik.no
+lavangen.no
+lavagis.no
+loabat.no
+loabát.no
+lebesby.no
+davvesiida.no
+leikanger.no
+leirfjord.no
+leka.no
+leksvik.no
+lenvik.no
+leangaviika.no
+leaŋgaviika.no
+lesja.no
+levanger.no
+lier.no
+lierne.no
+lillehammer.no
+lillesand.no
+lindesnes.no
+lindas.no
+lindås.no
+lom.no
+loppa.no
+lahppi.no
+láhppi.no
+lund.no
+lunner.no
+luroy.no
+lurøy.no
+luster.no
+lyngdal.no
+lyngen.no
+ivgu.no
+lardal.no
+lerdal.no
+lærdal.no
+lodingen.no
+lødingen.no
+lorenskog.no
+lørenskog.no
+loten.no
+løten.no
+malvik.no
+masoy.no
+måsøy.no
+muosat.no
+muosát.no
+mandal.no
+marker.no
+marnardal.no
+masfjorden.no
+meland.no
+meldal.no
+melhus.no
+meloy.no
+meløy.no
+meraker.no
+meråker.no
+moareke.no
+moåreke.no
+midsund.no
+midtre-gauldal.no
+modalen.no
+modum.no
+molde.no
+moskenes.no
+moss.no
+mosvik.no
+malselv.no
+målselv.no
+malatvuopmi.no
+málatvuopmi.no
+namdalseid.no
+aejrie.no
+namsos.no
+namsskogan.no
+naamesjevuemie.no
+nååmesjevuemie.no
+laakesvuemie.no
+nannestad.no
+narvik.no
+narviika.no
+naustdal.no
+nedre-eiker.no
+nes.akershus.no
+nes.buskerud.no
+nesna.no
+nesodden.no
+nesseby.no
+unjarga.no
+unjárga.no
+nesset.no
+nissedal.no
+nittedal.no
+nord-aurdal.no
+nord-fron.no
+nord-odal.no
+norddal.no
+nordkapp.no
+davvenjarga.no
+davvenjárga.no
+nordre-land.no
+nordreisa.no
+raisa.no
+ráisa.no
+nore-og-uvdal.no
+notodden.no
+naroy.no
+nærøy.no
+notteroy.no
+nøtterøy.no
+odda.no
+oksnes.no
+øksnes.no
+oppdal.no
+oppegard.no
+oppegård.no
+orkdal.no
+orland.no
+ørland.no
+orskog.no
+ørskog.no
+orsta.no
+ørsta.no
+os.hedmark.no
+os.hordaland.no
+osen.no
+osteroy.no
+osterøy.no
+ostre-toten.no
+østre-toten.no
+overhalla.no
+ovre-eiker.no
+øvre-eiker.no
+oyer.no
+øyer.no
+oygarden.no
+øygarden.no
+oystre-slidre.no
+øystre-slidre.no
+porsanger.no
+porsangu.no
+porsáŋgu.no
+porsgrunn.no
+radoy.no
+radøy.no
+rakkestad.no
+rana.no
+ruovat.no
+randaberg.no
+rauma.no
+rendalen.no
+rennebu.no
+rennesoy.no
+rennesøy.no
+rindal.no
+ringebu.no
+ringerike.no
+ringsaker.no
+rissa.no
+risor.no
+risør.no
+roan.no
+rollag.no
+rygge.no
+ralingen.no
+rælingen.no
+rodoy.no
+rødøy.no
+romskog.no
+rømskog.no
+roros.no
+røros.no
+rost.no
+røst.no
+royken.no
+røyken.no
+royrvik.no
+røyrvik.no
+rade.no
+råde.no
+salangen.no
+siellak.no
+saltdal.no
+salat.no
+sálát.no
+sálat.no
+samnanger.no
+sande.more-og-romsdal.no
+sande.møre-og-romsdal.no
+sande.vestfold.no
+sandefjord.no
+sandnes.no
+sandoy.no
+sandøy.no
+sarpsborg.no
+sauda.no
+sauherad.no
+sel.no
+selbu.no
+selje.no
+seljord.no
+sigdal.no
+siljan.no
+sirdal.no
+skaun.no
+skedsmo.no
+ski.no
+skien.no
+skiptvet.no
+skjervoy.no
+skjervøy.no
+skierva.no
+skiervá.no
+skjak.no
+skjåk.no
+skodje.no
+skanland.no
+skånland.no
+skanit.no
+skánit.no
+smola.no
+smøla.no
+snillfjord.no
+snasa.no
+snåsa.no
+snoasa.no
+snaase.no
+snåase.no
+sogndal.no
+sokndal.no
+sola.no
+solund.no
+songdalen.no
+sortland.no
+spydeberg.no
+stange.no
+stavanger.no
+steigen.no
+steinkjer.no
+stjordal.no
+stjørdal.no
+stokke.no
+stor-elvdal.no
+stord.no
+stordal.no
+storfjord.no
+omasvuotna.no
+strand.no
+stranda.no
+stryn.no
+sula.no
+suldal.no
+sund.no
+sunndal.no
+surnadal.no
+sveio.no
+svelvik.no
+sykkylven.no
+sogne.no
+søgne.no
+somna.no
+sømna.no
+sondre-land.no
+søndre-land.no
+sor-aurdal.no
+sør-aurdal.no
+sor-fron.no
+sør-fron.no
+sor-odal.no
+sør-odal.no
+sor-varanger.no
+sør-varanger.no
+matta-varjjat.no
+mátta-várjjat.no
+sorfold.no
+sørfold.no
+sorreisa.no
+sørreisa.no
+sorum.no
+sørum.no
+tana.no
+deatnu.no
+time.no
+tingvoll.no
+tinn.no
+tjeldsund.no
+dielddanuorri.no
+tjome.no
+tjøme.no
+tokke.no
+tolga.no
+torsken.no
+tranoy.no
+tranøy.no
+tromso.no
+tromsø.no
+tromsa.no
+romsa.no
+trondheim.no
+troandin.no
+trysil.no
+trana.no
+træna.no
+trogstad.no
+trøgstad.no
+tvedestrand.no
+tydal.no
+tynset.no
+tysfjord.no
+divtasvuodna.no
+divttasvuotna.no
+tysnes.no
+tysvar.no
+tysvær.no
+tonsberg.no
+tønsberg.no
+ullensaker.no
+ullensvang.no
+ulvik.no
+utsira.no
+vadso.no
+vadsø.no
+cahcesuolo.no
+čáhcesuolo.no
+vaksdal.no
+valle.no
+vang.no
+vanylven.no
+vardo.no
+vardø.no
+varggat.no
+várggát.no
+vefsn.no
+vaapste.no
+vega.no
+vegarshei.no
+vegårshei.no
+vennesla.no
+verdal.no
+verran.no
+vestby.no
+vestnes.no
+vestre-slidre.no
+vestre-toten.no
+vestvagoy.no
+vestvågøy.no
+vevelstad.no
+vik.no
+vikna.no
+vindafjord.no
+volda.no
+voss.no
+varoy.no
+værøy.no
+vagan.no
+vågan.no
+voagat.no
+vagsoy.no
+vågsøy.no
+vaga.no
+vågå.no
+valer.ostfold.no
+våler.østfold.no
+valer.hedmark.no
+våler.hedmark.no
+
+// np : http://www.mos.com.np/register.html
+*.np
+
+// nr : http://cenpac.net.nr/dns/index.html
+// Submitted by registry <technician@cenpac.net.nr>
+nr
+biz.nr
+info.nr
+gov.nr
+edu.nr
+org.nr
+net.nr
+com.nr
+
+// nu : https://en.wikipedia.org/wiki/.nu
+nu
+
+// nz : https://en.wikipedia.org/wiki/.nz
+// Submitted by registry <jay@nzrs.net.nz>
+nz
+ac.nz
+co.nz
+cri.nz
+geek.nz
+gen.nz
+govt.nz
+health.nz
+iwi.nz
+kiwi.nz
+maori.nz
+mil.nz
+māori.nz
+net.nz
+org.nz
+parliament.nz
+school.nz
+
+// om : https://en.wikipedia.org/wiki/.om
+om
+co.om
+com.om
+edu.om
+gov.om
+med.om
+museum.om
+net.om
+org.om
+pro.om
+
+// onion : https://tools.ietf.org/html/rfc7686
+onion
+
+// org : https://en.wikipedia.org/wiki/.org
+org
+
+// pa : http://www.nic.pa/
+// Some additional second level "domains" resolve directly as hostnames, such as
+// pannet.pa, so we add a rule for "pa".
+pa
+ac.pa
+gob.pa
+com.pa
+org.pa
+sld.pa
+edu.pa
+net.pa
+ing.pa
+abo.pa
+med.pa
+nom.pa
+
+// pe : https://www.nic.pe/InformeFinalComision.pdf
+pe
+edu.pe
+gob.pe
+nom.pe
+mil.pe
+org.pe
+com.pe
+net.pe
+
+// pf : http://www.gobin.info/domainname/formulaire-pf.pdf
+pf
+com.pf
+org.pf
+edu.pf
+
+// pg : https://en.wikipedia.org/wiki/.pg
+*.pg
+
+// ph : http://www.domains.ph/FAQ2.asp
+// Submitted by registry <jed@email.com.ph>
+ph
+com.ph
+net.ph
+org.ph
+gov.ph
+edu.ph
+ngo.ph
+mil.ph
+i.ph
+
+// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK
+pk
+com.pk
+net.pk
+edu.pk
+org.pk
+fam.pk
+biz.pk
+web.pk
+gov.pk
+gob.pk
+gok.pk
+gon.pk
+gop.pk
+gos.pk
+info.pk
+
+// pl http://www.dns.pl/english/index.html
+// Submitted by registry
+pl
+com.pl
+net.pl
+org.pl
+// pl functional domains (http://www.dns.pl/english/index.html)
+aid.pl
+agro.pl
+atm.pl
+auto.pl
+biz.pl
+edu.pl
+gmina.pl
+gsm.pl
+info.pl
+mail.pl
+miasta.pl
+media.pl
+mil.pl
+nieruchomosci.pl
+nom.pl
+pc.pl
+powiat.pl
+priv.pl
+realestate.pl
+rel.pl
+sex.pl
+shop.pl
+sklep.pl
+sos.pl
+szkola.pl
+targi.pl
+tm.pl
+tourism.pl
+travel.pl
+turystyka.pl
+// Government domains
+gov.pl
+ap.gov.pl
+griw.gov.pl
+ic.gov.pl
+is.gov.pl
+kmpsp.gov.pl
+konsulat.gov.pl
+kppsp.gov.pl
+kwp.gov.pl
+kwpsp.gov.pl
+mup.gov.pl
+mw.gov.pl
+oia.gov.pl
+oirm.gov.pl
+oke.gov.pl
+oow.gov.pl
+oschr.gov.pl
+oum.gov.pl
+pa.gov.pl
+pinb.gov.pl
+piw.gov.pl
+po.gov.pl
+pr.gov.pl
+psp.gov.pl
+psse.gov.pl
+pup.gov.pl
+rzgw.gov.pl
+sa.gov.pl
+sdn.gov.pl
+sko.gov.pl
+so.gov.pl
+sr.gov.pl
+starostwo.gov.pl
+ug.gov.pl
+ugim.gov.pl
+um.gov.pl
+umig.gov.pl
+upow.gov.pl
+uppo.gov.pl
+us.gov.pl
+uw.gov.pl
+uzs.gov.pl
+wif.gov.pl
+wiih.gov.pl
+winb.gov.pl
+wios.gov.pl
+witd.gov.pl
+wiw.gov.pl
+wkz.gov.pl
+wsa.gov.pl
+wskr.gov.pl
+wsse.gov.pl
+wuoz.gov.pl
+wzmiuw.gov.pl
+zp.gov.pl
+zpisdn.gov.pl
+// pl regional domains (http://www.dns.pl/english/index.html)
+augustow.pl
+babia-gora.pl
+bedzin.pl
+beskidy.pl
+bialowieza.pl
+bialystok.pl
+bielawa.pl
+bieszczady.pl
+boleslawiec.pl
+bydgoszcz.pl
+bytom.pl
+cieszyn.pl
+czeladz.pl
+czest.pl
+dlugoleka.pl
+elblag.pl
+elk.pl
+glogow.pl
+gniezno.pl
+gorlice.pl
+grajewo.pl
+ilawa.pl
+jaworzno.pl
+jelenia-gora.pl
+jgora.pl
+kalisz.pl
+kazimierz-dolny.pl
+karpacz.pl
+kartuzy.pl
+kaszuby.pl
+katowice.pl
+kepno.pl
+ketrzyn.pl
+klodzko.pl
+kobierzyce.pl
+kolobrzeg.pl
+konin.pl
+konskowola.pl
+kutno.pl
+lapy.pl
+lebork.pl
+legnica.pl
+lezajsk.pl
+limanowa.pl
+lomza.pl
+lowicz.pl
+lubin.pl
+lukow.pl
+malbork.pl
+malopolska.pl
+mazowsze.pl
+mazury.pl
+mielec.pl
+mielno.pl
+mragowo.pl
+naklo.pl
+nowaruda.pl
+nysa.pl
+olawa.pl
+olecko.pl
+olkusz.pl
+olsztyn.pl
+opoczno.pl
+opole.pl
+ostroda.pl
+ostroleka.pl
+ostrowiec.pl
+ostrowwlkp.pl
+pila.pl
+pisz.pl
+podhale.pl
+podlasie.pl
+polkowice.pl
+pomorze.pl
+pomorskie.pl
+prochowice.pl
+pruszkow.pl
+przeworsk.pl
+pulawy.pl
+radom.pl
+rawa-maz.pl
+rybnik.pl
+rzeszow.pl
+sanok.pl
+sejny.pl
+slask.pl
+slupsk.pl
+sosnowiec.pl
+stalowa-wola.pl
+skoczow.pl
+starachowice.pl
+stargard.pl
+suwalki.pl
+swidnica.pl
+swiebodzin.pl
+swinoujscie.pl
+szczecin.pl
+szczytno.pl
+tarnobrzeg.pl
+tgory.pl
+turek.pl
+tychy.pl
+ustka.pl
+walbrzych.pl
+warmia.pl
+warszawa.pl
+waw.pl
+wegrow.pl
+wielun.pl
+wlocl.pl
+wloclawek.pl
+wodzislaw.pl
+wolomin.pl
+wroclaw.pl
+zachpomor.pl
+zagan.pl
+zarow.pl
+zgora.pl
+zgorzelec.pl
+
+// pm : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
+pm
+
+// pn : http://www.government.pn/PnRegistry/policies.htm
+pn
+gov.pn
+co.pn
+org.pn
+edu.pn
+net.pn
+
+// post : https://en.wikipedia.org/wiki/.post
+post
+
+// pr : http://www.nic.pr/index.asp?f=1
+pr
+com.pr
+net.pr
+org.pr
+gov.pr
+edu.pr
+isla.pr
+pro.pr
+biz.pr
+info.pr
+name.pr
+// these aren't mentioned on nic.pr, but on https://en.wikipedia.org/wiki/.pr
+est.pr
+prof.pr
+ac.pr
+
+// pro : http://registry.pro/get-pro
+pro
+aaa.pro
+aca.pro
+acct.pro
+avocat.pro
+bar.pro
+cpa.pro
+eng.pro
+jur.pro
+law.pro
+med.pro
+recht.pro
+
+// ps : https://en.wikipedia.org/wiki/.ps
+// http://www.nic.ps/registration/policy.html#reg
+ps
+edu.ps
+gov.ps
+sec.ps
+plo.ps
+com.ps
+org.ps
+net.ps
+
+// pt : https://www.dns.pt/en/domain/pt-terms-and-conditions-registration-rules/
+pt
+net.pt
+gov.pt
+org.pt
+edu.pt
+int.pt
+publ.pt
+com.pt
+nome.pt
+
+// pw : https://en.wikipedia.org/wiki/.pw
+pw
+co.pw
+ne.pw
+or.pw
+ed.pw
+go.pw
+belau.pw
+
+// py : http://www.nic.py/pautas.html#seccion_9
+// Submitted by registry
+py
+com.py
+coop.py
+edu.py
+gov.py
+mil.py
+net.py
+org.py
+
+// qa : http://domains.qa/en/
+qa
+com.qa
+edu.qa
+gov.qa
+mil.qa
+name.qa
+net.qa
+org.qa
+sch.qa
+
+// re : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
+re
+asso.re
+com.re
+nom.re
+
+// ro : http://www.rotld.ro/
+ro
+arts.ro
+com.ro
+firm.ro
+info.ro
+nom.ro
+nt.ro
+org.ro
+rec.ro
+store.ro
+tm.ro
+www.ro
+
+// rs : https://www.rnids.rs/en/domains/national-domains
+rs
+ac.rs
+co.rs
+edu.rs
+gov.rs
+in.rs
+org.rs
+
+// ru : https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf
+// Submitted by George Georgievsky <gug@cctld.ru>
+ru
+
+// rw : https://www.ricta.org.rw/sites/default/files/resources/registry_registrar_contract_0.pdf
+rw
+ac.rw
+co.rw
+coop.rw
+gov.rw
+mil.rw
+net.rw
+org.rw
+
+// sa : http://www.nic.net.sa/
+sa
+com.sa
+net.sa
+org.sa
+gov.sa
+med.sa
+pub.sa
+edu.sa
+sch.sa
+
+// sb : http://www.sbnic.net.sb/
+// Submitted by registry <lee.humphries@telekom.com.sb>
+sb
+com.sb
+edu.sb
+gov.sb
+net.sb
+org.sb
+
+// sc : http://www.nic.sc/
+sc
+com.sc
+gov.sc
+net.sc
+org.sc
+edu.sc
+
+// sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm
+// Submitted by registry <admin@isoc.sd>
+sd
+com.sd
+net.sd
+org.sd
+edu.sd
+med.sd
+tv.sd
+gov.sd
+info.sd
+
+// se : https://en.wikipedia.org/wiki/.se
+// Submitted by registry <patrik.wallstrom@iis.se>
+se
+a.se
+ac.se
+b.se
+bd.se
+brand.se
+c.se
+d.se
+e.se
+f.se
+fh.se
+fhsk.se
+fhv.se
+g.se
+h.se
+i.se
+k.se
+komforb.se
+kommunalforbund.se
+komvux.se
+l.se
+lanbib.se
+m.se
+n.se
+naturbruksgymn.se
+o.se
+org.se
+p.se
+parti.se
+pp.se
+press.se
+r.se
+s.se
+t.se
+tm.se
+u.se
+w.se
+x.se
+y.se
+z.se
+
+// sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines
+sg
+com.sg
+net.sg
+org.sg
+gov.sg
+edu.sg
+per.sg
+
+// sh : http://nic.sh/rules.htm
+sh
+com.sh
+net.sh
+gov.sh
+org.sh
+mil.sh
+
+// si : https://en.wikipedia.org/wiki/.si
+si
+
+// sj : No registrations at this time.
+// Submitted by registry <jarle@uninett.no>
+sj
+
+// sk : https://en.wikipedia.org/wiki/.sk
+// list of 2nd level domains ?
+sk
+
+// sl : http://www.nic.sl
+// Submitted by registry <adam@neoip.com>
+sl
+com.sl
+net.sl
+edu.sl
+gov.sl
+org.sl
+
+// sm : https://en.wikipedia.org/wiki/.sm
+sm
+
+// sn : https://en.wikipedia.org/wiki/.sn
+sn
+art.sn
+com.sn
+edu.sn
+gouv.sn
+org.sn
+perso.sn
+univ.sn
+
+// so : http://sonic.so/policies/
+so
+com.so
+edu.so
+gov.so
+me.so
+net.so
+org.so
+
+// sr : https://en.wikipedia.org/wiki/.sr
+sr
+
+// ss : https://registry.nic.ss/
+// Submitted by registry <technical@nic.ss>
+ss
+biz.ss
+com.ss
+edu.ss
+gov.ss
+me.ss
+net.ss
+org.ss
+sch.ss
+
+// st : http://www.nic.st/html/policyrules/
+st
+co.st
+com.st
+consulado.st
+edu.st
+embaixada.st
+mil.st
+net.st
+org.st
+principe.st
+saotome.st
+store.st
+
+// su : https://en.wikipedia.org/wiki/.su
+su
+
+// sv : http://www.svnet.org.sv/niveldos.pdf
+sv
+com.sv
+edu.sv
+gob.sv
+org.sv
+red.sv
+
+// sx : https://en.wikipedia.org/wiki/.sx
+// Submitted by registry <jcvignes@openregistry.com>
+sx
+gov.sx
+
+// sy : https://en.wikipedia.org/wiki/.sy
+// see also: http://www.gobin.info/domainname/sy.doc
+sy
+edu.sy
+gov.sy
+net.sy
+mil.sy
+com.sy
+org.sy
+
+// sz : https://en.wikipedia.org/wiki/.sz
+// http://www.sispa.org.sz/
+sz
+co.sz
+ac.sz
+org.sz
+
+// tc : https://en.wikipedia.org/wiki/.tc
+tc
+
+// td : https://en.wikipedia.org/wiki/.td
+td
+
+// tel: https://en.wikipedia.org/wiki/.tel
+// http://www.telnic.org/
+tel
+
+// tf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
+tf
+
+// tg : https://en.wikipedia.org/wiki/.tg
+// http://www.nic.tg/
+tg
+
+// th : https://en.wikipedia.org/wiki/.th
+// Submitted by registry <krit@thains.co.th>
+th
+ac.th
+co.th
+go.th
+in.th
+mi.th
+net.th
+or.th
+
+// tj : http://www.nic.tj/policy.html
+tj
+ac.tj
+biz.tj
+co.tj
+com.tj
+edu.tj
+go.tj
+gov.tj
+int.tj
+mil.tj
+name.tj
+net.tj
+nic.tj
+org.tj
+test.tj
+web.tj
+
+// tk : https://en.wikipedia.org/wiki/.tk
+tk
+
+// tl : https://en.wikipedia.org/wiki/.tl
+tl
+gov.tl
+
+// tm : http://www.nic.tm/local.html
+tm
+com.tm
+co.tm
+org.tm
+net.tm
+nom.tm
+gov.tm
+mil.tm
+edu.tm
+
+// tn : http://www.registre.tn/fr/
+// https://whois.ati.tn/
+tn
+com.tn
+ens.tn
+fin.tn
+gov.tn
+ind.tn
+info.tn
+intl.tn
+mincom.tn
+nat.tn
+net.tn
+org.tn
+perso.tn
+tourism.tn
+
+// to : https://en.wikipedia.org/wiki/.to
+// Submitted by registry <egullich@colo.to>
+to
+com.to
+gov.to
+net.to
+org.to
+edu.to
+mil.to
+
+// tr : https://nic.tr/
+// https://nic.tr/forms/eng/policies.pdf
+// https://nic.tr/index.php?USRACTN=PRICELST
+tr
+av.tr
+bbs.tr
+bel.tr
+biz.tr
+com.tr
+dr.tr
+edu.tr
+gen.tr
+gov.tr
+info.tr
+mil.tr
+k12.tr
+kep.tr
+name.tr
+net.tr
+org.tr
+pol.tr
+tel.tr
+tsk.tr
+tv.tr
+web.tr
+// Used by Northern Cyprus
+nc.tr
+// Used by government agencies of Northern Cyprus
+gov.nc.tr
+
+// tt : http://www.nic.tt/
+tt
+co.tt
+com.tt
+org.tt
+net.tt
+biz.tt
+info.tt
+pro.tt
+int.tt
+coop.tt
+jobs.tt
+mobi.tt
+travel.tt
+museum.tt
+aero.tt
+name.tt
+gov.tt
+edu.tt
+
+// tv : https://en.wikipedia.org/wiki/.tv
+// Not listing any 2LDs as reserved since none seem to exist in practice,
+// Wikipedia notwithstanding.
+tv
+
+// tw : https://en.wikipedia.org/wiki/.tw
+tw
+edu.tw
+gov.tw
+mil.tw
+com.tw
+net.tw
+org.tw
+idv.tw
+game.tw
+ebiz.tw
+club.tw
+網路.tw
+組織.tw
+商業.tw
+
+// tz : http://www.tznic.or.tz/index.php/domains
+// Submitted by registry <manager@tznic.or.tz>
+tz
+ac.tz
+co.tz
+go.tz
+hotel.tz
+info.tz
+me.tz
+mil.tz
+mobi.tz
+ne.tz
+or.tz
+sc.tz
+tv.tz
+
+// ua : https://hostmaster.ua/policy/?ua
+// Submitted by registry <dk@cctld.ua>
+ua
+// ua 2LD
+com.ua
+edu.ua
+gov.ua
+in.ua
+net.ua
+org.ua
+// ua geographic names
+// https://hostmaster.ua/2ld/
+cherkassy.ua
+cherkasy.ua
+chernigov.ua
+chernihiv.ua
+chernivtsi.ua
+chernovtsy.ua
+ck.ua
+cn.ua
+cr.ua
+crimea.ua
+cv.ua
+dn.ua
+dnepropetrovsk.ua
+dnipropetrovsk.ua
+donetsk.ua
+dp.ua
+if.ua
+ivano-frankivsk.ua
+kh.ua
+kharkiv.ua
+kharkov.ua
+kherson.ua
+khmelnitskiy.ua
+khmelnytskyi.ua
+kiev.ua
+kirovograd.ua
+km.ua
+kr.ua
+kropyvnytskyi.ua
+krym.ua
+ks.ua
+kv.ua
+kyiv.ua
+lg.ua
+lt.ua
+lugansk.ua
+luhansk.ua
+lutsk.ua
+lv.ua
+lviv.ua
+mk.ua
+mykolaiv.ua
+nikolaev.ua
+od.ua
+odesa.ua
+odessa.ua
+pl.ua
+poltava.ua
+rivne.ua
+rovno.ua
+rv.ua
+sb.ua
+sebastopol.ua
+sevastopol.ua
+sm.ua
+sumy.ua
+te.ua
+ternopil.ua
+uz.ua
+uzhgorod.ua
+uzhhorod.ua
+vinnica.ua
+vinnytsia.ua
+vn.ua
+volyn.ua
+yalta.ua
+zakarpattia.ua
+zaporizhzhe.ua
+zaporizhzhia.ua
+zhitomir.ua
+zhytomyr.ua
+zp.ua
+zt.ua
+
+// ug : https://www.registry.co.ug/
+ug
+co.ug
+or.ug
+ac.ug
+sc.ug
+go.ug
+ne.ug
+com.ug
+org.ug
+
+// uk : https://en.wikipedia.org/wiki/.uk
+// Submitted by registry <Michael.Daly@nominet.org.uk>
+uk
+ac.uk
+co.uk
+gov.uk
+ltd.uk
+me.uk
+net.uk
+nhs.uk
+org.uk
+plc.uk
+police.uk
+*.sch.uk
+
+// us : https://en.wikipedia.org/wiki/.us
+us
+dni.us
+fed.us
+isa.us
+kids.us
+nsn.us
+// us geographic names
+ak.us
+al.us
+ar.us
+as.us
+az.us
+ca.us
+co.us
+ct.us
+dc.us
+de.us
+fl.us
+ga.us
+gu.us
+hi.us
+ia.us
+id.us
+il.us
+in.us
+ks.us
+ky.us
+la.us
+ma.us
+md.us
+me.us
+mi.us
+mn.us
+mo.us
+ms.us
+mt.us
+nc.us
+nd.us
+ne.us
+nh.us
+nj.us
+nm.us
+nv.us
+ny.us
+oh.us
+ok.us
+or.us
+pa.us
+pr.us
+ri.us
+sc.us
+sd.us
+tn.us
+tx.us
+ut.us
+vi.us
+vt.us
+va.us
+wa.us
+wi.us
+wv.us
+wy.us
+// The registrar notes several more specific domains available in each state,
+// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat
+// haphazard; in some states these domains resolve as addresses, while in others
+// only subdomains are available, or even nothing at all. We include the
+// most common ones where it's clear that different sites are different
+// entities.
+k12.ak.us
+k12.al.us
+k12.ar.us
+k12.as.us
+k12.az.us
+k12.ca.us
+k12.co.us
+k12.ct.us
+k12.dc.us
+k12.fl.us
+k12.ga.us
+k12.gu.us
+// k12.hi.us Bug 614565 - Hawaii has a state-wide DOE login
+k12.ia.us
+k12.id.us
+k12.il.us
+k12.in.us
+k12.ks.us
+k12.ky.us
+k12.la.us
+k12.ma.us
+k12.md.us
+k12.me.us
+k12.mi.us
+k12.mn.us
+k12.mo.us
+k12.ms.us
+k12.mt.us
+k12.nc.us
+// k12.nd.us Bug 1028347 - Removed at request of Travis Rosso <trossow@nd.gov>
+k12.ne.us
+k12.nh.us
+k12.nj.us
+k12.nm.us
+k12.nv.us
+k12.ny.us
+k12.oh.us
+k12.ok.us
+k12.or.us
+k12.pa.us
+k12.pr.us
+// k12.ri.us Removed at request of Kim Cournoyer <netsupport@staff.ri.net>
+k12.sc.us
+// k12.sd.us Bug 934131 - Removed at request of James Booze <James.Booze@k12.sd.us>
+k12.tn.us
+k12.tx.us
+k12.ut.us
+k12.vi.us
+k12.vt.us
+k12.va.us
+k12.wa.us
+k12.wi.us
+// k12.wv.us Bug 947705 - Removed at request of Verne Britton <verne@wvnet.edu>
+k12.wy.us
+cc.ak.us
+cc.al.us
+cc.ar.us
+cc.as.us
+cc.az.us
+cc.ca.us
+cc.co.us
+cc.ct.us
+cc.dc.us
+cc.de.us
+cc.fl.us
+cc.ga.us
+cc.gu.us
+cc.hi.us
+cc.ia.us
+cc.id.us
+cc.il.us
+cc.in.us
+cc.ks.us
+cc.ky.us
+cc.la.us
+cc.ma.us
+cc.md.us
+cc.me.us
+cc.mi.us
+cc.mn.us
+cc.mo.us
+cc.ms.us
+cc.mt.us
+cc.nc.us
+cc.nd.us
+cc.ne.us
+cc.nh.us
+cc.nj.us
+cc.nm.us
+cc.nv.us
+cc.ny.us
+cc.oh.us
+cc.ok.us
+cc.or.us
+cc.pa.us
+cc.pr.us
+cc.ri.us
+cc.sc.us
+cc.sd.us
+cc.tn.us
+cc.tx.us
+cc.ut.us
+cc.vi.us
+cc.vt.us
+cc.va.us
+cc.wa.us
+cc.wi.us
+cc.wv.us
+cc.wy.us
+lib.ak.us
+lib.al.us
+lib.ar.us
+lib.as.us
+lib.az.us
+lib.ca.us
+lib.co.us
+lib.ct.us
+lib.dc.us
+// lib.de.us Issue #243 - Moved to Private section at request of Ed Moore <Ed.Moore@lib.de.us>
+lib.fl.us
+lib.ga.us
+lib.gu.us
+lib.hi.us
+lib.ia.us
+lib.id.us
+lib.il.us
+lib.in.us
+lib.ks.us
+lib.ky.us
+lib.la.us
+lib.ma.us
+lib.md.us
+lib.me.us
+lib.mi.us
+lib.mn.us
+lib.mo.us
+lib.ms.us
+lib.mt.us
+lib.nc.us
+lib.nd.us
+lib.ne.us
+lib.nh.us
+lib.nj.us
+lib.nm.us
+lib.nv.us
+lib.ny.us
+lib.oh.us
+lib.ok.us
+lib.or.us
+lib.pa.us
+lib.pr.us
+lib.ri.us
+lib.sc.us
+lib.sd.us
+lib.tn.us
+lib.tx.us
+lib.ut.us
+lib.vi.us
+lib.vt.us
+lib.va.us
+lib.wa.us
+lib.wi.us
+// lib.wv.us Bug 941670 - Removed at request of Larry W Arnold <arnold@wvlc.lib.wv.us>
+lib.wy.us
+// k12.ma.us contains school districts in Massachusetts. The 4LDs are
+// managed independently except for private (PVT), charter (CHTR) and
+// parochial (PAROCH) schools. Those are delegated directly to the
+// 5LD operators. <k12-ma-hostmaster _ at _ rsuc.gweep.net>
+pvt.k12.ma.us
+chtr.k12.ma.us
+paroch.k12.ma.us
+// Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following
+// see also: http://domreg.merit.edu
+// see also: whois -h whois.domreg.merit.edu help
+ann-arbor.mi.us
+cog.mi.us
+dst.mi.us
+eaton.mi.us
+gen.mi.us
+mus.mi.us
+tec.mi.us
+washtenaw.mi.us
+
+// uy : http://www.nic.org.uy/
+uy
+com.uy
+edu.uy
+gub.uy
+mil.uy
+net.uy
+org.uy
+
+// uz : http://www.reg.uz/
+uz
+co.uz
+com.uz
+net.uz
+org.uz
+
+// va : https://en.wikipedia.org/wiki/.va
+va
+
+// vc : https://en.wikipedia.org/wiki/.vc
+// Submitted by registry <kshah@ca.afilias.info>
+vc
+com.vc
+net.vc
+org.vc
+gov.vc
+mil.vc
+edu.vc
+
+// ve : https://registro.nic.ve/
+// Submitted by registry nic@nic.ve and nicve@conatel.gob.ve
+ve
+arts.ve
+bib.ve
+co.ve
+com.ve
+e12.ve
+edu.ve
+firm.ve
+gob.ve
+gov.ve
+info.ve
+int.ve
+mil.ve
+net.ve
+nom.ve
+org.ve
+rar.ve
+rec.ve
+store.ve
+tec.ve
+web.ve
+
+// vg : https://en.wikipedia.org/wiki/.vg
+vg
+
+// vi : http://www.nic.vi/newdomainform.htm
+// http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other
+// TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they
+// are available for registration (which they do not seem to be).
+vi
+co.vi
+com.vi
+k12.vi
+net.vi
+org.vi
+
+// vn : https://www.vnnic.vn/en/domain/cctld-vn
+// https://vnnic.vn/sites/default/files/tailieu/vn.cctld.domains.txt
+vn
+ac.vn
+ai.vn
+biz.vn
+com.vn
+edu.vn
+gov.vn
+health.vn
+id.vn
+info.vn
+int.vn
+io.vn
+name.vn
+net.vn
+org.vn
+pro.vn
+
+// vn geographical names
+angiang.vn
+bacgiang.vn
+backan.vn
+baclieu.vn
+bacninh.vn
+baria-vungtau.vn
+bentre.vn
+binhdinh.vn
+binhduong.vn
+binhphuoc.vn
+binhthuan.vn
+camau.vn
+cantho.vn
+caobang.vn
+daklak.vn
+daknong.vn
+danang.vn
+dienbien.vn
+dongnai.vn
+dongthap.vn
+gialai.vn
+hagiang.vn
+haiduong.vn
+haiphong.vn
+hanam.vn
+hanoi.vn
+hatinh.vn
+haugiang.vn
+hoabinh.vn
+hungyen.vn
+khanhhoa.vn
+kiengiang.vn
+kontum.vn
+laichau.vn
+lamdong.vn
+langson.vn
+laocai.vn
+longan.vn
+namdinh.vn
+nghean.vn
+ninhbinh.vn
+ninhthuan.vn
+phutho.vn
+phuyen.vn
+quangbinh.vn
+quangnam.vn
+quangngai.vn
+quangninh.vn
+quangtri.vn
+soctrang.vn
+sonla.vn
+tayninh.vn
+thaibinh.vn
+thainguyen.vn
+thanhhoa.vn
+thanhphohochiminh.vn
+thuathienhue.vn
+tiengiang.vn
+travinh.vn
+tuyenquang.vn
+vinhlong.vn
+vinhphuc.vn
+yenbai.vn
+
+// vu : https://en.wikipedia.org/wiki/.vu
+// http://www.vunic.vu/
+vu
+com.vu
+edu.vu
+net.vu
+org.vu
+
+// wf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
+wf
+
+// ws : https://en.wikipedia.org/wiki/.ws
+// http://samoanic.ws/index.dhtml
+ws
+com.ws
+net.ws
+org.ws
+gov.ws
+edu.ws
+
+// yt : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
+yt
+
+// IDN ccTLDs
+// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then
+// U-label, and follow this format:
+// // A-Label ("<Latin renderings>", <language name>[, variant info]) : <ISO 3166 ccTLD>
+// // [sponsoring org]
+// U-Label
+
+// xn--mgbaam7a8h ("Emerat", Arabic) : AE
+// http://nic.ae/english/arabicdomain/rules.jsp
+امارات
+
+// xn--y9a3aq ("hye", Armenian) : AM
+// ISOC AM (operated by .am Registry)
+հայ
+
+// xn--54b7fta0cc ("Bangla", Bangla) : BD
+বাংলা
+
+// xn--90ae ("bg", Bulgarian) : BG
+бг
+
+// xn--mgbcpq6gpa1a ("albahrain", Arabic) : BH
+البحرين
+
+// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY
+// Operated by .by registry
+бел
+
+// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN
+// CNNIC
+// http://cnnic.cn/html/Dir/2005/10/11/3218.htm
+中国
+
+// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN
+// CNNIC
+// http://cnnic.cn/html/Dir/2005/10/11/3218.htm
+中國
+
+// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ
+الجزائر
+
+// xn--wgbh1c ("Egypt/Masr", Arabic) : EG
+// http://www.dotmasr.eg/
+مصر
+
+// xn--e1a4c ("eu", Cyrillic) : EU
+// https://eurid.eu
+ею
+
+// xn--qxa6a ("eu", Greek) : EU
+// https://eurid.eu
+ευ
+
+// xn--mgbah1a3hjkrd ("Mauritania", Arabic) : MR
+موريتانيا
+
+// xn--node ("ge", Georgian Mkhedruli) : GE
+გე
+
+// xn--qxam ("el", Greek) : GR
+// Hellenic Ministry of Infrastructure, Transport, and Networks
+ελ
+
+// xn--j6w193g ("Hong Kong", Chinese) : HK
+// https://www.hkirc.hk
+// Submitted by registry <hk.tech@hkirc.hk>
+// https://www.hkirc.hk/content.jsp?id=30#!/34
+香港
+公司.香港
+教育.香港
+政府.香港
+個人.香港
+網絡.香港
+組織.香港
+
+// xn--2scrj9c ("Bharat", Kannada) : IN
+// India
+ಭಾರತ
+
+// xn--3hcrj9c ("Bharat", Oriya) : IN
+// India
+ଭାରତ
+
+// xn--45br5cyl ("Bharatam", Assamese) : IN
+// India
+ভাৰত
+
+// xn--h2breg3eve ("Bharatam", Sanskrit) : IN
+// India
+भारतम्
+
+// xn--h2brj9c8c ("Bharot", Santali) : IN
+// India
+भारोत
+
+// xn--mgbgu82a ("Bharat", Sindhi) : IN
+// India
+ڀارت
+
+// xn--rvc1e0am3e ("Bharatam", Malayalam) : IN
+// India
+ഭാരതം
+
+// xn--h2brj9c ("Bharat", Devanagari) : IN
+// India
+भारत
+
+// xn--mgbbh1a ("Bharat", Kashmiri) : IN
+// India
+بارت
+
+// xn--mgbbh1a71e ("Bharat", Arabic) : IN
+// India
+بھارت
+
+// xn--fpcrj9c3d ("Bharat", Telugu) : IN
+// India
+భారత్
+
+// xn--gecrj9c ("Bharat", Gujarati) : IN
+// India
+ભારત
+
+// xn--s9brj9c ("Bharat", Gurmukhi) : IN
+// India
+ਭਾਰਤ
+
+// xn--45brj9c ("Bharat", Bengali) : IN
+// India
+ভারত
+
+// xn--xkc2dl3a5ee0h ("India", Tamil) : IN
+// India
+இந்தியா
+
+// xn--mgba3a4f16a ("Iran", Persian) : IR
+ایران
+
+// xn--mgba3a4fra ("Iran", Arabic) : IR
+ايران
+
+// xn--mgbtx2b ("Iraq", Arabic) : IQ
+// Communications and Media Commission
+عراق
+
+// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO
+// National Information Technology Center (NITC)
+// Royal Scientific Society, Al-Jubeiha
+الاردن
+
+// xn--3e0b707e ("Republic of Korea", Hangul) : KR
+한국
+
+// xn--80ao21a ("Kaz", Kazakh) : KZ
+қаз
+
+// xn--q7ce6a ("Lao", Lao) : LA
+ລາວ
+
+// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK
+// https://nic.lk
+ලංකා
+
+// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK
+// https://nic.lk
+இலங்கை
+
+// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA
+المغرب
+
+// xn--d1alf ("mkd", Macedonian) : MK
+// MARnet
+мкд
+
+// xn--l1acc ("mon", Mongolian) : MN
+мон
+
+// xn--mix891f ("Macao", Chinese, Traditional) : MO
+// MONIC / HNET Asia (Registry Operator for .mo)
+澳門
+
+// xn--mix082f ("Macao", Chinese, Simplified) : MO
+澳门
+
+// xn--mgbx4cd0ab ("Malaysia", Malay) : MY
+مليسيا
+
+// xn--mgb9awbf ("Oman", Arabic) : OM
+عمان
+
+// xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK
+پاکستان
+
+// xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK
+پاكستان
+
+// xn--ygbi2ammx ("Falasteen", Arabic) : PS
+// The Palestinian National Internet Naming Authority (PNINA)
+// http://www.pnina.ps
+فلسطين
+
+// xn--90a3ac ("srb", Cyrillic) : RS
+// https://www.rnids.rs/en/domains/national-domains
+срб
+пр.срб
+орг.срб
+обр.срб
+од.срб
+упр.срб
+ак.срб
+
+// xn--p1ai ("rf", Russian-Cyrillic) : RU
+// https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf
+// Submitted by George Georgievsky <gug@cctld.ru>
+рф
+
+// xn--wgbl6a ("Qatar", Arabic) : QA
+// http://www.ict.gov.qa/
+قطر
+
+// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA
+// http://www.nic.net.sa/
+السعودية
+
+// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant) : SA
+السعودیة
+
+// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA
+السعودیۃ
+
+// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA
+السعوديه
+
+// xn--mgbpl2fh ("sudan", Arabic) : SD
+// Operated by .sd registry
+سودان
+
+// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG
+新加坡
+
+// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG
+சிங்கப்பூர்
+
+// xn--ogbpf8fl ("Syria", Arabic) : SY
+سورية
+
+// xn--mgbtf8fl ("Syria", Arabic, variant) : SY
+سوريا
+
+// xn--o3cw4h ("Thai", Thai) : TH
+// http://www.thnic.co.th
+ไทย
+ศึกษา.ไทย
+ธุรกิจ.ไทย
+รัฐบาล.ไทย
+ทหาร.ไทย
+เน็ต.ไทย
+องค์กร.ไทย
+
+// xn--pgbs0dh ("Tunisia", Arabic) : TN
+// http://nic.tn
+تونس
+
+// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW
+// http://www.twnic.net/english/dn/dn_07a.htm
+台灣
+
+// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW
+// http://www.twnic.net/english/dn/dn_07a.htm
+台湾
+
+// xn--nnx388a ("Taiwan", Chinese, variant) : TW
+臺灣
+
+// xn--j1amh ("ukr", Cyrillic) : UA
+укр
+
+// xn--mgb2ddes ("AlYemen", Arabic) : YE
+اليمن
+
+// xxx : http://icmregistry.com
+xxx
+
+// ye : http://www.y.net.ye/services/domain_name.htm
+ye
+com.ye
+edu.ye
+gov.ye
+net.ye
+mil.ye
+org.ye
+
+// za : https://www.zadna.org.za/content/page/domain-information/
+ac.za
+agric.za
+alt.za
+co.za
+edu.za
+gov.za
+grondar.za
+law.za
+mil.za
+net.za
+ngo.za
+nic.za
+nis.za
+nom.za
+org.za
+school.za
+tm.za
+web.za
+
+// zm : https://zicta.zm/
+// Submitted by registry <info@zicta.zm>
+zm
+ac.zm
+biz.zm
+co.zm
+com.zm
+edu.zm
+gov.zm
+info.zm
+mil.zm
+net.zm
+org.zm
+sch.zm
+
+// zw : https://www.potraz.gov.zw/
+// Confirmed by registry <bmtengwa@potraz.gov.zw> 2017-01-25
+zw
+ac.zw
+co.zw
+gov.zw
+mil.zw
+org.zw
+
+
+// newGTLDs
+
+// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2024-03-06T15:14:58Z
+// This list is auto-generated, don't edit it manually.
+// aaa : American Automobile Association, Inc.
+// https://www.iana.org/domains/root/db/aaa.html
+aaa
+
+// aarp : AARP
+// https://www.iana.org/domains/root/db/aarp.html
+aarp
+
+// abb : ABB Ltd
+// https://www.iana.org/domains/root/db/abb.html
+abb
+
+// abbott : Abbott Laboratories, Inc.
+// https://www.iana.org/domains/root/db/abbott.html
+abbott
+
+// abbvie : AbbVie Inc.
+// https://www.iana.org/domains/root/db/abbvie.html
+abbvie
+
+// abc : Disney Enterprises, Inc.
+// https://www.iana.org/domains/root/db/abc.html
+abc
+
+// able : Able Inc.
+// https://www.iana.org/domains/root/db/able.html
+able
+
+// abogado : Registry Services, LLC
+// https://www.iana.org/domains/root/db/abogado.html
+abogado
+
+// abudhabi : Abu Dhabi Systems and Information Centre
+// https://www.iana.org/domains/root/db/abudhabi.html
+abudhabi
+
+// academy : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/academy.html
+academy
+
+// accenture : Accenture plc
+// https://www.iana.org/domains/root/db/accenture.html
+accenture
+
+// accountant : dot Accountant Limited
+// https://www.iana.org/domains/root/db/accountant.html
+accountant
+
+// accountants : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/accountants.html
+accountants
+
+// aco : ACO Severin Ahlmann GmbH & Co. KG
+// https://www.iana.org/domains/root/db/aco.html
+aco
+
+// actor : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/actor.html
+actor
+
+// ads : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/ads.html
+ads
+
+// adult : ICM Registry AD LLC
+// https://www.iana.org/domains/root/db/adult.html
+adult
+
+// aeg : Aktiebolaget Electrolux
+// https://www.iana.org/domains/root/db/aeg.html
+aeg
+
+// aetna : Aetna Life Insurance Company
+// https://www.iana.org/domains/root/db/aetna.html
+aetna
+
+// afl : Australian Football League
+// https://www.iana.org/domains/root/db/afl.html
+afl
+
+// africa : ZA Central Registry NPC trading as Registry.Africa
+// https://www.iana.org/domains/root/db/africa.html
+africa
+
+// agakhan : Fondation Aga Khan (Aga Khan Foundation)
+// https://www.iana.org/domains/root/db/agakhan.html
+agakhan
+
+// agency : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/agency.html
+agency
+
+// aig : American International Group, Inc.
+// https://www.iana.org/domains/root/db/aig.html
+aig
+
+// airbus : Airbus S.A.S.
+// https://www.iana.org/domains/root/db/airbus.html
+airbus
+
+// airforce : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/airforce.html
+airforce
+
+// airtel : Bharti Airtel Limited
+// https://www.iana.org/domains/root/db/airtel.html
+airtel
+
+// akdn : Fondation Aga Khan (Aga Khan Foundation)
+// https://www.iana.org/domains/root/db/akdn.html
+akdn
+
+// alibaba : Alibaba Group Holding Limited
+// https://www.iana.org/domains/root/db/alibaba.html
+alibaba
+
+// alipay : Alibaba Group Holding Limited
+// https://www.iana.org/domains/root/db/alipay.html
+alipay
+
+// allfinanz : Allfinanz Deutsche Vermögensberatung Aktiengesellschaft
+// https://www.iana.org/domains/root/db/allfinanz.html
+allfinanz
+
+// allstate : Allstate Fire and Casualty Insurance Company
+// https://www.iana.org/domains/root/db/allstate.html
+allstate
+
+// ally : Ally Financial Inc.
+// https://www.iana.org/domains/root/db/ally.html
+ally
+
+// alsace : Region Grand Est
+// https://www.iana.org/domains/root/db/alsace.html
+alsace
+
+// alstom : ALSTOM
+// https://www.iana.org/domains/root/db/alstom.html
+alstom
+
+// amazon : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/amazon.html
+amazon
+
+// americanexpress : American Express Travel Related Services Company, Inc.
+// https://www.iana.org/domains/root/db/americanexpress.html
+americanexpress
+
+// americanfamily : AmFam, Inc.
+// https://www.iana.org/domains/root/db/americanfamily.html
+americanfamily
+
+// amex : American Express Travel Related Services Company, Inc.
+// https://www.iana.org/domains/root/db/amex.html
+amex
+
+// amfam : AmFam, Inc.
+// https://www.iana.org/domains/root/db/amfam.html
+amfam
+
+// amica : Amica Mutual Insurance Company
+// https://www.iana.org/domains/root/db/amica.html
+amica
+
+// amsterdam : Gemeente Amsterdam
+// https://www.iana.org/domains/root/db/amsterdam.html
+amsterdam
+
+// analytics : Campus IP LLC
+// https://www.iana.org/domains/root/db/analytics.html
+analytics
+
+// android : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/android.html
+android
+
+// anquan : Beijing Qihu Keji Co., Ltd.
+// https://www.iana.org/domains/root/db/anquan.html
+anquan
+
+// anz : Australia and New Zealand Banking Group Limited
+// https://www.iana.org/domains/root/db/anz.html
+anz
+
+// aol : Oath Inc.
+// https://www.iana.org/domains/root/db/aol.html
+aol
+
+// apartments : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/apartments.html
+apartments
+
+// app : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/app.html
+app
+
+// apple : Apple Inc.
+// https://www.iana.org/domains/root/db/apple.html
+apple
+
+// aquarelle : Aquarelle.com
+// https://www.iana.org/domains/root/db/aquarelle.html
+aquarelle
+
+// arab : League of Arab States
+// https://www.iana.org/domains/root/db/arab.html
+arab
+
+// aramco : Aramco Services Company
+// https://www.iana.org/domains/root/db/aramco.html
+aramco
+
+// archi : Identity Digital Limited
+// https://www.iana.org/domains/root/db/archi.html
+archi
+
+// army : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/army.html
+army
+
+// art : UK Creative Ideas Limited
+// https://www.iana.org/domains/root/db/art.html
+art
+
+// arte : Association Relative à la Télévision Européenne G.E.I.E.
+// https://www.iana.org/domains/root/db/arte.html
+arte
+
+// asda : Wal-Mart Stores, Inc.
+// https://www.iana.org/domains/root/db/asda.html
+asda
+
+// associates : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/associates.html
+associates
+
+// athleta : The Gap, Inc.
+// https://www.iana.org/domains/root/db/athleta.html
+athleta
+
+// attorney : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/attorney.html
+attorney
+
+// auction : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/auction.html
+auction
+
+// audi : AUDI Aktiengesellschaft
+// https://www.iana.org/domains/root/db/audi.html
+audi
+
+// audible : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/audible.html
+audible
+
+// audio : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/audio.html
+audio
+
+// auspost : Australian Postal Corporation
+// https://www.iana.org/domains/root/db/auspost.html
+auspost
+
+// author : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/author.html
+author
+
+// auto : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/auto.html
+auto
+
+// autos : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/autos.html
+autos
+
+// avianca : Avianca Inc.
+// https://www.iana.org/domains/root/db/avianca.html
+avianca
+
+// aws : AWS Registry LLC
+// https://www.iana.org/domains/root/db/aws.html
+aws
+
+// axa : AXA Group Operations SAS
+// https://www.iana.org/domains/root/db/axa.html
+axa
+
+// azure : Microsoft Corporation
+// https://www.iana.org/domains/root/db/azure.html
+azure
+
+// baby : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/baby.html
+baby
+
+// baidu : Baidu, Inc.
+// https://www.iana.org/domains/root/db/baidu.html
+baidu
+
+// banamex : Citigroup Inc.
+// https://www.iana.org/domains/root/db/banamex.html
+banamex
+
+// band : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/band.html
+band
+
+// bank : fTLD Registry Services LLC
+// https://www.iana.org/domains/root/db/bank.html
+bank
+
+// bar : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+// https://www.iana.org/domains/root/db/bar.html
+bar
+
+// barcelona : Municipi de Barcelona
+// https://www.iana.org/domains/root/db/barcelona.html
+barcelona
+
+// barclaycard : Barclays Bank PLC
+// https://www.iana.org/domains/root/db/barclaycard.html
+barclaycard
+
+// barclays : Barclays Bank PLC
+// https://www.iana.org/domains/root/db/barclays.html
+barclays
+
+// barefoot : Gallo Vineyards, Inc.
+// https://www.iana.org/domains/root/db/barefoot.html
+barefoot
+
+// bargains : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/bargains.html
+bargains
+
+// baseball : MLB Advanced Media DH, LLC
+// https://www.iana.org/domains/root/db/baseball.html
+baseball
+
+// basketball : Fédération Internationale de Basketball (FIBA)
+// https://www.iana.org/domains/root/db/basketball.html
+basketball
+
+// bauhaus : Werkhaus GmbH
+// https://www.iana.org/domains/root/db/bauhaus.html
+bauhaus
+
+// bayern : Bayern Connect GmbH
+// https://www.iana.org/domains/root/db/bayern.html
+bayern
+
+// bbc : British Broadcasting Corporation
+// https://www.iana.org/domains/root/db/bbc.html
+bbc
+
+// bbt : BB&T Corporation
+// https://www.iana.org/domains/root/db/bbt.html
+bbt
+
+// bbva : BANCO BILBAO VIZCAYA ARGENTARIA, S.A.
+// https://www.iana.org/domains/root/db/bbva.html
+bbva
+
+// bcg : The Boston Consulting Group, Inc.
+// https://www.iana.org/domains/root/db/bcg.html
+bcg
+
+// bcn : Municipi de Barcelona
+// https://www.iana.org/domains/root/db/bcn.html
+bcn
+
+// beats : Beats Electronics, LLC
+// https://www.iana.org/domains/root/db/beats.html
+beats
+
+// beauty : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/beauty.html
+beauty
+
+// beer : Registry Services, LLC
+// https://www.iana.org/domains/root/db/beer.html
+beer
+
+// bentley : Bentley Motors Limited
+// https://www.iana.org/domains/root/db/bentley.html
+bentley
+
+// berlin : dotBERLIN GmbH & Co. KG
+// https://www.iana.org/domains/root/db/berlin.html
+berlin
+
+// best : BestTLD Pty Ltd
+// https://www.iana.org/domains/root/db/best.html
+best
+
+// bestbuy : BBY Solutions, Inc.
+// https://www.iana.org/domains/root/db/bestbuy.html
+bestbuy
+
+// bet : Identity Digital Limited
+// https://www.iana.org/domains/root/db/bet.html
+bet
+
+// bharti : Bharti Enterprises (Holding) Private Limited
+// https://www.iana.org/domains/root/db/bharti.html
+bharti
+
+// bible : American Bible Society
+// https://www.iana.org/domains/root/db/bible.html
+bible
+
+// bid : dot Bid Limited
+// https://www.iana.org/domains/root/db/bid.html
+bid
+
+// bike : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/bike.html
+bike
+
+// bing : Microsoft Corporation
+// https://www.iana.org/domains/root/db/bing.html
+bing
+
+// bingo : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/bingo.html
+bingo
+
+// bio : Identity Digital Limited
+// https://www.iana.org/domains/root/db/bio.html
+bio
+
+// black : Identity Digital Limited
+// https://www.iana.org/domains/root/db/black.html
+black
+
+// blackfriday : Registry Services, LLC
+// https://www.iana.org/domains/root/db/blackfriday.html
+blackfriday
+
+// blockbuster : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/blockbuster.html
+blockbuster
+
+// blog : Knock Knock WHOIS There, LLC
+// https://www.iana.org/domains/root/db/blog.html
+blog
+
+// bloomberg : Bloomberg IP Holdings LLC
+// https://www.iana.org/domains/root/db/bloomberg.html
+bloomberg
+
+// blue : Identity Digital Limited
+// https://www.iana.org/domains/root/db/blue.html
+blue
+
+// bms : Bristol-Myers Squibb Company
+// https://www.iana.org/domains/root/db/bms.html
+bms
+
+// bmw : Bayerische Motoren Werke Aktiengesellschaft
+// https://www.iana.org/domains/root/db/bmw.html
+bmw
+
+// bnpparibas : BNP Paribas
+// https://www.iana.org/domains/root/db/bnpparibas.html
+bnpparibas
+
+// boats : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/boats.html
+boats
+
+// boehringer : Boehringer Ingelheim International GmbH
+// https://www.iana.org/domains/root/db/boehringer.html
+boehringer
+
+// bofa : Bank of America Corporation
+// https://www.iana.org/domains/root/db/bofa.html
+bofa
+
+// bom : Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+// https://www.iana.org/domains/root/db/bom.html
+bom
+
+// bond : ShortDot SA
+// https://www.iana.org/domains/root/db/bond.html
+bond
+
+// boo : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/boo.html
+boo
+
+// book : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/book.html
+book
+
+// booking : Booking.com B.V.
+// https://www.iana.org/domains/root/db/booking.html
+booking
+
+// bosch : Robert Bosch GMBH
+// https://www.iana.org/domains/root/db/bosch.html
+bosch
+
+// bostik : Bostik SA
+// https://www.iana.org/domains/root/db/bostik.html
+bostik
+
+// boston : Registry Services, LLC
+// https://www.iana.org/domains/root/db/boston.html
+boston
+
+// bot : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/bot.html
+bot
+
+// boutique : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/boutique.html
+boutique
+
+// box : Intercap Registry Inc.
+// https://www.iana.org/domains/root/db/box.html
+box
+
+// bradesco : Banco Bradesco S.A.
+// https://www.iana.org/domains/root/db/bradesco.html
+bradesco
+
+// bridgestone : Bridgestone Corporation
+// https://www.iana.org/domains/root/db/bridgestone.html
+bridgestone
+
+// broadway : Celebrate Broadway, Inc.
+// https://www.iana.org/domains/root/db/broadway.html
+broadway
+
+// broker : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/broker.html
+broker
+
+// brother : Brother Industries, Ltd.
+// https://www.iana.org/domains/root/db/brother.html
+brother
+
+// brussels : DNS.be vzw
+// https://www.iana.org/domains/root/db/brussels.html
+brussels
+
+// build : Plan Bee LLC
+// https://www.iana.org/domains/root/db/build.html
+build
+
+// builders : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/builders.html
+builders
+
+// business : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/business.html
+business
+
+// buy : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/buy.html
+buy
+
+// buzz : DOTSTRATEGY CO.
+// https://www.iana.org/domains/root/db/buzz.html
+buzz
+
+// bzh : Association www.bzh
+// https://www.iana.org/domains/root/db/bzh.html
+bzh
+
+// cab : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cab.html
+cab
+
+// cafe : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cafe.html
+cafe
+
+// cal : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/cal.html
+cal
+
+// call : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/call.html
+call
+
+// calvinklein : PVH gTLD Holdings LLC
+// https://www.iana.org/domains/root/db/calvinklein.html
+calvinklein
+
+// cam : Cam Connecting SARL
+// https://www.iana.org/domains/root/db/cam.html
+cam
+
+// camera : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/camera.html
+camera
+
+// camp : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/camp.html
+camp
+
+// canon : Canon Inc.
+// https://www.iana.org/domains/root/db/canon.html
+canon
+
+// capetown : ZA Central Registry NPC trading as ZA Central Registry
+// https://www.iana.org/domains/root/db/capetown.html
+capetown
+
+// capital : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/capital.html
+capital
+
+// capitalone : Capital One Financial Corporation
+// https://www.iana.org/domains/root/db/capitalone.html
+capitalone
+
+// car : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/car.html
+car
+
+// caravan : Caravan International, Inc.
+// https://www.iana.org/domains/root/db/caravan.html
+caravan
+
+// cards : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cards.html
+cards
+
+// care : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/care.html
+care
+
+// career : dotCareer LLC
+// https://www.iana.org/domains/root/db/career.html
+career
+
+// careers : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/careers.html
+careers
+
+// cars : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/cars.html
+cars
+
+// casa : Registry Services, LLC
+// https://www.iana.org/domains/root/db/casa.html
+casa
+
+// case : Digity, LLC
+// https://www.iana.org/domains/root/db/case.html
+case
+
+// cash : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cash.html
+cash
+
+// casino : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/casino.html
+casino
+
+// catering : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/catering.html
+catering
+
+// catholic : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+// https://www.iana.org/domains/root/db/catholic.html
+catholic
+
+// cba : COMMONWEALTH BANK OF AUSTRALIA
+// https://www.iana.org/domains/root/db/cba.html
+cba
+
+// cbn : The Christian Broadcasting Network, Inc.
+// https://www.iana.org/domains/root/db/cbn.html
+cbn
+
+// cbre : CBRE, Inc.
+// https://www.iana.org/domains/root/db/cbre.html
+cbre
+
+// center : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/center.html
+center
+
+// ceo : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/ceo.html
+ceo
+
+// cern : European Organization for Nuclear Research ("CERN")
+// https://www.iana.org/domains/root/db/cern.html
+cern
+
+// cfa : CFA Institute
+// https://www.iana.org/domains/root/db/cfa.html
+cfa
+
+// cfd : ShortDot SA
+// https://www.iana.org/domains/root/db/cfd.html
+cfd
+
+// chanel : Chanel International B.V.
+// https://www.iana.org/domains/root/db/chanel.html
+chanel
+
+// channel : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/channel.html
+channel
+
+// charity : Public Interest Registry
+// https://www.iana.org/domains/root/db/charity.html
+charity
+
+// chase : JPMorgan Chase Bank, National Association
+// https://www.iana.org/domains/root/db/chase.html
+chase
+
+// chat : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/chat.html
+chat
+
+// cheap : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cheap.html
+cheap
+
+// chintai : CHINTAI Corporation
+// https://www.iana.org/domains/root/db/chintai.html
+chintai
+
+// christmas : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/christmas.html
+christmas
+
+// chrome : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/chrome.html
+chrome
+
+// church : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/church.html
+church
+
+// cipriani : Hotel Cipriani Srl
+// https://www.iana.org/domains/root/db/cipriani.html
+cipriani
+
+// circle : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/circle.html
+circle
+
+// cisco : Cisco Technology, Inc.
+// https://www.iana.org/domains/root/db/cisco.html
+cisco
+
+// citadel : Citadel Domain LLC
+// https://www.iana.org/domains/root/db/citadel.html
+citadel
+
+// citi : Citigroup Inc.
+// https://www.iana.org/domains/root/db/citi.html
+citi
+
+// citic : CITIC Group Corporation
+// https://www.iana.org/domains/root/db/citic.html
+citic
+
+// city : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/city.html
+city
+
+// claims : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/claims.html
+claims
+
+// cleaning : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cleaning.html
+cleaning
+
+// click : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/click.html
+click
+
+// clinic : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/clinic.html
+clinic
+
+// clinique : The Estée Lauder Companies Inc.
+// https://www.iana.org/domains/root/db/clinique.html
+clinique
+
+// clothing : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/clothing.html
+clothing
+
+// cloud : Aruba PEC S.p.A.
+// https://www.iana.org/domains/root/db/cloud.html
+cloud
+
+// club : Registry Services, LLC
+// https://www.iana.org/domains/root/db/club.html
+club
+
+// clubmed : Club Méditerranée S.A.
+// https://www.iana.org/domains/root/db/clubmed.html
+clubmed
+
+// coach : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/coach.html
+coach
+
+// codes : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/codes.html
+codes
+
+// coffee : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/coffee.html
+coffee
+
+// college : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/college.html
+college
+
+// cologne : dotKoeln GmbH
+// https://www.iana.org/domains/root/db/cologne.html
+cologne
+
+// commbank : COMMONWEALTH BANK OF AUSTRALIA
+// https://www.iana.org/domains/root/db/commbank.html
+commbank
+
+// community : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/community.html
+community
+
+// company : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/company.html
+company
+
+// compare : Registry Services, LLC
+// https://www.iana.org/domains/root/db/compare.html
+compare
+
+// computer : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/computer.html
+computer
+
+// comsec : VeriSign, Inc.
+// https://www.iana.org/domains/root/db/comsec.html
+comsec
+
+// condos : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/condos.html
+condos
+
+// construction : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/construction.html
+construction
+
+// consulting : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/consulting.html
+consulting
+
+// contact : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/contact.html
+contact
+
+// contractors : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/contractors.html
+contractors
+
+// cooking : Registry Services, LLC
+// https://www.iana.org/domains/root/db/cooking.html
+cooking
+
+// cool : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cool.html
+cool
+
+// corsica : Collectivité de Corse
+// https://www.iana.org/domains/root/db/corsica.html
+corsica
+
+// country : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/country.html
+country
+
+// coupon : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/coupon.html
+coupon
+
+// coupons : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/coupons.html
+coupons
+
+// courses : Registry Services, LLC
+// https://www.iana.org/domains/root/db/courses.html
+courses
+
+// cpa : American Institute of Certified Public Accountants
+// https://www.iana.org/domains/root/db/cpa.html
+cpa
+
+// credit : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/credit.html
+credit
+
+// creditcard : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/creditcard.html
+creditcard
+
+// creditunion : DotCooperation LLC
+// https://www.iana.org/domains/root/db/creditunion.html
+creditunion
+
+// cricket : dot Cricket Limited
+// https://www.iana.org/domains/root/db/cricket.html
+cricket
+
+// crown : Crown Equipment Corporation
+// https://www.iana.org/domains/root/db/crown.html
+crown
+
+// crs : Federated Co-operatives Limited
+// https://www.iana.org/domains/root/db/crs.html
+crs
+
+// cruise : Viking River Cruises (Bermuda) Ltd.
+// https://www.iana.org/domains/root/db/cruise.html
+cruise
+
+// cruises : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/cruises.html
+cruises
+
+// cuisinella : SCHMIDT GROUPE S.A.S.
+// https://www.iana.org/domains/root/db/cuisinella.html
+cuisinella
+
+// cymru : Nominet UK
+// https://www.iana.org/domains/root/db/cymru.html
+cymru
+
+// cyou : ShortDot SA
+// https://www.iana.org/domains/root/db/cyou.html
+cyou
+
+// dabur : Dabur India Limited
+// https://www.iana.org/domains/root/db/dabur.html
+dabur
+
+// dad : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/dad.html
+dad
+
+// dance : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/dance.html
+dance
+
+// data : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/data.html
+data
+
+// date : dot Date Limited
+// https://www.iana.org/domains/root/db/date.html
+date
+
+// dating : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/dating.html
+dating
+
+// datsun : NISSAN MOTOR CO., LTD.
+// https://www.iana.org/domains/root/db/datsun.html
+datsun
+
+// day : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/day.html
+day
+
+// dclk : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/dclk.html
+dclk
+
+// dds : Registry Services, LLC
+// https://www.iana.org/domains/root/db/dds.html
+dds
+
+// deal : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/deal.html
+deal
+
+// dealer : Intercap Registry Inc.
+// https://www.iana.org/domains/root/db/dealer.html
+dealer
+
+// deals : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/deals.html
+deals
+
+// degree : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/degree.html
+degree
+
+// delivery : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/delivery.html
+delivery
+
+// dell : Dell Inc.
+// https://www.iana.org/domains/root/db/dell.html
+dell
+
+// deloitte : Deloitte Touche Tohmatsu
+// https://www.iana.org/domains/root/db/deloitte.html
+deloitte
+
+// delta : Delta Air Lines, Inc.
+// https://www.iana.org/domains/root/db/delta.html
+delta
+
+// democrat : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/democrat.html
+democrat
+
+// dental : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/dental.html
+dental
+
+// dentist : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/dentist.html
+dentist
+
+// desi
+// https://www.iana.org/domains/root/db/desi.html
+desi
+
+// design : Registry Services, LLC
+// https://www.iana.org/domains/root/db/design.html
+design
+
+// dev : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/dev.html
+dev
+
+// dhl : Deutsche Post AG
+// https://www.iana.org/domains/root/db/dhl.html
+dhl
+
+// diamonds : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/diamonds.html
+diamonds
+
+// diet : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/diet.html
+diet
+
+// digital : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/digital.html
+digital
+
+// direct : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/direct.html
+direct
+
+// directory : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/directory.html
+directory
+
+// discount : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/discount.html
+discount
+
+// discover : Discover Financial Services
+// https://www.iana.org/domains/root/db/discover.html
+discover
+
+// dish : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/dish.html
+dish
+
+// diy : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/diy.html
+diy
+
+// dnp : Dai Nippon Printing Co., Ltd.
+// https://www.iana.org/domains/root/db/dnp.html
+dnp
+
+// docs : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/docs.html
+docs
+
+// doctor : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/doctor.html
+doctor
+
+// dog : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/dog.html
+dog
+
+// domains : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/domains.html
+domains
+
+// dot : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/dot.html
+dot
+
+// download : dot Support Limited
+// https://www.iana.org/domains/root/db/download.html
+download
+
+// drive : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/drive.html
+drive
+
+// dtv : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/dtv.html
+dtv
+
+// dubai : Dubai Smart Government Department
+// https://www.iana.org/domains/root/db/dubai.html
+dubai
+
+// dunlop : The Goodyear Tire & Rubber Company
+// https://www.iana.org/domains/root/db/dunlop.html
+dunlop
+
+// dupont : DuPont Specialty Products USA, LLC
+// https://www.iana.org/domains/root/db/dupont.html
+dupont
+
+// durban : ZA Central Registry NPC trading as ZA Central Registry
+// https://www.iana.org/domains/root/db/durban.html
+durban
+
+// dvag : Deutsche Vermögensberatung Aktiengesellschaft DVAG
+// https://www.iana.org/domains/root/db/dvag.html
+dvag
+
+// dvr : DISH Technologies L.L.C.
+// https://www.iana.org/domains/root/db/dvr.html
+dvr
+
+// earth : Interlink Systems Innovation Institute K.K.
+// https://www.iana.org/domains/root/db/earth.html
+earth
+
+// eat : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/eat.html
+eat
+
+// eco : Big Room Inc.
+// https://www.iana.org/domains/root/db/eco.html
+eco
+
+// edeka : EDEKA Verband kaufmännischer Genossenschaften e.V.
+// https://www.iana.org/domains/root/db/edeka.html
+edeka
+
+// education : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/education.html
+education
+
+// email : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/email.html
+email
+
+// emerck : Merck KGaA
+// https://www.iana.org/domains/root/db/emerck.html
+emerck
+
+// energy : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/energy.html
+energy
+
+// engineer : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/engineer.html
+engineer
+
+// engineering : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/engineering.html
+engineering
+
+// enterprises : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/enterprises.html
+enterprises
+
+// epson : Seiko Epson Corporation
+// https://www.iana.org/domains/root/db/epson.html
+epson
+
+// equipment : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/equipment.html
+equipment
+
+// ericsson : Telefonaktiebolaget L M Ericsson
+// https://www.iana.org/domains/root/db/ericsson.html
+ericsson
+
+// erni : ERNI Group Holding AG
+// https://www.iana.org/domains/root/db/erni.html
+erni
+
+// esq : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/esq.html
+esq
+
+// estate : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/estate.html
+estate
+
+// eurovision : European Broadcasting Union (EBU)
+// https://www.iana.org/domains/root/db/eurovision.html
+eurovision
+
+// eus : Puntueus Fundazioa
+// https://www.iana.org/domains/root/db/eus.html
+eus
+
+// events : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/events.html
+events
+
+// exchange : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/exchange.html
+exchange
+
+// expert : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/expert.html
+expert
+
+// exposed : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/exposed.html
+exposed
+
+// express : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/express.html
+express
+
+// extraspace : Extra Space Storage LLC
+// https://www.iana.org/domains/root/db/extraspace.html
+extraspace
+
+// fage : Fage International S.A.
+// https://www.iana.org/domains/root/db/fage.html
+fage
+
+// fail : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/fail.html
+fail
+
+// fairwinds : FairWinds Partners, LLC
+// https://www.iana.org/domains/root/db/fairwinds.html
+fairwinds
+
+// faith : dot Faith Limited
+// https://www.iana.org/domains/root/db/faith.html
+faith
+
+// family : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/family.html
+family
+
+// fan : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/fan.html
+fan
+
+// fans : ZDNS International Limited
+// https://www.iana.org/domains/root/db/fans.html
+fans
+
+// farm : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/farm.html
+farm
+
+// farmers : Farmers Insurance Exchange
+// https://www.iana.org/domains/root/db/farmers.html
+farmers
+
+// fashion : Registry Services, LLC
+// https://www.iana.org/domains/root/db/fashion.html
+fashion
+
+// fast : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/fast.html
+fast
+
+// fedex : Federal Express Corporation
+// https://www.iana.org/domains/root/db/fedex.html
+fedex
+
+// feedback : Top Level Spectrum, Inc.
+// https://www.iana.org/domains/root/db/feedback.html
+feedback
+
+// ferrari : Fiat Chrysler Automobiles N.V.
+// https://www.iana.org/domains/root/db/ferrari.html
+ferrari
+
+// ferrero : Ferrero Trading Lux S.A.
+// https://www.iana.org/domains/root/db/ferrero.html
+ferrero
+
+// fidelity : Fidelity Brokerage Services LLC
+// https://www.iana.org/domains/root/db/fidelity.html
+fidelity
+
+// fido : Rogers Communications Canada Inc.
+// https://www.iana.org/domains/root/db/fido.html
+fido
+
+// film : Motion Picture Domain Registry Pty Ltd
+// https://www.iana.org/domains/root/db/film.html
+film
+
+// final : Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+// https://www.iana.org/domains/root/db/final.html
+final
+
+// finance : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/finance.html
+finance
+
+// financial : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/financial.html
+financial
+
+// fire : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/fire.html
+fire
+
+// firestone : Bridgestone Licensing Services, Inc
+// https://www.iana.org/domains/root/db/firestone.html
+firestone
+
+// firmdale : Firmdale Holdings Limited
+// https://www.iana.org/domains/root/db/firmdale.html
+firmdale
+
+// fish : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/fish.html
+fish
+
+// fishing : Registry Services, LLC
+// https://www.iana.org/domains/root/db/fishing.html
+fishing
+
+// fit : Registry Services, LLC
+// https://www.iana.org/domains/root/db/fit.html
+fit
+
+// fitness : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/fitness.html
+fitness
+
+// flickr : Flickr, Inc.
+// https://www.iana.org/domains/root/db/flickr.html
+flickr
+
+// flights : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/flights.html
+flights
+
+// flir : FLIR Systems, Inc.
+// https://www.iana.org/domains/root/db/flir.html
+flir
+
+// florist : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/florist.html
+florist
+
+// flowers : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/flowers.html
+flowers
+
+// fly : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/fly.html
+fly
+
+// foo : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/foo.html
+foo
+
+// food : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/food.html
+food
+
+// football : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/football.html
+football
+
+// ford : Ford Motor Company
+// https://www.iana.org/domains/root/db/ford.html
+ford
+
+// forex : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/forex.html
+forex
+
+// forsale : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/forsale.html
+forsale
+
+// forum : Fegistry, LLC
+// https://www.iana.org/domains/root/db/forum.html
+forum
+
+// foundation : Public Interest Registry
+// https://www.iana.org/domains/root/db/foundation.html
+foundation
+
+// fox : FOX Registry, LLC
+// https://www.iana.org/domains/root/db/fox.html
+fox
+
+// free : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/free.html
+free
+
+// fresenius : Fresenius Immobilien-Verwaltungs-GmbH
+// https://www.iana.org/domains/root/db/fresenius.html
+fresenius
+
+// frl : FRLregistry B.V.
+// https://www.iana.org/domains/root/db/frl.html
+frl
+
+// frogans : OP3FT
+// https://www.iana.org/domains/root/db/frogans.html
+frogans
+
+// frontier : Frontier Communications Corporation
+// https://www.iana.org/domains/root/db/frontier.html
+frontier
+
+// ftr : Frontier Communications Corporation
+// https://www.iana.org/domains/root/db/ftr.html
+ftr
+
+// fujitsu : Fujitsu Limited
+// https://www.iana.org/domains/root/db/fujitsu.html
+fujitsu
+
+// fun : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/fun.html
+fun
+
+// fund : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/fund.html
+fund
+
+// furniture : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/furniture.html
+furniture
+
+// futbol : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/futbol.html
+futbol
+
+// fyi : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/fyi.html
+fyi
+
+// gal : Asociación puntoGAL
+// https://www.iana.org/domains/root/db/gal.html
+gal
+
+// gallery : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/gallery.html
+gallery
+
+// gallo : Gallo Vineyards, Inc.
+// https://www.iana.org/domains/root/db/gallo.html
+gallo
+
+// gallup : Gallup, Inc.
+// https://www.iana.org/domains/root/db/gallup.html
+gallup
+
+// game : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/game.html
+game
+
+// games : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/games.html
+games
+
+// gap : The Gap, Inc.
+// https://www.iana.org/domains/root/db/gap.html
+gap
+
+// garden : Registry Services, LLC
+// https://www.iana.org/domains/root/db/garden.html
+garden
+
+// gay : Registry Services, LLC
+// https://www.iana.org/domains/root/db/gay.html
+gay
+
+// gbiz : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/gbiz.html
+gbiz
+
+// gdn : Joint Stock Company "Navigation-information systems"
+// https://www.iana.org/domains/root/db/gdn.html
+gdn
+
+// gea : GEA Group Aktiengesellschaft
+// https://www.iana.org/domains/root/db/gea.html
+gea
+
+// gent : Easyhost BV
+// https://www.iana.org/domains/root/db/gent.html
+gent
+
+// genting : Resorts World Inc Pte. Ltd.
+// https://www.iana.org/domains/root/db/genting.html
+genting
+
+// george : Wal-Mart Stores, Inc.
+// https://www.iana.org/domains/root/db/george.html
+george
+
+// ggee : GMO Internet, Inc.
+// https://www.iana.org/domains/root/db/ggee.html
+ggee
+
+// gift : DotGift, LLC
+// https://www.iana.org/domains/root/db/gift.html
+gift
+
+// gifts : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/gifts.html
+gifts
+
+// gives : Public Interest Registry
+// https://www.iana.org/domains/root/db/gives.html
+gives
+
+// giving : Public Interest Registry
+// https://www.iana.org/domains/root/db/giving.html
+giving
+
+// glass : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/glass.html
+glass
+
+// gle : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/gle.html
+gle
+
+// global : Identity Digital Limited
+// https://www.iana.org/domains/root/db/global.html
+global
+
+// globo : Globo Comunicação e Participações S.A
+// https://www.iana.org/domains/root/db/globo.html
+globo
+
+// gmail : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/gmail.html
+gmail
+
+// gmbh : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/gmbh.html
+gmbh
+
+// gmo : GMO Internet, Inc.
+// https://www.iana.org/domains/root/db/gmo.html
+gmo
+
+// gmx : 1&1 Mail & Media GmbH
+// https://www.iana.org/domains/root/db/gmx.html
+gmx
+
+// godaddy : Go Daddy East, LLC
+// https://www.iana.org/domains/root/db/godaddy.html
+godaddy
+
+// gold : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/gold.html
+gold
+
+// goldpoint : YODOBASHI CAMERA CO.,LTD.
+// https://www.iana.org/domains/root/db/goldpoint.html
+goldpoint
+
+// golf : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/golf.html
+golf
+
+// goo : NTT DOCOMO, INC.
+// https://www.iana.org/domains/root/db/goo.html
+goo
+
+// goodyear : The Goodyear Tire & Rubber Company
+// https://www.iana.org/domains/root/db/goodyear.html
+goodyear
+
+// goog : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/goog.html
+goog
+
+// google : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/google.html
+google
+
+// gop : Republican State Leadership Committee, Inc.
+// https://www.iana.org/domains/root/db/gop.html
+gop
+
+// got : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/got.html
+got
+
+// grainger : Grainger Registry Services, LLC
+// https://www.iana.org/domains/root/db/grainger.html
+grainger
+
+// graphics : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/graphics.html
+graphics
+
+// gratis : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/gratis.html
+gratis
+
+// green : Identity Digital Limited
+// https://www.iana.org/domains/root/db/green.html
+green
+
+// gripe : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/gripe.html
+gripe
+
+// grocery : Wal-Mart Stores, Inc.
+// https://www.iana.org/domains/root/db/grocery.html
+grocery
+
+// group : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/group.html
+group
+
+// gucci : Guccio Gucci S.p.a.
+// https://www.iana.org/domains/root/db/gucci.html
+gucci
+
+// guge : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/guge.html
+guge
+
+// guide : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/guide.html
+guide
+
+// guitars : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/guitars.html
+guitars
+
+// guru : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/guru.html
+guru
+
+// hair : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/hair.html
+hair
+
+// hamburg : Hamburg Top-Level-Domain GmbH
+// https://www.iana.org/domains/root/db/hamburg.html
+hamburg
+
+// hangout : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/hangout.html
+hangout
+
+// haus : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/haus.html
+haus
+
+// hbo : HBO Registry Services, Inc.
+// https://www.iana.org/domains/root/db/hbo.html
+hbo
+
+// hdfc : HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED
+// https://www.iana.org/domains/root/db/hdfc.html
+hdfc
+
+// hdfcbank : HDFC Bank Limited
+// https://www.iana.org/domains/root/db/hdfcbank.html
+hdfcbank
+
+// health : Registry Services, LLC
+// https://www.iana.org/domains/root/db/health.html
+health
+
+// healthcare : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/healthcare.html
+healthcare
+
+// help : Innovation service Limited
+// https://www.iana.org/domains/root/db/help.html
+help
+
+// helsinki : City of Helsinki
+// https://www.iana.org/domains/root/db/helsinki.html
+helsinki
+
+// here : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/here.html
+here
+
+// hermes : HERMES INTERNATIONAL
+// https://www.iana.org/domains/root/db/hermes.html
+hermes
+
+// hiphop : Dot Hip Hop, LLC
+// https://www.iana.org/domains/root/db/hiphop.html
+hiphop
+
+// hisamitsu : Hisamitsu Pharmaceutical Co.,Inc.
+// https://www.iana.org/domains/root/db/hisamitsu.html
+hisamitsu
+
+// hitachi : Hitachi, Ltd.
+// https://www.iana.org/domains/root/db/hitachi.html
+hitachi
+
+// hiv : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/hiv.html
+hiv
+
+// hkt : PCCW-HKT DataCom Services Limited
+// https://www.iana.org/domains/root/db/hkt.html
+hkt
+
+// hockey : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/hockey.html
+hockey
+
+// holdings : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/holdings.html
+holdings
+
+// holiday : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/holiday.html
+holiday
+
+// homedepot : Home Depot Product Authority, LLC
+// https://www.iana.org/domains/root/db/homedepot.html
+homedepot
+
+// homegoods : The TJX Companies, Inc.
+// https://www.iana.org/domains/root/db/homegoods.html
+homegoods
+
+// homes : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/homes.html
+homes
+
+// homesense : The TJX Companies, Inc.
+// https://www.iana.org/domains/root/db/homesense.html
+homesense
+
+// honda : Honda Motor Co., Ltd.
+// https://www.iana.org/domains/root/db/honda.html
+honda
+
+// horse : Registry Services, LLC
+// https://www.iana.org/domains/root/db/horse.html
+horse
+
+// hospital : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/hospital.html
+hospital
+
+// host : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/host.html
+host
+
+// hosting : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/hosting.html
+hosting
+
+// hot : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/hot.html
+hot
+
+// hotels : Booking.com B.V.
+// https://www.iana.org/domains/root/db/hotels.html
+hotels
+
+// hotmail : Microsoft Corporation
+// https://www.iana.org/domains/root/db/hotmail.html
+hotmail
+
+// house : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/house.html
+house
+
+// how : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/how.html
+how
+
+// hsbc : HSBC Global Services (UK) Limited
+// https://www.iana.org/domains/root/db/hsbc.html
+hsbc
+
+// hughes : Hughes Satellite Systems Corporation
+// https://www.iana.org/domains/root/db/hughes.html
+hughes
+
+// hyatt : Hyatt GTLD, L.L.C.
+// https://www.iana.org/domains/root/db/hyatt.html
+hyatt
+
+// hyundai : Hyundai Motor Company
+// https://www.iana.org/domains/root/db/hyundai.html
+hyundai
+
+// ibm : International Business Machines Corporation
+// https://www.iana.org/domains/root/db/ibm.html
+ibm
+
+// icbc : Industrial and Commercial Bank of China Limited
+// https://www.iana.org/domains/root/db/icbc.html
+icbc
+
+// ice : IntercontinentalExchange, Inc.
+// https://www.iana.org/domains/root/db/ice.html
+ice
+
+// icu : ShortDot SA
+// https://www.iana.org/domains/root/db/icu.html
+icu
+
+// ieee : IEEE Global LLC
+// https://www.iana.org/domains/root/db/ieee.html
+ieee
+
+// ifm : ifm electronic gmbh
+// https://www.iana.org/domains/root/db/ifm.html
+ifm
+
+// ikano : Ikano S.A.
+// https://www.iana.org/domains/root/db/ikano.html
+ikano
+
+// imamat : Fondation Aga Khan (Aga Khan Foundation)
+// https://www.iana.org/domains/root/db/imamat.html
+imamat
+
+// imdb : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/imdb.html
+imdb
+
+// immo : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/immo.html
+immo
+
+// immobilien : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/immobilien.html
+immobilien
+
+// inc : Intercap Registry Inc.
+// https://www.iana.org/domains/root/db/inc.html
+inc
+
+// industries : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/industries.html
+industries
+
+// infiniti : NISSAN MOTOR CO., LTD.
+// https://www.iana.org/domains/root/db/infiniti.html
+infiniti
+
+// ing : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/ing.html
+ing
+
+// ink : Registry Services, LLC
+// https://www.iana.org/domains/root/db/ink.html
+ink
+
+// institute : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/institute.html
+institute
+
+// insurance : fTLD Registry Services LLC
+// https://www.iana.org/domains/root/db/insurance.html
+insurance
+
+// insure : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/insure.html
+insure
+
+// international : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/international.html
+international
+
+// intuit : Intuit Administrative Services, Inc.
+// https://www.iana.org/domains/root/db/intuit.html
+intuit
+
+// investments : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/investments.html
+investments
+
+// ipiranga : Ipiranga Produtos de Petroleo S.A.
+// https://www.iana.org/domains/root/db/ipiranga.html
+ipiranga
+
+// irish : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/irish.html
+irish
+
+// ismaili : Fondation Aga Khan (Aga Khan Foundation)
+// https://www.iana.org/domains/root/db/ismaili.html
+ismaili
+
+// ist : Istanbul Metropolitan Municipality
+// https://www.iana.org/domains/root/db/ist.html
+ist
+
+// istanbul : Istanbul Metropolitan Municipality
+// https://www.iana.org/domains/root/db/istanbul.html
+istanbul
+
+// itau : Itau Unibanco Holding S.A.
+// https://www.iana.org/domains/root/db/itau.html
+itau
+
+// itv : ITV Services Limited
+// https://www.iana.org/domains/root/db/itv.html
+itv
+
+// jaguar : Jaguar Land Rover Ltd
+// https://www.iana.org/domains/root/db/jaguar.html
+jaguar
+
+// java : Oracle Corporation
+// https://www.iana.org/domains/root/db/java.html
+java
+
+// jcb : JCB Co., Ltd.
+// https://www.iana.org/domains/root/db/jcb.html
+jcb
+
+// jeep : FCA US LLC.
+// https://www.iana.org/domains/root/db/jeep.html
+jeep
+
+// jetzt : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/jetzt.html
+jetzt
+
+// jewelry : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/jewelry.html
+jewelry
+
+// jio : Reliance Industries Limited
+// https://www.iana.org/domains/root/db/jio.html
+jio
+
+// jll : Jones Lang LaSalle Incorporated
+// https://www.iana.org/domains/root/db/jll.html
+jll
+
+// jmp : Matrix IP LLC
+// https://www.iana.org/domains/root/db/jmp.html
+jmp
+
+// jnj : Johnson & Johnson Services, Inc.
+// https://www.iana.org/domains/root/db/jnj.html
+jnj
+
+// joburg : ZA Central Registry NPC trading as ZA Central Registry
+// https://www.iana.org/domains/root/db/joburg.html
+joburg
+
+// jot : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/jot.html
+jot
+
+// joy : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/joy.html
+joy
+
+// jpmorgan : JPMorgan Chase Bank, National Association
+// https://www.iana.org/domains/root/db/jpmorgan.html
+jpmorgan
+
+// jprs : Japan Registry Services Co., Ltd.
+// https://www.iana.org/domains/root/db/jprs.html
+jprs
+
+// juegos : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/juegos.html
+juegos
+
+// juniper : JUNIPER NETWORKS, INC.
+// https://www.iana.org/domains/root/db/juniper.html
+juniper
+
+// kaufen : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/kaufen.html
+kaufen
+
+// kddi : KDDI CORPORATION
+// https://www.iana.org/domains/root/db/kddi.html
+kddi
+
+// kerryhotels : Kerry Trading Co. Limited
+// https://www.iana.org/domains/root/db/kerryhotels.html
+kerryhotels
+
+// kerrylogistics : Kerry Trading Co. Limited
+// https://www.iana.org/domains/root/db/kerrylogistics.html
+kerrylogistics
+
+// kerryproperties : Kerry Trading Co. Limited
+// https://www.iana.org/domains/root/db/kerryproperties.html
+kerryproperties
+
+// kfh : Kuwait Finance House
+// https://www.iana.org/domains/root/db/kfh.html
+kfh
+
+// kia : KIA MOTORS CORPORATION
+// https://www.iana.org/domains/root/db/kia.html
+kia
+
+// kids : DotKids Foundation Limited
+// https://www.iana.org/domains/root/db/kids.html
+kids
+
+// kim : Identity Digital Limited
+// https://www.iana.org/domains/root/db/kim.html
+kim
+
+// kindle : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/kindle.html
+kindle
+
+// kitchen : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/kitchen.html
+kitchen
+
+// kiwi : DOT KIWI LIMITED
+// https://www.iana.org/domains/root/db/kiwi.html
+kiwi
+
+// koeln : dotKoeln GmbH
+// https://www.iana.org/domains/root/db/koeln.html
+koeln
+
+// komatsu : Komatsu Ltd.
+// https://www.iana.org/domains/root/db/komatsu.html
+komatsu
+
+// kosher : Kosher Marketing Assets LLC
+// https://www.iana.org/domains/root/db/kosher.html
+kosher
+
+// kpmg : KPMG International Cooperative (KPMG International Genossenschaft)
+// https://www.iana.org/domains/root/db/kpmg.html
+kpmg
+
+// kpn : Koninklijke KPN N.V.
+// https://www.iana.org/domains/root/db/kpn.html
+kpn
+
+// krd : KRG Department of Information Technology
+// https://www.iana.org/domains/root/db/krd.html
+krd
+
+// kred : KredTLD Pty Ltd
+// https://www.iana.org/domains/root/db/kred.html
+kred
+
+// kuokgroup : Kerry Trading Co. Limited
+// https://www.iana.org/domains/root/db/kuokgroup.html
+kuokgroup
+
+// kyoto : Academic Institution: Kyoto Jyoho Gakuen
+// https://www.iana.org/domains/root/db/kyoto.html
+kyoto
+
+// lacaixa : Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa”
+// https://www.iana.org/domains/root/db/lacaixa.html
+lacaixa
+
+// lamborghini : Automobili Lamborghini S.p.A.
+// https://www.iana.org/domains/root/db/lamborghini.html
+lamborghini
+
+// lamer : The Estée Lauder Companies Inc.
+// https://www.iana.org/domains/root/db/lamer.html
+lamer
+
+// lancaster : LANCASTER
+// https://www.iana.org/domains/root/db/lancaster.html
+lancaster
+
+// land : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/land.html
+land
+
+// landrover : Jaguar Land Rover Ltd
+// https://www.iana.org/domains/root/db/landrover.html
+landrover
+
+// lanxess : LANXESS Corporation
+// https://www.iana.org/domains/root/db/lanxess.html
+lanxess
+
+// lasalle : Jones Lang LaSalle Incorporated
+// https://www.iana.org/domains/root/db/lasalle.html
+lasalle
+
+// lat : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/lat.html
+lat
+
+// latino : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/latino.html
+latino
+
+// latrobe : La Trobe University
+// https://www.iana.org/domains/root/db/latrobe.html
+latrobe
+
+// law : Registry Services, LLC
+// https://www.iana.org/domains/root/db/law.html
+law
+
+// lawyer : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/lawyer.html
+lawyer
+
+// lds : IRI Domain Management, LLC
+// https://www.iana.org/domains/root/db/lds.html
+lds
+
+// lease : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/lease.html
+lease
+
+// leclerc : A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc
+// https://www.iana.org/domains/root/db/leclerc.html
+leclerc
+
+// lefrak : LeFrak Organization, Inc.
+// https://www.iana.org/domains/root/db/lefrak.html
+lefrak
+
+// legal : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/legal.html
+legal
+
+// lego : LEGO Juris A/S
+// https://www.iana.org/domains/root/db/lego.html
+lego
+
+// lexus : TOYOTA MOTOR CORPORATION
+// https://www.iana.org/domains/root/db/lexus.html
+lexus
+
+// lgbt : Identity Digital Limited
+// https://www.iana.org/domains/root/db/lgbt.html
+lgbt
+
+// lidl : Schwarz Domains und Services GmbH & Co. KG
+// https://www.iana.org/domains/root/db/lidl.html
+lidl
+
+// life : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/life.html
+life
+
+// lifeinsurance : American Council of Life Insurers
+// https://www.iana.org/domains/root/db/lifeinsurance.html
+lifeinsurance
+
+// lifestyle : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/lifestyle.html
+lifestyle
+
+// lighting : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/lighting.html
+lighting
+
+// like : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/like.html
+like
+
+// lilly : Eli Lilly and Company
+// https://www.iana.org/domains/root/db/lilly.html
+lilly
+
+// limited : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/limited.html
+limited
+
+// limo : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/limo.html
+limo
+
+// lincoln : Ford Motor Company
+// https://www.iana.org/domains/root/db/lincoln.html
+lincoln
+
+// link : Nova Registry Ltd
+// https://www.iana.org/domains/root/db/link.html
+link
+
+// lipsy : Lipsy Ltd
+// https://www.iana.org/domains/root/db/lipsy.html
+lipsy
+
+// live : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/live.html
+live
+
+// living : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/living.html
+living
+
+// llc : Identity Digital Limited
+// https://www.iana.org/domains/root/db/llc.html
+llc
+
+// llp : Intercap Registry Inc.
+// https://www.iana.org/domains/root/db/llp.html
+llp
+
+// loan : dot Loan Limited
+// https://www.iana.org/domains/root/db/loan.html
+loan
+
+// loans : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/loans.html
+loans
+
+// locker : Orange Domains LLC
+// https://www.iana.org/domains/root/db/locker.html
+locker
+
+// locus : Locus Analytics LLC
+// https://www.iana.org/domains/root/db/locus.html
+locus
+
+// lol : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/lol.html
+lol
+
+// london : Dot London Domains Limited
+// https://www.iana.org/domains/root/db/london.html
+london
+
+// lotte : Lotte Holdings Co., Ltd.
+// https://www.iana.org/domains/root/db/lotte.html
+lotte
+
+// lotto : Identity Digital Limited
+// https://www.iana.org/domains/root/db/lotto.html
+lotto
+
+// love : Merchant Law Group LLP
+// https://www.iana.org/domains/root/db/love.html
+love
+
+// lpl : LPL Holdings, Inc.
+// https://www.iana.org/domains/root/db/lpl.html
+lpl
+
+// lplfinancial : LPL Holdings, Inc.
+// https://www.iana.org/domains/root/db/lplfinancial.html
+lplfinancial
+
+// ltd : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/ltd.html
+ltd
+
+// ltda : InterNetX, Corp
+// https://www.iana.org/domains/root/db/ltda.html
+ltda
+
+// lundbeck : H. Lundbeck A/S
+// https://www.iana.org/domains/root/db/lundbeck.html
+lundbeck
+
+// luxe : Registry Services, LLC
+// https://www.iana.org/domains/root/db/luxe.html
+luxe
+
+// luxury : Luxury Partners, LLC
+// https://www.iana.org/domains/root/db/luxury.html
+luxury
+
+// madrid : Comunidad de Madrid
+// https://www.iana.org/domains/root/db/madrid.html
+madrid
+
+// maif : Mutuelle Assurance Instituteur France (MAIF)
+// https://www.iana.org/domains/root/db/maif.html
+maif
+
+// maison : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/maison.html
+maison
+
+// makeup : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/makeup.html
+makeup
+
+// man : MAN SE
+// https://www.iana.org/domains/root/db/man.html
+man
+
+// management : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/management.html
+management
+
+// mango : PUNTO FA S.L.
+// https://www.iana.org/domains/root/db/mango.html
+mango
+
+// map : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/map.html
+map
+
+// market : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/market.html
+market
+
+// marketing : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/marketing.html
+marketing
+
+// markets : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/markets.html
+markets
+
+// marriott : Marriott Worldwide Corporation
+// https://www.iana.org/domains/root/db/marriott.html
+marriott
+
+// marshalls : The TJX Companies, Inc.
+// https://www.iana.org/domains/root/db/marshalls.html
+marshalls
+
+// mattel : Mattel Sites, Inc.
+// https://www.iana.org/domains/root/db/mattel.html
+mattel
+
+// mba : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/mba.html
+mba
+
+// mckinsey : McKinsey Holdings, Inc.
+// https://www.iana.org/domains/root/db/mckinsey.html
+mckinsey
+
+// med : Medistry LLC
+// https://www.iana.org/domains/root/db/med.html
+med
+
+// media : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/media.html
+media
+
+// meet : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/meet.html
+meet
+
+// melbourne : The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation
+// https://www.iana.org/domains/root/db/melbourne.html
+melbourne
+
+// meme : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/meme.html
+meme
+
+// memorial : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/memorial.html
+memorial
+
+// men : Exclusive Registry Limited
+// https://www.iana.org/domains/root/db/men.html
+men
+
+// menu : Dot Menu Registry, LLC
+// https://www.iana.org/domains/root/db/menu.html
+menu
+
+// merckmsd : MSD Registry Holdings, Inc.
+// https://www.iana.org/domains/root/db/merckmsd.html
+merckmsd
+
+// miami : Registry Services, LLC
+// https://www.iana.org/domains/root/db/miami.html
+miami
+
+// microsoft : Microsoft Corporation
+// https://www.iana.org/domains/root/db/microsoft.html
+microsoft
+
+// mini : Bayerische Motoren Werke Aktiengesellschaft
+// https://www.iana.org/domains/root/db/mini.html
+mini
+
+// mint : Intuit Administrative Services, Inc.
+// https://www.iana.org/domains/root/db/mint.html
+mint
+
+// mit : Massachusetts Institute of Technology
+// https://www.iana.org/domains/root/db/mit.html
+mit
+
+// mitsubishi : Mitsubishi Corporation
+// https://www.iana.org/domains/root/db/mitsubishi.html
+mitsubishi
+
+// mlb : MLB Advanced Media DH, LLC
+// https://www.iana.org/domains/root/db/mlb.html
+mlb
+
+// mls : The Canadian Real Estate Association
+// https://www.iana.org/domains/root/db/mls.html
+mls
+
+// mma : MMA IARD
+// https://www.iana.org/domains/root/db/mma.html
+mma
+
+// mobile : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/mobile.html
+mobile
+
+// moda : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/moda.html
+moda
+
+// moe : Interlink Systems Innovation Institute K.K.
+// https://www.iana.org/domains/root/db/moe.html
+moe
+
+// moi : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/moi.html
+moi
+
+// mom : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/mom.html
+mom
+
+// monash : Monash University
+// https://www.iana.org/domains/root/db/monash.html
+monash
+
+// money : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/money.html
+money
+
+// monster : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/monster.html
+monster
+
+// mormon : IRI Domain Management, LLC
+// https://www.iana.org/domains/root/db/mormon.html
+mormon
+
+// mortgage : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/mortgage.html
+mortgage
+
+// moscow : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+// https://www.iana.org/domains/root/db/moscow.html
+moscow
+
+// moto : Motorola Trademark Holdings, LLC
+// https://www.iana.org/domains/root/db/moto.html
+moto
+
+// motorcycles : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/motorcycles.html
+motorcycles
+
+// mov : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/mov.html
+mov
+
+// movie : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/movie.html
+movie
+
+// msd : MSD Registry Holdings, Inc.
+// https://www.iana.org/domains/root/db/msd.html
+msd
+
+// mtn : MTN Dubai Limited
+// https://www.iana.org/domains/root/db/mtn.html
+mtn
+
+// mtr : MTR Corporation Limited
+// https://www.iana.org/domains/root/db/mtr.html
+mtr
+
+// music : DotMusic Limited
+// https://www.iana.org/domains/root/db/music.html
+music
+
+// nab : National Australia Bank Limited
+// https://www.iana.org/domains/root/db/nab.html
+nab
+
+// nagoya : GMO Registry, Inc.
+// https://www.iana.org/domains/root/db/nagoya.html
+nagoya
+
+// natura : NATURA COSMÉTICOS S.A.
+// https://www.iana.org/domains/root/db/natura.html
+natura
+
+// navy : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/navy.html
+navy
+
+// nba : NBA REGISTRY, LLC
+// https://www.iana.org/domains/root/db/nba.html
+nba
+
+// nec : NEC Corporation
+// https://www.iana.org/domains/root/db/nec.html
+nec
+
+// netbank : COMMONWEALTH BANK OF AUSTRALIA
+// https://www.iana.org/domains/root/db/netbank.html
+netbank
+
+// netflix : Netflix, Inc.
+// https://www.iana.org/domains/root/db/netflix.html
+netflix
+
+// network : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/network.html
+network
+
+// neustar : NeuStar, Inc.
+// https://www.iana.org/domains/root/db/neustar.html
+neustar
+
+// new : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/new.html
+new
+
+// news : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/news.html
+news
+
+// next : Next plc
+// https://www.iana.org/domains/root/db/next.html
+next
+
+// nextdirect : Next plc
+// https://www.iana.org/domains/root/db/nextdirect.html
+nextdirect
+
+// nexus : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/nexus.html
+nexus
+
+// nfl : NFL Reg Ops LLC
+// https://www.iana.org/domains/root/db/nfl.html
+nfl
+
+// ngo : Public Interest Registry
+// https://www.iana.org/domains/root/db/ngo.html
+ngo
+
+// nhk : Japan Broadcasting Corporation (NHK)
+// https://www.iana.org/domains/root/db/nhk.html
+nhk
+
+// nico : DWANGO Co., Ltd.
+// https://www.iana.org/domains/root/db/nico.html
+nico
+
+// nike : NIKE, Inc.
+// https://www.iana.org/domains/root/db/nike.html
+nike
+
+// nikon : NIKON CORPORATION
+// https://www.iana.org/domains/root/db/nikon.html
+nikon
+
+// ninja : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/ninja.html
+ninja
+
+// nissan : NISSAN MOTOR CO., LTD.
+// https://www.iana.org/domains/root/db/nissan.html
+nissan
+
+// nissay : Nippon Life Insurance Company
+// https://www.iana.org/domains/root/db/nissay.html
+nissay
+
+// nokia : Nokia Corporation
+// https://www.iana.org/domains/root/db/nokia.html
+nokia
+
+// norton : NortonLifeLock Inc.
+// https://www.iana.org/domains/root/db/norton.html
+norton
+
+// now : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/now.html
+now
+
+// nowruz : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+// https://www.iana.org/domains/root/db/nowruz.html
+nowruz
+
+// nowtv : Starbucks (HK) Limited
+// https://www.iana.org/domains/root/db/nowtv.html
+nowtv
+
+// nra : NRA Holdings Company, INC.
+// https://www.iana.org/domains/root/db/nra.html
+nra
+
+// nrw : Minds + Machines GmbH
+// https://www.iana.org/domains/root/db/nrw.html
+nrw
+
+// ntt : NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+// https://www.iana.org/domains/root/db/ntt.html
+ntt
+
+// nyc : The City of New York by and through the New York City Department of Information Technology & Telecommunications
+// https://www.iana.org/domains/root/db/nyc.html
+nyc
+
+// obi : OBI Group Holding SE & Co. KGaA
+// https://www.iana.org/domains/root/db/obi.html
+obi
+
+// observer : Fegistry, LLC
+// https://www.iana.org/domains/root/db/observer.html
+observer
+
+// office : Microsoft Corporation
+// https://www.iana.org/domains/root/db/office.html
+office
+
+// okinawa : BRregistry, Inc.
+// https://www.iana.org/domains/root/db/okinawa.html
+okinawa
+
+// olayan : Competrol (Luxembourg) Sarl
+// https://www.iana.org/domains/root/db/olayan.html
+olayan
+
+// olayangroup : Competrol (Luxembourg) Sarl
+// https://www.iana.org/domains/root/db/olayangroup.html
+olayangroup
+
+// ollo : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/ollo.html
+ollo
+
+// omega : The Swatch Group Ltd
+// https://www.iana.org/domains/root/db/omega.html
+omega
+
+// one : One.com A/S
+// https://www.iana.org/domains/root/db/one.html
+one
+
+// ong : Public Interest Registry
+// https://www.iana.org/domains/root/db/ong.html
+ong
+
+// onl : iRegistry GmbH
+// https://www.iana.org/domains/root/db/onl.html
+onl
+
+// online : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/online.html
+online
+
+// ooo : INFIBEAM AVENUES LIMITED
+// https://www.iana.org/domains/root/db/ooo.html
+ooo
+
+// open : American Express Travel Related Services Company, Inc.
+// https://www.iana.org/domains/root/db/open.html
+open
+
+// oracle : Oracle Corporation
+// https://www.iana.org/domains/root/db/oracle.html
+oracle
+
+// orange : Orange Brand Services Limited
+// https://www.iana.org/domains/root/db/orange.html
+orange
+
+// organic : Identity Digital Limited
+// https://www.iana.org/domains/root/db/organic.html
+organic
+
+// origins : The Estée Lauder Companies Inc.
+// https://www.iana.org/domains/root/db/origins.html
+origins
+
+// osaka : Osaka Registry Co., Ltd.
+// https://www.iana.org/domains/root/db/osaka.html
+osaka
+
+// otsuka : Otsuka Holdings Co., Ltd.
+// https://www.iana.org/domains/root/db/otsuka.html
+otsuka
+
+// ott : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/ott.html
+ott
+
+// ovh : MédiaBC
+// https://www.iana.org/domains/root/db/ovh.html
+ovh
+
+// page : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/page.html
+page
+
+// panasonic : Panasonic Holdings Corporation
+// https://www.iana.org/domains/root/db/panasonic.html
+panasonic
+
+// paris : City of Paris
+// https://www.iana.org/domains/root/db/paris.html
+paris
+
+// pars : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+// https://www.iana.org/domains/root/db/pars.html
+pars
+
+// partners : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/partners.html
+partners
+
+// parts : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/parts.html
+parts
+
+// party : Blue Sky Registry Limited
+// https://www.iana.org/domains/root/db/party.html
+party
+
+// pay : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/pay.html
+pay
+
+// pccw : PCCW Enterprises Limited
+// https://www.iana.org/domains/root/db/pccw.html
+pccw
+
+// pet : Identity Digital Limited
+// https://www.iana.org/domains/root/db/pet.html
+pet
+
+// pfizer : Pfizer Inc.
+// https://www.iana.org/domains/root/db/pfizer.html
+pfizer
+
+// pharmacy : National Association of Boards of Pharmacy
+// https://www.iana.org/domains/root/db/pharmacy.html
+pharmacy
+
+// phd : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/phd.html
+phd
+
+// philips : Koninklijke Philips N.V.
+// https://www.iana.org/domains/root/db/philips.html
+philips
+
+// phone : Dish DBS Corporation
+// https://www.iana.org/domains/root/db/phone.html
+phone
+
+// photo : Registry Services, LLC
+// https://www.iana.org/domains/root/db/photo.html
+photo
+
+// photography : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/photography.html
+photography
+
+// photos : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/photos.html
+photos
+
+// physio : PhysBiz Pty Ltd
+// https://www.iana.org/domains/root/db/physio.html
+physio
+
+// pics : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/pics.html
+pics
+
+// pictet : Pictet Europe S.A.
+// https://www.iana.org/domains/root/db/pictet.html
+pictet
+
+// pictures : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/pictures.html
+pictures
+
+// pid : Top Level Spectrum, Inc.
+// https://www.iana.org/domains/root/db/pid.html
+pid
+
+// pin : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/pin.html
+pin
+
+// ping : Ping Registry Provider, Inc.
+// https://www.iana.org/domains/root/db/ping.html
+ping
+
+// pink : Identity Digital Limited
+// https://www.iana.org/domains/root/db/pink.html
+pink
+
+// pioneer : Pioneer Corporation
+// https://www.iana.org/domains/root/db/pioneer.html
+pioneer
+
+// pizza : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/pizza.html
+pizza
+
+// place : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/place.html
+place
+
+// play : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/play.html
+play
+
+// playstation : Sony Interactive Entertainment Inc.
+// https://www.iana.org/domains/root/db/playstation.html
+playstation
+
+// plumbing : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/plumbing.html
+plumbing
+
+// plus : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/plus.html
+plus
+
+// pnc : PNC Domain Co., LLC
+// https://www.iana.org/domains/root/db/pnc.html
+pnc
+
+// pohl : Deutsche Vermögensberatung Aktiengesellschaft DVAG
+// https://www.iana.org/domains/root/db/pohl.html
+pohl
+
+// poker : Identity Digital Limited
+// https://www.iana.org/domains/root/db/poker.html
+poker
+
+// politie : Politie Nederland
+// https://www.iana.org/domains/root/db/politie.html
+politie
+
+// porn : ICM Registry PN LLC
+// https://www.iana.org/domains/root/db/porn.html
+porn
+
+// pramerica : Prudential Financial, Inc.
+// https://www.iana.org/domains/root/db/pramerica.html
+pramerica
+
+// praxi : Praxi S.p.A.
+// https://www.iana.org/domains/root/db/praxi.html
+praxi
+
+// press : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/press.html
+press
+
+// prime : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/prime.html
+prime
+
+// prod : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/prod.html
+prod
+
+// productions : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/productions.html
+productions
+
+// prof : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/prof.html
+prof
+
+// progressive : Progressive Casualty Insurance Company
+// https://www.iana.org/domains/root/db/progressive.html
+progressive
+
+// promo : Identity Digital Limited
+// https://www.iana.org/domains/root/db/promo.html
+promo
+
+// properties : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/properties.html
+properties
+
+// property : Digital Property Infrastructure Limited
+// https://www.iana.org/domains/root/db/property.html
+property
+
+// protection : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/protection.html
+protection
+
+// pru : Prudential Financial, Inc.
+// https://www.iana.org/domains/root/db/pru.html
+pru
+
+// prudential : Prudential Financial, Inc.
+// https://www.iana.org/domains/root/db/prudential.html
+prudential
+
+// pub : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/pub.html
+pub
+
+// pwc : PricewaterhouseCoopers LLP
+// https://www.iana.org/domains/root/db/pwc.html
+pwc
+
+// qpon : dotQPON LLC
+// https://www.iana.org/domains/root/db/qpon.html
+qpon
+
+// quebec : PointQuébec Inc
+// https://www.iana.org/domains/root/db/quebec.html
+quebec
+
+// quest : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/quest.html
+quest
+
+// racing : Premier Registry Limited
+// https://www.iana.org/domains/root/db/racing.html
+racing
+
+// radio : European Broadcasting Union (EBU)
+// https://www.iana.org/domains/root/db/radio.html
+radio
+
+// read : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/read.html
+read
+
+// realestate : dotRealEstate LLC
+// https://www.iana.org/domains/root/db/realestate.html
+realestate
+
+// realtor : Real Estate Domains LLC
+// https://www.iana.org/domains/root/db/realtor.html
+realtor
+
+// realty : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/realty.html
+realty
+
+// recipes : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/recipes.html
+recipes
+
+// red : Identity Digital Limited
+// https://www.iana.org/domains/root/db/red.html
+red
+
+// redstone : Redstone Haute Couture Co., Ltd.
+// https://www.iana.org/domains/root/db/redstone.html
+redstone
+
+// redumbrella : Travelers TLD, LLC
+// https://www.iana.org/domains/root/db/redumbrella.html
+redumbrella
+
+// rehab : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/rehab.html
+rehab
+
+// reise : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/reise.html
+reise
+
+// reisen : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/reisen.html
+reisen
+
+// reit : National Association of Real Estate Investment Trusts, Inc.
+// https://www.iana.org/domains/root/db/reit.html
+reit
+
+// reliance : Reliance Industries Limited
+// https://www.iana.org/domains/root/db/reliance.html
+reliance
+
+// ren : ZDNS International Limited
+// https://www.iana.org/domains/root/db/ren.html
+ren
+
+// rent : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/rent.html
+rent
+
+// rentals : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/rentals.html
+rentals
+
+// repair : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/repair.html
+repair
+
+// report : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/report.html
+report
+
+// republican : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/republican.html
+republican
+
+// rest : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+// https://www.iana.org/domains/root/db/rest.html
+rest
+
+// restaurant : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/restaurant.html
+restaurant
+
+// review : dot Review Limited
+// https://www.iana.org/domains/root/db/review.html
+review
+
+// reviews : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/reviews.html
+reviews
+
+// rexroth : Robert Bosch GMBH
+// https://www.iana.org/domains/root/db/rexroth.html
+rexroth
+
+// rich : iRegistry GmbH
+// https://www.iana.org/domains/root/db/rich.html
+rich
+
+// richardli : Pacific Century Asset Management (HK) Limited
+// https://www.iana.org/domains/root/db/richardli.html
+richardli
+
+// ricoh : Ricoh Company, Ltd.
+// https://www.iana.org/domains/root/db/ricoh.html
+ricoh
+
+// ril : Reliance Industries Limited
+// https://www.iana.org/domains/root/db/ril.html
+ril
+
+// rio : Empresa Municipal de Informática SA - IPLANRIO
+// https://www.iana.org/domains/root/db/rio.html
+rio
+
+// rip : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/rip.html
+rip
+
+// rocks : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/rocks.html
+rocks
+
+// rodeo : Registry Services, LLC
+// https://www.iana.org/domains/root/db/rodeo.html
+rodeo
+
+// rogers : Rogers Communications Canada Inc.
+// https://www.iana.org/domains/root/db/rogers.html
+rogers
+
+// room : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/room.html
+room
+
+// rsvp : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/rsvp.html
+rsvp
+
+// rugby : World Rugby Strategic Developments Limited
+// https://www.iana.org/domains/root/db/rugby.html
+rugby
+
+// ruhr : dotSaarland GmbH
+// https://www.iana.org/domains/root/db/ruhr.html
+ruhr
+
+// run : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/run.html
+run
+
+// rwe : RWE AG
+// https://www.iana.org/domains/root/db/rwe.html
+rwe
+
+// ryukyu : BRregistry, Inc.
+// https://www.iana.org/domains/root/db/ryukyu.html
+ryukyu
+
+// saarland : dotSaarland GmbH
+// https://www.iana.org/domains/root/db/saarland.html
+saarland
+
+// safe : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/safe.html
+safe
+
+// safety : Safety Registry Services, LLC.
+// https://www.iana.org/domains/root/db/safety.html
+safety
+
+// sakura : SAKURA Internet Inc.
+// https://www.iana.org/domains/root/db/sakura.html
+sakura
+
+// sale : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/sale.html
+sale
+
+// salon : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/salon.html
+salon
+
+// samsclub : Wal-Mart Stores, Inc.
+// https://www.iana.org/domains/root/db/samsclub.html
+samsclub
+
+// samsung : SAMSUNG SDS CO., LTD
+// https://www.iana.org/domains/root/db/samsung.html
+samsung
+
+// sandvik : Sandvik AB
+// https://www.iana.org/domains/root/db/sandvik.html
+sandvik
+
+// sandvikcoromant : Sandvik AB
+// https://www.iana.org/domains/root/db/sandvikcoromant.html
+sandvikcoromant
+
+// sanofi : Sanofi
+// https://www.iana.org/domains/root/db/sanofi.html
+sanofi
+
+// sap : SAP AG
+// https://www.iana.org/domains/root/db/sap.html
+sap
+
+// sarl : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/sarl.html
+sarl
+
+// sas : Research IP LLC
+// https://www.iana.org/domains/root/db/sas.html
+sas
+
+// save : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/save.html
+save
+
+// saxo : Saxo Bank A/S
+// https://www.iana.org/domains/root/db/saxo.html
+saxo
+
+// sbi : STATE BANK OF INDIA
+// https://www.iana.org/domains/root/db/sbi.html
+sbi
+
+// sbs : ShortDot SA
+// https://www.iana.org/domains/root/db/sbs.html
+sbs
+
+// scb : The Siam Commercial Bank Public Company Limited ("SCB")
+// https://www.iana.org/domains/root/db/scb.html
+scb
+
+// schaeffler : Schaeffler Technologies AG & Co. KG
+// https://www.iana.org/domains/root/db/schaeffler.html
+schaeffler
+
+// schmidt : SCHMIDT GROUPE S.A.S.
+// https://www.iana.org/domains/root/db/schmidt.html
+schmidt
+
+// scholarships : Scholarships.com, LLC
+// https://www.iana.org/domains/root/db/scholarships.html
+scholarships
+
+// school : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/school.html
+school
+
+// schule : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/schule.html
+schule
+
+// schwarz : Schwarz Domains und Services GmbH & Co. KG
+// https://www.iana.org/domains/root/db/schwarz.html
+schwarz
+
+// science : dot Science Limited
+// https://www.iana.org/domains/root/db/science.html
+science
+
+// scot : Dot Scot Registry Limited
+// https://www.iana.org/domains/root/db/scot.html
+scot
+
+// search : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/search.html
+search
+
+// seat : SEAT, S.A. (Sociedad Unipersonal)
+// https://www.iana.org/domains/root/db/seat.html
+seat
+
+// secure : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/secure.html
+secure
+
+// security : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/security.html
+security
+
+// seek : Seek Limited
+// https://www.iana.org/domains/root/db/seek.html
+seek
+
+// select : Registry Services, LLC
+// https://www.iana.org/domains/root/db/select.html
+select
+
+// sener : Sener Ingeniería y Sistemas, S.A.
+// https://www.iana.org/domains/root/db/sener.html
+sener
+
+// services : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/services.html
+services
+
+// seven : Seven West Media Ltd
+// https://www.iana.org/domains/root/db/seven.html
+seven
+
+// sew : SEW-EURODRIVE GmbH & Co KG
+// https://www.iana.org/domains/root/db/sew.html
+sew
+
+// sex : ICM Registry SX LLC
+// https://www.iana.org/domains/root/db/sex.html
+sex
+
+// sexy : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/sexy.html
+sexy
+
+// sfr : Societe Francaise du Radiotelephone - SFR
+// https://www.iana.org/domains/root/db/sfr.html
+sfr
+
+// shangrila : Shangri‐La International Hotel Management Limited
+// https://www.iana.org/domains/root/db/shangrila.html
+shangrila
+
+// sharp : Sharp Corporation
+// https://www.iana.org/domains/root/db/sharp.html
+sharp
+
+// shaw : Shaw Cablesystems G.P.
+// https://www.iana.org/domains/root/db/shaw.html
+shaw
+
+// shell : Shell Information Technology International Inc
+// https://www.iana.org/domains/root/db/shell.html
+shell
+
+// shia : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+// https://www.iana.org/domains/root/db/shia.html
+shia
+
+// shiksha : Identity Digital Limited
+// https://www.iana.org/domains/root/db/shiksha.html
+shiksha
+
+// shoes : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/shoes.html
+shoes
+
+// shop : GMO Registry, Inc.
+// https://www.iana.org/domains/root/db/shop.html
+shop
+
+// shopping : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/shopping.html
+shopping
+
+// shouji : Beijing Qihu Keji Co., Ltd.
+// https://www.iana.org/domains/root/db/shouji.html
+shouji
+
+// show : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/show.html
+show
+
+// silk : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/silk.html
+silk
+
+// sina : Sina Corporation
+// https://www.iana.org/domains/root/db/sina.html
+sina
+
+// singles : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/singles.html
+singles
+
+// site : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/site.html
+site
+
+// ski : Identity Digital Limited
+// https://www.iana.org/domains/root/db/ski.html
+ski
+
+// skin : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/skin.html
+skin
+
+// sky : Sky International AG
+// https://www.iana.org/domains/root/db/sky.html
+sky
+
+// skype : Microsoft Corporation
+// https://www.iana.org/domains/root/db/skype.html
+skype
+
+// sling : DISH Technologies L.L.C.
+// https://www.iana.org/domains/root/db/sling.html
+sling
+
+// smart : Smart Communications, Inc. (SMART)
+// https://www.iana.org/domains/root/db/smart.html
+smart
+
+// smile : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/smile.html
+smile
+
+// sncf : Société Nationale SNCF
+// https://www.iana.org/domains/root/db/sncf.html
+sncf
+
+// soccer : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/soccer.html
+soccer
+
+// social : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/social.html
+social
+
+// softbank : SoftBank Group Corp.
+// https://www.iana.org/domains/root/db/softbank.html
+softbank
+
+// software : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/software.html
+software
+
+// sohu : Sohu.com Limited
+// https://www.iana.org/domains/root/db/sohu.html
+sohu
+
+// solar : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/solar.html
+solar
+
+// solutions : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/solutions.html
+solutions
+
+// song : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/song.html
+song
+
+// sony : Sony Corporation
+// https://www.iana.org/domains/root/db/sony.html
+sony
+
+// soy : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/soy.html
+soy
+
+// spa : Asia Spa and Wellness Promotion Council Limited
+// https://www.iana.org/domains/root/db/spa.html
+spa
+
+// space : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/space.html
+space
+
+// sport : SportAccord
+// https://www.iana.org/domains/root/db/sport.html
+sport
+
+// spot : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/spot.html
+spot
+
+// srl : InterNetX, Corp
+// https://www.iana.org/domains/root/db/srl.html
+srl
+
+// stada : STADA Arzneimittel AG
+// https://www.iana.org/domains/root/db/stada.html
+stada
+
+// staples : Staples, Inc.
+// https://www.iana.org/domains/root/db/staples.html
+staples
+
+// star : Star India Private Limited
+// https://www.iana.org/domains/root/db/star.html
+star
+
+// statebank : STATE BANK OF INDIA
+// https://www.iana.org/domains/root/db/statebank.html
+statebank
+
+// statefarm : State Farm Mutual Automobile Insurance Company
+// https://www.iana.org/domains/root/db/statefarm.html
+statefarm
+
+// stc : Saudi Telecom Company
+// https://www.iana.org/domains/root/db/stc.html
+stc
+
+// stcgroup : Saudi Telecom Company
+// https://www.iana.org/domains/root/db/stcgroup.html
+stcgroup
+
+// stockholm : Stockholms kommun
+// https://www.iana.org/domains/root/db/stockholm.html
+stockholm
+
+// storage : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/storage.html
+storage
+
+// store : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/store.html
+store
+
+// stream : dot Stream Limited
+// https://www.iana.org/domains/root/db/stream.html
+stream
+
+// studio : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/studio.html
+studio
+
+// study : Registry Services, LLC
+// https://www.iana.org/domains/root/db/study.html
+study
+
+// style : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/style.html
+style
+
+// sucks : Vox Populi Registry Ltd.
+// https://www.iana.org/domains/root/db/sucks.html
+sucks
+
+// supplies : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/supplies.html
+supplies
+
+// supply : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/supply.html
+supply
+
+// support : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/support.html
+support
+
+// surf : Registry Services, LLC
+// https://www.iana.org/domains/root/db/surf.html
+surf
+
+// surgery : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/surgery.html
+surgery
+
+// suzuki : SUZUKI MOTOR CORPORATION
+// https://www.iana.org/domains/root/db/suzuki.html
+suzuki
+
+// swatch : The Swatch Group Ltd
+// https://www.iana.org/domains/root/db/swatch.html
+swatch
+
+// swiss : Swiss Confederation
+// https://www.iana.org/domains/root/db/swiss.html
+swiss
+
+// sydney : State of New South Wales, Department of Premier and Cabinet
+// https://www.iana.org/domains/root/db/sydney.html
+sydney
+
+// systems : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/systems.html
+systems
+
+// tab : Tabcorp Holdings Limited
+// https://www.iana.org/domains/root/db/tab.html
+tab
+
+// taipei : Taipei City Government
+// https://www.iana.org/domains/root/db/taipei.html
+taipei
+
+// talk : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/talk.html
+talk
+
+// taobao : Alibaba Group Holding Limited
+// https://www.iana.org/domains/root/db/taobao.html
+taobao
+
+// target : Target Domain Holdings, LLC
+// https://www.iana.org/domains/root/db/target.html
+target
+
+// tatamotors : Tata Motors Ltd
+// https://www.iana.org/domains/root/db/tatamotors.html
+tatamotors
+
+// tatar : Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic"
+// https://www.iana.org/domains/root/db/tatar.html
+tatar
+
+// tattoo : Registry Services, LLC
+// https://www.iana.org/domains/root/db/tattoo.html
+tattoo
+
+// tax : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/tax.html
+tax
+
+// taxi : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/taxi.html
+taxi
+
+// tci : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+// https://www.iana.org/domains/root/db/tci.html
+tci
+
+// tdk : TDK Corporation
+// https://www.iana.org/domains/root/db/tdk.html
+tdk
+
+// team : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/team.html
+team
+
+// tech : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/tech.html
+tech
+
+// technology : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/technology.html
+technology
+
+// temasek : Temasek Holdings (Private) Limited
+// https://www.iana.org/domains/root/db/temasek.html
+temasek
+
+// tennis : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/tennis.html
+tennis
+
+// teva : Teva Pharmaceutical Industries Limited
+// https://www.iana.org/domains/root/db/teva.html
+teva
+
+// thd : Home Depot Product Authority, LLC
+// https://www.iana.org/domains/root/db/thd.html
+thd
+
+// theater : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/theater.html
+theater
+
+// theatre : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/theatre.html
+theatre
+
+// tiaa : Teachers Insurance and Annuity Association of America
+// https://www.iana.org/domains/root/db/tiaa.html
+tiaa
+
+// tickets : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/tickets.html
+tickets
+
+// tienda : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/tienda.html
+tienda
+
+// tips : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/tips.html
+tips
+
+// tires : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/tires.html
+tires
+
+// tirol : punkt Tirol GmbH
+// https://www.iana.org/domains/root/db/tirol.html
+tirol
+
+// tjmaxx : The TJX Companies, Inc.
+// https://www.iana.org/domains/root/db/tjmaxx.html
+tjmaxx
+
+// tjx : The TJX Companies, Inc.
+// https://www.iana.org/domains/root/db/tjx.html
+tjx
+
+// tkmaxx : The TJX Companies, Inc.
+// https://www.iana.org/domains/root/db/tkmaxx.html
+tkmaxx
+
+// tmall : Alibaba Group Holding Limited
+// https://www.iana.org/domains/root/db/tmall.html
+tmall
+
+// today : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/today.html
+today
+
+// tokyo : GMO Registry, Inc.
+// https://www.iana.org/domains/root/db/tokyo.html
+tokyo
+
+// tools : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/tools.html
+tools
+
+// top : .TOP Registry
+// https://www.iana.org/domains/root/db/top.html
+top
+
+// toray : Toray Industries, Inc.
+// https://www.iana.org/domains/root/db/toray.html
+toray
+
+// toshiba : TOSHIBA Corporation
+// https://www.iana.org/domains/root/db/toshiba.html
+toshiba
+
+// total : TotalEnergies SE
+// https://www.iana.org/domains/root/db/total.html
+total
+
+// tours : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/tours.html
+tours
+
+// town : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/town.html
+town
+
+// toyota : TOYOTA MOTOR CORPORATION
+// https://www.iana.org/domains/root/db/toyota.html
+toyota
+
+// toys : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/toys.html
+toys
+
+// trade : Elite Registry Limited
+// https://www.iana.org/domains/root/db/trade.html
+trade
+
+// trading : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/trading.html
+trading
+
+// training : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/training.html
+training
+
+// travel : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/travel.html
+travel
+
+// travelers : Travelers TLD, LLC
+// https://www.iana.org/domains/root/db/travelers.html
+travelers
+
+// travelersinsurance : Travelers TLD, LLC
+// https://www.iana.org/domains/root/db/travelersinsurance.html
+travelersinsurance
+
+// trust : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/trust.html
+trust
+
+// trv : Travelers TLD, LLC
+// https://www.iana.org/domains/root/db/trv.html
+trv
+
+// tube : Latin American Telecom LLC
+// https://www.iana.org/domains/root/db/tube.html
+tube
+
+// tui : TUI AG
+// https://www.iana.org/domains/root/db/tui.html
+tui
+
+// tunes : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/tunes.html
+tunes
+
+// tushu : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/tushu.html
+tushu
+
+// tvs : T V SUNDRAM IYENGAR & SONS LIMITED
+// https://www.iana.org/domains/root/db/tvs.html
+tvs
+
+// ubank : National Australia Bank Limited
+// https://www.iana.org/domains/root/db/ubank.html
+ubank
+
+// ubs : UBS AG
+// https://www.iana.org/domains/root/db/ubs.html
+ubs
+
+// unicom : China United Network Communications Corporation Limited
+// https://www.iana.org/domains/root/db/unicom.html
+unicom
+
+// university : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/university.html
+university
+
+// uno : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/uno.html
+uno
+
+// uol : UBN INTERNET LTDA.
+// https://www.iana.org/domains/root/db/uol.html
+uol
+
+// ups : UPS Market Driver, Inc.
+// https://www.iana.org/domains/root/db/ups.html
+ups
+
+// vacations : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/vacations.html
+vacations
+
+// vana : Internet Naming Company LLC
+// https://www.iana.org/domains/root/db/vana.html
+vana
+
+// vanguard : The Vanguard Group, Inc.
+// https://www.iana.org/domains/root/db/vanguard.html
+vanguard
+
+// vegas : Dot Vegas, Inc.
+// https://www.iana.org/domains/root/db/vegas.html
+vegas
+
+// ventures : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/ventures.html
+ventures
+
+// verisign : VeriSign, Inc.
+// https://www.iana.org/domains/root/db/verisign.html
+verisign
+
+// versicherung : tldbox GmbH
+// https://www.iana.org/domains/root/db/versicherung.html
+versicherung
+
+// vet : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/vet.html
+vet
+
+// viajes : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/viajes.html
+viajes
+
+// video : Dog Beach, LLC
+// https://www.iana.org/domains/root/db/video.html
+video
+
+// vig : VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe
+// https://www.iana.org/domains/root/db/vig.html
+vig
+
+// viking : Viking River Cruises (Bermuda) Ltd.
+// https://www.iana.org/domains/root/db/viking.html
+viking
+
+// villas : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/villas.html
+villas
+
+// vin : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/vin.html
+vin
+
+// vip : Registry Services, LLC
+// https://www.iana.org/domains/root/db/vip.html
+vip
+
+// virgin : Virgin Enterprises Limited
+// https://www.iana.org/domains/root/db/virgin.html
+virgin
+
+// visa : Visa Worldwide Pte. Limited
+// https://www.iana.org/domains/root/db/visa.html
+visa
+
+// vision : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/vision.html
+vision
+
+// viva : Saudi Telecom Company
+// https://www.iana.org/domains/root/db/viva.html
+viva
+
+// vivo : Telefonica Brasil S.A.
+// https://www.iana.org/domains/root/db/vivo.html
+vivo
+
+// vlaanderen : DNS.be vzw
+// https://www.iana.org/domains/root/db/vlaanderen.html
+vlaanderen
+
+// vodka : Registry Services, LLC
+// https://www.iana.org/domains/root/db/vodka.html
+vodka
+
+// volvo : Volvo Holding Sverige Aktiebolag
+// https://www.iana.org/domains/root/db/volvo.html
+volvo
+
+// vote : Monolith Registry LLC
+// https://www.iana.org/domains/root/db/vote.html
+vote
+
+// voting : Valuetainment Corp.
+// https://www.iana.org/domains/root/db/voting.html
+voting
+
+// voto : Monolith Registry LLC
+// https://www.iana.org/domains/root/db/voto.html
+voto
+
+// voyage : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/voyage.html
+voyage
+
+// wales : Nominet UK
+// https://www.iana.org/domains/root/db/wales.html
+wales
+
+// walmart : Wal-Mart Stores, Inc.
+// https://www.iana.org/domains/root/db/walmart.html
+walmart
+
+// walter : Sandvik AB
+// https://www.iana.org/domains/root/db/walter.html
+walter
+
+// wang : Zodiac Wang Limited
+// https://www.iana.org/domains/root/db/wang.html
+wang
+
+// wanggou : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/wanggou.html
+wanggou
+
+// watch : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/watch.html
+watch
+
+// watches : Identity Digital Limited
+// https://www.iana.org/domains/root/db/watches.html
+watches
+
+// weather : International Business Machines Corporation
+// https://www.iana.org/domains/root/db/weather.html
+weather
+
+// weatherchannel : International Business Machines Corporation
+// https://www.iana.org/domains/root/db/weatherchannel.html
+weatherchannel
+
+// webcam : dot Webcam Limited
+// https://www.iana.org/domains/root/db/webcam.html
+webcam
+
+// weber : Saint-Gobain Weber SA
+// https://www.iana.org/domains/root/db/weber.html
+weber
+
+// website : Radix Technologies Inc.
+// https://www.iana.org/domains/root/db/website.html
+website
+
+// wed
+// https://www.iana.org/domains/root/db/wed.html
+wed
+
+// wedding : Registry Services, LLC
+// https://www.iana.org/domains/root/db/wedding.html
+wedding
+
+// weibo : Sina Corporation
+// https://www.iana.org/domains/root/db/weibo.html
+weibo
+
+// weir : Weir Group IP Limited
+// https://www.iana.org/domains/root/db/weir.html
+weir
+
+// whoswho : Who's Who Registry
+// https://www.iana.org/domains/root/db/whoswho.html
+whoswho
+
+// wien : punkt.wien GmbH
+// https://www.iana.org/domains/root/db/wien.html
+wien
+
+// wiki : Registry Services, LLC
+// https://www.iana.org/domains/root/db/wiki.html
+wiki
+
+// williamhill : William Hill Organization Limited
+// https://www.iana.org/domains/root/db/williamhill.html
+williamhill
+
+// win : First Registry Limited
+// https://www.iana.org/domains/root/db/win.html
+win
+
+// windows : Microsoft Corporation
+// https://www.iana.org/domains/root/db/windows.html
+windows
+
+// wine : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/wine.html
+wine
+
+// winners : The TJX Companies, Inc.
+// https://www.iana.org/domains/root/db/winners.html
+winners
+
+// wme : William Morris Endeavor Entertainment, LLC
+// https://www.iana.org/domains/root/db/wme.html
+wme
+
+// wolterskluwer : Wolters Kluwer N.V.
+// https://www.iana.org/domains/root/db/wolterskluwer.html
+wolterskluwer
+
+// woodside : Woodside Petroleum Limited
+// https://www.iana.org/domains/root/db/woodside.html
+woodside
+
+// work : Registry Services, LLC
+// https://www.iana.org/domains/root/db/work.html
+work
+
+// works : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/works.html
+works
+
+// world : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/world.html
+world
+
+// wow : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/wow.html
+wow
+
+// wtc : World Trade Centers Association, Inc.
+// https://www.iana.org/domains/root/db/wtc.html
+wtc
+
+// wtf : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/wtf.html
+wtf
+
+// xbox : Microsoft Corporation
+// https://www.iana.org/domains/root/db/xbox.html
+xbox
+
+// xerox : Xerox DNHC LLC
+// https://www.iana.org/domains/root/db/xerox.html
+xerox
+
+// xihuan : Beijing Qihu Keji Co., Ltd.
+// https://www.iana.org/domains/root/db/xihuan.html
+xihuan
+
+// xin : Elegant Leader Limited
+// https://www.iana.org/domains/root/db/xin.html
+xin
+
+// xn--11b4c3d : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--11b4c3d.html
+कॉम
+
+// xn--1ck2e1b : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--1ck2e1b.html
+セール
+
+// xn--1qqw23a : Guangzhou YU Wei Information Technology Co., Ltd.
+// https://www.iana.org/domains/root/db/xn--1qqw23a.html
+佛山
+
+// xn--30rr7y : Excellent First Limited
+// https://www.iana.org/domains/root/db/xn--30rr7y.html
+慈善
+
+// xn--3bst00m : Eagle Horizon Limited
+// https://www.iana.org/domains/root/db/xn--3bst00m.html
+集团
+
+// xn--3ds443g : TLD REGISTRY LIMITED OY
+// https://www.iana.org/domains/root/db/xn--3ds443g.html
+在线
+
+// xn--3pxu8k : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--3pxu8k.html
+点看
+
+// xn--42c2d9a : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--42c2d9a.html
+คอม
+
+// xn--45q11c : Zodiac Gemini Ltd
+// https://www.iana.org/domains/root/db/xn--45q11c.html
+八卦
+
+// xn--4gbrim : Helium TLDs Ltd
+// https://www.iana.org/domains/root/db/xn--4gbrim.html
+موقع
+
+// xn--55qw42g : China Organizational Name Administration Center
+// https://www.iana.org/domains/root/db/xn--55qw42g.html
+公益
+
+// xn--55qx5d : China Internet Network Information Center (CNNIC)
+// https://www.iana.org/domains/root/db/xn--55qx5d.html
+公司
+
+// xn--5su34j936bgsg : Shangri‐La International Hotel Management Limited
+// https://www.iana.org/domains/root/db/xn--5su34j936bgsg.html
+香格里拉
+
+// xn--5tzm5g : Global Website TLD Asia Limited
+// https://www.iana.org/domains/root/db/xn--5tzm5g.html
+网站
+
+// xn--6frz82g : Identity Digital Limited
+// https://www.iana.org/domains/root/db/xn--6frz82g.html
+移动
+
+// xn--6qq986b3xl : Tycoon Treasure Limited
+// https://www.iana.org/domains/root/db/xn--6qq986b3xl.html
+我爱你
+
+// xn--80adxhks : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+// https://www.iana.org/domains/root/db/xn--80adxhks.html
+москва
+
+// xn--80aqecdr1a : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+// https://www.iana.org/domains/root/db/xn--80aqecdr1a.html
+католик
+
+// xn--80asehdb : CORE Association
+// https://www.iana.org/domains/root/db/xn--80asehdb.html
+онлайн
+
+// xn--80aswg : CORE Association
+// https://www.iana.org/domains/root/db/xn--80aswg.html
+сайт
+
+// xn--8y0a063a : China United Network Communications Corporation Limited
+// https://www.iana.org/domains/root/db/xn--8y0a063a.html
+联通
+
+// xn--9dbq2a : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--9dbq2a.html
+קום
+
+// xn--9et52u : RISE VICTORY LIMITED
+// https://www.iana.org/domains/root/db/xn--9et52u.html
+时尚
+
+// xn--9krt00a : Sina Corporation
+// https://www.iana.org/domains/root/db/xn--9krt00a.html
+微博
+
+// xn--b4w605ferd : Temasek Holdings (Private) Limited
+// https://www.iana.org/domains/root/db/xn--b4w605ferd.html
+淡马锡
+
+// xn--bck1b9a5dre4c : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--bck1b9a5dre4c.html
+ファッション
+
+// xn--c1avg : Public Interest Registry
+// https://www.iana.org/domains/root/db/xn--c1avg.html
+орг
+
+// xn--c2br7g : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--c2br7g.html
+नेट
+
+// xn--cck2b3b : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--cck2b3b.html
+ストア
+
+// xn--cckwcxetd : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--cckwcxetd.html
+アマゾン
+
+// xn--cg4bki : SAMSUNG SDS CO., LTD
+// https://www.iana.org/domains/root/db/xn--cg4bki.html
+삼성
+
+// xn--czr694b : Internet DotTrademark Organisation Limited
+// https://www.iana.org/domains/root/db/xn--czr694b.html
+商标
+
+// xn--czrs0t : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/xn--czrs0t.html
+商店
+
+// xn--czru2d : Zodiac Aquarius Limited
+// https://www.iana.org/domains/root/db/xn--czru2d.html
+商城
+
+// xn--d1acj3b : The Foundation for Network Initiatives “The Smart Internet”
+// https://www.iana.org/domains/root/db/xn--d1acj3b.html
+дети
+
+// xn--eckvdtc9d : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--eckvdtc9d.html
+ポイント
+
+// xn--efvy88h : Guangzhou YU Wei Information Technology Co., Ltd.
+// https://www.iana.org/domains/root/db/xn--efvy88h.html
+新闻
+
+// xn--fct429k : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--fct429k.html
+家電
+
+// xn--fhbei : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--fhbei.html
+كوم
+
+// xn--fiq228c5hs : TLD REGISTRY LIMITED OY
+// https://www.iana.org/domains/root/db/xn--fiq228c5hs.html
+中文网
+
+// xn--fiq64b : CITIC Group Corporation
+// https://www.iana.org/domains/root/db/xn--fiq64b.html
+中信
+
+// xn--fjq720a : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/xn--fjq720a.html
+娱乐
+
+// xn--flw351e : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/xn--flw351e.html
+谷歌
+
+// xn--fzys8d69uvgm : PCCW Enterprises Limited
+// https://www.iana.org/domains/root/db/xn--fzys8d69uvgm.html
+電訊盈科
+
+// xn--g2xx48c : Nawang Heli(Xiamen) Network Service Co., LTD.
+// https://www.iana.org/domains/root/db/xn--g2xx48c.html
+购物
+
+// xn--gckr3f0f : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--gckr3f0f.html
+クラウド
+
+// xn--gk3at1e : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--gk3at1e.html
+通販
+
+// xn--hxt814e : Zodiac Taurus Limited
+// https://www.iana.org/domains/root/db/xn--hxt814e.html
+网店
+
+// xn--i1b6b1a6a2e : Public Interest Registry
+// https://www.iana.org/domains/root/db/xn--i1b6b1a6a2e.html
+संगठन
+
+// xn--imr513n : Internet DotTrademark Organisation Limited
+// https://www.iana.org/domains/root/db/xn--imr513n.html
+餐厅
+
+// xn--io0a7i : China Internet Network Information Center (CNNIC)
+// https://www.iana.org/domains/root/db/xn--io0a7i.html
+网络
+
+// xn--j1aef : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--j1aef.html
+ком
+
+// xn--jlq480n2rg : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--jlq480n2rg.html
+亚马逊
+
+// xn--jvr189m : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--jvr189m.html
+食品
+
+// xn--kcrx77d1x4a : Koninklijke Philips N.V.
+// https://www.iana.org/domains/root/db/xn--kcrx77d1x4a.html
+飞利浦
+
+// xn--kput3i : Beijing RITT-Net Technology Development Co., Ltd
+// https://www.iana.org/domains/root/db/xn--kput3i.html
+手机
+
+// xn--mgba3a3ejt : Aramco Services Company
+// https://www.iana.org/domains/root/db/xn--mgba3a3ejt.html
+ارامكو
+
+// xn--mgba7c0bbn0a : Competrol (Luxembourg) Sarl
+// https://www.iana.org/domains/root/db/xn--mgba7c0bbn0a.html
+العليان
+
+// xn--mgbab2bd : CORE Association
+// https://www.iana.org/domains/root/db/xn--mgbab2bd.html
+بازار
+
+// xn--mgbca7dzdo : Abu Dhabi Systems and Information Centre
+// https://www.iana.org/domains/root/db/xn--mgbca7dzdo.html
+ابوظبي
+
+// xn--mgbi4ecexp : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+// https://www.iana.org/domains/root/db/xn--mgbi4ecexp.html
+كاثوليك
+
+// xn--mgbt3dhd : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+// https://www.iana.org/domains/root/db/xn--mgbt3dhd.html
+همراه
+
+// xn--mk1bu44c : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--mk1bu44c.html
+닷컴
+
+// xn--mxtq1m : Net-Chinese Co., Ltd.
+// https://www.iana.org/domains/root/db/xn--mxtq1m.html
+政府
+
+// xn--ngbc5azd : International Domain Registry Pty. Ltd.
+// https://www.iana.org/domains/root/db/xn--ngbc5azd.html
+شبكة
+
+// xn--ngbe9e0a : Kuwait Finance House
+// https://www.iana.org/domains/root/db/xn--ngbe9e0a.html
+بيتك
+
+// xn--ngbrx : League of Arab States
+// https://www.iana.org/domains/root/db/xn--ngbrx.html
+عرب
+
+// xn--nqv7f : Public Interest Registry
+// https://www.iana.org/domains/root/db/xn--nqv7f.html
+机构
+
+// xn--nqv7fs00ema : Public Interest Registry
+// https://www.iana.org/domains/root/db/xn--nqv7fs00ema.html
+组织机构
+
+// xn--nyqy26a : Stable Tone Limited
+// https://www.iana.org/domains/root/db/xn--nyqy26a.html
+健康
+
+// xn--otu796d : Jiang Yu Liang Cai Technology Company Limited
+// https://www.iana.org/domains/root/db/xn--otu796d.html
+招聘
+
+// xn--p1acf : Rusnames Limited
+// https://www.iana.org/domains/root/db/xn--p1acf.html
+рус
+
+// xn--pssy2u : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--pssy2u.html
+大拿
+
+// xn--q9jyb4c : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/xn--q9jyb4c.html
+みんな
+
+// xn--qcka1pmc : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/xn--qcka1pmc.html
+グーグル
+
+// xn--rhqv96g : Stable Tone Limited
+// https://www.iana.org/domains/root/db/xn--rhqv96g.html
+世界
+
+// xn--rovu88b : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/xn--rovu88b.html
+書籍
+
+// xn--ses554g : KNET Co., Ltd.
+// https://www.iana.org/domains/root/db/xn--ses554g.html
+网址
+
+// xn--t60b56a : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--t60b56a.html
+닷넷
+
+// xn--tckwe : VeriSign Sarl
+// https://www.iana.org/domains/root/db/xn--tckwe.html
+コム
+
+// xn--tiq49xqyj : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+// https://www.iana.org/domains/root/db/xn--tiq49xqyj.html
+天主教
+
+// xn--unup4y : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/xn--unup4y.html
+游戏
+
+// xn--vermgensberater-ctb : Deutsche Vermögensberatung Aktiengesellschaft DVAG
+// https://www.iana.org/domains/root/db/xn--vermgensberater-ctb.html
+vermögensberater
+
+// xn--vermgensberatung-pwb : Deutsche Vermögensberatung Aktiengesellschaft DVAG
+// https://www.iana.org/domains/root/db/xn--vermgensberatung-pwb.html
+vermögensberatung
+
+// xn--vhquv : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/xn--vhquv.html
+企业
+
+// xn--vuq861b : Beijing Tele-info Technology Co., Ltd.
+// https://www.iana.org/domains/root/db/xn--vuq861b.html
+信息
+
+// xn--w4r85el8fhu5dnra : Kerry Trading Co. Limited
+// https://www.iana.org/domains/root/db/xn--w4r85el8fhu5dnra.html
+嘉里大酒店
+
+// xn--w4rs40l : Kerry Trading Co. Limited
+// https://www.iana.org/domains/root/db/xn--w4rs40l.html
+嘉里
+
+// xn--xhq521b : Guangzhou YU Wei Information Technology Co., Ltd.
+// https://www.iana.org/domains/root/db/xn--xhq521b.html
+广东
+
+// xn--zfr164b : China Organizational Name Administration Center
+// https://www.iana.org/domains/root/db/xn--zfr164b.html
+政务
+
+// xyz : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/xyz.html
+xyz
+
+// yachts : XYZ.COM LLC
+// https://www.iana.org/domains/root/db/yachts.html
+yachts
+
+// yahoo : Oath Inc.
+// https://www.iana.org/domains/root/db/yahoo.html
+yahoo
+
+// yamaxun : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/yamaxun.html
+yamaxun
+
+// yandex : Yandex Europe B.V.
+// https://www.iana.org/domains/root/db/yandex.html
+yandex
+
+// yodobashi : YODOBASHI CAMERA CO.,LTD.
+// https://www.iana.org/domains/root/db/yodobashi.html
+yodobashi
+
+// yoga : Registry Services, LLC
+// https://www.iana.org/domains/root/db/yoga.html
+yoga
+
+// yokohama : GMO Registry, Inc.
+// https://www.iana.org/domains/root/db/yokohama.html
+yokohama
+
+// you : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/you.html
+you
+
+// youtube : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/youtube.html
+youtube
+
+// yun : Beijing Qihu Keji Co., Ltd.
+// https://www.iana.org/domains/root/db/yun.html
+yun
+
+// zappos : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/zappos.html
+zappos
+
+// zara : Industria de Diseño Textil, S.A. (INDITEX, S.A.)
+// https://www.iana.org/domains/root/db/zara.html
+zara
+
+// zero : Amazon Registry Services, Inc.
+// https://www.iana.org/domains/root/db/zero.html
+zero
+
+// zip : Charleston Road Registry Inc.
+// https://www.iana.org/domains/root/db/zip.html
+zip
+
+// zone : Binky Moon, LLC
+// https://www.iana.org/domains/root/db/zone.html
+zone
+
+// zuerich : Kanton Zürich (Canton of Zurich)
+// https://www.iana.org/domains/root/db/zuerich.html
+zuerich
+
+
+// ===END ICANN DOMAINS===
+// ===BEGIN PRIVATE DOMAINS===
+// (Note: these are in alphabetical order by company name)
+
+// 12CHARS: https://12chars.com
+// Submitted by Kenny Niehage <psl@12chars.com>
+12chars.dev
+12chars.it
+12chars.pro
+
+// 1GB LLC : https://www.1gb.ua/
+// Submitted by 1GB LLC <noc@1gb.com.ua>
+cc.ua
+inf.ua
+ltd.ua
+
+// 611coin : https://611project.org/
+611.to
+
+// A2 Hosting
+// Submitted by Tyler Hall <sysadmin@a2hosting.com>
+a2hosted.com
+cpserver.com
+
+// Aaron Marais' Gitlab pages: https://lab.aaronleem.co.za
+// Submitted by Aaron Marais <its_me@aaronleem.co.za>
+graphox.us
+
+// accesso Technology Group, plc. : https://accesso.com/
+// Submitted by accesso Team <accessoecommerce@accesso.com>
+*.devcdnaccesso.com
+
+// Acorn Labs : https://acorn.io
+// Submitted by Craig Jellick <domains@acorn.io>
+*.on-acorn.io
+
+// ActiveTrail: https://www.activetrail.biz/
+// Submitted by Ofer Kalaora <postmaster@activetrail.com>
+activetrail.biz
+
+// Adaptable.io : https://adaptable.io
+// Submitted by Mark Terrel <support@adaptable.io>
+adaptable.app
+
+// Adobe : https://www.adobe.com/
+// Submitted by Ian Boston <boston@adobe.com> and Lars Trieloff <trieloff@adobe.com>
+adobeaemcloud.com
+*.dev.adobeaemcloud.com
+aem.live
+hlx.live
+adobeaemcloud.net
+aem.page
+hlx.page
+hlx3.page
+
+// Adobe Developer Platform : https://developer.adobe.com
+// Submitted by Jesse MacFadyen<jessem@adobe.com>
+adobeio-static.net
+adobeioruntime.net
+
+// Agnat sp. z o.o. : https://domena.pl
+// Submitted by Przemyslaw Plewa <it-admin@domena.pl>
+beep.pl
+
+// Airkit : https://www.airkit.com/
+// Submitted by Grant Cooksey <security@airkit.com>
+airkitapps.com
+airkitapps-au.com
+airkitapps.eu
+
+// Aiven: https://aiven.io/
+// Submitted by Etienne Stalmans <security@aiven.io>
+aivencloud.com
+
+// Akamai : https://www.akamai.com/
+// Submitted by Akamai Team <publicsuffixlist@akamai.com>
+akadns.net
+akamai.net
+akamai-staging.net
+akamaiedge.net
+akamaiedge-staging.net
+akamaihd.net
+akamaihd-staging.net
+akamaiorigin.net
+akamaiorigin-staging.net
+akamaized.net
+akamaized-staging.net
+edgekey.net
+edgekey-staging.net
+edgesuite.net
+edgesuite-staging.net
+
+// alboto.ca : http://alboto.ca
+// Submitted by Anton Avramov <avramov@alboto.ca>
+barsy.ca
+
+// Alces Software Ltd : http://alces-software.com
+// Submitted by Mark J. Titorenko <mark.titorenko@alces-software.com>
+*.compute.estate
+*.alces.network
+
+// all-inkl.com : https://all-inkl.com
+// Submitted by Werner Kaltofen <wk@all-inkl.com>
+kasserver.com
+
+// Altervista: https://www.altervista.org
+// Submitted by Carlo Cannas <tech_staff@altervista.it>
+altervista.org
+
+// alwaysdata : https://www.alwaysdata.com
+// Submitted by Cyril <admin@alwaysdata.com>
+alwaysdata.net
+
+// Amaze Software : https://amaze.co
+// Submitted by Domain Admin <domainadmin@amaze.co>
+myamaze.net
+
+// Amazon : https://www.amazon.com/
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Subsections of Amazon/subsidiaries will appear until "concludes" tag
+
+// Amazon API Gateway
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 9e37648f-a66c-4655-9ab1-5981f8737197
+execute-api.cn-north-1.amazonaws.com.cn
+execute-api.cn-northwest-1.amazonaws.com.cn
+execute-api.af-south-1.amazonaws.com
+execute-api.ap-east-1.amazonaws.com
+execute-api.ap-northeast-1.amazonaws.com
+execute-api.ap-northeast-2.amazonaws.com
+execute-api.ap-northeast-3.amazonaws.com
+execute-api.ap-south-1.amazonaws.com
+execute-api.ap-south-2.amazonaws.com
+execute-api.ap-southeast-1.amazonaws.com
+execute-api.ap-southeast-2.amazonaws.com
+execute-api.ap-southeast-3.amazonaws.com
+execute-api.ap-southeast-4.amazonaws.com
+execute-api.ca-central-1.amazonaws.com
+execute-api.ca-west-1.amazonaws.com
+execute-api.eu-central-1.amazonaws.com
+execute-api.eu-central-2.amazonaws.com
+execute-api.eu-north-1.amazonaws.com
+execute-api.eu-south-1.amazonaws.com
+execute-api.eu-south-2.amazonaws.com
+execute-api.eu-west-1.amazonaws.com
+execute-api.eu-west-2.amazonaws.com
+execute-api.eu-west-3.amazonaws.com
+execute-api.il-central-1.amazonaws.com
+execute-api.me-central-1.amazonaws.com
+execute-api.me-south-1.amazonaws.com
+execute-api.sa-east-1.amazonaws.com
+execute-api.us-east-1.amazonaws.com
+execute-api.us-east-2.amazonaws.com
+execute-api.us-gov-east-1.amazonaws.com
+execute-api.us-gov-west-1.amazonaws.com
+execute-api.us-west-1.amazonaws.com
+execute-api.us-west-2.amazonaws.com
+
+// Amazon CloudFront
+// Submitted by Donavan Miller <donavanm@amazon.com>
+// Reference: 54144616-fd49-4435-8535-19c6a601bdb3
+cloudfront.net
+
+// Amazon Cognito
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 7bee1013-f456-47df-bfe8-03c78d946d61
+auth.af-south-1.amazoncognito.com
+auth.ap-northeast-1.amazoncognito.com
+auth.ap-northeast-2.amazoncognito.com
+auth.ap-northeast-3.amazoncognito.com
+auth.ap-south-1.amazoncognito.com
+auth.ap-southeast-1.amazoncognito.com
+auth.ap-southeast-2.amazoncognito.com
+auth.ap-southeast-3.amazoncognito.com
+auth.ca-central-1.amazoncognito.com
+auth.eu-central-1.amazoncognito.com
+auth.eu-north-1.amazoncognito.com
+auth.eu-south-1.amazoncognito.com
+auth.eu-west-1.amazoncognito.com
+auth.eu-west-2.amazoncognito.com
+auth.eu-west-3.amazoncognito.com
+auth.il-central-1.amazoncognito.com
+auth.me-south-1.amazoncognito.com
+auth.sa-east-1.amazoncognito.com
+auth.us-east-1.amazoncognito.com
+auth-fips.us-east-1.amazoncognito.com
+auth.us-east-2.amazoncognito.com
+auth-fips.us-east-2.amazoncognito.com
+auth-fips.us-gov-west-1.amazoncognito.com
+auth.us-west-1.amazoncognito.com
+auth-fips.us-west-1.amazoncognito.com
+auth.us-west-2.amazoncognito.com
+auth-fips.us-west-2.amazoncognito.com
+
+// Amazon EC2
+// Submitted by Luke Wells <psl-maintainers@amazon.com>
+// Reference: 4c38fa71-58ac-4768-99e5-689c1767e537
+*.compute.amazonaws.com
+*.compute-1.amazonaws.com
+*.compute.amazonaws.com.cn
+us-east-1.amazonaws.com
+
+// Amazon EMR
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 597f3f8e-9283-4e48-8e32-7ee25a1ff6ab
+emrappui-prod.cn-north-1.amazonaws.com.cn
+emrnotebooks-prod.cn-north-1.amazonaws.com.cn
+emrstudio-prod.cn-north-1.amazonaws.com.cn
+emrappui-prod.cn-northwest-1.amazonaws.com.cn
+emrnotebooks-prod.cn-northwest-1.amazonaws.com.cn
+emrstudio-prod.cn-northwest-1.amazonaws.com.cn
+emrappui-prod.af-south-1.amazonaws.com
+emrnotebooks-prod.af-south-1.amazonaws.com
+emrstudio-prod.af-south-1.amazonaws.com
+emrappui-prod.ap-east-1.amazonaws.com
+emrnotebooks-prod.ap-east-1.amazonaws.com
+emrstudio-prod.ap-east-1.amazonaws.com
+emrappui-prod.ap-northeast-1.amazonaws.com
+emrnotebooks-prod.ap-northeast-1.amazonaws.com
+emrstudio-prod.ap-northeast-1.amazonaws.com
+emrappui-prod.ap-northeast-2.amazonaws.com
+emrnotebooks-prod.ap-northeast-2.amazonaws.com
+emrstudio-prod.ap-northeast-2.amazonaws.com
+emrappui-prod.ap-northeast-3.amazonaws.com
+emrnotebooks-prod.ap-northeast-3.amazonaws.com
+emrstudio-prod.ap-northeast-3.amazonaws.com
+emrappui-prod.ap-south-1.amazonaws.com
+emrnotebooks-prod.ap-south-1.amazonaws.com
+emrstudio-prod.ap-south-1.amazonaws.com
+emrappui-prod.ap-southeast-1.amazonaws.com
+emrnotebooks-prod.ap-southeast-1.amazonaws.com
+emrstudio-prod.ap-southeast-1.amazonaws.com
+emrappui-prod.ap-southeast-2.amazonaws.com
+emrnotebooks-prod.ap-southeast-2.amazonaws.com
+emrstudio-prod.ap-southeast-2.amazonaws.com
+emrappui-prod.ap-southeast-3.amazonaws.com
+emrnotebooks-prod.ap-southeast-3.amazonaws.com
+emrstudio-prod.ap-southeast-3.amazonaws.com
+emrappui-prod.ca-central-1.amazonaws.com
+emrnotebooks-prod.ca-central-1.amazonaws.com
+emrstudio-prod.ca-central-1.amazonaws.com
+emrappui-prod.eu-central-1.amazonaws.com
+emrnotebooks-prod.eu-central-1.amazonaws.com
+emrstudio-prod.eu-central-1.amazonaws.com
+emrappui-prod.eu-north-1.amazonaws.com
+emrnotebooks-prod.eu-north-1.amazonaws.com
+emrstudio-prod.eu-north-1.amazonaws.com
+emrappui-prod.eu-south-1.amazonaws.com
+emrnotebooks-prod.eu-south-1.amazonaws.com
+emrstudio-prod.eu-south-1.amazonaws.com
+emrappui-prod.eu-west-1.amazonaws.com
+emrnotebooks-prod.eu-west-1.amazonaws.com
+emrstudio-prod.eu-west-1.amazonaws.com
+emrappui-prod.eu-west-2.amazonaws.com
+emrnotebooks-prod.eu-west-2.amazonaws.com
+emrstudio-prod.eu-west-2.amazonaws.com
+emrappui-prod.eu-west-3.amazonaws.com
+emrnotebooks-prod.eu-west-3.amazonaws.com
+emrstudio-prod.eu-west-3.amazonaws.com
+emrappui-prod.me-central-1.amazonaws.com
+emrnotebooks-prod.me-central-1.amazonaws.com
+emrstudio-prod.me-central-1.amazonaws.com
+emrappui-prod.me-south-1.amazonaws.com
+emrnotebooks-prod.me-south-1.amazonaws.com
+emrstudio-prod.me-south-1.amazonaws.com
+emrappui-prod.sa-east-1.amazonaws.com
+emrnotebooks-prod.sa-east-1.amazonaws.com
+emrstudio-prod.sa-east-1.amazonaws.com
+emrappui-prod.us-east-1.amazonaws.com
+emrnotebooks-prod.us-east-1.amazonaws.com
+emrstudio-prod.us-east-1.amazonaws.com
+emrappui-prod.us-east-2.amazonaws.com
+emrnotebooks-prod.us-east-2.amazonaws.com
+emrstudio-prod.us-east-2.amazonaws.com
+emrappui-prod.us-gov-east-1.amazonaws.com
+emrnotebooks-prod.us-gov-east-1.amazonaws.com
+emrstudio-prod.us-gov-east-1.amazonaws.com
+emrappui-prod.us-gov-west-1.amazonaws.com
+emrnotebooks-prod.us-gov-west-1.amazonaws.com
+emrstudio-prod.us-gov-west-1.amazonaws.com
+emrappui-prod.us-west-1.amazonaws.com
+emrnotebooks-prod.us-west-1.amazonaws.com
+emrstudio-prod.us-west-1.amazonaws.com
+emrappui-prod.us-west-2.amazonaws.com
+emrnotebooks-prod.us-west-2.amazonaws.com
+emrstudio-prod.us-west-2.amazonaws.com
+
+// Amazon Managed Workflows for Apache Airflow
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 4ab55e6f-90c0-4a8d-b6a0-52ca5dbb1c2e
+*.cn-north-1.airflow.amazonaws.com.cn
+*.cn-northwest-1.airflow.amazonaws.com.cn
+*.ap-northeast-1.airflow.amazonaws.com
+*.ap-northeast-2.airflow.amazonaws.com
+*.ap-south-1.airflow.amazonaws.com
+*.ap-southeast-1.airflow.amazonaws.com
+*.ap-southeast-2.airflow.amazonaws.com
+*.ca-central-1.airflow.amazonaws.com
+*.eu-central-1.airflow.amazonaws.com
+*.eu-north-1.airflow.amazonaws.com
+*.eu-west-1.airflow.amazonaws.com
+*.eu-west-2.airflow.amazonaws.com
+*.eu-west-3.airflow.amazonaws.com
+*.sa-east-1.airflow.amazonaws.com
+*.us-east-1.airflow.amazonaws.com
+*.us-east-2.airflow.amazonaws.com
+*.us-west-2.airflow.amazonaws.com
+
+// Amazon S3
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: cd5c8b3a-67b7-4b40-9236-c87ce81a3d10
+s3.dualstack.cn-north-1.amazonaws.com.cn
+s3-accesspoint.dualstack.cn-north-1.amazonaws.com.cn
+s3-website.dualstack.cn-north-1.amazonaws.com.cn
+s3.cn-north-1.amazonaws.com.cn
+s3-accesspoint.cn-north-1.amazonaws.com.cn
+s3-deprecated.cn-north-1.amazonaws.com.cn
+s3-object-lambda.cn-north-1.amazonaws.com.cn
+s3-website.cn-north-1.amazonaws.com.cn
+s3.dualstack.cn-northwest-1.amazonaws.com.cn
+s3-accesspoint.dualstack.cn-northwest-1.amazonaws.com.cn
+s3.cn-northwest-1.amazonaws.com.cn
+s3-accesspoint.cn-northwest-1.amazonaws.com.cn
+s3-object-lambda.cn-northwest-1.amazonaws.com.cn
+s3-website.cn-northwest-1.amazonaws.com.cn
+s3.dualstack.af-south-1.amazonaws.com
+s3-accesspoint.dualstack.af-south-1.amazonaws.com
+s3-website.dualstack.af-south-1.amazonaws.com
+s3.af-south-1.amazonaws.com
+s3-accesspoint.af-south-1.amazonaws.com
+s3-object-lambda.af-south-1.amazonaws.com
+s3-website.af-south-1.amazonaws.com
+s3.dualstack.ap-east-1.amazonaws.com
+s3-accesspoint.dualstack.ap-east-1.amazonaws.com
+s3.ap-east-1.amazonaws.com
+s3-accesspoint.ap-east-1.amazonaws.com
+s3-object-lambda.ap-east-1.amazonaws.com
+s3-website.ap-east-1.amazonaws.com
+s3.dualstack.ap-northeast-1.amazonaws.com
+s3-accesspoint.dualstack.ap-northeast-1.amazonaws.com
+s3-website.dualstack.ap-northeast-1.amazonaws.com
+s3.ap-northeast-1.amazonaws.com
+s3-accesspoint.ap-northeast-1.amazonaws.com
+s3-object-lambda.ap-northeast-1.amazonaws.com
+s3-website.ap-northeast-1.amazonaws.com
+s3.dualstack.ap-northeast-2.amazonaws.com
+s3-accesspoint.dualstack.ap-northeast-2.amazonaws.com
+s3-website.dualstack.ap-northeast-2.amazonaws.com
+s3.ap-northeast-2.amazonaws.com
+s3-accesspoint.ap-northeast-2.amazonaws.com
+s3-object-lambda.ap-northeast-2.amazonaws.com
+s3-website.ap-northeast-2.amazonaws.com
+s3.dualstack.ap-northeast-3.amazonaws.com
+s3-accesspoint.dualstack.ap-northeast-3.amazonaws.com
+s3-website.dualstack.ap-northeast-3.amazonaws.com
+s3.ap-northeast-3.amazonaws.com
+s3-accesspoint.ap-northeast-3.amazonaws.com
+s3-object-lambda.ap-northeast-3.amazonaws.com
+s3-website.ap-northeast-3.amazonaws.com
+s3.dualstack.ap-south-1.amazonaws.com
+s3-accesspoint.dualstack.ap-south-1.amazonaws.com
+s3-website.dualstack.ap-south-1.amazonaws.com
+s3.ap-south-1.amazonaws.com
+s3-accesspoint.ap-south-1.amazonaws.com
+s3-object-lambda.ap-south-1.amazonaws.com
+s3-website.ap-south-1.amazonaws.com
+s3.dualstack.ap-south-2.amazonaws.com
+s3-accesspoint.dualstack.ap-south-2.amazonaws.com
+s3.ap-south-2.amazonaws.com
+s3-accesspoint.ap-south-2.amazonaws.com
+s3-object-lambda.ap-south-2.amazonaws.com
+s3-website.ap-south-2.amazonaws.com
+s3.dualstack.ap-southeast-1.amazonaws.com
+s3-accesspoint.dualstack.ap-southeast-1.amazonaws.com
+s3-website.dualstack.ap-southeast-1.amazonaws.com
+s3.ap-southeast-1.amazonaws.com
+s3-accesspoint.ap-southeast-1.amazonaws.com
+s3-object-lambda.ap-southeast-1.amazonaws.com
+s3-website.ap-southeast-1.amazonaws.com
+s3.dualstack.ap-southeast-2.amazonaws.com
+s3-accesspoint.dualstack.ap-southeast-2.amazonaws.com
+s3-website.dualstack.ap-southeast-2.amazonaws.com
+s3.ap-southeast-2.amazonaws.com
+s3-accesspoint.ap-southeast-2.amazonaws.com
+s3-object-lambda.ap-southeast-2.amazonaws.com
+s3-website.ap-southeast-2.amazonaws.com
+s3.dualstack.ap-southeast-3.amazonaws.com
+s3-accesspoint.dualstack.ap-southeast-3.amazonaws.com
+s3.ap-southeast-3.amazonaws.com
+s3-accesspoint.ap-southeast-3.amazonaws.com
+s3-object-lambda.ap-southeast-3.amazonaws.com
+s3-website.ap-southeast-3.amazonaws.com
+s3.dualstack.ap-southeast-4.amazonaws.com
+s3-accesspoint.dualstack.ap-southeast-4.amazonaws.com
+s3.ap-southeast-4.amazonaws.com
+s3-accesspoint.ap-southeast-4.amazonaws.com
+s3-object-lambda.ap-southeast-4.amazonaws.com
+s3-website.ap-southeast-4.amazonaws.com
+s3.dualstack.ca-central-1.amazonaws.com
+s3-accesspoint.dualstack.ca-central-1.amazonaws.com
+s3-accesspoint-fips.dualstack.ca-central-1.amazonaws.com
+s3-fips.dualstack.ca-central-1.amazonaws.com
+s3-website.dualstack.ca-central-1.amazonaws.com
+s3.ca-central-1.amazonaws.com
+s3-accesspoint.ca-central-1.amazonaws.com
+s3-accesspoint-fips.ca-central-1.amazonaws.com
+s3-fips.ca-central-1.amazonaws.com
+s3-object-lambda.ca-central-1.amazonaws.com
+s3-website.ca-central-1.amazonaws.com
+s3.dualstack.ca-west-1.amazonaws.com
+s3-accesspoint.dualstack.ca-west-1.amazonaws.com
+s3-accesspoint-fips.dualstack.ca-west-1.amazonaws.com
+s3-fips.dualstack.ca-west-1.amazonaws.com
+s3-website.dualstack.ca-west-1.amazonaws.com
+s3.ca-west-1.amazonaws.com
+s3-accesspoint.ca-west-1.amazonaws.com
+s3-accesspoint-fips.ca-west-1.amazonaws.com
+s3-fips.ca-west-1.amazonaws.com
+s3-website.ca-west-1.amazonaws.com
+s3.dualstack.eu-central-1.amazonaws.com
+s3-accesspoint.dualstack.eu-central-1.amazonaws.com
+s3-website.dualstack.eu-central-1.amazonaws.com
+s3.eu-central-1.amazonaws.com
+s3-accesspoint.eu-central-1.amazonaws.com
+s3-object-lambda.eu-central-1.amazonaws.com
+s3-website.eu-central-1.amazonaws.com
+s3.dualstack.eu-central-2.amazonaws.com
+s3-accesspoint.dualstack.eu-central-2.amazonaws.com
+s3.eu-central-2.amazonaws.com
+s3-accesspoint.eu-central-2.amazonaws.com
+s3-object-lambda.eu-central-2.amazonaws.com
+s3-website.eu-central-2.amazonaws.com
+s3.dualstack.eu-north-1.amazonaws.com
+s3-accesspoint.dualstack.eu-north-1.amazonaws.com
+s3.eu-north-1.amazonaws.com
+s3-accesspoint.eu-north-1.amazonaws.com
+s3-object-lambda.eu-north-1.amazonaws.com
+s3-website.eu-north-1.amazonaws.com
+s3.dualstack.eu-south-1.amazonaws.com
+s3-accesspoint.dualstack.eu-south-1.amazonaws.com
+s3-website.dualstack.eu-south-1.amazonaws.com
+s3.eu-south-1.amazonaws.com
+s3-accesspoint.eu-south-1.amazonaws.com
+s3-object-lambda.eu-south-1.amazonaws.com
+s3-website.eu-south-1.amazonaws.com
+s3.dualstack.eu-south-2.amazonaws.com
+s3-accesspoint.dualstack.eu-south-2.amazonaws.com
+s3.eu-south-2.amazonaws.com
+s3-accesspoint.eu-south-2.amazonaws.com
+s3-object-lambda.eu-south-2.amazonaws.com
+s3-website.eu-south-2.amazonaws.com
+s3.dualstack.eu-west-1.amazonaws.com
+s3-accesspoint.dualstack.eu-west-1.amazonaws.com
+s3-website.dualstack.eu-west-1.amazonaws.com
+s3.eu-west-1.amazonaws.com
+s3-accesspoint.eu-west-1.amazonaws.com
+s3-deprecated.eu-west-1.amazonaws.com
+s3-object-lambda.eu-west-1.amazonaws.com
+s3-website.eu-west-1.amazonaws.com
+s3.dualstack.eu-west-2.amazonaws.com
+s3-accesspoint.dualstack.eu-west-2.amazonaws.com
+s3.eu-west-2.amazonaws.com
+s3-accesspoint.eu-west-2.amazonaws.com
+s3-object-lambda.eu-west-2.amazonaws.com
+s3-website.eu-west-2.amazonaws.com
+s3.dualstack.eu-west-3.amazonaws.com
+s3-accesspoint.dualstack.eu-west-3.amazonaws.com
+s3-website.dualstack.eu-west-3.amazonaws.com
+s3.eu-west-3.amazonaws.com
+s3-accesspoint.eu-west-3.amazonaws.com
+s3-object-lambda.eu-west-3.amazonaws.com
+s3-website.eu-west-3.amazonaws.com
+s3.dualstack.il-central-1.amazonaws.com
+s3-accesspoint.dualstack.il-central-1.amazonaws.com
+s3.il-central-1.amazonaws.com
+s3-accesspoint.il-central-1.amazonaws.com
+s3-object-lambda.il-central-1.amazonaws.com
+s3-website.il-central-1.amazonaws.com
+s3.dualstack.me-central-1.amazonaws.com
+s3-accesspoint.dualstack.me-central-1.amazonaws.com
+s3.me-central-1.amazonaws.com
+s3-accesspoint.me-central-1.amazonaws.com
+s3-object-lambda.me-central-1.amazonaws.com
+s3-website.me-central-1.amazonaws.com
+s3.dualstack.me-south-1.amazonaws.com
+s3-accesspoint.dualstack.me-south-1.amazonaws.com
+s3.me-south-1.amazonaws.com
+s3-accesspoint.me-south-1.amazonaws.com
+s3-object-lambda.me-south-1.amazonaws.com
+s3-website.me-south-1.amazonaws.com
+s3.amazonaws.com
+s3-1.amazonaws.com
+s3-ap-east-1.amazonaws.com
+s3-ap-northeast-1.amazonaws.com
+s3-ap-northeast-2.amazonaws.com
+s3-ap-northeast-3.amazonaws.com
+s3-ap-south-1.amazonaws.com
+s3-ap-southeast-1.amazonaws.com
+s3-ap-southeast-2.amazonaws.com
+s3-ca-central-1.amazonaws.com
+s3-eu-central-1.amazonaws.com
+s3-eu-north-1.amazonaws.com
+s3-eu-west-1.amazonaws.com
+s3-eu-west-2.amazonaws.com
+s3-eu-west-3.amazonaws.com
+s3-external-1.amazonaws.com
+s3-fips-us-gov-east-1.amazonaws.com
+s3-fips-us-gov-west-1.amazonaws.com
+mrap.accesspoint.s3-global.amazonaws.com
+s3-me-south-1.amazonaws.com
+s3-sa-east-1.amazonaws.com
+s3-us-east-2.amazonaws.com
+s3-us-gov-east-1.amazonaws.com
+s3-us-gov-west-1.amazonaws.com
+s3-us-west-1.amazonaws.com
+s3-us-west-2.amazonaws.com
+s3-website-ap-northeast-1.amazonaws.com
+s3-website-ap-southeast-1.amazonaws.com
+s3-website-ap-southeast-2.amazonaws.com
+s3-website-eu-west-1.amazonaws.com
+s3-website-sa-east-1.amazonaws.com
+s3-website-us-east-1.amazonaws.com
+s3-website-us-gov-west-1.amazonaws.com
+s3-website-us-west-1.amazonaws.com
+s3-website-us-west-2.amazonaws.com
+s3.dualstack.sa-east-1.amazonaws.com
+s3-accesspoint.dualstack.sa-east-1.amazonaws.com
+s3-website.dualstack.sa-east-1.amazonaws.com
+s3.sa-east-1.amazonaws.com
+s3-accesspoint.sa-east-1.amazonaws.com
+s3-object-lambda.sa-east-1.amazonaws.com
+s3-website.sa-east-1.amazonaws.com
+s3.dualstack.us-east-1.amazonaws.com
+s3-accesspoint.dualstack.us-east-1.amazonaws.com
+s3-accesspoint-fips.dualstack.us-east-1.amazonaws.com
+s3-fips.dualstack.us-east-1.amazonaws.com
+s3-website.dualstack.us-east-1.amazonaws.com
+s3.us-east-1.amazonaws.com
+s3-accesspoint.us-east-1.amazonaws.com
+s3-accesspoint-fips.us-east-1.amazonaws.com
+s3-deprecated.us-east-1.amazonaws.com
+s3-fips.us-east-1.amazonaws.com
+s3-object-lambda.us-east-1.amazonaws.com
+s3-website.us-east-1.amazonaws.com
+s3.dualstack.us-east-2.amazonaws.com
+s3-accesspoint.dualstack.us-east-2.amazonaws.com
+s3-accesspoint-fips.dualstack.us-east-2.amazonaws.com
+s3-fips.dualstack.us-east-2.amazonaws.com
+s3.us-east-2.amazonaws.com
+s3-accesspoint.us-east-2.amazonaws.com
+s3-accesspoint-fips.us-east-2.amazonaws.com
+s3-deprecated.us-east-2.amazonaws.com
+s3-fips.us-east-2.amazonaws.com
+s3-object-lambda.us-east-2.amazonaws.com
+s3-website.us-east-2.amazonaws.com
+s3.dualstack.us-gov-east-1.amazonaws.com
+s3-accesspoint.dualstack.us-gov-east-1.amazonaws.com
+s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com
+s3-fips.dualstack.us-gov-east-1.amazonaws.com
+s3.us-gov-east-1.amazonaws.com
+s3-accesspoint.us-gov-east-1.amazonaws.com
+s3-accesspoint-fips.us-gov-east-1.amazonaws.com
+s3-fips.us-gov-east-1.amazonaws.com
+s3-object-lambda.us-gov-east-1.amazonaws.com
+s3-website.us-gov-east-1.amazonaws.com
+s3.dualstack.us-gov-west-1.amazonaws.com
+s3-accesspoint.dualstack.us-gov-west-1.amazonaws.com
+s3-accesspoint-fips.dualstack.us-gov-west-1.amazonaws.com
+s3-fips.dualstack.us-gov-west-1.amazonaws.com
+s3.us-gov-west-1.amazonaws.com
+s3-accesspoint.us-gov-west-1.amazonaws.com
+s3-accesspoint-fips.us-gov-west-1.amazonaws.com
+s3-fips.us-gov-west-1.amazonaws.com
+s3-object-lambda.us-gov-west-1.amazonaws.com
+s3-website.us-gov-west-1.amazonaws.com
+s3.dualstack.us-west-1.amazonaws.com
+s3-accesspoint.dualstack.us-west-1.amazonaws.com
+s3-accesspoint-fips.dualstack.us-west-1.amazonaws.com
+s3-fips.dualstack.us-west-1.amazonaws.com
+s3-website.dualstack.us-west-1.amazonaws.com
+s3.us-west-1.amazonaws.com
+s3-accesspoint.us-west-1.amazonaws.com
+s3-accesspoint-fips.us-west-1.amazonaws.com
+s3-fips.us-west-1.amazonaws.com
+s3-object-lambda.us-west-1.amazonaws.com
+s3-website.us-west-1.amazonaws.com
+s3.dualstack.us-west-2.amazonaws.com
+s3-accesspoint.dualstack.us-west-2.amazonaws.com
+s3-accesspoint-fips.dualstack.us-west-2.amazonaws.com
+s3-fips.dualstack.us-west-2.amazonaws.com
+s3-website.dualstack.us-west-2.amazonaws.com
+s3.us-west-2.amazonaws.com
+s3-accesspoint.us-west-2.amazonaws.com
+s3-accesspoint-fips.us-west-2.amazonaws.com
+s3-deprecated.us-west-2.amazonaws.com
+s3-fips.us-west-2.amazonaws.com
+s3-object-lambda.us-west-2.amazonaws.com
+s3-website.us-west-2.amazonaws.com
+
+// Amazon SageMaker Notebook Instances
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: ce8ae0b1-0070-496d-be88-37c31837af9d
+notebook.af-south-1.sagemaker.aws
+notebook.ap-east-1.sagemaker.aws
+notebook.ap-northeast-1.sagemaker.aws
+notebook.ap-northeast-2.sagemaker.aws
+notebook.ap-northeast-3.sagemaker.aws
+notebook.ap-south-1.sagemaker.aws
+notebook.ap-south-2.sagemaker.aws
+notebook.ap-southeast-1.sagemaker.aws
+notebook.ap-southeast-2.sagemaker.aws
+notebook.ap-southeast-3.sagemaker.aws
+notebook.ap-southeast-4.sagemaker.aws
+notebook.ca-central-1.sagemaker.aws
+notebook-fips.ca-central-1.sagemaker.aws
+notebook.ca-west-1.sagemaker.aws
+notebook-fips.ca-west-1.sagemaker.aws
+notebook.eu-central-1.sagemaker.aws
+notebook.eu-central-2.sagemaker.aws
+notebook.eu-north-1.sagemaker.aws
+notebook.eu-south-1.sagemaker.aws
+notebook.eu-south-2.sagemaker.aws
+notebook.eu-west-1.sagemaker.aws
+notebook.eu-west-2.sagemaker.aws
+notebook.eu-west-3.sagemaker.aws
+notebook.il-central-1.sagemaker.aws
+notebook.me-central-1.sagemaker.aws
+notebook.me-south-1.sagemaker.aws
+notebook.sa-east-1.sagemaker.aws
+notebook.us-east-1.sagemaker.aws
+notebook-fips.us-east-1.sagemaker.aws
+notebook.us-east-2.sagemaker.aws
+notebook-fips.us-east-2.sagemaker.aws
+notebook.us-gov-east-1.sagemaker.aws
+notebook-fips.us-gov-east-1.sagemaker.aws
+notebook.us-gov-west-1.sagemaker.aws
+notebook-fips.us-gov-west-1.sagemaker.aws
+notebook.us-west-1.sagemaker.aws
+notebook.us-west-2.sagemaker.aws
+notebook-fips.us-west-2.sagemaker.aws
+notebook.cn-north-1.sagemaker.com.cn
+notebook.cn-northwest-1.sagemaker.com.cn
+
+// Amazon SageMaker Studio
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 057ee397-6bf8-4f20-b807-d7bc145ac980
+studio.af-south-1.sagemaker.aws
+studio.ap-east-1.sagemaker.aws
+studio.ap-northeast-1.sagemaker.aws
+studio.ap-northeast-2.sagemaker.aws
+studio.ap-northeast-3.sagemaker.aws
+studio.ap-south-1.sagemaker.aws
+studio.ap-southeast-1.sagemaker.aws
+studio.ap-southeast-2.sagemaker.aws
+studio.ap-southeast-3.sagemaker.aws
+studio.ca-central-1.sagemaker.aws
+studio.eu-central-1.sagemaker.aws
+studio.eu-north-1.sagemaker.aws
+studio.eu-south-1.sagemaker.aws
+studio.eu-west-1.sagemaker.aws
+studio.eu-west-2.sagemaker.aws
+studio.eu-west-3.sagemaker.aws
+studio.il-central-1.sagemaker.aws
+studio.me-central-1.sagemaker.aws
+studio.me-south-1.sagemaker.aws
+studio.sa-east-1.sagemaker.aws
+studio.us-east-1.sagemaker.aws
+studio.us-east-2.sagemaker.aws
+studio.us-gov-east-1.sagemaker.aws
+studio-fips.us-gov-east-1.sagemaker.aws
+studio.us-gov-west-1.sagemaker.aws
+studio-fips.us-gov-west-1.sagemaker.aws
+studio.us-west-1.sagemaker.aws
+studio.us-west-2.sagemaker.aws
+studio.cn-north-1.sagemaker.com.cn
+studio.cn-northwest-1.sagemaker.com.cn
+
+// Analytics on AWS
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 955f9f40-a495-4e73-ae85-67b77ac9cadd
+analytics-gateway.ap-northeast-1.amazonaws.com
+analytics-gateway.ap-northeast-2.amazonaws.com
+analytics-gateway.ap-south-1.amazonaws.com
+analytics-gateway.ap-southeast-1.amazonaws.com
+analytics-gateway.ap-southeast-2.amazonaws.com
+analytics-gateway.eu-central-1.amazonaws.com
+analytics-gateway.eu-west-1.amazonaws.com
+analytics-gateway.us-east-1.amazonaws.com
+analytics-gateway.us-east-2.amazonaws.com
+analytics-gateway.us-west-2.amazonaws.com
+
+// AWS Amplify
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 5ecce854-c033-4fc4-a755-1a9916d9a9bb
+*.amplifyapp.com
+
+// AWS App Runner
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 6828c008-ba5d-442f-ade5-48da4e7c2316
+*.awsapprunner.com
+
+// AWS Cloud9
+// Submitted by: AWS Security <psl-maintainers@amazon.com>
+// Reference: 30717f72-4007-4f0f-8ed4-864c6f2efec9
+webview-assets.aws-cloud9.af-south-1.amazonaws.com
+vfs.cloud9.af-south-1.amazonaws.com
+webview-assets.cloud9.af-south-1.amazonaws.com
+webview-assets.aws-cloud9.ap-east-1.amazonaws.com
+vfs.cloud9.ap-east-1.amazonaws.com
+webview-assets.cloud9.ap-east-1.amazonaws.com
+webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com
+vfs.cloud9.ap-northeast-1.amazonaws.com
+webview-assets.cloud9.ap-northeast-1.amazonaws.com
+webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com
+vfs.cloud9.ap-northeast-2.amazonaws.com
+webview-assets.cloud9.ap-northeast-2.amazonaws.com
+webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com
+vfs.cloud9.ap-northeast-3.amazonaws.com
+webview-assets.cloud9.ap-northeast-3.amazonaws.com
+webview-assets.aws-cloud9.ap-south-1.amazonaws.com
+vfs.cloud9.ap-south-1.amazonaws.com
+webview-assets.cloud9.ap-south-1.amazonaws.com
+webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com
+vfs.cloud9.ap-southeast-1.amazonaws.com
+webview-assets.cloud9.ap-southeast-1.amazonaws.com
+webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com
+vfs.cloud9.ap-southeast-2.amazonaws.com
+webview-assets.cloud9.ap-southeast-2.amazonaws.com
+webview-assets.aws-cloud9.ca-central-1.amazonaws.com
+vfs.cloud9.ca-central-1.amazonaws.com
+webview-assets.cloud9.ca-central-1.amazonaws.com
+webview-assets.aws-cloud9.eu-central-1.amazonaws.com
+vfs.cloud9.eu-central-1.amazonaws.com
+webview-assets.cloud9.eu-central-1.amazonaws.com
+webview-assets.aws-cloud9.eu-north-1.amazonaws.com
+vfs.cloud9.eu-north-1.amazonaws.com
+webview-assets.cloud9.eu-north-1.amazonaws.com
+webview-assets.aws-cloud9.eu-south-1.amazonaws.com
+vfs.cloud9.eu-south-1.amazonaws.com
+webview-assets.cloud9.eu-south-1.amazonaws.com
+webview-assets.aws-cloud9.eu-west-1.amazonaws.com
+vfs.cloud9.eu-west-1.amazonaws.com
+webview-assets.cloud9.eu-west-1.amazonaws.com
+webview-assets.aws-cloud9.eu-west-2.amazonaws.com
+vfs.cloud9.eu-west-2.amazonaws.com
+webview-assets.cloud9.eu-west-2.amazonaws.com
+webview-assets.aws-cloud9.eu-west-3.amazonaws.com
+vfs.cloud9.eu-west-3.amazonaws.com
+webview-assets.cloud9.eu-west-3.amazonaws.com
+webview-assets.aws-cloud9.il-central-1.amazonaws.com
+vfs.cloud9.il-central-1.amazonaws.com
+webview-assets.aws-cloud9.me-south-1.amazonaws.com
+vfs.cloud9.me-south-1.amazonaws.com
+webview-assets.cloud9.me-south-1.amazonaws.com
+webview-assets.aws-cloud9.sa-east-1.amazonaws.com
+vfs.cloud9.sa-east-1.amazonaws.com
+webview-assets.cloud9.sa-east-1.amazonaws.com
+webview-assets.aws-cloud9.us-east-1.amazonaws.com
+vfs.cloud9.us-east-1.amazonaws.com
+webview-assets.cloud9.us-east-1.amazonaws.com
+webview-assets.aws-cloud9.us-east-2.amazonaws.com
+vfs.cloud9.us-east-2.amazonaws.com
+webview-assets.cloud9.us-east-2.amazonaws.com
+webview-assets.aws-cloud9.us-west-1.amazonaws.com
+vfs.cloud9.us-west-1.amazonaws.com
+webview-assets.cloud9.us-west-1.amazonaws.com
+webview-assets.aws-cloud9.us-west-2.amazonaws.com
+vfs.cloud9.us-west-2.amazonaws.com
+webview-assets.cloud9.us-west-2.amazonaws.com
+
+// AWS Elastic Beanstalk
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: bb5a965c-dec3-4967-aa22-e306ad064797
+cn-north-1.eb.amazonaws.com.cn
+cn-northwest-1.eb.amazonaws.com.cn
+elasticbeanstalk.com
+af-south-1.elasticbeanstalk.com
+ap-east-1.elasticbeanstalk.com
+ap-northeast-1.elasticbeanstalk.com
+ap-northeast-2.elasticbeanstalk.com
+ap-northeast-3.elasticbeanstalk.com
+ap-south-1.elasticbeanstalk.com
+ap-southeast-1.elasticbeanstalk.com
+ap-southeast-2.elasticbeanstalk.com
+ap-southeast-3.elasticbeanstalk.com
+ca-central-1.elasticbeanstalk.com
+eu-central-1.elasticbeanstalk.com
+eu-north-1.elasticbeanstalk.com
+eu-south-1.elasticbeanstalk.com
+eu-west-1.elasticbeanstalk.com
+eu-west-2.elasticbeanstalk.com
+eu-west-3.elasticbeanstalk.com
+il-central-1.elasticbeanstalk.com
+me-south-1.elasticbeanstalk.com
+sa-east-1.elasticbeanstalk.com
+us-east-1.elasticbeanstalk.com
+us-east-2.elasticbeanstalk.com
+us-gov-east-1.elasticbeanstalk.com
+us-gov-west-1.elasticbeanstalk.com
+us-west-1.elasticbeanstalk.com
+us-west-2.elasticbeanstalk.com
+
+// (AWS) Elastic Load Balancing
+// Submitted by Luke Wells <psl-maintainers@amazon.com>
+// Reference: 12a3d528-1bac-4433-a359-a395867ffed2
+*.elb.amazonaws.com.cn
+*.elb.amazonaws.com
+
+// AWS Global Accelerator
+// Submitted by Daniel Massaguer <psl-maintainers@amazon.com>
+// Reference: d916759d-a08b-4241-b536-4db887383a6a
+awsglobalaccelerator.com
+
+// AWS re:Post Private
+// Submitted by AWS Security <psl-maintainers@amazon.com>
+// Reference: 83385945-225f-416e-9aa0-ad0632bfdcee
+*.private.repost.aws
+
+// eero
+// Submitted by Yue Kang <eero-dynamic-dns@amazon.com>
+// Reference: 264afe70-f62c-4c02-8ab9-b5281ed24461
+eero.online
+eero-stage.online
+
+// concludes Amazon
+
+// Amune : https://amune.org/
+// Submitted by Team Amune <cert@amune.org>
+t3l3p0rt.net
+tele.amune.org
+
+// Apigee : https://apigee.com/
+// Submitted by Apigee Security Team <security@apigee.com>
+apigee.io
+
+// Apis Networks: https://apisnetworks.com
+// Submitted by Matt Saladna <matt@apisnetworks.com>
+panel.dev
+
+// Apphud : https://apphud.com
+// Submitted by Alexander Selivanov <alex@apphud.com>
+siiites.com
+
+// Appspace : https://www.appspace.com
+// Submitted by Appspace Security Team <security@appspace.com>
+appspacehosted.com
+appspaceusercontent.com
+
+// Appudo UG (haftungsbeschränkt) : https://www.appudo.com
+// Submitted by Alexander Hochbaum <admin@appudo.com>
+appudo.net
+
+// Aptible : https://www.aptible.com/
+// Submitted by Thomas Orozco <thomas@aptible.com>
+on-aptible.com
+
+// Aquapal : https://aquapal.net/
+// Submitted by Aki Ueno <admin@aquapal.net>
+f5.si
+
+// ASEINet : https://www.aseinet.com/
+// Submitted by Asei SEKIGUCHI <mail@aseinet.com>
+user.aseinet.ne.jp
+gv.vc
+d.gv.vc
+
+// Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/
+// Submitted by Hector Martin <marcan@euskalencounter.org>
+user.party.eus
+
+// Association potager.org : https://potager.org/
+// Submitted by Lunar <jardiniers@potager.org>
+pimienta.org
+poivron.org
+potager.org
+sweetpepper.org
+
+// ASUSTOR Inc. : http://www.asustor.com
+// Submitted by Vincent Tseng <vincenttseng@asustor.com>
+myasustor.com
+
+// Atlassian : https://atlassian.com
+// Submitted by Sam Smyth <devloop@atlassian.com>
+cdn.prod.atlassian-dev.net
+
+// Authentick UG (haftungsbeschränkt) : https://authentick.net
+// Submitted by Lukas Reschke <lukas@authentick.net>
+translated.page
+
+// Autocode : https://autocode.com
+// Submitted by Jacob Lee <jacob@autocode.com>
+autocode.dev
+
+// AVM : https://avm.de
+// Submitted by Andreas Weise <a.weise@avm.de>
+myfritz.net
+
+// AVStack Pte. Ltd. : https://avstack.io
+// Submitted by Jasper Hugo <jasper@avstack.io>
+onavstack.net
+
+// AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com
+// Submitted by James Kennedy <domains@advisorwebsites.com>
+*.awdev.ca
+*.advisor.ws
+
+// AZ.pl sp. z.o.o: https://az.pl
+// Submitted by Krzysztof Wolski <krzysztof.wolski@home.eu>
+ecommerce-shop.pl
+
+// b-data GmbH : https://www.b-data.io
+// Submitted by Olivier Benz <olivier.benz@b-data.ch>
+b-data.io
+
+// backplane : https://www.backplane.io
+// Submitted by Anthony Voutas <anthony@backplane.io>
+backplaneapp.io
+
+// Balena : https://www.balena.io
+// Submitted by Petros Angelatos <petrosagg@balena.io>
+balena-devices.com
+
+// University of Banja Luka : https://unibl.org
+// Domains for Republic of Srpska administrative entity.
+// Submitted by Marko Ivanovic <kormang@hotmail.rs>
+rs.ba
+
+// Banzai Cloud
+// Submitted by Janos Matyas <info@banzaicloud.com>
+*.banzai.cloud
+app.banzaicloud.io
+*.backyards.banzaicloud.io
+
+// BASE, Inc. : https://binc.jp
+// Submitted by Yuya NAGASAWA <public-suffix-list@binc.jp>
+base.ec
+official.ec
+buyshop.jp
+fashionstore.jp
+handcrafted.jp
+kawaiishop.jp
+supersale.jp
+theshop.jp
+shopselect.net
+base.shop
+
+// BeagleBoard.org Foundation : https://beagleboard.org
+// Submitted by Jason Kridner <jkridner@beagleboard.org>
+beagleboard.io
+
+// Beget Ltd
+// Submitted by Lev Nekrasov <lnekrasov@beget.com>
+*.beget.app
+
+// Besties : https://besties.house
+// Submitted by Hazel Cora <hazy@besties.house>
+pages.gay
+
+// BetaInABox
+// Submitted by Adrian <adrian@betainabox.com>
+betainabox.com
+
+// BinaryLane : http://www.binarylane.com
+// Submitted by Nathan O'Sullivan <nathan@mammoth.com.au>
+bnr.la
+
+// Bitbucket : http://bitbucket.org
+// Submitted by Andy Ortlieb <aortlieb@atlassian.com>
+bitbucket.io
+
+// Blackbaud, Inc. : https://www.blackbaud.com
+// Submitted by Paul Crowder <paul.crowder@blackbaud.com>
+blackbaudcdn.net
+
+// Blatech : http://www.blatech.net
+// Submitted by Luke Bratch <luke@bratch.co.uk>
+of.je
+
+// Blue Bite, LLC : https://bluebite.com
+// Submitted by Joshua Weiss <admin.engineering@bluebite.com>
+bluebite.io
+
+// Boomla : https://boomla.com
+// Submitted by Tibor Halter <thalter@boomla.com>
+boomla.net
+
+// Boutir : https://www.boutir.com
+// Submitted by Eric Ng Ka Ka <ngkaka@boutir.com>
+boutir.com
+
+// Boxfuse : https://boxfuse.com
+// Submitted by Axel Fontaine <axel@boxfuse.com>
+boxfuse.io
+
+// bplaced : https://www.bplaced.net/
+// Submitted by Miroslav Bozic <security@bplaced.net>
+square7.ch
+bplaced.com
+bplaced.de
+square7.de
+bplaced.net
+square7.net
+
+// Brave : https://brave.com
+// Submitted by Andrea Brancaleoni <abrancaleoni@brave.com>
+*.s.brave.io
+
+// Brendly : https://brendly.rs
+// Submitted by Dusan Radovanovic <dusan.radovanovic@brendly.rs>
+shop.brendly.rs
+
+// BrowserSafetyMark
+// Submitted by Dave Tharp <browsersafetymark.io@quicinc.com>
+browsersafetymark.io
+
+// Bytemark Hosting : https://www.bytemark.co.uk
+// Submitted by Paul Cammish <paul.cammish@bytemark.co.uk>
+uk0.bigv.io
+dh.bytemark.co.uk
+vm.bytemark.co.uk
+
+// Caf.js Labs LLC : https://www.cafjs.com
+// Submitted by Antonio Lain <antlai@cafjs.com>
+cafjs.com
+
+// callidomus : https://www.callidomus.com/
+// Submitted by Marcus Popp <admin@callidomus.com>
+mycd.eu
+
+// Canva Pty Ltd : https://canva.com/
+// Submitted by Joel Aquilina <publicsuffixlist@canva.com>
+canva-apps.cn
+*.my.canvasite.cn
+canva-apps.com
+*.my.canva.site
+
+// Carrd : https://carrd.co
+// Submitted by AJ <aj@carrd.co>
+drr.ac
+uwu.ai
+carrd.co
+crd.co
+ju.mp
+
+// CentralNic : http://www.centralnic.com/names/domains
+// Submitted by registry <gavin.brown@centralnic.com>
+ae.org
+br.com
+cn.com
+com.de
+com.se
+de.com
+eu.com
+gb.net
+hu.net
+jp.net
+jpn.com
+mex.com
+ru.com
+sa.com
+se.net
+uk.com
+uk.net
+us.com
+za.bz
+za.com
+
+// No longer operated by CentralNic, these entries should be adopted and/or removed by current operators
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+ar.com
+hu.com
+kr.com
+no.com
+qc.com
+uy.com
+
+// Africa.com Web Solutions Ltd : https://registry.africa.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+africa.com
+
+// iDOT Services Limited : http://www.domain.gr.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+gr.com
+
+// Radix FZC : http://domains.in.net
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+in.net
+web.in
+
+// US REGISTRY LLC : http://us.org
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+us.org
+
+// co.com Registry, LLC : https://registry.co.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+co.com
+
+// Roar Domains LLC : https://roar.basketball/
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+aus.basketball
+nz.basketball
+
+// BRS Media : https://brsmedia.com/
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+radio.am
+radio.fm
+
+// c.la : http://www.c.la/
+c.la
+
+// certmgr.org : https://certmgr.org
+// Submitted by B. Blechschmidt <hostmaster@certmgr.org>
+certmgr.org
+
+// Cityhost LLC : https://cityhost.ua
+// Submitted by Maksym Rivtin <support@cityhost.net.ua>
+cx.ua
+
+// Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/
+// Submitted by Rishabh Nambiar & Michael Brown <team@discourse.org>
+discourse.group
+discourse.team
+
+// Clever Cloud : https://www.clever-cloud.com/
+// Submitted by Quentin Adam <noc@clever-cloud.com>
+cleverapps.io
+
+// Clerk : https://www.clerk.dev
+// Submitted by Colin Sidoti <systems@clerk.dev>
+clerk.app
+clerkstage.app
+*.lcl.dev
+*.lclstage.dev
+*.stg.dev
+*.stgstage.dev
+
+// ClickRising : https://clickrising.com/
+// Submitted by Umut Gumeli <infrastructure-publicsuffixlist@clickrising.com>
+clickrising.net
+
+// Cloud66 : https://www.cloud66.com/
+// Submitted by Khash Sajadi <khash@cloud66.com>
+c66.me
+cloud66.ws
+cloud66.zone
+
+// CloudAccess.net : https://www.cloudaccess.net/
+// Submitted by Pawel Panek <noc@cloudaccess.net>
+jdevcloud.com
+wpdevcloud.com
+cloudaccess.host
+freesite.host
+cloudaccess.net
+
+// cloudControl : https://www.cloudcontrol.com/
+// Submitted by Tobias Wilken <tw@cloudcontrol.com>
+cloudcontrolled.com
+cloudcontrolapp.com
+
+// Cloudera, Inc. : https://www.cloudera.com/
+// Submitted by Kedarnath Waikar <security@cloudera.com>
+*.cloudera.site
+
+// Cloudflare, Inc. : https://www.cloudflare.com/
+// Submitted by Cloudflare Team <publicsuffixlist@cloudflare.com>
+cf-ipfs.com
+cloudflare-ipfs.com
+trycloudflare.com
+pages.dev
+r2.dev
+workers.dev
+
+// Clovyr : https://clovyr.io
+// Submitted by Patrick Nielsen <patrick@clovyr.io>
+wnext.app
+
+// co.ca : http://registry.co.ca/
+co.ca
+
+// Co & Co : https://co-co.nl/
+// Submitted by Govert Versluis <govert@co-co.nl>
+*.otap.co
+
+// i-registry s.r.o. : http://www.i-registry.cz/
+// Submitted by Martin Semrad <semrad@i-registry.cz>
+co.cz
+
+// CDN77.com : http://www.cdn77.com
+// Submitted by Jan Krpes <jan.krpes@cdn77.com>
+c.cdn77.org
+cdn77-ssl.net
+r.cdn77.net
+rsc.cdn77.org
+ssl.origin.cdn77-secure.org
+
+// Cloud DNS Ltd : http://www.cloudns.net
+// Submitted by Aleksander Hristov <noc@cloudns.net>
+cloudns.asia
+cloudns.biz
+cloudns.club
+cloudns.cc
+cloudns.eu
+cloudns.in
+cloudns.info
+cloudns.org
+cloudns.pro
+cloudns.pw
+cloudns.us
+
+// CNPY : https://cnpy.gdn
+// Submitted by Angelo Gladding <angelo@lahacker.net>
+cnpy.gdn
+
+// Codeberg e. V. : https://codeberg.org
+// Submitted by Moritz Marquardt <git@momar.de>
+codeberg.page
+
+// CoDNS B.V.
+co.nl
+co.no
+
+// Combell.com : https://www.combell.com
+// Submitted by Thomas Wouters <thomas.wouters@combellgroup.com>
+webhosting.be
+hosting-cluster.nl
+
+// Convex : https://convex.dev/
+// Submitted by James Cowling <security@convex.dev>
+convex.site
+
+// Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/
+// Submitted by George Georgievsky <gug@cctld.ru>
+ac.ru
+edu.ru
+gov.ru
+int.ru
+mil.ru
+test.ru
+
+// COSIMO GmbH : http://www.cosimo.de
+// Submitted by Rene Marticke <rmarticke@cosimo.de>
+dyn.cosidns.de
+dynamisches-dns.de
+dnsupdater.de
+internet-dns.de
+l-o-g-i-n.de
+dynamic-dns.info
+feste-ip.net
+knx-server.net
+static-access.net
+
+// cPanel L.L.C. : https://www.cpanel.net/
+// Submitted by Dustin Scherer <public.suffix@cpanel.net>
+*.cprapid.com
+
+// Craynic, s.r.o. : http://www.craynic.com/
+// Submitted by Ales Krajnik <ales.krajnik@craynic.com>
+realm.cz
+
+// Crisp IM SAS : https://crisp.chat/
+// Submitted by Baptiste Jamin <hostmaster@crisp.chat>
+on.crisp.email
+
+// Cryptonomic : https://cryptonomic.net/
+// Submitted by Andrew Cady <public-suffix-list@cryptonomic.net>
+*.cryptonomic.net
+
+// Cupcake : https://cupcake.io/
+// Submitted by Jonathan Rudenberg <jonathan@cupcake.io>
+cupcake.is
+
+// Curv UG : https://curv-labs.de/
+// Submitted by Marvin Wiesner <Marvin@curv-labs.de>
+curv.dev
+
+// Customer OCI - Oracle Dyn https://cloud.oracle.com/home https://dyn.com/dns/
+// Submitted by Gregory Drake <support@dyn.com>
+// Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label
+*.customer-oci.com
+*.oci.customer-oci.com
+*.ocp.customer-oci.com
+*.ocs.customer-oci.com
+
+// Cyclic Software : https://www.cyclic.sh
+// Submitted by Kam Lasater <dns-admin@cyclic.sh>
+cyclic.app
+cyclic.cloud
+cyclic-app.com
+cyclic.co.in
+
+// cyon GmbH : https://www.cyon.ch/
+// Submitted by Dominic Luechinger <dol@cyon.ch>
+cyon.link
+cyon.site
+
+// Danger Science Group: https://dangerscience.com/
+// Submitted by Skylar MacDonald <skylar@dangerscience.com>
+fnwk.site
+folionetwork.site
+platform0.app
+
+// Daplie, Inc : https://daplie.com
+// Submitted by AJ ONeal <aj@daplie.com>
+daplie.me
+localhost.daplie.me
+
+// Datto, Inc. : https://www.datto.com/
+// Submitted by Philipp Heckel <ph@datto.com>
+dattolocal.com
+dattorelay.com
+dattoweb.com
+mydatto.com
+dattolocal.net
+mydatto.net
+
+// Dansk.net : http://www.dansk.net/
+// Submitted by Anani Voule <digital@digital.co.dk>
+biz.dk
+co.dk
+firm.dk
+reg.dk
+store.dk
+
+// dappnode.io : https://dappnode.io/
+// Submitted by Abel Boldu / DAppNode Team <community@dappnode.io>
+dyndns.dappnode.io
+
+// dapps.earth : https://dapps.earth/
+// Submitted by Daniil Burdakov <icqkill@gmail.com>
+*.dapps.earth
+*.bzz.dapps.earth
+
+// Dark, Inc. : https://darklang.com
+// Submitted by Paul Biggar <ops@darklang.com>
+builtwithdark.com
+
+// DataDetect, LLC. : https://datadetect.com
+// Submitted by Andrew Banchich <abanchich@sceven.com>
+demo.datadetect.com
+instance.datadetect.com
+
+// Datawire, Inc : https://www.datawire.io
+// Submitted by Richard Li <secalert@datawire.io>
+edgestack.me
+
+// DDNS5 : https://ddns5.com
+// Submitted by Cameron Elliott <cameron@cameronelliott.com>
+ddns5.com
+
+// Debian : https://www.debian.org/
+// Submitted by Peter Palfrader / Debian Sysadmin Team <dsa-publicsuffixlist@debian.org>
+debian.net
+
+// Deno Land Inc : https://deno.com/
+// Submitted by Luca Casonato <hostmaster@deno.com>
+deno.dev
+deno-staging.dev
+
+// deSEC : https://desec.io/
+// Submitted by Peter Thomassen <peter@desec.io>
+dedyn.io
+
+// Deta: https://www.deta.sh/
+// Submitted by Aavash Shrestha <aavash@deta.sh>
+deta.app
+deta.dev
+
+// Diher Solutions : https://diher.solutions
+// Submitted by Didi Hermawan <mail@diher.solutions>
+*.rss.my.id
+*.diher.solutions
+
+// Discord Inc : https://discord.com
+// Submitted by Sahn Lam <slam@discordapp.com>
+discordsays.com
+discordsez.com
+
+// DNS Africa Ltd https://dns.business
+// Submitted by Calvin Browne <calvin@dns.business>
+jozi.biz
+
+// DNShome : https://www.dnshome.de/
+// Submitted by Norbert Auler <mail@dnshome.de>
+dnshome.de
+
+// DotArai : https://www.dotarai.com/
+// Submitted by Atsadawat Netcharadsang <atsadawat@dotarai.co.th>
+online.th
+shop.th
+
+// DrayTek Corp. : https://www.draytek.com/
+// Submitted by Paul Fang <mis@draytek.com>
+drayddns.com
+
+// DreamCommerce : https://shoper.pl/
+// Submitted by Konrad Kotarba <konrad.kotarba@dreamcommerce.com>
+shoparena.pl
+
+// DreamHost : http://www.dreamhost.com/
+// Submitted by Andrew Farmer <andrew.farmer@dreamhost.com>
+dreamhosters.com
+
+// Drobo : http://www.drobo.com/
+// Submitted by Ricardo Padilha <rpadilha@drobo.com>
+mydrobo.com
+
+// Drud Holdings, LLC. : https://www.drud.com/
+// Submitted by Kevin Bridges <kevin@drud.com>
+drud.io
+drud.us
+
+// DuckDNS : http://www.duckdns.org/
+// Submitted by Richard Harper <richard@duckdns.org>
+duckdns.org
+
+// Bip : https://bip.sh
+// Submitted by Joel Kennedy <joel@bip.sh>
+bip.sh
+
+// bitbridge.net : Submitted by Craig Welch, abeliidev@gmail.com
+bitbridge.net
+
+// dy.fi : http://dy.fi/
+// Submitted by Heikki Hannikainen <hessu@hes.iki.fi>
+dy.fi
+tunk.org
+
+// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/
+dyndns-at-home.com
+dyndns-at-work.com
+dyndns-blog.com
+dyndns-free.com
+dyndns-home.com
+dyndns-ip.com
+dyndns-mail.com
+dyndns-office.com
+dyndns-pics.com
+dyndns-remote.com
+dyndns-server.com
+dyndns-web.com
+dyndns-wiki.com
+dyndns-work.com
+dyndns.biz
+dyndns.info
+dyndns.org
+dyndns.tv
+at-band-camp.net
+ath.cx
+barrel-of-knowledge.info
+barrell-of-knowledge.info
+better-than.tv
+blogdns.com
+blogdns.net
+blogdns.org
+blogsite.org
+boldlygoingnowhere.org
+broke-it.net
+buyshouses.net
+cechire.com
+dnsalias.com
+dnsalias.net
+dnsalias.org
+dnsdojo.com
+dnsdojo.net
+dnsdojo.org
+does-it.net
+doesntexist.com
+doesntexist.org
+dontexist.com
+dontexist.net
+dontexist.org
+doomdns.com
+doomdns.org
+dvrdns.org
+dyn-o-saur.com
+dynalias.com
+dynalias.net
+dynalias.org
+dynathome.net
+dyndns.ws
+endofinternet.net
+endofinternet.org
+endoftheinternet.org
+est-a-la-maison.com
+est-a-la-masion.com
+est-le-patron.com
+est-mon-blogueur.com
+for-better.biz
+for-more.biz
+for-our.info
+for-some.biz
+for-the.biz
+forgot.her.name
+forgot.his.name
+from-ak.com
+from-al.com
+from-ar.com
+from-az.net
+from-ca.com
+from-co.net
+from-ct.com
+from-dc.com
+from-de.com
+from-fl.com
+from-ga.com
+from-hi.com
+from-ia.com
+from-id.com
+from-il.com
+from-in.com
+from-ks.com
+from-ky.com
+from-la.net
+from-ma.com
+from-md.com
+from-me.org
+from-mi.com
+from-mn.com
+from-mo.com
+from-ms.com
+from-mt.com
+from-nc.com
+from-nd.com
+from-ne.com
+from-nh.com
+from-nj.com
+from-nm.com
+from-nv.com
+from-ny.net
+from-oh.com
+from-ok.com
+from-or.com
+from-pa.com
+from-pr.com
+from-ri.com
+from-sc.com
+from-sd.com
+from-tn.com
+from-tx.com
+from-ut.com
+from-va.com
+from-vt.com
+from-wa.com
+from-wi.com
+from-wv.com
+from-wy.com
+ftpaccess.cc
+fuettertdasnetz.de
+game-host.org
+game-server.cc
+getmyip.com
+gets-it.net
+go.dyndns.org
+gotdns.com
+gotdns.org
+groks-the.info
+groks-this.info
+ham-radio-op.net
+here-for-more.info
+hobby-site.com
+hobby-site.org
+home.dyndns.org
+homedns.org
+homeftp.net
+homeftp.org
+homeip.net
+homelinux.com
+homelinux.net
+homelinux.org
+homeunix.com
+homeunix.net
+homeunix.org
+iamallama.com
+in-the-band.net
+is-a-anarchist.com
+is-a-blogger.com
+is-a-bookkeeper.com
+is-a-bruinsfan.org
+is-a-bulls-fan.com
+is-a-candidate.org
+is-a-caterer.com
+is-a-celticsfan.org
+is-a-chef.com
+is-a-chef.net
+is-a-chef.org
+is-a-conservative.com
+is-a-cpa.com
+is-a-cubicle-slave.com
+is-a-democrat.com
+is-a-designer.com
+is-a-doctor.com
+is-a-financialadvisor.com
+is-a-geek.com
+is-a-geek.net
+is-a-geek.org
+is-a-green.com
+is-a-guru.com
+is-a-hard-worker.com
+is-a-hunter.com
+is-a-knight.org
+is-a-landscaper.com
+is-a-lawyer.com
+is-a-liberal.com
+is-a-libertarian.com
+is-a-linux-user.org
+is-a-llama.com
+is-a-musician.com
+is-a-nascarfan.com
+is-a-nurse.com
+is-a-painter.com
+is-a-patsfan.org
+is-a-personaltrainer.com
+is-a-photographer.com
+is-a-player.com
+is-a-republican.com
+is-a-rockstar.com
+is-a-socialist.com
+is-a-soxfan.org
+is-a-student.com
+is-a-teacher.com
+is-a-techie.com
+is-a-therapist.com
+is-an-accountant.com
+is-an-actor.com
+is-an-actress.com
+is-an-anarchist.com
+is-an-artist.com
+is-an-engineer.com
+is-an-entertainer.com
+is-by.us
+is-certified.com
+is-found.org
+is-gone.com
+is-into-anime.com
+is-into-cars.com
+is-into-cartoons.com
+is-into-games.com
+is-leet.com
+is-lost.org
+is-not-certified.com
+is-saved.org
+is-slick.com
+is-uberleet.com
+is-very-bad.org
+is-very-evil.org
+is-very-good.org
+is-very-nice.org
+is-very-sweet.org
+is-with-theband.com
+isa-geek.com
+isa-geek.net
+isa-geek.org
+isa-hockeynut.com
+issmarterthanyou.com
+isteingeek.de
+istmein.de
+kicks-ass.net
+kicks-ass.org
+knowsitall.info
+land-4-sale.us
+lebtimnetz.de
+leitungsen.de
+likes-pie.com
+likescandy.com
+merseine.nu
+mine.nu
+misconfused.org
+mypets.ws
+myphotos.cc
+neat-url.com
+office-on-the.net
+on-the-web.tv
+podzone.net
+podzone.org
+readmyblog.org
+saves-the-whales.com
+scrapper-site.net
+scrapping.cc
+selfip.biz
+selfip.com
+selfip.info
+selfip.net
+selfip.org
+sells-for-less.com
+sells-for-u.com
+sells-it.net
+sellsyourhome.org
+servebbs.com
+servebbs.net
+servebbs.org
+serveftp.net
+serveftp.org
+servegame.org
+shacknet.nu
+simple-url.com
+space-to-rent.com
+stuff-4-sale.org
+stuff-4-sale.us
+teaches-yoga.com
+thruhere.net
+traeumtgerade.de
+webhop.biz
+webhop.info
+webhop.net
+webhop.org
+worse-than.tv
+writesthisblog.com
+
+// ddnss.de : https://www.ddnss.de/
+// Submitted by Robert Niedziela <webmaster@ddnss.de>
+ddnss.de
+dyn.ddnss.de
+dyndns.ddnss.de
+dyndns1.de
+dyn-ip24.de
+home-webserver.de
+dyn.home-webserver.de
+myhome-server.de
+ddnss.org
+
+// Definima : http://www.definima.com/
+// Submitted by Maxence Bitterli <maxence@definima.com>
+definima.net
+definima.io
+
+// DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/
+// Submitted by Braxton Huggins <psl-maintainers@digitalocean.com>
+ondigitalocean.app
+
+// DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/
+// Submitted by Robin H. Johnson <psl-maintainers@digitalocean.com>
+*.digitaloceanspaces.com
+
+// dnstrace.pro : https://dnstrace.pro/
+// Submitted by Chris Partridge <chris@partridge.tech>
+bci.dnstrace.pro
+
+// Dynu.com : https://www.dynu.com/
+// Submitted by Sue Ye <sue@dynu.com>
+ddnsfree.com
+ddnsgeek.com
+giize.com
+gleeze.com
+kozow.com
+loseyourip.com
+ooguy.com
+theworkpc.com
+casacam.net
+dynu.net
+accesscam.org
+camdvr.org
+freeddns.org
+mywire.org
+webredirect.org
+myddns.rocks
+blogsite.xyz
+
+// dynv6 : https://dynv6.com
+// Submitted by Dominik Menke <dom@digineo.de>
+dynv6.net
+
+// E4YOU spol. s.r.o. : https://e4you.cz/
+// Submitted by Vladimir Dudr <info@e4you.cz>
+e4.cz
+
+// Easypanel : https://easypanel.io
+// Submitted by Andrei Canta <andrei@easypanel.io>
+easypanel.app
+easypanel.host
+
+// EasyWP : https://www.easywp.com
+// Submitted by <infracloudteam@namecheap.com>
+*.ewp.live
+
+// Elementor : Elementor Ltd.
+// Submitted by Anton Barkan <antonb@elementor.com>
+elementor.cloud
+elementor.cool
+
+// En root‽ : https://en-root.org
+// Submitted by Emmanuel Raviart <emmanuel@raviart.com>
+en-root.fr
+
+// Enalean SAS: https://www.enalean.com
+// Submitted by Thomas Cottier <thomas.cottier@enalean.com>
+mytuleap.com
+tuleap-partners.com
+
+// Encoretivity AB: https://encore.dev
+// Submitted by André Eriksson <andre@encore.dev>
+encr.app
+encoreapi.com
+
+// ECG Robotics, Inc: https://ecgrobotics.org
+// Submitted by <frc1533@ecgrobotics.org>
+onred.one
+staging.onred.one
+
+// encoway GmbH : https://www.encoway.de
+// Submitted by Marcel Daus <cloudops@encoway.de>
+eu.encoway.cloud
+
+// EU.org https://eu.org/
+// Submitted by Pierre Beyssac <hostmaster@eu.org>
+eu.org
+al.eu.org
+asso.eu.org
+at.eu.org
+au.eu.org
+be.eu.org
+bg.eu.org
+ca.eu.org
+cd.eu.org
+ch.eu.org
+cn.eu.org
+cy.eu.org
+cz.eu.org
+de.eu.org
+dk.eu.org
+edu.eu.org
+ee.eu.org
+es.eu.org
+fi.eu.org
+fr.eu.org
+gr.eu.org
+hr.eu.org
+hu.eu.org
+ie.eu.org
+il.eu.org
+in.eu.org
+int.eu.org
+is.eu.org
+it.eu.org
+jp.eu.org
+kr.eu.org
+lt.eu.org
+lu.eu.org
+lv.eu.org
+mc.eu.org
+me.eu.org
+mk.eu.org
+mt.eu.org
+my.eu.org
+net.eu.org
+ng.eu.org
+nl.eu.org
+no.eu.org
+nz.eu.org
+paris.eu.org
+pl.eu.org
+pt.eu.org
+q-a.eu.org
+ro.eu.org
+ru.eu.org
+se.eu.org
+si.eu.org
+sk.eu.org
+tr.eu.org
+uk.eu.org
+us.eu.org
+
+// Eurobyte : https://eurobyte.ru
+// Submitted by Evgeniy Subbotin <e.subbotin@eurobyte.ru>
+eurodir.ru
+
+// Evennode : http://www.evennode.com/
+// Submitted by Michal Kralik <support@evennode.com>
+eu-1.evennode.com
+eu-2.evennode.com
+eu-3.evennode.com
+eu-4.evennode.com
+us-1.evennode.com
+us-2.evennode.com
+us-3.evennode.com
+us-4.evennode.com
+
+// eDirect Corp. : https://hosting.url.com.tw/
+// Submitted by C.S. chang <cschang@corp.url.com.tw>
+twmail.cc
+twmail.net
+twmail.org
+mymailer.com.tw
+url.tw
+
+// Fabrica Technologies, Inc. : https://www.fabrica.dev/
+// Submitted by Eric Jiang <eric@fabrica.dev>
+onfabrica.com
+
+// FAITID : https://faitid.org/
+// Submitted by Maxim Alzoba <tech.contact@faitid.org>
+// https://www.flexireg.net/stat_info
+ru.net
+adygeya.ru
+bashkiria.ru
+bir.ru
+cbg.ru
+com.ru
+dagestan.ru
+grozny.ru
+kalmykia.ru
+kustanai.ru
+marine.ru
+mordovia.ru
+msk.ru
+mytis.ru
+nalchik.ru
+nov.ru
+pyatigorsk.ru
+spb.ru
+vladikavkaz.ru
+vladimir.ru
+abkhazia.su
+adygeya.su
+aktyubinsk.su
+arkhangelsk.su
+armenia.su
+ashgabad.su
+azerbaijan.su
+balashov.su
+bashkiria.su
+bryansk.su
+bukhara.su
+chimkent.su
+dagestan.su
+east-kazakhstan.su
+exnet.su
+georgia.su
+grozny.su
+ivanovo.su
+jambyl.su
+kalmykia.su
+kaluga.su
+karacol.su
+karaganda.su
+karelia.su
+khakassia.su
+krasnodar.su
+kurgan.su
+kustanai.su
+lenug.su
+mangyshlak.su
+mordovia.su
+msk.su
+murmansk.su
+nalchik.su
+navoi.su
+north-kazakhstan.su
+nov.su
+obninsk.su
+penza.su
+pokrovsk.su
+sochi.su
+spb.su
+tashkent.su
+termez.su
+togliatti.su
+troitsk.su
+tselinograd.su
+tula.su
+tuva.su
+vladikavkaz.su
+vladimir.su
+vologda.su
+
+// Fancy Bits, LLC : http://getchannels.com
+// Submitted by Aman Gupta <aman@getchannels.com>
+channelsdvr.net
+u.channelsdvr.net
+
+// Fastly Inc. : http://www.fastly.com/
+// Submitted by Fastly Security <security@fastly.com>
+edgecompute.app
+fastly-edge.com
+fastly-terrarium.com
+fastlylb.net
+map.fastlylb.net
+freetls.fastly.net
+map.fastly.net
+a.prod.fastly.net
+global.prod.fastly.net
+a.ssl.fastly.net
+b.ssl.fastly.net
+global.ssl.fastly.net
+
+// Fastmail : https://www.fastmail.com/
+// Submitted by Marc Bradshaw <marc@fastmailteam.com>
+*.user.fm
+
+// FASTVPS EESTI OU : https://fastvps.ru/
+// Submitted by Likhachev Vasiliy <lihachev@fastvps.ru>
+fastvps-server.com
+fastvps.host
+myfast.host
+fastvps.site
+myfast.space
+
+// Fedora : https://fedoraproject.org/
+// submitted by Patrick Uiterwijk <puiterwijk@fedoraproject.org>
+fedorainfracloud.org
+fedorapeople.org
+cloud.fedoraproject.org
+app.os.fedoraproject.org
+app.os.stg.fedoraproject.org
+
+// FearWorks Media Ltd. : https://fearworksmedia.co.uk
+// submitted by Keith Fairley <domains@fearworksmedia.co.uk>
+conn.uk
+copro.uk
+hosp.uk
+
+// Fermax : https://fermax.com/
+// submitted by Koen Van Isterdael <k.vanisterdael@fermax.be>
+mydobiss.com
+
+// FH Muenster : https://www.fh-muenster.de
+// Submitted by Robin Naundorf <r.naundorf@fh-muenster.de>
+fh-muenster.io
+
+// Filegear Inc. : https://www.filegear.com
+// Submitted by Jason Zhu <jason@owtware.com>
+filegear.me
+filegear-au.me
+filegear-de.me
+filegear-gb.me
+filegear-ie.me
+filegear-jp.me
+filegear-sg.me
+
+// Firebase, Inc.
+// Submitted by Chris Raynor <chris@firebase.com>
+firebaseapp.com
+
+// Firewebkit : https://www.firewebkit.com
+// Submitted by Majid Qureshi <mqureshi@amrayn.com>
+fireweb.app
+
+// FLAP : https://www.flap.cloud
+// Submitted by Louis Chemineau <louis@chmn.me>
+flap.id
+
+// FlashDrive : https://flashdrive.io
+// Submitted by Eric Chan <support@flashdrive.io>
+onflashdrive.app
+fldrv.com
+
+// FlutterFlow : https://flutterflow.io
+// Submitted by Anton Emelyanov <anton@flutterflow.io>
+flutterflow.app
+
+// fly.io: https://fly.io
+// Submitted by Kurt Mackey <kurt@fly.io>
+fly.dev
+edgeapp.net
+shw.io
+
+// Flynn : https://flynn.io
+// Submitted by Jonathan Rudenberg <jonathan@flynn.io>
+flynnhosting.net
+
+// Forgerock : https://www.forgerock.com
+// Submitted by Roderick Parr <roderick.parr@forgerock.com>
+forgeblocks.com
+id.forgerock.io
+
+// Framer : https://www.framer.com
+// Submitted by Koen Rouwhorst <koenrh@framer.com>
+framer.app
+framercanvas.com
+framer.media
+framer.photos
+framer.website
+framer.wiki
+
+// Frusky MEDIA&PR : https://www.frusky.de
+// Submitted by Victor Pupynin <hallo@frusky.de>
+*.frusky.de
+
+// RavPage : https://www.ravpage.co.il
+// Submitted by Roni Horowitz <roni@responder.co.il>
+ravpage.co.il
+
+// Frederik Braun https://frederik-braun.com
+// Submitted by Frederik Braun <fb@frederik-braun.com>
+0e.vc
+
+// Freebox : http://www.freebox.fr
+// Submitted by Romain Fliedel <rfliedel@freebox.fr>
+freebox-os.com
+freeboxos.com
+fbx-os.fr
+fbxos.fr
+freebox-os.fr
+freeboxos.fr
+
+// freedesktop.org : https://www.freedesktop.org
+// Submitted by Daniel Stone <daniel@fooishbar.org>
+freedesktop.org
+
+// freemyip.com : https://freemyip.com
+// Submitted by Cadence <contact@freemyip.com>
+freemyip.com
+
+// FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at
+// Submitted by Daniel A. Maierhofer <vorstand@funkfeuer.at>
+wien.funkfeuer.at
+
+// Futureweb GmbH : https://www.futureweb.at
+// Submitted by Andreas Schnederle-Wagner <schnederle@futureweb.at>
+*.futurecms.at
+*.ex.futurecms.at
+*.in.futurecms.at
+futurehosting.at
+futuremailing.at
+*.ex.ortsinfo.at
+*.kunden.ortsinfo.at
+*.statics.cloud
+
+// GCom Internet : https://www.gcom.net.au
+// Submitted by Leo Julius <support@gcom.net.au>
+aliases121.com
+
+// GDS : https://www.gov.uk/service-manual/technology/managing-domain-names
+// Submitted by Stephen Ford <hostmaster@digital.cabinet-office.gov.uk>
+independent-commission.uk
+independent-inquest.uk
+independent-inquiry.uk
+independent-panel.uk
+independent-review.uk
+public-inquiry.uk
+royal-commission.uk
+campaign.gov.uk
+service.gov.uk
+
+// CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk
+// Submitted by Jamie Tanna <jamie.tanna@digital.cabinet-office.gov.uk>
+api.gov.uk
+
+// Gehirn Inc. : https://www.gehirn.co.jp/
+// Submitted by Kohei YOSHIDA <tech@gehirn.co.jp>
+gehirn.ne.jp
+usercontent.jp
+
+// Gentlent, Inc. : https://www.gentlent.com
+// Submitted by Tom Klein <tom@gentlent.com>
+gentapps.com
+gentlentapis.com
+lab.ms
+cdn-edges.net
+
+// Ghost Foundation : https://ghost.org
+// Submitted by Matt Hanley <security@ghost.org>
+ghost.io
+
+// GignoSystemJapan: http://gsj.bz
+// Submitted by GignoSystemJapan <kakutou-ec@gsj.bz>
+gsj.bz
+
+// GitHub, Inc.
+// Submitted by Patrick Toomey <security@github.com>
+githubusercontent.com
+githubpreview.dev
+github.io
+
+// GitLab, Inc.
+// Submitted by Alex Hanselka <alex@gitlab.com>
+gitlab.io
+
+// Gitplac.si - https://gitplac.si
+// Submitted by Aljaž Starc <me@aljaxus.eu>
+gitapp.si
+gitpage.si
+
+// Glitch, Inc : https://glitch.com
+// Submitted by Mads Hartmann <mads@glitch.com>
+glitch.me
+
+// Global NOG Alliance : https://nogalliance.org/
+// Submitted by Sander Steffann <sander@nogalliance.org>
+nog.community
+
+// Globe Hosting SRL : https://www.globehosting.com/
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+co.ro
+shop.ro
+
+// GMO Pepabo, Inc. : https://pepabo.com/
+// Submitted by Hosting Div <admin@pepabo.com>
+lolipop.io
+angry.jp
+babyblue.jp
+babymilk.jp
+backdrop.jp
+bambina.jp
+bitter.jp
+blush.jp
+boo.jp
+boy.jp
+boyfriend.jp
+but.jp
+candypop.jp
+capoo.jp
+catfood.jp
+cheap.jp
+chicappa.jp
+chillout.jp
+chips.jp
+chowder.jp
+chu.jp
+ciao.jp
+cocotte.jp
+coolblog.jp
+cranky.jp
+cutegirl.jp
+daa.jp
+deca.jp
+deci.jp
+digick.jp
+egoism.jp
+fakefur.jp
+fem.jp
+flier.jp
+floppy.jp
+fool.jp
+frenchkiss.jp
+girlfriend.jp
+girly.jp
+gloomy.jp
+gonna.jp
+greater.jp
+hacca.jp
+heavy.jp
+her.jp
+hiho.jp
+hippy.jp
+holy.jp
+hungry.jp
+icurus.jp
+itigo.jp
+jellybean.jp
+kikirara.jp
+kill.jp
+kilo.jp
+kuron.jp
+littlestar.jp
+lolipopmc.jp
+lolitapunk.jp
+lomo.jp
+lovepop.jp
+lovesick.jp
+main.jp
+mods.jp
+mond.jp
+mongolian.jp
+moo.jp
+namaste.jp
+nikita.jp
+nobushi.jp
+noor.jp
+oops.jp
+parallel.jp
+parasite.jp
+pecori.jp
+peewee.jp
+penne.jp
+pepper.jp
+perma.jp
+pigboat.jp
+pinoko.jp
+punyu.jp
+pupu.jp
+pussycat.jp
+pya.jp
+raindrop.jp
+readymade.jp
+sadist.jp
+schoolbus.jp
+secret.jp
+staba.jp
+stripper.jp
+sub.jp
+sunnyday.jp
+thick.jp
+tonkotsu.jp
+under.jp
+upper.jp
+velvet.jp
+verse.jp
+versus.jp
+vivian.jp
+watson.jp
+weblike.jp
+whitesnow.jp
+zombie.jp
+heteml.net
+
+// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/
+// Submitted by Tom Whitwell <gov-uk-paas-support@digital.cabinet-office.gov.uk>
+cloudapps.digital
+london.cloudapps.digital
+
+// GOV.UK Pay : https://www.payments.service.gov.uk/
+// Submitted by Richard Baker <richard.baker@digital.cabinet-office.gov.uk>
+pymnt.uk
+
+// GlobeHosting, Inc.
+// Submitted by Zoltan Egresi <egresi@globehosting.com>
+ro.im
+
+// GoIP DNS Services : http://www.goip.de
+// Submitted by Christian Poulter <milchstrasse@goip.de>
+goip.de
+
+// Google, Inc.
+// Submitted by Eduardo Vela <evn@google.com>
+*.run.app
+web.app
+*.0emm.com
+appspot.com
+*.r.appspot.com
+codespot.com
+googleapis.com
+googlecode.com
+pagespeedmobilizer.com
+publishproxy.com
+withgoogle.com
+withyoutube.com
+*.gateway.dev
+cloud.goog
+translate.goog
+*.usercontent.goog
+cloudfunctions.net
+blogspot.ae
+blogspot.al
+blogspot.am
+blogspot.ba
+blogspot.be
+blogspot.bg
+blogspot.bj
+blogspot.ca
+blogspot.cf
+blogspot.ch
+blogspot.cl
+blogspot.co.at
+blogspot.co.id
+blogspot.co.il
+blogspot.co.ke
+blogspot.co.nz
+blogspot.co.uk
+blogspot.co.za
+blogspot.com
+blogspot.com.ar
+blogspot.com.au
+blogspot.com.br
+blogspot.com.by
+blogspot.com.co
+blogspot.com.cy
+blogspot.com.ee
+blogspot.com.eg
+blogspot.com.es
+blogspot.com.mt
+blogspot.com.ng
+blogspot.com.tr
+blogspot.com.uy
+blogspot.cv
+blogspot.cz
+blogspot.de
+blogspot.dk
+blogspot.fi
+blogspot.fr
+blogspot.gr
+blogspot.hk
+blogspot.hr
+blogspot.hu
+blogspot.ie
+blogspot.in
+blogspot.is
+blogspot.it
+blogspot.jp
+blogspot.kr
+blogspot.li
+blogspot.lt
+blogspot.lu
+blogspot.md
+blogspot.mk
+blogspot.mr
+blogspot.mx
+blogspot.my
+blogspot.nl
+blogspot.no
+blogspot.pe
+blogspot.pt
+blogspot.qa
+blogspot.re
+blogspot.ro
+blogspot.rs
+blogspot.ru
+blogspot.se
+blogspot.sg
+blogspot.si
+blogspot.sk
+blogspot.sn
+blogspot.td
+blogspot.tw
+blogspot.ug
+blogspot.vn
+
+// Goupile : https://goupile.fr
+// Submitted by Niels Martignene <hello@goupile.fr>
+goupile.fr
+
+// Government of the Netherlands: https://www.government.nl
+// Submitted by <domeinnaam@minaz.nl>
+gov.nl
+
+// Group 53, LLC : https://www.group53.com
+// Submitted by Tyler Todd <noc@nova53.net>
+awsmppl.com
+
+// GünstigBestellen : https://günstigbestellen.de
+// Submitted by Furkan Akkoc <info@hendelzon.de>
+günstigbestellen.de
+günstigliefern.de
+
+// Hakaran group: http://hakaran.cz
+// Submitted by Arseniy Sokolov <security@hakaran.cz>
+fin.ci
+free.hr
+caa.li
+ua.rs
+conf.se
+
+// Handshake : https://handshake.org
+// Submitted by Mike Damm <md@md.vc>
+hs.zone
+hs.run
+
+// Hashbang : https://hashbang.sh
+hashbang.sh
+
+// Hasura : https://hasura.io
+// Submitted by Shahidh K Muhammed <shahidh@hasura.io>
+hasura.app
+hasura-app.io
+
+// Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages): https://www.hs-heilbronn.de
+// Submitted by Richard Zowalla <mi-admin@hs-heilbronn.de>
+pages.it.hs-heilbronn.de
+
+// Hepforge : https://www.hepforge.org
+// Submitted by David Grellscheid <admin@hepforge.org>
+hepforge.org
+
+// Heroku : https://www.heroku.com/
+// Submitted by Tom Maher <tmaher@heroku.com>
+herokuapp.com
+herokussl.com
+
+// Hibernating Rhinos
+// Submitted by Oren Eini <oren@ravendb.net>
+ravendb.cloud
+ravendb.community
+ravendb.me
+development.run
+ravendb.run
+
+// home.pl S.A.: https://home.pl
+// Submitted by Krzysztof Wolski <krzysztof.wolski@home.eu>
+homesklep.pl
+
+// Homebase : https://homebase.id/
+// Submitted by Jason Babo <info@homebase.id>
+*.kin.one
+*.id.pub
+*.kin.pub
+
+// Hong Kong Productivity Council: https://www.hkpc.org/
+// Submitted by SECaaS Team <summchan@hkpc.org>
+secaas.hk
+
+// Hoplix : https://www.hoplix.com
+// Submitted by Danilo De Franco<info@hoplix.shop>
+hoplix.shop
+
+
+// HOSTBIP REGISTRY : https://www.hostbip.com/
+// Submitted by Atanunu Igbunuroghene <publicsuffixlist@hostbip.com>
+orx.biz
+biz.gl
+col.ng
+firm.ng
+gen.ng
+ltd.ng
+ngo.ng
+edu.scot
+sch.so
+
+// HostFly : https://www.ie.ua
+// Submitted by Bohdan Dub <support@hostfly.com.ua>
+ie.ua
+
+// HostyHosting (hostyhosting.com)
+hostyhosting.io
+
+// Häkkinen.fi
+// Submitted by Eero Häkkinen <Eero+psl@Häkkinen.fi>
+häkkinen.fi
+
+// Ici la Lune : http://www.icilalune.com/
+// Submitted by Simon Morvan <simon@icilalune.com>
+*.moonscale.io
+moonscale.net
+
+// iki.fi
+// Submitted by Hannu Aronsson <haa@iki.fi>
+iki.fi
+
+// iliad italia: https://www.iliad.it
+// Submitted by Marios Makassikis <mmakassikis@freebox.fr>
+ibxos.it
+iliadboxos.it
+
+// Impertrix Solutions : <https://impertrixcdn.com>
+// Submitted by Zhixiang Zhao <csuite@impertrix.com>
+impertrixcdn.com
+impertrix.com
+
+// Incsub, LLC: https://incsub.com/
+// Submitted by Aaron Edwards <sysadmins@incsub.com>
+smushcdn.com
+wphostedmail.com
+wpmucdn.com
+tempurl.host
+wpmudev.host
+
+// Individual Network Berlin e.V. : https://www.in-berlin.de/
+// Submitted by Christian Seitz <chris@in-berlin.de>
+dyn-berlin.de
+in-berlin.de
+in-brb.de
+in-butter.de
+in-dsl.de
+in-dsl.net
+in-dsl.org
+in-vpn.de
+in-vpn.net
+in-vpn.org
+
+// info.at : http://www.info.at/
+biz.at
+info.at
+
+// info.cx : http://info.cx
+// Submitted by Jacob Slater <whois@igloo.to>
+info.cx
+
+// Interlegis : http://www.interlegis.leg.br
+// Submitted by Gabriel Ferreira <registrobr@interlegis.leg.br>
+ac.leg.br
+al.leg.br
+am.leg.br
+ap.leg.br
+ba.leg.br
+ce.leg.br
+df.leg.br
+es.leg.br
+go.leg.br
+ma.leg.br
+mg.leg.br
+ms.leg.br
+mt.leg.br
+pa.leg.br
+pb.leg.br
+pe.leg.br
+pi.leg.br
+pr.leg.br
+rj.leg.br
+rn.leg.br
+ro.leg.br
+rr.leg.br
+rs.leg.br
+sc.leg.br
+se.leg.br
+sp.leg.br
+to.leg.br
+
+// intermetrics GmbH : https://pixolino.com/
+// Submitted by Wolfgang Schwarz <admin@intermetrics.de>
+pixolino.com
+
+// Internet-Pro, LLP: https://netangels.ru/
+// Submitted by Vasiliy Sheredeko <piphon@gmail.com>
+na4u.ru
+
+// iopsys software solutions AB : https://iopsys.eu/
+// Submitted by Roman Azarenko <roman.azarenko@iopsys.eu>
+iopsys.se
+
+// IPiFony Systems, Inc. : https://www.ipifony.com/
+// Submitted by Matthew Hardeman <mhardeman@ipifony.com>
+ipifony.net
+
+// IServ GmbH : https://iserv.de
+// Submitted by Mario Hoberg <info@iserv.de>
+iservschule.de
+mein-iserv.de
+schulplattform.de
+schulserver.de
+test-iserv.de
+iserv.dev
+
+// I-O DATA DEVICE, INC. : http://www.iodata.com/
+// Submitted by Yuji Minagawa <domains-admin@iodata.jp>
+iobb.net
+
+// Jelastic, Inc. : https://jelastic.com/
+// Submitted by Ihor Kolodyuk <ik@jelastic.com>
+mel.cloudlets.com.au
+cloud.interhostsolutions.be
+mycloud.by
+alp1.ae.flow.ch
+appengine.flow.ch
+es-1.axarnet.cloud
+diadem.cloud
+vip.jelastic.cloud
+jele.cloud
+it1.eur.aruba.jenv-aruba.cloud
+it1.jenv-aruba.cloud
+keliweb.cloud
+cs.keliweb.cloud
+oxa.cloud
+tn.oxa.cloud
+uk.oxa.cloud
+primetel.cloud
+uk.primetel.cloud
+ca.reclaim.cloud
+uk.reclaim.cloud
+us.reclaim.cloud
+ch.trendhosting.cloud
+de.trendhosting.cloud
+jele.club
+amscompute.com
+dopaas.com
+paas.hosted-by-previder.com
+rag-cloud.hosteur.com
+rag-cloud-ch.hosteur.com
+jcloud.ik-server.com
+jcloud-ver-jpc.ik-server.com
+demo.jelastic.com
+kilatiron.com
+paas.massivegrid.com
+jed.wafaicloud.com
+lon.wafaicloud.com
+ryd.wafaicloud.com
+j.scaleforce.com.cy
+jelastic.dogado.eu
+fi.cloudplatform.fi
+demo.datacenter.fi
+paas.datacenter.fi
+jele.host
+mircloud.host
+paas.beebyte.io
+sekd1.beebyteapp.io
+jele.io
+cloud-fr1.unispace.io
+jc.neen.it
+cloud.jelastic.open.tim.it
+jcloud.kz
+upaas.kazteleport.kz
+cloudjiffy.net
+fra1-de.cloudjiffy.net
+west1-us.cloudjiffy.net
+jls-sto1.elastx.net
+jls-sto2.elastx.net
+jls-sto3.elastx.net
+faststacks.net
+fr-1.paas.massivegrid.net
+lon-1.paas.massivegrid.net
+lon-2.paas.massivegrid.net
+ny-1.paas.massivegrid.net
+ny-2.paas.massivegrid.net
+sg-1.paas.massivegrid.net
+jelastic.saveincloud.net
+nordeste-idc.saveincloud.net
+j.scaleforce.net
+jelastic.tsukaeru.net
+sdscloud.pl
+unicloud.pl
+mircloud.ru
+jelastic.regruhosting.ru
+enscaled.sg
+jele.site
+jelastic.team
+orangecloud.tn
+j.layershift.co.uk
+phx.enscaled.us
+mircloud.us
+
+// Jino : https://www.jino.ru
+// Submitted by Sergey Ulyashin <ulyashin@jino.ru>
+myjino.ru
+*.hosting.myjino.ru
+*.landing.myjino.ru
+*.spectrum.myjino.ru
+*.vps.myjino.ru
+
+// Jotelulu S.L. : https://jotelulu.com
+// Submitted by Daniel Fariña <ingenieria@jotelulu.com>
+jotelulu.cloud
+
+// Joyent : https://www.joyent.com/
+// Submitted by Brian Bennett <brian.bennett@joyent.com>
+*.triton.zone
+*.cns.joyent.com
+
+// JS.ORG : http://dns.js.org
+// Submitted by Stefan Keim <admin@js.org>
+js.org
+
+// KaasHosting : http://www.kaashosting.nl/
+// Submitted by Wouter Bakker <hostmaster@kaashosting.nl>
+kaas.gg
+khplay.nl
+
+// Kakao : https://www.kakaocorp.com/
+// Submitted by JaeYoong Lee <cec@kakaocorp.com>
+ktistory.com
+
+// Kapsi : https://kapsi.fi
+// Submitted by Tomi Juntunen <erani@kapsi.fi>
+kapsi.fi
+
+// Keyweb AG : https://www.keyweb.de
+// Submitted by Martin Dannehl <postmaster@keymachine.de>
+keymachine.de
+
+// KingHost : https://king.host
+// Submitted by Felipe Keller Braz <felipebraz@kinghost.com.br>
+kinghost.net
+uni5.net
+
+// KnightPoint Systems, LLC : http://www.knightpoint.com/
+// Submitted by Roy Keene <rkeene@knightpoint.com>
+knightpoint.systems
+
+// KoobinEvent, SL: https://www.koobin.com
+// Submitted by Iván Oliva <ivan.oliva@koobin.com>
+koobin.events
+
+// KUROKU LTD : https://kuroku.ltd/
+// Submitted by DisposaBoy <security@oya.to>
+oya.to
+
+// Katholieke Universiteit Leuven: https://www.kuleuven.be
+// Submitted by Abuse KU Leuven <abuse@kuleuven.be>
+kuleuven.cloud
+ezproxy.kuleuven.be
+
+// .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf
+co.krd
+edu.krd
+
+// Krellian Ltd. : https://krellian.com
+// Submitted by Ben Francis <ben@krellian.com>
+krellian.net
+webthings.io
+
+// LCube - Professional hosting e.K. : https://www.lcube-webhosting.de
+// Submitted by Lars Laehn <info@lcube.de>
+git-repos.de
+lcube-server.de
+svn-repos.de
+
+// Leadpages : https://www.leadpages.net
+// Submitted by Greg Dallavalle <domains@leadpages.net>
+leadpages.co
+lpages.co
+lpusercontent.com
+
+// Lelux.fi : https://lelux.fi/
+// Submitted by Lelux Admin <publisuffix@lelux.site>
+lelux.site
+
+// Lifetime Hosting : https://Lifetime.Hosting/
+// Submitted by Mike Fillator <support@lifetime.hosting>
+co.business
+co.education
+co.events
+co.financial
+co.network
+co.place
+co.technology
+
+// Lightmaker Property Manager, Inc. : https://app.lmpm.com/
+// Submitted by Greg Holland <greg.holland@lmpm.com>
+app.lmpm.com
+
+// linkyard ldt: https://www.linkyard.ch/
+// Submitted by Mario Siegenthaler <mario.siegenthaler@linkyard.ch>
+linkyard.cloud
+linkyard-cloud.ch
+
+// Linode : https://linode.com
+// Submitted by <security@linode.com>
+members.linode.com
+*.nodebalancer.linode.com
+*.linodeobjects.com
+ip.linodeusercontent.com
+
+// LiquidNet Ltd : http://www.liquidnetlimited.com/
+// Submitted by Victor Velchev <admin@liquidnetlimited.com>
+we.bs
+
+// Localcert : https://localcert.dev
+// Submitted by Lann Martin <security@localcert.dev>
+*.user.localcert.dev
+
+// localzone.xyz
+// Submitted by Kenny Niehage <hello@yahe.sh>
+localzone.xyz
+
+// Log'in Line : https://www.loginline.com/
+// Submitted by Rémi Mach <remi.mach@loginline.com>
+loginline.app
+loginline.dev
+loginline.io
+loginline.services
+loginline.site
+
+// Lokalized : https://lokalized.nl
+// Submitted by Noah Taheij <noah@lokalized.nl>
+servers.run
+
+// Lõhmus Family, The
+// Submitted by Heiki Lõhmus <hostmaster at lohmus dot me>
+lohmus.me
+
+// LubMAN UMCS Sp. z o.o : https://lubman.pl/
+// Submitted by Ireneusz Maliszewski <ireneusz.maliszewski@lubman.pl>
+krasnik.pl
+leczna.pl
+lubartow.pl
+lublin.pl
+poniatowa.pl
+swidnik.pl
+
+// Lug.org.uk : https://lug.org.uk
+// Submitted by Jon Spriggs <admin@lug.org.uk>
+glug.org.uk
+lug.org.uk
+lugs.org.uk
+
+// Lukanet Ltd : https://lukanet.com
+// Submitted by Anton Avramov <register@lukanet.com>
+barsy.bg
+barsy.co.uk
+barsyonline.co.uk
+barsycenter.com
+barsyonline.com
+barsy.club
+barsy.de
+barsy.eu
+barsy.in
+barsy.info
+barsy.io
+barsy.me
+barsy.menu
+barsy.mobi
+barsy.net
+barsy.online
+barsy.org
+barsy.pro
+barsy.pub
+barsy.ro
+barsy.shop
+barsy.site
+barsy.support
+barsy.uk
+
+// Magento Commerce
+// Submitted by Damien Tournoud <dtournoud@magento.cloud>
+*.magentosite.cloud
+
+// May First - People Link : https://mayfirst.org/
+// Submitted by Jamie McClelland <info@mayfirst.org>
+mayfirst.info
+mayfirst.org
+
+// Mail.Ru Group : https://hb.cldmail.ru
+// Submitted by Ilya Zaretskiy <zaretskiy@corp.mail.ru>
+hb.cldmail.ru
+
+// Mail Transfer Platform : https://www.neupeer.com
+// Submitted by Li Hui <lihui@neupeer.com>
+cn.vu
+
+// Maze Play: https://www.mazeplay.com
+// Submitted by Adam Humpherys <adam@mws.dev>
+mazeplay.com
+
+// mcpe.me : https://mcpe.me
+// Submitted by Noa Heyl <hi@noa.dev>
+mcpe.me
+
+// McHost : https://mchost.ru
+// Submitted by Evgeniy Subbotin <e.subbotin@mchost.ru>
+mcdir.me
+mcdir.ru
+mcpre.ru
+vps.mcdir.ru
+
+// Mediatech : https://mediatech.by
+// Submitted by Evgeniy Kozhuhovskiy <ugenk@mediatech.by>
+mediatech.by
+mediatech.dev
+
+// Medicom Health : https://medicomhealth.com
+// Submitted by Michael Olson <molson@medicomhealth.com>
+hra.health
+
+// Memset hosting : https://www.memset.com
+// Submitted by Tom Whitwell <domains@memset.com>
+miniserver.com
+memset.net
+
+// Messerli Informatik AG : https://www.messerli.ch/
+// Submitted by Ruben Schmidmeister <psl-maintainers@messerli.ch>
+messerli.app
+
+// Meta Platforms, Inc. : https://meta.com/
+// Submitted by Jacob Cordero <public-suffix@meta.com>
+atmeta.com
+apps.fbsbx.com
+
+// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/
+// Submitted by Zdeněk Šustr <zdenek.sustr@cesnet.cz>
+*.cloud.metacentrum.cz
+custom.metacentrum.cz
+
+// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/
+// Submitted by Radim Janča <janca@cesnet.cz>
+flt.cloud.muni.cz
+usr.cloud.muni.cz
+
+// Meteor Development Group : https://www.meteor.com/hosting
+// Submitted by Pierre Carrier <pierre@meteor.com>
+meteorapp.com
+eu.meteorapp.com
+
+// Michau Enterprises Limited : http://www.co.pl/
+co.pl
+
+// Microsoft Corporation : http://microsoft.com
+// Submitted by Public Suffix List Admin <msftpsladmin@microsoft.com>
+// Managed by Corporate Domains
+// Microsoft Azure : https://home.azure
+*.azurecontainer.io
+*.cloudapp.azure.com
+azure-api.net
+azureedge.net
+azurefd.net
+azurewebsites.net
+azure-mobile.net
+azurestaticapps.net
+1.azurestaticapps.net
+2.azurestaticapps.net
+3.azurestaticapps.net
+4.azurestaticapps.net
+5.azurestaticapps.net
+6.azurestaticapps.net
+7.azurestaticapps.net
+centralus.azurestaticapps.net
+eastasia.azurestaticapps.net
+eastus2.azurestaticapps.net
+westeurope.azurestaticapps.net
+westus2.azurestaticapps.net
+cloudapp.net
+trafficmanager.net
+blob.core.windows.net
+servicebus.windows.net
+
+// minion.systems : http://minion.systems
+// Submitted by Robert Böttinger <r@minion.systems>
+csx.cc
+
+// Mintere : https://mintere.com/
+// Submitted by Ben Aubin <security@mintere.com>
+mintere.site
+
+// MobileEducation, LLC : https://joinforte.com
+// Submitted by Grayson Martin <grayson.martin@mobileeducation.us>
+forte.id
+
+// MODX Systems LLC : https://modx.com
+// Submitted by Elizabeth Southwell <elizabeth@modx.com>
+modx.dev
+
+// Mozilla Corporation : https://mozilla.com
+// Submitted by Ben Francis <bfrancis@mozilla.com>
+mozilla-iot.org
+
+// Mozilla Foundation : https://mozilla.org/
+// Submitted by glob <glob@mozilla.com>
+bmoattachments.org
+
+// MSK-IX : https://www.msk-ix.ru/
+// Submitted by Khannanov Roman <r.khannanov@msk-ix.ru>
+net.ru
+org.ru
+pp.ru
+
+// Mythic Beasts : https://www.mythic-beasts.com
+// Submitted by Paul Cammish <kelduum@mythic-beasts.com>
+hostedpi.com
+customer.mythic-beasts.com
+caracal.mythic-beasts.com
+fentiger.mythic-beasts.com
+lynx.mythic-beasts.com
+ocelot.mythic-beasts.com
+oncilla.mythic-beasts.com
+onza.mythic-beasts.com
+sphinx.mythic-beasts.com
+vs.mythic-beasts.com
+x.mythic-beasts.com
+yali.mythic-beasts.com
+cust.retrosnub.co.uk
+
+// Nabu Casa : https://www.nabucasa.com
+// Submitted by Paulus Schoutsen <infra@nabucasa.com>
+ui.nabu.casa
+
+// Net at Work Gmbh : https://www.netatwork.de
+// Submitted by Jan Jaeschke <jan.jaeschke@netatwork.de>
+cloud.nospamproxy.com
+
+// Netlify : https://www.netlify.com
+// Submitted by Jessica Parsons <jessica@netlify.com>
+netlify.app
+
+// Neustar Inc.
+// Submitted by Trung Tran <Trung.Tran@neustar.biz>
+4u.com
+
+// ngrok : https://ngrok.com/
+// Submitted by Alan Shreve <alan@ngrok.com>
+ngrok.app
+ngrok-free.app
+ngrok.dev
+ngrok-free.dev
+ngrok.io
+ap.ngrok.io
+au.ngrok.io
+eu.ngrok.io
+in.ngrok.io
+jp.ngrok.io
+sa.ngrok.io
+us.ngrok.io
+ngrok.pizza
+ngrok.pro
+
+// Nicolaus Copernicus University in Torun - MSK TORMAN (https://www.man.torun.pl)
+torun.pl
+
+// Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/
+// Submitted by Nicholas Ford <nick@nimbushosting.co.uk>
+nh-serv.co.uk
+
+// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/
+// Submitted by Jeff Wheelhouse <support@nearlyfreespeech.net>
+nfshost.com
+
+// Noop : https://noop.app
+// Submitted by Nathaniel Schweinberg <noop@rearc.io>
+*.developer.app
+noop.app
+
+// Northflank Ltd. : https://northflank.com/
+// Submitted by Marco Suter <marco@northflank.com>
+*.northflank.app
+*.build.run
+*.code.run
+*.database.run
+*.migration.run
+
+// Noticeable : https://noticeable.io
+// Submitted by Laurent Pellegrino <security@noticeable.io>
+noticeable.news
+
+// Now-DNS : https://now-dns.com
+// Submitted by Steve Russell <steve@now-dns.com>
+dnsking.ch
+mypi.co
+n4t.co
+001www.com
+ddnslive.com
+myiphost.com
+forumz.info
+16-b.it
+32-b.it
+64-b.it
+soundcast.me
+tcp4.me
+dnsup.net
+hicam.net
+now-dns.net
+ownip.net
+vpndns.net
+dynserv.org
+now-dns.org
+x443.pw
+now-dns.top
+ntdll.top
+freeddns.us
+crafting.xyz
+zapto.xyz
+
+// nsupdate.info : https://www.nsupdate.info/
+// Submitted by Thomas Waldmann <info@nsupdate.info>
+nsupdate.info
+nerdpol.ovh
+
+// No-IP.com : https://noip.com/
+// Submitted by Deven Reza <publicsuffixlist@noip.com>
+blogsyte.com
+brasilia.me
+cable-modem.org
+ciscofreak.com
+collegefan.org
+couchpotatofries.org
+damnserver.com
+ddns.me
+ditchyourip.com
+dnsfor.me
+dnsiskinky.com
+dvrcam.info
+dynns.com
+eating-organic.net
+fantasyleague.cc
+geekgalaxy.com
+golffan.us
+health-carereform.com
+homesecuritymac.com
+homesecuritypc.com
+hopto.me
+ilovecollege.info
+loginto.me
+mlbfan.org
+mmafan.biz
+myactivedirectory.com
+mydissent.net
+myeffect.net
+mymediapc.net
+mypsx.net
+mysecuritycamera.com
+mysecuritycamera.net
+mysecuritycamera.org
+net-freaks.com
+nflfan.org
+nhlfan.net
+no-ip.ca
+no-ip.co.uk
+no-ip.net
+noip.us
+onthewifi.com
+pgafan.net
+point2this.com
+pointto.us
+privatizehealthinsurance.net
+quicksytes.com
+read-books.org
+securitytactics.com
+serveexchange.com
+servehumour.com
+servep2p.com
+servesarcasm.com
+stufftoread.com
+ufcfan.org
+unusualperson.com
+workisboring.com
+3utilities.com
+bounceme.net
+ddns.net
+ddnsking.com
+gotdns.ch
+hopto.org
+myftp.biz
+myftp.org
+myvnc.com
+no-ip.biz
+no-ip.info
+no-ip.org
+noip.me
+redirectme.net
+servebeer.com
+serveblog.net
+servecounterstrike.com
+serveftp.com
+servegame.com
+servehalflife.com
+servehttp.com
+serveirc.com
+serveminecraft.net
+servemp3.com
+servepics.com
+servequake.com
+sytes.net
+webhop.me
+zapto.org
+
+// NodeArt : https://nodeart.io
+// Submitted by Konstantin Nosov <Nosov@nodeart.io>
+stage.nodeart.io
+
+// Nucleos Inc. : https://nucleos.com
+// Submitted by Piotr Zduniak <piotr@nucleos.com>
+pcloud.host
+
+// NYC.mn : http://www.information.nyc.mn
+// Submitted by Matthew Brown <mattbrown@nyc.mn>
+nyc.mn
+
+// Observable, Inc. : https://observablehq.com
+// Submitted by Mike Bostock <dns@observablehq.com>
+static.observableusercontent.com
+
+// Octopodal Solutions, LLC. : https://ulterius.io/
+// Submitted by Andrew Sampson <andrew@ulterius.io>
+cya.gg
+
+// OMG.LOL : <https://omg.lol>
+// Submitted by Adam Newbold <adam@omg.lol>
+omg.lol
+
+// Omnibond Systems, LLC. : https://www.omnibond.com
+// Submitted by Cole Estep <cole@omnibond.com>
+cloudycluster.net
+
+// OmniWe Limited: https://omniwe.com
+// Submitted by Vicary Archangel <vicary@omniwe.com>
+omniwe.site
+
+// One.com: https://www.one.com/
+// Submitted by Jacob Bunk Nielsen <jbn@one.com>
+123hjemmeside.dk
+123hjemmeside.no
+123homepage.it
+123kotisivu.fi
+123minsida.se
+123miweb.es
+123paginaweb.pt
+123sait.ru
+123siteweb.fr
+123webseite.at
+123webseite.de
+123website.be
+123website.ch
+123website.lu
+123website.nl
+service.one
+simplesite.com
+simplesite.com.br
+simplesite.gr
+simplesite.pl
+
+// One Fold Media : http://www.onefoldmedia.com/
+// Submitted by Eddie Jones <eddie@onefoldmedia.com>
+nid.io
+
+// Open Social : https://www.getopensocial.com/
+// Submitted by Alexander Varwijk <security@getopensocial.com>
+opensocial.site
+
+// OpenCraft GmbH : http://opencraft.com/
+// Submitted by Sven Marnach <sven@opencraft.com>
+opencraft.hosting
+
+// OpenResearch GmbH: https://openresearch.com/
+// Submitted by Philipp Schmid <ops@openresearch.com>
+orsites.com
+
+// Opera Software, A.S.A.
+// Submitted by Yngve Pettersen <yngve@opera.com>
+operaunite.com
+
+// Orange : https://www.orange.com
+// Submitted by Alexandre Linte <alexandre.linte@orange.com>
+tech.orange
+
+// Oursky Limited : https://authgear.com/, https://skygear.io/
+// Submitted by Authgear Team <hello@authgear.com>, Skygear Developer <hello@skygear.io>
+authgear-staging.com
+authgearapps.com
+skygearapp.com
+
+// OutSystems
+// Submitted by Duarte Santos <domain-admin@outsystemscloud.com>
+outsystemscloud.com
+
+// OVHcloud: https://ovhcloud.com
+// Submitted by Vincent Cassé <vincent.casse@ovhcloud.com>
+*.webpaas.ovh.net
+*.hosting.ovh.net
+
+// OwnProvider GmbH: http://www.ownprovider.com
+// Submitted by Jan Moennich <jan.moennich@ownprovider.com>
+ownprovider.com
+own.pm
+
+// OwO : https://whats-th.is/
+// Submitted by Dean Sheather <dean@deansheather.com>
+*.owo.codes
+
+// OX : http://www.ox.rs
+// Submitted by Adam Grand <webmaster@mail.ox.rs>
+ox.rs
+
+// oy.lc
+// Submitted by Charly Coste <changaco@changaco.oy.lc>
+oy.lc
+
+// Pagefog : https://pagefog.com/
+// Submitted by Derek Myers <derek@pagefog.com>
+pgfog.com
+
+// Pagefront : https://www.pagefronthq.com/
+// Submitted by Jason Kriss <jason@pagefronthq.com>
+pagefrontapp.com
+
+// PageXL : https://pagexl.com
+// Submitted by Yann Guichard <yann@pagexl.com>
+pagexl.com
+
+// Paywhirl, Inc : https://paywhirl.com/
+// Submitted by Daniel Netzer <dan@paywhirl.com>
+*.paywhirl.com
+
+// pcarrier.ca Software Inc: https://pcarrier.ca/
+// Submitted by Pierre Carrier <pc@rrier.ca>
+bar0.net
+bar1.net
+bar2.net
+rdv.to
+
+// .pl domains (grandfathered)
+art.pl
+gliwice.pl
+krakow.pl
+poznan.pl
+wroc.pl
+zakopane.pl
+
+// Pantheon Systems, Inc. : https://pantheon.io/
+// Submitted by Gary Dylina <gary@pantheon.io>
+pantheonsite.io
+gotpantheon.com
+
+// Peplink | Pepwave : http://peplink.com/
+// Submitted by Steve Leung <steveleung@peplink.com>
+mypep.link
+
+// Perspecta : https://perspecta.com/
+// Submitted by Kenneth Van Alstyne <kvanalstyne@perspecta.com>
+perspecta.cloud
+
+// PE Ulyanov Kirill Sergeevich : https://airy.host
+// Submitted by Kirill Ulyanov <k.ulyanov@airy.host>
+lk3.ru
+
+// Planet-Work : https://www.planet-work.com/
+// Submitted by Frédéric VANNIÈRE <f.vanniere@planet-work.com>
+on-web.fr
+
+// Platform.sh : https://platform.sh
+// Submitted by Nikola Kotur <nikola@platform.sh>
+*.upsun.app
+upsunapp.com
+ent.platform.sh
+eu.platform.sh
+us.platform.sh
+*.platformsh.site
+*.tst.site
+
+// Platter: https://platter.dev
+// Submitted by Patrick Flor <patrick@platter.dev>
+platter-app.com
+platter-app.dev
+platterp.us
+
+// Plesk : https://www.plesk.com/
+// Submitted by Anton Akhtyamov <program-managers@plesk.com>
+pdns.page
+plesk.page
+pleskns.com
+
+// Pley AB : https://www.pley.com/
+// Submitted by Henning Pohl <infra@pley.com>
+pley.games
+
+// Port53 : https://port53.io/
+// Submitted by Maximilian Schieder <maxi@zeug.co>
+dyn53.io
+
+// Porter : https://porter.run/
+// Submitted by Rudraksh MK <rudi@porter.run>
+onporter.run
+
+// Positive Codes Technology Company : http://co.bn/faq.html
+// Submitted by Zulfais <pc@co.bn>
+co.bn
+
+// Postman, Inc : https://postman.com
+// Submitted by Rahul Dhawan <security@postman.com>
+postman-echo.com
+pstmn.io
+mock.pstmn.io
+httpbin.org
+
+//prequalifyme.today : https://prequalifyme.today
+//Submitted by DeepakTiwari deepak@ivylead.io
+prequalifyme.today
+
+// prgmr.com : https://prgmr.com/
+// Submitted by Sarah Newman <owner@prgmr.com>
+xen.prgmr.com
+
+// priv.at : http://www.nic.priv.at/
+// Submitted by registry <lendl@nic.at>
+priv.at
+
+// privacytools.io : https://www.privacytools.io/
+// Submitted by Jonah Aragon <jonah@privacytools.io>
+prvcy.page
+
+// Protocol Labs : https://protocol.ai/
+// Submitted by Michael Burns <noc@protocol.ai>
+*.dweb.link
+
+// Protonet GmbH : http://protonet.io
+// Submitted by Martin Meier <admin@protonet.io>
+protonet.io
+
+// Publication Presse Communication SARL : https://ppcom.fr
+// Submitted by Yaacov Akiba Slama <admin@chirurgiens-dentistes-en-france.fr>
+chirurgiens-dentistes-en-france.fr
+byen.site
+
+// pubtls.org: https://www.pubtls.org
+// Submitted by Kor Nielsen <kor@pubtls.org>
+pubtls.org
+
+// PythonAnywhere LLP: https://www.pythonanywhere.com
+// Submitted by Giles Thomas <giles@pythonanywhere.com>
+pythonanywhere.com
+eu.pythonanywhere.com
+
+// QOTO, Org.
+// Submitted by Jeffrey Phillips Freeman <jeffrey.freeman@qoto.org>
+qoto.io
+
+// Qualifio : https://qualifio.com/
+// Submitted by Xavier De Cock <xdecock@gmail.com>
+qualifioapp.com
+
+// Quality Unit: https://qualityunit.com
+// Submitted by Vasyl Tsalko <vtsalko@qualityunit.com>
+ladesk.com
+
+// QuickBackend: https://www.quickbackend.com
+// Submitted by Dani Biro <dani@pymet.com>
+qbuser.com
+
+// Rad Web Hosting: https://radwebhosting.com
+// Submitted by Scott Claeys <s.claeys@radwebhosting.com>
+cloudsite.builders
+myradweb.net
+servername.us
+
+// Redgate Software: https://red-gate.com
+// Submitted by Andrew Farries <andrew.farries@red-gate.com>
+instances.spawn.cc
+
+// Redstar Consultants : https://www.redstarconsultants.com/
+// Submitted by Jons Slemmer <jons@redstarconsultants.com>
+instantcloud.cn
+
+// Russian Academy of Sciences
+// Submitted by Tech Support <support@rasnet.ru>
+ras.ru
+
+// QA2
+// Submitted by Daniel Dent (https://www.danieldent.com/)
+qa2.com
+
+// QCX
+// Submitted by Cassandra Beelen <cassandra@beelen.one>
+qcx.io
+*.sys.qcx.io
+
+// QNAP System Inc : https://www.qnap.com
+// Submitted by Nick Chang <nickchang@qnap.com>
+dev-myqnapcloud.com
+alpha-myqnapcloud.com
+myqnapcloud.com
+
+// Quip : https://quip.com
+// Submitted by Patrick Linehan <plinehan@quip.com>
+*.quipelements.com
+
+// Qutheory LLC : http://qutheory.io
+// Submitted by Jonas Schwartz <jonas@qutheory.io>
+vapor.cloud
+vaporcloud.io
+
+// Rackmaze LLC : https://www.rackmaze.com
+// Submitted by Kirill Pertsev <kika@rackmaze.com>
+rackmaze.com
+rackmaze.net
+
+// Rakuten Games, Inc : https://dev.viberplay.io
+// Submitted by Joshua Zhang <public-suffix@rgames.jp>
+g.vbrplsbx.io
+
+// Rancher Labs, Inc : https://rancher.com
+// Submitted by Vincent Fiduccia <domains@rancher.com>
+*.on-k3s.io
+*.on-rancher.cloud
+*.on-rio.io
+
+// Read The Docs, Inc : https://www.readthedocs.org
+// Submitted by David Fischer <team@readthedocs.org>
+readthedocs.io
+
+// Red Hat, Inc. OpenShift : https://openshift.redhat.com/
+// Submitted by Tim Kramer <tkramer@rhcloud.com>
+rhcloud.com
+
+// Render : https://render.com
+// Submitted by Anurag Goel <dev@render.com>
+app.render.com
+onrender.com
+
+// Repl.it : https://repl.it
+// Submitted by Lincoln Bergeson <psl@repl.it>
+replit.app
+id.replit.app
+firewalledreplit.co
+id.firewalledreplit.co
+repl.co
+id.repl.co
+replit.dev
+archer.replit.dev
+bones.replit.dev
+canary.replit.dev
+global.replit.dev
+hacker.replit.dev
+id.replit.dev
+janeway.replit.dev
+kim.replit.dev
+kira.replit.dev
+kirk.replit.dev
+odo.replit.dev
+paris.replit.dev
+picard.replit.dev
+pike.replit.dev
+prerelease.replit.dev
+reed.replit.dev
+riker.replit.dev
+sisko.replit.dev
+spock.replit.dev
+staging.replit.dev
+sulu.replit.dev
+tarpit.replit.dev
+teams.replit.dev
+tucker.replit.dev
+wesley.replit.dev
+worf.replit.dev
+repl.run
+
+// Resin.io : https://resin.io
+// Submitted by Tim Perry <tim@resin.io>
+resindevice.io
+devices.resinstaging.io
+
+// RethinkDB : https://www.rethinkdb.com/
+// Submitted by Chris Kastorff <info@rethinkdb.com>
+hzc.io
+
+// Revitalised Limited : http://www.revitalised.co.uk
+// Submitted by Jack Price <jack@revitalised.co.uk>
+wellbeingzone.eu
+wellbeingzone.co.uk
+
+// Rico Developments Limited : https://adimo.co
+// Submitted by Colin Brown <hello@adimo.co>
+adimo.co.uk
+
+// Riseup Networks : https://riseup.net
+// Submitted by Micah Anderson <micah@riseup.net>
+itcouldbewor.se
+
+// Rochester Institute of Technology : http://www.rit.edu/
+// Submitted by Jennifer Herting <jchits@rit.edu>
+git-pages.rit.edu
+
+// Rocky Enterprise Software Foundation : https://resf.org
+// Submitted by Neil Hanlon <neil@resf.org>
+rocky.page
+
+// Rusnames Limited: http://rusnames.ru/
+// Submitted by Sergey Zotov <admin@rusnames.ru>
+биз.рус
+ком.рус
+крым.рус
+мир.рус
+мск.рус
+орг.рус
+самара.рус
+сочи.рус
+спб.рус
+я.рус
+
+// SAKURA Internet Inc. : https://www.sakura.ad.jp/
+// Submitted by Internet Service Department <rs-vendor-ml@sakura.ad.jp>
+180r.com
+dojin.com
+sakuratan.com
+sakuraweb.com
+x0.com
+2-d.jp
+bona.jp
+crap.jp
+daynight.jp
+eek.jp
+flop.jp
+halfmoon.jp
+jeez.jp
+matrix.jp
+mimoza.jp
+ivory.ne.jp
+mail-box.ne.jp
+mints.ne.jp
+mokuren.ne.jp
+opal.ne.jp
+sakura.ne.jp
+sumomo.ne.jp
+topaz.ne.jp
+netgamers.jp
+nyanta.jp
+o0o0.jp
+rdy.jp
+rgr.jp
+rulez.jp
+s3.isk01.sakurastorage.jp
+s3.isk02.sakurastorage.jp
+saloon.jp
+sblo.jp
+skr.jp
+tank.jp
+uh-oh.jp
+undo.jp
+rs.webaccel.jp
+user.webaccel.jp
+websozai.jp
+xii.jp
+squares.net
+jpn.org
+kirara.st
+x0.to
+from.tv
+sakura.tv
+
+// Salesforce.com, Inc. https://salesforce.com/
+// Submitted by Michael Biven <mbiven@salesforce.com> and Aaron Romeo <aaron.romeo@salesforce.com>
+*.builder.code.com
+*.dev-builder.code.com
+*.stg-builder.code.com
+*.001.test.code-builder-stg.platform.salesforce.com
+
+// Sandstorm Development Group, Inc. : https://sandcats.io/
+// Submitted by Asheesh Laroia <asheesh@sandstorm.io>
+sandcats.io
+
+// SBE network solutions GmbH : https://www.sbe.de/
+// Submitted by Norman Meilick <nm@sbe.de>
+logoip.de
+logoip.com
+
+// Scaleway : https://www.scaleway.com/
+// Submitted by Rémy Léone <rleone@scaleway.com>
+fr-par-1.baremetal.scw.cloud
+fr-par-2.baremetal.scw.cloud
+nl-ams-1.baremetal.scw.cloud
+cockpit.fr-par.scw.cloud
+fnc.fr-par.scw.cloud
+functions.fnc.fr-par.scw.cloud
+k8s.fr-par.scw.cloud
+nodes.k8s.fr-par.scw.cloud
+s3.fr-par.scw.cloud
+s3-website.fr-par.scw.cloud
+whm.fr-par.scw.cloud
+priv.instances.scw.cloud
+pub.instances.scw.cloud
+k8s.scw.cloud
+cockpit.nl-ams.scw.cloud
+k8s.nl-ams.scw.cloud
+nodes.k8s.nl-ams.scw.cloud
+s3.nl-ams.scw.cloud
+s3-website.nl-ams.scw.cloud
+whm.nl-ams.scw.cloud
+cockpit.pl-waw.scw.cloud
+k8s.pl-waw.scw.cloud
+nodes.k8s.pl-waw.scw.cloud
+s3.pl-waw.scw.cloud
+s3-website.pl-waw.scw.cloud
+scalebook.scw.cloud
+smartlabeling.scw.cloud
+dedibox.fr
+
+// schokokeks.org GbR : https://schokokeks.org/
+// Submitted by Hanno Böck <hanno@schokokeks.org>
+schokokeks.net
+
+// Scottish Government: https://www.gov.scot
+// Submitted by Martin Ellis <martin.ellis@gov.scot>
+gov.scot
+service.gov.scot
+
+// Scry Security : http://www.scrysec.com
+// Submitted by Shante Adam <shante@skyhat.io>
+scrysec.com
+
+// Securepoint GmbH : https://www.securepoint.de
+// Submitted by Erik Anders <erik.anders@securepoint.de>
+firewall-gateway.com
+firewall-gateway.de
+my-gateway.de
+my-router.de
+spdns.de
+spdns.eu
+firewall-gateway.net
+my-firewall.org
+myfirewall.org
+spdns.org
+
+// Seidat : https://www.seidat.com
+// Submitted by Artem Kondratev <accounts@seidat.com>
+seidat.net
+
+// Sellfy : https://sellfy.com
+// Submitted by Yuriy Romadin <contact@sellfy.com>
+sellfy.store
+
+// Senseering GmbH : https://www.senseering.de
+// Submitted by Felix Mönckemeyer <f.moenckemeyer@senseering.de>
+senseering.net
+
+// Sendmsg: https://www.sendmsg.co.il
+// Submitted by Assaf Stern <domains@comstar.co.il>
+minisite.ms
+
+// Service Magnet : https://myservicemagnet.com
+// Submitted by Dave Sanders <dave@myservicemagnet.com>
+magnet.page
+
+// Service Online LLC : http://drs.ua/
+// Submitted by Serhii Bulakh <support@drs.ua>
+biz.ua
+co.ua
+pp.ua
+
+// Shift Crypto AG : https://shiftcrypto.ch
+// Submitted by alex <alex@shiftcrypto.ch>
+shiftcrypto.dev
+shiftcrypto.io
+
+// ShiftEdit : https://shiftedit.net/
+// Submitted by Adam Jimenez <adam@shiftcreate.com>
+shiftedit.io
+
+// Shopblocks : http://www.shopblocks.com/
+// Submitted by Alex Bowers <alex@shopblocks.com>
+myshopblocks.com
+
+// Shopify : https://www.shopify.com
+// Submitted by Alex Richter <alex.richter@shopify.com>
+myshopify.com
+
+// Shopit : https://www.shopitcommerce.com/
+// Submitted by Craig McMahon <craig@shopitcommerce.com>
+shopitsite.com
+
+// shopware AG : https://shopware.com
+// Submitted by Jens Küper <cloud@shopware.com>
+shopware.store
+
+// Siemens Mobility GmbH
+// Submitted by Oliver Graebner <security@mo-siemens.io>
+mo-siemens.io
+
+// SinaAppEngine : http://sae.sina.com.cn/
+// Submitted by SinaAppEngine <saesupport@sinacloud.com>
+1kapp.com
+appchizi.com
+applinzi.com
+sinaapp.com
+vipsinaapp.com
+
+// Siteleaf : https://www.siteleaf.com/
+// Submitted by Skylar Challand <support@siteleaf.com>
+siteleaf.net
+
+// Skyhat : http://www.skyhat.io
+// Submitted by Shante Adam <shante@skyhat.io>
+bounty-full.com
+alpha.bounty-full.com
+beta.bounty-full.com
+
+// Smallregistry by Promopixel SARL: https://www.smallregistry.net
+// Former AFNIC's SLDs
+// Submitted by Jérôme Lipowicz <support@promopixel.com>
+aeroport.fr
+avocat.fr
+chambagri.fr
+chirurgiens-dentistes.fr
+experts-comptables.fr
+medecin.fr
+notaires.fr
+pharmacien.fr
+port.fr
+veterinaire.fr
+
+// Small Technology Foundation : https://small-tech.org
+// Submitted by Aral Balkan <aral@small-tech.org>
+small-web.org
+
+// Smoove.io : https://www.smoove.io/
+// Submitted by Dan Kozak <dan@smoove.io>
+vp4.me
+
+// Snowflake Inc : https://www.snowflake.com/
+// Submitted by Faith Olapade <faith.olapade@snowflake.com>
+snowflake.app
+privatelink.snowflake.app
+streamlit.app
+streamlitapp.com
+
+// Snowplow Analytics : https://snowplowanalytics.com/
+// Submitted by Ian Streeter <ian@snowplowanalytics.com>
+try-snowplow.com
+
+// SourceHut : https://sourcehut.org
+// Submitted by Drew DeVault <sir@cmpwn.com>
+srht.site
+
+// Stackhero : https://www.stackhero.io
+// Submitted by Adrien Gillon <adrien+public-suffix-list@stackhero.io>
+stackhero-network.com
+
+// STACKIT : https://www.stackit.de/en/
+// Submitted by STACKIT-DNS Team (Simon Stier) <stackit-dns@mail.schwarz>
+runs.onstackit.cloud
+stackit.gg
+stackit.rocks
+stackit.run
+stackit.zone
+
+// Staclar : https://staclar.com
+// Submitted by Q Misell <q@staclar.com>
+musician.io
+// Submitted by Matthias Merkel <matthias.merkel@staclar.com>
+novecore.site
+
+// staticland : https://static.land
+// Submitted by Seth Vincent <sethvincent@gmail.com>
+static.land
+dev.static.land
+sites.static.land
+
+// Storebase : https://www.storebase.io
+// Submitted by Tony Schirmer <tony@storebase.io>
+storebase.store
+
+// Strategic System Consulting (eApps Hosting): https://www.eapps.com/
+// Submitted by Alex Oancea <aoancea@cloudscale365.com>
+vps-host.net
+atl.jelastic.vps-host.net
+njs.jelastic.vps-host.net
+ric.jelastic.vps-host.net
+
+// Sony Interactive Entertainment LLC : https://sie.com/
+// Submitted by David Coles <david.coles@sony.com>
+playstation-cloud.com
+
+// SourceLair PC : https://www.sourcelair.com
+// Submitted by Antonis Kalipetis <akalipetis@sourcelair.com>
+apps.lair.io
+*.stolos.io
+
+// SpaceKit : https://www.spacekit.io/
+// Submitted by Reza Akhavan <spacekit.io@gmail.com>
+spacekit.io
+
+// SpeedPartner GmbH: https://www.speedpartner.de/
+// Submitted by Stefan Neufeind <info@speedpartner.de>
+customer.speedpartner.de
+
+// Spreadshop (sprd.net AG) : https://www.spreadshop.com/
+// Submitted by Martin Breest <security@spreadshop.com>
+myspreadshop.at
+myspreadshop.com.au
+myspreadshop.be
+myspreadshop.ca
+myspreadshop.ch
+myspreadshop.com
+myspreadshop.de
+myspreadshop.dk
+myspreadshop.es
+myspreadshop.fi
+myspreadshop.fr
+myspreadshop.ie
+myspreadshop.it
+myspreadshop.net
+myspreadshop.nl
+myspreadshop.no
+myspreadshop.pl
+myspreadshop.se
+myspreadshop.co.uk
+
+// Standard Library : https://stdlib.com
+// Submitted by Jacob Lee <jacob@stdlib.com>
+api.stdlib.com
+
+// stereosense GmbH : https://www.involve.me
+// Submitted by Florian Burmann <publicsuffix@involve.me>
+feedback.ac
+forms.ac
+assessments.cx
+calculators.cx
+funnels.cx
+paynow.cx
+quizzes.cx
+researched.cx
+tests.cx
+surveys.so
+
+// Storipress : https://storipress.com
+// Submitted by Benno Liu <benno@storipress.com>
+storipress.app
+
+// Storj Labs Inc. : https://storj.io/
+// Submitted by Philip Hutchins <hostmaster@storj.io>
+storj.farm
+
+// Streak : https://streak.com
+// Submitted by Blake Kadatz <eng@streak.com>
+streak-link.com
+streaklinks.com
+streakusercontent.com
+
+// Studenten Net Twente : http://www.snt.utwente.nl/
+// Submitted by Silke Hofstra <syscom@snt.utwente.nl>
+utwente.io
+
+// Student-Run Computing Facility : https://www.srcf.net/
+// Submitted by Edwin Balani <sysadmins@srcf.net>
+soc.srcf.net
+user.srcf.net
+
+// Sub 6 Limited: http://www.sub6.com
+// Submitted by Dan Miller <dm@sub6.com>
+temp-dns.com
+
+// Supabase : https://supabase.io
+// Submitted by Inian Parameshwaran <security@supabase.io>
+supabase.co
+supabase.in
+supabase.net
+su.paba.se
+
+// Symfony, SAS : https://symfony.com/
+// Submitted by Fabien Potencier <fabien@symfony.com>
+*.s5y.io
+*.sensiosite.cloud
+
+// Syncloud : https://syncloud.org
+// Submitted by Boris Rybalkin <syncloud@syncloud.it>
+syncloud.it
+
+// Synology, Inc. : https://www.synology.com/
+// Submitted by Rony Weng <ronyweng@synology.com>
+dscloud.biz
+direct.quickconnect.cn
+dsmynas.com
+familyds.com
+diskstation.me
+dscloud.me
+i234.me
+myds.me
+synology.me
+dscloud.mobi
+dsmynas.net
+familyds.net
+dsmynas.org
+familyds.org
+vpnplus.to
+direct.quickconnect.to
+
+// Tabit Technologies Ltd. : https://tabit.cloud/
+// Submitted by Oren Agiv <oren@tabit.cloud>
+tabitorder.co.il
+mytabit.co.il
+mytabit.com
+
+// TAIFUN Software AG : http://taifun-software.de
+// Submitted by Bjoern Henke <dev-server@taifun-software.de>
+taifun-dns.de
+
+// Tailscale Inc. : https://www.tailscale.com
+// Submitted by David Anderson <danderson@tailscale.com>
+beta.tailscale.net
+ts.net
+*.c.ts.net
+
+// TASK geographical domains (www.task.gda.pl/uslugi/dns)
+gda.pl
+gdansk.pl
+gdynia.pl
+med.pl
+sopot.pl
+
+// team.blue https://team.blue
+// Submitted by Cedric Dubois <cedric.dubois@team.blue>
+site.tb-hosting.com
+
+// Teckids e.V. : https://www.teckids.org
+// Submitted by Dominik George <dominik.george@teckids.org>
+edugit.io
+s3.teckids.org
+
+// Telebit : https://telebit.cloud
+// Submitted by AJ ONeal <aj@telebit.cloud>
+telebit.app
+telebit.io
+*.telebit.xyz
+
+// Thingdust AG : https://thingdust.com/
+// Submitted by Adrian Imboden <adi@thingdust.com>
+*.firenet.ch
+*.svc.firenet.ch
+reservd.com
+thingdustdata.com
+cust.dev.thingdust.io
+cust.disrec.thingdust.io
+cust.prod.thingdust.io
+cust.testing.thingdust.io
+reservd.dev.thingdust.io
+reservd.disrec.thingdust.io
+reservd.testing.thingdust.io
+
+// ticket i/O GmbH : https://ticket.io
+// Submitted by Christian Franke <it@ticket.io>
+tickets.io
+
+// Tlon.io : https://tlon.io
+// Submitted by Mark Staarink <mark@tlon.io>
+arvo.network
+azimuth.network
+tlon.network
+
+// Tor Project, Inc. : https://torproject.org
+// Submitted by Antoine Beaupré <anarcat@torproject.org
+torproject.net
+pages.torproject.net
+
+// TownNews.com : http://www.townnews.com
+// Submitted by Dustin Ward <dward@townnews.com>
+bloxcms.com
+townnews-staging.com
+
+// TrafficPlex GmbH : https://www.trafficplex.de/
+// Submitted by Phillipp Röll <phillipp.roell@trafficplex.de>
+12hp.at
+2ix.at
+4lima.at
+lima-city.at
+12hp.ch
+2ix.ch
+4lima.ch
+lima-city.ch
+trafficplex.cloud
+de.cool
+12hp.de
+2ix.de
+4lima.de
+lima-city.de
+1337.pictures
+clan.rip
+lima-city.rocks
+webspace.rocks
+lima.zone
+
+// TransIP : https://www.transip.nl
+// Submitted by Rory Breuk <rbreuk@transip.nl>
+*.transurl.be
+*.transurl.eu
+*.transurl.nl
+
+// TransIP: https://www.transip.nl
+// Submitted by Cedric Dubois <cedric.dubois@team.blue>
+site.transip.me
+
+// TuxFamily : http://tuxfamily.org
+// Submitted by TuxFamily administrators <adm@staff.tuxfamily.org>
+tuxfamily.org
+
+// TwoDNS : https://www.twodns.de/
+// Submitted by TwoDNS-Support <support@two-dns.de>
+dd-dns.de
+diskstation.eu
+diskstation.org
+dray-dns.de
+draydns.de
+dyn-vpn.de
+dynvpn.de
+mein-vigor.de
+my-vigor.de
+my-wan.de
+syno-ds.de
+synology-diskstation.de
+synology-ds.de
+
+// Typedream : https://typedream.com
+// Submitted by Putri Karunia <putri@typedream.com>
+typedream.app
+
+// Typeform : https://www.typeform.com
+// Submitted by Sergi Ferriz <sergi.ferriz@typeform.com>
+pro.typeform.com
+
+// Uberspace : https://uberspace.de
+// Submitted by Moritz Werner <mwerner@jonaspasche.com>
+uber.space
+*.uberspace.de
+
+// UDR Limited : http://www.udr.hk.com
+// Submitted by registry <hostmaster@udr.hk.com>
+hk.com
+hk.org
+ltd.hk
+inc.hk
+
+// UK Intis Telecom LTD : https://it.com
+// Submitted by ITComdomains <to@it.com>
+it.com
+
+// UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/
+// see also: whois -h whois.udr.org.yt help
+// Submitted by Atanunu Igbunuroghene <publicsuffixlist@udr.org.yt>
+name.pm
+sch.tf
+biz.wf
+sch.wf
+org.yt
+
+// United Gameserver GmbH : https://united-gameserver.de
+// Submitted by Stefan Schwarz <sysadm@united-gameserver.de>
+virtualuser.de
+virtual-user.de
+
+// Upli : https://upli.io
+// Submitted by Lenny Bakkalian <lenny.bakkalian@gmail.com>
+upli.io
+
+// urown.net : https://urown.net
+// Submitted by Hostmaster <hostmaster@urown.net>
+urown.cloud
+dnsupdate.info
+
+// .US
+// Submitted by Ed Moore <Ed.Moore@lib.de.us>
+lib.de.us
+
+// VeryPositive SIA : http://very.lv
+// Submitted by Danko Aleksejevs <danko@very.lv>
+2038.io
+
+// Vercel, Inc : https://vercel.com/
+// Submitted by Connor Davis <security@vercel.com>
+vercel.app
+vercel.dev
+now.sh
+
+// Viprinet Europe GmbH : http://www.viprinet.com
+// Submitted by Simon Kissel <hostmaster@viprinet.com>
+router.management
+
+// Virtual-Info : https://www.virtual-info.info/
+// Submitted by Adnan RIHAN <hostmaster@v-info.info>
+v-info.info
+
+// Voorloper.com: https://voorloper.com
+// Submitted by Nathan van Bakel <info@voorloper.com>
+voorloper.cloud
+
+// Voxel.sh DNS : https://voxel.sh/dns/
+// Submitted by Mia Rehlinger <dns@voxel.sh>
+neko.am
+nyaa.am
+be.ax
+cat.ax
+es.ax
+eu.ax
+gg.ax
+mc.ax
+us.ax
+xy.ax
+nl.ci
+xx.gl
+app.gp
+blog.gt
+de.gt
+to.gt
+be.gy
+cc.hn
+io.kg
+jp.kg
+tv.kg
+uk.kg
+us.kg
+de.ls
+at.md
+de.md
+jp.md
+to.md
+indie.porn
+vxl.sh
+ch.tc
+me.tc
+we.tc
+nyan.to
+at.vg
+blog.vu
+dev.vu
+me.vu
+
+// V.UA Domain Administrator : https://domain.v.ua/
+// Submitted by Serhii Rostilo <sergey@rostilo.kiev.ua>
+v.ua
+
+// Vultr Objects : https://www.vultr.com/products/object-storage/
+// Submitted by Niels Maumenee <storage@vultr.com>
+*.vultrobjects.com
+
+// Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com
+// Submitted by Masayuki Note <masa@blade.wafflecell.com>
+wafflecell.com
+
+// Webflow, Inc. : https://www.webflow.com
+// Submitted by Webflow Security Team <security@webflow.com>
+webflow.io
+webflowtest.io
+
+// WebHare bv: https://www.webhare.com/
+// Submitted by Arnold Hendriks <info@webhare.com>
+*.webhare.dev
+
+// WebHotelier Technologies Ltd: https://www.webhotelier.net/
+// Submitted by Apostolos Tsakpinis <apostolos.tsakpinis@gmail.com>
+reserve-online.net
+reserve-online.com
+bookonline.app
+hotelwithflight.com
+
+// WeDeploy by Liferay, Inc. : https://www.wedeploy.com
+// Submitted by Henrique Vicente <security@wedeploy.com>
+wedeploy.io
+wedeploy.me
+wedeploy.sh
+
+// Western Digital Technologies, Inc : https://www.wdc.com
+// Submitted by Jung Jin <jungseok.jin@wdc.com>
+remotewd.com
+
+// WIARD Enterprises : https://wiardweb.com
+// Submitted by Kidd Hustle <kiddhustle@wiardweb.com>
+pages.wiardweb.com
+
+// Wikimedia Labs : https://wikitech.wikimedia.org
+// Submitted by Arturo Borrero Gonzalez <aborrero@wikimedia.org>
+wmflabs.org
+toolforge.org
+wmcloud.org
+
+// WISP : https://wisp.gg
+// Submitted by Stepan Fedotov <stepan@wisp.gg>
+panel.gg
+daemon.panel.gg
+
+// Wizard Zines : https://wizardzines.com
+// Submitted by Julia Evans <julia@wizardzines.com>
+messwithdns.com
+
+// WoltLab GmbH : https://www.woltlab.com
+// Submitted by Tim Düsterhus <security@woltlab.cloud>
+woltlab-demo.com
+myforum.community
+community-pro.de
+diskussionsbereich.de
+community-pro.net
+meinforum.net
+
+// Woods Valldata : https://www.woodsvalldata.co.uk/
+// Submitted by Chris Whittle <chris.whittle@woodsvalldata.co.uk>
+affinitylottery.org.uk
+raffleentry.org.uk
+weeklylottery.org.uk
+
+// WP Engine : https://wpengine.com/
+// Submitted by Michael Smith <michael.smith@wpengine.com>
+// Submitted by Brandon DuRette <brandon.durette@wpengine.com>
+wpenginepowered.com
+js.wpenginepowered.com
+
+// Wix.com, Inc. : https://www.wix.com
+// Submitted by Shahar Talmi <shahar@wix.com>
+wixsite.com
+editorx.io
+wixstudio.io
+wix.run
+
+// XenonCloud GbR: https://xenoncloud.net
+// Submitted by Julian Uphoff <publicsuffixlist@xenoncloud.net>
+half.host
+
+// XnBay Technology : http://www.xnbay.com/
+// Submitted by XnBay Developer <developer.xncloud@gmail.com>
+xnbay.com
+u2.xnbay.com
+u2-local.xnbay.com
+
+// XS4ALL Internet bv : https://www.xs4all.nl/
+// Submitted by Daniel Mostertman <unixbeheer+publicsuffix@xs4all.net>
+cistron.nl
+demon.nl
+xs4all.space
+
+// Yandex.Cloud LLC: https://cloud.yandex.com
+// Submitted by Alexander Lodin <security+psl@yandex-team.ru>
+yandexcloud.net
+storage.yandexcloud.net
+website.yandexcloud.net
+
+// YesCourse Pty Ltd : https://yescourse.com
+// Submitted by Atul Bhouraskar <atul@yescourse.com>
+official.academy
+
+// Yola : https://www.yola.com/
+// Submitted by Stefano Rivera <stefano@yola.com>
+yolasite.com
+
+// Yombo : https://yombo.net
+// Submitted by Mitch Schwenk <mitch@yombo.net>
+ybo.faith
+yombo.me
+homelink.one
+ybo.party
+ybo.review
+ybo.science
+ybo.trade
+
+// Yunohost : https://yunohost.org
+// Submitted by Valentin Grimaud <security@yunohost.org>
+ynh.fr
+nohost.me
+noho.st
+
+// ZaNiC : http://www.za.net/
+// Submitted by registry <hostmaster@nic.za.net>
+za.net
+za.org
+
+// ZAP-Hosting GmbH & Co. KG : https://zap-hosting.com
+// Submitted by Julian Alker <security@zap-hosting.com>
+zap.cloud
+
+// Zine EOOD : https://zine.bg/
+// Submitted by Martin Angelov <martin@zine.bg>
+bss.design
+
+// Zitcom A/S : https://www.zitcom.dk
+// Submitted by Emil Stahl <esp@zitcom.dk>
+basicserver.io
+virtualserver.io
+enterprisecloud.nu
+
+// ===END PRIVATE DOMAINS===
diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build
new file mode 100644
index 0000000000..c926d14707
--- /dev/null
+++ b/netwerk/dns/moz.build
@@ -0,0 +1,122 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: DNS")
+
+DIRS += ["tests"]
+
+XPIDL_SOURCES += [
+ "nsIDNSAdditionalInfo.idl",
+ "nsIDNSByTypeRecord.idl",
+ "nsIDNSListener.idl",
+ "nsIDNSRecord.idl",
+ "nsIDNSService.idl",
+ "nsIEffectiveTLDService.idl",
+ "nsIIDNService.idl",
+ "nsINativeDNSResolverOverride.idl",
+ "nsITRRSkipReason.idl",
+ "nsPIDNSService.idl",
+]
+
+XPIDL_MODULE = "necko_dns"
+
+EXTRA_JS_MODULES["netwerk-dns"] += [
+ "PublicSuffixList.sys.mjs",
+]
+
+XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.toml"]
+
+EXPORTS += [
+ "nsEffectiveTLDService.h",
+]
+
+EXPORTS.mozilla.net += [
+ "ChildDNSService.h",
+ "DNS.h",
+ "DNSByTypeRecord.h",
+ "DNSListenerProxy.h",
+ "DNSPacket.h",
+ "DNSRequestBase.h",
+ "DNSRequestChild.h",
+ "DNSRequestParent.h",
+ "DNSServiceBase.h",
+ "HTTPSSVC.h",
+ "IDNBlocklistUtils.h",
+ "NativeDNSResolverOverrideChild.h",
+ "NativeDNSResolverOverrideParent.h",
+ "TRRService.h",
+ "TRRServiceBase.h",
+ "TRRServiceChild.h",
+ "TRRServiceParent.h",
+]
+
+SOURCES += [
+ "GetAddrInfo.cpp", # Undefines UNICODE
+ "nsEffectiveTLDService.cpp", # Excluded from UNIFIED_SOURCES due to special build flags.
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ SOURCES += ["PlatformDNSWin.cpp"]
+elif CONFIG["OS_TARGET"] == "Linux" or CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += ["PlatformDNSUnix.cpp"]
+ OS_LIBS += ["resolv"]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ SOURCES += ["PlatformDNSAndroid.cpp"]
+else:
+ DEFINES["MOZ_NO_HTTPS_IMPL"] = 1
+
+UNIFIED_SOURCES += [
+ "ChildDNSService.cpp",
+ "DNS.cpp",
+ "DNSAdditionalInfo.cpp",
+ "DNSListenerProxy.cpp",
+ "DNSPacket.cpp",
+ "DNSRequestChild.cpp",
+ "DNSRequestParent.cpp",
+ "DNSServiceBase.cpp",
+ "DNSUtils.cpp",
+ "HostRecordQueue.cpp",
+ "HTTPSSVC.cpp",
+ "IDNBlocklistUtils.cpp",
+ "NativeDNSResolverOverrideChild.cpp",
+ "NativeDNSResolverOverrideParent.cpp",
+ "nsDNSService2.cpp",
+ "nsHostRecord.cpp",
+ "nsHostResolver.cpp",
+ "nsIDNService.cpp",
+ "punycode.c",
+ "TRR.cpp",
+ "TRRQuery.cpp",
+ "TRRService.cpp",
+ "TRRServiceBase.cpp",
+ "TRRServiceChild.cpp",
+ "TRRServiceParent.cpp",
+]
+
+IPDL_SOURCES = [
+ "PDNSRequest.ipdl",
+ "PDNSRequestParams.ipdlh",
+ "PNativeDNSResolverOverride.ipdl",
+ "PTRRService.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+GeneratedFile(
+ "etld_data.inc", script="prepare_tlds.py", inputs=["effective_tld_names.dat"]
+)
+
+# need to include etld_data.inc
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/ipc",
+ "/netwerk/protocol/http",
+]
+
+USE_LIBS += ["icu"]
diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp
new file mode 100644
index 0000000000..68a76a0c1f
--- /dev/null
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -0,0 +1,1792 @@
+/* -*- 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 "nsDNSService2.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsICancelable.h"
+#include "nsIPrefBranch.h"
+#include "nsIOService.h"
+#include "nsIXPConnect.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "nsDNSPrefetch.h"
+#include "nsThreadUtils.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIObliviousHttp.h"
+#include "prsystem.h"
+#include "prnetdb.h"
+#include "prmon.h"
+#include "prio.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsNetAddr.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsIObserverService.h"
+#include "nsINetworkLinkService.h"
+#include "DNSAdditionalInfo.h"
+#include "TRRService.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/ChildDNSService.h"
+#include "mozilla/net/DNSListenerProxy.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+static const char kPrefDnsCacheEntries[] = "network.dnsCacheEntries";
+static const char kPrefDnsCacheExpiration[] = "network.dnsCacheExpiration";
+static const char kPrefDnsCacheGrace[] =
+ "network.dnsCacheExpirationGracePeriod";
+static const char kPrefIPv4OnlyDomains[] = "network.dns.ipv4OnlyDomains";
+static const char kPrefBlockDotOnion[] = "network.dns.blockDotOnion";
+static const char kPrefDnsLocalDomains[] = "network.dns.localDomains";
+static const char kPrefDnsForceResolve[] = "network.dns.forceResolve";
+static const char kPrefDnsOfflineLocalhost[] = "network.dns.offline-localhost";
+static const char kPrefDnsNotifyResolution[] = "network.dns.notifyResolution";
+
+//-----------------------------------------------------------------------------
+
+class nsDNSRecord : public nsIDNSAddrRecord {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+ NS_DECL_NSIDNSADDRRECORD
+
+ explicit nsDNSRecord(nsHostRecord* hostRecord) {
+ mHostRecord = do_QueryObject(hostRecord);
+ }
+
+ private:
+ virtual ~nsDNSRecord() = default;
+
+ RefPtr<AddrHostRecord> mHostRecord;
+ // Since mIter is holding a weak reference to the NetAddr array we must
+ // make sure it is not released. So we also keep a RefPtr to the AddrInfo
+ // which is immutable.
+ RefPtr<AddrInfo> mAddrInfo;
+ nsTArray<NetAddr>::const_iterator mIter;
+ const NetAddr* iter() {
+ if (!mIter.GetArray()) {
+ return nullptr;
+ }
+ if (mIter.GetArray()->end() == mIter) {
+ return nullptr;
+ }
+ return &*mIter;
+ }
+
+ int mIterGenCnt = -1; // the generation count of
+ // mHostRecord->addr_info when we
+ // start iterating
+ bool mDone = false;
+};
+
+NS_IMPL_ISUPPORTS(nsDNSRecord, nsIDNSRecord, nsIDNSAddrRecord)
+
+NS_IMETHODIMP
+nsDNSRecord::GetCanonicalName(nsACString& result) {
+ // this method should only be called if we have a CNAME
+ NS_ENSURE_TRUE(mHostRecord->flags & nsHostResolver::RES_CANON_NAME,
+ NS_ERROR_NOT_AVAILABLE);
+
+ MutexAutoLock lock(mHostRecord->addr_info_lock);
+
+ // if the record is for an IP address literal, then the canonical
+ // host name is the IP address literal.
+ if (!mHostRecord->addr_info) {
+ result = mHostRecord->host;
+ return NS_OK;
+ }
+
+ if (mHostRecord->addr_info->CanonicalHostname().IsEmpty()) {
+ result = mHostRecord->addr_info->Hostname();
+ } else {
+ result = mHostRecord->addr_info->CanonicalHostname();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::IsTRR(bool* retval) {
+ MutexAutoLock lock(mHostRecord->addr_info_lock);
+ if (mHostRecord->addr_info) {
+ *retval = mHostRecord->addr_info->IsTRR();
+ } else {
+ *retval = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::ResolvedInSocketProcess(bool* retval) {
+ *retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetTrrFetchDuration(double* aTime) {
+ MutexAutoLock lock(mHostRecord->addr_info_lock);
+ if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) {
+ *aTime = mHostRecord->addr_info->GetTrrFetchDuration();
+ } else {
+ *aTime = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetTrrFetchDurationNetworkOnly(double* aTime) {
+ MutexAutoLock lock(mHostRecord->addr_info_lock);
+ if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) {
+ *aTime = mHostRecord->addr_info->GetTrrFetchDurationNetworkOnly();
+ } else {
+ *aTime = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetNextAddr(uint16_t port, NetAddr* addr) {
+ if (mDone) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mHostRecord->addr_info_lock.Lock();
+ if (mHostRecord->addr_info) {
+ if (mIterGenCnt != mHostRecord->addr_info_gencnt) {
+ // mHostRecord->addr_info has changed, restart the iteration.
+ mIter = nsTArray<NetAddr>::const_iterator();
+ mIterGenCnt = mHostRecord->addr_info_gencnt;
+ // Make sure to hold a RefPtr to the AddrInfo so we can iterate through
+ // the NetAddr array.
+ mAddrInfo = mHostRecord->addr_info;
+ }
+
+ bool startedFresh = !iter();
+
+ do {
+ if (!iter()) {
+ mIter = mAddrInfo->Addresses().begin();
+ } else {
+ mIter++;
+ }
+ } while (iter() && mHostRecord->Blocklisted(iter()));
+
+ if (!iter() && startedFresh) {
+ // If everything was blocklisted we want to reset the blocklist (and
+ // likely relearn it) and return the first address. That is better
+ // than nothing.
+ mHostRecord->ResetBlocklist();
+ mIter = mAddrInfo->Addresses().begin();
+ }
+
+ if (iter()) {
+ *addr = *mIter;
+ }
+
+ mHostRecord->addr_info_lock.Unlock();
+
+ if (!iter()) {
+ mDone = true;
+ mIter = nsTArray<NetAddr>::const_iterator();
+ mAddrInfo = nullptr;
+ mIterGenCnt = -1;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ } else {
+ mHostRecord->addr_info_lock.Unlock();
+
+ if (!mHostRecord->addr) {
+ // Both mHostRecord->addr_info and mHostRecord->addr are null.
+ // This can happen if mHostRecord->addr_info expired and the
+ // attempt to reresolve it failed.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ memcpy(addr, mHostRecord->addr.get(), sizeof(NetAddr));
+ mDone = true;
+ }
+
+ // set given port
+ port = htons(port);
+ if (addr->raw.family == AF_INET) {
+ addr->inet.port = port;
+ } else if (addr->raw.family == AF_INET6) {
+ addr->inet6.port = port;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetAddresses(nsTArray<NetAddr>& aAddressArray) {
+ if (mDone) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mHostRecord->addr_info_lock.Lock();
+ if (mHostRecord->addr_info) {
+ for (const auto& address : mHostRecord->addr_info->Addresses()) {
+ if (mHostRecord->Blocklisted(&address)) {
+ continue;
+ }
+ NetAddr* addr = aAddressArray.AppendElement(address);
+ if (addr->raw.family == AF_INET) {
+ addr->inet.port = 0;
+ } else if (addr->raw.family == AF_INET6) {
+ addr->inet6.port = 0;
+ }
+ }
+ mHostRecord->addr_info_lock.Unlock();
+ } else {
+ mHostRecord->addr_info_lock.Unlock();
+
+ if (!mHostRecord->addr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ NetAddr* addr = aAddressArray.AppendElement(NetAddr());
+ memcpy(addr, mHostRecord->addr.get(), sizeof(NetAddr));
+ if (addr->raw.family == AF_INET) {
+ addr->inet.port = 0;
+ } else if (addr->raw.family == AF_INET6) {
+ addr->inet6.port = 0;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetScriptableNextAddr(uint16_t port, nsINetAddr** result) {
+ NetAddr addr;
+ nsresult rv = GetNextAddr(port, &addr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&addr);
+ netaddr.forget(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetNextAddrAsString(nsACString& result) {
+ NetAddr addr;
+ nsresult rv = GetNextAddr(0, &addr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ char buf[kIPv6CStrBufSize];
+ if (addr.ToStringBuffer(buf, sizeof(buf))) {
+ result.Assign(buf);
+ return NS_OK;
+ }
+ NS_ERROR("NetAddrToString failed unexpectedly");
+ return NS_ERROR_FAILURE; // conversion failed for some reason
+}
+
+NS_IMETHODIMP
+nsDNSRecord::HasMore(bool* result) {
+ if (mDone) {
+ *result = false;
+ return NS_OK;
+ }
+
+ nsTArray<NetAddr>::const_iterator iterCopy = mIter;
+ int iterGenCntCopy = mIterGenCnt;
+
+ NetAddr addr;
+ *result = NS_SUCCEEDED(GetNextAddr(0, &addr));
+
+ mIter = iterCopy;
+ mIterGenCnt = iterGenCntCopy;
+ mDone = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::Rewind() {
+ mIter = nsTArray<NetAddr>::const_iterator();
+ mIterGenCnt = -1;
+ mDone = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::ReportUnusable(uint16_t aPort) {
+ // right now we don't use the port in the blocklist
+
+ MutexAutoLock lock(mHostRecord->addr_info_lock);
+
+ // Check that we are using a real addr_info (as opposed to a single
+ // constant address), and that the generation count is valid. Otherwise,
+ // ignore the report.
+
+ if (mHostRecord->addr_info && mIterGenCnt == mHostRecord->addr_info_gencnt &&
+ iter()) {
+ mHostRecord->ReportUnusable(iter());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetEffectiveTRRMode(nsIRequest::TRRMode* aMode) {
+ *aMode = mHostRecord->EffectiveTRRMode();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDNSRecord::GetTrrSkipReason(
+ nsITRRSkipReason::value* aTrrSkipReason) {
+ *aTrrSkipReason = mHostRecord->TrrSkipReason();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::GetTtl(uint32_t* aTtl) { return mHostRecord->GetTtl(aTtl); }
+
+class nsDNSByTypeRecord : public nsIDNSByTypeRecord,
+ public nsIDNSTXTRecord,
+ public nsIDNSHTTPSSVCRecord {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+ NS_DECL_NSIDNSBYTYPERECORD
+ NS_DECL_NSIDNSTXTRECORD
+ NS_DECL_NSIDNSHTTPSSVCRECORD
+
+ explicit nsDNSByTypeRecord(nsHostRecord* hostRecord) {
+ mHostRecord = do_QueryObject(hostRecord);
+ }
+
+ private:
+ virtual ~nsDNSByTypeRecord() = default;
+ RefPtr<TypeHostRecord> mHostRecord;
+};
+
+NS_IMPL_ISUPPORTS(nsDNSByTypeRecord, nsIDNSRecord, nsIDNSByTypeRecord,
+ nsIDNSTXTRecord, nsIDNSHTTPSSVCRecord)
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetType(uint32_t* aType) {
+ *aType = mHostRecord->GetType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetRecords(CopyableTArray<nsCString>& aRecords) {
+ // deep copy
+ return mHostRecord->GetRecords(aRecords);
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetRecordsAsOneString(nsACString& aRecords) {
+ // deep copy
+ return mHostRecord->GetRecordsAsOneString(aRecords);
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetRecords(nsTArray<RefPtr<nsISVCBRecord>>& aRecords) {
+ return mHostRecord->GetRecords(aRecords);
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetServiceModeRecord(bool aNoHttp2, bool aNoHttp3,
+ nsISVCBRecord** aRecord) {
+ return mHostRecord->GetServiceModeRecord(aNoHttp2, aNoHttp3, aRecord);
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetAllRecordsWithEchConfig(
+ bool aNoHttp2, bool aNoHttp3, bool* aAllRecordsHaveEchConfig,
+ bool* aAllRecordsInH3ExcludedList,
+ nsTArray<RefPtr<nsISVCBRecord>>& aResult) {
+ return mHostRecord->GetAllRecordsWithEchConfig(
+ aNoHttp2, aNoHttp3, aAllRecordsHaveEchConfig, aAllRecordsInH3ExcludedList,
+ aResult);
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetHasIPAddresses(bool* aResult) {
+ return mHostRecord->GetHasIPAddresses(aResult);
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetAllRecordsExcluded(bool* aResult) {
+ return mHostRecord->GetAllRecordsExcluded(aResult);
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetResults(mozilla::net::TypeRecordResultType* aResults) {
+ *aResults = mHostRecord->GetResults();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetTtl(uint32_t* aTtl) { return mHostRecord->GetTtl(aTtl); }
+
+//-----------------------------------------------------------------------------
+
+class nsDNSAsyncRequest final : public nsResolveHostCallback,
+ public nsICancelable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ nsDNSAsyncRequest(nsHostResolver* res, const nsACString& host,
+ const nsACString& trrServer, uint16_t type,
+ const OriginAttributes& attrs, nsIDNSListener* listener,
+ nsIDNSService::DNSFlags flags, uint16_t af)
+ : mResolver(res),
+ mHost(host),
+ mTrrServer(trrServer),
+ mType(type),
+ mOriginAttributes(attrs),
+ mListener(listener),
+ mFlags(flags),
+ mAF(af) {}
+
+ void OnResolveHostComplete(nsHostResolver*, nsHostRecord*, nsresult) override;
+ // Returns TRUE if the DNS listener arg is the same as the member listener
+ // Used in Cancellations to remove DNS requests associated with a
+ // particular hostname and nsIDNSListener
+ bool EqualsAsyncListener(nsIDNSListener* aListener) override;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override;
+
+ RefPtr<nsHostResolver> mResolver;
+ nsCString mHost; // hostname we're resolving
+ nsCString mTrrServer; // A trr server to be used.
+ uint16_t mType = 0;
+ const OriginAttributes
+ mOriginAttributes; // The originAttributes for this resolving
+ nsCOMPtr<nsIDNSListener> mListener;
+ nsIDNSService::DNSFlags mFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ uint16_t mAF = 0;
+
+ private:
+ virtual ~nsDNSAsyncRequest() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable)
+
+void nsDNSAsyncRequest::OnResolveHostComplete(nsHostResolver* resolver,
+ nsHostRecord* hostRecord,
+ nsresult status) {
+ // need to have an owning ref when we issue the callback to enable
+ // the caller to be able to addref/release multiple times without
+ // destroying the record prematurely.
+ nsCOMPtr<nsIDNSRecord> rec;
+ if (NS_SUCCEEDED(status) ||
+ mFlags & nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR) {
+ MOZ_ASSERT(hostRecord, "no host record");
+ if (hostRecord->type != nsDNSService::RESOLVE_TYPE_DEFAULT) {
+ rec = new nsDNSByTypeRecord(hostRecord);
+ } else {
+ rec = new nsDNSRecord(hostRecord);
+ }
+ }
+
+ mListener->OnLookupComplete(this, rec, status);
+ mListener = nullptr;
+}
+
+bool nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener* aListener) {
+ uintptr_t originalListenerAddr = reinterpret_cast<uintptr_t>(mListener.get());
+ RefPtr<DNSListenerProxy> wrapper = do_QueryObject(mListener);
+ if (wrapper) {
+ originalListenerAddr = wrapper->GetOriginalListenerAddress();
+ }
+
+ uintptr_t listenerAddr = reinterpret_cast<uintptr_t>(aListener);
+ return (listenerAddr == originalListenerAddr);
+}
+
+size_t nsDNSAsyncRequest::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+
+ // The following fields aren't measured.
+ // - mHost, because it's a non-owning pointer
+ // - mResolver, because it's a non-owning pointer
+ // - mListener, because it's a non-owning pointer
+
+ return n;
+}
+
+NS_IMETHODIMP
+nsDNSAsyncRequest::Cancel(nsresult reason) {
+ NS_ENSURE_ARG(NS_FAILED(reason));
+ MOZ_DIAGNOSTIC_ASSERT(mResolver, "mResolver should not be null");
+ mResolver->DetachCallback(mHost, mTrrServer, mType, mOriginAttributes, mFlags,
+ mAF, this, reason);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class nsDNSSyncRequest : public nsResolveHostCallback {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ public:
+ explicit nsDNSSyncRequest(PRMonitor* mon) : mMonitor(mon) {}
+
+ void OnResolveHostComplete(nsHostResolver*, nsHostRecord*, nsresult) override;
+ bool EqualsAsyncListener(nsIDNSListener* aListener) override;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override;
+
+ bool mDone = false;
+ nsresult mStatus = NS_OK;
+ RefPtr<nsHostRecord> mHostRecord;
+
+ private:
+ virtual ~nsDNSSyncRequest() = default;
+
+ PRMonitor* mMonitor = nullptr;
+};
+
+NS_IMPL_ISUPPORTS0(nsDNSSyncRequest)
+
+void nsDNSSyncRequest::OnResolveHostComplete(nsHostResolver* resolver,
+ nsHostRecord* hostRecord,
+ nsresult status) {
+ // store results, and wake up nsDNSService::Resolve to process results.
+ PR_EnterMonitor(mMonitor);
+ mDone = true;
+ mStatus = status;
+ mHostRecord = hostRecord;
+ PR_Notify(mMonitor);
+ PR_ExitMonitor(mMonitor);
+}
+
+bool nsDNSSyncRequest::EqualsAsyncListener(nsIDNSListener* aListener) {
+ // Sync request: no listener to compare
+ return false;
+}
+
+size_t nsDNSSyncRequest::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+
+ // The following fields aren't measured.
+ // - mHostRecord, because it's a non-owning pointer
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mMonitor
+
+ return n;
+}
+
+class NotifyDNSResolution : public Runnable {
+ public:
+ explicit NotifyDNSResolution(const nsACString& aHostname)
+ : mozilla::Runnable("NotifyDNSResolution"), mHostname(aHostname) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "dns-resolution-request",
+ NS_ConvertUTF8toUTF16(mHostname).get());
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCString mHostname;
+};
+
+//-----------------------------------------------------------------------------
+
+static StaticRefPtr<DNSServiceWrapper> gDNSServiceWrapper;
+
+NS_IMPL_ISUPPORTS(DNSServiceWrapper, nsIDNSService, nsPIDNSService)
+
+// static
+already_AddRefed<nsIDNSService> DNSServiceWrapper::GetSingleton() {
+ if (!gDNSServiceWrapper) {
+ gDNSServiceWrapper = new DNSServiceWrapper();
+ gDNSServiceWrapper->mDNSServiceInUse = ChildDNSService::GetSingleton();
+ if (gDNSServiceWrapper->mDNSServiceInUse) {
+ ClearOnShutdown(&gDNSServiceWrapper);
+ nsDNSPrefetch::Initialize(gDNSServiceWrapper);
+ } else {
+ gDNSServiceWrapper = nullptr;
+ }
+ }
+
+ return do_AddRef(gDNSServiceWrapper);
+}
+
+// static
+void DNSServiceWrapper::SwitchToBackupDNSService() {
+ if (!gDNSServiceWrapper) {
+ return;
+ }
+
+ gDNSServiceWrapper->mBackupDNSService = nsDNSService::GetSingleton();
+
+ MutexAutoLock lock(gDNSServiceWrapper->mLock);
+ gDNSServiceWrapper->mBackupDNSService.swap(
+ gDNSServiceWrapper->mDNSServiceInUse);
+}
+
+nsIDNSService* DNSServiceWrapper::DNSService() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MutexAutoLock lock(mLock);
+ return mDNSServiceInUse.get();
+}
+
+nsPIDNSService* DNSServiceWrapper::PIDNSService() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsPIDNSService> service = do_QueryInterface(DNSService());
+ return service.get();
+}
+
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS_INHERITED(nsDNSService, DNSServiceBase, nsIDNSService,
+ nsPIDNSService, nsIMemoryReporter)
+
+/******************************************************************************
+ * nsDNSService impl:
+ * singleton instance ctor/dtor methods
+ ******************************************************************************/
+static StaticRefPtr<nsDNSService> gDNSService;
+static Atomic<bool> gInited(false);
+
+already_AddRefed<nsIDNSService> GetOrInitDNSService() {
+ if (gInited) {
+ return nsDNSService::GetXPCOMSingleton();
+ }
+
+ nsCOMPtr<nsIDNSService> dns = nullptr;
+ auto initTask = [&dns]() { dns = do_GetService(NS_DNSSERVICE_CID); };
+ if (!NS_IsMainThread()) {
+ // Forward to the main thread synchronously.
+ RefPtr<nsIThread> mainThread = do_GetMainThread();
+ if (!mainThread) {
+ return nullptr;
+ }
+
+ SyncRunnable::DispatchToThread(
+ mainThread, NS_NewRunnableFunction("GetOrInitDNSService", initTask));
+ } else {
+ initTask();
+ }
+
+ return dns.forget();
+}
+
+already_AddRefed<nsIDNSService> nsDNSService::GetXPCOMSingleton() {
+ auto getDNSHelper = []() -> already_AddRefed<nsIDNSService> {
+ if (nsIOService::UseSocketProcess()) {
+ if (XRE_IsSocketProcess()) {
+ return GetSingleton();
+ }
+
+ if (XRE_IsParentProcess()) {
+ return DNSServiceWrapper::GetSingleton();
+ }
+
+ if (XRE_IsContentProcess()) {
+ return ChildDNSService::GetSingleton();
+ }
+
+ return nullptr;
+ }
+
+ if (XRE_IsParentProcess()) {
+ return GetSingleton();
+ }
+
+ if (XRE_IsContentProcess() || XRE_IsSocketProcess()) {
+ return ChildDNSService::GetSingleton();
+ }
+
+ return nullptr;
+ };
+
+ if (gInited) {
+ return getDNSHelper();
+ }
+
+ nsCOMPtr<nsIDNSService> dns = getDNSHelper();
+ if (dns) {
+ gInited = true;
+ }
+ return dns.forget();
+}
+
+already_AddRefed<nsDNSService> nsDNSService::GetSingleton() {
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess());
+ MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(), XRE_IsParentProcess());
+
+ if (!gDNSService) {
+ if (!NS_IsMainThread()) {
+ return nullptr;
+ }
+ gDNSService = new nsDNSService();
+ if (NS_SUCCEEDED(gDNSService->Init())) {
+ ClearOnShutdown(&gDNSService);
+ } else {
+ gDNSService = nullptr;
+ }
+ }
+
+ return do_AddRef(gDNSService);
+}
+
+void nsDNSService::ReadPrefs(const char* name) {
+ DNSServiceBase::ReadPrefs(name);
+
+ bool tmpbool;
+ uint32_t tmpint;
+ mResolverPrefsUpdated = false;
+
+ // resolver-specific prefs first
+ if (!name || !strcmp(name, kPrefDnsCacheEntries)) {
+ if (NS_SUCCEEDED(Preferences::GetUint(kPrefDnsCacheEntries, &tmpint))) {
+ if (!name || (tmpint != mResCacheEntries)) {
+ mResCacheEntries = tmpint;
+ mResolverPrefsUpdated = true;
+ }
+ }
+ }
+ if (!name || !strcmp(name, kPrefDnsCacheExpiration)) {
+ if (NS_SUCCEEDED(Preferences::GetUint(kPrefDnsCacheExpiration, &tmpint))) {
+ if (!name || (tmpint != mResCacheExpiration)) {
+ mResCacheExpiration = tmpint;
+ mResolverPrefsUpdated = true;
+ }
+ }
+ }
+ if (!name || !strcmp(name, kPrefDnsCacheGrace)) {
+ if (NS_SUCCEEDED(Preferences::GetUint(kPrefDnsCacheGrace, &tmpint))) {
+ if (!name || (tmpint != mResCacheGrace)) {
+ mResCacheGrace = tmpint;
+ mResolverPrefsUpdated = true;
+ }
+ }
+ }
+
+ // DNSservice prefs
+ if (!name || !strcmp(name, kPrefDnsOfflineLocalhost)) {
+ if (NS_SUCCEEDED(
+ Preferences::GetBool(kPrefDnsOfflineLocalhost, &tmpbool))) {
+ mOfflineLocalhost = tmpbool;
+ }
+ }
+ if (!name || !strcmp(name, kPrefBlockDotOnion)) {
+ if (NS_SUCCEEDED(Preferences::GetBool(kPrefBlockDotOnion, &tmpbool))) {
+ mBlockDotOnion = tmpbool;
+ }
+ }
+ if (!name || !strcmp(name, kPrefDnsNotifyResolution)) {
+ if (NS_SUCCEEDED(
+ Preferences::GetBool(kPrefDnsNotifyResolution, &tmpbool))) {
+ mNotifyResolution = tmpbool;
+ }
+ }
+ if (!name || !strcmp(name, kPrefIPv4OnlyDomains)) {
+ Preferences::GetCString(kPrefIPv4OnlyDomains, mIPv4OnlyDomains);
+ }
+ if (!name || !strcmp(name, kPrefDnsLocalDomains)) {
+ nsCString localDomains;
+ Preferences::GetCString(kPrefDnsLocalDomains, localDomains);
+ MutexAutoLock lock(mLock);
+ mLocalDomains.Clear();
+ for (const auto& token :
+ nsCCharSeparatedTokenizerTemplate<NS_IsAsciiWhitespace,
+ nsTokenizerFlags::SeparatorOptional>(
+ localDomains, ',')
+ .ToRange()) {
+ mLocalDomains.Insert(token);
+ }
+ }
+ if (!name || !strcmp(name, kPrefDnsForceResolve)) {
+ Preferences::GetCString(kPrefDnsForceResolve, mForceResolve);
+ mForceResolveOn = !mForceResolve.IsEmpty();
+ }
+}
+
+NS_IMETHODIMP
+nsDNSService::Init() {
+ MOZ_ASSERT(!mResolver);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ReadPrefs(nullptr);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "last-pb-context-exited", false);
+ observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ }
+
+ RefPtr<nsHostResolver> res;
+ nsresult rv = nsHostResolver::Create(mResCacheEntries, mResCacheExpiration,
+ mResCacheGrace, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv)) {
+ // now, set all of our member variables while holding the lock
+ MutexAutoLock lock(mLock);
+ mResolver = res;
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ // register as prefs observer
+ prefs->AddObserver(kPrefDnsCacheEntries, this, false);
+ prefs->AddObserver(kPrefDnsCacheExpiration, this, false);
+ prefs->AddObserver(kPrefDnsCacheGrace, this, false);
+ prefs->AddObserver(kPrefIPv4OnlyDomains, this, false);
+ prefs->AddObserver(kPrefDnsLocalDomains, this, false);
+ prefs->AddObserver(kPrefDnsForceResolve, this, false);
+ prefs->AddObserver(kPrefDnsOfflineLocalhost, this, false);
+ prefs->AddObserver(kPrefBlockDotOnion, this, false);
+ prefs->AddObserver(kPrefDnsNotifyResolution, this, false);
+ AddPrefObserver(prefs);
+ }
+
+ nsDNSPrefetch::Initialize(this);
+
+ RegisterWeakMemoryReporter(this);
+
+ nsCOMPtr<nsIObliviousHttpService> ohttpService(
+ do_GetService("@mozilla.org/network/oblivious-http-service;1"));
+
+ mTrrService = new TRRService();
+ if (NS_FAILED(mTrrService->Init())) {
+ mTrrService = nullptr;
+ }
+
+ nsCOMPtr<nsIIDNService> idn = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ mIDN = idn;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::Shutdown() {
+ UnregisterWeakMemoryReporter(this);
+
+ RefPtr<nsHostResolver> res;
+ {
+ MutexAutoLock lock(mLock);
+ res = std::move(mResolver);
+ }
+ if (res) {
+ // Shutdown outside lock.
+ res->Shutdown();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ observerService->RemoveObserver(this, "last-pb-context-exited");
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ return NS_OK;
+}
+
+bool nsDNSService::GetOffline() const {
+ bool offline = false;
+ nsCOMPtr<nsIIOService> io = do_GetService(NS_IOSERVICE_CONTRACTID);
+ if (io) {
+ io->GetOffline(&offline);
+ }
+ return offline;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetPrefetchEnabled(bool* outVal) {
+ MutexAutoLock lock(mLock);
+ *outVal = !mDisablePrefetch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::SetPrefetchEnabled(bool inVal) {
+ MutexAutoLock lock(mLock);
+ mDisablePrefetch = !inVal;
+ return NS_OK;
+}
+
+already_AddRefed<nsHostResolver> nsDNSService::GetResolverLocked() {
+ MutexAutoLock lock(mLock);
+ return do_AddRef(mResolver);
+}
+
+nsresult nsDNSService::PreprocessHostname(bool aLocalDomain,
+ const nsACString& aInput,
+ nsIIDNService* aIDN,
+ nsACString& aACE) {
+ // Enforce RFC 7686
+ if (mBlockDotOnion && StringEndsWith(aInput, ".onion"_ns)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (aLocalDomain) {
+ aACE.AssignLiteral("localhost");
+ return NS_OK;
+ }
+
+ if (mTrrService && mTrrService->MaybeBootstrap(aInput, aACE)) {
+ return NS_OK;
+ }
+
+ if (mForceResolveOn) {
+ MutexAutoLock lock(mLock);
+ if (!aInput.LowerCaseEqualsASCII("localhost") &&
+ !aInput.LowerCaseEqualsASCII("127.0.0.1")) {
+ aACE.Assign(mForceResolve);
+ return NS_OK;
+ }
+ }
+
+ if (!aIDN || IsAscii(aInput)) {
+ aACE = aInput;
+ return NS_OK;
+ }
+
+ if (!(IsUtf8(aInput) && NS_SUCCEEDED(aIDN->ConvertUTF8toACE(aInput, aACE)))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsDNSService::AsyncResolveInternal(
+ const nsACString& aHostname, uint16_t type, nsIDNSService::DNSFlags flags,
+ nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener,
+ nsIEventTarget* target_, const OriginAttributes& aOriginAttributes,
+ nsICancelable** result) {
+ // grab reference to global host resolver and IDN service. beware
+ // simultaneous shutdown!!
+ RefPtr<nsHostResolver> res;
+ nsCOMPtr<nsIIDNService> idn;
+ nsCOMPtr<nsIEventTarget> target = target_;
+ nsCOMPtr<nsIDNSListener> listener = aListener;
+ bool localDomain = false;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) {
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+ }
+
+ res = mResolver;
+ idn = mIDN;
+ localDomain = mLocalDomains.Contains(aHostname);
+ }
+
+ if (mNotifyResolution) {
+ NS_DispatchToMainThread(new NotifyDNSResolution(aHostname));
+ }
+
+ if (!res) {
+ return NS_ERROR_OFFLINE;
+ }
+
+ if ((type != RESOLVE_TYPE_DEFAULT) && (type != RESOLVE_TYPE_TXT) &&
+ (type != RESOLVE_TYPE_HTTPSSVC)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (DNSForbiddenByActiveProxy(aHostname, flags)) {
+ // nsHostResolver returns NS_ERROR_UNKNOWN_HOST for lots of reasons.
+ // We use a different error code to differentiate this failure and to make
+ // it clear(er) where this error comes from.
+ return NS_ERROR_UNKNOWN_PROXY_HOST;
+ }
+
+ nsCString hostname;
+ nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (GetOffline() &&
+ (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) {
+ flags |= RESOLVE_OFFLINE;
+ }
+
+ // make sure JS callers get notification on the main thread
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
+ if (wrappedListener && !target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+
+ if (target) {
+ listener = new DNSListenerProxy(listener, target);
+ }
+
+ uint16_t af =
+ (type != RESOLVE_TYPE_DEFAULT) ? 0 : GetAFForLookup(hostname, flags);
+
+ MOZ_ASSERT(listener);
+ RefPtr<nsDNSAsyncRequest> req =
+ new nsDNSAsyncRequest(res, hostname, DNSAdditionalInfo::URL(aInfo), type,
+ aOriginAttributes, listener, flags, af);
+ if (!req) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = res->ResolveHost(req->mHost, DNSAdditionalInfo::URL(aInfo),
+ DNSAdditionalInfo::Port(aInfo), type,
+ req->mOriginAttributes, flags, af, req);
+ req.forget(result);
+ return rv;
+}
+
+nsresult nsDNSService::CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType, nsIDNSService::DNSFlags aFlags,
+ nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener, nsresult aReason,
+ const OriginAttributes& aOriginAttributes) {
+ // grab reference to global host resolver and IDN service. beware
+ // simultaneous shutdown!!
+ RefPtr<nsHostResolver> res;
+ nsCOMPtr<nsIIDNService> idn;
+ bool localDomain = false;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+ }
+
+ res = mResolver;
+ idn = mIDN;
+ localDomain = mLocalDomains.Contains(aHostname);
+ }
+ if (!res) {
+ return NS_ERROR_OFFLINE;
+ }
+
+ nsCString hostname;
+ nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint16_t af =
+ (aType != RESOLVE_TYPE_DEFAULT) ? 0 : GetAFForLookup(hostname, aFlags);
+
+ res->CancelAsyncRequest(hostname, DNSAdditionalInfo::URL(aInfo), aType,
+ aOriginAttributes, aFlags, af, aListener, aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolve(const nsACString& aHostname,
+ nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags flags,
+ nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* listener, nsIEventTarget* target_,
+ JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx, uint8_t aArgc,
+ nsICancelable** result) {
+ OriginAttributes attrs;
+
+ if (aArgc == 1) {
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return AsyncResolveInternal(aHostname, aType, flags, aInfo, listener, target_,
+ attrs, result);
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolveNative(
+ const nsACString& aHostname, nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags flags, nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* aListener, nsIEventTarget* target_,
+ const OriginAttributes& aOriginAttributes, nsICancelable** result) {
+ return AsyncResolveInternal(aHostname, aType, flags, aInfo, aListener,
+ target_, aOriginAttributes, result);
+}
+
+NS_IMETHODIMP
+nsDNSService::NewAdditionalInfo(const nsACString& aTrrURL, int32_t aPort,
+ nsIDNSAdditionalInfo** aInfo) {
+ RefPtr<DNSAdditionalInfo> res = new DNSAdditionalInfo(aTrrURL, aPort);
+ res.forget(aInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolve(const nsACString& aHostname,
+ nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags aFlags,
+ nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* aListener, nsresult aReason,
+ JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx, uint8_t aArgc) {
+ OriginAttributes attrs;
+
+ if (aArgc == 1) {
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return CancelAsyncResolveInternal(aHostname, aType, aFlags, aInfo, aListener,
+ aReason, attrs);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolveNative(
+ const nsACString& aHostname, nsIDNSService::ResolveType aType,
+ nsIDNSService::DNSFlags aFlags, nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* aListener, nsresult aReason,
+ const OriginAttributes& aOriginAttributes) {
+ return CancelAsyncResolveInternal(aHostname, aType, aFlags, aInfo, aListener,
+ aReason, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+nsDNSService::Resolve(const nsACString& aHostname,
+ nsIDNSService::DNSFlags flags,
+ JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
+ uint8_t aArgc, nsIDNSRecord** result) {
+ OriginAttributes attrs;
+
+ if (aArgc == 1) {
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return ResolveNative(aHostname, flags, attrs, result);
+}
+
+NS_IMETHODIMP
+nsDNSService::ResolveNative(const nsACString& aHostname,
+ nsIDNSService::DNSFlags flags,
+ const OriginAttributes& aOriginAttributes,
+ nsIDNSRecord** result) {
+ // Synchronous resolution is not available on the main thread.
+ if (NS_IsMainThread()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return ResolveInternal(aHostname, flags, aOriginAttributes, result);
+}
+
+nsresult nsDNSService::DeprecatedSyncResolve(
+ const nsACString& aHostname, nsIDNSService::DNSFlags flags,
+ const OriginAttributes& aOriginAttributes, nsIDNSRecord** result) {
+ return ResolveInternal(aHostname, flags, aOriginAttributes, result);
+}
+
+nsresult nsDNSService::ResolveInternal(
+ const nsACString& aHostname, nsIDNSService::DNSFlags flags,
+ const OriginAttributes& aOriginAttributes, nsIDNSRecord** result) {
+ // grab reference to global host resolver and IDN service. beware
+ // simultaneous shutdown!!
+ RefPtr<nsHostResolver> res;
+ nsCOMPtr<nsIIDNService> idn;
+ bool localDomain = false;
+ {
+ MutexAutoLock lock(mLock);
+ res = mResolver;
+ idn = mIDN;
+ localDomain = mLocalDomains.Contains(aHostname);
+ }
+
+ if (mNotifyResolution) {
+ NS_DispatchToMainThread(new NotifyDNSResolution(aHostname));
+ }
+
+ NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE);
+
+ nsCString hostname;
+ nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (GetOffline() &&
+ (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) {
+ flags |= RESOLVE_OFFLINE;
+ }
+
+ if (DNSForbiddenByActiveProxy(aHostname, flags)) {
+ return NS_ERROR_UNKNOWN_PROXY_HOST;
+ }
+
+ //
+ // sync resolve: since the host resolver only works asynchronously, we need
+ // to use a mutex and a condvar to wait for the result. however, since the
+ // result may be in the resolvers cache, we might get called back recursively
+ // on the same thread. so, our mutex needs to be re-entrant. in other words,
+ // we need to use a monitor! ;-)
+ //
+
+ PRMonitor* mon = PR_NewMonitor();
+ if (!mon) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ PR_EnterMonitor(mon);
+ RefPtr<nsDNSSyncRequest> syncReq = new nsDNSSyncRequest(mon);
+
+ uint16_t af = GetAFForLookup(hostname, flags);
+
+ // TRR uses the main thread for the HTTPS channel to the DoH server.
+ // If this were to block the main thread while waiting for TRR it would
+ // likely cause a deadlock. Instead we intentionally choose to not use TRR
+ // for this.
+ if (NS_IsMainThread()) {
+ flags |= RESOLVE_DISABLE_TRR;
+ }
+
+ rv = res->ResolveHost(hostname, ""_ns, -1, RESOLVE_TYPE_DEFAULT,
+ aOriginAttributes, flags, af, syncReq);
+ if (NS_SUCCEEDED(rv)) {
+ // wait for result
+ while (!syncReq->mDone) {
+ PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
+ }
+
+ if (NS_FAILED(syncReq->mStatus)) {
+ rv = syncReq->mStatus;
+ } else {
+ NS_ASSERTION(syncReq->mHostRecord, "no host record");
+ RefPtr<nsDNSRecord> rec = new nsDNSRecord(syncReq->mHostRecord);
+ rec.forget(result);
+ }
+ }
+
+ PR_ExitMonitor(mon);
+ PR_DestroyMonitor(mon);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetMyHostName(nsACString& result) {
+ char name[100];
+ if (PR_GetSystemInfo(PR_SI_HOSTNAME, name, sizeof(name)) == PR_SUCCESS) {
+ result = name;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDNSService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ bool flushCache = false;
+ RefPtr<nsHostResolver> resolver = GetResolverLocked();
+
+ if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
+ if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) {
+ flushCache = true;
+ }
+ } else if (!strcmp(topic, "last-pb-context-exited")) {
+ flushCache = true;
+ } else if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ ReadPrefs(NS_ConvertUTF16toUTF8(data).get());
+ NS_ENSURE_TRUE(resolver, NS_ERROR_NOT_INITIALIZED);
+ if (mResolverPrefsUpdated && resolver) {
+ resolver->SetCacheLimits(mResCacheEntries, mResCacheExpiration,
+ mResCacheGrace);
+ }
+ } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Shutdown();
+ }
+
+ if (flushCache && resolver) {
+ resolver->FlushCache(false);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+uint16_t nsDNSService::GetAFForLookup(const nsACString& host,
+ nsIDNSService::DNSFlags flags) {
+ if (StaticPrefs::network_dns_disableIPv6() ||
+ (flags & RESOLVE_DISABLE_IPV6)) {
+ return PR_AF_INET;
+ }
+
+ MutexAutoLock lock(mLock);
+
+ uint16_t af = PR_AF_UNSPEC;
+
+ if (!mIPv4OnlyDomains.IsEmpty()) {
+ const char *domain, *domainEnd, *end;
+ uint32_t hostLen, domainLen;
+
+ // see if host is in one of the IPv4-only domains
+ domain = mIPv4OnlyDomains.BeginReading();
+ domainEnd = mIPv4OnlyDomains.EndReading();
+
+ nsACString::const_iterator hostStart;
+ host.BeginReading(hostStart);
+ hostLen = host.Length();
+
+ do {
+ // skip any whitespace
+ while (*domain == ' ' || *domain == '\t') {
+ ++domain;
+ }
+
+ // find end of this domain in the string
+ end = strchr(domain, ',');
+ if (!end) {
+ end = domainEnd;
+ }
+
+ // to see if the hostname is in the domain, check if the domain
+ // matches the end of the hostname.
+ domainLen = end - domain;
+ if (domainLen && hostLen >= domainLen) {
+ const char* hostTail = hostStart.get() + hostLen - domainLen;
+ if (nsCRT::strncasecmp(domain, hostTail, domainLen) == 0) {
+ // now, make sure either that the hostname is a direct match or
+ // that the hostname begins with a dot.
+ if (hostLen == domainLen || *hostTail == '.' ||
+ *(hostTail - 1) == '.') {
+ af = PR_AF_INET;
+ break;
+ }
+ }
+ }
+
+ domain = end + 1;
+ } while (*end);
+ }
+
+ if ((af != PR_AF_INET) && (flags & RESOLVE_DISABLE_IPV4)) {
+ af = PR_AF_INET6;
+ }
+
+ return af;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetDNSCacheEntries(
+ nsTArray<mozilla::net::DNSCacheEntries>* args) {
+ RefPtr<nsHostResolver> resolver = GetResolverLocked();
+ NS_ENSURE_TRUE(resolver, NS_ERROR_NOT_INITIALIZED);
+ resolver->GetDNSCacheEntries(args);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::ClearCache(bool aTrrToo) {
+ RefPtr<nsHostResolver> resolver = GetResolverLocked();
+ NS_ENSURE_TRUE(resolver, NS_ERROR_NOT_INITIALIZED);
+ resolver->FlushCache(aTrrToo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::ReloadParentalControlEnabled() {
+ if (mTrrService) {
+ mTrrService->mParentalControlEnabled =
+ TRRService::GetParentalControlEnabledInternal();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::SetDetectedTrrURI(const nsACString& aURI) {
+ if (mTrrService) {
+ mTrrService->SetDetectedTrrURI(aURI);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::SetHeuristicDetectionResult(nsITRRSkipReason::value aValue) {
+ if (mTrrService) {
+ mTrrService->SetHeuristicDetectionResult(aValue);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetHeuristicDetectionResult(nsITRRSkipReason::value* aValue) {
+ if (!mTrrService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aValue = mTrrService->GetHeuristicDetectionResult();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetTRRSkipReasonName(nsITRRSkipReason::value aValue,
+ nsACString& aName) {
+ return mozilla::net::GetTRRSkipReasonName(aValue, aName);
+}
+
+NS_IMETHODIMP
+nsDNSService::GetCurrentTrrURI(nsACString& aURI) {
+ if (mTrrService) {
+ mTrrService->GetURI(aURI);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetCurrentTrrMode(nsIDNSService::ResolverMode* aMode) {
+ *aMode = nsIDNSService::MODE_NATIVEONLY; // The default mode.
+ if (mTrrService) {
+ *aMode = mTrrService->Mode();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetCurrentTrrConfirmationState(uint32_t* aConfirmationState) {
+ *aConfirmationState = uint32_t(TRRService::CONFIRM_OFF);
+ if (mTrrService) {
+ *aConfirmationState = mTrrService->ConfirmationState();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetTrrDomain(nsACString& aTRRDomain) {
+ aTRRDomain.Truncate();
+ nsAutoCString url;
+ if (mTrrService) {
+ mTrrService->GetURI(url);
+ }
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
+ if (NS_FAILED(rv)) {
+ // An empty TRR domain in case of invalid URL.
+ return NS_OK;
+ }
+ return uri->GetHost(aTRRDomain);
+}
+
+nsresult nsDNSService::GetTRRDomainKey(nsACString& aTRRDomain) {
+ aTRRDomain = TRRService::ProviderKey();
+ return NS_OK;
+}
+
+size_t nsDNSService::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mIDN
+ // - mLock
+
+ size_t n = mallocSizeOf(this);
+ n += mResolver ? mResolver->SizeOfIncludingThis(mallocSizeOf) : 0;
+ n += mIPv4OnlyDomains.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mLocalDomains.SizeOfExcludingThis(mallocSizeOf);
+ n += mFailedSVCDomainNames.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& data : mFailedSVCDomainNames.Values()) {
+ n += data->ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& name : *data) {
+ n += name.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ }
+ }
+ return n;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(DNSServiceMallocSizeOf)
+
+NS_IMETHODIMP
+nsDNSService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/network/dns-service", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(DNSServiceMallocSizeOf),
+ "Memory used for the DNS service.");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::ReportFailedSVCDomainName(const nsACString& aOwnerName,
+ const nsACString& aSVCDomainName) {
+ MutexAutoLock lock(mLock);
+
+ mFailedSVCDomainNames.GetOrInsertNew(aOwnerName, 1)
+ ->AppendElement(aSVCDomainName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::IsSVCDomainNameFailed(const nsACString& aOwnerName,
+ const nsACString& aSVCDomainName,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+
+ MutexAutoLock lock(mLock);
+ *aResult = false;
+ nsTArray<nsCString>* failedList = mFailedSVCDomainNames.Get(aOwnerName);
+ if (!failedList) {
+ return NS_OK;
+ }
+
+ *aResult = failedList->Contains(aSVCDomainName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::ResetExcludedSVCDomainName(const nsACString& aOwnerName) {
+ MutexAutoLock lock(mLock);
+ mFailedSVCDomainNames.Remove(aOwnerName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetLastConfirmationStatus(nsresult* aConfirmationStatus) {
+ if (!mTrrService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aConfirmationStatus = mTrrService->LastConfirmationStatus();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDNSService::GetLastConfirmationSkipReason(
+ TRRSkippedReason* aSkipReason) {
+ if (!mTrrService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aSkipReason = mTrrService->LastConfirmationSkipReason();
+ return NS_OK;
+}
+
+namespace mozilla::net {
+nsresult GetTRRSkipReasonName(TRRSkippedReason aReason, nsACString& aName) {
+ static_assert(TRRSkippedReason::TRR_UNSET == 0);
+ static_assert(TRRSkippedReason::TRR_OK == 1);
+ static_assert(TRRSkippedReason::TRR_NO_GSERVICE == 2);
+ static_assert(TRRSkippedReason::TRR_PARENTAL_CONTROL == 3);
+ static_assert(TRRSkippedReason::TRR_OFF_EXPLICIT == 4);
+ static_assert(TRRSkippedReason::TRR_REQ_MODE_DISABLED == 5);
+ static_assert(TRRSkippedReason::TRR_MODE_NOT_ENABLED == 6);
+ static_assert(TRRSkippedReason::TRR_FAILED == 7);
+ static_assert(TRRSkippedReason::TRR_MODE_UNHANDLED_DEFAULT == 8);
+ static_assert(TRRSkippedReason::TRR_MODE_UNHANDLED_DISABLED == 9);
+ static_assert(TRRSkippedReason::TRR_DISABLED_FLAG == 10);
+ static_assert(TRRSkippedReason::TRR_TIMEOUT == 11);
+ static_assert(TRRSkippedReason::TRR_CHANNEL_DNS_FAIL == 12);
+ static_assert(TRRSkippedReason::TRR_IS_OFFLINE == 13);
+ static_assert(TRRSkippedReason::TRR_NOT_CONFIRMED == 14);
+ static_assert(TRRSkippedReason::TRR_DID_NOT_MAKE_QUERY == 15);
+ static_assert(TRRSkippedReason::TRR_UNKNOWN_CHANNEL_FAILURE == 16);
+ static_assert(TRRSkippedReason::TRR_HOST_BLOCKED_TEMPORARY == 17);
+ static_assert(TRRSkippedReason::TRR_SEND_FAILED == 18);
+ static_assert(TRRSkippedReason::TRR_NET_RESET == 19);
+ static_assert(TRRSkippedReason::TRR_NET_TIMEOUT == 20);
+ static_assert(TRRSkippedReason::TRR_NET_REFUSED == 21);
+ static_assert(TRRSkippedReason::TRR_NET_INTERRUPT == 22);
+ static_assert(TRRSkippedReason::TRR_NET_INADEQ_SEQURITY == 23);
+ static_assert(TRRSkippedReason::TRR_NO_ANSWERS == 24);
+ static_assert(TRRSkippedReason::TRR_DECODE_FAILED == 25);
+ static_assert(TRRSkippedReason::TRR_EXCLUDED == 26);
+ static_assert(TRRSkippedReason::TRR_SERVER_RESPONSE_ERR == 27);
+ static_assert(TRRSkippedReason::TRR_RCODE_FAIL == 28);
+ static_assert(TRRSkippedReason::TRR_NO_CONNECTIVITY == 29);
+ static_assert(TRRSkippedReason::TRR_NXDOMAIN == 30);
+ static_assert(TRRSkippedReason::TRR_REQ_CANCELLED == 31);
+ static_assert(TRRSkippedReason::ODOH_KEY_NOT_USABLE == 32);
+ static_assert(TRRSkippedReason::ODOH_UPDATE_KEY_FAILED == 33);
+ static_assert(TRRSkippedReason::ODOH_KEY_NOT_AVAILABLE == 34);
+ static_assert(TRRSkippedReason::ODOH_ENCRYPTION_FAILED == 35);
+ static_assert(TRRSkippedReason::ODOH_DECRYPTION_FAILED == 36);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH ==
+ 37);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH ==
+ 38);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY == 39);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_CANARY == 40);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS == 41);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS ==
+ 42);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS ==
+ 43);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY ==
+ 44);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_VPN == 45);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PROXY == 46);
+ static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_NRPT == 47);
+ static_assert(TRRSkippedReason::TRR_BAD_URL == 48);
+
+ switch (aReason) {
+ case TRRSkippedReason::TRR_UNSET:
+ aName = "TRR_UNSET"_ns;
+ break;
+ case TRRSkippedReason::TRR_OK:
+ aName = "TRR_OK"_ns;
+ break;
+ case TRRSkippedReason::TRR_NO_GSERVICE:
+ aName = "TRR_NO_GSERVICE"_ns;
+ break;
+ case TRRSkippedReason::TRR_PARENTAL_CONTROL:
+ aName = "TRR_PARENTAL_CONTROL"_ns;
+ break;
+ case TRRSkippedReason::TRR_OFF_EXPLICIT:
+ aName = "TRR_OFF_EXPLICIT"_ns;
+ break;
+ case TRRSkippedReason::TRR_REQ_MODE_DISABLED:
+ aName = "TRR_REQ_MODE_DISABLED"_ns;
+ break;
+ case TRRSkippedReason::TRR_MODE_NOT_ENABLED:
+ aName = "TRR_MODE_NOT_ENABLED"_ns;
+ break;
+ case TRRSkippedReason::TRR_FAILED:
+ aName = "TRR_FAILED"_ns;
+ break;
+ case TRRSkippedReason::TRR_MODE_UNHANDLED_DEFAULT:
+ aName = "TRR_MODE_UNHANDLED_DEFAULT"_ns;
+ break;
+ case TRRSkippedReason::TRR_MODE_UNHANDLED_DISABLED:
+ aName = "TRR_MODE_UNHANDLED_DISABLED"_ns;
+ break;
+ case TRRSkippedReason::TRR_DISABLED_FLAG:
+ aName = "TRR_DISABLED_FLAG"_ns;
+ break;
+ case TRRSkippedReason::TRR_TIMEOUT:
+ aName = "TRR_TIMEOUT"_ns;
+ break;
+ case TRRSkippedReason::TRR_CHANNEL_DNS_FAIL:
+ aName = "TRR_CHANNEL_DNS_FAIL"_ns;
+ break;
+ case TRRSkippedReason::TRR_IS_OFFLINE:
+ aName = "TRR_IS_OFFLINE"_ns;
+ break;
+ case TRRSkippedReason::TRR_NOT_CONFIRMED:
+ aName = "TRR_NOT_CONFIRMED"_ns;
+ break;
+ case TRRSkippedReason::TRR_DID_NOT_MAKE_QUERY:
+ aName = "TRR_DID_NOT_MAKE_QUERY"_ns;
+ break;
+ case TRRSkippedReason::TRR_UNKNOWN_CHANNEL_FAILURE:
+ aName = "TRR_UNKNOWN_CHANNEL_FAILURE"_ns;
+ break;
+ case TRRSkippedReason::TRR_HOST_BLOCKED_TEMPORARY:
+ aName = "TRR_HOST_BLOCKED_TEMPORARY"_ns;
+ break;
+ case TRRSkippedReason::TRR_SEND_FAILED:
+ aName = "TRR_SEND_FAILED"_ns;
+ break;
+ case TRRSkippedReason::TRR_NET_RESET:
+ aName = "TRR_NET_RESET"_ns;
+ break;
+ case TRRSkippedReason::TRR_NET_TIMEOUT:
+ aName = "TRR_NET_TIMEOUT"_ns;
+ break;
+ case TRRSkippedReason::TRR_NET_REFUSED:
+ aName = "TRR_NET_REFUSED"_ns;
+ break;
+ case TRRSkippedReason::TRR_NET_INTERRUPT:
+ aName = "TRR_NET_INTERRUPT"_ns;
+ break;
+ case TRRSkippedReason::TRR_NET_INADEQ_SEQURITY:
+ aName = "TRR_NET_INADEQ_SEQURITY"_ns;
+ break;
+ case TRRSkippedReason::TRR_NO_ANSWERS:
+ aName = "TRR_NO_ANSWERS"_ns;
+ break;
+ case TRRSkippedReason::TRR_DECODE_FAILED:
+ aName = "TRR_DECODE_FAILED"_ns;
+ break;
+ case TRRSkippedReason::TRR_EXCLUDED:
+ aName = "TRR_EXCLUDED"_ns;
+ break;
+ case TRRSkippedReason::TRR_SERVER_RESPONSE_ERR:
+ aName = "TRR_SERVER_RESPONSE_ERR"_ns;
+ break;
+ case TRRSkippedReason::TRR_RCODE_FAIL:
+ aName = "TRR_RCODE_FAIL"_ns;
+ break;
+ case TRRSkippedReason::TRR_NO_CONNECTIVITY:
+ aName = "TRR_NO_CONNECTIVITY"_ns;
+ break;
+ case TRRSkippedReason::TRR_NXDOMAIN:
+ aName = "TRR_NXDOMAIN"_ns;
+ break;
+ case TRRSkippedReason::TRR_REQ_CANCELLED:
+ aName = "TRR_REQ_CANCELLED"_ns;
+ break;
+ case TRRSkippedReason::ODOH_KEY_NOT_USABLE:
+ aName = "ODOH_KEY_NOT_USABLE"_ns;
+ break;
+ case TRRSkippedReason::ODOH_UPDATE_KEY_FAILED:
+ aName = "ODOH_UPDATE_KEY_FAILED"_ns;
+ break;
+ case TRRSkippedReason::ODOH_KEY_NOT_AVAILABLE:
+ aName = "ODOH_KEY_NOT_AVAILABLE"_ns;
+ break;
+ case TRRSkippedReason::ODOH_ENCRYPTION_FAILED:
+ aName = "ODOH_ENCRYPTION_FAILED"_ns;
+ break;
+ case TRRSkippedReason::ODOH_DECRYPTION_FAILED:
+ aName = "ODOH_DECRYPTION_FAILED"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH:
+ aName = "TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH:
+ aName = "TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY:
+ aName = "TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_CANARY:
+ aName = "TRR_HEURISTIC_TRIPPED_CANARY"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS:
+ aName = "TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS:
+ aName = "TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS:
+ aName = "TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY:
+ aName = "TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_VPN:
+ aName = "TRR_HEURISTIC_TRIPPED_VPN"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PROXY:
+ aName = "TRR_HEURISTIC_TRIPPED_PROXY"_ns;
+ break;
+ case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_NRPT:
+ aName = "TRR_HEURISTIC_TRIPPED_NRPT"_ns;
+ break;
+ case TRRSkippedReason::TRR_BAD_URL:
+ aName = "TRR_BAD_URL"_ns;
+ break;
+ default:
+ MOZ_ASSERT(false, "Unknown value");
+ }
+
+ return NS_OK;
+}
+} // namespace mozilla::net
diff --git a/netwerk/dns/nsDNSService2.h b/netwerk/dns/nsDNSService2.h
new file mode 100644
index 0000000000..11f22c038e
--- /dev/null
+++ b/netwerk/dns/nsDNSService2.h
@@ -0,0 +1,135 @@
+/* -*- 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 nsDNSService2_h__
+#define nsDNSService2_h__
+
+#include "DNSServiceBase.h"
+#include "nsClassHashtable.h"
+#include "nsPIDNSService.h"
+#include "nsIIDNService.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsHostResolver.h"
+#include "nsString.h"
+#include "nsTHashSet.h"
+#include "nsHashKeys.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Attributes.h"
+#include "TRRService.h"
+
+class nsAuthSSPI;
+
+class DNSServiceWrapper final : public nsPIDNSService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_NSPIDNSSERVICE(PIDNSService()->)
+ NS_FORWARD_NSIDNSSERVICE(DNSService()->)
+
+ DNSServiceWrapper() = default;
+
+ static already_AddRefed<nsIDNSService> GetSingleton();
+ static void SwitchToBackupDNSService();
+
+ private:
+ ~DNSServiceWrapper() = default;
+ nsIDNSService* DNSService();
+ nsPIDNSService* PIDNSService();
+
+ mozilla::Mutex mLock{"DNSServiceWrapper.mLock"};
+ nsCOMPtr<nsIDNSService> mDNSServiceInUse;
+ nsCOMPtr<nsIDNSService> mBackupDNSService;
+};
+
+class nsDNSService final : public mozilla::net::DNSServiceBase,
+ public nsPIDNSService,
+ public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSPIDNSSERVICE
+ NS_DECL_NSIDNSSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsDNSService() = default;
+
+ static already_AddRefed<nsIDNSService> GetXPCOMSingleton();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ bool GetOffline() const;
+
+ protected:
+ friend class nsAuthSSPI;
+ friend class DNSServiceWrapper;
+
+ nsresult DeprecatedSyncResolve(
+ const nsACString& aHostname, nsIDNSService::DNSFlags flags,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsIDNSRecord** result);
+
+ private:
+ ~nsDNSService() = default;
+
+ void ReadPrefs(const char* name) override;
+ static already_AddRefed<nsDNSService> GetSingleton();
+
+ uint16_t GetAFForLookup(const nsACString& host,
+ nsIDNSService::DNSFlags flags);
+
+ nsresult PreprocessHostname(bool aLocalDomain, const nsACString& aInput,
+ nsIIDNService* aIDN, nsACString& aACE);
+
+ nsresult AsyncResolveInternal(
+ const nsACString& aHostname, uint16_t type, nsIDNSService::DNSFlags flags,
+ nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener,
+ nsIEventTarget* target_,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsICancelable** result);
+
+ nsresult CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType,
+ nsIDNSService::DNSFlags aFlags, nsIDNSAdditionalInfo* aInfo,
+ nsIDNSListener* aListener, nsresult aReason,
+ const mozilla::OriginAttributes& aOriginAttributes);
+
+ nsresult ResolveInternal(const nsACString& aHostname,
+ nsIDNSService::DNSFlags flags,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsIDNSRecord** result);
+
+ // Locks the mutex and returns an addreffed resolver. May return null.
+ already_AddRefed<nsHostResolver> GetResolverLocked();
+
+ RefPtr<nsHostResolver> mResolver;
+ nsCOMPtr<nsIIDNService> mIDN;
+
+ // mLock protects access to mResolver, mLocalDomains, mIPv4OnlyDomains and
+ // mFailedSVCDomainNames
+ mozilla::Mutex mLock MOZ_UNANNOTATED{"nsDNSServer.mLock"};
+
+ // mIPv4OnlyDomains is a comma-separated list of domains for which only
+ // IPv4 DNS lookups are performed. This allows the user to disable IPv6 on
+ // a per-domain basis and work around broken DNS servers. See bug 68796.
+ nsCString mIPv4OnlyDomains;
+ nsCString mForceResolve;
+ bool mBlockDotOnion = false;
+ bool mNotifyResolution = false;
+ bool mOfflineLocalhost = false;
+ bool mForceResolveOn = false;
+ nsTHashSet<nsCString> mLocalDomains;
+ RefPtr<mozilla::net::TRRService> mTrrService;
+
+ uint32_t mResCacheEntries = 0;
+ uint32_t mResCacheExpiration = 0;
+ uint32_t mResCacheGrace = 0;
+ bool mResolverPrefsUpdated = false;
+ nsClassHashtable<nsCStringHashKey, nsTArray<nsCString>> mFailedSVCDomainNames;
+};
+
+already_AddRefed<nsIDNSService> GetOrInitDNSService();
+
+#endif // nsDNSService2_h__
diff --git a/netwerk/dns/nsEffectiveTLDService.cpp b/netwerk/dns/nsEffectiveTLDService.cpp
new file mode 100644
index 0000000000..fafbc296d5
--- /dev/null
+++ b/netwerk/dns/nsEffectiveTLDService.cpp
@@ -0,0 +1,518 @@
+/* -*- 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/. */
+
+// This service reads a file of rules describing TLD-like domain names. For a
+// complete description of the expected file format and parsing rules, see
+// http://wiki.mozilla.org/Gecko:Effective_TLD_Service
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Try.h"
+
+#include "MainThreadUtils.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsEffectiveTLDService.h"
+#include "nsIFile.h"
+#include "nsIIDNService.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/net/DNS.h"
+
+namespace etld_dafsa {
+
+// Generated file that includes kDafsa
+#include "etld_data.inc"
+
+} // namespace etld_dafsa
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsEffectiveTLDService, nsIEffectiveTLDService,
+ nsIMemoryReporter, nsIObserver)
+
+// ----------------------------------------------------------------------
+
+static nsEffectiveTLDService* gService = nullptr;
+
+nsEffectiveTLDService::nsEffectiveTLDService()
+ : mGraphLock("nsEffectiveTLDService::mGraph") {
+ mGraph.emplace(etld_dafsa::kDafsa);
+}
+
+nsresult nsEffectiveTLDService::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(this, "public-suffix-list-updated", false);
+
+ if (gService) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ nsresult rv;
+ mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ gService = this;
+ RegisterWeakMemoryReporter(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsEffectiveTLDService::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ /**
+ * Signal sent from netwerk/dns/PublicSuffixList.jsm
+ * aSubject is the nsIFile object for dafsa.bin
+ * aData is the absolute path to the dafsa.bin file (not used)
+ */
+ if (aSubject && (nsCRT::strcmp(aTopic, "public-suffix-list-updated") == 0)) {
+ nsCOMPtr<nsIFile> mDafsaBinFile(do_QueryInterface(aSubject));
+ NS_ENSURE_TRUE(mDafsaBinFile, NS_ERROR_ILLEGAL_VALUE);
+
+ AutoWriteLock lock(mGraphLock);
+ // Reset mGraph with kDafsa in case reassigning to mDafsaMap fails
+ mGraph.reset();
+ mGraph.emplace(etld_dafsa::kDafsa);
+
+ mDafsaMap.reset();
+ mMruTable.Clear();
+
+ MOZ_TRY(mDafsaMap.init(mDafsaBinFile));
+
+ size_t size = mDafsaMap.size();
+ const uint8_t* remoteDafsaPtr = mDafsaMap.get<uint8_t>().get();
+
+ auto remoteDafsa = mozilla::Span(remoteDafsaPtr, size);
+
+ mGraph.reset();
+ mGraph.emplace(remoteDafsa);
+ }
+ return NS_OK;
+}
+
+nsEffectiveTLDService::~nsEffectiveTLDService() {
+ UnregisterWeakMemoryReporter(this);
+ if (mIDNService) {
+ // Only clear gService if Init() finished successfully.
+ gService = nullptr;
+ }
+}
+
+// static
+nsEffectiveTLDService* nsEffectiveTLDService::GetInstance() {
+ if (gService) {
+ return gService;
+ }
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ return nullptr;
+ }
+ MOZ_ASSERT(
+ gService,
+ "gService must have been initialized in nsEffectiveTLDService::Init");
+ return gService;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(EffectiveTLDServiceMallocSizeOf)
+
+// The amount of heap memory measured here is tiny. It used to be bigger when
+// nsEffectiveTLDService used a separate hash table instead of binary search.
+// Nonetheless, we keep this code here in anticipation of bug 1083971 which will
+// change ETLDEntries::entries to a heap-allocated array modifiable at runtime.
+NS_IMETHODIMP
+nsEffectiveTLDService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/network/effective-TLD-service", KIND_HEAP,
+ UNITS_BYTES,
+ SizeOfIncludingThis(EffectiveTLDServiceMallocSizeOf),
+ "Memory used by the effective TLD service.");
+
+ return NS_OK;
+}
+
+size_t nsEffectiveTLDService::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) {
+ size_t n = aMallocSizeOf(this);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mIDNService
+
+ return n;
+}
+
+// External function for dealing with URI's correctly.
+// Pulls out the host portion from an nsIURI, and calls through to
+// GetPublicSuffixFromHost().
+NS_IMETHODIMP
+nsEffectiveTLDService::GetPublicSuffix(nsIURI* aURI,
+ nsACString& aPublicSuffix) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString host;
+ nsresult rv = NS_GetInnermostURIHost(aURI, host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetBaseDomainInternal(host, 0, false, aPublicSuffix);
+}
+
+NS_IMETHODIMP
+nsEffectiveTLDService::GetKnownPublicSuffix(nsIURI* aURI,
+ nsACString& aPublicSuffix) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString host;
+ nsresult rv = NS_GetInnermostURIHost(aURI, host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetBaseDomainInternal(host, 0, true, aPublicSuffix);
+}
+
+// External function for dealing with URI's correctly.
+// Pulls out the host portion from an nsIURI, and calls through to
+// GetBaseDomainFromHost().
+NS_IMETHODIMP
+nsEffectiveTLDService::GetBaseDomain(nsIURI* aURI, uint32_t aAdditionalParts,
+ nsACString& aBaseDomain) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_TRUE(((int32_t)aAdditionalParts) >= 0, NS_ERROR_INVALID_ARG);
+
+ nsAutoCString host;
+ nsresult rv = NS_GetInnermostURIHost(aURI, host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetBaseDomainInternal(host, aAdditionalParts + 1, false, aBaseDomain);
+}
+
+// External function for dealing with URIs to get a schemeless site.
+// Calls through to GetBaseDomain(), handling IP addresses and aliases by
+// just returning their serialized host.
+NS_IMETHODIMP
+nsEffectiveTLDService::GetSchemelessSite(nsIURI* aURI, nsACString& aSite) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsresult rv = GetBaseDomain(aURI, 0, aSite);
+ if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ rv = nsContentUtils::GetHostOrIPv6WithBrackets(aURI, aSite);
+ }
+ return rv;
+}
+
+// External function for dealing with URIs to get site correctly.
+// Calls through to GetSchemelessSite(), and serializes with the scheme and
+// "://" prepended.
+NS_IMETHODIMP
+nsEffectiveTLDService::GetSite(nsIURI* aURI, nsACString& aSite) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString scheme;
+ nsresult rv = aURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString schemeless;
+ rv = GetSchemelessSite(aURI, schemeless);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // aURI (and thus BaseDomain) may be the string '.'. If so, fail.
+ if (schemeless.Length() == 1 && schemeless.Last() == '.') {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Reject any URIs without a host that aren't file:// URIs.
+ if (schemeless.IsEmpty() && !aURI->SchemeIs("file")) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aSite.SetCapacity(scheme.Length() + 3 + schemeless.Length());
+ aSite.Append(scheme);
+ aSite.Append("://"_ns);
+ aSite.Append(schemeless);
+
+ return NS_OK;
+}
+
+// External function for dealing with a host string directly: finds the public
+// suffix (e.g. co.uk) for the given hostname. See GetBaseDomainInternal().
+NS_IMETHODIMP
+nsEffectiveTLDService::GetPublicSuffixFromHost(const nsACString& aHostname,
+ nsACString& aPublicSuffix) {
+ // Create a mutable copy of the hostname and normalize it to ACE.
+ // This will fail if the hostname includes invalid characters.
+ nsAutoCString normHostname(aHostname);
+ nsresult rv = NormalizeHostname(normHostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetBaseDomainInternal(normHostname, 0, false, aPublicSuffix);
+}
+
+NS_IMETHODIMP
+nsEffectiveTLDService::GetKnownPublicSuffixFromHost(const nsACString& aHostname,
+ nsACString& aPublicSuffix) {
+ // Create a mutable copy of the hostname and normalize it to ACE.
+ // This will fail if the hostname includes invalid characters.
+ nsAutoCString normHostname(aHostname);
+ nsresult rv = NormalizeHostname(normHostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetBaseDomainInternal(normHostname, 0, true, aPublicSuffix);
+}
+
+// External function for dealing with a host string directly: finds the base
+// domain (e.g. www.co.uk) for the given hostname and number of subdomain parts
+// requested. See GetBaseDomainInternal().
+NS_IMETHODIMP
+nsEffectiveTLDService::GetBaseDomainFromHost(const nsACString& aHostname,
+ uint32_t aAdditionalParts,
+ nsACString& aBaseDomain) {
+ NS_ENSURE_TRUE(((int32_t)aAdditionalParts) >= 0, NS_ERROR_INVALID_ARG);
+
+ // Create a mutable copy of the hostname and normalize it to ACE.
+ // This will fail if the hostname includes invalid characters.
+ nsAutoCString normHostname(aHostname);
+ nsresult rv = NormalizeHostname(normHostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetBaseDomainInternal(normHostname, aAdditionalParts + 1, false,
+ aBaseDomain);
+}
+
+NS_IMETHODIMP
+nsEffectiveTLDService::GetNextSubDomain(const nsACString& aHostname,
+ nsACString& aBaseDomain) {
+ // Create a mutable copy of the hostname and normalize it to ACE.
+ // This will fail if the hostname includes invalid characters.
+ nsAutoCString normHostname(aHostname);
+ nsresult rv = NormalizeHostname(normHostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetBaseDomainInternal(normHostname, -1, false, aBaseDomain);
+}
+
+// Finds the base domain for a host, with requested number of additional parts.
+// This will fail, generating an error, if the host is an IPv4/IPv6 address,
+// if more subdomain parts are requested than are available, or if the hostname
+// includes characters that are not valid in a URL. Normalization is performed
+// on the host string and the result will be in UTF8.
+nsresult nsEffectiveTLDService::GetBaseDomainInternal(
+ nsCString& aHostname, int32_t aAdditionalParts, bool aOnlyKnownPublicSuffix,
+ nsACString& aBaseDomain) {
+ const int kExceptionRule = 1;
+ const int kWildcardRule = 2;
+
+ if (aHostname.IsEmpty()) {
+ return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
+ }
+
+ // chomp any trailing dot, and keep track of it for later
+ bool trailingDot = aHostname.Last() == '.';
+ if (trailingDot) {
+ aHostname.Truncate(aHostname.Length() - 1);
+ }
+
+ // check the edge cases of the host being '.' or having a second trailing '.',
+ // since subsequent checks won't catch it.
+ if (aHostname.IsEmpty() || aHostname.Last() == '.') {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Lookup in the cache if this is a normal query. This is restricted to
+ // main thread-only as the cache is not thread-safe.
+ Maybe<TldCache::Entry> entry;
+ if (aAdditionalParts == 1 && NS_IsMainThread()) {
+ auto p = mMruTable.Lookup(aHostname);
+ if (p) {
+ if (NS_FAILED(p.Data().mResult)) {
+ return p.Data().mResult;
+ }
+
+ // There was a match, just return the cached value.
+ aBaseDomain = p.Data().mBaseDomain;
+ if (trailingDot) {
+ aBaseDomain.Append('.');
+ }
+
+ return NS_OK;
+ }
+
+ entry = Some(p);
+ }
+
+ // Check if we're dealing with an IPv4/IPv6 hostname, and return
+ if (mozilla::net::HostIsIPLiteral(aHostname)) {
+ // Update the MRU table if in use.
+ if (entry) {
+ entry->Set(TLDCacheEntry{aHostname, ""_ns, NS_ERROR_HOST_IS_IP_ADDRESS});
+ }
+
+ return NS_ERROR_HOST_IS_IP_ADDRESS;
+ }
+
+ // Walk up the domain tree, most specific to least specific,
+ // looking for matches at each level. Note that a given level may
+ // have multiple attributes (e.g. IsWild() and IsNormal()).
+ const char* prevDomain = nullptr;
+ const char* currDomain = aHostname.get();
+ const char* nextDot = strchr(currDomain, '.');
+ const char* end = currDomain + aHostname.Length();
+ // Default value of *eTLD is currDomain as set in the while loop below
+ const char* eTLD = nullptr;
+ bool hasKnownPublicSuffix = false;
+ while (true) {
+ // sanity check the string we're about to look up: it should not begin
+ // with a '.'; this would mean the hostname began with a '.' or had an
+ // embedded '..' sequence.
+ if (*currDomain == '.') {
+ // Update the MRU table if in use.
+ if (entry) {
+ entry->Set(TLDCacheEntry{aHostname, ""_ns, NS_ERROR_INVALID_ARG});
+ }
+
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int result;
+ {
+ AutoReadLock lock(mGraphLock);
+ // Perform the lookup.
+ result = mGraph->Lookup(Substring(currDomain, end));
+ }
+ if (result != Dafsa::kKeyNotFound) {
+ hasKnownPublicSuffix = true;
+ if (result == kWildcardRule && prevDomain) {
+ // wildcard rules imply an eTLD one level inferior to the match.
+ eTLD = prevDomain;
+ break;
+ }
+ if (result != kExceptionRule || !nextDot) {
+ // specific match, or we've hit the top domain level
+ eTLD = currDomain;
+ break;
+ }
+ if (result == kExceptionRule) {
+ // exception rules imply an eTLD one level superior to the match.
+ eTLD = nextDot + 1;
+ break;
+ }
+ }
+
+ if (!nextDot) {
+ // we've hit the top domain level; use it by default.
+ eTLD = currDomain;
+ break;
+ }
+
+ prevDomain = currDomain;
+ currDomain = nextDot + 1;
+ nextDot = strchr(currDomain, '.');
+ }
+
+ if (aOnlyKnownPublicSuffix && !hasKnownPublicSuffix) {
+ aBaseDomain.Truncate();
+ return NS_OK;
+ }
+
+ const char *begin, *iter;
+ if (aAdditionalParts < 0) {
+ NS_ASSERTION(aAdditionalParts == -1,
+ "aAdditionalParts can't be negative and different from -1");
+
+ for (iter = aHostname.get(); iter != eTLD && *iter != '.'; iter++) {
+ ;
+ }
+
+ if (iter != eTLD) {
+ iter++;
+ }
+ if (iter != eTLD) {
+ aAdditionalParts = 0;
+ }
+ } else {
+ // count off the number of requested domains.
+ begin = aHostname.get();
+ iter = eTLD;
+
+ while (true) {
+ if (iter == begin) {
+ break;
+ }
+
+ if (*(--iter) == '.' && aAdditionalParts-- == 0) {
+ ++iter;
+ ++aAdditionalParts;
+ break;
+ }
+ }
+ }
+
+ if (aAdditionalParts != 0) {
+ // Update the MRU table if in use.
+ if (entry) {
+ entry->Set(
+ TLDCacheEntry{aHostname, ""_ns, NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS});
+ }
+
+ return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
+ }
+
+ aBaseDomain = Substring(iter, end);
+
+ // Update the MRU table if in use.
+ if (entry) {
+ entry->Set(TLDCacheEntry{aHostname, nsCString(aBaseDomain), NS_OK});
+ }
+
+ // add on the trailing dot, if applicable
+ if (trailingDot) {
+ aBaseDomain.Append('.');
+ }
+
+ return NS_OK;
+}
+
+// Normalizes the given hostname, component by component. ASCII/ACE
+// components are lower-cased, and UTF-8 components are normalized per
+// RFC 3454 and converted to ACE.
+nsresult nsEffectiveTLDService::NormalizeHostname(nsCString& aHostname) {
+ if (!IsAscii(aHostname)) {
+ nsresult rv = mIDNService->ConvertUTF8toACE(aHostname, aHostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ ToLowerCase(aHostname);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEffectiveTLDService::HasRootDomain(const nsACString& aInput,
+ const nsACString& aHost, bool* aResult) {
+ return net::HasRootDomain(aInput, aHost, aResult);
+}
diff --git a/netwerk/dns/nsEffectiveTLDService.h b/netwerk/dns/nsEffectiveTLDService.h
new file mode 100644
index 0000000000..e68156582a
--- /dev/null
+++ b/netwerk/dns/nsEffectiveTLDService.h
@@ -0,0 +1,94 @@
+//* -*- 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 EffectiveTLDService_h
+#define EffectiveTLDService_h
+
+#include "nsIEffectiveTLDService.h"
+
+#include "mozilla/AutoMemMap.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Dafsa.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/MruCache.h"
+#include "mozilla/RWLock.h"
+
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+
+class nsIIDNService;
+
+class nsEffectiveTLDService final : public nsIEffectiveTLDService,
+ public nsIObserver,
+ public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEFFECTIVETLDSERVICE
+ NS_DECL_NSIMEMORYREPORTER
+ NS_DECL_NSIOBSERVER
+
+ nsEffectiveTLDService();
+ nsresult Init();
+
+ static nsEffectiveTLDService* GetInstance();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ private:
+ nsresult GetBaseDomainInternal(nsCString& aHostname, int32_t aAdditionalParts,
+ bool aOnlyKnownPublicSuffix,
+ nsACString& aBaseDomain);
+ nsresult NormalizeHostname(nsCString& aHostname);
+ ~nsEffectiveTLDService();
+
+ // Only set in `Init()` which is called during service construction.
+ nsCOMPtr<nsIIDNService> mIDNService;
+
+ // The DAFSA provides a compact encoding of the rather large eTLD list.
+ mozilla::Maybe<mozilla::Dafsa> mGraph MOZ_GUARDED_BY(mGraphLock);
+
+ // Memory map used for a new updated dafsa
+ mozilla::loader::AutoMemMap mDafsaMap MOZ_GUARDED_BY(mGraphLock);
+
+ // Lock for mGraph and mDafsaMap
+ mozilla::RWLock mGraphLock;
+
+ // Note that the cache entries here can record entries that were cached
+ // successfully or unsuccessfully. mResult must be checked before using an
+ // entry. If it's a success error code, the cache entry is valid and can be
+ // used.
+ struct TLDCacheEntry {
+ nsCString mHost;
+ nsCString mBaseDomain;
+ nsresult mResult;
+ };
+
+ // We use a small most recently used cache to compensate for DAFSA lookups
+ // being slightly slower than a binary search on a larger table of strings.
+ //
+ // We first check the cache for a matching result and avoid a DAFSA lookup
+ // if a match is found. Otherwise we lookup the domain in the DAFSA and then
+ // cache the result. During standard browsing the same domains are repeatedly
+ // fed into |GetBaseDomainInternal| so this ends up being an effective
+ // mitigation getting about a 99% hit rate with four tabs open.
+ struct TldCache
+ : public mozilla::MruCache<nsACString, TLDCacheEntry, TldCache> {
+ static mozilla::HashNumber Hash(const nsACString& aKey) {
+ return mozilla::HashString(aKey);
+ }
+ static bool Match(const nsACString& aKey, const TLDCacheEntry& aVal) {
+ return aKey == aVal.mHost;
+ }
+ };
+
+ // NOTE: Only used on the main thread, so not guarded by mGraphLock.
+ TldCache mMruTable;
+};
+
+#endif // EffectiveTLDService_h
diff --git a/netwerk/dns/nsHostRecord.cpp b/netwerk/dns/nsHostRecord.cpp
new file mode 100644
index 0000000000..159abb6e54
--- /dev/null
+++ b/netwerk/dns/nsHostRecord.cpp
@@ -0,0 +1,598 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsHostRecord.h"
+#include "TRRQuery.h"
+// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
+#include "DNSLogging.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "TRRService.h"
+
+//----------------------------------------------------------------------------
+// this macro filters out any flags that are not used when constructing the
+// host key. the significant flags are those that would affect the resulting
+// host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
+#define RES_KEY_FLAGS(_f) \
+ ((_f) & \
+ (nsHostResolver::RES_CANON_NAME | nsHostResolver::RES_DISABLE_TRR | \
+ nsIDNSService::RESOLVE_TRR_MODE_MASK | nsHostResolver::RES_IP_HINT))
+
+#define IS_ADDR_TYPE(_type) ((_type) == nsIDNSService::RESOLVE_TYPE_DEFAULT)
+#define IS_OTHER_TYPE(_type) ((_type) != nsIDNSService::RESOLVE_TYPE_DEFAULT)
+
+//----------------------------------------------------------------------------
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+nsHostKey::nsHostKey(const nsACString& aHost, const nsACString& aTrrServer,
+ uint16_t aType, nsIDNSService::DNSFlags aFlags,
+ uint16_t aAf, bool aPb, const nsACString& aOriginsuffix)
+ : host(aHost),
+ mTrrServer(aTrrServer),
+ type(aType),
+ flags(aFlags),
+ af(aAf),
+ pb(aPb),
+ originSuffix(aOriginsuffix) {}
+
+bool nsHostKey::operator==(const nsHostKey& other) const {
+ return host == other.host && mTrrServer == other.mTrrServer &&
+ type == other.type &&
+ RES_KEY_FLAGS(flags) == RES_KEY_FLAGS(other.flags) && af == other.af &&
+ originSuffix == other.originSuffix;
+}
+
+PLDHashNumber nsHostKey::Hash() const {
+ return AddToHash(HashString(host.get()), HashString(mTrrServer.get()), type,
+ RES_KEY_FLAGS(flags), af, HashString(originSuffix.get()));
+}
+
+size_t nsHostKey::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+ n += host.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mTrrServer.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += originSuffix.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ return n;
+}
+
+//----------------------------------------------------------------------------
+// nsHostRecord
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS0(nsHostRecord)
+
+nsHostRecord::nsHostRecord(const nsHostKey& key)
+ : nsHostKey(key), mTRRQuery("nsHostRecord.mTRRQuery") {}
+
+void nsHostRecord::Invalidate() { mDoomed = true; }
+
+void nsHostRecord::Cancel() {
+ RefPtr<TRRQuery> query;
+ {
+ auto lock = mTRRQuery.Lock();
+ query.swap(lock.ref());
+ }
+
+ if (query) {
+ query->Cancel(NS_ERROR_ABORT);
+ }
+}
+
+nsHostRecord::ExpirationStatus nsHostRecord::CheckExpiration(
+ const mozilla::TimeStamp& now) const {
+ if (!mGraceStart.IsNull() && now >= mGraceStart && !mValidEnd.IsNull() &&
+ now < mValidEnd) {
+ return nsHostRecord::EXP_GRACE;
+ }
+ if (!mValidEnd.IsNull() && now < mValidEnd) {
+ return nsHostRecord::EXP_VALID;
+ }
+
+ return nsHostRecord::EXP_EXPIRED;
+}
+
+void nsHostRecord::SetExpiration(const mozilla::TimeStamp& now,
+ unsigned int valid, unsigned int grace) {
+ mValidStart = now;
+ if ((valid + grace) < 60) {
+ grace = 60 - valid;
+ LOG(("SetExpiration: artificially bumped grace to %d\n", grace));
+ }
+ mGraceStart = now + TimeDuration::FromSeconds(valid);
+ mValidEnd = now + TimeDuration::FromSeconds(valid + grace);
+ mTtl = valid;
+}
+
+void nsHostRecord::CopyExpirationTimesAndFlagsFrom(
+ const nsHostRecord* aFromHostRecord) {
+ // This is used to copy information from a cache entry to a record. All
+ // information necessary for HasUsableRecord needs to be copied.
+ mValidStart = aFromHostRecord->mValidStart;
+ mValidEnd = aFromHostRecord->mValidEnd;
+ mGraceStart = aFromHostRecord->mGraceStart;
+ mDoomed = aFromHostRecord->mDoomed;
+ mTtl = uint32_t(aFromHostRecord->mTtl);
+}
+
+bool nsHostRecord::HasUsableResult(const mozilla::TimeStamp& now,
+ nsIDNSService::DNSFlags queryFlags) const {
+ if (mDoomed) {
+ return false;
+ }
+
+ return HasUsableResultInternal(now, queryFlags);
+}
+
+//----------------------------------------------------------------------------
+// AddrHostRecord
+//----------------------------------------------------------------------------
+
+static size_t SizeOfResolveHostCallbackListExcludingHead(
+ const mozilla::LinkedList<RefPtr<nsResolveHostCallback>>& aCallbacks,
+ MallocSizeOf mallocSizeOf) {
+ size_t n = aCallbacks.sizeOfExcludingThis(mallocSizeOf);
+
+ for (const nsResolveHostCallback* t = aCallbacks.getFirst(); t;
+ t = t->getNext()) {
+ n += t->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ return n;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(AddrHostRecord, nsHostRecord, AddrHostRecord)
+
+AddrHostRecord::AddrHostRecord(const nsHostKey& key) : nsHostRecord(key) {}
+
+AddrHostRecord::~AddrHostRecord() {
+ mCallbacks.clear();
+ Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mUnusableCount);
+}
+
+bool AddrHostRecord::Blocklisted(const NetAddr* aQuery) {
+ addr_info_lock.AssertCurrentThreadOwns();
+ LOG(("Checking unusable list for host [%s], host record [%p].\n", host.get(),
+ this));
+
+ // skip the string conversion for the common case of no blocklist
+ if (!mUnusableItems.Length()) {
+ return false;
+ }
+
+ char buf[kIPv6CStrBufSize];
+ if (!aQuery->ToStringBuffer(buf, sizeof(buf))) {
+ return false;
+ }
+ nsDependentCString strQuery(buf);
+
+ for (uint32_t i = 0; i < mUnusableItems.Length(); i++) {
+ if (mUnusableItems.ElementAt(i).Equals(strQuery)) {
+ LOG(("Address [%s] is blocklisted for host [%s].\n", buf, host.get()));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void AddrHostRecord::ReportUnusable(const NetAddr* aAddress) {
+ addr_info_lock.AssertCurrentThreadOwns();
+ LOG(
+ ("Adding address to blocklist for host [%s], host record [%p]."
+ "used trr=%d\n",
+ host.get(), this, mTRRSuccess));
+
+ ++mUnusableCount;
+
+ char buf[kIPv6CStrBufSize];
+ if (aAddress->ToStringBuffer(buf, sizeof(buf))) {
+ LOG(
+ ("Successfully adding address [%s] to blocklist for host "
+ "[%s].\n",
+ buf, host.get()));
+ mUnusableItems.AppendElement(nsCString(buf));
+ }
+}
+
+void AddrHostRecord::ResetBlocklist() {
+ addr_info_lock.AssertCurrentThreadOwns();
+ LOG(("Resetting blocklist for host [%s], host record [%p].\n", host.get(),
+ this));
+ mUnusableItems.Clear();
+}
+
+size_t AddrHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+
+ n += nsHostKey::SizeOfExcludingThis(mallocSizeOf);
+ n += SizeOfResolveHostCallbackListExcludingHead(mCallbacks, mallocSizeOf);
+
+ n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0;
+ n += mallocSizeOf(addr.get());
+
+ n += mUnusableItems.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (size_t i = 0; i < mUnusableItems.Length(); i++) {
+ n += mUnusableItems[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ }
+ return n;
+}
+
+bool AddrHostRecord::HasUsableResultInternal(
+ const mozilla::TimeStamp& now, nsIDNSService::DNSFlags queryFlags) const {
+ // don't use cached negative results for high priority queries.
+ if (negative && IsHighPriority(queryFlags)) {
+ return false;
+ }
+
+ if (CheckExpiration(now) == EXP_EXPIRED) {
+ return false;
+ }
+
+ if (negative) {
+ return true;
+ }
+
+ return addr_info || addr;
+}
+
+// Returns true if the entry can be removed, or false if it should be left.
+// Sets ResolveAgain true for entries being resolved right now.
+bool AddrHostRecord::RemoveOrRefresh(bool aTrrToo) {
+ // no need to flush TRRed names, they're not resolved "locally"
+ MutexAutoLock lock(addr_info_lock);
+ if (addr_info && !aTrrToo && addr_info->IsTRR()) {
+ return false;
+ }
+ if (LoadNative()) {
+ if (!onQueue()) {
+ // The request has been passed to the OS resolver. The resultant DNS
+ // record should be considered stale and not trusted; set a flag to
+ // ensure it is called again.
+ StoreResolveAgain(true);
+ }
+ // if onQueue is true, the host entry is already added to the cache
+ // but is still pending to get resolved: just leave it in hash.
+ return false;
+ }
+ // Already resolved; not in a pending state; remove from cache
+ return true;
+}
+
+void AddrHostRecord::NotifyRetryingTrr() {
+ MOZ_ASSERT(mFirstTRRSkippedReason ==
+ mozilla::net::TRRSkippedReason::TRR_UNSET);
+
+ // Save the skip reason of our first attempt for recording telemetry later.
+ mFirstTRRSkippedReason = mTRRSkippedReason;
+ mTRRSkippedReason = mozilla::net::TRRSkippedReason::TRR_UNSET;
+}
+
+void AddrHostRecord::ResolveComplete() {
+ if (LoadNativeUsed()) {
+ if (mNativeSuccess) {
+ uint32_t millis = static_cast<uint32_t>(mNativeDuration.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::DNS_NATIVE_LOOKUP_TIME, millis);
+ }
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ mNativeSuccess ? Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::osOK
+ : Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::osFail);
+ }
+
+ if (mResolverType == DNSResolverType::TRR) {
+ if (mTRRSuccess) {
+ MOZ_DIAGNOSTIC_ASSERT(mTRRSkippedReason ==
+ mozilla::net::TRRSkippedReason::TRR_OK);
+ uint32_t millis = static_cast<uint32_t>(mTrrDuration.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::DNS_TRR_LOOKUP_TIME3,
+ TRRService::ProviderKey(), millis);
+ }
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ mTRRSuccess ? Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrOK
+ : Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrFail);
+ }
+
+ if (nsHostResolver::Mode() == nsIDNSService::MODE_TRRFIRST ||
+ nsHostResolver::Mode() == nsIDNSService::MODE_TRRONLY) {
+ MOZ_ASSERT(mTRRSkippedReason != mozilla::net::TRRSkippedReason::TRR_UNSET);
+
+ Telemetry::Accumulate(Telemetry::TRR_SKIP_REASON_TRR_FIRST2,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(mTRRSkippedReason));
+ if (!mTRRSuccess && LoadNativeUsed()) {
+ Telemetry::Accumulate(
+ mNativeSuccess ? Telemetry::TRR_SKIP_REASON_NATIVE_SUCCESS
+ : Telemetry::TRR_SKIP_REASON_NATIVE_FAILED,
+ TRRService::ProviderKey(), static_cast<uint32_t>(mTRRSkippedReason));
+ }
+
+ if (IsRelevantTRRSkipReason(mTRRSkippedReason)) {
+ Telemetry::Accumulate(Telemetry::TRR_RELEVANT_SKIP_REASON_TRR_FIRST,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(mTRRSkippedReason));
+
+ if (!mTRRSuccess && LoadNativeUsed()) {
+ Telemetry::Accumulate(
+ mNativeSuccess ? Telemetry::TRR_RELEVANT_SKIP_REASON_NATIVE_SUCCESS
+ : Telemetry::TRR_RELEVANT_SKIP_REASON_NATIVE_FAILED,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(mTRRSkippedReason));
+ }
+ }
+
+ if (StaticPrefs::network_trr_retry_on_recoverable_errors() &&
+ nsHostResolver::Mode() == nsIDNSService::MODE_TRRFIRST) {
+ nsAutoCString telemetryKey(TRRService::ProviderKey());
+
+ if (mFirstTRRSkippedReason != mozilla::net::TRRSkippedReason::TRR_UNSET) {
+ telemetryKey.AppendLiteral("|");
+ telemetryKey.AppendInt(static_cast<uint32_t>(mFirstTRRSkippedReason));
+
+ Telemetry::Accumulate(mTRRSuccess
+ ? Telemetry::TRR_SKIP_REASON_RETRY_SUCCESS
+ : Telemetry::TRR_SKIP_REASON_RETRY_FAILED,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(mFirstTRRSkippedReason));
+ }
+
+ Telemetry::Accumulate(Telemetry::TRR_SKIP_REASON_STRICT_MODE,
+ telemetryKey,
+ static_cast<uint32_t>(mTRRSkippedReason));
+
+ if (mTRRSuccess) {
+ Telemetry::Accumulate(Telemetry::TRR_ATTEMPT_COUNT,
+ TRRService::ProviderKey(), mTrrAttempts);
+ }
+ }
+ }
+
+ if (mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE) {
+ if (flags & nsIDNSService::RESOLVE_DISABLE_TRR) {
+ // TRR is disabled on request, which is a next-level back-off method.
+ Telemetry::Accumulate(Telemetry::DNS_TRR_DISABLED3,
+ TRRService::ProviderKey(), mNativeSuccess);
+ } else {
+ if (mTRRSuccess) {
+ AccumulateCategoricalKeyed(TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST4::TRR);
+ } else if (mNativeSuccess) {
+ if (mResolverType == DNSResolverType::TRR) {
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST4::NativeAfterTRR);
+ } else {
+ AccumulateCategoricalKeyed(TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST4::Native);
+ }
+ } else {
+ AccumulateCategoricalKeyed(
+ TRRService::ProviderKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST4::BothFailed);
+ }
+ }
+ }
+
+ switch (mEffectiveTRRMode) {
+ case nsIRequest::TRR_DISABLED_MODE:
+ AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::nativeOnly);
+ break;
+ case nsIRequest::TRR_FIRST_MODE:
+ AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrFirst);
+ break;
+ case nsIRequest::TRR_ONLY_MODE:
+ AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrOnly);
+ break;
+ case nsIRequest::TRR_DEFAULT_MODE:
+ MOZ_ASSERT_UNREACHABLE("We should not have a default value here");
+ break;
+ }
+
+ if (mResolverType == DNSResolverType::TRR && !mTRRSuccess && mNativeSuccess &&
+ !LoadGetTtl() && TRRService::Get()) {
+ TRRService::Get()->AddToBlocklist(nsCString(host), originSuffix, pb, true);
+ }
+}
+
+AddrHostRecord::DnsPriority AddrHostRecord::GetPriority(
+ nsIDNSService::DNSFlags aFlags) {
+ if (IsHighPriority(aFlags)) {
+ return AddrHostRecord::DNS_PRIORITY_HIGH;
+ }
+ if (IsMediumPriority(aFlags)) {
+ return AddrHostRecord::DNS_PRIORITY_MEDIUM;
+ }
+
+ return AddrHostRecord::DNS_PRIORITY_LOW;
+}
+
+nsresult AddrHostRecord::GetTtl(uint32_t* aResult) {
+ NS_ENSURE_ARG(aResult);
+ *aResult = mTtl;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// TypeHostRecord
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(TypeHostRecord, nsHostRecord, TypeHostRecord,
+ nsIDNSTXTRecord, nsIDNSHTTPSSVCRecord)
+
+TypeHostRecord::TypeHostRecord(const nsHostKey& key)
+ : nsHostRecord(key), DNSHTTPSSVCRecordBase(key.host) {}
+
+TypeHostRecord::~TypeHostRecord() { mCallbacks.clear(); }
+
+bool TypeHostRecord::HasUsableResultInternal(
+ const mozilla::TimeStamp& now, nsIDNSService::DNSFlags queryFlags) const {
+ if (CheckExpiration(now) == EXP_EXPIRED) {
+ return false;
+ }
+
+ if (negative) {
+ return true;
+ }
+
+ return !mResults.is<Nothing>();
+}
+
+bool TypeHostRecord::RefreshForNegativeResponse() const { return false; }
+
+NS_IMETHODIMP TypeHostRecord::GetRecords(CopyableTArray<nsCString>& aRecords) {
+ // deep copy
+ MutexAutoLock lock(mResultsLock);
+
+ if (!mResults.is<TypeRecordTxt>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aRecords = mResults.as<CopyableTArray<nsCString>>();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TypeHostRecord::GetRecordsAsOneString(nsACString& aRecords) {
+ // deep copy
+ MutexAutoLock lock(mResultsLock);
+
+ if (!mResults.is<TypeRecordTxt>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ auto& results = mResults.as<CopyableTArray<nsCString>>();
+ for (uint32_t i = 0; i < results.Length(); i++) {
+ aRecords.Append(results[i]);
+ }
+ return NS_OK;
+}
+
+size_t TypeHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+
+ n += nsHostKey::SizeOfExcludingThis(mallocSizeOf);
+ n += SizeOfResolveHostCallbackListExcludingHead(mCallbacks, mallocSizeOf);
+
+ return n;
+}
+
+uint32_t TypeHostRecord::GetType() {
+ MutexAutoLock lock(mResultsLock);
+
+ return mResults.match(
+ [](TypeRecordEmpty&) {
+ MOZ_ASSERT(false, "This should never be the case");
+ return nsIDNSService::RESOLVE_TYPE_DEFAULT;
+ },
+ [](TypeRecordTxt&) { return nsIDNSService::RESOLVE_TYPE_TXT; },
+ [](TypeRecordHTTPSSVC&) { return nsIDNSService::RESOLVE_TYPE_HTTPSSVC; });
+}
+
+TypeRecordResultType TypeHostRecord::GetResults() {
+ MutexAutoLock lock(mResultsLock);
+ return mResults;
+}
+
+NS_IMETHODIMP
+TypeHostRecord::GetRecords(nsTArray<RefPtr<nsISVCBRecord>>& aRecords) {
+ MutexAutoLock lock(mResultsLock);
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& results = mResults.as<TypeRecordHTTPSSVC>();
+
+ for (const SVCB& r : results) {
+ RefPtr<nsISVCBRecord> rec = new mozilla::net::SVCBRecord(r);
+ aRecords.AppendElement(rec);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TypeHostRecord::GetServiceModeRecord(bool aNoHttp2, bool aNoHttp3,
+ nsISVCBRecord** aRecord) {
+ MutexAutoLock lock(mResultsLock);
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& results = mResults.as<TypeRecordHTTPSSVC>();
+ nsCOMPtr<nsISVCBRecord> result = GetServiceModeRecordInternal(
+ aNoHttp2, aNoHttp3, results, mAllRecordsExcluded);
+ if (!result) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ result.forget(aRecord);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TypeHostRecord::GetAllRecordsWithEchConfig(
+ bool aNoHttp2, bool aNoHttp3, bool* aAllRecordsHaveEchConfig,
+ bool* aAllRecordsInH3ExcludedList,
+ nsTArray<RefPtr<nsISVCBRecord>>& aResult) {
+ MutexAutoLock lock(mResultsLock);
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& records = mResults.as<TypeRecordHTTPSSVC>();
+ GetAllRecordsWithEchConfigInternal(aNoHttp2, aNoHttp3, records,
+ aAllRecordsHaveEchConfig,
+ aAllRecordsInH3ExcludedList, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TypeHostRecord::GetHasIPAddresses(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ MutexAutoLock lock(mResultsLock);
+
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto& results = mResults.as<TypeRecordHTTPSSVC>();
+ *aResult = HasIPAddressesInternal(results);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TypeHostRecord::GetAllRecordsExcluded(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ MutexAutoLock lock(mResultsLock);
+
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = mAllRecordsExcluded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TypeHostRecord::GetTtl(uint32_t* aResult) {
+ NS_ENSURE_ARG(aResult);
+ *aResult = mTtl;
+ return NS_OK;
+}
+
+void TypeHostRecord::ResolveComplete() {
+ if (IsRelevantTRRSkipReason(mTRRSkippedReason)) {
+ Telemetry::Accumulate(
+ Telemetry::TRR_RELEVANT_SKIP_REASON_TRR_FIRST_TYPE_REC,
+ TRRService::ProviderKey(), static_cast<uint32_t>(mTRRSkippedReason));
+ }
+
+ uint32_t millis = static_cast<uint32_t>(mTrrDuration.ToMilliseconds());
+ if (mTRRSuccess) {
+ Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_SUCCEEDED_LOOKUP_TIME, millis);
+ } else {
+ Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_FAILED_LOOKUP_TIME, millis);
+ }
+}
diff --git a/netwerk/dns/nsHostRecord.h b/netwerk/dns/nsHostRecord.h
new file mode 100644
index 0000000000..c3a73907ae
--- /dev/null
+++ b/netwerk/dns/nsHostRecord.h
@@ -0,0 +1,416 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHostRecord_h__
+#define nsHostRecord_h__
+
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/net/HTTPSSVC.h"
+#include "nsIDNSService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "PLDHashTable.h"
+#include "nsITRRSkipReason.h"
+
+class nsHostRecord;
+class nsHostResolver;
+
+namespace mozilla {
+namespace net {
+class HostRecordQueue;
+class TRR;
+class TRRQuery;
+} // namespace net
+} // namespace mozilla
+
+/**
+ * This class is used to notify listeners when a ResolveHost operation is
+ * complete. Classes that derive it must implement threadsafe nsISupports
+ * to be able to use RefPtr with this class.
+ */
+class nsResolveHostCallback
+ : public mozilla::LinkedListElement<RefPtr<nsResolveHostCallback>>,
+ public nsISupports {
+ public:
+ /**
+ * OnResolveHostComplete
+ *
+ * this function is called to complete a host lookup initiated by
+ * nsHostResolver::ResolveHost. it may be invoked recursively from
+ * ResolveHost or on an unspecified background thread.
+ *
+ * NOTE: it is the responsibility of the implementor of this method
+ * to handle the callback in a thread safe manner.
+ *
+ * @param resolver
+ * nsHostResolver object associated with this result
+ * @param record
+ * the host record containing the results of the lookup
+ * @param status
+ * if successful, |record| contains non-null results
+ */
+ virtual void OnResolveHostComplete(nsHostResolver* resolver,
+ nsHostRecord* record, nsresult status) = 0;
+ /**
+ * EqualsAsyncListener
+ *
+ * Determines if the listener argument matches the listener member var.
+ * For subclasses not implementing a member listener, should return false.
+ * For subclasses having a member listener, the function should check if
+ * they are the same. Used for cases where a pointer to an object
+ * implementing nsResolveHostCallback is unknown, but a pointer to
+ * the original listener is known.
+ *
+ * @param aListener
+ * nsIDNSListener object associated with the original request
+ */
+ virtual bool EqualsAsyncListener(nsIDNSListener* aListener) = 0;
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const = 0;
+
+ protected:
+ virtual ~nsResolveHostCallback() = default;
+};
+
+struct nsHostKey {
+ const nsCString host;
+ const nsCString mTrrServer;
+ uint16_t type = 0;
+ nsIDNSService::DNSFlags flags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ uint16_t af = 0;
+ bool pb = false;
+ const nsCString originSuffix;
+ explicit nsHostKey(const nsACString& host, const nsACString& aTrrServer,
+ uint16_t type, nsIDNSService::DNSFlags flags, uint16_t af,
+ bool pb, const nsACString& originSuffix);
+ bool operator==(const nsHostKey& other) const;
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ PLDHashNumber Hash() const;
+};
+
+/**
+ * nsHostRecord - ref counted object type stored in host resolver cache.
+ */
+class nsHostRecord : public mozilla::LinkedListElement<RefPtr<nsHostRecord>>,
+ public nsHostKey,
+ public nsISupports {
+ using TRRSkippedReason = mozilla::net::TRRSkippedReason;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return 0;
+ }
+
+ // Returns the TRR mode encoded by the flags
+ nsIRequest::TRRMode TRRMode();
+
+ // Records the first reason that caused TRR to be skipped or to fail.
+ void RecordReason(TRRSkippedReason reason) {
+ if (mTRRSkippedReason == TRRSkippedReason::TRR_UNSET) {
+ mTRRSkippedReason = reason;
+ }
+ }
+
+ enum DnsPriority {
+ DNS_PRIORITY_LOW = nsIDNSService::RESOLVE_PRIORITY_LOW,
+ DNS_PRIORITY_MEDIUM = nsIDNSService::RESOLVE_PRIORITY_MEDIUM,
+ DNS_PRIORITY_HIGH,
+ };
+
+ protected:
+ friend class nsHostResolver;
+ friend class mozilla::net::HostRecordQueue;
+ friend class mozilla::net::TRR;
+ friend class mozilla::net::TRRQuery;
+
+ explicit nsHostRecord(const nsHostKey& key);
+ virtual ~nsHostRecord() = default;
+
+ // Mark hostrecord as not usable
+ void Invalidate();
+
+ enum ExpirationStatus {
+ EXP_VALID,
+ EXP_GRACE,
+ EXP_EXPIRED,
+ };
+
+ ExpirationStatus CheckExpiration(const mozilla::TimeStamp& now) const;
+
+ // Convenience function for setting the timestamps above (mValidStart,
+ // mValidEnd, and mGraceStart). valid and grace are durations in seconds.
+ void SetExpiration(const mozilla::TimeStamp& now, unsigned int valid,
+ unsigned int grace);
+ void CopyExpirationTimesAndFlagsFrom(const nsHostRecord* aFromHostRecord);
+
+ // Checks if the record is usable (not expired and has a value)
+ bool HasUsableResult(const mozilla::TimeStamp& now,
+ nsIDNSService::DNSFlags queryFlags =
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS) const;
+
+ static DnsPriority GetPriority(nsIDNSService::DNSFlags aFlags);
+
+ virtual void Cancel();
+ virtual bool HasUsableResultInternal(
+ const mozilla::TimeStamp& now,
+ nsIDNSService::DNSFlags queryFlags) const = 0;
+ virtual bool RefreshForNegativeResponse() const { return true; }
+
+ mozilla::LinkedList<RefPtr<nsResolveHostCallback>> mCallbacks;
+
+ bool IsAddrRecord() const {
+ return type == nsIDNSService::RESOLVE_TYPE_DEFAULT;
+ }
+
+ virtual void Reset() {
+ mTRRSkippedReason = TRRSkippedReason::TRR_UNSET;
+ mFirstTRRSkippedReason = TRRSkippedReason::TRR_UNSET;
+ mTrrAttempts = 0;
+ mTRRSuccess = false;
+ mNativeSuccess = false;
+ }
+
+ virtual void OnCompleteLookup() {}
+
+ virtual void ResolveComplete() = 0;
+
+ // true if pending and on the queue (not yet given to getaddrinfo())
+ bool onQueue() { return LoadNative() && isInList(); }
+
+ // When the record began being valid. Used mainly for bookkeeping.
+ mozilla::TimeStamp mValidStart;
+
+ // When the record is no longer valid (it's time of expiration)
+ mozilla::TimeStamp mValidEnd;
+
+ // When the record enters its grace period. This must be before mValidEnd.
+ // If a record is in its grace period (and not expired), it will be used
+ // but a request to refresh it will be made.
+ mozilla::TimeStamp mGraceStart;
+
+ mozilla::TimeDuration mTrrDuration;
+
+ mozilla::Atomic<uint32_t, mozilla::Relaxed> mTtl{0};
+
+ // The computed TRR mode that is actually used by the request.
+ // It is set in nsHostResolver::NameLookup and is based on the mode of the
+ // default resolver and the TRRMode encoded in the flags.
+ // The mode into account if the TRR service is disabled,
+ // parental controls are on, domain matches exclusion list, etc.
+ mozilla::Atomic<nsIRequest::TRRMode> mEffectiveTRRMode{
+ nsIRequest::TRR_DEFAULT_MODE};
+
+ mozilla::Atomic<TRRSkippedReason> mTRRSkippedReason{
+ TRRSkippedReason::TRR_UNSET};
+ TRRSkippedReason mFirstTRRSkippedReason = TRRSkippedReason::TRR_UNSET;
+
+ mozilla::DataMutex<RefPtr<mozilla::net::TRRQuery>> mTRRQuery;
+
+ // counter of outstanding resolving calls
+ mozilla::Atomic<int32_t> mResolving{0};
+
+ // Number of times we've attempted TRR. Reset when we refresh.
+ // TRR is attempted at most twice - first attempt and retry.
+ mozilla::Atomic<int32_t> mTrrAttempts{0};
+
+ // True if this record is a cache of a failed lookup. Negative cache
+ // entries are valid just like any other (though never for more than 60
+ // seconds), but a use of that negative entry forces an asynchronous refresh.
+ bool negative = false;
+
+ // Explicitly expired
+ bool mDoomed = false;
+
+ // Whether this is resolved by TRR successfully or not.
+ bool mTRRSuccess = false;
+
+ // Whether this is resolved by native resolver successfully or not.
+ bool mNativeSuccess = false;
+
+ // When the lookups of this record started and their durations
+ mozilla::TimeStamp mNativeStart;
+ mozilla::TimeDuration mNativeDuration;
+
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, (
+ // true if this record is being resolved "natively", which means that
+ // it is either on the pending queue or owned by one of the worker threads.
+ (uint16_t, Native, 1),
+ (uint16_t, NativeUsed, 1),
+ // true if off queue and contributing to mActiveAnyThreadCount
+ (uint16_t, UsingAnyThread, 1),
+ (uint16_t, GetTtl, 1),
+ (uint16_t, ResolveAgain, 1)
+ ))
+ // clang-format on
+};
+
+// b020e996-f6ab-45e5-9bf5-1da71dd0053a
+#define ADDRHOSTRECORD_IID \
+ { \
+ 0xb020e996, 0xf6ab, 0x45e5, { \
+ 0x9b, 0xf5, 0x1d, 0xa7, 0x1d, 0xd0, 0x05, 0x3a \
+ } \
+ }
+
+class AddrHostRecord final : public nsHostRecord {
+ using Mutex = mozilla::Mutex;
+ using DNSResolverType = mozilla::net::DNSResolverType;
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(ADDRHOSTRECORD_IID)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ /* a fully resolved host record has either a non-null |addr_info| or |addr|
+ * field. if |addr_info| is null, it implies that the |host| is an IP
+ * address literal. in which case, |addr| contains the parsed address.
+ * otherwise, if |addr_info| is non-null, then it contains one or many
+ * IP addresses corresponding to the given host name. if both |addr_info|
+ * and |addr| are null, then the given host has not yet been fully resolved.
+ * |af| is the address family of the record we are querying for.
+ */
+
+ /* the lock protects |addr_info| and |addr_info_gencnt| because they
+ * are mutable and accessed by the resolver worker thread and the
+ * nsDNSService2 class. |addr| doesn't change after it has been
+ * assigned a value. only the resolver worker thread modifies
+ * nsHostRecord (and only in nsHostResolver::CompleteLookup);
+ * the other threads just read it. therefore the resolver worker
+ * thread doesn't need to lock when reading |addr_info|.
+ */
+ Mutex addr_info_lock MOZ_UNANNOTATED{"AddrHostRecord.addr_info_lock"};
+ // generation count of |addr_info|
+ int addr_info_gencnt = 0;
+ RefPtr<mozilla::net::AddrInfo> addr_info;
+ mozilla::UniquePtr<mozilla::net::NetAddr> addr;
+
+ // hold addr_info_lock when calling the blocklist functions
+ bool Blocklisted(const mozilla::net::NetAddr* query);
+ void ResetBlocklist();
+ void ReportUnusable(const mozilla::net::NetAddr* aAddress);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const override;
+
+ nsIRequest::TRRMode EffectiveTRRMode() const { return mEffectiveTRRMode; }
+ nsITRRSkipReason::value TrrSkipReason() const { return mTRRSkippedReason; }
+
+ nsresult GetTtl(uint32_t* aResult);
+
+ private:
+ friend class nsHostResolver;
+ friend class mozilla::net::HostRecordQueue;
+ friend class mozilla::net::TRR;
+ friend class mozilla::net::TRRQuery;
+
+ explicit AddrHostRecord(const nsHostKey& key);
+ ~AddrHostRecord();
+
+ // Checks if the record is usable (not expired and has a value)
+ bool HasUsableResultInternal(
+ const mozilla::TimeStamp& now,
+ nsIDNSService::DNSFlags queryFlags) const override;
+
+ bool RemoveOrRefresh(bool aTrrToo); // Mark records currently being resolved
+ // as needed to resolve again.
+
+ // Saves the skip reason of a first-attempt TRR lookup and clears
+ // it to prepare for a retry attempt.
+ void NotifyRetryingTrr();
+
+ static DnsPriority GetPriority(nsIDNSService::DNSFlags aFlags);
+
+ virtual void Reset() override {
+ nsHostRecord::Reset();
+ StoreNativeUsed(false);
+ mResolverType = DNSResolverType::Native;
+ }
+
+ virtual void OnCompleteLookup() override {
+ nsHostRecord::OnCompleteLookup();
+ // This should always be cleared when a request is completed.
+ StoreNative(false);
+ }
+
+ void ResolveComplete() override;
+
+ // TRR was used on this record
+ mozilla::Atomic<DNSResolverType> mResolverType{DNSResolverType::Native};
+
+ // The number of times ReportUnusable() has been called in the record's
+ // lifetime.
+ uint32_t mUnusableCount = 0;
+
+ // a list of addresses associated with this record that have been reported
+ // as unusable. the list is kept as a set of strings to make it independent
+ // of gencnt.
+ nsTArray<nsCString> mUnusableItems;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(AddrHostRecord, ADDRHOSTRECORD_IID)
+
+// 77b786a7-04be-44f2-987c-ab8aa96676e0
+#define TYPEHOSTRECORD_IID \
+ { \
+ 0x77b786a7, 0x04be, 0x44f2, { \
+ 0x98, 0x7c, 0xab, 0x8a, 0xa9, 0x66, 0x76, 0xe0 \
+ } \
+ }
+
+class TypeHostRecord final : public nsHostRecord,
+ public nsIDNSTXTRecord,
+ public nsIDNSHTTPSSVCRecord,
+ public mozilla::net::DNSHTTPSSVCRecordBase {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(TYPEHOSTRECORD_IID)
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDNSTXTRECORD
+ NS_DECL_NSIDNSHTTPSSVCRECORD
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const override;
+ uint32_t GetType();
+ mozilla::net::TypeRecordResultType GetResults();
+
+ private:
+ friend class nsHostResolver;
+ friend class mozilla::net::TRR;
+ friend class mozilla::net::TRRQuery;
+
+ explicit TypeHostRecord(const nsHostKey& key);
+ ~TypeHostRecord();
+
+ // Checks if the record is usable (not expired and has a value)
+ bool HasUsableResultInternal(
+ const mozilla::TimeStamp& now,
+ nsIDNSService::DNSFlags queryFlags) const override;
+ bool RefreshForNegativeResponse() const override;
+
+ void ResolveComplete() override;
+
+ mozilla::net::TypeRecordResultType mResults = AsVariant(mozilla::Nothing());
+ mozilla::Mutex mResultsLock MOZ_UNANNOTATED{"TypeHostRecord.mResultsLock"};
+
+ mozilla::Maybe<nsCString> mOriginHost;
+ bool mAllRecordsExcluded = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TypeHostRecord, TYPEHOSTRECORD_IID)
+
+static inline bool IsHighPriority(nsIDNSService::DNSFlags flags) {
+ return !(flags & (nsHostRecord::DNS_PRIORITY_LOW |
+ nsHostRecord::DNS_PRIORITY_MEDIUM));
+}
+
+static inline bool IsMediumPriority(nsIDNSService::DNSFlags flags) {
+ return flags & nsHostRecord::DNS_PRIORITY_MEDIUM;
+}
+
+static inline bool IsLowPriority(nsIDNSService::DNSFlags flags) {
+ return flags & nsHostRecord::DNS_PRIORITY_LOW;
+}
+
+#endif // nsHostRecord_h__
diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp
new file mode 100644
index 0000000000..ccd6dca57b
--- /dev/null
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -0,0 +1,1965 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(HAVE_RES_NINIT)
+# include <sys/types.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <arpa/nameser.h>
+# include <resolv.h>
+# define RES_RETRY_ON_FAILURE
+#endif
+
+#include <stdlib.h>
+#include <ctime>
+#include "nsHostResolver.h"
+#include "nsError.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsIThreadManager.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsXPCOMCIDInternal.h"
+#include "prthread.h"
+#include "prerror.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "PLDHashTable.h"
+#include "nsQueryObject.h"
+#include "nsURLHelper.h"
+#include "nsThreadUtils.h"
+#include "nsThreadPool.h"
+#include "GetAddrInfo.h"
+#include "TRR.h"
+#include "TRRQuery.h"
+#include "TRRService.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
+#include "DNSLogging.h"
+
+#ifdef XP_WIN
+# include "mozilla/WindowsVersion.h"
+#endif // XP_WIN
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/jni/Utils.h"
+#endif
+
+#define IS_ADDR_TYPE(_type) ((_type) == nsIDNSService::RESOLVE_TYPE_DEFAULT)
+#define IS_OTHER_TYPE(_type) ((_type) != nsIDNSService::RESOLVE_TYPE_DEFAULT)
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+// None of our implementations expose a TTL for negative responses, so we use a
+// constant always.
+static const unsigned int NEGATIVE_RECORD_LIFETIME = 60;
+
+//----------------------------------------------------------------------------
+
+// Use a persistent thread pool in order to avoid spinning up new threads all
+// the time. In particular, thread creation results in a res_init() call from
+// libc which is quite expensive.
+//
+// The pool dynamically grows between 0 and MaxResolverThreads() in size. New
+// requests go first to an idle thread. If that cannot be found and there are
+// fewer than MaxResolverThreads() currently in the pool a new thread is created
+// for high priority requests. If the new request is at a lower priority a new
+// thread will only be created if there are fewer than
+// MaxResolverThreadsAnyPriority() currently outstanding. If a thread cannot be
+// created or an idle thread located for the request it is queued.
+//
+// When the pool is greater than MaxResolverThreadsAnyPriority() in size a
+// thread will be destroyed after ShortIdleTimeoutSeconds of idle time. Smaller
+// pools use LongIdleTimeoutSeconds for a timeout period.
+
+// for threads 1 -> MaxResolverThreadsAnyPriority()
+#define LongIdleTimeoutSeconds 300
+// for threads MaxResolverThreadsAnyPriority() + 1 -> MaxResolverThreads()
+#define ShortIdleTimeoutSeconds 60
+
+//----------------------------------------------------------------------------
+
+namespace mozilla::net {
+LazyLogModule gHostResolverLog("nsHostResolver");
+} // namespace mozilla::net
+
+//----------------------------------------------------------------------------
+
+#if defined(RES_RETRY_ON_FAILURE)
+
+// this class represents the resolver state for a given thread. if we
+// encounter a lookup failure, then we can invoke the Reset method on an
+// instance of this class to reset the resolver (in case /etc/resolv.conf
+// for example changed). this is mainly an issue on GNU systems since glibc
+// only reads in /etc/resolv.conf once per thread. it may be an issue on
+// other systems as well.
+
+class nsResState {
+ public:
+ nsResState()
+ // initialize mLastReset to the time when this object
+ // is created. this means that a reset will not occur
+ // if a thread is too young. the alternative would be
+ // to initialize this to the beginning of time, so that
+ // the first failure would cause a reset, but since the
+ // thread would have just started up, it likely would
+ // already have current /etc/resolv.conf info.
+ : mLastReset(PR_IntervalNow()) {}
+
+ bool Reset() {
+ // reset no more than once per second
+ if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1) {
+ return false;
+ }
+
+ mLastReset = PR_IntervalNow();
+ auto result = res_ninit(&_res);
+
+ LOG(("nsResState::Reset() > 'res_ninit' returned %d", result));
+ return (result == 0);
+ }
+
+ private:
+ PRIntervalTime mLastReset;
+};
+
+#endif // RES_RETRY_ON_FAILURE
+
+//----------------------------------------------------------------------------
+
+static const char kPrefGetTtl[] = "network.dns.get-ttl";
+static const char kPrefNativeIsLocalhost[] = "network.dns.native-is-localhost";
+static const char kPrefThreadIdleTime[] =
+ "network.dns.resolver-thread-extra-idle-time-seconds";
+static bool sGetTtlEnabled = false;
+mozilla::Atomic<bool, mozilla::Relaxed> gNativeIsLocalhost;
+mozilla::Atomic<bool, mozilla::Relaxed> sNativeHTTPSSupported{false};
+
+static void DnsPrefChanged(const char* aPref, void* aSelf) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Should be getting pref changed notification on main thread!");
+
+ MOZ_ASSERT(aSelf);
+
+ if (!strcmp(aPref, kPrefGetTtl)) {
+#ifdef DNSQUERY_AVAILABLE
+ sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl);
+#endif
+ } else if (!strcmp(aPref, kPrefNativeIsLocalhost)) {
+ gNativeIsLocalhost = Preferences::GetBool(kPrefNativeIsLocalhost);
+ }
+}
+
+NS_IMPL_ISUPPORTS0(nsHostResolver)
+
+nsHostResolver::nsHostResolver(uint32_t maxCacheEntries,
+ uint32_t defaultCacheEntryLifetime,
+ uint32_t defaultGracePeriod)
+ : mMaxCacheEntries(maxCacheEntries),
+ mDefaultCacheLifetime(defaultCacheEntryLifetime),
+ mDefaultGracePeriod(defaultGracePeriod),
+ mIdleTaskCV(mLock, "nsHostResolver.mIdleTaskCV") {
+ mCreationTime = PR_Now();
+
+ mLongIdleTimeout = TimeDuration::FromSeconds(LongIdleTimeoutSeconds);
+ mShortIdleTimeout = TimeDuration::FromSeconds(ShortIdleTimeoutSeconds);
+}
+
+nsHostResolver::~nsHostResolver() = default;
+
+nsresult nsHostResolver::Init() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (NS_FAILED(GetAddrInfoInit())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(("nsHostResolver::Init this=%p", this));
+
+ mShutdown = false;
+ mNCS = NetworkConnectivityService::GetSingleton();
+
+ // The preferences probably haven't been loaded from the disk yet, so we
+ // need to register a callback that will set up the experiment once they
+ // are. We also need to explicitly set a value for the props otherwise the
+ // callback won't be called.
+ {
+ DebugOnly<nsresult> rv = Preferences::RegisterCallbackAndCall(
+ &DnsPrefChanged, kPrefGetTtl, this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Could not register DNS TTL pref callback.");
+ rv = Preferences::RegisterCallbackAndCall(&DnsPrefChanged,
+ kPrefNativeIsLocalhost, this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Could not register DNS pref callback.");
+ }
+
+#if defined(HAVE_RES_NINIT)
+ // We want to make sure the system is using the correct resolver settings,
+ // so we force it to reload those settings whenever we startup a subsequent
+ // nsHostResolver instance. We assume that there is no reason to do this
+ // for the first nsHostResolver instance since that is usually created
+ // during application startup.
+ static int initCount = 0;
+ if (initCount++ > 0) {
+ auto result = res_ninit(&_res);
+ LOG(("nsHostResolver::Init > 'res_ninit' returned %d", result));
+ }
+#endif
+
+ // We can configure the threadpool to keep threads alive for a while after
+ // the last ThreadFunc task has been executed.
+ int32_t poolTimeoutSecs = Preferences::GetInt(kPrefThreadIdleTime, 60);
+ uint32_t poolTimeoutMs;
+ if (poolTimeoutSecs < 0) {
+ // This means never shut down the idle threads
+ poolTimeoutMs = UINT32_MAX;
+ } else {
+ // We clamp down the idle time between 0 and one hour.
+ poolTimeoutMs =
+ mozilla::clamped<uint32_t>(poolTimeoutSecs * 1000, 0, 3600 * 1000);
+ }
+
+#if defined(XP_WIN)
+ // For some reason, the DNSQuery_A API doesn't work on Windows 10.
+ // It returns a success code, but no records. We only allow
+ // native HTTPS records on Win 11 for now.
+ sNativeHTTPSSupported = StaticPrefs::network_dns_native_https_query_win10() ||
+ mozilla::IsWin11OrLater();
+#elif defined(MOZ_WIDGET_ANDROID)
+ // android_res_nquery only got added in API level 29
+ sNativeHTTPSSupported = jni::GetAPIVersion() >= 29;
+#elif defined(XP_LINUX) || defined(XP_MACOSX)
+ sNativeHTTPSSupported = true;
+#endif
+ LOG(("Native HTTPS records supported=%d", bool(sNativeHTTPSSupported)));
+
+ nsCOMPtr<nsIThreadPool> threadPool = new nsThreadPool();
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(MaxResolverThreads()));
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(MaxResolverThreads()));
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadTimeout(poolTimeoutMs));
+ MOZ_ALWAYS_SUCCEEDS(
+ threadPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize));
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("DNS Resolver"_ns));
+ mResolverThreads = ToRefPtr(std::move(threadPool));
+
+ return NS_OK;
+}
+
+void nsHostResolver::ClearPendingQueue(
+ LinkedList<RefPtr<nsHostRecord>>& aPendingQ) {
+ // loop through pending queue, erroring out pending lookups.
+ if (!aPendingQ.isEmpty()) {
+ for (const RefPtr<nsHostRecord>& rec : aPendingQ) {
+ rec->Cancel();
+ if (rec->IsAddrRecord()) {
+ CompleteLookup(rec, NS_ERROR_ABORT, nullptr, rec->pb, rec->originSuffix,
+ rec->mTRRSkippedReason, nullptr);
+ } else {
+ mozilla::net::TypeRecordResultType empty(Nothing{});
+ CompleteLookupByType(rec, NS_ERROR_ABORT, empty, rec->mTRRSkippedReason,
+ 0, rec->pb);
+ }
+ }
+ }
+}
+
+//
+// FlushCache() is what we call when the network has changed. We must not
+// trust names that were resolved before this change. They may resolve
+// differently now.
+//
+// This function removes all existing resolved host entries from the hash.
+// Names that are in the pending queues can be left there. Entries in the
+// cache that have 'Resolve' set true but not 'OnQueue' are being resolved
+// right now, so we need to mark them to get re-resolved on completion!
+
+void nsHostResolver::FlushCache(bool aTrrToo) {
+ MutexAutoLock lock(mLock);
+
+ mQueue.FlushEvictionQ(mRecordDB, lock);
+
+ // Refresh the cache entries that are resolving RIGHT now, remove the rest.
+ for (auto iter = mRecordDB.Iter(); !iter.Done(); iter.Next()) {
+ nsHostRecord* record = iter.UserData();
+ // Try to remove the record, or mark it for refresh.
+ // By-type records are from TRR. We do not need to flush those entry
+ // when the network has change, because they are not local.
+ if (record->IsAddrRecord()) {
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(record);
+ MOZ_ASSERT(addrRec);
+ if (addrRec->RemoveOrRefresh(aTrrToo)) {
+ mQueue.MaybeRemoveFromQ(record, lock);
+ LOG(("Removing (%s) Addr record from mRecordDB", record->host.get()));
+ iter.Remove();
+ }
+ } else if (aTrrToo) {
+ // remove by type records
+ LOG(("Removing (%s) type record from mRecordDB", record->host.get()));
+ iter.Remove();
+ }
+ }
+}
+
+void nsHostResolver::Shutdown() {
+ LOG(("Shutting down host resolver.\n"));
+
+ {
+ DebugOnly<nsresult> rv =
+ Preferences::UnregisterCallback(&DnsPrefChanged, kPrefGetTtl, this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Could not unregister DNS TTL pref callback.");
+ }
+
+ LinkedList<RefPtr<nsHostRecord>> pendingQHigh, pendingQMed, pendingQLow,
+ evictionQ;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ mShutdown = true;
+
+ if (mNumIdleTasks) {
+ mIdleTaskCV.NotifyAll();
+ }
+
+ mQueue.ClearAll(
+ [&](nsHostRecord* aRec) {
+ mLock.AssertCurrentThreadOwns();
+ if (aRec->IsAddrRecord()) {
+ CompleteLookupLocked(aRec, NS_ERROR_ABORT, nullptr, aRec->pb,
+ aRec->originSuffix, aRec->mTRRSkippedReason,
+ nullptr, lock);
+ } else {
+ mozilla::net::TypeRecordResultType empty(Nothing{});
+ CompleteLookupByTypeLocked(aRec, NS_ERROR_ABORT, empty,
+ aRec->mTRRSkippedReason, 0, aRec->pb,
+ lock);
+ }
+ },
+ lock);
+
+ for (const auto& data : mRecordDB.Values()) {
+ data->Cancel();
+ }
+ // empty host database
+ mRecordDB.Clear();
+
+ mNCS = nullptr;
+ }
+
+ // Shutdown the resolver threads, but with a timeout of 2 seconds (prefable).
+ // If the timeout is exceeded, any stuck threads will be leaked.
+ mResolverThreads->ShutdownWithTimeout(
+ StaticPrefs::network_dns_resolver_shutdown_timeout_ms());
+
+ {
+ mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to shutdown GetAddrInfo");
+ }
+}
+
+nsresult nsHostResolver::GetHostRecord(
+ const nsACString& host, const nsACString& aTrrServer, uint16_t type,
+ nsIDNSService::DNSFlags flags, uint16_t af, bool pb,
+ const nsCString& originSuffix, nsHostRecord** result) {
+ MutexAutoLock lock(mLock);
+ nsHostKey key(host, aTrrServer, type, flags, af, pb, originSuffix);
+
+ RefPtr<nsHostRecord> rec =
+ mRecordDB.LookupOrInsertWith(key, [&] { return InitRecord(key); });
+ if (rec->IsAddrRecord()) {
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ if (addrRec->addr) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (rec->mResolving) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *result = rec.forget().take();
+ return NS_OK;
+}
+
+nsHostRecord* nsHostResolver::InitRecord(const nsHostKey& key) {
+ if (IS_ADDR_TYPE(key.type)) {
+ return new AddrHostRecord(key);
+ }
+ return new TypeHostRecord(key);
+}
+
+already_AddRefed<nsHostRecord> nsHostResolver::InitLoopbackRecord(
+ const nsHostKey& key, nsresult* aRv) {
+ MOZ_ASSERT(aRv);
+ MOZ_ASSERT(IS_ADDR_TYPE(key.type));
+
+ *aRv = NS_ERROR_FAILURE;
+ RefPtr<nsHostRecord> rec = InitRecord(key);
+
+ nsTArray<NetAddr> addresses;
+ NetAddr addr;
+ if (key.af == PR_AF_INET || key.af == PR_AF_UNSPEC) {
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(addr.InitFromString("127.0.0.1"_ns)));
+ addresses.AppendElement(addr);
+ }
+ if (key.af == PR_AF_INET6 || key.af == PR_AF_UNSPEC) {
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(addr.InitFromString("::1"_ns)));
+ addresses.AppendElement(addr);
+ }
+
+ RefPtr<AddrInfo> ai =
+ new AddrInfo(rec->host, DNSResolverType::Native, 0, std::move(addresses));
+
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ MutexAutoLock lock(addrRec->addr_info_lock);
+ addrRec->addr_info = ai;
+ addrRec->SetExpiration(TimeStamp::NowLoRes(), mDefaultCacheLifetime,
+ mDefaultGracePeriod);
+ addrRec->negative = false;
+
+ *aRv = NS_OK;
+ return rec.forget();
+}
+
+static bool IsNativeHTTPSEnabled() {
+ if (!StaticPrefs::network_dns_native_https_query()) {
+ return false;
+ }
+ return sNativeHTTPSSupported;
+}
+
+nsresult nsHostResolver::ResolveHost(const nsACString& aHost,
+ const nsACString& aTrrServer,
+ int32_t aPort, uint16_t type,
+ const OriginAttributes& aOriginAttributes,
+ nsIDNSService::DNSFlags flags, uint16_t af,
+ nsResolveHostCallback* aCallback) {
+ nsAutoCString host(aHost);
+ NS_ENSURE_TRUE(!host.IsEmpty(), NS_ERROR_UNEXPECTED);
+
+ nsAutoCString originSuffix;
+ aOriginAttributes.CreateSuffix(originSuffix);
+ LOG(("Resolving host [%s]<%s>%s%s type %d. [this=%p]\n", host.get(),
+ originSuffix.get(), flags & RES_BYPASS_CACHE ? " - bypassing cache" : "",
+ flags & RES_REFRESH_CACHE ? " - refresh cache" : "", type, this));
+
+ // ensure that we are working with a valid hostname before proceeding. see
+ // bug 304904 for details.
+ if (!net_IsValidHostName(host)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ // If TRR is disabled we can return immediately if the native API is disabled
+ if (!IsNativeHTTPSEnabled() && IS_OTHER_TYPE(type) &&
+ Mode() == nsIDNSService::MODE_TRROFF) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ // Used to try to parse to an IP address literal.
+ NetAddr tempAddr;
+ if (IS_OTHER_TYPE(type) && (NS_SUCCEEDED(tempAddr.InitFromString(host)))) {
+ // For by-type queries the host cannot be IP literal.
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ RefPtr<nsResolveHostCallback> callback(aCallback);
+ // if result is set inside the lock, then we need to issue the
+ // callback before returning.
+ RefPtr<nsHostRecord> result;
+ nsresult status = NS_OK, rv = NS_OK;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // check to see if there is already an entry for this |host|
+ // in the hash table. if so, then check to see if we can't
+ // just reuse the lookup result. otherwise, if there are
+ // any pending callbacks, then add to pending callbacks queue,
+ // and return. otherwise, add ourselves as first pending
+ // callback, and proceed to do the lookup.
+
+ Maybe<nsCString> originHost;
+ if (StaticPrefs::network_dns_port_prefixed_qname_https_rr() &&
+ type == nsIDNSService::RESOLVE_TYPE_HTTPSSVC && aPort != -1 &&
+ aPort != 443) {
+ originHost = Some(host);
+ host = nsPrintfCString("_%d._https.%s", aPort, host.get());
+ LOG((" Using port prefixed host name [%s]", host.get()));
+ }
+
+ bool excludedFromTRR = false;
+ if (TRRService::Get() && TRRService::Get()->IsExcludedFromTRR(host)) {
+ flags |= nsIDNSService::RESOLVE_DISABLE_TRR;
+ excludedFromTRR = true;
+
+ if (!aTrrServer.IsEmpty()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+
+ nsHostKey key(host, aTrrServer, type, flags, af,
+ (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix);
+
+ // Check if we have a localhost domain, if so hardcode to loopback
+ if (IS_ADDR_TYPE(type) && IsLoopbackHostname(host)) {
+ nsresult rv;
+ RefPtr<nsHostRecord> result = InitLoopbackRecord(key, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(result);
+ aCallback->OnResolveHostComplete(this, result, NS_OK);
+ return NS_OK;
+ }
+
+ RefPtr<nsHostRecord> rec =
+ mRecordDB.LookupOrInsertWith(key, [&] { return InitRecord(key); });
+
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ MOZ_ASSERT(rec, "Record should not be null");
+ MOZ_ASSERT((IS_ADDR_TYPE(type) && rec->IsAddrRecord() && addrRec) ||
+ (IS_OTHER_TYPE(type) && !rec->IsAddrRecord()));
+
+ if (IS_OTHER_TYPE(type) && originHost) {
+ RefPtr<TypeHostRecord> typeRec = do_QueryObject(rec);
+ typeRec->mOriginHost = std::move(originHost);
+ }
+
+ if (excludedFromTRR) {
+ rec->RecordReason(TRRSkippedReason::TRR_EXCLUDED);
+ }
+
+ if (!(flags & RES_BYPASS_CACHE) &&
+ rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
+ result = FromCache(rec, host, type, status, lock);
+ } else if (addrRec && addrRec->addr) {
+ // if the host name is an IP address literal and has been
+ // parsed, go ahead and use it.
+ LOG((" Using cached address for IP Literal [%s].\n", host.get()));
+ result = FromCachedIPLiteral(rec);
+ } else if (addrRec && NS_SUCCEEDED(tempAddr.InitFromString(host))) {
+ // try parsing the host name as an IP address literal to short
+ // circuit full host resolution. (this is necessary on some
+ // platforms like Win9x. see bug 219376 for more details.)
+ LOG((" Host is IP Literal [%s].\n", host.get()));
+ result = FromIPLiteral(addrRec, tempAddr);
+ } else if (mQueue.PendingCount() >= MAX_NON_PRIORITY_REQUESTS &&
+ !IsHighPriority(flags) && !rec->mResolving) {
+ LOG(
+ (" Lookup queue full: dropping %s priority request for "
+ "host [%s].\n",
+ IsMediumPriority(flags) ? "medium" : "low", host.get()));
+ if (IS_ADDR_TYPE(type)) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_OVERFLOW);
+ }
+ // This is a lower priority request and we are swamped, so refuse it.
+ rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+
+ // Check if the offline flag is set.
+ } else if (flags & RES_OFFLINE) {
+ LOG((" Offline request for host [%s]; ignoring.\n", host.get()));
+ rv = NS_ERROR_OFFLINE;
+
+ // We do not have a valid result till here.
+ // A/AAAA request can check for an alternative entry like AF_UNSPEC.
+ // Otherwise we need to start a new query.
+ } else if (!rec->mResolving) {
+ result =
+ FromUnspecEntry(rec, host, aTrrServer, originSuffix, type, flags, af,
+ aOriginAttributes.mPrivateBrowsingId > 0, status);
+ // If this is a by-type request or if no valid record was found
+ // in the cache or this is an AF_UNSPEC request, then start a
+ // new lookup.
+ if (!result) {
+ LOG((" No usable record in cache for host [%s] type %d.", host.get(),
+ type));
+
+ if (flags & RES_REFRESH_CACHE) {
+ rec->Invalidate();
+ }
+
+ // Add callback to the list of pending callbacks.
+ rec->mCallbacks.insertBack(callback);
+ rec->flags = flags;
+ rv = NameLookup(rec, lock);
+ if (IS_ADDR_TYPE(type)) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NETWORK_FIRST);
+ }
+ if (NS_FAILED(rv) && callback->isInList()) {
+ callback->remove();
+ } else {
+ LOG(
+ (" DNS lookup for host [%s] blocking "
+ "pending 'getaddrinfo' or trr query: "
+ "callback [%p]",
+ host.get(), callback.get()));
+ }
+ }
+ } else {
+ LOG(
+ (" Host [%s] is being resolved. Appending callback "
+ "[%p].",
+ host.get(), callback.get()));
+
+ rec->mCallbacks.insertBack(callback);
+
+ if (rec && rec->onQueue()) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NETWORK_SHARED);
+
+ // Consider the case where we are on a pending queue of
+ // lower priority than the request is being made at.
+ // In that case we should upgrade to the higher queue.
+
+ if (IsHighPriority(flags) && !IsHighPriority(rec->flags)) {
+ // Move from (low|med) to high.
+ mQueue.MoveToAnotherPendingQ(rec, flags, lock);
+ rec->flags = flags;
+ ConditionallyCreateThread(rec);
+ } else if (IsMediumPriority(flags) && IsLowPriority(rec->flags)) {
+ // Move from low to med.
+ mQueue.MoveToAnotherPendingQ(rec, flags, lock);
+ rec->flags = flags;
+ mIdleTaskCV.Notify();
+ }
+ }
+ }
+
+ if (result && callback->isInList()) {
+ callback->remove();
+ }
+ } // lock
+
+ if (result) {
+ callback->OnResolveHostComplete(this, result, status);
+ }
+
+ return rv;
+}
+
+already_AddRefed<nsHostRecord> nsHostResolver::FromCache(
+ nsHostRecord* aRec, const nsACString& aHost, uint16_t aType,
+ nsresult& aStatus, const MutexAutoLock& aLock) {
+ LOG((" Using cached record for host [%s].\n",
+ nsPromiseFlatCString(aHost).get()));
+
+ // put reference to host record on stack...
+ RefPtr<nsHostRecord> result = aRec;
+ if (IS_ADDR_TYPE(aType)) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
+ }
+
+ // For entries that are in the grace period
+ // or all cached negative entries, use the cache but start a new
+ // lookup in the background
+ ConditionallyRefreshRecord(aRec, aHost, aLock);
+
+ if (aRec->negative) {
+ LOG((" Negative cache entry for host [%s].\n",
+ nsPromiseFlatCString(aHost).get()));
+ if (IS_ADDR_TYPE(aType)) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_NEGATIVE_HIT);
+ }
+ aStatus = NS_ERROR_UNKNOWN_HOST;
+ }
+
+ return result.forget();
+}
+
+already_AddRefed<nsHostRecord> nsHostResolver::FromCachedIPLiteral(
+ nsHostRecord* aRec) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
+ RefPtr<nsHostRecord> result = aRec;
+ return result.forget();
+}
+
+already_AddRefed<nsHostRecord> nsHostResolver::FromIPLiteral(
+ AddrHostRecord* aAddrRec, const NetAddr& aAddr) {
+ // ok, just copy the result into the host record, and be
+ // done with it! ;-)
+ aAddrRec->addr = MakeUnique<NetAddr>(aAddr);
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
+ // put reference to host record on stack...
+ RefPtr<nsHostRecord> result = aAddrRec;
+ return result.forget();
+}
+
+already_AddRefed<nsHostRecord> nsHostResolver::FromUnspecEntry(
+ nsHostRecord* aRec, const nsACString& aHost, const nsACString& aTrrServer,
+ const nsACString& aOriginSuffix, uint16_t aType,
+ nsIDNSService::DNSFlags aFlags, uint16_t af, bool aPb, nsresult& aStatus) {
+ RefPtr<nsHostRecord> result = nullptr;
+ // If this is an IPV4 or IPV6 specific request, check if there is
+ // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(aRec);
+ if (addrRec && !(aFlags & RES_BYPASS_CACHE) &&
+ ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
+ // Check for an AF_UNSPEC entry.
+
+ const nsHostKey unspecKey(aHost, aTrrServer,
+ nsIDNSService::RESOLVE_TYPE_DEFAULT, aFlags,
+ PR_AF_UNSPEC, aPb, aOriginSuffix);
+ RefPtr<nsHostRecord> unspecRec = mRecordDB.Get(unspecKey);
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ if (unspecRec && unspecRec->HasUsableResult(now, aFlags)) {
+ MOZ_ASSERT(unspecRec->IsAddrRecord());
+
+ RefPtr<AddrHostRecord> addrUnspecRec = do_QueryObject(unspecRec);
+ MOZ_ASSERT(addrUnspecRec);
+ MOZ_ASSERT(addrUnspecRec->addr_info || addrUnspecRec->negative,
+ "Entry should be resolved or negative.");
+
+ LOG((" Trying AF_UNSPEC entry for host [%s] af: %s.\n",
+ PromiseFlatCString(aHost).get(),
+ (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
+
+ // We need to lock in case any other thread is reading
+ // addr_info.
+ MutexAutoLock lock(addrRec->addr_info_lock);
+
+ addrRec->addr_info = nullptr;
+ addrRec->addr_info_gencnt++;
+ if (unspecRec->negative) {
+ aRec->negative = unspecRec->negative;
+ aRec->CopyExpirationTimesAndFlagsFrom(unspecRec);
+ } else if (addrUnspecRec->addr_info) {
+ MutexAutoLock lock(addrUnspecRec->addr_info_lock);
+ if (addrUnspecRec->addr_info) {
+ // Search for any valid address in the AF_UNSPEC entry
+ // in the cache (not blocklisted and from the right
+ // family).
+ nsTArray<NetAddr> addresses;
+ for (const auto& addr : addrUnspecRec->addr_info->Addresses()) {
+ if ((af == addr.inet.family) &&
+ !addrUnspecRec->Blocklisted(&addr)) {
+ addresses.AppendElement(addr);
+ }
+ }
+ if (!addresses.IsEmpty()) {
+ addrRec->addr_info = new AddrInfo(
+ addrUnspecRec->addr_info->Hostname(),
+ addrUnspecRec->addr_info->CanonicalHostname(),
+ addrUnspecRec->addr_info->ResolverType(),
+ addrUnspecRec->addr_info->TRRType(), std::move(addresses));
+ addrRec->addr_info_gencnt++;
+ aRec->CopyExpirationTimesAndFlagsFrom(unspecRec);
+ }
+ }
+ }
+ // Now check if we have a new record.
+ if (aRec->HasUsableResult(now, aFlags)) {
+ result = aRec;
+ if (aRec->negative) {
+ aStatus = NS_ERROR_UNKNOWN_HOST;
+ }
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
+ ConditionallyRefreshRecord(aRec, aHost, lock);
+ } else if (af == PR_AF_INET6) {
+ // For AF_INET6, a new lookup means another AF_UNSPEC
+ // lookup. We have already iterated through the
+ // AF_UNSPEC addresses, so we mark this record as
+ // negative.
+ LOG(
+ (" No AF_INET6 in AF_UNSPEC entry: "
+ "host [%s] unknown host.",
+ nsPromiseFlatCString(aHost).get()));
+ result = aRec;
+ aRec->negative = true;
+ aStatus = NS_ERROR_UNKNOWN_HOST;
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NEGATIVE_HIT);
+ }
+ }
+ }
+
+ return result.forget();
+}
+
+void nsHostResolver::DetachCallback(
+ const nsACString& host, const nsACString& aTrrServer, uint16_t aType,
+ const OriginAttributes& aOriginAttributes, nsIDNSService::DNSFlags flags,
+ uint16_t af, nsResolveHostCallback* aCallback, nsresult status) {
+ RefPtr<nsHostRecord> rec;
+ RefPtr<nsResolveHostCallback> callback(aCallback);
+
+ {
+ MutexAutoLock lock(mLock);
+
+ nsAutoCString originSuffix;
+ aOriginAttributes.CreateSuffix(originSuffix);
+
+ nsHostKey key(host, aTrrServer, aType, flags, af,
+ (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix);
+ RefPtr<nsHostRecord> entry = mRecordDB.Get(key);
+ if (entry) {
+ // walk list looking for |callback|... we cannot assume
+ // that it will be there!
+
+ for (nsResolveHostCallback* c : entry->mCallbacks) {
+ if (c == callback) {
+ rec = entry;
+ c->remove();
+ break;
+ }
+ }
+ }
+ }
+
+ // complete callback with the given status code; this would only be done if
+ // the record was in the process of being resolved.
+ if (rec) {
+ callback->OnResolveHostComplete(this, rec, status);
+ }
+}
+
+nsresult nsHostResolver::ConditionallyCreateThread(nsHostRecord* rec) {
+ if (mNumIdleTasks) {
+ // wake up idle tasks to process this lookup
+ mIdleTaskCV.Notify();
+ } else if ((mActiveTaskCount < MaxResolverThreadsAnyPriority()) ||
+ (IsHighPriority(rec->flags) &&
+ mActiveTaskCount < MaxResolverThreads())) {
+ nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
+ "nsHostResolver::ThreadFunc", this, &nsHostResolver::ThreadFunc);
+ mActiveTaskCount++;
+ nsresult rv =
+ mResolverThreads->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ mActiveTaskCount--;
+ }
+ } else {
+ LOG((" Unable to find a thread for looking up host [%s].\n",
+ rec->host.get()));
+ }
+ return NS_OK;
+}
+
+nsresult nsHostResolver::TrrLookup_unlocked(nsHostRecord* rec, TRR* pushedTRR) {
+ MutexAutoLock lock(mLock);
+ return TrrLookup(rec, lock, pushedTRR);
+}
+
+void nsHostResolver::MaybeRenewHostRecord(nsHostRecord* aRec) {
+ MutexAutoLock lock(mLock);
+ MaybeRenewHostRecordLocked(aRec, lock);
+}
+
+void nsHostResolver::MaybeRenewHostRecordLocked(nsHostRecord* aRec,
+ const MutexAutoLock& aLock) {
+ mQueue.MaybeRenewHostRecord(aRec, aLock);
+}
+
+bool nsHostResolver::TRRServiceEnabledForRecord(nsHostRecord* aRec) {
+ MOZ_ASSERT(aRec, "Record must not be empty");
+ MOZ_ASSERT(aRec->mEffectiveTRRMode != nsIRequest::TRR_DEFAULT_MODE,
+ "effective TRR mode must be computed before this call");
+ if (!TRRService::Get()) {
+ aRec->RecordReason(TRRSkippedReason::TRR_NO_GSERVICE);
+ return false;
+ }
+
+ // We always try custom resolvers.
+ if (!aRec->mTrrServer.IsEmpty()) {
+ return true;
+ }
+
+ nsIRequest::TRRMode reqMode = aRec->mEffectiveTRRMode;
+ if (TRRService::Get()->Enabled(reqMode)) {
+ return true;
+ }
+
+ if (NS_IsOffline()) {
+ // If we are in the NOT_CONFIRMED state _because_ we lack connectivity,
+ // then we should report that the browser is offline instead.
+ aRec->RecordReason(TRRSkippedReason::TRR_IS_OFFLINE);
+ return false;
+ }
+
+ auto hasConnectivity = [this]() -> bool {
+ mLock.AssertCurrentThreadOwns();
+ if (!mNCS) {
+ return true;
+ }
+ nsINetworkConnectivityService::ConnectivityState ipv4 = mNCS->GetIPv4();
+ nsINetworkConnectivityService::ConnectivityState ipv6 = mNCS->GetIPv6();
+
+ if (ipv4 == nsINetworkConnectivityService::OK ||
+ ipv6 == nsINetworkConnectivityService::OK) {
+ return true;
+ }
+
+ if (ipv4 == nsINetworkConnectivityService::UNKNOWN ||
+ ipv6 == nsINetworkConnectivityService::UNKNOWN) {
+ // One of the checks hasn't completed yet. Optimistically assume we'll
+ // have network connectivity.
+ return true;
+ }
+
+ return false;
+ };
+
+ if (!hasConnectivity()) {
+ aRec->RecordReason(TRRSkippedReason::TRR_NO_CONNECTIVITY);
+ return false;
+ }
+
+ bool isConfirmed = TRRService::Get()->IsConfirmed();
+ if (!isConfirmed) {
+ aRec->RecordReason(TRRSkippedReason::TRR_NOT_CONFIRMED);
+ }
+
+ return isConfirmed;
+}
+
+// returns error if no TRR resolve is issued
+// it is impt this is not called while a native lookup is going on
+nsresult nsHostResolver::TrrLookup(nsHostRecord* aRec,
+ const MutexAutoLock& aLock, TRR* pushedTRR) {
+ if (Mode() == nsIDNSService::MODE_TRROFF ||
+ StaticPrefs::network_dns_disabled()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ LOG(("TrrLookup host:%s af:%" PRId16, aRec->host.get(), aRec->af));
+
+ RefPtr<nsHostRecord> rec(aRec);
+ mLock.AssertCurrentThreadOwns();
+
+ RefPtr<AddrHostRecord> addrRec;
+ RefPtr<TypeHostRecord> typeRec;
+
+ if (rec->IsAddrRecord()) {
+ addrRec = do_QueryObject(rec);
+ MOZ_ASSERT(addrRec);
+ } else {
+ typeRec = do_QueryObject(rec);
+ MOZ_ASSERT(typeRec);
+ }
+
+ MOZ_ASSERT(!rec->mResolving);
+
+ if (!TRRServiceEnabledForRecord(aRec)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ MaybeRenewHostRecordLocked(rec, aLock);
+
+ RefPtr<TRRQuery> query = new TRRQuery(this, rec);
+ nsresult rv = query->DispatchLookup(pushedTRR);
+ if (NS_FAILED(rv)) {
+ rec->RecordReason(TRRSkippedReason::TRR_DID_NOT_MAKE_QUERY);
+ return rv;
+ }
+
+ {
+ auto lock = rec->mTRRQuery.Lock();
+ MOZ_ASSERT(!lock.ref(), "TRR already in progress");
+ lock.ref() = query;
+ }
+
+ rec->mResolving++;
+ rec->mTrrAttempts++;
+ rec->StoreNative(false);
+ return NS_OK;
+}
+
+nsresult nsHostResolver::NativeLookup(nsHostRecord* aRec,
+ const MutexAutoLock& aLock) {
+ if (StaticPrefs::network_dns_disabled()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ LOG(("NativeLookup host:%s af:%" PRId16, aRec->host.get(), aRec->af));
+
+ // If this is not a A/AAAA request, make sure native HTTPS is enabled.
+ MOZ_ASSERT(aRec->IsAddrRecord() || IsNativeHTTPSEnabled());
+ mLock.AssertCurrentThreadOwns();
+
+ RefPtr<nsHostRecord> rec(aRec);
+
+ rec->mNativeStart = TimeStamp::Now();
+
+ // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
+ MaybeRenewHostRecordLocked(aRec, aLock);
+
+ mQueue.InsertRecord(rec, rec->flags, aLock);
+
+ rec->StoreNative(true);
+ rec->StoreNativeUsed(true);
+ rec->mResolving++;
+
+ nsresult rv = ConditionallyCreateThread(rec);
+
+ LOG((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n",
+ static_cast<uint32_t>(mActiveTaskCount),
+ static_cast<uint32_t>(mActiveAnyThreadCount),
+ static_cast<uint32_t>(mNumIdleTasks), mQueue.PendingCount()));
+
+ return rv;
+}
+
+// static
+nsIDNSService::ResolverMode nsHostResolver::Mode() {
+ if (TRRService::Get()) {
+ return TRRService::Get()->Mode();
+ }
+
+ // If we don't have a TRR service just return MODE_TRROFF so we don't make
+ // any TRR requests by mistake.
+ return nsIDNSService::MODE_TRROFF;
+}
+
+nsIRequest::TRRMode nsHostRecord::TRRMode() {
+ return nsIDNSService::GetTRRModeFromFlags(flags);
+}
+
+// static
+void nsHostResolver::ComputeEffectiveTRRMode(nsHostRecord* aRec) {
+ nsIDNSService::ResolverMode resolverMode = nsHostResolver::Mode();
+ nsIRequest::TRRMode requestMode = aRec->TRRMode();
+
+ // For domains that are excluded from TRR or when parental control is enabled,
+ // we fallback to NativeLookup. This happens even in MODE_TRRONLY. By default
+ // localhost and local are excluded (so we cover *.local hosts) See the
+ // network.trr.excluded-domains pref.
+
+ if (!TRRService::Get()) {
+ aRec->RecordReason(TRRSkippedReason::TRR_NO_GSERVICE);
+ aRec->mEffectiveTRRMode = requestMode;
+ return;
+ }
+
+ if (!aRec->mTrrServer.IsEmpty()) {
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_ONLY_MODE;
+ return;
+ }
+
+ if (TRRService::Get()->IsExcludedFromTRR(aRec->host)) {
+ aRec->RecordReason(TRRSkippedReason::TRR_EXCLUDED);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (TRRService::Get()->ParentalControlEnabled()) {
+ aRec->RecordReason(TRRSkippedReason::TRR_PARENTAL_CONTROL);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (resolverMode == nsIDNSService::MODE_TRROFF) {
+ aRec->RecordReason(TRRSkippedReason::TRR_OFF_EXPLICIT);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (requestMode == nsIRequest::TRR_DISABLED_MODE) {
+ aRec->RecordReason(TRRSkippedReason::TRR_REQ_MODE_DISABLED);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if ((requestMode == nsIRequest::TRR_DEFAULT_MODE &&
+ resolverMode == nsIDNSService::MODE_NATIVEONLY)) {
+ if (StaticPrefs::network_trr_display_fallback_warning()) {
+ TRRSkippedReason heuristicResult =
+ TRRService::Get()->GetHeuristicDetectionResult();
+ if (heuristicResult != TRRSkippedReason::TRR_UNSET &&
+ heuristicResult != TRRSkippedReason::TRR_OK) {
+ aRec->RecordReason(heuristicResult);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+ }
+ aRec->RecordReason(TRRSkippedReason::TRR_MODE_NOT_ENABLED);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (requestMode == nsIRequest::TRR_DEFAULT_MODE &&
+ resolverMode == nsIDNSService::MODE_TRRFIRST) {
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_FIRST_MODE;
+ return;
+ }
+
+ if (requestMode == nsIRequest::TRR_DEFAULT_MODE &&
+ resolverMode == nsIDNSService::MODE_TRRONLY) {
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_ONLY_MODE;
+ return;
+ }
+
+ aRec->mEffectiveTRRMode = requestMode;
+}
+
+// Kick-off a name resolve operation, using native resolver and/or TRR
+nsresult nsHostResolver::NameLookup(nsHostRecord* rec,
+ const mozilla::MutexAutoLock& aLock) {
+ LOG(("NameLookup host:%s af:%" PRId16, rec->host.get(), rec->af));
+ mLock.AssertCurrentThreadOwns();
+
+ if (rec->flags & RES_IP_HINT) {
+ LOG(("Skip lookup if RES_IP_HINT is set\n"));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ nsresult rv = NS_ERROR_UNKNOWN_HOST;
+ if (rec->mResolving) {
+ LOG(("NameLookup %s while already resolving\n", rec->host.get()));
+ return NS_OK;
+ }
+
+ // Make sure we reset the reason each time we attempt to do a new lookup
+ // so we don't wrongly report the reason for the previous one.
+ rec->Reset();
+
+ ComputeEffectiveTRRMode(rec);
+
+ if (!rec->mTrrServer.IsEmpty()) {
+ LOG(("NameLookup: %s use trr:%s", rec->host.get(), rec->mTrrServer.get()));
+ if (rec->mEffectiveTRRMode != nsIRequest::TRR_ONLY_MODE) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (rec->flags & nsIDNSService::RESOLVE_DISABLE_TRR) {
+ LOG(("TRR with server and DISABLE_TRR flag. Returning error."));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ return TrrLookup(rec, aLock);
+ }
+
+ LOG(("NameLookup: %s effectiveTRRmode: %d flags: %X", rec->host.get(),
+ static_cast<nsIRequest::TRRMode>(rec->mEffectiveTRRMode), rec->flags));
+
+ if (rec->flags & nsIDNSService::RESOLVE_DISABLE_TRR) {
+ rec->RecordReason(TRRSkippedReason::TRR_DISABLED_FLAG);
+ }
+
+ bool serviceNotReady = !TRRServiceEnabledForRecord(rec);
+
+ if (rec->mEffectiveTRRMode != nsIRequest::TRR_DISABLED_MODE &&
+ !((rec->flags & nsIDNSService::RESOLVE_DISABLE_TRR)) &&
+ !serviceNotReady) {
+ rv = TrrLookup(rec, aLock);
+ }
+
+ if (rec->mEffectiveTRRMode == nsIRequest::TRR_DISABLED_MODE ||
+ (rec->mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE &&
+ (rec->flags & nsIDNSService::RESOLVE_DISABLE_TRR || serviceNotReady ||
+ NS_FAILED(rv)))) {
+ if (!IsNativeHTTPSEnabled() && !rec->IsAddrRecord()) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ // If we use this branch then the mTRRUsed flag should not be set
+ // Even if we did call TrrLookup above, the fact that it failed sync-ly
+ // means that we didn't actually succeed in opening the channel.
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ MOZ_ASSERT_IF(addrRec, addrRec->mResolverType == DNSResolverType::Native);
+#endif
+
+ // We did not lookup via TRR - don't fallback to native if the
+ // network.trr.display_fallback_warning pref is set and either
+ // 1. we are in TRR first mode and confirmation failed
+ // 2. the record has trr_disabled and a heuristic skip reason
+ if (StaticPrefs::network_trr_display_fallback_warning() &&
+ rec->mEffectiveTRRMode != nsIRequest::TRR_ONLY_MODE) {
+ if ((rec->mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE &&
+ rec->mTRRSkippedReason == TRRSkippedReason::TRR_NOT_CONFIRMED) ||
+ (rec->mEffectiveTRRMode == nsIRequest::TRR_DISABLED_MODE &&
+ rec->mTRRSkippedReason >=
+ nsITRRSkipReason::TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH &&
+ rec->mTRRSkippedReason <=
+ nsITRRSkipReason::TRR_HEURISTIC_TRIPPED_NRPT)) {
+ LOG((
+ "NameLookup: ResolveHostComplete with status NS_ERROR_UNKNOWN_HOST "
+ "for: %s effectiveTRRmode: "
+ "%d SkippedReason: %d",
+ rec->host.get(),
+ static_cast<nsIRequest::TRRMode>(rec->mEffectiveTRRMode),
+ static_cast<int32_t>(rec->mTRRSkippedReason)));
+
+ mozilla::LinkedList<RefPtr<nsResolveHostCallback>> cbs =
+ std::move(rec->mCallbacks);
+ for (nsResolveHostCallback* c = cbs.getFirst(); c;
+ c = c->removeAndGetNext()) {
+ c->OnResolveHostComplete(this, rec, NS_ERROR_UNKNOWN_HOST);
+ }
+
+ return NS_OK;
+ }
+ }
+
+ rv = NativeLookup(rec, aLock);
+ }
+
+ return rv;
+}
+
+nsresult nsHostResolver::ConditionallyRefreshRecord(
+ nsHostRecord* rec, const nsACString& host, const MutexAutoLock& aLock) {
+ if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID ||
+ rec->negative) &&
+ !rec->mResolving && rec->RefreshForNegativeResponse()) {
+ LOG((" Using %s cache entry for host [%s] but starting async renewal.",
+ rec->negative ? "negative" : "positive", host.BeginReading()));
+ NameLookup(rec, aLock);
+
+ if (rec->IsAddrRecord() && !rec->negative) {
+ // negative entries are constantly being refreshed, only
+ // track positive grace period induced renewals
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_RENEWAL);
+ }
+ }
+ return NS_OK;
+}
+
+bool nsHostResolver::GetHostToLookup(nsHostRecord** result) {
+ bool timedOut = false;
+ TimeDuration timeout;
+ TimeStamp epoch, now;
+
+ MutexAutoLock lock(mLock);
+
+ timeout = (mNumIdleTasks >= MaxResolverThreadsAnyPriority())
+ ? mShortIdleTimeout
+ : mLongIdleTimeout;
+ epoch = TimeStamp::Now();
+
+ while (!mShutdown) {
+ // remove next record from Q; hand over owning reference. Check high, then
+ // med, then low
+
+#define SET_GET_TTL(var, val) (var)->StoreGetTtl(sGetTtlEnabled && (val))
+
+ RefPtr<nsHostRecord> rec = mQueue.Dequeue(true, lock);
+ if (rec) {
+ SET_GET_TTL(rec, false);
+ rec.forget(result);
+ return true;
+ }
+
+ if (mActiveAnyThreadCount < MaxResolverThreadsAnyPriority()) {
+ rec = mQueue.Dequeue(false, lock);
+ if (rec) {
+ MOZ_ASSERT(IsMediumPriority(rec->flags) || IsLowPriority(rec->flags));
+ mActiveAnyThreadCount++;
+ rec->StoreUsingAnyThread(true);
+ SET_GET_TTL(rec, true);
+ rec.forget(result);
+ return true;
+ }
+ }
+
+ // Determining timeout is racy, so allow one cycle through checking the
+ // queues before exiting.
+ if (timedOut) {
+ break;
+ }
+
+ // wait for one or more of the following to occur:
+ // (1) the pending queue has a host record to process
+ // (2) the shutdown flag has been set
+ // (3) the thread has been idle for too long
+
+ mNumIdleTasks++;
+ mIdleTaskCV.Wait(timeout);
+ mNumIdleTasks--;
+
+ now = TimeStamp::Now();
+
+ if (now - epoch >= timeout) {
+ timedOut = true;
+ } else {
+ // It is possible that CondVar::Wait() was interrupted and returned
+ // early, in which case we will loop back and re-enter it. In that
+ // case we want to do so with the new timeout reduced to reflect
+ // time already spent waiting.
+ timeout -= now - epoch;
+ epoch = now;
+ }
+ }
+
+ // tell thread to exit...
+ return false;
+}
+
+void nsHostResolver::PrepareRecordExpirationAddrRecord(
+ AddrHostRecord* rec) const {
+ // NOTE: rec->addr_info_lock is already held by parent
+ MOZ_ASSERT(((bool)rec->addr_info) != rec->negative);
+ mLock.AssertCurrentThreadOwns();
+ if (!rec->addr_info) {
+ rec->SetExpiration(TimeStamp::NowLoRes(), NEGATIVE_RECORD_LIFETIME, 0);
+ LOG(("Caching host [%s] negative record for %u seconds.\n", rec->host.get(),
+ NEGATIVE_RECORD_LIFETIME));
+ return;
+ }
+
+ unsigned int lifetime = mDefaultCacheLifetime;
+ unsigned int grace = mDefaultGracePeriod;
+
+ unsigned int ttl = mDefaultCacheLifetime;
+ if (sGetTtlEnabled || rec->addr_info->IsTRR()) {
+ if (rec->addr_info && rec->addr_info->TTL() != AddrInfo::NO_TTL_DATA) {
+ ttl = rec->addr_info->TTL();
+ }
+ lifetime = ttl;
+ grace = 0;
+ }
+
+ rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace);
+ LOG(("Caching host [%s] record for %u seconds (grace %d).", rec->host.get(),
+ lifetime, grace));
+}
+
+static bool different_rrset(AddrInfo* rrset1, AddrInfo* rrset2) {
+ if (!rrset1 || !rrset2) {
+ return true;
+ }
+
+ LOG(("different_rrset %s\n", rrset1->Hostname().get()));
+
+ if (rrset1->ResolverType() != rrset2->ResolverType()) {
+ return true;
+ }
+
+ if (rrset1->TRRType() != rrset2->TRRType()) {
+ return true;
+ }
+
+ if (rrset1->Addresses().Length() != rrset2->Addresses().Length()) {
+ LOG(("different_rrset true due to length change\n"));
+ return true;
+ }
+
+ nsTArray<NetAddr> orderedSet1 = rrset1->Addresses().Clone();
+ nsTArray<NetAddr> orderedSet2 = rrset2->Addresses().Clone();
+ orderedSet1.Sort();
+ orderedSet2.Sort();
+
+ bool eq = orderedSet1 == orderedSet2;
+ if (!eq) {
+ LOG(("different_rrset true due to content change\n"));
+ } else {
+ LOG(("different_rrset false\n"));
+ }
+ return !eq;
+}
+
+void nsHostResolver::AddToEvictionQ(nsHostRecord* rec,
+ const MutexAutoLock& aLock) {
+ mQueue.AddToEvictionQ(rec, mMaxCacheEntries, mRecordDB, aLock);
+}
+
+// After a first lookup attempt with TRR in mode 2, we may:
+// - If network.trr.retry_on_recoverable_errors is false, retry with native.
+// - If network.trr.retry_on_recoverable_errors is true:
+// - Retry with native if the first attempt failed because we got NXDOMAIN, an
+// unreachable address (TRR_DISABLED_FLAG), or we skipped TRR because
+// Confirmation failed.
+// - Trigger a "RetryTRR" Confirmation which will start a fresh
+// connection for TRR, and then retry the lookup with TRR.
+// - If the second attempt failed, fallback to native if
+// network.trr.strict_native_fallback is false.
+// Returns true if we retried with either TRR or Native.
+bool nsHostResolver::MaybeRetryTRRLookup(
+ AddrHostRecord* aAddrRec, nsresult aFirstAttemptStatus,
+ TRRSkippedReason aFirstAttemptSkipReason, nsresult aChannelStatus,
+ const MutexAutoLock& aLock) {
+ if (NS_FAILED(aFirstAttemptStatus) &&
+ (aChannelStatus == NS_ERROR_PROXY_UNAUTHORIZED ||
+ aChannelStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED) &&
+ aAddrRec->mEffectiveTRRMode == nsIRequest::TRR_ONLY_MODE) {
+ LOG(("MaybeRetryTRRLookup retry because of proxy connect failed"));
+ TRRService::Get()->DontUseTRRThread();
+ return DoRetryTRR(aAddrRec, aLock);
+ }
+
+ if (NS_SUCCEEDED(aFirstAttemptStatus) ||
+ aAddrRec->mEffectiveTRRMode != nsIRequest::TRR_FIRST_MODE ||
+ aFirstAttemptStatus == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
+ return false;
+ }
+
+ MOZ_ASSERT(!aAddrRec->mResolving);
+ if (!StaticPrefs::network_trr_retry_on_recoverable_errors()) {
+ LOG(("nsHostResolver::MaybeRetryTRRLookup retrying with native"));
+ return NS_SUCCEEDED(NativeLookup(aAddrRec, aLock));
+ }
+
+ if (IsFailedConfirmationOrNoConnectivity(aFirstAttemptSkipReason) ||
+ IsNonRecoverableTRRSkipReason(aFirstAttemptSkipReason) ||
+ IsBlockedTRRRequest(aFirstAttemptSkipReason)) {
+ LOG(
+ ("nsHostResolver::MaybeRetryTRRLookup retrying with native in strict "
+ "mode, skip reason was %d",
+ static_cast<uint32_t>(aFirstAttemptSkipReason)));
+ return NS_SUCCEEDED(NativeLookup(aAddrRec, aLock));
+ }
+
+ if (aAddrRec->mTrrAttempts > 1) {
+ if (!StaticPrefs::network_trr_strict_native_fallback()) {
+ LOG(
+ ("nsHostResolver::MaybeRetryTRRLookup retry failed. Using "
+ "native."));
+ return NS_SUCCEEDED(NativeLookup(aAddrRec, aLock));
+ }
+
+ if (aFirstAttemptSkipReason == TRRSkippedReason::TRR_TIMEOUT &&
+ StaticPrefs::network_trr_strict_native_fallback_allow_timeouts()) {
+ LOG(
+ ("nsHostResolver::MaybeRetryTRRLookup retry timed out. Using "
+ "native."));
+ return NS_SUCCEEDED(NativeLookup(aAddrRec, aLock));
+ }
+ LOG(("nsHostResolver::MaybeRetryTRRLookup mTrrAttempts>1, not retrying."));
+ return false;
+ }
+
+ LOG(
+ ("nsHostResolver::MaybeRetryTRRLookup triggering Confirmation and "
+ "retrying with TRR, skip reason was %d",
+ static_cast<uint32_t>(aFirstAttemptSkipReason)));
+ TRRService::Get()->RetryTRRConfirm();
+
+ return DoRetryTRR(aAddrRec, aLock);
+}
+
+bool nsHostResolver::DoRetryTRR(AddrHostRecord* aAddrRec,
+ const mozilla::MutexAutoLock& aLock) {
+ {
+ // Clear out the old query
+ auto trrQuery = aAddrRec->mTRRQuery.Lock();
+ trrQuery.ref() = nullptr;
+ }
+
+ if (NS_SUCCEEDED(TrrLookup(aAddrRec, aLock, nullptr /* pushedTRR */))) {
+ aAddrRec->NotifyRetryingTrr();
+ return true;
+ }
+
+ return false;
+}
+
+//
+// CompleteLookup() checks if the resolving should be redone and if so it
+// returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
+nsHostResolver::LookupStatus nsHostResolver::CompleteLookup(
+ nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
+ const nsACString& aOriginsuffix, TRRSkippedReason aReason,
+ mozilla::net::TRR* aTRRRequest) {
+ MutexAutoLock lock(mLock);
+ return CompleteLookupLocked(rec, status, aNewRRSet, pb, aOriginsuffix,
+ aReason, aTRRRequest, lock);
+}
+
+nsHostResolver::LookupStatus nsHostResolver::CompleteLookupLocked(
+ nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
+ const nsACString& aOriginsuffix, TRRSkippedReason aReason,
+ mozilla::net::TRR* aTRRRequest, const mozilla::MutexAutoLock& aLock) {
+ MOZ_ASSERT(rec);
+ MOZ_ASSERT(rec->pb == pb);
+ MOZ_ASSERT(rec->IsAddrRecord());
+
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ MOZ_ASSERT(addrRec);
+
+ RefPtr<AddrInfo> newRRSet(aNewRRSet);
+ MOZ_ASSERT(NS_FAILED(status) || newRRSet->Addresses().Length() > 0);
+
+ DNSResolverType type =
+ newRRSet ? newRRSet->ResolverType() : DNSResolverType::Native;
+
+ if (NS_FAILED(status)) {
+ newRRSet = nullptr;
+ }
+
+ if (addrRec->LoadResolveAgain() && (status != NS_ERROR_ABORT) &&
+ type == DNSResolverType::Native) {
+ LOG(("nsHostResolver record %p resolve again due to flushcache\n",
+ addrRec.get()));
+ addrRec->StoreResolveAgain(false);
+ return LOOKUP_RESOLVEAGAIN;
+ }
+
+ MOZ_ASSERT(addrRec->mResolving);
+ addrRec->mResolving--;
+ LOG((
+ "nsHostResolver::CompleteLookup %s %p %X resolver=%d stillResolving=%d\n",
+ addrRec->host.get(), aNewRRSet, (unsigned int)status, (int)type,
+ int(addrRec->mResolving)));
+
+ if (type != DNSResolverType::Native) {
+ if (NS_FAILED(status) && status != NS_ERROR_UNKNOWN_HOST &&
+ status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
+ // the errors are not failed resolves, that means
+ // something else failed, consider this as *TRR not used*
+ // for actually trying to resolve the host
+ addrRec->mResolverType = DNSResolverType::Native;
+ }
+
+ if (NS_FAILED(status)) {
+ if (aReason != TRRSkippedReason::TRR_UNSET) {
+ addrRec->RecordReason(aReason);
+ } else {
+ // Unknown failed reason.
+ addrRec->RecordReason(TRRSkippedReason::TRR_FAILED);
+ }
+ } else {
+ addrRec->mTRRSuccess = true;
+ addrRec->RecordReason(TRRSkippedReason::TRR_OK);
+ }
+
+ nsresult channelStatus = aTRRRequest->ChannelStatus();
+ if (MaybeRetryTRRLookup(addrRec, status, aReason, channelStatus, aLock)) {
+ MOZ_ASSERT(addrRec->mResolving);
+ return LOOKUP_OK;
+ }
+
+ if (!addrRec->mTRRSuccess) {
+ // no TRR success
+ newRRSet = nullptr;
+ }
+
+ if (NS_FAILED(status)) {
+ // This is the error that consumers expect.
+ status = NS_ERROR_UNKNOWN_HOST;
+ }
+ } else { // native resolve completed
+ if (addrRec->LoadUsingAnyThread()) {
+ mActiveAnyThreadCount--;
+ addrRec->StoreUsingAnyThread(false);
+ }
+
+ addrRec->mNativeSuccess = static_cast<bool>(newRRSet);
+ if (addrRec->mNativeSuccess) {
+ addrRec->mNativeDuration = TimeStamp::Now() - addrRec->mNativeStart;
+ }
+ }
+
+ addrRec->OnCompleteLookup();
+
+ // update record fields. We might have a addrRec->addr_info already if a
+ // previous lookup result expired and we're reresolving it or we get
+ // a late second TRR response.
+ if (!mShutdown) {
+ MutexAutoLock lock(addrRec->addr_info_lock);
+ RefPtr<AddrInfo> old_addr_info;
+ if (different_rrset(addrRec->addr_info, newRRSet)) {
+ LOG(("nsHostResolver record %p new gencnt\n", addrRec.get()));
+ old_addr_info = addrRec->addr_info;
+ addrRec->addr_info = std::move(newRRSet);
+ addrRec->addr_info_gencnt++;
+ } else {
+ if (addrRec->addr_info && newRRSet) {
+ auto builder = addrRec->addr_info->Build();
+ builder.SetTTL(newRRSet->TTL());
+ // Update trr timings
+ builder.SetTrrFetchDuration(newRRSet->GetTrrFetchDuration());
+ builder.SetTrrFetchDurationNetworkOnly(
+ newRRSet->GetTrrFetchDurationNetworkOnly());
+
+ addrRec->addr_info = builder.Finish();
+ }
+ old_addr_info = std::move(newRRSet);
+ }
+ addrRec->negative = !addrRec->addr_info;
+ PrepareRecordExpirationAddrRecord(addrRec);
+ }
+
+ if (LOG_ENABLED()) {
+ MutexAutoLock lock(addrRec->addr_info_lock);
+ if (addrRec->addr_info) {
+ for (const auto& elem : addrRec->addr_info->Addresses()) {
+ char buf[128];
+ elem.ToStringBuffer(buf, sizeof(buf));
+ LOG(("CompleteLookup: %s has %s\n", addrRec->host.get(), buf));
+ }
+ } else {
+ LOG(("CompleteLookup: %s has NO address\n", addrRec->host.get()));
+ }
+ }
+
+ // get the list of pending callbacks for this lookup, and notify
+ // them that the lookup is complete.
+ mozilla::LinkedList<RefPtr<nsResolveHostCallback>> cbs =
+ std::move(rec->mCallbacks);
+
+ LOG(("nsHostResolver record %p calling back dns users status:%X\n",
+ addrRec.get(), int(status)));
+
+ for (nsResolveHostCallback* c = cbs.getFirst(); c;
+ c = c->removeAndGetNext()) {
+ c->OnResolveHostComplete(this, rec, status);
+ }
+
+ OnResolveComplete(rec, aLock);
+
+#ifdef DNSQUERY_AVAILABLE
+ // Unless the result is from TRR, resolve again to get TTL
+ bool hasNativeResult = false;
+ {
+ MutexAutoLock lock(addrRec->addr_info_lock);
+ if (addrRec->addr_info && !addrRec->addr_info->IsTRR()) {
+ hasNativeResult = true;
+ }
+ }
+ if (hasNativeResult && !mShutdown && !addrRec->LoadGetTtl() &&
+ !rec->mResolving && sGetTtlEnabled) {
+ LOG(("Issuing second async lookup for TTL for host [%s].",
+ addrRec->host.get()));
+ addrRec->flags =
+ (addrRec->flags & ~nsIDNSService::RESOLVE_PRIORITY_MEDIUM) |
+ nsIDNSService::RESOLVE_PRIORITY_LOW;
+ DebugOnly<nsresult> rv = NativeLookup(rec, aLock);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Could not issue second async lookup for TTL.");
+ }
+#endif
+ return LOOKUP_OK;
+}
+
+nsHostResolver::LookupStatus nsHostResolver::CompleteLookupByType(
+ nsHostRecord* rec, nsresult status,
+ mozilla::net::TypeRecordResultType& aResult, TRRSkippedReason aReason,
+ uint32_t aTtl, bool pb) {
+ MutexAutoLock lock(mLock);
+ return CompleteLookupByTypeLocked(rec, status, aResult, aReason, aTtl, pb,
+ lock);
+}
+
+nsHostResolver::LookupStatus nsHostResolver::CompleteLookupByTypeLocked(
+ nsHostRecord* rec, nsresult status,
+ mozilla::net::TypeRecordResultType& aResult, TRRSkippedReason aReason,
+ uint32_t aTtl, bool pb, const mozilla::MutexAutoLock& aLock) {
+ MOZ_ASSERT(rec);
+ MOZ_ASSERT(rec->pb == pb);
+ MOZ_ASSERT(!rec->IsAddrRecord());
+
+ RefPtr<TypeHostRecord> typeRec = do_QueryObject(rec);
+ MOZ_ASSERT(typeRec);
+
+ MOZ_ASSERT(typeRec->mResolving);
+ typeRec->mResolving--;
+
+ if (NS_FAILED(status)) {
+ LOG(("nsHostResolver::CompleteLookupByType record %p [%s] status %x\n",
+ typeRec.get(), typeRec->host.get(), (unsigned int)status));
+ typeRec->SetExpiration(
+ TimeStamp::NowLoRes(),
+ StaticPrefs::network_dns_negative_ttl_for_type_record(), 0);
+ MOZ_ASSERT(aResult.is<TypeRecordEmpty>());
+ status = NS_ERROR_UNKNOWN_HOST;
+ typeRec->negative = true;
+ if (aReason != TRRSkippedReason::TRR_UNSET) {
+ typeRec->RecordReason(aReason);
+ } else {
+ // Unknown failed reason.
+ typeRec->RecordReason(TRRSkippedReason::TRR_FAILED);
+ }
+ } else {
+ size_t recordCount = 0;
+ if (aResult.is<TypeRecordTxt>()) {
+ recordCount = aResult.as<TypeRecordTxt>().Length();
+ } else if (aResult.is<TypeRecordHTTPSSVC>()) {
+ recordCount = aResult.as<TypeRecordHTTPSSVC>().Length();
+ }
+ LOG(
+ ("nsHostResolver::CompleteLookupByType record %p [%s], number of "
+ "records %zu\n",
+ typeRec.get(), typeRec->host.get(), recordCount));
+ MutexAutoLock typeLock(typeRec->mResultsLock);
+ typeRec->mResults = aResult;
+ typeRec->SetExpiration(TimeStamp::NowLoRes(), aTtl, mDefaultGracePeriod);
+ typeRec->negative = false;
+ typeRec->mTRRSuccess = !rec->LoadNative();
+ typeRec->mNativeSuccess = rec->LoadNative();
+ MOZ_ASSERT(aReason != TRRSkippedReason::TRR_UNSET);
+ typeRec->RecordReason(aReason);
+ }
+
+ mozilla::LinkedList<RefPtr<nsResolveHostCallback>> cbs =
+ std::move(typeRec->mCallbacks);
+
+ LOG(
+ ("nsHostResolver::CompleteLookupByType record %p calling back dns "
+ "users\n",
+ typeRec.get()));
+
+ for (nsResolveHostCallback* c = cbs.getFirst(); c;
+ c = c->removeAndGetNext()) {
+ c->OnResolveHostComplete(this, rec, status);
+ }
+
+ OnResolveComplete(rec, aLock);
+
+ return LOOKUP_OK;
+}
+
+void nsHostResolver::OnResolveComplete(nsHostRecord* aRec,
+ const mozilla::MutexAutoLock& aLock) {
+ if (!aRec->mResolving && !mShutdown) {
+ {
+ auto trrQuery = aRec->mTRRQuery.Lock();
+ if (trrQuery.ref()) {
+ aRec->mTrrDuration = trrQuery.ref()->Duration();
+ }
+ trrQuery.ref() = nullptr;
+ }
+ aRec->ResolveComplete();
+
+ AddToEvictionQ(aRec, aLock);
+ }
+}
+
+void nsHostResolver::CancelAsyncRequest(
+ const nsACString& host, const nsACString& aTrrServer, uint16_t aType,
+ const OriginAttributes& aOriginAttributes, nsIDNSService::DNSFlags flags,
+ uint16_t af, nsIDNSListener* aListener, nsresult status)
+
+{
+ MutexAutoLock lock(mLock);
+
+ nsAutoCString originSuffix;
+ aOriginAttributes.CreateSuffix(originSuffix);
+
+ // Lookup the host record associated with host, flags & address family
+
+ nsHostKey key(host, aTrrServer, aType, flags, af,
+ (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix);
+ RefPtr<nsHostRecord> rec = mRecordDB.Get(key);
+ if (!rec) {
+ return;
+ }
+
+ for (RefPtr<nsResolveHostCallback> c : rec->mCallbacks) {
+ if (c->EqualsAsyncListener(aListener)) {
+ c->remove();
+ c->OnResolveHostComplete(this, rec.get(), status);
+ break;
+ }
+ }
+
+ // If there are no more callbacks, remove the hash table entry
+ if (rec->mCallbacks.isEmpty()) {
+ mRecordDB.Remove(*static_cast<nsHostKey*>(rec.get()));
+ // If record is on a Queue, remove it
+ mQueue.MaybeRemoveFromQ(rec, lock);
+ }
+}
+
+size_t nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
+ MutexAutoLock lock(mLock);
+
+ size_t n = mallocSizeOf(this);
+
+ n += mRecordDB.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& entry : mRecordDB.Values()) {
+ n += entry->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // The following fields aren't measured.
+ // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to
+ // nsHostRecords that also pointed to by entries |mRecordDB|, and
+ // measured when |mRecordDB| is measured.
+
+ return n;
+}
+
+void nsHostResolver::ThreadFunc() {
+ LOG(("DNS lookup thread - starting execution.\n"));
+
+#if defined(RES_RETRY_ON_FAILURE)
+ nsResState rs;
+#endif
+ RefPtr<nsHostRecord> rec;
+ RefPtr<AddrInfo> ai;
+
+ do {
+ if (!rec) {
+ RefPtr<nsHostRecord> tmpRec;
+ if (!GetHostToLookup(getter_AddRefs(tmpRec))) {
+ break; // thread shutdown signal
+ }
+ // GetHostToLookup() returns an owning reference
+ MOZ_ASSERT(tmpRec);
+ rec.swap(tmpRec);
+ }
+
+ LOG1(("DNS lookup thread - Calling getaddrinfo for host [%s].\n",
+ rec->host.get()));
+
+ TimeStamp startTime = TimeStamp::Now();
+ bool getTtl = rec->LoadGetTtl();
+ TimeDuration inQueue = startTime - rec->mNativeStart;
+ uint32_t ms = static_cast<uint32_t>(inQueue.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::DNS_NATIVE_QUEUING, ms);
+
+ if (!rec->IsAddrRecord()) {
+ LOG(("byType on DNS thread"));
+ TypeRecordResultType result = AsVariant(mozilla::Nothing());
+ uint32_t ttl = UINT32_MAX;
+ nsresult status = ResolveHTTPSRecord(rec->host, rec->flags, result, ttl);
+ mozilla::glean::networking::dns_native_count
+ .EnumGet(rec->pb
+ ? glean::networking::DnsNativeCountLabel::eHttpsPrivate
+ : glean::networking::DnsNativeCountLabel::eHttpsRegular)
+ .Add(1);
+ CompleteLookupByType(rec, status, result, rec->mTRRSkippedReason, ttl,
+ rec->pb);
+ rec = nullptr;
+ continue;
+ }
+
+ nsresult status =
+ GetAddrInfo(rec->host, rec->af, rec->flags, getter_AddRefs(ai), getTtl);
+#if defined(RES_RETRY_ON_FAILURE)
+ if (NS_FAILED(status) && rs.Reset()) {
+ status = GetAddrInfo(rec->host, rec->af, rec->flags, getter_AddRefs(ai),
+ getTtl);
+ }
+#endif
+
+ mozilla::glean::networking::dns_native_count
+ .EnumGet(rec->pb ? glean::networking::DnsNativeCountLabel::ePrivate
+ : glean::networking::DnsNativeCountLabel::eRegular)
+ .Add(1);
+
+ if (RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec)) {
+ // obtain lock to check shutdown and manage inter-module telemetry
+ MutexAutoLock lock(mLock);
+
+ if (!mShutdown) {
+ TimeDuration elapsed = TimeStamp::Now() - startTime;
+ if (NS_SUCCEEDED(status)) {
+ if (!addrRec->addr_info_gencnt) {
+ // Time for initial lookup.
+ glean::networking::dns_lookup_time.AccumulateRawDuration(elapsed);
+ } else if (!getTtl) {
+ // Time for renewal; categorized by expiration strategy.
+ glean::networking::dns_renewal_time.AccumulateRawDuration(elapsed);
+ } else {
+ // Time to get TTL; categorized by expiration strategy.
+ glean::networking::dns_renewal_time_for_ttl.AccumulateRawDuration(
+ elapsed);
+ }
+ } else {
+ glean::networking::dns_failed_lookup_time.AccumulateRawDuration(
+ elapsed);
+ }
+ }
+ }
+
+ LOG1(("DNS lookup thread - lookup completed for host [%s]: %s.\n",
+ rec->host.get(), ai ? "success" : "failure: unknown host"));
+
+ if (LOOKUP_RESOLVEAGAIN ==
+ CompleteLookup(rec, status, ai, rec->pb, rec->originSuffix,
+ rec->mTRRSkippedReason, nullptr)) {
+ // leave 'rec' assigned and loop to make a renewed host resolve
+ LOG(("DNS lookup thread - Re-resolving host [%s].\n", rec->host.get()));
+ } else {
+ rec = nullptr;
+ }
+ } while (true);
+
+ MutexAutoLock lock(mLock);
+ mActiveTaskCount--;
+ LOG(("DNS lookup thread - queue empty, task finished.\n"));
+}
+
+void nsHostResolver::SetCacheLimits(uint32_t aMaxCacheEntries,
+ uint32_t aDefaultCacheEntryLifetime,
+ uint32_t aDefaultGracePeriod) {
+ MutexAutoLock lock(mLock);
+ mMaxCacheEntries = aMaxCacheEntries;
+ mDefaultCacheLifetime = aDefaultCacheEntryLifetime;
+ mDefaultGracePeriod = aDefaultGracePeriod;
+}
+
+nsresult nsHostResolver::Create(uint32_t maxCacheEntries,
+ uint32_t defaultCacheEntryLifetime,
+ uint32_t defaultGracePeriod,
+ nsHostResolver** result) {
+ RefPtr<nsHostResolver> res = new nsHostResolver(
+ maxCacheEntries, defaultCacheEntryLifetime, defaultGracePeriod);
+
+ nsresult rv = res->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ res.forget(result);
+ return NS_OK;
+}
+
+void nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries>* args) {
+ MutexAutoLock lock(mLock);
+ for (const auto& recordEntry : mRecordDB) {
+ // We don't pay attention to address literals, only resolved domains.
+ // Also require a host.
+ nsHostRecord* rec = recordEntry.GetWeak();
+ MOZ_ASSERT(rec, "rec should never be null here!");
+
+ if (!rec) {
+ continue;
+ }
+
+ // For now we only show A/AAAA records.
+ if (!rec->IsAddrRecord()) {
+ continue;
+ }
+
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ MOZ_ASSERT(addrRec);
+ if (!addrRec || !addrRec->addr_info) {
+ continue;
+ }
+
+ DNSCacheEntries info;
+ info.hostname = rec->host;
+ info.family = rec->af;
+ info.expiration =
+ (int64_t)(rec->mValidEnd - TimeStamp::NowLoRes()).ToSeconds();
+ if (info.expiration <= 0) {
+ // We only need valid DNS cache entries
+ continue;
+ }
+
+ {
+ MutexAutoLock lock(addrRec->addr_info_lock);
+ for (const auto& addr : addrRec->addr_info->Addresses()) {
+ char buf[kIPv6CStrBufSize];
+ if (addr.ToStringBuffer(buf, sizeof(buf))) {
+ info.hostaddr.AppendElement(buf);
+ }
+ }
+ info.TRR = addrRec->addr_info->IsTRR();
+ }
+
+ info.originAttributesSuffix = recordEntry.GetKey().originSuffix;
+ info.flags = nsPrintfCString("%u|0x%x|%u|%d|%s", rec->type, rec->flags,
+ rec->af, rec->pb, rec->mTrrServer.get());
+
+ args->AppendElement(std::move(info));
+ }
+}
+
+#undef LOG
+#undef LOG_ENABLED
diff --git a/netwerk/dns/nsHostResolver.h b/netwerk/dns/nsHostResolver.h
new file mode 100644
index 0000000000..02e6a343f8
--- /dev/null
+++ b/netwerk/dns/nsHostResolver.h
@@ -0,0 +1,344 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHostResolver_h__
+#define nsHostResolver_h__
+
+#include "nscore.h"
+#include "prnetdb.h"
+#include "PLDHashTable.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/DataMutex.h"
+#include "nsISupportsImpl.h"
+#include "nsIDNSListener.h"
+#include "nsTArray.h"
+#include "GetAddrInfo.h"
+#include "HostRecordQueue.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsHostRecord.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIThreadPool.h"
+#include "mozilla/net/NetworkConnectivityService.h"
+#include "mozilla/net/DNSByTypeRecord.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla {
+namespace net {
+class TRR;
+class TRRQuery;
+
+static inline uint32_t MaxResolverThreadsAnyPriority() {
+ return StaticPrefs::network_dns_max_any_priority_threads();
+}
+
+static inline uint32_t MaxResolverThreadsHighPriority() {
+ return StaticPrefs::network_dns_max_high_priority_threads();
+}
+
+static inline uint32_t MaxResolverThreads() {
+ return MaxResolverThreadsAnyPriority() + MaxResolverThreadsHighPriority();
+}
+
+} // namespace net
+} // namespace mozilla
+
+#define TRR_DISABLED(x) \
+ (((x) == nsIDNSService::MODE_NATIVEONLY) || \
+ ((x) == nsIDNSService::MODE_TRROFF))
+
+extern mozilla::Atomic<bool, mozilla::Relaxed> gNativeIsLocalhost;
+
+#define MAX_NON_PRIORITY_REQUESTS 150
+
+class AHostResolver {
+ public:
+ AHostResolver() = default;
+ virtual ~AHostResolver() = default;
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ enum LookupStatus {
+ LOOKUP_OK,
+ LOOKUP_RESOLVEAGAIN,
+ };
+
+ virtual LookupStatus CompleteLookup(nsHostRecord*, nsresult,
+ mozilla::net::AddrInfo*, bool pb,
+ const nsACString& aOriginsuffix,
+ mozilla::net::TRRSkippedReason aReason,
+ mozilla::net::TRR*) = 0;
+ virtual LookupStatus CompleteLookupByType(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ mozilla::net::TRRSkippedReason aReason, uint32_t aTtl, bool pb) = 0;
+ virtual nsresult GetHostRecord(const nsACString& host,
+ const nsACString& aTrrServer, uint16_t type,
+ nsIDNSService::DNSFlags flags, uint16_t af,
+ bool pb, const nsCString& originSuffix,
+ nsHostRecord** result) {
+ return NS_ERROR_FAILURE;
+ }
+ virtual nsresult TrrLookup_unlocked(nsHostRecord*,
+ mozilla::net::TRR* pushedTRR = nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+ virtual void MaybeRenewHostRecord(nsHostRecord* aRec) {}
+};
+
+/**
+ * nsHostResolver - an asynchronous host name resolver.
+ */
+class nsHostResolver : public nsISupports, public AHostResolver {
+ using CondVar = mozilla::CondVar;
+ using Mutex = mozilla::Mutex;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /**
+ * creates an addref'd instance of a nsHostResolver object.
+ */
+ static nsresult Create(uint32_t maxCacheEntries, // zero disables cache
+ uint32_t defaultCacheEntryLifetime, // seconds
+ uint32_t defaultGracePeriod, // seconds
+ nsHostResolver** result);
+
+ /**
+ * Set (new) cache limits.
+ */
+ void SetCacheLimits(uint32_t maxCacheEntries, // zero disables cache
+ uint32_t defaultCacheEntryLifetime, // seconds
+ uint32_t defaultGracePeriod); // seconds
+
+ /**
+ * puts the resolver in the shutdown state, which will cause any pending
+ * callbacks to be detached. any future calls to ResolveHost will fail.
+ */
+ void Shutdown();
+
+ /**
+ * resolve the given hostname and originAttributes asynchronously. the caller
+ * can synthesize a synchronous host lookup using a lock and a cvar. as noted
+ * above the callback will occur re-entrantly from an unspecified thread. the
+ * host lookup cannot be canceled (cancelation can be layered above this by
+ * having the callback implementation return without doing anything).
+ */
+ nsresult ResolveHost(const nsACString& aHost, const nsACString& trrServer,
+ int32_t aPort, uint16_t type,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsIDNSService::DNSFlags flags, uint16_t af,
+ nsResolveHostCallback* callback);
+
+ nsHostRecord* InitRecord(const nsHostKey& key);
+ mozilla::net::NetworkConnectivityService* GetNCS() {
+ return mNCS;
+ } // This is actually a singleton
+
+ /**
+ * return a resolved hard coded loopback dns record for the specified key
+ */
+ already_AddRefed<nsHostRecord> InitLoopbackRecord(const nsHostKey& key,
+ nsresult* aRv);
+
+ /**
+ * removes the specified callback from the nsHostRecord for the given
+ * hostname, originAttributes, flags, and address family. these parameters
+ * should correspond to the parameters passed to ResolveHost. this function
+ * executes the callback if the callback is still pending with the given
+ * status.
+ */
+ void DetachCallback(const nsACString& hostname, const nsACString& trrServer,
+ uint16_t type,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsIDNSService::DNSFlags flags, uint16_t af,
+ nsResolveHostCallback* callback, nsresult status);
+
+ /**
+ * Cancels an async request associated with the hostname, originAttributes,
+ * flags, address family and listener. Cancels first callback found which
+ * matches these criteria. These parameters should correspond to the
+ * parameters passed to ResolveHost. If this is the last callback associated
+ * with the host record, it is removed from any request queues it might be on.
+ */
+ void CancelAsyncRequest(const nsACString& host, const nsACString& trrServer,
+ uint16_t type,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsIDNSService::DNSFlags flags, uint16_t af,
+ nsIDNSListener* aListener, nsresult status);
+ /**
+ * values for the flags parameter passed to ResolveHost and DetachCallback
+ * that may be bitwise OR'd together.
+ *
+ * NOTE: in this implementation, these flags correspond exactly in value
+ * to the flags defined on nsIDNSService.
+ */
+ enum {
+ RES_BYPASS_CACHE = nsIDNSService::RESOLVE_BYPASS_CACHE,
+ RES_CANON_NAME = nsIDNSService::RESOLVE_CANONICAL_NAME,
+ RES_PRIORITY_MEDIUM = nsHostRecord::DNS_PRIORITY_MEDIUM,
+ RES_PRIORITY_LOW = nsHostRecord::DNS_PRIORITY_LOW,
+ RES_SPECULATE = nsIDNSService::RESOLVE_SPECULATE,
+ // RES_DISABLE_IPV6 = nsIDNSService::RESOLVE_DISABLE_IPV6, // Not used
+ RES_OFFLINE = nsIDNSService::RESOLVE_OFFLINE,
+ // RES_DISABLE_IPv4 = nsIDNSService::RESOLVE_DISABLE_IPV4, // Not Used
+ RES_ALLOW_NAME_COLLISION = nsIDNSService::RESOLVE_ALLOW_NAME_COLLISION,
+ RES_DISABLE_TRR = nsIDNSService::RESOLVE_DISABLE_TRR,
+ RES_REFRESH_CACHE = nsIDNSService::RESOLVE_REFRESH_CACHE,
+ RES_IP_HINT = nsIDNSService::RESOLVE_IP_HINT
+ };
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ /**
+ * Flush the DNS cache.
+ */
+ void FlushCache(bool aTrrToo);
+
+ LookupStatus CompleteLookup(nsHostRecord*, nsresult, mozilla::net::AddrInfo*,
+ bool pb, const nsACString& aOriginsuffix,
+ mozilla::net::TRRSkippedReason aReason,
+ mozilla::net::TRR* aTRRRequest) override;
+ LookupStatus CompleteLookupByType(nsHostRecord*, nsresult,
+ mozilla::net::TypeRecordResultType& aResult,
+ mozilla::net::TRRSkippedReason aReason,
+ uint32_t aTtl, bool pb) override;
+ nsresult GetHostRecord(const nsACString& host, const nsACString& trrServer,
+ uint16_t type, nsIDNSService::DNSFlags flags,
+ uint16_t af, bool pb, const nsCString& originSuffix,
+ nsHostRecord** result) override;
+ nsresult TrrLookup_unlocked(nsHostRecord*,
+ mozilla::net::TRR* pushedTRR = nullptr) override;
+ static nsIDNSService::ResolverMode Mode();
+
+ virtual void MaybeRenewHostRecord(nsHostRecord* aRec) override;
+
+ // Records true if the TRR service is enabled for the record's effective
+ // TRR mode. Also records the TRRSkipReason when the TRR service is not
+ // available/enabled.
+ bool TRRServiceEnabledForRecord(nsHostRecord* aRec) MOZ_REQUIRES(mLock);
+
+ private:
+ explicit nsHostResolver(uint32_t maxCacheEntries,
+ uint32_t defaultCacheEntryLifetime,
+ uint32_t defaultGracePeriod);
+ virtual ~nsHostResolver();
+
+ bool DoRetryTRR(AddrHostRecord* aAddrRec,
+ const mozilla::MutexAutoLock& aLock);
+ bool MaybeRetryTRRLookup(
+ AddrHostRecord* aAddrRec, nsresult aFirstAttemptStatus,
+ mozilla::net::TRRSkippedReason aFirstAttemptSkipReason,
+ nsresult aChannelStatus, const mozilla::MutexAutoLock& aLock);
+
+ LookupStatus CompleteLookupLocked(nsHostRecord*, nsresult,
+ mozilla::net::AddrInfo*, bool pb,
+ const nsACString& aOriginsuffix,
+ mozilla::net::TRRSkippedReason aReason,
+ mozilla::net::TRR* aTRRRequest,
+ const mozilla::MutexAutoLock& aLock)
+ MOZ_REQUIRES(mLock);
+ LookupStatus CompleteLookupByTypeLocked(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ mozilla::net::TRRSkippedReason aReason, uint32_t aTtl, bool pb,
+ const mozilla::MutexAutoLock& aLock) MOZ_REQUIRES(mLock);
+ nsresult Init();
+ static void ComputeEffectiveTRRMode(nsHostRecord* aRec);
+ nsresult NativeLookup(nsHostRecord* aRec,
+ const mozilla::MutexAutoLock& aLock);
+ nsresult TrrLookup(nsHostRecord*, const mozilla::MutexAutoLock& aLock,
+ mozilla::net::TRR* pushedTRR = nullptr);
+
+ // Kick-off a name resolve operation, using native resolver and/or TRR
+ nsresult NameLookup(nsHostRecord* aRec, const mozilla::MutexAutoLock& aLock);
+ bool GetHostToLookup(nsHostRecord** result);
+ void MaybeRenewHostRecordLocked(nsHostRecord* aRec,
+ const mozilla::MutexAutoLock& aLock)
+ MOZ_REQUIRES(mLock);
+
+ // Cancels host records in the pending queue and also
+ // calls CompleteLookup with the NS_ERROR_ABORT result code.
+ void ClearPendingQueue(mozilla::LinkedList<RefPtr<nsHostRecord>>& aPendingQ);
+ nsresult ConditionallyCreateThread(nsHostRecord* rec) MOZ_REQUIRES(mLock);
+
+ /**
+ * Starts a new lookup in the background for entries that are in the grace
+ * period with a failed connect or all cached entries are negative.
+ */
+ nsresult ConditionallyRefreshRecord(nsHostRecord* rec, const nsACString& host,
+ const mozilla::MutexAutoLock& aLock)
+ MOZ_REQUIRES(mLock);
+
+ void OnResolveComplete(nsHostRecord* aRec,
+ const mozilla::MutexAutoLock& aLock)
+ MOZ_REQUIRES(mLock);
+
+ void AddToEvictionQ(nsHostRecord* rec, const mozilla::MutexAutoLock& aLock)
+ MOZ_REQUIRES(mLock);
+
+ void ThreadFunc();
+
+ // Resolve the host from the DNS cache.
+ already_AddRefed<nsHostRecord> FromCache(nsHostRecord* aRec,
+ const nsACString& aHost,
+ uint16_t aType, nsresult& aStatus,
+ const mozilla::MutexAutoLock& aLock)
+ MOZ_REQUIRES(mLock);
+ // Called when the host name is an IP address and has been passed.
+ already_AddRefed<nsHostRecord> FromCachedIPLiteral(nsHostRecord* aRec);
+ // Like the above function, but the host name is not parsed to NetAddr yet.
+ already_AddRefed<nsHostRecord> FromIPLiteral(
+ AddrHostRecord* aAddrRec, const mozilla::net::NetAddr& aAddr);
+ // Called to check if we have an AF_UNSPEC entry in the cache.
+ already_AddRefed<nsHostRecord> FromUnspecEntry(
+ nsHostRecord* aRec, const nsACString& aHost, const nsACString& aTrrServer,
+ const nsACString& aOriginSuffix, uint16_t aType,
+ nsIDNSService::DNSFlags aFlags, uint16_t af, bool aPb, nsresult& aStatus)
+ MOZ_REQUIRES(mLock);
+
+ enum {
+ METHOD_HIT = 1,
+ METHOD_RENEWAL = 2,
+ METHOD_NEGATIVE_HIT = 3,
+ METHOD_LITERAL = 4,
+ METHOD_OVERFLOW = 5,
+ METHOD_NETWORK_FIRST = 6,
+ METHOD_NETWORK_SHARED = 7
+ };
+
+ uint32_t mMaxCacheEntries = 0;
+ uint32_t mDefaultCacheLifetime = 0; // granularity seconds
+ uint32_t mDefaultGracePeriod = 0; // granularity seconds
+ // mutable so SizeOfIncludingThis can be const
+ mutable Mutex mLock{"nsHostResolver.mLock"};
+ CondVar mIdleTaskCV;
+ nsRefPtrHashtable<nsGenericHashKey<nsHostKey>, nsHostRecord> mRecordDB
+ MOZ_GUARDED_BY(mLock);
+ PRTime mCreationTime;
+ mozilla::TimeDuration mLongIdleTimeout;
+ mozilla::TimeDuration mShortIdleTimeout;
+
+ RefPtr<nsIThreadPool> mResolverThreads;
+ RefPtr<mozilla::net::NetworkConnectivityService>
+ mNCS; // reference to a singleton
+ mozilla::net::HostRecordQueue mQueue MOZ_GUARDED_BY(mLock);
+ mozilla::Atomic<bool> mShutdown MOZ_GUARDED_BY(mLock){true};
+ mozilla::Atomic<uint32_t> mNumIdleTasks MOZ_GUARDED_BY(mLock){0};
+ mozilla::Atomic<uint32_t> mActiveTaskCount MOZ_GUARDED_BY(mLock){0};
+ mozilla::Atomic<uint32_t> mActiveAnyThreadCount MOZ_GUARDED_BY(mLock){0};
+
+ // Set the expiration time stamps appropriately.
+ void PrepareRecordExpirationAddrRecord(AddrHostRecord* rec) const;
+
+ public:
+ /*
+ * Called by the networking dashboard via the DnsService2
+ */
+ void GetDNSCacheEntries(nsTArray<mozilla::net::DNSCacheEntries>*);
+};
+
+#endif // nsHostResolver_h__
diff --git a/netwerk/dns/nsIDNKitInterface.h b/netwerk/dns/nsIDNKitInterface.h
new file mode 100644
index 0000000000..3e0f0d60ac
--- /dev/null
+++ b/netwerk/dns/nsIDNKitInterface.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2000-2002 Japan Network Information Center. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set forth bellow.
+ *
+ * LICENSE TERMS AND CONDITIONS
+ *
+ * The following License Terms and Conditions apply, unless a different
+ * license is obtained from Japan Network Information Center ("JPNIC"),
+ * a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
+ * Chiyoda-ku, Tokyo 101-0047, Japan.
+
+ * 1. Use, Modification and Redistribution (including distribution of any
+ * modified or derived work) in source and/or binary forms is permitted
+ * under this License Terms and Conditions.
+ *
+ * 2. Redistribution of source code must retain the copyright notices as they
+ * appear in each source code file, this License Terms and Conditions.
+ *
+ * 3. Redistribution in binary form must reproduce the Copyright Notice,
+ * this License Terms and Conditions, in the documentation and/or other
+ * materials provided with the distribution. For the purposes of binary
+ * distribution the "Copyright Notice" refers to the following language:
+ * "Copyright (c) 2000-2002 Japan Network Information Center. All rights reserved."
+ *
+ * 4. The name of JPNIC may not be used to endorse or promote products
+ * derived from this Software without specific prior written approval of
+ * JPNIC.
+ *
+ * 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JPNIC BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#ifndef nsIDNKitWrapper_h__
+#define nsIDNKitWrapper_h__
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * libidnkit result code.
+ */
+typedef enum {
+ idn_success,
+ idn_notfound,
+ idn_invalid_encoding,
+ idn_invalid_syntax,
+ idn_invalid_name,
+ idn_invalid_message,
+ idn_invalid_action,
+ idn_invalid_codepoint,
+ idn_invalid_length,
+ idn_buffer_overflow,
+ idn_noentry,
+ idn_nomemory,
+ idn_nofile,
+ idn_nomapping,
+ idn_context_required,
+ idn_prohibited,
+ idn_failure /* !!This must be the last one!! */
+} idn_result_t;
+
+/*
+ * BIDI type codes.
+ */
+typedef enum {
+ idn_biditype_r_al,
+ idn_biditype_l,
+ idn_biditype_others
+} idn_biditype_t;
+
+/*
+ * A Handle for nameprep operations.
+ */
+typedef struct idn_nameprep *idn_nameprep_t;
+
+
+/*
+ * The latest version of nameprep.
+ */
+#define IDN_NAMEPREP_CURRENT "nameprep-11"
+
+#undef assert
+#define assert(a)
+#define TRACE(a)
+
+
+/* race.c */
+idn_result_t race_decode_decompress(const char *from,
+ uint16_t *buf,
+ size_t buflen);
+idn_result_t race_compress_encode(const uint16_t *p,
+ int compress_mode,
+ char *to, size_t tolen);
+int get_compress_mode(uint16_t *p);
+
+
+/* nameprep.c */
+
+/*
+ * Create a handle for nameprep operations.
+ * The handle is stored in '*handlep', which is used other functions
+ * in this module.
+ * The version of the NAMEPREP specification can be specified with
+ * 'version' parameter. If 'version' is nullptr, the latest version
+ * is used.
+ *
+ * Returns:
+ * idn_success -- ok.
+ * idn_notfound -- specified version not found.
+ */
+idn_result_t
+idn_nameprep_create(const char *version, idn_nameprep_t *handlep);
+
+/*
+ * Close a handle, which was created by 'idn_nameprep_create'.
+ */
+void
+idn_nameprep_destroy(idn_nameprep_t handle);
+
+/*
+ * Perform character mapping on an UCS4 string specified by 'from', and
+ * store the result into 'to', whose length is specified by 'tolen'.
+ *
+ * Returns:
+ * idn_success -- ok.
+ * idn_buffer_overflow -- result buffer is too small.
+ */
+idn_result_t
+idn_nameprep_map(idn_nameprep_t handle, const uint32_t *from,
+ uint32_t *to, size_t tolen);
+
+/*
+ * Check if an UCS4 string 'str' contains any prohibited characters specified
+ * by the draft. If found, the pointer to the first such character is stored
+ * into '*found'. Otherwise '*found' will be nullptr.
+ *
+ * Returns:
+ * idn_success -- check has been done properly. (But this
+ * does not mean that no prohibited character
+ * was found. Check '*found' to see the
+ * result.)
+ */
+idn_result_t
+idn_nameprep_isprohibited(idn_nameprep_t handle, const uint32_t *str,
+ const uint32_t **found);
+
+/*
+ * Check if an UCS4 string 'str' contains any unassigned characters specified
+ * by the draft. If found, the pointer to the first such character is stored
+ * into '*found'. Otherwise '*found' will be nullptr.
+ *
+ * Returns:
+ * idn_success -- check has been done properly. (But this
+ * does not mean that no unassinged character
+ * was found. Check '*found' to see the
+ * result.)
+ */
+idn_result_t
+idn_nameprep_isunassigned(idn_nameprep_t handle, const uint32_t *str,
+ const uint32_t **found);
+
+/*
+ * Check if an UCS4 string 'str' is valid string specified by ``bidi check''
+ * of the draft. If it is not valid, the pointer to the first invalid
+ * character is stored into '*found'. Otherwise '*found' will be nullptr.
+ *
+ * Returns:
+ * idn_success -- check has been done properly. (But this
+ * does not mean that the string was valid.
+ * Check '*found' to see the result.)
+ */
+idn_result_t
+idn_nameprep_isvalidbidi(idn_nameprep_t handle, const uint32_t *str,
+ const uint32_t **found);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* nsIDNKitWrapper_h__ */
diff --git a/netwerk/dns/nsIDNSAdditionalInfo.idl b/netwerk/dns/nsIDNSAdditionalInfo.idl
new file mode 100644
index 0000000000..4f266d7937
--- /dev/null
+++ b/netwerk/dns/nsIDNSAdditionalInfo.idl
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(74db2955-6298-4d82-a3b9-7f9e8ba9e854)]
+interface nsIDNSAdditionalInfo : nsISupports
+{
+ readonly attribute int32_t port;
+ readonly attribute ACString resolverURL;
+};
diff --git a/netwerk/dns/nsIDNSByTypeRecord.idl b/netwerk/dns/nsIDNSByTypeRecord.idl
new file mode 100644
index 0000000000..1d11325af8
--- /dev/null
+++ b/netwerk/dns/nsIDNSByTypeRecord.idl
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDNSRecord.idl"
+
+%{ C++
+
+#include "mozilla/Maybe.h"
+
+#include "nsTArrayForwardDeclare.h"
+#include "nsHttp.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+template <typename... Ts> class Variant;
+struct Nothing;
+
+namespace net {
+ struct SVCB;
+ using TypeRecordResultType =
+ Variant<Nothing, CopyableTArray<nsCString>, CopyableTArray<SVCB>>;
+}
+}
+
+%}
+
+[ref] native CStringArrayRef(CopyableTArray<nsCString>);
+native TypeResult(mozilla::net::TypeRecordResultType);
+
+native MaybePort(mozilla::Maybe<uint16_t>);
+native MaybeAlpnTuple(mozilla::Maybe<std::tuple<nsCString, mozilla::net::SupportedAlpnRank>>);
+
+[scriptable, uuid(5d13241b-9d46-448a-90d8-77c418491026)]
+interface nsIDNSByTypeRecord : nsIDNSRecord
+{
+ /**
+ * Returns DNS request type that was made for this request.
+ */
+ readonly attribute unsigned long type;
+
+ [noscript] readonly attribute TypeResult results;
+};
+
+[scriptable, uuid(2a71750d-cb21-45f1-9e1c-666d18dd7645)]
+interface nsIDNSTXTRecord : nsISupports
+{
+ CStringArrayRef getRecords();
+
+ /*
+ * Return concatenated strings.
+ */
+ ACString getRecordsAsOneString();
+};
+
+[scriptable, uuid(2979ceaa-9c7e-49de-84b8-ea81c16aebf1)]
+interface nsISVCParam : nsISupports {
+ readonly attribute uint16_t type;
+};
+
+[scriptable, uuid(0dc58309-4d67-4fc4-a4e3-38dbde9d9f4c)]
+interface nsISVCParamAlpn : nsISupports {
+ readonly attribute Array<ACString> alpn;
+};
+
+[scriptable, uuid(b3ed89c3-2ae6-4c92-8176-b76bc2437fcb)]
+interface nsISVCParamNoDefaultAlpn : nsISupports {
+};
+
+[scriptable, uuid(a37c7bcb-bfcd-4ab4-b826-cc583859ba73)]
+interface nsISVCParamPort : nsISupports {
+ readonly attribute uint16_t port;
+};
+
+[scriptable, uuid(d3163d2f-0bbe-47d4-bcac-db3fb1433b39)]
+interface nsISVCParamIPv4Hint : nsISupports {
+ readonly attribute Array<nsINetAddr> ipv4Hint;
+};
+
+[scriptable, uuid(1f31e41d-b6d8-4796-b12a-82ef8d2b0e43)]
+interface nsISVCParamEchConfig : nsISupports {
+ readonly attribute ACString echconfig;
+};
+
+[scriptable, uuid(5100bce4-9d3b-42e1-a3c9-0f386bbc9dad)]
+interface nsISVCParamIPv6Hint : nsISupports {
+ readonly attribute Array<nsINetAddr> ipv6Hint;
+};
+
+[scriptable, uuid(bdcef040-452e-11eb-b378-0242ac130002)]
+interface nsISVCParamODoHConfig : nsISupports {
+ readonly attribute ACString ODoHConfig;
+};
+
+[scriptable, builtinclass, uuid(a4da5645-2160-4439-bd11-540a2d26c989)]
+interface nsISVCBRecord : nsISupports {
+ readonly attribute uint16_t priority;
+ readonly attribute ACString name;
+ [noscript, nostdcall, notxpcom] readonly attribute MaybePort port;
+ [noscript, nostdcall, notxpcom] readonly attribute MaybeAlpnTuple alpn;
+ readonly attribute ACString selectedAlpn;
+ readonly attribute ACString echConfig;
+ readonly attribute ACString ODoHConfig;
+ readonly attribute bool hasIPHintAddress;
+ readonly attribute Array<nsISVCParam> values;
+};
+
+[scriptable, uuid(5b649e95-e0d3-422b-99a6-79d70a041387)]
+interface nsIDNSHTTPSSVCRecord : nsISupports
+{
+ readonly attribute Array<nsISVCBRecord> records;
+ nsISVCBRecord GetServiceModeRecord(in boolean aNoHttp2, in boolean aNoHttp3);
+ /**
+ * Returns true if one of SVCB records has IPv4 or IPv6 hint addresses.
+ */
+ readonly attribute boolean hasIPAddresses;
+
+ /**
+ * Returns true when all names of SVCB records are in exclusion list.
+ */
+ readonly attribute boolean allRecordsExcluded;
+
+ /**
+ * Returns the ttl of this record.
+ */
+ readonly attribute uint32_t ttl;
+
+ Array<nsISVCBRecord> GetAllRecordsWithEchConfig(in boolean aNoHttp2,
+ in boolean aNoHttp3,
+ out boolean aAllRecordsHaveEchConfig,
+ out boolean aAllRecordsInH3ExcludedList);
+};
diff --git a/netwerk/dns/nsIDNSListener.idl b/netwerk/dns/nsIDNSListener.idl
new file mode 100644
index 0000000000..d925294733
--- /dev/null
+++ b/netwerk/dns/nsIDNSListener.idl
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIDNSRecord;
+interface nsIDNSByTypeRecord;
+
+/**
+ * nsIDNSListener
+ */
+[scriptable, uuid(27d49bfe-280c-49e0-bbaa-f6200c232c3d)]
+interface nsIDNSListener : nsISupports
+{
+ /**
+ * called when an asynchronous host lookup completes.
+ *
+ * @param aRequest
+ * the value returned from asyncResolve.
+ * @param aRecord
+ * the DNS record corresponding to the hostname that was resolved.
+ * this parameter is null if there was an error.
+ * depending on the type parameter passed to asyncResolve() the
+ * caller should QueryInterface to either nsIDNSAddrRecord or
+ * nsIDNSByTypeRecord.
+ * @param aStatus
+ * if the lookup failed, this parameter gives the reason.
+ */
+ void onLookupComplete(in nsICancelable aRequest,
+ in nsIDNSRecord aRecord,
+ in nsresult aStatus);
+};
diff --git a/netwerk/dns/nsIDNSRecord.idl b/netwerk/dns/nsIDNSRecord.idl
new file mode 100644
index 0000000000..82682bd900
--- /dev/null
+++ b/netwerk/dns/nsIDNSRecord.idl
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 "nsIRequest.idl"
+#include "nsITRRSkipReason.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+#include "nsTArrayForwardDeclare.h"
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ref] native nsNetAddrTArrayRef(nsTArray<mozilla::net::NetAddr>);
+interface nsINetAddr;
+
+/**
+ * nsIDNSRecord
+ *
+ * this interface represents the result of a DNS lookup. since a DNS
+ * query may return more than one resolved IP address, the record acts
+ * like an enumerator, allowing the caller to easily step through the
+ * list of IP addresses.
+ */
+[scriptable, uuid(f92228ae-c417-4188-a604-0830a95e7eb9)]
+interface nsIDNSRecord : nsISupports
+{
+};
+
+[scriptable, uuid(cb260e20-943f-4309-953b-78c90d3a7638)]
+interface nsIDNSAddrRecord : nsIDNSRecord
+{
+ /**
+ * @return the canonical hostname for this record. this value is empty if
+ * the record was not fetched with the RESOLVE_CANONICAL_NAME flag.
+ *
+ * e.g., www.mozilla.org --> rheet.mozilla.org
+ *
+ * That the result, if IDN will be returned as punycode.
+ * e.g., élève.w3c-test.org --> xn--lve-6lad.w3c-test.org
+ */
+ readonly attribute ACString canonicalName;
+
+ /**
+ * this function copies the value of the next IP address into the
+ * given NetAddr struct and increments the internal address iterator.
+ *
+ * @param aPort
+ * A port number to initialize the NetAddr with.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if there is not another IP address in
+ * the record.
+ */
+ [noscript] NetAddr getNextAddr(in uint16_t aPort);
+
+ /**
+ * this function copies the value of all working members of the RR
+ * set into the output array.
+ *
+ * @param aAddressArray
+ * The result set
+ */
+ [noscript] void getAddresses(out nsNetAddrTArrayRef aAddressArray);
+
+ /**
+ * this function returns the value of the next IP address as a
+ * scriptable address and increments the internal address iterator.
+ *
+ * @param aPort
+ * A port number to initialize the nsINetAddr with.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if there is not another IP address in
+ * the record.
+ */
+ nsINetAddr getScriptableNextAddr(in uint16_t aPort);
+
+ /**
+ * this function returns the value of the next IP address as a
+ * string and increments the internal address iterator.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if there is not another IP address in
+ * the record.
+ */
+ ACString getNextAddrAsString();
+
+ /**
+ * this function returns true if there is another address in the record.
+ */
+ boolean hasMore();
+
+ /**
+ * this function resets the internal address iterator to the first
+ * address in the record.
+ */
+ void rewind();
+
+ /**
+ * This function indicates that the last address obtained via getNextAddr*()
+ * was not usuable and should be skipped in future uses of this
+ * record if other addresses are available.
+ *
+ * @param aPort is the port number associated with the failure, if any.
+ * It may be zero if not applicable.
+ */
+ void reportUnusable(in uint16_t aPort);
+
+ /**
+ * Record retreived with TRR.
+ */
+ bool IsTRR();
+
+ /**
+ * Record is resolved in socket process.
+ */
+ bool resolvedInSocketProcess();
+
+ /**
+ * This attribute is only set if TRR is used and it measures time between
+ * asyncOpen on a channel and the time parsing of response if done.
+ * Thee time is measured in milliseconds.
+ */
+ readonly attribute double trrFetchDuration;
+
+ /**
+ * This attribute is only set if TRR is used and it measures time between
+ * sending a request and the time response is received from the network.
+ * This time is similat to the time above, but exludes a time needed to
+ * make a connection and a time neededto parse results (this also does not
+ * include delays that may be introduce because parsing is perform on the main
+ * thread).
+ * Thee time is measured in milliseconds.
+ */
+ readonly attribute double trrFetchDurationNetworkOnly;
+
+ /**
+ * The TRR mode this record is used.
+ */
+ readonly attribute nsIRequest_TRRMode effectiveTRRMode;
+
+ /**
+ * If the DNS request didn't use TRR, this value
+ * contains the reason why that was skipped.
+ */
+ readonly attribute nsITRRSkipReason_value trrSkipReason;
+
+ /**
+ * Returns the ttl of this record.
+ */
+ readonly attribute uint32_t ttl;
+};
diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl
new file mode 100644
index 0000000000..c1aecccd8c
--- /dev/null
+++ b/netwerk/dns/nsIDNSService.idl
@@ -0,0 +1,383 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 "nsIRequest.idl"
+#include "nsITRRSkipReason.idl"
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/TypedEnumBits.h"
+%}
+
+interface nsICancelable;
+interface nsIEventTarget;
+interface nsIDNSRecord;
+interface nsIDNSListener;
+interface nsIDNSAdditionalInfo;
+
+%{C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla { namespace net {
+ struct DNSCacheEntries;
+} }
+%}
+
+[ptr] native EntriesArray(nsTArray<mozilla::net::DNSCacheEntries>);
+[ref] native OriginAttributes(const mozilla::OriginAttributes);
+
+/**
+ * nsIDNSService
+ */
+[scriptable, builtinclass, uuid(de5642c6-61fc-4fcf-9a47-03226b0d4e21)]
+interface nsIDNSService : nsISupports
+{
+ /**
+ * These are the dns request types that are currently supported.
+ * RESOLVE_TYPE_DEFAULT is standard A/AAAA lookup
+ */
+ cenum ResolveType : 16 {
+ RESOLVE_TYPE_DEFAULT = 0,
+ RESOLVE_TYPE_TXT = 16,
+ RESOLVE_TYPE_HTTPSSVC = 65,
+ };
+
+ cenum ResolverMode : 32 { // 32 bits to allow this to be stored in an Atomic
+ MODE_NATIVEONLY = 0, // TRR OFF (by default)
+ MODE_RESERVED1 = 1, // Reserved value. Used to be parallel resolve.
+ MODE_TRRFIRST = 2, // fallback to native on TRR failure
+ MODE_TRRONLY = 3, // don't even fallback
+ MODE_RESERVED4 = 4, // Reserved value. Used to be race TRR with native.
+ MODE_TRROFF = 5 // identical to MODE_NATIVEONLY but explicitly selected
+ };
+
+ cenum DNSFlags : 32 {
+ RESOLVE_DEFAULT_FLAGS = 0,
+ // if set, this flag suppresses the internal DNS lookup cache.
+ RESOLVE_BYPASS_CACHE = (1 << 0),
+ // if set, the canonical name of the specified host will be queried.
+ RESOLVE_CANONICAL_NAME = (1 << 1),
+ // If PRIORITY flags are set, the query is given lower priority.
+ // Medium takes precedence if both MEDIUM and LOW are used.
+ RESOLVE_PRIORITY_MEDIUM = (1 << 2),
+ RESOLVE_PRIORITY_LOW = (1 << 3),
+ // if set, indicates request is speculative. Speculative requests
+ // return errors if prefetching is disabled by configuration.
+ RESOLVE_SPECULATE = (1 << 4),
+ // If set, only IPv4 addresses will be returned from resolve/asyncResolve.
+ RESOLVE_DISABLE_IPV6 = (1 << 5),
+ // If set, only literals and cached entries will be returned from resolve/asyncResolve.
+ RESOLVE_OFFLINE = (1 << 6),
+ // If set, only IPv6 addresses will be returned from resolve/asyncResolve.
+ RESOLVE_DISABLE_IPV4 = (1 << 7),
+ // If set, allow name collision results (127.0.53.53) which are normally filtered.
+ RESOLVE_ALLOW_NAME_COLLISION = (1 << 8),
+ // If set, do not use TRR for resolving the host name.
+ RESOLVE_DISABLE_TRR = (1 << 9),
+ // if set (together with RESOLVE_BYPASS_CACHE), invalidate the DNS
+ // existing cache entry first (if existing) then make a new resolve.
+ RESOLVE_REFRESH_CACHE = (1 << 10),
+ // These two bits encode the TRR mode of the request.
+ // Use the static helper methods GetFlagsFromTRRMode and
+ // GetTRRModeFromFlags to convert between the TRR mode and flags.
+ RESOLVE_TRR_MODE_MASK = (1 << 11) | (1 << 12),
+ RESOLVE_TRR_DISABLED_MODE = (1 << 11),
+ // Force resolution even when SOCKS proxy with DNS forwarding is configured.
+ // Only to be used for the proxy host resolution.
+ RESOLVE_IGNORE_SOCKS_DNS = (1 << 13),
+ // If set, only cached IP hint addresses will be returned from resolve/asyncResolve.
+ RESOLVE_IP_HINT = (1 << 14),
+ // If set, the DNS service will pass a DNS record to
+ // OnLookupComplete even when there was a resolution error.
+ RESOLVE_WANT_RECORD_ON_ERROR = (1 << 16),
+
+ // Bitflag containing all possible flags.
+ ALL_DNSFLAGS_BITS = ((1 << 17) - 1),
+ };
+
+ cenum ConfirmationState : 8 {
+ CONFIRM_OFF = 0,
+ CONFIRM_TRYING_OK = 1,
+ CONFIRM_OK = 2,
+ CONFIRM_FAILED = 3,
+ CONFIRM_TRYING_FAILED = 4,
+ CONFIRM_DISABLED = 5,
+ };
+
+ /**
+ * kicks off an asynchronous host lookup.
+ *
+ * @param aHostName
+ * the hostname or IP-address-literal to resolve.
+ * @param aType
+ * one of RESOLVE_TYPE_*.
+ * @param aFlags
+ * a bitwise OR of the RESOLVE_ prefixed constants defined below.
+ * @param aInfo
+ * a AdditionalInfo object that holds information about:
+ * - the resolver to be used such as TRR URL
+ * - the port number that could be used to construct a QNAME
+ * for HTTPS RR
+ * If null we use the default configuration.
+ * @param aListener
+ * the listener to be notified when the result is available.
+ * @param aListenerTarget
+ * optional parameter (may be null). if non-null, this parameter
+ * specifies the nsIEventTarget of the thread on which the
+ * listener's onLookupComplete should be called. however, if this
+ * parameter is null, then onLookupComplete will be called on an
+ * unspecified thread (possibly recursively).
+ * @param aOriginAttributes
+ * the originAttribute for this resolving, the DNS cache will be
+ * separated according to this originAttributes. This attribute is
+ * optional to avoid breaking add-ons.
+ *
+ * @return An object that can be used to cancel the host lookup.
+ */
+ [implicit_jscontext, optional_argc]
+ nsICancelable asyncResolve(in AUTF8String aHostName,
+ in nsIDNSService_ResolveType aType,
+ in nsIDNSService_DNSFlags aFlags,
+ in nsIDNSAdditionalInfo aInfo,
+ in nsIDNSListener aListener,
+ in nsIEventTarget aListenerTarget,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult asyncResolveNative(in AUTF8String aHostName,
+ in nsIDNSService_ResolveType aType,
+ in nsIDNSService_DNSFlags aFlags,
+ in nsIDNSAdditionalInfo aInfo,
+ in nsIDNSListener aListener,
+ in nsIEventTarget aListenerTarget,
+ in OriginAttributes aOriginAttributes,
+ out nsICancelable aResult);
+
+ /**
+ * Returns a new nsIDNSAdditionalInfo object containing the URL we pass to it.
+ */
+ nsIDNSAdditionalInfo newAdditionalInfo(in AUTF8String aTrrURL,
+ in int32_t aPort);
+
+ /**
+ * Attempts to cancel a previously requested async DNS lookup
+ *
+ * @param aHostName
+ * the hostname or IP-address-literal to resolve.
+ * @param aType
+ * one of RESOLVE_TYPE_*.
+ * @param aFlags
+ * a bitwise OR of the RESOLVE_ prefixed constants defined below.
+ * @param aInfo
+ * a AdditionalInfo object that holds information about:
+ * - the resolver to be used such as TRR URL
+ * - the port number that could be used to construct a QNAME
+ * for HTTPS RR
+ * If null we use the default configuration.
+ * @param aListener
+ * the original listener which was to be notified about the host lookup
+ * result - used to match request information to requestor.
+ * @param aReason
+ * nsresult reason for the cancellation
+ * @param aOriginAttributes
+ * the originAttribute for this resolving. This attribute is optional
+ * to avoid breaking add-ons.
+ */
+ [implicit_jscontext, optional_argc]
+ void cancelAsyncResolve(in AUTF8String aHostName,
+ in nsIDNSService_ResolveType aType,
+ in nsIDNSService_DNSFlags aFlags,
+ in nsIDNSAdditionalInfo aResolver,
+ in nsIDNSListener aListener,
+ in nsresult aReason,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult cancelAsyncResolveNative(in AUTF8String aHostName,
+ in nsIDNSService_ResolveType aType,
+ in nsIDNSService_DNSFlags aFlags,
+ in nsIDNSAdditionalInfo aResolver,
+ in nsIDNSListener aListener,
+ in nsresult aReason,
+ in OriginAttributes aOriginAttributes);
+
+ /**
+ * called to synchronously resolve a hostname.
+ *
+ * Since this method may block the calling thread for a long period of
+ * time, it may not be accessed from the main thread.
+ *
+ * @param aHostName
+ * the hostname or IP-address-literal to resolve.
+ * @param aFlags
+ * a bitwise OR of the RESOLVE_ prefixed constants defined below.
+ * @param aOriginAttributes
+ * the originAttribute for this resolving, the DNS cache will be
+ * separated according to this originAttributes. This attribute is
+ * optional to avoid breaking add-ons.
+ *
+ * @return DNS record corresponding to the given hostname.
+ * @throws NS_ERROR_UNKNOWN_HOST if host could not be resolved.
+ * @throws NS_ERROR_NOT_AVAILABLE if accessed from the main thread.
+ */
+ [implicit_jscontext, optional_argc]
+ nsIDNSRecord resolve(in AUTF8String aHostName,
+ in nsIDNSService_DNSFlags aFlags,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult resolveNative(in AUTF8String aHostName,
+ in nsIDNSService_DNSFlags aFlags,
+ in OriginAttributes aOriginAttributes,
+ out nsIDNSRecord aResult);
+
+ /**
+ * The method takes a pointer to an nsTArray
+ * and fills it with cache entry data
+ * Called by the networking dashboard
+ */
+ [noscript] void getDNSCacheEntries(in EntriesArray args);
+
+
+ /**
+ * Clears the DNS cache.
+ * @param aTrrToo
+ * If true we will clear TRR cached entries too. Since these
+ * are resolved remotely it's not necessary to clear them when
+ * the network status changes, but it's sometimes useful to do so
+ * for tests or other situations.
+ */
+ void clearCache(in boolean aTrrToo);
+
+ /**
+ * The method is used only for test purpose. We use this to recheck if
+ * parental control is enabled or not.
+ */
+ void reloadParentalControlEnabled();
+
+ /**
+ * Notifies the TRR service of a TRR that was automatically detected based
+ * on network preferences.
+ */
+ void setDetectedTrrURI(in AUTF8String aURI);
+
+ /**
+ * Stores the result of the TRR heuristic detection.
+ * Will be TRR_OK if no heuristics failed.
+ */
+ void setHeuristicDetectionResult(in nsITRRSkipReason_value value);
+
+ /**
+ * Returns the result of the last TRR heuristic detection.
+ * Will be TRR_OK if no heuristics failed.
+ */
+ readonly attribute nsITRRSkipReason_value heuristicDetectionResult;
+
+ ACString getTRRSkipReasonName(in nsITRRSkipReason_value value);
+
+ /**
+ * The channel status of the last TRR confirmation attempt.
+ * In strict mode it reflects the channel status of the last TRR request.
+ */
+ readonly attribute nsresult lastConfirmationStatus;
+
+ /**
+ * The TRR skip reason of the last TRR confirmation attempt.
+ * In strict mode it reflects the TRR skip reason of the last TRR request.
+ */
+ readonly attribute nsITRRSkipReason_value lastConfirmationSkipReason;
+
+ /**
+ * Notifies the DNS service that we failed to connect to this alternative
+ * endpoint.
+ * @param aOwnerName
+ * The owner name of this HTTPS RRs.
+ * @param aSVCDomainName
+ * The domain name of this alternative endpoint.
+ */
+ [noscript] void ReportFailedSVCDomainName(in ACString aOwnerName,
+ in ACString aSVCDomainName);
+
+ /**
+ * Check if the given domain name was failed to connect to before.
+ * @param aOwnerName
+ * The owner name of this HTTPS RRs.
+ * @param aSVCDomainName
+ * The domain name of this alternative endpoint.
+ */
+ [noscript] boolean IsSVCDomainNameFailed(in ACString aOwnerName,
+ in ACString aSVCDomainName);
+
+ /**
+ * Reset the exclusion list.
+ * @param aOwnerName
+ * The owner name of this HTTPS RRs.
+ */
+ [noscript] void ResetExcludedSVCDomainName(in ACString aOwnerName);
+
+ /**
+ * Returns a string containing the URI currently used by the TRR service.
+ */
+ readonly attribute AUTF8String currentTrrURI;
+
+ /**
+ * Returns the value of the TRR Service's current default mode.
+ */
+ readonly attribute nsIDNSService_ResolverMode currentTrrMode;
+
+ /**
+ * The TRRService's current confirmation state.
+ * This is mostly for testing purposes.
+ */
+ readonly attribute unsigned long currentTrrConfirmationState;
+
+ /**
+ * @return the hostname of the operating system.
+ */
+ readonly attribute AUTF8String myHostName;
+
+ /**
+ * returns the current TRR domain.
+ */
+ readonly attribute ACString trrDomain;
+
+ /**
+ * returns the telemetry key for current TRR domain.
+ */
+ readonly attribute ACString TRRDomainKey;
+
+ /*************************************************************************
+ * Listed below are the various flags that may be OR'd together to form
+ * the aFlags parameter passed to asyncResolve() and resolve().
+ */
+
+%{C++
+ static nsIDNSService::DNSFlags GetFlagsFromTRRMode(nsIRequest::TRRMode aMode) {
+ return static_cast<nsIDNSService::DNSFlags>(static_cast<uint32_t>(aMode) << 11);
+ }
+
+ static nsIRequest::TRRMode GetTRRModeFromFlags(nsIDNSService::DNSFlags aFlags) {
+ return static_cast<nsIRequest::TRRMode>((aFlags & RESOLVE_TRR_MODE_MASK) >> 11);
+ }
+%}
+
+};
+
+%{C++
+
+/**
+ * An observer notification for this topic is sent whenever the URI that the
+ * TRR service is using has changed.
+ */
+#define NS_NETWORK_TRR_URI_CHANGED_TOPIC "network:trr-uri-changed"
+
+/**
+ * An observer notification for this topic is sent whenever the mode that the
+ * TRR service is using has changed.
+ */
+#define NS_NETWORK_TRR_MODE_CHANGED_TOPIC "network:trr-mode-changed"
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsIDNSService::DNSFlags)
+
+%}
diff --git a/netwerk/dns/nsIDNService.cpp b/netwerk/dns/nsIDNService.cpp
new file mode 100644
index 0000000000..3db169d3af
--- /dev/null
+++ b/netwerk/dns/nsIDNService.cpp
@@ -0,0 +1,855 @@
+/* -*- 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 "MainThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "nsIDNService.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "harfbuzz/hb.h"
+#include "punycode.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/intl/FormatBuffer.h"
+#include "mozilla/intl/UnicodeProperties.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+
+#include "ICUUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::intl;
+using namespace mozilla::unicode;
+using namespace mozilla::net;
+using mozilla::Preferences;
+
+// Currently we use the non-transitional processing option -- see
+// http://unicode.org/reports/tr46/
+// To switch to transitional processing, change the value of this flag
+// and kTransitionalProcessing in netwerk/test/unit/test_idna2008.js to true
+// (revert bug 1218179).
+const intl::IDNA::ProcessingType kIDNA2008_DefaultProcessingType =
+ intl::IDNA::ProcessingType::NonTransitional;
+
+//-----------------------------------------------------------------------------
+// According to RFC 1034 - 3.1. Name space specifications and terminology
+// the maximum label size would be 63. However, this is enforced at the DNS
+// level and none of the other browsers seem to not enforce the VerifyDnsLength
+// check in https://unicode.org/reports/tr46/#ToASCII
+// Instead, we choose a rather arbitrary but larger size.
+static const uint32_t kMaxULabelSize = 256;
+// RFC 3490 - 5. ACE prefix
+static const char kACEPrefix[] = "xn--";
+
+//-----------------------------------------------------------------------------
+
+#define NS_NET_PREF_EXTRAALLOWED "network.IDN.extra_allowed_chars"
+#define NS_NET_PREF_EXTRABLOCKED "network.IDN.extra_blocked_chars"
+#define NS_NET_PREF_IDNRESTRICTION "network.IDN.restriction_profile"
+
+static inline bool isOnlySafeChars(const nsString& in,
+ const nsTArray<BlocklistRange>& aBlocklist) {
+ if (aBlocklist.IsEmpty()) {
+ return true;
+ }
+ const char16_t* cur = in.BeginReading();
+ const char16_t* end = in.EndReading();
+
+ for (; cur < end; ++cur) {
+ if (CharInBlocklist(*cur, aBlocklist)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// nsIDNService
+//-----------------------------------------------------------------------------
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsIDNService, nsIIDNService)
+
+static const char* gCallbackPrefs[] = {
+ NS_NET_PREF_EXTRAALLOWED,
+ NS_NET_PREF_EXTRABLOCKED,
+ NS_NET_PREF_IDNRESTRICTION,
+ nullptr,
+};
+
+nsresult nsIDNService::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Take a strong reference for our listener with the preferences service,
+ // which we will release on shutdown.
+ // It's OK if we remove the observer a bit early, as it just means we won't
+ // respond to `network.IDN.extra_{allowed,blocked}_chars` and
+ // `network.IDN.restriction_profile` pref changes during shutdown.
+ Preferences::RegisterPrefixCallbacks(PrefChanged, gCallbackPrefs, this);
+ RunOnShutdown(
+ [self = RefPtr{this}]() mutable {
+ Preferences::UnregisterPrefixCallbacks(PrefChanged, gCallbackPrefs,
+ self.get());
+ self = nullptr;
+ },
+ ShutdownPhase::XPCOMWillShutdown);
+ prefsChanged(nullptr);
+
+ return NS_OK;
+}
+
+void nsIDNService::prefsChanged(const char* pref) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AutoWriteLock lock(mLock);
+
+ if (!pref || nsLiteralCString(NS_NET_PREF_EXTRAALLOWED).Equals(pref) ||
+ nsLiteralCString(NS_NET_PREF_EXTRABLOCKED).Equals(pref)) {
+ InitializeBlocklist(mIDNBlocklist);
+ }
+ if (!pref || nsLiteralCString(NS_NET_PREF_IDNRESTRICTION).Equals(pref)) {
+ nsAutoCString profile;
+ if (NS_FAILED(
+ Preferences::GetCString(NS_NET_PREF_IDNRESTRICTION, profile))) {
+ profile.Truncate();
+ }
+ if (profile.EqualsLiteral("moderate")) {
+ mRestrictionProfile = eModeratelyRestrictiveProfile;
+ } else if (profile.EqualsLiteral("high")) {
+ mRestrictionProfile = eHighlyRestrictiveProfile;
+ } else {
+ mRestrictionProfile = eASCIIOnlyProfile;
+ }
+ }
+}
+
+nsIDNService::nsIDNService() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto createResult =
+ mozilla::intl::IDNA::TryCreate(kIDNA2008_DefaultProcessingType);
+ MOZ_ASSERT(createResult.isOk());
+ mIDNA = createResult.unwrap();
+}
+
+nsIDNService::~nsIDNService() = default;
+
+nsresult nsIDNService::IDNA2008ToUnicode(const nsACString& input,
+ nsAString& output) {
+ NS_ConvertUTF8toUTF16 inputStr(input);
+
+ Span<const char16_t> inputSpan{inputStr};
+ intl::nsTStringToBufferAdapter buffer(output);
+ auto result = mIDNA->LabelToUnicode(inputSpan, buffer);
+
+ nsresult rv = NS_OK;
+ if (result.isErr()) {
+ rv = ICUUtils::ICUErrorToNsResult(result.unwrapErr());
+ if (rv == NS_ERROR_FAILURE) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ intl::IDNA::Info info = result.unwrap();
+ if (info.HasErrors()) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+}
+
+nsresult nsIDNService::IDNA2008StringPrep(const nsAString& input,
+ nsAString& output,
+ stringPrepFlag flag) {
+ Span<const char16_t> inputSpan{input};
+ intl::nsTStringToBufferAdapter buffer(output);
+ auto result = mIDNA->LabelToUnicode(inputSpan, buffer);
+
+ nsresult rv = NS_OK;
+ if (result.isErr()) {
+ rv = ICUUtils::ICUErrorToNsResult(result.unwrapErr());
+ if (rv == NS_ERROR_FAILURE) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ intl::IDNA::Info info = result.unwrap();
+
+ // Output the result of nameToUnicode even if there were errors.
+ // But in the case of invalid punycode, the uidna_labelToUnicode result
+ // appears to get an appended U+FFFD REPLACEMENT CHARACTER, which will
+ // confuse our subsequent processing, so we drop that.
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1399540#c9)
+ if ((info.HasInvalidPunycode() || info.HasInvalidAceLabel()) &&
+ !output.IsEmpty() && output.Last() == 0xfffd) {
+ output.Truncate(output.Length() - 1);
+ }
+
+ if (flag == eStringPrepIgnoreErrors) {
+ return NS_OK;
+ }
+
+ if (flag == eStringPrepForDNS) {
+ // We ignore errors if the result is empty, or if the errors were just
+ // invalid hyphens (not punycode-decoding failure or invalid chars).
+ if (!output.IsEmpty()) {
+ if (info.HasErrorsIgnoringInvalidHyphen()) {
+ output.Truncate();
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ }
+ } else {
+ if (info.HasErrors()) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsIDNService::ConvertUTF8toACE(const nsACString& input,
+ nsACString& ace) {
+ return UTF8toACE(input, ace, eStringPrepForDNS);
+}
+
+nsresult nsIDNService::UTF8toACE(const nsACString& input, nsACString& ace,
+ stringPrepFlag flag) {
+ nsresult rv;
+ NS_ConvertUTF8toUTF16 ustr(input);
+
+ // map ideographic period to ASCII period etc.
+ normalizeFullStops(ustr);
+
+ uint32_t len, offset;
+ len = 0;
+ offset = 0;
+ nsAutoCString encodedBuf;
+
+ nsAString::const_iterator start, end;
+ ustr.BeginReading(start);
+ ustr.EndReading(end);
+ ace.Truncate();
+
+ // encode nodes if non ASCII
+ while (start != end) {
+ len++;
+ if (*start++ == (char16_t)'.') {
+ rv = stringPrepAndACE(Substring(ustr, offset, len - 1), encodedBuf, flag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ace.Append(encodedBuf);
+ ace.Append('.');
+ offset += len;
+ len = 0;
+ }
+ }
+
+ // encode the last node if non ASCII
+ if (len) {
+ rv = stringPrepAndACE(Substring(ustr, offset, len), encodedBuf, flag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ace.Append(encodedBuf);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::ConvertACEtoUTF8(const nsACString& input,
+ nsACString& _retval) {
+ return ACEtoUTF8(input, _retval, eStringPrepForDNS);
+}
+
+nsresult nsIDNService::ACEtoUTF8(const nsACString& input, nsACString& _retval,
+ stringPrepFlag flag) {
+ // RFC 3490 - 4.2 ToUnicode
+ // ToUnicode never fails. If any step fails, then the original input
+ // sequence is returned immediately in that step.
+ //
+ // Note that this refers to the decoding of a single label.
+ // ACEtoUTF8 may be called with a sequence of labels separated by dots;
+ // this test applies individually to each label.
+
+ uint32_t len = 0, offset = 0;
+ nsAutoCString decodedBuf;
+
+ nsACString::const_iterator start, end;
+ input.BeginReading(start);
+ input.EndReading(end);
+ _retval.Truncate();
+
+ // loop and decode nodes
+ while (start != end) {
+ len++;
+ if (*start++ == '.') {
+ nsDependentCSubstring origLabel(input, offset, len - 1);
+ if (NS_FAILED(decodeACE(origLabel, decodedBuf, flag))) {
+ // If decoding failed, use the original input sequence
+ // for this label.
+ _retval.Append(origLabel);
+ } else {
+ _retval.Append(decodedBuf);
+ }
+
+ _retval.Append('.');
+ offset += len;
+ len = 0;
+ }
+ }
+ // decode the last node
+ if (len) {
+ nsDependentCSubstring origLabel(input, offset, len);
+ if (NS_FAILED(decodeACE(origLabel, decodedBuf, flag))) {
+ _retval.Append(origLabel);
+ } else {
+ _retval.Append(decodedBuf);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::IsACE(const nsACString& input, bool* _retval) {
+ // look for the ACE prefix in the input string. it may occur
+ // at the beginning of any segment in the domain name. for
+ // example: "www.xn--ENCODED.com"
+
+ if (!IsAscii(input)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ auto stringContains = [](const nsACString& haystack,
+ const nsACString& needle) {
+ return std::search(haystack.BeginReading(), haystack.EndReading(),
+ needle.BeginReading(), needle.EndReading(),
+ [](unsigned char ch1, unsigned char ch2) {
+ return tolower(ch1) == tolower(ch2);
+ }) != haystack.EndReading();
+ };
+
+ *_retval =
+ StringBeginsWith(input, "xn--"_ns, nsCaseInsensitiveCStringComparator) ||
+ (!input.IsEmpty() && input[0] != '.' &&
+ stringContains(input, ".xn--"_ns));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::Normalize(const nsACString& input,
+ nsACString& output) {
+ // protect against bogus input
+ NS_ENSURE_TRUE(IsUtf8(input), NS_ERROR_UNEXPECTED);
+
+ NS_ConvertUTF8toUTF16 inUTF16(input);
+ normalizeFullStops(inUTF16);
+
+ // pass the domain name to stringprep label by label
+ nsAutoString outUTF16, outLabel;
+
+ uint32_t len = 0, offset = 0;
+ nsresult rv;
+ nsAString::const_iterator start, end;
+ inUTF16.BeginReading(start);
+ inUTF16.EndReading(end);
+
+ while (start != end) {
+ len++;
+ if (*start++ == char16_t('.')) {
+ rv = stringPrep(Substring(inUTF16, offset, len - 1), outLabel,
+ eStringPrepIgnoreErrors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outUTF16.Append(outLabel);
+ outUTF16.Append(char16_t('.'));
+ offset += len;
+ len = 0;
+ }
+ }
+ if (len) {
+ rv = stringPrep(Substring(inUTF16, offset, len), outLabel,
+ eStringPrepIgnoreErrors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outUTF16.Append(outLabel);
+ }
+
+ CopyUTF16toUTF8(outUTF16, output);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString& input,
+ bool* _isASCII,
+ nsACString& _retval) {
+ // If host is ACE, then convert to UTF-8 if the host is in the IDN whitelist.
+ // Else, if host is already UTF-8, then make sure it is normalized per IDN.
+
+ nsresult rv = NS_OK;
+
+ // Even if the hostname is not ASCII, individual labels may still be ACE, so
+ // test IsACE before testing IsASCII
+ bool isACE;
+ IsACE(input, &isACE);
+
+ if (IsAscii(input)) {
+ // first, canonicalize the host to lowercase, for whitelist lookup
+ _retval = input;
+ ToLowerCase(_retval);
+
+ if (isACE && !StaticPrefs::network_IDN_show_punycode()) {
+ // ACEtoUTF8() can't fail, but might return the original ACE string
+ nsAutoCString temp(_retval);
+ // Convert from ACE to UTF8 only those labels which are considered safe
+ // for display
+ ACEtoUTF8(temp, _retval, eStringPrepForUI);
+ *_isASCII = IsAscii(_retval);
+ } else {
+ *_isASCII = true;
+ }
+ } else {
+ // We have to normalize the hostname before testing against the domain
+ // whitelist (see bug 315411), and to ensure the entire string gets
+ // normalized.
+ //
+ // Normalization and the tests for safe display below, assume that the
+ // input is Unicode, so first convert any ACE labels to UTF8
+ if (isACE) {
+ nsAutoCString temp;
+ ACEtoUTF8(input, temp, eStringPrepIgnoreErrors);
+ rv = Normalize(temp, _retval);
+ } else {
+ rv = Normalize(input, _retval);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (StaticPrefs::network_IDN_show_punycode() &&
+ NS_SUCCEEDED(UTF8toACE(_retval, _retval, eStringPrepIgnoreErrors))) {
+ *_isASCII = true;
+ return NS_OK;
+ }
+
+ // normalization could result in an ASCII-only hostname. alternatively, if
+ // the host is converted to ACE by the normalizer, then the host may contain
+ // unsafe characters, so leave it ACE encoded. see bug 283016, bug 301694,
+ // and bug 309311.
+ *_isASCII = IsAscii(_retval);
+ if (!*_isASCII) {
+ // UTF8toACE with eStringPrepForUI may return a domain name where
+ // some labels are in UTF-8 and some are in ACE, depending on
+ // whether they are considered safe for display
+ rv = UTF8toACE(_retval, _retval, eStringPrepForUI);
+ *_isASCII = IsAscii(_retval);
+ return rv;
+ }
+ }
+
+ return NS_OK;
+} // Will generate a mutex still-held warning
+
+//-----------------------------------------------------------------------------
+
+static nsresult utf16ToUcs4(const nsAString& in, uint32_t* out,
+ uint32_t outBufLen, uint32_t* outLen) {
+ uint32_t i = 0;
+ nsAString::const_iterator start, end;
+ in.BeginReading(start);
+ in.EndReading(end);
+
+ while (start != end) {
+ char16_t curChar;
+
+ curChar = *start++;
+
+ if (start != end && NS_IS_SURROGATE_PAIR(curChar, *start)) {
+ out[i] = SURROGATE_TO_UCS4(curChar, *start);
+ ++start;
+ } else {
+ out[i] = curChar;
+ }
+
+ i++;
+ if (i >= outBufLen) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+ out[i] = (uint32_t)'\0';
+ *outLen = i;
+ return NS_OK;
+}
+
+static nsresult punycode(const nsAString& in, nsACString& out) {
+ uint32_t ucs4Buf[kMaxULabelSize + 1];
+ uint32_t ucs4Len = 0u;
+ nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxULabelSize, &ucs4Len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // need maximum 20 bits to encode 16 bit Unicode character
+ // (include null terminator)
+ const uint32_t kEncodedBufSize = kMaxULabelSize * 20 / 8 + 1 + 1;
+ char encodedBuf[kEncodedBufSize];
+ punycode_uint encodedLength = kEncodedBufSize;
+
+ enum punycode_status status =
+ punycode_encode(ucs4Len, ucs4Buf, nullptr, &encodedLength, encodedBuf);
+
+ if (punycode_success != status || encodedLength >= kEncodedBufSize) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ encodedBuf[encodedLength] = '\0';
+ out.Assign(nsDependentCString(kACEPrefix) + nsDependentCString(encodedBuf));
+
+ return rv;
+}
+
+// RFC 3454
+//
+// 1) Map -- For each character in the input, check if it has a mapping
+// and, if so, replace it with its mapping. This is described in section 3.
+//
+// 2) Normalize -- Possibly normalize the result of step 1 using Unicode
+// normalization. This is described in section 4.
+//
+// 3) Prohibit -- Check for any characters that are not allowed in the
+// output. If any are found, return an error. This is described in section
+// 5.
+//
+// 4) Check bidi -- Possibly check for right-to-left characters, and if any
+// are found, make sure that the whole string satisfies the requirements
+// for bidirectional strings. If the string does not satisfy the requirements
+// for bidirectional strings, return an error. This is described in section 6.
+//
+// 5) Check unassigned code points -- If allowUnassigned is false, check for
+// any unassigned Unicode points and if any are found return an error.
+// This is described in section 7.
+//
+nsresult nsIDNService::stringPrep(const nsAString& in, nsAString& out,
+ stringPrepFlag flag) {
+ return IDNA2008StringPrep(in, out, flag);
+}
+
+nsresult nsIDNService::stringPrepAndACE(const nsAString& in, nsACString& out,
+ stringPrepFlag flag) {
+ nsresult rv = NS_OK;
+
+ out.Truncate();
+
+ if (IsAscii(in)) {
+ LossyCopyUTF16toASCII(in, out);
+ // If label begins with xn-- we still want to check its validity
+ if (!StringBeginsWith(in, u"xn--"_ns, nsCaseInsensitiveStringComparator)) {
+ return NS_OK;
+ }
+ }
+
+ nsAutoString strPrep;
+ rv = stringPrep(in, strPrep, flag);
+ if (flag == eStringPrepForDNS) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (IsAscii(strPrep)) {
+ LossyCopyUTF16toASCII(strPrep, out);
+ return NS_OK;
+ }
+
+ if (flag == eStringPrepForUI && NS_SUCCEEDED(rv) && isLabelSafe(in)) {
+ CopyUTF16toUTF8(strPrep, out);
+ return NS_OK;
+ }
+
+ return punycode(strPrep, out);
+}
+
+// RFC 3490
+// 1) Whenever dots are used as label separators, the following characters
+// MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full
+// stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full
+// stop).
+
+void nsIDNService::normalizeFullStops(nsAString& s) {
+ nsAString::const_iterator start, end;
+ s.BeginReading(start);
+ s.EndReading(end);
+ int32_t index = 0;
+
+ while (start != end) {
+ switch (*start) {
+ case 0x3002:
+ case 0xFF0E:
+ case 0xFF61:
+ s.ReplaceLiteral(index, 1, u".");
+ break;
+ default:
+ break;
+ }
+ start++;
+ index++;
+ }
+}
+
+nsresult nsIDNService::decodeACE(const nsACString& in, nsACString& out,
+ stringPrepFlag flag) {
+ bool isAce;
+ IsACE(in, &isAce);
+ if (!isAce) {
+ out.Assign(in);
+ return NS_OK;
+ }
+
+ nsAutoString utf16;
+ nsresult result = IDNA2008ToUnicode(in, utf16);
+ NS_ENSURE_SUCCESS(result, result);
+
+ if (flag != eStringPrepForUI || isLabelSafe(utf16)) {
+ CopyUTF16toUTF8(utf16, out);
+ } else {
+ out.Assign(in);
+ return NS_OK;
+ }
+
+ // Validation: encode back to ACE and compare the strings
+ nsAutoCString ace;
+ nsresult rv = UTF8toACE(out, ace, flag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (flag == eStringPrepForDNS &&
+ !ace.Equals(in, nsCaseInsensitiveCStringComparator)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+namespace mozilla::net {
+
+enum ScriptCombo : int32_t {
+ UNSET = -1,
+ BOPO = 0,
+ CYRL = 1,
+ GREK = 2,
+ HANG = 3,
+ HANI = 4,
+ HIRA = 5,
+ KATA = 6,
+ LATN = 7,
+ OTHR = 8,
+ JPAN = 9, // Latin + Han + Hiragana + Katakana
+ CHNA = 10, // Latin + Han + Bopomofo
+ KORE = 11, // Latin + Han + Hangul
+ HNLT = 12, // Latin + Han (could be any of the above combinations)
+ FAIL = 13,
+};
+
+} // namespace mozilla::net
+
+bool nsIDNService::isLabelSafe(const nsAString& label) {
+ AutoReadLock lock(mLock);
+
+ if (!isOnlySafeChars(PromiseFlatString(label), mIDNBlocklist)) {
+ return false;
+ }
+
+ // We should never get here if the label is ASCII
+ NS_ASSERTION(!IsAscii(label), "ASCII label in IDN checking");
+ if (mRestrictionProfile == eASCIIOnlyProfile) {
+ return false;
+ }
+
+ nsAString::const_iterator current, end;
+ label.BeginReading(current);
+ label.EndReading(end);
+
+ Script lastScript = Script::INVALID;
+ uint32_t previousChar = 0;
+ uint32_t baseChar = 0; // last non-diacritic seen (base char for marks)
+ uint32_t savedNumberingSystem = 0;
+// Simplified/Traditional Chinese check temporarily disabled -- bug 857481
+#if 0
+ HanVariantType savedHanVariant = HVT_NotHan;
+#endif
+
+ ScriptCombo savedScript = ScriptCombo::UNSET;
+
+ while (current != end) {
+ uint32_t ch = *current++;
+
+ if (current != end && NS_IS_SURROGATE_PAIR(ch, *current)) {
+ ch = SURROGATE_TO_UCS4(ch, *current++);
+ }
+
+ IdentifierType idType = GetIdentifierType(ch);
+ if (idType == IDTYPE_RESTRICTED) {
+ return false;
+ }
+ MOZ_ASSERT(idType == IDTYPE_ALLOWED);
+
+ // Check for mixed script
+ Script script = UnicodeProperties::GetScriptCode(ch);
+ if (script != Script::COMMON && script != Script::INHERITED &&
+ script != lastScript) {
+ if (illegalScriptCombo(script, savedScript)) {
+ return false;
+ }
+ }
+
+ // U+30FC should be preceded by a Hiragana/Katakana.
+ if (ch == 0x30fc && lastScript != Script::HIRAGANA &&
+ lastScript != Script::KATAKANA) {
+ return false;
+ }
+
+ if (ch == 0x307 &&
+ (previousChar == 'i' || previousChar == 'j' || previousChar == 'l')) {
+ return false;
+ }
+
+ // Check for mixed numbering systems
+ auto genCat = GetGeneralCategory(ch);
+ if (genCat == HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) {
+ uint32_t zeroCharacter =
+ ch - mozilla::intl::UnicodeProperties::GetNumericValue(ch);
+ if (savedNumberingSystem == 0) {
+ // If we encounter a decimal number, save the zero character from that
+ // numbering system.
+ savedNumberingSystem = zeroCharacter;
+ } else if (zeroCharacter != savedNumberingSystem) {
+ return false;
+ }
+ }
+
+ if (genCat == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
+ // Check for consecutive non-spacing marks.
+ if (previousChar != 0 && previousChar == ch) {
+ return false;
+ }
+ // Check for marks whose expected script doesn't match the base script.
+ if (lastScript != Script::INVALID) {
+ UnicodeProperties::ScriptExtensionVector scripts;
+ auto extResult = UnicodeProperties::GetExtensions(ch, scripts);
+ MOZ_ASSERT(extResult.isOk());
+ if (extResult.isErr()) {
+ return false;
+ }
+
+ int nScripts = AssertedCast<int>(scripts.length());
+
+ // nScripts will always be >= 1, because even for undefined characters
+ // it will return Script::INVALID.
+ // If the mark just has script=COMMON or INHERITED, we can't check any
+ // more carefully, but if it has specific scriptExtension codes, then
+ // assume those are the only valid scripts to use it with.
+ if (nScripts > 1 || (Script(scripts[0]) != Script::COMMON &&
+ Script(scripts[0]) != Script::INHERITED)) {
+ while (--nScripts >= 0) {
+ if (Script(scripts[nScripts]) == lastScript) {
+ break;
+ }
+ }
+ if (nScripts == -1) {
+ return false;
+ }
+ }
+ }
+ // Check for diacritics on dotless-i, which would be indistinguishable
+ // from normal accented letter i.
+ if (baseChar == 0x0131 &&
+ ((ch >= 0x0300 && ch <= 0x0314) || ch == 0x031a)) {
+ return false;
+ }
+ } else {
+ baseChar = ch;
+ }
+
+ if (script != Script::COMMON && script != Script::INHERITED) {
+ lastScript = script;
+ }
+
+ // Simplified/Traditional Chinese check temporarily disabled -- bug 857481
+#if 0
+
+ // Check for both simplified-only and traditional-only Chinese characters
+ HanVariantType hanVariant = GetHanVariant(ch);
+ if (hanVariant == HVT_SimplifiedOnly || hanVariant == HVT_TraditionalOnly) {
+ if (savedHanVariant == HVT_NotHan) {
+ savedHanVariant = hanVariant;
+ } else if (hanVariant != savedHanVariant) {
+ return false;
+ }
+ }
+#endif
+
+ previousChar = ch;
+ }
+ return true;
+}
+
+// Scripts that we care about in illegalScriptCombo
+static inline ScriptCombo findScriptIndex(Script aScript) {
+ switch (aScript) {
+ case Script::BOPOMOFO:
+ return ScriptCombo::BOPO;
+ case Script::CYRILLIC:
+ return ScriptCombo::CYRL;
+ case Script::GREEK:
+ return ScriptCombo::GREK;
+ case Script::HANGUL:
+ return ScriptCombo::HANG;
+ case Script::HAN:
+ return ScriptCombo::HANI;
+ case Script::HIRAGANA:
+ return ScriptCombo::HIRA;
+ case Script::KATAKANA:
+ return ScriptCombo::KATA;
+ case Script::LATIN:
+ return ScriptCombo::LATN;
+ default:
+ return ScriptCombo::OTHR;
+ }
+}
+
+static const ScriptCombo scriptComboTable[13][9] = {
+ /* thisScript: BOPO CYRL GREK HANG HANI HIRA KATA LATN OTHR
+ * savedScript */
+ /* BOPO */ {BOPO, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL},
+ /* CYRL */ {FAIL, CYRL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL},
+ /* GREK */ {FAIL, FAIL, GREK, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL},
+ /* HANG */ {FAIL, FAIL, FAIL, HANG, KORE, FAIL, FAIL, KORE, FAIL},
+ /* HANI */ {CHNA, FAIL, FAIL, KORE, HANI, JPAN, JPAN, HNLT, FAIL},
+ /* HIRA */ {FAIL, FAIL, FAIL, FAIL, JPAN, HIRA, JPAN, JPAN, FAIL},
+ /* KATA */ {FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, KATA, JPAN, FAIL},
+ /* LATN */ {CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, LATN, OTHR},
+ /* OTHR */ {FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, OTHR, FAIL},
+ /* JPAN */ {FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, JPAN, JPAN, FAIL},
+ /* CHNA */ {CHNA, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL},
+ /* KORE */ {FAIL, FAIL, FAIL, KORE, KORE, FAIL, FAIL, KORE, FAIL},
+ /* HNLT */ {CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, HNLT, FAIL}};
+
+bool nsIDNService::illegalScriptCombo(Script script, ScriptCombo& savedScript) {
+ if (savedScript == ScriptCombo::UNSET) {
+ savedScript = findScriptIndex(script);
+ return false;
+ }
+
+ savedScript = scriptComboTable[savedScript][findScriptIndex(script)];
+ /*
+ * Special case combinations that depend on which profile is in use
+ * In the Highly Restrictive profile Latin is not allowed with any
+ * other script
+ *
+ * In the Moderately Restrictive profile Latin mixed with any other
+ * single script is allowed.
+ */
+ return ((savedScript == OTHR &&
+ mRestrictionProfile == eHighlyRestrictiveProfile) ||
+ savedScript == FAIL);
+}
diff --git a/netwerk/dns/nsIDNService.h b/netwerk/dns/nsIDNService.h
new file mode 100644
index 0000000000..1e90191326
--- /dev/null
+++ b/netwerk/dns/nsIDNService.h
@@ -0,0 +1,191 @@
+/* -*- 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 nsIDNService_h__
+#define nsIDNService_h__
+
+#include "nsIIDNService.h"
+#include "nsCOMPtr.h"
+
+#include "mozilla/RWLock.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "mozilla/net/IDNBlocklistUtils.h"
+#include "mozilla/intl/IDNA.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsString.h"
+
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+// nsIDNService
+//-----------------------------------------------------------------------------
+
+namespace mozilla::net {
+enum ScriptCombo : int32_t;
+}
+
+class nsIDNService final : public nsIIDNService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIDNSERVICE
+
+ nsIDNService();
+
+ nsresult Init();
+
+ protected:
+ virtual ~nsIDNService();
+
+ private:
+ enum stringPrepFlag {
+ eStringPrepForDNS,
+ eStringPrepForUI,
+ eStringPrepIgnoreErrors
+ };
+
+ /**
+ * Convert the following characters that must be recognized as label
+ * separators per RFC 3490 to ASCII full stop characters
+ *
+ * U+3002 (ideographic full stop)
+ * U+FF0E (fullwidth full stop)
+ * U+FF61 (halfwidth ideographic full stop)
+ */
+ void normalizeFullStops(nsAString& s);
+
+ /**
+ * Convert and encode a DNS label in ACE/punycode.
+ * @param flag
+ * if eStringPrepIgnoreErrors, all non-ASCII labels are
+ * converted to punycode.
+ * if eStringPrepForUI, only labels that are considered safe
+ * for display are converted.
+ * @see isLabelSafe
+ * if eStringPrepForDNS and stringPrep finds an illegal
+ * character, returns NS_FAILURE and out is empty
+ */
+ nsresult stringPrepAndACE(const nsAString& in, nsACString& out,
+ stringPrepFlag flag);
+
+ /**
+ * Convert a DNS label using the stringprep profile defined in RFC 3454
+ */
+ nsresult stringPrep(const nsAString& in, nsAString& out, stringPrepFlag flag);
+
+ /**
+ * Decode an ACE-encoded DNS label to UTF-8
+ *
+ * @param flag
+ * if eStringPrepForUI and the label is not considered safe to
+ * display, the output is the same as the input
+ * @see isLabelSafe
+ */
+ nsresult decodeACE(const nsACString& in, nsACString& out,
+ stringPrepFlag flag);
+
+ /**
+ * Convert complete domain names between UTF8 and ACE and vice versa
+ *
+ * @param flag is passed to decodeACE or stringPrepAndACE for each
+ * label individually, so the output may contain some labels in
+ * punycode and some in UTF-8
+ */
+ nsresult UTF8toACE(const nsACString& input, nsACString& ace,
+ stringPrepFlag flag);
+ nsresult ACEtoUTF8(const nsACString& input, nsACString& _retval,
+ stringPrepFlag flag);
+
+ void prefsChanged(const char* pref);
+
+ static void PrefChanged(const char* aPref, void* aSelf) {
+ auto* self = static_cast<nsIDNService*>(aSelf);
+ self->prefsChanged(aPref);
+ }
+
+ /**
+ * Determine whether a label is considered safe to display to the user
+ * according to the algorithm defined in UTR 39 and the profile
+ * selected in mRestrictionProfile.
+ *
+ * For the ASCII-only profile, returns false for all labels containing
+ * non-ASCII characters.
+ *
+ * For the other profiles, returns false for labels containing any of
+ * the following:
+ *
+ * Characters in scripts other than the "recommended scripts" and
+ * "aspirational scripts" defined in
+ * http://www.unicode.org/reports/tr31/#Table_Recommended_Scripts
+ * and http://www.unicode.org/reports/tr31/#Aspirational_Use_Scripts
+ * This includes codepoints that are not defined as Unicode
+ * characters
+ *
+ * Illegal combinations of scripts (@see illegalScriptCombo)
+ *
+ * Numbers from more than one different numbering system
+ *
+ * Sequences of the same non-spacing mark
+ *
+ * Both simplified-only and traditional-only Chinese characters
+ * XXX this test was disabled by bug 857481
+ */
+ bool isLabelSafe(const nsAString& label) MOZ_EXCLUDES(mLock);
+
+ /**
+ * Determine whether a combination of scripts in a single label is
+ * permitted according to the algorithm defined in UTR 39 and the
+ * profile selected in mRestrictionProfile.
+ *
+ * For the "Highly restrictive" profile, all characters in each
+ * identifier must be from a single script, or from the combinations:
+ * Latin + Han + Hiragana + Katakana;
+ * Latin + Han + Bopomofo; or
+ * Latin + Han + Hangul
+ *
+ * For the "Moderately restrictive" profile, Latin is also allowed
+ * with other scripts except Cyrillic and Greek
+ */
+ bool illegalScriptCombo(mozilla::intl::Script script,
+ mozilla::net::ScriptCombo& savedScript)
+ MOZ_REQUIRES_SHARED(mLock);
+
+ /**
+ * Convert a DNS label from ASCII to Unicode using IDNA2008
+ */
+ nsresult IDNA2008ToUnicode(const nsACString& input, nsAString& output);
+
+ /**
+ * Convert a DNS label to a normalized form conforming to IDNA2008
+ */
+ nsresult IDNA2008StringPrep(const nsAString& input, nsAString& output,
+ stringPrepFlag flag);
+
+ // never mutated after initializing.
+ mozilla::UniquePtr<mozilla::intl::IDNA> mIDNA;
+
+ // We use this rwlock to guard access to:
+ // |mIDNBlocklist|, |mRestrictionProfile|
+ mozilla::RWLock mLock{"nsIDNService"};
+
+ // guarded by mLock
+ nsTArray<mozilla::net::BlocklistRange> mIDNBlocklist MOZ_GUARDED_BY(mLock);
+
+ /**
+ * Restriction-level Detection profiles defined in UTR 39
+ * http://www.unicode.org/reports/tr39/#Restriction_Level_Detection,
+ * and selected by the pref network.IDN.restriction_profile
+ */
+ enum restrictionProfile {
+ eASCIIOnlyProfile,
+ eHighlyRestrictiveProfile,
+ eModeratelyRestrictiveProfile
+ };
+ // guarded by mLock;
+ restrictionProfile mRestrictionProfile MOZ_GUARDED_BY(mLock){
+ eASCIIOnlyProfile};
+};
+
+#endif // nsIDNService_h__
diff --git a/netwerk/dns/nsIEffectiveTLDService.idl b/netwerk/dns/nsIEffectiveTLDService.idl
new file mode 100644
index 0000000000..abf786e5ed
--- /dev/null
+++ b/netwerk/dns/nsIEffectiveTLDService.idl
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(68067eb5-ad8d-43cb-a043-1cc85ebe06e7)]
+interface nsIEffectiveTLDService : nsISupports
+{
+ /**
+ * Returns the public suffix of a URI. A public suffix is the highest-level domain
+ * under which individual domains may be registered; it may therefore contain one
+ * or more dots. For example, the public suffix for "www.bbc.co.uk" is "co.uk",
+ * because the .uk TLD does not allow the registration of domains at the
+ * second level ("bbc.uk" is forbidden).
+ *
+ * The public suffix will be returned encoded in ASCII/ACE and will be normalized
+ * according to RFC 3454, i.e. the same encoding returned by nsIURI::GetAsciiHost().
+ * If consumers wish to compare the result of this method against the host from
+ * another nsIURI, the host should be obtained using nsIURI::GetAsciiHost().
+ * In the case of nested URIs, the innermost URI will be used.
+ *
+ * @param aURI The URI to be analyzed
+ *
+ * @returns the public suffix
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * or other error returned by nsIIDNService::normalize when
+ * the hostname contains characters disallowed in URIs
+ * @throws NS_ERROR_HOST_IS_IP_ADDRESS
+ * if the host is a numeric IPv4 or IPv6 address (as determined by
+ * the success of a call to PR_StringToNetAddr()).
+ */
+ ACString getPublicSuffix(in nsIURI aURI);
+
+ /**
+ * Similar to getPublicSuffix, but the suffix is validated against
+ * the Public Suffix List. If the suffix is unknown this will return
+ * an empty string.
+ *
+ * @param aURI The URI to be analyzed
+ * @returns the public suffix if known, an empty string otherwise
+ * @see getPublicSuffixFromHost()
+ */
+ ACString getKnownPublicSuffix(in nsIURI aURI);
+
+ /**
+ * Returns the base domain of a URI; that is, the public suffix with a given
+ * number of additional domain name parts. For example, the result of this method
+ * for "www.bbc.co.uk", depending on the value of aAdditionalParts parameter, will
+ * be:
+ *
+ * 0 (default) -> bbc.co.uk
+ * 1 -> www.bbc.co.uk
+ *
+ * Similarly, the public suffix for "www.developer.mozilla.org" is "org", and the base
+ * domain will be:
+ *
+ * 0 (default) -> mozilla.org
+ * 1 -> developer.mozilla.org
+ * 2 -> www.developer.mozilla.org
+ *
+ * The base domain will be returned encoded in ASCII/ACE and will be normalized
+ * according to RFC 3454, i.e. the same encoding returned by nsIURI::GetAsciiHost().
+ * If consumers wish to compare the result of this method against the host from
+ * another nsIURI, the host should be obtained using nsIURI::GetAsciiHost().
+ * In the case of nested URIs, the innermost URI will be used.
+ *
+ * @param aURI The URI to be analyzed
+ * @param aAdditionalParts Number of domain name parts to be
+ * returned in addition to the public suffix
+ *
+ * @returns the base domain (public suffix plus the requested number of additional parts)
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * or other error returned by nsIIDNService::normalize when
+ * the hostname contains characters disallowed in URIs
+ * @throws NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
+ * when there are insufficient subdomain levels in the hostname to satisfy the
+ * requested aAdditionalParts value.
+ * @throws NS_ERROR_HOST_IS_IP_ADDRESS
+ * if aHost is a numeric IPv4 or IPv6 address (as determined by
+ * the success of a call to PR_StringToNetAddr()).
+ *
+ * @see getPublicSuffix()
+ */
+ ACString getBaseDomain(in nsIURI aURI, [optional] in uint32_t aAdditionalParts);
+
+ /**
+ * Get the Site without the scheme for the origin of aURI; e.g. for
+ * "https://www.bbc.co.uk/index.html", this would be "bbc.co.uk".
+ * This uses getBaseDomain() internally. This is appropriately permissive,
+ * and will return a schemeless site for aliased hostnames and IP addresses
+ * and will therefore not throw NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS or
+ * NS_ERROR_HOST_IS_IP_ADDRESS, e.g. "http://localhost/index.html" will
+ * return "localhost" successfully, rather than throwing an error.
+ *
+ * @param aHostURI
+ * The URI to analyze.
+ *
+ * @return the Site.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * or other error returned by nsIIDNService::normalize when
+ * the hostname contains characters disallowed in URIs
+ *
+ * @see getBaseDomain()
+ * @see getSite()
+ *
+ * @warning This function should not be used without good reason. Please
+ * use getSite() or the Origin if you are not absolutely certain.
+ */
+ ACString getSchemelessSite(in nsIURI aURI);
+
+ /**
+ * Get the Site for the origin of aURI; e.g. for
+ * "https://www.bbc.co.uk/index.html", this would be "https://bbc.co.uk".
+ * This uses getBaseDomain() internally. This is appropriately permissive,
+ * and will return a scheme for alaised hostnames and IP addresses and will
+ * therefore not throw NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS or
+ * NS_ERROR_HOST_IS_IP_ADDRESS, e.g. "http://localhost/index.html" will
+ * return "http://localhost" successfully, rather than throwing an error.
+ *
+ * @param aHostURI
+ * The URI to analyze.
+ *
+ * @return the Site.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * or other error returned by nsIIDNService::normalize when
+ * the hostname contains characters disallowed in URIs
+ *
+ * @see getBaseDomain()
+ */
+ ACString getSite(in nsIURI aURI);
+
+ /**
+ * NOTE: It is strongly recommended to use getPublicSuffix() above if a suitable
+ * nsIURI is available. Only use this method if this is not the case.
+ *
+ * Returns the public suffix of a host string. Otherwise identical to getPublicSuffix().
+ *
+ * @param aHost The host to be analyzed. Any additional parts (e.g. scheme,
+ * port, or path) will cause this method to throw. ASCII/ACE and
+ * UTF8 encodings are acceptable as input; normalization will
+ * be performed as specified in getBaseDomain().
+ *
+ * @see getPublicSuffix()
+ */
+ ACString getPublicSuffixFromHost(in AUTF8String aHost);
+
+ /**
+ * Similar to getPublicSuffixFromHost, but the suffix is validated against
+ * the Public Suffix List. If the suffix is unknown this will return
+ * an empty string.
+ *
+ * @param aHost The host to be analyzed.
+ * @returns the public suffix if known, an empty string otherwise
+ * @see getPublicSuffixFromHost()
+ */
+ ACString getKnownPublicSuffixFromHost(in AUTF8String aHost);
+
+ /**
+ * NOTE: It is strongly recommended to use getBaseDomain() above if a suitable
+ * nsIURI is available. Only use this method if this is not the case.
+ *
+ * Returns the base domain of a host string. Otherwise identical to getBaseDomain().
+ *
+ * @param aHost The host to be analyzed. Any additional parts (e.g. scheme,
+ * port, or path) will cause this method to throw. ASCII/ACE and
+ * UTF8 encodings are acceptable as input; normalization will
+ * be performed as specified in getBaseDomain().
+ *
+ * @see getBaseDomain()
+ */
+ ACString getBaseDomainFromHost(in AUTF8String aHost, [optional] in uint32_t aAdditionalParts);
+
+ /**
+ * Returns the parent sub-domain of a host string. If the host is a base
+ * domain, it will throw NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
+ *
+ * For example: "player.bbc.co.uk" would return "bbc.co.uk" and
+ * "bbc.co.uk" would throw NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
+ *
+ * @param aHost The host to be analyzed. Any additional parts (e.g. scheme,
+ * port, or path) will cause this method to throw. ASCII/ACE and
+ * UTF8 encodings are acceptable as input; normalization will
+ * be performed as specified in getBaseDomain().
+ */
+ ACString getNextSubDomain(in AUTF8String aHost);
+
+ /**
+ * Returns true if the |aInput| in is part of the root domain of |aHost|.
+ * For example, if |aInput| is "www.mozilla.org", and we pass in
+ * "mozilla.org" as |aHost|, this will return true. It would return false
+ * the other way around.
+ *
+ * @param aInput The host to be analyzed.
+ * @param aHost The host to compare to.
+ */
+ bool hasRootDomain(in AUTF8String aInput, in AUTF8String aHost);
+};
diff --git a/netwerk/dns/nsIIDNService.idl b/netwerk/dns/nsIIDNService.idl
new file mode 100644
index 0000000000..47ef561237
--- /dev/null
+++ b/netwerk/dns/nsIIDNService.idl
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIIDNService interface.
+ *
+ * IDN (Internationalized Domain Name) support. Provides facilities
+ * for manipulating IDN hostnames according to the specification set
+ * forth by the IETF.
+ *
+ * IDN effort:
+ * http://www.ietf.org/html.characters/idn-charter.html
+ * http://www.i-dns.net
+ *
+ * IDNA specification:
+ * http://search.ietf.org/internet-drafts/draft-ietf-idn-idna-06.txt
+ */
+
+[scriptable, uuid(a592a60e-3621-4f19-a318-2bf233cfad3e)]
+interface nsIIDNService : nsISupports
+{
+ /**
+ * Prepares the input hostname according to IDNA ToASCII operation,
+ * the input hostname is assumed to be UTF8-encoded.
+ */
+ ACString convertUTF8toACE(in AUTF8String input);
+
+
+ /**
+ * This is the ToUnicode operation as specified in the IDNA proposal,
+ * with an additional step to encode the result in UTF-8.
+ * It takes an ACE-encoded hostname and performs ToUnicode to it, then
+ * encodes the resulting string into UTF8.
+ */
+ AUTF8String convertACEtoUTF8(in ACString input);
+
+ /**
+ * Checks if the input string is ACE encoded or not.
+ */
+ boolean isACE(in ACString input);
+
+ /**
+ * Performs the unicode normalization needed for hostnames in IDN,
+ * for callers that want early normalization.
+ */
+ AUTF8String normalize(in AUTF8String input);
+
+ /**
+ * Normalizes and converts a host to UTF-8 if the host is in the IDN
+ * whitelist, otherwise converts it to ACE. This is useful for display
+ * purposes and to ensure an encoding consistent with nsIURI::GetHost().
+ * If the result is ASCII or ACE encoded, |isASCII| will be true.
+ */
+ AUTF8String convertToDisplayIDN(in AUTF8String input, out boolean isASCII);
+};
diff --git a/netwerk/dns/nsINativeDNSResolverOverride.idl b/netwerk/dns/nsINativeDNSResolverOverride.idl
new file mode 100644
index 0000000000..0a016af437
--- /dev/null
+++ b/netwerk/dns/nsINativeDNSResolverOverride.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 "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(8e38d536-5501-48c0-a412-6c450040c8c8)]
+interface nsINativeDNSResolverOverride : nsISupports
+{
+ /**
+ * Adds an IP override for this specific host.
+ */
+ void addIPOverride(in AUTF8String aHost, in ACString aIPLiteral);
+
+ /**
+ * Adds an HTTPS record override for this specific host.
+ * The input needs to be the raw bytes of a DNS answer.
+ */
+ void addHTTPSRecordOverride(in AUTF8String aHost,
+ [array, size_is(aLength), const] in uint8_t aData,
+ in unsigned long aLength);
+
+ /**
+ * Sets a CNAME override for this specific host.
+ */
+ void setCnameOverride(in AUTF8String aHost, in ACString aCNAME);
+
+ /**
+ * Clears the overrides for this specific host
+ */
+ void clearHostOverride(in AUTF8String aHost);
+
+ /**
+ * Clears all the host overrides that were previously set.
+ */
+ void clearOverrides();
+};
diff --git a/netwerk/dns/nsITRRSkipReason.idl b/netwerk/dns/nsITRRSkipReason.idl
new file mode 100644
index 0000000000..096f0151bb
--- /dev/null
+++ b/netwerk/dns/nsITRRSkipReason.idl
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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(e61b5d39-f6d6-4ed3-aead-1213b24c6f27)]
+interface nsITRRSkipReason: nsISupports
+{
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ // When adding a reason here, make sure it is documented in
+ // netwerk/docs/dns/trr-skip-reasons.md
+ cenum value: 32 {
+ TRR_UNSET = 0,
+ TRR_OK = 1, // Only set when we actually got a positive TRR result
+ TRR_NO_GSERVICE = 2, // no gService
+ TRR_PARENTAL_CONTROL = 3, // parental control is on
+ TRR_OFF_EXPLICIT = 4, // user has set mode5
+ TRR_REQ_MODE_DISABLED = 5, // request has disabled flags set
+ TRR_MODE_NOT_ENABLED = 6, // mode0
+ TRR_FAILED = 7, // unknown failure
+ TRR_MODE_UNHANDLED_DEFAULT = 8, // Unhandled case in ComputeEffectiveMode
+ TRR_MODE_UNHANDLED_DISABLED = 9, // Unhandled case in ComputeEffectiveMode
+ TRR_DISABLED_FLAG = 10, // the DISABLE_TRR flag was set
+ TRR_TIMEOUT = 11, // the TRR channel timed out
+ TRR_CHANNEL_DNS_FAIL = 12, // DoH server name failed to resolve
+ TRR_IS_OFFLINE = 13, // The browser is offline/no interfaces up
+ TRR_NOT_CONFIRMED = 14, // TRR confirmation is not done yet
+ TRR_DID_NOT_MAKE_QUERY = 15, // TrrLookup exited without doing a TRR query
+ TRR_UNKNOWN_CHANNEL_FAILURE = 16, // unknown channel failure reason
+ TRR_HOST_BLOCKED_TEMPORARY = 17, // host blocklisted
+ TRR_SEND_FAILED = 18, // The call to TRR::SendHTTPRequest failed
+ TRR_NET_RESET = 19, // NS_ERROR_NET_RESET
+ TRR_NET_TIMEOUT = 20, // NS_ERROR_NET_TIMEOUT
+ TRR_NET_REFUSED = 21, // NS_ERROR_CONNECTION_REFUSED
+ TRR_NET_INTERRUPT = 22, // NS_ERROR_NET_INTERRUPT
+ TRR_NET_INADEQ_SEQURITY = 23, // NS_ERROR_NET_INADEQUATE_SECURITY
+ TRR_NO_ANSWERS = 24, // TRR returned no answers
+ TRR_DECODE_FAILED = 25, // DohDecode failed
+ TRR_EXCLUDED = 26, // ExcludedFromTRR
+ TRR_SERVER_RESPONSE_ERR = 27, // Server responded with non-200 code
+ TRR_RCODE_FAIL = 28, // DNS response contains a non-NOERROR rcode
+ TRR_NO_CONNECTIVITY = 29, // Not confirmed because of no connectivity
+ TRR_NXDOMAIN = 30, // DNS response contains NXDOMAIN rcode (0x03)
+ TRR_REQ_CANCELLED = 31, // The request has been cancelled
+ ODOH_KEY_NOT_USABLE = 32, // We don't have a valid ODoHConfig to use.
+ ODOH_UPDATE_KEY_FAILED = 33, // Failed to update the ODoHConfigs.
+ ODOH_KEY_NOT_AVAILABLE = 34, // ODoH requests timeout because of no key.
+ ODOH_ENCRYPTION_FAILED = 35, // Failed to encrypt DNS packets.
+ ODOH_DECRYPTION_FAILED = 36, // Failed to decrypt DNS packets.
+ TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH = 37, // The google safesearch heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH = 38, // The youtube safesearch heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY = 39, // The zscaler canary heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_CANARY = 40, // The global canary heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS = 41, // The modified roots (enterprise_roots cert pref) heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS = 42, // The parental controls heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS = 43, // The third party roots heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY = 44, // The enterprise policy heuristic was tripped
+ TRR_HEURISTIC_TRIPPED_VPN = 45, // The heuristic was tripped due to a vpn being detected
+ TRR_HEURISTIC_TRIPPED_PROXY = 46, // The heuristic was tripped due to a proxy being detected
+ TRR_HEURISTIC_TRIPPED_NRPT = 47, // The heuristic was tripped due to a NRPT being detected
+ TRR_BAD_URL = 48, // We attempted to use a bad URL (doesn't parse or is not https).
+ };
+};
+
+%{ C++
+namespace mozilla {
+namespace net {
+
+using TRRSkippedReason = nsITRRSkipReason::value;
+
+inline bool IsRelevantTRRSkipReason(TRRSkippedReason aReason) {
+ // - TRR_REQ_MODE_DISABLED - these requests are intentionally skipping TRR.
+ // These include DNS queries used to bootstrap the TRR connection,
+ // captive portal checks, connectivity checks, etc.
+ // Since we don't want to use TRR for these connections, we don't need
+ // to include them with other relevant skip reasons.
+ // - TRR_DISABLED_FLAG - This reason is used when retrying failed connections,
+ // sync DNS resolves on the main thread, or requests coming from
+ // webextensions that choose to skip TRR
+ // - TRR_EXCLUDED - This reason is used when a certain domain is excluded
+ // from TRR because it is explicitly set by the user, or because it
+ // is part of the user's DNS suffix list, indicating a host that is likely
+ // to be on the local network.
+ if (aReason == TRRSkippedReason::TRR_REQ_MODE_DISABLED ||
+ aReason == TRRSkippedReason::TRR_DISABLED_FLAG ||
+ aReason == TRRSkippedReason::TRR_EXCLUDED) {
+ return false;
+ }
+ return true;
+}
+
+inline bool IsBlockedTRRRequest(TRRSkippedReason aReason) {
+ // See TRR::MaybeBlockRequest. These are the reasons that could block sending
+ // TRR requests.
+ return (aReason == TRRSkippedReason::TRR_EXCLUDED ||
+ aReason == TRRSkippedReason::TRR_MODE_NOT_ENABLED ||
+ aReason == TRRSkippedReason::TRR_HOST_BLOCKED_TEMPORARY);
+}
+
+inline bool IsNonRecoverableTRRSkipReason(TRRSkippedReason aReason) {
+ // These are non-recoverable reasons and we'll fallback to native without
+ // retrying.
+ return (aReason == TRRSkippedReason::TRR_NXDOMAIN ||
+ aReason == TRRSkippedReason::TRR_NO_ANSWERS ||
+ aReason == TRRSkippedReason::TRR_DISABLED_FLAG ||
+ aReason == TRRSkippedReason::TRR_RCODE_FAIL);
+}
+
+inline bool IsFailedConfirmationOrNoConnectivity(TRRSkippedReason aReason) {
+ // TRR is in non-confirmed state now, so we don't try to use TRR at all.
+ return (aReason == TRRSkippedReason::TRR_NOT_CONFIRMED ||
+ aReason == TRRSkippedReason::TRR_NO_CONNECTIVITY);
+}
+
+extern nsresult GetTRRSkipReasonName(TRRSkippedReason aReason, nsACString& aName);
+
+} // net
+} // mozilla
+%}
diff --git a/netwerk/dns/nsPIDNSService.idl b/netwerk/dns/nsPIDNSService.idl
new file mode 100644
index 0000000000..6d65e6be41
--- /dev/null
+++ b/netwerk/dns/nsPIDNSService.idl
@@ -0,0 +1,34 @@
+/* -*- 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 "nsIDNSService.idl"
+
+/**
+ * This is a private interface used by the internals of the networking library.
+ * It will never be frozen. Do not use it in external code.
+ */
+[scriptable, builtinclass, uuid(24e598fd-7b1a-436c-9154-14d8b38df8a5)]
+interface nsPIDNSService : nsIDNSService
+{
+ /**
+ * called to initialize the DNS service.
+ */
+ void init();
+
+ /**
+ * called to shutdown the DNS service. any pending asynchronous
+ * requests will be canceled, and the local cache of DNS records
+ * will be cleared. NOTE: the operating system may still have
+ * its own cache of DNS records, which would be unaffected by
+ * this method.
+ */
+ void shutdown();
+
+ /**
+ * Whether or not DNS prefetching (aka RESOLVE_SPECULATE) is enabled
+ */
+ attribute boolean prefetchEnabled;
+};
diff --git a/netwerk/dns/prepare_tlds.py b/netwerk/dns/prepare_tlds.py
new file mode 100644
index 0000000000..adebcec487
--- /dev/null
+++ b/netwerk/dns/prepare_tlds.py
@@ -0,0 +1,150 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import codecs
+import encodings.idna
+import re
+import sys
+
+from make_dafsa import words_to_bin, words_to_cxx
+
+"""
+Processes a file containing effective TLD data. See the following URL for a
+description of effective TLDs and of the file format that this script
+processes (although for the latter you're better off just reading this file's
+short source code).
+
+http://wiki.mozilla.org/Gecko:Effective_TLD_Service
+"""
+
+
+def getEffectiveTLDs(path):
+ file = codecs.open(path, "r", "UTF-8")
+ domains = set()
+ for line in file:
+ # line always contains a line terminator unless the file is empty
+ if len(line) == 0:
+ raise StopIteration
+ line = line.rstrip()
+ # comment, empty, or superfluous line for explicitness purposes
+ if line.startswith("//") or not line.strip():
+ continue
+ line = re.split(r"[ \t\n]", line, 1)[0]
+ entry = EffectiveTLDEntry(line)
+ domain = entry.domain()
+ assert domain not in domains, "repeating domain %s makes no sense" % domain
+ domains.add(domain)
+ yield entry
+
+
+def _normalizeHostname(domain):
+ """
+ Normalizes the given domain, component by component. ASCII components are
+ lowercased, while non-ASCII components are processed using the ToASCII
+ algorithm.
+ """
+
+ def convertLabel(label):
+ if _isASCII(label):
+ return label.lower()
+ return encodings.idna.ToASCII(label).decode("utf-8")
+
+ return ".".join(map(convertLabel, domain.split(".")))
+
+
+def _isASCII(s):
+ "True if s consists entirely of ASCII characters, false otherwise."
+ for c in s:
+ if ord(c) > 127:
+ return False
+ return True
+
+
+class EffectiveTLDEntry:
+ """
+ Stores an entry in an effective-TLD name file.
+ """
+
+ _exception = False
+ _wild = False
+
+ def __init__(self, line):
+ """
+ Creates a TLD entry from a line of data, which must have been stripped of
+ the line ending.
+ """
+ if line.startswith("!"):
+ self._exception = True
+ domain = line[1:]
+ elif line.startswith("*."):
+ self._wild = True
+ domain = line[2:]
+ else:
+ domain = line
+ self._domain = _normalizeHostname(domain)
+
+ def domain(self):
+ "The domain this represents."
+ return self._domain
+
+ def exception(self):
+ "True if this entry's domain denotes does not denote an effective TLD."
+ return self._exception
+
+ def wild(self):
+ "True if this entry represents a class of effective TLDs."
+ return self._wild
+
+
+#################
+# DO EVERYTHING #
+#################
+
+
+def main(output, effective_tld_filename, output_format="cxx"):
+ """
+ effective_tld_filename is the effective TLD file to parse.
+ based on the output format, either a C++ array of a binary representation
+ of a DAFSA representing the eTLD file is then printed to standard output
+ or a binary file is written to disk.
+ """
+
+ def typeEnum(etld):
+ """
+ Maps the flags to the DAFSA's enum types.
+ """
+ if etld.exception():
+ return 1
+ elif etld.wild():
+ return 2
+ else:
+ return 0
+
+ def dafsa_words():
+ """
+ make_dafsa expects lines of the form "<domain_name><enum_value>"
+ """
+ for etld in getEffectiveTLDs(effective_tld_filename):
+ yield "%s%d" % (etld.domain(), typeEnum(etld))
+
+ """ words_to_bin() returns a bytes while words_to_cxx() returns string """
+ if output_format == "bin":
+ output.write(words_to_bin(dafsa_words()))
+ else:
+ output.write(words_to_cxx(dafsa_words()))
+
+
+if __name__ == "__main__":
+ """
+ This program can output the DAFSA in two formats:
+ as C++ code that will be included and compiled at build time
+ or as a binary file that will be published in Remote Settings.
+
+ Flags for format options:
+ "cxx" -> C++ array [default]
+ "bin" -> Binary file
+ """
+
+ output_format = "bin" if "--bin" in sys.argv else "cxx"
+ main(sys.stdout, sys.argv[1], output_format=output_format)
diff --git a/netwerk/dns/punycode.c b/netwerk/dns/punycode.c
new file mode 100644
index 0000000000..4653216507
--- /dev/null
+++ b/netwerk/dns/punycode.c
@@ -0,0 +1,325 @@
+/*
+punycode.c from RFC 3492
+http://www.nicemice.net/idn/
+Adam M. Costello
+http://www.nicemice.net/amc/
+
+This is ANSI C code (C89) implementing Punycode (RFC 3492).
+
+
+C. Disclaimer and license
+
+ Regarding this entire document or any portion of it (including
+ the pseudocode and C code), the author makes no guarantees and
+ is not responsible for any damage resulting from its use. The
+ author grants irrevocable permission to anyone to use, modify,
+ and distribute it in any way that does not diminish the rights
+ of anyone else to use, modify, and distribute it, provided that
+ redistributed derivative works do not contain misleading author or
+ version information. Derivative works need not be licensed under
+ similar terms.
+*/
+
+#include "punycode.h"
+
+/**********************************************************/
+/* Implementation (would normally go in its own .c file): */
+
+#include <string.h>
+
+/*** Bootstring parameters for Punycode ***/
+
+enum {
+ base = 36,
+ tmin = 1,
+ tmax = 26,
+ skew = 38,
+ damp = 700,
+ initial_bias = 72,
+ initial_n = 0x80,
+ delimiter = 0x2D
+};
+
+/* basic(cp) tests whether cp is a basic code point: */
+#define basic(cp) ((punycode_uint)(cp) < 0x80)
+
+/* delim(cp) tests whether cp is a delimiter: */
+#define delim(cp) ((cp) == delimiter)
+
+/* decode_digit(cp) returns the numeric value of a basic code */
+/* point (for use in representing integers) in the range 0 to */
+/* base-1, or base if cp is does not represent a value. */
+
+static punycode_uint decode_digit(punycode_uint cp) {
+ return cp - 48 < 10 ? cp - 22
+ : cp - 65 < 26 ? cp - 65
+ : cp - 97 < 26 ? cp - 97
+ : base;
+}
+
+/* encode_digit(d,flag) returns the basic code point whose value */
+/* (when used for representing integers) is d, which needs to be in */
+/* the range 0 to base-1. The lowercase form is used unless flag is */
+/* nonzero, in which case the uppercase form is used. The behavior */
+/* is undefined if flag is nonzero and digit d has no uppercase form. */
+
+static char encode_digit(punycode_uint d, int flag) {
+ return d + 22 + 75 * (d < 26) - ((flag != 0) << 5);
+ /* 0..25 map to ASCII a..z or A..Z */
+ /* 26..35 map to ASCII 0..9 */
+}
+
+/* flagged(bcp) tests whether a basic code point is flagged */
+/* (uppercase). The behavior is undefined if bcp is not a */
+/* basic code point. */
+
+#define flagged(bcp) ((punycode_uint)(bcp)-65 < 26)
+
+/* encode_basic(bcp,flag) forces a basic code point to lowercase */
+/* if flag is zero, uppercase if flag is nonzero, and returns */
+/* the resulting code point. The code point is unchanged if it */
+/* is caseless. The behavior is undefined if bcp is not a basic */
+/* code point. */
+
+static char encode_basic(punycode_uint bcp, int flag) {
+ bcp -= (bcp - 97 < 26) << 5;
+ return bcp + ((!flag && (bcp - 65 < 26)) << 5);
+}
+
+/*** Platform-specific constants ***/
+
+/* maxint is the maximum value of a punycode_uint variable: */
+static const punycode_uint maxint = (punycode_uint)-1;
+/* Because maxint is unsigned, -1 becomes the maximum value. */
+
+/*** Bias adaptation function ***/
+
+static punycode_uint adapt(punycode_uint delta, punycode_uint numpoints,
+ int firsttime) {
+ punycode_uint k;
+
+ delta = firsttime ? delta / damp : delta >> 1;
+ /* delta >> 1 is a faster way of doing delta / 2 */
+ delta += delta / numpoints;
+
+ for (k = 0; delta > ((base - tmin) * tmax) / 2; k += base) {
+ delta /= base - tmin;
+ }
+
+ return k + (base - tmin + 1) * delta / (delta + skew);
+}
+
+/*** Main encode function ***/
+
+enum punycode_status punycode_encode(punycode_uint input_length,
+ const punycode_uint input[],
+ const unsigned char case_flags[],
+ punycode_uint* output_length,
+ char output[]) {
+ punycode_uint n, delta, h, b, out, max_out, bias, j, m, q, k, t;
+
+ /* Initialize the state: */
+
+ n = initial_n;
+ delta = out = 0;
+ max_out = *output_length;
+ bias = initial_bias;
+
+ /* Handle the basic code points: */
+
+ for (j = 0; j < input_length; ++j) {
+ if (basic(input[j])) {
+ if (max_out - out < 2) {
+ return punycode_big_output;
+ }
+ output[out++] =
+ case_flags ? encode_basic(input[j], case_flags[j]) : (char)input[j];
+ }
+ /* else if (input[j] < n) return punycode_bad_input; */
+ /* (not needed for Punycode with unsigned code points) */
+ }
+
+ h = b = out;
+
+ /* h is the number of code points that have been handled, b is the */
+ /* number of basic code points, and out is the number of characters */
+ /* that have been output. */
+
+ if (b > 0) {
+ output[out++] = delimiter;
+ }
+
+ /* Main encoding loop: */
+
+ while (h < input_length) {
+ /* All non-basic code points < n have been */
+ /* handled already. Find the next larger one: */
+
+ for (m = maxint, j = 0; j < input_length; ++j) {
+ /* if (basic(input[j])) continue; */
+ /* (not needed for Punycode) */
+ if (input[j] >= n && input[j] < m) {
+ m = input[j];
+ }
+ }
+
+ /* Increase delta enough to advance the decoder's */
+ /* <n,i> state to <m,0>, but guard against overflow: */
+
+ if (m - n > (maxint - delta) / (h + 1)) {
+ return punycode_overflow;
+ }
+ delta += (m - n) * (h + 1);
+ n = m;
+
+ for (j = 0; j < input_length; ++j) {
+ /* Punycode does not need to check whether input[j] is basic: */
+ if (input[j] < n /* || basic(input[j]) */) {
+ if (++delta == 0) {
+ return punycode_overflow;
+ }
+ }
+
+ if (input[j] == n) {
+ /* Represent delta as a generalized variable-length integer: */
+
+ for (q = delta, k = base;; k += base) {
+ if (out >= max_out) {
+ return punycode_big_output;
+ }
+ t = k <= bias /* + tmin */ ? tmin : /* +tmin not needed */
+ k >= bias + tmax ? tmax
+ : k - bias;
+ if (q < t) {
+ break;
+ }
+ output[out++] = encode_digit(t + (q - t) % (base - t), 0);
+ q = (q - t) / (base - t);
+ }
+
+ output[out++] = encode_digit(q, case_flags && case_flags[j]);
+ bias = adapt(delta, h + 1, h == b);
+ delta = 0;
+ ++h;
+ }
+ }
+
+ ++delta, ++n;
+ }
+
+ *output_length = out;
+ return punycode_success;
+}
+
+/*** Main decode function ***/
+
+enum punycode_status punycode_decode(punycode_uint input_length,
+ const char input[],
+ punycode_uint* output_length,
+ punycode_uint output[],
+ unsigned char case_flags[]) {
+ punycode_uint n, out, i, max_out, bias, b, j, in, oldi, w, k, digit, t;
+
+ if (!input_length) {
+ return punycode_bad_input;
+ }
+
+ /* Initialize the state: */
+
+ n = initial_n;
+ out = i = 0;
+ max_out = *output_length;
+ bias = initial_bias;
+
+ /* Handle the basic code points: Let b be the number of input code */
+ /* points before the last delimiter, or 0 if there is none, then */
+ /* copy the first b code points to the output. */
+
+ for (b = 0, j = input_length - 1; j > 0; --j) {
+ if (delim(input[j])) {
+ b = j;
+ break;
+ }
+ }
+ if (b > max_out) {
+ return punycode_big_output;
+ }
+
+ for (j = 0; j < b; ++j) {
+ if (case_flags) {
+ case_flags[out] = flagged(input[j]);
+ }
+ if (!basic(input[j])) {
+ return punycode_bad_input;
+ }
+ output[out++] = input[j];
+ }
+
+ /* Main decoding loop: Start just after the last delimiter if any */
+ /* basic code points were copied; start at the beginning otherwise. */
+
+ for (in = b > 0 ? b + 1 : 0; in < input_length; ++out) {
+ /* in is the index of the next character to be consumed, and */
+ /* out is the number of code points in the output array. */
+
+ /* Decode a generalized variable-length integer into delta, */
+ /* which gets added to i. The overflow checking is easier */
+ /* if we increase i as we go, then subtract off its starting */
+ /* value at the end to obtain delta. */
+
+ for (oldi = i, w = 1, k = base;; k += base) {
+ if (in >= input_length) {
+ return punycode_bad_input;
+ }
+ digit = decode_digit(input[in++]);
+ if (digit >= base) {
+ return punycode_bad_input;
+ }
+ if (digit > (maxint - i) / w) {
+ return punycode_overflow;
+ }
+ i += digit * w;
+ t = k <= bias /* + tmin */ ? tmin : /* +tmin not needed */
+ k >= bias + tmax ? tmax
+ : k - bias;
+ if (digit < t) {
+ break;
+ }
+ if (w > maxint / (base - t)) {
+ return punycode_overflow;
+ }
+ w *= (base - t);
+ }
+
+ bias = adapt(i - oldi, out + 1, oldi == 0);
+
+ /* i was supposed to wrap around from out+1 to 0, */
+ /* incrementing n each time, so we'll fix that now: */
+
+ if (i / (out + 1) > maxint - n) {
+ return punycode_overflow;
+ }
+ n += i / (out + 1);
+ i %= (out + 1);
+
+ /* Insert n at position i of the output: */
+
+ /* not needed for Punycode: */
+ /* if (decode_digit(n) <= base) return punycode_invalid_input; */
+ if (out >= max_out) {
+ return punycode_big_output;
+ }
+
+ if (case_flags) {
+ memmove(case_flags + i + 1, case_flags + i, out - i);
+ /* Case of last character determines uppercase flag: */
+ case_flags[i] = flagged(input[in - 1]);
+ }
+
+ memmove(output + i + 1, output + i, (out - i) * sizeof *output);
+ output[i++] = n;
+ }
+
+ *output_length = out;
+ return punycode_success;
+}
diff --git a/netwerk/dns/punycode.h b/netwerk/dns/punycode.h
new file mode 100644
index 0000000000..6a626c7a07
--- /dev/null
+++ b/netwerk/dns/punycode.h
@@ -0,0 +1,106 @@
+/*
+punycode.c from RFC 3492
+http://www.nicemice.net/idn/
+Adam M. Costello
+http://www.nicemice.net/amc/
+
+This is ANSI C code (C89) implementing Punycode (RFC 3492).
+
+
+
+C. Disclaimer and license
+
+ Regarding this entire document or any portion of it (including
+ the pseudocode and C code), the author makes no guarantees and
+ is not responsible for any damage resulting from its use. The
+ author grants irrevocable permission to anyone to use, modify,
+ and distribute it in any way that does not diminish the rights
+ of anyone else to use, modify, and distribute it, provided that
+ redistributed derivative works do not contain misleading author or
+ version information. Derivative works need not be licensed under
+ similar terms.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/************************************************************/
+/* Public interface (would normally go in its own .h file): */
+
+#include <limits.h>
+
+enum punycode_status {
+ punycode_success,
+ punycode_bad_input, /* Input is invalid. */
+ punycode_big_output, /* Output would exceed the space provided. */
+ punycode_overflow /* Input needs wider integers to process. */
+};
+
+#if UINT_MAX >= (1 << 26) - 1
+typedef unsigned int punycode_uint;
+#else
+typedef unsigned long punycode_uint;
+#endif
+
+enum punycode_status punycode_encode(punycode_uint input_length,
+ const punycode_uint input[],
+ const unsigned char case_flags[],
+ punycode_uint* output_length,
+ char output[]);
+
+/* punycode_encode() converts Unicode to Punycode. The input */
+/* is represented as an array of Unicode code points (not code */
+/* units; surrogate pairs are not allowed), and the output */
+/* will be represented as an array of ASCII code points. The */
+/* output string is *not* null-terminated; it will contain */
+/* zeros if and only if the input contains zeros. (Of course */
+/* the caller can leave room for a terminator and add one if */
+/* needed.) The input_length is the number of code points in */
+/* the input. The output_length is an in/out argument: the */
+/* caller passes in the maximum number of code points that it */
+/* can receive, and on successful return it will contain the */
+/* number of code points actually output. The case_flags array */
+/* holds input_length boolean values, where nonzero suggests that */
+/* the corresponding Unicode character be forced to uppercase */
+/* after being decoded (if possible), and zero suggests that */
+/* it be forced to lowercase (if possible). ASCII code points */
+/* are encoded literally, except that ASCII letters are forced */
+/* to uppercase or lowercase according to the corresponding */
+/* uppercase flags. If case_flags is a null pointer then ASCII */
+/* letters are left as they are, and other code points are */
+/* treated as if their uppercase flags were zero. The return */
+/* value can be any of the punycode_status values defined above */
+/* except punycode_bad_input; if not punycode_success, then */
+/* output_size and output might contain garbage. */
+
+enum punycode_status punycode_decode(punycode_uint input_length,
+ const char input[],
+ punycode_uint* output_length,
+ punycode_uint output[],
+ unsigned char case_flags[]);
+
+/* punycode_decode() converts Punycode to Unicode. The input is */
+/* represented as an array of ASCII code points, and the output */
+/* will be represented as an array of Unicode code points. The */
+/* input_length is the number of code points in the input. The */
+/* output_length is an in/out argument: the caller passes in */
+/* the maximum number of code points that it can receive, and */
+/* on successful return it will contain the actual number of */
+/* code points output. The case_flags array needs room for at */
+/* least output_length values, or it can be a null pointer if the */
+/* case information is not needed. A nonzero flag suggests that */
+/* the corresponding Unicode character be forced to uppercase */
+/* by the caller (if possible), while zero suggests that it be */
+/* forced to lowercase (if possible). ASCII code points are */
+/* output already in the proper case, but their flags will be set */
+/* appropriately so that applying the flags would be harmless. */
+/* The return value can be any of the punycode_status values */
+/* defined above; if not punycode_success, then output_length, */
+/* output, and case_flags might contain garbage. On success, the */
+/* decoder will never need to write an output_length greater than */
+/* input_length, because of how the encoding is defined. */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
diff --git a/netwerk/dns/tests/moz.build b/netwerk/dns/tests/moz.build
new file mode 100644
index 0000000000..fb176c1bd3
--- /dev/null
+++ b/netwerk/dns/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+DIRS += ["unit"]
diff --git a/netwerk/dns/tests/unit/data/fake_public_suffix_list.dat b/netwerk/dns/tests/unit/data/fake_public_suffix_list.dat
new file mode 100644
index 0000000000..ca1da32eeb
--- /dev/null
+++ b/netwerk/dns/tests/unit/data/fake_public_suffix_list.dat
@@ -0,0 +1,57 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// This is a fake suffix list created only for the purposes of testing,
+// The real PSL is rather large thus this file is kept small by cutting out most of the suffixes
+
+// The Original list can be found at https://publicsuffix.org/list/public_suffix_list.dat,
+// Learn more about the PSL at https://publicsuffix.org.
+
+// .xpcshelltest is the fake domain created specifically for the the tests while
+// the others are ripped from the real list, serving as examples.
+
+// This fake public suffix list was used to create the binary file fake_remote_dafsa.bin
+// The binary is built at compile time and can be found at
+// obj-dir/_tests/xpcshell/netwerk/dns/tests/unit/data/
+
+// The list created with help of netwerk/dns/prepare_tlds.py and xpcom/ds/tools/make_dafsa.py
+// The build directive for the binary is at moz.build at netwerk/dns/tests/unit/data/
+
+
+
+// ===BEGIN ICANN DOMAINS===
+
+// xpcshelltest : Used in tests
+xpcshelltest
+website.xpcshelltest
+com.xpcshelltest
+edu.xpcshelltest
+gov.xpcshelltest
+net.xpcshelltest
+mil.xpcshelltest
+org.xpcshelltest
+
+// ac : https://en.wikipedia.org/wiki/.ac
+ac
+coc.ac
+com.ac
+edu.ac
+gov.ac
+net.ac
+mil.ac
+org.ac
+
+// bj : https://en.wikipedia.org/wiki/.bj
+bj
+asso.bj
+barreau.bj
+gouv.bj
+
+// bm : http://www.bermudanic.bm/dnr-text.txt
+bm
+com.bm
+edu.bm
+gov.bm
+net.bm
+org.bm
diff --git a/netwerk/dns/tests/unit/data/moz.build b/netwerk/dns/tests/unit/data/moz.build
new file mode 100644
index 0000000000..0f5a164f77
--- /dev/null
+++ b/netwerk/dns/tests/unit/data/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+GeneratedFile(
+ "fake_remote_dafsa.bin",
+ script="../../../prepare_tlds.py",
+ inputs=["fake_public_suffix_list.dat"],
+ flags=["bin"],
+)
+
+TEST_HARNESS_FILES.xpcshell.netwerk.dns.tests.unit.data += ["!fake_remote_dafsa.bin"]
diff --git a/netwerk/dns/tests/unit/moz.build b/netwerk/dns/tests/unit/moz.build
new file mode 100644
index 0000000000..e5ce568e70
--- /dev/null
+++ b/netwerk/dns/tests/unit/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+DIRS += ["data"]
diff --git a/netwerk/dns/tests/unit/test_PublicSuffixList.js b/netwerk/dns/tests/unit/test_PublicSuffixList.js
new file mode 100644
index 0000000000..ad61abea8e
--- /dev/null
+++ b/netwerk/dns/tests/unit/test_PublicSuffixList.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PublicSuffixList } = ChromeUtils.importESModule(
+ "resource://gre/modules/netwerk-dns/PublicSuffixList.sys.mjs"
+);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+const CLIENT = PublicSuffixList.CLIENT;
+const SIGNAL = "public-suffix-list-updated";
+
+const PAYLOAD_UPDATED_RECORDS = {
+ current: [{ id: "tld-dafsa", "commit-hash": "current-commit-hash" }],
+ created: [],
+ updated: [
+ {
+ old: { id: "tld-dafsa", "commit-hash": "current-commit-hash" },
+ new: { id: "tld-dafsa", "commit-hash": "new-commit-hash" },
+ },
+ ],
+ deleted: [],
+};
+const PAYLOAD_CREATED_RECORDS = {
+ current: [],
+ created: [
+ {
+ id: "tld-dafsa",
+ "commit-hash": "new-commit-hash",
+ attachment: {},
+ },
+ ],
+ updated: [],
+ deleted: [],
+};
+const PAYLOAD_UPDATED_AND_CREATED_RECORDS = {
+ current: [{ id: "tld-dafsa", "commit-hash": "current-commit-hash" }],
+ created: [{ id: "tld-dafsa", "commit-hash": "another-commit-hash" }],
+ updated: [
+ {
+ old: { id: "tld-dafsa", "commit-hash": "current-commit-hash" },
+ new: { id: "tld-dafsa", "commit-hash": "new-commit-hash" },
+ },
+ ],
+ deleted: [],
+};
+
+const fakeDafsaBinFile = do_get_file("data/fake_remote_dafsa.bin");
+const mockedFilePath = fakeDafsaBinFile.path;
+
+function setup() {
+ Services.prefs.setBoolPref("network.psl.onUpdate_notify", true);
+}
+setup();
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.psl.onUpdate_notify");
+});
+
+/**
+ * downloadToDiskCalled is used by mockDownloadToDisk() and resetMockDownloadToDisk()
+ * to keep track weather CLIENT.attachments.download is called or not
+ * downloadToDiskBackup will help restore CLIENT.attachments.download to original definition
+ * notifyUpdateBackup will help restore PublicSuffixList.notifyUpdate to original definition
+ */
+let downloadToDiskCalled = false;
+const downloadToDiskBackup = CLIENT.attachments.downloadToDisk;
+
+// returns a fake fileURI and sends a signal with filePath and no nsifile
+function mockDownloadToDisk() {
+ downloadToDiskCalled = false;
+ CLIENT.attachments.downloadToDisk = async rec => {
+ downloadToDiskCalled = true;
+ return `file://${mockedFilePath}`; // Create a fake file URI
+ };
+}
+
+// resetMockDownloadToDisk() must be run at the end of the test that uses mockDownloadToDisk()
+const resetMockDownloadToDisk = () => {
+ CLIENT.attachments.downloadToDisk = downloadToDiskBackup;
+};
+
+add_task(async () => {
+ info("File path sent when record is in DB.");
+
+ await CLIENT.db.clear(); // Make sure there's no record initially
+ await CLIENT.db.create({
+ id: "tld-dafsa",
+ "commit-hash": "fake-commit-hash",
+ attachment: {},
+ });
+
+ mockDownloadToDisk();
+
+ const promiseSignal = TestUtils.topicObserved(SIGNAL);
+ await PublicSuffixList.init();
+ const observed = await promiseSignal;
+
+ Assert.equal(
+ observed[1],
+ mockedFilePath,
+ "File path sent when record is in DB."
+ );
+ await CLIENT.db.clear(); // Clean up the mockDownloaded record
+ resetMockDownloadToDisk();
+});
+
+add_task(async () => {
+ info("File path sent when record updated.");
+
+ mockDownloadToDisk();
+
+ const promiseSignal = TestUtils.topicObserved(SIGNAL);
+ await PublicSuffixList.init();
+ await CLIENT.emit("sync", { data: PAYLOAD_UPDATED_RECORDS });
+ const observed = await promiseSignal;
+
+ Assert.equal(
+ observed[1],
+ mockedFilePath,
+ "File path sent when record updated."
+ );
+ resetMockDownloadToDisk();
+});
+
+add_task(async () => {
+ info("Attachment downloaded when record created.");
+
+ mockDownloadToDisk();
+
+ await PublicSuffixList.init();
+ await CLIENT.emit("sync", { data: PAYLOAD_CREATED_RECORDS });
+
+ Assert.equal(
+ downloadToDiskCalled,
+ true,
+ "Attachment downloaded when record created."
+ );
+ resetMockDownloadToDisk();
+});
+
+add_task(async () => {
+ info("Attachment downloaded when record updated.");
+
+ mockDownloadToDisk();
+
+ await PublicSuffixList.init();
+ await CLIENT.emit("sync", { data: PAYLOAD_UPDATED_RECORDS });
+
+ Assert.equal(
+ downloadToDiskCalled,
+ true,
+ "Attachment downloaded when record updated."
+ );
+ resetMockDownloadToDisk();
+});
+
+add_task(async () => {
+ info("No download when more than one record is changed.");
+
+ mockDownloadToDisk();
+
+ await PublicSuffixList.init();
+ await CLIENT.emit("sync", { data: PAYLOAD_UPDATED_AND_CREATED_RECORDS });
+
+ Assert.equal(
+ downloadToDiskCalled,
+ false,
+ "No download when more than one record is changed."
+ );
+ resetMockDownloadToDisk();
+});
diff --git a/netwerk/dns/tests/unit/test_nsEffectiveTLDService_Reload_DAFSA.js b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_Reload_DAFSA.js
new file mode 100644
index 0000000000..6b89b070d5
--- /dev/null
+++ b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_Reload_DAFSA.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const SIGNAL = "public-suffix-list-updated";
+
+add_task(async () => {
+ info("Before fake dafsa reload.");
+
+ let suffix = Services.eTLD.getPublicSuffixFromHost("website.xpcshelltest");
+ Assert.equal(
+ suffix,
+ "xpcshelltest",
+ "Fake Suffix does not exist in current PSL."
+ );
+});
+
+add_task(async () => {
+ info("After fake dafsa reload.");
+
+ // reload the PSL with fake data containing .xpcshelltest
+ const fakeDafsaFile = do_get_file("data/fake_remote_dafsa.bin");
+ Services.obs.notifyObservers(fakeDafsaFile, SIGNAL, fakeDafsaFile.path);
+
+ let suffix = Services.eTLD.getPublicSuffixFromHost("website.xpcshelltest");
+ Assert.equal(
+ suffix,
+ "website.xpcshelltest",
+ "Fake Suffix now exists in PSL after DAFSA reload."
+ );
+});
diff --git a/netwerk/dns/tests/unit/test_nsEffectiveTLDService_getKnownPublicSuffix.js b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_getKnownPublicSuffix.js
new file mode 100644
index 0000000000..333692af97
--- /dev/null
+++ b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_getKnownPublicSuffix.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests getPublicSuffix with the validate argument.
+ */
+
+"use strict";
+
+add_task(() => {
+ for (let [suffix, isKnown] of [
+ ["", false],
+ [null, false],
+ ["mozbacon", false],
+ ["com", true],
+ ["circle", true],
+ ["bd", true],
+ ["gov.bd", true],
+ ["ck", true],
+ ["www.ck", true],
+ ["bs", true],
+ ["com.bs", true],
+ ["網絡.cn", true],
+ ["valléedaoste.it", true],
+ ["aurskog-høland.no", true],
+ ["公司.香港", true],
+ ["भारतम्", true],
+ ["فلسطين", true],
+ ]) {
+ let origin = "test." + suffix;
+ Assert.equal(
+ !!Services.eTLD.getKnownPublicSuffixFromHost(origin),
+ isKnown,
+ `"${suffix}" should ${isKnown ? " " : "not "}be a known public suffix`
+ );
+ Assert.equal(
+ !!Services.eTLD.getKnownPublicSuffix(
+ Services.io.newURI("http://" + origin)
+ ),
+ isKnown,
+ `"${suffix}" should ${isKnown ? " " : "not "}be a known public suffix`
+ );
+ }
+});
diff --git a/netwerk/dns/tests/unit/test_nsEffectiveTLDService_getSite.js b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_getSite.js
new file mode 100644
index 0000000000..5f6f3b1eb3
--- /dev/null
+++ b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_getSite.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests getSite and getSchemelessSite with example arguments
+ */
+
+"use strict";
+
+add_task(() => {
+ for (let [originString, result] of [
+ ["http://.", null],
+ ["http://com", "http://com"],
+ ["http://test", "http://test"],
+ ["http://test.", "http://test."],
+ ["http://[::1]", "http://[::1]"],
+ ["http://[::1]:8888", "http://[::1]"],
+ ["http://localhost", "http://localhost"],
+ ["http://127.0.0.1", "http://127.0.0.1"],
+ ["http://user:pass@[::1]", "http://[::1]"],
+ ["http://example.com", "http://example.com"],
+ ["https://example.com", "https://example.com"],
+ ["https://test.example.com", "https://example.com"],
+ ["https://test1.test2.example.com", "https://example.com"],
+ ["https://test1.test2.example.co.uk", "https://example.co.uk"],
+ ["https://test.example.com:8888/index.html", "https://example.com"],
+ [
+ "https://test1.test2.example.公司.香港",
+ "https://example.xn--55qx5d.xn--j6w193g",
+ ],
+ ]) {
+ let origin = Services.io.newURI(originString);
+ if (result === null) {
+ Assert.throws(
+ () => {
+ Services.eTLD.getSite(origin);
+ },
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Invalid origin for getSite throws"
+ );
+ } else {
+ let answer = Services.eTLD.getSite(origin);
+ Assert.equal(
+ answer,
+ result,
+ `"${originString}" should have site ${result}, got ${answer}.`
+ );
+ }
+ }
+});
+
+add_task(() => {
+ for (let [originString, result] of [
+ ["http://com", "com"],
+ ["http://test", "test"],
+ ["http://test.", "test."],
+ ["http://[::1]", "[::1]"],
+ ["http://[::1]:8888", "[::1]"],
+ ["http://localhost", "localhost"],
+ ["http://127.0.0.1", "127.0.0.1"],
+ ["http://user:pass@[::1]", "[::1]"],
+ ["http://example.com", "example.com"],
+ ["https://example.com", "example.com"],
+ ["https://test.example.com", "example.com"],
+ ["https://test1.test2.example.com", "example.com"],
+ ["https://test1.test2.example.co.uk", "example.co.uk"],
+ ["https://test1.test2.example.公司.香港", "example.xn--55qx5d.xn--j6w193g"],
+ ]) {
+ let origin = Services.io.newURI(originString);
+ let answer = Services.eTLD.getSchemelessSite(origin);
+ Assert.equal(
+ answer,
+ result,
+ `"${originString}" should have schemeless site ${result}, got ${answer}.`
+ );
+ }
+});
diff --git a/netwerk/dns/tests/unit/xpcshell.toml b/netwerk/dns/tests/unit/xpcshell.toml
new file mode 100644
index 0000000000..1047b7d496
--- /dev/null
+++ b/netwerk/dns/tests/unit/xpcshell.toml
@@ -0,0 +1,17 @@
+[DEFAULT]
+head = "../../../../services/common/tests/unit/head_global.js ../../../../services/common/tests/unit/head_helpers.js"
+firefox-appdir = "browser"
+support-files = [
+ "data/**",
+ "!/services/common/tests/unit/head_global.js",
+ "!/services/common/tests/unit/head_helpers.js",
+]
+
+["test_PublicSuffixList.js"]
+tags = "remote-settings"
+
+["test_nsEffectiveTLDService_Reload_DAFSA.js"]
+
+["test_nsEffectiveTLDService_getKnownPublicSuffix.js"]
+
+["test_nsEffectiveTLDService_getSite.js"]
diff --git a/netwerk/docs/cache2/doc.rst b/netwerk/docs/cache2/doc.rst
new file mode 100644
index 0000000000..b9be70abc8
--- /dev/null
+++ b/netwerk/docs/cache2/doc.rst
@@ -0,0 +1,569 @@
+HTTP Cache
+==========
+
+This document describes the **HTTP cache implementation**.
+
+The code resides in `/netwerk/cache2 (searchfox)
+<https://searchfox.org/mozilla-central/source/netwerk/cache2>`_
+
+API
+---
+
+Here is a detailed description of the HTTP cache v2 API, examples
+included. This document only contains what cannot be found or may not
+be clear directly from the `IDL files <https://searchfox.org/mozilla-central/search?q=&path=cache2%2FnsICache&case=false&regexp=false>`_ comments.
+
+- The cache API is **completely thread-safe** and **non-blocking**.
+- There is **no IPC support**. It's only accessible on the default
+ chrome process.
+- When there is no profile the new HTTP cache works, but everything is
+ stored only in memory not obeying any particular limits.
+
+.. _nsICacheStorageService:
+
+nsICacheStorageService
+----------------------
+
+- The HTTP cache entry-point. Accessible as a service only, fully
+ thread-safe, scriptable.
+
+- `nsICacheStorageService.idl (searchfox) <https://searchfox.org/mozilla-central/source/netwerk/cache2/nsICacheStorageService.idl>`_
+
+- \ ``"@mozilla.org/netwerk/cache-storage-service;1"``
+
+- Provides methods accessing "storage" objects – see `nsICacheStorage` below – giving further access to cache entries – see :ref:`nsICacheEntry <nsICacheEntry>` more below – per specific URL.
+
+- Currently we have 3 types of storages, all the access methods return
+ an :ref:`nsICacheStorage <nsICacheStorage>` object:
+
+ - **memory-only** (``memoryCacheStorage``): stores data only in a
+ memory cache, data in this storage are never put to disk
+
+ - **disk** (``diskCacheStorage``): stores data on disk, but for
+ existing entries also looks into the memory-only storage; when
+ instructed via a special argument also primarily looks into
+ application caches
+
+ .. note::
+
+ **application cache** (``appCacheStorage``): when a consumer has a
+ specific ``nsIApplicationCache`` (i.e. a particular app cache
+ version in a group) in hands, this storage will provide read and
+ write access to entries in that application cache; when the app
+ cache is not specified, this storage will operate over all
+ existing app caches. **This kind of storage is deprecated and will be removed** in `bug 1694662 <https://bugzilla.mozilla.org/show_bug.cgi?id=1694662>`_
+
+- The service also provides methods to clear the whole disk and memory
+ cache content or purge any intermediate memory structures:
+
+ - ``clear``– after it returns, all entries are no longer accessible
+ through the cache APIs; the method is fast to execute and
+ non-blocking in any way; the actual erase happens in background
+
+ - ``purgeFromMemory``– removes (schedules to remove) any
+ intermediate cache data held in memory for faster access (more
+ about the :ref:`Intermediate_Memory_Caching <Intermediate_Memory_Caching>` below)
+
+.. _nsILoadContextInfo:
+
+nsILoadContextInfo
+------------------
+
+- Distinguishes the scope of the storage demanded to open.
+
+- Mandatory argument to ``*Storage`` methods of :ref:`nsICacheStorageService <nsICacheStorageService>`.
+
+- `nsILoadContextInfo.idl (searchfox) <https://searchfox.org/mozilla-central/source/netwerk/base/nsILoadContextInfo.idl>`_
+
+
+- It is a helper interface wrapping following four arguments into a single one:
+
+ - **private-browsing** boolean flag
+ - **anonymous load** boolean flag
+ - **origin attributes** js value
+
+ .. note::
+
+ Helper functions to create nsILoadContextInfo objects:
+
+ - C++ consumers: functions at ``LoadContextInfo.h`` exported
+ header
+
+ - JS consumers: ``Services.loadContextInfo`` which is an instance of ``nsILoadContextInfoFactory``.
+
+- Two storage objects created with the same set of
+ ``nsILoadContextInfo``\ arguments are identical, containing the same
+ cache entries.
+
+- Two storage objects created with in any way different
+ ``nsILoadContextInfo``\ arguments are strictly and completely
+ distinct and cache entries in them do not overlap even when having
+ the same URIs.
+
+.. _nsICacheStorage:
+
+nsICacheStorage
+---------------
+
+- `nsICacheStorage.idl (searchfox) <https://searchfox.org/mozilla-central/source/netwerk/cache2/nsICacheStorage.idl>`_
+
+- Obtained from call to one of the ``*Storage`` methods on
+ :ref:`nsICacheStorageService <nsICacheStorageService>`.
+
+- Represents a distinct storage area (or scope) to put and get cache
+ entries mapped by URLs into and from it.
+
+- *Similarity with the old cache*\ : this interface may be with some
+ limitations considered as a mirror to ``nsICacheSession``, but less
+ generic and not inclining to abuse.
+
+nsICacheEntryOpenCallback
+-------------------------
+
+- `nsICacheEntryOpenCallback.idl (searchfox) <https://searchfox.org/mozilla-central/source/netwerk/cache2/nsICacheEntryOpenCallback.idl>`_
+
+- The result of ``nsICacheStorage.asyncOpenURI`` is always and only
+ sent to callbacks on this interface.
+
+- These callbacks are ensured to be invoked when ``asyncOpenURI``
+ returns ``NS_OK``.
+
+-
+
+ .. note::
+
+ When the
+ cache entry object is already present in memory or open as
+ "force-new" (a.k.a "open-truncate") this callback is invoked
+ sooner then the ``asyncOpenURI``\ method returns (i.e.
+ immediately); there is currently no way to opt out of this feature
+ (see `bug
+ 938186 <https://bugzilla.mozilla.org/show_bug.cgi?id=938186>`__).
+
+.. _nsICacheEntry:
+
+nsICacheEntry
+-------------
+
+- `nsICacheEntry.idl (searchfox) <https://searchfox.org/mozilla-central/source/netwerk/cache2/nsICacheEntry.idl>`_
+
+- Obtained asynchronously or pseudo-asynchronously by a call to
+ ``nsICacheStorage.asyncOpenURI``.
+
+- Provides access to a cached entry data and meta data for reading or
+ writing or in some cases both, see below.
+
+Lifetime of a new entry
+-----------------------
+
+- Such entry is initially empty (no data or meta data is stored in it).
+
+- The ``aNew``\ argument in ``onCacheEntryAvailable`` is ``true`` for
+ and only for new entries.
+
+- Only one consumer (the so called "*writer*") may have such an entry
+ available (obtained via ``onCacheEntryAvailable``).
+
+- Other parallel openers of the same cache entry are blocked (wait) for
+ invocation of their ``onCacheEntryAvailable`` until one of the
+ following occurs:
+
+ - The *writer* simply throws the entry away: other waiting opener in
+ line gets the entry again as "*new*", the cycle repeats.
+
+ .. note::
+
+ This applies in general, writers throwing away the cache entry
+ means a failure to write the cache entry and a new writer is
+ being looked for again, the cache entry remains empty (a.k.a.
+ "new").
+
+ - The *writer* stored all necessary meta data in the cache entry and
+ called ``metaDataReady`` on it: other consumers now get the entry
+ and may examine and potentially modify the meta data and read the
+ data (if any) of the cache entry.
+ - When the *writer* has data (i.e. the response payload) to write to
+ the cache entry, it **must** open the output stream on it
+ **before** it calls ``metaDataReady``.
+
+- When the *writer* still keeps the cache entry and has open and keeps
+ open the output stream on it, other consumers may open input streams
+ on the entry. The data will be available as the *writer* writes data
+ to the cache entry's output stream immediately, even before the
+ output stream is closed. This is called :ref:`concurrent
+ read/write <Concurrent_read_and_write>`.
+
+.. _Concurrent_read_and_write:
+
+Concurrent read and write
+-------------------------
+
+The cache supports reading a cache entry data while it is still being
+written by the first consumer - the *writer*.
+This can only be engaged for resumable responses that (`bug
+960902 <https://bugzilla.mozilla.org/show_bug.cgi?id=960902#c17>`__)
+don't need revalidation. Reason is that when the writer is interrupted
+(by e.g. external canceling of the loading channel) concurrent readers
+would not be able to reach the remaining unread content.
+
+.. note::
+
+ This could be improved by keeping the network load running and being
+ stored to the cache entry even after the writing channel has been
+ canceled.
+
+When the *writer* is interrupted, the first concurrent *reader* in line
+does a range request for the rest of the data - and becomes that way a
+new *writer*. The rest of the *readers* are still concurrently reading
+the content since output stream for the cache entry is again open and
+kept by the current *writer*.
+
+Lifetime of an existing entry with only a partial content
+---------------------------------------------------------
+
+- Such a cache entry is first examined in the
+ ``nsICacheEntryOpenCallback.onCacheEntryCheck`` callback, where it
+ has to be checked for completeness.
+- In this case, the ``Content-Length`` (or different indicator) header
+ doesn't equal to the data size reported by the cache entry.
+- The consumer then indicates the cache entry needs to be revalidated
+ by returning ``ENTRY_NEEDS_REVALIDATION``\ from
+ ``onCacheEntryCheck``.
+- This consumer, from the point of view the cache, takes a role of the
+ *writer*.
+- Other parallel consumers, if any, are blocked until the *writer*
+ calls ``setValid`` on the cache entry.
+- The consumer is then responsible to validate the partial content
+ cache entry with the network server and attempt to load the rest of
+ the data.
+- When the server responds positively (in case of an HTTP server with a
+ 206 response code) the *writer* (in this order) opens the output
+ stream on the cache entry and calls ``setValid`` to unblock other
+ pending openers.
+- Concurrent read/write is engaged.
+
+Lifetime of an existing entry that doesn't pass server revalidation
+-------------------------------------------------------------------
+
+- Such a cache entry is first examined in the
+ ``nsICacheEntryOpenCallback.onCacheEntryCheck`` callback, where the
+ consumer finds out it must be revalidated with the server before use.
+- The consumer then indicates the cache entry needs to be revalidated
+ by returning ``ENTRY_NEEDS_REVALIDATION``\ from
+ ``onCacheEntryCheck``.
+- This consumer, from the point of view the cache, takes a role of the
+ *writer*.
+- Other parallel consumers, if any, are blocked until the *writer*
+ calls ``setValid`` on the cache entry.
+- The consumer is then responsible to validate the partial content
+ cache entry with the network server.
+- The server responses with a 200 response which means the cached
+ content is no longer valid and a new version must be loaded from the
+ network.
+- The *writer* then calls ``recreate``\ on the cache entry. This
+ returns a new empty entry to write the meta data and data to, the
+ *writer* exchanges its cache entry by this new one and handles it as
+ a new one.
+- The *writer* then (in this order) fills the necessary meta data of
+ the cache entry, opens the output stream on it and calls
+ ``metaDataReady`` on it.
+- Any other pending openers, if any, are now given this new entry to
+ examine and read as an existing entry.
+
+Adding a new storage
+--------------------
+
+Should there be a need to add a new distinct storage for which the
+current scoping model would not be sufficient - use one of the two
+following ways:
+
+#. *[preferred]* Add a new ``<Your>Storage`` method on
+ :ref:`nsICacheStorageService <nsICacheStorageService>` and if needed give it any arguments to
+ specify the storage scope even more. Implementation only should need
+ to enhance the context key generation and parsing code and enhance
+ current - or create new when needed - :ref:`nsICacheStorage <nsICacheStorage>`
+ implementations to carry any additional information down to the cache
+ service.
+#. *[*\ **not**\ *preferred]* Add a new argument to
+ :ref:`nsILoadContextInfo <nsILoadContextInfo>`; **be careful
+ here**, since some arguments on the context may not be known during
+ the load time, what may lead to inter-context data leaking or
+ implementation problems. Adding more distinction to
+ :ref:`nsILoadContextInfo <nsILoadContextInfo>` also affects all existing storages which may
+ not be always desirable.
+
+See context keying details for more information.
+
+Threading
+---------
+
+The cache API is fully thread-safe.
+
+The cache is using a single background thread where any IO operations
+like opening, reading, writing and erasing happen. Also memory pool
+management, eviction, visiting loops happen on this thread.
+
+The thread supports several priority levels. Dispatching to a level with
+a lower number is executed sooner then dispatching to higher number
+layers; also any loop on lower levels yields to higher levels so that
+scheduled deletion of 1000 files will not block opening cache entries.
+
+#. **OPEN_PRIORITY:** except opening priority cache files also file
+ dooming happens here to prevent races
+#. **READ_PRIORITY:** top level documents and head blocking script cache
+ files are open and read as the first
+#. **OPEN**
+#. **READ:** any normal priority content, such as images are open and
+ read here
+#. **WRITE:** writes are processed as last, we cache data in memory in
+ the mean time
+#. **MANAGEMENT:** level for the memory pool and CacheEntry background
+ operations
+#. **CLOSE:** file closing level
+#. **INDEX:** index is being rebuild here
+#. **EVICT:** files overreaching the disk space consumption limit are
+ being evicted here
+
+NOTE: Special case for eviction - when an eviction is scheduled on the
+IO thread, all operations pending on the OPEN level are first merged to
+the OPEN_PRIORITY level. The eviction preparation operation - i.e.
+clearing of the internal IO state - is then put to the end of the
+OPEN_PRIORITY level. All this happens atomically.
+
+Storage and entries scopes
+--------------------------
+
+A *scope key* string used to map the storage scope is based on the
+arguments of :ref:`nsILoadContextInfo <nsILoadContextInfo>`. The form is following (currently
+pending in `bug
+968593 <https://bugzilla.mozilla.org/show_bug.cgi?id=968593>`__):
+
+.. code:: JavaScript
+
+ a,b,i1009,p,
+
+- Regular expression: ``(.([-,]+)?,)*``
+- The first letter is an identifier, identifiers are to be
+ alphabetically sorted and always terminate with ','
+- a - when present the scope is belonging to an **anonymous** load
+- b - when present the scope is **in browser element** load
+- i - when present must have a decimal integer value that represents an
+ app ID the scope belongs to, otherwise there is no app (app ID is
+ considered ``0``)
+- p - when present the scope is of a **private browsing** load, this
+ never persists
+
+``CacheStorageService``\ keeps a global hashtable mapped by the *scope
+key*. Elements in this global hashtable are hashtables of cache entries.
+The cache entries are mapped by concantation of Enhance ID and URI
+passed to ``nsICacheStorage.asyncOpenURI``. So that when an entry is
+being looked up, first the global hashtable is searched using the
+*scope key*. An entries hashtable is found. Then this entries hashtable
+is searched using <enhance-id:><uri> string. The elements in this
+hashtable are CacheEntry classes, see below.
+
+The hash tables keep a strong reference to ``CacheEntry`` objects. The
+only way to remove ``CacheEntry`` objects from memory is by exhausting a
+memory limit for :ref:`Intermediate_Memory_Caching <Intermediate_Memory_Caching>`, what triggers a background
+process of purging expired and then least used entries from memory.
+Another way is to directly call the
+``nsICacheStorageService.purge``\ method. That method is also called
+automatically on the ``"memory-pressure"`` indication.
+
+Access to the hashtables is protected by a global lock. We also - in a
+thread-safe manner - count the number of consumers keeping a reference
+on each entry. The open callback actually doesn't give the consumer
+directly the ``CacheEntry`` object but a small wrapper class that
+manages the 'consumer reference counter' on its cache entry. This both
+mechanisms ensure thread-safe access and also inability to have more
+then a single instance of a ``CacheEntry`` for a single
+<scope+enhanceID+URL> key.
+
+``CacheStorage``, implementing the :ref:`nsICacheStorage <nsICacheStorage>` interface, is
+forwarding all calls to internal methods of ``CacheStorageService``
+passing itself as an argument. ``CacheStorageService`` then generates
+the *scope key* using the ``nsILoadContextInfo`` of the storage. Note:
+CacheStorage keeps a thread-safe copy of ``nsILoadContextInfo`` passed
+to a ``*Storage`` method on ``nsICacheStorageService``.
+
+Invoking open callbacks
+-----------------------
+
+``CacheEntry``, implementing the ``nsICacheEntry`` interface, is
+responsible for managing the cache entry internal state and to properly
+invoke ``onCacheEntryCheck`` and ``onCacheEntryAvaiable`` callbacks to
+all callers of ``nsICacheStorage.asyncOpenURI``.
+
+- Keeps a FIFO of all openers.
+- Keeps its internal state like NOTLOADED, LOADING, EMPTY, WRITING,
+ READY, REVALIDATING.
+- Keeps the number of consumers keeping a reference to it.
+- Refers a ``CacheFile`` object that holds actual data and meta data
+ and, when told to, persists it to the disk.
+
+The openers FIFO is an array of ``CacheEntry::Callback`` objects.
+``CacheEntry::Callback`` keeps a strong reference to the opener plus the
+opening flags. ``nsICacheStorage.asyncOpenURI`` forwards to
+``CacheEntry::AsyncOpen`` and triggers the following pseudo-code:
+
+**CacheStorage::AsyncOpenURI** - the API entry point:
+
+- globally atomic:
+
+ - look a given ``CacheEntry`` in ``CacheStorageService`` hash tables
+ up
+ - if not found: create a new one, add it to the proper hash table
+ and set its state to NOTLOADED
+ - consumer reference ++
+
+- call to `CacheEntry::AsyncOpen`
+- consumer reference --
+
+**CacheEntry::AsyncOpen** (entry atomic):
+
+- the opener is added to FIFO, consumer reference ++ (dropped back
+ after an opener is removed from the FIFO)
+- state == NOTLOADED:
+
+ - state = LOADING
+ - when OPEN_TRUNCATE flag was used:
+
+ - ``CacheFile`` is created as 'new', state = EMPTY
+
+ - otherwise:
+
+ - ``CacheFile`` is created and load on it started
+ - ``CacheEntry::OnFileReady`` notification is now expected
+
+- state == LOADING: just do nothing and exit
+- call to `CacheEntry::InvokeCallbacks`
+
+**CacheEntry::InvokeCallbacks** (entry atomic):
+
+- called on:
+
+ - a new opener has been added to the FIFO via an ``AsyncOpen`` call
+ - asynchronous result of CacheFile open ``CacheEntry::OnFileReady>``
+ - the writer throws the entry away - ``CacheEntry::OnHandleClosed``
+ - the **output stream** of the entry has been **opened** or
+ **closed**
+ - ``metaDataReady``\ or ``setValid``\ on the entry has been called
+ - the entry has been **doomed**
+
+- state == EMPTY:
+
+ - on OPER_READONLY flag use: onCacheEntryAvailable with
+ ``null``\ for the cache entry
+ - otherwise:
+
+ - state = WRITING
+ - opener is removed from the FIFO and remembered as the current
+ '*writer*'
+ - onCacheEntryAvailable with ``aNew = true``\ and this entry is
+ invoked (on the caller thread) for the *writer*
+
+- state == READY:
+
+ - onCacheEntryCheck with the entry is invoked on the first opener in
+ FIFO - on the caller thread if demanded
+ - result == RECHECK_AFTER_WRITE_FINISHED:
+
+ - opener is left in the FIFO with a flag ``RecheckAfterWrite``
+ - such openers are skipped until the output stream on the entry
+ is closed, then ``onCacheEntryCheck`` is re-invoked on them
+ - Note: here is a potential for endless looping when
+ RECHECK_AFTER_WRITE_FINISHED is abused
+
+ - result == ENTRY_NEEDS_REVALIDATION:
+
+ - state = REVALIDATING, this prevents invocation of any callback
+ until ``CacheEntry::SetValid`` is called
+ - continue as in state ENTRY_WANTED (just below)
+
+ - result == ENTRY_WANTED:
+
+ - consumer reference ++ (dropped back when the consumer releases
+ the entry)
+ - onCacheEntryAvailable is invoked on the opener with
+ ``aNew = false``\ and the entry
+ - opener is removed from the FIFO
+
+ - result == ENTRY_NOT_WANTED:
+
+ - ``onCacheEntryAvailable`` is invoked on the opener with
+ ``null``\ for the entry
+ - opener is removed from the FIFO
+
+- state == WRITING or REVALIDATING:
+
+ - do nothing and exit
+
+- any other value of state is unexpected here (assertion failure)
+- loop this process while there are openers in the FIFO
+
+**CacheEntry::OnFileReady** (entry atomic):
+
+- load result == failure or the file has not been found on disk (is
+ new): state = EMPTY
+- otherwise: state = READY since the cache file has been found and is
+ usable containing meta data and data of the entry
+- call to ``CacheEntry::InvokeCallbacks``
+
+**CacheEntry::OnHandleClosed** (entry atomic):
+
+- Called when any consumer throws the cache entry away
+- If the handle is not the handle given to the current *writer*, then
+ exit
+- state == WRITING: the writer failed to call ``metaDataReady`` on the
+ entry - state = EMPTY
+- state == REVALIDATING: the writer failed the re-validation process
+ and failed to call ``setValid`` on the entry - state = READY
+- call to ``CacheEntry::InvokeCallbacks``
+
+**All consumers release the reference:**
+
+- the entry may now be purged (removed) from memory when found expired
+ or least used on overrun of the :ref:`memory
+ pool <Intermediate_Memory_Caching>` limit
+- when this is a disk cache entry, its cached data chunks are released
+ from memory and only meta data is kept
+
+.. _Intermediate_Memory_Caching:
+
+Intermediate memory caching
+---------------------------
+
+Intermediate memory caching of frequently used metadata (a.k.a. disk cache memory pool).
+
+For the disk cache entries we keep some of the most recent and most used
+cache entries' meta data in memory for immediate zero-thread-loop
+opening. The default size of this meta data memory pool is only 250kB
+and is controlled by a new ``browser.cache.disk.metadata_memory_limit``
+preference. When the limit is exceeded, we purge (throw away) first
+**expired** and then **least used** entries to free up memory again.
+
+Only ``CacheEntry`` objects that are already loaded and filled with data
+and having the 'consumer reference == 0' (`bug
+942835 <https://bugzilla.mozilla.org/show_bug.cgi?id=942835#c3>`__) can
+be purged.
+
+The 'least used' entries are recognized by the lowest value of
+`frecency <https://wiki.mozilla.org/User:Jesse/NewFrecency?title=User:Jesse/NewFrecency>`__
+we re-compute for each entry on its every access. The decay time is
+controlled by the ``browser.cache.frecency_half_life_hours`` preference
+and defaults to 6 hours. The best decay time will be based on results of
+`an experiment <https://bugzilla.mozilla.org/show_bug.cgi?id=986728>`__.
+
+The memory pool is represented by two lists (strong referring ordered
+arrays) of ``CacheEntry`` objects:
+
+#. Sorted by expiration time (that default to 0xFFFFFFFF)
+#. Sorted by frecency (defaults to 0)
+
+We have two such pools, one for memory-only entries actually
+representing the memory-only cache and one for disk cache entries for
+which we only keep the meta data. Each pool has a different limit
+checking - the memory cache pool is controlled by
+``browser.cache.memory.capacity``, the disk entries pool is already
+described above. The pool can be accessed and modified only on the cache
+background thread.
diff --git a/netwerk/docs/captive_portals.md b/netwerk/docs/captive_portals.md
new file mode 100644
index 0000000000..fecba8f913
--- /dev/null
+++ b/netwerk/docs/captive_portals.md
@@ -0,0 +1,68 @@
+# Captive Portal Detection
+
+## What are Captive Portals?
+A captive portal is what we call a network that requires your action before it allows you to connect to the Internet. This action could be to log in using a username and password, or just to accept the network's terms and conditions.
+
+There are many different ways in which captive portal network might attempt to direct you to the captive portal page.
+- A DNS resolver that always resolves to the captive portal server IP
+- A gateway that intercepts all HTTP requests and responds with a 302/307 redirect to the captive portal page
+- A gateway that rewrites all/specific HTTP responses
+ - Changing their content to be that of the captive portal page
+ - Injecting javascript or other content into the page (Some ISPs do this when the user hasn't paid their internet bill)
+- HTTPS requests are handled differently by captive portals:
+ - They might time out.
+ - They might present the wrong certificate in order to redirect to the captive portal.
+ - They might not be intercepted at all.
+
+## Implementation
+The [CaptivePortalService](https://searchfox.org/mozilla-central/source/netwerk/base/CaptivePortalService.h) controls when the checks are performed. Consumers can check the state on [nsICaptivePortalService](https://searchfox.org/mozilla-central/source/netwerk/base/nsICaptivePortalService.idl) to determine the state of the captive portal.
+- UNKNOWN
+ - The checks have not been performed or have timed out.
+- NOT_CAPTIVE
+ - No captive portal interference was detected.
+- UNLOCKED_PORTAL
+ - A captive portal was previously detected, but has been unlocked by the user. This state might cause the browser to increase the frequency of the captive portal checks.
+- LOCKED_PORTAL
+ - A captive portal was detected, and internet connectivity is not currently available.
+ - A [captive portal notification bar](https://searchfox.org/mozilla-central/source/browser/base/content/browser-captivePortal.js) might be displayed to the user.
+
+The Captive portal service uses [CaptiveDetect.sys.mjs](https://searchfox.org/mozilla-central/source/toolkit/components/captivedetect/CaptiveDetect.jsm) to perform the checks, which in turn uses XMLHttpRequest.
+This request needs to be exempted from HTTPS upgrades, DNS over HTTPS, and many new browser features in order to function as expected.
+
+```{note}
+
+CaptiveDetect.sys.mjs would benefit from being rewritten in rust or C++.
+This is because the API of XMLHttpRequest makes it difficult to distinguish between different types of network errors such as redirect loops vs certificate errors.
+
+Also, we don't currently allow any redirects to take place, even if the redirected resource acts as a transparent proxy (doesn't modify the response). This sometimes causes issues for users on networks which employ such transparent proxies.
+
+```
+
+## Preferences
+```js
+
+pref("network.captive-portal-service.enabled", false); // controls if the checking is performed
+pref("network.captive-portal-service.minInterval", 60000); // 60 seconds
+pref("network.captive-portal-service.maxInterval", 1500000); // 25 minutes
+// Every 10 checks, the delay is increased by a factor of 5
+pref("network.captive-portal-service.backoffFactor", "5.0");
+
+// The URL used to perform the captive portal checks
+pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/canonical.html");
+// The response we expect to receive back for the canonical URL
+// It contains valid HTML that when loaded in a browser redirects the user
+// to a support page explaining captive portals.
+pref("captivedetect.canonicalContent", "<meta http-equiv=\"refresh\" content=\"0;url=https://support.mozilla.org/kb/captive-portal\"/>");
+
+// The timeout for each request.
+pref("captivedetect.maxWaitingTime", 5000);
+// time to retrigger a new request
+pref("captivedetect.pollingTime", 3000);
+// Number of times to retry the captive portal check if there is an error or timeout.
+pref("captivedetect.maxRetryCount", 5);
+```
+
+## Connectivity checking
+
+The connectivity checker might use some of the captive portal URLs.
+Refer [this](connectivity_checking.md) page for more details.
diff --git a/netwerk/docs/connectivity_checking.md b/netwerk/docs/connectivity_checking.md
new file mode 100644
index 0000000000..fede9c0401
--- /dev/null
+++ b/netwerk/docs/connectivity_checking.md
@@ -0,0 +1,14 @@
+# Connectivity Checking
+We use a mechanism similar to captive portal checking to verify if the browser has internet connectivity. The [NetworkConnectivityService](https://searchfox.org/mozilla-central/source/netwerk/base/NetworkConnectivityService.h) will periodically connect to the same URL we use for captive portal detection, but will restrict its preferences to either IPv4 or IPv6. Based on which responses succeed, we can infer if Firefox has IPv4 and/or IPv6 connectivity. We also perform DNS queries to check if the system has a IPv4/IPv6 capable DNS resolver.
+
+## Preferences
+
+```js
+
+pref("network.connectivity-service.enabled", true);
+pref("network.connectivity-service.DNSv4.domain", "example.org");
+pref("network.connectivity-service.DNSv6.domain", "example.org");
+pref("network.connectivity-service.IPv4.url", "http://detectportal.firefox.com/success.txt?ipv4");
+pref("network.connectivity-service.IPv6.url", "http://detectportal.firefox.com/success.txt?ipv6");
+
+```
diff --git a/netwerk/docs/dns/dns-over-https-trr.md b/netwerk/docs/dns/dns-over-https-trr.md
new file mode 100644
index 0000000000..cf025a1e60
--- /dev/null
+++ b/netwerk/docs/dns/dns-over-https-trr.md
@@ -0,0 +1,156 @@
+# DNS over HTTPS (Trusted Recursive Resolver)
+
+## Terminology
+
+**DNS-over-HTTPS (DoH)** allows DNS to be resolved with enhanced
+privacy, secure transfers and comparable performance. The protocol is
+described in [RFC 8484](https://tools.ietf.org/html/rfc8484) .
+
+**Trusted Recursive Resolver (TRR)** is the name of Firefox\'s
+implementation of the protocol and the
+[policy](https://wiki.mozilla.org/Security/DOH-resolver-policy) that
+ensures only privacy-respecting DoH providers are recommended by
+Firefox.
+
+On this page we will use DoH when referring to the protocol, and TRR
+when referring to the implementation.
+
+**Unencrypted DNS (Do53)** is the regular way most programs resolve DNS
+names. This is usually done by the operating system by sending an
+unencrypted packet to the DNS server that normally listens on port 53.
+
+## DoH Rollout
+
+**DoH Rollout** refers to the frontend code that decides whether TRR
+will be enabled automatically for users in the [rollout
+population](https://support.mozilla.org/kb/firefox-dns-over-https#w_about-the-us-rollout-of-dns-over-https).
+
+The functioning of this module is described
+[here](https://wiki.mozilla.org/Security/DNS_Over_HTTPS).
+
+The code lives in
+[browser/components/doh](https://searchfox.org/mozilla-central/source/browser/components/doh).
+
+## Implementation
+
+When enabled TRR may work in two modes, TRR-first (2) and TRR-only (3).
+These are controlled by the **network.trr.mode** or **doh-rollout.mode**
+prefs. The difference is that when a DoH request fails in TRR-first
+mode, we then fallback to **Do53**.
+
+For TRR-first mode, we have a strict-fallback setting which can be
+enabled by setting network.trr.strict\_native\_fallback to true. With
+this, while we will still completely skip TRR for certain requests (like
+captive portal detection, bootstrapping the TRR provider, etc.) we will
+only fall back after a TRR failure to **Do53** for three possible
+reasons:
+1. We detected, via Confirmation, that TRR is currently out of
+service on the network. This could mean the provider is down or blocked.
+2. The address successfully resolved via TRR could not be connected to.
+3. TRR result is NXDOMAIN.
+
+When a DNS resolution doesn't use TRR we will normally preserve that data in the form of a _TRRSkippedReason_. A detailed explanation of each one is available [here](trr-skip-reasons).
+
+In other cases, instead of falling back, we will trigger a fresh
+Confirmation (which will start us on a fresh connection to the provider)
+and retry the lookup with TRR again. We only retry once.
+
+DNS name resolutions are performed in _nsHostResolver::ResolveHost_. If a
+cached response for the request could not be found,
+_nsHostResolver::NameLookup_ will trigger either a DoH or a Do53 request.
+First it checks the effective TRR mode of the request is as requests
+could have a different mode from the global one. If the request may use
+TRR, then we dispatch a request in _nsHostResolver::TrrLookup_. Since we
+usually reolve both IPv4 and IPv6 names, a **TRRQuery** object is
+created to perform and combine both responses.
+
+Once done, _nsHostResolver::CompleteLookup_ is called. If the DoH server
+returned a valid response we use it, otherwise we report a failure in
+TRR-only mode, or try Do53 in TRR-first mode.
+
+**TRRService** controls the global state and settings of the feature.
+Each individual request is performed by the **TRR** class.
+
+Since HTTP channels in Firefox normally work on the main thread, TRR
+uses a special implementation called **TRRServiceChannel** to avoid
+congestion on the main thread.
+
+## Dynamic Blocklist
+
+In order to improve performance TRR service manages a dynamic blocklist
+for host names that can\'t be resolved with DoH but work with the native
+resolver. Blocklisted entries will not be retried over DoH for one
+minute (See _network.trr.temp\_blocklist\_duration\_sec_
+pref). When a domain is added to the blocklist, we also check if there
+is an NS record for its parent domain, in which case we add that to the
+blocklist. This feature is controlled by the
+_network.trr.temp\_blocklist_ pref.
+
+## TRR Confirmation
+
+TRR requests normally have a 1.5 second timeout. If for some reason we
+do not get a response in that time we fall back to Do53. To avoid this
+delay for all requests when the DoH server is not accessible, we perform
+a confirmation check. If the check fails, we conclude that the server is
+not usable and will use Do53 directly. The confirmation check is retried
+periodically to check if the TRR connection is functional again.
+
+The confirmation state has one of the following values:
+
+- CONFIRM\_OFF: TRR is turned off, so the service is not active.
+- CONFIRM\_TRING\_OK: TRR in on, but we are not sure yet if the
+ DoH server is accessible. We optimistically try to resolve via
+ DoH and fall back to Do53 after 1.5 seconds. While in this state
+ the TRRService will be performing NS record requests to the DoH
+ server as a connectivity check. Depending on a successful
+ response it will either transition to the CONFIRM\_OK or
+ CONFIRM\_FAILED state.
+- CONFIRM\_OK: TRR is on and we have confirmed that the DoH server
+ is behaving adequately. Will use TRR for all requests (and fall
+ back to Do53 in case of timeout, NXDOMAIN, etc).
+- CONFIRM\_FAILED: TRR is on, but the DoH server is not
+ accessible. Either we have no network connectivity, or the
+ server is down. We don\'t perform DoH requests in this state
+ because they are sure to fail.
+- CONFIRM\_TRYING\_FAILED: This is equivalent to CONFIRM\_FAILED,
+ but we periodically enter this state when rechecking if the DoH
+ server is accessible.
+- CONFIRM\_DISABLED: We are in this state if the browser is in
+ TRR-only mode, or if the confirmation was explicitly disabled
+ via pref.
+
+The state machine for the confirmation is defined in the
+_HandleConfirmationEvent_ method in _TRRService.cpp_
+
+If strict fallback mode is enabled, Confirmation will set a flag to
+refresh our connection to the provider.
+
+## Excluded Domains
+
+Some domains will never be resolved via TRR. This includes:
+
+- domains listed in the **network.trr.builtin-excluded-domains** pref
+(normally domains that are equal or end in *localhost* or *local*)
+- domains listed in the **network.trr.excluded-domains** pref (chosen by the user)
+- domains that are subdomains of the network\'s DNS suffix
+(for example if the network has the **lan** suffix, domains such as **computer.lan** will not use TRR)
+- requests made by Firefox to check for the existence of a captive-portal
+- requests made by Firefox to check the network\'s IPv6 capabilities
+- domains listed in _/etc/hosts_
+
+## Steering
+
+
+A small set of TRR providers are only available on certain networks.
+Detection is performed in DoHHeuristics.jsm followed by a call to
+_TRRService::SetDetectedURI_. This causes Firefox to use the
+network specific TRR provider until a network change occurs.
+
+## User Choice
+
+The TRR feature is designed to prioritize user choice before user agent
+decisions. That means the user may explicitly disable TRR by setting
+**network.trr.mode** to **5** (TRR-disabled), and that
+_doh-rollout_ will not overwrite user settings. Changes to
+the TRR URL or TRR mode by the user will disable heuristics use the user
+configured settings.
diff --git a/netwerk/docs/dns/trr-skip-reasons.md b/netwerk/docs/dns/trr-skip-reasons.md
new file mode 100644
index 0000000000..dbbb4e3336
--- /dev/null
+++ b/netwerk/docs/dns/trr-skip-reasons.md
@@ -0,0 +1,324 @@
+# TRRSkippedReasons
+
+These values are defined in [TRRSkippedReason.h](https://searchfox.org/mozilla-central/source/netwerk/dns/nsITRRSkipReason.idl) and are recorded on _nsHostRecord_ for each resolution.
+We normally use them for telemetry or to determine the cause of a TRR failure.
+
+
+## TRR_UNSET
+
+Value: 0
+
+This reason is set on _nsHostRecord_ before we attempt to resolve the domain.
+Normally we should not report this value into telemetry - if we do that means there's a bug in the code.
+
+
+## TRR_OK
+
+Value: 1
+
+This reason is set when we got a positive TRR result. That means we used TRR for the DNS resolution, the HTTPS request got a 200 response, the response was properly decoded as a DNS packet and that packet contained relevant answers.
+
+
+## TRR_NO_GSERVICE
+
+Value: 2
+
+This reason is only set if there is no TRR service instance when trying to compute the TRR mode for a request. It indicates a bug in the implementation.
+
+
+## TRR_PARENTAL_CONTROL
+
+Value: 3
+
+This reason is set when we have detected system level parental controls are enabled. In this case we will not be using TRR for any requests.
+
+
+## TRR_OFF_EXPLICIT
+
+Value: 4
+
+This reason is set when DNS over HTTPS has been explicitly disabled by the user (by setting _network.trr.mode_ to _5_). In this case we will not be using TRR for any requests.
+
+
+## TRR_REQ_MODE_DISABLED
+
+Value: 5
+
+The request had the _nsIRequest::TRRMode_ set to _TRR\_DISABLED\_MODE_. That is usually the case for request that should not use TRR, such as the TRRServiceChannel, captive portal and connectivity checks, DoHHeuristics checks, requests originating from PAC scripts, etc.
+
+
+## TRR_MODE_NOT_ENABLED
+
+Value: 6
+
+This reason is set when the TRRService is not enabled. The only way we would end up reporting this to telemetry would be if the TRRService was enabled when the request was dispatched, but by the time it was processed the TRRService was disabled.
+
+
+## TRR_FAILED
+
+Value: 7
+
+The TRR request failed for an unknown reason.
+
+
+## TRR_MODE_UNHANDLED_DEFAULT
+
+Value: 8
+
+This reason is no longer used. This value may be recycled to mean something else in the future.
+
+
+## TRR_MODE_UNHANDLED_DISABLED
+
+Value: 9
+
+This reason is no longer used. This value may be recycled to mean something else in the future.
+
+
+## TRR_DISABLED_FLAG
+
+Value: 10
+
+This reason is used when retrying failed connections, sync DNS resolves on the main thread, or requests coming from webextensions that choose to skip TRR.
+
+
+## TRR_TIMEOUT
+
+Value: 11
+
+The TRR request timed out.
+
+## TRR_CHANNEL_DNS_FAIL
+
+Value: 12
+
+This reason is set when we fail to resolve the name of the DNS over HTTPS server.
+
+
+## TRR_IS_OFFLINE
+
+Value: 13
+
+This reason is recorded when the TRR request fails and the browser is offline (no active interfaces).
+
+
+## TRR_NOT_CONFIRMED
+
+Value: 14
+
+This reason is recorded when the TRR Service is not yet confirmed to work. Confirmation is only enabled when _Do53_ fallback is enabled.
+
+
+## TRR_DID_NOT_MAKE_QUERY
+
+Value: 15
+
+This reason is sent when _TrrLookup_ exited without doing a TRR query. It may be set during shutdown, or may indicate an implementation bug.
+
+
+## TRR_UNKNOWN_CHANNEL_FAILURE
+
+Value: 16
+
+The TRR request failed with an unknown channel failure reason.
+
+
+## TRR_HOST_BLOCKED_TEMPORARY
+
+Value: 17
+
+The reason is recorded when the host is temporarily blocked. This happens when a previous attempt to resolve it with TRR failed, but fallback to _Do53_ succeeded.
+
+
+## TRR_SEND_FAILED
+
+Value: 18
+
+The call to TRR::SendHTTPRequest failed.
+
+
+## TRR_NET_RESET
+
+Value: 19
+
+The request failed because the connection to the TRR server was reset.
+
+
+## TRR_NET_TIMEOUT
+
+Value: 20
+
+The request failed because the connection to the TRR server timed out.
+
+
+## TRR_NET_REFUSED
+
+Value: 21
+
+The request failed because the connection to the TRR server was refused.
+
+
+## TRR_NET_INTERRUPT
+
+Value: 22
+
+The request failed because the connection to the TRR server was interrupted.
+
+
+## TRR_NET_INADEQ_SEQURITY
+
+Value: 23
+
+The request failed because the connection to the TRR server used an invalid TLS configuration.
+
+
+## TRR_NO_ANSWERS
+
+Value: 24
+
+The TRR request succeeded but the encoded DNS packet contained no answers.
+
+
+## TRR_DECODE_FAILED
+
+Value: 25
+
+The TRR request succeeded but decoding the DNS packet failed.
+
+
+## TRR_EXCLUDED
+
+Value: 26
+
+This reason is set when the domain being resolved is excluded from TRR, either via the _network.trr.excluded-domains_ pref or because it was covered by the DNS Suffix of the user's network.
+
+
+## TRR_SERVER_RESPONSE_ERR
+
+Value: 27
+
+The server responded with non-200 code.
+
+
+## TRR_RCODE_FAIL
+
+Value: 28
+
+The decoded DNS packet contains an rcode that is different from NOERROR.
+
+
+## TRR_NO_CONNECTIVITY
+
+Value: 29
+
+This reason is set when the browser has no connectivity.
+
+
+## TRR_NXDOMAIN
+
+Value: 30
+
+This reason is set when the DNS response contains NXDOMAIN rcode (0x03).
+
+
+## TRR_REQ_CANCELLED
+
+Value: 31
+
+This reason is set when the request was cancelled prior to completion.
+
+## ODOH_KEY_NOT_USABLE
+
+Value: 32
+
+This reason is set when we don't have a valid ODoHConfig to use.
+
+## ODOH_UPDATE_KEY_FAILED
+
+Value: 33
+
+This reason is set when we failed to update the ODoHConfigs.
+
+## ODOH_KEY_NOT_AVAILABLE
+
+Value: 34
+
+This reason is set when ODoH requests timeout because of no key.
+
+## ODOH_ENCRYPTION_FAILED
+
+Value: 35
+
+This reason is set when we failed to encrypt DNS packets.
+
+## ODOH_DECRYPTION_FAILED
+
+Value: 36
+
+This reason is set when we failed to decrypt DNS packets.
+
+## TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH
+
+Value: 37
+
+This reason is set when the google safesearch heuristic was tripped.
+
+## TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH
+
+Value: 38
+
+This reason is set when the youtube safesearch heuristic was tripped.
+
+## TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY
+
+Value: 39
+
+This reason is set when the zscaler canary heuristic was tripped.
+
+## TRR_HEURISTIC_TRIPPED_CANARY
+
+Value: 40
+
+This reason is set when the global canary heuristic was tripped.
+
+## TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS
+
+Value: 41
+
+This reason is set when the modified roots (enterprise_roots cert pref) heuristic was tripped.
+
+## TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS
+
+Value: 42
+
+This reason is set when parental controls are detected.
+
+## TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS
+
+Value: 43
+
+This reason is set when the third party roots heuristic was tripped.
+
+## TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY
+
+Value: 44
+
+This reason is set when enterprise policy heuristic was tripped.
+
+## TRR_HEURISTIC_TRIPPED_VPN
+
+Value: 45
+
+This reason is set when the heuristic was tripped by a vpn being detected.
+
+## TRR_HEURISTIC_TRIPPED_PROXY
+
+Value: 46
+
+This reason is set when the heuristic was tripped by a proxy being detected.
+
+## TRR_HEURISTIC_TRIPPED_NRPT
+
+Value: 47
+
+This reason is set when the heuristic was tripped by a NRPT being detected.
diff --git a/netwerk/docs/early_hints.md b/netwerk/docs/early_hints.md
new file mode 100644
index 0000000000..6390365072
--- /dev/null
+++ b/netwerk/docs/early_hints.md
@@ -0,0 +1,157 @@
+# Early Hints
+
+[Early Hints](https://html.spec.whatwg.org/multipage/semantics.html#early-hints) is an informational HTTP status code allowing server to send headers likely to appear in the final response before sending the final response.
+This is used to send [Link headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) to start `preconnect`s and `preload`s.
+
+This document is about the implementation details of Early Hints in Firefox.
+We focus on the `preload` feature, as it is the main feature interacting with classes.
+For Early Hint `preconnect` the Early Hints specific code is rather small and only touches the code path on [`103 Early Hints` responses](#early-hints-response-on-main-document-load).
+
+```{mermaid}
+sequenceDiagram
+ participant F as Firefox
+ participant S as Server
+ autonumber
+ F->>+S: Main document Request: GET /
+ S-->>F: 103 Early Hints Response
+ note over F: Firefox starts<br/>hinted requests
+ note over S: Server Think Time
+ S->>-F: 200 OK final response
+```
+
+Early Hints benefits originate from leveraging Server Think Time.
+The duration between response (2) and (3) arriving is the theoretical maximal benefit Early Hints can have.
+The server think time can originate from creating dynamic content by interacting with databases or more commonly when proxying the request to a different server.
+
+```{contents}
+:local:
+:depth: 1
+```
+
+## `103 Early Hints` Response on Main Document Load
+
+On `103 Early Hints` response the `nsHttpChannel` handling main document load passes the link header and a few more from the `103 Early Hints` response to the `EarlyHintsService`
+
+When receiving a `103 Early Hints` response, the `nsHttpChannel` forwards the `Link` headers in the `103 Early Hints` response to the `EarlyHintsService`
+When the `DocumentLoadListener` receives a cross-origin redirect, it cancels all preloads in progress.
+
+```{note}
+Only the first `103 Early Hints` response is processed.
+The remaining `103 Early Hints` responses are ignored, even after same-origin redirects.
+When we receive cross origin redirects, all ongoing Early Hint preload requests are cancelled.
+```
+
+```{mermaid}
+graph TD
+ MainChannel[nsHttpChannel]
+ EHS[EarlyHintsService]
+ EHC[EarlyHintPreconnect]
+ EHP[EarlyHintPreloader]
+ PreloadChannel[nsIChannel]
+ PCL[ParentChannelListener]
+
+ MainChannel
+ -- "nsIEarlyHintsObserver::EarlyHint(LinkHeader, Csp, RefererPolicy)<br/>via DocumentLoadListener"
+ --> EHS
+ EHS
+ -- "rel=preconnect"
+ --> EHC
+ EHS -->|"rel=preload<br/>via OngoingEarlyHints"| EHP
+ EHP -->|"CSP checks then AsyncOpen"| PreloadChannel
+ PreloadChannel -->|mListener| PCL
+ PCL -->|mNextListener| EHP
+```
+
+## Main document Final Response
+
+On the final response the `DocumentLoadListener` retrieves the list of link headers from the `EarlyHintsService`.
+As a side effect, the `EarlyHintPreloader` also starts a 10s timer to cancel itself if the content process doesn't connect to the `EarlyHintPreloader`.
+The timeout shouldn't occur in normal circumstances, because the content process connects to that `EarlyHintPreloader` immediately.
+The timeout currently only occurs when:
+
+- the main response has different CSP requirements disallowing the load ([Bug 1815884](https://bugzilla.mozilla.org/show_bug.cgi?id=1815884)),
+- the main response has COEP headers disallowing the load ([Bug 1806403](https://bugzilla.mozilla.org/show_bug.cgi?id=1806403)),
+- the user reloads a website and the image/css is already in the image/css-cache ([Bug 1815884](https://bugzilla.mozilla.org/show_bug.cgi?id=1815884)),
+- the tab gets closed before the connect happens or possibly other corner cases.
+
+```{mermaid}
+graph TD
+ DLL[DocumentLoadListener]
+ EHP[EarlyHintPreloader]
+ PS[PreloadService]
+ EHR[EarlyHintsRegistrar]
+ Timer[nsITimer]
+
+ DLL
+ -- "(1)<br/>GetAllPreloads(newCspRequirements)<br/> via EarlyHintsService and OngoingEarlyHints"
+ --> EHP
+ EHP -->|"Start timer to cancel on<br/>ParentConnectTimeout<br/>after 10s"| Timer
+ EHP -->|"Register(earlyHintPreloaderId)"| EHR
+ Timer -->|"RefPtr"| EHP
+ EHR -->|"RefPtr"| EHP
+ DLL
+ -- "(2)<br/>Send to content process via IPC<br/>List of Links+earlyHintPreloaderId"
+ --> PS
+```
+
+## Preload request from Content process
+
+The Child process parses Link headers from the `103 Early Hints` response first and then from the main document response.
+Preloads from the Link headers of the `103 Early Hints` response have an `earlyHintPreloadId` assigned to them.
+The Preloader sets this `earlyHintPreloaderId` on the channel doing the preload before calling `AsyncOpen`.
+The `HttpChannelParent` looks for the `earlyHintPreloaderId` in `AsyncOpen` and connects to the `EarlyHintPreloader` via the `EarlyHintRegistrar` instead of doing a network request.
+
+```{mermaid}
+graph TD
+ PS[PreloadService]
+ Preloader["FetchPreloader<br/>FontPreloader<br/>imgLoader<br/>ScriptLoader<br/>StyleLoader"]
+ Parent["HttpChannelParent"]
+ EHR["EarlyHintRegistrar"]
+ EHP["EarlyHintPreloader"]
+
+ PS -- "PreloadLinkHeader" --> Preloader
+ Preloader -- "NewChannel<br/>SetEarlyHintPreloaderId<br/>AsyncOpen" --> Parent
+ Parent -- "EarlyHintRegistrar::OnParentReady(this, earlyHintPreloaderId)" --> EHR
+ EHR -- "OnParentConnect" --> EHP
+```
+
+## Early Hint Preload request
+
+The `EarlyHintPreloader` follows HTTP 3xx redirects and always sets the request header `X-Moz: early hint`.
+
+## Early Hint Preload response
+
+When the `EarlyHintPreloader` received the `OnStartRequest` it forwards all `nsIRequestObserver` functions to the `HttpChannelParent` as soon as it knows which `HttpChannelParent` to forward the `nsIRequestObserver` functions to.
+
+```{mermaid}
+graph TD
+ OPC["EHP::OnParentConnect"]
+ OSR["EHP::OnStartRequest"]
+ Invoke["Invoke StreamListenerFunctions"]
+ End(("&shy;"))
+
+ OPC -- "CancelTimer" --> Invoke
+ OSR -- "Suspend Channel if called<br/>before OnParentReady" --> Invoke
+ Invoke -- "Resume Channel if suspended<br/>Forward OSR+ODA+OSR<br/>Set Listener of ParentChanelListener to HttpChannelParent" --> End
+```
+
+## Final setup
+
+In the end all the remaining `OnDataAvailable` and `OnStopRequest` calls are passed down this call chain from `nsIChannel` to the preloader.
+
+```{mermaid}
+graph TD
+ Channel[nsIChannel]
+ PCL[ParentChannelListener]
+ HCP[HttpChanelParent]
+ HCC[HttpChannelChild]
+ Preloader[FetchPreloader/imgLoader/...]
+
+ Channel -- "mListener" --> PCL
+ PCL -- "mNextListener" --> HCP
+ HCP -- "mChannel" --> Channel
+ HCP -- "..." --> HCC
+ HCC -- "..." --> HCP
+ HCC -- "mListener" --> Preloader
+ Preloader -- "mChannel" --> HCC
+```
diff --git a/netwerk/docs/http/http3.md b/netwerk/docs/http/http3.md
new file mode 100644
index 0000000000..0f5e241569
--- /dev/null
+++ b/netwerk/docs/http/http3.md
@@ -0,0 +1,154 @@
+# Http3Session and Streams
+
+The HTTP/3 and QUIC protocol are implemented in the neqo library. Http3Session, Http3Steam, and Http3WebTransportStream are added to integrate the library into the existing necko code.
+
+The following classes are necessary:
+- HttpConnectionUDP - this is the object that is registered in nsHttpConnectionMgr and it is also used as an async listener to socket events (it implements nsIUDPSocketSyncListener)
+- nsUDPSocket - represent a UDP socket and implements nsASocketHandler. nsSocketTransportService manages UDP and TCP sockets and calls the corresponding nsASocketHandler when the socket encounters an error or has data to be read, etc.
+- NeqoHttp3Conn is a c++ object that maps to the rust object Http3Client.
+- Http3Session manages NeqoHttp3Conn/Http3Client and provides bridge between the rust implementation and necko legacy code, i.e. HttpConnectionUDP and nsHttpTransaction.
+- Http3Streams are used to map reading and writing from/into a nsHttpTransaction onto the NeqoHttp3Conn/Http3Client API (e.g. nsHttpTransaction::OnWriteSegment will call Http3Client::read_data). NeqoHttp3Conn is only accessed by Http3Sesson and NeqoHttp3Conn functions are exposed through Http3Session where needed.
+
+```{mermaid}
+graph TD
+ A[HttpConnectionMgr] --> B[HttpConnectionUDP]
+ B --> C[nsUDPSocket]
+ C --> B
+ D[nsSocketTransportService] --> C
+ B --> E[NeqoHttp3Conn]
+ B --> F[Http3Stream]
+ F -->|row| B
+ F --> G[nsHttpTransport]
+ G --> B
+ B --> G
+```
+
+## Interactions with Sockets and Driving Neqo
+
+As described in [this docs](https://github.com/mozilla/neqo/blob/main/neqo-http3/src/lib.rs), neqo does not create a socket, it produces, i.e. encodes, data that should be sent as a payload in a UDP packet and consumes data received on the UDP socket. Therefore the necko is responsible for creating a socket and reading and writing data from/into the socket. Necko uses nsUDPSocket and nsSocketTransportService for this.
+The UDP socket is constantly polled for reading. It is not polled for writing, we let QUIC control to not overload the network path and buffers.
+
+When the UDP socket has an available packet, nsSocketTransportService will return from the polling function and call nsUDPSocket::OnSocketReady, which calls HttpConnectionUDP::OnPacketReceived, HttpConnectionUDP::RecvData and further Http3Session::RecvData. For writing data
+HttpConnectionUDP::SendData is called which calls Http3Session::SendData.
+
+Neqo needs an external timer. The timer is managed by Http3Session. When the timer expires HttpConnectionUDP::OnQuicTimeoutExpired is executed that calls Http3Session::ProcessOutputAndEvents.
+
+HttpConnectionUDP::RecvData, HttpConnectionUDP::SendData or HttpConnectionUDP::OnQuicTimeoutExpired must be on the stack when we interact with neqo. The reason is that they are responsible for proper clean-up in case of an error. For example, if there is a slow reader that is ready to read, it will call Http3Session::TransactionHasDataToRecv to be registered in a list and HttpConnectionUDP::ForceRecv will be called that will call the same function chain as in the case a new packet is received, i.e. HttpConnectionUDP::RecvData and further Http3Session::RecvData. The other example is when a new HTTP transaction is added to the session, the transaction needs to send data. The transaction will be registered in a list and HttpConnectionUDP::ResumeSend will be called which further calls HttpConnectionUDP::SendData.
+
+Http3Session holds a reference to a ConnectionHandler object which is a wrapper object around HttpConnectionUDP. The destructor of ConnectionHandler calls nsHttpHandler::ReclaimConnection which is responsible for removing the connection from nsHttpConnectionMgr.
+HttpConnectionUDP::RecvData, HttpConnectionUDP::SendData or HttpConnectionUDP::OnQuicTimeoutExpired call HttpConnectionUDP::CloseTransaction which will cause Http3Session to remove the reference to the ConnectionHandler object. The ConnectionHandler object will be destroyed and nsHttpHandler::ReclaimConnection will be called.
+This behavior is historical and it is also used for HTTP/2 and older versions. In the case of the older versions, nsHttpHandler::ReclaimConnection may actually reuse a connection instead of removing it from nsHttpConnectionMgr.
+
+Three main neqo functions responsible for driving neqo are process_input, process_output, and next_event. They are called by:
+- Http3Session::ProcessInput,
+- Http3Session::ProcesOutput and,
+- Http3Session::ProcessEvents.
+
+**ProcessInput**
+In this function we take data from the UDP socket and call NeqoHttp3Conn::ProcessInput that maps to Http3Client::process_input. The packets are read from the socket until the socket buffer is empty.
+
+**ProcessEvents**
+This function process all available neqo events. It returns earlier only in case of a fatal error.
+It calls NeqoHttp3Conn::GetEvent which maps to Http3Client::next_event.
+The events and their handling will be explained below.
+
+**ProcessOutput**
+The function is called when necko has performed some action on neqo, e.g. new HTTP transaction is added, certificate verification is done, etc., or when the timer expires. In both cases, necko wants to check if neqo has data to send or change its state. This function calls NeqoHttp3Conn::ProcessOutput that maps to Http3Client::process_output. NeqoHttp3Conn::ProcessOutput may return a packet that is sent on the socket or a callback timeout. In the Http3Session::ProcessOutput function, NeqoHttp3Conn::ProcessOutput is called repeatedly and packets are sent until a callback timer is returned or a fatal error happens.
+
+**Http3Session::RecvData** performs the following steps:
+- ProcessSlowConsumers - explained below.
+- ProcessInput - process new packets.
+- ProcessEvents - look if there are new events
+- ProcessOutput - look if we have new packets to send after packets arrive(e.g. sending ack) or due to event processing (e.g. a stream has been canceled).
+
+**Http3Session::SendData** performed the following steps:
+- Process (HTTP and WebTransport) streams that have data to write.
+- ProcessOutput - look if there are new packets to be sent after streams have supplied data to neqo.
+
+
+**Http3Session::ProcessOutputAndEvents** performed the following steps:
+- ProcessOutput - after a timeout most probably neqo will have data to retransmit or it will send a ping
+- ProcessEvents - look if the state of the connection has changed, i.e. the connection timed out
+
+
+## HTTP and WebTransport Streams Reading Data
+
+The following diagram shows how data are read from an HTTP stream. The diagram for a WebTransport stream will be added later.
+
+```{mermaid}
+flowchart TD
+ A1[nsUDPSocket::OnSocketReady] --> |HttpConnectionUDP::OnPacketReceived| C[HttpConnectionUDP]
+ A[HttpConnectionUDP::ResumeRecv calls] --> C
+ B[HttpConnectionUDPForceIO] --> |HttpConnectionUDP::RecvData| C
+ C -->|1. Http3Session::RecvData| D[Http3Session]
+ D --> |2. Http3Stream::WriteSegments|E[Http3Stream]
+ E --> |3. nsHttpTransaction::WriteSegmentsAgain| F[nsHttpTransaction]
+ F --> |4. nsPipeOutputStream::WriteSegments| G["nsPipeOutputStream"]
+ G --> |5. nsHttpTransaction::WritePiipeSegnemt| F
+ F --> |6. Http3Stream::OnWriteSegment| E
+ E --> |"7. Return response headers or call Http3Session::ReadResponseData"|D
+ D --> |8. NeqoHttp3Conn::ReadResponseDataReadResponseData| H[NeqoHHttp3Conn]
+
+```
+
+When there is a new packet carrying a stream data arriving on a QUIC connection nsUDPSocket::OnSocketReady will be called which will call Http3Session::RecvData. Http3Session::RecvData and ProcessInput will read the new packet from the socket and give it to neqo for processing. In the next step, ProcessEvent will be called which will have a DataReadable event and Http3Stream::WriteSegments will be called.
+Http3Stream::WriteSegments calls nsHttpTransaction::WriteSegmentsAgain repeatedly until all data are read from the QUIC stream or until the pipe cannot accept more data. The latter can happen when listeners of an HTTP transaction or WebTransport stream are slow and are not able to read all data available on an HTTP3/WebTransport stream fast enough.
+
+When the pipe cannot accept more data nsHttpTransaction will call nsPipeOutputStream::AsyncWait and wait for the nsHttpTransaction::OnOutputStreamReady callback. When nsHttpTransaction::OnOutputStreamReady is called, Http3Stream/Session::TransactionHasDataToRecv is is executed with the following actions:
+- the corresponding stream to a list(mSlowConsumersReadyForRead) and
+- nsHttpConnection::ResumeRecv is called (i.e. it forces the same code path as when a socket has data to receive so that errors can be properly handled as explained previously).
+
+These streams will be processed in ProcessSlowConsumers which is called by Http3Session::RecvData.
+
+## HTTP and WebTransport Streams Writing Data
+
+The following diagram shows how data are sent from an HTTP stream. The diagram for a WebTransport stream will be added later.
+
+```{mermaid}
+flowchart TD
+ A[HttpConnectionUDP::ResumeSend calls] --> C[HttpConnectionUDP]
+ B[HttpConnectionUDPForceIO] --> |HttpConnectionUDP::SendData| C
+ C -->|1. Http3Session::SendData| D[Http3Session]
+ D --> |2. Http3Stream::ReadSegments|E[Http3Stream]
+ E --> |3. nsHttpTransaction::ReadSegmentsAgain| F[nsHttpTransaction]
+ F --> |4. nsPipeInputStream::ReadSegments| G["nsPipeInputStream(Request stream)"]
+ G --> |5. nsHttpTransaction::ReadRequestSegment| F
+ F --> |6. Http3Stream::OnReadSegment| E
+ E --> |7. Http3Session::TryActivating/SendRequestBody|D
+ D --> |8. NeqoHttp3Conn::Fetch/SendRequestBody| H[NeqoHHttp3Conn]
+```
+
+When a nsHttpTransaction has been newly added to a transaction or when nsHttpTransaction has more data to write Http3Session::StreamReadyToWrite is called (in the latter case through Http3Session::TransactionHasDataToWrite) which performs the following actions:
+- add the corresponding stream to a list(mReadyForWrite) and
+- call HttpConnectionUDP::ResumeSend
+
+The Http3Session::SendData function iterates through mReadyForWrite and calls Http3Stream::ReadSegments for each stream.
+
+## Neqo Events
+
+For **HeaderReady** and **DataReadable** the Http3Stream::WriteSegments function of the corresponding stream is called. The code path shown in the flowchart above will call the nssHttpTransaction served by the stream to take headers and data.
+
+**DataWritable** means that a stream could not accept more data earlier and that flow control now allows sending more data. Http3Sesson will mark the stream as writable(by calling Http3Session::StreamReadyToWrite) to verify if a stream wants to write more data.
+
+**Reset** and **StopSending** events will be propagated to the stream and the stream will be closed.
+
+**RequestsCreatable** events are posted when a QUIC connection could not accept new streams due to the flow control in the past and the stream flow control is increased and the streams are creatable again. Http3Session::ProcessPendingProcessPending will trigger the activation of the queued streams.
+
+**AuthenticationNeeded** and **EchFallbackAuthenticationNeeded** are posted when a certificate verification is needed.
+
+
+**ZeroRttRejected** is posted when zero RTT data was rejected.
+
+**ResumptionToken** is posted when a new resumption token is available.
+
+**ConnectionConnected**, **GoawayReceived**, **ConnectionClosing** and **ConnectionClosed** expose change in the connection state. Difference between **ConnectionClosing** and **ConnectionClosed** that after **ConnectionClosed** the connection can be immediately closed and after **ConnectionClosing** we will keep the connection object for a short time until **ConnectionClosed** event is received. During this period we will retransmit the closing frame if they are lost.
+
+### WebTransport Events
+
+**Negotiated** - WebTransport is negotiated only after the HTTP/3 settings frame has been received from the server. At that point **Negotiated** event is posted to inform the application.
+
+The **Session** event is posted when a WebTransport session is successfully negotiated.
+
+The **SessionClosed** event is posted when a connection is closed gracefully or abruptly.
+
+The **NewStream** is posted when a new stream has been opened by the peer.
diff --git a/netwerk/docs/http/lifecycle.rst b/netwerk/docs/http/lifecycle.rst
new file mode 100644
index 0000000000..09a326d4d6
--- /dev/null
+++ b/netwerk/docs/http/lifecycle.rst
@@ -0,0 +1,220 @@
+The Lifecycle of a HTTP Request
+===============================
+
+
+HTTP requests in Firefox go through several steps. Each piece of the request message and response message become available at certain points. Extracting that information is a challenge, though.
+
+What is Available When
+----------------------
+
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+| Data | When it's available | Sample JS code | Interfaces | Test code |
++=======================+===================================================+=======================================+========================+===============================+
+| HTTP request method | *http-on-modify-request* observer notification | channel.requestMethod | nsIHttpChannel_ | |
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+| HTTP request URI | *http-on-modify-request* observer notification | channel.URI | nsIChannel_ | |
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+| HTTP request headers | *http-on-modify-request* observer notification | channel.visitRequestHeaders(visitor) | nsIHttpChannel_ | |
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+| HTTP request body | *http-on-modify-request* observer notification | channel.uploadStream | nsIUploadChannel_ | |
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+|| HTTP response status || *http-on-examine-response* observer notification || channel.responseStatus || nsIHttpChannel_ || test_basic_functionality.js_ |
+|| || || channel.responseStatusText || || |
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+| HTTP response headers | *http-on-examine-response* observer notification | channel.visitResponseHeaders(visitor) | nsIHttpChannel_ | |
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+|| HTTP response body || *onStopRequest* via stream listener tee || See below || nsITraceableChannel_ || test_traceable_channel.js_ |
+|| || || || nsIStreamListenerTee_ || |
+|| || || || nsIPipe_ || |
++-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
+
+The Request: http-on-modify-request
+-----------------------------------
+
+Firefox fires a "http-on-modify-request" observer notification before sending the HTTP request, and this blocks the sending of the request until all observers exit. This is generally the point at which you can modify the HTTP request headers (hence the name).
+
+Attaching a listener for a request is pretty simple::
+
+ const obs = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe: function(channel, topic, data) {
+ if (!(channel instanceof Ci.nsIHttpChannel))
+ return;
+
+ // process the channel's data
+ }
+ }
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+
+See nsIObserverService_ for the details.
+
+The request method and URI are immediately available at this time. Request headers are trivially easy to get::
+
+ /**
+ * HTTP header visitor.
+ */
+ class HeaderVisitor {
+ #targetObject;
+
+ constructor(targetObject) {
+ this.#targetObject = targetObject;
+ }
+
+ // nsIHttpHeaderVisitor
+ visitHeader(header, value) {
+ this.#targetObject[header] = value;
+ }
+
+ QueryInterface = ChromeUtils.generateQI(["nsIHttpHeaderVisitor"]);
+ }
+
+ // ...
+ const requestHeaders = {};
+ const visitor = new HeaderVisitor(requestHeaders);
+ channel.visitRequestHeaders(visitor);
+
+This is also the time to set request headers, if you need to. The method for that on the nsIHttpChannel_ interface is `channel.setRequestHeader(header, value);`
+
+Most HTTP requests don't have a body, as they are GET requests. POST requests often have them, though. As the nsIUploadChannel_ documentation indicates, the body of most HTTP requests is available via a seekable stream (nsISeekableStream_). So you can simply capture the body stream and its current position, to revisit it later. network-helper.js_ has code to read the request body.
+
+The Response: http-on-examine-response
+--------------------------------------
+
+Firefox fires a "http-on-examine-response" observer notification after parsing the HTTP response status and headers, but **before** reading the response body. Attaching a listener for this phase is also very easy::
+
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+
+If you use the same observer for "http-on-modify-request" and "http-on-examine-response", make sure you check the topic argument before interacting with the channel.
+
+The response status is available via the *responseStatus* and *responseStatusText* properties. The response headers are available via the *visitResponseHeaders* method, and requires the same interface.
+
+The Response body: onStopRequest, stream listener tee
+-----------------------------------------------------
+
+During the "http-on-examine-response" notification, the response body is *not* available. You can, however, use a stream listener tee to *copy* the stream so that the original stream data goes on, and you have a separate input stream you can read from with the same data.
+
+Here's some sample code to illustrate what you need::
+
+ const Pipe = Components.Constructor(
+ "@mozilla.org/pipe;1",
+ "nsIPipe",
+ "init"
+ );
+ const StreamListenerTee = Components.Constructor(
+ "@mozilla.org/network/stream-listener-tee;1",
+ "nsIStreamListenerTee"
+ );
+ const ScriptableStream = Components.Constructor(
+ "@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init"
+ );
+
+ const obs = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver", "nsIRequestObserver"]),
+
+ /** @typedef {WeakMap<nsIHttpChannel, nsIPipe>} */
+ requestToTeePipe: new WeakMap,
+
+ // nsIObserver
+ observe: function(channel, topic, data) {
+ if (!(channel instanceof Ci.nsIHttpChannel))
+ return;
+
+ /* Create input and output streams to take the new data.
+ The 0xffffffff argument is the segment count.
+ It has to be this high because you don't know how much data is coming in the response body.
+
+ As for why these are blocking streams: I believe this is because there's no actual need to make them non-blocking.
+ The stream processing happens during onStopRequest(), so we have all the data then and the operation can be synchronous.
+ But I could be very wrong on this.
+ */
+ const pipe = new Pipe(false, false, 0, 0xffffffff);
+
+ // Install the stream listener tee to intercept the HTTP body.
+ const tee = new StreamListenerTee;
+ const originalListener = channel.setNewListener(tee);
+ tee.init(originalListener, pipe.outputStream, this);
+
+ this.requestToTeePipe.set(channel, pipe);
+ }
+
+ // nsIRequestObserver
+ onStartRequest: function() {
+ // do nothing
+ }
+
+ // nsIRequestObserver
+ onStopRequest: function(channel, statusCode) {
+ const pipe = this.requestToTeePipe.get(channel);
+
+ // No more data coming in anyway.
+ pipe.outputStream.close();
+ this.requestToTeePipe.delete(channel);
+
+ let length = 0;
+ try {
+ length = pipe.inputStream.available();
+ }
+ catch (e) {
+ if (e.result === Components.results.NS_BASE_STREAM_CLOSED)
+ throw e;
+ }
+
+ let responseBody = "";
+ if (length) {
+ // C++ code doesn't need the scriptable input stream.
+ const sin = new ScriptableStream(pipe.inputStream);
+ responseBody = sin.read(length);
+ sin.close();
+ }
+
+ void(responseBody); // do something with the body
+ }
+ }
+
+test_traceable_channel.js_ does essentially this.
+
+Character Encodings and Compression
+-----------------------------------
+
+Canceling Requests
+------------------
+
+HTTP Activity Distributor Notes
+-------------------------------
+
+URIContentLoader Notes
+----------------------
+
+Order of Operations
+-------------------
+
+1. The HTTP channel is constructed.
+2. The "http-on-modify-request" observer service notification fires.
+3. If the request has been canceled, exit at this step.
+4. The HTTP channel's request is submitted to the server. Time passes.
+5. The HTTP channel's response comes in from the server.
+6. The HTTP channel parses the response status and headers.
+7. The "http-on-examine-response" observer service notification fires.
+
+Useful Code Samples and References
+----------------------------------
+
+- nsIHttpProtocolHandler_ defines a lot of observer topics, and has a lot of details.
+
+.. _nsIHttpChannel: https://searchfox.org/mozilla-central/source/netwerk/protocol/http/nsIHttpChannel.idl
+.. _nsIChannel: https://searchfox.org/mozilla-central/source/netwerk/base/nsIChannel.idl
+.. _nsIUploadChannel: https://searchfox.org/mozilla-central/source/netwerk/base/nsIUploadChannel.idl
+.. _nsITraceableChannel: https://searchfox.org/mozilla-central/source/netwerk/base/nsITraceableChannel.idl
+.. _nsISeekableStream: https://searchfox.org/mozilla-central/source/xpcom/io/nsISeekableStream.idl
+.. _nsIObserverService: https://searchfox.org/mozilla-central/source/xpcom/ds/nsIObserverService.idl
+.. _nsIHttpProtocolHandler: https://searchfox.org/mozilla-central/source/netwerk/protocol/http/nsIHttpProtocolHandler.idl
+.. _nsIStreamListenerTee: https://searchfox.org/mozilla-central/source/netwerk/base/nsIStreamListenerTee.idl
+.. _nsIPipe: https://searchfox.org/mozilla-central/source/xpcom/io/nsIPipe.idl
+
+.. _test_basic_functionality.js: https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/test/test_basic_functionality.js
+.. _test_traceable_channel.js: https://searchfox.org/mozilla-central/source/netwerk/test/unit/test_traceable_channel.js
+.. _network-helper.js: https://searchfox.org/mozilla-central/source/devtools/shared/webconsole/network-helper.js
diff --git a/netwerk/docs/http/logging.rst b/netwerk/docs/http/logging.rst
new file mode 100644
index 0000000000..cee5a3e40d
--- /dev/null
+++ b/netwerk/docs/http/logging.rst
@@ -0,0 +1,341 @@
+HTTP Logging
+============
+
+
+Sometimes, while debugging your Web app (or client-side code using
+Necko), it can be useful to log HTTP traffic. This saves a log of HTTP-related
+information from your browser run into a file that you can examine (or
+upload to Bugzilla if a developer has asked you for a log).
+
+.. note::
+
+ **Note:** The `Web
+ Console <https://developer.mozilla.org/en-US/docs/Tools/Web_Console>`__
+ also offers the ability to peek at HTTP transactions within Firefox.
+ HTTP logging generally provides more detailed logging.
+
+.. _using-about-networking:
+
+Using about:logging
+-------------------
+
+This is the best and easiest way to do HTTP logging. At any point
+during while your browser is running, you can turn logging on and off.
+
+.. note::
+
+ **Note:** Before Firefox 108 the logging UI used to be located at `about:networking#logging`
+
+This allows you to capture only the "interesting" part of the browser's
+behavior (i.e. your bug), which makes the HTTP log much smaller and
+easier to analyze.
+
+#. Launch the browser and get it into whatever state you need to be in
+ just before your bug occurs.
+#. Open a new tab and type in "about:logging" into the URL bar.
+#. Adjust the location of the log file if you don't like the default
+#. Adjust the list of modules that you want to log: this list has the
+ exact same format as the MOZ_LOG environment variable (see below).
+ Generally the default list is OK, unless a Mozilla developer has told
+ you to modify it.
+
+ * For cookie issues, use presets ``Cookies``
+ * For WebSocket issues, use presets ``WebSockets``
+ * For HTTP/3 or QUIC issues, use presets ``HTTP/3``
+ * For other networking issues, use presets ``Networking``
+
+#. Click on Start Logging.
+#. Reproduce the bug (i.e. go to the web site that is broken for you and
+ make the bug happen in the browser)
+#. Make a note of the value of "Current Log File".
+#. Click on Stop Logging.
+#. Go to the folder containing the specified log file, and gather all
+ the log files. You will see several files that look like:
+ log.txt-main.1806.moz_log, log.txt-child.1954.moz_log,
+ log.txt-child.1970.moz_log, etc. This is because Firefox now uses
+ multiple processes, and each process gets its own log file.
+#. For many bugs, the "log.txt-main.moz_log" file is the only thing you need to
+ upload as a file attachment to your Bugzilla bug (this is assuming
+ you're logging to help a mozilla developer). Other bugs may require
+ all the logs to be uploaded--ask the developer if you're not sure.
+#. Pat yourself on the back--a job well done! Thanks for helping us
+ debug Firefox.
+
+.. note::
+
+ **Note:** The log may include sensitive data such as URLs and cookies.
+ To protect your privacy, we kindly request you to send the log file or
+ the profiler link directly and confidentially to necko@mozilla.com.
+
+Logging HTTP activity by manually setting environment variables
+---------------------------------------------------------------
+
+Sometimes the about:logging approach won't work, for instance if your
+bug occurs during startup, or you're running on mobile, etc. In that
+case you can set environment variables \*before\* you launch Firefox.
+Note that this approach winds up logging the whole browser history, so
+files can get rather large (they compress well :)
+
+Setting environment variables differs by operating system. Don't let the
+scary-looking command line stuff frighten you off; it's not hard at all!
+
+Windows
+~~~~~~~
+
+#. If Firefox is already running, exit out of it.
+
+#. Open a command prompt by holding down the Windows key and pressing "R".
+
+#. Type CMD and press enter, a new Command Prompt window with a black
+ background will appear.
+
+#. | Copy and paste the following lines one at a time into the Command
+ Prompt window. Press the enter key after each one.:
+ | **For 64-bit Windows:**
+
+ ::
+
+ set MOZ_LOG=timestamp,rotate:200,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5
+ set MOZ_LOG_FILE=%TEMP%\log.txt
+ "c:\Program Files\Mozilla Firefox\firefox.exe"
+
+ **For 32-bit Windows:**
+
+ ::
+
+ set MOZ_LOG=timestamp,rotate:200,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5
+ set MOZ_LOG_FILE=%TEMP%\log.txt
+ "c:\Program Files (x86)\Mozilla Firefox\firefox.exe"
+
+ (These instructions assume that you installed Firefox to the default
+ location, and that drive C: is your Windows startup disk. Make the
+ appropriate adjustments if those aren't the case.)
+
+#. Reproduce whatever problem it is that you're having.
+
+#. Once you've reproduced the problem, exit Firefox and look for the
+ generated log files in your temporary directory. You can type
+ "%TEMP%" directly into the Windows Explorer location bar to get there
+ quickly.
+
+Linux
+~~~~~
+
+This section offers information on how to capture HTTP logs for Firefox
+running on Linux.
+
+#. Quit out of Firefox if it's running.
+
+#. Open a new shell. The commands listed here assume a bash-compatible
+ shell.
+
+#. Copy and paste the following commands into the shell one at a time.
+ Make sure to hit enter after each line.
+
+ ::
+
+ export MOZ_LOG=timestamp,rotate:200,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5
+ export MOZ_LOG_FILE=/tmp/log.txt
+ cd /path/to/firefox
+ ./firefox
+
+#. Reproduce the problem you're debugging.
+
+#. When the problem has been reproduced, exit Firefox and look for the
+ generated log files, which you can find at ``/tmp/log.txt``.
+
+macOS
+~~~~~
+
+These instructions show how to log HTTP traffic in Firefox on macOS.
+
+#. Quit Firefox is if it's currently running, by using the Quit option
+ in the File menu. Keep in mind that simply closing all windows does
+ **not** quit Firefox on macOS (this is standard practice for Mac
+ applications).
+
+#. Run the Terminal application, which is located in the Utilities
+ subfolder in your startup disk's Applications folder.
+
+#. Copy and paste the following commands into the Terminal window,
+ hitting the return key after each line.
+
+ ::
+
+ export MOZ_LOG=timestamp,rotate:200,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5
+ export MOZ_LOG_FILE=~/Desktop/log.txt
+ cd /Applications/Firefox.app/Contents/MacOS
+ ./firefox
+
+ (The instructions assume that you've installed Firefox directly into
+ your startup disk's Applications folder. If you've put it elsewhere,
+ change the path used on the third line appropriately.)
+
+#. Reproduce whatever problem you're trying to debug.
+
+#. Quit Firefox and look for the generated ``log.txt`` log files on your
+ desktop.
+
+.. note::
+
+ **Note:** The generated log file uses Unix-style line endings. Older
+ editors may have problems with this, but if you're using an even
+ reasonably modern Mac OS X application to view the log, you won't
+ have any problems.
+
+Start logging using command line arguments
+------------------------------------------
+
+Since Firefox 61 it's possible to start logging in a bit simpler way
+than setting environment variables: using command line arguments. Here
+is an example for the **Windows** platform, on other platforms we accept
+the same form of the arguments:
+
+#. If Firefox is already running, exit out of it.
+
+#. Open a command prompt. On `Windows
+ XP <https://commandwindows.com/runline.htm>`__, you can find the
+ "Run..." command in the Start menu's "All Programs" submenu. On `all
+ newer versions of
+ Windows <http://www.xp-vista.com/other/where-is-run-in-windows-vista>`__,
+ you can hold down the Windows key and press "R".
+
+#. | Copy and paste the following line into the "Run" command window and
+ then press enter:
+ | **For 32-bit Windows:**
+
+ ::
+
+ "c:\Program Files (x86)\Mozilla Firefox\firefox.exe" -MOZ_LOG=timestamp,rotate:200,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5 -MOZ_LOG_FILE=%TEMP%\log.txt
+
+ **For 64-bit Windows:**
+
+ ::
+
+ "c:\Program Files\Mozilla Firefox\firefox.exe" -MOZ_LOG=timestamp,rotate:200,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5 -MOZ_LOG_FILE=%TEMP%\log.txt
+
+ (These instructions assume that you installed Firefox to the default
+ location, and that drive C: is your Windows startup disk. Make the
+ appropriate adjustments if those aren't the case.)
+
+#. Reproduce whatever problem it is that you're having.
+
+#. Once you've reproduced the problem, exit Firefox and look for the
+ generated log files in your temporary directory. You can type
+ "%TEMP%" directly into the Windows Explorer location bar to get there
+ quickly.
+
+Advanced techniques
+-------------------
+
+You can adjust some of the settings listed above to change what HTTP
+information get logged.
+
+Limiting the size of the logged data
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default there is no limit to the size of log file(s), and they
+capture the logging throughout the time Firefox runs, from start to
+finish. These files can get quite large (gigabytes)! So we have added
+a 'rotate:SIZE_IN_MB' option to MOZ_LOG (we use it in the examples
+above). If you are using Firefox >= 51, setting this option saves only
+the last N megabytes of logging data, which helps keep them manageable
+in size. (Unknown modules are ignored, so it's OK to use 'rotate' in
+your environment even if you're running Firefox <= 50: it will do
+nothing).
+
+This is accomplished by splitting the log into up to 4 separate files
+(their filenames have a numbered extension, .0, .1, .2, .3) The logging
+back end cycles the files it writes to, while ensuring that the sum of
+these files’ sizes will never go over the specified limit.
+
+Note 1: **the file with the largest number is not guaranteed to be the
+last file written!** We don’t move the files, we only cycle. Using the
+rotate module automatically adds timestamps to the log, so it’s always
+easy to recognize which file keeps the most recent data.
+
+Note 2: **rotate doesn’t support append**. When you specify rotate, on
+every start all the files (including any previous non-rotated log file)
+are deleted to avoid any mixture of information. The ``append`` module
+specified is then ignored.
+
+Use 'sync' if your browser crashes or hangs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, HTTP logging buffers messages and only periodically writes
+them to disk (this is more efficient and also makes logging less likely
+to interfere with race conditions, etc). However, if you are seeing
+your browser crash (or hang) you should add ",sync" to the list of
+logging modules in your MOZ_LOG environment variable. This will cause
+each log message to be immediately written (and fflush()'d), which is
+likely to give us more information about your crash.
+
+Turning on QUIC logging
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This can be done by setting `MOZ_LOG` to
+`timestamp,rotate:200,nsHttp:5,neqo_http3::*:5,neqo_transport::*:5`.
+
+Logging only HTTP request and response headers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are two ways to do this:
+
+#. Replace MOZ_LOG\ ``=nsHttp:5`` with MOZ_LOG\ ``=nsHttp:3`` in the
+ commands above.
+#. There's a handy extension for Firefox called `HTTP Header
+ Live <https://addons.mozilla.org/firefox/addon/3829>`__ that you can
+ use to capture just the HTTP request and response headers. This is a
+ useful tool when you want to peek at HTTP traffic.
+
+Turning off logging of socket-level transactions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you're not interested in socket-level log information, either because
+it's not relevant to your bug or because you're debugging something that
+includes a lot of noise that's hard to parse through, you can do that.
+Simply remove the text ``nsSocketTransport:5`` from the commands above.
+
+Turning off DNS query logging
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can turn off logging of host resolving (that is, DNS queries) by
+removing the text ``nsHostResolver:5`` from the commands above.
+
+Enable Logging for try server runs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can enable logging on try by passing the `env` argument via `mach try`.
+For example:
+
+.. note::
+
+ ``./mach try fuzzy --env "MOZ_LOG=nsHttp:5,SSLTokensCache:5"``
+
+How to enable QUIC logging
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The steps to enable QUIC logging (`QLOG <https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-main-schema/>`__) are:
+
+#. Go to ``about:config``, search for ``network.http.http3.enable_qlog`` and set it to true.
+#. Restart Firefox.
+#. QLOG files will be saved in the ``qlog_$PID`` directory located within your system's temporary directory.
+#. To visualize the QLOG data, visit https://qvis.quictools.info/. You can upload the QLOG files there to see the visual representation of the flows.
+
+See also
+--------
+
+- There are similar options available to debug mailnews protocols.
+ See `this
+ document <https://www-archive.mozilla.org/quality/mailnews/mail-troubleshoot.html>`__ for
+ more info about mailnews troubleshooting.
+- On the Windows platform, nightly Firefox builds have FTP logging
+ built-in (don't ask why this is only the case for Windows!). To
+ enable FTP logging, just set ``MOZ_LOG=nsFtp:5`` (in older versions
+ of Mozilla, you need to use ``nsFTPProtocol`` instead of ``nsFtp``).
+- When Mozilla's built-in logging capabilities aren't good enough, and
+ you need a full-fledged packet tracing tool, two free products are
+ `Wireshark <https://www.wireshark.org/>`__
+ and `ngrep <https://github.com/jpr5/ngrep/>`__. They are available
+ for Windows and most flavors of UNIX (including Linux and Mac OS
+ X), are rock solid, and offer enough features to help uncover any
+ Mozilla networking problem.
diff --git a/netwerk/docs/http_server_for_testing.rst b/netwerk/docs/http_server_for_testing.rst
new file mode 100644
index 0000000000..49224e38ad
--- /dev/null
+++ b/netwerk/docs/http_server_for_testing.rst
@@ -0,0 +1,482 @@
+HTTP Server for Unit Tests
+==========================
+
+This page describes the JavaScript implementation of an
+HTTP server located in ``netwerk/test/httpserver/``.
+
+Server Functionality
+~~~~~~~~~~~~~~~~~~~~
+
+Here are some of the things you can do with the server:
+
+- map a directory of files onto an HTTP path on the server, for an
+ arbitrary number of such directories (including nested directories)
+- define custom error handlers for HTTP error codes
+- serve a given file for requests for a specific path, optionally with
+ custom headers and status
+- define custom "CGI" handlers for specific paths using a
+ JavaScript-based API to create the response (headers and actual
+ content)
+- run multiple servers at once on different ports (8080, 8081, 8082,
+ and so on.)
+
+This functionality should be more than enough for you to use it with any
+test which requires HTTP-provided behavior.
+
+Where You Can Use It
+~~~~~~~~~~~~~~~~~~~~
+
+The server is written primarily for use from ``xpcshell``-based
+tests, and it can be used as an inline script or as an XPCOM component. The
+Mochitest framework also uses it to serve its tests, and
+`reftests <https://searchfox.org/mozilla-central/source/layout/tools/reftest/README.txt>`__
+can optionally use it when their behavior is dependent upon specific
+HTTP header values.
+
+Ways You Might Use It
+~~~~~~~~~~~~~~~~~~~~~
+
+- application update testing
+- cross-"server" security tests
+- cross-domain security tests, in combination with the right proxy
+ settings (for example, using `Proxy
+ AutoConfig <https://en.wikipedia.org/wiki/Proxy_auto-config>`__)
+- tests where the behavior is dependent on the values of HTTP headers
+ (for example, Content-Type)
+- anything which requires use of files not stored locally
+- open-id : the users could provide their own open id server (they only
+ need it when they're using their browser)
+- micro-blogging : users could host their own micro blog based on
+ standards like RSS/Atom
+- rest APIs : web application could interact with REST or SOAP APIs for
+ many purposes like : file/data storage, social sharing and so on
+- download testing
+
+Using the Server
+~~~~~~~~~~~~~~~~
+
+The best and first place you should look for documentation is
+``netwerk/test/httpserver/nsIHttpServer.idl``. It's extremely
+comprehensive and detailed, and it should be enough to figure out how to
+make the server do what you want. I also suggest taking a look at the
+less-comprehensive server
+`README <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/README>`__,
+although the IDL should usually be sufficient.
+
+Running the Server
+^^^^^^^^^^^^^^^^^^
+
+From test suites, the server should be importable as a testing-only JS
+module:
+
+.. code:: JavaScript
+
+ ChromeUtils.import("resource://testing-common/httpd.js");
+
+Once you've done that, you can create a new server as follows:
+
+.. code:: JavaScript
+
+ let server = new HttpServer(); // Or nsHttpServer() if you don't use ChromeUtils.import.
+
+ server.registerDirectory("/", nsILocalFileForBasePath);
+
+ server.start(-1); // uses a random available port, allows us to run tests concurrently
+ const SERVER_PORT = server.identity.primaryPort; // you can use this further on
+
+ // and when the tests are done, most likely from a callback...
+ server.stop(function() { /* continue execution here */ });
+
+You can also pass in a numeric port argument to the ``start()`` method,
+but we strongly suggest you don't do it. Using a dynamic port allow us
+to run your test in parallel with other tests which reduces wait times
+and makes everybody happy. If you really have to use a hardcoded port,
+you will have to annotate your test in the xpcshell manifest file with
+``run-sequentially = REASON``.
+However, this should only be used as the last possible option.
+
+.. note::
+
+ You **must** make sure to stop the server (the last line above)
+ before your test completes. Failure to do so will result in the
+ "XPConnect is being called on a scope without a Components property"
+ assertion, which will cause your test to fail in debug builds, and
+ you'll make people running tests grumbly because you've broken the
+ tests.
+
+Debugging Errors
+^^^^^^^^^^^^^^^^
+
+The server's default error pages don't give much information, partly
+because the error-dispatch mechanism doesn't currently accommodate doing
+so and partly because exposing errors in a real server could make it
+easier to exploit them. If you don't know why the server is acting a
+particular way, edit
+`httpd.js <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/httpd.js>`__
+and change the value of ``DEBUG`` to ``true``. This will cause the
+server to print information about the processing of requests (and errors
+encountered doing so) to the console, and it's usually not difficult to
+determine why problems exist from that output. ``DEBUG`` is ``false`` by
+default because the information printed with it set to ``true``
+unnecessarily obscures tinderbox output.
+
+Header Modification for Files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The server supports modifying the headers of the files (not request
+handlers) it serves. To modify the headers for a file, create a sibling
+file with the first file's name followed by ``^headers^``. Here's an
+example of how such a file might look:
+
+.. code:: text
+
+ HTTP 404 I want a cool HTTP description!
+ Content-Type: text/plain
+
+The status line is optional; all other lines specify HTTP headers in the
+standard HTTP format. Any line ending style is accepted, and the file
+may optionally end with a single newline character, to play nice with
+Unix text tools like ``diff`` and ``hg``.
+
+Hidden Files
+^^^^^^^^^^^^
+
+Any file which ends with a single ``^`` is inaccessible when querying
+the web server; if you try to access such a file you'll get a
+``404 File Not Found`` page instead. If for some reason you need to
+serve a file ending with a ``^``, just tack another ``^`` onto the end
+of the file name and the file will then become available at the
+single-``^`` location.
+
+At the moment this feature is basically a way to smuggle header
+modification for files into the file system without making those files
+accessible to clients; it remains to be seen whether and how hidden-file
+capabilities will otherwise be used.
+
+SJS: Server-Side Scripts
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Support for server-side scripts is provided through the SJS mechanism.
+Essentially an SJS is a file with a particular extension, chosen by the
+creator of the server, which contains a function with the name
+``handleRequest`` which is called to determine the response the server
+will generate. That function acts exactly like the ``handle`` function
+on the ``nsIHttpRequestHandler`` interface. First, tell the server what
+extension you're using:
+
+.. code:: JavaScript
+
+ const SJS_EXTENSION = "cgi";
+ server.registerContentType(SJS_EXTENSION, "sjs");
+
+Now just create an SJS with the extension ``cgi`` and write whatever you
+want. For example:
+
+.. code:: JavaScript
+
+ function handleRequest(request, response)
+ {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write("Hello world! This request was dynamically " +
+ "generated at " + new Date().toUTCString());
+ }
+
+Further examples may be found `in the Mozilla source
+tree <https://searchfox.org/mozilla-central/search?q=&path=.sjs>`__
+in existing tests. The request object is an instance of
+``nsIHttpRequest`` and the response is a ``nsIHttpResponse``.
+Please refer to the `IDL
+documentation <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/nsIHttpServer.idl>`
+for more details.
+
+Storing Information Across Requests
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+HTTP is basically a stateless protocol, and the httpd.js server API is
+for the most part similarly stateless. If you're using the server
+through the XPCOM interface you can simply store whatever state you want
+in enclosing environments or global variables. However, if you're using
+it through an SJS your request is processed in a near-empty environment
+every time processing occurs. To support stateful SJS behavior, the
+following functions have been added to the global scope in which a SJS
+handler executes, providing a simple key-value state storage mechanism:
+
+.. code:: JavaScript
+
+ /*
+ * v : T means v is of type T
+ * function A() : T means A() has type T
+ */
+
+ function getState(key : string) : string
+ function setState(key : string, value : string)
+ function getSharedState(key : string) : string
+ function setSharedState(key : string, value : string)
+ function getObjectState(key : string, callback : function(value : object) : void) // SJS API, XPCOM differs, see below
+ function setObjectState(key : string, value : object)
+
+A key is a string with arbitrary contents. The corresponding value is
+also a string, for the non-object-saving functions. For the
+object-saving functions, it is (wait for it) an object, or also
+``null``. Initially all keys are associated with the empty string or
+with ``null``, depending on whether the function accesses string- or
+object-valued storage. A stored value persists across requests and
+across server shutdowns and restarts. The state methods are available
+both in SJS and, for convenience when working with the server both via
+XPCOM and via SJS, XPCOM through the ``nsIHttpServer`` interface. The
+variants are designed to support different needs.
+
+.. warning::
+
+ **Warning:** Be careful using state: you, the user, are responsible
+ for synchronizing all uses of state through any of the available
+ methods. (This includes the methods that act only on per-path state:
+ you might still run into trouble there if your request handler
+ generates responses asynchronously. Further, any code with access to
+ the server XPCOM component could modify it between requests even if
+ you only ever used or modified that state while generating
+ synchronous responses.) JavaScript's run-to-completion behavior will
+ save you in simple cases, but with anything moderately complex you
+ are playing with fire, and if you do it wrong you will get burned.
+
+``getState`` and ``setState``
+'''''''''''''''''''''''''''''
+
+``getState`` and ``setState`` are designed for the case where a single
+request handler needs to store information from a first request of it
+for use in processing a second request of it — say, for example, if you
+wanted to implement a request handler implementing a counter:
+
+.. code:: JavaScript
+
+ /**
+ * Generates a response whose body is "0", "1", "2", and so on. each time a
+ * request is made. (Note that browser caching might make it appear
+ * to not quite have that behavior; a Cache-Control header would fix
+ * that issue if desired.)
+ */
+ function handleRequest(request, response)
+ {
+ var counter = +getState("counter"); // convert to number; +"" === 0
+ response.write("" + counter);
+ setState("counter", "" + ++counter);
+ }
+
+The useful feature of these two methods is that this state doesn't bleed
+outside the single path at which it resides. For example, if the above
+SJS were at ``/counter``, the value returned by ``getState("counter")``
+at some other path would be completely distinct from the counter
+implemented above. This makes it much simpler to write stateful handlers
+without state accidentally bleeding between unrelated handlers.
+
+.. note::
+
+ State saved by this method is specific to the HTTP path,
+ excluding query string and hash reference. ``/counter``,
+ ``/counter?foo``, and ``/counter?bar#baz`` all share the same state
+ for the purposes of these methods. (Indeed, non-shared state would be
+ significantly less useful if it changed when the query string
+ changed!)
+
+.. note::
+
+ The predefined ``__LOCATION__`` state
+ contains the native path of the SJS file itself. You can pass the
+ result directly to the ``nsILocalFile.initWithPath()``. Example:
+ ``thisSJSfile.initWithPath(getState('__LOCATION__'));``
+
+``getSharedState`` and ``setSharedState``
+'''''''''''''''''''''''''''''''''''''''''
+
+``getSharedState`` and ``setSharedState`` make up the functionality
+intentionally not supported by ``getState`` and set\ ``State``: state
+that exists between different paths. If you used the above handler at
+the paths ``/sharedCounters/1`` and ``/sharedCounters/2`` (changing the
+state-calls to use shared state, of course), the first load of either
+handler would return "0", a second load of either handler would return
+"1", a third load either handler would return "2", and so on. This more
+powerful functionality allows you to write cooperative handlers that
+expose and manipulate a piece of shared state. Be careful! One test can
+screw up another test pretty easily if it's not careful what it does
+with this functionality.
+
+``getObjectState`` and ``setObjectState``
+'''''''''''''''''''''''''''''''''''''''''
+
+``getObjectState`` and ``setObjectState`` support the remaining
+functionality not provided by the above methods: storing non-string
+values (object values or ``null``). These two methods are the same as
+``getSharedState`` and ``setSharedState``\ in that state is visible
+across paths; ``setObjectState`` in one handler will expose that value
+in another handler that uses ``getObjectState`` with the same key. (This
+choice was intentional, because object values already expose mutable
+state that you have to be careful about using.) This functionality is
+particularly useful for cooperative request handlers where one request
+*suspends* another, and that second request must then be *resumed* at a
+later time by a third request. Without object-valued storage you'd need
+to resort to polling on a string value using either of the previous
+state APIs; with this, however, you can make precise callbacks exactly
+when a particular event occurs.
+
+``getObjectState`` in an SJS differs in one important way from
+``getObjectState`` accessed via XPCOM. In XPCOM the method takes a
+single string argument and returns the object or ``null`` directly. In
+SJS, however, the process to return the value is slightly different:
+
+.. code:: JavaScript
+
+ function handleRequest(request, response)
+ {
+ var key = request.hasHeader("key")
+ ? request.getHeader("key")
+ : "unspecified";
+ var obj = null;
+ getObjectState(key, function(objval)
+ {
+ // This function is called synchronously with the object value
+ // associated with key.
+ obj = objval;
+ });
+ response.write("Keyed object " +
+ (obj && Object.prototype.hasOwnProperty.call(obj, "doStuff")
+ ? "has "
+ : "does not have ") +
+ "a doStuff method.");
+ }
+
+This idiosyncratic API is a restriction imposed by how sandboxes
+currently work: external functions added to the sandbox can't return
+object values when called within the sandbox. However, such functions
+can accept and call callback functions, so we simply use a callback
+function here to return the object value associated with the key.
+
+Advanced Dynamic Response Creation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The default behavior of request handlers is to fully construct the
+response, return, and only then send the generated data. For certain use
+cases, however, this is infeasible. For example, a handler which wanted
+to return an extremely large amount of data (say, over 4GB on a 32-bit
+system) might run out of memory doing so. Alternatively, precise control
+over the timing of data transmission might be required so that, for
+example, one request is received, "paused" while another request is
+received and completes, and then finished. httpd.js solves this problem
+by defining a ``processAsync()`` method which indicates to the server
+that the response will be written and finished by the handler. Here's an
+example of an SJS file which writes some data, waits five seconds, and
+then writes some more data and finishes the response:
+
+.. code:: JavaScript
+
+ var timer = null;
+
+ function handleRequest(request, response)
+ {
+ response.processAsync();
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("hello...");
+
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(function()
+ {
+ response.write("world!");
+ response.finish();
+ }, 5 * 1000 /* milliseconds */, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+
+The basic flow is simple: call ``processAsync`` to mark the response as
+being sent asynchronously, write data to the response body as desired,
+and when complete call ``finish()``. At the moment if you drop such a
+response on the floor, nothing will ever terminate the connection, and
+the server cannot be stopped (the stop API is asynchronous and
+callback-based); in the future a default connection timeout will likely
+apply, but for now, "don't do that".
+
+Full documentation for ``processAsync()`` and its interactions with
+other methods may, as always, be found in
+``netwerk/test/httpserver/nsIHttpServer.idl``.
+
+Manual, Arbitrary Response Creation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The standard mode of response creation is fully synchronous and is
+guaranteed to produce syntactically correct responses (excluding
+headers, which for the most part may be set to arbitrary values).
+Asynchronous processing enables the introduction of response handling
+coordinated with external events, but again, for the most part only
+syntactically correct responses may be generated. The third method of
+processing removes the correct-syntax property by allowing a response to
+contain completely arbitrary data through the ``seizePower()`` method.
+After this method is called, any data subsequently written to the
+response is written directly to the network as the response, skipping
+headers and making no attempt whatsoever to ensure any formatting of the
+transmitted data. As with asynchronous processing, the response is
+generated asynchronously and must be finished manually for the
+connection to be closed. (Again, nothing will terminate the connection
+for a response dropped on the floor, so again, "don't do that".) This
+mode of processing is useful for testing particular data formats that
+are either not HTTP or which do not match the precise, canonical
+representation that httpd.js generates. Here's an example of an SJS file
+which writes an apparent HTTP response whose status text contains a null
+byte (not allowed by HTTP/1.1, and attempting to set such status text
+through httpd.js would throw an exception) and which has a header that
+spans multiple lines (httpd.js responses otherwise generate only
+single-line headers):
+
+.. code:: JavaScript
+
+ function handleRequest(request, response)
+ {
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK Null byte \u0000 makes this response malformed\r\n" +
+ "X-Underpants-Gnomes-Strategy:\r\n" +
+ " Phase 1: Collect underpants.\r\n" +
+ " Phase 2: ...\r\n" +
+ " Phase 3: Profit!\r\n" +
+ "\r\n" +
+ "FAIL");
+ response.finish();
+ }
+
+While the asynchronous mode is capable of producing certain forms of
+invalid responses (through setting a bogus Content-Length header prior
+to the start of body transmission, among others), it must not be used in
+this manner. No effort will be made to preserve such implementation
+quirks (indeed, some are even likely to be removed over time): if you
+want to send malformed data, use ``seizePower()`` instead.
+
+Full documentation for ``seizePower()`` and its interactions with other
+methods may, as always, be found in
+``netwerk/test/httpserver/nsIHttpServer.idl``.
+
+Example Uses of the Server
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Shorter examples (for tests which only do one test):
+
+- ``netwerk/test/unit/test_bug331825.js``
+- ``netwerk/test/unit/test_httpcancel.js``
+- ``netwerk/test/unit/test_cookie_header.js``
+
+Longer tests (where you'd need to do multiple async server requests):
+
+- ``netwerk/test/httpserver/test/test_setstatusline.js``
+- ``netwerk/test/unit/test_content_sniffer.js``
+- ``netwerk/test/unit/test_authentication.js``
+- ``netwerk/test/unit/test_event_sink.js``
+- ``netwerk/test/httpserver/test/``
+
+Examples of modifying HTTP headers in files may be found at
+``netwerk/test/httpserver/test/data/cern_meta/``.
+
+Future Directions
+~~~~~~~~~~~~~~~~~
+
+The server, while very functional, is not yet complete. There are a
+number of things to fix and features to add, among them support for
+pipelining, support for incrementally-received requests (rather than
+buffering the entire body before invoking a request handler), and better
+conformance to the MUSTs and SHOULDs of HTTP/1.1. If you have
+suggestions for functionality or find bugs, file them in
+`Testing-httpd.js <https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=General>`__
+.
diff --git a/netwerk/docs/index.md b/netwerk/docs/index.md
new file mode 100644
index 0000000000..67f002b915
--- /dev/null
+++ b/netwerk/docs/index.md
@@ -0,0 +1,61 @@
+# Networking
+
+The Necko (aka Networking) component is Gecko's implementation of the web's networking protocols.
+Most of the component's source lives in `netwerk` directory and this document's source can be found in `netwerk/docs`.
+This page points to helpful resources for developers interested in contributing to Necko.
+
+Necko's [wiki page](https://wiki.mozilla.org/Networking) is dedicated to help users of firefox and bug authors. Readers can find various information related to bug filing, configuration of various networking features in the wiki page.
+
+
+
+The team can be reached:
+* on Matrix: [#necko:mozilla.org](https://chat.mozilla.org/#/room/#necko:mozilla.org)
+* by email: necko@mozilla.com
+* or by submitting a `Core::Networking` bug on [bugzilla.mozilla.org](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Networking)
+
+## How-to's
+```{toctree}
+:maxdepth: 1
+http/logging
+submitting_networking_bugs.md
+captive_portals.md
+connectivity_checking.md
+```
+
+## Tutorials
+```{toctree}
+:maxdepth: 1
+network_test_guidelines.md
+http_server_for_testing
+mochitest_with_http3.md
+```
+
+## Deep Dives
+### Necko Design
+```{toctree}
+:maxdepth: 1
+sec-necko-components.md
+cache2/doc
+http/http3.md
+Necko Bird’s-eye View <https://docs.google.com/presentation/d/1BRCK4WMYg-dUy07PB5H4jFVTpc4YnkQX8f5Y3KXqCs8>
+Gecko HTTP Walkthrough <https://docs.google.com/presentation/d/1iuYNLJfz24MN9SS5ljjhG07452-kZKtXmOeGjcc1-lU/>
+```
+
+### Necko Features
+```{toctree}
+:maxdepth: 1
+http/lifecycle
+dns/dns-over-https-trr
+url_parsers.md
+webtransport/webtransport
+captive_portals.md
+early_hints.md
+```
+
+## References
+```{toctree}
+:maxdepth: 1
+new_to_necko_resources
+neqo_triage_guideline.md
+necko_lingo.md
+```
diff --git a/netwerk/docs/mochitest_with_http3.md b/netwerk/docs/mochitest_with_http3.md
new file mode 100644
index 0000000000..a300a44698
--- /dev/null
+++ b/netwerk/docs/mochitest_with_http3.md
@@ -0,0 +1,76 @@
+# Introduction to Mochitest framework with HTTP/2 and HTTP/3 Support
+
+The Mochitest framework currently utilizes [httpd.js](https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/httpd.js) as its primary HTTP server, which only provides support for HTTP/1.1. To boost our testing capacity for HTTP/2 and HTTP/3 within necko, we improved the Mochitest framework to enable Firefox to connect to the test server using HTTP/2 or HTTP/3 while running Mochitest files.
+
+## Mochitest Framework Server Setup for HTTP/1.1
+As the diagram below, there is a back-end HTTP server running at `http://127.0.0.1:8888`. To ensure that Firefox can access multiple origins using a single HTTP server, the Mochitest framework employs proxy autoconfig (PAC). This [PAC script](https://searchfox.org/mozilla-central/rev/986024d59bff59819a3ed2f7c1d0f5254cdc3f3d/testing/mozbase/mozprofile/mozprofile/permissions.py#282-326) ensures that all plain HTTP connections are proxied to `127.0.0.1:8888`.
+
+When it comes to HTTPS connections, the Mochitest framework incorporates an additional SSL proxy between the HTTP server and the browser. Initially, Firefox sends a CONNECT request to the proxy to establish the tunnel. Upon successful setup, the proxy proceeds to relay data to the server.
+
+```{mermaid}
+graph LR
+ A[Firefox] -->|Request| B[SSL Proxy]
+ B -->|Request| C["Back-end Server (127.0.0.1:8888)<br/>Handles *.sjs, *.html, *.jpg, ..."]
+ C -->|Response| B
+ B -->|Response| A
+```
+
+## Mochitest Framework Server Setup for HTTP/2 and HTTP/3
+
+The diagram below depicts the architecture for HTTP/2 and HTTP/3.
+
+```{mermaid}
+graph LR
+ A[Firefox] -->|Request| B[Reverse Proxy]
+ B -->|Request| C["Back-end Server (127.0.0.1:8888)<br/>Handles *.sjs, *.html, *.jpg, ..."]
+ C -->|Response| B
+ B -->|Response| A
+ A -->|DNS lookup| D[DoH Server]
+ D -->|DNS response| A
+```
+
+### Back-end Server
+
+This is the same as the existing httpd.js.
+
+### Reverse Proxy
+
+Our reverse proxy, positioned in front of the back-end server, intercepts Firefox requests. Acting as the gateway for Firefox's HTTP/2 or HTTP/3 connections, the reverse proxy accepts these requests and translates them into HTTP/1.1 format before forwarding to the back-end server. Upon receiving a response from the back-end server, the reverse proxy subsequently relays this response back to Firefox.
+
+### DoH Server
+
+In order to route HTTP requests to the reverse proxy server, we’ll need a DoH server to be configured. The DoH server should return 127.0.0.1 for every A/AAAA DNS lookup.
+Moreover, the DoH server will also return an HTTPS RR for two reasons below:
+With the port information provided in the HTTPS RR, we can map all different port numbers in server-locations.txt to the port number that is used by the reverse proxy.
+With the “alpn” defined in the HTTPS RR, Firefox will automatically perform HTTPS upgrade and establish HTTP/2 or HTTP/3 connection to the reverse proxy server.
+
+### How to run test with HTTP/2 or HTTP/3 locally
+
+To execute tests with HTTP/2, include the `--use-http2-server` option. Here's an example:
+
+```
+./mach mochitest --use-http2-server PATH_TO_TEST_FILE
+```
+
+For HTTP/3 testing, switch the option to `--use-http3-server`. Like this:
+
+```
+./mach mochitest --use-http3-server PATH_TO_TEST_FILE
+```
+
+### Reasons for Skipped Tests
+
+We have several tests that are currently failing with HTTP/2 and HTTP/3 servers and they are skipped for now. There are a few reasons contributing to these failures:
+
+1. **Unexpected HTTPS upgrade**
+
+ HTTP/2 and HTTP/3 only support HTTPS, which has led us to upgrade all plain HTTP requests to HTTPS without exception. This change has caused some tests to fail because they expected the scheme to remain HTTP.
+ For example, this [test](https://searchfox.org/mozilla-central/rev/d31e56f7b3c2c18b8071a7b2a2fb6b4e01e3d3e8/netwerk/test/mochitests/file_domain_hierarchy_inner.html#8) expects the receiver's origin to be ``http://mochi.test:8888``.
+
+2. **Lack of Server Support for Some Features**
+
+ One example is that the HTTP/3 server doesn't support websocket for now, so tests in ``dom/websocket/test`` are all skipped.
+
+3. **Incompatibility with HTTPS**
+
+ Some tests were not designed to run with HTTPS. For these tests, skipping them is our only option.
diff --git a/netwerk/docs/necko_lingo.md b/netwerk/docs/necko_lingo.md
new file mode 100644
index 0000000000..78ff725cba
--- /dev/null
+++ b/netwerk/docs/necko_lingo.md
@@ -0,0 +1,243 @@
+# Necko Lingo<!-- omit from toc -->
+ Words We Keep Throwing Around Like Internet Confetti!
+
+- __B__
+ - [Background Thread](#background-thread)
+- __C__
+ - [Channel](#channel)
+ - [Child Process](#child-process)
+ - [Content Process](#content-process)
+- __D__
+ - [DoH](#doh)
+- __E__
+ - [Eventsource](#eventsource)
+ - [Electrolysis](#electrolysis)
+- __F__
+ - [Fetch](#fetch)
+ - [Fetch API](#fetch-api)
+ - [Fission](#fission)
+- __H__
+ - [H1/H2/H3](#h1h2h3)
+ - [Happy eyeballs](#happy-eyeballs)
+ - [HSTS](#hsts)
+- __L__
+ - [LoadInfo](#loadinfo)
+ - [Listener](#listener)
+- __M__
+ - [MainThread](#mainthread)
+ - [Mochitest](#mochitest)
+- __N__
+ - [neqo v/s Necko](#neqo-vs-necko)
+ - [NSS](#nss)
+ - [NSPR](#nspr)
+- __P__
+ - [PSM](#psm)
+ - [Parent Process](#parent-process)
+ - [Principal](#principal)
+- __O__
+ - [OMT](#omt)
+ - [OnStartRequest/OnDataAvailable/OnStopRequest/](#onstartrequestondataavailableonstoprequest)
+- __Q__
+ - [QUIC](#quic)
+- __R__
+ - [RCWN](#rcwn)
+- __S__
+ - [Socket Process](#socket-process)
+ - [Socket Thread](#socket-thread)
+ - [SOCKS Proxy](#socks-proxy)
+- __T__
+ - [TLS](#tls)
+ - [Triage](#triage)
+ - [TRR](#trr)
+- __W__
+ - [WebSocket](#websocket)
+ - [WebTransport](#webtransport)
+- __X__
+ - [Xpcshell-tests](#xpcshell-tests)
+ - [XHR](#xhr)
+
+
+
+
+## Background Thread
+Any thread that is not main thread.\
+Or this thread created [here](https://searchfox.org/mozilla-central/rev/23e7e940337d0e0b29aabe0080e4992d3860c940/ipc/glue/BackgroundImpl.cpp#880) for PBackground for IPC\
+Usually threads either have a dedicated name but background threads might also refer to the background thread pool: [NS_DispatchBackgroundTask](https://searchfox.org/mozilla-central/rev/23e7e940337d0e0b29aabe0080e4992d3860c940/xpcom/threads/nsThreadUtils.cpp#516).
+
+## Channel
+See [nsIChannel.idl](https://searchfox.org/mozilla-central/rev/bc6a50e6f08db0bb371ef7197c472555499e82c0/netwerk/base/nsIChannel.idl).
+It usually means nsHttpChannel.
+
+## Child Process
+Usually a firefox forked process - not the main process.\
+See [GeckoProcessTypes.h](https://searchfox.org/mozilla-central/source/__GENERATED__/xpcom/build/GeckoProcessTypes.h) for all process types in gecko.
+
+## Content Process
+Usually a firefox forked process running untrusted web content.
+
+## DoH
+[DNS](https://developer.mozilla.org/en-US/docs/Glossary/DNS) over HTTPS.\
+Refer [RFC 8484 - DNS Queries over HTTPS (DoH)](https://datatracker.ietf.org/doc/html/rfc8484).\
+Resolves DNS names by using a HTTPS server.\
+Refer [this link](https://firefox-source-docs.mozilla.org/networking/dns/dns-over-https-trr.html) for more details.
+
+## Eventsource
+Web API for [server-sent-events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). \
+Refer [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) for more details. \
+Necko is responsible for maintaining part of this code along with DOM.
+
+
+
+## Electrolysis
+Also known as E10S (E + 10 chars + S).\
+The process to make web content run in its own process.\
+Extended by the ‘Fission’ project, which introduced isolation for sites (really [eTLD+1’s](https://developer.mozilla.org/en-US/docs/Glossary/eTLD)).\
+Refer wiki [page](https://wiki.mozilla.org/Electrolysis) for more details.
+
+## Fetch
+[Fetch](https://fetch.spec.whatwg.org/) standard [aims](https://fetch.spec.whatwg.org/#goals) specifies standard for fetching resources across web. \
+Fetch and [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) slightly different things.
+
+## Fetch API
+[Web API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) for fetching resources from the web.
+The code is jointly maintained by DOM team and Necko team
+
+## Fission
+Similar to Electrolysis, but different domains ([eTLD+1’s](https://developer.mozilla.org/en-US/docs/Glossary/eTLD)) get their own content process to avoid [Spectre attacks](https://meltdownattack.com/). \
+Max 4 processes per [eTLD+1](https://developer.mozilla.org/en-US/docs/Glossary/eTLD). \
+Iframes get isolated from the parent. \
+Also referred to as origin isolation.
+
+## H1/H2/H3
+HTTP version: 0.9 / 1.0 / 1.1 / 2 / 3.
+
+## Happy Eyeballs
+RFC 6555/8305 – connecting via IPv4 and IPv6 simultaneously. \
+We implement this in a [different](https://searchfox.org/mozilla-central/rev/23e7e940337d0e0b29aabe0080e4992d3860c940/netwerk/protocol/http/DnsAndConnectSocket.cpp#202-206) way.
+
+## HSTS
+HTTP [Strict Transport Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security). \
+HSTS preload - a list of websites that will be upgraded to HTTPS without first needing a response.
+
+
+## LoadInfo
+Object containing information about about the load (who triggered the load, in which context, etc).
+Refer [nsILoadContext](https://searchfox.org/mozilla-central/source/netwerk/base/nsILoadContextInfo.idl) for more details.
+
+
+## Listener
+In async programming, we usually trigger an action, and set a listener to receive the result. \
+In necko it’s most usually referring to the listener of a channel, which is an object implementing [nsIStreamListener](https://searchfox.org/mozilla-central/rev/bc6a50e6f08db0bb371ef7197c472555499e82c0/netwerk/base/nsIStreamListener.idl) and/or [nsIRequestObserver](https://searchfox.org/mozilla-central/rev/bc6a50e6f08db0bb371ef7197c472555499e82c0/netwerk/base/nsIRequestObserver.idl#14).
+
+
+## MainThread
+Every process has a MainThread, which is also the master event loop for the process. \
+Many things are scoped to run on MainThread (DispatchToMainThread, etc).\
+For content processes, MainThread is where normal JS content runs ([Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) JavaScript runs on DOM Worker threads).
+
+
+## Mochitest
+Browser tests, run with full UI. \
+Refer [this](https://firefox-source-docs.mozilla.org/testing/mochitest-plain/index.html) for more details.
+
+## neqo v/s Necko
+_neqo_ is the name of the Mozilla [QUIC](https://github.com/mozilla/neqo) stack.
+Sometimes pronounced “knee-ko” or “neck-ou”, “knee-q-oo”.
+
+_Necko_ is the project name of the Mozilla networking stack.
+
+
+## NSS
+An acronym for the Network Security Services.
+Refer [this](https://firefox-source-docs.mozilla.org/security/nss/index.html) for more details.
+
+
+## NSPR
+An acronym for [Netscape Portable Runtime](https://firefox-source-docs.mozilla.org/nspr/index.html)
+Necko’s scope?
+
+## Observer
+Terminology used for referring the classes implementing the Observer design pattern. \
+Refer the following interfaces for more details:
+- [nsIObserver](https://searchfox.org/mozilla-central/source/xpcom/ds/nsIObserver.idl)
+- [nsIObserverService](https://searchfox.org/mozilla-central/source/xpcom/ds/nsIObserverService.idl)
+
+## PSM
+The PSM acronym may also be described as "Platform Security Module". \
+Glue code between Gecko and NSS. \
+Refer [this](https://wiki.mozilla.org/PSM:Topics) for more details.
+
+
+## Parent Process
+The process that runs the main structure of the browser, including the UI.
+It spawns the other processes needed for the browser. \
+Generally it is unrestricted, while most other processes have some level of sandboxing of permissions applied. \
+Before e10s, all code ran in the Parent Process.
+
+### Principal
+Abstraction encapsulating security details of a web page.
+Refer the following links for more details:
+- [sec-necko-components](https://firefox-source-docs.mozilla.org/networking/sec-necko-components.html)
+- [nsIPrincipal](https://searchfox.org/mozilla-central/source/caps/nsIPrincipal.idl)
+
+## OMT
+Abbreviation for Off Main Thread. OMT refers to processing data in non-main thread.
+There has been efforts in the past to move the processing of data to non-main thread to free up main thread resources.
+
+
+## OnStartRequest/OnDataAvailable/OnStopRequest/
+- OnStartRequest is listener notification sent when the necko has parsed the status and the header.
+- OnDataAvailable is a listener notification sent when necko has received the the data/body.
+- OnStopRequest is a listener notification sent when necko has received the complete response.
+- Refer to the following interface documentation for more details:
+ - [nsIRequestObserver](https://searchfox.org/mozilla-central/source/netwerk/base/nsIRequestObserver.idl)
+ - [nsIStreamListener.idl](https://searchfox.org/mozilla-central/source/netwerk/base/nsIStreamListener.idl)
+
+## QUIC
+An IETF transport protocol [RFC9000](https://datatracker.ietf.org/doc/html/rfc9000) primarily designed to carry HTTP/3, but now also used as a general-purpose Internet transport protocol for other workloads.
+Implemented in RUST and maintained by [neqo](https://github.com/mozilla/neqo).
+
+## RCWN
+Race cache with network. Feature that will send a request to network and cache at the same time and take the first to resolve.
+
+## Socket Process
+WIP project to move the actions of the socket thread into its own process for the purposes of isolation for security and stability (during crashes).
+
+## Socket Thread
+From the main process, a thread that handles opening and reading from the sockets for network communication. \
+We also use socket thread in content process for PHttpBackgroundChannel.ipdl
+
+## SOCKS Proxy
+Necko supports SOCKS proxy. \
+Refer [RFC 1928](https://datatracker.ietf.org/doc/html/rfc1928) for more details.
+
+## TLS
+Transport Layer Security. It’s implementation is maintained by NSS team.
+
+## Triage
+Team process of bug intake, analysis and categorization.
+
+## TRR
+Trusted recursive resolver.This is the name of our DoH implementation, as well as the name of the program that ensures DoH providers included in Firefox have agreed not to spy on users. \
+Refer the following for more details:
+- [Trusted_Recursive_Resolver](https://wiki.mozilla.org/Trusted_Recursive_Resolver)
+- [DOH-resolver-policy](https://wiki.mozilla.org/Security/DOH-resolver-policy)
+
+## WebSocket
+Server/client connections over TCP to pass data. A replacement for long-poll HTTP connection. \
+Refer [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455) and [whatwg spec](https://websockets.spec.whatwg.org/).
+
+
+## WebTransport
+A mechanism similar to WebSockets to transfer data between server and client, but built for HTTP/3; can also run over HTTP/2 (being implemented in gecko). \
+References:
+- [MDN Documentation on WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport)
+- [WebTransport RFC Draft](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/)
+
+## Xpcshell-tests
+Unit tests for testing our XPCOM code from JS context.
+Runs without a UI in a simpler setup (typically single-process). \
+Refer [firefox-source-docs](https://firefox-source-docs.mozilla.org/testing/xpcshell/index.html) for detailed explanation.
+
+## XHR
+[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). One way to perform AJAX, essentially a way to dynamically make network requests for use in the callers webpage, similar to Fetch.
diff --git a/netwerk/docs/neqo_triage_guideline.md b/netwerk/docs/neqo_triage_guideline.md
new file mode 100644
index 0000000000..1b02cbda5a
--- /dev/null
+++ b/netwerk/docs/neqo_triage_guideline.md
@@ -0,0 +1,12 @@
+# Neqo Triage Guideline
+
+[Neqo](https://github.com/mozilla/neqo/issues) has p1, p2, and p3 labels that correspond to the following Bugzilla labels:
+- p1 - the issue should be fixed as soon as possible because it is a defect or a fix has been planned for a project.
+ - This is P1 and P2 in Bugzilla.
+- p2 - the issue should be fixed but they are not critical, an issue can be in this state for a longer time.
+ - This is P3 in Bugzilla
+- p3 - we are not planning to fix the issue.
+ - This contains 2 sets of issues:
+ - we would take a fix if someone wants to work on it or
+ - we may not want to fix the issue at all at this time
+ - This is P5 in Bugzilla
diff --git a/netwerk/docs/network_test_guidelines.md b/netwerk/docs/network_test_guidelines.md
new file mode 100644
index 0000000000..f4b329e942
--- /dev/null
+++ b/netwerk/docs/network_test_guidelines.md
@@ -0,0 +1,175 @@
+# Networking Test Guidelines
+
+This is a high level document to introduce the different test types that are used in necko. The target audience is newcomer of necko team.
+
+## Necko Test Types
+
+We only introduce tests under [netwerk/test](https://searchfox.org/mozilla-central/source/netwerk/test) folder in this section.
+
+- [Chrome Tests](https://firefox-source-docs.mozilla.org/testing/chrome-tests/index.html)
+ - We usually write chrome tests when the code to be tested needs a browser windows to load some particular resources.
+ - Path: [netwerk/test/browser](https://searchfox.org/mozilla-central/source/netwerk/test/browser)
+- [Reftest](https://firefox-source-docs.mozilla.org/testing/webrender/index.html)
+ - Rarely used in necko.
+- [Mochitest](https://firefox-source-docs.mozilla.org/testing/mochitest-plain/index.html)
+ - Used when the code to be tested can be triggered by WebIDL. e.g., WebSocket and XMLHttpRequest.
+ - Path: [netwerk/test/mochitests](https://searchfox.org/mozilla-central/source/netwerk/test/mochitests)
+- [XPCShell tests](https://firefox-source-docs.mozilla.org/testing/xpcshell/index.html#xpcshell-tests)
+ - Mostly used in necko to test objects that can be accessed by JS. e.g., `nsIHttpChannel`.
+ - Path: [netwerk/test/unit](https://searchfox.org/mozilla-central/source/netwerk/test/unit)
+- [GTest](https://firefox-source-docs.mozilla.org/gtest/index.html)
+ - Useful when the code doesn't need a http server.
+ - Useful when writing code regarding to parsing strings. e.g., [Parsing Server Timing Header](https://searchfox.org/mozilla-central/rev/0249c123e74640ed91edeabba00649ef4d929372/netwerk/test/gtest/TestServerTimingHeader.cpp)
+- [Performance tests](https://firefox-source-docs.mozilla.org/testing/perfdocs/index.html)
+ - Current tests in [netwerk/test/perf](https://searchfox.org/mozilla-central/source/netwerk/test/perf) are all for testing `HTTP/3` code.
+
+There are also [web-platform-tests](https://firefox-source-docs.mozilla.org/web-platform/index.html) that is related to necko. We don't usually write new `web-platform-tests`. However, we do have lots of `web-platform-tests` for XHR, Fetch, and WebSocket.
+
+## Running Necko xpcshell-tests
+
+- Local:
+
+ Run all xpcshell-tests:
+
+ ```console
+ ./mach xpcshell-test netwerk/test/unit
+ ```
+
+ Note that xpcshell-tests are run in parallel, sometimes we want to run them sequentially for debugging.
+
+ ```console
+ ./mach xpcshell-test --sequential netwerk/test/unit
+ ```
+
+ Run a single tests:
+
+ ```console
+ ./mach xpcshell-test netwerk/test/unit/test_http3.js
+ ```
+
+ Run with socket process enabled:
+
+ ```console
+ ./mach xpcshell-test --setpref="network.http.network_access_on_socket_process.enabled=true" netwerk/test/unit/test_http3.js
+ ```
+
+ We usually debug networking issues with `HTTP Logging`. To enable logging when running tests:
+
+ ```console
+ MOZ_LOG=nsHttp:5 ./mach xpcshell-test netwerk/test/unit/test_http3.js
+ ```
+
+- Remote:
+
+ First of all, we need to know [Fuzzy Selector](https://firefox-source-docs.mozilla.org/tools/try/selectors/fuzzy.html), which is the tool we use to select which test to run on try. If you already know that your code change can be covered by necko xpcshell-tests, you can use the following command to run all tests in `netwerk/test/unit` on try.
+
+ ```console
+ ./mach try fuzzy netwerk/test/unit
+ ```
+
+ Run a single test on try:
+
+ ```console
+ ./mach try fuzzy netwerk/test/unit/test_http3.js
+ ```
+
+ Sometimes we want to debug the failed test on try with logging enabled:
+
+ ```console
+ ./mach try fuzzy --env "MOZ_LOG=nsHttp:5,nsHostResolver:5" netwerk/tesst/unit/test_http3.js
+ ```
+
+ Note that it's not usually a good idea to enabling logging when running all tests in a folder on try, since the raw log file can be really huge. The log file might be not available if the size exceeds the limit on try.
+ In the case that your code change is too generic or you are not sure which tests to run, you can use [Auto Selector](https://firefox-source-docs.mozilla.org/tools/try/selectors/auto.html) to let it select tests for you.
+
+## Debugging Intermittent Test Failures
+
+There are a lot of intermittent failures on try (usually not able to reproduce locally). Debugging these failures can be really annoying and time consuming. Here are some general guidelines to help you debug intermittent failures more efficiently.
+
+- Identify whether the failure is caused by your code change.
+ - Try to reproduce the intermittent failure locally. This is the most straightforward way. Adding `--verify` flag is also helpful when debugging locally (see this [document](https://firefox-source-docs.mozilla.org/testing/test-verification/index.html) for more details).
+ - We can check the failure summery on try to see if there is already a bug filed for this test failure. If yes, it's likely this is not caused by your code change.
+ - Re-trigger the failed test a few times and see if it passed. This can be easily done by clicking the `Push Health` button.
+ - Looking for similar failures happening now on other submissions. This can be done by:
+ ```
+ click on failing job -> read failure summary -> find similar ones by other authors in similar jobs
+ ```
+ - To re-run the failed test suite more times, you could add `rebuild` option to `./mach try`. For example, the following command allows to run necko xpcshell-tests 20 times on try.
+ ```
+ ./mach try fuzzy netwerk/test/unit --rebuild 20
+ ```
+- In the case that we really need to debug an intermittent test failure, see this [document](https://firefox-source-docs.mozilla.org/devtools/tests/debugging-intermittents.html) first for some general tips. Unfortunately, there is no easy way to debug this. One can try to isolate the failed test first and enable `HTTP logging` on try to collect the log for further analysis.
+
+## Writing Necko XPCShell Tests
+
+The most typical form of necko xpcsehll-test is creating an HTTP server and test your code by letting the server return some specific responses (e.g., `103 Early Hint`). We will only introduce how to write this kind of test in this document.
+
+- Code at server side
+
+ After [bug 1756557](https://bugzilla.mozilla.org/show_bug.cgi?id=1756557), it is possible to create a `nodejs` HTTP server in your test code. This saves us some time for writing code at the server side by reusing the HTTP module provided by `nodejs`.
+ This is what it looks like to create a simple HTTP server:
+
+ ```js
+ let server = new NodeHTTPServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("done");
+ });
+ ```
+
+ We can also create a `HTTP/2` server easily by replacing `NodeHTTPServer` with `NodeHTTP2Server` and adding the server certification.
+
+ ```js
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ let server = new NodeHTTP2Server();
+ ```
+
+- Code at client side
+
+ The recommend way is to create and open an HTTP channel and handle the response with a `Promise` asynchronously.
+ The code would be like:
+
+ ```js
+ function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+ }
+ let chan = makeChan(`http://localhost:${server.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ ```
+
+ This is what it looks like to put everything together:
+
+ ```js
+ add_task(async function test_http() {
+ let server = new NodeHTTPServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("done");
+ });
+ let chan = makeChan(`http://localhost:${server.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).protocolVersion, "http/1.1");
+ });
+ ```
diff --git a/netwerk/docs/new_to_necko_resources.rst b/netwerk/docs/new_to_necko_resources.rst
new file mode 100644
index 0000000000..a046d63bd6
--- /dev/null
+++ b/netwerk/docs/new_to_necko_resources.rst
@@ -0,0 +1,80 @@
+New-to-Necko Resources - An Aggregation
+=======================================
+
+This doc serves as a hub for resources/technologies a new-to-necko developer
+should get familiar with.
+
+Code Generation and IPC
+~~~~~~~~~~~~~~~~~~~~~~~
+
+* `IPC`_ (Inter-Process Communication) and `IPDL`_ (Inter-Thread and Inter-Process Message Passing)
+* `IDL`_ (Interface Description Language)
+ - Implementing an interface (C++/JS)
+ - XPCONNECT (scriptable/builtin)
+ - QueryInterface (QI) - do_QueryInterface/do_QueryObject
+ - do_GetService, do_CreateInstance
+* `WebIDL`_
+
+.. _IPC: /ipc/index.html
+.. _IDL: /xpcom/xpidl.html
+.. _IPDL: /ipc/ipdl.html
+.. _WebIDL: /toolkit/components/extensions/webextensions/webidl_bindings.html
+
+
+Necko interfaces
+~~~~~~~~~~~~~~~~
+
+* :searchfox:`nsISupports <xpcom/base/nsISupports.idl>`
+* :searchfox:`nsIRequest <netwerk/base/nsIRequest.idl>` ->
+ :searchfox:`nsIChannel <netwerk/base/nsIChannel.idl>` ->
+ :searchfox:`nsIHttpChannel <netwerk/protocol/http/nsIHttpChannel.idl>`
+* :searchfox:`nsIRequestObserver <netwerk/base/nsIRequestObserver.idl>` (onStart/onStopRequest)
+* :searchfox:`nsIStreamListener <netwerk/base/nsIStreamListener.idl>` (onDataAvailable)
+* :searchfox:`nsIInputStream <xpcom/io/nsIInputStream.idl>`/
+ :searchfox:`nsIOutputStream <xpcom/io/nsIOutputStream.idl>`
+
+Libraries
+~~~~~~~~~
+* `NSPR`_
+* `NSS`_
+* `PSM`_
+
+.. _NSPR: https://firefox-source-docs.mozilla.org/nspr/about_nspr.html?highlight=nspr
+.. _NSS: https://firefox-source-docs.mozilla.org/security/nss/legacy/faq/index.html
+.. _PSM: https://firefox-source-docs.mozilla.org/security/nss/legacy/faq/index.html?highlight=psm
+
+
+Preferences
+~~~~~~~~~~~
+* :searchfox:`all.js <modules/libpref/init/all.js>`
+* :searchfox:`firefox.js <browser/app/profile/firefox.js>`
+* :searchfox:`StaticPrefList.yaml <modules/libpref/init/StaticPrefList.yaml>`
+
+Debugging
+~~~~~~~~~
+* `HTTP Logging`_
+
+.. _HTTP Logging: /networking/http/logging.html
+
+Testing
+~~~~~~~
+* `xpcshell`_
+* `mochitest`_
+* `web-platform`_
+* `gtest`_
+* `marionette`_
+
+.. _xpcshell: /testing/xpcshell/index.html
+.. _mochitest: /browser/components/newtab/docs/v2-system-addon/mochitests.html
+.. _web-platform: /web-platform/index.html
+.. _gtest: /gtest/index.html
+.. _marionette: /testing/marionette/index.html
+
+
+See also
+~~~~~~~~
+ - E10S_ (Electrolysis) -> Split ``HttpChannel`` into: ``HttpChannelChild`` & ``HttpChannelParent``
+ - Fission_ -> Site isolation
+
+ .. _E10s: https://wiki.mozilla.org/Electrolysis
+ .. _Fission: https://hacks.mozilla.org/2021/05/introducing-firefox-new-site-isolation-security-architecture/
diff --git a/netwerk/docs/sec-necko-components.md b/netwerk/docs/sec-necko-components.md
new file mode 100644
index 0000000000..9a60cb013d
--- /dev/null
+++ b/netwerk/docs/sec-necko-components.md
@@ -0,0 +1,77 @@
+# Security and Networking Components
+
+This diagram models a high-level call flow upon performing an asyncOpen on an nsHttpChannel down into the NSS layer for a typical resource load.
+
+## Necko
+1. The LoadInfo, which contains [security related info](https://searchfox.org/mozilla-central/rev/27e4816536c891d85d63695025f2549fd7976392/netwerk/base/LoadInfo.h#284-294),
+ is passed to the channel (nsHttpChannel) on the parent process.
+2. The channel creates a transaction and the nsHttpConnectionMgr on the socket thread is signalled to handle the transaction.
+3. The transaction is then picked up on the socket thread and "dispatched" to a new or existing ConnectionEntry that is hashed by it's ConnectionInfo.
+4. The underlying connection, nsHttpConnection for Http/1.1 and Http/2 and HttpConnectionUDP for Http/3, will call into NSS for security functionality.
+
+## NSS
+Necko interacts with NSS through two distinct interfaces.
+ Primarily, most access flows via PSM which handles the configuration of TLS sockets, client certificate selection and server certificate verification.
+ However, Neqo (Mozilla's QUIC library) also relies directly on the TLS implementation inside NSS and uses it as an interface directly.
+
+NSS's internal structure is fairly convoluted, but there are five main areas relevant for Necko. Starting from the lowest level:
+1. [blapi.h](https://searchfox.org/mozilla-central/source/security/nss/lib/freebl/blapi.h) - exposes the wrappers for each cryptographic primitive supported by NSS and dispatches them to platform specific implementations.
+2. [pkcs11c.c](https://searchfox.org/mozilla-central/source/security/nss/lib/softoken/pkcs11c.c) - This wraps those underlying crypto primitives to provide a PKCS11 interface as a single module.
+3. [pk11pub.h](https://searchfox.org/mozilla-central/source/security/nss/lib/pk11wrap/pk11pub.h) - This wraps any module providing a PKCS11 interface and exposes high level cryptographic operations. It is widely used across Firefox.
+4. [ssl.h](https://searchfox.org/mozilla-central/source/security/nss/lib/ssl/ssl.h) and [sslexp.h](https://searchfox.org/mozilla-central/source/security/nss/lib/ssl/sslexp.h) expose our TLS interface for use in Necko's TLS and Neqo's QUIC connections.
+5. [cert.h](https://searchfox.org/mozilla-central/source/security/nss/lib/certdb/cert.h) exposes the certificate database functionality. [pkix.h](https://searchfox.org/mozilla-central/source/security/nss/lib/mozpkix/include/pkix/pkix.h) exposes the MozPkix certificate chain validation functions.
+
+
+```{mermaid}
+classDiagram
+
+class LoadInfo{
+ +Principal(s) (loading, triggering, toInherit)
+ +Context
+}
+
+nsHttpChannel --> nsHttpTransaction
+nsHttpTransaction --> nsHttpConnectionMgr
+nsHttpConnectionMgr --> ConnectionEntry : Via ConnectionInfo hash
+ConnectionEntry --> HttpConnectionBase
+
+HttpConnectionBase <-- nsHttpConnection : Is A
+HttpConnectionBase <-- HttpConnectionUDP : Is A
+
+nsHttpConnection --> nsSocketTransport2
+nsSocketTransport2 --> PSM
+PSM --> NSPR
+PSM --> `Off Main Thread CertVerifier`
+Neqo --> `Off Main Thread CertVerifier`
+
+%% for Http/3
+HttpConnectionUDP --> Http3Session : Http/3
+HttpConnectionUDP --> nsUDPSocket : Http/3
+nsUDPSocket --> NSPR : Http/3
+Http3Session --> Neqo : Http/3
+
+%% security TCP stack
+PSM --> TLS
+`Off Main Thread CertVerifier` --> Pcks11
+TLS --> Pcks11
+Pcks11 --> Blapi
+Blapi --> `Crypto Primitives`
+`Crypto Primitives` --> `Platform-Specific Crypto Implementations`
+
+%% transport security info
+PSM -- Transport Security Info
+Transport Security Info --> nsHttpChannel
+
+%% security UDP stack
+Neqo --> TLS
+`Off Main Thread CertVerifier`--> CertDB
+CertDB --> Builtins
+
+
+%% classes
+
+nsHttpChannel o-- LoadInfo
+nsHttpChannel o-- StreamListener
+nsHttpConnectionMgr o-- ConnectionEntry : Many
+
+```
diff --git a/netwerk/docs/submitting_networking_bugs.md b/netwerk/docs/submitting_networking_bugs.md
new file mode 100644
index 0000000000..f514bcb55c
--- /dev/null
+++ b/netwerk/docs/submitting_networking_bugs.md
@@ -0,0 +1,112 @@
+# Submitting Actionable Networking Bugs
+
+So you've found a networking issue with Firefox and decided to file a bug. First of all **Thanks!**. 🎉🎉🎉
+
+## Networking Bugs Lifecycle
+
+After a bug is filed, it gets triaged by one of the Necko team members.
+The engineer will consider the *steps to reproduce* then will do one of the following:
+- Assign a priority. An engineer will immediately or eventually start working on the bug.
+- Move the bug to another team.
+- Request more info from the reporter or someone else.
+
+A necko bug is considered triaged when it has a priority and the `[necko-triaged]` tag has been added to the whiteboard.
+
+As a bug reporter, please do not change the `Priority` or `Severity` flags. Doing so could prevent the bug from showing up in the triage queue.
+
+<div class="note">
+<div class="admonition-title">Note</div>
+
+> For bugs to get fixed as quickly as possible engineers should spend their time
+on the actual fix, not on figuring out what might be wrong. That's why it's
+important to go through the sections below and include as much information as
+possible in the bug report.
+
+</div>
+
+
+## Make Sure it's a Firefox Bug
+
+Sometimes a website may be misbehaving and you'll initially think it's caused by a bug in Firefox. However, extensions and other customizations could also cause an issue. Here are a few things to check before submitting the bug:
+- [Troubleshoot extensions, themes and hardware acceleration issues to solve common Firefox problems](https://support.mozilla.org/en-US/kb/troubleshoot-extensions-themes-to-fix-problems#w_start-firefox-in-troubleshoot-mode)
+ - This will confirm if an extension is causing the issue you're seeing. If the bug goes away, with extensions turned off, you might then want to figure out which extension is causing the problem. Turn off each extension and see if it keeps happening. Include this information in the bug report.
+- [Try reproducing the bug with a new Firefox profile](https://support.mozilla.org/en-US/kb/profile-manager-create-remove-switch-firefox-profiles#w_creating-a-profile)
+ - If a bug stops happening with a new profile, that could be caused by changed prefs, or some bad configuration in your active profile.
+ - Make sure to include the contents of `about:support` in your bug report.
+- Check if the bug also happens in other browsers
+
+## Make Sure the Bug has Clear Steps to Reproduce
+
+This is one of the most important requirements of getting the bug fixed. Clear steps to reproduce will help the engineer figure out what the problem is.
+If the bug can only be reproduced on a website that requires authentication you may provide a test account to the engineer via private email.
+If a certain interaction with a web server is required to reproduce the bug, feel free to attach a small nodejs, python, etc script to the bug.
+
+Sometimes a bug is intermittent (only happens occasionally) or the steps to reproduce it aren't obvious.
+It's still important to report these bugs but they should include additional info mentioned below so the engineers have a way to investigate.
+
+### Example 1:
+```
+ 1. Load `http://example.com`
+ 2. Click on button
+ 3. See that nothing happens and an exception is present in the web console.
+```
+### Example 2:
+```
+ 1. Download attached testcase
+ 2. Run testcase with the following command: `node index.js`
+ 3. Go to `http://localhost:8888/test` and click the button
+```
+
+## Additional Questions
+
+- Are you using a proxy? What kind?
+- Are you using DNS-over-HTTPS?
+ - If the `DoH mode` at about:networking#dns is 2 or 3 then the answer is yes.
+- What platform are you using? (Operating system, Linux distribution, etc)
+ - It's best to simply copy the output of `about:support`
+
+## MozRegression
+
+If a bug is easy to reproduce and you think it used to work before, consider using MozRegression to track down when/what started causing this issue.
+
+First you need to [install the tool](https://mozilla.github.io/mozregression/install.html). Then just follow [the instructions](https://mozilla.github.io/mozregression/quickstart.html) presented by mozregression. Reproducing the bug a dozen times might be necessary before the tool tracks down the cause.
+
+At the end you will be presented with a regression range that includes the commits that introduced the bug.
+
+## Performance Issues
+
+If you're seeing a performance issue (site is very slow to load, etc) you should consider submitting a performance profile.
+
+- Activate the profiler at: [https://profiler.firefox.com/](https://profiler.firefox.com/)
+- Use the `Networking` preset and click `Start Recording`.
+
+## Crashes
+
+If something you're doing is causing a crash, having the link to the stack trace is very useful.
+
+- Go to `about:crashes`
+- Paste the **Report ID** of the crash in the bug.
+
+## HTTP Logs
+
+See the [HTTP Logging](https://firefox-source-docs.mozilla.org/networking/http/logging.html) page for steps to capture HTTP logs.
+
+If the logs are large you can create a zip archive and attach them to the bug. If the archive is still too large to attach, you can upload it to a file storage service such as Google drive or OneDrive and submit the public link.
+
+Logs may include personal information such as cookies. Try using a fresh Firefox profile to capture the logs. If that is not possible, you can also put them in a password protected archive, or send them directly via email to the developer working on the bug.
+
+## Wireshark Dump
+
+In some cases it is necessary to see exactly what bytes Firefox is sending and receiving over the network. When that happens, the developer working on the bug might ask you for a wireshark capture.
+
+[Download](https://www.wireshark.org/download.html) it then run it while reproducing the bug.
+
+If the website you're loading to reproduce the bug is over HTTPS, then it might be necessary to [decrypt the capture file](https://wiki.wireshark.org/TLS#Using_the_.28Pre.29-Master-Secret) when recording it.
+
+## Web Console and Browser Console Errors
+
+Sometimes a website breaks because its assumptions about executing JavaScript in Firefox are wrong. When that happens the JavaScript engine might throw exceptions which could break the website you're viewing.
+
+When reporting a broken website or a proxy issue, also check the [web console](https://developer.mozilla.org/en-US/docs/Tools/Web_Console) `Press the Ctrl+Shift+K (Command+Option+K on OS X) keyboard shortcut` and [browser console](https://developer.mozilla.org/en-US/docs/Tools/Browser_Console) `keyboard: press Ctrl+Shift+J (or Cmd+Shift+J on a Mac).`
+
+If they contain errors or warnings, it would be good to add them to the bug report (As text is best, but a screenshot is also acceptable).
diff --git a/netwerk/docs/url_parsers.md b/netwerk/docs/url_parsers.md
new file mode 100644
index 0000000000..a0b978a2b1
--- /dev/null
+++ b/netwerk/docs/url_parsers.md
@@ -0,0 +1,143 @@
+# URL Parsers
+
+```{warning}
+In order to ensure thread safety it is important that all of the objects and interfaces of URI objects are immutable.
+If you are implementing a new URI type, please make sure that none of the type's public methods change the URL.
+```
+
+## Definitions
+- URI - Uniform Resource Identifier
+- URL - Uniform Resource Locator
+
+These two terms are used interchangeably throughout the codebase and essentially represent the same thing - a string of characters that represents a specific resource.
+
+## Motivation
+
+While we could simply pass strings around and leave it to the final consumer to deal with it, that creates a burden for the consumer and would probably be inefficient. Instead we parse the string into a nsIURI object as soon as possible and pass that object through function calls. This allows the consumer to easily extract only the part of the string they are interested in (eg. the hostname or the path).
+
+## Interfaces
+- [nsIURI](https://searchfox.org/mozilla-central/source/netwerk/base/nsIURI.idl)
+ - This is the most important interface for URI parsing. It contains a series of readonly attributes that consumers can use to extract information from the URI.
+- [nsIURL](https://searchfox.org/mozilla-central/source/netwerk/base/nsIURL.idl)
+ - Defines a structure for the URI's path (directory, fileName, fileBaseName, fileExtension)
+- [nsIFileURL](https://searchfox.org/mozilla-central/source/netwerk/base/nsIFileURL.idl)
+ - Has a file attribute of type `nsIFile`
+ - Used for local protocols to access the file represented by the `nsIURI`
+- [nsIMozIconURI](https://searchfox.org/mozilla-central/source/image/nsIIconURI.idl)
+ - Used to represent an icon. Contains additional attributes such as the size and contentType or state of the URL.
+- [nsIJARURI](https://searchfox.org/mozilla-central/source/modules/libjar/nsIJARURI.idl)
+ - Used to represent a resource inside of a JAR (zip archive) file.
+ - For example `jar:http://www.example.com/blue.jar!/ocean.html` represents the `/ocean.html` resource located inside the `blue.jar` archive that can be fetched via HTTP from example.com.
+- [nsIStandardURL](https://searchfox.org/mozilla-central/source/netwerk/base/nsIStandardURL.idl)
+ - Defines a few constant flags used to determine the type of the URL. No other attributes.
+- [nsINestedURI](https://searchfox.org/mozilla-central/source/netwerk/base/nsINestedURI.idl)
+ - Defines `innerURI` and `innermostURI`.
+ - `innermostURI` is just a helper - one could also get it by going through `innerURI` repeatedly until the attribute no longer QIs to nsINestedURI.
+- [nsISensitiveInfoHiddenURI](https://searchfox.org/mozilla-central/source/netwerk/base/nsISensitiveInfoHiddenURI.idl)
+ - Objects that implement this interface will have a `getSensitiveInfoHiddenSpec()` method that returns the spec of the URI with sensitive info (such as the password) replaced by the `*` symbol.
+
+### Diagram of Interfaces
+```{mermaid}
+classDiagram
+nsISupports <-- nsIURI
+nsIURI <-- nsIURL
+nsIURL <-- nsIFileURL
+nsIURI <-- nsIMozIconURI
+nsIURL <-- nsIJARURI
+nsISupports <-- nsIStandardURL
+nsISupports <-- nsINestedURI
+nsISupports <-- nsISensitiveInfoHiddenURI
+```
+
+### Mutation
+
+To ensure thread safety all implementations of nsIURI must be immutable.
+To change a URI the consumer must call `nsIURI.mutate()` which returns a `nsIMutator`. The `nsIMutator` has several setter methods that can be used change attributes on the concrete object. Once done changing the object, the consumer will call `nsIMutator.finalize()` to obtain an immutable `nsIURI`.
+
+- [nsIURIMutator](https://searchfox.org/mozilla-central/source/netwerk/base/nsIURIMutator.idl)
+ - This interface contains a series of setters that can be used to mutate and/or construct a `nsIURI`
+
+
+### Additional Interfaces
+
+- [nsISerializable](https://searchfox.org/mozilla-central/source/xpcom/ds/nsISerializable.idl)
+ - Allows us to serialize and deserialize URL objects into strings for persistent storage (such as session restore).
+
+## Implementations
+- [nsStandardURL](https://searchfox.org/mozilla-central/source/netwerk/base/nsStandardURL.h)
+- [SubstitutingURL](https://searchfox.org/mozilla-central/source/netwerk/protocol/res/SubstitutingURL.h)
+ - overrides nsStandardURL::GetFile to provide nsIFile resolution.
+ - This allows us to map URLs such as `resource://gre/actors/RemotePageChild.jsm` to the actual file on the disk.
+- [nsMozIconURI](https://searchfox.org/mozilla-central/source/image/decoders/icon/nsIconURI.h)
+ - Used to represent icon URLs
+- [nsSimpleURI](https://searchfox.org/mozilla-central/source/netwerk/base/nsSimpleURI.h)
+ - Used for simple URIs that normally don't have an authority (username, password, host, port)
+- [nsSimpleNestedURI](https://searchfox.org/mozilla-central/source/netwerk/base/nsSimpleNestedURI.h)
+ - eg. `view-source:http://example.com/path`
+ - Normally only the extra scheme of the nestedURI is relevant (eg. `view-source:`)
+ - Most of the getter/setters are delegated to the innerURI
+- [nsNestedAboutURI](https://searchfox.org/mozilla-central/source/netwerk/protocol/about/nsAboutProtocolHandler.h)
+ - Similar to nsSimpleNestedURI, but has an extra `mBaseURI` member that allows us to propagate the base URI to about:blank correctly`
+- [BlobURL](https://searchfox.org/mozilla-central/source/dom/file/uri/BlobURL.h)
+ - Used for javascript blobs
+ - Similar to nsSimpleURI, but also has a revoked field.
+- [DefaultURI](https://searchfox.org/mozilla-central/source/netwerk/base/DefaultURI.h)
+ - This class wraps an object parsed by the `rust-url` crate.
+ - While not yet enabled by default, due to small bugs in that parser, the plan is to eventually use this implementation for all _unknown protocols_ that don't have their own URL parser.
+- [nsJSURI](https://searchfox.org/mozilla-central/source/dom/jsurl/nsJSProtocolHandler.h)
+ - Used to represent javascript code (eg. `javascript:alert('hello')`)
+- [nsJARURI](https://searchfox.org/mozilla-central/source/modules/libjar/nsJARURI.h)
+ - Used to represent resources inside of JAR files.
+
+### Diagram of Implementations
+
+```{mermaid}
+classDiagram
+nsSimpleURI o-- BlobURL
+nsIMozIconURI o-- nsMozIconURI
+nsIFileURL o-- nsStandardURL
+nsIStandardURL o-- nsStandardURL
+nsISensitiveInfoHiddenURI o-- nsStandardURL
+nsStandardURL o-- SubstitutingURL
+nsIURI o-- nsSimpleURI
+nsSimpleURI o-- nsSimpleNestedURI
+nsSimpleNestedURI o-- nsNestedAboutURI
+
+nsIURI o-- DefaultURI
+
+nsSimpleURI o-- nsJSURI
+
+nsINestedURI o-- nsJARURI
+nsIJARURI o-- nsJARURI
+```
+
+## Class and Interface Diagram
+
+```{mermaid}
+classDiagram
+nsISupports <-- nsIURI
+nsIURI <-- nsIURL
+nsIURL <-- nsIFileURL
+nsIURI <-- nsIMozIconURI
+nsIURL <-- nsIJARURI
+nsISupports <-- nsIStandardURL
+nsISupports <-- nsINestedURI
+nsISupports <-- nsISensitiveInfoHiddenURI
+
+%% classes
+
+nsSimpleURI o-- BlobURL
+nsSimpleURI o-- nsJSURI
+nsIMozIconURI o-- nsMozIconURI
+nsIFileURL o-- nsStandardURL
+nsIStandardURL o-- nsStandardURL
+nsISensitiveInfoHiddenURI o-- nsStandardURL
+nsStandardURL o-- SubstitutingURL
+nsIURI o-- nsSimpleURI
+nsINestedURI o-- nsJARURI
+nsIJARURI o-- nsJARURI
+nsSimpleURI o-- nsSimpleNestedURI
+nsSimpleNestedURI o-- nsNestedAboutURI
+nsIURI o-- DefaultURI
+
+```
diff --git a/netwerk/docs/webtransport/webtransport.md b/netwerk/docs/webtransport/webtransport.md
new file mode 100644
index 0000000000..59e580d974
--- /dev/null
+++ b/netwerk/docs/webtransport/webtransport.md
@@ -0,0 +1,6 @@
+WebTransport (WIP)
+============
+
+Components:
+
+- [WebTransportSessionProxy](webtransportsessionproxy.md)
diff --git a/netwerk/docs/webtransport/webtransportsessionproxy.md b/netwerk/docs/webtransport/webtransportsessionproxy.md
new file mode 100644
index 0000000000..02fae55361
--- /dev/null
+++ b/netwerk/docs/webtransport/webtransportsessionproxy.md
@@ -0,0 +1,19 @@
+# WebTransportSessionProxy
+
+WebTransportSessionProxy is introduced to enable the creation of a Http3WebTransportSession and coordination of actions that are performed on the main thread and on the socket thread.
+
+WebTransportSessionProxy can be in different states and the following diagram depicts the transition between the states. “MT” and “ST” mean: the action is happening on the main and socket thread. More details about this class can be found in [WebTransportSessionProxy.h](https://searchfox.org/mozilla-central/source/netwerk/protocol/webtransport/WebTransportSessionProxy.h).
+
+```{mermaid}
+graph TD
+ A[INIT] -->|"nsIWebTransport::AsyncConnect; MT"| B[NEGOTIATING]
+ B -->|"200 response; ST"| C[NEGOTIATING_SUCCEEDED]
+ B -->|"nsHttpChannel::OnStart/OnStop failed; MT"| D[DONE]
+ B -->|"nsIWebTransport::CloseSession; MT"| D
+ C -->|"nsHttpChannel::OnStart/OnStop failed; MT"| F[SESSION_CLOSE_PENDING]
+ C -->|"nsHttpChannel::OnStart/OnStop succeeded; MT"| E[ACTIVE]
+ E -->|"nsIWebTransport::CloseSession; MT"| F
+ E -->|"The peer closed the session or HTTP/3 connection error; ST"| G[CLOSE_CALLBACK_PENDING]
+ F -->|"CloseSessionInternal called, The peer closed the session or HTTP/3 connection error; ST"| D
+ G -->|"CallOnSessionClosed or nsIWebTransport::CloseSession; MT"| D
+```
diff --git a/netwerk/ipc/ChannelEventQueue.cpp b/netwerk/ipc/ChannelEventQueue.cpp
new file mode 100644
index 0000000000..911deaa7d9
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.cpp
@@ -0,0 +1,227 @@
+/* -*- 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 "ChannelEventQueue.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Unused.h"
+#include "nsIChannel.h"
+#include "mozilla/dom/Document.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelEvent* ChannelEventQueue::TakeEvent() {
+ mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mFlushing);
+
+ if (mSuspended || mEventQueue.IsEmpty()) {
+ return nullptr;
+ }
+
+ UniquePtr<ChannelEvent> event(std::move(mEventQueue[0]));
+ mEventQueue.RemoveElementAt(0);
+
+ return event.release();
+}
+
+void ChannelEventQueue::FlushQueue() {
+ // Events flushed could include destruction of channel (and our own
+ // destructor) unless we make sure its refcount doesn't drop to 0 while this
+ // method is running.
+ nsCOMPtr<nsISupports> kungFuDeathGrip;
+ {
+ MutexAutoLock lock(mMutex);
+ kungFuDeathGrip = mOwner;
+ }
+ mozilla::Unused << kungFuDeathGrip; // Not used in this function
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mFlushing);
+ }
+#endif // DEBUG
+
+ bool needResumeOnOtherThread = false;
+
+ while (true) {
+ UniquePtr<ChannelEvent> event;
+ {
+ MutexAutoLock lock(mMutex);
+ event.reset(TakeEvent());
+ if (!event) {
+ MOZ_ASSERT(mFlushing);
+ mFlushing = false;
+ MOZ_ASSERT(mEventQueue.IsEmpty() || (mSuspended || !!mForcedCount));
+ break;
+ }
+ }
+
+ nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
+ MOZ_ASSERT(target);
+
+ bool isCurrentThread = false;
+ nsresult rv = target->IsOnCurrentThread(&isCurrentThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Simply run this event on current thread if we are not sure about it
+ // in release channel, or assert in Aurora/Nightly channel.
+ MOZ_DIAGNOSTIC_ASSERT(false);
+ isCurrentThread = true;
+ }
+
+ if (!isCurrentThread) {
+ // Next event needs to run on another thread. Put it back to
+ // the front of the queue can try resume on that thread.
+ Suspend();
+ PrependEvent(std::move(event));
+
+ needResumeOnOtherThread = true;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mFlushing);
+ mFlushing = false;
+ MOZ_ASSERT(!mEventQueue.IsEmpty());
+ }
+ break;
+ }
+
+ event->Run();
+ } // end of while(true)
+
+ // The flush procedure is aborted because next event cannot be run on current
+ // thread. We need to resume the event processing right after flush procedure
+ // is finished.
+ // Note: we cannot call Resume() while "mFlushing == true" because
+ // CompleteResume will not trigger FlushQueue while there is an ongoing flush.
+ if (needResumeOnOtherThread) {
+ Resume();
+ }
+}
+
+void ChannelEventQueue::Suspend() {
+ MutexAutoLock lock(mMutex);
+ SuspendInternal();
+}
+
+void ChannelEventQueue::SuspendInternal() {
+ mMutex.AssertCurrentThreadOwns();
+
+ mSuspended = true;
+ mSuspendCount++;
+}
+
+void ChannelEventQueue::Resume() {
+ MutexAutoLock lock(mMutex);
+ ResumeInternal();
+}
+
+void ChannelEventQueue::ResumeInternal() {
+ mMutex.AssertCurrentThreadOwns();
+
+ // Resuming w/o suspend: error in debug mode, ignore in build
+ MOZ_ASSERT(mSuspendCount > 0);
+ if (mSuspendCount <= 0) {
+ return;
+ }
+
+ if (!--mSuspendCount) {
+ if (mEventQueue.IsEmpty() || !!mForcedCount) {
+ // Nothing in queue to flush or waiting for AutoEventEnqueuer to
+ // finish the force enqueue period, simply clear the flag.
+ mSuspended = false;
+ return;
+ }
+
+ // Hold a strong reference of mOwner to avoid the channel release
+ // before CompleteResume was executed.
+ class CompleteResumeRunnable : public Runnable {
+ public:
+ explicit CompleteResumeRunnable(ChannelEventQueue* aQueue,
+ nsISupports* aOwner)
+ : Runnable("CompleteResumeRunnable"),
+ mQueue(aQueue),
+ mOwner(aOwner) {}
+
+ NS_IMETHOD Run() override {
+ mQueue->CompleteResume();
+ return NS_OK;
+ }
+
+ private:
+ virtual ~CompleteResumeRunnable() = default;
+
+ RefPtr<ChannelEventQueue> mQueue;
+ nsCOMPtr<nsISupports> mOwner;
+ };
+
+ if (!mOwner) {
+ return;
+ }
+
+ // Worker thread requires a CancelableRunnable.
+ RefPtr<Runnable> event = new CompleteResumeRunnable(this, mOwner);
+
+ nsCOMPtr<nsIEventTarget> target;
+ target = mEventQueue[0]->GetEventTarget();
+ MOZ_ASSERT(target);
+
+ Unused << NS_WARN_IF(
+ NS_FAILED(target->Dispatch(event.forget(), NS_DISPATCH_NORMAL)));
+ }
+}
+
+bool ChannelEventQueue::MaybeSuspendIfEventsAreSuppressed() {
+ // We only ever need to suppress events on the main thread, since this is
+ // where content scripts can run.
+ if (!NS_IsMainThread()) {
+ return false;
+ }
+
+ // Only suppress events for queues associated with XHRs, as these can cause
+ // content scripts to run.
+ if (mHasCheckedForXMLHttpRequest && !mForXMLHttpRequest) {
+ return false;
+ }
+
+ mMutex.AssertCurrentThreadOwns();
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(mOwner));
+ if (!channel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ // Figure out if this is for an XHR, if we haven't done so already.
+ if (!mHasCheckedForXMLHttpRequest) {
+ nsContentPolicyType contentType = loadInfo->InternalContentPolicyType();
+ mForXMLHttpRequest =
+ (contentType == nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST);
+ mHasCheckedForXMLHttpRequest = true;
+
+ if (!mForXMLHttpRequest) {
+ return false;
+ }
+ }
+
+ // Suspend the queue if the associated document has suppressed event handling,
+ // *and* it is not in the middle of a synchronous operation that might require
+ // XHR events to be processed (such as a synchronous XHR).
+ RefPtr<dom::Document> document;
+ loadInfo->GetLoadingDocument(getter_AddRefs(document));
+ if (document && document->EventHandlingSuppressed() &&
+ !document->IsInSyncOperation()) {
+ document->AddSuspendedChannelEventQueue(this);
+ SuspendInternal();
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/ChannelEventQueue.h b/netwerk/ipc/ChannelEventQueue.h
new file mode 100644
index 0000000000..1b61088e91
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -0,0 +1,383 @@
+/* -*- 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_ChannelEventQueue_h
+#define mozilla_net_ChannelEventQueue_h
+
+#include "nsTArray.h"
+#include "nsIEventTarget.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RecursiveMutex.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+
+class nsISupports;
+
+namespace mozilla {
+namespace net {
+
+class ChannelEvent {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(ChannelEvent)
+ MOZ_COUNTED_DTOR_VIRTUAL(ChannelEvent) virtual void Run() = 0;
+ virtual already_AddRefed<nsIEventTarget> GetEventTarget() = 0;
+};
+
+// Note that MainThreadChannelEvent should not be used in child process since
+// GetEventTarget() directly returns an unlabeled event target.
+class MainThreadChannelEvent : public ChannelEvent {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(MainThreadChannelEvent)
+ MOZ_COUNTED_DTOR_OVERRIDE(MainThreadChannelEvent)
+
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ return do_AddRef(GetMainThreadSerialEventTarget());
+ }
+};
+
+class ChannelFunctionEvent : public ChannelEvent {
+ public:
+ ChannelFunctionEvent(
+ std::function<already_AddRefed<nsIEventTarget>()>&& aGetEventTarget,
+ std::function<void()>&& aCallback)
+ : mGetEventTarget(std::move(aGetEventTarget)),
+ mCallback(std::move(aCallback)) {}
+
+ void Run() override { mCallback(); }
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ return mGetEventTarget();
+ }
+
+ private:
+ const std::function<already_AddRefed<nsIEventTarget>()> mGetEventTarget;
+ const std::function<void()> mCallback;
+};
+
+// UnsafePtr is a work-around our static analyzer that requires all
+// ref-counted objects to be captured in lambda via a RefPtr
+// The ChannelEventQueue makes it safe to capture "this" by pointer only.
+// This is required as work-around to prevent cycles until bug 1596295
+// is resolved.
+template <typename T>
+class UnsafePtr {
+ public:
+ explicit UnsafePtr(T* aPtr) : mPtr(aPtr) {}
+
+ T& operator*() const { return *mPtr; }
+ T* operator->() const {
+ MOZ_ASSERT(mPtr, "dereferencing a null pointer");
+ return mPtr;
+ }
+ operator T*() const& { return mPtr; }
+ explicit operator bool() const { return mPtr != nullptr; }
+
+ private:
+ T* const mPtr;
+};
+
+class NeckoTargetChannelFunctionEvent : public ChannelFunctionEvent {
+ public:
+ template <typename T>
+ NeckoTargetChannelFunctionEvent(T* aChild, std::function<void()>&& aCallback)
+ : ChannelFunctionEvent(
+ [child = UnsafePtr<T>(aChild)]() {
+ MOZ_ASSERT(child);
+ return child->GetNeckoTarget();
+ },
+ std::move(aCallback)) {}
+};
+
+// Workaround for Necko re-entrancy dangers. We buffer IPDL messages in a
+// queue if still dispatching previous one(s) to listeners/observers.
+// Otherwise synchronous XMLHttpRequests and/or other code that spins the
+// event loop (ex: IPDL rpc) could cause listener->OnDataAvailable (for
+// instance) to be dispatched and called before mListener->OnStartRequest has
+// completed.
+// The ChannelEventQueue implementation ensures strict ordering of
+// event execution across target threads.
+
+class ChannelEventQueue final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChannelEventQueue)
+
+ public:
+ explicit ChannelEventQueue(nsISupports* owner)
+ : mSuspendCount(0),
+ mSuspended(false),
+ mForcedCount(0),
+ mFlushing(false),
+ mHasCheckedForXMLHttpRequest(false),
+ mForXMLHttpRequest(false),
+ mOwner(owner),
+ mMutex("ChannelEventQueue::mMutex"),
+ mRunningMutex("ChannelEventQueue::mRunningMutex") {}
+
+ // Puts IPDL-generated channel event into queue, to be run later
+ // automatically when EndForcedQueueing and/or Resume is called.
+ //
+ // @param aCallback - the ChannelEvent
+ // @param aAssertionWhenNotQueued - this optional param will be used in an
+ // assertion when the event is executed directly.
+ inline void RunOrEnqueue(ChannelEvent* aCallback,
+ bool aAssertionWhenNotQueued = false);
+
+ // Append ChannelEvent in front of the event queue.
+ inline void PrependEvent(UniquePtr<ChannelEvent>&& aEvent);
+ inline void PrependEvents(nsTArray<UniquePtr<ChannelEvent>>& aEvents);
+
+ // After StartForcedQueueing is called, RunOrEnqueue() will start enqueuing
+ // events that will be run/flushed when EndForcedQueueing is called.
+ // - Note: queueing may still be required after EndForcedQueueing() (if the
+ // queue is suspended, etc): always call RunOrEnqueue() to avoid race
+ // conditions.
+ inline void StartForcedQueueing();
+ inline void EndForcedQueueing();
+
+ // Suspend/resume event queue. RunOrEnqueue() will start enqueuing
+ // events and they will be run/flushed when resume is called. These should be
+ // called when the channel owning the event queue is suspended/resumed.
+ void Suspend();
+ // Resume flushes the queue asynchronously, i.e. items in queue will be
+ // dispatched in a new event on the current thread.
+ void Resume();
+
+ void NotifyReleasingOwner() {
+ MutexAutoLock lock(mMutex);
+ mOwner = nullptr;
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool IsEmpty() {
+ MutexAutoLock lock(mMutex);
+ return mEventQueue.IsEmpty();
+ }
+#endif
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~ChannelEventQueue() = default;
+
+ void SuspendInternal();
+ void ResumeInternal();
+
+ bool MaybeSuspendIfEventsAreSuppressed() MOZ_REQUIRES(mMutex);
+
+ inline void MaybeFlushQueue();
+ void FlushQueue();
+ inline void CompleteResume();
+
+ ChannelEvent* TakeEvent();
+
+ nsTArray<UniquePtr<ChannelEvent>> mEventQueue MOZ_GUARDED_BY(mMutex);
+
+ uint32_t mSuspendCount MOZ_GUARDED_BY(mMutex);
+ bool mSuspended MOZ_GUARDED_BY(mMutex);
+ uint32_t mForcedCount // Support ForcedQueueing on multiple thread.
+ MOZ_GUARDED_BY(mMutex);
+ bool mFlushing MOZ_GUARDED_BY(mMutex);
+
+ // Whether the queue is associated with an XHR. This is lazily instantiated
+ // the first time it is needed. These are MainThread-only.
+ bool mHasCheckedForXMLHttpRequest;
+ bool mForXMLHttpRequest;
+
+ // Keep ptr to avoid refcount cycle: only grab ref during flushing.
+ nsISupports* mOwner MOZ_GUARDED_BY(mMutex);
+
+ // For atomic mEventQueue operation and state update
+ Mutex mMutex;
+
+ // To guarantee event execution order among threads
+ RecursiveMutex mRunningMutex MOZ_ACQUIRED_BEFORE(mMutex);
+
+ friend class AutoEventEnqueuer;
+};
+
+inline void ChannelEventQueue::RunOrEnqueue(ChannelEvent* aCallback,
+ bool aAssertionWhenNotQueued) {
+ MOZ_ASSERT(aCallback);
+ // Events execution could be a destruction of the channel (and our own
+ // destructor) unless we make sure its refcount doesn't drop to 0 while this
+ // method is running.
+ nsCOMPtr<nsISupports> kungFuDeathGrip;
+
+ // To avoid leaks.
+ UniquePtr<ChannelEvent> event(aCallback);
+
+ // To guarantee that the running event and all the events generated within
+ // it will be finished before events on other threads.
+ RecursiveMutexAutoLock lock(mRunningMutex);
+ {
+ MutexAutoLock lock(mMutex);
+ kungFuDeathGrip = mOwner; // must be under the lock
+
+ bool enqueue = !!mForcedCount || mSuspended || mFlushing ||
+ !mEventQueue.IsEmpty() ||
+ MaybeSuspendIfEventsAreSuppressed();
+ // To ensure strict ordering of events across multiple threads we buffer the
+ // events for the below cases:
+ // a. event queuing is forced by AutoEventEnqueuer
+ // b. event queue is suspended
+ // c. an event is currently flushed/executed from the queue
+ // d. queue is non-empty (pending events on remote thread targets)
+ if (enqueue) {
+ mEventQueue.AppendElement(std::move(event));
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
+ MOZ_ASSERT(target);
+
+ bool isCurrentThread = false;
+ DebugOnly<nsresult> rv = target->IsOnCurrentThread(&isCurrentThread);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!isCurrentThread) {
+ // Leverage Suspend/Resume mechanism to trigger flush procedure without
+ // creating a new one.
+ // The execution of further events in the queue is blocked until the
+ // target thread completes the execution of this event.
+ // A callback is dispatched to the target thread to flush events from the
+ // queue. This is done
+ // by ResumeInternal which dispatches a runnable
+ // (CompleteResumeRunnable) to the target thread. The target thread will
+ // call CompleteResume to flush the queue. All the events are run
+ // synchronously in their respective target threads.
+ SuspendInternal();
+ mEventQueue.AppendElement(std::move(event));
+ ResumeInternal();
+ return;
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued);
+ // execute the event synchronously if we are not queuing it and
+ // the target thread is the current thread
+ event->Run();
+}
+
+inline void ChannelEventQueue::StartForcedQueueing() {
+ MutexAutoLock lock(mMutex);
+ ++mForcedCount;
+}
+
+inline void ChannelEventQueue::EndForcedQueueing() {
+ bool tryFlush = false;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mForcedCount > 0);
+ if (!--mForcedCount) {
+ tryFlush = true;
+ }
+ }
+
+ if (tryFlush) {
+ MaybeFlushQueue();
+ }
+}
+
+inline void ChannelEventQueue::PrependEvent(UniquePtr<ChannelEvent>&& aEvent) {
+ MutexAutoLock lock(mMutex);
+
+ // Prepending event while no queue flush foreseen might cause the following
+ // channel events not run. This assertion here guarantee there must be a
+ // queue flush, either triggered by Resume or EndForcedQueueing, to execute
+ // the added event.
+ MOZ_ASSERT(mSuspended || !!mForcedCount);
+
+ mEventQueue.InsertElementAt(0, std::move(aEvent));
+}
+
+inline void ChannelEventQueue::PrependEvents(
+ nsTArray<UniquePtr<ChannelEvent>>& aEvents) {
+ MutexAutoLock lock(mMutex);
+
+ // Prepending event while no queue flush foreseen might cause the following
+ // channel events not run. This assertion here guarantee there must be a
+ // queue flush, either triggered by Resume or EndForcedQueueing, to execute
+ // the added events.
+ MOZ_ASSERT(mSuspended || !!mForcedCount);
+
+ mEventQueue.InsertElementsAt(0, aEvents.Length());
+
+ for (uint32_t i = 0; i < aEvents.Length(); i++) {
+ mEventQueue[i] = std::move(aEvents[i]);
+ }
+}
+
+inline void ChannelEventQueue::CompleteResume() {
+ bool tryFlush = false;
+ {
+ MutexAutoLock lock(mMutex);
+
+ // channel may have been suspended again since Resume fired event to call
+ // this.
+ if (!mSuspendCount) {
+ // we need to remain logically suspended (for purposes of queuing incoming
+ // messages) until this point, else new incoming messages could run before
+ // queued ones.
+ mSuspended = false;
+ tryFlush = true;
+ }
+ }
+
+ if (tryFlush) {
+ MaybeFlushQueue();
+ }
+}
+
+inline void ChannelEventQueue::MaybeFlushQueue() {
+ // Don't flush if forced queuing on, we're already being flushed, or
+ // suspended, or there's nothing to flush
+ bool flushQueue = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+ flushQueue = !mForcedCount && !mFlushing && !mSuspended &&
+ !mEventQueue.IsEmpty() && !MaybeSuspendIfEventsAreSuppressed();
+
+ // Only one thread is allowed to run FlushQueue at a time.
+ if (flushQueue) {
+ mFlushing = true;
+ }
+ }
+
+ if (flushQueue) {
+ FlushQueue();
+ }
+}
+
+// Ensures that RunOrEnqueue() will be collecting events during its lifetime
+// (letting caller know incoming IPDL msgs should be queued). Flushes the queue
+// when it goes out of scope.
+class MOZ_STACK_CLASS AutoEventEnqueuer {
+ public:
+ explicit AutoEventEnqueuer(ChannelEventQueue* queue) : mEventQueue(queue) {
+ {
+ // Probably not actually needed, since NotifyReleasingOwner should
+ // only happen after this, but safer to take it in case things change
+ MutexAutoLock lock(queue->mMutex);
+ mOwner = queue->mOwner;
+ }
+ mEventQueue->StartForcedQueueing();
+ }
+ ~AutoEventEnqueuer() { mEventQueue->EndForcedQueueing(); }
+
+ private:
+ RefPtr<ChannelEventQueue> mEventQueue;
+ // Ensure channel object lives longer than ChannelEventQueue.
+ nsCOMPtr<nsISupports> mOwner;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/ipc/DocumentChannel.cpp b/netwerk/ipc/DocumentChannel.cpp
new file mode 100644
index 0000000000..a0d6a0c1f9
--- /dev/null
+++ b/netwerk/ipc/DocumentChannel.cpp
@@ -0,0 +1,484 @@
+/* -*- 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 "mozilla/net/DocumentChannel.h"
+
+#include <inttypes.h>
+#include <utility>
+#include "mozIDOMWindow.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/net/DocumentChannelChild.h"
+#include "mozilla/net/ParentProcessDocumentChannel.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsHttpHandler.h"
+#include "nsIContentPolicy.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIDOMWindowInlines.h"
+#include "nsStringFwd.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nscore.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+extern mozilla::LazyLogModule gDocumentChannelLog;
+#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DocumentChannel::nsISupports
+
+NS_IMPL_ADDREF(DocumentChannel)
+NS_IMPL_RELEASE(DocumentChannel)
+
+NS_INTERFACE_MAP_BEGIN(DocumentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIIdentChannel)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequest)
+NS_INTERFACE_MAP_END
+
+DocumentChannel::DocumentChannel(nsDocShellLoadState* aLoadState,
+ net::LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey,
+ bool aUriModified,
+ bool aIsEmbeddingBlockedError)
+ : mLoadState(aLoadState),
+ mCacheKey(aCacheKey),
+ mLoadFlags(aLoadFlags),
+ mURI(aLoadState->URI()),
+ mLoadInfo(aLoadInfo),
+ mUriModified(aUriModified),
+ mIsEmbeddingBlockedError(aIsEmbeddingBlockedError) {
+ LOG(("DocumentChannel ctor [this=%p, uri=%s]", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ uint64_t channelId;
+ Unused << handler->NewChannelId(channelId);
+ mChannelId = channelId;
+}
+
+NS_IMETHODIMP
+DocumentChannel::AsyncOpen(nsIStreamListener* aListener) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void DocumentChannel::ShutdownListeners(nsresult aStatusCode) {
+ LOG(("DocumentChannel ShutdownListeners [this=%p, status=%" PRIx32 "]", this,
+ static_cast<uint32_t>(aStatusCode)));
+ mStatus = aStatusCode;
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ if (listener) {
+ listener->OnStartRequest(this);
+ }
+
+ mIsPending = false;
+
+ listener = mListener; // it might have changed!
+ nsCOMPtr<nsILoadGroup> loadGroup = mLoadGroup;
+
+ mListener = nullptr;
+ mLoadGroup = nullptr;
+ mCallbacks = nullptr;
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DocumentChannel::ShutdownListeners", [=, self = RefPtr{this}] {
+ if (listener) {
+ listener->OnStopRequest(self, aStatusCode);
+ }
+
+ if (loadGroup) {
+ loadGroup->RemoveRequest(self, nullptr, aStatusCode);
+ }
+ }));
+
+ DeleteIPDL();
+}
+
+void DocumentChannel::DisconnectChildListeners(
+ const nsresult& aStatus, const nsresult& aLoadGroupStatus) {
+ MOZ_ASSERT(NS_FAILED(aStatus));
+ mStatus = aLoadGroupStatus;
+ // Make sure we remove from the load group before
+ // setting mStatus, as existing tests expect the
+ // status to be successful when we disconnect.
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ mLoadGroup = nullptr;
+ }
+
+ ShutdownListeners(aStatus);
+}
+
+nsDocShell* DocumentChannel::GetDocShell() {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ if (!loadContext) {
+ return nullptr;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return nullptr;
+ }
+ auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
+ nsIDocShell* docshell = pDomWindow->GetDocShell();
+ return nsDocShell::Cast(docshell);
+}
+
+static bool URIUsesDocChannel(nsIURI* aURI) {
+ if (SchemeIsJavascript(aURI)) {
+ return false;
+ }
+
+ nsCString spec = aURI->GetSpecOrDefault();
+ return !spec.EqualsLiteral("about:crashcontent");
+}
+
+bool DocumentChannel::CanUseDocumentChannel(nsIURI* aURI) {
+ // We want to use DocumentChannel if we're using a supported scheme.
+ return URIUsesDocChannel(aURI);
+}
+
+/* static */
+already_AddRefed<DocumentChannel> DocumentChannel::CreateForDocument(
+ nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks,
+ uint32_t aCacheKey, bool aUriModified, bool aIsEmbeddingBlockedError) {
+ RefPtr<DocumentChannel> channel;
+ if (XRE_IsContentProcess()) {
+ channel =
+ new DocumentChannelChild(aLoadState, aLoadInfo, aLoadFlags, aCacheKey,
+ aUriModified, aIsEmbeddingBlockedError);
+ } else {
+ channel = new ParentProcessDocumentChannel(
+ aLoadState, aLoadInfo, aLoadFlags, aCacheKey, aUriModified,
+ aIsEmbeddingBlockedError);
+ }
+ channel->SetNotificationCallbacks(aNotificationCallbacks);
+ return channel.forget();
+}
+
+/* static */
+already_AddRefed<DocumentChannel> DocumentChannel::CreateForObject(
+ nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks) {
+ return CreateForDocument(aLoadState, aLoadInfo, aLoadFlags,
+ aNotificationCallbacks, 0, false, false);
+}
+
+NS_IMETHODIMP DocumentChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP DocumentChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP DocumentChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+DocumentChannel::Cancel(nsresult aStatusCode) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+DocumentChannel::Suspend() {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+DocumentChannel::Resume() {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// Remainder of nsIRequest/nsIChannel.
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP DocumentChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks(mCallbacks);
+ callbacks.forget(aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ nsCOMPtr<nsILoadGroup> loadGroup(mLoadGroup);
+ loadGroup.forget(aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::GetStatus(nsresult* aStatus) {
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::GetName(nsACString& aResult) {
+ if (!mURI) {
+ aResult.Truncate();
+ return NS_OK;
+ }
+ nsCString spec;
+ nsresult rv = mURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AssignLiteral("documentchannel:");
+ aResult.Append(spec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::IsPending(bool* aResult) {
+ *aResult = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+DocumentChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP DocumentChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ // Setting load flags for TYPE_OBJECT is OK, so long as the channel to parent
+ // isn't opened yet, or we're only setting the `LOAD_DOCUMENT_URI` flag.
+ auto contentPolicy = mLoadInfo->GetExternalContentPolicyType();
+ if (contentPolicy == ExtContentPolicy::TYPE_OBJECT) {
+ if (mWasOpened) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aLoadFlags == (mLoadFlags | nsIChannel::LOAD_DOCUMENT_URI),
+ "After the channel has been opened, can only set the "
+ "`LOAD_DOCUMENT_URI` flag.");
+ }
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+ }
+
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "DocumentChannel::SetLoadFlags: Don't set flags after creation "
+ "(differing flags %x != %x)",
+ (mLoadFlags ^ aLoadFlags) & mLoadFlags,
+ (mLoadFlags ^ aLoadFlags) & aLoadFlags);
+}
+
+NS_IMETHODIMP DocumentChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ nsCOMPtr<nsIURI> originalURI =
+ mLoadState->OriginalURI() ? mLoadState->OriginalURI() : mLoadState->URI();
+ originalURI.forget(aOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri(mURI);
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::GetOwner(nsISupports** aOwner) {
+ nsCOMPtr<nsISupports> owner(mOwner);
+ owner.forget(aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::SetOwner(nsISupports* aOwner) {
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::GetContentType(nsACString& aContentType) {
+ // We may be trying to load HTML object data, and have determined that we're
+ // going to be performing a document load. In that case, fake the "text/html"
+ // content type for nsObjectLoadingContent.
+ if ((mLoadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA) &&
+ (mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
+ aContentType = TEXT_HTML;
+ return NS_OK;
+ }
+
+ NS_ERROR("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::SetContentType(const nsACString& aContentType) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetContentCharset(nsACString& aContentCharset) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::SetContentCharset(
+ const nsACString& aContentCharset) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetContentLength(int64_t* aContentLength) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::SetContentLength(int64_t aContentLength) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::Open(nsIInputStream** aStream) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetContentDisposition(
+ uint32_t* aContentDisposition) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::SetContentDisposition(
+ uint32_t aContentDisposition) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ MOZ_CRASH("If we get here, something will be broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ MOZ_CRASH("If we get here, something will be broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ nsCOMPtr<nsILoadInfo> loadInfo(mLoadInfo);
+ loadInfo.forget(aLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_CRASH("If we get here, something is broken");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DocumentChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP DocumentChannel::GetCanceled(bool* aCanceled) {
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIIdentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+DocumentChannel::GetChannelId(uint64_t* aChannelId) {
+ *aChannelId = mChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentChannel::SetChannelId(uint64_t aChannelId) {
+ mChannelId = aChannelId;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+uint64_t InnerWindowIDForExtantDoc(nsDocShell* docShell) {
+ if (!docShell) {
+ return 0;
+ }
+
+ Document* doc = docShell->GetExtantDocument();
+ if (!doc) {
+ return 0;
+ }
+
+ return doc->InnerWindowID();
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/ipc/DocumentChannel.h b/netwerk/ipc/DocumentChannel.h
new file mode 100644
index 0000000000..f6a75d4f1b
--- /dev/null
+++ b/netwerk/ipc/DocumentChannel.h
@@ -0,0 +1,120 @@
+/* -*- 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_DocumentChannel_h
+#define mozilla_net_DocumentChannel_h
+
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIChannel.h"
+#include "nsIChildChannel.h"
+
+class nsDocShell;
+
+#define DOCUMENT_CHANNEL_IID \
+ { \
+ 0x6977bc44, 0xb1db, 0x41b7, { \
+ 0xb5, 0xc5, 0xe2, 0x13, 0x68, 0x22, 0xc9, 0x8f \
+ } \
+ }
+
+namespace mozilla {
+namespace net {
+
+uint64_t InnerWindowIDForExtantDoc(nsDocShell* docShell);
+
+/**
+ * DocumentChannel is a protocol agnostic placeholder nsIChannel implementation
+ * that we use so that nsDocShell knows about a connecting load. It transfers
+ * all data into a DocumentLoadListener (running in the parent process), which
+ * will create the real channel for the connection, and decide which process to
+ * load the resulting document in. If the document is to be loaded in the
+ * current process, then we'll synthesize a redirect replacing this placeholder
+ * channel with the real one, otherwise the originating docshell will be removed
+ * during the process switch.
+ */
+class DocumentChannel : public nsIIdentChannel {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIIDENTCHANNEL
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(DOCUMENT_CHANNEL_IID)
+
+ void SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
+ mTiming = aTiming;
+ }
+
+ void SetInitialClientInfo(const Maybe<dom::ClientInfo>& aInfo) {
+ mInitialClientInfo = aInfo;
+ }
+
+ void DisconnectChildListeners(const nsresult& aStatus,
+ const nsresult& aLoadGroupStatus);
+
+ /**
+ * Will create the appropriate document channel:
+ * Either a DocumentChannelChild if called from the content process or
+ * a ParentProcessDocumentChannel if called from the parent process.
+ * This operation is infallible.
+ */
+ static already_AddRefed<DocumentChannel> CreateForDocument(
+ nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks,
+ uint32_t aCacheKey, bool aUriModified, bool aIsEmbeddingBlockedError);
+ static already_AddRefed<DocumentChannel> CreateForObject(
+ nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks);
+
+ static bool CanUseDocumentChannel(nsIURI* aURI);
+
+ protected:
+ DocumentChannel(nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, bool aUriModified,
+ bool aIsEmbeddingBlockedError);
+
+ void ShutdownListeners(nsresult aStatusCode);
+ virtual void DeleteIPDL() {}
+
+ nsDocShell* GetDocShell();
+
+ virtual ~DocumentChannel() = default;
+
+ const RefPtr<nsDocShellLoadState> mLoadState;
+ const uint32_t mCacheKey;
+
+ nsresult mStatus = NS_OK;
+ bool mCanceled = false;
+ bool mIsPending = false;
+ bool mWasOpened = false;
+ uint64_t mChannelId;
+ uint32_t mLoadFlags = LOAD_NORMAL;
+ const nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mOwner;
+ RefPtr<nsDOMNavigationTiming> mTiming;
+ Maybe<dom::ClientInfo> mInitialClientInfo;
+ // mUriModified is true if we're doing a history load and the URI of the
+ // session history had been modified by pushState/replaceState.
+ bool mUriModified = false;
+ // mIsEmbeddingBlockedError is true if we're handling a load error and the
+ // status of the failed channel is NS_ERROR_XFO_VIOLATION or
+ // NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION.
+ bool mIsEmbeddingBlockedError = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DocumentChannel, DOCUMENT_CHANNEL_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DocumentChannel_h
diff --git a/netwerk/ipc/DocumentChannelChild.cpp b/netwerk/ipc/DocumentChannelChild.cpp
new file mode 100644
index 0000000000..ead243eaa2
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelChild.cpp
@@ -0,0 +1,483 @@
+/* -*- 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 "DocumentChannelChild.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/RemoteType.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "nsHashPropertyBag.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIXULRuntime.h"
+#include "nsIWritablePropertyBag.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsQueryObject.h"
+#include "nsDocShellLoadState.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+extern mozilla::LazyLogModule gDocumentChannelLog;
+#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DocumentChannelChild::nsISupports
+
+NS_INTERFACE_MAP_BEGIN(DocumentChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+NS_INTERFACE_MAP_END_INHERITING(DocumentChannel)
+
+NS_IMPL_ADDREF_INHERITED(DocumentChannelChild, DocumentChannel)
+NS_IMPL_RELEASE_INHERITED(DocumentChannelChild, DocumentChannel)
+
+DocumentChannelChild::DocumentChannelChild(nsDocShellLoadState* aLoadState,
+ net::LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags,
+ uint32_t aCacheKey,
+ bool aUriModified,
+ bool aIsEmbeddingBlockedError)
+ : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey,
+ aUriModified, aIsEmbeddingBlockedError) {
+ mLoadingContext = nullptr;
+ LOG(("DocumentChannelChild ctor [this=%p, uri=%s]", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+}
+
+DocumentChannelChild::~DocumentChannelChild() {
+ LOG(("DocumentChannelChild dtor [this=%p]", this));
+}
+
+NS_IMETHODIMP
+DocumentChannelChild::AsyncOpen(nsIStreamListener* aListener) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+
+ NS_ENSURE_TRUE(gNeckoChild, NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately, as we've done since before e10s.
+ rv = NS_CheckPortSafety(mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isNotDownload = mLoadState->FileName().IsVoid();
+
+ // If not a download, add ourselves to the load group
+ if (isNotDownload && mLoadGroup) {
+ // During this call, we can re-enter back into the DocumentChannelChild to
+ // call SetNavigationTiming.
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ if (mCanceled) {
+ // We may have been canceled already, either by on-modify-request
+ // listeners or by load group observers; in that case, don't create IPDL
+ // connection. See nsHttpChannel::AsyncOpen().
+ return mStatus;
+ }
+
+ gHttpHandler->OnOpeningDocumentRequest(this);
+
+ RefPtr<nsDocShell> docShell = GetDocShell();
+ if (!docShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // `loadingContext` is the BC that is initiating the resource load.
+ // For normal subdocument loads, the BC is the one that the subdoc will load
+ // into. For <object>/<embed> it's the embedder doc's BC.
+ RefPtr<BrowsingContext> loadingContext = docShell->GetBrowsingContext();
+ if (!loadingContext || loadingContext->IsDiscarded()) {
+ return NS_ERROR_FAILURE;
+ }
+ mLoadingContext = loadingContext;
+
+ Maybe<IPCClientInfo> ipcClientInfo;
+ if (mInitialClientInfo.isSome()) {
+ ipcClientInfo.emplace(mInitialClientInfo.ref().ToIPC());
+ }
+
+ DocumentChannelElementCreationArgs ipcElementCreationArgs;
+ switch (mLoadInfo->GetExternalContentPolicyType()) {
+ case ExtContentPolicy::TYPE_DOCUMENT:
+ case ExtContentPolicy::TYPE_SUBDOCUMENT: {
+ DocumentCreationArgs docArgs;
+ docArgs.uriModified() = mUriModified;
+ docArgs.isEmbeddingBlockedError() = mIsEmbeddingBlockedError;
+
+ ipcElementCreationArgs = docArgs;
+ break;
+ }
+
+ case ExtContentPolicy::TYPE_OBJECT: {
+ ObjectCreationArgs objectArgs;
+ objectArgs.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell);
+ objectArgs.loadFlags() = mLoadFlags;
+ objectArgs.contentPolicyType() = mLoadInfo->InternalContentPolicyType();
+ objectArgs.isUrgentStart() = UserActivation::IsHandlingUserInput();
+
+ ipcElementCreationArgs = objectArgs;
+ break;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("unsupported content policy type");
+ return NS_ERROR_FAILURE;
+ }
+
+ switch (mLoadInfo->GetExternalContentPolicyType()) {
+ case ExtContentPolicy::TYPE_DOCUMENT:
+ case ExtContentPolicy::TYPE_SUBDOCUMENT:
+ MOZ_ALWAYS_SUCCEEDS(loadingContext->SetCurrentLoadIdentifier(
+ Some(mLoadState->GetLoadIdentifier())));
+ break;
+
+ default:
+ break;
+ }
+
+ mLoadState->AssertProcessCouldTriggerLoadIfSystem();
+
+ DocumentChannelCreationArgs args(
+ mozilla::WrapNotNull(mLoadState), TimeStamp::Now(), mChannelId, mCacheKey,
+ mTiming, ipcClientInfo, ipcElementCreationArgs,
+ loadingContext->GetParentInitiatedNavigationEpoch());
+
+ gNeckoChild->SendPDocumentChannelConstructor(this, loadingContext, args);
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = listener;
+
+ return NS_OK;
+}
+
+IPCResult DocumentChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) {
+ if (aStatusCode == NS_ERROR_RECURSIVE_DOCUMENT_LOAD) {
+ // This exists so that we are able to fire an error event
+ // for when there are too many recursive iframe or object loads.
+ // This is an incomplete solution, because right now we don't have a unified
+ // way of firing error events due to errors in document channel.
+ // This should be fixed in bug 1629201.
+ MOZ_DIAGNOSTIC_ASSERT(mLoadingContext);
+ if (RefPtr<Element> embedder = mLoadingContext->GetEmbedderElement()) {
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedder)) {
+ if (RefPtr<nsFrameLoader> fl = flo->GetFrameLoader()) {
+ fl->FireErrorEvent();
+ }
+ }
+ }
+ }
+ ShutdownListeners(aStatusCode);
+ return IPC_OK();
+}
+
+IPCResult DocumentChannelChild::RecvDisconnectChildListeners(
+ const nsresult& aStatus, const nsresult& aLoadGroupStatus,
+ bool aContinueNavigating) {
+ // If this disconnect is not due to a process switch, perform the disconnect
+ // immediately.
+ if (!aContinueNavigating) {
+ DisconnectChildListeners(aStatus, aLoadGroupStatus);
+ return IPC_OK();
+ }
+
+ // Otherwise, the disconnect will occur later using some other mechanism,
+ // depending on what's happening to the loading DocShell. If this is a
+ // toplevel navigation, and this BrowsingContext enters the BFCache, we will
+ // cancel this channel when the PageHide event is firing, whereas if it does
+ // not enter BFCache (e.g. due to being an object, subframe or non-bfcached
+ // toplevel navigation), we will cancel this channel when the DocShell is
+ // destroyed.
+ nsDocShell* shell = GetDocShell();
+ if (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT &&
+ shell) {
+ MOZ_ASSERT(shell->GetBrowsingContext()->IsTop());
+ if (mozilla::SessionHistoryInParent() &&
+ shell->GetBrowsingContext()->IsInBFCache()) {
+ DisconnectChildListeners(aStatus, aLoadGroupStatus);
+ } else {
+ // Tell the DocShell which channel to cancel if it enters the BFCache.
+ shell->SetChannelToDisconnectOnPageHide(mChannelId);
+ }
+ }
+
+ return IPC_OK();
+}
+
+IPCResult DocumentChannelChild::RecvRedirectToRealChannel(
+ RedirectToRealChannelArgs&& aArgs,
+ nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints,
+ RedirectToRealChannelResolver&& aResolve) {
+ LOG(("DocumentChannelChild RecvRedirectToRealChannel [this=%p, uri=%s]", this,
+ aArgs.uri()->GetSpecOrDefault().get()));
+
+ // The document that created the cspToInherit.
+ // This is used when deserializing LoadInfo from the parent
+ // process, since we can't serialize Documents directly.
+ // TODO: For a fission OOP iframe this will be unavailable,
+ // as will the loadingContext computed in LoadInfoArgsToLoadInfo.
+ // Figure out if we need these for cross-origin subdocs.
+ RefPtr<dom::Document> cspToInheritLoadingDocument;
+ nsCOMPtr<nsIContentSecurityPolicy> policy = mLoadState->Csp();
+ if (policy) {
+ nsWeakPtr ctx =
+ static_cast<nsCSPContext*>(policy.get())->GetLoadingContext();
+ cspToInheritLoadingDocument = do_QueryReferent(ctx);
+ }
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(aArgs.loadInfo(), NOT_REMOTE_TYPE,
+ cspToInheritLoadingDocument,
+ getter_AddRefs(loadInfo)));
+
+ mRedirectResolver = std::move(aResolve);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ MOZ_ASSERT((aArgs.loadStateInternalLoadFlags() &
+ nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC) ||
+ aArgs.srcdocData().IsVoid());
+ nsresult rv = nsDocShell::CreateRealChannelForDocument(
+ getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr,
+ aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri());
+ if (newChannel) {
+ newChannel->SetLoadGroup(mLoadGroup);
+ }
+
+ if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) {
+ httpChannel->SetEarlyHints(std::move(aArgs.earlyHints()));
+ httpChannel->SetEarlyHintLinkType(aArgs.earlyHintLinkType());
+ }
+
+ // This is used to report any errors back to the parent by calling
+ // CrossProcessRedirectFinished.
+ auto scopeExit = MakeScopeExit([&]() {
+ mRedirectResolver(rv);
+ mRedirectResolver = nullptr;
+ });
+
+ if (NS_FAILED(rv)) {
+ return IPC_OK();
+ }
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel)) {
+ rv = httpChannel->SetChannelId(aArgs.channelId());
+ }
+ if (NS_FAILED(rv)) {
+ return IPC_OK();
+ }
+
+ rv = newChannel->SetOriginalURI(aArgs.originalURI());
+ if (NS_FAILED(rv)) {
+ return IPC_OK();
+ }
+
+ if (nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(newChannel)) {
+ rv = httpChannelInternal->SetRedirectMode(aArgs.redirectMode());
+ }
+ if (NS_FAILED(rv)) {
+ return IPC_OK();
+ }
+
+ newChannel->SetNotificationCallbacks(mCallbacks);
+
+ if (aArgs.init()) {
+ HttpBaseChannel::ReplacementChannelConfig config(*aArgs.init());
+ HttpBaseChannel::ConfigureReplacementChannel(
+ newChannel, config,
+ HttpBaseChannel::ReplacementReason::DocumentChannel);
+ }
+
+ if (aArgs.contentDisposition()) {
+ newChannel->SetContentDisposition(*aArgs.contentDisposition());
+ }
+
+ if (aArgs.contentDispositionFilename()) {
+ newChannel->SetContentDispositionFilename(
+ *aArgs.contentDispositionFilename());
+ }
+
+ nsDocShell* docShell = GetDocShell();
+ if (docShell && aArgs.loadingSessionHistoryInfo().isSome()) {
+ docShell->SetLoadingSessionHistoryInfo(
+ aArgs.loadingSessionHistoryInfo().ref());
+ }
+
+ // transfer any properties. This appears to be entirely a content-side
+ // interface and isn't copied across to the parent. Copying the values
+ // for this from this into the new actor will work, since the parent
+ // won't have the right details anyway.
+ // TODO: What about the process switch equivalent
+ // (ContentChild::RecvCrossProcessRedirect)? In that case there is no local
+ // existing actor in the destination process... We really need all information
+ // to go up to the parent, and then come down to the new child actor.
+ if (nsCOMPtr<nsIWritablePropertyBag> bag = do_QueryInterface(newChannel)) {
+ nsHashPropertyBag::CopyFrom(bag, aArgs.properties());
+ }
+
+ // connect parent.
+ nsCOMPtr<nsIChildChannel> childChannel = do_QueryInterface(newChannel);
+ if (childChannel) {
+ rv = childChannel->ConnectParent(
+ aArgs.registrarId()); // creates parent channel
+ if (NS_FAILED(rv)) {
+ return IPC_OK();
+ }
+ }
+ mRedirectChannel = newChannel;
+ mStreamFilterEndpoints = std::move(aEndpoints);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel,
+ aArgs.redirectFlags(),
+ GetMainThreadSerialEventTarget());
+
+ if (NS_SUCCEEDED(rv)) {
+ scopeExit.release();
+ }
+
+ // scopeExit will call CrossProcessRedirectFinished(rv) here
+ return IPC_OK();
+}
+
+IPCResult DocumentChannelChild::RecvUpgradeObjectLoad(
+ UpgradeObjectLoadResolver&& aResolve) {
+ // We're doing a load for an <object> or <embed> element if we got here.
+ MOZ_ASSERT(mLoadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA,
+ "Should have LOAD_HTML_OBJECT_DATA set");
+ MOZ_ASSERT(!(mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI),
+ "Shouldn't be a LOAD_DOCUMENT_URI load yet");
+ MOZ_ASSERT(mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_OBJECT,
+ "Should have the TYPE_OBJECT content policy type");
+
+ // If our load has already failed, or been cancelled, abort this attempt to
+ // upgade the load.
+ if (NS_FAILED(mStatus)) {
+ aResolve(nullptr);
+ return IPC_OK();
+ }
+
+ nsCOMPtr<nsIObjectLoadingContent> loadingContent;
+ NS_QueryNotificationCallbacks(this, loadingContent);
+ if (!loadingContent) {
+ return IPC_FAIL(this, "Channel is not for ObjectLoadingContent!");
+ }
+
+ // We're upgrading to a document channel now. Add the LOAD_DOCUMENT_URI flag
+ // after-the-fact.
+ mLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
+
+ RefPtr<BrowsingContext> browsingContext;
+ nsresult rv = loadingContent->UpgradeLoadToDocument(
+ this, getter_AddRefs(browsingContext));
+ if (NS_FAILED(rv) || !browsingContext) {
+ // Oops! Looks like something went wrong, so let's bail out.
+ mLoadFlags &= ~nsIChannel::LOAD_DOCUMENT_URI;
+ aResolve(nullptr);
+ return IPC_OK();
+ }
+
+ aResolve(browsingContext);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode) {
+ LOG(
+ ("DocumentChannelChild OnRedirectVerifyCallback [this=%p, "
+ "aRv=0x%08" PRIx32 " ]",
+ this, static_cast<uint32_t>(aStatusCode)));
+ nsCOMPtr<nsIChannel> redirectChannel = std::move(mRedirectChannel);
+ RedirectToRealChannelResolver redirectResolver = std::move(mRedirectResolver);
+
+ // If we've already shut down, then just notify the parent that
+ // we're done.
+ if (NS_FAILED(mStatus)) {
+ redirectChannel->SetNotificationCallbacks(nullptr);
+ redirectResolver(aStatusCode);
+ return NS_OK;
+ }
+
+ nsresult rv = aStatusCode;
+ if (NS_SUCCEEDED(rv)) {
+ if (nsCOMPtr<nsIChildChannel> childChannel =
+ do_QueryInterface(redirectChannel)) {
+ rv = childChannel->CompleteRedirectSetup(mListener);
+ } else {
+ rv = redirectChannel->AsyncOpen(mListener);
+ }
+ } else {
+ redirectChannel->SetNotificationCallbacks(nullptr);
+ }
+
+ for (auto& endpoint : mStreamFilterEndpoints) {
+ extensions::StreamFilterParent::Attach(redirectChannel,
+ std::move(endpoint));
+ }
+
+ redirectResolver(rv);
+
+ if (NS_FAILED(rv)) {
+ ShutdownListeners(rv);
+ return NS_OK;
+ }
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED);
+ }
+ mCallbacks = nullptr;
+ mListener = nullptr;
+
+ // This calls NeckoChild::DeallocPDocumentChannel(), which deletes |this| if
+ // IPDL holds the last reference. Don't rely on |this| existing after here!
+ if (CanSend()) {
+ Send__delete__(this);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentChannelChild::Cancel(nsresult aStatusCode) {
+ return CancelWithReason(aStatusCode, "DocumentChannelChild::Cancel"_ns);
+}
+
+NS_IMETHODIMP DocumentChannelChild::CancelWithReason(
+ nsresult aStatusCode, const nsACString& aReason) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ if (CanSend()) {
+ SendCancel(aStatusCode, aReason);
+ }
+
+ ShutdownListeners(aStatusCode);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/ipc/DocumentChannelChild.h b/netwerk/ipc/DocumentChannelChild.h
new file mode 100644
index 0000000000..7b2461cbb6
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelChild.h
@@ -0,0 +1,76 @@
+/* -*- 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_DocumentChannelChild_h
+#define mozilla_net_DocumentChannelChild_h
+
+#include "mozilla/net/PDocumentChannelChild.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/dom/nsCSPContext.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ * DocumentChannelChild is an implementation of DocumentChannel for nsDocShells
+ * in the content process, that uses PDocumentChannel to serialize everything
+ * across IPDL to the parent process.
+ */
+class DocumentChannelChild final : public DocumentChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public PDocumentChannelChild {
+ public:
+ DocumentChannelChild(nsDocShellLoadState* aLoadState,
+ class LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags,
+ uint32_t aCacheKey, bool aUriModified,
+ bool aIsEmbeddingBlockedError);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ NS_IMETHOD Cancel(nsresult aStatusCode) override;
+ NS_IMETHOD CancelWithReason(nsresult aStatusCode,
+ const nsACString& aReason) override;
+
+ mozilla::ipc::IPCResult RecvFailedAsyncOpen(const nsresult& aStatusCode);
+
+ mozilla::ipc::IPCResult RecvDisconnectChildListeners(
+ const nsresult& aStatus, const nsresult& aLoadGroupStatus,
+ bool aSwitchedProcess);
+
+ mozilla::ipc::IPCResult RecvDeleteSelf();
+
+ mozilla::ipc::IPCResult RecvRedirectToRealChannel(
+ RedirectToRealChannelArgs&& aArgs,
+ nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints,
+ RedirectToRealChannelResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvUpgradeObjectLoad(
+ UpgradeObjectLoadResolver&& aResolve);
+
+ private:
+ void DeleteIPDL() override {
+ if (CanSend()) {
+ Send__delete__(this);
+ }
+ }
+
+ ~DocumentChannelChild();
+
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+
+ RedirectToRealChannelResolver mRedirectResolver;
+ nsTArray<Endpoint<extensions::PStreamFilterParent>> mStreamFilterEndpoints;
+ dom::BrowsingContext* mLoadingContext;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DocumentChannelChild_h
diff --git a/netwerk/ipc/DocumentChannelParent.cpp b/netwerk/ipc/DocumentChannelParent.cpp
new file mode 100644
index 0000000000..a360f76bcb
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelParent.cpp
@@ -0,0 +1,160 @@
+/* -*- 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 "DocumentChannelParent.h"
+
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsDocShellLoadState.h"
+
+extern mozilla::LazyLogModule gDocumentChannelLog;
+#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+DocumentChannelParent::DocumentChannelParent() {
+ LOG(("DocumentChannelParent ctor [this=%p]", this));
+}
+
+DocumentChannelParent::~DocumentChannelParent() {
+ LOG(("DocumentChannelParent dtor [this=%p]", this));
+}
+
+bool DocumentChannelParent::Init(dom::CanonicalBrowsingContext* aContext,
+ const DocumentChannelCreationArgs& aArgs) {
+ RefPtr<nsDocShellLoadState> loadState = aArgs.loadState();
+ LOG(("DocumentChannelParent Init [this=%p, uri=%s]", this,
+ loadState->URI()->GetSpecOrDefault().get()));
+ if (aArgs.parentInitiatedNavigationEpoch() <
+ aContext->GetParentInitiatedNavigationEpoch()) {
+ nsresult rv = NS_BINDING_ABORTED;
+ return SendFailedAsyncOpen(rv);
+ }
+
+ ContentParent* contentParent =
+ static_cast<ContentParent*>(Manager()->Manager());
+
+ RefPtr<DocumentLoadListener::OpenPromise> promise;
+ if (loadState->GetChannelInitialized()) {
+ promise = DocumentLoadListener::ClaimParentLoad(
+ getter_AddRefs(mDocumentLoadListener), loadState->GetLoadIdentifier(),
+ Some(aArgs.channelId()));
+ }
+ if (!promise) {
+ bool isDocumentLoad =
+ aArgs.elementCreationArgs().type() ==
+ DocumentChannelElementCreationArgs::TDocumentCreationArgs;
+ mDocumentLoadListener = new DocumentLoadListener(aContext, isDocumentLoad);
+
+ Maybe<ClientInfo> clientInfo;
+ if (aArgs.initialClientInfo().isSome()) {
+ clientInfo.emplace(ClientInfo(aArgs.initialClientInfo().ref()));
+ }
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ if (isDocumentLoad) {
+ const DocumentCreationArgs& docArgs = aArgs.elementCreationArgs();
+
+ promise = mDocumentLoadListener->OpenDocument(
+ loadState, aArgs.cacheKey(), Some(aArgs.channelId()),
+ aArgs.asyncOpenTime(), aArgs.timing(), std::move(clientInfo),
+ Some(docArgs.uriModified()), Some(docArgs.isEmbeddingBlockedError()),
+ contentParent, &rv);
+ } else {
+ const ObjectCreationArgs& objectArgs = aArgs.elementCreationArgs();
+
+ promise = mDocumentLoadListener->OpenObject(
+ loadState, aArgs.cacheKey(), Some(aArgs.channelId()),
+ aArgs.asyncOpenTime(), aArgs.timing(), std::move(clientInfo),
+ objectArgs.embedderInnerWindowId(), objectArgs.loadFlags(),
+ objectArgs.contentPolicyType(), objectArgs.isUrgentStart(),
+ contentParent, this /* ObjectUpgradeHandler */, &rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(!promise);
+ return SendFailedAsyncOpen(rv);
+ }
+ }
+
+ RefPtr<DocumentChannelParent> self = this;
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) {
+ // The DLL is waiting for us to resolve the
+ // PDocumentChannel::RedirectToRealChannelPromise given as parameter.
+ auto promise = self->RedirectToRealChannel(
+ std::move(aResolveValue.mStreamFilterEndpoints),
+ aResolveValue.mRedirectFlags, aResolveValue.mLoadFlags,
+ aResolveValue.mEarlyHintLinkType);
+ // We chain the promise the DLL is waiting on to the one returned by
+ // RedirectToRealChannel. As soon as the promise returned is resolved
+ // or rejected, so will the DLL's promise.
+ promise->ChainTo(aResolveValue.mPromise.forget(), __func__);
+ self->mDocumentLoadListener = nullptr;
+ },
+ [self](DocumentLoadListener::OpenPromiseFailedType&& aRejectValue) {
+ if (self->CanSend()) {
+ Unused << self->SendDisconnectChildListeners(
+ aRejectValue.mStatus, aRejectValue.mLoadGroupStatus,
+ aRejectValue.mContinueNavigating);
+ }
+ self->mDocumentLoadListener = nullptr;
+ });
+
+ return true;
+}
+
+auto DocumentChannelParent::UpgradeObjectLoad()
+ -> RefPtr<ObjectUpgradePromise> {
+ return SendUpgradeObjectLoad()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](const UpgradeObjectLoadPromise::ResolveOrRejectValue& aValue) {
+ if (!aValue.IsResolve() || aValue.ResolveValue().IsNullOrDiscarded()) {
+ LOG(("DocumentChannelParent object load upgrade failed"));
+ return ObjectUpgradePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ return ObjectUpgradePromise::CreateAndResolve(
+ aValue.ResolveValue().get_canonical(), __func__);
+ });
+}
+
+RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+DocumentChannelParent::RedirectToRealChannel(
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags, uint32_t aEarlyHintLinkType) {
+ if (!CanSend()) {
+ return PDocumentChannelParent::RedirectToRealChannelPromise::
+ CreateAndReject(ResponseRejectReason::ChannelClosed, __func__);
+ }
+
+ ContentParent* cp = static_cast<ContentParent*>(Manager()->Manager());
+ nsTArray<EarlyHintConnectArgs> earlyHints;
+ mDocumentLoadListener->RegisterEarlyHintLinksAndGetConnectArgs(cp->ChildID(),
+ earlyHints);
+
+ RedirectToRealChannelArgs args;
+ mDocumentLoadListener->SerializeRedirectData(
+ args, false, aRedirectFlags, aLoadFlags, std::move(earlyHints),
+ aEarlyHintLinkType);
+ return SendRedirectToRealChannel(args, std::move(aStreamFilterEndpoints));
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/ipc/DocumentChannelParent.h b/netwerk/ipc/DocumentChannelParent.h
new file mode 100644
index 0000000000..7537c95295
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelParent.h
@@ -0,0 +1,68 @@
+/* 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_DocumentChannelParent_h
+#define mozilla_net_DocumentChannelParent_h
+
+#include "mozilla/net/DocumentLoadListener.h"
+#include "mozilla/net/PDocumentChannelParent.h"
+
+namespace mozilla {
+namespace dom {
+class CanonicalBrowsingContext;
+}
+namespace net {
+
+class EarlyHintConnectArgs;
+
+/**
+ * An actor that forwards all changes across to DocumentChannelChild, the
+ * nsIChannel implementation owned by a content process docshell.
+ */
+class DocumentChannelParent final
+ : public PDocumentChannelParent,
+ public DocumentLoadListener::ObjectUpgradeHandler {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(DocumentChannelParent, override);
+
+ explicit DocumentChannelParent();
+
+ bool Init(dom::CanonicalBrowsingContext* aContext,
+ const DocumentChannelCreationArgs& aArgs);
+
+ // PDocumentChannelParent
+ ipc::IPCResult RecvCancel(const nsresult& aStatus, const nsCString& aReason) {
+ if (mDocumentLoadListener) {
+ mDocumentLoadListener->Cancel(aStatus, aReason);
+ }
+ return IPC_OK();
+ }
+ void ActorDestroy(ActorDestroyReason aWhy) override {
+ if (mDocumentLoadListener) {
+ mDocumentLoadListener->Cancel(NS_BINDING_ABORTED,
+ "DocumentChannelParent::ActorDestroy"_ns);
+ }
+ }
+
+ private:
+ RefPtr<ObjectUpgradePromise> UpgradeObjectLoad() override;
+
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ RedirectToRealChannel(
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ uint32_t aEarlyHintLinkType);
+
+ virtual ~DocumentChannelParent();
+
+ RefPtr<DocumentLoadListener> mDocumentLoadListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DocumentChannelParent_h
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
new file mode 100644
index 0000000000..ca1f59e884
--- /dev/null
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -0,0 +1,3027 @@
+/* -*- 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 "DocumentLoadListener.h"
+
+#include "NeckoCommon.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_extensions.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ChildProcessChannelListener.h"
+#include "mozilla/dom/ClientChannelHelper.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/ProcessIsolation.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "nsContentSecurityUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsDSURIContentListener.h"
+#include "nsObjectLoadingContent.h"
+#include "nsOpenWindowInfo.h"
+#include "nsExternalHelperAppService.h"
+#include "nsHttpChannel.h"
+#include "nsIBrowser.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIStreamConverterService.h"
+#include "nsIViewSourceChannel.h"
+#include "nsImportModule.h"
+#include "nsIXULRuntime.h"
+#include "nsMimeTypes.h"
+#include "nsQueryObject.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsSandboxFlags.h"
+#include "nsSHistory.h"
+#include "nsStringStream.h"
+#include "nsURILoader.h"
+#include "nsWebNavigationInfo.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/RemoteWebProgressRequest.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/ExtensionPolicyService.h"
+
+#ifdef ANDROID
+# include "mozilla/widget/nsWindow.h"
+#endif /* ANDROID */
+
+mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
+#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
+
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+// Bug 136580: Limit to the number of nested content frames that can have the
+// same URL. This is to stop content that is recursively loading
+// itself. Note that "#foo" on the end of URL doesn't affect
+// whether it's considered identical, but "?foo" or ";foo" are
+// considered and compared.
+// Limit this to 2, like chromium does.
+static constexpr int kMaxSameURLContentFrames = 2;
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+static ContentParentId GetContentProcessId(ContentParent* aContentParent) {
+ return aContentParent ? aContentParent->ChildID() : ContentParentId{0};
+}
+
+static void SetNeedToAddURIVisit(nsIChannel* aChannel,
+ bool aNeedToAddURIVisit) {
+ nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
+ if (!props) {
+ return;
+ }
+
+ props->SetPropertyAsBool(u"docshell.needToAddURIVisit"_ns,
+ aNeedToAddURIVisit);
+}
+
+static auto SecurityFlagsForLoadInfo(nsDocShellLoadState* aLoadState)
+ -> nsSecurityFlags {
+ // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+
+ if (aLoadState->LoadType() == LOAD_ERROR_PAGE) {
+ securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
+ }
+
+ if (aLoadState->PrincipalToInherit()) {
+ bool isSrcdoc = aLoadState->HasInternalLoadFlags(
+ nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC);
+ bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ aLoadState->PrincipalToInherit(), aLoadState->URI(),
+ true, // aInheritForAboutBlank
+ isSrcdoc);
+
+ bool isData = SchemeIsData(aLoadState->URI());
+ if (inheritAttrs && !isData) {
+ securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+ }
+
+ return securityFlags;
+}
+
+// Construct a LoadInfo object to use when creating the internal channel for a
+// Document/SubDocument load.
+static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState)
+ -> already_AddRefed<LoadInfo> {
+ uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();
+ RefPtr<LoadInfo> loadInfo;
+
+ auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
+
+ if (aBrowsingContext->GetParent()) {
+ loadInfo = LoadInfo::CreateForFrame(
+ aBrowsingContext, aLoadState->TriggeringPrincipal(),
+ aLoadState->GetEffectiveTriggeringRemoteType(), securityFlags,
+ sandboxFlags);
+ } else {
+ OriginAttributes attrs;
+ aBrowsingContext->GetOriginAttributes(attrs);
+ loadInfo = LoadInfo::CreateForDocument(
+ aBrowsingContext, aLoadState->URI(), aLoadState->TriggeringPrincipal(),
+ aLoadState->GetEffectiveTriggeringRemoteType(), attrs, securityFlags,
+ sandboxFlags);
+ }
+
+ if (aLoadState->IsExemptFromHTTPSFirstMode()) {
+ uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
+ httpsOnlyStatus |= nsILoadInfo::HTTPS_FIRST_EXEMPT_NEXT_LOAD;
+ loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
+ }
+
+ loadInfo->SetWasSchemelessInput(aLoadState->GetWasSchemelessInput());
+
+ loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
+ loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
+ loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
+ loadInfo->SetHasValidUserGestureActivation(
+ aLoadState->HasValidUserGestureActivation());
+ loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
+
+ return loadInfo.forget();
+}
+
+// Construct a LoadInfo object to use when creating the internal channel for an
+// Object/Embed load.
+static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState,
+ uint64_t aInnerWindowId,
+ nsContentPolicyType aContentPolicyType,
+ uint32_t aSandboxFlags)
+ -> already_AddRefed<LoadInfo> {
+ RefPtr<WindowGlobalParent> wgp =
+ WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
+ MOZ_RELEASE_ASSERT(wgp);
+
+ auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
+
+ RefPtr<LoadInfo> loadInfo = LoadInfo::CreateForNonDocument(
+ wgp, wgp->DocumentPrincipal(), aContentPolicyType, securityFlags,
+ aSandboxFlags);
+
+ loadInfo->SetHasValidUserGestureActivation(
+ aLoadState->HasValidUserGestureActivation());
+ loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
+ loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
+ loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
+ loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
+
+ return loadInfo.forget();
+}
+
+/**
+ * An extension to nsDocumentOpenInfo that we run in the parent process, so
+ * that we can make the decision to retarget to content handlers or the external
+ * helper app, before we make process switching decisions.
+ *
+ * This modifies the behaviour of nsDocumentOpenInfo so that it can do
+ * retargeting, but doesn't do stream conversion (but confirms that we will be
+ * able to do so later).
+ *
+ * We still run nsDocumentOpenInfo in the content process, but disable
+ * retargeting, so that it can only apply stream conversion, and then send data
+ * to the docshell.
+ */
+class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
+ public nsIMultiPartChannelListener {
+ public:
+ ParentProcessDocumentOpenInfo(ParentChannelListener* aListener,
+ uint32_t aFlags,
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ bool aIsDocumentLoad)
+ : nsDocumentOpenInfo(aFlags, false),
+ mBrowsingContext(aBrowsingContext),
+ mListener(aListener),
+ mIsDocumentLoad(aIsDocumentLoad) {
+ LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this));
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The default content listener is always a docshell, so this manually
+ // implements the same checks, and if it succeeds, uses the parent
+ // channel listener so that we forward onto DocumentLoadListener.
+ bool TryDefaultContentListener(nsIChannel* aChannel,
+ const nsCString& aContentType) {
+ uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(aContentType);
+ if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) {
+ m_targetStreamListener = mListener;
+ nsLoadFlags loadFlags = 0;
+ aChannel->GetLoadFlags(&loadFlags);
+ aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED);
+ return true;
+ }
+ return false;
+ }
+
+ bool TryDefaultContentListener(nsIChannel* aChannel) override {
+ return TryDefaultContentListener(aChannel, mContentType);
+ }
+
+ // Generally we only support stream converters that can tell
+ // use exactly what type they'll output. If we find one, then
+ // we just target to our default listener directly (without
+ // conversion), and the content process nsDocumentOpenInfo will
+ // run and do the actual conversion.
+ nsresult TryStreamConversion(nsIChannel* aChannel) override {
+ // The one exception is nsUnknownDecoder, which works in the parent
+ // (and we need to know what the content type is before we can
+ // decide if it will be handled in the parent), so we run that here.
+ if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE) ||
+ mContentType.IsEmpty()) {
+ return nsDocumentOpenInfo::TryStreamConversion(aChannel);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> streamConvService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ nsAutoCString str;
+ rv = streamConvService->ConvertedType(mContentType, aChannel, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We only support passing data to the default content listener
+ // (docshell), and we don't supported chaining converters.
+ if (TryDefaultContentListener(aChannel, str)) {
+ mContentType = str;
+ return NS_OK;
+ }
+ // This is the same result as nsStreamConverterService uses when it
+ // can't find a converter
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService,
+ nsIChannel* aChannel) override {
+ RefPtr<nsIStreamListener> listener;
+ nsresult rv = aHelperAppService->CreateListener(
+ mContentType, aChannel, mBrowsingContext, false, nullptr,
+ getter_AddRefs(listener));
+ if (NS_SUCCEEDED(rv)) {
+ m_targetStreamListener = listener;
+ }
+ return rv;
+ }
+
+ nsDocumentOpenInfo* Clone() override {
+ mCloned = true;
+ return new ParentProcessDocumentOpenInfo(mListener, mFlags,
+ mBrowsingContext, mIsDocumentLoad);
+ }
+
+ nsresult OnDocumentStartRequest(nsIRequest* request) {
+ LOG(("ParentProcessDocumentOpenInfo OnDocumentStartRequest [this=%p]",
+ this));
+
+ nsresult rv = nsDocumentOpenInfo::OnStartRequest(request);
+
+ // If we didn't find a content handler,
+ // and we don't have a listener, then just forward to our
+ // default listener. This happens when the channel is in
+ // an error state, and we want to just forward that on to be
+ // handled in the content process.
+ if (NS_SUCCEEDED(rv) && !mUsedContentHandler && !m_targetStreamListener) {
+ m_targetStreamListener = mListener;
+ return m_targetStreamListener->OnStartRequest(request);
+ }
+ if (m_targetStreamListener != mListener) {
+ LOG(
+ ("ParentProcessDocumentOpenInfo targeted to non-default listener "
+ "[this=%p]",
+ this));
+ // If this is the only part, then we can immediately tell our listener
+ // that it won't be getting any content and disconnect it. For multipart
+ // channels we have to wait until we've handled all parts before we know.
+ // This does mean that the content process can still Cancel() a multipart
+ // response while the response is being handled externally, but this
+ // matches the single-process behaviour.
+ // If we got cloned, then we don't need to do this, as only the last link
+ // needs to do it.
+ // Multi-part channels are guaranteed to call OnAfterLastPart, which we
+ // forward to the listeners, so it will handle disconnection at that
+ // point.
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
+ do_QueryInterface(request);
+ if (!multiPartChannel && !mCloned) {
+ DisconnectChildListeners(NS_FAILED(rv) ? rv : NS_BINDING_RETARGETED,
+ rv);
+ }
+ }
+ return rv;
+ }
+
+ nsresult OnObjectStartRequest(nsIRequest* request) {
+ LOG(("ParentProcessDocumentOpenInfo OnObjectStartRequest [this=%p]", this));
+ // Just redirect to the nsObjectLoadingContent in the content process.
+ m_targetStreamListener = mListener;
+ return m_targetStreamListener->OnStartRequest(request);
+ }
+
+ NS_IMETHOD OnStartRequest(nsIRequest* request) override {
+ LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this));
+
+ if (mIsDocumentLoad) {
+ return OnDocumentStartRequest(request);
+ }
+
+ return OnObjectStartRequest(request);
+ }
+
+ NS_IMETHOD OnAfterLastPart(nsresult aStatus) override {
+ mListener->OnAfterLastPart(aStatus);
+ return NS_OK;
+ }
+
+ private:
+ virtual ~ParentProcessDocumentOpenInfo() {
+ LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this));
+ }
+
+ void DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupStatus) {
+ // Tell the DocumentLoadListener to notify the content process that it's
+ // been entirely retargeted, and to stop waiting.
+ // Clear mListener's pointer to the DocumentLoadListener to break the
+ // reference cycle.
+ RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
+ MOZ_ASSERT(doc);
+ doc->DisconnectListeners(aStatus, aLoadGroupStatus);
+ mListener->SetListenerAfterRedirect(nullptr);
+ }
+
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+ RefPtr<ParentChannelListener> mListener;
+ const bool mIsDocumentLoad;
+
+ /**
+ * Set to true if we got cloned to create a chained listener.
+ */
+ bool mCloned = false;
+};
+
+NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
+NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
+
+NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo)
+ NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
+NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo)
+
+NS_IMPL_ADDREF(DocumentLoadListener)
+NS_IMPL_RELEASE(DocumentLoadListener)
+
+NS_INTERFACE_MAP_BEGIN(DocumentLoadListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener)
+NS_INTERFACE_MAP_END
+
+DocumentLoadListener::DocumentLoadListener(
+ CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad)
+ : mIsDocumentLoad(aIsDocumentLoad) {
+ LOG(("DocumentLoadListener ctor [this=%p]", this));
+ mParentChannelListener =
+ new ParentChannelListener(this, aLoadingBrowsingContext);
+}
+
+DocumentLoadListener::~DocumentLoadListener() {
+ LOG(("DocumentLoadListener dtor [this=%p]", this));
+}
+
+void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
+ uint32_t aLoadFlags) {
+ if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
+ mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+ if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
+ previousURI = uri;
+ } else {
+ nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
+ &previousFlags);
+ }
+
+ // Get the HTTP response code, if available.
+ uint32_t responseStatus = 0;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ Unused << httpChannel->GetResponseStatus(&responseStatus);
+ }
+
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ GetDocumentBrowsingContext();
+ nsCOMPtr<nsIWidget> widget =
+ browsingContext->GetParentProcessWidgetContaining();
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ // Check if the URI has a http scheme and if either HSTS is enabled for this
+ // host, or if we were upgraded by HTTPS-Only/First. If this is the case, we
+ // want to mark this URI specially, as it will be followed shortly by an
+ // almost identical https history entry. That way the global history
+ // implementation can handle the visit appropriately (e.g. by marking it as
+ // `hidden`, so only the https url appears in default history views).
+ bool wasUpgraded =
+ uri->SchemeIs("http") &&
+ (loadInfo->GetHstsStatus() ||
+ (loadInfo->GetHttpsOnlyStatus() &
+ (nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST |
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED |
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED)));
+
+ nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
+ responseStatus, browsingContext, widget,
+ mLoadStateLoadType, wasUpgraded);
+}
+
+CanonicalBrowsingContext* DocumentLoadListener::GetLoadingBrowsingContext()
+ const {
+ return mParentChannelListener ? mParentChannelListener->GetBrowsingContext()
+ : nullptr;
+}
+
+CanonicalBrowsingContext* DocumentLoadListener::GetDocumentBrowsingContext()
+ const {
+ return mIsDocumentLoad ? GetLoadingBrowsingContext() : nullptr;
+}
+
+CanonicalBrowsingContext* DocumentLoadListener::GetTopBrowsingContext() const {
+ auto* loadingContext = GetLoadingBrowsingContext();
+ return loadingContext ? loadingContext->Top() : nullptr;
+}
+
+WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const {
+ return mParentWindowContext;
+}
+
+bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext,
+ nsDocShellLoadState* aLoadState, bool aIsDocumentLoad) {
+ // Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs.
+ // srcdoc URIs require their contents to be specified inline, so it isn't
+ // possible for undesirable recursion to occur without the aid of a
+ // non-srcdoc URI, which this method will block normally.
+ // Besides, URI is not enough to guarantee uniqueness of srcdoc documents.
+ nsAutoCString buffer;
+ if (aLoadState->URI()->SchemeIs("about")) {
+ nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) {
+ // Duplicates allowed up to depth limits
+ return true;
+ }
+ }
+
+ RefPtr<WindowGlobalParent> parent;
+ if (!aIsDocumentLoad) { // object load
+ parent = aLoadingContext->GetCurrentWindowGlobal();
+ } else {
+ parent = aLoadingContext->GetParentWindowContext();
+ }
+
+ int matchCount = 0;
+ CanonicalBrowsingContext* ancestorBC;
+ for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP;
+ ancestorWGP = ancestorBC->GetParentWindowContext()) {
+ ancestorBC = ancestorWGP->BrowsingContext();
+ MOZ_ASSERT(ancestorBC);
+ if (nsCOMPtr<nsIURI> parentURI = ancestorWGP->GetDocumentURI()) {
+ bool equal;
+ nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (equal) {
+ matchCount++;
+ if (matchCount >= kMaxSameURLContentFrames) {
+ NS_WARNING(
+ "Too many nested content frames/objects have the same url "
+ "(recursion?) "
+ "so giving up");
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+// Check that the load state, potentially received from a child process, appears
+// to be performing a load of the specified LoadingSessionHistoryInfo.
+// Returns a Result<…> containing the SessionHistoryEntry found for the
+// LoadingSessionHistoryInfo as success value if the validation succeeded, or a
+// static (telemetry-safe) string naming what did not match as a failure value
+// if the validation failed.
+static Result<SessionHistoryEntry*, const char*> ValidateHistoryLoad(
+ CanonicalBrowsingContext* aLoadingContext,
+ nsDocShellLoadState* aLoadState) {
+ MOZ_ASSERT(SessionHistoryInParent());
+ MOZ_ASSERT(aLoadState->LoadIsFromSessionHistory());
+
+ if (!aLoadState->GetLoadingSessionHistoryInfo()) {
+ return Err("Missing LoadingSessionHistoryInfo");
+ }
+
+ SessionHistoryEntry::LoadingEntry* loading = SessionHistoryEntry::GetByLoadId(
+ aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
+ if (!loading) {
+ return Err("Missing SessionHistoryEntry");
+ }
+
+ SessionHistoryInfo* snapshot = loading->mInfoSnapshotForValidation.get();
+ // History loads do not inherit principal.
+ if (aLoadState->HasInternalLoadFlags(
+ nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) {
+ return Err("LOAD_FLAGS_INHERIT_PRINCIPAL");
+ }
+
+ auto uriEq = [](nsIURI* a, nsIURI* b) -> bool {
+ bool eq = false;
+ return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq);
+ };
+ auto principalEq = [](nsIPrincipal* a, nsIPrincipal* b) -> bool {
+ return a == b || (a && b && a->Equals(b));
+ };
+
+ // XXX: Needing to do all of this validation manually is kinda gross.
+ if (!uriEq(snapshot->GetURI(), aLoadState->URI())) {
+ return Err("URI");
+ }
+ if (!uriEq(snapshot->GetOriginalURI(), aLoadState->OriginalURI())) {
+ return Err("OriginalURI");
+ }
+ if (!aLoadState->ResultPrincipalURIIsSome() ||
+ !uriEq(snapshot->GetResultPrincipalURI(),
+ aLoadState->ResultPrincipalURI())) {
+ return Err("ResultPrincipalURI");
+ }
+ if (!uriEq(snapshot->GetUnstrippedURI(), aLoadState->GetUnstrippedURI())) {
+ return Err("UnstrippedURI");
+ }
+ if (!principalEq(snapshot->GetTriggeringPrincipal(),
+ aLoadState->TriggeringPrincipal())) {
+ return Err("TriggeringPrincipal");
+ }
+ if (!principalEq(snapshot->GetPrincipalToInherit(),
+ aLoadState->PrincipalToInherit())) {
+ return Err("PrincipalToInherit");
+ }
+ if (!principalEq(snapshot->GetPartitionedPrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit())) {
+ return Err("PartitionedPrincipalToInherit");
+ }
+
+ // Everything matches!
+ return loading->mEntry;
+}
+
+auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
+ LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags,
+ uint32_t aCacheKey,
+ const Maybe<uint64_t>& aChannelId,
+ const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming,
+ Maybe<ClientInfo>&& aInfo, bool aUrgentStart,
+ dom::ContentParent* aContentParent,
+ nsresult* aRv) -> RefPtr<OpenPromise> {
+ auto* loadingContext = GetLoadingBrowsingContext();
+
+ MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(),
+ loadingContext->GetParentWindowContext());
+
+ OriginAttributes attrs;
+ loadingContext->GetOriginAttributes(attrs);
+
+ mLoadIdentifier = aLoadState->GetLoadIdentifier();
+ // See description of mFileName in nsDocShellLoadState.h
+ mIsDownload = !aLoadState->FileName().IsVoid();
+ mIsLoadingJSURI = net::SchemeIsJavascript(aLoadState->URI());
+
+ // Check for infinite recursive object or iframe loads
+ if (aLoadState->OriginalFrameSrc() || !mIsDocumentLoad) {
+ if (!CheckRecursiveLoad(loadingContext, aLoadState, mIsDocumentLoad)) {
+ *aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD;
+ mParentChannelListener = nullptr;
+ return nullptr;
+ }
+ }
+
+ auto* documentContext = GetDocumentBrowsingContext();
+
+ // If we are using SHIP and this load is from session history, validate that
+ // the load matches our local copy of the loading history entry.
+ //
+ // NOTE: Keep this check in-sync with the check in
+ // `nsDocShellLoadState::GetEffectiveTriggeringRemoteType()`!
+ RefPtr<SessionHistoryEntry> existingEntry;
+ if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() &&
+ aLoadState->LoadType() != LOAD_ERROR_PAGE) {
+ Result<SessionHistoryEntry*, const char*> result =
+ ValidateHistoryLoad(loadingContext, aLoadState);
+ if (result.isErr()) {
+ const char* mismatch = result.unwrapErr();
+ LOG(
+ ("DocumentLoadListener::Open with invalid loading history entry "
+ "[this=%p, mismatch=%s]",
+ this, mismatch));
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "DocumentLoadListener::Open for invalid history entry due to "
+ "mismatch of '%s'",
+ mismatch);
+#endif
+ *aRv = NS_ERROR_DOM_SECURITY_ERR;
+ mParentChannelListener = nullptr;
+ return nullptr;
+ }
+
+ existingEntry = result.unwrap();
+ if (!existingEntry->IsInSessionHistory() &&
+ !documentContext->HasLoadingHistoryEntry(existingEntry)) {
+ SessionHistoryEntry::RemoveLoadId(
+ aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
+ LOG(
+ ("DocumentLoadListener::Open with disconnected history entry "
+ "[this=%p]",
+ this));
+
+ *aRv = NS_BINDING_ABORTED;
+ mParentChannelListener = nullptr;
+ mChannel = nullptr;
+ return nullptr;
+ }
+ }
+
+ if (aLoadState->GetRemoteTypeOverride()) {
+ if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) ||
+ !loadingContext->IsTopContent()) {
+ LOG(
+ ("DocumentLoadListener::Open with invalid remoteTypeOverride "
+ "[this=%p]",
+ this));
+ *aRv = NS_ERROR_DOM_SECURITY_ERR;
+ mParentChannelListener = nullptr;
+ return nullptr;
+ }
+
+ mRemoteTypeOverride = aLoadState->GetRemoteTypeOverride();
+ }
+
+ if (NS_WARN_IF(!loadingContext->IsOwnedByProcess(
+ GetContentProcessId(aContentParent)))) {
+ LOG(
+ ("DocumentLoadListener::Open called from non-current content process "
+ "[this=%p, current=%" PRIu64 ", caller=%" PRIu64 "]",
+ this, loadingContext->OwnerProcessId(),
+ uint64_t(GetContentProcessId(aContentParent))));
+ *aRv = NS_BINDING_ABORTED;
+ mParentChannelListener = nullptr;
+ return nullptr;
+ }
+
+ if (mIsDocumentLoad && loadingContext->IsContent() &&
+ NS_WARN_IF(loadingContext->IsReplaced())) {
+ LOG(
+ ("DocumentLoadListener::Open called from replaced BrowsingContext "
+ "[this=%p, browserid=%" PRIx64 ", bcid=%" PRIx64 "]",
+ this, loadingContext->BrowserId(), loadingContext->Id()));
+ *aRv = NS_BINDING_ABORTED;
+ mParentChannelListener = nullptr;
+ return nullptr;
+ }
+
+ if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
+ loadingContext, aLoadState, aLoadInfo, mParentChannelListener,
+ nullptr, attrs, aLoadFlags, aCacheKey, *aRv,
+ getter_AddRefs(mChannel))) {
+ LOG(("DocumentLoadListener::Open failed to create channel [this=%p]",
+ this));
+ mParentChannelListener = nullptr;
+ return nullptr;
+ }
+
+ if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
+ mozilla::SessionHistoryInParent()) {
+ // It's hard to know at this point whether session history will be enabled
+ // in the browsing context, so we always create an entry for a load here.
+ mLoadingSessionHistoryInfo =
+ documentContext->CreateLoadingSessionHistoryEntryForLoad(
+ aLoadState, existingEntry, mChannel);
+ MOZ_ASSERT(mLoadingSessionHistoryInfo);
+ }
+
+ nsCOMPtr<nsIURI> uriBeingLoaded;
+ Unused << NS_WARN_IF(
+ NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded))));
+
+ RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
+ if (uriBeingLoaded && httpBaseChannel) {
+ nsCOMPtr<nsIURI> topWindowURI;
+ if (mIsDocumentLoad && loadingContext->IsTop()) {
+ // If this is for the top level loading, the top window URI should be the
+ // URI which we are loading.
+ topWindowURI = uriBeingLoaded;
+ } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils::
+ GetTopWindowExcludingExtensionAccessibleContentFrames(
+ loadingContext, uriBeingLoaded)) {
+ nsCOMPtr<nsIPrincipal> topWindowPrincipal =
+ topWindow->DocumentPrincipal();
+ if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
+ auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
+ basePrin->GetURI(getter_AddRefs(topWindowURI));
+ }
+ }
+ httpBaseChannel->SetTopWindowURI(topWindowURI);
+ }
+
+ nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
+ if (identChannel && aChannelId) {
+ Unused << identChannel->SetChannelId(*aChannelId);
+ }
+ mDocumentChannelId = aChannelId;
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(this);
+
+ if (mIsDocumentLoad && loadingContext->IsTop()) {
+ httpChannelImpl->SetEarlyHintObserver(this);
+ }
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
+ if (timedChannel) {
+ timedChannel->SetAsyncOpen(aAsyncOpenTime);
+ }
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
+ Unused << httpChannel->SetRequestContextID(
+ loadingContext->GetRequestContextId());
+
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChannel));
+ if (cos && aUrgentStart) {
+ cos->AddClassFlags(nsIClassOfService::UrgentStart);
+ }
+ }
+
+ // Setup a ClientChannelHelper to watch for redirects, and copy
+ // across any serviceworker related data between channels as needed.
+ AddClientChannelHelperInParent(mChannel, std::move(aInfo));
+
+ if (documentContext && !documentContext->StartDocumentLoad(this)) {
+ LOG(("DocumentLoadListener::Open failed StartDocumentLoad [this=%p]",
+ this));
+ *aRv = NS_BINDING_ABORTED;
+ mParentChannelListener = nullptr;
+ mChannel = nullptr;
+ return nullptr;
+ }
+
+ // Recalculate the openFlags, matching the logic in use in Content process.
+ // NOTE: The only case not handled here to mirror Content process is
+ // redirecting to re-use the channel.
+ MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel());
+ uint32_t openFlags =
+ nsDocShell::ComputeURILoaderFlags(loadingContext, aLoadState->LoadType());
+
+ RefPtr<ParentProcessDocumentOpenInfo> openInfo =
+ new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
+ loadingContext, mIsDocumentLoad);
+ openInfo->Prepare();
+
+#ifdef ANDROID
+ RefPtr<MozPromise<bool, bool, false>> promise;
+ if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
+ !(aLoadState->HasInternalLoadFlags(
+ nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
+ !(aLoadState->LoadType() & LOAD_HISTORY)) {
+ nsCOMPtr<nsIWidget> widget =
+ documentContext->GetParentProcessWidgetContaining();
+ RefPtr<nsWindow> window = nsWindow::From(widget);
+
+ if (window) {
+ promise = window->OnLoadRequest(
+ aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
+ aLoadState->InternalLoadFlags(), aLoadState->TriggeringPrincipal(),
+ aLoadState->HasValidUserGestureActivation(),
+ documentContext->IsTopContent());
+ }
+ }
+
+ if (promise) {
+ RefPtr<DocumentLoadListener> self = this;
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ bool handled = aValue.ResolveValue();
+ if (handled) {
+ self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
+ mParentChannelListener = nullptr;
+ } else {
+ nsresult rv = mChannel->AsyncOpen(openInfo);
+ if (NS_FAILED(rv)) {
+ self->DisconnectListeners(rv, rv);
+ mParentChannelListener = nullptr;
+ }
+ }
+ }
+ });
+ } else
+#endif /* ANDROID */
+ {
+ *aRv = mChannel->AsyncOpen(openInfo);
+ if (NS_FAILED(*aRv)) {
+ LOG(("DocumentLoadListener::Open failed AsyncOpen [this=%p rv=%" PRIx32
+ "]",
+ this, static_cast<uint32_t>(*aRv)));
+ if (documentContext) {
+ documentContext->EndDocumentLoad(false);
+ }
+ mParentChannelListener = nullptr;
+ return nullptr;
+ }
+ }
+
+ // HTTPS-Only Mode fights potential timeouts caused by upgrades. Instantly
+ // after opening the document channel we have to kick off countermeasures.
+ nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(this);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ loadInfo->SetChannelCreationOriginalURI(aLoadState->URI());
+
+ mContentParent = aContentParent;
+ mLoadStateExternalLoadFlags = aLoadState->LoadFlags();
+ mLoadStateInternalLoadFlags = aLoadState->InternalLoadFlags();
+ mLoadStateLoadType = aLoadState->LoadType();
+ mTiming = aTiming;
+ mSrcdocData = aLoadState->SrcdocData();
+ mBaseURI = aLoadState->BaseURI();
+ mOriginalUriString = aLoadState->GetOriginalURIString();
+ if (documentContext) {
+ mParentWindowContext = documentContext->GetParentWindowContext();
+ } else {
+ mParentWindowContext =
+ WindowGlobalParent::GetByInnerWindowId(aLoadInfo->GetInnerWindowID());
+ MOZ_RELEASE_ASSERT(mParentWindowContext->GetBrowsingContext() ==
+ GetLoadingBrowsingContext(),
+ "mismatched parent window context?");
+ }
+
+ // For content-initiated loads, this flag is set in nsDocShell::LoadURI.
+ // For parent-initiated loads, we have to set it here.
+ // Below comment is copied from nsDocShell::LoadURI -
+ // If we have a system triggering principal, we can assume that this load was
+ // triggered by some UI in the browser chrome, such as the URL bar or
+ // bookmark bar. This should count as a user interaction for the current sh
+ // entry, so that the user may navigate back to the current entry, from the
+ // entry that is going to be added as part of this load.
+ if (!mSupportsRedirectToRealChannel && aLoadState->TriggeringPrincipal() &&
+ aLoadState->TriggeringPrincipal()->IsSystemPrincipal()) {
+ WindowContext* topWc = loadingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true));
+ }
+ }
+
+ *aRv = NS_OK;
+ mOpenPromise = new OpenPromise::Private(__func__);
+ // We make the promise use direct task dispatch in order to reduce the number
+ // of event loops iterations.
+ mOpenPromise->UseDirectTaskDispatch(__func__);
+ return mOpenPromise;
+}
+
+auto DocumentLoadListener::OpenDocument(
+ nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
+ const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
+ Maybe<bool> aUriModified, Maybe<bool> aIsEmbeddingBlockedError,
+ dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr<OpenPromise> {
+ LOG(("DocumentLoadListener [%p] OpenDocument [uri=%s]", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ MOZ_ASSERT(mIsDocumentLoad);
+
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ GetDocumentBrowsingContext();
+
+ // If this is a top-level load, then rebuild the LoadInfo from scratch,
+ // since the goal is to be able to initiate loads in the parent, where the
+ // content process won't have provided us with an existing one.
+ RefPtr<LoadInfo> loadInfo =
+ CreateDocumentLoadInfo(browsingContext, aLoadState);
+
+ nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
+ browsingContext, std::move(aUriModified),
+ std::move(aIsEmbeddingBlockedError));
+
+ // Keep track of navigation for the Bounce Tracking Protection.
+ if (browsingContext->IsTopContent()) {
+ RefPtr<BounceTrackingState> bounceTrackingState =
+ browsingContext->GetBounceTrackingState();
+
+ // Not every browsing context has a BounceTrackingState. It's also null when
+ // the feature is disabled.
+ if (bounceTrackingState) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ nsresult rv =
+ loadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
+
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ DebugOnly<nsresult> rv = bounceTrackingState->OnStartNavigation(
+ triggeringPrincipal, loadInfo->GetHasValidUserGestureActivation());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "BounceTrackingState::OnStartNavigation failed");
+ }
+ }
+ }
+
+ return Open(aLoadState, loadInfo, loadFlags, aCacheKey, aChannelId,
+ aAsyncOpenTime, aTiming, std::move(aInfo), false, aContentParent,
+ aRv);
+}
+
+auto DocumentLoadListener::OpenObject(
+ nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
+ const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
+ uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
+ nsContentPolicyType aContentPolicyType, bool aUrgentStart,
+ dom::ContentParent* aContentParent,
+ ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv)
+ -> RefPtr<OpenPromise> {
+ LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ MOZ_ASSERT(!mIsDocumentLoad);
+
+ auto sandboxFlags = aLoadState->TriggeringSandboxFlags();
+
+ RefPtr<LoadInfo> loadInfo = CreateObjectLoadInfo(
+ aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags);
+
+ mObjectUpgradeHandler = aObjectUpgradeHandler;
+
+ return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId,
+ aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart,
+ aContentParent, aRv);
+}
+
+auto DocumentLoadListener::OpenInParent(nsDocShellLoadState* aLoadState,
+ bool aSupportsRedirectToRealChannel)
+ -> RefPtr<OpenPromise> {
+ MOZ_ASSERT(mIsDocumentLoad);
+
+ // We currently only support passing nullptr for aLoadInfo for
+ // top level browsing contexts.
+ auto* browsingContext = GetDocumentBrowsingContext();
+ if (!browsingContext->IsTopContent() ||
+ !browsingContext->GetContentParent()) {
+ LOG(("DocumentLoadListener::OpenInParent failed because of subdoc"));
+ return nullptr;
+ }
+
+ // Clone because this mutates the load flags in the load state, which
+ // breaks nsDocShells expectations of being able to do it.
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState);
+ loadState->CalculateLoadURIFlags();
+
+ RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
+ timing->NotifyNavigationStart(
+ browsingContext->IsActive()
+ ? nsDOMNavigationTiming::DocShellState::eActive
+ : nsDOMNavigationTiming::DocShellState::eInactive);
+
+ const mozilla::dom::LoadingSessionHistoryInfo* loadingInfo =
+ loadState->GetLoadingSessionHistoryInfo();
+
+ uint32_t cacheKey = 0;
+ auto loadType = aLoadState->LoadType();
+ if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
+ if (loadingInfo) {
+ cacheKey = loadingInfo->mInfo.GetCacheKey();
+ }
+ }
+
+ // Loads start in the content process might have exposed a channel id to
+ // observers, so we need to preserve the value in the parent. That can't have
+ // happened here, so Nothing() is fine.
+ Maybe<uint64_t> channelId = Nothing();
+
+ // Initial client info is only relevant for subdocument loads, which we're
+ // not supporting yet.
+ Maybe<dom::ClientInfo> initialClientInfo;
+
+ mSupportsRedirectToRealChannel = aSupportsRedirectToRealChannel;
+
+ // This is a top-level load, so rebuild the LoadInfo from scratch,
+ // since in the parent the
+ // content process won't have provided us with an existing one.
+ RefPtr<LoadInfo> loadInfo =
+ CreateDocumentLoadInfo(browsingContext, aLoadState);
+
+ nsLoadFlags loadFlags = loadState->CalculateChannelLoadFlags(
+ browsingContext,
+ Some(loadingInfo && loadingInfo->mInfo.GetURIWasModified()), Nothing());
+
+ nsresult rv;
+ return Open(loadState, loadInfo, loadFlags, cacheKey, channelId,
+ TimeStamp::Now(), timing, std::move(initialClientInfo), false,
+ browsingContext->GetContentParent(), &rv);
+}
+
+base::ProcessId DocumentLoadListener::OtherPid() const {
+ return mContentParent ? mContentParent->OtherPid() : base::ProcessId{0};
+}
+
+void DocumentLoadListener::FireStateChange(uint32_t aStateFlags,
+ nsresult aStatus) {
+ nsCOMPtr<nsIChannel> request = GetChannel();
+
+ RefPtr<BrowsingContextWebProgress> webProgress =
+ GetLoadingBrowsingContext()->GetWebProgress();
+
+ if (webProgress) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() {
+ webProgress->OnStateChange(webProgress, request, aStateFlags,
+ aStatus);
+ }));
+ }
+}
+
+static void SetNavigating(CanonicalBrowsingContext* aBrowsingContext,
+ bool aNavigating) {
+ nsCOMPtr<nsIBrowser> browser;
+ if (RefPtr<Element> currentElement = aBrowsingContext->GetEmbedderElement()) {
+ browser = currentElement->AsBrowser();
+ }
+
+ if (!browser) {
+ return;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DocumentLoadListener::SetNavigating",
+ [browser, aNavigating]() { browser->SetIsNavigating(aNavigating); }));
+}
+
+/* static */ bool DocumentLoadListener::LoadInParent(
+ CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
+ bool aSetNavigating) {
+ SetNavigating(aBrowsingContext, aSetNavigating);
+
+ RefPtr<DocumentLoadListener> load =
+ new DocumentLoadListener(aBrowsingContext, true);
+ RefPtr<DocumentLoadListener::OpenPromise> promise = load->OpenInParent(
+ aLoadState, /* aSupportsRedirectToRealChannel */ false);
+ if (!promise) {
+ SetNavigating(aBrowsingContext, false);
+ return false;
+ }
+
+ // We passed false for aSupportsRedirectToRealChannel, so we should always
+ // take the process switching path, and reject this promise.
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [load](DocumentLoadListener::OpenPromise::ResolveOrRejectValue&& aValue) {
+ MOZ_ASSERT(aValue.IsReject());
+ DocumentLoadListener::OpenPromiseFailedType& rejectValue =
+ aValue.RejectValue();
+ if (!rejectValue.mContinueNavigating) {
+ // If we're not switching the load to a new process, then it is
+ // finished (and failed), and we should fire a state change to notify
+ // observers. Normally the docshell would fire this, and it would get
+ // filtered out by BrowserParent if needed.
+ load->FireStateChange(nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ rejectValue.mStatus);
+ }
+ });
+
+ load->FireStateChange(nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ NS_OK);
+ SetNavigating(aBrowsingContext, false);
+ return true;
+}
+
+/* static */
+bool DocumentLoadListener::SpeculativeLoadInParent(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState) {
+ LOG(("DocumentLoadListener::OpenFromParent"));
+
+ RefPtr<DocumentLoadListener> listener =
+ new DocumentLoadListener(aBrowsingContext, true);
+
+ auto promise = listener->OpenInParent(aLoadState, true);
+ if (promise) {
+ // Create an entry in the redirect channel registrar to
+ // allocate an identifier for this load.
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ uint64_t loadIdentifier = aLoadState->GetLoadIdentifier();
+ DebugOnly<nsresult> rv =
+ registrar->RegisterChannel(nullptr, loadIdentifier);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // Register listener (as an nsIParentChannel) under our new identifier.
+ rv = registrar->LinkChannels(loadIdentifier, listener, nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ return !!promise;
+}
+
+void DocumentLoadListener::CleanupParentLoadAttempt(uint64_t aLoadIdent) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
+ RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
+
+ if (loadListener) {
+ // If the load listener is still registered, then we must have failed
+ // to connect DocumentChannel into it. Better cancel it!
+ loadListener->NotifyDocumentChannelFailed();
+ }
+
+ registrar->DeregisterChannels(aLoadIdent);
+}
+
+auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener,
+ uint64_t aLoadIdent,
+ Maybe<uint64_t> aChannelId)
+ -> RefPtr<OpenPromise> {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
+ RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
+ registrar->DeregisterChannels(aLoadIdent);
+
+ if (!loadListener) {
+ // The parent went away unexpectedly.
+ *aListener = nullptr;
+ return nullptr;
+ }
+
+ loadListener->mDocumentChannelId = aChannelId;
+
+ MOZ_DIAGNOSTIC_ASSERT(loadListener->mOpenPromise);
+ loadListener.forget(aListener);
+
+ return (*aListener)->mOpenPromise;
+}
+
+void DocumentLoadListener::NotifyDocumentChannelFailed() {
+ LOG(("DocumentLoadListener NotifyDocumentChannelFailed [this=%p]", this));
+ // There's been no calls to ClaimParentLoad, and so no listeners have been
+ // attached to mOpenPromise yet. As such we can run Then() on it.
+ mOpenPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) {
+ aResolveValue.mPromise->Resolve(NS_BINDING_ABORTED, __func__);
+ },
+ []() {});
+
+ Cancel(NS_BINDING_ABORTED,
+ "DocumentLoadListener::NotifyDocumentChannelFailed"_ns);
+}
+
+void DocumentLoadListener::Disconnect(bool aContinueNavigating) {
+ LOG(("DocumentLoadListener Disconnect [this=%p, aContinueNavigating=%d]",
+ this, aContinueNavigating));
+ // The nsHttpChannel may have a reference to this parent, release it
+ // to avoid circular references.
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(nullptr);
+ httpChannelImpl->SetEarlyHintObserver(nullptr);
+ }
+
+ // Don't cancel ongoing early hints when continuing to load the web page.
+ // Early hints are loaded earlier in the code and shouldn't get cancelled
+ // here. See also: Bug 1765652
+ if (!aContinueNavigating) {
+ mEarlyHintsService.Cancel("DocumentLoadListener::Disconnect"_ns);
+ }
+
+ if (auto* ctx = GetDocumentBrowsingContext()) {
+ ctx->EndDocumentLoad(aContinueNavigating);
+ }
+}
+
+void DocumentLoadListener::Cancel(const nsresult& aStatusCode,
+ const nsACString& aReason) {
+ LOG(
+ ("DocumentLoadListener Cancel [this=%p, "
+ "aStatusCode=%" PRIx32 " ]",
+ this, static_cast<uint32_t>(aStatusCode)));
+ if (mOpenPromiseResolved) {
+ return;
+ }
+ if (mChannel) {
+ mChannel->CancelWithReason(aStatusCode, aReason);
+ }
+
+ DisconnectListeners(aStatusCode, aStatusCode);
+}
+
+void DocumentLoadListener::DisconnectListeners(nsresult aStatus,
+ nsresult aLoadGroupStatus,
+ bool aContinueNavigating) {
+ LOG(
+ ("DocumentLoadListener DisconnectListener [this=%p, "
+ "aStatus=%" PRIx32 ", aLoadGroupStatus=%" PRIx32
+ ", aContinueNavigating=%d]",
+ this, static_cast<uint32_t>(aStatus),
+ static_cast<uint32_t>(aLoadGroupStatus), aContinueNavigating));
+
+ RejectOpenPromise(aStatus, aLoadGroupStatus, aContinueNavigating, __func__);
+
+ Disconnect(aContinueNavigating);
+
+ // Clear any pending stream filter requests. If we're going to be sending a
+ // response to the content process due to a navigation, our caller will have
+ // already stashed the array to be passed to `TriggerRedirectToRealChannel`,
+ // so it's safe for us to clear here.
+ // TODO: If we retargeted the stream to a non-default handler (e.g. to trigger
+ // a download), we currently never attach a stream filter. Should we attach a
+ // stream filter in those situations as well?
+ mStreamFilterRequests.Clear();
+}
+
+void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) {
+ LOG(
+ ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, "
+ "aRv=%" PRIx32 " ]",
+ this, static_cast<uint32_t>(aRv)));
+ if (NS_FAILED(aRv)) {
+ FinishReplacementChannelSetup(aRv);
+ return;
+ }
+
+ // Wait for background channel ready on target channel
+ nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(redirectReg);
+
+ nsCOMPtr<nsIParentChannel> redirectParentChannel;
+ redirectReg->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectParentChannel));
+ if (!redirectParentChannel) {
+ FinishReplacementChannelSetup(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIParentRedirectingChannel> redirectingParent =
+ do_QueryInterface(redirectParentChannel);
+ if (!redirectingParent) {
+ // Continue verification procedure if redirecting to non-Http protocol
+ FinishReplacementChannelSetup(NS_OK);
+ return;
+ }
+
+ // Ask redirected channel if verification can proceed.
+ // ReadyToVerify will be invoked when redirected channel is ready.
+ redirectingParent->ContinueVerification(this);
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::ReadyToVerify(nsresult aResultCode) {
+ FinishReplacementChannelSetup(aResultCode);
+ return NS_OK;
+}
+
+void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
+ LOG(
+ ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, "
+ "aResult=%x]",
+ this, int(aResult)));
+
+ auto endDocumentLoad = MakeScopeExit([&]() {
+ if (auto* ctx = GetDocumentBrowsingContext()) {
+ ctx->EndDocumentLoad(false);
+ }
+ });
+ mStreamFilterRequests.Clear();
+
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ nsCOMPtr<nsIParentChannel> redirectChannel;
+ nsresult rv = registrar->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectChannel));
+ if (NS_FAILED(rv) || !redirectChannel) {
+ aResult = NS_ERROR_FAILURE;
+ }
+
+ // Release all previously registered channels, they are no longer needed to
+ // be kept in the registrar from this moment.
+ registrar->DeregisterChannels(mRedirectChannelId);
+ mRedirectChannelId = 0;
+ if (NS_FAILED(aResult)) {
+ if (redirectChannel) {
+ redirectChannel->Delete();
+ }
+ mChannel->Cancel(aResult);
+ mChannel->Resume();
+ return;
+ }
+
+ MOZ_ASSERT(
+ !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this)));
+
+ redirectChannel->SetParentListener(mParentChannelListener);
+
+ ApplyPendingFunctions(redirectChannel);
+
+ if (!ResumeSuspendedChannel(redirectChannel)) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ // We added ourselves to the load group, but attempting
+ // to resume has notified us that the channel is already
+ // finished. Better remove ourselves from the loadgroup
+ // again. The only time the channel will be in a loadgroup
+ // is if we're connected to the parent process.
+ nsresult status = NS_OK;
+ mChannel->GetStatus(&status);
+ loadGroup->RemoveRequest(mChannel, nullptr, status);
+ }
+ }
+}
+
+void DocumentLoadListener::ApplyPendingFunctions(
+ nsIParentChannel* aChannel) const {
+ // We stored the values from all nsIParentChannel functions called since we
+ // couldn't handle them. Copy them across to the real channel since it
+ // should know what to do.
+
+ nsCOMPtr<nsIParentChannel> parentChannel = aChannel;
+ for (const auto& variant : mIParentChannelFunctions) {
+ variant.match(
+ [parentChannel](const ClassifierMatchedInfoParams& aParams) {
+ parentChannel->SetClassifierMatchedInfo(
+ aParams.mList, aParams.mProvider, aParams.mFullHash);
+ },
+ [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) {
+ parentChannel->SetClassifierMatchedTrackingInfo(aParams.mLists,
+ aParams.mFullHashes);
+ },
+ [parentChannel](const ClassificationFlagsParams& aParams) {
+ parentChannel->NotifyClassificationFlags(aParams.mClassificationFlags,
+ aParams.mIsThirdParty);
+ });
+ }
+
+ RefPtr<HttpChannelSecurityWarningReporter> reporter;
+ if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) {
+ reporter = httpParent;
+ } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) {
+ reporter = httpChannel->GetWarningReporter();
+ }
+ if (reporter) {
+ for (const auto& variant : mSecurityWarningFunctions) {
+ variant.match(
+ [reporter](const ReportSecurityMessageParams& aParams) {
+ Unused << reporter->ReportSecurityMessage(aParams.mMessageTag,
+ aParams.mMessageCategory);
+ },
+ [reporter](const LogBlockedCORSRequestParams& aParams) {
+ Unused << reporter->LogBlockedCORSRequest(
+ aParams.mMessage, aParams.mCategory, aParams.mIsWarning);
+ },
+ [reporter](const LogMimeTypeMismatchParams& aParams) {
+ Unused << reporter->LogMimeTypeMismatch(
+ aParams.mMessageName, aParams.mWarning, aParams.mURL,
+ aParams.mContentType);
+ });
+ }
+ }
+}
+
+bool DocumentLoadListener::ResumeSuspendedChannel(
+ nsIStreamListener* aListener) {
+ LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this));
+ RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
+ if (httpChannel) {
+ httpChannel->SetApplyConversion(mOldApplyConversion);
+ }
+
+ if (!mIsFinished) {
+ mParentChannelListener->SetListenerAfterRedirect(aListener);
+ }
+
+ // If we failed to suspend the channel, then we might have received
+ // some messages while the redirected was being handled.
+ // Manually send them on now.
+ nsTArray<StreamListenerFunction> streamListenerFunctions =
+ std::move(mStreamListenerFunctions);
+ if (!aListener) {
+ streamListenerFunctions.Clear();
+ }
+
+ ForwardStreamListenerFunctions(std::move(streamListenerFunctions), aListener);
+
+ // We don't expect to get new stream listener functions added
+ // via re-entrancy. If this ever happens, we should understand
+ // exactly why before allowing it.
+ NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
+ "Should not have added new stream listener function!");
+
+ mChannel->Resume();
+
+ // Our caller will invoke `EndDocumentLoad` for us.
+
+ return !mIsFinished;
+}
+
+void DocumentLoadListener::CancelEarlyHintPreloads() {
+ mEarlyHintsService.Cancel("DocumentLoadListener::CancelEarlyHintPreloads"_ns);
+}
+
+void DocumentLoadListener::RegisterEarlyHintLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) {
+ mEarlyHintsService.RegisterLinksAndGetConnectArgs(aCpId, aOutLinks);
+}
+
+void DocumentLoadListener::SerializeRedirectData(
+ RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ nsTArray<EarlyHintConnectArgs>&& aEarlyHints,
+ uint32_t aEarlyHintLinkType) const {
+ aArgs.uri() = GetChannelCreationURI();
+ aArgs.loadIdentifier() = mLoadIdentifier;
+ aArgs.earlyHints() = std::move(aEarlyHints);
+ aArgs.earlyHintLinkType() = aEarlyHintLinkType;
+
+ // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
+ // clears the principal to inherit, which fails tests (probably because this
+ // 'redirect' is usually just an implementation detail). It's also http
+ // only, and mChannel can be anything that we redirected to.
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
+ nsCOMPtr<nsIPrincipal> principalToInherit;
+ channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
+
+ const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(mChannel, loadContext);
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo;
+
+ // Only use CloneLoadInfoForRedirect if we have a load context,
+ // since it internally tries to pull OriginAttributes from the
+ // the load context and asserts if they don't match the load info.
+ // We can end up without a load context if the channel has been aborted
+ // and the callbacks have been cleared.
+ if (baseChannel && loadContext) {
+ redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect(
+ aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL);
+ redirectLoadInfo->SetResultPrincipalURI(aArgs.uri());
+
+ // The clone process clears this, and then we fail tests..
+ // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
+ if (principalToInherit) {
+ redirectLoadInfo->SetPrincipalToInherit(principalToInherit);
+ }
+ } else {
+ redirectLoadInfo =
+ static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone();
+
+ redirectLoadInfo->AppendRedirectHistoryEntry(mChannel, true);
+ }
+
+ const Maybe<ClientInfo>& reservedClientInfo =
+ channelLoadInfo->GetReservedClientInfo();
+ if (reservedClientInfo) {
+ redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo);
+ }
+
+ aArgs.registrarId() = mRedirectChannelId;
+
+#ifdef DEBUG
+ // We only set the granularFingerprintingProtection field when opening http
+ // channels. So, we mark the field as set here if the channel is not a
+ // nsHTTPChannel to pass the assertion check for getting this field in below
+ // LoadInfoToLoadInfoArgs() call.
+ if (!baseChannel) {
+ static_cast<mozilla::net::LoadInfo*>(redirectLoadInfo.get())
+ ->MarkOverriddenFingerprintingSettingsAsSet();
+ }
+#endif
+
+ MOZ_ALWAYS_SUCCEEDS(
+ ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo()));
+
+ mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI()));
+
+ // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we
+ // can't use baseChannel here.
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
+ MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId()));
+ }
+
+ aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(mChannel);
+ if (httpChannelInternal) {
+ MOZ_ALWAYS_SUCCEEDS(
+ httpChannelInternal->GetRedirectMode(&aArgs.redirectMode()));
+ }
+
+ if (baseChannel) {
+ aArgs.init() =
+ Some(baseChannel
+ ->CloneReplacementChannelConfig(
+ true, aRedirectFlags,
+ HttpBaseChannel::ReplacementReason::DocumentChannel)
+ .Serialize());
+ }
+
+ uint32_t contentDispositionTemp;
+ nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp);
+ if (NS_SUCCEEDED(rv)) {
+ aArgs.contentDisposition() = Some(contentDispositionTemp);
+ }
+
+ nsString contentDispositionFilenameTemp;
+ rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp);
+ if (NS_SUCCEEDED(rv)) {
+ aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp);
+ }
+
+ SetNeedToAddURIVisit(mChannel, false);
+
+ aArgs.newLoadFlags() = aLoadFlags;
+ aArgs.redirectFlags() = aRedirectFlags;
+ aArgs.properties() = do_QueryObject(mChannel);
+ aArgs.srcdocData() = mSrcdocData;
+ aArgs.baseUri() = mBaseURI;
+ aArgs.loadStateExternalLoadFlags() = mLoadStateExternalLoadFlags;
+ aArgs.loadStateInternalLoadFlags() = mLoadStateInternalLoadFlags;
+ aArgs.loadStateLoadType() = mLoadStateLoadType;
+ aArgs.originalUriString() = mOriginalUriString;
+ if (mLoadingSessionHistoryInfo) {
+ aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo);
+ }
+}
+
+static bool IsFirstLoadInWindow(nsIChannel* aChannel) {
+ if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aChannel)) {
+ bool tmp = false;
+ nsresult rv =
+ props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp);
+ return NS_SUCCEEDED(rv) && tmp;
+ }
+ return false;
+}
+
+// Get where the document loaded by this nsIChannel should be rendered. This
+// will be `OPEN_CURRENTWINDOW` unless we're loading an attachment which would
+// normally open in an external program, but we're instead choosing to render
+// internally.
+static int32_t GetWhereToOpen(nsIChannel* aChannel, bool aIsDocumentLoad) {
+ // Ignore content disposition for loads from an object or embed element.
+ if (!aIsDocumentLoad) {
+ return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+ }
+
+ // Always continue in the same window if we're not loading an attachment.
+ uint32_t disposition = nsIChannel::DISPOSITION_INLINE;
+ if (NS_FAILED(aChannel->GetContentDisposition(&disposition)) ||
+ disposition != nsIChannel::DISPOSITION_ATTACHMENT) {
+ return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+ }
+
+ // If the channel is for a new window target, continue in the same window.
+ if (IsFirstLoadInWindow(aChannel)) {
+ return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+ }
+
+ // Respect the user's preferences with browser.link.open_newwindow
+ // FIXME: There should probably be a helper for this, as the logic is
+ // duplicated in a few places.
+ int32_t where = Preferences::GetInt("browser.link.open_newwindow",
+ nsIBrowserDOMWindow::OPEN_NEWTAB);
+ if (where == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW ||
+ where == nsIBrowserDOMWindow::OPEN_NEWWINDOW ||
+ where == nsIBrowserDOMWindow::OPEN_NEWTAB) {
+ return where;
+ }
+ // NOTE: nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND is not allowed as a pref
+ // value.
+ return nsIBrowserDOMWindow::OPEN_NEWTAB;
+}
+
+static DocumentLoadListener::ProcessBehavior GetProcessSwitchBehavior(
+ Element* aBrowserElement) {
+ if (aBrowserElement->HasAttribute(u"maychangeremoteness"_ns)) {
+ return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_STANDARD;
+ }
+ nsCOMPtr<nsIBrowser> browser = aBrowserElement->AsBrowser();
+ bool isRemoteBrowser = false;
+ browser->GetIsRemoteBrowser(&isRemoteBrowser);
+ if (isRemoteBrowser) {
+ return DocumentLoadListener::ProcessBehavior::
+ PROCESS_BEHAVIOR_SUBFRAME_ONLY;
+ }
+ return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED;
+}
+
+static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext,
+ WindowGlobalParent* aParentWindow,
+ bool aSwitchToNewTab) {
+ if (NS_WARN_IF(!aBrowsingContext)) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: no browsing context"));
+ return false;
+ }
+ if (!aBrowsingContext->IsContent()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: non-content browsing context"));
+ return false;
+ }
+
+ // If we're switching into a new tab, we can skip the remaining checks, as
+ // we're not actually changing the process of aBrowsingContext, so whether or
+ // not it is allowed to process switch isn't relevant.
+ if (aSwitchToNewTab) {
+ return true;
+ }
+
+ if (aParentWindow && !aBrowsingContext->UseRemoteSubframes()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: remote subframes disabled"));
+ return false;
+ }
+
+ if (aParentWindow && aParentWindow->IsInProcess()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: Subframe with in-process parent"));
+ return false;
+ }
+
+ // Determine what process switching behaviour is being requested by the root
+ // <browser> element.
+ Element* browserElement = aBrowsingContext->Top()->GetEmbedderElement();
+ if (!browserElement) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: cannot get embedder element"));
+ return false;
+ }
+ nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
+ if (!browser) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: not loaded within nsIBrowser"));
+ return false;
+ }
+
+ DocumentLoadListener::ProcessBehavior processBehavior =
+ GetProcessSwitchBehavior(browserElement);
+
+ // Check if the process switch we're considering is disabled by the
+ // <browser>'s process behavior.
+ if (processBehavior ==
+ DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: switch disabled by <browser>"));
+ return false;
+ }
+ if (!aParentWindow && processBehavior ==
+ DocumentLoadListener::ProcessBehavior::
+ PROCESS_BEHAVIOR_SUBFRAME_ONLY) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: toplevel switch disabled by <browser>"));
+ return false;
+ }
+
+ return true;
+}
+
+static RefPtr<dom::BrowsingContextCallbackReceivedPromise> SwitchToNewTab(
+ CanonicalBrowsingContext* aLoadingBrowsingContext, int32_t aWhere) {
+ MOZ_ASSERT(aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB ||
+ aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND ||
+ aWhere == nsIBrowserDOMWindow::OPEN_NEWWINDOW,
+ "Unsupported open location");
+
+ auto promise =
+ MakeRefPtr<dom::BrowsingContextCallbackReceivedPromise::Private>(
+ __func__);
+
+ // Get the nsIBrowserDOMWindow for the given BrowsingContext's tab.
+ nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
+ aLoadingBrowsingContext->GetBrowserDOMWindow();
+ if (NS_WARN_IF(!browserDOMWindow)) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: Unable to get nsIBrowserDOMWindow"));
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return promise;
+ }
+
+ // Open a new content tab by calling into frontend. We don't need to worry
+ // about the triggering principal or CSP, as createContentWindow doesn't
+ // actually start loading anything, but use a null principal anyway in case
+ // something changes.
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ NullPrincipal::Create(aLoadingBrowsingContext->OriginAttributesRef());
+
+ RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo();
+ openInfo->mBrowsingContextReadyCallback =
+ new nsBrowsingContextReadyCallback(promise);
+ openInfo->mOriginAttributes = aLoadingBrowsingContext->OriginAttributesRef();
+ openInfo->mParent = aLoadingBrowsingContext;
+ openInfo->mForceNoOpener = true;
+ openInfo->mIsRemote = true;
+
+ // Do the actual work to open a new tab or window async.
+ nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DocumentLoadListener::SwitchToNewTab",
+ [browserDOMWindow, openInfo, aWhere, triggeringPrincipal, promise] {
+ RefPtr<BrowsingContext> bc;
+ nsresult rv = browserDOMWindow->CreateContentWindow(
+ /* uri */ nullptr, openInfo, aWhere,
+ nsIBrowserDOMWindow::OPEN_NO_REFERRER, triggeringPrincipal,
+ /* csp */ nullptr, getter_AddRefs(bc));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: CreateContentWindow threw"));
+ promise->Reject(rv, __func__);
+ }
+ if (bc) {
+ promise->Resolve(bc, __func__);
+ }
+ }));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(NS_ERROR_UNEXPECTED, __func__);
+ }
+ return promise;
+}
+
+bool DocumentLoadListener::MaybeTriggerProcessSwitch(
+ bool* aWillSwitchToRemote) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(mChannel);
+ MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
+ MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote);
+
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p, uri=%s, "
+ "browserid=%" PRIx64 "]",
+ this, GetChannelCreationURI()->GetSpecOrDefault().get(),
+ GetLoadingBrowsingContext()->Top()->BrowserId()));
+
+ // If we're doing an <object>/<embed> load, we may be doing a document load at
+ // this point. We never need to do a process switch for a non-document
+ // <object> or <embed> load.
+ if (!mIsDocumentLoad) {
+ if (!mChannel->IsDocument()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("Process Switch Abort: non-document load"));
+ return false;
+ }
+ nsresult status;
+ if (!nsObjectLoadingContent::IsSuccessfulRequest(mChannel, &status)) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("Process Switch Abort: error page"));
+ return false;
+ }
+ }
+
+ // Check if we should handle this load in a different tab or window.
+ int32_t where = GetWhereToOpen(mChannel, mIsDocumentLoad);
+ bool switchToNewTab = where != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+
+ // Get the loading BrowsingContext. This may not be the context which will be
+ // switching processes when switching to a new tab, and in the case of an
+ // <object> or <embed> element, as we don't create the final context until
+ // after process selection.
+ //
+ // - /!\ WARNING /!\ -
+ // Don't use `browsingContext->IsTop()` in this method! It will behave
+ // incorrectly for non-document loads such as `<object>` or `<embed>`.
+ // Instead, check whether or not `parentWindow` is null.
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ GetLoadingBrowsingContext();
+ // If switching to a new tab, the final BC isn't a frame.
+ RefPtr<WindowGlobalParent> parentWindow =
+ switchToNewTab ? nullptr : GetParentWindowContext();
+ if (!ContextCanProcessSwitch(browsingContext, parentWindow, switchToNewTab)) {
+ return false;
+ }
+
+ if (!browsingContext->IsOwnedByProcess(GetContentProcessId(mContentParent))) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
+ ("Process Switch Abort: context no longer owned by creator"));
+ Cancel(NS_BINDING_ABORTED,
+ "Process Switch Abort: context no longer owned by creator"_ns);
+ return false;
+ }
+
+ if (browsingContext->IsReplaced()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: replaced browsing context"));
+ Cancel(NS_BINDING_ABORTED,
+ "Process Switch Abort: replaced browsing context"_ns);
+ return false;
+ }
+
+ nsAutoCString currentRemoteType(NOT_REMOTE_TYPE);
+ if (mContentParent) {
+ currentRemoteType = mContentParent->GetRemoteType();
+ }
+
+ auto optionsResult = IsolationOptionsForNavigation(
+ browsingContext->Top(), switchToNewTab ? nullptr : parentWindow.get(),
+ GetChannelCreationURI(), mChannel, currentRemoteType,
+ HasCrossOriginOpenerPolicyMismatch(), switchToNewTab, mLoadStateLoadType,
+ mDocumentChannelId, mRemoteTypeOverride);
+ if (optionsResult.isErr()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
+ ("Process Switch Abort: CheckIsolationForNavigation Failed with %s",
+ GetStaticErrorName(optionsResult.inspectErr())));
+ Cancel(optionsResult.unwrapErr(),
+ "Process Switch Abort: CheckIsolationForNavigation Failed"_ns);
+ return false;
+ }
+
+ NavigationIsolationOptions options = optionsResult.unwrap();
+
+ if (options.mTryUseBFCache) {
+ MOZ_ASSERT(!parentWindow, "Can only BFCache toplevel windows");
+ MOZ_ASSERT(!switchToNewTab, "Can't BFCache for a tab switch");
+ bool sameOrigin = false;
+ if (auto* wgp = browsingContext->GetCurrentWindowGlobal()) {
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ MOZ_ALWAYS_SUCCEEDS(
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ mChannel, getter_AddRefs(resultPrincipal)));
+ sameOrigin =
+ wgp->DocumentPrincipal()->EqualsConsideringDomain(resultPrincipal);
+ }
+
+ // We only reset the window name for content.
+ mLoadingSessionHistoryInfo->mForceMaybeResetName.emplace(
+ StaticPrefs::privacy_window_name_update_enabled() &&
+ browsingContext->IsContent() && !sameOrigin);
+ }
+
+ MOZ_LOG(
+ gProcessIsolationLog, LogLevel::Verbose,
+ ("CheckIsolationForNavigation -> current:(%s) remoteType:(%s) replace:%d "
+ "group:%" PRIx64 " bfcache:%d shentry:%p newTab:%d",
+ currentRemoteType.get(), options.mRemoteType.get(),
+ options.mReplaceBrowsingContext, options.mSpecificGroupId,
+ options.mTryUseBFCache, options.mActiveSessionHistoryEntry.get(),
+ switchToNewTab));
+
+ // Check if a process switch is needed.
+ if (currentRemoteType == options.mRemoteType &&
+ !options.mReplaceBrowsingContext && !switchToNewTab) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Info,
+ ("Process Switch Abort: type (%s) is compatible",
+ options.mRemoteType.get()));
+ return false;
+ }
+
+ if (NS_WARN_IF(parentWindow && options.mRemoteType.IsEmpty())) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
+ ("Process Switch Abort: non-remote target process for subframe"));
+ return false;
+ }
+
+ *aWillSwitchToRemote = !options.mRemoteType.IsEmpty();
+
+ // If we've decided to re-target this load into a new tab or window (see
+ // `GetWhereToOpen`), do so before performing a process switch. This will
+ // require creating the new <browser> to load in, which may be performed
+ // async.
+ if (switchToNewTab) {
+ SwitchToNewTab(browsingContext, where)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this},
+ options](const RefPtr<BrowsingContext>& aBrowsingContext) mutable {
+ if (aBrowsingContext->IsDiscarded()) {
+ MOZ_LOG(
+ gProcessIsolationLog, LogLevel::Error,
+ ("Process Switch: Got invalid new-tab BrowsingContext"));
+ self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
+ return;
+ }
+
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("Process Switch: Redirected load to new tab"));
+ self->TriggerProcessSwitch(aBrowsingContext->Canonical(), options,
+ /* aIsNewTab */ true);
+ },
+ [self = RefPtr{this}](const CopyableErrorResult&) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
+ ("Process Switch: SwitchToNewTab failed"));
+ self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
+ });
+ return true;
+ }
+
+ // If we're doing a document load, we can immediately perform a process
+ // switch.
+ if (mIsDocumentLoad) {
+ TriggerProcessSwitch(browsingContext, options);
+ return true;
+ }
+
+ // We're not doing a document load, which means we must be performing an
+ // object load. We need a BrowsingContext to perform the switch in, so will
+ // trigger an upgrade.
+ if (!mObjectUpgradeHandler) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: no object upgrade handler"));
+ return false;
+ }
+
+ if (!StaticPrefs::fission_remoteObjectEmbed()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("Process Switch Abort: remote <object>/<embed> disabled"));
+ return false;
+ }
+
+ mObjectUpgradeHandler->UpgradeObjectLoad()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}, options, parentWindow](
+ const RefPtr<CanonicalBrowsingContext>& aBrowsingContext) mutable {
+ if (aBrowsingContext->IsDiscarded() ||
+ parentWindow != aBrowsingContext->GetParentWindowContext()) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
+ ("Process Switch: Got invalid BrowsingContext from object "
+ "upgrade!"));
+ self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
+ return;
+ }
+
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("Process Switch: Upgraded Object to Document Load"));
+ self->TriggerProcessSwitch(aBrowsingContext, options);
+ },
+ [self = RefPtr{this}](nsresult aStatusCode) {
+ MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
+ self->RedirectToRealChannelFinished(aStatusCode);
+ });
+ return true;
+}
+
+void DocumentLoadListener::TriggerProcessSwitch(
+ CanonicalBrowsingContext* aContext,
+ const NavigationIsolationOptions& aOptions, bool aIsNewTab) {
+ MOZ_DIAGNOSTIC_ASSERT(aIsNewTab || aContext->IsOwnedByProcess(
+ GetContentProcessId(mContentParent)),
+ "not owned by creator process anymore?");
+ if (MOZ_LOG_TEST(gProcessIsolationLog, LogLevel::Info)) {
+ nsCString currentRemoteType = "INVALID"_ns;
+ aContext->GetCurrentRemoteType(currentRemoteType, IgnoreErrors());
+
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Info,
+ ("Process Switch: Changing Remoteness from '%s' to '%s'",
+ currentRemoteType.get(), aOptions.mRemoteType.get()));
+ }
+
+ // Stash our stream filter requests to pass to TriggerRedirectToRealChannel,
+ // as the call to `DisconnectListeners` will clear our list.
+ nsTArray<StreamFilterRequest> streamFilterRequests =
+ std::move(mStreamFilterRequests);
+
+ // We're now committing to a process switch, so we can disconnect from
+ // the listeners in the old process.
+ // As the navigation is continuing, we don't actually want to cancel the
+ // request in the old process unless we're redirecting the load into a new
+ // tab.
+ DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, !aIsNewTab);
+
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("Process Switch: Calling ChangeRemoteness"));
+ aContext->ChangeRemoteness(aOptions, mLoadIdentifier)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}, requests = std::move(streamFilterRequests)](
+ BrowserParent* aBrowserParent) mutable {
+ MOZ_ASSERT(self->mChannel,
+ "Something went wrong, channel got cancelled");
+ self->TriggerRedirectToRealChannel(
+ Some(aBrowserParent ? aBrowserParent->Manager() : nullptr),
+ std::move(requests));
+ },
+ [self = RefPtr{this}](nsresult aStatusCode) {
+ MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
+ self->RedirectToRealChannelFinished(aStatusCode);
+ });
+}
+
+RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+DocumentLoadListener::RedirectToParentProcess(uint32_t aRedirectFlags,
+ uint32_t aLoadFlags) {
+ // This is largely the same as ContentChild::RecvCrossProcessRedirect,
+ // except without needing to deserialize or create an nsIChildChannel.
+
+ RefPtr<nsDocShellLoadState> loadState;
+ nsDocShellLoadState::CreateFromPendingChannel(
+ mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState));
+
+ loadState->SetLoadFlags(mLoadStateExternalLoadFlags);
+ loadState->SetInternalLoadFlags(mLoadStateInternalLoadFlags);
+ loadState->SetLoadType(mLoadStateLoadType);
+ if (mLoadingSessionHistoryInfo) {
+ loadState->SetLoadingSessionHistoryInfo(*mLoadingSessionHistoryInfo);
+ }
+
+ // This is poorly named now.
+ RefPtr<ChildProcessChannelListener> processListener =
+ ChildProcessChannelListener::GetSingleton();
+
+ auto promise =
+ MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
+ __func__);
+ promise->UseDirectTaskDispatch(__func__);
+ auto resolve = [promise](nsresult aResult) {
+ promise->Resolve(aResult, __func__);
+ };
+
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> endpoints;
+ processListener->OnChannelReady(loadState, mLoadIdentifier,
+ std::move(endpoints), mTiming,
+ std::move(resolve));
+
+ return promise;
+}
+
+RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+DocumentLoadListener::RedirectToRealChannel(
+ uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ const Maybe<ContentParent*>& aDestinationProcess,
+ nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
+ LOG(
+ ("DocumentLoadListener RedirectToRealChannel [this=%p] "
+ "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32,
+ this, aRedirectFlags, aLoadFlags));
+
+ if (mIsDocumentLoad) {
+ // TODO(djg): Add the last URI visit to history if success. Is there a
+ // better place to handle this? Need access to the updated aLoadFlags.
+ nsresult status = NS_OK;
+ mChannel->GetStatus(&status);
+ bool updateGHistory =
+ nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType);
+ if (NS_SUCCEEDED(status) && updateGHistory &&
+ !net::ChannelIsPost(mChannel)) {
+ AddURIVisit(mChannel, aLoadFlags);
+ }
+ }
+
+ // Register the new channel and obtain id for it
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+ nsCOMPtr<nsIChannel> chan = mChannel;
+ if (nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(chan)) {
+ chan = vsc->GetInnerChannel();
+ }
+ mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier();
+ MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(chan, mRedirectChannelId));
+
+ if (aDestinationProcess) {
+ RefPtr<ContentParent> cp = *aDestinationProcess;
+ if (!cp) {
+ MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty());
+ return RedirectToParentProcess(aRedirectFlags, aLoadFlags);
+ }
+
+ if (!cp->CanSend()) {
+ return PDocumentChannelParent::RedirectToRealChannelPromise::
+ CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
+ }
+
+ nsTArray<EarlyHintConnectArgs> ehArgs;
+ mEarlyHintsService.RegisterLinksAndGetConnectArgs(cp->ChildID(), ehArgs);
+
+ RedirectToRealChannelArgs args;
+ SerializeRedirectData(args, /* aIsCrossProcess */ true, aRedirectFlags,
+ aLoadFlags, std::move(ehArgs),
+ mEarlyHintsService.LinkType());
+ if (mTiming) {
+ mTiming->Anonymize(args.uri());
+ args.timing() = std::move(mTiming);
+ }
+
+ cp->TransmitBlobDataIfBlobURL(args.uri());
+
+ if (CanonicalBrowsingContext* bc = GetDocumentBrowsingContext()) {
+ if (bc->IsTop() && bc->IsActive()) {
+ nsContentUtils::RequestGeckoTaskBurst();
+ }
+ }
+
+ return cp->SendCrossProcessRedirect(args,
+ std::move(aStreamFilterEndpoints));
+ }
+
+ if (mOpenPromiseResolved) {
+ LOG(
+ ("DocumentLoadListener RedirectToRealChannel [this=%p] "
+ "promise already resolved. Aborting.",
+ this));
+ // The promise has already been resolved or aborted, so we have no way to
+ // return a promise again to the listener which would cancel the operation.
+ // Reject the promise immediately.
+ return PDocumentChannelParent::RedirectToRealChannelPromise::
+ CreateAndResolve(NS_BINDING_ABORTED, __func__);
+ }
+
+ // This promise will be passed on the promise listener which will
+ // resolve this promise for us.
+ auto promise =
+ MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
+ __func__);
+
+ mOpenPromise->Resolve(
+ OpenPromiseSucceededType({std::move(aStreamFilterEndpoints),
+ aRedirectFlags, aLoadFlags,
+ mEarlyHintsService.LinkType(), promise}),
+ __func__);
+
+ // There is no way we could come back here if the promise had been resolved
+ // previously. But for clarity and to avoid all doubt, we set this boolean to
+ // true.
+ mOpenPromiseResolved = true;
+
+ return promise;
+}
+
+void DocumentLoadListener::TriggerRedirectToRealChannel(
+ const Maybe<ContentParent*>& aDestinationProcess,
+ nsTArray<StreamFilterRequest> aStreamFilterRequests) {
+ LOG((
+ "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] "
+ "aDestinationProcess=%" PRId64,
+ this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1)));
+ // This initiates replacing the current DocumentChannel with a
+ // protocol specific 'real' channel, maybe in a different process than
+ // the current DocumentChannelChild, if aDestinationProces is set.
+ // It registers the current mChannel with the registrar to get an ID
+ // so that the remote end can setup a new IPDL channel and lookup
+ // the same underlying channel.
+ // We expect this process to finish with FinishReplacementChannelSetup
+ // (for both in-process and process switch cases), where we cleanup
+ // the registrar and copy across any needed state to the replacing
+ // IPDL parent object.
+
+ nsTArray<ParentEndpoint> parentEndpoints(aStreamFilterRequests.Length());
+ if (!aStreamFilterRequests.IsEmpty()) {
+ ContentParent* cp = aDestinationProcess.valueOr(mContentParent);
+ base::ProcessId pid = cp ? cp->OtherPid() : base::ProcessId{0};
+
+ for (StreamFilterRequest& request : aStreamFilterRequests) {
+ if (!pid) {
+ request.mPromise->Reject(false, __func__);
+ request.mPromise = nullptr;
+ continue;
+ }
+ ParentEndpoint parent;
+ nsresult rv = extensions::PStreamFilter::CreateEndpoints(
+ &parent, &request.mChildEndpoint);
+
+ if (NS_FAILED(rv)) {
+ request.mPromise->Reject(false, __func__);
+ request.mPromise = nullptr;
+ } else {
+ parentEndpoints.AppendElement(std::move(parent));
+ }
+ }
+ }
+
+ // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag
+ // for this channel switch so that it isn't recorded in session history etc.
+ // If there were redirect(s), then we want this switch to be recorded as a
+ // real one, since we have a new URI.
+ uint32_t redirectFlags = 0;
+ if (!mHaveVisibleRedirect) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
+ }
+
+ uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
+ MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags));
+ // We're pulling our flags from the inner channel, which may not have this
+ // flag set on it. This is the case when loading a 'view-source' channel.
+ if (mIsDocumentLoad || aDestinationProcess) {
+ newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
+ }
+ if (!aDestinationProcess) {
+ newLoadFlags |= nsIChannel::LOAD_REPLACE;
+ }
+
+ // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from
+ // both parent and content process channel instances), but only ever
+ // re-added to the parent-side nsHttpChannel.
+ // To match that behaviour, we want to explicitly avoid copying this flag
+ // back to our newly created content side channel, otherwise it can
+ // affect sub-resources loads in the same load group.
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ if (uri && uri->SchemeIs("https")) {
+ newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING;
+ }
+
+ RefPtr<DocumentLoadListener> self = this;
+ RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess,
+ std::move(parentEndpoints))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, requests = std::move(aStreamFilterRequests)](
+ const nsresult& aResponse) mutable {
+ for (StreamFilterRequest& request : requests) {
+ if (request.mPromise) {
+ request.mPromise->Resolve(std::move(request.mChildEndpoint),
+ __func__);
+ request.mPromise = nullptr;
+ }
+ }
+ self->RedirectToRealChannelFinished(aResponse);
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
+ });
+}
+
+void DocumentLoadListener::MaybeReportBlockedByURLClassifier(nsresult aStatus) {
+ auto* browsingContext = GetDocumentBrowsingContext();
+ if (!browsingContext || browsingContext->IsTop() ||
+ !StaticPrefs::privacy_trackingprotection_testing_report_blocked_node()) {
+ return;
+ }
+
+ if (!UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatus)) {
+ return;
+ }
+
+ RefPtr<WindowGlobalParent> parent = browsingContext->GetParentWindowContext();
+ if (parent) {
+ Unused << parent->SendAddBlockedFrameNodeByClassifier(browsingContext);
+ }
+}
+
+bool DocumentLoadListener::DocShellWillDisplayContent(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ return true;
+ }
+
+ // Always return errored loads to the <object> or <embed> element's process,
+ // as load errors will not be rendered as documents.
+ if (!mIsDocumentLoad) {
+ return false;
+ }
+
+ // nsDocShell attempts urifixup on some failure types,
+ // but also of those also display an error page if we don't
+ // succeed with fixup, so we don't need to check for it
+ // here.
+
+ auto* loadingContext = GetLoadingBrowsingContext();
+
+ bool isInitialDocument = true;
+ if (WindowGlobalParent* currentWindow =
+ loadingContext->GetCurrentWindowGlobal()) {
+ isInitialDocument = currentWindow->IsInitialDocument();
+ }
+
+ nsresult rv = nsDocShell::FilterStatusForErrorPage(
+ aStatus, mChannel, mLoadStateLoadType, loadingContext->IsTop(),
+ loadingContext->GetUseErrorPages(), isInitialDocument, nullptr);
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
+ ("Skipping process switch, as DocShell will not display content "
+ "(status: %s) %s",
+ GetStaticErrorName(aStatus),
+ GetChannelCreationURI()->GetSpecOrDefault().get()));
+ }
+
+ // If filtering returned a failure code, then an error page will
+ // be display for that code, so return true;
+ return NS_FAILED(rv);
+}
+
+bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) {
+ RefPtr<CanonicalBrowsingContext> bc = GetDocumentBrowsingContext();
+ if (!bc) {
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> newPostData;
+ bool wasSchemelessInput = false;
+ nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup(
+ mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(),
+ mLoadStateInternalLoadFlags &
+ nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
+ bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData),
+ &wasSchemelessInput);
+
+ // Since aStatus will be NS_OK for 4xx and 5xx error codes we
+ // have to check each request which was upgraded by https-first.
+ // If an error (including 4xx and 5xx) occured, then let's check if
+ // we can downgrade the scheme to HTTP again.
+ bool isHTTPSFirstFixup = false;
+ if (!newURI) {
+ newURI = nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(mChannel,
+ aStatus);
+ isHTTPSFirstFixup = true;
+ }
+
+ if (!newURI) {
+ return false;
+ }
+
+ // If we got a new URI, then we should initiate a load with that.
+ // Notify the listeners that this load is complete (with a code that
+ // won't trigger an error page), and then start the new one.
+ DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
+ loadState->SetCsp(cspToInherit);
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo->TriggeringPrincipal();
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+
+ loadState->SetPostDataStream(newPostData);
+
+ // Record whether the protocol was added through a fixup.
+ loadState->SetWasSchemelessInput(wasSchemelessInput);
+
+ if (isHTTPSFirstFixup) {
+ // We have to exempt the load from HTTPS-First to prevent a
+ // upgrade-downgrade loop.
+ loadState->SetIsExemptFromHTTPSFirstMode(true);
+ }
+
+ // Ensure to set referrer information in the fallback channel equally to the
+ // not-upgraded original referrer info.
+ //
+ // A simply copy of the referrer info from the upgraded one leads to problems.
+ // For example:
+ // 1. https://some-site.com redirects to http://other-site.com with referrer
+ // policy
+ // "no-referrer-when-downgrade".
+ // 2. https-first upgrades the redirection, so redirects to
+ // https://other-site.com,
+ // according to referrer policy the referrer will be send (https-> https)
+ // 3. Assume other-site.com is not supporting https, https-first performs
+ // fall-
+ // back.
+ // If the referrer info from the upgraded channel gets copied into the
+ // http fallback channel, the referrer info would contain the referrer
+ // (https://some-site.com). That would violate the policy
+ // "no-referrer-when-downgrade". A recreation of the original referrer info
+ // would ensure us that the referrer is set according to the referrer policy.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
+ if (referrerInfo) {
+ ReferrerPolicy referrerPolicy = referrerInfo->ReferrerPolicy();
+ nsCOMPtr<nsIURI> originalReferrer = referrerInfo->GetOriginalReferrer();
+ if (originalReferrer) {
+ // Create new ReferrerInfo with the original referrer and the referrer
+ // policy.
+ nsCOMPtr<nsIReferrerInfo> newReferrerInfo =
+ new ReferrerInfo(originalReferrer, referrerPolicy);
+ loadState->SetReferrerInfo(newReferrerInfo);
+ }
+ }
+ }
+
+ bc->LoadURI(loadState, false);
+ return true;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("DocumentLoadListener OnStartRequest [this=%p]", this));
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (multiPartChannel) {
+ multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
+ } else {
+ mChannel = do_QueryInterface(aRequest);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(mChannel);
+
+ if (mHaveVisibleRedirect && GetDocumentBrowsingContext() &&
+ mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo =
+ GetDocumentBrowsingContext()->ReplaceLoadingSessionHistoryEntryForLoad(
+ mLoadingSessionHistoryInfo.get(), mChannel);
+ }
+
+ RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
+
+ // Enforce CSP frame-ancestors and x-frame-options checks which
+ // might cancel the channel.
+ nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel);
+
+ // HTTPS-Only Mode tries to upgrade connections to https. Once loading
+ // is in progress we set that flag so that timeout counter measures
+ // do not kick in.
+ if (httpChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
+ bool isPrivateWin = loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) {
+ uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
+ httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
+ loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
+ }
+
+ if (mLoadingSessionHistoryInfo &&
+ nsDocShell::ShouldDiscardLayoutState(httpChannel)) {
+ mLoadingSessionHistoryInfo->mInfo.SetSaveLayoutStateFlag(false);
+ }
+ }
+
+ auto* loadingContext = GetLoadingBrowsingContext();
+ if (!loadingContext || loadingContext->IsDiscarded()) {
+ Cancel(NS_ERROR_UNEXPECTED, "No valid LoadingBrowsingContext."_ns);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ Cancel(NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+ "Aborting OnStartRequest after shutdown started."_ns);
+ return NS_OK;
+ }
+
+ // Block top-level data URI navigations if triggered by the web. Logging is
+ // performed in AllowTopLevelNavigationToDataURI.
+ if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(mChannel)) {
+ mChannel->Cancel(NS_ERROR_DOM_BAD_URI);
+ if (loadingContext) {
+ RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
+ new MaybeCloseWindowHelper(loadingContext);
+ // If a new window was opened specifically for this request, close it
+ // after blocking the navigation.
+ maybeCloseWindowHelper->SetShouldCloseWindow(
+ IsFirstLoadInWindow(mChannel));
+ Unused << maybeCloseWindowHelper->MaybeCloseWindow();
+ }
+ DisconnectListeners(NS_ERROR_DOM_BAD_URI, NS_ERROR_DOM_BAD_URI);
+ return NS_OK;
+ }
+
+ // Generally we want to switch to a real channel even if the request failed,
+ // since the listener might want to access protocol-specific data (like http
+ // response headers) in its error handling.
+ // An exception to this is when nsExtProtocolChannel handled the request and
+ // returned NS_ERROR_NO_CONTENT, since creating a real one in the content
+ // process will attempt to handle the URI a second time.
+ nsresult status = NS_OK;
+ aRequest->GetStatus(&status);
+ if (status == NS_ERROR_NO_CONTENT) {
+ DisconnectListeners(status, status);
+ return NS_OK;
+ }
+
+ // PerformCSPFrameAncestorAndXFOCheck may cancel a moz-extension request that
+ // needs to be handled here. Without this, the resource would be loaded and
+ // not blocked when the real channel is created in the content process.
+ if (status == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION && !httpChannel) {
+ DisconnectListeners(status, status);
+ return NS_OK;
+ }
+
+ // If this was a failed load and we want to try fixing the uri, then
+ // this will initiate a new load (and disconnect this one), and we don't
+ // need to do anything else.
+ if (MaybeHandleLoadErrorWithURIFixup(status)) {
+ return NS_OK;
+ }
+
+ mStreamListenerFunctions.AppendElement(StreamListenerFunction{
+ VariantIndex<0>{}, OnStartRequestParams{aRequest}});
+
+ if (mOpenPromiseResolved || mInitiatedRedirectToRealChannel) {
+ // I we have already resolved the promise, there's no point to continue
+ // attempting a process switch or redirecting to the real channel.
+ // We can also have multiple calls to OnStartRequest when dealing with
+ // multi-part content, but only want to redirect once.
+ return NS_OK;
+ }
+
+ // Keep track of server responses resulting in a document for the Bounce
+ // Tracking Protection.
+ if (mIsDocumentLoad && GetParentWindowContext() == nullptr &&
+ loadingContext->IsTopContent()) {
+ RefPtr<BounceTrackingState> bounceTrackingState =
+ loadingContext->GetBounceTrackingState();
+
+ // Not every browsing context has a BounceTrackingState. It's also null when
+ // the feature is disabled.
+ if (bounceTrackingState) {
+ DebugOnly<nsresult> rv =
+ bounceTrackingState->OnDocumentStartRequest(mChannel);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "BounceTrackingState::OnDocumentStartRequest failed.");
+ }
+ }
+
+ mChannel->Suspend();
+
+ mInitiatedRedirectToRealChannel = true;
+
+ MaybeReportBlockedByURLClassifier(status);
+
+ // Determine if a new process needs to be spawned. If it does, this will
+ // trigger a cross process switch, and we should hold off on redirecting to
+ // the real channel.
+ // If the channel has failed, and the docshell isn't going to display an
+ // error page for that failure, then don't allow process switching, since
+ // we just want to keep our existing document.
+ bool willBeRemote = false;
+ if (!DocShellWillDisplayContent(status) ||
+ !MaybeTriggerProcessSwitch(&willBeRemote)) {
+ // We're not going to be doing a process switch, so redirect to the real
+ // channel within our current process.
+ nsTArray<StreamFilterRequest> streamFilterRequests =
+ std::move(mStreamFilterRequests);
+ if (!mSupportsRedirectToRealChannel) {
+ RefPtr<BrowserParent> browserParent = loadingContext->GetBrowserParent();
+ if (browserParent->Manager() != mContentParent) {
+ LOG(
+ ("DocumentLoadListener::RedirectToRealChannel failed because "
+ "browsingContext no longer owned by creator"));
+ Cancel(NS_BINDING_ABORTED,
+ "DocumentLoadListener::RedirectToRealChannel failed because "
+ "browsingContext no longer owned by creator"_ns);
+ return NS_OK;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(
+ browserParent->GetBrowsingContext() == loadingContext,
+ "make sure the load is going to the right place");
+
+ // If the existing process is right for this load, but the bridge doesn't
+ // support redirects, then we need to do it manually, by faking a process
+ // switch.
+ DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED,
+ /* aContinueNavigating */ true);
+
+ // Notify the docshell that it should load using the newly connected
+ // channel
+ browserParent->ResumeLoad(mLoadIdentifier);
+
+ // Use the current process ID to run the 'process switch' path and connect
+ // the channel into the current process.
+ TriggerRedirectToRealChannel(Some(mContentParent),
+ std::move(streamFilterRequests));
+ } else {
+ TriggerRedirectToRealChannel(Nothing(), std::move(streamFilterRequests));
+ }
+
+ // If we're not switching, then check if we're currently remote.
+ if (mContentParent) {
+ willBeRemote = true;
+ }
+ }
+
+ if (httpChannel) {
+ uint32_t responseStatus = 0;
+ Unused << httpChannel->GetResponseStatus(&responseStatus);
+ mEarlyHintsService.FinalResponse(responseStatus);
+ } else {
+ mEarlyHintsService.Cancel(
+ "DocumentLoadListener::OnStartRequest: no httpChannel"_ns);
+ }
+
+ // If we're going to be delivering this channel to a remote content
+ // process, then we want to install any required content conversions
+ // in the content process.
+ // The caller of this OnStartRequest will install a conversion
+ // helper after we return if we haven't disabled conversion. Normally
+ // HttpChannelParent::OnStartRequest would disable conversion, but we're
+ // defering calling that until later. Manually disable it now to prevent the
+ // converter from being installed (since we want the child to do it), and
+ // also save the value so that when we do call
+ // HttpChannelParent::OnStartRequest, we can have the value as it originally
+ // was.
+ if (httpChannel) {
+ Unused << httpChannel->GetApplyConversion(&mOldApplyConversion);
+ if (willBeRemote) {
+ httpChannel->SetApplyConversion(false);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ LOG(("DocumentLoadListener OnStopRequest [this=%p]", this));
+ mStreamListenerFunctions.AppendElement(StreamListenerFunction{
+ VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}});
+
+ // If we're not a multi-part channel, then we're finished and we don't
+ // expect any further events. If we are, then this might be called again,
+ // so wait for OnAfterLastPart instead.
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (!multiPartChannel) {
+ mIsFinished = true;
+ }
+
+ mStreamFilterRequests.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this));
+ // This isn't supposed to happen, since we suspended the channel, but
+ // sometimes Suspend just doesn't work. This can happen when we're routing
+ // through nsUnknownDecoder to sniff the content type, and it doesn't handle
+ // being suspended. Let's just store the data and manually forward it to our
+ // redirected channel when it's ready.
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStreamListenerFunctions.AppendElement(StreamListenerFunction{
+ VariantIndex<1>{},
+ OnDataAvailableParams{aRequest, std::move(data), aOffset, aCount}});
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// DoucmentLoadListener::nsIMultiPartChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+DocumentLoadListener::OnAfterLastPart(nsresult aStatus) {
+ LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this));
+ if (!mInitiatedRedirectToRealChannel) {
+ // if we get here, and we haven't initiated a redirect to a real
+ // channel, then it means we never got OnStartRequest (maybe a problem?)
+ // and we retargeted everything.
+ LOG(("DocumentLoadListener Disconnecting child"));
+ DisconnectListeners(NS_BINDING_RETARGETED, NS_OK);
+ return NS_OK;
+ }
+ mStreamListenerFunctions.AppendElement(StreamListenerFunction{
+ VariantIndex<3>{}, OnAfterLastPartParams{aStatus}});
+ mIsFinished = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) {
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ GetLoadingBrowsingContext();
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) {
+ browsingContext.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIParentChannel
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+DocumentLoadListener::SetParentListener(
+ mozilla::net::ParentChannelListener* listener) {
+ // We don't need this (do we?)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ ClassifierMatchedInfoParams params;
+ params.mList = aList;
+ params.mProvider = aProvider;
+ params.mFullHash = aFullHash;
+
+ mIParentChannelFunctions.AppendElement(
+ IParentChannelFunction{VariantIndex<0>{}, std::move(params)});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHash) {
+ ClassifierMatchedTrackingInfoParams params;
+ params.mLists = aLists;
+ params.mFullHashes = aFullHash;
+
+ mIParentChannelFunctions.AppendElement(
+ IParentChannelFunction{VariantIndex<1>{}, std::move(params)});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ mIParentChannelFunctions.AppendElement(IParentChannelFunction{
+ VariantIndex<2>{},
+ ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::Delete() {
+ MOZ_ASSERT_UNREACHABLE("This method is unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::GetRemoteType(nsACString& aRemoteType) {
+ // FIXME: The remote type here should be pulled from the remote process used
+ // to create this DLL, not from the current `browsingContext`.
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ GetDocumentBrowsingContext();
+ if (!browsingContext) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ErrorResult error;
+ browsingContext->GetCurrentRemoteType(aRemoteType, error);
+ if (error.Failed()) {
+ aRemoteType = NOT_REMOTE_TYPE;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannelEventSink
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+DocumentLoadListener::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback) {
+ LOG(("DocumentLoadListener::AsyncOnChannelRedirect [this=%p flags=%" PRIu32
+ "]",
+ this, aFlags));
+ // We generally don't want to notify the content process about redirects,
+ // so just update our channel and tell the callback that we're good to go.
+ mChannel = aNewChannel;
+
+ // We need the original URI of the current channel to use to open the real
+ // channel in the content process. Unfortunately we overwrite the original
+ // uri of the new channel with the original pre-redirect URI, so grab
+ // a copy of it now and save it on the loadInfo corresponding to the
+ // new channel.
+ nsCOMPtr<nsILoadInfo> loadInfoFromChannel = mChannel->LoadInfo();
+ MOZ_ASSERT(loadInfoFromChannel);
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetOriginalURI(getter_AddRefs(uri));
+ loadInfoFromChannel->SetChannelCreationOriginalURI(uri);
+
+ // Since we're redirecting away from aOldChannel, we should check if it
+ // had a COOP mismatch, since we want the final result for this to
+ // include the state of all channels we redirected through.
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel);
+ if (httpChannel) {
+ bool isCOOPMismatch = false;
+ Unused << NS_WARN_IF(NS_FAILED(
+ httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
+ mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch;
+ }
+
+ // If HTTPS-Only mode is enabled, we need to check whether the exception-flag
+ // needs to be removed or set, by asking the PermissionManager.
+ nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(mChannel);
+
+ // We don't need to confirm internal redirects or record any
+ // history for them, so just immediately verify and return.
+ if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ LOG(
+ ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
+ "flags=REDIRECT_INTERNAL",
+ this));
+ aCallback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+
+ // Cancel cross origin redirects as described by whatwg:
+ // > Note: [The early hint reponse] is discarded if it is succeeded by a
+ // > cross-origin redirect.
+ // https://html.spec.whatwg.org/multipage/semantics.html#early-hints
+ nsCOMPtr<nsIURI> oldURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->CheckSameOriginURI(oldURI, uri, false, false);
+ if (NS_FAILED(rv)) {
+ mEarlyHintsService.Cancel(
+ "DocumentLoadListener::AsyncOnChannelRedirect: cors redirect"_ns);
+ }
+
+ if (GetDocumentBrowsingContext()) {
+ if (!net::ChannelIsPost(aOldChannel)) {
+ AddURIVisit(aOldChannel, 0);
+ nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
+ }
+ }
+ mHaveVisibleRedirect |= true;
+
+ LOG(
+ ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
+ "mHaveVisibleRedirect=%c",
+ this, mHaveVisibleRedirect ? 'T' : 'F'));
+
+ // Clear out our nsIParentChannel functions, since a normal parent
+ // channel would actually redirect and not have those values on the new one.
+ // We expect the URI classifier to run on the redirected channel with
+ // the new URI and set these again.
+ mIParentChannelFunctions.Clear();
+
+ // If we had a remote type override, ensure it's been cleared after a
+ // redirect, as it can't apply anymore.
+ mRemoteTypeOverride.reset();
+
+#ifdef ANDROID
+ nsCOMPtr<nsIURI> uriBeingLoaded =
+ AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
+
+ RefPtr<MozPromise<bool, bool, false>> promise;
+ RefPtr<CanonicalBrowsingContext> bc =
+ mParentChannelListener->GetBrowsingContext();
+ nsCOMPtr<nsIWidget> widget =
+ bc ? bc->GetParentProcessWidgetContaining() : nullptr;
+ RefPtr<nsWindow> window = nsWindow::From(widget);
+
+ if (window) {
+ promise = window->OnLoadRequest(uriBeingLoaded,
+ nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
+ nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
+ nullptr, false, bc->IsTopContent());
+ }
+
+ if (promise) {
+ RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback;
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ bool handled = aValue.ResolveValue();
+ if (handled) {
+ cb->OnRedirectVerifyCallback(NS_ERROR_ABORT);
+ } else {
+ cb->OnRedirectVerifyCallback(NS_OK);
+ }
+ }
+ });
+ } else
+#endif /* ANDROID */
+ {
+ aCallback->OnRedirectVerifyCallback(NS_OK);
+ }
+ return NS_OK;
+}
+
+nsIURI* DocumentLoadListener::GetChannelCreationURI() const {
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
+
+ nsCOMPtr<nsIURI> uri;
+ channelLoadInfo->GetChannelCreationOriginalURI(getter_AddRefs(uri));
+ if (uri) {
+ // See channelCreationOriginalURI for more info. We use this instead of the
+ // originalURI of the channel to help us avoid the situation when we use
+ // the URI of a redirect that has failed to happen.
+ return uri;
+ }
+
+ // Otherwise, get the original URI from the channel.
+ mChannel->GetOriginalURI(getter_AddRefs(uri));
+ return uri;
+}
+
+// This method returns the cached result of running the Cross-Origin-Opener
+// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
+bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const {
+ // If we found a COOP mismatch on an earlier channel and then
+ // redirected away from that, we should use that result.
+ if (mHasCrossOriginOpenerPolicyMismatch) {
+ return true;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel) {
+ // Not an nsIHttpChannelInternal assume it's okay to switch.
+ return false;
+ }
+
+ bool isCOOPMismatch = false;
+ Unused << NS_WARN_IF(NS_FAILED(
+ httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
+ return isCOOPMismatch;
+}
+
+auto DocumentLoadListener::AttachStreamFilter()
+ -> RefPtr<ChildEndpointPromise> {
+ LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this));
+
+ StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
+ request->mPromise = new ChildEndpointPromise::Private(__func__);
+ return request->mPromise;
+}
+
+NS_IMETHODIMP DocumentLoadListener::OnProgress(nsIRequest* aRequest,
+ int64_t aProgress,
+ int64_t aProgressMax) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aStatusArg) {
+ nsCOMPtr<nsIChannel> channel = mChannel;
+
+ RefPtr<BrowsingContextWebProgress> webProgress =
+ GetLoadingBrowsingContext()->GetWebProgress();
+ const nsString message(aStatusArg);
+
+ if (webProgress) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() {
+ webProgress->OnStatusChange(webProgress, channel, aStatus,
+ message.get());
+ }));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP DocumentLoadListener::EarlyHint(const nsACString& aLinkHeader,
+ const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader) {
+ LOG(("DocumentLoadListener::EarlyHint.\n"));
+ mEarlyHintsService.EarlyHint(aLinkHeader, GetChannelCreationURI(), mChannel,
+ aReferrerPolicy, aCSPHeader,
+ GetLoadingBrowsingContext());
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/ipc/DocumentLoadListener.h b/netwerk/ipc/DocumentLoadListener.h
new file mode 100644
index 0000000000..98aacc98f8
--- /dev/null
+++ b/netwerk/ipc/DocumentLoadListener.h
@@ -0,0 +1,630 @@
+/* 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_DocumentLoadListener_h
+#define mozilla_net_DocumentLoadListener_h
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/Variant.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+#include "EarlyHintsService.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/PDocumentChannelParent.h"
+#include "mozilla/net/ParentChannelListener.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIBrowser.h"
+#include "nsIChannelEventSink.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIParentChannel.h"
+#include "nsIParentRedirectingChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIRedirectResultListener.h"
+
+#define DOCUMENT_LOAD_LISTENER_IID \
+ { \
+ 0x3b393c56, 0x9e01, 0x11e9, { \
+ 0xa2, 0xa3, 0x2a, 0x2a, 0xe2, 0xdb, 0xcc, 0xe4 \
+ } \
+ }
+
+namespace mozilla {
+namespace dom {
+class CanonicalBrowsingContext;
+struct NavigationIsolationOptions;
+} // namespace dom
+namespace net {
+using ChildEndpointPromise =
+ MozPromise<mozilla::ipc::Endpoint<extensions::PStreamFilterChild>, bool,
+ true>;
+
+// If we've been asked to attach a stream filter to our channel,
+// then we return this promise and defer until we know the final
+// content process. At that point we setup Endpoints between
+// mStramFilterProcessId and the new content process, and send
+// the parent Endpoint to the new process.
+// Once we have confirmation of that being bound in the content
+// process, we resolve the promise the child Endpoint.
+struct StreamFilterRequest {
+ StreamFilterRequest() = default;
+ StreamFilterRequest(StreamFilterRequest&&) = default;
+ ~StreamFilterRequest() {
+ if (mPromise) {
+ mPromise->Reject(false, __func__);
+ }
+ }
+ RefPtr<ChildEndpointPromise::Private> mPromise;
+ mozilla::ipc::Endpoint<extensions::PStreamFilterChild> mChildEndpoint;
+};
+} // namespace net
+} // namespace mozilla
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::net::StreamFilterRequest)
+
+namespace mozilla {
+namespace net {
+
+class LoadInfo;
+
+/**
+ * DocumentLoadListener represents a connecting document load for a
+ * CanonicalBrowsingContext (in the parent process).
+ *
+ * It creates a network channel for the document load and then waits for it to
+ * receive a response (after all redirects are resolved). It then decides where
+ * to handle that load (could be in a different process from the initiator),
+ * and then sets up a real network nsIChannel to deliver the data to the final
+ * destination docshell, maybe through an nsIParentChannel/nsIChildChannel IPDL
+ * layer.
+ *
+ * In the case where this was initiated from an nsDocShell, we also create an
+ * nsIChannel to act as a placeholder within the docshell while this process
+ * completes, and then notify the docshell of a 'redirect' when we replace this
+ * channel with the real one.
+ */
+
+// TODO: We currently don't implement nsIProgressEventSink and forward those
+// to the child. Should we? We get the interface requested.
+class DocumentLoadListener : public nsIInterfaceRequestor,
+ public nsIAsyncVerifyRedirectReadyCallback,
+ public nsIParentChannel,
+ public nsIChannelEventSink,
+ public HttpChannelSecurityWarningReporter,
+ public nsIMultiPartChannelListener,
+ public nsIProgressEventSink,
+ public nsIEarlyHintObserver {
+ public:
+ // See the comment on GetLoadingBrowsingContext for explanation of
+ // aLoadingBrowsingContext.
+ DocumentLoadListener(dom::CanonicalBrowsingContext* aLoadingBrowsingContext,
+ bool aIsDocumentLoad);
+
+ struct OpenPromiseSucceededType {
+ nsTArray<mozilla::ipc::Endpoint<extensions::PStreamFilterParent>>
+ mStreamFilterEndpoints;
+ uint32_t mRedirectFlags;
+ uint32_t mLoadFlags;
+ uint32_t mEarlyHintLinkType;
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>
+ mPromise;
+ };
+ struct OpenPromiseFailedType {
+ nsresult mStatus;
+ nsresult mLoadGroupStatus;
+ // This is set to true if the navigation in the content process should not
+ // be cancelled, as the load is logically continuing within the current
+ // browsing session, just within a different process or browsing context.
+ bool mContinueNavigating = false;
+ };
+
+ using OpenPromise =
+ MozPromise<OpenPromiseSucceededType, OpenPromiseFailedType, true>;
+
+ // Interface which may be provided when performing an <object> or <embed> load
+ // with `DocumentLoadListener`, to allow upgrading the Object load to a proper
+ // Document load.
+ struct ObjectUpgradeHandler : public SupportsWeakPtr {
+ using ObjectUpgradePromise =
+ MozPromise<RefPtr<dom::CanonicalBrowsingContext>, nsresult,
+ true /* isExclusive */>;
+
+ // Upgrade an object load to be a potentially remote document.
+ //
+ // The returned promise will resolve with the BrowsingContext which has been
+ // created in the <object> or <embed> element to finish the load with.
+ virtual RefPtr<ObjectUpgradePromise> UpgradeObjectLoad() = 0;
+ };
+
+ private:
+ // Creates the channel, and then calls AsyncOpen on it.
+ // The DocumentLoadListener will require additional process from the consumer
+ // in order to complete the redirect to the end channel. This is done by
+ // returning a RedirectToRealChannelPromise and then waiting for it to be
+ // resolved or rejected accordingly.
+ // Once that promise is resolved; the consumer no longer needs to hold a
+ // reference to the DocumentLoadListener nor will the consumer required to be
+ // used again.
+ RefPtr<OpenPromise> Open(nsDocShellLoadState* aLoadState, LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey,
+ const Maybe<uint64_t>& aChannelId,
+ const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming,
+ Maybe<dom::ClientInfo>&& aInfo, bool aUrgentStart,
+ dom::ContentParent* aContentParent, nsresult* aRv);
+
+ public:
+ RefPtr<OpenPromise> OpenDocument(
+ nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
+ const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
+ Maybe<bool> aUriModified, Maybe<bool> aIsEmbeddingBlockedError,
+ dom::ContentParent* aContentParent, nsresult* aRv);
+
+ RefPtr<OpenPromise> OpenObject(
+ nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
+ const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
+ uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
+ nsContentPolicyType aContentPolicyType, bool aUrgentStart,
+ dom::ContentParent* aContentParent,
+ ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv);
+
+ // Creates a DocumentLoadListener entirely in the parent process and opens it,
+ // and never needs a DocumentChannel to connect to an existing docshell.
+ // Once we get a response it takes the 'process switch' path to find the right
+ // process and docshell, and delivers the response there directly.
+ static bool LoadInParent(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState,
+ bool aSetNavigating);
+
+ // Creates a DocumentLoadListener directly in the parent process and opens it,
+ // without needing an existing DocumentChannel.
+ // If successful it registers a unique identifier (return in aOutIdent) to
+ // keep it alive until a future DocumentChannel can attach to it, or we fail
+ // and clean up.
+ static bool SpeculativeLoadInParent(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState);
+
+ // Ensures that a load identifier allocated by OpenFromParent has
+ // been deregistered if it hasn't already been claimed.
+ // This also cancels the load.
+ static void CleanupParentLoadAttempt(uint64_t aLoadIdent);
+
+ // Looks up aLoadIdent to find the associated, cleans up the registration
+ static RefPtr<OpenPromise> ClaimParentLoad(DocumentLoadListener** aListener,
+ uint64_t aLoadIdent,
+ Maybe<uint64_t> aChannelId);
+
+ // Called by the DocumentChannelParent if actor got destroyed or the parent
+ // channel got deleted.
+ void Abort();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIASYNCVERIFYREDIRECTREADYCALLBACK
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ NS_DECL_NSIPROGRESSEVENTSINK
+ NS_DECL_NSIEARLYHINTOBSERVER
+
+ // We suspend the underlying channel when replacing ourselves with
+ // the real listener channel.
+ // This helper resumes the underlying channel again, and manually
+ // forwards any nsIStreamListener messages that arrived while we
+ // were suspended (which might have failed).
+ // Returns true if the channel was finished before we could resume it.
+ bool ResumeSuspendedChannel(nsIStreamListener* aListener);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(DOCUMENT_LOAD_LISTENER_IID)
+
+ // Called by the DocumentChannel if cancelled.
+ void Cancel(const nsresult& aStatusCode, const nsACString& aReason);
+
+ nsIChannel* GetChannel() const { return mChannel; }
+
+ uint32_t GetRedirectChannelId() const { return mRedirectChannelId; }
+
+ nsresult ReportSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) override {
+ ReportSecurityMessageParams params;
+ params.mMessageTag = aMessageTag;
+ params.mMessageCategory = aMessageCategory;
+ mSecurityWarningFunctions.AppendElement(
+ SecurityWarningFunction{VariantIndex<0>{}, std::move(params)});
+ return NS_OK;
+ }
+
+ nsresult LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) override {
+ LogBlockedCORSRequestParams params;
+ params.mMessage = aMessage;
+ params.mCategory = aCategory;
+ params.mIsWarning = aIsWarning;
+ mSecurityWarningFunctions.AppendElement(
+ SecurityWarningFunction{VariantIndex<1>{}, std::move(params)});
+ return NS_OK;
+ }
+
+ nsresult LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override {
+ LogMimeTypeMismatchParams params;
+ params.mMessageName = aMessageName;
+ params.mWarning = aWarning;
+ params.mURL = aURL;
+ params.mContentType = aContentType;
+ mSecurityWarningFunctions.AppendElement(
+ SecurityWarningFunction{VariantIndex<2>{}, std::move(params)});
+ return NS_OK;
+ }
+
+ // The content process corresponding to this DocumentLoadListener, or nullptr
+ // if connected to the parent process.
+ dom::ContentParent* GetContentParent() const { return mContentParent; }
+
+ // The process id of the content process that we are being called from
+ // or 0 initiated from a parent process load.
+ base::ProcessId OtherPid() const;
+
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter();
+
+ // EarlyHints aren't supported on ParentProcessDocumentChannels yet, allow
+ // EarlyHints to be cancelled from there (Bug 1819886)
+ void CancelEarlyHintPreloads();
+
+ // Gets the EarlyHint preloads for this document to pass them to the
+ // ContentProcess. Registers them in the EarlyHintRegister
+ void RegisterEarlyHintLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks);
+
+ // Serializes all data needed to setup the new replacement channel
+ // in the content process into the RedirectToRealChannelArgs struct.
+ void SerializeRedirectData(RedirectToRealChannelArgs& aArgs,
+ bool aIsCrossProcess, uint32_t aRedirectFlags,
+ uint32_t aLoadFlags,
+ nsTArray<EarlyHintConnectArgs>&& aEarlyHints,
+ uint32_t aEarlyHintLinkType) const;
+
+ uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
+ uint32_t GetLoadType() const { return mLoadStateLoadType; }
+ bool IsDownload() const { return mIsDownload; }
+ bool IsLoadingJSURI() const { return mIsLoadingJSURI; }
+
+ mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo() {
+ return mLoadingSessionHistoryInfo.get();
+ }
+
+ bool IsDocumentLoad() const { return mIsDocumentLoad; }
+
+ // Determine what process switching behavior a browser element should have.
+ enum ProcessBehavior : uint8_t {
+ // Gecko won't automatically change which process this frame, or it's
+ // subframes, are loaded in.
+ PROCESS_BEHAVIOR_DISABLED,
+
+ // If `useRemoteTabs` is enabled, Gecko will change which process this frame
+ // is loaded in automatically, without calling `performProcessSwitch`.
+ // When `useRemoteSubframes` is enabled, subframes will change processes.
+ PROCESS_BEHAVIOR_STANDARD,
+
+ // Gecko won't automatically change which process this frame is loaded, but
+ // when `useRemoteSubframes` is enabled, subframes will change processes.
+ //
+ // NOTE: This configuration is included only for backwards compatibility,
+ // and will be removed, as it can easily lead to invalid behavior.
+ PROCESS_BEHAVIOR_SUBFRAME_ONLY,
+ };
+
+ protected:
+ virtual ~DocumentLoadListener();
+
+ private:
+ RefPtr<OpenPromise> OpenInParent(nsDocShellLoadState* aLoadState,
+ bool aSupportsRedirectToRealChannel);
+
+ friend class ParentProcessDocumentOpenInfo;
+
+ // Will reject the promise to notify the DLL consumer that we are done.
+ //
+ // If `aContinueNavigating` is true, the navigation in the content process
+ // will not be aborted, as navigation is logically continuing in the existing
+ // browsing session (e.g. due to a process switch or entering the bfcache).
+ void DisconnectListeners(nsresult aStatus, nsresult aLoadGroupStatus,
+ bool aContinueNavigating = false);
+
+ // Called when we were created without a document channel, and creation has
+ // failed, and won't ever be attached.
+ void NotifyDocumentChannelFailed();
+
+ // Initiates the switch from DocumentChannel to the real protocol-specific
+ // channel, and ensures that RedirectToRealChannelFinished is called when
+ // this is complete.
+ void TriggerRedirectToRealChannel(
+ const Maybe<dom::ContentParent*>& aDestinationProcess,
+ nsTArray<StreamFilterRequest> aStreamFilterRequests);
+
+ // Called once the content-process side on setting up a replacement
+ // channel is complete. May wait for the new parent channel to
+ // finish, and then calls into FinishReplacementChannelSetup.
+ void RedirectToRealChannelFinished(nsresult aRv);
+
+ // Completes the replacement of the new channel.
+ // This redirects the ParentChannelListener to forward any future
+ // messages to the new channel, manually forwards any being held
+ // by us, and resumes the underlying source channel.
+ void FinishReplacementChannelSetup(nsresult aResult);
+
+ // Called from `OnStartRequest` to make the decision about whether or not to
+ // change process. This method will return `nullptr` if the current target
+ // process is appropriate.
+ // aWillSwitchToRemote is set to true if we initiate a process switch,
+ // and that the new remote type will be something other than NOT_REMOTE
+ bool MaybeTriggerProcessSwitch(bool* aWillSwitchToRemote);
+
+ // Called when the process switch is going to happen, potentially
+ // asynchronously, from `MaybeTriggerProcessSwitch`.
+ //
+ // aContext should be the target context for the navigation. This will either
+ // be the loading BrowsingContext, the newly created BrowsingContext for an
+ // object or embed element load, or a newly created tab for new tab load.
+ //
+ // If `aIsNewTab` is specified, the navigation in the original process will be
+ // aborted immediately, rather than waiting for a process switch to happen and
+ // the previous page to be unloaded or hidden.
+ void TriggerProcessSwitch(dom::CanonicalBrowsingContext* aContext,
+ const dom::NavigationIsolationOptions& aOptions,
+ bool aIsNewTab = false);
+
+ // A helper for TriggerRedirectToRealChannel that abstracts over
+ // the same-process and cross-process switch cases and returns
+ // a single promise to wait on.
+ using ParentEndpoint =
+ mozilla::ipc::Endpoint<extensions::PStreamFilterParent>;
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ RedirectToRealChannel(uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ const Maybe<dom::ContentParent*>& aDestinationProcess,
+ nsTArray<ParentEndpoint>&& aStreamFilterEndpoints);
+
+ // A helper for RedirectToRealChannel that handles the case where we started
+ // from a content process and are process switching into the parent process.
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ RedirectToParentProcess(uint32_t aRedirectFlags, uint32_t aLoadFlags);
+
+ // Return the Browsing Context that is performing the load.
+ // For document loads, the BC is the one that the (sub)doc
+ // will load into. For <object>/<embed>, it's the embedder document's BC.
+ dom::CanonicalBrowsingContext* GetLoadingBrowsingContext() const;
+
+ // Return the Browsing Context that document is being loaded into. For
+ // non-document loads, this will return nullptr.
+ dom::CanonicalBrowsingContext* GetDocumentBrowsingContext() const;
+ dom::CanonicalBrowsingContext* GetTopBrowsingContext() const;
+
+ // Return the Window Context which which contains the element which the load
+ // is being performed in. For toplevel loads, this will return `nullptr`.
+ dom::WindowGlobalParent* GetParentWindowContext() const;
+
+ void AddURIVisit(nsIChannel* aChannel, uint32_t aLoadFlags);
+ bool HasCrossOriginOpenerPolicyMismatch() const;
+ void ApplyPendingFunctions(nsIParentChannel* aChannel) const;
+
+ void Disconnect(bool aContinueNavigating);
+
+ void MaybeReportBlockedByURLClassifier(nsresult aStatus);
+
+ // Returns true if a channel with aStatus will display
+ // some sort of content (could be the actual channel data,
+ // attempt a uri fixup and new load, or an error page).
+ // Returns false if the docshell will ignore the load entirely.
+ bool DocShellWillDisplayContent(nsresult aStatus);
+
+ void FireStateChange(uint32_t aStateFlags, nsresult aStatus);
+
+ // Returns true if this is a failed load, where we have successfully
+ // created a fixed URI to attempt loading instead.
+ // If successful, this calls DisconnectListeners to completely finish
+ // the current load, and calls BrowsingContext::LoadURI to start the new one.
+ bool MaybeHandleLoadErrorWithURIFixup(nsresult aStatus);
+
+ // This defines a variant that describes all the attribute setters (and their
+ // parameters) from nsIParentChannel
+ //
+ // SetClassifierMatchedInfo(const nsACString& aList, const nsACString&
+ // aProvider, const nsACString& aFullHash) = 0;
+ // SetClassifierMatchedTrackingInfo(const nsACString& aLists, const
+ // nsACString& aFullHashes) = 0; NotifyClassificationFlags(uint32_t
+ // aClassificationFlags, bool aIsThirdParty) = 0;
+ struct ClassifierMatchedInfoParams {
+ nsCString mList;
+ nsCString mProvider;
+ nsCString mFullHash;
+ };
+
+ struct ClassifierMatchedTrackingInfoParams {
+ nsCString mLists;
+ nsCString mFullHashes;
+ };
+
+ struct ClassificationFlagsParams {
+ uint32_t mClassificationFlags;
+ bool mIsThirdParty;
+ };
+
+ using IParentChannelFunction =
+ mozilla::Variant<ClassifierMatchedInfoParams,
+ ClassifierMatchedTrackingInfoParams,
+ ClassificationFlagsParams>;
+
+ // Store a list of all the attribute setters that have been called on this
+ // channel, so that we can repeat them on the real channel that we redirect
+ // to.
+ nsTArray<IParentChannelFunction> mIParentChannelFunctions;
+
+ // This defines a variant this describes all the functions
+ // from HttpChannelSecurityWarningReporter so that we can forward
+ // them on to the real channel.
+
+ struct ReportSecurityMessageParams {
+ nsString mMessageTag;
+ nsString mMessageCategory;
+ };
+
+ struct LogBlockedCORSRequestParams {
+ nsString mMessage;
+ nsCString mCategory;
+ bool mIsWarning;
+ };
+
+ struct LogMimeTypeMismatchParams {
+ nsCString mMessageName;
+ bool mWarning = false;
+ nsString mURL;
+ nsString mContentType;
+ };
+
+ using SecurityWarningFunction =
+ mozilla::Variant<ReportSecurityMessageParams, LogBlockedCORSRequestParams,
+ LogMimeTypeMismatchParams>;
+ nsTArray<SecurityWarningFunction> mSecurityWarningFunctions;
+
+ // TODO Backtrack this.
+ // The set of nsIStreamListener functions that got called on this
+ // listener, so that we can replay them onto the replacement channel's
+ // listener. This should generally only be OnStartRequest, since we
+ // Suspend() the channel at that point, but it can fail sometimes
+ // so we have to support holding a list.
+ nsTArray<StreamListenerFunction> mStreamListenerFunctions;
+
+ nsCOMPtr<nsIChannel> mChannel;
+
+ Maybe<uint64_t> mDocumentChannelId;
+
+ // An instance of ParentChannelListener that we use as a listener
+ // between mChannel (and any future redirected mChannels) and us.
+ // This handles service worker interception, and retargetting
+ // OnDataAvailable/OnStopRequest messages onto the listener that
+ // replaces us.
+ RefPtr<ParentChannelListener> mParentChannelListener;
+
+ // Get the channel creation URI for constructing the channel in the content
+ // process. See the function for more details.
+ nsIURI* GetChannelCreationURI() const;
+
+ // The original navigation timing information containing various timestamps
+ // such as when the original load started.
+ // This will be passed back to the new content process should a process
+ // switch occurs.
+ RefPtr<nsDOMNavigationTiming> mTiming;
+
+ net::EarlyHintsService mEarlyHintsService;
+
+ // An optional ObjectUpgradeHandler which can be used to upgrade an <object>
+ // or <embed> element to contain a nsFrameLoader, allowing us to switch them
+ // into a different process.
+ //
+ // A weak pointer is held in order to avoid reference cycles.
+ WeakPtr<ObjectUpgradeHandler> mObjectUpgradeHandler;
+
+ // Used to identify an internal redirect in redirect chain.
+ // True when we have seen at least one non-interal redirect.
+ bool mHaveVisibleRedirect = false;
+
+ // Pending stream filter requests which should be attached when redirecting to
+ // the real channel. Moved into `TriggerRedirectToRealChannel` when the
+ // connection is ready.
+ nsTArray<StreamFilterRequest> mStreamFilterRequests;
+
+ nsString mSrcdocData;
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo>
+ mLoadingSessionHistoryInfo;
+
+ RefPtr<dom::WindowGlobalParent> mParentWindowContext;
+
+ // Flags from nsDocShellLoadState::LoadFlags/Type that we want to make
+ // available to the new docshell if we switch processes.
+ uint32_t mLoadStateExternalLoadFlags = 0;
+ uint32_t mLoadStateInternalLoadFlags = 0;
+ uint32_t mLoadStateLoadType = 0;
+
+ // Indicates if this load is a download.
+ bool mIsDownload = false;
+
+ // Indicates if we are loading a javascript URI.
+ bool mIsLoadingJSURI = false;
+
+ // Corresponding redirect channel registrar Id for the final channel that
+ // we want to use when redirecting the child, or doing a process switch.
+ // 0 means redirection is not started.
+ uint64_t mRedirectChannelId = 0;
+ // Set to true once we initiate the redirect to a real channel (either
+ // via a process switch or a same-process redirect, and Suspend the
+ // underlying channel.
+ bool mInitiatedRedirectToRealChannel = false;
+ // The value of GetApplyConversion on mChannel when OnStartRequest
+ // was called. We override it to false to prevent a conversion
+ // helper from being installed, but we need to restore the value
+ // later.
+ bool mOldApplyConversion = false;
+ // Set to true if any previous channel that we redirected away
+ // from had a COOP mismatch.
+ bool mHasCrossOriginOpenerPolicyMismatch = false;
+ // Set to true if we've received OnStopRequest, and shouldn't
+ // setup a reference from the ParentChannelListener to the replacement
+ // channel.
+ bool mIsFinished = false;
+
+ // The id of the currently pending load which is
+ // passed to the childChannel in order to identify it in the new process.
+ uint64_t mLoadIdentifier = 0;
+
+ Maybe<nsCString> mOriginalUriString;
+
+ // Parent-initiated loads do not support redirects to real channels.
+ bool mSupportsRedirectToRealChannel = true;
+
+ Maybe<nsCString> mRemoteTypeOverride;
+
+ // The ContentParent which this channel is currently connected to, or nullptr
+ // if connected to the parent process.
+ RefPtr<dom::ContentParent> mContentParent;
+
+ void RejectOpenPromise(nsresult aStatus, nsresult aLoadGroupStatus,
+ bool aContinueNavigating, const char* aLocation) {
+ // It is possible for mOpenPromise to not be set if AsyncOpen failed and
+ // the DocumentChannel got canceled.
+ if (!mOpenPromiseResolved && mOpenPromise) {
+ mOpenPromise->Reject(OpenPromiseFailedType({aStatus, aLoadGroupStatus,
+ aContinueNavigating}),
+ aLocation);
+ mOpenPromiseResolved = true;
+ }
+ }
+ RefPtr<OpenPromise::Private> mOpenPromise;
+ bool mOpenPromiseResolved = false;
+
+ const bool mIsDocumentLoad;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DocumentLoadListener, DOCUMENT_LOAD_LISTENER_IID)
+
+inline nsISupports* ToSupports(DocumentLoadListener* aObj) {
+ return static_cast<nsIInterfaceRequestor*>(aObj);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DocumentChannelParent_h
diff --git a/netwerk/ipc/InputChannelThrottleQueueChild.cpp b/netwerk/ipc/InputChannelThrottleQueueChild.cpp
new file mode 100644
index 0000000000..ef7a916b79
--- /dev/null
+++ b/netwerk/ipc/InputChannelThrottleQueueChild.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#include "InputChannelThrottleQueueChild.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED0(InputChannelThrottleQueueChild, ThrottleQueue)
+
+NS_IMETHODIMP
+InputChannelThrottleQueueChild::RecordRead(uint32_t aBytesRead) {
+ ThrottleQueue::RecordRead(aBytesRead);
+
+ RefPtr<InputChannelThrottleQueueChild> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "InputChannelThrottleQueueChild::RecordRead", [self, aBytesRead]() {
+ if (self->CanSend()) {
+ Unused << self->SendRecordRead(aBytesRead);
+ }
+ }));
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/InputChannelThrottleQueueChild.h b/netwerk/ipc/InputChannelThrottleQueueChild.h
new file mode 100644
index 0000000000..a758dac417
--- /dev/null
+++ b/netwerk/ipc/InputChannelThrottleQueueChild.h
@@ -0,0 +1,33 @@
+/* -*- 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 InputChannelThrottleQueueChild_h__
+#define InputChannelThrottleQueueChild_h__
+
+#include "mozilla/net/PInputChannelThrottleQueueChild.h"
+#include "mozilla/net/ThrottleQueue.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace net {
+
+class InputChannelThrottleQueueChild final
+ : public PInputChannelThrottleQueueChild,
+ public ThrottleQueue {
+ public:
+ friend class PInputChannelThrottleQueueChild;
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit InputChannelThrottleQueueChild() = default;
+ NS_IMETHOD RecordRead(uint32_t aBytesRead) override;
+
+ private:
+ virtual ~InputChannelThrottleQueueChild() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // InputChannelThrottleQueueChild_h__
diff --git a/netwerk/ipc/InputChannelThrottleQueueParent.cpp b/netwerk/ipc/InputChannelThrottleQueueParent.cpp
new file mode 100644
index 0000000000..d1e6e82f9f
--- /dev/null
+++ b/netwerk/ipc/InputChannelThrottleQueueParent.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#include "InputChannelThrottleQueueParent.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsIOService.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(InputChannelThrottleQueueParent)
+NS_INTERFACE_MAP_BEGIN(InputChannelThrottleQueueParent)
+ NS_INTERFACE_MAP_ENTRY(nsIInputChannelThrottleQueue)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(InputChannelThrottleQueueParent)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+InputChannelThrottleQueueParent::Release(void) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+
+ if (!nsAutoRefCnt::isThreadSafe) {
+ NS_ASSERT_OWNINGTHREAD(InputChannelThrottleQueueParent);
+ }
+
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "InputChannelThrottleQueueParent");
+
+ if (count == 0) {
+ if (!nsAutoRefCnt::isThreadSafe) {
+ NS_ASSERT_OWNINGTHREAD(InputChannelThrottleQueueParent);
+ }
+
+ mRefCnt = 1; /* stabilize */
+ delete (this);
+ return 0;
+ }
+
+ // When ref count goes down to 1 (held internally by IPDL), it means that
+ // we are done with this ThrottleQueue. We should send a delete message
+ // to delete the InputChannelThrottleQueueChild in socket process.
+ if (count == 1 && CanSend()) {
+ mozilla::Unused << Send__delete__(this);
+ return 1;
+ }
+ return count;
+}
+
+mozilla::ipc::IPCResult InputChannelThrottleQueueParent::RecvRecordRead(
+ const uint32_t& aBytesRead) {
+ mBytesProcessed += aBytesRead;
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+InputChannelThrottleQueueParent::RecordRead(uint32_t aBytesRead) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputChannelThrottleQueueParent::Available(uint32_t aRemaining,
+ uint32_t* aAvailable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputChannelThrottleQueueParent::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;
+
+ RefPtr<InputChannelThrottleQueueParent> self = this;
+ gIOService->CallOrWaitForSocketProcess(
+ [self, meanBytesPerSecond(mMeanBytesPerSecond),
+ maxBytesPerSecond(mMaxBytesPerSecond)] {
+ Unused << SocketProcessParent::GetSingleton()
+ ->SendPInputChannelThrottleQueueConstructor(
+ self, meanBytesPerSecond, maxBytesPerSecond);
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputChannelThrottleQueueParent::BytesProcessed(uint64_t* aResult) {
+ *aResult = mBytesProcessed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputChannelThrottleQueueParent::WrapStream(nsIInputStream* aInputStream,
+ nsIAsyncInputStream** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputChannelThrottleQueueParent::GetMeanBytesPerSecond(
+ uint32_t* aMeanBytesPerSecond) {
+ NS_ENSURE_ARG(aMeanBytesPerSecond);
+
+ *aMeanBytesPerSecond = mMeanBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputChannelThrottleQueueParent::GetMaxBytesPerSecond(
+ uint32_t* aMaxBytesPerSecond) {
+ NS_ENSURE_ARG(aMaxBytesPerSecond);
+
+ *aMaxBytesPerSecond = mMaxBytesPerSecond;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/InputChannelThrottleQueueParent.h b/netwerk/ipc/InputChannelThrottleQueueParent.h
new file mode 100644
index 0000000000..74d1a00bd7
--- /dev/null
+++ b/netwerk/ipc/InputChannelThrottleQueueParent.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef InputChannelThrottleQueueParent_h__
+#define InputChannelThrottleQueueParent_h__
+
+#include "nsISupportsImpl.h"
+#include "nsIThrottledInputChannel.h"
+#include "mozilla/net/PInputChannelThrottleQueueParent.h"
+
+namespace mozilla {
+namespace net {
+
+#define INPUT_CHANNEL_THROTTLE_QUEUE_PARENT_IID \
+ { \
+ 0x4f151655, 0x70b3, 0x4350, { \
+ 0x9b, 0xd9, 0xe3, 0x2b, 0xe5, 0xeb, 0xb2, 0x9e \
+ } \
+ }
+
+class InputChannelThrottleQueueParent final
+ : public PInputChannelThrottleQueueParent,
+ public nsIInputChannelThrottleQueue {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE
+ NS_DECLARE_STATIC_IID_ACCESSOR(INPUT_CHANNEL_THROTTLE_QUEUE_PARENT_IID)
+
+ friend class PInputChannelThrottleQueueParent;
+
+ explicit InputChannelThrottleQueueParent() = default;
+ mozilla::ipc::IPCResult RecvRecordRead(const uint32_t& aBytesRead);
+ void ActorDestroy(ActorDestroyReason aWhy) override {}
+
+ private:
+ virtual ~InputChannelThrottleQueueParent() = default;
+
+ uint64_t mBytesProcessed{0};
+ uint32_t mMeanBytesPerSecond{0};
+ uint32_t mMaxBytesPerSecond{0};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(InputChannelThrottleQueueParent,
+ INPUT_CHANNEL_THROTTLE_QUEUE_PARENT_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // InputChannelThrottleQueueParent_h__
diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh
new file mode 100644
index 0000000000..44af8d4808
--- /dev/null
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -0,0 +1,623 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=c: */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PHttpChannel;
+include ClientIPCTypes;
+include URIParams;
+include IPCServiceWorkerDescriptor;
+include IPCStream;
+include PBackgroundSharedTypes;
+include DOMTypes;
+include ProtocolTypes;
+
+include "mozilla/dom/FetchIPCTypes.h";
+include "mozilla/dom/PropertyBagUtils.h";
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/ipc/URIUtils.h";
+include "mozilla/net/CacheInfoIPCTypes.h";
+include "mozilla/AntiTrackingIPCUtils.h";
+include "mozilla/net/ClassOfService.h";
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using mozilla::net::RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h";
+using struct nsHttpAtom from "nsHttp.h";
+using class mozilla::net::nsHttpResponseHead from "nsHttpResponseHead.h";
+using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
+[RefCounted] using class nsIPropertyBag2 from "nsIPropertyBag2.h";
+[RefCounted] using class nsDOMNavigationTiming from "nsDOMNavigationTiming.h";
+[RefCounted] using class nsDocShellLoadState from "nsDocShellLoadState.h";
+using nsContentPolicyType from "nsIContentPolicy.h";
+using mozilla::net::PreferredAlternativeDataDeliveryTypeIPC from "nsICacheInfoChannel.h";
+using nsILoadInfo::CrossOriginEmbedderPolicy from "nsILoadInfo.h";
+using nsILoadInfo::StoragePermissionState from "nsILoadInfo.h";
+using struct mozilla::dom::LoadingSessionHistoryInfo from "mozilla/dom/SessionHistoryEntry.h";
+using mozilla::dom::RequestMode from "mozilla/dom/RequestBinding.h";
+using mozilla::net::LinkHeader from "nsNetUtil.h";
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// CookieJarSettings IPDL structs
+//-----------------------------------------------------------------------------
+
+struct CookiePermissionData
+{
+ PrincipalInfo principalInfo;
+ uint32_t cookiePermission;
+};
+
+struct CookieJarSettingsArgs
+{
+ // Copy of the cookie jar settings for the top-level document.
+ uint32_t cookieBehavior;
+ bool isFirstPartyIsolated;
+ bool shouldResistFingerprinting;
+ bool isOnContentBlockingAllowList;
+ CookiePermissionData[] cookiePermissions;
+ bool isFixed;
+ nsString partitionKey;
+ bool hasFingerprintingRandomizationKey;
+ uint8_t[] fingerprintingRandomizationKey;
+};
+
+//-----------------------------------------------------------------------------
+// Preferrer alternative data type
+//-----------------------------------------------------------------------------
+
+struct PreferredAlternativeDataTypeParams
+{
+ nsCString type;
+ nsCString contentType;
+ PreferredAlternativeDataDeliveryTypeIPC deliverAltData;
+};
+
+//-----------------------------------------------------------------------------
+// LoadInfo IPDL structs
+//-----------------------------------------------------------------------------
+
+struct RedirectHistoryEntryInfo
+{
+ PrincipalInfo principalInfo;
+ URIParams? referrerUri;
+ nsCString remoteAddress;
+};
+
+struct InterceptionInfoArg
+{
+ PrincipalInfo? triggeringPrincipalInfo;
+ nsContentPolicyType contentPolicyType;
+ RedirectHistoryEntryInfo[] redirectChain;
+ bool fromThirdParty;
+};
+
+struct LoadInfoArgs
+{
+ PrincipalInfo? requestingPrincipalInfo;
+ PrincipalInfo triggeringPrincipalInfo;
+ PrincipalInfo? principalToInheritInfo;
+ PrincipalInfo? topLevelPrincipalInfo;
+ URIParams? resultPrincipalURI;
+ nsCString triggeringRemoteType;
+ nsID sandboxedNullPrincipalID;
+ uint32_t securityFlags;
+ uint32_t sandboxFlags;
+ uint32_t triggeringSandboxFlags;
+ uint64_t triggeringWindowId;
+ bool triggeringStorageAccess;
+ nsContentPolicyType contentPolicyType;
+ uint32_t tainting;
+ bool blockAllMixedContent;
+ bool upgradeInsecureRequests;
+ bool browserUpgradeInsecureRequests;
+ bool browserDidUpgradeInsecureRequests;
+ bool browserWouldUpgradeInsecureRequests;
+ bool forceAllowDataURI;
+ bool allowInsecureRedirectToDataURI;
+ bool skipContentPolicyCheckForWebRequest;
+ bool originalFrameSrcLoad;
+ bool forceInheritPrincipalDropped;
+ uint64_t innerWindowID;
+ uint64_t browsingContextID;
+ uint64_t frameBrowsingContextID;
+ bool initialSecurityCheckDone;
+ bool isInThirdPartyContext;
+ bool? isThirdPartyContextToTopWindow;
+ bool isFormSubmission;
+ bool sendCSPViolationEvents;
+ OriginAttributes originAttributes;
+ RedirectHistoryEntryInfo[] redirectChainIncludingInternalRedirects;
+ RedirectHistoryEntryInfo[] redirectChain;
+ bool hasInjectedCookieForCookieBannerHandling;
+ bool wasSchemelessInput;
+
+ /**
+ * ClientInfo structure representing the window or worker that triggered
+ * this network request. May be Nothing if its a system internal request.
+ */
+ IPCClientInfo? clientInfo;
+
+ /**
+ * Non-subresource requests will result in the creation of a window or
+ * worker client. The reserved and initial ClientInfo values represent
+ * this resulting client. An initial ClientInfo represents an initial
+ * about:blank window that will be re-used while a reserved ClientInfo
+ * represents a to-be-newly-created window/worker.
+ */
+ IPCClientInfo? reservedClientInfo;
+ IPCClientInfo? initialClientInfo;
+
+ /**
+ * Subresource loads may have a controller set based on their owning
+ * window/worker client. We must send this across IPC to support
+ * performing interception in the parent.
+ */
+ IPCServiceWorkerDescriptor? controller;
+
+ nsCString[] corsUnsafeHeaders;
+ bool forcePreflight;
+ bool isPreflight;
+ bool loadTriggeredFromExternal;
+ bool serviceWorkerTaintingSynthesized;
+ bool documentHasUserInteracted;
+ bool allowListFutureDocumentsCreatedFromThisRedirectChain;
+ bool needForCheckingAntiTrackingHeuristic;
+ nsString cspNonce;
+ nsString integrityMetadata;
+ bool skipContentSniffing;
+ uint32_t httpsOnlyStatus;
+ bool hstsStatus;
+ bool hasValidUserGestureActivation;
+ bool allowDeprecatedSystemRequests;
+ bool isInDevToolsContext;
+ bool parserCreatedScript;
+ bool isFromProcessingFrameAttributes;
+ bool isMediaRequest;
+ bool isMediaInitialRequest;
+ bool isFromObjectOrEmbed;
+ CookieJarSettingsArgs cookieJarSettings;
+ uint32_t requestBlockingReason;
+ CSPInfo? cspToInheritInfo;
+ StoragePermissionState storagePermission;
+ uint64_t? overriddenFingerprintingSettings;
+ bool isMetaRefresh;
+ CrossOriginEmbedderPolicy loadingEmbedderPolicy;
+ bool originTrialCoepCredentiallessEnabledForTopLevel;
+ nullable nsIURI unstrippedURI;
+ InterceptionInfoArg? interceptionInfo;
+};
+
+/**
+ * This structure is used to carry selected properties of a LoadInfo
+ * object to child processes to merge LoadInfo changes from the parent
+ * process. We don't want to use LoadInfoArgs for that since it's
+ * too huge and we only care about small subpart of properties anyway.
+ */
+struct ParentLoadInfoForwarderArgs
+{
+ // WebExtextensions' WebRequest API allows extensions to intercept and
+ // redirect a channel to a data URI. This modifications happens in
+ // the parent and needs to be mirrored to the child so that security
+ // checks can pass.
+ bool allowInsecureRedirectToDataURI;
+
+ // The ServiceWorker controller that may be set in the parent when
+ // interception occurs.
+ IPCServiceWorkerDescriptor? controller;
+
+ // The service worker may synthesize a Response with a particular
+ // tainting value.
+ uint32_t tainting;
+
+ // This flag is used for any browsing context where we should not sniff
+ // the content type. E.g if an iframe has the XCTO nosniff header, then
+ // that flag is set to true so we skip content sniffing for that browsing
+ bool skipContentSniffing;
+
+ uint32_t httpsOnlyStatus;
+
+ bool hstsStatus;
+
+ // Returns true if at the time of the loadinfo construction the document
+ // that triggered this load has the bit hasValidTransientUserGestureActivation
+ // set or the load was triggered from External. (Mostly this bool is used
+ // in the context of Sec-Fetch-User.)
+ bool hasValidUserGestureActivation;
+
+ // The SystemPrincipal is disallowed to make requests to the public web
+ // and all requests will be cancelled. Setting this flag to true prevents
+ // the request from being cancelled.
+ bool allowDeprecatedSystemRequests;
+
+ bool isInDevToolsContext;
+
+ // Only ever returns true if the loadinfo is of TYPE_SCRIPT and
+ // the script was created by the HTML parser.
+ bool parserCreatedScript;
+
+ // Sandbox Flags of the Document that triggered the load
+ uint32_t triggeringSandboxFlags;
+
+ // Window ID and UsingStorageAccess of the Document that triggered the load.
+ // Used by the Storage Access API to determine if SubDocument loads should
+ // be partitioned or not.
+ uint64_t triggeringWindowId;
+ bool triggeringStorageAccess;
+
+ // We must also note that the tainting value was explicitly set
+ // by the service worker.
+ bool serviceWorkerTaintingSynthesized;
+
+ bool documentHasUserInteracted;
+ bool allowListFutureDocumentsCreatedFromThisRedirectChain;
+
+ CookieJarSettingsArgs? cookieJarSettings;
+
+ uint32_t requestBlockingReason;
+
+ StoragePermissionState storagePermission;
+
+ uint64_t? overriddenFingerprintingSettings;
+
+ bool isMetaRefresh;
+
+ bool? isThirdPartyContextToTopWindow;
+
+ bool isInThirdPartyContext;
+
+ nullable nsIURI unstrippedURI;
+
+ // IMPORTANT: when you add new properites here you must also update
+ // LoadInfoToParentLoadInfoForwarder and MergeParentLoadInfoForwarder
+ // in BackgroundUtils.cpp/.h!
+};
+
+/**
+ * This structure is used to carry selected properties of a LoadInfo
+ * object to the parent process that might have changed in the child
+ * during a redirect. We don't want to use LoadInfoArgs for that since
+ * it's too huge and we only care about small subpart of properties
+ * anyway.
+ */
+struct ChildLoadInfoForwarderArgs
+{
+ // The reserved and initial ClientInfo values may change during a
+ // redirect if the new channel is cross-origin to the old channel.
+ IPCClientInfo? reservedClientInfo;
+ IPCClientInfo? initialClientInfo;
+
+ // The ServiceWorker controller may be cleared in the child during
+ // a redirect.
+ IPCServiceWorkerDescriptor? controller;
+
+ uint32_t requestBlockingReason;
+};
+
+//-----------------------------------------------------------------------------
+// HTTP IPDL structs
+//-----------------------------------------------------------------------------
+
+struct CorsPreflightArgs
+{
+ nsCString[] unsafeHeaders;
+};
+
+struct HttpChannelOpenArgs
+{
+ nullable nsIURI uri;
+ // - TODO: bug 571161: unclear if any HTTP channel clients ever
+ // set originalURI != uri (about:credits?); also not clear if
+ // chrome channel would ever need to know. Get rid of next arg?
+ nullable nsIURI original;
+ nullable nsIURI doc;
+ nullable nsIReferrerInfo referrerInfo;
+ nullable nsIURI apiRedirectTo;
+ nullable nsIURI topWindowURI;
+ RequestHeaderTuples requestHeaders;
+ PreferredAlternativeDataTypeParams[] preferredAlternativeTypes;
+ TimeStamp launchServiceWorkerStart;
+ TimeStamp launchServiceWorkerEnd;
+ TimeStamp dispatchFetchEventStart;
+ TimeStamp dispatchFetchEventEnd;
+ TimeStamp handleFetchEventStart;
+ TimeStamp handleFetchEventEnd;
+ TimeStamp navigationStartTimeStamp;
+ uint64_t startPos;
+ uint64_t requestContextID;
+ uint64_t channelId;
+ uint64_t contentWindowId;
+ uint64_t browserId;
+ uint64_t earlyHintPreloaderId;
+ nsCString requestMethod;
+ ClassOfService classOfService;
+ nsCString entityID;
+ nsCString appCacheClientID;
+ CorsPreflightArgs? preflightArgs;
+ nsCString contentTypeHint;
+ nsString integrityMetadata;
+ IPCStream? uploadStream;
+ LoadInfoArgs loadInfo;
+ uint32_t loadFlags;
+ uint32_t thirdPartyFlags;
+ uint32_t tlsFlags;
+ uint32_t cacheKey;
+ uint32_t initialRwin;
+ uint32_t redirectMode;
+ int16_t priority;
+ bool uploadStreamHasHeaders;
+ bool allowSTS;
+ bool resumeAt;
+ bool allowSpdy;
+ bool allowHttp3;
+ bool allowAltSvc;
+ bool beConservative;
+ bool bypassProxy;
+ bool blockAuthPrompt;
+ bool allowStaleCacheContent;
+ RequestMode requestMode;
+ bool forceValidateCacheContent;
+ bool preferCacheLoadOverBypass;
+ bool forceMainDocumentChannel;
+ uint8_t redirectionLimit;
+ nsString classicScriptHintCharset;
+ nsString documentCharacterSet;
+ bool isUserAgentHeaderModified;
+};
+
+struct HttpChannelConnectArgs
+{
+ uint32_t registrarId;
+};
+
+union HttpChannelCreationArgs
+{
+ HttpChannelOpenArgs; // For AsyncOpen: the common case.
+ HttpChannelConnectArgs; // Used for redirected-to channels
+};
+
+struct ProxyInfoCloneArgs
+{
+ nsCString type;
+ nsCString host;
+ int32_t port;
+ nsCString username;
+ nsCString password;
+ uint32_t flags;
+ uint32_t timeout;
+ uint32_t resolveFlags;
+ nsCString proxyAuthorizationHeader;
+ nsCString connectionIsolationKey;
+};
+
+struct HttpConnectionInfoCloneArgs
+{
+ nsCString host;
+ int32_t port;
+ nsCString npnToken;
+ nsCString username;
+ OriginAttributes originAttributes;
+ bool endToEndSSL;
+ nsCString routedHost;
+ int32_t routedPort;
+ bool anonymous;
+ bool aPrivate; // use prefix to avoid code generation error
+ bool insecureScheme;
+ bool noSpdy;
+ bool beConservative;
+ bool bypassProxy;
+ bool anonymousAllowClientCert;
+ bool fallbackConnection;
+ uint32_t tlsFlags;
+ bool isolated;
+ bool isTrrServiceChannel;
+ uint8_t trrMode;
+ bool isIPv4Disabled;
+ bool isIPv6Disabled;
+ nsCString topWindowOrigin;
+ bool isHttp3;
+ bool webTransport;
+ bool hasIPHintAddress;
+ nsCString echConfig;
+ ProxyInfoCloneArgs[] proxyInfo;
+};
+
+struct ConsoleReportCollected {
+ uint32_t errorFlags;
+ nsCString category;
+ uint32_t propertiesFile;
+ nsCString sourceFileURI;
+ uint32_t lineNumber;
+ uint32_t columnNumber;
+ nsCString messageName;
+ nsString[] stringParams;
+};
+
+struct CookieStruct
+{
+ nsCString name;
+ nsCString value;
+ nsCString host;
+ nsCString path;
+ int64_t expiry;
+ int64_t lastAccessed;
+ int64_t creationTime;
+ bool isHttpOnly;
+ bool isSession;
+ bool isSecure;
+ bool isPartitioned;
+ int32_t sameSite;
+ int32_t rawSameSite;
+ uint8_t schemeMap;
+};
+
+struct DocumentCreationArgs {
+ bool uriModified;
+ bool isEmbeddingBlockedError;
+};
+
+struct ObjectCreationArgs {
+ uint64_t embedderInnerWindowId;
+ uint32_t loadFlags;
+ nsContentPolicyType contentPolicyType;
+ bool isUrgentStart;
+};
+
+union DocumentChannelElementCreationArgs {
+ DocumentCreationArgs;
+ ObjectCreationArgs;
+};
+
+struct DocumentChannelCreationArgs {
+ nsDocShellLoadState loadState;
+ TimeStamp asyncOpenTime;
+ uint64_t channelId;
+ uint32_t cacheKey;
+ nullable nsDOMNavigationTiming timing;
+ IPCClientInfo? initialClientInfo;
+ DocumentChannelElementCreationArgs elementCreationArgs;
+ uint64_t parentInitiatedNavigationEpoch;
+};
+
+struct EarlyHintConnectArgs {
+ LinkHeader link;
+ uint64_t earlyHintPreloaderId;
+};
+
+struct RedirectToRealChannelArgs {
+ uint32_t registrarId;
+ nullable nsIURI uri;
+ uint32_t newLoadFlags;
+ ReplacementChannelConfigInit? init;
+ LoadInfoArgs loadInfo;
+ uint64_t channelId;
+ nullable nsIURI originalURI;
+ uint32_t redirectMode;
+ uint32_t redirectFlags;
+ uint32_t? contentDisposition;
+ nsString? contentDispositionFilename;
+ nullable nsIPropertyBag2 properties;
+ uint32_t loadStateExternalLoadFlags;
+ uint32_t loadStateInternalLoadFlags;
+ uint32_t loadStateLoadType;
+ nullable nsDOMNavigationTiming timing;
+ nsString srcdocData;
+ nullable nsIURI baseUri;
+ LoadingSessionHistoryInfo? loadingSessionHistoryInfo;
+ uint64_t loadIdentifier;
+ nsCString? originalUriString;
+ EarlyHintConnectArgs[] earlyHints;
+ uint32_t earlyHintLinkType;
+};
+
+struct TimingStructArgs {
+ TimeStamp domainLookupStart;
+ TimeStamp domainLookupEnd;
+ TimeStamp connectStart;
+ TimeStamp tcpConnectEnd;
+ TimeStamp secureConnectionStart;
+ TimeStamp connectEnd;
+ TimeStamp requestStart;
+ TimeStamp responseStart;
+ TimeStamp responseEnd;
+ TimeStamp transactionPending;
+};
+
+struct ResourceTimingStructArgs {
+ TimeStamp domainLookupStart;
+ TimeStamp domainLookupEnd;
+ TimeStamp connectStart;
+ TimeStamp tcpConnectEnd;
+ TimeStamp secureConnectionStart;
+ TimeStamp connectEnd;
+ TimeStamp requestStart;
+ TimeStamp responseStart;
+ TimeStamp responseEnd;
+ TimeStamp fetchStart;
+ TimeStamp redirectStart;
+ TimeStamp redirectEnd;
+ uint64_t transferSize;
+ uint64_t encodedBodySize;
+
+ // Not actually part of resource timing, but not part of the transaction
+ // timings either. These need to be passed to HttpChannelChild along with
+ // the rest of the timings so the timing information in the child is complete.
+ TimeStamp cacheReadStart;
+ TimeStamp cacheReadEnd;
+
+ TimeStamp transactionPending;
+};
+
+struct HttpActivity
+{
+ nsCString host;
+ int32_t port;
+ bool endToEndSSL;
+};
+
+struct HttpConnectionActivity
+{
+ nsCString connInfoKey;
+ nsCString host;
+ int32_t port;
+ bool ssl;
+ bool hasECH;
+ bool isHttp3;
+};
+
+union HttpActivityArgs
+{
+ uint64_t;
+ HttpActivity;
+ HttpConnectionActivity;
+};
+
+struct TransactionObserverResult
+{
+ bool versionOk;
+ bool authOk;
+ nsresult closeReason;
+};
+
+struct SpeculativeConnectionOverriderArgs {
+ uint32_t parallelSpeculativeConnectLimit;
+ bool ignoreIdle;
+ bool isFromPredictor;
+ bool allow1918;
+};
+
+//-----------------------------------------------------------------------------
+// GIO IPDL structs
+//-----------------------------------------------------------------------------
+
+struct GIOChannelOpenArgs
+{
+ URIParams uri;
+ uint64_t startPos;
+ nsCString entityID;
+ IPCStream? uploadStream;
+ LoadInfoArgs loadInfo;
+ uint32_t loadFlags;
+};
+
+struct GIOChannelConnectArgs
+{
+ uint32_t channelId;
+};
+
+union GIOChannelCreationArgs
+{
+ GIOChannelOpenArgs; // For AsyncOpen: the common case.
+ GIOChannelConnectArgs; // Used for redirected-to channels
+};
+
+struct RemoteStreamInfo {
+ nullable nsIInputStream inputStream;
+ nsCString contentType;
+ int64_t contentLength;
+};
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp
new file mode 100644
index 0000000000..fe2b3df0bf
--- /dev/null
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -0,0 +1,337 @@
+
+/* 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 "nsHttp.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/ChildDNSService.h"
+#include "mozilla/net/CookieServiceChild.h"
+#include "mozilla/net/DataChannelChild.h"
+#ifdef MOZ_WIDGET_GTK
+# include "mozilla/net/GIOChannelChild.h"
+#endif
+#include "mozilla/net/FileChannelChild.h"
+#include "mozilla/net/WebSocketChannelChild.h"
+#include "mozilla/net/WebSocketEventListenerChild.h"
+#include "mozilla/net/DNSRequestChild.h"
+#include "mozilla/net/IPCTransportProvider.h"
+#include "mozilla/dom/network/TCPSocketChild.h"
+#include "mozilla/dom/network/TCPServerSocketChild.h"
+#include "mozilla/dom/network/UDPSocketChild.h"
+#include "mozilla/net/AltDataOutputStreamChild.h"
+#include "mozilla/net/SocketProcessBridgeChild.h"
+#ifdef MOZ_WEBRTC
+# include "mozilla/net/StunAddrsRequestChild.h"
+# include "mozilla/net/WebrtcTCPSocketChild.h"
+#endif
+
+#include "SerializedLoadContext.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIOService.h"
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsINetworkLinkService.h"
+#include "nsQueryObject.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsNetUtil.h"
+#include "SimpleChannel.h"
+
+using mozilla::dom::TCPServerSocketChild;
+using mozilla::dom::TCPSocketChild;
+using mozilla::dom::UDPSocketChild;
+
+namespace mozilla {
+namespace net {
+
+PNeckoChild* gNeckoChild = nullptr;
+
+// C++ file contents
+
+NeckoChild::~NeckoChild() {
+ // Send__delete__(gNeckoChild);
+ gNeckoChild = nullptr;
+}
+
+void NeckoChild::InitNeckoChild() {
+ if (!IsNeckoChild()) {
+ MOZ_ASSERT(false, "InitNeckoChild called by non-child!");
+ return;
+ }
+
+ if (!gNeckoChild) {
+ mozilla::dom::ContentChild* cpc =
+ mozilla::dom::ContentChild::GetSingleton();
+ NS_ASSERTION(cpc, "Content Protocol is NULL!");
+ if (NS_WARN_IF(cpc->IsShuttingDown())) {
+ return;
+ }
+ RefPtr<NeckoChild> child = new NeckoChild();
+ gNeckoChild = cpc->SendPNeckoConstructor(child);
+ NS_ASSERTION(gNeckoChild, "PNecko Protocol init failed!");
+ }
+}
+
+PStunAddrsRequestChild* NeckoChild::AllocPStunAddrsRequestChild() {
+ // We don't allocate here: instead we always use IPDL constructor that takes
+ // an existing object
+ MOZ_ASSERT_UNREACHABLE(
+ "AllocPStunAddrsRequestChild should not be called "
+ "on child");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor) {
+#ifdef MOZ_WEBRTC
+ StunAddrsRequestChild* p = static_cast<StunAddrsRequestChild*>(aActor);
+ p->ReleaseIPDLReference();
+#endif
+ return true;
+}
+
+PWebrtcTCPSocketChild* NeckoChild::AllocPWebrtcTCPSocketChild(
+ const Maybe<TabId>& tabId) {
+ // We don't allocate here: instead we always use IPDL constructor that takes
+ // an existing object
+ MOZ_ASSERT_UNREACHABLE(
+ "AllocPWebrtcTCPSocketChild should not be called on"
+ " child");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor) {
+#ifdef MOZ_WEBRTC
+ WebrtcTCPSocketChild* child = static_cast<WebrtcTCPSocketChild*>(aActor);
+ child->ReleaseIPDLReference();
+#endif
+ return true;
+}
+
+PAltDataOutputStreamChild* NeckoChild::AllocPAltDataOutputStreamChild(
+ const nsACString& type, const int64_t& predictedSize,
+ PHttpChannelChild* channel) {
+ // We don't allocate here: see HttpChannelChild::OpenAlternativeOutputStream()
+ MOZ_ASSERT_UNREACHABLE("AllocPAltDataOutputStreamChild should not be called");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPAltDataOutputStreamChild(
+ PAltDataOutputStreamChild* aActor) {
+ AltDataOutputStreamChild* child =
+ static_cast<AltDataOutputStreamChild*>(aActor);
+ child->ReleaseIPDLReference();
+ return true;
+}
+
+#ifdef MOZ_WIDGET_GTK
+PGIOChannelChild* NeckoChild::AllocPGIOChannelChild(
+ PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized,
+ const GIOChannelCreationArgs& aOpenArgs) {
+ // We don't allocate here: see GIOChannelChild::AsyncOpen()
+ MOZ_CRASH("AllocPGIOChannelChild should not be called");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPGIOChannelChild(PGIOChannelChild* channel) {
+ MOZ_ASSERT(IsNeckoChild(), "DeallocPGIOChannelChild called by non-child!");
+
+ GIOChannelChild* child = static_cast<GIOChannelChild*>(channel);
+ child->ReleaseIPDLReference();
+ return true;
+}
+#endif
+
+PCookieServiceChild* NeckoChild::AllocPCookieServiceChild() {
+ // We don't allocate here: see CookieService::GetSingleton()
+ MOZ_ASSERT_UNREACHABLE("AllocPCookieServiceChild should not be called");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPCookieServiceChild(PCookieServiceChild* cs) {
+ NS_ASSERTION(IsNeckoChild(),
+ "DeallocPCookieServiceChild called by non-child!");
+
+ CookieServiceChild* p = static_cast<CookieServiceChild*>(cs);
+ p->Release();
+ return true;
+}
+
+PWebSocketChild* NeckoChild::AllocPWebSocketChild(
+ PBrowserChild* browser, const SerializedLoadContext& aSerialized,
+ const uint32_t& aSerial) {
+ MOZ_ASSERT_UNREACHABLE("AllocPWebSocketChild should not be called");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPWebSocketChild(PWebSocketChild* child) {
+ WebSocketChannelChild* p = static_cast<WebSocketChannelChild*>(child);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PWebSocketEventListenerChild* NeckoChild::AllocPWebSocketEventListenerChild(
+ const uint64_t& aInnerWindowID) {
+ RefPtr<WebSocketEventListenerChild> c = new WebSocketEventListenerChild(
+ aInnerWindowID, GetMainThreadSerialEventTarget());
+ return c.forget().take();
+}
+
+bool NeckoChild::DeallocPWebSocketEventListenerChild(
+ PWebSocketEventListenerChild* aActor) {
+ RefPtr<WebSocketEventListenerChild> c =
+ dont_AddRef(static_cast<WebSocketEventListenerChild*>(aActor));
+ MOZ_ASSERT(c);
+ return true;
+}
+
+PSimpleChannelChild* NeckoChild::AllocPSimpleChannelChild(
+ const uint32_t& channelId) {
+ MOZ_ASSERT_UNREACHABLE("Should never get here");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPSimpleChannelChild(PSimpleChannelChild* child) {
+ static_cast<SimpleChannelChild*>(child)->Release();
+ return true;
+}
+
+PTCPSocketChild* NeckoChild::AllocPTCPSocketChild(const nsAString& host,
+ const uint16_t& port) {
+ TCPSocketChild* p = new TCPSocketChild(host, port, nullptr);
+ p->AddIPDLReference();
+ return p;
+}
+
+bool NeckoChild::DeallocPTCPSocketChild(PTCPSocketChild* child) {
+ TCPSocketChild* p = static_cast<TCPSocketChild*>(child);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PTCPServerSocketChild* NeckoChild::AllocPTCPServerSocketChild(
+ const uint16_t& aLocalPort, const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers) {
+ MOZ_ASSERT_UNREACHABLE("AllocPTCPServerSocket should not be called");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPTCPServerSocketChild(PTCPServerSocketChild* child) {
+ TCPServerSocketChild* p = static_cast<TCPServerSocketChild*>(child);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PUDPSocketChild* NeckoChild::AllocPUDPSocketChild(nsIPrincipal* aPrincipal,
+ const nsACString& aFilter) {
+ MOZ_ASSERT_UNREACHABLE("AllocPUDPSocket should not be called");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPUDPSocketChild(PUDPSocketChild* child) {
+ UDPSocketChild* p = static_cast<UDPSocketChild*>(child);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PTransportProviderChild* NeckoChild::AllocPTransportProviderChild() {
+ // This refcount is transferred to the receiver of the message that
+ // includes the PTransportProviderChild actor.
+ RefPtr<TransportProviderChild> res = new TransportProviderChild();
+
+ return res.forget().take();
+}
+
+bool NeckoChild::DeallocPTransportProviderChild(
+ PTransportProviderChild* aActor) {
+ return true;
+}
+
+/* Predictor Messages */
+mozilla::ipc::IPCResult NeckoChild::RecvPredOnPredictPrefetch(
+ nsIURI* aURI, const uint32_t& aHttpStatus) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "PredictorChild::RecvOnPredictPrefetch "
+ "off main thread.");
+ if (!aURI) {
+ return IPC_FAIL(this, "aURI is null");
+ }
+
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this));
+
+ predictor->OnPredictPrefetch(aURI, aHttpStatus);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoChild::RecvPredOnPredictPreconnect(nsIURI* aURI) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "PredictorChild::RecvOnPredictPreconnect "
+ "off main thread.");
+ if (!aURI) {
+ return IPC_FAIL(this, "aURI is null");
+ }
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this));
+
+ predictor->OnPredictPreconnect(aURI);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoChild::RecvPredOnPredictDNS(nsIURI* aURI) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "PredictorChild::RecvOnPredictDNS off "
+ "main thread.");
+ if (!aURI) {
+ return IPC_FAIL(this, "aURI is null");
+ }
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this));
+
+ predictor->OnPredictDNS(aURI);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoChild::RecvSpeculativeConnectRequest() {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "speculative-connect-request",
+ nullptr);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoChild::RecvNetworkChangeNotification(
+ nsCString const& type) {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
+ NS_ConvertUTF8toUTF16(type).get());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoChild::RecvSetTRRDomain(const nsCString& domain) {
+ RefPtr<net::ChildDNSService> dnsServiceChild =
+ dont_AddRef(net::ChildDNSService::GetSingleton());
+ if (dnsServiceChild) {
+ dnsServiceChild->SetTRRDomain(domain);
+ }
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h
new file mode 100644
index 0000000000..2434c02126
--- /dev/null
+++ b/netwerk/ipc/NeckoChild.h
@@ -0,0 +1,93 @@
+/* -*- 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_NeckoChild_h
+#define mozilla_net_NeckoChild_h
+
+#include "mozilla/net/PNeckoChild.h"
+#include "mozilla/net/NeckoCommon.h"
+
+namespace mozilla {
+namespace net {
+
+// Header file contents
+class NeckoChild : public PNeckoChild {
+ friend class PNeckoChild;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NeckoChild, override)
+
+ NeckoChild() = default;
+
+ static void InitNeckoChild();
+
+ protected:
+ virtual ~NeckoChild();
+
+ PStunAddrsRequestChild* AllocPStunAddrsRequestChild();
+ bool DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor);
+
+ PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId);
+ bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor);
+
+ PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(
+ const nsACString& type, const int64_t& predictedSize,
+ PHttpChannelChild* channel);
+ bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor);
+
+ PCookieServiceChild* AllocPCookieServiceChild();
+ bool DeallocPCookieServiceChild(PCookieServiceChild*);
+#ifdef MOZ_WIDGET_GTK
+ PGIOChannelChild* AllocPGIOChannelChild(
+ PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized,
+ const GIOChannelCreationArgs& aOpenArgs);
+ bool DeallocPGIOChannelChild(PGIOChannelChild*);
+#endif
+ PWebSocketChild* AllocPWebSocketChild(PBrowserChild*,
+ const SerializedLoadContext&,
+ const uint32_t&);
+ bool DeallocPWebSocketChild(PWebSocketChild*);
+ PTCPSocketChild* AllocPTCPSocketChild(const nsAString& host,
+ const uint16_t& port);
+ bool DeallocPTCPSocketChild(PTCPSocketChild*);
+ PTCPServerSocketChild* AllocPTCPServerSocketChild(
+ const uint16_t& aLocalPort, const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers);
+ bool DeallocPTCPServerSocketChild(PTCPServerSocketChild*);
+ PUDPSocketChild* AllocPUDPSocketChild(nsIPrincipal* aPrincipal,
+ const nsACString& aFilter);
+ bool DeallocPUDPSocketChild(PUDPSocketChild*);
+ PSimpleChannelChild* AllocPSimpleChannelChild(const uint32_t& channelId);
+ bool DeallocPSimpleChannelChild(PSimpleChannelChild* child);
+ PTransportProviderChild* AllocPTransportProviderChild();
+ bool DeallocPTransportProviderChild(PTransportProviderChild* aActor);
+ PWebSocketEventListenerChild* AllocPWebSocketEventListenerChild(
+ const uint64_t& aInnerWindowID);
+ bool DeallocPWebSocketEventListenerChild(PWebSocketEventListenerChild*);
+
+ /* Predictor Messsages */
+ mozilla::ipc::IPCResult RecvPredOnPredictPrefetch(
+ nsIURI* aURI, const uint32_t& aHttpStatus);
+ mozilla::ipc::IPCResult RecvPredOnPredictPreconnect(nsIURI* aURI);
+ mozilla::ipc::IPCResult RecvPredOnPredictDNS(nsIURI* aURI);
+
+ mozilla::ipc::IPCResult RecvSpeculativeConnectRequest();
+ mozilla::ipc::IPCResult RecvNetworkChangeNotification(nsCString const& type);
+
+ mozilla::ipc::IPCResult RecvSetTRRDomain(const nsCString& domain);
+};
+
+/**
+ * Reference to the PNecko Child protocol.
+ * Null if this is not a content process.
+ */
+extern PNeckoChild* gNeckoChild;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NeckoChild_h
diff --git a/netwerk/ipc/NeckoCommon.cpp b/netwerk/ipc/NeckoCommon.cpp
new file mode 100644
index 0000000000..9ad4483d32
--- /dev/null
+++ b/netwerk/ipc/NeckoCommon.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "NeckoCommon.h"
+
+#include "nsIInputStream.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIParentChannel.h"
+#include "nsStringStream.h"
+
+namespace mozilla::net {
+
+nsresult ForwardStreamListenerFunctions(nsTArray<StreamListenerFunction> aCalls,
+ nsIStreamListener* aParent) {
+ nsresult rv = NS_OK;
+ for (auto& variant : aCalls) {
+ variant.match(
+ [&](OnStartRequestParams& aParams) {
+ rv = aParent->OnStartRequest(aParams.request);
+ if (NS_FAILED(rv)) {
+ aParams.request->Cancel(rv);
+ }
+ },
+ [&](OnDataAvailableParams& aParams) {
+ // Don't deliver OnDataAvailable if we've
+ // already failed.
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIInputStream> stringStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stringStream),
+ std::move(aParams.data));
+ if (NS_SUCCEEDED(rv)) {
+ rv = aParent->OnDataAvailable(aParams.request, stringStream,
+ aParams.offset, aParams.count);
+ }
+ if (NS_FAILED(rv)) {
+ aParams.request->Cancel(rv);
+ }
+ },
+ [&](OnStopRequestParams& aParams) {
+ if (NS_SUCCEEDED(rv)) {
+ aParent->OnStopRequest(aParams.request, aParams.status);
+ } else {
+ aParent->OnStopRequest(aParams.request, rv);
+ }
+ rv = NS_OK;
+ },
+ [&](OnAfterLastPartParams& aParams) {
+ nsCOMPtr<nsIMultiPartChannelListener> multiListener =
+ do_QueryInterface(aParent);
+ if (multiListener) {
+ if (NS_SUCCEEDED(rv)) {
+ multiListener->OnAfterLastPart(aParams.status);
+ } else {
+ multiListener->OnAfterLastPart(rv);
+ }
+ }
+ });
+ }
+ return rv;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/ipc/NeckoCommon.h b/netwerk/ipc/NeckoCommon.h
new file mode 100644
index 0000000000..57f770db68
--- /dev/null
+++ b/netwerk/ipc/NeckoCommon.h
@@ -0,0 +1,144 @@
+/* -*- 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_NeckoCommon_h
+#define mozilla_net_NeckoCommon_h
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Variant.h"
+#include "nsIRequest.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "prenv.h"
+
+class nsIStreamListener;
+
+namespace mozilla {
+namespace dom {
+class BrowserChild;
+} // namespace dom
+} // namespace mozilla
+
+#if defined(DEBUG)
+# define NECKO_ERRORS_ARE_FATAL_DEFAULT true
+#else
+# define NECKO_ERRORS_ARE_FATAL_DEFAULT false
+#endif
+
+// TODO: Eventually remove NECKO_MAYBE_ABORT and DROP_DEAD (bug 575494).
+// Still useful for catching listener interfaces we don't yet support across
+// processes, etc.
+
+#define NECKO_MAYBE_ABORT(msg) \
+ do { \
+ bool abort = NECKO_ERRORS_ARE_FATAL_DEFAULT; \
+ const char* e = PR_GetEnv("NECKO_ERRORS_ARE_FATAL"); \
+ if (e) abort = (*e == '0') ? false : true; \
+ if (abort) { \
+ msg.AppendLiteral( \
+ " (set NECKO_ERRORS_ARE_FATAL=0 in your environment " \
+ "to convert this error into a warning.)"); \
+ MOZ_CRASH_UNSAFE(msg.get()); \
+ } else { \
+ msg.AppendLiteral( \
+ " (set NECKO_ERRORS_ARE_FATAL=1 in your environment " \
+ "to convert this warning into a fatal error.)"); \
+ NS_WARNING(msg.get()); \
+ } \
+ } while (0)
+
+#define DROP_DEAD() \
+ do { \
+ nsPrintfCString msg("NECKO ERROR: '%s' UNIMPLEMENTED", __FUNCTION__); \
+ NECKO_MAYBE_ABORT(msg); \
+ return NS_ERROR_NOT_IMPLEMENTED; \
+ } while (0)
+
+#define ENSURE_CALLED_BEFORE_ASYNC_OPEN() \
+ do { \
+ if (LoadIsPending() || LoadWasOpened()) { \
+ nsPrintfCString msg("'%s' called after AsyncOpen: %s +%d", __FUNCTION__, \
+ __FILE__, __LINE__); \
+ NECKO_MAYBE_ABORT(msg); \
+ } \
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS); \
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED); \
+ } while (0)
+
+// Fails call if made after request observers (on-modify-request, etc) have been
+// called
+
+#define ENSURE_CALLED_BEFORE_CONNECT() \
+ do { \
+ if (LoadRequestObserversCalled()) { \
+ nsPrintfCString msg("'%s' called too late: %s +%d", __FUNCTION__, \
+ __FILE__, __LINE__); \
+ NECKO_MAYBE_ABORT(msg); \
+ if (LoadIsPending()) return NS_ERROR_IN_PROGRESS; \
+ MOZ_ASSERT(LoadWasOpened()); \
+ return NS_ERROR_ALREADY_OPENED; \
+ } \
+ } while (0)
+
+namespace mozilla {
+namespace net {
+
+inline bool IsNeckoChild() {
+ static bool didCheck = false;
+ static bool amChild = false;
+
+ if (!didCheck) {
+ didCheck = true;
+ amChild = (XRE_GetProcessType() == GeckoProcessType_Content);
+ }
+ return amChild;
+}
+
+inline bool IsSocketProcessChild() {
+ static bool amChild = (XRE_GetProcessType() == GeckoProcessType_Socket);
+ return amChild;
+}
+
+class HttpChannelSecurityWarningReporter : public nsISupports {
+ public:
+ [[nodiscard]] virtual nsresult ReportSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) = 0;
+ [[nodiscard]] virtual nsresult LogBlockedCORSRequest(
+ const nsAString& aMessage, const nsACString& aCategory,
+ bool aIsWarning = false) = 0;
+ [[nodiscard]] virtual nsresult LogMimeTypeMismatch(
+ const nsACString& aMessageName, bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) = 0;
+};
+
+struct OnStartRequestParams {
+ nsCOMPtr<nsIRequest> request;
+};
+struct OnDataAvailableParams {
+ nsCOMPtr<nsIRequest> request;
+ nsCString data;
+ uint64_t offset;
+ uint32_t count;
+};
+struct OnStopRequestParams {
+ nsCOMPtr<nsIRequest> request;
+ nsresult status;
+};
+struct OnAfterLastPartParams {
+ nsresult status;
+};
+using StreamListenerFunction =
+ mozilla::Variant<OnStartRequestParams, OnDataAvailableParams,
+ OnStopRequestParams, OnAfterLastPartParams>;
+
+nsresult ForwardStreamListenerFunctions(nsTArray<StreamListenerFunction> aCalls,
+ nsIStreamListener* aParent);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NeckoCommon_h
diff --git a/netwerk/ipc/NeckoMessageUtils.h b/netwerk/ipc/NeckoMessageUtils.h
new file mode 100644
index 0000000000..a15398330e
--- /dev/null
+++ b/netwerk/ipc/NeckoMessageUtils.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 8; 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_NeckoMessageUtils_h
+#define mozilla_net_NeckoMessageUtils_h
+
+#include "mozilla/DebugOnly.h"
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsIHttpChannel.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "prio.h"
+#include "mozilla/net/DNS.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsITRRSkipReason.h"
+#include "nsIDNSService.h"
+
+namespace IPC {
+
+// nsIPermissionManager utilities
+
+struct Permission {
+ nsCString origin, type;
+ uint32_t capability, expireType;
+ int64_t expireTime;
+
+ Permission() : capability(0), expireType(0), expireTime(0) {}
+
+ Permission(const nsCString& aOrigin, const nsACString& aType,
+ const uint32_t aCapability, const uint32_t aExpireType,
+ const int64_t aExpireTime)
+ : origin(aOrigin),
+ type(aType),
+ capability(aCapability),
+ expireType(aExpireType),
+ expireTime(aExpireTime) {}
+
+ bool operator==(const Permission& aOther) const {
+ return aOther.origin == origin && aOther.type == type &&
+ aOther.capability == capability && aOther.expireType == expireType &&
+ aOther.expireTime == expireTime;
+ }
+};
+
+template <>
+struct ParamTraits<Permission> {
+ static void Write(MessageWriter* aWriter, const Permission& aParam) {
+ WriteParam(aWriter, aParam.origin);
+ WriteParam(aWriter, aParam.type);
+ WriteParam(aWriter, aParam.capability);
+ WriteParam(aWriter, aParam.expireType);
+ WriteParam(aWriter, aParam.expireTime);
+ }
+
+ static bool Read(MessageReader* aReader, Permission* aResult) {
+ return ReadParam(aReader, &aResult->origin) &&
+ ReadParam(aReader, &aResult->type) &&
+ ReadParam(aReader, &aResult->capability) &&
+ ReadParam(aReader, &aResult->expireType) &&
+ ReadParam(aReader, &aResult->expireTime);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::NetAddr> {
+ static void Write(MessageWriter* aWriter,
+ const mozilla::net::NetAddr& aParam) {
+ WriteParam(aWriter, aParam.raw.family);
+ if (aParam.raw.family == AF_UNSPEC) {
+ aWriter->WriteBytes(aParam.raw.data, sizeof(aParam.raw.data));
+ } else if (aParam.raw.family == AF_INET) {
+ WriteParam(aWriter, aParam.inet.port);
+ WriteParam(aWriter, aParam.inet.ip);
+ } else if (aParam.raw.family == AF_INET6) {
+ WriteParam(aWriter, aParam.inet6.port);
+ WriteParam(aWriter, aParam.inet6.flowinfo);
+ WriteParam(aWriter, aParam.inet6.ip.u64[0]);
+ WriteParam(aWriter, aParam.inet6.ip.u64[1]);
+ WriteParam(aWriter, aParam.inet6.scope_id);
+#if defined(XP_UNIX)
+ } else if (aParam.raw.family == AF_LOCAL) {
+ // Train's already off the rails: let's get a stack trace at least...
+ MOZ_CRASH(
+ "Error: please post stack trace to "
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=661158");
+ aWriter->WriteBytes(aParam.local.path, sizeof(aParam.local.path));
+#endif
+ } else {
+ if (XRE_IsParentProcess()) {
+ nsPrintfCString msg("%d", aParam.raw.family);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::UnknownNetAddrSocketFamily, msg);
+ }
+
+ MOZ_CRASH("Unknown socket family");
+ }
+ }
+
+ static bool Read(MessageReader* aReader, mozilla::net::NetAddr* aResult) {
+ if (!ReadParam(aReader, &aResult->raw.family)) return false;
+
+ if (aResult->raw.family == AF_UNSPEC) {
+ return aReader->ReadBytesInto(&aResult->raw.data,
+ sizeof(aResult->raw.data));
+ } else if (aResult->raw.family == AF_INET) {
+ return ReadParam(aReader, &aResult->inet.port) &&
+ ReadParam(aReader, &aResult->inet.ip);
+ } else if (aResult->raw.family == AF_INET6) {
+ return ReadParam(aReader, &aResult->inet6.port) &&
+ ReadParam(aReader, &aResult->inet6.flowinfo) &&
+ ReadParam(aReader, &aResult->inet6.ip.u64[0]) &&
+ ReadParam(aReader, &aResult->inet6.ip.u64[1]) &&
+ ReadParam(aReader, &aResult->inet6.scope_id);
+#if defined(XP_UNIX)
+ } else if (aResult->raw.family == AF_LOCAL) {
+ return aReader->ReadBytesInto(&aResult->local.path,
+ sizeof(aResult->local.path));
+#endif
+ }
+
+ /* We've been tricked by some socket family we don't know about! */
+ return false;
+ }
+};
+
+template <>
+struct ParamTraits<nsIRequest::TRRMode> {
+ static void Write(MessageWriter* aWriter, const nsIRequest::TRRMode& aParam) {
+ WriteParam(aWriter, (uint8_t)aParam);
+ }
+ static bool Read(MessageReader* aReader, nsIRequest::TRRMode* aResult) {
+ uint8_t mode;
+ if (!ReadParam(aReader, &mode)) {
+ return false;
+ }
+ // TODO: sanity check
+ *aResult = static_cast<nsIRequest::TRRMode>(mode);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<nsITRRSkipReason::value> {
+ static void Write(MessageWriter* aWriter,
+ const nsITRRSkipReason::value& aParam) {
+ WriteParam(aWriter, (uint8_t)aParam);
+ }
+ static bool Read(MessageReader* aReader, nsITRRSkipReason::value* aResult) {
+ uint8_t reason;
+ if (!ReadParam(aReader, &reason)) {
+ return false;
+ }
+ // TODO: sanity check
+ *aResult = static_cast<nsITRRSkipReason::value>(reason);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<nsIDNSService::DNSFlags>
+ : public BitFlagsEnumSerializer<
+ nsIDNSService::DNSFlags, nsIDNSService::DNSFlags::ALL_DNSFLAGS_BITS> {
+};
+
+template <>
+struct ParamTraits<nsIDNSService::ResolverMode> {
+ static void Write(MessageWriter* aWriter,
+ const nsIDNSService::ResolverMode& aParam) {
+ WriteParam(aWriter, (uint8_t)aParam);
+ }
+ static bool Read(MessageReader* aReader,
+ nsIDNSService::ResolverMode* aResult) {
+ uint8_t mode;
+ if (!ReadParam(aReader, &mode)) {
+ return false;
+ }
+ // TODO: sanity check
+ *aResult = static_cast<nsIDNSService::ResolverMode>(mode);
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_NeckoMessageUtils_h
diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp
new file mode 100644
index 0000000000..102b46a378
--- /dev/null
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -0,0 +1,889 @@
+/* -*- 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 "nsHttp.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/ExtensionProtocolHandler.h"
+#include "mozilla/net/PageThumbProtocolHandler.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/CookieServiceParent.h"
+#include "mozilla/net/WebSocketChannelParent.h"
+#include "mozilla/net/WebSocketEventListenerParent.h"
+#include "mozilla/net/DataChannelParent.h"
+#ifdef MOZ_WIDGET_GTK
+# include "mozilla/net/GIOChannelParent.h"
+#endif
+#include "mozilla/net/DocumentChannelParent.h"
+#include "mozilla/net/SimpleChannelParent.h"
+#include "mozilla/net/AltDataOutputStreamParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/FileChannelParent.h"
+#include "mozilla/net/DNSRequestParent.h"
+#include "mozilla/net/IPCTransportProvider.h"
+#include "mozilla/net/RemoteStreamGetter.h"
+#include "mozilla/net/RequestContextService.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/PSocketProcessBridgeParent.h"
+#ifdef MOZ_WEBRTC
+# include "mozilla/net/StunAddrsRequestParent.h"
+# include "mozilla/net/WebrtcTCPSocketParent.h"
+#endif
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/MaybeDiscarded.h"
+#include "mozilla/dom/network/TCPSocketParent.h"
+#include "mozilla/dom/network/TCPServerSocketParent.h"
+#include "mozilla/dom/network/UDPSocketParent.h"
+#ifdef MOZ_PLACES
+# include "mozilla/places/PageIconProtocolHandler.h"
+#endif
+#include "mozilla/LoadContext.h"
+#include "mozilla/MozPromise.h"
+#include "nsPrintfCString.h"
+#include "mozilla/dom/HTMLDNSPrefetch.h"
+#include "nsEscape.h"
+#include "SerializedLoadContext.h"
+#include "nsAuthInformationHolder.h"
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsISpeculativeConnect.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsIOService.h"
+
+using IPC::SerializedLoadContext;
+using mozilla::dom::BrowserParent;
+using mozilla::dom::ContentParent;
+using mozilla::dom::TCPServerSocketParent;
+using mozilla::dom::TCPSocketParent;
+using mozilla::dom::UDPSocketParent;
+using mozilla::ipc::LoadInfoArgsToLoadInfo;
+using mozilla::ipc::PrincipalInfo;
+#ifdef MOZ_PLACES
+using mozilla::places::PageIconProtocolHandler;
+#endif
+
+namespace mozilla {
+namespace net {
+
+// C++ file contents
+NeckoParent::NeckoParent() : mSocketProcessBridgeInited(false) {
+ // Init HTTP protocol handler now since we need atomTable up and running very
+ // early (IPDL argument handling for PHttpChannel constructor needs it) so
+ // normal init (during 1st Http channel request) isn't early enough.
+ nsCOMPtr<nsIProtocolHandler> proto =
+ do_GetService("@mozilla.org/network/protocol;1?name=http");
+}
+
+static PBOverrideStatus PBOverrideStatusFromLoadContext(
+ const SerializedLoadContext& aSerialized) {
+ if (!aSerialized.IsNotNull() && aSerialized.IsPrivateBitValid()) {
+ return (aSerialized.mOriginAttributes.mPrivateBrowsingId > 0)
+ ? kPBOverride_Private
+ : kPBOverride_NotPrivate;
+ }
+ return kPBOverride_Unset;
+}
+
+static already_AddRefed<nsIPrincipal> GetRequestingPrincipal(
+ const LoadInfoArgs& aLoadInfoArgs) {
+ const Maybe<PrincipalInfo>& optionalPrincipalInfo =
+ aLoadInfoArgs.requestingPrincipalInfo();
+
+ if (optionalPrincipalInfo.isNothing()) {
+ return nullptr;
+ }
+
+ const PrincipalInfo& principalInfo = optionalPrincipalInfo.ref();
+
+ auto principalOrErr = PrincipalInfoToPrincipal(principalInfo);
+ return principalOrErr.isOk() ? principalOrErr.unwrap().forget() : nullptr;
+}
+
+static already_AddRefed<nsIPrincipal> GetRequestingPrincipal(
+ const HttpChannelCreationArgs& aArgs) {
+ if (aArgs.type() != HttpChannelCreationArgs::THttpChannelOpenArgs) {
+ return nullptr;
+ }
+
+ const HttpChannelOpenArgs& args = aArgs.get_HttpChannelOpenArgs();
+ return GetRequestingPrincipal(args.loadInfo());
+}
+
+const char* NeckoParent::GetValidatedOriginAttributes(
+ const SerializedLoadContext& aSerialized, PContentParent* aContent,
+ nsIPrincipal* aRequestingPrincipal, OriginAttributes& aAttrs) {
+ if (!aSerialized.IsNotNull()) {
+ // If serialized is null, we cannot validate anything. We have to assume
+ // that this requests comes from a SystemPrincipal.
+ aAttrs = OriginAttributes(false);
+ } else {
+ aAttrs = aSerialized.mOriginAttributes;
+ }
+ return nullptr;
+}
+
+const char* NeckoParent::CreateChannelLoadContext(
+ PBrowserParent* aBrowser, PContentParent* aContent,
+ const SerializedLoadContext& aSerialized,
+ nsIPrincipal* aRequestingPrincipal, nsCOMPtr<nsILoadContext>& aResult) {
+ OriginAttributes attrs;
+ const char* error = GetValidatedOriginAttributes(aSerialized, aContent,
+ aRequestingPrincipal, attrs);
+ if (error) {
+ return error;
+ }
+
+ // if !UsingNeckoIPCSecurity(), we may not have a LoadContext to set. This is
+ // the common case for most xpcshell tests.
+ if (aSerialized.IsNotNull()) {
+ attrs.SyncAttributesWithPrivateBrowsing(
+ aSerialized.mOriginAttributes.mPrivateBrowsingId > 0);
+
+ RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(aBrowser);
+ dom::Element* topFrameElement = nullptr;
+ if (browserParent) {
+ topFrameElement = browserParent->GetOwnerElement();
+ }
+ aResult = new LoadContext(aSerialized, topFrameElement, attrs);
+ }
+
+ return nullptr;
+}
+
+void NeckoParent::ActorDestroy(ActorDestroyReason aWhy) {
+ // Nothing needed here. Called right before destructor since this is a
+ // non-refcounted class.
+}
+
+already_AddRefed<PHttpChannelParent> NeckoParent::AllocPHttpChannelParent(
+ PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
+ const HttpChannelCreationArgs& aOpenArgs) {
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ GetRequestingPrincipal(aOpenArgs);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ const char* error = CreateChannelLoadContext(
+ aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext);
+ if (error) {
+ printf_stderr(
+ "NeckoParent::AllocPHttpChannelParent: "
+ "FATAL error: %s: KILLING CHILD PROCESS\n",
+ error);
+ return nullptr;
+ }
+ PBOverrideStatus overrideStatus =
+ PBOverrideStatusFromLoadContext(aSerialized);
+ RefPtr<HttpChannelParent> p = new HttpChannelParent(
+ BrowserParent::GetFrom(aBrowser), loadContext, overrideStatus);
+ return p.forget();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPHttpChannelConstructor(
+ PHttpChannelParent* aActor, PBrowserParent* aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const HttpChannelCreationArgs& aOpenArgs) {
+ HttpChannelParent* p = static_cast<HttpChannelParent*>(aActor);
+ if (!p->Init(aOpenArgs)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+PStunAddrsRequestParent* NeckoParent::AllocPStunAddrsRequestParent() {
+#ifdef MOZ_WEBRTC
+ StunAddrsRequestParent* p = new StunAddrsRequestParent();
+ p->AddRef();
+ return p;
+#else
+ return nullptr;
+#endif
+}
+
+bool NeckoParent::DeallocPStunAddrsRequestParent(
+ PStunAddrsRequestParent* aActor) {
+#ifdef MOZ_WEBRTC
+ StunAddrsRequestParent* p = static_cast<StunAddrsRequestParent*>(aActor);
+ p->Release();
+#endif
+ return true;
+}
+
+PWebrtcTCPSocketParent* NeckoParent::AllocPWebrtcTCPSocketParent(
+ const Maybe<TabId>& aTabId) {
+#ifdef MOZ_WEBRTC
+ WebrtcTCPSocketParent* parent = new WebrtcTCPSocketParent(aTabId);
+ parent->AddRef();
+ return parent;
+#else
+ return nullptr;
+#endif
+}
+
+bool NeckoParent::DeallocPWebrtcTCPSocketParent(
+ PWebrtcTCPSocketParent* aActor) {
+#ifdef MOZ_WEBRTC
+ WebrtcTCPSocketParent* parent = static_cast<WebrtcTCPSocketParent*>(aActor);
+ parent->Release();
+#endif
+ return true;
+}
+
+PAltDataOutputStreamParent* NeckoParent::AllocPAltDataOutputStreamParent(
+ const nsACString& type, const int64_t& predictedSize,
+ PHttpChannelParent* channel) {
+ HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel);
+ nsCOMPtr<nsIAsyncOutputStream> stream;
+ nsresult rv = chan->OpenAlternativeOutputStream(type, predictedSize,
+ getter_AddRefs(stream));
+ AltDataOutputStreamParent* parent = new AltDataOutputStreamParent(stream);
+ parent->AddRef();
+ // If the return value was not NS_OK, the error code will be sent
+ // asynchronously to the child, after receiving the first message.
+ parent->SetError(rv);
+ return parent;
+}
+
+bool NeckoParent::DeallocPAltDataOutputStreamParent(
+ PAltDataOutputStreamParent* aActor) {
+ AltDataOutputStreamParent* parent =
+ static_cast<AltDataOutputStreamParent*>(aActor);
+ parent->Release();
+ return true;
+}
+
+already_AddRefed<PDocumentChannelParent>
+NeckoParent::AllocPDocumentChannelParent(
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aContext,
+ const DocumentChannelCreationArgs& args) {
+ RefPtr<DocumentChannelParent> p = new DocumentChannelParent();
+ return p.forget();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPDocumentChannelConstructor(
+ PDocumentChannelParent* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aContext,
+ const DocumentChannelCreationArgs& aArgs) {
+ DocumentChannelParent* p = static_cast<DocumentChannelParent*>(aActor);
+
+ if (aContext.IsNullOrDiscarded()) {
+ Unused << p->SendFailedAsyncOpen(NS_ERROR_FAILURE);
+ return IPC_OK();
+ }
+
+ if (!p->Init(aContext.get_canonical(), aArgs)) {
+ return IPC_FAIL(this, "Couldn't initialize DocumentChannel");
+ }
+
+ return IPC_OK();
+}
+
+PCookieServiceParent* NeckoParent::AllocPCookieServiceParent() {
+ return new CookieServiceParent();
+}
+
+bool NeckoParent::DeallocPCookieServiceParent(PCookieServiceParent* cs) {
+ delete cs;
+ return true;
+}
+
+PWebSocketParent* NeckoParent::AllocPWebSocketParent(
+ PBrowserParent* browser, const SerializedLoadContext& serialized,
+ const uint32_t& aSerial) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ const char* error = CreateChannelLoadContext(browser, Manager(), serialized,
+ nullptr, loadContext);
+ if (error) {
+ printf_stderr(
+ "NeckoParent::AllocPWebSocketParent: "
+ "FATAL error: %s: KILLING CHILD PROCESS\n",
+ error);
+ return nullptr;
+ }
+
+ RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(browser);
+ PBOverrideStatus overrideStatus = PBOverrideStatusFromLoadContext(serialized);
+ WebSocketChannelParent* p = new WebSocketChannelParent(
+ browserParent, loadContext, overrideStatus, aSerial);
+ p->AddRef();
+ return p;
+}
+
+bool NeckoParent::DeallocPWebSocketParent(PWebSocketParent* actor) {
+ WebSocketChannelParent* p = static_cast<WebSocketChannelParent*>(actor);
+ p->Release();
+ return true;
+}
+
+PWebSocketEventListenerParent* NeckoParent::AllocPWebSocketEventListenerParent(
+ const uint64_t& aInnerWindowID) {
+ RefPtr<WebSocketEventListenerParent> c =
+ new WebSocketEventListenerParent(aInnerWindowID);
+ return c.forget().take();
+}
+
+bool NeckoParent::DeallocPWebSocketEventListenerParent(
+ PWebSocketEventListenerParent* aActor) {
+ RefPtr<WebSocketEventListenerParent> c =
+ dont_AddRef(static_cast<WebSocketEventListenerParent*>(aActor));
+ MOZ_ASSERT(c);
+ return true;
+}
+
+already_AddRefed<PDataChannelParent> NeckoParent::AllocPDataChannelParent(
+ const uint32_t& channelId) {
+ RefPtr<DataChannelParent> p = new DataChannelParent();
+ return p.forget();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPDataChannelConstructor(
+ PDataChannelParent* actor, const uint32_t& channelId) {
+ DataChannelParent* p = static_cast<DataChannelParent*>(actor);
+ DebugOnly<bool> rv = p->Init(channelId);
+ MOZ_ASSERT(rv);
+ return IPC_OK();
+}
+
+#ifdef MOZ_WIDGET_GTK
+static already_AddRefed<nsIPrincipal> GetRequestingPrincipal(
+ const GIOChannelCreationArgs& aArgs) {
+ if (aArgs.type() != GIOChannelCreationArgs::TGIOChannelOpenArgs) {
+ return nullptr;
+ }
+
+ const GIOChannelOpenArgs& args = aArgs.get_GIOChannelOpenArgs();
+ return GetRequestingPrincipal(args.loadInfo());
+}
+
+PGIOChannelParent* NeckoParent::AllocPGIOChannelParent(
+ PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
+ const GIOChannelCreationArgs& aOpenArgs) {
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ GetRequestingPrincipal(aOpenArgs);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ const char* error = CreateChannelLoadContext(
+ aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext);
+ if (error) {
+ printf_stderr(
+ "NeckoParent::AllocPGIOChannelParent: "
+ "FATAL error: %s: KILLING CHILD PROCESS\n",
+ error);
+ return nullptr;
+ }
+ PBOverrideStatus overrideStatus =
+ PBOverrideStatusFromLoadContext(aSerialized);
+ GIOChannelParent* p = new GIOChannelParent(BrowserParent::GetFrom(aBrowser),
+ loadContext, overrideStatus);
+ p->AddRef();
+ return p;
+}
+
+bool NeckoParent::DeallocPGIOChannelParent(PGIOChannelParent* channel) {
+ GIOChannelParent* p = static_cast<GIOChannelParent*>(channel);
+ p->Release();
+ return true;
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPGIOChannelConstructor(
+ PGIOChannelParent* actor, PBrowserParent* aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const GIOChannelCreationArgs& aOpenArgs) {
+ GIOChannelParent* p = static_cast<GIOChannelParent*>(actor);
+ DebugOnly<bool> rv = p->Init(aOpenArgs);
+ MOZ_ASSERT(rv);
+ return IPC_OK();
+}
+#endif
+
+PSimpleChannelParent* NeckoParent::AllocPSimpleChannelParent(
+ const uint32_t& channelId) {
+ RefPtr<SimpleChannelParent> p = new SimpleChannelParent();
+ return p.forget().take();
+}
+
+bool NeckoParent::DeallocPSimpleChannelParent(PSimpleChannelParent* actor) {
+ RefPtr<SimpleChannelParent> p =
+ dont_AddRef(actor).downcast<SimpleChannelParent>();
+ return true;
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPSimpleChannelConstructor(
+ PSimpleChannelParent* actor, const uint32_t& channelId) {
+ SimpleChannelParent* p = static_cast<SimpleChannelParent*>(actor);
+ MOZ_ALWAYS_TRUE(p->Init(channelId));
+ return IPC_OK();
+}
+
+already_AddRefed<PFileChannelParent> NeckoParent::AllocPFileChannelParent(
+ const uint32_t& channelId) {
+ RefPtr<FileChannelParent> p = new FileChannelParent();
+ return p.forget();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPFileChannelConstructor(
+ PFileChannelParent* actor, const uint32_t& channelId) {
+ FileChannelParent* p = static_cast<FileChannelParent*>(actor);
+ DebugOnly<bool> rv = p->Init(channelId);
+ MOZ_ASSERT(rv);
+ return IPC_OK();
+}
+
+PTCPSocketParent* NeckoParent::AllocPTCPSocketParent(
+ const nsAString& /* host */, const uint16_t& /* port */) {
+ // We actually don't need host/port to construct a TCPSocketParent since
+ // TCPSocketParent will maintain an internal nsIDOMTCPSocket instance which
+ // can be delegated to get the host/port.
+ TCPSocketParent* p = new TCPSocketParent();
+ p->AddIPDLReference();
+ return p;
+}
+
+bool NeckoParent::DeallocPTCPSocketParent(PTCPSocketParent* actor) {
+ TCPSocketParent* p = static_cast<TCPSocketParent*>(actor);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PTCPServerSocketParent* NeckoParent::AllocPTCPServerSocketParent(
+ const uint16_t& aLocalPort, const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers) {
+ TCPServerSocketParent* p =
+ new TCPServerSocketParent(this, aLocalPort, aBacklog, aUseArrayBuffers);
+ p->AddIPDLReference();
+ return p;
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPTCPServerSocketConstructor(
+ PTCPServerSocketParent* aActor, const uint16_t& aLocalPort,
+ const uint16_t& aBacklog, const bool& aUseArrayBuffers) {
+ static_cast<TCPServerSocketParent*>(aActor)->Init();
+ return IPC_OK();
+}
+
+bool NeckoParent::DeallocPTCPServerSocketParent(PTCPServerSocketParent* actor) {
+ TCPServerSocketParent* p = static_cast<TCPServerSocketParent*>(actor);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PUDPSocketParent* NeckoParent::AllocPUDPSocketParent(
+ nsIPrincipal* /* unused */, const nsACString& /* unused */) {
+ RefPtr<UDPSocketParent> p = new UDPSocketParent(this);
+
+ return p.forget().take();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPUDPSocketConstructor(
+ PUDPSocketParent* aActor, nsIPrincipal* aPrincipal,
+ const nsACString& aFilter) {
+ if (!static_cast<UDPSocketParent*>(aActor)->Init(aPrincipal, aFilter)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+bool NeckoParent::DeallocPUDPSocketParent(PUDPSocketParent* actor) {
+ UDPSocketParent* p = static_cast<UDPSocketParent*>(actor);
+ p->Release();
+ return true;
+}
+
+already_AddRefed<PDNSRequestParent> NeckoParent::AllocPDNSRequestParent(
+ const nsACString& aHost, const nsACString& aTrrServer, const int32_t& aPort,
+ const uint16_t& aType, const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags) {
+ RefPtr<DNSRequestHandler> handler = new DNSRequestHandler();
+ RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPDNSRequestConstructor(
+ PDNSRequestParent* aActor, const nsACString& aHost,
+ const nsACString& aTrrServer, const int32_t& aPort, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags) {
+ RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor);
+ RefPtr<DNSRequestHandler> handler =
+ actor->GetDNSRequest()->AsDNSRequestHandler();
+ handler->DoAsyncResolve(aHost, aTrrServer, aPort, aType, aOriginAttributes,
+ aFlags);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvSpeculativeConnect(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes, const bool& aAnonymous) {
+ nsCOMPtr<nsISpeculativeConnect> speculator(gIOService);
+ nsCOMPtr<nsIPrincipal> principal(aPrincipal);
+ if (!aURI) {
+ return IPC_FAIL(this, "aURI must not be null");
+ }
+ if (aURI && speculator) {
+ if (aOriginAttributes) {
+ speculator->SpeculativeConnectWithOriginAttributesNative(
+ aURI, std::move(aOriginAttributes.ref()), nullptr, aAnonymous);
+ } else {
+ speculator->SpeculativeConnect(aURI, principal, nullptr, aAnonymous);
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvHTMLDNSPrefetch(
+ const nsAString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& flags) {
+ dom::HTMLDNSPrefetch::Prefetch(hostname, isHttps, aOriginAttributes, flags);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvCancelHTMLDNSPrefetch(
+ const nsAString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason) {
+ dom::HTMLDNSPrefetch::CancelPrefetch(hostname, isHttps, aOriginAttributes,
+ flags, reason);
+ return IPC_OK();
+}
+
+PTransportProviderParent* NeckoParent::AllocPTransportProviderParent() {
+ RefPtr<TransportProviderParent> res = new TransportProviderParent();
+ return res.forget().take();
+}
+
+bool NeckoParent::DeallocPTransportProviderParent(
+ PTransportProviderParent* aActor) {
+ RefPtr<TransportProviderParent> provider =
+ dont_AddRef(static_cast<TransportProviderParent*>(aActor));
+ return true;
+}
+
+/* Predictor Messages */
+mozilla::ipc::IPCResult NeckoParent::RecvPredPredict(
+ nsIURI* aTargetURI, nsIURI* aSourceURI, const uint32_t& aReason,
+ const OriginAttributes& aOriginAttributes, const bool& hasVerifier) {
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+
+ nsCOMPtr<nsINetworkPredictorVerifier> verifier;
+ if (hasVerifier) {
+ verifier = do_QueryInterface(predictor);
+ }
+ predictor->PredictNative(aTargetURI, aSourceURI, aReason, aOriginAttributes,
+ verifier);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPredLearn(
+ nsIURI* aTargetURI, nsIURI* aSourceURI, const uint32_t& aReason,
+ const OriginAttributes& aOriginAttributes) {
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+
+ predictor->LearnNative(aTargetURI, aSourceURI, aReason, aOriginAttributes);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPredReset() {
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+
+ predictor->Reset();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvRequestContextLoadBegin(
+ const uint64_t& rcid) {
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ RequestContextService::GetOrCreate();
+ if (!rcsvc) {
+ return IPC_OK();
+ }
+ nsCOMPtr<nsIRequestContext> rc;
+ rcsvc->GetRequestContext(rcid, getter_AddRefs(rc));
+ if (rc) {
+ rc->BeginLoad();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvRequestContextAfterDOMContentLoaded(
+ const uint64_t& rcid) {
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ RequestContextService::GetOrCreate();
+ if (!rcsvc) {
+ return IPC_OK();
+ }
+ nsCOMPtr<nsIRequestContext> rc;
+ rcsvc->GetRequestContext(rcid, getter_AddRefs(rc));
+ if (rc) {
+ rc->DOMContentLoaded();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvRemoveRequestContext(
+ const uint64_t& rcid) {
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ RequestContextService::GetOrCreate();
+ if (!rcsvc) {
+ return IPC_OK();
+ }
+
+ rcsvc->RemoveRequestContext(rcid);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvGetExtensionStream(
+ nsIURI* aURI, GetExtensionStreamResolver&& aResolve) {
+ if (!aURI) {
+ return IPC_FAIL(this, "aURI must not be null");
+ }
+
+ RefPtr<ExtensionProtocolHandler> ph(ExtensionProtocolHandler::GetSingleton());
+ MOZ_ASSERT(ph);
+
+ // Ask the ExtensionProtocolHandler to give us a new input stream for
+ // this URI. The request comes from an ExtensionProtocolHandler in the
+ // child process, but is not guaranteed to be a valid moz-extension URI,
+ // and not guaranteed to represent a resource that the child should be
+ // allowed to access. The ExtensionProtocolHandler is responsible for
+ // validating the request. Specifically, only URI's for local files that
+ // an extension is allowed to access via moz-extension URI's should be
+ // accepted.
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool terminateSender = true;
+ auto inputStreamOrReason = ph->NewStream(aURI, &terminateSender);
+ if (inputStreamOrReason.isOk()) {
+ inputStream = inputStreamOrReason.unwrap();
+ }
+
+ // If NewStream failed, we send back an invalid stream to the child so
+ // it can handle the error. MozPromise rejection is reserved for channel
+ // errors/disconnects.
+ aResolve(inputStream);
+
+ if (terminateSender) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvGetExtensionFD(
+ nsIURI* aURI, GetExtensionFDResolver&& aResolve) {
+ if (!aURI) {
+ return IPC_FAIL(this, "aURI must not be null");
+ }
+
+ RefPtr<ExtensionProtocolHandler> ph(ExtensionProtocolHandler::GetSingleton());
+ MOZ_ASSERT(ph);
+
+ // Ask the ExtensionProtocolHandler to give us a new input stream for
+ // this URI. The request comes from an ExtensionProtocolHandler in the
+ // child process, but is not guaranteed to be a valid moz-extension URI,
+ // and not guaranteed to represent a resource that the child should be
+ // allowed to access. The ExtensionProtocolHandler is responsible for
+ // validating the request. Specifically, only URI's for local files that
+ // an extension is allowed to access via moz-extension URI's should be
+ // accepted.
+ bool terminateSender = true;
+ auto result = ph->NewFD(aURI, &terminateSender, aResolve);
+
+ if (result.isErr() && terminateSender) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (result.isErr()) {
+ FileDescriptor invalidFD;
+ aResolve(invalidFD);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvInitSocketProcessBridge(
+ InitSocketProcessBridgeResolver&& aResolver) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Initing the socket process bridge must be async here in order to
+ // wait for the socket process launch before executing.
+ auto task = [self = RefPtr{this}, resolver = std::move(aResolver)]() {
+ // The content process might be already destroyed.
+ if (!self->CanSend()) {
+ return;
+ }
+
+ Endpoint<PSocketProcessBridgeChild> invalidEndpoint;
+ if (NS_WARN_IF(self->mSocketProcessBridgeInited)) {
+ resolver(std::move(invalidEndpoint));
+ return;
+ }
+
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (NS_WARN_IF(!parent)) {
+ resolver(std::move(invalidEndpoint));
+ return;
+ }
+
+ Endpoint<PSocketProcessBridgeParent> parentEndpoint;
+ Endpoint<PSocketProcessBridgeChild> childEndpoint;
+ if (NS_WARN_IF(NS_FAILED(PSocketProcessBridge::CreateEndpoints(
+ parent->OtherPid(), self->Manager()->OtherPid(), &parentEndpoint,
+ &childEndpoint)))) {
+ resolver(std::move(invalidEndpoint));
+ return;
+ }
+
+ if (NS_WARN_IF(!parent->SendInitSocketProcessBridgeParent(
+ self->Manager()->OtherPid(), std::move(parentEndpoint)))) {
+ resolver(std::move(invalidEndpoint));
+ return;
+ }
+
+ resolver(std::move(childEndpoint));
+ self->mSocketProcessBridgeInited = true;
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvResetSocketProcessBridge() {
+ // SendResetSocketProcessBridge is called from
+ // SocketProcessBridgeChild::ActorDestroy if the socket process
+ // crashes. This is necessary in order to properly initialize the
+ // restarted socket process.
+ mSocketProcessBridgeInited = false;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvEnsureHSTSData(
+ EnsureHSTSDataResolver&& aResolver) {
+ auto callback = [aResolver{std::move(aResolver)}](bool aResult) {
+ aResolver(aResult);
+ };
+ RefPtr<HSTSDataCallbackWrapper> wrapper =
+ new HSTSDataCallbackWrapper(std::move(callback));
+ gHttpHandler->EnsureHSTSDataReadyNative(wrapper);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvGetPageThumbStream(
+ nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs,
+ GetPageThumbStreamResolver&& aResolver) {
+ // Only the privileged about content process is allowed to access
+ // things over the moz-page-thumb protocol. Any other content process
+ // that tries to send this should have been blocked via the
+ // ScriptSecurityManager, but if somehow the process has been tricked into
+ // sending this message, we send IPC_FAIL in order to crash that
+ // likely-compromised content process.
+ if (static_cast<ContentParent*>(Manager())->GetRemoteType() !=
+ PRIVILEGEDABOUT_REMOTE_TYPE) {
+ return IPC_FAIL(this, "Wrong process type");
+ }
+
+ RefPtr<PageThumbProtocolHandler> ph(PageThumbProtocolHandler::GetSingleton());
+ MOZ_ASSERT(ph);
+
+ // Ask the PageThumbProtocolHandler to give us a new input stream for
+ // this URI. The request comes from a PageThumbProtocolHandler in the
+ // child process, but is not guaranteed to be a valid moz-page-thumb URI,
+ // and not guaranteed to represent a resource that the child should be
+ // allowed to access. The PageThumbProtocolHandler is responsible for
+ // validating the request.
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool terminateSender = true;
+ auto inputStreamPromise = ph->NewStream(aURI, &terminateSender);
+
+ if (terminateSender) {
+ return IPC_FAIL(this, "Malformed moz-page-thumb request");
+ }
+
+ inputStreamPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolver](const RemoteStreamInfo& aInfo) { aResolver(Some(aInfo)); },
+ [aResolver](nsresult aRv) {
+ // If NewStream failed, we send back an invalid stream to the child so
+ // it can handle the error. MozPromise rejection is reserved for channel
+ // errors/disconnects.
+ Unused << NS_WARN_IF(NS_FAILED(aRv));
+ aResolver(Nothing());
+ });
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvGetPageIconStream(
+ nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs,
+ GetPageIconStreamResolver&& aResolver) {
+#ifdef MOZ_PLACES
+ const nsACString& remoteType =
+ ContentParent::Cast(Manager())->GetRemoteType();
+
+ // Only the privileged about content process is allowed to access
+ // things over the page-icon protocol. Any other content process
+ // that tries to send this should have been blocked via the
+ // ScriptSecurityManager, but if somehow the process has been tricked into
+ // sending this message, we send IPC_FAIL in order to crash that
+ // likely-compromised content process.
+ if (remoteType != PRIVILEGEDABOUT_REMOTE_TYPE) {
+ return IPC_FAIL(this, "Wrong process type");
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, remoteType,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return IPC_FAIL(this, "Page-icon request must include loadInfo");
+ }
+
+ RefPtr<PageIconProtocolHandler> ph(PageIconProtocolHandler::GetSingleton());
+ MOZ_ASSERT(ph);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool terminateSender = true;
+ auto inputStreamPromise = ph->NewStream(aURI, loadInfo, &terminateSender);
+
+ if (terminateSender) {
+ return IPC_FAIL(this, "Malformed page-icon request");
+ }
+
+ inputStreamPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolver](const RemoteStreamInfo& aInfo) { aResolver(Some(aInfo)); },
+ [aResolver](nsresult aRv) {
+ // If NewStream failed, we send back an invalid stream to the child so
+ // it can handle the error. MozPromise rejection is reserved for channel
+ // errors/disconnects.
+ Unused << NS_WARN_IF(NS_FAILED(aRv));
+ aResolver(Nothing());
+ });
+
+ return IPC_OK();
+#else
+ return IPC_FAIL(this, "page-icon: protocol unavailable");
+#endif
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h
new file mode 100644
index 0000000000..9c7ffa4636
--- /dev/null
+++ b/netwerk/ipc/NeckoParent.h
@@ -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 "mozilla/BasePrincipal.h"
+#include "mozilla/net/PNeckoParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "nsIAuthPrompt2.h"
+#include "nsINetworkPredictor.h"
+#include "nsNetUtil.h"
+
+#ifndef mozilla_net_NeckoParent_h
+# define mozilla_net_NeckoParent_h
+
+namespace mozilla {
+namespace net {
+
+// Used to override channel Private Browsing status if needed.
+enum PBOverrideStatus {
+ kPBOverride_Unset = 0,
+ kPBOverride_Private,
+ kPBOverride_NotPrivate
+};
+
+// Header file contents
+class NeckoParent : public PNeckoParent {
+ friend class PNeckoParent;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NeckoParent, override)
+
+ NeckoParent();
+
+ [[nodiscard]] static const char* GetValidatedOriginAttributes(
+ const SerializedLoadContext& aSerialized, PContentParent* aBrowser,
+ nsIPrincipal* aRequestingPrincipal, mozilla::OriginAttributes& aAttrs);
+
+ /*
+ * Creates LoadContext for parent-side of an e10s channel.
+ *
+ * PContentParent corresponds to the process that is requesting the load.
+ *
+ * Returns null if successful, or an error string if failed.
+ */
+ [[nodiscard]] static const char* CreateChannelLoadContext(
+ PBrowserParent* aBrowser, PContentParent* aContent,
+ const SerializedLoadContext& aSerialized,
+ nsIPrincipal* aRequestingPrincipal, nsCOMPtr<nsILoadContext>& aResult);
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ PCookieServiceParent* AllocPCookieServiceParent();
+ virtual mozilla::ipc::IPCResult RecvPCookieServiceConstructor(
+ PCookieServiceParent* aActor) override {
+ return PNeckoParent::RecvPCookieServiceConstructor(aActor);
+ }
+
+ protected:
+ virtual ~NeckoParent() = default;
+
+ bool mSocketProcessBridgeInited;
+
+ already_AddRefed<PHttpChannelParent> AllocPHttpChannelParent(
+ PBrowserParent*, const SerializedLoadContext&,
+ const HttpChannelCreationArgs& aOpenArgs);
+ virtual mozilla::ipc::IPCResult RecvPHttpChannelConstructor(
+ PHttpChannelParent* aActor, PBrowserParent* aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const HttpChannelCreationArgs& aOpenArgs) override;
+
+ PStunAddrsRequestParent* AllocPStunAddrsRequestParent();
+ bool DeallocPStunAddrsRequestParent(PStunAddrsRequestParent* aActor);
+
+ PWebrtcTCPSocketParent* AllocPWebrtcTCPSocketParent(
+ const Maybe<TabId>& aTabId);
+ bool DeallocPWebrtcTCPSocketParent(PWebrtcTCPSocketParent* aActor);
+
+ PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent(
+ const nsACString& type, const int64_t& predictedSize,
+ PHttpChannelParent* channel);
+ bool DeallocPAltDataOutputStreamParent(PAltDataOutputStreamParent* aActor);
+
+ bool DeallocPCookieServiceParent(PCookieServiceParent*);
+ PWebSocketParent* AllocPWebSocketParent(
+ PBrowserParent* browser, const SerializedLoadContext& aSerialized,
+ const uint32_t& aSerial);
+ bool DeallocPWebSocketParent(PWebSocketParent*);
+ PTCPSocketParent* AllocPTCPSocketParent(const nsAString& host,
+ const uint16_t& port);
+
+ already_AddRefed<PDocumentChannelParent> AllocPDocumentChannelParent(
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aContext,
+ const DocumentChannelCreationArgs& args);
+ virtual mozilla::ipc::IPCResult RecvPDocumentChannelConstructor(
+ PDocumentChannelParent* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aContext,
+ const DocumentChannelCreationArgs& aArgs) override;
+ bool DeallocPDocumentChannelParent(PDocumentChannelParent* channel);
+
+ bool DeallocPTCPSocketParent(PTCPSocketParent*);
+ PTCPServerSocketParent* AllocPTCPServerSocketParent(
+ const uint16_t& aLocalPort, const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers);
+ virtual mozilla::ipc::IPCResult RecvPTCPServerSocketConstructor(
+ PTCPServerSocketParent*, const uint16_t& aLocalPort,
+ const uint16_t& aBacklog, const bool& aUseArrayBuffers) override;
+ bool DeallocPTCPServerSocketParent(PTCPServerSocketParent*);
+ PUDPSocketParent* AllocPUDPSocketParent(nsIPrincipal* aPrincipal,
+ const nsACString& aFilter);
+ virtual mozilla::ipc::IPCResult RecvPUDPSocketConstructor(
+ PUDPSocketParent*, nsIPrincipal* aPrincipal,
+ const nsACString& aFilter) override;
+ bool DeallocPUDPSocketParent(PUDPSocketParent*);
+ already_AddRefed<PDNSRequestParent> AllocPDNSRequestParent(
+ const nsACString& aHost, const nsACString& aTrrServer,
+ const int32_t& aPort, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags);
+ virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor(
+ PDNSRequestParent* actor, const nsACString& aHost,
+ const nsACString& trrServer, const int32_t& aPort, const uint16_t& type,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& flags) override;
+ mozilla::ipc::IPCResult RecvSpeculativeConnect(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes, const bool& aAnonymous);
+ mozilla::ipc::IPCResult RecvHTMLDNSPrefetch(
+ const nsAString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& flags);
+ mozilla::ipc::IPCResult RecvCancelHTMLDNSPrefetch(
+ const nsAString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& flags, const nsresult& reason);
+ PWebSocketEventListenerParent* AllocPWebSocketEventListenerParent(
+ const uint64_t& aInnerWindowID);
+ bool DeallocPWebSocketEventListenerParent(PWebSocketEventListenerParent*);
+
+ already_AddRefed<PDataChannelParent> AllocPDataChannelParent(
+ const uint32_t& channelId);
+
+ virtual mozilla::ipc::IPCResult RecvPDataChannelConstructor(
+ PDataChannelParent* aActor, const uint32_t& channelId) override;
+# ifdef MOZ_WIDGET_GTK
+ PGIOChannelParent* AllocPGIOChannelParent(
+ PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
+ const GIOChannelCreationArgs& aOpenArgs);
+ bool DeallocPGIOChannelParent(PGIOChannelParent* channel);
+
+ virtual mozilla::ipc::IPCResult RecvPGIOChannelConstructor(
+ PGIOChannelParent* aActor, PBrowserParent* aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const GIOChannelCreationArgs& aOpenArgs) override;
+# endif
+ PSimpleChannelParent* AllocPSimpleChannelParent(const uint32_t& channelId);
+ bool DeallocPSimpleChannelParent(PSimpleChannelParent* actor);
+
+ virtual mozilla::ipc::IPCResult RecvPSimpleChannelConstructor(
+ PSimpleChannelParent* aActor, const uint32_t& channelId) override;
+
+ already_AddRefed<PFileChannelParent> AllocPFileChannelParent(
+ const uint32_t& channelId);
+
+ virtual mozilla::ipc::IPCResult RecvPFileChannelConstructor(
+ PFileChannelParent* aActor, const uint32_t& channelId) override;
+
+ PTransportProviderParent* AllocPTransportProviderParent();
+ bool DeallocPTransportProviderParent(PTransportProviderParent* aActor);
+
+ /* Predictor Messages */
+ mozilla::ipc::IPCResult RecvPredPredict(
+ nsIURI* aTargetURI, nsIURI* aSourceURI,
+ const PredictorPredictReason& aReason,
+ const OriginAttributes& aOriginAttributes, const bool& hasVerifier);
+
+ mozilla::ipc::IPCResult RecvPredLearn(
+ nsIURI* aTargetURI, nsIURI* aSourceURI,
+ const PredictorPredictReason& aReason,
+ const OriginAttributes& aOriginAttributes);
+ mozilla::ipc::IPCResult RecvPredReset();
+
+ mozilla::ipc::IPCResult RecvRequestContextLoadBegin(const uint64_t& rcid);
+ mozilla::ipc::IPCResult RecvRequestContextAfterDOMContentLoaded(
+ const uint64_t& rcid);
+ mozilla::ipc::IPCResult RecvRemoveRequestContext(const uint64_t& rcid);
+
+ /* WebExtensions */
+ mozilla::ipc::IPCResult RecvGetExtensionStream(
+ nsIURI* aURI, GetExtensionStreamResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvGetExtensionFD(nsIURI* aURI,
+ GetExtensionFDResolver&& aResolve);
+
+ /* Page thumbnails remote resource loading */
+ mozilla::ipc::IPCResult RecvGetPageThumbStream(
+ nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs,
+ GetPageThumbStreamResolver&& aResolve);
+
+ /* Page icon remote resource loading */
+ mozilla::ipc::IPCResult RecvGetPageIconStream(
+ nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs,
+ GetPageIconStreamResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvInitSocketProcessBridge(
+ InitSocketProcessBridgeResolver&& aResolver);
+ mozilla::ipc::IPCResult RecvResetSocketProcessBridge();
+
+ mozilla::ipc::IPCResult RecvEnsureHSTSData(
+ EnsureHSTSDataResolver&& aResolver);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NeckoParent_h
diff --git a/netwerk/ipc/NeckoTargetHolder.cpp b/netwerk/ipc/NeckoTargetHolder.cpp
new file mode 100644
index 0000000000..90d80ce7d5
--- /dev/null
+++ b/netwerk/ipc/NeckoTargetHolder.cpp
@@ -0,0 +1,38 @@
+/* -*- 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 "NeckoTargetHolder.h"
+
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace net {
+
+already_AddRefed<nsISerialEventTarget> NeckoTargetHolder::GetNeckoTarget() {
+ nsCOMPtr<nsISerialEventTarget> target = mNeckoTarget;
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+nsresult NeckoTargetHolder::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable,
+ uint32_t aDispatchFlags) {
+ if (mNeckoTarget) {
+ return mNeckoTarget->Dispatch(std::move(aRunnable), aDispatchFlags);
+ }
+
+ nsCOMPtr<nsISerialEventTarget> mainThreadTarget =
+ GetMainThreadSerialEventTarget();
+ MOZ_ASSERT(mainThreadTarget);
+
+ return mainThreadTarget->Dispatch(std::move(aRunnable), aDispatchFlags);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoTargetHolder.h b/netwerk/ipc/NeckoTargetHolder.h
new file mode 100644
index 0000000000..254d6f138f
--- /dev/null
+++ b/netwerk/ipc/NeckoTargetHolder.h
@@ -0,0 +1,40 @@
+/* -*- 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_NeckoTargetHolder_h
+#define mozilla_net_NeckoTargetHolder_h
+
+#include "nsIEventTarget.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+// A helper class that implements GetNeckoTarget(). Basically, all e10s child
+// channels should inherit this class in order to get a labeled event target.
+class NeckoTargetHolder {
+ public:
+ explicit NeckoTargetHolder(nsISerialEventTarget* aNeckoTarget)
+ : mNeckoTarget(aNeckoTarget) {}
+
+ protected:
+ virtual ~NeckoTargetHolder() = default;
+ // Get event target for processing network events.
+ virtual already_AddRefed<nsISerialEventTarget> GetNeckoTarget();
+ // When |mNeckoTarget| is not null, use it to dispatch the runnable.
+ // Otherwise, dispatch the runnable to the main thread.
+ nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable,
+ uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
+
+ // EventTarget for labeling networking events.
+ nsCOMPtr<nsISerialEventTarget> mNeckoTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/ipc/PDataChannel.ipdl b/netwerk/ipc/PDataChannel.ipdl
new file mode 100644
index 0000000000..52d8642ff7
--- /dev/null
+++ b/netwerk/ipc/PDataChannel.ipdl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+[ChildImpl=virtual]
+async protocol PDataChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PDocumentChannel.ipdl b/netwerk/ipc/PDocumentChannel.ipdl
new file mode 100644
index 0000000000..1e40052a25
--- /dev/null
+++ b/netwerk/ipc/PDocumentChannel.ipdl
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PStreamFilter;
+include InputStreamParams;
+include PBackgroundSharedTypes;
+include NeckoChannelParams;
+include IPCServiceWorkerDescriptor;
+include IPCStream;
+include DOMTypes;
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+using class mozilla::net::nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+
+namespace mozilla {
+namespace net {
+
+protocol PDocumentChannel
+{
+ manager PNecko;
+
+parent:
+
+ async Cancel(nsresult status, nsCString reason);
+
+ async __delete__();
+
+child:
+
+ // Used to cancel child channel if we hit errors during creating and
+ // AsyncOpen of nsHttpChannel on the parent.
+ async FailedAsyncOpen(nsresult status);
+
+ // This message is sent to a child that has been redirected to another process.
+ // As a consequence, it should cleanup the channel listeners and remove the
+ // request from the loadGroup.
+ // aStatus must be an error result.
+ // aLoadGroupReason is used as mStatus when we remove the child channel from
+ // the loadgroup (but aStatus is passed as the parameter to RemoveRequest).
+ // We do this so we can remove using NS_BINDING_RETARGETED, but still have the
+ // channel not be in an error state.
+ async DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupReason, bool aContinueNavigating);
+
+ // Triggers replacing this DocumentChannel with a 'real' channel (like PHttpChannel),
+ // and notifies the listener via a redirect to the new channel.
+ async RedirectToRealChannel(RedirectToRealChannelArgs args, Endpoint<PStreamFilterParent>[] aEndpoint)
+ returns (nsresult rv);
+
+ // May only be called on a DocumentChannel created by nsObjectLoadingContent
+ // for an object or embed element load.
+ //
+ // Promotes the load from an object load to a proper document load, and
+ // returns the `BrowsingContext` which should be used to host the final load.
+ async UpgradeObjectLoad() returns (MaybeDiscardedBrowsingContext frameContext);
+};
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/ipc/PFileChannel.ipdl b/netwerk/ipc/PFileChannel.ipdl
new file mode 100644
index 0000000000..8f86fc81b1
--- /dev/null
+++ b/netwerk/ipc/PFileChannel.ipdl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=2 sts=2 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+/* Used to facilitate http redirects to file:// - see
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1345094
+ */
+[ChildImpl=virtual]
+async protocol PFileChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PInputChannelThrottleQueue.ipdl b/netwerk/ipc/PInputChannelThrottleQueue.ipdl
new file mode 100644
index 0000000000..b95d9cc77f
--- /dev/null
+++ b/netwerk/ipc/PInputChannelThrottleQueue.ipdl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+
+namespace mozilla {
+namespace net {
+
+protocol PInputChannelThrottleQueue
+{
+ manager PSocketProcess;
+
+parent:
+ async RecordRead(uint32_t aBytesRead);
+
+child:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl
new file mode 100644
index 0000000000..9eb9b5ca27
--- /dev/null
+++ b/netwerk/ipc/PNecko.ipdl
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+include protocol PHttpChannel;
+include protocol PCookieService;
+include protocol PBrowser;
+#ifdef MOZ_WIDGET_GTK
+include protocol PGIOChannel;
+#endif
+include protocol PWebSocket;
+include protocol PWebSocketEventListener;
+include protocol PTCPSocket;
+include protocol PTCPServerSocket;
+include protocol PUDPSocket;
+include protocol PDNSRequest;
+include protocol PDataChannel;
+include protocol PSimpleChannel;
+include protocol PTransportProvider;
+include protocol PStunAddrsRequest;
+include protocol PFileChannel;
+include protocol PWebrtcTCPSocket;
+include protocol PSocketProcessBridge;
+include protocol PDocumentChannel;
+
+include IPCStream;
+include NeckoChannelParams;
+include protocol PAltDataOutputStream;
+
+include "mozilla/dom/PermissionMessageUtils.h";
+
+using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
+using nsIDNSService::DNSFlags from "nsIDNSService.h";
+[RefCounted] using class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
+[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h";
+[RefCounted] using class nsIPrincipal from "nsIPrincipal.h";
+
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[NestedUpTo=inside_cpow] sync protocol PNecko
+{
+ manager PContent;
+ manages PHttpChannel;
+ manages PCookieService;
+ manages PWebSocket;
+ manages PWebSocketEventListener;
+ manages PTCPSocket;
+ manages PTCPServerSocket;
+ manages PUDPSocket;
+ manages PDNSRequest;
+ manages PDataChannel;
+#ifdef MOZ_WIDGET_GTK
+ manages PGIOChannel;
+#endif
+ manages PSimpleChannel;
+ manages PFileChannel;
+ manages PTransportProvider;
+ manages PAltDataOutputStream;
+ manages PStunAddrsRequest;
+ manages PWebrtcTCPSocket;
+ manages PDocumentChannel;
+
+parent:
+ async __delete__();
+
+ [Nested=inside_cpow] async PCookieService();
+ async PHttpChannel(nullable PBrowser browser,
+ SerializedLoadContext loadContext,
+ HttpChannelCreationArgs args);
+
+ async PWebSocket(nullable PBrowser browser, SerializedLoadContext loadContext,
+ uint32_t aSerialID);
+ async PTCPServerSocket(uint16_t localPort, uint16_t backlog, bool useArrayBuffers);
+ async PUDPSocket(nullable nsIPrincipal principal, nsCString filter);
+
+ async PDNSRequest(nsCString hostName, nsCString trrServer, int32_t port,
+ uint16_t type, OriginAttributes originAttributes,
+ DNSFlags flags);
+
+ async PDocumentChannel(MaybeDiscardedBrowsingContext browsingContext,
+ DocumentChannelCreationArgs args);
+
+ async PWebSocketEventListener(uint64_t aInnerWindowID);
+
+ /* Predictor Methods */
+ async PredPredict(nullable nsIURI targetURI, nullable nsIURI sourceURI,
+ uint32_t reason, OriginAttributes originAttributes,
+ bool hasVerifier);
+ async PredLearn(nullable nsIURI targetURI, nullable nsIURI sourceURI,
+ uint32_t reason, OriginAttributes originAttributes);
+ async PredReset();
+
+ async SpeculativeConnect(nullable nsIURI uri,
+ nullable nsIPrincipal principal,
+ OriginAttributes? originAttributes,
+ bool anonymous);
+ async HTMLDNSPrefetch(nsString hostname, bool isHttps,
+ OriginAttributes originAttributes, DNSFlags flags);
+ async CancelHTMLDNSPrefetch(nsString hostname, bool isHttps,
+ OriginAttributes originAttributes,
+ DNSFlags flags, nsresult reason);
+
+ /**
+ * channelId is used to establish a connection between redirect channels in
+ * the parent and the child when we're redirecting to a data: URI.
+ */
+ async PDataChannel(uint32_t channelId);
+#ifdef MOZ_WIDGET_GTK
+ async PGIOChannel(nullable PBrowser browser, SerializedLoadContext loadContext, GIOChannelCreationArgs args);
+#endif
+ async PSimpleChannel(uint32_t channelId);
+ async PFileChannel(uint32_t channelId);
+
+ async RequestContextLoadBegin(uint64_t rcid);
+ async RequestContextAfterDOMContentLoaded(uint64_t rcid);
+ async RemoveRequestContext(uint64_t rcid);
+
+ async PAltDataOutputStream(nsCString type, int64_t predictedSize, PHttpChannel channel);
+
+ async PStunAddrsRequest();
+
+ /* tabId is only required for web-proxy support, which isn't always needed */
+ async PWebrtcTCPSocket(TabId? tabId);
+
+ /**
+ * WebExtension-specific remote resource loading
+ */
+ async GetExtensionStream(nullable nsIURI uri) returns (nullable nsIInputStream stream);
+ async GetExtensionFD(nullable nsIURI uri) returns (FileDescriptor fd);
+
+ async InitSocketProcessBridge()
+ returns (Endpoint<PSocketProcessBridgeChild> endpoint);
+ async ResetSocketProcessBridge();
+
+ async EnsureHSTSData()
+ returns (bool result);
+
+ /**
+ * Page thumbnails remote resource loading
+ */
+ async GetPageThumbStream(nullable nsIURI uri, LoadInfoArgs loadInfo) returns (RemoteStreamInfo? info);
+ async GetPageIconStream(nullable nsIURI uri, LoadInfoArgs loadInfo) returns (RemoteStreamInfo? info);
+
+child:
+ /* Predictor Methods */
+ async PredOnPredictPrefetch(nullable nsIURI uri, uint32_t httpStatus);
+ async PredOnPredictPreconnect(nullable nsIURI uri);
+ async PredOnPredictDNS(nullable nsIURI uri);
+
+ async SpeculativeConnectRequest();
+
+ // Using medium high priority to deliver this notification possibly sooner than we
+ // enter poll() on the child process with infinite timeout.
+ [Priority=mediumhigh] async NetworkChangeNotification(nsCString type);
+
+ async PTransportProvider();
+
+ async SetTRRDomain(nsCString domain);
+
+both:
+ // Actually we need PTCPSocket() for parent. But ipdl disallows us having different
+ // signatures on parent and child. So when constructing the parent side object, we just
+ // leave host/port unused.
+ async PTCPSocket(nsString host, uint16_t port);
+};
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PProxyAutoConfig.ipdl b/netwerk/ipc/PProxyAutoConfig.ipdl
new file mode 100644
index 0000000000..7018243fa6
--- /dev/null
+++ b/netwerk/ipc/PProxyAutoConfig.ipdl
@@ -0,0 +1,21 @@
+/* -*- 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/. */
+
+namespace mozilla {
+namespace net {
+
+[ChildProc=Socket]
+sync protocol PProxyAutoConfig
+{
+child:
+ async ConfigurePAC(nsCString aPACURI, nsCString aPACScriptData,
+ bool aIncludePath, uint32_t aExtraHeapSize);
+ async GetProxyForURI(nsCString aTestURI, nsCString aTestHost)
+ returns (nsresult aStatus, nsCString aResult);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PProxyConfigLookup.ipdl b/netwerk/ipc/PProxyConfigLookup.ipdl
new file mode 100644
index 0000000000..9aaaf5fbcb
--- /dev/null
+++ b/netwerk/ipc/PProxyConfigLookup.ipdl
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+
+include NeckoChannelParams;
+
+namespace mozilla {
+namespace net {
+
+async protocol PProxyConfigLookup
+{
+ manager PSocketProcess;
+
+child:
+ async __delete__(ProxyInfoCloneArgs[] aProxyInfo, nsresult aResult);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PSimpleChannel.ipdl b/netwerk/ipc/PSimpleChannel.ipdl
new file mode 100644
index 0000000000..cae7fe8f1b
--- /dev/null
+++ b/netwerk/ipc/PSimpleChannel.ipdl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc, ChildImpl=virtual]
+async protocol PSimpleChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PSocketProcess.ipdl b/netwerk/ipc/PSocketProcess.ipdl
new file mode 100644
index 0000000000..ede6397fa4
--- /dev/null
+++ b/netwerk/ipc/PSocketProcess.ipdl
@@ -0,0 +1,222 @@
+/* -*- 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 protocol PDNSRequest;
+include protocol PSocketProcessBridge;
+include protocol PProfiler;
+include protocol PWebrtcTCPSocket;
+include protocol PHttpTransaction;
+include protocol PHttpConnectionMgr;
+include protocol PInputChannelThrottleQueue;
+include protocol PAltService;
+include protocol PAltSvcTransaction;
+include protocol PTRRService;
+include protocol PProxyConfigLookup;
+include protocol PNativeDNSResolverOverride;
+include protocol PProxyAutoConfig;
+include protocol PSocketProcessBackground;
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+include protocol PSandboxTesting;
+#endif
+
+include MemoryReportTypes;
+include NeckoChannelParams;
+include PrefsTypes;
+
+include "mozilla/ipc/ByteBufUtils.h";
+
+using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h";
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
+using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
+using base::ProcessId from "base/process.h";
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using PRTime from "prtime.h";
+[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h";
+using struct nsID from "nsID.h";
+using mozilla::net::SocketInfo from "mozilla/net/DashboardTypes.h";
+using mozilla::net::DNSCacheEntries from "mozilla/net/DashboardTypes.h";
+using mozilla::net::HttpRetParams from "mozilla/net/DashboardTypes.h";
+using nsIDNSService::DNSFlags from "nsIDNSService.h";
+
+#if defined(XP_WIN)
+[MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h";
+[MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h";
+[MoveOnly] using mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h";
+#endif // defined(XP_WIN)
+
+namespace mozilla {
+namespace net {
+
+struct HttpHandlerInitArgs {
+ nsCString mLegacyAppName;
+ nsCString mLegacyAppVersion;
+ nsCString mPlatform;
+ nsCString mOscpu;
+ nsCString mMisc;
+ nsCString mProduct;
+ nsCString mProductSub;
+ nsCString mAppName;
+ nsCString mAppVersion;
+ nsCString mCompatFirefox;
+ nsCString mCompatDevice;
+ nsCString mDeviceModelId;
+};
+
+struct SocketDataArgs
+{
+ uint64_t totalSent;
+ uint64_t totalRecv;
+ SocketInfo[] info;
+};
+
+struct SocketPorcessInitAttributes {
+ bool mOffline;
+ bool mConnectivity;
+ bool mInitSandbox;
+#if defined(XP_WIN)
+ bool mIsReadyForBackgroundProcessing;
+#endif
+ FileDescriptor? mSandboxBroker;
+};
+
+[NeedsOtherPid, ChildProc=Socket]
+sync protocol PSocketProcess
+{
+ manages PDNSRequest;
+ manages PWebrtcTCPSocket;
+ manages PHttpTransaction;
+ manages PHttpConnectionMgr;
+ manages PInputChannelThrottleQueue;
+ manages PAltService;
+ manages PAltSvcTransaction;
+ manages PTRRService;
+ manages PProxyConfigLookup;
+ manages PNativeDNSResolverOverride;
+
+parent:
+ async InitCrashReporter(NativeThreadId threadId);
+ async AddMemoryReport(MemoryReport aReport);
+ // Messages for sending telemetry to parent process.
+ async AccumulateChildHistograms(HistogramAccumulation[] accumulations);
+ async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations);
+ async UpdateChildScalars(ScalarAction[] actions);
+ async UpdateChildKeyedScalars(KeyedScalarAction[] actions);
+ async RecordChildEvents(ChildEventData[] events);
+ async RecordDiscardedData(DiscardedData data);
+
+ /* tabId is only required for web-proxy support, which isn't always needed */
+ async PWebrtcTCPSocket(TabId? tabId);
+ async ObserveHttpActivity(HttpActivityArgs aActivityArgs,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ nsCString aExtraStringData);
+ async InitSocketBackground(Endpoint<PSocketProcessBackgroundParent> aEndpoint);
+ async PAltService();
+ async PProxyConfigLookup(nullable nsIURI aUri, uint32_t aFlags);
+ async CachePushCheck(nullable nsIURI aPushedURL,
+ OriginAttributes aOriginAttributes,
+ nsCString aRequestString)
+ returns (bool aAccepted);
+
+ async ExcludeHttp2OrHttp3(HttpConnectionInfoCloneArgs aArgs);
+
+ async OnConsoleMessage(nsString aMessage);
+
+ // Sent from time-to-time to limit the amount of telemetry vulnerable to loss
+ // Buffer contains bincoded Rust structs.
+ // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html
+ async FOGData(ByteBuf buf);
+
+#if defined(XP_WIN)
+ async GetModulesTrust(ModulePaths aModPaths, bool aRunAtNormalPriority)
+ returns (ModulesMapResult? modMapResult);
+#endif // defined(XP_WIN)
+
+child:
+ async Init(SocketPorcessInitAttributes aAttributes);
+ async PreferenceUpdate(Pref pref);
+ async RequestMemoryReport(uint32_t generation,
+ bool anonymize,
+ bool minimizeMemoryUsage,
+ FileDescriptor? DMDFile)
+ returns (uint32_t aGeneration);
+ async SetOffline(bool offline);
+ async SetConnectivity(bool connectivity);
+ async InitLinuxSandbox(FileDescriptor? sandboxBroker);
+ async InitSocketProcessBridgeParent(ProcessId processId, Endpoint<PSocketProcessBridgeParent> endpoint);
+ async InitProfiler(Endpoint<PProfilerChild> aEndpoint);
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
+#endif
+ // test-only
+ async SocketProcessTelemetryPing();
+
+ async PHttpTransaction();
+ async PHttpConnectionMgr(HttpHandlerInitArgs aArgs);
+ async UpdateDeviceModelId(nsCString aModelId);
+
+ async OnHttpActivityDistributorActivated(bool aIsActivated);
+ async OnHttpActivityDistributorObserveProxyResponse(bool aIsEnabled);
+ async OnHttpActivityDistributorObserveConnection(bool aIsEnabled);
+ async PInputChannelThrottleQueue(uint32_t meanBytesPerSecond,
+ uint32_t maxBytesPerSecond);
+ async PAltSvcTransaction(HttpConnectionInfoCloneArgs aConnInfo,
+ uint32_t aCaps);
+ async ClearSessionCache() returns (void_t ok);
+ async PTRRService(bool aCaptiveIsPassed,
+ bool aParentalControlEnabled,
+ nsCString[] aDNSSuffixList);
+ async PNativeDNSResolverOverride();
+ async NotifyObserver(nsCString aTopic, nsString aData);
+
+ async GetSocketData()
+ returns (SocketDataArgs data);
+ async GetDNSCacheEntries()
+ returns (DNSCacheEntries[] entries);
+ async GetHttpConnectionData()
+ returns (HttpRetParams[] params);
+
+ async InitProxyAutoConfigChild(Endpoint<PProxyAutoConfigChild> endpoint);
+
+ async RecheckIPConnectivity();
+ async RecheckDNS();
+
+ // Tells the Socket process to flush any pending telemetry.
+ // Used in tests and ping assembly. Buffer contains bincoded Rust structs.
+ // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html
+ async FlushFOGData() returns (ByteBuf buf);
+
+ // Test-only method.
+ // Asks the Socket process to trigger test-only instrumentation.
+ // The unused returned value is to have a promise we can await.
+ async TestTriggerMetrics() returns (bool unused);
+
+#if defined(XP_WIN)
+ async GetUntrustedModulesData() returns (UntrustedModulesData? data);
+
+ /**
+ * This method is used to notifty a child process to start
+ * processing module loading events in UntrustedModulesProcessor.
+ * This should be called when the parent process has gone idle.
+ */
+ async UnblockUntrustedModulesThread();
+#endif // defined(XP_WIN)
+
+both:
+ async PDNSRequest(nsCString hostName, nsCString trrServer, int32_t port,
+ uint16_t type, OriginAttributes originAttributes,
+ DNSFlags flags);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PSocketProcessBackground.ipdl b/netwerk/ipc/PSocketProcessBackground.ipdl
new file mode 100644
index 0000000000..8c703a4ae2
--- /dev/null
+++ b/netwerk/ipc/PSocketProcessBackground.ipdl
@@ -0,0 +1,59 @@
+/* -*- 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 protocol PVerifySSLServerCert;
+include protocol PSelectTLSClientAuthCert;
+include protocol PIPCClientCerts;
+include protocol PWebSocketConnection;
+
+include PSMIPCTypes;
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+
+namespace mozilla {
+namespace net {
+
+/**
+ * PSocketProcessBackground is the top level IPC protocol between
+ * a background task queue in the socket process and the parent process.
+ * We use this to create several IPC protocols between the socket process
+ * and the parent process.
+ */
+[ChildProc=Socket]
+sync protocol PSocketProcessBackground
+{
+parent:
+ async InitVerifySSLServerCert(
+ Endpoint<PVerifySSLServerCertParent> aEndpoint,
+ ByteArray[] aPeerCertChain,
+ nsCString aHostName,
+ int32_t aPort,
+ OriginAttributes aOriginAttributes,
+ ByteArray? aStapledOCSPResponse,
+ ByteArray? aSctsFromTLSExtension,
+ DelegatedCredentialInfoArg? aDcInfo,
+ uint32_t aProviderFlags,
+ uint32_t aCertVerifierFlags);
+
+ async InitSelectTLSClientAuthCert(
+ Endpoint<PSelectTLSClientAuthCertParent> aEndpoint,
+ nsCString aHostName,
+ OriginAttributes aOriginAttributes,
+ int32_t aPort,
+ uint32_t aProviderFlags,
+ uint32_t aProviderTlsFlags,
+ ByteArray aServerCertBytes,
+ ByteArray[] aCANames,
+ uint64_t aBrowserId);
+
+ async InitIPCClientCerts(Endpoint<PIPCClientCertsParent> aEndpoint);
+
+ async InitWebSocketConnection(Endpoint<PWebSocketConnectionParent> aEndpoint,
+ uint32_t aListenerId);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PSocketProcessBridge.ipdl b/netwerk/ipc/PSocketProcessBridge.ipdl
new file mode 100644
index 0000000000..36c808bfdc
--- /dev/null
+++ b/netwerk/ipc/PSocketProcessBridge.ipdl
@@ -0,0 +1,48 @@
+/* -*- 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 protocol PBackgroundDataBridge;
+
+#ifdef MOZ_WEBRTC
+include protocol PMediaTransport;
+#endif // MOZ_WEBRTC
+
+
+namespace mozilla {
+namespace net {
+
+/**
+ * PSocketProcessBridge is the IPC protocol between the main thread in
+ * the content process and the main thread in the socket process.
+ * We use this to create PBackgroundDataBridge and PMediaTransport protocols
+ * between content process and socket process.
+ * Once created, PSocketProcessBridgeChild is the actor that lives in
+ * content process and PSocketProcessBridgeParent lives in
+ * socket process.
+ */
+[NeedsOtherPid, ParentProc=Socket, ChildProc=Content]
+sync protocol PSocketProcessBridge
+{
+parent:
+ /**
+ * For setting up PBackgroundDataBridge protocol, we use this message to
+ * create a background task queue and the BackgroundDataBridgeParent actor in
+ * socket process.
+ */
+ async InitBackgroundDataBridge(Endpoint<PBackgroundDataBridgeParent> aEndpoint,
+ uint64_t aChannelID);
+
+#ifdef MOZ_WEBRTC
+ /**
+ * Similar to the above, this message is for PMediaTransport.
+ */
+ async InitMediaTransport(Endpoint<PMediaTransportParent> aEndpoint);
+#endif // MOZ_WEBRTC
+
+};
+
+}
+}
diff --git a/netwerk/ipc/ParentChannelWrapper.cpp b/netwerk/ipc/ParentChannelWrapper.cpp
new file mode 100644
index 0000000000..3b89e3e38f
--- /dev/null
+++ b/netwerk/ipc/ParentChannelWrapper.cpp
@@ -0,0 +1,101 @@
+/* -*- 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 "ParentChannelWrapper.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "nsIViewSourceChannel.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "mozilla/dom/RemoteType.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(ParentChannelWrapper, nsIParentChannel, nsIStreamListener,
+ nsIRequestObserver);
+
+void ParentChannelWrapper::Register(uint64_t aRegistrarId) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ nsCOMPtr<nsIChannel> dummy;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_LinkRedirectChannels(aRegistrarId, this, getter_AddRefs(dummy)));
+
+#ifdef DEBUG
+ // The channel registered with the RedirectChannelRegistrar will be the inner
+ // channel when dealing with view-source loads.
+ if (nsCOMPtr<nsIViewSourceChannel> viewSource = do_QueryInterface(mChannel)) {
+ MOZ_ASSERT(dummy == viewSource->GetInnerChannel());
+ } else {
+ MOZ_ASSERT(dummy == mChannel);
+ }
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIParentChannel
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+ParentChannelWrapper::SetParentListener(
+ mozilla::net::ParentChannelListener* listener) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ParentChannelWrapper::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(mChannel);
+ if (classifiedChannel) {
+ classifiedChannel->SetMatchedInfo(aList, aProvider, aFullHash);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ParentChannelWrapper::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHash) {
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(mChannel);
+ if (classifiedChannel) {
+ nsTArray<nsCString> lists, fullhashes;
+ for (const nsACString& token : aLists.Split(',')) {
+ lists.AppendElement(token);
+ }
+ for (const nsACString& token : aFullHash.Split(',')) {
+ fullhashes.AppendElement(token);
+ }
+ classifiedChannel->SetMatchedTrackingInfo(lists, fullhashes);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ParentChannelWrapper::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ UrlClassifierCommon::SetClassificationFlagsHelper(
+ mChannel, aClassificationFlags, aIsThirdParty);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ParentChannelWrapper::Delete() { return NS_OK; }
+
+NS_IMETHODIMP
+ParentChannelWrapper::GetRemoteType(nsACString& aRemoteType) {
+ aRemoteType = NOT_REMOTE_TYPE;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/ipc/ParentChannelWrapper.h b/netwerk/ipc/ParentChannelWrapper.h
new file mode 100644
index 0000000000..8d7d6dd73d
--- /dev/null
+++ b/netwerk/ipc/ParentChannelWrapper.h
@@ -0,0 +1,39 @@
+/* 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_ParentChannelWrapper_h
+#define mozilla_net_ParentChannelWrapper_h
+
+#include "nsIParentChannel.h"
+#include "nsIStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+class ParentChannelWrapper : public nsIParentChannel {
+ public:
+ ParentChannelWrapper(nsIChannel* aChannel, nsIStreamListener* aListener)
+ : mChannel(aChannel), mListener(aListener) {}
+
+ // Registers this nsIParentChannel wrapper with the RedirectChannelRegistrar
+ // and holds a reference.
+ void Register(uint64_t aRegistrarId);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_FORWARD_NSISTREAMLISTENER(mListener->)
+ NS_FORWARD_NSIREQUESTOBSERVER(mListener->)
+
+ private:
+ virtual ~ParentChannelWrapper() = default;
+ const nsCOMPtr<nsIChannel> mChannel;
+ const nsCOMPtr<nsIStreamListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ParentChannelWrapper_h
diff --git a/netwerk/ipc/ParentProcessDocumentChannel.cpp b/netwerk/ipc/ParentProcessDocumentChannel.cpp
new file mode 100644
index 0000000000..ed9decac72
--- /dev/null
+++ b/netwerk/ipc/ParentProcessDocumentChannel.cpp
@@ -0,0 +1,314 @@
+/* -*- 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 "ParentProcessDocumentChannel.h"
+
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/ParentChannelWrapper.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/StaticPrefs_extensions.h"
+#include "nsCRT.h"
+#include "nsDocShell.h"
+#include "nsIObserverService.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIXULRuntime.h"
+#include "nsHttpHandler.h"
+#include "nsDocShellLoadState.h"
+
+extern mozilla::LazyLogModule gDocumentChannelLog;
+#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+namespace net {
+
+using RedirectToRealChannelPromise =
+ typename PDocumentChannelParent::RedirectToRealChannelPromise;
+
+NS_IMPL_ISUPPORTS_INHERITED(ParentProcessDocumentChannel, DocumentChannel,
+ nsIAsyncVerifyRedirectCallback, nsIObserver)
+
+ParentProcessDocumentChannel::ParentProcessDocumentChannel(
+ nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, bool aUriModified,
+ bool aIsEmbeddingBlockedError)
+ : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey,
+ aUriModified, aIsEmbeddingBlockedError) {
+ LOG(("ParentProcessDocumentChannel ctor [this=%p]", this));
+}
+
+ParentProcessDocumentChannel::~ParentProcessDocumentChannel() {
+ LOG(("ParentProcessDocumentChannel dtor [this=%p]", this));
+}
+
+RefPtr<RedirectToRealChannelPromise>
+ParentProcessDocumentChannel::RedirectToRealChannel(
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ const nsTArray<EarlyHintConnectArgs>& aEarlyHints) {
+ LOG(("ParentProcessDocumentChannel RedirectToRealChannel [this=%p]", this));
+ nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel();
+ channel->SetLoadFlags(aLoadFlags);
+ channel->SetNotificationCallbacks(mCallbacks);
+
+ if (mLoadGroup) {
+ channel->SetLoadGroup(mLoadGroup);
+ }
+
+ if (XRE_IsE10sParentProcess()) {
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_GetFinalChannelURI(channel, getter_AddRefs(uri)));
+ if (!nsDocShell::CanLoadInParentProcess(uri)) {
+ nsAutoCString msg;
+ uri->GetSpec(msg);
+ msg.Insert(
+ "Attempt to load a non-authorised load in the parent process: ", 0);
+ NS_ASSERTION(false, msg.get());
+ return RedirectToRealChannelPromise::CreateAndResolve(
+ NS_ERROR_CONTENT_BLOCKED, __func__);
+ }
+ }
+ mStreamFilterEndpoints = std::move(aStreamFilterEndpoints);
+
+ if (mDocumentLoadListener->IsDocumentLoad() &&
+ mozilla::SessionHistoryInParent() && GetDocShell() &&
+ mDocumentLoadListener->GetLoadingSessionHistoryInfo()) {
+ GetDocShell()->SetLoadingSessionHistoryInfo(
+ *mDocumentLoadListener->GetLoadingSessionHistoryInfo());
+ }
+
+ RefPtr<RedirectToRealChannelPromise> p = mPromise.Ensure(__func__);
+ // We make the promise use direct task dispatch in order to reduce the number
+ // of event loops iterations.
+ mPromise.UseDirectTaskDispatch(__func__);
+
+ nsresult rv =
+ gHttpHandler->AsyncOnChannelRedirect(this, channel, aRedirectFlags);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ParentProcessDocumentChannel RedirectToRealChannel "
+ "AsyncOnChannelRedirect failed [this=%p "
+ "aRv=%d]",
+ this, int(rv)));
+ OnRedirectVerifyCallback(rv);
+ }
+
+ return p;
+}
+
+NS_IMETHODIMP
+ParentProcessDocumentChannel::OnRedirectVerifyCallback(nsresult aResult) {
+ LOG(
+ ("ParentProcessDocumentChannel OnRedirectVerifyCallback [this=%p "
+ "aResult=%d]",
+ this, int(aResult)));
+
+ MOZ_ASSERT(mDocumentLoadListener);
+
+ if (NS_FAILED(aResult)) {
+ Cancel(aResult);
+ } else if (mCanceled) {
+ aResult = NS_ERROR_ABORT;
+ } else {
+ const nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel();
+ mLoadGroup->AddRequest(channel, nullptr);
+ // Adding the channel to the loadgroup could have triggered a status
+ // change with an observer being called destroying the docShell, resulting
+ // in the PPDC to be canceled.
+ if (mCanceled) {
+ aResult = NS_ERROR_ABORT;
+ } else {
+ mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED);
+ for (auto& endpoint : mStreamFilterEndpoints) {
+ extensions::StreamFilterParent::Attach(channel, std::move(endpoint));
+ }
+
+ RefPtr<ParentChannelWrapper> wrapper =
+ new ParentChannelWrapper(channel, mListener);
+
+ wrapper->Register(mDocumentLoadListener->GetRedirectChannelId());
+ }
+ }
+
+ mPromise.Resolve(aResult, __func__);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ParentProcessDocumentChannel::AsyncOpen(
+ nsIStreamListener* aListener) {
+ LOG(("ParentProcessDocumentChannel AsyncOpen [this=%p]", this));
+ auto docShell = RefPtr<nsDocShell>(GetDocShell());
+ MOZ_ASSERT(docShell);
+
+ bool isDocumentLoad = mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_OBJECT;
+
+ mDocumentLoadListener = MakeRefPtr<DocumentLoadListener>(
+ docShell->GetBrowsingContext()->Canonical(), isDocumentLoad);
+ LOG(("Created PPDocumentChannel with listener=%p",
+ mDocumentLoadListener.get()));
+
+ // Add observers.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ MOZ_ALWAYS_SUCCEEDS(observerService->AddObserver(
+ this, NS_HTTP_ON_MODIFY_REQUEST_TOPIC, false));
+ }
+
+ gHttpHandler->OnOpeningDocumentRequest(this);
+
+ if (isDocumentLoad) {
+ // Return value of setting synced field should be checked. See bug 1656492.
+ Unused << GetDocShell()->GetBrowsingContext()->SetCurrentLoadIdentifier(
+ Some(mLoadState->GetLoadIdentifier()));
+ }
+
+ nsresult rv = NS_OK;
+ Maybe<dom::ClientInfo> initialClientInfo = mInitialClientInfo;
+
+ RefPtr<DocumentLoadListener::OpenPromise> promise;
+ if (isDocumentLoad) {
+ promise = mDocumentLoadListener->OpenDocument(
+ mLoadState, mCacheKey, Some(mChannelId), TimeStamp::Now(), mTiming,
+ std::move(initialClientInfo), Some(mUriModified),
+ Some(mIsEmbeddingBlockedError), nullptr /* ContentParent */, &rv);
+ } else {
+ promise = mDocumentLoadListener->OpenObject(
+ mLoadState, mCacheKey, Some(mChannelId), TimeStamp::Now(), mTiming,
+ std::move(initialClientInfo), InnerWindowIDForExtantDoc(docShell),
+ mLoadFlags, mLoadInfo->InternalContentPolicyType(),
+ dom::UserActivation::IsHandlingUserInput(), nullptr /* ContentParent */,
+ nullptr /* ObjectUpgradeHandler */, &rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(!promise);
+ mDocumentLoadListener = nullptr;
+ RemoveObserver();
+ return rv;
+ }
+
+ mListener = aListener;
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ RefPtr<ParentProcessDocumentChannel> self = this;
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) {
+ self->mDocumentLoadListener->CancelEarlyHintPreloads();
+ nsTArray<EarlyHintConnectArgs> earlyHints;
+
+ // The DLL is waiting for us to resolve the
+ // RedirectToRealChannelPromise given as parameter.
+ RefPtr<RedirectToRealChannelPromise> p =
+ self->RedirectToRealChannel(
+ std::move(aResolveValue.mStreamFilterEndpoints),
+ aResolveValue.mRedirectFlags, aResolveValue.mLoadFlags,
+ earlyHints)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](RedirectToRealChannelPromise::ResolveOrRejectValue&&
+ aValue) -> RefPtr<RedirectToRealChannelPromise> {
+ MOZ_ASSERT(aValue.IsResolve());
+ nsresult rv = aValue.ResolveValue();
+ if (NS_FAILED(rv)) {
+ self->DisconnectChildListeners(rv, rv);
+ }
+ self->mLoadGroup = nullptr;
+ self->mListener = nullptr;
+ self->mCallbacks = nullptr;
+ self->RemoveObserver();
+ auto p =
+ MakeRefPtr<RedirectToRealChannelPromise::Private>(
+ __func__);
+ p->UseDirectTaskDispatch(__func__);
+ p->ResolveOrReject(std::move(aValue), __func__);
+ return p;
+ });
+ // We chain the promise the DLL is waiting on to the one returned by
+ // RedirectToRealChannel. As soon as the promise returned is
+ // resolved or rejected, so will the DLL's promise.
+ p->ChainTo(aResolveValue.mPromise.forget(), __func__);
+ },
+ [self](DocumentLoadListener::OpenPromiseFailedType&& aRejectValue) {
+ // If this is a normal failure, then we want to disconnect our listeners
+ // and notify them of the failure. If this is a process switch, then we
+ // can just ignore it silently, and trust that the switch will shut down
+ // our docshell and cancel us when it's ready.
+ if (!aRejectValue.mContinueNavigating) {
+ self->DisconnectChildListeners(aRejectValue.mStatus,
+ aRejectValue.mLoadGroupStatus);
+ }
+ self->RemoveObserver();
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP ParentProcessDocumentChannel::Cancel(nsresult aStatus) {
+ return CancelWithReason(aStatus, "ParentProcessDocumentChannel::Cancel"_ns);
+}
+
+NS_IMETHODIMP ParentProcessDocumentChannel::CancelWithReason(
+ nsresult aStatusCode, const nsACString& aReason) {
+ LOG(("ParentProcessDocumentChannel CancelWithReason [this=%p]", this));
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ // This will force the DocumentListener to abort the promise if there's one
+ // pending.
+ mDocumentLoadListener->Cancel(aStatusCode, aReason);
+
+ return NS_OK;
+}
+
+void ParentProcessDocumentChannel::RemoveObserver() {
+ if (nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService()) {
+ observerService->RemoveObserver(this, NS_HTTP_ON_MODIFY_REQUEST_TOPIC);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIObserver
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+ParentProcessDocumentChannel::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mRequestObserversCalled) {
+ // We have already emitted the event, we don't want to emit it again.
+ // We only care about forwarding the first NS_HTTP_ON_MODIFY_REQUEST_TOPIC
+ // encountered.
+ return NS_OK;
+ }
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aSubject);
+ if (!channel || mDocumentLoadListener->GetChannel() != channel) {
+ // Not a channel we are interested with.
+ return NS_OK;
+ }
+ LOG(("DocumentChannelParent Observe [this=%p aChannel=%p]", this,
+ channel.get()));
+ if (!nsCRT::strcmp(aTopic, NS_HTTP_ON_MODIFY_REQUEST_TOPIC)) {
+ mRequestObserversCalled = true;
+ gHttpHandler->OnModifyDocumentRequest(this);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/ipc/ParentProcessDocumentChannel.h b/netwerk/ipc/ParentProcessDocumentChannel.h
new file mode 100644
index 0000000000..f9b4ad616d
--- /dev/null
+++ b/netwerk/ipc/ParentProcessDocumentChannel.h
@@ -0,0 +1,62 @@
+/* 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_ParentProcessDocumentChannel_h
+#define mozilla_net_ParentProcessDocumentChannel_h
+
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "mozilla/net/DocumentLoadListener.h"
+#include "nsIObserver.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+
+namespace mozilla {
+namespace net {
+
+class EarlyHintConnectArgs;
+
+class ParentProcessDocumentChannel : public DocumentChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIObserver {
+ public:
+ ParentProcessDocumentChannel(nsDocShellLoadState* aLoadState,
+ class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey,
+ bool aUriModified,
+ bool aIsEmbeddingBlockedError);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSIOBSERVER
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ NS_IMETHOD Cancel(nsresult aStatusCode) override;
+ NS_IMETHOD CancelWithReason(nsresult aStatusCode,
+ const nsACString& aReason) override;
+
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ RedirectToRealChannel(
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ const nsTArray<EarlyHintConnectArgs>& aEarlyHints);
+
+ private:
+ virtual ~ParentProcessDocumentChannel();
+ void RemoveObserver();
+
+ RefPtr<DocumentLoadListener> mDocumentLoadListener;
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>
+ mStreamFilterEndpoints;
+ MozPromiseHolder<PDocumentChannelParent::RedirectToRealChannelPromise>
+ mPromise;
+ bool mRequestObserversCalled = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ParentProcessDocumentChannel_h
diff --git a/netwerk/ipc/ProxyAutoConfigChild.cpp b/netwerk/ipc/ProxyAutoConfigChild.cpp
new file mode 100644
index 0000000000..1ab78c65e3
--- /dev/null
+++ b/netwerk/ipc/ProxyAutoConfigChild.cpp
@@ -0,0 +1,225 @@
+/* -*- 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 "ProxyAutoConfigChild.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "ProxyAutoConfig.h"
+
+namespace mozilla::net {
+
+static bool sThreadLocalSetup = false;
+static uint32_t sThreadLocalIndex = 0xdeadbeef;
+StaticRefPtr<nsIThread> ProxyAutoConfigChild::sPACThread;
+bool ProxyAutoConfigChild::sShutdownObserverRegistered = false;
+static StaticRefPtr<ProxyAutoConfigChild> sActor;
+
+namespace {
+
+class ShutdownObserver final : public nsIObserver {
+ public:
+ ShutdownObserver() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ ~ShutdownObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ ProxyAutoConfigChild::ShutdownPACThread();
+ return NS_OK;
+}
+
+} // namespace
+
+// static
+void ProxyAutoConfigChild::BindProxyAutoConfigChild(
+ RefPtr<ProxyAutoConfigChild>&& aActor,
+ Endpoint<PProxyAutoConfigChild>&& aEndpoint) {
+ // We only allow one ProxyAutoConfigChild at a time, so we need to
+ // wait until the old one to be destroyed.
+ if (sActor) {
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "BindProxyAutoConfigChild",
+ [actor = std::move(aActor), endpoint = std::move(aEndpoint)]() mutable {
+ ProxyAutoConfigChild::BindProxyAutoConfigChild(std::move(actor),
+ std::move(endpoint));
+ }));
+ return;
+ }
+
+ if (aEndpoint.Bind(aActor)) {
+ sActor = aActor;
+ }
+}
+
+// static
+bool ProxyAutoConfigChild::Create(Endpoint<PProxyAutoConfigChild>&& aEndpoint) {
+ if (!sPACThread && !CreatePACThread()) {
+ NS_WARNING("Failed to create pac thread!");
+ return false;
+ }
+
+ if (!sShutdownObserverRegistered) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return false;
+ }
+ nsCOMPtr<nsIObserver> observer = new ShutdownObserver();
+ nsresult rv = obs->AddObserver(observer, "xpcom-shutdown-threads", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ sShutdownObserverRegistered = true;
+ }
+
+ RefPtr<ProxyAutoConfigChild> actor = new ProxyAutoConfigChild();
+ if (NS_FAILED(sPACThread->Dispatch(NS_NewRunnableFunction(
+ "ProxyAutoConfigChild::ProxyAutoConfigChild",
+ [actor = std::move(actor),
+ endpoint = std::move(aEndpoint)]() mutable {
+ MOZ_ASSERT(endpoint.IsValid());
+ ProxyAutoConfigChild::BindProxyAutoConfigChild(std::move(actor),
+ std::move(endpoint));
+ })))) {
+ NS_WARNING("Failed to dispatch runnable!");
+ return false;
+ }
+
+ return true;
+}
+
+// static
+bool ProxyAutoConfigChild::CreatePACThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (SocketProcessChild::GetSingleton()->IsShuttingDown()) {
+ NS_WARNING("Trying to create pac thread after shutdown has already begun!");
+ return false;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ if (NS_FAILED(NS_NewNamedThread("ProxyResolution", getter_AddRefs(thread)))) {
+ NS_WARNING("NS_NewNamedThread failed!");
+ return false;
+ }
+
+ sPACThread = thread.forget();
+ return true;
+}
+
+// static
+void ProxyAutoConfigChild::ShutdownPACThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sPACThread) {
+ // Wait until all actos are released.
+ SpinEventLoopUntil("ProxyAutoConfigChild::ShutdownPACThread"_ns,
+ [&]() { return !sActor; });
+
+ nsCOMPtr<nsIThread> thread = sPACThread.get();
+ sPACThread = nullptr;
+ MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
+ }
+}
+
+ProxyAutoConfigChild::ProxyAutoConfigChild()
+ : mPAC(MakeUnique<ProxyAutoConfig>()) {
+ if (!sThreadLocalSetup) {
+ sThreadLocalSetup = true;
+ PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
+ }
+
+ mPAC->SetThreadLocalIndex(sThreadLocalIndex);
+}
+
+ProxyAutoConfigChild::~ProxyAutoConfigChild() = default;
+
+mozilla::ipc::IPCResult ProxyAutoConfigChild::RecvConfigurePAC(
+ const nsACString& aPACURI, const nsACString& aPACScriptData,
+ const bool& aIncludePath, const uint32_t& aExtraHeapSize) {
+ mPAC->ConfigurePAC(aPACURI, aPACScriptData, aIncludePath, aExtraHeapSize,
+ GetMainThreadSerialEventTarget());
+ mPACLoaded = true;
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("ProxyAutoConfigChild::ProcessPendingQ", this,
+ &ProxyAutoConfigChild::ProcessPendingQ));
+ return IPC_OK();
+}
+
+void ProxyAutoConfigChild::PendingQuery::Resolve(nsresult aStatus,
+ const nsACString& aResult) {
+ mResolver(std::tuple<const nsresult&, const nsACString&>(aStatus, aResult));
+}
+
+mozilla::ipc::IPCResult ProxyAutoConfigChild::RecvGetProxyForURI(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ GetProxyForURIResolver&& aResolver) {
+ mPendingQ.insertBack(
+ new PendingQuery(aTestURI, aTestHost, std::move(aResolver)));
+ ProcessPendingQ();
+ return IPC_OK();
+}
+
+void ProxyAutoConfigChild::ProcessPendingQ() {
+ while (ProcessPending()) {
+ ;
+ }
+
+ if (mShutdown) {
+ mPAC->Shutdown();
+ } else {
+ // do GC while the thread has nothing pending
+ mPAC->GC();
+ }
+}
+
+bool ProxyAutoConfigChild::ProcessPending() {
+ if (mPendingQ.isEmpty()) {
+ return false;
+ }
+
+ if (mInProgress || !mPACLoaded) {
+ return false;
+ }
+
+ if (mShutdown) {
+ return true;
+ }
+
+ mInProgress = true;
+ RefPtr<PendingQuery> query = mPendingQ.popFirst();
+ nsCString result;
+ nsresult rv = mPAC->GetProxyForURI(query->URI(), query->Host(), result);
+ query->Resolve(rv, result);
+ mInProgress = false;
+ return true;
+}
+
+void ProxyAutoConfigChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mPendingQ.clear();
+ mShutdown = true;
+ mPAC->Shutdown();
+
+ // To avoid racing with the main thread, we need to dispatch
+ // ProxyAutoConfigChild::Destroy again.
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(NewNonOwningRunnableMethod(
+ "ProxyAutoConfigChild::Destroy", this, &ProxyAutoConfigChild::Destroy)));
+}
+
+void ProxyAutoConfigChild::Destroy() { sActor = nullptr; }
+
+} // namespace mozilla::net
diff --git a/netwerk/ipc/ProxyAutoConfigChild.h b/netwerk/ipc/ProxyAutoConfigChild.h
new file mode 100644
index 0000000000..19d757d646
--- /dev/null
+++ b/netwerk/ipc/ProxyAutoConfigChild.h
@@ -0,0 +1,84 @@
+/* -*- 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 ProxyAutoConfigChild_h__
+#define ProxyAutoConfigChild_h__
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/net/PProxyAutoConfigChild.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace net {
+
+class ProxyAutoConfig;
+
+class ProxyAutoConfigChild final : public PProxyAutoConfigChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProxyAutoConfigChild, final)
+
+ static bool Create(Endpoint<PProxyAutoConfigChild>&& aEndpoint);
+ static bool CreatePACThread();
+ static void ShutdownPACThread();
+
+ ProxyAutoConfigChild();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ mozilla::ipc::IPCResult RecvConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData,
+ const bool& aIncludePath,
+ const uint32_t& aExtraHeapSize);
+ mozilla::ipc::IPCResult RecvGetProxyForURI(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ GetProxyForURIResolver&& aResolver);
+
+ void Destroy();
+
+ private:
+ virtual ~ProxyAutoConfigChild();
+ void ProcessPendingQ();
+ bool ProcessPending();
+ static void BindProxyAutoConfigChild(
+ RefPtr<ProxyAutoConfigChild>&& aActor,
+ Endpoint<PProxyAutoConfigChild>&& aEndpoint);
+
+ UniquePtr<ProxyAutoConfig> mPAC;
+ bool mInProgress{false};
+ bool mPACLoaded{false};
+ bool mShutdown{false};
+
+ class PendingQuery final : public LinkedListElement<RefPtr<PendingQuery>> {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PendingQuery)
+
+ explicit PendingQuery(const nsACString& aTestURI,
+ const nsACString& aTestHost,
+ GetProxyForURIResolver&& aResolver)
+ : mURI(aTestURI), mHost(aTestHost), mResolver(std::move(aResolver)) {}
+
+ void Resolve(nsresult aStatus, const nsACString& aResult);
+ const nsCString& URI() const { return mURI; }
+ const nsCString& Host() const { return mHost; }
+
+ private:
+ ~PendingQuery() = default;
+
+ nsCString mURI;
+ nsCString mHost;
+ GetProxyForURIResolver mResolver;
+ };
+
+ LinkedList<RefPtr<PendingQuery>> mPendingQ;
+
+ static StaticRefPtr<nsIThread> sPACThread;
+ static bool sShutdownObserverRegistered;
+ static Atomic<uint32_t> sLiveActorCount;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ProxyAutoConfigChild_h__
diff --git a/netwerk/ipc/ProxyAutoConfigParent.cpp b/netwerk/ipc/ProxyAutoConfigParent.cpp
new file mode 100644
index 0000000000..d0a8727ef1
--- /dev/null
+++ b/netwerk/ipc/ProxyAutoConfigParent.cpp
@@ -0,0 +1,21 @@
+/* -*- 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 "ProxyAutoConfigParent.h"
+
+#include "mozilla/ipc/Endpoint.h"
+
+namespace mozilla::net {
+
+ProxyAutoConfigParent::ProxyAutoConfigParent() = default;
+
+ProxyAutoConfigParent::~ProxyAutoConfigParent() = default;
+
+void ProxyAutoConfigParent::Init(Endpoint<PProxyAutoConfigParent>&& aEndpoint) {
+ DebugOnly<bool> ok = aEndpoint.Bind(this);
+ MOZ_ASSERT(ok);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/ipc/ProxyAutoConfigParent.h b/netwerk/ipc/ProxyAutoConfigParent.h
new file mode 100644
index 0000000000..f65a0714f2
--- /dev/null
+++ b/netwerk/ipc/ProxyAutoConfigParent.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ProxyAutoConfigParent_h__
+#define ProxyAutoConfigParent_h__
+
+#include "mozilla/net/PProxyAutoConfigParent.h"
+
+namespace mozilla {
+namespace net {
+
+class ProxyAutoConfigParent final : public PProxyAutoConfigParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProxyAutoConfigParent, final)
+
+ ProxyAutoConfigParent();
+ void Init(Endpoint<PProxyAutoConfigParent>&& aEndpoint);
+
+ private:
+ virtual ~ProxyAutoConfigParent();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ProxyAutoConfigParent_h__
diff --git a/netwerk/ipc/ProxyConfigLookup.cpp b/netwerk/ipc/ProxyConfigLookup.cpp
new file mode 100644
index 0000000000..29ce74091d
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookup.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "ProxyConfigLookup.h"
+#include "ProxyConfigLookupChild.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsICancelable.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+// static
+nsresult ProxyConfigLookup::Create(
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI,
+ uint32_t aProxyResolveFlags, nsICancelable** aLookupCancellable) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<ProxyConfigLookup> lookUp =
+ new ProxyConfigLookup(std::move(aCallback), aURI, aProxyResolveFlags);
+ return lookUp->DoProxyResolve(aLookupCancellable);
+}
+
+ProxyConfigLookup::ProxyConfigLookup(
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI,
+ uint32_t aProxyResolveFlags)
+ : mCallback(std::move(aCallback)),
+ mURI(aURI),
+ mProxyResolveFlags(aProxyResolveFlags) {}
+
+ProxyConfigLookup::~ProxyConfigLookup() = default;
+
+nsresult ProxyConfigLookup::DoProxyResolve(nsICancelable** aLookupCancellable) {
+ if (!XRE_IsParentProcess()) {
+ RefPtr<ProxyConfigLookup> self = this;
+ bool result = ProxyConfigLookupChild::Create(
+ mURI, mProxyResolveFlags,
+ [self](nsIProxyInfo* aProxyinfo, nsresult aResult) {
+ self->OnProxyAvailable(nullptr, nullptr, aProxyinfo, aResult);
+ });
+ return result ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), mURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // using the nsIProtocolProxyService2 allows a minor performance
+ // optimization, but if an add-on has only provided the original interface
+ // then it is ok to use that version.
+ nsCOMPtr<nsICancelable> proxyRequest;
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ rv = pps2->AsyncResolve2(channel, mProxyResolveFlags, this, nullptr,
+ getter_AddRefs(proxyRequest));
+ } else {
+ rv = pps->AsyncResolve(channel, mProxyResolveFlags, this, nullptr,
+ getter_AddRefs(proxyRequest));
+ }
+
+ if (aLookupCancellable) {
+ proxyRequest.forget(aLookupCancellable);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP ProxyConfigLookup::OnProxyAvailable(nsICancelable* aRequest,
+ nsIChannel* aChannel,
+ nsIProxyInfo* aProxyinfo,
+ nsresult aResult) {
+ mCallback(aProxyinfo, aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(ProxyConfigLookup, nsIProtocolProxyCallback)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/ProxyConfigLookup.h b/netwerk/ipc/ProxyConfigLookup.h
new file mode 100644
index 0000000000..edef151964
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookup.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ProxyConfigLookup_h
+#define mozilla_net_ProxyConfigLookup_h
+
+#include <functional>
+#include "nsIProtocolProxyCallback.h"
+#include "nsCOMPtr.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class ProxyConfigLookup final : public nsIProtocolProxyCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ static nsresult Create(
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI,
+ uint32_t aProxyResolveFlags,
+ nsICancelable** aLookupCancellable = nullptr);
+
+ private:
+ explicit ProxyConfigLookup(
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI,
+ uint32_t aProxyResolveFlags);
+ virtual ~ProxyConfigLookup();
+ nsresult DoProxyResolve(nsICancelable** aLookupCancellable);
+
+ std::function<void(nsIProxyInfo*, nsresult)> mCallback;
+ nsCOMPtr<nsIURI> mURI;
+ uint32_t mProxyResolveFlags;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ProxyConfigLookup_h
diff --git a/netwerk/ipc/ProxyConfigLookupChild.cpp b/netwerk/ipc/ProxyConfigLookupChild.cpp
new file mode 100644
index 0000000000..913b8460c2
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookupChild.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyConfigLookupChild.h"
+
+#include "mozilla/net/SocketProcessChild.h"
+#include "nsProxyInfo.h"
+
+namespace mozilla {
+namespace net {
+
+// static
+bool ProxyConfigLookupChild::Create(
+ nsIURI* aURI, uint32_t aProxyResolveFlags,
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback) {
+ SocketProcessChild* socketChild = SocketProcessChild::GetSingleton();
+ if (!socketChild) {
+ return false;
+ }
+
+ RefPtr<ProxyConfigLookupChild> child =
+ new ProxyConfigLookupChild(std::move(aCallback));
+ return socketChild->SendPProxyConfigLookupConstructor(child, aURI,
+ aProxyResolveFlags);
+}
+
+ProxyConfigLookupChild::ProxyConfigLookupChild(
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback)
+ : mCallback(std::move(aCallback)) {}
+
+mozilla::ipc::IPCResult ProxyConfigLookupChild::Recv__delete__(
+ nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const nsresult& aResult) {
+ nsCOMPtr<nsIProxyInfo> proxyInfo =
+ nsProxyInfo::DeserializeProxyInfo(aProxyInfo);
+ mCallback(proxyInfo, aResult);
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/ProxyConfigLookupChild.h b/netwerk/ipc/ProxyConfigLookupChild.h
new file mode 100644
index 0000000000..0c64f2835e
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookupChild.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ProxyConfigLookupChild_h
+#define mozilla_net_ProxyConfigLookupChild_h
+
+#include "mozilla/net/PProxyConfigLookupChild.h"
+#include <functional>
+
+class nsIProxyInfo;
+
+namespace mozilla {
+namespace net {
+
+class ProxyConfigLookupChild final : public PProxyConfigLookupChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(ProxyConfigLookupChild, override)
+
+ static bool Create(nsIURI* aURI, uint32_t aProxyResolveFlags,
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback);
+
+ mozilla::ipc::IPCResult Recv__delete__(
+ nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const nsresult& aResult);
+
+ private:
+ explicit ProxyConfigLookupChild(
+ std::function<void(nsIProxyInfo*, nsresult)>&& aCallback);
+ virtual ~ProxyConfigLookupChild() = default;
+
+ std::function<void(nsIProxyInfo*, nsresult)> mCallback;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ProxyConfigLookupChild_h
diff --git a/netwerk/ipc/ProxyConfigLookupParent.cpp b/netwerk/ipc/ProxyConfigLookupParent.cpp
new file mode 100644
index 0000000000..597f43eef5
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookupParent.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyConfigLookupParent.h"
+#include "ProxyConfigLookup.h"
+#include "mozilla/Unused.h"
+#include "nsProxyInfo.h"
+
+namespace mozilla {
+namespace net {
+
+ProxyConfigLookupParent::ProxyConfigLookupParent(nsIURI* aURI,
+ uint32_t aProxyResolveFlags)
+ : mURI(aURI), mProxyResolveFlags(aProxyResolveFlags) {}
+
+ProxyConfigLookupParent::~ProxyConfigLookupParent() = default;
+
+void ProxyConfigLookupParent::DoProxyLookup() {
+ RefPtr<ProxyConfigLookupParent> self = this;
+ nsresult rv = ProxyConfigLookup::Create(
+ [self](nsIProxyInfo* aProxyInfo, nsresult aStatus) {
+ if (self->CanSend()) {
+ nsTArray<ProxyInfoCloneArgs> proxyInfoArray;
+ if (aProxyInfo && NS_SUCCEEDED(aStatus)) {
+ nsProxyInfo::SerializeProxyInfo(
+ static_cast<nsProxyInfo*>(aProxyInfo), proxyInfoArray);
+ }
+ Unused << Send__delete__(self, proxyInfoArray, aStatus);
+ }
+ },
+ mURI, mProxyResolveFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsTArray<ProxyInfoCloneArgs> emptyArray;
+ Unused << Send__delete__(self, emptyArray, rv);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/ProxyConfigLookupParent.h b/netwerk/ipc/ProxyConfigLookupParent.h
new file mode 100644
index 0000000000..6ce23866c2
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookupParent.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ProxyConfigLookupParent_h
+#define mozilla_net_ProxyConfigLookupParent_h
+
+#include "mozilla/net/PProxyConfigLookupParent.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class ProxyConfigLookupParent final : public PProxyConfigLookupParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(ProxyConfigLookupParent, override)
+
+ explicit ProxyConfigLookupParent(nsIURI* aURI, uint32_t aProxyResolveFlags);
+
+ void DoProxyLookup();
+
+ private:
+ virtual ~ProxyConfigLookupParent();
+
+ nsCOMPtr<nsIURI> mURI;
+ uint32_t mProxyResolveFlags;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ProxyConfigLookupParent_h
diff --git a/netwerk/ipc/SocketProcessBackgroundChild.cpp b/netwerk/ipc/SocketProcessBackgroundChild.cpp
new file mode 100644
index 0000000000..d3835ad0f5
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBackgroundChild.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "SocketProcessBackgroundChild.h"
+#include "SocketProcessLogging.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::net {
+
+StaticMutex SocketProcessBackgroundChild::sMutex;
+StaticRefPtr<SocketProcessBackgroundChild>
+ SocketProcessBackgroundChild::sInstance;
+StaticRefPtr<nsISerialEventTarget> SocketProcessBackgroundChild::sTaskQueue;
+
+// static
+RefPtr<SocketProcessBackgroundChild>
+SocketProcessBackgroundChild::GetSingleton() {
+ StaticMutexAutoLock lock(sMutex);
+ return sInstance;
+}
+
+// static
+void SocketProcessBackgroundChild::Create(
+ ipc::Endpoint<PSocketProcessBackgroundChild>&& aEndpoint) {
+ if (NS_WARN_IF(!aEndpoint.IsValid())) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Can't create SocketProcessBackgroundChild with invalid endpoint");
+ return;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_WARN_IF(NS_FAILED(NS_CreateBackgroundTaskQueue(
+ "SocketBackgroundChildQueue", getter_AddRefs(transportQueue))))) {
+ return;
+ }
+
+ RefPtr<SocketProcessBackgroundChild> actor =
+ new SocketProcessBackgroundChild();
+
+ transportQueue->Dispatch(NS_NewRunnableFunction(
+ "BindSocketBackgroundChild",
+ [endpoint = std::move(aEndpoint), actor]() mutable {
+ // We checked endpoint validity before the dispatch, so this cannot
+ // fail.
+ MOZ_ALWAYS_TRUE(endpoint.Bind(actor));
+ }));
+
+ // Immediately store the actor and queue into the global.
+ // Any messages dispatched to the queue will arrive after it has been bound.
+ LOG(("SocketProcessBackgroundChild::Create"));
+ StaticMutexAutoLock lock(sMutex);
+ MOZ_ASSERT(!sInstance && !sTaskQueue,
+ "Cannot initialize SocketProcessBackgroundChild twice!");
+ sInstance = actor;
+ sTaskQueue = transportQueue;
+}
+
+// static
+void SocketProcessBackgroundChild::Shutdown() {
+ nsCOMPtr<nsISerialEventTarget> taskQueue = TaskQueue();
+ if (!taskQueue) {
+ return;
+ }
+
+ taskQueue->Dispatch(
+ NS_NewRunnableFunction("SocketProcessBackgroundChild::Shutdown", []() {
+ LOG(("SocketProcessBackgroundChild::Shutdown"));
+ StaticMutexAutoLock lock(sMutex);
+ sInstance->Close();
+ sInstance = nullptr;
+ sTaskQueue = nullptr;
+ }));
+}
+
+// static
+already_AddRefed<nsISerialEventTarget>
+SocketProcessBackgroundChild::TaskQueue() {
+ StaticMutexAutoLock lock(sMutex);
+ return do_AddRef(sTaskQueue);
+}
+
+// static
+nsresult SocketProcessBackgroundChild::WithActor(
+ const char* aName,
+ MoveOnlyFunction<void(SocketProcessBackgroundChild*)> aCallback) {
+ nsCOMPtr<nsISerialEventTarget> taskQueue = TaskQueue();
+ if (!taskQueue) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return taskQueue->Dispatch(NS_NewRunnableFunction(
+ aName, [callback = std::move(aCallback)]() mutable {
+ RefPtr<SocketProcessBackgroundChild> actor =
+ SocketProcessBackgroundChild::GetSingleton();
+ if (actor) {
+ callback(actor);
+ }
+ }));
+}
+
+SocketProcessBackgroundChild::SocketProcessBackgroundChild() {
+ LOG(("SocketProcessBackgroundChild ctor"));
+}
+
+SocketProcessBackgroundChild::~SocketProcessBackgroundChild() {
+ LOG(("SocketProcessBackgroundChild dtor"));
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/ipc/SocketProcessBackgroundChild.h b/netwerk/ipc/SocketProcessBackgroundChild.h
new file mode 100644
index 0000000000..b83c8dbcc1
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBackgroundChild.h
@@ -0,0 +1,45 @@
+/* -*- 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_SocketProcessBackgroundChild_h
+#define mozilla_net_SocketProcessBackgroundChild_h
+
+#include "mozilla/MoveOnlyFunction.h"
+#include "mozilla/net/PSocketProcessBackgroundChild.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class SocketProcessBackgroundChild final
+ : public PSocketProcessBackgroundChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessBackgroundChild, final)
+
+ static void Create(ipc::Endpoint<PSocketProcessBackgroundChild>&& aEndpoint);
+ static void Shutdown();
+ // |aCallback| will be called with the actor asynchronously on |sTaskQueue|.
+ static nsresult WithActor(
+ const char* aName,
+ MoveOnlyFunction<void(SocketProcessBackgroundChild*)> aCallback);
+
+ private:
+ SocketProcessBackgroundChild();
+ virtual ~SocketProcessBackgroundChild();
+
+ static RefPtr<SocketProcessBackgroundChild> GetSingleton();
+ static already_AddRefed<nsISerialEventTarget> TaskQueue();
+
+ static StaticMutex sMutex;
+ static StaticRefPtr<SocketProcessBackgroundChild> sInstance
+ MOZ_GUARDED_BY(sMutex);
+ static StaticRefPtr<nsISerialEventTarget> sTaskQueue MOZ_GUARDED_BY(sMutex);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessBackgroundChild_h
diff --git a/netwerk/ipc/SocketProcessBackgroundParent.cpp b/netwerk/ipc/SocketProcessBackgroundParent.cpp
new file mode 100644
index 0000000000..c17e1467b6
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBackgroundParent.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SocketProcessBackgroundParent.h"
+#include "SocketProcessLogging.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/HttpConnectionMgrParent.h"
+#include "mozilla/net/WebSocketConnectionParent.h"
+#include "mozilla/psm/IPCClientCertsParent.h"
+#include "mozilla/psm/VerifySSLServerCertParent.h"
+#include "mozilla/psm/SelectTLSClientAuthCertParent.h"
+#include "nsIHttpChannelInternal.h"
+
+namespace mozilla::net {
+
+SocketProcessBackgroundParent::SocketProcessBackgroundParent() {
+ LOG(("SocketProcessBackgroundParent ctor"));
+}
+
+SocketProcessBackgroundParent::~SocketProcessBackgroundParent() {
+ LOG(("SocketProcessBackgroundParent dtor"));
+}
+
+mozilla::ipc::IPCResult
+SocketProcessBackgroundParent::RecvInitVerifySSLServerCert(
+ Endpoint<PVerifySSLServerCertParent>&& aEndpoint,
+ nsTArray<ByteArray>&& aPeerCertChain, const nsACString& aHostName,
+ const int32_t& aPort, const OriginAttributes& aOriginAttributes,
+ const Maybe<ByteArray>& aStapledOCSPResponse,
+ const Maybe<ByteArray>& aSctsFromTLSExtension,
+ const Maybe<DelegatedCredentialInfoArg>& aDcInfo,
+ const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags) {
+ LOG(("SocketProcessBackgroundParent::RecvInitVerifySSLServerCert\n"));
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Invalid endpoint");
+ }
+
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_FAILED(NS_CreateBackgroundTaskQueue("VerifySSLServerCert",
+ getter_AddRefs(transportQueue)))) {
+ return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed");
+ }
+
+ transportQueue->Dispatch(NS_NewRunnableFunction(
+ "InitVerifySSLServerCert",
+ [endpoint = std::move(aEndpoint),
+ peerCertChain = std::move(aPeerCertChain),
+ hostName = nsCString(aHostName), port(aPort),
+ originAttributes(aOriginAttributes),
+ stapledOCSPResponse = std::move(aStapledOCSPResponse),
+ sctsFromTLSExtension = std::move(aSctsFromTLSExtension),
+ dcInfo = std::move(aDcInfo), providerFlags(aProviderFlags),
+ certVerifierFlags(aCertVerifierFlags)]() mutable {
+ RefPtr<psm::VerifySSLServerCertParent> parent =
+ new psm::VerifySSLServerCertParent();
+ if (!endpoint.Bind(parent)) {
+ return;
+ }
+
+ parent->Dispatch(std::move(peerCertChain), hostName, port,
+ originAttributes, stapledOCSPResponse,
+ sctsFromTLSExtension, dcInfo, providerFlags,
+ certVerifierFlags);
+ }));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessBackgroundParent::RecvInitSelectTLSClientAuthCert(
+ Endpoint<PSelectTLSClientAuthCertParent>&& aEndpoint,
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ const int32_t& aPort, const uint32_t& aProviderFlags,
+ const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
+ nsTArray<ByteArray>&& aCANames, const uint64_t& aBrowserId) {
+ LOG(("SocketProcessBackgroundParent::RecvInitSelectTLSClientAuthCert\n"));
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Invalid endpoint");
+ }
+
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_FAILED(NS_CreateBackgroundTaskQueue("SelectTLSClientAuthCert",
+ getter_AddRefs(transportQueue)))) {
+ return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed");
+ }
+
+ transportQueue->Dispatch(NS_NewRunnableFunction(
+ "InitSelectTLSClientAuthCert",
+ [endpoint = std::move(aEndpoint), hostName = nsCString(aHostName),
+ originAttributes(aOriginAttributes), port(aPort),
+ providerFlags(aProviderFlags), providerTlsFlags(aProviderTlsFlags),
+ serverCertBytes(aServerCertBytes), CANAMEs(std::move(aCANames)),
+ browserId(aBrowserId)]() mutable {
+ RefPtr<psm::SelectTLSClientAuthCertParent> parent =
+ new psm::SelectTLSClientAuthCertParent();
+ if (!endpoint.Bind(parent)) {
+ return;
+ }
+
+ parent->Dispatch(hostName, originAttributes, port, providerFlags,
+ providerTlsFlags, serverCertBytes, std::move(CANAMEs),
+ browserId);
+ }));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessBackgroundParent::RecvInitIPCClientCerts(
+ Endpoint<PIPCClientCertsParent>&& aEndpoint) {
+ LOG(("SocketProcessBackgroundParent::RecvInitIPCClientCerts\n"));
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Invalid endpoint");
+ }
+
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_FAILED(NS_CreateBackgroundTaskQueue("IPCClientCerts",
+ getter_AddRefs(transportQueue)))) {
+ return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed");
+ }
+
+ transportQueue->Dispatch(NS_NewRunnableFunction(
+ "InitIPCClientCerts", [endpoint = std::move(aEndpoint)]() mutable {
+ RefPtr<psm::IPCClientCertsParent> parent =
+ new psm::IPCClientCertsParent();
+ endpoint.Bind(parent);
+ }));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessBackgroundParent::RecvInitWebSocketConnection(
+ Endpoint<PWebSocketConnectionParent>&& aEndpoint,
+ const uint32_t& aListenerId) {
+ LOG(("SocketProcessBackgroundParent::RecvInitWebSocketConnection\n"));
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Invalid endpoint");
+ }
+
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_FAILED(NS_CreateBackgroundTaskQueue("WebSocketConnection",
+ getter_AddRefs(transportQueue)))) {
+ return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed");
+ }
+
+ transportQueue->Dispatch(NS_NewRunnableFunction(
+ "InitWebSocketConnection",
+ [endpoint = std::move(aEndpoint), aListenerId]() mutable {
+ Maybe<nsCOMPtr<nsIHttpUpgradeListener>> listener =
+ net::HttpConnectionMgrParent::GetAndRemoveHttpUpgradeListener(
+ aListenerId);
+ if (!listener) {
+ return;
+ }
+
+ RefPtr<WebSocketConnectionParent> actor =
+ new WebSocketConnectionParent(*listener);
+ endpoint.Bind(actor);
+ }));
+ return IPC_OK();
+}
+
+void SocketProcessBackgroundParent::ActorDestroy(ActorDestroyReason aReason) {
+ LOG(("SocketProcessBackgroundParent::ActorDestroy"));
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/ipc/SocketProcessBackgroundParent.h b/netwerk/ipc/SocketProcessBackgroundParent.h
new file mode 100644
index 0000000000..0714f8dd6e
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBackgroundParent.h
@@ -0,0 +1,53 @@
+/* -*- 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_SocketProcessBackgroundParent_h
+#define mozilla_net_SocketProcessBackgroundParent_h
+
+#include "mozilla/net/PSocketProcessBackgroundParent.h"
+
+namespace mozilla {
+namespace net {
+
+class SocketProcessBackgroundParent final
+ : public PSocketProcessBackgroundParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessBackgroundParent, final)
+
+ SocketProcessBackgroundParent();
+
+ mozilla::ipc::IPCResult RecvInitVerifySSLServerCert(
+ Endpoint<PVerifySSLServerCertParent>&& aEndpoint,
+ nsTArray<ByteArray>&& aPeerCertChain, const nsACString& aHostName,
+ const int32_t& aPort, const OriginAttributes& aOriginAttributes,
+ const Maybe<ByteArray>& aStapledOCSPResponse,
+ const Maybe<ByteArray>& aSctsFromTLSExtension,
+ const Maybe<DelegatedCredentialInfoArg>& aDcInfo,
+ const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags);
+
+ mozilla::ipc::IPCResult RecvInitIPCClientCerts(
+ Endpoint<PIPCClientCertsParent>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvInitSelectTLSClientAuthCert(
+ Endpoint<PSelectTLSClientAuthCertParent>&& aEndpoint,
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ const int32_t& aPort, const uint32_t& aProviderFlags,
+ const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
+ nsTArray<ByteArray>&& aCANames, const uint64_t& aBrowserId);
+
+ mozilla::ipc::IPCResult RecvInitWebSocketConnection(
+ Endpoint<PWebSocketConnectionParent>&& aEndpoint,
+ const uint32_t& aListenerId);
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ private:
+ ~SocketProcessBackgroundParent();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessBackgroundParent_h
diff --git a/netwerk/ipc/SocketProcessBridgeChild.cpp b/netwerk/ipc/SocketProcessBridgeChild.cpp
new file mode 100644
index 0000000000..798ce37bf1
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBridgeChild.cpp
@@ -0,0 +1,186 @@
+/* -*- 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 "SocketProcessBridgeChild.h"
+#include "SocketProcessLogging.h"
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla {
+
+using dom::ContentChild;
+
+namespace net {
+
+StaticRefPtr<SocketProcessBridgeChild>
+ SocketProcessBridgeChild::sSocketProcessBridgeChild;
+
+NS_IMPL_ISUPPORTS(SocketProcessBridgeChild, nsIObserver)
+
+// static
+bool SocketProcessBridgeChild::Create(
+ Endpoint<PSocketProcessBridgeChild>&& aEndpoint) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sSocketProcessBridgeChild = new SocketProcessBridgeChild();
+
+ if (!aEndpoint.Bind(sSocketProcessBridgeChild)) {
+ MOZ_ASSERT(false, "Bind failed!");
+ sSocketProcessBridgeChild = nullptr;
+ return false;
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(sSocketProcessBridgeChild, "content-child-shutdown", false);
+ }
+
+ sSocketProcessBridgeChild->mSocketProcessPid = aEndpoint.OtherPid();
+ return true;
+}
+
+// static
+already_AddRefed<SocketProcessBridgeChild>
+SocketProcessBridgeChild::GetSingleton() {
+ RefPtr<SocketProcessBridgeChild> child = sSocketProcessBridgeChild;
+ return child.forget();
+}
+
+// static
+RefPtr<SocketProcessBridgeChild::GetPromise>
+SocketProcessBridgeChild::GetSocketProcessBridge() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StaticPrefs::network_process_enabled()) {
+ return GetPromise::CreateAndReject(nsCString("Socket process disabled!"),
+ __func__);
+ }
+
+ if (!gNeckoChild) {
+ return GetPromise::CreateAndReject(nsCString("No NeckoChild!"), __func__);
+ }
+
+ // ContentChild is shutting down, we should not try to create
+ // SocketProcessBridgeChild.
+ ContentChild* content = ContentChild::GetSingleton();
+ if (!content || content->IsShuttingDown()) {
+ return GetPromise::CreateAndReject(
+ nsCString("ContentChild is shutting down."), __func__);
+ }
+
+ if (sSocketProcessBridgeChild) {
+ return GetPromise::CreateAndResolve(sSocketProcessBridgeChild, __func__);
+ }
+
+ return gNeckoChild->SendInitSocketProcessBridge()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](NeckoChild::InitSocketProcessBridgePromise::ResolveOrRejectValue&&
+ aResult) {
+ ContentChild* content = ContentChild::GetSingleton();
+ if (!content || content->IsShuttingDown()) {
+ return GetPromise::CreateAndReject(
+ nsCString("ContentChild is shutting down."), __func__);
+ }
+ if (!sSocketProcessBridgeChild) {
+ if (aResult.IsReject()) {
+ return GetPromise::CreateAndReject(
+ nsCString("SendInitSocketProcessBridge failed"), __func__);
+ }
+
+ if (!aResult.ResolveValue().IsValid()) {
+ return GetPromise::CreateAndReject(
+ nsCString(
+ "SendInitSocketProcessBridge resolved with an invalid "
+ "endpoint!"),
+ __func__);
+ }
+
+ if (!SocketProcessBridgeChild::Create(
+ std::move(aResult.ResolveValue()))) {
+ return GetPromise::CreateAndReject(
+ nsCString("SendInitSocketProcessBridge resolved with a valid "
+ "endpoint, "
+ "but SocketProcessBridgeChild::Create failed!"),
+ __func__);
+ }
+ }
+
+ return GetPromise::CreateAndResolve(sSocketProcessBridgeChild,
+ __func__);
+ });
+}
+
+SocketProcessBridgeChild::SocketProcessBridgeChild() : mShuttingDown(false) {
+ LOG(("CONSTRUCT SocketProcessBridgeChild::SocketProcessBridgeChild\n"));
+}
+
+SocketProcessBridgeChild::~SocketProcessBridgeChild() {
+ LOG(("DESTRUCT SocketProcessBridgeChild::SocketProcessBridgeChild\n"));
+}
+
+mozilla::ipc::IPCResult SocketProcessBridgeChild::RecvTest() {
+ LOG(("SocketProcessBridgeChild::RecvTest\n"));
+ return IPC_OK();
+}
+
+void SocketProcessBridgeChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("SocketProcessBridgeChild::ActorDestroy\n"));
+ if (AbnormalShutdown == aWhy) {
+ if (gNeckoChild &&
+ !AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ // Let NeckoParent know that the socket process connections must be
+ // rebuilt.
+ gNeckoChild->SendResetSocketProcessBridge();
+ }
+
+ nsresult res;
+ nsCOMPtr<nsISerialEventTarget> mSTSThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ if (NS_SUCCEEDED(res) && mSTSThread) {
+ // This must be called off the main thread. If we don't make this call
+ // ipc::BackgroundChild::GetOrCreateSocketActorForCurrentThread() will
+ // return the previous actor that is no longer able to send. This causes
+ // rebuilding the socket process connections to fail.
+ MOZ_ALWAYS_SUCCEEDS(mSTSThread->Dispatch(NS_NewRunnableFunction(
+ "net::SocketProcessBridgeChild::ActorDestroy",
+ []() { ipc::BackgroundChild::CloseForCurrentThread(); })));
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, "content-child-shutdown");
+ }
+ GetCurrentSerialEventTarget()->Dispatch(
+ NewRunnableMethod("net::SocketProcessBridgeChild::DeferredDestroy", this,
+ &SocketProcessBridgeChild::DeferredDestroy));
+ mShuttingDown = true;
+}
+
+NS_IMETHODIMP
+SocketProcessBridgeChild::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "content-child-shutdown")) {
+ PSocketProcessBridgeChild::Close();
+ }
+ return NS_OK;
+}
+
+void SocketProcessBridgeChild::DeferredDestroy() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sSocketProcessBridgeChild = nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessBridgeChild.h b/netwerk/ipc/SocketProcessBridgeChild.h
new file mode 100644
index 0000000000..8b1c1ad6cf
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBridgeChild.h
@@ -0,0 +1,50 @@
+/* -*- 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_SocketProcessBridgeChild_h
+#define mozilla_net_SocketProcessBridgeChild_h
+
+#include <functional>
+#include "mozilla/net/PSocketProcessBridgeChild.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+namespace net {
+
+// The IPC actor implements PSocketProcessBridgeChild in content process.
+// This is allocated and kept alive by NeckoChild. When "content-child-shutdown"
+// topic is observed, this actor will be destroyed.
+class SocketProcessBridgeChild final : public PSocketProcessBridgeChild,
+ public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static already_AddRefed<SocketProcessBridgeChild> GetSingleton();
+ using GetPromise =
+ MozPromise<RefPtr<SocketProcessBridgeChild>, nsCString, false>;
+ static RefPtr<GetPromise> GetSocketProcessBridge();
+
+ mozilla::ipc::IPCResult RecvTest();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void DeferredDestroy();
+ bool IsShuttingDown() const { return mShuttingDown; };
+ ProcessId SocketProcessPid() const { return mSocketProcessPid; };
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SocketProcessBridgeChild);
+ static bool Create(Endpoint<PSocketProcessBridgeChild>&& aEndpoint);
+ explicit SocketProcessBridgeChild();
+ virtual ~SocketProcessBridgeChild();
+
+ static StaticRefPtr<SocketProcessBridgeChild> sSocketProcessBridgeChild;
+ bool mShuttingDown;
+ ProcessId mSocketProcessPid;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessBridgeChild_h
diff --git a/netwerk/ipc/SocketProcessBridgeParent.cpp b/netwerk/ipc/SocketProcessBridgeParent.cpp
new file mode 100644
index 0000000000..b16be05b4d
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBridgeParent.cpp
@@ -0,0 +1,97 @@
+/* -*- 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 "SocketProcessBridgeParent.h"
+#include "SocketProcessLogging.h"
+
+#ifdef MOZ_WEBRTC
+# include "mozilla/dom/MediaTransportParent.h"
+#endif
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "SocketProcessChild.h"
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+SocketProcessBridgeParent::SocketProcessBridgeParent(ProcessId aId) : mId(aId) {
+ LOG(
+ ("CONSTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent "
+ "mId=%" PRIPID "\n",
+ mId));
+}
+
+SocketProcessBridgeParent::~SocketProcessBridgeParent() {
+ LOG(("DESTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent\n"));
+}
+
+mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvInitBackgroundDataBridge(
+ mozilla::ipc::Endpoint<PBackgroundDataBridgeParent>&& aEndpoint,
+ uint64_t aChannelID) {
+ LOG(("SocketProcessBridgeParent::RecvInitBackgroundDataBridge\n"));
+
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_FAILED(NS_CreateBackgroundTaskQueue("BackgroundDataBridge",
+ getter_AddRefs(transportQueue)))) {
+ return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed");
+ }
+
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Invalid endpoint");
+ }
+
+ transportQueue->Dispatch(NS_NewRunnableFunction(
+ "BackgroundDataBridgeParent::Bind",
+ [endpoint = std::move(aEndpoint), aChannelID]() mutable {
+ RefPtr<net::BackgroundDataBridgeParent> actor =
+ new net::BackgroundDataBridgeParent(aChannelID);
+ endpoint.Bind(actor);
+ }));
+ return IPC_OK();
+}
+
+#ifdef MOZ_WEBRTC
+mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvInitMediaTransport(
+ mozilla::ipc::Endpoint<mozilla::dom::PMediaTransportParent>&& aEndpoint) {
+ LOG(("SocketProcessBridgeParent::RecvInitMediaTransport\n"));
+
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Invalid endpoint");
+ }
+
+ if (!mMediaTransportTaskQueue) {
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_FAILED(NS_CreateBackgroundTaskQueue(
+ "MediaTransport", getter_AddRefs(transportQueue)))) {
+ return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed");
+ }
+
+ mMediaTransportTaskQueue = std::move(transportQueue);
+ }
+
+ mMediaTransportTaskQueue->Dispatch(NS_NewRunnableFunction(
+ "BackgroundDataBridgeParent::Bind",
+ [endpoint = std::move(aEndpoint)]() mutable {
+ RefPtr<MediaTransportParent> actor = new MediaTransportParent();
+ endpoint.Bind(actor);
+ }));
+ return IPC_OK();
+}
+#endif
+
+void SocketProcessBridgeParent::ActorDestroy(ActorDestroyReason aReason) {
+ // See bug 1846478. We might be able to remove this dispatch.
+ GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "SocketProcessBridgeParent::ActorDestroy", [id = mId] {
+ if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) {
+ child->DestroySocketProcessBridgeParent(id);
+ }
+ }));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessBridgeParent.h b/netwerk/ipc/SocketProcessBridgeParent.h
new file mode 100644
index 0000000000..e9b6053665
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBridgeParent.h
@@ -0,0 +1,44 @@
+/* -*- 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_SocketProcessBridgeParent_h
+#define mozilla_net_SocketProcessBridgeParent_h
+
+#include "mozilla/net/PSocketProcessBridgeParent.h"
+
+namespace mozilla {
+namespace net {
+
+// The IPC actor implements PSocketProcessBridgeParent in socket process.
+// This is allocated and kept alive by SocketProcessChild. When |ActorDestroy|
+// is called, |SocketProcessChild::DestroySocketProcessBridgeParent| will be
+// called to destroy this actor.
+class SocketProcessBridgeParent final : public PSocketProcessBridgeParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessBridgeParent, final)
+
+ explicit SocketProcessBridgeParent(ProcessId aId);
+
+ mozilla::ipc::IPCResult RecvInitBackgroundDataBridge(
+ Endpoint<PBackgroundDataBridgeParent>&& aEndpoint, uint64_t aChannelID);
+
+#ifdef MOZ_WEBRTC
+ mozilla::ipc::IPCResult RecvInitMediaTransport(
+ Endpoint<PMediaTransportParent>&& aEndpoint);
+#endif
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ private:
+ ~SocketProcessBridgeParent();
+
+ nsCOMPtr<nsISerialEventTarget> mMediaTransportTaskQueue;
+ ProcessId mId;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessBridgeParent_h
diff --git a/netwerk/ipc/SocketProcessChild.cpp b/netwerk/ipc/SocketProcessChild.cpp
new file mode 100644
index 0000000000..000ebde2fd
--- /dev/null
+++ b/netwerk/ipc/SocketProcessChild.cpp
@@ -0,0 +1,833 @@
+/* -*- 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 "SocketProcessChild.h"
+#include "SocketProcessLogging.h"
+
+#include "base/task.h"
+#include "InputChannelThrottleQueueChild.h"
+#include "HttpInfo.h"
+#include "HttpTransactionChild.h"
+#include "HttpConnectionMgrChild.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/MemoryReportRequest.h"
+#include "mozilla/FOGIPC.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/ipc/CrashReporterClient.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/net/AltSvcTransactionChild.h"
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "mozilla/net/DNSRequestChild.h"
+#include "mozilla/net/DNSRequestParent.h"
+#include "mozilla/net/NativeDNSResolverOverrideChild.h"
+#include "mozilla/net/ProxyAutoConfigChild.h"
+#include "mozilla/net/SocketProcessBackgroundChild.h"
+#include "mozilla/net/TRRServiceChild.h"
+#include "mozilla/ipc/ProcessUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "NetworkConnectivityService.h"
+#include "nsDebugImpl.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "nsIDNSService.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsNSSComponent.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadManager.h"
+#include "SocketProcessBridgeParent.h"
+#include "jsapi.h"
+#include "js/Initialization.h"
+#include "js/Prefs.h"
+#include "XPCSelfHostedShmem.h"
+
+#if defined(XP_WIN)
+# include <process.h>
+
+# include "mozilla/WinDllServices.h"
+#else
+# include <unistd.h>
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+#endif
+
+#include "ChildProfilerController.h"
+
+#ifdef MOZ_WEBRTC
+# include "mozilla/net/WebrtcTCPSocketChild.h"
+#endif
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+# include "mozilla/SandboxTestingChild.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+using namespace ipc;
+
+static bool sInitializedJS = false;
+
+static Atomic<SocketProcessChild*> sSocketProcessChild;
+
+SocketProcessChild::SocketProcessChild() {
+ LOG(("CONSTRUCT SocketProcessChild::SocketProcessChild\n"));
+ nsDebugImpl::SetMultiprocessMode("Socket");
+
+ MOZ_COUNT_CTOR(SocketProcessChild);
+ sSocketProcessChild = this;
+}
+
+SocketProcessChild::~SocketProcessChild() {
+ LOG(("DESTRUCT SocketProcessChild::SocketProcessChild\n"));
+ MOZ_COUNT_DTOR(SocketProcessChild);
+ sSocketProcessChild = nullptr;
+}
+
+/* static */
+SocketProcessChild* SocketProcessChild::GetSingleton() {
+ return sSocketProcessChild;
+}
+
+#if defined(XP_MACOSX)
+extern "C" {
+void CGSShutdownServerConnections();
+};
+#endif
+
+void SocketProcessChild::InitSocketBackground() {
+ Endpoint<PSocketProcessBackgroundParent> parentEndpoint;
+ Endpoint<PSocketProcessBackgroundChild> childEndpoint;
+ if (NS_WARN_IF(NS_FAILED(PSocketProcessBackground::CreateEndpoints(
+ &parentEndpoint, &childEndpoint)))) {
+ return;
+ }
+
+ SocketProcessBackgroundChild::Create(std::move(childEndpoint));
+
+ Unused << SendInitSocketBackground(std::move(parentEndpoint));
+}
+
+namespace {
+
+class NetTeardownObserver final : public nsIObserver {
+ public:
+ NetTeardownObserver() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ ~NetTeardownObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(NetTeardownObserver, nsIObserver)
+
+NS_IMETHODIMP
+NetTeardownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) {
+ child->CloseIPCClientCertsActor();
+ }
+
+ return NS_OK;
+}
+
+} // namespace
+
+bool SocketProcessChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint,
+ const char* aParentBuildID) {
+ if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
+ return false;
+ }
+ if (NS_WARN_IF(!aEndpoint.Bind(this))) {
+ return false;
+ }
+ // This must be sent before any IPDL message, which may hit sentinel
+ // errors due to parent and content processes having different
+ // versions.
+ MessageChannel* channel = GetIPCChannel();
+ if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) {
+ // We need to quit this process if the buildID doesn't match the parent's.
+ // This can occur when an update occurred in the background.
+ ProcessChild::QuickExit();
+ }
+
+ // Init crash reporter support.
+ CrashReporterClient::InitSingleton(this);
+
+ if (NS_FAILED(NS_InitMinimalXPCOM())) {
+ return false;
+ }
+
+ InitSocketBackground();
+
+ SetThisProcessName("Socket Process");
+#if defined(XP_MACOSX)
+ // Close all current connections to the WindowServer. This ensures that the
+ // Activity Monitor will not label the socket process as "Not responding"
+ // because it's not running a native event loop. See bug 1384336.
+ CGSShutdownServerConnections();
+#endif // XP_MACOSX
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // Initialize DNS Service here, since it needs to be done in main thread.
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (!EnsureNSSInitializedChromeOrContent()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ nsCOMPtr<nsIObserver> observer = new NetTeardownObserver();
+ Unused << obs->AddObserver(observer, "profile-change-net-teardown", false);
+ }
+
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (!mSocketThread) {
+ return false;
+ }
+
+ return true;
+}
+
+void SocketProcessChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("SocketProcessChild::ActorDestroy\n"));
+
+ {
+ MutexAutoLock lock(mMutex);
+ mShuttingDown = true;
+ }
+
+ if (AbnormalShutdown == aWhy) {
+ NS_WARNING("Shutting down Socket process early due to a crash!");
+ ProcessChild::QuickExit();
+ }
+
+ // Send the last bits of Glean data over to the main process.
+ glean::FlushFOGData(
+ [](ByteBuf&& aBuf) { glean::SendFOGData(std::move(aBuf)); });
+
+ if (mProfilerController) {
+ mProfilerController->Shutdown();
+ mProfilerController = nullptr;
+ }
+
+ CrashReporterClient::DestroySingleton();
+ XRE_ShutdownChildProcess();
+}
+
+void SocketProcessChild::CleanUp() {
+ LOG(("SocketProcessChild::CleanUp\n"));
+
+ SocketProcessBackgroundChild::Shutdown();
+
+ for (const auto& parent : mSocketProcessBridgeParentMap.Values()) {
+ if (parent->CanSend()) {
+ parent->Close();
+ }
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ mBackgroundDataBridgeMap.Clear();
+ }
+
+ // Normally, the IPC channel should be already closed at this point, but
+ // sometimes it's not (bug 1788860). When the channel is closed, calling
+ // Close() again is harmless.
+ Close();
+
+ NS_ShutdownXPCOM(nullptr);
+
+ if (sInitializedJS) {
+ JS_ShutDown();
+ }
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvInit(
+ const SocketPorcessInitAttributes& aAttributes) {
+ Unused << RecvSetOffline(aAttributes.mOffline());
+ Unused << RecvSetConnectivity(aAttributes.mConnectivity());
+ if (aAttributes.mInitSandbox()) {
+ Unused << RecvInitLinuxSandbox(aAttributes.mSandboxBroker());
+ }
+
+#if defined(XP_WIN)
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->StartUntrustedModulesProcessor(
+ aAttributes.mIsReadyForBackgroundProcessing());
+#endif // defined(XP_WIN)
+
+ return IPC_OK();
+}
+
+IPCResult SocketProcessChild::RecvPreferenceUpdate(const Pref& aPref) {
+ Preferences::SetPreference(aPref);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& aDMDFile,
+ const RequestMemoryReportResolver& aResolver) {
+ nsPrintfCString processName("Socket (pid %u)", (unsigned)getpid());
+
+ mozilla::dom::MemoryReportRequestClient::Start(
+ aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, processName,
+ [&](const MemoryReport& aReport) {
+ Unused << GetSingleton()->SendAddMemoryReport(aReport);
+ },
+ aResolver);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvSetOffline(
+ const bool& aOffline) {
+ LOG(("SocketProcessChild::RecvSetOffline aOffline=%d\n", aOffline));
+
+ nsCOMPtr<nsIIOService> io(do_GetIOService());
+ NS_ASSERTION(io, "IO Service can not be null");
+
+ io->SetOffline(aOffline);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvSetConnectivity(
+ const bool& aConnectivity) {
+ nsCOMPtr<nsIIOService> io(do_GetIOService());
+ nsCOMPtr<nsIIOServiceInternal> ioInternal(do_QueryInterface(io));
+ NS_ASSERTION(ioInternal, "IO Service can not be null");
+
+ ioInternal->SetConnectivity(aConnectivity);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvInitLinuxSandbox(
+ const Maybe<ipc::FileDescriptor>& aBrokerFd) {
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ int fd = -1;
+ if (aBrokerFd.isSome()) {
+ fd = aBrokerFd.value().ClonePlatformHandle().release();
+ }
+ SetSocketProcessSandbox(fd);
+#endif // XP_LINUX && MOZ_SANDBOX
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvInitSocketProcessBridgeParent(
+ const ProcessId& aContentProcessId,
+ Endpoint<mozilla::net::PSocketProcessBridgeParent>&& aEndpoint) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mSocketProcessBridgeParentMap.Contains(aContentProcessId));
+
+ if (NS_WARN_IF(!aEndpoint.IsValid())) {
+ return IPC_FAIL(this, "invalid endpoint");
+ }
+
+ auto bridge = MakeRefPtr<SocketProcessBridgeParent>(aContentProcessId);
+ MOZ_ALWAYS_TRUE(aEndpoint.Bind(bridge));
+
+ mSocketProcessBridgeParentMap.InsertOrUpdate(aContentProcessId,
+ std::move(bridge));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvInitProfiler(
+ Endpoint<PProfilerChild>&& aEndpoint) {
+ mProfilerController =
+ mozilla::ChildProfilerController::Create(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+mozilla::ipc::IPCResult SocketProcessChild::RecvInitSandboxTesting(
+ Endpoint<PSandboxTestingChild>&& aEndpoint) {
+ if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) {
+ return IPC_FAIL(
+ this, "InitSandboxTesting failed to initialise the child process.");
+ }
+ return IPC_OK();
+}
+#endif
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvSocketProcessTelemetryPing() {
+ const uint32_t kExpectedUintValue = 42;
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_SOCKET_ONLY_UINT,
+ kExpectedUintValue);
+ return IPC_OK();
+}
+
+void SocketProcessChild::DestroySocketProcessBridgeParent(ProcessId aId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSocketProcessBridgeParentMap.Remove(aId);
+}
+
+PWebrtcTCPSocketChild* SocketProcessChild::AllocPWebrtcTCPSocketChild(
+ const Maybe<TabId>& tabId) {
+ // We don't allocate here: instead we always use IPDL constructor that takes
+ // an existing object
+ MOZ_ASSERT_UNREACHABLE(
+ "AllocPWebrtcTCPSocketChild should not be called on"
+ " socket child");
+ return nullptr;
+}
+
+bool SocketProcessChild::DeallocPWebrtcTCPSocketChild(
+ PWebrtcTCPSocketChild* aActor) {
+#ifdef MOZ_WEBRTC
+ WebrtcTCPSocketChild* child = static_cast<WebrtcTCPSocketChild*>(aActor);
+ child->ReleaseIPDLReference();
+#endif
+ return true;
+}
+
+already_AddRefed<PHttpTransactionChild>
+SocketProcessChild::AllocPHttpTransactionChild() {
+ RefPtr<HttpTransactionChild> actor = new HttpTransactionChild();
+ return actor.forget();
+}
+
+already_AddRefed<PHttpConnectionMgrChild>
+SocketProcessChild::AllocPHttpConnectionMgrChild(
+ const HttpHandlerInitArgs& aArgs) {
+ LOG(("SocketProcessChild::AllocPHttpConnectionMgrChild \n"));
+ MOZ_ASSERT(gHttpHandler);
+ gHttpHandler->SetHttpHandlerInitArgs(aArgs);
+
+ RefPtr<HttpConnectionMgrChild> actor = new HttpConnectionMgrChild();
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvUpdateDeviceModelId(
+ const nsACString& aModelId) {
+ MOZ_ASSERT(gHttpHandler);
+ gHttpHandler->SetDeviceModelId(aModelId);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessChild::RecvOnHttpActivityDistributorActivated(
+ const bool& aIsActivated) {
+ if (nsCOMPtr<nsIHttpActivityObserver> distributor =
+ components::HttpActivityDistributor::Service()) {
+ distributor->SetIsActive(aIsActivated);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessChild::RecvOnHttpActivityDistributorObserveProxyResponse(
+ const bool& aIsEnabled) {
+ nsCOMPtr<nsIHttpActivityDistributor> distributor =
+ do_GetService("@mozilla.org/network/http-activity-distributor;1");
+ if (distributor) {
+ Unused << distributor->SetObserveProxyResponse(aIsEnabled);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessChild::RecvOnHttpActivityDistributorObserveConnection(
+ const bool& aIsEnabled) {
+ nsCOMPtr<nsIHttpActivityDistributor> distributor =
+ do_GetService("@mozilla.org/network/http-activity-distributor;1");
+ if (distributor) {
+ Unused << distributor->SetObserveConnection(aIsEnabled);
+ }
+ return IPC_OK();
+}
+
+already_AddRefed<PInputChannelThrottleQueueChild>
+SocketProcessChild::AllocPInputChannelThrottleQueueChild(
+ const uint32_t& aMeanBytesPerSecond, const uint32_t& aMaxBytesPerSecond) {
+ RefPtr<InputChannelThrottleQueueChild> p =
+ new InputChannelThrottleQueueChild();
+ p->Init(aMeanBytesPerSecond, aMaxBytesPerSecond);
+ return p.forget();
+}
+
+already_AddRefed<PAltSvcTransactionChild>
+SocketProcessChild::AllocPAltSvcTransactionChild(
+ const HttpConnectionInfoCloneArgs& aConnInfo, const uint32_t& aCaps) {
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aConnInfo);
+ RefPtr<AltSvcTransactionChild> child =
+ new AltSvcTransactionChild(cinfo, aCaps);
+ return child.forget();
+}
+
+already_AddRefed<PDNSRequestChild> SocketProcessChild::AllocPDNSRequestChild(
+ const nsACString& aHost, const nsACString& aTrrServer, const int32_t& aPort,
+ const uint16_t& aType, const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags) {
+ RefPtr<DNSRequestHandler> handler = new DNSRequestHandler();
+ RefPtr<DNSRequestChild> actor = new DNSRequestChild(handler);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvPDNSRequestConstructor(
+ PDNSRequestChild* aActor, const nsACString& aHost,
+ const nsACString& aTrrServer, const int32_t& aPort, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags) {
+ RefPtr<DNSRequestChild> actor = static_cast<DNSRequestChild*>(aActor);
+ RefPtr<DNSRequestHandler> handler =
+ actor->GetDNSRequest()->AsDNSRequestHandler();
+ handler->DoAsyncResolve(aHost, aTrrServer, aPort, aType, aOriginAttributes,
+ aFlags);
+ return IPC_OK();
+}
+
+void SocketProcessChild::AddDataBridgeToMap(
+ uint64_t aChannelId, BackgroundDataBridgeParent* aActor) {
+ MutexAutoLock lock(mMutex);
+ mBackgroundDataBridgeMap.InsertOrUpdate(aChannelId, RefPtr{aActor});
+}
+
+void SocketProcessChild::RemoveDataBridgeFromMap(uint64_t aChannelId) {
+ MutexAutoLock lock(mMutex);
+ mBackgroundDataBridgeMap.Remove(aChannelId);
+}
+
+Maybe<RefPtr<BackgroundDataBridgeParent>>
+SocketProcessChild::GetAndRemoveDataBridge(uint64_t aChannelId) {
+ MutexAutoLock lock(mMutex);
+ return mBackgroundDataBridgeMap.Extract(aChannelId);
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvClearSessionCache(
+ ClearSessionCacheResolver&& aResolve) {
+ nsNSSComponent::DoClearSSLExternalAndInternalSessionCache();
+ aResolve(void_t{});
+ return IPC_OK();
+}
+
+already_AddRefed<PTRRServiceChild> SocketProcessChild::AllocPTRRServiceChild(
+ const bool& aCaptiveIsPassed, const bool& aParentalControlEnabled,
+ const nsTArray<nsCString>& aDNSSuffixList) {
+ RefPtr<TRRServiceChild> actor = new TRRServiceChild();
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvPTRRServiceConstructor(
+ PTRRServiceChild* aActor, const bool& aCaptiveIsPassed,
+ const bool& aParentalControlEnabled, nsTArray<nsCString>&& aDNSSuffixList) {
+ static_cast<TRRServiceChild*>(aActor)->Init(
+ aCaptiveIsPassed, aParentalControlEnabled, std::move(aDNSSuffixList));
+ return IPC_OK();
+}
+
+already_AddRefed<PNativeDNSResolverOverrideChild>
+SocketProcessChild::AllocPNativeDNSResolverOverrideChild() {
+ RefPtr<NativeDNSResolverOverrideChild> actor =
+ new NativeDNSResolverOverrideChild();
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessChild::RecvPNativeDNSResolverOverrideConstructor(
+ PNativeDNSResolverOverrideChild* aActor) {
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvNotifyObserver(
+ const nsACString& aTopic, const nsAString& aData) {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->NotifyObservers(nullptr, PromiseFlatCString(aTopic).get(),
+ PromiseFlatString(aData).get());
+ }
+ return IPC_OK();
+}
+
+namespace {
+
+class DataResolverBase {
+ public:
+ // This type is threadsafe-refcounted, as it's referenced on the socket
+ // thread, but must be destroyed on the main thread.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
+ DataResolverBase)
+
+ DataResolverBase() = default;
+
+ protected:
+ virtual ~DataResolverBase() = default;
+};
+
+template <typename DataType, typename ResolverType>
+class DataResolver final : public DataResolverBase {
+ public:
+ explicit DataResolver(ResolverType&& aResolve)
+ : mResolve(std::move(aResolve)) {}
+
+ void OnResolve(DataType&& aData) {
+ MOZ_ASSERT(OnSocketThread());
+
+ RefPtr<DataResolver<DataType, ResolverType>> self = this;
+ mData = std::move(aData);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::DataResolver::OnResolve",
+ [self{std::move(self)}]() { self->mResolve(std::move(self->mData)); }));
+ }
+
+ private:
+ virtual ~DataResolver() = default;
+
+ ResolverType mResolve;
+ DataType mData;
+};
+
+} // anonymous namespace
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvGetSocketData(
+ GetSocketDataResolver&& aResolve) {
+ if (!gSocketTransportService) {
+ aResolve(SocketDataArgs());
+ return IPC_OK();
+ }
+
+ RefPtr<
+ DataResolver<SocketDataArgs, SocketProcessChild::GetSocketDataResolver>>
+ resolver = new DataResolver<SocketDataArgs,
+ SocketProcessChild::GetSocketDataResolver>(
+ std::move(aResolve));
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "net::SocketProcessChild::RecvGetSocketData",
+ [resolver{std::move(resolver)}]() {
+ SocketDataArgs args;
+ gSocketTransportService->GetSocketConnections(&args.info());
+ args.totalSent() = gSocketTransportService->GetSentBytes();
+ args.totalRecv() = gSocketTransportService->GetReceivedBytes();
+ resolver->OnResolve(std::move(args));
+ }),
+ NS_DISPATCH_NORMAL);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvGetDNSCacheEntries(
+ GetDNSCacheEntriesResolver&& aResolve) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ aResolve(nsTArray<DNSCacheEntries>());
+ return IPC_OK();
+ }
+
+ RefPtr<DataResolver<nsTArray<DNSCacheEntries>,
+ SocketProcessChild::GetDNSCacheEntriesResolver>>
+ resolver =
+ new DataResolver<nsTArray<DNSCacheEntries>,
+ SocketProcessChild::GetDNSCacheEntriesResolver>(
+ std::move(aResolve));
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "net::SocketProcessChild::RecvGetDNSCacheEntries",
+ [resolver{std::move(resolver)}, dns{std::move(dns)}]() {
+ nsTArray<DNSCacheEntries> entries;
+ dns->GetDNSCacheEntries(&entries);
+ resolver->OnResolve(std::move(entries));
+ }),
+ NS_DISPATCH_NORMAL);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvGetHttpConnectionData(
+ GetHttpConnectionDataResolver&& aResolve) {
+ if (!gSocketTransportService) {
+ aResolve(nsTArray<HttpRetParams>());
+ return IPC_OK();
+ }
+
+ RefPtr<DataResolver<nsTArray<HttpRetParams>,
+ SocketProcessChild::GetHttpConnectionDataResolver>>
+ resolver =
+ new DataResolver<nsTArray<HttpRetParams>,
+ SocketProcessChild::GetHttpConnectionDataResolver>(
+ std::move(aResolve));
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "net::SocketProcessChild::RecvGetHttpConnectionData",
+ [resolver{std::move(resolver)}]() {
+ nsTArray<HttpRetParams> data;
+ HttpInfo::GetHttpConnectionData(&data);
+ resolver->OnResolve(std::move(data));
+ }),
+ NS_DISPATCH_NORMAL);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvInitProxyAutoConfigChild(
+ Endpoint<PProxyAutoConfigChild>&& aEndpoint) {
+ // For parsing PAC.
+ if (!sInitializedJS) {
+ JS::DisableJitBackend();
+
+ // Set all JS::Prefs.
+ SET_JS_PREFS_FROM_BROWSER_PREFS;
+
+ const char* jsInitFailureReason = JS_InitWithFailureDiagnostic();
+ if (jsInitFailureReason) {
+ MOZ_CRASH_UNSAFE(jsInitFailureReason);
+ }
+ sInitializedJS = true;
+
+ xpc::SelfHostedShmem::GetSingleton();
+ }
+
+ Unused << ProxyAutoConfigChild::Create(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvRecheckIPConnectivity() {
+ RefPtr<NetworkConnectivityService> ncs =
+ NetworkConnectivityService::GetSingleton();
+ if (ncs) {
+ ncs->RecheckIPConnectivity();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvRecheckDNS() {
+ RefPtr<NetworkConnectivityService> ncs =
+ NetworkConnectivityService::GetSingleton();
+ if (ncs) {
+ ncs->RecheckDNS();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvFlushFOGData(
+ FlushFOGDataResolver&& aResolver) {
+ glean::FlushFOGData(std::move(aResolver));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvTestTriggerMetrics(
+ TestTriggerMetricsResolver&& aResolve) {
+ mozilla::glean::test_only_ipc::a_counter.Add(
+ nsIXULRuntime::PROCESS_TYPE_SOCKET);
+ aResolve(true);
+ return IPC_OK();
+}
+
+#if defined(XP_WIN)
+mozilla::ipc::IPCResult SocketProcessChild::RecvGetUntrustedModulesData(
+ GetUntrustedModulesDataResolver&& aResolver) {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->GetUntrustedModulesData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolver](Maybe<UntrustedModulesData>&& aData) {
+ aResolver(std::move(aData));
+ },
+ [aResolver](nsresult aReason) { aResolver(Nothing()); });
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessChild::RecvUnblockUntrustedModulesThread() {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->NotifyObservers(nullptr, "unblock-untrusted-modules-thread", nullptr);
+ }
+ return IPC_OK();
+}
+#endif // defined(XP_WIN)
+
+bool SocketProcessChild::IsShuttingDown() {
+ MutexAutoLock lock(mMutex);
+ return mShuttingDown;
+}
+
+void SocketProcessChild::CloseIPCClientCertsActor() {
+ LOG(("SocketProcessChild::CloseIPCClientCertsActor"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSocketThread->Dispatch(NS_NewRunnableFunction(
+ "CloseIPCClientCertsActor", [self = RefPtr{this}]() {
+ LOG(("CloseIPCClientCertsActor"));
+ if (self->mIPCClientCertsChild) {
+ self->mIPCClientCertsChild->Close();
+ self->mIPCClientCertsChild = nullptr;
+ }
+ }));
+}
+
+already_AddRefed<psm::IPCClientCertsChild>
+SocketProcessChild::GetIPCClientCertsActor() {
+ LOG(("SocketProcessChild::GetIPCClientCertsActor"));
+ // Only socket thread can access the mIPCClientCertsChild.
+ if (!OnSocketThread()) {
+ return nullptr;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mShuttingDown) {
+ return nullptr;
+ }
+ }
+
+ if (mIPCClientCertsChild) {
+ RefPtr<psm::IPCClientCertsChild> actorChild = mIPCClientCertsChild;
+ return actorChild.forget();
+ }
+
+ ipc::Endpoint<psm::PIPCClientCertsParent> parentEndpoint;
+ ipc::Endpoint<psm::PIPCClientCertsChild> childEndpoint;
+ psm::PIPCClientCerts::CreateEndpoints(&parentEndpoint, &childEndpoint);
+
+ if (NS_FAILED(SocketProcessBackgroundChild::WithActor(
+ "SendInitIPCClientCerts",
+ [endpoint = std::move(parentEndpoint)](
+ SocketProcessBackgroundChild* aActor) mutable {
+ Unused << aActor->SendInitIPCClientCerts(std::move(endpoint));
+ }))) {
+ return nullptr;
+ }
+
+ RefPtr<psm::IPCClientCertsChild> actor = new psm::IPCClientCertsChild();
+ if (!childEndpoint.Bind(actor)) {
+ return nullptr;
+ }
+
+ mIPCClientCertsChild = actor;
+ return actor.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessChild.h b/netwerk/ipc/SocketProcessChild.h
new file mode 100644
index 0000000000..fbcac7462d
--- /dev/null
+++ b/netwerk/ipc/SocketProcessChild.h
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_SocketProcessChild_h
+#define mozilla_net_SocketProcessChild_h
+
+#include "mozilla/net/PSocketProcessChild.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/psm/IPCClientCertsChild.h"
+#include "mozilla/Mutex.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashMap.h"
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+# include "mozilla/PSandboxTestingChild.h"
+#endif
+
+namespace mozilla {
+class ChildProfilerController;
+}
+
+namespace mozilla {
+namespace net {
+
+class ProxyAutoConfigChild;
+class SocketProcessBackgroundChild;
+class SocketProcessBridgeParent;
+class BackgroundDataBridgeParent;
+
+// The IPC actor implements PSocketProcessChild in child process.
+// This is allocated and kept alive by SocketProcessImpl.
+class SocketProcessChild final : public PSocketProcessChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessChild, final)
+
+ SocketProcessChild();
+
+ static SocketProcessChild* GetSingleton();
+
+ bool Init(mozilla::ipc::UntypedEndpoint&& aEndpoint,
+ const char* aParentBuildID);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvInit(
+ const SocketPorcessInitAttributes& aAttributes);
+ mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& aPref);
+ mozilla::ipc::IPCResult RecvRequestMemoryReport(
+ const uint32_t& generation, const bool& anonymize,
+ const bool& minimizeMemoryUsage,
+ const Maybe<mozilla::ipc::FileDescriptor>& DMDFile,
+ const RequestMemoryReportResolver& aResolver);
+ mozilla::ipc::IPCResult RecvSetOffline(const bool& aOffline);
+ mozilla::ipc::IPCResult RecvSetConnectivity(const bool& aConnectivity);
+ mozilla::ipc::IPCResult RecvInitLinuxSandbox(
+ const Maybe<ipc::FileDescriptor>& aBrokerFd);
+ mozilla::ipc::IPCResult RecvInitSocketProcessBridgeParent(
+ const ProcessId& aContentProcessId,
+ Endpoint<mozilla::net::PSocketProcessBridgeParent>&& aEndpoint);
+ mozilla::ipc::IPCResult RecvInitProfiler(
+ Endpoint<mozilla::PProfilerChild>&& aEndpoint);
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ mozilla::ipc::IPCResult RecvInitSandboxTesting(
+ Endpoint<PSandboxTestingChild>&& aEndpoint);
+#endif
+ mozilla::ipc::IPCResult RecvSocketProcessTelemetryPing();
+
+ PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId);
+ bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor);
+
+ already_AddRefed<PHttpTransactionChild> AllocPHttpTransactionChild();
+
+ void CleanUp();
+ void DestroySocketProcessBridgeParent(ProcessId aId);
+
+ already_AddRefed<PHttpConnectionMgrChild> AllocPHttpConnectionMgrChild(
+ const HttpHandlerInitArgs& aArgs);
+ mozilla::ipc::IPCResult RecvUpdateDeviceModelId(const nsACString& aModelId);
+ mozilla::ipc::IPCResult RecvOnHttpActivityDistributorActivated(
+ const bool& aIsActivated);
+ mozilla::ipc::IPCResult RecvOnHttpActivityDistributorObserveProxyResponse(
+ const bool& aIsEnabled);
+ mozilla::ipc::IPCResult RecvOnHttpActivityDistributorObserveConnection(
+ const bool& aIsEnabled);
+
+ already_AddRefed<PInputChannelThrottleQueueChild>
+ AllocPInputChannelThrottleQueueChild(const uint32_t& aMeanBytesPerSecond,
+ const uint32_t& aMaxBytesPerSecond);
+
+ already_AddRefed<PAltSvcTransactionChild> AllocPAltSvcTransactionChild(
+ const HttpConnectionInfoCloneArgs& aConnInfo, const uint32_t& aCaps);
+
+ bool IsShuttingDown();
+
+ already_AddRefed<PDNSRequestChild> AllocPDNSRequestChild(
+ const nsACString& aHost, const nsACString& aTrrServer,
+ const int32_t& aPort, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags);
+ mozilla::ipc::IPCResult RecvPDNSRequestConstructor(
+ PDNSRequestChild* aActor, const nsACString& aHost,
+ const nsACString& aTrrServer, const int32_t& aPort, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags) override;
+
+ void AddDataBridgeToMap(uint64_t aChannelId,
+ BackgroundDataBridgeParent* aActor);
+ void RemoveDataBridgeFromMap(uint64_t aChannelId);
+ Maybe<RefPtr<BackgroundDataBridgeParent>> GetAndRemoveDataBridge(
+ uint64_t aChannelId);
+
+ mozilla::ipc::IPCResult RecvClearSessionCache(
+ ClearSessionCacheResolver&& aResolve);
+
+ already_AddRefed<PTRRServiceChild> AllocPTRRServiceChild(
+ const bool& aCaptiveIsPassed, const bool& aParentalControlEnabled,
+ const nsTArray<nsCString>& aDNSSuffixList);
+ mozilla::ipc::IPCResult RecvPTRRServiceConstructor(
+ PTRRServiceChild* aActor, const bool& aCaptiveIsPassed,
+ const bool& aParentalControlEnabled,
+ nsTArray<nsCString>&& aDNSSuffixList) override;
+
+ already_AddRefed<PNativeDNSResolverOverrideChild>
+ AllocPNativeDNSResolverOverrideChild();
+ mozilla::ipc::IPCResult RecvPNativeDNSResolverOverrideConstructor(
+ PNativeDNSResolverOverrideChild* aActor) override;
+
+ mozilla::ipc::IPCResult RecvNotifyObserver(const nsACString& aTopic,
+ const nsAString& aData);
+
+ mozilla::ipc::IPCResult RecvGetSocketData(GetSocketDataResolver&& aResolve);
+ mozilla::ipc::IPCResult RecvGetDNSCacheEntries(
+ GetDNSCacheEntriesResolver&& aResolve);
+ mozilla::ipc::IPCResult RecvGetHttpConnectionData(
+ GetHttpConnectionDataResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvInitProxyAutoConfigChild(
+ Endpoint<PProxyAutoConfigChild>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvRecheckIPConnectivity();
+ mozilla::ipc::IPCResult RecvRecheckDNS();
+
+ mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver);
+
+ mozilla::ipc::IPCResult RecvTestTriggerMetrics(
+ TestTriggerMetricsResolver&& aResolve);
+
+#if defined(XP_WIN)
+ mozilla::ipc::IPCResult RecvGetUntrustedModulesData(
+ GetUntrustedModulesDataResolver&& aResolver);
+ mozilla::ipc::IPCResult RecvUnblockUntrustedModulesThread();
+#endif // defined(XP_WIN)
+
+ already_AddRefed<psm::IPCClientCertsChild> GetIPCClientCertsActor();
+ void CloseIPCClientCertsActor();
+
+ protected:
+ friend class SocketProcessImpl;
+ ~SocketProcessChild();
+
+ void InitSocketBackground();
+
+ private:
+ // Mapping of content process id and the SocketProcessBridgeParent.
+ // This table keeps SocketProcessBridgeParent alive in socket process.
+ nsRefPtrHashtable<nsUint32HashKey, SocketProcessBridgeParent>
+ mSocketProcessBridgeParentMap;
+
+ RefPtr<ChildProfilerController> mProfilerController;
+
+ // Protect the table below.
+ Mutex mMutex MOZ_UNANNOTATED{"SocketProcessChild::mMutex"};
+ nsTHashMap<uint64_t, RefPtr<BackgroundDataBridgeParent>>
+ mBackgroundDataBridgeMap;
+
+ bool mShuttingDown MOZ_GUARDED_BY(mMutex) = false;
+
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+ // mIPCClientCertsChild is only accessed on the socket thread.
+ RefPtr<psm::IPCClientCertsChild> mIPCClientCertsChild;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessChild_h
diff --git a/netwerk/ipc/SocketProcessHost.cpp b/netwerk/ipc/SocketProcessHost.cpp
new file mode 100644
index 0000000000..5a09f5200c
--- /dev/null
+++ b/netwerk/ipc/SocketProcessHost.cpp
@@ -0,0 +1,292 @@
+/* -*- 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 "SocketProcessHost.h"
+
+#include "SocketProcessParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/ProcessUtils.h"
+#include "nsAppRunner.h"
+#include "nsIOService.h"
+#include "nsIObserverService.h"
+#include "ProfilerParent.h"
+#include "nsNetUtil.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/ProcessChild.h"
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxBroker.h"
+# include "mozilla/SandboxBrokerPolicyFactory.h"
+# include "mozilla/SandboxSettings.h"
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+#endif
+
+#if defined(XP_WIN)
+# include "mozilla/WinDllServices.h"
+#endif
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+bool SocketProcessHost::sLaunchWithMacSandbox = false;
+#endif
+
+SocketProcessHost::SocketProcessHost(Listener* aListener)
+ : GeckoChildProcessHost(GeckoProcessType_Socket),
+ mListener(aListener),
+ mTaskFactory(Some(this)),
+ mLaunchPhase(LaunchPhase::Unlaunched),
+ mShutdownRequested(false),
+ mChannelClosed(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(SocketProcessHost);
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ if (!sLaunchWithMacSandbox) {
+ sLaunchWithMacSandbox =
+ (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS_SANDBOX") == nullptr);
+ }
+ mDisableOSActivityMode = sLaunchWithMacSandbox;
+#endif
+}
+
+SocketProcessHost::~SocketProcessHost() { MOZ_COUNT_DTOR(SocketProcessHost); }
+
+bool SocketProcessHost::Launch() {
+ MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched);
+ MOZ_ASSERT(!mSocketProcessParent);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ std::vector<std::string> extraArgs;
+ ProcessChild::AddPlatformBuildID(extraArgs);
+
+ SharedPreferenceSerializer prefSerializer;
+ if (!prefSerializer.SerializeToSharedMemory(GeckoProcessType_VR,
+ /* remoteType */ ""_ns)) {
+ return false;
+ }
+ prefSerializer.AddSharedPrefCmdLineArgs(*this, extraArgs);
+
+ mLaunchPhase = LaunchPhase::Waiting;
+ if (!GeckoChildProcessHost::LaunchAndWaitForProcessHandle(extraArgs)) {
+ mLaunchPhase = LaunchPhase::Complete;
+ return false;
+ }
+
+ return true;
+}
+
+static void HandleErrorAfterDestroy(
+ RefPtr<SocketProcessHost::Listener>&& aListener) {
+ if (!aListener) {
+ return;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "HandleErrorAfterDestroy", [listener = std::move(aListener)]() {
+ listener->OnProcessLaunchComplete(nullptr, false);
+ }));
+}
+
+void SocketProcessHost::OnChannelConnected(base::ProcessId peer_pid) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ GeckoChildProcessHost::OnChannelConnected(peer_pid);
+
+ // Post a task to the main thread. Take the lock because mTaskFactory is not
+ // thread-safe.
+ RefPtr<Runnable> runnable;
+ {
+ MonitorAutoLock lock(mMonitor);
+ if (!mTaskFactory) {
+ HandleErrorAfterDestroy(std::move(mListener));
+ return;
+ }
+ runnable =
+ (*mTaskFactory)
+ .NewRunnableMethod(&SocketProcessHost::OnChannelConnectedTask);
+ }
+ NS_DispatchToMainThread(runnable);
+}
+
+void SocketProcessHost::OnChannelConnectedTask() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLaunchPhase == LaunchPhase::Waiting) {
+ InitAfterConnect(true);
+ }
+}
+
+void SocketProcessHost::InitAfterConnect(bool aSucceeded) {
+ MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting);
+ MOZ_ASSERT(!mSocketProcessParent);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mLaunchPhase = LaunchPhase::Complete;
+ if (!aSucceeded) {
+ if (mListener) {
+ mListener->OnProcessLaunchComplete(this, false);
+ }
+ return;
+ }
+
+ mSocketProcessParent = MakeRefPtr<SocketProcessParent>(this);
+ DebugOnly<bool> rv = TakeInitialEndpoint().Bind(mSocketProcessParent.get());
+ MOZ_ASSERT(rv);
+
+ SocketPorcessInitAttributes attributes;
+ nsCOMPtr<nsIIOService> ioService(do_GetIOService());
+ MOZ_ASSERT(ioService, "No IO service?");
+ DebugOnly<nsresult> result = ioService->GetOffline(&attributes.mOffline());
+ MOZ_ASSERT(NS_SUCCEEDED(result), "Failed getting offline?");
+ result = ioService->GetConnectivity(&attributes.mConnectivity());
+ MOZ_ASSERT(NS_SUCCEEDED(result), "Failed getting connectivity?");
+
+ attributes.mInitSandbox() = false;
+
+#if defined(XP_WIN)
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ attributes.mIsReadyForBackgroundProcessing() =
+ dllSvc->IsReadyForBackgroundProcessing();
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ if (GetEffectiveSocketProcessSandboxLevel() > 0) {
+ auto policy = SandboxBrokerPolicyFactory::GetSocketProcessPolicy(
+ GetActor()->OtherPid());
+ if (policy != nullptr) {
+ attributes.mSandboxBroker() = Some(FileDescriptor());
+ mSandboxBroker =
+ SandboxBroker::Create(std::move(policy), GetActor()->OtherPid(),
+ attributes.mSandboxBroker().ref());
+ // This is unlikely to fail and probably indicates OS resource
+ // exhaustion.
+ Unused << NS_WARN_IF(mSandboxBroker == nullptr);
+ MOZ_ASSERT(attributes.mSandboxBroker().ref().IsValid());
+ }
+ attributes.mInitSandbox() = true;
+ }
+#endif // XP_LINUX && MOZ_SANDBOX
+
+ Unused << GetActor()->SendInit(attributes);
+
+ Unused << GetActor()->SendInitProfiler(
+ ProfilerParent::CreateForProcess(GetActor()->OtherPid()));
+
+ if (mListener) {
+ mListener->OnProcessLaunchComplete(this, true);
+ }
+}
+
+void SocketProcessHost::Shutdown() {
+ MOZ_ASSERT(!mShutdownRequested);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mListener = nullptr;
+
+ if (mSocketProcessParent) {
+ // OnChannelClosed uses this to check if the shutdown was expected or
+ // unexpected.
+ mShutdownRequested = true;
+
+ // The channel might already be closed if we got here unexpectedly.
+ if (!mChannelClosed) {
+ mSocketProcessParent->Close();
+ }
+
+ return;
+ }
+
+ DestroyProcess();
+}
+
+void SocketProcessHost::OnChannelClosed() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mChannelClosed = true;
+
+ if (!mShutdownRequested && mListener) {
+ // This is an unclean shutdown. Notify our listener that we're going away.
+ mListener->OnProcessUnexpectedShutdown(this);
+ } else {
+ DestroyProcess();
+ }
+
+ // Release the actor.
+ SocketProcessParent::Destroy(std::move(mSocketProcessParent));
+ MOZ_ASSERT(!mSocketProcessParent);
+}
+
+void SocketProcessHost::DestroyProcess() {
+ {
+ MonitorAutoLock lock(mMonitor);
+ mTaskFactory.reset();
+ }
+
+ GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "DestroySocketProcessRunnable", [this] { Destroy(); }));
+}
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+bool SocketProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) {
+ GeckoChildProcessHost::FillMacSandboxInfo(aInfo);
+ if (!aInfo.shouldLog && PR_GetEnv("MOZ_SANDBOX_SOCKET_PROCESS_LOGGING")) {
+ aInfo.shouldLog = true;
+ }
+ return true;
+}
+
+/* static */
+MacSandboxType SocketProcessHost::GetMacSandboxType() {
+ return MacSandboxType_Socket;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// SocketProcessMemoryReporter
+//-----------------------------------------------------------------------------
+
+bool SocketProcessMemoryReporter::IsAlive() const {
+ MOZ_ASSERT(gIOService);
+
+ if (!gIOService->mSocketProcess) {
+ return false;
+ }
+
+ return gIOService->mSocketProcess->IsConnected();
+}
+
+bool SocketProcessMemoryReporter::SendRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& aDMDFile) {
+ MOZ_ASSERT(gIOService);
+
+ if (!gIOService->mSocketProcess) {
+ return false;
+ }
+
+ SocketProcessParent* actor = gIOService->mSocketProcess->GetActor();
+ if (!actor) {
+ return false;
+ }
+
+ return actor->SendRequestMemoryReport(aGeneration, aAnonymize,
+ aMinimizeMemoryUsage, aDMDFile);
+}
+
+int32_t SocketProcessMemoryReporter::Pid() const {
+ MOZ_ASSERT(gIOService);
+ return gIOService->SocketProcessPid();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessHost.h b/netwerk/ipc/SocketProcessHost.h
new file mode 100644
index 0000000000..44648b7fe7
--- /dev/null
+++ b/netwerk/ipc/SocketProcessHost.h
@@ -0,0 +1,150 @@
+/* -*- 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_SocketProcessHost_h
+#define mozilla_net_SocketProcessHost_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/MemoryReportingProcess.h"
+#include "mozilla/ipc/TaskFactory.h"
+
+namespace mozilla {
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+class SandboxBroker;
+#endif
+
+namespace net {
+
+class SocketProcessParent;
+
+// SocketProcessHost is the "parent process" container for a subprocess handle
+// and IPC connection. It owns the parent process IPDL actor, which in this
+// case, is a SocketProcessParent.
+// SocketProcessHost is allocated and managed by nsIOService in parent process.
+class SocketProcessHost final : public mozilla::ipc::GeckoChildProcessHost {
+ friend class SocketProcessParent;
+
+ public:
+ class Listener {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Listener)
+
+ // Called when the process of launching the process is complete.
+ virtual void OnProcessLaunchComplete(SocketProcessHost* aHost,
+ bool aSucceeded) = 0;
+
+ // Called when the channel is closed but Shutdown() is not invoked.
+ virtual void OnProcessUnexpectedShutdown(SocketProcessHost* aHost) = 0;
+
+ protected:
+ virtual ~Listener() = default;
+ };
+
+ explicit SocketProcessHost(Listener* listener);
+
+ // Launch the socket process asynchronously.
+ // The OnProcessLaunchComplete listener callback will be invoked
+ // either when a connection has been established, or if a connection
+ // could not be established due to an asynchronous error.
+ bool Launch();
+
+ // Inform the socket process that it should clean up its resources and shut
+ // down. This initiates an asynchronous shutdown sequence. After this method
+ // returns, it is safe for the caller to forget its pointer to the
+ // SocketProcessHost.
+ void Shutdown();
+
+ // Return the actor for the top-level actor of the process. Return null if
+ // the process is not connected.
+ SocketProcessParent* GetActor() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mSocketProcessParent.get();
+ }
+
+ bool IsConnected() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return !!mSocketProcessParent;
+ }
+
+ // Called on the IO thread.
+ void OnChannelConnected(base::ProcessId peer_pid) override;
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // Return the sandbox type to be used with this process type.
+ static MacSandboxType GetMacSandboxType();
+#endif
+
+ private:
+ ~SocketProcessHost();
+
+ // Called on the main thread.
+ void OnChannelConnectedTask();
+
+ // Called on the main thread after a connection has been established.
+ void InitAfterConnect(bool aSucceeded);
+
+ // Called on the main thread when the mSocketParent actor is shutting down.
+ void OnChannelClosed();
+
+ void DestroyProcess();
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ static bool sLaunchWithMacSandbox;
+
+ // Sandbox the Socket process at launch for all instances
+ bool IsMacSandboxLaunchEnabled() override { return sLaunchWithMacSandbox; }
+
+ // Override so we can turn on Socket process-specific sandbox logging
+ bool FillMacSandboxInfo(MacSandboxInfo& aInfo) override;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(SocketProcessHost);
+
+ RefPtr<Listener> mListener;
+ mozilla::Maybe<mozilla::ipc::TaskFactory<SocketProcessHost>> mTaskFactory;
+
+ enum class LaunchPhase { Unlaunched, Waiting, Complete };
+ LaunchPhase mLaunchPhase;
+
+ RefPtr<SocketProcessParent> mSocketProcessParent;
+ // mShutdownRequested is set to true only when Shutdown() is called.
+ // If mShutdownRequested is false and the IPC channel is closed,
+ // OnProcessUnexpectedShutdown will be invoked.
+ bool mShutdownRequested;
+ bool mChannelClosed;
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ UniquePtr<SandboxBroker> mSandboxBroker;
+#endif
+};
+
+class SocketProcessMemoryReporter : public MemoryReportingProcess {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessMemoryReporter, override)
+
+ SocketProcessMemoryReporter() = default;
+
+ bool IsAlive() const override;
+
+ bool SendRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<mozilla::ipc::FileDescriptor>& aDMDFile) override;
+
+ int32_t Pid() const override;
+
+ protected:
+ virtual ~SocketProcessMemoryReporter() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessHost_h
diff --git a/netwerk/ipc/SocketProcessImpl.cpp b/netwerk/ipc/SocketProcessImpl.cpp
new file mode 100644
index 0000000000..a863d4a875
--- /dev/null
+++ b/netwerk/ipc/SocketProcessImpl.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SocketProcessImpl.h"
+
+#include "base/command_line.h"
+#include "base/shared_memory.h"
+#include "base/string_util.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/GeckoArgs.h"
+#include "mozilla/ipc/ProcessUtils.h"
+#include "mozilla/ipc/IOThreadChild.h"
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+# include "mozilla/sandboxTarget.h"
+#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxSettings.h"
+# include "prlink.h"
+#endif
+
+#ifdef XP_UNIX
+# include <unistd.h> // For sleep().
+#endif
+
+using mozilla::ipc::IOThreadChild;
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gSocketProcessLog("socketprocess");
+
+SocketProcessImpl::~SocketProcessImpl() = default;
+
+bool SocketProcessImpl::Init(int aArgc, char* aArgv[]) {
+#ifdef XP_UNIX
+ if (PR_GetEnv("MOZ_DEBUG_SOCKET_PROCESS")) {
+ printf_stderr("\n\nSOCKETPROCESSnSOCKETPROCESS\n debug me @ %d\n\n",
+ base::GetCurrentProcId());
+ sleep(30);
+ }
+#endif
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+ LoadLibraryW(L"nss3.dll");
+ LoadLibraryW(L"softokn3.dll");
+ LoadLibraryW(L"freebl3.dll");
+ LoadLibraryW(L"ipcclientcerts.dll");
+ LoadLibraryW(L"winmm.dll");
+ mozilla::SandboxTarget::Instance()->StartSandbox();
+#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ PR_LoadLibrary("libnss3.so");
+ PR_LoadLibrary("libsoftokn3.so");
+ PR_LoadLibrary("libfreebl3.so");
+ PR_LoadLibrary("libipcclientcerts.so");
+ StartOpenBSDSandbox(GeckoProcessType_Socket);
+#endif
+
+ Maybe<const char*> parentBuildID =
+ geckoargs::sParentBuildID.Get(aArgc, aArgv);
+ if (parentBuildID.isNothing()) {
+ return false;
+ }
+
+ if (!ProcessChild::InitPrefs(aArgc, aArgv)) {
+ return false;
+ }
+
+ return mSocketProcessChild->Init(TakeInitialEndpoint(), *parentBuildID);
+}
+
+void SocketProcessImpl::CleanUp() { mSocketProcessChild->CleanUp(); }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessImpl.h b/netwerk/ipc/SocketProcessImpl.h
new file mode 100644
index 0000000000..c471ca2834
--- /dev/null
+++ b/netwerk/ipc/SocketProcessImpl.h
@@ -0,0 +1,35 @@
+/* -*- 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_SocketProcessImpl_h
+#define mozilla_net_SocketProcessImpl_h
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "SocketProcessChild.h"
+
+namespace mozilla {
+namespace net {
+
+// This class owns the subprocess instance of socket child process.
+// It is instantiated as a singleton in XRE_InitChildProcess.
+class SocketProcessImpl final : public mozilla::ipc::ProcessChild {
+ protected:
+ using ProcessChild = mozilla::ipc::ProcessChild;
+
+ public:
+ using ProcessChild::ProcessChild;
+ ~SocketProcessImpl();
+
+ bool Init(int aArgc, char* aArgv[]) override;
+ void CleanUp() override;
+
+ private:
+ RefPtr<SocketProcessChild> mSocketProcessChild = new SocketProcessChild;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessImpl_h
diff --git a/netwerk/ipc/SocketProcessLogging.h b/netwerk/ipc/SocketProcessLogging.h
new file mode 100644
index 0000000000..169de4986d
--- /dev/null
+++ b/netwerk/ipc/SocketProcessLogging.h
@@ -0,0 +1,20 @@
+/* -*- 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_SocketProcessLogging_h
+#define mozilla_SocketProcessLogging_h
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace net {
+extern LazyLogModule gSocketProcessLog;
+}
+} // namespace mozilla
+
+#define LOG(msg) MOZ_LOG(gSocketProcessLog, mozilla::LogLevel::Debug, msg)
+#define LOG_ENABLED() MOZ_LOG_TEST(gSocketProcessLog, mozilla::LogLevel::Debug)
+
+#endif // mozilla_SocketProcessLogging_h
diff --git a/netwerk/ipc/SocketProcessParent.cpp b/netwerk/ipc/SocketProcessParent.cpp
new file mode 100644
index 0000000000..6b1ccfb1ad
--- /dev/null
+++ b/netwerk/ipc/SocketProcessParent.cpp
@@ -0,0 +1,364 @@
+/* -*- 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 "SocketProcessParent.h"
+#include "SocketProcessLogging.h"
+
+#include "AltServiceParent.h"
+#include "CachePushChecker.h"
+#include "HttpTransactionParent.h"
+#include "SocketProcessHost.h"
+#include "TLSClientAuthCertSelection.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/MemoryReportRequest.h"
+#include "mozilla/FOGIPC.h"
+#include "mozilla/net/DNSRequestParent.h"
+#include "mozilla/net/ProxyConfigLookupParent.h"
+#include "mozilla/net/SocketProcessBackgroundParent.h"
+#include "mozilla/RemoteLazyInputStreamParent.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryIPC.h"
+#include "nsIConsoleService.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsIObserverService.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsIOService.h"
+#include "nsHttpHandler.h"
+#include "nsHttpConnectionInfo.h"
+#include "secerr.h"
+#ifdef MOZ_WEBRTC
+# include "mozilla/dom/ContentProcessManager.h"
+# include "mozilla/dom/BrowserParent.h"
+# include "mozilla/net/WebrtcTCPSocketParent.h"
+#endif
+#if defined(MOZ_WIDGET_ANDROID)
+# include "mozilla/java/GeckoProcessManagerWrappers.h"
+# include "mozilla/java/GeckoProcessTypeWrappers.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
+#if defined(XP_WIN)
+# include "mozilla/WinDllServices.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+static SocketProcessParent* sSocketProcessParent;
+
+SocketProcessParent::SocketProcessParent(SocketProcessHost* aHost)
+ : mHost(aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mHost);
+
+ MOZ_COUNT_CTOR(SocketProcessParent);
+ sSocketProcessParent = this;
+}
+
+SocketProcessParent::~SocketProcessParent() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_COUNT_DTOR(SocketProcessParent);
+ sSocketProcessParent = nullptr;
+}
+
+/* static */
+SocketProcessParent* SocketProcessParent::GetSingleton() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return sSocketProcessParent;
+}
+
+void SocketProcessParent::ActorDestroy(ActorDestroyReason aWhy) {
+#if defined(MOZ_WIDGET_ANDROID)
+ nsCOMPtr<nsIEventTarget> launcherThread(ipc::GetIPCLauncher());
+ MOZ_ASSERT(launcherThread);
+
+ auto procType = java::GeckoProcessType::SOCKET();
+ auto selector =
+ java::GeckoProcessManager::Selector::New(procType, OtherPid());
+
+ launcherThread->Dispatch(NS_NewRunnableFunction(
+ "SocketProcessParent::ActorDestroy",
+ [selector = java::GeckoProcessManager::Selector::GlobalRef(selector)]() {
+ java::GeckoProcessManager::ShutdownProcess(selector);
+ }));
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+ if (aWhy == AbnormalShutdown) {
+ GenerateCrashReport(OtherPid());
+ MaybeTerminateProcess();
+ }
+
+ if (mHost) {
+ mHost->OnChannelClosed();
+ }
+}
+
+bool SocketProcessParent::SendRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& aDMDFile) {
+ mMemoryReportRequest = MakeUnique<dom::MemoryReportRequestHost>(aGeneration);
+
+ PSocketProcessParent::SendRequestMemoryReport(
+ aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile,
+ [&](const uint32_t& aGeneration2) {
+ MOZ_ASSERT(gIOService);
+ if (!gIOService->SocketProcess()) {
+ return;
+ }
+ SocketProcessParent* actor = gIOService->SocketProcess()->GetActor();
+ if (!actor) {
+ return;
+ }
+ if (actor->mMemoryReportRequest) {
+ actor->mMemoryReportRequest->Finish(aGeneration2);
+ actor->mMemoryReportRequest = nullptr;
+ }
+ },
+ [&](mozilla::ipc::ResponseRejectReason) {
+ MOZ_ASSERT(gIOService);
+ if (!gIOService->SocketProcess()) {
+ return;
+ }
+ SocketProcessParent* actor = gIOService->SocketProcess()->GetActor();
+ if (!actor) {
+ return;
+ }
+ actor->mMemoryReportRequest = nullptr;
+ });
+
+ return true;
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvAddMemoryReport(
+ const MemoryReport& aReport) {
+ if (mMemoryReportRequest) {
+ mMemoryReportRequest->RecvReport(aReport);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvAccumulateChildHistograms(
+ nsTArray<HistogramAccumulation>&& aAccumulations) {
+ TelemetryIPC::AccumulateChildHistograms(Telemetry::ProcessID::Socket,
+ aAccumulations);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvAccumulateChildKeyedHistograms(
+ nsTArray<KeyedHistogramAccumulation>&& aAccumulations) {
+ TelemetryIPC::AccumulateChildKeyedHistograms(Telemetry::ProcessID::Socket,
+ aAccumulations);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvUpdateChildScalars(
+ nsTArray<ScalarAction>&& aScalarActions) {
+ TelemetryIPC::UpdateChildScalars(Telemetry::ProcessID::Socket,
+ aScalarActions);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvUpdateChildKeyedScalars(
+ nsTArray<KeyedScalarAction>&& aScalarActions) {
+ TelemetryIPC::UpdateChildKeyedScalars(Telemetry::ProcessID::Socket,
+ aScalarActions);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvRecordChildEvents(
+ nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) {
+ TelemetryIPC::RecordChildEvents(Telemetry::ProcessID::Socket, aEvents);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvRecordDiscardedData(
+ const mozilla::Telemetry::DiscardedData& aDiscardedData) {
+ TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID::Socket,
+ aDiscardedData);
+ return IPC_OK();
+}
+
+PWebrtcTCPSocketParent* SocketProcessParent::AllocPWebrtcTCPSocketParent(
+ const Maybe<TabId>& aTabId) {
+#ifdef MOZ_WEBRTC
+ WebrtcTCPSocketParent* parent = new WebrtcTCPSocketParent(aTabId);
+ parent->AddRef();
+ return parent;
+#else
+ return nullptr;
+#endif
+}
+
+bool SocketProcessParent::DeallocPWebrtcTCPSocketParent(
+ PWebrtcTCPSocketParent* aActor) {
+#ifdef MOZ_WEBRTC
+ WebrtcTCPSocketParent* parent = static_cast<WebrtcTCPSocketParent*>(aActor);
+ parent->Release();
+#endif
+ return true;
+}
+
+already_AddRefed<PDNSRequestParent> SocketProcessParent::AllocPDNSRequestParent(
+ const nsACString& aHost, const nsACString& aTrrServer, const int32_t& port,
+ const uint16_t& aType, const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags) {
+ RefPtr<DNSRequestHandler> handler = new DNSRequestHandler();
+ RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvPDNSRequestConstructor(
+ PDNSRequestParent* aActor, const nsACString& aHost,
+ const nsACString& aTrrServer, const int32_t& port, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags) {
+ RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor);
+ RefPtr<DNSRequestHandler> handler =
+ actor->GetDNSRequest()->AsDNSRequestHandler();
+ handler->DoAsyncResolve(aHost, aTrrServer, port, aType, aOriginAttributes,
+ aFlags);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvObserveHttpActivity(
+ const HttpActivityArgs& aArgs, const uint32_t& aActivityType,
+ const uint32_t& aActivitySubtype, const PRTime& aTimestamp,
+ const uint64_t& aExtraSizeData, const nsACString& aExtraStringData) {
+ nsCOMPtr<nsIHttpActivityDistributor> activityDistributor =
+ components::HttpActivityDistributor::Service();
+ MOZ_ASSERT(activityDistributor);
+
+ Unused << activityDistributor->ObserveActivityWithArgs(
+ aArgs, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData,
+ aExtraStringData);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvInitSocketBackground(
+ Endpoint<PSocketProcessBackgroundParent>&& aEndpoint) {
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Invalid endpoint");
+ }
+
+ nsCOMPtr<nsISerialEventTarget> transportQueue;
+ if (NS_FAILED(NS_CreateBackgroundTaskQueue("SocketBackgroundParentQueue",
+ getter_AddRefs(transportQueue)))) {
+ return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed");
+ }
+
+ transportQueue->Dispatch(
+ NS_NewRunnableFunction("BindSocketBackgroundParent",
+ [endpoint = std::move(aEndpoint)]() mutable {
+ RefPtr<SocketProcessBackgroundParent> parent =
+ new SocketProcessBackgroundParent();
+ endpoint.Bind(parent);
+ }));
+ return IPC_OK();
+}
+
+already_AddRefed<PAltServiceParent>
+SocketProcessParent::AllocPAltServiceParent() {
+ RefPtr<AltServiceParent> actor = new AltServiceParent();
+ return actor.forget();
+}
+
+already_AddRefed<PProxyConfigLookupParent>
+SocketProcessParent::AllocPProxyConfigLookupParent(
+ nsIURI* aURI, const uint32_t& aProxyResolveFlags) {
+ RefPtr<ProxyConfigLookupParent> actor =
+ new ProxyConfigLookupParent(aURI, aProxyResolveFlags);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvPProxyConfigLookupConstructor(
+ PProxyConfigLookupParent* aActor, nsIURI* aURI,
+ const uint32_t& aProxyResolveFlags) {
+ static_cast<ProxyConfigLookupParent*>(aActor)->DoProxyLookup();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvCachePushCheck(
+ nsIURI* aPushedURL, OriginAttributes&& aOriginAttributes,
+ nsCString&& aRequestString, CachePushCheckResolver&& aResolver) {
+ RefPtr<CachePushChecker> checker = new CachePushChecker(
+ aPushedURL, aOriginAttributes, aRequestString, aResolver);
+ if (NS_FAILED(checker->DoCheck())) {
+ aResolver(false);
+ }
+ return IPC_OK();
+}
+
+// To ensure that IPDL is finished before SocketParent gets deleted.
+class DeferredDeleteSocketProcessParent : public Runnable {
+ public:
+ explicit DeferredDeleteSocketProcessParent(
+ RefPtr<SocketProcessParent>&& aParent)
+ : Runnable("net::DeferredDeleteSocketProcessParent"),
+ mParent(std::move(aParent)) {}
+
+ NS_IMETHODIMP Run() override { return NS_OK; }
+
+ private:
+ RefPtr<SocketProcessParent> mParent;
+};
+
+/* static */
+void SocketProcessParent::Destroy(RefPtr<SocketProcessParent>&& aParent) {
+ NS_DispatchToMainThread(
+ new DeferredDeleteSocketProcessParent(std::move(aParent)));
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvExcludeHttp2OrHttp3(
+ const HttpConnectionInfoCloneArgs& aArgs) {
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aArgs);
+ if (!cinfo) {
+ MOZ_ASSERT(false, "failed to deserizlize http connection info");
+ return IPC_OK();
+ }
+
+ if (cinfo->IsHttp3()) {
+ gHttpHandler->ExcludeHttp3(cinfo);
+ } else {
+ gHttpHandler->ExcludeHttp2(cinfo);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvOnConsoleMessage(
+ const nsString& aMessage) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ consoleService->LogStringMessage(aMessage.get());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvFOGData(ByteBuf&& aBuf) {
+ glean::FOGData(std::move(aBuf));
+ return IPC_OK();
+}
+
+#if defined(XP_WIN)
+mozilla::ipc::IPCResult SocketProcessParent::RecvGetModulesTrust(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority,
+ GetModulesTrustResolver&& aResolver) {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->GetModulesTrust(std::move(aModPaths), aRunAtNormalPriority)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolver](ModulesMapResult&& aResult) {
+ aResolver(Some(ModulesMapResult(std::move(aResult))));
+ },
+ [aResolver](nsresult aRv) { aResolver(Nothing()); });
+ return IPC_OK();
+}
+#endif // defined(XP_WIN)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessParent.h b/netwerk/ipc/SocketProcessParent.h
new file mode 100644
index 0000000000..732e431e35
--- /dev/null
+++ b/netwerk/ipc/SocketProcessParent.h
@@ -0,0 +1,117 @@
+/* -*- 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_SocketProcessParent_h
+#define mozilla_net_SocketProcessParent_h
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/CrashReporterHelper.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/net/PSocketProcessParent.h"
+
+namespace mozilla {
+
+namespace dom {
+class MemoryReport;
+class MemoryReportRequestHost;
+} // namespace dom
+
+namespace net {
+
+class SocketProcessHost;
+
+// IPC actor of socket process in parent process. This is allocated and managed
+// by SocketProcessHost.
+class SocketProcessParent final
+ : public PSocketProcessParent,
+ public ipc::CrashReporterHelper<GeckoProcessType_Socket> {
+ public:
+ friend class SocketProcessHost;
+
+ NS_INLINE_DECL_REFCOUNTING(SocketProcessParent, final)
+
+ explicit SocketProcessParent(SocketProcessHost* aHost);
+
+ static SocketProcessParent* GetSingleton();
+
+ mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport);
+ mozilla::ipc::IPCResult RecvAccumulateChildHistograms(
+ nsTArray<HistogramAccumulation>&& aAccumulations);
+ mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms(
+ nsTArray<KeyedHistogramAccumulation>&& aAccumulations);
+ mozilla::ipc::IPCResult RecvUpdateChildScalars(
+ nsTArray<ScalarAction>&& aScalarActions);
+ mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(
+ nsTArray<KeyedScalarAction>&& aScalarActions);
+ mozilla::ipc::IPCResult RecvRecordChildEvents(
+ nsTArray<ChildEventData>&& events);
+ mozilla::ipc::IPCResult RecvRecordDiscardedData(
+ const DiscardedData& aDiscardedData);
+
+ PWebrtcTCPSocketParent* AllocPWebrtcTCPSocketParent(
+ const Maybe<TabId>& aTabId);
+ bool DeallocPWebrtcTCPSocketParent(PWebrtcTCPSocketParent* aActor);
+ already_AddRefed<PDNSRequestParent> AllocPDNSRequestParent(
+ const nsACString& aHost, const nsACString& aTrrServer,
+ const int32_t& port, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& aFlags);
+ virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor(
+ PDNSRequestParent* actor, const nsACString& aHost,
+ const nsACString& trrServer, const int32_t& port, const uint16_t& type,
+ const OriginAttributes& aOriginAttributes,
+ const nsIDNSService::DNSFlags& flags) override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool SendRequestMemoryReport(const uint32_t& aGeneration,
+ const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& aDMDFile);
+
+ mozilla::ipc::IPCResult RecvObserveHttpActivity(
+ const HttpActivityArgs& aArgs, const uint32_t& aActivityType,
+ const uint32_t& aActivitySubtype, const PRTime& aTimestamp,
+ const uint64_t& aExtraSizeData, const nsACString& aExtraStringData);
+
+ mozilla::ipc::IPCResult RecvInitSocketBackground(
+ Endpoint<PSocketProcessBackgroundParent>&& aEndpoint);
+
+ already_AddRefed<PAltServiceParent> AllocPAltServiceParent();
+
+ already_AddRefed<PProxyConfigLookupParent> AllocPProxyConfigLookupParent(
+ nsIURI* aURI, const uint32_t& aProxyResolveFlags);
+ mozilla::ipc::IPCResult RecvPProxyConfigLookupConstructor(
+ PProxyConfigLookupParent* aActor, nsIURI* aURI,
+ const uint32_t& aProxyResolveFlags) override;
+
+ mozilla::ipc::IPCResult RecvCachePushCheck(
+ nsIURI* aPushedURL, OriginAttributes&& aOriginAttributes,
+ nsCString&& aRequestString, CachePushCheckResolver&& aResolver);
+
+ mozilla::ipc::IPCResult RecvExcludeHttp2OrHttp3(
+ const HttpConnectionInfoCloneArgs& aArgs);
+ mozilla::ipc::IPCResult RecvOnConsoleMessage(const nsString& aMessage);
+
+ mozilla::ipc::IPCResult RecvFOGData(ByteBuf&& aBuf);
+
+#if defined(XP_WIN)
+ mozilla::ipc::IPCResult RecvGetModulesTrust(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority,
+ GetModulesTrustResolver&& aResolver);
+#endif // defined(XP_WIN)
+
+ private:
+ ~SocketProcessParent();
+
+ SocketProcessHost* mHost;
+ UniquePtr<dom::MemoryReportRequestHost> mMemoryReportRequest;
+
+ static void Destroy(RefPtr<SocketProcessParent>&& aParent);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SocketProcessParent_h
diff --git a/netwerk/ipc/moz.build b/netwerk/ipc/moz.build
new file mode 100644
index 0000000000..ee87862785
--- /dev/null
+++ b/netwerk/ipc/moz.build
@@ -0,0 +1,117 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "ChannelEventQueue.h",
+ "DocumentChannel.h",
+ "DocumentChannelChild.h",
+ "DocumentChannelParent.h",
+ "DocumentLoadListener.h",
+ "InputChannelThrottleQueueChild.h",
+ "InputChannelThrottleQueueParent.h",
+ "NeckoChild.h",
+ "NeckoCommon.h",
+ "NeckoMessageUtils.h",
+ "NeckoParent.h",
+ "NeckoTargetHolder.h",
+ "ParentChannelWrapper.h",
+ "ParentProcessDocumentChannel.h",
+ "ProxyAutoConfigChild.h",
+ "ProxyAutoConfigParent.h",
+ "ProxyConfigLookup.h",
+ "ProxyConfigLookupChild.h",
+ "ProxyConfigLookupParent.h",
+ "SocketProcessBackgroundChild.h",
+ "SocketProcessBackgroundParent.h",
+ "SocketProcessBridgeChild.h",
+ "SocketProcessBridgeParent.h",
+ "SocketProcessChild.h",
+ "SocketProcessHost.h",
+ "SocketProcessImpl.h",
+ "SocketProcessParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "ChannelEventQueue.cpp",
+ "DocumentChannel.cpp",
+ "DocumentChannelChild.cpp",
+ "DocumentChannelParent.cpp",
+ "DocumentLoadListener.cpp",
+ "InputChannelThrottleQueueChild.cpp",
+ "InputChannelThrottleQueueParent.cpp",
+ "NeckoChild.cpp",
+ "NeckoCommon.cpp",
+ "NeckoParent.cpp",
+ "NeckoTargetHolder.cpp",
+ "ParentChannelWrapper.cpp",
+ "ParentProcessDocumentChannel.cpp",
+ "ProxyConfigLookup.cpp",
+ "ProxyConfigLookupChild.cpp",
+ "ProxyConfigLookupParent.cpp",
+ "SocketProcessBackgroundChild.cpp",
+ "SocketProcessBackgroundParent.cpp",
+ "SocketProcessBridgeChild.cpp",
+ "SocketProcessBridgeParent.cpp",
+ "SocketProcessChild.cpp",
+ "SocketProcessHost.cpp",
+ "SocketProcessImpl.cpp",
+ "SocketProcessParent.cpp",
+]
+
+SOURCES += [
+ "ProxyAutoConfigChild.cpp",
+ "ProxyAutoConfigParent.cpp",
+]
+
+
+PREPROCESSED_IPDL_SOURCES += [
+ "PNecko.ipdl",
+ "PSocketProcess.ipdl",
+ "PSocketProcessBridge.ipdl",
+]
+
+IPDL_SOURCES = [
+ "NeckoChannelParams.ipdlh",
+ "PDataChannel.ipdl",
+ "PDocumentChannel.ipdl",
+ "PFileChannel.ipdl",
+ "PInputChannelThrottleQueue.ipdl",
+ "PProxyAutoConfig.ipdl",
+ "PProxyConfigLookup.ipdl",
+ "PSimpleChannel.ipdl",
+ "PSocketProcessBackground.ipdl",
+]
+
+# needed so --disable-webrtc builds work (yes, a bit messy)
+if not CONFIG["MOZ_WEBRTC"]:
+ IPDL_SOURCES += [
+ "../../dom/media/webrtc/transport/ipc/PStunAddrsRequest.ipdl",
+ "../../dom/media/webrtc/transport/ipc/PWebrtcTCPSocket.ipdl",
+ "../../dom/media/webrtc/transport/ipc/WebrtcProxyConfig.ipdlh",
+ ]
+ EXPORTS.mozilla.net += [
+ "../../dom/media/webrtc/transport/ipc/NrIceStunAddrMessageUtils.h",
+ "../../dom/media/webrtc/transport/ipc/PStunAddrsParams.h",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/caps",
+ "/dom/base",
+ "/dom/media/webrtc/transport",
+ "/media/webrtc",
+ "/modules/libjar",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+ "/security/manager/ssl",
+ "/xpcom/threads",
+]
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/netwerk/locales/en-US/necko.properties b/netwerk/locales/en-US/necko.properties
new file mode 100644
index 0000000000..7498cca20b
--- /dev/null
+++ b/netwerk/locales/en-US/necko.properties
@@ -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/.
+
+3=Looking up %1$S…
+4=Connected to %1$S…
+5=Sending request to %1$S…
+6=Transferring data from %1$S…
+7=Connecting to %1$S…
+8=Read %1$S
+9=Wrote %1$S
+10=Waiting for %1$S…
+11=Looked up %1$S…
+12=Performing a TLS handshake to %1$S…
+13=The TLS handshake finished for %1$S…
+
+RepostFormData=This web page is being redirected to a new location. Would you like to resend the form data you have typed to the new location?
+
+# Directory listing strings
+DirTitle=Index of %1$S
+DirGoUp=Up to higher level directory
+ShowHidden=Show hidden objects
+DirColName=Name
+DirColSize=Size
+DirColMTime=Last Modified
+DirFileLabel=File:
+
+SuperfluousAuth=You are about to log in to the site “%1$S” with the username “%2$S”, but the website does not require authentication. This may be an attempt to trick you.\n\nIs “%1$S” the site you want to visit?
+AutomaticAuth=You are about to log in to the site “%1$S” with the username “%2$S”.
+
+TrackerUriBlocked=The resource at “%1$S” was blocked because content blocking is enabled.
+UnsafeUriBlocked=The resource at “%1$S” was blocked by Safe Browsing.
+
+# LOCALIZATION NOTE (StrictUrlProtocolSetter): %1$S is the URL that has attempted to be changed. %2$S is the invalid target protocol.
+StrictUrlProtocolSetter=Url “%1$S“ change to protocol “%2$S“ was blocked.
+
+# LOCALIZATION NOTE (CORPBlocked): %1$S is the URL of the blocked resource. %2$S is the URL of the MDN page about CORP.
+CORPBlocked=The resource at “%1$S” was blocked due to its Cross-Origin-Resource-Policy header (or lack thereof). See %2$S
+CookieBlockedByPermission=Request to access cookies or storage on “%1$S” was blocked because of custom cookie permission.
+CookieBlockedTracker=Request to access cookie or storage on “%1$S” was blocked because it came from a tracker and content blocking is enabled.
+CookieBlockedAll=Request to access cookie or storage on “%1$S” was blocked because we are blocking all storage access requests.
+CookieBlockedForeign=Request to access cookie or storage on “%1$S” was blocked because we are blocking all third-party storage access requests and content blocking is enabled.
+# As part of dynamic state partitioning, third-party resources might be limited to "partitioned" storage access that is separate from the first-party context.
+# This allows e.g. cookies to still be set, and prevents tracking without totally blocking storage access. This message is shown in the web console when this happens
+# to inform developers that their storage is isolated.
+CookiePartitionedForeign2=Partitioned cookie or storage access was provided to “%1$S” because it is loaded in the third-party context and dynamic state partitioning is enabled.
+
+# LOCALIZATION NOTE (CookieAllowedForOriginByStorageAccessAPI): %2$S and %1$S are URLs.
+CookieAllowedForOriginByStorageAccessAPI=Storage access granted for origin “%2$S” on “%1$S”.
+# LOCALIZATION NOTE (CookieAllowedForOriginByHeuristic): %2$S and %1$S are URLs.
+CookieAllowedForOriginByHeuristic=Storage access automatically granted for origin “%2$S” on “%1$S”.
+# LOCALIZATION NOTE (CookieAllowedForFpiByHeuristic): %2$S and %1$S are URLs.
+CookieAllowedForFpiByHeuristic=Storage access automatically granted for First-Party isolation “%2$S” on “%1$S”.
+
+# LOCALIZATION NOTE(CookieRejectedNonRequiresSecure2): %1$S is the cookie name. Do not localize "SameSite=None" and "secure".
+CookieRejectedNonRequiresSecure2=Cookie “%1$S” rejected because it has the “SameSite=None” attribute but is missing the “secure” attribute.
+# LOCALIZATION NOTE(CookieRejectedNonRequiresSecureForBeta3): %1$S is the cookie name. %2$S is a URL. Do not localize "SameSite", "SameSite=None" and "secure".
+CookieRejectedNonRequiresSecureForBeta3=Cookie “%1$S” will be soon rejected because it has the “SameSite” attribute set to “None” without the “secure” attribute. To know more about the “SameSite“ attribute, read %2$S
+# LOCALIZATION NOTE(CookieLaxForced2): %1$S is the cookie name. Do not localize "SameSite", "Lax" and "SameSite=Lax".
+CookieLaxForced2=Cookie “%1$S” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.
+# LOCALIZATION NOTE(CookieLaxForcedForBeta2): %1$S is the cookie name. %2$S is a URL. Do not localize "SameSite", "Lax" and "SameSite=Lax", "SameSite=None".
+CookieLaxForcedForBeta2=Cookie “%1$S” does not have a proper “SameSite” attribute value. Soon, cookies without the “SameSite” attribute or with an invalid value will be treated as “Lax”. This means that the cookie will no longer be sent in third-party contexts. If your application depends on this cookie being available in such contexts, please add the “SameSite=None“ attribute to it. To know more about the “SameSite“ attribute, read %2$S
+# LOCALIZATION NOTE(CookieSameSiteValueInvalid2): %1$S is cookie name. Do not localize "SameSite", "Lax", "Strict" and "None"
+CookieSameSiteValueInvalid2=Invalid “SameSite“ value for cookie “%1$S”. The supported values are: “Lax“, “Strict“, “None“.
+# LOCALIZATION NOTE (CookieOversize): %1$S is the cookie name. %2$S is the number of bytes. "B" means bytes.
+CookieOversize=Cookie “%1$S” is invalid because its size is too big. Max size is %2$S B.
+# LOCALIZATION NOTE (CookiePathOversize): %1$S is the cookie name. %2$S is the number of bytes. "B" means bytes.
+CookiePathOversize=Cookie “%1$S” is invalid because its path size is too big. Max size is %2$S B.
+# LOCALIZATION NOTE (CookieRejectedByPermissionManager): %1$S is the cookie response header.
+CookieRejectedByPermissionManager=Cookie “%1$S” has been rejected by user set permissions.
+# LOCALIZATION NOTE (CookieRejectedInvalidCharName): %1$S is the cookie name.
+CookieRejectedInvalidCharName=Cookie “%1$S” has been rejected for invalid characters in the name.
+# LOCALIZATION NOTE (CookieRejectedInvalidDomain): %1$S is the cookie name.
+CookieRejectedInvalidDomain=Cookie “%1$S” has been rejected for invalid domain.
+# LOCALIZATION NOTE (CookieRejectedInvalidPrefix): %1$S is the cookie name.
+CookieRejectedInvalidPrefix=Cookie “%1$S” has been rejected for invalid prefix.
+# LOCALIZATION NOTE (CookieRejectedInvalidCharValue): %1$S is the cookie name.
+CookieRejectedInvalidCharValue=Cookie “%1$S” has been rejected for invalid characters in the value.
+# LOCALIZATION NOTE (CookieRejectedHttpOnlyButFromScript): %1$S is the cookie name.
+CookieRejectedHttpOnlyButFromScript=Cookie “%1$S” has been rejected because there is already an HTTP-Only cookie but script tried to store a new one.
+# LOCALIZATION NOTE (CookieRejectedSecureButHttp): %1$S is the cookie name.
+CookieRejectedSecureButNonHttps=Cookie “%1$S” has been rejected because a non-HTTPS cookie can’t be set as “secure”.
+# LOCALIZATION NOTE (CookieRejectedThirdParty): %1$S is the cookie response header.
+CookieRejectedThirdParty=Cookie “%1$S” has been rejected as third-party.
+# LOCALIZATION NOTE (CookieRejectedNonsecureOverSecure): %1$S is the cookie name.
+CookieRejectedNonsecureOverSecure=Cookie “%1$S” has been rejected because there is an existing “secure” cookie.
+# LOCALIZATION NOTE (CookieRejectedForNonSameSiteness): %1$S is the cookie name.
+CookieRejectedForNonSameSiteness=Cookie “%1$S” has been rejected because it is in a cross-site context and its “SameSite” is “Lax” or “Strict”.
+# LOCALIZATION NOTE (CookieRejectedPartitionedRequiresSecure): %1$S is the cookie name.
+CookieRejectedPartitionedRequiresSecure=Cookie “%1$S” has been rejected because it has the “Partitioned” attribute but is missing the “secure” attribute.
+
+# LOCALIZATION NOTE (CookieForeignNoPartitionedWarning): %1$S is the cookie name. Do not translate "Partitioned"
+CookieForeignNoPartitionedWarning=Cookie “%1$S” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute.
+
+# LOCALIZATION NOTE (CookieForeignNoPartitionedError): %1$S is the cookie name. Do not translate "Partitioned"
+CookieForeignNoPartitionedError=Cookie “%1$S” has been rejected because it is foreign and does not have the “Partitioned“ attribute.
+
+# LOCALIZATION NOTE (CookieBlockedCrossSiteRedirect): %1$S is the cookie name. Do not translate "SameSite", "Lax" or "Strict".
+CookieBlockedCrossSiteRedirect=Cookie “%1$S” with the “SameSite” attribute value “Lax” or “Strict” was omitted because of a cross-site redirect.
+
+# LOCALIZATION NOTE (APIDeprecationWarning): %1$S is the deprecated API; %2$S is the API function that should be used.
+APIDeprecationWarning=Warning: ‘%1$S’ deprecated, please use ‘%2$S’
+
+# LOCALIZATION NOTE (ResourceBlockedCORS): %1$S is the url of the resource blocked by ORB. $2$S is the reason.
+# example: The resource at <url> was blocked by OpaqueResponseBlocking. Reason: “nosniff with either blocklisted or text/plain”.
+ResourceBlockedORB=The resource at “%1$S” was blocked by OpaqueResponseBlocking. Reason: “%2$S”.
+
+InvalidHTTPResponseStatusLine=The status line of the HTTP response is invalid
diff --git a/netwerk/locales/jar.mn b/netwerk/locales/jar.mn
new file mode 100644
index 0000000000..95bafbb8a9
--- /dev/null
+++ b/netwerk/locales/jar.mn
@@ -0,0 +1,9 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@AB_CD@.jar:
+% locale necko @AB_CD@ %locale/@AB_CD@/necko/
+ locale/@AB_CD@/necko/necko.properties (%necko.properties)
diff --git a/netwerk/locales/moz.build b/netwerk/locales/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/netwerk/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/netwerk/metrics.yaml b/netwerk/metrics.yaml
new file mode 100644
index 0000000000..35388c7a73
--- /dev/null
+++ b/netwerk/metrics.yaml
@@ -0,0 +1,1029 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - 'Core :: Networking'
+
+networking:
+ speculative_connect_outcome:
+ type: labeled_counter
+ description: >
+ Counts the occurrence of each outcome of a speculative connection
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=909865
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=909865
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ labels:
+ - successful
+ - aborted_socket_limit
+ - aborted_socket_fail
+ - aborted_https_not_enabled
+ telemetry_mirror: NETWORKING_SPECULATIVE_CONNECT_OUTCOME
+
+ cookie_timestamp_fixed_count:
+ type: labeled_counter
+ description: >
+ Counts the number of times a cookie's invalid timestamp was fixed when
+ reading it from the DB.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828126
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828126#c5
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ labels:
+ - creationTime
+ - lastAccessed
+ expires: never
+
+ cookie_creation_fixup_diff:
+ type: custom_distribution
+ unit: second
+ description: >
+ If we fix up a cookie creation timestamp that is in the future this
+ metric records the number of seconds that timestamp was off from NOW.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828126
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828126#c5
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+ range_min: 0
+ range_max: 315360000000
+ bucket_count: 100
+ histogram_type: exponential
+
+ cookie_access_fixup_diff:
+ type: custom_distribution
+ unit: second
+ description: >
+ If we fix up a cookie lastAccessed timestamp that is in the future this
+ metric records the number of seconds that timestamp was off from NOW.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828126
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828126#c5
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+ range_min: 0
+ range_max: 315360000000
+ bucket_count: 100
+ histogram_type: exponential
+
+ cookie_count_total:
+ type: custom_distribution
+ description: >
+ Reports the total number of cookies in storage
+ range_min: 0
+ range_max: 4000
+ bucket_count: 22
+ histogram_type: exponential
+ unit: integer
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942#TDB
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - edgul@mozilla.com
+ expires: 128
+
+ cookie_count_partitioned:
+ type: custom_distribution
+ description: >
+ Reports the number of partitioned cookies in storage
+ range_min: 0
+ range_max: 4000
+ bucket_count: 22
+ histogram_type: exponential
+ unit: integer
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942#TDB
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - edgul@mozilla.com
+ expires: 128
+
+ cookie_count_unpartitioned:
+ type: custom_distribution
+ description: >
+ Reports the number of unpartitioned cookies in storage
+ range_min: 0
+ range_max: 4000
+ bucket_count: 22
+ histogram_type: exponential
+ unit: integer
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942#TDB
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - edgul@mozilla.com
+ expires: 128
+
+ cookie_count_part_by_key:
+ type: custom_distribution
+ description: >
+ A distribution of the partitioned cookies in storage belonging to
+ a particular cookie key (host + origin attributes)
+ range_min: 1
+ range_max: 220
+ bucket_count: 10
+ histogram_type: exponential
+ unit: integer
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942#TDB
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - edgul@mozilla.com
+ expires: 128
+
+ cookie_count_unpart_by_key:
+ type: custom_distribution
+ description: >
+ A distribution of the unpartitioned cookies in storage belonging to
+ a particular cookie key (host + origin attributes)
+ range_min: 1
+ range_max: 220
+ bucket_count: 10
+ histogram_type: exponential
+ unit: integer
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942#TDB
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - edgul@mozilla.com
+ expires: 128
+
+ cookie_purge_max:
+ type: custom_distribution
+ description: >
+ A distribution of the number of cookies purged across
+ all host + OAs as a result of exceeding the cookie maximum threshold
+ (single purge)
+ range_min: 1
+ range_max: 4000
+ bucket_count: 22
+ histogram_type: exponential
+ unit: integer
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942#TDB
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - edgul@mozilla.com
+ expires: 128
+
+ cookie_purge_entry_max:
+ type: custom_distribution
+ description: >
+ A distribution of the number of cookies purged for a single
+ host + OA entry as a result of exceeding the maximum threshold
+ for the given host + OA (single purge)
+ range_min: 1
+ range_max: 220
+ bucket_count: 10
+ histogram_type: exponential
+ unit: integer
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1828942#TDB
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - edgul@mozilla.com
+ expires: 128
+
+ set_cookie:
+ type: counter
+ description: >
+ This counts the number of times we set a cookie. Introduced
+ as a denomenator for measuring CHIPS adoption.
+ bugs:
+ - https://bugzilla.mozilla.org/1865199
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1865199#c3
+ notification_emails:
+ - bvandersloot@mozilla.com
+ expires: 132
+
+ set_cookie_foreign:
+ type: rate
+ description: >
+ This counts the number of times we set a cookie from a foreign (not
+ same-site) context. Introduced as a denomenator for measuring CHIPS
+ adoption.
+ bugs:
+ - https://bugzilla.mozilla.org/1865199
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1865199#c3
+ notification_emails:
+ - bvandersloot@mozilla.com
+ expires: 132
+ denominator_metric: networking.set_cookie
+
+ set_cookie_partitioned:
+ type: rate
+ description: >
+ This counts the number of times we set a cookie that has the Partitioned
+ attribute. This tracks the adoption of CHIPS.
+ bugs:
+ - https://bugzilla.mozilla.org/1865199
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1865199#c3
+ notification_emails:
+ - bvandersloot@mozilla.com
+ expires: 132
+ denominator_metric: networking.set_cookie
+
+ set_cookie_foreign_partitioned:
+ type: rate
+ description: >
+ This counts the number of times we set a cookie that has the Partitioned
+ attribute in a foreign (not same-site) context. This tracks the adoption
+ of CHIPS.
+ bugs:
+ - https://bugzilla.mozilla.org/1865199
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1865199#c3
+ notification_emails:
+ - bvandersloot@mozilla.com
+ expires: 132
+ denominator_metric: networking.set_cookie
+
+ dns_lookup_time:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time for a successful DNS resolution (msec) |
+ Migrated from Firefox Telemetry's `DNS_LOOKUP_TIME`.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240#c6
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: DNS_LOOKUP_TIME
+
+ dns_renewal_time:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time for a renewed DNS OS resolution (msec) |
+ Migrated from Firefox Telemetry's `DNS_RENEWAL_TIME`.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240#c6
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: DNS_RENEWAL_TIME
+
+ dns_renewal_time_for_ttl:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time for a DNS OS resolution (msec) used to get TTL |
+ Migrated from Firefox Telemetry's `DNS_RENEWAL_TIME_FOR_TTL`.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240#c6
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: DNS_RENEWAL_TIME_FOR_TTL
+
+ dns_failed_lookup_time:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time for an unsuccessful DNS OS resolution (msec) |
+ Migrated from Firefox Telemetry's `DNS_FAILED_LOOKUP_TIME`.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1838240#c6
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: DNS_FAILED_LOOKUP_TIME
+
+ dns_native_count:
+ type: labeled_counter
+ description: >
+ The count of calls to the native DNS APIs
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879165
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - vgosu@mozilla.com
+ - necko@mozilla.com
+ expires: 140
+ labels:
+ - regular
+ - private
+ - https_regular
+ - https_private
+
+ http_content_onstart_delay:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ The time between dispatching OnStartRequest from the socket thread and processing it on the main thread (content process).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926#c
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - rjesup@mozilla.com
+ expires: 130
+
+ http_content_onstop_delay:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ The time between dispatching OnStopRequest from the socket thread and processing it on the main thread (content process).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926#c
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - rjesup@mozilla.com
+ expires: 130
+
+ http_content_ondatafinished_delay:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ The time between dispatching OnDataFinished from the socket thread and processing it.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857615
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857615#c
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - rjesup@mozilla.com
+ expires: 130
+
+ http_content_ondatafinished_to_onstop_delay:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ The time between processing OnDataFinished and processing OnStopRequest (if OnDataFinished comes first)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857615
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857615#c
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - rjesup@mozilla.com
+ expires: 130
+
+ http_content_ondatafinished_to_onstop_delay_negative:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ The time between processing OnStopRequest and processing OnDataFinished (if OnStopRequest comes first)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857615
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857615#c
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - rjesup@mozilla.com
+ expires: 130
+
+ http_content_html5parser_ondatafinished_to_onstop_delay:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ The time between processing OnDataFinished and processing OnStopRequest (if OnDataFinished comes first)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - rjesup@mozilla.com
+ expires: 130
+
+ http_content_html5parser_ondatafinished_to_onstop_delay_negative:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ The time between processing OnStopRequest and processing OnDataFinished (if OnStopRequest comes first)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1857926
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - rjesup@mozilla.com
+ expires: 130
+
+ http_1_download_throughput:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 10000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The download throughput for http/1.0, http/1.1 requests larger than 10MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1846798
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1846798#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: NETWORKING_DOWNLOAD_THROUGHPUT_HTTP_1
+
+ http_2_download_throughput:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 10000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The download throughput for http/2 requests larger than 10MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1846798
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1846798#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: NETWORKING_DOWNLOAD_THROUGHPUT_HTTP_2
+
+ http_3_download_throughput:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 10000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The download throughput for http/3 requests larger than 10MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1846798
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1846798#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: NETWORKING_DOWNLOAD_THROUGHPUT_HTTP_3
+
+ http_1_upload_throughput:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/1.0, http/1.1 requests larger than 10MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_1
+
+ http_2_upload_throughput:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/2 requests larger than 10MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_2
+
+ http_3_upload_throughput:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/3 requests larger than 10MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - acreskey@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_3
+
+ http_1_upload_throughput_10_50:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/1.0, http/1.1 request size between 10MB and 50MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_1_10_50
+
+ http_1_upload_throughput_50_100:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/1.0, http/1.1 request size between 50MB and 100MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_1_50_100
+
+ http_1_upload_throughput_100:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/1.0, http/1.1 request size larger than 100MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_1_100
+
+ http_2_upload_throughput_10_50:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/2 request size between 10MB and 50MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_2_10_50
+
+ http_2_upload_throughput_50_100:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/2 request size between 50MB and 100MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_2_50_100
+
+ http_2_upload_throughput_100:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/2 request size larger than 100MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_2_100
+
+ http_3_upload_throughput_10_50:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/3 request size between 10MB and 50MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_3_10_50
+
+ http_3_upload_throughput_50_100:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/3 request size between 50MB and 100MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_3_50_100
+
+ http_3_upload_throughput_100:
+ type: custom_distribution
+ unit: mbps
+ range_min: 0
+ range_max: 5000
+ bucket_count: 100
+ histogram_type: exponential
+ description: >
+ The upload throughput for http/3 request size larger than 100MB. Measured in megabits per second, Mbps.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866739
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1858256#c2
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ telemetry_mirror: HTTP_UPLOAD_THROUGHPUT_MBPS_HTTP_3_100
+
+ residual_cache_folder_count:
+ type: counter
+ description: >
+ Counts how often we find a cache folder that wasn't purged
+ at shutdown by a background task process.
+ bugs:
+ - https://bugzilla.mozilla.org/1848542
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1848542
+ notification_emails:
+ - vgosu@mozilla.com
+ expires: 136
+
+ residual_cache_folder_removal:
+ type: labeled_counter
+ description: >
+ Counts how often succeed/fail in removing cache folder
+ that wasn't purged at shutdown
+ bugs:
+ - https://bugzilla.mozilla.org/1848542
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1848542
+ notification_emails:
+ - vgosu@mozilla.com
+ expires: 136
+ labels:
+ - success
+ - failure
+
+ trr_request_count:
+ type: labeled_counter
+ description: >
+ The count of successful TRR requests keyed by regular/private browsing
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866245
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1866245
+ notification_emails:
+ - vgosu@mozilla.com
+ - necko@mozilla.com
+ expires: 130
+ labels:
+ - regular
+ - private
+
+ http_response_version:
+ type: labeled_counter
+ description: >
+ HTTP protocol version used on response from nsHttp.h
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1876776
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1876776#c2
+ notification_emails:
+ - necko@mozilla.com
+ expires: never
+ labels:
+ - unknown
+ - http_1
+ - http_2
+ - http_3
+
+ https_rr_presented:
+ type: labeled_counter
+ description: >
+ HTTPS RR is presented or not
+ bugs:
+ - https://bugzilla.mozilla.org/1686421
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1686421
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ labels:
+ - presented
+ - presented_with_http3
+ - none
+
+ https_upgrade_with_https_rr:
+ type: labeled_counter
+ description: >
+ Whether an HTTP request gets upgraded to HTTPS because of HTTPS RR
+ bugs:
+ - https://bugzilla.mozilla.org/1686421
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1686421
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ labels:
+ - https_rr
+ - others
+
+ http_channel_onstart_success_https_rr:
+ type: labeled_counter
+ description: >
+ Successfully started HTTP channels when HTTPS RR is used
+ bugs:
+ - https://bugzilla.mozilla.org/1682552
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1682552
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+ labels:
+ - success
+ - failure
+ - success_ech_used
+ - failure_ech_used
+
+ http_channel_page_open_to_first_sent:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time in milliseconds from AsyncOpen to first byte of request sent,
+ applicable for page loads without HTTPS RR
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+
+ http_channel_sub_open_to_first_sent:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time in milliseconds from AsyncOpen to first byte of request sent,
+ applicable for sub-resource loads without HTTPS RR
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+
+ http_channel_page_open_to_first_sent_https_rr:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time in milliseconds from AsyncOpen to first byte of request sent,
+ applicable for page loads with HTTPS RR
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+
+ http_channel_sub_open_to_first_sent_https_rr:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time in milliseconds from AsyncOpen to first byte of request sent,
+ applicable for sub-resource loads with HTTPS RR
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+
+ transaction_wait_time_https_rr:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time from submission to dispatch of transaction when HTTPS RR is used (ms)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
+
+ transaction_wait_time:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time from submission to dispatch of transaction without HTTPS RR (ms)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1697480
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: never
diff --git a/netwerk/mime/moz.build b/netwerk/mime/moz.build
new file mode 100644
index 0000000000..a7b62777f4
--- /dev/null
+++ b/netwerk/mime/moz.build
@@ -0,0 +1,23 @@
+# -*- 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 += [
+ "nsIMIMEHeaderParam.idl",
+ "nsIMIMEInfo.idl",
+ "nsIMIMEService.idl",
+]
+
+XPIDL_MODULE = "mimetype"
+
+EXPORTS += [
+ "nsMimeTypes.h",
+]
+
+SOURCES += [
+ "nsMIMEHeaderParamImpl.cpp",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/mime/nsIMIMEHeaderParam.idl b/netwerk/mime/nsIMIMEHeaderParam.idl
new file mode 100644
index 0000000000..cdc28b7e90
--- /dev/null
+++ b/netwerk/mime/nsIMIMEHeaderParam.idl
@@ -0,0 +1,206 @@
+/* -*- Mode: IDL; 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/. */
+
+/*
+ * This interface allows any module to access the routine
+ * for MIME header parameter parsing (RFC 2231/5987)
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(9c9252a1-fdaf-40a2-9c2b-a3dc45e28dde)]
+interface nsIMIMEHeaderParam : nsISupports {
+
+ /**
+ * Given the value of a single header field (such as
+ * Content-Disposition and Content-Type) and the name of a parameter
+ * (e.g. filename, name, charset), returns the value of the parameter.
+ * The value is obtained by decoding RFC 2231/5987-style encoding,
+ * RFC 2047-style encoding, and converting to UniChar(UTF-16)
+ * from charset specified in RFC 2231/2047 encoding, UTF-8,
+ * <code>aFallbackCharset</code>, the locale charset as fallback if
+ * <code>TryLocaleCharset</code> is set, and null-padding as last resort
+ * if all else fails.
+ *
+ * <p>
+ * This method internally invokes <code>getParameterInternal</code>,
+ * However, it does not stop at decoding RFC 2231 (the task for
+ * <code>getParameterInternal</code> but tries to cope
+ * with several non-standard-compliant cases mentioned below.
+ *
+ * <p>
+ * Note that a lot of MUAs put RFC 2047-encoded parameters. Unfortunately,
+ * this includes Mozilla as of 2003-05-30. Even more standard-ignorant MUAs,
+ * web servers and application servers put 'raw 8bit characters'. This will
+ * try to cope with all these cases as gracefully as possible. Additionally,
+ * it returns the language tag if the parameter is encoded per RFC 2231 and
+ * includes lang.
+ *
+ * <p>
+ * Note that GetParameterHTTP skips some of the workarounds used for
+ * mail (MIME) header fields, and thus SHOULD be used from non-mail
+ * code.
+ *
+ *
+ * @param aHeaderVal a header string to get the value of a parameter
+ * from.
+ * @param aParamName the name of a MIME header parameter (e.g.
+ * filename, name, charset). If empty, returns
+ * the first (possibly) _unnamed_ 'parameter'.
+ * @param aFallbackCharset fallback charset to try if the string after
+ * RFC 2231/2047 decoding or the raw 8bit
+ * string is not UTF-8
+ * @param aTryLocaleCharset If set, makes yet another attempt
+ * with the locale charset.
+ * @param aLang If non-null, assigns it to a pointer
+ * to a string containing the value of language
+ * obtained from RFC 2231 parsing. Caller has to
+ * free it.
+ * @return the value of <code>aParamName</code> in Unichar(UTF-16).
+ */
+ AString getParameter(in ACString aHeaderVal,
+ in string aParamName,
+ in ACString aFallbackCharset,
+ in boolean aTryLocaleCharset,
+ out string aLang);
+
+
+ /**
+ * Like getParameter, but disabling encodings and workarounds specific to
+ * MIME (as opposed to HTTP).
+ */
+ AString getParameterHTTP(in ACString aHeaderVal,
+ in string aParamName,
+ in ACString aFallbackCharset,
+ in boolean aTryLocaleCharset,
+ out string aLang);
+
+ /**
+ * Given the value of a header field parameter using the encoding
+ * defined in RFC 5987, decode the value into a Unicode string, and extract
+ * the optional language parameter.
+ *
+ * <p>
+ * This function is purposefully picky; it will abort for all (most?)
+ * invalid inputs. This is by design. In particular, it does not support
+ * any character encodings other than UTF-8, in order not to promote
+ * non-interoperable usage.
+ *
+ * <p>
+ * Code that parses HTTP header fields (as opposed to MIME header fields)
+ * should use this function.
+ *
+ * @param aParamVal a header field parameter to decode.
+ * @param aLang will be set to the language part (possibly
+ * empty).
+ * @return the decoded parameter value.
+ */
+ AString decodeRFC5987Param(in ACString aParamVal,
+ out ACString aLang);
+
+ /**
+ * Given the value of a single header field (such as
+ * Content-Disposition and Content-Type) and the name of a parameter
+ * (e.g. filename, name, charset), returns the value of the parameter
+ * after decoding RFC 2231-style encoding.
+ * <p>
+ * For <strong>internal use only</strong>. The only other place where
+ * this needs to be invoked is |MimeHeaders_get_parameter| in
+ * mailnews/mime/src/mimehdrs.cpp defined as
+ * char * MimeHeaders_get_parameter (const char *header_value,
+ * const char *parm_name,
+ * char **charset, char **language)
+ *
+ * Otherwise, this method would have been made static.
+ *
+ * @param aHeaderVal a header string to get the value of a parameter from.
+ * @param aParamName the name of a MIME header parameter (e.g.
+ * filename, name, charset). If empty, returns
+ * the first (possibly) _unnamed_ 'parameter'.
+ * @param aCharset If non-null, it gets assigned a new pointer
+ * to a string containing the value of charset obtained
+ * from RFC 2231 parsing. Caller has to free it.
+ * @param aLang If non-null, it gets assigned a new pointer
+ * to a string containing the value of language obtained
+ * from RFC 2231 parsing. Caller has to free it.
+ * @return the value of <code>aParamName</code> after
+ * RFC 2231 decoding but without charset conversion.
+ */
+
+ [noscript]
+ string getParameterInternal(in ACString aHeaderVal,
+ in string aParamName,
+ out string aCharset,
+ out string aLang);
+
+
+ /**
+ * Given a header value, decodes RFC 2047-style encoding and
+ * returns the decoded header value in UTF-8 if either it's
+ * RFC-2047-encoded or aDefaultCharset is given. Otherwise,
+ * returns the input header value (in whatever encoding)
+ * as it is except that RFC 822 (using backslash) quotation and
+ * CRLF (if aEatContinuation is set) are stripped away
+ * <p>
+ * For internal use only. The only other place where this needs to be
+ * invoked is <code>MIME_DecodeMimeHeader</code> in
+ * mailnews/mime/src/mimehdrs.cpp defined as
+ * char * Mime_DecodeMimeHeader(char *header_val, const char *charset,
+ * bool override, bool eatcontinuation)
+ *
+ * @param aHeaderVal a header value to decode
+ * @param aDefaultCharset MIME charset to use in place of MIME charset
+ * specified in RFC 2047 style encoding
+ * when <code>aOverrideCharset</code> is set.
+ * @param aOverrideCharset When set, overrides MIME charset specified
+ * in RFC 2047 style encoding with <code>aDefaultCharset</code>
+ * @param aEatContinuation When set, removes CR/LF
+ * @return decoded header value
+ */
+ [noscript]
+ ACString decodeRFC2047Header(in string aHeaderVal,
+ in string aDefaultCharset,
+ in boolean aOverrideCharset,
+ in boolean aEatContinuation);
+
+
+ /**
+ * Given a header parameter, decodes RFC 2047 style encoding (if it's
+ * not obtained from RFC 2231 encoding), converts it to
+ * UTF-8 and returns the result in UTF-8 if an attempt to extract
+ * charset info. from a few different sources succeeds.
+ * Otherwise, returns the input header value (in whatever encoding)
+ * as it is except that RFC 822 (using backslash) quotation is
+ * stripped off.
+ * <p>
+ * For internal use only. The only other place where this needs to be
+ * invoked is <code>mime_decode_filename</code> in
+ * mailnews/mime/src/mimehdrs.cpp defined as
+ * char * mime_decode_filename(char *name, const char *charset,
+ * MimeDisplayOptions *opt)
+ *
+ * @param aParamValue the value of a parameter to decode and convert
+ * @param aCharset charset obtained from RFC 2231 decoding in which
+ * <code>aParamValue</code> is encoded. If null,
+ * indicates that it needs to try RFC 2047, instead.
+ * @param aDefaultCharset MIME charset to use when aCharset is null and
+ * cannot be obtained per RFC 2047 (most likely
+ * because 'bare' string is used.) Besides, it
+ * overrides aCharset/MIME charset obtained from
+ * RFC 2047 if <code>aOverrideCharset</code> is set.
+ * @param aOverrideCharset When set, overrides MIME charset specified
+ * in RFC 2047 style encoding with
+ * <code>aDefaultCharset</code>
+ * @return decoded parameter
+ */
+
+ [noscript]
+ ACString decodeParameter(in ACString aParamValue,
+ in string aCharset,
+ in string aDefaultCharset,
+ in boolean aOverrideCharset);
+};
diff --git a/netwerk/mime/nsIMIMEInfo.idl b/netwerk/mime/nsIMIMEInfo.idl
new file mode 100644
index 0000000000..a7ffcfe513
--- /dev/null
+++ b/netwerk/mime/nsIMIMEInfo.idl
@@ -0,0 +1,369 @@
+/* -*- 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 nsIURI;
+interface nsIFile;
+interface nsIUTF8StringEnumerator;
+interface nsIHandlerApp;
+interface nsIArray;
+interface nsIMutableArray;
+interface nsIInterfaceRequestor;
+webidl BrowsingContext;
+
+typedef long nsHandlerInfoAction;
+
+/**
+ * nsIHandlerInfo gives access to the information about how a given protocol
+ * scheme or MIME-type is handled.
+ */
+[scriptable, uuid(325e56a7-3762-4312-aec7-f1fcf84b4145)]
+interface nsIHandlerInfo : nsISupports {
+ /**
+ * The type of this handler info. For MIME handlers, this is the MIME type.
+ * For protocol handlers, it's the scheme.
+ *
+ * @return String representing the type.
+ */
+ readonly attribute ACString type;
+
+ /**
+ * A human readable description of the handler type
+ */
+ attribute AString description;
+
+ /**
+ * The application the user has said they want associated with this content
+ * type. This is not always guaranteed to be set!!
+ */
+ attribute nsIHandlerApp preferredApplicationHandler;
+
+ /**
+ * Applications that can handle this content type.
+ *
+ * The list will include the preferred handler, if any. Elements of this
+ * array are nsIHandlerApp objects, and this attribute will always reference
+ * an array, whether or not there are any possible handlers. If there are
+ * no possible handlers, the array will contain no elements, so just check
+ * its length (nsIArray::length) to see if there are any possible handlers.
+ */
+ readonly attribute nsIMutableArray possibleApplicationHandlers;
+
+ /**
+ * Indicates whether a default application handler exists,
+ * i.e. whether launchWithFile with action = useSystemDefault is possible
+ * and defaultDescription will contain usable information.
+ */
+ readonly attribute boolean hasDefaultHandler;
+
+ /**
+ * A pretty name description of the associated default application. Only
+ * usable if hasDefaultHandler is true.
+ */
+ readonly attribute AString defaultDescription;
+
+ /**
+ * Launches the application with the specified URI, in a way that
+ * depends on the value of preferredAction. preferredAction must be
+ * useHelperApp or useSystemDefault.
+ *
+ * @note Only the URI scheme is used to determine how to launch. This is
+ * essentially a pass-by-value operation. This means that in the case of
+ * a file: URI, the handler that is registered for file: will be launched
+ * and our code will not make any decision based on the content-type or
+ * extension, though the invoked file: handler is free to do so.
+ *
+ * @param aURI
+ * The URI to launch this application with
+ *
+ * @param aBrowsingContext
+ * The window to parent the dialog against, and, if a web handler
+ * is chosen, it is loaded in this window as well. See
+ * nsIHandlerApp.launchWithURI for more details.
+ *
+ * @throw NS_ERROR_INVALID_ARG if preferredAction is not valid for this
+ * call. Other exceptions may be thrown.
+ */
+ void launchWithURI(in nsIURI aURI,
+ [optional] in BrowsingContext aBrowsingContext);
+
+ /**
+ * preferredAction is how the user specified they would like to handle
+ * this content type: save to disk, use specified helper app, use OS
+ * default handler or handle using navigator; possible value constants
+ * listed below
+ */
+ attribute nsHandlerInfoAction preferredAction;
+
+ const long saveToDisk = 0;
+ /**
+ * Used to indicate that we know nothing about what to do with this. You
+ * could consider this to be not initialized.
+ */
+ const long alwaysAsk = 1;
+ const long useHelperApp = 2;
+ const long handleInternally = 3;
+ const long useSystemDefault = 4;
+
+ /**
+ * alwaysAskBeforeHandling: if true, we should always give the user a
+ * dialog asking how to dispose of this content.
+ */
+ attribute boolean alwaysAskBeforeHandling;
+};
+
+/**
+ * nsIMIMEInfo extends nsIHandlerInfo with a bunch of information specific to
+ * MIME content-types. There is a one-to-many relationship between MIME types
+ * and file extensions. This means that a MIMEInfo object may have multiple
+ * file extensions associated with it. However, the reverse is not true.
+ *
+ * MIMEInfo objects are generally retrieved from the MIME Service
+ * @see nsIMIMEService
+ */
+[scriptable, uuid(1c21acef-c7a1-40c6-9d40-a20480ee53a1)]
+interface nsIMIMEInfo : nsIHandlerInfo {
+ /**
+ * Gives you an array of file types associated with this type.
+ *
+ * @return Number of elements in the array.
+ * @return Array of extensions.
+ */
+ nsIUTF8StringEnumerator getFileExtensions();
+
+ /**
+ * Set File Extensions. Input is a comma delimited list of extensions.
+ */
+ void setFileExtensions(in AUTF8String aExtensions);
+
+ /**
+ * Returns whether or not the given extension is
+ * associated with this MIME info.
+ *
+ * @return TRUE if the association exists.
+ */
+ boolean extensionExists(in AUTF8String aExtension);
+
+ /**
+ * Append a given extension to the set of extensions
+ */
+ void appendExtension(in AUTF8String aExtension);
+
+ /**
+ * Returns the first extension association in
+ * the internal set of extensions.
+ *
+ * @return The first extension.
+ */
+ attribute AUTF8String primaryExtension;
+
+ /**
+ * The MIME type of this MIMEInfo.
+ *
+ * @return String representing the MIME type.
+ *
+ * @deprecated use nsIHandlerInfo::type instead.
+ */
+ readonly attribute ACString MIMEType;
+
+ /**
+ * Returns whether or not these two nsIMIMEInfos are logically
+ * equivalent.
+ *
+ * @returns PR_TRUE if the two are considered equal
+ */
+ boolean equals(in nsIMIMEInfo aMIMEInfo);
+
+ /**
+ * Returns a list of nsILocalHandlerApp objects containing
+ * handlers associated with this mimeinfo. Implemented per
+ * platform using information in this object to generate the
+ * best list. Typically used for an "open with" style user
+ * option.
+ *
+ * @return nsIArray of nsILocalHandlerApp
+ */
+ readonly attribute nsIArray possibleLocalHandlers;
+
+ /**
+ * Launches the application with the specified file, in a way that
+ * depends on the value of preferredAction. preferredAction must be
+ * useHelperApp or useSystemDefault.
+ *
+ * @param aFile The file to launch this application with.
+ *
+ * @throw NS_ERROR_INVALID_ARG if action is not valid for this function.
+ * Other exceptions may be thrown.
+ */
+ void launchWithFile(in nsIFile aFile);
+
+ /**
+ * Check if we ourselves are registered as the OS default for this type.
+ */
+ boolean isCurrentAppOSDefault();
+};
+
+/**
+ * nsIHandlerApp represents an external application that can handle content
+ * of some sort (either a MIME type or a protocol).
+ *
+ * FIXME: now that we've made nsIWebHandlerApp inherit from nsIHandlerApp,
+ * we should also try to make nsIWebContentHandlerInfo inherit from or possibly
+ * be replaced by nsIWebHandlerApp (bug 394710).
+ */
+[scriptable, uuid(8BDF20A4-9170-4548-AF52-78311A44F920)]
+interface nsIHandlerApp : nsISupports {
+
+ /**
+ * Human readable name for the handler
+ */
+ attribute AString name;
+
+ /**
+ * Detailed description for this handler. Suitable for
+ * a tooltip or short informative sentence.
+ */
+ attribute AString detailedDescription;
+
+ /**
+ * Whether or not the given handler app is logically equivalent to the
+ * invokant (i.e. they represent the same app).
+ *
+ * Two apps are the same if they are both either local or web handlers
+ * and their executables/URI templates and command line parameters are
+ * the same.
+ *
+ * @param aHandlerApp the handler app to compare to the invokant
+ *
+ * @returns true if the two are logically equivalent, false otherwise
+ */
+ boolean equals(in nsIHandlerApp aHandlerApp);
+
+ /**
+ * Launches the application with the specified URI.
+ *
+ * @param aURI
+ * The URI to launch this application with
+ *
+ * @param aBrowsingContext
+ *
+ * This represents the docshell to load the handler in and is passed
+ * through to nsIURILoader.openURI. If this parameter is null or
+ * not present, the web handler app implementation will attempt to
+ * find/create a place to load the handler and do so. As of this
+ * writing, it tries to load the web handler in a new window using
+ * nsIBrowserDOMWindow.openURI. In the future, it may attempt to
+ * have a more comprehensive strategy which could include handing
+ * off to the system default browser (bug 394479).
+ */
+ void launchWithURI(in nsIURI aURI,
+ [optional] in BrowsingContext aBrowsingContext);
+
+};
+
+/**
+ * nsILocalHandlerApp is a local OS-level executable
+ */
+[scriptable, uuid(D36B6329-52AE-4f45-80F4-B2536AE5F8B2)]
+interface nsILocalHandlerApp : nsIHandlerApp {
+
+ /**
+ * Pointer to the executable file used to handle content
+ */
+ attribute nsIFile executable;
+
+ /**
+ * Returns the current number of command line parameters.
+ */
+ readonly attribute unsigned long parameterCount;
+
+ /**
+ * Clears the current list of command line parameters.
+ */
+ void clearParameters();
+
+ /**
+ * Appends a command line parameter to the command line
+ * parameter list.
+ *
+ * @param param the parameter to add.
+ */
+ void appendParameter(in AString param);
+
+ /**
+ * Retrieves a specific command line parameter.
+ *
+ * @param param the index of the parameter to return.
+ *
+ * @return the parameter string.
+ *
+ * @throw NS_ERROR_INVALID_ARG if the index is out of range.
+ */
+ AString getParameter(in unsigned long parameterIndex);
+
+ /**
+ * Checks to see if a parameter exists in the command line
+ * parameter list.
+ *
+ * @param param the parameter to search for.
+ *
+ * @return TRUE if the parameter exists in the current list.
+ */
+ boolean parameterExists(in AString param);
+};
+
+/**
+ * nsIWebHandlerApp is a web-based handler, as speced by the WhatWG HTML5
+ * draft. Currently, only GET-based handlers are supported. At some point,
+ * we probably want to work with WhatWG to spec out and implement POST-based
+ * handlers as well.
+ */
+[scriptable, uuid(7521a093-c498-45ce-b462-df7ba0d882f6)]
+interface nsIWebHandlerApp : nsIHandlerApp {
+
+ /**
+ * Template used to construct the URI to GET. Template is expected to have
+ * a %s in it, and the escaped URI to be handled is inserted in place of
+ * that %s, as per the HTML5 spec.
+ */
+ attribute AUTF8String uriTemplate;
+};
+
+/**
+ * nsIDBusHandlerApp represents local applications launched by DBus a message
+ * invoking a method taking a single string argument descibing a URI
+ */
+[scriptable, uuid(1ffc274b-4cbf-4bb5-a635-05ad2cbb6534)]
+interface nsIDBusHandlerApp : nsIHandlerApp {
+
+ /**
+ * Service defines the dbus service that should handle this protocol.
+ * If its not set, NS_ERROR_FAILURE will be returned by LaunchWithURI
+ */
+ attribute AUTF8String service;
+
+ /**
+ * Objpath defines the object path of the dbus service that should handle
+ * this protocol. If its not set, NS_ERROR_FAILURE will be returned
+ * by LaunchWithURI
+ */
+ attribute AUTF8String objectPath;
+
+ /**
+ * DBusInterface defines the interface of the dbus service that should
+ * handle this protocol. If its not set, NS_ERROR_FAILURE will be
+ * returned by LaunchWithURI
+ */
+ attribute AUTF8String dBusInterface;
+
+ /**
+ * Method defines the dbus method that should be invoked to handle this
+ * protocol. If its not set, NS_ERROR_FAILURE will be returned by
+ * LaunchWithURI
+ */
+ attribute AUTF8String method;
+
+};
diff --git a/netwerk/mime/nsIMIMEService.idl b/netwerk/mime/nsIMIMEService.idl
new file mode 100644
index 0000000000..89cda79ee5
--- /dev/null
+++ b/netwerk/mime/nsIMIMEService.idl
@@ -0,0 +1,258 @@
+/* -*- 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 nsIFile;
+interface nsIMIMEInfo;
+interface nsIURI;
+interface nsIChannel;
+
+%{C++
+#define NS_MIMESERVICE_CID \
+{ /* 03af31da-3109-11d3-8cd0-0060b0fc14a3 */ \
+ 0x03af31da, \
+ 0x3109, \
+ 0x11d3, \
+ {0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \
+}
+%}
+
+/**
+ * The MIME service is responsible for mapping file extensions to MIME-types
+ * (see RFC 2045). It also provides access to nsIMIMEInfo interfaces and
+ * acts as a general convenience wrapper of nsIMIMEInfo interfaces.
+ *
+ * The MIME service maintains a database with a <b>one</b> MIME type <b>to many</b>
+ * file extensions rule. Adding the same file extension to multiple MIME types
+ * is illegal and behavior is undefined.
+ *
+ * @see nsIMIMEInfo
+ */
+[scriptable, main_process_scriptable_only, uuid(5b3675a1-02db-4f8f-a560-b34736635f47)]
+interface nsIMIMEService : nsISupports {
+ /**
+ * Retrieves an nsIMIMEInfo using both the extension
+ * and the type of a file. The type is given preference
+ * during the lookup. One of aMIMEType and aFileExt
+ * can be an empty string. At least one of aMIMEType and aFileExt
+ * must be nonempty.
+ */
+ nsIMIMEInfo getFromTypeAndExtension(in ACString aMIMEType, in AUTF8String aFileExt);
+
+ /**
+ * Retrieves a ACString representation of the MIME type
+ * associated with this file extension.
+ *
+ * @param A file extension (excluding the dot ('.')).
+ * @return The MIME type, if any.
+ */
+ ACString getTypeFromExtension(in AUTF8String aFileExt);
+
+ /**
+ * Retrieves a ACString representation of the MIME type
+ * associated with this URI. The association is purely
+ * file extension to MIME type based. No attempt to determine
+ * the type via server headers or byte scanning is made.
+ *
+ * @param The URI the user wants MIME info on.
+ * @return The MIME type, if any.
+ */
+ ACString getTypeFromURI(in nsIURI aURI);
+
+ /**
+ * Retrieves a ACString representation of the MIME type
+ * associated with this file extension. Only the default
+ * builtin list is examined. Unless you need a restricted
+ * set use getTypeFromURI.
+ *
+ * @param The URI the user wants MIME info on.
+ * @return The MIME type, if any.
+ */
+ ACString getDefaultTypeFromURI(in nsIURI aURI);
+
+ //
+ ACString getTypeFromFile(in nsIFile aFile);
+
+ /**
+ * Given a Type/Extension combination, returns the default extension
+ * for this type. This may be identical to the passed-in extension.
+ *
+ * @param aMIMEType The Type to get information on. Must not be empty.
+ * @param aFileExt File Extension. Can be empty.
+ */
+ AUTF8String getPrimaryExtension(in ACString aMIMEType, in AUTF8String aFileExt);
+
+ /*
+ * Returns an nsIMIMEInfo for the provided MIME type and extension
+ * obtained from an OS lookup. If no handler is found for the type and
+ * extension, returns a generic nsIMIMEInfo object. The MIME type and
+ * extension can be the empty string. When the type and extension don't
+ * map to the same handler, the semantics/resolution are platform
+ * specific. See the platform implementations for details.
+ *
+ * @param aType The MIME type to get handler information for.
+ * @param aFileExtension The filename extension to use either alone
+ * or with the MIME type to get handler information
+ * for. UTF-8 encoded.
+ * @param [out] aFound Out param indicating whether a MIMEInfo could
+ * be found for the provided type and/or extension.
+ * Set to false when neither extension nor the MIME
+ * type are mapped to a handler.
+ * @return A nsIMIMEInfo object. This function must return
+ * a MIMEInfo object if it can allocate one. The
+ * only justifiable reason for not returning one is
+ * an out-of-memory error.
+ */
+ nsIMIMEInfo getMIMEInfoFromOS(in ACString aType,
+ in ACString aFileExtension,
+ out boolean aFound);
+
+ /**
+ * Update the mime info's default app information based on OS
+ * lookups.
+ * Note: normally called automatically by nsIMIMEInfo. If you find
+ * yourself needing to call this from elsewhere, file a bug instead.
+ */
+ void updateDefaultAppInfo(in nsIMIMEInfo aMIMEInfo);
+
+ /**
+ * Default filename validation for getValidFileName and
+ * validateFileNameForSaving where other flags are not true.
+ * That is, the extension is modified to fit the content type,
+ * duplicate whitespace is collapsed, and long filenames are
+ * truncated. A valid content type must be supplied. See the
+ * description of getValidFileName for more details about how
+ * the flags are used.
+ */
+ const long VALIDATE_DEFAULT = 0;
+
+ /**
+ * If true, then the filename is only validated to ensure that it is
+ * acceptable for the file system. If false, then the extension is also
+ * checked to ensure that it is valid for the content type. If the
+ * extension is not valid, the filename is modified to have the proper
+ * extension.
+ */
+ const long VALIDATE_SANITIZE_ONLY = 1;
+
+ /**
+ * Don't collapse strings of duplicate whitespace into a single string.
+ */
+ const long VALIDATE_DONT_COLLAPSE_WHITESPACE = 2;
+
+ /**
+ * Don't truncate long filenames.
+ */
+ const long VALIDATE_DONT_TRUNCATE = 4;
+
+ /**
+ * True to ignore the content type and guess the type from any existing
+ * extension instead. "application/octet-stream" is used as the default
+ * if there is no extension or there is no information available for
+ * the extension.
+ */
+ const long VALIDATE_GUESS_FROM_EXTENSION = 8;
+
+ /**
+ * If the filename is empty, return the empty filename
+ * without modification.
+ */
+ const long VALIDATE_ALLOW_EMPTY = 16;
+
+ /**
+ * Don't apply a default filename if the non-extension portion of the
+ * filename is empty.
+ */
+ const long VALIDATE_NO_DEFAULT_FILENAME = 32;
+
+ /**
+ * When the filename has an invalid extension, force the the filename to
+ * have a valid extension appended to the end of the filename when that
+ * extension would normally be ignored for the given content type. This
+ * primarily is used when saving pages to ensure that the html extension
+ * is applied over any extension that might have been generated from a
+ * page title.
+ */
+ const long VALIDATE_FORCE_APPEND_EXTENSION = 64;
+
+ /**
+ * Don't modify filenames or extensions that might be invalid or dangerous
+ * on some platforms. If this flag is not used, these filenames will be
+ * modified so that the operating system does not treat them specially.
+ */
+ const long VALIDATE_ALLOW_INVALID_FILENAMES = 128;
+
+ /**
+ * Generate a valid filename from the channel that can be used to save
+ * the content of the channel to the local disk.
+ *
+ * The filename is determined from the content disposition, the filename
+ * of the uri, or a default filename. The following modifications are
+ * applied:
+ * - If the VALIDATE_SANITIZE_ONLY flag is not specified, then the
+ * extension of the filename is modified to suit the supplied content type.
+ * - Path separators (typically / and \) are replaced by underscores (_)
+ * - Characters that are not valid or would be confusing in filenames are
+ * replaced by spaces (*, :, etc)
+ * - Bidi related marks are replaced by underscores (_)
+ * - Whitespace and periods are removed from the beginning and end.
+ * - Unless VALIDATE_DONT_COLLAPSE_WHITESPACE is specified, multiple
+ * consecutive whitespace characters are collapsed to a single space
+ * character, either ' ' or an ideographic space 0x3000 if present.
+ * - Unless VALIDATE_DONT_TRUNCATE is specified, the filename is truncated
+ * to a maximum length, preserving the extension if possible.
+ * - Some filenames and extensions are invalid on certain platforms.
+ * These are replaced if possible unless VALIDATE_ALLOW_INVALID_FILENAMES
+ * is specified.
+ *
+ * If the VALIDATE_NO_DEFAULT_FILENAME flag is not specified, and after the
+ * rules above are applied, the resulting filename is empty, a default
+ * filename is used.
+ *
+ * If the VALIDATE_ALLOW_EMPTY flag is specified, an empty string may be
+ * returned only if the filename could not be determined or was blank.
+ *
+ * If either the VALIDATE_SANITIZE_ONLY or VALIDATE_GUESS_FROM_EXTENSION flags
+ * are specified, then the content type may be empty. Otherwise, the type must
+ * not be empty.
+ *
+ * The aOriginalURI would be specified if the channel is for a local file but
+ * it was originally sourced from a different uri.
+ *
+ * When saving an image, use validateFileNameForSaving instead and
+ * pass the result of imgIRequest::GetFileName() as the filename to
+ * check.
+ *
+ * @param aChannel The channel of the content to save.
+ * @param aType The MIME type to use, which would usually be the
+ * same as the content type of the channel.
+ * @param aOriginalURL the source url of the file, but may be null.
+ * @param aFlags one or more of the flags above.
+ * @returns The resulting filename.
+ */
+ AString getValidFileName(in nsIChannel aChannel,
+ in ACString aType,
+ in nsIURI aOriginalURI,
+ in unsigned long aFlags);
+
+ /**
+ * Similar to getValidFileName, but used when a specific filename needs
+ * to be validated. The filename is modified as needed based on the
+ * content type in the same manner as getValidFileName.
+ *
+ * If the filename came from a uri, it should not be escaped, that is,
+ * any needed unescaping of the filename should happen before calling
+ * this method.
+ *
+ * @param aType The MIME type to use.
+ * @param aFlags one or more of the flags above.
+ * @param aFileName The filename to validate.
+ * @returns The validated filename.
+ */
+ AString validateFileNameForSaving(in AString aFileName,
+ in ACString aType,
+ in unsigned long aFlags);
+};
diff --git a/netwerk/mime/nsMIMEHeaderParamImpl.cpp b/netwerk/mime/nsMIMEHeaderParamImpl.cpp
new file mode 100644
index 0000000000..22cf71728b
--- /dev/null
+++ b/netwerk/mime/nsMIMEHeaderParamImpl.cpp
@@ -0,0 +1,1360 @@
+/* -*- Mode: C++; tab-width: 4; 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 <string.h>
+#include "prprf.h"
+#include "prmem.h"
+#include "plbase64.h"
+#include "nsCRT.h"
+#include "nsTArray.h"
+#include "nsEscape.h"
+#include "nsMIMEHeaderParamImpl.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+
+using mozilla::Encoding;
+using mozilla::IsAscii;
+using mozilla::IsUtf8;
+
+// static functions declared below are moved from mailnews/mime/src/comi18n.cpp
+
+static char* DecodeQ(const char*, uint32_t);
+static bool Is7bitNonAsciiString(const char*, uint32_t);
+static void CopyRawHeader(const char*, uint32_t, const nsACString&,
+ nsACString&);
+static nsresult DecodeRFC2047Str(const char*, const nsACString&, bool,
+ nsACString&);
+static nsresult internalDecodeParameter(const nsACString&, const nsACString&,
+ const nsACString&, bool, bool,
+ nsACString&);
+
+static nsresult ToUTF8(const nsACString& aString, const nsACString& aCharset,
+ bool aAllowSubstitution, nsACString& aResult) {
+ if (aCharset.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const auto* encoding = Encoding::ForLabelNoReplacement(aCharset);
+ if (!encoding) {
+ return NS_ERROR_UCONV_NOCONV;
+ }
+ if (aAllowSubstitution) {
+ nsresult rv = encoding->DecodeWithoutBOMHandling(aString, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ return rv;
+ }
+ return encoding->DecodeWithoutBOMHandlingAndWithoutReplacement(aString,
+ aResult);
+}
+
+static nsresult ConvertStringToUTF8(const nsACString& aString,
+ const nsACString& aCharset, bool aSkipCheck,
+ bool aAllowSubstitution,
+ nsACString& aUTF8String) {
+ // return if ASCII only or valid UTF-8 providing that the ASCII/UTF-8
+ // check is requested. It may not be asked for if a caller suspects
+ // that the input is in non-ASCII 7bit charset (ISO-2022-xx, HZ) or
+ // it's in a charset other than UTF-8 that can be mistaken for UTF-8.
+ if (!aSkipCheck && (IsAscii(aString) || IsUtf8(aString))) {
+ aUTF8String = aString;
+ return NS_OK;
+ }
+
+ aUTF8String.Truncate();
+
+ nsresult rv = ToUTF8(aString, aCharset, aAllowSubstitution, aUTF8String);
+
+ // additional protection for cases where check is skipped and the input
+ // is actually in UTF-8 as opposed to aCharset. (i.e. caller's hunch
+ // was wrong.) We don't check ASCIIness assuming there's no charset
+ // incompatible with ASCII (we don't support EBCDIC).
+ if (aSkipCheck && NS_FAILED(rv) && IsUtf8(aString)) {
+ aUTF8String = aString;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+// XXX The chance of UTF-7 being used in the message header is really
+// low, but in theory it's possible.
+#define IS_7BIT_NON_ASCII_CHARSET(cset) \
+ (!nsCRT::strncasecmp((cset), "ISO-2022", 8) || \
+ !nsCRT::strncasecmp((cset), "HZ-GB", 5) || \
+ !nsCRT::strncasecmp((cset), "UTF-7", 5))
+
+NS_IMPL_ISUPPORTS(nsMIMEHeaderParamImpl, nsIMIMEHeaderParam)
+
+NS_IMETHODIMP
+nsMIMEHeaderParamImpl::GetParameter(const nsACString& aHeaderVal,
+ const char* aParamName,
+ const nsACString& aFallbackCharset,
+ bool aTryLocaleCharset, char** aLang,
+ nsAString& aResult) {
+ return DoGetParameter(aHeaderVal, aParamName, MIME_FIELD_ENCODING,
+ aFallbackCharset, aTryLocaleCharset, aLang, aResult);
+}
+
+NS_IMETHODIMP
+nsMIMEHeaderParamImpl::GetParameterHTTP(const nsACString& aHeaderVal,
+ const char* aParamName,
+ const nsACString& aFallbackCharset,
+ bool aTryLocaleCharset, char** aLang,
+ nsAString& aResult) {
+ return DoGetParameter(aHeaderVal, aParamName, HTTP_FIELD_ENCODING,
+ aFallbackCharset, aTryLocaleCharset, aLang, aResult);
+}
+
+/* static */
+nsresult nsMIMEHeaderParamImpl::GetParameterHTTP(const nsACString& aHeaderVal,
+ const char* aParamName,
+ nsAString& aResult) {
+ return DoGetParameter(aHeaderVal, aParamName, HTTP_FIELD_ENCODING, ""_ns,
+ false, nullptr, aResult);
+}
+
+/* static */
+// detects any non-null characters pass null
+bool nsMIMEHeaderParamImpl::ContainsTrailingCharPastNull(
+ const nsACString& aVal) {
+ nsACString::const_iterator first;
+ aVal.BeginReading(first);
+ nsACString::const_iterator end;
+ aVal.EndReading(end);
+
+ if (FindCharInReadable(L'\0', first, end)) {
+ while (first != end) {
+ if (*first != '\0') {
+ // contains trailing characters past the null character
+ return true;
+ }
+ ++first;
+ }
+ }
+ return false;
+}
+
+// XXX : aTryLocaleCharset is not yet effective.
+/* static */
+nsresult nsMIMEHeaderParamImpl::DoGetParameter(
+ const nsACString& aHeaderVal, const char* aParamName,
+ ParamDecoding aDecoding, const nsACString& aFallbackCharset,
+ bool aTryLocaleCharset, char** aLang, nsAString& aResult) {
+ aResult.Truncate();
+ nsresult rv;
+
+ // get parameter (decode RFC 2231/5987 when applicable, as specified by
+ // aDecoding (5987 being a subset of 2231) and return charset.)
+ nsCString med;
+ nsCString charset;
+ rv = DoParameterInternal(aHeaderVal, aParamName, aDecoding,
+ getter_Copies(charset), aLang, getter_Copies(med));
+ if (NS_FAILED(rv)) return rv;
+
+ // convert to UTF-8 after charset conversion and RFC 2047 decoding
+ // if necessary.
+
+ nsAutoCString str1;
+ rv = internalDecodeParameter(med, charset, ""_ns, false,
+ // was aDecoding == MIME_FIELD_ENCODING
+ // see bug 875615
+ true, str1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aFallbackCharset.IsEmpty()) {
+ const Encoding* encoding = Encoding::ForLabel(aFallbackCharset);
+ nsAutoCString str2;
+ if (NS_SUCCEEDED(ConvertStringToUTF8(str1, aFallbackCharset, false,
+ encoding != UTF_8_ENCODING, str2))) {
+ CopyUTF8toUTF16(str2, aResult);
+ return NS_OK;
+ }
+ }
+
+ if (IsUtf8(str1)) {
+ CopyUTF8toUTF16(str1, aResult);
+ return NS_OK;
+ }
+
+ if (aTryLocaleCharset && !NS_IsNativeUTF8()) {
+ return NS_CopyNativeToUnicode(str1, aResult);
+ }
+
+ CopyASCIItoUTF16(str1, aResult);
+ return NS_OK;
+}
+
+// remove backslash-encoded sequences from quoted-strings
+// modifies string in place, potentially shortening it
+void RemoveQuotedStringEscapes(char* src) {
+ char* dst = src;
+
+ for (char* c = src; *c; ++c) {
+ if (c[0] == '\\' && c[1]) {
+ // skip backslash if not at end
+ ++c;
+ }
+ *dst++ = *c;
+ }
+ *dst = 0;
+}
+
+// true is character is a hex digit
+bool IsHexDigit(char aChar) {
+ char c = aChar;
+
+ return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ||
+ (c >= '0' && c <= '9');
+}
+
+// validate that a C String containing %-escapes is syntactically valid
+bool IsValidPercentEscaped(const char* aValue, int32_t len) {
+ for (int32_t i = 0; i < len; i++) {
+ if (aValue[i] == '%') {
+ if (!IsHexDigit(aValue[i + 1]) || !IsHexDigit(aValue[i + 2])) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Support for continuations (RFC 2231, Section 3)
+
+// only a sane number supported
+#define MAX_CONTINUATIONS 999
+
+// part of a continuation
+
+class Continuation {
+ public:
+ Continuation(const char* aValue, uint32_t aLength, bool aNeedsPercentDecoding,
+ bool aWasQuotedString) {
+ value = aValue;
+ length = aLength;
+ needsPercentDecoding = aNeedsPercentDecoding;
+ wasQuotedString = aWasQuotedString;
+ }
+ Continuation() {
+ // empty constructor needed for nsTArray
+ value = nullptr;
+ length = 0;
+ needsPercentDecoding = false;
+ wasQuotedString = false;
+ }
+ ~Continuation() = default;
+
+ const char* value;
+ uint32_t length;
+ bool needsPercentDecoding;
+ bool wasQuotedString;
+};
+
+// combine segments into a single string, returning the allocated string
+// (or nullptr) while emptying the list
+char* combineContinuations(nsTArray<Continuation>& aArray) {
+ // Sanity check
+ if (aArray.Length() == 0) return nullptr;
+
+ // Get an upper bound for the length
+ uint32_t length = 0;
+ for (uint32_t i = 0; i < aArray.Length(); i++) {
+ length += aArray[i].length;
+ }
+
+ // Allocate
+ char* result = (char*)moz_xmalloc(length + 1);
+
+ // Concatenate
+ *result = '\0';
+
+ for (uint32_t i = 0; i < aArray.Length(); i++) {
+ Continuation cont = aArray[i];
+ if (!cont.value) break;
+
+ char* c = result + strlen(result);
+ strncat(result, cont.value, cont.length);
+ if (cont.needsPercentDecoding) {
+ nsUnescape(c);
+ }
+ if (cont.wasQuotedString) {
+ RemoveQuotedStringEscapes(c);
+ }
+ }
+
+ // return null if empty value
+ if (*result == '\0') {
+ free(result);
+ result = nullptr;
+ }
+
+ return result;
+}
+
+// add a continuation, return false on error if segment already has been seen
+bool addContinuation(nsTArray<Continuation>& aArray, uint32_t aIndex,
+ const char* aValue, uint32_t aLength,
+ bool aNeedsPercentDecoding, bool aWasQuotedString) {
+ if (aIndex < aArray.Length() && aArray[aIndex].value) {
+ NS_WARNING("duplicate RC2231 continuation segment #\n");
+ return false;
+ }
+
+ if (aIndex > MAX_CONTINUATIONS) {
+ NS_WARNING("RC2231 continuation segment # exceeds limit\n");
+ return false;
+ }
+
+ if (aNeedsPercentDecoding && aWasQuotedString) {
+ NS_WARNING(
+ "RC2231 continuation segment can't use percent encoding and quoted "
+ "string form at the same time\n");
+ return false;
+ }
+
+ Continuation cont(aValue, aLength, aNeedsPercentDecoding, aWasQuotedString);
+
+ if (aArray.Length() <= aIndex) {
+ aArray.SetLength(aIndex + 1);
+ }
+ aArray[aIndex] = cont;
+
+ return true;
+}
+
+// parse a segment number; return -1 on error
+int32_t parseSegmentNumber(const char* aValue, int32_t aLen) {
+ if (aLen < 1) {
+ NS_WARNING("segment number missing\n");
+ return -1;
+ }
+
+ if (aLen > 1 && aValue[0] == '0') {
+ NS_WARNING("leading '0' not allowed in segment number\n");
+ return -1;
+ }
+
+ int32_t segmentNumber = 0;
+
+ for (int32_t i = 0; i < aLen; i++) {
+ if (!(aValue[i] >= '0' && aValue[i] <= '9')) {
+ NS_WARNING("invalid characters in segment number\n");
+ return -1;
+ }
+
+ segmentNumber *= 10;
+ segmentNumber += aValue[i] - '0';
+ if (segmentNumber > MAX_CONTINUATIONS) {
+ NS_WARNING("Segment number exceeds sane size\n");
+ return -1;
+ }
+ }
+
+ return segmentNumber;
+}
+
+// validate a given octet sequence for compliance with the specified
+// encoding
+bool IsValidOctetSequenceForCharset(const nsACString& aCharset,
+ const char* aOctets) {
+ nsAutoCString tmpRaw;
+ tmpRaw.Assign(aOctets);
+ nsAutoCString tmpDecoded;
+
+ nsresult rv = ConvertStringToUTF8(tmpRaw, aCharset, false, false, tmpDecoded);
+
+ if (rv != NS_OK) {
+ // we can't decode; charset may be unsupported, or the octet sequence
+ // is broken (illegal or incomplete octet sequence contained)
+ NS_WARNING(
+ "RFC2231/5987 parameter value does not decode according to specified "
+ "charset\n");
+ return false;
+ }
+
+ return true;
+}
+
+// moved almost verbatim from mimehdrs.cpp
+// char *
+// MimeHeaders_get_parameter (const char *header_value, const char *parm_name,
+// char **charset, char **language)
+//
+// The format of these header lines is
+// <token> [ ';' <token> '=' <token-or-quoted-string> ]*
+NS_IMETHODIMP
+nsMIMEHeaderParamImpl::GetParameterInternal(const nsACString& aHeaderValue,
+ const char* aParamName,
+ char** aCharset, char** aLang,
+ char** aResult) {
+ return DoParameterInternal(aHeaderValue, aParamName, MIME_FIELD_ENCODING,
+ aCharset, aLang, aResult);
+}
+
+/* static */
+nsresult nsMIMEHeaderParamImpl::DoParameterInternal(
+ const nsACString& aHeaderValue, const char* aParamName,
+ ParamDecoding aDecoding, char** aCharset, char** aLang, char** aResult) {
+ if (aHeaderValue.IsEmpty() || !aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (ContainsTrailingCharPastNull(aHeaderValue)) {
+ // See Bug 1784348
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const nsCString& flat = PromiseFlatCString(aHeaderValue);
+ const char* str = flat.get();
+
+ if (!*str) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = nullptr;
+
+ if (aCharset) *aCharset = nullptr;
+ if (aLang) *aLang = nullptr;
+
+ nsAutoCString charset;
+
+ // change to (aDecoding != HTTP_FIELD_ENCODING) when we want to disable
+ // them for HTTP header fields later on, see bug 776324
+ bool acceptContinuations = true;
+
+ // skip leading white space.
+ for (; *str && nsCRT::IsAsciiSpace(*str); ++str) {
+ ;
+ }
+ const char* start = str;
+
+ // aParamName is empty. return the first (possibly) _unnamed_ 'parameter'
+ // For instance, return 'inline' in the following case:
+ // Content-Disposition: inline; filename=.....
+ if (!aParamName || !*aParamName) {
+ for (; *str && *str != ';' && !nsCRT::IsAsciiSpace(*str); ++str) {
+ ;
+ }
+ if (str == start) return NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY;
+
+ *aResult = (char*)moz_xmemdup(start, (str - start) + 1);
+ (*aResult)[str - start] = '\0'; // null-terminate
+ return NS_OK;
+ }
+
+ /* Skip forward to first ';' */
+ for (; *str && *str != ';' && *str != ','; ++str) {
+ ;
+ }
+ if (*str) str++;
+ /* Skip over following whitespace */
+ for (; *str && nsCRT::IsAsciiSpace(*str); ++str) {
+ ;
+ }
+
+ // Some broken http servers just specify parameters
+ // like 'filename' without specifying disposition
+ // method. Rewind to the first non-white-space
+ // character.
+
+ if (!*str) str = start;
+
+ // RFC2231 - The legitimate parm format can be:
+ // A. title=ThisIsTitle
+ // B. title*=us-ascii'en-us'This%20is%20wierd.
+ // C. title*0*=us-ascii'en'This%20is%20wierd.%20We
+ // title*1*=have%20to%20support%20this.
+ // title*2="Else..."
+ // D. title*0="Hey, what you think you are doing?"
+ // title*1="There is no charset and lang info."
+ // RFC5987: only A and B
+
+ // collect results for the different algorithms (plain filename,
+ // RFC5987/2231-encoded filename, + continuations) separately and decide
+ // which to use at the end
+ char* caseAResult = nullptr;
+ char* caseBResult = nullptr;
+ char* caseCDResult = nullptr;
+
+ // collect continuation segments
+ nsTArray<Continuation> segments;
+
+ // our copies of the charset parameter, kept separately as they might
+ // differ for the two formats
+ nsDependentCSubstring charsetB, charsetCD;
+
+ nsDependentCSubstring lang;
+
+ int32_t paramLen = strlen(aParamName);
+
+ while (*str) {
+ // find name/value
+
+ const char* nameStart = str;
+ const char* nameEnd = nullptr;
+ const char* valueStart = nullptr;
+ const char* valueEnd = nullptr;
+ bool isQuotedString = false;
+
+ NS_ASSERTION(!nsCRT::IsAsciiSpace(*str), "should be after whitespace.");
+
+ // Skip forward to the end of this token.
+ for (; *str && !nsCRT::IsAsciiSpace(*str) && *str != '=' && *str != ';';
+ str++) {
+ ;
+ }
+ nameEnd = str;
+
+ int32_t nameLen = nameEnd - nameStart;
+
+ // Skip over whitespace, '=', and whitespace
+ while (nsCRT::IsAsciiSpace(*str)) ++str;
+ if (!*str) {
+ break;
+ }
+ if (*str != '=') {
+ // don't accept parameters without "="
+ goto increment_str;
+ }
+ // Skip over '=' only if it was actually there
+ str++;
+ while (nsCRT::IsAsciiSpace(*str)) ++str;
+
+ if (*str != '"') {
+ // The value is a token, not a quoted string.
+ valueStart = str;
+ for (valueEnd = str; *valueEnd && *valueEnd != ';'; valueEnd++) {
+ ;
+ }
+ // ignore trailing whitespace:
+ while (valueEnd > valueStart && nsCRT::IsAsciiSpace(*(valueEnd - 1))) {
+ valueEnd--;
+ }
+ str = valueEnd;
+ } else {
+ isQuotedString = true;
+
+ ++str;
+ valueStart = str;
+ for (valueEnd = str; *valueEnd; ++valueEnd) {
+ if (*valueEnd == '\\' && *(valueEnd + 1)) {
+ ++valueEnd;
+ } else if (*valueEnd == '"') {
+ break;
+ }
+ }
+ str = valueEnd;
+ // *valueEnd != null means that *valueEnd is quote character.
+ if (*valueEnd) str++;
+ }
+
+ // See if this is the simplest case (case A above),
+ // a 'single' line value with no charset and lang.
+ // If so, copy it and return.
+ if (nameLen == paramLen &&
+ !nsCRT::strncasecmp(nameStart, aParamName, paramLen)) {
+ if (caseAResult) {
+ // we already have one caseA result, ignore subsequent ones
+ goto increment_str;
+ }
+
+ // if the parameter spans across multiple lines we have to strip out the
+ // line continuation -- jht 4/29/98
+ nsAutoCString tempStr(valueStart, valueEnd - valueStart);
+ tempStr.StripCRLF();
+ char* res = ToNewCString(tempStr, mozilla::fallible);
+ NS_ENSURE_TRUE(res, NS_ERROR_OUT_OF_MEMORY);
+
+ if (isQuotedString) RemoveQuotedStringEscapes(res);
+
+ caseAResult = res;
+ // keep going, we may find a RFC 2231/5987 encoded alternative
+ }
+ // case B, C, and D
+ else if (nameLen > paramLen &&
+ !nsCRT::strncasecmp(nameStart, aParamName, paramLen) &&
+ *(nameStart + paramLen) == '*') {
+ // 1st char past '*'
+ const char* cp = nameStart + paramLen + 1;
+
+ // if param name ends in "*" we need do to RFC5987 "ext-value" decoding
+ bool needExtDecoding = *(nameEnd - 1) == '*';
+
+ bool caseB = nameLen == paramLen + 1;
+ bool caseCStart = (*cp == '0') && needExtDecoding;
+
+ // parse the segment number
+ int32_t segmentNumber = -1;
+ if (!caseB) {
+ int32_t segLen = (nameEnd - cp) - (needExtDecoding ? 1 : 0);
+ segmentNumber = parseSegmentNumber(cp, segLen);
+
+ if (segmentNumber == -1) {
+ acceptContinuations = false;
+ goto increment_str;
+ }
+ }
+
+ // CaseB and start of CaseC: requires charset and optional language
+ // in quotes (quotes required even if lang is blank)
+ if (caseB || (caseCStart && acceptContinuations)) {
+ // look for single quotation mark(')
+ const char* sQuote1 = strchr(valueStart, 0x27);
+ const char* sQuote2 = sQuote1 ? strchr(sQuote1 + 1, 0x27) : nullptr;
+
+ // Two single quotation marks must be present even in
+ // absence of charset and lang.
+ if (!sQuote1 || !sQuote2) {
+ NS_WARNING(
+ "Mandatory two single quotes are missing in header parameter\n");
+ }
+
+ const char* charsetStart = nullptr;
+ int32_t charsetLength = 0;
+ const char* langStart = nullptr;
+ int32_t langLength = 0;
+ const char* rawValStart = nullptr;
+ int32_t rawValLength = 0;
+
+ if (sQuote2 && sQuote1) {
+ // both delimiters present: charSet'lang'rawVal
+ rawValStart = sQuote2 + 1;
+ rawValLength = valueEnd - rawValStart;
+
+ langStart = sQuote1 + 1;
+ langLength = sQuote2 - langStart;
+
+ charsetStart = valueStart;
+ charsetLength = sQuote1 - charsetStart;
+ } else if (sQuote1) {
+ // one delimiter; assume charset'rawVal
+ rawValStart = sQuote1 + 1;
+ rawValLength = valueEnd - rawValStart;
+
+ charsetStart = valueStart;
+ charsetLength = sQuote1 - valueStart;
+ } else {
+ // no delimiter: just rawVal
+ rawValStart = valueStart;
+ rawValLength = valueEnd - valueStart;
+ }
+
+ if (langLength != 0) {
+ lang.Assign(langStart, langLength);
+ }
+
+ // keep the charset for later
+ if (caseB) {
+ charsetB.Assign(charsetStart, charsetLength);
+ } else {
+ // if caseCorD
+ charsetCD.Assign(charsetStart, charsetLength);
+ }
+
+ // non-empty value part
+ if (rawValLength > 0) {
+ if (!caseBResult && caseB) {
+ if (!IsValidPercentEscaped(rawValStart, rawValLength)) {
+ goto increment_str;
+ }
+
+ // allocate buffer for the raw value
+ char* tmpResult = (char*)moz_xmemdup(rawValStart, rawValLength + 1);
+ *(tmpResult + rawValLength) = 0;
+
+ nsUnescape(tmpResult);
+ caseBResult = tmpResult;
+ } else {
+ // caseC
+ bool added = addContinuation(segments, 0, rawValStart, rawValLength,
+ needExtDecoding, isQuotedString);
+
+ if (!added) {
+ // continuation not added, stop processing them
+ acceptContinuations = false;
+ }
+ }
+ }
+ } // end of if-block : title*0*= or title*=
+ // caseD: a line of multiline param with no need for unescaping :
+ // title*[0-9]= or 2nd or later lines of a caseC param : title*[1-9]*=
+ else if (acceptContinuations && segmentNumber != -1) {
+ uint32_t valueLength = valueEnd - valueStart;
+
+ bool added =
+ addContinuation(segments, segmentNumber, valueStart, valueLength,
+ needExtDecoding, isQuotedString);
+
+ if (!added) {
+ // continuation not added, stop processing them
+ acceptContinuations = false;
+ }
+ } // end of if-block : title*[0-9]= or title*[1-9]*=
+ }
+
+ // str now points after the end of the value.
+ // skip over whitespace, ';', whitespace.
+ increment_str:
+ while (nsCRT::IsAsciiSpace(*str)) ++str;
+ if (*str == ';') {
+ ++str;
+ } else {
+ // stop processing the header field; either we are done or the
+ // separator was missing
+ break;
+ }
+ while (nsCRT::IsAsciiSpace(*str)) ++str;
+ }
+
+ caseCDResult = combineContinuations(segments);
+
+ if (caseBResult && !charsetB.IsEmpty()) {
+ // check that the 2231/5987 result decodes properly given the
+ // specified character set
+ if (!IsValidOctetSequenceForCharset(charsetB, caseBResult)) {
+ caseBResult = nullptr;
+ }
+ }
+
+ if (caseCDResult && !charsetCD.IsEmpty()) {
+ // check that the 2231/5987 result decodes properly given the
+ // specified character set
+ if (!IsValidOctetSequenceForCharset(charsetCD, caseCDResult)) {
+ caseCDResult = nullptr;
+ }
+ }
+
+ if (caseBResult) {
+ // prefer simple 5987 format over 2231 with continuations
+ *aResult = caseBResult;
+ caseBResult = nullptr;
+ charset.Assign(charsetB);
+ } else if (caseCDResult) {
+ // prefer 2231/5987 with or without continuations over plain format
+ *aResult = caseCDResult;
+ caseCDResult = nullptr;
+ charset.Assign(charsetCD);
+ } else if (caseAResult) {
+ *aResult = caseAResult;
+ caseAResult = nullptr;
+ }
+
+ // free unused stuff
+ free(caseAResult);
+ free(caseBResult);
+ free(caseCDResult);
+
+ // if we have a result
+ if (*aResult) {
+ // then return charset and lang as well
+ if (aLang && !lang.IsEmpty()) {
+ uint32_t len = lang.Length();
+ *aLang = (char*)moz_xmemdup(lang.BeginReading(), len + 1);
+ *(*aLang + len) = 0;
+ }
+ if (aCharset && !charset.IsEmpty()) {
+ uint32_t len = charset.Length();
+ *aCharset = (char*)moz_xmemdup(charset.BeginReading(), len + 1);
+ *(*aCharset + len) = 0;
+ }
+ }
+
+ return *aResult ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+nsresult internalDecodeRFC2047Header(const char* aHeaderVal,
+ const nsACString& aDefaultCharset,
+ bool aOverrideCharset,
+ bool aEatContinuations,
+ nsACString& aResult) {
+ aResult.Truncate();
+ if (!aHeaderVal) return NS_ERROR_INVALID_ARG;
+ if (!*aHeaderVal) return NS_OK;
+
+ // If aHeaderVal is RFC 2047 encoded or is not a UTF-8 string but
+ // aDefaultCharset is specified, decodes RFC 2047 encoding and converts
+ // to UTF-8. Otherwise, just strips away CRLF.
+ if (strstr(aHeaderVal, "=?") ||
+ (!aDefaultCharset.IsEmpty() &&
+ (!IsUtf8(nsDependentCString(aHeaderVal)) ||
+ Is7bitNonAsciiString(aHeaderVal, strlen(aHeaderVal))))) {
+ DecodeRFC2047Str(aHeaderVal, aDefaultCharset, aOverrideCharset, aResult);
+ } else if (aEatContinuations &&
+ (strchr(aHeaderVal, '\n') || strchr(aHeaderVal, '\r'))) {
+ aResult = aHeaderVal;
+ } else {
+ aEatContinuations = false;
+ aResult = aHeaderVal;
+ }
+
+ if (aEatContinuations) {
+ nsAutoCString temp(aResult);
+ temp.ReplaceSubstring("\n\t", " ");
+ temp.ReplaceSubstring("\r\t", " ");
+ temp.StripCRLF();
+ aResult = temp;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEHeaderParamImpl::DecodeRFC2047Header(const char* aHeaderVal,
+ const char* aDefaultCharset,
+ bool aOverrideCharset,
+ bool aEatContinuations,
+ nsACString& aResult) {
+ return internalDecodeRFC2047Header(aHeaderVal, nsCString(aDefaultCharset),
+ aOverrideCharset, aEatContinuations,
+ aResult);
+}
+
+// true if the character is allowed in a RFC 5987 value
+// see RFC 5987, Section 3.2.1, "attr-char"
+bool IsRFC5987AttrChar(char aChar) {
+ char c = aChar;
+
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ (c == '!' || c == '#' || c == '$' || c == '&' || c == '+' ||
+ c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
+ c == '|' || c == '~');
+}
+
+// percent-decode a value
+// returns false on failure
+bool PercentDecode(nsACString& aValue) {
+ char* c = (char*)moz_xmalloc(aValue.Length() + 1);
+
+ strcpy(c, PromiseFlatCString(aValue).get());
+ nsUnescape(c);
+ aValue.Assign(c);
+ free(c);
+
+ return true;
+}
+
+// Decode a parameter value using the encoding defined in RFC 5987
+//
+// charset "'" [ language ] "'" value-chars
+NS_IMETHODIMP
+nsMIMEHeaderParamImpl::DecodeRFC5987Param(const nsACString& aParamVal,
+ nsACString& aLang,
+ nsAString& aResult) {
+ nsAutoCString charset;
+ nsAutoCString language;
+ nsAutoCString value;
+
+ uint32_t delimiters = 0;
+ const nsCString& encoded = PromiseFlatCString(aParamVal);
+ const char* c = encoded.get();
+
+ while (*c) {
+ char tc = *c++;
+
+ if (tc == '\'') {
+ // single quote
+ delimiters++;
+ } else if (((unsigned char)tc) >= 128) {
+ // fail early, not ASCII
+ NS_WARNING("non-US-ASCII character in RFC5987-encoded param");
+ return NS_ERROR_INVALID_ARG;
+ } else {
+ if (delimiters == 0) {
+ // valid characters are checked later implicitly
+ charset.Append(tc);
+ } else if (delimiters == 1) {
+ // no value checking for now
+ language.Append(tc);
+ } else if (delimiters == 2) {
+ if (IsRFC5987AttrChar(tc)) {
+ value.Append(tc);
+ } else if (tc == '%') {
+ if (!IsHexDigit(c[0]) || !IsHexDigit(c[1])) {
+ // we expect two more characters
+ NS_WARNING("broken %-escape in RFC5987-encoded param");
+ return NS_ERROR_INVALID_ARG;
+ }
+ value.Append(tc);
+ // we consume two more
+ value.Append(*c++);
+ value.Append(*c++);
+ } else {
+ // character not allowed here
+ NS_WARNING("invalid character in RFC5987-encoded param");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ if (delimiters != 2) {
+ NS_WARNING("missing delimiters in RFC5987-encoded param");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // abort early for unsupported encodings
+ if (!charset.LowerCaseEqualsLiteral("utf-8")) {
+ NS_WARNING("unsupported charset in RFC5987-encoded param");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // percent-decode
+ if (!PercentDecode(value)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // return the encoding
+ aLang.Assign(language);
+
+ // finally convert octet sequence to UTF-8 and be done
+ nsAutoCString utf8;
+ nsresult rv = ConvertStringToUTF8(value, charset, true, false, utf8);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(utf8, aResult);
+ return NS_OK;
+}
+
+nsresult internalDecodeParameter(const nsACString& aParamValue,
+ const nsACString& aCharset,
+ const nsACString& aDefaultCharset,
+ bool aOverrideCharset, bool aDecode2047,
+ nsACString& aResult) {
+ aResult.Truncate();
+ // If aCharset is given, aParamValue was obtained from RFC2231/5987
+ // encoding and we're pretty sure that it's in aCharset.
+ if (!aCharset.IsEmpty()) {
+ return ConvertStringToUTF8(aParamValue, aCharset, true, true, aResult);
+ }
+
+ const nsCString& param = PromiseFlatCString(aParamValue);
+ nsAutoCString unQuoted;
+ nsACString::const_iterator s, e;
+ param.BeginReading(s);
+ param.EndReading(e);
+
+ // strip '\' when used to quote CR, LF, '"' and '\'
+ for (; s != e; ++s) {
+ if ((*s == '\\')) {
+ if (++s == e) {
+ --s; // '\' is at the end. move back and append '\'.
+ } else if (*s != nsCRT::CR && *s != nsCRT::LF && *s != '"' &&
+ *s != '\\') {
+ --s; // '\' is not foll. by CR,LF,'"','\'. move back and append '\'
+ }
+ // else : skip '\' and append the quoted character.
+ }
+ unQuoted.Append(*s);
+ }
+
+ aResult = unQuoted;
+ nsresult rv = NS_OK;
+
+ if (aDecode2047) {
+ nsAutoCString decoded;
+
+ // Try RFC 2047 encoding, instead.
+ rv = internalDecodeRFC2047Header(unQuoted.get(), aDefaultCharset,
+ aOverrideCharset, true, decoded);
+
+ if (NS_SUCCEEDED(rv) && !decoded.IsEmpty()) aResult = decoded;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMIMEHeaderParamImpl::DecodeParameter(const nsACString& aParamValue,
+ const char* aCharset,
+ const char* aDefaultCharset,
+ bool aOverrideCharset,
+ nsACString& aResult) {
+ return internalDecodeParameter(aParamValue, nsCString(aCharset),
+ nsCString(aDefaultCharset), aOverrideCharset,
+ true, aResult);
+}
+
+#define ISHEXCHAR(c) \
+ ((0x30 <= uint8_t(c) && uint8_t(c) <= 0x39) || \
+ (0x41 <= uint8_t(c) && uint8_t(c) <= 0x46) || \
+ (0x61 <= uint8_t(c) && uint8_t(c) <= 0x66))
+
+// Decode Q encoding (RFC 2047).
+// static
+char* DecodeQ(const char* in, uint32_t length) {
+ char *out, *dest = nullptr;
+
+ out = dest = (char*)calloc(length + 1, sizeof(char));
+ if (dest == nullptr) return nullptr;
+ while (length > 0) {
+ unsigned c = 0;
+ switch (*in) {
+ case '=':
+ // check if |in| in the form of '=hh' where h is [0-9a-fA-F].
+ if (length < 3 || !ISHEXCHAR(in[1]) || !ISHEXCHAR(in[2])) {
+ goto badsyntax;
+ }
+ PR_sscanf(in + 1, "%2X", &c);
+ *out++ = (char)c;
+ in += 3;
+ length -= 3;
+ break;
+
+ case '_':
+ *out++ = ' ';
+ in++;
+ length--;
+ break;
+
+ default:
+ if (*in & 0x80) goto badsyntax;
+ *out++ = *in++;
+ length--;
+ }
+ }
+ *out++ = '\0';
+
+ for (out = dest; *out; ++out) {
+ if (*out == '\t') *out = ' ';
+ }
+
+ return dest;
+
+badsyntax:
+ free(dest);
+ return nullptr;
+}
+
+// check if input is HZ (a 7bit encoding for simplified Chinese : RFC 1842))
+// or has ESC which may be an indication that it's in one of many ISO
+// 2022 7bit encodings (e.g. ISO-2022-JP(-2)/CN : see RFC 1468, 1922, 1554).
+// static
+bool Is7bitNonAsciiString(const char* input, uint32_t len) {
+ int32_t c;
+
+ enum {
+ hz_initial, // No HZ seen yet
+ hz_escaped, // Inside an HZ ~{ escape sequence
+ hz_seen, // Have seen at least one complete HZ sequence
+ hz_notpresent // Have seen something that is not legal HZ
+ } hz_state;
+
+ hz_state = hz_initial;
+ while (len) {
+ c = uint8_t(*input++);
+ len--;
+ if (c & 0x80) return false;
+ if (c == 0x1B) return true;
+ if (c == '~') {
+ switch (hz_state) {
+ case hz_initial:
+ case hz_seen:
+ if (*input == '{') {
+ hz_state = hz_escaped;
+ } else if (*input == '~') {
+ // ~~ is the HZ encoding of ~. Skip over second ~ as well
+ hz_state = hz_seen;
+ input++;
+ len--;
+ } else {
+ hz_state = hz_notpresent;
+ }
+ break;
+
+ case hz_escaped:
+ if (*input == '}') hz_state = hz_seen;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return hz_state == hz_seen;
+}
+
+#define REPLACEMENT_CHAR "\357\277\275" // EF BF BD (UTF-8 encoding of U+FFFD)
+
+// copy 'raw' sequences of octets in aInput to aOutput.
+// If aDefaultCharset is specified, the input is assumed to be in the
+// charset and converted to UTF-8. Otherwise, a blind copy is made.
+// If aDefaultCharset is specified, but the conversion to UTF-8
+// is not successful, each octet is replaced by Unicode replacement
+// chars. *aOutput is advanced by the number of output octets.
+// static
+void CopyRawHeader(const char* aInput, uint32_t aLen,
+ const nsACString& aDefaultCharset, nsACString& aOutput) {
+ int32_t c;
+
+ // If aDefaultCharset is not specified, make a blind copy.
+ if (aDefaultCharset.IsEmpty()) {
+ aOutput.Append(aInput, aLen);
+ return;
+ }
+
+ // Copy as long as it's US-ASCII. An ESC may indicate ISO 2022
+ // A ~ may indicate it is HZ
+ while (aLen && (c = uint8_t(*aInput++)) != 0x1B && c != '~' && !(c & 0x80)) {
+ aOutput.Append(char(c));
+ aLen--;
+ }
+ if (!aLen) {
+ return;
+ }
+ aInput--;
+
+ // skip ASCIIness/UTF8ness test if aInput is supected to be a 7bit non-ascii
+ // string and aDefaultCharset is a 7bit non-ascii charset.
+ bool skipCheck =
+ (c == 0x1B || c == '~') &&
+ IS_7BIT_NON_ASCII_CHARSET(PromiseFlatCString(aDefaultCharset).get());
+
+ // If not UTF-8, treat as default charset
+ nsAutoCString utf8Text;
+ if (NS_SUCCEEDED(ConvertStringToUTF8(Substring(aInput, aInput + aLen),
+ PromiseFlatCString(aDefaultCharset),
+ skipCheck, true, utf8Text))) {
+ aOutput.Append(utf8Text);
+ } else { // replace each octet with Unicode replacement char in UTF-8.
+ for (uint32_t i = 0; i < aLen; i++) {
+ c = uint8_t(*aInput++);
+ if (c & 0x80) {
+ aOutput.Append(REPLACEMENT_CHAR);
+ } else {
+ aOutput.Append(char(c));
+ }
+ }
+ }
+}
+
+nsresult DecodeQOrBase64Str(const char* aEncoded, size_t aLen, char aQOrBase64,
+ const nsACString& aCharset, nsACString& aResult) {
+ char* decodedText;
+ bool b64alloc = false;
+ NS_ASSERTION(aQOrBase64 == 'Q' || aQOrBase64 == 'B', "Should be 'Q' or 'B'");
+ if (aQOrBase64 == 'Q') {
+ decodedText = DecodeQ(aEncoded, aLen);
+ } else if (aQOrBase64 == 'B') {
+ decodedText = PL_Base64Decode(aEncoded, aLen, nullptr);
+ b64alloc = true;
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!decodedText) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString utf8Text;
+ // skip ASCIIness/UTF8ness test if aCharset is 7bit non-ascii charset.
+ nsresult rv = ConvertStringToUTF8(
+ nsDependentCString(decodedText), aCharset,
+ IS_7BIT_NON_ASCII_CHARSET(PromiseFlatCString(aCharset).get()), true,
+ utf8Text);
+ if (b64alloc) {
+ PR_Free(decodedText);
+ } else {
+ free(decodedText);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aResult.Append(utf8Text);
+
+ return NS_OK;
+}
+
+static const char especials[] = R"(()<>@,;:\"/[]?.=)";
+
+// |decode_mime_part2_str| taken from comi18n.c
+// Decode RFC2047-encoded words in the input and convert the result to UTF-8.
+// If aOverrideCharset is true, charset in RFC2047-encoded words is
+// ignored and aDefaultCharset is assumed, instead. aDefaultCharset
+// is also used to convert raw octets (without RFC 2047 encoding) to UTF-8.
+// static
+nsresult DecodeRFC2047Str(const char* aHeader,
+ const nsACString& aDefaultCharset,
+ bool aOverrideCharset, nsACString& aResult) {
+ const char *p, *q = nullptr, *r;
+ const char* begin; // tracking pointer for where we are in the input buffer
+ int32_t isLastEncodedWord = 0;
+ const char *charsetStart, *charsetEnd;
+ nsAutoCString prevCharset, curCharset;
+ nsAutoCString encodedText;
+ char prevEncoding = '\0', curEncoding;
+ nsresult rv;
+
+ begin = aHeader;
+
+ // To avoid buffer realloc, if possible, set capacity in advance. No
+ // matter what, more than 3x expansion can never happen for all charsets
+ // supported by Mozilla. SCSU/BCSU with the sliding window set to a
+ // non-BMP block may be exceptions, but Mozilla does not support them.
+ // Neither any known mail/news program use them. Even if there's, we're
+ // safe because we don't use a raw *char any more.
+ aResult.SetCapacity(3 * strlen(aHeader));
+
+ while ((p = strstr(begin, "=?")) != nullptr) {
+ if (isLastEncodedWord) {
+ // See if it's all whitespace.
+ for (q = begin; q < p; ++q) {
+ if (!strchr(" \t\r\n", *q)) {
+ break;
+ }
+ }
+ }
+
+ if (!isLastEncodedWord || q < p) {
+ if (!encodedText.IsEmpty()) {
+ rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
+ prevEncoding, prevCharset, aResult);
+ if (NS_FAILED(rv)) {
+ aResult.Append(encodedText);
+ }
+ encodedText.Truncate();
+ prevCharset.Truncate();
+ prevEncoding = '\0';
+ }
+ // copy the part before the encoded-word
+ CopyRawHeader(begin, p - begin, aDefaultCharset, aResult);
+ begin = p;
+ }
+
+ p += 2;
+
+ // Get charset info
+ charsetStart = p;
+ charsetEnd = nullptr;
+ for (q = p; *q != '?'; q++) {
+ if (*q <= ' ' || strchr(especials, *q)) {
+ goto badsyntax;
+ }
+
+ // RFC 2231 section 5
+ if (!charsetEnd && *q == '*') {
+ charsetEnd = q;
+ }
+ }
+ if (!charsetEnd) {
+ charsetEnd = q;
+ }
+
+ q++;
+ curEncoding = nsCRT::ToUpper(*q);
+ if (curEncoding != 'Q' && curEncoding != 'B') goto badsyntax;
+
+ if (q[1] != '?') goto badsyntax;
+
+ // loop-wise, keep going until we hit "?=". the inner check handles the
+ // nul terminator should the string terminate before we hit the right
+ // marker. (And the r[1] will never reach beyond the end of the string
+ // because *r != '?' is true if r is the nul character.)
+ for (r = q + 2; *r != '?' || r[1] != '='; r++) {
+ if (*r < ' ') goto badsyntax;
+ }
+ if (r == q + 2) {
+ // it's empty, skip
+ begin = r + 2;
+ isLastEncodedWord = 1;
+ continue;
+ }
+
+ curCharset.Assign(charsetStart, charsetEnd - charsetStart);
+ // Override charset if requested. Never override labeled UTF-8.
+ // Use default charset instead of UNKNOWN-8BIT
+ if ((aOverrideCharset &&
+ 0 != nsCRT::strcasecmp(curCharset.get(), "UTF-8")) ||
+ (!aDefaultCharset.IsEmpty() &&
+ 0 == nsCRT::strcasecmp(curCharset.get(), "UNKNOWN-8BIT"))) {
+ curCharset = aDefaultCharset;
+ }
+
+ const char* R;
+ R = r;
+ if (curEncoding == 'B') {
+ // bug 227290. ignore an extraneous '=' at the end.
+ // (# of characters in B-encoded part has to be a multiple of 4)
+ int32_t n = r - (q + 2);
+ R -= (n % 4 == 1 && !strncmp(r - 3, "===", 3)) ? 1 : 0;
+ }
+ // Bug 493544. Don't decode the encoded text until it ends
+ if (R[-1] != '=' &&
+ (prevCharset.IsEmpty() ||
+ (curCharset == prevCharset && curEncoding == prevEncoding))) {
+ encodedText.Append(q + 2, R - (q + 2));
+ prevCharset = curCharset;
+ prevEncoding = curEncoding;
+
+ begin = r + 2;
+ isLastEncodedWord = 1;
+ continue;
+ }
+
+ bool bDecoded; // If the current line has been decoded.
+ bDecoded = false;
+ if (!encodedText.IsEmpty()) {
+ if (curCharset == prevCharset && curEncoding == prevEncoding) {
+ encodedText.Append(q + 2, R - (q + 2));
+ bDecoded = true;
+ }
+ rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
+ prevEncoding, prevCharset, aResult);
+ if (NS_FAILED(rv)) {
+ aResult.Append(encodedText);
+ }
+ encodedText.Truncate();
+ prevCharset.Truncate();
+ prevEncoding = '\0';
+ }
+ if (!bDecoded) {
+ rv = DecodeQOrBase64Str(q + 2, R - (q + 2), curEncoding, curCharset,
+ aResult);
+ if (NS_FAILED(rv)) {
+ aResult.Append(encodedText);
+ }
+ }
+
+ begin = r + 2;
+ isLastEncodedWord = 1;
+ continue;
+
+ badsyntax:
+ if (!encodedText.IsEmpty()) {
+ rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
+ prevEncoding, prevCharset, aResult);
+ if (NS_FAILED(rv)) {
+ aResult.Append(encodedText);
+ }
+ encodedText.Truncate();
+ prevCharset.Truncate();
+ }
+ // copy the part before the encoded-word
+ aResult.Append(begin, p - begin);
+ begin = p;
+ isLastEncodedWord = 0;
+ }
+
+ if (!encodedText.IsEmpty()) {
+ rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
+ prevEncoding, prevCharset, aResult);
+ if (NS_FAILED(rv)) {
+ aResult.Append(encodedText);
+ }
+ }
+
+ // put the tail back
+ CopyRawHeader(begin, strlen(begin), aDefaultCharset, aResult);
+
+ nsAutoCString tempStr(aResult);
+ tempStr.ReplaceChar('\t', ' ');
+ aResult = tempStr;
+
+ return NS_OK;
+}
diff --git a/netwerk/mime/nsMIMEHeaderParamImpl.h b/netwerk/mime/nsMIMEHeaderParamImpl.h
new file mode 100644
index 0000000000..3a6a59c7cd
--- /dev/null
+++ b/netwerk/mime/nsMIMEHeaderParamImpl.h
@@ -0,0 +1,44 @@
+/* -*- 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 "nsIMIMEHeaderParam.h"
+
+#ifndef __nsmimeheaderparamimpl_h___
+# define __nsmimeheaderparamimpl_h___
+class nsMIMEHeaderParamImpl : public nsIMIMEHeaderParam {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMIMEHEADERPARAM
+
+ nsMIMEHeaderParamImpl() = default;
+
+ /**
+ * Identical to calling
+ * GetParameterHTTP(aHeaderVal, aParameterName, ""_ns, false,
+ * nullptr, aResult) See nsIMIMEHeaderParam.idl for more information.
+ */
+ static nsresult GetParameterHTTP(const nsACString& aHeaderVal,
+ const char* aParamName, nsAString& aResult);
+
+ private:
+ virtual ~nsMIMEHeaderParamImpl() = default;
+ enum ParamDecoding { MIME_FIELD_ENCODING = 1, HTTP_FIELD_ENCODING };
+
+ static nsresult DoGetParameter(const nsACString& aHeaderVal,
+ const char* aParamName,
+ ParamDecoding aDecoding,
+ const nsACString& aFallbackCharset,
+ bool aTryLocaleCharset, char** aLang,
+ nsAString& aResult);
+
+ static nsresult DoParameterInternal(const nsACString& aHeaderVal,
+ const char* aParamName,
+ ParamDecoding aDecoding, char** aCharset,
+ char** aLang, char** aResult);
+
+ static bool ContainsTrailingCharPastNull(const nsACString& aVal);
+};
+
+#endif
diff --git a/netwerk/mime/nsMimeTypes.h b/netwerk/mime/nsMimeTypes.h
new file mode 100644
index 0000000000..86934d44fd
--- /dev/null
+++ b/netwerk/mime/nsMimeTypes.h
@@ -0,0 +1,281 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This interface allows any module to access the encoder/decoder
+ * routines for RFC822 headers. This will allow any mail/news module
+ * to call on these routines.
+ */
+#ifndef nsMimeTypes_h_
+#define nsMimeTypes_h_
+
+/* Defines for various MIME content-types and encodings.
+ Whenever you type in a content-type, you should use one of these defines
+ instead, to help catch typos, and make central management of them easier.
+ */
+
+#define ANY_WILDCARD "*/*"
+#define AUDIO_WILDCARD "audio/*"
+#define IMAGE_WILDCARD "image/*"
+
+#define APPLICATION_APPLEFILE "application/applefile"
+#define APPLICATION_BINHEX "application/mac-binhex40"
+#define APPLICATION_MACBINARY "application/x-macbinary"
+#define APPLICATION_COMPRESS "application/x-compress"
+#define APPLICATION_COMPRESS2 "application/compress"
+#define APPLICATION_FORTEZZA_CKL "application/x-fortezza-ckl"
+#define APPLICATION_FORTEZZA_KRL "application/x-fortezza-krl"
+#define APPLICATION_GZIP "application/x-gzip"
+#define APPLICATION_GZIP2 "application/gzip"
+#define APPLICATION_GZIP3 "application/x-gunzip"
+#define APPLICATION_BROTLI "application/brotli"
+#define APPLICATION_ZIP "application/zip"
+#define APPLICATION_HTTP_INDEX_FORMAT "application/http-index-format"
+#define APPLICATION_ECMASCRIPT "application/ecmascript"
+#define APPLICATION_JAVASCRIPT "application/javascript"
+#define APPLICATION_XJAVASCRIPT "application/x-javascript"
+#define APPLICATION_JSON "application/json"
+#define APPLICATION_NETSCAPE_REVOCATION "application/x-netscape-revocation"
+#define APPLICATION_NS_PROXY_AUTOCONFIG "application/x-ns-proxy-autoconfig"
+#define APPLICATION_NS_JAVASCRIPT_AUTOCONFIG "application/x-javascript-config"
+#define APPLICATION_OCTET_STREAM "application/octet-stream"
+#define APPLICATION_PGP "application/pgp"
+#define APPLICATION_PGP2 "application/x-pgp-message"
+#define APPLICATION_POSTSCRIPT "application/postscript"
+#define APPLICATION_PDF "application/pdf"
+#define APPLICATION_PRE_ENCRYPTED "application/pre-encrypted"
+#define APPLICATION_RDF "application/rdf+xml"
+#define APPLICATION_UUENCODE "application/x-uuencode"
+#define APPLICATION_UUENCODE2 "application/x-uue"
+#define APPLICATION_UUENCODE3 "application/uuencode"
+#define APPLICATION_UUENCODE4 "application/uue"
+#define APPLICATION_X509_CA_CERT "application/x-x509-ca-cert"
+#define APPLICATION_X509_SERVER_CERT "application/x-x509-server-cert"
+#define APPLICATION_X509_EMAIL_CERT "application/x-x509-email-cert"
+#define APPLICATION_X509_USER_CERT "application/x-x509-user-cert"
+#define APPLICATION_X509_CRL "application/x-pkcs7-crl"
+#define APPLICATION_XPKCS7_MIME "application/x-pkcs7-mime"
+#define APPLICATION_PKCS7_MIME "application/pkcs7-mime"
+#define APPLICATION_XPKCS7_SIGNATURE "application/x-pkcs7-signature"
+#define APPLICATION_PKCS7_SIGNATURE "application/pkcs7-signature"
+#define APPLICATION_WWW_FORM_URLENCODED "application/x-www-form-urlencoded"
+#define APPLICATION_OLEOBJECT "application/oleobject"
+#define APPLICATION_OLEOBJECT2 "application/x-oleobject"
+#define APPLICATION_JAVAARCHIVE "application/java-archive"
+#define APPLICATION_MARIMBA "application/marimba"
+#define APPLICATION_WEB_MANIFEST "application/manifest+json"
+#define APPLICATION_XMARIMBA "application/x-marimba"
+#define APPLICATION_XPINSTALL "application/x-xpinstall"
+#define APPLICATION_XML "application/xml"
+#define APPLICATION_XHTML_XML "application/xhtml+xml"
+#define APPLICATION_XSLT_XML "application/xslt+xml"
+#define APPLICATION_MATHML_XML "application/mathml+xml"
+#define APPLICATION_RDF_XML "application/rdf+xml"
+#define APPLICATION_WAPXHTML_XML "application/vnd.wap.xhtml+xml"
+#define APPLICATION_PACKAGE "application/package"
+#define APPLICATION_WASM "application/wasm"
+#define APPLICATION_MSEXCEL "application/msexcel"
+#define APPLICATION_MSPPT "application/mspowerpoint"
+#define APPLICATION_MSWORD "application/msword"
+#define APPLICATION_MSWORD_TEMPLATE "application/msword-template"
+#define APPLICATION_VND_CES_QUICKPOINT "application/vnd.ces-quickpoint"
+#define APPLICATION_VND_CES_QUICKSHEET "application/vnd.ces-quicksheet"
+#define APPLICATION_VND_CES_QUICKWORD "application/vnd.ces-quickword"
+#define APPLICATION_VND_MS_EXCEL "application/vnd.ms-excel"
+#define APPLICATION_VND_MS_EXCEL2 \
+ "application/vnd.ms-excel.sheet.macroenabled.12"
+#define APPLICATION_VND_MS_PPT "application/vnd.ms-powerpoint"
+#define APPLICATION_VND_MS_PPT2 \
+ "application/vnd.ms-powerpoint.presentation.macroenabled.12"
+#define APPLICATION_VND_MS_WORD "application/vnd.ms-word"
+#define APPLICATION_VND_MS_WORD2 "application/vnd.ms-word.document.12"
+#define APPLICATION_VND_MS_WORD3 \
+ "application/vnd.ms-word.document.macroenabled.12"
+#define APPLICATION_VND_MSWORD "application/vnd.msword"
+#define APPLICATION_VND_PRESENTATIONML_PRESENTATION \
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
+#define APPLICATION_VND_PRESENTATIONML_TEMPLATE \
+ "application/vnd.openxmlformats-officedocument.presentationml.template"
+#define APPLICATION_VND_SPREADSHEETML_SHEET \
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+#define APPLICATION_VND_SPREADSHEETML_TEMPLATE \
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.template"
+#define APPLICATION_VND_WORDPROCESSINGML_DOCUMENT \
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+#define APPLICATION_VND_WORDPROCESSINGML_TEMPLATE \
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.template"
+#define APPLICATION_VND_PRESENTATION_OPENXML \
+ "application/vnd.presentation-openxml"
+#define APPLICATION_VND_PRESENTATION_OPENXMLM \
+ "application/vnd.presentation-openxmlm"
+#define APPLICATION_VND_SPREADSHEET_OPENXML \
+ "application/vnd.spreadsheet-openxml"
+#define APPLICATION_VND_WORDPROSSING_OPENXML \
+ "application/vnd.wordprocessing-openxml"
+#define APPLICATION_XPROTOBUF "application/x-protobuf"
+#define APPLICATION_XPROTOBUFFER "application/x-protobuffer"
+
+#define AUDIO_BASIC "audio/basic"
+#define AUDIO_OGG "audio/ogg"
+#define AUDIO_WAV "audio/x-wav"
+#define AUDIO_WEBM "audio/webm"
+#define AUDIO_MP3 "audio/mpeg"
+#define AUDIO_MP4 "audio/mp4"
+#define AUDIO_AMR "audio/amr"
+#define AUDIO_FLAC "audio/flac"
+#define AUDIO_3GPP "audio/3gpp"
+#define AUDIO_3GPP2 "audio/3gpp2"
+#define AUDIO_MIDI "audio/x-midi"
+#define AUDIO_MATROSKA "audio/x-matroska"
+#define AUDIO_AAC "audio/aac"
+#define AUDIO_AACP "audio/aacp"
+#define AUDIO_MPEG_TS "audio/mp2t"
+#define AUDIO_MPEG_URL "audio/mpegurl"
+
+#define BINARY_OCTET_STREAM "binary/octet-stream"
+
+#define IMAGE_GIF "image/gif"
+#define IMAGE_JPEG "image/jpeg"
+#define IMAGE_JPG "image/jpg"
+#define IMAGE_PJPEG "image/pjpeg"
+#define IMAGE_PNG "image/png"
+#define IMAGE_APNG "image/apng"
+#define IMAGE_X_PNG "image/x-png"
+#define IMAGE_PPM "image/x-portable-pixmap"
+#define IMAGE_XBM "image/x-xbitmap"
+#define IMAGE_XBM2 "image/x-xbm"
+#define IMAGE_XBM3 "image/xbm"
+#define IMAGE_ART "image/x-jg"
+#define IMAGE_TIFF "image/tiff"
+#define IMAGE_BMP "image/bmp"
+#define IMAGE_BMP_MS "image/x-ms-bmp"
+// This is used internally to represent Windows clipboard BMPs which remove
+// part of the header.
+#define IMAGE_BMP_MS_CLIPBOARD "image/x-ms-clipboard-bmp"
+#define IMAGE_ICO "image/x-icon"
+#define IMAGE_ICO_MS "image/vnd.microsoft.icon"
+#define IMAGE_ICON_MS "image/icon"
+#define IMAGE_MNG "video/x-mng"
+#define IMAGE_JNG "image/x-jng"
+#define IMAGE_SVG_XML "image/svg+xml"
+#define IMAGE_WEBP "image/webp"
+#define IMAGE_AVIF "image/avif"
+#define IMAGE_JXL "image/jxl"
+
+#define MESSAGE_EXTERNAL_BODY "message/external-body"
+#define MESSAGE_NEWS "message/news"
+#define MESSAGE_RFC822 "message/rfc822"
+
+#define MULTIPART_ALTERNATIVE "multipart/alternative"
+#define MULTIPART_APPLEDOUBLE "multipart/appledouble"
+#define MULTIPART_DIGEST "multipart/digest"
+#define MULTIPART_FORM_DATA "multipart/form-data"
+#define MULTIPART_HEADER_SET "multipart/header-set"
+#define MULTIPART_MIXED "multipart/mixed"
+#define MULTIPART_PARALLEL "multipart/parallel"
+#define MULTIPART_SIGNED "multipart/signed"
+#define MULTIPART_RELATED "multipart/related"
+#define MULTIPART_MIXED_REPLACE "multipart/x-mixed-replace"
+#define MULTIPART_BYTERANGES "multipart/byteranges"
+
+#define SUN_ATTACHMENT "x-sun-attachment"
+
+#define TEXT_ENRICHED "text/enriched"
+#define TEXT_CALENDAR "text/calendar"
+#define TEXT_HTML "text/html"
+#define TEXT_MDL "text/mdl"
+#define TEXT_PLAIN "text/plain"
+#define TEXT_RICHTEXT "text/richtext"
+#define TEXT_VCARD "text/vcard"
+#define TEXT_CSS "text/css"
+#define TEXT_JSSS "text/jsss"
+#define TEXT_JSON "text/json"
+#define TEXT_XML "text/xml"
+#define TEXT_RDF "text/rdf"
+#define TEXT_VTT "text/vtt"
+#define TEXT_ECMASCRIPT "text/ecmascript"
+#define TEXT_JAVASCRIPT "text/javascript"
+#define TEXT_XSL "text/xsl"
+#define TEXT_EVENT_STREAM "text/event-stream"
+#define TEXT_CACHE_MANIFEST "text/cache-manifest"
+#define TEXT_CSV "text/csv"
+
+#define VIDEO_MPEG "video/mpeg"
+#define VIDEO_MP4 "video/mp4"
+#define VIDEO_QUICKTIME "video/quicktime"
+#define VIDEO_RAW "video/x-raw-yuv"
+#define VIDEO_OGG "video/ogg"
+#define VIDEO_WEBM "video/webm"
+#define VIDEO_3GPP "video/3gpp"
+#define VIDEO_3GPP2 "video/3gpp2"
+#define VIDEO_MPEG_TS "video/mp2t"
+#define VIDEO_AVI "video/avi"
+#define VIDEO_MATROSKA "video/x-matroska"
+#define APPLICATION_OGG "application/ogg"
+#define APPLICATION_MPEGURL "application/vnd.apple.mpegurl"
+#define APPLICATION_DASH_XML "application/dash+xml"
+
+/* x-uuencode-apple-single. QuickMail made me do this. */
+#define UUENCODE_APPLE_SINGLE "x-uuencode-apple-single"
+
+/* The standard MIME message-content-encoding values:
+ */
+#define ENCODING_7BIT "7bit"
+#define ENCODING_8BIT "8bit"
+#define ENCODING_BINARY "binary"
+#define ENCODING_BASE64 "base64"
+#define ENCODING_QUOTED_PRINTABLE "quoted-printable"
+
+/* Some nonstandard encodings. Note that the names are TOTALLY RANDOM,
+ and code that looks for these in network-provided data must look for
+ all the possibilities.
+ */
+#define ENCODING_COMPRESS "x-compress"
+#define ENCODING_COMPRESS2 "compress"
+#define ENCODING_ZLIB "x-zlib"
+#define ENCODING_ZLIB2 "zlib"
+#define ENCODING_GZIP "x-gzip"
+#define ENCODING_GZIP2 "gzip"
+#define ENCODING_DEFLATE "x-deflate"
+#define ENCODING_DEFLATE2 "deflate"
+#define ENCODING_UUENCODE "x-uuencode"
+#define ENCODING_UUENCODE2 "x-uue"
+#define ENCODING_UUENCODE3 "uuencode"
+#define ENCODING_UUENCODE4 "uue"
+#define ENCODING_YENCODE "x-yencode"
+
+/* Some names of parameters that various MIME headers include.
+ */
+#define PARAM_PROTOCOL "protocol"
+#define PARAM_MICALG "micalg"
+#define PARAM_MICALG_MD2 "rsa-md2"
+#define PARAM_MICALG_MD5 "rsa-md5"
+#define PARAM_MICALG_MD5_2 "md5"
+#define PARAM_MICALG_SHA1 "sha1"
+#define PARAM_MICALG_SHA1_2 "sha-1"
+#define PARAM_MICALG_SHA1_3 "rsa-sha1"
+#define PARAM_MICALG_SHA1_4 "rsa-sha-1"
+#define PARAM_MICALG_SHA1_5 "rsa-sha"
+#define PARAM_MICALG_SHA256 "sha-256"
+#define PARAM_MICALG_SHA256_2 "sha256"
+#define PARAM_MICALG_SHA256_3 "2.16.840.1.101.3.4.2.1"
+#define PARAM_MICALG_SHA384 "sha-384"
+#define PARAM_MICALG_SHA384_2 "sha384"
+#define PARAM_MICALG_SHA384_3 "2.16.840.1.101.3.4.2.2"
+#define PARAM_MICALG_SHA512 "sha-512"
+#define PARAM_MICALG_SHA512_2 "sha512"
+#define PARAM_MICALG_SHA512_3 "2.16.840.1.101.3.4.2.3"
+#define PARAM_X_MAC_CREATOR "x-mac-creator"
+#define PARAM_X_MAC_TYPE "x-mac-type"
+#define PARAM_FORMAT "format"
+
+#define UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type"
+#define APPLICATION_GUESS_FROM_EXT "application/x-vnd.mozilla.guess-from-ext"
+#define VIEWSOURCE_CONTENT_TYPE "application/x-view-source"
+
+#define APPLICATION_DIRECTORY \
+ "application/directory" /* text/x-vcard is synonym */
+
+#endif /* nsMimeTypes_h_ */
diff --git a/netwerk/moz.build b/netwerk/moz.build
new file mode 100644
index 0000000000..fd4002b9c6
--- /dev/null
+++ b/netwerk/moz.build
@@ -0,0 +1,35 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+DIRS += [
+ "base",
+ "cookie",
+ "dns",
+ "socket",
+ "mime",
+ "streamconv",
+ "cache2",
+ "protocol",
+ "system",
+ "ipc",
+ "url-classifier",
+]
+
+if CONFIG["MOZ_SCTP"]:
+ DIRS += ["sctp/src", "sctp/datachannel"]
+
+if CONFIG["NECKO_WIFI"]:
+ DIRS += ["wifi"]
+
+DIRS += ["locales"]
+
+DIRS += ["build"]
+TEST_DIRS += ["test"]
+
+SPHINX_TREES["/networking"] = "docs"
diff --git a/netwerk/protocol/about/moz.build b/netwerk/protocol/about/moz.build
new file mode 100644
index 0000000000..f130d9b07b
--- /dev/null
+++ b/netwerk/protocol/about/moz.build
@@ -0,0 +1,32 @@
+# -*- 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 += [
+ "nsIAboutModule.idl",
+]
+
+XPIDL_MODULE = "necko_about"
+
+EXPORTS += [
+ "nsAboutProtocolHandler.h",
+ "nsAboutProtocolUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsAboutBlank.cpp",
+ "nsAboutCache.cpp",
+ "nsAboutCacheEntry.cpp",
+ "nsAboutProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/cache2",
+]
diff --git a/netwerk/protocol/about/nsAboutBlank.cpp b/netwerk/protocol/about/nsAboutBlank.cpp
new file mode 100644
index 0000000000..88db0d2de3
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAboutBlank.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+
+NS_IMPL_ISUPPORTS(nsAboutBlank, nsIAboutModule)
+
+NS_IMETHODIMP
+nsAboutBlank::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIInputStream> in;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(in), ""_ns);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI,
+ in.forget(), "text/html"_ns, "utf-8"_ns,
+ aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAboutBlank::GetURIFlags(nsIURI* aURI, uint32_t* result) {
+ *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::MAKE_LINKABLE |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutBlank::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+nsresult nsAboutBlank::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsAboutBlank> about = new nsAboutBlank();
+ return about->QueryInterface(aIID, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutBlank.h b/netwerk/protocol/about/nsAboutBlank.h
new file mode 100644
index 0000000000..6cb43507f5
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.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 nsAboutBlank_h__
+#define nsAboutBlank_h__
+
+#include "nsIAboutModule.h"
+
+class nsAboutBlank : public nsIAboutModule {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutBlank() = default;
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ private:
+ virtual ~nsAboutBlank() = default;
+};
+
+#define NS_ABOUT_BLANK_MODULE_CID \
+ { /* 3decd6c8-30ef-11d3-8cd0-0060b0fc14a3 */ \
+ 0x3decd6c8, 0x30ef, 0x11d3, { \
+ 0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+#endif // nsAboutBlank_h__
diff --git a/netwerk/protocol/about/nsAboutCache.cpp b/netwerk/protocol/about/nsAboutCache.cpp
new file mode 100644
index 0000000000..1873277a7a
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -0,0 +1,533 @@
+/* -*- 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 "nsAboutCache.h"
+#include "nsIInputStream.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "nsIPipe.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsPrintfCString.h"
+
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
+
+#include "nsThreadUtils.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule)
+NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest,
+ nsICacheStorageVisitor)
+
+NS_IMETHODIMP
+nsAboutCache::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ RefPtr<Channel> channel = new Channel();
+ rv = channel->Init(aURI, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+
+ return NS_OK;
+}
+
+nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ mCancel = false;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream), 16384,
+ (uint32_t)-1,
+ true, // non-blocking input
+ false // blocking output
+ );
+
+ nsAutoCString storageName;
+ rv = ParseURI(aURI, storageName);
+ if (NS_FAILED(rv)) return rv;
+
+ mOverview = storageName.IsEmpty();
+ if (mOverview) {
+ // ...and visit all we can
+ mStorageList.AppendElement("memory"_ns);
+ mStorageList.AppendElement("disk"_ns);
+ } else {
+ // ...and visit just the specified storage, entries will output too
+ mStorageList.AppendElement(storageName);
+ }
+
+ // The entries header is added on encounter of the first entry
+ mEntriesHeaderAdded = false;
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), aURI,
+ inputStream.forget(), "text/html"_ns,
+ "utf-8"_ns, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ mBuffer.AssignLiteral(
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <title>Network Cache Storage Information</title>\n"
+ " <meta charset=\"utf-8\">\n"
+ " <meta name=\"color-scheme\" content=\"light dark\">\n"
+ " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
+ "chrome:; object-src 'none'\"/>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/in-content/info-pages.css\"/>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/aboutCache.css\"/>\n"
+ "</head>\n"
+ "<body class=\"aboutPageWideContainer\">\n"
+ "<h1>Information about the Network Cache Storage Service</h1>\n");
+
+ if (!mOverview) {
+ mBuffer.AppendLiteral(
+ "<a href=\"about:cache?storage=\">Back to overview</a>");
+ }
+
+ rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener* aListener) {
+ nsresult rv;
+
+ if (!mChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Kick the walk loop.
+ rv = VisitNextStorage();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mChannel->AsyncOpen(aListener);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsAboutCache::Channel::ParseURI(nsIURI* uri, nsACString& storage) {
+ //
+ // about:cache[?storage=<storage-name>[&context=<context-key>]]
+ //
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ storage.Truncate();
+
+ nsACString::const_iterator start, valueStart, end;
+ path.BeginReading(start);
+ path.EndReading(end);
+
+ valueStart = end;
+ if (!FindInReadable("?storage="_ns, start, valueStart)) {
+ return NS_OK;
+ }
+
+ storage.Assign(Substring(valueStart, end));
+
+ return NS_OK;
+}
+
+nsresult nsAboutCache::Channel::VisitNextStorage() {
+ if (!mStorageList.Length()) return NS_ERROR_NOT_AVAILABLE;
+
+ mStorageName = mStorageList[0];
+ mStorageList.RemoveElementAt(0);
+
+ // Must re-dispatch since we cannot start another visit cycle
+ // from visitor callback. The cache v1 service doesn't like it.
+ // TODO - mayhemer, bug 913828, remove this dispatch and call
+ // directly.
+ return NS_DispatchToMainThread(mozilla::NewRunnableMethod(
+ "nsAboutCache::Channel::FireVisitStorage", this,
+ &nsAboutCache::Channel::FireVisitStorage));
+}
+
+void nsAboutCache::Channel::FireVisitStorage() {
+ nsresult rv;
+
+ rv = VisitStorage(mStorageName);
+ if (NS_FAILED(rv)) {
+ nsAutoCString escaped;
+ nsAppendEscapedHTML(mStorageName, escaped);
+ mBuffer.Append(nsPrintfCString(
+ "<p>Unrecognized storage name '%s' in about:cache URL</p>",
+ escaped.get()));
+
+ rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ // Simulate finish of a visit cycle, this tries the next storage
+ // or closes the output stream (i.e. the UI loader will stop spinning)
+ OnCacheEntryVisitCompleted();
+ }
+}
+
+nsresult nsAboutCache::Channel::VisitStorage(nsACString const& storageName) {
+ nsresult rv;
+
+ rv = GetStorage(storageName, nullptr, getter_AddRefs(mStorage));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mStorage->AsyncVisitStorage(this, !mOverview);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+// static
+nsresult nsAboutCache::GetStorage(nsACString const& storageName,
+ nsILoadContextInfo* loadInfo,
+ nsICacheStorage** storage) {
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorageService> cacheService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (storageName == "disk") {
+ rv = cacheService->DiskCacheStorage(loadInfo, getter_AddRefs(cacheStorage));
+ } else if (storageName == "memory") {
+ rv = cacheService->MemoryCacheStorage(loadInfo,
+ getter_AddRefs(cacheStorage));
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ cacheStorage.forget(storage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount,
+ uint64_t aConsumption,
+ uint64_t aCapacity,
+ nsIFile* aDirectory) {
+ // We need mStream for this
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBuffer.AssignLiteral("<h2>");
+ nsAppendEscapedHTML(mStorageName, mBuffer);
+ mBuffer.AppendLiteral(
+ "</h2>\n"
+ "<table id=\"");
+ mBuffer.AppendLiteral("\">\n");
+
+ // Write out cache info
+ // Number of entries
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Number of entries:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aEntryCount);
+ mBuffer.AppendLiteral(
+ "</td>\n"
+ " </tr>\n");
+
+ // Maximum storage size
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Maximum storage size:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aCapacity / 1024);
+ mBuffer.AppendLiteral(
+ " KiB</td>\n"
+ " </tr>\n");
+
+ // Storage in use
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Storage in use:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aConsumption / 1024);
+ mBuffer.AppendLiteral(
+ " KiB</td>\n"
+ " </tr>\n");
+
+ // Storage disk location
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Storage disk location:</th>\n"
+ " <td>");
+ if (aDirectory) {
+ nsAutoString path;
+ aDirectory->GetPath(path);
+ mBuffer.Append(NS_ConvertUTF16toUTF8(path));
+ } else {
+ mBuffer.AppendLiteral("none, only stored in memory");
+ }
+ mBuffer.AppendLiteral(
+ " </td>\n"
+ " </tr>\n");
+
+ if (mOverview) { // The about:cache case
+ if (aEntryCount != 0) { // Add the "List Cache Entries" link
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <td colspan=\"2\"><a href=\"about:cache?storage=");
+ nsAppendEscapedHTML(mStorageName, mBuffer);
+ mBuffer.AppendLiteral(
+ "\">List Cache Entries</a></td>\n"
+ " </tr>\n");
+ }
+ }
+
+ mBuffer.AppendLiteral("</table>\n");
+
+ // The entries header is added on encounter of the first entry
+ mEntriesHeaderAdded = false;
+
+ nsresult rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ if (mOverview) {
+ // OnCacheEntryVisitCompleted() is not called when we do not iterate
+ // cache entries. Since this moves forward to the next storage in
+ // the list we want to visit, artificially call it here.
+ OnCacheEntryVisitCompleted();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheEntryInfo(
+ nsIURI* aURI, const nsACString& aIdEnhance, int64_t aDataSize,
+ int64_t aAltDataSize, uint32_t aFetchCount, uint32_t aLastModified,
+ uint32_t aExpirationTime, bool aPinned, nsILoadContextInfo* aInfo) {
+ // We need mStream for this
+ if (!mStream || mCancel) {
+ // Returning a failure from this callback stops the iteration
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mEntriesHeaderAdded) {
+ mBuffer.AppendLiteral(
+ "<hr/>\n"
+ "<table id=\"entries\">\n"
+ " <colgroup>\n"
+ " <col id=\"col-key\">\n"
+ " <col id=\"col-dataSize\">\n"
+ " <col id=\"col-altDataSize\">\n"
+ " <col id=\"col-fetchCount\">\n"
+ " <col id=\"col-lastModified\">\n"
+ " <col id=\"col-expires\">\n"
+ " <col id=\"col-pinned\">\n"
+ " </colgroup>\n"
+ " <thead>\n"
+ " <tr>\n"
+ " <th>Key</th>\n"
+ " <th>Data size</th>\n"
+ " <th>Alternative Data size</th>\n"
+ " <th>Fetch count</th>\n"
+ " <th>Last Modifed</th>\n"
+ " <th>Expires</th>\n"
+ " <th>Pinning</th>\n"
+ " </tr>\n"
+ " </thead>\n");
+ mEntriesHeaderAdded = true;
+ }
+
+ // Generate a about:cache-entry URL for this entry...
+
+ nsAutoCString url;
+ url.AssignLiteral("about:cache-entry?storage=");
+ nsAppendEscapedHTML(mStorageName, url);
+
+ nsAutoCString context;
+ CacheFileUtils::AppendKeyPrefix(aInfo, context);
+ url.AppendLiteral("&amp;context=");
+ nsAppendEscapedHTML(context, url);
+
+ url.AppendLiteral("&amp;eid=");
+ nsAppendEscapedHTML(aIdEnhance, url);
+
+ nsAutoCString cacheUriSpec;
+ aURI->GetAsciiSpec(cacheUriSpec);
+ nsAutoCString escapedCacheURI;
+ nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI);
+ url.AppendLiteral("&amp;uri=");
+ url += escapedCacheURI;
+
+ // Entry start...
+ mBuffer.AppendLiteral(" <tr>\n");
+
+ // URI
+ mBuffer.AppendLiteral(" <td><a href=\"");
+ mBuffer.Append(url);
+ mBuffer.AppendLiteral("\">");
+ if (!aIdEnhance.IsEmpty()) {
+ nsAppendEscapedHTML(aIdEnhance, mBuffer);
+ mBuffer.Append(':');
+ }
+ mBuffer.Append(escapedCacheURI);
+ mBuffer.AppendLiteral("</a>");
+
+ if (!context.IsEmpty()) {
+ mBuffer.AppendLiteral("<br><span title=\"Context separation key\">");
+ nsAutoCString escapedContext;
+ nsAppendEscapedHTML(context, escapedContext);
+ mBuffer.Append(escapedContext);
+ mBuffer.AppendLiteral("</span>");
+ }
+
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Content length
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aDataSize);
+ mBuffer.AppendLiteral(" bytes</td>\n");
+
+ // Length of alternative content
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aAltDataSize);
+ mBuffer.AppendLiteral(" bytes</td>\n");
+
+ // Number of accesses
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aFetchCount);
+ mBuffer.AppendLiteral("</td>\n");
+
+ // vars for reporting time
+ char buf[255];
+
+ // Last modified time
+ mBuffer.AppendLiteral(" <td>");
+ if (aLastModified) {
+ PrintTimeString(buf, sizeof(buf), aLastModified);
+ mBuffer.Append(buf);
+ } else {
+ mBuffer.AppendLiteral("No last modified time");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Expires time
+ mBuffer.AppendLiteral(" <td>");
+
+ // Bug - 633747.
+ // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
+ // So we check if time is 0, then we show a message, "Expired Immediately"
+ if (aExpirationTime == 0) {
+ mBuffer.AppendLiteral("Expired Immediately");
+ } else if (aExpirationTime < 0xFFFFFFFF) {
+ PrintTimeString(buf, sizeof(buf), aExpirationTime);
+ mBuffer.Append(buf);
+ } else {
+ mBuffer.AppendLiteral("No expiration time");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Pinning
+ mBuffer.AppendLiteral(" <td>");
+ if (aPinned) {
+ mBuffer.AppendLiteral("Pinned");
+ } else {
+ mBuffer.AppendLiteral("&nbsp;");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Entry is done...
+ mBuffer.AppendLiteral(" </tr>\n");
+
+ return FlushBuffer();
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheEntryVisitCompleted() {
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mEntriesHeaderAdded) {
+ mBuffer.AppendLiteral("</table>\n");
+ }
+
+ // Kick another storage visiting (from a storage that allows us.)
+ while (mStorageList.Length()) {
+ nsresult rv = VisitNextStorage();
+ if (NS_SUCCEEDED(rv)) {
+ // Expecting new round of OnCache* calls.
+ return NS_OK;
+ }
+ }
+
+ // We are done!
+ mBuffer.AppendLiteral(
+ "</body>\n"
+ "</html>\n");
+ nsresult rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+ mStream->Close();
+
+ return NS_OK;
+}
+
+nsresult nsAboutCache::Channel::FlushBuffer() {
+ nsresult rv;
+
+ uint32_t bytesWritten;
+ rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
+ mBuffer.Truncate();
+
+ if (NS_FAILED(rv)) {
+ mCancel = true;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAboutCache::GetURIFlags(nsIURI* aURI, uint32_t* result) {
+ *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::IS_SECURE_CHROME_UI;
+ return NS_OK;
+}
+
+// static
+nsresult nsAboutCache::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsAboutCache> about = new nsAboutCache();
+ return about->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsAboutCache::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutCache.h b/netwerk/protocol/about/nsAboutCache.h
new file mode 100644
index 0000000000..65292b9aae
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.h
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutCache_h__
+#define nsAboutCache_h__
+
+#include "nsIAboutModule.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsICacheStorage.h"
+
+#include "nsString.h"
+#include "nsIChannel.h"
+#include "nsIOutputStream.h"
+#include "nsILoadContextInfo.h"
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#define NS_FORWARD_SAFE_NSICHANNEL_SUBSET(_to) \
+ NS_IMETHOD GetOriginalURI(nsIURI** aOriginalURI) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetOriginalURI(aOriginalURI); \
+ } \
+ NS_IMETHOD SetOriginalURI(nsIURI* aOriginalURI) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetOriginalURI(aOriginalURI); \
+ } \
+ NS_IMETHOD GetURI(nsIURI** aURI) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetURI(aURI); \
+ } \
+ NS_IMETHOD GetOwner(nsISupports** aOwner) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetOwner(aOwner); \
+ } \
+ NS_IMETHOD SetOwner(nsISupports* aOwner) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->SetOwner(aOwner); \
+ } \
+ NS_IMETHOD GetNotificationCallbacks( \
+ nsIInterfaceRequestor** aNotificationCallbacks) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetNotificationCallbacks(aNotificationCallbacks); \
+ } \
+ NS_IMETHOD SetNotificationCallbacks( \
+ nsIInterfaceRequestor* aNotificationCallbacks) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetNotificationCallbacks(aNotificationCallbacks); \
+ } \
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) \
+ override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetSecurityInfo(aSecurityInfo); \
+ } \
+ NS_IMETHOD GetContentType(nsACString& aContentType) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentType(aContentType); \
+ } \
+ NS_IMETHOD SetContentType(const nsACString& aContentType) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentType(aContentType); \
+ } \
+ NS_IMETHOD GetContentCharset(nsACString& aContentCharset) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentCharset(aContentCharset); \
+ } \
+ NS_IMETHOD SetContentCharset(const nsACString& aContentCharset) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentCharset(aContentCharset); \
+ } \
+ NS_IMETHOD GetContentLength(int64_t* aContentLength) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentLength(aContentLength); \
+ } \
+ NS_IMETHOD SetContentLength(int64_t aContentLength) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentLength(aContentLength); \
+ } \
+ NS_IMETHOD GetContentDisposition(uint32_t* aContentDisposition) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentDisposition(aContentDisposition); \
+ } \
+ NS_IMETHOD SetContentDisposition(uint32_t aContentDisposition) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentDisposition(aContentDisposition); \
+ } \
+ NS_IMETHOD GetContentDispositionFilename( \
+ nsAString& aContentDispositionFilename) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentDispositionFilename( \
+ aContentDispositionFilename); \
+ } \
+ NS_IMETHOD SetContentDispositionFilename( \
+ const nsAString& aContentDispositionFilename) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentDispositionFilename( \
+ aContentDispositionFilename); \
+ } \
+ NS_IMETHOD GetContentDispositionHeader( \
+ nsACString& aContentDispositionHeader) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentDispositionHeader( \
+ aContentDispositionHeader); \
+ } \
+ NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetLoadInfo(aLoadInfo); \
+ } \
+ NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->SetLoadInfo(aLoadInfo); \
+ } \
+ NS_IMETHOD GetIsDocument(bool* aIsDocument) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetIsDocument(aIsDocument); \
+ } \
+ NS_IMETHOD GetCanceled(bool* aCanceled) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetCanceled(aCanceled); \
+ };
+
+class nsAboutCache final : public nsIAboutModule {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutCache() = default;
+
+ [[nodiscard]] static nsresult Create(REFNSIID aIID, void** aResult);
+
+ [[nodiscard]] static nsresult GetStorage(nsACString const& storageName,
+ nsILoadContextInfo* loadInfo,
+ nsICacheStorage** storage);
+
+ protected:
+ virtual ~nsAboutCache() = default;
+
+ class Channel final : public nsIChannel, public nsICacheStorageVisitor {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHESTORAGEVISITOR
+ NS_FORWARD_SAFE_NSIREQUEST(mChannel)
+ NS_FORWARD_SAFE_NSICHANNEL_SUBSET(mChannel)
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ NS_IMETHOD Open(nsIInputStream** _retval) override;
+
+ private:
+ virtual ~Channel() = default;
+
+ public:
+ [[nodiscard]] nsresult Init(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+ [[nodiscard]] nsresult ParseURI(nsIURI* uri, nsACString& storage);
+
+ // Finds a next storage we wish to visit (we use this method
+ // even there is a specified storage name, which is the only
+ // one in the list then.) Posts FireVisitStorage() when found.
+ [[nodiscard]] nsresult VisitNextStorage();
+ // Helper method that calls VisitStorage() for the current storage.
+ // When it fails, OnCacheEntryVisitCompleted is simulated to close
+ // the output stream and thus the about:cache channel.
+ void FireVisitStorage();
+ // Kiks the visit cycle for the given storage, names can be:
+ // "disk", "memory", "appcache"
+ // Note: any newly added storage type has to be manually handled here.
+ [[nodiscard]] nsresult VisitStorage(nsACString const& storageName);
+
+ // Writes content of mBuffer to mStream and truncates
+ // the buffer. It may fail when the input stream is closed by canceling
+ // the input stream channel. It can be used to stop the cache iteration
+ // process.
+ [[nodiscard]] nsresult FlushBuffer();
+
+ // Whether we are showing overview status of all available
+ // storages.
+ bool mOverview = false;
+
+ // Flag initially false, that indicates the entries header has
+ // been added to the output HTML.
+ bool mEntriesHeaderAdded = false;
+
+ // Cancelation flag
+ bool mCancel = false;
+
+ // The list of all storage names we want to visit
+ nsTArray<nsCString> mStorageList;
+ nsCString mStorageName;
+ nsCOMPtr<nsICacheStorage> mStorage;
+
+ // Output data buffering and streaming output
+ nsCString mBuffer;
+ nsCOMPtr<nsIOutputStream> mStream;
+
+ // The input stream channel, the one that actually does the job
+ nsCOMPtr<nsIChannel> mChannel;
+ };
+};
+
+#define NS_ABOUT_CACHE_MODULE_CID \
+ { /* 9158c470-86e4-11d4-9be2-00e09872a416 */ \
+ 0x9158c470, 0x86e4, 0x11d4, { \
+ 0x9b, 0xe2, 0x00, 0xe0, 0x98, 0x72, 0xa4, 0x16 \
+ } \
+ }
+
+#endif // nsAboutCache_h__
diff --git a/netwerk/protocol/about/nsAboutCacheEntry.cpp b/netwerk/protocol/about/nsAboutCacheEntry.cpp
new file mode 100644
index 0000000000..08dfcf6ca0
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp
@@ -0,0 +1,559 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "nsAboutCacheEntry.h"
+
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
+#include "mozilla/Sprintf.h"
+#include "nsAboutCache.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsICacheStorage.h"
+#include "nsIPipe.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsInputStreamPump.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::net;
+
+#define HEXDUMP_MAX_ROWS 16
+
+static void HexDump(uint32_t* state, const char* buf, int32_t n,
+ nsCString& result) {
+ char temp[16];
+
+ const unsigned char* p;
+ while (n) {
+ SprintfLiteral(temp, "%08x: ", *state);
+ result.Append(temp);
+ *state += HEXDUMP_MAX_ROWS;
+
+ p = (const unsigned char*)buf;
+
+ int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n);
+
+ // print hex codes:
+ for (i = 0; i < row_max; ++i) {
+ SprintfLiteral(temp, "%02x ", *p++);
+ result.Append(temp);
+ }
+ for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) {
+ result.AppendLiteral(" ");
+ }
+
+ // print ASCII glyphs if possible:
+ p = (const unsigned char*)buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ switch (*p) {
+ case '<':
+ result.AppendLiteral("&lt;");
+ break;
+ case '>':
+ result.AppendLiteral("&gt;");
+ break;
+ case '&':
+ result.AppendLiteral("&amp;");
+ break;
+ default:
+ if (*p < 0x7F && *p > 0x1F) {
+ result.Append(*p);
+ } else {
+ result.Append('.');
+ }
+ }
+ }
+
+ result.Append('\n');
+
+ buf += row_max;
+ n -= row_max;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::nsISupports
+
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry, nsIAboutModule)
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry::Channel, nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor, nsIStreamListener, nsIRequest,
+ nsIChannel)
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::nsIAboutModule
+
+NS_IMETHODIMP
+nsAboutCacheEntry::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ nsresult rv;
+
+ RefPtr<Channel> channel = new Channel();
+ rv = channel->Init(uri, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::GetURIFlags(nsIURI* aURI, uint32_t* result) {
+ *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::Channel
+
+nsresult nsAboutCacheEntry::Channel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = GetContentStream(uri, getter_AddRefs(stream));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), uri,
+ stream.forget(), "text/html"_ns,
+ "utf-8"_ns, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::GetContentStream(nsIURI* uri,
+ nsIInputStream** result) {
+ nsresult rv;
+
+ // Init: (block size, maximum length)
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(mOutputStream), true,
+ false, 256, UINT32_MAX);
+
+ constexpr auto buffer =
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
+ "chrome:; object-src 'none'\" />\n"
+ " <meta name=\"color-scheme\" content=\"light dark\" />\n"
+ " <title>Cache entry information</title>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/in-content/info-pages.css\" "
+ "type=\"text/css\"/>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/aboutCacheEntry.css\" type=\"text/css\"/>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Cache entry information</h1>\n"_ns;
+ uint32_t n;
+ rv = mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ if (NS_FAILED(rv)) return rv;
+ if (n != buffer.Length()) return NS_ERROR_UNEXPECTED;
+
+ rv = OpenCacheEntry(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ inputStream.forget(result);
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::OpenCacheEntry(nsIURI* uri) {
+ nsresult rv;
+
+ rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo), mEnhanceId,
+ getter_AddRefs(mCacheURI));
+ if (NS_FAILED(rv)) return rv;
+
+ return OpenCacheEntry();
+}
+
+nsresult nsAboutCacheEntry::Channel::OpenCacheEntry() {
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorage> storage;
+ rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo,
+ getter_AddRefs(storage));
+ if (NS_FAILED(rv)) return rv;
+
+ // Invokes OnCacheEntryAvailable()
+ rv = storage->AsyncOpenURI(
+ mCacheURI, mEnhanceId,
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::ParseURI(nsIURI* uri,
+ nsACString& storageName,
+ nsILoadContextInfo** loadInfo,
+ nsCString& enahnceID,
+ nsIURI** cacheUri) {
+ //
+ // about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string]
+ //
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end;
+ path.BeginReading(begin);
+ path.EndReading(end);
+
+ keyBegin = begin;
+ keyEnd = end;
+ if (!FindInReadable("?storage="_ns, keyBegin, keyEnd)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ valBegin = keyEnd; // the value of the storage key starts after the key
+
+ keyBegin = keyEnd;
+ keyEnd = end;
+ if (!FindInReadable("&context="_ns, keyBegin, keyEnd)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageName.Assign(Substring(valBegin, keyBegin));
+ valBegin = keyEnd; // the value of the context key starts after the key
+
+ keyBegin = keyEnd;
+ keyEnd = end;
+ if (!FindInReadable("&eid="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE;
+
+ nsAutoCString contextKey(Substring(valBegin, keyBegin));
+ valBegin = keyEnd; // the value of the eid key starts after the key
+
+ keyBegin = keyEnd;
+ keyEnd = end;
+ if (!FindInReadable("&uri="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE;
+
+ enahnceID.Assign(Substring(valBegin, keyBegin));
+
+ valBegin = keyEnd; // the value of the uri key starts after the key
+ nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one
+
+ // Uf... parsing done, now get some objects from it...
+
+ nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(contextKey);
+ if (!info) return NS_ERROR_FAILURE;
+ info.forget(loadInfo);
+
+ rv = NS_NewURI(cacheUri, uriSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsICacheEntryOpenCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnCacheEntryCheck(nsICacheEntry* aEntry,
+ uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnCacheEntryAvailable(nsICacheEntry* entry,
+ bool isNew, nsresult status) {
+ nsresult rv;
+
+ mWaitingForData = false;
+ if (entry) {
+ rv = WriteCacheEntryDescription(entry);
+ } else {
+ rv = WriteCacheEntryUnavailable();
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mWaitingForData) {
+ // Data is not expected, close the output of content now.
+ CloseContent();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Print-out helper methods
+//-----------------------------------------------------------------------------
+
+#define APPEND_ROW(label, value) \
+ PR_BEGIN_MACRO \
+ buffer.AppendLiteral( \
+ " <tr>\n" \
+ " <th>"); \
+ buffer.AppendLiteral(label); \
+ buffer.AppendLiteral( \
+ ":</th>\n" \
+ " <td>"); \
+ buffer.Append(value); \
+ buffer.AppendLiteral( \
+ "</td>\n" \
+ " </tr>\n"); \
+ PR_END_MACRO
+
+nsresult nsAboutCacheEntry::Channel::WriteCacheEntryDescription(
+ nsICacheEntry* entry) {
+ nsresult rv;
+ // This method appears to run in a situation where the run-time stack
+ // should have plenty of space, so allocating a large string on the
+ // stack is OK.
+ nsAutoCStringN<4097> buffer;
+ uint32_t n;
+
+ nsAutoCString str;
+
+ rv = entry->GetKey(str);
+ if (NS_FAILED(rv)) return rv;
+
+ buffer.AssignLiteral(
+ "<table>\n"
+ " <tr>\n"
+ " <th>key:</th>\n"
+ " <td id=\"td-key\">");
+
+ // Test if the key is actually a URI
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), str);
+
+ nsAutoCString escapedStr;
+ nsAppendEscapedHTML(str, escapedStr);
+
+ // javascript: and data: URLs should not be linkified
+ // since clicking them can cause scripts to run - bug 162584
+ if (NS_SUCCEEDED(rv) &&
+ !(uri->SchemeIs("javascript") || uri->SchemeIs("data"))) {
+ buffer.AppendLiteral("<a href=\"");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("\">");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("</a>");
+ uri = nullptr;
+ } else {
+ buffer.Append(escapedStr);
+ }
+ buffer.AppendLiteral(
+ "</td>\n"
+ " </tr>\n");
+
+ // temp vars for reporting
+ char timeBuf[255];
+ uint32_t u = 0;
+ nsAutoCString s;
+
+ // Fetch Count
+ s.Truncate();
+ entry->GetFetchCount(&u);
+ s.AppendInt(u);
+ APPEND_ROW("fetch count", s);
+
+ // Last Fetched
+ entry->GetLastFetched(&u);
+ if (u) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("last fetched", timeBuf);
+ } else {
+ APPEND_ROW("last fetched", "No last fetch time (bug 1000338)");
+ }
+
+ // Last Modified
+ entry->GetLastModified(&u);
+ if (u) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("last modified", timeBuf);
+ } else {
+ APPEND_ROW("last modified", "No last modified time (bug 1000338)");
+ }
+
+ // Expiration Time
+ entry->GetExpirationTime(&u);
+
+ // Bug - 633747.
+ // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
+ // So we check if time is 0, then we show a message, "Expired Immediately"
+ if (u == 0) {
+ APPEND_ROW("expires", "Expired Immediately");
+ } else if (u < 0xFFFFFFFF) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("expires", timeBuf);
+ } else {
+ APPEND_ROW("expires", "No expiration time");
+ }
+
+ // Data Size
+ s.Truncate();
+ uint32_t dataSize;
+ if (NS_FAILED(entry->GetStorageDataSize(&dataSize))) dataSize = 0;
+ s.AppendInt(
+ (int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed.
+ s.AppendLiteral(" B");
+ APPEND_ROW("Data size", s);
+
+ // TODO - mayhemer
+ // Here used to be a link to the disk file (in the old cache for entries that
+ // did not fit any of the block files, in the new cache every time).
+ // I'd rather have a small set of buttons here to action on the entry:
+ // 1. save the content
+ // 2. save as a complete HTTP response (response head, headers, content)
+ // 3. doom the entry
+ // A new bug(s) should be filed here.
+
+ // Security Info
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ entry->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (securityInfo) {
+ APPEND_ROW("Security", "This is a secure document.");
+ } else {
+ APPEND_ROW(
+ "Security",
+ "This document does not have any security info associated with it.");
+ }
+
+ buffer.AppendLiteral(
+ "</table>\n"
+ "<hr/>\n"
+ "<table>\n");
+
+ mBuffer = &buffer; // make it available for OnMetaDataElement().
+ entry->VisitMetaData(this);
+ mBuffer = nullptr;
+
+ buffer.AppendLiteral("</table>\n");
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ buffer.Truncate();
+
+ // Provide a hexdump of the data
+ if (!dataSize) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ entry->OpenInputStream(0, getter_AddRefs(stream));
+ if (!stream) {
+ return NS_OK;
+ }
+
+ RefPtr<nsInputStreamPump> pump;
+ rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream);
+ if (NS_FAILED(rv)) {
+ return NS_OK; // just ignore
+ }
+
+ rv = pump->AsyncRead(this);
+ if (NS_FAILED(rv)) {
+ return NS_OK; // just ignore
+ }
+
+ mWaitingForData = true;
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable() {
+ uint32_t n;
+ constexpr auto buffer = "The cache entry you selected is not available."_ns;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsICacheEntryMetaDataVisitor implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnMetaDataElement(char const* key,
+ char const* value) {
+ mBuffer->AppendLiteral(
+ " <tr>\n"
+ " <th>");
+ mBuffer->Append(key);
+ mBuffer->AppendLiteral(
+ ":</th>\n"
+ " <td>");
+ nsAppendEscapedHTML(nsDependentCString(value), *mBuffer);
+ mBuffer->AppendLiteral(
+ "</td>\n"
+ " </tr>\n");
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest* request) {
+ mHexDumpState = 0;
+
+ constexpr auto buffer = "<hr/>\n<pre>"_ns;
+ uint32_t n;
+ return mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t n;
+ return aInputStream->ReadSegments(&nsAboutCacheEntry::Channel::PrintCacheData,
+ this, aCount, &n);
+}
+
+/* static */
+nsresult nsAboutCacheEntry::Channel::PrintCacheData(
+ nsIInputStream* aInStream, void* aClosure, const char* aFromSegment,
+ uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) {
+ nsAboutCacheEntry::Channel* a =
+ static_cast<nsAboutCacheEntry::Channel*>(aClosure);
+
+ nsCString buffer;
+ HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer);
+
+ uint32_t n;
+ a->mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ *aWriteCount = aCount;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnStopRequest(nsIRequest* request,
+ nsresult result) {
+ constexpr auto buffer = "</pre>\n"_ns;
+ uint32_t n;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ CloseContent();
+
+ return NS_OK;
+}
+
+void nsAboutCacheEntry::Channel::CloseContent() {
+ constexpr auto buffer = "</body>\n</html>\n"_ns;
+ uint32_t n;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ mOutputStream->Close();
+ mOutputStream = nullptr;
+}
diff --git a/netwerk/protocol/about/nsAboutCacheEntry.h b/netwerk/protocol/about/nsAboutCacheEntry.h
new file mode 100644
index 0000000000..76f8649061
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.h
@@ -0,0 +1,86 @@
+/* -*- 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 nsAboutCacheEntry_h__
+#define nsAboutCacheEntry_h__
+
+#include "nsIAboutModule.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntry.h"
+#include "nsIStreamListener.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+
+class nsIAsyncOutputStream;
+class nsIInputStream;
+class nsILoadContextInfo;
+class nsIURI;
+
+class nsAboutCacheEntry final : public nsIAboutModule {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+ private:
+ virtual ~nsAboutCacheEntry() = default;
+
+ class Channel final : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor,
+ public nsIStreamListener,
+ public nsIChannel {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_FORWARD_SAFE_NSICHANNEL(mChannel)
+ NS_FORWARD_SAFE_NSIREQUEST(mChannel)
+
+ Channel() = default;
+
+ private:
+ virtual ~Channel() = default;
+
+ public:
+ [[nodiscard]] nsresult Init(nsIURI* uri, nsILoadInfo* aLoadInfo);
+
+ [[nodiscard]] nsresult GetContentStream(nsIURI*, nsIInputStream**);
+ [[nodiscard]] nsresult OpenCacheEntry(nsIURI*);
+ [[nodiscard]] nsresult OpenCacheEntry();
+ [[nodiscard]] nsresult WriteCacheEntryDescription(nsICacheEntry*);
+ [[nodiscard]] nsresult WriteCacheEntryUnavailable();
+ [[nodiscard]] nsresult ParseURI(nsIURI* uri, nsACString& storageName,
+ nsILoadContextInfo** loadInfo,
+ nsCString& enahnceID, nsIURI** cacheUri);
+ void CloseContent();
+
+ [[nodiscard]] static nsresult PrintCacheData(
+ nsIInputStream* aInStream, void* aClosure, const char* aFromSegment,
+ uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount);
+
+ private:
+ nsCString mStorageName, mEnhanceId;
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ nsCOMPtr<nsIURI> mCacheURI;
+
+ nsCString* mBuffer{nullptr};
+ nsCOMPtr<nsIAsyncOutputStream> mOutputStream;
+ bool mWaitingForData{false};
+ uint32_t mHexDumpState{0};
+
+ nsCOMPtr<nsIChannel> mChannel;
+ };
+};
+
+#define NS_ABOUT_CACHE_ENTRY_MODULE_CID \
+ { /* 7fa5237d-b0eb-438f-9e50-ca0166e63788 */ \
+ 0x7fa5237d, 0xb0eb, 0x438f, { \
+ 0x9e, 0x50, 0xca, 0x01, 0x66, 0xe6, 0x37, 0x88 \
+ } \
+ }
+
+#endif // nsAboutCacheEntry_h__
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.cpp b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
new file mode 100644
index 0000000000..5bbaa9010e
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
@@ -0,0 +1,428 @@
+/* -*- 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 "base/basictypes.h"
+#include "mozilla/ArrayUtils.h"
+
+#include "nsAboutProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsIAboutModule.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsError.h"
+#include "nsNetUtil.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIChannel.h"
+#include "nsIScriptError.h"
+#include "nsIClassInfoImpl.h"
+
+#include "mozilla/ipc/URIUtils.h"
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kNestedAboutURICID, NS_NESTEDABOUTURI_CID);
+
+static bool IsSafeToLinkForUntrustedContent(nsIURI* aURI) {
+ nsAutoCString path;
+ aURI->GetPathQueryRef(path);
+
+ int32_t f = path.FindChar('#');
+ if (f >= 0) {
+ path.SetLength(f);
+ }
+
+ f = path.FindChar('?');
+ if (f >= 0) {
+ path.SetLength(f);
+ }
+
+ ToLowerCase(path);
+
+ // The about modules for these URL types have the
+ // URI_SAFE_FOR_UNTRUSTED_CONTENT and MAKE_LINKABLE flags set.
+ return path.EqualsLiteral("blank") || path.EqualsLiteral("logo") ||
+ path.EqualsLiteral("srcdoc");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsAboutProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags, nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("about");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags) {
+ // First use the default (which is "unsafe for content"):
+ *aFlags = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_SCHEME_NOT_SELF_LINKABLE;
+
+ // Now try to see if this URI overrides the default:
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ nsresult rv = NS_GetAboutModule(aURI, getter_AddRefs(aboutMod));
+ if (NS_FAILED(rv)) {
+ // Swallow this and just tell the consumer the default:
+ return NS_OK;
+ }
+ uint32_t aboutModuleFlags = 0;
+ rv = aboutMod->GetURIFlags(aURI, &aboutModuleFlags);
+ // This should never happen, so pass back the error:
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Secure (https) pages can load safe about pages without becoming
+ // mixed content.
+ if (aboutModuleFlags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) {
+ *aFlags |= URI_IS_POTENTIALLY_TRUSTWORTHY;
+ // about: pages can only be loaded by unprivileged principals
+ // if they are marked as LINKABLE
+ if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
+ // Replace URI_DANGEROUS_TO_LOAD with URI_LOADABLE_BY_ANYONE.
+ *aFlags &= ~URI_DANGEROUS_TO_LOAD;
+ *aFlags |= URI_LOADABLE_BY_ANYONE;
+ }
+ }
+ return NS_OK;
+}
+
+// static
+nsresult nsAboutProtocolHandler::CreateNewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** aResult) {
+ *aResult = nullptr;
+ nsresult rv;
+
+ // Use a simple URI to parse out some stuff first
+ nsCOMPtr<nsIURI> url;
+ rv = NS_MutateURI(new nsSimpleURI::Mutator()).SetSpec(aSpec).Finalize(url);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (IsSafeToLinkForUntrustedContent(url)) {
+ // We need to indicate that this baby is safe. Use an inner URI that
+ // no one but the security manager will see. Make sure to preserve our
+ // path, in case someone decides to hardcode checks for particular
+ // about: URIs somewhere.
+ nsAutoCString spec;
+ rv = url->GetPathQueryRef(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ spec.InsertLiteral("moz-safe-about:", 0);
+
+ nsCOMPtr<nsIURI> inner;
+ rv = NS_NewURI(getter_AddRefs(inner), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_MutateURI(new nsNestedAboutURI::Mutator())
+ .Apply(&nsINestedAboutURIMutator::InitWithBase, inner, aBaseURI)
+ .SetSpec(aSpec)
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ url.swap(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+
+ // about:what you ask?
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ nsresult rv = NS_GetAboutModule(uri, getter_AddRefs(aboutMod));
+
+ nsAutoCString path;
+ if (NS_SUCCEEDED(NS_GetAboutModuleName(uri, path)) &&
+ path.EqualsLiteral("srcdoc")) {
+ // about:srcdoc is meant to be unresolvable, yet is included in the
+ // about lookup tables so that it can pass security checks when used in
+ // a srcdoc iframe. To ensure that it stays unresolvable, we pretend
+ // that it doesn't exist.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
+ // This looks like an about: we don't know about. Convert
+ // this to an invalid URI error.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+ }
+
+ uint32_t flags = 0;
+ if (NS_FAILED(aboutMod->GetURIFlags(uri, &flags))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool safeForUntrustedContent =
+ (flags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) != 0;
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ safeForUntrustedContent ||
+ (flags & (nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD)) == 0,
+ "Only unprivileged content should be loaded in child processes. (Did "
+ "you forget to add URI_SAFE_FOR_UNTRUSTED_CONTENT to your about: "
+ "page?)");
+
+ // The standard return case:
+ rv = aboutMod->NewChannel(uri, aLoadInfo, result);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
+ // This looks like an about: we don't know about. Convert
+ // this to an invalid URI error.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+ }
+
+ // Not all implementations of nsIAboutModule::NewChannel()
+ // set the LoadInfo on the newly created channel yet, as
+ // an interim solution we set the LoadInfo here if not
+ // available on the channel. Bug 1087720
+ nsCOMPtr<nsILoadInfo> loadInfo = (*result)->LoadInfo();
+ if (aLoadInfo != loadInfo) {
+ NS_ASSERTION(false,
+ "nsIAboutModule->newChannel(aURI, aLoadInfo) needs to "
+ "set LoadInfo");
+ AutoTArray<nsString, 2> params = {
+ u"nsIAboutModule->newChannel(aURI)"_ns,
+ u"nsIAboutModule->newChannel(aURI, aLoadInfo)"_ns};
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Security by Default"_ns,
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params);
+ (*result)->SetLoadInfo(aLoadInfo);
+ }
+
+ // If this URI is safe for untrusted content, enforce that its
+ // principal be based on the channel's originalURI by setting the
+ // owner to null.
+ // Note: this relies on aboutMod's newChannel implementation
+ // having set the proper originalURI, which probably isn't ideal.
+ if (safeForUntrustedContent) {
+ (*result)->SetOwner(nullptr);
+ }
+
+ RefPtr<nsNestedAboutURI> aboutURI;
+ if (NS_SUCCEEDED(
+ uri->QueryInterface(kNestedAboutURICID, getter_AddRefs(aboutURI))) &&
+ aboutURI->GetBaseURI()) {
+ nsCOMPtr<nsIWritablePropertyBag2> writableBag = do_QueryInterface(*result);
+ if (writableBag) {
+ writableBag->SetPropertyAsInterface(u"baseURI"_ns,
+ aboutURI->GetBaseURI());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Safe about protocol handler impl
+
+NS_IMPL_ISUPPORTS(nsSafeAboutProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("moz-safe-about");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ *result = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////
+// nsNestedAboutURI implementation
+
+NS_IMPL_CLASSINFO(nsNestedAboutURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_NESTEDABOUTURI_CID);
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsNestedAboutURI)
+
+NS_INTERFACE_MAP_BEGIN(nsNestedAboutURI)
+ if (aIID.Equals(kNestedAboutURICID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_IMPL_QUERY_CLASSINFO(nsNestedAboutURI)
+NS_INTERFACE_MAP_END_INHERITING(nsSimpleNestedURI)
+
+// nsISerializable
+
+NS_IMETHODIMP
+nsNestedAboutURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsNestedAboutURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsresult rv = nsSimpleNestedURI::ReadPrivate(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ bool haveBase;
+ rv = aStream->ReadBoolean(&haveBase);
+ if (NS_FAILED(rv)) return rv;
+
+ if (haveBase) {
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) return rv;
+
+ mBaseURI = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNestedAboutURI::Write(nsIObjectOutputStream* aStream) {
+ nsresult rv = nsSimpleNestedURI::Write(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteBoolean(mBaseURI != nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mBaseURI) {
+ // A previous iteration of this code wrote out mBaseURI as nsISupports
+ // and then read it in as nsIURI, which is non-kosher when mBaseURI
+ // implements more than just a single line of interfaces and the
+ // canonical nsISupports* isn't the one a static_cast<> of mBaseURI
+ // would produce. For backwards compatibility with existing
+ // serializations we continue to write mBaseURI as nsISupports but
+ // switch to reading it as nsISupports, with a post-read QI to get to
+ // nsIURI.
+ rv = aStream->WriteCompoundObject(mBaseURI, NS_GET_IID(nsISupports), true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsNestedAboutURI::Serialize(mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ NestedAboutURIParams params;
+ URIParams nestedParams;
+
+ nsSimpleNestedURI::Serialize(nestedParams);
+ params.nestedParams() = nestedParams;
+
+ if (mBaseURI) {
+ SerializeURI(mBaseURI, params.baseURI());
+ }
+
+ aParams = params;
+}
+
+bool nsNestedAboutURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TNestedAboutURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const NestedAboutURIParams& params = aParams.get_NestedAboutURIParams();
+ if (!nsSimpleNestedURI::Deserialize(params.nestedParams())) {
+ return false;
+ }
+
+ mBaseURI = nullptr;
+ if (params.baseURI()) {
+ mBaseURI = DeserializeURI(*params.baseURI());
+ }
+ return true;
+}
+
+// nsSimpleURI
+/* virtual */ nsSimpleURI* nsNestedAboutURI::StartClone(
+ nsSimpleURI::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef) {
+ // Sadly, we can't make use of nsSimpleNestedURI::StartClone here.
+ // However, this function is expected to exactly match that function,
+ // aside from the "new ns***URI()" call.
+ NS_ENSURE_TRUE(mInnerURI, nullptr);
+
+ nsCOMPtr<nsIURI> innerClone;
+ nsresult rv = NS_OK;
+ if (aRefHandlingMode == eHonorRef) {
+ innerClone = mInnerURI;
+ } else if (aRefHandlingMode == eReplaceRef) {
+ rv = NS_GetURIWithNewRef(mInnerURI, aNewRef, getter_AddRefs(innerClone));
+ } else {
+ rv = NS_GetURIWithoutRef(mInnerURI, getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsNestedAboutURI* url = new nsNestedAboutURI(innerClone, mBaseURI);
+ SetRefOnClone(url, aRefHandlingMode, aNewRef);
+
+ return url;
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsNestedAboutURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable,
+ nsINestedAboutURIMutator)
+
+NS_IMETHODIMP
+nsNestedAboutURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsNestedAboutURI::Mutator> mutator = new nsNestedAboutURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.h b/netwerk/protocol/about/nsAboutProtocolHandler.h
new file mode 100644
index 0000000000..e861f065b3
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.h
@@ -0,0 +1,139 @@
+/* -*- 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 nsAboutProtocolHandler_h___
+#define nsAboutProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsSimpleNestedURI.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "nsIURIMutator.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class nsAboutProtocolHandler : public nsIProtocolHandlerWithDynamicFlags,
+ public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+
+ // nsAboutProtocolHandler methods:
+ nsAboutProtocolHandler() = default;
+
+ static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result);
+
+ private:
+ virtual ~nsAboutProtocolHandler() = default;
+};
+
+class nsSafeAboutProtocolHandler final : public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsSafeAboutProtocolHandler methods:
+ nsSafeAboutProtocolHandler() = default;
+
+ private:
+ ~nsSafeAboutProtocolHandler() = default;
+};
+
+// Class to allow us to propagate the base URI to about:blank correctly
+class nsNestedAboutURI final : public nsSimpleNestedURI {
+ private:
+ nsNestedAboutURI(nsIURI* aInnerURI, nsIURI* aBaseURI)
+ : nsSimpleNestedURI(aInnerURI), mBaseURI(aBaseURI) {}
+ nsNestedAboutURI() {}
+ virtual ~nsNestedAboutURI() = default;
+
+ public:
+ // Override QI so we can QI to our CID as needed
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+
+ // Override StartClone(), the nsISerializable methods, and
+ virtual nsSimpleURI* StartClone(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef) override;
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override;
+ NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override;
+
+ // nsISerializable
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;
+
+ nsIURI* GetBaseURI() const { return mBaseURI; }
+
+ protected:
+ nsCOMPtr<nsIURI> mBaseURI;
+ bool Deserialize(const mozilla::ipc::URIParams&);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsNestedAboutURI>,
+ public nsISerializable,
+ public nsINestedAboutURIMutator {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ [[nodiscard]] NS_IMETHOD Deserialize(
+ const mozilla::ipc::URIParams& aParams) override {
+ return InitFromIPCParams(aParams);
+ }
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ NS_ADDREF(*aMutator = this);
+ }
+ return InitFromSpec(aSpec);
+ }
+
+ [[nodiscard]] NS_IMETHOD InitWithBase(nsIURI* aInnerURI,
+ nsIURI* aBaseURI) override {
+ mURI = new nsNestedAboutURI(aInnerURI, aBaseURI);
+ return NS_OK;
+ }
+
+ friend class nsNestedAboutURI;
+ };
+
+ friend BaseURIMutator<nsNestedAboutURI>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsAboutProtocolHandler_h___ */
diff --git a/netwerk/protocol/about/nsAboutProtocolUtils.h b/netwerk/protocol/about/nsAboutProtocolUtils.h
new file mode 100644
index 0000000000..fdc92620f3
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolUtils.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 nsAboutProtocolUtils_h
+#define nsAboutProtocolUtils_h
+
+#include "mozilla/Try.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIAboutModule.h"
+#include "nsServiceManagerUtils.h"
+#include "prtime.h"
+
+[[nodiscard]] inline nsresult NS_GetAboutModuleName(nsIURI* aAboutURI,
+ nsCString& aModule) {
+ NS_ASSERTION(aAboutURI->SchemeIs("about"),
+ "should be used only on about: URIs");
+
+ MOZ_TRY(aAboutURI->GetPathQueryRef(aModule));
+
+ int32_t f = aModule.FindCharInSet("#?"_ns);
+ if (f != kNotFound) {
+ aModule.Truncate(f);
+ }
+
+ // convert to lowercase, as all about: modules are lowercase
+ ToLowerCase(aModule);
+ return NS_OK;
+}
+
+[[nodiscard]] inline bool NS_IsContentAccessibleAboutURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI->SchemeIs("about"), "Should be used only on about: URIs");
+ nsAutoCString name;
+ if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, name)))) {
+ return true;
+ }
+ return name.EqualsLiteral("blank") || name.EqualsLiteral("srcdoc");
+}
+
+inline nsresult NS_GetAboutModule(nsIURI* aAboutURI, nsIAboutModule** aModule) {
+ MOZ_ASSERT(aAboutURI, "Must have URI");
+
+ nsAutoCString contractID;
+ MOZ_TRY(NS_GetAboutModuleName(aAboutURI, contractID));
+
+ // look up a handler to deal with "what"
+ contractID.InsertLiteral(NS_ABOUT_MODULE_CONTRACTID_PREFIX, 0);
+
+ return CallGetService(contractID.get(), aModule);
+}
+
+inline PRTime SecondsToPRTime(uint32_t t_sec) {
+ return (PRTime)t_sec * PR_USEC_PER_SEC;
+}
+
+inline void PrintTimeString(char* buf, uint32_t bufsize, uint32_t t_sec) {
+ PRExplodedTime et;
+ PRTime t_usec = SecondsToPRTime(t_sec);
+ PR_ExplodeTime(t_usec, PR_LocalTimeParameters, &et);
+ PR_FormatTime(buf, bufsize, "%Y-%m-%d %H:%M:%S", &et);
+}
+
+#endif
diff --git a/netwerk/protocol/about/nsIAboutModule.idl b/netwerk/protocol/about/nsIAboutModule.idl
new file mode 100644
index 0000000000..7750dd2d4b
--- /dev/null
+++ b/netwerk/protocol/about/nsIAboutModule.idl
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIChannel;
+interface nsILoadInfo;
+
+[scriptable, uuid(c0c19db9-1b5a-4ac5-b656-ed6f8149fa48)]
+interface nsIAboutModule : nsISupports
+{
+
+ /**
+ * Constructs a new channel for the about protocol module.
+ *
+ * @param aURI the uri of the new channel
+ * @param aLoadInfo the loadinfo of the new channel
+ */
+ nsIChannel newChannel(in nsIURI aURI,
+ in nsILoadInfo aLoadInfo);
+
+ /**
+ * A flag that indicates whether a URI should be run with content
+ * privileges. If it is, the about: protocol handler will enforce that
+ * the principal of channels created for it be based on their
+ * originalURI or URI (depending on the channel flags), by setting
+ * their "owner" to null.
+ * If content needs to be able to link to this URI, specify
+ * URI_CONTENT_LINKABLE as well.
+ */
+ const unsigned long URI_SAFE_FOR_UNTRUSTED_CONTENT = (1 << 0);
+
+ /**
+ * A flag that indicates whether script should be enabled for the
+ * given about: URI even if it's disabled in general.
+ */
+ const unsigned long ALLOW_SCRIPT = (1 << 1);
+
+ /**
+ * A flag that indicates whether this about: URI doesn't want to be listed
+ * in about:about, especially if it's not useful without a query string.
+ */
+ const unsigned long HIDE_FROM_ABOUTABOUT = (1 << 2);
+
+ /**
+ * A flag that indicates whether this about: URI wants Indexed DB enabled.
+ */
+ const unsigned long ENABLE_INDEXED_DB = (1 << 3);
+
+ /**
+ * A flag that indicates that this URI can be loaded in a child process
+ */
+ const unsigned long URI_CAN_LOAD_IN_CHILD = (1 << 4);
+
+ /**
+ * A flag that indicates that this URI must be loaded in a child process
+ */
+ const unsigned long URI_MUST_LOAD_IN_CHILD = (1 << 5);
+
+ /**
+ * Obsolete. This flag no longer has any effect and will be removed in future.
+ */
+ const unsigned long MAKE_UNLINKABLE = (1 << 6);
+
+ /**
+ * A flag that indicates that this URI should be linkable from content.
+ * Ignored unless URI_SAFE_FOR_UNTRUSTED_CONTENT is also specified.
+ *
+ * When adding a new about module with this flag make sure to also update
+ * IsSafeToLinkForUntrustedContent() in nsAboutProtocolHandler.cpp
+ */
+ const unsigned long MAKE_LINKABLE = (1 << 7);
+
+ /**
+ * A flag that indicates that this URI can be loaded in the privileged
+ * activity stream content process if said process is enabled. Ignored unless
+ * URI_MUST_LOAD_IN_CHILD is also specified.
+ */
+ const unsigned long URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS = (1 << 8);
+
+ /**
+ * A flag that indicates that this URI must be loaded in an extension process (if available).
+ */
+ const unsigned long URI_MUST_LOAD_IN_EXTENSION_PROCESS = (1 << 9);
+
+ /**
+ * A flag that indicates that this about: URI is a secure chrome UI
+ */
+ const unsigned long IS_SECURE_CHROME_UI = (1 << 10);
+
+ /**
+ * A method to get the flags that apply to a given about: URI. The URI
+ * passed in is guaranteed to be one of the URIs that this module
+ * registered to deal with.
+ */
+ unsigned long getURIFlags(in nsIURI aURI);
+
+ /**
+ * A method to get the chrome URI that corresponds to a given about URI.
+ */
+ nsIURI getChromeURI(in nsIURI aURI);
+};
+
+%{C++
+
+#define NS_ABOUT_MODULE_CONTRACTID "@mozilla.org/network/protocol/about;1"
+#define NS_ABOUT_MODULE_CONTRACTID_PREFIX NS_ABOUT_MODULE_CONTRACTID "?what="
+#define NS_ABOUT_MODULE_CONTRACTID_LENGTH 49 // strlen(NS_ABOUT_MODULE_CONTRACTID_PREFIX)
+
+%}
diff --git a/netwerk/protocol/data/DataChannelChild.cpp b/netwerk/protocol/data/DataChannelChild.cpp
new file mode 100644
index 0000000000..3ce6f02dd5
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataChannelChild.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(DataChannelChild, nsDataChannel, nsIChildChannel)
+
+DataChannelChild::DataChannelChild(nsIURI* aURI)
+ : nsDataChannel(aURI), mIPCOpen(false) {}
+
+NS_IMETHODIMP
+DataChannelChild::ConnectParent(uint32_t aId) {
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gNeckoChild->SendPDataChannelConstructor(this, aId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // IPC now has a ref to us.
+ mIPCOpen = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ nsresult rv;
+ rv = AsyncOpen(aListener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mIPCOpen) {
+ Unused << Send__delete__(this);
+ }
+ return NS_OK;
+}
+
+void DataChannelChild::ActorDestroy(ActorDestroyReason why) {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/data/DataChannelChild.h b/netwerk/protocol/data/DataChannelChild.h
new file mode 100644
index 0000000000..8e9f91ea60
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_DATACHANNELCHILD_H
+#define NS_DATACHANNELCHILD_H
+
+#include "nsDataChannel.h"
+#include "nsIChildChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PDataChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+class DataChannelChild : public nsDataChannel,
+ public nsIChildChannel,
+ public PDataChannelChild {
+ public:
+ explicit DataChannelChild(nsIURI* uri);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ protected:
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ private:
+ ~DataChannelChild() = default;
+
+ bool mIPCOpen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_DATACHANNELCHILD_H */
diff --git a/netwerk/protocol/data/DataChannelParent.cpp b/netwerk/protocol/data/DataChannelParent.cpp
new file mode 100644
index 0000000000..e427120a55
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+#ifdef FUZZING_SNAPSHOT
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__
+#else
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DataChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool DataChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+
+ MOZ_ALWAYS_SUCCEEDS_FUZZING(
+ NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::Delete() {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+void DataChannelParent::ActorDestroy(ActorDestroyReason why) {}
+
+NS_IMETHODIMP
+DataChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsDataChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/data/DataChannelParent.h b/netwerk/protocol/data/DataChannelParent.h
new file mode 100644
index 0000000000..122cdda182
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_DATACHANNELPARENT_H
+#define NS_DATACHANNELPARENT_H
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PDataChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects to data:, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class DataChannelParent : public nsIParentChannel, public PDataChannelParent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ [[nodiscard]] bool Init(const uint64_t& aChannelId);
+
+ private:
+ ~DataChannelParent() = default;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_DATACHANNELPARENT_H */
diff --git a/netwerk/protocol/data/moz.build b/netwerk/protocol/data/moz.build
new file mode 100644
index 0000000000..482c0b8a2f
--- /dev/null
+++ b/netwerk/protocol/data/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "DataChannelChild.h",
+ "DataChannelParent.h",
+]
+
+EXPORTS += [
+ "nsDataChannel.h",
+ "nsDataHandler.h",
+]
+
+UNIFIED_SOURCES += [
+ "DataChannelChild.cpp",
+ "DataChannelParent.cpp",
+ "nsDataChannel.cpp",
+ "nsDataHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/netwerk/protocol/data/nsDataChannel.cpp b/netwerk/protocol/data/nsDataChannel.cpp
new file mode 100644
index 0000000000..87b00adea3
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.cpp
@@ -0,0 +1,146 @@
+/* -*- 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/. */
+
+// data implementation
+
+#include "nsDataChannel.h"
+
+#include "mozilla/Base64.h"
+#include "nsDataHandler.h"
+#include "nsIInputStream.h"
+#include "nsEscape.h"
+#include "nsStringStream.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/ContentParent.h"
+
+using namespace mozilla;
+
+/**
+ * Helper for performing a fallible unescape.
+ *
+ * @param aStr The string to unescape.
+ * @param aBuffer Buffer to unescape into if necessary.
+ * @param rv Out: nsresult indicating success or failure of unescaping.
+ * @return Reference to the string containing the unescaped data.
+ */
+const nsACString& Unescape(const nsACString& aStr, nsACString& aBuffer,
+ nsresult* rv) {
+ MOZ_ASSERT(rv);
+
+ bool appended = false;
+ *rv = NS_UnescapeURL(aStr.Data(), aStr.Length(), /* aFlags = */ 0, aBuffer,
+ appended, mozilla::fallible);
+ if (NS_FAILED(*rv) || !appended) {
+ return aStr;
+ }
+
+ return aBuffer;
+}
+
+nsresult nsDataChannel::OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) {
+ NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+
+ // In order to avoid potentially building up a new path including the
+ // ref portion of the URI, which we don't care about, we clone a version
+ // of the URI that does not have a ref and in most cases should share
+ // string buffers with the original URI.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_GetURIWithoutRef(URI(), getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString contentType, contentCharset;
+ nsDependentCSubstring dataRange;
+ bool lBase64;
+ rv = nsDataHandler::ParsePathWithoutRef(path, contentType, &contentCharset,
+ lBase64, &dataRange, &mMimeType);
+ if (NS_FAILED(rv)) return rv;
+
+ // This will avoid a copy if nothing needs to be unescaped.
+ nsAutoCString unescapedBuffer;
+ const nsACString& data = Unescape(dataRange, unescapedBuffer, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (lBase64 && &data == &unescapedBuffer) {
+ // Don't allow spaces in base64-encoded content. This is only
+ // relevant for escaped spaces; other spaces are stripped in
+ // NewURI. We know there were no escaped spaces if the data buffer
+ // wasn't used in |Unescape|.
+ unescapedBuffer.StripWhitespace();
+ }
+
+ nsCOMPtr<nsIInputStream> bufInStream;
+ uint32_t contentLen;
+ if (lBase64) {
+ nsAutoCString decodedData;
+ rv = Base64Decode(data, decodedData);
+ if (NS_FAILED(rv)) {
+ // Returning this error code instead of what Base64Decode returns
+ // (NS_ERROR_ILLEGAL_VALUE) will prevent rendering of redirect response
+ // content by HTTP channels. It's also more logical error to return.
+ // Here we know the URL is actually corrupted.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ contentLen = decodedData.Length();
+ rv = NS_NewCStringInputStream(getter_AddRefs(bufInStream), decodedData);
+ } else {
+ contentLen = data.Length();
+ rv = NS_NewCStringInputStream(getter_AddRefs(bufInStream), data);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ SetContentType(contentType);
+ SetContentCharset(contentCharset);
+ mContentLength = contentLen;
+
+ // notify "data-channel-opened" observers
+ MaybeSendDataChannelOpenNotification();
+
+ bufInStream.forget(result);
+
+ return NS_OK;
+}
+
+nsresult nsDataChannel::MaybeSendDataChannelOpenNotification() {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (!obsService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = GetLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isTopLevel;
+ rv = loadInfo->GetIsTopLevelLoad(&isTopLevel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint64_t browsingContextID;
+ rv = loadInfo->GetBrowsingContextID(&browsingContextID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if ((browsingContextID != 0 && isTopLevel) ||
+ !loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ obsService->NotifyObservers(static_cast<nsIChannel*>(this),
+ "data-channel-opened", nullptr);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/protocol/data/nsDataChannel.h b/netwerk/protocol/data/nsDataChannel.h
new file mode 100644
index 0000000000..d7313d66a0
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// data implementation header
+
+#ifndef nsDataChannel_h___
+#define nsDataChannel_h___
+
+#include "nsBaseChannel.h"
+
+class nsIInputStream;
+
+class nsDataChannel : public nsBaseChannel {
+ public:
+ explicit nsDataChannel(nsIURI* uri) { SetURI(uri); }
+
+ const nsACString& MimeType() const { return mMimeType; }
+
+ protected:
+ [[nodiscard]] virtual nsresult OpenContentStream(
+ bool async, nsIInputStream** result, nsIChannel** channel) override;
+
+ nsCString mMimeType;
+
+ private:
+ nsresult MaybeSendDataChannelOpenNotification();
+};
+
+#endif /* nsDataChannel_h___ */
diff --git a/netwerk/protocol/data/nsDataHandler.cpp b/netwerk/protocol/data/nsDataHandler.cpp
new file mode 100644
index 0000000000..d3a5743097
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.cpp
@@ -0,0 +1,272 @@
+/* -*- 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 "nsDataChannel.h"
+#include "nsDataHandler.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "nsIOService.h"
+#include "DataChannelChild.h"
+#include "nsNetUtil.h"
+#include "nsSimpleURI.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/dom/MimeType.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Try.h"
+#include "DefaultURI.h"
+
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsDataHandler, nsIProtocolHandler, nsISupportsWeakReference)
+
+nsresult nsDataHandler::Create(const nsIID& aIID, void** aResult) {
+ RefPtr<nsDataHandler> ph = new nsDataHandler();
+ return ph->QueryInterface(aIID, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsDataHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("data");
+ return NS_OK;
+}
+
+/* static */ nsresult nsDataHandler::CreateNewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** result) {
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString contentType;
+ bool base64;
+ MOZ_TRY(ParseURI(aSpec, contentType, /* contentCharset = */ nullptr, base64,
+ /* dataBuffer = */ nullptr));
+
+ // Strip whitespace unless this is text, where whitespace is important
+ // Don't strip escaped whitespace though (bug 391951)
+ nsresult rv;
+ if (base64 || (StaticPrefs::network_url_strip_data_url_whitespace() &&
+ strncmp(contentType.get(), "text/", 5) != 0 &&
+ contentType.Find("xml") == kNotFound)) {
+ // it's ascii encoded binary, don't let any spaces in
+ rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator())
+ .Apply(&nsISimpleURIMutator::SetSpecAndFilterWhitespace, aSpec,
+ nullptr)
+ .Finalize(uri);
+ } else {
+ rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(uri);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ // use DefaultURI to check for validity when we have possible hostnames
+ // since nsSimpleURI doesn't know about hostnames
+ auto pos = aSpec.Find("data:");
+ if (pos != kNotFound) {
+ nsDependentCSubstring rest(aSpec, pos + sizeof("data:") - 1, -1);
+ if (StringBeginsWith(rest, "//"_ns)) {
+ nsCOMPtr<nsIURI> uriWithHost;
+ rv = NS_MutateURI(new mozilla::net::DefaultURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(uriWithHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ uri.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ RefPtr<nsDataChannel> channel;
+ if (XRE_IsParentProcess()) {
+ channel = new nsDataChannel(uri);
+ } else {
+ channel = new mozilla::net::DataChannelChild(uri);
+ }
+
+ // set the loadInfo on the new channel
+ nsresult rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::AllowPort(int32_t port, const char* scheme, bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+namespace {
+
+bool TrimSpacesAndBase64(nsACString& aMimeType) {
+ const char* beg = aMimeType.BeginReading();
+ const char* end = aMimeType.EndReading();
+
+ // trim leading and trailing spaces
+ while (beg < end && NS_IsHTTPWhitespace(*beg)) {
+ ++beg;
+ }
+ if (beg == end) {
+ aMimeType.Truncate();
+ return false;
+ }
+ while (end > beg && NS_IsHTTPWhitespace(*(end - 1))) {
+ --end;
+ }
+ if (beg == end) {
+ aMimeType.Truncate();
+ return false;
+ }
+
+ // trim trailing `; base64` (if any) and remember it
+ const char* pos = end - 1;
+ bool foundBase64 = false;
+ if (pos > beg && *pos == '4' && --pos > beg && *pos == '6' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 'e' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 's' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 'a' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 'b') {
+ while (--pos > beg && NS_IsHTTPWhitespace(*pos)) {
+ }
+ if (pos >= beg && *pos == ';') {
+ end = pos;
+ foundBase64 = true;
+ }
+ }
+
+ // actually trim off the spaces and trailing base64, returning if we found it.
+ const char* s = aMimeType.BeginReading();
+ aMimeType.Assign(Substring(aMimeType, beg - s, end - s));
+ return foundBase64;
+}
+
+} // namespace
+
+nsresult nsDataHandler::ParsePathWithoutRef(const nsACString& aPath,
+ nsCString& aContentType,
+ nsCString* aContentCharset,
+ bool& aIsBase64,
+ nsDependentCSubstring* aDataBuffer,
+ nsCString* aMimeType) {
+ static constexpr auto kCharset = "charset"_ns;
+
+ // This implements https://fetch.spec.whatwg.org/#data-url-processor
+ // It also returns the full mimeType in aMimeType so fetch/XHR may access it
+ // for content-length headers. The contentType and charset parameters retain
+ // our legacy behavior, as much Gecko code generally expects GetContentType
+ // to yield only the MimeType's essence, not its full value with parameters.
+
+ aIsBase64 = false;
+
+ int32_t commaIdx = aPath.FindChar(',');
+
+ // This is a hack! When creating a URL using the DOM API we want to ignore
+ // if a comma is missing. But if we're actually loading a data: URI, in which
+ // case aContentCharset is not null, then we want to return an error if a
+ // comma is missing.
+ if (aContentCharset && commaIdx == kNotFound) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // "Let mimeType be the result of collecting a sequence of code points that
+ // are not equal to U+002C (,), given position."
+ nsCString mimeType(Substring(aPath, 0, commaIdx));
+
+ // "Strip leading and trailing ASCII whitespace from mimeType."
+ // "If mimeType ends with U+003B (;), followed by zero or more U+0020 SPACE,
+ // followed by an ASCII case-insensitive match for "base64", then ..."
+ aIsBase64 = TrimSpacesAndBase64(mimeType);
+
+ // "If mimeType starts with ";", then prepend "text/plain" to mimeType."
+ if (mimeType.Length() > 0 && mimeType.CharAt(0) == ';') {
+ mimeType = "text/plain"_ns + mimeType;
+ }
+
+ // "Let mimeTypeRecord be the result of parsing mimeType."
+ // This also checks for instances of ;base64 in the middle of the MimeType.
+ // This is against the current spec, but we're doing it because we have
+ // historically seen webcompat issues relying on this (see bug 781693).
+ if (mozilla::UniquePtr<CMimeType> parsed = CMimeType::Parse(mimeType)) {
+ parsed->GetEssence(aContentType);
+ if (aContentCharset) {
+ parsed->GetParameterValue(kCharset, *aContentCharset);
+ }
+ if (aMimeType) {
+ parsed->Serialize(*aMimeType);
+ }
+ if (parsed->IsBase64() &&
+ !StaticPrefs::network_url_strict_data_url_base64_placement()) {
+ aIsBase64 = true;
+ }
+ } else {
+ // "If mimeTypeRecord is failure, then set mimeTypeRecord to
+ // text/plain;charset=US-ASCII."
+ aContentType.AssignLiteral("text/plain");
+ if (aContentCharset) {
+ aContentCharset->AssignLiteral("US-ASCII");
+ }
+ if (aMimeType) {
+ aMimeType->AssignLiteral("text/plain;charset=US-ASCII");
+ }
+ }
+
+ if (aDataBuffer) {
+ aDataBuffer->Rebind(aPath, commaIdx + 1);
+ }
+
+ return NS_OK;
+}
+
+static inline char ToLower(const char c) {
+ if (c >= 'A' && c <= 'Z') {
+ return char(c + ('a' - 'A'));
+ }
+ return c;
+}
+
+nsresult nsDataHandler::ParseURI(const nsACString& spec, nsCString& contentType,
+ nsCString* contentCharset, bool& isBase64,
+ nsCString* dataBuffer) {
+ static constexpr auto kDataScheme = "data:"_ns;
+
+ // move past "data:"
+ const char* pos = std::search(
+ spec.BeginReading(), spec.EndReading(), kDataScheme.BeginReading(),
+ kDataScheme.EndReading(),
+ [](const char a, const char b) { return ToLower(a) == ToLower(b); });
+ if (pos == spec.EndReading()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ uint32_t scheme = pos - spec.BeginReading();
+ scheme += kDataScheme.Length();
+
+ // Find the start of the hash ref if present.
+ int32_t hash = spec.FindChar('#', scheme);
+
+ auto pathWithoutRef = Substring(spec, scheme, hash != kNotFound ? hash : -1);
+ nsDependentCSubstring dataRange;
+ nsresult rv = ParsePathWithoutRef(pathWithoutRef, contentType, contentCharset,
+ isBase64, &dataRange);
+ if (NS_SUCCEEDED(rv) && dataBuffer) {
+ if (!dataBuffer->Assign(dataRange, mozilla::fallible)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return rv;
+}
diff --git a/netwerk/protocol/data/nsDataHandler.h b/netwerk/protocol/data/nsDataHandler.h
new file mode 100644
index 0000000000..4796f0f453
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDataHandler_h___
+#define nsDataHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsDataHandler : public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ virtual ~nsDataHandler() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsDataHandler methods:
+ nsDataHandler() = default;
+
+ static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result);
+
+ // Define a Create method to be used with a factory:
+ [[nodiscard]] static nsresult Create(const nsIID& aIID, void** aResult);
+
+ // Parse a data: URI and return the individual parts
+ // (the given spec will temporarily be modified but will be returned
+ // to the original before returning)
+ // contentCharset and dataBuffer can be nullptr if they are not needed.
+ [[nodiscard]] static nsresult ParseURI(const nsACString& spec,
+ nsCString& contentType,
+ nsCString* contentCharset,
+ bool& isBase64, nsCString* dataBuffer);
+
+ // Parse the path portion of a data: URI and return the individual parts.
+ //
+ // Note: The path is assumed *not* to have a ref portion.
+ //
+ // @arg aPath The path portion of the spec. Must not have ref portion.
+ // @arg aContentType Out param, will hold the parsed content type.
+ // @arg aContentCharset Optional, will hold the charset if specified.
+ // @arg aIsBase64 Out param, indicates if the data is base64 encoded.
+ // @arg aDataBuffer Optional, will reference the substring in |aPath| that
+ // contains the data portion of the path. No copy is made.
+ [[nodiscard]] static nsresult ParsePathWithoutRef(
+ const nsACString& aPath, nsCString& aContentType,
+ nsCString* aContentCharset, bool& aIsBase64,
+ nsDependentCSubstring* aDataBuffer, nsCString* aMimeType = nullptr);
+};
+
+#endif /* nsDataHandler_h___ */
diff --git a/netwerk/protocol/data/nsDataModule.cpp b/netwerk/protocol/data/nsDataModule.cpp
new file mode 100644
index 0000000000..8bcda94362
--- /dev/null
+++ b/netwerk/protocol/data/nsDataModule.cpp
@@ -0,0 +1,15 @@
+/* -*- 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 "nsIGenericFactory.h"
+#include "nsDataHandler.h"
+
+// The list of components we register
+static const nsModuleComponentInfo components[] = {
+ {"Data Protocol Handler", NS_DATAHANDLER_CID,
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data", nsDataHandler::Create},
+};
+
+NS_IMPL_NSGETMODULE(nsDataProtocolModule, components)
diff --git a/netwerk/protocol/file/FileChannelChild.cpp b/netwerk/protocol/file/FileChannelChild.cpp
new file mode 100644
index 0000000000..fa9cc00584
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelChild.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileChannelChild.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(FileChannelChild, nsFileChannel, nsIChildChannel)
+
+FileChannelChild::FileChannelChild(nsIURI* uri) : nsFileChannel(uri) {}
+
+NS_IMETHODIMP
+FileChannelChild::ConnectParent(uint32_t id) {
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gNeckoChild->SendPFileChannelConstructor(this, id)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelChild::CompleteRedirectSetup(nsIStreamListener* listener) {
+ nsresult rv;
+
+ rv = AsyncOpen(listener);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (CanSend()) {
+ Unused << Send__delete__(this);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/file/FileChannelChild.h b/netwerk/protocol/file/FileChannelChild.h
new file mode 100644
index 0000000000..e6d67b7d1c
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelChild.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__FileChannelChild_h
+#define mozilla__net__FileChannelChild_h
+
+#include "nsFileChannel.h"
+#include "nsIChildChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PFileChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+class FileChannelChild : public nsFileChannel,
+ public nsIChildChannel,
+ public PFileChannelChild {
+ public:
+ explicit FileChannelChild(nsIURI* uri);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ private:
+ ~FileChannelChild() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* mozilla__net__FileChannelChild_h */
diff --git a/netwerk/protocol/file/FileChannelParent.cpp b/netwerk/protocol/file/FileChannelParent.cpp
new file mode 100644
index 0000000000..e54fb11260
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelParent.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+#ifdef FUZZING_SNAPSHOT
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__
+#else
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(FileChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool FileChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+
+ MOZ_ALWAYS_SUCCEEDS_FUZZING(
+ NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::Delete() {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+void FileChannelParent::ActorDestroy(ActorDestroyReason why) {}
+
+NS_IMETHODIMP
+FileChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsDataChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+FileChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/file/FileChannelParent.h b/netwerk/protocol/file/FileChannelParent.h
new file mode 100644
index 0000000000..f2a10d8f87
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelParent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__FileChannelParent_h
+#define mozilla__net__FileChannelParent_h
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PFileChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects to file:, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class FileChannelParent : public nsIParentChannel, public PFileChannelParent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ [[nodiscard]] bool Init(const uint64_t& aChannelId);
+
+ private:
+ ~FileChannelParent() = default;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* mozilla__net__FileChannelParent_h */
diff --git a/netwerk/protocol/file/moz.build b/netwerk/protocol/file/moz.build
new file mode 100644
index 0000000000..821e20e1c5
--- /dev/null
+++ b/netwerk/protocol/file/moz.build
@@ -0,0 +1,40 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: File")
+
+EXPORTS.mozilla.net += [
+ "FileChannelChild.h",
+ "FileChannelParent.h",
+ "nsFileProtocolHandler.h",
+]
+
+EXPORTS += [
+ "nsFileChannel.h",
+]
+
+XPIDL_SOURCES += [
+ "nsIFileChannel.idl",
+ "nsIFileProtocolHandler.idl",
+]
+
+XPIDL_MODULE = "necko_file"
+
+UNIFIED_SOURCES += [
+ "FileChannelChild.cpp",
+ "FileChannelParent.cpp",
+ "nsFileChannel.cpp",
+ "nsFileProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/netwerk/protocol/file/nsFileChannel.cpp b/netwerk/protocol/file/nsFileChannel.cpp
new file mode 100644
index 0000000000..096f8807ba
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -0,0 +1,569 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsFileChannel.h"
+#include "nsBaseContentStream.h"
+#include "nsDirectoryIndexStream.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIOutputStream.h"
+#include "nsIFileStreams.h"
+#include "nsFileProtocolHandler.h"
+#include "nsProxyRelease.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "../protocol/http/nsHttpHandler.h"
+
+#include "nsIFileURL.h"
+#include "nsIURIMutator.h"
+#include "nsIFile.h"
+#include "nsIMIMEService.h"
+#include "prio.h"
+#include <algorithm>
+
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+//-----------------------------------------------------------------------------
+
+class nsFileCopyEvent : public Runnable {
+ public:
+ nsFileCopyEvent(nsIOutputStream* dest, nsIInputStream* source, int64_t len)
+ : mozilla::Runnable("nsFileCopyEvent"),
+ mDest(dest),
+ mSource(source),
+ mLen(len),
+ mStatus(NS_OK),
+ mInterruptStatus(NS_OK) {}
+
+ // Read the current status of the file copy operation.
+ nsresult Status() { return mStatus; }
+
+ // Call this method to perform the file copy synchronously.
+ void DoCopy();
+
+ // Call this method to perform the file copy on a background thread. The
+ // callback is dispatched when the file copy completes.
+ nsresult Dispatch(nsIRunnable* callback, nsITransportEventSink* sink,
+ nsIEventTarget* target);
+
+ // Call this method to interrupt a file copy operation that is occuring on
+ // a background thread. The status parameter passed to this function must
+ // be a failure code and is set as the status of this file copy operation.
+ void Interrupt(nsresult status) {
+ NS_ASSERTION(NS_FAILED(status), "must be a failure code");
+ mInterruptStatus = status;
+ }
+
+ NS_IMETHOD Run() override {
+ DoCopy();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsCOMPtr<nsIRunnable> mCallback;
+ nsCOMPtr<nsITransportEventSink> mSink;
+ nsCOMPtr<nsIOutputStream> mDest;
+ nsCOMPtr<nsIInputStream> mSource;
+ int64_t mLen;
+ nsresult mStatus; // modified on i/o thread only
+ nsresult mInterruptStatus; // modified on main thread only
+};
+
+void nsFileCopyEvent::DoCopy() {
+ // We'll copy in chunks this large by default. This size affects how
+ // frequently we'll check for interrupts.
+ const int32_t chunk =
+ nsIOService::gDefaultSegmentSize * nsIOService::gDefaultSegmentCount;
+
+ nsresult rv = NS_OK;
+
+ int64_t len = mLen, progress = 0;
+ while (len) {
+ // If we've been interrupted, then stop copying.
+ rv = mInterruptStatus;
+ if (NS_FAILED(rv)) break;
+
+ int32_t num = std::min((int32_t)len, chunk);
+
+ uint32_t result;
+ rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result);
+ if (NS_FAILED(rv)) break;
+ if (result != (uint32_t)num) {
+ // stopped prematurely (out of disk space)
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ }
+
+ // Dispatch progress notification
+ if (mSink) {
+ progress += num;
+ mSink->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING, progress, mLen);
+ }
+
+ len -= num;
+ }
+
+ if (NS_FAILED(rv)) mStatus = rv;
+
+ // Close the output stream before notifying our callback so that others may
+ // freely "play" with the file.
+ mDest->Close();
+
+ // Notify completion
+ if (mCallback) {
+ mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL);
+
+ // Release the callback on the target thread to avoid destroying stuff on
+ // the wrong thread.
+ NS_ProxyRelease("nsFileCopyEvent::mCallback", mCallbackTarget,
+ mCallback.forget());
+ }
+}
+
+nsresult nsFileCopyEvent::Dispatch(nsIRunnable* callback,
+ nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ // Use the supplied event target for all asynchronous operations.
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ // Build a coalescing proxy for progress events
+ nsresult rv =
+ net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink, target);
+
+ if (NS_FAILED(rv)) return rv;
+
+ // Dispatch ourselves to I/O thread pool...
+ nsCOMPtr<nsIEventTarget> pool =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ return pool->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+
+// This is a dummy input stream that when read, performs the file copy. The
+// copy happens on a background thread via mCopyEvent.
+
+class nsFileUploadContentStream : public nsBaseContentStream {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsFileUploadContentStream,
+ nsBaseContentStream)
+
+ nsFileUploadContentStream(bool nonBlocking, nsIOutputStream* dest,
+ nsIInputStream* source, int64_t len,
+ nsITransportEventSink* sink)
+ : nsBaseContentStream(nonBlocking),
+ mCopyEvent(new nsFileCopyEvent(dest, source, len)),
+ mSink(sink) {}
+
+ bool IsInitialized() { return mCopyEvent != nullptr; }
+
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun fun, void* closure, uint32_t count,
+ uint32_t* result) override;
+ NS_IMETHOD AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t count, nsIEventTarget* target) override;
+
+ private:
+ virtual ~nsFileUploadContentStream() = default;
+
+ void OnCopyComplete();
+
+ RefPtr<nsFileCopyEvent> mCopyEvent;
+ nsCOMPtr<nsITransportEventSink> mSink;
+};
+
+NS_IMETHODIMP
+nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0; // nothing is ever actually read from this stream
+
+ if (IsClosed()) return NS_OK;
+
+ if (IsNonBlocking()) {
+ // Inform the caller that they will have to wait for the copy operation to
+ // complete asynchronously. We'll kick of the copy operation once they
+ // call AsyncWait.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Perform copy synchronously, and then close out the stream.
+ mCopyEvent->DoCopy();
+ nsresult status = mCopyEvent->Status();
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+ return status;
+}
+
+NS_IMETHODIMP
+nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback* callback,
+ uint32_t flags, uint32_t count,
+ nsIEventTarget* target) {
+ nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target);
+ if (NS_FAILED(rv) || IsClosed()) return rv;
+
+ if (IsNonBlocking()) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod("nsFileUploadContentStream::OnCopyComplete", this,
+ &nsFileUploadContentStream::OnCopyComplete);
+ mCopyEvent->Dispatch(callback, mSink, target);
+ }
+
+ return NS_OK;
+}
+
+void nsFileUploadContentStream::OnCopyComplete() {
+ // This method is being called to indicate that we are done copying.
+ nsresult status = mCopyEvent->Status();
+
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+}
+
+//-----------------------------------------------------------------------------
+
+nsFileChannel::nsFileChannel(nsIURI* uri) : mUploadLength(0), mFileURI(uri) {}
+
+nsresult nsFileChannel::Init() {
+ NS_ENSURE_STATE(mLoadInfo);
+
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ MOZ_ALWAYS_SUCCEEDS(handler->NewChannelId(mChannelId));
+
+ // If we have a link file, we should resolve its target right away.
+ // This is to protect against a same origin attack where the same link file
+ // can point to different resources right after the first resource is loaded.
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIURI> targetURI;
+#ifdef XP_WIN
+ nsAutoString fileTarget;
+#else
+ nsAutoCString fileTarget;
+#endif
+ nsCOMPtr<nsIFile> resolvedFile;
+ bool symLink;
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mFileURI);
+ if (fileURL && NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
+ NS_SUCCEEDED(file->IsSymlink(&symLink)) && symLink &&
+#ifdef XP_WIN
+ NS_SUCCEEDED(file->GetTarget(fileTarget)) &&
+ NS_SUCCEEDED(
+ NS_NewLocalFile(fileTarget, true, getter_AddRefs(resolvedFile))) &&
+#else
+ NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) &&
+ NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, true,
+ getter_AddRefs(resolvedFile))) &&
+#endif
+ NS_SUCCEEDED(
+ NS_NewFileURI(getter_AddRefs(targetURI), resolvedFile, nullptr))) {
+ // Make an effort to match up the query strings.
+ nsCOMPtr<nsIURL> origURL = do_QueryInterface(mFileURI);
+ nsCOMPtr<nsIURL> targetURL = do_QueryInterface(targetURI);
+ nsAutoCString queryString;
+ if (origURL && targetURL && NS_SUCCEEDED(origURL->GetQuery(queryString))) {
+ Unused
+ << NS_MutateURI(targetURI).SetQuery(queryString).Finalize(targetURI);
+ }
+
+ SetURI(targetURI);
+ SetOriginalURI(mFileURI);
+ mLoadInfo->SetResultPrincipalURI(targetURI);
+ } else {
+ SetURI(mFileURI);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFileChannel::MakeFileInputStream(nsIFile* file,
+ nsCOMPtr<nsIInputStream>& stream,
+ nsCString& contentType,
+ bool async) {
+ // we accept that this might result in a disk hit to stat the file
+ bool isDir;
+ nsresult rv = file->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ CheckForBrokenChromeURL(mLoadInfo, OriginalURI());
+ }
+
+ if (async && (NS_ERROR_FILE_NOT_FOUND == rv)) {
+ // We don't return "Not Found" errors here. Since we could not find
+ // the file, it's not a directory anyway.
+ isDir = false;
+ } else {
+ return rv;
+ }
+ }
+
+ if (isDir) {
+ rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(stream));
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
+ contentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
+ }
+ } else {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
+ async ? nsIFileInputStream::DEFER_OPEN : 0);
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
+ // Use file extension to infer content type
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mime->GetTypeFromFile(file, contentType);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsFileChannel::OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) {
+ // NOTE: the resulting file is a clone, so it is safe to pass it to the
+ // file input stream which will be read on a background thread.
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> newURI;
+ if (NS_SUCCEEDED(fileHandler->ReadURLFile(file, getter_AddRefs(newURI))) ||
+ NS_SUCCEEDED(fileHandler->ReadShellLink(file, getter_AddRefs(newURI)))) {
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannel(getter_AddRefs(newChannel), newURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv)) return rv;
+
+ *result = nullptr;
+ newChannel.forget(channel);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+
+ if (mUploadStream) {
+ // Pass back a nsFileUploadContentStream instance that knows how to perform
+ // the file copy when "read" (the resulting stream in this case does not
+ // actually return any data).
+
+ nsCOMPtr<nsIOutputStream> fileStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), file,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+ PR_IRUSR | PR_IWUSR);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsFileUploadContentStream> uploadStream =
+ new nsFileUploadContentStream(async, fileStream, mUploadStream,
+ mUploadLength, this);
+ if (!uploadStream || !uploadStream->IsInitialized()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ stream = std::move(uploadStream);
+
+ mContentLength = 0;
+
+ // Since there isn't any content to speak of we just set the content-type
+ // to something other than "unknown" to avoid triggering the content-type
+ // sniffer code in nsBaseChannel.
+ // However, don't override explicitly set types.
+ if (!HasContentTypeHint()) {
+ SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
+ }
+ } else {
+ nsAutoCString contentType;
+ rv = MakeFileInputStream(file, stream, contentType, async);
+ if (NS_FAILED(rv)) return rv;
+
+ EnableSynthesizedProgressEvents(true);
+
+ // fixup content length and type
+
+ // when we are called from asyncOpen, the content length fixup will be
+ // performed on a background thread and block the listener invocation via
+ // ListenerBlockingPromise method
+ if (!async && mContentLength < 0) {
+ rv = FixupContentLength(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!contentType.IsEmpty()) {
+ SetContentType(contentType);
+ }
+ }
+
+ // notify "file-channel-opened" observers
+ MaybeSendFileOpenNotification();
+
+ *result = nullptr;
+ stream.swap(*result);
+ return NS_OK;
+}
+
+nsresult nsFileChannel::ListenerBlockingPromise(BlockingPromise** aPromise) {
+ NS_ENSURE_ARG(aPromise);
+ *aPromise = nullptr;
+
+ if (mContentLength >= 0) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEventTarget> sts(
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+ if (!sts) {
+ return FixupContentLength(true);
+ }
+
+ RefPtr<TaskQueue> taskQueue = TaskQueue::Create(sts.forget(), "FileChannel");
+ RefPtr<nsFileChannel> self = this;
+ RefPtr<BlockingPromise> promise =
+ mozilla::InvokeAsync(taskQueue, __func__, [self{std::move(self)}]() {
+ nsresult rv = self->FixupContentLength(true);
+ if (NS_FAILED(rv)) {
+ return BlockingPromise::CreateAndReject(rv, __func__);
+ }
+ return BlockingPromise::CreateAndResolve(NS_OK, __func__);
+ });
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+nsresult nsFileChannel::FixupContentLength(bool async) {
+ MOZ_ASSERT(mContentLength < 0);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int64_t size;
+ rv = file->GetFileSize(&size);
+ if (NS_FAILED(rv)) {
+ if (async && NS_ERROR_FILE_NOT_FOUND == rv) {
+ size = 0;
+ } else {
+ return rv;
+ }
+ }
+ mContentLength = size;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, nsBaseChannel, nsIUploadChannel,
+ nsIFileChannel, nsIIdentChannel)
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIFileChannel
+
+NS_IMETHODIMP
+nsFileChannel::GetFile(nsIFile** file) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(URI());
+ NS_ENSURE_STATE(fileURL);
+
+ // This returns a cloned nsIFile
+ return fileURL->GetFile(file);
+}
+
+nsresult nsFileChannel::MaybeSendFileOpenNotification() {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (!obsService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = GetLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isTopLevel;
+ rv = loadInfo->GetIsTopLevelLoad(&isTopLevel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint64_t browsingContextID;
+ rv = loadInfo->GetBrowsingContextID(&browsingContextID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if ((browsingContextID != 0 && isTopLevel) ||
+ !loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ obsService->NotifyObservers(static_cast<nsIIdentChannel*>(this),
+ "file-channel-opened", nullptr);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIUploadChannel
+
+NS_IMETHODIMP
+nsFileChannel::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentType,
+ int64_t contentLength) {
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+
+ if ((mUploadStream = stream)) {
+ mUploadLength = contentLength;
+ if (mUploadLength < 0) {
+ // Make sure we know how much data we are uploading.
+ uint64_t avail;
+ nsresult rv = mUploadStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ // if this doesn't fit in the javascript MAX_SAFE_INTEGER
+ // pretend we don't know the size
+ mUploadLength = InScriptableRange(avail) ? avail : -1;
+ }
+ } else {
+ mUploadLength = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileChannel::GetUploadStream(nsIInputStream** result) {
+ *result = do_AddRef(mUploadStream).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIIdentChannel
+
+NS_IMETHODIMP
+nsFileChannel::GetChannelId(uint64_t* aChannelId) {
+ *aChannelId = mChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileChannel::SetChannelId(uint64_t aChannelId) {
+ mChannelId = aChannelId;
+ return NS_OK;
+}
diff --git a/netwerk/protocol/file/nsFileChannel.h b/netwerk/protocol/file/nsFileChannel.h
new file mode 100644
index 0000000000..781ce5b7af
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et 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 nsFileChannel_h__
+#define nsFileChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIFileChannel.h"
+#include "nsIUploadChannel.h"
+
+class nsFileChannel : public nsBaseChannel,
+ public nsIFileChannel,
+ public nsIUploadChannel,
+ public nsIIdentChannel {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILECHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_FORWARD_NSIREQUEST(nsBaseChannel::)
+ NS_FORWARD_NSICHANNEL(nsBaseChannel::)
+ NS_DECL_NSIIDENTCHANNEL
+
+ explicit nsFileChannel(nsIURI* uri);
+
+ nsresult Init();
+
+ protected:
+ ~nsFileChannel() = default;
+
+ // Called to construct a blocking file input stream for the given file. This
+ // method also returns a best guess at the content-type for the data stream.
+ // NOTE: If the channel has a type hint set, contentType will be left
+ // untouched. The caller should not use it in that case.
+ [[nodiscard]] nsresult MakeFileInputStream(nsIFile* file,
+ nsCOMPtr<nsIInputStream>& stream,
+ nsCString& contentType,
+ bool async);
+
+ [[nodiscard]] virtual nsresult OpenContentStream(
+ bool async, nsIInputStream** result, nsIChannel** channel) override;
+
+ // Implementing the pump blocking promise to fixup content length on a
+ // background thread prior to calling on mListener
+ virtual nsresult ListenerBlockingPromise(BlockingPromise** promise) override;
+
+ private:
+ nsresult FixupContentLength(bool async);
+ nsresult MaybeSendFileOpenNotification();
+
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ int64_t mUploadLength;
+ nsCOMPtr<nsIURI> mFileURI;
+ uint64_t mChannelId = 0;
+};
+
+#endif // !nsFileChannel_h__
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.cpp b/netwerk/protocol/file/nsFileProtocolHandler.cpp
new file mode 100644
index 0000000000..10a1b9c029
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:ts=4 sw=2 sts=2 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFile.h"
+#include "nsFileProtocolHandler.h"
+#include "nsFileChannel.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+#include "nsIURIMutator.h"
+
+#include "nsNetUtil.h"
+
+#include "FileChannelChild.h"
+
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/net/NeckoCommon.h"
+
+// URL file handling, copied and modified from
+// xpfe/components/bookmarks/src/nsBookmarksService.cpp
+#ifdef XP_WIN
+# include <shlobj.h>
+# include <intshcut.h>
+# include "nsIFileURL.h"
+# ifdef CompareString
+# undef CompareString
+# endif
+#endif
+
+// URL file handling for freedesktop.org
+#ifdef XP_UNIX
+# include "nsINIParser.h"
+# define DESKTOP_ENTRY_SECTION "Desktop Entry"
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult nsFileProtocolHandler::Init() { return NS_OK; }
+
+NS_IMPL_ISUPPORTS(nsFileProtocolHandler, nsIFileProtocolHandler,
+ nsIProtocolHandler, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsIProtocolHandler methods:
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ nsAutoString path;
+ nsresult rv = aFile->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ if (path.Length() < 4) return NS_ERROR_NOT_AVAILABLE;
+ if (!StringTail(path, 4).LowerCaseEqualsLiteral(".url"))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ HRESULT result;
+
+ rv = NS_ERROR_NOT_AVAILABLE;
+
+ IUniformResourceLocatorW* urlLink = nullptr;
+ result =
+ ::CoCreateInstance(CLSID_InternetShortcut, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IUniformResourceLocatorW, (void**)&urlLink);
+ if (SUCCEEDED(result) && urlLink) {
+ IPersistFile* urlFile = nullptr;
+ result = urlLink->QueryInterface(IID_IPersistFile, (void**)&urlFile);
+ if (SUCCEEDED(result) && urlFile) {
+ result = urlFile->Load(path.get(), STGM_READ);
+ if (SUCCEEDED(result)) {
+ LPWSTR lpTemp = nullptr;
+
+ // The URL this method will give us back seems to be already
+ // escaped. Hence, do not do escaping of our own.
+ result = urlLink->GetURL(&lpTemp);
+ if (SUCCEEDED(result) && lpTemp) {
+ rv = NS_NewURI(aURI, nsDependentString(lpTemp));
+ // free the string that GetURL alloc'd
+ CoTaskMemFree(lpTemp);
+ }
+ }
+ urlFile->Release();
+ }
+ urlLink->Release();
+ }
+ return rv;
+}
+
+#elif defined(XP_UNIX)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ // We only support desktop files that end in ".desktop" like the spec says:
+ // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html
+ nsAutoCString leafName;
+ nsresult rv = aFile->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv) || !StringEndsWith(leafName, ".desktop"_ns)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool isFile = false;
+ rv = aFile->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsINIParser parser;
+ rv = parser.Init(aFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ parser.GetString(DESKTOP_ENTRY_SECTION, "Type", type);
+ if (!type.EqualsLiteral("Link")) return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString url;
+ rv = parser.GetString(DESKTOP_ENTRY_SECTION, "URL", url);
+ if (NS_FAILED(rv) || url.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_NewURI(aURI, url);
+}
+
+#else // other platforms
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+#endif // ReadURLFile()
+
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadShellLink(nsIFile* aFile, nsIURI** aURI) {
+#if defined(XP_WIN)
+ nsAutoString path;
+ MOZ_TRY(aFile->GetPath(path));
+
+ if (path.Length() < 4 ||
+ !StringTail(path, 4).LowerCaseEqualsLiteral(".lnk")) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<IPersistFile> persistFile;
+ RefPtr<IShellLinkW> shellLink;
+ WCHAR lpTemp[MAX_PATH];
+ // Get a pointer to the IPersistFile interface.
+ if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, getter_AddRefs(shellLink))) ||
+ FAILED(shellLink->QueryInterface(IID_IPersistFile,
+ getter_AddRefs(persistFile))) ||
+ FAILED(persistFile->Load(path.get(), STGM_READ)) ||
+ FAILED(shellLink->Resolve(nullptr, SLR_NO_UI)) ||
+ FAILED(shellLink->GetPath(lpTemp, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIFile> linkedFile;
+ MOZ_TRY(NS_NewLocalFile(nsDependentString(lpTemp), false,
+ getter_AddRefs(linkedFile)));
+ return NS_NewFileURI(aURI, linkedFile);
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("file");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ nsresult rv;
+
+ RefPtr<nsFileChannel> chan;
+ if (mozilla::net::IsNeckoChild()) {
+ chan = new mozilla::net::FileChannelChild(uri);
+ } else {
+ chan = new nsFileChannel(uri);
+ }
+
+ // set the loadInfo on the new channel ; must do this
+ // before calling Init() on it, since it needs the load
+ // info be already set.
+ rv = chan->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = chan->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *result = chan.forget().downcast<nsBaseChannel>().take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* result) {
+ // don't override anything.
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIFileProtocolHandler methods:
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURI(nsIFile* aFile, nsIURI** aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ RefPtr<nsIFile> file(aFile);
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ return NS_MutateURI(new mozilla::net::nsStandardURL::Mutator())
+ .Apply(&nsIFileURLMutator::SetFile, file)
+ .Finalize(aResult);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURIMutator(nsIFile* aFile,
+ nsIURIMutator** aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+
+ nsCOMPtr<nsIURIMutator> mutator = new mozilla::net::nsStandardURL::Mutator();
+ nsCOMPtr<nsIFileURLMutator> fileMutator = do_QueryInterface(mutator, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ rv = fileMutator->SetFile(aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mutator.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromFile(nsIFile* file, nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromActualFile(nsIFile* file,
+ nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromActualFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromDir(nsIFile* file, nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromDir(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetFileFromURLSpec(const nsACString& spec,
+ nsIFile** result) {
+ return net_GetFileFromURLSpec(spec, result);
+}
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.h b/netwerk/protocol/file/nsFileProtocolHandler.h
new file mode 100644
index 0000000000..ff2b30634a
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.h
@@ -0,0 +1,28 @@
+/* -*- 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 nsFileProtocolHandler_h__
+#define nsFileProtocolHandler_h__
+
+#include "nsIFileProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsIURIMutator;
+
+class nsFileProtocolHandler : public nsIFileProtocolHandler,
+ public nsSupportsWeakReference {
+ virtual ~nsFileProtocolHandler() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIFILEPROTOCOLHANDLER
+
+ nsFileProtocolHandler() = default;
+
+ [[nodiscard]] nsresult Init();
+};
+
+#endif // !nsFileProtocolHandler_h__
diff --git a/netwerk/protocol/file/nsIFileChannel.idl b/netwerk/protocol/file/nsIFileChannel.idl
new file mode 100644
index 0000000000..e5fcdecb4c
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileChannel.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+/**
+ * nsIFileChannel
+ */
+[scriptable, uuid(06169120-136d-45a5-b535-498f1f755ab7)]
+interface nsIFileChannel : nsISupports
+{
+ readonly attribute nsIFile file;
+};
diff --git a/netwerk/protocol/file/nsIFileProtocolHandler.idl b/netwerk/protocol/file/nsIFileProtocolHandler.idl
new file mode 100644
index 0000000000..d470dcee2a
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileProtocolHandler.idl
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIFile;
+interface nsIURIMutator;
+
+[scriptable, uuid(1fb25bd5-4354-4dcd-8d97-621b7b3ed2e4)]
+interface nsIFileProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * This method constructs a new file URI
+ *
+ * @param aFile nsIFile
+ * @return reference to a new nsIURI object
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * This method constructs a new file URI, and returns a URI mutator
+ * that has not yet been finalized, allowing the URI to be changed without
+ * being cloned.
+ *
+ * @param aFile nsIFile
+ * @return reference to a new nsIURIMutator object
+ */
+ nsIURIMutator newFileURIMutator(in nsIFile file);
+
+ /**
+ * DEPRECATED, AVOID IF AT ALL POSSIBLE.
+ *
+ * Calling this will cause IO on the calling thread, to determine
+ * if the file is a directory or file, and based on that behaves as
+ * if you called getURLSpecFromDir or getURLSpecFromActualFile,
+ * respectively. This IO may take multiple seconds (e.g. for network
+ * paths, slow external drives that need to be woken up, etc.).
+ *
+ * Usually, the caller should *know* that the `file` argument is
+ * either a directory (in which case it should call getURLSpecFromDir)
+ * or a non-directory file (in which case it should call
+ * getURLSpecFromActualFile), and not need to call this method.
+ */
+ [noscript] AUTF8String getURLSpecFromFile(in nsIFile file);
+
+ /**
+ * Converts a non-directory nsIFile to the corresponding URL string.
+ * NOTE: under some platforms this is a lossy conversion (e.g., Mac
+ * Carbon build). If the nsIFile is a local file, then the result
+ * will be a file:// URL string.
+ *
+ * The resulting string may contain URL-escaped characters.
+ *
+ * Should only be called on files which are not directories. If
+ * called on directories, the resulting URL may lack a trailing slash
+ * and cause relative URLs in such a document to misbehave.
+ */
+ AUTF8String getURLSpecFromActualFile(in nsIFile file);
+
+ /**
+ * Converts a directory nsIFile to the corresponding URL string.
+ * NOTE: under some platforms this is a lossy conversion (e.g., Mac
+ * Carbon build). If the nsIFile is a local file, then the result
+ * will be a file:// URL string.
+ *
+ * The resulting string may contain URL-escaped characters.
+ *
+ * Should only be called on files which are directories (will enforce
+ * the URL ends with a slash).
+ */
+ AUTF8String getURLSpecFromDir(in nsIFile file);
+
+ /**
+ * Converts the URL string into the corresponding nsIFile if possible.
+ * A local file will be created if the URL string begins with file://.
+ */
+ nsIFile getFileFromURLSpec(in AUTF8String url);
+
+ /**
+ * Takes a local file and tries to interpret it as an internet shortcut
+ * (e.g. .url files on windows).
+ * @param file The local file to read
+ * @return The URI the file refers to
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if the OS does not support such files.
+ * @throw NS_ERROR_NOT_AVAILABLE if this file is not an internet shortcut.
+ */
+ nsIURI readURLFile(in nsIFile file);
+
+ /**
+ * Takes a local file and tries to interpret it as a shell link file
+ * (.lnk files on Windows)
+ * @param file The local file to read
+ * @return The URI the file refers to
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if the OS does not support such files.
+ * @throw NS_ERROR_NOT_AVAILABLE if this file is not a shell link.
+ */
+ nsIURI readShellLink(in nsIFile file);
+};
diff --git a/netwerk/protocol/gio/GIOChannelChild.cpp b/netwerk/protocol/gio/GIOChannelChild.cpp
new file mode 100644
index 0000000000..2adb611666
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelChild.cpp
@@ -0,0 +1,458 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/NeckoChild.h"
+#include "GIOChannelChild.h"
+#include "nsGIOProtocolHandler.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsContentUtils.h"
+#include "nsIBrowserChild.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "base/compiler_specific.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsIURIMutator.h"
+#include "nsContentSecurityManager.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/Logging.h"
+
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+#undef LOG
+#define LOG(args) MOZ_LOG(gGIOLog, mozilla::LogLevel::Debug, args)
+namespace net {
+
+GIOChannelChild::GIOChannelChild(nsIURI* aUri)
+ : mEventQ(new ChannelEventQueue(static_cast<nsIChildChannel*>(this))) {
+ SetURI(aUri);
+ // We could support thread retargeting, but as long as we're being driven by
+ // IPDL on the main thread it doesn't buy us anything.
+ DisallowThreadRetargeting();
+}
+
+void GIOChannelChild::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void GIOChannelChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(GIOChannelChild, nsBaseChannel, nsIChildChannel)
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelChild::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("GIOChannelChild::AsyncOpen [this=%p]\n", this));
+
+ NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(
+ !static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately, as we've done since before e10s.
+ rv = NS_CheckPortSafety(nsBaseChannel::URI()); // Need to disambiguate,
+ // because in the child ipdl,
+ // a typedef URI is defined...
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ mListener = listener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ Maybe<mozilla::ipc::IPCStream> ipcStream;
+ mozilla::ipc::SerializeIPCStream(do_AddRef(mUploadStream), ipcStream,
+ /* aAllowLazy */ false);
+
+ uint32_t loadFlags = 0;
+ GetLoadFlags(&loadFlags);
+
+ GIOChannelOpenArgs openArgs;
+ SerializeURI(nsBaseChannel::URI(), openArgs.uri());
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.uploadStream() = ipcStream;
+ openArgs.loadFlags() = loadFlags;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This must happen before the constructor message is sent.
+ SetupNeckoTarget();
+
+ // The socket transport layer in the chrome process now has a logical ref to
+ // us until OnStopRequest is called.
+ AddIPDLReference();
+
+ if (!gNeckoChild->SendPGIOChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), openArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsPending = true;
+ mWasOpened = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::IsPending(bool* aResult) {
+ *aResult = mIsPending;
+ return NS_OK;
+}
+
+nsresult GIOChannelChild::OpenContentStream(bool aAsync,
+ nsIInputStream** aStream,
+ nsIChannel** aChannel) {
+ MOZ_CRASH("GIOChannel*Child* should never have OpenContentStream called!");
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvOnStartRequest(
+ const nsresult& aChannelStatus, const int64_t& aContentLength,
+ const nsACString& aContentType, const nsACString& aEntityID,
+ const URIParams& aURI) {
+ LOG(("GIOChannelChild::RecvOnStartRequest [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus,
+ aContentLength, aContentType = nsCString(aContentType),
+ aEntityID = nsCString(aEntityID), aURI]() {
+ self->DoOnStartRequest(aChannelStatus, aContentLength, aContentType,
+ aEntityID, aURI);
+ }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsACString& aContentType,
+ const nsACString& aEntityID,
+ const URIParams& aURI) {
+ LOG(("GIOChannelChild::DoOnStartRequest [this=%p]\n", this));
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ mContentLength = aContentLength;
+ SetContentType(aContentType);
+ mEntityID = aEntityID;
+
+ nsCString spec;
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ nsresult rv = uri->GetSpec(spec);
+ if (NS_SUCCEEDED(rv)) {
+ // Changes nsBaseChannel::URI()
+ rv = NS_MutateURI(mURI).SetSpec(spec).Finalize(mURI);
+ }
+
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnStartRequest(this);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvOnDataAvailable(
+ const nsresult& aChannelStatus, const nsACString& aData,
+ const uint64_t& aOffset, const uint32_t& aCount) {
+ LOG(("GIOChannelChild::RecvOnDataAvailable [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus,
+ aData = nsCString(aData), aOffset, aCount]() {
+ self->DoOnDataAvailable(aChannelStatus, aData, aOffset, aCount);
+ }));
+
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoOnDataAvailable(const nsresult& aChannelStatus,
+ const nsACString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount) {
+ LOG(("GIOChannelChild::DoOnDataAvailable [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv =
+ NS_NewByteInputStream(getter_AddRefs(stringStream),
+ Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnDataAvailable(this, stringStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ stringStream->Close();
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvOnStopRequest(
+ const nsresult& aChannelStatus) {
+ LOG(("GIOChannelChild::RecvOnStopRequest [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aChannelStatus)));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus]() {
+ self->DoOnStopRequest(aChannelStatus);
+ }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoOnStopRequest(const nsresult& aChannelStatus) {
+ LOG(("GIOChannelChild::DoOnStopRequest [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aChannelStatus)));
+
+ if (!mCanceled) {
+ mStatus = aChannelStatus;
+ }
+
+ { // Ensure that all queued ipdl events are dispatched before
+ // we initiate protocol deletion below.
+ mIsPending = false;
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ (void)mListener->OnStopRequest(this, aChannelStatus);
+
+ mListener = nullptr;
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aChannelStatus);
+ }
+ }
+
+ // This calls NeckoChild::DeallocPGIOChannelChild(), which deletes |this| if
+ // IPDL holds the last reference. Don't rely on |this| existing after here!
+ Send__delete__(this);
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) {
+ LOG(("GIOChannelChild::RecvFailedAsyncOpen [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatusCode)));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aStatusCode]() {
+ self->DoFailedAsyncOpen(aStatusCode);
+ }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoFailedAsyncOpen(const nsresult& aStatusCode) {
+ LOG(("GIOChannelChild::DoFailedAsyncOpen [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatusCode)));
+ mStatus = aStatusCode;
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatusCode);
+ }
+
+ if (mListener) {
+ mListener->OnStartRequest(this);
+ mIsPending = false;
+ mListener->OnStopRequest(this, aStatusCode);
+ } else {
+ mIsPending = false;
+ }
+
+ mListener = nullptr;
+
+ if (mIPCOpen) {
+ Send__delete__(this);
+ }
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvDeleteSelf() {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<GIOChannelChild>(this)]() { self->DoDeleteSelf(); }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoDeleteSelf() {
+ if (mIPCOpen) {
+ Send__delete__(this);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelChild::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelChild::Cancel(nsresult aStatus) {
+ LOG(("GIOChannelChild::Cancel [this=%p]\n", this));
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (mIPCOpen) {
+ SendCancel(aStatus);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::Suspend() {
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("GIOChannelChild::Suspend [this=%p]\n", this));
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ if (!mSuspendCount++) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::Resume() {
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("GIOChannelChild::Resume [this=%p]\n", this));
+
+ // SendResume only once, when suspend count drops to 0.
+ if (!--mSuspendCount && mSuspendSent) {
+ SendResume();
+ }
+ mEventQ->Resume();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelChild::ConnectParent(uint32_t aId) {
+ NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(
+ !static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(),
+ NS_ERROR_FAILURE);
+
+ LOG(("GIOChannelChild::ConnectParent [this=%p]\n", this));
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ // This must happen before the constructor message is sent.
+ SetupNeckoTarget();
+
+ // The socket transport in the chrome process now holds a logical ref to us
+ // until OnStopRequest, or we do a redirect, or we hit an IPDL error.
+ AddIPDLReference();
+
+ GIOChannelConnectArgs connectArgs(aId);
+
+ if (!gNeckoChild->SendPGIOChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ LOG(("GIOChannelChild::CompleteRedirectSetup [this=%p]\n", this));
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = aListener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ // We already have an open IPDL connection to the parent. If on-modify-request
+ // listeners or load group observers canceled us, let the parent handle it
+ // and send it back to us naturally.
+ return NS_OK;
+}
+
+void GIOChannelChild::SetupNeckoTarget() {
+ if (mNeckoTarget) {
+ return;
+ }
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/gio/GIOChannelChild.h b/netwerk/protocol/gio/GIOChannelChild.h
new file mode 100644
index 0000000000..158ab6804f
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelChild.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_GIOCHANNELCHILD_H
+#define NS_GIOCHANNELCHILD_H
+
+#include "mozilla/net/PGIOChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsBaseChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIChildChannel.h"
+#include "nsIEventTarget.h"
+#include "nsIStreamListener.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class GIOChannelChild final : public PGIOChannelChild,
+ public nsBaseChannel,
+ public nsIChildChannel {
+ public:
+ using nsIStreamListener = ::nsIStreamListener;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ NS_IMETHOD Cancel(nsresult aStatus) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+
+ explicit GIOChannelChild(nsIURI* uri);
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+
+ // Note that we handle this ourselves, overriding the nsBaseChannel
+ // default behavior, in order to be e10s-friendly.
+ NS_IMETHOD IsPending(bool* aResult) override;
+
+ nsresult OpenContentStream(bool aAsync, nsIInputStream** aStream,
+ nsIChannel** aChannel) override;
+
+ bool IsSuspended() const;
+
+ protected:
+ virtual ~GIOChannelChild() = default;
+
+ mozilla::ipc::IPCResult RecvOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsACString& aContentType,
+ const nsACString& aEntityID,
+ const URIParams& aURI) override;
+ mozilla::ipc::IPCResult RecvOnDataAvailable(const nsresult& aChannelStatus,
+ const nsACString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount) override;
+ mozilla::ipc::IPCResult RecvOnStopRequest(
+ const nsresult& aChannelStatus) override;
+ mozilla::ipc::IPCResult RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) override;
+ mozilla::ipc::IPCResult RecvDeleteSelf() override;
+
+ void DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsACString& aContentType,
+ const nsACString& aEntityID, const URIParams& aURI);
+ void DoOnDataAvailable(const nsresult& aChannelStatus,
+ const nsACString& aData, const uint64_t& aOffset,
+ const uint32_t& aCount);
+ void DoOnStopRequest(const nsresult& aChannelStatus);
+ void DoFailedAsyncOpen(const nsresult& aStatusCode);
+ void DoDeleteSelf();
+
+ void SetupNeckoTarget() override;
+
+ friend class NeckoTargetChannelFunctionEvent;
+
+ private:
+ nsCOMPtr<nsIInputStream> mUploadStream;
+
+ bool mIPCOpen = false;
+ const RefPtr<ChannelEventQueue> mEventQ;
+
+ bool mCanceled = false;
+ uint32_t mSuspendCount = 0;
+ ;
+ bool mIsPending = false;
+
+ uint64_t mStartPos = 0;
+ nsCString mEntityID;
+
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ bool mSuspendSent = false;
+};
+
+inline bool GIOChannelChild::IsSuspended() const { return mSuspendCount != 0; }
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_GIOCHANNELCHILD_H */
diff --git a/netwerk/protocol/gio/GIOChannelParent.cpp b/netwerk/protocol/gio/GIOChannelParent.cpp
new file mode 100644
index 0000000000..a19829a81d
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelParent.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GIOChannelParent.h"
+#include "nsGIOProtocolHandler.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsISecureBrowserUI.h"
+#include "nsQueryObject.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+#undef LOG
+#define LOG(args) MOZ_LOG(gGIOLog, mozilla::LogLevel::Debug, args)
+namespace net {
+
+GIOChannelParent::GIOChannelParent(dom::BrowserParent* aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mLoadContext(aLoadContext),
+ mPBOverride(aOverrideStatus),
+ mBrowserParent(aIframeEmbedding) {
+ mEventQ = new ChannelEventQueue(static_cast<nsIParentChannel*>(this));
+}
+
+void GIOChannelParent::ActorDestroy(ActorDestroyReason why) {
+ // We may still have refcount>0 if the channel hasn't called OnStopRequest
+ // yet, but we must not send any more msgs to child.
+ mIPCClosed = true;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(GIOChannelParent, nsIStreamListener, nsIParentChannel,
+ nsIInterfaceRequestor, nsIRequestObserver)
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent methods
+//-----------------------------------------------------------------------------
+
+bool GIOChannelParent::Init(const GIOChannelCreationArgs& aOpenArgs) {
+ switch (aOpenArgs.type()) {
+ case GIOChannelCreationArgs::TGIOChannelOpenArgs: {
+ const GIOChannelOpenArgs& a = aOpenArgs.get_GIOChannelOpenArgs();
+ return DoAsyncOpen(a.uri(), a.startPos(), a.entityID(), a.uploadStream(),
+ a.loadInfo(), a.loadFlags());
+ }
+ case GIOChannelCreationArgs::TGIOChannelConnectArgs: {
+ const GIOChannelConnectArgs& cArgs =
+ aOpenArgs.get_GIOChannelConnectArgs();
+ return ConnectChannel(cArgs.channelId());
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown open type");
+ return false;
+ }
+}
+
+bool GIOChannelParent::DoAsyncOpen(const URIParams& aURI,
+ const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const Maybe<IPCStream>& aUploadStream,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const uint32_t& aLoadFlags) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri) {
+ return false;
+ }
+
+#ifdef DEBUG
+ LOG(("GIOChannelParent DoAsyncOpen [this=%p uri=%s]\n", this,
+ uri->GetSpecOrDefault().get()));
+#endif
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsAutoCString remoteType;
+ rv = GetRemoteType(remoteType);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, remoteType,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ OriginAttributes attrs;
+ rv = loadInfo->GetOriginAttributes(&attrs);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = NS_NewChannelInternal(getter_AddRefs(chan), uri, loadInfo, nullptr,
+ nullptr, nullptr, aLoadFlags, ios);
+
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ mChannel = chan;
+
+ nsIChannel* gioChan = static_cast<nsIChannel*>(mChannel.get());
+
+ rv = gioChan->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ return true;
+}
+
+bool GIOChannelParent::ConnectChannel(const uint64_t& channelId) {
+ nsresult rv;
+
+ LOG(("Looking for a registered channel [this=%p, id=%" PRIx64 "]", this,
+ channelId));
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv)) {
+ mChannel = channel;
+ }
+
+ LOG((" found channel %p, rv=%08" PRIx32, mChannel.get(),
+ static_cast<uint32_t>(rv)));
+
+ return true;
+}
+
+mozilla::ipc::IPCResult GIOChannelParent::RecvCancel(const nsresult& status) {
+ if (mChannel) {
+ mChannel->Cancel(status);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GIOChannelParent::RecvSuspend() {
+ if (mChannel) {
+ mChannel->Suspend();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GIOChannelParent::RecvResume() {
+ if (mChannel) {
+ mChannel->Resume();
+ }
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("GIOChannelParent::OnStartRequest [this=%p]\n", this));
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(chan);
+ NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
+
+ int64_t contentLength;
+ chan->GetContentLength(&contentLength);
+ nsCString contentType;
+ chan->GetContentType(contentType);
+ nsresult channelStatus = NS_OK;
+ chan->GetStatus(&channelStatus);
+
+ nsCString entityID;
+ URIParams uriparam;
+ nsCOMPtr<nsIURI> uri;
+ chan->GetURI(getter_AddRefs(uri));
+ SerializeURI(uri, uriparam);
+
+ if (mIPCClosed || !SendOnStartRequest(channelStatus, contentLength,
+ contentType, entityID, uriparam)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("GIOChannelParent::OnStopRequest: [this=%p status=%" PRIu32 "]\n", this,
+ static_cast<uint32_t>(aStatusCode)));
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("GIOChannelParent::OnDataAvailable [this=%p]\n", this));
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsresult channelStatus = NS_OK;
+ mChannel->GetStatus(&channelStatus);
+
+ if (mIPCClosed ||
+ !SendOnDataAvailable(channelStatus, data, aOffset, aCount)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Do not need ptr to ParentChannelListener.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::Delete() {
+ if (mIPCClosed || !SendDeleteSelf()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::GetInterface(const nsIID& uuid, void** result) {
+ if (uuid.Equals(NS_GET_IID(nsIAuthPromptProvider)) ||
+ uuid.Equals(NS_GET_IID(nsISecureBrowserUI))) {
+ if (mBrowserParent) {
+ return mBrowserParent->QueryInterface(uuid, result);
+ }
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(uuid, result);
+}
+
+//---------------------
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/gio/GIOChannelParent.h b/netwerk/protocol/gio/GIOChannelParent.h
new file mode 100644
index 0000000000..32d3bd0555
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelParent.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_GIOCHANNELPARENT_H
+#define NS_GIOCHANNELPARENT_H
+
+#include "mozilla/net/PGIOChannelParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIParentChannel.h"
+#include "nsIInterfaceRequestor.h"
+
+class nsILoadContext;
+
+namespace mozilla {
+
+namespace dom {
+class BrowserParent;
+} // namespace dom
+
+namespace net {
+class ChannelEventQueue;
+
+class GIOChannelParent final : public PGIOChannelParent,
+ public nsIParentChannel,
+ public nsIInterfaceRequestor {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ GIOChannelParent(dom::BrowserParent* aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus);
+
+ bool Init(const GIOChannelCreationArgs& aOpenArgs);
+
+ protected:
+ virtual ~GIOChannelParent() = default;
+
+ bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const Maybe<mozilla::ipc::IPCStream>& aUploadStream,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const uint32_t& aLoadFlags);
+
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during HTTP->FTP redirects.
+ bool ConnectChannel(const uint64_t& channelId);
+
+ virtual mozilla::ipc::IPCResult RecvCancel(const nsresult& status) override;
+ virtual mozilla::ipc::IPCResult RecvSuspend() override;
+ virtual mozilla::ipc::IPCResult RecvResume() override;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIChannel> mChannel;
+
+ bool mIPCClosed = false;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ PBOverrideStatus mPBOverride;
+
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus = NS_OK;
+
+ RefPtr<mozilla::dom::BrowserParent> mBrowserParent;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_GIOCHANNELPARENT_H */
diff --git a/netwerk/protocol/gio/PGIOChannel.ipdl b/netwerk/protocol/gio/PGIOChannel.ipdl
new file mode 100644
index 0000000000..e4f1f6380b
--- /dev/null
+++ b/netwerk/protocol/gio/PGIOChannel.ipdl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include URIParams;
+
+//FIXME: bug #792908 (NeckoChannelParams already included by PNecko)
+include NeckoChannelParams;
+
+using PRTime from "prtime.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+async protocol PGIOChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async __delete__();
+
+ async Cancel(nsresult status);
+ async Suspend();
+ async Resume();
+
+child:
+ async OnStartRequest(nsresult aChannelStatus,
+ int64_t aContentLength,
+ nsCString aContentType,
+ nsCString aEntityID,
+ URIParams aURI);
+ async OnDataAvailable(nsresult channelStatus,
+ nsCString data,
+ uint64_t offset,
+ uint32_t count);
+ async OnStopRequest(nsresult channelStatus);
+ async FailedAsyncOpen(nsresult statusCode);
+
+ async DeleteSelf();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/gio/components.conf b/netwerk/protocol/gio/components.conf
new file mode 100644
index 0000000000..a2bbfd8f19
--- /dev/null
+++ b/netwerk/protocol/gio/components.conf
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+Classes = [
+ {
+ 'cid': '{ee706783-3af8-4d19-9e84-e2ebfe213480}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-gio'],
+ 'singleton': True,
+ 'type': 'nsGIOProtocolHandler',
+ 'headers': ['nsGIOProtocolHandler.h'],
+ 'constructor': 'nsGIOProtocolHandler::GetSingleton',
+ 'categories': { 'xpcom-startup': 'nsGIOProtocolHandler' },
+ 'protocol_config': {
+ 'scheme': 'moz-gio',
+ 'flags': [
+ 'URI_STD',
+ 'URI_DANGEROUS_TO_LOAD',
+ ],
+ },
+ },
+]
diff --git a/netwerk/protocol/gio/moz.build b/netwerk/protocol/gio/moz.build
new file mode 100644
index 0000000000..4078e5605c
--- /dev/null
+++ b/netwerk/protocol/gio/moz.build
@@ -0,0 +1,42 @@
+# -*- 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/.
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS += [
+ "nsGIOProtocolHandler.h",
+]
+
+EXPORTS.mozilla.net += [
+ "GIOChannelChild.h",
+ "GIOChannelParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "GIOChannelChild.cpp",
+ "GIOChannelParent.cpp",
+ "nsGIOProtocolHandler.cpp",
+]
+
+IPDL_SOURCES = [
+ "PGIOChannel.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
diff --git a/netwerk/protocol/gio/nsGIOProtocolHandler.cpp b/netwerk/protocol/gio/nsGIOProtocolHandler.cpp
new file mode 100644
index 0000000000..469d7982d4
--- /dev/null
+++ b/netwerk/protocol/gio/nsGIOProtocolHandler.cpp
@@ -0,0 +1,1027 @@
+/* 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/. */
+
+/*
+ * This code is based on original Mozilla gnome-vfs extension. It implements
+ * input stream provided by GVFS/GIO.
+ */
+#include "nsGIOProtocolHandler.h"
+#include "GIOChannelChild.h"
+#include "mozilla/Components.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIObserver.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsIStringBundle.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURI.h"
+#include "nsIAuthPrompt.h"
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "mozilla/Monitor.h"
+#include "prtime.h"
+#include <gio/gio.h>
+#include <algorithm>
+
+using namespace mozilla;
+
+#define MOZ_GIO_SCHEME "moz-gio"
+#define MOZ_GIO_SUPPORTED_PROTOCOLS "network.gio.supported-protocols"
+
+//-----------------------------------------------------------------------------
+
+// NSPR_LOG_MODULES=gio:5
+LazyLogModule gGIOLog("gio");
+#undef LOG
+#define LOG(args) MOZ_LOG(gGIOLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+static nsresult MapGIOResult(gint code) {
+ switch (code) {
+ case G_IO_ERROR_NOT_FOUND:
+ return NS_ERROR_FILE_NOT_FOUND; // shows error
+ case G_IO_ERROR_INVALID_ARGUMENT:
+ return NS_ERROR_INVALID_ARG;
+ case G_IO_ERROR_NOT_SUPPORTED:
+ return NS_ERROR_NOT_AVAILABLE;
+ case G_IO_ERROR_NO_SPACE:
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ case G_IO_ERROR_READ_ONLY:
+ return NS_ERROR_FILE_READ_ONLY;
+ case G_IO_ERROR_PERMISSION_DENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED; // wrong password/login
+ case G_IO_ERROR_CLOSED:
+ return NS_BASE_STREAM_CLOSED; // was EOF
+ case G_IO_ERROR_NOT_DIRECTORY:
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+ case G_IO_ERROR_PENDING:
+ return NS_ERROR_IN_PROGRESS;
+ case G_IO_ERROR_EXISTS:
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+ case G_IO_ERROR_IS_DIRECTORY:
+ return NS_ERROR_FILE_IS_DIRECTORY;
+ case G_IO_ERROR_NOT_MOUNTED:
+ return NS_ERROR_NOT_CONNECTED; // shows error
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ return NS_ERROR_UNKNOWN_HOST; // shows error
+ case G_IO_ERROR_CANCELLED:
+ return NS_ERROR_ABORT;
+ case G_IO_ERROR_NOT_EMPTY:
+ return NS_ERROR_FILE_DIR_NOT_EMPTY;
+ case G_IO_ERROR_FILENAME_TOO_LONG:
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ case G_IO_ERROR_INVALID_FILENAME:
+ return NS_ERROR_FILE_INVALID_PATH;
+ case G_IO_ERROR_TIMED_OUT:
+ return NS_ERROR_NET_TIMEOUT; // shows error
+ case G_IO_ERROR_WOULD_BLOCK:
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ case G_IO_ERROR_FAILED_HANDLED:
+ return NS_ERROR_ABORT; // Cancel on login dialog
+
+ /* unhandled:
+ G_IO_ERROR_NOT_REGULAR_FILE,
+ G_IO_ERROR_NOT_SYMBOLIC_LINK,
+ G_IO_ERROR_NOT_MOUNTABLE_FILE,
+ G_IO_ERROR_TOO_MANY_LINKS,
+ G_IO_ERROR_ALREADY_MOUNTED,
+ G_IO_ERROR_CANT_CREATE_BACKUP,
+ G_IO_ERROR_WRONG_ETAG,
+ G_IO_ERROR_WOULD_RECURSE,
+ G_IO_ERROR_BUSY,
+ G_IO_ERROR_WOULD_MERGE,
+ G_IO_ERROR_TOO_MANY_OPEN_FILES
+ */
+ // Make GCC happy
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+static nsresult MapGIOResult(GError* result) {
+ if (!result) {
+ return NS_OK;
+ }
+ return MapGIOResult(result->code);
+}
+
+/** Return values for mount operation.
+ * These enums are used as mount operation return values.
+ */
+enum class MountOperationResult {
+ MOUNT_OPERATION_IN_PROGRESS, /** \enum operation in progress */
+ MOUNT_OPERATION_SUCCESS, /** \enum operation successful */
+ MOUNT_OPERATION_FAILED /** \enum operation not successful */
+};
+
+//-----------------------------------------------------------------------------
+/**
+ * Sort function compares according to file type (directory/file)
+ * and alphabethical order
+ * @param a pointer to GFileInfo object to compare
+ * @param b pointer to GFileInfo object to compare
+ * @return -1 when first object should be before the second, 0 when equal,
+ * +1 when second object should be before the first
+ */
+static gint FileInfoComparator(gconstpointer a, gconstpointer b) {
+ GFileInfo* ia = (GFileInfo*)a;
+ GFileInfo* ib = (GFileInfo*)b;
+ if (g_file_info_get_file_type(ia) == G_FILE_TYPE_DIRECTORY &&
+ g_file_info_get_file_type(ib) != G_FILE_TYPE_DIRECTORY) {
+ return -1;
+ }
+ if (g_file_info_get_file_type(ib) == G_FILE_TYPE_DIRECTORY &&
+ g_file_info_get_file_type(ia) != G_FILE_TYPE_DIRECTORY) {
+ return 1;
+ }
+
+ return nsCRT::strcasecmp(g_file_info_get_name(ia), g_file_info_get_name(ib));
+}
+
+/* Declaration of mount callback functions */
+static void mount_enclosing_volume_finished(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data);
+static void mount_operation_ask_password(
+ GMountOperation* mount_op, const char* message, const char* default_user,
+ const char* default_domain, GAskPasswordFlags flags, gpointer user_data);
+//-----------------------------------------------------------------------------
+class nsGIOInputStream final : public nsIInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ explicit nsGIOInputStream(const nsCString& uriSpec) : mSpec(uriSpec) {}
+
+ void SetChannel(nsIChannel* channel) {
+ // We need to hold an owning reference to our channel. This is done
+ // so we can access the channel's notification callbacks to acquire
+ // a reference to a nsIAuthPrompt if we need to handle an interactive
+ // mount operation.
+ //
+ // However, the channel can only be accessed on the main thread, so
+ // we have to be very careful with ownership. Moreover, it doesn't
+ // support threadsafe addref/release, so proxying is the answer.
+ //
+ // Also, it's important to note that this likely creates a reference
+ // cycle since the channel likely owns this stream. This reference
+ // cycle is broken in our Close method.
+
+ mChannel = do_AddRef(channel).take();
+ }
+ void SetMountResult(MountOperationResult result, gint error_code);
+
+ private:
+ ~nsGIOInputStream() { Close(); }
+ nsresult DoOpen();
+ nsresult DoRead(char* aBuf, uint32_t aCount, uint32_t* aCountRead);
+ nsresult SetContentTypeOfChannel(const char* contentType);
+ nsresult MountVolume();
+ nsresult DoOpenDirectory();
+ nsresult DoOpenFile(GFileInfo* info);
+ nsCString mSpec;
+ nsIChannel* mChannel{nullptr}; // manually refcounted
+ GFile* mHandle{nullptr};
+ GFileInputStream* mStream{nullptr};
+ uint64_t mBytesRemaining{UINT64_MAX};
+ nsresult mStatus{NS_OK};
+ GList* mDirList{nullptr};
+ GList* mDirListPtr{nullptr};
+ nsCString mDirBuf;
+ uint32_t mDirBufCursor{0};
+ bool mDirOpen{false};
+ MountOperationResult mMountRes =
+ MountOperationResult::MOUNT_OPERATION_SUCCESS;
+ mozilla::Monitor mMonitorMountInProgress MOZ_UNANNOTATED{
+ "GIOInputStream::MountFinished"};
+ gint mMountErrorCode{};
+};
+
+/**
+ * Set result of mount operation and notify monitor waiting for results.
+ * This method is called in main thread as long as it is used only
+ * in mount_enclosing_volume_finished function.
+ * @param result Result of mount operation
+ */
+void nsGIOInputStream::SetMountResult(MountOperationResult result,
+ gint error_code) {
+ mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
+ mMountRes = result;
+ mMountErrorCode = error_code;
+ mon.Notify();
+}
+
+/**
+ * Start mount operation and wait in loop until it is finished. This method is
+ * called from thread which is trying to read from location.
+ */
+nsresult nsGIOInputStream::MountVolume() {
+ GMountOperation* mount_op = g_mount_operation_new();
+ g_signal_connect(mount_op, "ask-password",
+ G_CALLBACK(mount_operation_ask_password), mChannel);
+ mMountRes = MountOperationResult::MOUNT_OPERATION_IN_PROGRESS;
+ /* g_file_mount_enclosing_volume uses a dbus request to mount the volume.
+ Callback mount_enclosing_volume_finished is called in main thread
+ (not this thread on which this method is called). */
+ g_file_mount_enclosing_volume(mHandle, G_MOUNT_MOUNT_NONE, mount_op, nullptr,
+ mount_enclosing_volume_finished, this);
+ mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
+ /* Waiting for finish of mount operation thread */
+ while (mMountRes == MountOperationResult::MOUNT_OPERATION_IN_PROGRESS) {
+ mon.Wait();
+ }
+
+ g_object_unref(mount_op);
+
+ if (mMountRes == MountOperationResult::MOUNT_OPERATION_FAILED) {
+ return MapGIOResult(mMountErrorCode);
+ }
+ return NS_OK;
+}
+
+/**
+ * Create list of infos about objects in opened directory
+ * Return: NS_OK when list obtained, otherwise error code according
+ * to failed operation.
+ */
+nsresult nsGIOInputStream::DoOpenDirectory() {
+ GError* error = nullptr;
+
+ GFileEnumerator* f_enum = g_file_enumerate_children(
+ mHandle, "standard::*,time::*", G_FILE_QUERY_INFO_NONE, nullptr, &error);
+ if (!f_enum) {
+ nsresult rv = MapGIOResult(error);
+ g_warning("Cannot read from directory: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+ // fill list of file infos
+ GFileInfo* info = g_file_enumerator_next_file(f_enum, nullptr, &error);
+ while (info) {
+ mDirList = g_list_append(mDirList, info);
+ info = g_file_enumerator_next_file(f_enum, nullptr, &error);
+ }
+ g_object_unref(f_enum);
+ if (error) {
+ g_warning("Error reading directory content: %s", error->message);
+ nsresult rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ mDirOpen = true;
+
+ // Sort list of file infos by using FileInfoComparator function
+ mDirList = g_list_sort(mDirList, FileInfoComparator);
+ mDirListPtr = mDirList;
+
+ // Write column names
+ mDirBuf.AppendLiteral(
+ "200: filename content-length last-modified file-type\n");
+
+ SetContentTypeOfChannel(APPLICATION_HTTP_INDEX_FORMAT);
+ return NS_OK;
+}
+
+/**
+ * Create file stream and set mime type for channel
+ * @param info file info used to determine mime type
+ * @return NS_OK when file stream created successfuly, error code otherwise
+ */
+nsresult nsGIOInputStream::DoOpenFile(GFileInfo* info) {
+ GError* error = nullptr;
+
+ mStream = g_file_read(mHandle, nullptr, &error);
+ if (!mStream) {
+ nsresult rv = MapGIOResult(error);
+ g_warning("Cannot read from file: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+
+ const char* content_type = g_file_info_get_content_type(info);
+ if (content_type) {
+ char* mime_type = g_content_type_get_mime_type(content_type);
+ if (mime_type) {
+ if (strcmp(mime_type, APPLICATION_OCTET_STREAM) != 0) {
+ SetContentTypeOfChannel(mime_type);
+ }
+ g_free(mime_type);
+ }
+ } else {
+ g_warning("Missing content type.");
+ }
+
+ mBytesRemaining = g_file_info_get_size(info);
+ // Update the content length attribute on the channel. We do this
+ // synchronously without proxying. This hack is not as bad as it looks!
+ mChannel->SetContentLength(mBytesRemaining);
+
+ return NS_OK;
+}
+
+/**
+ * Start file open operation, mount volume when needed and according to file
+ * type create file output stream or read directory content.
+ * @return NS_OK when file or directory opened successfully, error code
+ * otherwise
+ */
+nsresult nsGIOInputStream::DoOpen() {
+ nsresult rv;
+ GError* error = nullptr;
+
+ NS_ASSERTION(mHandle == nullptr, "already open");
+
+ mHandle = g_file_new_for_uri(mSpec.get());
+
+ GFileInfo* info = g_file_query_info(mHandle, "standard::*",
+ G_FILE_QUERY_INFO_NONE, nullptr, &error);
+
+ if (error) {
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED) {
+ // location is not yet mounted, try to mount
+ g_error_free(error);
+ if (NS_IsMainThread()) {
+ return NS_ERROR_NOT_CONNECTED;
+ }
+ error = nullptr;
+ rv = MountVolume();
+ if (rv != NS_OK) {
+ return rv;
+ }
+ // get info again
+ info = g_file_query_info(mHandle, "standard::*", G_FILE_QUERY_INFO_NONE,
+ nullptr, &error);
+ // second try to get file info from remote files after media mount
+ if (!info) {
+ g_warning("Unable to get file info: %s", error->message);
+ rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ } else {
+ g_warning("Unable to get file info: %s", error->message);
+ rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ }
+ // Get file type to handle directories and file differently
+ GFileType f_type = g_file_info_get_file_type(info);
+ if (f_type == G_FILE_TYPE_DIRECTORY) {
+ // directory
+ rv = DoOpenDirectory();
+ } else if (f_type != G_FILE_TYPE_UNKNOWN) {
+ // file
+ rv = DoOpenFile(info);
+ } else {
+ g_warning("Unable to get file type.");
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ }
+ if (info) {
+ g_object_unref(info);
+ }
+ return rv;
+}
+
+/**
+ * Read content of file or create file list from directory
+ * @param aBuf read destination buffer
+ * @param aCount length of destination buffer
+ * @param aCountRead number of read characters
+ * @return NS_OK when read successfully, NS_BASE_STREAM_CLOSED when end of file,
+ * error code otherwise
+ */
+nsresult nsGIOInputStream::DoRead(char* aBuf, uint32_t aCount,
+ uint32_t* aCountRead) {
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ if (mStream) {
+ // file read
+ GError* error = nullptr;
+ uint32_t bytes_read = g_input_stream_read(G_INPUT_STREAM(mStream), aBuf,
+ aCount, nullptr, &error);
+ if (error) {
+ rv = MapGIOResult(error);
+ *aCountRead = 0;
+ g_warning("Cannot read from file: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+ *aCountRead = bytes_read;
+ mBytesRemaining -= *aCountRead;
+ return NS_OK;
+ }
+ if (mDirOpen) {
+ // directory read
+ while (aCount && rv != NS_BASE_STREAM_CLOSED) {
+ // Copy data out of our buffer
+ uint32_t bufLen = mDirBuf.Length() - mDirBufCursor;
+ if (bufLen) {
+ uint32_t n = std::min(bufLen, aCount);
+ memcpy(aBuf, mDirBuf.get() + mDirBufCursor, n);
+ *aCountRead += n;
+ aBuf += n;
+ aCount -= n;
+ mDirBufCursor += n;
+ }
+
+ if (!mDirListPtr) // Are we at the end of the directory list?
+ {
+ rv = NS_BASE_STREAM_CLOSED;
+ } else if (aCount) // Do we need more data?
+ {
+ GFileInfo* info = (GFileInfo*)mDirListPtr->data;
+
+ // Prune '.' and '..' from directory listing.
+ const char* fname = g_file_info_get_name(info);
+ if (fname && fname[0] == '.' &&
+ (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0'))) {
+ mDirListPtr = mDirListPtr->next;
+ continue;
+ }
+
+ mDirBuf.AssignLiteral("201: ");
+
+ // The "filename" field
+ nsCString escName;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID);
+ if (nu && fname) {
+ nu->EscapeString(nsDependentCString(fname),
+ nsINetUtil::ESCAPE_URL_PATH, escName);
+
+ mDirBuf.Append(escName);
+ mDirBuf.Append(' ');
+ }
+
+ // The "content-length" field
+ // XXX truncates size from 64-bit to 32-bit
+ mDirBuf.AppendInt(int32_t(g_file_info_get_size(info)));
+ mDirBuf.Append(' ');
+
+ // The "last-modified" field
+ //
+ // NSPR promises: PRTime is compatible with time_t
+ // we just need to convert from seconds to microseconds
+ GTimeVal gtime;
+ g_file_info_get_modification_time(info, &gtime);
+
+ PRExplodedTime tm;
+ PRTime pt = ((PRTime)gtime.tv_sec) * 1000000;
+ PR_ExplodeTime(pt, PR_GMTParameters, &tm);
+ {
+ char buf[64];
+ PR_FormatTimeUSEnglish(buf, sizeof(buf),
+ "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
+ &tm);
+ mDirBuf.Append(buf);
+ }
+
+ // The "file-type" field
+ switch (g_file_info_get_file_type(info)) {
+ case G_FILE_TYPE_REGULAR:
+ mDirBuf.AppendLiteral("FILE ");
+ break;
+ case G_FILE_TYPE_DIRECTORY:
+ mDirBuf.AppendLiteral("DIRECTORY ");
+ break;
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ mDirBuf.AppendLiteral("SYMBOLIC-LINK ");
+ break;
+ default:
+ break;
+ }
+ mDirBuf.Append('\n');
+
+ mDirBufCursor = 0;
+ mDirListPtr = mDirListPtr->next;
+ }
+ }
+ }
+ return rv;
+}
+
+/**
+ * This class is used to implement SetContentTypeOfChannel.
+ */
+class nsGIOSetContentTypeEvent : public mozilla::Runnable {
+ public:
+ nsGIOSetContentTypeEvent(nsIChannel* channel, const char* contentType)
+ : mozilla::Runnable("nsGIOSetContentTypeEvent"),
+ mChannel(channel),
+ mContentType(contentType) {
+ // stash channel reference in mChannel. no AddRef here! see note
+ // in SetContentTypeOfchannel.
+ }
+
+ NS_IMETHOD Run() override {
+ mChannel->SetContentType(mContentType);
+ return NS_OK;
+ }
+
+ private:
+ nsIChannel* mChannel;
+ nsCString mContentType;
+};
+
+nsresult nsGIOInputStream::SetContentTypeOfChannel(const char* contentType) {
+ // We need to proxy this call over to the main thread. We post an
+ // asynchronous event in this case so that we don't delay reading data, and
+ // we know that this is safe to do since the channel's reference will be
+ // released asynchronously as well. We trust the ordering of the main
+ // thread's event queue to protect us against memory corruption.
+
+ nsresult rv;
+ nsCOMPtr<nsIRunnable> ev =
+ new nsGIOSetContentTypeEvent(mChannel, contentType);
+ if (!ev) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ rv = NS_DispatchToMainThread(ev);
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsGIOInputStream, nsIInputStream)
+
+/**
+ * Free all used memory and close stream.
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Close() {
+ if (mStream) {
+ g_object_unref(mStream);
+ mStream = nullptr;
+ }
+
+ if (mHandle) {
+ g_object_unref(mHandle);
+ mHandle = nullptr;
+ }
+
+ if (mDirList) {
+ // Destroy the list of GIOFileInfo objects...
+ g_list_foreach(mDirList, (GFunc)g_object_unref, nullptr);
+ g_list_free(mDirList);
+ mDirList = nullptr;
+ mDirListPtr = nullptr;
+ }
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread("nsGIOInputStream::mChannel", dont_AddRef(mChannel));
+
+ mChannel = nullptr;
+ }
+
+ mSpec.Truncate(); // free memory
+
+ // Prevent future reads from re-opening the handle.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Return number of remaining bytes available on input
+ * @param aResult remaining bytes
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Available(uint64_t* aResult) {
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ *aResult = mBytesRemaining;
+
+ return NS_OK;
+}
+
+/**
+ * Return the status of the input stream
+ */
+NS_IMETHODIMP
+nsGIOInputStream::StreamStatus() { return mStatus; }
+
+/**
+ * Trying to read from stream. When location is not available it tries to mount
+ * it.
+ * @param aBuf buffer to put read data
+ * @param aCount length of aBuf
+ * @param aCountRead number of bytes actually read
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aCountRead) {
+ *aCountRead = 0;
+ // Check if file is already opened, otherwise open it
+ if (!mStream && !mDirOpen && mStatus == NS_OK) {
+ mStatus = DoOpen();
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+ }
+
+ mStatus = DoRead(aBuf, aCount, aCountRead);
+ // Check if all data has been read
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ return NS_OK;
+ }
+
+ // Check whenever any error appears while reading
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsGIOInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) {
+ // There is no way to implement this using GnomeVFS, but fortunately
+ // that doesn't matter. Because we are a blocking input stream, Necko
+ // isn't going to call our ReadSegments method.
+ MOZ_ASSERT_UNREACHABLE("nsGIOInputStream::ReadSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOInputStream::IsNonBlocking(bool* aResult) {
+ *aResult = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+/**
+ * Called when finishing mount operation. Result of operation is set in
+ * nsGIOInputStream. This function is called in main thread as an async request
+ * typically from dbus.
+ * @param source_object GFile object which requested the mount
+ * @param res result object
+ * @param user_data pointer to nsGIOInputStream
+ */
+static void mount_enclosing_volume_finished(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data) {
+ GError* error = nullptr;
+
+ nsGIOInputStream* istream = static_cast<nsGIOInputStream*>(user_data);
+
+ g_file_mount_enclosing_volume_finish(G_FILE(source_object), res, &error);
+
+ if (error) {
+ g_warning("Mount failed: %s %d", error->message, error->code);
+ istream->SetMountResult(MountOperationResult::MOUNT_OPERATION_FAILED,
+ error->code);
+ g_error_free(error);
+ } else {
+ istream->SetMountResult(MountOperationResult::MOUNT_OPERATION_SUCCESS, 0);
+ }
+}
+
+/**
+ * This function is called when username or password are requested from user.
+ * This function is called in main thread as async request from dbus.
+ * @param mount_op mount operation
+ * @param message message to show to user
+ * @param default_user preffered user
+ * @param default_domain domain name
+ * @param flags what type of information is required
+ * @param user_data nsIChannel
+ */
+static void mount_operation_ask_password(
+ GMountOperation* mount_op, const char* message, const char* default_user,
+ const char* default_domain, GAskPasswordFlags flags, gpointer user_data) {
+ nsIChannel* channel = (nsIChannel*)user_data;
+ if (!channel) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // We can't handle request for domain
+ if (flags & G_ASK_PASSWORD_NEED_DOMAIN) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ nsCOMPtr<nsIAuthPrompt> prompt;
+ NS_QueryNotificationCallbacks(channel, prompt);
+
+ // If no auth prompt, then give up. We could failover to using the
+ // WindowWatcher service, but that might defeat a consumer's purposeful
+ // attempt to disable authentication (for whatever reason).
+ if (!prompt) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Parse out the host and port...
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ nsAutoCString scheme, hostPort;
+ uri->GetScheme(scheme);
+ uri->GetHostPort(hostPort);
+
+ // It doesn't make sense for either of these strings to be empty. What kind
+ // of funky URI is this?
+ if (scheme.IsEmpty() || hostPort.IsEmpty()) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Construct the single signon key. Altering the value of this key will
+ // cause people's remembered passwords to be forgotten. Think carefully
+ // before changing the way this key is constructed.
+ nsAutoString key, realm;
+
+ NS_ConvertUTF8toUTF16 dispHost(scheme);
+ dispHost.AppendLiteral("://");
+ dispHost.Append(NS_ConvertUTF8toUTF16(hostPort));
+
+ key = dispHost;
+ if (*default_domain != '\0') {
+ // We assume the realm string is ASCII. That might be a bogus assumption,
+ // but we have no idea what encoding GnomeVFS is using, so for now we'll
+ // limit ourselves to ISO-Latin-1. XXX What is a better solution?
+ realm.Append('"');
+ realm.Append(NS_ConvertASCIItoUTF16(default_domain));
+ realm.Append('"');
+ key.Append(' ');
+ key.Append(realm);
+ }
+ // Construct the message string...
+ //
+ // We use Necko's string bundle here. This code really should be encapsulated
+ // behind some Necko API, after all this code is based closely on the code in
+ // nsHttpChannel.cpp.
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleSvc) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties",
+ getter_AddRefs(bundle));
+ if (!bundle) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ nsAutoString nsmessage;
+
+ if (flags & G_ASK_PASSWORD_NEED_PASSWORD) {
+ if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
+ if (!realm.IsEmpty()) {
+ AutoTArray<nsString, 2> strings = {realm, dispHost};
+ bundle->FormatStringFromName("EnterLoginForRealm3", strings, nsmessage);
+ } else {
+ AutoTArray<nsString, 1> strings = {dispHost};
+ bundle->FormatStringFromName("EnterUserPasswordFor2", strings,
+ nsmessage);
+ }
+ } else {
+ NS_ConvertUTF8toUTF16 userName(default_user);
+ AutoTArray<nsString, 2> strings = {userName, dispHost};
+ bundle->FormatStringFromName("EnterPasswordFor", strings, nsmessage);
+ }
+ } else {
+ g_warning("Unknown mount operation request (flags: %x)", flags);
+ }
+
+ if (nsmessage.IsEmpty()) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Prompt the user...
+ nsresult rv;
+ bool retval = false;
+ char16_t *user = nullptr, *pass = nullptr;
+ if (default_user) {
+ // user will be freed by PromptUsernameAndPassword
+ user = ToNewUnicode(NS_ConvertUTF8toUTF16(default_user));
+ }
+ if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
+ rv = prompt->PromptUsernameAndPassword(
+ nullptr, nsmessage.get(), key.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, &user, &pass, &retval);
+ } else {
+ rv = prompt->PromptPassword(nullptr, nsmessage.get(), key.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, &pass,
+ &retval);
+ }
+ if (NS_FAILED(rv) || !retval) { // was || user == '\0' || pass == '\0'
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ free(user);
+ free(pass);
+ return;
+ }
+ /* GIO should accept UTF8 */
+ g_mount_operation_set_username(mount_op, NS_ConvertUTF16toUTF8(user).get());
+ g_mount_operation_set_password(mount_op, NS_ConvertUTF16toUTF8(pass).get());
+ free(user);
+ free(pass);
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_HANDLED);
+}
+
+//-----------------------------------------------------------------------------
+
+mozilla::StaticRefPtr<nsGIOProtocolHandler> nsGIOProtocolHandler::sSingleton;
+
+already_AddRefed<nsGIOProtocolHandler> nsGIOProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new nsGIOProtocolHandler();
+ sSingleton->Init();
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+NS_IMPL_ISUPPORTS(nsGIOProtocolHandler, nsIProtocolHandler, nsIObserver)
+
+nsresult nsGIOProtocolHandler::Init() {
+ if (net::IsNeckoChild()) {
+ net::NeckoChild::InitNeckoChild();
+ }
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ InitSupportedProtocolsPref(prefs);
+ prefs->AddObserver(MOZ_GIO_SUPPORTED_PROTOCOLS, this, false);
+ }
+
+ return NS_OK;
+}
+
+void nsGIOProtocolHandler::InitSupportedProtocolsPref(nsIPrefBranch* prefs) {
+ nsCOMPtr<nsIIOService> ioService = components::IO::Service();
+ if (NS_WARN_IF(!ioService)) {
+ LOG(("gio: ioservice not available\n"));
+ return;
+ }
+
+ // Get user preferences to determine which protocol is supported.
+ // Gvfs/GIO has a set of supported protocols like obex, network, archive,
+ // computer, dav, cdda, gphoto2, trash, etc. Some of these seems to be
+ // irrelevant to process by browser. By default accept only sftp protocol so
+ // far.
+ nsAutoCString prefValue;
+ nsresult rv = prefs->GetCharPref(MOZ_GIO_SUPPORTED_PROTOCOLS, prefValue);
+ if (NS_SUCCEEDED(rv)) {
+ prefValue.StripWhitespace();
+ ToLowerCase(prefValue);
+ } else {
+ prefValue.AssignLiteral("" // use none by default
+ );
+ }
+ LOG(("gio: supported protocols \"%s\"\n", prefValue.get()));
+
+ // Unregister any previously registered dynamic protocols.
+ for (const nsCString& scheme : mSupportedProtocols) {
+ LOG(("gio: unregistering handler for \"%s\"", scheme.get()));
+ ioService->UnregisterProtocolHandler(scheme);
+ }
+ mSupportedProtocols.Clear();
+
+ // Register each protocol from the pref branch to reference
+ // nsGIOProtocolHandler.
+ for (const nsDependentCSubstring& protocol : prefValue.Split(',')) {
+ if (NS_WARN_IF(!StringEndsWith(protocol, ":"_ns))) {
+ continue; // each protocol must end with a `:` character to be recognized
+ }
+
+ nsCString scheme(Substring(protocol, 0, protocol.Length() - 1));
+ if (NS_SUCCEEDED(ioService->RegisterProtocolHandler(
+ scheme, this,
+ nsIProtocolHandler::URI_STD |
+ nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
+ /* aDefaultPort */ -1))) {
+ LOG(("gio: successfully registered handler for \"%s\"", scheme.get()));
+ mSupportedProtocols.AppendElement(scheme);
+ } else {
+ LOG(("gio: failed to register handler for \"%s\"", scheme.get()));
+ }
+ }
+}
+
+bool nsGIOProtocolHandler::IsSupportedProtocol(const nsCString& aScheme) {
+ for (const auto& protocol : mSupportedProtocols) {
+ if (aScheme.EqualsIgnoreCase(protocol)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral(MOZ_GIO_SCHEME);
+ return NS_OK;
+}
+
+static bool IsValidGIOScheme(const nsACString& aScheme) {
+ // Verify that GIO supports this URI scheme.
+ GVfs* gvfs = g_vfs_get_default();
+
+ if (!gvfs) {
+ g_warning("Cannot get GVfs object.");
+ return false;
+ }
+
+ const gchar* const* uri_schemes = g_vfs_get_supported_uri_schemes(gvfs);
+
+ while (*uri_schemes != nullptr) {
+ // While flatSpec ends with ':' the uri_scheme does not. Therefore do not
+ // compare last character.
+ if (aScheme.Equals(*uri_schemes)) {
+ return true;
+ }
+ uri_schemes++;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv;
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsSupportedProtocol(scheme)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ // g_vfs_get_supported_uri_schemes() returns a very limited list in the
+ // child due to the sandbox, so we only check if its valid for the parent.
+ if (XRE_IsParentProcess() && !IsValidGIOScheme(scheme)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ RefPtr<nsBaseChannel> channel;
+ if (net::IsNeckoChild()) {
+ channel = new mozilla::net::GIOChannelChild(aURI);
+ // set the loadInfo on the new channel
+ channel->SetLoadInfo(aLoadInfo);
+
+ rv = channel->SetContentType(nsLiteralCString(UNKNOWN_CONTENT_TYPE));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(aResult);
+ return NS_OK;
+ }
+
+ RefPtr<nsGIOInputStream> stream = new nsGIOInputStream(spec);
+ if (!stream) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<nsGIOInputStream> tmpStream = stream;
+ rv = NS_NewInputStreamChannelInternal(aResult, aURI, tmpStream.forget(),
+ nsLiteralCString(UNKNOWN_CONTENT_TYPE),
+ ""_ns, // aContentCharset
+ aLoadInfo);
+ if (NS_SUCCEEDED(rv)) {
+ stream->SetChannel(*aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::AllowPort(int32_t aPort, const char* aScheme,
+ bool* aResult) {
+ // Don't override anything.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ InitSupportedProtocolsPref(prefs);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/protocol/gio/nsGIOProtocolHandler.h b/netwerk/protocol/gio/nsGIOProtocolHandler.h
new file mode 100644
index 0000000000..08e7c01bed
--- /dev/null
+++ b/netwerk/protocol/gio/nsGIOProtocolHandler.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGIOProtocolHandler_h___
+#define nsGIOProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsIObserver.h"
+#include "nsIPrefBranch.h"
+#include "nsStringFwd.h"
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gGIOLog;
+
+class nsGIOProtocolHandler final : public nsIProtocolHandler,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+
+ static already_AddRefed<nsGIOProtocolHandler> GetSingleton();
+ bool IsSupportedProtocol(const nsCString& aScheme);
+
+ protected:
+ ~nsGIOProtocolHandler() = default;
+
+ private:
+ nsresult Init();
+
+ void InitSupportedProtocolsPref(nsIPrefBranch* prefs);
+
+ static mozilla::StaticRefPtr<nsGIOProtocolHandler> sSingleton;
+ nsTArray<nsCString> mSupportedProtocols;
+};
+
+#endif // nsGIOProtocolHandler_h___
diff --git a/netwerk/protocol/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp
new file mode 100644
index 0000000000..40df46b739
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -0,0 +1,94 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+/*
+ Currently supported is h2
+*/
+
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+
+#include "ASpdySession.h"
+#include "PSpdyPush.h"
+#include "Http2Push.h"
+#include "Http2Session.h"
+
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace net {
+
+ASpdySession* ASpdySession::NewSpdySession(net::SpdyVersion version,
+ nsISocketTransport* aTransport,
+ bool attemptingEarlyData) {
+ // This is a necko only interface, so we can enforce version
+ // requests as a precondition
+ MOZ_ASSERT(version == SpdyVersion::HTTP_2, "Unsupported spdy version");
+
+ // Don't do a runtime check of IsSpdyV?Enabled() here because pref value
+ // may have changed since starting negotiation. The selected protocol comes
+ // from a list provided in the SERVER HELLO filtered by our acceptable
+ // versions, so there is no risk of the server ignoring our prefs.
+
+ return Http2Session::CreateSession(aTransport, version, attemptingEarlyData);
+}
+
+SpdyInformation::SpdyInformation() {
+ // highest index of enabled protocols is the
+ // most preferred for ALPN negotiaton
+ Version = SpdyVersion::HTTP_2;
+ VersionString = "h2"_ns;
+ ALPNCallbacks = Http2Session::ALPNCallback;
+}
+
+//////////////////////////////////////////
+// SpdyPushCache
+//////////////////////////////////////////
+
+SpdyPushCache::~SpdyPushCache() { mHashHttp2.Clear(); }
+
+bool SpdyPushCache::RegisterPushedStreamHttp2(const nsCString& key,
+ Http2PushedStream* stream) {
+ LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X\n", key.get(),
+ stream->StreamID()));
+ if (mHashHttp2.Get(key)) {
+ LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X duplicate key\n",
+ key.get(), stream->StreamID()));
+ return false;
+ }
+ mHashHttp2.InsertOrUpdate(key, stream);
+ return true;
+}
+
+Http2PushedStream* SpdyPushCache::RemovePushedStreamHttp2(
+ const nsCString& key) {
+ Http2PushedStream* rv = mHashHttp2.Get(key);
+ LOG3(("SpdyPushCache::RemovePushedStreamHttp2 %s 0x%X\n", key.get(),
+ rv ? rv->StreamID() : 0));
+ if (rv) mHashHttp2.Remove(key);
+ return rv;
+}
+
+Http2PushedStream* SpdyPushCache::RemovePushedStreamHttp2ByID(
+ const nsCString& key, const uint32_t& streamID) {
+ Http2PushedStream* rv = mHashHttp2.Get(key);
+ LOG3(("SpdyPushCache::RemovePushedStreamHttp2ByID %s 0x%X 0x%X", key.get(),
+ rv ? rv->StreamID() : 0, streamID));
+ if (rv && streamID == rv->StreamID()) {
+ mHashHttp2.Remove(key);
+ } else {
+ // Ensure we overwrite our rv with null in case the stream IDs don't match
+ rv = nullptr;
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h
new file mode 100644
index 0000000000..45831a140c
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -0,0 +1,115 @@
+/* -*- 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_ASpdySession_h
+#define mozilla_net_ASpdySession_h
+
+#include "nsAHttpTransaction.h"
+#include "prinrval.h"
+#include "nsHttp.h"
+#include "nsString.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnection;
+
+class ASpdySession : public nsAHttpTransaction {
+ public:
+ ASpdySession() = default;
+ virtual ~ASpdySession() = default;
+
+ [[nodiscard]] virtual bool AddStream(nsAHttpTransaction*, int32_t,
+ nsIInterfaceRequestor*) = 0;
+ virtual bool CanReuse() = 0;
+ virtual bool RoomForMoreStreams() = 0;
+ virtual PRIntervalTime IdleTime() = 0;
+ virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0;
+ virtual void DontReuse() = 0;
+ virtual enum SpdyVersion SpdyVersion() = 0;
+
+ static ASpdySession* NewSpdySession(net::SpdyVersion version,
+ nsISocketTransport*, bool);
+
+ virtual bool TestJoinConnection(const nsACString& hostname, int32_t port) = 0;
+ virtual bool JoinConnection(const nsACString& hostname, int32_t port) = 0;
+
+ virtual void PrintDiagnostics(nsCString& log) = 0;
+
+ bool ResponseTimeoutEnabled() const final { return true; }
+
+ virtual void SendPing() = 0;
+
+ const static uint32_t kSendingChunkSize = 16000;
+ const static uint32_t kTCPSendBufferSize = 131072;
+ const static uint32_t kInitialPushAllowance = 131072; // match default pref
+
+ // This is roughly the amount of data a suspended channel will have to
+ // buffer before h2 flow control kicks in.
+ const static uint32_t kInitialRwin = 12 * 1024 * 1024; // 12MB
+
+ const static uint32_t kDefaultMaxConcurrent = 100;
+
+ // soft errors are errors that terminate a stream without terminating the
+ // connection. In general non-network errors are stream errors as well
+ // as network specific items like cancels.
+ static bool SoftStreamError(nsresult code) {
+ if (NS_SUCCEEDED(code) || code == NS_BASE_STREAM_WOULD_BLOCK) {
+ return false;
+ }
+
+ // this could go either way, but because there are network instances of
+ // it being a hard error we should consider it hard.
+ if (code == NS_ERROR_FAILURE || code == NS_ERROR_OUT_OF_MEMORY) {
+ return false;
+ }
+
+ if (NS_ERROR_GET_MODULE(code) != NS_ERROR_MODULE_NETWORK) {
+ return true;
+ }
+
+ // these are network specific soft errors
+ return (code == NS_BASE_STREAM_CLOSED || code == NS_BINDING_FAILED ||
+ code == NS_BINDING_ABORTED || code == NS_BINDING_REDIRECTED ||
+ code == NS_ERROR_INVALID_CONTENT_ENCODING ||
+ code == NS_BINDING_RETARGETED ||
+ code == NS_ERROR_CORRUPTED_CONTENT);
+ }
+
+ virtual void SetCleanShutdown(bool) = 0;
+ virtual WebSocketSupport GetWebSocketSupport() = 0;
+
+ virtual already_AddRefed<mozilla::net::nsHttpConnection> CreateTunnelStream(
+ nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket = false) = 0;
+};
+
+using ALPNCallback = bool (*)(nsITLSSocketControl*);
+
+// this is essentially a single instantiation as a member of nsHttpHandler.
+// It could be all static except using static ctors of XPCOM objects is a
+// bad idea.
+class SpdyInformation {
+ public:
+ SpdyInformation();
+ ~SpdyInformation() = default;
+
+ SpdyVersion Version; // telemetry enum e.g. SPDY_VERSION_31
+ nsCString VersionString; // npn string e.g. "spdy/3.1"
+
+ // the ALPNCallback function allows the protocol stack to decide whether or
+ // not to offer a particular protocol based on the known TLS information
+ // that we will offer in the client hello (such as version). There has
+ // not been a Server Hello received yet, so not much else can be considered.
+ ALPNCallback ALPNCallbacks;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ASpdySession_h
diff --git a/netwerk/protocol/http/AltDataOutputStreamChild.cpp b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
new file mode 100644
index 0000000000..89e34a4694
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
@@ -0,0 +1,204 @@
+/* -*- 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/AltDataOutputStreamChild.h"
+#include "mozilla/Unused.h"
+#include "nsIInputStream.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(AltDataOutputStreamChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) AltDataOutputStreamChild::Release() {
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "AltDataOutputStreamChild");
+
+ if (mRefCnt == 1 && mIPCOpen) {
+ // The only reference left is the IPDL one. After the parent replies back
+ // with a DeleteSelf message, the child will call Send__delete__(this),
+ // decrementing the ref count and triggering the destructor.
+ SendDeleteSelf();
+ return 1;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(AltDataOutputStreamChild)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+AltDataOutputStreamChild::AltDataOutputStreamChild()
+ : mIPCOpen(false), mError(NS_OK), mCallbackFlags(0) {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+void AltDataOutputStreamChild::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void AltDataOutputStreamChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+
+ if (mCallback) {
+ NotifyListener();
+ }
+
+ Release();
+}
+
+bool AltDataOutputStreamChild::WriteDataInChunks(
+ const nsDependentCSubstring& data) {
+ const size_t kChunkSize = 128 * 1024;
+ size_t next = std::min(data.Length(), kChunkSize);
+ for (size_t i = 0; i < data.Length();
+ i = next, next = std::min(data.Length(), next + kChunkSize)) {
+ nsCString chunk(Substring(data, i, kChunkSize));
+ if (mIPCOpen && !SendWriteData(chunk)) {
+ mIPCOpen = false;
+ return false;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Close() { return CloseWithStatus(NS_OK); }
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Flush() {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+
+ // This is a no-op
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::StreamStatus() {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mError;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ if (WriteDataInChunks(nsDependentCSubstring(aBuf, aCount))) {
+ *_retval = aCount;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::IsNonBlocking(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamChild::RecvError(
+ const nsresult& err) {
+ mError = err;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamChild::RecvDeleteSelf() {
+ PAltDataOutputStreamChild::Send__delete__(this);
+ return IPC_OK();
+}
+
+// nsIAsyncOutputStream
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::CloseWithStatus(nsresult aStatus) {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ Unused << SendClose(aStatus);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ mCallback = aCallback;
+ mCallbackFlags = aFlags;
+ mCallbackTarget = aEventTarget;
+
+ if (!mCallback) {
+ return NS_OK;
+ }
+
+ // The stream is blocking so it is writable at any time
+ if (!mIPCOpen || !(aFlags & WAIT_CLOSURE_ONLY)) {
+ NotifyListener();
+ }
+
+ return NS_OK;
+}
+
+void AltDataOutputStreamChild::NotifyListener() {
+ MOZ_ASSERT(mCallback);
+
+ if (!mCallbackTarget) {
+ mCallbackTarget = GetMainThreadSerialEventTarget();
+ }
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
+ NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
+
+ mCallback = nullptr;
+ mCallbackTarget = nullptr;
+
+ asyncCallback->OnOutputStreamReady(this);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamChild.h b/netwerk/protocol/http/AltDataOutputStreamChild.h
new file mode 100644
index 0000000000..1a6d5143dd
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.h
@@ -0,0 +1,51 @@
+/* -*- 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_AltDataOutputStreamChild_h
+#define mozilla_net_AltDataOutputStreamChild_h
+
+#include "mozilla/net/PAltDataOutputStreamChild.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+class AltDataOutputStreamChild : public PAltDataOutputStreamChild,
+ public nsIAsyncOutputStream {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAM
+ explicit AltDataOutputStreamChild();
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+ // Saves an error code which will be reported to the writer on the next call.
+ virtual mozilla::ipc::IPCResult RecvError(const nsresult& err);
+ virtual mozilla::ipc::IPCResult RecvDeleteSelf();
+
+ private:
+ virtual ~AltDataOutputStreamChild() = default;
+ // Sends data to the parent process in 256k chunks.
+ bool WriteDataInChunks(const nsDependentCSubstring& data);
+ void NotifyListener();
+
+ bool mIPCOpen;
+ // If there was an error opening the output stream or writing to it on the
+ // parent side, this will be set to the error code. We check it before we
+ // write so we can report an error to the consumer.
+ nsresult mError;
+
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamChild_h
diff --git a/netwerk/protocol/http/AltDataOutputStreamParent.cpp b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
new file mode 100644
index 0000000000..252e313358
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
@@ -0,0 +1,87 @@
+/* -*- 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 "mozilla/net/AltDataOutputStreamParent.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Unused.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS0(AltDataOutputStreamParent)
+
+AltDataOutputStreamParent::AltDataOutputStreamParent(nsIOutputStream* aStream)
+ : mOutputStream(aStream), mStatus(NS_OK), mIPCOpen(true) {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ PerfStats::RecordMeasurementStart(PerfStats::Metric::JSBC_IO_Write);
+}
+
+AltDataOutputStreamParent::~AltDataOutputStreamParent() {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamParent::RecvWriteData(
+ const nsCString& data) {
+ if (NS_FAILED(mStatus)) {
+ if (mIPCOpen) {
+ Unused << SendError(mStatus);
+ }
+ return IPC_OK();
+ }
+ nsresult rv;
+ uint32_t n;
+ if (mOutputStream) {
+ rv = mOutputStream->Write(data.BeginReading(), data.Length(), &n);
+ MOZ_ASSERT(n == data.Length() || NS_FAILED(rv));
+ if (NS_FAILED(rv) && mIPCOpen) {
+ Unused << SendError(rv);
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamParent::RecvClose(
+ const nsresult& aStatus) {
+ PerfStats::RecordMeasurementEnd(PerfStats::Metric::JSBC_IO_Write);
+
+ if (NS_FAILED(mStatus)) {
+ if (mIPCOpen) {
+ Unused << SendError(mStatus);
+ }
+ return IPC_OK();
+ }
+
+ if (!mOutputStream) {
+ return IPC_OK();
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> asyncOutputStream =
+ do_QueryInterface(mOutputStream);
+ MOZ_ASSERT(asyncOutputStream);
+
+ nsresult rv = asyncOutputStream->CloseWithStatus(aStatus);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ Unused << SendError(rv);
+ }
+
+ mOutputStream = nullptr;
+ return IPC_OK();
+}
+
+void AltDataOutputStreamParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mIPCOpen = false;
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamParent::RecvDeleteSelf() {
+ mIPCOpen = false;
+ Unused << SendDeleteSelf();
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamParent.h b/netwerk/protocol/http/AltDataOutputStreamParent.h
new file mode 100644
index 0000000000..a9642b9e45
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.h
@@ -0,0 +1,52 @@
+/* -*- 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_AltDataOutputStreamParent_h
+#define mozilla_net_AltDataOutputStreamParent_h
+
+#include "mozilla/net/PAltDataOutputStreamParent.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+// Forwards data received from the content process to an output stream.
+class AltDataOutputStreamParent : public PAltDataOutputStreamParent,
+ public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // Called from NeckoParent::AllocPAltDataOutputStreamParent which also opens
+ // the output stream.
+ // aStream may be null
+ explicit AltDataOutputStreamParent(nsIOutputStream* aStream);
+
+ // Called when data is received from the content process.
+ // We proceed to write that data to the output stream.
+ mozilla::ipc::IPCResult RecvWriteData(const nsCString& data);
+ // Called when AltDataOutputStreamChild::Close() is
+ // Closes and nulls the output stream.
+ mozilla::ipc::IPCResult RecvClose(const nsresult& aStatus);
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ // Sets an error that will be reported to the content process.
+ void SetError(nsresult status) { mStatus = status; }
+ mozilla::ipc::IPCResult RecvDeleteSelf();
+
+ private:
+ virtual ~AltDataOutputStreamParent();
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ // In case any error occurs mStatus will be != NS_OK, and this status code
+ // will be sent to the content process asynchronously.
+ nsresult mStatus;
+ bool mIPCOpen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamParent_h
diff --git a/netwerk/protocol/http/AltServiceChild.cpp b/netwerk/protocol/http/AltServiceChild.cpp
new file mode 100644
index 0000000000..79959f2161
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceChild.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltServiceChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsProxyInfo.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<AltServiceChild> sAltServiceChild;
+
+AltServiceChild::AltServiceChild() {
+ LOG(("AltServiceChild ctor [%p]\n", this));
+}
+
+AltServiceChild::~AltServiceChild() {
+ LOG(("AltServiceChild dtor [%p]\n", this));
+}
+
+// static
+bool AltServiceChild::EnsureAltServiceChild() {
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sAltServiceChild) {
+ return true;
+ }
+
+ SocketProcessChild* socketChild = SocketProcessChild::GetSingleton();
+ if (!socketChild || socketChild->IsShuttingDown()) {
+ return false;
+ }
+
+ sAltServiceChild = new AltServiceChild();
+ ClearOnShutdown(&sAltServiceChild);
+
+ if (!socketChild->SendPAltServiceConstructor(sAltServiceChild)) {
+ sAltServiceChild = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+// static
+void AltServiceChild::ClearHostMapping(nsHttpConnectionInfo* aCi) {
+ LOG(("AltServiceChild::ClearHostMapping ci=%s", aCi->HashKey().get()));
+ MOZ_ASSERT(aCi);
+
+ RefPtr<nsHttpConnectionInfo> ci = aCi->Clone();
+ auto task = [ci{std::move(ci)}]() {
+ if (!EnsureAltServiceChild()) {
+ return;
+ }
+
+ if (!ci->GetOrigin().IsEmpty() && sAltServiceChild->CanSend()) {
+ Unused << sAltServiceChild->SendClearHostMapping(
+ ci->GetOrigin(), ci->OriginPort(), ci->GetOriginAttributes());
+ }
+ };
+
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::AltServiceChild::ClearHostMapping", task)));
+ return;
+ }
+
+ task();
+}
+
+// static
+void AltServiceChild::ProcessHeader(
+ const nsCString& aBuf, const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, int32_t aOriginPort,
+ const nsCString& aUsername, bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks, nsProxyInfo* aProxyInfo, uint32_t aCaps,
+ const OriginAttributes& aOriginAttributes) {
+ LOG(("AltServiceChild::ProcessHeader"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!EnsureAltServiceChild()) {
+ return;
+ }
+
+ if (!sAltServiceChild->CanSend()) {
+ return;
+ }
+
+ nsTArray<ProxyInfoCloneArgs> proxyInfoArray;
+ if (aProxyInfo) {
+ nsProxyInfo::SerializeProxyInfo(aProxyInfo, proxyInfoArray);
+ }
+
+ Unused << sAltServiceChild->SendProcessHeader(
+ aBuf, aOriginScheme, aOriginHost, aOriginPort, aUsername,
+ aPrivateBrowsing, proxyInfoArray, aCaps, aOriginAttributes);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltServiceChild.h b/netwerk/protocol/http/AltServiceChild.h
new file mode 100644
index 0000000000..cc70a0e048
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceChild.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AltServiceChild_h__
+#define AltServiceChild_h__
+
+#include "mozilla/net/PAltServiceChild.h"
+
+class nsIInterfaceRequestor;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnectionInfo;
+class nsProxyInfo;
+
+class AltServiceChild final : public PAltServiceChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(AltServiceChild, override)
+
+ static bool EnsureAltServiceChild();
+ static void ClearHostMapping(nsHttpConnectionInfo* aCi);
+ static void ProcessHeader(const nsCString& aBuf,
+ const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, int32_t aOriginPort,
+ const nsCString& aUsername, bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks,
+ nsProxyInfo* aProxyInfo, uint32_t aCaps,
+ const OriginAttributes& aOriginAttributes);
+
+ private:
+ AltServiceChild();
+ virtual ~AltServiceChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltServiceChild_h__
diff --git a/netwerk/protocol/http/AltServiceParent.cpp b/netwerk/protocol/http/AltServiceParent.cpp
new file mode 100644
index 0000000000..468925c2ad
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceParent.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltServiceParent.h"
+#include "AlternateServices.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+mozilla::ipc::IPCResult AltServiceParent::RecvClearHostMapping(
+ const nsCString& aHost, const int32_t& aPort,
+ const OriginAttributes& aOriginAttributes) {
+ LOG(("AltServiceParent::RecvClearHostMapping [this=%p]\n", this));
+ if (gHttpHandler) {
+ gHttpHandler->AltServiceCache()->ClearHostMapping(aHost, aPort,
+ aOriginAttributes);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltServiceParent::RecvProcessHeader(
+ const nsCString& aBuf, const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, const int32_t& aOriginPort,
+ const nsACString& aUsername, const bool& aPrivateBrowsing,
+ nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const uint32_t& aCaps,
+ const OriginAttributes& aOriginAttributes) {
+ LOG(("AltServiceParent::RecvProcessHeader [this=%p]\n", this));
+ nsProxyInfo* pi = aProxyInfo.IsEmpty()
+ ? nullptr
+ : nsProxyInfo::DeserializeProxyInfo(aProxyInfo);
+ AltSvcMapping::ProcessHeader(aBuf, aOriginScheme, aOriginHost, aOriginPort,
+ aUsername, aPrivateBrowsing, nullptr, pi, aCaps,
+ aOriginAttributes);
+ return IPC_OK();
+}
+
+void AltServiceParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("AltServiceParent::ActorDestroy [this=%p]\n", this));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltServiceParent.h b/netwerk/protocol/http/AltServiceParent.h
new file mode 100644
index 0000000000..471b7049b2
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceParent.h
@@ -0,0 +1,39 @@
+/* -*- 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 AltServiceParent_h__
+#define AltServiceParent_h__
+
+#include "mozilla/net/PAltServiceParent.h"
+
+namespace mozilla {
+namespace net {
+
+class AltServiceParent final : public PAltServiceParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(AltServiceParent, override)
+ AltServiceParent() = default;
+
+ mozilla::ipc::IPCResult RecvClearHostMapping(
+ const nsCString& aHost, const int32_t& aPort,
+ const OriginAttributes& aOriginAttributes);
+
+ mozilla::ipc::IPCResult RecvProcessHeader(
+ const nsCString& aBuf, const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, const int32_t& aOriginPort,
+ const nsACString& aUsername, const bool& aPrivateBrowsing,
+ nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const uint32_t& aCaps,
+ const OriginAttributes& aOriginAttributes);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ virtual ~AltServiceParent() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltServiceParent_h__
diff --git a/netwerk/protocol/http/AltSvcTransactionChild.cpp b/netwerk/protocol/http/AltSvcTransactionChild.cpp
new file mode 100644
index 0000000000..096d584c0e
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionChild.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltSvcTransactionChild.h"
+#include "AlternateServices.h"
+#include "nsHttpConnectionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+AltSvcTransactionChild::AltSvcTransactionChild(nsHttpConnectionInfo* aConnInfo,
+ uint32_t aCaps)
+ : mConnInfo(aConnInfo), mCaps(aCaps) {
+ LOG(("AltSvcTransactionChild %p ctor", this));
+}
+
+AltSvcTransactionChild::~AltSvcTransactionChild() {
+ LOG(("AltSvcTransactionChild %p dtor", this));
+}
+
+void AltSvcTransactionChild::OnTransactionDestroy(bool aValidateResult) {
+ LOG(("AltSvcTransactionChild::OnTransactionDestroy %p aValidateResult=%d",
+ this, aValidateResult));
+ RefPtr<AltSvcTransactionChild> self = this;
+ auto task = [self, aValidateResult]() {
+ if (self->CanSend()) {
+ self->Send__delete__(self, aValidateResult);
+ }
+ };
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("AltSvcTransactionChild::OnTransactionClose",
+ std::move(task)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ task();
+}
+
+void AltSvcTransactionChild::OnTransactionClose(bool aValidateResult) {
+ LOG(("AltSvcTransactionChild::OnTransactionClose %p aValidateResult=%d", this,
+ aValidateResult));
+ RefPtr<AltSvcTransactionChild> self = this;
+ auto task = [self, aValidateResult]() {
+ if (self->CanSend()) {
+ self->SendOnTransactionClose(aValidateResult);
+ }
+ };
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("AltSvcTransactionChild::OnTransactionClose",
+ std::move(task)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ task();
+}
+
+already_AddRefed<SpeculativeTransaction>
+AltSvcTransactionChild::CreateTransaction() {
+ RefPtr<SpeculativeTransaction> transaction =
+ new AltSvcTransaction<AltSvcTransactionChild>(mConnInfo, nullptr, mCaps,
+ this, mConnInfo->IsHttp3());
+ return transaction.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltSvcTransactionChild.h b/netwerk/protocol/http/AltSvcTransactionChild.h
new file mode 100644
index 0000000000..eb3b4aa580
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionChild.h
@@ -0,0 +1,39 @@
+/* -*- 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 AltSvcTransactionChild_h__
+#define AltSvcTransactionChild_h__
+
+#include "mozilla/net/PAltSvcTransactionChild.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnectionInfo;
+class SpeculativeTransaction;
+
+class AltSvcTransactionChild final : public PAltSvcTransactionChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcTransactionChild, override)
+
+ explicit AltSvcTransactionChild(nsHttpConnectionInfo* aConnInfo,
+ uint32_t aCaps);
+
+ void OnTransactionDestroy(bool aValidateResult);
+ void OnTransactionClose(bool aValidateResult);
+
+ already_AddRefed<SpeculativeTransaction> CreateTransaction();
+
+ private:
+ virtual ~AltSvcTransactionChild();
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ uint32_t mCaps;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltSvcTransactionChild_h__
diff --git a/netwerk/protocol/http/AltSvcTransactionParent.cpp b/netwerk/protocol/http/AltSvcTransactionParent.cpp
new file mode 100644
index 0000000000..494efa7f65
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionParent.cpp
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltSvcTransactionParent.h"
+#include "AlternateServices.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttpConnectionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF_INHERITED(AltSvcTransactionParent, NullHttpTransaction)
+NS_IMPL_RELEASE_INHERITED(AltSvcTransactionParent, NullHttpTransaction)
+
+NS_INTERFACE_MAP_BEGIN(AltSvcTransactionParent)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(AltSvcTransactionParent)
+NS_INTERFACE_MAP_END_INHERITING(NullHttpTransaction)
+
+AltSvcTransactionParent::AltSvcTransactionParent(
+ nsHttpConnectionInfo* aConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, AltSvcMappingValidator* aValidator)
+ : SpeculativeTransaction(aConnInfo, aCallbacks,
+ aCaps & ~NS_HTTP_ALLOW_KEEPALIVE),
+ mValidator(aValidator) {
+ LOG(("AltSvcTransactionParent %p ctor", this));
+ MOZ_ASSERT(mValidator);
+}
+
+AltSvcTransactionParent::~AltSvcTransactionParent() {
+ LOG(("AltSvcTransactionParent %p dtor", this));
+}
+
+bool AltSvcTransactionParent::Init() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (!parent) {
+ return false;
+ }
+
+ HttpConnectionInfoCloneArgs connInfo;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(mConnectionInfo, connInfo);
+ return parent->SendPAltSvcTransactionConstructor(this, connInfo, mCaps);
+}
+
+mozilla::ipc::IPCResult AltSvcTransactionParent::Recv__delete__(
+ const bool& aValidateResult) {
+ LOG(("AltSvcTransactionParent::Recv__delete__ this=%p", this));
+ mValidator->OnTransactionDestroy(aValidateResult);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltSvcTransactionParent::RecvOnTransactionClose(
+ const bool& aValidateResult) {
+ LOG(("AltSvcTransactionParent::RecvOnTransactionClose this=%p", this));
+ mValidator->OnTransactionClose(aValidateResult);
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltSvcTransactionParent.h b/netwerk/protocol/http/AltSvcTransactionParent.h
new file mode 100644
index 0000000000..dc910ad301
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AltSvcTransactionParent_h__
+#define AltSvcTransactionParent_h__
+
+#include "mozilla/net/PAltSvcTransactionParent.h"
+#include "mozilla/net/SpeculativeTransaction.h"
+
+namespace mozilla {
+namespace net {
+
+class AltSvcMappingValidator;
+
+// 03d22e57-c364-4871-989a-6593eb909d24
+#define ALTSVCTRANSACTIONPARENT_IID \
+ { \
+ 0x03d22e57, 0xc364, 0x4871, { \
+ 0x98, 0x9a, 0x65, 0x93, 0xeb, 0x90, 0x9d, 0x24 \
+ } \
+ }
+
+class AltSvcTransactionParent final : public PAltSvcTransactionParent,
+ public SpeculativeTransaction {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECLARE_STATIC_IID_ACCESSOR(ALTSVCTRANSACTIONPARENT_IID)
+
+ explicit AltSvcTransactionParent(nsHttpConnectionInfo* aConnInfo,
+ nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps,
+ AltSvcMappingValidator* aValidator);
+
+ bool Init();
+ mozilla::ipc::IPCResult Recv__delete__(const bool& aValidateResult);
+ mozilla::ipc::IPCResult RecvOnTransactionClose(const bool& aValidateResult);
+
+ private:
+ virtual ~AltSvcTransactionParent();
+
+ RefPtr<AltSvcMappingValidator> mValidator;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(AltSvcTransactionParent,
+ ALTSVCTRANSACTIONPARENT_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltSvcTransactionParent_h__
diff --git a/netwerk/protocol/http/AlternateServices.cpp b/netwerk/protocol/http/AlternateServices.cpp
new file mode 100644
index 0000000000..a386206b6d
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -0,0 +1,1354 @@
+/* -*- 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 "HttpLog.h"
+
+#include "AlternateServices.h"
+#include "LoadInfo.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/net/AltSvcTransactionChild.h"
+#include "mozilla/net/AltSvcTransactionParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsEscape.h"
+#include "nsHttpChannel.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "nsHttpTransaction.h"
+#include "nsIOService.h"
+#include "nsITLSSocketControl.h"
+#include "nsIWellKnownOpportunisticUtils.h"
+#include "nsThreadUtils.h"
+
+/* RFC 7838 Alternative Services
+ http://httpwg.org/http-extensions/opsec.html
+ note that connections currently do not do mixed-scheme (the I attribute
+ in the ConnectionInfo prevents it) but could, do not honor tls-commit and
+ should not, and always require authentication
+*/
+
+namespace mozilla {
+namespace net {
+
+// function places true in outIsHTTPS if scheme is https, false if
+// http, and returns an error if neither. originScheme passed into
+// alternate service should already be normalized to those lower case
+// strings by the URI parser (and so there is an assert)- this is an extra
+// check.
+static nsresult SchemeIsHTTPS(const nsACString& originScheme,
+ bool& outIsHTTPS) {
+ outIsHTTPS = originScheme.EqualsLiteral("https");
+
+ if (!outIsHTTPS && !originScheme.EqualsLiteral("http")) {
+ MOZ_ASSERT(!originScheme.LowerCaseEqualsLiteral("https") &&
+ !originScheme.LowerCaseEqualsLiteral("http"),
+ "The scheme should already be lowercase");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+bool AltSvcMapping::AcceptableProxy(nsProxyInfo* proxyInfo) {
+ return !proxyInfo || proxyInfo->IsDirect() || proxyInfo->IsSOCKS();
+}
+
+void AltSvcMapping::ProcessHeader(
+ const nsCString& buf, const nsCString& originScheme,
+ const nsCString& originHost, int32_t originPort, const nsACString& username,
+ bool privateBrowsing, nsIInterfaceRequestor* callbacks,
+ nsProxyInfo* proxyInfo, uint32_t caps,
+ const OriginAttributes& originAttributes,
+ bool aDontValidate /* = false */) { // aDontValidate is only used for
+ // testing
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("AltSvcMapping::ProcessHeader: %s\n", buf.get()));
+
+ if (StaticPrefs::network_http_altsvc_proxy_checks() &&
+ !AcceptableProxy(proxyInfo)) {
+ LOG(("AltSvcMapping::ProcessHeader ignoring due to proxy\n"));
+ return;
+ }
+
+ bool isHTTPS;
+ if (NS_FAILED(SchemeIsHTTPS(originScheme, isHTTPS))) {
+ return;
+ }
+ if (!isHTTPS && !gHttpHandler->AllowAltSvcOE()) {
+ LOG(("Alt-Svc Response Header for http:// origin but OE disabled\n"));
+ return;
+ }
+
+ LOG(("Alt-Svc Response Header %s\n", buf.get()));
+ ParsedHeaderValueListList parsedAltSvc(buf);
+ int32_t numEntriesInHeader = parsedAltSvc.mValues.Length();
+
+ // Only use one http3 version.
+ bool http3Found = false;
+
+ for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
+ uint32_t maxage = 86400; // default
+ nsAutoCString hostname;
+ nsAutoCString npnToken;
+ int32_t portno = originPort;
+ bool clearEntry = false;
+ bool isHttp3 = false;
+
+ for (uint32_t pairIndex = 0;
+ pairIndex < parsedAltSvc.mValues[index].mValues.Length();
+ ++pairIndex) {
+ nsDependentCSubstring& currentName =
+ parsedAltSvc.mValues[index].mValues[pairIndex].mName;
+ nsDependentCSubstring& currentValue =
+ parsedAltSvc.mValues[index].mValues[pairIndex].mValue;
+
+ if (!pairIndex) {
+ if (currentName.EqualsLiteral("clear")) {
+ clearEntry = true;
+ --numEntriesInHeader; // Only want to keep track of actual alt-svc
+ // maps, not clearing
+ break;
+ }
+
+ // h2=[hostname]:443 or h3-xx=[hostname]:port
+ // XX is current version we support and it is define in nsHttp.h.
+ isHttp3 = gHttpHandler->IsHttp3VersionSupported(currentName);
+ npnToken = currentName;
+
+ int32_t colonIndex = currentValue.FindChar(':');
+ if (colonIndex >= 0) {
+ portno =
+ atoi(PromiseFlatCString(currentValue).get() + colonIndex + 1);
+ } else {
+ colonIndex = 0;
+ }
+ hostname.Assign(currentValue.BeginReading(), colonIndex);
+ } else if (currentName.EqualsLiteral("ma")) {
+ maxage = atoi(PromiseFlatCString(currentValue).get());
+ } else {
+ LOG(("Alt Svc ignoring parameter %s", currentName.BeginReading()));
+ }
+ }
+
+ if (clearEntry) {
+ nsCString suffix;
+ originAttributes.CreateSuffix(suffix);
+ LOG(("Alt Svc clearing mapping for %s:%d:%s", originHost.get(),
+ originPort, suffix.get()));
+ gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort,
+ originAttributes);
+ continue;
+ }
+
+ if (NS_FAILED(NS_CheckPortSafety(portno, originScheme.get()))) {
+ LOG(("Alt Svc doesn't allow port %d, ignoring", portno));
+ continue;
+ }
+
+ // unescape modifies a c string in place, so afterwards
+ // update nsCString length
+ nsUnescape(npnToken.BeginWriting());
+ npnToken.SetLength(strlen(npnToken.BeginReading()));
+
+ if (http3Found && isHttp3) {
+ LOG(("Alt Svc ignore multiple Http3 options (%s)", npnToken.get()));
+ continue;
+ }
+
+ SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
+ if (!(npnToken.Equals(spdyInfo->VersionString) &&
+ StaticPrefs::network_http_http2_enabled()) &&
+ !(isHttp3 && nsHttpHandler::IsHttp3Enabled() &&
+ !gHttpHandler->IsHttp3Excluded(hostname.IsEmpty() ? originHost
+ : hostname))) {
+ LOG(("Alt Svc unknown protocol %s, ignoring", npnToken.get()));
+ continue;
+ }
+
+ if (isHttp3) {
+ http3Found = true;
+ }
+
+ RefPtr<AltSvcMapping> mapping =
+ new AltSvcMapping(gHttpHandler->AltServiceCache()->GetStoragePtr(),
+ gHttpHandler->AltServiceCache()->StorageEpoch(),
+ originScheme, originHost, originPort, username,
+ privateBrowsing, NowInSeconds() + maxage, hostname,
+ portno, npnToken, originAttributes, isHttp3);
+ if (mapping->TTL() <= 0) {
+ LOG(("Alt Svc invalid map"));
+ mapping = nullptr;
+ // since this isn't a parse error, let's clear any existing mapping
+ // as that would have happened if we had accepted the parameters.
+ gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort,
+ originAttributes);
+ } else if (!aDontValidate) {
+ gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
+ originAttributes);
+ } else {
+ gHttpHandler->UpdateAltServiceMappingWithoutValidation(
+ mapping, proxyInfo, callbacks, caps, originAttributes);
+ }
+ }
+
+ if (numEntriesInHeader) { // Ignore headers that were just "alt-svc: clear"
+ Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_ENTRIES_PER_HEADER,
+ numEntriesInHeader);
+ }
+}
+
+AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
+ const nsACString& originScheme,
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& username, bool privateBrowsing,
+ uint32_t expiresAt,
+ const nsACString& alternateHost,
+ int32_t alternatePort, const nsACString& npnToken,
+ const OriginAttributes& originAttributes,
+ bool aIsHttp3)
+ : mStorage(storage),
+ mStorageEpoch(epoch),
+ mAlternateHost(alternateHost),
+ mAlternatePort(alternatePort),
+ mOriginHost(originHost),
+ mOriginPort(originPort),
+ mUsername(username),
+ mPrivate(privateBrowsing),
+ mExpiresAt(expiresAt),
+ mNPNToken(npnToken),
+ mOriginAttributes(originAttributes),
+ mIsHttp3(aIsHttp3) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) {
+ LOG(("AltSvcMapping ctor %p invalid scheme\n", this));
+ mExpiresAt = 0; // invalid
+ }
+
+ if (mAlternatePort == -1) {
+ mAlternatePort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+ if (mOriginPort == -1) {
+ mOriginPort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+
+ LOG(("AltSvcMapping ctor %p %s://%s:%d to %s:%d\n", this,
+ nsCString(originScheme).get(), mOriginHost.get(), mOriginPort,
+ mAlternateHost.get(), mAlternatePort));
+
+ if (mAlternateHost.IsEmpty()) {
+ mAlternateHost = mOriginHost;
+ }
+
+ if ((mAlternatePort == mOriginPort) &&
+ mAlternateHost.EqualsIgnoreCase(mOriginHost.get()) && !mIsHttp3) {
+ // Http2 on the same host:port does not make sense because we are
+ // connecting to the same end point over the same protocol (TCP) as with
+ // original host. On the other hand, for Http3 alt-svc can be hosted on
+ // the same host:port because protocol(UDP vs. TCP) is always different and
+ // we are not connecting to the same end point.
+ LOG(("Alt Svc is also origin Svc - ignoring\n"));
+ mExpiresAt = 0; // invalid
+ }
+
+ if (mExpiresAt) {
+ MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate,
+ mOriginAttributes, mIsHttp3);
+ }
+}
+
+void AltSvcMapping::MakeHashKey(nsCString& outKey,
+ const nsACString& originScheme,
+ const nsACString& originHost,
+ int32_t originPort, bool privateBrowsing,
+ const OriginAttributes& originAttributes,
+ bool aHttp3) {
+ outKey.Truncate();
+
+ if (originPort == -1) {
+ bool isHttps = originScheme.EqualsLiteral("https");
+ originPort = isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+
+ outKey.Append(originScheme);
+ outKey.Append(':');
+ outKey.Append(originHost);
+ outKey.Append(':');
+ outKey.AppendInt(originPort);
+ outKey.Append(':');
+ outKey.Append(privateBrowsing ? 'P' : '.');
+ outKey.Append(':');
+ nsAutoCString suffix;
+ originAttributes.CreateSuffix(suffix);
+ outKey.Append(suffix);
+ outKey.Append(':');
+
+ outKey.Append(aHttp3 ? '3' : '.');
+}
+
+int32_t AltSvcMapping::TTL() { return mExpiresAt - NowInSeconds(); }
+
+void AltSvcMapping::SyncString(const nsCString& str) {
+ MOZ_ASSERT(NS_IsMainThread());
+ (void)mStorage->Put(HashKey(), str,
+ mPrivate ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+}
+
+void AltSvcMapping::Sync() {
+ if (!mStorage) {
+ return;
+ }
+ if (mSyncOnlyOnSuccess && !mValidated) {
+ return;
+ }
+ nsCString value;
+ Serialize(value);
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> r;
+ r = NewRunnableMethod<nsCString>("net::AltSvcMapping::SyncString", this,
+ &AltSvcMapping::SyncString, value);
+ NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ (void)mStorage->Put(HashKey(), value,
+ mPrivate ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+}
+
+void AltSvcMapping::SetValidated(bool val) {
+ mValidated = val;
+ Sync();
+}
+
+void AltSvcMapping::SetMixedScheme(bool val) {
+ mMixedScheme = val;
+ Sync();
+}
+
+void AltSvcMapping::SetExpiresAt(int32_t val) {
+ mExpiresAt = val;
+ Sync();
+}
+
+void AltSvcMapping::SetExpired() {
+ LOG(("AltSvcMapping SetExpired %p origin %s alternate %s\n", this,
+ mOriginHost.get(), mAlternateHost.get()));
+ mExpiresAt = NowInSeconds() - 1;
+ Sync();
+}
+
+bool AltSvcMapping::RouteEquals(AltSvcMapping* map) {
+ MOZ_ASSERT(map->mHashKey.Equals(mHashKey));
+ return mAlternateHost.Equals(map->mAlternateHost) &&
+ (mAlternatePort == map->mAlternatePort) &&
+ mNPNToken.Equals(map->mNPNToken);
+}
+
+void AltSvcMapping::GetConnectionInfo(
+ nsHttpConnectionInfo** outCI, nsProxyInfo* pi,
+ const OriginAttributes& originAttributes) {
+ RefPtr<nsHttpConnectionInfo> ci = new nsHttpConnectionInfo(
+ mOriginHost, mOriginPort, mNPNToken, mUsername, pi, originAttributes,
+ mAlternateHost, mAlternatePort, mIsHttp3, false);
+
+ // http:// without the mixed-scheme attribute needs to be segmented in the
+ // connection manager connection information hash with this attribute
+ if (!mHttps && !mMixedScheme) {
+ ci->SetInsecureScheme(true);
+ }
+ ci->SetPrivate(mPrivate);
+ ci.forget(outCI);
+}
+
+void AltSvcMapping::Serialize(nsCString& out) {
+ // Be careful, when serializing new members, add them to the end of this list.
+ out = mHttps ? "https:"_ns : "http:"_ns;
+ out.Append(mOriginHost);
+ out.Append(':');
+ out.AppendInt(mOriginPort);
+ out.Append(':');
+ out.Append(mAlternateHost);
+ out.Append(':');
+ out.AppendInt(mAlternatePort);
+ out.Append(':');
+ out.Append(mUsername);
+ out.Append(':');
+ out.Append(mPrivate ? 'y' : 'n');
+ out.Append(':');
+ out.AppendInt(mExpiresAt);
+ out.Append(':');
+ out.Append(mNPNToken);
+ out.Append(':');
+ out.Append(mValidated ? 'y' : 'n');
+ out.Append(':');
+ out.AppendInt(mStorageEpoch);
+ out.Append(':');
+ out.Append(mMixedScheme ? 'y' : 'n');
+ out.Append(':');
+ nsAutoCString suffix;
+ mOriginAttributes.CreateSuffix(suffix);
+ out.Append(suffix);
+ out.Append(':');
+ out.Append(""_ns); // Formerly topWindowOrigin. Now unused empty string.
+ out.Append('|'); // Be careful, the top window origin may contain colons!
+ out.Append('n'); // Formerly mIsolated. Now always 'n'. Should remove someday
+ out.Append(':');
+ out.Append(mIsHttp3 ? 'y' : 'n');
+ out.Append(':');
+ // Add code to serialize new members here!
+}
+
+AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
+ const nsCString& str)
+ : mStorage(storage), mStorageEpoch(epoch) {
+ mValidated = false;
+ nsresult code;
+ char separator = ':';
+
+ // The the do {} while(0) loop acts like try/catch(e){} with the break in
+ // _NS_NEXT_TOKEN
+ do {
+#ifdef _NS_NEXT_TOKEN
+ COMPILER ERROR
+#endif
+#define _NS_NEXT_TOKEN \
+ start = idx + 1; \
+ idx = str.FindChar(separator, start); \
+ if (idx < 0) break;
+ int32_t start = 0;
+ int32_t idx;
+ idx = str.FindChar(separator, start);
+ if (idx < 0) break;
+ // Be careful, when deserializing new members, add them to the end of this
+ // list.
+ mHttps = Substring(str, start, idx - start).EqualsLiteral("https");
+ _NS_NEXT_TOKEN;
+ mOriginHost = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mOriginPort =
+ nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mAlternateHost = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mAlternatePort =
+ nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mUsername = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mPrivate = Substring(str, start, idx - start).EqualsLiteral("y");
+ _NS_NEXT_TOKEN;
+ mExpiresAt = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mNPNToken = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mValidated = Substring(str, start, idx - start).EqualsLiteral("y");
+ _NS_NEXT_TOKEN;
+ mStorageEpoch =
+ nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mMixedScheme = Substring(str, start, idx - start).EqualsLiteral("y");
+ _NS_NEXT_TOKEN;
+ Unused << mOriginAttributes.PopulateFromSuffix(
+ Substring(str, start, idx - start));
+ // The separator after the top window origin is a pipe character since the
+ // origin string can contain colons.
+ separator = '|';
+ _NS_NEXT_TOKEN;
+ // TopWindowOrigin used to be encoded here. Now it's unused.
+ separator = ':';
+ _NS_NEXT_TOKEN;
+ // mIsolated used to be encoded here. Now it's unused.
+ _NS_NEXT_TOKEN;
+ mIsHttp3 = Substring(str, start, idx - start).EqualsLiteral("y");
+ // Add code to deserialize new members here!
+#undef _NS_NEXT_TOKEN
+
+ MakeHashKey(mHashKey, mHttps ? "https"_ns : "http"_ns, mOriginHost,
+ mOriginPort, mPrivate, mOriginAttributes, mIsHttp3);
+ } while (false);
+}
+
+AltSvcMappingValidator::AltSvcMappingValidator(AltSvcMapping* aMap)
+ : mMapping(aMap) {
+ LOG(("AltSvcMappingValidator ctor %p map %p [%s -> %s]", this, aMap,
+ aMap->OriginHost().get(), aMap->AlternateHost().get()));
+ MOZ_ASSERT(mMapping);
+ MOZ_ASSERT(mMapping->HTTPS()); // http:// uses the .wk path
+}
+
+void AltSvcMappingValidator::OnTransactionDestroy(bool aValidateResult) {
+ mMapping->SetValidated(aValidateResult);
+ if (!mMapping->Validated()) {
+ // try again later
+ mMapping->SetExpiresAt(NowInSeconds() + 2);
+ }
+ LOG(
+ ("AltSvcMappingValidator::OnTransactionDestroy %p map %p validated %d "
+ "[%s]",
+ this, mMapping.get(), mMapping->Validated(), mMapping->HashKey().get()));
+}
+
+void AltSvcMappingValidator::OnTransactionClose(bool aValidateResult) {
+ mMapping->SetValidated(aValidateResult);
+ LOG(
+ ("AltSvcMappingValidator::OnTransactionClose %p map %p validated %d "
+ "[%s]",
+ this, mMapping.get(), mMapping->Validated(), mMapping->HashKey().get()));
+}
+
+template <class Validator>
+AltSvcTransaction<Validator>::AltSvcTransaction(
+ nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
+ Validator* aValidator, bool aIsHttp3)
+ : SpeculativeTransaction(ci, callbacks, caps),
+ mValidator(aValidator),
+ mIsHttp3(aIsHttp3),
+ mRunning(true),
+ mTriedToValidate(false),
+ mTriedToWrite(false),
+ mValidatedResult(false) {
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess());
+ MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(), XRE_IsParentProcess());
+ // We don't want to let this transaction use consistent connection.
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+}
+
+template <class Validator>
+AltSvcTransaction<Validator>::~AltSvcTransaction() {
+ LOG(("AltSvcTransaction dtor %p running %d", this, mRunning));
+
+ if (mRunning) {
+ mValidatedResult = MaybeValidate(NS_OK);
+ mValidator->OnTransactionDestroy(mValidatedResult);
+ }
+}
+
+template <class Validator>
+bool AltSvcTransaction<Validator>::MaybeValidate(nsresult reason) {
+ if (mTriedToValidate) {
+ return mValidatedResult;
+ }
+ mTriedToValidate = true;
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p reason=%" PRIx32
+ " running=%d conn=%p write=%d",
+ this, static_cast<uint32_t>(reason), mRunning, mConnection.get(),
+ mTriedToWrite));
+
+ if (mTriedToWrite && reason == NS_BASE_STREAM_CLOSED) {
+ // The normal course of events is to cause the transaction to fail with
+ // CLOSED on a write - so that's a success that means the HTTP/2 session
+ // is setup.
+ reason = NS_OK;
+ }
+
+ if (NS_FAILED(reason) || !mRunning || !mConnection) {
+ LOG(("AltSvcTransaction::MaybeValidate %p Failed due to precondition",
+ this));
+ return false;
+ }
+
+ // insist on >= http/2
+ HttpVersion version = mConnection->Version();
+ LOG(("AltSvcTransaction::MaybeValidate() %p version %d\n", this,
+ static_cast<int32_t>(version)));
+ if ((!mIsHttp3 && (version != HttpVersion::v2_0)) ||
+ (mIsHttp3 && (version != HttpVersion::v3_0))) {
+ LOG(
+ ("AltSvcTransaction::MaybeValidate %p Failed due to protocol version"
+ " expacted %s.",
+ this, mIsHttp3 ? "Http3" : "Http2"));
+ return false;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> socketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p socketControl=%p\n", this,
+ socketControl.get()));
+
+ if (socketControl->GetFailedVerification()) {
+ LOG(
+ ("AltSvcTransaction::MaybeValidate() %p "
+ "not validated due to auth error",
+ this));
+ return false;
+ }
+
+ LOG(
+ ("AltSvcTransaction::MaybeValidate() %p "
+ "validating alternate service with successful auth check",
+ this));
+
+ return true;
+}
+
+template <class Validator>
+void AltSvcTransaction<Validator>::Close(nsresult reason) {
+ LOG(("AltSvcTransaction::Close() %p reason=%" PRIx32 " running %d", this,
+ static_cast<uint32_t>(reason), mRunning));
+
+ mValidatedResult = MaybeValidate(reason);
+ mValidator->OnTransactionClose(mValidatedResult);
+ if (!mValidatedResult && mConnection) {
+ mConnection->DontReuse();
+ }
+ NullHttpTransaction::Close(reason);
+}
+
+template <class Validator>
+nsresult AltSvcTransaction<Validator>::ReadSegments(
+ nsAHttpSegmentReader* reader, uint32_t count, uint32_t* countRead) {
+ LOG(("AltSvcTransaction::ReadSegements() %p\n", this));
+ mTriedToWrite = true;
+ return NullHttpTransaction::ReadSegments(reader, count, countRead);
+}
+
+class WellKnownChecker {
+ public:
+ WellKnownChecker(nsIURI* uri, const nsCString& origin, uint32_t caps,
+ nsHttpConnectionInfo* ci, AltSvcMapping* mapping)
+ : mWaiting(
+ 2) // waiting for 2 channels (default and alternate) to complete
+ ,
+ mOrigin(origin),
+ mAlternatePort(ci->RoutedPort()),
+ mMapping(mapping),
+ mCI(ci),
+ mURI(uri),
+ mCaps(caps) {
+ LOG(("WellKnownChecker ctor %p\n", this));
+ MOZ_ASSERT(!mMapping->HTTPS());
+ }
+
+ nsresult Start() {
+ LOG(("WellKnownChecker::Start %p\n", this));
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new LoadInfo(nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ loadInfo->SetOriginAttributes(mCI->GetOriginAttributes());
+ // allow deprecated HTTP request from SystemPrincipal
+ loadInfo->SetAllowDeprecatedSystemRequests(true);
+
+ RefPtr<nsHttpChannel> chan = new nsHttpChannel();
+ nsresult rv;
+
+ mTransactionAlternate = new TransactionObserver(chan, this);
+ RefPtr<nsHttpConnectionInfo> newCI = mCI->Clone();
+ rv = MakeChannel(chan, mTransactionAlternate, newCI, mURI, mCaps, loadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ chan = new nsHttpChannel();
+ mTransactionOrigin = new TransactionObserver(chan, this);
+ newCI = nullptr;
+ return MakeChannel(chan, mTransactionOrigin, newCI, mURI, mCaps, loadInfo);
+ }
+
+ void Done(TransactionObserver* finished) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("WellKnownChecker::Done %p waiting for %d\n", this, mWaiting));
+
+ mWaiting--; // another channel is complete
+ if (!mWaiting) { // there are all complete!
+ nsAutoCString mAlternateCT, mOriginCT;
+ mTransactionOrigin->mChannel->GetContentType(mOriginCT);
+ mTransactionAlternate->mChannel->GetContentType(mAlternateCT);
+ nsCOMPtr<nsIWellKnownOpportunisticUtils> uu =
+ do_CreateInstance(NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID);
+ bool accepted = false;
+
+ if (!mTransactionOrigin->mStatusOK) {
+ LOG(("WellKnownChecker::Done %p origin was not 200 response code\n",
+ this));
+ } else if (!mTransactionAlternate->mAuthOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not TLS authenticated\n",
+ this));
+ } else if (!mTransactionAlternate->mStatusOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not 200 response code\n",
+ this));
+ } else if (!mTransactionAlternate->mVersionOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not at least h2 or h3\n",
+ this));
+ } else if (!mTransactionAlternate->mWKResponse.Equals(
+ mTransactionOrigin->mWKResponse)) {
+ LOG(
+ ("WellKnownChecker::Done %p alternate and origin "
+ ".wk representations don't match\norigin: %s\alternate:%s\n",
+ this, mTransactionOrigin->mWKResponse.get(),
+ mTransactionAlternate->mWKResponse.get()));
+ } else if (!mAlternateCT.Equals(mOriginCT)) {
+ LOG(
+ ("WellKnownChecker::Done %p alternate and origin content types "
+ "dont match\n",
+ this));
+ } else if (!mAlternateCT.EqualsLiteral("application/json")) {
+ LOG(("WellKnownChecker::Done %p .wk content type is %s\n", this,
+ mAlternateCT.get()));
+ } else if (!uu) {
+ LOG(("WellKnownChecker::Done %p json parser service unavailable\n",
+ this));
+ } else {
+ accepted = true;
+ }
+
+ if (accepted) {
+ MOZ_ASSERT(!mMapping->HTTPS()); // https:// does not use .wk
+
+ nsresult rv = uu->Verify(mTransactionAlternate->mWKResponse, mOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ bool validWK = false;
+ Unused << uu->GetValid(&validWK);
+ if (!validWK) {
+ LOG(("WellKnownChecker::Done %p json parser declares invalid\n%s\n",
+ this, mTransactionAlternate->mWKResponse.get()));
+ accepted = false;
+ }
+ } else {
+ LOG(("WellKnownChecker::Done %p .wk jason eval failed to run\n",
+ this));
+ accepted = false;
+ }
+ }
+
+ MOZ_ASSERT(!mMapping->Validated());
+ if (accepted) {
+ LOG(("WellKnownChecker::Done %p Alternate for %s ACCEPTED\n", this,
+ mOrigin.get()));
+ mMapping->SetValidated(true);
+ } else {
+ LOG(("WellKnownChecker::Done %p Alternate for %s FAILED\n", this,
+ mOrigin.get()));
+ // try again soon
+ mMapping->SetExpiresAt(NowInSeconds() + 2);
+ }
+
+ delete this;
+ }
+ }
+
+ ~WellKnownChecker() { LOG(("WellKnownChecker dtor %p\n", this)); }
+
+ private:
+ nsresult MakeChannel(nsHttpChannel* chan, TransactionObserver* obs,
+ nsHttpConnectionInfo* ci, nsIURI* uri, uint32_t caps,
+ nsILoadInfo* loadInfo) {
+ uint64_t channelId;
+ nsLoadFlags flags;
+
+ ExtContentPolicyType contentPolicyType =
+ loadInfo->GetExternalContentPolicyType();
+
+ if (NS_FAILED(gHttpHandler->NewChannelId(channelId)) ||
+ NS_FAILED(chan->Init(uri, caps, nullptr, 0, nullptr, channelId,
+ contentPolicyType, loadInfo)) ||
+ NS_FAILED(chan->SetAllowAltSvc(false)) ||
+ NS_FAILED(chan->SetRedirectMode(
+ nsIHttpChannelInternal::REDIRECT_MODE_ERROR)) ||
+ NS_FAILED(chan->GetLoadFlags(&flags))) {
+ return NS_ERROR_FAILURE;
+ }
+ flags |= HttpBaseChannel::LOAD_BYPASS_CACHE;
+ if (NS_FAILED(chan->SetLoadFlags(flags))) {
+ return NS_ERROR_FAILURE;
+ }
+ chan->SetTransactionObserver(obs);
+ chan->SetConnectionInfo(ci);
+ return chan->AsyncOpen(obs);
+ }
+
+ RefPtr<TransactionObserver> mTransactionAlternate;
+ RefPtr<TransactionObserver> mTransactionOrigin;
+ uint32_t mWaiting; // semaphore
+ nsCString mOrigin;
+ int32_t mAlternatePort;
+ RefPtr<AltSvcMapping> mMapping;
+ RefPtr<nsHttpConnectionInfo> mCI;
+ nsCOMPtr<nsIURI> mURI;
+ uint32_t mCaps;
+};
+
+NS_IMPL_ISUPPORTS(TransactionObserver, nsIStreamListener)
+
+TransactionObserver::TransactionObserver(nsHttpChannel* channel,
+ WellKnownChecker* checker)
+ : mChannel(channel),
+ mChecker(checker),
+ mRanOnce(false),
+ mStatusOK(false),
+ mAuthOK(false),
+ mVersionOK(false) {
+ LOG(("TransactionObserver ctor %p channel %p checker %p\n", this, channel,
+ checker));
+ mChannelRef = do_QueryInterface((nsIHttpChannel*)channel);
+}
+
+void TransactionObserver::Complete(bool versionOK, bool authOK,
+ nsresult reason) {
+ if (mRanOnce) {
+ return;
+ }
+ mRanOnce = true;
+
+ mVersionOK = versionOK;
+ mAuthOK = authOK;
+
+ LOG(
+ ("TransactionObserve::Complete %p authOK %d versionOK %d"
+ " reason %" PRIx32,
+ this, authOK, versionOK, static_cast<uint32_t>(reason)));
+}
+
+#define MAX_WK 32768
+
+NS_IMETHODIMP
+TransactionObserver::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // only consider the first 32KB.. because really.
+ mWKResponse.SetCapacity(MAX_WK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream, uint64_t aOffset,
+ uint32_t aCount) {
+ MOZ_ASSERT(NS_IsMainThread());
+ uint64_t oldLen = static_cast<uint64_t>(mWKResponse.Length());
+ uint64_t newLen = static_cast<uint64_t>(aCount) + oldLen;
+ if (newLen < MAX_WK) {
+ auto handleOrErr = mWKResponse.BulkWrite(newLen, oldLen, false);
+ if (handleOrErr.isErr()) {
+ return handleOrErr.unwrapErr();
+ }
+ auto handle = handleOrErr.unwrap();
+ uint32_t amtRead;
+ if (NS_SUCCEEDED(
+ aStream->Read(handle.Elements() + oldLen, aCount, &amtRead))) {
+ MOZ_ASSERT(oldLen + amtRead <= newLen);
+ handle.Finish(oldLen + amtRead, false);
+ LOG(("TransactionObserver onDataAvailable %p read %d of .wk [%zd]\n",
+ this, amtRead, mWKResponse.Length()));
+ } else {
+ LOG(("TransactionObserver onDataAvailable %p read error\n", this));
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnStopRequest(nsIRequest* aRequest, nsresult code) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("TransactionObserver onStopRequest %p code %" PRIx32 "\n", this,
+ static_cast<uint32_t>(code)));
+ if (NS_SUCCEEDED(code)) {
+ nsHttpResponseHead* hdrs = mChannel->GetResponseHead();
+ LOG(("TransactionObserver onStopRequest %p http resp %d\n", this,
+ hdrs ? hdrs->Status() : -1));
+ mStatusOK = hdrs && (hdrs->Status() == 200);
+ }
+ if (mChecker) {
+ mChecker->Done(this);
+ }
+ return NS_OK;
+}
+
+void AltSvcCache::EnsureStorageInited() {
+ static Atomic<bool> initialized(false);
+
+ if (initialized) {
+ return;
+ }
+
+ auto initTask = [&]() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // nsIDataStorage gives synchronous access to a memory based hash table
+ // that is backed by disk where those writes are done asynchronously
+ // on another thread
+ nsCOMPtr<nsIDataStorageManager> dataStorageManager(
+ do_GetService("@mozilla.org/security/datastoragemanager;1"));
+ if (!dataStorageManager) {
+ LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE MANAGER\n"));
+ return;
+ }
+ nsresult rv = dataStorageManager->Get(
+ nsIDataStorageManager::AlternateServices, getter_AddRefs(mStorage));
+ if (NS_FAILED(rv) || !mStorage) {
+ LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE\n"));
+ return;
+ }
+ initialized = true;
+
+ mStorageEpoch = NowInSeconds();
+ };
+
+ if (NS_IsMainThread()) {
+ initTask();
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget();
+ if (!main) {
+ return;
+ }
+
+ SyncRunnable::DispatchToThread(
+ main,
+ NS_NewRunnableFunction("AltSvcCache::EnsureStorageInited", initTask));
+}
+
+already_AddRefed<AltSvcMapping> AltSvcCache::LookupMapping(
+ const nsCString& key, bool privateBrowsing) {
+ LOG(("AltSvcCache::LookupMapping %p %s\n", this, key.get()));
+ if (!mStorage) {
+ LOG(("AltSvcCache::LookupMapping %p no backing store\n", this));
+ return nullptr;
+ }
+
+ if (NS_IsMainThread()) {
+ bool isReady;
+ nsresult rv = mStorage->IsReady(&isReady);
+ if (NS_FAILED(rv)) {
+ LOG(("AltSvcCache::LookupMapping %p mStorage->IsReady failed\n", this));
+ return nullptr;
+ }
+ if (!isReady) {
+ LOG(("AltSvcCache::LookupMapping %p skip when storage is not ready\n",
+ this));
+ return nullptr;
+ }
+ }
+
+ nsAutoCString val;
+ nsresult rv =
+ mStorage->Get(key,
+ privateBrowsing ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent,
+ val);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
+ LOG(("AltSvcCache::LookupMapping %p mStorage->Get failed \n", this));
+ return nullptr;
+ }
+ if (rv == NS_ERROR_NOT_AVAILABLE || val.IsEmpty()) {
+ LOG(("AltSvcCache::LookupMapping %p MISS\n", this));
+ return nullptr;
+ }
+ RefPtr<AltSvcMapping> mapping =
+ new AltSvcMapping(mStorage, mStorageEpoch, val);
+ if (!mapping->Validated() && (mapping->StorageEpoch() != mStorageEpoch)) {
+ // this was an in progress validation abandoned in a different session
+ // rare edge case will not detect session change - that's ok as only impact
+ // will be loss of alt-svc to this origin for this session.
+ LOG(("AltSvcCache::LookupMapping %p invalid hit - MISS\n", this));
+ (void)mStorage->Remove(key, mapping->Private()
+ ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+ return nullptr;
+ }
+
+ if (mapping->IsHttp3() &&
+ (!nsHttpHandler::IsHttp3Enabled() ||
+ !gHttpHandler->IsHttp3VersionSupported(mapping->NPNToken()) ||
+ gHttpHandler->IsHttp3Excluded(mapping->AlternateHost()))) {
+ // If Http3 is disabled or the version not supported anymore, remove the
+ // mapping.
+ (void)mStorage->Remove(key, mapping->Private()
+ ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+ return nullptr;
+ }
+
+ if (mapping->TTL() <= 0) {
+ LOG(("AltSvcCache::LookupMapping %p expired hit - MISS\n", this));
+ (void)mStorage->Remove(key, mapping->Private()
+ ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mapping->Private() == privateBrowsing);
+ LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, mapping.get()));
+ return mapping.forget();
+}
+
+// This is only used for testing!
+void AltSvcCache::UpdateAltServiceMappingWithoutValidation(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
+ uint32_t caps, const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mStorage) {
+ return;
+ }
+ RefPtr<AltSvcMapping> existing =
+ LookupMapping(map->HashKey(), map->Private());
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMappingWithoutValidation %p map %p "
+ "existing %p %s",
+ this, map, existing.get(), map->AlternateHost().get()));
+ if (!existing) {
+ map->SetValidated(true);
+ }
+}
+
+void AltSvcCache::UpdateAltServiceMapping(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
+ uint32_t caps, const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mStorage) {
+ return;
+ }
+ RefPtr<AltSvcMapping> existing =
+ LookupMapping(map->HashKey(), map->Private());
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p existing %p %s "
+ "validated=%d",
+ this, map, existing.get(), map->AlternateHost().get(),
+ existing ? existing->Validated() : 0));
+
+ if (existing && existing->Validated()) {
+ if (existing->RouteEquals(map)) {
+ // update expires in storage
+ // if this is http:// then a ttl can only be extended via .wk, so ignore
+ // this header path unless it is making things shorter
+ if (existing->HTTPS()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p updates ttl of "
+ "%p\n",
+ this, map, existing.get()));
+ existing->SetExpiresAt(map->GetExpiresAt());
+ } else {
+ if (map->GetExpiresAt() < existing->GetExpiresAt()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p reduces ttl of "
+ "%p\n",
+ this, map, existing.get()));
+ existing->SetExpiresAt(map->GetExpiresAt());
+ } else {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p tries to extend "
+ "%p but"
+ " cannot as without .wk\n",
+ this, map, existing.get()));
+ }
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_MAPPING_CHANGED_TARGET,
+ false);
+ return;
+ }
+
+ if (map->GetExpiresAt() < existing->GetExpiresAt()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p ttl shorter than "
+ "%p, ignoring",
+ this, map, existing.get()));
+ return;
+ }
+
+ // new alternate. start new validation
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p may overwrite %p\n",
+ this, map, existing.get()));
+ Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_MAPPING_CHANGED_TARGET, true);
+ }
+
+ if (existing && !existing->Validated()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p ignored because %p "
+ "still in progress\n",
+ this, map, existing.get()));
+ return;
+ }
+
+ if (map->IsHttp3()) {
+ bool isDirectOrNoProxy = pi ? pi->IsDirect() : true;
+ if (!isDirectOrNoProxy) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p ignored h3 because "
+ "proxy is in use %p\n",
+ this, map, existing.get()));
+ return;
+ }
+ }
+
+ // start new validation, but don't overwrite a valid existing mapping unless
+ // this completes successfully
+ MOZ_ASSERT(!map->Validated());
+ if (!existing) {
+ map->Sync();
+ } else {
+ map->SetSyncOnlyOnSuccess(true);
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci;
+ map->GetConnectionInfo(getter_AddRefs(ci), pi, originAttributes);
+ caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+ caps |= NS_HTTP_ERROR_SOFTLY;
+
+ if (map->HTTPS()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p validation via "
+ "speculative connect started\n",
+ this));
+ // for https resources we only establish a connection
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = new AltSvcOverride(aCallbacks);
+ RefPtr<AltSvcMappingValidator> validator = new AltSvcMappingValidator(map);
+ RefPtr<SpeculativeTransaction> transaction;
+ if (nsIOService::UseSocketProcess()) {
+ RefPtr<AltSvcTransactionParent> parent =
+ new AltSvcTransactionParent(ci, aCallbacks, caps, validator);
+ if (!parent->Init()) {
+ return;
+ }
+ transaction = parent;
+ } else {
+ transaction = new AltSvcTransaction<AltSvcMappingValidator>(
+ ci, aCallbacks, caps, validator, map->IsHttp3());
+ }
+
+ nsresult rv =
+ gHttpHandler->SpeculativeConnect(ci, callbacks, caps, transaction);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p "
+ "speculative connect failed with code %08x\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ } else {
+ // for http:// resources we fetch .well-known too
+ nsAutoCString origin("http://"_ns);
+
+ // Check whether origin is an ipv6 address. In that case we need to add
+ // '[]'.
+ if (map->OriginHost().FindChar(':') != kNotFound) {
+ origin.Append('[');
+ origin.Append(map->OriginHost());
+ origin.Append(']');
+ } else {
+ origin.Append(map->OriginHost());
+ }
+ if (map->OriginPort() != NS_HTTP_DEFAULT_PORT) {
+ origin.Append(':');
+ origin.AppendInt(map->OriginPort());
+ }
+
+ nsCOMPtr<nsIURI> wellKnown;
+ nsAutoCString uri(origin);
+ uri.AppendLiteral("/.well-known/http-opportunistic");
+ NS_NewURI(getter_AddRefs(wellKnown), uri);
+
+ auto* checker = new WellKnownChecker(wellKnown, origin, caps, ci, map);
+ if (NS_FAILED(checker->Start())) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p .wk checker failed to "
+ "start\n",
+ this));
+ map->SetExpired();
+ delete checker;
+ checker = nullptr;
+ } else {
+ // object deletes itself when done if started
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker started %p\n",
+ this, checker));
+ }
+ }
+}
+
+already_AddRefed<AltSvcMapping> AltSvcCache::GetAltServiceMapping(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ bool privateBrowsing, const OriginAttributes& originAttributes,
+ bool aHttp2Allowed, bool aHttp3Allowed) {
+ EnsureStorageInited();
+
+ bool isHTTPS;
+ if (NS_FAILED(SchemeIsHTTPS(scheme, isHTTPS))) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvc()) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvcOE() && !isHTTPS) {
+ return nullptr;
+ }
+
+ // First look for HTTP3
+ if (aHttp3Allowed) {
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing,
+ originAttributes, true);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
+ LOG(
+ ("AltSvcCache::GetAltServiceMapping %p key=%s "
+ "existing=%p validated=%d ttl=%d",
+ this, key.get(), existing.get(), existing ? existing->Validated() : 0,
+ existing ? existing->TTL() : 0));
+ if (existing && existing->Validated()) {
+ return existing.forget();
+ }
+ }
+
+ // Now look for HTTP2.
+ if (aHttp2Allowed) {
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing,
+ originAttributes, false);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
+ LOG(
+ ("AltSvcCache::GetAltServiceMapping %p key=%s "
+ "existing=%p validated=%d ttl=%d",
+ this, key.get(), existing.get(), existing ? existing->Validated() : 0,
+ existing ? existing->TTL() : 0));
+ if (existing && existing->Validated()) {
+ return existing.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+class ProxyClearHostMapping : public Runnable {
+ public:
+ explicit ProxyClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes)
+ : Runnable("net::ProxyClearHostMapping"),
+ mHost(host),
+ mPort(port),
+ mOriginAttributes(originAttributes) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ gHttpHandler->AltServiceCache()->ClearHostMapping(mHost, mPort,
+ mOriginAttributes);
+ return NS_OK;
+ }
+
+ private:
+ nsCString mHost;
+ int32_t mPort;
+ OriginAttributes mOriginAttributes;
+};
+
+void AltSvcCache::ClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event =
+ new ProxyClearHostMapping(host, port, originAttributes);
+ if (event) {
+ NS_DispatchToMainThread(event);
+ }
+ return;
+ }
+ nsAutoCString key;
+ for (int secure = 0; secure < 2; ++secure) {
+ constexpr auto http = "http"_ns;
+ constexpr auto https = "https"_ns;
+ const nsLiteralCString& scheme = secure ? https : http;
+ for (int pb = 1; pb >= 0; --pb) {
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
+ originAttributes, false);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, bool(pb));
+ if (existing) {
+ existing->SetExpired();
+ }
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
+ originAttributes, true);
+ existing = LookupMapping(key, bool(pb));
+ if (existing) {
+ existing->SetExpired();
+ }
+ }
+ }
+}
+
+void AltSvcCache::ClearHostMapping(nsHttpConnectionInfo* ci) {
+ if (!ci->GetOrigin().IsEmpty()) {
+ ClearHostMapping(ci->GetOrigin(), ci->OriginPort(),
+ ci->GetOriginAttributes());
+ }
+}
+
+void AltSvcCache::ClearAltServiceMappings() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mStorage) {
+ (void)mStorage->Clear();
+ }
+}
+
+nsresult AltSvcCache::GetAltSvcCacheKeys(nsTArray<nsCString>& value) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (gHttpHandler->AllowAltSvc() && mStorage) {
+ nsTArray<RefPtr<nsIDataStorageItem>> items;
+ nsresult rv = mStorage->GetAll(items);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ for (const auto& item : items) {
+ nsAutoCString key;
+ rv = item->GetKey(key);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ value.AppendElement(key);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetInterface(const nsIID& iid, void** result) {
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+
+ if (mCallbacks) {
+ return mCallbacks->GetInterface(iid, result);
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIgnoreIdle(bool* ignoreIdle) {
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetParallelSpeculativeConnectLimit(
+ uint32_t* parallelSpeculativeConnectLimit) {
+ *parallelSpeculativeConnectLimit = 32;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIsFromPredictor(bool* isFromPredictor) {
+ *isFromPredictor = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetAllow1918(bool* allow) {
+ // normally we don't do speculative connects to 1918.. and we use
+ // speculative connects for the mapping validation, so override
+ // that default here for alt-svc
+ *allow = true;
+ return NS_OK;
+}
+
+template class AltSvcTransaction<AltSvcTransactionChild>;
+
+NS_IMPL_ISUPPORTS(AltSvcOverride, nsIInterfaceRequestor,
+ nsISpeculativeConnectionOverrider)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AlternateServices.h b/netwerk/protocol/http/AlternateServices.h
new file mode 100644
index 0000000000..4de5a281c0
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+Alt-Svc allows separation of transport routing from the origin host without
+using a proxy. See https://httpwg.github.io/http-extensions/alt-svc.html and
+https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
+
+ Nice To Have Future Enhancements::
+ * flush on network change event when we have an indicator
+ * use established https channel for http instead separate of conninfo hash
+ * pin via http-tls header
+ * clear based on origin when a random fail happens not just 421
+ * upon establishment of channel, cancel and retry trans that have not yet
+ written anything
+ * persistent storage (including private browsing filter)
+ * memory reporter for cache, but this is rather tiny
+*/
+
+#ifndef mozilla_net_AlternateServices_h
+#define mozilla_net_AlternateServices_h
+
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsIDataStorage.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsISpeculativeConnect.h"
+#include "mozilla/BasePrincipal.h"
+#include "SpeculativeTransaction.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsProxyInfo;
+class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class nsHttpChannel;
+class WellKnownChecker;
+
+class AltSvcMapping {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMapping)
+
+ private: // ctor from ProcessHeader
+ AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch,
+ const nsACString& originScheme, const nsACString& originHost,
+ int32_t originPort, const nsACString& username,
+ bool privateBrowsing, uint32_t expiresAt,
+ const nsACString& alternateHost, int32_t alternatePort,
+ const nsACString& npnToken,
+ const OriginAttributes& originAttributes, bool aIsHttp3);
+
+ public:
+ AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch,
+ const nsCString& str);
+
+ static void ProcessHeader(
+ const nsCString& buf, const nsCString& originScheme,
+ const nsCString& originHost, int32_t originPort,
+ const nsACString& username, bool privateBrowsing,
+ nsIInterfaceRequestor* callbacks, nsProxyInfo* proxyInfo, uint32_t caps,
+ const OriginAttributes& originAttributes,
+ bool aDontValidate = false); // aDontValidate is only used for testing!
+
+ // AcceptableProxy() decides whether a particular proxy configuration (pi) is
+ // suitable for use with Alt-Svc. No proxy (including a null pi) is suitable.
+ static bool AcceptableProxy(nsProxyInfo* proxyInfo);
+
+ const nsCString& AlternateHost() const { return mAlternateHost; }
+ const nsCString& OriginHost() const { return mOriginHost; }
+ uint32_t OriginPort() const { return mOriginPort; }
+ const nsCString& HashKey() const { return mHashKey; }
+ uint32_t AlternatePort() const { return mAlternatePort; }
+ bool Validated() { return mValidated; }
+ int32_t GetExpiresAt() { return mExpiresAt; }
+ bool RouteEquals(AltSvcMapping* map);
+ bool HTTPS() { return mHttps; }
+
+ void GetConnectionInfo(nsHttpConnectionInfo** outCI, nsProxyInfo* pi,
+ const OriginAttributes& originAttributes);
+
+ int32_t TTL();
+ int32_t StorageEpoch() { return mStorageEpoch; }
+ bool Private() { return mPrivate; }
+
+ void SetValidated(bool val);
+ void SetMixedScheme(bool val);
+ void SetExpiresAt(int32_t val);
+ void SetExpired();
+ void Sync();
+ void SetSyncOnlyOnSuccess(bool aSOOS) { mSyncOnlyOnSuccess = aSOOS; }
+
+ static void MakeHashKey(nsCString& outKey, const nsACString& originScheme,
+ const nsACString& originHost, int32_t originPort,
+ bool privateBrowsing,
+ const OriginAttributes& originAttributes,
+ bool aHttp3);
+
+ bool IsHttp3() { return mIsHttp3; }
+ const nsCString& NPNToken() const { return mNPNToken; }
+
+ private:
+ virtual ~AltSvcMapping() = default;
+ void SyncString(const nsCString& str);
+ nsCOMPtr<nsIDataStorage> mStorage;
+ int32_t mStorageEpoch;
+ void Serialize(nsCString& out);
+
+ nsCString mHashKey;
+
+ // If you change any of these members, update Serialize()
+ nsCString mAlternateHost;
+ int32_t mAlternatePort{-1};
+
+ nsCString mOriginHost;
+ int32_t mOriginPort{-1};
+
+ nsCString mUsername;
+ bool mPrivate{false};
+
+ // alt-svc mappping
+ uint32_t mExpiresAt{0};
+
+ bool mValidated{false};
+ // origin is https://
+ MOZ_INIT_OUTSIDE_CTOR bool mHttps{false};
+ // .wk allows http and https on same con
+ MOZ_INIT_OUTSIDE_CTOR bool mMixedScheme{false};
+
+ nsCString mNPNToken;
+
+ OriginAttributes mOriginAttributes;
+
+ bool mSyncOnlyOnSuccess{false};
+ bool mIsHttp3{false};
+};
+
+class AltSvcOverride : public nsIInterfaceRequestor,
+ public nsISpeculativeConnectionOverrider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ explicit AltSvcOverride(nsIInterfaceRequestor* aRequestor)
+ : mCallbacks(aRequestor) {}
+
+ private:
+ virtual ~AltSvcOverride() = default;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+class TransactionObserver final : public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ TransactionObserver(nsHttpChannel* channel, WellKnownChecker* checker);
+ void Complete(bool versionOK, bool authOK, nsresult reason);
+
+ private:
+ friend class WellKnownChecker;
+ virtual ~TransactionObserver() = default;
+
+ nsCOMPtr<nsISupports> mChannelRef;
+ nsHttpChannel* mChannel;
+ WellKnownChecker* mChecker;
+ nsCString mWKResponse;
+
+ bool mRanOnce;
+ bool mStatusOK; // HTTP Status 200
+ // These two values could be accessed on sts thread.
+ Atomic<bool> mAuthOK; // confirmed no TLS failure
+ Atomic<bool> mVersionOK; // connection h2
+};
+
+class AltSvcCache {
+ public:
+ AltSvcCache() = default;
+ virtual ~AltSvcCache() = default;
+ void UpdateAltServiceMapping(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor*,
+ uint32_t caps,
+ const OriginAttributes& originAttributes); // main thread
+ void UpdateAltServiceMappingWithoutValidation(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor*,
+ uint32_t caps,
+ const OriginAttributes& originAttributes); // main thread
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ bool privateBrowsing, const OriginAttributes& originAttributes,
+ bool aHttp2Allowed, bool aHttp3Allowed);
+ void ClearAltServiceMappings();
+ void ClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes);
+ void ClearHostMapping(nsHttpConnectionInfo* ci);
+ nsIDataStorage* GetStoragePtr() { return mStorage.get(); }
+ int32_t StorageEpoch() { return mStorageEpoch; }
+ nsresult GetAltSvcCacheKeys(nsTArray<nsCString>& value);
+
+ private:
+ void EnsureStorageInited();
+ already_AddRefed<AltSvcMapping> LookupMapping(const nsCString& key,
+ bool privateBrowsing);
+ nsCOMPtr<nsIDataStorage> mStorage;
+ int32_t mStorageEpoch{0};
+};
+
+// This class is used to write the validated result to AltSvcMapping when the
+// AltSvcTransaction is closed and destroyed.
+class AltSvcMappingValidator final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMappingValidator)
+
+ explicit AltSvcMappingValidator(AltSvcMapping* aMap);
+
+ void OnTransactionDestroy(bool aValidateResult);
+
+ void OnTransactionClose(bool aValidateResult);
+
+ protected:
+ virtual ~AltSvcMappingValidator() = default;
+
+ RefPtr<AltSvcMapping> mMapping;
+};
+
+// This is the asynchronous null transaction used to validate
+// an alt-svc advertisement only for https://
+// When http over socket process is enabled, this class should live only in
+// socket process.
+template <class Validator>
+class AltSvcTransaction final : public SpeculativeTransaction {
+ public:
+ AltSvcTransaction(nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks,
+ uint32_t caps, Validator* aValidator, bool aIsHttp3);
+
+ ~AltSvcTransaction() override;
+
+ // AltSvcTransaction is used to validate the alt-svc record, so we don't want
+ // to fetch HTTPS RR for this.
+ virtual nsresult FetchHTTPSRR() override { return NS_ERROR_NOT_IMPLEMENTED; }
+
+ private:
+ // check on alternate route.
+ // also evaluate 'reasonable assurances' for opportunistic security
+ bool MaybeValidate(nsresult reason);
+
+ public:
+ void Close(nsresult reason) override;
+ nsresult ReadSegments(nsAHttpSegmentReader* reader, uint32_t count,
+ uint32_t* countRead) override;
+
+ private:
+ RefPtr<Validator> mValidator;
+ uint32_t mIsHttp3 : 1;
+ uint32_t mRunning : 1;
+ uint32_t mTriedToValidate : 1;
+ uint32_t mTriedToWrite : 1;
+ uint32_t mValidatedResult : 1;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // include guard
diff --git a/netwerk/protocol/http/BackgroundChannelRegistrar.cpp b/netwerk/protocol/http/BackgroundChannelRegistrar.cpp
new file mode 100644
index 0000000000..13f1ca88f0
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundChannelRegistrar.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "BackgroundChannelRegistrar.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "HttpBackgroundChannelParent.h"
+#include "HttpChannelParent.h"
+#include "nsXULAppAPI.h"
+
+namespace {
+mozilla::StaticRefPtr<mozilla::net::BackgroundChannelRegistrar> gSingleton;
+}
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(BackgroundChannelRegistrar, nsIBackgroundChannelRegistrar)
+
+BackgroundChannelRegistrar::BackgroundChannelRegistrar() {
+ // BackgroundChannelRegistrar is a main-thread-only object.
+ // All the operations should be run on main thread.
+ // It should be used on chrome process only.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+BackgroundChannelRegistrar::~BackgroundChannelRegistrar() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+// static
+already_AddRefed<nsIBackgroundChannelRegistrar>
+BackgroundChannelRegistrar::GetOrCreate() {
+ if (!gSingleton) {
+ gSingleton = new BackgroundChannelRegistrar();
+ ClearOnShutdown(&gSingleton);
+ }
+ return do_AddRef(gSingleton);
+}
+
+void BackgroundChannelRegistrar::NotifyChannelLinked(
+ HttpChannelParent* aChannelParent, HttpBackgroundChannelParent* aBgParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aChannelParent);
+ MOZ_ASSERT(aBgParent);
+
+ aBgParent->LinkToChannel(aChannelParent);
+ aChannelParent->OnBackgroundParentReady(aBgParent);
+}
+
+// nsIBackgroundChannelRegistrar
+void BackgroundChannelRegistrar::DeleteChannel(uint64_t aKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mChannels.Remove(aKey);
+ mBgChannels.Remove(aKey);
+}
+
+void BackgroundChannelRegistrar::LinkHttpChannel(uint64_t aKey,
+ HttpChannelParent* aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aChannel);
+
+ RefPtr<HttpBackgroundChannelParent> bgParent;
+ bool found = mBgChannels.Remove(aKey, getter_AddRefs(bgParent));
+
+ if (!found) {
+ mChannels.InsertOrUpdate(aKey, RefPtr{aChannel});
+ return;
+ }
+
+ MOZ_ASSERT(bgParent);
+ NotifyChannelLinked(aChannel, bgParent);
+}
+
+void BackgroundChannelRegistrar::LinkBackgroundChannel(
+ uint64_t aKey, HttpBackgroundChannelParent* aBgChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBgChannel);
+
+ RefPtr<HttpChannelParent> parent;
+ bool found = mChannels.Remove(aKey, getter_AddRefs(parent));
+
+ if (!found) {
+ mBgChannels.InsertOrUpdate(aKey, RefPtr{aBgChannel});
+ return;
+ }
+
+ MOZ_ASSERT(parent);
+ NotifyChannelLinked(parent, aBgChannel);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/BackgroundChannelRegistrar.h b/netwerk/protocol/http/BackgroundChannelRegistrar.h
new file mode 100644
index 0000000000..8f926103a2
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundChannelRegistrar.h
@@ -0,0 +1,55 @@
+/* -*- 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_BackgroundChannelRegistrar_h__
+#define mozilla_net_BackgroundChannelRegistrar_h__
+
+#include "nsIBackgroundChannelRegistrar.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/AlreadyAddRefed.h"
+
+namespace mozilla {
+namespace net {
+
+class HttpBackgroundChannelParent;
+class HttpChannelParent;
+
+class BackgroundChannelRegistrar final : public nsIBackgroundChannelRegistrar {
+ using ChannelHashtable =
+ nsRefPtrHashtable<nsUint64HashKey, HttpChannelParent>;
+ using BackgroundChannelHashtable =
+ nsRefPtrHashtable<nsUint64HashKey, HttpBackgroundChannelParent>;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBACKGROUNDCHANNELREGISTRAR
+
+ explicit BackgroundChannelRegistrar();
+
+ // Singleton accessor
+ static already_AddRefed<nsIBackgroundChannelRegistrar> GetOrCreate();
+
+ private:
+ virtual ~BackgroundChannelRegistrar();
+
+ // A helper function for BackgroundChannelRegistrar itself to callback
+ // HttpChannelParent and HttpBackgroundChannelParent when both objects are
+ // ready. aChannelParent and aBgParent is the pair of HttpChannelParent and
+ // HttpBackgroundChannelParent that should be linked together.
+ void NotifyChannelLinked(HttpChannelParent* aChannelParent,
+ HttpBackgroundChannelParent* aBgParent);
+
+ // Store unlinked HttpChannelParent objects.
+ ChannelHashtable mChannels;
+
+ // Store unlinked HttpBackgroundChannelParent objects.
+ BackgroundChannelHashtable mBgChannels;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BackgroundChannelRegistrar_h__
diff --git a/netwerk/protocol/http/BackgroundDataBridgeChild.cpp b/netwerk/protocol/http/BackgroundDataBridgeChild.cpp
new file mode 100644
index 0000000000..481bec5985
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeChild.cpp
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/BackgroundDataBridgeChild.h"
+#include "mozilla/net/HttpBackgroundChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+BackgroundDataBridgeChild::BackgroundDataBridgeChild(
+ HttpBackgroundChannelChild* aBgChild)
+ : mBgChild(aBgChild) {
+ MOZ_ASSERT(aBgChild);
+}
+
+BackgroundDataBridgeChild::~BackgroundDataBridgeChild() = default;
+
+void BackgroundDataBridgeChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mBgChild = nullptr;
+}
+
+mozilla::ipc::IPCResult BackgroundDataBridgeChild::RecvOnTransportAndData(
+ const uint64_t& offset, const uint32_t& count, const nsACString& data,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ if (!mBgChild) {
+ return IPC_OK();
+ }
+
+ if (mBgChild->ChannelClosed()) {
+ Close();
+ return IPC_OK();
+ }
+
+ return mBgChild->RecvOnTransportAndData(NS_OK, NS_NET_STATUS_RECEIVING_FROM,
+ offset, count, data, true,
+ aOnDataAvailableStartTime);
+}
+
+mozilla::ipc::IPCResult BackgroundDataBridgeChild::RecvOnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStartTime) {
+ if (!mBgChild) {
+ return IPC_OK();
+ }
+
+ if (mBgChild->ChannelClosed()) {
+ Close();
+ return IPC_OK();
+ }
+
+ return mBgChild->RecvOnStopRequest(
+ aStatus, aTiming, aLastActiveTabOptHit, aResponseTrailers,
+ nsTArray<ConsoleReportCollected>(), true, aOnStopRequestStartTime);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/BackgroundDataBridgeChild.h b/netwerk/protocol/http/BackgroundDataBridgeChild.h
new file mode 100644
index 0000000000..13c890e005
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeChild.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_BackgroundDataBridgeChild_h
+#define mozilla_net_BackgroundDataBridgeChild_h
+
+#include "mozilla/net/PBackgroundDataBridgeChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+
+namespace mozilla {
+namespace net {
+
+class HttpBackgroundChannelChild;
+
+class BackgroundDataBridgeChild final : public PBackgroundDataBridgeChild {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundDataBridgeChild, override)
+
+ explicit BackgroundDataBridgeChild(HttpBackgroundChannelChild* aBgChild);
+
+ protected:
+ virtual ~BackgroundDataBridgeChild();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<HttpBackgroundChannelChild> mBgChild;
+
+ public:
+ mozilla::ipc::IPCResult RecvOnTransportAndData(
+ const uint64_t& offset, const uint32_t& count, const nsACString& data,
+ const TimeStamp& aOnDataAvailableStartTime);
+ mozilla::ipc::IPCResult RecvOnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStartTime);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BackgroundDataBridgeChild_h
diff --git a/netwerk/protocol/http/BackgroundDataBridgeParent.cpp b/netwerk/protocol/http/BackgroundDataBridgeParent.cpp
new file mode 100644
index 0000000000..843c64023e
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeParent.cpp
@@ -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/. */
+
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+BackgroundDataBridgeParent::BackgroundDataBridgeParent(uint64_t aChannelID)
+ : mChannelID(aChannelID), mBackgroundThread(GetCurrentSerialEventTarget()) {
+ if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) {
+ child->AddDataBridgeToMap(aChannelID, this);
+ }
+}
+
+void BackgroundDataBridgeParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) {
+ child->RemoveDataBridgeFromMap(mChannelID);
+ }
+}
+
+already_AddRefed<nsISerialEventTarget>
+BackgroundDataBridgeParent::GetBackgroundThread() {
+ return do_AddRef(mBackgroundThread);
+}
+
+void BackgroundDataBridgeParent::Destroy() {
+ RefPtr<BackgroundDataBridgeParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction("BackgroundDataBridgeParent::Destroy",
+ [self]() {
+ if (self->CanSend()) {
+ self->Close();
+ }
+ }),
+ NS_DISPATCH_NORMAL));
+}
+
+void BackgroundDataBridgeParent::OnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStart) {
+ RefPtr<BackgroundDataBridgeParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction("BackgroundDataBridgeParent::OnStopRequest",
+ [self, aStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers, aOnStopRequestStart]() {
+ if (self->CanSend()) {
+ Unused << self->SendOnStopRequest(
+ aStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers, aOnStopRequestStart);
+ self->Close();
+ }
+ }),
+ NS_DISPATCH_NORMAL));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/BackgroundDataBridgeParent.h b/netwerk/protocol/http/BackgroundDataBridgeParent.h
new file mode 100644
index 0000000000..9bda9f3aa9
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeParent.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_BackgroundDataBridgeParent_h
+#define mozilla_net_BackgroundDataBridgeParent_h
+
+#include "mozilla/net/PBackgroundDataBridgeParent.h"
+
+namespace mozilla {
+namespace net {
+
+class BackgroundDataBridgeParent final : public PBackgroundDataBridgeParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundDataBridgeParent, override)
+
+ explicit BackgroundDataBridgeParent(uint64_t aChannelID);
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ already_AddRefed<nsISerialEventTarget> GetBackgroundThread();
+ void Destroy();
+ void OnStopRequest(nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStart);
+
+ private:
+ virtual ~BackgroundDataBridgeParent() = default;
+
+ uint64_t mChannelID;
+ nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BackgroundDataBridgeParent_h
diff --git a/netwerk/protocol/http/BinaryHttpRequest.cpp b/netwerk/protocol/http/BinaryHttpRequest.cpp
new file mode 100644
index 0000000000..fa28f4c5c9
--- /dev/null
+++ b/netwerk/protocol/http/BinaryHttpRequest.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "BinaryHttpRequest.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(BinaryHttpRequest, nsIBinaryHttpRequest)
+
+NS_IMETHODIMP BinaryHttpRequest::GetMethod(nsACString& aMethod) {
+ aMethod.Assign(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetScheme(nsACString& aScheme) {
+ aScheme.Assign(mScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetAuthority(nsACString& aAuthority) {
+ aAuthority.Assign(mAuthority);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetPath(nsACString& aPath) {
+ aPath.Assign(mPath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetHeaderNames(
+ nsTArray<nsCString>& aHeaderNames) {
+ aHeaderNames.Assign(mHeaderNames);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetHeaderValues(
+ nsTArray<nsCString>& aHeaderValues) {
+ aHeaderValues.Assign(mHeaderValues);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetContent(nsTArray<uint8_t>& aContent) {
+ aContent.Assign(mContent);
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/BinaryHttpRequest.h b/netwerk/protocol/http/BinaryHttpRequest.h
new file mode 100644
index 0000000000..f8803f511c
--- /dev/null
+++ b/netwerk/protocol/http/BinaryHttpRequest.h
@@ -0,0 +1,48 @@
+/* -*- 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_BinaryHttpRequest_h
+#define mozilla_net_BinaryHttpRequest_h
+
+#include "nsIBinaryHttp.h"
+#include "nsString.h"
+
+namespace mozilla::net {
+
+class BinaryHttpRequest final : public nsIBinaryHttpRequest {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIBINARYHTTPREQUEST
+
+ BinaryHttpRequest(const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aAuthority, const nsACString& aPath,
+ nsTArray<nsCString>&& aHeaderNames,
+ nsTArray<nsCString>&& aHeaderValues,
+ nsTArray<uint8_t>&& aContent)
+ : mMethod(aMethod),
+ mScheme(aScheme),
+ mAuthority(aAuthority),
+ mPath(aPath),
+ mHeaderNames(std::move(aHeaderNames)),
+ mHeaderValues(std::move(aHeaderValues)),
+ mContent(std::move(aContent)) {}
+
+ private:
+ ~BinaryHttpRequest() = default;
+
+ const nsAutoCString mMethod;
+ const nsAutoCString mScheme;
+ const nsAutoCString mAuthority;
+ const nsAutoCString mPath;
+ const nsTArray<nsCString> mHeaderNames;
+ const nsTArray<nsCString> mHeaderValues;
+ const nsTArray<uint8_t> mContent;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_BinaryHttpRequest_h
diff --git a/netwerk/protocol/http/CacheControlParser.cpp b/netwerk/protocol/http/CacheControlParser.cpp
new file mode 100644
index 0000000000..79cdef5439
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "CacheControlParser.h"
+
+namespace mozilla {
+namespace net {
+
+CacheControlParser::CacheControlParser(nsACString const& aHeader)
+ : Tokenizer(aHeader, nullptr, "-_"),
+ mMaxAgeSet(false),
+ mMaxAge(0),
+ mMaxStaleSet(false),
+ mMaxStale(0),
+ mMinFreshSet(false),
+ mMinFresh(0),
+ mStaleWhileRevalidateSet(false),
+ mStaleWhileRevalidate(0),
+ mNoCache(false),
+ mNoStore(false),
+ mPublic(false),
+ mPrivate(false),
+ mImmutable(false) {
+ SkipWhites();
+ if (!CheckEOF()) {
+ Directive();
+ }
+}
+
+void CacheControlParser::Directive() {
+ do {
+ SkipWhites();
+ if (CheckWord("no-cache")) {
+ mNoCache = true;
+ IgnoreDirective(); // ignore any optionally added values
+ } else if (CheckWord("no-store")) {
+ mNoStore = true;
+ } else if (CheckWord("max-age")) {
+ mMaxAgeSet = SecondsValue(&mMaxAge);
+ } else if (CheckWord("max-stale")) {
+ mMaxStaleSet = SecondsValue(&mMaxStale, PR_UINT32_MAX);
+ } else if (CheckWord("min-fresh")) {
+ mMinFreshSet = SecondsValue(&mMinFresh);
+ } else if (CheckWord("stale-while-revalidate")) {
+ mStaleWhileRevalidateSet = SecondsValue(&mStaleWhileRevalidate);
+ } else if (CheckWord("public")) {
+ mPublic = true;
+ } else if (CheckWord("private")) {
+ mPrivate = true;
+ } else if (CheckWord("immutable")) {
+ mImmutable = true;
+ } else {
+ IgnoreDirective();
+ }
+
+ SkipWhites();
+ if (CheckEOF()) {
+ return;
+ }
+
+ } while (CheckChar(','));
+
+ NS_WARNING("Unexpected input in Cache-control header value");
+}
+
+bool CacheControlParser::SecondsValue(uint32_t* seconds, uint32_t defaultVal) {
+ SkipWhites();
+ if (!CheckChar('=')) {
+ *seconds = defaultVal;
+ return !!defaultVal;
+ }
+
+ SkipWhites();
+ if (!ReadInteger(seconds)) {
+ NS_WARNING("Unexpected value in Cache-control header value");
+ return false;
+ }
+
+ return true;
+}
+
+void CacheControlParser::IgnoreDirective() {
+ Token t;
+ while (Next(t)) {
+ if (t.Equals(Token::Char(',')) || t.Equals(Token::EndOfFile())) {
+ Rollback();
+ break;
+ }
+ if (t.Equals(Token::Char('"'))) {
+ SkipUntil(Token::Char('"'));
+ if (!CheckChar('"')) {
+ NS_WARNING(
+ "Missing quoted string expansion in Cache-control header value");
+ break;
+ }
+ }
+ }
+}
+
+bool CacheControlParser::MaxAge(uint32_t* seconds) {
+ *seconds = mMaxAge;
+ return mMaxAgeSet;
+}
+
+bool CacheControlParser::MaxStale(uint32_t* seconds) {
+ *seconds = mMaxStale;
+ return mMaxStaleSet;
+}
+
+bool CacheControlParser::MinFresh(uint32_t* seconds) {
+ *seconds = mMinFresh;
+ return mMinFreshSet;
+}
+
+bool CacheControlParser::StaleWhileRevalidate(uint32_t* seconds) {
+ *seconds = mStaleWhileRevalidate;
+ return mStaleWhileRevalidateSet;
+}
+
+bool CacheControlParser::NoCache() { return mNoCache; }
+
+bool CacheControlParser::NoStore() { return mNoStore; }
+
+bool CacheControlParser::Public() { return mPublic; }
+
+bool CacheControlParser::Private() { return mPrivate; }
+
+bool CacheControlParser::Immutable() { return mImmutable; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/CacheControlParser.h b/netwerk/protocol/http/CacheControlParser.h
new file mode 100644
index 0000000000..6a6588be0c
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.h
@@ -0,0 +1,52 @@
+/* -*- 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 CacheControlParser_h__
+#define CacheControlParser_h__
+
+#include "mozilla/Tokenizer.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheControlParser final : Tokenizer {
+ public:
+ explicit CacheControlParser(nsACString const& header);
+
+ [[nodiscard]] bool MaxAge(uint32_t* seconds);
+ [[nodiscard]] bool MaxStale(uint32_t* seconds);
+ [[nodiscard]] bool MinFresh(uint32_t* seconds);
+ [[nodiscard]] bool StaleWhileRevalidate(uint32_t* seconds);
+ bool NoCache();
+ bool NoStore();
+ bool Public();
+ bool Private();
+ bool Immutable();
+
+ private:
+ void Directive();
+ void IgnoreDirective();
+ [[nodiscard]] bool SecondsValue(uint32_t* seconds, uint32_t defaultVal = 0);
+
+ bool mMaxAgeSet;
+ uint32_t mMaxAge;
+ bool mMaxStaleSet;
+ uint32_t mMaxStale;
+ bool mMinFreshSet;
+ uint32_t mMinFresh;
+ bool mStaleWhileRevalidateSet;
+ uint32_t mStaleWhileRevalidate;
+ bool mNoCache;
+ bool mNoStore;
+ bool mPublic;
+ bool mPrivate;
+ bool mImmutable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/CachePushChecker.cpp b/netwerk/protocol/http/CachePushChecker.cpp
new file mode 100644
index 0000000000..9f48aa7eda
--- /dev/null
+++ b/netwerk/protocol/http/CachePushChecker.cpp
@@ -0,0 +1,248 @@
+/* -*- 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 "CachePushChecker.h"
+
+#include "LoadContextInfo.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "nsICacheEntry.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsThreadUtils.h"
+#include "CacheControlParser.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(CachePushChecker, nsICacheEntryOpenCallback);
+
+CachePushChecker::CachePushChecker(nsIURI* aPushedURL,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aRequestString,
+ std::function<void(bool)>&& aCallback)
+ : mPushedURL(aPushedURL),
+ mOriginAttributes(aOriginAttributes),
+ mRequestString(aRequestString),
+ mCallback(std::move(aCallback)),
+ mCurrentEventTarget(GetCurrentSerialEventTarget()) {}
+
+nsresult CachePushChecker::DoCheck() {
+ if (XRE_IsSocketProcess()) {
+ RefPtr<CachePushChecker> self = this;
+ return NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "CachePushChecker::DoCheck",
+ [self]() {
+ if (SocketProcessChild* child =
+ SocketProcessChild::GetSingleton()) {
+ child
+ ->SendCachePushCheck(self->mPushedURL,
+ self->mOriginAttributes,
+ self->mRequestString)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](bool aResult) { self->InvokeCallback(aResult); },
+ [](const mozilla::ipc::ResponseRejectReason) {});
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsICacheStorageService> css =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, mOriginAttributes);
+ nsCOMPtr<nsICacheStorage> ds;
+ rv = css->DiskCacheStorage(lci, getter_AddRefs(ds));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return ds->AsyncOpenURI(
+ mPushedURL, ""_ns,
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this);
+}
+
+NS_IMETHODIMP
+CachePushChecker::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // We never care to fully open the entry, since we won't actually use it.
+ // We just want to be able to do all our checks to see if a future channel can
+ // use this entry, or if we need to accept the push.
+ *result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
+
+ bool isForcedValid = false;
+ entry->GetIsForcedValid(&isForcedValid);
+
+ nsHttpRequestHead requestHead;
+ requestHead.ParseHeaderSet(mRequestString.BeginReading());
+ nsHttpResponseHead cachedResponseHead;
+ bool acceptPush = true;
+ auto onExitGuard = MakeScopeExit([&] { InvokeCallback(acceptPush); });
+
+ nsresult rv =
+ nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
+ if (NS_FAILED(rv)) {
+ // Couldn't make sense of what's in the cache entry, go ahead and accept
+ // the push.
+ return NS_OK;
+ }
+
+ if ((cachedResponseHead.Status() / 100) != 2) {
+ // Assume the push is sending us a success, while we don't have one in the
+ // cache, so we'll accept the push.
+ return NS_OK;
+ }
+
+ // Get the method that was used to generate the cached response
+ nsCString buf;
+ rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
+ if (NS_FAILED(rv)) {
+ // Can't check request method, accept the push
+ return NS_OK;
+ }
+ nsAutoCString pushedMethod;
+ requestHead.Method(pushedMethod);
+ if (!buf.Equals(pushedMethod)) {
+ // Methods don't match, accept the push
+ return NS_OK;
+ }
+
+ int64_t size, contentLength;
+ rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
+ if (NS_FAILED(rv)) {
+ // Couldn't figure out if this was partial or not, accept the push.
+ return NS_OK;
+ }
+
+ if (size == int64_t(-1) || contentLength != size) {
+ // This is partial content in the cache, accept the push.
+ return NS_OK;
+ }
+
+ nsAutoCString requestedETag;
+ if (NS_FAILED(requestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
+ // Can't check etag
+ return NS_OK;
+ }
+ if (!requestedETag.IsEmpty()) {
+ nsAutoCString cachedETag;
+ if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
+ // Can't check etag
+ return NS_OK;
+ }
+ if (!requestedETag.Equals(cachedETag)) {
+ // ETags don't match, accept the push.
+ return NS_OK;
+ }
+ }
+
+ nsAutoCString imsString;
+ Unused << requestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
+ if (!buf.IsEmpty()) {
+ uint32_t ims = buf.ToInteger(&rv);
+ uint32_t lm;
+ rv = cachedResponseHead.GetLastModifiedValue(&lm);
+ if (NS_SUCCEEDED(rv) && lm && lm < ims) {
+ // The push appears to be newer than what's in our cache, accept it.
+ return NS_OK;
+ }
+ }
+
+ nsAutoCString cacheControlRequestHeader;
+ Unused << requestHead.GetHeader(nsHttp::Cache_Control,
+ cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+ if (cacheControlRequest.NoStore()) {
+ // Don't use a no-store cache entry, accept the push.
+ return NS_OK;
+ }
+
+ nsCString cachedAuth;
+ rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t lastModifiedTime;
+ rv = entry->GetLastModified(&lastModifiedTime);
+ if (NS_SUCCEEDED(rv)) {
+ if ((gHttpHandler->SessionStartTime() > lastModifiedTime) &&
+ !cachedAuth.IsEmpty()) {
+ // Need to revalidate this, as the auth is old. Accept the push.
+ return NS_OK;
+ }
+
+ if (cachedAuth.IsEmpty() &&
+ requestHead.HasHeader(nsHttp::Authorization)) {
+ // They're pushing us something with auth, but we didn't cache anything
+ // with auth. Accept the push.
+ return NS_OK;
+ }
+ }
+ }
+
+ bool weaklyFramed, isImmutable;
+ nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
+ &weaklyFramed, &isImmutable);
+
+ // We'll need this value in later computations...
+ uint32_t lastModifiedTime;
+ rv = entry->GetLastModified(&lastModifiedTime);
+ if (NS_FAILED(rv)) {
+ // Ugh, this really sucks. OK, accept the push.
+ return NS_OK;
+ }
+
+ // Determine if this is the first time that this cache entry
+ // has been accessed during this session.
+ bool fromPreviousSession =
+ (gHttpHandler->SessionStartTime() > lastModifiedTime);
+
+ bool validationRequired = nsHttp::ValidationRequired(
+ isForcedValid, &cachedResponseHead, 0 /*NWGH: ??? - loadFlags*/, false,
+ false /* forceValidateCacheContent */, isImmutable, false, requestHead,
+ entry, cacheControlRequest, fromPreviousSession);
+
+ if (validationRequired) {
+ // A real channel would most likely hit the net at this point, so let's
+ // accept the push.
+ return NS_OK;
+ }
+
+ // If we get here, then we would be able to use this cache entry. Cancel the
+ // push so as not to waste any more bandwidth.
+ acceptPush = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CachePushChecker::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsresult result) {
+ // Nothing to do here, all the work is in OnCacheEntryCheck.
+ return NS_OK;
+}
+
+void CachePushChecker::InvokeCallback(bool aResult) {
+ RefPtr<CachePushChecker> self = this;
+ auto task = [self, aResult]() { self->mCallback(aResult); };
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ mCurrentEventTarget->Dispatch(
+ NS_NewRunnableFunction("CachePushChecker::InvokeCallback",
+ std::move(task)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ task();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/CachePushChecker.h b/netwerk/protocol/http/CachePushChecker.h
new file mode 100644
index 0000000000..72855443a0
--- /dev/null
+++ b/netwerk/protocol/http/CachePushChecker.h
@@ -0,0 +1,45 @@
+/* -*- 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 CachePushChecker_h__
+#define CachePushChecker_h__
+
+#include <functional>
+#include "mozilla/OriginAttributes.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsIEventTarget.h"
+#include "nsString.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace net {
+
+class CachePushChecker final : public nsICacheEntryOpenCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ explicit CachePushChecker(nsIURI* aPushedURL,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aRequestString,
+ std::function<void(bool)>&& aCallback);
+ nsresult DoCheck();
+
+ private:
+ ~CachePushChecker() = default;
+ void InvokeCallback(bool aResult);
+
+ nsCOMPtr<nsIURI> mPushedURL;
+ OriginAttributes mOriginAttributes;
+ nsCString mRequestString;
+ std::function<void(bool)> mCallback;
+ nsCOMPtr<nsIEventTarget> mCurrentEventTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/ClassOfService.h b/netwerk/protocol/http/ClassOfService.h
new file mode 100644
index 0000000000..bf971d41df
--- /dev/null
+++ b/netwerk/protocol/http/ClassOfService.h
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __ClassOfService_h__
+#define __ClassOfService_h__
+
+#include "nsIClassOfService.h"
+#include "nsPrintfCString.h"
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla::net {
+
+class ClassOfService {
+ public:
+ ClassOfService() : mClassFlags(0), mIncremental(false) {}
+ ClassOfService(unsigned long flags, bool incremental)
+ : mClassFlags(flags), mIncremental(incremental) {}
+
+ // class flags (priority)
+ unsigned long Flags() const { return mClassFlags; }
+ void SetFlags(unsigned long flags) { mClassFlags = flags; }
+
+ // incremental flags
+ bool Incremental() const { return mIncremental; }
+ void SetIncremental(bool incremental) { mIncremental = incremental; }
+
+ static void ToString(const ClassOfService aCos, nsACString& aOut) {
+ return ToString(aCos.Flags(), aOut);
+ }
+
+ static void ToString(unsigned long aFlags, nsACString& aOut) {
+ aOut = nsPrintfCString("%lX", aFlags);
+ }
+
+ private:
+ unsigned long mClassFlags;
+ bool mIncremental;
+ friend IPC::ParamTraits<mozilla::net::ClassOfService>;
+ friend bool operator==(const ClassOfService& lhs, const ClassOfService& rhs);
+ friend bool operator!=(const ClassOfService& lhs, const ClassOfService& rhs);
+};
+
+inline bool operator==(const ClassOfService& lhs, const ClassOfService& rhs) {
+ return lhs.mClassFlags == rhs.mClassFlags &&
+ lhs.mIncremental == rhs.mIncremental;
+}
+
+inline bool operator!=(const ClassOfService& lhs, const ClassOfService& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace mozilla::net
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::net::ClassOfService> {
+ typedef mozilla::net::ClassOfService paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mClassFlags);
+ WriteParam(aWriter, aParam.mIncremental);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mClassFlags) ||
+ !ReadParam(aReader, &aResult->mIncremental))
+ return false;
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif
diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp
new file mode 100644
index 0000000000..aa1358538d
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -0,0 +1,239 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpConnection.h"
+#include "HttpConnectionUDP.h"
+#include "Http2Session.h"
+#include "nsHttpHandler.h"
+#include "nsIConsoleService.h"
+#include "nsHttpRequestHead.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla {
+namespace net {
+
+void nsHttpConnectionMgr::PrintDiagnostics() {
+ nsresult rv =
+ PostEvent(&nsHttpConnectionMgr::OnMsgPrintDiagnostics, 0, nullptr);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnectionMgr::PrintDiagnostics\n"
+ " failed to post OnMsgPrintDiagnostics event"));
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgPrintDiagnostics(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService) return;
+
+ mLogData.AppendPrintf("HTTP Connection Diagnostics\n---------------------\n");
+ mLogData.AppendPrintf("IsSpdyEnabled() = %d\n",
+ StaticPrefs::network_http_http2_enabled());
+ mLogData.AppendPrintf("MaxSocketCount() = %d\n",
+ gHttpHandler->MaxSocketCount());
+ mLogData.AppendPrintf("mNumActiveConns = %d\n", mNumActiveConns);
+ mLogData.AppendPrintf("mNumIdleConns = %d\n", mNumIdleConns);
+
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ mLogData.AppendPrintf(
+ " AtActiveConnectionLimit = %d\n",
+ AtActiveConnectionLimit(ent, NS_HTTP_ALLOW_KEEPALIVE));
+
+ ent->PrintDiagnostics(mLogData, MaxPersistConnections(ent));
+ }
+
+ consoleService->LogStringMessage(NS_ConvertUTF8toUTF16(mLogData).Data());
+ mLogData.Truncate();
+}
+
+void ConnectionEntry::PrintDiagnostics(nsCString& log,
+ uint32_t aMaxPersistConns) {
+ log.AppendPrintf(" ent host = %s hashkey = %s\n", mConnInfo->Origin(),
+ mConnInfo->HashKey().get());
+
+ log.AppendPrintf(" RestrictConnections = %d\n", RestrictConnections());
+ log.AppendPrintf(" Pending Q Length = %zu\n", PendingQueueLength());
+ log.AppendPrintf(" Active Conns Length = %zu\n", mActiveConns.Length());
+ log.AppendPrintf(" Idle Conns Length = %zu\n", mIdleConns.Length());
+ log.AppendPrintf(" DnsAndSock Length = %zu\n",
+ mDnsAndConnectSockets.Length());
+ log.AppendPrintf(" Coalescing Keys Length = %zu\n",
+ mCoalescingKeys.Length());
+ log.AppendPrintf(" Spdy using = %d\n", mUsingSpdy);
+
+ uint32_t i;
+ for (i = 0; i < mActiveConns.Length(); ++i) {
+ log.AppendPrintf(" :: Active Connection #%u\n", i);
+ mActiveConns[i]->PrintDiagnostics(log);
+ }
+ for (i = 0; i < mIdleConns.Length(); ++i) {
+ log.AppendPrintf(" :: Idle Connection #%u\n", i);
+ mIdleConns[i]->PrintDiagnostics(log);
+ }
+ for (i = 0; i < mDnsAndConnectSockets.Length(); ++i) {
+ log.AppendPrintf(" :: Half Open #%u\n", i);
+ mDnsAndConnectSockets[i]->PrintDiagnostics(log);
+ }
+
+ mPendingQ.PrintDiagnostics(log);
+
+ for (i = 0; i < mCoalescingKeys.Length(); ++i) {
+ log.AppendPrintf(" :: Coalescing Key #%u %s\n", i,
+ mCoalescingKeys[i].get());
+ }
+}
+
+void PendingTransactionQueue::PrintDiagnostics(nsCString& log) {
+ uint32_t i = 0;
+ for (const auto& entry : mPendingTransactionTable) {
+ log.AppendPrintf(" :: Pending Transactions with Window ID = %" PRIu64
+ "\n",
+ entry.GetKey());
+ for (uint32_t j = 0; j < entry.GetData()->Length(); ++j) {
+ log.AppendPrintf(" ::: Pending Transaction #%u\n", i);
+ entry.GetData()->ElementAt(j)->PrintDiagnostics(log);
+ ++i;
+ }
+ }
+}
+
+void DnsAndConnectSocket::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" has connected = %d, isSpeculative = %d\n",
+ HasConnected(), IsSpeculative());
+
+ TimeStamp now = TimeStamp::Now();
+
+ if (mPrimaryTransport.mSynStarted.IsNull()) {
+ log.AppendPrintf(" primary not started\n");
+ } else {
+ log.AppendPrintf(" primary started %.2fms ago\n",
+ (now - mPrimaryTransport.mSynStarted).ToMilliseconds());
+ }
+
+ if (mBackupTransport.mSynStarted.IsNull()) {
+ log.AppendPrintf(" backup not started\n");
+ } else {
+ log.AppendPrintf(" backup started %.2f ago\n",
+ (now - mBackupTransport.mSynStarted).ToMilliseconds());
+ }
+
+ log.AppendPrintf(" primary transport %d, backup transport %d\n",
+ !!mPrimaryTransport.mSocketTransport,
+ !!mBackupTransport.mSocketTransport);
+}
+
+void nsHttpConnection::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" CanDirectlyActivate = %d\n", CanDirectlyActivate());
+
+ log.AppendPrintf(" npncomplete = %d setupSSLCalled = %d\n",
+ mTlsHandshaker->NPNComplete(),
+ mTlsHandshaker->SetupSSLCalled());
+
+ log.AppendPrintf(" spdyVersion = %d reportedSpdy = %d everspdy = %d\n",
+ static_cast<int32_t>(mUsingSpdyVersion), mReportedSpdy,
+ mEverUsedSpdy);
+
+ log.AppendPrintf(" iskeepalive = %d dontReuse = %d isReused = %d\n",
+ IsKeepAlive(), mDontReuse, mIsReused);
+
+ log.AppendPrintf(" mTransaction = %d mSpdySession = %d\n", !!mTransaction,
+ !!mSpdySession);
+
+ PRIntervalTime now = PR_IntervalNow();
+ log.AppendPrintf(" time since last read = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastReadTime));
+
+ log.AppendPrintf(" max-read/read/written %" PRId64 "/%" PRId64 "/%" PRId64
+ "\n",
+ mMaxBytesRead, mTotalBytesRead, mTotalBytesWritten);
+
+ log.AppendPrintf(" rtt = %ums\n", PR_IntervalToMilliseconds(mRtt));
+
+ log.AppendPrintf(" idlemonitoring = %d transactionCount=%d\n",
+ mIdleMonitoring, mHttp1xTransactionCount);
+
+ if (mSpdySession) mSpdySession->PrintDiagnostics(log);
+}
+
+void HttpConnectionUDP::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" CanDirectlyActivate = %d\n", CanDirectlyActivate());
+
+ log.AppendPrintf(" dontReuse = %d isReused = %d\n", mDontReuse, mIsReused);
+
+ log.AppendPrintf(" read/written %" PRId64 "/%" PRId64 "\n",
+ mHttp3Session ? mHttp3Session->BytesRead() : 0,
+ mHttp3Session ? mHttp3Session->GetBytesWritten() : 0);
+
+ log.AppendPrintf(" rtt = %ums\n", PR_IntervalToMilliseconds(mRtt));
+}
+
+void Http2Session::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" ::: HTTP2\n");
+ log.AppendPrintf(
+ " shouldgoaway = %d mClosed = %d CanReuse = %d nextID=0x%X\n",
+ mShouldGoAway, mClosed, CanReuse(), mNextStreamID);
+
+ log.AppendPrintf(" concurrent = %d maxconcurrent = %d\n", mConcurrent,
+ mMaxConcurrent);
+
+ log.AppendPrintf(" roomformorestreams = %d roomformoreconcurrent = %d\n",
+ RoomForMoreStreams(), RoomForMoreConcurrent());
+
+ log.AppendPrintf(" transactionHashCount = %d streamIDHashCount = %d\n",
+ mStreamTransactionHash.Count(), mStreamIDHash.Count());
+
+ log.AppendPrintf(" Queued Stream Size = %zu\n", mQueuedStreams.Length());
+
+ PRIntervalTime now = PR_IntervalNow();
+ log.AppendPrintf(" Ping Threshold = %ums\n",
+ PR_IntervalToMilliseconds(mPingThreshold));
+ log.AppendPrintf(" Ping Timeout = %ums\n",
+ PR_IntervalToMilliseconds(gHttpHandler->SpdyPingTimeout()));
+ log.AppendPrintf(" Idle for Any Activity (ping) = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastReadEpoch));
+ log.AppendPrintf(" Idle for Data Activity = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastDataReadEpoch));
+ if (mPingSentEpoch) {
+ log.AppendPrintf(" Ping Outstanding (ping) = %ums, expired = %d\n",
+ PR_IntervalToMilliseconds(now - mPingSentEpoch),
+ now - mPingSentEpoch >= gHttpHandler->SpdyPingTimeout());
+ } else {
+ log.AppendPrintf(" No Ping Outstanding\n");
+ }
+}
+
+void nsHttpTransaction::PrintDiagnostics(nsCString& log) {
+ if (!mRequestHead) return;
+
+ nsAutoCString requestURI;
+ mRequestHead->RequestURI(requestURI);
+ log.AppendPrintf(" :::: uri = %s\n", requestURI.get());
+ log.AppendPrintf(" caps = 0x%x\n", static_cast<uint32_t>(mCaps));
+ log.AppendPrintf(" priority = %d\n", mPriority);
+ log.AppendPrintf(" restart count = %u\n", mRestartCount);
+}
+
+void PendingTransactionInfo::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" ::: Pending transaction\n");
+ mTransaction->PrintDiagnostics(log);
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(mDnsAndSock);
+ log.AppendPrintf(" Waiting for half open sock: %p or connection: %p\n",
+ dnsAndSock.get(), mActiveConn.get());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionEntry.cpp b/netwerk/protocol/http/ConnectionEntry.cpp
new file mode 100644
index 0000000000..473f0ab9fc
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionEntry.cpp
@@ -0,0 +1,1102 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "ConnectionEntry.h"
+#include "nsQueryObject.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+// ConnectionEntry
+ConnectionEntry::~ConnectionEntry() {
+ LOG(("ConnectionEntry::~ConnectionEntry this=%p", this));
+
+ MOZ_ASSERT(!mIdleConns.Length());
+ MOZ_ASSERT(!mActiveConns.Length());
+ MOZ_DIAGNOSTIC_ASSERT(!mDnsAndConnectSockets.Length());
+ MOZ_ASSERT(!PendingQueueLength());
+ MOZ_ASSERT(!UrgentStartQueueLength());
+ MOZ_ASSERT(!mDoNotDestroy);
+}
+
+ConnectionEntry::ConnectionEntry(nsHttpConnectionInfo* ci)
+ : mConnInfo(ci),
+ mUsingSpdy(false),
+ mCanUseSpdy(true),
+ mPreferIPv4(false),
+ mPreferIPv6(false),
+ mUsedForConnection(false),
+ mDoNotDestroy(false) {
+ LOG(("ConnectionEntry::ConnectionEntry this=%p key=%s", this,
+ ci->HashKey().get()));
+}
+
+bool ConnectionEntry::AvailableForDispatchNow() {
+ if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
+ return true;
+ }
+
+ return gHttpHandler->ConnMgr()->GetH2orH3ActiveConn(this, false, false) !=
+ nullptr;
+}
+
+uint32_t ConnectionEntry::UnconnectedDnsAndConnectSockets() const {
+ uint32_t unconnectedDnsAndConnectSockets = 0;
+ for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); ++i) {
+ if (!mDnsAndConnectSockets[i]->HasConnected()) {
+ ++unconnectedDnsAndConnectSockets;
+ }
+ }
+ return unconnectedDnsAndConnectSockets;
+}
+
+void ConnectionEntry::InsertIntoDnsAndConnectSockets(
+ DnsAndConnectSocket* sock) {
+ mDnsAndConnectSockets.AppendElement(sock);
+ gHttpHandler->ConnMgr()->IncreaseNumDnsAndConnectSockets();
+}
+
+void ConnectionEntry::RemoveDnsAndConnectSocket(DnsAndConnectSocket* dnsAndSock,
+ bool abandon) {
+ if (abandon) {
+ dnsAndSock->Abandon();
+ }
+ if (mDnsAndConnectSockets.RemoveElement(dnsAndSock)) {
+ gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
+ }
+
+ if (!UnconnectedDnsAndConnectSockets()) {
+ // perhaps this reverted RestrictConnections()
+ // use the PostEvent version of processpendingq to avoid
+ // altering the pending q vector from an arbitrary stack
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ConnectionEntry::RemoveDnsAndConnectSocket\n"
+ " failed to process pending queue\n"));
+ }
+ }
+}
+
+void ConnectionEntry::CloseAllDnsAndConnectSockets() {
+ for (const auto& dnsAndSock : mDnsAndConnectSockets) {
+ dnsAndSock->Abandon();
+ gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
+ }
+ mDnsAndConnectSockets.Clear();
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ConnectionEntry::CloseAllDnsAndConnectSockets\n"
+ " failed to process pending queue\n"));
+ }
+}
+
+void ConnectionEntry::DisallowHttp2() {
+ mCanUseSpdy = false;
+
+ // If we have any spdy connections, we want to go ahead and close them when
+ // they're done so we can free up some connections.
+ for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
+ if (mActiveConns[i]->UsingSpdy()) {
+ mActiveConns[i]->DontReuse();
+ }
+ }
+ for (uint32_t i = 0; i < mIdleConns.Length(); ++i) {
+ if (mIdleConns[i]->UsingSpdy()) {
+ mIdleConns[i]->DontReuse();
+ }
+ }
+
+ // Can't coalesce if we're not using spdy
+ mCoalescingKeys.Clear();
+}
+
+void ConnectionEntry::DontReuseHttp3Conn() {
+ MOZ_ASSERT(mConnInfo->IsHttp3());
+
+ // If we have any spdy connections, we want to go ahead and close them when
+ // they're done so we can free up some connections.
+ for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
+ mActiveConns[i]->DontReuse();
+ }
+
+ // Can't coalesce if we're not using http3
+ mCoalescingKeys.Clear();
+}
+
+void ConnectionEntry::RecordIPFamilyPreference(uint16_t family) {
+ LOG(("ConnectionEntry::RecordIPFamilyPreference %p, af=%u", this, family));
+
+ if (family == PR_AF_INET && !mPreferIPv6) {
+ mPreferIPv4 = true;
+ }
+
+ if (family == PR_AF_INET6 && !mPreferIPv4) {
+ mPreferIPv6 = true;
+ }
+
+ LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
+ (bool)mPreferIPv6));
+}
+
+void ConnectionEntry::ResetIPFamilyPreference() {
+ LOG(("ConnectionEntry::ResetIPFamilyPreference %p", this));
+
+ mPreferIPv4 = false;
+ mPreferIPv6 = false;
+}
+
+bool net::ConnectionEntry::PreferenceKnown() const {
+ return (bool)mPreferIPv4 || (bool)mPreferIPv6;
+}
+
+size_t ConnectionEntry::PendingQueueLength() const {
+ return mPendingQ.PendingQueueLength();
+}
+
+size_t ConnectionEntry::PendingQueueLengthForWindow(uint64_t windowId) const {
+ return mPendingQ.PendingQueueLengthForWindow(windowId);
+}
+
+void ConnectionEntry::AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result) {
+ mPendingQ.AppendPendingUrgentStartQ(result);
+}
+
+void ConnectionEntry::AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ mPendingQ.AppendPendingQForFocusedWindow(windowId, result, maxCount);
+ LOG(
+ ("ConnectionEntry::AppendPendingQForFocusedWindow [ci=%s], "
+ "pendingQ count=%zu for focused window (id=%" PRIu64 ")\n",
+ mConnInfo->HashKey().get(), result.Length(), windowId));
+}
+
+void ConnectionEntry::AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ mPendingQ.AppendPendingQForNonFocusedWindows(windowId, result, maxCount);
+ LOG(
+ ("ConnectionEntry::AppendPendingQForNonFocusedWindows [ci=%s], "
+ "pendingQ count=%zu for non focused window\n",
+ mConnInfo->HashKey().get(), result.Length()));
+}
+
+void ConnectionEntry::RemoveEmptyPendingQ() { mPendingQ.RemoveEmptyPendingQ(); }
+
+void ConnectionEntry::InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /*= false*/) {
+ mPendingQ.InsertTransactionSorted(pendingQ, pendingTransInfo,
+ aInsertAsFirstForTheSamePriority);
+}
+
+void ConnectionEntry::ReschedTransaction(nsHttpTransaction* aTrans) {
+ mPendingQ.ReschedTransaction(aTrans);
+}
+
+void ConnectionEntry::InsertTransaction(
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /* = false */) {
+ mPendingQ.InsertTransaction(pendingTransInfo,
+ aInsertAsFirstForTheSamePriority);
+ pendingTransInfo->Transaction()->OnPendingQueueInserted(mConnInfo->HashKey());
+}
+
+nsTArray<RefPtr<PendingTransactionInfo>>*
+ConnectionEntry::GetTransactionPendingQHelper(nsAHttpTransaction* trans) {
+ return mPendingQ.GetTransactionPendingQHelper(trans);
+}
+
+bool ConnectionEntry::RestrictConnections() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (AvailableForDispatchNow()) {
+ // this might be a h2/spdy connection in this connection entry that
+ // is able to be immediately muxxed, or it might be one that
+ // was found in the same state through a coalescing hash
+ LOG(
+ ("ConnectionEntry::RestrictConnections %p %s restricted due to "
+ "active >=h2\n",
+ this, mConnInfo->HashKey().get()));
+ return true;
+ }
+
+ // If this host is trying to negotiate a SPDY session right now,
+ // don't create any new ssl connections until the result of the
+ // negotiation is known.
+
+ bool doRestrict = mConnInfo->FirstHopSSL() &&
+ StaticPrefs::network_http_http2_enabled() && mUsingSpdy &&
+ (mDnsAndConnectSockets.Length() || mActiveConns.Length());
+
+ // If there are no restrictions, we are done
+ if (!doRestrict) {
+ return false;
+ }
+
+ // If the restriction is based on a tcp handshake in progress
+ // let that connect and then see if it was SPDY or not
+ if (UnconnectedDnsAndConnectSockets()) {
+ return true;
+ }
+
+ // There is a concern that a host is using a mix of HTTP/1 and SPDY.
+ // In that case we don't want to restrict connections just because
+ // there is a single active HTTP/1 session in use.
+ if (mUsingSpdy && mActiveConns.Length()) {
+ bool confirmedRestrict = false;
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ HttpConnectionBase* conn = mActiveConns[index];
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if ((connTCP && !connTCP->ReportedNPN()) || conn->CanDirectlyActivate()) {
+ confirmedRestrict = true;
+ break;
+ }
+ }
+ doRestrict = confirmedRestrict;
+ if (!confirmedRestrict) {
+ LOG(
+ ("nsHttpConnectionMgr spdy connection restriction to "
+ "%s bypassed.\n",
+ mConnInfo->Origin()));
+ }
+ }
+ return doRestrict;
+}
+
+uint32_t ConnectionEntry::TotalActiveConnections() const {
+ // Add in the in-progress tcp connections, we will assume they are
+ // keepalive enabled.
+ // Exclude DnsAndConnectSocket's that has already created a usable connection.
+ // This prevents the limit being stuck on ipv6 connections that
+ // eventually time out after typical 21 seconds of no ACK+SYN reply.
+ return mActiveConns.Length() + UnconnectedDnsAndConnectSockets();
+}
+
+size_t ConnectionEntry::UrgentStartQueueLength() {
+ return mPendingQ.UrgentStartQueueLength();
+}
+
+void ConnectionEntry::PrintPendingQ() { mPendingQ.PrintPendingQ(); }
+
+void ConnectionEntry::Compact() {
+ mIdleConns.Compact();
+ mActiveConns.Compact();
+ mPendingQ.Compact();
+}
+
+void ConnectionEntry::RemoveFromIdleConnectionsIndex(size_t inx) {
+ mIdleConns.RemoveElementAt(inx);
+ gHttpHandler->ConnMgr()->DecrementNumIdleConns();
+}
+
+bool ConnectionEntry::RemoveFromIdleConnections(nsHttpConnection* conn) {
+ if (!mIdleConns.RemoveElement(conn)) {
+ return false;
+ }
+
+ gHttpHandler->ConnMgr()->DecrementNumIdleConns();
+ return true;
+}
+
+void ConnectionEntry::CancelAllTransactions(nsresult reason) {
+ mPendingQ.CancelAllTransactions(reason);
+}
+
+nsresult ConnectionEntry::CloseIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<nsHttpConnection> deleteProtector(conn);
+ if (!RemoveFromIdleConnections(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // The connection is closed immediately no need to call EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ return NS_OK;
+}
+
+void ConnectionEntry::CloseIdleConnections() {
+ while (mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(mIdleConns[0]);
+ RemoveFromIdleConnectionsIndex(0);
+ // The connection is closed immediately no need to call EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ }
+}
+
+void ConnectionEntry::CloseIdleConnections(uint32_t maxToClose) {
+ uint32_t closed = 0;
+ while (mIdleConns.Length() && (closed < maxToClose)) {
+ RefPtr<nsHttpConnection> conn(mIdleConns[0]);
+ RemoveFromIdleConnectionsIndex(0);
+ // The connection is closed immediately no need to call EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ closed++;
+ }
+}
+
+void ConnectionEntry::CloseH2WebsocketConnections() {
+ while (mH2WebsocketConns.Length()) {
+ RefPtr<HttpConnectionBase> conn(mH2WebsocketConns[0]);
+ mH2WebsocketConns.RemoveElementAt(0);
+
+ // safe to close connection since we are on the socket thread
+ // closing via transaction to break connection/transaction bond
+ conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
+ }
+}
+
+nsresult ConnectionEntry::RemoveIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!RemoveFromIdleConnections(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ conn->EndIdleMonitoring();
+ return NS_OK;
+}
+
+bool ConnectionEntry::IsInIdleConnections(HttpConnectionBase* conn) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ return connTCP && mIdleConns.Contains(connTCP);
+}
+
+already_AddRefed<nsHttpConnection> ConnectionEntry::GetIdleConnection(
+ bool respectUrgency, bool urgentTrans, bool* onlyUrgent) {
+ RefPtr<nsHttpConnection> conn;
+ size_t index = 0;
+ while (!conn && (mIdleConns.Length() > index)) {
+ conn = mIdleConns[index];
+
+ if (!conn->CanReuse()) {
+ RemoveFromIdleConnectionsIndex(index);
+ LOG((" dropping stale connection: [conn=%p]\n", conn.get()));
+ conn->Close(NS_ERROR_ABORT);
+ conn = nullptr;
+ continue;
+ }
+
+ // non-urgent transactions can only be dispatched on non-urgent
+ // started or used connections.
+ if (respectUrgency && conn->IsUrgentStartPreferred() && !urgentTrans) {
+ LOG((" skipping urgent: [conn=%p]", conn.get()));
+ conn = nullptr;
+ ++index;
+ continue;
+ }
+
+ *onlyUrgent = false;
+
+ RemoveFromIdleConnectionsIndex(index);
+ conn->EndIdleMonitoring();
+ LOG((" reusing connection: [conn=%p]\n", conn.get()));
+ }
+
+ return conn.forget();
+}
+
+nsresult ConnectionEntry::RemoveActiveConnection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mActiveConns.RemoveElement(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+
+ return NS_OK;
+}
+
+nsresult ConnectionEntry::RemovePendingConnection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mPendingConns.RemoveElement(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void ConnectionEntry::ClosePersistentConnections() {
+ LOG(("ConnectionEntry::ClosePersistentConnections [ci=%s]\n",
+ mConnInfo->HashKey().get()));
+ CloseIdleConnections();
+
+ int32_t activeCount = mActiveConns.Length();
+ for (int32_t i = 0; i < activeCount; i++) {
+ mActiveConns[i]->DontReuse();
+ }
+
+ mCoalescingKeys.Clear();
+}
+
+uint32_t ConnectionEntry::PruneDeadConnections() {
+ uint32_t timeToNextExpire = UINT32_MAX;
+
+ for (int32_t len = mIdleConns.Length(); len > 0; --len) {
+ int32_t idx = len - 1;
+ RefPtr<nsHttpConnection> conn(mIdleConns[idx]);
+ if (!conn->CanReuse()) {
+ RemoveFromIdleConnectionsIndex(idx);
+ // The connection is closed immediately no need to call
+ // EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ } else {
+ timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+
+ if (mUsingSpdy) {
+ for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
+ // Http3 has its own timers, it is not using this one.
+ if (connTCP && connTCP->UsingSpdy()) {
+ if (!connTCP->CanReuse()) {
+ // Marking it don't-reuse will create an active
+ // tear down if the spdy session is idle.
+ connTCP->DontReuse();
+ } else {
+ timeToNextExpire = std::min(timeToNextExpire, connTCP->TimeToLive());
+ }
+ }
+ }
+ }
+
+ return timeToNextExpire;
+}
+
+void ConnectionEntry::VerifyTraffic() {
+ if (!mConnInfo->IsHttp3()) {
+ for (uint32_t index = 0; index < mPendingConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mPendingConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(true);
+ }
+ }
+
+ uint32_t numConns = mActiveConns.Length();
+ if (numConns) {
+ // Walk the list backwards to allow us to remove entries easily.
+ for (int index = numConns - 1; index >= 0; index--) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(true);
+ if (conn->EverUsedSpdy() &&
+ StaticPrefs::
+ network_http_http2_move_to_pending_list_after_network_change()) {
+ mActiveConns.RemoveElementAt(index);
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+ mPendingConns.AppendElement(conn);
+ LOG(("Move active connection to pending list [conn=%p]\n",
+ conn.get()));
+ }
+ }
+ }
+ }
+
+ // Iterate the idle connections and unmark them for traffic checks.
+ for (uint32_t index = 0; index < mIdleConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mIdleConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(false);
+ }
+ }
+ }
+}
+
+void ConnectionEntry::InsertIntoIdleConnections_internal(
+ nsHttpConnection* conn) {
+ uint32_t idx;
+ for (idx = 0; idx < mIdleConns.Length(); idx++) {
+ nsHttpConnection* idleConn = mIdleConns[idx];
+ if (idleConn->MaxBytesRead() < conn->MaxBytesRead()) {
+ break;
+ }
+ }
+
+ mIdleConns.InsertElementAt(idx, conn);
+}
+
+void ConnectionEntry::InsertIntoIdleConnections(nsHttpConnection* conn) {
+ InsertIntoIdleConnections_internal(conn);
+ gHttpHandler->ConnMgr()->NewIdleConnectionAdded(conn->TimeToLive());
+ conn->BeginIdleMonitoring();
+}
+
+bool ConnectionEntry::IsInActiveConns(HttpConnectionBase* conn) {
+ return mActiveConns.Contains(conn);
+}
+
+void ConnectionEntry::InsertIntoActiveConns(HttpConnectionBase* conn) {
+ mActiveConns.AppendElement(conn);
+ gHttpHandler->ConnMgr()->IncrementActiveConnCount();
+}
+
+bool ConnectionEntry::IsInH2WebsocketConns(HttpConnectionBase* conn) {
+ return mH2WebsocketConns.Contains(conn);
+}
+
+void ConnectionEntry::InsertIntoH2WebsocketConns(HttpConnectionBase* conn) {
+ // no incrementing of connection count since it is just a "fake" connection
+ mH2WebsocketConns.AppendElement(conn);
+}
+
+void ConnectionEntry::RemoveH2WebsocketConns(HttpConnectionBase* conn) {
+ mH2WebsocketConns.RemoveElement(conn);
+}
+
+void ConnectionEntry::MakeAllDontReuseExcept(HttpConnectionBase* conn) {
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ HttpConnectionBase* otherConn = mActiveConns[index];
+ if (otherConn != conn) {
+ LOG(
+ ("ConnectionEntry::MakeAllDontReuseExcept shutting down old "
+ "connection (%p) "
+ "because new "
+ "spdy connection (%p) takes precedence\n",
+ otherConn, conn));
+ otherConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING);
+ otherConn->DontReuse();
+ }
+ }
+
+ // Cancel any other pending connections - their associated transactions
+ // are in the pending queue and will be dispatched onto this new connection
+ CloseAllDnsAndConnectSockets();
+}
+
+bool ConnectionEntry::FindConnToClaim(
+ PendingTransactionInfo* pendingTransInfo) {
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ for (const auto& dnsAndSock : mDnsAndConnectSockets) {
+ if (dnsAndSock->AcceptsTransaction(trans) && dnsAndSock->Claim()) {
+ pendingTransInfo->RememberDnsAndConnectSocket(dnsAndSock);
+ // We've found a speculative connection or a connection that
+ // is free to be used in the DnsAndConnectSockets list.
+ // A free to be used connection is a connection that was
+ // open for a concrete transaction, but that trunsaction
+ // ended up using another connection.
+ LOG(
+ ("ConnectionEntry::FindConnToClaim [ci = %s]\n"
+ "Found a speculative or a free-to-use DnsAndConnectSocket\n",
+ mConnInfo->HashKey().get()));
+
+ // return OK because we have essentially opened a new connection
+ // by converting a speculative DnsAndConnectSockets to general use
+ return true;
+ }
+ }
+
+ // consider null transactions that are being used to drive the ssl handshake
+ // if the transaction creating this connection can re-use persistent
+ // connections
+ if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
+ uint32_t activeLength = mActiveConns.Length();
+ for (uint32_t i = 0; i < activeLength; i++) {
+ if (pendingTransInfo->TryClaimingActiveConn(mActiveConns[i])) {
+ LOG(
+ ("ConnectionEntry::FindConnectingSocket [ci = %s] "
+ "Claiming a null transaction for later use\n",
+ mConnInfo->HashKey().get()));
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool ConnectionEntry::MakeFirstActiveSpdyConnDontReuse() {
+ if (!mUsingSpdy) {
+ return false;
+ }
+
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ HttpConnectionBase* conn = mActiveConns[index];
+ if (conn->UsingSpdy() && conn->CanReuse()) {
+ conn->DontReuse();
+ return true;
+ }
+ }
+ return false;
+}
+
+// Return an active h2 or h3 connection
+// that can be directly activated or null.
+HttpConnectionBase* ConnectionEntry::GetH2orH3ActiveConn() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ HttpConnectionBase* experienced = nullptr;
+ HttpConnectionBase* noExperience = nullptr;
+ uint32_t activeLen = mActiveConns.Length();
+
+ // activeLen should generally be 1.. this is a setup race being resolved
+ // take a conn who can activate and is experienced
+ for (uint32_t index = 0; index < activeLen; ++index) {
+ HttpConnectionBase* tmp = mActiveConns[index];
+ if (tmp->CanDirectlyActivate()) {
+ if (tmp->IsExperienced()) {
+ experienced = tmp;
+ break;
+ }
+ noExperience = tmp; // keep looking for a better option
+ }
+ }
+
+ // if that worked, cleanup anything else and exit
+ if (experienced) {
+ for (uint32_t index = 0; index < activeLen; ++index) {
+ HttpConnectionBase* tmp = mActiveConns[index];
+ // in the case where there is a functional h2 session, drop the others
+ if (tmp != experienced) {
+ tmp->DontReuse();
+ }
+ }
+
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "found an active experienced connection %p in native connection "
+ "entry\n",
+ this, mConnInfo->HashKey().get(), experienced));
+ return experienced;
+ }
+
+ if (noExperience) {
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "found an active but inexperienced connection %p in native connection "
+ "entry\n",
+ this, mConnInfo->HashKey().get(), noExperience));
+ return noExperience;
+ }
+
+ return nullptr;
+}
+
+void ConnectionEntry::CloseActiveConnections() {
+ while (mActiveConns.Length()) {
+ RefPtr<HttpConnectionBase> conn(mActiveConns[0]);
+ mActiveConns.RemoveElementAt(0);
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+
+ // Since HttpConnectionBase::Close doesn't break the bond with
+ // the connection's transaction, we must explicitely tell it
+ // to close its transaction and not just self.
+ conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
+ }
+}
+
+void ConnectionEntry::CloseAllActiveConnsWithNullTransactcion(
+ nsresult aCloseCode) {
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ RefPtr<HttpConnectionBase> activeConn = mActiveConns[index];
+ nsAHttpTransaction* liveTransaction = activeConn->Transaction();
+ if (liveTransaction && liveTransaction->IsNullTransaction()) {
+ LOG(
+ ("ConnectionEntry::CloseAllActiveConnsWithNullTransactcion "
+ "also canceling Null Transaction %p on conn %p\n",
+ liveTransaction, activeConn.get()));
+ activeConn->CloseTransaction(liveTransaction, aCloseCode);
+ }
+ }
+}
+
+void ConnectionEntry::ClosePendingConnections() {
+ while (mPendingConns.Length()) {
+ RefPtr<HttpConnectionBase> conn(mPendingConns[0]);
+ mPendingConns.RemoveElementAt(0);
+
+ // Since HttpConnectionBase::Close doesn't break the bond with
+ // the connection's transaction, we must explicitely tell it
+ // to close its transaction and not just self.
+ conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
+ }
+}
+
+void ConnectionEntry::PruneNoTraffic() {
+ LOG((" pruning no traffic [ci=%s]\n", mConnInfo->HashKey().get()));
+ if (mConnInfo->IsHttp3()) {
+ return;
+ }
+
+ uint32_t numConns = mActiveConns.Length();
+ if (numConns) {
+ // Walk the list backwards to allow us to remove entries easily.
+ for (int index = numConns - 1; index >= 0; index--) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn && conn->NoTraffic()) {
+ mActiveConns.RemoveElementAt(index);
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+ conn->Close(NS_ERROR_ABORT);
+ LOG(
+ (" closed active connection due to no traffic "
+ "[conn=%p]\n",
+ conn.get()));
+ }
+ }
+ }
+}
+
+uint32_t ConnectionEntry::TimeoutTick() {
+ uint32_t timeoutTickNext = 3600; // 1hr
+
+ if (mConnInfo->IsHttp3()) {
+ return timeoutTickNext;
+ }
+
+ LOG(
+ ("ConnectionEntry::TimeoutTick() this=%p host=%s "
+ "idle=%zu active=%zu"
+ " dnsAndSock-len=%zu pending=%zu"
+ " urgentStart pending=%zu\n",
+ this, mConnInfo->Origin(), IdleConnectionsLength(), ActiveConnsLength(),
+ mDnsAndConnectSockets.Length(), PendingQueueLength(),
+ UrgentStartQueueLength()));
+
+ // First call the tick handler for each active connection.
+ PRIntervalTime tickTime = PR_IntervalNow();
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn) {
+ uint32_t connNextTimeout = conn->ReadTimeoutTick(tickTime);
+ timeoutTickNext = std::min(timeoutTickNext, connNextTimeout);
+ }
+ }
+
+ // Now check for any stalled DnsAndConnectSockets.
+ if (mDnsAndConnectSockets.Length()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
+
+ for (const auto& dnsAndSock : Reversed(mDnsAndConnectSockets)) {
+ double delta = dnsAndSock->Duration(currentTime);
+ // If the socket has timed out, close it so the waiting
+ // transaction will get the proper signal.
+ if (delta > maxConnectTime_ms) {
+ LOG(("Force timeout of DnsAndConnectSocket to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ dnsAndSock->CloseTransports(NS_ERROR_NET_TIMEOUT);
+ }
+
+ // If this DnsAndConnectSocket hangs around for 5 seconds after we've
+ // closed() it then just abandon the socket.
+ if (delta > maxConnectTime_ms + 5000) {
+ LOG(("Abandon DnsAndConnectSocket to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ RemoveDnsAndConnectSocket(dnsAndSock, true);
+ }
+ }
+ }
+ if (mDnsAndConnectSockets.Length()) {
+ timeoutTickNext = 1;
+ }
+
+ return timeoutTickNext;
+}
+
+void ConnectionEntry::MoveConnection(HttpConnectionBase* proxyConn,
+ ConnectionEntry* otherEnt) {
+ // To avoid changing mNumActiveConns/mNumIdleConns counter use internal
+ // functions.
+ RefPtr<HttpConnectionBase> deleteProtector(proxyConn);
+ if (mActiveConns.RemoveElement(proxyConn)) {
+ otherEnt->mActiveConns.AppendElement(proxyConn);
+ return;
+ }
+
+ RefPtr<nsHttpConnection> proxyConnTCP = do_QueryObject(proxyConn);
+ if (proxyConnTCP) {
+ if (mIdleConns.RemoveElement(proxyConnTCP)) {
+ otherEnt->InsertIntoIdleConnections_internal(proxyConnTCP);
+ return;
+ }
+ }
+}
+
+HttpRetParams ConnectionEntry::GetConnectionData() {
+ HttpRetParams data;
+ data.host = mConnInfo->Origin();
+ data.port = mConnInfo->OriginPort();
+ for (uint32_t i = 0; i < mActiveConns.Length(); i++) {
+ HttpConnInfo info;
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
+ if (connTCP) {
+ info.ttl = connTCP->TimeToLive();
+ } else {
+ info.ttl = 0;
+ }
+ info.rtt = mActiveConns[i]->Rtt();
+ info.SetHTTPProtocolVersion(mActiveConns[i]->Version());
+ data.active.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < mIdleConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = mIdleConns[i]->TimeToLive();
+ info.rtt = mIdleConns[i]->Rtt();
+ info.SetHTTPProtocolVersion(mIdleConns[i]->Version());
+ data.idle.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); i++) {
+ DnsAndConnectSockets dnsAndSock{};
+ dnsAndSock.speculative = mDnsAndConnectSockets[i]->IsSpeculative();
+ data.dnsAndSocks.AppendElement(dnsAndSock);
+ }
+ if (mConnInfo->IsHttp3()) {
+ data.httpVersion = "HTTP/3"_ns;
+ } else if (mUsingSpdy) {
+ data.httpVersion = "HTTP/2"_ns;
+ } else {
+ data.httpVersion = "HTTP <= 1.1"_ns;
+ }
+ data.ssl = mConnInfo->EndToEndSSL();
+ return data;
+}
+
+void ConnectionEntry::LogConnections() {
+ if (!mConnInfo->IsHttp3()) {
+ LOG(("active urgent conns ["));
+ for (HttpConnectionBase* conn : mActiveConns) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ MOZ_ASSERT(connTCP);
+ if (connTCP->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+ LOG(("] active regular conns ["));
+ for (HttpConnectionBase* conn : mActiveConns) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ MOZ_ASSERT(connTCP);
+ if (!connTCP->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+
+ LOG(("] idle urgent conns ["));
+ for (nsHttpConnection* conn : mIdleConns) {
+ if (conn->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+ LOG(("] idle regular conns ["));
+ for (nsHttpConnection* conn : mIdleConns) {
+ if (!conn->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+ } else {
+ LOG(("active conns ["));
+ for (HttpConnectionBase* conn : mActiveConns) {
+ LOG((" %p", conn));
+ }
+ MOZ_ASSERT(mIdleConns.Length() == 0);
+ }
+ LOG(("]"));
+}
+
+bool ConnectionEntry::RemoveTransFromPendingQ(nsHttpTransaction* aTrans) {
+ // We will abandon all DnsAndConnectSockets belonging to the given
+ // transaction.
+ nsTArray<RefPtr<PendingTransactionInfo>>* infoArray =
+ GetTransactionPendingQHelper(aTrans);
+
+ RefPtr<PendingTransactionInfo> pendingTransInfo;
+ int32_t transIndex =
+ infoArray ? infoArray->IndexOf(aTrans, 0, PendingComparator()) : -1;
+ if (transIndex >= 0) {
+ pendingTransInfo = (*infoArray)[transIndex];
+ infoArray->RemoveElementAt(transIndex);
+ }
+
+ if (!pendingTransInfo) {
+ return false;
+ }
+
+ // Abandon all DnsAndConnectSockets belonging to the given transaction.
+ nsWeakPtr tmp = pendingTransInfo->ForgetDnsAndConnectSocketAndActiveConn();
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(tmp);
+ if (dnsAndSock) {
+ RemoveDnsAndConnectSocket(dnsAndSock, true);
+ }
+ return true;
+}
+
+void ConnectionEntry::MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo) {
+ if (!mConnInfo->HashKey().Equals(aConnInfo->HashKey())) {
+ return;
+ }
+
+ const nsCString& echConfig = aConnInfo->GetEchConfig();
+ if (mConnInfo->GetEchConfig().Equals(echConfig)) {
+ return;
+ }
+
+ LOG(("ConnectionEntry::MaybeUpdateEchConfig [ci=%s]\n",
+ mConnInfo->HashKey().get()));
+
+ mConnInfo->SetEchConfig(echConfig);
+
+ // If echConfig is changed, we should close all DnsAndConnectSockets and idle
+ // connections. This is to make sure the new echConfig will be used for the
+ // next connection.
+ CloseAllDnsAndConnectSockets();
+ CloseIdleConnections();
+}
+
+bool ConnectionEntry::MaybeProcessCoalescingKeys(nsIDNSAddrRecord* dnsRecord,
+ bool aIsHttp3) {
+ if (!mConnInfo || !mConnInfo->EndToEndSSL() || (!aIsHttp3 && !AllowHttp2()) ||
+ mConnInfo->UsingProxy() || !mCoalescingKeys.IsEmpty() || !dnsRecord) {
+ return false;
+ }
+
+ nsTArray<NetAddr> addressSet;
+ nsresult rv = dnsRecord->GetAddresses(addressSet);
+
+ if (NS_FAILED(rv) || addressSet.IsEmpty()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < addressSet.Length(); ++i) {
+ if ((addressSet[i].raw.family == AF_INET && addressSet[i].inet.ip == 0) ||
+ (addressSet[i].raw.family == AF_INET6 &&
+ addressSet[i].inet6.ip.u64[0] == 0 &&
+ addressSet[i].inet6.ip.u64[1] == 0)) {
+ // Bug 1680249 - Don't create the coalescing key if the ip address is
+ // `0.0.0.0` or `::`.
+ LOG(
+ ("ConnectionEntry::MaybeProcessCoalescingKeys skip creating "
+ "Coalescing Key for host [%s]",
+ mConnInfo->Origin()));
+ continue;
+ }
+ nsCString* newKey = mCoalescingKeys.AppendElement(nsCString());
+ newKey->SetLength(kIPv6CStrBufSize + 26);
+ addressSet[i].ToStringBuffer(newKey->BeginWriting(), kIPv6CStrBufSize);
+ newKey->SetLength(strlen(newKey->BeginReading()));
+ if (mConnInfo->GetAnonymous()) {
+ newKey->AppendLiteral("~A:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ if (mConnInfo->GetFallbackConnection()) {
+ newKey->AppendLiteral("~F:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ newKey->AppendInt(mConnInfo->OriginPort());
+ newKey->AppendLiteral("/[");
+ nsAutoCString suffix;
+ mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
+ newKey->Append(suffix);
+ newKey->AppendLiteral("]viaDNS");
+ LOG(
+ ("ConnectionEntry::MaybeProcessCoalescingKeys "
+ "Established New Coalescing Key # %d for host "
+ "%s [%s]",
+ i, mConnInfo->Origin(), newKey->get()));
+ }
+ return true;
+}
+
+nsresult ConnectionEntry::CreateDnsAndConnectSocket(
+ nsAHttpTransaction* trans, uint32_t caps, bool speculative,
+ bool isFromPredictor, bool urgentStart, bool allow1918,
+ PendingTransactionInfo* pendingTransInfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT((speculative && !pendingTransInfo) ||
+ (!speculative && pendingTransInfo));
+
+ RefPtr<DnsAndConnectSocket> sock = new DnsAndConnectSocket(
+ mConnInfo, trans, caps, speculative, isFromPredictor, urgentStart);
+
+ if (speculative) {
+ sock->SetAllow1918(allow1918);
+ }
+
+ nsresult rv = sock->Init(this);
+ if (NS_FAILED(rv)) {
+ sock->Abandon();
+ return rv;
+ }
+
+ InsertIntoDnsAndConnectSockets(sock);
+
+ if (pendingTransInfo && sock->Claim()) {
+ pendingTransInfo->RememberDnsAndConnectSocket(sock);
+ }
+
+ return NS_OK;
+}
+
+bool ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3(nsresult aError) {
+ LOG(
+ ("ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3 %p "
+ "error=%" PRIx32,
+ this, static_cast<uint32_t>(aError)));
+ if (!IsHttp3()) {
+ MOZ_ASSERT(false, "Should not be called for non Http/3 connection");
+ return false;
+ }
+
+ if (!StaticPrefs::network_http_http3_retry_different_ip_family()) {
+ return false;
+ }
+
+ // Only allow to retry with these two errors.
+ if (aError != NS_ERROR_CONNECTION_REFUSED &&
+ aError != NS_ERROR_PROXY_CONNECTION_REFUSED) {
+ return false;
+ }
+
+ // Already retried once.
+ if (mRetriedDifferentIPFamilyForHttp3) {
+ return false;
+ }
+
+ return true;
+}
+
+void ConnectionEntry::SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily) {
+ LOG(("ConnectionEntry::SetRetryDifferentIPFamilyForHttp3 %p, af=%u", this,
+ aIPFamily));
+
+ mPreferIPv4 = false;
+ mPreferIPv6 = false;
+
+ if (aIPFamily == AF_INET) {
+ mPreferIPv6 = true;
+ }
+
+ if (aIPFamily == AF_INET6) {
+ mPreferIPv4 = true;
+ }
+
+ mRetriedDifferentIPFamilyForHttp3 = true;
+
+ LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
+ (bool)mPreferIPv6));
+ MOZ_DIAGNOSTIC_ASSERT(mPreferIPv4 ^ mPreferIPv6);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionEntry.h b/netwerk/protocol/http/ConnectionEntry.h
new file mode 100644
index 0000000000..811c5481de
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionEntry.h
@@ -0,0 +1,231 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ConnectionEntry_h__
+#define ConnectionEntry_h__
+
+#include "PendingTransactionInfo.h"
+#include "PendingTransactionQueue.h"
+#include "DnsAndConnectSocket.h"
+#include "DashboardTypes.h"
+
+namespace mozilla {
+namespace net {
+
+// ConnectionEntry
+//
+// nsHttpConnectionMgr::mCT maps connection info hash key to ConnectionEntry
+// object, which contains list of active and idle connections as well as the
+// list of pending transactions.
+class ConnectionEntry {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConnectionEntry)
+ explicit ConnectionEntry(nsHttpConnectionInfo* ci);
+
+ void ReschedTransaction(nsHttpTransaction* aTrans);
+
+ nsTArray<RefPtr<PendingTransactionInfo>>* GetTransactionPendingQHelper(
+ nsAHttpTransaction* trans);
+
+ void InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ void InsertTransaction(PendingTransactionInfo* aPendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ size_t UrgentStartQueueLength();
+
+ void PrintPendingQ();
+
+ void Compact();
+
+ void CancelAllTransactions(nsresult reason);
+
+ nsresult CloseIdleConnection(nsHttpConnection* conn);
+ void CloseIdleConnections();
+ void CloseIdleConnections(uint32_t maxToClose);
+ void CloseH2WebsocketConnections();
+ void ClosePendingConnections();
+ nsresult RemoveIdleConnection(nsHttpConnection* conn);
+ bool IsInIdleConnections(HttpConnectionBase* conn);
+ size_t IdleConnectionsLength() const { return mIdleConns.Length(); }
+ void InsertIntoIdleConnections(nsHttpConnection* conn);
+ already_AddRefed<nsHttpConnection> GetIdleConnection(bool respectUrgency,
+ bool urgentTrans,
+ bool* onlyUrgent);
+
+ size_t ActiveConnsLength() const { return mActiveConns.Length(); }
+ void InsertIntoActiveConns(HttpConnectionBase* conn);
+ bool IsInActiveConns(HttpConnectionBase* conn);
+ nsresult RemoveActiveConnection(HttpConnectionBase* conn);
+ nsresult RemovePendingConnection(HttpConnectionBase* conn);
+ void MakeAllDontReuseExcept(HttpConnectionBase* conn);
+ bool FindConnToClaim(PendingTransactionInfo* pendingTransInfo);
+ void CloseActiveConnections();
+ void CloseAllActiveConnsWithNullTransactcion(nsresult aCloseCode);
+
+ bool IsInH2WebsocketConns(HttpConnectionBase* conn);
+ void InsertIntoH2WebsocketConns(HttpConnectionBase* conn);
+ void RemoveH2WebsocketConns(HttpConnectionBase* conn);
+
+ HttpConnectionBase* GetH2orH3ActiveConn();
+ // Make an active spdy connection DontReuse.
+ // TODO: this is a helper function and should nbe improved.
+ bool MakeFirstActiveSpdyConnDontReuse();
+
+ void ClosePersistentConnections();
+
+ uint32_t PruneDeadConnections();
+ void VerifyTraffic();
+ void PruneNoTraffic();
+ uint32_t TimeoutTick();
+
+ void MoveConnection(HttpConnectionBase* proxyConn, ConnectionEntry* otherEnt);
+
+ size_t DnsAndConnectSocketsLength() const {
+ return mDnsAndConnectSockets.Length();
+ }
+
+ void InsertIntoDnsAndConnectSockets(DnsAndConnectSocket* sock);
+ void RemoveDnsAndConnectSocket(DnsAndConnectSocket* dnsAndSock, bool abandon);
+ void CloseAllDnsAndConnectSockets();
+
+ HttpRetParams GetConnectionData();
+ void LogConnections();
+
+ const RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool AvailableForDispatchNow();
+
+ // calculate the number of half open sockets that have not had at least 1
+ // connection complete
+ uint32_t UnconnectedDnsAndConnectSockets() const;
+
+ // Remove a particular DnsAndConnectSocket from the mDnsAndConnectSocket array
+ bool RemoveDnsAndConnectSocket(DnsAndConnectSocket*);
+
+ bool MaybeProcessCoalescingKeys(nsIDNSAddrRecord* dnsRecord,
+ bool aIsHttp3 = false);
+
+ nsresult CreateDnsAndConnectSocket(nsAHttpTransaction* trans, uint32_t caps,
+ bool speculative, bool isFromPredictor,
+ bool urgentStart, bool allow1918,
+ PendingTransactionInfo* pendingTransInfo);
+
+ // Spdy sometimes resolves the address in the socket manager in order
+ // to re-coalesce sharded HTTP hosts. The dotted decimal address is
+ // combined with the Anonymous flag and OA from the connection information
+ // to build the hash key for hosts in the same ip pool.
+ //
+ nsTArray<nsCString> mCoalescingKeys;
+
+ // To have the UsingSpdy flag means some host with the same connection
+ // entry has done NPN=spdy/* at some point. It does not mean every
+ // connection is currently using spdy.
+ bool mUsingSpdy : 1;
+
+ // Determines whether or not we can continue to use spdy-enabled
+ // connections in the future. This is generally set to false either when
+ // some connection here encounters connection-based auth (such as NTLM)
+ // or when some connection here encounters a server that is totally
+ // busted (such as it fails to properly perform the h2 handshake).
+ bool mCanUseSpdy : 1;
+
+ // Flags to remember our happy-eyeballs decision.
+ // Reset only by Ctrl-F5 reload.
+ // True when we've first connected an IPv4 server for this host,
+ // initially false.
+ bool mPreferIPv4 : 1;
+ // True when we've first connected an IPv6 server for this host,
+ // initially false.
+ bool mPreferIPv6 : 1;
+
+ // True if this connection entry has initiated a socket
+ bool mUsedForConnection : 1;
+
+ bool mDoNotDestroy : 1;
+
+ bool IsHttp3() const { return mConnInfo->IsHttp3(); }
+ bool AllowHttp2() const { return mCanUseSpdy; }
+ void DisallowHttp2();
+ void DontReuseHttp3Conn();
+
+ // Set the IP family preference flags according the connected family
+ void RecordIPFamilyPreference(uint16_t family);
+ // Resets all flags to their default values
+ void ResetIPFamilyPreference();
+ // True iff there is currently an established IP family preference
+ bool PreferenceKnown() const;
+
+ // Return the count of pending transactions for all window ids.
+ size_t PendingQueueLength() const;
+ size_t PendingQueueLengthForWindow(uint64_t windowId) const;
+
+ void AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result);
+
+ // Append transactions to the |result| whose window id
+ // is equal to |windowId|.
+ // NOTE: maxCount == 0 will get all transactions in the queue.
+ void AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Append transactions whose window id isn't equal to |windowId|.
+ // NOTE: windowId == 0 will get all transactions for both
+ // focused and non-focused windows.
+ void AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Remove the empty pendingQ in |mPendingTransactionTable|.
+ void RemoveEmptyPendingQ();
+
+ void PrintDiagnostics(nsCString& log, uint32_t aMaxPersistConns);
+
+ bool RestrictConnections();
+
+ // Return total active connection count, which is the sum of
+ // active connections and unconnected half open connections.
+ uint32_t TotalActiveConnections() const;
+
+ bool RemoveTransFromPendingQ(nsHttpTransaction* aTrans);
+
+ void MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo);
+
+ bool AllowToRetryDifferentIPFamilyForHttp3(nsresult aError);
+ void SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily);
+
+ private:
+ void InsertIntoIdleConnections_internal(nsHttpConnection* conn);
+ void RemoveFromIdleConnectionsIndex(size_t inx);
+ bool RemoveFromIdleConnections(nsHttpConnection* conn);
+
+ nsTArray<RefPtr<nsHttpConnection>> mIdleConns; // idle persistent connections
+ nsTArray<RefPtr<HttpConnectionBase>> mActiveConns; // active connections
+ // When a connection is added to this mPendingConns list, it is primarily
+ // to keep the connection alive and to continue serving its ongoing
+ // transaction. While in this list, the connection will not be available to
+ // serve any new transactions and will remain here until its current
+ // transaction is complete.
+ nsTArray<RefPtr<HttpConnectionBase>> mPendingConns;
+ // "fake" http2 websocket connections that needs to be cleaned up on shutdown
+ nsTArray<RefPtr<HttpConnectionBase>> mH2WebsocketConns;
+
+ nsTArray<RefPtr<DnsAndConnectSocket>>
+ mDnsAndConnectSockets; // dns resolution and half open connections
+
+ PendingTransactionQueue mPendingQ;
+ ~ConnectionEntry();
+
+ bool mRetriedDifferentIPFamilyForHttp3 = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !ConnectionEntry_h__
diff --git a/netwerk/protocol/http/ConnectionHandle.cpp b/netwerk/protocol/http/ConnectionHandle.cpp
new file mode 100644
index 0000000000..621c094112
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionHandle.cpp
@@ -0,0 +1,98 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "ConnectionHandle.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+ConnectionHandle::~ConnectionHandle() {
+ if (mConn) {
+ nsresult rv = gHttpHandler->ReclaimConnection(mConn);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ConnectionHandle::~ConnectionHandle\n"
+ " failed to reclaim connection %p\n",
+ mConn.get()));
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS0(ConnectionHandle)
+
+nsresult ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction* trans,
+ nsHttpRequestHead* req,
+ nsHttpResponseHead* resp,
+ bool* reset) {
+ return mConn->OnHeadersAvailable(trans, req, resp, reset);
+}
+
+void ConnectionHandle::CloseTransaction(nsAHttpTransaction* trans,
+ nsresult reason) {
+ mConn->CloseTransaction(trans, reason);
+}
+
+nsresult ConnectionHandle::TakeTransport(nsISocketTransport** aTransport,
+ nsIAsyncInputStream** aInputStream,
+ nsIAsyncOutputStream** aOutputStream) {
+ return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
+}
+
+Http3WebTransportSession* ConnectionHandle::GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ return mConn->GetWebTransportSession(aTransaction);
+}
+
+bool ConnectionHandle::IsPersistent() {
+ MOZ_ASSERT(OnSocketThread());
+ return mConn->IsPersistent();
+}
+
+bool ConnectionHandle::IsReused() {
+ MOZ_ASSERT(OnSocketThread());
+ return mConn->IsReused();
+}
+
+void ConnectionHandle::DontReuse() {
+ MOZ_ASSERT(OnSocketThread());
+ mConn->DontReuse();
+}
+
+nsresult ConnectionHandle::PushBack(const char* buf, uint32_t bufLen) {
+ return mConn->PushBack(buf, bufLen);
+}
+
+already_AddRefed<HttpConnectionBase> ConnectionHandle::TakeHttpConnection() {
+ // return our connection object to the caller and clear it internally
+ // do not drop our reference - the caller now owns it.
+ MOZ_ASSERT(mConn);
+ return mConn.forget();
+}
+
+already_AddRefed<HttpConnectionBase> ConnectionHandle::HttpConnection() {
+ RefPtr<HttpConnectionBase> rv(mConn);
+ return rv.forget();
+}
+
+void ConnectionHandle::CurrentBrowserIdChanged(uint64_t id) {
+ // Do nothing.
+}
+
+PRIntervalTime ConnectionHandle::LastWriteTime() {
+ return mConn->LastWriteTime();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionHandle.h b/netwerk/protocol/http/ConnectionHandle.h
new file mode 100644
index 0000000000..79b627f28a
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionHandle.h
@@ -0,0 +1,41 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ConnectionHandle_h__
+#define ConnectionHandle_h__
+
+#include "nsAHttpConnection.h"
+#include "HttpConnectionBase.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// ConnectionHandle
+//
+// thin wrapper around a real connection, used to keep track of references
+// to the connection to determine when the connection may be reused. the
+// transaction owns a reference to this handle. this extra
+// layer of indirection greatly simplifies consumer code, avoiding the
+// need for consumer code to know when to give the connection back to the
+// connection manager.
+//
+class ConnectionHandle : public nsAHttpConnection {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPCONNECTION(mConn)
+
+ explicit ConnectionHandle(HttpConnectionBase* conn) : mConn(conn) {}
+ void Reset() { mConn = nullptr; }
+
+ private:
+ virtual ~ConnectionHandle();
+ RefPtr<HttpConnectionBase> mConn;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ConnectionHandle_h__
diff --git a/netwerk/protocol/http/DnsAndConnectSocket.cpp b/netwerk/protocol/http/DnsAndConnectSocket.cpp
new file mode 100644
index 0000000000..8895ac97e1
--- /dev/null
+++ b/netwerk/protocol/http/DnsAndConnectSocket.cpp
@@ -0,0 +1,1391 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "ConnectionHandle.h"
+#include "DnsAndConnectSocket.h"
+#include "nsHttpConnection.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSRecord.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsSocketTransportService2.h"
+#include "nsDNSService2.h"
+#include "nsQueryObject.h"
+#include "nsURLHelper.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsHttpHandler.h"
+#include "ConnectionEntry.h"
+#include "HttpConnectionUDP.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/net/NeckoChannelParams.h" // For HttpActivityArgs.
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+namespace mozilla {
+namespace net {
+
+//////////////////////// DnsAndConnectSocket
+NS_IMPL_ADDREF(DnsAndConnectSocket)
+NS_IMPL_RELEASE(DnsAndConnectSocket)
+
+NS_INTERFACE_MAP_BEGIN(DnsAndConnectSocket)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(DnsAndConnectSocket)
+NS_INTERFACE_MAP_END
+
+static void NotifyActivity(nsHttpConnectionInfo* aConnInfo, uint32_t aSubtype) {
+ HttpConnectionActivity activity(
+ aConnInfo->HashKey(), aConnInfo->GetOrigin(), aConnInfo->OriginPort(),
+ aConnInfo->EndToEndSSL(), !aConnInfo->GetEchConfig().IsEmpty(),
+ aConnInfo->IsHttp3());
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION, aSubtype, PR_Now(), 0, ""_ns);
+}
+
+DnsAndConnectSocket::DnsAndConnectSocket(nsHttpConnectionInfo* ci,
+ nsAHttpTransaction* trans,
+ uint32_t caps, bool speculative,
+ bool isFromPredictor, bool urgentStart)
+ : mTransaction(trans),
+ mCaps(caps),
+ mSpeculative(speculative),
+ mUrgentStart(urgentStart),
+ mIsFromPredictor(isFromPredictor),
+ mConnInfo(ci) {
+ MOZ_ASSERT(ci && trans, "constructor with null arguments");
+ LOG(("Creating DnsAndConnectSocket [this=%p trans=%p ent=%s key=%s]\n", this,
+ trans, mConnInfo->Origin(), mConnInfo->HashKey().get()));
+
+ mIsHttp3 = mConnInfo->IsHttp3();
+ if (speculative) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN>
+ totalSpeculativeConn;
+ ++totalSpeculativeConn;
+
+ if (isFromPredictor) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED>
+ totalPreconnectsCreated;
+ ++totalPreconnectsCreated;
+ }
+ }
+
+ MOZ_ASSERT(mConnInfo);
+ NotifyActivity(mConnInfo,
+ mSpeculative
+ ? NS_HTTP_ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED
+ : NS_HTTP_ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED);
+}
+
+void DnsAndConnectSocket::CheckIsDone() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mDNSRequest);
+ MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mDNSRequest);
+}
+
+DnsAndConnectSocket::~DnsAndConnectSocket() {
+ LOG(("Destroying DnsAndConnectSocket [this=%p]\n", this));
+ MOZ_ASSERT(mState == DnsAndSocketState::DONE);
+ CheckIsDone();
+ // Check in case something goes wrong that we decrease
+ // the nsHttpConnectionMgr active connection number.
+ mPrimaryTransport.MaybeSetConnectingDone();
+ mBackupTransport.MaybeSetConnectingDone();
+
+ if (mSpeculative) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN>
+ unusedSpeculativeConn;
+ ++unusedSpeculativeConn;
+
+ if (mIsFromPredictor) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED>
+ totalPreconnectsUnused;
+ ++totalPreconnectsUnused;
+ }
+ }
+}
+
+nsresult DnsAndConnectSocket::Init(ConnectionEntry* ent) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mState == DnsAndSocketState::INIT);
+
+ if (mConnInfo->GetRoutedHost().IsEmpty()) {
+ mPrimaryTransport.mHost = mConnInfo->GetOrigin();
+ mBackupTransport.mHost = mConnInfo->GetOrigin();
+ } else {
+ mPrimaryTransport.mHost = mConnInfo->GetRoutedHost();
+ mBackupTransport.mHost = mConnInfo->GetRoutedHost();
+ }
+
+ CheckProxyConfig();
+
+ if (!mSkipDnsResolution) {
+ nsresult rv = SetupDnsFlags(ent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return SetupEvent(SetupEvents::INIT_EVENT);
+}
+
+void DnsAndConnectSocket::CheckProxyConfig() {
+ if (nsCOMPtr<nsProxyInfo> proxyInfo = mConnInfo->ProxyInfo()) {
+ nsAutoCString proxyType(proxyInfo->Type());
+
+ bool proxyTransparent = false;
+ if (proxyType.EqualsLiteral("socks") || proxyType.EqualsLiteral("socks4")) {
+ proxyTransparent = true;
+ if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ mProxyTransparentResolvesHost = true;
+ }
+ }
+
+ if (mProxyTransparentResolvesHost) {
+ // Name resolution is done on the server side. Just pretend
+ // client resolution is complete, this will get picked up later.
+ // since we don't need to do DNS now, we bypass the resolving
+ // step by initializing mNetAddr to an empty address, but we
+ // must keep the port. The SOCKS IO layer will use the hostname
+ // we send it when it's created, rather than the empty address
+ // we send with the connect call.
+ mPrimaryTransport.mSkipDnsResolution = true;
+ mBackupTransport.mSkipDnsResolution = true;
+ mSkipDnsResolution = true;
+ }
+
+ if (!proxyTransparent && !proxyInfo->Host().IsEmpty()) {
+ mProxyNotTransparent = true;
+ mPrimaryTransport.mHost = proxyInfo->Host();
+ mBackupTransport.mHost = proxyInfo->Host();
+ }
+ }
+}
+
+nsresult DnsAndConnectSocket::SetupDnsFlags(ConnectionEntry* ent) {
+ LOG(("DnsAndConnectSocket::SetupDnsFlags [this=%p] ", this));
+
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ bool disableIpv6ForBackup = false;
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ if (mCaps & NS_HTTP_DISABLE_IPV4) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+ } else if (mCaps & NS_HTTP_DISABLE_IPV6) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ } else if (ent->PreferenceKnown()) {
+ if (ent->mPreferIPv6) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+ } else if (ent->mPreferIPv4) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ }
+ mPrimaryTransport.mRetryWithDifferentIPFamily = true;
+ mBackupTransport.mRetryWithDifferentIPFamily = true;
+ } else if (gHttpHandler->FastFallbackToIPv4()) {
+ // For backup connections, we disable IPv6. That's because some users have
+ // broken IPv6 connectivity (leading to very long timeouts), and disabling
+ // IPv6 on the backup connection gives them a much better user experience
+ // with dual-stack hosts, though they still pay the 250ms delay for each new
+ // connection. This strategy is also known as "happy eyeballs".
+ disableIpv6ForBackup = true;
+ }
+
+ if (ent->mConnInfo->HasIPHintAddress()) {
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The spec says: "If A and AAAA records for TargetName are locally
+ // available, the client SHOULD ignore these hints.", so we check if the DNS
+ // record is in cache before setting USE_IP_HINT_ADDRESS.
+ nsCOMPtr<nsIDNSRecord> record;
+ rv = dns->ResolveNative(
+ mPrimaryTransport.mHost, nsIDNSService::RESOLVE_OFFLINE,
+ mConnInfo->GetOriginAttributes(), getter_AddRefs(record));
+ if (NS_FAILED(rv) || !record) {
+ LOG(("Setting Socket to use IP hint address"));
+ dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
+ }
+ }
+
+ dnsFlags |=
+ nsIDNSService::GetFlagsFromTRRMode(NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps));
+
+ // When we get here, we are not resolving using any configured proxy likely
+ // because of individual proxy setting on the request or because the host is
+ // excluded from proxying. Hence, force resolution despite global proxy-DNS
+ // configuration.
+ dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
+
+ NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+
+ mPrimaryTransport.mDnsFlags = dnsFlags;
+ mBackupTransport.mDnsFlags = dnsFlags;
+ if (disableIpv6ForBackup) {
+ mBackupTransport.mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ }
+ LOG(("DnsAndConnectSocket::SetupDnsFlags flags=%u flagsBackup=%u [this=%p]",
+ mPrimaryTransport.mDnsFlags, mBackupTransport.mDnsFlags, this));
+ NS_ASSERTION(
+ !(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+ return NS_OK;
+}
+
+nsresult DnsAndConnectSocket::SetupEvent(SetupEvents event) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("DnsAndConnectSocket::SetupEvent state=%d event=%d this=%p", mState,
+ event, this));
+ nsresult rv = NS_OK;
+ switch (event) {
+ case SetupEvents::INIT_EVENT:
+ MOZ_ASSERT(mState == DnsAndSocketState::INIT);
+ rv = mPrimaryTransport.Init(this);
+ if (NS_FAILED(rv)) {
+ mState = DnsAndSocketState::DONE;
+ } else if (mPrimaryTransport.FirstResolving()) {
+ mState = DnsAndSocketState::RESOLVING;
+ } else if (!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) {
+ mState = DnsAndSocketState::CONNECTING;
+ SetupBackupTimer();
+ } else {
+ MOZ_ASSERT(false);
+ mState = DnsAndSocketState::DONE;
+ Abandon();
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ break;
+ case SetupEvents::RESOLVED_PRIMARY_EVENT:
+ // This event may be posted multiple times if a DNS lookup is
+ // retriggered, e.g with different parameter.
+ if (!mIsHttp3 && (mState == DnsAndSocketState::RESOLVING)) {
+ mState = DnsAndSocketState::CONNECTING;
+ SetupBackupTimer();
+ }
+ break;
+ case SetupEvents::PRIMARY_DONE_EVENT:
+ MOZ_ASSERT((mState == DnsAndSocketState::RESOLVING) ||
+ (mState == DnsAndSocketState::CONNECTING) ||
+ (mState == DnsAndSocketState::ONE_CONNECTED));
+ CancelBackupTimer();
+ mBackupTransport.CancelDnsResolution();
+ if (mBackupTransport.ConnectingOrRetry()) {
+ mState = DnsAndSocketState::ONE_CONNECTED;
+ } else {
+ mState = DnsAndSocketState::DONE;
+ }
+ break;
+ case SetupEvents::BACKUP_DONE_EVENT:
+ MOZ_ASSERT((mState == DnsAndSocketState::CONNECTING) ||
+ (mState == DnsAndSocketState::ONE_CONNECTED));
+ if (mPrimaryTransport.ConnectingOrRetry()) {
+ mState = DnsAndSocketState::ONE_CONNECTED;
+ } else {
+ mState = DnsAndSocketState::DONE;
+ }
+ break;
+ case SetupEvents::BACKUP_TIMER_FIRED_EVENT:
+ MOZ_ASSERT(mState == DnsAndSocketState::CONNECTING);
+ mBackupTransport.Init(this);
+ }
+ LOG(("DnsAndConnectSocket::SetupEvent state=%d", mState));
+
+ if (mState == DnsAndSocketState::DONE) {
+ CheckIsDone();
+ RefPtr<DnsAndConnectSocket> self(this);
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ if (ent) {
+ ent->RemoveDnsAndConnectSocket(this, false);
+ }
+ return rv;
+ }
+ return NS_OK;
+}
+
+void DnsAndConnectSocket::SetupBackupTimer() {
+ uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
+ MOZ_ASSERT(!mSynTimer, "timer already initd");
+
+ // When using Fast Open the correct transport will be setup for sure (it is
+ // guaranteed), but it can be that it will happened a bit later.
+ if (timeout && (!mSpeculative || mConnInfo->GetFallbackConnection()) &&
+ !mIsHttp3) {
+ // Setup the timer that will establish a backup socket
+ // if we do not get a writable event on the main one.
+ // We do this because a lost SYN takes a very long time
+ // to repair at the TCP level.
+ //
+ // Failure to setup the timer is something we can live with,
+ // so don't return an error in that case.
+ NS_NewTimerWithCallback(getter_AddRefs(mSynTimer), this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p]", this));
+ } else if (timeout) {
+ LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p], did not arm\n",
+ this));
+ }
+}
+
+void DnsAndConnectSocket::CancelBackupTimer() {
+ // If the syntimer is still armed, we can cancel it because no backup
+ // socket should be formed at this point
+ if (!mSynTimer) {
+ return;
+ }
+
+ LOG(("DnsAndConnectSocket::CancelBackupTimer()"));
+ mSynTimer->Cancel();
+
+ // Keeping the reference to the timer to remember we have already
+ // performed the backup connection.
+}
+
+void DnsAndConnectSocket::Abandon() {
+ LOG(("DnsAndConnectSocket::Abandon [this=%p ent=%s] %p %p %p %p", this,
+ mConnInfo->Origin(), mPrimaryTransport.mSocketTransport.get(),
+ mBackupTransport.mSocketTransport.get(),
+ mPrimaryTransport.mStreamOut.get(), mBackupTransport.mStreamOut.get()));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ mPrimaryTransport.Abandon();
+ mBackupTransport.Abandon();
+
+ // Stop the timer - we don't want any new backups.
+ CancelBackupTimer();
+
+ mState = DnsAndSocketState::DONE;
+}
+
+double DnsAndConnectSocket::Duration(TimeStamp epoch) {
+ if (mPrimaryTransport.mSynStarted.IsNull()) {
+ return 0;
+ }
+
+ return (epoch - mPrimaryTransport.mSynStarted).ToMilliseconds();
+}
+
+NS_IMETHODIMP // method for nsITimerCallback
+DnsAndConnectSocket::Notify(nsITimer* timer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(timer == mSynTimer, "wrong timer");
+
+ MOZ_ASSERT(!mBackupTransport.mDNSRequest);
+ MOZ_ASSERT(!mBackupTransport.mSocketTransport);
+ MOZ_ASSERT(mSynTimer);
+
+ DebugOnly<nsresult> rv = SetupEvent(BACKUP_TIMER_FIRED_EVENT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Keeping the reference to the timer to remember we have already
+ // performed the backup connection.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP // method for nsINamed
+DnsAndConnectSocket::GetName(nsACString& aName) {
+ aName.AssignLiteral("DnsAndConnectSocket");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DnsAndConnectSocket::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ LOG((
+ "DnsAndConnectSocket::OnLookupComplete: this=%p mState=%d status %" PRIx32
+ ".",
+ this, mState, static_cast<uint32_t>(status)));
+
+ if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface((rec))) {
+ nsIRequest::TRRMode effectivemode = nsIRequest::TRR_DEFAULT_MODE;
+ addrRecord->GetEffectiveTRRMode(&effectivemode);
+ nsITRRSkipReason::value skipReason = nsITRRSkipReason::TRR_UNSET;
+ addrRecord->GetTrrSkipReason(&skipReason);
+ if (mTransaction) {
+ mTransaction->SetTRRInfo(effectivemode, skipReason);
+ }
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(request);
+ RefPtr<DnsAndConnectSocket> deleteProtector(this);
+
+ if (!request || (!IsPrimary(request) && !IsBackup(request))) {
+ return NS_OK;
+ }
+
+ if (IsPrimary(request) && NS_SUCCEEDED(status)) {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RESOLVED_HOST, 0);
+ }
+
+ // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
+ // proxy host is not found, so we fixup the error code.
+ // For SOCKS proxies (mProxyTransparent == true), the socket
+ // transport resolves the real host here, so there's no fixup
+ // (see bug 226943).
+ if (mProxyNotTransparent && (status == NS_ERROR_UNKNOWN_HOST)) {
+ status = NS_ERROR_UNKNOWN_PROXY_HOST;
+ }
+
+ nsresult rv;
+ // remember if it was primary because TransportSetups will delete the ref to
+ // the DNS request and check cannot be performed later.
+ bool isPrimary = IsPrimary(request);
+ if (isPrimary) {
+ rv = mPrimaryTransport.OnLookupComplete(this, rec, status);
+ if ((!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) ||
+ (mIsHttp3 && mPrimaryTransport.Resolved())) {
+ SetupEvent(SetupEvents::RESOLVED_PRIMARY_EVENT);
+ }
+ } else {
+ rv = mBackupTransport.OnLookupComplete(this, rec, status);
+ }
+
+ if (NS_FAILED(rv) || mIsHttp3) {
+ // If we are retrying DNS, we should not setup the connection.
+ if (mIsHttp3 && mPrimaryTransport.mState ==
+ TransportSetup::TransportSetupState::RETRY_RESOLVING) {
+ LOG(("Retry DNS for Http3"));
+ return NS_OK;
+ }
+
+ // Before calling SetupConn we need to hold reference to this, i.e. a
+ // delete protector, because the corresponding ConnectionEntry may be
+ // abandoned and that will abandon this DnsAndConnectSocket.
+ SetupConn(isPrimary, rv);
+ // During a connection dispatch that will happen in SetupConn,
+ // a ConnectionEntry may be abandon and that will abandon this
+ // DnsAndConnectSocket. In that case mState will already be
+ // DnsAndSocketState::DONE and we do not need to set it again.
+ if (mState != DnsAndSocketState::DONE) {
+ if (isPrimary) {
+ SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
+ } else {
+ SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+// method for nsIAsyncOutputStreamCallback
+NS_IMETHODIMP
+DnsAndConnectSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_DIAGNOSTIC_ASSERT(mPrimaryTransport.mSocketTransport ||
+ mBackupTransport.mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(IsPrimary(out) || IsBackup(out), "stream mismatch");
+
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ Unused << ent;
+
+ RefPtr<DnsAndConnectSocket> deleteProtector(this);
+
+ LOG(("DnsAndConnectSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this,
+ mConnInfo->Origin(), IsPrimary(out) ? "primary" : "backup"));
+
+ // Remember if it was primary or backup reuest.
+ bool isPrimary = IsPrimary(out);
+ nsresult rv = NS_OK;
+ if (isPrimary) {
+ rv = mPrimaryTransport.CheckConnectedResult(this);
+ if (!mPrimaryTransport.DoneConnecting()) {
+ return NS_OK;
+ }
+ MOZ_ASSERT((NS_SUCCEEDED(rv) &&
+ (mPrimaryTransport.mState ==
+ TransportSetup::TransportSetupState::CONNECTING_DONE) &&
+ mPrimaryTransport.mSocketTransport) ||
+ (NS_FAILED(rv) &&
+ (mPrimaryTransport.mState ==
+ TransportSetup::TransportSetupState::DONE) &&
+ !mPrimaryTransport.mSocketTransport));
+ } else if (IsBackup(out)) {
+ rv = mBackupTransport.CheckConnectedResult(this);
+ if (!mBackupTransport.DoneConnecting()) {
+ return NS_OK;
+ }
+ MOZ_ASSERT((NS_SUCCEEDED(rv) &&
+ (mBackupTransport.mState ==
+ TransportSetup::TransportSetupState::CONNECTING_DONE) &&
+ mBackupTransport.mSocketTransport) ||
+ (NS_FAILED(rv) &&
+ (mBackupTransport.mState ==
+ TransportSetup::TransportSetupState::DONE) &&
+ !mBackupTransport.mSocketTransport));
+ } else {
+ MOZ_ASSERT(false, "unexpected stream");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Before calling SetupConn we need to hold a reference to this, i.e. a
+ // delete protector, because the corresponding ConnectionEntry may be
+ // abandoned and that will abandon this DnsAndConnectSocket.
+ rv = SetupConn(isPrimary, rv);
+ if (mState != DnsAndSocketState::DONE) {
+ // During a connection dispatch that will happen in SetupConn,
+ // a ConnectionEntry may be abandon and that will abandon this
+ // DnsAndConnectSocket. In that case mState will already be
+ // DnsAndSocketState::DONE and we do not need to set it again.
+ if (isPrimary) {
+ SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
+ } else {
+ SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
+ }
+ }
+ return rv;
+}
+
+nsresult DnsAndConnectSocket::SetupConn(bool isPrimary, nsresult status) {
+ // assign the new socket to the http connection
+
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ if (!ent) {
+ Abandon();
+ return NS_OK;
+ }
+
+ RefPtr<HttpConnectionBase> conn;
+
+ nsresult rv = NS_OK;
+ if (isPrimary) {
+ rv = mPrimaryTransport.SetupConn(mTransaction, ent, status, mCaps,
+ getter_AddRefs(conn));
+ } else {
+ rv = mBackupTransport.SetupConn(mTransaction, ent, status, mCaps,
+ getter_AddRefs(conn));
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("DnsAndConnectSocket::SetupConn "
+ "conn->init (%p) failed %" PRIx32 "\n",
+ conn.get(), static_cast<uint32_t>(rv)));
+
+ if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) {
+ if (mIsHttp3 && !mConnInfo->GetWebTransport()) {
+ trans->DisableHttp3(true);
+ gHttpHandler->ExcludeHttp3(mConnInfo);
+ }
+ // The transaction's connection info is changed after DisableHttp3(), so
+ // this is the only point we can remove this transaction from its conn
+ // entry.
+ ent->RemoveTransFromPendingQ(trans);
+ }
+ mTransaction->Close(rv);
+
+ return rv;
+ }
+
+ // This half-open socket has created a connection. This flag excludes it
+ // from counter of actual connections used for checking limits.
+ mHasConnected = true;
+
+ // if this is still in the pending list, remove it and dispatch it
+ RefPtr<PendingTransactionInfo> pendingTransInfo =
+ gHttpHandler->ConnMgr()->FindTransactionHelper(true, ent, mTransaction);
+ if (pendingTransInfo) {
+ MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction");
+
+ ent->InsertIntoActiveConns(conn);
+ if (mIsHttp3) {
+ // Each connection must have a ConnectionHandle wrapper.
+ // In case of Http < 2 the a ConnectionHandle is created for each
+ // transaction in DispatchAbstractTransaction.
+ // In case of Http2/ and Http3, ConnectionHandle is created only once.
+ // Http2 connection always starts as http1 connection and the first
+ // transaction use DispatchAbstractTransaction to be dispatched and
+ // a ConnectionHandle is created. All consecutive transactions for
+ // Http2 use a short-cut in DispatchTransaction and call
+ // HttpConnectionBase::Activate (DispatchAbstractTransaction is never
+ // called).
+ // In case of Http3 the short-cut HttpConnectionBase::Activate is always
+ // used also for the first transaction, therefore we need to create
+ // ConnectionHandle here.
+ RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
+ pendingTransInfo->Transaction()->SetConnection(handle);
+ }
+ rv = gHttpHandler->ConnMgr()->DispatchTransaction(
+ ent, pendingTransInfo->Transaction(), conn);
+ } else {
+ // this transaction was dispatched off the pending q before all the
+ // sockets established themselves.
+
+ // After about 1 second allow for the possibility of restarting a
+ // transaction due to server close. Keep at sub 1 second as that is the
+ // minimum granularity we can expect a server to be timing out with.
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (connTCP) {
+ connTCP->SetIsReusedAfter(950);
+ }
+
+ // if we are using ssl and no other transactions are waiting right now,
+ // then form a null transaction to drive the SSL handshake to
+ // completion. Afterwards the connection will be 100% ready for the next
+ // transaction to use it. Make an exception for SSL tunneled HTTP proxy as
+ // the NullHttpTransaction does not know how to drive Connect
+ // Http3 cannot be dispatched using OnMsgReclaimConnection (see below),
+ // therefore we need to use a Nulltransaction.
+ if (!connTCP ||
+ (ent->mConnInfo->FirstHopSSL() && !ent->UrgentStartQueueLength() &&
+ !ent->PendingQueueLength() && !ent->mConnInfo->UsingConnect())) {
+ LOG(
+ ("DnsAndConnectSocket::SetupConn null transaction will "
+ "be used to finish SSL handshake on conn %p\n",
+ conn.get()));
+ RefPtr<nsAHttpTransaction> trans;
+ if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
+ // null transactions cannot be put in the entry queue, so that
+ // explains why it is not present.
+ mDispatchedMTransaction = true;
+ trans = mTransaction;
+ } else {
+ trans = new NullHttpTransaction(mConnInfo, callbacks, mCaps);
+ }
+
+ ent->InsertIntoActiveConns(conn);
+ rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(ent, trans,
+ mCaps, conn, 0);
+ } else {
+ // otherwise just put this in the persistent connection pool
+ LOG(
+ ("DnsAndConnectSocket::SetupConn no transaction match "
+ "returning conn %p to pool\n",
+ conn.get()));
+ gHttpHandler->ConnMgr()->OnMsgReclaimConnection(conn);
+
+ // We expect that there is at least one tranasction in the pending
+ // queue that can take this connection, but it can happened that
+ // all transactions are blocked or they have took other idle
+ // connections. In that case the connection has been added to the
+ // idle queue.
+ // If the connection is in the idle queue but it is using ssl, make
+ // a nulltransaction for it to finish ssl handshake!
+ if (ent->mConnInfo->FirstHopSSL() && !ent->mConnInfo->UsingConnect()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ // If RemoveIdleConnection succeeds that means that conn is in the
+ // idle queue.
+ if (connTCP && NS_SUCCEEDED(ent->RemoveIdleConnection(connTCP))) {
+ RefPtr<nsAHttpTransaction> trans;
+ if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
+ mDispatchedMTransaction = true;
+ trans = mTransaction;
+ } else {
+ trans = new NullHttpTransaction(ent->mConnInfo, callbacks, mCaps);
+ }
+ ent->InsertIntoActiveConns(conn);
+ rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
+ ent, trans, mCaps, conn, 0);
+ }
+ }
+ }
+ }
+
+ // If this halfOpenConn was speculative, but at the end the conn got a
+ // non-null transaction than this halfOpen is not speculative anymore!
+ if (conn->Transaction() && !conn->Transaction()->IsNullTransaction()) {
+ Claim();
+ }
+
+ return rv;
+}
+
+// method for nsITransportEventSink
+NS_IMETHODIMP
+DnsAndConnectSocket::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_ASSERT(IsPrimary(trans) || IsBackup(trans));
+ if (mTransaction) {
+ if (IsPrimary(trans) ||
+ (IsBackup(trans) && (status == NS_NET_STATUS_CONNECTED_TO) &&
+ mPrimaryTransport.mSocketTransport)) {
+ // Send this status event only if the transaction is still pending,
+ // i.e. it has not found a free already connected socket.
+ // Sockets in halfOpen state can only get following events:
+ // NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
+ // mBackupTransport is only started after
+ // NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore
+ // NS_NET_STATUS_CONNECTING_TO event for mBackupTransport and
+ // send NS_NET_STATUS_CONNECTED_TO.
+ // mBackupTransport must be connected before mSocketTransport(e.g.
+ // mPrimaryTransport.mSocketTransport != nullpttr).
+ mTransaction->OnTransportStatus(trans, status, progress);
+ }
+ }
+
+ if (status == NS_NET_STATUS_CONNECTED_TO) {
+ if (IsPrimary(trans)) {
+ mPrimaryTransport.mConnectedOK = true;
+ } else {
+ mBackupTransport.mConnectedOK = true;
+ }
+ }
+
+ // The rest of this method only applies to the primary transport
+ if (!IsPrimary(trans)) {
+ return NS_OK;
+ }
+
+ // if we are doing spdy coalescing and haven't recorded the ip address
+ // for this entry before then make the hash key if our dns lookup
+ // just completed. We can't do coalescing if using a proxy because the
+ // ip addresses are not available to the client.
+
+ nsCOMPtr<nsIDNSAddrRecord> dnsRecord(
+ do_GetInterface(mPrimaryTransport.mSocketTransport));
+ if (status == NS_NET_STATUS_CONNECTING_TO &&
+ StaticPrefs::network_http_http2_enabled() &&
+ StaticPrefs::network_http_http2_coalesce_hostnames()) {
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ if (ent) {
+ if (ent->MaybeProcessCoalescingKeys(dnsRecord)) {
+ gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool DnsAndConnectSocket::IsPrimary(nsITransport* trans) {
+ return trans == mPrimaryTransport.mSocketTransport;
+}
+
+bool DnsAndConnectSocket::IsPrimary(nsIAsyncOutputStream* out) {
+ return out == mPrimaryTransport.mStreamOut;
+}
+
+bool DnsAndConnectSocket::IsPrimary(nsICancelable* dnsRequest) {
+ return dnsRequest == mPrimaryTransport.mDNSRequest;
+}
+
+bool DnsAndConnectSocket::IsBackup(nsITransport* trans) {
+ return trans == mBackupTransport.mSocketTransport;
+}
+
+bool DnsAndConnectSocket::IsBackup(nsIAsyncOutputStream* out) {
+ return out == mBackupTransport.mStreamOut;
+}
+
+bool DnsAndConnectSocket::IsBackup(nsICancelable* dnsRequest) {
+ return dnsRequest == mBackupTransport.mDNSRequest;
+}
+
+// method for nsIInterfaceRequestor
+NS_IMETHODIMP
+DnsAndConnectSocket::GetInterface(const nsIID& iid, void** result) {
+ if (mTransaction) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ return callbacks->GetInterface(iid, result);
+ }
+ }
+ return NS_ERROR_NO_INTERFACE;
+}
+
+bool DnsAndConnectSocket::AcceptsTransaction(nsHttpTransaction* trans) {
+ // When marked as urgent start, only accept urgent start marked transactions.
+ // Otherwise, accept any kind of transaction.
+ return !mUrgentStart || (trans->Caps() & nsIClassOfService::UrgentStart);
+}
+
+bool DnsAndConnectSocket::Claim() {
+ if (mSpeculative) {
+ mSpeculative = false;
+ mAllow1918 = true;
+ auto resetFlag = [](TransportSetup& transport) {
+ uint32_t flags;
+ if (transport.mSocketTransport &&
+ NS_SUCCEEDED(
+ transport.mSocketTransport->GetConnectionFlags(&flags))) {
+ flags &= ~nsISocketTransport::DISABLE_RFC1918;
+ flags &= ~nsISocketTransport::IS_SPECULATIVE_CONNECTION;
+ transport.mSocketTransport->SetConnectionFlags(flags);
+ }
+ };
+ resetFlag(mPrimaryTransport);
+ resetFlag(mBackupTransport);
+
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN>
+ usedSpeculativeConn;
+ ++usedSpeculativeConn;
+
+ if (mIsFromPredictor) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED>
+ totalPreconnectsUsed;
+ ++totalPreconnectsUsed;
+ }
+
+ // Http3 has its own syn-retransmission, therefore it does not need a
+ // backup connection.
+ if (mPrimaryTransport.ConnectingOrRetry() &&
+ !mBackupTransport.mSocketTransport && !mSynTimer && !mIsHttp3) {
+ SetupBackupTimer();
+ }
+ }
+
+ if (mFreeToUse) {
+ mFreeToUse = false;
+
+ if (mPrimaryTransport.mSocketTransport) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (NS_SUCCEEDED(mPrimaryTransport.mSocketTransport->GetTlsSocketControl(
+ getter_AddRefs(tlsSocketControl))) &&
+ tlsSocketControl) {
+ Unused << tlsSocketControl->Claim();
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void DnsAndConnectSocket::Unclaim() {
+ MOZ_ASSERT(!mSpeculative && !mFreeToUse);
+ // We will keep the backup-timer running. Most probably this halfOpen will
+ // be used by a transaction from which this transaction took the halfOpen.
+ // (this is happening because of the transaction priority.)
+ mFreeToUse = true;
+}
+
+void DnsAndConnectSocket::CloseTransports(nsresult error) {
+ if (mPrimaryTransport.mSocketTransport) {
+ mPrimaryTransport.mSocketTransport->Close(error);
+ }
+ if (mBackupTransport.mSocketTransport) {
+ mBackupTransport.mSocketTransport->Close(error);
+ }
+}
+
+DnsAndConnectSocket::TransportSetup::TransportSetup(bool isBackup)
+ : mState(TransportSetup::TransportSetupState::INIT), mIsBackup(isBackup) {}
+
+nsresult DnsAndConnectSocket::TransportSetup::Init(
+ DnsAndConnectSocket* dnsAndSock) {
+ nsresult rv;
+ mSynStarted = TimeStamp::Now();
+ if (mSkipDnsResolution) {
+ mState = TransportSetup::TransportSetupState::CONNECTING;
+ rv = SetupStreams(dnsAndSock);
+ } else {
+ mState = TransportSetup::TransportSetupState::RESOLVING;
+ rv = ResolveHost(dnsAndSock);
+ }
+ if (NS_FAILED(rv)) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+}
+
+void DnsAndConnectSocket::TransportSetup::CancelDnsResolution() {
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+ if (mState == TransportSetup::TransportSetupState::RESOLVING) {
+ mState = TransportSetup::TransportSetupState::INIT;
+ }
+}
+
+void DnsAndConnectSocket::TransportSetup::Abandon() {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+}
+
+void DnsAndConnectSocket::TransportSetup::SetConnecting() {
+ MOZ_ASSERT(!mWaitingForConnect);
+ mWaitingForConnect = true;
+ gHttpHandler->ConnMgr()->StartedConnect();
+}
+
+void DnsAndConnectSocket::TransportSetup::MaybeSetConnectingDone() {
+ if (mWaitingForConnect) {
+ mWaitingForConnect = false;
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ }
+}
+
+void DnsAndConnectSocket::TransportSetup::CloseAll() {
+ MaybeSetConnectingDone();
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport = nullptr;
+ }
+
+ // Tell output stream (and backup) to forget the half open socket.
+ if (mStreamOut) {
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ }
+
+ // Lose references to input stream (and backup).
+ if (mStreamIn) {
+ mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamIn = nullptr;
+ }
+
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ mConnectedOK = false;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::CheckConnectedResult(
+ DnsAndConnectSocket* dnsAndSock) {
+ mState = TransportSetup::TransportSetupState::CONNECTING_DONE;
+ MaybeSetConnectingDone();
+
+ if (mSkipDnsResolution) {
+ return NS_OK;
+ }
+ bool retryDns = false;
+ mSocketTransport->GetRetryDnsIfPossible(&retryDns);
+ if (!retryDns) {
+ return NS_OK;
+ }
+
+ bool retry = false;
+ if (mRetryWithDifferentIPFamily) {
+ mRetryWithDifferentIPFamily = false;
+ mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
+ nsIDNSService::RESOLVE_DISABLE_IPV4);
+ mResetFamilyPreference = true;
+ retry = true;
+ } else if (!(mDnsFlags & nsIDNSService::RESOLVE_DISABLE_TRR)) {
+ bool trrEnabled;
+ mDNSRecord->IsTRR(&trrEnabled);
+ if (trrEnabled) {
+ nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
+ mDNSRecord->GetEffectiveTRRMode(&trrMode);
+ // If current trr mode is trr only, we should not retry.
+ if (trrMode != nsIRequest::TRR_ONLY_MODE) {
+ // Drop state to closed. This will trigger a new round of
+ // DNS resolving. Bypass the cache this time since the
+ // cached data came from TRR and failed already!
+ LOG((" failed to connect with TRR enabled, try w/o\n"));
+ mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR |
+ nsIDNSService::RESOLVE_BYPASS_CACHE |
+ nsIDNSService::RESOLVE_REFRESH_CACHE;
+ retry = true;
+ }
+ }
+ }
+
+ if (retry) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
+ nsresult rv = ResolveHost(dnsAndSock);
+ if (NS_FAILED(rv)) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::SetupConn(
+ nsAHttpTransaction* transaction, ConnectionEntry* ent, nsresult status,
+ uint32_t cap, HttpConnectionBase** connection) {
+ RefPtr<HttpConnectionBase> conn;
+ if (!ent->mConnInfo->IsHttp3()) {
+ conn = new nsHttpConnection();
+ } else {
+ conn = new HttpConnectionUDP();
+ }
+
+ NotifyActivity(ent->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_CONNECTION_CREATED);
+
+ LOG(
+ ("DnsAndConnectSocket::SocketTransport::SetupConn "
+ "Created new nshttpconnection %p %s\n",
+ conn.get(), ent->mConnInfo->IsHttp3() ? "using http3" : ""));
+
+ NullHttpTransaction* nullTrans = transaction->QueryNullTransaction();
+ if (nullTrans) {
+ conn->BootstrapTimings(nullTrans->Timings());
+ }
+
+ // Some capabilities are needed before a transaction actually gets
+ // scheduled (e.g. how to negotiate false start)
+ conn->SetTransactionCaps(transaction->Caps());
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ transaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsresult rv = NS_OK;
+ if (!ent->mConnInfo->IsHttp3()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ rv =
+ connTCP->Init(ent->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mSocketTransport, mStreamIn, mStreamOut, mConnectedOK,
+ status, callbacks,
+ PR_MillisecondsToInterval(static_cast<uint32_t>(
+ (TimeStamp::Now() - mSynStarted).ToMilliseconds())),
+ cap & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE);
+ } else {
+ RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(conn);
+ rv = connUDP->Init(ent->mConnInfo, mDNSRecord, status, callbacks, cap);
+ if (NS_SUCCEEDED(rv)) {
+ if (nsHttpHandler::IsHttp3Enabled() &&
+ StaticPrefs::network_http_http2_coalesce_hostnames()) {
+ if (ent->MaybeProcessCoalescingKeys(mDNSRecord, true)) {
+ gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
+ }
+ }
+ }
+ }
+
+ bool resetPreference = false;
+ if (mResetFamilyPreference ||
+ (mSocketTransport &&
+ NS_SUCCEEDED(
+ mSocketTransport->GetResetIPFamilyPreference(&resetPreference)) &&
+ resetPreference)) {
+ ent->ResetIPFamilyPreference();
+ }
+
+ NetAddr peeraddr;
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
+ ent->RecordIPFamilyPreference(peeraddr.raw.family);
+ }
+
+ conn.forget(connection);
+ mSocketTransport = nullptr;
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mState = TransportSetup::TransportSetupState::DONE;
+ return rv;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::SetupStreams(
+ DnsAndConnectSocket* dnsAndSock) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
+
+ nsresult rv;
+ nsTArray<nsCString> socketTypes;
+ const nsHttpConnectionInfo* ci = dnsAndSock->mConnInfo;
+ if (dnsAndSock->mIsHttp3) {
+ socketTypes.AppendElement("quic"_ns);
+ } else {
+ if (ci->FirstHopSSL()) {
+ socketTypes.AppendElement("ssl"_ns);
+ } else {
+ const nsCString& defaultType = gHttpHandler->DefaultSocketType();
+ if (!defaultType.IsVoid()) {
+ socketTypes.AppendElement(defaultType);
+ }
+ }
+ }
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsISocketTransportService> sts;
+
+ sts = components::SocketTransport::Service();
+ if (!sts) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG(
+ ("DnsAndConnectSocket::SetupStreams [this=%p ent=%s] "
+ "setup routed transport to origin %s:%d via %s:%d\n",
+ this, ci->HashKey().get(), ci->Origin(), ci->OriginPort(),
+ ci->RoutedHost(), ci->RoutedPort()));
+
+ nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
+ if (routedSTS) {
+ rv = routedSTS->CreateRoutedTransport(
+ socketTypes, ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(),
+ ci->RoutedPort(), ci->ProxyInfo(), mDNSRecord,
+ getter_AddRefs(socketTransport));
+ } else {
+ if (!ci->GetRoutedHost().IsEmpty()) {
+ // There is a route requested, but the legacy nsISocketTransportService
+ // can't handle it.
+ // Origin should be reachable on origin host name, so this should
+ // not be a problem - but log it.
+ LOG(
+ ("DnsAndConnectSocket this=%p using legacy nsISocketTransportService "
+ "means explicit route %s:%d will be ignored.\n",
+ this, ci->RoutedHost(), ci->RoutedPort()));
+ }
+
+ rv = sts->CreateTransport(socketTypes, ci->GetOrigin(), ci->OriginPort(),
+ ci->ProxyInfo(), mDNSRecord,
+ getter_AddRefs(socketTransport));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t tmpFlags = 0;
+ if (dnsAndSock->mCaps & NS_HTTP_REFRESH_DNS) {
+ tmpFlags = nsISocketTransport::BYPASS_CACHE;
+ }
+
+ tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode(
+ NS_HTTP_TRR_MODE_FROM_FLAGS(dnsAndSock->mCaps));
+
+ if (dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS) {
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
+ }
+
+ // When we are making a speculative connection we do not propagate all flags
+ // in mCaps, so we need to query nsHttpConnectionInfo directly as well.
+ if ((dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) ||
+ ci->GetAnonymousAllowClientCert()) {
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
+ }
+
+ if (ci->GetPrivate()) {
+ tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
+ }
+
+ Unused << socketTransport->SetIsPrivate(ci->GetPrivate());
+
+ if (dnsAndSock->mCaps & NS_HTTP_DISALLOW_ECH) {
+ tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
+ }
+
+ if (dnsAndSock->mCaps & NS_HTTP_IS_RETRY) {
+ tmpFlags |= nsISocketTransport::IS_RETRY;
+ }
+
+ if (((dnsAndSock->mCaps & NS_HTTP_BE_CONSERVATIVE) ||
+ ci->GetBeConservative()) &&
+ gHttpHandler->ConnMgr()->BeConservativeIfProxied(ci->ProxyInfo())) {
+ LOG(("Setting Socket to BE_CONSERVATIVE"));
+ tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
+ }
+
+ if (ci->HasIPHintAddress()) {
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The spec says: "If A and AAAA records for TargetName are locally
+ // available, the client SHOULD ignore these hints.", so we check if the DNS
+ // record is in cache before setting USE_IP_HINT_ADDRESS.
+ nsCOMPtr<nsIDNSRecord> record;
+ rv = dns->ResolveNative(mHost, nsIDNSService::RESOLVE_OFFLINE,
+ dnsAndSock->mConnInfo->GetOriginAttributes(),
+ getter_AddRefs(record));
+ if (NS_FAILED(rv) || !record) {
+ LOG(("Setting Socket to use IP hint address"));
+ tmpFlags |= nsISocketTransport::USE_IP_HINT_ADDRESS;
+ }
+ }
+
+ if (mRetryWithDifferentIPFamily) {
+ // From the same reason, let the backup socket fail faster to try the other
+ // family.
+ uint16_t fallbackTimeout =
+ mIsBackup ? gHttpHandler->GetFallbackSynTimeout() : 0;
+ if (fallbackTimeout) {
+ socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
+ fallbackTimeout);
+ }
+ }
+
+ if (!dnsAndSock->Allow1918()) {
+ tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
+ }
+
+ if (dnsAndSock->mSpeculative) {
+ tmpFlags |= nsISocketTransport::IS_SPECULATIVE_CONNECTION;
+ }
+
+ socketTransport->SetConnectionFlags(tmpFlags);
+ socketTransport->SetTlsFlags(ci->GetTlsFlags());
+
+ const OriginAttributes& originAttributes =
+ dnsAndSock->mConnInfo->GetOriginAttributes();
+ if (originAttributes != OriginAttributes()) {
+ socketTransport->SetOriginAttributes(originAttributes);
+ }
+
+ socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
+
+ rv = socketTransport->SetEventSink(dnsAndSock, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = socketTransport->SetSecurityCallbacks(dnsAndSock);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (gHttpHandler->EchConfigEnabled() && !ci->GetEchConfig().IsEmpty()) {
+ MOZ_ASSERT(!ci->IsHttp3());
+ LOG(("Setting ECH"));
+ rv = socketTransport->SetEchConfig(ci->GetEchConfig());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NotifyActivity(dnsAndSock->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET);
+ }
+
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(ci);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ if (ent) {
+ Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
+ ent->mUsedForConnection);
+ ent->mUsedForConnection = true;
+ }
+
+ nsCOMPtr<nsIOutputStream> sout;
+ rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(sout));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> sin;
+ rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(sin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSocketTransport = socketTransport.forget();
+ mStreamIn = do_QueryInterface(sin);
+ mStreamOut = do_QueryInterface(sout);
+
+ rv = mStreamOut->AsyncWait(dnsAndSock, 0, 0, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ SetConnecting();
+ }
+
+ return rv;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::ResolveHost(
+ DnsAndConnectSocket* dnsAndSock) {
+ MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
+ LOG(("DnsAndConnectSocket::TransportSetup::ResolveHost [this=%p %s%s]", this,
+ PromiseFlatCString(mHost).get(),
+ (mDnsFlags & nsIDNSService::RESOLVE_BYPASS_CACHE) ? " bypass cache"
+ : ""));
+ nsCOMPtr<nsIDNSService> dns = GetOrInitDNSService();
+ if (!dns) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mIsBackup) {
+ dnsAndSock->mTransaction->OnTransportStatus(
+ nullptr, NS_NET_STATUS_RESOLVING_HOST, 0);
+ }
+
+ nsresult rv = NS_OK;
+ do {
+ rv = dns->AsyncResolveNative(
+ mHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ mDnsFlags | nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR, nullptr,
+ dnsAndSock, gSocketTransportService,
+ dnsAndSock->mConnInfo->GetOriginAttributes(),
+ getter_AddRefs(mDNSRequest));
+ } while (NS_FAILED(rv) && ShouldRetryDNS());
+
+ if (NS_FAILED(rv)) {
+ mDNSRequest = nullptr;
+ }
+ return rv;
+}
+
+bool DnsAndConnectSocket::TransportSetup::ShouldRetryDNS() {
+ if (mDnsFlags & nsIDNSService::RESOLVE_IP_HINT) {
+ mDnsFlags &= ~nsIDNSService::RESOLVE_IP_HINT;
+ return true;
+ }
+
+ if (mRetryWithDifferentIPFamily) {
+ mRetryWithDifferentIPFamily = false;
+ mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
+ nsIDNSService::RESOLVE_DISABLE_IPV4);
+ mResetFamilyPreference = true;
+ return true;
+ }
+ return false;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::OnLookupComplete(
+ DnsAndConnectSocket* dnsAndSock, nsIDNSRecord* rec, nsresult status) {
+ mDNSRequest = nullptr;
+ if (NS_SUCCEEDED(status)) {
+ mDNSRecord = do_QueryInterface(rec);
+ MOZ_ASSERT(mDNSRecord);
+
+ if (dnsAndSock->mConnInfo->IsHttp3()) {
+ mState = TransportSetup::TransportSetupState::RESOLVED;
+ return status;
+ }
+ nsresult rv = SetupStreams(dnsAndSock);
+ if (NS_SUCCEEDED(rv)) {
+ mState = TransportSetup::TransportSetupState::CONNECTING;
+ } else {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+ }
+
+ // DNS lookup status failed
+
+ if (ShouldRetryDNS()) {
+ mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
+ nsresult rv = ResolveHost(dnsAndSock);
+ if (NS_FAILED(rv)) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+ }
+
+ mState = TransportSetup::TransportSetupState::DONE;
+ return status;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/DnsAndConnectSocket.h b/netwerk/protocol/http/DnsAndConnectSocket.h
new file mode 100644
index 0000000000..8409de3d08
--- /dev/null
+++ b/netwerk/protocol/http/DnsAndConnectSocket.h
@@ -0,0 +1,274 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DnsAndConnectSocket_h__
+#define DnsAndConnectSocket_h__
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAHttpConnection.h"
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsINamed.h"
+#include "nsITransport.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+// 8d411b53-54bc-4a99-8b78-ff125eab1564
+#define NS_DNSANDCONNECTSOCKET_IID \
+ { \
+ 0x8d411b53, 0x54bc, 0x4a99, { \
+ 0x8b, 0x78, 0xff, 0x12, 0x5e, 0xab, 0x15, 0x64 \
+ } \
+ }
+
+class PendingTransactionInfo;
+class ConnectionEntry;
+
+class DnsAndConnectSocket final : public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor,
+ public nsITimerCallback,
+ public nsINamed,
+ public nsSupportsWeakReference,
+ public nsIDNSListener {
+ ~DnsAndConnectSocket();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_DNSANDCONNECTSOCKET_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+ NS_DECL_NSIDNSLISTENER
+
+ DnsAndConnectSocket(nsHttpConnectionInfo* ci, nsAHttpTransaction* trans,
+ uint32_t caps, bool speculative, bool isFromPredictor,
+ bool urgentStart);
+
+ [[nodiscard]] nsresult Init(ConnectionEntry* ent);
+ void Abandon();
+ double Duration(TimeStamp epoch);
+ void CloseTransports(nsresult error);
+
+ bool IsSpeculative() { return mSpeculative; }
+
+ bool Allow1918() { return mAllow1918; }
+ void SetAllow1918(bool val) { mAllow1918 = val; }
+
+ bool HasConnected() { return mHasConnected; }
+
+ void PrintDiagnostics(nsCString& log);
+
+ // Checks whether the transaction can be dispatched using this
+ // half-open's connection. If this half-open is marked as urgent-start,
+ // it only accepts urgent start transactions. Call only before Claim().
+ bool AcceptsTransaction(nsHttpTransaction* trans);
+ bool Claim();
+ void Unclaim();
+
+ private:
+ // This performs checks that the DnsAndConnectSocket has been properly cleand
+ // up.
+ void CheckIsDone();
+
+ /**
+ * State:
+ * INIT: initial state. From this state:
+ * 1) change the state to RESOLVING and start the primary DNS lookup
+ * if mSkipDnsResolution is false,
+ * 2) or the lookup is skip and the state changes to CONNECTING and
+ * start the backup timer.
+ * 3) or changes to DONE in case of an error.
+ * RESOLVING: the primary DNS resolution is in progress. From this state
+ * we transition into CONNECTING or DONE.
+ * CONNECTING: We change to this state when the primary connection has
+ * started. At that point the backup timer is started.
+ * ONE_CONNECTED: We change into this state when one of the connections
+ * is connected and the second is in progres.
+ * DONE
+ *
+ * Events:
+ * INIT_EVENT: Start the primary dns resolution (if mSkipDnsResolution is
+ * false), otherwise start the primary connection.
+ * RESOLVED_PRIMARY_EVENT: the primary DNS resolution is done. This event
+ * may be resent due to DNS retries
+ * CONNECTED_EVENT: A connecion (primary or backup) is done
+ */
+ enum DnsAndSocketState {
+ INIT,
+ RESOLVING,
+ CONNECTING,
+ ONE_CONNECTED,
+ DONE
+ } mState = INIT;
+
+ enum SetupEvents {
+ INIT_EVENT,
+ RESOLVED_PRIMARY_EVENT,
+ PRIMARY_DONE_EVENT,
+ BACKUP_DONE_EVENT,
+ BACKUP_TIMER_FIRED_EVENT
+ };
+
+ // This structure is responsible for performing DNS lookup, creating socket
+ // and connecting the socket.
+ struct TransportSetup {
+ enum TransportSetupState {
+ INIT,
+ RESOLVING,
+ RESOLVED,
+ RETRY_RESOLVING,
+ CONNECTING,
+ CONNECTING_DONE,
+ DONE
+ } mState;
+
+ bool FirstResolving() {
+ return mState == TransportSetup::TransportSetupState::RESOLVING;
+ }
+ bool ConnectingOrRetry() {
+ return (mState == TransportSetup::TransportSetupState::CONNECTING) ||
+ (mState == TransportSetup::TransportSetupState::RETRY_RESOLVING) ||
+ (mState == TransportSetup::TransportSetupState::CONNECTING_DONE);
+ }
+ bool Resolved() {
+ return mState == TransportSetup::TransportSetupState::RESOLVED;
+ }
+ bool DoneConnecting() {
+ return (mState == TransportSetup::TransportSetupState::CONNECTING_DONE) ||
+ (mState == TransportSetup::TransportSetupState::DONE);
+ }
+
+ nsCString mHost;
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ nsCOMPtr<nsIDNSAddrRecord> mDNSRecord;
+ nsIDNSService::DNSFlags mDnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ bool mRetryWithDifferentIPFamily = false;
+ bool mResetFamilyPreference = false;
+ bool mSkipDnsResolution = false;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncOutputStream> mStreamOut;
+ nsCOMPtr<nsIAsyncInputStream> mStreamIn;
+ TimeStamp mSynStarted;
+ bool mConnectedOK = false;
+ bool mIsBackup;
+
+ bool mWaitingForConnect = false;
+ void SetConnecting();
+ void MaybeSetConnectingDone();
+
+ nsresult Init(DnsAndConnectSocket* dnsAndSock);
+ void CancelDnsResolution();
+ void Abandon();
+ void CloseAll();
+ nsresult SetupConn(nsAHttpTransaction* transaction, ConnectionEntry* ent,
+ nsresult status, uint32_t cap,
+ HttpConnectionBase** connection);
+ [[nodiscard]] nsresult SetupStreams(DnsAndConnectSocket* dnsAndSock);
+ nsresult ResolveHost(DnsAndConnectSocket* dnsAndSock);
+ bool ShouldRetryDNS();
+ nsresult OnLookupComplete(DnsAndConnectSocket* dnsAndSock,
+ nsIDNSRecord* rec, nsresult status);
+ nsresult CheckConnectedResult(DnsAndConnectSocket* dnsAndSock);
+
+ protected:
+ explicit TransportSetup(bool isBackup);
+ };
+
+ struct PrimaryTransportSetup final : TransportSetup {
+ PrimaryTransportSetup() : TransportSetup(false) {}
+ };
+
+ struct BackupTransportSetup final : TransportSetup {
+ BackupTransportSetup() : TransportSetup(true) {}
+ };
+
+ nsresult SetupConn(bool isPrimary, nsresult status);
+ void SetupBackupTimer();
+ void CancelBackupTimer();
+
+ bool IsPrimary(nsITransport* trans);
+ bool IsPrimary(nsIAsyncOutputStream* out);
+ bool IsPrimary(nsICancelable* dnsRequest);
+ bool IsBackup(nsITransport* trans);
+ bool IsBackup(nsIAsyncOutputStream* out);
+ bool IsBackup(nsICancelable* dnsRequest);
+
+ // To find out whether |mTransaction| is still in the connection entry's
+ // pending queue. If the transaction is found and |removeWhenFound| is
+ // true, the transaction will be removed from the pending queue.
+ already_AddRefed<PendingTransactionInfo> FindTransactionHelper(
+ bool removeWhenFound);
+
+ void CheckProxyConfig();
+ nsresult SetupDnsFlags(ConnectionEntry* ent);
+ nsresult SetupEvent(SetupEvents event);
+
+ RefPtr<nsAHttpTransaction> mTransaction;
+ bool mDispatchedMTransaction = false;
+
+ PrimaryTransportSetup mPrimaryTransport;
+ uint32_t mCaps;
+
+ // mSpeculative is set if the socket was created from
+ // SpeculativeConnect(). It is cleared when a transaction would normally
+ // start a new connection from scratch but instead finds this one in
+ // the half open list and claims it for its own use. (which due to
+ // the vagaries of scheduling from the pending queue might not actually
+ // match up - but it prevents a speculative connection from opening
+ // more connections that are needed.)
+ bool mSpeculative;
+
+ // If created with a non-null urgent transaction, remember it, so we can
+ // mark the connection as urgent rightaway it's created.
+ bool mUrgentStart;
+
+ // mIsFromPredictor is set if the socket originated from the network
+ // Predictor. It is used to gather telemetry data on used speculative
+ // connections from the predictor.
+ bool mIsFromPredictor;
+
+ bool mAllow1918 = true;
+
+ // mHasConnected tracks whether one of the sockets has completed the
+ // connection process. It may have completed unsuccessfully.
+ bool mHasConnected = false;
+
+ bool mBackupConnStatsSet = false;
+
+ // A DnsAndConnectSocket can be made for a concrete non-null transaction,
+ // but the transaction can be dispatch to another connection. In that
+ // case we can free this transaction to be claimed by other
+ // transactions.
+ bool mFreeToUse = true;
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ nsCOMPtr<nsITimer> mSynTimer;
+ BackupTransportSetup mBackupTransport;
+
+ bool mIsHttp3;
+
+ bool mSkipDnsResolution = false;
+ bool mProxyNotTransparent = false;
+ bool mProxyTransparentResolvesHost = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DnsAndConnectSocket, NS_DNSANDCONNECTSOCKET_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DnsAndConnectSocket_h__
diff --git a/netwerk/protocol/http/EarlyHintPreconnect.cpp b/netwerk/protocol/http/EarlyHintPreconnect.cpp
new file mode 100644
index 0000000000..4282ae554e
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreconnect.cpp
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EarlyHintPreconnect.h"
+
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIOService.h"
+#include "nsIURI.h"
+#include "SpeculativeTransaction.h"
+
+namespace mozilla::net {
+
+namespace {
+class EarlyHintsPreConnectOverride : public nsIInterfaceRequestor,
+ public nsISpeculativeConnectionOverrider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ explicit EarlyHintsPreConnectOverride(uint32_t aConnectionLimit)
+ : mConnectionLimit(aConnectionLimit) {}
+
+ private:
+ virtual ~EarlyHintsPreConnectOverride() = default;
+
+ // Only set once on main thread and can be read on multiple threads.
+ const uint32_t mConnectionLimit;
+};
+
+NS_IMPL_ISUPPORTS(EarlyHintsPreConnectOverride, nsIInterfaceRequestor,
+ nsISpeculativeConnectionOverrider)
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetInterface(const nsIID& iid, void** result) {
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetIgnoreIdle(bool* ignoreIdle) {
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetParallelSpeculativeConnectLimit(
+ uint32_t* parallelSpeculativeConnectLimit) {
+ *parallelSpeculativeConnectLimit = mConnectionLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetIsFromPredictor(bool* isFromPredictor) {
+ *isFromPredictor = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetAllow1918(bool* allow) {
+ *allow = false;
+ return NS_OK;
+}
+
+} // namespace
+
+void EarlyHintPreconnect::MaybePreconnect(
+ const LinkHeader& aHeader, nsIURI* aBaseURI,
+ OriginAttributes&& aOriginAttributes) {
+ if (!StaticPrefs::network_early_hints_preconnect_enabled()) {
+ return;
+ }
+
+ if (!gIOService) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(aHeader.NewResolveHref(getter_AddRefs(uri), aBaseURI))) {
+ return;
+ }
+
+ // only preconnect secure context urls
+ if (!uri->SchemeIs("https")) {
+ return;
+ }
+
+ uint32_t maxPreconnectCount =
+ StaticPrefs::network_early_hints_preconnect_max_connections();
+ nsCOMPtr<nsIInterfaceRequestor> callbacks =
+ new EarlyHintsPreConnectOverride(maxPreconnectCount);
+ // Note that the http connection manager will limit the number of
+ // connections we can make, so it should be fine we don't check duplicate
+ // preconnect attempts here.
+ CORSMode corsMode = dom::Element::StringToCORSMode(aHeader.mCrossOrigin);
+ gIOService->SpeculativeConnectWithOriginAttributesNative(
+ uri, std::move(aOriginAttributes), callbacks, corsMode == CORS_ANONYMOUS);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintPreconnect.h b/netwerk/protocol/http/EarlyHintPreconnect.h
new file mode 100644
index 0000000000..119d425c06
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreconnect.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_EarlyHintPreconnect_h
+#define mozilla_net_EarlyHintPreconnect_h
+
+#include "nsNetUtil.h"
+
+namespace mozilla::net {
+
+class EarlyHintPreconnect final {
+ public:
+ static void MaybePreconnect(const LinkHeader& aHeader, nsIURI* aBaseURI,
+ OriginAttributes&& aOriginAttributes);
+
+ EarlyHintPreconnect() = delete;
+ EarlyHintPreconnect(const EarlyHintPreconnect&) = delete;
+ EarlyHintPreconnect& operator=(const EarlyHintPreconnect&) = delete;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintPreconnect_h
diff --git a/netwerk/protocol/http/EarlyHintPreloader.cpp b/netwerk/protocol/http/EarlyHintPreloader.cpp
new file mode 100644
index 0000000000..920fea7fcd
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreloader.cpp
@@ -0,0 +1,846 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EarlyHintPreloader.h"
+
+#include "EarlyHintRegistrar.h"
+#include "EarlyHintsService.h"
+#include "ErrorList.h"
+#include "HttpChannelParent.h"
+#include "MainThreadUtils.h"
+#include "NeckoCommon.h"
+#include "gfxPlatform.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/EarlyHintRegistrar.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsAttrValue.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsHttpChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIChannel.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsILoadContext.h"
+#include "nsILoadInfo.h"
+#include "nsIParentChannel.h"
+#include "nsIReferrerInfo.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "ParentChannelListener.h"
+#include "nsIChannel.h"
+#include "nsInterfaceRequestorAgg.h"
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=EarlyHint:5
+// set MOZ_LOG_FILE=earlyhint.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file earlyhint.log
+//
+static mozilla::LazyLogModule gEarlyHintLog("EarlyHint");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gEarlyHintLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gEarlyHintLog, mozilla::LogLevel::Debug)
+
+namespace mozilla::net {
+
+namespace {
+// This id uniquely identifies each early hint preloader in the
+// EarlyHintRegistrar. Must only be accessed from main thread.
+static uint64_t gEarlyHintPreloaderId{0};
+} // namespace
+
+//=============================================================================
+// OngoingEarlyHints
+//=============================================================================
+
+void OngoingEarlyHints::CancelAll(const nsACString& aReason) {
+ for (auto& preloader : mPreloaders) {
+ preloader->CancelChannel(NS_ERROR_ABORT, aReason, /* aDeleteEntry */ true);
+ }
+ mPreloaders.Clear();
+ mStartedPreloads.Clear();
+}
+
+bool OngoingEarlyHints::Contains(const PreloadHashKey& aKey) {
+ return mStartedPreloads.Contains(aKey);
+}
+
+bool OngoingEarlyHints::Add(const PreloadHashKey& aKey,
+ RefPtr<EarlyHintPreloader> aPreloader) {
+ if (!mStartedPreloads.Contains(aKey)) {
+ mStartedPreloads.Insert(aKey);
+ mPreloaders.AppendElement(aPreloader);
+ return true;
+ }
+ return false;
+}
+
+void OngoingEarlyHints::RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) {
+ // register all channels before returning
+ for (auto& preload : mPreloaders) {
+ EarlyHintConnectArgs args;
+ if (preload->Register(aCpId, args)) {
+ aOutLinks.AppendElement(std::move(args));
+ }
+ }
+}
+
+//=============================================================================
+// EarlyHintPreloader
+//=============================================================================
+
+EarlyHintPreloader::EarlyHintPreloader() {
+ AssertIsOnMainThread();
+ mConnectArgs.earlyHintPreloaderId() = ++gEarlyHintPreloaderId;
+};
+
+EarlyHintPreloader::~EarlyHintPreloader() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ Telemetry::Accumulate(Telemetry::EH_STATE_OF_PRELOAD_REQUEST, mState);
+}
+
+/* static */
+Maybe<PreloadHashKey> EarlyHintPreloader::GenerateHashKey(
+ ASDestination aAs, nsIURI* aURI, nsIPrincipal* aPrincipal,
+ CORSMode aCorsMode, bool aIsModulepreload) {
+ if (aIsModulepreload) {
+ return Some(PreloadHashKey::CreateAsScript(
+ aURI, aCorsMode, JS::loader::ScriptKind::eModule));
+ }
+ if (aAs == ASDestination::DESTINATION_FONT && aCorsMode != CORS_NONE) {
+ return Some(PreloadHashKey::CreateAsFont(aURI, aCorsMode));
+ }
+ if (aAs == ASDestination::DESTINATION_IMAGE) {
+ return Some(PreloadHashKey::CreateAsImage(aURI, aPrincipal, aCorsMode));
+ }
+ if (aAs == ASDestination::DESTINATION_SCRIPT) {
+ return Some(PreloadHashKey::CreateAsScript(
+ aURI, aCorsMode, JS::loader::ScriptKind::eClassic));
+ }
+ if (aAs == ASDestination::DESTINATION_STYLE) {
+ return Some(PreloadHashKey::CreateAsStyle(
+ aURI, aPrincipal, aCorsMode,
+ css::SheetParsingMode::eAuthorSheetFeatures));
+ }
+ if (aAs == ASDestination::DESTINATION_FETCH && aCorsMode != CORS_NONE) {
+ return Some(PreloadHashKey::CreateAsFetch(aURI, aCorsMode));
+ }
+ return Nothing();
+}
+
+/* static */
+nsSecurityFlags EarlyHintPreloader::ComputeSecurityFlags(CORSMode aCORSMode,
+ ASDestination aAs) {
+ if (aAs == ASDestination::DESTINATION_FONT) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ CORSMode::CORS_NONE,
+ nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS);
+ }
+ if (aAs == ASDestination::DESTINATION_IMAGE) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_INHERITED_CONTEXT) |
+ nsILoadInfo::SEC_ALLOW_CHROME;
+ }
+ if (aAs == ASDestination::DESTINATION_SCRIPT) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS) |
+ nsILoadInfo::SEC_ALLOW_CHROME;
+ }
+ if (aAs == ASDestination::DESTINATION_STYLE) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_INHERITED_CONTEXT) |
+ nsILoadInfo::SEC_ALLOW_CHROME;
+ ;
+ }
+ if (aAs == ASDestination::DESTINATION_FETCH) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS);
+ }
+ MOZ_ASSERT(false, "Unexpected ASDestination");
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ CORSMode::CORS_NONE,
+ nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS);
+}
+
+// static
+void EarlyHintPreloader::MaybeCreateAndInsertPreload(
+ OngoingEarlyHints* aOngoingEarlyHints, const LinkHeader& aLinkHeader,
+ nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ const nsACString& aResponseReferrerPolicy, const nsACString& aCSPHeader,
+ uint64_t aBrowsingContextID,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext,
+ bool aIsModulepreload) {
+ nsAttrValue as;
+ ParseAsValue(aLinkHeader.mAs, as);
+
+ ASDestination destination = static_cast<ASDestination>(as.GetEnumValue());
+ CollectResourcesTypeTelemetry(destination);
+
+ if (!StaticPrefs::network_early_hints_enabled()) {
+ return;
+ }
+
+ if (destination == ASDestination::DESTINATION_INVALID && !aIsModulepreload) {
+ // return early when it's definitly not an asset type we preload
+ // would be caught later as well, e.g. when creating the PreloadHashKey
+ return;
+ }
+
+ if (destination == ASDestination::DESTINATION_FONT &&
+ !gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_ENSURE_SUCCESS_VOID(
+ NS_NewURI(getter_AddRefs(uri), aLinkHeader.mHref, nullptr, aBaseURI));
+ // The link relation may apply to a different resource, specified
+ // in the anchor parameter. For the link relations supported so far,
+ // we simply abort if the link applies to a resource different to the
+ // one we've loaded
+ if (!nsContentUtils::LinkContextIsURI(aLinkHeader.mAnchor, uri)) {
+ return;
+ }
+
+ // only preload secure context urls
+ if (!nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri)) {
+ return;
+ }
+
+ CORSMode corsMode = dom::Element::StringToCORSMode(aLinkHeader.mCrossOrigin);
+
+ Maybe<PreloadHashKey> hashKey =
+ GenerateHashKey(destination, uri, aPrincipal, corsMode, aIsModulepreload);
+ if (!hashKey) {
+ return;
+ }
+
+ if (aOngoingEarlyHints->Contains(*hashKey)) {
+ return;
+ }
+
+ nsContentPolicyType contentPolicyType =
+ aIsModulepreload ? (IsScriptLikeOrInvalid(aLinkHeader.mAs)
+ ? nsContentPolicyType::TYPE_SCRIPT
+ : nsContentPolicyType::TYPE_INVALID)
+ : AsValueToContentPolicy(as);
+
+ if (contentPolicyType == nsContentPolicyType::TYPE_INVALID) {
+ return;
+ }
+
+ dom::ReferrerPolicy linkReferrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
+ aLinkHeader.mReferrerPolicy);
+
+ dom::ReferrerPolicy responseReferrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
+ NS_ConvertUTF8toUTF16(aResponseReferrerPolicy));
+
+ // The early hint may have two referrer policies, one from the response header
+ // and one from the link element.
+ //
+ // For example, in this server response:
+ // HTTP/1.1 103 Early Hints
+ // Referrer-Policy : origin
+ // Link: </style.css>; rel=preload; as=style referrerpolicy=no-referrer
+ //
+ // The link header referrer policy, if present, will take precedence over
+ // the response referrer policy
+ dom::ReferrerPolicy finalReferrerPolicy = responseReferrerPolicy;
+ if (linkReferrerPolicy != dom::ReferrerPolicy::_empty) {
+ finalReferrerPolicy = linkReferrerPolicy;
+ }
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ new dom::ReferrerInfo(aBaseURI, finalReferrerPolicy);
+
+ RefPtr<EarlyHintPreloader> earlyHintPreloader = new EarlyHintPreloader();
+
+ earlyHintPreloader->mLoadContext = aLoadingBrowsingContext;
+
+ // Security flags for modulepreload's request mode are computed here directly
+ // until full support for worker destinations can be added.
+ //
+ // Implements "To fetch a single module script,"
+ // Step 9. If destination is "worker", "sharedworker", or "serviceworker",
+ // and the top-level module fetch flag is set, then set request's
+ // mode to "same-origin".
+ nsSecurityFlags securityFlags =
+ aIsModulepreload
+ ? ((aLinkHeader.mAs.LowerCaseEqualsASCII("worker") ||
+ aLinkHeader.mAs.LowerCaseEqualsASCII("sharedworker") ||
+ aLinkHeader.mAs.LowerCaseEqualsASCII("serviceworker"))
+ ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT) |
+ (corsMode == CORS_USE_CREDENTIALS
+ ? nsILoadInfo::SEC_COOKIES_INCLUDE
+ : nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) |
+ nsILoadInfo::SEC_ALLOW_CHROME
+ : EarlyHintPreloader::ComputeSecurityFlags(corsMode, destination);
+
+ // Verify that the resource should be loaded.
+ // This isn't the ideal way to test the resource against the CSP.
+ // The problem comes from the fact that at the stage of Early Hint
+ // processing we have not yet created a document where we would normally store
+ // the CSP.
+
+ // First we will create a load info,
+ // nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
+ aPrincipal, // loading principal
+ aPrincipal, // triggering principal
+ nullptr /* aLoadingContext node */,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
+
+ if (aCSPHeader.Length() != 0) {
+ // If the CSP header is present then create a new CSP and apply the header
+ // directives to it
+ nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
+ nsresult rv = csp->SetRequestContextWithPrincipal(
+ aPrincipal, aBaseURI, u""_ns, 0 /* aInnerWindowId */);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = CSP_AppendCSPFromHeader(csp, NS_ConvertUTF8toUTF16(aCSPHeader),
+ false /* report only */);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // We create a temporary ClientInfo. This is required on the loadInfo as
+ // that is how the CSP is queried. More specificially, as a hack to be able
+ // to call NS_CheckContentLoadPolicy on nsILoadInfo which exclusively
+ // accesses the CSP from the ClientInfo, we create a synthetic ClientInfo to
+ // hold the CSP we are creating. This is not a safe thing to do in any other
+ // circumstance because ClientInfos are always describing a ClientSource
+ // that corresponds to a global or potential global, so creating an info
+ // without a source is unsound. For the purposes of doing things before a
+ // global exists, fetch has the concept of a
+ // https://fetch.spec.whatwg.org/#concept-request-reserved-client and
+ // nsILoadInfo explicity has methods around GiveReservedClientSource which
+ // are primarily used by ClientChannelHelper. If you are trying to do real
+ // CSP stuff and the ClientInfo is not there yet, please enhance the logic
+ // around ClientChannelHelper.
+
+ mozilla::ipc::PrincipalInfo principalInfo;
+ rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ dom::ClientInfo clientInfo(nsID::GenerateUUID(), dom::ClientType::Window,
+ principalInfo, TimeStamp::Now());
+
+ // Our newly-created CSP is set on the ClientInfo via the indirect route of
+ // first serializing to CSPInfo
+ ipc::CSPInfo cspInfo;
+ rv = CSPToCSPInfo(csp, &cspInfo);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ clientInfo.SetCspInfo(cspInfo);
+
+ // This ClientInfo is then set on the new loadInfo.
+ // It can now be used to test the resource against the policy
+ secCheckLoadInfo->SetClientInfo(clientInfo);
+ }
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(uri, secCheckLoadInfo, &shouldLoad,
+ nsContentUtils::GetContentPolicy());
+
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ return;
+ }
+
+ NS_ENSURE_SUCCESS_VOID(earlyHintPreloader->OpenChannel(
+ uri, aPrincipal, securityFlags, contentPolicyType, referrerInfo,
+ aCookieJarSettings, aBrowsingContextID));
+
+ earlyHintPreloader->SetLinkHeader(aLinkHeader);
+
+ DebugOnly<bool> result =
+ aOngoingEarlyHints->Add(*hashKey, earlyHintPreloader);
+ MOZ_ASSERT(result);
+}
+
+nsresult EarlyHintPreloader::OpenChannel(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, nsIReferrerInfo* aReferrerInfo,
+ nsICookieJarSettings* aCookieJarSettings, uint64_t aBrowsingContextID) {
+ MOZ_ASSERT(aContentPolicyType == nsContentPolicyType::TYPE_IMAGE ||
+ aContentPolicyType ==
+ nsContentPolicyType::TYPE_INTERNAL_FETCH_PRELOAD ||
+ aContentPolicyType == nsContentPolicyType::TYPE_SCRIPT ||
+ aContentPolicyType == nsContentPolicyType::TYPE_STYLESHEET ||
+ aContentPolicyType == nsContentPolicyType::TYPE_FONT);
+
+ nsresult rv =
+ NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal, aSecurityFlags,
+ aContentPolicyType, aCookieJarSettings,
+ /* aPerformanceStorage */ nullptr,
+ /* aLoadGroup */ nullptr,
+ /* aCallbacks */ this, nsIRequest::LOAD_NORMAL);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsHttpChannel> httpChannelObject = do_QueryObject(mChannel);
+ if (!httpChannelObject) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+ DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(aReferrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ success = httpChannel->SetRequestHeader("X-Moz"_ns, "early hint"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+
+ mParentListener = new ParentChannelListener(this, nullptr);
+
+ PriorizeAsPreload();
+
+ rv = mChannel->AsyncOpen(mParentListener);
+ if (NS_FAILED(rv)) {
+ mParentListener = nullptr;
+ return rv;
+ }
+
+ SetState(ePreloaderOpened);
+
+ // Setting the BrowsingContextID here to let Early Hint requests show up in
+ // devtools. Normally that would automatically happen if we would pass the
+ // nsILoadGroup in ns_NewChannel above, but the nsILoadGroup is inaccessible
+ // here in the ParentProcess. The nsILoadGroup only exists in ContentProcess
+ // as part of the document and nsDocShell. It is also not yet determined which
+ // ContentProcess this load belongs to.
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ static_cast<LoadInfo*>(loadInfo.get())
+ ->UpdateBrowsingContextID(aBrowsingContextID);
+
+ return NS_OK;
+}
+
+void EarlyHintPreloader::PriorizeAsPreload() {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ Unused << mChannel->GetLoadFlags(&loadFlags);
+ Unused << mChannel->SetLoadFlags(loadFlags | nsIRequest::LOAD_BACKGROUND);
+
+ if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel)) {
+ Unused << cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+}
+
+void EarlyHintPreloader::SetLinkHeader(const LinkHeader& aLinkHeader) {
+ mConnectArgs.link() = aLinkHeader;
+}
+
+bool EarlyHintPreloader::IsFromContentParent(dom::ContentParentId aCpId) const {
+ return aCpId == mCpId;
+}
+
+bool EarlyHintPreloader::Register(dom::ContentParentId aCpId,
+ EarlyHintConnectArgs& aOut) {
+ mCpId = aCpId;
+
+ // Set minimum delay of 1ms to always start the timer after the function call
+ // completed.
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this,
+ std::max(StaticPrefs::network_early_hints_parent_connect_timeout(),
+ (uint32_t)1),
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(!mTimer);
+ CancelChannel(NS_ERROR_ABORT, "new-timer-failed"_ns,
+ /* aDeleteEntry */ false);
+ return false;
+ }
+
+ // Create an entry in the redirect channel registrar
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->RegisterEarlyHint(mConnectArgs.earlyHintPreloaderId(), this);
+
+ aOut = mConnectArgs;
+ return true;
+}
+
+nsresult EarlyHintPreloader::CancelChannel(nsresult aStatus,
+ const nsACString& aReason,
+ bool aDeleteEntry) {
+ LOG(("EarlyHintPreloader::CancelChannel [this=%p]\n", this));
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (aDeleteEntry) {
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->DeleteEntry(mCpId, mConnectArgs.earlyHintPreloaderId());
+ }
+ // clear redirect channel in case this channel is cleared between the call of
+ // EarlyHintPreloader::AsyncOnChannelRedirect and
+ // EarlyHintPreloader::OnRedirectResult
+ mRedirectChannel = nullptr;
+ if (mChannel) {
+ if (mSuspended) {
+ mChannel->Resume();
+ }
+ mChannel->CancelWithReason(aStatus, aReason);
+ // Clearing mChannel is safe, because this EarlyHintPreloader is not in the
+ // EarlyHintRegistrar after this function call and we won't call
+ // SetHttpChannelFromEarlyHintPreloader nor OnStartRequest on mParent.
+ mChannel = nullptr;
+ SetState(ePreloaderCancelled);
+ }
+ return NS_OK;
+}
+
+void EarlyHintPreloader::OnParentReady(nsIParentChannel* aParent) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aParent);
+ LOG(("EarlyHintPreloader::OnParentReady [this=%p]\n", this));
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(mChannel, "earlyhints-connectback", nullptr);
+ }
+
+ mParent = aParent;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->DeleteEntry(mCpId, mConnectArgs.earlyHintPreloaderId());
+
+ if (mOnStartRequestCalled) {
+ SetParentChannel();
+ InvokeStreamListenerFunctions();
+ }
+}
+
+void EarlyHintPreloader::SetParentChannel() {
+ RefPtr<HttpBaseChannel> channel = do_QueryObject(mChannel);
+ RefPtr<HttpChannelParent> parent = do_QueryObject(mParent);
+ parent->SetHttpChannelFromEarlyHintPreloader(channel);
+}
+
+// Adapted from
+// https://searchfox.org/mozilla-central/rev/b4150d1c6fae0c51c522df2d2c939cf5ad331d4c/netwerk/ipc/DocumentLoadListener.cpp#1311
+void EarlyHintPreloader::InvokeStreamListenerFunctions() {
+ AssertIsOnMainThread();
+ RefPtr<EarlyHintPreloader> self(this);
+
+ LOG((
+ "EarlyHintPreloader::InvokeStreamListenerFunctions [this=%p parent=%p]\n",
+ this, mParent.get()));
+
+ // If we failed to suspend the channel, then we might have received
+ // some messages while the redirected was being handled.
+ // Manually send them on now.
+ if (!mIsFinished) {
+ // This is safe to do, because OnStartRequest/OnStopRequest/OnDataAvailable
+ // are all called on the main thread. They can't be called until we worked
+ // through all functions in the streamListnerFunctions array.
+ mParentListener->SetListenerAfterRedirect(mParent);
+ }
+ nsTArray<StreamListenerFunction> streamListenerFunctions =
+ std::move(mStreamListenerFunctions);
+
+ ForwardStreamListenerFunctions(std::move(streamListenerFunctions), mParent);
+
+ // We don't expect to get new stream listener functions added
+ // via re-entrancy. If this ever happens, we should understand
+ // exactly why before allowing it.
+ NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
+ "Should not have added new stream listener function!");
+
+ if (mChannel && mSuspended) {
+ mChannel->Resume();
+ }
+ mChannel = nullptr;
+ mParent = nullptr;
+ mParentListener = nullptr;
+
+ SetState(ePreloaderUsed);
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(EarlyHintPreloader, nsIRequestObserver, nsIStreamListener,
+ nsIChannelEventSink, nsIInterfaceRequestor,
+ nsIRedirectResultListener, nsIMultiPartChannelListener,
+ nsINamed, nsITimerCallback);
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+// Implementation copied and adapted from DocumentLoadListener::OnStartRequest
+// https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2317-2508
+NS_IMETHODIMP
+EarlyHintPreloader::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("EarlyHintPreloader::OnStartRequest [this=%p]\n", this));
+ AssertIsOnMainThread();
+
+ mOnStartRequestCalled = true;
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (multiPartChannel) {
+ multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
+ } else {
+ mChannel = do_QueryInterface(aRequest);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(mChannel);
+
+ nsresult status = NS_OK;
+ Unused << aRequest->GetStatus(&status);
+
+ if (mParent) {
+ SetParentChannel();
+ mParent->OnStartRequest(aRequest);
+ InvokeStreamListenerFunctions();
+ } else {
+ // Don't suspend the chanel when the channel got cancelled with
+ // CancelChannel, because then OnStopRequest wouldn't get called and we
+ // wouldn't clean up the channel.
+ if (NS_SUCCEEDED(status)) {
+ mChannel->Suspend();
+ mSuspended = true;
+ }
+ mStreamListenerFunctions.AppendElement(
+ AsVariant(OnStartRequestParams{aRequest}));
+ }
+
+ // return error after adding the OnStartRequest forward. The OnStartRequest
+ // failure has to be forwarded to listener, because they called AsyncOpen on
+ // this channel
+ return status;
+}
+
+// Implementation copied from DocumentLoadListener::OnStopRequest
+// https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2510-2528
+NS_IMETHODIMP
+EarlyHintPreloader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ AssertIsOnMainThread();
+ LOG(("EarlyHintPreloader::OnStopRequest [this=%p]\n", this));
+ mStreamListenerFunctions.AppendElement(
+ AsVariant(OnStopRequestParams{aRequest, aStatusCode}));
+
+ // If we're not a multi-part channel, then we're finished and we don't
+ // expect any further events. If we are, then this might be called again,
+ // so wait for OnAfterLastPart instead.
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (!multiPartChannel) {
+ mIsFinished = true;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+// Implementation copied from DocumentLoadListener::OnDataAvailable
+// https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2530-2549
+NS_IMETHODIMP
+EarlyHintPreloader::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ AssertIsOnMainThread();
+ LOG(("EarlyHintPreloader::OnDataAvailable [this=%p]\n", this));
+ // This isn't supposed to happen, since we suspended the channel, but
+ // sometimes Suspend just doesn't work. This can happen when we're routing
+ // through nsUnknownDecoder to sniff the content type, and it doesn't handle
+ // being suspended. Let's just store the data and manually forward it to our
+ // redirected channel when it's ready.
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStreamListenerFunctions.AppendElement(AsVariant(
+ OnDataAvailableParams{aRequest, std::move(data), aOffset, aCount}));
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIMultiPartChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::OnAfterLastPart(nsresult aStatus) {
+ LOG(("EarlyHintPreloader::OnAfterLastPart [this=%p]", this));
+ mStreamListenerFunctions.AppendElement(
+ AsVariant(OnAfterLastPartParams{aStatus}));
+ mIsFinished = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ LOG(("EarlyHintPreloader::AsyncOnChannelRedirect [this=%p]", this));
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ callback->OnRedirectVerifyCallback(rv);
+ return NS_OK;
+ }
+
+ // HTTP request headers are not automatically forwarded to the new channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ rv = httpChannel->SetRequestHeader("X-Moz"_ns, "early hint"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Assign to mChannel after we get notification about success of the
+ // redirect in OnRedirectResult.
+ mRedirectChannel = aNewChannel;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::OnRedirectResult(nsresult aStatus) {
+ LOG(("EarlyHintPreloader::OnRedirectResult [this=%p] aProceeding=0x%" PRIx32,
+ this, static_cast<uint32_t>(aStatus)));
+ if (NS_SUCCEEDED(aStatus) && mRedirectChannel) {
+ mChannel = mRedirectChannel;
+ }
+
+ mRedirectChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsINamed
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::GetName(nsACString& aName) {
+ aName.AssignLiteral("EarlyHintPreloader");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsITimerCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::Notify(nsITimer* timer) {
+ // Death grip, because we will most likely remove the last reference when
+ // deleting us from the EarlyHintRegistrar
+ RefPtr<EarlyHintPreloader> deathGrip(this);
+
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->DeleteEntry(mCpId, mConnectArgs.earlyHintPreloaderId());
+
+ mTimer = nullptr;
+ mRedirectChannel = nullptr;
+ if (mChannel) {
+ if (mSuspended) {
+ mChannel->Resume();
+ }
+ mChannel->CancelWithReason(NS_ERROR_ABORT, "parent-connect-timeout"_ns);
+ mChannel = nullptr;
+ }
+ SetState(ePreloaderTimeout);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIRedirectResultListener*>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext != nullptr) {
+ nsCOMPtr<nsILoadContext> loadContext = mLoadContext;
+ loadContext.forget(aResult);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+void EarlyHintPreloader::CollectResourcesTypeTelemetry(
+ ASDestination aASDestination) {
+ if (aASDestination == ASDestination::DESTINATION_FONT) {
+ glean::netwerk::early_hints.Get("font"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_SCRIPT) {
+ glean::netwerk::early_hints.Get("script"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_STYLE) {
+ glean::netwerk::early_hints.Get("stylesheet"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_IMAGE) {
+ glean::netwerk::early_hints.Get("image"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_FETCH) {
+ glean::netwerk::early_hints.Get("fetch"_ns).Add(1);
+ } else {
+ glean::netwerk::early_hints.Get("other"_ns).Add(1);
+ }
+}
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintPreloader.h b/netwerk/protocol/http/EarlyHintPreloader.h
new file mode 100644
index 0000000000..782e189731
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreloader.h
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_EarlyHintPreloader_h
+#define mozilla_net_EarlyHintPreloader_h
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PreloadHashKey.h"
+#include "NeckoCommon.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsHashtablesFwd.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIRedirectResultListener.h"
+#include "nsIStreamListener.h"
+#include "nsITimer.h"
+#include "nsNetUtil.h"
+
+class nsAttrValue;
+class nsICookieJarSettings;
+class nsILoadContext;
+class nsIPrincipal;
+class nsIReferrerInfo;
+
+namespace mozilla::dom {
+class CanonicalBrowsingContext;
+}
+
+namespace mozilla::net {
+
+class EarlyHintPreloader;
+class EarlyHintConnectArgs;
+class ParentChannelListener;
+struct LinkHeader;
+
+// class keeping track of all ongoing early hints
+class OngoingEarlyHints final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(OngoingEarlyHints)
+
+ OngoingEarlyHints() = default;
+
+ // returns whether a preload with that key already existed
+ bool Contains(const PreloadHashKey& aKey);
+ bool Add(const PreloadHashKey& aKey, RefPtr<EarlyHintPreloader> aPreloader);
+
+ void CancelAll(const nsACString& aReason);
+
+ // registers all channels and returns the ids
+ void RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks);
+
+ private:
+ ~OngoingEarlyHints() = default;
+
+ // We need to do two things requiring two separate variables to keep track of
+ // preloads:
+ // - deduplicate Link headers when starting preloads, therefore we store them
+ // hashset with PreloadHashKey to look up whether we started the preload
+ // already
+ // - pass link headers in order they were received when passing all started
+ // preloads to the content process, therefore we store them in a nsTArray
+ nsTHashSet<PreloadHashKey> mStartedPreloads;
+ nsTArray<RefPtr<EarlyHintPreloader>> mPreloaders;
+};
+
+class EarlyHintPreloader final : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIRedirectResultListener,
+ public nsIInterfaceRequestor,
+ public nsIMultiPartChannelListener,
+ public nsINamed,
+ public nsITimerCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ // required by NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+ NS_DECL_NSITIMERCALLBACK
+
+ public:
+ // Create and insert a preload into OngoingEarlyHints if the same preload
+ // wasn't already issued and the LinkHeader can be parsed correctly.
+ static void MaybeCreateAndInsertPreload(
+ OngoingEarlyHints* aOngoingEarlyHints, const LinkHeader& aHeader,
+ nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ const nsACString& aReferrerPolicy, const nsACString& aCSPHeader,
+ uint64_t aBrowsingContextID,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext,
+ bool aIsModulepreload);
+
+ // register Channel to EarlyHintRegistrar. Returns true and sets connect args
+ // if successful
+ bool Register(dom::ContentParentId aCpId, EarlyHintConnectArgs& aOut);
+
+ // Allows EarlyHintRegistrar to check if the correct content process accesses
+ // this preload. Preventing compromised content processes to access Early Hint
+ // preloads from other origins
+ bool IsFromContentParent(dom::ContentParentId aCpId) const;
+
+ // Should be called by the preloader service when the preload is not
+ // needed after all, because the final response returns a non-2xx status
+ // code. If aDeleteEntry is false, the calling function MUST make sure that
+ // the EarlyHintPreloader is not in the EarlyHintRegistrar anymore. Because
+ // after this function, the EarlyHintPreloader can't connect back to the
+ // parent anymore.
+ nsresult CancelChannel(nsresult aStatus, const nsACString& aReason,
+ bool aDeleteEntry);
+
+ void OnParentReady(nsIParentChannel* aParent);
+
+ private:
+ void SetParentChannel();
+ void InvokeStreamListenerFunctions();
+
+ EarlyHintPreloader();
+ ~EarlyHintPreloader();
+
+ static Maybe<PreloadHashKey> GenerateHashKey(ASDestination aAs, nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ CORSMode corsMode,
+ bool aIsModulepreload);
+
+ static nsSecurityFlags ComputeSecurityFlags(CORSMode aCORSMode,
+ ASDestination aAs);
+
+ // call to start the preload
+ nsresult OpenChannel(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsIReferrerInfo* aReferrerInfo,
+ nsICookieJarSettings* aCookieJarSettings,
+ uint64_t aBrowsingContextID);
+ void PriorizeAsPreload();
+ void SetLinkHeader(const LinkHeader& aLinkHeader);
+
+ static void CollectResourcesTypeTelemetry(ASDestination aASDestination);
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+
+ dom::ContentParentId mCpId;
+ EarlyHintConnectArgs mConnectArgs;
+
+ // Copy behavior from DocumentLoadListener.h:
+ // https://searchfox.org/mozilla-central/rev/c0bed29d643393af6ebe77aa31455f283f169202/netwerk/ipc/DocumentLoadListener.h#487-512
+ // The set of nsIStreamListener functions that got called on this
+ // listener, so that we can replay them onto the replacement channel's
+ // listener. This should generally only be OnStartRequest, since we
+ // Suspend() the channel at that point, but it can fail sometimes
+ // so we have to support holding a list.
+ nsTArray<StreamListenerFunction> mStreamListenerFunctions;
+
+ // Set to true once OnStartRequest is called
+ bool mOnStartRequestCalled = false;
+ // Set to true if we suspended mChannel in the OnStartRequest call
+ bool mSuspended = false;
+ nsCOMPtr<nsIParentChannel> mParent;
+ // Set to true after we've received the last OnStopRequest, and shouldn't
+ // setup a reference from the ParentChannelListener to the replacement
+ // channel.
+ bool mIsFinished = false;
+
+ RefPtr<ParentChannelListener> mParentListener;
+ nsCOMPtr<nsITimer> mTimer;
+
+ // Hold the load context to provide data to web extension and anti tracking.
+ // See Bug 1836289 and Bug 1875268
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ private:
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ enum EHPreloaderState : uint32_t {
+ ePreloaderCreated = 0,
+ ePreloaderOpened,
+ ePreloaderUsed,
+ ePreloaderCancelled,
+ ePreloaderTimeout,
+ };
+ EHPreloaderState mState = ePreloaderCreated;
+ void SetState(EHPreloaderState aState) { mState = aState; }
+};
+
+inline nsISupports* ToSupports(EarlyHintPreloader* aObj) {
+ return static_cast<nsIInterfaceRequestor*>(aObj);
+}
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintPreloader_h
diff --git a/netwerk/protocol/http/EarlyHintRegistrar.cpp b/netwerk/protocol/http/EarlyHintRegistrar.cpp
new file mode 100644
index 0000000000..d77c430819
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintRegistrar.cpp
@@ -0,0 +1,130 @@
+/* -*- 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 "EarlyHintRegistrar.h"
+
+#include "EarlyHintPreloader.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+
+namespace {
+mozilla::StaticRefPtr<mozilla::net::EarlyHintRegistrar> gSingleton;
+} // namespace
+
+namespace mozilla::net {
+
+namespace {
+
+class EHShutdownObserver final : public nsIObserver {
+ public:
+ EHShutdownObserver() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ ~EHShutdownObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(EHShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+EHShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ EarlyHintRegistrar::CleanUp();
+ return NS_OK;
+}
+
+} // namespace
+
+EarlyHintRegistrar::EarlyHintRegistrar() {
+ // EarlyHintRegistrar is a main-thread-only object.
+ // All the operations should be run on main thread.
+ // It should be used on chrome process only.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+EarlyHintRegistrar::~EarlyHintRegistrar() { MOZ_ASSERT(NS_IsMainThread()); }
+
+// static
+void EarlyHintRegistrar::CleanUp() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gSingleton) {
+ return;
+ }
+
+ for (auto& preloader : gSingleton->mEarlyHint) {
+ if (auto p = preloader.GetData()) {
+ // Don't delete entry from EarlyHintPreloader, because that would
+ // invalidate the iterator.
+
+ p->CancelChannel(NS_ERROR_ABORT, "EarlyHintRegistrar::CleanUp"_ns,
+ /* aDeleteEntry */ false);
+ }
+ }
+ gSingleton->mEarlyHint.Clear();
+}
+
+// static
+already_AddRefed<EarlyHintRegistrar> EarlyHintRegistrar::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gSingleton) {
+ gSingleton = new EarlyHintRegistrar();
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIObserver> observer = new EHShutdownObserver();
+ nsresult rv =
+ obs->AddObserver(observer, "profile-change-net-teardown", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ mozilla::ClearOnShutdown(&gSingleton);
+ }
+ return do_AddRef(gSingleton);
+}
+
+void EarlyHintRegistrar::DeleteEntry(dom::ContentParentId aCpId,
+ uint64_t aEarlyHintPreloaderId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<EarlyHintPreloader> ehp = mEarlyHint.Get(aEarlyHintPreloaderId);
+ if (ehp && ehp->IsFromContentParent(aCpId)) {
+ mEarlyHint.Remove(aEarlyHintPreloaderId);
+ }
+}
+
+void EarlyHintRegistrar::RegisterEarlyHint(uint64_t aEarlyHintPreloaderId,
+ EarlyHintPreloader* aEhp) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aEhp);
+
+ mEarlyHint.InsertOrUpdate(aEarlyHintPreloaderId, RefPtr{aEhp});
+}
+
+bool EarlyHintRegistrar::LinkParentChannel(dom::ContentParentId aCpId,
+ uint64_t aEarlyHintPreloaderId,
+ nsIParentChannel* aParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aParent);
+
+ RefPtr<EarlyHintPreloader> ehp;
+ bool found = mEarlyHint.Get(aEarlyHintPreloaderId, getter_AddRefs(ehp));
+ if (ehp && ehp->IsFromContentParent(aCpId)) {
+ ehp->OnParentReady(aParent);
+ }
+ MOZ_ASSERT(ehp || !found);
+ return found;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintRegistrar.h b/netwerk/protocol/http/EarlyHintRegistrar.h
new file mode 100644
index 0000000000..2684301090
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintRegistrar.h
@@ -0,0 +1,82 @@
+/* -*- 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_EarlyHintRegistrar_h__
+#define mozilla_net_EarlyHintRegistrar_h__
+
+#include "mozilla/RefCounted.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/AlreadyAddRefed.h"
+
+class nsIParentChannel;
+
+namespace mozilla::net {
+
+class EarlyHintPreloader;
+
+/*
+ * Registrar for pairing EarlyHintPreloader and HttpChannelParent via
+ * earlyHintPreloaderId. EarlyHintPreloader has to be registered first.
+ * EarlyHintPreloader::OnParentReady will be invoked to notify the
+ * EarlyHintpreloader about the existence of the associated HttpChannelParent.
+ */
+class EarlyHintRegistrar final : public RefCounted<EarlyHintRegistrar> {
+ using EarlyHintHashtable =
+ nsRefPtrHashtable<nsUint64HashKey, EarlyHintPreloader>;
+
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(EarlyHintRegistrar)
+
+ EarlyHintRegistrar();
+ ~EarlyHintRegistrar();
+
+ /*
+ * Store EarlyHintPreloader for the HttpChannelParent to connect back to.
+ *
+ * @param aEarlyHintPreloaderId identifying the EarlyHintPreloader
+ * @param aEhp the EarlyHintPreloader to store
+ */
+ void RegisterEarlyHint(uint64_t aEarlyHintPreloaderId,
+ EarlyHintPreloader* aEhp);
+
+ /*
+ * Link the provided nsIParentChannel with the associated EarlyHintPreloader.
+ *
+ * @param aEarlyHintPreloaderId identifying the EarlyHintPreloader
+ * @param aParent the nsIParentChannel to be paired
+ * @param aChannelId the id of the nsIChannel connecting to the
+ * EarlyHintPreloader.
+ */
+ bool LinkParentChannel(dom::ContentParentId aCpId,
+ uint64_t aEarlyHintPreloaderId,
+ nsIParentChannel* aParent);
+
+ /*
+ * Delete previous stored EarlyHintPreloader
+ *
+ * @param aEarlyHintPreloaderId identifying the EarlyHintPreloader
+ */
+ void DeleteEntry(dom::ContentParentId aCpId, uint64_t aEarlyHintPreloaderId);
+
+ /*
+ * This is called when "profile-change-net-teardown" is observed. We use this
+ * to cancel the ongoing preload and clear the linked EarlyHintPreloader
+ * objects.
+ */
+ static void CleanUp();
+
+ // Singleton accessor
+ static already_AddRefed<EarlyHintRegistrar> GetOrCreate();
+
+ private:
+ // Store unlinked EarlyHintPreloader objects.
+ EarlyHintHashtable mEarlyHint;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintRegistrar_h__
diff --git a/netwerk/protocol/http/EarlyHintsService.cpp b/netwerk/protocol/http/EarlyHintsService.cpp
new file mode 100644
index 0000000000..d8057259b8
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintsService.cpp
@@ -0,0 +1,191 @@
+/* -*- 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 "EarlyHintsService.h"
+#include "EarlyHintPreconnect.h"
+#include "EarlyHintPreloader.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/PreloadHashKey.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsICookieJarSettings.h"
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+
+namespace mozilla::net {
+
+EarlyHintsService::EarlyHintsService()
+ : mOngoingEarlyHints(new OngoingEarlyHints()) {}
+
+// implementing the destructor in the .cpp file to allow EarlyHintsService.h
+// not to include EarlyHintPreloader.h, decoupling the two files and hopefully
+// allow faster compile times
+EarlyHintsService::~EarlyHintsService() = default;
+
+void EarlyHintsService::EarlyHint(
+ const nsACString& aLinkHeader, nsIURI* aBaseURI, nsIChannel* aChannel,
+ const nsACString& aReferrerPolicy, const nsACString& aCSPHeader,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext) {
+ mEarlyHintsCount++;
+ if (mFirstEarlyHint.isNothing()) {
+ mFirstEarlyHint.emplace(TimeStamp::NowLoRes());
+ } else {
+ // Only allow one early hint response with link headers. See
+ // https://html.spec.whatwg.org/multipage/semantics.html#early-hints
+ // > Note: Only the first early hint response served during the navigation
+ // > is handled, and it is discarded if it is succeeded by a cross-origin
+ // > redirect.
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ // We only follow Early Hints sent on the main document. Make sure that we got
+ // the main document channel here.
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ MOZ_ASSERT(false, "Early Hint on non-document channel");
+ return;
+ }
+ nsCOMPtr<nsIPrincipal> principal;
+ // We want to set the top-level document as the triggeringPrincipal for the
+ // load of the sub-resources (image, font, fetch, script, style, fetch and in
+ // the future maybe more). We can't use the `triggeringPrincipal` of the main
+ // document channel, because it is the `systemPrincipal` for user initiated
+ // loads. Same for the `LoadInfo::FindPrincipalToInherit(aChannel)`.
+ //
+ // On 3xx redirects of the main document to cross site locations, all Early
+ // Hint preloads get cancelled as specified in the whatwg spec:
+ //
+ // Note: Only the first early hint response served during the navigation is
+ // handled, and it is discarded if it is succeeded by a cross-origin
+ // redirect. [1]
+ //
+ // Therefore the channel doesn't need to change the principal for any reason
+ // and has the correct principal for the whole lifetime.
+ //
+ // [1]: https://html.spec.whatwg.org/multipage/semantics.html#early-hints
+ nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ aChannel, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ if (NS_FAILED(
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)))) {
+ return;
+ }
+
+ // TODO: find out why LinkHeaderParser uses utf16 and check if it can be
+ // changed to utf8
+ auto linkHeaders = ParseLinkHeader(NS_ConvertUTF8toUTF16(aLinkHeader));
+
+ for (auto& linkHeader : linkHeaders) {
+ CollectLinkTypeTelemetry(linkHeader.mRel);
+ if (linkHeader.mRel.LowerCaseEqualsLiteral("preconnect")) {
+ mLinkType |= dom::LinkStyle::ePRECONNECT;
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ aChannel, originAttributes);
+ EarlyHintPreconnect::MaybePreconnect(linkHeader, aBaseURI,
+ std::move(originAttributes));
+ } else if (linkHeader.mRel.LowerCaseEqualsLiteral("preload")) {
+ mLinkType |= dom::LinkStyle::ePRELOAD;
+ EarlyHintPreloader::MaybeCreateAndInsertPreload(
+ mOngoingEarlyHints, linkHeader, aBaseURI, principal,
+ cookieJarSettings, aReferrerPolicy, aCSPHeader,
+ loadInfo->GetBrowsingContextID(), aLoadingBrowsingContext, false);
+ } else if (linkHeader.mRel.LowerCaseEqualsLiteral("modulepreload")) {
+ mLinkType |= dom::LinkStyle::eMODULE_PRELOAD;
+ EarlyHintPreloader::MaybeCreateAndInsertPreload(
+ mOngoingEarlyHints, linkHeader, aBaseURI, principal,
+ cookieJarSettings, aReferrerPolicy, aCSPHeader,
+ loadInfo->GetBrowsingContextID(), aLoadingBrowsingContext, true);
+ }
+ }
+}
+
+void EarlyHintsService::FinalResponse(uint32_t aResponseStatus) {
+ // We will collect telemetry mosly once for a document.
+ // In case of a reddirect this will be called multiple times.
+ CollectTelemetry(Some(aResponseStatus));
+}
+
+void EarlyHintsService::Cancel(const nsACString& aReason) {
+ CollectTelemetry(Nothing());
+ mOngoingEarlyHints->CancelAll(aReason);
+}
+
+void EarlyHintsService::RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) {
+ mOngoingEarlyHints->RegisterLinksAndGetConnectArgs(aCpId, aOutLinks);
+}
+
+void EarlyHintsService::CollectTelemetry(Maybe<uint32_t> aResponseStatus) {
+ // EH_NUM_OF_HINTS_PER_PAGE is only collected for the 2xx responses,
+ // regardless of the number of received mEarlyHintsCount.
+ // Other telemetry probes are only collected if there was at least one
+ // EarlyHins response.
+ if (aResponseStatus && (*aResponseStatus <= 299)) {
+ Telemetry::Accumulate(Telemetry::EH_NUM_OF_HINTS_PER_PAGE,
+ mEarlyHintsCount);
+ }
+ if (mEarlyHintsCount == 0) {
+ return;
+ }
+
+ Telemetry::LABELS_EH_FINAL_RESPONSE label =
+ Telemetry::LABELS_EH_FINAL_RESPONSE::Cancel;
+ if (aResponseStatus) {
+ if (*aResponseStatus <= 299) {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::R2xx;
+
+ MOZ_ASSERT(mFirstEarlyHint);
+ Telemetry::AccumulateTimeDelta(Telemetry::EH_TIME_TO_FINAL_RESPONSE,
+ *mFirstEarlyHint, TimeStamp::NowLoRes());
+ } else if (*aResponseStatus <= 399) {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::R3xx;
+ } else if (*aResponseStatus <= 499) {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::R4xx;
+ } else {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::Other;
+ }
+ }
+
+ Telemetry::AccumulateCategorical(label);
+
+ // Reset telemetry counters and timestamps.
+ mEarlyHintsCount = 0;
+ mFirstEarlyHint = Nothing();
+}
+
+void EarlyHintsService::CollectLinkTypeTelemetry(const nsAString& aRel) {
+ if (aRel.LowerCaseEqualsLiteral("dns-prefetch")) {
+ glean::netwerk::eh_link_type.Get("dns-prefetch"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("icon")) {
+ glean::netwerk::eh_link_type.Get("icon"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("modulepreload")) {
+ glean::netwerk::eh_link_type.Get("modulepreload"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("preconnect")) {
+ glean::netwerk::eh_link_type.Get("preconnect"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("prefetch")) {
+ glean::netwerk::eh_link_type.Get("prefetch"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("preload")) {
+ glean::netwerk::eh_link_type.Get("preload"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("prerender")) {
+ glean::netwerk::eh_link_type.Get("prerender"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("stylesheet")) {
+ glean::netwerk::eh_link_type.Get("stylesheet"_ns).Add(1);
+ } else {
+ glean::netwerk::eh_link_type.Get("other"_ns).Add(1);
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintsService.h b/netwerk/protocol/http/EarlyHintsService.h
new file mode 100644
index 0000000000..b256759add
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintsService.h
@@ -0,0 +1,59 @@
+/* -*- 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_EarlyHintsService_h
+#define mozilla_net_EarlyHintsService_h
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+class nsIChannel;
+class nsIURI;
+class nsIInterfaceRequestor;
+namespace mozilla::dom {
+class CanonicalBrowsingContext;
+}
+
+namespace mozilla::net {
+
+class EarlyHintConnectArgs;
+class OngoingEarlyHints;
+
+class EarlyHintsService {
+ public:
+ EarlyHintsService();
+ ~EarlyHintsService();
+ void EarlyHint(const nsACString& aLinkHeader, nsIURI* aBaseURI,
+ nsIChannel* aChannel, const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext);
+ void FinalResponse(uint32_t aResponseStatus);
+ void Cancel(const nsACString& aReason);
+
+ void RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks);
+
+ uint32_t LinkType() const { return mLinkType; }
+
+ private:
+ void CollectTelemetry(Maybe<uint32_t> aResponseStatus);
+ void CollectLinkTypeTelemetry(const nsAString& aRel);
+
+ Maybe<TimeStamp> mFirstEarlyHint;
+ uint32_t mEarlyHintsCount{0};
+
+ RefPtr<OngoingEarlyHints> mOngoingEarlyHints;
+ uint32_t mLinkType = 0;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintsService_h
diff --git a/netwerk/protocol/http/HPKEConfigManager.sys.mjs b/netwerk/protocol/http/HPKEConfigManager.sys.mjs
new file mode 100644
index 0000000000..b08cc892c5
--- /dev/null
+++ b/netwerk/protocol/http/HPKEConfigManager.sys.mjs
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let knownConfigs = new Map();
+
+export class HPKEConfigManager {
+ static async get(aURL, aOptions = {}) {
+ try {
+ let config = await this.#getInternal(aURL, aOptions);
+ return new Uint8Array(config);
+ } catch (ex) {
+ console.error(ex);
+ return null;
+ }
+ }
+
+ static async #getInternal(aURL, aOptions = {}) {
+ let { maxAge = -1 } = aOptions;
+ let knownConfig = knownConfigs.get(aURL);
+ if (
+ knownConfig &&
+ (maxAge < 0 || Date.now() - knownConfig.fetchDate < maxAge)
+ ) {
+ return knownConfig.config;
+ }
+ return this.fetchAndStore(aURL, aOptions);
+ }
+
+ static async fetchAndStore(aURL, aOptions = {}) {
+ let fetchDate = Date.now();
+ let resp = await fetch(aURL, { signal: aOptions.abortSignal });
+ if (!resp?.ok) {
+ throw new Error(
+ `Fetching HPKE config from ${aURL} failed with error ${resp.status}`
+ );
+ }
+ let config = await resp.blob().then(b => b.arrayBuffer());
+ knownConfigs.set(aURL, { config, fetchDate });
+ return config;
+ }
+}
diff --git a/netwerk/protocol/http/HTTPSRecordResolver.cpp b/netwerk/protocol/http/HTTPSRecordResolver.cpp
new file mode 100644
index 0000000000..79fd92df7f
--- /dev/null
+++ b/netwerk/protocol/http/HTTPSRecordResolver.cpp
@@ -0,0 +1,117 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HTTPSRecordResolver.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSAdditionalInfo.h"
+#include "nsIDNSService.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsNetCID.h"
+#include "nsAHttpTransaction.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(HTTPSRecordResolver, nsIDNSListener)
+
+HTTPSRecordResolver::HTTPSRecordResolver(nsAHttpTransaction* aTransaction)
+ : mTransaction(aTransaction),
+ mConnInfo(aTransaction->ConnectionInfo()),
+ mCaps(aTransaction->Caps()) {}
+
+HTTPSRecordResolver::~HTTPSRecordResolver() = default;
+
+nsresult HTTPSRecordResolver::FetchHTTPSRRInternal(
+ nsIEventTarget* aTarget, nsICancelable** aDNSRequest) {
+ NS_ENSURE_ARG_POINTER(aTarget);
+
+ // Only fetch HTTPS RR for https.
+ if (!mConnInfo->FirstHopSSL()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsIDNSService::DNSFlags flags =
+ nsIDNSService::GetFlagsFromTRRMode(mConnInfo->GetTRRMode());
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ flags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+
+ nsCOMPtr<nsIDNSAdditionalInfo> info;
+ if (mConnInfo->OriginPort() != NS_HTTPS_DEFAULT_PORT) {
+ dns->NewAdditionalInfo(""_ns, mConnInfo->OriginPort(),
+ getter_AddRefs(info));
+ }
+ return dns->AsyncResolveNative(
+ mConnInfo->GetOrigin(), nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags, info,
+ this, aTarget, mConnInfo->GetOriginAttributes(), aDNSRequest);
+}
+
+NS_IMETHODIMP HTTPSRecordResolver::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord,
+ nsresult aStatus) {
+ nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface(aRecord);
+ // This will be called again when receving the result of speculatively loading
+ // the addr records of the target name. In this case, just return NS_OK.
+ if (addrRecord) {
+ return NS_OK;
+ }
+
+ if (!mTransaction) {
+ // The connecttion is not interesed in a response anymore.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = do_QueryInterface(aRecord);
+ if (!record || NS_FAILED(aStatus)) {
+ return mTransaction->OnHTTPSRRAvailable(nullptr, nullptr);
+ }
+
+ nsCOMPtr<nsISVCBRecord> svcbRecord;
+ if (NS_FAILED(record->GetServiceModeRecord(mCaps & NS_HTTP_DISALLOW_SPDY,
+ mCaps & NS_HTTP_DISALLOW_HTTP3,
+ getter_AddRefs(svcbRecord)))) {
+ return mTransaction->OnHTTPSRRAvailable(record, nullptr);
+ }
+
+ return mTransaction->OnHTTPSRRAvailable(record, svcbRecord);
+}
+
+void HTTPSRecordResolver::PrefetchAddrRecord(const nsACString& aTargetName,
+ bool aRefreshDNS) {
+ MOZ_ASSERT(mTransaction);
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) {
+ return;
+ }
+
+ nsIDNSService::DNSFlags flags = nsIDNSService::GetFlagsFromTRRMode(
+ mTransaction->ConnectionInfo()->GetTRRMode());
+ if (aRefreshDNS) {
+ flags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ Unused << dns->AsyncResolveNative(
+ aTargetName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, this,
+ GetCurrentSerialEventTarget(),
+ mTransaction->ConnectionInfo()->GetOriginAttributes(),
+ getter_AddRefs(tmpOutstanding));
+}
+
+void HTTPSRecordResolver::Close() { mTransaction = nullptr; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HTTPSRecordResolver.h b/netwerk/protocol/http/HTTPSRecordResolver.h
new file mode 100644
index 0000000000..2aa5ae55d7
--- /dev/null
+++ b/netwerk/protocol/http/HTTPSRecordResolver.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; 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 HTTPSRecordResolver_h__
+#define HTTPSRecordResolver_h__
+
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsHttpConnectionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class nsAHttpTransaction;
+
+// This class is the place to put some common code about fetching HTTPS RR.
+class HTTPSRecordResolver : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ explicit HTTPSRecordResolver(nsAHttpTransaction* aTransaction);
+ nsresult FetchHTTPSRRInternal(nsIEventTarget* aTarget,
+ nsICancelable** aDNSRequest);
+ void PrefetchAddrRecord(const nsACString& aTargetName, bool aRefreshDNS);
+
+ void Close();
+
+ protected:
+ virtual ~HTTPSRecordResolver();
+
+ private:
+ RefPtr<nsAHttpTransaction> mTransaction;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ uint32_t mCaps;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HTTPSRecordResolver_h__
diff --git a/netwerk/protocol/http/Http2Compression.cpp b/netwerk/protocol/http/Http2Compression.cpp
new file mode 100644
index 0000000000..b95883f13f
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.cpp
@@ -0,0 +1,1458 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "Http2Compression.h"
+#include "Http2HuffmanIncoming.h"
+#include "Http2HuffmanOutgoing.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsIMemoryReporter.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+static nsDeque<nvPair>* gStaticHeaders = nullptr;
+
+class HpackStaticTableReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HpackStaticTableReporter() = default;
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ MOZ_COLLECT_REPORT("explicit/network/hpack/static-table", KIND_HEAP,
+ UNITS_BYTES,
+ gStaticHeaders->SizeOfIncludingThis(MallocSizeOf),
+ "Memory usage of HPACK static table.");
+
+ return NS_OK;
+ }
+
+ private:
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~HpackStaticTableReporter() = default;
+};
+
+NS_IMPL_ISUPPORTS(HpackStaticTableReporter, nsIMemoryReporter)
+
+class HpackDynamicTableReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit HpackDynamicTableReporter(Http2BaseCompressor* aCompressor)
+ : mCompressor(aCompressor) {}
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ MutexAutoLock lock(mMutex);
+ if (mCompressor) {
+ MOZ_COLLECT_REPORT("explicit/network/hpack/dynamic-tables", KIND_HEAP,
+ UNITS_BYTES,
+ mCompressor->SizeOfExcludingThis(MallocSizeOf),
+ "Aggregate memory usage of HPACK dynamic tables.");
+ }
+ return NS_OK;
+ }
+
+ private:
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~HpackDynamicTableReporter() = default;
+
+ Mutex mMutex{"HpackDynamicTableReporter"};
+ Http2BaseCompressor* mCompressor MOZ_GUARDED_BY(mMutex);
+
+ friend class Http2BaseCompressor;
+};
+
+NS_IMPL_ISUPPORTS(HpackDynamicTableReporter, nsIMemoryReporter)
+
+StaticRefPtr<HpackStaticTableReporter> gStaticReporter;
+
+void Http2CompressionCleanup() {
+ // this happens after the socket thread has been destroyed
+ delete gStaticHeaders;
+ gStaticHeaders = nullptr;
+ UnregisterStrongMemoryReporter(gStaticReporter);
+ gStaticReporter = nullptr;
+}
+
+static void AddStaticElement(const nsCString& name, const nsCString& value) {
+ nvPair* pair = new nvPair(name, value);
+ gStaticHeaders->Push(pair);
+}
+
+static void AddStaticElement(const nsCString& name) {
+ AddStaticElement(name, ""_ns);
+}
+
+static void InitializeStaticHeaders() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!gStaticHeaders) {
+ gStaticHeaders = new nsDeque<nvPair>();
+ gStaticReporter = new HpackStaticTableReporter();
+ RegisterStrongMemoryReporter(gStaticReporter);
+ AddStaticElement(":authority"_ns);
+ AddStaticElement(":method"_ns, "GET"_ns);
+ AddStaticElement(":method"_ns, "POST"_ns);
+ AddStaticElement(":path"_ns, "/"_ns);
+ AddStaticElement(":path"_ns, "/index.html"_ns);
+ AddStaticElement(":scheme"_ns, "http"_ns);
+ AddStaticElement(":scheme"_ns, "https"_ns);
+ AddStaticElement(":status"_ns, "200"_ns);
+ AddStaticElement(":status"_ns, "204"_ns);
+ AddStaticElement(":status"_ns, "206"_ns);
+ AddStaticElement(":status"_ns, "304"_ns);
+ AddStaticElement(":status"_ns, "400"_ns);
+ AddStaticElement(":status"_ns, "404"_ns);
+ AddStaticElement(":status"_ns, "500"_ns);
+ AddStaticElement("accept-charset"_ns);
+ AddStaticElement("accept-encoding"_ns, "gzip, deflate"_ns);
+ AddStaticElement("accept-language"_ns);
+ AddStaticElement("accept-ranges"_ns);
+ AddStaticElement("accept"_ns);
+ AddStaticElement("access-control-allow-origin"_ns);
+ AddStaticElement("age"_ns);
+ AddStaticElement("allow"_ns);
+ AddStaticElement("authorization"_ns);
+ AddStaticElement("cache-control"_ns);
+ AddStaticElement("content-disposition"_ns);
+ AddStaticElement("content-encoding"_ns);
+ AddStaticElement("content-language"_ns);
+ AddStaticElement("content-length"_ns);
+ AddStaticElement("content-location"_ns);
+ AddStaticElement("content-range"_ns);
+ AddStaticElement("content-type"_ns);
+ AddStaticElement("cookie"_ns);
+ AddStaticElement("date"_ns);
+ AddStaticElement("etag"_ns);
+ AddStaticElement("expect"_ns);
+ AddStaticElement("expires"_ns);
+ AddStaticElement("from"_ns);
+ AddStaticElement("host"_ns);
+ AddStaticElement("if-match"_ns);
+ AddStaticElement("if-modified-since"_ns);
+ AddStaticElement("if-none-match"_ns);
+ AddStaticElement("if-range"_ns);
+ AddStaticElement("if-unmodified-since"_ns);
+ AddStaticElement("last-modified"_ns);
+ AddStaticElement("link"_ns);
+ AddStaticElement("location"_ns);
+ AddStaticElement("max-forwards"_ns);
+ AddStaticElement("proxy-authenticate"_ns);
+ AddStaticElement("proxy-authorization"_ns);
+ AddStaticElement("range"_ns);
+ AddStaticElement("referer"_ns);
+ AddStaticElement("refresh"_ns);
+ AddStaticElement("retry-after"_ns);
+ AddStaticElement("server"_ns);
+ AddStaticElement("set-cookie"_ns);
+ AddStaticElement("strict-transport-security"_ns);
+ AddStaticElement("transfer-encoding"_ns);
+ AddStaticElement("user-agent"_ns);
+ AddStaticElement("vary"_ns);
+ AddStaticElement("via"_ns);
+ AddStaticElement("www-authenticate"_ns);
+ }
+}
+
+size_t nvPair::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t nvPair::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+nvFIFO::nvFIFO() { InitializeStaticHeaders(); }
+
+nvFIFO::~nvFIFO() { Clear(); }
+
+void nvFIFO::AddElement(const nsCString& name, const nsCString& value) {
+ nvPair* pair = new nvPair(name, value);
+ mByteCount += pair->Size();
+ MutexAutoLock lock(mMutex);
+ mTable.PushFront(pair);
+}
+
+void nvFIFO::AddElement(const nsCString& name) { AddElement(name, ""_ns); }
+
+void nvFIFO::RemoveElement() {
+ nvPair* pair = nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ pair = mTable.Pop();
+ }
+ if (pair) {
+ mByteCount -= pair->Size();
+ delete pair;
+ }
+}
+
+uint32_t nvFIFO::ByteCount() const { return mByteCount; }
+
+uint32_t nvFIFO::Length() const {
+ return mTable.GetSize() + gStaticHeaders->GetSize();
+}
+
+uint32_t nvFIFO::VariableLength() const { return mTable.GetSize(); }
+
+size_t nvFIFO::StaticLength() const { return gStaticHeaders->GetSize(); }
+
+void nvFIFO::Clear() {
+ mByteCount = 0;
+ MutexAutoLock lock(mMutex);
+ while (mTable.GetSize()) {
+ delete mTable.Pop();
+ }
+}
+
+const nvPair* nvFIFO::operator[](size_t index) const {
+ // NWGH - ensure index > 0
+ // NWGH - subtract 1 from index here
+ if (index >= (mTable.GetSize() + gStaticHeaders->GetSize())) {
+ MOZ_ASSERT(false);
+ NS_WARNING("nvFIFO Table Out of Range");
+ return nullptr;
+ }
+ if (index >= gStaticHeaders->GetSize()) {
+ return mTable.ObjectAt(index - gStaticHeaders->GetSize());
+ }
+ return gStaticHeaders->ObjectAt(index);
+}
+
+Http2BaseCompressor::Http2BaseCompressor() {
+ mDynamicReporter = new HpackDynamicTableReporter(this);
+ RegisterStrongMemoryReporter(mDynamicReporter);
+}
+
+Http2BaseCompressor::~Http2BaseCompressor() {
+ if (mPeakSize) {
+ Telemetry::Accumulate(mPeakSizeID, mPeakSize);
+ }
+ if (mPeakCount) {
+ Telemetry::Accumulate(mPeakCountID, mPeakCount);
+ }
+ UnregisterStrongMemoryReporter(mDynamicReporter);
+ {
+ MutexAutoLock lock(mDynamicReporter->mMutex);
+ mDynamicReporter->mCompressor = nullptr;
+ }
+ mDynamicReporter = nullptr;
+}
+
+size_t nvFIFO::SizeOfDynamicTable(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = 0;
+ MutexAutoLock lock(mMutex);
+ for (const auto elem : mTable) {
+ size += elem->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return size;
+}
+
+void Http2BaseCompressor::ClearHeaderTable() { mHeaderTable.Clear(); }
+
+size_t Http2BaseCompressor::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mHeaderTable.SizeOfDynamicTable(aMallocSizeOf);
+}
+
+void Http2BaseCompressor::MakeRoom(uint32_t amount, const char* direction) {
+ uint32_t countEvicted = 0;
+ uint32_t bytesEvicted = 0;
+
+ // make room in the header table
+ while (mHeaderTable.VariableLength() &&
+ ((mHeaderTable.ByteCount() + amount) > mMaxBuffer)) {
+ // NWGH - remove the "- 1" here
+ uint32_t index = mHeaderTable.Length() - 1;
+ LOG(("HTTP %s header table index %u %s %s removed for size.\n", direction,
+ index, mHeaderTable[index]->mName.get(),
+ mHeaderTable[index]->mValue.get()));
+ ++countEvicted;
+ bytesEvicted += mHeaderTable[index]->Size();
+ mHeaderTable.RemoveElement();
+ }
+
+ if (!strcmp(direction, "decompressor")) {
+ Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_DECOMPRESSOR,
+ countEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_DECOMPRESSOR,
+ bytesEvicted);
+ Telemetry::Accumulate(
+ Telemetry::HPACK_BYTES_EVICTED_RATIO_DECOMPRESSOR,
+ (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
+ } else {
+ Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_COMPRESSOR,
+ countEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_COMPRESSOR,
+ bytesEvicted);
+ Telemetry::Accumulate(
+ Telemetry::HPACK_BYTES_EVICTED_RATIO_COMPRESSOR,
+ (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
+ }
+}
+
+void Http2BaseCompressor::DumpState(const char* preamble) {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ if (!mDumpTables) {
+ return;
+ }
+
+ LOG(("%s", preamble));
+
+ LOG(("Header Table"));
+ uint32_t i;
+ uint32_t length = mHeaderTable.Length();
+ uint32_t staticLength = mHeaderTable.StaticLength();
+ // NWGH - make i = 1; i <= length; ++i
+ for (i = 0; i < length; ++i) {
+ const nvPair* pair = mHeaderTable[i];
+ // NWGH - make this <= staticLength
+ LOG(("%sindex %u: %s %s", i < staticLength ? "static " : "", i,
+ pair->mName.get(), pair->mValue.get()));
+ }
+}
+
+void Http2BaseCompressor::SetMaxBufferSizeInternal(uint32_t maxBufferSize) {
+ MOZ_ASSERT(maxBufferSize <= mMaxBufferSetting);
+
+ LOG(("Http2BaseCompressor::SetMaxBufferSizeInternal %u called",
+ maxBufferSize));
+
+ while (mHeaderTable.VariableLength() &&
+ (mHeaderTable.ByteCount() > maxBufferSize)) {
+ mHeaderTable.RemoveElement();
+ }
+
+ mMaxBuffer = maxBufferSize;
+}
+
+nsresult Http2BaseCompressor::SetInitialMaxBufferSize(uint32_t maxBufferSize) {
+ MOZ_ASSERT(mSetInitialMaxBufferSizeAllowed);
+
+ if (mSetInitialMaxBufferSizeAllowed) {
+ mMaxBufferSetting = maxBufferSize;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+void Http2BaseCompressor::SetDumpTables(bool dumpTables) {
+ mDumpTables = dumpTables;
+}
+
+nsresult Http2Decompressor::DecodeHeaderBlock(const uint8_t* data,
+ uint32_t datalen,
+ nsACString& output, bool isPush) {
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOffset = 0;
+ mData = data;
+ mDataLen = datalen;
+ mOutput = &output;
+ // Add in some space to hopefully not have to reallocate while decompressing
+ // the headers. 512 bytes seems like a good enough number.
+ mOutput->Truncate();
+ mOutput->SetCapacity(datalen + 512);
+ mHeaderStatus.Truncate();
+ mHeaderHost.Truncate();
+ mHeaderScheme.Truncate();
+ mHeaderPath.Truncate();
+ mHeaderMethod.Truncate();
+ mSeenNonColonHeader = false;
+ mIsPush = isPush;
+
+ nsresult rv = NS_OK;
+ nsresult softfail_rv = NS_OK;
+ while (NS_SUCCEEDED(rv) && (mOffset < mDataLen)) {
+ bool modifiesTable = true;
+ const char* preamble = "Decompressor state after ?";
+ if (mData[mOffset] & 0x80) {
+ rv = DoIndexed();
+ preamble = "Decompressor state after indexed";
+ } else if (mData[mOffset] & 0x40) {
+ rv = DoLiteralWithIncremental();
+ preamble = "Decompressor state after literal with incremental";
+ } else if (mData[mOffset] & 0x20) {
+ rv = DoContextUpdate();
+ preamble = "Decompressor state after context update";
+ } else if (mData[mOffset] & 0x10) {
+ modifiesTable = false;
+ rv = DoLiteralNeverIndexed();
+ preamble = "Decompressor state after literal never index";
+ } else {
+ modifiesTable = false;
+ rv = DoLiteralWithoutIndex();
+ preamble = "Decompressor state after literal without index";
+ }
+ DumpState(preamble);
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ if (modifiesTable) {
+ // Unfortunately, we can't count on our peer now having the same state
+ // as us, so let's terminate the session and we can try again later.
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is an http-level error that we can handle by resetting the stream
+ // in the upper layers. Let's note that we saw this, then continue
+ // decompressing until we either hit the end of the header block or find a
+ // hard failure. That way we won't get an inconsistent compression state
+ // with the server.
+ softfail_rv = rv;
+ rv = NS_OK;
+ } else if (rv == NS_ERROR_NET_RESET) {
+ // This happens when we detect connection-based auth being requested in
+ // the response headers. We'll paper over it for now, and the session will
+ // handle this as if it received RST_STREAM with HTTP_1_1_REQUIRED.
+ softfail_rv = rv;
+ rv = NS_OK;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return softfail_rv;
+}
+
+nsresult Http2Decompressor::DecodeInteger(uint32_t prefixLen, uint32_t& accum) {
+ accum = 0;
+
+ if (prefixLen) {
+ uint32_t mask = (1 << prefixLen) - 1;
+
+ accum = mData[mOffset] & mask;
+ ++mOffset;
+
+ if (accum != mask) {
+ // the simple case for small values
+ return NS_OK;
+ }
+ }
+
+ uint32_t factor = 1; // 128 ^ 0
+
+ // we need a series of bytes. The high bit signifies if we need another one.
+ // The first one is a a factor of 128 ^ 0, the next 128 ^1, the next 128 ^2,
+ // ..
+
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Ran out of data to decode integer");
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+ bool chainBit = mData[mOffset] & 0x80;
+ accum += (mData[mOffset] & 0x7f) * factor;
+
+ ++mOffset;
+ factor = factor * 128;
+
+ while (chainBit) {
+ // really big offsets are just trawling for overflows
+ if (accum >= 0x800000) {
+ NS_WARNING("Decoding integer >= 0x800000");
+ // This is not strictly fatal to the session, but given the fact that
+ // the value is way to large to be reasonable, let's just tell our peer
+ // to go away.
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Ran out of data to decode integer");
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+ chainBit = mData[mOffset] & 0x80;
+ accum += (mData[mOffset] & 0x7f) * factor;
+ ++mOffset;
+ factor = factor * 128;
+ }
+ return NS_OK;
+}
+
+static bool HasConnectionBasedAuth(const nsACString& headerValue) {
+ for (const nsACString& authMethod :
+ nsCCharSeparatedTokenizer(headerValue, '\n').ToRange()) {
+ if (authMethod.LowerCaseEqualsLiteral("ntlm")) {
+ return true;
+ }
+ if (authMethod.LowerCaseEqualsLiteral("negotiate")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult Http2Decompressor::OutputHeader(const nsACString& name,
+ const nsACString& value) {
+ // exclusions
+ if (!mIsPush &&
+ (name.EqualsLiteral("connection") || name.EqualsLiteral("host") ||
+ name.EqualsLiteral("keep-alive") ||
+ name.EqualsLiteral("proxy-connection") || name.EqualsLiteral("te") ||
+ name.EqualsLiteral("transfer-encoding") ||
+ name.EqualsLiteral("upgrade") || name.Equals(("accept-encoding")))) {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor illegal response header found, not gatewaying: %s",
+ toLog.get()));
+ return NS_OK;
+ }
+
+ // Bug 1663836: reject invalid HTTP response header names - RFC7540 Sec 10.3
+ const char* cFirst = name.BeginReading();
+ if (cFirst != nullptr && *cFirst == ':') {
+ ++cFirst;
+ }
+ if (!nsHttp::IsValidToken(cFirst, name.EndReading())) {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor invalid response header found. [%s]\n",
+ toLog.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Look for upper case characters in the name.
+ for (const char* cPtr = name.BeginReading(); cPtr && cPtr < name.EndReading();
+ ++cPtr) {
+ if (*cPtr <= 'Z' && *cPtr >= 'A') {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor upper case response header found. [%s]\n",
+ toLog.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ // Look for CR, LF or NUL in value - could be smuggling (RFC7540 Sec 10.3)
+ // treat as malformed
+ if (!nsHttp::IsReasonableHeaderValue(value)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Status comes first
+ if (name.EqualsLiteral(":status")) {
+ nsAutoCString status("HTTP/2 "_ns);
+ status.Append(value);
+ status.AppendLiteral("\r\n");
+ mOutput->Insert(status, 0);
+ mHeaderStatus = value;
+ } else if (name.EqualsLiteral(":authority")) {
+ mHeaderHost = value;
+ } else if (name.EqualsLiteral(":scheme")) {
+ mHeaderScheme = value;
+ } else if (name.EqualsLiteral(":path")) {
+ mHeaderPath = value;
+ } else if (name.EqualsLiteral(":method")) {
+ mHeaderMethod = value;
+ }
+
+ // http/2 transport level headers shouldn't be gatewayed into http/1
+ bool isColonHeader = false;
+ for (const char* cPtr = name.BeginReading(); cPtr && cPtr < name.EndReading();
+ ++cPtr) {
+ if (*cPtr == ':') {
+ isColonHeader = true;
+ break;
+ }
+ if (*cPtr != ' ' && *cPtr != '\t') {
+ isColonHeader = false;
+ break;
+ }
+ }
+
+ if (isColonHeader) {
+ // :status is the only pseudo-header field allowed in received HEADERS
+ // frames, PUSH_PROMISE allows the other pseudo-header fields
+ if (!name.EqualsLiteral(":status") && !mIsPush) {
+ LOG(("HTTP Decompressor found illegal response pseudo-header %s",
+ name.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (mSeenNonColonHeader) {
+ LOG(("HTTP Decompressor found illegal : header %s", name.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ LOG(("HTTP Decompressor not gatewaying %s into http/1",
+ name.BeginReading()));
+ return NS_OK;
+ }
+
+ LOG(("Http2Decompressor::OutputHeader %s %s", name.BeginReading(),
+ value.BeginReading()));
+ mSeenNonColonHeader = true;
+ mOutput->Append(name);
+ mOutput->AppendLiteral(": ");
+ mOutput->Append(value);
+ mOutput->AppendLiteral("\r\n");
+
+ // Need to check if the server is going to try to speak connection-based auth
+ // with us. If so, we need to kill this via h2, and dial back with http/1.1.
+ // Technically speaking, the server should've just reset or goaway'd us with
+ // HTTP_1_1_REQUIRED, but there are some busted servers out there, so we need
+ // to check on our own to work around them.
+ if (name.EqualsLiteral("www-authenticate") ||
+ name.EqualsLiteral("proxy-authenticate")) {
+ if (HasConnectionBasedAuth(value)) {
+ LOG3(("Http2Decompressor %p connection-based auth found in %s", this,
+ name.BeginReading()));
+ return NS_ERROR_NET_RESET;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::OutputHeader(uint32_t index) {
+ // NWGH - make this < index
+ // bounds check
+ if (mHeaderTable.Length() <= index) {
+ LOG(("Http2Decompressor::OutputHeader index too large %u", index));
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ return OutputHeader(mHeaderTable[index]->mName, mHeaderTable[index]->mValue);
+}
+
+nsresult Http2Decompressor::CopyHeaderString(uint32_t index, nsACString& name) {
+ // NWGH - make this < index
+ // bounds check
+ if (mHeaderTable.Length() <= index) {
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ name = mHeaderTable[index]->mName;
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::CopyStringFromInput(uint32_t bytes,
+ nsACString& val) {
+ if (mOffset + bytes > mDataLen) {
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ val.Assign(reinterpret_cast<const char*>(mData) + mOffset, bytes);
+ mOffset += bytes;
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::DecodeFinalHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint8_t& bitsLeft) {
+ MOZ_ASSERT(mOffset <= mDataLen);
+ if (mOffset > mDataLen) {
+ NS_WARNING("DecodeFinalHuffmanCharacter would read beyond end of buffer");
+ return NS_ERROR_FAILURE;
+ }
+ uint8_t mask = (1 << bitsLeft) - 1;
+ uint8_t idx = mData[mOffset - 1] & mask;
+ idx <<= (8 - bitsLeft);
+ // Don't update bitsLeft yet, because we need to check that value against the
+ // number of bits used by our encoding later on. We'll update when we are sure
+ // how many bits we've actually used.
+
+ if (table->IndexHasANextTable(idx)) {
+ // Can't chain to another table when we're all out of bits in the encoding
+ LOG(("DecodeFinalHuffmanCharacter trying to chain when we're out of bits"));
+ return NS_ERROR_FAILURE;
+ }
+
+ const HuffmanIncomingEntry* entry = table->Entry(idx);
+
+ if (bitsLeft < entry->mPrefixLen) {
+ // We don't have enough bits to actually make a match, this is some sort of
+ // invalid coding
+ LOG(("DecodeFinalHuffmanCharacter does't have enough bits to match"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is a character!
+ if (entry->mValue == 256) {
+ // EOS
+ LOG(("DecodeFinalHuffmanCharacter actually decoded an EOS"));
+ return NS_ERROR_FAILURE;
+ }
+ c = static_cast<uint8_t>(entry->mValue & 0xFF);
+ bitsLeft -= entry->mPrefixLen;
+
+ return NS_OK;
+}
+
+uint8_t Http2Decompressor::ExtractByte(uint8_t bitsLeft,
+ uint32_t& bytesConsumed) {
+ MOZ_DIAGNOSTIC_ASSERT(mOffset < mDataLen);
+ uint8_t rv;
+
+ if (bitsLeft) {
+ // Need to extract bitsLeft bits from the previous byte, and 8 - bitsLeft
+ // bits from the current byte
+ uint8_t mask = (1 << bitsLeft) - 1;
+ rv = (mData[mOffset - 1] & mask) << (8 - bitsLeft);
+ rv |= (mData[mOffset] & ~mask) >> bitsLeft;
+ } else {
+ rv = mData[mOffset];
+ }
+
+ // We always update these here, under the assumption that all 8 bits we got
+ // here will be used. These may be re-adjusted later in the case that we don't
+ // use up all 8 bits of the byte.
+ ++mOffset;
+ ++bytesConsumed;
+
+ return rv;
+}
+
+nsresult Http2Decompressor::DecodeHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint32_t& bytesConsumed,
+ uint8_t& bitsLeft) {
+ uint8_t idx = ExtractByte(bitsLeft, bytesConsumed);
+
+ if (table->IndexHasANextTable(idx)) {
+ if (mOffset >= mDataLen) {
+ if (!bitsLeft || (mOffset > mDataLen)) {
+ // TODO - does this get me into trouble in the new world?
+ // No info left in input to try to consume, we're done
+ LOG(("DecodeHuffmanCharacter all out of bits to consume, can't chain"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // We might get lucky here!
+ return DecodeFinalHuffmanCharacter(table->NextTable(idx), c, bitsLeft);
+ }
+
+ // We're sorry, Mario, but your princess is in another castle
+ return DecodeHuffmanCharacter(table->NextTable(idx), c, bytesConsumed,
+ bitsLeft);
+ }
+
+ const HuffmanIncomingEntry* entry = table->Entry(idx);
+ if (entry->mValue == 256) {
+ LOG(("DecodeHuffmanCharacter found an actual EOS"));
+ return NS_ERROR_FAILURE;
+ }
+ c = static_cast<uint8_t>(entry->mValue & 0xFF);
+
+ // Need to adjust bitsLeft (and possibly other values) because we may not have
+ // consumed all of the bits of the byte we extracted.
+ if (entry->mPrefixLen <= bitsLeft) {
+ bitsLeft -= entry->mPrefixLen;
+ --mOffset;
+ --bytesConsumed;
+ } else {
+ bitsLeft = 8 - (entry->mPrefixLen - bitsLeft);
+ }
+ MOZ_ASSERT(bitsLeft < 8);
+
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::CopyHuffmanStringFromInput(uint32_t bytes,
+ nsACString& val) {
+ if (mOffset + bytes > mDataLen) {
+ LOG(("CopyHuffmanStringFromInput not enough data"));
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t bytesRead = 0;
+ uint8_t bitsLeft = 0;
+ nsAutoCString buf;
+ nsresult rv;
+ uint8_t c;
+
+ while (bytesRead < bytes) {
+ uint32_t bytesConsumed = 0;
+ rv = DecodeHuffmanCharacter(&HuffmanIncomingRoot, c, bytesConsumed,
+ bitsLeft);
+ if (NS_FAILED(rv)) {
+ LOG(("CopyHuffmanStringFromInput failed to decode a character"));
+ return rv;
+ }
+
+ bytesRead += bytesConsumed;
+ buf.Append(c);
+ }
+
+ if (bytesRead > bytes) {
+ LOG(("CopyHuffmanStringFromInput read more bytes than was allowed!"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bitsLeft) {
+ // The shortest valid code is 4 bits, so we know there can be at most one
+ // character left that our loop didn't decode. Check to see if that's the
+ // case, and if so, add it to our output.
+ rv = DecodeFinalHuffmanCharacter(&HuffmanIncomingRoot, c, bitsLeft);
+ if (NS_SUCCEEDED(rv)) {
+ buf.Append(c);
+ }
+ }
+
+ if (bitsLeft > 7) {
+ LOG(("CopyHuffmanStringFromInput more than 7 bits of padding"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bitsLeft) {
+ // Any bits left at this point must belong to the EOS symbol, so make sure
+ // they make sense (ie, are all ones)
+ uint8_t mask = (1 << bitsLeft) - 1;
+ uint8_t bits = mData[mOffset - 1] & mask;
+ if (bits != mask) {
+ LOG(
+ ("CopyHuffmanStringFromInput ran out of data but found possible "
+ "non-EOS symbol"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ val = buf;
+ LOG(("CopyHuffmanStringFromInput decoded a full string!"));
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::DoIndexed() {
+ // this starts with a 1 bit pattern
+ MOZ_ASSERT(mData[mOffset] & 0x80);
+
+ // This is a 7 bit prefix
+
+ uint32_t index;
+ nsresult rv = DecodeInteger(7, index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LOG(("HTTP decompressor indexed entry %u\n", index));
+
+ if (index == 0) {
+ return NS_ERROR_FAILURE;
+ }
+ // NWGH - remove this line, since we'll keep everything 1-indexed
+ index--; // Internally, we 0-index everything, since this is, y'know, C++
+
+ return OutputHeader(index);
+}
+
+nsresult Http2Decompressor::DoLiteralInternal(nsACString& name,
+ nsACString& value,
+ uint32_t namePrefixLen) {
+ // guts of doliteralwithoutindex and doliteralwithincremental
+ MOZ_ASSERT(((mData[mOffset] & 0xF0) == 0x00) || // withoutindex
+ ((mData[mOffset] & 0xF0) == 0x10) || // neverindexed
+ ((mData[mOffset] & 0xC0) == 0x40)); // withincremental
+
+ // first let's get the name
+ uint32_t index;
+ nsresult rv = DecodeInteger(namePrefixLen, index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // sanity check
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Http2 Decompressor ran out of data");
+ // This is session-fatal
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isHuffmanEncoded;
+
+ if (!index) {
+ // name is embedded as a literal
+ uint32_t nameLen;
+ isHuffmanEncoded = mData[mOffset] & (1 << 7);
+ rv = DecodeInteger(7, nameLen);
+ if (NS_SUCCEEDED(rv)) {
+ if (isHuffmanEncoded) {
+ rv = CopyHuffmanStringFromInput(nameLen, name);
+ } else {
+ rv = CopyStringFromInput(nameLen, name);
+ }
+ }
+ LOG(("Http2Decompressor::DoLiteralInternal literal name %s",
+ name.BeginReading()));
+ } else {
+ // NWGH - make this index, not index - 1
+ // name is from headertable
+ rv = CopyHeaderString(index - 1, name);
+ LOG(("Http2Decompressor::DoLiteralInternal indexed name %d %s", index,
+ name.BeginReading()));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // sanity check
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Http2 Decompressor ran out of data");
+ // This is session-fatal
+ return NS_ERROR_FAILURE;
+ }
+
+ // now the value
+ uint32_t valueLen;
+ isHuffmanEncoded = mData[mOffset] & (1 << 7);
+ rv = DecodeInteger(7, valueLen);
+ if (NS_SUCCEEDED(rv)) {
+ if (isHuffmanEncoded) {
+ rv = CopyHuffmanStringFromInput(valueLen, value);
+ } else {
+ rv = CopyStringFromInput(valueLen, value);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t newline = 0;
+ while ((newline = value.FindChar('\n', newline)) != -1) {
+ if (value[newline + 1] == ' ' || value[newline + 1] == '\t') {
+ LOG(("Http2Decompressor::Disallowing folded header value %s",
+ value.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // Increment this to avoid always finding the same newline and looping
+ // forever
+ ++newline;
+ }
+
+ LOG(("Http2Decompressor::DoLiteralInternal value %s", value.BeginReading()));
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::DoLiteralWithoutIndex() {
+ // this starts with 0000 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x00);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 4);
+
+ LOG(("HTTP decompressor literal without index %s %s\n", name.get(),
+ value.get()));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ return rv;
+}
+
+nsresult Http2Decompressor::DoLiteralWithIncremental() {
+ // this starts with 01 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xC0) == 0x40);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 6);
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ // Let NET_RESET continue on so that we don't get out of sync, as it is just
+ // used to kill the stream, not the session.
+ if (NS_FAILED(rv) && rv != NS_ERROR_NET_RESET) {
+ return rv;
+ }
+
+ uint32_t room = nvPair(name, value).Size();
+ if (room > mMaxBuffer) {
+ ClearHeaderTable();
+ LOG(
+ ("HTTP decompressor literal with index not inserted due to size %u %s "
+ "%s\n",
+ room, name.get(), value.get()));
+ DumpState("Decompressor state after ClearHeaderTable");
+ return rv;
+ }
+
+ MakeRoom(room, "decompressor");
+
+ // Incremental Indexing implicitly adds a row to the header table.
+ mHeaderTable.AddElement(name, value);
+
+ uint32_t currentSize = mHeaderTable.ByteCount();
+ if (currentSize > mPeakSize) {
+ mPeakSize = currentSize;
+ }
+
+ uint32_t currentCount = mHeaderTable.VariableLength();
+ if (currentCount > mPeakCount) {
+ mPeakCount = currentCount;
+ }
+
+ LOG(("HTTP decompressor literal with index 0 %s %s\n", name.get(),
+ value.get()));
+
+ return rv;
+}
+
+nsresult Http2Decompressor::DoLiteralNeverIndexed() {
+ // This starts with 0001 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x10);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 4);
+
+ LOG(("HTTP decompressor literal never indexed %s %s\n", name.get(),
+ value.get()));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ return rv;
+}
+
+nsresult Http2Decompressor::DoContextUpdate() {
+ // This starts with 001 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xE0) == 0x20);
+
+ // Getting here means we have to adjust the max table size, because the
+ // compressor on the other end has signaled to us through HPACK (not H2)
+ // that it's using a size different from the currently-negotiated size.
+ // This change could either come about because we've sent a
+ // SETTINGS_HEADER_TABLE_SIZE, or because the encoder has decided that
+ // the current negotiated size doesn't fit its needs (for whatever reason)
+ // and so it needs to change it (either up to the max allowed by our SETTING,
+ // or down to some value below that)
+ uint32_t newMaxSize;
+ nsresult rv = DecodeInteger(5, newMaxSize);
+ LOG(("Http2Decompressor::DoContextUpdate new maximum size %u", newMaxSize));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (newMaxSize > mMaxBufferSetting) {
+ // This is fatal to the session - peer is trying to use a table larger
+ // than we have made available.
+ return NS_ERROR_FAILURE;
+ }
+
+ SetMaxBufferSizeInternal(newMaxSize);
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////
+
+nsresult Http2Compressor::EncodeHeaderBlock(
+ const nsCString& nvInput, const nsACString& method, const nsACString& path,
+ const nsACString& host, const nsACString& scheme,
+ const nsACString& protocol, bool simpleConnectForm, nsACString& output) {
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOutput = &output;
+ output.Truncate();
+ mParsedContentLength = -1;
+
+ bool isWebsocket = (!simpleConnectForm && !protocol.IsEmpty());
+
+ // first thing's first - context size updates (if necessary)
+ if (mBufferSizeChangeWaiting) {
+ if (mLowestBufferSizeWaiting < mMaxBufferSetting) {
+ EncodeTableSizeChange(mLowestBufferSizeWaiting);
+ }
+ EncodeTableSizeChange(mMaxBufferSetting);
+ mBufferSizeChangeWaiting = false;
+ }
+
+ // colon headers first
+ if (!simpleConnectForm) {
+ ProcessHeader(nvPair(":method"_ns, method), false, false);
+ ProcessHeader(nvPair(":path"_ns, path), true, false);
+ ProcessHeader(nvPair(":authority"_ns, host), false, false);
+ ProcessHeader(nvPair(":scheme"_ns, scheme), false, false);
+ if (isWebsocket) {
+ ProcessHeader(nvPair(":protocol"_ns, protocol), false, false);
+ }
+ } else {
+ ProcessHeader(nvPair(":method"_ns, method), false, false);
+ ProcessHeader(nvPair(":authority"_ns, host), false, false);
+ }
+
+ // now the non colon headers
+ const char* beginBuffer = nvInput.BeginReading();
+
+ // This strips off the HTTP/1 method+path+version
+ int32_t crlfIndex = nvInput.Find("\r\n");
+ while (true) {
+ int32_t startIndex = crlfIndex + 2;
+
+ crlfIndex = nvInput.Find("\r\n", startIndex);
+ if (crlfIndex == -1) {
+ break;
+ }
+
+ int32_t colonIndex = Substring(nvInput, 0, crlfIndex).Find(":", startIndex);
+ if (colonIndex == -1) {
+ break;
+ }
+
+ nsDependentCSubstring name =
+ Substring(beginBuffer + startIndex, beginBuffer + colonIndex);
+ // all header names are lower case in http/2
+ ToLowerCase(name);
+
+ // exclusions
+ if (name.EqualsLiteral("connection") || name.EqualsLiteral("host") ||
+ name.EqualsLiteral("keep-alive") ||
+ name.EqualsLiteral("proxy-connection") || name.EqualsLiteral("te") ||
+ name.EqualsLiteral("transfer-encoding") ||
+ name.EqualsLiteral("upgrade") ||
+ name.EqualsLiteral("sec-websocket-key")) {
+ continue;
+ }
+
+ // colon headers are for http/2 and this is http/1 input, so that
+ // is probably a smuggling attack of some kind
+ bool isColonHeader = false;
+ for (const char* cPtr = name.BeginReading();
+ cPtr && cPtr < name.EndReading(); ++cPtr) {
+ if (*cPtr == ':') {
+ isColonHeader = true;
+ break;
+ }
+ if (*cPtr != ' ' && *cPtr != '\t') {
+ isColonHeader = false;
+ break;
+ }
+ }
+ if (isColonHeader) {
+ continue;
+ }
+
+ int32_t valueIndex = colonIndex + 1;
+
+ while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') {
+ ++valueIndex;
+ }
+
+ nsDependentCSubstring value =
+ Substring(beginBuffer + valueIndex, beginBuffer + crlfIndex);
+
+ if (name.EqualsLiteral("content-length")) {
+ int64_t len;
+ nsCString tmp(value);
+ if (nsHttp::ParseInt64(tmp.get(), nullptr, &len)) {
+ mParsedContentLength = len;
+ }
+ }
+
+ if (name.EqualsLiteral("cookie")) {
+ // cookie crumbling
+ bool haveMoreCookies = true;
+ int32_t nextCookie = valueIndex;
+ while (haveMoreCookies) {
+ int32_t semiSpaceIndex =
+ Substring(nvInput, 0, crlfIndex).Find("; ", nextCookie);
+ if (semiSpaceIndex == -1) {
+ haveMoreCookies = false;
+ semiSpaceIndex = crlfIndex;
+ }
+ nsDependentCSubstring cookie =
+ Substring(beginBuffer + nextCookie, beginBuffer + semiSpaceIndex);
+ // cookies less than 20 bytes are not indexed
+ ProcessHeader(nvPair(name, cookie), false, cookie.Length() < 20);
+ nextCookie = semiSpaceIndex + 2;
+ }
+ } else {
+ // allow indexing of every non-cookie except authorization
+ ProcessHeader(nvPair(name, value), false,
+ name.EqualsLiteral("authorization"));
+ }
+ }
+
+ // NB: This is a *really* ugly hack, but to do this in the right place (the
+ // transaction) would require totally reworking how/when the transaction
+ // creates its request stream, which is not worth the effort and risk of
+ // breakage just to add one header only to h2 connections.
+ if (!simpleConnectForm && !isWebsocket) {
+ // Add in TE: trailers for regular requests
+ nsAutoCString te("te");
+ nsAutoCString trailers("trailers");
+ ProcessHeader(nvPair(te, trailers), false, false);
+ }
+
+ mOutput = nullptr;
+ DumpState("Compressor state after EncodeHeaderBlock");
+ return NS_OK;
+}
+
+void Http2Compressor::DoOutput(Http2Compressor::outputCode code,
+ const class nvPair* pair, uint32_t index) {
+ // start Byte needs to be calculated from the offset after
+ // the opcode has been written out in case the output stream
+ // buffer gets resized/relocated
+ uint32_t offset = mOutput->Length();
+ uint8_t* startByte;
+
+ switch (code) {
+ case kNeverIndexedLiteral:
+ LOG(
+ ("HTTP compressor %p neverindex literal with name reference %u %s "
+ "%s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(4, index); // 0001 4 bit prefix
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = (*startByte & 0x0f) | 0x10;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kPlainLiteral:
+ LOG(("HTTP compressor %p noindex literal with name reference %u %s %s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(4, index); // 0000 4 bit prefix
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte & 0x0f;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kIndexedLiteral:
+ LOG(("HTTP compressor %p literal with name reference %u %s %s\n", this,
+ index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(6, index); // 01 2 bit prefix
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = (*startByte & 0x3f) | 0x40;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kIndex:
+ LOG(("HTTP compressor %p index %u %s %s\n", this, index,
+ pair->mName.get(), pair->mValue.get()));
+ // NWGH - make this plain old index instead of index + 1
+ // In this case, we are passed the raw 0-based C index, and need to
+ // increment to make it 1-based and comply with the spec
+ EncodeInteger(7, index + 1);
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x80; // 1 1 bit prefix
+ break;
+ }
+}
+
+// writes the encoded integer onto the output
+void Http2Compressor::EncodeInteger(uint32_t prefixLen, uint32_t val) {
+ uint32_t mask = (1 << prefixLen) - 1;
+ uint8_t tmp;
+
+ if (val < mask) {
+ // 1 byte encoding!
+ tmp = val;
+ mOutput->Append(reinterpret_cast<char*>(&tmp), 1);
+ return;
+ }
+
+ if (mask) {
+ val -= mask;
+ tmp = mask;
+ mOutput->Append(reinterpret_cast<char*>(&tmp), 1);
+ }
+
+ uint32_t q, r;
+ do {
+ q = val / 128;
+ r = val % 128;
+ tmp = r;
+ if (q) {
+ tmp |= 0x80; // chain bit
+ }
+ val = q;
+ mOutput->Append(reinterpret_cast<char*>(&tmp), 1);
+ } while (q);
+}
+
+void Http2Compressor::HuffmanAppend(const nsCString& value) {
+ nsAutoCString buf;
+ uint8_t bitsLeft = 8;
+ uint32_t length = value.Length();
+ uint32_t offset;
+ uint8_t* startByte;
+
+ for (uint32_t i = 0; i < length; ++i) {
+ uint8_t idx = static_cast<uint8_t>(value[i]);
+ uint8_t huffLength = HuffmanOutgoing[idx].mLength;
+ uint32_t huffValue = HuffmanOutgoing[idx].mValue;
+
+ if (bitsLeft < 8) {
+ // Fill in the least significant <bitsLeft> bits of the previous byte
+ // first
+ uint32_t val;
+ if (huffLength >= bitsLeft) {
+ val = huffValue & ~((1 << (huffLength - bitsLeft)) - 1);
+ val >>= (huffLength - bitsLeft);
+ } else {
+ val = huffValue << (bitsLeft - huffLength);
+ }
+ val &= ((1 << bitsLeft) - 1);
+ offset = buf.Length() - 1;
+ startByte = reinterpret_cast<unsigned char*>(buf.BeginWriting()) + offset;
+ *startByte = *startByte | static_cast<uint8_t>(val & 0xFF);
+ if (huffLength >= bitsLeft) {
+ huffLength -= bitsLeft;
+ bitsLeft = 8;
+ } else {
+ bitsLeft -= huffLength;
+ huffLength = 0;
+ }
+ }
+
+ while (huffLength >= 8) {
+ uint32_t mask = ~((1 << (huffLength - 8)) - 1);
+ uint8_t val = ((huffValue & mask) >> (huffLength - 8)) & 0xFF;
+ buf.Append(reinterpret_cast<char*>(&val), 1);
+ huffLength -= 8;
+ }
+
+ if (huffLength) {
+ // Fill in the most significant <huffLength> bits of the next byte
+ bitsLeft = 8 - huffLength;
+ uint8_t val = (huffValue & ((1 << huffLength) - 1)) << bitsLeft;
+ buf.Append(reinterpret_cast<char*>(&val), 1);
+ }
+ }
+
+ if (bitsLeft != 8) {
+ // Pad the last <bitsLeft> bits with ones, which corresponds to the EOS
+ // encoding
+ uint8_t val = (1 << bitsLeft) - 1;
+ offset = buf.Length() - 1;
+ startByte = reinterpret_cast<unsigned char*>(buf.BeginWriting()) + offset;
+ *startByte = *startByte | val;
+ }
+
+ // Now we know how long our encoded string is, we can fill in our length
+ uint32_t bufLength = buf.Length();
+ offset = mOutput->Length();
+ EncodeInteger(7, bufLength);
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x80;
+
+ // Finally, we can add our REAL data!
+ mOutput->Append(buf);
+ LOG(
+ ("Http2Compressor::HuffmanAppend %p encoded %d byte original on %d "
+ "bytes.\n",
+ this, length, bufLength));
+}
+
+void Http2Compressor::ProcessHeader(const nvPair inputPair, bool noLocalIndex,
+ bool neverIndex) {
+ uint32_t newSize = inputPair.Size();
+ uint32_t headerTableSize = mHeaderTable.Length();
+ uint32_t matchedIndex = 0u;
+ uint32_t nameReference = 0u;
+ bool match = false;
+
+ LOG(("Http2Compressor::ProcessHeader %s %s", inputPair.mName.get(),
+ inputPair.mValue.get()));
+
+ // NWGH - make this index = 1; index <= headerTableSize; ++index
+ for (uint32_t index = 0; index < headerTableSize; ++index) {
+ if (mHeaderTable[index]->mName.Equals(inputPair.mName)) {
+ // NWGH - make this nameReference = index
+ nameReference = index + 1;
+ if (mHeaderTable[index]->mValue.Equals(inputPair.mValue)) {
+ match = true;
+ matchedIndex = index;
+ break;
+ }
+ }
+ }
+
+ // We need to emit a new literal
+ if (!match || noLocalIndex || neverIndex) {
+ if (neverIndex) {
+ DoOutput(kNeverIndexedLiteral, &inputPair, nameReference);
+ DumpState("Compressor state after literal never index");
+ return;
+ }
+
+ if (noLocalIndex || (newSize > (mMaxBuffer / 2)) || (mMaxBuffer < 128)) {
+ DoOutput(kPlainLiteral, &inputPair, nameReference);
+ DumpState("Compressor state after literal without index");
+ return;
+ }
+
+ // make sure to makeroom() first so that any implied items
+ // get preserved.
+ MakeRoom(newSize, "compressor");
+ DoOutput(kIndexedLiteral, &inputPair, nameReference);
+
+ mHeaderTable.AddElement(inputPair.mName, inputPair.mValue);
+ LOG(("HTTP compressor %p new literal placed at index 0\n", this));
+ DumpState("Compressor state after literal with index");
+ return;
+ }
+
+ // emit an index
+ DoOutput(kIndex, &inputPair, matchedIndex);
+
+ DumpState("Compressor state after index");
+}
+
+void Http2Compressor::EncodeTableSizeChange(uint32_t newMaxSize) {
+ uint32_t offset = mOutput->Length();
+ EncodeInteger(5, newMaxSize);
+ uint8_t* startByte =
+ reinterpret_cast<uint8_t*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x20;
+}
+
+void Http2Compressor::SetMaxBufferSize(uint32_t maxBufferSize) {
+ mMaxBufferSetting = maxBufferSize;
+ SetMaxBufferSizeInternal(maxBufferSize);
+ if (!mBufferSizeChangeWaiting) {
+ mBufferSizeChangeWaiting = true;
+ mLowestBufferSizeWaiting = maxBufferSize;
+ } else if (maxBufferSize < mLowestBufferSizeWaiting) {
+ mLowestBufferSizeWaiting = maxBufferSize;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Compression.h b/netwerk/protocol/http/Http2Compression.h
new file mode 100644
index 0000000000..ad47949938
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.h
@@ -0,0 +1,207 @@
+/* -*- 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_Http2Compression_Internal_h
+#define mozilla_net_Http2Compression_Internal_h
+
+// HPACK - RFC 7541
+// https://www.rfc-editor.org/rfc/rfc7541.txt
+
+#include "mozilla/Attributes.h"
+#include "nsDeque.h"
+#include "nsString.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+void Http2CompressionCleanup();
+
+class nvPair {
+ public:
+ nvPair(const nsACString& name, const nsACString& value)
+ : mName(name), mValue(value) {}
+
+ uint32_t Size() const { return mName.Length() + mValue.Length() + 32; }
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ nsCString mName;
+ nsCString mValue;
+};
+
+class nvFIFO {
+ public:
+ nvFIFO();
+ ~nvFIFO();
+ void AddElement(const nsCString& name, const nsCString& value);
+ void AddElement(const nsCString& name);
+ void RemoveElement();
+ uint32_t ByteCount() const;
+ uint32_t Length() const;
+ uint32_t VariableLength() const;
+ size_t StaticLength() const;
+ void Clear();
+ const nvPair* operator[](size_t index) const;
+ size_t SizeOfDynamicTable(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ uint32_t mByteCount{0};
+ nsDeque<nvPair> mTable;
+
+ // This mutex is held when adding or removing elements in the table
+ // and when accessing the table from the main thread (in SizeOfDynamicTable)
+ // Since the operator[] and other const methods are always called
+ // on the socket thread, they don't need to lock the mutex.
+ // Mutable so it can be locked in SizeOfDynamicTable which is const
+ mutable Mutex mMutex MOZ_UNANNOTATED{"nvFIFO"};
+};
+
+class HpackDynamicTableReporter;
+
+class Http2BaseCompressor {
+ public:
+ Http2BaseCompressor();
+ virtual ~Http2BaseCompressor();
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ nsresult SetInitialMaxBufferSize(uint32_t maxBufferSize);
+ void SetDumpTables(bool dumpTables);
+
+ protected:
+ const static uint32_t kDefaultMaxBuffer = 4096;
+
+ virtual void ClearHeaderTable();
+ virtual void MakeRoom(uint32_t amount, const char* direction);
+ virtual void DumpState(const char*);
+ virtual void SetMaxBufferSizeInternal(uint32_t maxBufferSize);
+
+ nsACString* mOutput{nullptr};
+ nvFIFO mHeaderTable;
+
+ uint32_t mMaxBuffer{kDefaultMaxBuffer};
+ uint32_t mMaxBufferSetting{kDefaultMaxBuffer};
+ bool mSetInitialMaxBufferSizeAllowed{true};
+
+ uint32_t mPeakSize{0};
+ uint32_t mPeakCount{0};
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::HistogramID mPeakSizeID;
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::HistogramID mPeakCountID;
+
+ bool mDumpTables{false};
+
+ private:
+ RefPtr<HpackDynamicTableReporter> mDynamicReporter;
+};
+
+class Http2Compressor;
+
+class Http2Decompressor final : public Http2BaseCompressor {
+ public:
+ Http2Decompressor() {
+ mPeakSizeID = Telemetry::HPACK_PEAK_SIZE_DECOMPRESSOR;
+ mPeakCountID = Telemetry::HPACK_PEAK_COUNT_DECOMPRESSOR;
+ };
+ virtual ~Http2Decompressor() = default;
+
+ // NS_OK: Produces the working set of HTTP/1 formatted headers
+ [[nodiscard]] nsresult DecodeHeaderBlock(const uint8_t* data,
+ uint32_t datalen, nsACString& output,
+ bool isPush);
+
+ void GetStatus(nsACString& hdr) { hdr = mHeaderStatus; }
+ void GetHost(nsACString& hdr) { hdr = mHeaderHost; }
+ void GetScheme(nsACString& hdr) { hdr = mHeaderScheme; }
+ void GetPath(nsACString& hdr) { hdr = mHeaderPath; }
+ void GetMethod(nsACString& hdr) { hdr = mHeaderMethod; }
+
+ private:
+ [[nodiscard]] nsresult DoIndexed();
+ [[nodiscard]] nsresult DoLiteralWithoutIndex();
+ [[nodiscard]] nsresult DoLiteralWithIncremental();
+ [[nodiscard]] nsresult DoLiteralInternal(nsACString&, nsACString&, uint32_t);
+ [[nodiscard]] nsresult DoLiteralNeverIndexed();
+ [[nodiscard]] nsresult DoContextUpdate();
+
+ [[nodiscard]] nsresult DecodeInteger(uint32_t prefixLen, uint32_t& accum);
+ [[nodiscard]] nsresult OutputHeader(uint32_t index);
+ [[nodiscard]] nsresult OutputHeader(const nsACString& name,
+ const nsACString& value);
+
+ [[nodiscard]] nsresult CopyHeaderString(uint32_t index, nsACString& name);
+ [[nodiscard]] nsresult CopyStringFromInput(uint32_t bytes, nsACString& val);
+ uint8_t ExtractByte(uint8_t bitsLeft, uint32_t& bytesConsumed);
+ [[nodiscard]] nsresult CopyHuffmanStringFromInput(uint32_t bytes,
+ nsACString& val);
+ [[nodiscard]] nsresult DecodeHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint32_t& bytesConsumed,
+ uint8_t& bitsLeft);
+ [[nodiscard]] nsresult DecodeFinalHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint8_t& bitsLeft);
+
+ nsCString mHeaderStatus;
+ nsCString mHeaderHost;
+ nsCString mHeaderScheme;
+ nsCString mHeaderPath;
+ nsCString mHeaderMethod;
+
+ // state variables when DecodeBlock() is on the stack
+ uint32_t mOffset{0};
+ const uint8_t* mData{nullptr};
+ uint32_t mDataLen{0};
+ bool mSeenNonColonHeader{false};
+ bool mIsPush{false};
+};
+
+class Http2Compressor final : public Http2BaseCompressor {
+ public:
+ Http2Compressor() {
+ mPeakSizeID = Telemetry::HPACK_PEAK_SIZE_COMPRESSOR;
+ mPeakCountID = Telemetry::HPACK_PEAK_COUNT_COMPRESSOR;
+ };
+ virtual ~Http2Compressor() = default;
+
+ // HTTP/1 formatted header block as input - HTTP/2 formatted
+ // header block as output
+ [[nodiscard]] nsresult EncodeHeaderBlock(
+ const nsCString& nvInput, const nsACString& method,
+ const nsACString& path, const nsACString& host, const nsACString& scheme,
+ const nsACString& protocol, bool simpleConnectForm, nsACString& output);
+
+ int64_t GetParsedContentLength() {
+ return mParsedContentLength;
+ } // -1 on not found
+
+ void SetMaxBufferSize(uint32_t maxBufferSize);
+
+ private:
+ enum outputCode {
+ kNeverIndexedLiteral,
+ kPlainLiteral,
+ kIndexedLiteral,
+ kIndex
+ };
+
+ void DoOutput(Http2Compressor::outputCode code, const class nvPair* pair,
+ uint32_t index);
+ void EncodeInteger(uint32_t prefixLen, uint32_t val);
+ void ProcessHeader(const nvPair inputPair, bool noLocalIndex,
+ bool neverIndex);
+ void HuffmanAppend(const nsCString& value);
+ void EncodeTableSizeChange(uint32_t newMaxSize);
+
+ int64_t mParsedContentLength{-1};
+ bool mBufferSizeChangeWaiting{false};
+ uint32_t mLowestBufferSizeWaiting{0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Compression_Internal_h
diff --git a/netwerk/protocol/http/Http2HuffmanIncoming.h b/netwerk/protocol/http/Http2HuffmanIncoming.h
new file mode 100644
index 0000000000..0b5a8e9a1f
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanIncoming.h
@@ -0,0 +1,709 @@
+/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanIncoming_h
+#define mozilla__net__Http2HuffmanIncoming_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+struct HuffmanIncomingEntry {
+ uint16_t mValue : 9; // 9 bits so it can hold 0..256
+ uint16_t mPrefixLen : 7; // only holds 1..8
+};
+
+// The data members are public only so they can be statically constructed. All
+// accesses should be done through the functions.
+struct HuffmanIncomingTable {
+ // The normal entries, for indices in the range 0..(mNumEntries-1).
+ const HuffmanIncomingEntry* const mEntries;
+
+ // The next tables, for indices in the range mNumEntries..255. Must be
+ // |nullptr| if mIndexOfFirstNextTable is 256.
+ const HuffmanIncomingTable** const mNextTables;
+
+ // The index of the first next table (equal to the number of entries in
+ // mEntries). This cannot be a uint8_t because it can have the value 256,
+ // in which case there are no next tables and mNextTables must be |nullptr|.
+ const uint16_t mIndexOfFirstNextTable;
+
+ const uint8_t mPrefixLen;
+
+ bool IndexHasANextTable(uint8_t aIndex) const {
+ return aIndex >= mIndexOfFirstNextTable;
+ }
+
+ const HuffmanIncomingEntry* Entry(uint8_t aIndex) const {
+ MOZ_ASSERT(aIndex < mIndexOfFirstNextTable);
+ return &mEntries[aIndex];
+ }
+
+ const HuffmanIncomingTable* NextTable(uint8_t aIndex) const {
+ MOZ_ASSERT(aIndex >= mIndexOfFirstNextTable);
+ return mNextTables[aIndex - mIndexOfFirstNextTable];
+ }
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_254[] = {
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_254 = {
+ HuffmanIncomingEntries_254, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_254[] = {
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {128, 4}, {128, 4},
+ {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4},
+ {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4},
+ {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4},
+ {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4},
+ {130, 4}, {130, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4},
+ {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4},
+ {131, 4}, {131, 4}, {131, 4}, {131, 4}, {162, 4}, {162, 4}, {162, 4},
+ {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4},
+ {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {184, 4},
+ {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4},
+ {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4},
+ {184, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4},
+ {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4},
+ {194, 4}, {194, 4}, {194, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4},
+ {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4},
+ {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4}, {226, 4}, {226, 4},
+ {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4},
+ {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4},
+ {153, 5}, {153, 5}, {153, 5}, {153, 5}, {153, 5}, {153, 5}, {153, 5},
+ {153, 5}, {161, 5}, {161, 5}, {161, 5}, {161, 5}, {161, 5}, {161, 5},
+ {161, 5}, {161, 5}, {167, 5}, {167, 5}, {167, 5}, {167, 5}, {167, 5},
+ {167, 5}, {167, 5}, {167, 5}, {172, 5}, {172, 5}, {172, 5}, {172, 5},
+ {172, 5}, {172, 5}, {172, 5}, {172, 5},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_254 = {
+ HuffmanIncomingEntries_255_254, nullptr, 256, 5};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_246[] = {
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_246 = {
+ HuffmanIncomingEntries_255_255_246, nullptr, 256, 1};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_247[] = {
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_247 = {
+ HuffmanIncomingEntries_255_255_247, nullptr, 256, 1};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_248[] = {
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_248 = {
+ HuffmanIncomingEntries_255_255_248, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_249[] = {
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_249 = {
+ HuffmanIncomingEntries_255_255_249, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_250[] = {
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_250 = {
+ HuffmanIncomingEntries_255_255_250, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_251[] = {
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_251 = {
+ HuffmanIncomingEntries_255_255_251, nullptr, 256, 3};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_252[] = {
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_252 = {
+ HuffmanIncomingEntries_255_255_252, nullptr, 256, 3};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_253[] = {
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_253 = {
+ HuffmanIncomingEntries_255_255_253, nullptr, 256, 3};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_254[] = {
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {2, 4}, {2, 4}, {2, 4},
+ {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4},
+ {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {3, 4},
+ {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4},
+ {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4},
+ {3, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4},
+ {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4},
+ {4, 4}, {4, 4}, {4, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4},
+ {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4},
+ {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4}, {6, 4}, {6, 4},
+ {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4},
+ {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4},
+ {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4},
+ {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4},
+ {7, 4}, {7, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4},
+ {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4},
+ {8, 4}, {8, 4}, {8, 4}, {8, 4}, {11, 4}, {11, 4}, {11, 4},
+ {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4},
+ {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {12, 4},
+ {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4},
+ {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4},
+ {12, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4},
+ {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4},
+ {14, 4}, {14, 4}, {14, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4},
+ {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4},
+ {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4}, {16, 4}, {16, 4},
+ {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4},
+ {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4},
+ {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4},
+ {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4},
+ {17, 4}, {17, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4},
+ {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4},
+ {18, 4}, {18, 4}, {18, 4}, {18, 4},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_254 = {
+ HuffmanIncomingEntries_255_255_254, nullptr, 256, 4};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_255[] = {
+ {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4},
+ {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4},
+ {19, 4}, {19, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4},
+ {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4},
+ {20, 4}, {20, 4}, {20, 4}, {20, 4}, {21, 4}, {21, 4}, {21, 4},
+ {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4},
+ {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {23, 4},
+ {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4},
+ {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4},
+ {23, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4},
+ {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4},
+ {24, 4}, {24, 4}, {24, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4},
+ {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4},
+ {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4}, {26, 4}, {26, 4},
+ {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4},
+ {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4},
+ {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4},
+ {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4},
+ {27, 4}, {27, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4},
+ {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4},
+ {28, 4}, {28, 4}, {28, 4}, {28, 4}, {29, 4}, {29, 4}, {29, 4},
+ {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4},
+ {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {30, 4},
+ {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4},
+ {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4},
+ {30, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4},
+ {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4},
+ {31, 4}, {31, 4}, {31, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4},
+ {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4},
+ {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4}, {220, 4}, {220, 4},
+ {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4},
+ {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4},
+ {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4},
+ {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4},
+ {249, 4}, {249, 4}, {10, 6}, {10, 6}, {10, 6}, {10, 6}, {13, 6},
+ {13, 6}, {13, 6}, {13, 6}, {22, 6}, {22, 6}, {22, 6}, {22, 6},
+ {256, 6}, {256, 6}, {256, 6}, {256, 6},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_255 = {
+ HuffmanIncomingEntries_255_255_255, nullptr, 256, 6};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255[] = {
+ {176, 5}, {176, 5}, {176, 5}, {176, 5}, {176, 5}, {176, 5}, {176, 5},
+ {176, 5}, {177, 5}, {177, 5}, {177, 5}, {177, 5}, {177, 5}, {177, 5},
+ {177, 5}, {177, 5}, {179, 5}, {179, 5}, {179, 5}, {179, 5}, {179, 5},
+ {179, 5}, {179, 5}, {179, 5}, {209, 5}, {209, 5}, {209, 5}, {209, 5},
+ {209, 5}, {209, 5}, {209, 5}, {209, 5}, {216, 5}, {216, 5}, {216, 5},
+ {216, 5}, {216, 5}, {216, 5}, {216, 5}, {216, 5}, {217, 5}, {217, 5},
+ {217, 5}, {217, 5}, {217, 5}, {217, 5}, {217, 5}, {217, 5}, {227, 5},
+ {227, 5}, {227, 5}, {227, 5}, {227, 5}, {227, 5}, {227, 5}, {227, 5},
+ {229, 5}, {229, 5}, {229, 5}, {229, 5}, {229, 5}, {229, 5}, {229, 5},
+ {229, 5}, {230, 5}, {230, 5}, {230, 5}, {230, 5}, {230, 5}, {230, 5},
+ {230, 5}, {230, 5}, {129, 6}, {129, 6}, {129, 6}, {129, 6}, {132, 6},
+ {132, 6}, {132, 6}, {132, 6}, {133, 6}, {133, 6}, {133, 6}, {133, 6},
+ {134, 6}, {134, 6}, {134, 6}, {134, 6}, {136, 6}, {136, 6}, {136, 6},
+ {136, 6}, {146, 6}, {146, 6}, {146, 6}, {146, 6}, {154, 6}, {154, 6},
+ {154, 6}, {154, 6}, {156, 6}, {156, 6}, {156, 6}, {156, 6}, {160, 6},
+ {160, 6}, {160, 6}, {160, 6}, {163, 6}, {163, 6}, {163, 6}, {163, 6},
+ {164, 6}, {164, 6}, {164, 6}, {164, 6}, {169, 6}, {169, 6}, {169, 6},
+ {169, 6}, {170, 6}, {170, 6}, {170, 6}, {170, 6}, {173, 6}, {173, 6},
+ {173, 6}, {173, 6}, {178, 6}, {178, 6}, {178, 6}, {178, 6}, {181, 6},
+ {181, 6}, {181, 6}, {181, 6}, {185, 6}, {185, 6}, {185, 6}, {185, 6},
+ {186, 6}, {186, 6}, {186, 6}, {186, 6}, {187, 6}, {187, 6}, {187, 6},
+ {187, 6}, {189, 6}, {189, 6}, {189, 6}, {189, 6}, {190, 6}, {190, 6},
+ {190, 6}, {190, 6}, {196, 6}, {196, 6}, {196, 6}, {196, 6}, {198, 6},
+ {198, 6}, {198, 6}, {198, 6}, {228, 6}, {228, 6}, {228, 6}, {228, 6},
+ {232, 6}, {232, 6}, {232, 6}, {232, 6}, {233, 6}, {233, 6}, {233, 6},
+ {233, 6}, {1, 7}, {1, 7}, {135, 7}, {135, 7}, {137, 7}, {137, 7},
+ {138, 7}, {138, 7}, {139, 7}, {139, 7}, {140, 7}, {140, 7}, {141, 7},
+ {141, 7}, {143, 7}, {143, 7}, {147, 7}, {147, 7}, {149, 7}, {149, 7},
+ {150, 7}, {150, 7}, {151, 7}, {151, 7}, {152, 7}, {152, 7}, {155, 7},
+ {155, 7}, {157, 7}, {157, 7}, {158, 7}, {158, 7}, {165, 7}, {165, 7},
+ {166, 7}, {166, 7}, {168, 7}, {168, 7}, {174, 7}, {174, 7}, {175, 7},
+ {175, 7}, {180, 7}, {180, 7}, {182, 7}, {182, 7}, {183, 7}, {183, 7},
+ {188, 7}, {188, 7}, {191, 7}, {191, 7}, {197, 7}, {197, 7}, {231, 7},
+ {231, 7}, {239, 7}, {239, 7}, {9, 8}, {142, 8}, {144, 8}, {145, 8},
+ {148, 8}, {159, 8}, {171, 8}, {206, 8}, {215, 8}, {225, 8}, {236, 8},
+ {237, 8},
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTables_255_255[] = {
+ &HuffmanIncoming_255_255_246, &HuffmanIncoming_255_255_247,
+ &HuffmanIncoming_255_255_248, &HuffmanIncoming_255_255_249,
+ &HuffmanIncoming_255_255_250, &HuffmanIncoming_255_255_251,
+ &HuffmanIncoming_255_255_252, &HuffmanIncoming_255_255_253,
+ &HuffmanIncoming_255_255_254, &HuffmanIncoming_255_255_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255 = {
+ HuffmanIncomingEntries_255_255, HuffmanIncomingNextTables_255_255, 246, 8};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255[] = {
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {35, 4},
+ {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4},
+ {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4},
+ {35, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4},
+ {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4},
+ {62, 4}, {62, 4}, {62, 4}, {0, 5}, {0, 5}, {0, 5}, {0, 5},
+ {0, 5}, {0, 5}, {0, 5}, {0, 5}, {36, 5}, {36, 5}, {36, 5},
+ {36, 5}, {36, 5}, {36, 5}, {36, 5}, {36, 5}, {64, 5}, {64, 5},
+ {64, 5}, {64, 5}, {64, 5}, {64, 5}, {64, 5}, {64, 5}, {91, 5},
+ {91, 5}, {91, 5}, {91, 5}, {91, 5}, {91, 5}, {91, 5}, {91, 5},
+ {93, 5}, {93, 5}, {93, 5}, {93, 5}, {93, 5}, {93, 5}, {93, 5},
+ {93, 5}, {126, 5}, {126, 5}, {126, 5}, {126, 5}, {126, 5}, {126, 5},
+ {126, 5}, {126, 5}, {94, 6}, {94, 6}, {94, 6}, {94, 6}, {125, 6},
+ {125, 6}, {125, 6}, {125, 6}, {60, 7}, {60, 7}, {96, 7}, {96, 7},
+ {123, 7}, {123, 7},
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTables_255[] = {
+ &HuffmanIncoming_255_254,
+ &HuffmanIncoming_255_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255 = {
+ HuffmanIncomingEntries_255, HuffmanIncomingNextTables_255, 254, 7};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntriesRoot[] = {
+ {48, 5}, {48, 5}, {48, 5}, {48, 5}, {48, 5}, {48, 5}, {48, 5},
+ {48, 5}, {49, 5}, {49, 5}, {49, 5}, {49, 5}, {49, 5}, {49, 5},
+ {49, 5}, {49, 5}, {50, 5}, {50, 5}, {50, 5}, {50, 5}, {50, 5},
+ {50, 5}, {50, 5}, {50, 5}, {97, 5}, {97, 5}, {97, 5}, {97, 5},
+ {97, 5}, {97, 5}, {97, 5}, {97, 5}, {99, 5}, {99, 5}, {99, 5},
+ {99, 5}, {99, 5}, {99, 5}, {99, 5}, {99, 5}, {101, 5}, {101, 5},
+ {101, 5}, {101, 5}, {101, 5}, {101, 5}, {101, 5}, {101, 5}, {105, 5},
+ {105, 5}, {105, 5}, {105, 5}, {105, 5}, {105, 5}, {105, 5}, {105, 5},
+ {111, 5}, {111, 5}, {111, 5}, {111, 5}, {111, 5}, {111, 5}, {111, 5},
+ {111, 5}, {115, 5}, {115, 5}, {115, 5}, {115, 5}, {115, 5}, {115, 5},
+ {115, 5}, {115, 5}, {116, 5}, {116, 5}, {116, 5}, {116, 5}, {116, 5},
+ {116, 5}, {116, 5}, {116, 5}, {32, 6}, {32, 6}, {32, 6}, {32, 6},
+ {37, 6}, {37, 6}, {37, 6}, {37, 6}, {45, 6}, {45, 6}, {45, 6},
+ {45, 6}, {46, 6}, {46, 6}, {46, 6}, {46, 6}, {47, 6}, {47, 6},
+ {47, 6}, {47, 6}, {51, 6}, {51, 6}, {51, 6}, {51, 6}, {52, 6},
+ {52, 6}, {52, 6}, {52, 6}, {53, 6}, {53, 6}, {53, 6}, {53, 6},
+ {54, 6}, {54, 6}, {54, 6}, {54, 6}, {55, 6}, {55, 6}, {55, 6},
+ {55, 6}, {56, 6}, {56, 6}, {56, 6}, {56, 6}, {57, 6}, {57, 6},
+ {57, 6}, {57, 6}, {61, 6}, {61, 6}, {61, 6}, {61, 6}, {65, 6},
+ {65, 6}, {65, 6}, {65, 6}, {95, 6}, {95, 6}, {95, 6}, {95, 6},
+ {98, 6}, {98, 6}, {98, 6}, {98, 6}, {100, 6}, {100, 6}, {100, 6},
+ {100, 6}, {102, 6}, {102, 6}, {102, 6}, {102, 6}, {103, 6}, {103, 6},
+ {103, 6}, {103, 6}, {104, 6}, {104, 6}, {104, 6}, {104, 6}, {108, 6},
+ {108, 6}, {108, 6}, {108, 6}, {109, 6}, {109, 6}, {109, 6}, {109, 6},
+ {110, 6}, {110, 6}, {110, 6}, {110, 6}, {112, 6}, {112, 6}, {112, 6},
+ {112, 6}, {114, 6}, {114, 6}, {114, 6}, {114, 6}, {117, 6}, {117, 6},
+ {117, 6}, {117, 6}, {58, 7}, {58, 7}, {66, 7}, {66, 7}, {67, 7},
+ {67, 7}, {68, 7}, {68, 7}, {69, 7}, {69, 7}, {70, 7}, {70, 7},
+ {71, 7}, {71, 7}, {72, 7}, {72, 7}, {73, 7}, {73, 7}, {74, 7},
+ {74, 7}, {75, 7}, {75, 7}, {76, 7}, {76, 7}, {77, 7}, {77, 7},
+ {78, 7}, {78, 7}, {79, 7}, {79, 7}, {80, 7}, {80, 7}, {81, 7},
+ {81, 7}, {82, 7}, {82, 7}, {83, 7}, {83, 7}, {84, 7}, {84, 7},
+ {85, 7}, {85, 7}, {86, 7}, {86, 7}, {87, 7}, {87, 7}, {89, 7},
+ {89, 7}, {106, 7}, {106, 7}, {107, 7}, {107, 7}, {113, 7}, {113, 7},
+ {118, 7}, {118, 7}, {119, 7}, {119, 7}, {120, 7}, {120, 7}, {121, 7},
+ {121, 7}, {122, 7}, {122, 7}, {38, 8}, {42, 8}, {44, 8}, {59, 8},
+ {88, 8}, {90, 8},
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTablesRoot[] = {
+ &HuffmanIncoming_254,
+ &HuffmanIncoming_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncomingRoot = {
+ HuffmanIncomingEntriesRoot, HuffmanIncomingNextTablesRoot, 254, 8};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanIncoming_h
diff --git a/netwerk/protocol/http/Http2HuffmanOutgoing.h b/netwerk/protocol/http/Http2HuffmanOutgoing.h
new file mode 100644
index 0000000000..376313ea91
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanOutgoing.h
@@ -0,0 +1,85 @@
+/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanOutgoing_h
+#define mozilla__net__Http2HuffmanOutgoing_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanOutgoingEntry {
+ uint32_t mValue;
+ uint8_t mLength;
+};
+
+static const HuffmanOutgoingEntry HuffmanOutgoing[] = {
+ {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28},
+ {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28},
+ {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28},
+ {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28},
+ {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28},
+ {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28},
+ {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28},
+ {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28},
+ {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12},
+ {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11},
+ {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11},
+ {0x000000fa, 8}, {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6},
+ {0x00000000, 5}, {0x00000001, 5}, {0x00000002, 5}, {0x00000019, 6},
+ {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6}, {0x0000001d, 6},
+ {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8},
+ {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10},
+ {0x00001ffa, 13}, {0x00000021, 6}, {0x0000005d, 7}, {0x0000005e, 7},
+ {0x0000005f, 7}, {0x00000060, 7}, {0x00000061, 7}, {0x00000062, 7},
+ {0x00000063, 7}, {0x00000064, 7}, {0x00000065, 7}, {0x00000066, 7},
+ {0x00000067, 7}, {0x00000068, 7}, {0x00000069, 7}, {0x0000006a, 7},
+ {0x0000006b, 7}, {0x0000006c, 7}, {0x0000006d, 7}, {0x0000006e, 7},
+ {0x0000006f, 7}, {0x00000070, 7}, {0x00000071, 7}, {0x00000072, 7},
+ {0x000000fc, 8}, {0x00000073, 7}, {0x000000fd, 8}, {0x00001ffb, 13},
+ {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022, 6},
+ {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5},
+ {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6},
+ {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7},
+ {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5},
+ {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5},
+ {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7},
+ {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15},
+ {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28},
+ {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20},
+ {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23},
+ {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23},
+ {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23},
+ {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23},
+ {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23},
+ {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23},
+ {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24},
+ {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22},
+ {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21},
+ {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24},
+ {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23},
+ {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21},
+ {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23},
+ {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22},
+ {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23},
+ {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19},
+ {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25},
+ {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27},
+ {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25},
+ {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27},
+ {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24},
+ {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26},
+ {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27},
+ {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21},
+ {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23},
+ {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25},
+ {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23},
+ {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26},
+ {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27},
+ {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27},
+ {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26},
+ {0x3fffffff, 30}};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanOutgoing_h
diff --git a/netwerk/protocol/http/Http2Push.cpp b/netwerk/protocol/http/Http2Push.cpp
new file mode 100644
index 0000000000..35cee9cd2a
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -0,0 +1,557 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "Http2Push.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpTransaction.h"
+#include "nsIHttpPushListener.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+// Because WeakPtr isn't thread-safe we must ensure that the object is destroyed
+// on the socket thread, so any Release() called on a different thread is
+// dispatched to the socket thread.
+bool Http2PushedStreamWrapper::DispatchRelease() {
+ if (OnSocketThread()) {
+ return false;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewNonOwningRunnableMethod("net::Http2PushedStreamWrapper::Release", this,
+ &Http2PushedStreamWrapper::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMPL_ADDREF(Http2PushedStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+Http2PushedStreamWrapper::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the socket thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "Http2PushedStreamWrapper");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(Http2PushedStreamWrapper)
+NS_INTERFACE_MAP_END
+
+Http2PushedStreamWrapper::Http2PushedStreamWrapper(
+ Http2PushedStream* aPushStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mStream = aPushStream;
+ mRequestString = aPushStream->GetRequestString();
+ mResourceUrl = aPushStream->GetResourceUrl();
+ mStreamID = aPushStream->StreamID();
+}
+
+Http2PushedStreamWrapper::~Http2PushedStreamWrapper() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+}
+
+Http2PushedStream* Http2PushedStreamWrapper::GetStream() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mStream) {
+ Http2StreamBase* stream = mStream;
+ return static_cast<Http2PushedStream*>(stream);
+ }
+ return nullptr;
+}
+
+void Http2PushedStreamWrapper::OnPushFailed() {
+ if (OnSocketThread()) {
+ if (mStream) {
+ Http2StreamBase* stream = mStream;
+ static_cast<Http2PushedStream*>(stream)->OnPushFailed();
+ }
+ } else {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("net::Http2PushedStreamWrapper::OnPushFailed", this,
+ &Http2PushedStreamWrapper::OnPushFailed),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+//////////////////////////////////////////
+// Http2PushedStream
+//////////////////////////////////////////
+
+Http2PushedStream::Http2PushedStream(
+ Http2PushTransactionBuffer* aTransaction, Http2Session* aSession,
+ Http2StreamBase* aAssociatedStream, uint32_t aID,
+ uint64_t aCurrentForegroundTabOuterContentWindowId)
+ : Http2StreamBase((aTransaction->QueryHttpTransaction())
+ ? aTransaction->QueryHttpTransaction()->BrowserId()
+ : 0,
+ aSession, 0, aCurrentForegroundTabOuterContentWindowId),
+ mAssociatedTransaction(aAssociatedStream->Transaction()),
+ mBufferedPush(aTransaction),
+ mTransaction(aTransaction) {
+ LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID));
+ mStreamID = aID;
+ MOZ_ASSERT(!(aID & 1)); // must be even to be a pushed stream
+ mBufferedPush->SetPushStream(this);
+ mRequestContext = aAssociatedStream->RequestContext();
+ mLastRead = TimeStamp::Now();
+ mPriorityDependency = aAssociatedStream->PriorityDependency();
+ if (mPriorityDependency == Http2Session::kUrgentStartGroupID ||
+ mPriorityDependency == Http2Session::kLeaderGroupID) {
+ mPriorityDependency = Http2Session::kFollowerGroupID;
+ }
+ // Cache this for later use in case of tab switch.
+ mDefaultPriorityDependency = mPriorityDependency;
+ SetPriorityDependency(aAssociatedStream->Priority() + 1, mPriorityDependency);
+ // Assume we are on the same tab as our associated stream, for priority
+ // purposes. It's possible this could change when we get paired with a sink,
+ // but it's unlikely and doesn't much matter anyway.
+ mTransactionBrowserId = aAssociatedStream->TransactionBrowserId();
+}
+
+bool Http2PushedStream::GetPushComplete() { return mPushCompleted; }
+
+nsresult Http2PushedStream::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ nsresult rv = Http2StreamBase::WriteSegments(writer, count, countWritten);
+ if (NS_SUCCEEDED(rv) && *countWritten) {
+ mLastRead = TimeStamp::Now();
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ mPushCompleted = true;
+ rv = NS_OK; // this is what a normal HTTP transaction would do
+ }
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) mStatus = rv;
+ return rv;
+}
+
+bool Http2PushedStream::DeferCleanup(nsresult status) {
+ LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 "\n", this,
+ static_cast<uint32_t>(status)));
+
+ if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer on success\n",
+ this, static_cast<uint32_t>(status)));
+ return true;
+ }
+ if (mDeferCleanupOnPush) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer onPush ref\n",
+ this, static_cast<uint32_t>(status)));
+ return true;
+ }
+ if (mConsumerStream) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32
+ " defer active consumer\n",
+ this, static_cast<uint32_t>(status)));
+ return true;
+ }
+ LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 " not deferred\n",
+ this, static_cast<uint32_t>(status)));
+ return false;
+}
+
+// return true if channel implements nsIHttpPushListener
+bool Http2PushedStream::TryOnPush() {
+ nsHttpTransaction* trans = mAssociatedTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return false;
+ }
+
+ if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) {
+ return false;
+ }
+
+ mDeferCleanupOnPush = true;
+ mResourceUrl = Origin() + Path();
+ RefPtr<Http2PushedStreamWrapper> stream = new Http2PushedStreamWrapper(this);
+ trans->OnPush(stream);
+ return true;
+}
+
+// side effect free static method to determine if Http2StreamBase implements
+// nsIHttpPushListener
+bool Http2PushedStream::TestOnPush(Http2StreamBase* stream) {
+ if (!stream) {
+ return false;
+ }
+ nsAHttpTransaction* abstractTransaction = stream->Transaction();
+ if (!abstractTransaction) {
+ return false;
+ }
+ nsHttpTransaction* trans = abstractTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return false;
+ }
+ return trans->Caps() & NS_HTTP_ONPUSH_LISTENER;
+}
+
+nsresult Http2PushedStream::ReadSegments(nsAHttpSegmentReader* reader, uint32_t,
+ uint32_t* count) {
+ nsresult rv = NS_OK;
+ *count = 0;
+
+ mozilla::OriginAttributes originAttributes;
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS: {
+ // The request headers for this has been processed, so we need to verify
+ // that :authority, :scheme, and :path MUST be present. :method MUST NOT
+ // be present
+ mSocketTransport->GetOriginAttributes(&originAttributes);
+ RefPtr<Http2Session> session = Session();
+ CreatePushHashKey(mHeaderScheme, mHeaderHost, originAttributes,
+ session->Serial(), mHeaderPath, mOrigin, mHashKey);
+
+ LOG3(("Http2PushStream 0x%X hash key %s\n", mStreamID, mHashKey.get()));
+
+ // the write side of a pushed transaction just involves manipulating a
+ // little state
+ SetSentFin(true);
+ Http2StreamBase::mRequestHeadersDone = 1;
+ Http2StreamBase::mOpenGenerated = 1;
+ Http2StreamBase::ChangeState(UPSTREAM_COMPLETE);
+ } break;
+
+ case UPSTREAM_COMPLETE:
+ // Let's just clear the stream's transmit buffer by pushing it into
+ // the session. This is probably a window adjustment.
+ LOG3(("Http2Push::ReadSegments 0x%X \n", mStreamID));
+ mSegmentReader = reader;
+ rv = TransmitFrame(nullptr, nullptr, true);
+ mSegmentReader = nullptr;
+ break;
+
+ case GENERATING_BODY:
+ case SENDING_BODY:
+ case SENDING_FIN_STREAM:
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+void Http2PushedStream::AdjustInitialWindow() {
+ LOG3(("Http2PushStream %p 0x%X AdjustInitialWindow", this, mStreamID));
+ if (mConsumerStream) {
+ LOG3(
+ ("Http2PushStream::AdjustInitialWindow %p 0x%X "
+ "calling super consumer %p 0x%X\n",
+ this, mStreamID, mConsumerStream, mConsumerStream->StreamID()));
+ Http2StreamBase::AdjustInitialWindow();
+ // Http2PushedStream::ReadSegments is needed to call TransmitFrame()
+ // and actually get this information into the session bytestream
+ RefPtr<Http2Session> session = Session();
+ session->TransactionHasDataToWrite(this);
+ }
+ // Otherwise, when we get hooked up, the initial window will get bumped
+ // anyway, so we're good to go.
+}
+
+void Http2PushedStream::SetConsumerStream(Http2StreamBase* consumer) {
+ LOG3(("Http2PushedStream::SetConsumerStream this=%p consumer=%p", this,
+ consumer));
+
+ mConsumerStream = consumer;
+ mDeferCleanupOnPush = false;
+}
+
+bool Http2PushedStream::GetHashKey(nsCString& key) {
+ if (mHashKey.IsEmpty()) return false;
+
+ key = mHashKey;
+ return true;
+}
+
+void Http2PushedStream::ConnectPushedStream(Http2StreamBase* stream) {
+ RefPtr<Http2Session> session = Session();
+ session->ConnectPushedStream(stream);
+}
+
+bool Http2PushedStream::IsOrphaned(TimeStamp now) {
+ MOZ_ASSERT(!now.IsNull());
+
+ // if session is not transmitting, and is also not connected to a consumer
+ // stream, and its been like that for too long then it is oprhaned
+
+ if (mConsumerStream || mDeferCleanupOnPush) {
+ return false;
+ }
+
+ if (mOnPushFailed) {
+ return true;
+ }
+
+ bool rv = ((now - mLastRead).ToSeconds() > 30.0);
+ if (rv) {
+ LOG3(("Http2PushedStream:IsOrphaned 0x%X IsOrphaned %3.2f\n", mStreamID,
+ (now - mLastRead).ToSeconds()));
+ }
+ return rv;
+}
+
+nsresult Http2PushedStream::GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!*countWritten) {
+ rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ return rv;
+}
+
+void Http2PushedStream::CurrentBrowserIdChanged(uint64_t id) {
+ if (mConsumerStream) {
+ // Pass through to our sink, who will handle things appropriately.
+ mConsumerStream->CurrentBrowserIdChanged(id);
+ return;
+ }
+
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+
+ mCurrentBrowserId = id;
+ RefPtr<Http2Session> session = Session();
+ if (!session->UseH2Deps()) {
+ return;
+ }
+
+ uint32_t oldDependency = mPriorityDependency;
+ if (mTransactionBrowserId != mCurrentBrowserId) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ nsHttp::NotifyActiveTabLoadOptimization();
+ } else {
+ mPriorityDependency = mDefaultPriorityDependency;
+ }
+
+ if (mPriorityDependency != oldDependency) {
+ session->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight);
+ }
+}
+
+// ConvertPushHeaders is used to convert the pushed request headers
+// into HTTP/1 format and report some telemetry
+nsresult Http2PushedStream::ConvertPushHeaders(Http2Decompressor* decompressor,
+ nsACString& aHeadersIn,
+ nsACString& aHeadersOut) {
+ nsresult rv = decompressor->DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(aHeadersIn.BeginReading()),
+ aHeadersIn.Length(), aHeadersOut, true);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2PushedStream::ConvertPushHeaders %p Error\n", this));
+ return rv;
+ }
+
+ nsCString method;
+ decompressor->GetHost(mHeaderHost);
+ decompressor->GetScheme(mHeaderScheme);
+ decompressor->GetPath(mHeaderPath);
+
+ if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() ||
+ mHeaderPath.IsEmpty()) {
+ LOG3(
+ ("Http2PushedStream::ConvertPushHeaders %p Error - missing required "
+ "host=%s scheme=%s path=%s\n",
+ this, mHeaderHost.get(), mHeaderScheme.get(), mHeaderPath.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ decompressor->GetMethod(method);
+ if (!method.EqualsLiteral("GET")) {
+ LOG3(
+ ("Http2PushedStream::ConvertPushHeaders %p Error - method not "
+ "supported: "
+ "%s\n",
+ this, method.get()));
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ aHeadersIn.Truncate();
+ LOG(("id 0x%X decoded push headers %s %s %s are:\n%s", mStreamID,
+ mHeaderScheme.get(), mHeaderHost.get(), mHeaderPath.get(),
+ aHeadersOut.BeginReading()));
+ return NS_OK;
+}
+
+nsresult Http2PushedStream::CallToReadData(uint32_t count,
+ uint32_t* countRead) {
+ return mTransaction->ReadSegments(this, count, countRead);
+}
+
+nsresult Http2PushedStream::CallToWriteData(uint32_t count,
+ uint32_t* countWritten) {
+ return mTransaction->WriteSegments(this, count, countWritten);
+}
+
+nsresult Http2PushedStream::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void Http2PushedStream::CloseStream(nsresult reason) {
+ mTransaction->Close(reason);
+ mSession = nullptr;
+}
+
+//////////////////////////////////////////
+// Http2PushTransactionBuffer
+// This is the nsAHttpTransction owned by the stream when the pushed
+// stream has not yet been matched with a pull request
+//////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer)
+
+Http2PushTransactionBuffer::Http2PushTransactionBuffer() {
+ mBufferedHTTP1 = MakeUnique<char[]>(mBufferedHTTP1Size);
+}
+
+Http2PushTransactionBuffer::~Http2PushTransactionBuffer() {
+ delete mRequestHead;
+}
+
+void Http2PushTransactionBuffer::SetConnection(nsAHttpConnection* conn) {}
+
+nsAHttpConnection* Http2PushTransactionBuffer::Connection() { return nullptr; }
+
+void Http2PushTransactionBuffer::GetSecurityCallbacks(
+ nsIInterfaceRequestor** outCB) {
+ *outCB = nullptr;
+}
+
+void Http2PushTransactionBuffer::OnTransportStatus(nsITransport* transport,
+ nsresult status,
+ int64_t progress) {}
+
+nsHttpConnectionInfo* Http2PushTransactionBuffer::ConnectionInfo() {
+ if (!mPushStream) {
+ return nullptr;
+ }
+ if (!mPushStream->Transaction()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(mPushStream->Transaction() != this);
+ return mPushStream->Transaction()->ConnectionInfo();
+}
+
+bool Http2PushTransactionBuffer::IsDone() { return mIsDone; }
+
+nsresult Http2PushTransactionBuffer::Status() { return mStatus; }
+
+uint32_t Http2PushTransactionBuffer::Caps() { return 0; }
+
+uint64_t Http2PushTransactionBuffer::Available() {
+ return mBufferedHTTP1Used - mBufferedHTTP1Consumed;
+}
+
+nsresult Http2PushTransactionBuffer::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) {
+ *countRead = 0;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult Http2PushTransactionBuffer::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) {
+ EnsureBuffer(mBufferedHTTP1, mBufferedHTTP1Size + kDefaultBufferSize,
+ mBufferedHTTP1Used, mBufferedHTTP1Size);
+ }
+
+ count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used);
+ nsresult rv = writer->OnWriteSegment(&mBufferedHTTP1[mBufferedHTTP1Used],
+ count, countWritten);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferedHTTP1Used += *countWritten;
+ } else if (rv == NS_BASE_STREAM_CLOSED) {
+ mIsDone = true;
+ }
+
+ if (Available() || mIsDone) {
+ Http2StreamBase* consumer = mPushStream->GetConsumerStream();
+
+ if (consumer) {
+ LOG3(
+ ("Http2PushTransactionBuffer::WriteSegments notifying connection "
+ "consumer data available 0x%X [%" PRIu64 "] done=%d\n",
+ mPushStream->StreamID(), Available(), mIsDone));
+ mPushStream->ConnectPushedStream(consumer);
+ }
+ }
+
+ return rv;
+}
+
+uint32_t Http2PushTransactionBuffer::Http1xTransactionCount() { return 0; }
+
+nsHttpRequestHead* Http2PushTransactionBuffer::RequestHead() {
+ if (!mRequestHead) mRequestHead = new nsHttpRequestHead();
+ return mRequestHead;
+}
+
+nsresult Http2PushTransactionBuffer::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void Http2PushTransactionBuffer::SetProxyConnectFailed() {}
+
+void Http2PushTransactionBuffer::Close(nsresult reason) {
+ mStatus = reason;
+ mIsDone = true;
+}
+
+nsresult Http2PushTransactionBuffer::GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ *countWritten = std::min(count, static_cast<uint32_t>(Available()));
+ if (*countWritten) {
+ memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten);
+ mBufferedHTTP1Consumed += *countWritten;
+ }
+
+ // If all the data has been consumed then reset the buffer
+ if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) {
+ mBufferedHTTP1Consumed = 0;
+ mBufferedHTTP1Used = 0;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Push.h b/netwerk/protocol/http/Http2Push.h
new file mode 100644
index 0000000000..b7e25c2028
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.h
@@ -0,0 +1,182 @@
+/* -*- 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_Http2Push_Internal_h
+#define mozilla_net_Http2Push_Internal_h
+
+// HTTP/2 - RFC 7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "Http2Session.h"
+#include "Http2StreamBase.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsHttpRequestHead.h"
+#include "nsIRequestContext.h"
+#include "nsString.h"
+#include "PSpdyPush.h"
+
+namespace mozilla {
+namespace net {
+
+class Http2PushTransactionBuffer;
+
+class Http2PushedStream final : public Http2StreamBase {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http2PushedStream, override)
+
+ Http2PushedStream(Http2PushTransactionBuffer* aTransaction,
+ Http2Session* aSession, Http2StreamBase* aAssociatedStream,
+ uint32_t aID,
+ uint64_t aCurrentForegroundTabOuterContentWindowId);
+
+ Http2PushedStream* GetHttp2PushedStream() override { return this; }
+ bool GetPushComplete();
+
+ // The consumer stream is the synthetic pull stream hooked up to this push
+ Http2StreamBase* GetConsumerStream() { return mConsumerStream; };
+
+ void SetConsumerStream(Http2StreamBase* consumer);
+ [[nodiscard]] bool GetHashKey(nsCString& key);
+
+ // override of Http2StreamBase
+ [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*) override;
+ [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*) override;
+ void AdjustInitialWindow() override;
+
+ nsAHttpTransaction* Transaction() override { return mTransaction; }
+ nsIRequestContext* RequestContext() override { return mRequestContext; };
+ void ConnectPushedStream(Http2StreamBase* stream);
+
+ [[nodiscard]] bool TryOnPush();
+ [[nodiscard]] static bool TestOnPush(Http2StreamBase* stream);
+
+ virtual bool DeferCleanup(nsresult status) override;
+ void SetDeferCleanupOnSuccess(bool val) { mDeferCleanupOnSuccess = val; }
+
+ bool IsOrphaned(TimeStamp now);
+ void OnPushFailed() {
+ mDeferCleanupOnPush = false;
+ mOnPushFailed = true;
+ }
+
+ [[nodiscard]] nsresult GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten);
+
+ // overload of Http2StreamBase
+ virtual bool HasSink() override { return !!mConsumerStream; }
+ void SetPushComplete() { mPushCompleted = true; }
+ virtual void CurrentBrowserIdChanged(uint64_t) override;
+
+ nsCString& GetRequestString() { return mRequestString; }
+ nsCString& GetResourceUrl() { return mResourceUrl; }
+
+ nsresult ConvertPushHeaders(Http2Decompressor* decompressor,
+ nsACString& aHeadersIn, nsACString& aHeadersOut);
+
+ void CloseStream(nsresult reason) override;
+
+ protected:
+ nsresult CallToReadData(uint32_t count, uint32_t* countRead) override;
+ nsresult CallToWriteData(uint32_t count, uint32_t* countWritten) override;
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+
+ private:
+ virtual ~Http2PushedStream() = default;
+ // paired request stream that consumes from real http/2 one.. null until a
+ // match is made.
+ Http2StreamBase* mConsumerStream{nullptr};
+
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ nsAHttpTransaction* mAssociatedTransaction;
+
+ Http2PushTransactionBuffer* mBufferedPush;
+ mozilla::TimeStamp mLastRead;
+
+ nsCString mHashKey;
+ nsresult mStatus{NS_OK};
+ bool mPushCompleted{false}; // server push FIN received
+ bool mDeferCleanupOnSuccess{true};
+
+ // mDeferCleanupOnPush prevents Http2Session::CleanupStream() from
+ // destroying the push stream on an error code during the period between
+ // when we need to do OnPush() on another thread and the time it takes
+ // for that event to create a synthetic pull stream attached to this
+ // object. That synthetic pull will become mConsuemerStream.
+ // Ths is essentially a delete protecting reference.
+ bool mDeferCleanupOnPush{false};
+ bool mOnPushFailed{false};
+ nsCString mRequestString;
+ nsCString mResourceUrl;
+
+ uint32_t mDefaultPriorityDependency;
+
+ // The underlying HTTP transaction. This pointer is used as the key
+ // in the Http2Session mStreamTransactionHash so it is important to
+ // keep a reference to it as long as this stream is a member of that hash.
+ // (i.e. don't change it or release it after it is set in the ctor).
+ RefPtr<nsAHttpTransaction> const mTransaction;
+};
+
+class Http2PushTransactionBuffer final : public nsAHttpTransaction {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+
+ Http2PushTransactionBuffer();
+
+ [[nodiscard]] nsresult GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten);
+ void SetPushStream(Http2PushedStream* stream) { mPushStream = stream; }
+
+ private:
+ virtual ~Http2PushTransactionBuffer();
+ uint64_t Available();
+
+ const static uint32_t kDefaultBufferSize = 4096;
+
+ nsresult mStatus{NS_OK};
+ nsHttpRequestHead* mRequestHead{nullptr};
+ Http2PushedStream* mPushStream{nullptr};
+ bool mIsDone{false};
+
+ UniquePtr<char[]> mBufferedHTTP1;
+ uint32_t mBufferedHTTP1Size{kDefaultBufferSize};
+ uint32_t mBufferedHTTP1Used{0};
+ uint32_t mBufferedHTTP1Consumed{0};
+};
+
+class Http2PushedStreamWrapper : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ bool DispatchRelease();
+
+ explicit Http2PushedStreamWrapper(Http2PushedStream* aPushStream);
+
+ nsCString& GetRequestString() { return mRequestString; }
+ nsCString& GetResourceUrl() { return mResourceUrl; }
+ Http2PushedStream* GetStream();
+ void OnPushFailed();
+ uint32_t StreamID() { return mStreamID; }
+
+ private:
+ virtual ~Http2PushedStreamWrapper();
+
+ nsCString mRequestString;
+ nsCString mResourceUrl;
+ uint32_t mStreamID;
+ WeakPtr<Http2StreamBase> mStream;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Push_Internal_h
diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp
new file mode 100644
index 0000000000..e969d60c4d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -0,0 +1,4513 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "AltServiceChild.h"
+#include "CacheControlParser.h"
+#include "CachePushChecker.h"
+#include "Http2Push.h"
+#include "Http2Session.h"
+#include "Http2Stream.h"
+#include "Http2StreamBase.h"
+#include "Http2StreamTunnel.h"
+#include "LoadContextInfo.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsHttp.h"
+#include "nsHttpConnection.h"
+#include "nsHttpHandler.h"
+#include "nsIRequestContext.h"
+#include "nsISupportsPriority.h"
+#include "nsITLSSocketControl.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+#include "prnetdb.h"
+#include "sslerr.h"
+#include "sslt.h"
+
+namespace mozilla {
+namespace net {
+
+// Http2Session has multiple inheritance of things that implement nsISupports
+NS_IMPL_ADDREF(Http2Session)
+NS_IMPL_RELEASE(Http2Session)
+NS_INTERFACE_MAP_BEGIN(Http2Session)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(Http2Session)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+static void RemoveStreamFromQueue(Http2StreamBase* aStream,
+ nsTArray<WeakPtr<Http2StreamBase>>& queue) {
+ for (const auto& stream : Reversed(queue)) {
+ if (stream == aStream) {
+ queue.RemoveElement(stream);
+ }
+ }
+}
+
+static void AddStreamToQueue(Http2StreamBase* aStream,
+ nsTArray<WeakPtr<Http2StreamBase>>& queue) {
+ if (!queue.Contains(aStream)) {
+ queue.AppendElement(aStream);
+ }
+}
+
+static already_AddRefed<Http2StreamBase> GetNextStreamFromQueue(
+ nsTArray<WeakPtr<Http2StreamBase>>& queue) {
+ while (!queue.IsEmpty() && !queue[0]) {
+ MOZ_ASSERT(false);
+ queue.RemoveElementAt(0);
+ }
+ if (queue.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<Http2StreamBase> stream = queue[0].get();
+ queue.RemoveElementAt(0);
+ return stream.forget();
+}
+
+// "magic" refers to the string that preceeds HTTP/2 on the wire
+// to help find any intermediaries speaking an older version of HTTP
+const uint8_t Http2Session::kMagicHello[] = {
+ 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
+ 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
+
+Http2Session* Http2Session::CreateSession(nsISocketTransport* aSocketTransport,
+ enum SpdyVersion version,
+ bool attemptingEarlyData) {
+ if (!gHttpHandler) {
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ Unused << handler.get();
+ }
+
+ Http2Session* session =
+ new Http2Session(aSocketTransport, version, attemptingEarlyData);
+ session->SendHello();
+ return session;
+}
+
+Http2Session::Http2Session(nsISocketTransport* aSocketTransport,
+ enum SpdyVersion version, bool attemptingEarlyData)
+ : mSocketTransport(aSocketTransport),
+ mSegmentReader(nullptr),
+ mSegmentWriter(nullptr),
+ mNextStreamID(3) // 1 is reserved for Updgrade handshakes
+ ,
+ mLastPushedID(0),
+ mConcurrentHighWater(0),
+ mDownstreamState(BUFFERING_OPENING_SETTINGS),
+ mInputFrameBufferSize(kDefaultBufferSize),
+ mInputFrameBufferUsed(0),
+ mInputFrameDataSize(0),
+ mInputFrameDataRead(0),
+ mInputFrameFinal(false),
+ mInputFrameType(0),
+ mInputFrameFlags(0),
+ mInputFrameID(0),
+ mPaddingLength(0),
+ mInputFrameDataStream(nullptr),
+ mNeedsCleanup(nullptr),
+ mDownstreamRstReason(NO_HTTP_ERROR),
+ mExpectedHeaderID(0),
+ mExpectedPushPromiseID(0),
+ mContinuedPromiseStream(0),
+ mFlatHTTPResponseHeadersOut(0),
+ mShouldGoAway(false),
+ mClosed(false),
+ mCleanShutdown(false),
+ mReceivedSettings(false),
+ mTLSProfileConfirmed(false),
+ mGoAwayReason(NO_HTTP_ERROR),
+ mClientGoAwayReason(UNASSIGNED),
+ mPeerGoAwayReason(UNASSIGNED),
+ mGoAwayID(0),
+ mOutgoingGoAwayID(0),
+ mConcurrent(0),
+ mServerPushedResources(0),
+ mServerInitialStreamWindow(kDefaultRwin),
+ mLocalSessionWindow(kDefaultRwin),
+ mServerSessionWindow(kDefaultRwin),
+ mInitialRwin(ASpdySession::kInitialRwin),
+ mOutputQueueSize(kDefaultQueueSize),
+ mOutputQueueUsed(0),
+ mOutputQueueSent(0),
+ mLastReadEpoch(PR_IntervalNow()),
+ mPingSentEpoch(0),
+ mPreviousUsed(false),
+ mAggregatedHeaderSize(0),
+ mWaitingForSettingsAck(false),
+ mGoAwayOnPush(false),
+ mUseH2Deps(false),
+ mAttemptingEarlyData(attemptingEarlyData),
+ mOriginFrameActivated(false),
+ mCntActivated(0),
+ mTlsHandshakeFinished(false),
+ mPeerFailedHandshake(false),
+ mTrrStreams(0),
+ mEnableWebsockets(false),
+ mPeerAllowsWebsockets(false),
+ mProcessedWaitingWebsockets(false) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ static uint64_t sSerial;
+ mSerial = ++sSerial;
+
+ LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n", this, mSerial));
+
+ mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
+ mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
+ mDecompressBuffer.SetCapacity(kDefaultBufferSize);
+
+ mPushAllowance = gHttpHandler->SpdyPushAllowance();
+ mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
+ mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
+ mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
+
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ mPingThreshold = gHttpHandler->SpdyPingThreshold();
+ mPreviousPingThreshold = mPingThreshold;
+ mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId();
+
+ mEnableWebsockets = StaticPrefs::network_http_http2_websockets();
+
+ bool dumpHpackTables = StaticPrefs::network_http_http2_enable_hpack_dump();
+ mCompressor.SetDumpTables(dumpHpackTables);
+ mDecompressor.SetDumpTables(dumpHpackTables);
+}
+
+void Http2Session::Shutdown(nsresult aReason) {
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ ShutdownStream(stream, aReason);
+ }
+
+ for (auto& stream : mTunnelStreams) {
+ ShutdownStream(stream, aReason);
+ }
+}
+
+void Http2Session::ShutdownStream(Http2StreamBase* aStream, nsresult aReason) {
+ // On a clean server hangup the server sets the GoAwayID to be the ID of
+ // the last transaction it processed. If the ID of stream in the
+ // local stream is greater than that it can safely be restarted because the
+ // server guarantees it was not partially processed. Streams that have not
+ // registered an ID haven't actually been sent yet so they can always be
+ // restarted.
+ if (mCleanShutdown &&
+ (aStream->StreamID() > mGoAwayID || !aStream->HasRegisteredID())) {
+ CloseStream(aStream, NS_ERROR_NET_RESET); // can be restarted
+ } else if (aStream->RecvdData()) {
+ CloseStream(aStream, NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else if (mGoAwayReason == INADEQUATE_SECURITY) {
+ CloseStream(aStream, NS_ERROR_NET_INADEQUATE_SECURITY);
+ } else if (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) {
+ CloseStream(aStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY);
+ } else if (!mCleanShutdown && SecurityErrorThatMayNeedRestart(aReason)) {
+ CloseStream(aStream, aReason);
+ } else {
+ CloseStream(aStream, NS_ERROR_ABORT);
+ }
+}
+
+Http2Session::~Http2Session() {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread());
+ LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X", this,
+ mDownstreamState));
+
+ Shutdown(NS_OK);
+
+ if (mTrrStreams) {
+ Telemetry::Accumulate(Telemetry::DNS_TRR_REQUEST_PER_CONN, mTrrStreams);
+ }
+ Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
+ Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN_3, mCntActivated);
+ Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
+ mServerPushedResources);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
+ Telemetry::Accumulate(Telemetry::HTTP2_FAIL_BEFORE_SETTINGS,
+ mPeerFailedHandshake);
+}
+
+inline nsresult Http2Session::SessionError(enum errorType reason) {
+ LOG3(("Http2Session::SessionError %p reason=0x%x mPeerGoAwayReason=0x%x",
+ this, reason, mPeerGoAwayReason));
+ mGoAwayReason = reason;
+
+ if (reason == INADEQUATE_SECURITY) {
+ // This one is special, as we have an error page just for this
+ return NS_ERROR_NET_INADEQUATE_SECURITY;
+ }
+
+ // We're the one sending a generic GOAWAY
+ return NS_ERROR_NET_HTTP2_SENT_GOAWAY;
+}
+
+void Http2Session::LogIO(Http2Session* self, Http2StreamBase* stream,
+ const char* label, const char* data,
+ uint32_t datalen) {
+ if (!LOG5_ENABLED()) return;
+
+ LOG5(("Http2Session::LogIO %p stream=%p id=0x%X [%s]", self, stream,
+ stream ? stream->StreamID() : 0, label));
+
+ // Max line is (16 * 3) + 10(prefix) + newline + null
+ char linebuf[128];
+ uint32_t index;
+ char* line = linebuf;
+
+ linebuf[127] = 0;
+
+ for (index = 0; index < datalen; ++index) {
+ if (!(index % 16)) {
+ if (index) {
+ *line = 0;
+ LOG5(("%s", linebuf));
+ }
+ line = linebuf;
+ snprintf(line, 128, "%08X: ", index);
+ line += 10;
+ }
+ snprintf(line, 128 - (line - linebuf), "%02X ",
+ (reinterpret_cast<const uint8_t*>(data))[index]);
+ line += 3;
+ }
+ if (index) {
+ *line = 0;
+ LOG5(("%s", linebuf));
+ }
+}
+
+using Http2ControlFx = nsresult (*)(Http2Session*);
+static Http2ControlFx sControlFunctions[] = {
+ nullptr, // type 0 data is not a control function
+ Http2Session::RecvHeaders,
+ Http2Session::RecvPriority,
+ Http2Session::RecvRstStream,
+ Http2Session::RecvSettings,
+ Http2Session::RecvPushPromise,
+ Http2Session::RecvPing,
+ Http2Session::RecvGoAway,
+ Http2Session::RecvWindowUpdate,
+ Http2Session::RecvContinuation,
+ Http2Session::RecvAltSvc, // extension for type 0x0A
+ Http2Session::RecvUnused, // 0x0B was BLOCKED still radioactive
+ Http2Session::RecvOrigin // extension for type 0x0C
+};
+
+bool Http2Session::RoomForMoreConcurrent() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return (mConcurrent < mMaxConcurrent);
+}
+
+bool Http2Session::RoomForMoreStreams() {
+ if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) {
+ return false;
+ }
+
+ return !mShouldGoAway;
+}
+
+PRIntervalTime Http2Session::IdleTime() {
+ return PR_IntervalNow() - mLastDataReadEpoch;
+}
+
+uint32_t Http2Session::ReadTimeoutTick(PRIntervalTime now) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n", this,
+ PR_IntervalToSeconds(now - mLastReadEpoch)));
+
+ if (!mPingThreshold) {
+ return UINT32_MAX;
+ }
+
+ if ((now - mLastReadEpoch) < mPingThreshold) {
+ // recent activity means ping is not an issue
+ if (mPingSentEpoch) {
+ mPingSentEpoch = 0;
+ if (mPreviousUsed) {
+ // restore the former value
+ mPingThreshold = mPreviousPingThreshold;
+ mPreviousUsed = false;
+ }
+ }
+
+ return PR_IntervalToSeconds(mPingThreshold) -
+ PR_IntervalToSeconds(now - mLastReadEpoch);
+ }
+
+ if (mPingSentEpoch) {
+ bool isTrr = (mTrrStreams > 0);
+ uint32_t pingTimeout = isTrr ? StaticPrefs::network_trr_ping_timeout()
+ : gHttpHandler->SpdyPingTimeout();
+ LOG3(
+ ("Http2Session::ReadTimeoutTick %p handle outstanding ping, "
+ "timeout=%d\n",
+ this, pingTimeout));
+ if ((now - mPingSentEpoch) >= pingTimeout) {
+ LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
+ if (mConnection) {
+ mConnection->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
+ }
+ mPingSentEpoch = 0;
+ if (isTrr) {
+ // These must be set this way to ensure we gracefully restart all
+ // streams
+ mGoAwayID = 0;
+ mCleanShutdown = true;
+ // If TRR is mode 2, this Http2Session will be closed due to TRR request
+ // timeout, so we won't reach this code. If we are in mode 3, the
+ // request timeout is usually larger than the ping timeout. We close the
+ // stream with NS_ERROR_NET_RESET, so the transactions can be restarted.
+ Close(NS_ERROR_NET_RESET);
+ } else {
+ Close(NS_ERROR_NET_TIMEOUT);
+ }
+ return UINT32_MAX;
+ }
+ return 1; // run the tick aggressively while ping is outstanding
+ }
+
+ LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
+
+ mPingSentEpoch = PR_IntervalNow();
+ if (!mPingSentEpoch) {
+ mPingSentEpoch = 1; // avoid the 0 sentinel value
+ }
+ GeneratePing(false);
+ Unused << ResumeRecv(); // read the ping reply
+
+ // Check for orphaned push streams. This looks expensive, but generally the
+ // list is empty.
+ Http2PushedStream* deleteMe;
+ TimeStamp timestampNow;
+ do {
+ deleteMe = nullptr;
+
+ for (uint32_t index = mPushedStreams.Length(); index > 0; --index) {
+ Http2PushedStream* pushedStream = mPushedStreams[index - 1];
+
+ if (timestampNow.IsNull()) {
+ timestampNow = TimeStamp::Now(); // lazy initializer
+ }
+
+ // if stream finished, but is not connected, and its been like that for
+ // long then cleanup the stream.
+ if (pushedStream->IsOrphaned(timestampNow)) {
+ LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n", this,
+ pushedStream->StreamID()));
+ deleteMe = pushedStream;
+ break; // don't CleanupStream() while iterating this vector
+ }
+ }
+ if (deleteMe) CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
+
+ } while (deleteMe);
+
+ return 1; // run the tick aggressively while ping is outstanding
+}
+
+uint32_t Http2Session::RegisterStreamID(Http2StreamBase* stream,
+ uint32_t aNewID) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mNextStreamID < 0xfffffff0,
+ "should have stopped admitting streams");
+ MOZ_ASSERT(!(aNewID & 1),
+ "0 for autoassign pull, otherwise explicit even push assignment");
+
+ if (!aNewID) {
+ // auto generate a new pull stream ID
+ aNewID = mNextStreamID;
+ MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
+ mNextStreamID += 2;
+ }
+
+ LOG1(
+ ("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
+ "concurrent=%d",
+ this, stream, aNewID, mConcurrent));
+
+ // We've used up plenty of ID's on this session. Start
+ // moving to a new one before there is a crunch involving
+ // server push streams or concurrent non-registered submits
+ if (aNewID >= kMaxStreamID) mShouldGoAway = true;
+
+ // integrity check
+ if (mStreamIDHash.Contains(aNewID)) {
+ LOG3((" New ID already present\n"));
+ MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
+ mShouldGoAway = true;
+ return kDeadStreamID;
+ }
+
+ mStreamIDHash.InsertOrUpdate(aNewID, stream);
+
+ if (aNewID & 1) {
+ // don't count push streams here
+ RefPtr<nsHttpConnectionInfo> ci(stream->ConnectionInfo());
+ if (ci && ci->GetIsTrrServiceChannel()) {
+ IncrementTrrCounter();
+ }
+ }
+ return aNewID;
+}
+
+bool Http2Session::AddStream(nsAHttpTransaction* aHttpTransaction,
+ int32_t aPriority,
+ nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // integrity check
+ if (mStreamTransactionHash.Contains(aHttpTransaction)) {
+ LOG3((" New transaction already present\n"));
+ MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
+ return false;
+ }
+
+ if (!mConnection) {
+ mConnection = aHttpTransaction->Connection();
+ }
+
+ if (!mFirstHttpTransaction && !mTlsHandshakeFinished) {
+ mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
+ LOG3(("Http2Session::AddStream first session=%p trans=%p ", this,
+ mFirstHttpTransaction.get()));
+ }
+
+ if (mClosed || mShouldGoAway) {
+ nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
+ if (trans) {
+ RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
+ pushedStreamWrapper = trans->GetPushedStream();
+ if (!pushedStreamWrapper || !pushedStreamWrapper->GetStream()) {
+ LOG3(
+ ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - "
+ "resched.\n",
+ this, aHttpTransaction, trans));
+ aHttpTransaction->SetConnection(nullptr);
+ nsresult rv =
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::AddStream %p atrans=%p trans=%p failed to "
+ "initiate "
+ "transaction (%08x).\n",
+ this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
+ }
+ return true;
+ }
+ }
+ }
+
+ aHttpTransaction->SetConnection(this);
+ aHttpTransaction->OnActivated();
+
+ CreateStream(aHttpTransaction, aPriority, Http2StreamBaseType::Normal);
+ return true;
+}
+
+void Http2Session::CreateStream(nsAHttpTransaction* aHttpTransaction,
+ int32_t aPriority,
+ Http2StreamBaseType streamType) {
+ RefPtr<Http2StreamBase> refStream;
+ switch (streamType) {
+ case Http2StreamBaseType::Normal:
+ refStream =
+ new Http2Stream(aHttpTransaction, this, aPriority, mCurrentBrowserId);
+ break;
+ case Http2StreamBaseType::WebSocket:
+ case Http2StreamBaseType::Tunnel:
+ case Http2StreamBaseType::ServerPush:
+ MOZ_RELEASE_ASSERT(false);
+ return;
+ }
+
+ LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
+ "NextID=0x%X (tentative)",
+ this, refStream.get(), mSerial, mNextStreamID));
+
+ RefPtr<Http2StreamBase> stream = refStream;
+ mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, std::move(refStream));
+
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+
+ // Kick off the SYN transmit without waiting for the poll loop
+ // This won't work for the first stream because there is no segment reader
+ // yet.
+ if (mSegmentReader) {
+ uint32_t countRead;
+ Unused << ReadSegments(nullptr, kDefaultBufferSize, &countRead);
+ }
+
+ if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+ !aHttpTransaction->IsNullTransaction()) {
+ LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
+ this, aHttpTransaction));
+ DontReuse();
+ }
+}
+
+already_AddRefed<nsHttpConnection> Http2Session::CreateTunnelStream(
+ nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket) {
+ RefPtr<Http2StreamTunnel> refStream = CreateTunnelStreamFromConnInfo(
+ this, mCurrentBrowserId, aHttpTransaction->ConnectionInfo(),
+ aIsWebSocket);
+
+ RefPtr<nsHttpConnection> newConn = refStream->CreateHttpConnection(
+ aHttpTransaction, aCallbacks, aRtt, aIsWebSocket);
+
+ mTunnelStreams.AppendElement(std::move(refStream));
+ return newConn.forget();
+}
+
+void Http2Session::QueueStream(Http2StreamBase* stream) {
+ // will be removed via processpending or a shutdown path
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!stream->CountAsActive());
+ MOZ_ASSERT(!stream->Queued());
+
+ LOG3(("Http2Session::QueueStream %p stream %p queued.", this, stream));
+
+#ifdef DEBUG
+ for (const auto& qStream : mQueuedStreams) {
+ MOZ_ASSERT(qStream != stream);
+ MOZ_ASSERT(qStream->Queued());
+ }
+#endif
+
+ stream->SetQueued(true);
+ AddStreamToQueue(stream, mQueuedStreams);
+}
+
+void Http2Session::ProcessPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<Http2StreamBase> stream;
+ while (RoomForMoreConcurrent() &&
+ (stream = GetNextStreamFromQueue(mQueuedStreams))) {
+ LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.", this,
+ stream.get()));
+ MOZ_ASSERT(!stream->CountAsActive());
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ }
+}
+
+nsresult Http2Session::NetworkRead(nsAHttpSegmentWriter* writer, char* buf,
+ uint32_t count, uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!count) {
+ *countWritten = 0;
+ return NS_OK;
+ }
+
+ nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
+ if (NS_SUCCEEDED(rv) && *countWritten > 0) {
+ mLastReadEpoch = PR_IntervalNow();
+ }
+ return rv;
+}
+
+void Http2Session::SetWriteCallbacks() {
+ if (mConnection &&
+ (GetWriteQueueSize() || (mOutputQueueUsed > mOutputQueueSent))) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http2Session::RealignOutputQueue() {
+ if (mAttemptingEarlyData) {
+ // We can't realign right now, because we may need what's in there if early
+ // data fails.
+ return;
+ }
+
+ mOutputQueueUsed -= mOutputQueueSent;
+ memmove(mOutputQueueBuffer.get(), mOutputQueueBuffer.get() + mOutputQueueSent,
+ mOutputQueueUsed);
+ mOutputQueueSent = 0;
+}
+
+void Http2Session::FlushOutputQueue() {
+ if (!mSegmentReader || !mOutputQueueUsed) return;
+
+ nsresult rv;
+ uint32_t countRead;
+ uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
+
+ if (!avail && mAttemptingEarlyData) {
+ // This is kind of a hack, but there are cases where we'll have already
+ // written the data we want whlie doing early data, but we get called again
+ // with a reader, and we need to avoid calling the reader when there's
+ // nothing for it to read.
+ return;
+ }
+
+ rv = mSegmentReader->OnReadSegment(
+ mOutputQueueBuffer.get() + mOutputQueueSent, avail, &countRead);
+ LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%" PRIx32 " actual=%d",
+ this, avail, static_cast<uint32_t>(rv), countRead));
+
+ // Dont worry about errors on write, we will pick this up as a read error too
+ if (NS_FAILED(rv)) return;
+
+ mOutputQueueSent += countRead;
+
+ if (mAttemptingEarlyData) {
+ return;
+ }
+
+ if (countRead == avail) {
+ mOutputQueueUsed = 0;
+ mOutputQueueSent = 0;
+ return;
+ }
+
+ // If the output queue is close to filling up and we have sent out a good
+ // chunk of data from the beginning then realign it.
+
+ if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
+ ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
+ RealignOutputQueue();
+ }
+}
+
+void Http2Session::DontReuse() {
+ LOG3(("Http2Session::DontReuse %p\n", this));
+ if (!OnSocketThread()) {
+ LOG3(("Http2Session %p not on socket thread\n", this));
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "Http2Session::DontReuse", this, &Http2Session::DontReuse);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ mShouldGoAway = true;
+ if (!mClosed && !mStreamTransactionHash.Count()) {
+ Close(NS_OK);
+ }
+}
+
+enum SpdyVersion Http2Session::SpdyVersion() { return SpdyVersion::HTTP_2; }
+
+uint32_t Http2Session::GetWriteQueueSize() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return mReadyForWrite.Length();
+}
+
+void Http2Session::ChangeDownstreamState(enum internalStateType newState) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X", this,
+ mDownstreamState, newState));
+ mDownstreamState = newState;
+}
+
+void Http2Session::ResetDownstreamState() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::ResetDownstreamState() %p", this));
+ ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+
+ if (mInputFrameFinal && mInputFrameDataStream) {
+ mInputFrameFinal = false;
+ LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
+ mInputFrameDataStream->SetRecvdFin(true);
+ MaybeDecrementConcurrent(mInputFrameDataStream);
+ }
+ mInputFrameFinal = false;
+ mInputFrameBufferUsed = 0;
+ mInputFrameDataStream = nullptr;
+}
+
+// return true if activated (and counted against max)
+// otherwise return false and queue
+bool Http2Session::TryToActivate(Http2StreamBase* aStream) {
+ if (aStream->Queued()) {
+ LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this,
+ aStream));
+ return false;
+ }
+
+ if (!RoomForMoreConcurrent()) {
+ LOG3(
+ ("Http2Session::TryToActivate %p stream=%p no room for more concurrent "
+ "streams\n",
+ this, aStream));
+ QueueStream(aStream);
+ return false;
+ }
+
+ LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
+ IncrementConcurrent(aStream);
+
+ mCntActivated++;
+ return true;
+}
+
+void Http2Session::IncrementConcurrent(Http2StreamBase* stream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
+ "Do not activate pushed streams");
+
+ nsAHttpTransaction* trans = stream->Transaction();
+ if (!trans || !trans->IsNullTransaction()) {
+ MOZ_ASSERT(!stream->CountAsActive());
+ stream->SetCountAsActive(true);
+ ++mConcurrent;
+
+ if (mConcurrent > mConcurrentHighWater) {
+ mConcurrentHighWater = mConcurrent;
+ }
+ LOG3(
+ ("Http2Session::IncrementCounter %p counting stream %p Currently %d "
+ "streams in session, high water mark is %d\n",
+ this, stream, mConcurrent, mConcurrentHighWater));
+ }
+}
+
+// call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header)
+// dest must have 9 bytes of allocated space
+template <typename charType>
+void Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
+ uint8_t frameType, uint8_t frameFlags,
+ uint32_t streamID) {
+ MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
+ MOZ_ASSERT(!(streamID & 0x80000000));
+ MOZ_ASSERT(!frameFlags || (frameType != FRAME_TYPE_PRIORITY &&
+ frameType != FRAME_TYPE_RST_STREAM &&
+ frameType != FRAME_TYPE_GOAWAY &&
+ frameType != FRAME_TYPE_WINDOW_UPDATE));
+
+ dest[0] = 0x00;
+ NetworkEndian::writeUint16(dest + 1, frameLength);
+ dest[3] = frameType;
+ dest[4] = frameFlags;
+ NetworkEndian::writeUint32(dest + 5, streamID);
+}
+
+char* Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded) {
+ // this is an infallible allocation (if an allocation is
+ // needed, which is probably isn't)
+ EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
+ mOutputQueueUsed, mOutputQueueSize);
+ return mOutputQueueBuffer.get() + mOutputQueueUsed;
+}
+
+template void Http2Session::CreateFrameHeader(char* dest, uint16_t frameLength,
+ uint8_t frameType,
+ uint8_t frameFlags,
+ uint32_t streamID);
+
+template void Http2Session::CreateFrameHeader(uint8_t* dest,
+ uint16_t frameLength,
+ uint8_t frameType,
+ uint8_t frameFlags,
+ uint32_t streamID);
+
+void Http2Session::MaybeDecrementConcurrent(Http2StreamBase* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n", this,
+ aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
+
+ if (!aStream->CountAsActive()) return;
+
+ MOZ_ASSERT(mConcurrent);
+ aStream->SetCountAsActive(false);
+ --mConcurrent;
+ ProcessPending();
+}
+
+// Need to decompress some data in order to keep the compression
+// context correct, but we really don't care what the result is
+nsresult Http2Session::UncompressAndDiscard(bool isPush) {
+ nsresult rv;
+ nsAutoCString trash;
+
+ rv = mDecompressor.DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(mDecompressBuffer.BeginReading()),
+ mDecompressBuffer.Length(), trash, isPush);
+ mDecompressBuffer.Truncate();
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n", this));
+ mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ return NS_OK;
+}
+
+void Http2Session::GeneratePing(bool isAck) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
+
+ char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 8);
+ mOutputQueueUsed += kFrameHeaderBytes + 8;
+
+ if (isAck) {
+ CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
+ memcpy(packet + kFrameHeaderBytes,
+ mInputFrameBuffer.get() + kFrameHeaderBytes, 8);
+ } else {
+ CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
+ memset(packet + kFrameHeaderBytes, 0, 8);
+ }
+
+ LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8);
+ FlushOutputQueue();
+}
+
+void Http2Session::GenerateSettingsAck() {
+ // need to generate ack of this settings frame
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
+
+ char* packet = EnsureOutputBuffer(kFrameHeaderBytes);
+ mOutputQueueUsed += kFrameHeaderBytes;
+ CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
+ LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes);
+ FlushOutputQueue();
+}
+
+void Http2Session::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GeneratePriority %p %X %X\n", this, aID,
+ aPriorityWeight));
+
+ char* packet = CreatePriorityFrame(aID, 0, aPriorityWeight);
+
+ LogIO(this, nullptr, "Generate Priority", packet, kFrameHeaderBytes + 5);
+ FlushOutputQueue();
+}
+
+void Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // make sure we don't do this twice for the same stream (at least if we
+ // have a stream entry for it)
+ Http2StreamBase* stream = mStreamIDHash.Get(aID);
+ if (stream) {
+ if (stream->SentReset()) return;
+ stream->SetSentReset(true);
+ }
+
+ LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
+
+ uint32_t frameSize = kFrameHeaderBytes + 4;
+ char* packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+ CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
+
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode);
+
+ LogIO(this, nullptr, "Generate Reset", packet, frameSize);
+ FlushOutputQueue();
+}
+
+void Http2Session::GenerateGoAway(uint32_t aStatusCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
+
+ mClientGoAwayReason = aStatusCode;
+ uint32_t frameSize = kFrameHeaderBytes + 8;
+ char* packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+
+ CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
+
+ // last-good-stream-id are bytes 9-12 reflecting pushes
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID);
+
+ // bytes 13-16 are the status code.
+ NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode);
+
+ LogIO(this, nullptr, "Generate GoAway", packet, frameSize);
+ FlushOutputQueue();
+}
+
+// The Hello is comprised of
+// 1] 24 octets of magic, which are designed to
+// flush out silent but broken intermediaries
+// 2] a settings frame which sets a small flow control window for pushes
+// 3] a window update frame which creates a large session flow control window
+// 4] 6 priority frames for streams which will never be opened with headers
+// these streams (3, 5, 7, 9, b, d) build a dependency tree that all other
+// streams will be direct leaves of.
+void Http2Session::SendHello() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::SendHello %p\n", this));
+
+ // sized for magic + 5 settings and a session window update and 6 priority
+ // frames 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window
+ // update, 6 priority frames at 14 (9 + 5) each
+ static const uint32_t maxSettings = 5;
+ static const uint32_t prioritySize =
+ kPriorityGroupCount * (kFrameHeaderBytes + 5);
+ static const uint32_t maxDataLen =
+ 24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize;
+ char* packet = EnsureOutputBuffer(maxDataLen);
+ memcpy(packet, kMagicHello, 24);
+ mOutputQueueUsed += 24;
+ LogIO(this, nullptr, "Magic Connection Header", packet, 24);
+
+ packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ memset(packet, 0, maxDataLen - 24);
+
+ // frame header will be filled in after we know how long the frame is
+ uint8_t numberOfEntries = 0;
+
+ // entries need to be listed in order by ID
+ // 1st entry is bytes 9 to 14
+ // 2nd entry is bytes 15 to 20
+ // 3rd entry is bytes 21 to 26
+ // 4th entry is bytes 27 to 32
+ // 5th entry is bytes 33 to 38
+
+ // Let the other endpoint know about our default HPACK decompress table size
+ uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer();
+ mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize);
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_HEADER_TABLE_SIZE);
+ NetworkEndian::writeUint32(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2,
+ maxHpackBufferSize);
+ numberOfEntries++;
+
+ if (!StaticPrefs::network_http_http2_allow_push()) {
+ // If we don't support push then set MAX_CONCURRENT to 0 and also
+ // set ENABLE_PUSH to 0
+ NetworkEndian::writeUint16(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_ENABLE_PUSH);
+ // The value portion of the setting pair is already initialized to 0
+ numberOfEntries++;
+
+ NetworkEndian::writeUint16(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_MAX_CONCURRENT);
+ // The value portion of the setting pair is already initialized to 0
+ numberOfEntries++;
+
+ mWaitingForSettingsAck = true;
+ }
+
+ // Advertise the Push RWIN for the session, and on each new pull stream
+ // send a window update
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_INITIAL_WINDOW);
+ NetworkEndian::writeUint32(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
+ numberOfEntries++;
+
+ // Make sure the other endpoint knows that we're sticking to the default max
+ // frame size
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_MAX_FRAME_SIZE);
+ NetworkEndian::writeUint32(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
+ numberOfEntries++;
+
+ MOZ_ASSERT(numberOfEntries <= maxSettings);
+ uint32_t dataLen = 6 * numberOfEntries;
+ CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
+ mOutputQueueUsed += kFrameHeaderBytes + dataLen;
+
+ LogIO(this, nullptr, "Generate Settings", packet,
+ kFrameHeaderBytes + dataLen);
+
+ // now bump the local session window from 64KB
+ uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
+ if (kDefaultRwin < mInitialRwin) {
+ // send a window update for the session (Stream 0) for something large
+ mLocalSessionWindow = mInitialRwin;
+
+ packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);
+
+ LOG3(("Session Window increase at start of session %p %u\n", this,
+ sessionWindowBump));
+ LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4);
+ }
+
+ if (StaticPrefs::network_http_http2_enabled_deps() &&
+ gHttpHandler->CriticalRequestPrioritization()) {
+ mUseH2Deps = true;
+ MOZ_ASSERT(mNextStreamID == kLeaderGroupID);
+ CreatePriorityNode(kLeaderGroupID, 0, 200, "leader");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kOtherGroupID);
+ CreatePriorityNode(kOtherGroupID, 0, 100, "other");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kBackgroundGroupID);
+ CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
+ CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0,
+ "speculative");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
+ CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID);
+ CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart");
+ mNextStreamID += 2;
+ // Hey, you! YES YOU! If you add/remove any groups here, you almost
+ // certainly need to change the lookup of the stream/ID hash in
+ // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
+ }
+
+ FlushOutputQueue();
+}
+
+void Http2Session::SendPriorityFrame(uint32_t streamID, uint32_t dependsOn,
+ uint8_t weight) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(
+ ("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X "
+ "weight %d\n",
+ this, streamID, dependsOn, weight));
+
+ char* packet = CreatePriorityFrame(streamID, dependsOn, weight);
+
+ LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5);
+ FlushOutputQueue();
+}
+
+char* Http2Session::CreatePriorityFrame(uint32_t streamID, uint32_t dependsOn,
+ uint8_t weight) {
+ MOZ_ASSERT(streamID, "Priority on stream 0");
+ char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 5);
+ CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
+ mOutputQueueUsed += kFrameHeaderBytes + 5;
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes,
+ dependsOn); // depends on
+ packet[kFrameHeaderBytes + 4] = weight; // weight
+ return packet;
+}
+
+void Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn,
+ uint8_t weight, const char* label) {
+ char* packet = CreatePriorityFrame(streamID, dependsOn, weight);
+
+ LOG3(
+ ("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
+ "weight %d for %s class\n",
+ this, streamID, dependsOn, weight, label));
+ LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
+}
+
+// perform a bunch of integrity checks on the stream.
+// returns true if passed, false (plus LOG and ABORT) if failed.
+bool Http2Session::VerifyStream(Http2StreamBase* aStream,
+ uint32_t aOptionalID = 0) {
+ // This is annoying, but at least it is O(1)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+#ifndef DEBUG
+ // Only do the real verification in debug builds
+ return true;
+#else // DEBUG
+
+ if (!aStream) return true;
+
+ uint32_t test = 0;
+
+ do {
+ if (aStream->StreamID() == kDeadStreamID) break;
+
+ test++;
+ if (aStream->StreamID()) {
+ Http2StreamBase* idStream = mStreamIDHash.Get(aStream->StreamID());
+
+ test++;
+ if (idStream != aStream) break;
+
+ if (aOptionalID) {
+ test++;
+ if (idStream->StreamID() != aOptionalID) break;
+ }
+ }
+
+ if (aStream->IsTunnel()) {
+ return true;
+ }
+
+ nsAHttpTransaction* trans = aStream->Transaction();
+
+ test++;
+ if (!trans) break;
+
+ test++;
+ if (mStreamTransactionHash.GetWeak(trans) != aStream) break;
+
+ // tests passed
+ return true;
+ } while (false);
+
+ LOG3(
+ ("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
+ "optionalID=0x%X trans=%p test=%d\n",
+ this, aStream, aStream->StreamID(), aOptionalID, aStream->Transaction(),
+ test));
+
+ MOZ_ASSERT(false, "VerifyStream");
+ return false;
+#endif // DEBUG
+}
+
+// static
+Http2StreamTunnel* Http2Session::CreateTunnelStreamFromConnInfo(
+ Http2Session* session, uint64_t bcId, nsHttpConnectionInfo* info,
+ bool isWebSocket) {
+ MOZ_ASSERT(info);
+ MOZ_ASSERT(session);
+
+ if (isWebSocket) {
+ LOG(("Http2Session creating Http2StreamWebSocket"));
+ MOZ_ASSERT(session->GetWebSocketSupport() == WebSocketSupport::SUPPORTED);
+ return new Http2StreamWebSocket(
+ session, nsISupportsPriority::PRIORITY_NORMAL, bcId, info);
+ }
+
+ MOZ_ASSERT(info->UsingHttpProxy() && info->UsingConnect());
+ LOG(("Http2Session creating Http2StreamTunnel"));
+ return new Http2StreamTunnel(session, nsISupportsPriority::PRIORITY_NORMAL,
+ bcId, info);
+}
+
+void Http2Session::CleanupStream(Http2StreamBase* aStream, nsresult aResult,
+ errorType aResetCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::CleanupStream %p %p 0x%X %" PRIX32 "\n", this, aStream,
+ aStream ? aStream->StreamID() : 0, static_cast<uint32_t>(aResult)));
+ if (!aStream) {
+ return;
+ }
+
+ Http2PushedStream* pushSource = nullptr;
+ Http2Stream* h2Stream = aStream->GetHttp2Stream();
+ if (h2Stream) {
+ pushSource = h2Stream->PushSource();
+ if (pushSource) {
+ // aStream is a synthetic attached to an even push
+ MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
+ MOZ_ASSERT(!aStream->StreamID());
+ MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
+ h2Stream->ClearPushSource();
+ }
+ }
+
+ if (aStream->DeferCleanup(aResult)) {
+ LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
+ return;
+ }
+
+ if (!VerifyStream(aStream)) {
+ LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
+ return;
+ }
+
+ // don't reset a stream that has recevied a fin or rst
+ if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() &&
+ !(mInputFrameFinal &&
+ (aStream == mInputFrameDataStream))) { // !(recvdfin with mark pending)
+ LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n",
+ aStream->StreamID(), aResetCode));
+ GenerateRstStream(aResetCode, aStream->StreamID());
+ }
+
+ CloseStream(aStream, aResult);
+
+ // Remove the stream from the ID hash table and, if an even id, the pushed
+ // table too.
+ uint32_t id = aStream->StreamID();
+ if (id > 0) {
+ mStreamIDHash.Remove(id);
+ if (!(id & 1)) {
+ mPushedStreams.RemoveElement(aStream);
+ Http2PushedStream* pushStream = static_cast<Http2PushedStream*>(aStream);
+ nsAutoCString hashKey;
+ DebugOnly<bool> rv = pushStream->GetHashKey(hashKey);
+ MOZ_ASSERT(rv);
+ nsIRequestContext* requestContext = aStream->RequestContext();
+ if (requestContext) {
+ SpdyPushCache* cache = requestContext->GetSpdyPushCache();
+ if (cache) {
+ // Make sure the id of the stream in the push cache is the same
+ // as the id of the stream we're cleaning up! See bug 1368080.
+ Http2PushedStream* trash =
+ cache->RemovePushedStreamHttp2ByID(hashKey, aStream->StreamID());
+ LOG3(
+ ("Http2Session::CleanupStream %p aStream=%p pushStream=%p "
+ "trash=%p",
+ this, aStream, pushStream, trash));
+ }
+ }
+ }
+ }
+
+ RemoveStreamFromQueues(aStream);
+
+ // removing from the stream transaction hash will
+ // delete the Http2StreamBase and drop the reference to
+ // its transaction
+ mStreamTransactionHash.Remove(aStream->Transaction());
+ mTunnelStreams.RemoveElement(aStream);
+
+ if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);
+
+ if (pushSource) {
+ pushSource->SetDeferCleanupOnSuccess(false);
+ CleanupStream(pushSource, aResult, aResetCode);
+ }
+}
+
+void Http2Session::CleanupStream(uint32_t aID, nsresult aResult,
+ errorType aResetCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ Http2StreamBase* stream = mStreamIDHash.Get(aID);
+ LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n", this, aID,
+ stream));
+ if (!stream) {
+ return;
+ }
+ CleanupStream(stream, aResult, aResetCode);
+}
+
+void Http2Session::RemoveStreamFromQueues(Http2StreamBase* aStream) {
+ RemoveStreamFromQueue(aStream, mReadyForWrite);
+ RemoveStreamFromQueue(aStream, mQueuedStreams);
+ RemoveStreamFromQueue(aStream, mPushesReadyForRead);
+ RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
+}
+
+void Http2Session::CloseStream(Http2StreamBase* aStream, nsresult aResult,
+ bool aRemoveFromQueue) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::CloseStream %p %p 0x%x %" PRIX32 "\n", this, aStream,
+ aStream->StreamID(), static_cast<uint32_t>(aResult)));
+
+ MaybeDecrementConcurrent(aStream);
+
+ // Check if partial frame reader
+ if (aStream == mInputFrameDataStream) {
+ LOG3(("Stream had active partial read frame on close"));
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ mInputFrameDataStream = nullptr;
+ }
+
+ if (aRemoveFromQueue) {
+ RemoveStreamFromQueues(aStream);
+ }
+
+ // Send the stream the close() indication
+ aStream->CloseStream(aResult);
+}
+
+nsresult Http2Session::SetInputFrameDataStream(uint32_t streamID) {
+ mInputFrameDataStream = mStreamIDHash.Get(streamID);
+ if (VerifyStream(mInputFrameDataStream, streamID)) return NS_OK;
+
+ LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
+ streamID));
+ mInputFrameDataStream = nullptr;
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult Http2Session::ParsePadding(uint8_t& paddingControlBytes,
+ uint16_t& paddingLength) {
+ if (mInputFrameFlags & kFlag_PADDED) {
+ paddingLength =
+ *reinterpret_cast<uint8_t*>(&mInputFrameBuffer[kFrameHeaderBytes]);
+ paddingControlBytes = 1;
+ } else {
+ paddingLength = 0;
+ paddingControlBytes = 0;
+ }
+
+ if (static_cast<uint32_t>(paddingLength + paddingControlBytes) >
+ mInputFrameDataSize) {
+ // This is fatal to the session
+ LOG3(
+ ("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR "
+ "paddingLength %d > frame size %d\n",
+ this, mInputFrameID, paddingLength, mInputFrameDataSize));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvHeaders(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS ||
+ self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+
+ bool isContinuation = self->mExpectedHeaderID != 0;
+
+ // If this doesn't have END_HEADERS set on it then require the next
+ // frame to be HEADERS of the same ID
+ bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
+
+ if (endHeadersFlag) {
+ self->mExpectedHeaderID = 0;
+ } else {
+ self->mExpectedHeaderID = self->mInputFrameID;
+ }
+
+ uint32_t priorityLen = 0;
+ if (self->mInputFrameFlags & kFlag_PRIORITY) {
+ priorityLen = 5;
+ }
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Find out how much padding this frame has, so we can only extract the real
+ // header data from the frame.
+ uint16_t paddingLength = 0;
+ uint8_t paddingControlBytes = 0;
+
+ if (!isContinuation) {
+ self->mDecompressBuffer.Truncate();
+ rv = self->ParsePadding(paddingControlBytes, paddingLength);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ LOG3(
+ ("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
+ "end_stream=%d end_headers=%d priority_group=%d "
+ "paddingLength=%d padded=%d\n",
+ self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
+ self->mInputFrameFlags & kFlag_END_STREAM,
+ self->mInputFrameFlags & kFlag_END_HEADERS,
+ self->mInputFrameFlags & kFlag_PRIORITY, paddingLength,
+ self->mInputFrameFlags & kFlag_PADDED));
+
+ if ((paddingControlBytes + priorityLen + paddingLength) >
+ self->mInputFrameDataSize) {
+ // This is fatal to the session
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameDataStream) {
+ // Cannot find stream. We can continue the session, but we need to
+ // uncompress the header block to maintain the correct compression context
+
+ LOG3(
+ ("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
+ "0x%X failed. NextStreamID = 0x%X\n",
+ self, self->mInputFrameID, self->mNextStreamID));
+
+ if (self->mInputFrameID >= self->mNextStreamID) {
+ self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
+ }
+
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ priorityLen],
+ self->mInputFrameDataSize - paddingControlBytes - priorityLen -
+ paddingLength);
+
+ if (self->mInputFrameFlags & kFlag_END_HEADERS) {
+ rv = self->UncompressAndDiscard(false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
+ // this is fatal to the session
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // make sure this is either the first headers or a trailer
+ if (self->mInputFrameDataStream->AllHeadersReceived() &&
+ !(self->mInputFrameFlags & kFlag_END_STREAM)) {
+ // Any header block after the first that does *not* end the stream is
+ // illegal.
+ LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self,
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ // queue up any compression bytes
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ priorityLen],
+ self->mInputFrameDataSize - paddingControlBytes - priorityLen -
+ paddingLength);
+
+ self->mInputFrameDataStream->UpdateTransportReadEvents(
+ self->mInputFrameDataSize);
+ self->mLastDataReadEpoch = self->mLastReadEpoch;
+
+ if (!isContinuation) {
+ self->mAggregatedHeaderSize = self->mInputFrameDataSize -
+ paddingControlBytes - priorityLen -
+ paddingLength;
+ } else {
+ self->mAggregatedHeaderSize += self->mInputFrameDataSize -
+ paddingControlBytes - priorityLen -
+ paddingLength;
+ }
+
+ if (!endHeadersFlag) { // more are coming - don't process yet
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (isContinuation) {
+ Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
+ self->mAggregatedHeaderSize);
+ }
+
+ rv = self->ResponseHeadersComplete();
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
+ self, self->mInputFrameID));
+ self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ // This is fatal to the session.
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ }
+ return rv;
+}
+
+// ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
+// should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
+// fine, and any other error is fatal to the session.
+nsresult Http2Session::ResponseHeadersComplete() {
+ LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d", this,
+ mInputFrameDataStream->StreamID(), mInputFrameFinal));
+
+ // Anything prior to AllHeadersReceived() => true is actual headers. After
+ // that, we need to handle them as trailers instead (which are special-cased
+ // so we don't have to use the nasty chunked parser for all h2, just in case).
+ if (mInputFrameDataStream->AllHeadersReceived()) {
+ LOG3(("Http2Session::ResponseHeadersComplete processing trailers"));
+ MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
+ nsresult rv = mInputFrameDataStream->ConvertResponseTrailers(
+ &mDecompressor, mDecompressBuffer);
+ if (NS_FAILED(rv)) {
+ LOG3((
+ "Http2Session::ResponseHeadersComplete trailer conversion failed\n"));
+ return rv;
+ }
+ mFlatHTTPResponseHeadersOut = 0;
+ mFlatHTTPResponseHeaders.Truncate();
+ if (mInputFrameFinal) {
+ // need to process the fin
+ ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
+ } else {
+ ResetDownstreamState();
+ }
+
+ return NS_OK;
+ }
+
+ // if this turns out to be a 1xx response code we have to
+ // undo the headers received bit that we are setting here.
+ bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived();
+ mInputFrameDataStream->SetAllHeadersReceived();
+
+ // The stream needs to see flattened http headers
+ // Uncompressed http/2 format headers currently live in
+ // Http2StreamBase::mDecompressBuffer - convert that to HTTP format in
+ // mFlatHTTPResponseHeaders via ConvertHeaders()
+
+ nsresult rv;
+ int32_t httpResponseCode; // out param to ConvertResponseHeaders
+ mFlatHTTPResponseHeadersOut = 0;
+ rv = mInputFrameDataStream->ConvertResponseHeaders(
+ &mDecompressor, mDecompressBuffer, mFlatHTTPResponseHeaders,
+ httpResponseCode);
+ if (rv == NS_ERROR_NET_RESET) {
+ LOG(
+ ("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders "
+ "reset\n",
+ this));
+ // This means the stream found connection-oriented auth. Treat this like we
+ // got a reset with HTTP_1_1_REQUIRED.
+ mInputFrameDataStream->DisableSpdy();
+ CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
+ ResetDownstreamState();
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // allow more headers in the case of 1xx
+ if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
+ mInputFrameDataStream->UnsetAllHeadersReceived();
+ }
+
+ ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvPriority(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
+
+ if (self->mInputFrameDataSize != 5) {
+ LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n", self,
+ self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t newPriorityDependency = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+ bool exclusive = !!(newPriorityDependency & 0x80000000);
+ newPriorityDependency &= 0x7fffffff;
+ uint8_t newPriorityWeight =
+ *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
+
+ // undefined what it means when the server sends a priority frame. ignore it.
+ LOG3(
+ ("Http2Session::RecvPriority %p 0x%X received dependency=0x%X "
+ "weight=%u exclusive=%d",
+ self->mInputFrameDataStream, self->mInputFrameID, newPriorityDependency,
+ newPriorityWeight, exclusive));
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvRstStream(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
+
+ if (self->mInputFrameDataSize != 4) {
+ LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
+ self, self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ self->mDownstreamRstReason = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+
+ LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
+ self, self->mDownstreamRstReason, self->mInputFrameID));
+
+ DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!self->mInputFrameDataStream) {
+ // if we can't find the stream just ignore it (4.2 closed)
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mInputFrameDataStream->SetRecvdReset(true);
+ self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
+ self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvSettings(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n", self,
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameDataSize % 6) {
+ // Number of Settings is determined by dividing by each 6 byte setting
+ // entry. So the payload must be a multiple of 6.
+ LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d", self,
+ self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ self->mReceivedSettings = true;
+
+ uint32_t numEntries = self->mInputFrameDataSize / 6;
+ LOG3(
+ ("Http2Session::RecvSettings %p SETTINGS Control Frame "
+ "with %d entries ack=%X",
+ self, numEntries, self->mInputFrameFlags & kFlag_ACK));
+
+ if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n",
+ self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ for (uint32_t index = 0; index < numEntries; ++index) {
+ uint8_t* setting =
+ reinterpret_cast<uint8_t*>(self->mInputFrameBuffer.get()) +
+ kFrameHeaderBytes + index * 6;
+
+ uint16_t id = NetworkEndian::readUint16(setting);
+ uint32_t value = NetworkEndian::readUint32(setting + 2);
+ LOG3(("Settings ID %u, Value %u", id, value));
+
+ switch (id) {
+ case SETTINGS_TYPE_HEADER_TABLE_SIZE:
+ LOG3(("Compression header table setting received: %d\n", value));
+ self->mCompressor.SetMaxBufferSize(value);
+ break;
+
+ case SETTINGS_TYPE_ENABLE_PUSH:
+ LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
+ // nop
+ break;
+
+ case SETTINGS_TYPE_MAX_CONCURRENT:
+ self->mMaxConcurrent = value;
+ Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
+ self->ProcessPending();
+ break;
+
+ case SETTINGS_TYPE_INITIAL_WINDOW: {
+ Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
+ int32_t delta = value - self->mServerInitialStreamWindow;
+ self->mServerInitialStreamWindow = value;
+
+ // SETTINGS only adjusts stream windows. Leave the session window alone.
+ // We need to add the delta to all open streams (delta can be negative)
+ for (const auto& stream : self->mStreamTransactionHash.Values()) {
+ stream->UpdateServerReceiveWindow(delta);
+ }
+ } break;
+
+ case SETTINGS_TYPE_MAX_FRAME_SIZE: {
+ if ((value < kMaxFrameData) || (value >= 0x01000000)) {
+ LOG3(("Received invalid max frame size 0x%X", value));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ // We stick to the default for simplicity's sake, so nothing to change
+ } break;
+
+ case SETTINGS_TYPE_ENABLE_CONNECT_PROTOCOL: {
+ if (value == 1) {
+ LOG3(("Enabling extended CONNECT"));
+ self->mPeerAllowsWebsockets = true;
+ } else if (value > 1) {
+ LOG3(("Peer sent invalid value for ENABLE_CONNECT_PROTOCOL %d",
+ value));
+ return self->SessionError(PROTOCOL_ERROR);
+ } else if (self->mPeerAllowsWebsockets) {
+ LOG3(("Peer tried to re-disable extended CONNECT"));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ self->mHasTransactionWaitingForWebsockets = true;
+ } break;
+
+ default:
+ LOG3(("Received an unknown SETTING id %d. Ignoring.", id));
+ break;
+ }
+ }
+
+ self->ResetDownstreamState();
+
+ if (!(self->mInputFrameFlags & kFlag_ACK)) {
+ self->GenerateSettingsAck();
+ } else if (self->mWaitingForSettingsAck) {
+ self->mGoAwayOnPush = true;
+ }
+
+ if (!self->mProcessedWaitingWebsockets) {
+ self->mProcessedWaitingWebsockets = true;
+ }
+
+ if (self->mHasTransactionWaitingForWebsockets) {
+ // trigger a queued websockets transaction -- enabled or not
+ LOG3(("Http2Sesssion::RecvSettings triggering queued websocket"));
+ RefPtr<nsHttpConnectionInfo> ci;
+ self->GetConnectionInfo(getter_AddRefs(ci));
+ gHttpHandler->ConnMgr()->ProcessPendingQ(ci);
+ self->mHasTransactionWaitingForWebsockets = false;
+ }
+
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvPushPromise(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE ||
+ self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+
+ // Find out how much padding this frame has, so we can only extract the real
+ // header data from the frame.
+ uint16_t paddingLength = 0;
+ uint8_t paddingControlBytes = 0;
+
+ // If this doesn't have END_PUSH_PROMISE set on it then require the next
+ // frame to be PUSH_PROMISE of the same ID
+ uint32_t promiseLen;
+ uint32_t promisedID;
+
+ if (self->mExpectedPushPromiseID) {
+ promiseLen = 0; // really a continuation frame
+ promisedID = self->mContinuedPromiseStream;
+ } else {
+ self->mDecompressBuffer.Truncate();
+ nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ promiseLen = 4;
+ promisedID =
+ NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes + paddingControlBytes);
+ promisedID &= 0x7fffffff;
+ if (promisedID <= self->mLastPushedID) {
+ LOG3(("Http2Session::RecvPushPromise %p ID too low %u expected > %u.\n",
+ self, promisedID, self->mLastPushedID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ self->mLastPushedID = promisedID;
+ }
+
+ uint32_t associatedID = self->mInputFrameID;
+
+ if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
+ self->mExpectedPushPromiseID = 0;
+ self->mContinuedPromiseStream = 0;
+ } else {
+ self->mExpectedPushPromiseID = self->mInputFrameID;
+ self->mContinuedPromiseStream = promisedID;
+ }
+
+ if ((paddingControlBytes + promiseLen + paddingLength) >
+ self->mInputFrameDataSize) {
+ // This is fatal to the session
+ LOG3(
+ ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
+ "PROTOCOL_ERROR extra %d > frame size %d\n",
+ self, promisedID, associatedID,
+ (paddingControlBytes + promiseLen + paddingLength),
+ self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ LOG3(
+ ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
+ "paddingLength %d padded %d\n",
+ self, promisedID, associatedID, paddingLength,
+ self->mInputFrameFlags & kFlag_PADDED));
+
+ if (!associatedID || !promisedID || (promisedID & 1)) {
+ LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ // confirm associated-to
+ nsresult rv = self->SetInputFrameDataStream(associatedID);
+ if (NS_FAILED(rv)) return rv;
+
+ Http2StreamBase* associatedStream = self->mInputFrameDataStream;
+ ++(self->mServerPushedResources);
+
+ // Anytime we start using the high bit of stream ID (either client or server)
+ // begin to migrate to a new session.
+ if (promisedID >= kMaxStreamID) self->mShouldGoAway = true;
+
+ bool resetStream = true;
+ SpdyPushCache* cache = nullptr;
+
+ if (self->mShouldGoAway && !Http2PushedStream::TestOnPush(associatedStream)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p cache push while in GoAway "
+ "mode refused.\n",
+ self));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else if (!StaticPrefs::network_http_http2_allow_push()) {
+ // ENABLE_PUSH and MAX_CONCURRENT_STREAMS of 0 in settings disabled push
+ LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
+ if (self->mGoAwayOnPush) {
+ LOG3(("Http2Session::RecvPushPromise sending GOAWAY"));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else if (!(associatedID & 1)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p assocated=0x%X on pushed (even) "
+ "stream not allowed\n",
+ self, associatedID));
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ } else if (!associatedStream) {
+ LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n",
+ self));
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ } else if (Http2PushedStream::TestOnPush(associatedStream)) {
+ LOG3(("Http2Session::RecvPushPromise %p will be handled by push listener.",
+ self));
+ resetStream = false;
+ } else {
+ nsIRequestContext* requestContext = associatedStream->RequestContext();
+ if (requestContext) {
+ cache = requestContext->GetSpdyPushCache();
+ if (!cache) {
+ cache = new SpdyPushCache();
+ requestContext->SetSpdyPushCache(cache);
+ }
+ }
+ if (!cache) {
+ // this is unexpected, but we can handle it just by refusing the push
+ LOG3(
+ ("Http2Session::RecvPushPromise Push Recevied without push cache\n"));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else {
+ resetStream = false;
+ }
+ }
+
+ if (resetStream) {
+ // Need to decompress the headers even though we aren't using them yet in
+ // order to keep the compression context consistent for other frames
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ promiseLen],
+ self->mInputFrameDataSize - paddingControlBytes - promiseLen -
+ paddingLength);
+ if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
+ rv = self->UncompressAndDiscard(true);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ }
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ promiseLen],
+ self->mInputFrameDataSize - paddingControlBytes - promiseLen -
+ paddingLength);
+
+ if (self->mInputFrameType != FRAME_TYPE_CONTINUATION) {
+ self->mAggregatedHeaderSize = self->mInputFrameDataSize -
+ paddingControlBytes - promiseLen -
+ paddingLength;
+ } else {
+ self->mAggregatedHeaderSize += self->mInputFrameDataSize -
+ paddingControlBytes - promiseLen -
+ paddingLength;
+ }
+
+ if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise not finishing processing for "
+ "multi-frame push\n"));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->mInputFrameType == FRAME_TYPE_CONTINUATION) {
+ Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
+ self->mAggregatedHeaderSize);
+ }
+
+ // Create the buffering transaction and push stream
+ RefPtr<Http2PushTransactionBuffer> transactionBuffer =
+ new Http2PushTransactionBuffer();
+ transactionBuffer->SetConnection(self);
+ RefPtr<Http2PushedStream> pushedStream(
+ new Http2PushedStream(transactionBuffer, self, associatedStream,
+ promisedID, self->mCurrentBrowserId));
+
+ rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
+ self->mDecompressBuffer,
+ pushedStream->GetRequestString());
+
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ // This means the decompression completed ok, but there was a problem with
+ // the decoded headers. Reset the stream and go away.
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) {
+ // This is fatal to the session.
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+
+ // Ownership of the pushed stream is by the transaction hash, just as it
+ // is for a client initiated stream. Errors that aren't fatal to the
+ // whole session must call cleanupStream() after this point in order
+ // to remove the stream from that hash.
+ WeakPtr<Http2StreamBase> pushedWeak = pushedStream.get();
+ self->mStreamTransactionHash.InsertOrUpdate(transactionBuffer,
+ std::move(pushedStream));
+ self->mPushedStreams.AppendElement(
+ static_cast<Http2PushedStream*>(pushedWeak.get()));
+
+ if (self->RegisterStreamID(pushedWeak, promisedID) == kDeadStreamID) {
+ LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
+ self->mGoAwayReason = INTERNAL_ERROR;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (promisedID > self->mOutgoingGoAwayID) {
+ self->mOutgoingGoAwayID = promisedID;
+ }
+
+ // Fake the request side of the pushed HTTP transaction. Sets up hash
+ // key and origin
+ uint32_t notUsed;
+ Unused << pushedWeak->ReadSegments(nullptr, 1, &notUsed);
+
+ nsAutoCString key;
+ if (!static_cast<Http2PushedStream*>(pushedWeak.get())->GetHashKey(key)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise one of :authority :scheme :path "
+ "missing from push\n"));
+ self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // does the pushed origin belong on this connection?
+ LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
+ pushedWeak->Origin().get()));
+ nsCOMPtr<nsIURI> pushedOrigin;
+ rv = MakeOriginURL(pushedWeak->Origin(), pushedOrigin);
+ nsAutoCString pushedHostName;
+ int32_t pushedPort = -1;
+ if (NS_SUCCEEDED(rv)) {
+ rv = pushedOrigin->GetHost(pushedHostName);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = pushedOrigin->GetPort(&pushedPort);
+ if (NS_SUCCEEDED(rv) && pushedPort == -1) {
+ // Need to get the right default port, so TestJoinConnection below can
+ // check things correctly. See bug 1397621.
+ if (pushedOrigin->SchemeIs("http")) {
+ pushedPort = NS_HTTP_DEFAULT_PORT;
+ } else {
+ pushedPort = NS_HTTPS_DEFAULT_PORT;
+ }
+ }
+ }
+ if (NS_FAILED(rv) || !self->TestJoinConnection(pushedHostName, pushedPort)) {
+ LOG3((
+ "Http2Session::RecvPushPromise %p pushed stream mismatched origin %s\n",
+ self, pushedWeak->Origin().get()));
+ self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (static_cast<Http2PushedStream*>(pushedWeak.get())->TryOnPush()) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p channel implements "
+ "nsIHttpPushListener "
+ "stream %p will not be placed into session cache.\n",
+ self, pushedWeak.get()));
+ } else {
+ LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n",
+ self));
+ if (!cache->RegisterPushedStreamHttp2(
+ key, static_cast<Http2PushedStream*>(pushedWeak.get()))) {
+ // This only happens if they've already pushed us this item.
+ LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
+ self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // Kick off a lookup into the HTTP cache so we can cancel the push if it's
+ // unneeded (we already have it in our local regular cache). See bug
+ // 1367551.
+
+ // Build up our full URL for the cache lookup
+ nsAutoCString spec;
+ spec.Assign(pushedWeak->Origin());
+ spec.Append(pushedWeak->Path());
+ nsCOMPtr<nsIURI> pushedURL;
+ // Nifty trick: this doesn't actually do anything origin-specific, it's just
+ // named that way. So by passing it the full spec here, we get a URL with
+ // the full path.
+ // Another nifty trick! Even though this is using nsIURIs (which are not
+ // generally ok off the main thread), since we're not using the protocol
+ // handler to create any URIs, this will work just fine here. Don't try this
+ // at home, though, kids. I'm a trained professional.
+ if (NS_SUCCEEDED(MakeOriginURL(spec, pushedURL))) {
+ LOG3(("Http2Session::RecvPushPromise %p check disk cache for entry",
+ self));
+ mozilla::OriginAttributes oa;
+ pushedWeak->GetOriginAttributes(&oa);
+ RefPtr<Http2Session> session = self;
+ auto cachePushCheckCallback = [session, promisedID](bool aAccepted) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!aAccepted) {
+ session->CleanupStream(promisedID, NS_ERROR_FAILURE,
+ Http2Session::REFUSED_STREAM_ERROR);
+ }
+ };
+ RefPtr<CachePushChecker> checker = new CachePushChecker(
+ pushedURL, oa,
+ static_cast<Http2PushedStream*>(pushedWeak.get())->GetRequestString(),
+ std::move(cachePushCheckCallback));
+ if (NS_FAILED(checker->DoCheck())) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p failed to open cache entry for "
+ "push check",
+ self));
+ }
+ }
+ }
+
+ if (!pushedWeak) {
+ // We have an up to date entry in our cache, so the stream was closed
+ // and released in the cachePushCheckCallback above.
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ pushedWeak->SetHTTPState(Http2StreamBase::RESERVED_BY_REMOTE);
+ static_assert(Http2StreamBase::kWorstPriority >= 0,
+ "kWorstPriority out of range");
+ uint32_t priorityDependency = pushedWeak->PriorityDependency();
+ uint8_t priorityWeight = pushedWeak->PriorityWeight();
+ self->SendPriorityFrame(promisedID, priorityDependency, priorityWeight);
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvPing(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
+
+ LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
+ self->mInputFrameFlags));
+
+ if (self->mInputFrameDataSize != 8) {
+ LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d", self,
+ self->mInputFrameDataSize));
+ return self->SessionError(FRAME_SIZE_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n", self,
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameFlags & kFlag_ACK) {
+ // presumably a reply to our timeout ping.. don't reply to it
+ self->mPingSentEpoch = 0;
+ // We need to reset mPreviousUsed. If we don't, the next time
+ // Http2Session::SendPing is called, it will have no effect.
+ self->mPreviousUsed = false;
+ } else {
+ // reply with a ack'd ping
+ self->GeneratePing(true);
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvGoAway(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
+
+ if (self->mInputFrameDataSize < 8) {
+ // data > 8 is an opaque token that we can't interpret. NSPR Logs will
+ // have the hex of all packets so there is no point in separately logging.
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
+ self, self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
+ self, self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ self->mConnection->SetCloseReason(ConnectionCloseReason::GO_AWAY);
+ self->mShouldGoAway = true;
+ self->mGoAwayID = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes);
+ self->mGoAwayID &= 0x7fffffff;
+ self->mCleanShutdown = true;
+ self->mPeerGoAwayReason = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
+
+ // Find streams greater than the last-good ID and mark them for deletion
+ // in the mGoAwayStreamsToRestart queue. The underlying transaction can be
+ // restarted.
+ for (const auto& stream : self->mStreamTransactionHash.Values()) {
+ // these streams were not processed by the server and can be restarted.
+ // Do that after the enumerator completes to avoid the risk of
+ // a restart event re-entrantly modifying this hash. Be sure not to restart
+ // a pushed (even numbered) stream
+ if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
+ !stream->HasRegisteredID()) {
+ self->mGoAwayStreamsToRestart.Push(stream);
+ }
+ }
+
+ // Process the streams marked for deletion and restart.
+ size_t size = self->mGoAwayStreamsToRestart.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2StreamBase* stream =
+ static_cast<Http2StreamBase*>(self->mGoAwayStreamsToRestart.PopFront());
+
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->DisableSpdy();
+ }
+ self->CloseStream(stream, NS_ERROR_NET_RESET);
+ if (stream->HasRegisteredID()) {
+ self->mStreamIDHash.Remove(stream->StreamID());
+ }
+ self->mStreamTransactionHash.Remove(stream->Transaction());
+ }
+
+ // Queued streams can also be deleted from this session and restarted
+ // in another one. (they were never sent on the network so they implicitly
+ // are not covered by the last-good id.
+ for (const auto& stream : self->mQueuedStreams) {
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->DisableSpdy();
+ }
+ self->CloseStream(stream, NS_ERROR_NET_RESET, false);
+ self->mStreamTransactionHash.Remove(stream->Transaction());
+ }
+ self->mQueuedStreams.Clear();
+
+ LOG3(
+ ("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
+ "live streams=%d\n",
+ self, self->mGoAwayID, self->mPeerGoAwayReason,
+ self->mStreamTransactionHash.Count()));
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvWindowUpdate(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
+
+ if (self->mInputFrameDataSize != 4) {
+ LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
+ self, self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ uint32_t delta = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes);
+ delta &= 0x7fffffff;
+
+ LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n", self, delta,
+ self->mInputFrameID));
+
+ if (self->mInputFrameID) { // stream window
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!self->mInputFrameDataStream) {
+ LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
+ self, self->mInputFrameID));
+ // only reset the session if the ID is one we haven't ever opened
+ if (self->mInputFrameID >= self->mNextStreamID) {
+ self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
+ }
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (delta == 0) {
+ LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update",
+ self));
+ self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
+ PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ int64_t oldRemoteWindow =
+ self->mInputFrameDataStream->ServerReceiveWindow();
+ self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
+ if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
+ // a window cannot reach 2^31 and be in compliance. Our calculations
+ // are 64 bit safe though.
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p stream window "
+ "exceeds 2^31 - 1\n",
+ self));
+ self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
+ FLOW_CONTROL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p stream 0x%X window "
+ "%" PRId64 " increased by %" PRIu32 " now %" PRId64 ".\n",
+ self, self->mInputFrameID, oldRemoteWindow, delta,
+ oldRemoteWindow + delta));
+
+ } else { // session window update
+ if (delta == 0) {
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p received 0 session window update",
+ self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ int64_t oldRemoteWindow = self->mServerSessionWindow;
+ self->mServerSessionWindow += delta;
+
+ if (self->mServerSessionWindow >= 0x80000000) {
+ // a window cannot reach 2^31 and be in compliance. Our calculations
+ // are 64 bit safe though.
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p session window "
+ "exceeds 2^31 - 1\n",
+ self));
+ return self->SessionError(FLOW_CONTROL_ERROR);
+ }
+
+ if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p restart session window\n", self));
+ for (const auto& stream : self->mStreamTransactionHash.Values()) {
+ MOZ_ASSERT(self->mServerSessionWindow > 0);
+
+ if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
+ continue;
+ }
+
+ AddStreamToQueue(stream, self->mReadyForWrite);
+ self->SetWriteCallbacks();
+ }
+ }
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p session window "
+ "%" PRId64 " increased by %d now %" PRId64 ".\n",
+ self, oldRemoteWindow, delta, oldRemoteWindow + delta));
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvContinuation(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+ MOZ_ASSERT(self->mInputFrameID);
+ MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
+ MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
+
+ LOG3(
+ ("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
+ "promise id 0x%X header id 0x%X\n",
+ self, self->mInputFrameFlags, self->mInputFrameID,
+ self->mExpectedPushPromiseID, self->mExpectedHeaderID));
+
+ DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!self->mInputFrameDataStream) {
+ LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ // continued headers
+ if (self->mExpectedHeaderID) {
+ self->mInputFrameFlags &= ~kFlag_PRIORITY;
+ return RecvHeaders(self);
+ }
+
+ // continued push promise
+ if (self->mInputFrameFlags & kFlag_END_HEADERS) {
+ self->mInputFrameFlags &= ~kFlag_END_HEADERS;
+ self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
+ }
+ return RecvPushPromise(self);
+}
+
+class UpdateAltSvcEvent : public Runnable {
+ public:
+ UpdateAltSvcEvent(const nsCString& header, const nsCString& aOrigin,
+ nsHttpConnectionInfo* aCI)
+ : Runnable("net::UpdateAltSvcEvent"),
+ mHeader(header),
+ mOrigin(aOrigin),
+ mCI(aCI) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCString originScheme;
+ nsCString originHost;
+ int32_t originPort = -1;
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
+ LOG(("UpdateAltSvcEvent origin does not parse %s\n", mOrigin.get()));
+ return NS_OK;
+ }
+ uri->GetScheme(originScheme);
+ uri->GetHost(originHost);
+ uri->GetPort(&originPort);
+
+ if (XRE_IsSocketProcess()) {
+ AltServiceChild::ProcessHeader(
+ mHeader, originScheme, originHost, originPort, mCI->GetUsername(),
+ mCI->GetPrivate(), nullptr, mCI->ProxyInfo(), 0,
+ mCI->GetOriginAttributes());
+ return NS_OK;
+ }
+
+ AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
+ mCI->GetUsername(), mCI->GetPrivate(), nullptr,
+ mCI->ProxyInfo(), 0,
+ mCI->GetOriginAttributes());
+ return NS_OK;
+ }
+
+ private:
+ nsCString mHeader;
+ nsCString mOrigin;
+ RefPtr<nsHttpConnectionInfo> mCI;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+// defined as an http2 extension - alt-svc
+// defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft
+// -06 sec 4 as this is an extension, never generate protocol error - just
+// ignore problems
+nsresult Http2Session::RecvAltSvc(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
+ LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
+ self->mInputFrameFlags, self->mInputFrameID));
+
+ if (self->mInputFrameDataSize < 2) {
+ LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint16_t originLen = NetworkEndian::readUint16(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes);
+ if (originLen + 2U > self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!gHttpHandler->AllowAltSvc()) {
+ LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint16_t altSvcFieldValueLen =
+ static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
+ LOG3((
+ "Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
+ self, originLen, altSvcFieldValueLen));
+
+ if (self->mInputFrameDataSize > 2000) {
+ LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ bool impliedOrigin = true;
+ if (originLen) {
+ origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2,
+ originLen);
+ impliedOrigin = false;
+ }
+
+ nsAutoCString altSvcFieldValue;
+ if (altSvcFieldValueLen) {
+ altSvcFieldValue.Assign(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
+ altSvcFieldValueLen);
+ }
+
+ if (altSvcFieldValue.IsEmpty() ||
+ !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
+ LOG(
+ ("Http2Session %p Alt-Svc Response Header seems unreasonable - "
+ "skipping\n",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->mInputFrameID & 1) {
+ // pulled streams apply to the origin of the pulled stream.
+ // If the origin field is filled in the frame, the frame should be ignored
+ if (!origin.IsEmpty()) {
+ LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
+ !self->mInputFrameDataStream ||
+ !self->mInputFrameDataStream->Transaction() ||
+ !self->mInputFrameDataStream->Transaction()->RequestHead()) {
+ LOG3(
+ ("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin);
+ } else if (!self->mInputFrameID) {
+ // ID 0 streams must supply their own origin
+ if (origin.IsEmpty()) {
+ LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ } else {
+ // handling of push streams is not defined. Let's ignore it
+ LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
+ if (!self->mConnection || !ci) {
+ LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
+ self->mInputFrameID));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!impliedOrigin) {
+ bool okToReroute = true;
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ self->mConnection->GetTLSSocketControl(getter_AddRefs(ssl));
+ if (!ssl) {
+ okToReroute = false;
+ }
+
+ // a little off main thread origin parser. This is a non critical function
+ // because any alternate route created has to be verified anyhow
+ nsAutoCString specifiedOriginHost;
+ if (StringBeginsWith(origin, "https://"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
+ } else if (StringBeginsWith(origin, "http://"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
+ }
+
+ int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
+ if (colonOffset != kNotFound) {
+ specifiedOriginHost.Truncate(colonOffset);
+ }
+
+ if (okToReroute) {
+ ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
+ }
+
+ if (!okToReroute) {
+ LOG3(
+ ("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin "
+ "%s",
+ self, origin.BeginReading()));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ }
+
+ RefPtr<UpdateAltSvcEvent> event =
+ new UpdateAltSvcEvent(altSvcFieldValue, origin, ci);
+ NS_DispatchToMainThread(event);
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+void Http2Session::Received421(nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated));
+ if (!mOriginFrameActivated || !ci) {
+ return;
+ }
+
+ nsAutoCString key(ci->GetOrigin());
+ key.Append(':');
+ key.AppendInt(ci->OriginPort());
+ mOriginFrame.Remove(key);
+ LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get()));
+}
+
+nsresult Http2Session::RecvUnused(Http2Session* self) {
+ LOG3(("Http2Session %p unknown frame type %x ignored\n", self,
+ self->mInputFrameType));
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+// defined as an http2 extension - origin
+// defines receipt of frame type 0x0b..
+// http://httpwg.org/http-extensions/origin-frame.html as this is an extension,
+// never generate protocol error - just ignore problems
+nsresult Http2Session::RecvOrigin(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN);
+ LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self,
+ self->mInputFrameFlags, self->mInputFrameID));
+
+ if (self->mInputFrameFlags & 0x0F) {
+ LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvOrigin %p not stream 0", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->ConnectionInfo()->UsingProxy()) {
+ LOG3(("Http2Session::RecvOrigin %p must not use proxy", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!gHttpHandler->AllowOriginExtension()) {
+ LOG3(("Http2Session::RecvOrigin %p origin extension pref'd off", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint32_t offset = 0;
+ self->mOriginFrameActivated = true;
+
+ while (self->mInputFrameDataSize >= (offset + 2U)) {
+ uint16_t originLen = NetworkEndian::readUint16(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset);
+ LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n",
+ self, originLen));
+ if (originLen + 2U + offset > self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self));
+ break;
+ }
+
+ nsAutoCString originString;
+ nsCOMPtr<nsIURI> originURL;
+ originString.Assign(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2,
+ originLen);
+ offset += originLen + 2;
+ if (NS_FAILED(MakeOriginURL(originString, originURL))) {
+ LOG3(
+ ("Http2Session::RecvOrigin %p origin frame string %s failed to "
+ "parse\n",
+ self, originString.get()));
+ continue;
+ }
+
+ LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n",
+ self, originString.get()));
+ if (!originURL->SchemeIs("https")) {
+ LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self));
+ continue;
+ }
+
+ int32_t port = -1;
+ originURL->GetPort(&port);
+ if (port == -1) {
+ port = 443;
+ }
+ // dont use ->GetHostPort because we want explicit 443
+ nsAutoCString host;
+ originURL->GetHost(host);
+ nsAutoCString key(host);
+ key.Append(':');
+ key.AppendInt(port);
+ self->mOriginFrame.WithEntryHandle(key, [&](auto&& entry) {
+ if (!entry) {
+ entry.Insert(true);
+ RefPtr<HttpConnectionBase> conn(self->HttpConnection());
+ MOZ_ASSERT(conn.get());
+ gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port);
+ } else {
+ LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n",
+ self));
+ }
+ });
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
+// of these methods
+//-----------------------------------------------------------------------------
+
+void Http2Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+ int64_t aProgress) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ switch (aStatus) {
+ // These should appear only once, deliver to the first
+ // transaction on the session.
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: {
+ if (!mFirstHttpTransaction) {
+ // if we still do not have a HttpTransaction store timings info in
+ // a HttpConnection.
+ // If some error occur it can happen that we do not have a connection.
+ if (mConnection) {
+ RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection();
+ conn->SetEvent(aStatus);
+ }
+ } else {
+ mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus,
+ aProgress);
+ }
+
+ if (aStatus == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+ mFirstHttpTransaction = nullptr;
+ mTlsHandshakeFinished = true;
+ }
+ break;
+ }
+
+ default:
+ // The other transport events are ignored here because there is no good
+ // way to map them to the right transaction in http/2. Instead, the events
+ // are generated again from the http/2 code and passed directly to the
+ // correct transaction.
+
+ // NS_NET_STATUS_SENDING_TO:
+ // This is generated by the socket transport when (part) of
+ // a transaction is written out
+ //
+ // There is no good way to map it to the right transaction in http/2,
+ // so it is ignored here and generated separately when the request
+ // is sent from Http2StreamBase::TransmitFrame
+
+ // NS_NET_STATUS_WAITING_FOR:
+ // Created by nsHttpConnection when the request has been totally sent.
+ // There is no good way to map it to the right transaction in http/2,
+ // so it is ignored here and generated separately when the same
+ // condition is complete in Http2StreamBase when there is no more
+ // request body left to be transmitted.
+
+ // NS_NET_STATUS_RECEIVING_FROM
+ // Generated in session whenever we read a data frame or a HEADERS
+ // that can be attributed to a particular stream/transaction
+
+ break;
+ }
+}
+
+// ReadSegments() is used to write data to the network. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to http/2 data. Sometimes control data like window-update are
+// generated instead.
+
+nsresult Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead,
+ bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mSegmentReader || !reader || (mSegmentReader == reader),
+ "Inconsistent Write Function Callback");
+
+ nsresult rv = ConfirmTLSProfile();
+ if (NS_FAILED(rv)) {
+ if (mGoAwayReason == INADEQUATE_SECURITY) {
+ LOG3(
+ ("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY "
+ "%" PRIx32,
+ this, static_cast<uint32_t>(NS_ERROR_NET_INADEQUATE_SECURITY)));
+ rv = NS_ERROR_NET_INADEQUATE_SECURITY;
+ }
+ return rv;
+ }
+
+ if (reader) mSegmentReader = reader;
+
+ *countRead = 0;
+
+ LOG3(("Http2Session::ReadSegments %p", this));
+
+ RefPtr<Http2StreamBase> stream = GetNextStreamFromQueue(mReadyForWrite);
+
+ if (!stream) {
+ LOG3(("Http2Session %p could not identify a stream to write; suspending.",
+ this));
+ uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent;
+ FlushOutputQueue();
+ uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent;
+ if (availBeforeFlush != availAfterFlush) {
+ LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments",
+ this));
+ Unused << ResumeRecv();
+ }
+ SetWriteCallbacks();
+ if (mAttemptingEarlyData) {
+ // We can still try to send our preamble as early-data
+ *countRead = mOutputQueueUsed - mOutputQueueSent;
+ LOG(("Http2Session %p nothing to send because of 0RTT failed", this));
+ Unused << ResumeRecv();
+ }
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ uint32_t earlyDataUsed = 0;
+ if (mAttemptingEarlyData) {
+ if (!stream->Do0RTT()) {
+ LOG3(
+ ("Http2Session %p will not get early data from Http2StreamBase %p "
+ "0x%X",
+ this, stream.get(), stream->StreamID()));
+ FlushOutputQueue();
+ SetWriteCallbacks();
+ if (!mCannotDo0RTTStreams.Contains(stream)) {
+ mCannotDo0RTTStreams.AppendElement(stream);
+ }
+ // We can still send our preamble
+ *countRead = mOutputQueueUsed - mOutputQueueSent;
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Need to adjust this to only take as much as we can fit in with the
+ // preamble/settings/priority stuff
+ count -= (mOutputQueueUsed - mOutputQueueSent);
+
+ // Keep track of this to add it into countRead later, as
+ // stream->ReadSegments will likely change the value of mOutputQueueUsed.
+ earlyDataUsed = mOutputQueueUsed - mOutputQueueSent;
+ }
+
+ LOG3(
+ ("Http2Session %p will write from Http2StreamBase %p 0x%X "
+ "block-input=%d block-output=%d\n",
+ this, stream.get(), stream->StreamID(), stream->RequestBlockedOnRead(),
+ stream->BlockedOnRwin()));
+
+ rv = stream->ReadSegments(this, count, countRead);
+
+ if (earlyDataUsed) {
+ // Do this here because countRead could get reset somewhere down the rabbit
+ // hole of stream->ReadSegments, and we want to make sure we return the
+ // proper value to our caller.
+ *countRead += earlyDataUsed;
+ }
+
+ if (mAttemptingEarlyData && !m0RTTStreams.Contains(stream)) {
+ LOG3(("Http2Session::ReadSegmentsAgain adding stream %d to m0RTTStreams\n",
+ stream->StreamID()));
+ m0RTTStreams.AppendElement(stream);
+ }
+
+ // Not every permutation of stream->ReadSegents produces data (and therefore
+ // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
+ // of that. But we might still have old data buffered that would be good
+ // to flush.
+ FlushOutputQueue();
+
+ // Allow new server reads - that might be data or control information
+ // (e.g. window updates or http replies) that are responses to these writes
+ Unused << ResumeRecv();
+
+ if (stream->RequestBlockedOnRead()) {
+ // We are blocked waiting for input - either more http headers or
+ // any request body data. When more data from the request stream
+ // becomes available the httptransaction will call conn->ResumeSend().
+
+ LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
+
+ // call readsegments again if there are other streams ready
+ // to run in this session
+ if (GetWriteQueueSize()) {
+ rv = NS_OK;
+ } else {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ SetWriteCallbacks();
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::ReadSegments %p may return FAIL code %" PRIX32, this,
+ static_cast<uint32_t>(rv)));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+
+ CleanupStream(stream, rv, CANCEL_ERROR);
+ if (SoftStreamError(rv)) {
+ LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
+ *again = false;
+ SetWriteCallbacks();
+ rv = NS_OK;
+ }
+ return rv;
+ }
+
+ if (*countRead > 0) {
+ LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d", this,
+ stream.get(), *countRead));
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ return rv;
+ }
+
+ if (stream->BlockedOnRwin()) {
+ LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
+ this, stream.get(), stream->StreamID()));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", this,
+ stream.get()));
+
+ // call readsegments again if there are other streams ready
+ // to go in this session
+ SetWriteCallbacks();
+
+ return rv;
+}
+
+nsresult Http2Session::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ bool again = false;
+ return ReadSegmentsAgain(reader, count, countRead, &again);
+}
+
+nsresult Http2Session::ReadyToProcessDataFrame(
+ enum internalStateType newState) {
+ MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
+ newState == DISCARDING_DATA_FRAME_PADDING);
+ ChangeDownstreamState(newState);
+
+ Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mInputFrameDataSize >> 10);
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ if (!mInputFrameID) {
+ LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
+ this));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ nsresult rv = SetInputFrameDataStream(mInputFrameID);
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
+ "failed. probably due to verification.\n",
+ this, mInputFrameID));
+ return rv;
+ }
+ if (!mInputFrameDataStream) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
+ "failed. Next = 0x%X",
+ this, mInputFrameID, mNextStreamID));
+ if (mInputFrameID >= mNextStreamID) {
+ GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
+ }
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (mInputFrameDataStream->RecvdFin() ||
+ mInputFrameDataStream->RecvdReset() ||
+ mInputFrameDataStream->SentReset()) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Data arrived for already server closed stream.\n",
+ this, mInputFrameID));
+ if (mInputFrameDataStream->RecvdFin() ||
+ mInputFrameDataStream->RecvdReset()) {
+ GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
+ }
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (mInputFrameDataSize == 0 && !mInputFrameFinal) {
+ // Only if non-final because the stream properly handles final frames of any
+ // size, and we want the stream to be able to notice its own end flag.
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Ignoring 0-length non-terminal data frame.",
+ this, mInputFrameID));
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (newState == PROCESSING_DATA_FRAME &&
+ !mInputFrameDataStream->AllHeadersReceived()) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Receiving data frame without having headers.",
+ this, mInputFrameID));
+ CleanupStream(mInputFrameDataStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY,
+ PROTOCOL_ERROR);
+ return NS_OK;
+ }
+
+ LOG3(
+ ("Start Processing Data Frame. "
+ "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
+ this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
+ mInputFrameDataSize));
+ UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
+
+ if (mInputFrameDataStream) {
+ mInputFrameDataStream->SetRecvdData(true);
+ }
+
+ return NS_OK;
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just the http2 frame header and from there the appropriate *Stream
+// is identified from the Stream-ID. The http transaction associated with
+// that read then pulls in the data directly, which it will feed to
+// OnWriteSegment(). That function will gateway it into http and feed
+// it to the appropriate transaction.
+
+// we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
+// and decide if it is data or control.. if it is control, just deal with it.
+// if it is data, identify the stream
+// call stream->WriteSegments which can call this::OnWriteSegment to get the
+// data. It always gets full frames if they are part of the stream
+
+nsresult Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten, bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::WriteSegments %p InternalState %X\n", this,
+ mDownstreamState));
+
+ *countWritten = 0;
+
+ if (mClosed) {
+ LOG(("Http2Session::WriteSegments %p already closed", this));
+ // We return NS_ERROR_ABORT (a "soft" error) here, so when this error is
+ // propagated to another Http2Session, the Http2Session will not be closed
+ // due to this error code.
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = ConfirmTLSProfile();
+ if (NS_FAILED(rv)) return rv;
+
+ SetWriteCallbacks();
+
+ // If there are http transactions attached to a push stream with filled
+ // buffers trigger that data pump here. This only reads from buffers (not the
+ // network) so mDownstreamState doesn't matter.
+ RefPtr<Http2StreamBase> pushConnectedStream =
+ GetNextStreamFromQueue(mPushesReadyForRead);
+ if (pushConnectedStream) {
+ return ProcessConnectedPush(pushConnectedStream, writer, count,
+ countWritten);
+ }
+
+ // feed gecko channels that previously stopped consuming data
+ // only take data from stored buffers
+ RefPtr<Http2StreamBase> slowConsumer =
+ GetNextStreamFromQueue(mSlowConsumersReadyForRead);
+ if (slowConsumer) {
+ internalStateType savedState = mDownstreamState;
+ mDownstreamState = NOT_USING_NETWORK;
+ rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
+ mDownstreamState = savedState;
+ return rv;
+ }
+
+ // The BUFFERING_OPENING_SETTINGS state is just like any
+ // BUFFERING_FRAME_HEADER except the only frame type it will allow is SETTINGS
+
+ // The session layer buffers the leading 8 byte header of every frame.
+ // Non-Data frames are then buffered for their full length, but data
+ // frames (type 0) are passed through to the http stack unprocessed
+
+ if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
+ mDownstreamState == BUFFERING_FRAME_HEADER) {
+ // The first 9 bytes of every frame is header information that
+ // we are going to want to strip before passing to http. That is
+ // true of both control and data packets.
+
+ MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes,
+ "Frame Buffer Used Too Large for State");
+
+ rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
+ kFrameHeaderBytes - mInputFrameBufferUsed, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p buffering frame header read failure %" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Frame Header",
+ &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
+
+ mInputFrameBufferUsed += *countWritten;
+
+ if (mInputFrameBufferUsed < kFrameHeaderBytes) {
+ LOG3(
+ ("Http2Session::WriteSegments %p "
+ "BUFFERING FRAME HEADER incomplete size=%d",
+ this, mInputFrameBufferUsed));
+ return rv;
+ }
+
+ // 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
+ uint8_t totallyWastedByte = mInputFrameBuffer.get()[0];
+ mInputFrameDataSize =
+ NetworkEndian::readUint16(mInputFrameBuffer.get() + 1);
+ if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) {
+ LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte,
+ mInputFrameDataSize));
+ return SessionError(PROTOCOL_ERROR);
+ }
+ mInputFrameType = *reinterpret_cast<uint8_t*>(mInputFrameBuffer.get() +
+ kFrameLengthBytes);
+ mInputFrameFlags = *reinterpret_cast<uint8_t*>(
+ mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes);
+ mInputFrameID =
+ NetworkEndian::readUint32(mInputFrameBuffer.get() + kFrameLengthBytes +
+ kFrameTypeBytes + kFrameFlagBytes);
+ mInputFrameID &= 0x7fffffff;
+ mInputFrameDataRead = 0;
+
+ if (mInputFrameType == FRAME_TYPE_DATA ||
+ mInputFrameType == FRAME_TYPE_HEADERS) {
+ mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
+ } else {
+ mInputFrameFinal = false;
+ }
+
+ mPaddingLength = 0;
+
+ LOG3(("Http2Session::WriteSegments[%p::%" PRIu64 "] Frame Header Read "
+ "type %X data len %u flags %x id 0x%X",
+ this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
+ mInputFrameID));
+
+ // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION
+ // of a HEADERS frame with a matching ID (section 6.2)
+ if (mExpectedHeaderID && ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
+ (mExpectedHeaderID != mInputFrameID))) {
+ LOG3(
+ ("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ // if mExpectedPushPromiseID is non 0, it means this frame must be a
+ // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
+ if (mExpectedPushPromiseID &&
+ ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
+ (mExpectedPushPromiseID != mInputFrameID))) {
+ LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
+ mExpectedPushPromiseID));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
+ mInputFrameType != FRAME_TYPE_SETTINGS) {
+ LOG3(("First Frame Type Must Be Settings\n"));
+ mPeerFailedHandshake = true;
+
+ // Don't allow any more h2 connections to this host
+ RefPtr<nsHttpConnectionInfo> ci = ConnectionInfo();
+ if (ci) {
+ gHttpHandler->ExcludeHttp2(ci);
+ }
+
+ // Go through and re-start all of our transactions with h2 disabled.
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ stream->DisableSpdy();
+ CloseStream(stream, NS_ERROR_NET_RESET);
+ }
+ mStreamTransactionHash.Clear();
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ if (mInputFrameType != FRAME_TYPE_DATA) { // control frame
+ EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes,
+ kFrameHeaderBytes, mInputFrameBufferSize);
+ ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
+ } else if (mInputFrameFlags & kFlag_PADDED) {
+ ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
+ } else {
+ rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
+ MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED,
+ "Processing padding control on unpadded frame");
+
+ MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1),
+ "Frame buffer used too large for state");
+
+ rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
+ (kFrameHeaderBytes + 1) - mInputFrameBufferUsed,
+ countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session %p buffering data frame padding control read failure "
+ "%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Data Frame Padding Control",
+ &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
+
+ mInputFrameBufferUsed += *countWritten;
+
+ if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) {
+ LOG3(
+ ("Http2Session::WriteSegments %p "
+ "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
+ this, mInputFrameBufferUsed - 8));
+ return rv;
+ }
+
+ ++mInputFrameDataRead;
+
+ char* control = &mInputFrameBuffer[kFrameHeaderBytes];
+ mPaddingLength = static_cast<uint8_t>(*control);
+
+ LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
+ mInputFrameID, mPaddingLength));
+
+ if (1U + mPaddingLength > mInputFrameDataSize) {
+ LOG3(
+ ("Http2Session::WriteSegments %p stream 0x%X padding too large for "
+ "frame",
+ this, mInputFrameID));
+ return SessionError(PROTOCOL_ERROR);
+ }
+ if (1U + mPaddingLength == mInputFrameDataSize) {
+ // This frame consists entirely of padding, we can just discard it
+ LOG3(
+ ("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
+ this, mInputFrameID));
+ rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ LOG3(
+ ("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
+ this, mInputFrameID));
+ rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
+ nsresult streamCleanupCode;
+
+ // There is no bounds checking on the error code.. we provide special
+ // handling for a couple of cases and all others (including unknown) are
+ // equivalent to cancel.
+ if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
+ streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely
+ mInputFrameDataStream->ReuseConnectionOnRestartOK(true);
+ } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
+ streamCleanupCode = NS_ERROR_NET_RESET;
+ mInputFrameDataStream->ReuseConnectionOnRestartOK(true);
+ mInputFrameDataStream->DisableSpdy();
+ // actually allow restart by unsticking
+ mInputFrameDataStream->MakeNonSticky();
+ } else {
+ streamCleanupCode = mInputFrameDataStream->RecvdData()
+ ? NS_ERROR_NET_PARTIAL_TRANSFER
+ : NS_ERROR_NET_INTERRUPT;
+ }
+
+ if (mDownstreamRstReason == COMPRESSION_ERROR) {
+ mShouldGoAway = true;
+ }
+
+ // mInputFrameDataStream is reset by ChangeDownstreamState
+ Http2StreamBase* stream = mInputFrameDataStream;
+ ResetDownstreamState();
+ LOG3(
+ ("Http2Session::WriteSegments cleanup stream on recv of rst "
+ "session=%p stream=%p 0x%X\n",
+ this, stream, stream ? stream->StreamID() : 0));
+ CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
+ return NS_OK;
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME ||
+ mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
+ // The cleanup stream should only be set while stream->WriteSegments is
+ // on the stack and then cleaned up in this code block afterwards.
+ MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
+ mNeedsCleanup = nullptr; /* just in case */
+
+ if (!mInputFrameDataStream) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ uint32_t streamID = mInputFrameDataStream->StreamID();
+ mSegmentWriter = writer;
+ rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ if (SoftStreamError(rv)) {
+ // This will happen when the transaction figures out it is EOF, generally
+ // due to a content-length match being made. Return OK from this function
+ // otherwise the whole session would be torn down.
+
+ // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
+ // back to PROCESSING_DATA_FRAME where we came from
+ mDownstreamState = PROCESSING_DATA_FRAME;
+
+ if (mInputFrameDataRead == mInputFrameDataSize) ResetDownstreamState();
+ LOG3(
+ ("Http2Session::WriteSegments session=%p id 0x%X "
+ "needscleanup=%p. cleanup stream based on "
+ "stream->writeSegments returning code %" PRIx32 "\n",
+ this, streamID, mNeedsCleanup, static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
+ CleanupStream(
+ streamID,
+ (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK,
+ CANCEL_ERROR);
+ mNeedsCleanup = nullptr;
+ *again = false;
+ rv = ResumeRecv();
+ if (NS_FAILED(rv)) {
+ LOG3(("ResumeRecv returned code %x", static_cast<uint32_t>(rv)));
+ }
+ return NS_OK;
+ }
+
+ if (mNeedsCleanup) {
+ LOG3(
+ ("Http2Session::WriteSegments session=%p stream=%p 0x%X "
+ "cleanup stream based on mNeedsCleanup.\n",
+ this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
+ CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
+ mNeedsCleanup = nullptr;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p data frame read failure %" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ }
+
+ return rv;
+ }
+
+ if (mDownstreamState == DISCARDING_DATA_FRAME ||
+ mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
+ char trash[4096];
+ uint32_t discardCount =
+ std::min(mInputFrameDataSize - mInputFrameDataRead, 4096U);
+ LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of %s",
+ this, discardCount,
+ mDownstreamState == DISCARDING_DATA_FRAME ? "data" : "padding"));
+
+ if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) {
+ // Only do this short-cirtuit if we're not discarding a pure padding
+ // frame, as we need to potentially handle the stream FIN in those cases.
+ // See bug 1381016 comment 36 for more details.
+ ResetDownstreamState();
+ Unused << ResumeRecv();
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = NetworkRead(writer, trash, discardCount, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p discard frame read failure %" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+
+ if (mInputFrameDataRead == mInputFrameDataSize) {
+ Http2StreamBase* streamToCleanup = nullptr;
+ if (mInputFrameFinal) {
+ streamToCleanup = mInputFrameDataStream;
+ }
+
+ bool discardedPadding =
+ (mDownstreamState == DISCARDING_DATA_FRAME_PADDING);
+ ResetDownstreamState();
+
+ if (streamToCleanup) {
+ Http2PushedStream* pushed = streamToCleanup->GetHttp2PushedStream();
+ if (discardedPadding && pushed) {
+ // Pushed streams are special on padding-only final data frames.
+ // See bug 1409570 comments 6-8 for details.
+ pushed->SetPushComplete();
+ Http2StreamBase* pushSink = pushed->GetConsumerStream();
+ if (pushSink) {
+ bool enqueueSink = true;
+ for (const auto& s : mPushesReadyForRead) {
+ if (s == pushSink) {
+ enqueueSink = false;
+ break;
+ }
+ }
+ if (enqueueSink) {
+ AddStreamToQueue(pushSink, mPushesReadyForRead);
+ // No use trying to clean up, it won't do anything, anyway
+ streamToCleanup = nullptr;
+ }
+ }
+ }
+ CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
+ }
+ }
+ return rv;
+ }
+
+ if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
+ MOZ_ASSERT(false); // this cannot happen
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes,
+ "Frame Buffer Header Not Present");
+ MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize,
+ "allocation for control frame insufficient");
+
+ rv = NetworkRead(writer,
+ &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
+ mInputFrameDataSize - mInputFrameDataRead, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p buffering control frame read failure %" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Control Frame",
+ &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
+ *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+
+ if (mInputFrameDataRead != mInputFrameDataSize) return NS_OK;
+
+ MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
+ if (mInputFrameType < FRAME_TYPE_LAST) {
+ rv = sControlFunctions[mInputFrameType](this);
+ } else {
+ // Section 4.1 requires this to be ignored; though protocol_error would
+ // be better
+ LOG3(("Http2Session %p unknown frame type %x ignored\n", this,
+ mInputFrameType));
+ ResetDownstreamState();
+ rv = NS_OK;
+ }
+
+ MOZ_ASSERT(NS_FAILED(rv) || mDownstreamState != BUFFERING_CONTROL_FRAME,
+ "Control Handler returned OK but did not change state");
+
+ if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);
+ return rv;
+}
+
+nsresult Http2Session::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ bool again = false;
+ return WriteSegmentsAgain(writer, count, countWritten, &again);
+}
+
+nsresult Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) {
+ MOZ_ASSERT(mAttemptingEarlyData);
+ LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this,
+ aRestart, aAlpnChanged));
+
+ for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
+ if (m0RTTStreams[i]) {
+ m0RTTStreams[i]->Finish0RTT(aRestart, aAlpnChanged);
+ }
+ }
+
+ if (aRestart) {
+ // 0RTT failed
+ if (aAlpnChanged) {
+ // This is a slightly more involved case - we need to get all our streams/
+ // transactions back in the queue so they can restart as http/1
+
+ // These must be set this way to ensure we gracefully restart all streams
+ mGoAwayID = 0;
+ mCleanShutdown = true;
+
+ // Close takes care of the rest of our work for us. The reason code here
+ // doesn't matter, as we aren't actually going to send a GOAWAY frame, but
+ // we use NS_ERROR_NET_RESET as it's closest to the truth.
+ Close(NS_ERROR_NET_RESET);
+ } else {
+ // This is the easy case - early data failed, but we're speaking h2, so
+ // we just need to rewind to the beginning of the preamble and try again.
+ mOutputQueueSent = 0;
+
+ for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
+ if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
+ TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
+ }
+ }
+ }
+ } else {
+ // 0RTT succeeded
+ for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
+ if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
+ TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
+ }
+ }
+ // Make sure we look for any incoming data in repsonse to our early data.
+ Unused << ResumeRecv();
+ }
+
+ mAttemptingEarlyData = false;
+ m0RTTStreams.Clear();
+ mCannotDo0RTTStreams.Clear();
+ RealignOutputQueue();
+
+ return NS_OK;
+}
+
+nsresult Http2Session::ProcessConnectedPush(
+ Http2StreamBase* pushConnectedStream, nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n", this,
+ pushConnectedStream->StreamID()));
+ mSegmentWriter = writer;
+ nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+
+ // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
+ // so we need this check to determine the truth.
+ Http2Stream* h2Stream = pushConnectedStream->GetHttp2Stream();
+ MOZ_ASSERT(h2Stream);
+ if (NS_SUCCEEDED(rv) && !*countWritten && h2Stream &&
+ h2Stream->PushSource() && h2Stream->PushSource()->GetPushComplete()) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
+ rv = NS_OK;
+ }
+
+ // if we return OK to nsHttpConnection it will use mSocketInCondition
+ // to determine whether to schedule more reads, incorrectly
+ // assuming that nsHttpConnection::OnSocketWrite() was called.
+ if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ Unused << ResumeRecv();
+ }
+ return rv;
+}
+
+nsresult Http2Session::ProcessSlowConsumer(Http2StreamBase* slowConsumer,
+ nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n", this,
+ slowConsumer->StreamID()));
+ mSegmentWriter = writer;
+ nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+ LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %" PRIX32
+ " %d\n",
+ this, slowConsumer->StreamID(), static_cast<uint32_t>(rv),
+ *countWritten));
+ if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (NS_SUCCEEDED(rv) && (*countWritten > 0)) {
+ // There have been buffered bytes successfully fed into the
+ // formerly blocked consumer. Repeat until buffer empty or
+ // consumer is blocked again.
+ UpdateLocalRwin(slowConsumer, 0);
+ ConnectSlowConsumer(slowConsumer);
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+void Http2Session::UpdateLocalStreamWindow(Http2StreamBase* stream,
+ uint32_t bytes) {
+ if (!stream) { // this is ok - it means there was a data frame for a rst
+ // stream
+ return;
+ }
+
+ // If this data packet was not for a valid or live stream then there
+ // is no reason to mess with the flow control
+ if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
+ mInputFrameFinal) {
+ return;
+ }
+
+ stream->DecrementClientReceiveWindow(bytes);
+
+ // Don't necessarily ack every data packet. Only do it
+ // after a significant amount of data.
+ uint64_t unacked = stream->LocalUnAcked();
+ int64_t localWindow = stream->ClientReceiveWindow();
+
+ LOG3(
+ ("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
+ "unacked=%" PRIu64 " localWindow=%" PRId64 "\n",
+ this, stream->StreamID(), bytes, unacked, localWindow));
+
+ if (!unacked) return;
+
+ if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) {
+ return;
+ }
+
+ if (!stream->HasSink()) {
+ LOG3(
+ ("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No "
+ "Sink\n",
+ this, stream->StreamID()));
+ return;
+ }
+
+ // Generate window updates directly out of session instead of the stream
+ // in order to avoid queue delays in getting the 'ACK' out.
+ uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
+
+ LOG3(
+ ("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
+ this, stream->StreamID(), toack));
+ stream->IncrementClientReceiveWindow(toack);
+ if (toack == 0) {
+ // Ensure we never send an illegal 0 window update
+ return;
+ }
+
+ // room for this packet needs to be ensured before calling this function
+ char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
+
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
+
+ LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4);
+ // dont flush here, this write can commonly be coalesced with a
+ // session window update to immediately follow.
+}
+
+void Http2Session::UpdateLocalSessionWindow(uint32_t bytes) {
+ if (!bytes) return;
+
+ mLocalSessionWindow -= bytes;
+
+ LOG3(
+ ("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
+ "localWindow=%" PRId64 "\n",
+ this, bytes, mLocalSessionWindow));
+
+ // Don't necessarily ack every data packet. Only do it
+ // after a significant amount of data.
+ if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
+ (mLocalSessionWindow > kEmergencyWindowThreshold)) {
+ return;
+ }
+
+ // Only send max bits of window updates at a time.
+ uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
+ uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
+
+ LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", this,
+ toack));
+ mLocalSessionWindow += toack;
+
+ if (toack == 0) {
+ // Ensure we never send an illegal 0 window update
+ return;
+ }
+
+ // room for this packet needs to be ensured before calling this function
+ char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
+
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
+
+ LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4);
+ // dont flush here, this write can commonly be coalesced with others
+}
+
+void Http2Session::UpdateLocalRwin(Http2StreamBase* stream, uint32_t bytes) {
+ // make sure there is room for 2 window updates even though
+ // we may not generate any.
+ EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4));
+
+ UpdateLocalStreamWindow(stream, bytes);
+ UpdateLocalSessionWindow(bytes);
+ FlushOutputQueue();
+}
+
+void Http2Session::Close(nsresult aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mClosed) return;
+
+ LOG3(("Http2Session::Close %p %" PRIX32, this,
+ static_cast<uint32_t>(aReason)));
+
+ mClosed = true;
+
+ Shutdown(aReason);
+
+ mStreamIDHash.Clear();
+ mStreamTransactionHash.Clear();
+ mTunnelStreams.Clear();
+ mProcessedWaitingWebsockets = true;
+
+ uint32_t goAwayReason;
+ if (mGoAwayReason != NO_HTTP_ERROR) {
+ goAwayReason = mGoAwayReason;
+ } else if (NS_SUCCEEDED(aReason)) {
+ goAwayReason = NO_HTTP_ERROR;
+ } else if (aReason == NS_ERROR_NET_HTTP2_SENT_GOAWAY) {
+ goAwayReason = PROTOCOL_ERROR;
+ } else if (mCleanShutdown) {
+ goAwayReason = NO_HTTP_ERROR;
+ } else {
+ goAwayReason = INTERNAL_ERROR;
+ }
+ if (!mAttemptingEarlyData) {
+ GenerateGoAway(goAwayReason);
+ }
+ mConnection = nullptr;
+ mSegmentReader = nullptr;
+ mSegmentWriter = nullptr;
+}
+
+nsHttpConnectionInfo* Http2Session::ConnectionInfo() {
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ return ci.get();
+}
+
+void Http2Session::CloseTransaction(nsAHttpTransaction* aTransaction,
+ nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32, this, aTransaction,
+ static_cast<uint32_t>(aResult)));
+
+ // Generally this arrives as a cancel event from the connection manager.
+
+ // need to find the stream and call CleanupStream() on it.
+ RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(aTransaction);
+ if (!stream) {
+ LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32 " - not found.", this,
+ aTransaction, static_cast<uint32_t>(aResult)));
+ return;
+ }
+ LOG3(
+ ("Http2Session::CloseTransaction probably a cancel. "
+ "this=%p, trans=%p, result=%" PRIx32 ", streamID=0x%X stream=%p",
+ this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamID(),
+ stream.get()));
+ CleanupStream(stream, aResult, CANCEL_ERROR);
+ nsresult rv = ResumeRecv();
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::CloseTransaction %p %p %x ResumeRecv returned %x",
+ this, aTransaction, static_cast<uint32_t>(aResult),
+ static_cast<uint32_t>(rv)));
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult Http2Session::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv;
+
+ // If we can release old queued data then we can try and write the new
+ // data directly to the network without using the output queue at all
+ if (mOutputQueueUsed) FlushOutputQueue();
+
+ if (!mOutputQueueUsed && mSegmentReader) {
+ // try and write directly without output queue
+ rv = mSegmentReader->OnReadSegment(buf, count, countRead);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ *countRead = 0;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*countRead < count) {
+ uint32_t required = count - *countRead;
+ // assuming a commitment() happened, this ensurebuffer is a nop
+ // but just in case the queuesize is too small for the required data
+ // call ensurebuffer().
+ EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
+ memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
+ mOutputQueueUsed = required;
+ }
+
+ *countRead = count;
+ return NS_OK;
+ }
+
+ // At this point we are going to buffer the new data in the output
+ // queue if it fits. By coalescing multiple small submissions into one larger
+ // buffer we can get larger writes out to the network later on.
+
+ // This routine should not be allowed to fill up the output queue
+ // all on its own - at least kQueueReserved bytes are always left
+ // for other routines to use - but this is an all-or-nothing function,
+ // so if it will not all fit just return WOULD_BLOCK
+
+ if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved)) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
+ mOutputQueueUsed += count;
+ *countRead = count;
+
+ FlushOutputQueue();
+
+ return NS_OK;
+}
+
+nsresult Http2Session::CommitToSegmentSize(uint32_t count,
+ bool forceCommitment) {
+ if (mOutputQueueUsed && !mAttemptingEarlyData) FlushOutputQueue();
+
+ // would there be enough room to buffer this if needed?
+ if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) {
+ return NS_OK;
+ }
+
+ // if we are using part of our buffers already, try again later unless
+ // forceCommitment is set.
+ if (mOutputQueueUsed && !forceCommitment) return NS_BASE_STREAM_WOULD_BLOCK;
+
+ if (mOutputQueueUsed) {
+ // normally we avoid the memmove of RealignOutputQueue, but we'll try
+ // it if forceCommitment is set before growing the buffer.
+ RealignOutputQueue();
+
+ // is there enough room now?
+ if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) {
+ return NS_OK;
+ }
+ }
+
+ // resize the buffers as needed
+ EnsureOutputBuffer(count + kQueueReserved);
+
+ MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
+ "buffer not as large as expected");
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult Http2Session::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv;
+
+ if (!mSegmentWriter) {
+ // the only way this could happen would be if Close() were called on the
+ // stack with WriteSegments()
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mDownstreamState == NOT_USING_NETWORK ||
+ mDownstreamState == BUFFERING_FRAME_HEADER ||
+ mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME) {
+ if (mInputFrameFinal && mInputFrameDataRead == mInputFrameDataSize) {
+ *countWritten = 0;
+ SetNeedsCleanup();
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
+ rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
+ if (NS_FAILED(rv)) return rv;
+
+ LogIO(this, mInputFrameDataStream, "Reading Data Frame", buf,
+ *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+ if (mPaddingLength &&
+ (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
+ // We are crossing from real HTTP data into the realm of padding. If
+ // we've actually crossed the line, we need to munge countWritten for the
+ // sake of goodness and sanity. No matter what, any future calls to
+ // WriteSegments need to just discard data until we reach the end of this
+ // frame.
+ if (mInputFrameDataSize != mInputFrameDataRead) {
+ // Only change state if we still have padding to read. If we don't do
+ // this, we can end up hanging on frames that combine real data,
+ // padding, and END_STREAM (see bug 1019921)
+ ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
+ }
+ uint32_t paddingRead =
+ mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
+ LOG3(
+ ("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
+ "crossed from HTTP data into padding (%d of %d) countWritten=%d",
+ this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
+ paddingRead, mPaddingLength, *countWritten));
+ *countWritten -= paddingRead;
+ LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
+ this, mInputFrameID, *countWritten));
+ }
+
+ mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
+ if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal) {
+ ResetDownstreamState();
+ }
+
+ return rv;
+ }
+
+ if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
+ if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
+ mInputFrameFinal) {
+ *countWritten = 0;
+ SetNeedsCleanup();
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ count = std::min<uint32_t>(
+ count, mFlatHTTPResponseHeaders.Length() - mFlatHTTPResponseHeadersOut);
+ memcpy(buf, mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
+ count);
+ mFlatHTTPResponseHeadersOut += count;
+ *countWritten = count;
+
+ if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
+ // Since mInputFrameFinal can be reset, we need to also check RecvdFin to
+ // see if a stream doesn’t expect more frames.
+ if (!mInputFrameFinal && !mInputFrameDataStream->RecvdFin()) {
+ // If more frames are expected in this stream, then reset the state so
+ // they can be handled. Otherwise (e.g. a 0 length response with the fin
+ // on the incoming headers) stay in PROCESSING_COMPLETE_HEADERS state so
+ // the SetNeedsCleanup() code above can cleanup the stream.
+ ResetDownstreamState();
+ }
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+}
+
+void Http2Session::SetNeedsCleanup() {
+ LOG3(
+ ("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
+ "stream %p 0x%X",
+ this, mInputFrameDataStream, mInputFrameDataStream->StreamID()));
+
+ // This will result in Close() being called
+ MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
+ mInputFrameDataStream->SetResponseIsComplete();
+ mNeedsCleanup = mInputFrameDataStream;
+ ResetDownstreamState();
+}
+
+void Http2Session::ConnectPushedStream(Http2StreamBase* stream) {
+ AddStreamToQueue(stream, mPushesReadyForRead);
+ Unused << ForceRecv();
+}
+
+void Http2Session::ConnectSlowConsumer(Http2StreamBase* stream) {
+ LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n", this,
+ stream->StreamID()));
+ AddStreamToQueue(stream, mSlowConsumersReadyForRead);
+ Unused << ForceRecv();
+}
+
+nsresult Http2Session::BufferOutput(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ RefPtr<nsAHttpSegmentReader> old;
+ mSegmentReader.swap(old); // Make mSegmentReader null
+ nsresult rv = OnReadSegment(buf, count, countRead);
+ mSegmentReader.swap(old); // Restore the old mSegmentReader
+ return rv;
+}
+
+bool // static
+Http2Session::ALPNCallback(nsITLSSocketControl* tlsSocketControl) {
+ LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", tlsSocketControl));
+ if (tlsSocketControl) {
+ int16_t version = tlsSocketControl->GetSSLVersionOffered();
+ LOG3(("Http2Session::ALPNCallback version=%x\n", version));
+
+ if (version == nsITLSSocketControl::TLS_VERSION_1_2 &&
+ !gHttpHandler->IsH2MandatorySuiteEnabled()) {
+ LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
+ return false;
+ }
+
+ if (version >= nsITLSSocketControl::TLS_VERSION_1_2) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult Http2Session::ConfirmTLSProfile() {
+ if (mTLSProfileConfirmed) {
+ return NS_OK;
+ }
+
+ LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", this,
+ mConnection.get()));
+
+ if (mAttemptingEarlyData) {
+ LOG3(
+ ("Http2Session::ConfirmTLSProfile %p temporarily passing due to early "
+ "data\n",
+ this));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_http_http2_enforce_tls_profile()) {
+ LOG3(
+ ("Http2Session::ConfirmTLSProfile %p passed due to configuration "
+ "bypass\n",
+ this));
+ mTLSProfileConfirmed = true;
+ return NS_OK;
+ }
+
+ if (!mConnection) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(ssl));
+ LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this,
+ ssl.get()));
+ if (!ssl) return NS_ERROR_FAILURE;
+
+ int16_t version = ssl->GetSSLVersionUsed();
+ LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
+ if (version < nsITLSSocketControl::TLS_VERSION_1_2) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n",
+ this));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ uint16_t kea = ssl->GetKEAUsed();
+ if (kea == ssl_kea_ecdh_hybrid && !StaticPrefs::security_tls_enable_kyber()) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to disabled KEA %d\n",
+ this, kea));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ if (kea != ssl_kea_dh && kea != ssl_kea_ecdh && kea != ssl_kea_ecdh_hybrid) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
+ this, kea));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ uint32_t keybits = ssl->GetKEAKeyBits();
+ if (kea == ssl_kea_dh && keybits < 2048) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n",
+ this, keybits));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+ if (kea == ssl_kea_ecdh && keybits < 224) { // see rfc7540 9.2.1.
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n",
+ this, keybits));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ int16_t macAlgorithm = ssl->GetMACAlgorithmUsed();
+ LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n", this,
+ macAlgorithm));
+ if (macAlgorithm != nsITLSSocketControl::SSL_MAC_AEAD) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n",
+ this));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ /* We are required to send SNI. We do that already, so no check is done
+ * here to make sure we did. */
+
+ /* We really should check to ensure TLS compression isn't enabled on
+ * this connection. However, we never enable TLS compression on our end,
+ * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
+ * for the possibility for an interface to ensure it never gets turned on. */
+
+ mTLSProfileConfirmed = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Modified methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+void Http2Session::TransactionHasDataToWrite(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
+
+ // a trapped signal from the http transaction to the connection that
+ // it is no longer blocked on read.
+
+ RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream || !VerifyStream(stream)) {
+ LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
+ this, caller));
+ return;
+ }
+
+ LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n", this,
+ stream->StreamID()));
+
+ if (!mClosed) {
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ } else {
+ LOG3(
+ ("Http2Session::TransactionHasDataToWrite %p closed so not setting "
+ "Ready4Write\n",
+ this));
+ }
+
+ // NSPR poll will not poll the network if there are non system PR_FileDesc's
+ // that are ready - so we can get into a deadlock waiting for the system IO
+ // to come back here if we don't force the send loop manually.
+ Unused << ForceSend();
+}
+
+void Http2Session::TransactionHasDataToRecv(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
+
+ // a signal from the http transaction to the connection that it will consume
+ // more
+ RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream || !VerifyStream(stream)) {
+ LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found", this,
+ caller));
+ return;
+ }
+
+ LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n", this,
+ stream->StreamID()));
+ TransactionHasDataToRecv(stream);
+}
+
+void Http2Session::TransactionHasDataToWrite(Http2StreamBase* stream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x", this,
+ stream, stream->StreamID()));
+
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ Unused << ForceSend();
+}
+
+void Http2Session::TransactionHasDataToRecv(Http2StreamBase* caller) {
+ ConnectSlowConsumer(caller);
+}
+
+bool Http2Session::IsPersistent() { return true; }
+
+nsresult Http2Session::TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) {
+ MOZ_ASSERT(false, "TakeTransport of Http2Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+Http3WebTransportSession* Http2Session::GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ MOZ_ASSERT(false, "GetWebTransportSession of Http2Session");
+ return nullptr;
+}
+
+already_AddRefed<HttpConnectionBase> Http2Session::TakeHttpConnection() {
+ MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
+ return nullptr;
+}
+
+already_AddRefed<HttpConnectionBase> Http2Session::HttpConnection() {
+ if (mConnection) {
+ return mConnection->HttpConnection();
+ }
+ return nullptr;
+}
+
+void Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) {
+ *aOut = nullptr;
+}
+
+void Http2Session::SetConnection(nsAHttpConnection* aConn) {
+ mConnection = aConn;
+}
+
+//-----------------------------------------------------------------------------
+// unused methods of nsAHttpTransaction
+// We can be sure of this because Http2Session is only constructed in
+// nsHttpConnection and is never passed out of that object or a
+// TLSFilterTransaction TLS tunnel
+//-----------------------------------------------------------------------------
+
+void Http2Session::SetProxyConnectFailed() {
+ MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
+}
+
+bool Http2Session::IsDone() { return !mStreamTransactionHash.Count(); }
+
+nsresult Http2Session::Status() {
+ MOZ_ASSERT(false, "Http2Session::Status()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+uint32_t Http2Session::Caps() {
+ MOZ_ASSERT(false, "Http2Session::Caps()");
+ return 0;
+}
+
+nsHttpRequestHead* Http2Session::RequestHead() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(false,
+ "Http2Session::RequestHead() "
+ "should not be called after http/2 is setup");
+ return nullptr;
+}
+
+uint32_t Http2Session::Http1xTransactionCount() { return 0; }
+
+nsresult Http2Session::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // Generally this cannot be done with http/2 as transactions are
+ // started right away.
+
+ LOG3(("Http2Session::TakeSubTransactions %p\n", this));
+
+ if (mConcurrentHighWater > 0) return NS_ERROR_ALREADY_OPENED;
+
+ LOG3((" taking %d\n", mStreamTransactionHash.Count()));
+
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ outTransactions.AppendElement(iter.Key());
+
+ // Removing the stream from the hash will delete the stream and drop the
+ // transaction reference the hash held.
+ iter.Remove();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection* Http2Session::Connection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mConnection;
+}
+
+nsresult Http2Session::OnHeadersAvailable(nsAHttpTransaction* transaction,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ return NS_OK;
+}
+
+bool Http2Session::IsReused() {
+ if (!mConnection) {
+ return false;
+ }
+
+ return mConnection->IsReused();
+}
+
+nsresult Http2Session::PushBack(const char* buf, uint32_t len) {
+ return mConnection->PushBack(buf, len);
+}
+
+void Http2Session::SendPing() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http2Session::SendPing %p mPreviousUsed=%d", this, mPreviousUsed));
+
+ if (mPreviousUsed) {
+ // alredy in progress, get out
+ return;
+ }
+
+ mPingSentEpoch = PR_IntervalNow();
+ if (!mPingSentEpoch) {
+ mPingSentEpoch = 1; // avoid the 0 sentinel value
+ }
+ if (!mPingThreshold ||
+ (mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
+ mPreviousPingThreshold = mPingThreshold;
+ mPreviousUsed = true;
+ mPingThreshold = gHttpHandler->NetworkChangedTimeout();
+ // Reset mLastReadEpoch, so we can really check when do we got pong from the
+ // server.
+ mLastReadEpoch = 0;
+ }
+ GeneratePing(false);
+ Unused << ResumeRecv();
+}
+
+bool Http2Session::TestOriginFrame(const nsACString& hostname, int32_t port) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mOriginFrameActivated);
+
+ nsAutoCString key(hostname);
+ key.Append(':');
+ key.AppendInt(port);
+ bool rv = mOriginFrame.Get(key);
+ LOG3(("TestOriginFrame() hash.get %p %s %d\n", this, key.get(), rv));
+ if (!rv && ConnectionInfo()) {
+ // the SNI is also implicitly in this list, so consult that too
+ nsHttpConnectionInfo* ci = ConnectionInfo();
+ rv = nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
+ (port == ci->OriginPort());
+ LOG3(("TestOriginFrame() %p sni test %d\n", this, rv));
+ }
+ return rv;
+}
+
+bool Http2Session::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ return RealJoinConnection(hostname, port, true);
+}
+
+bool Http2Session::JoinConnection(const nsACString& hostname, int32_t port) {
+ return RealJoinConnection(hostname, port, false);
+}
+
+bool Http2Session::RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding) {
+ if (!mConnection || mClosed || mShouldGoAway) {
+ return false;
+ }
+
+ nsHttpConnectionInfo* ci = ConnectionInfo();
+ if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
+ (port == ci->OriginPort())) {
+ return true;
+ }
+
+ if (!mReceivedSettings) {
+ return false;
+ }
+
+ if (mOriginFrameActivated) {
+ bool originFrameResult = TestOriginFrame(hostname, port);
+ if (!originFrameResult) {
+ return false;
+ }
+ } else {
+ LOG3(("JoinConnection %p no origin frame check used.\n", this));
+ }
+
+ nsAutoCString key(hostname);
+ key.Append(':');
+ key.Append(justKidding ? 'k' : '.');
+ key.AppendInt(port);
+ bool cachedResult;
+ if (mJoinConnectionCache.Get(key, &cachedResult)) {
+ LOG(("joinconnection [%p %s] %s result=%d cache\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), cachedResult));
+ return cachedResult;
+ }
+
+ nsresult rv;
+ bool isJoined = false;
+
+ nsCOMPtr<nsITLSSocketControl> sslSocketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(sslSocketControl));
+ if (!sslSocketControl) {
+ return false;
+ }
+
+ // try all the coalescable versions we support.
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ bool joinedReturn = false;
+ if (StaticPrefs::network_http_http2_enabled()) {
+ if (justKidding) {
+ rv = sslSocketControl->TestJoinConnection(info->VersionString, hostname,
+ port, &isJoined);
+ } else {
+ rv = sslSocketControl->JoinConnection(info->VersionString, hostname, port,
+ &isJoined);
+ }
+ if (NS_SUCCEEDED(rv) && isJoined) {
+ joinedReturn = true;
+ }
+ }
+
+ LOG(("joinconnection [%p %s] %s result=%d lookup\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
+ mJoinConnectionCache.InsertOrUpdate(key, joinedReturn);
+ if (!justKidding) {
+ // cache a kidding entry too as this one is good for both
+ nsAutoCString key2(hostname);
+ key2.Append(':');
+ key2.Append('k');
+ key2.AppendInt(port);
+ if (!mJoinConnectionCache.Get(key2)) {
+ mJoinConnectionCache.InsertOrUpdate(key2, joinedReturn);
+ }
+ }
+ return joinedReturn;
+}
+
+void Http2Session::CurrentBrowserIdChanged(uint64_t id) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCurrentBrowserId = id;
+
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ stream->CurrentBrowserIdChanged(id);
+ }
+}
+
+void Http2Session::SetCleanShutdown(bool aCleanShutdown) {
+ mCleanShutdown = aCleanShutdown;
+}
+
+WebSocketSupport Http2Session::GetWebSocketSupport() {
+ LOG3(("Http2Session::GetWebSocketSupport %p enable=%d allow=%d processed=%d",
+ this, mEnableWebsockets, mPeerAllowsWebsockets,
+ mProcessedWaitingWebsockets));
+
+ if (!mEnableWebsockets) {
+ return WebSocketSupport::NO_SUPPORT;
+ }
+
+ if (mPeerAllowsWebsockets) {
+ return WebSocketSupport::SUPPORTED;
+ }
+
+ if (!mProcessedWaitingWebsockets) {
+ mHasTransactionWaitingForWebsockets = true;
+ return WebSocketSupport::UNSURE;
+ }
+
+ return WebSocketSupport::NO_SUPPORT;
+}
+
+PRIntervalTime Http2Session::LastWriteTime() {
+ return mConnection->LastWriteTime();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h
new file mode 100644
index 0000000000..767e5cf9f7
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.h
@@ -0,0 +1,626 @@
+/* -*- 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_Http2Session_h
+#define mozilla_net_Http2Session_h
+
+// HTTP/2 - RFC 7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "ASpdySession.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsAHttpConnection.h"
+#include "nsCOMArray.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashMap.h"
+#include "nsDeque.h"
+#include "nsHashKeys.h"
+#include "nsHttpRequestHead.h"
+#include "nsICacheEntryOpenCallback.h"
+
+#include "Http2Compression.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class Http2PushedStream;
+class Http2StreamBase;
+class Http2StreamTunnel;
+class nsHttpTransaction;
+class nsHttpConnection;
+
+enum Http2StreamBaseType { Normal, WebSocket, Tunnel, ServerPush };
+
+// b23b147c-c4f8-4d6e-841a-09f29a010de7
+#define NS_HTTP2SESSION_IID \
+ { \
+ 0xb23b147c, 0xc4f8, 0x4d6e, { \
+ 0x84, 0x1a, 0x09, 0xf2, 0x9a, 0x01, 0x0d, 0xe7 \
+ } \
+ }
+
+class Http2Session final : public ASpdySession,
+ public nsAHttpConnection,
+ public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter {
+ ~Http2Session();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP2SESSION_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+
+ static Http2Session* CreateSession(nsISocketTransport*,
+ enum SpdyVersion version,
+ bool attemptingEarlyData);
+
+ [[nodiscard]] bool AddStream(nsAHttpTransaction*, int32_t,
+ nsIInterfaceRequestor*) override;
+ bool CanReuse() override { return !mShouldGoAway && !mClosed; }
+ bool RoomForMoreStreams() override;
+ enum SpdyVersion SpdyVersion() override;
+ bool TestJoinConnection(const nsACString& hostname, int32_t port) override;
+ bool JoinConnection(const nsACString& hostname, int32_t port) override;
+
+ // When the connection is active this is called up to once every 1 second
+ // return the interval (in seconds) that the connection next wants to
+ // have this invoked. It might happen sooner depending on the needs of
+ // other connections.
+ uint32_t ReadTimeoutTick(PRIntervalTime now) override;
+
+ // Idle time represents time since "goodput".. e.g. a data or header frame
+ PRIntervalTime IdleTime() override;
+
+ // Registering with a newID of 0 means pick the next available odd ID
+ uint32_t RegisterStreamID(Http2StreamBase*, uint32_t aNewID = 0);
+
+ /*
+ HTTP/2 framing
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Length (16) | Type (8) | Flags (8) |
+ +-+-------------+---------------+-------------------------------+
+ |R| Stream Identifier (31) |
+ +-+-------------------------------------------------------------+
+ | Frame Data (0...) ...
+ +---------------------------------------------------------------+
+ */
+
+ enum FrameType {
+ FRAME_TYPE_DATA = 0x0,
+ FRAME_TYPE_HEADERS = 0x1,
+ FRAME_TYPE_PRIORITY = 0x2,
+ FRAME_TYPE_RST_STREAM = 0x3,
+ FRAME_TYPE_SETTINGS = 0x4,
+ FRAME_TYPE_PUSH_PROMISE = 0x5,
+ FRAME_TYPE_PING = 0x6,
+ FRAME_TYPE_GOAWAY = 0x7,
+ FRAME_TYPE_WINDOW_UPDATE = 0x8,
+ FRAME_TYPE_CONTINUATION = 0x9,
+ FRAME_TYPE_ALTSVC = 0xA,
+ FRAME_TYPE_UNUSED = 0xB,
+ FRAME_TYPE_ORIGIN = 0xC,
+ FRAME_TYPE_LAST = 0xD
+ };
+
+ // NO_ERROR is a macro defined on windows, so we'll name the HTTP2 goaway
+ // code NO_ERROR to be NO_HTTP_ERROR
+ enum errorType {
+ NO_HTTP_ERROR = 0,
+ PROTOCOL_ERROR = 1,
+ INTERNAL_ERROR = 2,
+ FLOW_CONTROL_ERROR = 3,
+ SETTINGS_TIMEOUT_ERROR = 4,
+ STREAM_CLOSED_ERROR = 5,
+ FRAME_SIZE_ERROR = 6,
+ REFUSED_STREAM_ERROR = 7,
+ CANCEL_ERROR = 8,
+ COMPRESSION_ERROR = 9,
+ CONNECT_ERROR = 10,
+ ENHANCE_YOUR_CALM = 11,
+ INADEQUATE_SECURITY = 12,
+ HTTP_1_1_REQUIRED = 13,
+ UNASSIGNED = 31
+ };
+
+ // These are frame flags. If they, or other undefined flags, are
+ // used on frames other than the comments indicate they MUST be ignored.
+ const static uint8_t kFlag_END_STREAM = 0x01; // data, headers
+ const static uint8_t kFlag_END_HEADERS = 0x04; // headers, continuation
+ const static uint8_t kFlag_END_PUSH_PROMISE = 0x04; // push promise
+ const static uint8_t kFlag_ACK = 0x01; // ping and settings
+ const static uint8_t kFlag_PADDED =
+ 0x08; // data, headers, push promise, continuation
+ const static uint8_t kFlag_PRIORITY = 0x20; // headers
+
+ enum {
+ SETTINGS_TYPE_HEADER_TABLE_SIZE = 1, // compression table size
+ SETTINGS_TYPE_ENABLE_PUSH = 2, // can be used to disable push
+ SETTINGS_TYPE_MAX_CONCURRENT = 3, // streams recvr allowed to initiate
+ SETTINGS_TYPE_INITIAL_WINDOW = 4, // bytes for flow control default
+ SETTINGS_TYPE_MAX_FRAME_SIZE =
+ 5, // max frame size settings sender allows receipt of
+ // 6 is SETTINGS_TYPE_MAX_HEADER_LIST - advisory, we ignore it
+ // 7 is unassigned
+ SETTINGS_TYPE_ENABLE_CONNECT_PROTOCOL =
+ 8 // if sender implements extended CONNECT for websockets
+ };
+
+ // This should be big enough to hold all of your control packets,
+ // but if it needs to grow for huge headers it can do so dynamically.
+ const static uint32_t kDefaultBufferSize = 2048;
+
+ // kDefaultQueueSize must be >= other queue size constants
+ const static uint32_t kDefaultQueueSize = 32768;
+ const static uint32_t kQueueMinimumCleanup = 24576;
+ const static uint32_t kQueueTailRoom = 4096;
+ const static uint32_t kQueueReserved = 1024;
+
+ const static uint32_t kMaxStreamID = 0x7800000;
+
+ // This is a sentinel for a deleted stream. It is not a valid
+ // 31 bit stream ID.
+ const static uint32_t kDeadStreamID = 0xffffdead;
+
+ // below the emergency threshold of local window we ack every received
+ // byte. Above that we coalesce bytes into the MinimumToAck size.
+ const static int32_t kEmergencyWindowThreshold = 96 * 1024;
+ const static uint32_t kMinimumToAck = 4 * 1024 * 1024;
+
+ // The default rwin is 64KB - 1 unless updated by a settings frame
+ const static uint32_t kDefaultRwin = 65535;
+
+ // We limit frames to 2^14 bytes of length in order to preserve responsiveness
+ // This is the smallest allowed value for SETTINGS_MAX_FRAME_SIZE
+ const static uint32_t kMaxFrameData = 0x4000;
+
+ const static uint8_t kFrameLengthBytes = 3;
+ const static uint8_t kFrameStreamIDBytes = 4;
+ const static uint8_t kFrameFlagBytes = 1;
+ const static uint8_t kFrameTypeBytes = 1;
+ const static uint8_t kFrameHeaderBytes = kFrameLengthBytes + kFrameFlagBytes +
+ kFrameTypeBytes +
+ kFrameStreamIDBytes;
+
+ enum {
+ kLeaderGroupID = 0x3,
+ kOtherGroupID = 0x5,
+ kBackgroundGroupID = 0x7,
+ kSpeculativeGroupID = 0x9,
+ kFollowerGroupID = 0xB,
+ kUrgentStartGroupID = 0xD
+ // Hey, you! YES YOU! If you add/remove any groups here, you almost
+ // certainly need to change the lookup of the stream/ID hash in
+ // Http2Session::OnTransportStatus and |kPriorityGroupCount| below.
+ // Yeah, that's right. YOU!
+ };
+ const static uint8_t kPriorityGroupCount = 6;
+
+ static nsresult RecvHeaders(Http2Session*);
+ static nsresult RecvPriority(Http2Session*);
+ static nsresult RecvRstStream(Http2Session*);
+ static nsresult RecvSettings(Http2Session*);
+ static nsresult RecvPushPromise(Http2Session*);
+ static nsresult RecvPing(Http2Session*);
+ static nsresult RecvGoAway(Http2Session*);
+ static nsresult RecvWindowUpdate(Http2Session*);
+ static nsresult RecvContinuation(Http2Session*);
+ static nsresult RecvAltSvc(Http2Session*);
+ static nsresult RecvUnused(Http2Session*);
+ static nsresult RecvOrigin(Http2Session*);
+
+ char* EnsureOutputBuffer(uint32_t needed);
+
+ template <typename charType>
+ void CreateFrameHeader(charType dest, uint16_t frameLength, uint8_t frameType,
+ uint8_t frameFlags, uint32_t streamID);
+
+ // For writing the data stream to LOG4
+ static void LogIO(Http2Session*, Http2StreamBase*, const char*, const char*,
+ uint32_t);
+
+ // overload of nsAHttpConnection
+ void TransactionHasDataToWrite(nsAHttpTransaction*) override;
+ void TransactionHasDataToRecv(nsAHttpTransaction*) override;
+
+ // a similar version for Http2StreamBase
+ void TransactionHasDataToWrite(Http2StreamBase*);
+ void TransactionHasDataToRecv(Http2StreamBase* caller);
+
+ // an overload of nsAHttpSegementReader
+ [[nodiscard]] virtual nsresult CommitToSegmentSize(
+ uint32_t count, bool forceCommitment) override;
+ [[nodiscard]] nsresult BufferOutput(const char*, uint32_t, uint32_t*);
+ void FlushOutputQueue();
+ uint32_t AmountOfOutputBuffered() {
+ return mOutputQueueUsed - mOutputQueueSent;
+ }
+
+ uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; }
+
+ [[nodiscard]] bool TryToActivate(Http2StreamBase* stream);
+ void ConnectPushedStream(Http2StreamBase* stream);
+ void ConnectSlowConsumer(Http2StreamBase* stream);
+
+ [[nodiscard]] nsresult ConfirmTLSProfile();
+ [[nodiscard]] static bool ALPNCallback(nsITLSSocketControl* tlsSocketControl);
+
+ uint64_t Serial() { return mSerial; }
+
+ void PrintDiagnostics(nsCString& log) override;
+
+ // Streams need access to these
+ uint32_t SendingChunkSize() { return mSendingChunkSize; }
+ uint32_t PushAllowance() { return mPushAllowance; }
+ Http2Compressor* Compressor() { return &mCompressor; }
+ nsISocketTransport* SocketTransport() { return mSocketTransport; }
+ int64_t ServerSessionWindow() { return mServerSessionWindow; }
+ void DecrementServerSessionWindow(uint32_t bytes) {
+ mServerSessionWindow -= bytes;
+ }
+ uint32_t InitialRwin() { return mInitialRwin; }
+
+ void SendPing() override;
+ bool UseH2Deps() { return mUseH2Deps; }
+ void SetCleanShutdown(bool) override;
+
+ // overload of nsAHttpTransaction
+ [[nodiscard]] nsresult ReadSegmentsAgain(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*, bool*) final;
+ [[nodiscard]] nsresult WriteSegmentsAgain(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*, bool*) final;
+ [[nodiscard]] bool Do0RTT() final { return true; }
+ [[nodiscard]] nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) final;
+
+ // For use by an HTTP2Stream
+ void Received421(nsHttpConnectionInfo* ci);
+
+ void SendPriorityFrame(uint32_t streamID, uint32_t dependsOn, uint8_t weight);
+ void IncrementTrrCounter() { mTrrStreams++; }
+
+ WebSocketSupport GetWebSocketSupport() override;
+
+ already_AddRefed<nsHttpConnection> CreateTunnelStream(
+ nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket = false) override;
+
+ void CleanupStream(Http2StreamBase*, nsresult, errorType);
+
+ private:
+ Http2Session(nsISocketTransport*, enum SpdyVersion version,
+ bool attemptingEarlyData);
+
+ static Http2StreamTunnel* CreateTunnelStreamFromConnInfo(
+ Http2Session* session, uint64_t bcId, nsHttpConnectionInfo* connInfo,
+ bool isWebSocket);
+
+ // These internal states do not correspond to the states of the HTTP/2
+ // specification
+ enum internalStateType {
+ BUFFERING_OPENING_SETTINGS,
+ BUFFERING_FRAME_HEADER,
+ BUFFERING_CONTROL_FRAME,
+ PROCESSING_DATA_FRAME_PADDING_CONTROL,
+ PROCESSING_DATA_FRAME,
+ DISCARDING_DATA_FRAME_PADDING,
+ DISCARDING_DATA_FRAME,
+ PROCESSING_COMPLETE_HEADERS,
+ PROCESSING_CONTROL_RST_STREAM,
+ NOT_USING_NETWORK
+ };
+
+ static const uint8_t kMagicHello[24];
+
+ void CreateStream(nsAHttpTransaction* aHttpTransaction, int32_t aPriority,
+ Http2StreamBaseType streamType);
+
+ [[nodiscard]] nsresult ResponseHeadersComplete();
+ uint32_t GetWriteQueueSize();
+ void ChangeDownstreamState(enum internalStateType);
+ void ResetDownstreamState();
+ [[nodiscard]] nsresult ReadyToProcessDataFrame(enum internalStateType);
+ [[nodiscard]] nsresult UncompressAndDiscard(bool);
+ void GeneratePing(bool);
+ void GenerateSettingsAck();
+ void GeneratePriority(uint32_t, uint8_t);
+ void GenerateRstStream(uint32_t, uint32_t);
+ void GenerateGoAway(uint32_t);
+ void CleanupStream(uint32_t, nsresult, errorType);
+ void CloseStream(Http2StreamBase* aStream, nsresult aResult,
+ bool aRemoveFromQueue = true);
+ void SendHello();
+ void RemoveStreamFromQueues(Http2StreamBase*);
+ [[nodiscard]] nsresult ParsePadding(uint8_t&, uint16_t&);
+
+ void SetWriteCallbacks();
+ void RealignOutputQueue();
+
+ void ProcessPending();
+ [[nodiscard]] nsresult ProcessConnectedPush(Http2StreamBase*,
+ nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] nsresult ProcessSlowConsumer(Http2StreamBase*,
+ nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+
+ [[nodiscard]] nsresult SetInputFrameDataStream(uint32_t);
+ void CreatePriorityNode(uint32_t, uint32_t, uint8_t, const char*);
+ char* CreatePriorityFrame(uint32_t, uint32_t, uint8_t);
+ bool VerifyStream(Http2StreamBase*, uint32_t);
+ void SetNeedsCleanup();
+
+ void UpdateLocalRwin(Http2StreamBase* stream, uint32_t bytes);
+ void UpdateLocalStreamWindow(Http2StreamBase* stream, uint32_t bytes);
+ void UpdateLocalSessionWindow(uint32_t bytes);
+
+ void MaybeDecrementConcurrent(Http2StreamBase* stream);
+ bool RoomForMoreConcurrent();
+ void IncrementConcurrent(Http2StreamBase* stream);
+ void QueueStream(Http2StreamBase* stream);
+
+ // a wrapper for all calls to the nshttpconnection level segment writer. Used
+ // to track network I/O for timeout purposes
+ [[nodiscard]] nsresult NetworkRead(nsAHttpSegmentWriter*, char*, uint32_t,
+ uint32_t*);
+
+ void Shutdown(nsresult aReason);
+ void ShutdownStream(Http2StreamBase* aStream, nsresult aResult);
+
+ nsresult SessionError(enum errorType);
+
+ // This is intended to be nsHttpConnectionMgr:nsConnectionHandle taken
+ // from the first transaction on this session. That object contains the
+ // pointer to the real network-level nsHttpConnection object.
+ RefPtr<nsAHttpConnection> mConnection;
+
+ // The underlying socket transport object is needed to propogate some events
+ nsISocketTransport* mSocketTransport;
+
+ // These are temporary state variables to hold the argument to
+ // Read/WriteSegments so it can be accessed by On(read/write)segment
+ // further up the stack.
+ RefPtr<nsAHttpSegmentReader> mSegmentReader;
+ nsAHttpSegmentWriter* mSegmentWriter;
+
+ uint32_t mSendingChunkSize; /* the transmission chunk size */
+ uint32_t mNextStreamID; /* 24 bits */
+ uint32_t mLastPushedID;
+ uint32_t mConcurrentHighWater; /* max parallelism on session */
+ uint32_t mPushAllowance; /* rwin for unmatched pushes */
+
+ internalStateType mDownstreamState; /* in frame, between frames, etc.. */
+
+ // Maintain 2 indexes - one by stream ID, one by transaction pointer.
+ // There are also several lists of streams: ready to write, queued due to
+ // max parallelism, streams that need to force a read for push, and the full
+ // set of pushed streams.
+ nsTHashMap<nsUint32HashKey, Http2StreamBase*> mStreamIDHash;
+ nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http2StreamBase>
+ mStreamTransactionHash;
+ nsTArray<RefPtr<Http2StreamTunnel>> mTunnelStreams;
+
+ nsTArray<WeakPtr<Http2StreamBase>> mReadyForWrite;
+ nsTArray<WeakPtr<Http2StreamBase>> mQueuedStreams;
+ nsTArray<WeakPtr<Http2StreamBase>> mPushesReadyForRead;
+ nsTArray<WeakPtr<Http2StreamBase>> mSlowConsumersReadyForRead;
+ nsTArray<Http2PushedStream*> mPushedStreams;
+
+ // Compression contexts for header transport.
+ // HTTP/2 compresses only HTTP headers and does not reset the context in
+ // between frames. Even data that is not associated with a stream (e.g invalid
+ // stream ID) is passed through these contexts to keep the compression
+ // context correct.
+ Http2Compressor mCompressor;
+ Http2Decompressor mDecompressor;
+ nsCString mDecompressBuffer;
+
+ // mInputFrameBuffer is used to store received control packets and the 8 bytes
+ // of header on data packets
+ uint32_t mInputFrameBufferSize; // buffer allocation
+ uint32_t mInputFrameBufferUsed; // amt of allocation used
+ UniquePtr<char[]> mInputFrameBuffer;
+
+ // mInputFrameDataSize/Read are used for tracking the amount of data consumed
+ // in a frame after the 8 byte header. Control frames are always fully
+ // buffered and the fixed 8 byte leading header is at mInputFrameBuffer + 0,
+ // the first data byte (i.e. the first settings/goaway/etc.. specific byte) is
+ // at mInputFrameBuffer + 8 The frame size is mInputFrameDataSize + the
+ // constant 8 byte header
+ uint32_t mInputFrameDataSize;
+ uint32_t mInputFrameDataRead;
+ bool mInputFrameFinal; // This frame was marked FIN
+ uint8_t mInputFrameType;
+ uint8_t mInputFrameFlags;
+ uint32_t mInputFrameID;
+ uint16_t mPaddingLength;
+
+ // When a frame has been received that is addressed to a particular stream
+ // (e.g. a data frame after the stream-id has been decoded), this points
+ // to the stream.
+ Http2StreamBase* mInputFrameDataStream;
+
+ // mNeedsCleanup is a state variable to defer cleanup of a closed stream
+ // If needed, It is set in session::OnWriteSegments() and acted on and
+ // cleared when the stack returns to session::WriteSegments(). The stream
+ // cannot be destroyed directly out of OnWriteSegments because
+ // stream::writeSegments() is on the stack at that time.
+ Http2StreamBase* mNeedsCleanup;
+
+ // This reason code in the last processed RESET frame
+ uint32_t mDownstreamRstReason;
+
+ // When HEADERS/PROMISE are chained together, this is the expected ID of the
+ // next recvd frame which must be the same type
+ uint32_t mExpectedHeaderID;
+ uint32_t mExpectedPushPromiseID;
+ uint32_t mContinuedPromiseStream;
+
+ // for the conversion of downstream http headers into http/2 formatted headers
+ // The data here does not persist between frames
+ nsCString mFlatHTTPResponseHeaders;
+ uint32_t mFlatHTTPResponseHeadersOut;
+
+ // when set, the session will go away when it reaches 0 streams. This flag
+ // is set when: the stream IDs are running out (at either the client or the
+ // server), when DontReuse() is called, a RST that is not specific to a
+ // particular stream is received, a GOAWAY frame has been received from
+ // the server.
+ bool mShouldGoAway;
+
+ // the session has received a nsAHttpTransaction::Close() call
+ bool mClosed;
+
+ // the session received a GoAway frame with a valid GoAwayID
+ bool mCleanShutdown;
+
+ // the session received the opening SETTINGS frame from the server
+ bool mReceivedSettings;
+
+ // The TLS comlpiance checks are not done in the ctor beacuse of bad
+ // exception handling - so we do them at IO time and cache the result
+ bool mTLSProfileConfirmed;
+
+ // A specifc reason code for the eventual GoAway frame. If set to
+ // NO_HTTP_ERROR only NO_HTTP_ERROR, PROTOCOL_ERROR, or INTERNAL_ERROR will be
+ // sent.
+ errorType mGoAwayReason;
+
+ // The error code sent/received on the session goaway frame. UNASSIGNED/31
+ // if not transmitted.
+ int32_t mClientGoAwayReason;
+ int32_t mPeerGoAwayReason;
+
+ // If a GoAway message was received this is the ID of the last valid
+ // stream. 0 otherwise. (0 is never a valid stream id.)
+ uint32_t mGoAwayID;
+
+ // The last stream processed ID we will send in our GoAway frame.
+ uint32_t mOutgoingGoAwayID;
+
+ // The limit on number of concurrent streams for this session. Normally it
+ // is basically unlimited, but the SETTINGS control message from the
+ // server might bring it down.
+ uint32_t mMaxConcurrent;
+
+ // The actual number of concurrent streams at this moment. Generally below
+ // mMaxConcurrent, but the max can be lowered in real time to a value
+ // below the current value
+ uint32_t mConcurrent;
+
+ // The number of server initiated promises, tracked for telemetry
+ uint32_t mServerPushedResources;
+
+ // The server rwin for new streams as determined from a SETTINGS frame
+ uint32_t mServerInitialStreamWindow;
+
+ // The Local Session window is how much data the server is allowed to send
+ // (across all streams) without getting a window update to stream 0. It is
+ // signed because asynchronous changes via SETTINGS can drive it negative.
+ int64_t mLocalSessionWindow;
+
+ // The Remote Session Window is how much data the client is allowed to send
+ // (across all streams) without receiving a window update to stream 0. It is
+ // signed because asynchronous changes via SETTINGS can drive it negative.
+ int64_t mServerSessionWindow;
+
+ // The initial value of the local stream and session window
+ uint32_t mInitialRwin;
+
+ // This is a output queue of bytes ready to be written to the SSL stream.
+ // When that streams returns WOULD_BLOCK on direct write the bytes get
+ // coalesced together here. This results in larger writes to the SSL layer.
+ // The buffer is not dynamically grown to accomodate stream writes, but
+ // does expand to accept infallible session wide frames like GoAway and RST.
+ uint32_t mOutputQueueSize;
+ uint32_t mOutputQueueUsed;
+ uint32_t mOutputQueueSent;
+ UniquePtr<char[]> mOutputQueueBuffer;
+
+ PRIntervalTime mPingThreshold;
+ PRIntervalTime mLastReadEpoch; // used for ping timeouts
+ PRIntervalTime mLastDataReadEpoch; // used for IdleTime()
+ PRIntervalTime mPingSentEpoch;
+
+ PRIntervalTime mPreviousPingThreshold; // backup for the former value
+ bool mPreviousUsed; // true when backup is used
+
+ // used as a temporary buffer while enumerating the stream hash during GoAway
+ nsDeque<Http2StreamBase> mGoAwayStreamsToRestart;
+
+ // Each session gets a unique serial number because the push cache is
+ // correlated by the load group and the serial number can be used as part of
+ // the cache key to make sure streams aren't shared across sessions.
+ uint64_t mSerial;
+
+ // Telemetry for continued headers (pushed and pulled) for quic design
+ uint32_t mAggregatedHeaderSize;
+
+ // If push is disabled, we want to be able to send PROTOCOL_ERRORs if we
+ // receive a PUSH_PROMISE, but we have to wait for the SETTINGS ACK before
+ // we can actually tell the other end to go away. These help us keep track
+ // of that state so we can behave appropriately.
+ bool mWaitingForSettingsAck;
+ bool mGoAwayOnPush;
+
+ bool mUseH2Deps;
+
+ bool mAttemptingEarlyData;
+ // The ID(s) of the stream(s) that we are getting 0RTT data from.
+ nsTArray<WeakPtr<Http2StreamBase>> m0RTTStreams;
+ // The ID(s) of the stream(s) that are not able to send 0RTT data. We need to
+ // remember them put them into mReadyForWrite queue when 0RTT finishes.
+ nsTArray<WeakPtr<Http2StreamBase>> mCannotDo0RTTStreams;
+
+ bool RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding);
+ bool TestOriginFrame(const nsACString& name, int32_t port);
+ bool mOriginFrameActivated;
+ nsTHashMap<nsCStringHashKey, bool> mOriginFrame;
+
+ nsTHashMap<nsCStringHashKey, bool> mJoinConnectionCache;
+
+ uint64_t mCurrentBrowserId;
+
+ uint32_t mCntActivated;
+
+ // A h2 session will be created before all socket events are trigered,
+ // e.g. NS_NET_STATUS_TLS_HANDSHAKE_ENDED.
+ // We should propagate this events to the first nsHttpTransaction.
+ RefPtr<nsHttpTransaction> mFirstHttpTransaction;
+ bool mTlsHandshakeFinished;
+
+ bool mPeerFailedHandshake;
+
+ private:
+ uint32_t mTrrStreams;
+
+ // websockets
+ bool mEnableWebsockets; // Whether we allow websockets, based on a pref
+ bool mPeerAllowsWebsockets; // Whether our peer allows websockets, based on
+ // SETTINGS
+ bool mProcessedWaitingWebsockets; // True once we've received at least one
+ // SETTINGS
+ // Setting this to true means there is a transaction waiting for the result of
+ // WebSocket support. We'll need to process the pending queue once we've
+ // received the settings.
+ bool mHasTransactionWaitingForWebsockets = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Http2Session, NS_HTTP2SESSION_IID);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Session_h
diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp
new file mode 100644
index 0000000000..cd758f694d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -0,0 +1,298 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "Http2Push.h"
+#include "Http2Stream.h"
+#include "nsHttp.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpRequestHead.h"
+#include "nsISocketTransport.h"
+#include "Http2Session.h"
+#include "PSpdyPush.h"
+#include "nsIRequestContext.h"
+#include "nsHttpTransaction.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla::net {
+
+Http2Stream::Http2Stream(nsAHttpTransaction* httpTransaction,
+ Http2Session* session, int32_t priority, uint64_t bcId)
+ : Http2StreamBase((httpTransaction->QueryHttpTransaction())
+ ? httpTransaction->QueryHttpTransaction()->BrowserId()
+ : 0,
+ session, priority, bcId),
+ mTransaction(httpTransaction) {
+ LOG1(("Http2Stream::Http2Stream %p trans=%p", this, httpTransaction));
+}
+
+Http2Stream::~Http2Stream() { ClearPushSource(); }
+
+void Http2Stream::CloseStream(nsresult reason) {
+ // In case we are connected to a push, make sure the push knows we are closed,
+ // so it doesn't try to give us any more DATA that comes on it after our
+ // close.
+ ClearPushSource();
+
+ mTransaction->Close(reason);
+ mSession = nullptr;
+}
+
+void Http2Stream::ClearPushSource() {
+ if (mPushSource) {
+ mPushSource->SetConsumerStream(nullptr);
+ mPushSource = nullptr;
+ }
+}
+
+nsresult Http2Stream::CheckPushCache() {
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ // check the push cache for GET
+ if (!head->IsGet()) {
+ return NS_OK;
+ }
+
+ RefPtr<Http2Session> session = Session();
+
+ nsAutoCString authorityHeader;
+ nsAutoCString hashkey;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+
+ mozilla::OriginAttributes originAttributes;
+ mSocketTransport->GetOriginAttributes(&originAttributes);
+
+ CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
+ authorityHeader, originAttributes, session->Serial(),
+ requestURI, mOrigin, hashkey);
+
+ // from :scheme, :authority, :path
+ nsIRequestContext* requestContext = mTransaction->RequestContext();
+ SpdyPushCache* cache = nullptr;
+ if (requestContext) {
+ cache = requestContext->GetSpdyPushCache();
+ }
+
+ RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
+ Http2PushedStream* pushedStream = nullptr;
+
+ // If a push stream is attached to the transaction via onPush, match only
+ // with that one. This occurs when a push was made with in conjunction with
+ // a nsIHttpPushListener
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans && (pushedStreamWrapper = trans->TakePushedStream()) &&
+ (pushedStream = pushedStreamWrapper->GetStream())) {
+ RefPtr<Http2Session> pushSession = pushedStream->Session();
+ if (pushSession == session) {
+ LOG3(
+ ("Pushed Stream match based on OnPush correlation %p", pushedStream));
+ } else {
+ LOG3(("Pushed Stream match failed due to stream mismatch %p %" PRId64
+ " %" PRId64 "\n",
+ pushedStream, pushSession->Serial(), session->Serial()));
+ pushedStream->OnPushFailed();
+ pushedStream = nullptr;
+ }
+ }
+
+ // we remove the pushedstream from the push cache so that
+ // it will not be used for another GET. This does not destroy the
+ // stream itself - that is done when the transactionhash is done with it.
+ if (cache && !pushedStream) {
+ pushedStream = cache->RemovePushedStreamHttp2(hashkey);
+ }
+
+ LOG3(
+ ("Pushed Stream Lookup "
+ "session=%p key=%s requestcontext=%p cache=%p hit=%p\n",
+ session.get(), hashkey.get(), requestContext, cache, pushedStream));
+
+ if (pushedStream) {
+ LOG3(("Pushed Stream Match located %p id=0x%X key=%s\n", pushedStream,
+ pushedStream->StreamID(), hashkey.get()));
+ pushedStream->SetConsumerStream(this);
+ mPushSource = pushedStream;
+ SetSentFin(true);
+ AdjustPushedPriority();
+
+ // There is probably pushed data buffered so trigger a read manually
+ // as we can't rely on future network events to do it
+ session->ConnectPushedStream(this);
+ mOpenGenerated = 1;
+
+ // if the "mother stream" had TRR, this one is a TRR stream too!
+ RefPtr<nsHttpConnectionInfo> ci(Transaction()->ConnectionInfo());
+ if (ci && ci->GetIsTrrServiceChannel()) {
+ session->IncrementTrrCounter();
+ }
+ }
+
+ return NS_OK;
+}
+
+uint32_t Http2Stream::GetWireStreamId() {
+ // >0 even numbered IDs are pushed streams.
+ // odd numbered IDs are pulled streams.
+ // 0 is the sink for a pushed stream.
+ if (!mStreamID) {
+ MOZ_ASSERT(mPushSource);
+ if (!mPushSource) {
+ return 0;
+ }
+
+ MOZ_ASSERT(mPushSource->StreamID());
+ MOZ_ASSERT(!(mPushSource->StreamID() & 1)); // is a push stream
+
+ // If the pushed stream has recvd a FIN, there is no reason to update
+ // the window
+ if (mPushSource->RecvdFin() || mPushSource->RecvdReset() ||
+ (mPushSource->HTTPState() == RESERVED_BY_REMOTE)) {
+ return 0;
+ }
+ return mPushSource->StreamID();
+ }
+
+ if (mState == RESERVED_BY_REMOTE) {
+ // h2-14 prevents sending a window update in this state
+ return 0;
+ }
+ return mStreamID;
+}
+
+void Http2Stream::AdjustPushedPriority() {
+ // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled
+ // streams. 0 is the sink for a pushed stream.
+
+ if (mStreamID || !mPushSource) return;
+
+ MOZ_ASSERT(mPushSource->StreamID() && !(mPushSource->StreamID() & 1));
+
+ // If the pushed stream has recvd a FIN, there is no reason to update
+ // the window
+ if (mPushSource->RecvdFin() || mPushSource->RecvdReset()) return;
+
+ // Ensure we pick up the right dependency to place the pushed stream under.
+ UpdatePriorityDependency();
+
+ EnsureBuffer(mTxInlineFrame,
+ mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5,
+ mTxInlineFrameUsed, mTxInlineFrameSize);
+ uint8_t* packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
+ mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5;
+
+ RefPtr<Http2Session> session = Session();
+ session->CreateFrameHeader(packet, 5, Http2Session::FRAME_TYPE_PRIORITY, 0,
+ mPushSource->StreamID());
+
+ mPushSource->SetPriorityDependency(mPriority, mPriorityDependency);
+ uint32_t wireDep = PR_htonl(mPriorityDependency);
+ memcpy(packet + Http2Session::kFrameHeaderBytes, &wireDep, 4);
+ memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
+
+ LOG3(("AdjustPushedPriority %p id 0x%X to dep %X weight %X\n", this,
+ mPushSource->StreamID(), mPriorityDependency, mPriorityWeight));
+}
+
+bool Http2Stream::IsReadingFromPushStream() { return !!mPushSource; }
+
+nsresult Http2Stream::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n", this, count,
+ mUpstreamState, mStreamID));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSegmentWriter);
+
+ if (mPushSource) {
+ nsresult rv;
+ rv = mPushSource->GetBufferedData(buf, count, countWritten);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<Http2Session> session = Session();
+ session->ConnectPushedStream(this);
+ return NS_OK;
+ }
+
+ return Http2StreamBase::OnWriteSegment(buf, count, countWritten);
+}
+
+nsresult Http2Stream::CallToReadData(uint32_t count, uint32_t* countRead) {
+ return mTransaction->ReadSegments(this, count, countRead);
+}
+
+nsresult Http2Stream::CallToWriteData(uint32_t count, uint32_t* countWritten) {
+ return mTransaction->WriteSegments(this, count, countWritten);
+}
+
+// This is really a headers frame, but open is pretty clear from a workflow pov
+nsresult Http2Stream::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+ RefPtr<Http2Session> session = Session();
+ LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n", this,
+ mStreamID, session.get(), requestURI.get()));
+
+ nsAutoCString authorityHeader;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+
+ nsAutoCString method;
+ nsAutoCString path;
+ head->Method(method);
+ head->Path(path);
+
+ rv = session->Compressor()->EncodeHeaderBlock(
+ mFlatHttpRequestHeaders, method, path, authorityHeader, scheme,
+ EmptyCString(), false, aCompressedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t clVal = session->Compressor()->GetParsedContentLength();
+ if (clVal != -1) {
+ mRequestBodyLenRemaining = clVal;
+ }
+
+ // Determine whether to put the fin bit on the header frame or whether
+ // to wait for a data packet to put it on.
+
+ if (head->IsGet() || head->IsHead()) {
+ // for GET and HEAD place the fin bit right on the
+ // header packet
+ firstFrameFlags |= Http2Session::kFlag_END_STREAM;
+ } else if (head->IsPost() || head->IsPut() || head->IsConnect()) {
+ // place fin in a data frame even for 0 length messages for iterop
+ } else if (!mRequestBodyLenRemaining) {
+ // for other HTTP extension methods, rely on the content-length
+ // to determine whether or not to put fin on headers
+ firstFrameFlags |= Http2Session::kFlag_END_STREAM;
+ }
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ aCompressedData.Length() * 100 /
+ (11 + requestURI.Length() + mFlatHttpRequestHeaders.Length());
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h
new file mode 100644
index 0000000000..aae1ca1b5d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -0,0 +1,59 @@
+/* -*- 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_Http2Stream_h
+#define mozilla_net_Http2Stream_h
+
+#include "Http2StreamBase.h"
+
+namespace mozilla::net {
+
+class Http2Stream : public Http2StreamBase {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http2Stream, override)
+
+ Http2Stream(nsAHttpTransaction* httpTransaction, Http2Session* session,
+ int32_t priority, uint64_t bcId);
+
+ void CloseStream(nsresult reason) override;
+ Http2Stream* GetHttp2Stream() override { return this; }
+ uint32_t GetWireStreamId() override;
+
+ nsresult OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) override;
+
+ nsresult CheckPushCache();
+ Http2PushedStream* PushSource() { return mPushSource; }
+ bool IsReadingFromPushStream();
+ void ClearPushSource();
+
+ nsAHttpTransaction* Transaction() override { return mTransaction; }
+ nsIRequestContext* RequestContext() override {
+ return mTransaction ? mTransaction->RequestContext() : nullptr;
+ }
+
+ protected:
+ ~Http2Stream();
+ nsresult CallToReadData(uint32_t count, uint32_t* countRead) override;
+ nsresult CallToWriteData(uint32_t count, uint32_t* countWritten) override;
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+
+ private:
+ // For Http2Push
+ void AdjustPushedPriority();
+ Http2PushedStream* mPushSource{nullptr};
+
+ // The underlying HTTP transaction. This pointer is used as the key
+ // in the Http2Session mStreamTransactionHash so it is important to
+ // keep a reference to it as long as this stream is a member of that hash.
+ // (i.e. don't change it or release it after it is set in the ctor).
+ RefPtr<nsAHttpTransaction> mTransaction;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http2Stream_h
diff --git a/netwerk/protocol/http/Http2StreamBase.cpp b/netwerk/protocol/http/Http2StreamBase.cpp
new file mode 100644
index 0000000000..bb135a59d9
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamBase.cpp
@@ -0,0 +1,1324 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "Http2Compression.h"
+#include "Http2Session.h"
+#include "Http2StreamBase.h"
+#include "Http2Stream.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsAlgorithm.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIClassOfService.h"
+#include "prnetdb.h"
+
+namespace mozilla::net {
+
+Http2StreamBase::Http2StreamBase(uint64_t aTransactionBrowserId,
+ Http2Session* session, int32_t priority,
+ uint64_t currentBrowserId)
+ : mSession(
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(session))),
+ mRequestHeadersDone(0),
+ mOpenGenerated(0),
+ mAllHeadersReceived(0),
+ mQueued(0),
+ mSocketTransport(session->SocketTransport()),
+ mCurrentBrowserId(currentBrowserId),
+ mTransactionBrowserId(aTransactionBrowserId),
+ mTxInlineFrameSize(Http2Session::kDefaultBufferSize),
+ mChunkSize(session->SendingChunkSize()),
+ mRequestBlockedOnRead(0),
+ mRecvdFin(0),
+ mReceivedData(0),
+ mRecvdReset(0),
+ mSentReset(0),
+ mCountAsActive(0),
+ mSentFin(0),
+ mSentWaitingFor(0),
+ mSetTCPSocketBuffer(0),
+ mBypassInputBuffer(0) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG1(("Http2StreamBase::Http2StreamBase %p", this));
+
+ mServerReceiveWindow = session->GetServerInitialStreamWindow();
+ mClientReceiveWindow = session->PushAllowance();
+
+ mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize);
+
+ static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority,
+ "Lowest Priority should be less than kNormalPriority");
+
+ // values of priority closer to 0 are higher priority for the priority
+ // argument. This value is used as a group, which maps to a
+ // weight that is related to the nsISupportsPriority that we are given.
+ int32_t httpPriority;
+ if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
+ httpPriority = kWorstPriority;
+ } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
+ httpPriority = kBestPriority;
+ } else {
+ httpPriority = kNormalPriority + priority;
+ }
+ MOZ_ASSERT(httpPriority >= 0);
+ SetPriority(static_cast<uint32_t>(httpPriority));
+}
+
+Http2StreamBase::~Http2StreamBase() {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread());
+
+ mStreamID = Http2Session::kDeadStreamID;
+
+ LOG3(("Http2StreamBase::~Http2StreamBase %p", this));
+}
+
+already_AddRefed<Http2Session> Http2StreamBase::Session() {
+ RefPtr<Http2Session> session = do_QueryReferent(mSession);
+ return session.forget();
+}
+
+// ReadSegments() is used to write data down the socket. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to HTTP/2 data. Sometimes control data like a window-update is
+// generated instead.
+
+nsresult Http2StreamBase::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ LOG3(("Http2StreamBase %p ReadSegments reader=%p count=%d state=%x", this,
+ reader, count, mUpstreamState));
+ RefPtr<Http2Session> session = Session();
+ // Reader is nullptr when this is a push stream.
+ MOZ_DIAGNOSTIC_ASSERT(!reader || (reader == session) ||
+ (IsTunnel() && NS_FAILED(Condition())));
+
+ if (NS_FAILED(Condition())) {
+ return Condition();
+ }
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ mRequestBlockedOnRead = 0;
+
+ if (mRecvdFin || mRecvdReset) {
+ // Don't transmit any request frames if the peer cannot respond
+ LOG3(
+ ("Http2StreamBase %p ReadSegments request stream aborted due to"
+ " response side closure\n",
+ this));
+ return NS_ERROR_ABORT;
+ }
+
+ // avoid runt chunks if possible by anticipating
+ // full data frames
+ if (count > (mChunkSize + 8)) {
+ uint32_t numchunks = count / (mChunkSize + 8);
+ count = numchunks * (mChunkSize + 8);
+ }
+
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS:
+ case GENERATING_BODY:
+ case SENDING_BODY:
+ // Call into the HTTP Transaction to generate the HTTP request
+ // stream. That stream will show up in OnReadSegment().
+ mSegmentReader = reader;
+ rv = CallToReadData(count, countRead);
+ mSegmentReader = nullptr;
+
+ LOG3(("Http2StreamBase::ReadSegments %p trans readsegments rv %" PRIx32
+ " read=%d\n",
+ this, static_cast<uint32_t>(rv), *countRead));
+
+ // Check to see if the transaction's request could be written out now.
+ // If not, mark the stream for callback when writing can proceed.
+ if (NS_SUCCEEDED(rv) && mUpstreamState == GENERATING_HEADERS &&
+ !mRequestHeadersDone) {
+ session->TransactionHasDataToWrite(this);
+ }
+
+ // mTxinlineFrameUsed represents any queued un-sent frame. It might
+ // be 0 if there is no such frame, which is not a gurantee that we
+ // don't have more request body to send - just that any data that was
+ // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
+ // a queued, but complete, http/2 frame length.
+
+ // Mark that we are blocked on read if the http transaction needs to
+ // provide more of the request message body and there is nothing queued
+ // for writing
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) {
+ LOG(("Http2StreamBase %p mRequestBlockedOnRead = 1", this));
+ mRequestBlockedOnRead = 1;
+ }
+
+ // A transaction that had already generated its headers before it was
+ // queued at the session level (due to concurrency concerns) may not call
+ // onReadSegment off the ReadSegments() stack above.
+
+ // When mTransaction->ReadSegments returns NS_BASE_STREAM_WOULD_BLOCK it
+ // means it may have already finished providing all the request data
+ // necessary to generate open, calling OnReadSegment will drive sending
+ // the request; this may happen after dequeue of the stream.
+
+ if (mUpstreamState == GENERATING_HEADERS &&
+ (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG3(("Http2StreamBase %p ReadSegments forcing OnReadSegment call\n",
+ this));
+ uint32_t wasted = 0;
+ mSegmentReader = reader;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ mSegmentReader = nullptr;
+
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ if (NS_SUCCEEDED(rv2)) {
+ mRequestBlockedOnRead = 0;
+ }
+ }
+
+ // If the sending flow control window is open (!mBlockedOnRwin) then
+ // continue sending the request
+ if (!mBlockedOnRwin && mOpenGenerated && !mTxInlineFrameUsed &&
+ NS_SUCCEEDED(rv) && (!*countRead) && CloseSendStreamWhenDone()) {
+ MOZ_ASSERT(!mQueued);
+ MOZ_ASSERT(mRequestHeadersDone);
+ LOG3(
+ ("Http2StreamBase::ReadSegments %p 0x%X: Sending request data "
+ "complete, "
+ "mUpstreamState=%x\n",
+ this, mStreamID, mUpstreamState));
+ if (mSentFin) {
+ ChangeState(UPSTREAM_COMPLETE);
+ } else {
+ GenerateDataFrameHeader(0, true);
+ ChangeState(SENDING_FIN_STREAM);
+ session->TransactionHasDataToWrite(this);
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+ break;
+
+ case SENDING_FIN_STREAM:
+ // We were trying to send the FIN-STREAM but were blocked from
+ // sending it out - try again.
+ if (!mSentFin) {
+ mSegmentReader = reader;
+ rv = TransmitFrame(nullptr, nullptr, false);
+ mSegmentReader = nullptr;
+ MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+ "Transmit Frame should be all or nothing");
+ if (NS_SUCCEEDED(rv)) ChangeState(UPSTREAM_COMPLETE);
+ } else {
+ rv = NS_OK;
+ mTxInlineFrameUsed = 0; // cancel fin data packet
+ ChangeState(UPSTREAM_COMPLETE);
+ }
+
+ *countRead = 0;
+
+ // don't change OK to WOULD BLOCK. we are really done sending if OK
+ break;
+
+ case UPSTREAM_COMPLETE:
+ *countRead = 0;
+ rv = NS_OK;
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Http2StreamBase::ReadSegments unknown state");
+ break;
+ }
+
+ return rv;
+}
+
+uint64_t Http2StreamBase::LocalUnAcked() {
+ // reduce unacked by the amount of undelivered data
+ // to help assert flow control
+ uint64_t undelivered = mSimpleBuffer.Available();
+
+ if (undelivered > mLocalUnacked) {
+ return 0;
+ }
+ return mLocalUnacked - undelivered;
+}
+
+nsresult Http2StreamBase::BufferInput(uint32_t count, uint32_t* countWritten) {
+ char buf[SimpleBufferPage::kSimpleBufferPageSize];
+ if (SimpleBufferPage::kSimpleBufferPageSize < count) {
+ count = SimpleBufferPage::kSimpleBufferPageSize;
+ }
+
+ mBypassInputBuffer = 1;
+ nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+ mBypassInputBuffer = 0;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSimpleBuffer.Write(buf, *countWritten);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return rv;
+}
+
+bool Http2StreamBase::DeferCleanup(nsresult status) {
+ // do not cleanup a stream that has data buffered for the transaction
+ return (NS_SUCCEEDED(status) && mSimpleBuffer.Available());
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just a call through to the associated nsHttpTransaction for this stream
+// for the remaining data bytes indicated by the current DATA frame.
+
+nsresult Http2StreamBase::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
+
+ LOG3(("Http2StreamBase::WriteSegments %p count=%d state=%x", this, count,
+ mUpstreamState));
+
+ mSegmentWriter = writer;
+ nsresult rv = CallToWriteData(count, countWritten);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // consuming transaction won't take data. but we need to read it into a
+ // buffer so that it won't block other streams. but we should not advance
+ // the flow control window so that we'll eventually push back on the sender.
+ rv = BufferInput(count, countWritten);
+ LOG3(("Http2StreamBase::WriteSegments %p Buffered %" PRIX32 " %d\n", this,
+ static_cast<uint32_t>(rv), *countWritten));
+ }
+
+ LOG3(("Http2StreamBase::WriteSegments %" PRIX32 "",
+ static_cast<uint32_t>(rv)));
+ mSegmentWriter = nullptr;
+ return rv;
+}
+
+nsresult Http2StreamBase::ParseHttpRequestHeaders(const char* buf,
+ uint32_t avail,
+ uint32_t* countUsed) {
+ // Returns NS_OK even if the headers are incomplete
+ // set mRequestHeadersDone flag if they are complete
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
+ MOZ_ASSERT(!mRequestHeadersDone);
+
+ LOG3(("Http2StreamBase::ParseHttpRequestHeaders %p avail=%d state=%x", this,
+ avail, mUpstreamState));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+
+ // We can use the simple double crlf because firefox is the
+ // only client we are parsing
+ int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+ if (endHeader == kNotFound) {
+ // We don't have all the headers yet
+ LOG3(
+ ("Http2StreamBase::ParseHttpRequestHeaders %p "
+ "Need more header bytes. Len = %zd",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return NS_OK;
+ }
+
+ // We have recvd all the headers, trim the local
+ // buffer of the final empty line, and set countUsed to reflect
+ // the whole header has been consumed.
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+ mRequestHeadersDone = 1;
+
+ Http2Stream* selfRegularStream = this->GetHttp2Stream();
+ if (selfRegularStream) {
+ return selfRegularStream->CheckPushCache();
+ }
+
+ return NS_OK;
+}
+
+// This is really a headers frame, but open is pretty clear from a workflow pov
+nsresult Http2StreamBase::GenerateOpen() {
+ // It is now OK to assign a streamID that we are assured will
+ // be monotonically increasing amongst new streams on this
+ // session
+ RefPtr<Http2Session> session = Session();
+ mStreamID = session->RegisterStreamID(this);
+ MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
+ MOZ_ASSERT(!mOpenGenerated);
+
+ mOpenGenerated = 1;
+
+ LOG3(("Http2StreamBase %p Stream ID 0x%X [session=%p]\n", this, mStreamID,
+ session.get()));
+
+ if (mStreamID >= 0x80000000) {
+ // streamID must fit in 31 bits. Evading This is theoretically possible
+ // because stream ID assignment is asynchronous to stream creation
+ // because of the protocol requirement that the new stream ID
+ // be monotonically increasing. In reality this is really not possible
+ // because new streams stop being added to a session with millions of
+ // IDs still available and no race condition is going to bridge that gap;
+ // so we can be comfortable on just erroring out for correctness in that
+ // case.
+ LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Now we need to convert the flat http headers into a set
+ // of HTTP/2 headers by writing to mTxInlineFrame{sz}
+
+ nsCString compressedData;
+ uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;
+
+ nsresult rv = GenerateHeaders(compressedData, firstFrameFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (firstFrameFlags & Http2Session::kFlag_END_STREAM) {
+ SetSentFin(true);
+ }
+
+ // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it
+ // exceeds the 2^14-1 limit for 1 frame. Do it by inserting header size gaps
+ // in the existing frame for the new headers and for the first one a priority
+ // field. There is no question this is ugly, but a 16KB HEADERS frame should
+ // be a long tail event, so this is really just for correctness and a nop in
+ // the base case.
+ //
+
+ MOZ_ASSERT(!mTxInlineFrameUsed);
+
+ uint32_t dataLength = compressedData.Length();
+ uint32_t maxFrameData =
+ Http2Session::kMaxFrameData - 5; // 5 bytes for priority
+ uint32_t numFrames = 1;
+
+ if (dataLength > maxFrameData) {
+ numFrames +=
+ ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
+ Http2Session::kMaxFrameData;
+ MOZ_ASSERT(numFrames > 1);
+ }
+
+ // note that we could still have 1 frame for 0 bytes of data. that's ok.
+
+ uint32_t messageSize = dataLength;
+ messageSize += Http2Session::kFrameHeaderBytes +
+ 5; // frame header + priority overhead in HEADERS frame
+ messageSize += (numFrames - 1) *
+ Http2Session::kFrameHeaderBytes; // frame header overhead in
+ // CONTINUATION frames
+
+ EnsureBuffer(mTxInlineFrame, messageSize, mTxInlineFrameUsed,
+ mTxInlineFrameSize);
+
+ mTxInlineFrameUsed += messageSize;
+ UpdatePriorityDependency();
+ LOG1(
+ ("Http2StreamBase %p Generating %d bytes of HEADERS for stream 0x%X with "
+ "priority weight %u dep 0x%X frames %u\n",
+ this, mTxInlineFrameUsed, mStreamID, mPriorityWeight,
+ mPriorityDependency, numFrames));
+
+ uint32_t outputOffset = 0;
+ uint32_t compressedDataOffset = 0;
+ for (uint32_t idx = 0; idx < numFrames; ++idx) {
+ uint32_t flags, frameLen;
+ bool lastFrame = (idx == numFrames - 1);
+
+ flags = 0;
+ frameLen = maxFrameData;
+ if (!idx) {
+ flags |= firstFrameFlags;
+ // Only the first frame needs the 4-byte offset
+ maxFrameData = Http2Session::kMaxFrameData;
+ }
+ if (lastFrame) {
+ frameLen = dataLength;
+ flags |= Http2Session::kFlag_END_HEADERS;
+ }
+ dataLength -= frameLen;
+
+ session->CreateFrameHeader(mTxInlineFrame.get() + outputOffset,
+ frameLen + (idx ? 0 : 5),
+ (idx) ? Http2Session::FRAME_TYPE_CONTINUATION
+ : Http2Session::FRAME_TYPE_HEADERS,
+ flags, mStreamID);
+ outputOffset += Http2Session::kFrameHeaderBytes;
+
+ if (!idx) {
+ uint32_t wireDep = PR_htonl(mPriorityDependency);
+ memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4);
+ memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1);
+ outputOffset += 5;
+ }
+
+ memcpy(mTxInlineFrame.get() + outputOffset,
+ compressedData.BeginReading() + compressedDataOffset, frameLen);
+ compressedDataOffset += frameLen;
+ outputOffset += frameLen;
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
+
+ mFlatHttpRequestHeaders.Truncate();
+
+ return NS_OK;
+}
+
+void Http2StreamBase::AdjustInitialWindow() {
+ // The default initial_window is sized for pushed streams. When we
+ // generate a client pulled stream we want to disable flow control for
+ // the stream with a window update. Do the same for pushed streams
+ // when they connect to a pull.
+
+ uint32_t wireStreamId = GetWireStreamId();
+ if (wireStreamId == 0) {
+ return;
+ }
+
+ // right now mClientReceiveWindow is the lower push limit
+ // bump it up to the pull limit set by the channel or session
+ // don't allow windows less than push
+ uint32_t bump = 0;
+ RefPtr<Http2Session> session = Session();
+ nsHttpTransaction* trans = HttpTransaction();
+ if (trans && trans->InitialRwin()) {
+ bump = (trans->InitialRwin() > mClientReceiveWindow)
+ ? (trans->InitialRwin() - mClientReceiveWindow)
+ : 0;
+ } else {
+ MOZ_ASSERT(session->InitialRwin() >= mClientReceiveWindow);
+ bump = session->InitialRwin() - mClientReceiveWindow;
+ }
+
+ LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n", this,
+ wireStreamId, bump));
+ if (!bump) { // nothing to do
+ return;
+ }
+
+ EnsureBuffer(mTxInlineFrame,
+ mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
+ mTxInlineFrameUsed, mTxInlineFrameSize);
+ uint8_t* packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
+ mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4;
+
+ session->CreateFrameHeader(packet, 4, Http2Session::FRAME_TYPE_WINDOW_UPDATE,
+ 0, wireStreamId);
+
+ mClientReceiveWindow += bump;
+ bump = PR_htonl(bump);
+ memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
+}
+
+void Http2StreamBase::UpdateTransportReadEvents(uint32_t count) {
+ mTotalRead += count;
+ if (!mSocketTransport) {
+ return;
+ }
+
+ if (Transaction()) {
+ Transaction()->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_RECEIVING_FROM, mTotalRead);
+ }
+}
+
+void Http2StreamBase::UpdateTransportSendEvents(uint32_t count) {
+ mTotalSent += count;
+
+ // Setting the TCP send buffer, introduced in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=790184, which the following
+ // comment refers to, is being removed once we verify no increases in error
+ // rate.
+ //
+ // normally on non-windows platform we use TCP autotuning for
+ // the socket buffers, and this works well (managing enough
+ // buffers for BDP while conserving memory) for HTTP even when
+ // it creates really deep queues. However this 'buffer bloat' is
+ // a problem for http/2 because it ruins the low latency properties
+ // necessary for PING and cancel to work meaningfully.
+
+ // If this stream represents a large upload, disable autotuning for
+ // the session and cap the send buffers by default at 128KB.
+ // (10Mbit/sec @ 100ms)
+ //
+ uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
+ if (StaticPrefs::network_http_http2_send_buffer_size() > 0 &&
+ (mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
+ mSetTCPSocketBuffer = 1;
+ mSocketTransport->SetSendBufferSize(bufferSize);
+ }
+
+ if ((mUpstreamState != SENDING_FIN_STREAM) && Transaction()) {
+ Transaction()->OnTransportStatus(mSocketTransport, NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+ }
+
+ if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
+ mSentWaitingFor = 1;
+ if (Transaction()) {
+ Transaction()->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR, 0);
+ }
+ }
+}
+
+nsresult Http2StreamBase::TransmitFrame(const char* buf, uint32_t* countUsed,
+ bool forceCommitment) {
+ // If TransmitFrame returns SUCCESS than all the data is sent (or at least
+ // buffered at the session level), if it returns WOULD_BLOCK then none of
+ // the data is sent.
+
+ // You can call this function with no data and no out parameter in order to
+ // flush internal buffers that were previously blocked on writing. You can
+ // of course feed new data to it as well.
+
+ LOG3(("Http2StreamBase::TransmitFrame %p inline=%d stream=%d", this,
+ mTxInlineFrameUsed, mTxStreamFrameSize));
+ if (countUsed) *countUsed = 0;
+
+ if (!mTxInlineFrameUsed) {
+ MOZ_ASSERT(!buf);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
+ MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
+ MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
+ "TransmitFrame arguments inconsistent");
+
+ uint32_t transmittedCount;
+ nsresult rv;
+ RefPtr<Http2Session> session = Session();
+
+ // In the (relatively common) event that we have a small amount of data
+ // split between the inlineframe and the streamframe, then move the stream
+ // data into the inlineframe via copy in order to coalesce into one write.
+ // Given the interaction with ssl this is worth the small copy cost.
+ if (mTxStreamFrameSize && mTxInlineFrameUsed &&
+ mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
+ mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
+ LOG3(("Coalesce Transmit"));
+ memcpy(&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize);
+ if (countUsed) *countUsed += mTxStreamFrameSize;
+ mTxInlineFrameUsed += mTxStreamFrameSize;
+ mTxStreamFrameSize = 0;
+ }
+
+ rv = mSegmentReader->CommitToSegmentSize(
+ mTxStreamFrameSize + mTxInlineFrameUsed, forceCommitment);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
+ session->TransactionHasDataToWrite(this);
+ }
+ if (NS_FAILED(rv)) { // this will include WOULD_BLOCK
+ return rv;
+ }
+
+ // This function calls mSegmentReader->OnReadSegment to report the actual
+ // http/2 bytes through to the session object and then the HttpConnection
+ // which calls the socket write function. It will accept all of the inline and
+ // stream data because of the above 'commitment' even if it has to buffer
+
+ rv = session->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
+ mTxInlineFrameUsed, &transmittedCount);
+ LOG3(
+ ("Http2StreamBase::TransmitFrame for inline BufferOutput session=%p "
+ "stream=%p result %" PRIx32 " len=%d",
+ session.get(), this, static_cast<uint32_t>(rv), transmittedCount));
+
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "inconsistent inline commitment result");
+
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
+ "inconsistent inline commitment count");
+
+ Http2Session::LogIO(session, this, "Writing from Inline Buffer",
+ reinterpret_cast<char*>(mTxInlineFrame.get()),
+ transmittedCount);
+
+ if (mTxStreamFrameSize) {
+ if (!buf) {
+ // this cannot happen
+ MOZ_ASSERT(false,
+ "Stream transmit with null buf argument to "
+ "TransmitFrame()");
+ LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If there is already data buffered, just add to that to form
+ // a single TLS Application Data Record - otherwise skip the memcpy
+ if (session->AmountOfOutputBuffered()) {
+ rv = session->BufferOutput(buf, mTxStreamFrameSize, &transmittedCount);
+ } else {
+ rv = session->OnReadSegment(buf, mTxStreamFrameSize, &transmittedCount);
+ }
+
+ LOG3(
+ ("Http2StreamBase::TransmitFrame for regular session=%p "
+ "stream=%p result %" PRIx32 " len=%d",
+ session.get(), this, static_cast<uint32_t>(rv), transmittedCount));
+
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "inconsistent stream commitment result");
+
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
+ "inconsistent stream commitment count");
+
+ Http2Session::LogIO(session, this, "Writing from Transaction Buffer", buf,
+ transmittedCount);
+
+ *countUsed += mTxStreamFrameSize;
+ }
+
+ if (!mAttempting0RTT) {
+ session->FlushOutputQueue();
+ }
+
+ // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
+ UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
+
+ mTxInlineFrameUsed = 0;
+ mTxStreamFrameSize = 0;
+
+ return NS_OK;
+}
+
+void Http2StreamBase::ChangeState(enum upstreamStateType newState) {
+ LOG3(("Http2StreamBase::ChangeState() %p from %X to %X", this, mUpstreamState,
+ newState));
+ mUpstreamState = newState;
+}
+
+void Http2StreamBase::GenerateDataFrameHeader(uint32_t dataLength,
+ bool lastFrame) {
+ LOG3(("Http2StreamBase::GenerateDataFrameHeader %p len=%d last=%d", this,
+ dataLength, lastFrame));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
+ MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
+
+ uint8_t frameFlags = 0;
+ if (lastFrame) {
+ frameFlags |= Http2Session::kFlag_END_STREAM;
+ if (dataLength) SetSentFin(true);
+ }
+
+ RefPtr<Http2Session> session = Session();
+ session->CreateFrameHeader(mTxInlineFrame.get(), dataLength,
+ Http2Session::FRAME_TYPE_DATA, frameFlags,
+ mStreamID);
+
+ mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes;
+ mTxStreamFrameSize = dataLength;
+}
+
+// ConvertResponseHeaders is used to convert the response headers
+// into HTTP/1 format and report some telemetry
+nsresult Http2StreamBase::ConvertResponseHeaders(
+ Http2Decompressor* decompressor, nsACString& aHeadersIn,
+ nsACString& aHeadersOut, int32_t& httpResponseCode) {
+ nsresult rv = decompressor->DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(aHeadersIn.BeginReading()),
+ aHeadersIn.Length(), aHeadersOut, false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2StreamBase::ConvertResponseHeaders %p decode Error\n", this));
+ return rv;
+ }
+
+ nsAutoCString statusString;
+ decompressor->GetStatus(statusString);
+ if (statusString.IsEmpty()) {
+ LOG3(("Http2StreamBase::ConvertResponseHeaders %p Error - no status\n",
+ this));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult errcode;
+ httpResponseCode = statusString.ToInteger(&errcode);
+
+ // Ensure the :status is just an HTTP status code
+ // https://tools.ietf.org/html/rfc7540#section-8.1.2.4
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1352146
+ nsAutoCString parsedStatusString;
+ parsedStatusString.AppendInt(httpResponseCode);
+ if (!parsedStatusString.Equals(statusString)) {
+ LOG3(
+ ("Http2StreamBase::ConvertResposeHeaders %p status %s is not just a "
+ "code",
+ this, statusString.BeginReading()));
+ // Results in stream reset with PROTOCOL_ERROR
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ LOG3(("Http2StreamBase::ConvertResponseHeaders %p response code %d\n", this,
+ httpResponseCode));
+
+ if (httpResponseCode == 421) {
+ // Origin Frame requires 421 to remove this origin from the origin set
+ RefPtr<Http2Session> session = Session();
+ session->Received421(ConnectionInfo());
+ }
+
+ if (aHeadersIn.Length() && aHeadersOut.Length()) {
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
+ uint32_t ratio = aHeadersIn.Length() * 100 / aHeadersOut.Length();
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
+ }
+
+ // The decoding went ok. Now we can customize and clean up.
+
+ aHeadersIn.Truncate();
+ aHeadersOut.AppendLiteral("X-Firefox-Spdy: h2");
+ aHeadersOut.AppendLiteral("\r\n\r\n");
+ LOG(("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
+ HandleResponseHeaders(aHeadersOut, httpResponseCode);
+
+ return NS_OK;
+}
+
+nsresult Http2StreamBase::ConvertResponseTrailers(
+ Http2Decompressor* decompressor, nsACString& aTrailersIn) {
+ LOG3(("Http2StreamBase::ConvertResponseTrailers %p", this));
+ nsAutoCString flatTrailers;
+
+ nsresult rv = decompressor->DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(aTrailersIn.BeginReading()),
+ aTrailersIn.Length(), flatTrailers, false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2StreamBase::ConvertResponseTrailers %p decode Error", this));
+ return rv;
+ }
+
+ nsHttpTransaction* trans = HttpTransaction();
+ if (trans) {
+ trans->SetHttpTrailers(flatTrailers);
+ } else {
+ LOG3(("Http2StreamBase::ConvertResponseTrailers %p no trans", this));
+ }
+
+ return NS_OK;
+}
+
+void Http2StreamBase::SetResponseIsComplete() {
+ nsHttpTransaction* trans = HttpTransaction();
+ if (trans) {
+ trans->SetResponseIsComplete();
+ }
+}
+
+void Http2StreamBase::SetAllHeadersReceived() {
+ if (mAllHeadersReceived) {
+ return;
+ }
+
+ if (mState == RESERVED_BY_REMOTE) {
+ // pushed streams needs to wait until headers have
+ // arrived to open up their window
+ LOG3(
+ ("Http2StreamBase::SetAllHeadersReceived %p state OPEN from reserved\n",
+ this));
+ mState = OPEN;
+ AdjustInitialWindow();
+ }
+
+ mAllHeadersReceived = 1;
+}
+
+bool Http2StreamBase::AllowFlowControlledWrite() {
+ RefPtr<Http2Session> session = Session();
+ return (session->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
+}
+
+void Http2StreamBase::UpdateServerReceiveWindow(int32_t delta) {
+ mServerReceiveWindow += delta;
+
+ if (mBlockedOnRwin && AllowFlowControlledWrite()) {
+ LOG3(
+ ("Http2StreamBase::UpdateServerReceived UnPause %p 0x%X "
+ "Open stream window\n",
+ this, mStreamID));
+ RefPtr<Http2Session> session = Session();
+ session->TransactionHasDataToWrite(this);
+ }
+}
+
+void Http2StreamBase::SetPriority(uint32_t newPriority) {
+ int32_t httpPriority = static_cast<int32_t>(newPriority);
+ if (httpPriority > kWorstPriority) {
+ httpPriority = kWorstPriority;
+ } else if (httpPriority < kBestPriority) {
+ httpPriority = kBestPriority;
+ }
+ mPriority = static_cast<uint32_t>(httpPriority);
+ mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
+ (httpPriority - kNormalPriority);
+
+ mPriorityDependency = 0; // maybe adjusted later
+}
+
+void Http2StreamBase::SetPriorityDependency(uint32_t newPriority,
+ uint32_t newDependency) {
+ SetPriority(newPriority);
+ mPriorityDependency = newDependency;
+}
+
+static uint32_t GetPriorityDependencyFromTransaction(nsHttpTransaction* trans) {
+ MOZ_ASSERT(trans);
+
+ uint32_t classFlags = trans->GetClassOfService().Flags();
+
+ if (classFlags & nsIClassOfService::UrgentStart) {
+ return Http2Session::kUrgentStartGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Leader) {
+ return Http2Session::kLeaderGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Follower) {
+ return Http2Session::kFollowerGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Speculative) {
+ return Http2Session::kSpeculativeGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Background) {
+ return Http2Session::kBackgroundGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Unblocked) {
+ return Http2Session::kOtherGroupID;
+ }
+
+ return Http2Session::kFollowerGroupID; // unmarked followers
+}
+
+void Http2StreamBase::UpdatePriorityDependency() {
+ RefPtr<Http2Session> session = Session();
+ if (!session->UseH2Deps()) {
+ return;
+ }
+
+ nsHttpTransaction* trans = HttpTransaction();
+ if (!trans) {
+ return;
+ }
+
+ // we create 6 fake dependency streams per session,
+ // these streams are never opened with HEADERS. our first opened stream is 0xd
+ // 3 depends 0, weight 200, leader class (kLeaderGroupID)
+ // 5 depends 0, weight 100, other (kOtherGroupID)
+ // 7 depends 0, weight 0, background (kBackgroundGroupID)
+ // 9 depends 7, weight 0, speculative (kSpeculativeGroupID)
+ // b depends 3, weight 0, follower class (kFollowerGroupID)
+ // d depends 0, weight 240, urgent-start class (kUrgentStartGroupID)
+ //
+ // streams for leaders (html, js, css) depend on 3
+ // streams for folowers (images) depend on b
+ // default streams (xhr, async js) depend on 5
+ // explicit bg streams (beacon, etc..) depend on 7
+ // spculative bg streams depend on 9
+ // urgent-start streams depend on d
+
+ mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
+
+ if (gHttpHandler->ActiveTabPriority() &&
+ mTransactionBrowserId != mCurrentBrowserId &&
+ mPriorityDependency != Http2Session::kUrgentStartGroupID) {
+ LOG3(
+ ("Http2StreamBase::UpdatePriorityDependency %p "
+ " depends on background group for trans %p\n",
+ this, trans));
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+
+ nsHttp::NotifyActiveTabLoadOptimization();
+ }
+
+ LOG1(
+ ("Http2StreamBase::UpdatePriorityDependency %p "
+ "depends on stream 0x%X\n",
+ this, mPriorityDependency));
+}
+
+void Http2StreamBase::CurrentBrowserIdChanged(uint64_t id) {
+ if (!mStreamID) {
+ // For pushed streams, we ignore the direct call from the session and
+ // instead let it come to the internal function from the pushed stream, so
+ // we don't accidentally send two PRIORITY frames for the same stream.
+ return;
+ }
+
+ CurrentBrowserIdChangedInternal(id);
+}
+
+void Http2StreamBase::CurrentBrowserIdChangedInternal(uint64_t id) {
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+ RefPtr<Http2Session> session = Session();
+ LOG3(
+ ("Http2StreamBase::CurrentBrowserIdChangedInternal "
+ "%p browserId=%" PRIx64 "\n",
+ this, id));
+
+ mCurrentBrowserId = id;
+
+ if (!session->UseH2Deps()) {
+ return;
+ }
+
+ // Urgent start takes an absolute precedence, so don't
+ // change mPriorityDependency here.
+ if (mPriorityDependency == Http2Session::kUrgentStartGroupID) {
+ return;
+ }
+
+ if (mTransactionBrowserId != mCurrentBrowserId) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ LOG3(
+ ("Http2StreamBase::CurrentBrowserIdChangedInternal %p "
+ "move into background group.\n",
+ this));
+
+ nsHttp::NotifyActiveTabLoadOptimization();
+ } else {
+ nsHttpTransaction* trans = HttpTransaction();
+ if (!trans) {
+ return;
+ }
+
+ mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
+ LOG3(
+ ("Http2StreamBase::CurrentBrowserIdChangedInternal %p "
+ "depends on stream 0x%X\n",
+ this, mPriorityDependency));
+ }
+
+ uint32_t modifyStreamID = GetWireStreamId();
+
+ if (modifyStreamID) {
+ session->SendPriorityFrame(modifyStreamID, mPriorityDependency,
+ mPriorityWeight);
+ }
+}
+
+void Http2StreamBase::SetRecvdFin(bool aStatus) {
+ mRecvdFin = aStatus ? 1 : 0;
+ if (!aStatus) return;
+
+ if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
+ mState = CLOSED_BY_REMOTE;
+ } else if (mState == CLOSED_BY_LOCAL) {
+ mState = CLOSED;
+ }
+}
+
+void Http2StreamBase::SetSentFin(bool aStatus) {
+ mSentFin = aStatus ? 1 : 0;
+ if (!aStatus) return;
+
+ if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
+ mState = CLOSED_BY_LOCAL;
+ } else if (mState == CLOSED_BY_REMOTE) {
+ mState = CLOSED;
+ }
+}
+
+void Http2StreamBase::SetRecvdReset(bool aStatus) {
+ mRecvdReset = aStatus ? 1 : 0;
+ if (!aStatus) return;
+ mState = CLOSED;
+}
+
+void Http2StreamBase::SetSentReset(bool aStatus) {
+ mSentReset = aStatus ? 1 : 0;
+ if (!aStatus) return;
+ mState = CLOSED;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult Http2StreamBase::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG3(("Http2StreamBase::OnReadSegment %p count=%d state=%x", this, count,
+ mUpstreamState));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mSegmentReader) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ uint32_t dataLength;
+ RefPtr<Http2Session> session = Session();
+
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS:
+ // The buffer is the HTTP request stream, including at least part of the
+ // HTTP request header. This state's job is to build a HEADERS frame
+ // from the header information. count is the number of http bytes
+ // available (which may include more than the header), and in countRead we
+ // return the number of those bytes that we consume (i.e. the portion that
+ // are header bytes)
+
+ if (!mRequestHeadersDone) {
+ if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) {
+ return rv;
+ }
+ }
+
+ if (mRequestHeadersDone && !mOpenGenerated) {
+ if (!session->TryToActivate(this)) {
+ LOG3(
+ ("Http2StreamBase::OnReadSegment %p cannot activate now. "
+ "queued.\n",
+ this));
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ if (NS_FAILED(rv = GenerateOpen())) {
+ return rv;
+ }
+ }
+
+ LOG3(
+ ("ParseHttpRequestHeaders %p used %d of %d. "
+ "requestheadersdone = %d mOpenGenerated = %d\n",
+ this, *countRead, count, mRequestHeadersDone, mOpenGenerated));
+ if (mOpenGenerated) {
+ SetHTTPState(OPEN);
+ AdjustInitialWindow();
+ // This version of TransmitFrame cannot block
+ rv = TransmitFrame(nullptr, nullptr, true);
+ ChangeState(GENERATING_BODY);
+ break;
+ }
+ MOZ_ASSERT(*countRead == count,
+ "Header parsing not complete but unused data");
+ break;
+
+ case GENERATING_BODY:
+ // if there is session flow control and either the stream window is active
+ // and exhaused or the session window is exhausted then suspend
+ if (!AllowFlowControlledWrite()) {
+ *countRead = 0;
+ LOG3(
+ ("Http2StreamBase this=%p, id 0x%X request body suspended because "
+ "remote window is stream=%" PRId64 " session=%" PRId64 ".\n",
+ this, mStreamID, mServerReceiveWindow,
+ session->ServerSessionWindow()));
+ mBlockedOnRwin = true;
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ mBlockedOnRwin = false;
+
+ // The chunk is the smallest of: availableData, configured chunkSize,
+ // stream window, session window, or 14 bit framing limit.
+ // Its amazing we send anything at all.
+ dataLength = std::min(count, mChunkSize);
+
+ if (dataLength > Http2Session::kMaxFrameData) {
+ dataLength = Http2Session::kMaxFrameData;
+ }
+
+ if (dataLength > session->ServerSessionWindow()) {
+ dataLength = static_cast<uint32_t>(session->ServerSessionWindow());
+ }
+
+ if (dataLength > mServerReceiveWindow) {
+ dataLength = static_cast<uint32_t>(mServerReceiveWindow);
+ }
+
+ LOG3(
+ ("Http2StreamBase this=%p id 0x%X send calculation "
+ "avail=%d chunksize=%d stream window=%" PRId64
+ " session window=%" PRId64 " "
+ "max frame=%d USING=%u\n",
+ this, mStreamID, count, mChunkSize, mServerReceiveWindow,
+ session->ServerSessionWindow(), Http2Session::kMaxFrameData,
+ dataLength));
+
+ session->DecrementServerSessionWindow(dataLength);
+ mServerReceiveWindow -= dataLength;
+
+ LOG3(("Http2StreamBase %p id 0x%x request len remaining %" PRId64 ", "
+ "count avail %u, chunk used %u",
+ this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
+ if (!dataLength && mRequestBodyLenRemaining) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ if (dataLength > mRequestBodyLenRemaining) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRequestBodyLenRemaining -= dataLength;
+ GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
+ ChangeState(SENDING_BODY);
+ [[fallthrough]];
+
+ case SENDING_BODY:
+ MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
+ rv = TransmitFrame(buf, countRead, false);
+ MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+ "Transmit Frame should be all or nothing");
+
+ LOG3(("TransmitFrame() rv=%" PRIx32 " returning %d data bytes. "
+ "Header is %d Body is %d.",
+ static_cast<uint32_t>(rv), *countRead, mTxInlineFrameUsed,
+ mTxStreamFrameSize));
+
+ // normalize a partial write with a WOULD_BLOCK into just a partial write
+ // as some code will take WOULD_BLOCK to mean an error with nothing
+ // written (e.g. nsHttpTransaction::ReadRequestSegment()
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) rv = NS_OK;
+
+ // If that frame was all sent, look for another one
+ if (!mTxInlineFrameUsed) ChangeState(GENERATING_BODY);
+ break;
+
+ case SENDING_FIN_STREAM:
+ MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
+ break;
+
+ case UPSTREAM_COMPLETE: {
+ MOZ_ASSERT(this->GetHttp2Stream() &&
+ this->GetHttp2Stream()->IsReadingFromPushStream());
+ rv = TransmitFrame(nullptr, nullptr, true);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Http2StreamBase::OnReadSegment non-write state");
+ break;
+ }
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult Http2StreamBase::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG3(("Http2StreamBase::OnWriteSegment %p count=%d state=%x 0x%X\n", this,
+ count, mUpstreamState, mStreamID));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mSegmentWriter) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // sometimes we have read data from the network and stored it in a pipe
+ // so that other streams can proceed when the gecko caller is not processing
+ // data events fast enough and flow control hasn't caught up yet. This
+ // gets the stored data out of that pipe
+ if (!mBypassInputBuffer && mSimpleBuffer.Available()) {
+ *countWritten = mSimpleBuffer.Read(buf, count);
+ MOZ_ASSERT(*countWritten);
+ LOG3(
+ ("Http2StreamBase::OnWriteSegment read from flow control buffer %p %x "
+ "%d\n",
+ this, mStreamID, *countWritten));
+ return NS_OK;
+ }
+
+ // read from the network
+ return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+}
+
+// -----------------------------------------------------------------------------
+// mirror nsAHttpTransaction
+// -----------------------------------------------------------------------------
+
+bool Http2StreamBase::Do0RTT() {
+ MOZ_ASSERT(Transaction());
+ mAttempting0RTT = false;
+ nsAHttpTransaction* trans = Transaction();
+ if (trans) {
+ mAttempting0RTT = trans->Do0RTT();
+ }
+ return mAttempting0RTT;
+}
+
+nsresult Http2StreamBase::Finish0RTT(bool aRestart, bool aAlpnChanged) {
+ MOZ_ASSERT(Transaction());
+ mAttempting0RTT = false;
+ // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for
+ // both arguments because as long as the alpn token stayed the same, we can
+ // just reuse what we have in our buffer to send instead of having to have
+ // the transaction rewind and read it all over again. We only need to rewind
+ // the transaction if we're switching to a new protocol, because our buffer
+ // won't get used in that case.
+ // ..
+ // however, we send in the aRestart value to indicate that early data failed
+ // for devtools purposes
+ nsresult rv = NS_OK;
+ nsAHttpTransaction* trans = Transaction();
+ if (trans) {
+ rv = trans->Finish0RTT(aAlpnChanged, aAlpnChanged);
+ if (aRestart) {
+ nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
+ if (hTrans) {
+ hTrans->Refused0RTT();
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult Http2StreamBase::GetOriginAttributes(mozilla::OriginAttributes* oa) {
+ if (!mSocketTransport) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mSocketTransport->GetOriginAttributes(oa);
+}
+
+nsHttpTransaction* Http2StreamBase::HttpTransaction() {
+ return (Transaction()) ? Transaction()->QueryHttpTransaction() : nullptr;
+}
+
+nsHttpConnectionInfo* Http2StreamBase::ConnectionInfo() {
+ if (Transaction()) {
+ return Transaction()->ConnectionInfo();
+ }
+ return nullptr;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http2StreamBase.h b/netwerk/protocol/http/Http2StreamBase.h
new file mode 100644
index 0000000000..80d13f3dcd
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamBase.h
@@ -0,0 +1,377 @@
+/* -*- Mode: C++; tab-width: 8; 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_Http2StreamBase_h
+#define mozilla_net_Http2StreamBase_h
+
+// HTTP/2 - RFC7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsAHttpTransaction.h"
+#include "nsISupportsPriority.h"
+#include "SimpleBuffer.h"
+#include "nsISupportsImpl.h"
+#include "nsIURI.h"
+
+class nsISocketTransport;
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+class OriginAttributes;
+}
+
+namespace mozilla::net {
+
+class nsStandardURL;
+class Http2Session;
+class Http2Stream;
+class Http2PushedStream;
+class Http2Decompressor;
+
+class Http2StreamBase : public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public SupportsWeakPtr {
+ public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+
+ enum stateType {
+ IDLE,
+ RESERVED_BY_REMOTE,
+ OPEN,
+ CLOSED_BY_LOCAL,
+ CLOSED_BY_REMOTE,
+ CLOSED
+ };
+
+ const static int32_t kNormalPriority = 0x1000;
+ const static int32_t kWorstPriority =
+ kNormalPriority + nsISupportsPriority::PRIORITY_LOWEST;
+ const static int32_t kBestPriority =
+ kNormalPriority + nsISupportsPriority::PRIORITY_HIGHEST;
+
+ Http2StreamBase(uint64_t, Http2Session*, int32_t, uint64_t);
+
+ uint32_t StreamID() { return mStreamID; }
+
+ stateType HTTPState() { return mState; }
+ void SetHTTPState(stateType val) { mState = val; }
+
+ [[nodiscard]] virtual nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] virtual nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+ virtual bool DeferCleanup(nsresult status);
+
+ const nsCString& Origin() const { return mOrigin; }
+ const nsCString& Host() const { return mHeaderHost; }
+ const nsCString& Path() const { return mHeaderPath; }
+
+ bool RequestBlockedOnRead() {
+ return static_cast<bool>(mRequestBlockedOnRead);
+ }
+
+ bool HasRegisteredID() { return mStreamID != 0; }
+
+ virtual nsAHttpTransaction* Transaction() { return nullptr; }
+ nsHttpTransaction* HttpTransaction();
+ virtual nsIRequestContext* RequestContext() { return nullptr; }
+
+ virtual void CloseStream(nsresult reason) = 0;
+ void SetResponseIsComplete();
+
+ void SetRecvdFin(bool aStatus);
+ bool RecvdFin() { return mRecvdFin; }
+
+ void SetRecvdData(bool aStatus) { mReceivedData = aStatus ? 1 : 0; }
+ bool RecvdData() { return mReceivedData; }
+
+ void SetSentFin(bool aStatus);
+ bool SentFin() { return mSentFin; }
+
+ void SetRecvdReset(bool aStatus);
+ bool RecvdReset() { return mRecvdReset; }
+
+ void SetSentReset(bool aStatus);
+ bool SentReset() { return mSentReset; }
+
+ void SetQueued(bool aStatus) { mQueued = aStatus ? 1 : 0; }
+ bool Queued() { return mQueued; }
+
+ void SetCountAsActive(bool aStatus) { mCountAsActive = aStatus ? 1 : 0; }
+ bool CountAsActive() { return mCountAsActive; }
+
+ void SetAllHeadersReceived();
+ void UnsetAllHeadersReceived() { mAllHeadersReceived = 0; }
+ bool AllHeadersReceived() { return mAllHeadersReceived; }
+
+ void UpdateTransportSendEvents(uint32_t count);
+ void UpdateTransportReadEvents(uint32_t count);
+
+ // NS_ERROR_ABORT terminates stream, other failure terminates session
+ [[nodiscard]] nsresult ConvertResponseHeaders(Http2Decompressor*, nsACString&,
+ nsACString&, int32_t&);
+ [[nodiscard]] nsresult ConvertResponseTrailers(Http2Decompressor*,
+ nsACString&);
+
+ bool AllowFlowControlledWrite();
+ void UpdateServerReceiveWindow(int32_t delta);
+ int64_t ServerReceiveWindow() { return mServerReceiveWindow; }
+
+ void DecrementClientReceiveWindow(uint32_t delta) {
+ mClientReceiveWindow -= delta;
+ mLocalUnacked += delta;
+ }
+
+ void IncrementClientReceiveWindow(uint32_t delta) {
+ mClientReceiveWindow += delta;
+ mLocalUnacked -= delta;
+ }
+
+ uint64_t LocalUnAcked();
+ int64_t ClientReceiveWindow() { return mClientReceiveWindow; }
+
+ bool BlockedOnRwin() { return mBlockedOnRwin; }
+
+ uint32_t Priority() { return mPriority; }
+ uint32_t PriorityDependency() { return mPriorityDependency; }
+ uint8_t PriorityWeight() { return mPriorityWeight; }
+ void SetPriority(uint32_t);
+ void SetPriorityDependency(uint32_t, uint32_t);
+ void UpdatePriorityDependency();
+
+ uint64_t TransactionBrowserId() { return mTransactionBrowserId; }
+
+ // A pull stream has an implicit sink, a pushed stream has a sink
+ // once it is matched to a pull stream.
+ virtual bool HasSink() { return true; }
+
+ already_AddRefed<Http2Session> Session();
+
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT();
+ nsresult Finish0RTT(bool aRestart, bool aAlpnChanged);
+
+ nsresult GetOriginAttributes(mozilla::OriginAttributes* oa);
+
+ virtual void CurrentBrowserIdChanged(uint64_t id);
+ void CurrentBrowserIdChangedInternal(
+ uint64_t id); // For use by pushed streams only
+
+ virtual bool IsTunnel() { return false; }
+
+ virtual uint32_t GetWireStreamId() { return mStreamID; }
+ virtual Http2Stream* GetHttp2Stream() { return nullptr; }
+ virtual Http2PushedStream* GetHttp2PushedStream() { return nullptr; }
+
+ [[nodiscard]] virtual nsresult OnWriteSegment(char*, uint32_t,
+ uint32_t*) override;
+
+ virtual nsHttpConnectionInfo* ConnectionInfo();
+
+ bool DataBuffered() { return mSimpleBuffer.Available(); }
+
+ virtual nsresult Condition() { return NS_OK; }
+
+ virtual void DisableSpdy() {
+ if (Transaction()) {
+ Transaction()->DisableSpdy();
+ }
+ }
+ virtual void ReuseConnectionOnRestartOK(bool aReuse) {
+ if (Transaction()) {
+ Transaction()->ReuseConnectionOnRestartOK(aReuse);
+ }
+ }
+ virtual void MakeNonSticky() {
+ if (Transaction()) {
+ Transaction()->MakeNonSticky();
+ }
+ }
+
+ protected:
+ virtual ~Http2StreamBase();
+
+ virtual void HandleResponseHeaders(nsACString& aHeadersOut,
+ int32_t httpResponseCode) {}
+ virtual nsresult CallToWriteData(uint32_t count, uint32_t* countRead) = 0;
+ virtual nsresult CallToReadData(uint32_t count, uint32_t* countWritten) = 0;
+ virtual bool CloseSendStreamWhenDone() { return true; }
+
+ // These internal states track request generation
+ enum upstreamStateType {
+ GENERATING_HEADERS,
+ GENERATING_BODY,
+ SENDING_BODY,
+ SENDING_FIN_STREAM,
+ UPSTREAM_COMPLETE
+ };
+
+ uint32_t mStreamID{0};
+
+ // The session that this stream is a subset of
+ nsWeakPtr mSession;
+
+ // These are temporary state variables to hold the argument to
+ // Read/WriteSegments so it can be accessed by On(read/write)segment
+ // further up the stack.
+ RefPtr<nsAHttpSegmentReader> mSegmentReader;
+ nsAHttpSegmentWriter* mSegmentWriter{nullptr};
+
+ nsCString mOrigin;
+ nsCString mHeaderHost;
+ nsCString mHeaderScheme;
+ nsCString mHeaderPath;
+
+ // Each stream goes from generating_headers to upstream_complete, perhaps
+ // looping on multiple instances of generating_body and
+ // sending_body for each frame in the upload.
+ enum upstreamStateType mUpstreamState { GENERATING_HEADERS };
+
+ // The HTTP/2 state for the stream from section 5.1
+ enum stateType mState { IDLE };
+
+ // Flag is set when all http request headers have been read ID is not stable
+ uint32_t mRequestHeadersDone : 1;
+
+ // Flag is set when ID is stable and concurrency limits are met
+ uint32_t mOpenGenerated : 1;
+
+ // Flag is set when all http response headers have been read
+ uint32_t mAllHeadersReceived : 1;
+
+ // Flag is set when stream is queued inside the session due to
+ // concurrency limits being exceeded
+ uint32_t mQueued : 1;
+
+ void ChangeState(enum upstreamStateType);
+
+ virtual void AdjustInitialWindow();
+ [[nodiscard]] nsresult TransmitFrame(const char*, uint32_t*,
+ bool forceCommitment);
+
+ // The underlying socket transport object is needed to propogate some events
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+
+ uint8_t mPriorityWeight = 0; // h2 weight
+ uint32_t mPriorityDependency = 0; // h2 stream id this one depends on
+ uint64_t mCurrentBrowserId;
+ uint64_t mTransactionBrowserId{0};
+
+ // The InlineFrame and associated data is used for composing control
+ // frames and data frame headers.
+ UniquePtr<uint8_t[]> mTxInlineFrame;
+ uint32_t mTxInlineFrameSize{0};
+ uint32_t mTxInlineFrameUsed{0};
+
+ uint32_t mPriority = 0; // geckoish weight
+
+ // Buffer for request header compression.
+ nsCString mFlatHttpRequestHeaders;
+
+ // Track the content-length of a request body so that we can
+ // place the fin flag on the last data packet instead of waiting
+ // for a stream closed indication. Relying on stream close results
+ // in an extra 0-length runt packet and seems to have some interop
+ // problems with the google servers. Connect does rely on stream
+ // close by setting this to the max value.
+ int64_t mRequestBodyLenRemaining{0};
+
+ private:
+ friend class mozilla::DefaultDelete<Http2StreamBase>;
+
+ [[nodiscard]] nsresult ParseHttpRequestHeaders(const char*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] nsresult GenerateOpen();
+
+ virtual nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) = 0;
+
+ void GenerateDataFrameHeader(uint32_t, bool);
+
+ [[nodiscard]] nsresult BufferInput(uint32_t, uint32_t*);
+
+ // The quanta upstream data frames are chopped into
+ uint32_t mChunkSize;
+
+ // Flag is set when the HTTP processor has more data to send
+ // but has blocked in doing so.
+ uint32_t mRequestBlockedOnRead : 1;
+
+ // Flag is set after the response frame bearing the fin bit has
+ // been processed. (i.e. after the server has closed).
+ uint32_t mRecvdFin : 1;
+
+ // Flag is set after 1st DATA frame has been passed to stream
+ uint32_t mReceivedData : 1;
+
+ // Flag is set after RST_STREAM has been received for this stream
+ uint32_t mRecvdReset : 1;
+
+ // Flag is set after RST_STREAM has been generated for this stream
+ uint32_t mSentReset : 1;
+
+ // Flag is set when stream is counted towards MAX_CONCURRENT streams in
+ // session
+ uint32_t mCountAsActive : 1;
+
+ // Flag is set when a FIN has been placed on a data or header frame
+ // (i.e after the client has closed)
+ uint32_t mSentFin : 1;
+
+ // Flag is set after the WAITING_FOR Transport event has been generated
+ uint32_t mSentWaitingFor : 1;
+
+ // Flag is set after TCP send autotuning has been disabled
+ uint32_t mSetTCPSocketBuffer : 1;
+
+ // Flag is set when OnWriteSegment is being called directly from stream
+ // instead of transaction
+ uint32_t mBypassInputBuffer : 1;
+
+ // mTxStreamFrameSize tracks the progress of
+ // transmitting a request body data frame. The data frame itself
+ // is never copied into the spdy layer.
+ uint32_t mTxStreamFrameSize{0};
+
+ // mClientReceiveWindow, mServerReceiveWindow, and mLocalUnacked are for flow
+ // control. *window are signed because the race conditions in asynchronous
+ // SETTINGS messages can force them temporarily negative.
+
+ // mClientReceiveWindow is how much data the server will send without getting
+ // a
+ // window update
+ int64_t mClientReceiveWindow;
+
+ // mServerReceiveWindow is how much data the client is allowed to send without
+ // getting a window update
+ int64_t mServerReceiveWindow;
+
+ // LocalUnacked is the number of bytes received by the client but not
+ // yet reflected in a window update. Sending that update will increment
+ // ClientReceiveWindow
+ uint64_t mLocalUnacked{0};
+
+ // True when sending is suspended becuase the server receive window is
+ // <= 0
+ bool mBlockedOnRwin{false};
+
+ // For Progress Events
+ uint64_t mTotalSent{0};
+ uint64_t mTotalRead{0};
+
+ // Used to store stream data when the transaction channel cannot keep up
+ // and flow control has not yet kicked in.
+ SimpleBuffer mSimpleBuffer;
+
+ bool mAttempting0RTT{false};
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http2StreamBase_h
diff --git a/netwerk/protocol/http/Http2StreamTunnel.cpp b/netwerk/protocol/http/Http2StreamTunnel.cpp
new file mode 100644
index 0000000000..4e0f05d58b
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamTunnel.cpp
@@ -0,0 +1,764 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "nsHttpHandler.h"
+#include "Http2StreamTunnel.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsQueryObject.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla::net {
+
+bool Http2StreamTunnel::DispatchRelease() {
+ if (OnSocketThread()) {
+ return false;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewNonOwningRunnableMethod("net::Http2StreamTunnel::Release", this,
+ &Http2StreamTunnel::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMPL_ADDREF(Http2StreamTunnel)
+NS_IMETHODIMP_(MozExternalRefCountType)
+Http2StreamTunnel::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the socket thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "Http2StreamTunnel");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(Http2StreamTunnel)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(Http2StreamTunnel)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY(nsISocketTransport)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(Http2StreamTunnel)
+
+Http2StreamTunnel::Http2StreamTunnel(Http2Session* session, int32_t priority,
+ uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo)
+ : Http2StreamBase(0, session, priority, bcId),
+ mConnectionInfo(aConnectionInfo) {}
+
+Http2StreamTunnel::~Http2StreamTunnel() { ClearTransactionsBlockedOnTunnel(); }
+
+void Http2StreamTunnel::HandleResponseHeaders(nsACString& aHeadersOut,
+ int32_t httpResponseCode) {}
+
+// TODO We do not need this. Fix in bug 1772212.
+void Http2StreamTunnel::ClearTransactionsBlockedOnTunnel() {
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnectionInfo);
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2StreamTunnel::ClearTransactionsBlockedOnTunnel %p\n"
+ " ProcessPendingQ failed: %08x\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetKeepaliveEnabled(bool aKeepaliveEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) {
+ return mSocketTransport->GetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void Http2StreamTunnel::CloseStream(nsresult aReason) {
+ LOG(("Http2StreamTunnel::CloseStream this=%p", this));
+ RefPtr<Http2Session> session = Session();
+ if (NS_SUCCEEDED(mCondition)) {
+ mSession = nullptr;
+ // Let the session pickup that the stream has been closed.
+ mCondition = aReason;
+ if (NS_SUCCEEDED(aReason)) {
+ aReason = NS_BASE_STREAM_CLOSED;
+ }
+ mOutput->OnSocketReady(aReason);
+ mInput->OnSocketReady(aReason);
+ }
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::Close(nsresult aReason) {
+ LOG(("Http2StreamTunnel::Close this=%p", this));
+ RefPtr<Http2Session> session = Session();
+ if (NS_SUCCEEDED(mCondition)) {
+ mSession = nullptr;
+ if (NS_SUCCEEDED(aReason)) {
+ aReason = NS_BASE_STREAM_CLOSED;
+ }
+ mOutput->CloseWithStatus(aReason);
+ mInput->CloseWithStatus(aReason);
+ // Let the session pickup that the stream has been closed.
+ mCondition = aReason;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::Bind(NetAddr* aLocalAddr) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetEchConfigUsed(bool* aEchConfigUsed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetEchConfig(const nsACString& aEchConfig) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::ResolvedByTRR(bool* aResolvedByTRR) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP Http2StreamTunnel::GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP Http2StreamTunnel::GetTrrSkipReason(
+ nsITRRSkipReason::value* aTrrSkipReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::IsAlive(bool* aAlive) {
+ RefPtr<Http2Session> session = Session();
+ if (mSocketTransport && session) {
+ return mSocketTransport->IsAlive(aAlive);
+ }
+ *aAlive = false;
+ return NS_OK;
+}
+
+#define FWD_TS_T_PTR(fx, ts) \
+ NS_IMETHODIMP \
+ Http2StreamTunnel::fx(ts* arg) { return mSocketTransport->fx(arg); }
+
+#define FWD_TS_T_ADDREF(fx, ts) \
+ NS_IMETHODIMP \
+ Http2StreamTunnel::fx(ts** arg) { return mSocketTransport->fx(arg); }
+
+#define FWD_TS_T(fx, ts) \
+ NS_IMETHODIMP \
+ Http2StreamTunnel::fx(ts arg) { return mSocketTransport->fx(arg); }
+
+FWD_TS_T_PTR(GetKeepaliveEnabled, bool);
+FWD_TS_T_PTR(GetSendBufferSize, uint32_t);
+FWD_TS_T(SetSendBufferSize, uint32_t);
+FWD_TS_T_PTR(GetPort, int32_t);
+FWD_TS_T_PTR(GetPeerAddr, mozilla::net::NetAddr);
+FWD_TS_T_PTR(GetSelfAddr, mozilla::net::NetAddr);
+FWD_TS_T_ADDREF(GetScriptablePeerAddr, nsINetAddr);
+FWD_TS_T_ADDREF(GetScriptableSelfAddr, nsINetAddr);
+FWD_TS_T_ADDREF(GetTlsSocketControl, nsITLSSocketControl);
+FWD_TS_T_PTR(GetConnectionFlags, uint32_t);
+FWD_TS_T(SetConnectionFlags, uint32_t);
+FWD_TS_T(SetIsPrivate, bool);
+FWD_TS_T_PTR(GetTlsFlags, uint32_t);
+FWD_TS_T(SetTlsFlags, uint32_t);
+FWD_TS_T_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS_T(SetRecvBufferSize, uint32_t);
+FWD_TS_T_PTR(GetResetIPFamilyPreference, bool);
+
+nsresult Http2StreamTunnel::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ return mSocketTransport->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult Http2StreamTunnel::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ return mSocketTransport->SetOriginAttributes(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ return mSocketTransport->GetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ return mSocketTransport->SetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetHost(nsACString& aHost) {
+ return mSocketTransport->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetTimeout(uint32_t aType, uint32_t* _retval) {
+ return mSocketTransport->GetTimeout(aType, _retval);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetTimeout(uint32_t aType, uint32_t aValue) {
+ return mSocketTransport->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetReuseAddrPort(bool aReuseAddrPort) {
+ return mSocketTransport->SetReuseAddrPort(aReuseAddrPort);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetLinger(bool aPolarity, int16_t aTimeout) {
+ return mSocketTransport->SetLinger(aPolarity, aTimeout);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetQoSBits(uint8_t* aQoSBits) {
+ return mSocketTransport->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetQoSBits(uint8_t aQoSBits) {
+ return mSocketTransport->SetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetRetryDnsIfPossible(bool* aRetry) {
+ return mSocketTransport->GetRetryDnsIfPossible(aRetry);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetStatus(nsresult* aStatus) {
+ return mSocketTransport->GetStatus(aStatus);
+}
+
+already_AddRefed<nsHttpConnection> Http2StreamTunnel::CreateHttpConnection(
+ nsAHttpTransaction* httpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket) {
+ mInput = new InputStreamTunnel(this);
+ mOutput = new OutputStreamTunnel(this);
+ RefPtr<nsHttpConnection> conn = new nsHttpConnection();
+
+ conn->SetTransactionCaps(httpTransaction->Caps());
+ nsresult rv =
+ conn->Init(httpTransaction->ConnectionInfo(),
+ gHttpHandler->ConnMgr()->MaxRequestDelay(), this, mInput,
+ mOutput, true, NS_OK, aCallbacks, aRtt, aIsWebSocket);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ mTransaction = httpTransaction;
+ return conn.forget();
+}
+
+nsresult Http2StreamTunnel::CallToReadData(uint32_t count,
+ uint32_t* countRead) {
+ LOG(("Http2StreamTunnel::CallToReadData this=%p", this));
+ return mOutput->OnSocketReady(NS_OK);
+}
+
+nsresult Http2StreamTunnel::CallToWriteData(uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("Http2StreamTunnel::CallToWriteData this=%p", this));
+ if (!mInput->HasCallback()) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ return mInput->OnSocketReady(NS_OK);
+}
+
+nsresult Http2StreamTunnel::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ nsAutoCString authorityHeader;
+ authorityHeader = mConnectionInfo->GetOrigin();
+ authorityHeader.Append(':');
+ authorityHeader.AppendInt(mConnectionInfo->OriginPort());
+
+ RefPtr<Http2Session> session = Session();
+
+ LOG3(("Http2StreamTunnel %p Stream ID 0x%X [session=%p] for %s\n", this,
+ mStreamID, session.get(), authorityHeader.get()));
+
+ mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+
+ nsresult rv = session->Compressor()->EncodeHeaderBlock(
+ mFlatHttpRequestHeaders, "CONNECT"_ns, EmptyCString(), authorityHeader,
+ EmptyCString(), EmptyCString(), true, aCompressedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ aCompressedData.Length() * 100 /
+ (11 + authorityHeader.Length() + mFlatHttpRequestHeaders.Length());
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+
+ return NS_OK;
+}
+
+OutputStreamTunnel::OutputStreamTunnel(Http2StreamTunnel* aStream) {
+ mWeakStream = do_GetWeakReference(aStream);
+}
+
+OutputStreamTunnel::~OutputStreamTunnel() {
+ NS_ProxyRelease("OutputStreamTunnel::~OutputStreamTunnel",
+ gSocketTransportService, mWeakStream.forget());
+}
+
+nsresult OutputStreamTunnel::OnSocketReady(nsresult condition) {
+ LOG(("OutputStreamTunnel::OnSocketReady [this=%p cond=%" PRIx32
+ " callback=%p]\n",
+ this, static_cast<uint32_t>(condition), mCallback.get()));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIOutputStreamCallback> callback;
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) {
+ mCondition = condition;
+ }
+ callback = std::move(mCallback);
+
+ nsresult rv = NS_OK;
+ if (callback) {
+ rv = callback->OnOutputStreamReady(this);
+ MaybeSetRequestDone(callback);
+ }
+
+ return rv;
+}
+
+void OutputStreamTunnel::MaybeSetRequestDone(
+ nsIOutputStreamCallback* aCallback) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(aCallback);
+ if (!conn) {
+ return;
+ }
+
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (conn->RequestDone()) {
+ tunnel->SetRequestDone();
+ }
+}
+
+NS_IMPL_ISUPPORTS(OutputStreamTunnel, nsIOutputStream, nsIAsyncOutputStream)
+
+NS_IMETHODIMP
+OutputStreamTunnel::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+OutputStreamTunnel::Flush() { return NS_OK; }
+
+NS_IMETHODIMP
+OutputStreamTunnel::StreamStatus() { return mCondition; }
+
+NS_IMETHODIMP
+OutputStreamTunnel::Write(const char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("OutputStreamTunnel::Write [this=%p count=%u]\n", this, count));
+
+ *countWritten = 0;
+ if (NS_FAILED(mCondition)) {
+ return mCondition;
+ }
+
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return tunnel->OnReadSegment(buf, count, countWritten);
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::WriteFrom(nsIInputStream* stream, uint32_t count,
+ uint32_t* countRead) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::CloseWithStatus(nsresult reason) {
+ LOG(("OutputStreamTunnel::CloseWithStatus [this=%p reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ mCondition = reason;
+
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ mWeakStream = nullptr;
+ if (!tunnel) {
+ return NS_OK;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ if (!session) {
+ return NS_OK;
+ }
+ session->CleanupStream(tunnel, reason, Http2Session::CANCEL_ERROR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::AsyncWait(nsIOutputStreamCallback* callback, uint32_t flags,
+ uint32_t amount, nsIEventTarget* target) {
+ LOG(("OutputStreamTunnel::AsyncWait [this=%p]\n", this));
+
+ // The following parametr are not used:
+ MOZ_ASSERT(!flags);
+ MOZ_ASSERT(!amount);
+ Unused << target;
+
+ RefPtr<OutputStreamTunnel> self(this);
+ if (NS_FAILED(mCondition)) {
+ Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "OutputStreamTunnel::CallOnSocketReady",
+ [self{std::move(self)}]() { self->OnSocketReady(NS_OK); }));
+ } else if (callback) {
+ // Inform the proxy connection that the inner connetion wants to
+ // read data.
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session;
+ rv = GetSession(getter_AddRefs(session));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ session->TransactionHasDataToWrite(tunnel);
+ }
+
+ mCallback = callback;
+ return NS_OK;
+}
+
+InputStreamTunnel::InputStreamTunnel(Http2StreamTunnel* aStream) {
+ mWeakStream = do_GetWeakReference(aStream);
+}
+
+InputStreamTunnel::~InputStreamTunnel() {
+ NS_ProxyRelease("InputStreamTunnel::~InputStreamTunnel",
+ gSocketTransportService, mWeakStream.forget());
+}
+
+nsresult InputStreamTunnel::OnSocketReady(nsresult condition) {
+ LOG(("InputStreamTunnel::OnSocketReady [this=%p cond=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) {
+ mCondition = condition;
+ }
+ callback = std::move(mCallback);
+
+ return callback ? callback->OnInputStreamReady(this) : NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(InputStreamTunnel, nsIInputStream, nsIAsyncInputStream)
+
+NS_IMETHODIMP
+InputStreamTunnel::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+InputStreamTunnel::Available(uint64_t* avail) {
+ LOG(("InputStreamTunnel::Available [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) {
+ return mCondition;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::StreamStatus() {
+ LOG(("InputStreamTunnel::StreamStatus [this=%p]\n", this));
+
+ return mCondition;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::Read(char* buf, uint32_t count, uint32_t* countRead) {
+ LOG(("InputStreamTunnel::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ if (NS_FAILED(mCondition)) {
+ return mCondition;
+ }
+
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return tunnel->OnWriteSegment(buf, count, countRead);
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::CloseWithStatus(nsresult reason) {
+ LOG(("InputStreamTunnel::CloseWithStatus [this=%p reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ mCondition = reason;
+
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ mWeakStream = nullptr;
+ if (!tunnel) {
+ return NS_OK;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ if (!session) {
+ return NS_OK;
+ }
+ session->CleanupStream(tunnel, reason, Http2Session::CANCEL_ERROR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t amount, nsIEventTarget* target) {
+ LOG(("InputStreamTunnel::AsyncWait [this=%p mCondition=%x]\n", this,
+ static_cast<uint32_t>(mCondition)));
+
+ // The following parametr are not used:
+ MOZ_ASSERT(!flags);
+ MOZ_ASSERT(!amount);
+ Unused << target;
+
+ RefPtr<InputStreamTunnel> self(this);
+ if (NS_FAILED(mCondition)) {
+ Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "InputStreamTunnel::CallOnSocketReady",
+ [self{std::move(self)}]() { self->OnSocketReady(NS_OK); }));
+ } else if (callback) {
+ // Inform the proxy connection that the inner connetion wants to
+ // read data.
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session;
+ rv = GetSession(getter_AddRefs(session));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (tunnel->DataBuffered()) {
+ session->TransactionHasDataToRecv(tunnel);
+ }
+ }
+
+ mCallback = callback;
+ return NS_OK;
+}
+
+nsresult OutputStreamTunnel::GetStream(Http2StreamTunnel** aStream) {
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ MOZ_ASSERT(tunnel);
+ if (!tunnel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ tunnel.forget(aStream);
+ return NS_OK;
+}
+
+nsresult OutputStreamTunnel::GetSession(Http2Session** aSession) {
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ MOZ_ASSERT(session);
+ if (!session) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ session.forget(aSession);
+ return NS_OK;
+}
+
+nsresult InputStreamTunnel::GetStream(Http2StreamTunnel** aStream) {
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ MOZ_ASSERT(tunnel);
+ if (!tunnel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ tunnel.forget(aStream);
+ return NS_OK;
+}
+
+nsresult InputStreamTunnel::GetSession(Http2Session** aSession) {
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ if (!session) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ session.forget(aSession);
+ return NS_OK;
+}
+
+Http2StreamWebSocket::Http2StreamWebSocket(
+ Http2Session* session, int32_t priority, uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo)
+ : Http2StreamTunnel(session, priority, bcId, aConnectionInfo) {
+ LOG(("Http2StreamWebSocket ctor:%p", this));
+}
+
+Http2StreamWebSocket::~Http2StreamWebSocket() {
+ LOG(("Http2StreamWebSocket dtor:%p", this));
+}
+
+nsresult Http2StreamWebSocket::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ nsAutoCString authorityHeader;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<Http2Session> session = Session();
+ LOG3(("Http2StreamWebSocket %p Stream ID 0x%X [session=%p] for %s\n", this,
+ mStreamID, session.get(), authorityHeader.get()));
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+ nsAutoCString path;
+ head->Path(path);
+
+ rv = session->Compressor()->EncodeHeaderBlock(
+ mFlatHttpRequestHeaders, "CONNECT"_ns, path, authorityHeader, scheme,
+ "websocket"_ns, false, aCompressedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ aCompressedData.Length() * 100 /
+ (11 + authorityHeader.Length() + mFlatHttpRequestHeaders.Length());
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+ return NS_OK;
+}
+
+void Http2StreamWebSocket::CloseStream(nsresult aReason) {
+ LOG(("Http2StreamWebSocket::CloseStream this=%p aReason=%x", this,
+ static_cast<uint32_t>(aReason)));
+ if (mTransaction) {
+ mTransaction->Close(aReason);
+ mTransaction = nullptr;
+ }
+ Http2StreamTunnel::CloseStream(aReason);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http2StreamTunnel.h b/netwerk/protocol/http/Http2StreamTunnel.h
new file mode 100644
index 0000000000..78f0300f4c
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamTunnel.h
@@ -0,0 +1,151 @@
+/* -*- 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_Http2StreamTunnel_h
+#define mozilla_net_Http2StreamTunnel_h
+
+#include "Http2StreamBase.h"
+#include "nsHttpConnection.h"
+#include "nsWeakReference.h"
+
+namespace mozilla::net {
+
+class OutputStreamTunnel;
+class InputStreamTunnel;
+
+// c881f764-a183-45cb-9dec-d9872b2f47b2
+#define NS_HTTP2STREAMTUNNEL_IID \
+ { \
+ 0xc881f764, 0xa183, 0x45cb, { \
+ 0x9d, 0xec, 0xd9, 0x87, 0x2b, 0x2f, 0x47, 0xb2 \
+ } \
+ }
+
+class Http2StreamTunnel : public Http2StreamBase,
+ public nsISocketTransport,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP2STREAMTUNNEL_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+
+ Http2StreamTunnel(Http2Session* session, int32_t priority, uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo);
+ bool IsTunnel() override { return true; }
+
+ already_AddRefed<nsHttpConnection> CreateHttpConnection(
+ nsAHttpTransaction* httpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket);
+
+ nsHttpConnectionInfo* ConnectionInfo() override { return mConnectionInfo; }
+
+ void SetRequestDone() { mSendClosed = true; }
+ nsresult Condition() override { return mCondition; }
+ void CloseStream(nsresult reason) override;
+ void DisableSpdy() override {
+ if (mTransaction) {
+ mTransaction->DisableHttp2ForProxy();
+ }
+ }
+ void ReuseConnectionOnRestartOK(bool aReuse) override {
+ // Do nothing here. We'll never reuse the connection.
+ }
+ void MakeNonSticky() override {}
+
+ protected:
+ ~Http2StreamTunnel();
+ nsresult CallToReadData(uint32_t count, uint32_t* countRead) override;
+ nsresult CallToWriteData(uint32_t count, uint32_t* countWritten) override;
+ void HandleResponseHeaders(nsACString& aHeadersOut,
+ int32_t httpResponseCode) override;
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+ bool CloseSendStreamWhenDone() override { return mSendClosed; }
+
+ RefPtr<nsAHttpTransaction> mTransaction;
+
+ void ClearTransactionsBlockedOnTunnel();
+ bool DispatchRelease();
+
+ RefPtr<OutputStreamTunnel> mOutput;
+ RefPtr<InputStreamTunnel> mInput;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ bool mSendClosed{false};
+ nsresult mCondition{NS_OK};
+};
+
+// f9d10060-f5d4-443e-ba59-f84ea975c5f0
+#define NS_OUTPUTSTREAMTUNNEL_IID \
+ { \
+ 0xf9d10060, 0xf5d4, 0x443e, { \
+ 0xba, 0x59, 0xf8, 0x4e, 0xa9, 0x75, 0xc5, 0xf0 \
+ } \
+ }
+
+class OutputStreamTunnel : public nsIAsyncOutputStream {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_OUTPUTSTREAMTUNNEL_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit OutputStreamTunnel(Http2StreamTunnel* aStream);
+
+ nsresult OnSocketReady(nsresult condition);
+ void MaybeSetRequestDone(nsIOutputStreamCallback* aCallback);
+
+ private:
+ virtual ~OutputStreamTunnel();
+
+ nsresult GetStream(Http2StreamTunnel** aStream);
+ nsresult GetSession(Http2Session** aSession);
+
+ nsWeakPtr mWeakStream;
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ nsresult mCondition{NS_OK};
+};
+
+class InputStreamTunnel : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit InputStreamTunnel(Http2StreamTunnel* aStream);
+
+ nsresult OnSocketReady(nsresult condition);
+ bool HasCallback() { return !!mCallback; }
+
+ private:
+ virtual ~InputStreamTunnel();
+
+ nsresult GetStream(Http2StreamTunnel** aStream);
+ nsresult GetSession(Http2Session** aSession);
+
+ nsWeakPtr mWeakStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsresult mCondition{NS_OK};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Http2StreamTunnel, NS_HTTP2STREAMTUNNEL_IID)
+NS_DEFINE_STATIC_IID_ACCESSOR(OutputStreamTunnel, NS_OUTPUTSTREAMTUNNEL_IID)
+
+class Http2StreamWebSocket : public Http2StreamTunnel {
+ public:
+ Http2StreamWebSocket(Http2Session* session, int32_t priority, uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo);
+ void CloseStream(nsresult reason) override;
+
+ protected:
+ virtual ~Http2StreamWebSocket();
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http2StreamTunnel_h
diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp
new file mode 100644
index 0000000000..0369da94ac
--- /dev/null
+++ b/netwerk/protocol/http/Http3Session.cpp
@@ -0,0 +1,2522 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ASpdySession.h" // because of SoftStreamError()
+#include "Http3Session.h"
+#include "Http3Stream.h"
+#include "Http3StreamBase.h"
+#include "Http3WebTransportSession.h"
+#include "Http3WebTransportStream.h"
+#include "HttpConnectionUDP.h"
+#include "HttpLog.h"
+#include "QuicSocketControl.h"
+#include "SSLServerCertVerification.h"
+#include "SSLTokensCache.h"
+#include "ScopedNSSTypes.h"
+#include "mozilla/RandomNum.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DNS.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsIOService.h"
+#include "nsITLSSocketControl.h"
+#include "nsNetAddr.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "sslerr.h"
+
+namespace mozilla::net {
+
+const uint64_t HTTP3_APP_ERROR_NO_ERROR = 0x100;
+// const uint64_t HTTP3_APP_ERROR_GENERAL_PROTOCOL_ERROR = 0x101;
+// const uint64_t HTTP3_APP_ERROR_INTERNAL_ERROR = 0x102;
+// const uint64_t HTTP3_APP_ERROR_STREAM_CREATION_ERROR = 0x103;
+// const uint64_t HTTP3_APP_ERROR_CLOSED_CRITICAL_STREAM = 0x104;
+// const uint64_t HTTP3_APP_ERROR_FRAME_UNEXPECTED = 0x105;
+// const uint64_t HTTP3_APP_ERROR_FRAME_ERROR = 0x106;
+// const uint64_t HTTP3_APP_ERROR_EXCESSIVE_LOAD = 0x107;
+// const uint64_t HTTP3_APP_ERROR_ID_ERROR = 0x108;
+// const uint64_t HTTP3_APP_ERROR_SETTINGS_ERROR = 0x109;
+// const uint64_t HTTP3_APP_ERROR_MISSING_SETTINGS = 0x10a;
+const uint64_t HTTP3_APP_ERROR_REQUEST_REJECTED = 0x10b;
+const uint64_t HTTP3_APP_ERROR_REQUEST_CANCELLED = 0x10c;
+// const uint64_t HTTP3_APP_ERROR_REQUEST_INCOMPLETE = 0x10d;
+// const uint64_t HTTP3_APP_ERROR_EARLY_RESPONSE = 0x10e;
+// const uint64_t HTTP3_APP_ERROR_CONNECT_ERROR = 0x10f;
+const uint64_t HTTP3_APP_ERROR_VERSION_FALLBACK = 0x110;
+
+// const uint32_t UDP_MAX_PACKET_SIZE = 4096;
+const uint32_t MAX_PTO_COUNTS = 16;
+
+const uint32_t TRANSPORT_ERROR_STATELESS_RESET = 20;
+
+NS_IMPL_ADDREF(Http3Session)
+NS_IMPL_RELEASE(Http3Session)
+NS_INTERFACE_MAP_BEGIN(Http3Session)
+ NS_INTERFACE_MAP_ENTRY(nsAHttpConnection)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(Http3Session)
+NS_INTERFACE_MAP_END
+
+Http3Session::Http3Session() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Session::Http3Session [this=%p]", this));
+
+ mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId();
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
+}
+
+static nsresult RawBytesToNetAddr(uint16_t aFamily, const uint8_t* aRemoteAddr,
+ uint16_t remotePort, NetAddr* netAddr) {
+ if (aFamily == AF_INET) {
+ netAddr->inet.family = AF_INET;
+ netAddr->inet.port = htons(remotePort);
+ memcpy(&netAddr->inet.ip, aRemoteAddr, 4);
+ } else if (aFamily == AF_INET6) {
+ netAddr->inet6.family = AF_INET6;
+ netAddr->inet6.port = htons(remotePort);
+ memcpy(&netAddr->inet6.ip.u8, aRemoteAddr, 16);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo,
+ nsINetAddr* aSelfAddr, nsINetAddr* aPeerAddr,
+ HttpConnectionUDP* udpConn, uint32_t controlFlags,
+ nsIInterfaceRequestor* callbacks) {
+ LOG3(("Http3Session::Init %p", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(udpConn);
+
+ mConnInfo = aConnInfo->Clone();
+ mNetAddr = aPeerAddr;
+
+ bool httpsProxy =
+ aConnInfo->ProxyInfo() ? aConnInfo->ProxyInfo()->IsHTTPS() : false;
+
+ // Create security control and info object for quic.
+ mSocketControl = new QuicSocketControl(
+ httpsProxy ? aConnInfo->ProxyInfo()->Host() : aConnInfo->GetOrigin(),
+ httpsProxy ? aConnInfo->ProxyInfo()->Port() : aConnInfo->OriginPort(),
+ controlFlags, this);
+
+ NetAddr selfAddr;
+ MOZ_ALWAYS_SUCCEEDS(aSelfAddr->GetNetAddr(&selfAddr));
+ NetAddr peerAddr;
+ MOZ_ALWAYS_SUCCEEDS(aPeerAddr->GetNetAddr(&peerAddr));
+
+ LOG3(
+ ("Http3Session::Init origin=%s, alpn=%s, selfAddr=%s, peerAddr=%s,"
+ " qpack table size=%u, max blocked streams=%u webtransport=%d "
+ "[this=%p]",
+ PromiseFlatCString(mConnInfo->GetOrigin()).get(),
+ PromiseFlatCString(mConnInfo->GetNPNToken()).get(),
+ selfAddr.ToString().get(), peerAddr.ToString().get(),
+ gHttpHandler->DefaultQpackTableSize(),
+ gHttpHandler->DefaultHttp3MaxBlockedStreams(),
+ mConnInfo->GetWebTransport(), this));
+
+ if (mConnInfo->GetWebTransport()) {
+ mWebTransportNegotiationStatus = WebTransportNegotiation::NEGOTIATING;
+ }
+
+ uint32_t datagramSize =
+ StaticPrefs::network_webtransport_datagrams_enabled()
+ ? StaticPrefs::network_webtransport_datagram_size()
+ : 0;
+ nsresult rv = NeqoHttp3Conn::Init(
+ mConnInfo->GetOrigin(), mConnInfo->GetNPNToken(), selfAddr, peerAddr,
+ gHttpHandler->DefaultQpackTableSize(),
+ gHttpHandler->DefaultHttp3MaxBlockedStreams(),
+ StaticPrefs::network_http_http3_max_data(),
+ StaticPrefs::network_http_http3_max_stream_data(),
+ StaticPrefs::network_http_http3_version_negotiation_enabled(),
+ mConnInfo->GetWebTransport(), gHttpHandler->Http3QlogDir(), datagramSize,
+ StaticPrefs::network_http_http3_max_accumlated_time_ms(),
+ getter_AddRefs(mHttp3Connection));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString peerId;
+ mSocketControl->GetPeerId(peerId);
+ nsTArray<uint8_t> token;
+ SessionCacheInfo info;
+ udpConn->ChangeConnectionState(ConnectionState::TLS_HANDSHAKING);
+
+ if (StaticPrefs::network_http_http3_enable_0rtt() &&
+ NS_SUCCEEDED(SSLTokensCache::Get(peerId, token, info))) {
+ LOG(("Found a resumption token in the cache."));
+ mHttp3Connection->SetResumptionToken(token);
+ mSocketControl->SetSessionCacheInfo(std::move(info));
+ if (mHttp3Connection->IsZeroRtt()) {
+ LOG(("Can send ZeroRtt data"));
+ RefPtr<Http3Session> self(this);
+ mState = ZERORTT;
+ udpConn->ChangeConnectionState(ConnectionState::ZERORTT);
+ mZeroRttStarted = TimeStamp::Now();
+ // Let the nsHttpConnectionMgr know that the connection can accept
+ // transactions.
+ // We need to dispatch the following function to this thread so that
+ // it is executed after the current function. At this point a
+ // Http3Session is still being initialized and ReportHttp3Connection
+ // will try to dispatch transaction on this session therefore it
+ // needs to be executed after the initializationg is done.
+ DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("Http3Session::ReportHttp3Connection",
+ [self]() { self->ReportHttp3Connection(); }));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "NS_DispatchToCurrentThread failed");
+ }
+ }
+
+ if (mState != ZERORTT) {
+ ZeroRttTelemetry(ZeroRttOutcome::NOT_USED);
+ }
+
+ auto config = mConnInfo->GetEchConfig();
+ if (config.IsEmpty()) {
+ if (StaticPrefs::security_tls_ech_grease_http3() && config.IsEmpty()) {
+ if ((RandomUint64().valueOr(0) % 100) >=
+ 100 - StaticPrefs::security_tls_ech_grease_probability()) {
+ // Setting an empty config enables GREASE mode.
+ mSocketControl->SetEchConfig(config);
+ mEchExtensionStatus = EchExtensionStatus::kGREASE;
+ }
+ }
+ } else if (gHttpHandler->EchConfigEnabled(true) && !config.IsEmpty()) {
+ mSocketControl->SetEchConfig(config);
+ mEchExtensionStatus = EchExtensionStatus::kReal;
+ HttpConnectionActivity activity(
+ mConnInfo->HashKey(), mConnInfo->GetOrigin(), mConnInfo->OriginPort(),
+ mConnInfo->EndToEndSSL(), !mConnInfo->GetEchConfig().IsEmpty(),
+ mConnInfo->IsHttp3());
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET, PR_Now(), 0, ""_ns);
+ } else {
+ mEchExtensionStatus = EchExtensionStatus::kNotPresent;
+ }
+
+ // After this line, Http3Session and HttpConnectionUDP become a cycle. We put
+ // this line in the end of Http3Session::Init to make sure Http3Session can be
+ // released when Http3Session::Init early returned.
+ mUdpConn = udpConn;
+ return NS_OK;
+}
+
+void Http3Session::DoSetEchConfig(const nsACString& aEchConfig) {
+ LOG(("Http3Session::DoSetEchConfig %p of length %zu", this,
+ aEchConfig.Length()));
+ nsTArray<uint8_t> config;
+ config.AppendElements(
+ reinterpret_cast<const uint8_t*>(aEchConfig.BeginReading()),
+ aEchConfig.Length());
+ mHttp3Connection->SetEchConfig(config);
+}
+
+nsresult Http3Session::SendPriorityUpdateFrame(uint64_t aStreamId,
+ uint8_t aPriorityUrgency,
+ bool aPriorityIncremental) {
+ return mHttp3Connection->PriorityUpdate(aStreamId, aPriorityUrgency,
+ aPriorityIncremental);
+}
+
+// Shutdown the http3session and close all transactions.
+void Http3Session::Shutdown() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+
+ bool isEchRetry = mError == mozilla::psm::GetXPCOMFromNSSError(
+ SSL_ERROR_ECH_RETRY_WITH_ECH);
+ bool allowToRetryWithDifferentIPFamily =
+ mBeforeConnectedError &&
+ gHttpHandler->ConnMgr()->AllowToRetryDifferentIPFamilyForHttp3(mConnInfo,
+ mError);
+ LOG(("Http3Session::Shutdown %p allowToRetryWithDifferentIPFamily=%d", this,
+ allowToRetryWithDifferentIPFamily));
+ if ((mBeforeConnectedError ||
+ (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR)) &&
+ (mError !=
+ mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) &&
+ !isEchRetry && !mConnInfo->GetWebTransport() &&
+ !allowToRetryWithDifferentIPFamily && !mDontExclude) {
+ gHttpHandler->ExcludeHttp3(mConnInfo);
+ }
+
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ if (mBeforeConnectedError) {
+ // We have an error before we were connected, just restart transactions.
+ // The transaction restart code path will remove AltSvc mapping and the
+ // direct path will be used.
+ MOZ_ASSERT(NS_FAILED(mError));
+ if (isEchRetry) {
+ // We have to propagate this error to nsHttpTransaction, so the
+ // transaction will be restarted with a new echConfig.
+ stream->Close(mError);
+ } else {
+ if (allowToRetryWithDifferentIPFamily && mNetAddr) {
+ NetAddr addr;
+ mNetAddr->GetNetAddr(&addr);
+ gHttpHandler->ConnMgr()->SetRetryDifferentIPFamilyForHttp3(
+ mConnInfo, addr.raw.family);
+ // We want the transaction to be restarted with the same connection
+ // info.
+ stream->Transaction()->DoNotRemoveAltSvc();
+ // We already set the preference in SetRetryDifferentIPFamilyForHttp3,
+ // so we want to keep it for the next retry.
+ stream->Transaction()->DoNotResetIPFamilyPreference();
+ stream->Close(NS_ERROR_NET_RESET);
+ // Since Http3Session::Shutdown can be called multiple times, we set
+ // mDontExclude for not putting this domain into the excluded list.
+ mDontExclude = true;
+ } else {
+ stream->Close(NS_ERROR_NET_RESET);
+ }
+ }
+ } else if (!stream->HasStreamId()) {
+ if (NS_SUCCEEDED(mError)) {
+ // Connection has not been started yet. We can restart it.
+ stream->Transaction()->DoNotRemoveAltSvc();
+ }
+ stream->Close(NS_ERROR_NET_RESET);
+ } else if (stream->GetHttp3Stream() &&
+ stream->GetHttp3Stream()->RecvdData()) {
+ stream->Close(NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else if (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR) {
+ stream->Close(NS_ERROR_NET_HTTP3_PROTOCOL_ERROR);
+ } else if (mError == NS_ERROR_NET_RESET) {
+ stream->Close(NS_ERROR_NET_RESET);
+ } else {
+ stream->Close(NS_ERROR_ABORT);
+ }
+ RemoveStreamFromQueues(stream);
+ if (stream->HasStreamId()) {
+ mStreamIdHash.Remove(stream->StreamId());
+ }
+ }
+ mStreamTransactionHash.Clear();
+
+ for (const auto& stream : mWebTransportSessions) {
+ stream->Close(NS_ERROR_ABORT);
+ RemoveStreamFromQueues(stream);
+ mStreamIdHash.Remove(stream->StreamId());
+ }
+ mWebTransportSessions.Clear();
+
+ for (const auto& stream : mWebTransportStreams) {
+ stream->Close(NS_ERROR_ABORT);
+ RemoveStreamFromQueues(stream);
+ mStreamIdHash.Remove(stream->StreamId());
+ }
+
+ RefPtr<Http3StreamBase> stream;
+ while ((stream = mQueuedStreams.PopFront())) {
+ LOG(("Close remaining stream in queue:%p", stream.get()));
+ stream->SetQueued(false);
+ stream->Close(NS_ERROR_ABORT);
+ }
+ mWebTransportStreams.Clear();
+}
+
+Http3Session::~Http3Session() {
+ LOG3(("Http3Session::~Http3Session %p", this));
+
+ EchOutcomeTelemetry();
+ Telemetry::Accumulate(Telemetry::HTTP3_REQUEST_PER_CONN, mTransactionCount);
+ Telemetry::Accumulate(Telemetry::HTTP3_BLOCKED_BY_STREAM_LIMIT_PER_CONN,
+ mBlockedByStreamLimitCount);
+ Telemetry::Accumulate(Telemetry::HTTP3_TRANS_BLOCKED_BY_STREAM_LIMIT_PER_CONN,
+ mTransactionsBlockedByStreamLimitCount);
+
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_TRANS_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_CONN,
+ mTransactionsSenderBlockedByFlowControlCount);
+
+ if (mThroughCaptivePortal) {
+ if (mTotalBytesRead || mTotalBytesWritten) {
+ auto total =
+ Clamp<uint32_t>((mTotalBytesRead >> 10) + (mTotalBytesWritten >> 10),
+ 0, std::numeric_limits<uint32_t>::max());
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_DATA_TRANSFERRED_CAPTIVE_PORTAL,
+ total);
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_HTTP_CONNECTIONS_CAPTIVE_PORTAL, 1);
+ }
+
+ Shutdown();
+}
+
+// This function may return a socket error.
+// It will not return an error if socket error is
+// NS_BASE_STREAM_WOULD_BLOCK.
+// A caller of this function will close the Http3 connection
+// in case of a error.
+// The only callers is:
+// HttpConnectionUDP::RecvData ->
+// Http3Session::RecvData
+void Http3Session::ProcessInput(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mUdpConn);
+
+ LOG(("Http3Session::ProcessInput writer=%p [this=%p state=%d]",
+ mUdpConn.get(), this, mState));
+
+ while (true) {
+ nsTArray<uint8_t> data;
+ NetAddr addr{};
+ // RecvWithAddr actually does not return an error.
+ nsresult rv = socket->RecvWithAddr(&addr, data);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ if (NS_FAILED(rv) || data.IsEmpty()) {
+ break;
+ }
+ rv = mHttp3Connection->ProcessInput(addr, data);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ LOG(("Http3Session::ProcessInput received=%zu", data.Length()));
+ mTotalBytesRead += data.Length();
+ }
+}
+
+nsresult Http3Session::ProcessTransactionRead(uint64_t stream_id) {
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(stream_id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessTransactionRead - stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ stream_id, this));
+ return NS_OK;
+ }
+
+ return ProcessTransactionRead(stream);
+}
+
+nsresult Http3Session::ProcessTransactionRead(Http3StreamBase* stream) {
+ nsresult rv = stream->WriteSegments();
+
+ if (ASpdySession::SoftStreamError(rv) || stream->Done()) {
+ LOG3(
+ ("Http3Session::ProcessSingleTransactionRead session=%p stream=%p "
+ "0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n",
+ this, stream, stream->StreamId(), static_cast<uint32_t>(rv),
+ stream->Done()));
+ CloseStream(stream,
+ (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult Http3Session::ProcessEvents() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Session::ProcessEvents [this=%p]", this));
+
+ // We need an array to pick up header data or a resumption token.
+ nsTArray<uint8_t> data;
+ Http3Event event{};
+ event.tag = Http3Event::Tag::NoEvent;
+
+ nsresult rv = mHttp3Connection->GetEvent(&event, data);
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ while (event.tag != Http3Event::Tag::NoEvent) {
+ switch (event.tag) {
+ case Http3Event::Tag::HeaderReady: {
+ MOZ_ASSERT(mState == CONNECTED);
+ LOG(("Http3Session::ProcessEvents - HeaderReady"));
+ uint64_t id = event.header_ready.stream_id;
+
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - HeaderReady - stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ id, this));
+ break;
+ }
+
+ MOZ_RELEASE_ASSERT(stream->GetHttp3Stream(),
+ "This must be a Http3Stream");
+
+ stream->SetResponseHeaders(data, event.header_ready.fin,
+ event.header_ready.interim);
+
+ rv = ProcessTransactionRead(stream);
+
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ mUdpConn->NotifyDataRead();
+ break;
+ }
+ case Http3Event::Tag::DataReadable: {
+ MOZ_ASSERT(mState == CONNECTED);
+ LOG(("Http3Session::ProcessEvents - DataReadable"));
+ uint64_t id = event.data_readable.stream_id;
+
+ nsresult rv = ProcessTransactionRead(id);
+
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ break;
+ }
+ case Http3Event::Tag::DataWritable: {
+ MOZ_ASSERT(CanSendData());
+ LOG(("Http3Session::ProcessEvents - DataWritable"));
+
+ RefPtr<Http3StreamBase> stream =
+ mStreamIdHash.Get(event.data_writable.stream_id);
+
+ if (stream) {
+ StreamReadyToWrite(stream);
+ }
+ } break;
+ case Http3Event::Tag::Reset:
+ LOG(("Http3Session::ProcessEvents %p - Reset", this));
+ ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error,
+ RESET);
+ break;
+ case Http3Event::Tag::StopSending:
+ LOG(
+ ("Http3Session::ProcessEvents %p - StopSeniding with error "
+ "0x%" PRIx64,
+ this, event.stop_sending.error));
+ if (event.stop_sending.error == HTTP3_APP_ERROR_NO_ERROR) {
+ RefPtr<Http3StreamBase> stream =
+ mStreamIdHash.Get(event.data_writable.stream_id);
+ if (stream) {
+ RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream();
+ MOZ_RELEASE_ASSERT(httpStream, "This must be a Http3Stream");
+ httpStream->StopSending();
+ }
+ } else {
+ ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error,
+ STOP_SENDING);
+ }
+ break;
+ case Http3Event::Tag::PushPromise:
+ LOG(("Http3Session::ProcessEvents - PushPromise"));
+ break;
+ case Http3Event::Tag::PushHeaderReady:
+ LOG(("Http3Session::ProcessEvents - PushHeaderReady"));
+ break;
+ case Http3Event::Tag::PushDataReadable:
+ LOG(("Http3Session::ProcessEvents - PushDataReadable"));
+ break;
+ case Http3Event::Tag::PushCanceled:
+ LOG(("Http3Session::ProcessEvents - PushCanceled"));
+ break;
+ case Http3Event::Tag::RequestsCreatable:
+ LOG(("Http3Session::ProcessEvents - StreamCreatable"));
+ ProcessPending();
+ break;
+ case Http3Event::Tag::AuthenticationNeeded:
+ LOG(("Http3Session::ProcessEvents - AuthenticationNeeded %d",
+ mAuthenticationStarted));
+ if (!mAuthenticationStarted) {
+ mAuthenticationStarted = true;
+ LOG(("Http3Session::ProcessEvents - AuthenticationNeeded called"));
+ CallCertVerification(Nothing());
+ }
+ break;
+ case Http3Event::Tag::ZeroRttRejected:
+ LOG(("Http3Session::ProcessEvents - ZeroRttRejected"));
+ if (mState == ZERORTT) {
+ mState = INITIALIZING;
+ mTransactionCount = 0;
+ Finish0Rtt(true);
+ ZeroRttTelemetry(ZeroRttOutcome::USED_REJECTED);
+ }
+ break;
+ case Http3Event::Tag::ResumptionToken: {
+ LOG(("Http3Session::ProcessEvents - ResumptionToken"));
+ if (StaticPrefs::network_http_http3_enable_0rtt() && !data.IsEmpty()) {
+ LOG(("Got a resumption token"));
+ nsAutoCString peerId;
+ mSocketControl->GetPeerId(peerId);
+ if (NS_FAILED(SSLTokensCache::Put(
+ peerId, data.Elements(), data.Length(), mSocketControl,
+ PR_Now() + event.resumption_token.expire_in))) {
+ LOG(("Adding resumption token failed"));
+ }
+ }
+ } break;
+ case Http3Event::Tag::ConnectionConnected: {
+ LOG(("Http3Session::ProcessEvents - ConnectionConnected"));
+ bool was0RTT = mState == ZERORTT;
+ mState = CONNECTED;
+ SetSecInfo();
+ mSocketControl->HandshakeCompleted();
+ if (was0RTT) {
+ Finish0Rtt(false);
+ ZeroRttTelemetry(ZeroRttOutcome::USED_SUCCEEDED);
+ }
+
+ OnTransportStatus(mSocketTransport, NS_NET_STATUS_CONNECTED_TO, 0);
+ // Also send the NS_NET_STATUS_TLS_HANDSHAKE_ENDED event.
+ OnTransportStatus(mSocketTransport, NS_NET_STATUS_TLS_HANDSHAKE_ENDED,
+ 0);
+
+ ReportHttp3Connection();
+ // Maybe call ResumeSend:
+ // In case ZeroRtt has been used and it has been rejected, 2 events will
+ // be received: ZeroRttRejected and ConnectionConnected. ZeroRttRejected
+ // that will put all transaction into mReadyForWrite queue and it will
+ // call MaybeResumeSend, but that will not have an effect because the
+ // connection is ont in CONNECTED state. When ConnectionConnected event
+ // is received call MaybeResumeSend to trigger reads for the
+ // zero-rtt-rejected transactions.
+ MaybeResumeSend();
+ } break;
+ case Http3Event::Tag::GoawayReceived:
+ LOG(("Http3Session::ProcessEvents - GoawayReceived"));
+ mUdpConn->SetCloseReason(ConnectionCloseReason::GO_AWAY);
+ mGoawayReceived = true;
+ break;
+ case Http3Event::Tag::ConnectionClosing:
+ LOG(("Http3Session::ProcessEvents - ConnectionClosing"));
+ if (NS_SUCCEEDED(mError) && !IsClosing()) {
+ mError = NS_ERROR_NET_HTTP3_PROTOCOL_ERROR;
+ CloseConnectionTelemetry(event.connection_closing.error, true);
+
+ auto isStatelessResetOrNoError = [](CloseError& aError) -> bool {
+ if (aError.tag == CloseError::Tag::TransportInternalErrorOther &&
+ aError.transport_internal_error_other._0 ==
+ TRANSPORT_ERROR_STATELESS_RESET) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::TransportError &&
+ aError.transport_error._0 == 0) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::PeerError &&
+ aError.peer_error._0 == 0) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::AppError &&
+ aError.app_error._0 == HTTP3_APP_ERROR_NO_ERROR) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::PeerAppError &&
+ aError.peer_app_error._0 == HTTP3_APP_ERROR_NO_ERROR) {
+ return true;
+ }
+ return false;
+ };
+ if (isStatelessResetOrNoError(event.connection_closing.error)) {
+ mError = NS_ERROR_NET_RESET;
+ }
+ if (event.connection_closing.error.tag == CloseError::Tag::EchRetry) {
+ mSocketControl->SetRetryEchConfig(Substring(
+ reinterpret_cast<const char*>(data.Elements()), data.Length()));
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH);
+ }
+ }
+ return mError;
+ break;
+ case Http3Event::Tag::ConnectionClosed:
+ LOG(("Http3Session::ProcessEvents - ConnectionClosed"));
+ if (NS_SUCCEEDED(mError)) {
+ mError = NS_ERROR_NET_TIMEOUT;
+ mUdpConn->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
+ CloseConnectionTelemetry(event.connection_closed.error, false);
+ }
+ mIsClosedByNeqo = true;
+ if (event.connection_closed.error.tag == CloseError::Tag::EchRetry) {
+ mSocketControl->SetRetryEchConfig(Substring(
+ reinterpret_cast<const char*>(data.Elements()), data.Length()));
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH);
+ }
+ LOG(("Http3Session::ProcessEvents - ConnectionClosed error=%" PRIx32,
+ static_cast<uint32_t>(mError)));
+ // We need to return here and let HttpConnectionUDP close the session.
+ return mError;
+ break;
+ case Http3Event::Tag::EchFallbackAuthenticationNeeded: {
+ nsCString echPublicName(reinterpret_cast<const char*>(data.Elements()),
+ data.Length());
+ LOG(
+ ("Http3Session::ProcessEvents - EchFallbackAuthenticationNeeded "
+ "echPublicName=%s",
+ echPublicName.get()));
+ if (!mAuthenticationStarted) {
+ mAuthenticationStarted = true;
+ CallCertVerification(Some(echPublicName));
+ }
+ } break;
+ case Http3Event::Tag::WebTransport: {
+ switch (event.web_transport._0.tag) {
+ case WebTransportEventExternal::Tag::Negotiated:
+ LOG(("Http3Session::ProcessEvents - WebTransport %d",
+ event.web_transport._0.negotiated._0));
+ MOZ_ASSERT(mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING);
+ mWebTransportNegotiationStatus =
+ event.web_transport._0.negotiated._0
+ ? WebTransportNegotiation::SUCCEEDED
+ : WebTransportNegotiation::FAILED;
+ WebTransportNegotiationDone();
+ break;
+ case WebTransportEventExternal::Tag::Session: {
+ MOZ_ASSERT(mState == CONNECTED);
+
+ uint64_t id = event.web_transport._0.session._0;
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport Session "
+ " sessionId=0x%" PRIx64,
+ id));
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport Session - "
+ "stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ id, this));
+ break;
+ }
+
+ MOZ_RELEASE_ASSERT(stream->GetHttp3WebTransportSession(),
+ "It must be a WebTransport session");
+ stream->SetResponseHeaders(data, false, false);
+
+ rv = stream->WriteSegments();
+
+ if (ASpdySession::SoftStreamError(rv) || stream->Done()) {
+ LOG3(
+ ("Http3Session::ProcessSingleTransactionRead session=%p "
+ "stream=%p "
+ "0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n",
+ this, stream.get(), stream->StreamId(),
+ static_cast<uint32_t>(rv), stream->Done()));
+ // We need to keep the transaction, so we can use it to remove the
+ // stream from mStreamTransactionHash.
+ nsAHttpTransaction* trans = stream->Transaction();
+ if (mStreamTransactionHash.Contains(trans)) {
+ CloseStream(stream, (rv == NS_BINDING_RETARGETED)
+ ? NS_BINDING_RETARGETED
+ : NS_OK);
+ mStreamTransactionHash.Remove(trans);
+ } else {
+ stream->GetHttp3WebTransportSession()->TransactionIsDone(
+ (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED
+ : NS_OK);
+ }
+ break;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ } break;
+ case WebTransportEventExternal::Tag::SessionClosed: {
+ uint64_t id = event.web_transport._0.session_closed.stream_id;
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport SessionClosed "
+ " sessionId=0x%" PRIx64,
+ id));
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport SessionClosed - "
+ "stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ id, this));
+ break;
+ }
+
+ RefPtr<Http3WebTransportSession> wt =
+ stream->GetHttp3WebTransportSession();
+ MOZ_RELEASE_ASSERT(wt, "It must be a WebTransport session");
+
+ bool cleanly = false;
+
+ // TODO we do not handle the case when a WebTransport session stream
+ // is closed before headers are sent.
+ SessionCloseReasonExternal& reasonExternal =
+ event.web_transport._0.session_closed.reason;
+ uint32_t status = 0;
+ nsCString reason = ""_ns;
+ if (reasonExternal.tag == SessionCloseReasonExternal::Tag::Error) {
+ status = reasonExternal.error._0;
+ } else if (reasonExternal.tag ==
+ SessionCloseReasonExternal::Tag::Status) {
+ status = reasonExternal.status._0;
+ cleanly = true;
+ } else {
+ status = reasonExternal.clean._0;
+ reason.Assign(reinterpret_cast<const char*>(data.Elements()),
+ data.Length());
+ cleanly = true;
+ }
+ LOG(("reason.tag=%u err=%u data=%s\n",
+ static_cast<uint32_t>(reasonExternal.tag), status,
+ reason.get()));
+ wt->OnSessionClosed(cleanly, status, reason);
+
+ } break;
+ case WebTransportEventExternal::Tag::NewStream: {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport NewStream "
+ "streamId=0x%" PRIx64 " sessionId=0x%" PRIx64,
+ event.web_transport._0.new_stream.stream_id,
+ event.web_transport._0.new_stream.session_id));
+ uint64_t sessionId = event.web_transport._0.new_stream.session_id;
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport NewStream - "
+ "session not found "
+ "sessionId=0x%" PRIx64 " [this=%p].",
+ sessionId, this));
+ break;
+ }
+
+ RefPtr<Http3WebTransportSession> wt =
+ stream->GetHttp3WebTransportSession();
+ if (!wt) {
+ break;
+ }
+
+ RefPtr<Http3WebTransportStream> wtStream =
+ wt->OnIncomingWebTransportStream(
+ event.web_transport._0.new_stream.stream_type,
+ event.web_transport._0.new_stream.stream_id);
+ if (!wtStream) {
+ break;
+ }
+
+ // WebTransportStream is managed by Http3Session now.
+ mWebTransportStreams.AppendElement(wtStream);
+ mWebTransportStreamToSessionMap.InsertOrUpdate(wtStream->StreamId(),
+ wt->StreamId());
+ mStreamIdHash.InsertOrUpdate(wtStream->StreamId(),
+ std::move(wtStream));
+ } break;
+ case WebTransportEventExternal::Tag::Datagram:
+ LOG(
+ ("Http3Session::ProcessEvents - "
+ "WebTransportEventExternal::Tag::Datagram [this=%p]",
+ this));
+ uint64_t sessionId = event.web_transport._0.datagram.session_id;
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport Datagram - "
+ "session not found "
+ "sessionId=0x%" PRIx64 " [this=%p].",
+ sessionId, this));
+ break;
+ }
+
+ RefPtr<Http3WebTransportSession> wt =
+ stream->GetHttp3WebTransportSession();
+ if (!wt) {
+ break;
+ }
+
+ wt->OnDatagramReceived(std::move(data));
+ break;
+ }
+ } break;
+ default:
+ break;
+ }
+ // Delete previous content of data
+ data.TruncateLength(0);
+ rv = mHttp3Connection->GetEvent(&event, data);
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+
+ return NS_OK;
+} // namespace net
+
+// This function may return a socket error.
+// It will not return an error if socket error is
+// NS_BASE_STREAM_WOULD_BLOCK.
+// A Caller of this function will close the Http3 connection
+// if this function returns an error.
+// Callers are:
+// 1) HttpConnectionUDP::OnQuicTimeoutExpired
+// 2) HttpConnectionUDP::SendData ->
+// Http3Session::SendData
+nsresult Http3Session::ProcessOutput(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mUdpConn);
+
+ LOG(("Http3Session::ProcessOutput reader=%p, [this=%p]", mUdpConn.get(),
+ this));
+
+ mSocket = socket;
+ nsresult rv = mHttp3Connection->ProcessOutputAndSend(
+ this,
+ [](void* aContext, uint16_t aFamily, const uint8_t* aAddr, uint16_t aPort,
+ const uint8_t* aData, uint32_t aLength) {
+ Http3Session* self = (Http3Session*)aContext;
+
+ uint32_t written = 0;
+ NetAddr addr;
+ if (NS_FAILED(RawBytesToNetAddr(aFamily, aAddr, aPort, &addr))) {
+ return NS_OK;
+ }
+
+ LOG3(
+ ("Http3Session::ProcessOutput sending packet with %u bytes to %s "
+ "port=%d [this=%p].",
+ aLength, addr.ToString().get(), aPort, self));
+
+ nsresult rv =
+ self->mSocket->SendWithAddress(&addr, aData, aLength, &written);
+
+ LOG(("Http3Session::ProcessOutput sending packet rv=%d osError=%d",
+ static_cast<int32_t>(rv), NS_FAILED(rv) ? PR_GetOSError() : 0));
+ if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+ self->mSocketError = rv;
+ // If there was an error that is not NS_BASE_STREAM_WOULD_BLOCK
+ // return from here. We do not need to set a timer, because we
+ // will close the connection.
+ return rv;
+ }
+ self->mTotalBytesWritten += aLength;
+ self->mLastWriteTime = PR_IntervalNow();
+ return NS_OK;
+ },
+ [](void* aContext, uint64_t timeout) {
+ Http3Session* self = (Http3Session*)aContext;
+ self->SetupTimer(timeout);
+ });
+ mSocket = nullptr;
+ return rv;
+}
+
+// This is only called when timer expires.
+// It is called by HttpConnectionUDP::OnQuicTimeout.
+// If tihs function returns an error OnQuicTimeout will handle the error
+// properly and close the connection.
+nsresult Http3Session::ProcessOutputAndEvents(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // ProcessOutput could fire another timer. Need to unset the flag before that.
+ mTimerActive = false;
+
+ MOZ_ASSERT(mTimerShouldTrigger);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_TIMER_DELAYED,
+ mTimerShouldTrigger, TimeStamp::Now());
+
+ mTimerShouldTrigger = TimeStamp();
+
+ nsresult rv = SendData(socket);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ProcessEvents();
+}
+
+void Http3Session::SetupTimer(uint64_t aTimeout) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // UINT64_MAX indicated a no-op from neqo, which only happens when a
+ // connection is in or going to be Closed state.
+ if (aTimeout == UINT64_MAX) {
+ return;
+ }
+
+ LOG3(
+ ("Http3Session::SetupTimer to %" PRIu64 "ms [this=%p].", aTimeout, this));
+
+ // Remember the time when the timer should trigger.
+ mTimerShouldTrigger =
+ TimeStamp::Now() + TimeDuration::FromMilliseconds(aTimeout);
+
+ if (mTimerActive && mTimer) {
+ LOG(
+ (" -- Previous timer has not fired. Update the delay instead of "
+ "re-initializing the timer"));
+ mTimer->SetDelay(aTimeout);
+ return;
+ }
+
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer),
+ [conn = RefPtr{mUdpConn}](nsITimer*) { conn->OnQuicTimeoutExpired(); },
+ aTimeout, nsITimer::TYPE_ONE_SHOT,
+ "net::HttpConnectionUDP::OnQuicTimeout");
+
+ mTimerActive = true;
+
+ if (NS_FAILED(rv)) {
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("net::HttpConnectionUDP::OnQuicTimeoutExpired",
+ mUdpConn, &HttpConnectionUDP::OnQuicTimeoutExpired));
+ }
+}
+
+bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction,
+ int32_t aPriority,
+ nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
+
+ if (!mConnection) {
+ // Get the connection from the first transaction.
+ mConnection = aHttpTransaction->Connection();
+ }
+
+ if (IsClosing()) {
+ LOG3(
+ ("Http3Session::AddStream %p atrans=%p trans=%p session unusable - "
+ "resched.\n",
+ this, aHttpTransaction, trans));
+ aHttpTransaction->SetConnection(nullptr);
+ nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3Session::AddStream %p atrans=%p trans=%p failed to initiate "
+ "transaction (0x%" PRIx32 ").\n",
+ this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
+ }
+ return true;
+ }
+
+ aHttpTransaction->SetConnection(this);
+ aHttpTransaction->OnActivated();
+ // reset the read timers to wash away any idle time
+ mLastWriteTime = PR_IntervalNow();
+
+ ClassOfService cos;
+ if (trans) {
+ cos = trans->GetClassOfService();
+ }
+
+ Http3StreamBase* stream = nullptr;
+
+ if (trans && trans->IsForWebTransport()) {
+ LOG3(("Http3Session::AddStream new WeTransport session %p atrans=%p.\n",
+ this, aHttpTransaction));
+ stream = new Http3WebTransportSession(aHttpTransaction, this);
+ mHasWebTransportSession = true;
+ } else {
+ LOG3(("Http3Session::AddStream %p atrans=%p.\n", this, aHttpTransaction));
+ stream = new Http3Stream(aHttpTransaction, this, cos, mCurrentBrowserId);
+ }
+
+ mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, RefPtr{stream});
+
+ if (mState == ZERORTT) {
+ if (!stream->Do0RTT()) {
+ LOG(("Http3Session %p will not get early data from Http3Stream %p", this,
+ stream));
+ if (!mCannotDo0RTTStreams.Contains(stream)) {
+ mCannotDo0RTTStreams.AppendElement(stream);
+ }
+ if ((mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING) &&
+ (trans && trans->IsForWebTransport())) {
+ LOG(("waiting for negotiation"));
+ mWaitingForWebTransportNegotiation.AppendElement(stream);
+ }
+ return true;
+ }
+ m0RTTStreams.AppendElement(stream);
+ }
+
+ if ((mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING) &&
+ (trans && trans->IsForWebTransport())) {
+ LOG(("waiting for negotiation"));
+ mWaitingForWebTransportNegotiation.AppendElement(stream);
+ return true;
+ }
+
+ if (!mFirstHttpTransaction && !IsConnected()) {
+ mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
+ LOG3(("Http3Session::AddStream first session=%p trans=%p ", this,
+ mFirstHttpTransaction.get()));
+ }
+ StreamReadyToWrite(stream);
+
+ return true;
+}
+
+bool Http3Session::CanReuse() {
+ // TODO: we assume "pooling" is disabled here, so we don't allow this session
+ // to be reused. "pooling" will be implemented in bug 1815735.
+ return CanSendData() && !(mGoawayReceived || mShouldClose) &&
+ !mHasWebTransportSession;
+}
+
+void Http3Session::QueueStream(Http3StreamBase* stream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!stream->Queued());
+
+ LOG3(("Http3Session::QueueStream %p stream %p queued.", this, stream));
+
+ stream->SetQueued(true);
+ mQueuedStreams.Push(stream);
+}
+
+void Http3Session::ProcessPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<Http3StreamBase> stream;
+ while ((stream = mQueuedStreams.PopFront())) {
+ LOG3(("Http3Session::ProcessPending %p stream %p woken from queue.", this,
+ stream.get()));
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ mReadyForWrite.Push(stream);
+ }
+ MaybeResumeSend();
+}
+
+static void RemoveStreamFromQueue(Http3StreamBase* aStream,
+ nsRefPtrDeque<Http3StreamBase>& queue) {
+ size_t size = queue.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ RefPtr<Http3StreamBase> stream = queue.PopFront();
+ if (stream != aStream) {
+ queue.Push(stream);
+ }
+ }
+}
+
+void Http3Session::RemoveStreamFromQueues(Http3StreamBase* aStream) {
+ RemoveStreamFromQueue(aStream, mReadyForWrite);
+ RemoveStreamFromQueue(aStream, mQueuedStreams);
+ mSlowConsumersReadyForRead.RemoveElement(aStream);
+}
+
+// This is called by Http3Stream::OnReadSegment.
+// ProcessOutput will be called in Http3Session::ReadSegment that
+// calls Http3Stream::OnReadSegment.
+nsresult Http3Session::TryActivating(
+ const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aAuthorityHeader, const nsACString& aPath,
+ const nsACString& aHeaders, uint64_t* aStreamId, Http3StreamBase* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(*aStreamId == UINT64_MAX);
+
+ LOG(("Http3Session::TryActivating [stream=%p, this=%p state=%d]", aStream,
+ this, mState));
+
+ if (IsClosing()) {
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aStream->Queued()) {
+ LOG3(("Http3Session::TryActivating %p stream=%p already queued.\n", this,
+ aStream));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mState == ZERORTT) {
+ if (!aStream->Do0RTT()) {
+ MOZ_ASSERT(!mCannotDo0RTTStreams.Contains(aStream));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+
+ nsresult rv = NS_OK;
+ RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream();
+ if (httpStream) {
+ rv = mHttp3Connection->Fetch(
+ aMethod, aScheme, aAuthorityHeader, aPath, aHeaders, aStreamId,
+ httpStream->PriorityUrgency(), httpStream->PriorityIncremental());
+ } else {
+ MOZ_RELEASE_ASSERT(aStream->GetHttp3WebTransportSession(),
+ "It must be a WebTransport session");
+ // Don't call CreateWebTransport if we are still waiting for the negotiation
+ // result.
+ if (mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING) {
+ if (!mWaitingForWebTransportNegotiation.Contains(aStream)) {
+ mWaitingForWebTransportNegotiation.AppendElement(aStream);
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ rv = mHttp3Connection->CreateWebTransport(aAuthorityHeader, aPath, aHeaders,
+ aStreamId);
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::TryActivating returns error=0x%" PRIx32 "[stream=%p, "
+ "this=%p]",
+ static_cast<uint32_t>(rv), aStream, this));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3Session::TryActivating %p stream=%p no room for more "
+ "concurrent streams\n",
+ this, aStream));
+ mTransactionsBlockedByStreamLimitCount++;
+ if (mQueuedStreams.GetSize() == 0) {
+ mBlockedByStreamLimitCount++;
+ }
+ QueueStream(aStream);
+ return rv;
+ }
+ // Ignore this error. This may happen if some events are not handled yet.
+ // TODO we may try to add an assertion here.
+ return NS_OK;
+ }
+
+ LOG(("Http3Session::TryActivating streamId=0x%" PRIx64
+ " for stream=%p [this=%p].",
+ *aStreamId, aStream, this));
+
+ MOZ_ASSERT(*aStreamId != UINT64_MAX);
+
+ if (mTransactionCount > 0 && mStreamIdHash.IsEmpty()) {
+ MOZ_ASSERT(mConnectionIdleStart);
+ MOZ_ASSERT(mFirstStreamIdReuseIdleConnection.isNothing());
+
+ mConnectionIdleEnd = TimeStamp::Now();
+ mFirstStreamIdReuseIdleConnection = Some(*aStreamId);
+ }
+ mStreamIdHash.InsertOrUpdate(*aStreamId, RefPtr{aStream});
+ mTransactionCount++;
+
+ return NS_OK;
+}
+
+// This is called by Http3WebTransportStream::OnReadSegment.
+// TODO: this function is almost the same as TryActivating().
+// We should try to reduce the duplicate code.
+nsresult Http3Session::TryActivatingWebTransportStream(
+ uint64_t* aStreamId, Http3StreamBase* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(*aStreamId == UINT64_MAX);
+
+ LOG(
+ ("Http3Session::TryActivatingWebTransportStream [stream=%p, this=%p "
+ "state=%d]",
+ aStream, this, mState));
+
+ if (IsClosing()) {
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aStream->Queued()) {
+ LOG3(
+ ("Http3Session::TryActivatingWebTransportStream %p stream=%p already "
+ "queued.\n",
+ this, aStream));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv = NS_OK;
+ RefPtr<Http3WebTransportStream> wtStream =
+ aStream->GetHttp3WebTransportStream();
+ MOZ_RELEASE_ASSERT(wtStream, "It must be a WebTransport stream");
+ rv = mHttp3Connection->CreateWebTransportStream(
+ wtStream->SessionId(), wtStream->StreamType(), aStreamId);
+
+ if (NS_FAILED(rv)) {
+ LOG((
+ "Http3Session::TryActivatingWebTransportStream returns error=0x%" PRIx32
+ "[stream=%p, "
+ "this=%p]",
+ static_cast<uint32_t>(rv), aStream, this));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3Session::TryActivatingWebTransportStream %p stream=%p no room "
+ "for more "
+ "concurrent streams\n",
+ this, aStream));
+ QueueStream(aStream);
+ return rv;
+ }
+
+ return rv;
+ }
+
+ LOG(("Http3Session::TryActivatingWebTransportStream streamId=0x%" PRIx64
+ " for stream=%p [this=%p].",
+ *aStreamId, aStream, this));
+
+ MOZ_ASSERT(*aStreamId != UINT64_MAX);
+
+ RefPtr<Http3StreamBase> session = mStreamIdHash.Get(wtStream->SessionId());
+ MOZ_ASSERT(session);
+ Http3WebTransportSession* wtSession = session->GetHttp3WebTransportSession();
+ MOZ_ASSERT(wtSession);
+
+ wtSession->RemoveWebTransportStream(wtStream);
+
+ // WebTransportStream is managed by Http3Session now.
+ mWebTransportStreams.AppendElement(wtStream);
+ mWebTransportStreamToSessionMap.InsertOrUpdate(*aStreamId,
+ session->StreamId());
+ mStreamIdHash.InsertOrUpdate(*aStreamId, std::move(wtStream));
+ return NS_OK;
+}
+
+// This is only called by Http3Stream::OnReadSegment.
+// ProcessOutput will be called in Http3Session::ReadSegment that
+// calls Http3Stream::OnReadSegment.
+void Http3Session::CloseSendingSide(uint64_t aStreamId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mHttp3Connection->CloseStream(aStreamId);
+}
+
+// This is only called by Http3Stream::OnReadSegment.
+// ProcessOutput will be called in Http3Session::ReadSegment that
+// calls Http3Stream::OnReadSegment.
+nsresult Http3Session::SendRequestBody(uint64_t aStreamId, const char* buf,
+ uint32_t count, uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv = mHttp3Connection->SendRequestBody(
+ aStreamId, (const uint8_t*)buf, count, countRead);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mTransactionsSenderBlockedByFlowControlCount++;
+ } else if (NS_FAILED(rv)) {
+ // Ignore this error. This may happen if some events are not handled yet.
+ // TODO we may try to add an assertion here.
+ // We will pretend that sender is blocked, that will cause the caller to
+ // stop trying.
+ *countRead = 0;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ MOZ_ASSERT((*countRead != 0) || NS_FAILED(rv));
+ return rv;
+}
+
+void Http3Session::ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError,
+ ResetType aType) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t sessionId = 0;
+ if (mWebTransportStreamToSessionMap.Get(aStreamId, &sessionId)) {
+ uint8_t wtError = Http3ErrorToWebTransportError(aError);
+ nsresult rv = GetNSResultFromWebTransportError(wtError);
+
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId);
+ if (stream) {
+ if (aType == RESET) {
+ stream->SetRecvdReset();
+ }
+ RefPtr<Http3WebTransportStream> wtStream =
+ stream->GetHttp3WebTransportStream();
+ if (wtStream) {
+ CloseWebTransportStream(wtStream, rv);
+ }
+ }
+
+ RefPtr<Http3StreamBase> session = mStreamIdHash.Get(sessionId);
+ if (session) {
+ Http3WebTransportSession* wtSession =
+ session->GetHttp3WebTransportSession();
+ MOZ_ASSERT(wtSession);
+ if (wtSession) {
+ if (aType == RESET) {
+ wtSession->OnStreamReset(aStreamId, rv);
+ } else {
+ wtSession->OnStreamStopSending(aStreamId, rv);
+ }
+ }
+ }
+ return;
+ }
+
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId);
+ if (!stream) {
+ return;
+ }
+
+ RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream();
+ if (!httpStream) {
+ return;
+ }
+
+ // We only handle some of Http3 error as epecial, the rest are just equivalent
+ // to cancel.
+ if (aError == HTTP3_APP_ERROR_VERSION_FALLBACK) {
+ // We will restart the request and the alt-svc will be removed
+ // automatically.
+ // Also disable http3 we want http1.1.
+ httpStream->Transaction()->DisableHttp3(false);
+ httpStream->Transaction()->DisableSpdy();
+ CloseStream(stream, NS_ERROR_NET_RESET);
+ } else if (aError == HTTP3_APP_ERROR_REQUEST_REJECTED) {
+ // This request was rejected because server is probably busy or going away.
+ // We can restart the request using alt-svc. Without calling
+ // DoNotRemoveAltSvc the alt-svc route will be removed.
+ httpStream->Transaction()->DoNotRemoveAltSvc();
+ CloseStream(stream, NS_ERROR_NET_RESET);
+ } else {
+ if (httpStream->RecvdData()) {
+ CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else {
+ CloseStream(stream, NS_ERROR_NET_INTERRUPT);
+ }
+ }
+}
+
+void Http3Session::SetConnection(nsAHttpConnection* aConn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mConnection = aConn;
+}
+
+void Http3Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) {
+ *aOut = nullptr;
+}
+
+// TODO
+void Http3Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+ int64_t aProgress) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if ((aStatus == NS_NET_STATUS_CONNECTED_TO) && !IsConnected()) {
+ // We should ignore the event. This is sent by the nsSocketTranpsort
+ // and it does not mean that HTTP3 session is connected.
+ // We will use this event to mark start of TLS handshake
+ aStatus = NS_NET_STATUS_TLS_HANDSHAKE_STARTING;
+ }
+
+ switch (aStatus) {
+ // These should appear only once, deliver to the first
+ // transaction on the session.
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: {
+ if (!mFirstHttpTransaction) {
+ // if we still do not have a HttpTransaction store timings info in
+ // a HttpConnection.
+ // If some error occur it can happen that we do not have a connection.
+ if (mConnection) {
+ RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection();
+ conn->SetEvent(aStatus);
+ }
+ } else {
+ mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus,
+ aProgress);
+ }
+
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
+ mFirstHttpTransaction = nullptr;
+ }
+ break;
+ }
+
+ default:
+ // The other transport events are ignored here because there is no good
+ // way to map them to the right transaction in HTTP3. Instead, the events
+ // are generated again from the HTTP3 code and passed directly to the
+ // correct transaction.
+
+ // NS_NET_STATUS_SENDING_TO:
+ // This is generated by the socket transport when (part) of
+ // a transaction is written out
+ //
+ // There is no good way to map it to the right transaction in HTTP3,
+ // so it is ignored here and generated separately when the request
+ // is sent from Http3Stream.
+
+ // NS_NET_STATUS_WAITING_FOR:
+ // Created by nsHttpConnection when the request has been totally sent.
+ // There is no good way to map it to the right transaction in HTTP3,
+ // so it is ignored here and generated separately when the same
+ // condition is complete in Http3Stream when there is no more
+ // request body left to be transmitted.
+
+ // NS_NET_STATUS_RECEIVING_FROM
+ // Generated in Http3Stream whenever the stream reads data.
+
+ break;
+ }
+}
+
+bool Http3Session::IsDone() { return mState == CLOSED; }
+
+nsresult Http3Session::Status() {
+ MOZ_ASSERT(false, "Http3Session::Status()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+uint32_t Http3Session::Caps() {
+ MOZ_ASSERT(false, "Http3Session::Caps()");
+ return 0;
+}
+
+nsresult Http3Session::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ MOZ_ASSERT(false, "Http3Session::ReadSegments()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult Http3Session::SendData(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Session::SendData [this=%p]", this));
+
+ // 1) go through all streams/transactions that are ready to write and
+ // write their data into quic streams (no network write yet).
+ // 2) call ProcessOutput that will loop until all available packets are
+ // written to a socket or the socket returns an error code.
+ // 3) if we still have streams ready to write call ResumeSend()(we may
+ // still have such streams because on an stream error we return earlier
+ // to let the error be handled).
+ // 4)
+
+ nsresult rv = NS_OK;
+ RefPtr<Http3StreamBase> stream;
+
+ // Step 1)
+ while (CanSendData() && (stream = mReadyForWrite.PopFront())) {
+ LOG(("Http3Session::SendData call ReadSegments from stream=%p [this=%p]",
+ stream.get(), this));
+
+ rv = stream->ReadSegments();
+
+ // on stream error we return earlier to let the error be handled.
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session::SendData %p returns error code 0x%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // Just in case!
+ rv = NS_OK;
+ } else if (ASpdySession::SoftStreamError(rv)) {
+ CloseStream(stream, rv);
+ LOG3(("Http3Session::SendData %p soft error override\n", this));
+ rv = NS_OK;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // Step 2:
+ // Call actual network write.
+ rv = ProcessOutput(socket);
+ }
+
+ // Step 3:
+ MaybeResumeSend();
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+
+ // Let the connection know we sent some app data successfully.
+ if (stream && NS_SUCCEEDED(rv)) {
+ mUdpConn->NotifyDataWrite();
+ }
+
+ return rv;
+}
+
+void Http3Session::StreamReadyToWrite(Http3StreamBase* aStream) {
+ MOZ_ASSERT(aStream);
+ mReadyForWrite.Push(aStream);
+ if (CanSendData() && mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http3Session::MaybeResumeSend() {
+ if ((mReadyForWrite.GetSize() > 0) && CanSendData() && mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+nsresult Http3Session::ProcessSlowConsumers() {
+ if (mSlowConsumersReadyForRead.IsEmpty()) {
+ return NS_OK;
+ }
+
+ RefPtr<Http3StreamBase> slowConsumer =
+ mSlowConsumersReadyForRead.ElementAt(0);
+ mSlowConsumersReadyForRead.RemoveElementAt(0);
+
+ nsresult rv = ProcessTransactionRead(slowConsumer);
+
+ return rv;
+}
+
+nsresult Http3Session::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ MOZ_ASSERT(false, "Http3Session::WriteSegments()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult Http3Session::RecvData(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Process slow consumers.
+ nsresult rv = ProcessSlowConsumers();
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session %p ProcessSlowConsumers returns 0x%" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ ProcessInput(socket);
+
+ rv = ProcessEvents();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = SendData(socket);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+const uint32_t HTTP3_TELEMETRY_APP_NECKO = 42;
+
+void Http3Session::Close(nsresult aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Session::Close [this=%p]", this));
+
+ if (NS_FAILED(mError)) {
+ CloseInternal(false);
+ } else {
+ mError = aReason;
+ // If necko closes connection, this will map to the "closing" key and the
+ // value HTTP3_TELEMETRY_APP_NECKO.
+ Telemetry::Accumulate(Telemetry::HTTP3_CONNECTION_CLOSE_CODE_3,
+ "app_closing"_ns, HTTP3_TELEMETRY_APP_NECKO);
+ CloseInternal(true);
+ }
+
+ if (mCleanShutdown || mIsClosedByNeqo || NS_FAILED(mSocketError)) {
+ // It is network-tear-down, a socker error or neqo is state CLOSED
+ // (it does not need to send any more packets or wait for new packets).
+ // We need to remove all references, so that
+ // Http3Session will be destroyed.
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mConnection = nullptr;
+ mUdpConn = nullptr;
+ mState = CLOSED;
+ }
+ if (mConnection) {
+ // resume sending to send CLOSE_CONNECTION frame.
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http3Session::CloseInternal(bool aCallNeqoClose) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (IsClosing()) {
+ return;
+ }
+
+ LOG(("Http3Session::Closing [this=%p]", this));
+
+ if (mState != CONNECTED) {
+ mBeforeConnectedError = true;
+ }
+
+ if (mState == ZERORTT) {
+ ZeroRttTelemetry(aCallNeqoClose ? ZeroRttOutcome::USED_CONN_CLOSED_BY_NECKO
+ : ZeroRttOutcome::USED_CONN_ERROR);
+ }
+
+ mState = CLOSING;
+ Shutdown();
+
+ if (aCallNeqoClose) {
+ mHttp3Connection->Close(HTTP3_APP_ERROR_NO_ERROR);
+ }
+
+ mStreamIdHash.Clear();
+ mStreamTransactionHash.Clear();
+}
+
+nsHttpConnectionInfo* Http3Session::ConnectionInfo() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ return ci.get();
+}
+
+void Http3Session::SetProxyConnectFailed() {
+ MOZ_ASSERT(false, "Http3Session::SetProxyConnectFailed()");
+}
+
+nsHttpRequestHead* Http3Session::RequestHead() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(false,
+ "Http3Session::RequestHead() "
+ "should not be called after http/3 is setup");
+ return nullptr;
+}
+
+uint32_t Http3Session::Http1xTransactionCount() { return 0; }
+
+nsresult Http3Session::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection* Http3Session::Connection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mConnection;
+}
+
+nsresult Http3Session::OnHeadersAvailable(nsAHttpTransaction* transaction,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ MOZ_ASSERT(mConnection);
+ if (mConnection) {
+ return mConnection->OnHeadersAvailable(transaction, requestHead,
+ responseHead, reset);
+ }
+ return NS_OK;
+}
+
+bool Http3Session::IsReused() {
+ if (mConnection) {
+ return mConnection->IsReused();
+ }
+ return true;
+}
+
+nsresult Http3Session::PushBack(const char* buf, uint32_t len) {
+ return NS_ERROR_UNEXPECTED;
+}
+
+already_AddRefed<HttpConnectionBase> Http3Session::TakeHttpConnection() {
+ MOZ_ASSERT(false, "TakeHttpConnection of Http3Session");
+ return nullptr;
+}
+
+already_AddRefed<HttpConnectionBase> Http3Session::HttpConnection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mConnection) {
+ return mConnection->HttpConnection();
+ }
+ return nullptr;
+}
+
+void Http3Session::CloseTransaction(nsAHttpTransaction* aTransaction,
+ nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::CloseTransaction %p %p 0x%" PRIx32, this, aTransaction,
+ static_cast<uint32_t>(aResult)));
+
+ // Generally this arrives as a cancel event from the connection manager.
+
+ // need to find the stream and call CloseStream() on it.
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction);
+ if (!stream) {
+ LOG3(("Http3Session::CloseTransaction %p %p 0x%" PRIx32 " - not found.",
+ this, aTransaction, static_cast<uint32_t>(aResult)));
+ return;
+ }
+ LOG3(
+ ("Http3Session::CloseTransaction probably a cancel. this=%p, "
+ "trans=%p, result=0x%" PRIx32 ", streamId=0x%" PRIx64 " stream=%p",
+ this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamId(),
+ stream.get()));
+ CloseStream(stream, aResult);
+ if (mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http3Session::CloseStream(Http3StreamBase* aStream, nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ RefPtr<Http3WebTransportStream> wtStream =
+ aStream->GetHttp3WebTransportStream();
+ if (wtStream) {
+ CloseWebTransportStream(wtStream, aResult);
+ return;
+ }
+
+ RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream();
+ if (httpStream && !httpStream->RecvdFin() && !httpStream->RecvdReset() &&
+ httpStream->HasStreamId()) {
+ mHttp3Connection->CancelFetch(httpStream->StreamId(),
+ HTTP3_APP_ERROR_REQUEST_CANCELLED);
+ }
+
+ aStream->Close(aResult);
+ CloseStreamInternal(aStream, aResult);
+}
+
+void Http3Session::CloseStreamInternal(Http3StreamBase* aStream,
+ nsresult aResult) {
+ LOG3(("Http3Session::CloseStreamInternal %p %p 0x%" PRIx32, this, aStream,
+ static_cast<uint32_t>(aResult)));
+ if (aStream->HasStreamId()) {
+ // We know the transaction reusing an idle connection has succeeded or
+ // failed.
+ if (mFirstStreamIdReuseIdleConnection.isSome() &&
+ aStream->StreamId() == *mFirstStreamIdReuseIdleConnection) {
+ MOZ_ASSERT(mConnectionIdleStart);
+ MOZ_ASSERT(mConnectionIdleEnd);
+
+ if (mConnectionIdleStart) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP3_TIME_TO_REUSE_IDLE_CONNECTTION_MS,
+ NS_SUCCEEDED(aResult) ? "succeeded"_ns : "failed"_ns,
+ mConnectionIdleStart, mConnectionIdleEnd);
+ }
+
+ mConnectionIdleStart = TimeStamp();
+ mConnectionIdleEnd = TimeStamp();
+ mFirstStreamIdReuseIdleConnection.reset();
+ }
+
+ mStreamIdHash.Remove(aStream->StreamId());
+
+ // Start to idle when we remove the last stream.
+ if (mStreamIdHash.IsEmpty()) {
+ mConnectionIdleStart = TimeStamp::Now();
+ }
+ }
+ RemoveStreamFromQueues(aStream);
+ if (nsAHttpTransaction* transaction = aStream->Transaction()) {
+ mStreamTransactionHash.Remove(transaction);
+ }
+ mWebTransportSessions.RemoveElement(aStream);
+ mWebTransportStreams.RemoveElement(aStream);
+ // Close(NS_OK) implies that the NeqoHttp3Conn will be closed, so we can only
+ // do this when there is no Http3Steeam, WebTransportSession and
+ // WebTransportStream.
+ if ((mShouldClose || mGoawayReceived) &&
+ (!mStreamTransactionHash.Count() && mWebTransportSessions.IsEmpty() &&
+ mWebTransportStreams.IsEmpty())) {
+ MOZ_ASSERT(!IsClosing());
+ Close(NS_OK);
+ }
+}
+
+void Http3Session::CloseWebTransportStream(Http3WebTransportStream* aStream,
+ nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::CloseWebTransportStream %p %p 0x%" PRIx32, this, aStream,
+ static_cast<uint32_t>(aResult)));
+ if (aStream && !aStream->RecvdFin() && !aStream->RecvdReset() &&
+ (aStream->HasStreamId())) {
+ mHttp3Connection->ResetStream(aStream->StreamId(),
+ HTTP3_APP_ERROR_REQUEST_CANCELLED);
+ }
+
+ aStream->Close(aResult);
+ CloseStreamInternal(aStream, aResult);
+}
+
+void Http3Session::ResetWebTransportStream(Http3WebTransportStream* aStream,
+ uint64_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::ResetWebTransportStream %p %p 0x%" PRIx64, this, aStream,
+ aErrorCode));
+ mHttp3Connection->ResetStream(aStream->StreamId(), aErrorCode);
+}
+
+void Http3Session::StreamStopSending(Http3WebTransportStream* aStream,
+ uint8_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Session::StreamStopSending %p %p 0x%" PRIx32, this, aStream,
+ static_cast<uint32_t>(aErrorCode)));
+ mHttp3Connection->StreamStopSending(aStream->StreamId(), aErrorCode);
+}
+
+nsresult Http3Session::TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) {
+ MOZ_ASSERT(false, "TakeTransport of Http3Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+Http3WebTransportSession* Http3Session::GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction);
+
+ if (!stream || !stream->GetHttp3WebTransportSession()) {
+ MOZ_ASSERT(false, "There must be a stream");
+ return nullptr;
+ }
+ RemoveStreamFromQueues(stream);
+ mStreamTransactionHash.Remove(aTransaction);
+ mWebTransportSessions.AppendElement(stream);
+ return stream->GetHttp3WebTransportSession();
+}
+
+bool Http3Session::IsPersistent() { return true; }
+
+void Http3Session::DontReuse() {
+ LOG3(("Http3Session::DontReuse %p\n", this));
+ if (!OnSocketThread()) {
+ LOG3(("Http3Session %p not on socket thread\n", this));
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "Http3Session::DontReuse", this, &Http3Session::DontReuse);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mGoawayReceived || IsClosing()) {
+ return;
+ }
+
+ mShouldClose = true;
+ if (!mStreamTransactionHash.Count()) {
+ Close(NS_OK);
+ }
+}
+
+void Http3Session::CloseWebTransportConn() {
+ LOG3(("Http3Session::CloseWebTransportConn %p\n", this));
+ // We need to dispatch, since Http3Session could be released in
+ // HttpConnectionUDP::CloseTransaction.
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("Http3Session::CloseWebTransportConn",
+ [self = RefPtr{this}]() {
+ if (self->mUdpConn) {
+ self->mUdpConn->CloseTransaction(
+ self, NS_ERROR_ABORT);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+void Http3Session::CurrentBrowserIdChanged(uint64_t id) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCurrentBrowserId = id;
+
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream();
+ if (httpStream) {
+ httpStream->CurrentBrowserIdChanged(id);
+ }
+ }
+}
+
+// This is called by Http3Stream::OnWriteSegment.
+nsresult Http3Session::ReadResponseData(uint64_t aStreamId, char* aBuf,
+ uint32_t aCount,
+ uint32_t* aCountWritten, bool* aFin) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv = mHttp3Connection->ReadResponseData(aStreamId, (uint8_t*)aBuf,
+ aCount, aCountWritten, aFin);
+
+ // This should not happen, i.e. stream must be present in neqo and in necko at
+ // the same time.
+ MOZ_ASSERT(rv != NS_ERROR_INVALID_ARG);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session::ReadResponseData return an error %" PRIx32
+ " [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ // This error will be handled by neqo and the whole connection will be
+ // closed. We will return NS_BASE_STREAM_WOULD_BLOCK here.
+ *aCountWritten = 0;
+ *aFin = false;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ MOZ_ASSERT((*aCountWritten != 0) || aFin || NS_FAILED(rv));
+ return rv;
+}
+
+void Http3Session::TransactionHasDataToWrite(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::TransactionHasDataToWrite %p trans=%p", this, caller));
+
+ // a trapped signal from the http transaction to the connection that
+ // it is no longer blocked on read.
+
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream) {
+ LOG3(("Http3Session::TransactionHasDataToWrite %p caller %p not found",
+ this, caller));
+ return;
+ }
+
+ LOG3(("Http3Session::TransactionHasDataToWrite %p ID is 0x%" PRIx64, this,
+ stream->StreamId()));
+
+ StreamHasDataToWrite(stream);
+}
+
+void Http3Session::StreamHasDataToWrite(Http3StreamBase* aStream) {
+ if (!IsClosing()) {
+ StreamReadyToWrite(aStream);
+ } else {
+ LOG3(
+ ("Http3Session::TransactionHasDataToWrite %p closed so not setting "
+ "Ready4Write\n",
+ this));
+ }
+
+ // NSPR poll will not poll the network if there are non system PR_FileDesc's
+ // that are ready - so we can get into a deadlock waiting for the system IO
+ // to come back here if we don't force the send loop manually.
+ Unused << ForceSend();
+}
+
+void Http3Session::TransactionHasDataToRecv(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::TransactionHasDataToRecv %p trans=%p", this, caller));
+
+ // a signal from the http transaction to the connection that it will consume
+ // more
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream) {
+ LOG3(("Http3Session::TransactionHasDataToRecv %p caller %p not found", this,
+ caller));
+ return;
+ }
+
+ LOG3(("Http3Session::TransactionHasDataToRecv %p ID is 0x%" PRIx64 "\n", this,
+ stream->StreamId()));
+ ConnectSlowConsumer(stream);
+}
+
+void Http3Session::ConnectSlowConsumer(Http3StreamBase* stream) {
+ LOG3(("Http3Session::ConnectSlowConsumer %p 0x%" PRIx64 "\n", this,
+ stream->StreamId()));
+ mSlowConsumersReadyForRead.AppendElement(stream);
+ Unused << ForceRecv();
+}
+
+bool Http3Session::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ return RealJoinConnection(hostname, port, true);
+}
+
+bool Http3Session::JoinConnection(const nsACString& hostname, int32_t port) {
+ return RealJoinConnection(hostname, port, false);
+}
+
+// TODO test
+bool Http3Session::RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mConnection || !CanSendData() || mShouldClose || mGoawayReceived) {
+ return false;
+ }
+
+ nsHttpConnectionInfo* ci = ConnectionInfo();
+ if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
+ (port == ci->OriginPort())) {
+ return true;
+ }
+
+ nsAutoCString key(hostname);
+ key.Append(':');
+ key.Append(justKidding ? 'k' : '.');
+ key.AppendInt(port);
+ bool cachedResult;
+ if (mJoinConnectionCache.Get(key, &cachedResult)) {
+ LOG(("joinconnection [%p %s] %s result=%d cache\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), cachedResult));
+ return cachedResult;
+ }
+
+ nsresult rv;
+ bool isJoined = false;
+
+ nsCOMPtr<nsITLSSocketControl> sslSocketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(sslSocketControl));
+ if (!sslSocketControl) {
+ return false;
+ }
+
+ bool joinedReturn = false;
+ if (justKidding) {
+ rv = sslSocketControl->TestJoinConnection(mConnInfo->GetNPNToken(),
+ hostname, port, &isJoined);
+ } else {
+ rv = sslSocketControl->JoinConnection(mConnInfo->GetNPNToken(), hostname,
+ port, &isJoined);
+ }
+ if (NS_SUCCEEDED(rv) && isJoined) {
+ joinedReturn = true;
+ }
+
+ LOG(("joinconnection [%p %s] %s result=%d lookup\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
+ mJoinConnectionCache.InsertOrUpdate(key, joinedReturn);
+ if (!justKidding) {
+ // cache a kidding entry too as this one is good for both
+ nsAutoCString key2(hostname);
+ key2.Append(':');
+ key2.Append('k');
+ key2.AppendInt(port);
+ if (!mJoinConnectionCache.Get(key2)) {
+ mJoinConnectionCache.InsertOrUpdate(key2, joinedReturn);
+ }
+ }
+ return joinedReturn;
+}
+
+void Http3Session::CallCertVerification(Maybe<nsCString> aEchPublicName) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Session::CallCertVerification [this=%p]", this));
+
+ NeqoCertificateInfo certInfo;
+ if (NS_FAILED(mHttp3Connection->PeerCertificateInfo(&certInfo))) {
+ LOG(("Http3Session::CallCertVerification [this=%p] - no cert", this));
+ mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE);
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE);
+ return;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> stapledOCSPResponse;
+ if (certInfo.stapled_ocsp_responses_present) {
+ stapledOCSPResponse.emplace(std::move(certInfo.stapled_ocsp_responses));
+ }
+
+ Maybe<nsTArray<uint8_t>> sctsFromTLSExtension;
+ if (certInfo.signed_cert_timestamp_present) {
+ sctsFromTLSExtension.emplace(std::move(certInfo.signed_cert_timestamp));
+ }
+
+ uint32_t providerFlags;
+ // the return value is always NS_OK, just ignore it.
+ Unused << mSocketControl->GetProviderFlags(&providerFlags);
+
+ nsCString echConfig;
+ nsresult nsrv = mSocketControl->GetEchConfig(echConfig);
+ bool verifyToEchPublicName = NS_SUCCEEDED(nsrv) && !echConfig.IsEmpty() &&
+ aEchPublicName && !aEchPublicName->IsEmpty();
+ const nsACString& hostname =
+ verifyToEchPublicName ? *aEchPublicName : mSocketControl->GetHostName();
+
+ SECStatus rv = psm::AuthCertificateHookWithInfo(
+ mSocketControl, hostname, static_cast<const void*>(this),
+ std::move(certInfo.certs), stapledOCSPResponse, sctsFromTLSExtension,
+ providerFlags);
+ if ((rv != SECSuccess) && (rv != SECWouldBlock)) {
+ LOG(("Http3Session::CallCertVerification [this=%p] AuthCertificate failed",
+ this));
+ mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE);
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE);
+ }
+}
+
+void Http3Session::Authenticated(int32_t aError) {
+ LOG(("Http3Session::Authenticated error=0x%" PRIx32 " [this=%p].", aError,
+ this));
+ if ((mState == INITIALIZING) || (mState == ZERORTT)) {
+ if (psm::IsNSSErrorCode(aError)) {
+ mError = psm::GetXPCOMFromNSSError(aError);
+ LOG(("Http3Session::Authenticated psm-error=0x%" PRIx32 " [this=%p].",
+ static_cast<uint32_t>(mError), this));
+ }
+ mHttp3Connection->PeerAuthenticated(aError);
+
+ // Call OnQuicTimeoutExpired to properly process neqo events and outputs.
+ // We call OnQuicTimeoutExpired instead of ProcessOutputAndEvents, because
+ // HttpConnectionUDP must close this session in case of an error.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("net::HttpConnectionUDP::OnQuicTimeoutExpired",
+ mUdpConn, &HttpConnectionUDP::OnQuicTimeoutExpired));
+ mUdpConn->ChangeConnectionState(ConnectionState::TRANSFERING);
+ }
+}
+
+void Http3Session::SetSecInfo() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NeqoSecretInfo secInfo;
+ if (NS_SUCCEEDED(mHttp3Connection->GetSecInfo(&secInfo))) {
+ mSocketControl->SetSSLVersionUsed(secInfo.version);
+ mSocketControl->SetResumed(secInfo.resumed);
+ mSocketControl->SetNegotiatedNPN(secInfo.alpn);
+
+ mSocketControl->SetInfo(secInfo.cipher, secInfo.version, secInfo.group,
+ secInfo.signature_scheme, secInfo.ech_accepted);
+ mHandshakeSucceeded = true;
+ }
+
+ if (!mSocketControl->HasServerCert()) {
+ mSocketControl->RebuildCertificateInfoFromSSLTokenCache();
+ }
+}
+
+// Transport error have values from 0x0 to 0x11.
+// (https://tools.ietf.org/html/draft-ietf-quic-transport-34#section-20.1)
+// We will map this error to 0-16.
+// 17 will capture error codes between and including 0x12 and 0x0ff. This
+// error codes are not define by the spec but who know peer may sent them.
+// CryptoAlerts have value 0x100 + alert code. The range of alert code is
+// 0x00-0xff. (https://tools.ietf.org/html/draft-ietf-quic-tls_34#section-4.8)
+// Since telemetry does not allow more than 100 bucket, we use three diffrent
+// keys to map all alert codes.
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_INTERNAL_ERROR = 15;
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_END = 16;
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_UNKNOWN = 17;
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_CRYPTO_UNKNOWN = 18;
+// All errors from CloseError::Tag::CryptoError will be map to 19
+const uint32_t HTTP3_TELEMETRY_CRYPTO_ERROR = 19;
+
+uint64_t GetCryptoAlertCode(nsCString& key, uint64_t error) {
+ if (error < 100) {
+ key.Append("_a"_ns);
+ return error;
+ }
+ if (error < 200) {
+ error -= 100;
+ key.Append("_b"_ns);
+ return error;
+ }
+ if (error < 256) {
+ error -= 200;
+ key.Append("_c"_ns);
+ return error;
+ }
+ return HTTP3_TELEMETRY_TRANSPORT_CRYPTO_UNKNOWN;
+}
+
+uint64_t GetTransportErrorCodeForTelemetry(nsCString& key, uint64_t error) {
+ if (error <= HTTP3_TELEMETRY_TRANSPORT_END) {
+ return error;
+ }
+ if (error < 0x100) {
+ return HTTP3_TELEMETRY_TRANSPORT_UNKNOWN;
+ }
+
+ return GetCryptoAlertCode(key, error - 0x100);
+}
+
+// Http3 error codes are 0x100-0x110.
+// (https://tools.ietf.org/html/draft-ietf-quic-http-33#section-8.1)
+// The mapping is described below.
+// 0x00-0x10 mapped to 0-16
+// 0x11-0xff mapped to 17
+// 0x100-0x110 mapped to 18-36
+// 0x111-0x1ff mapped to 37
+// 0x200-0x202 mapped to 38-40
+// Others mapped to 41
+const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_1 = 17;
+const uint32_t HTTP3_TELEMETRY_APP_START = 18;
+// Values between 0x111 and 0x1ff are no definded and will be map to 18.
+const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_2 = 37;
+// Error codes between 0x200 and 0x202 are related to qpack.
+// (https://tools.ietf.org/html/draft-ietf-quic-qpack-20#section-6)
+// They will be mapped to 19-21
+const uint32_t HTTP3_TELEMETRY_APP_QPACK_START = 38;
+// Values greater or equal to 0x203 are no definded and will be map to 41.
+const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_3 = 41;
+
+uint64_t GetAppErrorCodeForTelemetry(uint64_t error) {
+ if (error <= 0x10) {
+ return error;
+ }
+ if (error <= 0xff) {
+ return HTTP3_TELEMETRY_APP_UNKNOWN_1;
+ }
+ if (error <= 0x110) {
+ return error - 0x100 + HTTP3_TELEMETRY_APP_START;
+ }
+ if (error < 0x200) {
+ return HTTP3_TELEMETRY_APP_UNKNOWN_2;
+ }
+ if (error <= 0x202) {
+ return error - 0x200 + HTTP3_TELEMETRY_APP_QPACK_START;
+ }
+ return HTTP3_TELEMETRY_APP_UNKNOWN_3;
+}
+
+void Http3Session::CloseConnectionTelemetry(CloseError& aError, bool aClosing) {
+ uint64_t value = 0;
+ nsCString key = EmptyCString();
+
+ switch (aError.tag) {
+ case CloseError::Tag::TransportInternalError:
+ key = "transport_internal"_ns;
+ value = HTTP3_TELEMETRY_TRANSPORT_INTERNAL_ERROR;
+ break;
+ case CloseError::Tag::TransportInternalErrorOther:
+ key = "transport_other"_ns;
+ value = aError.transport_internal_error_other._0;
+ break;
+ case CloseError::Tag::TransportError:
+ key = "transport"_ns;
+ value = GetTransportErrorCodeForTelemetry(key, aError.transport_error._0);
+ break;
+ case CloseError::Tag::CryptoError:
+ key = "transport"_ns;
+ value = HTTP3_TELEMETRY_CRYPTO_ERROR;
+ break;
+ case CloseError::Tag::CryptoAlert:
+ key = "transport_crypto_alert"_ns;
+ value = GetCryptoAlertCode(key, aError.crypto_alert._0);
+ break;
+ case CloseError::Tag::PeerAppError:
+ key = "peer_app"_ns;
+ value = GetAppErrorCodeForTelemetry(aError.peer_app_error._0);
+ break;
+ case CloseError::Tag::PeerError:
+ key = "peer_transport"_ns;
+ value = GetTransportErrorCodeForTelemetry(key, aError.peer_error._0);
+ break;
+ case CloseError::Tag::AppError:
+ key = "app"_ns;
+ value = GetAppErrorCodeForTelemetry(aError.app_error._0);
+ break;
+ case CloseError::Tag::EchRetry:
+ key = "transport_crypto_alert"_ns;
+ value = 100;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(value <= 100);
+
+ key.Append(aClosing ? "_closing"_ns : "_closed"_ns);
+
+ Telemetry::Accumulate(Telemetry::HTTP3_CONNECTION_CLOSE_CODE_3, key, value);
+
+ Http3Stats stats{};
+ mHttp3Connection->GetStats(&stats);
+
+ if (stats.packets_tx > 0) {
+ unsigned long loss = (stats.lost * 10000) / stats.packets_tx;
+ Telemetry::Accumulate(Telemetry::HTTP3_LOSS_RATIO, loss);
+
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK, "ack"_ns, stats.late_ack);
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK, "pto"_ns, stats.pto_ack);
+
+ unsigned long late_ack_ratio = (stats.late_ack * 10000) / stats.packets_tx;
+ unsigned long pto_ack_ratio = (stats.pto_ack * 10000) / stats.packets_tx;
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK_RATIO, "ack"_ns,
+ late_ack_ratio);
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK_RATIO, "pto"_ns,
+ pto_ack_ratio);
+
+ for (uint32_t i = 0; i < MAX_PTO_COUNTS; i++) {
+ nsAutoCString key;
+ key.AppendInt(i);
+ Telemetry::Accumulate(Telemetry::HTTP3_COUNTS_PTO, key,
+ stats.pto_counts[i]);
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP3_DROP_DGRAMS, stats.dropped_rx);
+ Telemetry::Accumulate(Telemetry::HTTP3_SAVED_DGRAMS, stats.saved_datagrams);
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP3_RECEIVED_SENT_DGRAMS, "received"_ns,
+ stats.packets_rx);
+ Telemetry::Accumulate(Telemetry::HTTP3_RECEIVED_SENT_DGRAMS, "sent"_ns,
+ stats.packets_tx);
+}
+
+void Http3Session::Finish0Rtt(bool aRestart) {
+ for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
+ if (m0RTTStreams[i]) {
+ if (aRestart) {
+ // When we need to restart transactions remove them from all lists.
+ if (m0RTTStreams[i]->HasStreamId()) {
+ mStreamIdHash.Remove(m0RTTStreams[i]->StreamId());
+ }
+ RemoveStreamFromQueues(m0RTTStreams[i]);
+ // The stream is ready to write again.
+ mReadyForWrite.Push(m0RTTStreams[i]);
+ }
+ m0RTTStreams[i]->Finish0RTT(aRestart);
+ }
+ }
+
+ for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
+ if (mCannotDo0RTTStreams[i]) {
+ mReadyForWrite.Push(mCannotDo0RTTStreams[i]);
+ }
+ }
+ m0RTTStreams.Clear();
+ mCannotDo0RTTStreams.Clear();
+ MaybeResumeSend();
+}
+
+void Http3Session::ReportHttp3Connection() {
+ if (CanSendData() && !mHttp3ConnectionReported) {
+ mHttp3ConnectionReported = true;
+ gHttpHandler->ConnMgr()->ReportHttp3Connection(mUdpConn);
+ MaybeResumeSend();
+ }
+}
+
+void Http3Session::EchOutcomeTelemetry() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsAutoCString key;
+ switch (mEchExtensionStatus) {
+ case EchExtensionStatus::kNotPresent:
+ key = "NONE";
+ break;
+ case EchExtensionStatus::kGREASE:
+ key = "GREASE";
+ break;
+ case EchExtensionStatus::kReal:
+ key = "REAL";
+ break;
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP3_ECH_OUTCOME, key,
+ mHandshakeSucceeded ? 0 : 1);
+}
+
+void Http3Session::ZeroRttTelemetry(ZeroRttOutcome aOutcome) {
+ Telemetry::Accumulate(Telemetry::HTTP3_0RTT_STATE, aOutcome);
+
+ nsAutoCString key;
+
+ switch (aOutcome) {
+ case USED_SUCCEEDED:
+ key = "succeeded"_ns;
+ break;
+ case USED_REJECTED:
+ key = "rejected"_ns;
+ break;
+ case USED_CONN_ERROR:
+ key = "conn_error"_ns;
+ break;
+ case USED_CONN_CLOSED_BY_NECKO:
+ key = "conn_closed_by_necko"_ns;
+ break;
+ default:
+ break;
+ }
+
+ if (!key.IsEmpty()) {
+ MOZ_ASSERT(mZeroRttStarted);
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_0RTT_STATE_DURATION, key,
+ mZeroRttStarted, TimeStamp::Now());
+ }
+}
+
+nsresult Http3Session::GetTransactionTLSSocketControl(
+ nsITLSSocketControl** tlsSocketControl) {
+ NS_IF_ADDREF(*tlsSocketControl = mSocketControl);
+ return NS_OK;
+}
+
+PRIntervalTime Http3Session::LastWriteTime() { return mLastWriteTime; }
+
+void Http3Session::WebTransportNegotiationDone() {
+ for (size_t i = 0; i < mWaitingForWebTransportNegotiation.Length(); ++i) {
+ if (mWaitingForWebTransportNegotiation[i]) {
+ mReadyForWrite.Push(mWaitingForWebTransportNegotiation[i]);
+ }
+ }
+ mWaitingForWebTransportNegotiation.Clear();
+ MaybeResumeSend();
+}
+
+//=========================================================================
+// WebTransport
+//=========================================================================
+
+nsresult Http3Session::CloseWebTransport(uint64_t aSessionId, uint32_t aError,
+ const nsACString& aMessage) {
+ return mHttp3Connection->CloseWebTransport(aSessionId, aError, aMessage);
+}
+
+nsresult Http3Session::CreateWebTransportStream(
+ uint64_t aSessionId, WebTransportStreamType aStreamType,
+ uint64_t* aStreamId) {
+ return mHttp3Connection->CreateWebTransportStream(aSessionId, aStreamType,
+ aStreamId);
+}
+
+void Http3Session::SendDatagram(Http3WebTransportSession* aSession,
+ nsTArray<uint8_t>& aData,
+ uint64_t aTrackingId) {
+ nsresult rv = mHttp3Connection->WebTransportSendDatagram(aSession->StreamId(),
+ aData, aTrackingId);
+ LOG(("Http3Session::SendDatagram %p res=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ if (!aTrackingId) {
+ return;
+ }
+
+ switch (rv) {
+ case NS_OK:
+ aSession->OnOutgoingDatagramOutCome(
+ aTrackingId, WebTransportSessionEventListener::DatagramOutcome::SENT);
+ break;
+ case NS_ERROR_NOT_AVAILABLE:
+ aSession->OnOutgoingDatagramOutCome(
+ aTrackingId, WebTransportSessionEventListener::DatagramOutcome::
+ DROPPED_TOO_MUCH_DATA);
+ break;
+ default:
+ aSession->OnOutgoingDatagramOutCome(
+ aTrackingId,
+ WebTransportSessionEventListener::DatagramOutcome::UNKNOWN);
+ break;
+ }
+}
+
+uint64_t Http3Session::MaxDatagramSize(uint64_t aSessionId) {
+ uint64_t size = 0;
+ Unused << mHttp3Connection->WebTransportMaxDatagramSize(aSessionId, &size);
+ return size;
+}
+
+void Http3Session::SetSendOrder(Http3StreamBase* aStream,
+ Maybe<int64_t> aSendOrder) {
+ if (!IsClosing()) {
+ nsresult rv = mHttp3Connection->WebTransportSetSendOrder(
+ aStream->StreamId(), aSendOrder);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ Unused << rv;
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h
new file mode 100644
index 0000000000..c72f24959e
--- /dev/null
+++ b/netwerk/protocol/http/Http3Session.h
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Http3Session_H__
+#define Http3Session_H__
+
+#include "HttpTrafficAnalyzer.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/net/NeqoHttp3Conn.h"
+#include "nsAHttpConnection.h"
+#include "nsDeque.h"
+#include "nsISupportsImpl.h"
+#include "nsITimer.h"
+#include "nsIUDPSocket.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashMap.h"
+#include "nsWeakReference.h"
+
+/*
+ * WebTransport
+ *
+ * Http3Session and the underlying neqo code support multiplexing of multiple
+ * WebTransport and multiplexing WebTransport sessions with regular HTTP
+ * traffic. Whether WebTransport sessions are polled, will be controlled by the
+ * nsHttpConnectionMgr.
+ *
+ * WebTransport support is negotiated using HTTP/3 setting. Before the settings
+ * are available all WebTransport transactions are queued in
+ * mWaitingForWebTransportNegotiation. The information on whether WebTransport
+ * is supported is received via an HTTP/3 event. The event is
+ * Http3Event::Tag::WebTransport with the value
+ * WebTransportEventExternal::Tag::Negotiated that can be true or false. If
+ * the WebTransport feature has been negotiated, queued transactions will be
+ * activated otherwise they will be canceled(i.e. WebTransportNegotiationDone).
+ *
+ * The lifetime of a WebTransport session
+ *
+ * A WebTransport lifetime consists of 2 parts:
+ * - WebTransport session setup
+ * - WebTransport session active time
+ *
+ * WebTransport session setup:
+ * A WebTransport session uses a regular HTTP request for negotiation.
+ * Therefore when a new WebTransport is started a nsHttpChannel and the
+ * corresponding nsHttpTransaction are created. The nsHttpTransaction is
+ * dispatched to a Http3Session and after the
+ * WebTransportEventExternal::Tag::Negotiated event it behaves almost the same
+ * as a regular transaction, e.g. it is added to mStreamTransactionHash and
+ * mStreamIdHash. For activating the session NeqoHttp3Conn::CreateWebTransport
+ * is called instead of NeqoHttp3Conn::Fetch(this is called for the regular
+ * HTTP requests). In this phase, the WebTransport session is canceled in the
+ * same way a regular request is canceled, by canceling the corresponding
+ * nsHttpChannel. If HTTP/3 connection is closed in this phase the
+ * corresponding nsHttpTransaction is canceled and this may cause the
+ * transaction to be restarted (this is the existing restart logic) or the
+ * error is propagated to the nsHttpChannel and its listener(via OnStartRequest
+ * and OnStopRequest as the regular HTTP request).
+ * The phase ends when a connection breaks or when the event
+ * Http3Event::Tag::WebTransport with the value
+ * WebTransportEventExternal::Tag::Session is received. The parameter
+ * aData(from NeqoHttp3Conn::GetEvent) contain the HTTP head of the response.
+ * The headers may be:
+ * - failed code, i.e. anything except 200. In this case, the nsHttpTransaction
+ * behaves the same as a normal HTTP request and the nsHttpChannel listener
+ * will be informed via OnStartRequest and OnStopRequest calls.
+ * - success code, i.e. 200. The code will be propagated to the
+ * nsHttpTransaction. The transaction will parse the header and call
+ * Http3Session::GetWebTransportSession. The function transfers WebTransport
+ * session into the next phase:
+ * - Removes nsHttpTransaction from mStreamTransactionHash.
+ * - Adds the stream to mWebTransportSessions
+ * - The nsHttpTransaction supplies Http3WebTransportSession to the
+ * WebTransportSessionProxy and vice versa.
+ * TODO remove this circular referencing.
+ *
+ * WebTransport session active time:
+ * During this phase the following actions are possible:
+ * - Cancelling a WebTransport session by the application:
+ * The application calls Http3WebTransportSession::CloseSession. This
+ * transfers Http3WebTransportSession into the CLOSE_PENDING state and calls
+ * Http3Session::ConnectSlowConsumer to add itself to the “ready for reading
+ * queue”. Consequently, the Http3Session will call
+ * Http3WebTransportSession::WriteSegments and
+ * Http3Session::CloseWebTransport will be called to send the closing signal
+ * to the peer. After this, the Http3WebTransportSession is in the state DONE
+ * and it will be removed from the Http3Session(the CloseStream function
+ * takes care of this).
+ * - The peer sending a session closing signal:
+ * Http3Session will receive a Http3Event::Tag::WebTransport event with value
+ * WebTransportEventExternal::Tag::SessionClosed. The
+ * Http3WebTransportSession::OnSessionClosed function for the corresponding
+ * stream wil be called. The function will inform the corresponding
+ * WebTransportSessionProxy by calling OnSessionClosed function. The
+ * Http3WebTransportSession is in the state DONE and will be removed from the
+ * Http3Session(the CloseStream function takes care of this).
+ */
+
+namespace mozilla::net {
+
+class HttpConnectionUDP;
+class Http3StreamBase;
+class QuicSocketControl;
+
+// IID for the Http3Session interface
+#define NS_HTTP3SESSION_IID \
+ { \
+ 0x8fc82aaf, 0xc4ef, 0x46ed, { \
+ 0x89, 0x41, 0x93, 0x95, 0x8f, 0xac, 0x4f, 0x21 \
+ } \
+ }
+
+enum class EchExtensionStatus {
+ kNotPresent, // No ECH Extension was sent
+ kGREASE, // A GREASE ECH Extension was sent
+ kReal // A 'real' ECH Extension was sent
+};
+
+class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP3SESSION_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+
+ Http3Session();
+ nsresult Init(const nsHttpConnectionInfo* aConnInfo, nsINetAddr* selfAddr,
+ nsINetAddr* peerAddr, HttpConnectionUDP* udpConn,
+ uint32_t controlFlags, nsIInterfaceRequestor* callbacks);
+
+ bool IsConnected() const { return mState == CONNECTED; }
+ bool CanSendData() const {
+ return (mState == CONNECTED) || (mState == ZERORTT);
+ }
+ bool IsClosing() const { return (mState == CLOSING || mState == CLOSED); }
+ bool IsClosed() const { return mState == CLOSED; }
+
+ bool AddStream(nsAHttpTransaction* aHttpTransaction, int32_t aPriority,
+ nsIInterfaceRequestor* aCallbacks);
+
+ bool CanReuse();
+
+ // The following functions are used by Http3Stream and
+ // Http3WebTransportSession:
+ nsresult TryActivating(const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aAuthorityHeader,
+ const nsACString& aPath, const nsACString& aHeaders,
+ uint64_t* aStreamId, Http3StreamBase* aStream);
+ // The folowing functions are used by Http3Stream:
+ void CloseSendingSide(uint64_t aStreamId);
+ nsresult SendRequestBody(uint64_t aStreamId, const char* buf, uint32_t count,
+ uint32_t* countRead);
+ nsresult ReadResponseHeaders(uint64_t aStreamId,
+ nsTArray<uint8_t>& aResponseHeaders, bool* aFin);
+ nsresult ReadResponseData(uint64_t aStreamId, char* aBuf, uint32_t aCount,
+ uint32_t* aCountWritten, bool* aFin);
+
+ // The folowing functions are used by Http3WebTransportSession:
+ nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
+ const nsACString& aMessage);
+ nsresult CreateWebTransportStream(uint64_t aSessionId,
+ WebTransportStreamType aStreamType,
+ uint64_t* aStreamId);
+ void CloseStream(Http3StreamBase* aStream, nsresult aResult);
+ void CloseStreamInternal(Http3StreamBase* aStream, nsresult aResult);
+
+ void SetCleanShutdown(bool aCleanShutdown) {
+ mCleanShutdown = aCleanShutdown;
+ }
+
+ bool TestJoinConnection(const nsACString& hostname, int32_t port);
+ bool JoinConnection(const nsACString& hostname, int32_t port);
+
+ void TransactionHasDataToWrite(nsAHttpTransaction* caller) override;
+ void TransactionHasDataToRecv(nsAHttpTransaction* caller) override;
+ [[nodiscard]] nsresult GetTransactionTLSSocketControl(
+ nsITLSSocketControl**) override;
+
+ // This function will be called by QuicSocketControl when the certificate
+ // verification is done.
+ void Authenticated(int32_t aError);
+
+ nsresult ProcessOutputAndEvents(nsIUDPSocket* socket);
+
+ void ReportHttp3Connection();
+
+ int64_t GetBytesWritten() { return mTotalBytesWritten; }
+ int64_t BytesRead() { return mTotalBytesRead; }
+
+ nsresult SendData(nsIUDPSocket* socket);
+ nsresult RecvData(nsIUDPSocket* socket);
+
+ void DoSetEchConfig(const nsACString& aEchConfig);
+
+ nsresult SendPriorityUpdateFrame(uint64_t aStreamId, uint8_t aPriorityUrgency,
+ bool aPriorityIncremental);
+
+ void ConnectSlowConsumer(Http3StreamBase* stream);
+
+ nsresult TryActivatingWebTransportStream(uint64_t* aStreamId,
+ Http3StreamBase* aStream);
+ void CloseWebTransportStream(Http3WebTransportStream* aStream,
+ nsresult aResult);
+ void StreamHasDataToWrite(Http3StreamBase* aStream);
+ void ResetWebTransportStream(Http3WebTransportStream* aStream,
+ uint64_t aErrorCode);
+ void StreamStopSending(Http3WebTransportStream* aStream, uint8_t aErrorCode);
+
+ void SendDatagram(Http3WebTransportSession* aSession,
+ nsTArray<uint8_t>& aData, uint64_t aTrackingId);
+
+ uint64_t MaxDatagramSize(uint64_t aSessionId);
+
+ void SetSendOrder(Http3StreamBase* aStream, Maybe<int64_t> aSendOrder);
+
+ void CloseWebTransportConn();
+
+ private:
+ ~Http3Session();
+
+ void CloseInternal(bool aCallNeqoClose);
+ void Shutdown();
+
+ bool RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding);
+
+ nsresult ProcessOutput(nsIUDPSocket* socket);
+ void ProcessInput(nsIUDPSocket* socket);
+ nsresult ProcessEvents();
+
+ nsresult ProcessTransactionRead(uint64_t stream_id);
+ nsresult ProcessTransactionRead(Http3StreamBase* stream);
+ nsresult ProcessSlowConsumers();
+
+ void SetupTimer(uint64_t aTimeout);
+
+ enum ResetType {
+ RESET,
+ STOP_SENDING,
+ };
+ void ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError,
+ ResetType aType);
+
+ void QueueStream(Http3StreamBase* stream);
+ void RemoveStreamFromQueues(Http3StreamBase*);
+ void ProcessPending();
+
+ void CallCertVerification(Maybe<nsCString> aEchPublicName);
+ void SetSecInfo();
+
+ void EchOutcomeTelemetry();
+
+ void StreamReadyToWrite(Http3StreamBase* aStream);
+ void MaybeResumeSend();
+
+ void CloseConnectionTelemetry(CloseError& aError, bool aClosing);
+ void Finish0Rtt(bool aRestart);
+
+ enum ZeroRttOutcome {
+ NOT_USED,
+ USED_SUCCEEDED,
+ USED_REJECTED,
+ USED_CONN_ERROR,
+ USED_CONN_CLOSED_BY_NECKO
+ };
+ void ZeroRttTelemetry(ZeroRttOutcome aOutcome);
+
+ RefPtr<NeqoHttp3Conn> mHttp3Connection;
+ RefPtr<nsAHttpConnection> mConnection;
+ // We need an extra map to store the mapping of WebTransportSession and
+ // WebTransportStreams to handle the case that a stream is already removed
+ // from mStreamIdHash and we still need the WebTransportSession.
+ nsTHashMap<nsUint64HashKey, uint64_t> mWebTransportStreamToSessionMap;
+ nsRefPtrHashtable<nsUint64HashKey, Http3StreamBase> mStreamIdHash;
+ nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http3StreamBase>
+ mStreamTransactionHash;
+
+ nsRefPtrDeque<Http3StreamBase> mReadyForWrite;
+
+ nsTArray<RefPtr<Http3StreamBase>> mSlowConsumersReadyForRead;
+ nsRefPtrDeque<Http3StreamBase> mQueuedStreams;
+
+ enum State {
+ INITIALIZING,
+ ZERORTT,
+ CONNECTED,
+ CLOSING,
+ CLOSED
+ } mState{INITIALIZING};
+
+ bool mAuthenticationStarted{false};
+ bool mCleanShutdown{false};
+ bool mGoawayReceived{false};
+ bool mShouldClose{false};
+ bool mIsClosedByNeqo{false};
+ bool mHttp3ConnectionReported = false;
+ // mError is neqo error (a protocol error) and that may mean that we will
+ // send some packets after that.
+ nsresult mError{NS_OK};
+ // This is a socket error, there is no poioint in sending anything on that
+ // socket.
+ nsresult mSocketError{NS_OK};
+ bool mBeforeConnectedError{false};
+ uint64_t mCurrentBrowserId;
+
+ // True if the mTimer is inited and waiting for firing.
+ bool mTimerActive{false};
+
+ RefPtr<HttpConnectionUDP> mUdpConn;
+
+ // The underlying socket transport object is needed to propogate some events
+ RefPtr<nsISocketTransport> mSocketTransport;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ nsTHashMap<nsCStringHashKey, bool> mJoinConnectionCache;
+
+ RefPtr<QuicSocketControl> mSocketControl;
+
+ uint64_t mTransactionCount = 0;
+
+ // The stream(s) that we are getting 0RTT data from.
+ nsTArray<WeakPtr<Http3StreamBase>> m0RTTStreams;
+ // The stream(s) that are not able to send 0RTT data. We need to
+ // remember them put them into mReadyForWrite queue when 0RTT finishes.
+ nsTArray<WeakPtr<Http3StreamBase>> mCannotDo0RTTStreams;
+
+ // The following variables are needed for telemetry.
+ TimeStamp mConnectionIdleStart;
+ TimeStamp mConnectionIdleEnd;
+ Maybe<uint64_t> mFirstStreamIdReuseIdleConnection;
+ TimeStamp mTimerShouldTrigger;
+ TimeStamp mZeroRttStarted;
+ uint64_t mBlockedByStreamLimitCount = 0;
+ uint64_t mTransactionsBlockedByStreamLimitCount = 0;
+ uint64_t mTransactionsSenderBlockedByFlowControlCount = 0;
+
+ // NS_NET_STATUS_CONNECTED_TO event will be created by the Http3Session.
+ // We want to propagate it to the first transaction.
+ RefPtr<nsHttpTransaction> mFirstHttpTransaction;
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool mThroughCaptivePortal = false;
+ int64_t mTotalBytesRead = 0; // total data read
+ int64_t mTotalBytesWritten = 0; // total data read
+ PRIntervalTime mLastWriteTime = 0;
+
+ // Records whether we sent an ECH Extension and whether it was a GREASE Xtn
+ EchExtensionStatus mEchExtensionStatus = EchExtensionStatus::kNotPresent;
+
+ // Records whether the handshake finished successfully and we established a
+ // a connection.
+ bool mHandshakeSucceeded = false;
+
+ nsCOMPtr<nsINetAddr> mNetAddr;
+
+ enum WebTransportNegotiation { DISABLED, NEGOTIATING, FAILED, SUCCEEDED };
+ WebTransportNegotiation mWebTransportNegotiationStatus{
+ WebTransportNegotiation::DISABLED};
+
+ nsTArray<WeakPtr<Http3StreamBase>> mWaitingForWebTransportNegotiation;
+ // 1795854 implement the case when WebTransport is not supported.
+ // Also, implement the case when the HTTP/3 session fails before settings
+ // are exchanged.
+ void WebTransportNegotiationDone();
+
+ nsTArray<RefPtr<Http3StreamBase>> mWebTransportSessions;
+ nsTArray<RefPtr<Http3StreamBase>> mWebTransportStreams;
+
+ bool mHasWebTransportSession = false;
+ // When true, we don't add this connection info into the Http/3 excluded list.
+ bool mDontExclude = false;
+ // The lifetime of the UDP socket is managed by the HttpConnectionUDP. This
+ // is only used in Http3Session::ProcessOutput. Using raw pointer here to
+ // improve performance.
+ nsIUDPSocket* mSocket;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID);
+
+} // namespace mozilla::net
+
+#endif // Http3Session_H__
diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp
new file mode 100644
index 0000000000..4f33ca07e2
--- /dev/null
+++ b/netwerk/protocol/http/Http3Stream.cpp
@@ -0,0 +1,533 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+#include "Http3Session.h"
+#include "Http3Stream.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpTransaction.h"
+#include "nsIClassOfService.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsIOService.h"
+#include "nsHttpHandler.h"
+
+#include <stdio.h>
+
+namespace mozilla {
+namespace net {
+
+Http3StreamBase::Http3StreamBase(nsAHttpTransaction* trans,
+ Http3Session* session)
+ : mTransaction(trans), mSession(session) {}
+
+Http3StreamBase::~Http3StreamBase() = default;
+
+Http3Stream::Http3Stream(nsAHttpTransaction* httpTransaction,
+ Http3Session* session, const ClassOfService& cos,
+ uint64_t currentBrowserId)
+ : Http3StreamBase(httpTransaction, session),
+ mCurrentBrowserId(currentBrowserId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Stream::Http3Stream [this=%p]", this));
+
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ mTransactionBrowserId = trans->BrowserId();
+ }
+
+ SetPriority(cos.Flags());
+ SetIncremental(cos.Incremental());
+}
+
+void Http3Stream::Close(nsresult aResult) {
+ mRecvState = RECV_DONE;
+ mTransaction->Close(aResult);
+ // Clear the mSession to break the cycle.
+ mSession = nullptr;
+}
+
+bool Http3Stream::GetHeadersString(const char* buf, uint32_t avail,
+ uint32_t* countUsed) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Stream::GetHeadersString %p avail=%u.", this, avail));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+ // We can use the simple double crlf because firefox is the
+ // only client we are parsing
+ int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+ if (endHeader == kNotFound) {
+ // We don't have all the headers yet
+ LOG3(
+ ("Http3Stream::GetHeadersString %p "
+ "Need more header bytes. Len = %zu",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return false;
+ }
+
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+
+ return true;
+}
+
+void Http3Stream::SetPriority(uint32_t aCos) {
+ if (aCos & nsIClassOfService::UrgentStart) {
+ // coming from an user interaction => response should be the highest
+ // priority
+ mPriorityUrgency = 1;
+ } else if (aCos & nsIClassOfService::Leader) {
+ // main html document normal priority
+ mPriorityUrgency = 2;
+ } else if (aCos & nsIClassOfService::Unblocked) {
+ mPriorityUrgency = 3;
+ } else if (aCos & nsIClassOfService::Follower) {
+ mPriorityUrgency = 4;
+ } else if (aCos & nsIClassOfService::Speculative) {
+ mPriorityUrgency = 6;
+ } else if (aCos & nsIClassOfService::Background) {
+ // background tasks can be deprioritzed to the lowest priority
+ mPriorityUrgency = 6;
+ } else if (aCos & nsIClassOfService::Tail) {
+ mPriorityUrgency = 6;
+ } else {
+ // all others get a lower priority than the main html document
+ mPriorityUrgency = 4;
+ }
+}
+
+void Http3Stream::SetIncremental(bool incremental) {
+ mPriorityIncremental = incremental;
+}
+
+nsresult Http3Stream::TryActivating() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Stream::TryActivating [this=%p]", this));
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ nsAutoCString authorityHeader;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+
+ nsAutoCString method;
+ nsAutoCString path;
+ head->Method(method);
+ head->Path(path);
+
+#ifdef DEBUG
+ nsAutoCString contentLength;
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Length, contentLength))) {
+ int64_t len;
+ if (nsHttp::ParseInt64(contentLength.get(), nullptr, &len)) {
+ mRequestBodyLenExpected = len;
+ }
+ }
+#endif
+
+ return mSession->TryActivating(method, scheme, authorityHeader, path,
+ mFlatHttpRequestHeaders, &mStreamId, this);
+}
+
+void Http3Stream::CurrentBrowserIdChanged(uint64_t id) {
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+
+ bool previouslyFocused = (mCurrentBrowserId == mTransactionBrowserId);
+ mCurrentBrowserId = id;
+ bool nowFocused = (mCurrentBrowserId == mTransactionBrowserId);
+
+ if (!StaticPrefs::
+ network_http_http3_send_background_tabs_deprioritization() ||
+ previouslyFocused == nowFocused) {
+ return;
+ }
+
+ mSession->SendPriorityUpdateFrame(mStreamId, PriorityUrgency(),
+ PriorityIncremental());
+}
+
+nsresult Http3Stream::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Stream::OnReadSegment count=%u state=%d [this=%p]", count,
+ mSendState, this));
+
+ nsresult rv = NS_OK;
+
+ switch (mSendState) {
+ case PREPARING_HEADERS: {
+ bool done = GetHeadersString(buf, count, countRead);
+
+ if (*countRead) {
+ mTotalSent += *countRead;
+ }
+
+ if (!done) {
+ break;
+ }
+ mSendState = WAITING_TO_ACTIVATE;
+ }
+ [[fallthrough]];
+ case WAITING_TO_ACTIVATE:
+ rv = TryActivating();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(("Http3Stream::OnReadSegment %p cannot activate now. queued.\n",
+ this));
+ rv = *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Stream::OnReadSegment %p cannot activate error=0x%" PRIx32
+ ".",
+ this, static_cast<uint32_t>(rv)));
+ break;
+ }
+
+ // Successfully activated.
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+
+ mSendState = SENDING_BODY;
+ break;
+ case SENDING_BODY: {
+ rv = mSession->SendRequestBody(mStreamId, buf, count, countRead);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSendingBlockedByFlowControlCount++;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3Stream::OnReadSegment %p sending body returns "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ break;
+ }
+
+#ifdef DEBUG
+ mRequestBodyLenSent += *countRead;
+#endif
+ mTotalSent += *countRead;
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+ } break;
+ case EARLY_RESPONSE:
+ // We do not need to send the rest of the request, so just ignore it.
+ *countRead = count;
+#ifdef DEBUG
+ mRequestBodyLenSent += count;
+#endif
+ break;
+ default:
+ MOZ_ASSERT(false, "We are done sending this request!");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ mSocketOutCondition = rv;
+
+ return mSocketOutCondition;
+}
+
+void Http3Stream::SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders,
+ bool aFin, bool interim) {
+ MOZ_ASSERT(mRecvState == BEFORE_HEADERS ||
+ mRecvState == READING_INTERIM_HEADERS);
+ mFlatResponseHeaders.AppendElements(aResponseHeaders);
+ mRecvState = (interim) ? READING_INTERIM_HEADERS : READING_HEADERS;
+ mDataReceived = true;
+ mFin = aFin;
+}
+
+void Http3Stream::StopSending() {
+ MOZ_ASSERT((mSendState == SENDING_BODY) || (mSendState == SEND_DONE));
+ if (mSendState == SENDING_BODY) {
+ mSendState = EARLY_RESPONSE;
+ }
+}
+
+nsresult Http3Stream::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Stream::OnWriteSegment [this=%p, state=%d", this, mRecvState));
+ nsresult rv = NS_OK;
+ switch (mRecvState) {
+ case BEFORE_HEADERS: {
+ *countWritten = 0;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } break;
+ case READING_HEADERS:
+ case READING_INTERIM_HEADERS: {
+ // SetResponseHeaders should have been previously called.
+ MOZ_ASSERT(!mFlatResponseHeaders.IsEmpty(), "Headers empty!");
+ *countWritten = (mFlatResponseHeaders.Length() > count)
+ ? count
+ : mFlatResponseHeaders.Length();
+ memcpy(buf, mFlatResponseHeaders.Elements(), *countWritten);
+
+ mFlatResponseHeaders.RemoveElementsAt(0, *countWritten);
+ if (mFlatResponseHeaders.Length() == 0) {
+ if (mRecvState == READING_INTERIM_HEADERS) {
+ // neqo makes sure that fin cannot be received before the final
+ // headers are received.
+ MOZ_ASSERT(!mFin);
+ mRecvState = BEFORE_HEADERS;
+ } else {
+ mRecvState = mFin ? RECEIVED_FIN : READING_DATA;
+ }
+ }
+
+ if (*countWritten == 0) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ mTotalRead += *countWritten;
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM,
+ mTotalRead);
+ }
+ } break;
+ case READING_DATA: {
+ rv = mSession->ReadResponseData(mStreamId, buf, count, countWritten,
+ &mFin);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ if (*countWritten == 0) {
+ if (mFin) {
+ mRecvState = RECV_DONE;
+ rv = NS_BASE_STREAM_CLOSED;
+ } else {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ } else {
+ mTotalRead += *countWritten;
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM,
+ mTotalRead);
+
+ if (mFin) {
+ mRecvState = RECEIVED_FIN;
+ }
+ }
+ } break;
+ case RECEIVED_FIN:
+ rv = NS_BASE_STREAM_CLOSED;
+ mRecvState = RECV_DONE;
+ break;
+ case RECV_DONE:
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // Remember the error received from lower layers. A stream pipe may overwrite
+ // it.
+ // If rv == NS_OK this will reset mSocketInCondition.
+ mSocketInCondition = rv;
+
+ return rv;
+}
+
+nsresult Http3Stream::ReadSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mRecvState == RECV_DONE) {
+ // Don't transmit any request frames if the peer cannot respond or respone
+ // is already done.
+ LOG3(
+ ("Http3Stream %p ReadSegments request stream aborted due to"
+ " response side closure\n",
+ this));
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t transactionBytes;
+ bool again = true;
+ do {
+ transactionBytes = 0;
+ rv = mSocketOutCondition = NS_OK;
+ LOG(("Http3Stream::ReadSegments state=%d [this=%p]", mSendState, this));
+ switch (mSendState) {
+ case WAITING_TO_ACTIVATE: {
+ // A transaction that had already generated its headers before it was
+ // queued at the session level (due to concurrency concerns) may not
+ // call onReadSegment off the ReadSegments() stack above.
+ LOG3(
+ ("Http3Stream %p ReadSegments forcing OnReadSegment call\n", this));
+ uint32_t wasted = 0;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ if (mSendState != SENDING_BODY) {
+ break;
+ }
+ }
+ // If we are in state SENDING_BODY we can continue sending data.
+ [[fallthrough]];
+ case PREPARING_HEADERS:
+ case SENDING_BODY: {
+ rv = mTransaction->ReadSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again);
+ } break;
+ default:
+ transactionBytes = 0;
+ rv = NS_OK;
+ break;
+ }
+
+ LOG(("Http3Stream::ReadSegments rv=0x%" PRIx32 " read=%u sock-cond=%" PRIx32
+ " again=%d [this=%p]",
+ static_cast<uint32_t>(rv), transactionBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again, this));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_WAITING_FOR, 0);
+ mSession->CloseSendingSide(mStreamId);
+ mSendState = SEND_DONE;
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_TRANS,
+ mSendingBlockedByFlowControlCount);
+
+#ifdef DEBUG
+ MOZ_ASSERT(mRequestBodyLenSent == mRequestBodyLenExpected);
+#endif
+ rv = NS_OK;
+ again = false;
+ }
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+ return rv;
+}
+
+nsresult Http3Stream::WriteSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Stream::WriteSegments [this=%p]", this));
+ nsresult rv = NS_OK;
+ uint32_t countWrittenSingle = 0;
+ bool again = true;
+
+ do {
+ mSocketInCondition = NS_OK;
+ countWrittenSingle = 0;
+ rv = mTransaction->WriteSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &countWrittenSingle, &again);
+ LOG(("Http3Stream::WriteSegments rv=0x%" PRIx32
+ " countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]",
+ static_cast<uint32_t>(rv), countWrittenSingle,
+ static_cast<uint32_t>(mSocketInCondition), this));
+ if (mTransaction->IsDone()) {
+ // If a transaction has read the amount of data specified in
+ // Content-Length it is marked as done.The Http3Stream should be
+ // marked as done as well to start the process of cleanup and
+ // closure.
+ mRecvState = RECV_DONE;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketInCondition)) {
+ if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+bool Http3Stream::Do0RTT() {
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = mTransaction->Do0RTT();
+ return mAttempting0RTT;
+}
+
+nsresult Http3Stream::Finish0RTT(bool aRestart) {
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = false;
+ nsresult rv = mTransaction->Finish0RTT(aRestart, false);
+ if (aRestart) {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ trans->Refused0RTT();
+ }
+
+ // Reset Http3Sream states as well.
+ mSendState = PREPARING_HEADERS;
+ mRecvState = BEFORE_HEADERS;
+ mStreamId = UINT64_MAX;
+ mFlatHttpRequestHeaders = "";
+ mQueued = false;
+ mDataReceived = false;
+ mResetRecv = false;
+ mFlatResponseHeaders.TruncateLength(0);
+ mTotalSent = 0;
+ mTotalRead = 0;
+ mFin = false;
+ mSendingBlockedByFlowControlCount = 0;
+ mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return rv;
+}
+
+uint8_t Http3Stream::PriorityUrgency() {
+ if (!StaticPrefs::network_http_http3_priority()) {
+ // send default priority which is equivalent to sending no priority
+ return 3;
+ }
+
+ if (StaticPrefs::network_http_http3_send_background_tabs_deprioritization() &&
+ mCurrentBrowserId != mTransactionBrowserId) {
+ // Low priority
+ return 6;
+ }
+ return mPriorityUrgency;
+}
+
+bool Http3Stream::PriorityIncremental() {
+ if (!StaticPrefs::network_http_http3_priority()) {
+ // send default priority which is equivalent to sending no priority
+ return false;
+ }
+ return mPriorityIncremental;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http3Stream.h b/netwerk/protocol/http/Http3Stream.h
new file mode 100644
index 0000000000..1048b20ef5
--- /dev/null
+++ b/netwerk/protocol/http/Http3Stream.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; 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_Http3Stream_h
+#define mozilla_net_Http3Stream_h
+
+#include "nsAHttpTransaction.h"
+#include "ARefBase.h"
+#include "Http3StreamBase.h"
+#include "mozilla/WeakPtr.h"
+#include "nsIClassOfService.h"
+
+namespace mozilla {
+namespace net {
+
+class Http3Session;
+
+class Http3Stream final : public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public Http3StreamBase {
+ public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ // for RefPtr
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http3Stream, override)
+
+ Http3Stream(nsAHttpTransaction*, Http3Session*, const ClassOfService&,
+ uint64_t);
+
+ Http3WebTransportSession* GetHttp3WebTransportSession() override {
+ return nullptr;
+ }
+ Http3WebTransportStream* GetHttp3WebTransportStream() override {
+ return nullptr;
+ }
+ Http3Stream* GetHttp3Stream() override { return this; }
+
+ nsresult TryActivating();
+
+ void CurrentBrowserIdChanged(uint64_t id);
+
+ [[nodiscard]] nsresult ReadSegments() override;
+ [[nodiscard]] nsresult WriteSegments() override;
+
+ bool Done() const override { return mRecvState == RECV_DONE; }
+
+ void Close(nsresult aResult) override;
+ bool RecvdData() const { return mDataReceived; }
+
+ void StopSending();
+
+ void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) override;
+
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT() override;
+ nsresult Finish0RTT(bool aRestart) override;
+
+ uint8_t PriorityUrgency();
+ bool PriorityIncremental();
+
+ private:
+ ~Http3Stream() = default;
+
+ bool GetHeadersString(const char* buf, uint32_t avail, uint32_t* countUsed);
+ nsresult StartRequest();
+
+ void SetPriority(uint32_t aCos);
+ void SetIncremental(bool incremental);
+
+ /**
+ * SendStreamState:
+ * While sending request:
+ * - PREPARING_HEADERS:
+ * In this state we are collecting the headers and in some cases also
+ * waiting to be able to create a new stream.
+ * We need to read all headers into a buffer before calling
+ * Http3Session::TryActivating. Neqo may not have place for a new
+ * stream if it hits MAX_STREAMS limit. In that case the steam will be
+ * queued and dequeue when neqo can again create new stream
+ * (RequestsCreatable will be called).
+ * If transaction has data to send state changes to SENDING_BODY,
+ * otherwise the state transfers to READING_HEADERS.
+ * - SENDING_BODY:
+ * The stream will be in this state while the transaction is sending
+ * request body. Http3Session::SendRequestBody will be call to give
+ * the data to neqo.
+ * After SENDING_BODY, the state transfers to READING_HEADERS.
+ * - EARLY_RESPONSE:
+ * The server may send STOP_SENDING frame with error HTTP_NO_ERROR.
+ * That error means that the server is not interested in the request
+ * body. In this state the server will just ignore the request body.
+ **/
+ enum SendStreamState {
+ PREPARING_HEADERS,
+ WAITING_TO_ACTIVATE,
+ SENDING_BODY,
+ EARLY_RESPONSE,
+ SEND_DONE
+ } mSendState{PREPARING_HEADERS};
+
+ /**
+ * RecvStreamState:
+ * - BEFORE_HEADERS:
+ * The stream has not received headers yet.
+ * - READING_HEADERS and READING_INTERIM_HEADERS:
+ * In this state Http3Session::ReadResponseHeaders will be called to
+ * read the response headers. All headers will be read at once into
+ * mFlatResponseHeaders. The stream will be in this state until all
+ * headers are given to the transaction.
+ * If the steam was in the READING_INTERIM_HEADERS state it will
+ * change back to the BEFORE_HEADERS state. If the stream has been
+ * in the READING_HEADERS state it will change to the READING_DATA
+ * state. If the stream was closed by the server after sending headers
+ * the stream will transit into RECEIVED_FIN state. neqo makes sure
+ * that response headers and data are received in the right order,
+ * e.g. 1xx cannot be received after a non-1xx response, fin cannot
+ * follow 1xx response, etc.
+ * - READING_DATA:
+ * In this state Http3Session::ReadResponseData will be called and the
+ * response body will be given to the transaction.
+ * This state may transfer to RECEIVED_FIN or DONE state.
+ * - DONE:
+ * The transaction is done.
+ **/
+ enum RecvStreamState {
+ BEFORE_HEADERS,
+ READING_HEADERS,
+ READING_INTERIM_HEADERS,
+ READING_DATA,
+ RECEIVED_FIN,
+ RECV_DONE
+ } mRecvState{BEFORE_HEADERS};
+
+ nsCString mFlatHttpRequestHeaders;
+ bool mDataReceived{false};
+ nsTArray<uint8_t> mFlatResponseHeaders;
+ uint64_t mTransactionBrowserId{0};
+ uint64_t mCurrentBrowserId;
+ uint8_t mPriorityUrgency{3}; // urgency field of http priority
+ bool mPriorityIncremental{false};
+
+ // For Progress Events
+ uint64_t mTotalSent{0};
+ uint64_t mTotalRead{0};
+
+ bool mAttempting0RTT = false;
+
+ uint32_t mSendingBlockedByFlowControlCount = 0;
+
+ nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+
+#ifdef DEBUG
+ uint32_t mRequestBodyLenExpected{0};
+ uint32_t mRequestBodyLenSent{0};
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http3Stream_h
diff --git a/netwerk/protocol/http/Http3StreamBase.h b/netwerk/protocol/http/Http3StreamBase.h
new file mode 100644
index 0000000000..9ca85de98e
--- /dev/null
+++ b/netwerk/protocol/http/Http3StreamBase.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; 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_Http3StreamBase_h
+#define mozilla_net_Http3StreamBase_h
+
+#include "nsAHttpTransaction.h"
+#include "ARefBase.h"
+#include "mozilla/WeakPtr.h"
+#include "nsIClassOfService.h"
+
+namespace mozilla::net {
+
+class Http3Session;
+class Http3Stream;
+class Http3WebTransportSession;
+class Http3WebTransportStream;
+
+class Http3StreamBase : public SupportsWeakPtr, public ARefBase {
+ public:
+ Http3StreamBase(nsAHttpTransaction* trans, Http3Session* session);
+
+ virtual Http3WebTransportSession* GetHttp3WebTransportSession() = 0;
+ virtual Http3WebTransportStream* GetHttp3WebTransportStream() = 0;
+ virtual Http3Stream* GetHttp3Stream() = 0;
+
+ bool HasStreamId() const { return mStreamId != UINT64_MAX; }
+ uint64_t StreamId() const { return mStreamId; }
+
+ [[nodiscard]] virtual nsresult ReadSegments() = 0;
+ [[nodiscard]] virtual nsresult WriteSegments() = 0;
+
+ virtual bool Done() const = 0;
+
+ virtual void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) = 0;
+
+ void SetQueued(bool aStatus) { mQueued = aStatus; }
+ bool Queued() const { return mQueued; }
+
+ virtual void Close(nsresult aResult) = 0;
+
+ nsAHttpTransaction* Transaction() { return mTransaction; }
+
+ // Mirrors nsAHttpTransaction
+ virtual bool Do0RTT() { return false; }
+ virtual nsresult Finish0RTT(bool aRestart) { return NS_OK; }
+
+ virtual bool RecvdFin() const { return mFin; }
+ virtual bool RecvdReset() const { return mResetRecv; }
+ virtual void SetRecvdReset() { mResetRecv = true; }
+
+ protected:
+ ~Http3StreamBase();
+
+ uint64_t mStreamId{UINT64_MAX};
+ int64_t mSendOrder{0};
+ bool mSendOrderIsSet{false};
+ RefPtr<nsAHttpTransaction> mTransaction;
+ RefPtr<Http3Session> mSession;
+ bool mQueued{false};
+ bool mFin{false};
+ bool mResetRecv{false};
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http3StreamBase_h
diff --git a/netwerk/protocol/http/Http3WebTransportSession.cpp b/netwerk/protocol/http/Http3WebTransportSession.cpp
new file mode 100644
index 0000000000..9ef4da70c0
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportSession.cpp
@@ -0,0 +1,518 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+#include "Http3WebTransportSession.h"
+#include "Http3WebTransportStream.h"
+#include "Http3Session.h"
+#include "Http3Stream.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpTransaction.h"
+#include "nsIClassOfService.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "nsIOService.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla::net {
+
+Http3WebTransportSession::Http3WebTransportSession(nsAHttpTransaction* trans,
+ Http3Session* aHttp3Session)
+ : Http3StreamBase(trans, aHttp3Session) {}
+
+Http3WebTransportSession::~Http3WebTransportSession() = default;
+
+nsresult Http3WebTransportSession::ReadSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::ReadSegments %p mSendState=%d mRecvState=%d",
+ this, mSendState, mRecvState));
+ if (mSendState == PROCESSING_DATAGRAM) {
+ return NS_OK;
+ }
+
+ if ((mRecvState == RECV_DONE) || (mRecvState == ACTIVE) ||
+ (mRecvState == CLOSE_PENDING)) {
+ // Don't transmit any request frames if the peer cannot respond or respone
+ // is already done.
+ LOG3((
+ "Http3WebTransportSession %p ReadSegments request stream aborted due to"
+ " response side closure\n",
+ this));
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t transactionBytes;
+ bool again = true;
+ do {
+ transactionBytes = 0;
+ rv = mSocketOutCondition = NS_OK;
+ LOG(("Http3WebTransportSession::ReadSegments state=%d [this=%p]",
+ mSendState, this));
+ switch (mSendState) {
+ case PREPARING_HEADERS: {
+ rv = mTransaction->ReadSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again);
+ } break;
+ case WAITING_TO_ACTIVATE: {
+ // A transaction that had already generated its headers before it was
+ // queued at the session level (due to concurrency concerns) may not
+ // call onReadSegment off the ReadSegments() stack above.
+ LOG3(
+ ("Http3WebTransportSession %p ReadSegments forcing OnReadSegment "
+ "call\n",
+ this));
+ uint32_t wasted = 0;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ } break;
+ default:
+ transactionBytes = 0;
+ rv = NS_OK;
+ break;
+ }
+
+ LOG(("Http3WebTransportSession::ReadSegments rv=0x%" PRIx32
+ " read=%u sock-cond=%" PRIx32 " again=%d [this=%p]",
+ static_cast<uint32_t>(rv), transactionBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again, this));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_WAITING_FOR, 0);
+
+ mSendState = PROCESSING_DATAGRAM;
+ rv = NS_OK;
+ again = false;
+ }
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+ return rv;
+}
+
+bool Http3WebTransportSession::ConsumeHeaders(const char* buf, uint32_t avail,
+ uint32_t* countUsed) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3WebTransportSession::ConsumeHeaders %p avail=%u.", this, avail));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+ // We can use the simple double crlf because firefox is the
+ // only client we are parsing
+ int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+ if (endHeader == kNotFound) {
+ // We don't have all the headers yet
+ LOG3(
+ ("Http3WebTransportSession::ConsumeHeaders %p "
+ "Need more header bytes. Len = %zu",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return false;
+ }
+
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+
+ return true;
+}
+
+nsresult Http3WebTransportSession::TryActivating() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::TryActivating [this=%p]", this));
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ nsAutoCString host;
+ nsresult rv = head->GetHeader(nsHttp::Host, host);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+ nsAutoCString path;
+ head->Path(path);
+
+ return mSession->TryActivating(""_ns, ""_ns, host, path,
+ mFlatHttpRequestHeaders, &mStreamId, this);
+}
+
+nsresult Http3WebTransportSession::OnReadSegment(const char* buf,
+ uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportSession::OnReadSegment count=%u state=%d [this=%p]",
+ count, mSendState, this));
+
+ nsresult rv = NS_OK;
+
+ switch (mSendState) {
+ case PREPARING_HEADERS: {
+ if (!ConsumeHeaders(buf, count, countRead)) {
+ break;
+ }
+ mSendState = WAITING_TO_ACTIVATE;
+ }
+ [[fallthrough]];
+ case WAITING_TO_ACTIVATE:
+ rv = TryActivating();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3WebTransportSession::OnReadSegment %p cannot activate now. "
+ "queued.\n",
+ this));
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3WebTransportSession::OnReadSegment %p cannot activate "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ break;
+ }
+
+ // Successfully activated.
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO, 0);
+
+ mSendState = PROCESSING_DATAGRAM;
+ break;
+ default:
+ MOZ_ASSERT(false, "We are done sending this request!");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ mSocketOutCondition = rv;
+
+ return mSocketOutCondition;
+}
+
+nsresult Http3WebTransportSession::WriteSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::WriteSegments [this=%p]", this));
+ nsresult rv = NS_OK;
+ uint32_t countWrittenSingle = 0;
+ bool again = true;
+
+ if (mRecvState == CLOSE_PENDING) {
+ mSession->CloseWebTransport(mStreamId, mStatus, mReason);
+ mRecvState = RECV_DONE;
+ // This will closed the steam because the stream is Done().
+ return NS_OK;
+ }
+
+ do {
+ mSocketInCondition = NS_OK;
+ countWrittenSingle = 0;
+ rv = mTransaction->WriteSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &countWrittenSingle, &again);
+ LOG(("Http3WebTransportSession::WriteSegments rv=0x%" PRIx32
+ " countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]",
+ static_cast<uint32_t>(rv), countWrittenSingle,
+ static_cast<uint32_t>(mSocketInCondition), this));
+ if (mTransaction->IsDone()) {
+ // An HTTP transaction used for setting up a WebTransport session will
+ // receive only response headers and afterward, it will be marked as
+ // done. At this point, the session negotiation has finished and the
+ // WebTransport session transfers into the ACTIVE state.
+ mRecvState = ACTIVE;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketInCondition)) {
+ if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+void Http3WebTransportSession::SetResponseHeaders(
+ nsTArray<uint8_t>& aResponseHeaders, bool fin, bool interim) {
+ MOZ_ASSERT(mRecvState == BEFORE_HEADERS ||
+ mRecvState == READING_INTERIM_HEADERS);
+ mFlatResponseHeaders.AppendElements(aResponseHeaders);
+ mRecvState = (interim) ? READING_INTERIM_HEADERS : READING_HEADERS;
+}
+
+nsresult Http3WebTransportSession::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportSession::OnWriteSegment [this=%p, state=%d", this,
+ mRecvState));
+ nsresult rv = NS_OK;
+ switch (mRecvState) {
+ case BEFORE_HEADERS: {
+ *countWritten = 0;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } break;
+ case READING_HEADERS:
+ case READING_INTERIM_HEADERS: {
+ // SetResponseHeaders should have been previously called.
+ MOZ_ASSERT(!mFlatResponseHeaders.IsEmpty(), "Headers empty!");
+ *countWritten = (mFlatResponseHeaders.Length() > count)
+ ? count
+ : mFlatResponseHeaders.Length();
+ memcpy(buf, mFlatResponseHeaders.Elements(), *countWritten);
+
+ mFlatResponseHeaders.RemoveElementsAt(0, *countWritten);
+ if (mFlatResponseHeaders.Length() == 0) {
+ if (mRecvState == READING_INTERIM_HEADERS) {
+ // neqo makes sure that fin cannot be received before the final
+ // headers are received.
+ mRecvState = BEFORE_HEADERS;
+ } else {
+ mRecvState = ACTIVE;
+ }
+ }
+
+ if (*countWritten == 0) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM,
+ 0);
+ }
+ } break;
+ case ACTIVE:
+ case CLOSE_PENDING:
+ case RECV_DONE:
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // Remember the error received from lower layers. A stream pipe may overwrite
+ // it.
+ // If rv == NS_OK this will reset mSocketInCondition.
+ mSocketInCondition = rv;
+
+ return rv;
+}
+
+void Http3WebTransportSession::Close(nsresult aResult) {
+ LOG(("Http3WebTransportSession::Close %p", this));
+ if (mListener) {
+ mListener->OnSessionClosed(NS_SUCCEEDED(aResult), 0, ""_ns);
+ mListener = nullptr;
+ }
+ if (mTransaction) {
+ mTransaction->Close(aResult);
+ mTransaction = nullptr;
+ }
+ mRecvState = RECV_DONE;
+ mSendState = SEND_DONE;
+
+ if (mSession) {
+ mSession->CloseWebTransportConn();
+ mSession = nullptr;
+ }
+}
+
+void Http3WebTransportSession::OnSessionClosed(bool aCleanly, uint32_t aStatus,
+ const nsACString& aReason) {
+ if (mTransaction) {
+ mTransaction->Close(NS_BASE_STREAM_CLOSED);
+ mTransaction = nullptr;
+ }
+ if (mListener) {
+ mListener->OnSessionClosed(aCleanly, aStatus, aReason);
+ mListener = nullptr;
+ }
+ mRecvState = RECV_DONE;
+ mSendState = SEND_DONE;
+
+ mSession->CloseWebTransportConn();
+}
+
+void Http3WebTransportSession::CloseSession(uint32_t aStatus,
+ const nsACString& aReason) {
+ if ((mRecvState != CLOSE_PENDING) && (mRecvState != RECV_DONE)) {
+ mStatus = aStatus;
+ mReason = aReason;
+ mSession->ConnectSlowConsumer(this);
+ mRecvState = CLOSE_PENDING;
+ mSendState = SEND_DONE;
+ }
+ mListener = nullptr;
+}
+
+void Http3WebTransportSession::TransactionIsDone(nsresult aResult) {
+ mTransaction->Close(aResult);
+ mTransaction = nullptr;
+}
+
+void Http3WebTransportSession::CreateOutgoingBidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback) {
+ return CreateStreamInternal(true, std::move(aCallback));
+}
+
+void Http3WebTransportSession::CreateOutgoingUnidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback) {
+ return CreateStreamInternal(false, std::move(aCallback));
+}
+
+void Http3WebTransportSession::CreateStreamInternal(
+ bool aBidi,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback) {
+ LOG(("Http3WebTransportSession::CreateStreamInternal this=%p aBidi=%d", this,
+ aBidi));
+ if (mRecvState != ACTIVE) {
+ aCallback(Err(NS_ERROR_NOT_AVAILABLE));
+ return;
+ }
+
+ RefPtr<Http3WebTransportStream> stream =
+ aBidi ? new Http3WebTransportStream(mSession, mStreamId,
+ WebTransportStreamType::BiDi,
+ std::move(aCallback))
+ : new Http3WebTransportStream(mSession, mStreamId,
+ WebTransportStreamType::UniDi,
+ std::move(aCallback));
+ mSession->StreamHasDataToWrite(stream);
+ // Put the newly created stream in to |mStreams| to keep it alive.
+ mStreams.AppendElement(std::move(stream));
+}
+
+// This is called by Http3Session::TryActivatingWebTransportStream. When called,
+// this means a WebTransport stream is successfully activated and the stream
+// will be managed by Http3Session.
+void Http3WebTransportSession::RemoveWebTransportStream(
+ Http3WebTransportStream* aStream) {
+ LOG(
+ ("Http3WebTransportSession::RemoveWebTransportStream "
+ "this=%p aStream=%p",
+ this, aStream));
+ DebugOnly<bool> existed = mStreams.RemoveElement(aStream);
+ MOZ_ASSERT(existed);
+}
+
+already_AddRefed<Http3WebTransportStream>
+Http3WebTransportSession::OnIncomingWebTransportStream(
+ WebTransportStreamType aType, uint64_t aId) {
+ LOG(
+ ("Http3WebTransportSession::OnIncomingWebTransportStream "
+ "this=%p",
+ this));
+
+ if (mRecvState != ACTIVE) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mTransaction);
+ RefPtr<Http3WebTransportStream> stream =
+ new Http3WebTransportStream(mSession, mStreamId, aType, aId);
+ if (NS_FAILED(stream->InitInputPipe())) {
+ return nullptr;
+ }
+
+ if (aType == WebTransportStreamType::BiDi) {
+ if (NS_FAILED(stream->InitOutputPipe())) {
+ return nullptr;
+ }
+ }
+
+ if (!mListener) {
+ return nullptr;
+ }
+
+ mListener->OnIncomingStreamAvailableInternal(stream);
+ return stream.forget();
+}
+
+void Http3WebTransportSession::SendDatagram(nsTArray<uint8_t>&& aData,
+ uint64_t aTrackingId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::SendDatagram this=%p", this));
+ if (mSendState != PROCESSING_DATAGRAM) {
+ return;
+ }
+
+ mSession->SendDatagram(this, aData, aTrackingId);
+ mSession->StreamHasDataToWrite(this);
+}
+
+void Http3WebTransportSession::OnDatagramReceived(nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::OnDatagramReceived this=%p", this));
+ if (mRecvState != ACTIVE || !mListener) {
+ return;
+ }
+
+ mListener->OnDatagramReceivedInternal(std::move(aData));
+}
+
+void Http3WebTransportSession::GetMaxDatagramSize() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mRecvState != ACTIVE || !mListener) {
+ return;
+ }
+
+ uint64_t size = mSession->MaxDatagramSize(mStreamId);
+ mListener->OnMaxDatagramSize(size);
+}
+
+void Http3WebTransportSession::OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::OnOutgoingDatagramOutCome this=%p id=%" PRIx64
+ ", outCome=%d mRecvState=%d",
+ this, aId, static_cast<uint32_t>(aOutCome), mRecvState));
+ if (mRecvState != ACTIVE || !mListener || !aId) {
+ return;
+ }
+
+ mListener->OnOutgoingDatagramOutCome(aId, aOutCome);
+}
+
+void Http3WebTransportSession::OnStreamStopSending(uint64_t aId,
+ nsresult aError) {
+ LOG(("OnStreamStopSending id:%" PRId64, aId));
+ if (!mListener) {
+ return;
+ }
+
+ mListener->OnStopSending(aId, aError);
+}
+
+void Http3WebTransportSession::OnStreamReset(uint64_t aId, nsresult aError) {
+ LOG(("OnStreamReset id:%" PRId64, aId));
+ if (!mListener) {
+ return;
+ }
+
+ mListener->OnResetReceived(aId, aError);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http3WebTransportSession.h b/netwerk/protocol/http/Http3WebTransportSession.h
new file mode 100644
index 0000000000..19afb1f172
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportSession.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; 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_Http3WebTransportSession_h
+#define mozilla_net_Http3WebTransportSession_h
+
+#include "ARefBase.h"
+#include "Http3StreamBase.h"
+#include "nsIWebTransport.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/net/NeqoHttp3Conn.h"
+
+namespace mozilla::net {
+
+class Http3Session;
+
+// TODO Http3WebTransportSession is very similar to Http3Stream. It should
+// be built on top of it with a couple of small changes. The docs will be added
+// when this is implemented.
+
+class Http3WebTransportSession final : public Http3StreamBase,
+ public nsAHttpSegmentWriter,
+ public nsAHttpSegmentReader {
+ public:
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http3WebTransportSession, override)
+
+ Http3WebTransportSession(nsAHttpTransaction*, Http3Session*);
+
+ Http3WebTransportSession* GetHttp3WebTransportSession() override {
+ return this;
+ }
+ Http3WebTransportStream* GetHttp3WebTransportStream() override {
+ return nullptr;
+ }
+ Http3Stream* GetHttp3Stream() override { return nullptr; }
+
+ [[nodiscard]] nsresult ReadSegments() override;
+ [[nodiscard]] nsresult WriteSegments() override;
+
+ bool Done() const override { return mRecvState == RECV_DONE; }
+
+ void Close(nsresult aResult) override;
+
+ void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) override;
+ void SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* listener) {
+ mListener = listener;
+ }
+
+ nsresult TryActivating();
+ void TransactionIsDone(nsresult aResult);
+ void CloseSession(uint32_t aStatus, const nsACString& aReason);
+ void OnSessionClosed(bool aCleanly, uint32_t aStatus,
+ const nsACString& aReason);
+
+ void CreateOutgoingBidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+ void CreateOutgoingUnidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+ void RemoveWebTransportStream(Http3WebTransportStream* aStream);
+
+ already_AddRefed<Http3WebTransportStream> OnIncomingWebTransportStream(
+ WebTransportStreamType aType, uint64_t aId);
+
+ void SendDatagram(nsTArray<uint8_t>&& aData, uint64_t aTrackingId);
+
+ void OnDatagramReceived(nsTArray<uint8_t>&& aData);
+
+ void GetMaxDatagramSize();
+
+ void OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome);
+
+ void OnStreamStopSending(uint64_t aId, nsresult aError);
+ void OnStreamReset(uint64_t aId, nsresult aError);
+
+ private:
+ virtual ~Http3WebTransportSession();
+
+ bool ConsumeHeaders(const char* buf, uint32_t avail, uint32_t* countUsed);
+
+ void CreateStreamInternal(
+ bool aBidi,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+
+ enum RecvStreamState {
+ BEFORE_HEADERS,
+ READING_HEADERS,
+ READING_INTERIM_HEADERS,
+ ACTIVE,
+ CLOSE_PENDING,
+ RECV_DONE
+ } mRecvState{BEFORE_HEADERS};
+
+ enum SendStreamState {
+ PREPARING_HEADERS,
+ WAITING_TO_ACTIVATE,
+ PROCESSING_DATAGRAM,
+ SEND_DONE,
+ } mSendState{PREPARING_HEADERS};
+
+ nsCString mFlatHttpRequestHeaders;
+ nsTArray<uint8_t> mFlatResponseHeaders;
+ nsTArray<RefPtr<Http3WebTransportStream>> mStreams;
+
+ nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<WebTransportSessionEventListener> mListener;
+ uint32_t mStatus{0};
+ nsCString mReason;
+};
+
+} // namespace mozilla::net
+#endif // mozilla_net_Http3WebTransportSession_h
diff --git a/netwerk/protocol/http/Http3WebTransportStream.cpp b/netwerk/protocol/http/Http3WebTransportStream.cpp
new file mode 100644
index 0000000000..c76d8ef798
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportStream.cpp
@@ -0,0 +1,667 @@
+/* -*- Mode: C++; tab-width: 8; 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 "Http3WebTransportStream.h"
+
+#include "HttpLog.h"
+#include "Http3Session.h"
+#include "Http3WebTransportSession.h"
+#include "mozilla/TimeStamp.h"
+#include "nsHttpHandler.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsSocketTransportService2.h"
+#include "nsIWebTransportStream.h"
+
+namespace mozilla::net {
+
+namespace {
+
+// This is an nsAHttpTransaction that does nothing.
+class DummyWebTransportStreamTransaction : public nsAHttpTransaction {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DummyWebTransportStreamTransaction() = default;
+
+ void SetConnection(nsAHttpConnection*) override {}
+ nsAHttpConnection* Connection() override { return nullptr; }
+ void GetSecurityCallbacks(nsIInterfaceRequestor**) override {}
+ void OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress) override {}
+ bool IsDone() override { return false; }
+ nsresult Status() override { return NS_OK; }
+ uint32_t Caps() override { return 0; }
+ [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*) override {
+ return NS_OK;
+ }
+ [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*) override {
+ return NS_OK;
+ }
+ void Close(nsresult reason) override {}
+ nsHttpConnectionInfo* ConnectionInfo() override { return nullptr; }
+ void SetProxyConnectFailed() override {}
+ nsHttpRequestHead* RequestHead() override { return nullptr; }
+ uint32_t Http1xTransactionCount() override { return 0; }
+ [[nodiscard]] nsresult TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) override {
+ return NS_OK;
+ }
+
+ private:
+ virtual ~DummyWebTransportStreamTransaction() = default;
+};
+
+NS_IMPL_ISUPPORTS(DummyWebTransportStreamTransaction, nsISupportsWeakReference)
+
+class WebTransportSendStreamStats : public nsIWebTransportSendStreamStats {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebTransportSendStreamStats(uint64_t aSent, uint64_t aAcked)
+ : mTimeStamp(TimeStamp::Now()),
+ mTotalSent(aSent),
+ mTotalAcknowledged(aAcked) {}
+
+ NS_IMETHOD GetTimestamp(mozilla::TimeStamp* aTimestamp) override {
+ *aTimestamp = mTimeStamp;
+ return NS_OK;
+ }
+ NS_IMETHOD GetBytesSent(uint64_t* aBytesSent) override {
+ *aBytesSent = mTotalSent;
+ return NS_OK;
+ }
+ NS_IMETHOD GetBytesAcknowledged(uint64_t* aBytesAcknowledged) override {
+ *aBytesAcknowledged = mTotalAcknowledged;
+ return NS_OK;
+ }
+
+ private:
+ virtual ~WebTransportSendStreamStats() = default;
+
+ TimeStamp mTimeStamp;
+ uint64_t mTotalSent;
+ uint64_t mTotalAcknowledged;
+};
+
+NS_IMPL_ISUPPORTS(WebTransportSendStreamStats, nsIWebTransportSendStreamStats)
+
+class WebTransportReceiveStreamStats
+ : public nsIWebTransportReceiveStreamStats {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebTransportReceiveStreamStats(uint64_t aReceived)
+ : mTimeStamp(TimeStamp::Now()), mTotalReceived(aReceived) {}
+
+ NS_IMETHOD GetTimestamp(mozilla::TimeStamp* aTimestamp) override {
+ *aTimestamp = mTimeStamp;
+ return NS_OK;
+ }
+ NS_IMETHOD GetBytesReceived(uint64_t* aByteReceived) override {
+ *aByteReceived = mTotalReceived;
+ return NS_OK;
+ }
+
+ private:
+ virtual ~WebTransportReceiveStreamStats() = default;
+
+ TimeStamp mTimeStamp;
+ uint64_t mTotalReceived;
+};
+
+NS_IMPL_ISUPPORTS(WebTransportReceiveStreamStats,
+ nsIWebTransportReceiveStreamStats)
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(Http3WebTransportStream, nsIInputStreamCallback)
+
+Http3WebTransportStream::Http3WebTransportStream(
+ Http3Session* aSession, uint64_t aSessionId, WebTransportStreamType aType,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback)
+ : Http3StreamBase(new DummyWebTransportStreamTransaction(), aSession),
+ mSessionId(aSessionId),
+ mStreamType(aType),
+ mStreamRole(OUTGOING),
+ mStreamReadyCallback(std::move(aCallback)) {
+ LOG(("Http3WebTransportStream outgoing ctor %p", this));
+}
+
+Http3WebTransportStream::Http3WebTransportStream(Http3Session* aSession,
+ uint64_t aSessionId,
+ WebTransportStreamType aType,
+ uint64_t aStreamId)
+ : Http3StreamBase(new DummyWebTransportStreamTransaction(), aSession),
+ mSessionId(aSessionId),
+ mStreamType(aType),
+ mStreamRole(INCOMING),
+ // WAITING_DATA indicates we are waiting
+ // Http3WebTransportStream::OnInputStreamReady to be called.
+ mSendState(WAITING_DATA),
+ mStreamReadyCallback(nullptr) {
+ LOG(("Http3WebTransportStream incoming ctor %p", this));
+ mStreamId = aStreamId;
+}
+
+Http3WebTransportStream::~Http3WebTransportStream() {
+ LOG(("Http3WebTransportStream dtor %p", this));
+}
+
+nsresult Http3WebTransportStream::TryActivating() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mSession->TryActivatingWebTransportStream(&mStreamId, this);
+}
+
+NS_IMETHODIMP Http3WebTransportStream::OnInputStreamReady(
+ nsIAsyncInputStream* aStream) {
+ LOG1(
+ ("Http3WebTransportStream::OnInputStreamReady [this=%p stream=%p "
+ "state=%d]",
+ this, aStream, mSendState));
+ if (mSendState == SEND_DONE) {
+ // already closed
+ return NS_OK;
+ }
+
+ mSendState = SENDING;
+ mSession->StreamHasDataToWrite(this);
+ return NS_OK;
+}
+
+nsresult Http3WebTransportStream::InitOutputPipe() {
+ nsCOMPtr<nsIAsyncOutputStream> out;
+ nsCOMPtr<nsIAsyncInputStream> in;
+ NS_NewPipe2(getter_AddRefs(in), getter_AddRefs(out), true, true,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mSendStreamPipeIn = std::move(in);
+ mSendStreamPipeOut = std::move(out);
+ }
+
+ nsresult rv =
+ mSendStreamPipeIn->AsyncWait(this, 0, 0, gSocketTransportService);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mSendState = WAITING_DATA;
+ return NS_OK;
+}
+
+nsresult Http3WebTransportStream::InitInputPipe() {
+ nsCOMPtr<nsIAsyncOutputStream> out;
+ nsCOMPtr<nsIAsyncInputStream> in;
+ NS_NewPipe2(getter_AddRefs(in), getter_AddRefs(out), true, true,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mReceiveStreamPipeIn = std::move(in);
+ mReceiveStreamPipeOut = std::move(out);
+ }
+
+ mRecvState = READING;
+ return NS_OK;
+}
+
+void Http3WebTransportStream::GetWriterAndReader(
+ nsIAsyncOutputStream** aOutOutputStream,
+ nsIAsyncInputStream** aOutInputStream) {
+ nsCOMPtr<nsIAsyncOutputStream> output;
+ nsCOMPtr<nsIAsyncInputStream> input;
+ {
+ MutexAutoLock lock(mMutex);
+ output = mSendStreamPipeOut;
+ input = mReceiveStreamPipeIn;
+ }
+
+ output.forget(aOutOutputStream);
+ input.forget(aOutInputStream);
+}
+
+already_AddRefed<nsIWebTransportSendStreamStats>
+Http3WebTransportStream::GetSendStreamStats() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats =
+ new WebTransportSendStreamStats(mTotalSent, mTotalAcknowledged);
+ return stats.forget();
+}
+
+already_AddRefed<nsIWebTransportReceiveStreamStats>
+Http3WebTransportStream::GetReceiveStreamStats() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats =
+ new WebTransportReceiveStreamStats(mTotalReceived);
+ return stats.forget();
+}
+
+nsresult Http3WebTransportStream::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportStream::OnReadSegment count=%u state=%d [this=%p]",
+ count, mSendState, this));
+
+ nsresult rv = NS_OK;
+
+ switch (mSendState) {
+ case WAITING_TO_ACTIVATE:
+ rv = TryActivating();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p cannot activate now. "
+ "queued.\n",
+ this));
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p cannot activate "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ mStreamReadyCallback(Err(rv));
+ mStreamReadyCallback = nullptr;
+ break;
+ }
+
+ rv = InitOutputPipe();
+ if (NS_SUCCEEDED(rv) && mStreamType == WebTransportStreamType::BiDi) {
+ rv = InitInputPipe();
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p failed to create pipe "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ mSendState = SEND_DONE;
+ mStreamReadyCallback(Err(rv));
+ mStreamReadyCallback = nullptr;
+ break;
+ }
+
+ // Successfully activated.
+ mStreamReadyCallback(RefPtr{this});
+ mStreamReadyCallback = nullptr;
+ break;
+ case SENDING: {
+ rv = mSession->SendRequestBody(mStreamId, buf, count, countRead);
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p sending body returns "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ mTotalSent += *countRead;
+ } break;
+ case WAITING_DATA:
+ // Still waiting
+ LOG3((
+ "Http3WebTransportStream::OnReadSegment %p Still waiting for data...",
+ this));
+ break;
+ case SEND_DONE:
+ LOG3(("Http3WebTransportStream::OnReadSegment %p called after SEND_DONE ",
+ this));
+ MOZ_ASSERT(false, "We are done sending this request!");
+ MOZ_ASSERT(mStreamReadyCallback);
+ rv = NS_ERROR_UNEXPECTED;
+ mStreamReadyCallback(Err(rv));
+ mStreamReadyCallback = nullptr;
+ break;
+ }
+
+ mSocketOutCondition = rv;
+
+ return mSocketOutCondition;
+}
+
+// static
+nsresult Http3WebTransportStream::ReadRequestSegment(
+ nsIInputStream* stream, void* closure, const char* buf, uint32_t offset,
+ uint32_t count, uint32_t* countRead) {
+ Http3WebTransportStream* wtStream = (Http3WebTransportStream*)closure;
+ nsresult rv = wtStream->OnReadSegment(buf, count, countRead);
+ LOG(("Http3WebTransportStream::ReadRequestSegment %p read=%u", wtStream,
+ *countRead));
+ return rv;
+}
+
+nsresult Http3WebTransportStream::ReadSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::ReadSegments [this=%p]", this));
+ nsresult rv = NS_OK;
+ uint32_t sendBytes = 0;
+ bool again = true;
+ do {
+ sendBytes = 0;
+ rv = mSocketOutCondition = NS_OK;
+ LOG(("Http3WebTransportStream::ReadSegments state=%d [this=%p]", mSendState,
+ this));
+ switch (mSendState) {
+ case WAITING_TO_ACTIVATE: {
+ LOG3(
+ ("Http3WebTransportStream %p ReadSegments forcing OnReadSegment "
+ "call\n",
+ this));
+ uint32_t wasted = 0;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ if (mSendState != WAITING_DATA) {
+ break;
+ }
+ }
+ [[fallthrough]];
+ case WAITING_DATA:
+ [[fallthrough]];
+ case SENDING: {
+ if (mStreamRole == INCOMING &&
+ mStreamType == WebTransportStreamType::UniDi) {
+ rv = NS_OK;
+ break;
+ }
+ mSendState = SENDING;
+ rv = mSendStreamPipeIn->ReadSegments(ReadRequestSegment, this,
+ nsIOService::gDefaultSegmentSize,
+ &sendBytes);
+ } break;
+ case SEND_DONE: {
+ return NS_OK;
+ }
+ default:
+ sendBytes = 0;
+ rv = NS_OK;
+ break;
+ }
+
+ LOG(("Http3WebTransportStream::ReadSegments rv=0x%" PRIx32
+ " read=%u sock-cond=%" PRIx32 " again=%d mSendFin=%d [this=%p]",
+ static_cast<uint32_t>(rv), sendBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again, mSendFin, this));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED || !mPendingTasks.IsEmpty()) {
+ rv = NS_OK;
+ sendBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the writer didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSendState = WAITING_DATA;
+ rv = mSendStreamPipeIn->AsyncWait(this, 0, 0, gSocketTransportService);
+ }
+ again = false;
+
+ // Got a WebTransport specific error
+ if (rv >= NS_ERROR_WEBTRANSPORT_CODE_BASE &&
+ rv <= NS_ERROR_WEBTRANSPORT_CODE_END) {
+ uint8_t errorCode = GetWebTransportErrorFromNSResult(rv);
+ mSendState = SEND_DONE;
+ Reset(WebTransportErrorToHttp3Error(errorCode));
+ rv = NS_OK;
+ }
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!sendBytes) {
+ mSendState = SEND_DONE;
+ rv = NS_OK;
+ again = false;
+ if (!mPendingTasks.IsEmpty()) {
+ LOG(("Has pending tasks to do"));
+ nsTArray<std::function<void()>> tasks = std::move(mPendingTasks);
+ for (const auto& task : tasks) {
+ task();
+ }
+ }
+ // Tell the underlying stream we're done
+ SendFin();
+ }
+
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+ return rv;
+}
+
+nsresult Http3WebTransportStream::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportStream::OnWriteSegment [this=%p, state=%d", this,
+ static_cast<uint32_t>(mRecvState)));
+ nsresult rv = NS_OK;
+ switch (mRecvState) {
+ case READING: {
+ rv = mSession->ReadResponseData(mStreamId, buf, count, countWritten,
+ &mFin);
+ if (*countWritten == 0) {
+ if (mFin) {
+ mRecvState = RECV_DONE;
+ rv = NS_BASE_STREAM_CLOSED;
+ } else {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ } else {
+ mTotalReceived += *countWritten;
+ if (mFin) {
+ mRecvState = RECEIVED_FIN;
+ }
+ }
+ } break;
+ case RECEIVED_FIN:
+ rv = NS_BASE_STREAM_CLOSED;
+ mRecvState = RECV_DONE;
+ break;
+ case RECV_DONE:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ default:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ // Remember the error received from lower layers. A stream pipe may overwrite
+ // it.
+ // If rv == NS_OK this will reset mSocketInCondition.
+ mSocketInCondition = rv;
+
+ return rv;
+}
+
+// static
+nsresult Http3WebTransportStream::WritePipeSegment(nsIOutputStream* stream,
+ void* closure, char* buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t* countWritten) {
+ Http3WebTransportStream* self = (Http3WebTransportStream*)closure;
+
+ nsresult rv = self->OnWriteSegment(buf, count, countWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LOG(("Http3WebTransportStream::WritePipeSegment %p written=%u", self,
+ *countWritten));
+
+ return rv;
+}
+
+nsresult Http3WebTransportStream::WriteSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mReceiveStreamPipeOut) {
+ return NS_OK;
+ }
+
+ LOG(("Http3WebTransportStream::WriteSegments [this=%p]", this));
+
+ nsresult rv = NS_OK;
+ uint32_t countWrittenSingle = 0;
+ bool again = true;
+
+ do {
+ mSocketInCondition = NS_OK;
+ countWrittenSingle = 0;
+ rv = mReceiveStreamPipeOut->WriteSegments(WritePipeSegment, this,
+ nsIOService::gDefaultSegmentSize,
+ &countWrittenSingle);
+ LOG(("Http3WebTransportStream::WriteSegments rv=0x%" PRIx32
+ " countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]",
+ static_cast<uint32_t>(rv), countWrittenSingle,
+ static_cast<uint32_t>(mSocketInCondition), this));
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketInCondition)) {
+ if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketInCondition;
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ mReceiveStreamPipeOut->Close();
+ rv = NS_OK;
+ }
+ }
+ again = false;
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+bool Http3WebTransportStream::Done() const {
+ return mSendState == SEND_DONE && mRecvState == RECV_DONE;
+}
+
+void Http3WebTransportStream::Close(nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::Close [this=%p]", this));
+ mTransaction = nullptr;
+ if (mSendStreamPipeIn) {
+ mSendStreamPipeIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mSendStreamPipeIn->CloseWithStatus(aResult);
+ }
+ if (mReceiveStreamPipeOut) {
+ mReceiveStreamPipeOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mReceiveStreamPipeOut->CloseWithStatus(aResult);
+ }
+ mSendState = SEND_DONE;
+ mRecvState = RECV_DONE;
+ mSession = nullptr;
+}
+
+void Http3WebTransportStream::SendFin() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::SendFin [this=%p mSendState=%d]", this,
+ mSendState));
+
+ if (mSendFin || !mSession || mResetError) {
+ // Already closed.
+ return;
+ }
+
+ mSendFin = true;
+
+ switch (mSendState) {
+ case SENDING: {
+ mPendingTasks.AppendElement([self = RefPtr{this}]() {
+ self->mSession->CloseSendingSide(self->mStreamId);
+ });
+ } break;
+ case WAITING_DATA:
+ mSendState = SEND_DONE;
+ [[fallthrough]];
+ case SEND_DONE:
+ mSession->CloseSendingSide(mStreamId);
+ // StreamHasDataToWrite needs to be called to trigger ProcessOutput.
+ mSession->StreamHasDataToWrite(this);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid mSendState!");
+ break;
+ }
+}
+
+void Http3WebTransportStream::Reset(uint64_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::Reset [this=%p, mSendState=%d]", this,
+ mSendState));
+
+ if (mResetError || !mSession || mSendFin) {
+ // The stream is already reset.
+ return;
+ }
+
+ mResetError = Some(aErrorCode);
+
+ switch (mSendState) {
+ case SENDING: {
+ LOG(("Http3WebTransportStream::Reset [this=%p] reset after sending data",
+ this));
+ mPendingTasks.AppendElement([self = RefPtr{this}]() {
+ // "Reset" needs a special treatment here. If we are sending data and
+ // ResetWebTransportStream is called before Http3Session::ProcessOutput,
+ // neqo will drop the last piece of data.
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("Http3WebTransportStream::Reset", [self]() {
+ self->mSession->ResetWebTransportStream(self, *self->mResetError);
+ self->mSession->StreamHasDataToWrite(self);
+ self->mSession->ConnectSlowConsumer(self);
+ }));
+ });
+ } break;
+ case WAITING_DATA:
+ mSendState = SEND_DONE;
+ [[fallthrough]];
+ case SEND_DONE:
+ mSession->ResetWebTransportStream(this, *mResetError);
+ // StreamHasDataToWrite needs to be called to trigger ProcessOutput.
+ mSession->StreamHasDataToWrite(this);
+ mSession->ConnectSlowConsumer(this);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid mSendState!");
+ break;
+ }
+}
+
+void Http3WebTransportStream::SendStopSending(uint8_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::SendStopSending [this=%p, mSendState=%d]",
+ this, mSendState));
+
+ if (mSendState == WAITING_TO_ACTIVATE) {
+ return;
+ }
+
+ if (mStopSendingError || !mSession) {
+ return;
+ }
+
+ mStopSendingError = Some(aErrorCode);
+
+ mSession->StreamStopSending(this, *mStopSendingError);
+ // StreamHasDataToWrite needs to be called to trigger ProcessOutput.
+ mSession->StreamHasDataToWrite(this);
+}
+
+void Http3WebTransportStream::SetSendOrder(Maybe<int64_t> aSendOrder) {
+ mSession->SetSendOrder(this, aSendOrder);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http3WebTransportStream.h b/netwerk/protocol/http/Http3WebTransportStream.h
new file mode 100644
index 0000000000..545e793246
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportStream.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; 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_Http3WebTransportStream_h
+#define mozilla_net_Http3WebTransportStream_h
+
+#include <functional>
+#include "Http3StreamBase.h"
+#include "mozilla/net/NeqoHttp3Conn.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+
+class nsIWebTransportSendStreamStats;
+class nsIWebTransportReceiveStreamStats;
+
+namespace mozilla::net {
+
+class Http3WebTransportSession;
+
+class Http3WebTransportStream final : public Http3StreamBase,
+ public nsAHttpSegmentWriter,
+ public nsAHttpSegmentReader,
+ public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ explicit Http3WebTransportStream(
+ Http3Session* aSession, uint64_t aSessionId, WebTransportStreamType aType,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+ explicit Http3WebTransportStream(Http3Session* aSession, uint64_t aSessionId,
+ WebTransportStreamType aType,
+ uint64_t aStreamId);
+
+ Http3WebTransportSession* GetHttp3WebTransportSession() override {
+ return nullptr;
+ }
+ Http3WebTransportStream* GetHttp3WebTransportStream() override {
+ return this;
+ }
+ Http3Stream* GetHttp3Stream() override { return nullptr; }
+
+ void SetSendOrder(Maybe<int64_t> aSendOrder);
+
+ [[nodiscard]] nsresult ReadSegments() override;
+ [[nodiscard]] nsresult WriteSegments() override;
+
+ bool Done() const override;
+ void Close(nsresult aResult) override;
+
+ void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) override {}
+
+ uint64_t SessionId() const { return mSessionId; }
+ WebTransportStreamType StreamType() const { return mStreamType; }
+
+ void SendFin();
+ void Reset(uint64_t aErrorCode);
+ void SendStopSending(uint8_t aErrorCode);
+
+ already_AddRefed<nsIWebTransportSendStreamStats> GetSendStreamStats();
+ already_AddRefed<nsIWebTransportReceiveStreamStats> GetReceiveStreamStats();
+ void GetWriterAndReader(nsIAsyncOutputStream** aOutOutputStream,
+ nsIAsyncInputStream** aOutInputStream);
+
+ // When mRecvState is RECV_DONE, this means we already received the FIN.
+ bool RecvDone() const { return mRecvState == RECV_DONE; }
+
+ private:
+ friend class Http3WebTransportSession;
+ virtual ~Http3WebTransportStream();
+
+ nsresult TryActivating();
+ static nsresult ReadRequestSegment(nsIInputStream*, void*, const char*,
+ uint32_t, uint32_t, uint32_t*);
+ static nsresult WritePipeSegment(nsIOutputStream*, void*, char*, uint32_t,
+ uint32_t, uint32_t*);
+ nsresult InitOutputPipe();
+ nsresult InitInputPipe();
+
+ uint64_t mSessionId{UINT64_MAX};
+ WebTransportStreamType mStreamType{WebTransportStreamType::BiDi};
+
+ enum StreamRole {
+ INCOMING,
+ OUTGOING,
+ } mStreamRole{INCOMING};
+
+ enum SendStreamState {
+ WAITING_TO_ACTIVATE,
+ WAITING_DATA,
+ SENDING,
+ SEND_DONE,
+ } mSendState{WAITING_TO_ACTIVATE};
+
+ enum RecvStreamState { BEFORE_READING, READING, RECEIVED_FIN, RECV_DONE };
+ Atomic<RecvStreamState> mRecvState{BEFORE_READING};
+
+ nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+ nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>
+ mStreamReadyCallback;
+
+ Mutex mMutex{"Http3WebTransportStream::mMutex"};
+ nsCOMPtr<nsIAsyncInputStream> mSendStreamPipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSendStreamPipeOut MOZ_GUARDED_BY(mMutex);
+
+ nsCOMPtr<nsIAsyncInputStream> mReceiveStreamPipeIn MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIAsyncOutputStream> mReceiveStreamPipeOut;
+
+ uint64_t mTotalSent = 0;
+ uint64_t mTotalReceived = 0;
+ // TODO: neqo doesn't expose this information for now.
+ uint64_t mTotalAcknowledged = 0;
+ bool mSendFin{false};
+ // The error code used to reset the stream. Should be only set once.
+ Maybe<uint64_t> mResetError;
+ // The error code used for STOP_SENDING. Should be only set once.
+ Maybe<uint8_t> mStopSendingError;
+
+ // This is used when SendFin or Reset is called when mSendState is SENDING.
+ nsTArray<std::function<void()>> mPendingTasks;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http3WebTransportStream_h
diff --git a/netwerk/protocol/http/HttpAuthUtils.cpp b/netwerk/protocol/http/HttpAuthUtils.cpp
new file mode 100644
index 0000000000..9bd34ef769
--- /dev/null
+++ b/netwerk/protocol/http/HttpAuthUtils.cpp
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HttpAuthUtils.h"
+#include "mozilla/Tokenizer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsUnicharUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+
+namespace mozilla {
+namespace net {
+namespace auth {
+
+namespace detail {
+
+bool MatchesBaseURI(const nsACString& matchScheme, const nsACString& matchHost,
+ int32_t matchPort, nsDependentCSubstring const& url) {
+ // check if scheme://host:port matches baseURI
+
+ // parse the base URI
+ mozilla::Tokenizer t(url);
+ mozilla::Tokenizer::Token token;
+
+ t.SkipWhites();
+
+ // We don't know if the url to check against starts with scheme
+ // or a host name. Start recording here.
+ t.Record();
+
+ mozilla::Unused << t.Next(token);
+
+ // The ipv6 literals MUST be enclosed with [] in the preference.
+ bool ipv6 = false;
+ if (token.Equals(mozilla::Tokenizer::Token::Char('['))) {
+ nsDependentCSubstring ipv6BareLiteral;
+ if (!t.ReadUntil(mozilla::Tokenizer::Token::Char(']'), ipv6BareLiteral)) {
+ // Broken ipv6 literal
+ return false;
+ }
+
+ nsDependentCSubstring ipv6Literal;
+ t.Claim(ipv6Literal, mozilla::Tokenizer::INCLUDE_LAST);
+ if (!matchHost.Equals(ipv6Literal, nsCaseInsensitiveUTF8StringComparator) &&
+ !matchHost.Equals(ipv6BareLiteral,
+ nsCaseInsensitiveUTF8StringComparator)) {
+ return false;
+ }
+
+ ipv6 = true;
+ } else if (t.CheckChar(':') && t.CheckChar('/') && t.CheckChar('/')) {
+ if (!matchScheme.Equals(token.Fragment())) {
+ return false;
+ }
+ // Re-start recording the hostname from the point after scheme://.
+ t.Record();
+ }
+
+ while (t.Next(token)) {
+ bool eof = token.Equals(mozilla::Tokenizer::Token::EndOfFile());
+ bool port = token.Equals(mozilla::Tokenizer::Token::Char(':'));
+
+ if (eof || port) {
+ if (!ipv6) { // Match already performed above.
+ nsDependentCSubstring hostName;
+ t.Claim(hostName);
+
+ // An empty hostname means to accept everything for the schema
+ if (!hostName.IsEmpty()) {
+ /*
+ host: bar.com foo.bar.com foobar.com foo.bar.com bar.com
+ pref: bar.com bar.com bar.com .bar.com .bar.com
+ result: accept accept reject accept reject
+ */
+ if (!StringEndsWith(matchHost, hostName,
+ nsCaseInsensitiveUTF8StringComparator)) {
+ return false;
+ }
+ if (matchHost.Length() > hostName.Length() &&
+ matchHost[matchHost.Length() - hostName.Length() - 1] != '.' &&
+ hostName[0] != '.') {
+ return false;
+ }
+ }
+ }
+
+ if (port) {
+ uint16_t portNumber;
+ if (!t.ReadInteger(&portNumber)) {
+ // Missing port number
+ return false;
+ }
+ if (matchPort != portNumber) {
+ return false;
+ }
+ if (!t.CheckEOF()) {
+ return false;
+ }
+ }
+ } else if (ipv6) {
+ // After an ipv6 literal there can only be EOF or :port. Everything else
+ // must be treated as non-match/broken input.
+ return false;
+ }
+ }
+
+ // All negative checks has passed positively.
+ return true;
+}
+
+} // namespace detail
+
+bool URIMatchesPrefPattern(nsIURI* uri, const char* pref) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return false;
+ }
+
+ nsAutoCString scheme, host;
+ int32_t port;
+
+ if (NS_FAILED(uri->GetScheme(scheme))) {
+ return false;
+ }
+ if (NS_FAILED(uri->GetAsciiHost(host))) {
+ return false;
+ }
+
+ port = NS_GetRealPort(uri);
+ if (port == -1) {
+ return false;
+ }
+
+ nsAutoCString hostList;
+ if (NS_FAILED(prefs->GetCharPref(pref, hostList))) {
+ return false;
+ }
+
+ // pseudo-BNF
+ // ----------
+ //
+ // url-list base-url ( base-url "," LWS )*
+ // base-url ( scheme-part | host-part | scheme-part host-part )
+ // scheme-part scheme "://"
+ // host-part host [":" port]
+ //
+ // for example:
+ // "https://, http://office.foo.com"
+ //
+
+ mozilla::Tokenizer t(hostList);
+ while (!t.CheckEOF()) {
+ t.SkipWhites();
+ nsDependentCSubstring url;
+ mozilla::Unused << t.ReadUntil(mozilla::Tokenizer::Token::Char(','), url);
+ if (url.IsEmpty()) {
+ continue;
+ }
+ if (detail::MatchesBaseURI(scheme, host, port, url)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace auth
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpAuthUtils.h b/netwerk/protocol/http/HttpAuthUtils.h
new file mode 100644
index 0000000000..c045f81701
--- /dev/null
+++ b/netwerk/protocol/http/HttpAuthUtils.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpAuthUtils_h__
+#define HttpAuthUtils_h__
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+namespace auth {
+
+/* Tries to match the given URI against the value of a given pref
+ *
+ * The pref should be in pseudo-BNF format.
+ * url-list base-url ( base-url "," LWS )*
+ * base-url ( scheme-part | host-part | scheme-part host-part )
+ * scheme-part scheme "://"
+ * host-part host [":" port]
+ *
+ * for example:
+ * "https://, http://office.foo.com"
+ *
+ * Will return true if the URI matches any of the patterns, or false otherwise.
+ */
+bool URIMatchesPrefPattern(nsIURI* uri, const char* pref);
+
+} // namespace auth
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpAuthUtils_h__
diff --git a/netwerk/protocol/http/HttpBackgroundChannelChild.cpp b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
new file mode 100644
index 0000000000..bb113064a4
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
@@ -0,0 +1,499 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpBackgroundChannelChild.h"
+
+#include "HttpChannelChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/net/BackgroundDataBridgeChild.h"
+#include "mozilla/Unused.h"
+#include "nsSocketTransportService2.h"
+
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::IPCResult;
+
+namespace mozilla {
+namespace net {
+
+// HttpBackgroundChannelChild
+HttpBackgroundChannelChild::HttpBackgroundChannelChild() = default;
+
+HttpBackgroundChannelChild::~HttpBackgroundChannelChild() = default;
+
+nsresult HttpBackgroundChannelChild::Init(HttpChannelChild* aChannelChild) {
+ LOG(
+ ("HttpBackgroundChannelChild::Init [this=%p httpChannel=%p "
+ "channelId=%" PRIu64 "]\n",
+ this, aChannelChild, aChannelChild->ChannelId()));
+ MOZ_ASSERT(OnSocketThread());
+ NS_ENSURE_ARG(aChannelChild);
+
+ mChannelChild = aChannelChild;
+
+ if (NS_WARN_IF(!CreateBackgroundChannel())) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // mChannelChild may be nulled already. Use aChannelChild
+ aChannelChild->mCreateBackgroundChannelFailed = true;
+#endif
+ mChannelChild = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ mFirstODASource = ODA_PENDING;
+ mOnStopRequestCalled = false;
+ return NS_OK;
+}
+
+void HttpBackgroundChannelChild::CreateDataBridge(
+ Endpoint<PBackgroundDataBridgeChild>&& aEndpoint) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mChannelChild) {
+ return;
+ }
+
+ RefPtr<BackgroundDataBridgeChild> dataBridgeChild =
+ new BackgroundDataBridgeChild(this);
+ aEndpoint.Bind(dataBridgeChild);
+}
+
+void HttpBackgroundChannelChild::OnChannelClosed() {
+ LOG(("HttpBackgroundChannelChild::OnChannelClosed [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mChannelChild) {
+ mChannelChild->mBackgroundChildQueueFinalState =
+ mQueuedRunnables.IsEmpty() ? HttpChannelChild::BCKCHILD_EMPTY
+ : HttpChannelChild::BCKCHILD_NON_EMPTY;
+ }
+#endif
+
+ // HttpChannelChild is not going to handle any incoming message.
+ mChannelChild = nullptr;
+
+ // Remove pending IPC messages as well.
+ mQueuedRunnables.Clear();
+ mConsoleReportTask = nullptr;
+}
+
+bool HttpBackgroundChannelChild::ChannelClosed() {
+ MOZ_ASSERT(OnSocketThread());
+
+ return !mChannelChild;
+}
+
+void HttpBackgroundChannelChild::OnStartRequestReceived(
+ Maybe<uint32_t> aMultiPartID) {
+ LOG(("HttpBackgroundChannelChild::OnStartRequestReceived [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mChannelChild);
+ MOZ_ASSERT(!mStartReceived || *aMultiPartID > 0);
+
+ mStartReceived = true;
+
+ nsTArray<nsCOMPtr<nsIRunnable>> runnables = std::move(mQueuedRunnables);
+
+ for (const auto& event : runnables) {
+ // Note: these runnables call Recv* methods on HttpBackgroundChannelChild
+ // but not the Process* methods on HttpChannelChild.
+ event->Run();
+ }
+
+ // Ensure no new message is enqueued.
+ MOZ_ASSERT(mQueuedRunnables.IsEmpty());
+}
+
+bool HttpBackgroundChannelChild::CreateBackgroundChannel() {
+ LOG(("HttpBackgroundChannelChild::CreateBackgroundChannel [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mChannelChild);
+
+ PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ return false;
+ }
+
+ const uint64_t channelId = mChannelChild->ChannelId();
+ if (!actorChild->SendPHttpBackgroundChannelConstructor(this, channelId)) {
+ return false;
+ }
+
+ mChannelChild->OnBackgroundChildReady(this);
+ return true;
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnAfterLastPart(
+ const nsresult& aStatus) {
+ LOG(("HttpBackgroundChannelChild::RecvOnAfterLastPart [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessOnAfterLastPart(aStatus);
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnProgress(
+ const int64_t& aProgress, const int64_t& aProgressMax) {
+ LOG(("HttpBackgroundChannelChild::RecvOnProgress [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessOnProgress(aProgress, aProgressMax);
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnStatus(const nsresult& aStatus) {
+ LOG(("HttpBackgroundChannelChild::RecvOnStatus [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessOnStatus(aStatus);
+ return IPC_OK();
+}
+
+bool HttpBackgroundChannelChild::IsWaitingOnStartRequest() {
+ MOZ_ASSERT(OnSocketThread());
+
+ // Need to wait for OnStartRequest if it is sent by
+ // parent process but not received by content process.
+ return !mStartReceived;
+}
+
+// PHttpBackgroundChannelChild
+IPCResult HttpBackgroundChannelChild::RecvOnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStart) {
+ LOG((
+ "HttpBackgroundChannelChild::RecvOnStartRequest [this=%p, status=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(aArgs.channelStatus())));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mFirstODASource =
+ aArgs.dataFromSocketProcess() ? ODA_FROM_SOCKET : ODA_FROM_PARENT;
+
+ mChannelChild->ProcessOnStartRequest(aResponseHead, aUseResponseHead,
+ aRequestHeaders, aArgs, aAltData,
+ aOnStartRequestStart);
+ // Allow to queue other runnable since OnStartRequest Event already hits the
+ // child's mEventQ.
+ OnStartRequestReceived(aArgs.multiPartID());
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsACString& aData,
+ const bool& aDataFromSocketProcess,
+ const TimeStamp& aOnDataAvailableStart) {
+ RefPtr<HttpBackgroundChannelChild> self = this;
+ std::function<void()> callProcessOnTransportAndData =
+ [self, aChannelStatus, aTransportStatus, aOffset, aCount,
+ data = nsCString(aData), aDataFromSocketProcess,
+ aOnDataAvailableStart]() {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvOnTransportAndData [this=%p, "
+ "aDataFromSocketProcess=%d, mFirstODASource=%d]\n",
+ self.get(), aDataFromSocketProcess, self->mFirstODASource));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!self->mChannelChild)) {
+ return;
+ }
+
+ if (((self->mFirstODASource == ODA_FROM_SOCKET) &&
+ !aDataFromSocketProcess) ||
+ ((self->mFirstODASource == ODA_FROM_PARENT) &&
+ aDataFromSocketProcess)) {
+ return;
+ }
+
+ // The HttpTransactionChild in socket process may not know that this
+ // request is cancelled or failed due to the IPC delay. In this case, we
+ // should not forward ODA to HttpChannelChild.
+ nsresult channelStatus;
+ self->mChannelChild->GetStatus(&channelStatus);
+ if (NS_FAILED(channelStatus)) {
+ return;
+ }
+
+ self->mChannelChild->ProcessOnTransportAndData(
+ aChannelStatus, aTransportStatus, aOffset, aCount, data,
+ aOnDataAvailableStart);
+ };
+
+ // Bug 1641336: Race only happens if the data is from socket process.
+ if (IsWaitingOnStartRequest()) {
+ LOG((" > pending until OnStartRequest [offset=%" PRIu64 " count=%" PRIu32
+ "]\n",
+ aOffset, aCount));
+
+ mQueuedRunnables.AppendElement(NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::RecvOnTransportAndData",
+ std::move(callProcessOnTransportAndData)));
+ return IPC_OK();
+ }
+
+ callProcessOnTransportAndData();
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ const bool& aFromSocketProcess, const TimeStamp& aOnStopRequestStart) {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvOnStopRequest [this=%p, "
+ "aFromSocketProcess=%d, mFirstODASource=%d]\n",
+ this, aFromSocketProcess, mFirstODASource));
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ // It's enough to set this from (just before) OnStopRequest notification,
+ // since we don't need this value sooner than a channel was done loading -
+ // everything this timestamp affects takes place only after a channel's
+ // OnStopRequest.
+ nsHttp::SetLastActiveTabLoadOptimizationHit(aLastActiveTabOptHit);
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ if (IsWaitingOnStartRequest()) {
+ LOG((" > pending until OnStartRequest [status=%" PRIx32 "]\n",
+ static_cast<uint32_t>(aChannelStatus)));
+
+ RefPtr<HttpBackgroundChannelChild> self = this;
+
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::RecvOnStopRequest",
+ [self, aChannelStatus, aTiming, aLastActiveTabOptHit, aResponseTrailers,
+ consoleReports = CopyableTArray{std::move(aConsoleReports)},
+ aFromSocketProcess, aOnStopRequestStart]() mutable {
+ self->RecvOnStopRequest(aChannelStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers, std::move(consoleReports),
+ aFromSocketProcess, aOnStopRequestStart);
+ });
+
+ mQueuedRunnables.AppendElement(task.forget());
+ return IPC_OK();
+ }
+
+ if (mFirstODASource != ODA_FROM_SOCKET) {
+ if (!aFromSocketProcess) {
+ mOnStopRequestCalled = true;
+ mChannelChild->ProcessOnStopRequest(
+ aChannelStatus, aTiming, aResponseTrailers,
+ std::move(aConsoleReports), false, aOnStopRequestStart);
+ }
+ return IPC_OK();
+ }
+
+ MOZ_ASSERT(mFirstODASource == ODA_FROM_SOCKET);
+
+ if (aFromSocketProcess) {
+ MOZ_ASSERT(!mOnStopRequestCalled);
+ mOnStopRequestCalled = true;
+ mChannelChild->ProcessOnStopRequest(
+ aChannelStatus, aTiming, aResponseTrailers, std::move(aConsoleReports),
+ true, aOnStopRequestStart);
+ if (mConsoleReportTask) {
+ mConsoleReportTask();
+ mConsoleReportTask = nullptr;
+ }
+ return IPC_OK();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ LOG(("HttpBackgroundChannelChild::RecvOnConsoleReport [this=%p]\n", this));
+ MOZ_ASSERT(mFirstODASource == ODA_FROM_SOCKET);
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ if (IsWaitingOnStartRequest()) {
+ LOG((" > pending until OnStartRequest\n"));
+
+ RefPtr<HttpBackgroundChannelChild> self = this;
+
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::RecvOnConsoleReport",
+ [self, consoleReports =
+ CopyableTArray{std::move(aConsoleReports)}]() mutable {
+ self->RecvOnConsoleReport(std::move(consoleReports));
+ });
+
+ mQueuedRunnables.AppendElement(task.forget());
+ return IPC_OK();
+ }
+
+ if (mOnStopRequestCalled) {
+ mChannelChild->ProcessOnConsoleReport(std::move(aConsoleReports));
+ } else {
+ RefPtr<HttpBackgroundChannelChild> self = this;
+ mConsoleReportTask = [self, consoleReports = CopyableTArray{
+ std::move(aConsoleReports)}]() mutable {
+ self->mChannelChild->ProcessOnConsoleReport(std::move(consoleReports));
+ };
+ }
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvNotifyClassificationFlags(
+ const uint32_t& aClassificationFlags, const bool& aIsThirdParty) {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvNotifyClassificationFlags "
+ "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ // NotifyClassificationFlags has no order dependency to OnStartRequest.
+ // It this be handled as soon as possible
+ mChannelChild->ProcessNotifyClassificationFlags(aClassificationFlags,
+ aIsThirdParty);
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvSetClassifierMatchedInfo(
+ const ClassifierInfo& info) {
+ LOG(("HttpBackgroundChannelChild::RecvSetClassifierMatchedInfo [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ // SetClassifierMatchedInfo has no order dependency to OnStartRequest.
+ // It this be handled as soon as possible
+ mChannelChild->ProcessSetClassifierMatchedInfo(info.list(), info.provider(),
+ info.fullhash());
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo(
+ const ClassifierInfo& info) {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo "
+ "[this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ // SetClassifierMatchedTrackingInfo has no order dependency to
+ // OnStartRequest. It this be handled as soon as possible
+ mChannelChild->ProcessSetClassifierMatchedTrackingInfo(info.list(),
+ info.fullhash());
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint) {
+ LOG(("HttpBackgroundChannelChild::RecvAttachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessAttachStreamFilter(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvDetachStreamFilters() {
+ LOG(("HttpBackgroundChannelChild::RecvDetachStreamFilters [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessDetachStreamFilters();
+ return IPC_OK();
+}
+
+void HttpBackgroundChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpBackgroundChannelChild::ActorDestroy[this=%p]\n", this));
+ // This function might be called during shutdown phase, so OnSocketThread()
+ // might return false even on STS thread. Use IsOnCurrentThreadInfallible()
+ // to get correct information.
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ // Ensure all IPC messages received before ActorDestroy can be
+ // handled correctly. If there is any pending IPC message, destroyed
+ // mChannelChild until those messages are flushed.
+ // If background channel is not closed by normal IPDL actor deletion,
+ // remove the HttpChannelChild reference and notify background channel
+ // destroyed immediately.
+ if (aWhy == Deletion && !mQueuedRunnables.IsEmpty()) {
+ LOG((" > pending until queued messages are flushed\n"));
+ RefPtr<HttpBackgroundChannelChild> self = this;
+ mQueuedRunnables.AppendElement(NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::ActorDestroy", [self]() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<HttpChannelChild> channelChild =
+ std::move(self->mChannelChild);
+
+ if (channelChild) {
+ channelChild->OnBackgroundChildDestroyed(self);
+ }
+ }));
+ return;
+ }
+
+ if (mChannelChild) {
+ RefPtr<HttpChannelChild> channelChild = std::move(mChannelChild);
+
+ channelChild->OnBackgroundChildDestroyed(this);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpBackgroundChannelChild.h b/netwerk/protocol/http/HttpBackgroundChannelChild.h
new file mode 100644
index 0000000000..da88ec9501
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.h
@@ -0,0 +1,156 @@
+/* -*- 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_HttpBackgroundChannelChild_h
+#define mozilla_net_HttpBackgroundChannelChild_h
+
+#include "mozilla/net/PHttpBackgroundChannelChild.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "nsIRunnable.h"
+#include "nsTArray.h"
+
+using mozilla::ipc::IPCResult;
+
+namespace mozilla {
+namespace net {
+
+class PBackgroundDataBridgeChild;
+class BackgroundDataBridgeChild;
+class HttpChannelChild;
+
+class HttpBackgroundChannelChild final : public PHttpBackgroundChannelChild {
+ friend class BackgroundChannelCreateCallback;
+ friend class PHttpBackgroundChannelChild;
+ friend class HttpChannelChild;
+ friend class BackgroundDataBridgeChild;
+
+ public:
+ explicit HttpBackgroundChannelChild();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HttpBackgroundChannelChild, final)
+
+ // Associate this background channel with a HttpChannelChild and
+ // initiate the createion of the PBackground IPC channel.
+ nsresult Init(HttpChannelChild* aChannelChild);
+
+ // Callback while the associated HttpChannelChild is not going to
+ // handle any incoming messages over background channel.
+ void OnChannelClosed();
+
+ // Return true if OnChannelClosed has been called.
+ bool ChannelClosed();
+
+ // Callback when OnStartRequest is received and handled by HttpChannelChild.
+ // Enqueued messages in background channel will be flushed.
+ void OnStartRequestReceived(Maybe<uint32_t> aMultiPartID);
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool IsQueueEmpty() const { return mQueuedRunnables.IsEmpty(); }
+#endif
+
+ protected:
+ IPCResult RecvOnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStart);
+
+ IPCResult RecvOnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsACString& aData,
+ const bool& aDataFromSocketProcess,
+ const TimeStamp& aOnDataAvailableStart);
+
+ IPCResult RecvOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ const bool& aFromSocketProcess, const TimeStamp& aOnStopRequestStart);
+
+ IPCResult RecvOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports);
+
+ IPCResult RecvOnAfterLastPart(const nsresult& aStatus);
+
+ IPCResult RecvOnProgress(const int64_t& aProgress,
+ const int64_t& aProgressMax);
+
+ IPCResult RecvOnStatus(const nsresult& aStatus);
+
+ IPCResult RecvNotifyClassificationFlags(const uint32_t& aClassificationFlags,
+ const bool& aIsThirdParty);
+
+ IPCResult RecvSetClassifierMatchedInfo(const ClassifierInfo& info);
+
+ IPCResult RecvSetClassifierMatchedTrackingInfo(const ClassifierInfo& info);
+
+ IPCResult RecvAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint);
+
+ IPCResult RecvDetachStreamFilters();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void CreateDataBridge(Endpoint<PBackgroundDataBridgeChild>&& aEndpoint);
+
+ private:
+ virtual ~HttpBackgroundChannelChild();
+
+ // Initiate the creation of the PBckground IPC channel.
+ // Return false if failed.
+ bool CreateBackgroundChannel();
+
+ // Check OnStartRequest is sent by parent process over main thread IPC
+ // but not yet received on child process.
+ // return true before RecvOnStartRequestSent is invoked.
+ // return false if RecvOnStartRequestSent is invoked but not
+ // OnStartRequestReceived.
+ // return true after both RecvOnStartRequestSend and OnStartRequestReceived
+ // are invoked.
+ bool IsWaitingOnStartRequest();
+
+ // Associated HttpChannelChild for handling the channel events.
+ // Will be removed while failed to create background channel,
+ // destruction of the background channel, or explicitly dissociation
+ // via OnChannelClosed callback.
+ RefPtr<HttpChannelChild> mChannelChild;
+
+ // True if OnStartRequest is received by HttpChannelChild.
+ // Should only access on STS thread.
+ bool mStartReceived = false;
+
+ // Store pending messages that require to be handled after OnStartRequest.
+ // Should be flushed after OnStartRequest is received and handled.
+ // Should only access on STS thread.
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
+
+ enum ODASource {
+ ODA_PENDING = 0, // ODA is pending
+ ODA_FROM_PARENT = 1, // ODA from parent process.
+ ODA_FROM_SOCKET = 2 // response coming from the network
+ };
+ // We need to know the first ODA will be from socket process or parent
+ // process. This information is from OnStartRequest message from parent
+ // process.
+ ODASource mFirstODASource;
+
+ // Indicate whether HttpChannelChild::ProcessOnStopRequest is called.
+ bool mOnStopRequestCalled = false;
+
+ // This is used when we receive the console report from parent process, but
+ // still not get the OnStopRequest from socket process.
+ std::function<void()> mConsoleReportTask;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpBackgroundChannelChild_h
diff --git a/netwerk/protocol/http/HttpBackgroundChannelParent.cpp b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
new file mode 100644
index 0000000000..e30fa13d37
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
@@ -0,0 +1,526 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpBackgroundChannelParent.h"
+
+#include "HttpChannelParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/BackgroundChannelRegistrar.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsNetCID.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+
+using mozilla::dom::ContentParent;
+using mozilla::ipc::AssertIsInMainProcess;
+using mozilla::ipc::AssertIsOnBackgroundThread;
+using mozilla::ipc::BackgroundParent;
+using mozilla::ipc::IPCResult;
+using mozilla::ipc::IsOnBackgroundThread;
+
+namespace mozilla {
+namespace net {
+
+/*
+ * Helper class for continuing the AsyncOpen procedure on main thread.
+ */
+class ContinueAsyncOpenRunnable final : public Runnable {
+ public:
+ ContinueAsyncOpenRunnable(HttpBackgroundChannelParent* aActor,
+ const uint64_t& aChannelId)
+ : Runnable("net::ContinueAsyncOpenRunnable"),
+ mActor(aActor),
+ mChannelId(aChannelId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActor);
+ }
+
+ NS_IMETHOD Run() override {
+ LOG(
+ ("HttpBackgroundChannelParent::ContinueAsyncOpen [this=%p "
+ "channelId=%" PRIu64 "]\n",
+ mActor.get(), mChannelId));
+ AssertIsInMainProcess();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
+ BackgroundChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ registrar->LinkBackgroundChannel(mChannelId, mActor);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<HttpBackgroundChannelParent> mActor;
+ const uint64_t mChannelId;
+};
+
+HttpBackgroundChannelParent::HttpBackgroundChannelParent()
+ : mIPCOpened(true),
+ mBgThreadMutex("HttpBackgroundChannelParent::BgThreadMutex") {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ {
+ MutexAutoLock lock(mBgThreadMutex);
+ mBackgroundThread = NS_GetCurrentThread();
+ }
+}
+
+HttpBackgroundChannelParent::~HttpBackgroundChannelParent() {
+ MOZ_ASSERT(NS_IsMainThread() || IsOnBackgroundThread());
+ MOZ_ASSERT(!mIPCOpened);
+}
+
+nsresult HttpBackgroundChannelParent::Init(const uint64_t& aChannelId) {
+ LOG(("HttpBackgroundChannelParent::Init [this=%p channelId=%" PRIu64 "]\n",
+ this, aChannelId));
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<ContinueAsyncOpenRunnable> runnable =
+ new ContinueAsyncOpenRunnable(this, aChannelId);
+
+ return NS_DispatchToMainThread(runnable);
+}
+
+void HttpBackgroundChannelParent::LinkToChannel(
+ HttpChannelParent* aChannelParent) {
+ LOG(("HttpBackgroundChannelParent::LinkToChannel [this=%p channel=%p]\n",
+ this, aChannelParent));
+ AssertIsInMainProcess();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mIPCOpened) {
+ return;
+ }
+
+ mChannelParent = aChannelParent;
+}
+
+void HttpBackgroundChannelParent::OnChannelClosed() {
+ LOG(("HttpBackgroundChannelParent::OnChannelClosed [this=%p]\n", this));
+ AssertIsInMainProcess();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mIPCOpened) {
+ return;
+ }
+
+ nsresult rv;
+
+ {
+ MutexAutoLock lock(mBgThreadMutex);
+ RefPtr<HttpBackgroundChannelParent> self = this;
+ rv = mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpBackgroundChannelParent::OnChannelClosed",
+ [self]() {
+ LOG(("HttpBackgroundChannelParent::DeleteRunnable [this=%p]\n",
+ self.get()));
+ AssertIsOnBackgroundThread();
+
+ if (!self->mIPCOpened.compareExchange(true, false)) {
+ return;
+ }
+
+ Unused << self->Send__delete__(self);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+bool HttpBackgroundChannelParent::OnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const nsCOMPtr<nsICacheEntry>& aAltDataSource,
+ TimeStamp aOnStartRequestStart) {
+ LOG(("HttpBackgroundChannelParent::OnStartRequest [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsHttpResponseHead, const bool,
+ const nsHttpHeaderArray,
+ const HttpChannelOnStartRequestArgs,
+ const nsCOMPtr<nsICacheEntry>, TimeStamp>(
+ "net::HttpBackgroundChannelParent::OnStartRequest", this,
+ &HttpBackgroundChannelParent::OnStartRequest, aResponseHead,
+ aUseResponseHead, aRequestHeaders, aArgs, aAltDataSource,
+ aOnStartRequestStart),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ HttpChannelAltDataStream altData;
+ if (aAltDataSource) {
+ nsAutoCString altDataType;
+ Unused << aAltDataSource->GetAltDataType(altDataType);
+
+ if (!altDataType.IsEmpty()) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = aAltDataSource->OpenAlternativeInputStream(
+ altDataType, getter_AddRefs(inputStream));
+ if (NS_SUCCEEDED(rv)) {
+ Unused << mozilla::ipc::SerializeIPCStream(inputStream.forget(),
+ altData.altDataInputStream(),
+ /* aAllowLazy */ true);
+ }
+ }
+ }
+
+ return SendOnStartRequest(aResponseHead, aUseResponseHead, aRequestHeaders,
+ aArgs, altData, aOnStartRequestStart);
+}
+
+bool HttpBackgroundChannelParent::OnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsCString& aData,
+ TimeStamp aOnDataAvailableStart) {
+ LOG(("HttpBackgroundChannelParent::OnTransportAndData [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult, const nsresult, const uint64_t,
+ const uint32_t, const nsCString, TimeStamp>(
+ "net::HttpBackgroundChannelParent::OnTransportAndData", this,
+ &HttpBackgroundChannelParent::OnTransportAndData, aChannelStatus,
+ aTransportStatus, aOffset, aCount, aData, aOnDataAvailableStart),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ nsHttp::SendFunc<nsDependentCSubstring> sendFunc =
+ [self = UnsafePtr<HttpBackgroundChannelParent>(this), aChannelStatus,
+ aTransportStatus,
+ aOnDataAvailableStart](const nsDependentCSubstring& aData,
+ uint64_t aOffset, uint32_t aCount) {
+ return self->SendOnTransportAndData(aChannelStatus, aTransportStatus,
+ aOffset, aCount, aData, false,
+ aOnDataAvailableStart);
+ };
+
+ return nsHttp::SendDataInChunks(aData, aOffset, aCount, sendFunc);
+}
+
+bool HttpBackgroundChannelParent::OnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const nsTArray<ConsoleReportCollected>& aConsoleReports,
+ TimeStamp aOnStopRequestStart) {
+ LOG(
+ ("HttpBackgroundChannelParent::OnStopRequest [this=%p "
+ "status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aChannelStatus)));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult, const ResourceTimingStructArgs,
+ const nsHttpHeaderArray,
+ const CopyableTArray<ConsoleReportCollected>,
+ TimeStamp>(
+ "net::HttpBackgroundChannelParent::OnStopRequest", this,
+ &HttpBackgroundChannelParent::OnStopRequest, aChannelStatus,
+ aTiming, aResponseTrailers, aConsoleReports, aOnStopRequestStart),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ // See the child code for why we do this.
+ TimeStamp lastActTabOpt = nsHttp::GetLastActiveTabLoadOptimizationHit();
+
+ return SendOnStopRequest(aChannelStatus, aTiming, lastActTabOpt,
+ aResponseTrailers, aConsoleReports, false,
+ aOnStopRequestStart);
+}
+
+bool HttpBackgroundChannelParent::OnConsoleReport(
+ const nsTArray<ConsoleReportCollected>& aConsoleReports) {
+ LOG(("HttpBackgroundChannelParent::OnConsoleReport [this=%p]", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const CopyableTArray<ConsoleReportCollected>>(
+ "net::HttpBackgroundChannelParent::OnConsoleReport", this,
+ &HttpBackgroundChannelParent::OnConsoleReport, aConsoleReports),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnConsoleReport(aConsoleReports);
+}
+
+bool HttpBackgroundChannelParent::OnAfterLastPart(const nsresult aStatus) {
+ LOG(("HttpBackgroundChannelParent::OnAfterLastPart [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult>(
+ "net::HttpBackgroundChannelParent::OnAfterLastPart", this,
+ &HttpBackgroundChannelParent::OnAfterLastPart, aStatus),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnAfterLastPart(aStatus);
+}
+
+bool HttpBackgroundChannelParent::OnProgress(const int64_t aProgress,
+ const int64_t aProgressMax) {
+ LOG(("HttpBackgroundChannelParent::OnProgress [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const int64_t, const int64_t>(
+ "net::HttpBackgroundChannelParent::OnProgress", this,
+ &HttpBackgroundChannelParent::OnProgress, aProgress, aProgressMax),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnProgress(aProgress, aProgressMax);
+}
+
+bool HttpBackgroundChannelParent::OnStatus(const nsresult aStatus) {
+ LOG(("HttpBackgroundChannelParent::OnStatus [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult>(
+ "net::HttpBackgroundChannelParent::OnStatus", this,
+ &HttpBackgroundChannelParent::OnStatus, aStatus),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnStatus(aStatus);
+}
+
+bool HttpBackgroundChannelParent::OnNotifyClassificationFlags(
+ uint32_t aClassificationFlags, bool aIsThirdParty) {
+ LOG(
+ ("HttpBackgroundChannelParent::OnNotifyClassificationFlags "
+ "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<uint32_t, bool>(
+ "net::HttpBackgroundChannelParent::OnNotifyClassificationFlags",
+ this, &HttpBackgroundChannelParent::OnNotifyClassificationFlags,
+ aClassificationFlags, aIsThirdParty),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendNotifyClassificationFlags(aClassificationFlags, aIsThirdParty);
+}
+
+bool HttpBackgroundChannelParent::OnSetClassifierMatchedInfo(
+ const nsACString& aList, const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ LOG(("HttpBackgroundChannelParent::OnSetClassifierMatchedInfo [this=%p]\n",
+ this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsCString, const nsCString, const nsCString>(
+ "net::HttpBackgroundChannelParent::OnSetClassifierMatchedInfo",
+ this, &HttpBackgroundChannelParent::OnSetClassifierMatchedInfo,
+ aList, aProvider, aFullHash),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ ClassifierInfo info;
+ info.list() = aList;
+ info.fullhash() = aFullHash;
+ info.provider() = aProvider;
+
+ return SendSetClassifierMatchedInfo(info);
+}
+
+bool HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ LOG(
+ ("HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo "
+ "[this=%p]\n",
+ this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsCString, const nsCString>(
+ "net::HttpBackgroundChannelParent::"
+ "OnSetClassifierMatchedTrackingInfo",
+ this,
+ &HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo,
+ aLists, aFullHashes),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ ClassifierInfo info;
+ info.list() = aLists;
+ info.fullhash() = aFullHashes;
+
+ return SendSetClassifierMatchedTrackingInfo(info);
+}
+
+nsISerialEventTarget* HttpBackgroundChannelParent::GetBackgroundTarget() {
+ MOZ_ASSERT(mBackgroundThread);
+ return mBackgroundThread.get();
+}
+
+auto HttpBackgroundChannelParent::AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint)
+ -> RefPtr<ChildEndpointPromise> {
+ LOG(("HttpBackgroundChannelParent::AttachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(IsOnBackgroundThread());
+
+ if (NS_WARN_IF(!mIPCOpened) ||
+ !SendAttachStreamFilter(std::move(aParentEndpoint))) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ return ChildEndpointPromise::CreateAndResolve(std::move(aChildEndpoint),
+ __func__);
+}
+
+auto HttpBackgroundChannelParent::DetachStreamFilters()
+ -> RefPtr<GenericPromise> {
+ LOG(("HttpBackgroundChannelParent::DetachStreamFilters [this=%p]\n", this));
+ if (NS_WARN_IF(!mIPCOpened) || !SendDetachStreamFilters()) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+}
+
+void HttpBackgroundChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpBackgroundChannelParent::ActorDestroy [this=%p]\n", this));
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ mIPCOpened = false;
+
+ RefPtr<HttpBackgroundChannelParent> self = this;
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::HttpBackgroundChannelParent::ActorDestroy", [self]() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<HttpChannelParent> channelParent =
+ std::move(self->mChannelParent);
+
+ if (channelParent) {
+ channelParent->OnBackgroundParentDestroyed();
+ }
+ }));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpBackgroundChannelParent.h b/netwerk/protocol/http/HttpBackgroundChannelParent.h
new file mode 100644
index 0000000000..7715d9bdfb
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.h
@@ -0,0 +1,124 @@
+/* -*- 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_HttpBackgroundChannelParent_h
+#define mozilla_net_HttpBackgroundChannelParent_h
+
+#include "mozilla/net/PHttpBackgroundChannelParent.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "nsID.h"
+#include "nsISupportsImpl.h"
+
+class nsISerialEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class HttpChannelParent;
+
+class HttpBackgroundChannelParent final : public PHttpBackgroundChannelParent {
+ public:
+ explicit HttpBackgroundChannelParent();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HttpBackgroundChannelParent, final)
+
+ // Try to find associated HttpChannelParent with the same
+ // channel Id.
+ nsresult Init(const uint64_t& aChannelId);
+
+ // Callbacks for BackgroundChannelRegistrar to notify
+ // the associated HttpChannelParent is found.
+ void LinkToChannel(HttpChannelParent* aChannelParent);
+
+ // Callbacks for HttpChannelParent to close the background
+ // IPC channel.
+ void OnChannelClosed();
+
+ // To send OnStartRequest message over background channel.
+ bool OnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const nsCOMPtr<nsICacheEntry>& aCacheEntry,
+ TimeStamp aOnStartRequestStart);
+
+ // To send OnTransportAndData message over background channel.
+ bool OnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount,
+ const nsCString& aData,
+ TimeStamp aOnDataAvailableStart);
+
+ // To send OnStopRequest message over background channel.
+ bool OnStopRequest(const nsresult& aChannelStatus,
+ const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const nsTArray<ConsoleReportCollected>& aConsoleReports,
+ TimeStamp aOnStopRequestStart);
+
+ // When ODA and OnStopRequest are sending from socket process to child
+ // process, this is the last IPC message sent from parent process.
+ bool OnConsoleReport(const nsTArray<ConsoleReportCollected>& aConsoleReports);
+
+ // To send OnAfterLastPart message over background channel.
+ bool OnAfterLastPart(const nsresult aStatus);
+
+ // To send OnProgress message over background channel.
+ bool OnProgress(const int64_t aProgress, const int64_t aProgressMax);
+
+ // To send OnStatus message over background channel.
+ bool OnStatus(const nsresult aStatus);
+
+ // To send FlushedForDiversion and DivertMessages messages
+ // over background channel.
+ bool OnDiversion();
+
+ // To send NotifyClassificationFlags message over background channel.
+ bool OnNotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+
+ // To send SetClassifierMatchedInfo message over background channel.
+ bool OnSetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash);
+
+ // To send SetClassifierMatchedTrackingInfo message over background channel.
+ bool OnSetClassifierMatchedTrackingInfo(const nsACString& aLists,
+ const nsACString& aFullHashes);
+
+ nsISerialEventTarget* GetBackgroundTarget();
+
+ using ChildEndpointPromise =
+ MozPromise<ipc::Endpoint<extensions::PStreamFilterChild>, bool, true>;
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint);
+
+ [[nodiscard]] RefPtr<GenericPromise> DetachStreamFilters();
+
+ protected:
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ virtual ~HttpBackgroundChannelParent();
+
+ Atomic<bool> mIPCOpened;
+
+ // Used to ensure atomicity of mBackgroundThread
+ Mutex mBgThreadMutex MOZ_UNANNOTATED;
+
+ nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
+
+ // associated HttpChannelParent for generating the channel events
+ RefPtr<HttpChannelParent> mChannelParent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpBackgroundChannelParent_h
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
new file mode 100644
index 0000000000..59242f6933
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -0,0 +1,6479 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "mozilla/net/HttpBaseChannel.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "HttpBaseChannel.h"
+#include "HttpLog.h"
+#include "LoadInfo.h"
+#include "ReferrerInfo.h"
+#include "mozIRemoteLazyInputStream.h"
+#include "mozIThirdPartyUtil.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/PermissionManager.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/browser/NimbusFeatures.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/ProcessIsolation.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/OpaqueResponseUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "nsBufferedStreams.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentSecurityUtils.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsEscape.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHttpChannel.h"
+#include "nsHTTPCompressConv.h"
+#include "nsHttpHandler.h"
+#include "nsICacheInfoChannel.h"
+#include "nsICachingChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsICookieService.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDNSService.h"
+#include "nsIEncodedChannel.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsILoadGroupChild.h"
+#include "nsIMIMEInputStream.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMutableArray.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsISeekableStream.h"
+#include "nsIStorageStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsITimedChannel.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURIMutator.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsServerTiming.h"
+#include "nsStreamListenerWrapper.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/net/SFVService.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsQueryObject.h"
+
+using mozilla::dom::RequestMode;
+
+#define LOGORB(msg, ...) \
+ MOZ_LOG(GetORBLog(), LogLevel::Debug, \
+ ("%s: %p " msg, __func__, this, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace net {
+
+static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
+ // IMPORTANT: keep this list ASCII-code sorted
+ static nsHttpAtomLiteral const* blackList[] = {
+ &nsHttp::Accept,
+ &nsHttp::Accept_Encoding,
+ &nsHttp::Accept_Language,
+ &nsHttp::Alternate_Service_Used,
+ &nsHttp::Authentication,
+ &nsHttp::Authorization,
+ &nsHttp::Connection,
+ &nsHttp::Content_Length,
+ &nsHttp::Cookie,
+ &nsHttp::Host,
+ &nsHttp::If,
+ &nsHttp::If_Match,
+ &nsHttp::If_Modified_Since,
+ &nsHttp::If_None_Match,
+ &nsHttp::If_None_Match_Any,
+ &nsHttp::If_Range,
+ &nsHttp::If_Unmodified_Since,
+ &nsHttp::Proxy_Authenticate,
+ &nsHttp::Proxy_Authorization,
+ &nsHttp::Range,
+ &nsHttp::TE,
+ &nsHttp::Transfer_Encoding,
+ &nsHttp::Upgrade,
+ &nsHttp::User_Agent,
+ &nsHttp::WWW_Authenticate};
+
+ class HttpAtomComparator {
+ nsHttpAtom const& mTarget;
+
+ public:
+ explicit HttpAtomComparator(nsHttpAtom const& aTarget) : mTarget(aTarget) {}
+ int operator()(nsHttpAtom const* aVal) const {
+ if (mTarget == *aVal) {
+ return 0;
+ }
+ return strcmp(mTarget.get(), aVal->get());
+ }
+ int operator()(nsHttpAtomLiteral const* aVal) const {
+ if (mTarget == *aVal) {
+ return 0;
+ }
+ return strcmp(mTarget.get(), aVal->get());
+ }
+ };
+
+ size_t unused;
+ return BinarySearchIf(blackList, 0, ArrayLength(blackList),
+ HttpAtomComparator(aHeader), &unused);
+}
+
+class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit AddHeadersToChannelVisitor(nsIHttpChannel* aChannel)
+ : mChannel(aChannel) {}
+
+ NS_IMETHOD VisitHeader(const nsACString& aHeader,
+ const nsACString& aValue) override {
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
+ DebugOnly<nsresult> rv =
+ mChannel->SetRequestHeader(aHeader, aValue, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~AddHeadersToChannelVisitor() = default;
+
+ nsCOMPtr<nsIHttpChannel> mChannel;
+};
+
+NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
+
+static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() {
+ uint32_t pref = StaticPrefs::
+ browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly();
+ if (NS_WARN_IF(pref >
+ static_cast<uint32_t>(OpaqueResponseFilterFetch::All))) {
+ return OpaqueResponseFilterFetch::All;
+ }
+
+ return static_cast<OpaqueResponseFilterFetch>(pref);
+}
+
+HttpBaseChannel::HttpBaseChannel()
+ : mReportCollector(new ConsoleReportCollector()),
+ mHttpHandler(gHttpHandler),
+ mChannelCreationTime(0),
+ mComputedCrossOriginOpenerPolicy(nsILoadInfo::OPENER_POLICY_UNSAFE_NONE),
+ mStartPos(UINT64_MAX),
+ mTransferSize(0),
+ mRequestSize(0),
+ mDecodedBodySize(0),
+ mSupportsHTTP3(false),
+ mEncodedBodySize(0),
+ mRequestContextID(0),
+ mContentWindowId(0),
+ mBrowserId(0),
+ mAltDataLength(-1),
+ mChannelId(0),
+ mReqContentLength(0U),
+ mStatus(NS_OK),
+ mCanceled(false),
+ mFirstPartyClassificationFlags(0),
+ mThirdPartyClassificationFlags(0),
+ mLoadFlags(LOAD_NORMAL),
+ mCaps(0),
+ mClassOfService(0, false),
+ mTlsFlags(0),
+ mSuspendCount(0),
+ mInitialRwin(0),
+ mProxyResolveFlags(0),
+ mContentDispositionHint(UINT32_MAX),
+ mRequestMode(RequestMode::No_cors),
+ mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
+ mLastRedirectFlags(0),
+ mPriority(PRIORITY_NORMAL),
+ mRedirectionLimit(gHttpHandler->RedirectionLimit()),
+ mRedirectCount(0),
+ mInternalRedirectCount(0),
+ mCachedOpaqueResponseBlockingPref(
+ StaticPrefs::browser_opaqueResponseBlocking()),
+ mChannelBlockedByOpaqueResponse(false),
+ mDummyChannelForImageCache(false),
+ mHasContentDecompressed(false) {
+ StoreApplyConversion(true);
+ StoreAllowSTS(true);
+ StoreTracingEnabled(true);
+ StoreReportTiming(true);
+ StoreAllowSpdy(true);
+ StoreAllowHttp3(true);
+ StoreAllowAltSvc(true);
+ StoreResponseTimeoutEnabled(true);
+ StoreAllRedirectsSameOrigin(true);
+ StoreAllRedirectsPassTimingAllowCheck(true);
+ StoreUpgradableToSecure(true);
+ StoreIsUserAgentHeaderModified(false);
+
+ this->mSelfAddr.inet = {};
+ this->mPeerAddr.inet = {};
+ LOG(("Creating HttpBaseChannel @%p\n", this));
+
+ // Subfields of unions cannot be targeted in an initializer list.
+#ifdef MOZ_VALGRIND
+ // Zero the entire unions so that Valgrind doesn't complain when we send them
+ // to another process.
+ memset(&mSelfAddr, 0, sizeof(NetAddr));
+ memset(&mPeerAddr, 0, sizeof(NetAddr));
+#endif
+ mSelfAddr.raw.family = PR_AF_UNSPEC;
+ mPeerAddr.raw.family = PR_AF_UNSPEC;
+}
+
+HttpBaseChannel::~HttpBaseChannel() {
+ LOG(("Destroying HttpBaseChannel @%p\n", this));
+
+ // Make sure we don't leak
+ CleanRedirectCacheChainIfNecessary();
+
+ ReleaseMainThreadOnlyReferences();
+}
+
+namespace { // anon
+
+class NonTailRemover : public nsISupports {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonTailRemover(nsIRequestContext* rc) : mRequestContext(rc) {}
+
+ private:
+ virtual ~NonTailRemover() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mRequestContext->RemoveNonTailRequest();
+ }
+
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+};
+
+NS_IMPL_ISUPPORTS0(NonTailRemover)
+
+} // namespace
+
+void HttpBaseChannel::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ RemoveAsNonTailRequest();
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
+ arrayToRelease.AppendElement(mLoadGroup.forget());
+ arrayToRelease.AppendElement(mLoadInfo.forget());
+ arrayToRelease.AppendElement(mCallbacks.forget());
+ arrayToRelease.AppendElement(mProgressSink.forget());
+ arrayToRelease.AppendElement(mPrincipal.forget());
+ arrayToRelease.AppendElement(mListener.forget());
+ arrayToRelease.AppendElement(mCompressListener.forget());
+ arrayToRelease.AppendElement(mORB.forget());
+
+ if (LoadAddedAsNonTailRequest()) {
+ // RemoveNonTailRequest() on our request context must be called on the main
+ // thread
+ MOZ_RELEASE_ASSERT(mRequestContext,
+ "Someone released rc or set flags w/o having it?");
+
+ nsCOMPtr<nsISupports> nonTailRemover(new NonTailRemover(mRequestContext));
+ arrayToRelease.AppendElement(nonTailRemover.forget());
+ }
+
+ NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
+}
+
+void HttpBaseChannel::AddClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ LOG(
+ ("HttpBaseChannel::AddClassificationFlags classificationFlags=%d "
+ "thirdparty=%d %p",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+
+ if (aIsThirdParty) {
+ mThirdPartyClassificationFlags |= aClassificationFlags;
+ } else {
+ mFirstPartyClassificationFlags |= aClassificationFlags;
+ }
+}
+
+static bool isSecureOrTrustworthyURL(nsIURI* aURI) {
+ return aURI->SchemeIs("https") ||
+ (StaticPrefs::network_http_encoding_trustworthy_is_https() &&
+ nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI));
+}
+
+nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
+ uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo) {
+ LOG1(("HttpBaseChannel::Init [this=%p]\n", this));
+
+ MOZ_ASSERT(aURI, "null uri");
+
+ mURI = aURI;
+ mOriginalURI = aURI;
+ mDocumentURI = nullptr;
+ mCaps = aCaps;
+ mProxyResolveFlags = aProxyResolveFlags;
+ mProxyURI = aProxyURI;
+ mChannelId = aChannelId;
+ mLoadInfo = aLoadInfo;
+
+ // Construct connection info object
+ nsAutoCString host;
+ int32_t port = -1;
+ bool isHTTPS = isSecureOrTrustworthyURL(mURI);
+
+ nsresult rv = mURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG1(("host=%s port=%d\n", host.get(), port));
+
+ rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) return rv;
+ LOG1(("uri=%s\n", mSpec.get()));
+
+ // Assert default request method
+ MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));
+
+ // Set request headers
+ nsAutoCString hostLine;
+ rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = gHttpHandler->AddStandardRequestHeaders(
+ &mRequestHead, isHTTPS, aContentPolicyType,
+ nsContentUtils::ShouldResistFingerprinting(this,
+ RFPTarget::HttpUserAgent));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
+ !type.EqualsLiteral("unknown")) {
+ mProxyInfo = aProxyInfo;
+ }
+
+ mCurrentThread = GetCurrentSerialEventTarget();
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpBaseChannel)
+NS_IMPL_RELEASE(HttpBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIIdentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
+ NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpBaseChannel)
+NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetName(nsACString& aName) {
+ aName = mSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPending(bool* aIsPending) {
+ NS_ENSURE_ARG_POINTER(aIsPending);
+ *aIsPending = LoadIsPending() || LoadForcePending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetStatus(nsresult* aStatus) {
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ NS_ENSURE_ARG_POINTER(aLoadGroup);
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ mProgressSink = nullptr;
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ NS_ENSURE_ARG_POINTER(aLoadFlags);
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ if (!LoadIsOCSP()) {
+ return GetTRRModeImpl(aTRRMode);
+ }
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ nsIDNSService::ResolverMode trrMode = nsIDNSService::MODE_NATIVEONLY;
+ // If this is an OCSP channel, and the global TRR mode is TRR_ONLY (3)
+ // then we set the mode for this channel as TRR_DISABLED_MODE.
+ // We do this to prevent a TRR service channel's OCSP validation from
+ // blocking DNS resolution completely.
+ if (dns && NS_SUCCEEDED(dns->GetCurrentTrrMode(&trrMode)) &&
+ trrMode == nsIDNSService::MODE_TRRONLY) {
+ *aTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return NS_OK;
+ }
+
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocshellUserAgentOverride() {
+ RefPtr<dom::BrowsingContext> bc;
+ MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
+ if (!bc) {
+ return NS_OK;
+ }
+
+ nsAutoString customUserAgent;
+ bc->GetCustomUserAgent(customUserAgent);
+ if (customUserAgent.IsEmpty() || customUserAgent.IsVoid()) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
+ nsresult rv = SetRequestHeader("User-Agent"_ns, utf8CustomUserAgent, false);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ *aOriginalURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetURI(nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ *aURI = do_AddRef(mURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOwner(nsISupports** aOwner) {
+ NS_ENSURE_ARG_POINTER(aOwner);
+ *aOwner = do_AddRef(mOwner).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOwner(nsISupports* aOwner) {
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ *aLoadInfo = do_AddRef(mLoadInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ *aCallbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ mProgressSink = nullptr;
+
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentType(nsACString& aContentType) {
+ if (!mResponseHead) {
+ aContentType.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mResponseHead->ContentType(aContentType);
+ if (!aContentType.IsEmpty()) {
+ return NS_OK;
+ }
+
+ aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentType(const nsACString& aContentType) {
+ if (mListener || LoadWasOpened() || mDummyChannelForImageCache) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString contentTypeBuf, charsetBuf;
+ bool hadCharset;
+ net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);
+
+ mResponseHead->SetContentType(contentTypeBuf);
+
+ // take care not to stomp on an existing charset
+ if (hadCharset) mResponseHead->SetContentCharset(charsetBuf);
+
+ } else {
+ // We are being given a content-type hint.
+ bool dummy;
+ net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
+ &dummy);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentCharset(nsACString& aContentCharset) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->ContentCharset(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentCharset(const nsACString& aContentCharset) {
+ if (mListener) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->SetContentCharset(aContentCharset);
+ } else {
+ // Charset hint
+ mContentCharsetHint = aContentCharset;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ // See bug 1658877. If mContentDispositionHint is already
+ // DISPOSITION_ATTACHMENT, it means this channel is created from a
+ // download attribute. In this case, we should prefer the value from the
+ // download attribute rather than the value in content disposition header.
+ // DISPOSITION_FORCE_INLINE is used to explicitly set inline, used by
+ // the pdf reader when loading a attachment pdf without having to
+ // download it.
+ if (mContentDispositionHint == nsIChannel::DISPOSITION_ATTACHMENT ||
+ mContentDispositionHint == nsIChannel::DISPOSITION_FORCE_INLINE) {
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_FAILED(rv)) {
+ if (mContentDispositionHint == UINT32_MAX) return rv;
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+ }
+
+ *aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ aContentDispositionFilename.Truncate();
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_GetFilenameFromDisposition(aContentDispositionFilename, header);
+ }
+
+ // If we failed to get the filename from header, we should use
+ // mContentDispositionFilename, since mContentDispositionFilename is set from
+ // the download attribute.
+ if (NS_FAILED(rv)) {
+ if (!mContentDispositionFilename) {
+ return rv;
+ }
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ mContentDispositionFilename =
+ MakeUnique<nsString>(aContentDispositionFilename);
+
+ // For safety reasons ensure the filename doesn't contain null characters and
+ // replace them with underscores. We may later pass the extension to system
+ // MIME APIs that expect null terminated strings.
+ mContentDispositionFilename->ReplaceChar(char16_t(0), '_');
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
+ aContentDispositionHeader);
+ if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentLength(int64_t* aContentLength) {
+ NS_ENSURE_ARG_POINTER(aContentLength);
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ if (LoadDeliveringAltData()) {
+ MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
+ *aContentLength = mAltDataLength;
+ return NS_OK;
+ }
+
+ *aContentLength = mResponseHead->ContentLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentLength(int64_t value) {
+ if (!mDummyChannelForImageCache) {
+ MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ MOZ_ASSERT(mResponseHead);
+ mResponseHead->SetContentLength(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::Open(nsIInputStream** aStream) {
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_IN_PROGRESS);
+
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_ImplementChannelOpen(this, aStream);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStream(nsIInputStream** stream) {
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = do_AddRef(mUploadStream).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentTypeArg,
+ int64_t contentLength) {
+ // NOTE: for backwards compatibility and for compatibility with old style
+ // plugins, |stream| may include headers, specifically Content-Type and
+ // Content-Length headers. in this case, |contentType| and |contentLength|
+ // would be unspecified. this is traditionally the case of a POST request,
+ // and so we select POST as the request method if contentType and
+ // contentLength are unspecified.
+
+ if (stream) {
+ nsAutoCString method;
+ bool hasHeaders = false;
+
+ // This method and ExplicitSetUploadStream mean different things by "empty
+ // content type string". This method means "no header", but
+ // ExplicitSetUploadStream means "header with empty value". So we have to
+ // massage the contentType argument into the form ExplicitSetUploadStream
+ // expects.
+ nsCOMPtr<nsIMIMEInputStream> mimeStream;
+ nsCString contentType(contentTypeArg);
+ if (contentType.IsEmpty()) {
+ contentType.SetIsVoid(true);
+ method = "POST"_ns;
+
+ // MIME streams are a special case, and include headers which need to be
+ // copied to the channel.
+ mimeStream = do_QueryInterface(stream);
+ if (mimeStream) {
+ // Copy non-origin related headers to the channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new AddHeadersToChannelVisitor(this);
+ mimeStream->VisitHeaders(visitor);
+
+ return ExplicitSetUploadStream(stream, contentType, contentLength,
+ method, hasHeaders);
+ }
+
+ hasHeaders = true;
+ } else {
+ method = "PUT"_ns;
+
+ MOZ_ASSERT(
+ NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
+ "nsIMIMEInputStream should not be set with an explicit content type");
+ }
+ return ExplicitSetUploadStream(stream, contentType, contentLength, method,
+ hasHeaders);
+ }
+
+ // if stream is null, ExplicitSetUploadStream returns error.
+ // So we need special case for GET method.
+ StoreUploadStreamHasHeaders(false);
+ mRequestHead.SetMethod("GET"_ns); // revert to GET request
+ mUploadStream = nullptr;
+ return NS_OK;
+}
+
+namespace {
+
+class MIMEHeaderCopyVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ explicit MIMEHeaderCopyVisitor(nsIMIMEInputStream* aDest) : mDest(aDest) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD VisitHeader(const nsACString& aName,
+ const nsACString& aValue) override {
+ return mDest->AddHeader(PromiseFlatCString(aName).get(),
+ PromiseFlatCString(aValue).get());
+ }
+
+ private:
+ ~MIMEHeaderCopyVisitor() = default;
+
+ nsCOMPtr<nsIMIMEInputStream> mDest;
+};
+
+NS_IMPL_ISUPPORTS(MIMEHeaderCopyVisitor, nsIHttpHeaderVisitor)
+
+static void NormalizeCopyComplete(void* aClosure, nsresult aStatus) {
+#ifdef DEBUG
+ // Called on the STS thread by NS_AsyncCopy
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ bool result = false;
+ sts->IsOnCurrentThread(&result);
+ MOZ_ASSERT(result, "Should only be called on the STS thread.");
+#endif
+
+ RefPtr<GenericPromise::Private> ready =
+ already_AddRefed(static_cast<GenericPromise::Private*>(aClosure));
+ if (NS_SUCCEEDED(aStatus)) {
+ ready->Resolve(true, __func__);
+ } else {
+ ready->Reject(aStatus, __func__);
+ }
+}
+
+// Normalize the upload stream for a HTTP channel, so that is one of the
+// expected and compatible types. Components like WebExtensions and DevTools
+// expect that upload streams in the parent process are cloneable, seekable, and
+// synchronous to read, which this function helps guarantee somewhat efficiently
+// and without loss of information.
+//
+// If the replacement stream outparameter is not initialized to `nullptr`, the
+// returned stream should be used instead of `aUploadStream` as the upload
+// stream for the HTTP channel, and the previous stream should not be touched
+// again.
+//
+// If aReadyPromise is non-nullptr after the function is called, it is a promise
+// which should be awaited before continuing to `AsyncOpen` the HTTP channel,
+// as the replacement stream will not be ready until it is resolved.
+static nsresult NormalizeUploadStream(nsIInputStream* aUploadStream,
+ nsIInputStream** aReplacementStream,
+ GenericPromise** aReadyPromise) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ *aReplacementStream = nullptr;
+ *aReadyPromise = nullptr;
+
+ // Unwrap RemoteLazyInputStream and normalize the contents as we're in the
+ // parent process.
+ if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
+ do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> internal;
+ if (NS_SUCCEEDED(
+ lazyStream->TakeInternalStream(getter_AddRefs(internal)))) {
+ nsCOMPtr<nsIInputStream> replacement;
+ nsresult rv = NormalizeUploadStream(internal, getter_AddRefs(replacement),
+ aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (replacement) {
+ replacement.forget(aReplacementStream);
+ } else {
+ internal.forget(aReplacementStream);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Preserve MIME information on the stream when normalizing.
+ if (nsCOMPtr<nsIMIMEInputStream> mime = do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> data;
+ nsresult rv = mime->GetData(getter_AddRefs(data));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> replacement;
+ rv =
+ NormalizeUploadStream(data, getter_AddRefs(replacement), aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (replacement) {
+ nsCOMPtr<nsIMIMEInputStream> replacementMime(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new MIMEHeaderCopyVisitor(replacementMime);
+ rv = mime->VisitHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = replacementMime->SetData(replacement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ replacementMime.forget(aReplacementStream);
+ }
+ return NS_OK;
+ }
+
+ // Preserve "real" buffered input streams which wrap data (i.e. are backed by
+ // nsBufferedInputStream), but normalize the wrapped stream.
+ if (nsCOMPtr<nsIBufferedInputStream> buffered =
+ do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> data;
+ if (NS_SUCCEEDED(buffered->GetData(getter_AddRefs(data)))) {
+ nsCOMPtr<nsIInputStream> replacement;
+ nsresult rv = NormalizeUploadStream(data, getter_AddRefs(replacement),
+ aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (replacement) {
+ // This buffer size should be kept in sync with HTMLFormSubmission.
+ rv = NS_NewBufferedInputStream(aReplacementStream, replacement.forget(),
+ 8192);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Preserve multiplex input streams, normalizing each individual inner stream
+ // to avoid unnecessary copying.
+ if (nsCOMPtr<nsIMultiplexInputStream> multiplex =
+ do_QueryInterface(aUploadStream)) {
+ uint32_t count = multiplex->GetCount();
+ nsTArray<nsCOMPtr<nsIInputStream>> streams(count);
+ nsTArray<RefPtr<GenericPromise>> promises(count);
+ bool replace = false;
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIInputStream> inner;
+ nsresult rv = multiplex->GetStream(i, getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<GenericPromise> promise;
+ nsCOMPtr<nsIInputStream> replacement;
+ rv = NormalizeUploadStream(inner, getter_AddRefs(replacement),
+ getter_AddRefs(promise));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (promise) {
+ promises.AppendElement(promise);
+ }
+ if (replacement) {
+ streams.AppendElement(replacement);
+ replace = true;
+ } else {
+ streams.AppendElement(inner);
+ }
+ }
+
+ // If any of the inner streams needed to be replaced, replace the entire
+ // nsIMultiplexInputStream.
+ if (replace) {
+ nsresult rv;
+ nsCOMPtr<nsIMultiplexInputStream> replacement =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto& stream : streams) {
+ rv = replacement->AppendStream(stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(CallQueryInterface(replacement, aReplacementStream));
+ }
+
+ // Wait for all inner promises to settle before resolving the final promise.
+ if (!promises.IsEmpty()) {
+ RefPtr<GenericPromise> ready =
+ GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](GenericPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResults)
+ -> RefPtr<GenericPromise> {
+ MOZ_ASSERT(aResults.IsResolve(),
+ "AllSettled never rejects");
+ for (auto& result : aResults.ResolveValue()) {
+ if (result.IsReject()) {
+ return GenericPromise::CreateAndReject(
+ result.RejectValue(), __func__);
+ }
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+ ready.forget(aReadyPromise);
+ }
+ return NS_OK;
+ }
+
+ // If the stream is cloneable, seekable and non-async, we can allow it. Async
+ // input streams can cause issues, as various consumers of input streams
+ // expect the payload to be synchronous and `Available()` to be the length of
+ // the stream, which is not true for asynchronous streams.
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(aUploadStream);
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
+ if (NS_InputStreamIsCloneable(aUploadStream) && seekable && !async) {
+ return NS_OK;
+ }
+
+ // Asynchronously copy our non-normalized stream into a StorageStream so that
+ // it is seekable, cloneable, and synchronous once the copy completes.
+
+ NS_WARNING("Upload Stream is being copied into StorageStream");
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ nsresult rv =
+ NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> sink;
+ rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> replacementStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(replacementStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure the source stream is buffered before starting the copy so we can use
+ // ReadSegments, as nsStorageStream doesn't implement WriteSegments.
+ nsCOMPtr<nsIInputStream> source = aUploadStream;
+ if (!NS_InputStreamIsBuffered(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> bufferedSource;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedSource),
+ source.forget(), 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+ source = bufferedSource.forget();
+ }
+
+ // Perform an AsyncCopy into the input stream on the STS.
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr<GenericPromise::Private> ready = new GenericPromise::Private(__func__);
+ rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
+ NormalizeCopyComplete, do_AddRef(ready).take());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ready.get()->Release();
+ return rv;
+ }
+
+ replacementStream.forget(aReplacementStream);
+ ready.forget(aReadyPromise);
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
+ nsIInputStream** aClonedStream) {
+ NS_ENSURE_ARG_POINTER(aContentLength);
+ NS_ENSURE_ARG_POINTER(aClonedStream);
+ *aClonedStream = nullptr;
+
+ if (!XRE_IsParentProcess()) {
+ NS_WARNING("CloneUploadStream is only supported in the parent process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!mUploadStream) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv =
+ NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ clonedStream.forget(aClonedStream);
+
+ *aContentLength = mReqContentLength;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel2
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
+ const nsACString& aContentType,
+ int64_t aContentLength,
+ const nsACString& aMethod,
+ bool aStreamHasHeaders) {
+ // Ensure stream is set and method is valid
+ NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
+
+ {
+ DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
+ MOZ_ASSERT(
+ !aStreamHasHeaders || NS_FAILED(CallQueryInterface(
+ aStream, getter_AddRefs(mimeStream.value))),
+ "nsIMIMEInputStream should not include headers");
+ }
+
+ nsresult rv = SetRequestMethod(aMethod);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aStreamHasHeaders && !aContentType.IsVoid()) {
+ if (aContentType.IsEmpty()) {
+ SetEmptyRequestHeader("Content-Type"_ns);
+ } else {
+ SetRequestHeader("Content-Type"_ns, aContentType, false);
+ }
+ }
+
+ StoreUploadStreamHasHeaders(aStreamHasHeaders);
+
+ return InternalSetUploadStream(aStream, aContentLength, !aStreamHasHeaders);
+}
+
+nsresult HttpBaseChannel::InternalSetUploadStream(
+ nsIInputStream* aUploadStream, int64_t aContentLength,
+ bool aSetContentLengthHeader) {
+ // If we're not on the main thread, such as for TRR, the content length must
+ // be provided, as we can't normalize our upload stream.
+ if (!NS_IsMainThread()) {
+ if (aContentLength < 0) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Upload content length must be explicit off-main-thread");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
+ if (!NS_InputStreamIsCloneable(aUploadStream) || !seekable) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Upload stream must be cloneable & seekable off-main-thread");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mUploadStream = aUploadStream;
+ ExplicitSetUploadStreamLength(aContentLength, aSetContentLengthHeader);
+ return NS_OK;
+ }
+
+ // Normalize the upload stream we're provided to ensure that it is cloneable,
+ // seekable, and synchronous when in the parent process.
+ //
+ // This might be an async operation, in which case ready will be returned and
+ // resolved when the operation is complete.
+ nsCOMPtr<nsIInputStream> replacement;
+ RefPtr<GenericPromise> ready;
+ if (XRE_IsParentProcess()) {
+ nsresult rv = NormalizeUploadStream(
+ aUploadStream, getter_AddRefs(replacement), getter_AddRefs(ready));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mUploadStream = replacement ? replacement.get() : aUploadStream;
+
+ // Once the upload stream is ready, fetch its length before proceeding with
+ // AsyncOpen.
+ auto onReady = [self = RefPtr{this}, aContentLength, aSetContentLengthHeader,
+ stream = mUploadStream]() {
+ auto setLengthAndResume = [self, aSetContentLengthHeader](int64_t aLength) {
+ self->StorePendingUploadStreamNormalization(false);
+ self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
+ aSetContentLengthHeader);
+ self->MaybeResumeAsyncOpen();
+ };
+
+ if (aContentLength >= 0) {
+ setLengthAndResume(aContentLength);
+ return;
+ }
+
+ int64_t length;
+ if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
+ setLengthAndResume(length);
+ return;
+ }
+
+ InputStreamLengthHelper::GetAsyncLength(stream, setLengthAndResume);
+ };
+ StorePendingUploadStreamNormalization(true);
+
+ // Resolve onReady synchronously unless a promise is returned.
+ if (ready) {
+ ready->Then(GetCurrentSerialEventTarget(), __func__,
+ [onReady = std::move(onReady)](
+ GenericPromise::ResolveOrRejectValue&&) { onReady(); });
+ } else {
+ onReady();
+ }
+ return NS_OK;
+}
+
+void HttpBaseChannel::ExplicitSetUploadStreamLength(
+ uint64_t aContentLength, bool aSetContentLengthHeader) {
+ // We already have the content length. We don't need to determinate it.
+ mReqContentLength = aContentLength;
+
+ if (!aSetContentLengthHeader) {
+ return;
+ }
+
+ nsAutoCString header;
+ header.AssignLiteral("Content-Length");
+
+ // Maybe the content-length header has been already set.
+ nsAutoCString value;
+ nsresult rv = GetRequestHeader(header, value);
+ if (NS_SUCCEEDED(rv) && !value.IsEmpty()) {
+ return;
+ }
+
+ // SetRequestHeader propagates headers to chrome if HttpChannelChild
+ MOZ_ASSERT(!LoadWasOpened());
+ nsAutoCString contentLengthStr;
+ contentLengthStr.AppendInt(aContentLength);
+ SetRequestHeader(header, contentLengthStr, false);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
+ NS_ENSURE_ARG(hasHeaders);
+
+ *hasHeaders = LoadUploadStreamHasHeaders();
+ return NS_OK;
+}
+
+bool HttpBaseChannel::MaybeWaitForUploadStreamNormalization(
+ nsIStreamListener* aListener, nsISupports* aContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamNormalization(),
+ "AsyncOpen() called twice?");
+
+ if (!LoadPendingUploadStreamNormalization()) {
+ return false;
+ }
+
+ mListener = aListener;
+ StoreAsyncOpenWaitingForStreamNormalization(true);
+ return true;
+}
+
+void HttpBaseChannel::MaybeResumeAsyncOpen() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
+
+ if (!LoadAsyncOpenWaitingForStreamNormalization()) {
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ listener.swap(mListener);
+
+ StoreAsyncOpenWaitingForStreamNormalization(false);
+
+ nsresult rv = AsyncOpen(listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DoAsyncAbort(rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIEncodedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApplyConversion(bool* value) {
+ *value = LoadApplyConversion();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetApplyConversion(bool value) {
+ LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this,
+ value));
+ StoreApplyConversion(value);
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::DoApplyContentConversions(
+ nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener) {
+ return DoApplyContentConversions(aNextListener, aNewNextListener, nullptr);
+}
+
+// create a listener chain that looks like this
+// http-channel -> decompressor (n times) -> InterceptFailedOnSTop ->
+// channel-creator-listener
+//
+// we need to do this because not every decompressor has fully streamed output
+// so may need a call to OnStopRequest to identify its completion state.. and if
+// it creates an error there the channel status code needs to be updated before
+// calling the terminal listener. Having the decompress do it via cancel() means
+// channels cannot effectively be used in two contexts (specifically this one
+// and a peek context for sniffing)
+//
+class InterceptFailedOnStop : public nsIStreamListener {
+ virtual ~InterceptFailedOnStop() = default;
+ nsCOMPtr<nsIStreamListener> mNext;
+ HttpBaseChannel* mChannel;
+
+ public:
+ InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
+ : mNext(arg), mChannel(chan) {}
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
+ return mNext->OnStartRequest(aRequest);
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override {
+ if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
+ LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %" PRIx32,
+ mChannel, static_cast<uint32_t>(aStatusCode)));
+ mChannel->mStatus = aStatusCode;
+ }
+ return mNext->OnStopRequest(aRequest, aStatusCode);
+ }
+
+ NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) override {
+ return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+ }
+};
+
+NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener,
+ nsISupports* aCtxt) {
+ *aNewNextListener = nullptr;
+ if (!mResponseHead || !aNextListener) {
+ return NS_OK;
+ }
+
+ LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
+
+ if (!LoadApplyConversion()) {
+ LOG(("not applying conversion per ApplyConversion\n"));
+ return NS_OK;
+ }
+
+ if (LoadHasAppliedConversion()) {
+ LOG(("not applying conversion because HasAppliedConversion is true\n"));
+ return NS_OK;
+ }
+
+ if (LoadDeliveringAltData()) {
+ MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
+ LOG(("not applying conversion because delivering alt-data\n"));
+ return NS_OK;
+ }
+
+ nsAutoCString contentEncoding;
+ nsresult rv =
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ if (NS_FAILED(rv) || contentEncoding.IsEmpty()) return NS_OK;
+
+ nsCOMPtr<nsIStreamListener> nextListener =
+ new InterceptFailedOnStop(aNextListener, this);
+
+ // The encodings are listed in the order they were applied
+ // (see rfc 2616 section 14.11), so they need to removed in reverse
+ // order. This is accomplished because the converter chain ends up
+ // being a stack with the last converter created being the first one
+ // to accept the raw network data.
+
+ char* cePtr = contentEncoding.BeginWriting();
+ uint32_t count = 0;
+ while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) {
+ if (++count > 16) {
+ // That's ridiculous. We only understand 2 different ones :)
+ // but for compatibility with old code, we will just carry on without
+ // removing the encodings
+ LOG(("Too many Content-Encodings. Ignoring remainder.\n"));
+ break;
+ }
+
+ if (gHttpHandler->IsAcceptableEncoding(val,
+ isSecureOrTrustworthyURL(mURI))) {
+ RefPtr<nsHTTPCompressConv> converter = new nsHTTPCompressConv();
+ nsAutoCString from(val);
+ ToLowerCase(from);
+ rv = converter->AsyncConvertData(from.get(), "uncompressed", nextListener,
+ aCtxt);
+ if (NS_FAILED(rv)) {
+ LOG(("Unexpected failure of AsyncConvertData %s\n", val));
+ return rv;
+ }
+
+ LOG(("converter removed '%s' content-encoding\n", val));
+ if (Telemetry::CanRecordPrereleaseData()) {
+ int mode = 0;
+ if (from.EqualsLiteral("gzip") || from.EqualsLiteral("x-gzip")) {
+ mode = 1;
+ } else if (from.EqualsLiteral("deflate") ||
+ from.EqualsLiteral("x-deflate")) {
+ mode = 2;
+ } else if (from.EqualsLiteral("br")) {
+ mode = 3;
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
+ }
+ nextListener = converter;
+ } else {
+ if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
+ }
+ }
+ *aNewNextListener = do_AddRef(nextListener).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) {
+ if (!mResponseHead) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+
+ nsAutoCString encoding;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
+ if (encoding.IsEmpty()) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+ RefPtr<nsContentEncodings> enumerator =
+ new nsContentEncodings(this, encoding.get());
+ enumerator.forget(aEncodings);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <public>
+//-----------------------------------------------------------------------------
+
+HttpBaseChannel::nsContentEncodings::nsContentEncodings(
+ nsIHttpChannel* aChannel, const char* aEncodingHeader)
+ : mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(false) {
+ mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
+ mCurStart = mCurEnd;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISimpleEnumerator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::HasMore(bool* aMoreEncodings) {
+ if (mReady) {
+ *aMoreEncodings = true;
+ return NS_OK;
+ }
+
+ nsresult rv = PrepareForNext();
+ *aMoreEncodings = NS_SUCCEEDED(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding) {
+ aNextEncoding.Truncate();
+ if (!mReady) {
+ nsresult rv = PrepareForNext();
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ const nsACString& encoding = Substring(mCurStart, mCurEnd);
+
+ nsACString::const_iterator start, end;
+ encoding.BeginReading(start);
+ encoding.EndReading(end);
+
+ bool haveType = false;
+ if (CaseInsensitiveFindInReadable("gzip"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_GZIP);
+ haveType = true;
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("compress"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("deflate"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_ZIP);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("br"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_BROTLI);
+ haveType = true;
+ }
+ }
+
+ // Prepare to fetch the next encoding
+ mCurEnd = mCurStart;
+ mReady = false;
+
+ if (haveType) return NS_OK;
+
+ NS_WARNING("Unknown encoding type");
+ return NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(HttpBaseChannel::nsContentEncodings, nsIUTF8StringEnumerator,
+ nsIStringEnumerator)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <private>
+//-----------------------------------------------------------------------------
+
+nsresult HttpBaseChannel::nsContentEncodings::PrepareForNext(void) {
+ MOZ_ASSERT(mCurStart == mCurEnd, "Indeterminate state");
+
+ // At this point both mCurStart and mCurEnd point to somewhere
+ // past the end of the next thing we want to return
+
+ while (mCurEnd != mEncodingHeader) {
+ --mCurEnd;
+ if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd)) break;
+ }
+ if (mCurEnd == mEncodingHeader) {
+ return NS_ERROR_NOT_AVAILABLE; // no more encodings
+ }
+ ++mCurEnd;
+
+ // At this point mCurEnd points to the first char _after_ the
+ // header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
+
+ mCurStart = mCurEnd - 1;
+ while (mCurStart != mEncodingHeader && *mCurStart != ',' &&
+ !nsCRT::IsAsciiSpace(*mCurStart)) {
+ --mCurStart;
+ }
+ if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart)) {
+ ++mCurStart; // we stopped because of a weird char, so move up one
+ }
+
+ // At this point mCurStart and mCurEnd bracket the encoding string
+ // we want. Check that it's not "identity"
+ if (Substring(mCurStart, mCurEnd)
+ .Equals("identity", nsCaseInsensitiveCStringComparator)) {
+ mCurEnd = mCurStart;
+ return PrepareForNext();
+ }
+
+ mReady = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelId(uint64_t* aChannelId) {
+ NS_ENSURE_ARG_POINTER(aChannelId);
+ *aChannelId = mChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelId(uint64_t aChannelId) {
+ mChannelId = aChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ if (!mContentWindowId) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ GetCallback(loadContext);
+ if (loadContext) {
+ nsCOMPtr<mozIDOMWindowProxy> topWindow;
+ loadContext->GetTopWindow(getter_AddRefs(topWindow));
+ if (topWindow) {
+ if (nsPIDOMWindowInner* inner =
+ nsPIDOMWindowOuter::From(topWindow)->GetCurrentInnerWindow()) {
+ mContentWindowId = inner->WindowID();
+ }
+ }
+ }
+ }
+ *aWindowId = mContentWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetBrowserId(uint64_t aId) {
+ mBrowserId = aId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetBrowserId(uint64_t* aId) {
+ EnsureBrowserId();
+ *aId = mBrowserId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ mContentWindowId = aWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsThirdPartyTrackingResource(bool* aIsTrackingResource) {
+ MOZ_ASSERT(
+ !(mFirstPartyClassificationFlags && mThirdPartyClassificationFlags));
+ *aIsTrackingResource = UrlClassifierCommon::IsTrackingClassificationFlag(
+ mThirdPartyClassificationFlags,
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsThirdPartySocialTrackingResource(
+ bool* aIsThirdPartySocialTrackingResource) {
+ MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+ !mThirdPartyClassificationFlags);
+ *aIsThirdPartySocialTrackingResource =
+ UrlClassifierCommon::IsSocialTrackingClassificationFlag(
+ mThirdPartyClassificationFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetClassificationFlags(uint32_t* aFlags) {
+ if (mThirdPartyClassificationFlags) {
+ *aFlags = mThirdPartyClassificationFlags;
+ } else {
+ *aFlags = mFirstPartyClassificationFlags;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFirstPartyClassificationFlags(uint32_t* aFlags) {
+ *aFlags = mFirstPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyClassificationFlags(uint32_t* aFlags) {
+ *aFlags = mThirdPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransferSize(uint64_t* aTransferSize) {
+ MutexAutoLock lock(mOnDataFinishedMutex);
+ *aTransferSize = mTransferSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestSize(uint64_t* aRequestSize) {
+ *aRequestSize = mRequestSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ *aDecodedBodySize = mDecodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ MutexAutoLock lock(mOnDataFinishedMutex);
+ *aEncodedBodySize = mEncodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetSupportsHTTP3(bool* aSupportsHTTP3) {
+ *aSupportsHTTP3 = mSupportsHTTP3;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHasHTTPSRR(bool* aHasHTTPSRR) {
+ *aHasHTTPSRR = LoadHasHTTPSRR();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestMethod(nsACString& aMethod) {
+ mRequestHead.Method(aMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestMethod(const nsACString& aMethod) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ const nsCString& flatMethod = PromiseFlatCString(aMethod);
+
+ // Method names are restricted to valid HTTP tokens.
+ if (!nsHttp::IsValidToken(flatMethod)) return NS_ERROR_INVALID_ARG;
+
+ mRequestHead.SetMethod(flatMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ NS_ENSURE_ARG_POINTER(aReferrerInfo);
+ *aReferrerInfo = do_AddRef(mReferrerInfo).take();
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::SetReferrerInfoInternal(
+ nsIReferrerInfo* aReferrerInfo, bool aClone, bool aCompute,
+ bool aRespectBeforeConnect) {
+ LOG(
+ ("HttpBaseChannel::SetReferrerInfoInternal [this=%p aClone(%d) "
+ "aCompute(%d)]\n",
+ this, aClone, aCompute));
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ }
+
+ mReferrerInfo = aReferrerInfo;
+
+ // clear existing referrer, if any
+ nsresult rv = ClearReferrerHeader();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mReferrerInfo) {
+ return NS_OK;
+ }
+
+ if (aClone) {
+ mReferrerInfo = static_cast<dom::ReferrerInfo*>(aReferrerInfo)->Clone();
+ }
+
+ dom::ReferrerInfo* referrerInfo =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get());
+
+ // Don't set referrerInfo if it has not been initialized.
+ if (!referrerInfo->IsInitialized()) {
+ mReferrerInfo = nullptr;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aClone) {
+ // Record the telemetry once we set the referrer info to the channel
+ // successfully.
+ referrerInfo->RecordTelemetry(this);
+ }
+
+ if (aCompute) {
+ rv = referrerInfo->ComputeReferrer(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIURI> computedReferrer = mReferrerInfo->GetComputedReferrer();
+ if (!computedReferrer) {
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = computedReferrer->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return SetReferrerHeader(spec, aRespectBeforeConnect);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return SetReferrerInfoInternal(aReferrerInfo, true, true, true);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerInfoWithoutClone(nsIReferrerInfo* aReferrerInfo) {
+ return SetReferrerInfoInternal(aReferrerInfo, false, true, true);
+}
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+NS_IMETHODIMP
+HttpBaseChannel::GetProxyURI(nsIURI** aOut) {
+ NS_ENSURE_ARG_POINTER(aOut);
+ nsCOMPtr<nsIURI> result(mProxyURI);
+ result.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue) {
+ aValue.Truncate();
+
+ // XXX might be better to search the header list directly instead of
+ // hitting the http atom hash table.
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ return mRequestHead.GetHeader(atom, aValue);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ const nsCString& flatHeader = PromiseFlatCString(aHeader);
+ const nsCString& flatValue = PromiseFlatCString(aValue);
+
+ LOG(
+ ("HttpBaseChannel::SetRequestHeader [this=%p header=\"%s\" value=\"%s\" "
+ "merge=%u]\n",
+ this, flatHeader.get(), flatValue.get(), aMerge));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader) ||
+ !nsHttp::IsReasonableHeaderValue(flatValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ return mRequestHead.SetHeader(aHeader, flatValue, aMerge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewReferrerInfo(const nsACString& aUrl,
+ nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ nsresult rv;
+ // Create URI from string
+ nsCOMPtr<nsIURI> aURI;
+ rv = NS_NewURI(getter_AddRefs(aURI), aUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Create new ReferrerInfo and initialize it.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new mozilla::dom::ReferrerInfo();
+ rv = referrerInfo->Init(aPolicy, aSendReferrer, aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Set ReferrerInfo
+ return SetReferrerInfo(referrerInfo);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ const nsCString& flatHeader = PromiseFlatCString(aHeader);
+
+ LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n", this,
+ flatHeader.get()));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ return mRequestHead.SetEmptyHeader(aHeader);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* visitor) {
+ return mRequestHead.VisitHeaders(visitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor* visitor) {
+ return mRequestHead.VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterSkipDefault);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseHeader(const nsACString& header,
+ nsACString& value) {
+ value.Truncate();
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ return mResponseHead->GetHeader(atom, value);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ LOG(
+ ("HttpBaseChannel::SetResponseHeader [this=%p header=\"%s\" value=\"%s\" "
+ "merge=%u]\n",
+ this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(),
+ merge));
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ // these response headers must not be changed
+ if (atom == nsHttp::Content_Type || atom == nsHttp::Content_Length ||
+ atom == nsHttp::Content_Encoding || atom == nsHttp::Trailer ||
+ atom == nsHttp::Transfer_Encoding) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ StoreResponseHeadersModified(true);
+
+ return mResponseHead->SetHeader(header, value, merge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* visitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mResponseHead->VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterResponse);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->GetOriginalHeader(atom, aVisitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->VisitHeaders(
+ aVisitor, nsHttpHeaderArray::eFilterResponseOriginal);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSTS(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadAllowSTS();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSTS(bool value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ StoreAllowSTS(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsOCSP(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadIsOCSP();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsOCSP(bool value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ StoreIsOCSP(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsUserAgentHeaderModified(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadIsUserAgentHeaderModified();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsUserAgentHeaderModified(bool value) {
+ StoreIsUserAgentHeaderModified(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectionLimit(uint32_t* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = mRedirectionLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectionLimit(uint32_t value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mRedirectionLimit = std::min<uint32_t>(value, 0xff);
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::OverrideSecurityInfo(
+ nsITransportSecurityInfo* aSecurityInfo) {
+ MOZ_ASSERT(!mSecurityInfo,
+ "This can only be called when we don't have a security info "
+ "object already");
+ MOZ_RELEASE_ASSERT(
+ aSecurityInfo,
+ "This can only be called with a valid security info object");
+ MOZ_ASSERT(!BypassServiceWorker(),
+ "This can only be called on channels that are not bypassing "
+ "interception");
+ MOZ_ASSERT(LoadResponseCouldBeSynthesized(),
+ "This can only be called on channels that can be intercepted");
+ if (mSecurityInfo) {
+ LOG(
+ ("HttpBaseChannel::OverrideSecurityInfo mSecurityInfo is null! "
+ "[this=%p]\n",
+ this));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!LoadResponseCouldBeSynthesized()) {
+ LOG(
+ ("HttpBaseChannel::OverrideSecurityInfo channel cannot be intercepted! "
+ "[this=%p]\n",
+ this));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoStoreResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoStore();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoCacheResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoCache();
+ if (!*value) *value = mResponseHead->ExpiresInPast();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPrivateResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->Private();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatus(uint32_t* aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *aValue = mResponseHead->Status();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatusText(nsACString& aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ nsAutoCString version;
+ // https://fetch.spec.whatwg.org :
+ // Responses over an HTTP/2 connection will always have the empty byte
+ // sequence as status message as HTTP/2 does not support them.
+ if (NS_WARN_IF(NS_FAILED(GetProtocolVersion(version))) ||
+ !version.EqualsLiteral("h2")) {
+ mResponseHead->StatusText(aValue);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestSucceeded(bool* aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ uint32_t status = mResponseHead->Status();
+ *aValue = (status / 100 == 2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::RedirectTo(nsIURI* targetURI) {
+ NS_ENSURE_ARG(targetURI);
+
+ nsAutoCString spec;
+ targetURI->GetAsciiSpec(spec);
+ LOG(("HttpBaseChannel::RedirectTo [this=%p, uri=%s]", this, spec.get()));
+ LogCallingScriptLocation(this);
+
+ // We cannot redirect after OnStartRequest of the listener
+ // has been called, since to redirect we have to switch channels
+ // and the dance with OnStartRequest et al has to start over.
+ // This would break the nsIStreamListener contract.
+ NS_ENSURE_FALSE(LoadOnStartRequestCalled(), NS_ERROR_NOT_AVAILABLE);
+
+ mAPIRedirectToURI = targetURI;
+ // Only Web Extensions are allowed to redirect a channel to a data:
+ // URI. To avoid any bypasses after the channel was flagged by
+ // the WebRequst API, we are dropping the flag here.
+ mLoadInfo->SetAllowInsecureRedirectToDataURI(false);
+
+ // We may want to rewrite origin allowance, hence we need an
+ // artificial response head.
+ if (!mResponseHead) {
+ mResponseHead.reset(new nsHttpResponseHead());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::UpgradeToSecure() {
+ // Upgrades are handled internally between http-on-modify-request and
+ // http-on-before-connect, which means upgrades are only possible during
+ // on-modify, or WebRequest.onBeforeRequest in Web Extensions. Once we are
+ // past the code path where upgrades are handled, attempting an upgrade
+ // will throw an error.
+ NS_ENSURE_TRUE(LoadUpgradableToSecure(), NS_ERROR_NOT_AVAILABLE);
+
+ StoreUpgradeToSecure(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestContextID(uint64_t* aRCID) {
+ NS_ENSURE_ARG_POINTER(aRCID);
+ *aRCID = mRequestContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestContextID(uint64_t aRCID) {
+ mRequestContextID = aRCID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue) {
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = IsNavigation();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsMainDocumentChannel(bool aValue) {
+ StoreForceMainDocumentChannel(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ // Try to use ALPN if available and if it is not for a proxy, i.e if an
+ // https proxy was not used or if https proxy was used but the connection to
+ // the origin server is also https. In the case, an https proxy was used and
+ // the connection to the origin server was http, mSecurityInfo will be from
+ // the proxy.
+ if (!mConnectionInfo || !mConnectionInfo->UsingHttpsProxy() ||
+ mConnectionInfo->EndToEndSSL()) {
+ nsAutoCString protocol;
+ if (mSecurityInfo &&
+ NS_SUCCEEDED(mSecurityInfo->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ // The negotiated protocol was not empty so we can use it.
+ aProtocolVersion = protocol;
+ return NS_OK;
+ }
+ }
+
+ if (mResponseHead) {
+ HttpVersion version = mResponseHead->Version();
+ aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) {
+ if (!aTopWindowURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mTopWindowURI) {
+ LOG(
+ ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
+ "mTopWindowURI is already set.\n",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> topWindowURI;
+ Unused << GetTopWindowURI(getter_AddRefs(topWindowURI));
+
+ // Don't modify |mTopWindowURI| if we can get one from GetTopWindowURI().
+ if (topWindowURI) {
+ LOG(
+ ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
+ "Return an error since we got a top window uri.\n",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ mTopWindowURI = aTopWindowURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTopWindowURI(nsIURI** aTopWindowURI) {
+ nsCOMPtr<nsIURI> uriBeingLoaded =
+ AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(this);
+ return GetTopWindowURI(uriBeingLoaded, aTopWindowURI);
+}
+
+nsresult HttpBaseChannel::GetTopWindowURI(nsIURI* aURIBeingLoaded,
+ nsIURI** aTopWindowURI) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<mozIThirdPartyUtil> util;
+ // Only compute the top window URI once. In e10s, this must be computed in the
+ // child. The parent gets the top window URI through HttpChannelOpenArgs.
+ if (!mTopWindowURI) {
+ util = components::ThirdPartyUtil::Service();
+ if (!util) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = util->GetTopWindowForChannel(this, aURIBeingLoaded,
+ getter_AddRefs(win));
+ if (NS_SUCCEEDED(rv)) {
+ rv = util->GetURIFromWindow(win, getter_AddRefs(mTopWindowURI));
+#if DEBUG
+ if (mTopWindowURI) {
+ nsCString spec;
+ if (NS_SUCCEEDED(mTopWindowURI->GetSpec(spec))) {
+ LOG(("HttpChannelBase::Setting topwindow URI spec %s [this=%p]\n",
+ spec.get(), this));
+ }
+ }
+#endif
+ }
+ }
+ *aTopWindowURI = do_AddRef(mTopWindowURI).take();
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
+ NS_ENSURE_ARG_POINTER(aDocumentURI);
+ *aDocumentURI = do_AddRef(mDocumentURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocumentURI(nsIURI* aDocumentURI) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mDocumentURI = aDocumentURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestVersion(uint32_t* major, uint32_t* minor) {
+ HttpVersion version = mRequestHead.Version();
+
+ if (major) {
+ *major = static_cast<uint32_t>(version) / 10;
+ }
+ if (minor) {
+ *minor = static_cast<uint32_t>(version) % 10;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseVersion(uint32_t* major, uint32_t* minor) {
+ if (!mResponseHead) {
+ *major = *minor = 0; // we should at least be kind about it
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ HttpVersion version = mResponseHead->Version();
+
+ if (major) {
+ *major = static_cast<uint32_t>(version) / 10;
+ }
+ if (minor) {
+ *minor = static_cast<uint32_t>(version) % 10;
+ }
+
+ return NS_OK;
+}
+
+void HttpBaseChannel::NotifySetCookie(const nsACString& aCookie) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(static_cast<nsIChannel*>(this),
+ "http-on-response-set-cookie",
+ NS_ConvertASCIItoUTF16(aCookie).get());
+ }
+}
+
+bool HttpBaseChannel::IsBrowsingContextDiscarded() const {
+ // If there is no loadGroup attached to the current channel, we check the
+ // global private browsing state for the private channel instead. For
+ // non-private channel, we will always return false here.
+ //
+ // Note that we can only access the global private browsing state in the
+ // parent process. So, we will fallback to just return false in the content
+ // process.
+ if (!mLoadGroup) {
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+
+ return mLoadInfo->GetOriginAttributes().mPrivateBrowsingId != 0 &&
+ !dom::CanonicalBrowsingContext::IsPrivateBrowsingActive();
+ }
+
+ return mLoadGroup->GetIsBrowsingContextDiscarded();
+}
+
+// https://mikewest.github.io/corpp/#process-navigation-response
+nsresult HttpBaseChannel::ProcessCrossOriginEmbedderPolicyHeader() {
+ nsresult rv;
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+
+ // Only consider Cross-Origin-Embedder-Policy for document loads.
+ if (mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT &&
+ mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ return NS_OK;
+ }
+
+ nsILoadInfo::CrossOriginEmbedderPolicy resultPolicy =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+ bool isCoepCredentiallessEnabled;
+ rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ &isCoepCredentiallessEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &resultPolicy);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // https://html.spec.whatwg.org/multipage/origin.html#coep
+ if (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT &&
+ !nsHttpChannel::IsRedirectStatus(mResponseHead->Status()) &&
+ mLoadInfo->GetLoadingEmbedderPolicy() !=
+ nsILoadInfo::EMBEDDER_POLICY_NULL &&
+ resultPolicy != nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP &&
+ resultPolicy != nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
+ return NS_ERROR_DOM_COEP_FAILED;
+ }
+
+ return NS_OK;
+}
+
+// https://mikewest.github.io/corpp/#corp-check
+nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() {
+ // Fetch 4.5.9
+ dom::RequestMode requestMode;
+ MOZ_ALWAYS_SUCCEEDS(GetRequestMode(&requestMode));
+ // XXX this seems wrong per spec? What about navigate
+ if (requestMode != RequestMode::No_cors) {
+ return NS_OK;
+ }
+
+ // We only apply this for resources.
+ auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (extContentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
+ extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ return NS_OK;
+ }
+
+ if (extContentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // COEP pref off, skip CORP checking for subdocument.
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+ // COEP 3.2.1.2 when request targets a nested browsing context then embedder
+ // policy value is "unsafe-none", then return allowed.
+ if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_NULL) {
+ return NS_OK;
+ }
+ }
+
+ MOZ_ASSERT(mLoadInfo->GetLoadingPrincipal(),
+ "Resources should always have a LoadingPrincipal");
+ if (!mResponseHead) {
+ return NS_OK;
+ }
+
+ if (mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()) {
+ return NS_OK;
+ }
+
+ nsAutoCString content;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Resource_Policy,
+ content);
+
+ if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ if (content.IsEmpty()) {
+ if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
+ bool requestIncludesCredentials = false;
+ nsresult rv = GetCorsIncludeCredentials(&requestIncludesCredentials);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ // COEP: Set policy to `same-origin` if: response’s
+ // request-includes-credentials is true, or forNavigation is true.
+ if (requestIncludesCredentials ||
+ extContentPolicyType == ExtContentPolicyType::TYPE_SUBDOCUMENT) {
+ content = "same-origin"_ns;
+ }
+ } else if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
+ // COEP 3.2.1.6 If policy is null, and embedder policy is
+ // "require-corp", set policy to "same-origin". Note that we treat
+ // invalid value as "cross-origin", which spec indicates. We might want
+ // to make that stricter.
+ content = "same-origin"_ns;
+ }
+ }
+ }
+
+ if (content.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> channelOrigin;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(channelOrigin));
+
+ // Cross-Origin-Resource-Policy = %s"same-origin" / %s"same-site" /
+ // %s"cross-origin"
+ if (content.EqualsLiteral("same-origin")) {
+ if (!channelOrigin->Equals(mLoadInfo->GetLoadingPrincipal())) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+ return NS_OK;
+ }
+ if (content.EqualsLiteral("same-site")) {
+ nsAutoCString documentBaseDomain;
+ nsAutoCString resourceBaseDomain;
+ mLoadInfo->GetLoadingPrincipal()->GetBaseDomain(documentBaseDomain);
+ channelOrigin->GetBaseDomain(resourceBaseDomain);
+ if (documentBaseDomain != resourceBaseDomain) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+
+ nsCOMPtr<nsIURI> resourceURI = channelOrigin->GetURI();
+ if (!mLoadInfo->GetLoadingPrincipal()->SchemeIs("https") &&
+ resourceURI->SchemeIs("https")) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+// This method runs steps 1-4 of the algorithm to compare
+// cross-origin-opener policies
+static bool CompareCrossOriginOpenerPolicies(
+ nsILoadInfo::CrossOriginOpenerPolicy documentPolicy,
+ nsIPrincipal* documentOrigin,
+ nsILoadInfo::CrossOriginOpenerPolicy resultPolicy,
+ nsIPrincipal* resultOrigin) {
+ if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
+ resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ return true;
+ }
+
+ if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE ||
+ resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ return false;
+ }
+
+ if (documentPolicy == resultPolicy && documentOrigin->Equals(resultOrigin)) {
+ return true;
+ }
+
+ return false;
+}
+
+// This runs steps 1-5 of the algorithm when navigating a top level document.
+// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+nsresult HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ StoreHasCrossOriginOpenerPolicyMismatch(false);
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy()) {
+ return NS_OK;
+ }
+
+ // Only consider Cross-Origin-Opener-Policy for toplevel document loads.
+ if (mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ return NS_OK;
+ }
+
+ // Maybe the channel failed and we have no response head?
+ if (!mResponseHead) {
+ // Not having a response head is not a hard failure at the point where
+ // this method is called.
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::BrowsingContext> ctx;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
+
+ // In xpcshell-tests we don't always have a browsingContext
+ if (!ctx) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> resultOrigin;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultOrigin));
+
+ // Get the policy of the active document, and the policy for the result.
+ nsILoadInfo::CrossOriginOpenerPolicy documentPolicy = ctx->GetOpenerPolicy();
+ nsILoadInfo::CrossOriginOpenerPolicy resultPolicy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+ Unused << ComputeCrossOriginOpenerPolicy(documentPolicy, &resultPolicy);
+ mComputedCrossOriginOpenerPolicy = resultPolicy;
+
+ // Add a permission to mark this site as high-value into the permission DB.
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ mozilla::dom::AddHighValuePermission(
+ resultOrigin, mozilla::dom::kHighValueCOOPPermission);
+ }
+
+ // If bc's popup sandboxing flag set is not empty and potentialCOOP is
+ // non-null, then navigate bc to a network error and abort these steps.
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
+ mLoadInfo->GetSandboxFlags()) {
+ LOG((
+ "HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch network error "
+ "for non empty sandboxing and non null COOP"));
+ return NS_ERROR_DOM_COOP_FAILED;
+ }
+
+ // In xpcshell-tests we don't always have a current window global
+ RefPtr<mozilla::dom::WindowGlobalParent> currentWindowGlobal =
+ ctx->Canonical()->GetCurrentWindowGlobal();
+ if (!currentWindowGlobal) {
+ return NS_OK;
+ }
+
+ // We use the top window principal as the documentOrigin
+ nsCOMPtr<nsIPrincipal> documentOrigin =
+ currentWindowGlobal->DocumentPrincipal();
+
+ bool compareResult = CompareCrossOriginOpenerPolicies(
+ documentPolicy, documentOrigin, resultPolicy, resultOrigin);
+
+ if (LOG_ENABLED()) {
+ LOG(
+ ("HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch - "
+ "doc:%d result:%d - compare:%d\n",
+ documentPolicy, resultPolicy, compareResult));
+ nsAutoCString docOrigin("(null)");
+ nsCOMPtr<nsIURI> uri = documentOrigin->GetURI();
+ if (uri) {
+ uri->GetSpec(docOrigin);
+ }
+ nsAutoCString resOrigin("(null)");
+ uri = resultOrigin->GetURI();
+ if (uri) {
+ uri->GetSpec(resOrigin);
+ }
+ LOG(("doc origin:%s - res origin: %s\n", docOrigin.get(), resOrigin.get()));
+ }
+
+ if (compareResult) {
+ return NS_OK;
+ }
+
+ // If one of the following is false:
+ // - document's policy is same-origin-allow-popups
+ // - resultPolicy is null
+ // - doc is the initial about:blank document
+ // then we have a mismatch.
+
+ if (documentPolicy != nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ if (!currentWindowGlobal->IsInitialDocument()) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::ProcessCrossOriginSecurityHeaders() {
+ StoreProcessCrossOriginSecurityHeadersCalled(true);
+ nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ComputeCrossOriginOpenerPolicyMismatch();
+}
+
+enum class Report { Error, Warning };
+
+// Helper Function to report messages to the console when the loaded
+// script had a wrong MIME type.
+void ReportMimeTypeMismatch(HttpBaseChannel* aChannel, const char* aMessageName,
+ nsIURI* aURI, const nsACString& aContentType,
+ Report report) {
+ NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 contentType(aContentType);
+
+ aChannel->LogMimeTypeMismatch(nsCString(aMessageName),
+ report == Report::Warning, spec, contentType);
+}
+
+// Check and potentially enforce X-Content-Type-Options: nosniff
+nsresult ProcessXCTO(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ return NS_OK;
+ }
+
+ // 1) Query the XCTO header and check if 'nosniff' is the first value.
+ nsAutoCString contentTypeOptionsHeader;
+ if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) {
+ // if failed to get XCTO header, then there is nothing to do.
+ return NS_OK;
+ }
+
+ // let's compare the header (ignoring case)
+ // e.g. "NoSniFF" -> "nosniff"
+ // if it's not 'nosniff' then there is nothing to do here
+ if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
+ // since we are getting here, the XCTO header was sent;
+ // a non matching value most likely means a mistake happenend;
+ // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
+ AutoTArray<nsString, 1> params;
+ CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement());
+ RefPtr<dom::Document> doc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "XCTO"_ns, doc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "XCTOHeaderValueMissing", params);
+ return NS_OK;
+ }
+
+ // 2) Query the content type from the channel
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+
+ // 3) Compare the expected MIME type with the actual type
+ if (aLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_STYLESHEET) {
+ if (contentType.EqualsLiteral(TEXT_CSS)) {
+ return NS_OK;
+ }
+ ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
+ Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SCRIPT) {
+ if (nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(contentType))) {
+ return NS_OK;
+ }
+ ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
+ Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ auto policyType = aLoadInfo->GetExternalContentPolicyType();
+ if (policyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ policyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // If the header XCTO nosniff is set for any browsing context, then
+ // we set the skipContentSniffing flag on the Loadinfo. Within
+ // GetMIMETypeFromContent we then bail early and do not do any sniffing.
+ aLoadInfo->SetSkipContentSniffing(true);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// Ensure that a load of type script has correct MIME type
+nsresult EnsureMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ return NS_OK;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SCRIPT) {
+ // if this is not a script load, then there is nothing to do
+ return NS_OK;
+ }
+
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+ NS_ConvertUTF8toUTF16 typeString(contentType);
+
+ if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ // script load has type script
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript);
+ return NS_OK;
+ }
+
+ switch (aLoadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
+ case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worklet_load);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected script type");
+ break;
+ }
+
+ if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI)) {
+ // same origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin);
+ } else {
+ bool cors = false;
+ nsAutoCString corsOrigin;
+ nsresult rv = aResponseHead->GetHeader(
+ nsHttp::ResolveAtom("Access-Control-Allow-Origin"_ns), corsOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ if (corsOrigin.Equals("*")) {
+ cors = true;
+ } else {
+ nsCOMPtr<nsIURI> corsOriginURI;
+ rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(corsOriginURI)) {
+ cors = true;
+ }
+ }
+ }
+ }
+ if (cors) {
+ // cors origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin);
+ } else {
+ // cross origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin);
+ }
+ }
+
+ bool block = false;
+ if (StringBeginsWith(contentType, "image/"_ns)) {
+ // script load has type image
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image);
+ block = true;
+ } else if (StringBeginsWith(contentType, "audio/"_ns)) {
+ // script load has type audio
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio);
+ block = true;
+ } else if (StringBeginsWith(contentType, "video/"_ns)) {
+ // script load has type video
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video);
+ block = true;
+ } else if (StringBeginsWith(contentType, "text/csv"_ns)) {
+ // script load has type text/csv
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv);
+ block = true;
+ }
+
+ if (block) {
+ ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (StringBeginsWith(contentType, "text/plain"_ns)) {
+ // script load has type text/plain
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain);
+ } else if (StringBeginsWith(contentType, "text/xml"_ns)) {
+ // script load has type text/xml
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml);
+ } else if (StringBeginsWith(contentType, "application/octet-stream"_ns)) {
+ // script load has type application/octet-stream
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream);
+ } else if (StringBeginsWith(contentType, "application/xml"_ns)) {
+ // script load has type application/xml
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml);
+ } else if (StringBeginsWith(contentType, "application/json"_ns)) {
+ // script load has type application/json
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json);
+ } else if (StringBeginsWith(contentType, "text/json"_ns)) {
+ // script load has type text/json
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json);
+ } else if (StringBeginsWith(contentType, "text/html"_ns)) {
+ // script load has type text/html
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html);
+ } else if (contentType.IsEmpty()) {
+ // script load has no type
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty);
+ } else {
+ // script load has unknown type
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown);
+ }
+
+ // We restrict importScripts() in worker code to JavaScript MIME types.
+ nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE) {
+ ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType",
+ aURI, contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
+ // Do not block the load if the feature is not enabled.
+ if (!StaticPrefs::security_block_Worker_with_wrong_mime()) {
+ return NS_OK;
+ }
+
+ ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // ES6 modules require a strict MIME type check.
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) {
+ ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ return NS_OK;
+}
+
+// Warn when a load of type script uses a wrong MIME type and
+// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO.
+void WarnWrongMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // If there is no uri, no response head or no loadInfo, then there is
+ // nothing to do.
+ return;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SCRIPT) {
+ // If this is not a script load, then there is nothing to do.
+ return;
+ }
+
+ bool succeeded;
+ MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestSucceeded(&succeeded));
+ if (!succeeded) {
+ // Do not warn for failed loads: HTTP error pages are usually in HTML.
+ return;
+ }
+
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+ NS_ConvertUTF8toUTF16 typeString(contentType);
+ if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
+ contentType, Report::Warning);
+ }
+}
+
+nsresult HttpBaseChannel::ValidateMIMEType() {
+ nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ProcessXCTO(this, mURI, mResponseHead.get(), mLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WarnWrongMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
+ return NS_OK;
+}
+
+bool HttpBaseChannel::ShouldFilterOpaqueResponse(
+ OpaqueResponseFilterFetch aFilterType) const {
+ MOZ_DIAGNOSTIC_ASSERT(ShouldBlockOpaqueResponse());
+
+ if (!mLoadInfo || ConfiguredFilterFetchResponseBehaviour() != aFilterType) {
+ return false;
+ }
+
+ // We should filter a response in the parent if it is opaque and is the result
+ // of a fetch() function from the Fetch specification.
+ return mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH;
+}
+
+bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
+ if (!mURI || !mResponseHead || !mLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ LOGORB("No block: no mURI, mResponseHead, or mLoadInfo");
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = mLoadInfo->GetLoadingPrincipal();
+ if (!principal || principal->IsSystemPrincipal()) {
+ // If it's a top-level load or a system principal, then there is nothing to
+ // do.
+ LOGORB("No block: top-level load or system principal");
+ return false;
+ }
+
+ // Check if the response is a opaque response, which means requestMode should
+ // be RequestMode::No_cors and responseType should be ResponseType::Opaque.
+ nsContentPolicyType contentPolicy = mLoadInfo->InternalContentPolicyType();
+
+ // Skip the RequestMode would be RequestMode::Navigate
+ if (contentPolicy == nsIContentPolicy::TYPE_DOCUMENT ||
+ contentPolicy == nsIContentPolicy::TYPE_SUBDOCUMENT ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ // Skip the RequestMode would be RequestMode::Same_origin
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
+ return false;
+ }
+
+ uint32_t securityMode = mLoadInfo->GetSecurityMode();
+ // Skip when RequestMode would not be RequestMode::no_cors
+ if (securityMode !=
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL) {
+ LOGORB("No block: not no_cors requests");
+ return false;
+ }
+
+ // Only continue when ResponseType would be ResponseType::Opaque
+ if (mLoadInfo->GetTainting() != mozilla::LoadTainting::Opaque) {
+ LOGORB("No block: not opaque response");
+ return false;
+ }
+
+ auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (extContentPolicyType == ExtContentPolicy::TYPE_OBJECT ||
+ extContentPolicyType == ExtContentPolicy::TYPE_OBJECT_SUBREQUEST ||
+ extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
+ extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ LOGORB("No block: object || websocket request || save as download");
+ return false;
+ }
+
+ // Ignore the request from object or embed elements
+ if (mLoadInfo->GetIsFromObjectOrEmbed()) {
+ LOGORB("No block: Request From <object> or <embed>");
+ return false;
+ }
+
+ // Exclude no_cors System XHR
+ if (extContentPolicyType == ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ if (securityMode ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT) {
+ LOGORB("No block: System XHR");
+ return false;
+ }
+ }
+
+ uint32_t httpsOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
+ if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_BYPASS_ORB) {
+ LOGORB("No block: HTTPS_ONLY_BYPASS_ORB");
+ return false;
+ }
+
+ bool isInDevToolsContext;
+ mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
+ if (isInDevToolsContext) {
+ LOGORB("No block: Request created by devtools");
+ return false;
+ }
+
+ return true;
+}
+
+OpaqueResponse HttpBaseChannel::BlockOrFilterOpaqueResponse(
+ OpaqueResponseBlocker* aORB, const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
+ const char* aFormat, ...) {
+ NimbusFeatures::RecordExposureEvent("opaqueResponseBlocking"_ns, true);
+
+ const bool shouldFilter =
+ ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::BlockedByORB);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(GetORBLog(), LogLevel::Debug))) {
+ va_list ap;
+ va_start(ap, aFormat);
+ nsVprintfCString logString(aFormat, ap);
+ va_end(ap);
+
+ LOGORB("%s: %s", shouldFilter ? "Filtered" : "Blocked", logString.get());
+ }
+
+ if (shouldFilter) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::FILTERED_FETCH);
+ // The existence of `mORB` depends on `BlockOrFilterOpaqueResponse` being
+ // called before or after sniffing has completed.
+ // Another requirement is that `OpaqueResponseFilter` must come after
+ // `OpaqueResponseBlocker`, which is why in the case of having an
+ // `OpaqueResponseBlocker` we let it handle creating an
+ // `OpaqueResponseFilter`.
+ if (aORB) {
+ MOZ_DIAGNOSTIC_ASSERT(!mORB || aORB == mORB);
+ aORB->FilterResponse();
+ } else {
+ mListener = new OpaqueResponseFilter(mListener);
+ }
+ return OpaqueResponse::Allow;
+ }
+
+ LogORBError(aReason, aTelemetryReason);
+ return OpaqueResponse::Block;
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+OpaqueResponse
+HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
+ if (!ShouldBlockOpaqueResponse()) {
+ return OpaqueResponse::Allow;
+ }
+
+ // Regardless of if ORB is enabled or not, we check if we should filter the
+ // response in the parent. This way data won't reach a content process that
+ // will create a filtered `Response` object. This is enabled when
+ // 'browser.opaqueResponseBlocking.filterFetchResponse' is
+ // `OpaqueResponseFilterFetch::All`.
+ // See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
+ if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::All)) {
+ mListener = new OpaqueResponseFilter(mListener);
+
+ // If we're filtering a response in the parent, there will be no data to
+ // determine if it should be blocked or not so the only option we have is to
+ // allow it.
+ return OpaqueResponse::Allow;
+ }
+
+ if (!mCachedOpaqueResponseBlockingPref) {
+ return OpaqueResponse::Allow;
+ }
+
+ // If ORB is enabled, we check if we should filter the response in the parent.
+ // This way data won't reach a content process that will create a filtered
+ // `Response` object. We allow ORB to determine if the response should be
+ // blocked or filtered, but regardless no data should reach the content
+ // process. This is enabled when
+ // 'browser.opaqueResponseBlocking.filterFetchResponse' is
+ // `OpaqueResponseFilterFetch::AllowedByORB`.
+ // See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
+ if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::AllowedByORB)) {
+ mListener = new OpaqueResponseFilter(mListener);
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::
+ OPAQUE_RESPONSE_BLOCKING_CROSS_ORIGIN_OPAQUE_RESPONSE_COUNT,
+ 1);
+
+ PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "Before sniff"_ns);
+
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ // Step 1
+ nsAutoCString contentType;
+ mResponseHead->ContentType(contentType);
+
+ // Step 2
+ nsAutoCString contentTypeOptionsHeader;
+ bool nosniff =
+ mResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
+ contentTypeOptionsHeader.EqualsIgnoreCase("nosniff");
+
+ // Step 3
+ switch (GetOpaqueResponseBlockedReason(contentType, mResponseHead->Status(),
+ nosniff)) {
+ case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED:
+ // Step 3.1
+ return OpaqueResponse::Allow;
+ case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING:
+ LOGORB("Allowed %s in a spec breaking way", contentType.get());
+ return OpaqueResponse::Allow;
+ case OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED:
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns,
+ OpaqueResponseBlockedTelemetryReason::MIME_NEVER_SNIFFED,
+ "BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
+ case OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED:
+ // Step 3.3
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns,
+ OpaqueResponseBlockedTelemetryReason::RESP_206_BLCLISTED,
+ "BLOCKED_206_AND_BLOCKEDLISTED");
+ case OpaqueResponseBlockedReason::
+ BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN:
+ // Step 3.4
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns,
+ OpaqueResponseBlockedTelemetryReason::NOSNIFF_BLC_OR_TEXTP,
+ "BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
+ default:
+ break;
+ }
+
+ // Step 4
+ // If it's a media subsequent request, we assume that it will only be made
+ // after a successful initial request.
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ bool isMediaInitialRequest;
+ mLoadInfo->GetIsMediaInitialRequest(&isMediaInitialRequest);
+ if (!isMediaInitialRequest) {
+ return OpaqueResponse::Allow;
+ }
+ }
+
+ // Step 5
+ if (mResponseHead->Status() == 206 &&
+ !IsFirstPartialResponse(*mResponseHead)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"response status is 206 and not first partial response"_ns,
+ OpaqueResponseBlockedTelemetryReason::RESP_206_BLCLISTED,
+ "Is not a valid partial response given 0");
+ }
+
+ // Setup for steps 6, 7, 8 and 10.
+ // Steps 6 and 7 are handled by the sniffer framework.
+ // Steps 8 and 10 by are handled by
+ // `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+ if (mLoadFlags & nsIChannel::LOAD_CALL_CONTENT_SNIFFERS) {
+ mSnifferCategoryType = SnifferCategoryType::All;
+ } else {
+ mSnifferCategoryType = SnifferCategoryType::OpaqueResponseBlocking;
+ }
+
+ mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
+
+ // Install an input stream listener that performs ORB checks that depend on
+ // inspecting the incoming data. It is crucial that `OnStartRequest` is called
+ // on this listener either after sniffing is completed or that we skip
+ // sniffing, otherwise `OpaqueResponseBlocker` will allow responses that it
+ // shouldn't.
+ mORB = new OpaqueResponseBlocker(mListener, this, contentType, nosniff);
+ mListener = mORB;
+
+ nsAutoCString contentEncoding;
+ nsresult rv =
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+
+ if (NS_SUCCEEDED(rv) && !contentEncoding.IsEmpty()) {
+ return OpaqueResponse::SniffCompressed;
+ }
+ mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
+ return OpaqueResponse::Sniff;
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
+ const nsACString& aContentType, bool aNoSniff) {
+ PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "After sniff"_ns);
+
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mCachedOpaqueResponseBlockingPref);
+
+ // Step 9
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: media request"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_MEDIA,
+ "media request");
+ }
+
+ // Step 11
+ if (aNoSniff) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: nosniff is true"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_NOSNIFF, "nosniff");
+ }
+
+ // Step 12
+ if (mResponseHead &&
+ (mResponseHead->Status() < 200 || mResponseHead->Status() > 299)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: status code is not in allowed range"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_STA_CODE,
+ "status code (%d) is not allowed", mResponseHead->Status());
+ }
+
+ // Step 13
+ if (!mResponseHead || aContentType.IsEmpty()) {
+ LOGORB("Allowed: mimeType is failure");
+ return OpaqueResponse::Allow;
+ }
+
+ // Step 14
+ if (StringBeginsWith(aContentType, "image/"_ns) ||
+ StringBeginsWith(aContentType, "video/"_ns) ||
+ StringBeginsWith(aContentType, "audio/"_ns)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_CT_FAIL,
+ "ContentType is image/video/audio");
+ }
+
+ return OpaqueResponse::Sniff;
+}
+
+bool HttpBaseChannel::NeedOpaqueResponseAllowedCheckAfterSniff() const {
+ return mORB ? mORB->IsSniffing() : false;
+}
+
+void HttpBaseChannel::BlockOpaqueResponseAfterSniff(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ LogORBError(aReason, aTelemetryReason);
+ mORB->BlockResponse(this, NS_ERROR_FAILURE);
+}
+
+void HttpBaseChannel::AllowOpaqueResponseAfterSniff() {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ mORB->AllowResponse();
+}
+
+void HttpBaseChannel::SetChannelBlockedByOpaqueResponse() {
+ mChannelBlockedByOpaqueResponse = true;
+
+ RefPtr<dom::BrowsingContext> browsingContext =
+ dom::BrowsingContext::GetCurrentTopByBrowserId(mBrowserId);
+ if (!browsingContext) {
+ return;
+ }
+
+ dom::WindowContext* windowContext = browsingContext->GetTopWindowContext();
+ if (windowContext) {
+ windowContext->Canonical()->SetShouldReportHasBlockedOpaqueResponse(
+ mLoadInfo->InternalContentPolicyType());
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCookie(const nsACString& aCookieHeader) {
+ if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK;
+
+ if (IsBrowsingContextDiscarded()) {
+ return NS_OK;
+ }
+
+ // empty header isn't an error
+ if (aCookieHeader.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsICookieService* cs = gHttpHandler->GetCookieService();
+ NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
+
+ nsresult rv = cs->SetCookieStringFromHttp(mURI, aCookieHeader, this);
+ if (NS_SUCCEEDED(rv)) {
+ NotifySetCookie(aCookieHeader);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyFlags(uint32_t* aFlags) {
+ *aFlags = LoadThirdPartyFlags();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ StoreThirdPartyFlags(aFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetForceAllowThirdPartyCookie(bool* aForce) {
+ *aForce = !!(LoadThirdPartyFlags() &
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ if (aForce) {
+ StoreThirdPartyFlags(LoadThirdPartyFlags() |
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ } else {
+ StoreThirdPartyFlags(LoadThirdPartyFlags() &
+ ~nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCanceled(bool* aCanceled) {
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelIsForDownload(bool* aChannelIsForDownload) {
+ *aChannelIsForDownload = LoadChannelIsForDownload();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
+ StoreChannelIsForDownload(aChannelIsForDownload);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) {
+ auto RedirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = RedirectedCachekeys.ref();
+ ref = WrapUnique(cacheKeys);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalAddress(nsACString& addr) {
+ if (mSelfAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetLength(kIPv6CStrBufSize);
+ mSelfAddr.ToStringBuffer(addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ aMessages.Clear();
+ for (const auto& pair : mSecurityConsoleMessages) {
+ nsresult rv;
+ nsCOMPtr<nsISecurityConsoleMessage> message =
+ do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ message->SetTag(pair.first);
+ message->SetCategory(pair.second);
+ aMessages.AppendElement(message);
+ }
+
+ MOZ_ASSERT(mSecurityConsoleMessages.Length() == aMessages.Length());
+ mSecurityConsoleMessages.Clear();
+
+ return NS_OK;
+}
+
+/* Please use this method with care. This can cause the message
+ * queue to grow large and cause the channel to take up a lot
+ * of memory. Use only static string messages and do not add
+ * server side data to the queue, as that can be large.
+ * Add only a limited number of messages to the queue to keep
+ * the channel size down and do so only in rare erroneous situations.
+ * More information can be found here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=846918
+ */
+nsresult HttpBaseChannel::AddSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ // nsSecurityConsoleMessage is not thread-safe refcounted.
+ // Delay the object construction until requested.
+ // See TakeAllSecurityMessages()
+ std::pair<nsString, nsString> pair(aMessageTag, aMessageCategory);
+ mSecurityConsoleMessages.AppendElement(std::move(pair));
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+
+ auto innerWindowID = loadInfo->GetInnerWindowID();
+
+ nsAutoString errorText;
+ rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES,
+ NS_ConvertUTF16toUTF8(aMessageTag).get(), errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->InitWithSourceURI(
+ errorText, mURI, u""_ns, 0, 0, nsIScriptError::warningFlag,
+ NS_ConvertUTF16toUTF8(aMessageCategory), innerWindowID);
+
+ console->LogMessage(error);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalPort(int32_t* port) {
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mSelfAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mSelfAddr.inet.port);
+ } else if (mSelfAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mSelfAddr.inet6.port);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemoteAddress(nsACString& addr) {
+ if (mPeerAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetLength(kIPv6CStrBufSize);
+ mPeerAddr.ToStringBuffer(addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemotePort(int32_t* port) {
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mPeerAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mPeerAddr.inet.port);
+ } else if (mPeerAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mPeerAddr.inet6.port);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::HTTPUpgrade(const nsACString& aProtocolName,
+ nsIHttpUpgradeListener* aListener) {
+ NS_ENSURE_ARG(!aProtocolName.IsEmpty());
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mUpgradeProtocol = aProtocolName;
+ mUpgradeProtocolCallback = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOnlyConnect(bool* aOnlyConnect) {
+ NS_ENSURE_ARG_POINTER(aOnlyConnect);
+
+ *aOnlyConnect = mCaps & NS_HTTP_CONNECT_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetConnectOnly() {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ if (!mUpgradeProtocolCallback) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCaps |= NS_HTTP_CONNECT_ONLY;
+ mProxyResolveFlags = nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL;
+ return SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_ANONYMOUS |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSpdy(bool* aAllowSpdy) {
+ NS_ENSURE_ARG_POINTER(aAllowSpdy);
+
+ *aAllowSpdy = LoadAllowSpdy();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) {
+ StoreAllowSpdy(aAllowSpdy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowHttp3(bool* aAllowHttp3) {
+ NS_ENSURE_ARG_POINTER(aAllowHttp3);
+
+ *aAllowHttp3 = LoadAllowHttp3();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowHttp3(bool aAllowHttp3) {
+ StoreAllowHttp3(aAllowHttp3);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowAltSvc(bool* aAllowAltSvc) {
+ NS_ENSURE_ARG_POINTER(aAllowAltSvc);
+
+ *aAllowAltSvc = LoadAllowAltSvc();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc) {
+ StoreAllowAltSvc(aAllowAltSvc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBeConservative(bool* aBeConservative) {
+ NS_ENSURE_ARG_POINTER(aBeConservative);
+
+ *aBeConservative = LoadBeConservative();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBeConservative(bool aBeConservative) {
+ StoreBeConservative(aBeConservative);
+ return NS_OK;
+}
+
+bool HttpBaseChannel::BypassProxy() {
+ return StaticPrefs::network_proxy_allow_bypass() && LoadBypassProxy();
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBypassProxy(bool* aBypassProxy) {
+ NS_ENSURE_ARG_POINTER(aBypassProxy);
+
+ *aBypassProxy = BypassProxy();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBypassProxy(bool aBypassProxy) {
+ if (StaticPrefs::network_proxy_allow_bypass()) {
+ StoreBypassProxy(aBypassProxy);
+ } else {
+ NS_WARNING("bypassProxy set but network.proxy.allow_bypass is disabled");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsTRRServiceChannel(bool* aIsTRRServiceChannel) {
+ NS_ENSURE_ARG_POINTER(aIsTRRServiceChannel);
+
+ *aIsTRRServiceChannel = LoadIsTRRServiceChannel();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsTRRServiceChannel(bool aIsTRRServiceChannel) {
+ StoreIsTRRServiceChannel(aIsTRRServiceChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsResolvedByTRR(bool* aResolvedByTRR) {
+ NS_ENSURE_ARG_POINTER(aResolvedByTRR);
+ *aResolvedByTRR = LoadResolvedByTRR();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEffectiveTRRMode(nsIRequest::TRRMode* aEffectiveTRRMode) {
+ *aEffectiveTRRMode = mEffectiveTRRMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTrrSkipReason(nsITRRSkipReason::value* aTrrSkipReason) {
+ *aTrrSkipReason = mTRRSkipReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsLoadedBySocketProcess(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = LoadLoadedBySocketProcess();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTlsFlags(uint32_t* aTlsFlags) {
+ NS_ENSURE_ARG_POINTER(aTlsFlags);
+
+ *aTlsFlags = mTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTlsFlags(uint32_t aTlsFlags) {
+ mTlsFlags = aTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApiRedirectToURI(nsIURI** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = do_AddRef(mAPIRedirectToURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseTimeoutEnabled(bool* aEnable) {
+ if (NS_WARN_IF(!aEnable)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aEnable = LoadResponseTimeoutEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable) {
+ StoreResponseTimeoutEnabled(aEnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitialRwin(uint32_t* aRwin) {
+ if (NS_WARN_IF(!aRwin)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aRwin = mInitialRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitialRwin(uint32_t aRwin) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mInitialRwin = aRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::ForcePending(bool aForcePending) {
+ StoreForcePending(aForcePending);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ uint32_t lastMod;
+ nsresult rv = mResponseHead->GetLastModifiedValue(&lastMod);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *lastModifiedTime = lastMod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude) {
+ *aInclude = LoadCorsIncludeCredentials();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude) {
+ StoreCorsIncludeCredentials(aInclude);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestMode(RequestMode* aMode) {
+ *aMode = mRequestMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestMode(RequestMode aMode) {
+ MOZ_ASSERT(aMode != RequestMode::EndGuard_);
+ mRequestMode = aMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectMode(uint32_t* aMode) {
+ *aMode = mRedirectMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectMode(uint32_t aMode) {
+ mRedirectMode = aMode;
+ return NS_OK;
+}
+
+namespace {
+
+bool ContainsAllFlags(uint32_t aLoadFlags, uint32_t aMask) {
+ return (aLoadFlags & aMask) == aMask;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode) {
+ NS_ENSURE_ARG_POINTER(aFetchCacheMode);
+
+ // Otherwise try to guess an appropriate cache mode from the load flags.
+ if (ContainsAllFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
+ } else if (ContainsAllFlags(mLoadFlags, LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
+ } else if (ContainsAllFlags(mLoadFlags, VALIDATE_ALWAYS) ||
+ LoadForceValidateCacheContent()) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
+ } else if (ContainsAllFlags(
+ mLoadFlags,
+ VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
+ } else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
+ } else {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+void SetCacheFlags(uint32_t& aLoadFlags, uint32_t aFlags) {
+ // First, clear any possible cache related flags.
+ uint32_t allPossibleFlags =
+ nsIRequest::INHIBIT_CACHING | nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::VALIDATE_ALWAYS | nsIRequest::LOAD_FROM_CACHE |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE;
+ aLoadFlags &= ~allPossibleFlags;
+
+ // Then set the new flags.
+ aLoadFlags |= aFlags;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ // Now, set the load flags that implement each cache mode.
+ switch (aFetchCacheMode) {
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT:
+ // The "default" mode means to use the http cache normally and
+ // respect any http cache-control headers. We effectively want
+ // to clear our cache related load flags.
+ SetCacheFlags(mLoadFlags, 0);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
+ // no-store means don't consult the cache on the way to the network, and
+ // don't store the response in the cache even if it's cacheable.
+ SetCacheFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
+ // reload means don't consult the cache on the way to the network, but
+ // do store the response in the cache if possible.
+ SetCacheFlags(mLoadFlags, LOAD_BYPASS_CACHE);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
+ // no-cache means always validate what's in the cache.
+ SetCacheFlags(mLoadFlags, VALIDATE_ALWAYS);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
+ // force-cache means don't validate unless if the response would vary.
+ SetCacheFlags(mLoadFlags, VALIDATE_NEVER);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
+ // only-if-cached means only from cache, no network, no validation,
+ // generate a network error if the document was't in the cache. The
+ // privacy implications of these flags (making it fast/easy to check if
+ // the user has things in their cache without any network traffic side
+ // effects) are addressed in the Request constructor which
+ // enforces/requires same-origin request mode.
+ SetCacheFlags(mLoadFlags,
+ VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE);
+ break;
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ uint32_t finalMode = 0;
+ MOZ_ALWAYS_SUCCEEDS(GetFetchCacheMode(&finalMode));
+ MOZ_DIAGNOSTIC_ASSERT(finalMode == aFetchCacheMode);
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
+ mIntegrityMetadata = aIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ aIntegrityMetadata = mIntegrityMetadata;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetPriority(int32_t* value) {
+ *value = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::AdjustPriority(int32_t delta) {
+ return SetPriority(mPriority + delta);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEntityID(nsACString& aEntityID) {
+ // Don't return an entity ID for Non-GET requests which require
+ // additional data
+ if (!mRequestHead.IsGet()) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ uint64_t size = UINT64_MAX;
+ nsAutoCString etag, lastmod;
+ if (mResponseHead) {
+ // Don't return an entity if the server sent the following header:
+ // Accept-Ranges: none
+ // Not sending the Accept-Ranges header means we can still try
+ // sending range requests.
+ nsAutoCString acceptRanges;
+ Unused << mResponseHead->GetHeader(nsHttp::Accept_Ranges, acceptRanges);
+ if (!acceptRanges.IsEmpty() &&
+ !nsHttp::FindToken(acceptRanges.get(), "bytes",
+ HTTP_HEADER_VALUE_SEPS)) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ size = mResponseHead->TotalEntitySize();
+ Unused << mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
+ Unused << mResponseHead->GetHeader(nsHttp::ETag, etag);
+ }
+ nsCString entityID;
+ NS_EscapeURL(etag.BeginReading(), etag.Length(),
+ esc_AlwaysCopy | esc_FileBaseName | esc_Forced, entityID);
+ entityID.Append('/');
+ entityID.AppendInt(int64_t(size));
+ entityID.Append('/');
+ entityID.Append(lastmod);
+ // NOTE: Appending lastmod as the last part avoids having to escape it
+
+ aEntityID = entityID;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIConsoleReportCollector
+//-----------------------------------------------------------------------------
+
+void HttpBaseChannel::AddConsoleReport(
+ uint32_t aErrorFlags, const nsACString& aCategory,
+ nsContentUtils::PropertiesFile aPropertiesFile,
+ const nsACString& aSourceFileURI, uint32_t aLineNumber,
+ uint32_t aColumnNumber, const nsACString& aMessageName,
+ const nsTArray<nsString>& aStringParams) {
+ mReportCollector->AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile,
+ aSourceFileURI, aLineNumber, aColumnNumber,
+ aMessageName, aStringParams);
+
+ // If this channel is already part of a loadGroup, we can flush this console
+ // report immediately.
+ HttpBaseChannel::MaybeFlushConsoleReports();
+}
+
+void HttpBaseChannel::FlushReportsToConsole(uint64_t aInnerWindowID,
+ ReportAction aAction) {
+ mReportCollector->FlushReportsToConsole(aInnerWindowID, aAction);
+}
+
+void HttpBaseChannel::FlushReportsToConsoleForServiceWorkerScope(
+ const nsACString& aScope, ReportAction aAction) {
+ mReportCollector->FlushReportsToConsoleForServiceWorkerScope(aScope, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(dom::Document* aDocument,
+ ReportAction aAction) {
+ mReportCollector->FlushConsoleReports(aDocument, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(nsILoadGroup* aLoadGroup,
+ ReportAction aAction) {
+ mReportCollector->FlushConsoleReports(aLoadGroup, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(
+ nsIConsoleReportCollector* aCollector) {
+ mReportCollector->FlushConsoleReports(aCollector);
+}
+
+void HttpBaseChannel::StealConsoleReports(
+ nsTArray<net::ConsoleReportCollected>& aReports) {
+ mReportCollector->StealConsoleReports(aReports);
+}
+
+void HttpBaseChannel::ClearConsoleReports() {
+ mReportCollector->ClearConsoleReports();
+}
+
+bool HttpBaseChannel::IsNavigation() {
+ return LoadForceMainDocumentChannel() || (mLoadFlags & LOAD_DOCUMENT_URI);
+}
+
+bool HttpBaseChannel::BypassServiceWorker() const {
+ return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
+}
+
+bool HttpBaseChannel::ShouldIntercept(nsIURI* aURI) {
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+ bool shouldIntercept = false;
+
+ if (!StaticPrefs::dom_serviceWorkers_enabled()) {
+ return false;
+ }
+
+ // We should never intercept internal redirects. The ServiceWorker code
+ // can trigger interntal redirects as the result of a FetchEvent. If
+ // we re-intercept then an infinite loop can occur.
+ //
+ // Its also important that we do not set the LOAD_BYPASS_SERVICE_WORKER
+ // flag because an internal redirect occurs. Its possible that another
+ // interception should occur after the internal redirect. For example,
+ // if the ServiceWorker chooses not to call respondWith() the channel
+ // will be reset with an internal redirect. If the request is a navigation
+ // and the network then triggers a redirect its possible the new URL
+ // should be intercepted again.
+ //
+ // Note, HSTS upgrade redirects are often treated the same as internal
+ // redirects. In this case, however, we intentionally allow interception
+ // of HSTS upgrade redirects. This matches the expected spec behavior and
+ // does not run the risk of infinite loops as described above.
+ bool internalRedirect =
+ mLastRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ if (controller && mLoadInfo && !BypassServiceWorker() && !internalRedirect) {
+ nsresult rv = controller->ShouldPrepareForIntercept(
+ aURI ? aURI : mURI.get(), this, &shouldIntercept);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+ return shouldIntercept;
+}
+
+void HttpBaseChannel::AddAsNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (EnsureRequestContext()) {
+ LOG((
+ "HttpBaseChannel::AddAsNonTailRequest this=%p, rc=%p, already added=%d",
+ this, mRequestContext.get(), (bool)LoadAddedAsNonTailRequest()));
+
+ if (!LoadAddedAsNonTailRequest()) {
+ mRequestContext->AddNonTailRequest();
+ StoreAddedAsNonTailRequest(true);
+ }
+ }
+}
+
+void HttpBaseChannel::RemoveAsNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mRequestContext) {
+ LOG(
+ ("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already "
+ "added=%d",
+ this, mRequestContext.get(), (bool)LoadAddedAsNonTailRequest()));
+
+ if (LoadAddedAsNonTailRequest()) {
+ mRequestContext->RemoveNonTailRequest();
+ StoreAddedAsNonTailRequest(false);
+ }
+ }
+}
+
+#ifdef DEBUG
+void HttpBaseChannel::AssertPrivateBrowsingId() {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+
+ if (!loadContext) {
+ return;
+ }
+
+ // We skip testing of favicon loading here since it could be triggered by XUL
+ // image which uses SystemPrincipal. The SystemPrincpal doesn't have
+ // mPrivateBrowsingId.
+ if (mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal() &&
+ mLoadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return;
+ }
+
+ OriginAttributes docShellAttrs;
+ loadContext->GetOriginAttributes(docShellAttrs);
+ MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId ==
+ docShellAttrs.mPrivateBrowsingId,
+ "PrivateBrowsingId values are not the same between LoadInfo and "
+ "LoadContext.");
+}
+#endif
+
+already_AddRefed<nsILoadInfo> HttpBaseChannel::CloneLoadInfoForRedirect(
+ nsIURI* aNewURI, uint32_t aRedirectFlags) {
+ // make a copy of the loadinfo, append to the redirectchain
+ // this will be set on the newly created channel for the redirect target.
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<mozilla::net::LoadInfo*>(mLoadInfo.get())->Clone();
+
+ ExtContentPolicyType contentPolicyType =
+ mLoadInfo->GetExternalContentPolicyType();
+ if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // Reset PrincipalToInherit to a null principal. We'll credit the the
+ // redirecting resource's result principal as the new principal's precursor.
+ // This means that a data: URI will end up loading in a process based on the
+ // redirected-from URI.
+ nsCOMPtr<nsIPrincipal> redirectPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(redirectPrincipal));
+ nsCOMPtr<nsIPrincipal> nullPrincipalToInherit =
+ NullPrincipal::CreateWithInheritedAttributes(redirectPrincipal);
+ newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
+ }
+
+ bool isTopLevelDoc = newLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT;
+
+ if (isTopLevelDoc) {
+ // re-compute the origin attributes of the loadInfo if it's top-level load.
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ OriginAttributes docShellAttrs;
+ if (loadContext) {
+ loadContext->GetOriginAttributes(docShellAttrs);
+ }
+
+ OriginAttributes attrs = newLoadInfo->GetOriginAttributes();
+
+ MOZ_ASSERT(
+ docShellAttrs.mUserContextId == attrs.mUserContextId,
+ "docshell and necko should have the same userContextId attribute.");
+ MOZ_ASSERT(
+ docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
+ "docshell and necko should have the same privateBrowsingId attribute.");
+ MOZ_ASSERT(docShellAttrs.mGeckoViewSessionContextId ==
+ attrs.mGeckoViewSessionContextId,
+ "docshell and necko should have the same "
+ "geckoViewSessionContextId attribute");
+
+ attrs = docShellAttrs;
+ attrs.SetFirstPartyDomain(true, aNewURI);
+ newLoadInfo->SetOriginAttributes(attrs);
+
+ // re-compute the upgrade insecure requests bit for document navigations
+ // since it should only apply to same-origin navigations (redirects).
+ // we only do this if the CSP of the triggering element (the cspToInherit)
+ // uses 'upgrade-insecure-requests', otherwise UIR does not apply.
+ nsCOMPtr<nsIContentSecurityPolicy> csp = newLoadInfo->GetCspToInherit();
+ if (csp) {
+ bool upgradeInsecureRequests = false;
+ csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
+ if (upgradeInsecureRequests) {
+ nsCOMPtr<nsIPrincipal> resultPrincipal =
+ BasePrincipal::CreateContentPrincipal(
+ aNewURI, newLoadInfo->GetOriginAttributes());
+ bool isConsideredSameOriginforUIR =
+ nsContentSecurityUtils::IsConsideredSameOriginForUIR(
+ newLoadInfo->TriggeringPrincipal(), resultPrincipal);
+ static_cast<mozilla::net::LoadInfo*>(newLoadInfo.get())
+ ->SetUpgradeInsecureRequests(isConsideredSameOriginforUIR);
+ }
+ }
+ }
+
+ // Leave empty, we want a 'clean ground' when creating the new channel.
+ // This will be ensured to be either set by the protocol handler or set
+ // to the redirect target URI properly after the channel creation.
+ newLoadInfo->SetResultPrincipalURI(nullptr);
+
+ bool isInternalRedirect =
+ (aRedirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ // Reset our sandboxed null principal ID when cloning loadInfo for an
+ // externally visible redirect.
+ if (!isInternalRedirect) {
+ // If we've redirected from http to something that isn't, clear
+ // the "external" flag, as loads that now go to other apps should be
+ // allowed to go ahead and not trip infinite-loop protection
+ // (see bug 1717314 for context).
+ if (!aNewURI->SchemeIs("http") && !aNewURI->SchemeIs("https")) {
+ newLoadInfo->SetLoadTriggeredFromExternal(false);
+ }
+ newLoadInfo->ResetSandboxedNullPrincipalID();
+ }
+
+ newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect);
+
+ return newLoadInfo.forget();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITraceableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewListener(nsIStreamListener* aListener,
+ bool aMustApplyContentConversion,
+ nsIStreamListener** _retval) {
+ LOG((
+ "HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
+ this, mListener.get(), aListener));
+
+ if (!LoadTracingEnabled()) return NS_ERROR_FAILURE;
+
+ NS_ENSURE_STATE(mListener);
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
+
+ wrapper.forget(_retval);
+ mListener = aListener;
+ if (aMustApplyContentConversion) {
+ StoreListenerRequiresContentConversion(true);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel helpers
+//-----------------------------------------------------------------------------
+
+void HttpBaseChannel::ReleaseListeners() {
+ MOZ_ASSERT(mCurrentThread->IsOnCurrentThread(),
+ "Should only be called on the current thread");
+
+ mListener = nullptr;
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+ mCompressListener = nullptr;
+ mORB = nullptr;
+}
+
+void HttpBaseChannel::DoNotifyListener() {
+ LOG(("HttpBaseChannel::DoNotifyListener this=%p", this));
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set AfterOnStartRequestBegun to true
+ // before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ // This channel has finished its job, potentially release any tail-blocked
+ // requests with this.
+ RemoveAsNonTailRequest();
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+
+ // If this is a navigation, then we must let the docshell flush the reports
+ // to the console later. The LoadDocument() is pointing at the detached
+ // document that started the navigation. We want to show the reports on the
+ // new document. Otherwise the console is wiped and the user never sees
+ // the information.
+ if (!IsNavigation()) {
+ if (mLoadGroup) {
+ FlushConsoleReports(mLoadGroup);
+ } else {
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ FlushConsoleReports(doc);
+ }
+ }
+}
+
+void HttpBaseChannel::AddCookiesToRequest() {
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ return;
+ }
+
+ bool useCookieService = (XRE_IsParentProcess());
+ nsAutoCString cookie;
+ if (useCookieService) {
+ nsICookieService* cs = gHttpHandler->GetCookieService();
+ if (cs) {
+ cs->GetCookieStringFromHttp(mURI, this, cookie);
+ }
+
+ if (cookie.IsEmpty()) {
+ cookie = mUserSetCookieHeader;
+ } else if (!mUserSetCookieHeader.IsEmpty()) {
+ cookie.AppendLiteral("; ");
+ cookie.Append(mUserSetCookieHeader);
+ }
+ } else {
+ cookie = mUserSetCookieHeader;
+ }
+
+ // If we are in the child process, we want the parent seeing any
+ // cookie headers that might have been set by SetRequestHeader()
+ SetRequestHeader(nsHttp::Cookie.val(), cookie, false);
+}
+
+/* static */
+void HttpBaseChannel::PropagateReferenceIfNeeded(
+ nsIURI* aURI, nsCOMPtr<nsIURI>& aRedirectURI) {
+ bool hasRef = false;
+ nsresult rv = aRedirectURI->GetHasRef(&hasRef);
+ if (NS_SUCCEEDED(rv) && !hasRef) {
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+ if (!ref.IsEmpty()) {
+ // NOTE: SetRef will fail if mRedirectURI is immutable
+ // (e.g. an about: URI)... Oh well.
+ Unused << NS_MutateURI(aRedirectURI).SetRef(ref).Finalize(aRedirectURI);
+ }
+ }
+}
+
+bool HttpBaseChannel::ShouldRewriteRedirectToGET(
+ uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method) {
+ // for 301 and 302, only rewrite POST
+ if (httpStatus == 301 || httpStatus == 302) {
+ return method == nsHttpRequestHead::kMethod_Post;
+ }
+
+ // rewrite for 303 unless it was HEAD
+ if (httpStatus == 303) return method != nsHttpRequestHead::kMethod_Head;
+
+ // otherwise, such as for 307, do not rewrite
+ return false;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ *aResult = false;
+ uint32_t httpStatus = 0;
+ if (NS_FAILED(GetResponseStatus(&httpStatus))) {
+ return NS_OK;
+ }
+
+ nsAutoCString method(aMethod);
+ nsHttpRequestHead::ParsedMethodType parsedMethod;
+ nsHttpRequestHead::ParseMethod(method, parsedMethod);
+ // Fetch 4.4.11, which is slightly different than the perserved method
+ // algrorithm: strip request-body-header for GET->GET redirection for 303.
+ *aResult =
+ ShouldRewriteRedirectToGET(httpStatus, parsedMethod) &&
+ !(httpStatus == 303 && parsedMethod == nsHttpRequestHead::kMethod_Get);
+
+ return NS_OK;
+}
+
+HttpBaseChannel::ReplacementChannelConfig
+HttpBaseChannel::CloneReplacementChannelConfig(bool aPreserveMethod,
+ uint32_t aRedirectFlags,
+ ReplacementReason aReason) {
+ ReplacementChannelConfig config;
+ config.redirectFlags = aRedirectFlags;
+ config.classOfService = mClassOfService;
+
+ if (mPrivateBrowsingOverriden) {
+ config.privateBrowsing = Some(mPrivateBrowsing);
+ }
+
+ if (mReferrerInfo) {
+ // When cloning for a document channel replacement (parent process
+ // copying values for a new content process channel), this happens after
+ // OnStartRequest so we have the headers for the response available.
+ // We don't want to apply them to the referrer for the channel though,
+ // since that is the referrer for the current document, and the header
+ // should only apply to navigations from the current document.
+ if (aReason == ReplacementReason::DocumentChannel) {
+ config.referrerInfo = mReferrerInfo;
+ } else {
+ dom::ReferrerPolicy referrerPolicy = dom::ReferrerPolicy::_empty;
+ nsAutoCString tRPHeaderCValue;
+ Unused << GetResponseHeader("referrer-policy"_ns, tRPHeaderCValue);
+ NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue);
+
+ if (!tRPHeaderValue.IsEmpty()) {
+ referrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyFromHeaderString(tRPHeaderValue);
+ }
+
+ // In case we are here because an upgrade happened through mixed content
+ // upgrading, CSP upgrade-insecure-requests, HTTPS-Only or HTTPS-First, we
+ // have to recalculate the referrer based on the original referrer to
+ // account for the different scheme. This does NOT apply to HSTS.
+ // See Bug 1857894 and order of https://fetch.spec.whatwg.org/#main-fetch.
+ // Otherwise, if we have a new referrer policy, we want to recalculate the
+ // referrer based on the old computed referrer (Bug 1678545).
+ bool wasNonHSTSUpgrade =
+ (aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) &&
+ (!mLoadInfo->GetHstsStatus());
+ if (wasNonHSTSUpgrade) {
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetOriginalReferrer();
+ config.referrerInfo =
+ new dom::ReferrerInfo(referrer, mReferrerInfo->ReferrerPolicy(),
+ mReferrerInfo->GetSendReferrer());
+ } else if (referrerPolicy != dom::ReferrerPolicy::_empty) {
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
+ config.referrerInfo = new dom::ReferrerInfo(
+ referrer, referrerPolicy, mReferrerInfo->GetSendReferrer());
+ } else {
+ config.referrerInfo = mReferrerInfo;
+ }
+ }
+ }
+
+ nsCOMPtr<nsITimedChannel> oldTimedChannel(
+ do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
+ if (oldTimedChannel) {
+ config.timedChannelInfo = Some(dom::TimedChannelInfo());
+ config.timedChannelInfo->timingEnabled() = LoadTimingEnabled();
+ config.timedChannelInfo->redirectCount() = mRedirectCount;
+ config.timedChannelInfo->internalRedirectCount() = mInternalRedirectCount;
+ config.timedChannelInfo->asyncOpen() = mAsyncOpenTime;
+ config.timedChannelInfo->channelCreation() = mChannelCreationTimestamp;
+ config.timedChannelInfo->redirectStart() = mRedirectStartTimeStamp;
+ config.timedChannelInfo->redirectEnd() = mRedirectEndTimeStamp;
+ config.timedChannelInfo->initiatorType() = mInitiatorType;
+ config.timedChannelInfo->allRedirectsSameOrigin() =
+ LoadAllRedirectsSameOrigin();
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
+ LoadAllRedirectsPassTimingAllowCheck();
+ // Execute the timing allow check to determine whether
+ // to report the redirect timing info
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ // TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
+ // AllRedirectsPassTimingAllowCheck on them.
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+ config.timedChannelInfo->timingAllowCheckForPrincipal() =
+ Some(oldTimedChannel->TimingAllowCheck(principal));
+ }
+
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
+ LoadAllRedirectsPassTimingAllowCheck();
+ config.timedChannelInfo->launchServiceWorkerStart() =
+ mLaunchServiceWorkerStart;
+ config.timedChannelInfo->launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
+ config.timedChannelInfo->dispatchFetchEventStart() =
+ mDispatchFetchEventStart;
+ config.timedChannelInfo->dispatchFetchEventEnd() = mDispatchFetchEventEnd;
+ config.timedChannelInfo->handleFetchEventStart() = mHandleFetchEventStart;
+ config.timedChannelInfo->handleFetchEventEnd() = mHandleFetchEventEnd;
+ config.timedChannelInfo->responseStart() =
+ mTransactionTimings.responseStart;
+ config.timedChannelInfo->responseEnd() = mTransactionTimings.responseEnd;
+ }
+
+ if (aPreserveMethod) {
+ // since preserveMethod is true, we need to ensure that the appropriate
+ // request method gets set on the channel, regardless of whether or not
+ // we set the upload stream above. This means SetRequestMethod() will
+ // be called twice if ExplicitSetUploadStream() gets called above.
+
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ config.method = Some(method);
+
+ if (mUploadStream) {
+ // rewind upload stream
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+ config.uploadStream = mUploadStream;
+ }
+ config.uploadStreamLength = mReqContentLength;
+ config.uploadStreamHasHeaders = LoadUploadStreamHasHeaders();
+
+ nsAutoCString contentType;
+ nsresult rv = mRequestHead.GetHeader(nsHttp::Content_Type, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ config.contentType = Some(contentType);
+ }
+
+ nsAutoCString contentLength;
+ rv = mRequestHead.GetHeader(nsHttp::Content_Length, contentLength);
+ if (NS_SUCCEEDED(rv)) {
+ config.contentLength = Some(contentLength);
+ }
+ }
+
+ return config;
+}
+
+/* static */ void HttpBaseChannel::ConfigureReplacementChannel(
+ nsIChannel* newChannel, const ReplacementChannelConfig& config,
+ ReplacementReason aReason) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
+ if (cos) {
+ cos->SetClassOfService(config.classOfService);
+ }
+
+ // Try to preserve the privacy bit if it has been overridden
+ if (config.privateBrowsing) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(*config.privateBrowsing);
+ }
+ }
+
+ // Transfer the timing data (if we are dealing with an nsITimedChannel).
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ if (config.timedChannelInfo && newTimedChannel) {
+ newTimedChannel->SetTimingEnabled(config.timedChannelInfo->timingEnabled());
+
+ // If we're an internal redirect, or a document channel replacement,
+ // then we shouldn't record any new timing for this and just copy
+ // over the existing values.
+ bool shouldHideTiming = aReason != ReplacementReason::Redirect;
+ if (shouldHideTiming) {
+ newTimedChannel->SetRedirectCount(
+ config.timedChannelInfo->redirectCount());
+ int32_t newCount = config.timedChannelInfo->internalRedirectCount() + 1;
+ newTimedChannel->SetInternalRedirectCount(std::max(
+ newCount, static_cast<int32_t>(
+ config.timedChannelInfo->internalRedirectCount())));
+ } else {
+ int32_t newCount = config.timedChannelInfo->redirectCount() + 1;
+ newTimedChannel->SetRedirectCount(std::max(
+ newCount,
+ static_cast<int32_t>(config.timedChannelInfo->redirectCount())));
+ newTimedChannel->SetInternalRedirectCount(
+ config.timedChannelInfo->internalRedirectCount());
+ }
+
+ if (shouldHideTiming) {
+ if (!config.timedChannelInfo->channelCreation().IsNull()) {
+ newTimedChannel->SetChannelCreation(
+ config.timedChannelInfo->channelCreation());
+ }
+
+ if (!config.timedChannelInfo->asyncOpen().IsNull()) {
+ newTimedChannel->SetAsyncOpen(config.timedChannelInfo->asyncOpen());
+ }
+ }
+
+ // If the RedirectStart is null, we will use the AsyncOpen value of the
+ // previous channel (this is the first redirect in the redirects chain).
+ if (config.timedChannelInfo->redirectStart().IsNull()) {
+ // Only do this for real redirects. Internal redirects should be hidden.
+ if (!shouldHideTiming) {
+ newTimedChannel->SetRedirectStart(config.timedChannelInfo->asyncOpen());
+ }
+ } else {
+ newTimedChannel->SetRedirectStart(
+ config.timedChannelInfo->redirectStart());
+ }
+
+ // For internal redirects just propagate the last redirect end time
+ // forward. Otherwise the new redirect end time is the last response
+ // end time.
+ TimeStamp newRedirectEnd;
+ if (shouldHideTiming) {
+ newRedirectEnd = config.timedChannelInfo->redirectEnd();
+ } else if (!config.timedChannelInfo->responseEnd().IsNull()) {
+ newRedirectEnd = config.timedChannelInfo->responseEnd();
+ } else {
+ newRedirectEnd = TimeStamp::Now();
+ }
+ newTimedChannel->SetRedirectEnd(newRedirectEnd);
+
+ newTimedChannel->SetInitiatorType(config.timedChannelInfo->initiatorType());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannelInfo->allRedirectsSameOrigin());
+
+ if (config.timedChannelInfo->timingAllowCheckForPrincipal()) {
+ newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() &&
+ *config.timedChannelInfo->timingAllowCheckForPrincipal());
+ }
+
+ // Propagate service worker measurements across redirects. The
+ // PeformanceResourceTiming.workerStart API expects to see the
+ // worker start time after a redirect.
+ newTimedChannel->SetLaunchServiceWorkerStart(
+ config.timedChannelInfo->launchServiceWorkerStart());
+ newTimedChannel->SetLaunchServiceWorkerEnd(
+ config.timedChannelInfo->launchServiceWorkerEnd());
+ newTimedChannel->SetDispatchFetchEventStart(
+ config.timedChannelInfo->dispatchFetchEventStart());
+ newTimedChannel->SetDispatchFetchEventEnd(
+ config.timedChannelInfo->dispatchFetchEventEnd());
+ newTimedChannel->SetHandleFetchEventStart(
+ config.timedChannelInfo->handleFetchEventStart());
+ newTimedChannel->SetHandleFetchEventEnd(
+ config.timedChannelInfo->handleFetchEventEnd());
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel) {
+ return; // no other options to set
+ }
+
+ if (config.uploadStream) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
+ if (uploadChannel2 || uploadChannel) {
+ // replicate original call to SetUploadStream...
+ if (uploadChannel2) {
+ const nsACString& ctype =
+ config.contentType ? *config.contentType : VoidCString();
+ // If header is not present mRequestHead.HasHeaderValue will truncated
+ // it. But we want to end up with a void string, not an empty string,
+ // because ExplicitSetUploadStream treats the former as "no header" and
+ // the latter as "header with empty string value".
+
+ const nsACString& method =
+ config.method ? *config.method : VoidCString();
+
+ uploadChannel2->ExplicitSetUploadStream(
+ config.uploadStream, ctype, config.uploadStreamLength, method,
+ config.uploadStreamHasHeaders);
+ } else {
+ if (config.uploadStreamHasHeaders) {
+ uploadChannel->SetUploadStream(config.uploadStream, ""_ns,
+ config.uploadStreamLength);
+ } else {
+ nsAutoCString ctype;
+ if (config.contentType) {
+ ctype = *config.contentType;
+ } else {
+ ctype = "application/octet-stream"_ns;
+ }
+ if (config.contentLength && !config.contentLength->IsEmpty()) {
+ uploadChannel->SetUploadStream(
+ config.uploadStream, ctype,
+ nsCRT::atoll(config.contentLength->get()));
+ }
+ }
+ }
+ }
+ }
+
+ if (config.referrerInfo) {
+ DebugOnly<nsresult> success{};
+ success = httpChannel->SetReferrerInfo(config.referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ }
+
+ if (config.method) {
+ DebugOnly<nsresult> rv = httpChannel->SetRequestMethod(*config.method);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+HttpBaseChannel::ReplacementChannelConfig::ReplacementChannelConfig(
+ const dom::ReplacementChannelConfigInit& aInit) {
+ redirectFlags = aInit.redirectFlags();
+ classOfService = aInit.classOfService();
+ privateBrowsing = aInit.privateBrowsing();
+ method = aInit.method();
+ referrerInfo = aInit.referrerInfo();
+ timedChannelInfo = aInit.timedChannelInfo();
+ uploadStream = aInit.uploadStream();
+ uploadStreamLength = aInit.uploadStreamLength();
+ uploadStreamHasHeaders = aInit.uploadStreamHasHeaders();
+ contentType = aInit.contentType();
+ contentLength = aInit.contentLength();
+}
+
+dom::ReplacementChannelConfigInit
+HttpBaseChannel::ReplacementChannelConfig::Serialize() {
+ dom::ReplacementChannelConfigInit config;
+ config.redirectFlags() = redirectFlags;
+ config.classOfService() = classOfService;
+ config.privateBrowsing() = privateBrowsing;
+ config.method() = method;
+ config.referrerInfo() = referrerInfo;
+ config.timedChannelInfo() = timedChannelInfo;
+ config.uploadStream() =
+ uploadStream ? RemoteLazyInputStream::WrapStream(uploadStream) : nullptr;
+ config.uploadStreamLength() = uploadStreamLength;
+ config.uploadStreamHasHeaders() = uploadStreamHasHeaders;
+ config.contentType() = contentType;
+ config.contentLength() = contentLength;
+
+ return config;
+}
+
+nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI* newURI,
+ nsIChannel* newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags) {
+ nsresult rv;
+
+ LOG(
+ ("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ // Ensure the channel's loadInfo's result principal URI so that it's
+ // either non-null or updated to the redirect target URI.
+ // We must do this because in case the loadInfo's result principal URI
+ // is null, it would be taken from OriginalURI of the channel. But we
+ // overwrite it with the whole redirect chain first URI before opening
+ // the target channel, hence the information would be lost.
+ // If the protocol handler that created the channel wants to use
+ // the originalURI of the channel as the principal URI, this fulfills
+ // that request - newURI is the original URI of the channel.
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ rv = newLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!resultPrincipalURI) {
+ rv = newLoadInfo->SetResultPrincipalURI(newURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsLoadFlags loadFlags = mLoadFlags;
+ loadFlags |= LOAD_REPLACE;
+
+ // if the original channel was using SSL and this channel is not using
+ // SSL, then no need to inhibit persistent caching. however, if the
+ // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
+ // set, then allow the flag to apply to the redirected channel as well.
+ // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
+ // we only need to check if the original channel was using SSL.
+ if (mURI->SchemeIs("https")) {
+ loadFlags &= ~INHIBIT_PERSISTENT_CACHING;
+ }
+
+ newChannel->SetLoadFlags(loadFlags);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+
+ ReplacementReason redirectType =
+ (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)
+ ? ReplacementReason::InternalRedirect
+ : ReplacementReason::Redirect;
+ ReplacementChannelConfig config = CloneReplacementChannelConfig(
+ preserveMethod, redirectFlags, redirectType);
+ ConfigureReplacementChannel(newChannel, config, redirectType);
+
+ // Check whether or not this was a cross-domain redirect.
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ bool sameOriginWithOriginalUri = SameOriginWithOriginalUri(newURI);
+ if (config.timedChannelInfo && newTimedChannel) {
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannelInfo->allRedirectsSameOrigin() &&
+ sameOriginWithOriginalUri);
+ }
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ // TODO: create tests for cross-origin redirect in bug 1662896.
+ if (sameOriginWithOriginalUri) {
+ newChannel->SetContentDisposition(mContentDispositionHint);
+ if (mContentDispositionFilename) {
+ newChannel->SetContentDispositionFilename(*mContentDispositionFilename);
+ }
+ }
+
+ if (!httpChannel) return NS_OK; // no other options to set
+
+ // Preserve the CORS preflight information.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
+ if (httpInternal) {
+ httpInternal->SetLastRedirectFlags(redirectFlags);
+
+ if (LoadRequireCORSPreflight()) {
+ httpInternal->SetCorsPreflightParameters(mUnsafeHeaders, false);
+ }
+ }
+
+ // convey the LoadAllowSTS() flags
+ rv = httpChannel->SetAllowSTS(LoadAllowSTS());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // convey the Accept header value
+ {
+ nsAutoCString oldAcceptValue;
+ nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
+ if (NS_SUCCEEDED(hasHeader)) {
+ rv = httpChannel->SetRequestHeader("Accept"_ns, oldAcceptValue, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // convey the User-Agent header value
+ // since we might be setting custom user agent from DevTools.
+ if (httpInternal && mRequestMode == RequestMode::No_cors &&
+ redirectType == ReplacementReason::Redirect) {
+ nsAutoCString oldUserAgent;
+ nsresult hasHeader =
+ mRequestHead.GetHeader(nsHttp::User_Agent, oldUserAgent);
+ if (NS_SUCCEEDED(hasHeader)) {
+ rv = httpChannel->SetRequestHeader("User-Agent"_ns, oldUserAgent, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // convery the IsUserAgentHeaderModified value.
+ if (httpInternal) {
+ rv = httpInternal->SetIsUserAgentHeaderModified(
+ LoadIsUserAgentHeaderModified());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // share the request context - see bug 1236650
+ rv = httpChannel->SetRequestContextID(mRequestContextID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // When on the parent process, the channel can't attempt to get it itself.
+ // When on the child process, it would be waste to query it again.
+ rv = httpChannel->SetBrowserId(mBrowserId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Not setting this flag would break carrying permissions down to the child
+ // process when the channel is artificially forced to be a main document load.
+ rv = httpChannel->SetIsMainDocumentChannel(LoadForceMainDocumentChannel());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve the loading order
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(newChannel);
+ if (p) {
+ p->SetPriority(mPriority);
+ }
+
+ if (httpInternal) {
+ // Convey third party cookie, conservative, and spdy flags.
+ rv = httpInternal->SetThirdPartyFlags(LoadThirdPartyFlags());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowSpdy(LoadAllowSpdy());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowHttp3(LoadAllowHttp3());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowAltSvc(LoadAllowAltSvc());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetBeConservative(LoadBeConservative());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetIsTRRServiceChannel(LoadIsTRRServiceChannel());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetTlsFlags(mTlsFlags);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Ensure the type of realChannel involves all types it may redirect to.
+ // Such as nsHttpChannel and InterceptedChannel.
+ // Even thought InterceptedChannel itself doesn't require these information,
+ // it may still be necessary for the following redirections.
+ // E.g. nsHttpChannel -> InterceptedChannel -> nsHttpChannel
+ RefPtr<HttpBaseChannel> realChannel;
+ CallQueryInterface(newChannel, realChannel.StartAssignment());
+ if (realChannel) {
+ realChannel->SetTopWindowURI(mTopWindowURI);
+
+ realChannel->StoreTaintedOriginFlag(
+ ShouldTaintReplacementChannelOrigin(newChannel, redirectFlags));
+ }
+
+ // update the DocumentURI indicator since we are being redirected.
+ // if this was a top-level document channel, then the new channel
+ // should have its mDocumentURI point to newURI; otherwise, we
+ // just need to pass along our mDocumentURI to the new channel.
+ if (newURI && (mURI == mDocumentURI)) {
+ rv = httpInternal->SetDocumentURI(newURI);
+ } else {
+ rv = httpInternal->SetDocumentURI(mDocumentURI);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // if there is a chain of keys for redirect-responses we transfer it to
+ // the new channel (see bug #561276)
+ {
+ auto redirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = redirectedCachekeys.ref();
+ if (ref) {
+ LOG(
+ ("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p] transferring chain of redirect cache-keys",
+ this));
+ rv = httpInternal->SetCacheKeysRedirectChain(ref.release());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // Preserve Request mode.
+ rv = httpInternal->SetRequestMode(mRequestMode);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve Redirect mode flag.
+ rv = httpInternal->SetRedirectMode(mRedirectMode);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve Integrity metadata.
+ rv = httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ httpInternal->SetAltDataForChild(LoadAltDataForChild());
+ if (LoadDisableAltDataCache()) {
+ httpInternal->DisableAltDataCache();
+ }
+ }
+
+ // transfer any properties
+ nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
+ if (bag) {
+ for (const auto& entry : mPropertyHash) {
+ bag->SetProperty(entry.GetKey(), entry.GetWeak());
+ }
+ }
+
+ // Pass the preferred alt-data type on to the new channel.
+ nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
+ if (cacheInfoChan) {
+ for (auto& data : mPreferredCachedAltDataTypes) {
+ cacheInfoChan->PreferAlternativeDataType(data.type(), data.contentType(),
+ data.deliverAltData());
+ }
+
+ if (LoadForceValidateCacheContent()) {
+ Unused << cacheInfoChan->SetForceValidateCacheContent(true);
+ }
+ }
+
+ if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ // Copy non-origin related headers to the new channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new AddHeadersToChannelVisitor(httpChannel);
+ rv = mRequestHead.VisitHeaders(visitor);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // we need to strip Authentication headers for cross-origin requests
+ // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
+ nsAutoCString authHeader;
+ if (StaticPrefs::network_http_redirect_stripAuthHeader() &&
+ NS_SUCCEEDED(
+ httpChannel->GetRequestHeader("Authorization"_ns, authHeader))) {
+ if (NS_ShouldRemoveAuthHeaderOnRedirect(static_cast<nsIChannel*>(this),
+ newChannel, redirectFlags)) {
+ rv = httpChannel->SetRequestHeader("Authorization"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ return NS_OK;
+}
+
+// check whether the new channel is of same origin as the current channel
+bool HttpBaseChannel::IsNewChannelSameOrigin(nsIChannel* aNewChannel) {
+ bool isSameOrigin = false;
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+
+ if (!ssm) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+
+ nsresult rv = ssm->CheckSameOriginURI(newURI, mURI, false, false);
+ if (NS_SUCCEEDED(rv)) {
+ isSameOrigin = true;
+ }
+
+ return isSameOrigin;
+}
+
+bool HttpBaseChannel::ShouldTaintReplacementChannelOrigin(
+ nsIChannel* aNewChannel, uint32_t aRedirectFlags) {
+ if (LoadTaintedOriginFlag()) {
+ return true;
+ }
+
+ if (NS_IsInternalSameURIRedirect(this, aNewChannel, aRedirectFlags) ||
+ NS_IsHSTSUpgradeRedirect(this, aNewChannel, aRedirectFlags)) {
+ return false;
+ }
+
+ // If new channel is not of same origin we need to taint unless
+ // mURI <-> mOriginalURI/LoadingPrincipal are same origin.
+ if (IsNewChannelSameOrigin(aNewChannel)) {
+ return false;
+ }
+
+ nsresult rv;
+
+ if (mLoadInfo->GetLoadingPrincipal()) {
+ bool sameOrigin = false;
+ rv = mLoadInfo->GetLoadingPrincipal()->IsSameOrigin(mURI, &sameOrigin);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ return !sameOrigin;
+ }
+ if (!mOriginalURI) {
+ return true;
+ }
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (!ssm) {
+ return true;
+ }
+
+ rv = ssm->CheckSameOriginURI(mOriginalURI, mURI, false, false);
+ return NS_FAILED(rv);
+}
+
+// Redirect Tracking
+bool HttpBaseChannel::SameOriginWithOriginalUri(nsIURI* aURI) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ bool isPrivateWin = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ nsresult rv =
+ ssm->CheckSameOriginURI(aURI, mOriginalURI, false, isPrivateWin);
+ return (NS_SUCCEEDED(rv));
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIClassifiedChannel
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedList(nsACString& aList) {
+ aList = mMatchedList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedProvider(nsACString& aProvider) {
+ aProvider = mMatchedProvider;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedFullHash(nsACString& aFullHash) {
+ aFullHash = mMatchedFullHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ NS_ENSURE_ARG(!aList.IsEmpty());
+
+ mMatchedList = aList;
+ mMatchedProvider = aProvider;
+ mMatchedFullHash = aFullHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingLists(nsTArray<nsCString>& aLists) {
+ aLists = mMatchedTrackingLists.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingFullHashes(
+ nsTArray<nsCString>& aFullHashes) {
+ aFullHashes = mMatchedTrackingFullHashes.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedTrackingInfo(
+ const nsTArray<nsCString>& aLists, const nsTArray<nsCString>& aFullHashes) {
+ NS_ENSURE_ARG(!aLists.IsEmpty());
+ // aFullHashes can be empty for non hash-matching algorithm, for example,
+ // host based test entries in preference.
+
+ mMatchedTrackingLists = aLists.Clone();
+ mMatchedTrackingFullHashes = aFullHashes.Clone();
+ return NS_OK;
+}
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTimingEnabled(bool enabled) {
+ StoreTimingEnabled(enabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTimingEnabled(bool* _retval) {
+ *_retval = LoadTimingEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
+ *_retval = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelCreation(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ TimeDuration adjust = aValue - mChannelCreationTimestamp;
+ mChannelCreationTimestamp = aValue;
+ mChannelCreationTime += (PRTime)adjust.ToMicroseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAsyncOpen(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ mAsyncOpenTime = aValue;
+ StoreAsyncOpenTimeOverriden(true);
+ return NS_OK;
+}
+
+/**
+ * @return the number of redirects. There is no check for cross-domain
+ * redirects. This check must be done by the consumers.
+ */
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectCount(uint8_t* aRedirectCount) {
+ *aRedirectCount = mRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount) {
+ mRedirectCount = aRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInternalRedirectCount(uint8_t* aRedirectCount) {
+ *aRedirectCount = mInternalRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount) {
+ mInternalRedirectCount = aRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectStart(TimeStamp* _retval) {
+ *_retval = mRedirectStartTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectStart(TimeStamp aRedirectStart) {
+ mRedirectStartTimeStamp = aRedirectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectEnd(TimeStamp* _retval) {
+ *_retval = mRedirectEndTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectEnd(TimeStamp aRedirectEnd) {
+ mRedirectEndTimeStamp = aRedirectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsSameOrigin(bool* aAllRedirectsSameOrigin) {
+ *aAllRedirectsSameOrigin = LoadAllRedirectsSameOrigin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
+ StoreAllRedirectsSameOrigin(aAllRedirectsSameOrigin);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool* aPassesCheck) {
+ *aPassesCheck = LoadAllRedirectsPassTimingAllowCheck();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck) {
+ StoreAllRedirectsPassTimingAllowCheck(aPassesCheck);
+ return NS_OK;
+}
+
+// https://fetch.spec.whatwg.org/#tao-check
+NS_IMETHODIMP
+HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> resourcePrincipal;
+ nsresult rv =
+ ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
+ if (NS_FAILED(rv) || !resourcePrincipal || !aOrigin) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool sameOrigin = false;
+ rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
+
+ nsAutoCString serializedOrigin;
+ nsContentSecurityManager::GetSerializedOrigin(aOrigin, resourcePrincipal,
+ serializedOrigin, mLoadInfo);
+
+ // All redirects are same origin
+ if (sameOrigin && (!serializedOrigin.IsEmpty() &&
+ !serializedOrigin.EqualsLiteral("null"))) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString headerValue;
+ rv = GetResponseHeader("Timing-Allow-Origin"_ns, headerValue);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ Tokenizer p(headerValue);
+ Tokenizer::Token t;
+
+ p.Record();
+ nsAutoCString headerItem;
+ while (p.Next(t)) {
+ if (t.Type() == Tokenizer::TOKEN_EOF ||
+ t.Equals(Tokenizer::Token::Char(','))) {
+ p.Claim(headerItem);
+ nsHttp::TrimHTTPWhitespace(headerItem, headerItem);
+ // If the list item contains a case-sensitive match for the value of the
+ // origin, or a wildcard, return pass
+ if (headerItem == serializedOrigin || headerItem == "*") {
+ *_retval = true;
+ return NS_OK;
+ }
+ // We start recording again for the following items in the list
+ p.Record();
+ }
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mLaunchServiceWorkerStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
+ mLaunchServiceWorkerStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mLaunchServiceWorkerEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
+ mLaunchServiceWorkerEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mDispatchFetchEventStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) {
+ mDispatchFetchEventStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mDispatchFetchEventEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) {
+ mDispatchFetchEventEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mHandleFetchEventStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) {
+ mHandleFetchEventStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mHandleFetchEventEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) {
+ mHandleFetchEventEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTcpConnectEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.tcpConnectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.secureConnectionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.requestStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadStart(TimeStamp* _retval) {
+ *_retval = mCacheReadStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadEnd(TimeStamp* _retval) {
+ *_retval = mCacheReadEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransactionPending(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.transactionPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitiatorType(nsAString& aInitiatorType) {
+ aInitiatorType = mInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitiatorType(const nsAString& aInitiatorType) {
+ mInitiatorType = aInitiatorType;
+ return NS_OK;
+}
+
+#define IMPL_TIMING_ATTR(name) \
+ NS_IMETHODIMP \
+ HttpBaseChannel::Get##name##Time(PRTime* _retval) { \
+ TimeStamp stamp; \
+ Get##name(&stamp); \
+ if (stamp.IsNull()) { \
+ *_retval = 0; \
+ return NS_OK; \
+ } \
+ *_retval = \
+ mChannelCreationTime + \
+ (PRTime)((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
+ return NS_OK; \
+ }
+
+IMPL_TIMING_ATTR(ChannelCreation)
+IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
+IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
+IMPL_TIMING_ATTR(DispatchFetchEventStart)
+IMPL_TIMING_ATTR(DispatchFetchEventEnd)
+IMPL_TIMING_ATTR(HandleFetchEventStart)
+IMPL_TIMING_ATTR(HandleFetchEventEnd)
+IMPL_TIMING_ATTR(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(TcpConnectEnd)
+IMPL_TIMING_ATTR(SecureConnectionStart)
+IMPL_TIMING_ATTR(ConnectEnd)
+IMPL_TIMING_ATTR(RequestStart)
+IMPL_TIMING_ATTR(ResponseStart)
+IMPL_TIMING_ATTR(ResponseEnd)
+IMPL_TIMING_ATTR(CacheReadStart)
+IMPL_TIMING_ATTR(CacheReadEnd)
+IMPL_TIMING_ATTR(RedirectStart)
+IMPL_TIMING_ATTR(RedirectEnd)
+IMPL_TIMING_ATTR(TransactionPending)
+
+#undef IMPL_TIMING_ATTR
+
+void HttpBaseChannel::MaybeReportTimingData() {
+ // If performance timing is disabled, there is no need for the Performance
+ // object anymore.
+ if (!LoadTimingEnabled()) {
+ return;
+ }
+
+ // There is no point in continuing, since the performance object in the parent
+ // isn't the same as the one in the child which will be reporting resource
+ // performance.
+ if (XRE_IsE10sParentProcess()) {
+ return;
+ }
+
+ // Devtools can create fetch requests on behalf the content document.
+ // If we don't exclude these requests, they'd also be reported
+ // to the content document.
+ bool isInDevToolsContext;
+ mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
+ if (isInDevToolsContext) {
+ return;
+ }
+
+ mozilla::dom::PerformanceStorage* documentPerformance =
+ mLoadInfo->GetPerformanceStorage();
+ if (documentPerformance) {
+ documentPerformance->AddEntry(this, this);
+ return;
+ }
+
+ if (!nsGlobalWindowInner::GetInnerWindowWithId(
+ mLoadInfo->GetInnerWindowID())) {
+ // The inner window is in a different process.
+ dom::ContentChild* child = dom::ContentChild::GetSingleton();
+
+ if (!child) {
+ return;
+ }
+ nsAutoString initiatorType;
+ nsAutoString entryName;
+
+ UniquePtr<dom::PerformanceTimingData> performanceTimingData(
+ dom::PerformanceTimingData::Create(this, this, 0, initiatorType,
+ entryName));
+ if (!performanceTimingData) {
+ return;
+ }
+
+ LoadInfoArgs loadInfoArgs;
+ mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ child->SendReportFrameTimingData(loadInfoArgs, entryName, initiatorType,
+ std::move(performanceTimingData));
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReportResourceTiming(bool enabled) {
+ StoreReportTiming(enabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReportResourceTiming(bool* _retval) {
+ *_retval = LoadReportTiming();
+ return NS_OK;
+}
+
+nsIURI* HttpBaseChannel::GetReferringPage() {
+ nsCOMPtr<nsPIDOMWindowInner> pDomWindow = GetInnerDOMWindow();
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ return pDomWindow->GetDocumentURI();
+}
+
+nsPIDOMWindowInner* HttpBaseChannel::GetInnerDOMWindow() {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ if (!loadContext) {
+ return nullptr;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return nullptr;
+ }
+ auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow =
+ pDomWindow->GetCurrentInnerWindow();
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ return innerWindow;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIThrottledInputChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mThrottleQueue = aQueue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue) {
+ NS_ENSURE_ARG_POINTER(aQueue);
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue = mThrottleQueue;
+ queue.forget(aQueue);
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+
+bool HttpBaseChannel::EnsureRequestContextID() {
+ if (mRequestContextID) {
+ // Already have a request context ID, no need to do the rest of this work
+ LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
+ mRequestContextID));
+ return true;
+ }
+
+ // Find the loadgroup at the end of the chain in order
+ // to make sure all channels derived from the load group
+ // use the same connection scope.
+ nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
+ if (!childLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadGroup> rootLoadGroup;
+ childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
+ if (!rootLoadGroup) {
+ return false;
+ }
+
+ // Set the load group connection scope on this channel and its transaction
+ rootLoadGroup->GetRequestContextID(&mRequestContextID);
+
+ LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
+ mRequestContextID));
+
+ return true;
+}
+
+bool HttpBaseChannel::EnsureRequestContext() {
+ if (mRequestContext) {
+ // Already have a request context, no need to do the rest of this work
+ return true;
+ }
+
+ if (!EnsureRequestContextID()) {
+ return false;
+ }
+
+ nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+ if (!rcsvc) {
+ return false;
+ }
+
+ rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(mRequestContext));
+ return static_cast<bool>(mRequestContext);
+}
+
+void HttpBaseChannel::EnsureBrowserId() {
+ if (mBrowserId) {
+ return;
+ }
+
+ RefPtr<dom::BrowsingContext> bc;
+ MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
+
+ if (bc) {
+ mBrowserId = bc->GetBrowserId();
+ }
+}
+
+void HttpBaseChannel::SetCorsPreflightParameters(
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {
+ MOZ_RELEASE_ASSERT(!LoadRequestObserversCalled());
+
+ StoreRequireCORSPreflight(true);
+ mUnsafeHeaders = aUnsafeHeaders.Clone();
+ if (aShouldStripRequestBodyHeader) {
+ mUnsafeHeaders.RemoveElementsBy([&](const nsCString& aHeader) {
+ return aHeader.LowerCaseEqualsASCII("content-type") ||
+ aHeader.LowerCaseEqualsASCII("content-encoding") ||
+ aHeader.LowerCaseEqualsASCII("content-language") ||
+ aHeader.LowerCaseEqualsASCII("content-location");
+ });
+ }
+}
+
+void HttpBaseChannel::SetAltDataForChild(bool aIsForChild) {
+ StoreAltDataForChild(aIsForChild);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBlockAuthPrompt(bool* aValue) {
+ if (!aValue) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aValue = LoadBlockAuthPrompt();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBlockAuthPrompt(bool aValue) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ StoreBlockAuthPrompt(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) {
+ if (!mConnectionInfo) {
+ return NS_ERROR_FAILURE;
+ }
+ aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastRedirectFlags(uint32_t* aValue) {
+ NS_ENSURE_ARG(aValue);
+ *aValue = mLastRedirectFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLastRedirectFlags(uint32_t aValue) {
+ mLastRedirectFlags = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ // for internal redirect due to auth retry we do not have any limit
+ // as we might restrict the number of times a user might retry
+ // authentication
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY) {
+ return NS_OK;
+ }
+ // Some platform features, like Service Workers, depend on internal
+ // redirects. We should allow some number of internal redirects above
+ // and beyond the normal redirect limit so these features continue
+ // to work.
+ static const int8_t kMinInternalRedirects = 5;
+
+ if (mInternalRedirectCount >= (mRedirectionLimit + kMinInternalRedirects)) {
+ LOG(("internal redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aRedirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ if (mRedirectCount >= mRedirectionLimit) {
+ LOG(("redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ // in case https-only mode is enabled which upgrades top-level requests to
+ // https and the page answers with a redirect (meta, 302, win.location, ...)
+ // then this method can break the cycle which causes the https-only exception
+ // page to appear. Note that https-first mode breaks upgrade downgrade endless
+ // loops within ShouldUpgradeHTTPSFirstRequest because https-first does not
+ // display an exception page but needs a soft fallback/downgrade.
+ if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
+ mURI, mLoadInfo,
+ {nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
+ EnforceForHTTPSOnlyMode})) {
+ LOG(("upgrade downgrade redirect loop!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: This function duplicates code from nsBaseChannel. This will go away
+// once HTTP uses nsBaseChannel (part of bug 312760)
+/* static */
+void HttpBaseChannel::CallTypeSniffers(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsIChannel* chan = static_cast<nsIChannel*>(aClosure);
+ const char* snifferType = [chan]() {
+ if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(chan)) {
+ switch (httpChannel->GetSnifferCategoryType()) {
+ case SnifferCategoryType::NetContent:
+ return NS_CONTENT_SNIFFER_CATEGORY;
+ case SnifferCategoryType::OpaqueResponseBlocking:
+ return NS_ORB_SNIFFER_CATEGORY;
+ case SnifferCategoryType::All:
+ return NS_CONTENT_AND_ORB_SNIFFER_CATEGORY;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected SnifferCategoryType!");
+ }
+ }
+
+ return NS_CONTENT_SNIFFER_CATEGORY;
+ }();
+
+ nsAutoCString newType;
+ NS_SniffContent(snifferType, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+template <class T>
+static void ParseServerTimingHeader(
+ const UniquePtr<T>& aHeader, nsTArray<nsCOMPtr<nsIServerTiming>>& aOutput) {
+ if (!aHeader) {
+ return;
+ }
+
+ nsAutoCString serverTimingHeader;
+ Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader);
+ if (serverTimingHeader.IsEmpty()) {
+ return;
+ }
+
+ ServerTimingParser parser(serverTimingHeader);
+ parser.Parse();
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> array = parser.TakeServerTimingHeaders();
+ aOutput.AppendElements(array);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetServerTiming(nsIArray** aServerTiming) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aServerTiming);
+
+ nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> data;
+ rv = GetNativeServerTiming(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (const auto& entry : data) {
+ array->AppendElement(entry);
+ }
+
+ array.forget(aServerTiming);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNativeServerTiming(
+ nsTArray<nsCOMPtr<nsIServerTiming>>& aServerTiming) {
+ aServerTiming.Clear();
+
+ if (nsContentUtils::ComputeIsSecureContext(this)) {
+ ParseServerTimingHeader(mResponseHead, aServerTiming);
+ ParseServerTimingHeader(mResponseTrailers, aServerTiming);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::CancelByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ return Cancel(aErrorCode);
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetIPv4Disabled() {
+ mCaps |= NS_HTTP_DISABLE_IPV4;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetIPv6Disabled() {
+ mCaps |= NS_HTTP_DISABLE_IPV6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy(
+ bool aIsOriginTrialCoepCredentiallessEnabled,
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ *aOutPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL;
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!nsContentUtils::ComputeIsSecureContext(this)) {
+ // Feature is only available for secure contexts.
+ return NS_OK;
+ }
+
+ nsAutoCString content;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Embedder_Policy,
+ content);
+ *aOutPolicy = NS_GetCrossOriginEmbedderPolicyFromHeader(
+ content, aIsOriginTrialCoepCredentiallessEnabled);
+ return NS_OK;
+}
+
+// Obtain a cross-origin opener-policy from a response response and a
+// cross-origin opener policy initiator.
+// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+NS_IMETHODIMP HttpBaseChannel::ComputeCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
+ nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) {
+ MOZ_ASSERT(aOutPolicy);
+ *aOutPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // COOP headers are ignored for insecure-context loads.
+ if (!nsContentUtils::ComputeIsSecureContext(this)) {
+ return NS_OK;
+ }
+
+ nsAutoCString openerPolicy;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy,
+ openerPolicy);
+
+ // Cross-Origin-Opener-Policy = %s"same-origin" /
+ // %s"same-origin-allow-popups" /
+ // %s"unsafe-none"; case-sensitive
+
+ nsCOMPtr<nsISFVService> sfv = GetSFVService();
+
+ nsCOMPtr<nsISFVItem> item;
+ nsresult rv = sfv->ParseItem(openerPolicy, getter_AddRefs(item));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISFVBareItem> value;
+ rv = item->GetValue(getter_AddRefs(value));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISFVToken> token = do_QueryInterface(value);
+ if (!token) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = token->GetValue(openerPolicy);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsILoadInfo::CrossOriginOpenerPolicy policy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+
+ if (openerPolicy.EqualsLiteral("same-origin")) {
+ policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN;
+ } else if (openerPolicy.EqualsLiteral("same-origin-allow-popups")) {
+ policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS;
+ }
+ if (policy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) {
+ nsILoadInfo::CrossOriginEmbedderPolicy coep =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+ bool isCoepCredentiallessEnabled;
+ rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ &isCoepCredentiallessEnabled);
+ if (!isCoepCredentiallessEnabled) {
+ nsAutoCString originTrialToken;
+ Unused << mResponseHead->GetHeader(nsHttp::OriginTrial, originTrialToken);
+ if (!originTrialToken.IsEmpty()) {
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultPrincipal));
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ OriginTrials trials;
+ trials.UpdateFromToken(NS_ConvertASCIItoUTF16(originTrialToken),
+ resultPrincipal);
+ if (trials.IsEnabled(OriginTrial::CoepCredentialless)) {
+ isCoepCredentiallessEnabled = true;
+ }
+ }
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_SUCCEEDED(
+ GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &coep)) &&
+ (coep == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP ||
+ coep == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS)) {
+ policy =
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ }
+ }
+
+ *aOutPolicy = policy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
+ MOZ_ASSERT(aPolicy);
+ if (!aPolicy) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ // If this method is called before OnStartRequest (ie. before we call
+ // ComputeCrossOriginOpenerPolicy) or if we were unable to compute the
+ // policy we'll throw an error.
+ if (!LoadOnStartRequestCalled()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aPolicy = mComputedCrossOriginOpenerPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch(bool* aIsMismatch) {
+ // This should only be called in parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ *aIsMismatch = LoadHasCrossOriginOpenerPolicyMismatch();
+ return NS_OK;
+}
+
+void HttpBaseChannel::MaybeFlushConsoleReports() {
+ // Flush if we have a known window ID.
+ if (mLoadInfo->GetInnerWindowID() > 0) {
+ FlushReportsToConsole(mLoadInfo->GetInnerWindowID());
+ return;
+ }
+
+ // If this channel is part of a loadGroup, we can flush the console reports
+ // immediately.
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_SUCCEEDED(rv) && loadGroup) {
+ FlushConsoleReports(loadGroup);
+ }
+}
+
+void HttpBaseChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {}
+
+NS_IMETHODIMP HttpBaseChannel::SetWaitForHTTPSSVCRecord() {
+ mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR;
+ return NS_OK;
+}
+
+bool HttpBaseChannel::Http3Allowed() const {
+ bool isDirectOrNoProxy =
+ mProxyInfo ? static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()
+ : true;
+ return !mUpgradeProtocolCallback && isDirectOrNoProxy &&
+ !(mCaps & NS_HTTP_BE_CONSERVATIVE) && !LoadBeConservative() &&
+ LoadAllowHttp3();
+}
+
+void HttpBaseChannel::SetDummyChannelForImageCache() {
+ mDummyChannelForImageCache = true;
+ MOZ_ASSERT(!mResponseHead,
+ "SetDummyChannelForImageCache should only be called once");
+ mResponseHead = MakeUnique<nsHttpResponseHead>();
+}
+
+void HttpBaseChannel::SetEarlyHints(
+ nsTArray<EarlyHintConnectArgs>&& aEarlyHints) {
+ mEarlyHints = std::move(aEarlyHints);
+}
+
+nsTArray<EarlyHintConnectArgs>&& HttpBaseChannel::TakeEarlyHints() {
+ return std::move(mEarlyHints);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetEarlyHintPreloaderId(uint64_t aEarlyHintPreloaderId) {
+ mEarlyHintPreloaderId = aEarlyHintPreloaderId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEarlyHintPreloaderId(uint64_t* aEarlyHintPreloaderId) {
+ NS_ENSURE_ARG_POINTER(aEarlyHintPreloaderId);
+ *aEarlyHintPreloaderId = mEarlyHintPreloaderId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ mClassicScriptHintCharset = aClassicScriptHintCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ aClassicScriptHintCharset = mClassicScriptHintCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ mDocumentCharacterSet = aDocumentCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetDocumentCharacterSet(
+ nsAString& aDocumentCharacterSet) {
+ aDocumentCharacterSet = mDocumentCharacterSet;
+ return NS_OK;
+}
+
+void HttpBaseChannel::SetConnectionInfo(nsHttpConnectionInfo* aCI) {
+ mConnectionInfo = aCI ? aCI->Clone() : nullptr;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsProxyUsed(bool* aIsProxyUsed) {
+ if (mProxyInfo) {
+ if (!static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()) {
+ StoreIsProxyUsed(true);
+ }
+ }
+ *aIsProxyUsed = LoadIsProxyUsed();
+ return NS_OK;
+}
+
+static void CollectORBBlockTelemetry(
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
+ ExtContentPolicy aPolicy) {
+ Telemetry::LABELS_ORB_BLOCK_REASON label{
+ static_cast<uint32_t>(aTelemetryReason)};
+ Telemetry::AccumulateCategorical(label);
+
+ switch (aPolicy) {
+ case ExtContentPolicy::TYPE_INVALID:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::INVALID);
+ break;
+ case ExtContentPolicy::TYPE_OTHER:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::OTHER);
+ break;
+ case ExtContentPolicy::TYPE_FETCH:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::BLOCKED_FETCH);
+ break;
+ case ExtContentPolicy::TYPE_SCRIPT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::SCRIPT);
+ break;
+ case ExtContentPolicy::TYPE_IMAGE:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::IMAGE);
+ break;
+ case ExtContentPolicy::TYPE_STYLESHEET:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::STYLESHEET);
+ break;
+ case ExtContentPolicy::TYPE_XMLHTTPREQUEST:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::XMLHTTPREQUEST);
+ break;
+ case ExtContentPolicy::TYPE_DTD:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::DTD);
+ break;
+ case ExtContentPolicy::TYPE_FONT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::FONT);
+ break;
+ case ExtContentPolicy::TYPE_MEDIA:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::MEDIA);
+ break;
+ case ExtContentPolicy::TYPE_CSP_REPORT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::CSP_REPORT);
+ break;
+ case ExtContentPolicy::TYPE_XSLT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::XSLT);
+ break;
+ case ExtContentPolicy::TYPE_IMAGESET:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::IMAGESET);
+ break;
+ case ExtContentPolicy::TYPE_WEB_MANIFEST:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::WEB_MANIFEST);
+ break;
+ case ExtContentPolicy::TYPE_SPECULATIVE:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::SPECULATIVE);
+ break;
+ case ExtContentPolicy::TYPE_UA_FONT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::UA_FONT);
+ break;
+ case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::PROXIED_WEBRTC_MEDIA);
+ break;
+ case ExtContentPolicy::TYPE_PING:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::PING);
+ break;
+ case ExtContentPolicy::TYPE_BEACON:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::BEACON);
+ break;
+ case ExtContentPolicy::TYPE_WEB_TRANSPORT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::WEB_TRANSPORT);
+ break;
+ case ExtContentPolicy::TYPE_WEB_IDENTITY:
+ // Don't bother extending the telemetry for this.
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::OTHER);
+ break;
+ case ExtContentPolicy::TYPE_DOCUMENT:
+ case ExtContentPolicy::TYPE_SUBDOCUMENT:
+ case ExtContentPolicy::TYPE_OBJECT:
+ case ExtContentPolicy::TYPE_OBJECT_SUBREQUEST:
+ case ExtContentPolicy::TYPE_WEBSOCKET:
+ case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD:
+ MOZ_ASSERT_UNREACHABLE("Shouldn't block this type");
+ // DOCUMENT, SUBDOCUMENT, OBJECT, OBJECT_SUBREQUEST,
+ // WEBSOCKET and SAVEAS_DOWNLOAD are excluded from ORB
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::EXCLUDED);
+ break;
+ // Do not add default: so that compilers can catch the missing case.
+ }
+}
+
+void HttpBaseChannel::LogORBError(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
+ auto policy = mLoadInfo->GetExternalContentPolicyType();
+ CollectORBBlockTelemetry(aTelemetryReason, policy);
+
+ // Blocking `ExtContentPolicy::TYPE_BEACON` isn't web observable, so keep
+ // quiet in the console about blocking it.
+ if (policy == ExtContentPolicy::TYPE_BEACON) {
+ return;
+ }
+
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ nsAutoCString uri;
+ nsresult rv = nsContentUtils::AnonymizeURI(mURI, uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ uint64_t contentWindowId;
+ GetTopLevelContentWindowId(&contentWindowId);
+ if (contentWindowId) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ u"A resource is blocked by OpaqueResponseBlocking, please check browser console for details."_ns,
+ nsIScriptError::warningFlag, "ORB"_ns, contentWindowId, mURI);
+ }
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(uri));
+ params.AppendElement(aReason);
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "ORB"_ns, doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "ResourceBlockedORB", params);
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetEarlyHintLinkType(
+ uint32_t aEarlyHintLinkType) {
+ mEarlyHintLinkType = aEarlyHintLinkType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetEarlyHintLinkType(
+ uint32_t* aEarlyHintLinkType) {
+ *aEarlyHintLinkType = mEarlyHintLinkType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHasContentDecompressed(bool aValue) {
+ LOG(("HttpBaseChannel::SetHasContentDecompressed [this=%p value=%d]\n", this,
+ aValue));
+ mHasContentDecompressed = aValue;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpBaseChannel::GetHasContentDecompressed(bool* value) {
+ *value = mHasContentDecompressed;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
new file mode 100644
index 0000000000..f3678bb0c9
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -0,0 +1,1194 @@
+/* -*- 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_HttpBaseChannel_h
+#define mozilla_net_HttpBaseChannel_h
+
+#include <utility>
+
+#include "OpaqueResponseUtils.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PrivateBrowsingChannel.h"
+#include "nsCOMPtr.h"
+#include "nsHashPropertyBag.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIClassOfService.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIEncodedChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsILoadInfo.h"
+#include "nsIResumableChannel.h"
+#include "nsIStringEnumerator.h"
+#include "nsISupportsPriority.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITimedChannel.h"
+#include "nsITraceableChannel.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURI.h"
+#include "nsIUploadChannel2.h"
+#include "nsStringEnumerator.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#define HTTP_BASE_CHANNEL_IID \
+ { \
+ 0x9d5cde03, 0xe6e9, 0x4612, { \
+ 0xbf, 0xef, 0xbb, 0x66, 0xf3, 0xbb, 0x74, 0x46 \
+ } \
+ }
+
+class nsIProgressEventSink;
+class nsISecurityConsoleMessage;
+class nsIPrincipal;
+
+namespace mozilla {
+
+namespace dom {
+class PerformanceStorage;
+class ContentParent;
+} // namespace dom
+
+class LogCollector;
+
+namespace net {
+extern mozilla::LazyLogModule gHttpLog;
+
+class OpaqueResponseBlocker;
+class PreferredAlternativeDataTypeParams;
+
+enum CacheDisposition : uint8_t {
+ kCacheUnresolved = 0,
+ kCacheHit = 1,
+ kCacheHitViaReval = 2,
+ kCacheMissedViaReval = 3,
+ kCacheMissed = 4,
+ kCacheUnknown = 5
+};
+
+// These need to be kept in sync with
+// "browser.opaqueResponseBlocking.filterFetchResponse"
+enum class OpaqueResponseFilterFetch { Never, AllowedByORB, BlockedByORB, All };
+
+/*
+ * This class is a partial implementation of nsIHttpChannel. It contains code
+ * shared by nsHttpChannel and HttpChannelChild.
+ * - Note that this class has nothing to do with nsBaseChannel, which is an
+ * earlier effort at a base class for channels that somehow never made it all
+ * the way to the HTTP channel.
+ */
+class HttpBaseChannel : public nsHashPropertyBag,
+ public nsIEncodedChannel,
+ public nsIHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsIFormPOSTActionChannel,
+ public nsIUploadChannel2,
+ public nsISupportsPriority,
+ public nsIClassOfService,
+ public nsIResumableChannel,
+ public nsITraceableChannel,
+ public PrivateBrowsingChannel<HttpBaseChannel>,
+ public nsITimedChannel,
+ public nsIForcePendingChannel,
+ public nsIConsoleReportCollector,
+ public nsIThrottledInputChannel,
+ public nsIClassifiedChannel {
+ protected:
+ virtual ~HttpBaseChannel();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIFORMPOSTACTIONCHANNEL
+ NS_DECL_NSIUPLOADCHANNEL2
+ NS_DECL_NSITRACEABLECHANNEL
+ NS_DECL_NSITIMEDCHANNEL
+ NS_DECL_NSITHROTTLEDINPUTCHANNEL
+ NS_DECL_NSICLASSIFIEDCHANNEL
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_BASE_CHANNEL_IID)
+
+ HttpBaseChannel();
+
+ [[nodiscard]] virtual nsresult Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI* aProxyURI, uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo);
+
+ // nsIRequest
+ NS_IMETHOD GetName(nsACString& aName) override;
+ NS_IMETHOD IsPending(bool* aIsPending) override;
+ NS_IMETHOD GetStatus(nsresult* aStatus) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ NS_IMETHOD GetLoadFlags(nsLoadFlags* aLoadFlags) override;
+ NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) override;
+ NS_IMETHOD GetTRRMode(nsIRequest::TRRMode* aTRRMode) override;
+ NS_IMETHOD SetTRRMode(nsIRequest::TRRMode aTRRMode) override;
+ NS_IMETHOD SetDocshellUserAgentOverride();
+
+ // nsIChannel
+ NS_IMETHOD GetOriginalURI(nsIURI** aOriginalURI) override;
+ NS_IMETHOD SetOriginalURI(nsIURI* aOriginalURI) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetOwner(nsISupports** aOwner) override;
+ NS_IMETHOD SetOwner(nsISupports* aOwner) override;
+ NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override;
+ NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override;
+ NS_IMETHOD GetIsDocument(bool* aIsDocument) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) override;
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aCallbacks) override;
+ NS_IMETHOD GetContentType(nsACString& aContentType) override;
+ NS_IMETHOD SetContentType(const nsACString& aContentType) override;
+ NS_IMETHOD GetContentCharset(nsACString& aContentCharset) override;
+ NS_IMETHOD SetContentCharset(const nsACString& aContentCharset) override;
+ NS_IMETHOD GetContentDisposition(uint32_t* aContentDisposition) override;
+ NS_IMETHOD SetContentDisposition(uint32_t aContentDisposition) override;
+ NS_IMETHOD GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) override;
+ NS_IMETHOD SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) override;
+ NS_IMETHOD GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) override;
+ NS_IMETHOD GetContentLength(int64_t* aContentLength) override;
+ NS_IMETHOD SetContentLength(int64_t aContentLength) override;
+ NS_IMETHOD Open(nsIInputStream** aResult) override;
+ NS_IMETHOD GetBlockAuthPrompt(bool* aValue) override;
+ NS_IMETHOD SetBlockAuthPrompt(bool aValue) override;
+ NS_IMETHOD GetCanceled(bool* aCanceled) override;
+
+ // nsIEncodedChannel
+ NS_IMETHOD GetApplyConversion(bool* value) override;
+ NS_IMETHOD SetApplyConversion(bool value) override;
+ NS_IMETHOD GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) override;
+ NS_IMETHOD DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener,
+ nsISupports* aCtxt) override;
+ NS_IMETHOD SetHasContentDecompressed(bool value) override;
+ NS_IMETHOD GetHasContentDecompressed(bool* value) override;
+
+ // HttpBaseChannel::nsIHttpChannel
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+ NS_IMETHOD SetRequestMethod(const nsACString& aMethod) override;
+ NS_IMETHOD GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) override;
+ NS_IMETHOD SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) override;
+ NS_IMETHOD SetReferrerInfoWithoutClone(
+ nsIReferrerInfo* aReferrerInfo) override;
+ NS_IMETHOD GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue) override;
+ NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) override;
+ NS_IMETHOD SetNewReferrerInfo(const nsACString& aUrl,
+ nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) override;
+ NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
+ NS_IMETHOD VisitRequestHeaders(nsIHttpHeaderVisitor* visitor) override;
+ NS_IMETHOD VisitNonDefaultRequestHeaders(
+ nsIHttpHeaderVisitor* visitor) override;
+ NS_IMETHOD ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) override;
+ NS_IMETHOD GetResponseHeader(const nsACString& header,
+ nsACString& value) override;
+ NS_IMETHOD SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) override;
+ NS_IMETHOD VisitResponseHeaders(nsIHttpHeaderVisitor* visitor) override;
+ NS_IMETHOD GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) override;
+ NS_IMETHOD VisitOriginalResponseHeaders(
+ nsIHttpHeaderVisitor* aVisitor) override;
+ NS_IMETHOD GetAllowSTS(bool* value) override;
+ NS_IMETHOD SetAllowSTS(bool value) override;
+ NS_IMETHOD GetRedirectionLimit(uint32_t* value) override;
+ NS_IMETHOD SetRedirectionLimit(uint32_t value) override;
+ NS_IMETHOD IsNoStoreResponse(bool* value) override;
+ NS_IMETHOD IsNoCacheResponse(bool* value) override;
+ NS_IMETHOD IsPrivateResponse(bool* value) override;
+ NS_IMETHOD GetResponseStatus(uint32_t* aValue) override;
+ NS_IMETHOD GetResponseStatusText(nsACString& aValue) override;
+ NS_IMETHOD GetRequestSucceeded(bool* aValue) override;
+ NS_IMETHOD RedirectTo(nsIURI* newURI) override;
+ NS_IMETHOD UpgradeToSecure() override;
+ NS_IMETHOD GetRequestContextID(uint64_t* aRCID) override;
+ NS_IMETHOD GetTransferSize(uint64_t* aTransferSize) override;
+ NS_IMETHOD GetRequestSize(uint64_t* aRequestSize) override;
+ NS_IMETHOD GetDecodedBodySize(uint64_t* aDecodedBodySize) override;
+ NS_IMETHOD GetEncodedBodySize(uint64_t* aEncodedBodySize) override;
+ NS_IMETHOD GetSupportsHTTP3(bool* aSupportsHTTP3) override;
+ NS_IMETHOD GetHasHTTPSRR(bool* aHasHTTPSRR) override;
+ NS_IMETHOD SetRequestContextID(uint64_t aRCID) override;
+ NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
+ NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
+ NS_IMETHOD GetProtocolVersion(nsACString& aProtocolVersion) override;
+ NS_IMETHOD GetChannelId(uint64_t* aChannelId) override;
+ NS_IMETHOD SetChannelId(uint64_t aChannelId) override;
+ NS_IMETHOD GetTopLevelContentWindowId(uint64_t* aContentWindowId) override;
+ NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
+ NS_IMETHOD GetBrowserId(uint64_t* aId) override;
+ NS_IMETHOD SetBrowserId(uint64_t aId) override;
+ NS_IMETHOD GetIsProxyUsed(bool* aIsProxyUsed) override;
+
+ using nsIClassifiedChannel::IsThirdPartyTrackingResource;
+
+ virtual void SetSource(UniquePtr<ProfileChunkedBuffer> aSource) override {
+ mSource = std::move(aSource);
+ }
+
+ // nsIHttpChannelInternal
+ NS_IMETHOD GetDocumentURI(nsIURI** aDocumentURI) override;
+ NS_IMETHOD SetDocumentURI(nsIURI* aDocumentURI) override;
+ NS_IMETHOD GetRequestVersion(uint32_t* major, uint32_t* minor) override;
+ NS_IMETHOD GetResponseVersion(uint32_t* major, uint32_t* minor) override;
+ NS_IMETHOD SetCookie(const nsACString& aCookieHeader) override;
+ NS_IMETHOD GetThirdPartyFlags(uint32_t* aForce) override;
+ NS_IMETHOD SetThirdPartyFlags(uint32_t aForce) override;
+ NS_IMETHOD GetForceAllowThirdPartyCookie(bool* aForce) override;
+ NS_IMETHOD SetForceAllowThirdPartyCookie(bool aForce) override;
+ NS_IMETHOD GetChannelIsForDownload(bool* aChannelIsForDownload) override;
+ NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;
+ NS_IMETHOD SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) override;
+ NS_IMETHOD GetLocalAddress(nsACString& addr) override;
+ NS_IMETHOD GetLocalPort(int32_t* port) override;
+ NS_IMETHOD GetRemoteAddress(nsACString& addr) override;
+ NS_IMETHOD GetRemotePort(int32_t* port) override;
+ NS_IMETHOD GetOnlyConnect(bool* aOnlyConnect) override;
+ NS_IMETHOD SetConnectOnly() override;
+ NS_IMETHOD GetAllowSpdy(bool* aAllowSpdy) override;
+ NS_IMETHOD SetAllowSpdy(bool aAllowSpdy) override;
+ NS_IMETHOD GetAllowHttp3(bool* aAllowHttp3) override;
+ NS_IMETHOD SetAllowHttp3(bool aAllowHttp3) override;
+ NS_IMETHOD GetAllowAltSvc(bool* aAllowAltSvc) override;
+ NS_IMETHOD SetAllowAltSvc(bool aAllowAltSvc) override;
+ NS_IMETHOD GetBeConservative(bool* aBeConservative) override;
+ NS_IMETHOD SetBeConservative(bool aBeConservative) override;
+ NS_IMETHOD GetBypassProxy(bool* aBypassProxy) override;
+ NS_IMETHOD SetBypassProxy(bool aBypassProxy) override;
+ bool BypassProxy();
+
+ NS_IMETHOD GetIsTRRServiceChannel(bool* aTRR) override;
+ NS_IMETHOD SetIsTRRServiceChannel(bool aTRR) override;
+ NS_IMETHOD GetIsResolvedByTRR(bool* aResolvedByTRR) override;
+ NS_IMETHOD GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) override;
+ NS_IMETHOD GetTrrSkipReason(nsITRRSkipReason::value* aTrrSkipReason) override;
+ NS_IMETHOD GetIsLoadedBySocketProcess(bool* aResult) override;
+ NS_IMETHOD GetIsOCSP(bool* value) override;
+ NS_IMETHOD SetIsOCSP(bool value) override;
+ NS_IMETHOD GetTlsFlags(uint32_t* aTlsFlags) override;
+ NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override;
+ NS_IMETHOD GetApiRedirectToURI(nsIURI** aApiRedirectToURI) override;
+ [[nodiscard]] virtual nsresult AddSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory);
+ NS_IMETHOD TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage>& aMessages) override;
+ NS_IMETHOD GetResponseTimeoutEnabled(bool* aEnable) override;
+ NS_IMETHOD SetResponseTimeoutEnabled(bool aEnable) override;
+ NS_IMETHOD GetInitialRwin(uint32_t* aRwin) override;
+ NS_IMETHOD SetInitialRwin(uint32_t aRwin) override;
+ NS_IMETHOD ForcePending(bool aForcePending) override;
+ NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override;
+ NS_IMETHOD GetCorsIncludeCredentials(bool* aInclude) override;
+ NS_IMETHOD SetCorsIncludeCredentials(bool aInclude) override;
+ NS_IMETHOD GetRequestMode(dom::RequestMode* aRequestMode) override;
+ NS_IMETHOD SetRequestMode(dom::RequestMode aRequestMode) override;
+ NS_IMETHOD GetRedirectMode(uint32_t* aRedirectMode) override;
+ NS_IMETHOD SetRedirectMode(uint32_t aRedirectMode) override;
+ NS_IMETHOD GetFetchCacheMode(uint32_t* aFetchCacheMode) override;
+ NS_IMETHOD SetFetchCacheMode(uint32_t aFetchCacheMode) override;
+ NS_IMETHOD GetTopWindowURI(nsIURI** aTopWindowURI) override;
+ NS_IMETHOD SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) override;
+ NS_IMETHOD GetProxyURI(nsIURI** proxyURI) override;
+ virtual void SetCorsPreflightParameters(
+ const nsTArray<nsCString>& unsafeHeaders,
+ bool aShouldStripRequestBodyHeader) override;
+ virtual void SetAltDataForChild(bool aIsForChild) override;
+ virtual void DisableAltDataCache() override {
+ StoreDisableAltDataCache(true);
+ };
+
+ NS_IMETHOD GetConnectionInfoHashKey(
+ nsACString& aConnectionInfoHashKey) override;
+ NS_IMETHOD GetIntegrityMetadata(nsAString& aIntegrityMetadata) override;
+ NS_IMETHOD SetIntegrityMetadata(const nsAString& aIntegrityMetadata) override;
+ NS_IMETHOD GetLastRedirectFlags(uint32_t* aValue) override;
+ NS_IMETHOD SetLastRedirectFlags(uint32_t aValue) override;
+ NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override;
+ NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
+ NS_IMETHOD CancelByURLClassifier(nsresult aErrorCode) override;
+ NS_IMETHOD SetIPv4Disabled(void) override;
+ NS_IMETHOD SetIPv6Disabled(void) override;
+ NS_IMETHOD GetCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy* aCrossOriginOpenerPolicy) override;
+ NS_IMETHOD ComputeCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
+ nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) override;
+ NS_IMETHOD HasCrossOriginOpenerPolicyMismatch(bool* aIsMismatch) override;
+ NS_IMETHOD GetResponseEmbedderPolicy(
+ bool aIsOriginTrialCoepCredentiallessEnabled,
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) override;
+
+ inline void CleanRedirectCacheChainIfNecessary() {
+ auto redirectedCachekeys = mRedirectedCachekeys.Lock();
+ redirectedCachekeys.ref() = nullptr;
+ }
+ NS_IMETHOD HTTPUpgrade(const nsACString& aProtocolName,
+ nsIHttpUpgradeListener* aListener) override;
+ void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() override;
+
+ NS_IMETHOD SetWaitForHTTPSSVCRecord() override;
+
+ NS_IMETHOD SetEarlyHintPreloaderId(uint64_t aEarlyHintPreloaderId) override;
+ NS_IMETHOD GetEarlyHintPreloaderId(uint64_t* aEarlyHintPreloaderId) override;
+
+ NS_IMETHOD SetEarlyHintLinkType(uint32_t aEarlyHintLinkType) override;
+ NS_IMETHOD GetEarlyHintLinkType(uint32_t* aEarlyHintLinkType) override;
+
+ NS_IMETHOD SetIsUserAgentHeaderModified(bool value) override;
+ NS_IMETHOD GetIsUserAgentHeaderModified(bool* value) override;
+
+ NS_IMETHOD SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) override;
+ NS_IMETHOD GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) override;
+
+ NS_IMETHOD SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) override;
+ NS_IMETHOD GetDocumentCharacterSet(nsAString& aDocumentCharacterSet) override;
+
+ virtual void SetConnectionInfo(
+ mozilla::net::nsHttpConnectionInfo* aCI) override;
+
+ // nsISupportsPriority
+ NS_IMETHOD GetPriority(int32_t* value) override;
+ NS_IMETHOD AdjustPriority(int32_t delta) override;
+
+ // nsIClassOfService
+ NS_IMETHOD GetClassFlags(uint32_t* outFlags) override {
+ *outFlags = mClassOfService.Flags();
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetIncremental(bool* outIncremental) override {
+ *outIncremental = mClassOfService.Incremental();
+ return NS_OK;
+ }
+
+ // nsIResumableChannel
+ NS_IMETHOD GetEntityID(nsACString& aEntityID) override;
+
+ // nsIConsoleReportCollector
+ void AddConsoleReport(uint32_t aErrorFlags, const nsACString& aCategory,
+ nsContentUtils::PropertiesFile aPropertiesFile,
+ const nsACString& aSourceFileURI, uint32_t aLineNumber,
+ uint32_t aColumnNumber, const nsACString& aMessageName,
+ const nsTArray<nsString>& aStringParams) override;
+
+ void FlushReportsToConsole(
+ uint64_t aInnerWindowID,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushReportsToConsoleForServiceWorkerScope(
+ const nsACString& aScope,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushConsoleReports(
+ dom::Document* aDocument,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushConsoleReports(
+ nsILoadGroup* aLoadGroup,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushConsoleReports(nsIConsoleReportCollector* aCollector) override;
+
+ void StealConsoleReports(
+ nsTArray<net::ConsoleReportCollected>& aReports) override;
+
+ void ClearConsoleReports() override;
+
+ class nsContentEncodings : public nsStringEnumeratorBase {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ using nsStringEnumeratorBase::GetNext;
+
+ nsContentEncodings(nsIHttpChannel* aChannel, const char* aEncodingHeader);
+
+ private:
+ virtual ~nsContentEncodings() = default;
+
+ [[nodiscard]] nsresult PrepareForNext(void);
+
+ // We do not own the buffer. The channel owns it.
+ const char* mEncodingHeader;
+ const char* mCurStart; // points to start of current header
+ const char* mCurEnd; // points to end of current header
+
+ // Hold a ref to our channel so that it can't go away and take the
+ // header with it.
+ nsCOMPtr<nsIHttpChannel> mChannel;
+
+ bool mReady;
+ };
+
+ nsHttpResponseHead* GetResponseHead() const { return mResponseHead.get(); }
+ nsHttpRequestHead* GetRequestHead() { return &mRequestHead; }
+ nsHttpHeaderArray* GetResponseTrailers() const {
+ return mResponseTrailers.get();
+ }
+
+ void SetDummyChannelForImageCache();
+
+ const NetAddr& GetSelfAddr() { return mSelfAddr; }
+ const NetAddr& GetPeerAddr() { return mPeerAddr; }
+
+ [[nodiscard]] nsresult OverrideSecurityInfo(
+ nsITransportSecurityInfo* aSecurityInfo);
+
+ void LogORBError(const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason);
+
+ public: /* Necko internal use only... */
+ int64_t GetAltDataLength() { return mAltDataLength; }
+ bool IsNavigation();
+
+ bool IsDeliveringAltData() const { return LoadDeliveringAltData(); }
+
+ static void PropagateReferenceIfNeeded(nsIURI* aURI,
+ nsCOMPtr<nsIURI>& aRedirectURI);
+
+ // Return whether upon a redirect code of httpStatus for method, the
+ // request method should be rewritten to GET.
+ static bool ShouldRewriteRedirectToGET(
+ uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method);
+
+ // Like nsIEncodedChannel::DoApplyConversions except context is set to
+ // mListenerContext.
+ [[nodiscard]] nsresult DoApplyContentConversions(
+ nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener);
+
+ void AddClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+
+ const uint64_t& ChannelId() const { return mChannelId; }
+
+ nsresult InternalSetUploadStream(nsIInputStream* uploadStream,
+ int64_t aContentLength = -1,
+ bool aSetContentLengthHeader = false);
+
+ void SetUploadStreamHasHeaders(bool hasHeaders) {
+ StoreUploadStreamHasHeaders(hasHeaders);
+ }
+
+ virtual nsresult SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect = true) {
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ }
+ return mRequestHead.SetHeader(nsHttp::Referer, aReferrer);
+ }
+
+ nsresult ClearReferrerHeader() {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ return mRequestHead.ClearHeader(nsHttp::Referer);
+ }
+
+ void SetTopWindowURI(nsIURI* aTopWindowURI) { mTopWindowURI = aTopWindowURI; }
+
+ // Set referrerInfo and compute the referrer header if neccessary.
+ // Pass true for aSetOriginal if this is a new referrer and should
+ // overwrite the 'original' value, false if this is a mutation (like
+ // stripping the path).
+ nsresult SetReferrerInfoInternal(nsIReferrerInfo* aReferrerInfo, bool aClone,
+ bool aCompute, bool aRespectBeforeConnect);
+
+ struct ReplacementChannelConfig {
+ ReplacementChannelConfig() = default;
+ explicit ReplacementChannelConfig(
+ const dom::ReplacementChannelConfigInit& aInit);
+
+ uint32_t redirectFlags = 0;
+ ClassOfService classOfService = {0, false};
+ Maybe<bool> privateBrowsing = Nothing();
+ Maybe<nsCString> method;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ Maybe<dom::TimedChannelInfo> timedChannelInfo;
+ nsCOMPtr<nsIInputStream> uploadStream;
+ uint64_t uploadStreamLength = 0;
+ bool uploadStreamHasHeaders = false;
+ Maybe<nsCString> contentType;
+ Maybe<nsCString> contentLength;
+
+ dom::ReplacementChannelConfigInit Serialize();
+ };
+
+ enum class ReplacementReason {
+ Redirect,
+ InternalRedirect,
+ DocumentChannel,
+ };
+
+ // Create a ReplacementChannelConfig object that can be used to duplicate the
+ // current channel.
+ ReplacementChannelConfig CloneReplacementChannelConfig(
+ bool aPreserveMethod, uint32_t aRedirectFlags, ReplacementReason aReason);
+
+ static void ConfigureReplacementChannel(nsIChannel*,
+ const ReplacementChannelConfig&,
+ ReplacementReason);
+
+ // Called before we create the redirect target channel.
+ already_AddRefed<nsILoadInfo> CloneLoadInfoForRedirect(
+ nsIURI* aNewURI, uint32_t aRedirectFlags);
+
+ // True if we've already applied content conversion to the data
+ // passed to mListener.
+ bool HasAppliedConversion() { return LoadHasAppliedConversion(); }
+
+ // https://fetch.spec.whatwg.org/#concept-request-tainted-origin
+ bool HasRedirectTaintedOrigin() { return LoadTaintedOriginFlag(); }
+
+ bool ChannelBlockedByOpaqueResponse() const {
+ return mChannelBlockedByOpaqueResponse;
+ }
+ bool CachedOpaqueResponseBlockingPref() const {
+ return mCachedOpaqueResponseBlockingPref;
+ }
+
+ TimeStamp GetOnStartRequestStartTime() const {
+ return mOnStartRequestStartTime;
+ }
+ TimeStamp GetDataAvailableStartTime() const {
+ return mOnDataAvailableStartTime;
+ }
+ TimeStamp GetOnStopRequestStartTime() const {
+ return mOnStopRequestStartTime;
+ }
+
+ protected:
+ nsresult GetTopWindowURI(nsIURI* aURIBeingLoaded, nsIURI** aTopWindowURI);
+
+ // Handle notifying listener, removing from loadgroup if request failed.
+ void DoNotifyListener();
+ virtual void DoNotifyListenerCleanup() = 0;
+
+ // drop reference to listener, its callbacks, and the progress sink
+ virtual void ReleaseListeners();
+
+ // Call AsyncAbort().
+ virtual void DoAsyncAbort(nsresult aStatus) = 0;
+
+ // This is fired only when a cookie is created due to the presence of
+ // Set-Cookie header in the response header of any network request.
+ // This notification will come only after the "http-on-examine-response"
+ // was fired.
+ void NotifySetCookie(const nsACString& aCookie);
+
+ void MaybeReportTimingData();
+ nsIURI* GetReferringPage();
+ nsPIDOMWindowInner* GetInnerDOMWindow();
+
+ void AddCookiesToRequest();
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI*, nsIChannel*, bool preserveMethod, uint32_t redirectFlags);
+
+ bool IsNewChannelSameOrigin(nsIChannel* aNewChannel);
+
+ // WHATWG Fetch Standard 4.4. HTTP-redirect fetch, step 10
+ virtual bool ShouldTaintReplacementChannelOrigin(nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags);
+
+ // bundle calling OMR observers and marking flag into one function
+ inline void CallOnModifyRequestObservers() {
+ gHttpHandler->OnModifyRequest(this);
+ MOZ_ASSERT(!LoadRequestObserversCalled());
+ StoreRequestObserversCalled(true);
+ }
+
+ // Helper function to simplify getting notification callbacks.
+ template <class T>
+ void GetCallback(nsCOMPtr<T>& aResult) {
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(aResult));
+ }
+
+ // Redirect tracking
+ // Checks whether or not aURI and mOriginalURI share the same domain.
+ virtual bool SameOriginWithOriginalUri(nsIURI* aURI);
+
+ [[nodiscard]] bool BypassServiceWorker() const;
+
+ // Returns true if this channel should intercept the network request and
+ // prepare for a possible synthesized response instead.
+ bool ShouldIntercept(nsIURI* aURI = nullptr);
+
+#ifdef DEBUG
+ // Check if mPrivateBrowsingId matches between LoadInfo and LoadContext.
+ void AssertPrivateBrowsingId();
+#endif
+
+ static void CallTypeSniffers(void* aClosure, const uint8_t* aData,
+ uint32_t aCount);
+
+ nsresult CheckRedirectLimit(uint32_t aRedirectFlags) const;
+
+ bool MaybeWaitForUploadStreamNormalization(nsIStreamListener* aListener,
+ nsISupports* aContext);
+
+ void MaybeFlushConsoleReports();
+
+ bool IsBrowsingContextDiscarded() const;
+
+ nsresult ProcessCrossOriginEmbedderPolicyHeader();
+
+ nsresult ProcessCrossOriginResourcePolicyHeader();
+
+ nsresult ComputeCrossOriginOpenerPolicyMismatch();
+
+ nsresult ProcessCrossOriginSecurityHeaders();
+
+ nsresult ValidateMIMEType();
+
+ bool ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch aFilterType) const;
+ bool ShouldBlockOpaqueResponse() const;
+ OpaqueResponse BlockOrFilterOpaqueResponse(
+ OpaqueResponseBlocker* aORB, const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
+ const char* aFormat, ...);
+
+ OpaqueResponse PerformOpaqueResponseSafelistCheckBeforeSniff();
+
+ OpaqueResponse PerformOpaqueResponseSafelistCheckAfterSniff(
+ const nsACString& aContentType, bool aNoSniff);
+
+ bool NeedOpaqueResponseAllowedCheckAfterSniff() const;
+ void BlockOpaqueResponseAfterSniff(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason);
+ void AllowOpaqueResponseAfterSniff();
+ void SetChannelBlockedByOpaqueResponse();
+ bool Http3Allowed() const;
+
+ friend class OpaqueResponseBlocker;
+ friend class PrivateBrowsingChannel<HttpBaseChannel>;
+ friend class InterceptFailedOnStop;
+ friend class HttpChannelParent;
+
+ protected:
+ // this section is for main-thread-only object
+ // all the references need to be proxy released on main thread.
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsCOMPtr<nsIURI> mAPIRedirectToURI;
+ nsCOMPtr<nsIURI> mProxyURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIURI> mTopWindowURI;
+ nsCOMPtr<nsIStreamListener> mListener;
+ // An instance of nsHTTPCompressConv
+ nsCOMPtr<nsIStreamListener> mCompressListener;
+ nsCOMPtr<nsIEventTarget> mCurrentThread;
+
+ RefPtr<OpaqueResponseBlocker> mORB;
+
+ private:
+ // Proxy release all members above on main thread.
+ void ReleaseMainThreadOnlyReferences();
+
+ void ExplicitSetUploadStreamLength(uint64_t aContentLength,
+ bool aSetContentLengthHeader);
+
+ void MaybeResumeAsyncOpen();
+
+ protected:
+ nsCString mSpec; // ASCII encoded URL spec
+ nsCString mContentTypeHint;
+ nsCString mContentCharsetHint;
+ nsCString mUserSetCookieHeader;
+ // HTTP Upgrade Data
+ nsCString mUpgradeProtocol;
+ // Resumable channel specific data
+ nsCString mEntityID;
+ // The initiator type (for this resource) - how was the resource referenced in
+ // the HTML file.
+ nsString mInitiatorType;
+ // Holds the name of the preferred alt-data type for each contentType.
+ nsTArray<PreferredAlternativeDataTypeParams> mPreferredCachedAltDataTypes;
+ // Holds the name of the alternative data type the channel returned.
+ nsCString mAvailableCachedAltDataType;
+ nsString mIntegrityMetadata;
+
+ // Classified channel's matched information
+ nsCString mMatchedList;
+ nsCString mMatchedProvider;
+ nsCString mMatchedFullHash;
+
+ nsTArray<nsCString> mMatchedTrackingLists;
+ nsTArray<nsCString> mMatchedTrackingFullHashes;
+
+ nsCOMPtr<nsISupports> mOwner;
+
+ nsHttpRequestHead mRequestHead;
+ // Upload throttling.
+ nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ UniquePtr<nsHttpResponseHead> mResponseHead;
+ UniquePtr<nsHttpHeaderArray> mResponseTrailers;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeProtocolCallback;
+ UniquePtr<nsString> mContentDispositionFilename;
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+ // Accessed on MainThread and Cache2 IO thread
+ DataMutex<UniquePtr<nsTArray<nsCString>>> mRedirectedCachekeys{
+ "mRedirectedCacheKeys"};
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+
+ nsTArray<std::pair<nsString, nsString>> mSecurityConsoleMessages;
+ nsTArray<nsCString> mUnsafeHeaders;
+
+ // A time value equal to the starting time of the fetch that initiates the
+ // redirect.
+ mozilla::TimeStamp mRedirectStartTimeStamp;
+ // A time value equal to the time immediately after receiving the last byte of
+ // the response of the last redirect.
+ mozilla::TimeStamp mRedirectEndTimeStamp;
+
+ PRTime mChannelCreationTime;
+ TimeStamp mChannelCreationTimestamp;
+ TimeStamp mAsyncOpenTime;
+ TimeStamp mCacheReadStart;
+ TimeStamp mCacheReadEnd;
+ TimeStamp mLaunchServiceWorkerStart;
+ TimeStamp mLaunchServiceWorkerEnd;
+ TimeStamp mDispatchFetchEventStart;
+ TimeStamp mDispatchFetchEventEnd;
+ TimeStamp mHandleFetchEventStart;
+ TimeStamp mHandleFetchEventEnd;
+ TimeStamp mOnStartRequestStartTime;
+ TimeStamp mOnDataAvailableStartTime;
+ TimeStamp mOnStopRequestStartTime;
+ // copied from the transaction before we null out mTransaction
+ // so that the timing can still be queried from OnStopRequest
+ TimingStruct mTransactionTimings;
+
+ // Gets computed during ComputeCrossOriginOpenerPolicyMismatch so we have
+ // the channel's policy even if we don't know policy initiator.
+ nsILoadInfo::CrossOriginOpenerPolicy mComputedCrossOriginOpenerPolicy;
+
+ uint64_t mStartPos;
+ uint64_t mTransferSize;
+ uint64_t mRequestSize;
+ uint64_t mDecodedBodySize;
+ // True only when the channel supports any of the versions of HTTP3
+ bool mSupportsHTTP3;
+ uint64_t mEncodedBodySize;
+ uint64_t mRequestContextID;
+ // ID of the top-level document's inner window this channel is being
+ // originated from.
+ uint64_t mContentWindowId;
+ uint64_t mBrowserId;
+ int64_t mAltDataLength;
+ uint64_t mChannelId;
+ uint64_t mReqContentLength;
+
+ Atomic<nsresult, ReleaseAcquire> mStatus;
+
+ // Use Release-Acquire ordering to ensure the OMT ODA is ignored while channel
+ // is canceled on main thread.
+ Atomic<bool, ReleaseAcquire> mCanceled;
+ Atomic<uint32_t, ReleaseAcquire> mFirstPartyClassificationFlags;
+ Atomic<uint32_t, ReleaseAcquire> mThirdPartyClassificationFlags;
+
+ // mutex to guard members accessed during OnDataFinished in
+ // HttpChannelChild.cpp
+ Mutex mOnDataFinishedMutex{"HttpChannelChild::OnDataFinishedMutex"};
+
+ UniquePtr<ProfileChunkedBuffer> mSource;
+
+ uint32_t mLoadFlags;
+ uint32_t mCaps;
+
+ ClassOfService mClassOfService;
+ // This should be set the the actual TRR mode used to resolve the request.
+ // Is initially set to TRR_DEFAULT_MODE, but should be updated to the actual
+ // mode used by the request
+ nsIRequest::TRRMode mEffectiveTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason mTRRSkipReason = TRRSkippedReason::TRR_UNSET;
+
+ public:
+ void SetEarlyHints(
+ nsTArray<mozilla::net::EarlyHintConnectArgs>&& aEarlyHints);
+ nsTArray<mozilla::net::EarlyHintConnectArgs>&& TakeEarlyHints();
+
+ protected:
+ // Storing Http 103 Early Hint preloads. The parent process is responsible to
+ // start the early hint preloads, but the http child needs to be able to look
+ // them up. They are sent via IPC and stored in this variable. This is set on
+ // main document channel
+ nsTArray<EarlyHintConnectArgs> mEarlyHints;
+ // EarlyHintRegistrar id to connect back to the preload. Set on preload
+ // channels started from the above list
+ uint64_t mEarlyHintPreloaderId = 0;
+ uint32_t mEarlyHintLinkType = 0;
+
+ nsString mClassicScriptHintCharset;
+ nsString mDocumentCharacterSet;
+
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields1, 32, (
+ (uint32_t, UpgradeToSecure, 1),
+ (uint32_t, ApplyConversion, 1),
+ // Set to true if DoApplyContentConversions has been applied to
+ // our default mListener.
+ (uint32_t, HasAppliedConversion, 1),
+ (uint32_t, IsPending, 1),
+ (uint32_t, WasOpened, 1),
+ // if 1 all "http-on-{opening|modify|etc}-request" observers have been
+ // called.
+ (uint32_t, RequestObserversCalled, 1),
+ (uint32_t, ResponseHeadersModified, 1),
+ (uint32_t, AllowSTS, 1),
+ (uint32_t, ThirdPartyFlags, 3),
+ (uint32_t, UploadStreamHasHeaders, 1),
+ (uint32_t, ChannelIsForDownload, 1),
+ (uint32_t, TracingEnabled, 1),
+ // True if timing collection is enabled
+ (uint32_t, TimingEnabled, 1),
+ (uint32_t, ReportTiming, 1),
+ (uint32_t, AllowSpdy, 1),
+ (uint32_t, AllowHttp3, 1),
+ (uint32_t, AllowAltSvc, 1),
+ // !!! This is also used by the URL classifier to exempt channels from
+ // classification. If this is changed or removed, make sure we also update
+ // NS_ShouldClassifyChannel accordingly !!!
+ (uint32_t, BeConservative, 1),
+ // If the current channel is used to as a TRR connection.
+ (uint32_t, IsTRRServiceChannel, 1),
+ // If the request was performed to a TRR resolved IP address.
+ // Will be false if loading the resource does not create a connection
+ // (for example when it's loaded from the cache).
+ (uint32_t, ResolvedByTRR, 1),
+ (uint32_t, ResponseTimeoutEnabled, 1),
+ // A flag that should be false only if a cross-domain redirect occurred
+ (uint32_t, AllRedirectsSameOrigin, 1),
+
+ // Is 1 if no redirects have occured or if all redirects
+ // pass the Resource Timing timing-allow-check
+ (uint32_t, AllRedirectsPassTimingAllowCheck, 1),
+
+ // True if this channel was intercepted and could receive a synthesized
+ // response.
+ (uint32_t, ResponseCouldBeSynthesized, 1),
+
+ (uint32_t, BlockAuthPrompt, 1),
+
+ // If true, we behave as if the LOAD_FROM_CACHE flag has been set.
+ // Used to enforce that flag's behavior but not expose it externally.
+ (uint32_t, AllowStaleCacheContent, 1),
+
+ // If true, we behave as if the VALIDATE_ALWAYS flag has been set.
+ // Used to force validate the cached content.
+ (uint32_t, ForceValidateCacheContent, 1),
+
+ // If true, we prefer the LOAD_FROM_CACHE flag over LOAD_BYPASS_CACHE or
+ // LOAD_BYPASS_LOCAL_CACHE.
+ (uint32_t, PreferCacheLoadOverBypass, 1),
+
+ (uint32_t, IsProxyUsed, 1)
+ ))
+
+ // Broken up into two bitfields to avoid alignment requirements of uint64_t.
+ // (Too many bits used for one uint32_t.)
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields2, 32, (
+ // True iff this request has been calculated in its request context as
+ // a non tail request. We must remove it again when this channel is done.
+ (uint32_t, AddedAsNonTailRequest, 1),
+
+ // True if AsyncOpen() is called when the upload stream normalization or
+ // length is still unknown. AsyncOpen() will be retriggered when
+ // normalization is complete and length has been determined.
+ (uint32_t, AsyncOpenWaitingForStreamNormalization, 1),
+
+ // Defaults to true. This is set to false when it is no longer possible
+ // to upgrade the request to a secure channel.
+ (uint32_t, UpgradableToSecure, 1),
+
+ // Tainted origin flag of a request, specified by
+ // WHATWG Fetch Standard 2.2.5.
+ (uint32_t, TaintedOriginFlag, 1),
+
+ // If the channel is being used to check OCSP
+ (uint32_t, IsOCSP, 1),
+
+ // Used by system requests such as remote settings and updates to
+ // retry requests without proxies.
+ (uint32_t, BypassProxy, 1),
+
+ // Indicate whether the response of this channel is coming from
+ // socket process.
+ (uint32_t, LoadedBySocketProcess, 1),
+
+ // Indicates whether the user-agent header has been modifed since the channel
+ // was created.
+ (uint32_t, IsUserAgentHeaderModified, 1)
+ ))
+ // clang-format on
+
+ // An opaque flags for non-standard behavior of the TLS system.
+ // It is unlikely this will need to be set outside of telemetry studies
+ // relating to the TLS implementation.
+ uint32_t mTlsFlags;
+
+ // Current suspension depth for this channel object
+ uint32_t mSuspendCount;
+
+ // Per channel transport window override (0 means no override)
+ uint32_t mInitialRwin;
+
+ uint32_t mProxyResolveFlags;
+
+ uint32_t mContentDispositionHint;
+
+ dom::RequestMode mRequestMode;
+ uint32_t mRedirectMode;
+
+ // If this channel was created as the result of a redirect, then this value
+ // will reflect the redirect flags passed to the SetupReplacementChannel()
+ // method.
+ uint32_t mLastRedirectFlags;
+
+ int16_t mPriority;
+ uint8_t mRedirectionLimit;
+
+ // Performance tracking
+ // Number of redirects that has occurred.
+ int8_t mRedirectCount;
+ // Number of internal redirects that has occurred.
+ int8_t mInternalRedirectCount;
+
+ enum class SnifferCategoryType {
+ NetContent = 0,
+ OpaqueResponseBlocking,
+ All
+ };
+ SnifferCategoryType mSnifferCategoryType = SnifferCategoryType::NetContent;
+
+ // Used to ensure the same pref value is being used across the
+ // lifetime of this http channel.
+ const bool mCachedOpaqueResponseBlockingPref;
+ bool mChannelBlockedByOpaqueResponse;
+
+ bool mDummyChannelForImageCache;
+
+ bool mHasContentDecompressed;
+
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields3, 8, (
+ (bool, AsyncOpenTimeOverriden, 1),
+ (bool, ForcePending, 1),
+
+ // true if the channel is deliving alt-data.
+ (bool, DeliveringAltData, 1),
+
+ (bool, CorsIncludeCredentials, 1),
+
+ // These parameters are used to ensure that we do not call OnStartRequest
+ // and OnStopRequest more than once.
+ (bool, OnStartRequestCalled, 1),
+ (bool, OnStopRequestCalled, 1),
+
+ // Defaults to false. Is set to true at the begining of OnStartRequest.
+ // Used to ensure methods can't be called before OnStartRequest.
+ (bool, AfterOnStartRequestBegun, 1),
+
+ (bool, RequireCORSPreflight, 1)
+ ))
+
+ // Broken up into two bitfields to avoid alignment requirements of uint16_t.
+ // (Too many bits used for one uint8_t.)
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields4, 8, (
+ // This flag will be true if the consumer is requesting alt-data AND the
+ // consumer is in the child process.
+ (bool, AltDataForChild, 1),
+ // This flag will be true if the consumer cannot process alt-data. This
+ // is used in the webextension StreamFilter handler. If true, we bypass
+ // using alt-data for the request.
+ (bool, DisableAltDataCache, 1),
+
+ (bool, ForceMainDocumentChannel, 1),
+ // This is set true if the channel is waiting for upload stream
+ // normalization or the InputStreamLengthHelper::GetAsyncLength callback.
+ (bool, PendingUploadStreamNormalization, 1),
+
+ // Set to true if our listener has indicated that it requires
+ // content conversion to be done by us.
+ (bool, ListenerRequiresContentConversion, 1),
+
+ // True if this is a navigation to a page with a different cross origin
+ // opener policy ( see ComputeCrossOriginOpenerPolicyMismatch )
+ (uint32_t, HasCrossOriginOpenerPolicyMismatch, 1),
+
+ // True if HTTPS RR is used during the connection establishment of this
+ // channel.
+ (uint32_t, HasHTTPSRR, 1),
+
+ // Ensures that ProcessCrossOriginSecurityHeadersCalled has been called
+ // before calling CallOnStartRequest.
+ (uint32_t, ProcessCrossOriginSecurityHeadersCalled, 1)
+ ))
+ // clang-format on
+
+ bool EnsureRequestContextID();
+ bool EnsureRequestContext();
+
+ // Adds/removes this channel as a non-tailed request in its request context
+ // these helpers ensure we add it only once and remove it only when added
+ // via AddedAsNonTailRequest member tracking.
+ void AddAsNonTailRequest();
+ void RemoveAsNonTailRequest();
+
+ void EnsureBrowserId();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpBaseChannel, HTTP_BASE_CHANNEL_IID)
+
+// Share some code while working around C++'s absurd inability to handle casting
+// of member functions between base/derived types.
+// - We want to store member function pointer to call at resume time, but one
+// such function--HandleAsyncAbort--we want to share between the
+// nsHttpChannel/HttpChannelChild. Can't define it in base class, because
+// then we'd have to cast member function ptr between base/derived class
+// types. Sigh...
+template <class T>
+class HttpAsyncAborter {
+ public:
+ explicit HttpAsyncAborter(T* derived)
+ : mThis(derived), mCallOnResume(nullptr) {}
+
+ // Aborts channel: calls OnStart/Stop with provided status, removes channel
+ // from loadGroup.
+ [[nodiscard]] nsresult AsyncAbort(nsresult status);
+
+ // Does most the actual work.
+ void HandleAsyncAbort();
+
+ // AsyncCall calls a member function asynchronously (via an event).
+ // retval isn't refcounted and is set only when event was successfully
+ // posted, the event is returned for the purpose of cancelling when needed
+ [[nodiscard]] virtual nsresult AsyncCall(
+ void (T::*funcPtr)(), nsRunnableMethod<T>** retval = nullptr);
+
+ private:
+ T* mThis;
+
+ protected:
+ // Function to be called at resume time
+ std::function<nsresult(T*)> mCallOnResume;
+};
+
+template <class T>
+[[nodiscard]] nsresult HttpAsyncAborter<T>::AsyncAbort(nsresult status) {
+ MOZ_LOG(gHttpLog, LogLevel::Debug,
+ ("HttpAsyncAborter::AsyncAbort [this=%p status=%" PRIx32 "]\n", mThis,
+ static_cast<uint32_t>(status)));
+
+ mThis->mStatus = status;
+
+ // if this fails? Callers ignore our return value anyway....
+ return AsyncCall(&T::HandleAsyncAbort);
+}
+
+// Each subclass needs to define its own version of this (which just calls this
+// base version), else we wind up casting base/derived member function ptrs
+template <class T>
+inline void HttpAsyncAborter<T>::HandleAsyncAbort() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mThis->mSuspendCount) {
+ MOZ_LOG(
+ gHttpLog, LogLevel::Debug,
+ ("Waiting until resume to do async notification [this=%p]\n", mThis));
+ mCallOnResume = [](T* self) {
+ self->HandleAsyncAbort();
+ return NS_OK;
+ };
+ return;
+ }
+
+ mThis->DoNotifyListener();
+
+ // finally remove ourselves from the load group.
+ if (mThis->mLoadGroup) {
+ mThis->mLoadGroup->RemoveRequest(mThis, nullptr, mThis->mStatus);
+ }
+}
+
+template <class T>
+nsresult HttpAsyncAborter<T>::AsyncCall(void (T::*funcPtr)(),
+ nsRunnableMethod<T>** retval) {
+ nsresult rv;
+
+ RefPtr<nsRunnableMethod<T>> event =
+ NewRunnableMethod("net::HttpAsyncAborter::AsyncCall", mThis, funcPtr);
+ rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv) && retval) {
+ *retval = event;
+ }
+
+ return rv;
+}
+
+class ProxyReleaseRunnable final : public mozilla::Runnable {
+ public:
+ explicit ProxyReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed)
+ : Runnable("ProxyReleaseRunnable"), mDoomed(std::move(aDoomed)) {}
+
+ NS_IMETHOD
+ Run() override {
+ mDoomed.Clear();
+ return NS_OK;
+ }
+
+ private:
+ virtual ~ProxyReleaseRunnable() = default;
+
+ nsTArray<nsCOMPtr<nsISupports>> mDoomed;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpBaseChannel_h
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
new file mode 100644
index 0000000000..393d0aa37d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -0,0 +1,3391 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/PBackgroundDataBridge.h"
+#include "nsHttp.h"
+#include "nsICacheEntry.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+
+#include "AltDataOutputStreamChild.h"
+#include "CookieServiceChild.h"
+#include "HttpBackgroundChannelChild.h"
+#include "NetworkMarker.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsStringStream.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsQueryObject.h"
+#include "nsNetUtil.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/SocketProcessBridgeChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "SerializedLoadContext.h"
+#include "nsInputStreamPump.h"
+#include "nsContentSecurityManager.h"
+#include "nsICompressConvStats.h"
+#include "mozilla/dom/Document.h"
+#include "nsIScriptError.h"
+#include "nsISerialEventTarget.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsSocketTransportService2.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIOService.h"
+
+#include <functional>
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild
+//-----------------------------------------------------------------------------
+
+HttpChannelChild::HttpChannelChild()
+ : HttpAsyncAborter<HttpChannelChild>(this),
+ NeckoTargetHolder(nullptr),
+ mCacheEntryAvailable(false),
+ mAltDataCacheEntryAvailable(false),
+ mSendResumeAt(false),
+ mKeptAlive(false),
+ mIPCActorDeleted(false),
+ mSuspendSent(false),
+ mIsFirstPartOfMultiPart(false),
+ mIsLastPartOfMultiPart(false),
+ mSuspendForWaitCompleteRedirectSetup(false),
+ mRecvOnStartRequestSentCalled(false),
+ mSuspendedByWaitingForPermissionCookie(false),
+ mAlreadyReleased(false) {
+ LOG(("Creating HttpChannelChild @%p\n", this));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mLastStatusReported =
+ mChannelCreationTimestamp; // in case we enable the profiler after Init()
+ mAsyncOpenTime = TimeStamp::Now();
+ mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this));
+
+ // Ensure that the cookie service is initialized before the first
+ // IPC HTTP channel is created.
+ // We require that the parent cookie service actor exists while
+ // processing HTTP responses.
+ RefPtr<CookieServiceChild> cookieService = CookieServiceChild::GetSingleton();
+}
+
+HttpChannelChild::~HttpChannelChild() {
+ LOG(("Destroying HttpChannelChild @%p\n", this));
+
+ // See HttpChannelChild::Release, HttpChannelChild should be always destroyed
+ // on the main thread.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy && mAsyncOpenSucceeded &&
+ !mSuccesfullyRedirected && !LoadOnStopRequestCalled()) {
+ bool emptyBgChildQueue, nullBgChild;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+ nullBgChild = !mBgChild;
+ emptyBgChildQueue = !nullBgChild && mBgChild->IsQueueEmpty();
+ }
+
+ uint32_t flags =
+ (mRedirectChannelChild ? 1 << 0 : 0) |
+ (mEventQ->IsEmpty() ? 1 << 1 : 0) | (nullBgChild ? 1 << 2 : 0) |
+ (emptyBgChildQueue ? 1 << 3 : 0) |
+ (LoadOnStartRequestCalled() ? 1 << 4 : 0) |
+ (mBackgroundChildQueueFinalState == BCKCHILD_EMPTY ? 1 << 5 : 0) |
+ (mBackgroundChildQueueFinalState == BCKCHILD_NON_EMPTY ? 1 << 6 : 0) |
+ (mRemoteChannelExistedAtCancel ? 1 << 7 : 0) |
+ (mEverHadBgChildAtAsyncOpen ? 1 << 8 : 0) |
+ (mEverHadBgChildAtConnectParent ? 1 << 9 : 0) |
+ (mCreateBackgroundChannelFailed ? 1 << 10 : 0) |
+ (mBgInitFailCallbackTriggered ? 1 << 11 : 0) |
+ (mCanSendAtCancel ? 1 << 12 : 0) | (!!mSuspendCount ? 1 << 13 : 0) |
+ (!!mCallOnResume ? 1 << 14 : 0);
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "~HttpChannelChild, LoadOnStopRequestCalled()=false, mStatus=0x%08x, "
+ "mActorDestroyReason=%d, 20200717 flags=%u",
+ static_cast<uint32_t>(nsresult(mStatus)),
+ static_cast<int32_t>(mActorDestroyReason ? *mActorDestroyReason : -1),
+ flags);
+ }
+#endif
+
+ mEventQ->NotifyReleasingOwner();
+
+ ReleaseMainThreadOnlyReferences();
+}
+
+void HttpChannelChild::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ return;
+ }
+
+ NS_ReleaseOnMainThread("HttpChannelChild::mRedirectChannelChild",
+ mRedirectChannelChild.forget());
+}
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) HttpChannelChild::Release() {
+ if (!NS_IsMainThread()) {
+ nsrefcnt count = mRefCnt;
+ nsresult rv = NS_DispatchToMainThread(NewNonOwningRunnableMethod(
+ "HttpChannelChild::Release", this, &HttpChannelChild::Release));
+
+ // Continue Release procedure if failed to dispatch to main thread.
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ return count - 1;
+ }
+ }
+
+ nsrefcnt count = --mRefCnt;
+ MOZ_ASSERT(int32_t(count) >= 0, "dup release");
+
+ // Normally we Send_delete in OnStopRequest, but when we need to retain the
+ // remote channel for security info IPDL itself holds 1 reference, so we
+ // Send_delete when refCnt==1. But if !CanSend(), then there's nobody to send
+ // to, so we fall through.
+ if (mKeptAlive && count == 1 && CanSend()) {
+ NS_LOG_RELEASE(this, 1, "HttpChannelChild");
+ mKeptAlive = false;
+ // We send a message to the parent, which calls SendDelete, and then the
+ // child calling Send__delete__() to finally drop the refcount to 0.
+ TrySendDeletingChannel();
+ return 1;
+ }
+
+ if (count == 0) {
+ mRefCnt = 1; /* stabilize */
+
+ // We don't have a listener when AsyncOpen has failed or when this channel
+ // has been sucessfully redirected.
+ if (MOZ_LIKELY(LoadOnStartRequestCalled() && LoadOnStopRequestCalled()) ||
+ !mListener || mAlreadyReleased) {
+ NS_LOG_RELEASE(this, 0, "HttpChannelChild");
+ delete this;
+ return 0;
+ }
+
+ // This ensures that when the refcount goes to 0 again, we don't dispatch
+ // yet another runnable and get in a loop.
+ mAlreadyReleased = true;
+
+ // This makes sure we fulfill the stream listener contract all the time.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = NS_ERROR_ABORT;
+ }
+
+ // Turn the stabilization refcount into a regular strong reference.
+
+ // 1) We tell refcount logging about the "stabilization" AddRef, which
+ // will become the reference for |channel|. We do this first so that we
+ // don't tell refcount logging that the refcount has dropped to zero, which
+ // it will interpret as destroying the object.
+ NS_LOG_ADDREF(this, 2, "HttpChannelChild", sizeof(*this));
+
+ // 2) We tell refcount logging about the original call to Release().
+ NS_LOG_RELEASE(this, 1, "HttpChannelChild");
+
+ // 3) Finally, we turn the reference into a regular smart pointer.
+ RefPtr<HttpChannelChild> channel = dont_AddRef(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "~HttpChannelChild>DoNotifyListener",
+ [chan = std::move(channel)] { chan->DoNotifyListener(false); }));
+ // If NS_DispatchToMainThread failed then we're going to leak the runnable,
+ // and thus the channel, so there's no need to do anything else.
+ return mRefCnt;
+ }
+
+ NS_LOG_RELEASE(this, count, "HttpChannelChild");
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICacheInfoChannel,
+ !mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMultiPartChannel, mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIThreadRetargetableRequest,
+ !mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelChild)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::PHttpChannelChild
+//-----------------------------------------------------------------------------
+
+void HttpChannelChild::OnBackgroundChildReady(
+ HttpBackgroundChannelChild* aBgChild) {
+ LOG(("HttpChannelChild::OnBackgroundChildReady [this=%p, bgChild=%p]\n", this,
+ aBgChild));
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ // mBgChild might be removed or replaced while the original background
+ // channel is inited on STS thread.
+ if (mBgChild != aBgChild) {
+ return;
+ }
+
+ MOZ_ASSERT(mBgInitFailCallback);
+ mBgInitFailCallback = nullptr;
+ }
+}
+
+void HttpChannelChild::OnBackgroundChildDestroyed(
+ HttpBackgroundChannelChild* aBgChild) {
+ LOG(("HttpChannelChild::OnBackgroundChildDestroyed [this=%p]\n", this));
+ // This function might be called during shutdown phase, so OnSocketThread()
+ // might return false even on STS thread. Use IsOnCurrentThreadInfallible()
+ // to get correct information.
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ nsCOMPtr<nsIRunnable> callback;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ // mBgChild might be removed or replaced while the original background
+ // channel is destroyed on STS thread.
+ if (aBgChild != mBgChild) {
+ return;
+ }
+
+ mBgChild = nullptr;
+ callback = std::move(mBgInitFailCallback);
+ }
+
+ if (callback) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mBgInitFailCallbackTriggered = true;
+#endif
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ neckoTarget->Dispatch(callback, NS_DISPATCH_NORMAL);
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvOnStartRequestSent() {
+ LOG(("HttpChannelChild::RecvOnStartRequestSent [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mRecvOnStartRequestSentCalled);
+
+ mRecvOnStartRequestSentCalled = true;
+
+ if (mSuspendedByWaitingForPermissionCookie) {
+ mSuspendedByWaitingForPermissionCookie = false;
+ mEventQ->Resume();
+ }
+ return IPC_OK();
+}
+
+void HttpChannelChild::ProcessOnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStartTime) {
+ LOG(("HttpChannelChild::ProcessOnStartRequest [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ TimeStamp start = TimeStamp::Now();
+
+ mAltDataInputStream = DeserializeIPCStream(aAltData.altDataInputStream());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aResponseHead,
+ aUseResponseHead, aRequestHeaders, aArgs, start]() {
+ TimeDuration delay = TimeStamp::Now() - start;
+ glean::networking::http_content_onstart_delay.AccumulateRawDuration(
+ delay);
+
+ self->OnStartRequest(aResponseHead, aUseResponseHead, aRequestHeaders,
+ aArgs);
+ }));
+}
+
+static void ResourceTimingStructArgsToTimingsStruct(
+ const ResourceTimingStructArgs& aArgs, TimingStruct& aTimings) {
+ aTimings.domainLookupStart = aArgs.domainLookupStart();
+ aTimings.domainLookupEnd = aArgs.domainLookupEnd();
+ aTimings.connectStart = aArgs.connectStart();
+ aTimings.tcpConnectEnd = aArgs.tcpConnectEnd();
+ aTimings.secureConnectionStart = aArgs.secureConnectionStart();
+ aTimings.connectEnd = aArgs.connectEnd();
+ aTimings.requestStart = aArgs.requestStart();
+ aTimings.responseStart = aArgs.responseStart();
+ aTimings.responseEnd = aArgs.responseEnd();
+ aTimings.transactionPending = aArgs.transactionPending();
+}
+
+void HttpChannelChild::OnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs) {
+ LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this));
+
+ // If this channel was aborted by ActorDestroy, then there may be other
+ // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to
+ // be handled. In that case we just ignore them to avoid calling the listener
+ // twice.
+ if (LoadOnStartRequestCalled() && mIPCActorDeleted) {
+ return;
+ }
+
+ // Copy arguments only. It's possible to handle other IPC between
+ // OnStartRequest and DoOnStartRequest.
+ mComputedCrossOriginOpenerPolicy = aArgs.openerPolicy();
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aArgs.channelStatus();
+ }
+
+ // Cookies headers should not be visible to the child process
+ MOZ_ASSERT(!aRequestHeaders.HasHeader(nsHttp::Cookie));
+ MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie));
+
+ if (aUseResponseHead && !mCanceled) {
+ mResponseHead = MakeUnique<nsHttpResponseHead>(aResponseHead);
+ }
+
+ mSecurityInfo = aArgs.securityInfo();
+
+ ipc::MergeParentLoadInfoForwarder(aArgs.loadInfoForwarder(), mLoadInfo);
+
+ mIsFromCache = aArgs.isFromCache();
+ mIsRacing = aArgs.isRacing();
+ mCacheEntryAvailable = aArgs.cacheEntryAvailable();
+ mCacheEntryId = aArgs.cacheEntryId();
+ mCacheFetchCount = aArgs.cacheFetchCount();
+ mProtocolVersion = aArgs.protocolVersion();
+ mCacheExpirationTime = aArgs.cacheExpirationTime();
+ mSelfAddr = aArgs.selfAddr();
+ mPeerAddr = aArgs.peerAddr();
+
+ mRedirectCount = aArgs.redirectCount();
+ mAvailableCachedAltDataType = aArgs.altDataType();
+ StoreDeliveringAltData(aArgs.deliveringAltData());
+ mAltDataLength = aArgs.altDataLength();
+ StoreResolvedByTRR(aArgs.isResolvedByTRR());
+ mEffectiveTRRMode = aArgs.effectiveTRRMode();
+ mTRRSkipReason = aArgs.trrSkipReason();
+
+ SetApplyConversion(aArgs.applyConversion());
+
+ StoreAfterOnStartRequestBegun(true);
+ StoreHasHTTPSRR(aArgs.hasHTTPSRR());
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mCacheKey = aArgs.cacheKey();
+
+ StoreIsProxyUsed(aArgs.isProxyUsed());
+
+ // replace our request headers with what actually got sent in the parent
+ mRequestHead.SetHeaders(aRequestHeaders);
+
+ // Note: this is where we would notify "http-on-examine-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // gHttpHandler->OnExamineResponse(this);
+
+ ResourceTimingStructArgsToTimingsStruct(aArgs.timing(), mTransactionTimings);
+
+ nsAutoCString cosString;
+ ClassOfService::ToString(mClassOfService, cosString);
+ if (!mAsyncOpenTime.IsNull() &&
+ !aArgs.timing().transactionPending().IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_ASYNC_OPEN_CHILD_TO_TRANSACTION_PENDING_EXP_MS,
+ cosString, mAsyncOpenTime, aArgs.timing().transactionPending());
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelAsyncOpenToTransactionPending,
+ aArgs.timing().transactionPending() - mAsyncOpenTime);
+ }
+
+ const TimeStamp now = TimeStamp::Now();
+ if (!aArgs.timing().responseStart().IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_RESPONSE_START_PARENT_TO_CONTENT_EXP_MS, cosString,
+ aArgs.timing().responseStart(), now);
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelResponseStartParentToContent,
+ now - aArgs.timing().responseStart());
+ }
+ if (!mOnStartRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnStartRequestToContent,
+ now - mOnStartRequestStartTime);
+ }
+
+ StoreAllRedirectsSameOrigin(aArgs.allRedirectsSameOrigin());
+
+ mMultiPartID = aArgs.multiPartID();
+ mIsFirstPartOfMultiPart = aArgs.isFirstPartOfMultiPart();
+ mIsLastPartOfMultiPart = aArgs.isLastPartOfMultiPart();
+
+ if (aArgs.overrideReferrerInfo()) {
+ // The arguments passed to SetReferrerInfoInternal here should mirror the
+ // arguments passed in
+ // nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown(), except for
+ // aRespectBeforeConnect which we pass false here since we're intentionally
+ // overriding the referrer after BeginConnect().
+ Unused << SetReferrerInfoInternal(aArgs.overrideReferrerInfo(), false, true,
+ false);
+ }
+
+ if (!aArgs.cookie().IsEmpty()) {
+ SetCookie(aArgs.cookie());
+ }
+
+ if (aArgs.shouldWaitForOnStartRequestSent() &&
+ !mRecvOnStartRequestSentCalled) {
+ LOG((" > pending DoOnStartRequest until RecvOnStartRequestSent\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mEventQ->Suspend();
+ mSuspendedByWaitingForPermissionCookie = true;
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpChannelChild>(this)]() {
+ self->DoOnStartRequest(self);
+ }));
+ return;
+ }
+
+ // Remember whether HTTP3 is supported
+ if (mResponseHead) {
+ mSupportsHTTP3 =
+ nsHttpHandler::IsHttp3SupportedByServer(mResponseHead.get());
+ }
+
+ DoOnStartRequest(this);
+}
+
+void HttpChannelChild::ProcessOnAfterLastPart(const nsresult& aStatus) {
+ LOG(("HttpChannelChild::ProcessOnAfterLastPart [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ self->OnAfterLastPart(aStatus);
+ }));
+}
+
+void HttpChannelChild::OnAfterLastPart(const nsresult& aStatus) {
+ if (LoadOnStopRequestCalled()) {
+ return;
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ // If a preferred alt-data type was set, the parent would hold a reference to
+ // the cache entry in case the child calls openAlternativeOutputStream().
+ // (see nsHttpChannel::OnStopRequest)
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntryAvailable = mCacheEntryAvailable;
+ }
+ mCacheEntryAvailable = false;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ CleanupBackgroundChannel();
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ // If IPDL is already closed, then do nothing.
+ if (CanSend()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(true);
+ }
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest) {
+ nsresult rv;
+
+ LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ // We handle all the listener chaining before OnStartRequest at this moment.
+ // Prevent additional listeners being added to the chain after the request
+ // as started.
+ StoreTracingEnabled(false);
+
+ // mListener could be null if the redirect setup is not completed.
+ MOZ_ASSERT(mListener || LoadOnStartRequestCalled());
+ if (!mListener) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ rv = listener->OnStartRequest(aRequest);
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ StoreOnStartRequestCalled(true);
+
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv, "HttpChannelChild listener->OnStartRequest failed"_ns);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv,
+ "HttpChannelChild DoApplyContentConversions failed"_ns);
+ } else if (listener) {
+ mListener = listener;
+ mCompressListener = listener;
+ }
+}
+
+void HttpChannelChild::ProcessOnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsACString& aData,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ LOG(("HttpChannelChild::ProcessOnTransportAndData [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpChannelChild>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus,
+ aTransportStatus, aOffset, aCount, aData = nsCString(aData),
+ aOnDataAvailableStartTime]() {
+ self->mOnDataAvailableStartTime = aOnDataAvailableStartTime;
+ self->OnTransportAndData(aChannelStatus, aTransportStatus, aOffset,
+ aCount, aData);
+ }));
+}
+
+void HttpChannelChild::OnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsACString& aData) {
+ LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ if (mCanceled || NS_FAILED(mStatus)) {
+ return;
+ }
+
+ if (!mOnDataAvailableStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnDataAvailableToContent,
+ TimeStamp::Now() - mOnDataAvailableStartTime);
+ }
+
+ // Hold queue lock throughout all three calls, else we might process a later
+ // necko msg in between them.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ int64_t progressMax;
+ if (NS_FAILED(GetContentLength(&progressMax))) {
+ progressMax = -1;
+ }
+
+ const int64_t progress = aOffset + aCount;
+
+ // OnTransportAndData will be run on retargeted thread if applicable, however
+ // OnStatus/OnProgress event can only be fired on main thread. We need to
+ // dispatch the status/progress event handling back to main thread with the
+ // appropriate event target for networking.
+ if (NS_IsMainThread()) {
+ DoOnStatus(this, aTransportStatus);
+ DoOnProgress(this, progress, progressMax);
+ } else {
+ RefPtr<HttpChannelChild> self = this;
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpChannelChild::OnTransportAndData",
+ [self, aTransportStatus, progress, progressMax]() {
+ self->DoOnStatus(self, aTransportStatus);
+ self->DoOnProgress(self, progress, progressMax);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // OnDataAvailable
+ //
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv =
+ NS_NewByteInputStream(getter_AddRefs(stringStream),
+ Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv, "HttpChannelChild NS_NewByteInputStream failed"_ns);
+ return;
+ }
+
+ DoOnDataAvailable(this, stringStream, aOffset, aCount);
+ stringStream->Close();
+
+ // TODO: Bug 1523916 backpressure needs to take into account if the data is
+ // coming from the main process or from the socket process via PBackground.
+ if (NeedToReportBytesRead()) {
+ mUnreportBytesRead += aCount;
+ if (mUnreportBytesRead >= gHttpHandler->SendWindowSize() >> 2) {
+ if (NS_IsMainThread()) {
+ Unused << SendBytesRead(mUnreportBytesRead);
+ } else {
+ // PHttpChannel connects to the main thread
+ RefPtr<HttpChannelChild> self = this;
+ int32_t bytesRead = mUnreportBytesRead;
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction("net::HttpChannelChild::SendBytesRead",
+ [self, bytesRead]() {
+ Unused << self->SendBytesRead(bytesRead);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mUnreportBytesRead = 0;
+ }
+ }
+}
+
+bool HttpChannelChild::NeedToReportBytesRead() {
+ if (mCacheNeedToReportBytesReadInitialized) {
+ return mNeedToReportBytesRead;
+ }
+
+ // Might notify parent for partial cache, and the IPC message is ignored by
+ // parent.
+ int64_t contentLength = -1;
+ if (gHttpHandler->SendWindowSize() == 0 || mIsFromCache ||
+ NS_FAILED(GetContentLength(&contentLength)) ||
+ contentLength < gHttpHandler->SendWindowSize()) {
+ mNeedToReportBytesRead = false;
+ }
+
+ mCacheNeedToReportBytesReadInitialized = true;
+ return mNeedToReportBytesRead;
+}
+
+void HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status) {
+ LOG(("HttpChannelChild::DoOnStatus [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending() &&
+ !(mLoadFlags & LOAD_BACKGROUND)) {
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(aRequest, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+}
+
+void HttpChannelChild::DoOnProgress(nsIRequest* aRequest, int64_t progress,
+ int64_t progressMax) {
+ LOG(("HttpChannelChild::DoOnProgress [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending()) {
+ // OnProgress
+ //
+ if (progress > 0) {
+ mProgressSink->OnProgress(aRequest, progress, progressMax);
+ }
+ }
+
+ // mOnProgressEventSent indicates we have flushed all the
+ // progress events on the main thread. It is needed if
+ // we do not want to dispatch OnDataFinished before sending
+ // all of the progress updates.
+ if (progress == progressMax) {
+ mOnProgressEventSent = true;
+ }
+}
+
+void HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoOnDataAvailable", NETWORK);
+ LOG(("HttpChannelChild::DoOnDataAvailable [this=%p]\n", this));
+ if (mCanceled) return;
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ nsresult rv = listener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ CancelOnMainThread(rv, "HttpChannelChild OnDataAvailable failed"_ns);
+ }
+ }
+}
+
+void HttpChannelChild::SendOnDataFinished(const nsresult& aChannelStatus) {
+ LOG(("HttpChannelChild::SendOnDataFinished [this=%p]\n", this));
+
+ if (mCanceled) return;
+
+ // we need to ensure we OnDataFinished only after all the progress
+ // updates are dispatched on the main thread
+ if (StaticPrefs::network_send_OnDataFinished_after_progress_updates() &&
+ !mOnProgressEventSent) {
+ return;
+ }
+
+ if (mListener) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> omtEventListener =
+ do_QueryInterface(mListener);
+ if (omtEventListener) {
+ LOG(
+ ("HttpChannelChild::SendOnDataFinished sending data end "
+ "notification[this=%p]\n",
+ this));
+ // We want to calculate the delta time between this call and
+ // ProcessOnStopRequest. Complicating things is that OnStopRequest
+ // could come first, and that it will run on a different thread, so
+ // we need to synchronize and lock data.
+ omtEventListener->OnDataFinished(aChannelStatus);
+ } else {
+ LOG(
+ ("HttpChannelChild::SendOnDataFinished missing "
+ "nsIThreadRetargetableStreamListener "
+ "implementation [this=%p]\n",
+ this));
+ }
+ }
+}
+
+class RecordStopRequestDelta final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecordStopRequestDelta);
+
+ TimeStamp mOnStopRequestTime;
+ TimeStamp mOnDataFinishedTime;
+
+ private:
+ ~RecordStopRequestDelta() {
+ if (mOnDataFinishedTime.IsNull() || mOnStopRequestTime.IsNull()) {
+ return;
+ }
+
+ TimeDuration delta = (mOnStopRequestTime - mOnDataFinishedTime);
+ if (delta.ToMilliseconds() < 0) {
+ // Because Telemetry can't handle negatives
+ delta = -delta;
+ glean::networking::http_content_ondatafinished_to_onstop_delay_negative
+ .AccumulateRawDuration(delta);
+ } else {
+ glean::networking::http_content_ondatafinished_to_onstop_delay
+ .AccumulateRawDuration(delta);
+ }
+ }
+};
+
+void HttpChannelChild::ProcessOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports, bool aFromSocketProcess,
+ const TimeStamp& aOnStopRequestStartTime) {
+ LOG(
+ ("HttpChannelChild::ProcessOnStopRequest [this=%p, "
+ "aFromSocketProcess=%d]\n",
+ this, aFromSocketProcess));
+ MOZ_ASSERT(OnSocketThread());
+ { // assign some of the members that would be accessed by the listeners
+ // upon getting OnDataFinished notications
+ MutexAutoLock lock(mOnDataFinishedMutex);
+ mTransferSize = aTiming.transferSize();
+ mEncodedBodySize = aTiming.encodedBodySize();
+ }
+
+ RefPtr<RecordStopRequestDelta> timing;
+ TimeStamp start = TimeStamp::Now();
+ if (StaticPrefs::network_send_OnDataFinished()) {
+ timing = new RecordStopRequestDelta;
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpChannelChild>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpChannelChild>(this), status = aChannelStatus,
+ start, timing]() {
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration delay = now - start;
+ glean::networking::http_content_ondatafinished_delay
+ .AccumulateRawDuration(delay);
+ timing->mOnDataFinishedTime = now;
+ self->SendOnDataFinished(status);
+ }));
+ }
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus, aTiming,
+ aResponseTrailers,
+ consoleReports = CopyableTArray{aConsoleReports.Clone()},
+ aFromSocketProcess, start, timing]() mutable {
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration delay = now - start;
+ glean::networking::http_content_onstop_delay.AccumulateRawDuration(
+ delay);
+ if (timing) {
+ timing->mOnStopRequestTime = now;
+ }
+ self->OnStopRequest(aChannelStatus, aTiming, aResponseTrailers);
+ if (!aFromSocketProcess) {
+ self->DoOnConsoleReport(std::move(consoleReports));
+ self->ContinueOnStopRequest();
+ }
+ }));
+}
+
+void HttpChannelChild::ProcessOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ LOG(("HttpChannelChild::ProcessOnConsoleReport [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this),
+ consoleReports = CopyableTArray{aConsoleReports.Clone()}]() mutable {
+ self->DoOnConsoleReport(std::move(consoleReports));
+ self->ContinueOnStopRequest();
+ }));
+}
+
+void HttpChannelChild::DoOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ if (aConsoleReports.IsEmpty()) {
+ return;
+ }
+
+ for (ConsoleReportCollected& report : aConsoleReports) {
+ if (report.propertiesFile() <
+ nsContentUtils::PropertiesFile::PropertiesFile_COUNT) {
+ AddConsoleReport(report.errorFlags(), report.category(),
+ nsContentUtils::PropertiesFile(report.propertiesFile()),
+ report.sourceFileURI(), report.lineNumber(),
+ report.columnNumber(), report.messageName(),
+ report.stringParams());
+ }
+ }
+ MaybeFlushConsoleReports();
+}
+
+void HttpChannelChild::RecordChannelCompletionDurationForEarlyHint() {
+ if (!mLoadGroup) {
+ return;
+ }
+
+ uint32_t earlyHintType = 0;
+ nsCOMPtr<nsIRequest> req;
+ Unused << mLoadGroup->GetDefaultLoadRequest(getter_AddRefs(req));
+ if (nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(req)) {
+ Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
+ }
+
+ if (!earlyHintType) {
+ return;
+ }
+
+ nsAutoCString earlyHintKey;
+ if (mIsFromCache) {
+ earlyHintKey.Append("cache_"_ns);
+ } else {
+ earlyHintKey.Append("net_"_ns);
+ }
+ if (earlyHintType & LinkStyle::ePRECONNECT) {
+ earlyHintKey.Append("preconnect_"_ns);
+ }
+ if (earlyHintType & LinkStyle::ePRELOAD) {
+ earlyHintKey.Append("preload_"_ns);
+ earlyHintKey.Append(mEarlyHintPreloaderId ? "1"_ns : "0"_ns);
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_CHANNEL_COMPLETION_TIME,
+ earlyHintKey, mAsyncOpenTime,
+ TimeStamp::Now());
+}
+
+void HttpChannelChild::OnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers) {
+ LOG(("HttpChannelChild::OnStopRequest [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aChannelStatus)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If this channel was aborted by ActorDestroy, then there may be other
+ // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to
+ // be handled. In that case we just ignore them to avoid calling the listener
+ // twice.
+ if (LoadOnStopRequestCalled() && mIPCActorDeleted) {
+ return;
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ ResourceTimingStructArgsToTimingsStruct(aTiming, mTransactionTimings);
+
+ // Do not overwrite or adjust the original mAsyncOpenTime by timing.fetchStart
+ // We must use the original child process time in order to account for child
+ // side work and IPC transit overhead.
+ // XXX: This depends on TimeStamp being equivalent across processes.
+ // This is true for modern hardware but for older platforms it is not always
+ // true.
+
+ mRedirectStartTimeStamp = aTiming.redirectStart();
+ mRedirectEndTimeStamp = aTiming.redirectEnd();
+ // mTransferSize and mEncodedBodySize are set in ProcessOnStopRequest
+ // TODO: check if we need to move assignments of other members to
+ // ProcessOnStopRequest
+
+ mCacheReadStart = aTiming.cacheReadStart();
+ mCacheReadEnd = aTiming.cacheReadEnd();
+
+ const TimeStamp now = TimeStamp::Now();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, now, mTransferSize, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ RecordChannelCompletionDurationForEarlyHint();
+
+ TimeDuration channelCompletionDuration = now - mAsyncOpenTime;
+ if (mIsFromCache) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion_Cache,
+ channelCompletionDuration);
+ } else {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelCompletion_Network,
+ channelCompletionDuration);
+ }
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion,
+ channelCompletionDuration);
+
+ if (!aTiming.responseEnd().IsNull()) {
+ nsAutoCString cosString;
+ ClassOfService::ToString(mClassOfService, cosString);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_RESPONSE_END_PARENT_TO_CONTENT_MS, cosString,
+ aTiming.responseEnd(), now);
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelResponseEndParentToContent,
+ now - aTiming.responseEnd());
+ }
+
+ if (!mOnStopRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnStopRequestToContent,
+ now - mOnStopRequestStartTime);
+ }
+
+ mResponseTrailers = MakeUnique<nsHttpHeaderArray>(aResponseTrailers);
+
+ DoPreOnStopRequest(aChannelStatus);
+
+ { // We must flush the queue before we Send__delete__
+ // (although we really shouldn't receive any msgs after OnStop),
+ // so make sure this goes out of scope before then.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ DoOnStopRequest(this, aChannelStatus);
+ // DoOnStopRequest() calls ReleaseListeners()
+ }
+}
+
+void HttpChannelChild::ContinueOnStopRequest() {
+ // If we're a multi-part stream, then don't cleanup yet, and we'll do so
+ // in OnAfterLastPart.
+ if (mMultiPartID) {
+ LOG(
+ ("HttpChannelChild::OnStopRequest - Expecting future parts on a "
+ "multipart channel postpone cleaning up."));
+ return;
+ }
+
+ CollectMixedContentTelemetry();
+
+ CleanupBackgroundChannel();
+
+ // If there is a possibility we might want to write alt data to the cache
+ // entry, we keep the channel alive. We still send the DocumentChannelCleanup
+ // message but request the cache entry to be kept by the parent.
+ // If the channel has failed, the cache entry is in a non-writtable state and
+ // we want to release it to not block following consumers.
+ if (NS_SUCCEEDED(mStatus) && !mPreferredCachedAltDataTypes.IsEmpty()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(false); // don't clear cache entry
+ return;
+ }
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ // If IPDL is already closed, then do nothing.
+ if (CanSend()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(true);
+ }
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::DoPreOnStopRequest(nsresult aStatus) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoPreOnStopRequest", NETWORK);
+ LOG(("HttpChannelChild::DoPreOnStopRequest [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatus)));
+ StoreIsPending(false);
+
+ MaybeReportTimingData();
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ CollectOMTTelemetry();
+}
+
+void HttpChannelChild::CollectOMTTelemetry() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Only collect telemetry for HTTP channel that is loaded successfully and
+ // completely.
+ if (mCanceled || NS_FAILED(mStatus)) {
+ return;
+ }
+
+ // Use content policy type to accumulate data by usage.
+ nsAutoCString key(
+ NS_CP_ContentTypeName(mLoadInfo->InternalContentPolicyType()));
+
+ Telemetry::AccumulateCategoricalKeyed(
+ key, static_cast<LABELS_HTTP_CHILD_OMT_STATS>(mOMTResult));
+}
+
+void HttpChannelChild::CollectMixedContentTelemetry() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsContentPolicyType internalLoadType;
+ mLoadInfo->GetInternalContentPolicyType(&internalLoadType);
+ bool statusIsSuccess = NS_SUCCEEDED(mStatus);
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ if (!doc) {
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE ||
+ internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedImageSuccess
+ : eUseCounter_custom_MixedContentUpgradedImageFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedImageSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedImageFailure);
+ }
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_VIDEO) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedVideoSuccess
+ : eUseCounter_custom_MixedContentUpgradedVideoFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedVideoSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedVideoFailure);
+ }
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_AUDIO) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedAudioSuccess
+ : eUseCounter_custom_MixedContentUpgradedAudioFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedAudioSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedAudioFailure);
+ }
+ }
+}
+
+void HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest,
+ nsresult aChannelStatus) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoOnStopRequest", NETWORK);
+ LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadIsPending());
+
+ auto checkForBlockedContent = [&]() {
+ // NB: We use aChannelStatus here instead of mStatus because if there was an
+ // nsCORSListenerProxy on this request, it will override the tracking
+ // protection's return value.
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ aChannelStatus) ||
+ aChannelStatus == NS_ERROR_MALWARE_URI ||
+ aChannelStatus == NS_ERROR_UNWANTED_URI ||
+ aChannelStatus == NS_ERROR_BLOCKED_URI ||
+ aChannelStatus == NS_ERROR_HARMFUL_URI ||
+ aChannelStatus == NS_ERROR_PHISHING_URI) {
+ nsCString list, provider, fullhash;
+
+ nsresult rv = GetMatchedList(list);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = GetMatchedProvider(provider);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = GetMatchedFullHash(fullhash);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ UrlClassifierCommon::SetBlockedContent(this, aChannelStatus, list,
+ provider, fullhash);
+ }
+ };
+ checkForBlockedContent();
+
+ MaybeLogCOEPError(aChannelStatus);
+
+ // See bug 1587686. If the redirect setup is not completed, the post-redirect
+ // channel will be not opened and mListener will be null.
+ MOZ_ASSERT(mListener || !LoadWasOpened());
+ if (!mListener) {
+ return;
+ }
+
+ MOZ_ASSERT(!LoadOnStopRequestCalled(),
+ "We should not call OnStopRequest twice");
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(aRequest, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // If we're a multi-part stream, then don't cleanup yet, and we'll do so
+ // in OnAfterLastPart.
+ if (mMultiPartID) {
+ LOG(
+ ("HttpChannelChild::DoOnStopRequest - Expecting future parts on a "
+ "multipart channel not releasing listeners."));
+ StoreOnStopRequestCalled(false);
+ StoreOnStartRequestCalled(false);
+ return;
+ }
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ // If a preferred alt-data type was set, the parent would hold a reference to
+ // the cache entry in case the child calls openAlternativeOutputStream().
+ // (see nsHttpChannel::OnStopRequest)
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntryAvailable = mCacheEntryAvailable;
+ }
+ mCacheEntryAvailable = false;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+void HttpChannelChild::ProcessOnProgress(const int64_t& aProgress,
+ const int64_t& aProgressMax) {
+ MOZ_ASSERT(OnSocketThread());
+ LOG(("HttpChannelChild::ProcessOnProgress [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this), aProgress, aProgressMax]() {
+ AutoEventEnqueuer ensureSerialDispatch(self->mEventQ);
+ self->DoOnProgress(self, aProgress, aProgressMax);
+ }));
+}
+
+void HttpChannelChild::ProcessOnStatus(const nsresult& aStatus) {
+ MOZ_ASSERT(OnSocketThread());
+ LOG(("HttpChannelChild::ProcessOnStatus [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ AutoEventEnqueuer ensureSerialDispatch(self->mEventQ);
+ self->DoOnStatus(self, aStatus);
+ }));
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatus) {
+ LOG(("HttpChannelChild::RecvFailedAsyncOpen [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ self->FailedAsyncOpen(aStatus);
+ }));
+ return IPC_OK();
+}
+
+// We need to have an implementation of this function just so that we can keep
+// all references to mCallOnResume of type HttpChannelChild: it's not OK in C++
+// to set a member function ptr to a base class function.
+void HttpChannelChild::HandleAsyncAbort() {
+ HttpAsyncAborter<HttpChannelChild>::HandleAsyncAbort();
+
+ // Ignore all the messages from background channel after channel aborted.
+ CleanupBackgroundChannel();
+}
+
+void HttpChannelChild::FailedAsyncOpen(const nsresult& status) {
+ LOG(("HttpChannelChild::FailedAsyncOpen [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Might be called twice in race condition in theory.
+ // (one by RecvFailedAsyncOpen, another by
+ // HttpBackgroundChannelChild::ActorFailed)
+ if (LoadOnStartRequestCalled()) {
+ return;
+ }
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = status;
+ }
+
+ // We're already being called from IPDL, therefore already "async"
+ HandleAsyncAbort();
+
+ if (CanSend()) {
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::CleanupBackgroundChannel() {
+ MutexAutoLock lock(mBgChildMutex);
+
+ AUTO_PROFILER_LABEL("HttpChannelChild::CleanupBackgroundChannel", NETWORK);
+ LOG(("HttpChannelChild::CleanupBackgroundChannel [this=%p bgChild=%p]\n",
+ this, mBgChild.get()));
+
+ mBgInitFailCallback = nullptr;
+
+ if (!mBgChild) {
+ return;
+ }
+
+ RefPtr<HttpBackgroundChannelChild> bgChild = std::move(mBgChild);
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+ if (!OnSocketThread()) {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed",
+ bgChild,
+ &HttpBackgroundChannelChild::OnChannelClosed),
+ NS_DISPATCH_NORMAL);
+ } else {
+ bgChild->OnChannelClosed();
+ }
+}
+
+void HttpChannelChild::DoNotifyListenerCleanup() {
+ LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
+}
+
+void HttpChannelChild::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvDeleteSelf() {
+ LOG(("HttpChannelChild::RecvDeleteSelf [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The redirection is vetoed. No need to suspend the event queue.
+ if (mSuspendForWaitCompleteRedirectSetup) {
+ mSuspendForWaitCompleteRedirectSetup = false;
+ mEventQ->Resume();
+ }
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this)]() { self->DeleteSelf(); }));
+ return IPC_OK();
+}
+
+void HttpChannelChild::DeleteSelf() { Send__delete__(this); }
+
+void HttpChannelChild::NotifyOrReleaseListeners(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(rv) ||
+ (LoadOnStartRequestCalled() && LoadOnStopRequestCalled())) {
+ ReleaseListeners();
+ return;
+ }
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+
+ // This is enough what we need. Undelivered notifications will be pushed.
+ // DoNotifyListener ensures the call to ReleaseListeners when done.
+ DoNotifyListener();
+}
+
+void HttpChannelChild::DoNotifyListener(bool aUseEventQueue) {
+ LOG(("HttpChannelChild::DoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set LoadAfterOnStartRequestBegun() to
+ // true before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ // avoid reentrancy bugs by setting this now
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ if (aUseEventQueue) {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this)] {
+ self->ContinueDoNotifyListener();
+ }));
+ } else {
+ ContinueDoNotifyListener();
+ }
+}
+
+void HttpChannelChild::ContinueDoNotifyListener() {
+ LOG(("HttpChannelChild::ContinueDoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-request" observers
+ gHttpHandler->OnStopRequest(this);
+
+ // This channel has finished its job, potentially release any tail-blocked
+ // requests with this.
+ RemoveAsNonTailRequest();
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+
+ // If this is a navigation, then we must let the docshell flush the reports
+ // to the console later. The LoadDocument() is pointing at the detached
+ // document that started the navigation. We want to show the reports on the
+ // new document. Otherwise the console is wiped and the user never sees
+ // the information.
+ if (!IsNavigation()) {
+ if (mLoadGroup) {
+ FlushConsoleReports(mLoadGroup);
+ } else {
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ FlushConsoleReports(doc);
+ }
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvReportSecurityMessage(
+ const nsAString& messageTag, const nsAString& messageCategory) {
+ DebugOnly<nsresult> rv = AddSecurityMessage(messageTag, messageCategory);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect1Begin(
+ const uint32_t& aRegistrarId, nsIURI* aNewUri,
+ const uint32_t& aNewLoadFlags, const uint32_t& aRedirectFlags,
+ const ParentLoadInfoForwarderArgs& aLoadInfoForwarder,
+ const nsHttpResponseHead& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const uint64_t& aChannelId,
+ const NetAddr& aOldPeerAddr, const ResourceTimingStructArgs& aTiming) {
+ // TODO: handle security info
+ LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this));
+ // We set peer address of child to the old peer,
+ // Then it will be updated to new peer in OnStartRequest
+ mPeerAddr = aOldPeerAddr;
+
+ // Cookies headers should not be visible to the child process
+ MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aRegistrarId,
+ newUri = RefPtr{aNewUri}, aNewLoadFlags, aRedirectFlags,
+ aLoadInfoForwarder, aResponseHead,
+ aSecurityInfo = nsCOMPtr{aSecurityInfo}, aChannelId, aTiming]() {
+ self->Redirect1Begin(aRegistrarId, newUri, aNewLoadFlags,
+ aRedirectFlags, aLoadInfoForwarder, aResponseHead,
+ aSecurityInfo, aChannelId, aTiming);
+ }));
+ return IPC_OK();
+}
+
+nsresult HttpChannelChild::SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel) {
+ LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this));
+
+ if (mCanceled) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(uri, redirectFlags);
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), uri, redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We won't get OnStartRequest, set cookies here.
+ mResponseHead = MakeUnique<nsHttpResponseHead>(*responseHead);
+
+ bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(
+ mResponseHead->Status(), mRequestHead.ParsedMethod());
+
+ rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannelChild = do_QueryInterface(newChannel);
+ newChannel.forget(outChannel);
+
+ return NS_OK;
+}
+
+void HttpChannelChild::Redirect1Begin(
+ const uint32_t& registrarId, nsIURI* newOriginalURI,
+ const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ nsITransportSecurityInfo* securityInfo, const uint64_t& channelId,
+ const ResourceTimingStructArgs& timing) {
+ nsresult rv;
+
+ LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
+
+ MOZ_ASSERT(newOriginalURI, "newOriginalURI should not be null");
+
+ ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo);
+ ResourceTimingStructArgsToTimingsStruct(timing, mTransactionTimings);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+ nsAutoCString contentType;
+ responseHead.ContentType(contentType);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ 0, kCacheUnknown, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())), newOriginalURI,
+ redirectFlags, channelId);
+ }
+
+ mSecurityInfo = securityInfo;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = SetupRedirect(newOriginalURI, &responseHead, redirectFlags,
+ getter_AddRefs(newChannel));
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ALWAYS_SUCCEEDS(newChannel->SetLoadFlags(newLoadFlags));
+
+ if (mRedirectChannelChild) {
+ // Set the channelId allocated in parent to the child instance
+ nsCOMPtr<nsIHttpChannel> httpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+ if (httpChannel) {
+ rv = httpChannel->SetChannelId(channelId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mRedirectChannelChild->ConnectParent(registrarId);
+ }
+
+ nsCOMPtr<nsISerialEventTarget> target = GetNeckoTarget();
+ MOZ_ASSERT(target);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags,
+ target);
+ }
+
+ if (NS_FAILED(rv)) OnRedirectVerifyCallback(rv);
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect3Complete() {
+ LOG(("HttpChannelChild::RecvRedirect3Complete [this=%p]\n", this));
+ nsCOMPtr<nsIChannel> redirectChannel =
+ do_QueryInterface(mRedirectChannelChild);
+ MOZ_ASSERT(redirectChannel);
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), redirectChannel]() {
+ nsresult rv = NS_OK;
+ Unused << self->GetStatus(&rv);
+ if (NS_FAILED(rv)) {
+ // Pre-redirect channel was canceled. Call |HandleAsyncAbort|, so
+ // mListener's OnStart/StopRequest can be called. Nothing else will
+ // trigger these notification after this point.
+ // We do this before |CompleteRedirectSetup|, so post-redirect channel
+ // stays unopened and we also make sure that OnStart/StopRequest won't
+ // be called twice.
+ self->HandleAsyncAbort();
+
+ nsCOMPtr<nsIHttpChannelChild> chan =
+ do_QueryInterface(redirectChannel);
+ RefPtr<HttpChannelChild> httpChannelChild =
+ static_cast<HttpChannelChild*>(chan.get());
+ if (httpChannelChild) {
+ // For sending an IPC message to parent channel so that the loading
+ // can be cancelled.
+ Unused << httpChannelChild->CancelWithReason(
+ rv, "HttpChannelChild Redirect3 failed"_ns);
+
+ // The post-redirect channel could still get OnStart/StopRequest IPC
+ // messages from parent, but the mListener is still null. So, we
+ // call |DoNotifyListener| to pretend that OnStart/StopRequest are
+ // already called.
+ httpChannelChild->DoNotifyListener();
+ }
+ return;
+ }
+
+ self->Redirect3Complete();
+ }));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirectFailed(
+ const nsresult& status) {
+ LOG(("HttpChannelChild::RecvRedirectFailed this=%p status=%X\n", this,
+ static_cast<uint32_t>(status)));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), status]() {
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ self->GetCallback(vetoHook);
+ if (vetoHook) {
+ vetoHook->OnRedirectResult(status);
+ }
+
+ if (RefPtr<HttpChannelChild> httpChannelChild =
+ do_QueryObject(self->mRedirectChannelChild)) {
+ // For sending an IPC message to parent channel so that the loading
+ // can be cancelled.
+ Unused << httpChannelChild->CancelWithReason(
+ status, "HttpChannelChild RecvRedirectFailed"_ns);
+
+ // The post-redirect channel could still get OnStart/StopRequest IPC
+ // messages from parent, but the mListener is still null. So, we
+ // call |DoNotifyListener| to pretend that OnStart/StopRequest are
+ // already called.
+ httpChannelChild->DoNotifyListener();
+ }
+ }));
+
+ return IPC_OK();
+}
+
+void HttpChannelChild::ProcessNotifyClassificationFlags(
+ uint32_t aClassificationFlags, bool aIsThirdParty) {
+ LOG(
+ ("HttpChannelChild::ProcessNotifyClassificationFlags thirdparty=%d "
+ "flags=%" PRIu32 " [this=%p]\n",
+ static_cast<int>(aIsThirdParty), aClassificationFlags, this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aClassificationFlags,
+ aIsThirdParty]() {
+ self->AddClassificationFlags(aClassificationFlags, aIsThirdParty);
+ }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedInfo(
+ const nsACString& aList, const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ LOG(("HttpChannelChild::ProcessSetClassifierMatchedInfo [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this), aList = nsCString(aList),
+ aProvider = nsCString(aProvider), aFullHash = nsCString(aFullHash)]() {
+ self->SetMatchedInfo(aList, aProvider, aFullHash);
+ }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ LOG(("HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ nsTArray<nsCString> lists, fullhashes;
+ for (const nsACString& token : aLists.Split(',')) {
+ lists.AppendElement(token);
+ }
+ for (const nsACString& token : aFullHashes.Split(',')) {
+ fullhashes.AppendElement(token);
+ }
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this),
+ lists = CopyableTArray{std::move(lists)},
+ fullhashes = CopyableTArray{std::move(fullhashes)}]() {
+ self->SetMatchedTrackingInfo(lists, fullhashes);
+ }));
+}
+
+// Completes the redirect and cleans up the old channel.
+void HttpChannelChild::Redirect3Complete() {
+ LOG(("HttpChannelChild::Redirect3Complete [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Using an error as the default so that when we fail to forward this redirect
+ // to the target channel, we make sure to notify the current listener from
+ // CleanupRedirectingChannel.
+ nsresult rv = NS_BINDING_ABORTED;
+
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ GetCallback(vetoHook);
+ if (vetoHook) {
+ vetoHook->OnRedirectResult(NS_OK);
+ }
+
+ // Chrome channel has been AsyncOpen'd. Reflect this in child.
+ if (mRedirectChannelChild) {
+ rv = mRedirectChannelChild->CompleteRedirectSetup(mListener);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mSuccesfullyRedirected = NS_SUCCEEDED(rv);
+#endif
+ }
+
+ CleanupRedirectingChannel(rv);
+}
+
+void HttpChannelChild::CleanupRedirectingChannel(nsresult rv) {
+ // Redirecting to new channel: shut this down and init new channel
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_ABORTED);
+
+ if (NS_SUCCEEDED(rv)) {
+ mLoadInfo->AppendRedirectHistoryEntry(this, false);
+ } else {
+ NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?");
+ }
+
+ // Release ref to new channel.
+ mRedirectChannelChild = nullptr;
+
+ NotifyOrReleaseListeners(rv);
+ CleanupBackgroundChannel();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ConnectParent(uint32_t registrarId) {
+ LOG(("HttpChannelChild::ConnectParent [this=%p, id=%" PRIu32 "]\n", this,
+ registrarId));
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ if (browserChild && !browserChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ // This must happen before the constructor message is sent. Otherwise messages
+ // from the parent could arrive quickly and be delivered to the wrong event
+ // target.
+ SetEventTarget();
+
+ if (browserChild) {
+ MOZ_ASSERT(browserChild->WebNavigation());
+ if (BrowsingContext* bc = browserChild->GetBrowsingContext()) {
+ mBrowserId = bc->BrowserId();
+ }
+ }
+
+ HttpChannelConnectArgs connectArgs(registrarId);
+ if (!gNeckoChild->SendPHttpChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ MOZ_ASSERT(!mBgChild);
+ MOZ_ASSERT(!mBgInitFailCallback);
+
+ mBgInitFailCallback = NewRunnableMethod<nsresult>(
+ "HttpChannelChild::OnRedirectVerifyCallback", this,
+ &HttpChannelChild::OnRedirectVerifyCallback, NS_ERROR_FAILURE);
+
+ RefPtr<HttpBackgroundChannelChild> bgChild =
+ new HttpBackgroundChannelChild();
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+
+ RefPtr<HttpChannelChild> self = this;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<HttpChannelChild>>(
+ "HttpBackgroundChannelChild::Init", bgChild,
+ &HttpBackgroundChannelChild::Init, std::move(self)),
+ NS_DISPATCH_NORMAL);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mBgChild = std::move(bgChild);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mEverHadBgChildAtConnectParent = true;
+#endif
+ }
+
+ // Should wait for CompleteRedirectSetup to set the listener.
+ mEventQ->Suspend();
+ MOZ_ASSERT(!mSuspendForWaitCompleteRedirectSetup);
+ mSuspendForWaitCompleteRedirectSetup = true;
+
+ // Connect to socket process after mEventQ is suspended.
+ MaybeConnectToSocketProcess();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ LOG(("HttpChannelChild::CompleteRedirectSetup [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ // Resume the suspension in ConnectParent.
+ auto eventQueueResumeGuard = MakeScopeExit([&] {
+ MOZ_ASSERT(mSuspendForWaitCompleteRedirectSetup);
+ mEventQ->Resume();
+ mSuspendForWaitCompleteRedirectSetup = false;
+ });
+
+ /*
+ * No need to check for cancel: we don't get here if nsHttpChannel canceled
+ * before AsyncOpen(); if it's canceled after that, OnStart/Stop will just
+ * get called with error code as usual. So just setup mListener and make the
+ * channel reflect AsyncOpen'ed state.
+ */
+
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+ StoreIsPending(true);
+ StoreWasOpened(true);
+ mListener = aListener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ // We already have an open IPDL connection to the parent. If on-modify-request
+ // listeners or load group observers canceled us, let the parent handle it
+ // and send it back to us naturally.
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIAsyncVerifyRedirectCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) {
+ LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIURI> redirectURI;
+
+ DebugOnly<nsresult> rv = NS_OK;
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+
+ if (NS_SUCCEEDED(aResult) && !mRedirectChannelChild) {
+ // mRedirectChannelChild doesn't exist means we're redirecting to a protocol
+ // that doesn't implement nsIChildChannel. The redirect result should be set
+ // as failed by veto listeners and shouldn't enter this condition. As the
+ // last resort, we synthesize the error result as NS_ERROR_DOM_BAD_URI here
+ // to let nsHttpChannel::ContinueProcessResponse2 know it's redirecting to
+ // another protocol and throw an error.
+ LOG((" redirecting to a protocol that doesn't implement nsIChildChannel"));
+ aResult = NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (newHttpChannel) {
+ // Must not be called until after redirect observers called.
+ newHttpChannel->SetOriginalURI(mOriginalURI);
+ referrerInfo = newHttpChannel->GetReferrerInfo();
+ }
+
+ RequestHeaderTuples emptyHeaders;
+ RequestHeaderTuples* headerTuples = &emptyHeaders;
+ nsLoadFlags loadFlags = 0;
+ Maybe<CorsPreflightArgs> corsPreflightArgs;
+
+ nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelChild && NS_SUCCEEDED(aResult)) {
+ rv = newHttpChannelChild->AddCookiesToRequest();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ newHttpChannelChild->GetClientSetCorsPreflightParameters(corsPreflightArgs);
+ }
+
+ if (NS_SUCCEEDED(aResult)) {
+ // Note: this is where we would notify "http-on-modify-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // After we verify redirect, nsHttpChannel may hit the network: must give
+ // "http-on-modify-request" observers the chance to cancel before that.
+ // base->CallOnModifyRequestObservers();
+
+ nsCOMPtr<nsIHttpChannelInternal> newHttpChannelInternal =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelInternal) {
+ Unused << newHttpChannelInternal->GetApiRedirectToURI(
+ getter_AddRefs(redirectURI));
+ }
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mRedirectChannelChild);
+ if (request) {
+ request->GetLoadFlags(&loadFlags);
+ }
+ }
+
+ uint32_t sourceRequestBlockingReason = 0;
+ mLoadInfo->GetRequestBlockingReason(&sourceRequestBlockingReason);
+
+ Maybe<ChildLoadInfoForwarderArgs> targetLoadInfoForwarder;
+ nsCOMPtr<nsIChannel> newChannel = do_QueryInterface(mRedirectChannelChild);
+ if (newChannel) {
+ ChildLoadInfoForwarderArgs args;
+ nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
+ LoadInfoToChildLoadInfoForwarder(loadInfo, &args);
+ targetLoadInfoForwarder.emplace(args);
+ }
+
+ if (CanSend()) {
+ SendRedirect2Verify(aResult, *headerTuples, sourceRequestBlockingReason,
+ targetLoadInfoForwarder, loadFlags, referrerInfo,
+ redirectURI, corsPreflightArgs);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP HttpChannelChild::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP HttpChannelChild::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Cancel(nsresult aStatus) {
+ LOG(("HttpChannelChild::Cancel [this=%p, status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+ // only logging on parent is necessary
+ Maybe<nsCString> logStack = CallingScriptLocationString();
+ Maybe<nsCString> logOnParent;
+ if (logStack.isSome()) {
+ logOnParent = Some(""_ns);
+ logOnParent->AppendPrintf(
+ "[this=%p] cancelled call in child process from script: %s", this,
+ logStack->get());
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCanceled) {
+ // If this cancel occurs before nsHttpChannel has been set up, AsyncOpen
+ // is responsible for cleaning up.
+ mCanceled = true;
+ mStatus = aStatus;
+
+ bool remoteChannelExists = RemoteChannelExists();
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mCanSendAtCancel = CanSend();
+ mRemoteChannelExistedAtCancel = remoteChannelExists;
+#endif
+
+ if (remoteChannelExists) {
+ SendCancel(aStatus, mLoadInfo->GetRequestBlockingReason(),
+ mCanceledReason, logOnParent);
+ } else if (MOZ_UNLIKELY(!LoadOnStartRequestCalled() ||
+ !LoadOnStopRequestCalled())) {
+ Unused << AsyncAbort(mStatus);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Suspend() {
+ LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%" PRIu32 "\n", this,
+ mSuspendCount + 1));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LogCallingScriptLocation(this);
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ // Don't SendSuspend at all if we're diverting callbacks to the parent;
+ // suspend will be called at the correct time in the parent itself.
+ if (!mSuspendCount++) {
+ if (RemoteChannelExists()) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Resume() {
+ LOG(("HttpChannelChild::Resume [this=%p, mSuspendCount=%" PRIu32 "\n", this,
+ mSuspendCount - 1));
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ LogCallingScriptLocation(this);
+
+ nsresult rv = NS_OK;
+
+ // SendResume only once, when suspend count drops to 0.
+ // Don't SendResume at all if we're diverting callbacks to the parent (unless
+ // suspend was sent earlier); otherwise, resume will be called at the correct
+ // time in the parent itself.
+ if (!--mSuspendCount) {
+ if (RemoteChannelExists() && mSuspendSent) {
+ SendResume();
+ }
+ if (mCallOnResume) {
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<HttpChannelChild> self = this;
+ std::function<nsresult(HttpChannelChild*)> callOnResume = nullptr;
+ std::swap(callOnResume, mCallOnResume);
+ rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpChannelChild::mCallOnResume",
+ [callOnResume, self{std::move(self)}]() { callOnResume(self); }),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ mEventQ->Resume();
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+ *aSecurityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AsyncOpen(nsIStreamListener* aListener) {
+ LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get()));
+
+ nsresult rv = AsyncOpenInternal(aListener);
+ if (NS_FAILED(rv)) {
+ uint32_t blockingReason = 0;
+ mLoadInfo->GetRequestBlockingReason(&blockingReason);
+ LOG(
+ ("HttpChannelChild::AsyncOpen failed [this=%p rv=0x%08x "
+ "blocking-reason=%u]\n",
+ this, static_cast<uint32_t>(rv), blockingReason));
+
+ gHttpHandler->OnFailedOpeningRequest(this);
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mAsyncOpenSucceeded = NS_SUCCEEDED(rv);
+#endif
+ return rv;
+}
+
+nsresult HttpChannelChild::AsyncOpenInternal(nsIStreamListener* aListener) {
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ LogCallingScriptLocation(this);
+
+ if (!mLoadGroup && !mCallbacks) {
+ // If no one called SetLoadGroup or SetNotificationCallbacks, the private
+ // state has not been updated on PrivateBrowsingChannel (which we derive
+ // from) Hence, we have to call UpdatePrivateBrowsing() here
+ UpdatePrivateBrowsing();
+ }
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ if (mCanceled) {
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
+ return NS_OK;
+ }
+
+ if (!LoadAsyncOpenTimeOverriden()) {
+ mAsyncOpenTime = TimeStamp::Now();
+ }
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ nsAutoCString cookie;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) {
+ mUserSetCookieHeader = cookie;
+ }
+
+ DebugOnly<nsresult> check = AddCookiesToRequest();
+ MOZ_ASSERT(NS_SUCCEEDED(check));
+
+ //
+ // NOTE: From now on we must return NS_OK; all errors must be handled via
+ // OnStart/OnStopRequest
+ //
+
+ // We notify "http-on-opening-request" observers in the child
+ // process so that devtools can capture a stack trace at the
+ // appropriate spot. See bug 806753 for some information about why
+ // other http-* notifications are disabled in child processes.
+ gHttpHandler->OnOpeningRequest(this);
+
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+ StoreIsPending(true);
+ StoreWasOpened(true);
+ mListener = listener;
+
+ if (mCanceled) {
+ // We may have been canceled already, either by on-modify-request
+ // listeners or by load group observers; in that case, don't create IPDL
+ // connection. See nsHttpChannel::AsyncOpen().
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ // Set user agent override from docshell
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ rv = ContinueAsyncOpen();
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ }
+ return rv;
+}
+
+// Assigns an nsISerialEventTarget to our IPDL actor so that IPC messages are
+// sent to the correct DocGroup/TabGroup.
+void HttpChannelChild::SetEventTarget() {
+ MutexAutoLock lock(mEventTargetMutex);
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+already_AddRefed<nsISerialEventTarget> HttpChannelChild::GetNeckoTarget() {
+ nsCOMPtr<nsISerialEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ target = mNeckoTarget;
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+already_AddRefed<nsIEventTarget> HttpChannelChild::GetODATarget() {
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ if (mODATarget) {
+ target = mODATarget;
+ } else {
+ target = mNeckoTarget;
+ }
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+nsresult HttpChannelChild::ContinueAsyncOpen() {
+ nsresult rv;
+ //
+ // Send request to the chrome process...
+ //
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ // This id identifies the inner window's top-level document,
+ // which changes on every new load or navigation.
+ uint64_t contentWindowId = 0;
+ TimeStamp navigationStartTimeStamp;
+ if (browserChild) {
+ MOZ_ASSERT(browserChild->WebNavigation());
+ if (RefPtr<Document> document = browserChild->GetTopLevelDocument()) {
+ contentWindowId = document->InnerWindowID();
+ nsDOMNavigationTiming* navigationTiming = document->GetNavigationTiming();
+ if (navigationTiming) {
+ navigationStartTimeStamp =
+ navigationTiming->GetNavigationStartTimeStamp();
+ }
+ }
+ if (BrowsingContext* bc = browserChild->GetBrowsingContext()) {
+ mBrowserId = bc->BrowserId();
+ }
+ }
+ SetTopLevelContentWindowId(contentWindowId);
+
+ if (browserChild && !browserChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // add ourselves to the load group.
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ HttpChannelOpenArgs openArgs;
+ // No access to HttpChannelOpenArgs members, but they each have a
+ // function with the struct name that returns a ref.
+ openArgs.uri() = mURI;
+ openArgs.original() = mOriginalURI;
+ openArgs.doc() = mDocumentURI;
+ openArgs.apiRedirectTo() = mAPIRedirectToURI;
+ openArgs.loadFlags() = mLoadFlags;
+ openArgs.requestHeaders() = mClientSetRequestHeaders;
+ mRequestHead.Method(openArgs.requestMethod());
+ openArgs.preferredAlternativeTypes() = mPreferredCachedAltDataTypes.Clone();
+ openArgs.referrerInfo() = mReferrerInfo;
+
+ if (mUploadStream) {
+ MOZ_ALWAYS_TRUE(SerializeIPCStream(do_AddRef(mUploadStream),
+ openArgs.uploadStream(),
+ /* aAllowLazy */ false));
+ }
+
+ Maybe<CorsPreflightArgs> optionalCorsPreflightArgs;
+ GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs);
+
+ // NB: This call forces us to cache mTopWindowURI if we haven't already.
+ nsCOMPtr<nsIURI> uri;
+ GetTopWindowURI(mURI, getter_AddRefs(uri));
+
+ openArgs.topWindowURI() = mTopWindowURI;
+
+ openArgs.preflightArgs() = optionalCorsPreflightArgs;
+
+ openArgs.uploadStreamHasHeaders() = LoadUploadStreamHasHeaders();
+ openArgs.priority() = mPriority;
+ openArgs.classOfService() = mClassOfService;
+ openArgs.redirectionLimit() = mRedirectionLimit;
+ openArgs.allowSTS() = LoadAllowSTS();
+ openArgs.thirdPartyFlags() = LoadThirdPartyFlags();
+ openArgs.resumeAt() = mSendResumeAt;
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.allowSpdy() = LoadAllowSpdy();
+ openArgs.allowHttp3() = LoadAllowHttp3();
+ openArgs.allowAltSvc() = LoadAllowAltSvc();
+ openArgs.beConservative() = LoadBeConservative();
+ openArgs.bypassProxy() = BypassProxy();
+ openArgs.tlsFlags() = mTlsFlags;
+ openArgs.initialRwin() = mInitialRwin;
+
+ openArgs.cacheKey() = mCacheKey;
+
+ openArgs.blockAuthPrompt() = LoadBlockAuthPrompt();
+
+ openArgs.allowStaleCacheContent() = LoadAllowStaleCacheContent();
+ openArgs.preferCacheLoadOverBypass() = LoadPreferCacheLoadOverBypass();
+
+ openArgs.contentTypeHint() = mContentTypeHint;
+
+ rv = mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ EnsureRequestContextID();
+ openArgs.requestContextID() = mRequestContextID;
+
+ openArgs.requestMode() = mRequestMode;
+ openArgs.redirectMode() = mRedirectMode;
+
+ openArgs.channelId() = mChannelId;
+
+ openArgs.integrityMetadata() = mIntegrityMetadata;
+
+ openArgs.contentWindowId() = contentWindowId;
+ openArgs.browserId() = mBrowserId;
+
+ LOG(("HttpChannelChild::ContinueAsyncOpen this=%p gid=%" PRIu64
+ " browser id=%" PRIx64,
+ this, mChannelId, mBrowserId));
+
+ openArgs.launchServiceWorkerStart() = mLaunchServiceWorkerStart;
+ openArgs.launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
+ openArgs.dispatchFetchEventStart() = mDispatchFetchEventStart;
+ openArgs.dispatchFetchEventEnd() = mDispatchFetchEventEnd;
+ openArgs.handleFetchEventStart() = mHandleFetchEventStart;
+ openArgs.handleFetchEventEnd() = mHandleFetchEventEnd;
+
+ openArgs.forceMainDocumentChannel() = LoadForceMainDocumentChannel();
+
+ openArgs.navigationStartTimeStamp() = navigationStartTimeStamp;
+ openArgs.earlyHintPreloaderId() = mEarlyHintPreloaderId;
+
+ openArgs.classicScriptHintCharset() = mClassicScriptHintCharset;
+
+ openArgs.isUserAgentHeaderModified() = LoadIsUserAgentHeaderModified();
+
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ if (doc) {
+ nsAutoString documentCharacterSet;
+ doc->GetCharacterSet(documentCharacterSet);
+ openArgs.documentCharacterSet() = documentCharacterSet;
+ }
+
+ // This must happen before the constructor message is sent. Otherwise messages
+ // from the parent could arrive quickly and be delivered to the wrong event
+ // target.
+ SetEventTarget();
+
+ if (!gNeckoChild->SendPHttpChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), openArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+
+ // Service worker might use the same HttpChannelChild to do async open
+ // twice. Need to disconnect with previous background channel before
+ // creating the new one, to prevent receiving further notification
+ // from it.
+ if (mBgChild) {
+ RefPtr<HttpBackgroundChannelChild> prevBgChild = std::move(mBgChild);
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed",
+ prevBgChild,
+ &HttpBackgroundChannelChild::OnChannelClosed),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(!mBgInitFailCallback);
+
+ mBgInitFailCallback = NewRunnableMethod<nsresult>(
+ "HttpChannelChild::FailedAsyncOpen", this,
+ &HttpChannelChild::FailedAsyncOpen, NS_ERROR_FAILURE);
+
+ RefPtr<HttpBackgroundChannelChild> bgChild =
+ new HttpBackgroundChannelChild();
+
+ RefPtr<HttpChannelChild> self = this;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<HttpChannelChild>>(
+ "HttpBackgroundChannelChild::Init", bgChild,
+ &HttpBackgroundChannelChild::Init, self),
+ NS_DISPATCH_NORMAL);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mBgChild = std::move(bgChild);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mEverHadBgChildAtAsyncOpen = true;
+#endif
+ }
+
+ MaybeConnectToSocketProcess();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ LOG(("HttpChannelChild::SetRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetRequestHeader(aHeader, aValue, aMerge);
+ if (NS_FAILED(rv)) return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ tuple->mHeader = aHeader;
+ tuple->mValue = aValue;
+ tuple->mMerge = aMerge;
+ tuple->mEmpty = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetEmptyRequestHeader(const nsACString& aHeader) {
+ LOG(("HttpChannelChild::SetEmptyRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetEmptyRequestHeader(aHeader);
+ if (NS_FAILED(rv)) return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ tuple->mHeader = aHeader;
+ tuple->mMerge = false;
+ tuple->mEmpty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RedirectTo(nsIURI* newURI) {
+ // disabled until/unless addons run in child or something else needs this
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UpgradeToSecure() {
+ // disabled until/unless addons run in child or something else needs this
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetProtocolVersion(nsACString& aProtocolVersion) {
+ aProtocolVersion = mProtocolVersion;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsAuthChannel(bool* aIsAuthChannel) { DROP_DEAD(); }
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCacheEntryAvailable && !mAltDataCacheEntryAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *_retval = mCacheFetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCacheEntryAvailable) return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = mCacheExpirationTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsFromCache(bool* value) {
+ if (!LoadIsPending()) return NS_ERROR_NOT_AVAILABLE;
+
+ *value = mIsFromCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ bool fromCache = false;
+ if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache ||
+ !mCacheEntryAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aCacheEntryId = mCacheEntryId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsRacing(bool* aIsRacing) {
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsRacing = mIsRacing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheKey(uint32_t* cacheKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ *cacheKey = mCacheKey;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCacheKey(uint32_t cacheKey) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mCacheKey = cacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) {
+ StoreAllowStaleCacheContent(aAllowStaleCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetAllowStaleCacheContent(bool* aAllowStaleCacheContent) {
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = LoadAllowStaleCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetForceValidateCacheContent(
+ bool aForceValidateCacheContent) {
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetForceValidateCacheContent(
+ bool* aForceValidateCacheContent) {
+ NS_ENSURE_ARG(aForceValidateCacheContent);
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetPreferCacheLoadOverBypass(
+ bool aPreferCacheLoadOverBypass) {
+ StorePreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetPreferCacheLoadOverBypass(
+ bool* aPreferCacheLoadOverBypass) {
+ NS_ENSURE_ARG(aPreferCacheLoadOverBypass);
+ *aPreferCacheLoadOverBypass = LoadPreferCacheLoadOverBypass();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+HttpChannelChild::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataType(nsACString& aType) {
+ // Must be called during or after OnStartRequest
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OpenAlternativeOutputStream(const nsACString& aType,
+ int64_t aPredictedSize,
+ nsIAsyncOutputStream** _retval) {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<AltDataOutputStreamChild> stream = new AltDataOutputStreamChild();
+ stream->AddIPDLReference();
+
+ if (!gNeckoChild->SendPAltDataOutputStreamConstructor(
+ stream, nsCString(aType), aPredictedSize, WrapNotNull(this))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ stream.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetOriginalInputStream(nsIInputStreamReceiver* aReceiver) {
+ if (aReceiver == nullptr) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mOriginalInputStreamReceiver = aReceiver;
+ Unused << SendOpenOriginalCacheInputStream();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataInputStream(nsIInputStream** aInputStream) {
+ NS_ENSURE_ARG_POINTER(aInputStream);
+
+ nsCOMPtr<nsIInputStream> is = mAltDataInputStream;
+ is.forget(aInputStream);
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvOriginalCacheInputStreamAvailable(
+ const Maybe<IPCStream>& aStream) {
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+ nsCOMPtr<nsIInputStreamReceiver> receiver;
+ receiver.swap(mOriginalInputStreamReceiver);
+ if (receiver) {
+ receiver->OnInputStreamReady(stream);
+ }
+
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ResumeAt(uint64_t startPos, const nsACString& entityID) {
+ LOG(("HttpChannelChild::ResumeAt [this=%p]\n", this));
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mStartPos = startPos;
+ mEntityID = entityID;
+ mSendResumeAt = true;
+ return NS_OK;
+}
+
+// GetEntityID is shared in HttpBaseChannel
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetPriority(int32_t aPriority) {
+ LOG(("HttpChannelChild::SetPriority %p p=%d", this, aPriority));
+ int16_t newValue = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
+ if (mPriority == newValue) return NS_OK;
+ mPriority = newValue;
+ if (RemoteChannelExists()) SendSetPriority(mPriority);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIClassOfService
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelChild::SetClassFlags(uint32_t inFlags) {
+ if (mClassOfService.Flags() == inFlags) {
+ return NS_OK;
+ }
+
+ mClassOfService.SetFlags(inFlags);
+
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AddClassFlags(uint32_t inFlags) {
+ mClassOfService.SetFlags(inFlags | mClassOfService.Flags());
+
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ClearClassFlags(uint32_t inFlags) {
+ mClassOfService.SetFlags(~inFlags & mClassOfService.Flags());
+
+ LOG(("HttpChannelChild %p ClassOfService=%lu", this,
+ mClassOfService.Flags()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetClassOfService(ClassOfService inCos) {
+ mClassOfService = inCos;
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetIncremental(bool inIncremental) {
+ mClassOfService.SetIncremental(inIncremental);
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); }
+
+NS_IMETHODIMP HttpChannelChild::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ DROP_DEAD();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelChild
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP HttpChannelChild::AddCookiesToRequest() {
+ HttpBaseChannel::AddCookiesToRequest();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders(
+ RequestHeaderTuples** aRequestHeaders) {
+ *aRequestHeaders = &mClientSetRequestHeaders;
+ return NS_OK;
+}
+
+void HttpChannelChild::GetClientSetCorsPreflightParameters(
+ Maybe<CorsPreflightArgs>& aArgs) {
+ if (LoadRequireCORSPreflight()) {
+ CorsPreflightArgs args;
+ args.unsafeHeaders() = mUnsafeHeaders.Clone();
+ aArgs.emplace(args);
+ } else {
+ aArgs = Nothing();
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RemoveCorsPreflightCacheEntry(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ const OriginAttributes& aOriginAttributes) {
+ PrincipalInfo principalInfo;
+ MOZ_ASSERT(aURI, "aURI should not be null");
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ bool result = false;
+ // Be careful to not attempt to send a message to the parent after the
+ // actor has been destroyed.
+ if (CanSend()) {
+ result = SendRemoveCorsPreflightCacheEntry(aURI, principalInfo,
+ aOriginAttributes);
+ }
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIMuliPartChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetBaseChannel(nsIChannel** aBaseChannel) {
+ if (!mMultiPartID) {
+ MOZ_ASSERT(false, "Not a multipart channel");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<nsIChannel> channel = this;
+ channel.forget(aBaseChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetPartID(uint32_t* aPartID) {
+ if (!mMultiPartID) {
+ MOZ_ASSERT(false, "Not a multipart channel");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aPartID = *mMultiPartID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsFirstPart(bool* aIsFirstPart) {
+ if (!mMultiPartID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsFirstPart = mIsFirstPartOfMultiPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsLastPart(bool* aIsLastPart) {
+ if (!mMultiPartID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsLastPart = mIsLastPartOfMultiPart;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ LOG(("HttpChannelChild::RetargetDeliveryTo [this=%p, aNewTarget=%p]", this,
+ aNewTarget));
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+ MOZ_ASSERT(aNewTarget);
+
+ NS_ENSURE_ARG(aNewTarget);
+ if (aNewTarget->IsOnCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::successMainThread;
+ return NS_OK;
+ }
+
+ if (mMultiPartID) {
+ // TODO: Maybe add a new label for this? Maybe it doesn't
+ // matter though, since we also blocked QI, so we shouldn't
+ // ever get here.
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener;
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // Ensure that |mListener| and any subsequent listeners can be retargeted
+ // to another thread.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (!retargetableListener || NS_FAILED(rv)) {
+ NS_WARNING("Listener is not retargetable");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener;
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Subsequent listeners are not retargetable");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListenerChain;
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ MOZ_ASSERT(!mODATarget);
+ mODATarget = aNewTarget;
+ }
+
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::success;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ MutexAutoLock lock(mEventTargetMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mODATarget;
+ if (!mODATarget) {
+ target = GetCurrentSerialEventTarget();
+ }
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+void HttpChannelChild::TrySendDeletingChannel() {
+ AUTO_PROFILER_LABEL("HttpChannelChild::TrySendDeletingChannel", NETWORK);
+ if (!mDeletingChannelSent.compareExchange(false, true)) {
+ // SendDeletingChannel is already sent.
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ if (NS_WARN_IF(!CanSend())) {
+ // IPC actor is destroyed already, do not send more messages.
+ return;
+ }
+
+ Unused << PHttpChannelChild::SendDeletingChannel();
+ return;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NewNonOwningRunnableMethod(
+ "net::HttpChannelChild::TrySendDeletingChannel", this,
+ &HttpChannelChild::TrySendDeletingChannel),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult HttpChannelChild::AsyncCallImpl(
+ void (HttpChannelChild::*funcPtr)(),
+ nsRunnableMethod<HttpChannelChild>** retval) {
+ nsresult rv;
+
+ RefPtr<nsRunnableMethod<HttpChannelChild>> event =
+ NewRunnableMethod("net::HttpChannelChild::AsyncCall", this, funcPtr);
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ rv = neckoTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ if (NS_SUCCEEDED(rv) && retval) {
+ *retval = event;
+ }
+
+ return rv;
+}
+
+nsresult HttpChannelChild::SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect) {
+ // Normally this would be ENSURE_CALLED_BEFORE_CONNECT, but since the
+ // "connect" is done in the main process, and LoadRequestObserversCalled() is
+ // never set in the ChannelChild, before connect basically means before
+ // asyncOpen.
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ }
+
+ // remove old referrer if any
+ mClientSetRequestHeaders.RemoveElementsBy(
+ [](const auto& header) { return "Referer"_ns.Equals(header.mHeader); });
+
+ return HttpBaseChannel::SetReferrerHeader(aReferrer, aRespectBeforeConnect);
+}
+
+void HttpChannelChild::CancelOnMainThread(nsresult aRv,
+ const nsACString& aReason) {
+ LOG(("HttpChannelChild::CancelOnMainThread [this=%p]", this));
+
+ if (NS_IsMainThread()) {
+ CancelWithReason(aRv, aReason);
+ return;
+ }
+
+ mEventQ->Suspend();
+ // Cancel is expected to preempt any other channel events, thus we put this
+ // event in the front of mEventQ to make sure nsIStreamListener not receiving
+ // any ODA/OnStopRequest callbacks.
+ nsCString reason(aReason);
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aRv, reason]() {
+ self->CancelWithReason(aRv, reason);
+ }));
+ mEventQ->Resume();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvSetPriority(
+ const int16_t& aPriority) {
+ mPriority = aPriority;
+ return IPC_OK();
+}
+
+// We don't have a copyable Endpoint and NeckoTargetChannelFunctionEvent takes
+// std::function<void()>. It's not possible to avoid the copy from the type of
+// lambda to std::function, so does the capture list. Hence, we're forced to
+// use the old-fashioned channel event inheritance.
+class AttachStreamFilterEvent : public ChannelEvent {
+ public:
+ AttachStreamFilterEvent(HttpChannelChild* aChild,
+ already_AddRefed<nsIEventTarget> aTarget,
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint)
+ : mChild(aChild), mTarget(aTarget), mEndpoint(std::move(aEndpoint)) {}
+
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ nsCOMPtr<nsIEventTarget> target = mTarget;
+ return target.forget();
+ }
+
+ void Run() override {
+ extensions::StreamFilterParent::Attach(mChild, std::move(mEndpoint));
+ }
+
+ private:
+ HttpChannelChild* mChild;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Endpoint<extensions::PStreamFilterParent> mEndpoint;
+};
+
+void HttpChannelChild::RegisterStreamFilter(
+ RefPtr<extensions::StreamFilterParent>& aStreamFilter) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mStreamFilters.AppendElement(aStreamFilter);
+}
+
+void HttpChannelChild::ProcessAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint) {
+ LOG(("HttpChannelChild::ProcessAttachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new AttachStreamFilterEvent(this, GetNeckoTarget(),
+ std::move(aEndpoint)));
+}
+
+void HttpChannelChild::OnDetachStreamFilters() {
+ LOG(("HttpChannelChild::OnDetachStreamFilters [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ for (auto& StreamFilter : mStreamFilters) {
+ StreamFilter->Disconnect("ServiceWorker fallback redirection"_ns);
+ }
+ mStreamFilters.Clear();
+}
+
+void HttpChannelChild::ProcessDetachStreamFilters() {
+ LOG(("HttpChannelChild::ProcessDetachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this)]() {
+ self->OnDetachStreamFilters();
+ }));
+}
+
+void HttpChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mActorDestroyReason.emplace(aWhy);
+#endif
+
+ // OnStartRequest might be dropped if IPDL is destroyed abnormally
+ // and BackgroundChild might have pending IPC messages.
+ // Clean up BackgroundChild at this time to prevent memleak.
+ if (aWhy != Deletion) {
+ // Make sure all the messages are processed.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mStatus = NS_ERROR_DOCSHELL_DYING;
+ HandleAsyncAbort();
+
+ // Cleanup the background channel before we resume the eventQ so we don't
+ // get any other events.
+ CleanupBackgroundChannel();
+
+ mIPCActorDeleted = true;
+ mCanceled = true;
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvLogBlockedCORSRequest(
+ const nsAString& aMessage, const nsACString& aCategory,
+ const bool& aIsWarning) {
+ Unused << LogBlockedCORSRequest(aMessage, aCategory, aIsWarning);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ uint64_t innerWindowID = mLoadInfo->GetInnerWindowID();
+ bool privateBrowsing = !!mLoadInfo->GetOriginAttributes().mPrivateBrowsingId;
+ bool fromChromeContext =
+ mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal();
+ nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, privateBrowsing,
+ fromChromeContext, aMessage,
+ aCategory, aIsWarning);
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvLogMimeTypeMismatch(
+ const nsACString& aMessageName, const bool& aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ Unused << LogMimeTypeMismatch(aMessageName, aWarning, aURL, aContentType);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(aURL);
+ params.AppendElement(aContentType);
+ nsContentUtils::ReportToConsole(
+ aWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag,
+ "MIMEMISMATCH"_ns, doc, nsContentUtils::eSECURITY_PROPERTIES,
+ nsCString(aMessageName).get(), params);
+ return NS_OK;
+}
+
+nsresult HttpChannelChild::MaybeLogCOEPError(nsresult aStatus) {
+ if (aStatus == NS_ERROR_DOM_CORP_FAILED) {
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ nsAutoCString url;
+ mURI->GetSpec(url);
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(url));
+ // The MDN URL intentionally ends with a # so the webconsole linkification
+ // doesn't ignore the final ) of the URL
+ params.AppendElement(
+ u"https://developer.mozilla.org/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)#"_ns);
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "COEP"_ns, doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "CORPBlocked", params);
+ }
+
+ return NS_OK;
+}
+
+nsresult HttpChannelChild::CrossProcessRedirectFinished(nsresult aStatus) {
+ if (!CanSend()) {
+ return NS_BINDING_FAILED;
+ }
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ return mStatus;
+}
+
+void HttpChannelChild::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = true;
+#endif
+}
+
+void HttpChannelChild::MaybeConnectToSocketProcess() {
+ if (!nsIOService::UseSocketProcess()) {
+ return;
+ }
+
+ if (!StaticPrefs::network_send_ODA_to_content_directly()) {
+ return;
+ }
+
+ RefPtr<HttpBackgroundChannelChild> bgChild;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+ bgChild = mBgChild;
+ }
+ SocketProcessBridgeChild::GetSocketProcessBridge()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [bgChild, channelId = ChannelId()](
+ const RefPtr<SocketProcessBridgeChild>& aBridge) {
+ Endpoint<PBackgroundDataBridgeParent> parentEndpoint;
+ Endpoint<PBackgroundDataBridgeChild> childEndpoint;
+ PBackgroundDataBridge::CreateEndpoints(&parentEndpoint, &childEndpoint);
+ aBridge->SendInitBackgroundDataBridge(std::move(parentEndpoint),
+ channelId);
+
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::CreateDataBridge",
+ [bgChild, endpoint = std::move(childEndpoint)]() mutable {
+ bgChild->CreateDataBridge(std::move(endpoint));
+ }),
+ NS_DISPATCH_NORMAL);
+ },
+ []() { NS_WARNING("Failed to create SocketProcessBridgeChild"); });
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpChannelChild::SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) {
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h
new file mode 100644
index 0000000000..38895a0555
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -0,0 +1,481 @@
+/* -*- 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_HttpChannelChild_h
+#define mozilla_net_HttpChannelChild_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPrefsBase.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PHttpChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsICacheEntry.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIChildChannel.h"
+#include "nsIHttpChannelChild.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "mozilla/net/DNS.h"
+
+using mozilla::Telemetry::LABELS_HTTP_CHILD_OMT_STATS;
+
+class nsIEventTarget;
+class nsIInterceptedBodyCallback;
+class nsISerialEventTarget;
+class nsITransportSecurityInfo;
+class nsInputStreamPump;
+
+#define HTTP_CHANNEL_CHILD_IID \
+ { \
+ 0x321bd99e, 0x2242, 0x4dc6, { \
+ 0xbb, 0xec, 0xd5, 0x06, 0x29, 0x7c, 0x39, 0x83 \
+ } \
+ }
+
+namespace mozilla::net {
+
+class HttpBackgroundChannelChild;
+
+class HttpChannelChild final : public PHttpChannelChild,
+ public HttpBaseChannel,
+ public HttpAsyncAborter<HttpChannelChild>,
+ public nsICacheInfoChannel,
+ public nsIProxiedChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIChildChannel,
+ public nsIHttpChannelChild,
+ public nsIMultiPartChannel,
+ public nsIThreadRetargetableRequest,
+ public NeckoTargetHolder {
+ virtual ~HttpChannelChild();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIHTTPCHANNELCHILD
+ NS_DECL_NSIMULTIPARTCHANNEL
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_CHILD_IID)
+
+ HttpChannelChild();
+
+ // Methods HttpBaseChannel didn't implement for us or that we override.
+ //
+ // nsIRequest
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+
+ // HttpBaseChannel::nsIHttpChannel
+ NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) override;
+ NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
+ NS_IMETHOD RedirectTo(nsIURI* newURI) override;
+ NS_IMETHOD UpgradeToSecure() override;
+ NS_IMETHOD GetProtocolVersion(nsACString& aProtocolVersion) override;
+ void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
+ NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override;
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD SetClassOfService(ClassOfService inCos) override;
+ NS_IMETHOD SetIncremental(bool inIncremental) override;
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ nsresult SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect) override;
+
+ [[nodiscard]] bool IsSuspended();
+
+ // Callback while background channel is ready.
+ void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild);
+ // Callback while background channel is destroyed.
+ void OnBackgroundChildDestroyed(HttpBackgroundChannelChild* aBgChild);
+
+ nsresult CrossProcessRedirectFinished(nsresult aStatus);
+
+ void RegisterStreamFilter(
+ RefPtr<extensions::StreamFilterParent>& aStreamFilter);
+
+ protected:
+ mozilla::ipc::IPCResult RecvOnStartRequestSent() override;
+ mozilla::ipc::IPCResult RecvFailedAsyncOpen(const nsresult& status) override;
+ mozilla::ipc::IPCResult RecvRedirect1Begin(
+ const uint32_t& registrarId, nsIURI* newOriginalURI,
+ const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ nsITransportSecurityInfo* securityInfo, const uint64_t& channelId,
+ const NetAddr& oldPeerAddr,
+ const ResourceTimingStructArgs& aTiming) override;
+ mozilla::ipc::IPCResult RecvRedirect3Complete() override;
+ mozilla::ipc::IPCResult RecvRedirectFailed(const nsresult& status) override;
+ mozilla::ipc::IPCResult RecvDeleteSelf() override;
+
+ mozilla::ipc::IPCResult RecvReportSecurityMessage(
+ const nsAString& messageTag, const nsAString& messageCategory) override;
+
+ mozilla::ipc::IPCResult RecvSetPriority(const int16_t& aPriority) override;
+
+ mozilla::ipc::IPCResult RecvOriginalCacheInputStreamAvailable(
+ const Maybe<IPCStream>& aStream) override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual void DoNotifyListenerCleanup() override;
+
+ virtual void DoAsyncAbort(nsresult aStatus) override;
+
+ nsresult AsyncCall(
+ void (HttpChannelChild::*funcPtr)(),
+ nsRunnableMethod<HttpChannelChild>** retval = nullptr) override {
+ // Normally, this method would just be implemented directly, but clang
+ // miscompiles the corresponding non-virtual thunk on linux x86.
+ // It however doesn't when going though a non-virtual method.
+ // https://bugs.llvm.org/show_bug.cgi?id=38466
+ return AsyncCallImpl(funcPtr, retval);
+ };
+
+ // Get event target for processing network events.
+ already_AddRefed<nsISerialEventTarget> GetNeckoTarget() override;
+
+ virtual mozilla::ipc::IPCResult RecvLogBlockedCORSRequest(
+ const nsAString& aMessage, const nsACString& aCategory,
+ const bool& aIsWarning) override;
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning = false) override;
+
+ virtual mozilla::ipc::IPCResult RecvLogMimeTypeMismatch(
+ const nsACString& aMessageName, const bool& aWarning,
+ const nsAString& aURL, const nsAString& aContentType) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ private:
+ // We want to handle failure result of AsyncOpen, hence AsyncOpen calls the
+ // Internal method
+ nsresult AsyncOpenInternal(nsIStreamListener* aListener);
+
+ nsresult AsyncCallImpl(void (HttpChannelChild::*funcPtr)(),
+ nsRunnableMethod<HttpChannelChild>** retval);
+
+ // Sets the event target for future IPC messages. Messages will either be
+ // directed to the TabGroup or DocGroup, depending on the LoadInfo associated
+ // with the channel. Should be called when a new channel is being set up,
+ // before the constructor message is sent to the parent.
+ void SetEventTarget();
+
+ // Get event target for ODA.
+ already_AddRefed<nsIEventTarget> GetODATarget();
+
+ [[nodiscard]] nsresult ContinueAsyncOpen();
+ void ProcessOnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStartTime);
+
+ // Callbacks while receiving OnTransportAndData/OnStopRequest/OnProgress/
+ // OnStatus/FlushedForDiversion/DivertMessages on background IPC channel.
+ void ProcessOnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsACString& aData,
+ const TimeStamp& aOnDataAvailableStartTime);
+ void ProcessOnStopRequest(const nsresult& aChannelStatus,
+ const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ bool aFromSocketProcess,
+ const TimeStamp& aOnStopRequestStartTime);
+ void ProcessOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports);
+
+ void ProcessNotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+ void ProcessSetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash);
+ void ProcessSetClassifierMatchedTrackingInfo(const nsACString& aLists,
+ const nsACString& aFullHashes);
+ void ProcessOnAfterLastPart(const nsresult& aStatus);
+ void ProcessOnProgress(const int64_t& aProgress, const int64_t& aProgressMax);
+
+ void ProcessOnStatus(const nsresult& aStatus);
+
+ void ProcessAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint);
+ void ProcessDetachStreamFilters();
+
+ // Return true if we need to tell the parent the size of unreported received
+ // data
+ bool NeedToReportBytesRead();
+ int32_t mUnreportBytesRead = 0;
+
+ void DoOnConsoleReport(nsTArray<ConsoleReportCollected>&& aConsoleReports);
+ void DoOnStartRequest(nsIRequest* aRequest);
+ void DoOnStatus(nsIRequest* aRequest, nsresult status);
+ void DoOnProgress(nsIRequest* aRequest, int64_t progress,
+ int64_t progressMax);
+ void DoOnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t offset, uint32_t count);
+ void DoPreOnStopRequest(nsresult aStatus);
+ void DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus);
+ void ContinueOnStopRequest();
+
+ // Try send DeletingChannel message to parent side. Dispatch an async task to
+ // main thread if invoking on non-main thread.
+ void TrySendDeletingChannel();
+
+ // Try invoke Cancel if on main thread, or prepend a CancelEvent in mEventQ to
+ // ensure Cacnel is processed before any other channel events.
+ void CancelOnMainThread(nsresult aRv, const nsACString& aReason);
+
+ nsresult MaybeLogCOEPError(nsresult aStatus);
+
+ private:
+ // this section is for main-thread-only object
+ // all the references need to be proxy released on main thread.
+ nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
+
+ // Proxy release all members above on main thread.
+ void ReleaseMainThreadOnlyReferences();
+
+ private:
+ nsCString mProtocolVersion;
+
+ RequestHeaderTuples mClientSetRequestHeaders;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ nsCOMPtr<nsIInputStreamReceiver> mOriginalInputStreamReceiver;
+ nsCOMPtr<nsIInputStream> mAltDataInputStream;
+
+ // Used to ensure atomicity of mBgChild and mBgInitFailCallback
+ Mutex mBgChildMutex{"HttpChannelChild::BgChildMutex"};
+
+ // Associated HTTP background channel
+ RefPtr<HttpBackgroundChannelChild> mBgChild MOZ_GUARDED_BY(mBgChildMutex);
+
+ // Error handling procedure if failed to establish PBackground IPC
+ nsCOMPtr<nsIRunnable> mBgInitFailCallback MOZ_GUARDED_BY(mBgChildMutex);
+
+ // Remove the association with background channel after OnStopRequest
+ // or AsyncAbort.
+ void CleanupBackgroundChannel();
+
+ // Target thread for delivering ODA.
+ nsCOMPtr<nsISerialEventTarget> mODATarget MOZ_GUARDED_BY(mEventTargetMutex);
+ // Used to ensure atomicity of mNeckoTarget / mODATarget;
+ Mutex mEventTargetMutex{"HttpChannelChild::EventTargetMutex"};
+
+ TimeStamp mLastStatusReported;
+
+ uint64_t mCacheEntryId{0};
+
+ // The result of RetargetDeliveryTo for this channel.
+ // |notRequested| represents OMT is not requested by the channel owner.
+ Atomic<LABELS_HTTP_CHILD_OMT_STATS, mozilla::Relaxed> mOMTResult{
+ LABELS_HTTP_CHILD_OMT_STATS::notRequested};
+
+ uint32_t mCacheKey{0};
+ int32_t mCacheFetchCount{0};
+ uint32_t mCacheExpirationTime{
+ static_cast<uint32_t>(nsICacheEntry::NO_EXPIRATION_TIME)};
+
+ // If we're handling a multi-part response, then this is set to the current
+ // part ID during OnStartRequest.
+ Maybe<uint32_t> mMultiPartID;
+
+ // To ensure only one SendDeletingChannel is triggered.
+ Atomic<bool> mDeletingChannelSent{false};
+
+ Atomic<bool, SequentiallyConsistent> mIsFromCache{false};
+ Atomic<bool, SequentiallyConsistent> mIsRacing{false};
+ // Set if we get the result and cache |mNeedToReportBytesRead|
+ Atomic<bool, SequentiallyConsistent> mCacheNeedToReportBytesReadInitialized{
+ false};
+ // True if we need to tell the parent the size of unreported received data
+ Atomic<bool, SequentiallyConsistent> mNeedToReportBytesRead{true};
+ Atomic<uint32_t, mozilla::Relaxed> mOnProgressEventSent{false};
+ // Attached StreamFilterParents
+ // Using raw pointer here since StreamFilterParent owns the channel.
+ // Should be only accessed on the main thread.
+ using StreamFilters = nsTArray<extensions::StreamFilterParent*>;
+ StreamFilters mStreamFilters;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = false;
+ bool mAsyncOpenSucceeded = false;
+ bool mSuccesfullyRedirected = false;
+ bool mRemoteChannelExistedAtCancel = false;
+ bool mEverHadBgChildAtAsyncOpen = false;
+ bool mEverHadBgChildAtConnectParent = false;
+ bool mCreateBackgroundChannelFailed = false;
+ bool mBgInitFailCallbackTriggered = false;
+ bool mCanSendAtCancel = false;
+ // State of the HttpBackgroundChannelChild's event queue during destruction.
+ enum BckChildQueueStatus {
+ // BckChild never told us
+ BCKCHILD_UNKNOWN,
+ // BckChild was empty at the time of destruction
+ BCKCHILD_EMPTY,
+ // BckChild was keeping events in the queue at the destruction time!
+ BCKCHILD_NON_EMPTY
+ };
+ Atomic<BckChildQueueStatus> mBackgroundChildQueueFinalState{BCKCHILD_UNKNOWN};
+ Maybe<ActorDestroyReason> mActorDestroyReason;
+#endif
+
+ uint8_t mCacheEntryAvailable : 1;
+ uint8_t mAltDataCacheEntryAvailable : 1;
+
+ // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
+ uint8_t mSendResumeAt : 1;
+
+ uint8_t mKeptAlive : 1; // IPC kept open, but only for security info
+
+ // Set when ActorDestroy(ActorDestroyReason::Deletion) is called
+ // The channel must ignore any following OnStart/Stop/DataAvailable messages
+ uint8_t mIPCActorDeleted : 1;
+
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ uint8_t mSuspendSent : 1;
+
+ // True if this channel is a multi-part channel, and the first part
+ // is currently being processed.
+ uint8_t mIsFirstPartOfMultiPart : 1;
+
+ // True if this channel is a multi-part channel, and the last part
+ // is currently being processed.
+ uint8_t mIsLastPartOfMultiPart : 1;
+
+ // True if this channel is suspended by ConnectParent and not resumed by
+ // CompleteRedirectSetup/RecvDeleteSelf.
+ uint8_t mSuspendForWaitCompleteRedirectSetup : 1;
+
+ // True if RecvOnStartRequestSent was received.
+ uint8_t mRecvOnStartRequestSentCalled : 1;
+
+ // True if this channel is for a document and suspended by waiting for
+ // permission or cookie. That is, RecvOnStartRequestSent is received.
+ uint8_t mSuspendedByWaitingForPermissionCookie : 1;
+
+ // HttpChannelChild::Release has some special logic that makes sure
+ // OnStart/OnStop are always called when releasing the channel.
+ // But we have to make sure we only do this once - otherwise we could
+ // get stuck in a loop.
+ uint8_t mAlreadyReleased : 1;
+
+ void CleanupRedirectingChannel(nsresult rv);
+
+ // Calls OnStartRequest and/or OnStopRequest on our listener in case we didn't
+ // do that so far. If we already did, it will just release references to
+ // cleanup.
+ void NotifyOrReleaseListeners(nsresult rv);
+
+ // true after successful AsyncOpen until OnStopRequest completes.
+ bool RemoteChannelExists() { return CanSend() && !mKeptAlive; }
+
+ void OnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs);
+ void OnTransportAndData(const nsresult& channelStatus, const nsresult& status,
+ const uint64_t& offset, const uint32_t& count,
+ const nsACString& data);
+ void OnStopRequest(const nsresult& channelStatus,
+ const ResourceTimingStructArgs& timing,
+ const nsHttpHeaderArray& aResponseTrailers);
+ void FailedAsyncOpen(const nsresult& status);
+ void HandleAsyncAbort();
+ void Redirect1Begin(const uint32_t& registrarId, nsIURI* newOriginalURI,
+ const uint32_t& newLoadFlags,
+ const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ nsITransportSecurityInfo* securityInfo,
+ const uint64_t& channelId,
+ const ResourceTimingStructArgs& timing);
+ void Redirect3Complete();
+ void DeleteSelf();
+ // aUseEventQueue should only be false when called from
+ // HttpChannelChild::Release to make sure OnStopRequest is called syncly.
+ void DoNotifyListener(bool aUseEventQueue = true);
+ void ContinueDoNotifyListener();
+ void OnAfterLastPart(const nsresult& aStatus);
+ void MaybeConnectToSocketProcess();
+ void OnDetachStreamFilters();
+ void SendOnDataFinished(const nsresult& aChannelStatus);
+
+ // Create a a new channel to be used in a redirection, based on the provided
+ // response headers.
+ [[nodiscard]] nsresult SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel);
+
+ // Collect telemetry for the successful rate of OMT.
+ void CollectOMTTelemetry();
+
+ // Collect telemetry for mixed content.
+ void CollectMixedContentTelemetry();
+
+ void RecordChannelCompletionDurationForEarlyHint();
+
+ friend class HttpAsyncAborter<HttpChannelChild>;
+ friend class InterceptStreamListener;
+ friend class InterceptedChannelContent;
+ friend class HttpBackgroundChannelChild;
+ friend class NeckoTargetChannelFunctionEvent;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelChild, HTTP_CHANNEL_CHILD_IID)
+
+//-----------------------------------------------------------------------------
+// inline functions
+//-----------------------------------------------------------------------------
+
+inline bool HttpChannelChild::IsSuspended() { return mSuspendCount != 0; }
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_HttpChannelChild_h
diff --git a/netwerk/protocol/http/HttpChannelParams.ipdlh b/netwerk/protocol/http/HttpChannelParams.ipdlh
new file mode 100644
index 0000000000..f7601a7785
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParams.ipdlh
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+include IPCServiceWorkerDescriptor;
+include NeckoChannelParams;
+include IPCStream;
+
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/ipc/TransportSecurityInfoUtils.h";
+include "mozilla/net/NeckoMessageUtils.h";
+
+using class mozilla::net::nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using nsILoadInfo::CrossOriginOpenerPolicy from "nsILoadInfo.h";
+[RefCounted] using class nsIReferrerInfo from "nsIReferrerInfo.h";
+[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
+using nsIRequest::TRRMode from "nsIRequest.h";
+using mozilla::net::TRRSkippedReason from "nsITRRSkipReason.h";
+
+namespace mozilla {
+namespace net {
+
+struct HttpChannelOnStartRequestArgs
+{
+ nullable nsITransportSecurityInfo securityInfo;
+ nullable nsIReferrerInfo overrideReferrerInfo;
+ uint64_t cacheEntryId;
+ int64_t altDataLength;
+ nsCString altDataType;
+ nsCString cookie;
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ ResourceTimingStructArgs timing;
+ ParentLoadInfoForwarderArgs loadInfoForwarder;
+ nsresult channelStatus;
+ TRRMode effectiveTRRMode;
+ TRRSkippedReason trrSkipReason;
+ uint32_t cacheFetchCount;
+ uint32_t cacheExpirationTime;
+ uint32_t cacheKey;
+ uint32_t? multiPartID;
+ bool isFromCache;
+ bool isRacing;
+ bool cacheEntryAvailable;
+ bool deliveringAltData;
+ bool applyConversion;
+ bool isResolvedByTRR;
+ bool allRedirectsSameOrigin;
+ bool isFirstPartOfMultiPart;
+ bool isLastPartOfMultiPart;
+ CrossOriginOpenerPolicy openerPolicy;
+ bool shouldWaitForOnStartRequestSent;
+ bool dataFromSocketProcess;
+ bool hasHTTPSRR;
+ bool isProxyUsed;
+ uint8_t redirectCount;
+ nsCString protocolVersion;
+};
+
+struct HttpChannelAltDataStream
+{
+ IPCStream? altDataInputStream;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
new file mode 100644
index 0000000000..e734d15a7d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -0,0 +1,2202 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "ErrorList.h"
+#include "HttpLog.h"
+
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/EarlyHintRegistrar.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/CookieServiceParent.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "HttpBackgroundChannelParent.h"
+#include "ParentChannelListener.h"
+#include "nsDebug.h"
+#include "nsICacheInfoChannel.h"
+#include "nsHttpHandler.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPriority.h"
+#include "mozilla/net/BackgroundChannelRegistrar.h"
+#include "nsSerializationHelper.h"
+#include "nsISerializable.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "nsQueryObject.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIPrompt.h"
+#include "nsIPromptFactory.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "nsIWindowWatcher.h"
+#include "mozilla/dom/Document.h"
+#include "nsISecureBrowserUI.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "nsQueryObject.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIViewSourceChannel.h"
+
+using namespace mozilla;
+
+namespace geckoprofiler::markers {
+
+struct ChannelMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("ChannelMarker");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString8View& aURL, uint64_t aChannelId) {
+ if (aURL.Length() != 0) {
+ aWriter.StringProperty("url", aURL);
+ }
+ aWriter.IntProperty("channelId", static_cast<int64_t>(aChannelId));
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema(MS::Location::MarkerChart, MS::Location::MarkerTable);
+ schema.SetTableLabel("{marker.name} - {marker.data.url}");
+ schema.AddKeyFormatSearchable("url", MS::Format::Url,
+ MS::Searchable::Searchable);
+ schema.AddStaticLabelValue(
+ "Description",
+ "Timestamp capturing various phases of a network channel's lifespan.");
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+using mozilla::BasePrincipal;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+HttpChannelParent::HttpChannelParent(dom::BrowserParent* iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mLoadContext(aLoadContext),
+ mIPCClosed(false),
+ mPBOverride(aOverrideStatus),
+ mStatus(NS_OK),
+ mIgnoreProgress(false),
+ mHasSuspendedByBackPressure(false),
+ mCacheNeedFlowControlInitialized(false),
+ mNeedFlowControl(true),
+ mSuspendedForFlowControl(false),
+ mAfterOnStartRequestBegun(false),
+ mDataSentToChildProcess(false) {
+ LOG(("Creating HttpChannelParent [this=%p]\n", this));
+
+ // Ensure gHttpHandler is initialized: we need the atom table up and running.
+ nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
+
+ MOZ_ASSERT(gHttpHandler);
+ mHttpHandler = gHttpHandler;
+
+ mBrowserParent = iframeEmbedding;
+
+ mSendWindowSize = gHttpHandler->SendWindowSize();
+
+ mEventQ =
+ new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this));
+}
+
+HttpChannelParent::~HttpChannelParent() {
+ LOG(("Destroying HttpChannelParent [this=%p]\n", this));
+ CleanupBackgroundChannel();
+
+ MOZ_ASSERT(!mRedirectCallback);
+ if (NS_WARN_IF(mRedirectCallback)) {
+ mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_UNEXPECTED);
+ mRedirectCallback = nullptr;
+ }
+ mEventQ->NotifyReleasingOwner();
+}
+
+void HttpChannelParent::ActorDestroy(ActorDestroyReason why) {
+ // We may still have refcount>0 if nsHttpChannel hasn't called OnStopRequest
+ // yet, but child process has crashed. We must not try to send any more msgs
+ // to child, or IPDL will kill chrome process, too.
+ mIPCClosed = true;
+ CleanupBackgroundChannel();
+}
+
+bool HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) {
+ LOG(("HttpChannelParent::Init [this=%p]\n", this));
+ AUTO_PROFILER_LABEL("HttpChannelParent::Init", NETWORK);
+ switch (aArgs.type()) {
+ case HttpChannelCreationArgs::THttpChannelOpenArgs: {
+ const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
+ return DoAsyncOpen(
+ a.uri(), a.original(), a.doc(), a.referrerInfo(), a.apiRedirectTo(),
+ a.topWindowURI(), a.loadFlags(), a.requestHeaders(),
+ a.requestMethod(), a.uploadStream(), a.uploadStreamHasHeaders(),
+ a.priority(), a.classOfService(), a.redirectionLimit(), a.allowSTS(),
+ a.thirdPartyFlags(), a.resumeAt(), a.startPos(), a.entityID(),
+ a.allowSpdy(), a.allowHttp3(), a.allowAltSvc(), a.beConservative(),
+ a.bypassProxy(), a.tlsFlags(), a.loadInfo(), a.cacheKey(),
+ a.requestContextID(), a.preflightArgs(), a.initialRwin(),
+ a.blockAuthPrompt(), a.allowStaleCacheContent(),
+ a.preferCacheLoadOverBypass(), a.contentTypeHint(), a.requestMode(),
+ a.redirectMode(), a.channelId(), a.integrityMetadata(),
+ a.contentWindowId(), a.preferredAlternativeTypes(), a.browserId(),
+ a.launchServiceWorkerStart(), a.launchServiceWorkerEnd(),
+ a.dispatchFetchEventStart(), a.dispatchFetchEventEnd(),
+ a.handleFetchEventStart(), a.handleFetchEventEnd(),
+ a.forceMainDocumentChannel(), a.navigationStartTimeStamp(),
+ a.earlyHintPreloaderId(), a.classicScriptHintCharset(),
+ a.documentCharacterSet(), a.isUserAgentHeaderModified());
+ }
+ case HttpChannelCreationArgs::THttpChannelConnectArgs: {
+ const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
+ return ConnectChannel(cArgs.registrarId());
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown open type");
+ return false;
+ }
+}
+
+void HttpChannelParent::TryInvokeAsyncOpen(nsresult aRv) {
+ LOG(("HttpChannelParent::TryInvokeAsyncOpen [this=%p barrier=%u rv=%" PRIx32
+ "]\n",
+ this, mAsyncOpenBarrier, static_cast<uint32_t>(aRv)));
+ MOZ_ASSERT(NS_IsMainThread());
+ AUTO_PROFILER_LABEL("HttpChannelParent::TryInvokeAsyncOpen", NETWORK);
+
+ // TryInvokeAsyncOpen is called more than we expected.
+ // Assert in nightly build but ignore it in release channel.
+ MOZ_DIAGNOSTIC_ASSERT(mAsyncOpenBarrier > 0);
+ if (NS_WARN_IF(!mAsyncOpenBarrier)) {
+ return;
+ }
+
+ if (--mAsyncOpenBarrier > 0 && NS_SUCCEEDED(aRv)) {
+ // Need to wait for more events.
+ return;
+ }
+
+ InvokeAsyncOpen(aRv);
+}
+
+void HttpChannelParent::OnBackgroundParentReady(
+ HttpBackgroundChannelParent* aBgParent) {
+ LOG(("HttpChannelParent::OnBackgroundParentReady [this=%p bgParent=%p]\n",
+ this, aBgParent));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mBgParent);
+
+ mBgParent = aBgParent;
+
+ mPromise.ResolveIfExists(true, __func__);
+}
+
+void HttpChannelParent::OnBackgroundParentDestroyed() {
+ LOG(("HttpChannelParent::OnBackgroundParentDestroyed [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mPromise.IsEmpty()) {
+ MOZ_ASSERT(!mBgParent);
+ mPromise.Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+
+ if (!mBgParent) {
+ return;
+ }
+
+ // Background channel is closed unexpectly, abort PHttpChannel operation.
+ mBgParent = nullptr;
+ Delete();
+}
+
+void HttpChannelParent::CleanupBackgroundChannel() {
+ LOG(("HttpChannelParent::CleanupBackgroundChannel [this=%p bgParent=%p]\n",
+ this, mBgParent.get()));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mBgParent) {
+ RefPtr<HttpBackgroundChannelParent> bgParent = std::move(mBgParent);
+ bgParent->OnChannelClosed();
+ return;
+ }
+
+ // The nsHttpChannel may have a reference to this parent, release it
+ // to avoid circular references.
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(nullptr);
+ }
+
+ if (!mPromise.IsEmpty()) {
+ mRequest.DisconnectIfExists();
+ mPromise.Reject(NS_ERROR_FAILURE, __func__);
+
+ if (!mChannel) {
+ return;
+ }
+
+ // This HttpChannelParent might still have a reference from
+ // BackgroundChannelRegistrar.
+ nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
+ BackgroundChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ registrar->DeleteChannel(mChannel->ChannelId());
+
+ // If mAsyncOpenBarrier is greater than zero, it means AsyncOpen procedure
+ // is still on going. we need to abort AsyncOpen with failure to destroy
+ // PHttpChannel actor.
+ if (mAsyncOpenBarrier) {
+ TryInvokeAsyncOpen(NS_ERROR_FAILURE);
+ }
+ }
+}
+
+base::ProcessId HttpChannelParent::OtherPid() const {
+ if (mIPCClosed) {
+ return 0;
+ }
+ return PHttpChannelParent::OtherPid();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelParent)
+NS_IMPL_RELEASE(HttpChannelParent)
+NS_INTERFACE_MAP_BEGIN(HttpChannelParent)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIParentRedirectingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelParent)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::GetInterface(const nsIID& aIID, void** result) {
+ // A system XHR can be created without reference to a window, hence mTabParent
+ // may be null. In that case we want to let the window watcher pick a prompt
+ // directly.
+ if (!mBrowserParent && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ bool hasWindowCreator = false;
+ Unused << wwatch->HasWindowCreator(&hasWindowCreator);
+ if (!hasWindowCreator) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIPromptFactory> factory = do_QueryInterface(wwatch);
+ if (!factory) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ rv = factory->GetPrompt(nullptr, aIID, reinterpret_cast<void**>(result));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, result);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::PHttpChannelParent
+//-----------------------------------------------------------------------------
+
+void HttpChannelParent::AsyncOpenFailed(nsresult aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NS_FAILED(aRv));
+
+ // Break the reference cycle among HttpChannelParent,
+ // ParentChannelListener, and nsHttpChannel to avoid memory leakage.
+ mChannel = nullptr;
+ mParentListener = nullptr;
+
+ if (!mIPCClosed) {
+ Unused << SendFailedAsyncOpen(aRv);
+ }
+}
+
+void HttpChannelParent::InvokeAsyncOpen(nsresult rv) {
+ LOG(("HttpChannelParent::InvokeAsyncOpen [this=%p rv=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(rv)) {
+ AsyncOpenFailed(rv);
+ return;
+ }
+
+ rv = mChannel->AsyncOpen(mParentListener);
+ if (NS_FAILED(rv)) {
+ AsyncOpenFailed(rv);
+ }
+}
+
+void HttpChannelParent::InvokeEarlyHintPreloader(
+ nsresult rv, uint64_t aEarlyHintPreloaderId) {
+ LOG(("HttpChannelParent::InvokeEarlyHintPreloader [this=%p rv=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ContentParentId cpId =
+ static_cast<ContentParent*>(Manager()->Manager())->ChildID();
+
+ RefPtr<EarlyHintRegistrar> ehr = EarlyHintRegistrar::GetOrCreate();
+ if (NS_SUCCEEDED(rv)) {
+ rv = ehr->LinkParentChannel(cpId, aEarlyHintPreloaderId, this)
+ ? NS_OK
+ : NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(rv)) {
+ ehr->DeleteEntry(cpId, aEarlyHintPreloaderId);
+ AsyncOpenFailed(NS_ERROR_FAILURE);
+ }
+}
+
+bool HttpChannelParent::DoAsyncOpen(
+ nsIURI* aURI, nsIURI* aOriginalURI, nsIURI* aDocURI,
+ nsIReferrerInfo* aReferrerInfo, nsIURI* aAPIRedirectToURI,
+ nsIURI* aTopWindowURI, const uint32_t& aLoadFlags,
+ const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+ const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+ const int16_t& priority, const ClassOfService& classOfService,
+ const uint8_t& redirectionLimit, const bool& allowSTS,
+ const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+ const uint64_t& startPos, const nsCString& entityID, const bool& allowSpdy,
+ const bool& allowHttp3, const bool& allowAltSvc, const bool& beConservative,
+ const bool& bypassProxy, const uint32_t& tlsFlags,
+ const LoadInfoArgs& aLoadInfoArgs, const uint32_t& aCacheKey,
+ const uint64_t& aRequestContextID,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
+ const bool& aAllowStaleCacheContent, const bool& aPreferCacheLoadOverBypass,
+ const nsCString& aContentTypeHint, const dom::RequestMode& aRequestMode,
+ const uint32_t& aRedirectMode, const uint64_t& aChannelId,
+ const nsString& aIntegrityMetadata, const uint64_t& aContentWindowId,
+ const nsTArray<PreferredAlternativeDataTypeParams>&
+ aPreferredAlternativeTypes,
+ const uint64_t& aBrowserId, const TimeStamp& aLaunchServiceWorkerStart,
+ const TimeStamp& aLaunchServiceWorkerEnd,
+ const TimeStamp& aDispatchFetchEventStart,
+ const TimeStamp& aDispatchFetchEventEnd,
+ const TimeStamp& aHandleFetchEventStart,
+ const TimeStamp& aHandleFetchEventEnd,
+ const bool& aForceMainDocumentChannel,
+ const TimeStamp& aNavigationStartTimeStamp,
+ const uint64_t& aEarlyHintPreloaderId,
+ const nsAString& aClassicScriptHintCharset,
+ const nsAString& aDocumentCharacterSet,
+ const bool& aIsUserAgentHeaderModified) {
+ MOZ_ASSERT(aURI, "aURI should not be NULL");
+
+ if (aEarlyHintPreloaderId) {
+ // Wait for HttpBackgrounChannel to continue the async open procedure.
+ mEarlyHintPreloaderId = aEarlyHintPreloaderId;
+ RefPtr<HttpChannelParent> self = this;
+ WaitForBgParent(aChannelId)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, aEarlyHintPreloaderId]() {
+ self->mRequest.Complete();
+ self->InvokeEarlyHintPreloader(NS_OK, aEarlyHintPreloaderId);
+ },
+ [self, aEarlyHintPreloaderId](nsresult aStatus) {
+ self->mRequest.Complete();
+ self->InvokeEarlyHintPreloader(aStatus, aEarlyHintPreloaderId);
+ })
+ ->Track(mRequest);
+ return true;
+ }
+
+ if (!aURI) {
+ // this check is neccessary to prevent null deref
+ // in opt builds
+ return false;
+ }
+
+ LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s, gid=%" PRIu64
+ " browserid=%" PRIx64 "]\n",
+ this, aURI->GetSpecOrDefault().get(), aChannelId, aBrowserId));
+
+ PROFILER_MARKER("Receive AsyncOpen in Parent", NETWORK, {}, ChannelMarker,
+ aURI->GetSpecOrDefault(), aChannelId);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsAutoCString remoteType;
+ rv = GetRemoteType(remoteType);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, remoteType,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannelInternal(getter_AddRefs(channel), aURI, loadInfo, nullptr,
+ nullptr, nullptr, aLoadFlags, ios);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(channel, &rv);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ // Set attributes needed to create a FetchEvent from this channel.
+ httpChannel->SetRequestMode(aRequestMode);
+ httpChannel->SetRedirectMode(aRedirectMode);
+
+ // Set the channelId allocated in child to the parent instance
+ httpChannel->SetChannelId(aChannelId);
+ httpChannel->SetTopLevelContentWindowId(aContentWindowId);
+ httpChannel->SetBrowserId(aBrowserId);
+
+ httpChannel->SetIntegrityMetadata(aIntegrityMetadata);
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(httpChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(this);
+ }
+ httpChannel->SetTimingEnabled(true);
+ if (mPBOverride != kPBOverride_Unset) {
+ httpChannel->SetPrivate(mPBOverride == kPBOverride_Private);
+ }
+
+ if (doResumeAt) httpChannel->ResumeAt(startPos, entityID);
+
+ if (aOriginalURI) {
+ httpChannel->SetOriginalURI(aOriginalURI);
+ }
+
+ if (aDocURI) {
+ httpChannel->SetDocumentURI(aDocURI);
+ }
+
+ if (aReferrerInfo) {
+ // Referrer header is computed in child no need to recompute here
+ rv =
+ httpChannel->SetReferrerInfoInternal(aReferrerInfo, false, false, true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ httpChannel->SetClassicScriptHintCharset(aClassicScriptHintCharset);
+ httpChannel->SetDocumentCharacterSet(aDocumentCharacterSet);
+
+ if (aAPIRedirectToURI) {
+ httpChannel->RedirectTo(aAPIRedirectToURI);
+ }
+
+ if (aTopWindowURI) {
+ httpChannel->SetTopWindowURI(aTopWindowURI);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ httpChannel->SetLoadFlags(aLoadFlags);
+ }
+
+ if (aForceMainDocumentChannel) {
+ httpChannel->SetIsMainDocumentChannel(true);
+ }
+
+ for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
+ if (requestHeaders[i].mEmpty) {
+ httpChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader);
+ } else {
+ httpChannel->SetRequestHeader(requestHeaders[i].mHeader,
+ requestHeaders[i].mValue,
+ requestHeaders[i].mMerge);
+ }
+ }
+
+ httpChannel->SetIsUserAgentHeaderModified(aIsUserAgentHeaderModified);
+
+ RefPtr<ParentChannelListener> parentListener = new ParentChannelListener(
+ this, mBrowserParent ? mBrowserParent->GetBrowsingContext() : nullptr);
+
+ httpChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
+
+ if (aCorsPreflightArgs.isSome()) {
+ const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
+ httpChannel->SetCorsPreflightParameters(args.unsafeHeaders(), false);
+ }
+
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
+ if (stream) {
+ rv = httpChannel->InternalSetUploadStream(stream);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel =
+ do_QueryInterface(static_cast<nsIChannel*>(httpChannel.get()));
+ if (cacheChannel) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ for (const auto& data : aPreferredAlternativeTypes) {
+ cacheChannel->PreferAlternativeDataType(data.type(), data.contentType(),
+ data.deliverAltData());
+ }
+
+ cacheChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
+ cacheChannel->SetPreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
+
+ // This is to mark that the results are going to the content process.
+ if (httpChannelImpl) {
+ httpChannelImpl->SetAltDataForChild(true);
+ }
+ }
+
+ httpChannel->SetContentType(aContentTypeHint);
+
+ if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
+ httpChannel->SetPriority(priority);
+ }
+ if (classOfService.Flags() || classOfService.Incremental()) {
+ httpChannel->SetClassOfService(classOfService);
+ }
+ httpChannel->SetRedirectionLimit(redirectionLimit);
+ httpChannel->SetAllowSTS(allowSTS);
+ httpChannel->SetThirdPartyFlags(thirdPartyFlags);
+ httpChannel->SetAllowSpdy(allowSpdy);
+ httpChannel->SetAllowHttp3(allowHttp3);
+ httpChannel->SetAllowAltSvc(allowAltSvc);
+ httpChannel->SetBeConservative(beConservative);
+ httpChannel->SetTlsFlags(tlsFlags);
+ httpChannel->SetInitialRwin(aInitialRwin);
+ httpChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
+
+ httpChannel->SetLaunchServiceWorkerStart(aLaunchServiceWorkerStart);
+ httpChannel->SetLaunchServiceWorkerEnd(aLaunchServiceWorkerEnd);
+ httpChannel->SetDispatchFetchEventStart(aDispatchFetchEventStart);
+ httpChannel->SetDispatchFetchEventEnd(aDispatchFetchEventEnd);
+ httpChannel->SetHandleFetchEventStart(aHandleFetchEventStart);
+ httpChannel->SetHandleFetchEventEnd(aHandleFetchEventEnd);
+
+ httpChannel->SetNavigationStartTimeStamp(aNavigationStartTimeStamp);
+ httpChannel->SetRequestContextID(aRequestContextID);
+
+ // Store the strong reference of channel and parent listener object until
+ // all the initialization procedure is complete without failure, to remove
+ // cycle reference in fail case and to avoid memory leakage.
+ mChannel = std::move(httpChannel);
+ mParentListener = std::move(parentListener);
+ mChannel->SetNotificationCallbacks(mParentListener);
+
+ MOZ_ASSERT(!mBgParent);
+ MOZ_ASSERT(mPromise.IsEmpty());
+ // Wait for HttpBackgrounChannel to continue the async open procedure.
+ ++mAsyncOpenBarrier;
+ RefPtr<HttpChannelParent> self = this;
+ WaitForBgParent(mChannel->ChannelId())
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self]() {
+ self->mRequest.Complete();
+ self->TryInvokeAsyncOpen(NS_OK);
+ },
+ [self](nsresult aStatus) {
+ self->mRequest.Complete();
+ self->TryInvokeAsyncOpen(aStatus);
+ })
+ ->Track(mRequest);
+ return true;
+}
+
+RefPtr<GenericNonExclusivePromise> HttpChannelParent::WaitForBgParent(
+ uint64_t aChannelId) {
+ LOG(("HttpChannelParent::WaitForBgParent [this=%p]\n", this));
+ MOZ_ASSERT(!mBgParent);
+
+ if (!mChannel && !mEarlyHintPreloaderId) {
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
+ BackgroundChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+ registrar->LinkHttpChannel(aChannelId, this);
+
+ if (mBgParent) {
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+ }
+
+ return mPromise.Ensure(__func__);
+}
+
+bool HttpChannelParent::ConnectChannel(const uint32_t& registrarId) {
+ nsresult rv;
+
+ LOG(
+ ("HttpChannelParent::ConnectChannel: Looking for a registered channel "
+ "[this=%p, id=%" PRIu32 "]\n",
+ this, registrarId));
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_LinkRedirectChannels(registrarId, this, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not find the http channel to connect its IPC parent");
+ // This makes the channel delete itself safely. It's the only thing
+ // we can do now, since this parent channel cannot be used and there is
+ // no other way to tell the child side there were something wrong.
+ Delete();
+ return true;
+ }
+
+ LOG((" found channel %p, rv=%08" PRIx32, channel.get(),
+ static_cast<uint32_t>(rv)));
+ mChannel = do_QueryObject(channel);
+ if (!mChannel) {
+ LOG((" but it's not HttpBaseChannel"));
+ Delete();
+ return true;
+ }
+
+ LOG((" and it is HttpBaseChannel %p", mChannel.get()));
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(this);
+ }
+
+ if (mPBOverride != kPBOverride_Unset) {
+ // redirected-to channel may not support PB
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(mPBOverride == kPBOverride_Private);
+ }
+ }
+
+ MOZ_ASSERT(!mBgParent);
+ MOZ_ASSERT(mPromise.IsEmpty());
+ // Waiting for background channel
+ RefPtr<HttpChannelParent> self = this;
+ WaitForBgParent(mChannel->ChannelId())
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self]() { self->mRequest.Complete(); },
+ [self](const nsresult& aResult) {
+ NS_ERROR("failed to establish the background channel");
+ self->mRequest.Complete();
+ })
+ ->Track(mRequest);
+ return true;
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSetPriority(
+ const int16_t& priority) {
+ LOG(("HttpChannelParent::RecvSetPriority [this=%p, priority=%d]\n", this,
+ priority));
+ AUTO_PROFILER_LABEL("HttpChannelParent::RecvSetPriority", NETWORK);
+
+ if (mChannel) {
+ mChannel->SetPriority(priority);
+ }
+
+ nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
+ do_QueryInterface(mRedirectChannel);
+ if (priorityRedirectChannel) priorityRedirectChannel->SetPriority(priority);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSetClassOfService(
+ const ClassOfService& cos) {
+ if (mChannel) {
+ mChannel->SetClassOfService(cos);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSuspend() {
+ LOG(("HttpChannelParent::RecvSuspend [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Suspend();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvResume() {
+ LOG(("HttpChannelParent::RecvResume [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Resume();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvCancel(
+ const nsresult& status, const uint32_t& requestBlockingReason,
+ const nsACString& reason, const mozilla::Maybe<nsCString>& logString) {
+ LOG(("HttpChannelParent::RecvCancel [this=%p, reason=%s]\n", this,
+ PromiseFlatCString(reason).get()));
+
+ // logging child cancel reason on the parent side
+ if (logString.isSome()) {
+ LOG(("HttpChannelParent::RecvCancel: %s", logString->get()));
+ }
+
+ // May receive cancel before channel has been constructed!
+ if (mChannel) {
+ mChannel->CancelWithReason(status, reason);
+
+ if (MOZ_UNLIKELY(requestBlockingReason !=
+ nsILoadInfo::BLOCKING_REASON_NONE)) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ loadInfo->SetRequestBlockingReason(requestBlockingReason);
+ }
+
+ // Once we receive |Cancel|, child will stop sending RecvBytesRead. Force
+ // the channel resumed if needed.
+ if (mSuspendedForFlowControl) {
+ LOG((" resume the channel due to e10s backpressure relief by cancel"));
+ Unused << mChannel->Resume();
+ mSuspendedForFlowControl = false;
+ }
+ } else if (!mIPCClosed) {
+ // Make sure that the child correctly delivers all stream listener
+ // notifications.
+ Unused << SendFailedAsyncOpen(status);
+ }
+
+ // We won't need flow control anymore. Toggle the flag to avoid |Suspend|
+ // since OnDataAvailable could be off-main-thread.
+ mCacheNeedFlowControlInitialized = true;
+ mNeedFlowControl = false;
+
+ // If the channel is cancelled before the redirect is completed
+ // RecvRedirect2Verify will not be called, so we must clear the callback.
+ if (mRedirectCallback) {
+ mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_UNEXPECTED);
+ mRedirectCallback = nullptr;
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvRedirect2Verify(
+ const nsresult& aResult, const RequestHeaderTuples& changedHeaders,
+ const uint32_t& aSourceRequestBlockingReason,
+ const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
+ const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
+ nsIURI* aAPIRedirectURI,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs) {
+ LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aResult)));
+
+ // Result from the child. If something fails here, we might overwrite a
+ // success with a further failure.
+ nsresult result = aResult;
+
+ // Local results.
+ nsresult rv;
+
+ if (NS_SUCCEEDED(result)) {
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannel);
+
+ if (newHttpChannel) {
+ if (aAPIRedirectURI) {
+ rv = newHttpChannel->RedirectTo(aAPIRedirectURI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
+ if (changedHeaders[i].mEmpty) {
+ rv = newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader);
+ } else {
+ rv = newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader,
+ changedHeaders[i].mValue,
+ changedHeaders[i].mMerge);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // A successfully redirected channel must have the LOAD_REPLACE flag.
+ MOZ_ASSERT(loadFlags & nsIChannel::LOAD_REPLACE);
+ if (loadFlags & nsIChannel::LOAD_REPLACE) {
+ newHttpChannel->SetLoadFlags(loadFlags);
+ }
+
+ if (aCorsPreflightArgs.isSome()) {
+ nsCOMPtr<nsIHttpChannelInternal> newInternalChannel =
+ do_QueryInterface(newHttpChannel);
+ MOZ_RELEASE_ASSERT(newInternalChannel);
+ const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
+ newInternalChannel->SetCorsPreflightParameters(args.unsafeHeaders(),
+ false);
+ }
+
+ if (aReferrerInfo) {
+ RefPtr<HttpBaseChannel> baseChannel = do_QueryObject(newHttpChannel);
+ MOZ_ASSERT(baseChannel);
+ if (baseChannel) {
+ // Referrer header is computed in child no need to recompute here
+ rv = baseChannel->SetReferrerInfoInternal(aReferrerInfo, false, false,
+ true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ if (aTargetLoadInfoForwarder.isSome()) {
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newHttpChannel->LoadInfo();
+ rv = MergeChildLoadInfoForwarder(aTargetLoadInfoForwarder.ref(),
+ newLoadInfo);
+ if (NS_FAILED(rv) && NS_SUCCEEDED(result)) {
+ result = rv;
+ }
+ }
+ }
+ }
+
+ // If the redirect is vetoed, reason is set on the source (current) channel's
+ // load info, so we must carry iver the change.
+ // The channel may have already been cleaned up, so there is nothing we can
+ // do.
+ if (MOZ_UNLIKELY(aSourceRequestBlockingReason !=
+ nsILoadInfo::BLOCKING_REASON_NONE) &&
+ mChannel) {
+ nsCOMPtr<nsILoadInfo> sourceLoadInfo = mChannel->LoadInfo();
+ sourceLoadInfo->SetRequestBlockingReason(aSourceRequestBlockingReason);
+ }
+
+ // Continue the verification procedure if child has veto the redirection.
+ if (NS_FAILED(result)) {
+ ContinueRedirect2Verify(result);
+ return IPC_OK();
+ }
+
+ // Wait for background channel ready on target channel
+ nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(redirectReg);
+
+ nsCOMPtr<nsIParentChannel> redirectParentChannel;
+ rv = redirectReg->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectParentChannel));
+ if (!redirectParentChannel) {
+ ContinueRedirect2Verify(rv);
+ return IPC_OK();
+ }
+
+ nsCOMPtr<nsIParentRedirectingChannel> redirectedParent =
+ do_QueryInterface(redirectParentChannel);
+ if (!redirectedParent) {
+ // Continue verification procedure if redirecting to non-Http protocol
+ ContinueRedirect2Verify(result);
+ return IPC_OK();
+ }
+
+ // Ask redirected channel if verification can proceed.
+ // ContinueRedirect2Verify will be invoked when redirected channel is ready.
+ redirectedParent->ContinueVerification(this);
+
+ return IPC_OK();
+}
+
+// from nsIParentRedirectingChannel
+NS_IMETHODIMP
+HttpChannelParent::ContinueVerification(
+ nsIAsyncVerifyRedirectReadyCallback* aCallback) {
+ LOG(("HttpChannelParent::ContinueVerification [this=%p callback=%p]\n", this,
+ aCallback));
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCallback);
+
+ // Continue the verification procedure if background channel is ready.
+ if (mBgParent) {
+ aCallback->ReadyToVerify(NS_OK);
+ return NS_OK;
+ }
+
+ // ConnectChannel must be received before Redirect2Verify.
+ MOZ_ASSERT(!mPromise.IsEmpty());
+
+ // Otherwise, wait for the background channel.
+ nsCOMPtr<nsIAsyncVerifyRedirectReadyCallback> callback = aCallback;
+ if (mChannel) {
+ WaitForBgParent(mChannel->ChannelId())
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [callback]() { callback->ReadyToVerify(NS_OK); },
+ [callback](const nsresult& aResult) {
+ NS_ERROR("failed to establish the background channel");
+ callback->ReadyToVerify(aResult);
+ });
+ } else {
+ // mChannel can be null for several reasons (AsyncOpenFailed, etc)
+ NS_ERROR("No channel for ContinueVerification");
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [callback] { callback->ReadyToVerify(NS_ERROR_FAILURE); }));
+ }
+ return NS_OK;
+}
+
+void HttpChannelParent::ContinueRedirect2Verify(const nsresult& aResult) {
+ LOG(
+ ("HttpChannelParent::ContinueRedirect2Verify "
+ "[this=%p result=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aResult)));
+
+ if (mRedirectCallback) {
+ LOG(
+ ("HttpChannelParent::ContinueRedirect2Verify call "
+ "OnRedirectVerifyCallback"
+ " [this=%p result=%" PRIx32 ", mRedirectCallback=%p]\n",
+ this, static_cast<uint32_t>(aResult), mRedirectCallback.get()));
+ mRedirectCallback->OnRedirectVerifyCallback(aResult);
+ mRedirectCallback = nullptr;
+ } else {
+ LOG(
+ ("RecvRedirect2Verify[%p]: NO CALLBACKS! | "
+ "mRedirectChannelId: %" PRIx64 ", mRedirectChannel: %p",
+ this, mRedirectChannelId, mRedirectChannel.get()));
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvDocumentChannelCleanup(
+ const bool& clearCacheEntry) {
+ CleanupBackgroundChannel(); // Background channel can be closed.
+ mChannel = nullptr; // Reclaim some memory sooner.
+ if (clearCacheEntry) {
+ mCacheEntry = nullptr; // Else we'll block other channels reading same URI
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(
+ nsIURI* uri, const mozilla::ipc::PrincipalInfo& requestingPrincipal,
+ const OriginAttributes& originAttributes) {
+ if (!uri) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ auto principalOrErr = PrincipalInfoToPrincipal(requestingPrincipal);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+ nsCORSListenerProxy::RemoveFromCorsPreflightCache(uri, principal,
+ originAttributes);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSetCookies(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, const bool& aFromHttp, nsTArray<CookieStruct>&& aCookies) {
+ net::PCookieServiceParent* csParent =
+ LoneManagedOrNullAsserts(Manager()->ManagedPCookieServiceParent());
+ NS_ENSURE_TRUE(csParent, IPC_OK());
+
+ auto* cs = static_cast<net::CookieServiceParent*>(csParent);
+
+ BrowsingContext* browsingContext = nullptr;
+ if (mBrowserParent) {
+ browsingContext = mBrowserParent->GetBrowsingContext();
+ }
+
+ return cs->SetCookies(nsCString(aBaseDomain), aOriginAttributes, aHost,
+ aFromHttp, aCookies, browsingContext);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+static ResourceTimingStructArgs GetTimingAttributes(HttpBaseChannel* aChannel) {
+ ResourceTimingStructArgs args;
+ TimeStamp timeStamp;
+ aChannel->GetDomainLookupStart(&timeStamp);
+ args.domainLookupStart() = timeStamp;
+ aChannel->GetDomainLookupEnd(&timeStamp);
+ args.domainLookupEnd() = timeStamp;
+ aChannel->GetConnectStart(&timeStamp);
+ args.connectStart() = timeStamp;
+ aChannel->GetTcpConnectEnd(&timeStamp);
+ args.tcpConnectEnd() = timeStamp;
+ aChannel->GetSecureConnectionStart(&timeStamp);
+ args.secureConnectionStart() = timeStamp;
+ aChannel->GetConnectEnd(&timeStamp);
+ args.connectEnd() = timeStamp;
+ aChannel->GetRequestStart(&timeStamp);
+ args.requestStart() = timeStamp;
+ aChannel->GetResponseStart(&timeStamp);
+ args.responseStart() = timeStamp;
+ aChannel->GetResponseEnd(&timeStamp);
+ args.responseEnd() = timeStamp;
+ aChannel->GetAsyncOpen(&timeStamp);
+ args.fetchStart() = timeStamp;
+ aChannel->GetRedirectStart(&timeStamp);
+ args.redirectStart() = timeStamp;
+ aChannel->GetRedirectEnd(&timeStamp);
+ args.redirectEnd() = timeStamp;
+
+ uint64_t size = 0;
+ aChannel->GetTransferSize(&size);
+ args.transferSize() = size;
+
+ aChannel->GetEncodedBodySize(&size);
+ args.encodedBodySize() = size;
+ // decodedBodySize can be computed in the child process so it doesn't need
+ // to be passed down.
+
+ aChannel->GetCacheReadStart(&timeStamp);
+ args.cacheReadStart() = timeStamp;
+
+ aChannel->GetCacheReadEnd(&timeStamp);
+ args.cacheReadEnd() = timeStamp;
+
+ aChannel->GetTransactionPending(&timeStamp);
+ args.transactionPending() = timeStamp;
+ return args;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ nsresult rv;
+
+ LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n", this,
+ aRequest));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<uint32_t> multiPartID;
+ bool isFirstPartOfMultiPart = false;
+ bool isLastPartOfMultiPart = false;
+ DebugOnly<bool> isMultiPart = false;
+
+ RefPtr<HttpBaseChannel> chan = do_QueryObject(aRequest);
+ if (!chan) {
+ if (nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
+ do_QueryInterface(aRequest)) {
+ isMultiPart = true;
+ nsCOMPtr<nsIChannel> baseChannel;
+ multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
+ chan = do_QueryObject(baseChannel);
+
+ uint32_t partID = 0;
+ multiPartChannel->GetPartID(&partID);
+ multiPartID = Some(partID);
+ multiPartChannel->GetIsFirstPart(&isFirstPartOfMultiPart);
+ multiPartChannel->GetIsLastPart(&isLastPartOfMultiPart);
+ } else if (nsCOMPtr<nsIViewSourceChannel> viewSourceChannel =
+ do_QueryInterface(aRequest)) {
+ chan = do_QueryObject(viewSourceChannel->GetInnerChannel());
+ }
+ }
+ MOZ_ASSERT(multiPartID || !isMultiPart, "Changed multi-part state?");
+
+ if (!chan) {
+ LOG((" aRequest is not HttpBaseChannel"));
+ NS_ERROR(
+ "Expecting only HttpBaseChannel as aRequest in "
+ "HttpChannelParent::OnStartRequest");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mAfterOnStartRequestBegun = true;
+
+ // Todo: re-enable when bug 1589749 is fixed.
+ /*MOZ_ASSERT(mChannel == chan,
+ "HttpChannelParent getting OnStartRequest from a different "
+ "HttpBaseChannel instance");*/
+
+ HttpChannelOnStartRequestArgs args;
+
+ // Send down any permissions/cookies which are relevant to this URL if we are
+ // performing a document load. We can't do that if mIPCClosed is set.
+ if (!mIPCClosed) {
+ PContentParent* pcp = Manager()->Manager();
+ MOZ_ASSERT(pcp, "We should have a manager if our IPC isn't closed");
+ DebugOnly<nsresult> rv =
+ static_cast<ContentParent*>(pcp)->AboutToLoadHttpFtpDocumentForChild(
+ chan, &args.shouldWaitForOnStartRequestSent());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ args.multiPartID() = multiPartID;
+ args.isFirstPartOfMultiPart() = isFirstPartOfMultiPart;
+ args.isLastPartOfMultiPart() = isLastPartOfMultiPart;
+
+ args.cacheExpirationTime() = nsICacheEntry::NO_EXPIRATION_TIME;
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(chan);
+
+ if (httpChannelImpl) {
+ httpChannelImpl->IsFromCache(&args.isFromCache());
+ httpChannelImpl->IsRacing(&args.isRacing());
+ httpChannelImpl->GetCacheEntryId(&args.cacheEntryId());
+ httpChannelImpl->GetCacheTokenFetchCount(&args.cacheFetchCount());
+ httpChannelImpl->GetCacheTokenExpirationTime(&args.cacheExpirationTime());
+ httpChannelImpl->GetProtocolVersion(args.protocolVersion());
+
+ mDataSentToChildProcess = httpChannelImpl->DataSentToChildProcess();
+
+ // If RCWN is enabled and cache wins, we can't use the ODA from socket
+ // process.
+ if (args.isRacing()) {
+ mDataSentToChildProcess =
+ httpChannelImpl->DataSentToChildProcess() && !args.isFromCache();
+ }
+ args.dataFromSocketProcess() = mDataSentToChildProcess;
+ }
+
+ // Propagate whether or not conversion should occur from the parent-side
+ // channel to the child-side channel. Then disable the parent-side
+ // conversion so that it only occurs in the child.
+ Unused << chan->GetApplyConversion(&args.applyConversion());
+ chan->SetApplyConversion(false);
+
+ // If we've already applied the conversion (as can happen if we installed
+ // a multipart converted), then don't apply it again on the child.
+ if (chan->HasAppliedConversion()) {
+ args.applyConversion() = false;
+ }
+
+ chan->GetStatus(&args.channelStatus());
+
+ // Keep the cache entry for future use when opening alternative streams.
+ // It could be already released by nsHttpChannel at that time.
+ nsCOMPtr<nsISupports> cacheEntry;
+
+ if (httpChannelImpl) {
+ httpChannelImpl->GetCacheToken(getter_AddRefs(cacheEntry));
+ mCacheEntry = do_QueryInterface(cacheEntry);
+ args.cacheEntryAvailable() = static_cast<bool>(mCacheEntry);
+
+ httpChannelImpl->GetCacheKey(&args.cacheKey());
+ httpChannelImpl->GetAlternativeDataType(args.altDataType());
+ }
+
+ args.altDataLength() = chan->GetAltDataLength();
+ args.deliveringAltData() = chan->IsDeliveringAltData();
+
+ args.securityInfo() = SecurityInfo();
+
+ chan->GetRedirectCount(&args.redirectCount());
+ chan->GetHasHTTPSRR(&args.hasHTTPSRR());
+
+ chan->GetIsProxyUsed(&args.isProxyUsed());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo,
+ &args.loadInfoForwarder());
+
+ nsHttpResponseHead* responseHead = chan->GetResponseHead();
+ bool useResponseHead = !!responseHead;
+ nsHttpResponseHead cleanedUpResponseHead;
+
+ if (responseHead &&
+ (responseHead->HasHeader(nsHttp::Set_Cookie) || multiPartID)) {
+ cleanedUpResponseHead = *responseHead;
+ cleanedUpResponseHead.ClearHeader(nsHttp::Set_Cookie);
+ if (multiPartID) {
+ nsCOMPtr<nsIChannel> multiPartChannel = do_QueryInterface(aRequest);
+ // For the multipart channel, use the parsed subtype instead. Note that
+ // `chan` is the underlying base channel of the multipart channel in this
+ // case, which is different from `multiPartChannel`.
+ MOZ_ASSERT(multiPartChannel);
+ nsAutoCString contentType;
+ multiPartChannel->GetContentType(contentType);
+ cleanedUpResponseHead.SetContentType(contentType);
+ }
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (!responseHead) {
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (chan->ChannelBlockedByOpaqueResponse() &&
+ chan->CachedOpaqueResponseBlockingPref()) {
+ responseHead->ClearHeaders();
+ }
+
+ chan->GetIsResolvedByTRR(&args.isResolvedByTRR());
+ chan->GetAllRedirectsSameOrigin(&args.allRedirectsSameOrigin());
+ chan->GetCrossOriginOpenerPolicy(&args.openerPolicy());
+ args.selfAddr() = chan->GetSelfAddr();
+ args.peerAddr() = chan->GetPeerAddr();
+ args.timing() = GetTimingAttributes(mChannel);
+ if (mOverrideReferrerInfo) {
+ args.overrideReferrerInfo() = ToRefPtr(std::move(mOverrideReferrerInfo));
+ }
+ if (!mCookie.IsEmpty()) {
+ args.cookie() = std::move(mCookie);
+ }
+
+ nsHttpRequestHead* requestHead = chan->GetRequestHead();
+ // !!! We need to lock headers and please don't forget to unlock them !!!
+ requestHead->Enter();
+
+ nsHttpHeaderArray cleanedUpRequestHeaders;
+ bool cleanedUpRequest = false;
+ if (requestHead->HasHeader(nsHttp::Cookie)) {
+ cleanedUpRequestHeaders = requestHead->Headers();
+ cleanedUpRequestHeaders.ClearHeader(nsHttp::Cookie);
+ cleanedUpRequest = true;
+ }
+
+ rv = NS_OK;
+
+ nsCOMPtr<nsICacheEntry> altDataSource;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel =
+ do_QueryInterface(static_cast<nsIChannel*>(mChannel.get()));
+ if (cacheChannel) {
+ for (const auto& pref : cacheChannel->PreferredAlternativeDataTypes()) {
+ if (pref.type() == args.altDataType() &&
+ pref.deliverAltData() ==
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::
+ SERIALIZE) {
+ altDataSource = mCacheEntry;
+ break;
+ }
+ }
+ }
+
+ nsIRequest::TRRMode effectiveMode = nsIRequest::TRR_DEFAULT_MODE;
+ mChannel->GetEffectiveTRRMode(&effectiveMode);
+ args.effectiveTRRMode() = effectiveMode;
+
+ TRRSkippedReason reason = TRRSkippedReason::TRR_UNSET;
+ mChannel->GetTrrSkipReason(&reason);
+ args.trrSkipReason() = reason;
+
+ if (mIPCClosed ||
+ !mBgParent->OnStartRequest(
+ *responseHead, useResponseHead,
+ cleanedUpRequest ? cleanedUpRequestHeaders : requestHead->Headers(),
+ args, altDataSource, chan->GetOnStartRequestStartTime())) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ requestHead->Exit();
+
+ // Need to wait for the cookies/permissions to content process, which is sent
+ // via PContent in AboutToLoadHttpFtpDocumentForChild. For multipart channel,
+ // send only one time since the cookies/permissions are the same.
+ if (NS_SUCCEEDED(rv) && args.shouldWaitForOnStartRequestSent() &&
+ multiPartID.valueOr(0) == 0) {
+ LOG(("HttpChannelParent::SendOnStartRequestSent\n"));
+ Unused << SendOnStartRequestSent();
+ }
+
+ if (!args.timing().domainLookupEnd().IsNull() &&
+ !args.timing().connectStart().IsNull()) {
+ nsAutoCString protocolVersion;
+ mChannel->GetProtocolVersion(protocolVersion);
+ uint32_t classOfServiceFlags = 0;
+ mChannel->GetClassFlags(&classOfServiceFlags);
+ nsAutoCString cosString;
+ ClassOfService::ToString(classOfServiceFlags, cosString);
+ nsAutoCString key(
+ nsPrintfCString("%s_%s", protocolVersion.get(), cosString.get()));
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_DNS_END_TO_CONNECT_START_EXP_MS, key,
+ args.timing().domainLookupEnd(), args.timing().connectStart());
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("HttpChannelParent::OnStopRequest: [this=%p aRequest=%p status=%" PRIx32
+ "]\n",
+ this, aRequest, static_cast<uint32_t>(aStatusCode)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(nullptr);
+ }
+
+ nsHttpHeaderArray* responseTrailer = mChannel->GetResponseTrailers();
+
+ nsTArray<ConsoleReportCollected> consoleReports;
+
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(mChannel);
+ TimeStamp onStopRequestStart;
+ if (httpChannel) {
+ httpChannel->StealConsoleReports(consoleReports);
+ onStopRequestStart = httpChannel->GetOnStopRequestStartTime();
+ }
+
+ // Either IPC channel is closed or background channel
+ // is ready to send OnStopRequest.
+ MOZ_ASSERT(mIPCClosed || mBgParent);
+
+ if (mDataSentToChildProcess) {
+ if (mIPCClosed || !mBgParent ||
+ !mBgParent->OnConsoleReport(consoleReports)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+ }
+
+ // If we're handling a multi-part stream, then send this directly
+ // over PHttpChannel to make synchronization easier.
+ if (mIPCClosed || !mBgParent ||
+ !mBgParent->OnStopRequest(
+ aStatusCode, GetTimingAttributes(mChannel),
+ responseTrailer ? *responseTrailer : nsHttpHeaderArray(),
+ consoleReports, onStopRequestStart)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (NeedFlowControl()) {
+ bool isLocal = false;
+ NetAddr peerAddr = mChannel->GetPeerAddr();
+
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ isLocal = (peerAddr.raw.family == PR_AF_LOCAL);
+#endif
+
+ isLocal = isLocal || peerAddr.IsLoopbackAddr();
+
+ if (!isLocal) {
+ if (!mHasSuspendedByBackPressure) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ NotSuspended);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ Suspended);
+
+ // Only analyze non-local suspended cases, which we are interested in.
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_CP_TYPE,
+ loadInfo->InternalContentPolicyType());
+ }
+ } else {
+ if (!mHasSuspendedByBackPressure) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ NotSuspendedLocal);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ SuspendedLocal);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIMultiPartChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnAfterLastPart(nsresult aStatus) {
+ LOG(("HttpChannelParent::OnAfterLastPart [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If IPC channel is closed, there is nothing we can do. Just return NS_OK.
+ if (mIPCClosed) {
+ return NS_OK;
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // OnAfterLastPart.
+ MOZ_ASSERT(mBgParent);
+
+ if (!mBgParent || !mBgParent->OnAfterLastPart(aStatus)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("HttpChannelParent::OnDataAvailable [this=%p aRequest=%p offset=%" PRIu64
+ " count=%" PRIu32 "]\n",
+ this, aRequest, aOffset, aCount));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mDataSentToChildProcess) {
+ uint32_t n;
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &n);
+ }
+
+ nsresult channelStatus = NS_OK;
+ mChannel->GetStatus(&channelStatus);
+
+ nsresult transportStatus = NS_NET_STATUS_RECEIVING_FROM;
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ TimeStamp onDataAvailableStart = TimeStamp::Now();
+ if (httpChannelImpl) {
+ if (httpChannelImpl->IsReadingFromCache()) {
+ transportStatus = NS_NET_STATUS_READING;
+ }
+ onDataAvailableStart = httpChannelImpl->GetDataAvailableStartTime();
+ }
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Either IPC channel is closed or background channel
+ // is ready to send OnTransportAndData.
+ MOZ_ASSERT(mIPCClosed || mBgParent);
+
+ if (mIPCClosed || !mBgParent ||
+ !mBgParent->OnTransportAndData(channelStatus, transportStatus, aOffset,
+ aCount, data, onDataAvailableStart)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t count = static_cast<int32_t>(aCount);
+
+ if (NeedFlowControl()) {
+ // We're going to run out of sending window size
+ if (mSendWindowSize > 0 && mSendWindowSize <= count) {
+ MOZ_ASSERT(!mSuspendedForFlowControl);
+ LOG((" suspend the channel due to e10s backpressure"));
+ Unused << mChannel->Suspend();
+ mSuspendedForFlowControl = true;
+ mHasSuspendedByBackPressure = true;
+ } else if (!mResumedTimestamp.IsNull()) {
+ // Calculate the delay when the first packet arrived after resume
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_DELAY_TIME_MS,
+ mResumedTimestamp);
+ mResumedTimestamp = TimeStamp();
+ }
+ mSendWindowSize -= count;
+ }
+
+ return NS_OK;
+}
+
+bool HttpChannelParent::NeedFlowControl() {
+ if (mCacheNeedFlowControlInitialized) {
+ return mNeedFlowControl;
+ }
+
+ int64_t contentLength = -1;
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+
+ // By design, we won't trigger the flow control if
+ // a. pref-out
+ // b. the resource is from cache or partial cache
+ // c. the resource is small
+ // d. data will be sent from socket process to child process directly
+ // Note that we served the cached resource first for partical cache, which is
+ // ignored here since we only take the first ODA into consideration.
+ if (gHttpHandler->SendWindowSize() == 0 || !httpChannelImpl ||
+ httpChannelImpl->IsReadingFromCache() ||
+ NS_FAILED(httpChannelImpl->GetContentLength(&contentLength)) ||
+ contentLength < gHttpHandler->SendWindowSize() ||
+ mDataSentToChildProcess) {
+ mNeedFlowControl = false;
+ }
+ mCacheNeedFlowControlInitialized = true;
+ return mNeedFlowControl;
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvBytesRead(
+ const int32_t& aCount) {
+ if (!NeedFlowControl()) {
+ return IPC_OK();
+ }
+
+ LOG(("HttpChannelParent::RecvBytesRead [this=%p count=%" PRId32 "]\n", this,
+ aCount));
+
+ if (mSendWindowSize <= 0 && mSendWindowSize + aCount > 0) {
+ MOZ_ASSERT(mSuspendedForFlowControl);
+ LOG((" resume the channel due to e10s backpressure relief"));
+ Unused << mChannel->Resume();
+ mSuspendedForFlowControl = false;
+
+ mResumedTimestamp = TimeStamp::Now();
+ }
+ mSendWindowSize += aCount;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvOpenOriginalCacheInputStream() {
+ if (mIPCClosed) {
+ return IPC_OK();
+ }
+ Maybe<IPCStream> ipcStream;
+ if (mCacheEntry) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+ if (NS_SUCCEEDED(rv)) {
+ Unused << mozilla::ipc::SerializeIPCStream(
+ inputStream.forget(), ipcStream, /* aAllowLazy */ false);
+ }
+ }
+
+ Unused << SendOriginalCacheInputStreamAvailable(ipcStream);
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIProgressEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnProgress(nsIRequest* aRequest, int64_t aProgress,
+ int64_t aProgressMax) {
+ LOG(("HttpChannelParent::OnProgress [this=%p progress=%" PRId64 "max=%" PRId64
+ "]\n",
+ this, aProgress, aProgressMax));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If IPC channel is closed, there is nothing we can do. Just return NS_OK.
+ if (mIPCClosed) {
+ return NS_OK;
+ }
+
+ // If it indicates this precedes OnDataAvailable, child can derive the value
+ // in ODA.
+ if (mIgnoreProgress) {
+ mIgnoreProgress = false;
+ return NS_OK;
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // OnProgress.
+ MOZ_ASSERT(mBgParent);
+
+ // Send OnProgress events to the child for data upload progress notifications
+ // (i.e. status == NS_NET_STATUS_SENDING_TO) or if the channel has
+ // LOAD_BACKGROUND set.
+ if (!mBgParent || !mBgParent->OnProgress(aProgress, aProgressMax)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStatus(nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aStatusArg) {
+ LOG(("HttpChannelParent::OnStatus [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If IPC channel is closed, there is nothing we can do. Just return NS_OK.
+ if (mIPCClosed) {
+ return NS_OK;
+ }
+
+ // If this precedes OnDataAvailable, transportStatus will be derived in ODA.
+ if (aStatus == NS_NET_STATUS_RECEIVING_FROM ||
+ aStatus == NS_NET_STATUS_READING) {
+ // The transport status and progress generated by ODA will be coalesced
+ // into one IPC message. Therefore, we can ignore the next OnProgress event
+ // since it is generated by ODA as well.
+ mIgnoreProgress = true;
+ return NS_OK;
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // OnStatus.
+ MOZ_ASSERT(mIPCClosed || mBgParent);
+
+ // Otherwise, send to child now
+ if (!mBgParent || !mBgParent->OnStatus(aStatus)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ LOG(("HttpChannelParent::SetParentListener [this=%p aListener=%p]\n", this,
+ aListener));
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(!mParentListener,
+ "SetParentListener should only be called for "
+ "new HttpChannelParents after a redirect, when "
+ "mParentListener is null.");
+ mParentListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ LOG(("HttpChannelParent::SetClassifierMatchedInfo [this=%p]\n", this));
+ if (!mIPCClosed) {
+ MOZ_ASSERT(mBgParent);
+ Unused << mBgParent->OnSetClassifierMatchedInfo(aList, aProvider,
+ aFullHash);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ LOG(("HttpChannelParent::SetClassifierMatchedTrackingInfo [this=%p]\n",
+ this));
+ if (!mIPCClosed) {
+ MOZ_ASSERT(mBgParent);
+ Unused << mBgParent->OnSetClassifierMatchedTrackingInfo(aLists,
+ aFullHashes);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ LOG(
+ ("HttpChannelParent::NotifyClassificationFlags "
+ "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+ if (!mIPCClosed) {
+ MOZ_ASSERT(mBgParent);
+ Unused << mBgParent->OnNotifyClassificationFlags(aClassificationFlags,
+ aIsThirdParty);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::Delete() {
+ if (!mIPCClosed) Unused << DoSendDeleteSelf();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+bool HttpChannelParent::IsRedirectDueToAuthRetry(uint32_t redirectFlags) {
+ return (redirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentRedirectingChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::StartRedirect(nsIChannel* newChannel, uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ nsresult rv;
+
+ LOG(("HttpChannelParent::StartRedirect [this=%p, newChannel=%p callback=%p]",
+ this, newChannel, callback));
+
+ // Register the new channel and obtain id for it
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier();
+ rv = registrar->RegisterChannel(newChannel, mRedirectChannelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("Registered %p channel under id=%" PRIx64, newChannel,
+ mRedirectChannelId));
+
+ if (mIPCClosed) {
+ return NS_BINDING_ABORTED;
+ }
+
+ // If this is an internal redirect for service worker interception or
+ // internal redirect due to auth retries, then hide it from the child
+ // process. The original e10s interception code was not designed with this
+ // in mind and its not necessary to replace the HttpChannelChild/Parent
+ // objects in this case.
+ if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ nsCOMPtr<nsIInterceptedChannel> oldIntercepted =
+ do_QueryInterface(static_cast<nsIChannel*>(mChannel.get()));
+ nsCOMPtr<nsIInterceptedChannel> newIntercepted =
+ do_QueryInterface(newChannel);
+
+ // 1. We only want to hide the special internal redirects from
+ // nsHttpChannel to InterceptedHttpChannel.
+ // 2. We want to allow through internal redirects
+ // initiated from the InterceptedHttpChannel even if they are to another
+ // InterceptedHttpChannel, except the interception reset, since
+ // corresponding HttpChannelChild/Parent objects can be reused for reset
+ // case.
+ // 3. If this is an internal redirect due to auth retry then we will
+ // hide it from the child process
+
+ if ((!oldIntercepted && newIntercepted) ||
+ (oldIntercepted && !newIntercepted && oldIntercepted->IsReset()) ||
+ (IsRedirectDueToAuthRetry(redirectFlags))) {
+ // We need to move across the reserved and initial client information
+ // to the new channel. Normally this would be handled by the child
+ // ClientChannelHelper, but that is not notified of this redirect since
+ // we're not propagating it back to the child process.
+ nsCOMPtr<nsILoadInfo> oldLoadInfo = mChannel->LoadInfo();
+
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
+
+ Maybe<ClientInfo> reservedClientInfo(
+ oldLoadInfo->GetReservedClientInfo());
+ if (reservedClientInfo.isSome()) {
+ newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
+ }
+
+ Maybe<ClientInfo> initialClientInfo(oldLoadInfo->GetInitialClientInfo());
+ if (initialClientInfo.isSome()) {
+ newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
+ }
+
+ // If this is ServiceWorker fallback redirect, info HttpChannelChild to
+ // detach StreamFilters. Otherwise StreamFilters will be attached twice
+ // on the same HttpChannelChild when opening the new nsHttpChannel.
+ if (oldIntercepted) {
+ Unused << DetachStreamFilters();
+ }
+
+ // Re-link the HttpChannelParent to the new channel.
+ nsCOMPtr<nsIChannel> linkedChannel;
+ rv = NS_LinkRedirectChannels(mRedirectChannelId, this,
+ getter_AddRefs(linkedChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(linkedChannel == newChannel);
+
+ // We immediately store the channel as our nested mChannel.
+ // None of the redirect IPC messaging takes place.
+ mChannel = do_QueryObject(newChannel);
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+ }
+
+ // Sending down the original URI, because that is the URI we have
+ // to construct the channel from - this is the URI we've been actually
+ // redirected to. URI of the channel may be an inner channel URI.
+ // URI of the channel will be reconstructed by the protocol handler
+ // on the child process, no need to send it then.
+ nsCOMPtr<nsIURI> newOriginalURI;
+ newChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
+
+ uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
+ MOZ_ALWAYS_SUCCEEDS(newChannel->GetLoadFlags(&newLoadFlags));
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(SecurityInfo());
+
+ // If the channel is a HTTP channel, we also want to inform the child
+ // about the parent's channelId attribute, so that both parent and child
+ // share the same ID. Useful for monitoring channel activity in devtools.
+ uint64_t channelId = 0;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (httpChannel) {
+ rv = httpChannel->GetChannelId(&channelId);
+ NS_ENSURE_SUCCESS(rv, NS_BINDING_ABORTED);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+
+ ParentLoadInfoForwarderArgs loadInfoForwarderArg;
+ mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo,
+ &loadInfoForwarderArg);
+
+ nsHttpResponseHead* responseHead = mChannel->GetResponseHead();
+
+ nsHttpResponseHead cleanedUpResponseHead;
+ if (responseHead && responseHead->HasHeader(nsHttp::Set_Cookie)) {
+ cleanedUpResponseHead = *responseHead;
+ cleanedUpResponseHead.ClearHeader(nsHttp::Set_Cookie);
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (!responseHead) {
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (!mIPCClosed) {
+ if (!SendRedirect1Begin(mRedirectChannelId, newOriginalURI, newLoadFlags,
+ redirectFlags, loadInfoForwarderArg, *responseHead,
+ securityInfo, channelId, mChannel->GetPeerAddr(),
+ GetTimingAttributes(mChannel))) {
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ // Result is handled in RecvRedirect2Verify above
+
+ mRedirectChannel = newChannel;
+ mRedirectCallback = callback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::CompleteRedirect(nsresult status) {
+ LOG(("HttpChannelParent::CompleteRedirect [this=%p status=0x%X]\n", this,
+ static_cast<uint32_t>(status)));
+
+ // If this was an internal redirect for a service worker interception then
+ // we will not have a redirecting channel here. Hide this redirect from
+ // the child.
+ if (!mRedirectChannel) {
+ return NS_OK;
+ }
+
+ if (!mIPCClosed) {
+ // TODO: check return value: assume child dead if failed
+ if (NS_SUCCEEDED(status)) {
+ Unused << SendRedirect3Complete();
+ } else {
+ Unused << SendRedirectFailed(status);
+ }
+ }
+
+ mRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+nsresult HttpChannelParent::OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ // We need to make sure the child does not call SendDocumentChannelCleanup()
+ // before opening the altOutputStream, because that clears mCacheEntry.
+ if (!mCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv =
+ mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
+ if (NS_SUCCEEDED(rv)) {
+ mCacheEntry->SetMetaDataElement("alt-data-from-child", "1");
+ }
+ return rv;
+}
+
+already_AddRefed<nsITransportSecurityInfo> HttpChannelParent::SecurityInfo() {
+ if (!mChannel) {
+ return nullptr;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ return securityInfo.forget();
+}
+
+bool HttpChannelParent::DoSendDeleteSelf() {
+ mIPCClosed = true;
+ bool rv = SendDeleteSelf();
+
+ CleanupBackgroundChannel();
+
+ return rv;
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvDeletingChannel() {
+ // We need to ensure that the parent channel will not be sending any more IPC
+ // messages after this, as the child is going away. DoSendDeleteSelf will
+ // set mIPCClosed = true;
+ if (!DoSendDeleteSelf()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelSecurityWarningReporter
+//-----------------------------------------------------------------------------
+
+nsresult HttpChannelParent::ReportSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) {
+ if (mIPCClosed || NS_WARN_IF(!SendReportSecurityMessage(
+ nsString(aMessageTag), nsString(aMessageCategory)))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncVerifyRedirectReadyCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::ReadyToVerify(nsresult aResult) {
+ LOG(("HttpChannelParent::ReadyToVerify [this=%p result=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aResult)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ContinueRedirect2Verify(aResult);
+
+ return NS_OK;
+}
+
+void HttpChannelParent::DoSendSetPriority(int16_t aValue) {
+ if (!mIPCClosed) {
+ Unused << SendSetPriority(aValue);
+ }
+}
+
+nsresult HttpChannelParent::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ if (mIPCClosed ||
+ NS_WARN_IF(!SendLogBlockedCORSRequest(
+ nsString(aMessage), nsCString(aCategory), aIsWarning))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult HttpChannelParent::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) {
+ if (mIPCClosed || NS_WARN_IF(!SendLogMimeTypeMismatch(
+ nsCString(aMessageName), aWarning, nsString(aURL),
+ nsString(aContentType)))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aRedirectFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback) {
+ LOG(
+ ("HttpChannelParent::AsyncOnChannelRedirect [this=%p, old=%p, "
+ "new=%p, flags=%u]",
+ this, aOldChannel, aNewChannel, aRedirectFlags));
+
+ return StartRedirect(aNewChannel, aRedirectFlags, aCallback);
+}
+
+//-----------------------------------------------------------------------------
+// nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnRedirectResult(nsresult status) {
+ LOG(("HttpChannelParent::OnRedirectResult [this=%p, status=0x%X]", this,
+ static_cast<uint32_t>(status)));
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIParentChannel> redirectChannel;
+ if (mRedirectChannelId) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ rv = registrar->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectChannel));
+ if (NS_FAILED(rv) || !redirectChannel) {
+ // Redirect might get canceled before we got AsyncOnChannelRedirect
+ LOG(("Registered parent channel not found under id=%" PRIx64,
+ mRedirectChannelId));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = registrar->GetRegisteredChannel(mRedirectChannelId,
+ getter_AddRefs(newChannel));
+ MOZ_ASSERT(newChannel, "Already registered channel not found");
+
+ if (NS_SUCCEEDED(rv)) {
+ newChannel->Cancel(NS_BINDING_ABORTED);
+ }
+ }
+
+ // Release all previously registered channels, they are no longer need to be
+ // kept in the registrar from this moment.
+ registrar->DeregisterChannels(mRedirectChannelId);
+
+ mRedirectChannelId = 0;
+ }
+
+ if (!redirectChannel) {
+ if (NS_FAILED(rv)) {
+ status = rv;
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+ }
+
+ CompleteRedirect(status);
+
+ if (NS_SUCCEEDED(status)) {
+ if (!SameCOMIdentity(redirectChannel,
+ static_cast<nsIParentRedirectingChannel*>(this))) {
+ Delete();
+ mParentListener->SetListenerAfterRedirect(redirectChannel);
+ redirectChannel->SetParentListener(mParentListener);
+ }
+ } else if (redirectChannel) {
+ // Delete the redirect target channel: continue using old channel
+ redirectChannel->Delete();
+ }
+
+ return NS_OK;
+}
+
+void HttpChannelParent::OverrideReferrerInfoDuringBeginConnect(
+ nsIReferrerInfo* aReferrerInfo) {
+ MOZ_ASSERT(aReferrerInfo);
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+
+ mOverrideReferrerInfo = aReferrerInfo;
+}
+
+auto HttpChannelParent::AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint)
+ -> RefPtr<ChildEndpointPromise> {
+ LOG(("HttpChannelParent::AttachStreamFilter [this=%p]", this));
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+
+ if (mIPCClosed) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // SendAttachStreamFilter.
+ MOZ_ASSERT(mBgParent);
+ return InvokeAsync(mBgParent->GetBackgroundTarget(), mBgParent.get(),
+ __func__, &HttpBackgroundChannelParent::AttachStreamFilter,
+ std::move(aParentEndpoint), std::move(aChildEndpoint));
+}
+
+auto HttpChannelParent::DetachStreamFilters() -> RefPtr<GenericPromise> {
+ LOG(("HttpChannelParent::DeattachStreamFilter [this=%p]", this));
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+
+ if (NS_WARN_IF(mIPCClosed)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ MOZ_ASSERT(mBgParent);
+ return InvokeAsync(mBgParent->GetBackgroundTarget(), mBgParent.get(),
+ __func__,
+ &HttpBackgroundChannelParent::DetachStreamFilters);
+}
+
+void HttpChannelParent::SetHttpChannelFromEarlyHintPreloader(
+ HttpBaseChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ if (mChannel) {
+ MOZ_ASSERT(false, "SetHttpChannel called with mChannel aready set");
+ return;
+ }
+
+ mChannel = aChannel;
+}
+
+void HttpChannelParent::SetCookie(nsCString&& aCookie) {
+ LOG(("HttpChannelParent::SetCookie [this=%p]", this));
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+ MOZ_ASSERT(mCookie.IsEmpty());
+
+ // The loadGroup of the channel in the parent process could be null in the
+ // XPCShell content process test, see test_cookiejars_wrap.js. In this case,
+ // we cannot explicitly set the loadGroup for the parent channel because it's
+ // created from the content process. To workaround this, we add a testing pref
+ // to skip this check.
+ if (!Preferences::GetBool(
+ "network.cookie.skip_browsing_context_check_in_parent_for_testing") &&
+ mChannel->IsBrowsingContextDiscarded()) {
+ return;
+ }
+ mCookie = std::move(aCookie);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
new file mode 100644
index 0000000000..fdd55f9a1d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -0,0 +1,333 @@
+/* -*- 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_HttpChannelParent_h
+#define mozilla_net_HttpChannelParent_h
+
+#include "HttpBaseChannel.h"
+#include "nsHttp.h"
+#include "mozilla/net/PHttpChannelParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/MozPromise.h"
+#include "nsIParentRedirectingChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIChannelEventSink.h"
+#include "nsIRedirectResultListener.h"
+#include "nsHttpChannel.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIURI.h"
+
+class nsICacheEntry;
+
+#define HTTP_CHANNEL_PARENT_IID \
+ { \
+ 0x982b2372, 0x7aa5, 0x4e8a, { \
+ 0xbd, 0x9f, 0x89, 0x74, 0xd7, 0xf0, 0x58, 0xeb \
+ } \
+ }
+
+namespace mozilla {
+
+namespace dom {
+class BrowserParent;
+} // namespace dom
+
+namespace net {
+
+class HttpBackgroundChannelParent;
+class ParentChannelListener;
+class ChannelEventQueue;
+
+class HttpChannelParent final : public nsIInterfaceRequestor,
+ public PHttpChannelParent,
+ public nsIParentRedirectingChannel,
+ public nsIProgressEventSink,
+ public HttpChannelSecurityWarningReporter,
+ public nsIAsyncVerifyRedirectReadyCallback,
+ public nsIChannelEventSink,
+ public nsIRedirectResultListener,
+ public nsIMultiPartChannelListener {
+ virtual ~HttpChannelParent();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIPARENTREDIRECTINGCHANNEL
+ NS_DECL_NSIPROGRESSEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIASYNCVERIFYREDIRECTREADYCALLBACK
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_IID)
+
+ HttpChannelParent(dom::BrowserParent* iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus);
+
+ [[nodiscard]] bool Init(const HttpChannelCreationArgs& aArgs);
+
+ // Forwarded to nsHttpChannel::SetApplyConversion.
+ void SetApplyConversion(bool aApplyConversion) {
+ if (mChannel) {
+ mChannel->SetApplyConversion(aApplyConversion);
+ }
+ }
+
+ [[nodiscard]] nsresult OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval);
+
+ // Callbacks for each asynchronous tasks required in AsyncOpen
+ // procedure, will call InvokeAsyncOpen when all the expected
+ // tasks is finished successfully or when any failure happened.
+ // @see mAsyncOpenBarrier.
+ void TryInvokeAsyncOpen(nsresult aRv);
+
+ void InvokeAsyncOpen(nsresult rv);
+
+ void InvokeEarlyHintPreloader(nsresult rv, uint64_t aEarlyHintPreloaderId);
+
+ // Calls SendSetPriority if mIPCClosed is false.
+ void DoSendSetPriority(int16_t aValue);
+
+ // Callback while background channel is ready.
+ void OnBackgroundParentReady(HttpBackgroundChannelParent* aBgParent);
+ // Callback while background channel is destroyed.
+ void OnBackgroundParentDestroyed();
+
+ base::ProcessId OtherPid() const;
+
+ // Inform the child actor that our referrer info was modified late during
+ // BeginConnect.
+ void OverrideReferrerInfoDuringBeginConnect(nsIReferrerInfo* aReferrerInfo);
+
+ // Set the cookie string, which will be informed to the child actor during
+ // PHttpBackgroundChannel::OnStartRequest. Note that CookieService also sends
+ // the information to all actors via PContent, a main thread IPC, which could
+ // be slower than background IPC PHttpBackgroundChannel::OnStartRequest.
+ // Therefore, another cookie notification via PBackground is needed to
+ // guarantee the listener in child has the necessary cookies before
+ // OnStartRequest.
+ void SetCookie(nsCString&& aCookie);
+
+ using ChildEndpointPromise =
+ MozPromise<ipc::Endpoint<extensions::PStreamFilterChild>, bool, true>;
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint);
+ [[nodiscard]] RefPtr<GenericPromise> DetachStreamFilters();
+
+ // Should only be called from EarlyHintPreloader. mChannel should be null at
+ // the point of calling. Sets mChannel to aChannel. Used by the
+ // EarlyHintPreloader to redirect the channel to this parent as soon as the
+ // final channel becomes available after all http redirects.
+ void SetHttpChannelFromEarlyHintPreloader(HttpBaseChannel* aChannel);
+
+ protected:
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during redirects.
+ [[nodiscard]] bool ConnectChannel(const uint32_t& registrarId);
+
+ [[nodiscard]] bool DoAsyncOpen(
+ nsIURI* uri, nsIURI* originalUri, nsIURI* docUri,
+ nsIReferrerInfo* aReferrerInfo, nsIURI* aAPIRedirectToURI,
+ nsIURI* topWindowUri, const uint32_t& loadFlags,
+ const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+ const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+ const int16_t& priority, const ClassOfService& classOfService,
+ const uint8_t& redirectionLimit, const bool& allowSTS,
+ const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+ const uint64_t& startPos, const nsCString& entityID,
+ const bool& allowSpdy, const bool& allowHttp3, const bool& allowAltSvc,
+ const bool& beConservative, const bool& bypassProxy,
+ const uint32_t& tlsFlags, const LoadInfoArgs& aLoadInfoArgs,
+ const uint32_t& aCacheKey, const uint64_t& aRequestContextID,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
+ const bool& aAllowStaleCacheContent,
+ const bool& aPreferCacheLoadOverBypass, const nsCString& aContentTypeHint,
+ const dom::RequestMode& aRequestMode, const uint32_t& aRedirectMode,
+ const uint64_t& aChannelId, const nsString& aIntegrityMetadata,
+ const uint64_t& aContentWindowId,
+ const nsTArray<PreferredAlternativeDataTypeParams>&
+ aPreferredAlternativeTypes,
+ const uint64_t& aBrowserId, const TimeStamp& aLaunchServiceWorkerStart,
+ const TimeStamp& aLaunchServiceWorkerEnd,
+ const TimeStamp& aDispatchFetchEventStart,
+ const TimeStamp& aDispatchFetchEventEnd,
+ const TimeStamp& aHandleFetchEventStart,
+ const TimeStamp& aHandleFetchEventEnd,
+ const bool& aForceMainDocumentChannel,
+ const TimeStamp& aNavigationStartTimeStamp,
+ const uint64_t& aEarlyHintPreloaderId,
+ const nsAString& aClassicScriptHintCharset,
+ const nsAString& aDocumentCharacterSet,
+ const bool& aIsUserAgentHeaderModified);
+
+ virtual mozilla::ipc::IPCResult RecvSetPriority(
+ const int16_t& priority) override;
+ virtual mozilla::ipc::IPCResult RecvSetClassOfService(
+ const ClassOfService& cos) override;
+ virtual mozilla::ipc::IPCResult RecvSuspend() override;
+ virtual mozilla::ipc::IPCResult RecvResume() override;
+ virtual mozilla::ipc::IPCResult RecvCancel(
+ const nsresult& status, const uint32_t& requestBlockingReason,
+ const nsACString& reason,
+ const mozilla::Maybe<nsCString>& logString) override;
+ virtual mozilla::ipc::IPCResult RecvRedirect2Verify(
+ const nsresult& result, const RequestHeaderTuples& changedHeaders,
+ const uint32_t& aSourceRequestBlockingReason,
+ const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
+ const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
+ nsIURI* apiRedirectUri,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs) override;
+ virtual mozilla::ipc::IPCResult RecvDocumentChannelCleanup(
+ const bool& clearCacheEntry) override;
+ virtual mozilla::ipc::IPCResult RecvRemoveCorsPreflightCacheEntry(
+ nsIURI* uri, const mozilla::ipc::PrincipalInfo& requestingPrincipal,
+ const OriginAttributes& originAttributes) override;
+ virtual mozilla::ipc::IPCResult RecvSetCookies(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, const bool& aFromHttp,
+ nsTArray<CookieStruct>&& aCookies) override;
+ virtual mozilla::ipc::IPCResult RecvBytesRead(const int32_t& aCount) override;
+ virtual mozilla::ipc::IPCResult RecvOpenOriginalCacheInputStream() override;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ friend class ParentChannelListener;
+ RefPtr<mozilla::dom::BrowserParent> mBrowserParent;
+
+ [[nodiscard]] nsresult ReportSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) override;
+ nsresult LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning = false) override;
+ nsresult LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ // Calls SendDeleteSelf and sets mIPCClosed to true because we should not
+ // send any more messages after that. Bug 1274886
+ [[nodiscard]] bool DoSendDeleteSelf();
+ // Called to notify the parent channel to not send any more IPC messages.
+ virtual mozilla::ipc::IPCResult RecvDeletingChannel() override;
+
+ private:
+ already_AddRefed<nsITransportSecurityInfo> SecurityInfo();
+
+ // final step for Redirect2Verify procedure, will be invoked while both
+ // redirecting and redirected channel are ready or any error happened.
+ // OnRedirectVerifyCallback will be invoked for finishing the async
+ // redirect verification procedure.
+ void ContinueRedirect2Verify(const nsresult& aResult);
+
+ void AsyncOpenFailed(nsresult aRv);
+
+ // Request to pair with a HttpBackgroundChannelParent with the same channel
+ // id, a promise will be returned so the caller can append callbacks on it.
+ // If called multiple times before mBgParent is available, the same promise
+ // will be returned and the callbacks will be invoked in order.
+ [[nodiscard]] RefPtr<GenericNonExclusivePromise> WaitForBgParent(
+ uint64_t aChannelId);
+
+ // Remove the association with background channel after main-thread IPC
+ // is about to be destroyed or no further event is going to be sent, i.e.,
+ // DocumentChannelCleanup.
+ void CleanupBackgroundChannel();
+
+ // Check if the channel needs to enable the flow control on the IPC channel.
+ // That is, we may suspend the channel if the ODA-s to child process are not
+ // consumed quickly enough. Otherwise, memory explosion could happen.
+ bool NeedFlowControl();
+
+ bool IsRedirectDueToAuthRetry(uint32_t redirectFlags);
+
+ int32_t mSendWindowSize;
+
+ friend class HttpBackgroundChannelParent;
+
+ uint64_t mEarlyHintPreloaderId{};
+
+ RefPtr<HttpBaseChannel> mChannel;
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ RefPtr<nsHttpHandler> mHttpHandler;
+
+ RefPtr<ParentChannelListener> mParentListener;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ RefPtr<HttpBackgroundChannelParent> mBgParent;
+
+ MozPromiseHolder<GenericNonExclusivePromise> mPromise;
+ MozPromiseRequestHolder<GenericNonExclusivePromise> mRequest;
+
+ // To calculate the delay caused by the e10s back-pressure suspension
+ TimeStamp mResumedTimestamp;
+
+ Atomic<bool> mIPCClosed; // PHttpChannel actor has been Closed()
+
+ // Corresponding redirect channel registrar Id. 0 means redirection is not
+ // started.
+ uint64_t mRedirectChannelId = 0;
+
+ PBOverrideStatus mPBOverride;
+
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+
+ // The referrer info, set during nsHttpChannel::BeginConnect, to override the
+ // original one. This info will be sent in OnStartRequest.
+ nsCOMPtr<nsIReferrerInfo> mOverrideReferrerInfo;
+
+ // The cookie string in Set-Cookie header. This info will be sent in
+ // OnStartRequest.
+ nsCString mCookie;
+
+ // OnStatus is always called before OnProgress.
+ // Set true in OnStatus if next OnProgress can be ignored
+ // since the information can be recontructed from ODA.
+ uint8_t mIgnoreProgress : 1;
+
+ uint8_t mHasSuspendedByBackPressure : 1;
+
+ // Set if we get the result of and cache |mNeedFlowControl|
+ uint8_t mCacheNeedFlowControlInitialized : 1;
+ uint8_t mNeedFlowControl : 1;
+ uint8_t mSuspendedForFlowControl : 1;
+
+ // Defaults to false. Is set to true at the begining of OnStartRequest.
+ // Used to ensure methods can't be called before OnStartRequest.
+ uint8_t mAfterOnStartRequestBegun : 1;
+
+ // Number of events to wait before actually invoking AsyncOpen on the main
+ // channel. For each asynchronous step required before InvokeAsyncOpen, should
+ // increase 1 to mAsyncOpenBarrier and invoke TryInvokeAsyncOpen after
+ // finished. This attribute is main thread only.
+ uint8_t mAsyncOpenBarrier = 0;
+
+ // When true, ODAs are sent from the socket process to the child process
+ // directly.
+ uint8_t mDataSentToChildProcess : 1;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelParent, HTTP_CHANNEL_PARENT_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpChannelParent_h
diff --git a/netwerk/protocol/http/HttpConnectionBase.cpp b/netwerk/protocol/http/HttpConnectionBase.cpp
new file mode 100644
index 0000000000..a94bc839ba
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionBase.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#define TLS_EARLY_DATA_NOT_AVAILABLE 0
+#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
+#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
+
+#include "mozilla/Telemetry.h"
+#include "HttpConnectionBase.h"
+#include "nsHttpHandler.h"
+#include "nsIClassOfService.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <public>
+//-----------------------------------------------------------------------------
+
+HttpConnectionBase::HttpConnectionBase() {
+ LOG(("Creating HttpConnectionBase @%p\n", this));
+}
+
+void HttpConnectionBase::BootstrapTimings(TimingStruct times) {
+ mBootstrappedTimingsSet = true;
+ mBootstrappedTimings = times;
+}
+
+void HttpConnectionBase::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aCallbacks) {
+ MutexAutoLock lock(mCallbacksLock);
+ // This is called both on and off the main thread. For JS-implemented
+ // callbacks, we requires that the call happen on the main thread, but
+ // for C++-implemented callbacks we don't care. Use a pointer holder with
+ // strict checking disabled.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "nsHttpConnection::mCallbacks", aCallbacks, false);
+}
+
+void HttpConnectionBase::SetTrafficCategory(HttpTrafficCategory aCategory) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (aCategory == HttpTrafficCategory::eInvalid ||
+ mTrafficCategory.Contains(aCategory)) {
+ return;
+ }
+ Unused << mTrafficCategory.AppendElement(aCategory);
+}
+
+void HttpConnectionBase::ChangeConnectionState(ConnectionState aState) {
+ LOG(("HttpConnectionBase::ChangeConnectionState this=%p (%d->%d)", this,
+ static_cast<uint32_t>(mConnectionState), static_cast<uint32_t>(aState)));
+
+ // The state can't move backward.
+ if (aState <= mConnectionState) {
+ return;
+ }
+
+ mConnectionState = aState;
+}
+
+void HttpConnectionBase::RecordConnectionCloseTelemetry(nsresult aReason) {
+ /**
+ *
+ * The returned telemetry key has the format:
+ * "Version_EndToEndSSL_IsTrrServiceChannel_ExperienceState_ConnectionState"
+ *
+ * - Version: The HTTP version of the connection.
+ * - EndToEndSSL: Indicates whether SSL encryption is end-to-end.
+ * - IsTrrServiceChannel: Specifies if the connection is used to send TRR
+ * requests.
+ * - ExperienceState: ConnectionExperienceState
+ * - ConnectionState: The connection state before closing.
+ */
+ auto key = nsPrintfCString("%d_%d_%d_%d_%d", static_cast<uint32_t>(Version()),
+ mConnInfo->EndToEndSSL(),
+ mConnInfo->GetIsTrrServiceChannel(),
+ static_cast<uint32_t>(mExperienceState),
+ static_cast<uint32_t>(mConnectionState));
+ SetCloseReason(ToCloseReason(aReason));
+ LOG(("RecordConnectionCloseTelemetry key=%s reason=%d\n", key.get(),
+ static_cast<uint32_t>(mCloseReason)));
+ Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_CLOSE_REASON, key,
+ static_cast<uint32_t>(mCloseReason));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionBase.h b/netwerk/protocol/http/HttpConnectionBase.h
new file mode 100644
index 0000000000..a4ccac735f
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionBase.h
@@ -0,0 +1,246 @@
+/* -*- 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 HttpConnectionBase_h__
+#define HttpConnectionBase_h__
+
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+class Http3WebTransportSession;
+
+enum class ConnectionState : uint32_t {
+ HALF_OPEN = 0,
+ INITED,
+ TLS_HANDSHAKING,
+ ZERORTT,
+ TRANSFERING,
+ CLOSED
+};
+
+enum class ConnectionExperienceState : uint32_t {
+ Not_Experienced = 0,
+ First_Request_Sent = (1 << 0),
+ First_Response_Received = (1 << 1),
+ Experienced = (1 << 2),
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ConnectionExperienceState);
+
+// 1dcc863e-db90-4652-a1fe-13fea0b54e46
+#define HTTPCONNECTIONBASE_IID \
+ { \
+ 0x437e7d26, 0xa2fd, 0x49f2, { \
+ 0xb3, 0x7c, 0x84, 0x23, 0xf0, 0x94, 0x72, 0x36 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection - represents a connection to a HTTP server (or proxy)
+//
+// NOTE: this objects lives on the socket thread only. it should not be
+// accessed from any other thread.
+//-----------------------------------------------------------------------------
+
+class HttpConnectionBase : public nsSupportsWeakReference {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPCONNECTIONBASE_IID)
+
+ HttpConnectionBase();
+
+ // Activate causes the given transaction to be processed on this
+ // connection. It fails if there is already an existing transaction unless
+ // a multiplexing protocol such as SPDY is being used
+ [[nodiscard]] virtual nsresult Activate(nsAHttpTransaction*, uint32_t caps,
+ int32_t pri) = 0;
+
+ // Close the underlying socket transport.
+ virtual void Close(nsresult reason, bool aIsShutdown = false) = 0;
+
+ virtual bool CanReuse() = 0; // can this connection be reused?
+ virtual bool CanDirectlyActivate() = 0;
+
+ virtual void DontReuse() = 0;
+
+ virtual nsAHttpTransaction* Transaction() = 0;
+ nsHttpConnectionInfo* ConnectionInfo() { return mConnInfo; }
+
+ virtual void CloseTransaction(nsAHttpTransaction*, nsresult,
+ bool aIsShutdown = false) = 0;
+
+ [[nodiscard]] virtual nsresult OnHeadersAvailable(nsAHttpTransaction*,
+ nsHttpRequestHead*,
+ nsHttpResponseHead*,
+ bool* reset) = 0;
+
+ [[nodiscard]] virtual nsresult TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) = 0;
+
+ Http3WebTransportSession* GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ return nullptr;
+ }
+
+ virtual bool UsingSpdy() { return false; }
+ virtual bool UsingHttp3() { return false; }
+
+ virtual void SetTransactionCaps(uint32_t aCaps) { mTransactionCaps = aCaps; }
+
+ virtual void PrintDiagnostics(nsCString& log) = 0;
+
+ // IsExperienced() returns true when the connection has started at least one
+ // non null HTTP transaction of any version.
+ bool IsExperienced() { return mExperienced; }
+
+ virtual bool TestJoinConnection(const nsACString& hostname, int32_t port) = 0;
+ virtual bool JoinConnection(const nsACString& hostname, int32_t port) = 0;
+
+ // Return true when the socket this connection is using has not been
+ // authenticated using a client certificate. Before SSL negotiation
+ // has finished this returns false.
+ virtual bool NoClientCertAuth() const { return true; }
+
+ // HTTP/2 websocket support
+ virtual WebSocketSupport GetWebSocketSupport() {
+ return WebSocketSupport::NO_SUPPORT;
+ }
+
+ void GetConnectionInfo(nsHttpConnectionInfo** ci) {
+ *ci = do_AddRef(mConnInfo).take();
+ }
+ virtual void GetTLSSocketControl(nsITLSSocketControl** result) = 0;
+
+ [[nodiscard]] virtual nsresult ResumeSend() = 0;
+ [[nodiscard]] virtual nsresult ResumeRecv() = 0;
+ [[nodiscard]] virtual nsresult ForceSend() = 0;
+ [[nodiscard]] virtual nsresult ForceRecv() = 0;
+ virtual HttpVersion Version() = 0;
+ virtual bool IsProxyConnectInProgress() = 0;
+ virtual bool LastTransactionExpectedNoContent() = 0;
+ virtual void SetLastTransactionExpectedNoContent(bool) = 0;
+ virtual int64_t BytesWritten() = 0; // includes TLS
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
+ void SetTrafficCategory(HttpTrafficCategory);
+
+ void BootstrapTimings(TimingStruct times);
+
+ virtual bool IsPersistent() = 0;
+ virtual bool IsReused() = 0;
+ [[nodiscard]] virtual nsresult PushBack(const char* data,
+ uint32_t length) = 0;
+ PRIntervalTime Rtt() { return mRtt; }
+ virtual void SetEvent(nsresult aStatus) = 0;
+
+ virtual nsISocketTransport* Transport() { return nullptr; }
+
+ virtual nsresult GetSelfAddr(NetAddr* addr) = 0;
+ virtual nsresult GetPeerAddr(NetAddr* addr) = 0;
+ virtual bool ResolvedByTRR() = 0;
+ virtual nsIRequest::TRRMode EffectiveTRRMode() = 0;
+ virtual TRRSkippedReason TRRSkipReason() = 0;
+ virtual bool GetEchConfigUsed() = 0;
+ virtual PRIntervalTime LastWriteTime() = 0;
+
+ void ChangeConnectionState(ConnectionState aState);
+ void SetCloseReason(ConnectionCloseReason aReason) {
+ if (mCloseReason == ConnectionCloseReason::UNSET) {
+ mCloseReason = aReason;
+ }
+ }
+
+ void RecordConnectionCloseTelemetry(nsresult aReason);
+
+ protected:
+ // The capabailities associated with the most recent transaction
+ uint32_t mTransactionCaps{0};
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool mExperienced{false};
+ // Used to track whether this connection is serving the first request.
+ bool mHasFirstHttpTransaction{false};
+
+ bool mBootstrappedTimingsSet{false};
+ TimingStruct mBootstrappedTimings;
+
+ Mutex mCallbacksLock MOZ_UNANNOTATED{"nsHttpConnection::mCallbacksLock"};
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mCallbacks;
+
+ nsTArray<HttpTrafficCategory> mTrafficCategory;
+ PRIntervalTime mRtt{0};
+ nsresult mErrorBeforeConnect = NS_OK;
+
+ ConnectionState mConnectionState = ConnectionState::HALF_OPEN;
+
+ // Represent if the connection has served more than one request.
+ ConnectionExperienceState mExperienceState =
+ ConnectionExperienceState::Not_Experienced;
+
+ ConnectionCloseReason mCloseReason = ConnectionCloseReason::UNSET;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpConnectionBase, HTTPCONNECTIONBASE_IID)
+
+#define NS_DECL_HTTPCONNECTIONBASE \
+ [[nodiscard]] nsresult Activate(nsAHttpTransaction*, uint32_t, int32_t) \
+ override; \
+ [[nodiscard]] nsresult OnHeadersAvailable( \
+ nsAHttpTransaction*, nsHttpRequestHead*, nsHttpResponseHead*, \
+ bool* reset) override; \
+ [[nodiscard]] nsresult TakeTransport( \
+ nsISocketTransport**, nsIAsyncInputStream**, nsIAsyncOutputStream**) \
+ override; \
+ void Close(nsresult, bool aIsShutdown = false) override; \
+ bool CanReuse() override; \
+ bool CanDirectlyActivate() override; \
+ void DontReuse() override; \
+ void CloseTransaction(nsAHttpTransaction*, nsresult, \
+ bool aIsShutdown = false) override; \
+ void PrintDiagnostics(nsCString&) override; \
+ bool TestJoinConnection(const nsACString&, int32_t) override; \
+ bool JoinConnection(const nsACString&, int32_t) override; \
+ void GetTLSSocketControl(nsITLSSocketControl** result) override; \
+ [[nodiscard]] nsresult ResumeSend() override; \
+ [[nodiscard]] nsresult ResumeRecv() override; \
+ [[nodiscard]] nsresult ForceSend() override; \
+ [[nodiscard]] nsresult ForceRecv() override; \
+ HttpVersion Version() override; \
+ bool IsProxyConnectInProgress() override; \
+ bool LastTransactionExpectedNoContent() override; \
+ void SetLastTransactionExpectedNoContent(bool val) override; \
+ bool IsPersistent() override; \
+ bool IsReused() override; \
+ [[nodiscard]] nsresult PushBack(const char* data, uint32_t length) override; \
+ void SetEvent(nsresult aStatus) override; \
+ virtual nsAHttpTransaction* Transaction() override; \
+ PRIntervalTime LastWriteTime() override;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpConnectionBase_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrChild.cpp b/netwerk/protocol/http/HttpConnectionMgrChild.cpp
new file mode 100644
index 0000000000..96756ca035
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrChild.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpConnectionMgrChild.h"
+#include "HttpTransactionChild.h"
+#include "AltSvcTransactionChild.h"
+#include "EventTokenBucket.h"
+#include "mozilla/net/WebSocketConnectionChild.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpHandler.h"
+#include "nsISpeculativeConnect.h"
+
+namespace mozilla::net {
+
+HttpConnectionMgrChild::HttpConnectionMgrChild()
+ : mConnMgr(gHttpHandler->ConnMgr()) {
+ MOZ_ASSERT(mConnMgr);
+}
+
+HttpConnectionMgrChild::~HttpConnectionMgrChild() {
+ LOG(("HttpConnectionMgrChild dtor:%p", this));
+}
+
+void HttpConnectionMgrChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpConnectionMgrChild::ActorDestroy [this=%p]\n", this));
+}
+
+mozilla::ipc::IPCResult
+HttpConnectionMgrChild::RecvDoShiftReloadConnectionCleanupWithConnInfo(
+ const HttpConnectionInfoCloneArgs& aArgs) {
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aArgs);
+ nsresult rv = mConnMgr->DoShiftReloadConnectionCleanupWithConnInfo(cinfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("HttpConnectionMgrChild::DoShiftReloadConnectionCleanupWithConnInfo "
+ "failed "
+ "(%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvUpdateCurrentBrowserId(
+ const uint64_t& aId) {
+ mConnMgr->UpdateCurrentBrowserId(aId);
+ return IPC_OK();
+}
+
+nsHttpTransaction* ToRealHttpTransaction(PHttpTransactionChild* aTrans) {
+ HttpTransactionChild* transChild = static_cast<HttpTransactionChild*>(aTrans);
+ LOG(("ToRealHttpTransaction: [transChild=%p] \n", transChild));
+ RefPtr<nsHttpTransaction> trans = transChild->GetHttpTransaction();
+ MOZ_ASSERT(trans);
+ return trans;
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvAddTransaction(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority) {
+ Unused << mConnMgr->AddTransaction(ToRealHttpTransaction(aTrans), aPriority);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+HttpConnectionMgrChild::RecvAddTransactionWithStickyConn(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority,
+ PHttpTransactionChild* aTransWithStickyConn) {
+ Unused << mConnMgr->AddTransactionWithStickyConn(
+ ToRealHttpTransaction(aTrans), aPriority,
+ ToRealHttpTransaction(aTransWithStickyConn));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvRescheduleTransaction(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority) {
+ Unused << mConnMgr->RescheduleTransaction(ToRealHttpTransaction(aTrans),
+ aPriority);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+HttpConnectionMgrChild::RecvUpdateClassOfServiceOnTransaction(
+ PHttpTransactionChild* aTrans, const ClassOfService& aClassOfService) {
+ mConnMgr->UpdateClassOfServiceOnTransaction(ToRealHttpTransaction(aTrans),
+ aClassOfService);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvCancelTransaction(
+ PHttpTransactionChild* aTrans, const nsresult& aReason) {
+ Unused << mConnMgr->CancelTransaction(ToRealHttpTransaction(aTrans), aReason);
+ return IPC_OK();
+}
+
+namespace {
+
+class SpeculativeConnectionOverrider final
+ : public nsIInterfaceRequestor,
+ public nsISpeculativeConnectionOverrider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+
+ explicit SpeculativeConnectionOverrider(
+ SpeculativeConnectionOverriderArgs&& aArgs)
+ : mArgs(std::move(aArgs)) {}
+
+ private:
+ virtual ~SpeculativeConnectionOverrider() = default;
+
+ SpeculativeConnectionOverriderArgs mArgs;
+};
+
+NS_IMPL_ISUPPORTS(SpeculativeConnectionOverrider, nsIInterfaceRequestor,
+ nsISpeculativeConnectionOverrider)
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetInterface(const nsIID& iid, void** result) {
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetIgnoreIdle(bool* aIgnoreIdle) {
+ *aIgnoreIdle = mArgs.ignoreIdle();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetParallelSpeculativeConnectLimit(
+ uint32_t* aParallelSpeculativeConnectLimit) {
+ *aParallelSpeculativeConnectLimit = mArgs.parallelSpeculativeConnectLimit();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetIsFromPredictor(bool* aIsFromPredictor) {
+ *aIsFromPredictor = mArgs.isFromPredictor();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetAllow1918(bool* aAllow) {
+ *aAllow = mArgs.allow1918();
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvSpeculativeConnect(
+ const HttpConnectionInfoCloneArgs& aConnInfo,
+ Maybe<SpeculativeConnectionOverriderArgs> aOverriderArgs, uint32_t aCaps,
+ Maybe<PAltSvcTransactionChild*> aTrans, const bool& aFetchHTTPSRR) {
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aConnInfo);
+ nsCOMPtr<nsIInterfaceRequestor> overrider =
+ aOverriderArgs
+ ? new SpeculativeConnectionOverrider(std::move(aOverriderArgs.ref()))
+ : nullptr;
+ RefPtr<SpeculativeTransaction> trans;
+ if (aTrans) {
+ trans = static_cast<AltSvcTransactionChild*>(*aTrans)->CreateTransaction();
+ }
+
+ Unused << mConnMgr->SpeculativeConnect(cinfo, overrider, aCaps, trans,
+ aFetchHTTPSRR);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvStartWebSocketConnection(
+ PHttpTransactionChild* aTransWithStickyConn, uint32_t aListenerId) {
+ RefPtr<WebSocketConnectionChild> child = new WebSocketConnectionChild();
+ child->Init(aListenerId);
+ nsCOMPtr<nsIHttpUpgradeListener> listener =
+ static_cast<nsIHttpUpgradeListener*>(child.get());
+ Unused << mConnMgr->CompleteUpgrade(
+ ToRealHttpTransaction(aTransWithStickyConn), listener);
+ return IPC_OK();
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpConnectionMgrChild.h b/netwerk/protocol/http/HttpConnectionMgrChild.h
new file mode 100644
index 0000000000..2279f9ec46
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrChild.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionMgrChild_h__
+#define HttpConnectionMgrChild_h__
+
+#include "mozilla/net/PHttpConnectionMgrChild.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla::net {
+
+class nsHttpConnectionMgr;
+
+class HttpConnectionMgrChild final : public PHttpConnectionMgrChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(HttpConnectionMgrChild, override)
+
+ explicit HttpConnectionMgrChild();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvDoShiftReloadConnectionCleanupWithConnInfo(
+ const HttpConnectionInfoCloneArgs& aArgs);
+ mozilla::ipc::IPCResult RecvUpdateCurrentBrowserId(const uint64_t& aId);
+ mozilla::ipc::IPCResult RecvAddTransaction(PHttpTransactionChild* aTrans,
+ const int32_t& aPriority);
+ mozilla::ipc::IPCResult RecvAddTransactionWithStickyConn(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority,
+ PHttpTransactionChild* aTransWithStickyConn);
+ mozilla::ipc::IPCResult RecvRescheduleTransaction(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority);
+ mozilla::ipc::IPCResult RecvUpdateClassOfServiceOnTransaction(
+ PHttpTransactionChild* aTrans, const ClassOfService& aClassOfService);
+ mozilla::ipc::IPCResult RecvCancelTransaction(PHttpTransactionChild* aTrans,
+ const nsresult& aReason);
+ mozilla::ipc::IPCResult RecvSpeculativeConnect(
+ const HttpConnectionInfoCloneArgs& aConnInfo,
+ Maybe<SpeculativeConnectionOverriderArgs> aOverriderArgs, uint32_t aCaps,
+ Maybe<PAltSvcTransactionChild*> aTrans, const bool& aFetchHTTPSRR);
+ mozilla::ipc::IPCResult RecvStartWebSocketConnection(
+ PHttpTransactionChild* aTransWithStickyConn, uint32_t aListenerId);
+
+ private:
+ virtual ~HttpConnectionMgrChild();
+
+ RefPtr<nsHttpConnectionMgr> mConnMgr;
+};
+
+} // namespace mozilla::net
+
+#endif // HttpConnectionMgrChild_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrParent.cpp b/netwerk/protocol/http/HttpConnectionMgrParent.cpp
new file mode 100644
index 0000000000..594fea20ec
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrParent.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpConnectionMgrParent.h"
+#include "AltSvcTransactionParent.h"
+#include "mozilla/net/HttpTransactionParent.h"
+#include "mozilla/net/WebSocketConnectionParent.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIOService.h"
+#include "nsQueryObject.h"
+
+namespace mozilla::net {
+
+nsTHashMap<uint32_t, nsCOMPtr<nsIHttpUpgradeListener>>
+ HttpConnectionMgrParent::sHttpUpgradeListenerMap;
+uint32_t HttpConnectionMgrParent::sListenerId = 0;
+StaticMutex HttpConnectionMgrParent::sLock;
+
+// static
+uint32_t HttpConnectionMgrParent::AddHttpUpgradeListenerToMap(
+ nsIHttpUpgradeListener* aListener) {
+ StaticMutexAutoLock lock(sLock);
+ uint32_t id = sListenerId++;
+ sHttpUpgradeListenerMap.InsertOrUpdate(id, nsCOMPtr{aListener});
+ return id;
+}
+
+// static
+void HttpConnectionMgrParent::RemoveHttpUpgradeListenerFromMap(uint32_t aId) {
+ StaticMutexAutoLock lock(sLock);
+ sHttpUpgradeListenerMap.Remove(aId);
+}
+
+// static
+Maybe<nsCOMPtr<nsIHttpUpgradeListener>>
+HttpConnectionMgrParent::GetAndRemoveHttpUpgradeListener(uint32_t aId) {
+ StaticMutexAutoLock lock(sLock);
+ return sHttpUpgradeListenerMap.Extract(aId);
+}
+
+NS_IMPL_ISUPPORTS0(HttpConnectionMgrParent)
+
+nsresult HttpConnectionMgrParent::Init(
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConnections,
+ uint16_t maxPersistentConnectionsPerHost,
+ uint16_t maxPersistentConnectionsPerProxy, uint16_t maxRequestDelay,
+ bool throttleEnabled, uint32_t throttleVersion, uint32_t throttleSuspendFor,
+ uint32_t throttleResumeFor, uint32_t throttleReadLimit,
+ uint32_t throttleReadInterval, uint32_t throttleHoldTime,
+ uint32_t throttleMaxTime, bool beConservativeForProxy) {
+ // We don't have to do anything here. nsHttpConnectionMgr in socket process is
+ // initialized by nsHttpHandler.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::Shutdown() {
+ if (mShutDown) {
+ return NS_OK;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mShutDown = true;
+ Unused << Send__delete__(this);
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::UpdateRequestTokenBucket(
+ EventTokenBucket* aBucket) {
+ // We don't have to do anything here. UpdateRequestTokenBucket() will be
+ // triggered by pref change in socket process.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::DoShiftReloadConnectionCleanup() {
+ // Do nothing here. DoShiftReloadConnectionCleanup() will be triggered by
+ // observer notification or pref change in socket process.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCi) {
+ if (!aCi) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ HttpConnectionInfoCloneArgs connInfoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(aCi, connInfoArgs);
+
+ RefPtr<HttpConnectionMgrParent> self = this;
+ auto task = [self, connInfoArgs{std::move(connInfoArgs)}]() {
+ Unused << self->SendDoShiftReloadConnectionCleanupWithConnInfo(
+ connInfoArgs);
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::PruneDeadConnections() {
+ // Do nothing here. PruneDeadConnections() will be triggered by
+ // observer notification or pref change in socket process.
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::AbortAndCloseAllConnections(int32_t, ARefBase*) {
+ // Do nothing here. AbortAndCloseAllConnections() will be triggered by
+ // observer notification in socket process.
+}
+
+nsresult HttpConnectionMgrParent::UpdateParam(nsParamName name,
+ uint16_t value) {
+ // Do nothing here. UpdateParam() will be triggered by pref change in
+ // socket process.
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::PrintDiagnostics() {
+ // Do nothing here. PrintDiagnostics() will be triggered by pref change in
+ // socket process.
+}
+
+nsresult HttpConnectionMgrParent::UpdateCurrentBrowserId(uint64_t aId) {
+ RefPtr<HttpConnectionMgrParent> self = this;
+ auto task = [self, aId]() {
+ Unused << self->SendUpdateCurrentBrowserId(aId);
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::AddTransaction(HttpTransactionShell* aTrans,
+ int32_t aPriority) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendAddTransaction(WrapNotNull(aTrans->AsHttpTransactionParent()),
+ aPriority);
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::AddTransactionWithStickyConn(
+ HttpTransactionShell* aTrans, int32_t aPriority,
+ HttpTransactionShell* aTransWithStickyConn) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendAddTransactionWithStickyConn(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aPriority,
+ WrapNotNull(aTransWithStickyConn->AsHttpTransactionParent()));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::RescheduleTransaction(
+ HttpTransactionShell* aTrans, int32_t aPriority) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendRescheduleTransaction(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aPriority);
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell* aTrans, const ClassOfService& aClassOfService) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return;
+ }
+
+ Unused << SendUpdateClassOfServiceOnTransaction(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aClassOfService);
+}
+
+nsresult HttpConnectionMgrParent::CancelTransaction(
+ HttpTransactionShell* aTrans, nsresult aReason) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendCancelTransaction(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aReason);
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::ReclaimConnection(HttpConnectionBase*) {
+ MOZ_ASSERT_UNREACHABLE("ReclaimConnection should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::ProcessPendingQ(nsHttpConnectionInfo*) {
+ MOZ_ASSERT_UNREACHABLE("ProcessPendingQ should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::ProcessPendingQ() {
+ MOZ_ASSERT_UNREACHABLE("ProcessPendingQ should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::GetSocketThreadTarget(nsIEventTarget**) {
+ MOZ_ASSERT_UNREACHABLE("GetSocketThreadTarget should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::SpeculativeConnect(
+ nsHttpConnectionInfo* aConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, SpeculativeTransaction* aTransaction, bool aFetchHTTPSRR) {
+ NS_ENSURE_ARG_POINTER(aConnInfo);
+
+ nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+ do_GetInterface(aCallbacks);
+ Maybe<SpeculativeConnectionOverriderArgs> overriderArgs;
+ if (overrider) {
+ overriderArgs.emplace();
+ overriderArgs->parallelSpeculativeConnectLimit() =
+ overrider->GetParallelSpeculativeConnectLimit();
+ overriderArgs->ignoreIdle() = overrider->GetIgnoreIdle();
+ overriderArgs->isFromPredictor() = overrider->GetIsFromPredictor();
+ overriderArgs->allow1918() = overrider->GetAllow1918();
+ }
+
+ HttpConnectionInfoCloneArgs connInfo;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(aConnInfo, connInfo);
+ RefPtr<AltSvcTransactionParent> trans = do_QueryObject(aTransaction);
+ RefPtr<HttpConnectionMgrParent> self = this;
+ auto task = [self, connInfo{std::move(connInfo)},
+ overriderArgs{std::move(overriderArgs)}, aCaps,
+ trans{std::move(trans)}, aFetchHTTPSRR]() {
+ Maybe<NotNull<AltSvcTransactionParent*>> maybeTrans;
+ if (trans) {
+ maybeTrans.emplace(WrapNotNull(trans.get()));
+ }
+ Unused << self->SendSpeculativeConnect(connInfo, overriderArgs, aCaps,
+ maybeTrans, aFetchHTTPSRR);
+ };
+
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::VerifyTraffic() {
+ // Do nothing here. VerifyTraffic() will be triggered by observer notification
+ // in socket process.
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
+ // Do nothing.
+}
+
+void HttpConnectionMgrParent::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ // Do nothing.
+}
+
+nsresult HttpConnectionMgrParent::ClearConnectionHistory() {
+ // Do nothing here. ClearConnectionHistory() will be triggered by
+ // observer notification in socket process.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::CompleteUpgrade(
+ HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
+ MOZ_ASSERT(aTrans->AsHttpTransactionParent());
+
+ if (!CanSend()) {
+ // OnUpgradeFailed is expected to be called on socket thread.
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (target) {
+ nsCOMPtr<nsIHttpUpgradeListener> listener = aUpgradeListener;
+ target->Dispatch(NS_NewRunnableFunction(
+ "HttpConnectionMgrParent::CompleteUpgrade", [listener]() {
+ Unused << listener->OnUpgradeFailed(NS_ERROR_NOT_AVAILABLE);
+ }));
+ }
+ return NS_OK;
+ }
+
+ // We need to link the id and the upgrade listener here, so
+ // WebSocketConnectionParent can connect to the listener correctly later.
+ uint32_t id = AddHttpUpgradeListenerToMap(aUpgradeListener);
+ Unused << SendStartWebSocketConnection(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), id);
+ return NS_OK;
+}
+
+nsHttpConnectionMgr* HttpConnectionMgrParent::AsHttpConnectionMgr() {
+ return nullptr;
+}
+
+HttpConnectionMgrParent* HttpConnectionMgrParent::AsHttpConnectionMgrParent() {
+ return this;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpConnectionMgrParent.h b/netwerk/protocol/http/HttpConnectionMgrParent.h
new file mode 100644
index 0000000000..f884dfd0a5
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrParent.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionMgrParent_h__
+#define HttpConnectionMgrParent_h__
+
+#include "HttpConnectionMgrShell.h"
+#include "mozilla/net/PHttpConnectionMgrParent.h"
+#include "mozilla/StaticMutex.h"
+
+namespace mozilla::net {
+
+// HttpConnectionMgrParent plays the role of nsHttpConnectionMgr and delegates
+// the work to the nsHttpConnectionMgr in socket process.
+class HttpConnectionMgrParent final : public PHttpConnectionMgrParent,
+ public HttpConnectionMgrShell {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_HTTPCONNECTIONMGRSHELL
+
+ explicit HttpConnectionMgrParent() = default;
+
+ static uint32_t AddHttpUpgradeListenerToMap(
+ nsIHttpUpgradeListener* aListener);
+ static void RemoveHttpUpgradeListenerFromMap(uint32_t aId);
+ static Maybe<nsCOMPtr<nsIHttpUpgradeListener>>
+ GetAndRemoveHttpUpgradeListener(uint32_t aId);
+
+ private:
+ virtual ~HttpConnectionMgrParent() = default;
+
+ bool mShutDown{false};
+ static uint32_t sListenerId;
+ static StaticMutex sLock MOZ_UNANNOTATED;
+ static nsTHashMap<uint32_t, nsCOMPtr<nsIHttpUpgradeListener>>
+ sHttpUpgradeListenerMap;
+};
+
+} // namespace mozilla::net
+
+#endif // HttpConnectionMgrParent_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrShell.h b/netwerk/protocol/http/HttpConnectionMgrShell.h
new file mode 100644
index 0000000000..9c58ad5e63
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrShell.h
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionMgrShell_h__
+#define HttpConnectionMgrShell_h__
+
+#include "nsISupports.h"
+
+class nsIEventTarget;
+class nsIHttpUpgradeListener;
+class nsIInterfaceRequestor;
+
+namespace mozilla::net {
+
+class ARefBase;
+class EventTokenBucket;
+class HttpTransactionShell;
+class nsHttpConnectionInfo;
+class HttpConnectionBase;
+class nsHttpConnectionMgr;
+class HttpConnectionMgrParent;
+class SpeculativeTransaction;
+class ClassOfService;
+
+//----------------------------------------------------------------------------
+// Abstract base class for HTTP connection manager in chrome process
+//----------------------------------------------------------------------------
+
+// f5379ff9-2758-4bec-9992-2351c258aed6
+#define HTTPCONNECTIONMGRSHELL_IID \
+ { \
+ 0xf5379ff9, 0x2758, 0x4bec, { \
+ 0x99, 0x92, 0x23, 0x51, 0xc2, 0x58, 0xae, 0xd6 \
+ } \
+ }
+
+class HttpConnectionMgrShell : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPCONNECTIONMGRSHELL_IID)
+
+ enum nsParamName : uint32_t {
+ MAX_URGENT_START_Q,
+ MAX_CONNECTIONS,
+ MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ MAX_REQUEST_DELAY,
+ THROTTLING_ENABLED,
+ THROTTLING_SUSPEND_FOR,
+ THROTTLING_RESUME_FOR,
+ THROTTLING_READ_LIMIT,
+ THROTTLING_READ_INTERVAL,
+ THROTTLING_HOLD_TIME,
+ THROTTLING_MAX_TIME,
+ PROXY_BE_CONSERVATIVE
+ };
+
+ [[nodiscard]] virtual nsresult Init(
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConnections,
+ uint16_t maxPersistentConnectionsPerHost,
+ uint16_t maxPersistentConnectionsPerProxy, uint16_t maxRequestDelay,
+ bool throttleEnabled, uint32_t throttleVersion,
+ uint32_t throttleSuspendFor, uint32_t throttleResumeFor,
+ uint32_t throttleReadLimit, uint32_t throttleReadInterval,
+ uint32_t throttleHoldTime, uint32_t throttleMaxTime,
+ bool beConservativeForProxy) = 0;
+
+ [[nodiscard]] virtual nsresult Shutdown() = 0;
+
+ // called from main thread to post a new request token bucket
+ // to the socket thread
+ [[nodiscard]] virtual nsresult UpdateRequestTokenBucket(
+ EventTokenBucket* aBucket) = 0;
+
+ // Close all idle persistent connections and prevent any active connections
+ // from being reused.
+ [[nodiscard]] virtual nsresult DoShiftReloadConnectionCleanup() = 0;
+
+ // Like DoShiftReloadConnectionCleanup() above, but also resets CI specific
+ // information such as Happy Eyeballs history.
+ [[nodiscard]] virtual nsresult DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo*) = 0;
+
+ // called to force the connection manager to prune its list of idle
+ // connections.
+ [[nodiscard]] virtual nsresult PruneDeadConnections() = 0;
+
+ // this cancels all outstanding transactions but does not shut down the mgr
+ virtual void AbortAndCloseAllConnections(int32_t, ARefBase*) = 0;
+
+ // called to update a parameter after the connection manager has already
+ // been initialized.
+ [[nodiscard]] virtual nsresult UpdateParam(nsParamName name,
+ uint16_t value) = 0;
+
+ // Causes a large amount of connection diagnostic information to be
+ // printed to the javascript console
+ virtual void PrintDiagnostics() = 0;
+
+ virtual nsresult UpdateCurrentBrowserId(uint64_t aId) = 0;
+
+ // adds a transaction to the list of managed transactions.
+ [[nodiscard]] virtual nsresult AddTransaction(HttpTransactionShell*,
+ int32_t priority) = 0;
+
+ // Add a new transaction with a sticky connection from |transWithStickyConn|.
+ [[nodiscard]] virtual nsresult AddTransactionWithStickyConn(
+ HttpTransactionShell* trans, int32_t priority,
+ HttpTransactionShell* transWithStickyConn) = 0;
+
+ // called to reschedule the given transaction. it must already have been
+ // added to the connection manager via AddTransaction.
+ [[nodiscard]] virtual nsresult RescheduleTransaction(HttpTransactionShell*,
+ int32_t priority) = 0;
+
+ void virtual UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell*, const ClassOfService& classOfService) = 0;
+
+ // cancels a transaction w/ the given reason.
+ [[nodiscard]] virtual nsresult CancelTransaction(HttpTransactionShell*,
+ nsresult reason) = 0;
+
+ // called when a connection is done processing a transaction. if the
+ // connection can be reused then it will be added to the idle list, else
+ // it will be closed.
+ [[nodiscard]] virtual nsresult ReclaimConnection(
+ HttpConnectionBase* conn) = 0;
+
+ // called to force the transaction queue to be processed once more, giving
+ // preference to the specified connection.
+ [[nodiscard]] virtual nsresult ProcessPendingQ(nsHttpConnectionInfo*) = 0;
+
+ // Try and process all pending transactions
+ [[nodiscard]] virtual nsresult ProcessPendingQ() = 0;
+
+ // called to get a reference to the socket transport service. the socket
+ // transport service is not available when the connection manager is down.
+ [[nodiscard]] virtual nsresult GetSocketThreadTarget(nsIEventTarget**) = 0;
+
+ // called to indicate a transaction for the connectionInfo is likely coming
+ // soon. The connection manager may use this information to start a TCP
+ // and/or SSL level handshake for that resource immediately so that it is
+ // ready when the transaction is submitted. No obligation is taken on by the
+ // connection manager, nor is the submitter obligated to actually submit a
+ // real transaction for this connectionInfo.
+ [[nodiscard]] virtual nsresult SpeculativeConnect(
+ nsHttpConnectionInfo*, nsIInterfaceRequestor*, uint32_t caps = 0,
+ SpeculativeTransaction* = nullptr, bool aFetchHTTPSRR = false) = 0;
+
+ // "VerifyTraffic" means marking connections now, and then check again in
+ // N seconds to see if there's been any traffic and if not, kill
+ // that connection.
+ [[nodiscard]] virtual nsresult VerifyTraffic() = 0;
+
+ virtual void ExcludeHttp2(const nsHttpConnectionInfo* ci) = 0;
+ virtual void ExcludeHttp3(const nsHttpConnectionInfo* ci) = 0;
+
+ // clears the connection history mCT
+ [[nodiscard]] virtual nsresult ClearConnectionHistory() = 0;
+
+ // called by the main thread to execute the taketransport() logic on the
+ // socket thread after a 101 response has been received and the socket
+ // needs to be transferred to an expectant upgrade listener such as
+ // websockets.
+ // @param aTrans: a transaction that contains a sticky connection. We'll
+ // take the transport of this connection.
+ [[nodiscard]] virtual nsresult CompleteUpgrade(
+ HttpTransactionShell* aTrans,
+ nsIHttpUpgradeListener* aUpgradeListener) = 0;
+
+ virtual nsHttpConnectionMgr* AsHttpConnectionMgr() = 0;
+ virtual HttpConnectionMgrParent* AsHttpConnectionMgrParent() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpConnectionMgrShell,
+ HTTPCONNECTIONMGRSHELL_IID)
+
+#define NS_DECL_HTTPCONNECTIONMGRSHELL \
+ virtual nsresult Init( \
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConnections, \
+ uint16_t maxPersistentConnectionsPerHost, \
+ uint16_t maxPersistentConnectionsPerProxy, uint16_t maxRequestDelay, \
+ bool throttleEnabled, uint32_t throttleVersion, \
+ uint32_t throttleSuspendFor, uint32_t throttleResumeFor, \
+ uint32_t throttleReadLimit, uint32_t throttleReadInterval, \
+ uint32_t throttleHoldTime, uint32_t throttleMaxTime, \
+ bool beConservativeForProxy) override; \
+ virtual nsresult Shutdown() override; \
+ virtual nsresult UpdateRequestTokenBucket(EventTokenBucket* aBucket) \
+ override; \
+ virtual nsresult DoShiftReloadConnectionCleanup() override; \
+ virtual nsresult DoShiftReloadConnectionCleanupWithConnInfo( \
+ nsHttpConnectionInfo*) override; \
+ virtual nsresult PruneDeadConnections() override; \
+ virtual void AbortAndCloseAllConnections(int32_t, ARefBase*) override; \
+ virtual nsresult UpdateParam(nsParamName name, uint16_t value) override; \
+ virtual void PrintDiagnostics() override; \
+ virtual nsresult UpdateCurrentBrowserId(uint64_t aId) override; \
+ virtual nsresult AddTransaction(HttpTransactionShell*, int32_t priority) \
+ override; \
+ virtual nsresult AddTransactionWithStickyConn( \
+ HttpTransactionShell* trans, int32_t priority, \
+ HttpTransactionShell* transWithStickyConn) override; \
+ virtual nsresult RescheduleTransaction(HttpTransactionShell*, \
+ int32_t priority) override; \
+ void virtual UpdateClassOfServiceOnTransaction( \
+ HttpTransactionShell*, const ClassOfService& classOfService) override; \
+ virtual nsresult CancelTransaction(HttpTransactionShell*, nsresult reason) \
+ override; \
+ virtual nsresult ReclaimConnection(HttpConnectionBase* conn) override; \
+ virtual nsresult ProcessPendingQ(nsHttpConnectionInfo*) override; \
+ virtual nsresult ProcessPendingQ() override; \
+ virtual nsresult GetSocketThreadTarget(nsIEventTarget**) override; \
+ virtual nsresult SpeculativeConnect( \
+ nsHttpConnectionInfo*, nsIInterfaceRequestor*, uint32_t caps = 0, \
+ SpeculativeTransaction* = nullptr, bool aFetchHTTPSRR = false) override; \
+ virtual nsresult VerifyTraffic() override; \
+ virtual void ExcludeHttp2(const nsHttpConnectionInfo* ci) override; \
+ virtual void ExcludeHttp3(const nsHttpConnectionInfo* ci) override; \
+ virtual nsresult ClearConnectionHistory() override; \
+ virtual nsresult CompleteUpgrade(HttpTransactionShell* aTrans, \
+ nsIHttpUpgradeListener* aUpgradeListener) \
+ override; \
+ nsHttpConnectionMgr* AsHttpConnectionMgr() override; \
+ HttpConnectionMgrParent* AsHttpConnectionMgrParent() override;
+
+} // namespace mozilla::net
+
+#endif // HttpConnectionMgrShell_h__
diff --git a/netwerk/protocol/http/HttpConnectionUDP.cpp b/netwerk/protocol/http/HttpConnectionUDP.cpp
new file mode 100644
index 0000000000..b0e39a6d56
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionUDP.cpp
@@ -0,0 +1,706 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#define TLS_EARLY_DATA_NOT_AVAILABLE 0
+#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
+#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
+
+#include "ASpdySession.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "HttpConnectionUDP.h"
+#include "nsHttpHandler.h"
+#include "Http3Session.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISocketProvider.h"
+#include "nsNetAddr.h"
+#include "nsINetAddr.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP <public>
+//-----------------------------------------------------------------------------
+
+HttpConnectionUDP::HttpConnectionUDP() : mHttpHandler(gHttpHandler) {
+ LOG(("Creating HttpConnectionUDP @%p\n", this));
+}
+
+HttpConnectionUDP::~HttpConnectionUDP() {
+ LOG(("Destroying HttpConnectionUDP @%p\n", this));
+
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+}
+
+nsresult HttpConnectionUDP::Init(nsHttpConnectionInfo* info,
+ nsIDNSRecord* dnsRecord, nsresult status,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps) {
+ LOG1(("HttpConnectionUDP::Init this=%p", this));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+
+ mConnInfo = info;
+ MOZ_ASSERT(mConnInfo);
+ MOZ_ASSERT(mConnInfo->IsHttp3());
+
+ mErrorBeforeConnect = status;
+ mAlpnToken = mConnInfo->GetNPNToken();
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "HttpConnectionUDP::mCallbacks", callbacks, false);
+ SetCloseReason(ToCloseReason(mErrorBeforeConnect));
+ return mErrorBeforeConnect;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> dnsAddrRecord = do_QueryInterface(dnsRecord);
+ if (!dnsAddrRecord) {
+ return NS_ERROR_FAILURE;
+ }
+ dnsAddrRecord->IsTRR(&mResolvedByTRR);
+ dnsAddrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ dnsAddrRecord->GetTrrSkipReason(&mTRRSkipReason);
+ NetAddr peerAddr;
+ nsresult rv = dnsAddrRecord->GetNextAddr(mConnInfo->GetRoutedHost().IsEmpty()
+ ? mConnInfo->OriginPort()
+ : mConnInfo->RoutedPort(),
+ &peerAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mSocket = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We need an address here so that we can convey the IP version of the
+ // socket.
+ NetAddr local;
+ memset(&local, 0, sizeof(local));
+ local.raw.family = peerAddr.raw.family;
+ rv = mSocket->InitWithAddress(&local, nullptr, false, 1);
+ if (NS_FAILED(rv)) {
+ mSocket = nullptr;
+ return rv;
+ }
+
+ rv = mSocket->SetRecvBufferSize(
+ StaticPrefs::network_http_http3_recvBufferSize());
+ if (NS_FAILED(rv)) {
+ LOG(("HttpConnectionUDP::Init SetRecvBufferSize failed %d [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ mSocket->Close();
+ mSocket = nullptr;
+ return rv;
+ }
+
+ if (peerAddr.raw.family == AF_INET) {
+ rv = mSocket->SetDontFragment(true);
+ if (NS_FAILED(rv)) {
+ LOG(("HttpConnectionUDP::Init SetDontFragment failed %d [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+
+ // get the resulting socket address.
+ rv = mSocket->GetLocalAddr(getter_AddRefs(mSelfAddr));
+ if (NS_FAILED(rv)) {
+ mSocket->Close();
+ mSocket = nullptr;
+ return rv;
+ }
+
+ uint32_t controlFlags = 0;
+ if (caps & NS_HTTP_LOAD_ANONYMOUS) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
+ }
+ if (mConnInfo->GetPrivate()) {
+ controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+ }
+ if (((caps & NS_HTTP_BE_CONSERVATIVE) || mConnInfo->GetBeConservative()) &&
+ gHttpHandler->ConnMgr()->BeConservativeIfProxied(
+ mConnInfo->ProxyInfo())) {
+ controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
+ }
+
+ if (mResolvedByTRR) {
+ controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
+ }
+
+ mPeerAddr = new nsNetAddr(&peerAddr);
+ mHttp3Session = new Http3Session();
+ rv = mHttp3Session->Init(mConnInfo, mSelfAddr, mPeerAddr, this, controlFlags,
+ callbacks);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("HttpConnectionUDP::Init mHttp3Session->Init failed "
+ "[this=%p rv=%x]\n",
+ this, static_cast<uint32_t>(rv)));
+ mSocket->Close();
+ mSocket = nullptr;
+ mHttp3Session = nullptr;
+ return rv;
+ }
+
+ ChangeConnectionState(ConnectionState::INITED);
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "HttpConnectionUDP::mCallbacks", callbacks, false);
+
+ // Call SyncListen at the end of this function. This call will actually
+ // attach the sockte to SocketTransportService.
+ rv = mSocket->SyncListen(this);
+ if (NS_FAILED(rv)) {
+ mSocket->Close();
+ mSocket = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// called on the socket thread
+nsresult HttpConnectionUDP::Activate(nsAHttpTransaction* trans, uint32_t caps,
+ int32_t pri) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG1(("HttpConnectionUDP::Activate [this=%p trans=%p caps=%x]\n", this, trans,
+ caps));
+
+ if (!mExperienced && !trans->IsNullTransaction()) {
+ mHasFirstHttpTransaction = true;
+ // For QUIC we have HttpConnecitonUDP before the actual connection
+ // has been establish so wait for TLS handshake to be finished before
+ // we mark the connection 'experienced'.
+ if (!mExperienced && mHttp3Session && mHttp3Session->IsConnected()) {
+ mExperienced = true;
+ }
+ if (mBootstrappedTimingsSet) {
+ mBootstrappedTimingsSet = false;
+ nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
+ if (hTrans) {
+ hTrans->BootstrapTimings(mBootstrappedTimings);
+ }
+ }
+ mBootstrappedTimings = TimingStruct();
+ }
+
+ mTransactionCaps = caps;
+ mPriority = pri;
+
+ NS_ENSURE_ARG_POINTER(trans);
+
+ // Connection failures are Activated() just like regular transacions.
+ // If we don't have a confirmation of a connected socket then test it
+ // with a write() to get relevant error code.
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ CloseTransaction(nullptr, mErrorBeforeConnect);
+ trans->Close(mErrorBeforeConnect);
+ gHttpHandler->ExcludeHttp3(mConnInfo);
+ return mErrorBeforeConnect;
+ }
+
+ if (!mHttp3Session->AddStream(trans, pri, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ trans->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mHasFirstHttpTransaction && mExperienced) {
+ mHasFirstHttpTransaction = false;
+ mExperienceState |= ConnectionExperienceState::Experienced;
+ }
+
+ Unused << ResumeSend();
+ return NS_OK;
+}
+
+void HttpConnectionUDP::Close(nsresult reason, bool aIsShutdown) {
+ LOG(("HttpConnectionUDP::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mConnectionState != ConnectionState::CLOSED) {
+ RecordConnectionCloseTelemetry(reason);
+ ChangeConnectionState(ConnectionState::CLOSED);
+ }
+
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ if (!mTrafficCategory.IsEmpty()) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->IncrementHttpConnection(std::move(mTrafficCategory));
+ MOZ_ASSERT(mTrafficCategory.IsEmpty());
+ }
+ }
+ if (mSocket) {
+ mSocket->Close();
+ mSocket = nullptr;
+ }
+}
+
+void HttpConnectionUDP::DontReuse() {
+ LOG(("HttpConnectionUDP::DontReuse %p http3session=%p\n", this,
+ mHttp3Session.get()));
+ mDontReuse = true;
+ if (mHttp3Session) {
+ mHttp3Session->DontReuse();
+ }
+}
+
+bool HttpConnectionUDP::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mHttp3Session && CanDirectlyActivate()) {
+ return mHttp3Session->TestJoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool HttpConnectionUDP::JoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mHttp3Session && CanDirectlyActivate()) {
+ return mHttp3Session->JoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool HttpConnectionUDP::CanReuse() {
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ return false;
+ }
+ if (mDontReuse) {
+ return false;
+ }
+
+ if (mHttp3Session) {
+ return mHttp3Session->CanReuse();
+ }
+ return false;
+}
+
+bool HttpConnectionUDP::CanDirectlyActivate() {
+ // return true if a new transaction can be addded to ths connection at any
+ // time through Activate(). In practice this means this is a healthy SPDY
+ // connection with room for more concurrent streams.
+
+ if (mHttp3Session) {
+ return CanReuse();
+ }
+ return false;
+}
+
+//----------------------------------------------------------------------------
+// HttpConnectionUDP::nsAHttpConnection compatible methods
+//----------------------------------------------------------------------------
+
+nsresult HttpConnectionUDP::OnHeadersAvailable(nsAHttpTransaction* trans,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ LOG(
+ ("HttpConnectionUDP::OnHeadersAvailable [this=%p trans=%p "
+ "response-head=%p]\n",
+ this, trans, responseHead));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NS_ENSURE_ARG_POINTER(trans);
+ MOZ_ASSERT(responseHead, "No response head?");
+
+ DebugOnly<nsresult> rv =
+ responseHead->SetHeader(nsHttp::X_Firefox_Http3, mAlpnToken);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // deal with 408 Server Timeouts
+ uint16_t responseStatus = responseHead->Status();
+ static const PRIntervalTime k1000ms = PR_MillisecondsToInterval(1000);
+ if (responseStatus == 408) {
+ // If this error could be due to a persistent connection reuse then
+ // we pass an error code of NS_ERROR_NET_RESET to
+ // trigger the transaction 'restart' mechanism. We tell it to reset its
+ // response headers so that it will be ready to receive the new response.
+ if (mIsReused &&
+ ((PR_IntervalNow() - mHttp3Session->LastWriteTime()) < k1000ms)) {
+ Close(NS_ERROR_NET_RESET);
+ *reset = true;
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool HttpConnectionUDP::IsReused() { return mIsReused; }
+
+nsresult HttpConnectionUDP::TakeTransport(
+ nsISocketTransport** aTransport, nsIAsyncInputStream** aInputStream,
+ nsIAsyncOutputStream** aOutputStream) {
+ return NS_ERROR_FAILURE;
+}
+
+void HttpConnectionUDP::GetTLSSocketControl(nsITLSSocketControl** secinfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("HttpConnectionUDP::GetTLSSocketControl http3Session=%p\n",
+ mHttp3Session.get()));
+
+ if (mHttp3Session &&
+ NS_SUCCEEDED(mHttp3Session->GetTransactionTLSSocketControl(secinfo))) {
+ return;
+ }
+
+ *secinfo = nullptr;
+}
+
+nsresult HttpConnectionUDP::PushBack(const char* data, uint32_t length) {
+ LOG(("HttpConnectionUDP::PushBack [this=%p, length=%d]\n", this, length));
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+class HttpConnectionUDPForceIO : public Runnable {
+ public:
+ HttpConnectionUDPForceIO(HttpConnectionUDP* aConn, bool doRecv)
+ : Runnable("net::HttpConnectionUDPForceIO"),
+ mConn(aConn),
+ mDoRecv(doRecv) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mDoRecv) {
+ return mConn->RecvData();
+ }
+
+ MOZ_ASSERT(mConn->mForceSendPending);
+ mConn->mForceSendPending = false;
+
+ return mConn->SendData();
+ }
+
+ private:
+ RefPtr<HttpConnectionUDP> mConn;
+ bool mDoRecv;
+};
+
+nsresult HttpConnectionUDP::ResumeSend() {
+ LOG(("HttpConnectionUDP::ResumeSend [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ RefPtr<HttpConnectionUDP> self(this);
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("HttpConnectionUDP::CallSendData",
+ [self{std::move(self)}]() { self->SendData(); }));
+ return NS_OK;
+}
+
+nsresult HttpConnectionUDP::ResumeRecv() { return NS_OK; }
+
+void HttpConnectionUDP::ForceSendIO(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ HttpConnectionUDP* self = static_cast<HttpConnectionUDP*>(aClosure);
+ MOZ_ASSERT(aTimer == self->mForceSendTimer);
+ self->mForceSendTimer = nullptr;
+ NS_DispatchToCurrentThread(new HttpConnectionUDPForceIO(self, false));
+}
+
+nsresult HttpConnectionUDP::MaybeForceSendIO() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // due to bug 1213084 sometimes real I/O events do not get serviced when
+ // NSPR derived I/O events are ready and this can cause a deadlock with
+ // https over https proxying. Normally we would expect the write callback to
+ // be invoked before this timer goes off, but set it at the old windows
+ // tick interval (kForceDelay) as a backup for those circumstances.
+ static const uint32_t kForceDelay = 17; // ms
+
+ if (mForceSendPending) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(!mForceSendTimer);
+ mForceSendPending = true;
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mForceSendTimer), HttpConnectionUDP::ForceSendIO, this,
+ kForceDelay, nsITimer::TYPE_ONE_SHOT,
+ "net::HttpConnectionUDP::MaybeForceSendIO");
+}
+
+// trigger an asynchronous read
+nsresult HttpConnectionUDP::ForceRecv() {
+ LOG(("HttpConnectionUDP::ForceRecv [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return NS_DispatchToCurrentThread(new HttpConnectionUDPForceIO(this, true));
+}
+
+// trigger an asynchronous write
+nsresult HttpConnectionUDP::ForceSend() {
+ LOG(("HttpConnectionUDP::ForceSend [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return MaybeForceSendIO();
+}
+
+HttpVersion HttpConnectionUDP::Version() { return HttpVersion::v3_0; }
+
+PRIntervalTime HttpConnectionUDP::LastWriteTime() {
+ return mHttp3Session->LastWriteTime();
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP <private>
+//-----------------------------------------------------------------------------
+
+void HttpConnectionUDP::CloseTransaction(nsAHttpTransaction* trans,
+ nsresult reason, bool aIsShutdown) {
+ LOG(("HttpConnectionUDP::CloseTransaction[this=%p trans=%p reason=%" PRIx32
+ "]\n",
+ this, trans, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(trans == mHttp3Session);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (NS_SUCCEEDED(reason) || (reason == NS_BASE_STREAM_CLOSED)) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ // The connection and security errors clear out alt-svc mappings
+ // in case any previously validated ones are now invalid
+ if (((reason == NS_ERROR_NET_RESET) ||
+ (NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY)) &&
+ mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
+ gHttpHandler->ClearHostMapping(mConnInfo);
+ }
+
+ mDontReuse = true;
+ if (mHttp3Session) {
+ mHttp3Session->SetCleanShutdown(aIsShutdown);
+ mHttp3Session->Close(reason);
+ if (!mHttp3Session->IsClosed()) {
+ // During closing phase we still keep mHttp3Session session,
+ // to resend CLOSE_CONNECTION frames.
+ return;
+ }
+ }
+
+ mHttp3Session = nullptr;
+
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ mCallbacks = nullptr;
+ }
+
+ Close(reason, aIsShutdown);
+
+ // flag the connection as reused here for convenience sake. certainly
+ // it might be going away instead ;-)
+ mIsReused = true;
+}
+
+void HttpConnectionUDP::OnQuicTimeoutExpired() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("HttpConnectionUDP::OnQuicTimeoutExpired [this=%p]\n", this));
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no transaction; ignoring event\n"));
+ return;
+ }
+
+ nsresult rv = mHttp3Session->ProcessOutputAndEvents(mSocket);
+ if (NS_FAILED(rv)) {
+ CloseTransaction(mHttp3Session, rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpConnectionUDP)
+NS_IMPL_RELEASE(HttpConnectionUDP)
+
+NS_INTERFACE_MAP_BEGIN(HttpConnectionUDP)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPSocketSyncListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(HttpConnectionBase)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpConnectionUDP)
+NS_INTERFACE_MAP_END
+
+void HttpConnectionUDP::NotifyDataRead() {
+ mExperienceState |= ConnectionExperienceState::First_Response_Received;
+}
+
+void HttpConnectionUDP::NotifyDataWrite() {
+ mExperienceState |= ConnectionExperienceState::First_Request_Sent;
+}
+
+// called on the socket transport thread
+nsresult HttpConnectionUDP::RecvData() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no Http3Session; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = mHttp3Session->RecvData(mSocket);
+ LOG(("HttpConnectionUDP::OnInputReady %p rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
+
+ return NS_OK;
+}
+
+// called on the socket transport thread
+nsresult HttpConnectionUDP::SendData() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no Http3Session; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = mHttp3Session->SendData(mSocket);
+ LOG(("HttpConnectionUDP::OnInputReady %p rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+// not called on the socket transport thread
+NS_IMETHODIMP
+HttpConnectionUDP::GetInterface(const nsIID& iid, void** result) {
+ // NOTE: This function is only called on the UI thread via sync proxy from
+ // the socket transport thread. If that weren't the case, then we'd
+ // have to worry about the possibility of mHttp3Session going away
+ // part-way through this function call. See CloseTransaction.
+
+ // NOTE - there is a bug here, the call to getinterface is proxied off the
+ // nss thread, not the ui thread as the above comment says. So there is
+ // indeed a chance of mSession going away. bug 615342
+
+ MOZ_ASSERT(!OnSocketThread(), "on socket thread");
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ callbacks = mCallbacks;
+ }
+ if (callbacks) return callbacks->GetInterface(iid, result);
+ return NS_ERROR_NO_INTERFACE;
+}
+
+void HttpConnectionUDP::SetEvent(nsresult aStatus) {
+ switch (aStatus) {
+ case NS_NET_STATUS_RESOLVING_HOST:
+ mBootstrappedTimings.domainLookupStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_RESOLVED_HOST:
+ mBootstrappedTimings.domainLookupEnd = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTING_TO:
+ mBootstrappedTimings.connectStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTED_TO:
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ mBootstrappedTimings.secureConnectionStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ break;
+ default:
+ break;
+ }
+}
+
+bool HttpConnectionUDP::IsProxyConnectInProgress() { return false; }
+
+bool HttpConnectionUDP::LastTransactionExpectedNoContent() {
+ return mLastTransactionExpectedNoContent;
+}
+
+void HttpConnectionUDP::SetLastTransactionExpectedNoContent(bool val) {
+ mLastTransactionExpectedNoContent = val;
+}
+
+bool HttpConnectionUDP::IsPersistent() { return !mDontReuse; }
+
+nsAHttpTransaction* HttpConnectionUDP::Transaction() { return mHttp3Session; }
+
+int64_t HttpConnectionUDP::BytesWritten() {
+ if (!mHttp3Session) {
+ return 0;
+ }
+ return mHttp3Session->GetBytesWritten();
+}
+
+NS_IMETHODIMP HttpConnectionUDP::OnPacketReceived(nsIUDPSocket* aSocket) {
+ RecvData();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpConnectionUDP::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus) {
+ CloseTransaction(mHttp3Session, aStatus);
+ return NS_OK;
+}
+
+nsresult HttpConnectionUDP::GetSelfAddr(NetAddr* addr) {
+ if (mSelfAddr) {
+ return mSelfAddr->GetNetAddr(addr);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult HttpConnectionUDP::GetPeerAddr(NetAddr* addr) {
+ if (mPeerAddr) {
+ return mPeerAddr->GetNetAddr(addr);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+bool HttpConnectionUDP::ResolvedByTRR() { return mResolvedByTRR; }
+
+nsIRequest::TRRMode HttpConnectionUDP::EffectiveTRRMode() {
+ return mEffectiveTRRMode;
+}
+
+TRRSkippedReason HttpConnectionUDP::TRRSkipReason() { return mTRRSkipReason; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionUDP.h b/netwerk/protocol/http/HttpConnectionUDP.h
new file mode 100644
index 0000000000..e5cae24586
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionUDP.h
@@ -0,0 +1,138 @@
+/* -*- 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 HttpConnectionUDP_h__
+#define HttpConnectionUDP_h__
+
+#include "HttpConnectionBase.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsISupportsPriority.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+#include "Http3Session.h"
+
+class nsIDNSRecord;
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+
+// 1dcc863e-db90-4652-a1fe-13fea0b54e46
+#define HTTPCONNECTIONUDP_IID \
+ { \
+ 0xb97d2036, 0xb441, 0x48be, { \
+ 0xb3, 0x1e, 0x25, 0x3e, 0xe8, 0x32, 0xdd, 0x67 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP - represents a connection to a HTTP3 server
+//
+// NOTE: this objects lives on the socket thread only. it should not be
+// accessed from any other thread.
+//-----------------------------------------------------------------------------
+
+class HttpConnectionUDP final : public HttpConnectionBase,
+ public nsIUDPSocketSyncListener,
+ public nsIInterfaceRequestor {
+ private:
+ virtual ~HttpConnectionUDP();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPCONNECTIONUDP_IID)
+ NS_DECL_HTTPCONNECTIONBASE
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETSYNCLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ HttpConnectionUDP();
+
+ [[nodiscard]] nsresult Init(nsHttpConnectionInfo* info,
+ nsIDNSRecord* dnsRecord, nsresult status,
+ nsIInterfaceRequestor* callbacks, uint32_t caps);
+
+ friend class HttpConnectionUDPForceIO;
+
+ [[nodiscard]] static nsresult ReadFromStream(nsIInputStream*, void*,
+ const char*, uint32_t, uint32_t,
+ uint32_t*);
+
+ bool UsingHttp3() override { return true; }
+
+ void OnQuicTimeoutExpired();
+
+ int64_t BytesWritten() override;
+
+ nsresult GetSelfAddr(NetAddr* addr) override;
+ nsresult GetPeerAddr(NetAddr* addr) override;
+ bool ResolvedByTRR() override;
+ nsIRequest::TRRMode EffectiveTRRMode() override;
+ TRRSkippedReason TRRSkipReason() override;
+ bool GetEchConfigUsed() override { return false; }
+
+ void NotifyDataRead();
+ void NotifyDataWrite();
+
+ private:
+ [[nodiscard]] nsresult OnTransactionDone(nsresult reason);
+ nsresult RecvData();
+ nsresult SendData();
+
+ private:
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ RefPtr<nsIAsyncInputStream> mInputOverflow;
+
+ bool mConnectedTransport = false;
+ bool mDontReuse = false;
+ bool mIsReused = false;
+ bool mLastTransactionExpectedNoContent = false;
+
+ int32_t mPriority = nsISupportsPriority::PRIORITY_NORMAL;
+
+ private:
+ // For ForceSend()
+ static void ForceSendIO(nsITimer* aTimer, void* aClosure);
+ [[nodiscard]] nsresult MaybeForceSendIO();
+ bool mForceSendPending = false;
+ nsCOMPtr<nsITimer> mForceSendTimer;
+
+ PRIntervalTime mLastRequestBytesSentTime = 0;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+
+ nsCOMPtr<nsINetAddr> mSelfAddr;
+ nsCOMPtr<nsINetAddr> mPeerAddr;
+ bool mResolvedByTRR = false;
+ nsIRequest::TRRMode mEffectiveTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason mTRRSkipReason = nsITRRSkipReason::TRR_UNSET;
+
+ private:
+ // Http3
+ RefPtr<Http3Session> mHttp3Session;
+ nsCString mAlpnToken;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpConnectionUDP, HTTPCONNECTIONUDP_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpConnectionUDP_h__
diff --git a/netwerk/protocol/http/HttpInfo.cpp b/netwerk/protocol/http/HttpInfo.cpp
new file mode 100644
index 0000000000..b3574cab1c
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.cpp
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHandler.h"
+#include "HttpInfo.h"
+
+void mozilla::net::HttpInfo::GetHttpConnectionData(
+ nsTArray<HttpRetParams>* args) {
+ if (gHttpHandler && gHttpHandler->ConnMgr()) {
+ gHttpHandler->ConnMgr()->GetConnectionData(args);
+ }
+}
diff --git a/netwerk/protocol/http/HttpInfo.h b/netwerk/protocol/http/HttpInfo.h
new file mode 100644
index 0000000000..981a3df56b
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpInfo__
+#define nsHttpInfo__
+
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace net {
+
+struct HttpRetParams;
+
+class HttpInfo {
+ public:
+ /* Calls getConnectionData method in nsHttpConnectionMgr. */
+ static void GetHttpConnectionData(nsTArray<HttpRetParams>*);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpInfo__
diff --git a/netwerk/protocol/http/HttpLog.h b/netwerk/protocol/http/HttpLog.h
new file mode 100644
index 0000000000..58de97590b
--- /dev/null
+++ b/netwerk/protocol/http/HttpLog.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpLog_h__
+#define HttpLog_h__
+
+/*******************************************************************************
+ * This file should ONLY be #included by source (.cpp) files in the /http
+ * directory, not headers (.h). If you need to use LOG() in a .h file, call
+ * PR_LOG directly.
+ *
+ * This file should also be the first #include in your file.
+ *
+ * Yes, this is kludgy.
+ *******************************************************************************/
+
+#include "mozilla/net/NeckoChild.h"
+
+// Get rid of Chromium's LOG definition
+#undef LOG
+
+//
+// Log module for HTTP Protocol logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsHttp:5
+// set MOZ_LOG_FILE=http.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file http.log.
+//
+namespace mozilla {
+namespace net {
+Maybe<nsCString> CallingScriptLocationString();
+void LogCallingScriptLocation(void* instance);
+void LogCallingScriptLocation(void* instance,
+ const Maybe<nsCString>& aLogLocation);
+extern LazyLogModule gHttpLog;
+} // namespace net
+} // namespace mozilla
+
+// http logging
+#define LOG1(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Error, args)
+#define LOG2(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Warning, args)
+#define LOG3(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Info, args)
+#define LOG4(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args)
+#define LOG5(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Verbose, args)
+#define LOG(args) LOG4(args)
+#define LOGTIME(start, args) \
+ MOZ_LOG_TIME(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, &(start), args)
+
+#define LOG1_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Error)
+#define LOG2_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Warning)
+#define LOG3_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Info)
+#define LOG4_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Debug)
+#define LOG5_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Verbose)
+#define LOG_ENABLED() LOG4_ENABLED()
+
+#endif // HttpLog_h__
diff --git a/netwerk/protocol/http/HttpTrafficAnalyzer.cpp b/netwerk/protocol/http/HttpTrafficAnalyzer.cpp
new file mode 100644
index 0000000000..5b8f3bbb21
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.cpp
@@ -0,0 +1,269 @@
+/* -*- 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 "HttpTrafficAnalyzer.h"
+#include "HttpLog.h"
+
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla {
+namespace net {
+
+constexpr auto kInvalidCategory = "INVALID_CATEGORY"_ns;
+
+#define DEFINE_CATEGORY(_name, _idx) nsLiteralCString("Y" #_idx "_" #_name),
+static constexpr nsLiteralCString gKeyName[] = {
+#include "HttpTrafficAnalyzer.inc"
+ kInvalidCategory,
+};
+#undef DEFINE_CATEGORY
+
+#define DEFINE_CATEGORY(_name, _idx) \
+ Telemetry::LABELS_HTTP_TRAFFIC_ANALYSIS_3::Y##_idx##_##_name,
+static const Telemetry::LABELS_HTTP_TRAFFIC_ANALYSIS_3 gTelemetryLabel[] = {
+#include "HttpTrafficAnalyzer.inc"
+};
+#undef DEFINE_CATEGORY
+
+// ----------------------------------------------------
+// | Flags | Load Type |
+// ----------------------------------------------------
+// | nsIClassOfService::Leader | A |
+// | w/o nsIRequest::LOAD_BACKGROUND | B |
+// | w/ nsIRequest::LOAD_BACKGROUND | C |
+// ----------------------------------------------------
+// | Category | List Category |
+// ----------------------------------------------------
+// | Basic Disconnected List | I |
+// | Content | II |
+// | Fingerprinting | III |
+// ----------------------------------------------------
+// ====================================================
+// | Normal Mode |
+// ----------------------------------------------------
+// | Y = 0 for system principals |
+// | Y = 1 for first party |
+// | Y = 2 for non-listed third party type |
+// ----------------------------------------------------
+// | \Y\ | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I | 3 | 4 | 5 |
+// | Category II | 6 | 7 | 8 |
+// | Category III | 9 | 10 | 11 |
+// ====================================================
+// | Private Mode |
+// ----------------------------------------------------
+// | Y = 12 for system principals |
+// | Y = 13 for first party |
+// | Y = 14 for non-listed third party type |
+// ----------------------------------------------------
+// | \Y\ | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I | 15 | 16 | 17 |
+// | Category II | 18 | 19 | 20 |
+// | Category III | 21 | 22 | 23 |
+// ====================================================
+
+HttpTrafficCategory HttpTrafficAnalyzer::CreateTrafficCategory(
+ bool aIsPrivateMode, bool aIsSystemPrincipal, bool aIsThirdParty,
+ ClassOfService aClassOfService, TrackingClassification aClassification) {
+ uint8_t category = aIsPrivateMode ? 12 : 0;
+ if (aIsSystemPrincipal) {
+ MOZ_ASSERT_IF(!aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y0_N1Sys"));
+ MOZ_ASSERT_IF(aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y12_P1Sys"));
+ return static_cast<HttpTrafficCategory>(category);
+ }
+ ++category;
+
+ if (!aIsThirdParty) {
+ MOZ_ASSERT_IF(!aIsPrivateMode, gKeyName[category].EqualsLiteral("Y1_N1"));
+ MOZ_ASSERT_IF(aIsPrivateMode, gKeyName[category].EqualsLiteral("Y13_P1"));
+ return static_cast<HttpTrafficCategory>(category);
+ }
+
+ switch (aClassification) {
+ case TrackingClassification::eNone:
+ ++category;
+ MOZ_ASSERT_IF(!aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y2_N3Oth"));
+ MOZ_ASSERT_IF(aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y14_P3Oth"));
+ return static_cast<HttpTrafficCategory>(category);
+ case TrackingClassification::eBasic:
+ category += 2;
+ break;
+ case TrackingClassification::eContent:
+ category += 5;
+ break;
+ case TrackingClassification::eFingerprinting:
+ category += 8;
+ break;
+ default:
+ MOZ_ASSERT(false, "incorrect classification");
+ return HttpTrafficCategory::eInvalid;
+ }
+
+ switch (aClassOfService) {
+ case ClassOfService::eLeader:
+ MOZ_ASSERT_IF(
+ !aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y3_N3BasicLead")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y6_N3ContentLead")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y9_N3FpLead")));
+ MOZ_ASSERT_IF(
+ aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y15_P3BasicLead")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y18_P3ContentLead")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y21_P3FpLead")));
+ return static_cast<HttpTrafficCategory>(category);
+ case ClassOfService::eBackground:
+ ++category;
+
+ MOZ_ASSERT_IF(
+ !aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y4_N3BasicBg")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y7_N3ContentBg")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y10_N3FpBg")));
+ MOZ_ASSERT_IF(
+ aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y16_P3BasicBg")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y19_P3ContentBg")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y22_P3FpBg")));
+
+ return static_cast<HttpTrafficCategory>(category);
+ case ClassOfService::eOther:
+ category += 2;
+
+ MOZ_ASSERT_IF(
+ !aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y5_N3BasicOth")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y8_N3ContentOth")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y11_N3FpOth")));
+ MOZ_ASSERT_IF(
+ aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y17_P3BasicOth")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y20_P3ContentOth")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y23_P3FpOth")));
+
+ return static_cast<HttpTrafficCategory>(category);
+ }
+
+ MOZ_ASSERT(false, "incorrect class of service");
+ return HttpTrafficCategory::eInvalid;
+}
+
+void HttpTrafficAnalyzer::IncrementHttpTransaction(
+ HttpTrafficCategory aCategory) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+ LOG(("HttpTrafficAnalyzer::IncrementHttpTransaction [%s] [this=%p]\n",
+ gKeyName[aCategory].get(), this));
+
+ Telemetry::AccumulateCategoricalKeyed("Transaction"_ns,
+ gTelemetryLabel[aCategory]);
+}
+
+void HttpTrafficAnalyzer::IncrementHttpConnection(
+ HttpTrafficCategory aCategory) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+ LOG(("HttpTrafficAnalyzer::IncrementHttpConnection [%s] [this=%p]\n",
+ gKeyName[aCategory].get(), this));
+
+ Telemetry::AccumulateCategoricalKeyed("Connection"_ns,
+ gTelemetryLabel[aCategory]);
+}
+
+void HttpTrafficAnalyzer::IncrementHttpConnection(
+ nsTArray<HttpTrafficCategory>&& aCategories) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(!aCategories.IsEmpty(), "empty category");
+
+ nsTArray<HttpTrafficCategory> categories(std::move(aCategories));
+
+ LOG(("HttpTrafficAnalyzer::IncrementHttpConnection size=%" PRIuPTR
+ " [this=%p]\n",
+ categories.Length(), this));
+
+ // divide categories into 4 parts:
+ // 1) normal 1st-party (Y in {0, 1})
+ // 2) normal 3rd-party (1 < Y < 12)
+ // 3) private 1st-party (Y in {12, 13})
+ // 4) private 3rd-party (13 < Y < 24)
+ // Normal and private transaction should not share the same connection,
+ // and we choose 3rd-party prior than 1st-party.
+ HttpTrafficCategory best = categories[0];
+ for (auto category : categories) {
+ MOZ_ASSERT(category != HttpTrafficCategory::eInvalid, "invalid category");
+
+ if (category == 0 || category == 1 || category == 12 || category == 13) {
+ // first party
+ MOZ_ASSERT(gKeyName[category].EqualsLiteral("Y0_N1Sys") ||
+ gKeyName[category].EqualsLiteral("Y1_N1") ||
+ gKeyName[category].EqualsLiteral("Y12_P1Sys") ||
+ gKeyName[category].EqualsLiteral("Y13_P1"));
+ continue;
+ }
+ // third party
+ MOZ_ASSERT(gKeyName[24].Equals(kInvalidCategory),
+ "category definition isn't consistent");
+ best = category;
+ break;
+ }
+
+ IncrementHttpConnection(best);
+}
+
+#define CLAMP_U32(num) \
+ Clamp<uint32_t>(num, 0, std::numeric_limits<uint32_t>::max())
+
+void HttpTrafficAnalyzer::AccumulateHttpTransferredSize(
+ HttpTrafficCategory aCategory, uint64_t aBytesRead, uint64_t aBytesSent) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+ LOG(("HttpTrafficAnalyzer::AccumulateHttpTransferredSize [%s] rb=%" PRIu64 " "
+ "sb=%" PRIu64 " [this=%p]\n",
+ gKeyName[aCategory].get(), aBytesRead, aBytesSent, this));
+
+ // Telemetry supports uint32_t only, and we send KB here.
+ auto total = CLAMP_U32((aBytesRead >> 10) + (aBytesSent >> 10));
+ if (aBytesRead || aBytesSent) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::NETWORKING_DATA_TRANSFERRED_V3_KB,
+ NS_ConvertUTF8toUTF16(gKeyName[aCategory]), total);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpTrafficAnalyzer.h b/netwerk/protocol/http/HttpTrafficAnalyzer.h
new file mode 100644
index 0000000000..e319a0697e
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+#define mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+
+#include <stdint.h>
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace net {
+
+#define DEFINE_CATEGORY(_name, _idx) e##_name = _idx##u,
+enum HttpTrafficCategory : uint8_t {
+#include "HttpTrafficAnalyzer.inc"
+ eInvalid,
+};
+#undef DEFINE_CATEGORY
+
+class HttpTrafficAnalyzer final {
+ public:
+ enum ClassOfService : uint8_t {
+ eLeader = 0,
+ eBackground = 1,
+ eOther = 255,
+ };
+
+ enum TrackingClassification : uint8_t {
+ eNone = 0,
+ eBasic = 1,
+ eContent = 2,
+ eFingerprinting = 3,
+ };
+
+ static HttpTrafficCategory CreateTrafficCategory(
+ bool aIsPrivateMode, bool aIsSystemPrincipal, bool aIsThirdParty,
+ ClassOfService aClassOfService, TrackingClassification aClassification);
+
+ void IncrementHttpTransaction(HttpTrafficCategory aCategory);
+ void IncrementHttpConnection(HttpTrafficCategory aCategory);
+ void IncrementHttpConnection(nsTArray<HttpTrafficCategory>&& aCategories);
+ void AccumulateHttpTransferredSize(HttpTrafficCategory aCategory,
+ uint64_t aBytesRead, uint64_t aBytesSent);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
diff --git a/netwerk/protocol/http/HttpTrafficAnalyzer.inc b/netwerk/protocol/http/HttpTrafficAnalyzer.inc
new file mode 100644
index 0000000000..3311d33ee8
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.inc
@@ -0,0 +1,106 @@
+// Type A) Parser blocking script loads will have an mContentPolicyType
+// indicating a script load and a nsIClassOfService::Leader class of service.
+// Type B) I couldn’t find a class flag that corresponds to synchronous loads
+// that block the load event, but I think a simple way to determine
+// which ones they are is to examine whether the channel belongs to a
+// load group and do not have a LOAD_BACKGROUND load flag.
+// If that condition holds, then it is blocking the load event of some document.
+// Type C) I think a simple way to find channels that don’t block document loads
+// (e.g. XHR and fetch) is to look for those which are in a load group
+// and have a LOAD_BACKGROUND load flag.
+
+
+// Y=0 - all system principal connections.
+DEFINE_CATEGORY(N1Sys, 0)
+
+// Y=1 - all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(N1, 1)
+
+// Y=2 - all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(N3Oth, 2)
+
+// Y=3 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (A)
+DEFINE_CATEGORY(N3BasicLead, 3)
+
+// Y=4 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (B)
+DEFINE_CATEGORY(N3BasicBg, 4)
+
+// Y=5 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (C)
+DEFINE_CATEGORY(N3BasicOth, 5)
+
+// Y=6 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(N3ContentLead, 6)
+
+// Y=7 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(N3ContentBg, 7)
+
+// Y=8 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(N3ContentOth, 8)
+
+// Y=9 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(N3FpLead, 9)
+
+// Y=10 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(N3FpBg, 10)
+
+// Y=11 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(N3FpOth, 11)
+
+// Y=12 - private mode system principal connections.
+DEFINE_CATEGORY(P1Sys, 12)
+
+// Y=13 - private mode and all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(P1, 13)
+
+// Y=14 - private mode and all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(P3Oth, 14)
+
+// Y=15 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (A)
+DEFINE_CATEGORY(P3BasicLead, 15)
+
+// Y=16 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (B)
+DEFINE_CATEGORY(P3BasicBg, 16)
+
+// Y=17 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (C)
+DEFINE_CATEGORY(P3BasicOth, 17)
+
+// Y=18 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(P3ContentLead, 18)
+
+// Y=19 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(P3ContentBg, 19)
+
+// Y=20 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(P3ContentOth, 20)
+
+// Y=21 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(P3FpLead, 21)
+
+// Y=22 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(P3FpBg, 22)
+
+// Y=23 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(P3FpOth, 23)
diff --git a/netwerk/protocol/http/HttpTransactionChild.cpp b/netwerk/protocol/http/HttpTransactionChild.cpp
new file mode 100644
index 0000000000..c4294cb0b4
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionChild.cpp
@@ -0,0 +1,665 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpTransactionChild.h"
+
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/InputChannelThrottleQueueChild.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsInputStreamPump.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsProxyInfo.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsSerializationHelper.h"
+#include "OpaqueResponseUtils.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(HttpTransactionChild, nsIRequestObserver, nsIStreamListener,
+ nsITransportEventSink, nsIThrottledInputChannel,
+ nsIThreadRetargetableStreamListener, nsIEarlyHintObserver);
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild <public>
+//-----------------------------------------------------------------------------
+
+HttpTransactionChild::HttpTransactionChild() {
+ LOG(("Creating HttpTransactionChild @%p\n", this));
+}
+
+HttpTransactionChild::~HttpTransactionChild() {
+ LOG(("Destroying HttpTransactionChild @%p\n", this));
+}
+
+static already_AddRefed<nsIRequestContext> CreateRequestContext(
+ uint64_t aRequestContextID) {
+ if (!aRequestContextID) {
+ return nullptr;
+ }
+
+ nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+ if (!rcsvc) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIRequestContext> requestContext;
+ rcsvc->GetRequestContext(aRequestContextID, getter_AddRefs(requestContext));
+
+ return requestContext.forget();
+}
+
+nsresult HttpTransactionChild::InitInternal(
+ uint32_t caps, const HttpConnectionInfoCloneArgs& infoArgs,
+ nsHttpRequestHead* requestHead, nsIInputStream* requestBody,
+ uint64_t requestContentLength, bool requestBodyHasHeaders,
+ uint64_t browserId, uint8_t httpTrafficCategory, uint64_t requestContextID,
+ ClassOfService classOfService, uint32_t initialRwin,
+ bool responseTimeoutEnabled, uint64_t channelId,
+ bool aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg) {
+ LOG(("HttpTransactionChild::InitInternal [this=%p caps=%x]\n", this, caps));
+
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(infoArgs);
+ nsCOMPtr<nsIRequestContext> rc = CreateRequestContext(requestContextID);
+
+ HttpTransactionShell::OnPushCallback pushCallback = nullptr;
+ if (caps & NS_HTTP_ONPUSH_LISTENER) {
+ RefPtr<HttpTransactionChild> self = this;
+ pushCallback = [self](uint32_t aPushedStreamId, const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ bool res = false;
+ if (self->CanSend()) {
+ res =
+ self->SendOnH2PushStream(aPushedStreamId, PromiseFlatCString(aUrl),
+ PromiseFlatCString(aRequestString));
+ }
+ return res ? NS_OK : NS_ERROR_FAILURE;
+ };
+ }
+
+ std::function<void(TransactionObserverResult&&)> observer;
+ if (aHasTransactionObserver) {
+ nsMainThreadPtrHandle<HttpTransactionChild> handle(
+ new nsMainThreadPtrHolder<HttpTransactionChild>(
+ "HttpTransactionChildProxy", this, false));
+ observer = [handle](TransactionObserverResult&& aResult) {
+ handle->mTransactionObserverResult.emplace(std::move(aResult));
+ };
+ }
+
+ RefPtr<nsHttpTransaction> transWithPushedStream;
+ uint32_t pushedStreamId = 0;
+ if (aPushedStreamArg) {
+ HttpTransactionChild* transChild = static_cast<HttpTransactionChild*>(
+ aPushedStreamArg.ref().transWithPushedStream().AsChild().get());
+ transWithPushedStream = transChild->GetHttpTransaction();
+ pushedStreamId = aPushedStreamArg.ref().pushedStreamId();
+ }
+
+ nsresult rv = mTransaction->Init(
+ caps, cinfo, requestHead, requestBody, requestContentLength,
+ requestBodyHasHeaders, GetCurrentSerialEventTarget(),
+ nullptr, // TODO: security callback, fix in bug 1512479.
+ this, browserId, static_cast<HttpTrafficCategory>(httpTrafficCategory),
+ rc, classOfService, initialRwin, responseTimeoutEnabled, channelId,
+ std::move(observer), std::move(pushCallback), transWithPushedStream,
+ pushedStreamId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ Unused << mTransaction->AsyncRead(this, getter_AddRefs(mTransactionPump));
+ return rv;
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvCancelPump(
+ const nsresult& aStatus) {
+ LOG(("HttpTransactionChild::RecvCancelPump start [this=%p]\n", this));
+ CancelInternal(aStatus);
+ return IPC_OK();
+}
+
+void HttpTransactionChild::CancelInternal(nsresult aStatus) {
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (mTransactionPump) {
+ mTransactionPump->Cancel(mStatus);
+ }
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvSuspendPump() {
+ LOG(("HttpTransactionChild::RecvSuspendPump start [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ mTransactionPump->Suspend();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvResumePump() {
+ LOG(("HttpTransactionChild::RecvResumePump start [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ mTransactionPump->Resume();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvInit(
+ const uint32_t& aCaps, const HttpConnectionInfoCloneArgs& aArgs,
+ const nsHttpRequestHead& aReqHeaders, const Maybe<IPCStream>& aRequestBody,
+ const uint64_t& aReqContentLength, const bool& aReqBodyIncludesHeaders,
+ const uint64_t& aTopLevelOuterContentWindowId,
+ const uint8_t& aHttpTrafficCategory, const uint64_t& aRequestContextID,
+ const ClassOfService& aClassOfService, const uint32_t& aInitialRwin,
+ const bool& aResponseTimeoutEnabled, const uint64_t& aChannelId,
+ const bool& aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg,
+ const mozilla::Maybe<PInputChannelThrottleQueueChild*>& aThrottleQueue,
+ const bool& aIsDocumentLoad, const TimeStamp& aRedirectStart,
+ const TimeStamp& aRedirectEnd) {
+ mRequestHead = aReqHeaders;
+ if (aRequestBody) {
+ mUploadStream = mozilla::ipc::DeserializeIPCStream(aRequestBody);
+ }
+
+ mTransaction = new nsHttpTransaction();
+ mChannelId = aChannelId;
+ mIsDocumentLoad = aIsDocumentLoad;
+ mRedirectStart = aRedirectStart;
+ mRedirectEnd = aRedirectEnd;
+
+ if (aThrottleQueue.isSome()) {
+ mThrottleQueue =
+ static_cast<InputChannelThrottleQueueChild*>(aThrottleQueue.ref());
+ }
+
+ nsresult rv = InitInternal(
+ aCaps, aArgs, &mRequestHead, mUploadStream, aReqContentLength,
+ aReqBodyIncludesHeaders, aTopLevelOuterContentWindowId,
+ aHttpTrafficCategory, aRequestContextID, aClassOfService, aInitialRwin,
+ aResponseTimeoutEnabled, aChannelId, aHasTransactionObserver,
+ aPushedStreamArg);
+ if (NS_FAILED(rv)) {
+ LOG(("HttpTransactionChild::RecvInit: [this=%p] InitInternal failed!\n",
+ this));
+ mTransaction = nullptr;
+ SendOnInitFailed(rv);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvSetDNSWasRefreshed() {
+ LOG(("HttpTransactionChild::SetDNSWasRefreshed [this=%p]\n", this));
+ if (mTransaction) {
+ mTransaction->SetDNSWasRefreshed();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvDontReuseConnection() {
+ LOG(("HttpTransactionChild::RecvDontReuseConnection [this=%p]\n", this));
+ if (mTransaction) {
+ mTransaction->DontReuseConnection();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvSetH2WSConnRefTaken() {
+ LOG(("HttpTransactionChild::RecvSetH2WSConnRefTaken [this=%p]\n", this));
+ if (mTransaction) {
+ mTransaction->SetH2WSConnRefTaken();
+ }
+ return IPC_OK();
+}
+
+void HttpTransactionChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpTransactionChild::ActorDestroy [this=%p]\n", this));
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+}
+
+nsHttpTransaction* HttpTransactionChild::GetHttpTransaction() {
+ return mTransaction.get();
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild <nsIStreamListener>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionChild::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("HttpTransactionChild::OnDataAvailable [this=%p, aOffset= %" PRIu64
+ " aCount=%" PRIu32 "]\n",
+ this, aOffset, aCount));
+
+ // Don't bother sending IPC if already canceled.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // TODO: send string data in chunks and handle errors. Bug 1600129.
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mLogicalOffset += aCount;
+
+ if (NS_IsMainThread()) {
+ if (!CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttp::SendFunc<nsCString> sendFunc =
+ [self = UnsafePtr<HttpTransactionChild>(this)](
+ const nsCString& aData, uint64_t aOffset, uint32_t aCount) {
+ return self->SendOnDataAvailable(aData, aOffset, aCount,
+ TimeStamp::Now());
+ };
+
+ LOG((" ODA to parent process"));
+ if (!nsHttp::SendDataInChunks(data, aOffset, aCount, sendFunc)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mDataBridgeParent);
+
+ if (!mDataBridgeParent->CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttp::SendFunc<nsDependentCSubstring> sendFunc =
+ [self = UnsafePtr<HttpTransactionChild>(this)](
+ const nsDependentCSubstring& aData, uint64_t aOffset,
+ uint32_t aCount) {
+ return self->mDataBridgeParent->SendOnTransportAndData(
+ aOffset, aCount, aData, TimeStamp::Now());
+ };
+
+ LOG((" ODA to content process"));
+ if (!nsHttp::SendDataInChunks(data, aOffset, aCount, sendFunc)) {
+ MOZ_ASSERT(false, "Send ODA to content process failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // We still need to send ODA to parent process, because the data needs to be
+ // saved in cache. Note that we set dataSentToChildProcess to true, so this
+ // ODA will not be sent to child process.
+ RefPtr<HttpTransactionChild> self = this;
+ rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "HttpTransactionChild::OnDataAvailable",
+ [self, offset(aOffset), count(aCount), data(data)]() {
+ nsHttp::SendFunc<nsCString> sendFunc =
+ [self](const nsCString& aData, uint64_t aOffset,
+ uint32_t aCount) {
+ return self->SendOnDataAvailable(aData, aOffset, aCount,
+ TimeStamp::Now());
+ };
+
+ if (!nsHttp::SendDataInChunks(data, offset, count, sendFunc)) {
+ self->CancelInternal(NS_ERROR_FAILURE);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+static TimingStructArgs ToTimingStructArgs(TimingStruct aTiming) {
+ TimingStructArgs args;
+ args.domainLookupStart() = aTiming.domainLookupStart;
+ args.domainLookupEnd() = aTiming.domainLookupEnd;
+ args.connectStart() = aTiming.connectStart;
+ args.tcpConnectEnd() = aTiming.tcpConnectEnd;
+ args.secureConnectionStart() = aTiming.secureConnectionStart;
+ args.connectEnd() = aTiming.connectEnd;
+ args.requestStart() = aTiming.requestStart;
+ args.responseStart() = aTiming.responseStart;
+ args.responseEnd() = aTiming.responseEnd;
+ args.transactionPending() = aTiming.transactionPending;
+ return args;
+}
+
+// The maximum number of bytes to consider when attempting to sniff.
+// See https://mimesniff.spec.whatwg.org/#reading-the-resource-header.
+static const uint32_t MAX_BYTES_SNIFFED = 1445;
+
+static void GetDataForSniffer(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsTArray<uint8_t>* outData = static_cast<nsTArray<uint8_t>*>(aClosure);
+ outData->AppendElements(aData, std::min(aCount, MAX_BYTES_SNIFFED));
+}
+
+bool HttpTransactionChild::CanSendODAToContentProcessDirectly(
+ const Maybe<nsHttpResponseHead>& aHead) {
+ if (!StaticPrefs::network_send_ODA_to_content_directly()) {
+ return false;
+ }
+
+ // If this is a document load, the content process that receives ODA is not
+ // decided yet, so don't bother to do the rest check.
+ if (mIsDocumentLoad) {
+ return false;
+ }
+
+ if (!aHead) {
+ return false;
+ }
+
+ // We only need to deliver ODA when the response is succeed.
+ if (aHead->Status() != 200) {
+ return false;
+ }
+
+ // UnknownDecoder could be used in parent process, so we can't send ODA to
+ // content process.
+ if (!aHead->HasContentType()) {
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("HttpTransactionChild::OnStartRequest start [this=%p] mTransaction=%p\n",
+ this, mTransaction.get()));
+
+ // Don't bother sending IPC to parent process if already canceled.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mTransaction);
+
+ nsresult status;
+ aRequest->GetStatus(&status);
+
+ mProtocolVersion.Truncate();
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(mTransaction->SecurityInfo());
+ if (securityInfo) {
+ nsAutoCString protocol;
+ if (NS_SUCCEEDED(securityInfo->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ mProtocolVersion.Assign(protocol);
+ }
+ }
+
+ UniquePtr<nsHttpResponseHead> head(mTransaction->TakeResponseHead());
+ Maybe<nsHttpResponseHead> optionalHead;
+ nsTArray<uint8_t> dataForSniffer;
+ if (head) {
+ if (mProtocolVersion.IsEmpty()) {
+ HttpVersion version = head->Version();
+ mProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
+ }
+ optionalHead = Some(*head);
+
+ if (GetOpaqueResponseBlockedReason(*head) ==
+ OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF) {
+ RefPtr<nsInputStreamPump> pump = do_QueryObject(mTransactionPump);
+ pump->PeekStream(GetDataForSniffer, &dataForSniffer);
+ }
+ }
+
+ Maybe<nsCString> optionalAltSvcUsed;
+ nsCString altSvcUsed;
+ if (NS_SUCCEEDED(mTransaction->RequestHead()->GetHeader(
+ nsHttp::Alternate_Service_Used, altSvcUsed)) &&
+ !altSvcUsed.IsEmpty()) {
+ optionalAltSvcUsed.emplace(altSvcUsed);
+ }
+
+ if (CanSendODAToContentProcessDirectly(optionalHead)) {
+ Maybe<RefPtr<BackgroundDataBridgeParent>> dataBridgeParent =
+ SocketProcessChild::GetSingleton()->GetAndRemoveDataBridge(mChannelId);
+ // Check if there is a registered BackgroundDataBridgeParent.
+ if (dataBridgeParent) {
+ mDataBridgeParent = std::move(dataBridgeParent.ref());
+
+ nsCOMPtr<nsISerialEventTarget> backgroundThread =
+ mDataBridgeParent->GetBackgroundThread();
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
+ retargetableTransactionPump = do_QueryObject(mTransactionPump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableTransactionPump);
+
+ nsresult rv =
+ retargetableTransactionPump->RetargetDeliveryTo(backgroundThread);
+ LOG((" Retarget to background thread [this=%p rv=%08x]\n", this,
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ mDataBridgeParent->Destroy();
+ mDataBridgeParent = nullptr;
+ }
+ }
+ }
+
+ int32_t proxyConnectResponseCode =
+ mTransaction->GetProxyConnectResponseCode();
+
+ nsIRequest::TRRMode mode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason reason = nsITRRSkipReason::TRR_UNSET;
+ {
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ bool isTrr = false;
+ bool echConfigUsed = false;
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(selfAddr, peerAddr, isTrr, mode, reason,
+ echConfigUsed);
+ }
+ }
+
+ Unused << SendOnStartRequest(
+ status, optionalHead, securityInfo, mTransaction->ProxyConnectFailed(),
+ ToTimingStructArgs(mTransaction->Timings()), proxyConnectResponseCode,
+ dataForSniffer, optionalAltSvcUsed, !!mDataBridgeParent,
+ mTransaction->TakeRestartedState(), mTransaction->HTTPSSVCReceivedStage(),
+ mTransaction->GetSupportsHTTP3(), mode, reason, mTransaction->Caps(),
+ TimeStamp::Now());
+ return NS_OK;
+}
+
+ResourceTimingStructArgs HttpTransactionChild::GetTimingAttributes() {
+ // Note that not all fields in ResourceTimingStructArgs are filled, since
+ // we only need some in HttpChannelChild::OnStopRequest.
+ ResourceTimingStructArgs args;
+ args.domainLookupStart() = mTransaction->GetDomainLookupStart();
+ args.domainLookupEnd() = mTransaction->GetDomainLookupEnd();
+ args.connectStart() = mTransaction->GetConnectStart();
+ args.tcpConnectEnd() = mTransaction->GetTcpConnectEnd();
+ args.secureConnectionStart() = mTransaction->GetSecureConnectionStart();
+ args.connectEnd() = mTransaction->GetConnectEnd();
+ args.requestStart() = mTransaction->GetRequestStart();
+ args.responseStart() = mTransaction->GetResponseStart();
+ args.responseEnd() = mTransaction->GetResponseEnd();
+ args.transferSize() = mTransaction->GetTransferSize();
+ args.encodedBodySize() = mLogicalOffset;
+ args.redirectStart() = mRedirectStart;
+ args.redirectEnd() = mRedirectEnd;
+ args.transferSize() = mTransaction->GetTransferSize();
+ args.transactionPending() = mTransaction->GetPendingTime();
+ return args;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ LOG(("HttpTransactionChild::OnStopRequest [this=%p]\n", this));
+
+ mTransactionPump = nullptr;
+
+ auto onStopGuard = MakeScopeExit([&] {
+ LOG((" calling mDataBridgeParent->OnStopRequest by ScopeExit [this=%p]\n",
+ this));
+ MOZ_ASSERT(NS_FAILED(mStatus), "This shoule be only called when failure");
+ if (mDataBridgeParent) {
+ mDataBridgeParent->OnStopRequest(mStatus, ResourceTimingStructArgs(),
+ TimeStamp(), nsHttpHeaderArray(),
+ TimeStamp::Now());
+ mDataBridgeParent = nullptr;
+ }
+ });
+
+ // Don't bother sending IPC to parent process if already canceled.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!CanSend()) {
+ mStatus = NS_ERROR_UNEXPECTED;
+ return mStatus;
+ }
+
+ MOZ_ASSERT(mTransaction);
+
+ UniquePtr<nsHttpHeaderArray> headerArray(
+ mTransaction->TakeResponseTrailers());
+ Maybe<nsHttpHeaderArray> responseTrailers;
+ if (headerArray) {
+ responseTrailers.emplace(*headerArray);
+ }
+
+ onStopGuard.release();
+
+ TimeStamp lastActTabOpt = nsHttp::GetLastActiveTabLoadOptimizationHit();
+
+ if (mDataBridgeParent) {
+ mDataBridgeParent->OnStopRequest(
+ aStatus, GetTimingAttributes(), lastActTabOpt,
+ responseTrailers ? *responseTrailers : nsHttpHeaderArray(),
+ TimeStamp::Now());
+ mDataBridgeParent = nullptr;
+ }
+
+ RefPtr<nsHttpConnectionInfo> connInfo = mTransaction->GetConnInfo();
+ HttpConnectionInfoCloneArgs infoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(connInfo, infoArgs);
+ Unused << SendOnStopRequest(aStatus, mTransaction->ResponseIsComplete(),
+ mTransaction->GetTransferSize(),
+ ToTimingStructArgs(mTransaction->Timings()),
+ responseTrailers, mTransactionObserverResult,
+ lastActTabOpt, infoArgs, TimeStamp::Now());
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild <nsITransportEventSink>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionChild::OnTransportStatus(nsITransport* aTransport,
+ nsresult aStatus, int64_t aProgress,
+ int64_t aProgressMax) {
+ LOG(("HttpTransactionChild::OnTransportStatus [this=%p status=%" PRIx32
+ " progress=%" PRId64 "]\n",
+ this, static_cast<uint32_t>(aStatus), aProgress));
+
+ if (!CanSend()) {
+ return NS_OK;
+ }
+
+ Maybe<NetworkAddressArg> arg;
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO ||
+ aStatus == NS_NET_STATUS_WAITING_FOR) {
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ bool isTrr = false;
+ bool echConfigUsed = false;
+ nsIRequest::TRRMode mode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason reason = nsITRRSkipReason::TRR_UNSET;
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(selfAddr, peerAddr, isTrr, mode, reason,
+ echConfigUsed);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport =
+ do_QueryInterface(aTransport);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&selfAddr);
+ socketTransport->GetPeerAddr(&peerAddr);
+ socketTransport->ResolvedByTRR(&isTrr);
+ socketTransport->GetEffectiveTRRMode(&mode);
+ socketTransport->GetTrrSkipReason(&reason);
+ socketTransport->GetEchConfigUsed(&echConfigUsed);
+ }
+ }
+ arg.emplace(selfAddr, peerAddr, isTrr, mode, reason, echConfigUsed);
+ }
+
+ Unused << SendOnTransportStatus(aStatus, aProgress, aProgressMax, arg);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIThrottledInputChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionChild::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue) {
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue =
+ static_cast<nsIInputChannelThrottleQueue*>(mThrottleQueue.get());
+ queue.forget(aQueue);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIThreadRetargetableStreamListener
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpTransactionChild::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::OnDataFinished(nsresult aStatus) { return NS_OK; }
+
+NS_IMETHODIMP
+HttpTransactionChild::EarlyHint(const nsACString& aValue,
+ const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader) {
+ LOG(("HttpTransactionChild::EarlyHint"));
+ if (CanSend()) {
+ Unused << SendEarlyHint(aValue, aReferrerPolicy, aCSPHeader);
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpTransactionChild.h b/netwerk/protocol/http/HttpTransactionChild.h
new file mode 100644
index 0000000000..7dcb4d4c63
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionChild.h
@@ -0,0 +1,127 @@
+/* -*- 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 HttpTransactionChild_h__
+#define HttpTransactionChild_h__
+
+#include "mozilla/Atomics.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/PHttpTransactionChild.h"
+#include "nsHttpRequestHead.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsIRequest.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITransport.h"
+
+class nsInputStreamPump;
+
+namespace mozilla::net {
+
+class BackgroundDataBridgeParent;
+class InputChannelThrottleQueueChild;
+class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class nsProxyInfo;
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild commutes between parent process and socket process,
+// manages the real nsHttpTransaction and transaction pump.
+//-----------------------------------------------------------------------------
+class HttpTransactionChild final : public PHttpTransactionChild,
+ public nsITransportEventSink,
+ public nsIThrottledInputChannel,
+ public nsIThreadRetargetableStreamListener,
+ public nsIEarlyHintObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSITHROTTLEDINPUTCHANNEL
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSIEARLYHINTOBSERVER
+
+ explicit HttpTransactionChild();
+
+ mozilla::ipc::IPCResult RecvInit(
+ const uint32_t& aCaps, const HttpConnectionInfoCloneArgs& aArgs,
+ const nsHttpRequestHead& aReqHeaders,
+ const Maybe<IPCStream>& aRequestBody, const uint64_t& aReqContentLength,
+ const bool& aReqBodyIncludesHeaders,
+ const uint64_t& aTopLevelOuterContentWindowId,
+ const uint8_t& aHttpTrafficCategory, const uint64_t& aRequestContextID,
+ const ClassOfService& aClassOfService, const uint32_t& aInitialRwin,
+ const bool& aResponseTimeoutEnabled, const uint64_t& aChannelId,
+ const bool& aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg,
+ const mozilla::Maybe<PInputChannelThrottleQueueChild*>& aThrottleQueue,
+ const bool& aIsDocumentLoad, const TimeStamp& aRedirectStart,
+ const TimeStamp& aRedirectEnd);
+ mozilla::ipc::IPCResult RecvCancelPump(const nsresult& aStatus);
+ mozilla::ipc::IPCResult RecvSuspendPump();
+ mozilla::ipc::IPCResult RecvResumePump();
+ mozilla::ipc::IPCResult RecvSetDNSWasRefreshed();
+ mozilla::ipc::IPCResult RecvDontReuseConnection();
+ mozilla::ipc::IPCResult RecvSetH2WSConnRefTaken();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsHttpTransaction* GetHttpTransaction();
+
+ private:
+ virtual ~HttpTransactionChild();
+
+ nsProxyInfo* ProxyInfoCloneArgsToProxyInfo(
+ const nsTArray<ProxyInfoCloneArgs>& aArgs);
+ already_AddRefed<nsHttpConnectionInfo> DeserializeHttpConnectionInfoCloneArgs(
+ const HttpConnectionInfoCloneArgs& aInfoArgs);
+ // Initialize the *real* nsHttpTransaction. See |nsHttpTransaction::Init|
+ // for the parameters.
+ [[nodiscard]] nsresult InitInternal(
+ uint32_t caps, const HttpConnectionInfoCloneArgs& infoArgs,
+ nsHttpRequestHead* requestHead,
+ nsIInputStream* requestBody, // use the trick in bug 1277681
+ uint64_t requestContentLength, bool requestBodyHasHeaders,
+ uint64_t topLevelOuterContentWindowId, uint8_t httpTrafficCategory,
+ uint64_t requestContextID, ClassOfService classOfService,
+ uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId,
+ bool aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg);
+
+ void CancelInternal(nsresult aStatus);
+
+ bool CanSendODAToContentProcessDirectly(
+ const Maybe<nsHttpResponseHead>& aHead);
+
+ ResourceTimingStructArgs GetTimingAttributes();
+
+ // Use Release-Acquire ordering to ensure the OMT ODA is ignored while
+ // transaction is canceled on main thread.
+ Atomic<bool, ReleaseAcquire> mCanceled{false};
+ Atomic<nsresult, ReleaseAcquire> mStatus{NS_OK};
+ uint64_t mChannelId{0};
+ nsHttpRequestHead mRequestHead;
+ bool mIsDocumentLoad{false};
+ uint64_t mLogicalOffset{0};
+ TimeStamp mRedirectStart;
+ TimeStamp mRedirectEnd;
+ nsCString mProtocolVersion;
+
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ RefPtr<nsHttpTransaction> mTransaction;
+ nsCOMPtr<nsIRequest> mTransactionPump;
+ Maybe<TransactionObserverResult> mTransactionObserverResult;
+ RefPtr<InputChannelThrottleQueueChild> mThrottleQueue;
+ RefPtr<BackgroundDataBridgeParent> mDataBridgeParent;
+};
+
+} // namespace mozilla::net
+
+inline nsISupports* ToSupports(mozilla::net::HttpTransactionChild* p) {
+ return static_cast<nsIStreamListener*>(p);
+}
+
+#endif // nsHttpTransactionChild_h__
diff --git a/netwerk/protocol/http/HttpTransactionParent.cpp b/netwerk/protocol/http/HttpTransactionParent.cpp
new file mode 100644
index 0000000000..5408aee641
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionParent.cpp
@@ -0,0 +1,924 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpTransactionParent.h"
+
+#include "HttpTrafficAnalyzer.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/InputChannelThrottleQueueParent.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttpHandler.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSerializationHelper.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(HttpTransactionParent)
+NS_INTERFACE_MAP_BEGIN(HttpTransactionParent)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpTransactionParent)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequest)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP_(MozExternalRefCountType) HttpTransactionParent::Release(void) {
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "HttpTransactionParent");
+ if (count == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete (this);
+ return 0;
+ }
+
+ // When ref count goes down to 1 (held internally by IPDL), it means that
+ // we are done with this transaction. We should send a delete message
+ // to delete the transaction child in socket process.
+ if (count == 1 && CanSend()) {
+ if (!NS_IsMainThread()) {
+ RefPtr<HttpTransactionParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+ NS_NewRunnableFunction("HttpTransactionParent::Release", [self]() {
+ mozilla::Unused << self->Send__delete__(self);
+ // Make sure we can not send IPC after Send__delete__().
+ MOZ_ASSERT(!self->CanSend());
+ })));
+ } else {
+ mozilla::Unused << Send__delete__(this);
+ }
+ return 1;
+ }
+ return count;
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionParent <public>
+//-----------------------------------------------------------------------------
+
+HttpTransactionParent::HttpTransactionParent(bool aIsDocumentLoad)
+ : mIsDocumentLoad(aIsDocumentLoad) {
+ LOG(("Creating HttpTransactionParent @%p\n", this));
+ mEventQ = new ChannelEventQueue(static_cast<nsIRequest*>(this));
+}
+
+HttpTransactionParent::~HttpTransactionParent() {
+ LOG(("Destroying HttpTransactionParent @%p\n", this));
+ mEventQ->NotifyReleasingOwner();
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionParent <nsAHttpTransactionShell>
+//-----------------------------------------------------------------------------
+
+// Let socket process init the *real* nsHttpTransaction.
+nsresult HttpTransactionParent::Init(
+ uint32_t caps, nsHttpConnectionInfo* cinfo, nsHttpRequestHead* requestHead,
+ nsIInputStream* requestBody, uint64_t requestContentLength,
+ bool requestBodyHasHeaders, nsIEventTarget* target,
+ nsIInterfaceRequestor* callbacks, nsITransportEventSink* eventsink,
+ uint64_t browserId, HttpTrafficCategory trafficCategory,
+ nsIRequestContext* requestContext, ClassOfService classOfService,
+ uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId,
+ TransactionObserverFunc&& transactionObserver,
+ OnPushCallback&& aOnPushCallback,
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) {
+ LOG(("HttpTransactionParent::Init [this=%p caps=%x]\n", this, caps));
+
+ if (!CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mEventsink = eventsink;
+ mTargetThread = GetCurrentSerialEventTarget();
+ mChannelId = channelId;
+ mTransactionObserver = std::move(transactionObserver);
+ mOnPushCallback = std::move(aOnPushCallback);
+ mCaps = caps;
+ mConnInfo = cinfo->Clone();
+ mIsHttp3Used = cinfo->IsHttp3();
+
+ HttpConnectionInfoCloneArgs infoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(cinfo, infoArgs);
+
+ Maybe<mozilla::ipc::IPCStream> ipcStream;
+ if (!mozilla::ipc::SerializeIPCStream(do_AddRef(requestBody), ipcStream,
+ /* aAllowLazy */ false)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t requestContextID = requestContext ? requestContext->GetID() : 0;
+
+ Maybe<H2PushedStreamArg> pushedStreamArg;
+ if (aTransWithPushedStream && aPushedStreamId) {
+ MOZ_ASSERT(aTransWithPushedStream->AsHttpTransactionParent());
+ pushedStreamArg.emplace(
+ WrapNotNull(aTransWithPushedStream->AsHttpTransactionParent()),
+ aPushedStreamId);
+ }
+
+ nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mEventsink);
+ Maybe<NotNull<PInputChannelThrottleQueueParent*>> throttleQueue;
+ if (throttled) {
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue;
+ nsresult rv = throttled->GetThrottleQueue(getter_AddRefs(queue));
+ // In case of failure, just carry on without throttling.
+ if (NS_SUCCEEDED(rv) && queue) {
+ LOG1(("HttpTransactionParent::Init %p using throttle queue %p\n", this,
+ queue.get()));
+ RefPtr<InputChannelThrottleQueueParent> tqParent = do_QueryObject(queue);
+ MOZ_ASSERT(tqParent);
+ throttleQueue.emplace(WrapNotNull(tqParent.get()));
+ }
+ }
+
+ // TODO: Figure out if we have to implement nsIThreadRetargetableRequest in
+ // bug 1544378.
+ if (!SendInit(caps, infoArgs, *requestHead, ipcStream, requestContentLength,
+ requestBodyHasHeaders, browserId,
+ static_cast<uint8_t>(trafficCategory), requestContextID,
+ classOfService, initialRwin, responseTimeoutEnabled, mChannelId,
+ !!mTransactionObserver, pushedStreamArg, throttleQueue,
+ mIsDocumentLoad, mRedirectStart, mRedirectEnd)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString reqHeaderBuf = nsHttp::ConvertRequestHeadToString(
+ *requestHead, !!requestBody, requestBodyHasHeaders,
+ cinfo->UsingConnect());
+ requestContentLength += reqHeaderBuf.Length();
+
+ mRequestSize = InScriptableRange(requestContentLength)
+ ? static_cast<int64_t>(requestContentLength)
+ : -1;
+
+ return NS_OK;
+}
+
+nsresult HttpTransactionParent::AsyncRead(nsIStreamListener* listener,
+ nsIRequest** pump) {
+ MOZ_ASSERT(pump);
+
+ *pump = do_AddRef(this).take();
+ mChannel = listener;
+ return NS_OK;
+}
+
+UniquePtr<nsHttpResponseHead> HttpTransactionParent::TakeResponseHead() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
+
+ mResponseHeadTaken = true;
+ return std::move(mResponseHead);
+}
+
+UniquePtr<nsHttpHeaderArray> HttpTransactionParent::TakeResponseTrailers() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
+
+ mResponseTrailersTaken = true;
+ return std::move(mResponseTrailers);
+}
+
+void HttpTransactionParent::SetSniffedTypeToChannel(
+ nsInputStreamPump::PeekSegmentFun aCallTypeSniffers, nsIChannel* aChannel) {
+ if (!mDataForSniffer.IsEmpty()) {
+ aCallTypeSniffers(aChannel, mDataForSniffer.Elements(),
+ mDataForSniffer.Length());
+ }
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ MutexAutoLock lock(mEventTargetMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mODATarget;
+ if (!mODATarget) {
+ target = mTargetThread;
+ }
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+already_AddRefed<nsISerialEventTarget> HttpTransactionParent::GetODATarget() {
+ nsCOMPtr<nsISerialEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ target = mODATarget ? mODATarget : mTargetThread;
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+NS_IMETHODIMP HttpTransactionParent::RetargetDeliveryTo(
+ nsISerialEventTarget* aEventTarget) {
+ LOG(("HttpTransactionParent::RetargetDeliveryTo [this=%p, aTarget=%p]", this,
+ aEventTarget));
+
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+ MOZ_ASSERT(!mODATarget);
+ NS_ENSURE_ARG(aEventTarget);
+
+ if (aEventTarget->IsOnCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mChannel, &rv);
+ if (!retargetableListener || NS_FAILED(rv)) {
+ NS_WARNING("Listener is not retargetable");
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Subsequent listeners are not retargetable");
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ mODATarget = aEventTarget;
+ }
+
+ return NS_OK;
+}
+
+void HttpTransactionParent::SetDNSWasRefreshed() {
+ MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
+ Unused << SendSetDNSWasRefreshed();
+}
+
+void HttpTransactionParent::GetNetworkAddresses(
+ NetAddr& self, NetAddr& peer, bool& aResolvedByTRR,
+ nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason,
+ bool& aEchConfigUsed) {
+ self = mSelfAddr;
+ peer = mPeerAddr;
+ aResolvedByTRR = mResolvedByTRR;
+ aEffectiveTRRMode = mEffectiveTRRMode;
+ aSkipReason = mTRRSkipReason;
+ aEchConfigUsed = mEchConfigUsed;
+}
+
+bool HttpTransactionParent::HasStickyConnection() const {
+ return mCaps & NS_HTTP_STICKY_CONNECTION;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetDomainLookupStart() {
+ return mTimings.domainLookupStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetDomainLookupEnd() {
+ return mTimings.domainLookupEnd;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetConnectStart() {
+ return mTimings.connectStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetTcpConnectEnd() {
+ return mTimings.tcpConnectEnd;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetSecureConnectionStart() {
+ return mTimings.secureConnectionStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetConnectEnd() {
+ return mTimings.connectEnd;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetRequestStart() {
+ return mTimings.requestStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetResponseStart() {
+ return mTimings.responseStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetResponseEnd() {
+ return mTimings.responseEnd;
+}
+
+TimingStruct HttpTransactionParent::Timings() { return mTimings; }
+
+bool HttpTransactionParent::ResponseIsComplete() { return mResponseIsComplete; }
+
+int64_t HttpTransactionParent::GetTransferSize() { return mTransferSize; }
+
+int64_t HttpTransactionParent::GetRequestSize() { return mRequestSize; }
+
+bool HttpTransactionParent::IsHttp3Used() { return mIsHttp3Used; }
+
+bool HttpTransactionParent::DataSentToChildProcess() {
+ return mDataSentToChildProcess;
+}
+
+already_AddRefed<nsITransportSecurityInfo>
+HttpTransactionParent::SecurityInfo() {
+ return do_AddRef(mSecurityInfo);
+}
+
+bool HttpTransactionParent::ProxyConnectFailed() { return mProxyConnectFailed; }
+
+bool HttpTransactionParent::TakeRestartedState() {
+ bool result = mRestarted;
+ mRestarted = false;
+ return result;
+}
+
+uint32_t HttpTransactionParent::HTTPSSVCReceivedStage() {
+ return mHTTPSSVCReceivedStage;
+}
+
+void HttpTransactionParent::DontReuseConnection() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Unused << SendDontReuseConnection();
+}
+
+void HttpTransactionParent::SetH2WSConnRefTaken() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Unused << SendSetH2WSConnRefTaken();
+}
+
+void HttpTransactionParent::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aCallbacks) {
+ // TODO: we might don't need to implement this.
+ // Will figure out in bug 1512479.
+}
+
+void HttpTransactionParent::SetDomainLookupStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mDomainLookupStart = timeStamp;
+ mTimings.domainLookupStart = mDomainLookupStart;
+}
+void HttpTransactionParent::SetDomainLookupEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mDomainLookupEnd = timeStamp;
+ mTimings.domainLookupEnd = mDomainLookupEnd;
+}
+
+nsHttpTransaction* HttpTransactionParent::AsHttpTransaction() {
+ return nullptr;
+}
+
+HttpTransactionParent* HttpTransactionParent::AsHttpTransactionParent() {
+ return this;
+}
+
+int32_t HttpTransactionParent::GetProxyConnectResponseCode() {
+ return mProxyConnectResponseCode;
+}
+
+bool HttpTransactionParent::Http2Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_SPDY;
+}
+
+bool HttpTransactionParent::Http3Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_HTTP3;
+}
+
+already_AddRefed<nsHttpConnectionInfo> HttpTransactionParent::GetConnInfo()
+ const {
+ RefPtr<nsHttpConnectionInfo> connInfo = mConnInfo->Clone();
+ return connInfo.forget();
+}
+
+already_AddRefed<nsIEventTarget> HttpTransactionParent::GetNeckoTarget() {
+ nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget();
+ return target.forget();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings, const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aTrrSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime) {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpTransactionParent>(this), aStatus,
+ aResponseHead, securityInfo = nsCOMPtr{aSecurityInfo},
+ aProxyConnectFailed, aTimings, aProxyConnectResponseCode,
+ aDataForSniffer = CopyableTArray{std::move(aDataForSniffer)},
+ aAltSvcUsed, aDataToChildProcess, aRestarted,
+ aHTTPSSVCReceivedStage, aSupportsHttp3, aMode, aTrrSkipReason,
+ aCaps, aOnStartRequestStartTime]() mutable {
+ self->DoOnStartRequest(
+ aStatus, aResponseHead, securityInfo, aProxyConnectFailed, aTimings,
+ aProxyConnectResponseCode, std::move(aDataForSniffer), aAltSvcUsed,
+ aDataToChildProcess, aRestarted, aHTTPSSVCReceivedStage,
+ aSupportsHttp3, aMode, aTrrSkipReason, aCaps,
+ aOnStartRequestStartTime);
+ }));
+ return IPC_OK();
+}
+
+static void TimingStructArgsToTimingsStruct(const TimingStructArgs& aArgs,
+ TimingStruct& aTimings) {
+ // If domainLookupStart/End was set by the channel before, we use these
+ // timestamps instead the ones from the transaction.
+ if (aTimings.domainLookupStart.IsNull() &&
+ aTimings.domainLookupEnd.IsNull()) {
+ aTimings.domainLookupStart = aArgs.domainLookupStart();
+ aTimings.domainLookupEnd = aArgs.domainLookupEnd();
+ }
+ aTimings.connectStart = aArgs.connectStart();
+ aTimings.tcpConnectEnd = aArgs.tcpConnectEnd();
+ aTimings.secureConnectionStart = aArgs.secureConnectionStart();
+ aTimings.connectEnd = aArgs.connectEnd();
+ aTimings.requestStart = aArgs.requestStart();
+ aTimings.responseStart = aArgs.responseStart();
+ aTimings.responseEnd = aArgs.responseEnd();
+ aTimings.transactionPending = aArgs.transactionPending();
+}
+
+void HttpTransactionParent::DoOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings, const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime) {
+ LOG(("HttpTransactionParent::DoOnStartRequest [this=%p aStatus=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(aStatus)));
+
+ if (mCanceled) {
+ return;
+ }
+
+ MOZ_ASSERT(!mOnStartRequestCalled);
+
+ mStatus = aStatus;
+ mDataSentToChildProcess = aDataToChildProcess;
+ mHTTPSSVCReceivedStage = aHTTPSSVCReceivedStage;
+ mSupportsHTTP3 = aSupportsHttp3;
+ mEffectiveTRRMode = aMode;
+ mTRRSkipReason = aSkipReason;
+ mCaps = aCaps;
+ mSecurityInfo = aSecurityInfo;
+ mOnStartRequestStartTime = aOnStartRequestStartTime;
+
+ if (aResponseHead.isSome()) {
+ mResponseHead = MakeUnique<nsHttpResponseHead>(aResponseHead.ref());
+ }
+ mProxyConnectFailed = aProxyConnectFailed;
+ TimingStructArgsToTimingsStruct(aTimings, mTimings);
+
+ mProxyConnectResponseCode = aProxyConnectResponseCode;
+ mDataForSniffer = std::move(aDataForSniffer);
+ mRestarted = aRestarted;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ MOZ_ASSERT(httpChannel, "mChannel is expected to implement nsIHttpChannel");
+ if (httpChannel) {
+ if (aAltSvcUsed.isSome()) {
+ Unused << httpChannel->SetRequestHeader(
+ nsHttp::Alternate_Service_Used.val(), aAltSvcUsed.ref(), false);
+ }
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv = mChannel->OnStartRequest(this);
+ mOnStartRequestCalled = true;
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnTransportStatus(
+ const nsresult& aStatus, const int64_t& aProgress,
+ const int64_t& aProgressMax,
+ Maybe<NetworkAddressArg>&& aNetworkAddressArg) {
+ if (aNetworkAddressArg) {
+ mSelfAddr = aNetworkAddressArg->selfAddr();
+ mPeerAddr = aNetworkAddressArg->peerAddr();
+ mResolvedByTRR = aNetworkAddressArg->resolvedByTRR();
+ mEffectiveTRRMode = aNetworkAddressArg->mode();
+ mTRRSkipReason = aNetworkAddressArg->trrSkipReason();
+ mEchConfigUsed = aNetworkAddressArg->echConfigUsed();
+ }
+ mEventsink->OnTransportStatus(nullptr, aStatus, aProgress, aProgressMax);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnDataAvailable(
+ const nsCString& aData, const uint64_t& aOffset, const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ LOG(("HttpTransactionParent::RecvOnDataAvailable [this=%p, aOffset= %" PRIu64
+ " aCount=%" PRIu32,
+ this, aOffset, aCount));
+
+ // The final transfer size is updated in OnStopRequest ipc message, but in the
+ // case that the socket process is crashed or something went wrong, we might
+ // not get the OnStopRequest. So, let's update the transfer size here.
+ mTransferSize += aCount;
+
+ if (mCanceled) {
+ return IPC_OK();
+ }
+
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpTransactionParent>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpTransactionParent>(this), aData, aOffset, aCount,
+ aOnDataAvailableStartTime]() {
+ self->DoOnDataAvailable(aData, aOffset, aCount,
+ aOnDataAvailableStartTime);
+ }));
+ return IPC_OK();
+}
+
+void HttpTransactionParent::DoOnDataAvailable(
+ const nsCString& aData, const uint64_t& aOffset, const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ LOG(("HttpTransactionParent::DoOnDataAvailable [this=%p]\n", this));
+ if (mCanceled) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv =
+ NS_NewByteInputStream(getter_AddRefs(stringStream),
+ Span(aData.get(), aCount), NS_ASSIGNMENT_DEPEND);
+
+ if (NS_FAILED(rv)) {
+ CancelOnMainThread(rv);
+ return;
+ }
+
+ mOnDataAvailableStartTime = aOnDataAvailableStartTime;
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mChannel->OnDataAvailable(this, stringStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ CancelOnMainThread(rv);
+ }
+}
+
+// Note: Copied from HttpChannelChild.
+void HttpTransactionParent::CancelOnMainThread(nsresult aRv) {
+ LOG(("HttpTransactionParent::CancelOnMainThread [this=%p]", this));
+
+ if (NS_IsMainThread()) {
+ Cancel(aRv);
+ return;
+ }
+
+ mEventQ->Suspend();
+ // Cancel is expected to preempt any other channel events, thus we put this
+ // event in the front of mEventQ to make sure nsIStreamListener not receiving
+ // any ODA/OnStopRequest callbacks.
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpTransactionParent>(this), aRv]() {
+ self->Cancel(aRv);
+ }));
+ mEventQ->Resume();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& aResponseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ const TimeStamp& aLastActiveTabOptHit,
+ const HttpConnectionInfoCloneArgs& aArgs,
+ const TimeStamp& aOnStopRequestStartTime) {
+ LOG(("HttpTransactionParent::RecvOnStopRequest [this=%p status=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(aStatus)));
+
+ nsHttp::SetLastActiveTabLoadOptimizationHit(aLastActiveTabOptHit);
+
+ if (mCanceled) {
+ return IPC_OK();
+ }
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aArgs);
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpTransactionParent>(this), aStatus,
+ aResponseIsComplete, aTransferSize, aTimings, aResponseTrailers,
+ aTransactionObserverResult{std::move(aTransactionObserverResult)},
+ cinfo{std::move(cinfo)}, aOnStopRequestStartTime]() mutable {
+ self->DoOnStopRequest(aStatus, aResponseIsComplete, aTransferSize,
+ aTimings, aResponseTrailers,
+ std::move(aTransactionObserverResult), cinfo,
+ aOnStopRequestStartTime);
+ }));
+ return IPC_OK();
+}
+
+void HttpTransactionParent::DoOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& aResponseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ nsHttpConnectionInfo* aConnInfo, const TimeStamp& aOnStopRequestStartTime) {
+ LOG(("HttpTransactionParent::DoOnStopRequest [this=%p]\n", this));
+ if (mCanceled) {
+ return;
+ }
+
+ MOZ_ASSERT(!mOnStopRequestCalled, "We should not call OnStopRequest twice");
+
+ mStatus = aStatus;
+
+ nsCOMPtr<nsIRequest> deathGrip = this;
+
+ mResponseIsComplete = aResponseIsComplete;
+ mTransferSize = aTransferSize;
+ mOnStopRequestStartTime = aOnStopRequestStartTime;
+
+ TimingStructArgsToTimingsStruct(aTimings, mTimings);
+
+ if (aResponseTrailers.isSome()) {
+ mResponseTrailers = MakeUnique<nsHttpHeaderArray>(aResponseTrailers.ref());
+ }
+ mConnInfo = aConnInfo;
+ if (aTransactionObserverResult.isSome()) {
+ TransactionObserverFunc obs = nullptr;
+ std::swap(obs, mTransactionObserver);
+ obs(std::move(*aTransactionObserverResult));
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ Unused << mChannel->OnStopRequest(this, mStatus);
+ mOnStopRequestCalled = true;
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnInitFailed(
+ const nsresult& aStatus) {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mEventsink);
+ if (request) {
+ request->Cancel(aStatus);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnH2PushStream(
+ const uint32_t& aPushedStreamId, const nsCString& aResourceUrl,
+ const nsCString& aRequestString) {
+ MOZ_ASSERT(mOnPushCallback);
+
+ mOnPushCallback(aPushedStreamId, aResourceUrl, aRequestString, this);
+ return IPC_OK();
+} // namespace net
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvEarlyHint(
+ const nsCString& aValue, const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader) {
+ LOG(
+ ("HttpTransactionParent::RecvEarlyHint header=%s aReferrerPolicy=%s "
+ "aCSPHeader=%s",
+ PromiseFlatCString(aValue).get(),
+ PromiseFlatCString(aReferrerPolicy).get(),
+ PromiseFlatCString(aCSPHeader).get()));
+ nsCOMPtr<nsIEarlyHintObserver> obs = do_QueryInterface(mChannel);
+ if (obs) {
+ Unused << obs->EarlyHint(aValue, aReferrerPolicy, aCSPHeader);
+ }
+
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionParent <nsIRequest>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionParent::GetName(nsACString& aResult) {
+ aResult.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::IsPending(bool* aRetval) {
+ *aRetval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetStatus(nsresult* aStatus) {
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpTransactionParent::SetCanceledReason(
+ const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP HttpTransactionParent::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP HttpTransactionParent::CancelWithReason(
+ nsresult aStatus, const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::Cancel(nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("HttpTransactionParent::Cancel [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+
+ if (mCanceled) {
+ LOG((" already canceled\n"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_FAILED(aStatus), "cancel with non-failure status code");
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (CanSend()) {
+ Unused << SendCancelPump(mStatus);
+ }
+
+ // Put DoNotifyListener() in front of the queue to avoid OnDataAvailable
+ // being called after cancellation. Note that
+ // HttpTransactionParent::OnStart/StopRequest are driven by IPC messages and
+ // HttpTransactionChild won't send IPC if already canceled. That's why we have
+ // to call DoNotifyListener().
+ mEventQ->Suspend();
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpTransactionParent>(this)]() {
+ self->DoNotifyListener();
+ }));
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+void HttpTransactionParent::DoNotifyListener() {
+ LOG(("HttpTransactionParent::DoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mChannel && !mOnStartRequestCalled) {
+ nsCOMPtr<nsIStreamListener> listener = mChannel;
+ mOnStartRequestCalled = true;
+ listener->OnStartRequest(this);
+ }
+ mOnStartRequestCalled = true;
+
+ // This is to make sure that ODA in the event queue can be processed before
+ // OnStopRequest.
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpTransactionParent>(this)] {
+ self->ContinueDoNotifyListener();
+ }));
+}
+
+void HttpTransactionParent::ContinueDoNotifyListener() {
+ LOG(("HttpTransactionParent::ContinueDoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mChannel && !mOnStopRequestCalled) {
+ nsCOMPtr<nsIStreamListener> listener = mChannel;
+ mOnStopRequestCalled = true; // avoid reentrancy bugs by setting this now
+ listener->OnStopRequest(this, mStatus);
+ }
+ mOnStopRequestCalled = true;
+
+ mChannel = nullptr;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::Suspend() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ if (!mSuspendCount++ && CanSend()) {
+ Unused << SendSuspendPump();
+ }
+ mEventQ->Suspend();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::Resume() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSuspendCount, "Resume called more than Suspend");
+
+ // SendResume only once, when suspend count drops to 0.
+ if (mSuspendCount && !--mSuspendCount) {
+ if (CanSend()) {
+ Unused << SendResumePump();
+ }
+
+ if (mCallOnResume) {
+ nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<HttpTransactionParent> self = this;
+ std::function<void()> callOnResume = nullptr;
+ std::swap(callOnResume, mCallOnResume);
+ neckoTarget->Dispatch(
+ NS_NewRunnableFunction("net::HttpTransactionParent::mCallOnResume",
+ [callOnResume]() { callOnResume(); }),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void HttpTransactionParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpTransactionParent::ActorDestroy [this=%p]\n", this));
+ if (aWhy != Deletion) {
+ // Make sure all the messages are processed.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mStatus = NS_ERROR_FAILURE;
+ HandleAsyncAbort();
+
+ mCanceled = true;
+ }
+}
+
+void HttpTransactionParent::HandleAsyncAbort() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(
+ ("HttpTransactionParent Waiting until resume to do async notification "
+ "[this=%p]\n",
+ this));
+ RefPtr<HttpTransactionParent> self = this;
+ mCallOnResume = [self]() { self->HandleAsyncAbort(); };
+ return;
+ }
+
+ DoNotifyListener();
+}
+
+bool HttpTransactionParent::GetSupportsHTTP3() { return mSupportsHTTP3; }
+
+void HttpTransactionParent::SetIsForWebTransport(bool SetIsForWebTransport) {
+ // TODO: bug 1791727
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetPendingTime() {
+ return mTimings.transactionPending;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpTransactionParent.h b/netwerk/protocol/http/HttpTransactionParent.h
new file mode 100644
index 0000000000..e6e74ebf97
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionParent.h
@@ -0,0 +1,197 @@
+/* -*- 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 HttpTransactionParent_h__
+#define HttpTransactionParent_h__
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/net/HttpTransactionShell.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/PHttpTransactionParent.h"
+#include "nsCOMPtr.h"
+#include "nsHttp.h"
+#include "nsIRequest.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsITransport.h"
+#include "nsITransportSecurityInfo.h"
+
+namespace mozilla::net {
+
+class ChannelEventQueue;
+class nsHttpConnectionInfo;
+
+#define HTTP_TRANSACTION_PARENT_IID \
+ { \
+ 0xb83695cb, 0xc24b, 0x4c53, { \
+ 0x85, 0x9b, 0x77, 0x77, 0x3e, 0xc5, 0x44, 0xe5 \
+ } \
+ }
+
+// HttpTransactionParent plays the role of nsHttpTransaction and delegates the
+// work to the nsHttpTransaction in socket process.
+class HttpTransactionParent final : public PHttpTransactionParent,
+ public HttpTransactionShell,
+ public nsIRequest,
+ public nsIThreadRetargetableRequest {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_HTTPTRANSACTIONSHELL
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_TRANSACTION_PARENT_IID)
+
+ explicit HttpTransactionParent(bool aIsDocumentLoad);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings,
+ const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime);
+ mozilla::ipc::IPCResult RecvOnTransportStatus(
+ const nsresult& aStatus, const int64_t& aProgress,
+ const int64_t& aProgressMax,
+ Maybe<NetworkAddressArg>&& aNetworkAddressArg);
+ mozilla::ipc::IPCResult RecvOnDataAvailable(
+ const nsCString& aData, const uint64_t& aOffset, const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime);
+ mozilla::ipc::IPCResult RecvOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& responseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ const TimeStamp& aLastActiveTabOptHit,
+ const HttpConnectionInfoCloneArgs& aArgs,
+ const TimeStamp& aOnStopRequestStartTime);
+ mozilla::ipc::IPCResult RecvOnInitFailed(const nsresult& aStatus);
+
+ mozilla::ipc::IPCResult RecvOnH2PushStream(const uint32_t& aPushedStreamId,
+ const nsCString& aResourceUrl,
+ const nsCString& aRequestString);
+ mozilla::ipc::IPCResult RecvEarlyHint(const nsCString& aValue,
+ const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader);
+
+ virtual mozilla::TimeStamp GetPendingTime() override;
+
+ already_AddRefed<nsIEventTarget> GetNeckoTarget();
+
+ void SetSniffedTypeToChannel(
+ nsInputStreamPump::PeekSegmentFun aCallTypeSniffers,
+ nsIChannel* aChannel);
+
+ void SetRedirectTimestamp(TimeStamp aRedirectStart, TimeStamp aRedirectEnd) {
+ mRedirectStart = aRedirectStart;
+ mRedirectEnd = aRedirectEnd;
+ }
+
+ virtual TimeStamp GetOnStartRequestStartTime() const override {
+ return mOnStartRequestStartTime;
+ }
+ virtual TimeStamp GetDataAvailableStartTime() const override {
+ return mOnDataAvailableStartTime;
+ }
+ virtual TimeStamp GetOnStopRequestStartTime() const override {
+ return mOnStopRequestStartTime;
+ }
+
+ private:
+ virtual ~HttpTransactionParent();
+
+ void GetStructFromInfo(nsHttpConnectionInfo* aInfo,
+ HttpConnectionInfoCloneArgs& aArgs);
+ void DoOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings,
+ const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime);
+ void DoOnDataAvailable(const nsCString& aData, const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime);
+ void DoOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& responseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ nsHttpConnectionInfo* aConnInfo,
+ const TimeStamp& aOnStopRequestStartTime);
+ void DoNotifyListener();
+ void ContinueDoNotifyListener();
+ // Get event target for ODA.
+ already_AddRefed<nsISerialEventTarget> GetODATarget();
+ void CancelOnMainThread(nsresult aRv);
+ void HandleAsyncAbort();
+
+ nsCOMPtr<nsITransportEventSink> mEventsink;
+ nsCOMPtr<nsIStreamListener> mChannel;
+ nsCOMPtr<nsISerialEventTarget> mTargetThread;
+ nsCOMPtr<nsISerialEventTarget> mODATarget;
+ Mutex mEventTargetMutex MOZ_UNANNOTATED{
+ "HttpTransactionParent::EventTargetMutex"};
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ UniquePtr<nsHttpResponseHead> mResponseHead;
+ UniquePtr<nsHttpHeaderArray> mResponseTrailers;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ bool mResponseIsComplete{false};
+ int64_t mTransferSize{0};
+ int64_t mRequestSize{0};
+ bool mIsHttp3Used = false;
+ bool mProxyConnectFailed{false};
+ Atomic<bool, ReleaseAcquire> mCanceled{false};
+ Atomic<nsresult, ReleaseAcquire> mStatus{NS_OK};
+ int32_t mSuspendCount{0};
+ bool mResponseHeadTaken{false};
+ bool mResponseTrailersTaken{false};
+ bool mOnStartRequestCalled{false};
+ bool mOnStopRequestCalled{false};
+ bool mResolvedByTRR{false};
+ nsIRequest::TRRMode mEffectiveTRRMode{nsIRequest::TRR_DEFAULT_MODE};
+ TRRSkippedReason mTRRSkipReason{nsITRRSkipReason::TRR_UNSET};
+ bool mEchConfigUsed = false;
+ int32_t mProxyConnectResponseCode{0};
+ uint64_t mChannelId{0};
+ bool mDataSentToChildProcess{false};
+ bool mIsDocumentLoad;
+ bool mRestarted{false};
+ Atomic<uint32_t, ReleaseAcquire> mCaps{0};
+ TimeStamp mRedirectStart;
+ TimeStamp mRedirectEnd;
+
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ TimingStruct mTimings;
+ TimeStamp mDomainLookupStart;
+ TimeStamp mDomainLookupEnd;
+ TimeStamp mOnStartRequestStartTime;
+ TimeStamp mOnDataAvailableStartTime;
+ TimeStamp mOnStopRequestStartTime;
+ TransactionObserverFunc mTransactionObserver;
+ OnPushCallback mOnPushCallback;
+ nsTArray<uint8_t> mDataForSniffer;
+ std::function<void()> mCallOnResume;
+ uint32_t mHTTPSSVCReceivedStage{};
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ bool mSupportsHTTP3 = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransactionParent,
+ HTTP_TRANSACTION_PARENT_IID)
+
+} // namespace mozilla::net
+
+#endif // nsHttpTransactionParent_h__
diff --git a/netwerk/protocol/http/HttpTransactionShell.h b/netwerk/protocol/http/HttpTransactionShell.h
new file mode 100644
index 0000000000..495ab19a1b
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionShell.h
@@ -0,0 +1,242 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpTransactionShell_h__
+#define HttpTransactionShell_h__
+
+#include <functional>
+
+#include "TimingStruct.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIClassOfService.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsISupports.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsInputStreamPump.h"
+
+class nsIEventTraget;
+class nsIInputStream;
+class nsIInterfaceRequestor;
+class nsIRequest;
+class nsIRequestContext;
+class nsITransportEventSink;
+
+namespace mozilla::net {
+
+enum HttpTrafficCategory : uint8_t;
+class Http2PushedStreamWrapper;
+class HttpTransactionParent;
+class nsHttpConnectionInfo;
+class nsHttpHeaderArray;
+class nsHttpRequestHead;
+class nsHttpTransaction;
+class TransactionObserverResult;
+union NetAddr;
+
+//----------------------------------------------------------------------------
+// Abstract base class for a HTTP transaction in the chrome process
+//----------------------------------------------------------------------------
+
+// 95e5a5b7-6aa2-4011-920a-0908b52f95d4
+#define HTTPTRANSACTIONSHELL_IID \
+ { \
+ 0x95e5a5b7, 0x6aa2, 0x4011, { \
+ 0x92, 0x0a, 0x09, 0x08, 0xb5, 0x2f, 0x95, 0xd4 \
+ } \
+ }
+
+class HttpTransactionShell : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPTRANSACTIONSHELL_IID)
+
+ using TransactionObserverFunc =
+ std::function<void(TransactionObserverResult&&)>;
+ using OnPushCallback = std::function<nsresult(
+ uint32_t, const nsACString&, const nsACString&, HttpTransactionShell*)>;
+
+ //
+ // called to initialize the transaction
+ //
+ // @param caps
+ // the transaction capabilities (see nsHttp.h)
+ // @param connInfo
+ // the connection type for this transaction.
+ // @param reqHeaders
+ // the request header struct
+ // @param reqBody
+ // the request body (POST or PUT data stream)
+ // @param reqBodyIncludesHeaders
+ // fun stuff to support NPAPI plugins.
+ // @param target
+ // the dispatch target were notifications should be sent.
+ // @param callbacks
+ // the notification callbacks to be given to PSM.
+ // @param browserId
+ // indicate the id of the browser in which this transaction is being
+ // loaded.
+ [[nodiscard]] nsresult virtual Init(
+ uint32_t caps, nsHttpConnectionInfo* connInfo,
+ nsHttpRequestHead* reqHeaders, nsIInputStream* reqBody,
+ uint64_t reqContentLength, bool reqBodyIncludesHeaders,
+ nsIEventTarget* consumerTarget, nsIInterfaceRequestor* callbacks,
+ nsITransportEventSink* eventsink, uint64_t browserId,
+ HttpTrafficCategory trafficCategory, nsIRequestContext* requestContext,
+ ClassOfService classOfService, uint32_t initialRwin,
+ bool responseTimeoutEnabled, uint64_t channelId,
+ TransactionObserverFunc&& transactionObserver,
+ OnPushCallback&& aOnPushCallback,
+ HttpTransactionShell* aTransWithPushedStream,
+ uint32_t aPushedStreamId) = 0;
+
+ // @param aListener
+ // receives notifications.
+ // @param pump
+ // the pump that will contain the response data. async wait on this
+ // input stream for data. On first notification, headers should be
+ // available (check transaction status).
+ virtual nsresult AsyncRead(nsIStreamListener* listener,
+ nsIRequest** pump) = 0;
+
+ // Called to take ownership of the response headers; the transaction
+ // will drop any reference to the response headers after this call.
+ virtual UniquePtr<nsHttpResponseHead> TakeResponseHead() = 0;
+
+ // Called to take ownership of the trailer headers.
+ // Returning null if there is no trailer.
+ virtual UniquePtr<nsHttpHeaderArray> TakeResponseTrailers() = 0;
+
+ virtual already_AddRefed<nsITransportSecurityInfo> SecurityInfo() = 0;
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
+
+ virtual void GetNetworkAddresses(NetAddr& self, NetAddr& peer,
+ bool& aResolvedByTRR,
+ nsIRequest::TRRMode& aEffectiveTRRMode,
+ TRRSkippedReason& aSkipReason,
+ bool& aEchConfigUsed) = 0;
+
+ // Functions for Timing interface
+ virtual mozilla::TimeStamp GetDomainLookupStart() = 0;
+ virtual mozilla::TimeStamp GetDomainLookupEnd() = 0;
+ virtual mozilla::TimeStamp GetConnectStart() = 0;
+ virtual mozilla::TimeStamp GetTcpConnectEnd() = 0;
+ virtual mozilla::TimeStamp GetSecureConnectionStart() = 0;
+
+ virtual mozilla::TimeStamp GetConnectEnd() = 0;
+ virtual mozilla::TimeStamp GetRequestStart() = 0;
+ virtual mozilla::TimeStamp GetResponseStart() = 0;
+ virtual mozilla::TimeStamp GetResponseEnd() = 0;
+
+ virtual void SetDomainLookupStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull = false) = 0;
+ virtual void SetDomainLookupEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull = false) = 0;
+
+ virtual TimingStruct Timings() = 0;
+
+ virtual mozilla::TimeStamp GetPendingTime() = 0;
+
+ // Called to set/find out if the transaction generated a complete response.
+ virtual bool ResponseIsComplete() = 0;
+ virtual int64_t GetTransferSize() = 0;
+ virtual int64_t GetRequestSize() = 0;
+ virtual bool IsHttp3Used() = 0;
+
+ // Called to notify that a requested DNS cache entry was refreshed.
+ virtual void SetDNSWasRefreshed() = 0;
+
+ virtual void DontReuseConnection() = 0;
+ virtual bool HasStickyConnection() const = 0;
+
+ virtual void SetH2WSConnRefTaken() = 0;
+
+ virtual bool ProxyConnectFailed() = 0;
+ virtual int32_t GetProxyConnectResponseCode() = 0;
+
+ virtual bool DataSentToChildProcess() = 0;
+
+ virtual nsHttpTransaction* AsHttpTransaction() = 0;
+ virtual HttpTransactionParent* AsHttpTransactionParent() = 0;
+
+ virtual bool TakeRestartedState() = 0;
+ virtual uint32_t HTTPSSVCReceivedStage() = 0;
+
+ virtual bool Http2Disabled() const = 0;
+ virtual bool Http3Disabled() const = 0;
+ virtual already_AddRefed<nsHttpConnectionInfo> GetConnInfo() const = 0;
+
+ virtual bool GetSupportsHTTP3() = 0;
+
+ virtual void SetIsForWebTransport(bool aIsForWebTransport) = 0;
+
+ virtual TimeStamp GetOnStartRequestStartTime() const { return TimeStamp(); }
+ virtual TimeStamp GetDataAvailableStartTime() const { return TimeStamp(); }
+ virtual TimeStamp GetOnStopRequestStartTime() const { return TimeStamp(); }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransactionShell, HTTPTRANSACTIONSHELL_IID)
+
+#define NS_DECL_HTTPTRANSACTIONSHELL \
+ virtual nsresult Init( \
+ uint32_t caps, nsHttpConnectionInfo* connInfo, \
+ nsHttpRequestHead* reqHeaders, nsIInputStream* reqBody, \
+ uint64_t reqContentLength, bool reqBodyIncludesHeaders, \
+ nsIEventTarget* consumerTarget, nsIInterfaceRequestor* callbacks, \
+ nsITransportEventSink* eventsink, uint64_t browserId, \
+ HttpTrafficCategory trafficCategory, nsIRequestContext* requestContext, \
+ ClassOfService classOfService, uint32_t initialRwin, \
+ bool responseTimeoutEnabled, uint64_t channelId, \
+ TransactionObserverFunc&& transactionObserver, \
+ OnPushCallback&& aOnPushCallback, \
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) \
+ override; \
+ virtual nsresult AsyncRead(nsIStreamListener* listener, nsIRequest** pump) \
+ override; \
+ virtual UniquePtr<nsHttpResponseHead> TakeResponseHead() override; \
+ virtual UniquePtr<nsHttpHeaderArray> TakeResponseTrailers() override; \
+ virtual already_AddRefed<nsITransportSecurityInfo> SecurityInfo() override; \
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) \
+ override; \
+ virtual void GetNetworkAddresses( \
+ NetAddr& self, NetAddr& peer, bool& aResolvedByTRR, \
+ nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason, \
+ bool& aEchConfigUsed) override; \
+ virtual mozilla::TimeStamp GetDomainLookupStart() override; \
+ virtual mozilla::TimeStamp GetDomainLookupEnd() override; \
+ virtual mozilla::TimeStamp GetConnectStart() override; \
+ virtual mozilla::TimeStamp GetTcpConnectEnd() override; \
+ virtual mozilla::TimeStamp GetSecureConnectionStart() override; \
+ virtual mozilla::TimeStamp GetConnectEnd() override; \
+ virtual mozilla::TimeStamp GetRequestStart() override; \
+ virtual mozilla::TimeStamp GetResponseStart() override; \
+ virtual mozilla::TimeStamp GetResponseEnd() override; \
+ virtual void SetDomainLookupStart(mozilla::TimeStamp timeStamp, \
+ bool onlyIfNull = false) override; \
+ virtual void SetDomainLookupEnd(mozilla::TimeStamp timeStamp, \
+ bool onlyIfNull = false) override; \
+ virtual TimingStruct Timings() override; \
+ virtual bool ResponseIsComplete() override; \
+ virtual int64_t GetTransferSize() override; \
+ virtual int64_t GetRequestSize() override; \
+ virtual bool IsHttp3Used() override; \
+ virtual void SetDNSWasRefreshed() override; \
+ virtual void DontReuseConnection() override; \
+ virtual bool HasStickyConnection() const override; \
+ virtual void SetH2WSConnRefTaken() override; \
+ virtual bool ProxyConnectFailed() override; \
+ virtual int32_t GetProxyConnectResponseCode() override; \
+ virtual bool DataSentToChildProcess() override; \
+ virtual nsHttpTransaction* AsHttpTransaction() override; \
+ virtual HttpTransactionParent* AsHttpTransactionParent() override; \
+ virtual bool TakeRestartedState() override; \
+ virtual uint32_t HTTPSSVCReceivedStage() override; \
+ virtual bool Http2Disabled() const override; \
+ virtual bool Http3Disabled() const override; \
+ virtual already_AddRefed<nsHttpConnectionInfo> GetConnInfo() const override; \
+ virtual bool GetSupportsHTTP3() override; \
+ virtual void SetIsForWebTransport(bool aIsForWebTransport) override;
+
+} // namespace mozilla::net
+
+#endif // HttpTransactionShell_h__
diff --git a/netwerk/protocol/http/HttpWinUtils.cpp b/netwerk/protocol/http/HttpWinUtils.cpp
new file mode 100644
index 0000000000..b566cdc4f6
--- /dev/null
+++ b/netwerk/protocol/http/HttpWinUtils.cpp
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpWinUtils.h"
+#include "nsIURI.h"
+#include "nsHttpChannel.h"
+#include "mozilla/ClearOnShutdown.h"
+#include <proofofpossessioncookieinfo.h>
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<IProofOfPossessionCookieInfoManager> sPopCookieManager;
+static bool sPopCookieManagerAvailable = true;
+
+void AddWindowsSSO(nsHttpChannel* channel) {
+ if (!sPopCookieManagerAvailable) {
+ return;
+ }
+ HRESULT hr;
+ if (!sPopCookieManager) {
+ GUID CLSID_ProofOfPossessionCookieInfoManager;
+ GUID IID_IProofOfPossessionCookieInfoManager;
+
+ CLSIDFromString(L"{A9927F85-A304-4390-8B23-A75F1C668600}",
+ &CLSID_ProofOfPossessionCookieInfoManager);
+ IIDFromString(L"{CDAECE56-4EDF-43DF-B113-88E4556FA1BB}",
+ &IID_IProofOfPossessionCookieInfoManager);
+
+ hr = CoCreateInstance(CLSID_ProofOfPossessionCookieInfoManager, NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_IProofOfPossessionCookieInfoManager,
+ reinterpret_cast<void**>(&sPopCookieManager));
+ if (FAILED(hr)) {
+ sPopCookieManagerAvailable = false;
+ return;
+ }
+
+ RunOnShutdown([&] {
+ if (sPopCookieManager) {
+ sPopCookieManager = nullptr;
+ }
+ });
+ }
+
+ DWORD cookieCount = 0;
+ ProofOfPossessionCookieInfo* cookieInfo = nullptr;
+
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ nsAutoCString urispec;
+ uri->GetSpec(urispec);
+
+ hr = sPopCookieManager->GetCookieInfoForUri(
+ NS_ConvertUTF8toUTF16(urispec).get(), &cookieCount, &cookieInfo);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ nsAutoCString host;
+ uri->GetHost(host);
+ bool addCookies = false;
+ if (StringEndsWith(host, ".live.com"_ns)) {
+ addCookies = true;
+ }
+
+ nsAutoString allCookies;
+
+ for (DWORD i = 0; i < cookieCount; i++) {
+ nsAutoString cookieData;
+ cookieData.Assign(cookieInfo[i].data);
+ // Strip old Set-Cookie info for WinInet
+ int32_t semicolon = cookieData.FindChar(';');
+ if (semicolon >= 0) {
+ cookieData.SetLength(semicolon);
+ }
+ if (StringBeginsWith(nsDependentString(cookieInfo[i].name), u"x-ms-"_ns)) {
+ channel->SetRequestHeader(NS_ConvertUTF16toUTF8(cookieInfo[i].name),
+ NS_ConvertUTF16toUTF8(cookieData),
+ true /* merge */);
+ } else if (addCookies) {
+ if (!allCookies.IsEmpty()) {
+ allCookies.AppendLiteral("; ");
+ }
+ allCookies.Append(cookieInfo[i].name);
+ allCookies.AppendLiteral("=");
+ allCookies.Append(cookieData);
+ }
+ }
+
+ // Merging cookie headers doesn't work correctly as it separates the new
+ // cookies using commas instead of semicolons, so we have to replace
+ // the entire header.
+ if (!allCookies.IsEmpty()) {
+ nsAutoCString cookieHeader;
+ channel->GetRequestHeader(nsHttp::Cookie.val(), cookieHeader);
+ if (!cookieHeader.IsEmpty()) {
+ cookieHeader.AppendLiteral("; ");
+ }
+ cookieHeader.Append(NS_ConvertUTF16toUTF8(allCookies));
+ channel->SetRequestHeader(nsHttp::Cookie.val(), cookieHeader, false);
+ }
+ if (cookieInfo) {
+ FreeProofOfPossessionCookieInfoArray(cookieInfo, cookieCount);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpWinUtils.h b/netwerk/protocol/http/HttpWinUtils.h
new file mode 100644
index 0000000000..7d78f7188b
--- /dev/null
+++ b/netwerk/protocol/http/HttpWinUtils.h
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpWinUtils_h__
+#define HttpWinUtils_h__
+
+namespace mozilla {
+namespace net {
+
+class nsHttpChannel;
+
+void AddWindowsSSO(nsHttpChannel* channel);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpWinUtils_h__
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp
new file mode 100644
index 0000000000..c58fbc9639
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -0,0 +1,1714 @@
+/* -*- 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 "InterceptedHttpChannel.h"
+#include "NetworkMarker.h"
+#include "nsContentSecurityManager.h"
+#include "nsEscape.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "nsHttpChannel.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIRedirectResultListener.h"
+#include "nsStringStream.h"
+#include "nsStreamUtils.h"
+#include "nsQueryObject.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla::net {
+
+mozilla::LazyLogModule gInterceptedLog("Intercepted");
+
+#define INTERCEPTED_LOG(args) MOZ_LOG(gInterceptedLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS_INHERITED(InterceptedHttpChannel, HttpBaseChannel,
+ nsIInterceptedChannel, nsICacheInfoChannel,
+ nsIAsyncVerifyRedirectCallback, nsIRequestObserver,
+ nsIStreamListener, nsIThreadRetargetableRequest,
+ nsIThreadRetargetableStreamListener,
+ nsIClassOfService)
+
+InterceptedHttpChannel::InterceptedHttpChannel(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp)
+ : HttpAsyncAborter<InterceptedHttpChannel>(this),
+ mProgress(0),
+ mProgressReported(0),
+ mSynthesizedStreamLength(-1),
+ mResumeStartPos(0),
+ mCallingStatusAndProgress(false) {
+ // Pre-set the creation and AsyncOpen times based on the original channel
+ // we are intercepting. We don't want our extra internal redirect to mask
+ // any time spent processing the channel.
+ INTERCEPTED_LOG(("Creating InterceptedHttpChannel [%p]", this));
+ mChannelCreationTime = aCreationTime;
+ mChannelCreationTimestamp = aCreationTimestamp;
+ mInterceptedChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = aAsyncOpenTimestamp;
+}
+
+void InterceptedHttpChannel::ReleaseListeners() {
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+ HttpBaseChannel::ReleaseListeners();
+ mSynthesizedResponseHead.reset();
+ mRedirectChannel = nullptr;
+ mBodyReader = nullptr;
+ mReleaseHandle = nullptr;
+ mProgressSink = nullptr;
+ mBodyCallback = nullptr;
+ mPump = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(!LoadIsPending());
+}
+
+nsresult InterceptedHttpChannel::SetupReplacementChannel(
+ nsIURI* aURI, nsIChannel* aChannel, bool aPreserveMethod,
+ uint32_t aRedirectFlags) {
+ INTERCEPTED_LOG(
+ ("InterceptedHttpChannel::SetupReplacementChannel [%p] flag: %u", this,
+ aRedirectFlags));
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ aURI, aChannel, aPreserveMethod, aRedirectFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = CheckRedirectLimit(aRedirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // While we can't resume an synthetic response, we can still propagate
+ // the resume params across redirects for other channels to handle.
+ if (mResumeStartPos > 0) {
+ nsCOMPtr<nsIResumableChannel> resumable = do_QueryInterface(aChannel);
+ if (!resumable) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ resumable->ResumeAt(mResumeStartPos, mResumeEntityId);
+ }
+
+ return NS_OK;
+}
+
+void InterceptedHttpChannel::AsyncOpenInternal() {
+ // We save this timestamp from outside of the if block in case we enable the
+ // profiler after AsyncOpen().
+ INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpenInternal [%p]", this));
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+
+ // If an error occurs in this file we must ensure mListener callbacks are
+ // invoked in some way. We either Cancel() or ResetInterception below
+ // depending on which path we take.
+ nsresult rv = NS_OK;
+
+ // Start the interception, record the start time.
+ mTimeStamps.Init(this);
+ mTimeStamps.RecordTime();
+
+ // We should have pre-set the AsyncOpen time based on the original channel if
+ // timings are enabled.
+ if (LoadTimingEnabled()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mAsyncOpenTime.IsNull());
+ }
+
+ StoreIsPending(true);
+ StoreResponseCouldBeSynthesized(true);
+
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ // If we already have a synthesized body then we are pre-synthesized.
+ // This can happen for two reasons:
+ // 1. We have a pre-synthesized redirect in e10s mode. In this case
+ // we should follow the redirect.
+ // 2. We are handling a "fake" redirect for an opaque response. Here
+ // we should just process the synthetic body.
+ if (mBodyReader) {
+ // If we fail in this path, then cancel the channel. We don't want
+ // to ResetInterception() after a synthetic result has already been
+ // produced by the ServiceWorker.
+ auto autoCancel = MakeScopeExit([&] {
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ });
+
+ // The fetch event will not be dispatched, record current time for
+ // FetchHandlerStart and FetchHandlerFinish.
+ SetFetchHandlerStart(TimeStamp::Now());
+ SetFetchHandlerFinish(TimeStamp::Now());
+
+ if (ShouldRedirect()) {
+ rv = FollowSyntheticRedirect();
+ return;
+ }
+
+ rv = StartPump();
+ return;
+ }
+
+ // If we fail the initial interception, then attempt to ResetInterception
+ // to fall back to network. We only cancel if the reset fails.
+ auto autoReset = MakeScopeExit([&] {
+ if (NS_FAILED(rv)) {
+ rv = ResetInterception(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ }
+ }
+ });
+
+ // Otherwise we need to trigger a FetchEvent in a ServiceWorker.
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ if (NS_WARN_IF(!controller)) {
+ rv = NS_ERROR_DOM_INVALID_STATE_ERR;
+ return;
+ }
+
+ rv = controller->ChannelIntercepted(this);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+bool InterceptedHttpChannel::ShouldRedirect() const {
+ // Determine if the synthetic response requires us to perform a real redirect.
+ return nsHttpChannel::WillRedirect(*mResponseHead) &&
+ !mLoadInfo->GetDontFollowRedirects();
+}
+
+nsresult InterceptedHttpChannel::FollowSyntheticRedirect() {
+ // Perform a real redirect based on the synthetic response.
+
+ nsCOMPtr<nsIIOService> ioService;
+ nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString location;
+ rv = mResponseHead->GetHeader(nsHttp::Location, location);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
+ locationBuf)) {
+ location = locationBuf;
+ }
+
+ nsCOMPtr<nsIURI> redirectURI;
+ rv = ioService->NewURI(nsDependentCString(location.get()), nullptr, mURI,
+ getter_AddRefs(redirectURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_CORRUPTED_CONTENT);
+
+ uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+ if (nsHttp::IsPermanentRedirect(mResponseHead->Status())) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ }
+
+ PropagateReferenceIfNeeded(mURI, redirectURI);
+
+ bool rewriteToGET = ShouldRewriteRedirectToGET(mResponseHead->Status(),
+ mRequestHead.ParsedMethod());
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(redirectURI, redirectFlags);
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), redirectURI,
+ redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ mLoadFlags, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET,
+ redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = std::move(newChannel);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel,
+ redirectFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ OnRedirectVerifyCallback(rv);
+ } else {
+ // Redirect success, record the finish time and the final status.
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Redirected);
+ }
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::RedirectForResponseURL(
+ nsIURI* aResponseURI, bool aResponseRedirected) {
+ // Perform a service worker redirect to another InterceptedHttpChannel using
+ // the given response URL. It allows content to see the final URL where
+ // appropriate and also helps us enforce cross-origin restrictions. The
+ // resulting channel will then process the synthetic response as normal. This
+ // extra redirect is performed so that listeners treat the result as unsafe
+ // cross-origin data.
+
+ nsresult rv = NS_OK;
+
+ // We want to pass ownership of the body callback to the new synthesized
+ // channel. We need to hold a reference to the callbacks on the stack
+ // as well, though, so we can call them if a failure occurs.
+ nsCOMPtr<nsIInterceptedBodyCallback> bodyCallback = std::move(mBodyCallback);
+
+ RefPtr<InterceptedHttpChannel> newChannel = CreateForSynthesis(
+ mResponseHead.get(), mBodyReader, bodyCallback, mChannelCreationTime,
+ mChannelCreationTimestamp, mAsyncOpenTime);
+
+ // If the response has been redirected, propagate all the URLs to content.
+ // Thus, the exact value of the redirect flag does not matter as long as it's
+ // not REDIRECT_INTERNAL.
+ uint32_t flags = aResponseRedirected ? nsIChannelEventSink::REDIRECT_TEMPORARY
+ : nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(aResponseURI, flags);
+
+ ExtContentPolicyType contentPolicyType =
+ redirectLoadInfo->GetExternalContentPolicyType();
+
+ rv = newChannel->Init(aResponseURI, mCaps,
+ static_cast<nsProxyInfo*>(mProxyInfo.get()),
+ mProxyResolveFlags, mProxyURI, mChannelId,
+ contentPolicyType, redirectLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Normally we don't propagate the LoadInfo's service worker tainting
+ // synthesis flag on redirect. A real redirect normally will want to allow
+ // normal tainting to proceed from its starting taint. For this particular
+ // redirect, though, we are performing a redirect to communicate the URL of
+ // the service worker synthetic response itself. This redirect still
+ // represents the synthetic response, so we must preserve the flag.
+ if (redirectLoadInfo && mLoadInfo &&
+ mLoadInfo->GetServiceWorkerTaintingSynthesized()) {
+ redirectLoadInfo->SynthesizeServiceWorkerTainting(mLoadInfo->GetTainting());
+ }
+
+ rv = SetupReplacementChannel(aResponseURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = newChannel;
+
+ MOZ_ASSERT(mBodyReader);
+ MOZ_ASSERT(!LoadApplyConversion());
+ newChannel->SetApplyConversion(false);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
+
+ if (NS_FAILED(rv)) {
+ // Make sure to call the body callback since we took ownership
+ // above. Neither the new channel or our standard
+ // OnRedirectVerifyCallback() code will invoke the callback. Do it here.
+ bodyCallback->BodyComplete(rv);
+
+ OnRedirectVerifyCallback(rv);
+ }
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::StartPump() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPump);
+ MOZ_DIAGNOSTIC_ASSERT(mBodyReader);
+
+ // We don't support resuming an intercepted channel. We can't guarantee the
+ // ServiceWorker will always return the same data and we can't rely on the
+ // http cache code to detect changes. For now, just force the channel to
+ // NS_ERROR_NOT_RESUMABLE which should cause the front-end to recreate the
+ // channel without calling ResumeAt().
+ //
+ // It would also be possible to convert this information to a range request,
+ // but its unclear if we should do that for ServiceWorker FetchEvents. See:
+ //
+ // https://github.com/w3c/ServiceWorker/issues/1201
+ if (mResumeStartPos > 0) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ // For progress we trust the content-length for the "maximum" size.
+ // We can't determine the full size from the stream itself since
+ // we may only receive the data incrementally. We can't trust
+ // Available() here.
+ // TODO: We could implement an nsIFixedLengthInputStream interface and
+ // QI to it here. This would let us determine the total length
+ // for streams that support it. See bug 1388774.
+ Unused << GetContentLength(&mSynthesizedStreamLength);
+
+ nsresult rv =
+ nsInputStreamPump::Create(getter_AddRefs(mPump), mBodyReader, 0, 0, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mPump->AsyncRead(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mPump->Suspend();
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::OpenRedirectChannel() {
+ INTERCEPTED_LOG(
+ ("InterceptedHttpChannel::OpenRedirectChannel [%p], mRedirectChannel: %p",
+ this, mRedirectChannel.get()));
+ nsresult rv = NS_OK;
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (!mRedirectChannel) {
+ return NS_ERROR_DOM_ABORT_ERR;
+ }
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // open new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ return rv;
+}
+
+void InterceptedHttpChannel::MaybeCallStatusAndProgress() {
+ // OnStatus() and OnProgress() must only be called on the main thread. If
+ // we are on a separate thread, then we maybe need to schedule a runnable
+ // to call them asynchronousnly.
+ if (!NS_IsMainThread()) {
+ // Check to see if we are already trying to call OnStatus/OnProgress
+ // asynchronously. If we are, then don't queue up another runnable.
+ // We don't want to flood the main thread.
+ if (mCallingStatusAndProgress) {
+ return;
+ }
+ mCallingStatusAndProgress = true;
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ "InterceptedHttpChannel::MaybeCallStatusAndProgress", this,
+ &InterceptedHttpChannel::MaybeCallStatusAndProgress);
+ MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
+
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We are about to capture out progress position. Clear the flag we use
+ // to de-duplicate progress report runnables. We want any further progress
+ // updates to trigger another runnable. We do this before capture the
+ // progress value since we're using atomics and not a mutex lock.
+ mCallingStatusAndProgress = false;
+
+ // Capture the current status from our atomic count.
+ int64_t progress = mProgress;
+
+ MOZ_DIAGNOSTIC_ASSERT(progress >= mProgressReported);
+
+ // Do nothing if we've already made the calls for this amount of progress
+ // or if the channel is not configured for these calls. Note, the check
+ // for mProgressSink here means we will not fire any spurious late calls
+ // after ReleaseListeners() is executed.
+ if (progress <= mProgressReported || mCanceled || !mProgressSink ||
+ (mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ return;
+ }
+
+ // Capture the host name on the first set of calls to avoid doing this
+ // string processing repeatedly.
+ if (mProgressReported == 0) {
+ nsAutoCString host;
+ MOZ_ALWAYS_SUCCEEDS(mURI->GetHost(host));
+ CopyUTF8toUTF16(host, mStatusHost);
+ }
+
+ mProgressSink->OnStatus(this, NS_NET_STATUS_READING, mStatusHost.get());
+
+ mProgressSink->OnProgress(this, progress, mSynthesizedStreamLength);
+
+ mProgressReported = progress;
+}
+
+void InterceptedHttpChannel::MaybeCallBodyCallback() {
+ nsCOMPtr<nsIInterceptedBodyCallback> callback = std::move(mBodyCallback);
+ if (callback) {
+ callback->BodyComplete(mStatus);
+ }
+}
+
+// static
+already_AddRefed<InterceptedHttpChannel>
+InterceptedHttpChannel::CreateForInterception(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp) {
+ // Create an InterceptedHttpChannel that will trigger a FetchEvent
+ // in a ServiceWorker when opened.
+ RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
+ aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
+
+ return ref.forget();
+}
+
+// static
+already_AddRefed<InterceptedHttpChannel>
+InterceptedHttpChannel::CreateForSynthesis(
+ const nsHttpResponseHead* aHead, nsIInputStream* aBody,
+ nsIInterceptedBodyCallback* aBodyCallback, PRTime aCreationTime,
+ const TimeStamp& aCreationTimestamp, const TimeStamp& aAsyncOpenTimestamp) {
+ MOZ_DIAGNOSTIC_ASSERT(aHead);
+ MOZ_DIAGNOSTIC_ASSERT(aBody);
+
+ // Create an InterceptedHttpChannel that already has a synthesized response.
+ // The synthetic response will be processed when opened. A FetchEvent
+ // will not be triggered.
+ RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
+ aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
+
+ ref->mResponseHead = MakeUnique<nsHttpResponseHead>(*aHead);
+ ref->mBodyReader = aBody;
+ ref->mBodyCallback = aBodyCallback;
+
+ return ref.forget();
+}
+
+NS_IMETHODIMP InterceptedHttpChannel::SetCanceledReason(
+ const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP InterceptedHttpChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Cancel(nsresult aStatus) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::Cancel [%p]", this));
+ // Note: This class has been designed to send all error results through
+ // Cancel(). Don't add calls directly to AsyncAbort() or
+ // DoNotifyListener(). Instead call Cancel().
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ // The interception is canceled, record the finish time stamp and the final
+ // status
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Canceled);
+
+ mCanceled = true;
+
+ if (mLastStatusReported && profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ // mLastStatusReported can be null if Cancel is called before we added the
+ // start marker.
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_CANCEL,
+ mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource));
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aStatus));
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ if (mPump) {
+ return mPump->Cancel(mStatus);
+ }
+
+ return AsyncAbort(mStatus);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Suspend(void) {
+ ++mSuspendCount;
+ if (mPump) {
+ return mPump->Suspend();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Resume(void) {
+ --mSuspendCount;
+ if (mPump) {
+ return mPump->Resume();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ nsCOMPtr<nsITransportSecurityInfo> ref(mSecurityInfo);
+ ref.forget(aSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpen [%p], listener: %p", this,
+ aListener));
+ nsCOMPtr<nsIStreamListener> listener(aListener);
+
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ return rv;
+ }
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // After this point we should try to return NS_OK and notify the listener
+ // of the result.
+ mListener = aListener;
+
+ AsyncOpenInternal();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ // Synthetic responses should not trigger CORS blocking.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetPriority(int32_t aPriority) {
+ mPriority = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(aClassFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ClearClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(~aClassFlags & mClassOfService.Flags());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AddClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(aClassFlags | mClassOfService.Flags());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetClassOfService(ClassOfService cos) {
+ mClassOfService = cos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetIncremental(bool incremental) {
+ mClassOfService.SetIncremental(incremental);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ResumeAt(uint64_t aStartPos,
+ const nsACString& aEntityId) {
+ // We don't support resuming synthesized responses, but we do track this
+ // information so it can be passed on to the resulting nsHttpChannel if
+ // ResetInterception is called.
+ mResumeStartPos = aStartPos;
+ mResumeEntityId = aEntityId;
+ return NS_OK;
+}
+
+void InterceptedHttpChannel::DoNotifyListenerCleanup() {
+ // Prefer to cleanup in ReleaseListeners() as it seems to be called
+ // more consistently in necko.
+}
+
+void InterceptedHttpChannel::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+namespace {
+
+class ResetInterceptionHeaderVisitor final : public nsIHttpHeaderVisitor {
+ nsCOMPtr<nsIHttpChannel> mTarget;
+
+ ~ResetInterceptionHeaderVisitor() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ // We skip Cookie header here, since it will be added during
+ // nsHttpChannel::AsyncOpen.
+ if (aHeader.Equals(nsHttp::Cookie.val())) {
+ return NS_OK;
+ }
+ if (aValue.IsEmpty()) {
+ return mTarget->SetEmptyRequestHeader(aHeader);
+ }
+ return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */);
+ }
+
+ public:
+ explicit ResetInterceptionHeaderVisitor(nsIHttpChannel* aTarget)
+ : mTarget(aTarget) {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor)
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ResetInterception(bool aBypass) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s",
+ this, aBypass ? "true" : "false"));
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ mInterceptionReset = true;
+
+ uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mURI, flags);
+
+ if (aBypass) {
+ redirectLoadInfo->ClearController();
+ // TODO: Audit whether we should also be calling
+ // ServiceWorkerManager::StopControllingClient for maximum correctness.
+ }
+
+ nsresult rv =
+ NS_NewChannelInternal(getter_AddRefs(newChannel), mURI, redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ mLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+
+ RefPtr<HttpBaseChannel> newBaseChannel = do_QueryObject(newChannel);
+ MOZ_ASSERT(newBaseChannel,
+ "The redirect channel should be a base channel.");
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ size, kCacheUnknown, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())), mURI, flags,
+ newBaseChannel->ChannelId());
+ }
+
+ rv = SetupReplacementChannel(mURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Restore the non-default headers for fallback channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel));
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new ResetInterceptionHeaderVisitor(httpChannel);
+ rv = VisitNonDefaultRequestHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITimedChannel> newTimedChannel = do_QueryInterface(newChannel);
+ if (newTimedChannel) {
+ if (!mAsyncOpenTime.IsNull()) {
+ newTimedChannel->SetAsyncOpen(mAsyncOpenTime);
+ }
+ if (!mChannelCreationTimestamp.IsNull()) {
+ newTimedChannel->SetChannelCreation(mChannelCreationTimestamp);
+ }
+ }
+
+ if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL) {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ rv = newChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ rv = newChannel->SetLoadFlags(loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mRedirectChannel = std::move(newChannel);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
+
+ if (NS_FAILED(rv)) {
+ OnRedirectVerifyCallback(rv);
+ } else {
+ // ResetInterception success, record the finish time stamps and the final
+ // status.
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Reset);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SynthesizeStatus(uint16_t aStatus,
+ const nsACString& aReason) {
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ nsAutoCString statusLine;
+ statusLine.AppendLiteral("HTTP/1.1 ");
+ statusLine.AppendInt(aStatus);
+ statusLine.AppendLiteral(" ");
+ statusLine.Append(aReason);
+
+ NS_ENSURE_SUCCESS(mSynthesizedResponseHead->ParseStatusLine(statusLine),
+ NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SynthesizeHeader(const nsACString& aName,
+ const nsACString& aValue) {
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ nsAutoCString header = aName + ": "_ns + aValue;
+ // Overwrite any existing header.
+ nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::StartSynthesizedResponse(
+ nsIInputStream* aBody, nsIInterceptedBodyCallback* aBodyCallback,
+ nsICacheInfoChannel* aSynthesizedCacheInfo, const nsACString& aFinalURLSpec,
+ bool aResponseRedirected) {
+ nsresult rv = NS_OK;
+
+ auto autoCleanup = MakeScopeExit([&] {
+ // Auto-cancel on failure. Do this first to get mStatus set, if necessary.
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ // If we early exit before taking ownership of the body, then automatically
+ // invoke the callback. This could be due to an error or because we're not
+ // going to consume it due to a redirect, etc.
+ if (aBodyCallback) {
+ aBodyCallback->BodyComplete(mStatus);
+ }
+ });
+
+ if (NS_FAILED(mStatus)) {
+ // Return NS_OK. The channel should fire callbacks with an error code
+ // if it was cancelled before this point.
+ return NS_OK;
+ }
+
+ // Take ownership of the body callbacks If a failure occurs we will
+ // automatically Cancel() the channel. This will then invoke OnStopRequest()
+ // which will invoke the correct callback. In the case of an opaque response
+ // redirect we pass ownership of the callback to the new channel.
+ mBodyCallback = aBodyCallback;
+ aBodyCallback = nullptr;
+
+ mSynthesizedCacheInfo = aSynthesizedCacheInfo;
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ mResponseHead = std::move(mSynthesizedResponseHead);
+
+ if (ShouldRedirect()) {
+ rv = FollowSyntheticRedirect();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ // Intercepted responses should already be decoded.
+ SetApplyConversion(false);
+
+ // Errors and redirects may not have a body. Synthesize an empty string
+ // stream here so later code can be simpler.
+ mBodyReader = aBody;
+ if (!mBodyReader) {
+ rv = NS_NewCStringInputStream(getter_AddRefs(mBodyReader), ""_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = mURI;
+ }
+
+ bool equal = false;
+ Unused << mURI->Equals(responseURI, &equal);
+ if (!equal) {
+ rv = RedirectForResponseURL(responseURI, aResponseRedirected);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ rv = StartPump();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::FinishSynthesizedResponse() {
+ if (mCanceled) {
+ // Return NS_OK. The channel should fire callbacks with an error code
+ // if it was cancelled before this point.
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CancelInterception(nsresult aStatus) {
+ return Cancel(aStatus);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetChannel(nsIChannel** aChannel) {
+ nsCOMPtr<nsIChannel> ref(this);
+ ref.forget(aChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetSecureUpgradedChannelURI(
+ nsIURI** aSecureUpgradedChannelURI) {
+ nsCOMPtr<nsIURI> ref(mURI);
+ ref.forget(aSecureUpgradedChannelURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetChannelInfo(
+ mozilla::dom::ChannelInfo* aChannelInfo) {
+ return aChannelInfo->ResurrectInfoOnChannel(this);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetInternalContentPolicyType(
+ nsContentPolicyType* aPolicyType) {
+ if (mLoadInfo) {
+ *aPolicyType = mLoadInfo->InternalContentPolicyType();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetConsoleReportCollector(
+ nsIConsoleReportCollector** aConsoleReportCollector) {
+ nsCOMPtr<nsIConsoleReportCollector> ref(this);
+ ref.forget(aConsoleReportCollector);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFetchHandlerStart(TimeStamp aTimeStamp) {
+ mTimeStamps.RecordTime(std::move(aTimeStamp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFetchHandlerFinish(TimeStamp aTimeStamp) {
+ mTimeStamps.RecordTime(std::move(aTimeStamp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetRemoteWorkerLaunchStart(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchStart = aTimeStamp > mTimeStamps.mInterceptionStart
+ ? aTimeStamp
+ : mTimeStamps.mInterceptionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetRemoteWorkerLaunchEnd(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchEnd = aTimeStamp > mTimeStamps.mInterceptionStart
+ ? aTimeStamp
+ : mTimeStamps.mInterceptionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetLaunchServiceWorkerStart(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mServiceWorkerLaunchStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetLaunchServiceWorkerEnd(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mServiceWorkerLaunchEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetDispatchFetchEventStart(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mInterceptionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetDispatchFetchEventEnd(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mFetchHandlerStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetHandleFetchEventStart(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mFetchHandlerStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetHandleFetchEventEnd(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mFetchHandlerFinish;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetIsReset(bool* aResult) {
+ *aResult = mInterceptionReset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetReleaseHandle(nsISupports* aHandle) {
+ mReleaseHandle = aHandle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnRedirectVerifyCallback(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OpenRedirectChannel();
+ }
+
+ nsCOMPtr<nsIRedirectResultListener> hook;
+ GetCallback(hook);
+ if (hook) {
+ hook->OnRedirectResult(rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ MaybeCallBodyCallback();
+
+ StoreIsPending(false);
+ // We can only release listeners after the redirected channel really owns
+ // mListener. Otherwise, the OnStart/OnStopRequest functions of mListener will
+ // not be called.
+ if (NS_SUCCEEDED(rv)) {
+ ReleaseListeners();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::OnStartRequest [%p]", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+
+ MOZ_ASSERT_IF(!mLoadInfo->GetServiceWorkerTaintingSynthesized(),
+ mLoadInfo->GetLoadingPrincipal());
+ // No need to do ORB checks if these conditions hold.
+ MOZ_DIAGNOSTIC_ASSERT(mLoadInfo->GetServiceWorkerTaintingSynthesized() ||
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal());
+
+ if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
+ }
+
+ nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ Cancel(mStatus);
+ }
+
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_DOM_CORP_FAILED;
+ Cancel(mStatus);
+ }
+
+ rv = ComputeCrossOriginOpenerPolicyMismatch();
+ if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ Cancel(mStatus);
+ }
+
+ rv = ValidateMIMEType();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ Cancel(mStatus);
+ }
+
+ StoreOnStartRequestCalled(true);
+ if (mListener) {
+ return mListener->OnStartRequest(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::OnStopRequest [%p]", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ MaybeCallBodyCallback();
+
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Synthesized);
+
+ // Its possible that we have any async runnable queued to report some
+ // progress when OnStopRequest() is triggered. Report any left over
+ // progress immediately. The extra runnable will then do nothing thanks
+ // to the ReleaseListeners() call below.
+ MaybeCallStatusAndProgress();
+
+ StoreIsPending(false);
+
+ // Register entry to the PerformanceStorage resource timing
+ MaybeReportTimingData();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ nsresult rv = NS_OK;
+ if (mListener) {
+ rv = mListener->OnStopRequest(this, mStatus);
+ }
+
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // Any thread if the channel has been retargeted.
+
+ if (mCanceled || !mListener) {
+ // If there is no listener, we still need to drain the stream in order
+ // maintain necko invariants.
+ uint32_t unused = 0;
+ aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &unused);
+ return mStatus;
+ }
+ if (mProgressSink) {
+ if (!(mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ mProgress = aOffset + aCount;
+ MaybeCallStatusAndProgress();
+ }
+ }
+
+ return mListener->OnDataAvailable(this, aInputStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnDataFinished(nsresult aStatus) {
+ if (mCanceled || !mListener) {
+ return aStatus;
+ }
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener);
+ if (retargetableListener) {
+ return retargetableListener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aNewTarget);
+
+ // If retargeting to the main thread, do nothing.
+ if (aNewTarget->IsOnCurrentThread()) {
+ return NS_OK;
+ }
+
+ // Retargeting is only valid during OnStartRequest for nsIChannels. So
+ // we should only be called if we have a pump.
+ if (!mPump) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mPump->RetargetDeliveryTo(aNewTarget);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ if (!mPump) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mPump->GetDeliveryTarget(aEventTarget);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// InterceptedHttpChannel::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+// InterceptedHttpChannel does not really implement the nsICacheInfoChannel
+// interface, we tranfers parameters to the saved
+// nsICacheInfoChannel(mSynthesizedCacheInfo) from StartSynthesizedResponse. And
+// we return false in IsFromCache and NS_ERROR_NOT_AVAILABLE for all other
+// methods while the saved mSynthesizedCacheInfo does not exist.
+NS_IMETHODIMP
+InterceptedHttpChannel::IsFromCache(bool* value) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->IsFromCache(value);
+ }
+ *value = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::IsRacing(bool* value) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->IsRacing(value);
+ }
+ *value = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheEntryId(aCacheEntryId);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheTokenFetchCount(_retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheTokenExpirationTime(_retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetAllowStaleCacheContent(
+ bool aAllowStaleCacheContent) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetAllowStaleCacheContent(
+ aAllowStaleCacheContent);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAllowStaleCacheContent(
+ bool* aAllowStaleCacheContent) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAllowStaleCacheContent(
+ aAllowStaleCacheContent);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetForceValidateCacheContent(
+ bool aForceValidateCacheContent) {
+ // We store aForceValidateCacheContent locally because
+ // mSynthesizedCacheInfo isn't present until a response
+ // is actually synthesized, which is too late for the value
+ // to be forwarded during the redirect to the intercepted
+ // channel.
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetForceValidateCacheContent(
+ aForceValidateCacheContent);
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+InterceptedHttpChannel::GetForceValidateCacheContent(
+ bool* aForceValidateCacheContent) {
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+#ifdef DEBUG
+ if (mSynthesizedCacheInfo) {
+ bool synthesizedForceValidateCacheContent;
+ mSynthesizedCacheInfo->GetForceValidateCacheContent(
+ &synthesizedForceValidateCacheContent);
+ MOZ_ASSERT(*aForceValidateCacheContent ==
+ synthesizedForceValidateCacheContent);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetPreferCacheLoadOverBypass(
+ bool* aPreferCacheLoadOverBypass) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetPreferCacheLoadOverBypass(
+ aPreferCacheLoadOverBypass);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetPreferCacheLoadOverBypass(
+ bool aPreferCacheLoadOverBypass) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetPreferCacheLoadOverBypass(
+ aPreferCacheLoadOverBypass);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+InterceptedHttpChannel::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAlternativeDataType(nsACString& aType) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAlternativeDataType(aType);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->OpenAlternativeOutputStream(
+ type, predictedSize, _retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetOriginalInputStream(
+ nsIInputStreamReceiver* aReceiver) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetOriginalInputStream(aReceiver);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAlternativeDataInputStream(
+ nsIInputStream** aInputStream) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAlternativeDataInputStream(aInputStream);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheKey(uint32_t* key) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheKey(key);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetCacheKey(uint32_t key) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetCacheKey(key);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// InterceptionTimeStamps implementation
+InterceptedHttpChannel::InterceptionTimeStamps::InterceptionTimeStamps()
+ : mStage(InterceptedHttpChannel::InterceptionTimeStamps::InterceptionStart),
+ mStatus(InterceptedHttpChannel::InterceptionTimeStamps::Created) {}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::Init(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(mStatus == Created);
+
+ mStatus = Initialized;
+
+ mIsNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(aChannel);
+ mKey = mIsNonSubresourceRequest ? "navigation"_ns : "subresource"_ns;
+ nsCOMPtr<nsIInterceptedChannel> interceptedChannel =
+ do_QueryInterface(aChannel);
+ // It must be a InterceptedHttpChannel
+ MOZ_ASSERT(interceptedChannel);
+ if (!mIsNonSubresourceRequest) {
+ interceptedChannel->GetSubresourceTimeStampKey(aChannel, mSubresourceKey);
+ }
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
+ InterceptedHttpChannel::InterceptionTimeStamps::Status&& aStatus,
+ TimeStamp&& aTimeStamp) {
+ // Only allow passing Synthesized, Reset, Redirected, and Canceled in this
+ // method.
+ MOZ_ASSERT(aStatus == Synthesized || aStatus == Reset ||
+ aStatus == Canceled || aStatus == Redirected);
+ if (mStatus == Canceled) {
+ return;
+ }
+
+ // If current status is not Initialized, only Canceled can be recorded.
+ // That means it is canceled after other operation is done, ex. synthesized.
+ MOZ_ASSERT(mStatus == Initialized || aStatus == Canceled);
+
+ switch (mStatus) {
+ case Initialized:
+ mStatus = aStatus;
+ break;
+ case Synthesized:
+ mStatus = CanceledAfterSynthesized;
+ break;
+ case Reset:
+ mStatus = CanceledAfterReset;
+ break;
+ case Redirected:
+ mStatus = CanceledAfterRedirected;
+ break;
+ // Channel is cancelled before calling AsyncOpenInternal(), no need to
+ // record the cancel time stamp.
+ case Created:
+ return;
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+
+ RecordTimeInternal(std::move(aTimeStamp));
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
+ TimeStamp&& aTimeStamp) {
+ MOZ_ASSERT(mStatus == Initialized || mStatus == Canceled);
+ if (mStatus == Canceled) {
+ return;
+ }
+ RecordTimeInternal(std::move(aTimeStamp));
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTimeInternal(
+ TimeStamp&& aTimeStamp) {
+ MOZ_ASSERT(mStatus != Created);
+
+ if (mStatus == Canceled && mStage != InterceptionFinish) {
+ mFetchHandlerStart = aTimeStamp;
+ mFetchHandlerFinish = aTimeStamp;
+ mStage = InterceptionFinish;
+ }
+
+ switch (mStage) {
+ case InterceptionStart: {
+ MOZ_ASSERT(mInterceptionStart.IsNull());
+ mInterceptionStart = aTimeStamp;
+ mStage = FetchHandlerStart;
+ break;
+ }
+ case (FetchHandlerStart): {
+ MOZ_ASSERT(mFetchHandlerStart.IsNull());
+ mFetchHandlerStart = aTimeStamp;
+ mStage = FetchHandlerFinish;
+ break;
+ }
+ case (FetchHandlerFinish): {
+ MOZ_ASSERT(mFetchHandlerFinish.IsNull());
+ mFetchHandlerFinish = aTimeStamp;
+ mStage = InterceptionFinish;
+ break;
+ }
+ case InterceptionFinish: {
+ mInterceptionFinish = aTimeStamp;
+ SaveTimeStamps();
+ return;
+ }
+ default: {
+ return;
+ }
+ }
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::GenKeysWithStatus(
+ nsCString& aKey, nsCString& aSubresourceKey) {
+ nsAutoCString statusString;
+ switch (mStatus) {
+ case Synthesized:
+ statusString = "synthesized"_ns;
+ break;
+ case Reset:
+ statusString = "reset"_ns;
+ break;
+ case Redirected:
+ statusString = "redirected"_ns;
+ break;
+ case Canceled:
+ statusString = "canceled"_ns;
+ break;
+ case CanceledAfterSynthesized:
+ statusString = "canceled-after-synthesized"_ns;
+ break;
+ case CanceledAfterReset:
+ statusString = "canceled-after-reset"_ns;
+ break;
+ case CanceledAfterRedirected:
+ statusString = "canceled-after-redirected"_ns;
+ break;
+ default:
+ return;
+ }
+ aKey = mKey;
+ aSubresourceKey = mSubresourceKey;
+ aKey.AppendLiteral("_");
+ aSubresourceKey.AppendLiteral("_");
+ aKey.Append(statusString);
+ aSubresourceKey.Append(statusString);
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::SaveTimeStamps() {
+ MOZ_ASSERT(mStatus != Initialized && mStatus != Created);
+
+ if (mStatus == Synthesized || mStatus == Reset) {
+ Telemetry::HistogramID id =
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_FINISH_SYNTHESIZED_RESPONSE_MS_2;
+ if (mStatus == Reset) {
+ id = Telemetry::SERVICE_WORKER_FETCH_EVENT_CHANNEL_RESET_MS_2;
+ }
+
+ Telemetry::Accumulate(
+ id, mKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mFetchHandlerFinish).ToMilliseconds()));
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ id, mSubresourceKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mFetchHandlerFinish).ToMilliseconds()));
+ }
+ }
+
+ if (!mFetchHandlerStart.IsNull()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS_2, mKey,
+ static_cast<uint32_t>(
+ (mFetchHandlerStart - mInterceptionStart).ToMilliseconds()));
+
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS_2, mSubresourceKey,
+ static_cast<uint32_t>(
+ (mFetchHandlerStart - mInterceptionStart).ToMilliseconds()));
+ }
+ }
+
+ nsAutoCString key, subresourceKey;
+ GenKeysWithStatus(key, subresourceKey);
+
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS_2, key,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mInterceptionStart).ToMilliseconds()));
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS_2,
+ subresourceKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mInterceptionStart).ToMilliseconds()));
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.h b/netwerk/protocol/http/InterceptedHttpChannel.h
new file mode 100644
index 0000000000..b534999513
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedHttpChannel.h
@@ -0,0 +1,316 @@
+/* -*- 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_InterceptedHttpChannel_h
+#define mozilla_net_InterceptedHttpChannel_h
+
+#include "HttpBaseChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIInputStream.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+namespace mozilla::net {
+
+// This class represents an http channel that is being intercepted by a
+// ServiceWorker. This means that when the channel is opened a FetchEvent
+// will be fired on the ServiceWorker thread. The channel will complete
+// depending on what the worker does. The options are:
+//
+// 1. If the ServiceWorker does not handle the FetchEvent or does not call
+// FetchEvent.respondWith(), then the channel needs to fall back to a
+// normal request. When this happens ResetInterception() is called and
+// the channel will perform an internal redirect back to an nsHttpChannel.
+//
+// 2. If the ServiceWorker provides a Response to FetchEvent.respondWith()
+// then the status, headers, and body must be synthesized. When
+// FinishSynthesizedResponse() is called the synthesized data must be
+// reported back to the channel listener. This is handled in a few
+// different ways:
+// a. If a redirect was synthesized, then we perform the redirect to
+// a new nsHttpChannel. This new channel might trigger yet another
+// interception.
+// b. If a same-origin or CORS Response was synthesized, then we simply
+// crate an nsInputStreamPump to process it and call back to the
+// listener.
+// c. If an opaque Response was synthesized, then we perform an internal
+// redirect to a new InterceptedHttpChannel using the cross-origin URL.
+// When this new channel is opened, it then creates a pump as in case
+// (b). The extra redirect here is to make sure the various listeners
+// treat the result as unsafe cross-origin data.
+//
+// 3. If an error occurs, such as the ServiceWorker passing garbage to
+// FetchEvent.respondWith(), then CancelInterception() is called. This is
+// handled the same as a normal nsIChannel::Cancel() call. We abort the
+// channel and end up calling OnStopRequest() with an error code.
+class InterceptedHttpChannel final
+ : public HttpBaseChannel,
+ public HttpAsyncAborter<InterceptedHttpChannel>,
+ public nsIInterceptedChannel,
+ public nsICacheInfoChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIThreadRetargetableRequest,
+ public nsIThreadRetargetableStreamListener {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINTERCEPTEDCHANNEL
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ private:
+ friend class HttpAsyncAborter<InterceptedHttpChannel>;
+
+ UniquePtr<nsHttpResponseHead> mSynthesizedResponseHead;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIInputStream> mBodyReader;
+ nsCOMPtr<nsISupports> mReleaseHandle;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIInterceptedBodyCallback> mBodyCallback;
+ nsCOMPtr<nsICacheInfoChannel> mSynthesizedCacheInfo;
+ RefPtr<nsInputStreamPump> mPump;
+ TimeStamp mInterceptedChannelCreationTimestamp;
+
+ // For the profiler markers
+ TimeStamp mLastStatusReported;
+
+ Atomic<int64_t> mProgress;
+ int64_t mProgressReported;
+ int64_t mSynthesizedStreamLength;
+ uint64_t mResumeStartPos;
+ nsCString mResumeEntityId;
+ nsString mStatusHost;
+ Atomic<bool> mCallingStatusAndProgress;
+ bool mInterceptionReset{false};
+
+ /**
+ * InterceptionTimeStamps is used to record the time stamps of the
+ * interception.
+ * The general usage:
+ * Step 1. Initialize the InterceptionTimeStamps;
+ * InterceptionTimeStamps::Init(channel);
+ * Step 2. Record time for each stage
+ * InterceptionTimeStamps::RecordTime(); or
+ * InterceptionTimeStamps::RecordTime(timeStamp);
+ * Step 3. Record time for the last stage with the final status
+ * InterceptionTimeStamps::RecordTime(InterceptionTimeStamps::Synthesized);
+ */
+ class InterceptionTimeStamps final {
+ public:
+ // The possible status of the interception.
+ enum Status {
+ Created,
+ Initialized,
+ Synthesized,
+ Reset,
+ Redirected,
+ Canceled,
+ CanceledAfterSynthesized,
+ CanceledAfterReset,
+ CanceledAfterRedirected
+ };
+
+ InterceptionTimeStamps();
+ ~InterceptionTimeStamps() = default;
+
+ /**
+ * Initialize with the given channel.
+ * This method should be called before any RecordTime().
+ */
+ void Init(nsIChannel* aChannel);
+
+ /**
+ * Record the given time stamp for current stage. If there is no given time
+ * stamp, TimeStamp::Now() will be recorded.
+ * The current stage is auto moved to the next one.
+ */
+ void RecordTime(TimeStamp&& aTimeStamp = TimeStamp::Now());
+
+ /**
+ * Record the given time stamp for the last stage(InterceptionFinish) and
+ * set the final status to the given status.
+ * If these is no given time stamp, TimeStamp::Now() will be recorded.
+ * Notice that this method is for the last stage, it calls SaveTimeStamps()
+ * to write data into telemetries.
+ */
+ void RecordTime(Status&& aStatus,
+ TimeStamp&& aTimeStamp = TimeStamp::Now());
+
+ // The time stamp which the intercepted channel is created and async opend.
+ TimeStamp mInterceptionStart;
+
+ // The time stamp which the interception finishes.
+ TimeStamp mInterceptionFinish;
+
+ // The time stamp which the fetch event starts to be handled by fetch event
+ // handler.
+ TimeStamp mFetchHandlerStart;
+
+ // The time stamp which the fetch event handling finishes. It would the time
+ // which remote worker sends result back.
+ TimeStamp mFetchHandlerFinish;
+
+ private:
+ // The stage of interception.
+ enum Stage {
+ InterceptionStart,
+ FetchHandlerStart,
+ FetchHandlerFinish,
+ InterceptionFinish
+ } mStage;
+
+ // The final status of the interception.
+ Status mStatus;
+
+ bool mIsNonSubresourceRequest;
+ // The keys used for telemetries.
+ nsCString mKey;
+ nsCString mSubresourceKey;
+
+ void RecordTimeInternal(TimeStamp&& aTimeStamp);
+
+ // Generate the record keys with final status.
+ void GenKeysWithStatus(nsCString& aKey, nsCString& aSubresourceKey);
+
+ // Save the time stamps into telemetries.
+ void SaveTimeStamps();
+ };
+
+ InterceptionTimeStamps mTimeStamps;
+
+ InterceptedHttpChannel(PRTime aCreationTime,
+ const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp);
+ ~InterceptedHttpChannel() = default;
+
+ virtual void ReleaseListeners() override;
+
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI* aURI, nsIChannel* aChannel, bool aPreserveMethod,
+ uint32_t aRedirectFlags) override;
+
+ void AsyncOpenInternal();
+
+ bool ShouldRedirect() const;
+
+ nsresult FollowSyntheticRedirect();
+
+ // If the response's URL is different from the request's then do a service
+ // worker redirect. If Response.redirected is false we do an internal
+ // redirect. Otherwise, if Response.redirect is true do a non-internal
+ // redirect so end consumers detect the redirected state.
+ nsresult RedirectForResponseURL(nsIURI* aResponseURI,
+ bool aResponseRedirected);
+
+ nsresult StartPump();
+
+ nsresult OpenRedirectChannel();
+
+ void MaybeCallStatusAndProgress();
+
+ void MaybeCallBodyCallback();
+
+ TimeStamp mServiceWorkerLaunchStart;
+ TimeStamp mServiceWorkerLaunchEnd;
+
+ public:
+ static already_AddRefed<InterceptedHttpChannel> CreateForInterception(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp);
+
+ static already_AddRefed<InterceptedHttpChannel> CreateForSynthesis(
+ const nsHttpResponseHead* aHead, nsIInputStream* aBody,
+ nsIInterceptedBodyCallback* aBodyCallback, PRTime aCreationTime,
+ const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp);
+
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+
+ NS_IMETHOD
+ Cancel(nsresult aStatus) override;
+
+ NS_IMETHOD
+ Suspend(void) override;
+
+ NS_IMETHOD
+ Resume(void) override;
+
+ NS_IMETHOD
+ GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ NS_IMETHOD
+ AsyncOpen(nsIStreamListener* aListener) override;
+
+ NS_IMETHOD
+ LogBlockedCORSRequest(const nsAString& aMessage, const nsACString& aCategory,
+ bool aIsWarning) override;
+
+ NS_IMETHOD
+ LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ NS_IMETHOD
+ GetIsAuthChannel(bool* aIsAuthChannel) override;
+
+ NS_IMETHOD
+ SetPriority(int32_t aPriority) override;
+
+ NS_IMETHOD
+ SetClassFlags(uint32_t aClassFlags) override;
+
+ NS_IMETHOD
+ ClearClassFlags(uint32_t flags) override;
+
+ NS_IMETHOD
+ AddClassFlags(uint32_t flags) override;
+
+ NS_IMETHOD
+ SetClassOfService(ClassOfService cos) override;
+
+ NS_IMETHOD
+ SetIncremental(bool incremental) override;
+
+ NS_IMETHOD
+ ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ NS_IMETHOD
+ SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override {
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override {
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) override;
+ NS_IMETHOD GetLaunchServiceWorkerStart(TimeStamp* aRetVal) override;
+
+ NS_IMETHOD SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) override;
+ NS_IMETHOD GetLaunchServiceWorkerEnd(TimeStamp* aRetVal) override;
+
+ NS_IMETHOD GetDispatchFetchEventStart(TimeStamp* aRetVal) override;
+ NS_IMETHOD GetDispatchFetchEventEnd(TimeStamp* aRetVal) override;
+
+ NS_IMETHOD GetHandleFetchEventStart(TimeStamp* aRetVal) override;
+ NS_IMETHOD GetHandleFetchEventEnd(TimeStamp* aRetVal) override;
+
+ void DoNotifyListenerCleanup() override;
+
+ void DoAsyncAbort(nsresult aStatus) override;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_InterceptedHttpChannel_h
diff --git a/netwerk/protocol/http/MockHttpAuth.cpp b/netwerk/protocol/http/MockHttpAuth.cpp
new file mode 100644
index 0000000000..588b30fadc
--- /dev/null
+++ b/netwerk/protocol/http/MockHttpAuth.cpp
@@ -0,0 +1,46 @@
+/* vim:set ts=4 sw=2 sts=2 et ci: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "MockHttpAuth.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(MockHttpAuth, nsIHttpAuthenticator)
+
+NS_IMETHODIMP MockHttpAuth::ChallengeReceived(
+ nsIHttpAuthenticableChannel* aChannel, const nsACString& aChallenge,
+ bool aProxyAuth, nsISupports** aSessionState,
+ nsISupports** aContinuationState, bool* aInvalidatesIdentity) {
+ *aInvalidatesIdentity = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MockHttpAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* aChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& aChallenge,
+ bool aProxyAuth, const nsAString& aDomain, const nsAString& aUser,
+ const nsAString& aPassword, nsISupports* aSessionState,
+ nsISupports* aContinuationState, nsICancelable** aCancel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP MockHttpAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* aChannel, const nsACString& aChallenge,
+ bool aProxyAuth, const nsAString& aDomain, const nsAString& aUser,
+ const nsAString& aPassword, nsISupports** aSessionState,
+ nsISupports** aContinuationState, uint32_t* aFlags, nsACString& _retval) {
+ _retval.Assign("moz_test_credentials");
+ return NS_OK;
+}
+
+NS_IMETHODIMP MockHttpAuth::GetAuthFlags(uint32_t* aAuthFlags) {
+ *aAuthFlags |= nsIHttpAuthenticator::CONNECTION_BASED;
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/MockHttpAuth.h b/netwerk/protocol/http/MockHttpAuth.h
new file mode 100644
index 0000000000..a1f737c955
--- /dev/null
+++ b/netwerk/protocol/http/MockHttpAuth.h
@@ -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/. */
+
+#ifndef MockHttpAuth_h__
+#define MockHttpAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+
+namespace mozilla::net {
+
+// This authenticator is only used for testing purposes.
+class MockHttpAuth : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ MockHttpAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> Create() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator = new MockHttpAuth();
+ return authenticator.forget();
+ }
+
+ private:
+ virtual ~MockHttpAuth() = default;
+};
+
+} // namespace mozilla::net
+
+#endif // MockHttpAuth_h__
diff --git a/netwerk/protocol/http/NetworkMarker.cpp b/netwerk/protocol/http/NetworkMarker.cpp
new file mode 100644
index 0000000000..ece1845fef
--- /dev/null
+++ b/netwerk/protocol/http/NetworkMarker.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=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 "NetworkMarker.h"
+
+#include "HttpBaseChannel.h"
+#include "nsIChannelEventSink.h"
+
+namespace mozilla {
+namespace net {
+
+static constexpr net::TimingStruct scEmptyNetTimingStruct;
+
+void profiler_add_network_marker(
+ nsIURI* aURI, const nsACString& aRequestMethod, int32_t aPriority,
+ uint64_t aChannelId, NetworkLoadType aType, mozilla::TimeStamp aStart,
+ mozilla::TimeStamp aEnd, int64_t aCount,
+ mozilla::net::CacheDisposition aCacheDisposition, uint64_t aInnerWindowID,
+ bool aIsPrivateBrowsing, const mozilla::net::TimingStruct* aTimings,
+ UniquePtr<ProfileChunkedBuffer> aSource,
+ const Maybe<nsDependentCString>& aContentType, nsIURI* aRedirectURI,
+ uint32_t aRedirectFlags, uint64_t aRedirectChannelId) {
+ if (!profiler_thread_is_being_profiled_for_markers()) {
+ return;
+ }
+
+ nsAutoCStringN<2048> name;
+ name.AppendASCII("Load ");
+ // top 32 bits are process id of the load
+ name.AppendInt(aChannelId & 0xFFFFFFFFu);
+
+ // These can do allocations/frees/etc; avoid if not active
+ nsAutoCStringN<2048> spec;
+ if (aURI) {
+ aURI->GetAsciiSpec(spec);
+ name.AppendASCII(": ");
+ name.Append(spec);
+ }
+
+ nsAutoCString redirect_spec;
+ if (aRedirectURI) {
+ aRedirectURI->GetAsciiSpec(redirect_spec);
+ }
+
+ struct NetworkMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("Network");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter, mozilla::TimeStamp aStart,
+ mozilla::TimeStamp aEnd, int64_t aID, const ProfilerString8View& aURI,
+ const ProfilerString8View& aRequestMethod, NetworkLoadType aType,
+ int32_t aPri, int64_t aCount, net::CacheDisposition aCacheDisposition,
+ bool aIsPrivateBrowsing, const net::TimingStruct& aTimings,
+ const ProfilerString8View& aRedirectURI,
+ const ProfilerString8View& aContentType, uint32_t aRedirectFlags,
+ int64_t aRedirectChannelId) {
+ // This payload still streams a startTime and endTime property because it
+ // made the migration to MarkerTiming on the front-end easier.
+ aWriter.TimeProperty("startTime", aStart);
+ aWriter.TimeProperty("endTime", aEnd);
+
+ aWriter.IntProperty("id", aID);
+ aWriter.StringProperty("status", GetNetworkState(aType));
+ if (Span<const char> cacheString = GetCacheState(aCacheDisposition);
+ !cacheString.IsEmpty()) {
+ aWriter.StringProperty("cache", cacheString);
+ }
+ aWriter.IntProperty("pri", aPri);
+ if (aCount > 0) {
+ aWriter.IntProperty("count", aCount);
+ }
+ if (aURI.Length() != 0) {
+ aWriter.StringProperty("URI", aURI);
+ }
+ if (aRedirectURI.Length() != 0) {
+ aWriter.StringProperty("RedirectURI", aRedirectURI);
+ aWriter.StringProperty("redirectType", getRedirectType(aRedirectFlags));
+ aWriter.BoolProperty(
+ "isHttpToHttpsRedirect",
+ aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE);
+
+ if (aRedirectChannelId != 0) {
+ aWriter.IntProperty("redirectId", aRedirectChannelId);
+ }
+ }
+
+ aWriter.StringProperty("requestMethod", aRequestMethod);
+
+ if (aContentType.Length() != 0) {
+ aWriter.StringProperty("contentType", aContentType);
+ } else {
+ aWriter.NullProperty("contentType");
+ }
+
+ if (aIsPrivateBrowsing) {
+ aWriter.BoolProperty("isPrivateBrowsing", aIsPrivateBrowsing);
+ }
+
+ if (aType != NetworkLoadType::LOAD_START) {
+ aWriter.TimeProperty("domainLookupStart", aTimings.domainLookupStart);
+ aWriter.TimeProperty("domainLookupEnd", aTimings.domainLookupEnd);
+ aWriter.TimeProperty("connectStart", aTimings.connectStart);
+ aWriter.TimeProperty("tcpConnectEnd", aTimings.tcpConnectEnd);
+ aWriter.TimeProperty("secureConnectionStart",
+ aTimings.secureConnectionStart);
+ aWriter.TimeProperty("connectEnd", aTimings.connectEnd);
+ aWriter.TimeProperty("requestStart", aTimings.requestStart);
+ aWriter.TimeProperty("responseStart", aTimings.responseStart);
+ aWriter.TimeProperty("responseEnd", aTimings.responseEnd);
+ }
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ return MarkerSchema::SpecialFrontendLocation{};
+ }
+
+ private:
+ static Span<const char> GetNetworkState(NetworkLoadType aType) {
+ switch (aType) {
+ case NetworkLoadType::LOAD_START:
+ return MakeStringSpan("STATUS_START");
+ case NetworkLoadType::LOAD_STOP:
+ return MakeStringSpan("STATUS_STOP");
+ case NetworkLoadType::LOAD_REDIRECT:
+ return MakeStringSpan("STATUS_REDIRECT");
+ case NetworkLoadType::LOAD_CANCEL:
+ return MakeStringSpan("STATUS_CANCEL");
+ default:
+ MOZ_ASSERT(false, "Unexpected NetworkLoadType enum value.");
+ return MakeStringSpan("");
+ }
+ }
+
+ static Span<const char> GetCacheState(
+ net::CacheDisposition aCacheDisposition) {
+ switch (aCacheDisposition) {
+ case net::kCacheUnresolved:
+ return MakeStringSpan("Unresolved");
+ case net::kCacheHit:
+ return MakeStringSpan("Hit");
+ case net::kCacheHitViaReval:
+ return MakeStringSpan("HitViaReval");
+ case net::kCacheMissedViaReval:
+ return MakeStringSpan("MissedViaReval");
+ case net::kCacheMissed:
+ return MakeStringSpan("Missed");
+ case net::kCacheUnknown:
+ return MakeStringSpan("");
+ default:
+ MOZ_ASSERT(false, "Unexpected CacheDisposition enum value.");
+ return MakeStringSpan("");
+ }
+ }
+
+ static Span<const char> getRedirectType(uint32_t aRedirectFlags) {
+ MOZ_ASSERT(aRedirectFlags != 0, "aRedirectFlags should be non-zero");
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
+ return MakeStringSpan("Temporary");
+ }
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) {
+ return MakeStringSpan("Permanent");
+ }
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ return MakeStringSpan("Internal");
+ }
+ MOZ_ASSERT(false, "Couldn't find a redirect type from aRedirectFlags");
+ return MakeStringSpan("");
+ }
+ };
+
+ profiler_add_marker(
+ name, geckoprofiler::category::NETWORK,
+ {MarkerTiming::Interval(aStart, aEnd),
+ MarkerStack::TakeBacktrace(std::move(aSource)),
+ MarkerInnerWindowId(aInnerWindowID)},
+ NetworkMarker{}, aStart, aEnd, static_cast<int64_t>(aChannelId), spec,
+ aRequestMethod, aType, aPriority, aCount, aCacheDisposition,
+ aIsPrivateBrowsing, aTimings ? *aTimings : scEmptyNetTimingStruct,
+ redirect_spec,
+ aContentType ? ProfilerString8View(*aContentType) : ProfilerString8View(),
+ aRedirectFlags, aRedirectChannelId);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/NetworkMarker.h b/netwerk/protocol/http/NetworkMarker.h
new file mode 100644
index 0000000000..cab1de5f07
--- /dev/null
+++ b/netwerk/protocol/http/NetworkMarker.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et cin ts=4 sw=2 sts=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 NetworkMarker_h__
+#define NetworkMarker_h__
+
+#include "mozilla/ProfilerMarkers.h"
+
+namespace mozilla {
+namespace net {
+
+struct TimingStruct;
+enum CacheDisposition : uint8_t;
+
+enum class NetworkLoadType {
+ LOAD_START,
+ LOAD_STOP,
+ LOAD_REDIRECT,
+ LOAD_CANCEL
+};
+
+void profiler_add_network_marker(
+ nsIURI* aURI, const nsACString& aRequestMethod, int32_t aPriority,
+ uint64_t aChannelId, NetworkLoadType aType, mozilla::TimeStamp aStart,
+ mozilla::TimeStamp aEnd, int64_t aCount,
+ mozilla::net::CacheDisposition aCacheDisposition, uint64_t aInnerWindowID,
+ bool aIsPrivateBrowsing,
+ const mozilla::net::TimingStruct* aTimings = nullptr,
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource = nullptr,
+ const mozilla::Maybe<nsDependentCString>& aContentType = mozilla::Nothing(),
+ nsIURI* aRedirectURI = nullptr, uint32_t aRedirectFlags = 0,
+ uint64_t aRedirectChannelId = 0);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkMarker_h__
diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp
new file mode 100644
index 0000000000..eae11441af
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -0,0 +1,865 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NullHttpChannel.h"
+#include "nsContentUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NullHttpChannel, nsINullChannel, nsIHttpChannel,
+ nsIIdentChannel, nsITimedChannel)
+
+NullHttpChannel::NullHttpChannel() {
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+}
+
+NullHttpChannel::NullHttpChannel(nsIHttpChannel* chan)
+ : mAllRedirectsSameOrigin(false), mAllRedirectsPassTimingAllowCheck(false) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ ssm->GetChannelURIPrincipal(chan, getter_AddRefs(mResourcePrincipal));
+
+ Unused << chan->GetResponseHeader("Timing-Allow-Origin"_ns,
+ mTimingAllowOriginHeader);
+ chan->GetURI(getter_AddRefs(mURI));
+ chan->GetOriginalURI(getter_AddRefs(mOriginalURI));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+
+ nsCOMPtr<nsITimedChannel> timedChanel(do_QueryInterface(chan));
+ if (timedChanel) {
+ timedChanel->GetInitiatorType(mInitiatorType);
+ }
+}
+
+nsresult NullHttpChannel::Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI) {
+ mURI = aURI;
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetChannelId(uint64_t* aChannelId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetChannelId(uint64_t aChannelId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetBrowserId(uint64_t*) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::SetBrowserId(uint64_t) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetTransferSize(uint64_t* aTransferSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestSize(uint64_t* aRequestSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestMethod(nsACString& aRequestMethod) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestMethod(const nsACString& aRequestMethod) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrerInfoWithoutClone(nsIReferrerInfo* aReferrerInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& _retval) {
+ _retval.Truncate();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetNewReferrerInfo(const nsACString& aUrl,
+ nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllowSTS(bool* aAllowSTS) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllowSTS(bool aAllowSTS) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectionLimit(uint32_t* aRedirectionLimit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectionLimit(uint32_t aRedirectionLimit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStatus(uint32_t* aResponseStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStatusText(nsACString& aResponseStatusText) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestSucceeded(bool* aRequestSucceeded) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseHeader(const nsACString& header,
+ nsACString& _retval) {
+ _retval.Truncate();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetOriginalResponseHeader(const nsACString& header,
+ nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsNoStoreResponse(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsNoCacheResponse(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsPrivateResponse(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::RedirectTo(nsIURI* aNewURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::UpgradeToSecure() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestContextID(uint64_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestContextID(uint64_t rcID) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void NullHttpChannel::SetSource(
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource) {}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ *aOriginalURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetOwner(nsISupports** aOwner) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetOwner(nsISupports* aOwner) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentType(nsACString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentType(const nsACString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentCharset(nsACString& aContentCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentCharset(const nsACString& aContentCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentLength(int64_t* aContentLength) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentLength(int64_t aContentLength) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Open(nsIInputStream** aStream) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetName(nsACString& aName) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetStatus(nsresult* aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::SetCanceledReason(const nsACString& aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::GetCanceledReason(nsACString& aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Cancel(nsresult aStatus) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetCanceled(bool* aCanceled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetTimingEnabled(bool* aTimingEnabled) {
+ // We don't want to report timing for null channels.
+ *aTimingEnabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTimingEnabled(bool aTimingEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectCount(uint8_t* aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectCount(uint8_t aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetInternalRedirectCount(uint8_t* aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetInternalRedirectCount(uint8_t aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetChannelCreation(mozilla::TimeStamp* aChannelCreation) {
+ *aChannelCreation = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetChannelCreation(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ TimeDuration adjust = aValue - mChannelCreationTimestamp;
+ mChannelCreationTimestamp = aValue;
+ mChannelCreationTime += (PRTime)adjust.ToMicroseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAsyncOpen(mozilla::TimeStamp* aAsyncOpen) {
+ *aAsyncOpen = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAsyncOpen(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ mAsyncOpenTime = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLaunchServiceWorkerStart(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLaunchServiceWorkerStart(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLaunchServiceWorkerEnd(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLaunchServiceWorkerEnd(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDispatchFetchEventStart(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetDispatchFetchEventStart(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDispatchFetchEventEnd(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetDispatchFetchEventEnd(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetHandleFetchEventStart(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetHandleFetchEventStart(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetHandleFetchEventEnd(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetHandleFetchEventEnd(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDomainLookupStart(mozilla::TimeStamp* aDomainLookupStart) {
+ *aDomainLookupStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDomainLookupEnd(mozilla::TimeStamp* aDomainLookupEnd) {
+ *aDomainLookupEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetConnectStart(mozilla::TimeStamp* aConnectStart) {
+ *aConnectStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTcpConnectEnd(mozilla::TimeStamp* aTcpConnectEnd) {
+ *aTcpConnectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetSecureConnectionStart(
+ mozilla::TimeStamp* aSecureConnectionStart) {
+ *aSecureConnectionStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetConnectEnd(mozilla::TimeStamp* aConnectEnd) {
+ *aConnectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestStart(mozilla::TimeStamp* aRequestStart) {
+ *aRequestStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStart(mozilla::TimeStamp* aResponseStart) {
+ *aResponseStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseEnd(mozilla::TimeStamp* aResponseEnd) {
+ *aResponseEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectStart(mozilla::TimeStamp* aRedirectStart) {
+ *aRedirectStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectStart(mozilla::TimeStamp aRedirectStart) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectEnd(mozilla::TimeStamp* aRedirectEnd) {
+ *aRedirectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectEnd(mozilla::TimeStamp aRedirectEnd) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetInitiatorType(nsAString& aInitiatorType) {
+ aInitiatorType = mInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetInitiatorType(const nsAString& aInitiatorType) {
+ mInitiatorType = aInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllRedirectsSameOrigin(bool* aAllRedirectsSameOrigin) {
+ *aAllRedirectsSameOrigin = mAllRedirectsSameOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllRedirectsPassTimingAllowCheck(
+ bool* aAllRedirectsPassTimingAllowCheck) {
+ *aAllRedirectsPassTimingAllowCheck = mAllRedirectsPassTimingAllowCheck;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllRedirectsPassTimingAllowCheck(
+ bool aAllRedirectsPassTimingAllowCheck) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetCacheReadStart(mozilla::TimeStamp* aCacheReadStart) {
+ *aCacheReadStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetCacheReadEnd(mozilla::TimeStamp* aCacheReadEnd) {
+ *aCacheReadEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTransactionPending(mozilla::TimeStamp* aRetVal) {
+ *aRetVal = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetIsMainDocumentChannel(bool* aValue) {
+ *aValue = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetIsMainDocumentChannel(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategor,
+ bool aIsWarning) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReportResourceTiming(bool enabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetReportResourceTiming(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetServerTiming(nsIArray** aServerTiming) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetNativeServerTiming(
+ nsTArray<nsCOMPtr<nsIServerTiming>>& aServerTiming) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::GetDocumentCharacterSet(
+ nsAString& aDocumentCharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#define IMPL_TIMING_ATTR(name) \
+ NS_IMETHODIMP \
+ NullHttpChannel::Get##name##Time(PRTime* _retval) { \
+ TimeStamp stamp; \
+ Get##name(&stamp); \
+ if (stamp.IsNull()) { \
+ *_retval = 0; \
+ return NS_OK; \
+ } \
+ *_retval = \
+ mChannelCreationTime + \
+ (PRTime)((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
+ return NS_OK; \
+ }
+
+IMPL_TIMING_ATTR(ChannelCreation)
+IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
+IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
+IMPL_TIMING_ATTR(DispatchFetchEventStart)
+IMPL_TIMING_ATTR(DispatchFetchEventEnd)
+IMPL_TIMING_ATTR(HandleFetchEventStart)
+IMPL_TIMING_ATTR(HandleFetchEventEnd)
+IMPL_TIMING_ATTR(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(TcpConnectEnd)
+IMPL_TIMING_ATTR(SecureConnectionStart)
+IMPL_TIMING_ATTR(ConnectEnd)
+IMPL_TIMING_ATTR(RequestStart)
+IMPL_TIMING_ATTR(ResponseStart)
+IMPL_TIMING_ATTR(ResponseEnd)
+IMPL_TIMING_ATTR(CacheReadStart)
+IMPL_TIMING_ATTR(CacheReadEnd)
+IMPL_TIMING_ATTR(RedirectStart)
+IMPL_TIMING_ATTR(RedirectEnd)
+IMPL_TIMING_ATTR(TransactionPending)
+
+#undef IMPL_TIMING_ATTR
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/NullHttpChannel.h b/netwerk/protocol/http/NullHttpChannel.h
new file mode 100644
index 0000000000..478d13e0a2
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.h
@@ -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/. */
+
+#ifndef mozilla_net_NullHttpChannel_h
+#define mozilla_net_NullHttpChannel_h
+
+#include "nsINullChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsITimedChannel.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace net {
+
+class nsProxyInfo;
+
+class NullHttpChannel final : public nsINullChannel,
+ public nsIHttpChannel,
+ public nsITimedChannel {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINULLCHANNEL
+ NS_DECL_NSIHTTPCHANNEL
+ NS_DECL_NSITIMEDCHANNEL
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIIDENTCHANNEL
+
+ NullHttpChannel();
+
+ // Copies the URI, Principal and Timing-Allow-Origin headers from the
+ // passed channel to this object, to be used for resource timing checks
+ explicit NullHttpChannel(nsIHttpChannel* chan);
+
+ // Same signature as nsHttpChannel::Init
+ [[nodiscard]] nsresult Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI);
+
+ private:
+ ~NullHttpChannel() = default;
+
+ protected:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ nsString mInitiatorType;
+ PRTime mChannelCreationTime;
+ TimeStamp mAsyncOpenTime;
+ TimeStamp mChannelCreationTimestamp;
+ nsCOMPtr<nsIPrincipal> mResourcePrincipal;
+ nsCString mTimingAllowOriginHeader;
+ bool mAllRedirectsSameOrigin{false};
+ bool mAllRedirectsPassTimingAllowCheck{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NullHttpChannel_h
diff --git a/netwerk/protocol/http/NullHttpTransaction.cpp b/netwerk/protocol/http/NullHttpTransaction.cpp
new file mode 100644
index 0000000000..f27eb16138
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -0,0 +1,212 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/NeckoChannelParams.h" // For HttpActivityArgs.
+#include "nsHttp.h"
+#include "NullHttpTransaction.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsQueryObject.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NullHttpTransaction, NullHttpTransaction,
+ nsISupportsWeakReference)
+
+NullHttpTransaction::NullHttpTransaction(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps)
+ : mStatus(NS_OK),
+ mCaps(caps | NS_HTTP_ALLOW_KEEPALIVE),
+ mRequestHead(nullptr),
+ mIsDone(false),
+ mClaimed(false),
+ mCallbacks(callbacks),
+ mConnectionInfo(ci) {
+ nsresult rv;
+ mActivityDistributor =
+ do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool activityDistributorActive;
+ rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
+ if (NS_SUCCEEDED(rv) && activityDistributorActive) {
+ // There are some observers registered at activity distributor.
+ LOG(
+ ("NulHttpTransaction::NullHttpTransaction() "
+ "mActivityDistributor is active "
+ "[this=%p, %s]",
+ this, ci->GetOrigin().get()));
+ } else {
+ // There is no observer, so don't use it.
+ mActivityDistributor = nullptr;
+ }
+}
+
+NullHttpTransaction::~NullHttpTransaction() {
+ mCallbacks = nullptr;
+ delete mRequestHead;
+}
+
+bool NullHttpTransaction::Claim() {
+ if (mClaimed) {
+ return false;
+ }
+ mClaimed = true;
+ return true;
+}
+
+void NullHttpTransaction::Unclaim() { mClaimed = false; }
+
+void NullHttpTransaction::SetConnection(nsAHttpConnection* conn) {
+ mConnection = conn;
+}
+
+nsAHttpConnection* NullHttpTransaction::Connection() {
+ return mConnection.get();
+}
+
+void NullHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** outCB) {
+ nsCOMPtr<nsIInterfaceRequestor> copyCB(mCallbacks);
+ *outCB = copyCB.forget().take();
+}
+
+void NullHttpTransaction::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress) {
+ if (status == NS_NET_STATUS_RESOLVING_HOST) {
+ if (mTimings.domainLookupStart.IsNull()) {
+ mTimings.domainLookupStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
+ if (mTimings.domainLookupEnd.IsNull()) {
+ mTimings.domainLookupEnd = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_CONNECTING_TO) {
+ if (mTimings.connectStart.IsNull()) {
+ mTimings.connectStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_CONNECTED_TO) {
+ TimeStamp tnow = TimeStamp::Now();
+ if (mTimings.connectEnd.IsNull()) {
+ mTimings.connectEnd = tnow;
+ }
+ if (mTimings.tcpConnectEnd.IsNull()) {
+ mTimings.tcpConnectEnd = tnow;
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) {
+ if (mTimings.secureConnectionStart.IsNull()) {
+ mTimings.secureConnectionStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+ mTimings.connectEnd = TimeStamp::Now();
+ ;
+ }
+
+ if (mActivityDistributor) {
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivity(mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL()),
+ NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT, static_cast<uint32_t>(status),
+ PR_Now(), progress, ""_ns);
+ }
+}
+
+bool NullHttpTransaction::IsDone() { return mIsDone; }
+
+nsresult NullHttpTransaction::Status() { return mStatus; }
+
+uint32_t NullHttpTransaction::Caps() { return mCaps; }
+
+nsresult NullHttpTransaction::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) {
+ *countRead = 0;
+ mIsDone = true;
+ return NS_BASE_STREAM_CLOSED;
+}
+
+nsresult NullHttpTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ *countWritten = 0;
+ return NS_BASE_STREAM_CLOSED;
+}
+
+uint32_t NullHttpTransaction::Http1xTransactionCount() { return 0; }
+
+nsHttpRequestHead* NullHttpTransaction::RequestHead() {
+ // We suport a requesthead at all so that a CONNECT tunnel transaction
+ // can obtain a Host header from it, but we lazy-popualate that header.
+
+ if (!mRequestHead) {
+ mRequestHead = new nsHttpRequestHead();
+
+ nsAutoCString hostHeader;
+ nsCString host(mConnectionInfo->GetOrigin());
+ nsresult rv = nsHttpHandler::GenerateHostPort(
+ host, mConnectionInfo->OriginPort(), hostHeader);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mRequestHead->SetHeader(nsHttp::Host, hostHeader);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (mActivityDistributor) {
+ // Report request headers.
+ nsCString reqHeaderBuf;
+ mRequestHead->Flatten(reqHeaderBuf, false);
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivity(mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL()),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, reqHeaderBuf);
+ }
+ }
+
+ // CONNECT tunnels may also want Proxy-Authorization but that is a lot
+ // harder to determine, so for now we will let those connections fail in
+ // the NullHttpTransaction and let them be retried from the pending queue
+ // with a bound transaction
+ }
+
+ return mRequestHead;
+}
+
+nsresult NullHttpTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void NullHttpTransaction::SetProxyConnectFailed() {}
+
+void NullHttpTransaction::Close(nsresult reason) {
+ mStatus = reason;
+ mConnection = nullptr;
+ mIsDone = true;
+ if (mActivityDistributor) {
+ // Report that this transaction is closing.
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivity(mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL()),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0, ""_ns);
+ }
+}
+
+nsHttpConnectionInfo* NullHttpTransaction::ConnectionInfo() {
+ return mConnectionInfo;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/NullHttpTransaction.h b/netwerk/protocol/http/NullHttpTransaction.h
new file mode 100644
index 0000000000..cedbc6fd63
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -0,0 +1,94 @@
+/* -*- 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_NullHttpTransaction_h
+#define mozilla_net_NullHttpTransaction_h
+
+#include "nsAHttpTransaction.h"
+#include "mozilla/Attributes.h"
+#include "TimingStruct.h"
+
+// This is the minimal nsAHttpTransaction implementation. A NullHttpTransaction
+// can be used to drive connection level semantics (such as SSL handshakes
+// tunnels) so that a nsHttpConnection becomes fully established in
+// anticipation of a real transaction needing to use it soon.
+
+class nsIHttpActivityObserver;
+
+namespace mozilla {
+namespace net {
+
+class nsAHttpConnection;
+class nsHttpConnectionInfo;
+class nsHttpRequestHead;
+
+// 6c445340-3b82-4345-8efa-4902c3b8805a
+#define NS_NULLHTTPTRANSACTION_IID \
+ { \
+ 0x6c445340, 0x3b82, 0x4345, { \
+ 0x8e, 0xfa, 0x49, 0x02, 0xc3, 0xb8, 0x80, 0x5a \
+ } \
+ }
+
+class NullHttpTransaction : public nsAHttpTransaction {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NULLHTTPTRANSACTION_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+
+ NullHttpTransaction(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks, uint32_t caps);
+
+ [[nodiscard]] bool Claim();
+ void Unclaim();
+
+ // Overload of nsAHttpTransaction methods
+ bool IsNullTransaction() final { return true; }
+ NullHttpTransaction* QueryNullTransaction() final { return this; }
+ bool ResponseTimeoutEnabled() const final { return true; }
+ PRIntervalTime ResponseTimeout() final { return PR_SecondsToInterval(15); }
+
+ // We have to override this function because |mTransaction| in
+ // nsHalfOpenSocket could be either nsHttpTransaction or NullHttpTransaction.
+ // NullHttpTransaction will be activated on the connection immediately after
+ // creation and be never put in a pending queue, so it's OK to just return 0.
+ uint64_t BrowserId() override { return 0; }
+
+ TimingStruct Timings() { return mTimings; }
+
+ mozilla::TimeStamp GetTcpConnectEnd() { return mTimings.tcpConnectEnd; }
+ mozilla::TimeStamp GetSecureConnectionStart() {
+ return mTimings.secureConnectionStart;
+ }
+
+ protected:
+ virtual ~NullHttpTransaction();
+
+ private:
+ nsresult mStatus;
+
+ protected:
+ uint32_t mCaps;
+ nsHttpRequestHead* mRequestHead;
+
+ private:
+ bool mIsDone;
+ bool mClaimed;
+ TimingStruct mTimings;
+
+ protected:
+ RefPtr<nsAHttpConnection> mConnection;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(NullHttpTransaction, NS_NULLHTTPTRANSACTION_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NullHttpTransaction_h
diff --git a/netwerk/protocol/http/ObliviousHttpChannel.cpp b/netwerk/protocol/http/ObliviousHttpChannel.cpp
new file mode 100644
index 0000000000..fafbf84b0b
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpChannel.cpp
@@ -0,0 +1,860 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "ObliviousHttpChannel.h"
+
+#include "BinaryHttpRequest.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsStringStream.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(ObliviousHttpChannel, nsIChannel, nsIHttpChannel,
+ nsIObliviousHttpChannel, nsIHttpChannelInternal,
+ nsIIdentChannel, nsIRequest, nsIRequestObserver,
+ nsIStreamListener, nsIUploadChannel2, nsITimedChannel)
+
+ObliviousHttpChannel::ObliviousHttpChannel(
+ nsIURI* targetURI, const nsTArray<uint8_t>& encodedConfig,
+ nsIHttpChannel* innerChannel)
+ : mTargetURI(targetURI),
+ mEncodedConfig(encodedConfig.Clone()),
+ mInnerChannel(innerChannel),
+ mInnerChannelInternal(do_QueryInterface(innerChannel)),
+ mInnerChannelTimed(do_QueryInterface(innerChannel)) {
+ LOG(("ObliviousHttpChannel ctor [this=%p]", this));
+ MOZ_ASSERT(mInnerChannel);
+ MOZ_ASSERT(mInnerChannelInternal);
+ MOZ_ASSERT(mInnerChannelTimed);
+}
+
+ObliviousHttpChannel::~ObliviousHttpChannel() {
+ LOG(("ObliviousHttpChannel dtor [this=%p]", this));
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ return mInnerChannel->GetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ return mInnerChannel->SetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetBrowserId(uint64_t* aWindowId) {
+ return mInnerChannel->GetBrowserId(aWindowId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetBrowserId(uint64_t aId) {
+ return mInnerChannel->SetBrowserId(aId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetTransferSize(uint64_t* aTransferSize) {
+ return mInnerChannel->GetTransferSize(aTransferSize);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestSize(uint64_t* aRequestSize) {
+ return mInnerChannel->GetRequestSize(aRequestSize);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ return mInnerChannel->GetDecodedBodySize(aDecodedBodySize);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestMethod(nsACString& aRequestMethod) {
+ aRequestMethod.Assign(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRequestMethod(const nsACString& aRequestMethod) {
+ mMethod.Assign(aRequestMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ return mInnerChannel->GetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return mInnerChannel->SetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetReferrerInfoWithoutClone(
+ nsIReferrerInfo* aReferrerInfo) {
+ return mInnerChannel->SetReferrerInfoWithoutClone(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& _retval) {
+ _retval.Truncate();
+ auto value = mHeaders.Lookup(aHeader);
+ if (!value) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ _retval.Assign(*value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ mHeaders.WithEntryHandle(aHeader, [&aValue, aMerge](auto&& entry) {
+ if (!entry) {
+ entry.Insert(aValue);
+ return;
+ }
+ if (!aMerge) {
+ entry.Update(aValue);
+ return;
+ }
+ nsAutoCString newValue(*entry);
+ newValue.AppendLiteral(", ");
+ newValue.Append(aValue);
+ entry.Update(newValue);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetNewReferrerInfo(
+ const nsACString& aUrl, nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ return mInnerChannel->SetNewReferrerInfo(aUrl, aPolicy, aSendReferrer);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ return SetRequestHeader(aHeader, EmptyCString(), false);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ for (auto iter = mHeaders.ConstIter(); !iter.Done(); iter.Next()) {
+ nsresult rv = aVisitor->VisitHeader(iter.Key(), iter.Data());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitNonDefaultRequestHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ return mInnerChannel->VisitNonDefaultRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetAllowSTS(bool* aAllowSTS) {
+ return mInnerChannel->GetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetAllowSTS(bool aAllowSTS) {
+ return mInnerChannel->SetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRedirectionLimit(uint32_t* aRedirectionLimit) {
+ return mInnerChannel->GetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRedirectionLimit(uint32_t aRedirectionLimit) {
+ return mInnerChannel->SetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetResponseStatus(uint32_t* aResponseStatus) {
+ if (!mBinaryHttpResponse) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ uint16_t responseStatus;
+ nsresult rv = mBinaryHttpResponse->GetStatus(&responseStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aResponseStatus = responseStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetResponseStatusText(nsACString& aResponseStatusText) {
+ LOG(("ObliviousHttpChannel::GetResponseStatusText NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestSucceeded(bool* aRequestSucceeded) {
+ uint32_t responseStatus;
+ nsresult rv = GetResponseStatus(&responseStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aRequestSucceeded = (responseStatus / 100) == 2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetIsMainDocumentChannel(bool* aValue) {
+ return mInnerChannel->GetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetIsMainDocumentChannel(bool aValue) {
+ return mInnerChannel->SetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetResponseHeader(const nsACString& header,
+ nsACString& _retval) {
+ if (!mBinaryHttpResponse) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsTArray<nsCString> responseHeaderNames;
+ nsTArray<nsCString> responseHeaderValues;
+ nsresult rv = mBinaryHttpResponse->GetHeaderNames(responseHeaderNames);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mBinaryHttpResponse->GetHeaderValues(responseHeaderValues);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ for (size_t i = 0;
+ i < responseHeaderNames.Length() && i < responseHeaderValues.Length();
+ i++) {
+ if (responseHeaderNames[i].EqualsIgnoreCase(header)) {
+ _retval.Assign(responseHeaderValues[i]);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ LOG(("ObliviousHttpChannel::SetResponseHeader NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ if (!mBinaryHttpResponse) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsTArray<nsCString> responseHeaderNames;
+ nsTArray<nsCString> responseHeaderValues;
+ nsresult rv = mBinaryHttpResponse->GetHeaderNames(responseHeaderNames);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mBinaryHttpResponse->GetHeaderValues(responseHeaderValues);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ for (size_t i = 0;
+ i < responseHeaderNames.Length() && i < responseHeaderValues.Length();
+ i++) {
+ rv = aVisitor->VisitHeader(responseHeaderNames[i], responseHeaderValues[i]);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetOriginalResponseHeader(
+ const nsACString& header, nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitOriginalResponseHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ LOG(
+ ("ObliviousHttpChannel::VisitOriginalResponseHeaders NOT IMPLEMENTED "
+ "[this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ LOG(
+ ("ObliviousHttpChannel::ShouldStripRequestBodyHeader NOT IMPLEMENTED "
+ "[this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::IsNoStoreResponse(bool* _retval) {
+ LOG(("ObliviousHttpChannel::IsNoStoreResponse NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::IsNoCacheResponse(bool* _retval) {
+ LOG(("ObliviousHttpChannel::IsNoCacheResponse NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::IsPrivateResponse(bool* _retval) {
+ LOG(("ObliviousHttpChannel::IsPrivateResponse NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::RedirectTo(nsIURI* aNewURI) {
+ LOG(("ObliviousHttpChannel::RedirectTo NOT IMPLEMENTED [this=%p]", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::UpgradeToSecure() {
+ LOG(("ObliviousHttpChannel::UpgradeToSecure NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestContextID(uint64_t* _retval) {
+ return mInnerChannel->GetRequestContextID(_retval);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRequestContextID(uint64_t rcID) {
+ return mInnerChannel->SetRequestContextID(rcID);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ return mInnerChannel->GetProtocolVersion(aProtocolVersion);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ LOG(("ObliviousHttpChannel::GetEncodedBodySize NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ return mInnerChannel->LogBlockedCORSRequest(aMessage, aCategory, aIsWarning);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ return mInnerChannel->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
+ aContentType);
+}
+
+void ObliviousHttpChannel::SetSource(
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource) {
+ LOG(("ObliviousHttpChannel::SetSource NOT IMPLEMENTED [this=%p]", this));
+ // NS_ERROR_NOT_IMPLEMENTED
+}
+
+void ObliviousHttpChannel::SetConnectionInfo(nsHttpConnectionInfo* aCi) {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->SetConnectionInfo(aCi);
+ }
+}
+
+void ObliviousHttpChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+ }
+}
+
+void ObliviousHttpChannel::DisableAltDataCache() {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->DisableAltDataCache();
+ }
+}
+
+void ObliviousHttpChannel::SetAltDataForChild(bool aIsForChild) {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->SetAltDataForChild(aIsForChild);
+ }
+}
+
+void ObliviousHttpChannel::SetCorsPreflightParameters(
+ nsTArray<nsTString<char>> const& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->SetCorsPreflightParameters(
+ aUnsafeHeaders, aShouldStripRequestBodyHeader);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ return mInnerChannel->GetOriginalURI(aOriginalURI);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ return mInnerChannel->SetOriginalURI(aOriginalURI);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mTargetURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetOwner(nsISupports** aOwner) {
+ return mInnerChannel->GetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetOwner(nsISupports* aOwner) {
+ return mInnerChannel->SetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ return mInnerChannel->GetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ return mInnerChannel->SetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ return mInnerChannel->GetSecurityInfo(aSecurityInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentType(nsACString& aContentType) {
+ return GetResponseHeader("content-type"_ns, aContentType);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentType(const nsACString& aContentType) {
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentCharset(nsACString& aContentCharset) {
+ return mInnerChannel->GetContentCharset(aContentCharset);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentCharset(const nsACString& aContentCharset) {
+ return mInnerChannel->SetContentCharset(aContentCharset);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentLength(int64_t* aContentLength) {
+ return mInnerChannel->GetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentLength(int64_t aContentLength) {
+ return mInnerChannel->SetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::Open(nsIInputStream** aStream) {
+ LOG(("ObliviousHttpChannel::Open NOT IMPLEMENTED [this=%p]", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ LOG(("ObliviousHttpChannel::AsyncOpen [this=%p, listener=%p]", this,
+ aListener));
+ mStreamListener = aListener;
+ nsresult rv = mInnerChannel->SetRequestMethod("POST"_ns);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mInnerChannel->SetRequestHeader("Content-Type"_ns,
+ "message/ohttp-req"_ns, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoCString scheme;
+ if (!mTargetURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ rv = mTargetURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoCString authority;
+ rv = mTargetURI->GetHostPort(authority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoCString path;
+ rv = mTargetURI->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<nsCString> headerNames;
+ nsTArray<nsCString> headerValues;
+ for (auto iter = mHeaders.ConstIter(); !iter.Done(); iter.Next()) {
+ headerNames.AppendElement(iter.Key());
+ headerValues.AppendElement(iter.Data());
+ }
+ if (!mContentType.IsEmpty() && !headerNames.Contains("Content-Type")) {
+ headerNames.AppendElement("Content-Type"_ns);
+ headerValues.AppendElement(mContentType);
+ }
+ nsCOMPtr<nsIBinaryHttp> bhttp(
+ do_GetService("@mozilla.org/network/binary-http;1"));
+ nsCOMPtr<nsIBinaryHttpRequest> bhttpRequest(new BinaryHttpRequest(
+ mMethod, scheme, authority, path, std::move(headerNames),
+ std::move(headerValues), std::move(mContent)));
+ nsTArray<uint8_t> encodedRequest;
+ rv = bhttp->EncodeRequest(bhttpRequest, encodedRequest);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIObliviousHttp> obliviousHttp(
+ do_GetService("@mozilla.org/network/oblivious-http;1"));
+ if (!obliviousHttp) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = obliviousHttp->EncapsulateRequest(mEncodedConfig, encodedRequest,
+ getter_AddRefs(mEncapsulatedRequest));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<uint8_t> encRequest;
+ rv = mEncapsulatedRequest->GetEncRequest(encRequest);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIUploadChannel2> uploadChannel(do_QueryInterface(mInnerChannel));
+ if (!uploadChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<nsIInputStream> uploadStream;
+ uint32_t streamLength = encRequest.Length();
+ rv = NS_NewByteInputStream(getter_AddRefs(uploadStream),
+ std::move(encRequest));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = uploadChannel->ExplicitSetUploadStream(
+ uploadStream, "message/ohttp-req"_ns, streamLength, "POST"_ns, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return mInnerChannel->AsyncOpen(this);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetCanceled(bool* aCanceled) {
+ return mInnerChannel->GetCanceled(aCanceled);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ return mInnerChannel->GetContentDisposition(aContentDisposition);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return mInnerChannel->SetContentDisposition(aContentDisposition);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return mInnerChannel->GetContentDispositionFilename(
+ aContentDispositionFilename);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return mInnerChannel->SetContentDispositionFilename(
+ aContentDispositionFilename);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return mInnerChannel->GetContentDispositionHeader(aContentDispositionHeader);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ return mInnerChannel->GetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ return mInnerChannel->SetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetIsDocument(bool* aIsDocument) {
+ return mInnerChannel->GetIsDocument(aIsDocument);
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream, uint64_t aOffset,
+ uint32_t aCount) {
+ LOG(
+ ("ObliviousHttpChannel::OnDataAvailable [this=%p, request=%p, stream=%p, "
+ "offset=%" PRIu64 ", count=%u]",
+ this, aRequest, aStream, aOffset, aCount));
+ MOZ_ASSERT(aOffset == mRawResponse.Length());
+ size_t oldLength = mRawResponse.Length();
+ size_t newLength = oldLength + aCount;
+ if (newLength < oldLength) { // i.e., overflow
+ return NS_ERROR_FAILURE;
+ }
+ mRawResponse.SetCapacity(newLength);
+ mRawResponse.SetLengthAndRetainStorage(newLength);
+ void* dest = mRawResponse.Elements() + oldLength;
+ uint64_t written = 0;
+ nsresult rv = NS_ReadInputStreamToBuffer(aStream, &dest, aCount, &written);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (written != aCount) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("ObliviousHttpChannel::OnStartRequest [this=%p, request=%p]", this,
+ aRequest));
+ return NS_OK;
+}
+
+nsresult ObliviousHttpChannel::ProcessOnStopRequest() {
+ if (mRawResponse.IsEmpty()) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIObliviousHttp> obliviousHttp(
+ do_GetService("@mozilla.org/network/oblivious-http;1"));
+ if (!obliviousHttp) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIObliviousHttpClientResponse> response;
+ nsresult rv = mEncapsulatedRequest->GetResponse(getter_AddRefs(response));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<uint8_t> decapsulated;
+ rv = response->Decapsulate(mRawResponse, decapsulated);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIBinaryHttp> bhttp(
+ do_GetService("@mozilla.org/network/binary-http;1"));
+ if (!bhttp) {
+ return NS_ERROR_FAILURE;
+ }
+ return bhttp->DecodeResponse(decapsulated,
+ getter_AddRefs(mBinaryHttpResponse));
+}
+
+void ObliviousHttpChannel::EmitOnDataAvailable() {
+ if (!mBinaryHttpResponse) {
+ return;
+ }
+ nsTArray<uint8_t> content;
+ nsresult rv = mBinaryHttpResponse->GetContent(content);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (content.IsEmpty()) {
+ return;
+ }
+ if (content.Length() > std::numeric_limits<uint32_t>::max()) {
+ return;
+ }
+ uint32_t contentLength = (uint32_t)content.Length();
+ nsCOMPtr<nsIInputStream> contentStream;
+ rv = NS_NewByteInputStream(getter_AddRefs(contentStream), std::move(content));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = mStreamListener->OnDataAvailable(this, contentStream, 0, contentLength);
+ Unused << rv;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ LOG(("ObliviousHttpChannel::OnStopRequest [this=%p, request=%p, status=%u]",
+ this, aRequest, (uint32_t)aStatusCode));
+
+ auto releaseStreamListener = MakeScopeExit(
+ [self = RefPtr{this}]() mutable { self->mStreamListener = nullptr; });
+
+ if (NS_SUCCEEDED(aStatusCode)) {
+ bool requestSucceeded;
+ nsresult rv = mInnerChannel->GetRequestSucceeded(&requestSucceeded);
+ if (NS_SUCCEEDED(rv) && requestSucceeded) {
+ aStatusCode = ProcessOnStopRequest();
+ }
+ }
+ Unused << mStreamListener->OnStartRequest(this);
+ if (NS_SUCCEEDED(aStatusCode)) {
+ EmitOnDataAvailable();
+ }
+ Unused << mStreamListener->OnStopRequest(this, aStatusCode);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIObliviousHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRelayChannel(nsIHttpChannel** aChannel) {
+ if (!aChannel) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aChannel = do_AddRef(mInnerChannel).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIUploadChannel2
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP ObliviousHttpChannel::ExplicitSetUploadStream(
+ nsIInputStream* aStream, const nsACString& aContentType,
+ int64_t aContentLength, const nsACString& aMethod, bool aStreamHasHeaders) {
+ // This function should only be called before AsyncOpen.
+ if (mStreamListener) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ if (aMethod != "POST"_ns || aStreamHasHeaders) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mMethod.Assign(aMethod);
+ uint64_t available;
+ if (aContentLength < 0) {
+ nsresult rv = aStream->Available(&available);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ available = aContentLength;
+ }
+ if (available > std::numeric_limits<int64_t>::max()) {
+ return NS_ERROR_FAILURE;
+ }
+ mContent.SetCapacity(available);
+ mContent.SetLengthAndRetainStorage(available);
+ void* dest = mContent.Elements();
+ uint64_t written = 0;
+ nsresult rv =
+ NS_ReadInputStreamToBuffer(aStream, &dest, (int64_t)available, &written);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (written != available) {
+ return NS_ERROR_FAILURE;
+ }
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::GetUploadStreamHasHeaders(
+ bool* aUploadStreamHasHeaders) {
+ *aUploadStreamHasHeaders = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::CloneUploadStream(
+ int64_t* aContentLength, nsIInputStream** _retval) {
+ LOG(("ObliviousHttpChannel::CloneUploadStream NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::GetDocumentCharacterSet(
+ nsAString& aDocumenharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/ObliviousHttpChannel.h b/netwerk/protocol/http/ObliviousHttpChannel.h
new file mode 100644
index 0000000000..9dc9c9f689
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpChannel.h
@@ -0,0 +1,72 @@
+/* -*- 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_ObliviousHttpChannel_h
+#define mozilla_net_ObliviousHttpChannel_h
+
+#include "nsIBinaryHttp.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIObliviousHttp.h"
+#include "nsIObliviousHttpChannel.h"
+#include "nsIStreamListener.h"
+#include "nsITimedChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsTHashMap.h"
+
+namespace mozilla::net {
+
+class ObliviousHttpChannel final : public nsIObliviousHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsIStreamListener,
+ public nsIUploadChannel2,
+ public nsITimedChannel {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIHTTPCHANNEL
+ NS_DECL_NSIOBLIVIOUSHTTPCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIUPLOADCHANNEL2
+
+ ObliviousHttpChannel(nsIURI* targetURI,
+ const nsTArray<uint8_t>& encodedConfig,
+ nsIHttpChannel* innerChannel);
+
+ NS_FORWARD_SAFE_NSIREQUEST(mInnerChannel)
+ NS_FORWARD_SAFE_NSIIDENTCHANNEL(mInnerChannel)
+ NS_FORWARD_SAFE_NSIHTTPCHANNELINTERNAL(mInnerChannelInternal)
+ NS_FORWARD_SAFE_NSITIMEDCHANNEL(mInnerChannelTimed)
+
+ protected:
+ ~ObliviousHttpChannel();
+
+ nsresult ProcessOnStopRequest();
+ void EmitOnDataAvailable();
+
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsTArray<uint8_t> mEncodedConfig;
+
+ nsCString mMethod{"GET"_ns};
+ nsCString mContentType;
+ nsTHashMap<nsCStringHashKey, nsCString> mHeaders;
+ nsTArray<uint8_t> mContent;
+
+ nsCOMPtr<nsIHttpChannel> mInnerChannel;
+ nsCOMPtr<nsIHttpChannelInternal> mInnerChannelInternal;
+ nsCOMPtr<nsITimedChannel> mInnerChannelTimed;
+ nsCOMPtr<nsIObliviousHttpClientRequest> mEncapsulatedRequest;
+ nsTArray<uint8_t> mRawResponse;
+ nsCOMPtr<nsIBinaryHttpResponse> mBinaryHttpResponse;
+
+ nsCOMPtr<nsIStreamListener> mStreamListener;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_ObliviousHttpChannel_h
diff --git a/netwerk/protocol/http/ObliviousHttpService.cpp b/netwerk/protocol/http/ObliviousHttpService.cpp
new file mode 100644
index 0000000000..f1a1cc7b88
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpService.cpp
@@ -0,0 +1,235 @@
+/* -*- 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 "ObliviousHttpService.h"
+
+#include "DNSUtils.h"
+#include "ObliviousHttpChannel.h"
+#include "mozilla/Base64.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(ObliviousHttpService, nsIObliviousHttpService, nsIObserver,
+ nsIStreamLoaderObserver)
+
+ObliviousHttpService::ObliviousHttpService()
+ : mTRRConfig(ObliviousHttpConfig(), "ObliviousHttpService::mTRRConfig") {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch) {
+ prefBranch->AddObserver("network.trr.ohttp", this, false);
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "network:captive-portal-connectivity-changed",
+ false);
+ obs->AddObserver(this, "network:trr-confirmation", false);
+ }
+
+ ReadPrefs("*"_ns);
+}
+
+static constexpr nsLiteralCString kTRRohttpConfigURIPref =
+ "network.trr.ohttp.config_uri"_ns;
+static constexpr nsLiteralCString kTRRohttpRelayURIPref =
+ "network.trr.ohttp.relay_uri"_ns;
+
+void ObliviousHttpService::FetchConfig(bool aConfigURIChanged) {
+ auto scopeExit = MakeScopeExit([&] {
+ nsCOMPtr<nsIObserverService> obs(mozilla::services::GetObserverService());
+ if (!obs) {
+ return;
+ }
+ obs->NotifyObservers(nullptr, "ohttp-service-config-loaded", u"no-changes");
+ });
+
+ {
+ auto trrConfig = mTRRConfig.Lock();
+ if (aConfigURIChanged) {
+ // If the config URI has changed, we need to clear the config.
+ trrConfig->mEncodedConfig.Clear();
+ } else if (trrConfig->mEncodedConfig.Length()) {
+ // The URI hasn't changed and we already have a config. No need to reload
+ return;
+ }
+ }
+
+ nsAutoCString configURIString;
+ nsresult rv =
+ Preferences::GetCString(kTRRohttpConfigURIPref.get(), configURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIURI> configURI;
+ rv = NS_NewURI(getter_AddRefs(configURI), configURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = DNSUtils::CreateChannelHelper(configURI, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = channel->SetLoadFlags(
+ nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (!httpChannel) {
+ return;
+ }
+ // This connection should not use TRR
+ rv = httpChannel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = httpChannel->AsyncOpen(loader);
+
+ if (NS_SUCCEEDED(rv)) {
+ scopeExit.release();
+ return;
+ }
+
+ nsPrintfCString msg(
+ "ObliviousHttpService::FetchConfig AsyncOpen failed rv=%X",
+ static_cast<uint32_t>(rv));
+ NS_WARNING(msg.get());
+}
+
+void ObliviousHttpService::ReadPrefs(const nsACString& whichPref) {
+ if (whichPref.Equals(kTRRohttpRelayURIPref) || whichPref.EqualsLiteral("*")) {
+ nsAutoCString relayURIString;
+ nsresult rv =
+ Preferences::GetCString(kTRRohttpRelayURIPref.get(), relayURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIURI> relayURI;
+ rv = NS_NewURI(getter_AddRefs(relayURI), relayURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ auto trrConfig = mTRRConfig.Lock();
+ trrConfig->mRelayURI = relayURI;
+ }
+
+ if (whichPref.Equals(kTRRohttpConfigURIPref) ||
+ whichPref.EqualsLiteral("*")) {
+ FetchConfig(true);
+ }
+}
+
+// nsIObliviousHttpService
+
+NS_IMETHODIMP
+ObliviousHttpService::NewChannel(nsIURI* relayURI, nsIURI* targetURI,
+ const nsTArray<uint8_t>& encodedConfig,
+ nsIChannel** result) {
+ nsCOMPtr<nsIChannel> innerChannel;
+ nsresult rv =
+ DNSUtils::CreateChannelHelper(relayURI, getter_AddRefs(innerChannel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIHttpChannel> innerHttpChannel(do_QueryInterface(innerChannel));
+ if (!innerHttpChannel) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIChannel> obliviousHttpChannel(
+ new ObliviousHttpChannel(targetURI, encodedConfig, innerHttpChannel));
+ obliviousHttpChannel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpService::GetTRRSettings(nsIURI** relayURI,
+ nsTArray<uint8_t>& encodedConfig) {
+ auto trrConfig = mTRRConfig.Lock();
+ *relayURI = do_AddRef(trrConfig->mRelayURI).take();
+ encodedConfig.Assign(trrConfig->mEncodedConfig.Clone());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpService::ClearTRRConfig() {
+ auto trrConfig = mTRRConfig.Lock();
+ trrConfig->mEncodedConfig.Clear();
+ return NS_OK;
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+ObliviousHttpService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ ReadPrefs(NS_ConvertUTF16toUTF8(data));
+ } else if (!strcmp(topic, "xpcom-shutdown")) {
+ if (nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID)) {
+ prefBranch->RemoveObserver("network.trr.ohttp", this);
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ obs->RemoveObserver(this, "network:captive-portal-connectivity-changed");
+ obs->RemoveObserver(this, "network:trr-confirmation");
+ }
+ } else if (!strcmp(topic, "network:captive-portal-connectivity-changed") ||
+ (!strcmp(topic, "network:trr-confirmation") && data &&
+ u"CONFIRM_FAILED"_ns.Equals(data))) {
+ FetchConfig(false);
+ }
+ return NS_OK;
+}
+
+// nsIStreamLoaderObserver
+
+NS_IMETHODIMP
+ObliviousHttpService::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aLength,
+ const uint8_t* aContent) {
+ if (NS_SUCCEEDED(aStatus)) {
+ auto trrConfig = mTRRConfig.Lock();
+ trrConfig->mEncodedConfig.Clear();
+ trrConfig->mEncodedConfig.AppendElements(aContent, aLength);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService(
+ mozilla::services::GetObserverService());
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = observerService->NotifyObservers(
+ nullptr, "ohttp-service-config-loaded",
+ NS_SUCCEEDED(aStatus) ? u"success" : u"failed");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/ObliviousHttpService.h b/netwerk/protocol/http/ObliviousHttpService.h
new file mode 100644
index 0000000000..2d5b390072
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpService.h
@@ -0,0 +1,46 @@
+/* -*- 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_ObliviousHttpService_h
+#define mozilla_net_ObliviousHttpService_h
+
+#include "mozilla/DataMutex.h"
+#include "nsCOMPtr.h"
+#include "nsIObliviousHttp.h"
+#include "nsIObserver.h"
+#include "nsIStreamLoader.h"
+
+namespace mozilla::net {
+
+class ObliviousHttpConfig {
+ public:
+ nsCOMPtr<nsIURI> mRelayURI;
+ nsTArray<uint8_t> mEncodedConfig;
+};
+
+class ObliviousHttpService final : public nsIObliviousHttpService,
+ public nsIObserver,
+ public nsIStreamLoaderObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBLIVIOUSHTTPSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ ObliviousHttpService();
+
+ private:
+ ~ObliviousHttpService() = default;
+ void ReadPrefs(const nsACString& whichPref);
+ void FetchConfig(bool aConfigURIChanged);
+
+ DataMutex<ObliviousHttpConfig> mTRRConfig;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_ObliviousHttpService_h
diff --git a/netwerk/protocol/http/OpaqueResponseUtils.cpp b/netwerk/protocol/http/OpaqueResponseUtils.cpp
new file mode 100644
index 0000000000..6597c0b771
--- /dev/null
+++ b/netwerk/protocol/http/OpaqueResponseUtils.cpp
@@ -0,0 +1,679 @@
+/* -*- 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/OpaqueResponseUtils.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/dom/JSValidatorParent.h"
+#include "ErrorList.h"
+#include "nsContentUtils.h"
+#include "nsHttpResponseHead.h"
+#include "nsISupports.h"
+#include "nsMimeTypes.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsStringStream.h"
+#include "HttpBaseChannel.h"
+
+static mozilla::LazyLogModule gORBLog("ORB");
+
+#define LOGORB(msg, ...) \
+ MOZ_LOG(gORBLog, LogLevel::Debug, \
+ ("%s: %p " msg, __func__, this, ##__VA_ARGS__))
+
+namespace mozilla::net {
+
+static bool IsOpaqueSafeListedMIMEType(const nsACString& aContentType) {
+ if (aContentType.EqualsLiteral(TEXT_CSS) ||
+ aContentType.EqualsLiteral(IMAGE_SVG_XML)) {
+ return true;
+ }
+
+ NS_ConvertUTF8toUTF16 typeString(aContentType);
+ return nsContentUtils::IsJavascriptMIMEType(typeString);
+}
+
+// These need to be kept in sync with
+// "browser.opaqueResponseBlocking.mediaExceptionsStrategy"
+enum class OpaqueResponseMediaException { NoExceptions, AllowSome, AllowAll };
+
+static OpaqueResponseMediaException ConfiguredMediaExceptionsStrategy() {
+ uint32_t pref = StaticPrefs::
+ browser_opaqueResponseBlocking_mediaExceptionsStrategy_DoNotUseDirectly();
+ if (NS_WARN_IF(pref > static_cast<uint32_t>(
+ OpaqueResponseMediaException::AllowAll))) {
+ return OpaqueResponseMediaException::AllowAll;
+ }
+
+ return static_cast<OpaqueResponseMediaException>(pref);
+}
+
+static bool IsOpaqueSafeListedSpecBreakingMIMEType(
+ const nsACString& aContentType, bool aNoSniff) {
+ // Avoid trouble with DASH/HLS. See bug 1698040.
+ if (aContentType.EqualsLiteral(APPLICATION_DASH_XML) ||
+ aContentType.EqualsLiteral(APPLICATION_MPEGURL) ||
+ aContentType.EqualsLiteral(AUDIO_MPEG_URL) ||
+ aContentType.EqualsLiteral(TEXT_VTT)) {
+ return true;
+ }
+
+ // Do what Chromium does. This is from bug 1828375, and we should ideally
+ // revert this.
+ if (aContentType.EqualsLiteral(TEXT_PLAIN) && aNoSniff) {
+ return true;
+ }
+
+ switch (ConfiguredMediaExceptionsStrategy()) {
+ case OpaqueResponseMediaException::NoExceptions:
+ break;
+ case OpaqueResponseMediaException::AllowSome:
+ if (aContentType.EqualsLiteral(AUDIO_MP3) ||
+ aContentType.EqualsLiteral(AUDIO_AAC) ||
+ aContentType.EqualsLiteral(AUDIO_AACP) ||
+ aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) {
+ return true;
+ }
+ break;
+ case OpaqueResponseMediaException::AllowAll:
+ if (StringBeginsWith(aContentType, "audio/"_ns) ||
+ StringBeginsWith(aContentType, "video/"_ns) ||
+ aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) {
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static bool IsOpaqueBlockListedMIMEType(const nsACString& aContentType) {
+ return aContentType.EqualsLiteral(TEXT_HTML) ||
+ StringEndsWith(aContentType, "+json"_ns) ||
+ aContentType.EqualsLiteral(APPLICATION_JSON) ||
+ aContentType.EqualsLiteral(TEXT_JSON) ||
+ StringEndsWith(aContentType, "+xml"_ns) ||
+ aContentType.EqualsLiteral(APPLICATION_XML) ||
+ aContentType.EqualsLiteral(TEXT_XML);
+}
+
+static bool IsOpaqueBlockListedNeverSniffedMIMEType(
+ const nsACString& aContentType) {
+ return aContentType.EqualsLiteral(APPLICATION_GZIP2) ||
+ aContentType.EqualsLiteral(APPLICATION_MSEXCEL) ||
+ aContentType.EqualsLiteral(APPLICATION_MSPPT) ||
+ aContentType.EqualsLiteral(APPLICATION_MSWORD) ||
+ aContentType.EqualsLiteral(APPLICATION_MSWORD_TEMPLATE) ||
+ aContentType.EqualsLiteral(APPLICATION_PDF) ||
+ aContentType.EqualsLiteral(APPLICATION_MPEGURL) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKPOINT) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKSHEET) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKWORD) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_EXCEL) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_EXCEL2) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_PPT) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_PPT2) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD2) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD3) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MSWORD) ||
+ aContentType.EqualsLiteral(
+ APPLICATION_VND_PRESENTATIONML_PRESENTATION) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATIONML_TEMPLATE) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEETML_SHEET) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEETML_TEMPLATE) ||
+ aContentType.EqualsLiteral(
+ APPLICATION_VND_WORDPROCESSINGML_DOCUMENT) ||
+ aContentType.EqualsLiteral(
+ APPLICATION_VND_WORDPROCESSINGML_TEMPLATE) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATION_OPENXML) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATION_OPENXMLM) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEET_OPENXML) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_WORDPROSSING_OPENXML) ||
+ aContentType.EqualsLiteral(APPLICATION_GZIP) ||
+ aContentType.EqualsLiteral(APPLICATION_XPROTOBUF) ||
+ aContentType.EqualsLiteral(APPLICATION_XPROTOBUFFER) ||
+ aContentType.EqualsLiteral(APPLICATION_ZIP) ||
+ aContentType.EqualsLiteral(AUDIO_MPEG_URL) ||
+ aContentType.EqualsLiteral(MULTIPART_BYTERANGES) ||
+ aContentType.EqualsLiteral(MULTIPART_SIGNED) ||
+ aContentType.EqualsLiteral(TEXT_EVENT_STREAM) ||
+ aContentType.EqualsLiteral(TEXT_CSV) ||
+ aContentType.EqualsLiteral(TEXT_VTT) ||
+ aContentType.EqualsLiteral(APPLICATION_DASH_XML);
+}
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ const nsACString& aContentType, uint16_t aStatus, bool aNoSniff) {
+ if (aContentType.IsEmpty()) {
+ return OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF;
+ }
+
+ if (IsOpaqueSafeListedMIMEType(aContentType)) {
+ return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED;
+ }
+
+ // For some MIME types we deviate from spec and allow when we ideally
+ // shouldn't. These are returnened before any blocking takes place.
+ if (IsOpaqueSafeListedSpecBreakingMIMEType(aContentType, aNoSniff)) {
+ return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING;
+ }
+
+ if (IsOpaqueBlockListedNeverSniffedMIMEType(aContentType)) {
+ return OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED;
+ }
+
+ if (aStatus == 206 && IsOpaqueBlockListedMIMEType(aContentType)) {
+ return OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED;
+ }
+
+ nsAutoCString contentTypeOptionsHeader;
+ if (aNoSniff && (IsOpaqueBlockListedMIMEType(aContentType) ||
+ aContentType.EqualsLiteral(TEXT_PLAIN))) {
+ return OpaqueResponseBlockedReason::
+ BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN;
+ }
+
+ return OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF;
+}
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ nsHttpResponseHead& aResponseHead) {
+ nsAutoCString contentType;
+ aResponseHead.ContentType(contentType);
+
+ nsAutoCString contentTypeOptionsHeader;
+ bool nosniff =
+ aResponseHead.GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
+ contentTypeOptionsHeader.EqualsIgnoreCase("nosniff");
+
+ return GetOpaqueResponseBlockedReason(contentType, aResponseHead.Status(),
+ nosniff);
+}
+
+Result<std::tuple<int64_t, int64_t, int64_t>, nsresult>
+ParseContentRangeHeaderString(const nsAutoCString& aRangeStr) {
+ // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
+ const int32_t spacePos = aRangeStr.Find(" "_ns);
+ const int32_t dashPos = aRangeStr.Find("-"_ns, spacePos);
+ const int32_t slashPos = aRangeStr.Find("/"_ns, dashPos);
+
+ nsAutoCString rangeStartText;
+ aRangeStr.Mid(rangeStartText, spacePos + 1, dashPos - (spacePos + 1));
+
+ nsresult rv;
+ const int64_t rangeStart = rangeStartText.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ if (0 > rangeStart) {
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ nsAutoCString rangeEndText;
+ aRangeStr.Mid(rangeEndText, dashPos + 1, slashPos - (dashPos + 1));
+ const int64_t rangeEnd = rangeEndText.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ if (rangeStart > rangeEnd) {
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ nsAutoCString rangeTotalText;
+ aRangeStr.Right(rangeTotalText, aRangeStr.Length() - (slashPos + 1));
+ if (rangeTotalText[0] == '*') {
+ return std::make_tuple(rangeStart, rangeEnd, (int64_t)-1);
+ }
+
+ const int64_t rangeTotal = rangeTotalText.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ if (rangeEnd >= rangeTotal) {
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ return std::make_tuple(rangeStart, rangeEnd, rangeTotal);
+}
+
+bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead) {
+ MOZ_ASSERT(aResponseHead.Status() == 206);
+
+ nsAutoCString contentRange;
+ Unused << aResponseHead.GetHeader(nsHttp::Content_Range, contentRange);
+
+ auto rangeOrErr = ParseContentRangeHeaderString(contentRange);
+ if (rangeOrErr.isErr()) {
+ return false;
+ }
+
+ const int64_t responseFirstBytePos = std::get<0>(rangeOrErr.unwrap());
+ return responseFirstBytePos == 0;
+}
+
+LogModule* GetORBLog() { return gORBLog; }
+
+OpaqueResponseFilter::OpaqueResponseFilter(nsIStreamListener* aNext)
+ : mNext(aNext) {
+ LOGORB();
+}
+
+NS_IMETHODIMP
+OpaqueResponseFilter::OnStartRequest(nsIRequest* aRequest) {
+ LOGORB();
+ nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(httpBaseChannel);
+
+ nsHttpResponseHead* responseHead = httpBaseChannel->GetResponseHead();
+
+ if (responseHead) {
+ // Filtered opaque responses doesn't need headers, so we just drop them.
+ responseHead->ClearHeaders();
+ }
+
+ mNext->OnStartRequest(aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OpaqueResponseFilter::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOGORB();
+ uint32_t result;
+ // No data for filtered opaque responses should reach the content process, so
+ // we just discard them.
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
+ &result);
+}
+
+NS_IMETHODIMP
+OpaqueResponseFilter::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ LOGORB();
+ mNext->OnStopRequest(aRequest, aStatusCode);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(OpaqueResponseFilter, nsIStreamListener, nsIRequestObserver)
+
+OpaqueResponseBlocker::OpaqueResponseBlocker(nsIStreamListener* aNext,
+ HttpBaseChannel* aChannel,
+ const nsCString& aContentType,
+ bool aNoSniff)
+ : mNext(aNext), mContentType(aContentType), mNoSniff(aNoSniff) {
+ // Storing aChannel as a member is tricky as aChannel owns us and it's
+ // hard to ensure aChannel is alive when we about to use it without
+ // creating a cycle. This is all doable but need some extra efforts.
+ //
+ // So we are just passing aChannel from the caller when we need to use it.
+ MOZ_ASSERT(aChannel);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gORBLog, LogLevel::Debug))) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ LOGORB(" channel=%p, uri=%s", aChannel, uri->GetSpecOrDefault().get());
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(aChannel->CachedOpaqueResponseBlockingPref());
+}
+
+NS_IMETHODIMP
+OpaqueResponseBlocker::OnStartRequest(nsIRequest* aRequest) {
+ LOGORB();
+
+ if (mState == State::Sniffing) {
+ Unused << EnsureOpaqueResponseIsAllowedAfterSniff(aRequest);
+ }
+
+ // mState will remain State::Sniffing if we need to wait
+ // for JS validator to make a decision.
+ //
+ // When the state is Sniffing, we can't call mNext->OnStartRequest
+ // because fetch requests need the cancellation to be done
+ // before its FetchDriver::OnStartRequest is called, otherwise it'll
+ // resolve the promise regardless the decision of JS validator.
+ if (mState != State::Sniffing) {
+ nsresult rv = mNext->OnStartRequest(aRequest);
+ return NS_SUCCEEDED(mStatus) ? rv : mStatus;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OpaqueResponseBlocker::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ LOGORB();
+
+ nsresult statusForStop = aStatusCode;
+
+ if (mState == State::Blocked && NS_FAILED(mStatus)) {
+ statusForStop = mStatus;
+ }
+
+ if (mState == State::Sniffing) {
+ // It is the call to JSValidatorParent::OnStopRequest that will trigger the
+ // JS parser.
+ mStartOfJavaScriptValidation = TimeStamp::Now();
+
+ MOZ_ASSERT(mJSValidator);
+ mPendingOnStopRequestStatus = Some(aStatusCode);
+ mJSValidator->OnStopRequest(aStatusCode, *aRequest);
+ return NS_OK;
+ }
+
+ return mNext->OnStopRequest(aRequest, statusForStop);
+}
+
+NS_IMETHODIMP
+OpaqueResponseBlocker::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOGORB();
+
+ if (mState == State::Allowed) {
+ return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+ }
+
+ if (mState == State::Blocked) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mState == State::Sniffing);
+
+ nsCString data;
+ if (!data.SetLength(aCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t read;
+ nsresult rv = aInputStream->Read(data.BeginWriting(), aCount, &read);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_ASSERT(mJSValidator);
+
+ mJSValidator->OnDataAvailable(data);
+
+ return NS_OK;
+}
+
+nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
+ nsIRequest* aRequest) {
+ nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(httpBaseChannel);
+
+ // The `AfterSniff` check shouldn't be run when
+ // 1. We have made a decision already
+ // 2. The JS validator is running, so we should wait
+ // for its result.
+ if (mState != State::Sniffing || mJSValidator) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+
+ nsresult rv =
+ httpBaseChannel->GetLoadInfo(getter_AddRefs<nsILoadInfo>(loadInfo));
+ if (NS_FAILED(rv)) {
+ LOGORB("Failed to get LoadInfo");
+ BlockResponse(httpBaseChannel, rv);
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = httpBaseChannel->GetURI(getter_AddRefs<nsIURI>(uri));
+ if (NS_FAILED(rv)) {
+ LOGORB("Failed to get uri");
+ BlockResponse(httpBaseChannel, rv);
+ return rv;
+ }
+
+ switch (httpBaseChannel->PerformOpaqueResponseSafelistCheckAfterSniff(
+ mContentType, mNoSniff)) {
+ case OpaqueResponse::Block:
+ BlockResponse(httpBaseChannel, NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ case OpaqueResponse::Allow:
+ AllowResponse();
+ return NS_OK;
+ case OpaqueResponse::Sniff:
+ case OpaqueResponse::SniffCompressed:
+ break;
+ }
+
+ MOZ_ASSERT(mState == State::Sniffing);
+ return ValidateJavaScript(httpBaseChannel, uri, loadInfo);
+}
+
+OpaqueResponse
+OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
+ HttpBaseChannel* aChannel, bool aAllow) {
+ if (aAllow) {
+ return OpaqueResponse::Allow;
+ }
+
+ return aChannel->BlockOrFilterOpaqueResponse(
+ this, u"Javascript validation failed"_ns,
+ OpaqueResponseBlockedTelemetryReason::JS_VALIDATION_FAILED,
+ "Javascript validation failed");
+}
+
+static void RecordTelemetry(const TimeStamp& aStartOfValidation,
+ const TimeStamp& aStartOfJavaScriptValidation,
+ OpaqueResponseBlocker::ValidatorResult aResult) {
+ using ValidatorResult = OpaqueResponseBlocker::ValidatorResult;
+ MOZ_DIAGNOSTIC_ASSERT(aStartOfValidation);
+
+ auto key = [aResult]() {
+ switch (aResult) {
+ case ValidatorResult::JavaScript:
+ return "javascript"_ns;
+ case ValidatorResult::JSON:
+ return "json"_ns;
+ case ValidatorResult::Other:
+ return "other"_ns;
+ case ValidatorResult::Failure:
+ return "failure"_ns;
+ }
+ MOZ_ASSERT_UNREACHABLE("Switch statement should be saturated");
+ return "failure"_ns;
+ }();
+
+ TimeStamp now = TimeStamp::Now();
+ PROFILER_MARKER_TEXT(
+ "ORB safelist check", NETWORK,
+ MarkerTiming::Interval(aStartOfValidation, aStartOfJavaScriptValidation),
+ nsPrintfCString("Receive data for validation (%s)", key.get()));
+
+ PROFILER_MARKER_TEXT(
+ "ORB safelist check", NETWORK,
+ MarkerTiming::Interval(aStartOfJavaScriptValidation, now),
+ nsPrintfCString("JS Validation (%s)", key.get()));
+
+ Telemetry::AccumulateTimeDelta(Telemetry::ORB_RECEIVE_DATA_FOR_VALIDATION_MS,
+ key, aStartOfValidation,
+ aStartOfJavaScriptValidation);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::ORB_JAVASCRIPT_VALIDATION_MS, key,
+ aStartOfJavaScriptValidation, now);
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::OpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::OpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel,
+ nsIURI* aURI,
+ nsILoadInfo* aLoadInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(aChannel);
+ MOZ_ASSERT(aURI && aLoadInfo);
+
+ if (!StaticPrefs::browser_opaqueResponseBlocking_javascriptValidator()) {
+ LOGORB("Allowed: JS Validator is disabled");
+ AllowResponse();
+ return NS_OK;
+ }
+
+ int64_t contentLength;
+ nsresult rv = aChannel->GetContentLength(&contentLength);
+ if (NS_FAILED(rv)) {
+ LOGORB("Blocked: No Content Length");
+ BlockResponse(aChannel, rv);
+ return rv;
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::OPAQUE_RESPONSE_BLOCKING_JAVASCRIPT_VALIDATION_COUNT,
+ 1);
+
+ LOGORB("Send %s to the validator", aURI->GetSpecOrDefault().get());
+ // https://whatpr.org/fetch/1442.html#orb-algorithm, step 15
+ mJSValidator = dom::JSValidatorParent::Create();
+ mJSValidator->IsOpaqueResponseAllowed(
+ [self = RefPtr{this}, channel = nsCOMPtr{aChannel}, uri = nsCOMPtr{aURI},
+ loadInfo = nsCOMPtr{aLoadInfo}, startOfValidation = TimeStamp::Now()](
+ Maybe<ipc::Shmem> aSharedData, ValidatorResult aResult) {
+ MOZ_LOG(gORBLog, LogLevel::Debug,
+ ("JSValidator resolved for %s with %s",
+ uri->GetSpecOrDefault().get(),
+ aSharedData.isSome() ? "true" : "false"));
+ bool allowed = aResult == ValidatorResult::JavaScript;
+ switch (self->EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
+ channel, allowed)) {
+ case OpaqueResponse::Allow:
+ // It's possible that the JS validation failed for this request,
+ // however we decided that we need to filter the response instead
+ // of blocking. So we set allowed to true manually when that's the
+ // case.
+ allowed = true;
+ self->AllowResponse();
+ break;
+ case OpaqueResponse::Block:
+ // We'll filter the data out later
+ self->AllowResponse();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "We should only ever have Allow or Block here.");
+ allowed = false;
+ self->BlockResponse(channel, NS_ERROR_FAILURE);
+ break;
+ }
+
+ self->ResolveAndProcessData(channel, allowed, aSharedData);
+ if (aSharedData.isSome()) {
+ self->mJSValidator->DeallocShmem(aSharedData.ref());
+ }
+
+ RecordTelemetry(startOfValidation, self->mStartOfJavaScriptValidation,
+ aResult);
+
+ Unused << dom::PJSValidatorParent::Send__delete__(self->mJSValidator);
+ self->mJSValidator = nullptr;
+ });
+
+ return NS_OK;
+}
+
+bool OpaqueResponseBlocker::IsSniffing() const {
+ return mState == State::Sniffing;
+}
+
+void OpaqueResponseBlocker::AllowResponse() {
+ LOGORB("Sniffer is done, allow response, this=%p", this);
+ MOZ_ASSERT(mState == State::Sniffing);
+ mState = State::Allowed;
+}
+
+void OpaqueResponseBlocker::BlockResponse(HttpBaseChannel* aChannel,
+ nsresult aStatus) {
+ LOGORB("Sniffer is done, block response, this=%p", this);
+ MOZ_ASSERT(mState == State::Sniffing);
+ mState = State::Blocked;
+ mStatus = aStatus;
+ aChannel->SetChannelBlockedByOpaqueResponse();
+ aChannel->CancelWithReason(mStatus,
+ "OpaqueResponseBlocker::BlockResponse"_ns);
+}
+
+void OpaqueResponseBlocker::FilterResponse() {
+ MOZ_ASSERT(mState == State::Sniffing);
+
+ if (mShouldFilter) {
+ return;
+ }
+
+ mShouldFilter = true;
+
+ mNext = new OpaqueResponseFilter(mNext);
+}
+
+void OpaqueResponseBlocker::ResolveAndProcessData(
+ HttpBaseChannel* aChannel, bool aAllowed, Maybe<ipc::Shmem>& aSharedData) {
+ if (!aAllowed) {
+ // OpaqueResponseFilter allows us to filter the headers
+ mNext = new OpaqueResponseFilter(mNext);
+ }
+
+ nsresult rv = OnStartRequest(aChannel);
+
+ if (!aAllowed || NS_FAILED(rv)) {
+ MOZ_ASSERT_IF(!aAllowed, mState == State::Allowed);
+ // No need to call OnDataAvailable because
+ // 1. The input stream is consumed by
+ // OpaqueResponseBlocker::OnDataAvailable already
+ // 2. We don't want to pass any data over
+ MaybeRunOnStopRequest(aChannel);
+ return;
+ }
+
+ MOZ_ASSERT(mState == State::Allowed);
+
+ if (aSharedData.isNothing()) {
+ MaybeRunOnStopRequest(aChannel);
+ return;
+ }
+
+ const ipc::Shmem& mem = aSharedData.ref();
+ nsCOMPtr<nsIInputStream> input;
+ rv = NS_NewByteInputStream(getter_AddRefs(input),
+ Span(mem.get<char>(), mem.Size<char>()),
+ NS_ASSIGNMENT_DEPEND);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ BlockResponse(aChannel, rv);
+ MaybeRunOnStopRequest(aChannel);
+ return;
+ }
+
+ // When this line reaches, the state is either State::Allowed or
+ // State::Blocked. The OnDataAvailable call will either call
+ // the next listener or reject the request.
+ OnDataAvailable(aChannel, input, 0, mem.Size<char>());
+
+ MaybeRunOnStopRequest(aChannel);
+}
+
+void OpaqueResponseBlocker::MaybeRunOnStopRequest(HttpBaseChannel* aChannel) {
+ MOZ_ASSERT(mState != State::Sniffing);
+ if (mPendingOnStopRequestStatus.isSome()) {
+ OnStopRequest(aChannel, mPendingOnStopRequestStatus.value());
+ }
+}
+
+NS_IMPL_ISUPPORTS(OpaqueResponseBlocker, nsIStreamListener, nsIRequestObserver)
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/OpaqueResponseUtils.h b/netwerk/protocol/http/OpaqueResponseUtils.h
new file mode 100644
index 0000000000..7b37095b01
--- /dev/null
+++ b/netwerk/protocol/http/OpaqueResponseUtils.h
@@ -0,0 +1,216 @@
+/* -*- 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_OpaqueResponseUtils_h
+#define mozilla_net_OpaqueResponseUtils_h
+
+#include "ipc/EnumSerializer.h"
+#include "mozilla/TimeStamp.h"
+#include "nsIContentPolicy.h"
+#include "nsIEncodedChannel.h"
+#include "nsIStreamListener.h"
+#include "nsUnknownDecoder.h"
+#include "nsMimeTypes.h"
+#include "nsIHttpChannel.h"
+
+#include "mozilla/Variant.h"
+#include "mozilla/Logging.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIContentSniffer;
+
+namespace mozilla::dom {
+class JSValidatorParent;
+}
+
+namespace mozilla::ipc {
+class Shmem;
+}
+
+namespace mozilla::net {
+
+class HttpBaseChannel;
+class nsHttpResponseHead;
+
+enum class OpaqueResponseBlockedReason : uint32_t {
+ ALLOWED_SAFE_LISTED,
+ ALLOWED_SAFE_LISTED_SPEC_BREAKING,
+ BLOCKED_BLOCKLISTED_NEVER_SNIFFED,
+ BLOCKED_206_AND_BLOCKLISTED,
+ BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN,
+ BLOCKED_SHOULD_SNIFF
+};
+
+enum class OpaqueResponseBlockedTelemetryReason : uint32_t {
+ MIME_NEVER_SNIFFED,
+ RESP_206_BLCLISTED,
+ NOSNIFF_BLC_OR_TEXTP,
+ RESP_206_NO_FIRST,
+ AFTER_SNIFF_MEDIA,
+ AFTER_SNIFF_NOSNIFF,
+ AFTER_SNIFF_STA_CODE,
+ AFTER_SNIFF_CT_FAIL,
+ MEDIA_NOT_INITIAL,
+ MEDIA_INCORRECT_RESP,
+ JS_VALIDATION_FAILED
+};
+
+enum class OpaqueResponse { Block, Allow, SniffCompressed, Sniff };
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ const nsACString& aContentType, uint16_t aStatus, bool aNoSniff);
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ nsHttpResponseHead& aResponseHead);
+
+// Returns a tuple of (rangeStart, rangeEnd, rangeTotal) from the input range
+// header string if succeed.
+Result<std::tuple<int64_t, int64_t, int64_t>, nsresult>
+ParseContentRangeHeaderString(const nsAutoCString& aRangeStr);
+
+bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead);
+
+LogModule* GetORBLog();
+
+// Helper class to filter data for opaque responses destined for `Window.fetch`.
+// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque.
+class OpaqueResponseFilter final : public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER;
+
+ explicit OpaqueResponseFilter(nsIStreamListener* aNext);
+
+ private:
+ virtual ~OpaqueResponseFilter() = default;
+
+ nsCOMPtr<nsIStreamListener> mNext;
+};
+
+class OpaqueResponseBlocker final : public nsIStreamListener {
+ enum class State { Sniffing, Allowed, Blocked };
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER;
+
+ OpaqueResponseBlocker(nsIStreamListener* aNext, HttpBaseChannel* aChannel,
+ const nsCString& aContentType, bool aNoSniff);
+
+ bool IsSniffing() const;
+ void AllowResponse();
+ void BlockResponse(HttpBaseChannel* aChannel, nsresult aStatus);
+ void FilterResponse();
+
+ nsresult EnsureOpaqueResponseIsAllowedAfterSniff(nsIRequest* aRequest);
+
+ OpaqueResponse EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
+ HttpBaseChannel* aChannel, bool aAllow);
+
+ // The four possible results for validation. `JavaScript` and `JSON` are
+ // self-explanatory. `JavaScript` is the only successful result, in the sense
+ // that it will allow the opaque response, whereas `JSON` will block. `Other`
+ // is the case where validation fails, because the response is neither
+ // `JavaScript` nor `JSON`, but the framework itself works as intended.
+ // `Failure` implies that something has gone wrong, such as allocation, etc.
+ enum class ValidatorResult : uint32_t { JavaScript, JSON, Other, Failure };
+
+ private:
+ virtual ~OpaqueResponseBlocker() = default;
+
+ nsresult ValidateJavaScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsILoadInfo* aLoadInfo);
+
+ void ResolveAndProcessData(HttpBaseChannel* aChannel, bool aAllowed,
+ Maybe<ipc::Shmem>& aSharedData);
+
+ void MaybeRunOnStopRequest(HttpBaseChannel* aChannel);
+
+ nsCOMPtr<nsIStreamListener> mNext;
+
+ const nsCString mContentType;
+ const bool mNoSniff;
+ bool mShouldFilter = false;
+
+ State mState = State::Sniffing;
+ nsresult mStatus = NS_OK;
+
+ TimeStamp mStartOfJavaScriptValidation;
+
+ RefPtr<dom::JSValidatorParent> mJSValidator;
+
+ Maybe<nsresult> mPendingOnStopRequestStatus{Nothing()};
+};
+
+class nsCompressedAudioVideoImageDetector : public nsUnknownDecoder {
+ const std::function<void(void*, const uint8_t*, uint32_t)> mCallback;
+
+ public:
+ nsCompressedAudioVideoImageDetector(
+ nsIStreamListener* aListener,
+ std::function<void(void*, const uint8_t*, uint32_t)>&& aCallback)
+ : nsUnknownDecoder(aListener), mCallback(aCallback) {}
+
+ protected:
+ virtual void DetermineContentType(nsIRequest* aRequest) override {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ return;
+ }
+
+ const char* testData = mBuffer;
+ uint32_t testDataLen = mBufferLen;
+ // Check if data are compressed.
+ nsAutoCString decodedData;
+
+ // ConvertEncodedData is always called only on a single thread for each
+ // instance of an object.
+ nsresult rv = ConvertEncodedData(aRequest, mBuffer, mBufferLen);
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock lock(mMutex);
+ decodedData = mDecodedData;
+ }
+ if (!decodedData.IsEmpty()) {
+ testData = decodedData.get();
+ testDataLen = std::min<uint32_t>(decodedData.Length(), 512u);
+ }
+
+ mCallback(httpChannel, (const uint8_t*)testData, testDataLen);
+
+ nsAutoCString contentType;
+ rv = httpChannel->GetContentType(contentType);
+
+ MutexAutoLock lock(mMutex);
+ if (!contentType.IsEmpty()) {
+ mContentType = contentType;
+ } else {
+ mContentType = UNKNOWN_CONTENT_TYPE;
+ }
+
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel) {
+ encodedChannel->SetHasContentDecompressed(true);
+ }
+ }
+};
+} // namespace mozilla::net
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::net::OpaqueResponseBlocker::ValidatorResult>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::net::OpaqueResponseBlocker::ValidatorResult,
+ mozilla::net::OpaqueResponseBlocker::ValidatorResult::JavaScript,
+ mozilla::net::OpaqueResponseBlocker::ValidatorResult::Failure> {};
+} // namespace IPC
+
+#endif // mozilla_net_OpaqueResponseUtils_h
diff --git a/netwerk/protocol/http/PAltDataOutputStream.ipdl b/netwerk/protocol/http/PAltDataOutputStream.ipdl
new file mode 100644
index 0000000000..9fbf589e95
--- /dev/null
+++ b/netwerk/protocol/http/PAltDataOutputStream.ipdl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc]
+protocol PAltDataOutputStream
+{
+ manager PNecko;
+
+parent:
+ // Sends data from the child to the parent that will be written to the cache.
+ async WriteData(nsCString data);
+ // Signals that writing to the output stream is done.
+ async Close(nsresult status);
+
+ async __delete__();
+
+child:
+ // The parent calls this method to signal that an error has ocurred.
+ // This may mean that opening the output stream has failed or that writing to
+ // the stream has returned an error.
+ async Error(nsresult err);
+
+both:
+ // After sending this message, the parent will respond by sending DeleteSelf
+ // back to the child, after which it is guaranteed to not send any more IPC
+ // messages.
+ // When receiving this message, the child will send __delete__ tearing down
+ // the IPC channel.
+ async DeleteSelf();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PAltService.ipdl b/netwerk/protocol/http/PAltService.ipdl
new file mode 100644
index 0000000000..c4b4f494b5
--- /dev/null
+++ b/netwerk/protocol/http/PAltService.ipdl
@@ -0,0 +1,38 @@
+/* -*- 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 protocol PSocketProcess;
+include NeckoChannelParams;
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+
+namespace mozilla {
+namespace net {
+
+protocol PAltService
+{
+ manager PSocketProcess;
+
+parent:
+ async ClearHostMapping(nsCString host, int32_t port,
+ OriginAttributes originAttributes);
+
+ async ProcessHeader(nsCString buf,
+ nsCString originScheme,
+ nsCString originHost,
+ int32_t originPort,
+ nsCString username,
+ bool privateBrowsing,
+ ProxyInfoCloneArgs[] proxyInfo,
+ uint32_t caps,
+ OriginAttributes originAttributes);
+
+child:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PAltSvcTransaction.ipdl b/netwerk/protocol/http/PAltSvcTransaction.ipdl
new file mode 100644
index 0000000000..001b53a4c7
--- /dev/null
+++ b/netwerk/protocol/http/PAltSvcTransaction.ipdl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+
+namespace mozilla {
+namespace net {
+
+protocol PAltSvcTransaction
+{
+ manager PSocketProcess;
+
+parent:
+ async __delete__(bool aValidateResult);
+ async OnTransactionClose(bool aValidateResult);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PBackgroundDataBridge.ipdl b/netwerk/protocol/http/PBackgroundDataBridge.ipdl
new file mode 100644
index 0000000000..d5a3be4fab
--- /dev/null
+++ b/netwerk/protocol/http/PBackgroundDataBridge.ipdl
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include HttpChannelParams;
+include NeckoChannelParams;
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[ParentProc=Socket, ChildProc=Content]
+async protocol PBackgroundDataBridge
+{
+child:
+ async OnTransportAndData(uint64_t offset,
+ uint32_t count,
+ nsCString data,
+ TimeStamp onDataAvailableStart);
+
+ async OnStopRequest(nsresult aStatus,
+ ResourceTimingStructArgs timing,
+ TimeStamp lastActiveTabOptimization,
+ nsHttpHeaderArray responseTrailers,
+ TimeStamp onStopRequestStart);
+
+both:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpBackgroundChannel.ipdl b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
new file mode 100644
index 0000000000..1f930992a9
--- /dev/null
+++ b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+include protocol PStreamFilter;
+include NeckoChannelParams;
+include HttpChannelParams;
+include PURLClassifierInfo;
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+async protocol PHttpBackgroundChannel
+{
+ manager PBackground;
+
+child:
+ async OnStartRequest(nsHttpResponseHead responseHead,
+ bool useResponseHead,
+ nsHttpHeaderArray requestHeaders,
+ HttpChannelOnStartRequestArgs args,
+ HttpChannelAltDataStream altData,
+ TimeStamp onStartRequestStart);
+
+ // Combines a single OnDataAvailable and its associated OnProgress &
+ // OnStatus calls into one IPDL message
+ async OnTransportAndData(nsresult channelStatus,
+ nsresult transportStatus,
+ uint64_t offset,
+ uint32_t count,
+ nsCString data,
+ bool dataFromSocketProcess,
+ TimeStamp onDataAvailableStart);
+
+
+ async OnStopRequest(nsresult channelStatus,
+ ResourceTimingStructArgs timing,
+ TimeStamp lastActiveTabOptimization,
+ nsHttpHeaderArray responseTrailers,
+ ConsoleReportCollected[] consoleReport,
+ bool fromSocketProcess,
+ TimeStamp onStopRequestStart);
+
+ async OnConsoleReport(ConsoleReportCollected[] consoleReport);
+
+ async OnProgress(int64_t progress, int64_t progressMax);
+
+ async OnStatus(nsresult status);
+
+ // Forwards nsIMultiPartChannelListener::onAfterLastPart. Make sure we've
+ // disconnected our listeners, since we might not have on the last
+ // OnStopRequest.
+ async OnAfterLastPart(nsresult aStatus);
+
+ // Tell the child that the resource being loaded has been classified.
+ async NotifyClassificationFlags(uint32_t aClassificationFlags, bool aIsThirdParty);
+
+ // Tell the child information of matched URL againts SafeBrowsing list
+ async SetClassifierMatchedInfo(ClassifierInfo info);
+
+ // Tell the child information of matched URL againts SafeBrowsing tracking list
+ async SetClassifierMatchedTrackingInfo(ClassifierInfo info);
+
+ async AttachStreamFilter(Endpoint<PStreamFilterParent> aEndpoint);
+
+ async DetachStreamFilters();
+
+ async __delete__();
+
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl
new file mode 100644
index 0000000000..76243a0635
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include InputStreamParams;
+include PBackgroundSharedTypes;
+include NeckoChannelParams;
+include IPCServiceWorkerDescriptor;
+include IPCStream;
+include HttpChannelParams;
+
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/net/ClassOfService.h";
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using mozilla::net::ClassOfService from "mozilla/net/ClassOfService.h";
+[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[ChildImpl=virtual, ParentImpl=virtual]
+protocol PHttpChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async SetClassOfService(ClassOfService cos);
+
+ async Suspend();
+ async Resume();
+
+ async Cancel(nsresult status, uint32_t requestBlockingReason,
+ nsCString aReason, nsCString? logString);
+
+ // Reports approval/veto of redirect by child process redirect observers
+ async Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders,
+ uint32_t sourceRequestBlockingReason,
+ ChildLoadInfoForwarderArgs? targetLoadInfoForwarder,
+ uint32_t loadFlags,
+ nullable nsIReferrerInfo referrerInfo,
+ nullable nsIURI apiRedirectTo,
+ CorsPreflightArgs? corsPreflightArgs);
+
+ // For document loads we keep this protocol open after child's
+ // OnStopRequest, and send this msg (instead of __delete__) to allow
+ // partial cleanup on parent.
+ async DocumentChannelCleanup(bool clearCacheEntry);
+
+ // Child has detected a CORS check failure, so needs to tell the parent
+ // to remove any matching entry from the CORS preflight cache.
+ async RemoveCorsPreflightCacheEntry(nullable nsIURI uri,
+ PrincipalInfo requestingPrincipal,
+ OriginAttributes originAttributes);
+
+ // Send cookies to the parent for a given channel. Compared to PCookieService
+ // SetCookies this method allows the parent to determine which BrowsingContext
+ // the request was sent for.
+ async SetCookies(nsCString baseDomain,
+ OriginAttributes attrs,
+ nullable nsIURI host,
+ bool fromHttp,
+ CookieStruct[] cookies);
+
+ // After receiving this message, the parent calls SendDeleteSelf, and makes
+ // sure not to send any more messages after that.
+ async DeletingChannel();
+
+ // Called to get the input stream when altData was delivered.
+ async OpenOriginalCacheInputStream();
+
+ // Tell the parent the amount bytes read by child for the e10s back pressure
+ // flow control
+ async BytesRead(int32_t count);
+
+ async __delete__();
+
+child:
+ // Used to cancel child channel if we hit errors during creating and
+ // AsyncOpen of nsHttpChannel on the parent.
+ async FailedAsyncOpen(nsresult status);
+
+ // OnStartRequest is sent over PHttpBackgroundChannel. However, sometime we
+ // need to wait for some PContent IPCs, e.g., permission, cookies. Those IPC
+ // are sent just before the background thread OnStartRequest, which is racy.
+ // Therefore, need one main thread IPC event for synchronizing the event
+ // sequence.
+ async OnStartRequestSent();
+
+ // Called to initiate content channel redirect, starts talking to sinks
+ // on the content process and reports result via Redirect2Verify above
+ async Redirect1Begin(uint32_t registrarId,
+ nullable nsIURI newOriginalUri,
+ uint32_t newLoadFlags,
+ uint32_t redirectFlags,
+ ParentLoadInfoForwarderArgs loadInfoForwarder,
+ nsHttpResponseHead responseHead,
+ nullable nsITransportSecurityInfo securityInfo,
+ uint64_t channelId,
+ NetAddr oldPeerAddr,
+ ResourceTimingStructArgs timing);
+
+ // Called if redirect successful so that child can complete setup.
+ async Redirect3Complete();
+
+ // Called if the redirect failed/was vetoed
+ async RedirectFailed(nsresult status);
+
+ // Report a security message to the console associated with this
+ // channel.
+ async ReportSecurityMessage(nsString messageTag, nsString messageCategory);
+
+ // Tell child to delete channel (all IPDL deletes must be done from child to
+ // avoid races: see bug 591708).
+ async DeleteSelf();
+
+ // When CORS blocks the request in the parent process, it doesn't have the
+ // correct window ID, so send the message to the child for logging to the web
+ // console.
+ async LogBlockedCORSRequest(nsString message,
+ nsCString category,
+ bool isWarning);
+
+ async LogMimeTypeMismatch(nsCString messageName,
+ bool warning,
+ nsString url,
+ nsString contentType);
+
+ async OriginalCacheInputStreamAvailable(IPCStream? stream);
+
+
+both:
+ async SetPriority(int16_t priority);
+};
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannelParams.h b/netwerk/protocol/http/PHttpChannelParams.h
new file mode 100644
index 0000000000..8e2840db68
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannelParams.h
@@ -0,0 +1,290 @@
+/* -*- 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_PHttpChannelParams_h
+#define mozilla_net_PHttpChannelParams_h
+
+#define ALLOW_LATE_NSHTTP_H_INCLUDE 1
+#include "base/basictypes.h"
+
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+
+namespace mozilla {
+namespace net {
+
+struct RequestHeaderTuple {
+ nsCString mHeader;
+ nsCString mValue;
+ bool mMerge;
+ bool mEmpty;
+
+ bool operator==(const RequestHeaderTuple& other) const {
+ return mHeader.Equals(other.mHeader) && mValue.Equals(other.mValue) &&
+ mMerge == other.mMerge && mEmpty == other.mEmpty;
+ }
+};
+
+typedef CopyableTArray<RequestHeaderTuple> RequestHeaderTuples;
+
+} // namespace net
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::net::RequestHeaderTuple> {
+ typedef mozilla::net::RequestHeaderTuple paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mHeader);
+ WriteParam(aWriter, aParam.mValue);
+ WriteParam(aWriter, aParam.mMerge);
+ WriteParam(aWriter, aParam.mEmpty);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mHeader) ||
+ !ReadParam(aReader, &aResult->mValue) ||
+ !ReadParam(aReader, &aResult->mMerge) ||
+ !ReadParam(aReader, &aResult->mEmpty))
+ return false;
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpAtom> {
+ typedef mozilla::net::nsHttpAtom paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ // aParam.get() cannot be null.
+ MOZ_ASSERT(aParam.get(), "null nsHTTPAtom value");
+ nsAutoCString value(aParam.get());
+ WriteParam(aWriter, value);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ nsAutoCString value;
+ if (!ReadParam(aReader, &value)) return false;
+
+ *aResult = mozilla::net::nsHttp::ResolveAtom(value);
+ MOZ_ASSERT(aResult->get(), "atom table not initialized");
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpHeaderArray::nsEntry> {
+ typedef mozilla::net::nsHttpHeaderArray::nsEntry paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ if (aParam.headerNameOriginal.IsEmpty()) {
+ WriteParam(aWriter, aParam.header);
+ } else {
+ WriteParam(aWriter, aParam.headerNameOriginal);
+ }
+ WriteParam(aWriter, aParam.value);
+ switch (aParam.variety) {
+ case mozilla::net::nsHttpHeaderArray::eVarietyUnknown:
+ WriteParam(aWriter, (uint8_t)0);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride:
+ WriteParam(aWriter, (uint8_t)1);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault:
+ WriteParam(aWriter, (uint8_t)2);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestEnforceDefault:
+ WriteParam(aWriter, (uint8_t)3);
+ break;
+ case mozilla::net::nsHttpHeaderArray::
+ eVarietyResponseNetOriginalAndResponse:
+ WriteParam(aWriter, (uint8_t)4);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal:
+ WriteParam(aWriter, (uint8_t)5);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponse:
+ WriteParam(aWriter, (uint8_t)6);
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint8_t variety;
+ nsAutoCString header;
+ if (!ReadParam(aReader, &header) || !ReadParam(aReader, &aResult->value) ||
+ !ReadParam(aReader, &variety))
+ return false;
+
+ mozilla::net::nsHttpAtom atom = mozilla::net::nsHttp::ResolveAtom(header);
+ aResult->header = atom;
+ if (!header.Equals(atom.get())) {
+ aResult->headerNameOriginal = header;
+ }
+
+ switch (variety) {
+ case 0:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyUnknown;
+ break;
+ case 1:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride;
+ break;
+ case 2:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault;
+ break;
+ case 3:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyRequestEnforceDefault;
+ break;
+ case 4:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::
+ eVarietyResponseNetOriginalAndResponse;
+ break;
+ case 5:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal;
+ break;
+ case 6:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponse;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpHeaderArray> {
+ typedef mozilla::net::nsHttpHeaderArray paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ paramType& p = const_cast<paramType&>(aParam);
+
+ WriteParam(aWriter, p.mHeaders);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mHeaders)) return false;
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpRequestHead> {
+ typedef mozilla::net::nsHttpRequestHead paramType;
+
+ static void Write(MessageWriter* aWriter,
+ const paramType& aParam) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ aParam.Enter();
+ WriteParam(aWriter, aParam.mHeaders);
+ WriteParam(aWriter, aParam.mMethod);
+ WriteParam(aWriter, static_cast<uint32_t>(aParam.mVersion));
+ WriteParam(aWriter, aParam.mRequestURI);
+ WriteParam(aWriter, aParam.mPath);
+ WriteParam(aWriter, aParam.mOrigin);
+ WriteParam(aWriter, static_cast<uint8_t>(aParam.mParsedMethod));
+ WriteParam(aWriter, aParam.mHTTPS);
+ aParam.Exit();
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t version;
+ uint8_t method;
+ aResult->Enter();
+ if (!ReadParam(aReader, &aResult->mHeaders) ||
+ !ReadParam(aReader, &aResult->mMethod) ||
+ !ReadParam(aReader, &version) ||
+ !ReadParam(aReader, &aResult->mRequestURI) ||
+ !ReadParam(aReader, &aResult->mPath) ||
+ !ReadParam(aReader, &aResult->mOrigin) ||
+ !ReadParam(aReader, &method) || !ReadParam(aReader, &aResult->mHTTPS)) {
+ aResult->Exit();
+ return false;
+ }
+
+ aResult->mVersion = static_cast<mozilla::net::HttpVersion>(version);
+ aResult->mParsedMethod =
+ static_cast<mozilla::net::nsHttpRequestHead::ParsedMethodType>(method);
+ aResult->Exit();
+ return true;
+ }
+};
+
+// Note that the code below MUST be synchronized with the code in
+// nsHttpRequestHead's copy constructor.
+template <>
+struct ParamTraits<mozilla::net::nsHttpResponseHead> {
+ typedef mozilla::net::nsHttpResponseHead paramType;
+
+ static void Write(MessageWriter* aWriter,
+ const paramType& aParam) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ aParam.Enter();
+ WriteParam(aWriter, aParam.mHeaders);
+ WriteParam(aWriter, static_cast<uint32_t>(aParam.mVersion));
+ WriteParam(aWriter, aParam.mStatus);
+ WriteParam(aWriter, aParam.mStatusText);
+ WriteParam(aWriter, aParam.mContentLength);
+ WriteParam(aWriter, aParam.mContentType);
+ WriteParam(aWriter, aParam.mContentCharset);
+ WriteParam(aWriter, aParam.mHasCacheControl);
+ WriteParam(aWriter, aParam.mCacheControlPublic);
+ WriteParam(aWriter, aParam.mCacheControlPrivate);
+ WriteParam(aWriter, aParam.mCacheControlNoStore);
+ WriteParam(aWriter, aParam.mCacheControlNoCache);
+ WriteParam(aWriter, aParam.mCacheControlImmutable);
+ WriteParam(aWriter, aParam.mCacheControlStaleWhileRevalidateSet);
+ WriteParam(aWriter, aParam.mCacheControlStaleWhileRevalidate);
+ WriteParam(aWriter, aParam.mCacheControlMaxAgeSet);
+ WriteParam(aWriter, aParam.mCacheControlMaxAge);
+ WriteParam(aWriter, aParam.mPragmaNoCache);
+ aParam.Exit();
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t version;
+ aResult->Enter();
+ if (!ReadParam(aReader, &aResult->mHeaders) ||
+ !ReadParam(aReader, &version) ||
+ !ReadParam(aReader, &aResult->mStatus) ||
+ !ReadParam(aReader, &aResult->mStatusText) ||
+ !ReadParam(aReader, &aResult->mContentLength) ||
+ !ReadParam(aReader, &aResult->mContentType) ||
+ !ReadParam(aReader, &aResult->mContentCharset) ||
+ !ReadParam(aReader, &aResult->mHasCacheControl) ||
+ !ReadParam(aReader, &aResult->mCacheControlPublic) ||
+ !ReadParam(aReader, &aResult->mCacheControlPrivate) ||
+ !ReadParam(aReader, &aResult->mCacheControlNoStore) ||
+ !ReadParam(aReader, &aResult->mCacheControlNoCache) ||
+ !ReadParam(aReader, &aResult->mCacheControlImmutable) ||
+ !ReadParam(aReader, &aResult->mCacheControlStaleWhileRevalidateSet) ||
+ !ReadParam(aReader, &aResult->mCacheControlStaleWhileRevalidate) ||
+ !ReadParam(aReader, &aResult->mCacheControlMaxAgeSet) ||
+ !ReadParam(aReader, &aResult->mCacheControlMaxAge) ||
+ !ReadParam(aReader, &aResult->mPragmaNoCache)) {
+ aResult->Exit();
+ return false;
+ }
+
+ aResult->mVersion = static_cast<mozilla::net::HttpVersion>(version);
+ aResult->Exit();
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_PHttpChannelParams_h
diff --git a/netwerk/protocol/http/PHttpConnectionMgr.ipdl b/netwerk/protocol/http/PHttpConnectionMgr.ipdl
new file mode 100644
index 0000000000..dfd46bc3cb
--- /dev/null
+++ b/netwerk/protocol/http/PHttpConnectionMgr.ipdl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PAltSvcTransaction;
+include protocol PSocketProcess;
+include protocol PHttpTransaction;
+
+include NeckoChannelParams;
+
+include "mozilla/net/ClassOfService.h";
+
+using mozilla::net::ClassOfService from "mozilla/net/ClassOfService.h";
+
+namespace mozilla {
+namespace net {
+
+async protocol PHttpConnectionMgr
+{
+ manager PSocketProcess;
+
+child:
+ async __delete__();
+
+ async DoShiftReloadConnectionCleanupWithConnInfo(HttpConnectionInfoCloneArgs aArgs);
+ async UpdateCurrentBrowserId(uint64_t aId);
+ async AddTransaction(PHttpTransaction aTrans, int32_t aPriority);
+ async AddTransactionWithStickyConn(PHttpTransaction aTrans, int32_t aPriority,
+ PHttpTransaction aTransWithStickyConn);
+ async RescheduleTransaction(PHttpTransaction aTrans, int32_t aPriority);
+ async UpdateClassOfServiceOnTransaction(PHttpTransaction aTrans,
+ ClassOfService aClassOfService);
+ async CancelTransaction(PHttpTransaction aTrans, nsresult aReason);
+ async SpeculativeConnect(HttpConnectionInfoCloneArgs aConnInfo,
+ SpeculativeConnectionOverriderArgs? aOverriderArgs,
+ uint32_t aCaps, PAltSvcTransaction? aTrans,
+ bool aFetchHTTPSRR);
+ async StartWebSocketConnection(PHttpTransaction aTransWithStickyConn,
+ uint32_t aListenerId);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpTransaction.ipdl b/netwerk/protocol/http/PHttpTransaction.ipdl
new file mode 100644
index 0000000000..670dc296ce
--- /dev/null
+++ b/netwerk/protocol/http/PHttpTransaction.ipdl
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+include protocol PInputChannelThrottleQueue;
+
+include IPCStream;
+include NeckoChannelParams;
+
+include "mozilla/ipc/TransportSecurityInfoUtils.h";
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/net/ClassOfService.h";
+
+using class mozilla::net::nsHttpRequestHead from "nsHttpRequestHead.h";
+using class mozilla::net::nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using mozilla::net::ClassOfService from "mozilla/net/ClassOfService.h";
+[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
+using mozilla::net::TRRSkippedReason from "nsITRRSkipReason.h";
+using nsIRequest::TRRMode from "nsIRequest.h";
+
+namespace mozilla {
+namespace net {
+
+struct H2PushedStreamArg {
+ PHttpTransaction transWithPushedStream;
+ uint32_t pushedStreamId;
+};
+
+struct NetworkAddressArg {
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ bool resolvedByTRR;
+ TRRMode mode;
+ TRRSkippedReason trrSkipReason;
+ bool echConfigUsed;
+};
+
+protocol PHttpTransaction
+{
+ manager PSocketProcess;
+
+parent:
+ async OnStartRequest(nsresult status,
+ nsHttpResponseHead? responseHead,
+ nullable nsITransportSecurityInfo securityInfo,
+ bool proxyConnectFailed,
+ TimingStructArgs timings,
+ int32_t proxyConnectResponseCode,
+ uint8_t[] dataForSniffer,
+ nsCString? altSvcUsed,
+ bool dataToChildProcess,
+ bool restarted,
+ uint32_t HTTPSSVCReceivedStage,
+ bool supportsHttp3,
+ TRRMode trrMode,
+ TRRSkippedReason trrSkipReason,
+ uint32_t caps,
+ TimeStamp onStartRequestStart);
+ async OnTransportStatus(nsresult status,
+ int64_t progress,
+ int64_t progressMax,
+ NetworkAddressArg? networkAddressArg);
+ async OnDataAvailable(nsCString data,
+ uint64_t offset,
+ uint32_t count,
+ TimeStamp onDataAvailableStart);
+ async OnStopRequest(nsresult status,
+ bool responseIsComplete,
+ int64_t transferSize,
+ TimingStructArgs timings,
+ nsHttpHeaderArray? responseTrailers,
+ TransactionObserverResult? transactionObserverResult,
+ TimeStamp lastActiveTabOptimization,
+ HttpConnectionInfoCloneArgs connInfoArgs,
+ TimeStamp onStopRequestStart);
+ async OnInitFailed(nsresult status);
+ async OnH2PushStream(uint32_t pushedStreamId,
+ nsCString resourceUrl,
+ nsCString requestString);
+ async EarlyHint(nsCString linkHeader, nsCString referrerPolicy, nsCString cspHeader);
+
+child:
+ async __delete__();
+ async Init(uint32_t caps,
+ HttpConnectionInfoCloneArgs aArgs,
+ nsHttpRequestHead reqHeaders,
+ IPCStream? requestBody,
+ uint64_t reqContentLength,
+ bool reqBodyIncludesHeaders,
+ uint64_t topLevelOuterContentWindowId,
+ uint8_t httpTrafficCategory,
+ uint64_t requestContextID,
+ ClassOfService classOfService,
+ uint32_t initialRwin,
+ bool responseTimeoutEnabled,
+ uint64_t channelId,
+ bool hasTransactionObserver,
+ H2PushedStreamArg? pushedStreamArg,
+ PInputChannelThrottleQueue? throttleQueue,
+ bool aIsDocumentLoad,
+ TimeStamp aRedirectStart,
+ TimeStamp aRedirectEnd);
+
+ async CancelPump(nsresult status);
+ async SuspendPump();
+ async ResumePump();
+
+ async SetDNSWasRefreshed();
+ async DontReuseConnection();
+ async SetH2WSConnRefTaken();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PSpdyPush.h b/netwerk/protocol/http/PSpdyPush.h
new file mode 100644
index 0000000000..bea6d6be7c
--- /dev/null
+++ b/netwerk/protocol/http/PSpdyPush.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// SPDY Server Push
+
+/*
+ A pushed stream is put into a memory buffer (The SpdyPushTransactionBuffer)
+ and spooled there until a GET is made that can be matched up with it. At
+ that time we have two spdy streams - the GET (aka the sink) and the PUSH
+ (aka the source). Data is copied between those two streams for the lifetime
+ of the transaction. This is true even if the transaction buffer is empty,
+ partly complete, or totally loaded at the time the GET correspondence is made.
+
+ correspondence is done through a hash table of the full url, the spdy session,
+ and the load group. The load group is implicit because that's where the
+ hash is stored, the other items comprise the hash key.
+
+ Pushed streams are subject to aggressive flow control before they are matched
+ with a GET at which point flow control is effectively disabled to match the
+ client pull behavior.
+*/
+
+#ifndef mozilla_net_SpdyPush_Public_h
+#define mozilla_net_SpdyPush_Public_h
+
+#include "nsTHashMap.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace net {
+
+class Http2PushedStream;
+
+// One cache per load group
+class SpdyPushCache {
+ public:
+ // The cache holds only weak pointers - no references
+ SpdyPushCache() = default;
+ virtual ~SpdyPushCache();
+ [[nodiscard]] bool RegisterPushedStreamHttp2(const nsCString& key,
+ Http2PushedStream* stream);
+ Http2PushedStream* RemovePushedStreamHttp2(const nsCString& key);
+ Http2PushedStream* RemovePushedStreamHttp2ByID(const nsCString& key,
+ const uint32_t& streamID);
+
+ private:
+ nsTHashMap<nsCStringHashKey, Http2PushedStream*> mHashHttp2;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SpdyPush_Public_h
diff --git a/netwerk/protocol/http/ParentChannelListener.cpp b/netwerk/protocol/http/ParentChannelListener.cpp
new file mode 100644
index 0000000000..d35a0a9a12
--- /dev/null
+++ b/netwerk/protocol/http/ParentChannelListener.cpp
@@ -0,0 +1,292 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "ParentChannelListener.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ServiceWorkerInterceptController.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "mozilla/SchedulerGroup.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIPrompt.h"
+#include "nsISecureBrowserUI.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIWindowWatcher.h"
+#include "nsQueryObject.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIPromptFactory.h"
+#include "Element.h"
+#include "nsILoginManagerAuthPrompter.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "nsIWebNavigation.h"
+
+using mozilla::dom::ServiceWorkerInterceptController;
+
+namespace mozilla {
+namespace net {
+
+ParentChannelListener::ParentChannelListener(
+ nsIStreamListener* aListener,
+ dom::CanonicalBrowsingContext* aBrowsingContext)
+ : mNextListener(aListener), mBrowsingContext(aBrowsingContext) {
+ LOG(("ParentChannelListener::ParentChannelListener [this=%p, next=%p]", this,
+ aListener));
+
+ mInterceptController = new ServiceWorkerInterceptController();
+}
+
+ParentChannelListener::~ParentChannelListener() {
+ LOG(("ParentChannelListener::~ParentChannelListener %p", this));
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(ParentChannelListener)
+NS_IMPL_RELEASE(ParentChannelListener)
+NS_INTERFACE_MAP_BEGIN(ParentChannelListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
+ NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAuthPromptProvider, mBrowsingContext)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(ParentChannelListener)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::OnStartRequest(nsIRequest* aRequest) {
+ if (!mNextListener) return NS_ERROR_UNEXPECTED;
+
+ // If we're not a multi-part channel, then we can drop mListener and break the
+ // reference cycle. If we are, then this might be called again, so wait for
+ // OnAfterLastPart instead.
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (multiPartChannel) {
+ mIsMultiPart = true;
+ }
+
+ LOG(("ParentChannelListener::OnStartRequest [this=%p]\n", this));
+ return mNextListener->OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+ParentChannelListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ if (!mNextListener) return NS_ERROR_UNEXPECTED;
+
+ LOG(("ParentChannelListener::OnStopRequest: [this=%p status=%" PRIu32 "]\n",
+ this, static_cast<uint32_t>(aStatusCode)));
+ nsresult rv = mNextListener->OnStopRequest(aRequest, aStatusCode);
+
+ if (!mIsMultiPart) {
+ mNextListener = nullptr;
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ if (!mNextListener) return NS_ERROR_UNEXPECTED;
+
+ LOG(("ParentChannelListener::OnDataAvailable [this=%p]\n", this));
+ return mNextListener->OnDataAvailable(aRequest, aInputStream, aOffset,
+ aCount);
+}
+
+NS_IMETHODIMP
+ParentChannelListener::OnDataFinished(nsresult aStatus) {
+ if (!mNextListener) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mNextListener);
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIMultiPartChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::OnAfterLastPart(nsresult aStatus) {
+ nsCOMPtr<nsIMultiPartChannelListener> multiListener =
+ do_QueryInterface(mNextListener);
+ if (multiListener) {
+ multiListener->OnAfterLastPart(aStatus);
+ }
+
+ mNextListener = nullptr;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::GetInterface(const nsIID& aIID, void** result) {
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController))) {
+ return QueryInterface(aIID, result);
+ }
+
+ if (mBrowsingContext && aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ nsCOMPtr<dom::Element> frameElement =
+ mBrowsingContext->Top()->GetEmbedderElement();
+ if (frameElement) {
+ nsCOMPtr<nsPIDOMWindowOuter> win = frameElement->OwnerDoc()->GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_NO_INTERFACE);
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ rv = wwatch->GetNewPrompter(win, getter_AddRefs(prompt));
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ prompt.forget(result);
+ return NS_OK;
+ }
+ }
+
+ if (mBrowsingContext && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
+ nsresult rv =
+ GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL, aIID, result);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir;
+ if (mNextListener && NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
+ getter_AddRefs(ir)))) {
+ return ir->GetInterface(aIID, result);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+void ParentChannelListener::SetListenerAfterRedirect(
+ nsIStreamListener* aListener) {
+ mNextListener = aListener;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsINetworkInterceptController
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::ShouldPrepareForIntercept(nsIURI* aURI,
+ nsIChannel* aChannel,
+ bool* aShouldIntercept) {
+ // If parent-side interception is enabled just forward to the real
+ // network controler.
+ if (mInterceptController) {
+ return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
+ aShouldIntercept);
+ }
+ *aShouldIntercept = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ParentChannelListener::ChannelIntercepted(nsIInterceptedChannel* aChannel) {
+ // If parent-side interception is enabled just forward to the real
+ // network controler.
+ if (mInterceptController) {
+ return mInterceptController->ChannelIntercepted(aChannel);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIAuthPromptProvider
+//
+
+NS_IMETHODIMP
+ParentChannelListener::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
+ void** aResult) {
+ if (!mBrowsingContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ // we're either allowing auth, or it's a proxy request
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ RefPtr<dom::Element> frame = mBrowsingContext->Top()->GetEmbedderElement();
+ if (frame) window = frame->OwnerDoc()->GetWindow();
+
+ // Get an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+ nsCOMPtr<nsISupports> prompt;
+ rv = wwatch->GetPrompt(window, iid, getter_AddRefs(prompt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoginManagerAuthPrompter> prompter = do_QueryInterface(prompt);
+ if (prompter) {
+ prompter->SetBrowser(frame);
+ }
+
+ *aResult = prompt.forget().take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIThreadRetargetableStreamListener
+//
+
+NS_IMETHODIMP
+ParentChannelListener::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mNextListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ParentChannelListener.h b/netwerk/protocol/http/ParentChannelListener.h
new file mode 100644
index 0000000000..cd0fbfeadb
--- /dev/null
+++ b/netwerk/protocol/http/ParentChannelListener.h
@@ -0,0 +1,86 @@
+/* -*- 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_ParentChannelListener_h
+#define mozilla_net_ParentChannelListener_h
+
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMultiPartChannel.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+#define PARENT_CHANNEL_LISTENER \
+ { \
+ 0xa4e2c10c, 0xceba, 0x457f, { \
+ 0xa8, 0x0d, 0x78, 0x2b, 0x23, 0xba, 0xbd, 0x16 \
+ } \
+ }
+
+class ParentChannelListener final : public nsIInterfaceRequestor,
+ public nsIMultiPartChannelListener,
+ public nsINetworkInterceptController,
+ public nsIThreadRetargetableStreamListener,
+ private nsIAuthPromptProvider {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+ NS_DECL_NSIAUTHPROMPTPROVIDER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(PARENT_CHANNEL_LISTENER)
+
+ explicit ParentChannelListener(
+ nsIStreamListener* aListener,
+ dom::CanonicalBrowsingContext* aBrowsingContext);
+
+ // Called to set a new listener which replaces the old one after a redirect.
+ void SetListenerAfterRedirect(nsIStreamListener* aListener);
+
+ dom::CanonicalBrowsingContext* GetBrowsingContext() const {
+ return mBrowsingContext;
+ }
+
+ private:
+ virtual ~ParentChannelListener();
+
+ // Can be the original HttpChannelParent that created this object (normal
+ // case), a different {HTTP|FTP}ChannelParent that we've been redirected to,
+ // or some other listener that we have been diverted to via
+ // nsIDivertableChannel.
+ nsCOMPtr<nsIStreamListener> mNextListener;
+
+ // This will be populated with a real network controller if parent-side
+ // interception is enabled.
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+
+ RefPtr<dom::CanonicalBrowsingContext> mBrowsingContext;
+
+ // True if we received OnStartRequest for a nsIMultiPartChannel, and are
+ // expected AllPartsStopped to be called when complete.
+ bool mIsMultiPart = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ParentChannelListener, PARENT_CHANNEL_LISTENER)
+
+inline nsISupports* ToSupports(ParentChannelListener* aDoc) {
+ return static_cast<nsIInterfaceRequestor*>(aDoc);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ParentChannelListener_h
diff --git a/netwerk/protocol/http/PendingTransactionInfo.cpp b/netwerk/protocol/http/PendingTransactionInfo.cpp
new file mode 100644
index 0000000000..53c457a4b1
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionInfo.cpp
@@ -0,0 +1,136 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "PendingTransactionInfo.h"
+#include "NullHttpTransaction.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+namespace mozilla {
+namespace net {
+
+PendingTransactionInfo::~PendingTransactionInfo() {
+ if (mDnsAndSock) {
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(mDnsAndSock);
+ LOG(
+ ("PendingTransactionInfo::PendingTransactionInfo "
+ "[trans=%p halfOpen=%p]",
+ mTransaction.get(), dnsAndSock.get()));
+ if (dnsAndSock) {
+ dnsAndSock->Unclaim();
+ }
+ mDnsAndSock = nullptr;
+ } else if (mActiveConn) {
+ RefPtr<HttpConnectionBase> activeConn = do_QueryReferent(mActiveConn);
+ if (activeConn && activeConn->Transaction() &&
+ activeConn->Transaction()->IsNullTransaction()) {
+ NullHttpTransaction* nullTrans =
+ activeConn->Transaction()->QueryNullTransaction();
+ nullTrans->Unclaim();
+ LOG((
+ "PendingTransactionInfo::PendingTransactionInfo - mark %p unclaimed.",
+ activeConn.get()));
+ }
+ }
+}
+
+bool PendingTransactionInfo::IsAlreadyClaimedInitializingConn() {
+ LOG(
+ ("PendingTransactionInfo::IsAlreadyClaimedInitializingConn "
+ "[trans=%p, halfOpen=%p, activeConn=%p]\n",
+ mTransaction.get(), mDnsAndSock.get(), mActiveConn.get()));
+
+ // When this transaction has already established a half-open
+ // connection, we want to prevent any duplicate half-open
+ // connections from being established and bound to this
+ // transaction. Allow only use of an idle persistent connection
+ // (if found) for transactions referred by a half-open connection.
+ bool alreadyDnsAndSockOrWaitingForTLS = false;
+ if (mDnsAndSock) {
+ MOZ_ASSERT(!mActiveConn);
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(mDnsAndSock);
+ LOG(
+ ("PendingTransactionInfo::IsAlreadyClaimedInitializingConn "
+ "[trans=%p, dnsAndSock=%p]\n",
+ mTransaction.get(), dnsAndSock.get()));
+ if (dnsAndSock) {
+ alreadyDnsAndSockOrWaitingForTLS = true;
+ } else {
+ // If we have not found the halfOpen socket, remove the pointer.
+ mDnsAndSock = nullptr;
+ }
+ } else if (mActiveConn) {
+ MOZ_ASSERT(!mDnsAndSock);
+ RefPtr<HttpConnectionBase> activeConn = do_QueryReferent(mActiveConn);
+ LOG(
+ ("PendingTransactionInfo::IsAlreadyClaimedInitializingConn "
+ "[trans=%p, activeConn=%p]\n",
+ mTransaction.get(), activeConn.get()));
+ // Check if this transaction claimed a connection that is still
+ // performing tls handshake with a NullHttpTransaction or it is between
+ // finishing tls and reclaiming (When nullTrans finishes tls handshake,
+ // httpConnection does not have a transaction any more and a
+ // ReclaimConnection is dispatched). But if an error occurred the
+ // connection will be closed, it will exist but CanReused will be
+ // false.
+ if (activeConn &&
+ ((activeConn->Transaction() &&
+ activeConn->Transaction()->IsNullTransaction()) ||
+ (!activeConn->Transaction() && activeConn->CanReuse()))) {
+ alreadyDnsAndSockOrWaitingForTLS = true;
+ } else {
+ // If we have not found the connection, remove the pointer.
+ mActiveConn = nullptr;
+ }
+ }
+
+ return alreadyDnsAndSockOrWaitingForTLS;
+}
+
+nsWeakPtr PendingTransactionInfo::ForgetDnsAndConnectSocketAndActiveConn() {
+ nsWeakPtr dnsAndSock = mDnsAndSock;
+
+ mDnsAndSock = nullptr;
+ mActiveConn = nullptr;
+ return dnsAndSock;
+}
+
+void PendingTransactionInfo::RememberDnsAndConnectSocket(
+ DnsAndConnectSocket* sock) {
+ mDnsAndSock =
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
+}
+
+bool PendingTransactionInfo::TryClaimingActiveConn(HttpConnectionBase* conn) {
+ nsAHttpTransaction* activeTrans = conn->Transaction();
+ NullHttpTransaction* nullTrans =
+ activeTrans ? activeTrans->QueryNullTransaction() : nullptr;
+ if (nullTrans && nullTrans->Claim()) {
+ mActiveConn =
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn));
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ conn->GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (tlsSocketControl) {
+ Unused << tlsSocketControl->Claim();
+ }
+ return true;
+ }
+ return false;
+}
+
+void PendingTransactionInfo::AddDnsAndConnectSocket(DnsAndConnectSocket* sock) {
+ mDnsAndSock =
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PendingTransactionInfo.h b/netwerk/protocol/http/PendingTransactionInfo.h
new file mode 100644
index 0000000000..863b43ae03
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionInfo.h
@@ -0,0 +1,63 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PendingTransactionInfo_h__
+#define PendingTransactionInfo_h__
+
+#include "DnsAndConnectSocket.h"
+
+namespace mozilla {
+namespace net {
+
+class PendingTransactionInfo final : public ARefBase {
+ public:
+ explicit PendingTransactionInfo(nsHttpTransaction* trans)
+ : mTransaction(trans) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PendingTransactionInfo, override)
+
+ void PrintDiagnostics(nsCString& log);
+
+ // Return true if the transaction has claimed a DnsAndConnectSocket or
+ // a connection in TLS handshake phase.
+ bool IsAlreadyClaimedInitializingConn();
+
+ // This function return a weak poointer to DnsAndConnectSocket.
+ // The pointer is used by the caller(ConnectionEntry) to remove the
+ // DnsAndConnectSocket from the internal list. PendingTransactionInfo
+ // cannot perform this opereation.
+ [[nodiscard]] nsWeakPtr ForgetDnsAndConnectSocketAndActiveConn();
+
+ // Remember associated DnsAndConnectSocket.
+ void RememberDnsAndConnectSocket(DnsAndConnectSocket* sock);
+ // Similar as above, but for a ActiveConn that is performing a TLS handshake
+ // and has only a NullTransaction associated.
+ bool TryClaimingActiveConn(HttpConnectionBase* conn);
+ // It is similar as above, but in tihs case the halfOpen is made for this
+ // PendingTransactionInfo and it is already claimed.
+ void AddDnsAndConnectSocket(DnsAndConnectSocket* sock);
+
+ nsHttpTransaction* Transaction() const { return mTransaction; }
+
+ private:
+ RefPtr<nsHttpTransaction> mTransaction;
+ nsWeakPtr mDnsAndSock;
+ nsWeakPtr mActiveConn;
+
+ ~PendingTransactionInfo();
+};
+
+class PendingComparator {
+ public:
+ bool Equals(const PendingTransactionInfo* aPendingTrans,
+ const nsAHttpTransaction* aTrans) const {
+ return aPendingTrans->Transaction() == aTrans;
+ }
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !PendingTransactionInfo_h__
diff --git a/netwerk/protocol/http/PendingTransactionQueue.cpp b/netwerk/protocol/http/PendingTransactionQueue.cpp
new file mode 100644
index 0000000000..9598769cc8
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionQueue.cpp
@@ -0,0 +1,287 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "PendingTransactionQueue.h"
+#include "nsHttpHandler.h"
+#include "mozilla/ChaosMode.h"
+
+namespace mozilla {
+namespace net {
+
+static uint64_t TabIdForQueuing(nsAHttpTransaction* transaction) {
+ return gHttpHandler->ActiveTabPriority() ? transaction->BrowserId() : 0;
+}
+
+// This function decides the transaction's order in the pending queue.
+// Given two transactions t1 and t2, returning true means that t2 is
+// more important than t1 and thus should be dispatched first.
+static bool TransactionComparator(nsHttpTransaction* t1,
+ nsHttpTransaction* t2) {
+ bool t1Blocking =
+ t1->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
+ bool t2Blocking =
+ t2->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
+
+ if (t1Blocking > t2Blocking) {
+ return false;
+ }
+
+ if (t2Blocking > t1Blocking) {
+ return true;
+ }
+
+ return t1->Priority() >= t2->Priority();
+}
+
+void PendingTransactionQueue::InsertTransactionNormal(
+ PendingTransactionInfo* info,
+ bool aInsertAsFirstForTheSamePriority /*= false*/) {
+ LOG(
+ ("PendingTransactionQueue::InsertTransactionNormal"
+ " trans=%p, bid=%" PRIu64 "\n",
+ info->Transaction(), info->Transaction()->BrowserId()));
+
+ uint64_t windowId = TabIdForQueuing(info->Transaction());
+ nsTArray<RefPtr<PendingTransactionInfo>>* const infoArray =
+ mPendingTransactionTable.GetOrInsertNew(windowId);
+
+ // XXX At least if a new array was empty before, this isn't efficient, as it
+ // does an insert-sort. It would be better to just append all elements and
+ // then sort.
+ InsertTransactionSorted(*infoArray, info, aInsertAsFirstForTheSamePriority);
+}
+
+void PendingTransactionQueue::InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /*= false*/) {
+ // insert the transaction into the front of the queue based on following
+ // rules:
+ // 1. The transaction has NS_HTTP_LOAD_AS_BLOCKING or NS_HTTP_LOAD_UNBLOCKED.
+ // 2. The transaction's priority is higher.
+ //
+ // search in reverse order under the assumption that many of the
+ // existing transactions will have the same priority (usually 0).
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction* t = pendingQ[i]->Transaction();
+ if (TransactionComparator(trans, t)) {
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling) ||
+ aInsertAsFirstForTheSamePriority) {
+ int32_t samePriorityCount;
+ for (samePriorityCount = 0; i - samePriorityCount >= 0;
+ ++samePriorityCount) {
+ if (pendingQ[i - samePriorityCount]->Transaction()->Priority() !=
+ trans->Priority()) {
+ break;
+ }
+ }
+ if (aInsertAsFirstForTheSamePriority) {
+ i -= samePriorityCount;
+ } else {
+ // skip over 0...all of the elements with the same priority.
+ i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
+ }
+ }
+ pendingQ.InsertElementAt(i + 1, pendingTransInfo);
+ return;
+ }
+ }
+ pendingQ.InsertElementAt(0, pendingTransInfo);
+}
+
+void PendingTransactionQueue::InsertTransaction(
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /* = false */) {
+ if (pendingTransInfo->Transaction()->Caps() & NS_HTTP_URGENT_START) {
+ LOG(
+ (" adding transaction to pending queue "
+ "[trans=%p urgent-start-count=%zu]\n",
+ pendingTransInfo->Transaction(), mUrgentStartQ.Length() + 1));
+ // put this transaction on the urgent-start queue...
+ InsertTransactionSorted(mUrgentStartQ, pendingTransInfo);
+ } else {
+ LOG(
+ (" adding transaction to pending queue "
+ "[trans=%p pending-count=%zu]\n",
+ pendingTransInfo->Transaction(), PendingQueueLength() + 1));
+ // put this transaction on the pending queue...
+ InsertTransactionNormal(pendingTransInfo);
+ }
+}
+
+nsTArray<RefPtr<PendingTransactionInfo>>*
+PendingTransactionQueue::GetTransactionPendingQHelper(
+ nsAHttpTransaction* trans) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ = nullptr;
+ int32_t caps = trans->Caps();
+ if (caps & NS_HTTP_URGENT_START) {
+ pendingQ = &(mUrgentStartQ);
+ } else {
+ pendingQ = mPendingTransactionTable.Get(TabIdForQueuing(trans));
+ }
+ return pendingQ;
+}
+
+void PendingTransactionQueue::AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result) {
+ result.InsertElementsAt(0, mUrgentStartQ.Elements(), mUrgentStartQ.Length());
+ mUrgentStartQ.Clear();
+}
+
+void PendingTransactionQueue::AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* infoArray = nullptr;
+ if (!mPendingTransactionTable.Get(windowId, &infoArray)) {
+ result.Clear();
+ return;
+ }
+
+ uint32_t countToAppend = maxCount;
+ countToAppend = countToAppend > infoArray->Length() || countToAppend == 0
+ ? infoArray->Length()
+ : countToAppend;
+
+ result.InsertElementsAt(result.Length(), infoArray->Elements(),
+ countToAppend);
+ infoArray->RemoveElementsAt(0, countToAppend);
+
+ LOG(
+ ("PendingTransactionQueue::AppendPendingQForFocusedWindow, "
+ "pendingQ count=%zu window.count=%zu for focused window (id=%" PRIu64
+ ")\n",
+ result.Length(), infoArray->Length(), windowId));
+}
+
+void PendingTransactionQueue::AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ // XXX Adjust the order of transactions in a smarter manner.
+ uint32_t totalCount = 0;
+ for (const auto& entry : mPendingTransactionTable) {
+ if (windowId && entry.GetKey() == windowId) {
+ continue;
+ }
+
+ uint32_t count = 0;
+ for (; count < entry.GetWeak()->Length(); ++count) {
+ if (maxCount && totalCount == maxCount) {
+ break;
+ }
+
+ // Because elements in |result| could come from multiple penndingQ,
+ // call |InsertTransactionSorted| to make sure the order is correct.
+ InsertTransactionSorted(result, entry.GetWeak()->ElementAt(count));
+ ++totalCount;
+ }
+ entry.GetWeak()->RemoveElementsAt(0, count);
+
+ if (maxCount && totalCount == maxCount) {
+ if (entry.GetWeak()->Length()) {
+ // There are still some pending transactions for background
+ // tabs but we limit their dispatch. This is considered as
+ // an active tab optimization.
+ nsHttp::NotifyActiveTabLoadOptimization();
+ }
+ break;
+ }
+ }
+}
+
+void PendingTransactionQueue::ReschedTransaction(nsHttpTransaction* aTrans) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ =
+ GetTransactionPendingQHelper(aTrans);
+
+ int32_t index =
+ pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1;
+ if (index >= 0) {
+ RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
+ pendingQ->RemoveElementAt(index);
+ InsertTransactionSorted(*pendingQ, pendingTransInfo);
+ }
+}
+
+void PendingTransactionQueue::RemoveEmptyPendingQ() {
+ for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
+ if (it.UserData()->IsEmpty()) {
+ it.Remove();
+ }
+ }
+}
+
+size_t PendingTransactionQueue::PendingQueueLength() const {
+ size_t length = 0;
+ for (const auto& data : mPendingTransactionTable.Values()) {
+ length += data->Length();
+ }
+
+ return length;
+}
+
+size_t PendingTransactionQueue::PendingQueueLengthForWindow(
+ uint64_t windowId) const {
+ auto* pendingQ = mPendingTransactionTable.Get(windowId);
+ return (pendingQ) ? pendingQ->Length() : 0;
+}
+
+size_t PendingTransactionQueue::UrgentStartQueueLength() {
+ return mUrgentStartQ.Length();
+}
+
+void PendingTransactionQueue::PrintPendingQ() {
+ LOG(("urgent queue ["));
+ for (const auto& info : mUrgentStartQ) {
+ LOG((" %p", info->Transaction()));
+ }
+ for (const auto& entry : mPendingTransactionTable) {
+ LOG(("] window id = %" PRIx64 " queue [", entry.GetKey()));
+ for (const auto& info : *entry.GetWeak()) {
+ LOG((" %p", info->Transaction()));
+ }
+ }
+ LOG(("]"));
+}
+
+void PendingTransactionQueue::Compact() {
+ mUrgentStartQ.Compact();
+ for (const auto& data : mPendingTransactionTable.Values()) {
+ data->Compact();
+ }
+}
+
+void PendingTransactionQueue::CancelAllTransactions(nsresult reason) {
+ for (const auto& pendingTransInfo : mUrgentStartQ) {
+ LOG(("PendingTransactionQueue::CancelAllTransactions %p\n",
+ pendingTransInfo->Transaction()));
+ pendingTransInfo->Transaction()->Close(reason);
+ }
+ mUrgentStartQ.Clear();
+
+ for (const auto& data : mPendingTransactionTable.Values()) {
+ for (const auto& pendingTransInfo : *data) {
+ LOG(("PendingTransactionQueue::CancelAllTransactions %p\n",
+ pendingTransInfo->Transaction()));
+ pendingTransInfo->Transaction()->Close(reason);
+ }
+ data->Clear();
+ }
+
+ mPendingTransactionTable.Clear();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PendingTransactionQueue.h b/netwerk/protocol/http/PendingTransactionQueue.h
new file mode 100644
index 0000000000..0b0d51e2cd
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionQueue.h
@@ -0,0 +1,92 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PendingTransactionQueue_h__
+#define PendingTransactionQueue_h__
+
+#include "nsClassHashtable.h"
+#include "nsHttpTransaction.h"
+#include "PendingTransactionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class PendingTransactionQueue {
+ public:
+ PendingTransactionQueue() = default;
+
+ void ReschedTransaction(nsHttpTransaction* aTrans);
+
+ nsTArray<RefPtr<PendingTransactionInfo>>* GetTransactionPendingQHelper(
+ nsAHttpTransaction* trans);
+
+ void InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ // Add a transaction information into the pending queue in
+ // |mPendingTransactionTable| according to the transaction's
+ // top level outer content window id.
+ void InsertTransaction(PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ void AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result);
+
+ // Append transactions to the |result| whose window id
+ // is equal to |windowId|.
+ // NOTE: maxCount == 0 will get all transactions in the queue.
+ void AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Append transactions whose window id isn't equal to |windowId|.
+ // NOTE: windowId == 0 will get all transactions for both
+ // focused and non-focused windows.
+ void AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Return the count of pending transactions for all window ids.
+ size_t PendingQueueLength() const;
+ size_t PendingQueueLengthForWindow(uint64_t windowId) const;
+
+ // Remove the empty pendingQ in |mPendingTransactionTable|.
+ void RemoveEmptyPendingQ();
+
+ void PrintDiagnostics(nsCString& log);
+
+ size_t UrgentStartQueueLength();
+
+ void PrintPendingQ();
+
+ void Compact();
+
+ void CancelAllTransactions(nsresult reason);
+
+ ~PendingTransactionQueue() = default;
+
+ private:
+ void InsertTransactionNormal(PendingTransactionInfo* info,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ nsTArray<RefPtr<PendingTransactionInfo>>
+ mUrgentStartQ; // the urgent start transaction queue
+
+ // This table provides a mapping from top level outer content window id
+ // to a queue of pending transaction information.
+ // The transaction's order in pending queue is decided by whether it's a
+ // blocking transaction and its priority.
+ // Note that the window id could be 0 if the http request
+ // is initialized without a window.
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<PendingTransactionInfo>>>
+ mPendingTransactionTable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !PendingTransactionQueue_h__
diff --git a/netwerk/protocol/http/QuicSocketControl.cpp b/netwerk/protocol/http/QuicSocketControl.cpp
new file mode 100644
index 0000000000..183b9f5fd5
--- /dev/null
+++ b/netwerk/protocol/http/QuicSocketControl.cpp
@@ -0,0 +1,128 @@
+/* -*- 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 "QuicSocketControl.h"
+
+#include "Http3Session.h"
+#include "SharedCertVerifier.h"
+#include "nsISocketProvider.h"
+#include "nsIWebProgressListener.h"
+#include "nsNSSComponent.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "sslt.h"
+#include "ssl.h"
+
+namespace mozilla {
+namespace net {
+
+QuicSocketControl::QuicSocketControl(const nsCString& aHostName, int32_t aPort,
+ uint32_t aProviderFlags,
+ Http3Session* aHttp3Session)
+ : CommonSocketControl(aHostName, aPort, aProviderFlags) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mHttp3Session = do_GetWeakReference(
+ static_cast<nsISupportsWeakReference*>(aHttp3Session));
+}
+
+void QuicSocketControl::SetCertVerificationResult(PRErrorCode errorCode) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ SetUsedPrivateDNS(GetProviderFlags() & nsISocketProvider::USED_PRIVATE_DNS);
+
+ if (errorCode) {
+ mFailedVerification = true;
+ SetCanceled(errorCode);
+ }
+
+ CallAuthenticated();
+}
+
+NS_IMETHODIMP
+QuicSocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ *aSSLVersionOffered = nsITLSSocketControl::TLS_VERSION_1_3;
+ return NS_OK;
+}
+
+void QuicSocketControl::CallAuthenticated() {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ RefPtr<Http3Session> http3Session = do_QueryReferent(mHttp3Session);
+ if (http3Session) {
+ http3Session->Authenticated(GetErrorCode());
+ }
+}
+
+void QuicSocketControl::HandshakeCompleted() {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ uint32_t state = nsIWebProgressListener::STATE_IS_SECURE;
+
+ // If we're here, the TLS handshake has succeeded. If the overridable error
+ // category is nonzero, the user has added an override for a certificate
+ // error.
+ if (mOverridableErrorCategory.isSome() &&
+ *mOverridableErrorCategory !=
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) {
+ state |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+ }
+
+ SetSecurityState(state);
+ mHandshakeCompleted = true;
+}
+
+void QuicSocketControl::SetNegotiatedNPN(const nsACString& aValue) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mNegotiatedNPN = aValue;
+ mNPNCompleted = true;
+}
+
+void QuicSocketControl::SetInfo(uint16_t aCipherSuite,
+ uint16_t aProtocolVersion,
+ uint16_t aKeaGroupName,
+ uint16_t aSignatureScheme, bool aEchAccepted) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ SSLCipherSuiteInfo cipherInfo;
+ if (SSL_GetCipherSuiteInfo(aCipherSuite, &cipherInfo, sizeof cipherInfo) ==
+ SECSuccess) {
+ mCipherSuite.emplace(aCipherSuite);
+ mProtocolVersion.emplace(aProtocolVersion & 0xFF);
+ mKeaGroupName.emplace(getKeaGroupName(aKeaGroupName));
+ mSignatureSchemeName.emplace(getSignatureName(aSignatureScheme));
+ mIsAcceptedEch.emplace(aEchAccepted);
+ }
+}
+
+NS_IMETHODIMP
+QuicSocketControl::GetEchConfig(nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ aEchConfig = mEchConfig;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuicSocketControl::SetEchConfig(const nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mEchConfig = aEchConfig;
+ RefPtr<Http3Session> http3Session = do_QueryReferent(mHttp3Session);
+ if (http3Session) {
+ http3Session->DoSetEchConfig(mEchConfig);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuicSocketControl::GetRetryEchConfig(nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ aEchConfig = mRetryEchConfig;
+ return NS_OK;
+}
+
+void QuicSocketControl::SetRetryEchConfig(const nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mRetryEchConfig = aEchConfig;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/QuicSocketControl.h b/netwerk/protocol/http/QuicSocketControl.h
new file mode 100644
index 0000000000..1042a2d659
--- /dev/null
+++ b/netwerk/protocol/http/QuicSocketControl.h
@@ -0,0 +1,67 @@
+/* -*- 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 QuicSocketControl_h
+#define QuicSocketControl_h
+
+#include "CommonSocketControl.h"
+#include "nsIWeakReferenceUtils.h"
+
+namespace mozilla {
+namespace net {
+
+class Http3Session;
+
+// IID for the QuicSocketControl interface
+#define NS_QUICSOCKETCONTROL_IID \
+ { \
+ 0xdbc67fd0, 0x1ac6, 0x457b, { \
+ 0x91, 0x4e, 0x4c, 0x86, 0x60, 0xff, 0x00, 0x69 \
+ } \
+ }
+
+class QuicSocketControl final : public CommonSocketControl {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_QUICSOCKETCONTROL_IID);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(QuicSocketControl, CommonSocketControl);
+
+ NS_IMETHOD GetSSLVersionOffered(int16_t* aSSLVersionOffered) override;
+
+ QuicSocketControl(const nsCString& aHostName, int32_t aPort,
+ uint32_t aProviderFlags, Http3Session* aHttp3Session);
+
+ void SetNegotiatedNPN(const nsACString& aValue);
+ void SetInfo(uint16_t aCipherSuite, uint16_t aProtocolVersion,
+ uint16_t aKeaGroup, uint16_t aSignatureScheme,
+ bool aEchAccepted);
+
+ void CallAuthenticated();
+
+ void HandshakeCompleted();
+ void SetCertVerificationResult(PRErrorCode errorCode) override;
+
+ NS_IMETHOD GetEchConfig(nsACString& aEchConfig) override;
+ NS_IMETHOD SetEchConfig(const nsACString& aEchConfig) override;
+ NS_IMETHOD GetRetryEchConfig(nsACString& aEchConfig) override;
+ void SetRetryEchConfig(const nsACString& aEchConfig);
+
+ private:
+ ~QuicSocketControl() = default;
+
+ // For Authentication done callback and echConfig.
+ nsWeakPtr mHttp3Session;
+
+ nsCString mEchConfig;
+ nsCString mRetryEchConfig;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(QuicSocketControl, NS_QUICSOCKETCONTROL_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // QuicSocketControl_h
diff --git a/netwerk/protocol/http/README b/netwerk/protocol/http/README
new file mode 100644
index 0000000000..621e9e950c
--- /dev/null
+++ b/netwerk/protocol/http/README
@@ -0,0 +1,119 @@
+ Darin Fisher
+ darin@netscape.com
+ 8/8/2001
+
+ HTTP DESIGN NOTES
+
+
+CLASS BREAKDOWN
+
+ nsHttpHandler
+ - implements nsIProtocolHandler
+ - manages preferences
+ - owns the authentication cache
+ - holds references to frequently used services
+
+ nsHttpChannel
+ - implements nsIHttpChannel
+ - talks to the cache
+ - initiates http transactions
+ - processes http response codes
+ - intercepts progress notifications
+
+ nsHttpConnection
+ - implements nsIStreamListener & nsIStreamProvider
+ - talks to the socket transport service
+ - feeds data to its transaction object
+ - routes progress notifications
+
+ nsHttpConnectionInfo
+ - identifies a connection
+
+ nsHttpTransaction
+ - implements nsIRequest
+ - encapsulates a http request and response
+ - parses incoming data
+
+ nsHttpChunkedDecoder
+ - owned by a transaction
+ - removes chunked decoding
+
+ nsHttpRequestHead
+ - owns a nsHttpHeaderArray
+ - knows how to fill a request buffer
+
+ nsHttpResponseHead
+ - owns a nsHttpHeaderArray
+ - knows how to parse response lines
+ - performs common header manipulations/calculations
+
+ nsHttpHeaderArray
+ - stores http "<header>:<value>" pairs
+
+ nsHttpAuthCache
+ - stores authentication credentials for http auth domains
+
+ nsHttpBasicAuth
+ - implements nsIHttpAuthenticator
+ - generates BASIC auth credentials from user:pass
+
+
+ATOMS
+
+ nsHttp:: (header namespace)
+
+ eg. nsHttp::Content_Length
+
+
+TRANSACTION MODEL
+
+ InitiateTransaction -> ActivateConnection -> AsyncWrite, AsyncRead
+
+ The channel creates transactions, and passes them to the handler via
+ InitiateTransaction along with a nsHttpConnectionInfo object
+ identifying the requested connection. The handler either dispatches
+ the transaction immediately or queues it up to be dispatched later,
+ depending on whether or not the limit on the number of connections
+ to the requested server has been reached. Once the transaction can
+ be run, the handler looks for an idle connection or creates a new
+ connection, and then (re)activates the connection, assigning it the
+ new transaction.
+
+ Once activated the connection ensures that it has a socket transport,
+ and then calls AsyncWrite and AsyncRead on the socket transport. This
+ begins the process of talking to the server. To minimize buffering,
+ socket transport thread-proxying is completely disabled (using the flags
+ DONT_PROXY_LISTENER | DONT_PROXY_PROVIDER | DONT_PROXY_OBSERVER with
+ both AsyncWrite and AsyncRead). This means that the nsHttpConnection's
+ OnStartRequest, OnDataAvailable, OnDataWritable, and OnStopRequest
+ methods will execute on the socket transport thread.
+
+ The transaction defines (non-virtual) OnDataReadable, OnDataWritable, and
+ OnStopTransaction methods, which the connection calls in response to
+ its OnDataAvailable, OnDataWritable, and OnStopRequest methods, respectively.
+ The transaction owns a nsStreamListenerProxy created by the channel, which
+ it uses to transfer data from the socket thread over to the client's thread.
+ To mimize buffering, the transaction implements nsIInputStream, and passes
+ itself to the stream listener proxy's OnDataAvailable. In this way, we
+ have effectively wedged the response parsing between the socket and the
+ thread proxy's buffer. When read, the transaction turns around and reads
+ from the socket using the buffer passed to it. The transaction scans the
+ buffer for headers, removes them as they are detected, and copies the headers
+ into its nsHttpResponseHead object. The rest of the data remains in the
+ buffer, and is proxied over to the client's thread to be handled first by the
+ http channel and eventually by the client.
+
+ There are several other major design factors, including:
+
+ - transaction cancelation
+ - progress notification
+ - SSL tunneling
+ - chunked decoding
+ - thread safety
+ - premature EOF detection and transaction restarting
+ - pipelining (not yet implemented)
+
+
+CACHING
+
+<EOF>
diff --git a/netwerk/protocol/http/SpeculativeTransaction.cpp b/netwerk/protocol/http/SpeculativeTransaction.cpp
new file mode 100644
index 0000000000..eab54c24d5
--- /dev/null
+++ b/netwerk/protocol/http/SpeculativeTransaction.cpp
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "SpeculativeTransaction.h"
+#include "HTTPSRecordResolver.h"
+#include "nsICachingChannel.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+SpeculativeTransaction::SpeculativeTransaction(
+ nsHttpConnectionInfo* aConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, std::function<void(bool)>&& aCallback)
+ : NullHttpTransaction(aConnInfo, aCallbacks, aCaps),
+ mCloseCallback(std::move(aCallback)) {}
+
+already_AddRefed<SpeculativeTransaction>
+SpeculativeTransaction::CreateWithNewConnInfo(nsHttpConnectionInfo* aConnInfo) {
+ RefPtr<SpeculativeTransaction> trans =
+ new SpeculativeTransaction(aConnInfo, mCallbacks, mCaps);
+ trans->mParallelSpeculativeConnectLimit = mParallelSpeculativeConnectLimit;
+ trans->mIgnoreIdle = mIgnoreIdle;
+ trans->mIsFromPredictor = mIsFromPredictor;
+ trans->mAllow1918 = mAllow1918;
+ return trans.forget();
+}
+
+nsresult SpeculativeTransaction::FetchHTTPSRR() {
+ LOG(("SpeculativeTransaction::FetchHTTPSRR [this=%p]", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<HTTPSRecordResolver> resolver = new HTTPSRecordResolver(this);
+ nsCOMPtr<nsICancelable> dnsRequest;
+ return resolver->FetchHTTPSRRInternal(GetCurrentSerialEventTarget(),
+ getter_AddRefs(dnsRequest));
+}
+
+nsresult SpeculativeTransaction::OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("SpeculativeTransaction::OnHTTPSRRAvailable [this=%p]", this));
+
+ if (!aHTTPSSVCRecord || !aHighestPriorityRecord) {
+ gHttpHandler->ConnMgr()->DoSpeculativeConnection(this, false);
+ return NS_OK;
+ }
+
+ RefPtr<nsHttpConnectionInfo> connInfo = ConnectionInfo();
+ RefPtr<nsHttpConnectionInfo> newInfo =
+ connInfo->CloneAndAdoptHTTPSSVCRecord(aHighestPriorityRecord);
+ RefPtr<SpeculativeTransaction> newTrans = CreateWithNewConnInfo(newInfo);
+ gHttpHandler->ConnMgr()->DoSpeculativeConnection(newTrans, false);
+ return NS_OK;
+}
+
+nsresult SpeculativeTransaction::ReadSegments(nsAHttpSegmentReader* aReader,
+ uint32_t aCount,
+ uint32_t* aCountRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mTriedToWrite = true;
+ return NullHttpTransaction::ReadSegments(aReader, aCount, aCountRead);
+}
+
+void SpeculativeTransaction::Close(nsresult aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NullHttpTransaction::Close(aReason);
+
+ if (mCloseCallback) {
+ mCloseCallback(mTriedToWrite && aReason == NS_BASE_STREAM_CLOSED);
+ mCloseCallback = nullptr;
+ }
+}
+
+void SpeculativeTransaction::InvokeCallback() {
+ if (mCloseCallback) {
+ mCloseCallback(true);
+ mCloseCallback = nullptr;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/SpeculativeTransaction.h b/netwerk/protocol/http/SpeculativeTransaction.h
new file mode 100644
index 0000000000..851fe2b39f
--- /dev/null
+++ b/netwerk/protocol/http/SpeculativeTransaction.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; 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 SpeculativeTransaction_h__
+#define SpeculativeTransaction_h__
+
+#include "mozilla/Maybe.h"
+#include "NullHttpTransaction.h"
+
+namespace mozilla {
+namespace net {
+
+class HTTPSRecordResolver;
+
+class SpeculativeTransaction : public NullHttpTransaction {
+ public:
+ SpeculativeTransaction(nsHttpConnectionInfo* aConnInfo,
+ nsIInterfaceRequestor* aCallbacks, uint32_t aCaps,
+ std::function<void(bool)>&& aCallback = nullptr);
+
+ already_AddRefed<SpeculativeTransaction> CreateWithNewConnInfo(
+ nsHttpConnectionInfo* aConnInfo);
+
+ virtual nsresult FetchHTTPSRR() override;
+
+ virtual nsresult OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) override;
+
+ void SetParallelSpeculativeConnectLimit(uint32_t aLimit) {
+ mParallelSpeculativeConnectLimit.emplace(aLimit);
+ }
+ void SetIgnoreIdle(bool aIgnoreIdle) { mIgnoreIdle.emplace(aIgnoreIdle); }
+ void SetIsFromPredictor(bool aIsFromPredictor) {
+ mIsFromPredictor.emplace(aIsFromPredictor);
+ }
+ void SetAllow1918(bool aAllow1918) { mAllow1918.emplace(aAllow1918); }
+
+ const Maybe<uint32_t>& ParallelSpeculativeConnectLimit() {
+ return mParallelSpeculativeConnectLimit;
+ }
+ const Maybe<bool>& IgnoreIdle() { return mIgnoreIdle; }
+ const Maybe<bool>& IsFromPredictor() { return mIsFromPredictor; }
+ const Maybe<bool>& Allow1918() { return mAllow1918; }
+
+ void Close(nsresult aReason) override;
+ nsresult ReadSegments(nsAHttpSegmentReader* aReader, uint32_t aCount,
+ uint32_t* aCountRead) override;
+ void InvokeCallback();
+
+ protected:
+ virtual ~SpeculativeTransaction() = default;
+
+ private:
+ Maybe<uint32_t> mParallelSpeculativeConnectLimit;
+ Maybe<bool> mIgnoreIdle;
+ Maybe<bool> mIsFromPredictor;
+ Maybe<bool> mAllow1918;
+
+ bool mTriedToWrite = false;
+ std::function<void(bool)> mCloseCallback;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // SpeculativeTransaction_h__
diff --git a/netwerk/protocol/http/TLSTransportLayer.cpp b/netwerk/protocol/http/TLSTransportLayer.cpp
new file mode 100644
index 0000000000..eff46898ea
--- /dev/null
+++ b/netwerk/protocol/http/TLSTransportLayer.cpp
@@ -0,0 +1,866 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "Http2StreamTunnel.h"
+#include "TLSTransportLayer.h"
+#include "nsISocketProvider.h"
+#include "nsITLSSocketControl.h"
+#include "nsQueryObject.h"
+#include "nsSocketProviderService.h"
+#include "nsSocketTransport2.h"
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+// TLSTransportLayerInputStream impl
+//-----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(TLSTransportLayer::InputStreamWrapper, nsIInputStream,
+ nsIAsyncInputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::InputStreamWrapper::AddRef() { return mTransport->AddRef(); }
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::InputStreamWrapper::Release() {
+ return mTransport->Release();
+}
+
+TLSTransportLayer::InputStreamWrapper::InputStreamWrapper(
+ nsIAsyncInputStream* aInputStream, TLSTransportLayer* aTransport)
+ : mSocketIn(aInputStream), mTransport(aTransport) {}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::Close() {
+ LOG(("TLSTransportLayer::InputStreamWrapper::Close [this=%p]\n", this));
+ return mSocketIn->Close();
+}
+
+NS_IMETHODIMP TLSTransportLayer::InputStreamWrapper::Available(
+ uint64_t* avail) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::Available [this=%p]\n", this));
+ return mSocketIn->Available(avail);
+}
+
+NS_IMETHODIMP TLSTransportLayer::InputStreamWrapper::StreamStatus() {
+ LOG(("TLSTransportLayer::InputStreamWrapper::StreamStatus [this=%p]\n",
+ this));
+ return mSocketIn->StreamStatus();
+}
+
+nsresult TLSTransportLayer::InputStreamWrapper::ReadDirectly(
+ char* buf, uint32_t count, uint32_t* countRead) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::ReadDirectly [this=%p]\n",
+ this));
+ return mSocketIn->Read(buf, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::Read(char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::Read [this=%p]\n", this));
+
+ *countRead = 0;
+
+ if (NS_FAILED(mStatus)) {
+ return (mStatus == NS_BASE_STREAM_CLOSED) ? NS_OK : mStatus;
+ }
+
+ int32_t bytesRead = PR_Read(mTransport->mFD, buf, count);
+ if (bytesRead > 0) {
+ *countRead = bytesRead;
+ } else if (bytesRead < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ LOG((
+ "TLSTransportLayer::InputStreamWrapper::Read %p PR_Read would block ",
+ this));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ // If reading from the socket succeeded (NS_SUCCEEDED(mStatus)),
+ // but the nss layer encountered an error remember the error.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = ErrorAccordingToNSPR(code);
+ LOG(("TLSTransportLayer::InputStreamWrapper::Read %p nss error %" PRIx32
+ ".\n",
+ this, static_cast<uint32_t>(mStatus)));
+ }
+ }
+
+ if (NS_SUCCEEDED(mStatus) && !bytesRead) {
+ LOG(
+ ("TLSTransportLayer::InputStreamWrapper::Read %p "
+ "Second layer of TLS stripping results in STREAM_CLOSED\n",
+ this));
+ mStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ LOG(("TLSTransportLayer::InputStreamWrapper::Read %p rv=%" PRIx32
+ " didread=%d "
+ "2 layers of ssl stripped to plaintext\n",
+ this, static_cast<uint32_t>(mStatus), bytesRead));
+ return mStatus;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::ReadSegments(nsWriteSegmentFun writer,
+ void* closure,
+ uint32_t count,
+ uint32_t* countRead) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::ReadSegments [this=%p]\n",
+ this));
+ return mSocketIn->ReadSegments(writer, closure, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::IsNonBlocking(bool* nonblocking) {
+ return mSocketIn->IsNonBlocking(nonblocking);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::CloseWithStatus(nsresult reason) {
+ LOG(
+ ("TLSTransportLayer::InputStreamWrapper::CloseWithStatus [this=%p "
+ "reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ return mSocketIn->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::AsyncWait(
+ nsIInputStreamCallback* callback, uint32_t flags, uint32_t amount,
+ nsIEventTarget* target) {
+ LOG(
+ ("TLSTransportLayer::InputStreamWrapper::AsyncWait [this=%p, "
+ "callback=%p]\n",
+ this, callback));
+ mTransport->mInputCallback = callback;
+ // Don't bother to call PR_POLL when |callback| is NULL. We call |AsyncWait|
+ // directly to null out the underlying callback.
+ if (!callback) {
+ return mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+
+ PRPollDesc pd;
+ pd.fd = mTransport->mFD;
+ pd.in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ // Only run PR_Poll on the socket thread. Also, make sure this lives at least
+ // as long as that operation.
+ auto DoPoll = [self = RefPtr{this}, pd(pd)]() mutable {
+ int32_t rv = PR_Poll(&pd, 1, PR_INTERVAL_NO_TIMEOUT);
+ LOG(("TLSTransportLayer::InputStreamWrapper::AsyncWait rv=%d", rv));
+ };
+ if (OnSocketThread()) {
+ DoPoll();
+ } else {
+ gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "TLSTransportLayer::InputStreamWrapper::AsyncWait", DoPoll));
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSTransportLayerOutputStream impl
+//-----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(TLSTransportLayer::OutputStreamWrapper, nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::OutputStreamWrapper::AddRef() {
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::OutputStreamWrapper::Release() {
+ return mTransport->Release();
+}
+
+TLSTransportLayer::OutputStreamWrapper::OutputStreamWrapper(
+ nsIAsyncOutputStream* aOutputStream, TLSTransportLayer* aTransport)
+ : mSocketOut(aOutputStream), mTransport(aTransport) {}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::Close() {
+ LOG(("TLSTransportLayer::OutputStreamWrapper::Close [this=%p]\n", this));
+ return mSocketOut->Close();
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::Flush() {
+ LOG(("TLSTransportLayerOutputStream::Flush [this=%p]\n", this));
+ return mSocketOut->Flush();
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::StreamStatus() {
+ LOG(("TLSTransportLayerOutputStream::StreamStatus [this=%p]\n", this));
+ return mSocketOut->StreamStatus();
+}
+
+nsresult TLSTransportLayer::OutputStreamWrapper::WriteDirectly(
+ const char* buf, uint32_t count, uint32_t* countWritten) {
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::WriteDirectly [this=%p "
+ "count=%u]\n",
+ this, count));
+ return mSocketOut->Write(buf, count, countWritten);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::Write(const char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("TLSTransportLayer::OutputStreamWrapper::Write [this=%p count=%u]\n",
+ this, count));
+
+ *countWritten = 0;
+
+ if (NS_FAILED(mStatus)) {
+ return (mStatus == NS_BASE_STREAM_CLOSED) ? NS_OK : mStatus;
+ }
+
+ int32_t written = PR_Write(mTransport->mFD, buf, count);
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::Write %p PRWrite(%d) = %d "
+ "%d\n",
+ this, count, written, PR_GetError() == PR_WOULD_BLOCK_ERROR));
+
+ if (written > 0) {
+ *countWritten = written;
+ } else if (written < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::Write %p PRWrite would "
+ "block ",
+ this));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Writing to the socket succeeded, but failed in nss layer.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = ErrorAccordingToNSPR(code);
+ }
+ }
+
+ return mStatus;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::WriteSegments(nsReadSegmentFun reader,
+ void* closure,
+ uint32_t count,
+ uint32_t* countRead) {
+ return mSocketOut->WriteSegments(reader, closure, count, countRead);
+}
+
+// static
+nsresult TLSTransportLayer::OutputStreamWrapper::WriteFromSegments(
+ nsIInputStream* input, void* closure, const char* fromSegment,
+ uint32_t offset, uint32_t count, uint32_t* countRead) {
+ OutputStreamWrapper* self = (OutputStreamWrapper*)closure;
+ return self->Write(fromSegment, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::WriteFrom(nsIInputStream* stream,
+ uint32_t count,
+ uint32_t* countRead) {
+ return stream->ReadSegments(WriteFromSegments, this, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::IsNonBlocking(bool* nonblocking) {
+ return mSocketOut->IsNonBlocking(nonblocking);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::CloseWithStatus(nsresult reason) {
+ LOG(("OutputStreamWrapper::CloseWithStatus [this=%p reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ return mSocketOut->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::AsyncWait(
+ nsIOutputStreamCallback* callback, uint32_t flags, uint32_t amount,
+ nsIEventTarget* target) {
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::AsyncWait [this=%p, "
+ "mOutputCallback=%p "
+ "callback=%p]\n",
+ this, mTransport->mOutputCallback.get(), callback));
+ mTransport->mOutputCallback = callback;
+ // Don't bother to call PR_POLL when |callback| is NULL. We call |AsyncWait|
+ // directly to null out the underlying callback.
+ if (!callback) {
+ return mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+
+ PRPollDesc pd;
+ pd.fd = mTransport->mFD;
+ pd.in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ int32_t rv = PR_Poll(&pd, 1, PR_INTERVAL_NO_TIMEOUT);
+ LOG(("TLSTransportLayer::OutputStreamWrapper::AsyncWait rv=%d", rv));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSTransportLayer impl
+//-----------------------------------------------------------------------------
+
+static PRDescIdentity sTLSTransportLayerIdentity;
+static PRIOMethods sTLSTransportLayerMethods;
+static PRIOMethods* sTLSTransportLayerMethodsPtr = nullptr;
+
+bool TLSTransportLayer::DispatchRelease() {
+ if (OnSocketThread()) {
+ return false;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewNonOwningRunnableMethod("net::TLSTransportLayer::Release", this,
+ &TLSTransportLayer::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMPL_ADDREF(TLSTransportLayer)
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the socket thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "TLSTransportLayer");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(TLSTransportLayer)
+ NS_INTERFACE_MAP_ENTRY(nsISocketTransport)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(TLSTransportLayer)
+NS_INTERFACE_MAP_END
+
+TLSTransportLayer::TLSTransportLayer(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream,
+ nsIInputStreamCallback* aOwner)
+ : mSocketTransport(aTransport),
+ mSocketInWrapper(aInputStream, this),
+ mSocketOutWrapper(aOutputStream, this),
+ mOwner(aOwner) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSTransportLayer ctor this=[%p]", this));
+}
+
+TLSTransportLayer::~TLSTransportLayer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSTransportLayer dtor this=[%p]", this));
+ if (mFD) {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ mTLSSocketControl = nullptr;
+}
+
+bool TLSTransportLayer::Init(const char* aTLSHost, int32_t aTLSPort) {
+ LOG(("TLSTransportLayer::Init this=[%p]", this));
+ nsCOMPtr<nsISocketProvider> provider;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+ if (!spserv) {
+ return false;
+ }
+
+ spserv->GetSocketProvider("ssl", getter_AddRefs(provider));
+ if (!provider) {
+ return false;
+ }
+
+ // Install an NSPR layer to handle getpeername() with a failure. This is kind
+ // of silly, but the default one used by the pipe asserts when called and the
+ // nss code calls it to see if we are connected to a real socket or not.
+ if (!sTLSTransportLayerMethodsPtr) {
+ // one time initialization
+ sTLSTransportLayerIdentity = PR_GetUniqueIdentity("TLSTransportLayer");
+ sTLSTransportLayerMethods = *PR_GetDefaultIOMethods();
+ sTLSTransportLayerMethods.getpeername = GetPeerName;
+ sTLSTransportLayerMethods.getsocketoption = GetSocketOption;
+ sTLSTransportLayerMethods.setsocketoption = SetSocketOption;
+ sTLSTransportLayerMethods.read = Read;
+ sTLSTransportLayerMethods.write = Write;
+ sTLSTransportLayerMethods.send = Send;
+ sTLSTransportLayerMethods.recv = Recv;
+ sTLSTransportLayerMethods.close = Close;
+ sTLSTransportLayerMethods.poll = Poll;
+ sTLSTransportLayerMethodsPtr = &sTLSTransportLayerMethods;
+ }
+
+ mFD = PR_CreateIOLayerStub(sTLSTransportLayerIdentity,
+ &sTLSTransportLayerMethods);
+ if (!mFD) {
+ return false;
+ }
+
+ mFD->secret = reinterpret_cast<PRFilePrivate*>(this);
+
+ return NS_SUCCEEDED(provider->AddToSocket(
+ PR_AF_INET, aTLSHost, aTLSPort, nullptr, OriginAttributes(), 0, 0, mFD,
+ getter_AddRefs(mTLSSocketControl)));
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OnInputStreamReady(nsIAsyncInputStream* in) {
+ nsCOMPtr<nsIInputStreamCallback> callback = std::move(mInputCallback);
+ if (callback) {
+ return callback->OnInputStreamReady(&mSocketInWrapper);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ nsCOMPtr<nsIOutputStreamCallback> callback = std::move(mOutputCallback);
+ nsresult rv = NS_OK;
+ if (callback) {
+ rv = callback->OnOutputStreamReady(&mSocketOutWrapper);
+
+ RefPtr<OutputStreamTunnel> tunnel = do_QueryObject(out);
+ if (tunnel) {
+ tunnel->MaybeSetRequestDone(callback);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetKeepaliveEnabled(bool aKeepaliveEnabled) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetKeepaliveEnabled(aKeepaliveEnabled);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetKeepaliveVals(keepaliveIdleTime,
+ keepaliveRetryInterval);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mSocketTransport->SetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::Close(nsresult aReason) {
+ LOG(("TLSTransportLayer::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aReason)));
+
+ mInputCallback = nullptr;
+ mOutputCallback = nullptr;
+ if (mSocketTransport) {
+ mSocketTransport->Close(aReason);
+ mSocketTransport = nullptr;
+ }
+ mSocketInWrapper.AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOutWrapper.AsyncWait(nullptr, 0, 0, nullptr);
+
+ if (mOwner) {
+ RefPtr<TLSTransportLayer> self = this;
+ Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "TLSTransportLayer::Close", [self{std::move(self)}]() {
+ nsCOMPtr<nsIInputStreamCallback> inputCallback =
+ std::move(self->mOwner);
+ if (inputCallback) {
+ // This is hack. We need to make
+ // nsHttpConnection::OnInputStreamReady be called, so
+ // nsHttpConnection::CloseTransaction can be called to release the
+ // transaction.
+ Unused << inputCallback->OnInputStreamReady(
+ &self->mSocketInWrapper);
+ }
+ }));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetEventSink(aSink, aEventTarget);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::Bind(NetAddr* aLocalAddr) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->Bind(aLocalAddr);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetEchConfigUsed(bool* aEchConfigUsed) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetEchConfigUsed(aEchConfigUsed);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetEchConfig(const nsACString& aEchConfig) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetEchConfig(aEchConfig);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::ResolvedByTRR(bool* aResolvedByTRR) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->ResolvedByTRR(aResolvedByTRR);
+}
+
+NS_IMETHODIMP TLSTransportLayer::GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetEffectiveTRRMode(aEffectiveTRRMode);
+}
+
+NS_IMETHODIMP TLSTransportLayer::GetTrrSkipReason(
+ nsITRRSkipReason::value* aTrrSkipReason) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetTrrSkipReason(aTrrSkipReason);
+}
+
+#define FWD_TS_PTR(fx, ts) \
+ NS_IMETHODIMP \
+ TLSTransportLayer::fx(ts* arg) { \
+ if (!mSocketTransport) return NS_ERROR_FAILURE; \
+ return mSocketTransport->fx(arg); \
+ }
+
+#define FWD_TS_ADDREF(fx, ts) \
+ NS_IMETHODIMP \
+ TLSTransportLayer::fx(ts** arg) { \
+ if (!mSocketTransport) return NS_ERROR_FAILURE; \
+ return mSocketTransport->fx(arg); \
+ }
+
+#define FWD_TS(fx, ts) \
+ NS_IMETHODIMP \
+ TLSTransportLayer::fx(ts arg) { \
+ if (!mSocketTransport) return NS_ERROR_FAILURE; \
+ return mSocketTransport->fx(arg); \
+ }
+
+FWD_TS_PTR(GetKeepaliveEnabled, bool);
+FWD_TS_PTR(GetSendBufferSize, uint32_t);
+FWD_TS(SetSendBufferSize, uint32_t);
+FWD_TS_PTR(GetPort, int32_t);
+FWD_TS_PTR(GetPeerAddr, mozilla::net::NetAddr);
+FWD_TS_PTR(GetSelfAddr, mozilla::net::NetAddr);
+FWD_TS_ADDREF(GetScriptablePeerAddr, nsINetAddr);
+FWD_TS_ADDREF(GetScriptableSelfAddr, nsINetAddr);
+FWD_TS_PTR(IsAlive, bool);
+FWD_TS_PTR(GetConnectionFlags, uint32_t);
+FWD_TS(SetConnectionFlags, uint32_t);
+FWD_TS(SetIsPrivate, bool);
+FWD_TS_PTR(GetTlsFlags, uint32_t);
+FWD_TS(SetTlsFlags, uint32_t);
+FWD_TS_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS(SetRecvBufferSize, uint32_t);
+FWD_TS_PTR(GetResetIPFamilyPreference, bool);
+
+nsresult TLSTransportLayer::GetTlsSocketControl(
+ nsITLSSocketControl** tlsSocketControl) {
+ if (!mTLSSocketControl) {
+ return NS_ERROR_ABORT;
+ }
+
+ *tlsSocketControl = do_AddRef(mTLSSocketControl).take();
+ return NS_OK;
+}
+
+nsresult TLSTransportLayer::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult TLSTransportLayer::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetOriginAttributes(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetHost(nsACString& aHost) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetTimeout(uint32_t aType, uint32_t* _retval) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetTimeout(aType, _retval);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetTimeout(uint32_t aType, uint32_t aValue) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetReuseAddrPort(bool aReuseAddrPort) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetReuseAddrPort(aReuseAddrPort);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetLinger(bool aPolarity, int16_t aTimeout) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetLinger(aPolarity, aTimeout);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetQoSBits(uint8_t* aQoSBits) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetQoSBits(uint8_t aQoSBits) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetRetryDnsIfPossible(bool* aRetry) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetRetryDnsIfPossible(aRetry);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetStatus(nsresult* aStatus) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetStatus(aStatus);
+}
+
+int32_t TLSTransportLayer::OutputInternal(const char* aBuf, int32_t aAmount) {
+ LOG(("TLSTransportLayer::OutputInternal %p %d", this, aAmount));
+
+ uint32_t outCountWrite = 0;
+ nsresult rv = mSocketOutWrapper.WriteDirectly(aBuf, aAmount, &outCountWrite);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ } else {
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ }
+ return -1;
+ }
+
+ return outCountWrite;
+}
+
+int32_t TLSTransportLayer::InputInternal(char* aBuf, int32_t aAmount) {
+ LOG(("TLSTransportLayer::InputInternal aAmount=%d\n", aAmount));
+
+ uint32_t outCountRead = 0;
+ nsresult rv = mSocketInWrapper.ReadDirectly(aBuf, aAmount, &outCountRead);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ } else {
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ }
+ return -1;
+ }
+ return outCountRead;
+}
+
+PRStatus TLSTransportLayer::GetPeerName(PRFileDesc* aFD, PRNetAddr* addr) {
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(aFD->secret);
+ NetAddr peeraddr;
+ if (NS_FAILED(self->Transport()->GetPeerAddr(&peeraddr))) {
+ return PR_FAILURE;
+ }
+ NetAddrToPRNetAddr(&peeraddr, addr);
+ return PR_SUCCESS;
+}
+
+PRStatus TLSTransportLayer::GetSocketOption(PRFileDesc* aFD,
+ PRSocketOptionData* aOpt) {
+ if (aOpt->option == PR_SockOpt_Nonblocking) {
+ aOpt->value.non_blocking = PR_TRUE;
+ return PR_SUCCESS;
+ }
+ return PR_FAILURE;
+}
+
+PRStatus TLSTransportLayer::SetSocketOption(PRFileDesc* aFD,
+ const PRSocketOptionData* aOpt) {
+ return PR_FAILURE;
+}
+
+PRStatus TLSTransportLayer::Close(PRFileDesc* aFD) { return PR_SUCCESS; }
+
+int32_t TLSTransportLayer::Write(PRFileDesc* aFD, const void* aBuf,
+ int32_t aAmount) {
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(aFD->secret);
+ return self->OutputInternal(static_cast<const char*>(aBuf), aAmount);
+}
+
+int32_t TLSTransportLayer::Send(PRFileDesc* aFD, const void* aBuf,
+ int32_t aAmount, int, PRIntervalTime) {
+ return Write(aFD, aBuf, aAmount);
+}
+
+int32_t TLSTransportLayer::Read(PRFileDesc* aFD, void* aBuf, int32_t aAmount) {
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(aFD->secret);
+ return self->InputInternal(static_cast<char*>(aBuf), aAmount);
+}
+
+int32_t TLSTransportLayer::Recv(PRFileDesc* aFD, void* aBuf, int32_t aAmount,
+ int, PRIntervalTime) {
+ return Read(aFD, aBuf, aAmount);
+}
+
+int16_t TLSTransportLayer::Poll(PRFileDesc* fd, int16_t in_flags,
+ int16_t* out_flags) {
+ LOG(("TLSTransportLayer::Poll fd=%p inf_flags=%d\n", fd, (int)in_flags));
+ *out_flags = in_flags;
+
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(fd->secret);
+ if (!self) {
+ return 0;
+ }
+
+ if (in_flags & PR_POLL_READ) {
+ self->mSocketInWrapper.mSocketIn->AsyncWait(self, 0, 0, nullptr);
+ } else if (in_flags & PR_POLL_WRITE) {
+ self->mSocketOutWrapper.mSocketOut->AsyncWait(self, 0, 0, nullptr);
+ }
+
+ return in_flags;
+}
+
+bool TLSTransportLayer::HasDataToRecv() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mFD) {
+ return false;
+ }
+ int32_t n = 0;
+ char c;
+ n = PR_Recv(mFD, &c, 1, PR_MSG_PEEK, 0);
+ return n > 0;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/TLSTransportLayer.h b/netwerk/protocol/http/TLSTransportLayer.h
new file mode 100644
index 0000000000..85489ffa40
--- /dev/null
+++ b/netwerk/protocol/http/TLSTransportLayer.h
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TLSTransportLayer_h__
+#define TLSTransportLayer_h__
+
+#include "nsSocketTransportService2.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "prio.h"
+
+namespace mozilla::net {
+
+// TLSTransportLayer will provide a secondary TLS layer. It will be added as a
+// layer between nsHttpConnection and nsSocketTransport.
+// The mSocketTransport, mSocketIn, and mSocketOut of nsHttpConnection will be
+// replaced by TLSTransportLayer.
+//
+// The input path of reading data from a socket is shown below.
+// nsHttpConnection::OnSocketReadable
+// nsHttpConnection::OnWriteSegment
+// nsHttpConnection::mSocketIn->Read
+// TLSTransportLayer::InputStreamWrapper::Read
+// TLSTransportLayer::InputInternal
+// TLSTransportLayer::InputStreamWrapper::ReadDirectly
+// nsSocketInputStream::Read
+//
+// The output path of writing data to a socket is shown below.
+// nsHttpConnection::OnSocketWritable
+// nsHttpConnection::OnReadSegment
+// TLSTransportLayer::OutputStreamWrapper::Write
+// TLSTransportLayer::OutputInternal
+// TLSTransportLayer::OutputStreamWrapper::WriteDirectly
+// nsSocketOutputStream::Write
+
+// 9d6a3bc6-1f90-41d0-9b02-33ccd169052b
+#define NS_TLSTRANSPORTLAYER_IID \
+ { \
+ 0x9d6a3bc6, 0x1f90, 0x41d0, { \
+ 0x9b, 0x02, 0x33, 0xcc, 0xd1, 0x69, 0x05, 0x2b \
+ } \
+ }
+
+class TLSTransportLayer final : public nsISocketTransport,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TLSTRANSPORTLAYER_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ explicit TLSTransportLayer(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream,
+ nsIInputStreamCallback* aOwner);
+ bool Init(const char* aTLSHost, int32_t aTLSPort);
+ already_AddRefed<nsIAsyncInputStream> GetInputStreamWrapper() {
+ nsCOMPtr<nsIAsyncInputStream> stream = &mSocketInWrapper;
+ return stream.forget();
+ }
+ already_AddRefed<nsIAsyncOutputStream> GetOutputStreamWrapper() {
+ nsCOMPtr<nsIAsyncOutputStream> stream = &mSocketOutWrapper;
+ return stream.forget();
+ }
+
+ bool HasDataToRecv();
+
+ void ReleaseOwner() { mOwner = nullptr; }
+
+ private:
+ class InputStreamWrapper : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit InputStreamWrapper(nsIAsyncInputStream* aInputStream,
+ TLSTransportLayer* aTransport);
+
+ nsresult ReadDirectly(char* buf, uint32_t count, uint32_t* countRead);
+ nsresult Status() { return mStatus; }
+ void SetStatus(nsresult aStatus) { mStatus = aStatus; }
+
+ private:
+ friend class TLSTransportLayer;
+ virtual ~InputStreamWrapper() = default;
+ nsresult ReturnDataFromBuffer(char* buf, uint32_t count,
+ uint32_t* countRead);
+
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+
+ nsresult mStatus{NS_OK};
+ // The lifetime of InputStreamWrapper and OutputStreamWrapper are bound to
+ // TLSTransportLayer, so using |mTransport| as a raw pointer should be safe.
+ TLSTransportLayer* MOZ_OWNING_REF mTransport;
+ };
+
+ class OutputStreamWrapper : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit OutputStreamWrapper(nsIAsyncOutputStream* aOutputStream,
+ TLSTransportLayer* aTransport);
+
+ nsresult WriteDirectly(const char* buf, uint32_t count,
+ uint32_t* countWritten);
+ nsresult Status() { return mStatus; }
+ void SetStatus(nsresult aStatus) { mStatus = aStatus; }
+
+ private:
+ friend class TLSTransportLayer;
+ virtual ~OutputStreamWrapper() = default;
+ static nsresult WriteFromSegments(nsIInputStream*, void*, const char*,
+ uint32_t offset, uint32_t count,
+ uint32_t* countRead);
+
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsresult mStatus{NS_OK};
+ TLSTransportLayer* MOZ_OWNING_REF mTransport;
+ };
+
+ virtual ~TLSTransportLayer();
+ bool DispatchRelease();
+
+ nsISocketTransport* Transport() { return mSocketTransport; }
+
+ int32_t OutputInternal(const char* aBuf, int32_t aAmount);
+ int32_t InputInternal(char* aBuf, int32_t aAmount);
+
+ static PRStatus GetPeerName(PRFileDesc* fd, PRNetAddr* addr);
+ static PRStatus GetSocketOption(PRFileDesc* fd, PRSocketOptionData* aOpt);
+ static PRStatus SetSocketOption(PRFileDesc* fd,
+ const PRSocketOptionData* data);
+ static int32_t Write(PRFileDesc* fd, const void* buf, int32_t amount);
+ static int32_t Read(PRFileDesc* fd, void* buf, int32_t amount);
+ static int32_t Send(PRFileDesc* fd, const void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout);
+ static int32_t Recv(PRFileDesc* fd, void* buf, int32_t amount, int flags,
+ PRIntervalTime timeout);
+ static PRStatus Close(PRFileDesc* fd);
+ static int16_t Poll(PRFileDesc* fd, int16_t in_flags, int16_t* out_flags);
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ InputStreamWrapper mSocketInWrapper;
+ OutputStreamWrapper mSocketOutWrapper;
+ nsCOMPtr<nsITLSSocketControl> mTLSSocketControl;
+ nsCOMPtr<nsIInputStreamCallback> mInputCallback;
+ nsCOMPtr<nsIOutputStreamCallback> mOutputCallback;
+ PRFileDesc* mFD{nullptr};
+ nsCOMPtr<nsIInputStreamCallback> mOwner;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TLSTransportLayer, NS_TLSTRANSPORTLAYER_IID)
+
+} // namespace mozilla::net
+
+inline nsISupports* ToSupports(mozilla::net::TLSTransportLayer* aTransport) {
+ return static_cast<nsISocketTransport*>(aTransport);
+}
+
+#endif
diff --git a/netwerk/protocol/http/TRRServiceChannel.cpp b/netwerk/protocol/http/TRRServiceChannel.cpp
new file mode 100644
index 0000000000..3a316d1e45
--- /dev/null
+++ b/netwerk/protocol/http/TRRServiceChannel.cpp
@@ -0,0 +1,1581 @@
+/* -*- 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 "TRRServiceChannel.h"
+
+#include "HttpLog.h"
+#include "AltServiceChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Unused.h"
+#include "nsDNSPrefetch.h"
+#include "nsEscape.h"
+#include "nsHttpTransaction.h"
+#include "nsICancelable.h"
+#include "nsICachingChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIOService.h"
+#include "nsISeekableStream.h"
+#include "nsURLHelper.h"
+#include "ProxyConfigLookup.h"
+#include "TRRLoadInfo.h"
+#include "ReferrerInfo.h"
+#include "TRR.h"
+#include "TRRService.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(TRRServiceChannel)
+
+// Because nsSupportsWeakReference isn't thread-safe we must ensure that
+// TRRServiceChannel is destroyed on the target thread. Any Release() called
+// on a different thread is dispatched to the target thread.
+bool TRRServiceChannel::DispatchRelease() {
+ if (mCurrentEventTarget->IsOnCurrentThread()) {
+ return false;
+ }
+
+ mCurrentEventTarget->Dispatch(
+ NewNonOwningRunnableMethod("net::TRRServiceChannel::Release", this,
+ &TRRServiceChannel::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TRRServiceChannel::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the target thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "TRRServiceChannel");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(TRRServiceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(TRRServiceChannel)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+TRRServiceChannel::TRRServiceChannel()
+ : HttpAsyncAborter<TRRServiceChannel>(this),
+ mProxyRequest(nullptr, "TRRServiceChannel::mProxyRequest"),
+ mCurrentEventTarget(GetCurrentSerialEventTarget()) {
+ LOG(("TRRServiceChannel ctor [this=%p]\n", this));
+}
+
+TRRServiceChannel::~TRRServiceChannel() {
+ LOG(("TRRServiceChannel dtor [this=%p]\n", this));
+}
+
+NS_IMETHODIMP TRRServiceChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP TRRServiceChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::Cancel(nsresult status) {
+ LOG(("TRRServiceChannel::Cancel [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = status;
+
+ nsCOMPtr<nsICancelable> proxyRequest;
+ {
+ auto req = mProxyRequest.Lock();
+ proxyRequest.swap(*req);
+ }
+
+ if (proxyRequest) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "CancelProxyRequest",
+ [proxyRequest, status]() { proxyRequest->Cancel(status); }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ CancelNetworkRequest(status);
+ return NS_OK;
+}
+
+void TRRServiceChannel::CancelNetworkRequest(nsresult aStatus) {
+ if (mTransaction) {
+ nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
+ if (NS_FAILED(rv)) {
+ LOG(("failed to cancel the transaction\n"));
+ }
+ }
+ if (mTransactionPump) mTransactionPump->Cancel(aStatus);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::Suspend() {
+ LOG(("TRRServiceChannel::SuspendInternal [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ return mTransactionPump->Suspend();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::Resume() {
+ LOG(("TRRServiceChannel::Resume [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ return mTransactionPump->Resume();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetSecurityInfo(nsITransportSecurityInfo** securityInfo) {
+ NS_ENSURE_ARG_POINTER(securityInfo);
+ *securityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::AsyncOpen(nsIStreamListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ if (mCanceled) {
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ // HttpBaseChannel::MaybeWaitForUploadStreamNormalization can only be used on
+ // main thread, so we can only return an error here.
+#ifdef NIGHTLY_BUILD
+ MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
+#endif
+ if (LoadPendingUploadStreamNormalization()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gHttpHandler->Active()) {
+ LOG((" after HTTP shutdown..."));
+ ReleaseListeners();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ StoreIsPending(true);
+ StoreWasOpened(true);
+
+ mListener = aListener;
+
+ mAsyncOpenTime = TimeStamp::Now();
+
+ rv = MaybeResolveProxyAndBeginConnect();
+ if (NS_FAILED(rv)) {
+ Unused << AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::MaybeResolveProxyAndBeginConnect() {
+ nsresult rv;
+
+ // The common case for HTTP channels is to begin proxy resolution and return
+ // at this point. The only time we know mProxyInfo already is if we're
+ // proxying a non-http protocol like ftp. We don't need to discover proxy
+ // settings if we are never going to make a network connection.
+ // If mConnectionInfo is already supplied, we don't need to do proxy
+ // resolution again.
+ if (!mProxyInfo && !mConnectionInfo &&
+ !(mLoadFlags & (nsICachingChannel::LOAD_ONLY_FROM_CACHE |
+ nsICachingChannel::LOAD_NO_NETWORK_IO)) &&
+ NS_SUCCEEDED(ResolveProxy())) {
+ return NS_OK;
+ }
+
+ rv = BeginConnect();
+ if (NS_FAILED(rv)) {
+ Unused << AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::ResolveProxy() {
+ LOG(("TRRServiceChannel::ResolveProxy [this=%p]\n", this));
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(
+ NewRunnableMethod("TRRServiceChannel::ResolveProxy", this,
+ &TRRServiceChannel::ResolveProxy),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO: bug 1625171. Consider moving proxy resolution to socket process.
+ RefPtr<TRRServiceChannel> self = this;
+ nsCOMPtr<nsICancelable> proxyRequest;
+ nsresult rv = ProxyConfigLookup::Create(
+ [self](nsIProxyInfo* aProxyInfo, nsresult aStatus) {
+ self->OnProxyAvailable(nullptr, nullptr, aProxyInfo, aStatus);
+ },
+ mURI, mProxyResolveFlags, getter_AddRefs(proxyRequest));
+
+ if (NS_FAILED(rv)) {
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ return mCurrentEventTarget->Dispatch(
+ NewRunnableMethod<nsresult>("TRRServiceChannel::AsyncAbort", this,
+ &TRRServiceChannel::AsyncAbort, rv),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+
+ {
+ auto req = mProxyRequest.Lock();
+ // We only set mProxyRequest if the channel hasn't already been cancelled
+ // on another thread.
+ if (!mCanceled) {
+ *req = proxyRequest.forget();
+ }
+ }
+
+ // If the channel has been cancelled, we go ahead and cancel the proxy
+ // request right here.
+ if (proxyRequest) {
+ proxyRequest->Cancel(mStatus);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnProxyAvailable(nsICancelable* request, nsIChannel* channel,
+ nsIProxyInfo* pi, nsresult status) {
+ LOG(("TRRServiceChannel::OnProxyAvailable [this=%p pi=%p status=%" PRIx32
+ " mStatus=%" PRIx32 "]\n",
+ this, pi, static_cast<uint32_t>(status),
+ static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ RefPtr<TRRServiceChannel> self = this;
+ nsCOMPtr<nsIProxyInfo> info = pi;
+ return mCurrentEventTarget->Dispatch(
+ NS_NewRunnableFunction("TRRServiceChannel::OnProxyAvailable",
+ [self, info, status]() {
+ self->OnProxyAvailable(nullptr, nullptr, info,
+ status);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(mCurrentEventTarget->IsOnCurrentThread());
+
+ {
+ auto proxyRequest = mProxyRequest.Lock();
+ *proxyRequest = nullptr;
+ }
+
+ nsresult rv;
+
+ // If status is a failure code, then it means that we failed to resolve
+ // proxy info. That is a non-fatal error assuming it wasn't because the
+ // request was canceled. We just failover to DIRECT when proxy resolution
+ // fails (failure can mean that the PAC URL could not be loaded).
+
+ if (NS_SUCCEEDED(status)) mProxyInfo = pi;
+
+ if (!gHttpHandler->Active()) {
+ LOG(
+ ("nsHttpChannel::OnProxyAvailable [this=%p] "
+ "Handler no longer active.\n",
+ this));
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ rv = BeginConnect();
+ }
+
+ if (NS_FAILED(rv)) {
+ Unused << AsyncAbort(rv);
+ }
+ return rv;
+}
+
+nsresult TRRServiceChannel::BeginConnect() {
+ LOG(("TRRServiceChannel::BeginConnect [this=%p]\n", this));
+ nsresult rv;
+
+ // Construct connection info object
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+ bool isHttps = mURI->SchemeIs("https");
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Just a warning here because some nsIURIs do not implement this method.
+ Unused << NS_WARN_IF(NS_FAILED(mURI->GetUsername(mUsername)));
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) {
+ rv = NS_ERROR_MALFORMED_URI;
+ return rv;
+ }
+ LOG(("host=%s port=%d\n", host.get(), port));
+ LOG(("uri=%s\n", mSpec.get()));
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (mProxyInfo) proxyInfo = do_QueryInterface(mProxyInfo);
+
+ mRequestHead.SetHTTPS(isHttps);
+ mRequestHead.SetOrigin(scheme, host, port);
+
+ RefPtr<nsHttpConnectionInfo> connInfo = new nsHttpConnectionInfo(
+ host, port, ""_ns, mUsername, proxyInfo, OriginAttributes(), isHttps);
+ // TODO: Bug 1622778 for using AltService in socket process.
+ StoreAllowAltSvc(XRE_IsParentProcess() && LoadAllowAltSvc());
+ bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo);
+ bool http3Allowed = Http3Allowed();
+ if (!http3Allowed) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+
+ RefPtr<AltSvcMapping> mapping;
+ if (!mConnectionInfo && LoadAllowAltSvc() && // per channel
+ (http2Allowed || http3Allowed) && !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
+ AltSvcMapping::AcceptableProxy(proxyInfo) &&
+ (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
+ (mapping = gHttpHandler->GetAltServiceMapping(
+ scheme, host, port, mPrivateBrowsing, OriginAttributes(),
+ http2Allowed, http3Allowed))) {
+ LOG(("TRRServiceChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n",
+ this, scheme.get(), mapping->AlternateHost().get(),
+ mapping->AlternatePort(), mapping->HashKey().get()));
+
+ if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
+ nsAutoCString altUsedLine(mapping->AlternateHost());
+ bool defaultPort =
+ mapping->AlternatePort() ==
+ (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
+ if (!defaultPort) {
+ altUsedLine.AppendLiteral(":");
+ altUsedLine.AppendInt(mapping->AlternatePort());
+ }
+ rv = mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ LOG(("TRRServiceChannel %p Using connection info from altsvc mapping",
+ this));
+ mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo,
+ OriginAttributes());
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+ } else if (mConnectionInfo) {
+ LOG(("TRRServiceChannel %p Using channel supplied connection info", this));
+ } else {
+ LOG(("TRRServiceChannel %p Using default connection info", this));
+
+ mConnectionInfo = connInfo;
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ }
+
+ // Need to re-ask the handler, since mConnectionInfo may not be the connInfo
+ // we used earlier
+ if (gHttpHandler->IsHttp2Excluded(mConnectionInfo)) {
+ StoreAllowSpdy(0);
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ mConnectionInfo->SetNoSpdy(true);
+ }
+
+ // If TimingEnabled flag is not set after OnModifyRequest() then
+ // clear the already recorded AsyncOpen value for consistency.
+ if (!LoadTimingEnabled()) mAsyncOpenTime = TimeStamp();
+
+ // if this somehow fails we can go on without it
+ Unused << gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
+
+ // Adjust mCaps according to our request headers:
+ // - If "Connection: close" is set as a request header, then do not bother
+ // trying to establish a keep-alive connection.
+ if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close")) {
+ mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE);
+ }
+
+ if (gHttpHandler->CriticalRequestPrioritization()) {
+ if (mClassOfService.Flags() & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::UrgentStart &&
+ gHttpHandler->IsUrgentStartEnabled()) {
+ mCaps |= NS_HTTP_URGENT_START;
+ SetPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+ }
+
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ MaybeStartDNSPrefetch();
+
+ rv = ContinueOnBeforeConnect();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::ContinueOnBeforeConnect() {
+ LOG(("TRRServiceChannel::ContinueOnBeforeConnect [this=%p]\n", this));
+
+ // ensure that we are using a valid hostname
+ if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin()))) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (LoadIsTRRServiceChannel()) {
+ mCaps |= NS_HTTP_LARGE_KEEPALIVE;
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+
+ mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
+ mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
+ LoadBeConservative());
+ mConnectionInfo->SetTlsFlags(mTlsFlags);
+ mConnectionInfo->SetIsTrrServiceChannel(LoadIsTRRServiceChannel());
+ mConnectionInfo->SetTRRMode(nsIRequest::GetTRRMode());
+ mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
+ mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
+
+ if (mLoadFlags & LOAD_FRESH_CONNECTION) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_TRR_CONNECTION_CYCLE_COUNT,
+ NS_ConvertUTF8toUTF16(TRRService::ProviderKey()), 1);
+ nsresult rv =
+ gHttpHandler->ConnMgr()->DoSingleConnectionCleanup(mConnectionInfo);
+ LOG(
+ ("TRRServiceChannel::BeginConnect "
+ "DoSingleConnectionCleanup succeeded=%d %08x [this=%p]",
+ NS_SUCCEEDED(rv), static_cast<uint32_t>(rv), this));
+ }
+
+ return Connect();
+}
+
+nsresult TRRServiceChannel::Connect() {
+ LOG(("TRRServiceChannel::Connect [this=%p]\n", this));
+
+ nsresult rv = SetupTransaction();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mTransaction->AsyncRead(this, getter_AddRefs(mTransactionPump));
+}
+
+nsresult TRRServiceChannel::SetupTransaction() {
+ LOG((
+ "TRRServiceChannel::SetupTransaction "
+ "[this=%p, cos=%lu, inc=%d, prio=%d]\n",
+ this, mClassOfService.Flags(), mClassOfService.Incremental(), mPriority));
+
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ if (!LoadAllowSpdy()) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ // Check a proxy info from mConnectionInfo. TRR channel may use a proxy that
+ // is set in mConnectionInfo but acutally the channel do not have mProxyInfo
+ // set. This can happend when network.trr.async_connInfo is true.
+ bool useNonDirectProxy = mConnectionInfo->ProxyInfo()
+ ? !mConnectionInfo->ProxyInfo()->IsDirect()
+ : false;
+ if (!Http3Allowed() || useNonDirectProxy) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ if (LoadBeConservative()) {
+ mCaps |= NS_HTTP_BE_CONSERVATIVE;
+ }
+
+ // Use the URI path if not proxying (transparent proxying such as proxy
+ // CONNECT does not count here). Also figure out what HTTP version to use.
+ nsAutoCString buf, path;
+ nsCString* requestURI;
+
+ // This is the normal e2e H1 path syntax "/index.html"
+ rv = mURI->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // path may contain UTF-8 characters, so ensure that they're escaped.
+ if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII | esc_Spaces,
+ buf)) {
+ requestURI = &buf;
+ } else {
+ requestURI = &path;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref1 = requestURI->FindChar('#');
+ if (ref1 != kNotFound) {
+ requestURI->SetLength(ref1);
+ }
+
+ if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
+ mRequestHead.SetVersion(gHttpHandler->HttpVersion());
+ } else {
+ mRequestHead.SetPath(*requestURI);
+
+ // RequestURI should be the absolute uri H1 proxy syntax
+ // "http://foo/index.html" so we will overwrite the relative version in
+ // requestURI
+ rv = mURI->GetUserPass(buf);
+ if (NS_FAILED(rv)) return rv;
+ if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
+ strncmp(mSpec.get(), "https:", 6) == 0)) {
+ nsCOMPtr<nsIURI> tempURI = nsIOService::CreateExposableURI(mURI);
+ rv = tempURI->GetAsciiSpec(path);
+ if (NS_FAILED(rv)) return rv;
+ requestURI = &path;
+ } else {
+ requestURI = &mSpec;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref2 = requestURI->FindChar('#');
+ if (ref2 != kNotFound) {
+ requestURI->SetLength(ref2);
+ }
+
+ mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
+ }
+
+ mRequestHead.SetRequestURI(*requestURI);
+
+ // Force setting no-cache header for TRRServiceChannel.
+ // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
+ // no proxy is configured since we might be talking with a transparent
+ // proxy, i.e. one that operates at the network level. See bug #14772.
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
+ // no-cache'
+ if (mRequestHead.Version() >= HttpVersion::v1_1) {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // create wrapper for this channel's notification callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ // create the transaction object
+ mTransaction = new nsHttpTransaction();
+ LOG1(("TRRServiceChannel %p created nsHttpTransaction %p\n", this,
+ mTransaction.get()));
+
+ // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
+ if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
+
+ if (LoadTimingEnabled()) mCaps |= NS_HTTP_TIMING_ENABLED;
+
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+ HttpTransactionShell::OnPushCallback pushCallback = nullptr;
+ if (pushListener) {
+ mCaps |= NS_HTTP_ONPUSH_LISTENER;
+ nsWeakPtr weakPtrThis(
+ do_GetWeakReference(static_cast<nsIHttpChannel*>(this)));
+ pushCallback = [weakPtrThis](uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis)) {
+ return static_cast<TRRServiceChannel*>(channel.get())
+ ->OnPush(aPushedStreamId, aUrl, aRequestString, aTransaction);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ };
+ }
+
+ EnsureRequestContext();
+
+ rv = mTransaction->Init(
+ mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
+ LoadUploadStreamHasHeaders(), mCurrentEventTarget, callbacks, this,
+ mBrowserId, HttpTrafficCategory::eInvalid, mRequestContext,
+ mClassOfService, mInitialRwin, LoadResponseTimeoutEnabled(), mChannelId,
+ nullptr, std::move(pushCallback), mTransWithPushedStream,
+ mPushedStreamId);
+
+ mTransWithPushedStream = nullptr;
+
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ return rv;
+}
+
+void TRRServiceChannel::SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) {
+ MOZ_ASSERT(!mTransWithPushedStream);
+ LOG(("TRRServiceChannel::SetPushedStreamTransaction [this=%p] trans=%p", this,
+ aTransWithPushedStream));
+
+ mTransWithPushedStream = aTransWithPushedStream;
+ mPushedStreamId = aPushedStreamId;
+}
+
+nsresult TRRServiceChannel::OnPush(uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ MOZ_ASSERT(aTransaction);
+ LOG(("TRRServiceChannel::OnPush [this=%p, trans=%p]\n", this, aTransaction));
+
+ MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+
+ if (!pushListener) {
+ LOG(
+ ("TRRServiceChannel::OnPush [this=%p] notification callbacks do not "
+ "implement nsIHttpPushListener\n",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIURI> pushResource;
+ nsresult rv;
+
+ // Create a Channel for the Push Resource
+ rv = NS_NewURI(getter_AddRefs(pushResource), aUrl);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<TRRLoadInfo*>(mLoadInfo.get())->Clone();
+ nsCOMPtr<nsIChannel> pushHttpChannel;
+ rv = gHttpHandler->CreateTRRServiceChannel(pushResource, nullptr, 0, nullptr,
+ loadInfo,
+ getter_AddRefs(pushHttpChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pushHttpChannel->SetLoadFlags(mLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<TRRServiceChannel> channel;
+ CallQueryInterface(pushHttpChannel, channel.StartAssignment());
+ MOZ_ASSERT(channel);
+ if (!channel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // new channel needs mrqeuesthead and headers from pushedStream
+ channel->mRequestHead.ParseHeaderSet(aRequestString.BeginReading());
+ channel->mLoadGroup = mLoadGroup;
+ channel->mCallbacks = mCallbacks;
+
+ // Link the pushed stream with the new channel and call listener
+ channel->SetPushedStreamTransactionAndId(aTransaction, aPushedStreamId);
+ rv = pushListener->OnPush(this, channel);
+ return rv;
+}
+
+void TRRServiceChannel::MaybeStartDNSPrefetch() {
+ if (mConnectionInfo->UsingHttpProxy() ||
+ (mLoadFlags & (nsICachingChannel::LOAD_NO_NETWORK_IO |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE))) {
+ return;
+ }
+
+ LOG(
+ ("TRRServiceChannel::MaybeStartDNSPrefetch [this=%p] "
+ "prefetching%s\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
+
+ OriginAttributes originAttributes;
+ mDNSPrefetch =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode(), this,
+ LoadTimingEnabled());
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ dnsFlags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ nsresult rv = mDNSPrefetch->PrefetchHigh(dnsFlags);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::CallOnStartRequest() {
+ LOG(("TRRServiceChannel::CallOnStartRequest [this=%p]", this));
+
+ if (LoadOnStartRequestCalled()) {
+ LOG(("CallOnStartRequest already invoked before"));
+ return mStatus;
+ }
+
+ nsresult rv = NS_OK;
+ StoreTracingEnabled(false);
+
+ // Ensure mListener->OnStartRequest will be invoked before exiting
+ // this function.
+ auto onStartGuard = MakeScopeExit([&] {
+ LOG(
+ (" calling mListener->OnStartRequest by ScopeExit [this=%p, "
+ "listener=%p]\n",
+ this, mListener.get()));
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ deleteProtector->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+ });
+
+ if (mResponseHead && !mResponseHead->HasContentCharset()) {
+ mResponseHead->SetContentCharset(mContentCharsetHint);
+ }
+
+ LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
+ mListener.get()));
+
+ // About to call OnStartRequest, dismiss the guard object.
+ onStartGuard.release();
+
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OsStartRequest twice");
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ rv = deleteProtector->OnStartRequest(this);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ StoreOnStartRequestCalled(true);
+ }
+
+ if (!mResponseHead) {
+ return NS_OK;
+ }
+
+ nsAutoCString contentEncoding;
+ rv = mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ if (NS_FAILED(rv) || contentEncoding.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // DoApplyContentConversions can only be called on the main thread.
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIStreamListener> listener;
+ rv =
+ DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ AfterApplyContentConversions(rv, listener);
+ return NS_OK;
+ }
+
+ Suspend();
+
+ RefPtr<TRRServiceChannel> self = this;
+ rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction("TRRServiceChannel::DoApplyContentConversions",
+ [self]() {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = self->DoApplyContentConversions(
+ self->mListener, getter_AddRefs(listener),
+ nullptr);
+ self->AfterApplyContentConversions(rv, listener);
+ }),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ Resume();
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void TRRServiceChannel::AfterApplyContentConversions(
+ nsresult aResult, nsIStreamListener* aListener) {
+ LOG(("TRRServiceChannel::AfterApplyContentConversions [this=%p]", this));
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ RefPtr<TRRServiceChannel> self = this;
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ self->mCurrentEventTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "TRRServiceChannel::AfterApplyContentConversions",
+ [self, aResult, listener]() {
+ self->Resume();
+ self->AfterApplyContentConversions(aResult, listener);
+ }),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ if (NS_FAILED(aResult)) {
+ Unused << AsyncAbort(aResult);
+ return;
+ }
+
+ if (aListener) {
+ mListener = aListener;
+ mCompressListener = aListener;
+ StoreHasAppliedConversion(true);
+ }
+}
+
+void TRRServiceChannel::ProcessAltService() {
+ // e.g. Alt-Svc: h2=":443"; ma=60
+ // e.g. Alt-Svc: h2="otherhost:443"
+ // Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
+ // alternative = protocol-id "=" alt-authority
+ // protocol-id = token ; percent-encoded ALPN protocol identifier
+ // alt-authority = quoted-string ; containing [ uri-host ] ":" port
+
+ if (!LoadAllowAltSvc()) { // per channel opt out
+ return;
+ }
+
+ if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
+ return;
+ }
+
+ nsCString scheme;
+ mURI->GetScheme(scheme);
+ bool isHttp = scheme.EqualsLiteral("http");
+ if (!isHttp && !scheme.EqualsLiteral("https")) {
+ return;
+ }
+
+ nsCString altSvc;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (altSvc.IsEmpty()) {
+ return;
+ }
+
+ if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
+ LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
+ return;
+ }
+
+ nsCString originHost;
+ int32_t originPort = 80;
+ mURI->GetPort(&originPort);
+ if (NS_FAILED(mURI->GetAsciiHost(originHost))) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (mProxyInfo) {
+ proxyInfo = do_QueryInterface(mProxyInfo);
+ }
+
+ auto processHeaderTask = [altSvc, scheme, originHost, originPort,
+ userName(mUsername),
+ privateBrowsing(mPrivateBrowsing), callbacks,
+ proxyInfo, caps(mCaps)]() {
+ if (XRE_IsSocketProcess()) {
+ AltServiceChild::ProcessHeader(altSvc, scheme, originHost, originPort,
+ userName, privateBrowsing, callbacks,
+ proxyInfo, caps & NS_HTTP_DISALLOW_SPDY,
+ OriginAttributes());
+ return;
+ }
+
+ AltSvcMapping::ProcessHeader(
+ altSvc, scheme, originHost, originPort, userName, privateBrowsing,
+ callbacks, proxyInfo, caps & NS_HTTP_DISALLOW_SPDY, OriginAttributes());
+ };
+
+ if (NS_IsMainThread()) {
+ processHeaderTask();
+ return;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "TRRServiceChannel::ProcessAltService", std::move(processHeaderTask)));
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnStartRequest(nsIRequest* request) {
+ LOG(("TRRServiceChannel::OnStartRequest [this=%p request=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+
+ if (!(mCanceled || NS_FAILED(mStatus))) {
+ // capture the request's status, so our consumers will know ASAP of any
+ // connection failures, etc - bug 93581
+ nsresult status;
+ request->GetStatus(&status);
+ mStatus = status;
+ }
+
+ MOZ_ASSERT(request == mTransactionPump, "Unexpected request");
+
+ StoreAfterOnStartRequestBegun(true);
+ if (mTransaction) {
+ if (!mSecurityInfo) {
+ // grab the security info from the connection object; the transaction
+ // is guaranteed to own a reference to the connection.
+ mSecurityInfo = mTransaction->SecurityInfo();
+ }
+ }
+
+ if (NS_SUCCEEDED(mStatus) && mTransaction) {
+ // mTransactionPump doesn't hit OnInputStreamReady and call this until
+ // all of the response headers have been acquired, so we can take
+ // ownership of them from the transaction.
+ mResponseHead = mTransaction->TakeResponseHead();
+ if (mResponseHead) {
+ uint32_t httpStatus = mResponseHead->Status();
+ if (mTransaction->ProxyConnectFailed()) {
+ LOG(("TRRServiceChannel proxy connect failed httpStatus: %d",
+ httpStatus));
+ MOZ_ASSERT(mConnectionInfo->UsingConnect(),
+ "proxy connect failed but not using CONNECT?");
+ nsresult rv = HttpProxyResponseToErrorCode(httpStatus);
+ mTransaction->DontReuseConnection();
+ Cancel(rv);
+ return CallOnStartRequest();
+ }
+
+ if ((httpStatus < 500) && (httpStatus != 421) && (httpStatus != 407)) {
+ ProcessAltService();
+ }
+
+ if (httpStatus == 300 || httpStatus == 301 || httpStatus == 302 ||
+ httpStatus == 303 || httpStatus == 307 || httpStatus == 308) {
+ nsresult rv = SyncProcessRedirection(httpStatus);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ mStatus = rv;
+ DoNotifyListener();
+ return rv;
+ }
+ } else {
+ NS_WARNING("No response head in OnStartRequest");
+ }
+ }
+
+ // avoid crashing if mListener happens to be null...
+ if (!mListener) {
+ MOZ_ASSERT_UNREACHABLE("mListener is null");
+ return NS_OK;
+ }
+
+ return CallOnStartRequest();
+}
+
+nsresult TRRServiceChannel::SyncProcessRedirection(uint32_t aHttpStatus) {
+ nsAutoCString location;
+
+ // if a location header was not given, then we can't perform the redirect,
+ // so just carry on as though this were a normal response.
+ if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
+ locationBuf)) {
+ location = locationBuf;
+ }
+
+ LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
+ uint32_t(mRedirectionLimit)));
+
+ nsCOMPtr<nsIURI> redirectURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(redirectURI), location);
+
+ if (NS_FAILED(rv)) {
+ LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // move the reference of the old location to the new one if the new
+ // one has none.
+ PropagateReferenceIfNeeded(mURI, redirectURI);
+
+ bool rewriteToGET =
+ ShouldRewriteRedirectToGET(aHttpStatus, mRequestHead.ParsedMethod());
+
+ // Let's not rewrite the method to GET for TRR requests.
+ if (rewriteToGET) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the method is not safe (such as POST, PUT, DELETE, ...)
+ if (!mRequestHead.IsSafeMethod()) {
+ LOG(("TRRServiceChannel: unsafe redirect to:%s\n", location.get()));
+ }
+
+ uint32_t redirectFlags;
+ if (nsHttp::IsPermanentRedirect(aHttpStatus)) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ } else {
+ redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+ }
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ static_cast<TRRLoadInfo*>(mLoadInfo.get())->Clone();
+ rv = gHttpHandler->CreateTRRServiceChannel(redirectURI, nullptr, 0, nullptr,
+ redirectLoadInfo,
+ getter_AddRefs(newChannel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET,
+ redirectFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ newChannel->SetOriginalURI(mOriginalURI);
+
+ rv = newChannel->AsyncOpen(mListener);
+ LOG((" new channel AsyncOpen returned %" PRIX32, static_cast<uint32_t>(rv)));
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::SetupReplacementChannel(nsIURI* aNewURI,
+ nsIChannel* aNewChannel,
+ bool aPreserveMethod,
+ uint32_t aRedirectFlags) {
+ LOG(
+ ("TRRServiceChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, aNewChannel, aPreserveMethod));
+
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ aNewURI, aNewChannel, aPreserveMethod, aRedirectFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = CheckRedirectLimit(aRedirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ if (!httpChannel) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // convey the ApplyConversion flag (bug 91862)
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel) {
+ encodedChannel->SetApplyConversion(LoadApplyConversion());
+ }
+
+ if (mContentTypeHint.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Make sure we set content-type on the old channel properly.
+ MOZ_ASSERT(mContentTypeHint.Equals("application/dns-message"));
+
+ // Apply TRR specific settings. Note that we already know mContentTypeHint is
+ // "application/dns-message" here.
+ return TRR::SetupTRRServiceChannelInternal(
+ httpChannel,
+ mRequestHead.ParsedMethod() == nsHttpRequestHead::kMethod_Get,
+ mContentTypeHint);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* input,
+ uint64_t offset, uint32_t count) {
+ LOG(("TRRServiceChannel::OnDataAvailable [this=%p request=%p offset=%" PRIu64
+ " count=%" PRIu32 "]\n",
+ this, request, offset, count));
+
+ // don't send out OnDataAvailable notifications if we've been canceled.
+ if (mCanceled) return mStatus;
+
+ MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
+
+ if (mListener) {
+ return mListener->OnDataAvailable(this, input, offset, count);
+ }
+
+ return NS_ERROR_ABORT;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnStopRequest(nsIRequest* request, nsresult status) {
+ LOG(("TRRServiceChannel::OnStopRequest [this=%p request=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(status)));
+
+ if (mCanceled || NS_FAILED(mStatus)) status = mStatus;
+
+ mTransactionTimings = mTransaction->Timings();
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+
+ if (mListener) {
+ LOG(("TRRServiceChannel %p calling OnStopRequest\n", this));
+ MOZ_ASSERT(LoadOnStartRequestCalled(),
+ "OnStartRequest should be called before OnStopRequest");
+ MOZ_ASSERT(!LoadOnStopRequestCalled(),
+ "We should not call OnStopRequest twice");
+ StoreOnStopRequestCalled(true);
+ mListener->OnStopRequest(this, status);
+ }
+ StoreOnStopRequestCalled(true);
+
+ mDNSPrefetch = nullptr;
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, status);
+ }
+
+ ReleaseListeners();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ LOG(
+ ("TRRServiceChannel::OnLookupComplete [this=%p] prefetch complete%s: "
+ "%s status[0x%" PRIx32 "]\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
+ NS_SUCCEEDED(status) ? "success" : "failure",
+ static_cast<uint32_t>(status)));
+
+ // We no longer need the dns prefetch object. Note: mDNSPrefetch could be
+ // validly null if OnStopRequest has already been called.
+ // We only need the domainLookup timestamps when not loading from cache
+ if (mDNSPrefetch && mDNSPrefetch->TimingsValid() && mTransaction) {
+ TimeStamp connectStart = mTransaction->GetConnectStart();
+ TimeStamp requestStart = mTransaction->GetRequestStart();
+ // We only set the domainLookup timestamps if we're not using a
+ // persistent connection.
+ if (requestStart.IsNull() && connectStart.IsNull()) {
+ mTransaction->SetDomainLookupStart(mDNSPrefetch->StartTimestamp());
+ mTransaction->SetDomainLookupEnd(mDNSPrefetch->EndTimestamp());
+ }
+ }
+ mDNSPrefetch = nullptr;
+
+ // Unset DNS cache refresh if it was requested,
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ mCaps &= ~NS_HTTP_REFRESH_DNS;
+ if (mTransaction) {
+ mTransaction->SetDNSWasRefreshed();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetPriority(int32_t value) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRServiceChannel::OnClassOfServiceUpdated() {
+ LOG(("TRRServiceChannel::OnClassOfServiceUpdated this=%p, cos=%lu inc=%d",
+ this, mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (mTransaction) {
+ gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
+ mClassOfService);
+ }
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags);
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetIncremental(bool inFlag) {
+ bool previous = mClassOfService.Incremental();
+ mClassOfService.SetIncremental(inFlag);
+ if (previous != mClassOfService.Incremental()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetClassOfService(ClassOfService cos) {
+ ClassOfService previous = mClassOfService;
+ mClassOfService = cos;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::AddClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags | mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::ClearClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(~inFlags & mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRServiceChannel::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetProxyInfo(nsIProxyInfo** result) {
+ if (!mConnectionInfo) {
+ *result = do_AddRef(mProxyInfo).take();
+ } else {
+ *result = do_AddRef(mConnectionInfo->ProxyInfo()).take();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP TRRServiceChannel::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ NS_ENSURE_ARG_POINTER(aResponseCode);
+
+ *aResponseCode = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ return HttpBaseChannel::GetLoadFlags(aLoadFlags);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ if (aLoadFlags & (nsICachingChannel::LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
+ nsICachingChannel::LOAD_NO_NETWORK_IO)) {
+ MOZ_ASSERT(false, "Wrong load flags!");
+ return NS_ERROR_FAILURE;
+ }
+
+ return HttpBaseChannel::SetLoadFlags(aLoadFlags);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetURI(nsIURI** aURI) {
+ return HttpBaseChannel::GetURI(aURI);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) {
+ return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ return HttpBaseChannel::GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetRequestMethod(nsACString& aMethod) {
+ return HttpBaseChannel::GetRequestMethod(aMethod);
+}
+
+void TRRServiceChannel::DoNotifyListener() {
+ LOG(("TRRServiceChannel::DoNotifyListener this=%p", this));
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set AfterOnStartRequestBegun to true
+ // before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+}
+
+void TRRServiceChannel::DoNotifyListenerCleanup() {}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupStart();
+ } else {
+ *_retval = mTransactionTimings.domainLookupStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupEnd();
+ } else {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetConnectStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectStart();
+ } else {
+ *_retval = mTransactionTimings.connectStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetTcpConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetTcpConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.tcpConnectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetSecureConnectionStart();
+ } else {
+ *_retval = mTransactionTimings.secureConnectionStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.connectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetRequestStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetRequestStart();
+ } else {
+ *_retval = mTransactionTimings.requestStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetResponseStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseStart();
+ } else {
+ *_retval = mTransactionTimings.responseStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetResponseEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseEnd();
+ } else {
+ *_retval = mTransactionTimings.responseEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP TRRServiceChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+bool TRRServiceChannel::SameOriginWithOriginalUri(nsIURI* aURI) { return true; }
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/TRRServiceChannel.h b/netwerk/protocol/http/TRRServiceChannel.h
new file mode 100644
index 0000000000..0d726deb57
--- /dev/null
+++ b/netwerk/protocol/http/TRRServiceChannel.h
@@ -0,0 +1,177 @@
+/* -*- 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_TRRServiceChannel_h
+#define mozilla_net_TRRServiceChannel_h
+
+#include "HttpBaseChannel.h"
+#include "mozilla/DataMutex.h"
+#include "nsIDNSListener.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIProxiedChannel.h"
+#include "nsIStreamListener.h"
+#include "nsWeakReference.h"
+
+class nsDNSPrefetch;
+
+namespace mozilla::net {
+
+class HttpTransactionShell;
+class nsHttpHandler;
+
+// Use to support QI nsIChannel to TRRServiceChannel
+#define NS_TRRSERVICECHANNEL_IID \
+ { \
+ 0x361c4bb1, 0xd6b2, 0x493b, { \
+ 0x86, 0xbc, 0x88, 0xd3, 0x5d, 0x16, 0x38, 0xfa \
+ } \
+ }
+
+// TRRServiceChannel is designed to fetch DNS data from DoH server. This channel
+// MUST only be used by TRR.
+class TRRServiceChannel : public HttpBaseChannel,
+ public HttpAsyncAborter<TRRServiceChannel>,
+ public nsIDNSListener,
+ public nsIStreamListener,
+ public nsITransportEventSink,
+ public nsIProxiedChannel,
+ public nsIProtocolProxyCallback,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TRRSERVICECHANNEL_IID)
+
+ // nsIRequest
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ NS_IMETHOD GetLoadFlags(nsLoadFlags* aLoadFlags) override;
+ NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+ NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
+
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aCallbacks) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD SetIncremental(bool inFlag) override;
+ NS_IMETHOD SetClassOfService(ClassOfService cos) override;
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+ NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override {
+ return NS_OK;
+ }
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override {
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult OnPush(uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction);
+ void SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId);
+
+ // nsITimedChannel
+ NS_IMETHOD GetDomainLookupStart(
+ mozilla::TimeStamp* aDomainLookupStart) override;
+ NS_IMETHOD GetDomainLookupEnd(mozilla::TimeStamp* aDomainLookupEnd) override;
+ NS_IMETHOD GetConnectStart(mozilla::TimeStamp* aConnectStart) override;
+ NS_IMETHOD GetTcpConnectEnd(mozilla::TimeStamp* aTcpConnectEnd) override;
+ NS_IMETHOD GetSecureConnectionStart(
+ mozilla::TimeStamp* aSecureConnectionStart) override;
+ NS_IMETHOD GetConnectEnd(mozilla::TimeStamp* aConnectEnd) override;
+ NS_IMETHOD GetRequestStart(mozilla::TimeStamp* aRequestStart) override;
+ NS_IMETHOD GetResponseStart(mozilla::TimeStamp* aResponseStart) override;
+ NS_IMETHOD GetResponseEnd(mozilla::TimeStamp* aResponseEnd) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ NS_IMETHOD TimingAllowCheck(nsIPrincipal* aOrigin, bool* aResult) override;
+
+ protected:
+ TRRServiceChannel();
+ virtual ~TRRServiceChannel();
+
+ void CancelNetworkRequest(nsresult aStatus);
+ nsresult BeginConnect();
+ nsresult ContinueOnBeforeConnect();
+ nsresult Connect();
+ nsresult SetupTransaction();
+ void OnClassOfServiceUpdated();
+ virtual void DoNotifyListenerCleanup() override;
+ virtual void DoAsyncAbort(nsresult aStatus) override;
+ bool IsIsolated() { return false; };
+ void ProcessAltService();
+ nsresult CallOnStartRequest();
+
+ void MaybeStartDNSPrefetch();
+ void DoNotifyListener();
+ nsresult MaybeResolveProxyAndBeginConnect();
+ nsresult ResolveProxy();
+ void AfterApplyContentConversions(nsresult aResult,
+ nsIStreamListener* aListener);
+ nsresult SyncProcessRedirection(uint32_t aHttpStatus);
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI* aNewURI, nsIChannel* aNewChannel, bool aPreserveMethod,
+ uint32_t aRedirectFlags) override;
+ // Skip this check for TRRServiceChannel.
+ virtual bool ShouldTaintReplacementChannelOrigin(
+ nsIChannel* aNewChannel, uint32_t aRedirectFlags) override {
+ return false;
+ }
+ virtual bool SameOriginWithOriginalUri(nsIURI* aURI) override;
+ bool DispatchRelease();
+
+ nsCString mUsername;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ nsCOMPtr<nsIRequest> mTransactionPump;
+ RefPtr<HttpTransactionShell> mTransaction;
+ uint32_t mPushedStreamId{0};
+ RefPtr<HttpTransactionShell> mTransWithPushedStream;
+ DataMutex<nsCOMPtr<nsICancelable>> mProxyRequest;
+ nsCOMPtr<nsIEventTarget> mCurrentEventTarget;
+
+ friend class HttpAsyncAborter<TRRServiceChannel>;
+ friend class nsHttpHandler;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TRRServiceChannel, NS_TRRSERVICECHANNEL_IID)
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_TRRServiceChannel_h
diff --git a/netwerk/protocol/http/TimingStruct.h b/netwerk/protocol/http/TimingStruct.h
new file mode 100644
index 0000000000..74645e2bff
--- /dev/null
+++ b/netwerk/protocol/http/TimingStruct.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 TimingStruct_h_
+#define TimingStruct_h_
+
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+struct TimingStruct {
+ TimeStamp domainLookupStart;
+ TimeStamp domainLookupEnd;
+ TimeStamp connectStart;
+ TimeStamp tcpConnectEnd;
+ TimeStamp secureConnectionStart;
+ TimeStamp connectEnd;
+ TimeStamp requestStart;
+ TimeStamp responseStart;
+ TimeStamp responseEnd;
+ TimeStamp transactionPending;
+};
+
+struct ResourceTimingStruct : TimingStruct {
+ TimeStamp fetchStart;
+ TimeStamp redirectStart;
+ TimeStamp redirectEnd;
+ uint64_t transferSize;
+ uint64_t encodedBodySize;
+
+ // Not actually part of resource timing, but not part of the transaction
+ // timings either. These need to be passed to HttpChannelChild along with
+ // the rest of the timings so the timing information in the child is complete.
+ TimeStamp cacheReadStart;
+ TimeStamp cacheReadEnd;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/TlsHandshaker.cpp b/netwerk/protocol/http/TlsHandshaker.cpp
new file mode 100644
index 0000000000..5288fb939d
--- /dev/null
+++ b/netwerk/protocol/http/TlsHandshaker.cpp
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "TlsHandshaker.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsHttpConnection.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "nsITLSSocketControl.h"
+
+#define TLS_EARLY_DATA_NOT_AVAILABLE 0
+#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
+#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(TlsHandshaker, nsITlsHandshakeCallbackListener)
+
+TlsHandshaker::TlsHandshaker(nsHttpConnectionInfo* aInfo,
+ nsHttpConnection* aOwner)
+ : mConnInfo(aInfo), mOwner(aOwner) {
+ LOG(("TlsHandshaker ctor %p", this));
+}
+
+TlsHandshaker::~TlsHandshaker() { LOG(("TlsHandshaker dtor %p", this)); }
+
+NS_IMETHODIMP
+TlsHandshaker::CertVerificationDone() {
+ LOG(("TlsHandshaker::CertVerificationDone mOwner=%p", mOwner.get()));
+ if (mOwner) {
+ Unused << mOwner->ResumeSend();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TlsHandshaker::ClientAuthCertificateSelected() {
+ LOG(("TlsHandshaker::ClientAuthCertificateSelected mOwner=%p", mOwner.get()));
+ if (mOwner) {
+ Unused << mOwner->ResumeSend();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TlsHandshaker::HandshakeDone() {
+ LOG(("TlsHandshaker::HandshakeDone mOwner=%p", mOwner.get()));
+ if (mOwner) {
+ mTlsHandshakeComplitionPending = true;
+
+ // HandshakeDone needs to be dispatched so that it is not called inside
+ // nss locks.
+ RefPtr<TlsHandshaker> self(this);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "TlsHandshaker::HandshakeDoneInternal", [self{std::move(self)}]() {
+ if (self->mTlsHandshakeComplitionPending && self->mOwner) {
+ self->mOwner->HandshakeDoneInternal();
+ self->mTlsHandshakeComplitionPending = false;
+ }
+ }));
+ }
+ return NS_OK;
+}
+
+void TlsHandshaker::SetupSSL(bool aInSpdyTunnel, bool aForcePlainText) {
+ if (!mOwner) {
+ return;
+ }
+
+ LOG1(("TlsHandshaker::SetupSSL %p caps=0x%X %s\n", mOwner.get(),
+ mOwner->TransactionCaps(), mConnInfo->HashKey().get()));
+
+ if (mSetupSSLCalled) { // do only once
+ return;
+ }
+ mSetupSSLCalled = true;
+
+ if (mNPNComplete) {
+ return;
+ }
+
+ // we flip this back to false if SetNPNList succeeds at the end
+ // of this function
+ mNPNComplete = true;
+
+ if (!mConnInfo->FirstHopSSL() || aForcePlainText) {
+ return;
+ }
+
+ // if we are connected to the proxy with TLS, start the TLS
+ // flow immediately without waiting for a CONNECT sequence.
+ DebugOnly<nsresult> rv{};
+ if (aInSpdyTunnel) {
+ rv = InitSSLParams(false, true);
+ } else {
+ bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
+ rv = InitSSLParams(usingHttpsProxy, usingHttpsProxy);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult TlsHandshaker::InitSSLParams(bool connectingToProxy,
+ bool proxyStartSSL) {
+ LOG(("TlsHandshaker::InitSSLParams [mOwner=%p] connectingToProxy=%d\n",
+ mOwner.get(), connectingToProxy));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mOwner) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
+ if (!ssl) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If proxy is use or 0RTT is excluded for a origin, don't use early-data.
+ if (mConnInfo->UsingProxy() || gHttpHandler->Is0RttTcpExcluded(mConnInfo)) {
+ ssl->DisableEarlyData();
+ }
+
+ if (proxyStartSSL) {
+ nsresult rv = ssl->ProxyStartSSL();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (NS_SUCCEEDED(
+ SetupNPNList(ssl, mOwner->TransactionCaps(), connectingToProxy)) &&
+ NS_SUCCEEDED(ssl->SetHandshakeCallbackListener(this))) {
+ LOG(("InitSSLParams Setting up SPDY Negotiation OK mOwner=%p",
+ mOwner.get()));
+ mNPNComplete = false;
+ }
+
+ return NS_OK;
+}
+
+// The naming of NPN is historical - this function creates the basic
+// offer list for both NPN and ALPN. ALPN validation callbacks are made
+// now before the handshake is complete, and NPN validation callbacks
+// are made during the handshake.
+nsresult TlsHandshaker::SetupNPNList(nsITLSSocketControl* ssl, uint32_t caps,
+ bool connectingToProxy) {
+ nsTArray<nsCString> protocolArray;
+
+ // The first protocol is used as the fallback if none of the
+ // protocols supported overlap with the server's list.
+ // When using ALPN the advertised preferences are protocolArray indicies
+ // {1, .., N, 0} in decreasing order.
+ // For NPN, In the case of overlap, matching priority is driven by
+ // the order of the server's advertisement - with index 0 used when
+ // there is no match.
+ protocolArray.AppendElement("http/1.1"_ns);
+
+ if (StaticPrefs::network_http_http2_enabled() &&
+ (connectingToProxy || !(caps & NS_HTTP_DISALLOW_SPDY)) &&
+ !(connectingToProxy && (caps & NS_HTTP_DISALLOW_HTTP2_PROXY))) {
+ LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (info->ALPNCallbacks(ssl)) {
+ protocolArray.AppendElement(info->VersionString);
+ }
+ } else {
+ LOG(("nsHttpConnection::SetupSSL Disallow SPDY NPN selection"));
+ }
+
+ nsresult rv = ssl->SetNPNList(protocolArray);
+ LOG(("TlsHandshaker::SetupNPNList %p %" PRIx32 "\n", mOwner.get(),
+ static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+// Checks if TLS handshake is needed and it is responsible to move it forward.
+bool TlsHandshaker::EnsureNPNComplete() {
+ if (!mOwner) {
+ mNPNComplete = true;
+ return true;
+ }
+
+ nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
+ MOZ_ASSERT(transport);
+ if (!transport) {
+ // this cannot happen
+ mNPNComplete = true;
+ return true;
+ }
+
+ if (mNPNComplete) {
+ return true;
+ }
+
+ if (mTlsHandshakeComplitionPending) {
+ return false;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
+ if (!ssl) {
+ FinishNPNSetup(false, false);
+ return true;
+ }
+
+ if (!m0RTTChecked) {
+ // We reuse m0RTTChecked. We want to send this status only once.
+ RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
+ nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
+ if (transaction && transport) {
+ transaction->OnTransportStatus(transport,
+ NS_NET_STATUS_TLS_HANDSHAKE_STARTING, 0);
+ }
+ }
+
+ LOG(("TlsHandshaker::EnsureNPNComplete [mOwner=%p] drive TLS handshake",
+ mOwner.get()));
+ nsresult rv = ssl->DriveHandshake();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ FinishNPNSetup(false, true);
+ return true;
+ }
+
+ Check0RttEnabled(ssl);
+ return false;
+}
+
+void TlsHandshaker::EarlyDataDone() {
+ if (mEarlyDataState == EarlyData::USED) {
+ mEarlyDataState = EarlyData::DONE_USED;
+ } else if (mEarlyDataState == EarlyData::CANNOT_BE_USED) {
+ mEarlyDataState = EarlyData::DONE_CANNOT_BE_USED;
+ } else if (mEarlyDataState == EarlyData::NOT_AVAILABLE) {
+ mEarlyDataState = EarlyData::DONE_NOT_AVAILABLE;
+ }
+}
+
+void TlsHandshaker::FinishNPNSetup(bool handshakeSucceeded,
+ bool hasSecurityInfo) {
+ LOG(("TlsHandshaker::FinishNPNSetup mOwner=%p", mOwner.get()));
+ mNPNComplete = true;
+
+ mOwner->PostProcessNPNSetup(handshakeSucceeded, hasSecurityInfo,
+ EarlyDataUsed());
+ EarlyDataDone();
+}
+
+void TlsHandshaker::Check0RttEnabled(nsITLSSocketControl* ssl) {
+ if (!mOwner) {
+ return;
+ }
+
+ if (m0RTTChecked) {
+ return;
+ }
+
+ m0RTTChecked = true;
+
+ if (mConnInfo->UsingProxy()) {
+ return;
+ }
+
+ // There is no ALPN info (yet!). We need to consider doing 0RTT. We
+ // will do so if there is ALPN information from a previous session
+ // (AlpnEarlySelection), we are using HTTP/1, and the request data can
+ // be safely retried.
+ if (NS_FAILED(ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN))) {
+ LOG1(
+ ("TlsHandshaker::Check0RttEnabled %p - "
+ "early selected alpn not available",
+ mOwner.get()));
+ } else {
+ mOwner->ChangeConnectionState(ConnectionState::ZERORTT);
+ LOG1(
+ ("TlsHandshaker::Check0RttEnabled %p -"
+ "early selected alpn: %s",
+ mOwner.get(), mEarlyNegotiatedALPN.get()));
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (!mEarlyNegotiatedALPN.Equals(info->VersionString)) {
+ // This is the HTTP/1 case.
+ // Check if early-data is allowed for this transaction.
+ RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
+ if (transaction && transaction->Do0RTT()) {
+ LOG(
+ ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - We "
+ "can do 0RTT (http/1)!",
+ mOwner.get()));
+ mEarlyDataState = EarlyData::USED;
+ } else {
+ mEarlyDataState = EarlyData::CANNOT_BE_USED;
+ // Poll for read now. Polling for write will cause us to busy wait.
+ // When the handshake is done the polling flags will be set correctly.
+ Unused << mOwner->ResumeRecv();
+ }
+ } else {
+ // We have h2, we can at least 0-RTT the preamble and opening
+ // SETTINGS, etc, and maybe some of the first request
+ LOG(
+ ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - Starting "
+ "0RTT for h2!",
+ mOwner.get()));
+ mEarlyDataState = EarlyData::USED;
+ mOwner->Start0RTTSpdy(info->Version);
+ }
+ }
+}
+
+void TlsHandshaker::EarlyDataTelemetry(int16_t tlsVersion,
+ bool earlyDataAccepted,
+ int64_t aContentBytesWritten0RTT) {
+ // Send the 0RTT telemetry only for tls1.3
+ if (tlsVersion > nsITLSSocketControl::TLS_VERSION_1_2) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
+ (mEarlyDataState == EarlyData::NOT_AVAILABLE)
+ ? TLS_EARLY_DATA_NOT_AVAILABLE
+ : ((mEarlyDataState == EarlyData::USED)
+ ? TLS_EARLY_DATA_AVAILABLE_AND_USED
+ : TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED));
+ if (EarlyDataUsed()) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
+ earlyDataAccepted);
+ }
+ if (earlyDataAccepted) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN,
+ aContentBytesWritten0RTT);
+ }
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/TlsHandshaker.h b/netwerk/protocol/http/TlsHandshaker.h
new file mode 100644
index 0000000000..fc01b07518
--- /dev/null
+++ b/netwerk/protocol/http/TlsHandshaker.h
@@ -0,0 +1,95 @@
+/* -*- 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 TlsHandshaker_h__
+#define TlsHandshaker_h__
+
+#include "nsITlsHandshakeListener.h"
+
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla::net {
+
+class nsHttpConnection;
+class nsHttpConnectionInfo;
+
+class TlsHandshaker : public nsITlsHandshakeCallbackListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSHANDSHAKECALLBACKLISTENER
+
+ TlsHandshaker(nsHttpConnectionInfo* aInfo, nsHttpConnection* aOwner);
+
+ void SetupSSL(bool aInSpdyTunnel, bool aForcePlainText);
+ [[nodiscard]] nsresult InitSSLParams(bool connectingToProxy,
+ bool ProxyStartSSL);
+ [[nodiscard]] nsresult SetupNPNList(nsITLSSocketControl* ssl, uint32_t caps,
+ bool connectingToProxy);
+ // Makes certain the SSL handshake is complete and NPN negotiation
+ // has had a chance to happen
+ [[nodiscard]] bool EnsureNPNComplete();
+ void FinishNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo);
+ bool EarlyDataAvailable() const {
+ return mEarlyDataState == EarlyData::USED ||
+ mEarlyDataState == EarlyData::CANNOT_BE_USED;
+ }
+ bool EarlyDataWasAvailable() const {
+ return mEarlyDataState != EarlyData::NOT_AVAILABLE &&
+ mEarlyDataState != EarlyData::DONE_NOT_AVAILABLE;
+ }
+ bool EarlyDataUsed() const { return mEarlyDataState == EarlyData::USED; }
+ bool EarlyDataCanNotBeUsed() const {
+ return mEarlyDataState == EarlyData::CANNOT_BE_USED;
+ }
+ void EarlyDataDone();
+ void EarlyDataTelemetry(int16_t tlsVersion, bool earlyDataAccepted,
+ int64_t aContentBytesWritten0RTT);
+
+ bool NPNComplete() const { return mNPNComplete; }
+ bool SetupSSLCalled() const { return mSetupSSLCalled; }
+ bool TlsHandshakeComplitionPending() const {
+ return mTlsHandshakeComplitionPending;
+ }
+ const nsCString& EarlyNegotiatedALPN() const { return mEarlyNegotiatedALPN; }
+ void SetNPNComplete() { mNPNComplete = true; }
+ void NotifyClose() {
+ mTlsHandshakeComplitionPending = false;
+ mOwner = nullptr;
+ }
+
+ private:
+ virtual ~TlsHandshaker();
+
+ void Check0RttEnabled(nsITLSSocketControl* ssl);
+
+ // SPDY related
+ bool mSetupSSLCalled{false};
+ bool mNPNComplete{false};
+
+ bool mTlsHandshakeComplitionPending{false};
+ // Helper variable for 0RTT handshake;
+ // Possible 0RTT has been checked.
+ bool m0RTTChecked{false};
+ // 0RTT data state.
+ enum EarlyData {
+ NOT_AVAILABLE,
+ USED,
+ CANNOT_BE_USED,
+ DONE_NOT_AVAILABLE,
+ DONE_USED,
+ DONE_CANNOT_BE_USED,
+ };
+ EarlyData mEarlyDataState{EarlyData::NOT_AVAILABLE};
+ nsCString mEarlyNegotiatedALPN;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ // nsHttpConnection and TlsHandshaker create a reference cycle. To break this
+ // cycle, NotifyClose() needs to be called in nsHttpConnection::Close().
+ RefPtr<nsHttpConnection> mOwner;
+};
+
+} // namespace mozilla::net
+
+#endif // TlsHandshaker_h__
diff --git a/netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs b/netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs
new file mode 100644
index 0000000000..c37eb104d0
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+export function WellKnownOpportunisticUtils() {
+ this.valid = false;
+ this.mixed = false;
+ this.lifetime = 0;
+}
+
+WellKnownOpportunisticUtils.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIWellKnownOpportunisticUtils"]),
+
+ verify(aJSON, aOrigin) {
+ try {
+ let arr = JSON.parse(aJSON.toLowerCase());
+ if (!arr.includes(aOrigin.toLowerCase())) {
+ throw new Error("invalid origin");
+ }
+ } catch (e) {
+ return;
+ }
+ this.valid = true;
+ },
+};
diff --git a/netwerk/protocol/http/binary_http/Cargo.toml b/netwerk/protocol/http/binary_http/Cargo.toml
new file mode 100644
index 0000000000..97db55bc33
--- /dev/null
+++ b/netwerk/protocol/http/binary_http/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "binary_http"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+nserror = { path = "../../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../../xpcom/rust/nsstring" }
+bhttp = "0.3"
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+xpcom = { path = "../../../../xpcom/rust/xpcom" }
diff --git a/netwerk/protocol/http/binary_http/src/binary_http.h b/netwerk/protocol/http/binary_http/src/binary_http.h
new file mode 100644
index 0000000000..2051a00e83
--- /dev/null
+++ b/netwerk/protocol/http/binary_http/src/binary_http.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _binary_http_h_
+#define _binary_http_h_
+
+#include "nsISupportsUtils.h" // for nsresult, etc.
+
+// {b43b3f73-8160-4ab2-9f5d-4129a9708081}
+#define NS_BINARY_HTTP_CID \
+ { \
+ 0xb43b3f73, 0x8160, 0x4ab2, { \
+ 0x9f, 0x5d, 0x41, 0x29, 0xa9, 0x70, 0x80, 0x81 \
+ } \
+ }
+
+extern "C" {
+nsresult binary_http_constructor(REFNSIID iid, void** result);
+};
+
+#endif // _binary_http_h_
diff --git a/netwerk/protocol/http/binary_http/src/lib.rs b/netwerk/protocol/http/binary_http/src/lib.rs
new file mode 100644
index 0000000000..fab357dcca
--- /dev/null
+++ b/netwerk/protocol/http/binary_http/src/lib.rs
@@ -0,0 +1,264 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate bhttp;
+extern crate nserror;
+extern crate nsstring;
+extern crate thin_vec;
+#[macro_use]
+extern crate xpcom;
+
+use bhttp::{Message, Mode};
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_UNEXPECTED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use std::io::Cursor;
+use thin_vec::ThinVec;
+use xpcom::interfaces::{nsIBinaryHttpRequest, nsIBinaryHttpResponse};
+use xpcom::RefPtr;
+
+enum HeaderComponent {
+ Name,
+ Value,
+}
+
+// Extracts either the names or the values of a slice of header (name, value) pairs.
+fn extract_header_components(
+ headers: &[(Vec<u8>, Vec<u8>)],
+ component: HeaderComponent,
+) -> ThinVec<nsCString> {
+ let mut header_components = ThinVec::with_capacity(headers.len());
+ for (name, value) in headers {
+ match component {
+ HeaderComponent::Name => header_components.push(nsCString::from(name.clone())),
+ HeaderComponent::Value => header_components.push(nsCString::from(value.clone())),
+ }
+ }
+ header_components
+}
+
+#[xpcom(implement(nsIBinaryHttpRequest), atomic)]
+struct BinaryHttpRequest {
+ method: Vec<u8>,
+ scheme: Vec<u8>,
+ authority: Vec<u8>,
+ path: Vec<u8>,
+ headers: Vec<(Vec<u8>, Vec<u8>)>,
+ content: Vec<u8>,
+}
+
+impl BinaryHttpRequest {
+ xpcom_method!(get_method => GetMethod() -> nsACString);
+ fn get_method(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.method.clone()))
+ }
+
+ xpcom_method!(get_scheme => GetScheme() -> nsACString);
+ fn get_scheme(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.scheme.clone()))
+ }
+
+ xpcom_method!(get_authority => GetAuthority() -> nsACString);
+ fn get_authority(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.authority.clone()))
+ }
+
+ xpcom_method!(get_path => GetPath() -> nsACString);
+ fn get_path(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.path.clone()))
+ }
+
+ xpcom_method!(get_content => GetContent() -> ThinVec<u8>);
+ fn get_content(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.content.clone().into_iter().collect())
+ }
+
+ xpcom_method!(get_header_names => GetHeaderNames() -> ThinVec<nsCString>);
+ fn get_header_names(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Name,
+ ))
+ }
+
+ xpcom_method!(get_header_values => GetHeaderValues() -> ThinVec<nsCString>);
+ fn get_header_values(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Value,
+ ))
+ }
+}
+
+#[xpcom(implement(nsIBinaryHttpResponse), atomic)]
+struct BinaryHttpResponse {
+ status: u16,
+ headers: Vec<(Vec<u8>, Vec<u8>)>,
+ content: Vec<u8>,
+}
+
+impl BinaryHttpResponse {
+ xpcom_method!(get_status => GetStatus() -> u16);
+ fn get_status(&self) -> Result<u16, nsresult> {
+ Ok(self.status)
+ }
+
+ xpcom_method!(get_content => GetContent() -> ThinVec<u8>);
+ fn get_content(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.content.clone().into_iter().collect())
+ }
+
+ xpcom_method!(get_header_names => GetHeaderNames() -> ThinVec<nsCString>);
+ fn get_header_names(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Name,
+ ))
+ }
+
+ xpcom_method!(get_header_values => GetHeaderValues() -> ThinVec<nsCString>);
+ fn get_header_values(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Value,
+ ))
+ }
+}
+
+#[xpcom(implement(nsIBinaryHttp), atomic)]
+struct BinaryHttp {}
+
+impl BinaryHttp {
+ xpcom_method!(encode_request => EncodeRequest(request: *const nsIBinaryHttpRequest) -> ThinVec<u8>);
+ fn encode_request(&self, request: &nsIBinaryHttpRequest) -> Result<ThinVec<u8>, nsresult> {
+ let mut method = nsCString::new();
+ unsafe { request.GetMethod(&mut *method) }.to_result()?;
+ let mut scheme = nsCString::new();
+ unsafe { request.GetScheme(&mut *scheme) }.to_result()?;
+ let mut authority = nsCString::new();
+ unsafe { request.GetAuthority(&mut *authority) }.to_result()?;
+ let mut path = nsCString::new();
+ unsafe { request.GetPath(&mut *path) }.to_result()?;
+ let mut message = Message::request(
+ method.to_vec(),
+ scheme.to_vec(),
+ authority.to_vec(),
+ path.to_vec(),
+ );
+ let mut header_names = ThinVec::new();
+ unsafe { request.GetHeaderNames(&mut header_names) }.to_result()?;
+ let mut header_values = ThinVec::with_capacity(header_names.len());
+ unsafe { request.GetHeaderValues(&mut header_values) }.to_result()?;
+ if header_names.len() != header_values.len() {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+ for (name, value) in header_names.iter().zip(header_values.iter()) {
+ message.put_header(name.to_vec(), value.to_vec());
+ }
+ let mut content = ThinVec::new();
+ unsafe { request.GetContent(&mut content) }.to_result()?;
+ message.write_content(content);
+ let mut encoded = ThinVec::new();
+ message
+ .write_bhttp(Mode::KnownLength, &mut encoded)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(encoded)
+ }
+
+ xpcom_method!(decode_response => DecodeResponse(response: *const ThinVec<u8>) -> *const nsIBinaryHttpResponse);
+ fn decode_response(
+ &self,
+ response: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIBinaryHttpResponse>, nsresult> {
+ let mut cursor = Cursor::new(response);
+ let decoded = Message::read_bhttp(&mut cursor).map_err(|_| NS_ERROR_UNEXPECTED)?;
+ let status = decoded.control().status().ok_or(NS_ERROR_UNEXPECTED)?;
+ let headers = decoded
+ .header()
+ .iter()
+ .map(|field| (field.name().to_vec(), field.value().to_vec()))
+ .collect();
+ let content = decoded.content().to_vec();
+ let binary_http_response = BinaryHttpResponse::allocate(InitBinaryHttpResponse {
+ status,
+ headers,
+ content,
+ });
+ binary_http_response
+ .query_interface::<nsIBinaryHttpResponse>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+
+ xpcom_method!(decode_request => DecodeRequest(request: *const ThinVec<u8>) -> *const nsIBinaryHttpRequest);
+ fn decode_request(
+ &self,
+ request: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIBinaryHttpRequest>, nsresult> {
+ let mut cursor = Cursor::new(request);
+ let decoded = Message::read_bhttp(&mut cursor).map_err(|_| NS_ERROR_UNEXPECTED)?;
+ let method = decoded
+ .control()
+ .method()
+ .ok_or(NS_ERROR_UNEXPECTED)?
+ .to_vec();
+ let scheme = decoded
+ .control()
+ .scheme()
+ .ok_or(NS_ERROR_UNEXPECTED)?
+ .to_vec();
+ // authority and path can be empty, in which case we return empty arrays
+ let authority = decoded.control().authority().unwrap_or(&[]).to_vec();
+ let path = decoded.control().path().unwrap_or(&[]).to_vec();
+ let headers = decoded
+ .header()
+ .iter()
+ .map(|field| (field.name().to_vec(), field.value().to_vec()))
+ .collect();
+ let content = decoded.content().to_vec();
+ let binary_http_request = BinaryHttpRequest::allocate(InitBinaryHttpRequest {
+ method,
+ scheme,
+ authority,
+ path,
+ headers,
+ content,
+ });
+ binary_http_request
+ .query_interface::<nsIBinaryHttpRequest>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+
+ xpcom_method!(encode_response => EncodeResponse(response: *const nsIBinaryHttpResponse) -> ThinVec<u8>);
+ fn encode_response(&self, response: &nsIBinaryHttpResponse) -> Result<ThinVec<u8>, nsresult> {
+ let mut status = 0;
+ unsafe { response.GetStatus(&mut status) }.to_result()?;
+ let mut message = Message::response(status);
+ let mut header_names = ThinVec::new();
+ unsafe { response.GetHeaderNames(&mut header_names) }.to_result()?;
+ let mut header_values = ThinVec::with_capacity(header_names.len());
+ unsafe { response.GetHeaderValues(&mut header_values) }.to_result()?;
+ if header_names.len() != header_values.len() {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+ for (name, value) in header_names.iter().zip(header_values.iter()) {
+ message.put_header(name.to_vec(), value.to_vec());
+ }
+ let mut content = ThinVec::new();
+ unsafe { response.GetContent(&mut content) }.to_result()?;
+ message.write_content(content);
+ let mut encoded = ThinVec::new();
+ message
+ .write_bhttp(Mode::KnownLength, &mut encoded)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(encoded)
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn binary_http_constructor(
+ iid: *const xpcom::nsIID,
+ result: *mut *mut xpcom::reexports::libc::c_void,
+) -> nsresult {
+ let binary_http = BinaryHttp::allocate(InitBinaryHttp {});
+ unsafe { binary_http.QueryInterface(iid, result) }
+}
diff --git a/netwerk/protocol/http/components.conf b/netwerk/protocol/http/components.conf
new file mode 100644
index 0000000000..8c489d0105
--- /dev/null
+++ b/netwerk/protocol/http/components.conf
@@ -0,0 +1,33 @@
+# -*- 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/.
+
+Classes = [
+ {
+ 'cid': '{b4f96c89-5238-450c-8bda-e12c26f1d150}',
+ 'contract_ids': ['@mozilla.org/network/well-known-opportunistic-utils;1'],
+ 'esModule': 'resource://gre/modules/WellKnownOpportunisticUtils.sys.mjs',
+ 'constructor': 'WellKnownOpportunisticUtils',
+ },
+ {
+ 'cid': '{b43b3f73-8160-4ab2-9f5d-4129a9708081}',
+ 'contract_ids': ['@mozilla.org/network/binary-http;1'],
+ 'headers': ['/netwerk/protocol/http/binary_http/src/binary_http.h'],
+ 'legacy_constructor': 'binary_http_constructor',
+ },
+ {
+ 'cid': '{d581149e-3319-4563-b95e-46c64af5c4e8}',
+ 'contract_ids': ['@mozilla.org/network/oblivious-http;1'],
+ 'headers': ['/netwerk/protocol/http/oblivious_http/src/oblivious_http.h'],
+ 'legacy_constructor': 'oblivious_http_constructor',
+ },
+ {
+ 'cid': '{b1f08d56-fca6-4290-9500-d5168dc9d8c3}',
+ 'contract_ids': ['@mozilla.org/network/oblivious-http-service;1'],
+ 'interfaces': ['nsIObliviousHttpService'],
+ 'type': 'mozilla::net::ObliviousHttpService',
+ 'headers': ['/netwerk/protocol/http/ObliviousHttpService.h'],
+ },
+]
diff --git a/netwerk/protocol/http/http2_huffman_table.txt b/netwerk/protocol/http/http2_huffman_table.txt
new file mode 100644
index 0000000000..bfde068cd9
--- /dev/null
+++ b/netwerk/protocol/http/http2_huffman_table.txt
@@ -0,0 +1,257 @@
+ ( 0) |11111111|11000 1ff8 [13]
+ ( 1) |11111111|11111111|1011000 7fffd8 [23]
+ ( 2) |11111111|11111111|11111110|0010 fffffe2 [28]
+ ( 3) |11111111|11111111|11111110|0011 fffffe3 [28]
+ ( 4) |11111111|11111111|11111110|0100 fffffe4 [28]
+ ( 5) |11111111|11111111|11111110|0101 fffffe5 [28]
+ ( 6) |11111111|11111111|11111110|0110 fffffe6 [28]
+ ( 7) |11111111|11111111|11111110|0111 fffffe7 [28]
+ ( 8) |11111111|11111111|11111110|1000 fffffe8 [28]
+ ( 9) |11111111|11111111|11101010 ffffea [24]
+ ( 10) |11111111|11111111|11111111|111100 3ffffffc [30]
+ ( 11) |11111111|11111111|11111110|1001 fffffe9 [28]
+ ( 12) |11111111|11111111|11111110|1010 fffffea [28]
+ ( 13) |11111111|11111111|11111111|111101 3ffffffd [30]
+ ( 14) |11111111|11111111|11111110|1011 fffffeb [28]
+ ( 15) |11111111|11111111|11111110|1100 fffffec [28]
+ ( 16) |11111111|11111111|11111110|1101 fffffed [28]
+ ( 17) |11111111|11111111|11111110|1110 fffffee [28]
+ ( 18) |11111111|11111111|11111110|1111 fffffef [28]
+ ( 19) |11111111|11111111|11111111|0000 ffffff0 [28]
+ ( 20) |11111111|11111111|11111111|0001 ffffff1 [28]
+ ( 21) |11111111|11111111|11111111|0010 ffffff2 [28]
+ ( 22) |11111111|11111111|11111111|111110 3ffffffe [30]
+ ( 23) |11111111|11111111|11111111|0011 ffffff3 [28]
+ ( 24) |11111111|11111111|11111111|0100 ffffff4 [28]
+ ( 25) |11111111|11111111|11111111|0101 ffffff5 [28]
+ ( 26) |11111111|11111111|11111111|0110 ffffff6 [28]
+ ( 27) |11111111|11111111|11111111|0111 ffffff7 [28]
+ ( 28) |11111111|11111111|11111111|1000 ffffff8 [28]
+ ( 29) |11111111|11111111|11111111|1001 ffffff9 [28]
+ ( 30) |11111111|11111111|11111111|1010 ffffffa [28]
+ ( 31) |11111111|11111111|11111111|1011 ffffffb [28]
+ ' ' ( 32) |010100 14 [ 6]
+ '!' ( 33) |11111110|00 3f8 [10]
+ '"' ( 34) |11111110|01 3f9 [10]
+ '#' ( 35) |11111111|1010 ffa [12]
+ '$' ( 36) |11111111|11001 1ff9 [13]
+ '%' ( 37) |010101 15 [ 6]
+ '&' ( 38) |11111000 f8 [ 8]
+ ''' ( 39) |11111111|010 7fa [11]
+ '(' ( 40) |11111110|10 3fa [10]
+ ')' ( 41) |11111110|11 3fb [10]
+ '*' ( 42) |11111001 f9 [ 8]
+ '+' ( 43) |11111111|011 7fb [11]
+ ',' ( 44) |11111010 fa [ 8]
+ '-' ( 45) |010110 16 [ 6]
+ '.' ( 46) |010111 17 [ 6]
+ '/' ( 47) |011000 18 [ 6]
+ '0' ( 48) |00000 0 [ 5]
+ '1' ( 49) |00001 1 [ 5]
+ '2' ( 50) |00010 2 [ 5]
+ '3' ( 51) |011001 19 [ 6]
+ '4' ( 52) |011010 1a [ 6]
+ '5' ( 53) |011011 1b [ 6]
+ '6' ( 54) |011100 1c [ 6]
+ '7' ( 55) |011101 1d [ 6]
+ '8' ( 56) |011110 1e [ 6]
+ '9' ( 57) |011111 1f [ 6]
+ ':' ( 58) |1011100 5c [ 7]
+ ';' ( 59) |11111011 fb [ 8]
+ '<' ( 60) |11111111|1111100 7ffc [15]
+ '=' ( 61) |100000 20 [ 6]
+ '>' ( 62) |11111111|1011 ffb [12]
+ '?' ( 63) |11111111|00 3fc [10]
+ '@' ( 64) |11111111|11010 1ffa [13]
+ 'A' ( 65) |100001 21 [ 6]
+ 'B' ( 66) |1011101 5d [ 7]
+ 'C' ( 67) |1011110 5e [ 7]
+ 'D' ( 68) |1011111 5f [ 7]
+ 'E' ( 69) |1100000 60 [ 7]
+ 'F' ( 70) |1100001 61 [ 7]
+ 'G' ( 71) |1100010 62 [ 7]
+ 'H' ( 72) |1100011 63 [ 7]
+ 'I' ( 73) |1100100 64 [ 7]
+ 'J' ( 74) |1100101 65 [ 7]
+ 'K' ( 75) |1100110 66 [ 7]
+ 'L' ( 76) |1100111 67 [ 7]
+ 'M' ( 77) |1101000 68 [ 7]
+ 'N' ( 78) |1101001 69 [ 7]
+ 'O' ( 79) |1101010 6a [ 7]
+ 'P' ( 80) |1101011 6b [ 7]
+ 'Q' ( 81) |1101100 6c [ 7]
+ 'R' ( 82) |1101101 6d [ 7]
+ 'S' ( 83) |1101110 6e [ 7]
+ 'T' ( 84) |1101111 6f [ 7]
+ 'U' ( 85) |1110000 70 [ 7]
+ 'V' ( 86) |1110001 71 [ 7]
+ 'W' ( 87) |1110010 72 [ 7]
+ 'X' ( 88) |11111100 fc [ 8]
+ 'Y' ( 89) |1110011 73 [ 7]
+ 'Z' ( 90) |11111101 fd [ 8]
+ '[' ( 91) |11111111|11011 1ffb [13]
+ '\' ( 92) |11111111|11111110|000 7fff0 [19]
+ ']' ( 93) |11111111|11100 1ffc [13]
+ '^' ( 94) |11111111|111100 3ffc [14]
+ '_' ( 95) |100010 22 [ 6]
+ '`' ( 96) |11111111|1111101 7ffd [15]
+ 'a' ( 97) |00011 3 [ 5]
+ 'b' ( 98) |100011 23 [ 6]
+ 'c' ( 99) |00100 4 [ 5]
+ 'd' (100) |100100 24 [ 6]
+ 'e' (101) |00101 5 [ 5]
+ 'f' (102) |100101 25 [ 6]
+ 'g' (103) |100110 26 [ 6]
+ 'h' (104) |100111 27 [ 6]
+ 'i' (105) |00110 6 [ 5]
+ 'j' (106) |1110100 74 [ 7]
+ 'k' (107) |1110101 75 [ 7]
+ 'l' (108) |101000 28 [ 6]
+ 'm' (109) |101001 29 [ 6]
+ 'n' (110) |101010 2a [ 6]
+ 'o' (111) |00111 7 [ 5]
+ 'p' (112) |101011 2b [ 6]
+ 'q' (113) |1110110 76 [ 7]
+ 'r' (114) |101100 2c [ 6]
+ 's' (115) |01000 8 [ 5]
+ 't' (116) |01001 9 [ 5]
+ 'u' (117) |101101 2d [ 6]
+ 'v' (118) |1110111 77 [ 7]
+ 'w' (119) |1111000 78 [ 7]
+ 'x' (120) |1111001 79 [ 7]
+ 'y' (121) |1111010 7a [ 7]
+ 'z' (122) |1111011 7b [ 7]
+ '{' (123) |11111111|1111110 7ffe [15]
+ '|' (124) |11111111|100 7fc [11]
+ '}' (125) |11111111|111101 3ffd [14]
+ '~' (126) |11111111|11101 1ffd [13]
+ (127) |11111111|11111111|11111111|1100 ffffffc [28]
+ (128) |11111111|11111110|0110 fffe6 [20]
+ (129) |11111111|11111111|010010 3fffd2 [22]
+ (130) |11111111|11111110|0111 fffe7 [20]
+ (131) |11111111|11111110|1000 fffe8 [20]
+ (132) |11111111|11111111|010011 3fffd3 [22]
+ (133) |11111111|11111111|010100 3fffd4 [22]
+ (134) |11111111|11111111|010101 3fffd5 [22]
+ (135) |11111111|11111111|1011001 7fffd9 [23]
+ (136) |11111111|11111111|010110 3fffd6 [22]
+ (137) |11111111|11111111|1011010 7fffda [23]
+ (138) |11111111|11111111|1011011 7fffdb [23]
+ (139) |11111111|11111111|1011100 7fffdc [23]
+ (140) |11111111|11111111|1011101 7fffdd [23]
+ (141) |11111111|11111111|1011110 7fffde [23]
+ (142) |11111111|11111111|11101011 ffffeb [24]
+ (143) |11111111|11111111|1011111 7fffdf [23]
+ (144) |11111111|11111111|11101100 ffffec [24]
+ (145) |11111111|11111111|11101101 ffffed [24]
+ (146) |11111111|11111111|010111 3fffd7 [22]
+ (147) |11111111|11111111|1100000 7fffe0 [23]
+ (148) |11111111|11111111|11101110 ffffee [24]
+ (149) |11111111|11111111|1100001 7fffe1 [23]
+ (150) |11111111|11111111|1100010 7fffe2 [23]
+ (151) |11111111|11111111|1100011 7fffe3 [23]
+ (152) |11111111|11111111|1100100 7fffe4 [23]
+ (153) |11111111|11111110|11100 1fffdc [21]
+ (154) |11111111|11111111|011000 3fffd8 [22]
+ (155) |11111111|11111111|1100101 7fffe5 [23]
+ (156) |11111111|11111111|011001 3fffd9 [22]
+ (157) |11111111|11111111|1100110 7fffe6 [23]
+ (158) |11111111|11111111|1100111 7fffe7 [23]
+ (159) |11111111|11111111|11101111 ffffef [24]
+ (160) |11111111|11111111|011010 3fffda [22]
+ (161) |11111111|11111110|11101 1fffdd [21]
+ (162) |11111111|11111110|1001 fffe9 [20]
+ (163) |11111111|11111111|011011 3fffdb [22]
+ (164) |11111111|11111111|011100 3fffdc [22]
+ (165) |11111111|11111111|1101000 7fffe8 [23]
+ (166) |11111111|11111111|1101001 7fffe9 [23]
+ (167) |11111111|11111110|11110 1fffde [21]
+ (168) |11111111|11111111|1101010 7fffea [23]
+ (169) |11111111|11111111|011101 3fffdd [22]
+ (170) |11111111|11111111|011110 3fffde [22]
+ (171) |11111111|11111111|11110000 fffff0 [24]
+ (172) |11111111|11111110|11111 1fffdf [21]
+ (173) |11111111|11111111|011111 3fffdf [22]
+ (174) |11111111|11111111|1101011 7fffeb [23]
+ (175) |11111111|11111111|1101100 7fffec [23]
+ (176) |11111111|11111111|00000 1fffe0 [21]
+ (177) |11111111|11111111|00001 1fffe1 [21]
+ (178) |11111111|11111111|100000 3fffe0 [22]
+ (179) |11111111|11111111|00010 1fffe2 [21]
+ (180) |11111111|11111111|1101101 7fffed [23]
+ (181) |11111111|11111111|100001 3fffe1 [22]
+ (182) |11111111|11111111|1101110 7fffee [23]
+ (183) |11111111|11111111|1101111 7fffef [23]
+ (184) |11111111|11111110|1010 fffea [20]
+ (185) |11111111|11111111|100010 3fffe2 [22]
+ (186) |11111111|11111111|100011 3fffe3 [22]
+ (187) |11111111|11111111|100100 3fffe4 [22]
+ (188) |11111111|11111111|1110000 7ffff0 [23]
+ (189) |11111111|11111111|100101 3fffe5 [22]
+ (190) |11111111|11111111|100110 3fffe6 [22]
+ (191) |11111111|11111111|1110001 7ffff1 [23]
+ (192) |11111111|11111111|11111000|00 3ffffe0 [26]
+ (193) |11111111|11111111|11111000|01 3ffffe1 [26]
+ (194) |11111111|11111110|1011 fffeb [20]
+ (195) |11111111|11111110|001 7fff1 [19]
+ (196) |11111111|11111111|100111 3fffe7 [22]
+ (197) |11111111|11111111|1110010 7ffff2 [23]
+ (198) |11111111|11111111|101000 3fffe8 [22]
+ (199) |11111111|11111111|11110110|0 1ffffec [25]
+ (200) |11111111|11111111|11111000|10 3ffffe2 [26]
+ (201) |11111111|11111111|11111000|11 3ffffe3 [26]
+ (202) |11111111|11111111|11111001|00 3ffffe4 [26]
+ (203) |11111111|11111111|11111011|110 7ffffde [27]
+ (204) |11111111|11111111|11111011|111 7ffffdf [27]
+ (205) |11111111|11111111|11111001|01 3ffffe5 [26]
+ (206) |11111111|11111111|11110001 fffff1 [24]
+ (207) |11111111|11111111|11110110|1 1ffffed [25]
+ (208) |11111111|11111110|010 7fff2 [19]
+ (209) |11111111|11111111|00011 1fffe3 [21]
+ (210) |11111111|11111111|11111001|10 3ffffe6 [26]
+ (211) |11111111|11111111|11111100|000 7ffffe0 [27]
+ (212) |11111111|11111111|11111100|001 7ffffe1 [27]
+ (213) |11111111|11111111|11111001|11 3ffffe7 [26]
+ (214) |11111111|11111111|11111100|010 7ffffe2 [27]
+ (215) |11111111|11111111|11110010 fffff2 [24]
+ (216) |11111111|11111111|00100 1fffe4 [21]
+ (217) |11111111|11111111|00101 1fffe5 [21]
+ (218) |11111111|11111111|11111010|00 3ffffe8 [26]
+ (219) |11111111|11111111|11111010|01 3ffffe9 [26]
+ (220) |11111111|11111111|11111111|1101 ffffffd [28]
+ (221) |11111111|11111111|11111100|011 7ffffe3 [27]
+ (222) |11111111|11111111|11111100|100 7ffffe4 [27]
+ (223) |11111111|11111111|11111100|101 7ffffe5 [27]
+ (224) |11111111|11111110|1100 fffec [20]
+ (225) |11111111|11111111|11110011 fffff3 [24]
+ (226) |11111111|11111110|1101 fffed [20]
+ (227) |11111111|11111111|00110 1fffe6 [21]
+ (228) |11111111|11111111|101001 3fffe9 [22]
+ (229) |11111111|11111111|00111 1fffe7 [21]
+ (230) |11111111|11111111|01000 1fffe8 [21]
+ (231) |11111111|11111111|1110011 7ffff3 [23]
+ (232) |11111111|11111111|101010 3fffea [22]
+ (233) |11111111|11111111|101011 3fffeb [22]
+ (234) |11111111|11111111|11110111|0 1ffffee [25]
+ (235) |11111111|11111111|11110111|1 1ffffef [25]
+ (236) |11111111|11111111|11110100 fffff4 [24]
+ (237) |11111111|11111111|11110101 fffff5 [24]
+ (238) |11111111|11111111|11111010|10 3ffffea [26]
+ (239) |11111111|11111111|1110100 7ffff4 [23]
+ (240) |11111111|11111111|11111010|11 3ffffeb [26]
+ (241) |11111111|11111111|11111100|110 7ffffe6 [27]
+ (242) |11111111|11111111|11111011|00 3ffffec [26]
+ (243) |11111111|11111111|11111011|01 3ffffed [26]
+ (244) |11111111|11111111|11111100|111 7ffffe7 [27]
+ (245) |11111111|11111111|11111101|000 7ffffe8 [27]
+ (246) |11111111|11111111|11111101|001 7ffffe9 [27]
+ (247) |11111111|11111111|11111101|010 7ffffea [27]
+ (248) |11111111|11111111|11111101|011 7ffffeb [27]
+ (249) |11111111|11111111|11111111|1110 ffffffe [28]
+ (250) |11111111|11111111|11111101|100 7ffffec [27]
+ (251) |11111111|11111111|11111101|101 7ffffed [27]
+ (252) |11111111|11111111|11111101|110 7ffffee [27]
+ (253) |11111111|11111111|11111101|111 7ffffef [27]
+ (254) |11111111|11111111|11111110|000 7fffff0 [27]
+ (255) |11111111|11111111|11111011|10 3ffffee [26]
+ EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]
diff --git a/netwerk/protocol/http/make_incoming_tables.py b/netwerk/protocol/http/make_incoming_tables.py
new file mode 100644
index 0000000000..be917a6f0b
--- /dev/null
+++ b/netwerk/protocol/http/make_incoming_tables.py
@@ -0,0 +1,202 @@
+# This script exists to auto-generate Http2HuffmanIncoming.h from the table
+# contained in the HPACK spec. It's pretty simple to run:
+# python make_incoming_tables.py < http2_huffman_table.txt > Http2HuffmanIncoming.h
+# where huff_incoming.txt is copy/pasted text from the latest version of the
+# HPACK spec, with all non-relevant lines removed (the most recent version
+# of huff_incoming.txt also lives in this directory as an example).
+import sys
+
+
+def char_cmp(x, y):
+ rv = cmp(x["nbits"], y["nbits"])
+ if not rv:
+ rv = cmp(x["bpat"], y["bpat"])
+ if not rv:
+ rv = cmp(x["ascii"], y["ascii"])
+ return rv
+
+
+characters = []
+
+for line in sys.stdin:
+ line = line.rstrip()
+ obracket = line.rfind("[")
+ nbits = int(line[obracket + 1 : -1])
+
+ oparen = line.find(" (")
+ ascii = int(line[oparen + 2 : oparen + 5].strip())
+
+ bar = line.find("|", oparen)
+ space = line.find(" ", bar)
+ bpat = line[bar + 1 : space].strip().rstrip("|")
+
+ characters.append({"ascii": ascii, "nbits": nbits, "bpat": bpat})
+
+characters.sort(cmp=char_cmp)
+raw_entries = []
+for c in characters:
+ raw_entries.append((c["ascii"], c["bpat"]))
+
+
+class DefaultList(list):
+ def __init__(self, default=None):
+ self.__default = default
+
+ def __ensure_size(self, sz):
+ while sz > len(self):
+ self.append(self.__default)
+
+ def __getitem__(self, idx):
+ self.__ensure_size(idx + 1)
+ rv = super(DefaultList, self).__getitem__(idx)
+ return rv
+
+ def __setitem__(self, idx, val):
+ self.__ensure_size(idx + 1)
+ super(DefaultList, self).__setitem__(idx, val)
+
+
+def expand_to_8bit(bstr):
+ while len(bstr) < 8:
+ bstr += "0"
+ return int(bstr, 2)
+
+
+table = DefaultList()
+for r in raw_entries:
+ ascii, bpat = r
+ ascii = int(ascii)
+ bstrs = bpat.split("|")
+ curr_table = table
+ while len(bstrs) > 1:
+ idx = expand_to_8bit(bstrs[0])
+ if curr_table[idx] is None:
+ curr_table[idx] = DefaultList()
+ curr_table = curr_table[idx]
+ bstrs.pop(0)
+
+ idx = expand_to_8bit(bstrs[0])
+ curr_table[idx] = {
+ "prefix_len": len(bstrs[0]),
+ "mask": int(bstrs[0], 2),
+ "value": ascii,
+ }
+
+
+def output_table(table, name_suffix=""):
+ max_prefix_len = 0
+ for i, t in enumerate(table):
+ if isinstance(t, dict):
+ if t["prefix_len"] > max_prefix_len:
+ max_prefix_len = t["prefix_len"]
+ elif t is not None:
+ output_table(t, "%s_%s" % (name_suffix, i))
+
+ tablename = "HuffmanIncoming%s" % (name_suffix if name_suffix else "Root",)
+ entriestable = tablename.replace("HuffmanIncoming", "HuffmanIncomingEntries")
+ nexttable = tablename.replace("HuffmanIncoming", "HuffmanIncomingNextTables")
+ sys.stdout.write("static const HuffmanIncomingEntry %s[] = {\n" % (entriestable,))
+ prefix_len = 0
+ value = 0
+ i = 0
+ while i < 256:
+ t = table[i]
+ if isinstance(t, dict):
+ value = t["value"]
+ prefix_len = t["prefix_len"]
+ elif t is not None:
+ break
+
+ sys.stdout.write(" { %s, %s }" % (value, prefix_len))
+ sys.stdout.write(",\n")
+ i += 1
+
+ indexOfFirstNextTable = i
+ if i < 256:
+ sys.stdout.write("};\n")
+ sys.stdout.write("\n")
+ sys.stdout.write("static const HuffmanIncomingTable* %s[] = {\n" % (nexttable,))
+ while i < 256:
+ subtable = "%s_%s" % (name_suffix, i)
+ ptr = "&HuffmanIncoming%s" % (subtable,)
+ sys.stdout.write(" %s" % (ptr))
+ sys.stdout.write(",\n")
+ i += 1
+ else:
+ nexttable = "nullptr"
+
+ sys.stdout.write("};\n")
+ sys.stdout.write("\n")
+ sys.stdout.write("static const HuffmanIncomingTable %s = {\n" % (tablename,))
+ sys.stdout.write(" %s,\n" % (entriestable,))
+ sys.stdout.write(" %s,\n" % (nexttable,))
+ sys.stdout.write(" %s,\n" % (indexOfFirstNextTable,))
+ sys.stdout.write(" %s\n" % (max_prefix_len,))
+ sys.stdout.write("};\n")
+ sys.stdout.write("\n")
+
+
+sys.stdout.write(
+ """/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanIncoming_h
+#define mozilla__net__Http2HuffmanIncoming_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+struct HuffmanIncomingEntry {
+ uint16_t mValue:9; // 9 bits so it can hold 0..256
+ uint16_t mPrefixLen:7; // only holds 1..8
+};
+
+// The data members are public only so they can be statically constructed. All
+// accesses should be done through the functions.
+struct HuffmanIncomingTable {
+ // The normal entries, for indices in the range 0..(mNumEntries-1).
+ const HuffmanIncomingEntry* const mEntries;
+
+ // The next tables, for indices in the range mNumEntries..255. Must be
+ // |nullptr| if mIndexOfFirstNextTable is 256.
+ const HuffmanIncomingTable** const mNextTables;
+
+ // The index of the first next table (equal to the number of entries in
+ // mEntries). This cannot be a uint8_t because it can have the value 256,
+ // in which case there are no next tables and mNextTables must be |nullptr|.
+ const uint16_t mIndexOfFirstNextTable;
+
+ const uint8_t mPrefixLen;
+
+ bool IndexHasANextTable(uint8_t aIndex) const
+ {
+ return aIndex >= mIndexOfFirstNextTable;
+ }
+
+ const HuffmanIncomingEntry* Entry(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex < mIndexOfFirstNextTable);
+ return &mEntries[aIndex];
+ }
+
+ const HuffmanIncomingTable* NextTable(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex >= mIndexOfFirstNextTable);
+ return mNextTables[aIndex - mIndexOfFirstNextTable];
+ }
+};
+
+"""
+)
+
+output_table(table)
+
+sys.stdout.write(
+ """} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanIncoming_h
+"""
+)
diff --git a/netwerk/protocol/http/make_outgoing_tables.py b/netwerk/protocol/http/make_outgoing_tables.py
new file mode 100644
index 0000000000..b9a4268202
--- /dev/null
+++ b/netwerk/protocol/http/make_outgoing_tables.py
@@ -0,0 +1,58 @@
+# This script exists to auto-generate Http2HuffmanOutgoing.h from the table
+# contained in the HPACK spec. It's pretty simple to run:
+# python make_outgoing_tables.py < http2_huffman_table.txt > Http2HuffmanOutgoing.h
+# where huff_outgoing.txt is copy/pasted text from the latest version of the
+# HPACK spec, with all non-relevant lines removed (the most recent version
+# of huff_outgoing.txt also lives in this directory as an example).
+import sys
+
+sys.stdout.write(
+ """/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanOutgoing_h
+#define mozilla__net__Http2HuffmanOutgoing_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanOutgoingEntry {
+ uint32_t mValue;
+ uint8_t mLength;
+};
+
+static const HuffmanOutgoingEntry HuffmanOutgoing[] = {
+"""
+)
+
+entries = []
+for line in sys.stdin:
+ line = line.strip()
+ obracket = line.rfind("[")
+ nbits = int(line[obracket + 1 : -1])
+
+ lastbar = line.rfind("|")
+ space = line.find(" ", lastbar)
+ encend = line.rfind(" ", 0, obracket)
+
+ enc = line[space:encend].strip()
+ val = int(enc, 16)
+
+ entries.append({"length": nbits, "value": val})
+
+line = []
+for i, e in enumerate(entries):
+ sys.stdout.write(" { 0x%08x, %s }" % (e["value"], e["length"]))
+ if i < (len(entries) - 1):
+ sys.stdout.write(",")
+ sys.stdout.write("\n")
+
+sys.stdout.write(
+ """};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanOutgoing_h
+"""
+)
diff --git a/netwerk/protocol/http/metrics.yaml b/netwerk/protocol/http/metrics.yaml
new file mode 100644
index 0000000000..5c92eb2afd
--- /dev/null
+++ b/netwerk/protocol/http/metrics.yaml
@@ -0,0 +1,275 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - 'Core :: Networking: HTTP'
+
+
+netwerk:
+ early_hints:
+ type: labeled_counter
+ labels:
+ - stylesheet
+ - fonts
+ - scripts
+ - fetch
+ - image
+ - other
+ description: >
+ Counts the different type of resources that are sent for early hints.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1772124
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1772124
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797695
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+ eh_link_type:
+ type: labeled_counter
+ labels:
+ - dns-prefetch
+ - icon
+ - modulepreload
+ - preconnect
+ - prefetch
+ - preload
+ - prerender
+ - stylesheet
+ - other
+ description: >
+ Counts different type of link headers that are sent in early hint
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797936
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797936
+ notification_emails:
+ - necko@mozilla.com
+ - manuel@mozilla.com
+ expires: never
+
+network:
+ data_size_per_type:
+ type: labeled_counter
+ labels:
+ - text_html
+ - text_css
+ - text_json
+ - text_plain
+ - text_javascript
+ - text_other
+ - audio
+ - video
+ - multipart
+ - icon
+ - image
+ - ocsp
+ - xpinstall
+ - wasm
+ - pdf
+ - octet_stream
+ - proxy
+ - compressed
+ - x509
+ - application_other
+ description: >
+ Number of KB we transferred keyed by "contentType"
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ notification_emails:
+ - necko@mozilla.com
+ - rtestard@mozilla.com
+ expires: 130
+ telemetry_mirror: NETWORKING_DATA_TRANSFERRED_PER_CONTENT_TYPE
+ no_lint:
+ - COMMON_PREFIX
+
+ data_size_pb_per_type:
+ type: labeled_counter
+ labels:
+ - text_html
+ - text_css
+ - text_json
+ - text_plain
+ - text_javascript
+ - text_other
+ - audio
+ - video
+ - multipart
+ - icon
+ - image
+ - ocsp
+ - xpinstall
+ - wasm
+ - pdf
+ - octet_stream
+ - proxy
+ - compressed
+ - x509
+ - application_other
+ description: >
+ Number of KB we transferred keyed by "contentType"
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ notification_emails:
+ - necko@mozilla.com
+ - rtestard@mozilla.com
+ expires: 130
+ telemetry_mirror: NETWORKING_DATA_TRANSFERRED_PB_PER_CONTENT_TYPE
+ no_lint:
+ - COMMON_PREFIX
+
+ cors_authorization_header:
+ type: labeled_counter
+ labels:
+ - allowed
+ - disallowed
+ - covered_by_wildcard
+ description: >
+ Count how many times we see `Authorization` header in
+ `Access-Control-Request-Headers` header and the possible outcomes.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1687364
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1687364
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: 130
+
+ cache_hit_time:
+ type: timing_distribution
+ time_unit: millisecond
+ telemetry_mirror: NETWORK_CACHE_V2_HIT_TIME_MS
+ description: >
+ Time to open existing cache entry file.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1489524
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ expires: never
+
+ font_download_end:
+ type: timing_distribution
+ time_unit: millisecond
+ telemetry_mirror: WEBFONT_DOWNLOAD_TIME_AFTER_START
+ description: >
+ Time after navigationStart that all webfont downloads are completed.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - perf-telemetry-alerts@mozilla.com
+ - necko@mozilla.com
+ - bdekoz@mozilla.com
+ expires: never
+
+ first_from_cache:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from connection open to cache read start.
+ Corresponds to Legacy histogram HTTP_PAGE_OPEN_TO_FIRST_FROM_CACHE_V2 in
+ Desktop.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ expires: never
+
+ tcp_connection:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from the TCP SYN packet is received to
+ the connection is established and ready for HTTP.
+ Corresponds to Legacy histogram HTTP_PAGE_TCP_CONNECTION_2 in Desktop
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=772589
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+
+ dns_start:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from connection open to the DNS request
+ being issued.
+ Corresponds to Legacy histogram HTTP_PAGE_DNS_ISSUE_TIME in Desktop.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+
+ dns_end:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from the DNS request being issued to
+ the response.
+ Corresponds to Legacy histogram HTTP_PAGE_DNS_LOOKUP_TIME in Desktop.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+
+ tls_handshake:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from after the TCP SYN packet is
+ received to the secure connection is established and ready for HTTP.
+ Corresponds to Legacy histogram HTTP_PAGE_TLS_HANDSHAKE in Desktop.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=772589
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build
new file mode 100644
index 0000000000..db6b2a1a68
--- /dev/null
+++ b/netwerk/protocol/http/moz.build
@@ -0,0 +1,219 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: HTTP")
+
+XPIDL_SOURCES += [
+ "nsIBackgroundChannelRegistrar.idl",
+ "nsIBinaryHttp.idl",
+ "nsIEarlyHintObserver.idl",
+ "nsIHttpActivityObserver.idl",
+ "nsIHttpAuthenticableChannel.idl",
+ "nsIHttpAuthenticator.idl",
+ "nsIHttpAuthManager.idl",
+ "nsIHttpChannel.idl",
+ "nsIHttpChannelAuthProvider.idl",
+ "nsIHttpChannelChild.idl",
+ "nsIHttpChannelInternal.idl",
+ "nsIHttpHeaderVisitor.idl",
+ "nsIHttpProtocolHandler.idl",
+ "nsIObliviousHttp.idl",
+ "nsIObliviousHttpChannel.idl",
+ "nsIRaceCacheWithNetwork.idl",
+ "nsITlsHandshakeListener.idl",
+ "nsIWellKnownOpportunisticUtils.idl",
+]
+
+XPIDL_MODULE = "necko_http"
+
+EXPORTS += [
+ "nsCORSListenerProxy.h",
+ "nsHttp.h",
+ "nsHttpAtomList.h",
+ "nsHttpHeaderArray.h",
+ "nsHttpRequestHead.h",
+ "nsHttpResponseHead.h",
+]
+
+EXPORTS.mozilla.net += [
+ "AltDataOutputStreamChild.h",
+ "AltDataOutputStreamParent.h",
+ "AltServiceChild.h",
+ "AltServiceParent.h",
+ "AltSvcTransactionChild.h",
+ "AltSvcTransactionParent.h",
+ "BackgroundChannelRegistrar.h",
+ "BackgroundDataBridgeChild.h",
+ "BackgroundDataBridgeParent.h",
+ "ClassOfService.h",
+ "EarlyHintPreloader.h",
+ "EarlyHintRegistrar.h",
+ "EarlyHintsService.h",
+ "HttpAuthUtils.h",
+ "HttpBackgroundChannelChild.h",
+ "HttpBackgroundChannelParent.h",
+ "HttpBaseChannel.h",
+ "HttpChannelChild.h",
+ "HttpChannelParent.h",
+ "HttpConnectionMgrChild.h",
+ "HttpConnectionMgrParent.h",
+ "HttpConnectionMgrShell.h",
+ "HttpInfo.h",
+ "HttpTransactionChild.h",
+ "HttpTransactionParent.h",
+ "HttpTransactionShell.h",
+ "nsAHttpTransaction.h",
+ "nsServerTiming.h",
+ "NullHttpChannel.h",
+ "NullHttpTransaction.h",
+ "OpaqueResponseUtils.h",
+ "ParentChannelListener.h",
+ "PHttpChannelParams.h",
+ "PSpdyPush.h",
+ "SpeculativeTransaction.h",
+ "TimingStruct.h",
+]
+
+SOURCES += [
+ "nsHttpChannelAuthProvider.cpp", # redefines GetAuthType
+]
+
+UNIFIED_SOURCES += [
+ "AltDataOutputStreamChild.cpp",
+ "AltDataOutputStreamParent.cpp",
+ "AlternateServices.cpp",
+ "AltServiceChild.cpp",
+ "AltServiceParent.cpp",
+ "AltSvcTransactionChild.cpp",
+ "AltSvcTransactionParent.cpp",
+ "ASpdySession.cpp",
+ "BackgroundChannelRegistrar.cpp",
+ "BackgroundDataBridgeChild.cpp",
+ "BackgroundDataBridgeParent.cpp",
+ "BinaryHttpRequest.cpp",
+ "CacheControlParser.cpp",
+ "CachePushChecker.cpp",
+ "ConnectionDiagnostics.cpp",
+ "ConnectionEntry.cpp",
+ "ConnectionHandle.cpp",
+ "DnsAndConnectSocket.cpp",
+ "EarlyHintPreconnect.cpp",
+ "EarlyHintPreloader.cpp",
+ "EarlyHintRegistrar.cpp",
+ "EarlyHintsService.cpp",
+ "Http2Compression.cpp",
+ "Http2Push.cpp",
+ "Http2Session.cpp",
+ "Http2Stream.cpp",
+ "Http2StreamBase.cpp",
+ "Http2StreamTunnel.cpp",
+ "Http3Session.cpp",
+ "Http3Stream.cpp",
+ "Http3WebTransportSession.cpp",
+ "Http3WebTransportStream.cpp",
+ "HttpAuthUtils.cpp",
+ "HttpBackgroundChannelChild.cpp",
+ "HttpBackgroundChannelParent.cpp",
+ "HttpBaseChannel.cpp",
+ "HttpChannelChild.cpp",
+ "HttpChannelParent.cpp",
+ "HttpConnectionBase.cpp",
+ "HttpConnectionMgrChild.cpp",
+ "HttpConnectionMgrParent.cpp",
+ "HttpConnectionUDP.cpp",
+ "HttpInfo.cpp",
+ "HTTPSRecordResolver.cpp",
+ "HttpTrafficAnalyzer.cpp",
+ "HttpTransactionChild.cpp",
+ "HttpTransactionParent.cpp",
+ "InterceptedHttpChannel.cpp",
+ "MockHttpAuth.cpp",
+ "NetworkMarker.cpp",
+ "nsCORSListenerProxy.cpp",
+ "nsHttp.cpp",
+ "nsHttpActivityDistributor.cpp",
+ "nsHttpAuthCache.cpp",
+ "nsHttpAuthManager.cpp",
+ "nsHttpBasicAuth.cpp",
+ "nsHttpChannel.cpp",
+ "nsHttpChunkedDecoder.cpp",
+ "nsHttpConnection.cpp",
+ "nsHttpConnectionInfo.cpp",
+ "nsHttpConnectionMgr.cpp",
+ "nsHttpDigestAuth.cpp",
+ "nsHttpHeaderArray.cpp",
+ "nsHttpNTLMAuth.cpp",
+ "nsHttpRequestHead.cpp",
+ "nsHttpResponseHead.cpp",
+ "nsHttpTransaction.cpp",
+ "nsServerTiming.cpp",
+ "NullHttpChannel.cpp",
+ "NullHttpTransaction.cpp",
+ "ObliviousHttpChannel.cpp",
+ "ObliviousHttpService.cpp",
+ "OpaqueResponseUtils.cpp",
+ "ParentChannelListener.cpp",
+ "PendingTransactionInfo.cpp",
+ "PendingTransactionQueue.cpp",
+ "QuicSocketControl.cpp",
+ "SpeculativeTransaction.cpp",
+ "TlsHandshaker.cpp",
+ "TLSTransportLayer.cpp",
+ "TRRServiceChannel.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ UNIFIED_SOURCES += [
+ "HttpWinUtils.cpp",
+ ]
+
+# These files cannot be built in unified mode because of OS X headers.
+SOURCES += [
+ "nsHttpHandler.cpp",
+]
+
+IPDL_SOURCES += [
+ "HttpChannelParams.ipdlh",
+ "PAltDataOutputStream.ipdl",
+ "PAltService.ipdl",
+ "PAltSvcTransaction.ipdl",
+ "PBackgroundDataBridge.ipdl",
+ "PHttpBackgroundChannel.ipdl",
+ "PHttpChannel.ipdl",
+ "PHttpConnectionMgr.ipdl",
+ "PHttpTransaction.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/netwerk/base",
+ "/netwerk/cookie",
+ "/netwerk/dns",
+ "/netwerk/ipc",
+ "/netwerk/socket/neqo_glue",
+ "/netwerk/url-classifier",
+]
+
+if CONFIG["MOZ_AUTH_EXTENSION"]:
+ LOCAL_INCLUDES += [
+ "/extensions/auth",
+ ]
+
+EXTRA_JS_MODULES += [
+ "HPKEConfigManager.sys.mjs",
+ "WellKnownOpportunisticUtils.sys.mjs",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h
new file mode 100644
index 0000000000..7b28096bb4
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -0,0 +1,292 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAHttpConnection_h__
+#define nsAHttpConnection_h__
+
+#include "mozilla/net/DNS.h"
+#include "nsHttp.h"
+#include "nsISupports.h"
+#include "nsAHttpTransaction.h"
+#include "Http3WebTransportSession.h"
+#include "HttpTrafficAnalyzer.h"
+#include "nsIRequest.h"
+
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnectionInfo;
+class HttpConnectionBase;
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+//-----------------------------------------------------------------------------
+// Abstract base class for a HTTP connection
+//-----------------------------------------------------------------------------
+
+// 5a66aed7-eede-468b-ac2b-e5fb431fcc5c
+#define NS_AHTTPCONNECTION_IID \
+ { \
+ 0x5a66aed7, 0xeede, 0x468b, { \
+ 0xac, 0x2b, 0xe5, 0xfb, 0x43, 0x1f, 0xcc, 0x5c \
+ } \
+ }
+
+class nsAHttpConnection : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPCONNECTION_IID)
+
+ //-------------------------------------------------------------------------
+ // NOTE: these methods may only be called on the socket thread.
+ //-------------------------------------------------------------------------
+
+ //
+ // called by a transaction when the response headers have all been read.
+ // the connection can force the transaction to reset it's response headers,
+ // and prepare for a new set of response headers, by setting |*reset=TRUE|.
+ //
+ // @return failure code to close the transaction.
+ //
+ [[nodiscard]] virtual nsresult OnHeadersAvailable(nsAHttpTransaction*,
+ nsHttpRequestHead*,
+ nsHttpResponseHead*,
+ bool* reset) = 0;
+
+ //
+ // called by a transaction to resume either sending or receiving data
+ // after a transaction returned NS_BASE_STREAM_WOULD_BLOCK from its
+ // ReadSegments/WriteSegments methods.
+ //
+ [[nodiscard]] virtual nsresult ResumeSend() = 0;
+ [[nodiscard]] virtual nsresult ResumeRecv() = 0;
+
+ // called by a transaction to force a "send/recv from network" iteration
+ // even if not scheduled by socket associated with connection
+ [[nodiscard]] virtual nsresult ForceSend() = 0;
+ [[nodiscard]] virtual nsresult ForceRecv() = 0;
+
+ // After a connection has had ResumeSend() called by a transaction,
+ // and it is ready to write to the network it may need to know the
+ // transaction that has data to write. This is only an issue for
+ // multiplexed protocols like SPDY - h1
+ // implicitly has this information in a 1:1 relationship with the
+ // transaction(s) they manage.
+ virtual void TransactionHasDataToWrite(nsAHttpTransaction*) {
+ // by default do nothing - only multiplexed protocols need to overload
+ }
+
+ // This is the companion to *HasDataToWrite() for the case
+ // when a gecko caller has called ResumeRecv() after being paused
+ virtual void TransactionHasDataToRecv(nsAHttpTransaction*) {
+ // by default do nothing - only multiplexed protocols need to overload
+ }
+
+ // called by the connection manager to close a transaction being processed
+ // by this connection.
+ //
+ // @param transaction
+ // the transaction being closed.
+ // @param reason
+ // the reason for closing the transaction. NS_BASE_STREAM_CLOSED
+ // is equivalent to NS_OK.
+ //
+ virtual void CloseTransaction(nsAHttpTransaction* transaction,
+ nsresult reason) = 0;
+
+ // get a reference to the connection's connection info object.
+ virtual void GetConnectionInfo(nsHttpConnectionInfo**) = 0;
+
+ // get the transport level information for this connection. This may fail
+ // if it is in use.
+ [[nodiscard]] virtual nsresult TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) = 0;
+
+ [[nodiscard]] virtual Http3WebTransportSession* GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) = 0;
+
+ // called by a transaction to get the TLS socket control from the socket.
+ virtual void GetTLSSocketControl(nsITLSSocketControl**) = 0;
+
+ // called by a transaction to determine whether or not the connection is
+ // persistent... important in determining the end of a response.
+ virtual bool IsPersistent() = 0;
+
+ // called to determine or set if a connection has been reused.
+ virtual bool IsReused() = 0;
+ virtual void DontReuse() = 0;
+
+ // called by a transaction when the transaction reads more from the socket
+ // than it should have (eg. containing part of the next response).
+ [[nodiscard]] virtual nsresult PushBack(const char* data,
+ uint32_t length) = 0;
+
+ // Used to determine if the connection wants read events even though
+ // it has not written out a transaction. Used when a connection has issued
+ // a preamble such as a proxy ssl CONNECT sequence.
+ virtual bool IsProxyConnectInProgress() = 0;
+
+ // Used by a transaction to manage the state of previous response bodies on
+ // the same connection and work around buggy servers.
+ virtual bool LastTransactionExpectedNoContent() = 0;
+ virtual void SetLastTransactionExpectedNoContent(bool) = 0;
+
+ // Transfer the base http connection object along with a
+ // reference to it to the caller.
+ virtual already_AddRefed<HttpConnectionBase> TakeHttpConnection() = 0;
+
+ // Like TakeHttpConnection() but do not drop our own ref
+ virtual already_AddRefed<HttpConnectionBase> HttpConnection() = 0;
+
+ // Get the nsISocketTransport used by the connection without changing
+ // references or ownership.
+ virtual nsISocketTransport* Transport() = 0;
+
+ // The number of transaction bytes written out on this HTTP Connection, does
+ // not count CONNECT tunnel setup
+ virtual int64_t BytesWritten() = 0;
+
+ // Update the callbacks used to provide security info. May be called on
+ // any thread.
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
+
+ // nsHttp.h version
+ virtual HttpVersion Version() = 0;
+
+ // A notification of the current active tab id change.
+ virtual void CurrentBrowserIdChanged(uint64_t id) = 0;
+
+ // categories set by nsHttpTransaction to identify how this connection is
+ // being used.
+ virtual void SetTrafficCategory(HttpTrafficCategory) = 0;
+
+ virtual nsresult GetSelfAddr(NetAddr* addr) = 0;
+ virtual nsresult GetPeerAddr(NetAddr* addr) = 0;
+ virtual bool ResolvedByTRR() = 0;
+ virtual nsIRequest::TRRMode EffectiveTRRMode() = 0;
+ virtual nsITRRSkipReason::value TRRSkipReason() = 0;
+ virtual bool GetEchConfigUsed() = 0;
+ virtual PRIntervalTime LastWriteTime() = 0;
+ virtual void SetCloseReason(ConnectionCloseReason aReason) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
+
+#define NS_DECL_NSAHTTPCONNECTION(fwdObject) \
+ [[nodiscard]] nsresult OnHeadersAvailable( \
+ nsAHttpTransaction*, nsHttpRequestHead*, nsHttpResponseHead*, \
+ bool* reset) override; \
+ void CloseTransaction(nsAHttpTransaction*, nsresult) override; \
+ [[nodiscard]] nsresult TakeTransport( \
+ nsISocketTransport**, nsIAsyncInputStream**, nsIAsyncOutputStream**) \
+ override; \
+ [[nodiscard]] Http3WebTransportSession* GetWebTransportSession( \
+ nsAHttpTransaction* aTransaction) override; \
+ bool IsPersistent() override; \
+ bool IsReused() override; \
+ void DontReuse() override; \
+ [[nodiscard]] nsresult PushBack(const char*, uint32_t) override; \
+ already_AddRefed<HttpConnectionBase> TakeHttpConnection() override; \
+ already_AddRefed<HttpConnectionBase> HttpConnection() override; \
+ void CurrentBrowserIdChanged(uint64_t id) override; \
+ /* \
+ Thes methods below have automatic definitions that just forward the \
+ function to a lower level connection object \
+ */ \
+ void GetConnectionInfo(nsHttpConnectionInfo** result) override { \
+ if (!(fwdObject)) { \
+ *result = nullptr; \
+ return; \
+ } \
+ return (fwdObject)->GetConnectionInfo(result); \
+ } \
+ void GetTLSSocketControl(nsITLSSocketControl** result) override { \
+ if (!(fwdObject)) { \
+ *result = nullptr; \
+ return; \
+ } \
+ return (fwdObject)->GetTLSSocketControl(result); \
+ } \
+ [[nodiscard]] nsresult ResumeSend() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeSend(); \
+ } \
+ [[nodiscard]] nsresult ResumeRecv() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeRecv(); \
+ } \
+ [[nodiscard]] nsresult ForceSend() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceSend(); \
+ } \
+ [[nodiscard]] nsresult ForceRecv() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceRecv(); \
+ } \
+ nsISocketTransport* Transport() override { \
+ if (!(fwdObject)) return nullptr; \
+ return (fwdObject)->Transport(); \
+ } \
+ HttpVersion Version() override { \
+ return (fwdObject) ? (fwdObject)->Version() \
+ : mozilla::net::HttpVersion::UNKNOWN; \
+ } \
+ bool IsProxyConnectInProgress() override { \
+ return (!(fwdObject)) ? false : (fwdObject)->IsProxyConnectInProgress(); \
+ } \
+ bool LastTransactionExpectedNoContent() override { \
+ return (!(fwdObject)) ? false \
+ : (fwdObject)->LastTransactionExpectedNoContent(); \
+ } \
+ void SetLastTransactionExpectedNoContent(bool val) override { \
+ if (fwdObject) (fwdObject)->SetLastTransactionExpectedNoContent(val); \
+ } \
+ int64_t BytesWritten() override { \
+ return (fwdObject) ? (fwdObject)->BytesWritten() : 0; \
+ } \
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) override { \
+ if (fwdObject) (fwdObject)->SetSecurityCallbacks(aCallbacks); \
+ } \
+ void SetTrafficCategory(HttpTrafficCategory aCategory) override { \
+ if (fwdObject) (fwdObject)->SetTrafficCategory(aCategory); \
+ } \
+ nsresult GetSelfAddr(NetAddr* addr) override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->GetSelfAddr(addr); \
+ } \
+ nsresult GetPeerAddr(NetAddr* addr) override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->GetPeerAddr(addr); \
+ } \
+ bool ResolvedByTRR() override { \
+ return (!(fwdObject)) ? false : (fwdObject)->ResolvedByTRR(); \
+ } \
+ nsIRequest::TRRMode EffectiveTRRMode() override { \
+ return (!(fwdObject)) ? nsIRequest::TRR_DEFAULT_MODE \
+ : (fwdObject)->EffectiveTRRMode(); \
+ } \
+ nsITRRSkipReason::value TRRSkipReason() override { \
+ return (!(fwdObject)) ? nsITRRSkipReason::TRR_UNSET \
+ : (fwdObject)->TRRSkipReason(); \
+ } \
+ bool GetEchConfigUsed() override { \
+ return (!(fwdObject)) ? false : (fwdObject)->GetEchConfigUsed(); \
+ } \
+ void SetCloseReason(ConnectionCloseReason aReason) override { \
+ if (fwdObject) (fwdObject)->SetCloseReason(aReason); \
+ } \
+ PRIntervalTime LastWriteTime() override;
+
+// ThrottleResponse deliberately ommited since we want different implementation
+// for h1 and h2 connections.
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsAHttpConnection_h__
diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h
new file mode 100644
index 0000000000..06912c3695
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -0,0 +1,307 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAHttpTransaction_h__
+#define nsAHttpTransaction_h__
+
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include "nsIRequest.h"
+#include "nsITRRSkipReason.h"
+
+#ifdef Status
+/* Xlib headers insist on this for some reason... Nuke it because
+ it'll override our member name */
+typedef Status __StatusTmp;
+# undef Status
+typedef __StatusTmp Status;
+#endif
+
+class nsIDNSHTTPSSVCRecord;
+class nsIInterfaceRequestor;
+class nsIRequestContext;
+class nsISVCBRecord;
+class nsITLSSocketControl;
+class nsITransport;
+
+namespace mozilla {
+namespace net {
+
+class nsAHttpConnection;
+class nsAHttpSegmentReader;
+class nsAHttpSegmentWriter;
+class nsHttpTransaction;
+class nsHttpRequestHead;
+class nsHttpConnectionInfo;
+class NullHttpTransaction;
+
+enum class WebSocketSupport { UNSURE, NO_SUPPORT, SUPPORTED };
+
+//----------------------------------------------------------------------------
+// Abstract base class for a HTTP transaction:
+//
+// A transaction is a "sink" for the response data. The connection pushes
+// data to the transaction by writing to it. The transaction supports
+// WriteSegments and may refuse to accept data if its buffers are full (its
+// write function returns NS_BASE_STREAM_WOULD_BLOCK in this case).
+//----------------------------------------------------------------------------
+
+// 2af6d634-13e3-494c-8903-c9dce5c22fc0
+#define NS_AHTTPTRANSACTION_IID \
+ { \
+ 0x2af6d634, 0x13e3, 0x494c, { \
+ 0x89, 0x03, 0xc9, 0xdc, 0xe5, 0xc2, 0x2f, 0xc0 \
+ } \
+ }
+
+class nsAHttpTransaction : public nsSupportsWeakReference {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPTRANSACTION_IID)
+
+ // called by the connection when it takes ownership of the transaction.
+ virtual void SetConnection(nsAHttpConnection*) = 0;
+
+ // called by the connection after a successfull activation of this transaction
+ // in other words, tells the transaction it transitioned to the "active"
+ // state.
+ virtual void OnActivated() {}
+
+ // used to obtain the connection associated with this transaction
+ virtual nsAHttpConnection* Connection() = 0;
+
+ // called by the connection to get security callbacks to set on the
+ // socket transport.
+ virtual void GetSecurityCallbacks(nsIInterfaceRequestor**) = 0;
+
+ // called to report socket status (see nsITransportEventSink)
+ virtual void OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress) = 0;
+
+ // called to check the transaction status.
+ virtual bool IsDone() = 0;
+ virtual nsresult Status() = 0;
+ virtual uint32_t Caps() = 0;
+
+ // called to read request data from the transaction.
+ [[nodiscard]] virtual nsresult ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) = 0;
+
+ // called to write response data to the transaction.
+ [[nodiscard]] virtual nsresult WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) = 0;
+
+ // These versions of the functions allow the overloader to specify whether or
+ // not it is safe to call *Segments() in a loop while they return OK.
+ // The callee should turn again to false if it is not, otherwise leave
+ // untouched
+ [[nodiscard]] virtual nsresult ReadSegmentsAgain(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead,
+ bool* again) {
+ return ReadSegments(reader, count, countRead);
+ }
+ [[nodiscard]] virtual nsresult WriteSegmentsAgain(
+ nsAHttpSegmentWriter* writer, uint32_t count, uint32_t* countWritten,
+ bool* again) {
+ return WriteSegments(writer, count, countWritten);
+ }
+
+ // called to close the transaction
+ virtual void Close(nsresult reason) = 0;
+
+ // called to indicate a failure with proxy CONNECT
+ virtual void SetProxyConnectFailed() = 0;
+
+ // called to retrieve the request headers of the transaction
+ virtual nsHttpRequestHead* RequestHead() = 0;
+
+ // determine the number of real http/1.x transactions on this
+ // abstract object. Pipelines had multiple, SPDY has 0,
+ // normal http transactions have 1.
+ virtual uint32_t Http1xTransactionCount() = 0;
+
+ // called to remove the unused sub transactions from an object that can
+ // handle multiple transactions simultaneously (i.e. h2).
+ //
+ // Returns NS_ERROR_NOT_IMPLEMENTED if the object does not implement
+ // sub-transactions.
+ //
+ // Returns NS_ERROR_ALREADY_OPENED if the subtransactions have been
+ // at least partially written and cannot be moved.
+ //
+ [[nodiscard]] virtual nsresult TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) = 0;
+
+ // Occasionally the abstract interface has to give way to base implementations
+ // to respect differences between spdy, h2, etc..
+ // These Query* (and IsNullTransaction()) functions provide a way to do
+ // that without using xpcom or rtti. Any calling code that can't deal with
+ // a null response from one of them probably shouldn't be using
+ // nsAHttpTransaction
+
+ // equivalent to !!dynamic_cast<NullHttpTransaction *>(this)
+ // A null transaction is expected to return BASE_STREAM_CLOSED on all of
+ // its IO functions all the time.
+ virtual bool IsNullTransaction() { return false; }
+ virtual NullHttpTransaction* QueryNullTransaction() { return nullptr; }
+
+ // If we used rtti this would be the result of doing
+ // dynamic_cast<nsHttpTransaction *>(this).. i.e. it can be nullptr for
+ // non nsHttpTransaction implementations of nsAHttpTransaction
+ virtual nsHttpTransaction* QueryHttpTransaction() { return nullptr; }
+
+ // return the request context associated with the transaction
+ virtual nsIRequestContext* RequestContext() { return nullptr; }
+
+ // return the connection information associated with the transaction
+ virtual nsHttpConnectionInfo* ConnectionInfo() = 0;
+
+ // The base definition of these is done in nsHttpTransaction.cpp
+ virtual bool ResponseTimeoutEnabled() const;
+ virtual PRIntervalTime ResponseTimeout();
+
+ // conceptually the socket control is part of the connection, but sometimes
+ // in the case of TLS tunneled within TLS the transaction might present
+ // a more specific socket control that cannot be represented as a layer in
+ // the connection due to multiplexing. This interface represents such an
+ // overload. If it returns NS_FAILURE the connection should be considered
+ // authoritative.
+ [[nodiscard]] virtual nsresult GetTransactionTLSSocketControl(
+ nsITLSSocketControl**) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual void DisableSpdy() {}
+ // When called, we disallow to connect through a Http/2 proxy.
+ virtual void DisableHttp2ForProxy() {}
+ virtual void DisableHttp3(bool aAllowRetryHTTPSRR) {}
+ virtual void MakeNonSticky() {}
+ virtual void MakeRestartable() {}
+ virtual void ReuseConnectionOnRestartOK(bool) {}
+ virtual void SetIsHttp2Websocket(bool) {}
+ virtual bool IsHttp2Websocket() { return false; }
+ virtual void SetTRRInfo(nsIRequest::TRRMode aMode,
+ TRRSkippedReason aSkipReason){};
+
+ // We call this function if we want to use alt-svc host again on the next
+ // restart. If this function is not called on the next restart the
+ // transaction will use the original route.
+ // For example in case we receive a GOAWAY frame from a server, we can
+ // restart and use the same alt-svc. If we get VersionFallback we do not
+ // want to use the alt-svc on the restart.
+ virtual void DoNotRemoveAltSvc() {}
+
+ // We call this function if we do want to reset IP family preference again on
+ // the next restart.
+ virtual void DoNotResetIPFamilyPreference() {}
+
+ // Returns true if early-data is possible and transaction will remember
+ // that it is in 0RTT mode (to know should it rewide transaction or not
+ // in the case of an error).
+ [[nodiscard]] virtual bool Do0RTT() { return false; }
+ // This function will be called when a tls handshake has been finished and
+ // we know whether early-data that was sent has been accepted or not, e.g.
+ // do we need to restart a transaction. This will be called only if Do0RTT
+ // returns true.
+ // If aRestart parameter is true we need to restart the transaction,
+ // otherwise the erly-data has been accepted and we can continue the
+ // transaction.
+ // If aAlpnChanged is true (and we were assuming http/2), we'll need to take
+ // the transactions out of the session, rewind them all, and start them back
+ // over as http/1 transactions
+ // The function will return success or failure of the transaction restart.
+ [[nodiscard]] virtual nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual uint64_t BrowserId() {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+
+ virtual void OnProxyConnectComplete(int32_t aResponseCode) {}
+
+ virtual nsresult FetchHTTPSRR() { return NS_ERROR_NOT_IMPLEMENTED; }
+ virtual nsresult OnHTTPSRRAvailable(nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTransaction, NS_AHTTPTRANSACTION_IID)
+
+#define NS_DECL_NSAHTTPTRANSACTION \
+ void SetConnection(nsAHttpConnection*) override; \
+ nsAHttpConnection* Connection() override; \
+ void GetSecurityCallbacks(nsIInterfaceRequestor**) override; \
+ void OnTransportStatus(nsITransport* transport, nsresult status, \
+ int64_t progress) override; \
+ bool IsDone() override; \
+ nsresult Status() override; \
+ uint32_t Caps() override; \
+ [[nodiscard]] virtual nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t, \
+ uint32_t*) override; \
+ [[nodiscard]] virtual nsresult WriteSegments(nsAHttpSegmentWriter*, \
+ uint32_t, uint32_t*) override; \
+ virtual void Close(nsresult reason) override; \
+ nsHttpConnectionInfo* ConnectionInfo() override; \
+ void SetProxyConnectFailed() override; \
+ virtual nsHttpRequestHead* RequestHead() override; \
+ uint32_t Http1xTransactionCount() override; \
+ [[nodiscard]] nsresult TakeSubTransactions( \
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentReader {
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ public:
+ // any returned failure code stops segment iteration
+ [[nodiscard]] virtual nsresult OnReadSegment(const char* segment,
+ uint32_t count,
+ uint32_t* countRead) = 0;
+
+ // Ask the segment reader to commit to accepting size bytes of
+ // data from subsequent OnReadSegment() calls or throw hard
+ // (i.e. not wouldblock) exceptions. Implementations
+ // can return NS_ERROR_FAILURE if they never make commitments of that size
+ // (the default), NS_OK if they make the commitment, or
+ // NS_BASE_STREAM_WOULD_BLOCK if they cannot make the
+ // commitment now but might in the future and forceCommitment is not true .
+ // (forceCommitment requires a hard failure or OK at this moment.)
+ //
+ // SpdySession uses this to make sure frames are atomic.
+ [[nodiscard]] virtual nsresult CommitToSegmentSize(uint32_t size,
+ bool forceCommitment) {
+ return NS_ERROR_FAILURE;
+ }
+};
+
+#define NS_DECL_NSAHTTPSEGMENTREADER \
+ [[nodiscard]] nsresult OnReadSegment(const char*, uint32_t, uint32_t*) \
+ override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentWriter {
+ public:
+ // any returned failure code stops segment iteration
+ [[nodiscard]] virtual nsresult OnWriteSegment(char* segment, uint32_t count,
+ uint32_t* countWritten) = 0;
+};
+
+#define NS_DECL_NSAHTTPSEGMENTWRITER \
+ [[nodiscard]] nsresult OnWriteSegment(char*, uint32_t, uint32_t*) override;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsAHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.cpp b/netwerk/protocol/http/nsCORSListenerProxy.cpp
new file mode 100644
index 0000000000..90ac6a4ddc
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -0,0 +1,1753 @@
+/* -*- 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 "nsIThreadRetargetableStreamListener.h"
+#include "nsString.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticPrefs_content.h"
+#include "mozilla/StoragePrincipalHelper.h"
+
+#include "nsCORSListenerProxy.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMimeTypes.h"
+#include "nsStringStream.h"
+#include "nsGkAtoms.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsStreamUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsIScriptError.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+#include "nsIConsoleService.h"
+#include "nsINetworkInterceptController.h"
+#include "nsICorsPreflightCallback.h"
+#include "nsISupportsImpl.h"
+#include "nsHttpChannel.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsQueryObject.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+#define PREFLIGHT_CACHE_SIZE 100
+// 5 seconds is chosen to be compatible with Chromium.
+#define PREFLIGHT_DEFAULT_EXPIRY_SECONDS 5
+
+static inline nsAutoString GetStatusCodeAsString(nsIHttpChannel* aHttp) {
+ nsAutoString result;
+ uint32_t code;
+ if (NS_SUCCEEDED(aHttp->GetResponseStatus(&code))) {
+ result.AppendInt(code);
+ }
+ return result;
+}
+
+static void LogBlockedRequest(nsIRequest* aRequest, const char* aProperty,
+ const char16_t* aParam, uint32_t aBlockingReason,
+ nsIHttpChannel* aCreatingChannel,
+ bool aIsWarning = false) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ if (!aIsWarning) {
+ NS_SetRequestBlockingReason(channel, aBlockingReason);
+ }
+
+ nsCOMPtr<nsIURI> aUri;
+ channel->GetURI(getter_AddRefs(aUri));
+ nsAutoCString spec;
+ if (aUri) {
+ spec = aUri->GetSpecOrDefault();
+ }
+
+ // Generate the error message
+ nsAutoString blockedMessage;
+ AutoTArray<nsString, 2> params;
+ CopyUTF8toUTF16(spec, *params.AppendElement());
+ if (aParam) {
+ params.AppendElement(aParam);
+ }
+ NS_ConvertUTF8toUTF16 specUTF16(spec);
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES, aProperty, params, blockedMessage);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no formalizedStr");
+ return;
+ }
+
+ nsAutoString msg(blockedMessage.get());
+ nsDependentCString category(aProperty);
+
+ if (XRE_IsParentProcess()) {
+ if (aCreatingChannel) {
+ rv = aCreatingChannel->LogBlockedCORSRequest(msg, category, aIsWarning);
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+ NS_WARNING(
+ "Failed to log blocked cross-site request to web console from "
+ "parent->child, falling back to browser console");
+ }
+
+ bool privateBrowsing = false;
+ if (aRequest) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ privateBrowsing = nsContentUtils::IsInPrivateBrowsing(loadGroup);
+ }
+
+ bool fromChromeContext = false;
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ fromChromeContext = loadInfo->TriggeringPrincipal()->IsSystemPrincipal();
+ }
+
+ // we are passing aProperty as the category so we can link to the
+ // appropriate MDN docs depending on the specific error.
+ uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
+ // The |innerWindowID| could be 0 if this request is created from script.
+ // We can always try top level content window id in this case,
+ // since the window id can lead to current top level window's web console.
+ if (!innerWindowID) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (httpChannel) {
+ Unused << httpChannel->GetTopLevelContentWindowId(&innerWindowID);
+ }
+ }
+ nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, privateBrowsing,
+ fromChromeContext, msg, category,
+ aIsWarning);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Preflight cache
+
+class nsPreflightCache {
+ public:
+ struct TokenTime {
+ nsCString token;
+ TimeStamp expirationTime;
+ };
+
+ struct CacheEntry : public LinkedListElement<CacheEntry> {
+ explicit CacheEntry(nsCString& aKey, bool aPrivateBrowsing)
+ : mKey(aKey), mPrivateBrowsing(aPrivateBrowsing) {
+ MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry);
+ }
+
+ ~CacheEntry() { MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry); }
+
+ void PurgeExpired(TimeStamp now);
+ bool CheckRequest(const nsCString& aMethod,
+ const nsTArray<nsCString>& aHeaders);
+
+ nsCString mKey;
+ bool mPrivateBrowsing{false};
+ nsTArray<TokenTime> mMethods;
+ nsTArray<TokenTime> mHeaders;
+ };
+
+ MOZ_COUNTED_DEFAULT_CTOR(nsPreflightCache)
+
+ ~nsPreflightCache() {
+ Clear();
+ MOZ_COUNT_DTOR(nsPreflightCache);
+ }
+
+ bool Initialize() { return true; }
+
+ CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aWithCredentials,
+ const OriginAttributes& aOriginAttributes, bool aCreate);
+ void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ const OriginAttributes& aOriginAttributes);
+ void PurgePrivateBrowsingEntries();
+
+ void Clear();
+
+ private:
+ nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
+ LinkedList<CacheEntry> mList;
+};
+
+// Will be initialized in EnsurePreflightCache.
+static nsPreflightCache* sPreflightCache = nullptr;
+
+static bool EnsurePreflightCache() {
+ if (sPreflightCache) return true;
+
+ UniquePtr<nsPreflightCache> newCache(new nsPreflightCache());
+
+ if (newCache->Initialize()) {
+ sPreflightCache = newCache.release();
+ return true;
+ }
+
+ return false;
+}
+
+void nsPreflightCache::PurgePrivateBrowsingEntries() {
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ auto* entry = iter.UserData();
+ if (entry->mPrivateBrowsing) {
+ // last private browsing window closed, remove preflight cache entries
+ entry->removeFrom(sPreflightCache->mList);
+ iter.Remove();
+ }
+ }
+}
+
+void nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now) {
+ for (uint32_t i = 0, len = mMethods.Length(); i < len; ++i) {
+ if (now >= mMethods[i].expirationTime) {
+ mMethods.UnorderedRemoveElementAt(i);
+ --i; // Examine the element again, if necessary.
+ --len;
+ }
+ }
+ for (uint32_t i = 0, len = mHeaders.Length(); i < len; ++i) {
+ if (now >= mHeaders[i].expirationTime) {
+ mHeaders.UnorderedRemoveElementAt(i);
+ --i; // Examine the element again, if necessary.
+ --len;
+ }
+ }
+}
+
+bool nsPreflightCache::CacheEntry::CheckRequest(
+ const nsCString& aMethod, const nsTArray<nsCString>& aHeaders) {
+ PurgeExpired(TimeStamp::NowLoRes());
+
+ if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
+ struct CheckToken {
+ bool Equals(const TokenTime& e, const nsCString& method) const {
+ return e.token.Equals(method);
+ }
+ };
+
+ if (!mMethods.Contains(aMethod, CheckToken())) {
+ return false;
+ }
+ }
+
+ struct CheckHeaderToken {
+ bool Equals(const TokenTime& e, const nsCString& header) const {
+ return e.token.Equals(header, nsCaseInsensitiveCStringComparator);
+ }
+ } checker;
+ for (uint32_t i = 0; i < aHeaders.Length(); ++i) {
+ if (!mHeaders.Contains(aHeaders[i], checker)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsPreflightCache::CacheEntry* nsPreflightCache::GetEntry(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, bool aWithCredentials,
+ const OriginAttributes& aOriginAttributes, bool aCreate) {
+ nsCString key;
+ if (NS_FAILED(aPrincipal->GetPrefLightCacheKey(aURI, aWithCredentials,
+ aOriginAttributes, key))) {
+ NS_WARNING("Invalid cache key!");
+ return nullptr;
+ }
+
+ CacheEntry* existingEntry = nullptr;
+
+ if (mTable.Get(key, &existingEntry)) {
+ // Entry already existed so just return it. Also update the LRU list.
+
+ // Move to the head of the list.
+ existingEntry->removeFrom(mList);
+ mList.insertFront(existingEntry);
+
+ return existingEntry;
+ }
+
+ if (!aCreate) {
+ return nullptr;
+ }
+
+ // This is a new entry, allocate and insert into the table now so that any
+ // failures don't cause items to be removed from a full cache.
+ auto newEntry =
+ MakeUnique<CacheEntry>(key, aOriginAttributes.mPrivateBrowsingId != 0);
+
+ NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE,
+ "Something is borked, too many entries in the cache!");
+
+ // Now enforce the max count.
+ if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
+ // Try to kick out all the expired entries.
+ TimeStamp now = TimeStamp::NowLoRes();
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ auto* entry = iter.UserData();
+ entry->PurgeExpired(now);
+
+ if (entry->mHeaders.IsEmpty() && entry->mMethods.IsEmpty()) {
+ // Expired, remove from the list as well as the hash table.
+ entry->removeFrom(sPreflightCache->mList);
+ iter.Remove();
+ }
+ }
+
+ // If that didn't remove anything then kick out the least recently used
+ // entry.
+ if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
+ CacheEntry* lruEntry = static_cast<CacheEntry*>(mList.popLast());
+ MOZ_ASSERT(lruEntry);
+
+ // This will delete 'lruEntry'.
+ mTable.Remove(lruEntry->mKey);
+
+ NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1,
+ "Somehow tried to remove an entry that was never added!");
+ }
+ }
+
+ auto* newEntryWeakRef = mTable.InsertOrUpdate(key, std::move(newEntry)).get();
+ mList.insertFront(newEntryWeakRef);
+
+ return newEntryWeakRef;
+}
+
+void nsPreflightCache::RemoveEntries(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ const OriginAttributes& aOriginAttributes) {
+ CacheEntry* entry;
+ nsCString key;
+ if (NS_SUCCEEDED(aPrincipal->GetPrefLightCacheKey(aURI, true,
+ aOriginAttributes, key)) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+
+ if (NS_SUCCEEDED(aPrincipal->GetPrefLightCacheKey(aURI, false,
+ aOriginAttributes, key)) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+}
+
+void nsPreflightCache::Clear() {
+ mList.clear();
+ mTable.Clear();
+}
+
+//////////////////////////////////////////////////////////////////////////
+// nsCORSListenerProxy
+
+NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener, nsIRequestObserver,
+ nsIChannelEventSink, nsIInterfaceRequestor,
+ nsIThreadRetargetableStreamListener)
+
+/* static */
+void nsCORSListenerProxy::Shutdown() {
+ delete sPreflightCache;
+ sPreflightCache = nullptr;
+}
+
+/* static */
+void nsCORSListenerProxy::ClearCache() {
+ if (!sPreflightCache) {
+ return;
+ }
+ sPreflightCache->Clear();
+}
+
+// static
+void nsCORSListenerProxy::ClearPrivateBrowsingCache() {
+ if (!sPreflightCache) {
+ return;
+ }
+ sPreflightCache->PurgePrivateBrowsingEntries();
+}
+
+// Usually, when using an expanded principal, there's no particularly good
+// origin to do the request with. However if the expanded principal only wraps
+// one principal, we can use that one instead.
+//
+// This is needed so that DevTools can still do CORS-enabled requests (since
+// DevTools uses a triggering principal expanding the node principal to bypass
+// CSP checks, see Element::CreateDevToolsPrincipal(), bug 1604562, and bug
+// 1391994).
+static nsIPrincipal* GetOriginHeaderPrincipal(nsIPrincipal* aPrincipal) {
+ while (aPrincipal && aPrincipal->GetIsExpandedPrincipal()) {
+ auto* ep = BasePrincipal::Cast(aPrincipal)->As<ExpandedPrincipal>();
+ if (ep->AllowList().Length() != 1) {
+ break;
+ }
+ aPrincipal = ep->AllowList()[0];
+ }
+ return aPrincipal;
+}
+
+nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials)
+ : mOuterListener(aOuter),
+ mRequestingPrincipal(aRequestingPrincipal),
+ mOriginHeaderPrincipal(GetOriginHeaderPrincipal(aRequestingPrincipal)),
+ mWithCredentials(aWithCredentials),
+ mRequestApproved(false),
+ mHasBeenCrossSite(false),
+#ifdef DEBUG
+ mInited(false),
+#endif
+ mMutex("nsCORSListenerProxy") {
+}
+
+nsresult nsCORSListenerProxy::Init(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI) {
+ aChannel->GetNotificationCallbacks(
+ getter_AddRefs(mOuterNotificationCallbacks));
+ aChannel->SetNotificationCallbacks(this);
+
+ nsresult rv = UpdateChannel(aChannel, aAllowDataURI, UpdateType::Default);
+ if (NS_FAILED(rv)) {
+ {
+ MutexAutoLock lock(mMutex);
+ mOuterListener = nullptr;
+ }
+ mRequestingPrincipal = nullptr;
+ mOriginHeaderPrincipal = nullptr;
+ mOuterNotificationCallbacks = nullptr;
+ mHttpChannel = nullptr;
+ }
+#ifdef DEBUG
+ mInited = true;
+#endif
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ nsresult rv = CheckRequestApproved(aRequest);
+ mRequestApproved = NS_SUCCEEDED(rv);
+ if (!mRequestApproved) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
+ if (uri) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(channel,
+ attrs);
+
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(uri, mRequestingPrincipal, attrs);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(channel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(
+ uri, mRequestingPrincipal, attrs);
+ if (NS_FAILED(rv)) {
+ // Only warn here to ensure we fall through the request Cancel()
+ // and outer listener OnStartRequest() calls.
+ NS_WARNING("Failed to remove CORS preflight cache entry!");
+ }
+ }
+ }
+ }
+ }
+
+ aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ listener->OnStartRequest(aRequest);
+
+ // Reason for NS_ERROR_DOM_BAD_URI already logged in CheckRequestApproved()
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ return listener->OnStartRequest(aRequest);
+}
+
+namespace {
+class CheckOriginHeader final : public nsIHttpHeaderVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ CheckOriginHeader() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ if (aHeader.EqualsLiteral("Access-Control-Allow-Origin")) {
+ mHeaderCount++;
+ }
+
+ if (mHeaderCount > 1) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ return NS_OK;
+ }
+
+ private:
+ uint32_t mHeaderCount{0};
+
+ ~CheckOriginHeader() = default;
+};
+
+NS_IMPL_ISUPPORTS(CheckOriginHeader, nsIHttpHeaderVisitor)
+} // namespace
+
+nsresult nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest) {
+ // Check if this was actually a cross domain request
+ if (!mHasBeenCrossSite) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIHttpChannel> topChannel;
+ topChannel.swap(mHttpChannel);
+
+ if (StaticPrefs::content_cors_disable()) {
+ LogBlockedRequest(aRequest, "CORSDisabled", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDISABLED, topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Check if the request failed
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(aRequest, "CORSDidNotSucceed2", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
+ topChannel);
+ return rv;
+ }
+
+ if (NS_FAILED(status)) {
+ if (NS_BINDING_ABORTED != status) {
+ // Don't want to log mere cancellation as an error.
+ LogBlockedRequest(aRequest, "CORSDidNotSucceed2", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
+ topChannel);
+ }
+ return status;
+ }
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ if (!http) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
+ if (uri && uri->SchemeIs("moz-extension")) {
+ // moz-extension:-URLs do not support CORS, but can universally be read
+ // if an extension lists the resource in web_accessible_resources.
+ // Access will be checked in UpdateChannel.
+ return NS_OK;
+ }
+ LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSREQUESTNOTHTTP,
+ topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = http->LoadInfo();
+ if (loadInfo->GetServiceWorkerTaintingSynthesized()) {
+ // For synthesized responses, we don't need to perform any checks.
+ // Note: This would be unsafe if we ever changed our behavior to allow
+ // service workers to intercept CORS preflights.
+ return NS_OK;
+ }
+
+ // Check the Access-Control-Allow-Origin header
+ RefPtr<CheckOriginHeader> visitor = new CheckOriginHeader();
+ nsAutoCString allowedOriginHeader;
+
+ // check for duplicate headers
+ rv = http->VisitOriginalResponseHeaders(visitor);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(
+ aRequest, "CORSMultipleAllowOriginNotAllowed", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSMULTIPLEALLOWORIGINNOTALLOWED,
+ topChannel);
+ return rv;
+ }
+
+ rv = http->GetResponseHeader("Access-Control-Allow-Origin"_ns,
+ allowedOriginHeader);
+ if (NS_FAILED(rv)) {
+ auto statusCode = GetStatusCodeAsString(http);
+ LogBlockedRequest(aRequest, "CORSMissingAllowOrigin2", statusCode.get(),
+ nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWORIGIN,
+ topChannel);
+ return rv;
+ }
+
+ // Bug 1210985 - Explicitly point out the error that the credential is
+ // not supported if the allowing origin is '*'. Note that this check
+ // has to be done before the condition
+ //
+ // >> if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*"))
+ //
+ // below since "if (A && B)" is included in "if (A || !B)".
+ //
+ if (mWithCredentials && allowedOriginHeader.EqualsLiteral("*")) {
+ LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSNOTSUPPORTINGCREDENTIALS,
+ topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
+ MOZ_ASSERT(!mOriginHeaderPrincipal->GetIsExpandedPrincipal());
+ nsAutoCString origin;
+ mOriginHeaderPrincipal->GetWebExposedOriginSerialization(origin);
+
+ if (!allowedOriginHeader.Equals(origin)) {
+ LogBlockedRequest(
+ aRequest, "CORSAllowOriginNotMatchingOrigin",
+ NS_ConvertUTF8toUTF16(allowedOriginHeader).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSALLOWORIGINNOTMATCHINGORIGIN,
+ topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ // Check Access-Control-Allow-Credentials header
+ if (mWithCredentials) {
+ nsAutoCString allowCredentialsHeader;
+ rv = http->GetResponseHeader("Access-Control-Allow-Credentials"_ns,
+ allowCredentialsHeader);
+
+ if (!allowCredentialsHeader.EqualsLiteral("true")) {
+ LogBlockedRequest(
+ aRequest, "CORSMissingAllowCredentials", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWCREDENTIALS, topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = std::move(mOuterListener);
+ }
+ nsresult rv = listener->OnStopRequest(aRequest, aStatusCode);
+ mOuterNotificationCallbacks = nullptr;
+ mHttpChannel = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // NB: This can be called on any thread! But we're guaranteed that it is
+ // called between OnStartRequest and OnStopRequest, so we don't need to worry
+ // about races.
+
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ if (!mRequestApproved) {
+ // Reason for NS_ERROR_DOM_BAD_URI already logged in CheckRequestApproved()
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ return listener->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(listener);
+ if (retargetableListener) {
+ return retargetableListener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+void nsCORSListenerProxy::SetInterceptController(
+ nsINetworkInterceptController* aInterceptController) {
+ mInterceptController = aInterceptController;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mInterceptController) {
+ nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController);
+ *aResult = copy.forget().take();
+
+ return NS_OK;
+ }
+
+ return mOuterNotificationCallbacks
+ ? mOuterNotificationCallbacks->GetInterface(aIID, aResult)
+ : NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCb) {
+ nsresult rv;
+ if (NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
+ NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
+ // Internal redirects still need to be updated in order to maintain
+ // the correct headers. We use DataURIHandling::Allow, since unallowed
+ // data URIs should have been blocked before we got to the internal
+ // redirect.
+ rv = UpdateChannel(aNewChannel, DataURIHandling::Allow,
+ UpdateType::InternalOrHSTSRedirect);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "nsCORSListenerProxy::AsyncOnChannelRedirect: "
+ "internal redirect UpdateChannel() returned failure");
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ } else {
+ mIsRedirect = true;
+ // A real, external redirect. Perform CORS checking on new URL.
+ rv = CheckRequestApproved(aOldChannel);
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIURI> oldURI;
+ NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
+ if (oldURI) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aOldChannel,
+ attrs);
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal, attrs);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(aOldChannel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(
+ oldURI, mRequestingPrincipal, attrs);
+ if (NS_FAILED(rv)) {
+ // Only warn here to ensure we call the channel Cancel() below
+ NS_WARNING("Failed to remove CORS preflight cache entry!");
+ }
+ }
+ }
+ }
+ aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
+ // Reason for NS_ERROR_DOM_BAD_URI already logged in
+ // CheckRequestApproved()
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (mHasBeenCrossSite) {
+ // Once we've been cross-site, cross-origin redirects reset our source
+ // origin. Note that we need to call GetChannelURIPrincipal() because
+ // we are looking for the principal that is actually being loaded and not
+ // the principal that initiated the load.
+ nsCOMPtr<nsIPrincipal> oldChannelPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
+ aOldChannel, getter_AddRefs(oldChannelPrincipal));
+ nsCOMPtr<nsIPrincipal> newChannelPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
+ aNewChannel, getter_AddRefs(newChannelPrincipal));
+ if (!oldChannelPrincipal || !newChannelPrincipal) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_FAILED(rv)) {
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+
+ if (!oldChannelPrincipal->Equals(newChannelPrincipal)) {
+ // Spec says to set our source origin to a unique origin.
+ mOriginHeaderPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(oldChannelPrincipal);
+ }
+ }
+
+ bool rewriteToGET = false;
+ nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(aOldChannel);
+ if (oldHttpChannel) {
+ nsAutoCString method;
+ Unused << oldHttpChannel->GetRequestMethod(method);
+ Unused << oldHttpChannel->ShouldStripRequestBodyHeader(method,
+ &rewriteToGET);
+ }
+
+ rv = UpdateChannel(aNewChannel, DataURIHandling::Disallow,
+ rewriteToGET ? UpdateType::StripRequestBodyHeader
+ : UpdateType::Default);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "nsCORSListenerProxy::AsyncOnChannelRedirect: "
+ "UpdateChannel() returned failure");
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIChannelEventSink> outer =
+ do_GetInterface(mOuterNotificationCallbacks);
+ if (outer) {
+ return outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCb);
+ }
+
+ aCb->OnRedirectVerifyCallback(NS_OK);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener;
+ {
+ MutexAutoLock lock(mMutex);
+ retargetableListener = do_QueryInterface(mOuterListener);
+ }
+ if (!retargetableListener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return retargetableListener->CheckListenerChain();
+}
+
+// Please note that the CSP directive 'upgrade-insecure-requests' and the
+// HTTPS-Only Mode are relying on the promise that channels get updated from
+// http: to https: before the channel fetches any data from the netwerk. Such
+// channels should not be blocked by CORS and marked as cross origin requests.
+// E.g.: toplevel page: https://www.example.com loads
+// xhr: http://www.example.com/foo which gets updated to
+// https://www.example.com/foo
+// In such a case we should bail out of CORS and rely on the promise that
+// nsHttpChannel::Connect() upgrades the request from http to https.
+bool CheckInsecureUpgradePreventsCORS(nsIPrincipal* aRequestingPrincipal,
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // upgrade insecure requests is only applicable to http requests
+ if (!channelURI->SchemeIs("http")) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> originalURI;
+ rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString principalHost, channelHost, origChannelHost;
+
+ // if we can not query a host from the uri, there is nothing to do
+ if (NS_FAILED(aRequestingPrincipal->GetAsciiHost(principalHost)) ||
+ NS_FAILED(channelURI->GetAsciiHost(channelHost)) ||
+ NS_FAILED(originalURI->GetAsciiHost(origChannelHost))) {
+ return false;
+ }
+
+ // if the hosts do not match, there is nothing to do
+ if (!principalHost.EqualsIgnoreCase(channelHost.get())) {
+ return false;
+ }
+
+ // also check that uri matches the one of the originalURI
+ if (!channelHost.EqualsIgnoreCase(origChannelHost.get())) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI,
+ UpdateType aUpdateType) {
+ nsCOMPtr<nsIURI> uri, originalURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ // Introduced for DevTools in order to allow overriding some requests
+ // with the content of data: URIs.
+ if (loadInfo->GetAllowInsecureRedirectToDataURI() && uri->SchemeIs("data")) {
+ return NS_OK;
+ }
+
+ // exempt data URIs from the same origin check.
+ if (aAllowDataURI == DataURIHandling::Allow && originalURI == uri) {
+ if (uri->SchemeIs("data")) {
+ return NS_OK;
+ }
+ if (loadInfo->GetAboutBlankInherits() && NS_IsAboutBlank(uri)) {
+ return NS_OK;
+ }
+ }
+
+ // Set CORS attributes on channel so that intercepted requests get correct
+ // values. We have to do this here because the CheckMayLoad checks may lead
+ // to early return. We can't be sure this is an http channel though, so we
+ // can't return early on failure.
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aChannel);
+ if (internal) {
+ rv = internal->SetRequestMode(dom::RequestMode::Cors);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = internal->SetCorsIncludeCredentials(mWithCredentials);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // TODO: Bug 1353683
+ // consider calling SetBlockedRequest in nsCORSListenerProxy::UpdateChannel
+ //
+ // Check that the uri is ok to load
+ uint32_t flags = loadInfo->CheckLoadURIFlags();
+ rv = nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
+ mRequestingPrincipal, uri, flags, loadInfo->GetInnerWindowID());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (originalURI != uri) {
+ rv = nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
+ mRequestingPrincipal, originalURI, flags, loadInfo->GetInnerWindowID());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (uri->SchemeIs("moz-extension")) {
+ // moz-extension:-URLs do not support CORS, but can universally be read
+ // if an extension lists the resource in web_accessible_resources.
+ // This is enforced via the CheckLoadURIWithPrincipal call above:
+ // moz-extension resources have the URI_DANGEROUS_TO_LOAD flag, unless
+ // listed in web_accessible_resources.
+ return NS_OK;
+ }
+
+ if (!mHasBeenCrossSite &&
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false)) &&
+ (originalURI == uri ||
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI, false)))) {
+ return NS_OK;
+ }
+
+ // If the CSP directive 'upgrade-insecure-requests' is used or the HTTPS-Only
+ // Mode is enabled then we should not incorrectly require CORS if the only
+ // difference of a subresource request and the main page is the scheme. e.g.
+ // toplevel page: https://www.example.com loads
+ // xhr: http://www.example.com/somefoo,
+ // then the xhr request will be upgraded to https before it fetches any data
+ // from the netwerk, hence we shouldn't require CORS in that specific case.
+ if (CheckInsecureUpgradePreventsCORS(mRequestingPrincipal, aChannel)) {
+ // Check if https-only mode upgrades this later anyway
+ nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo();
+ if (nsHTTPSOnlyUtils::IsSafeToAcceptCORSOrMixedContent(loadinfo)) {
+ return NS_OK;
+ }
+ // Check if 'upgrade-insecure-requests' is used
+ if (loadInfo->GetUpgradeInsecureRequests() ||
+ loadInfo->GetBrowserUpgradeInsecureRequests()) {
+ return NS_OK;
+ }
+ }
+
+ // Check if we need to do a preflight, and if so set one up. This must be
+ // called once we know that the request is going, or has gone, cross-origin.
+ rv = CheckPreflightNeeded(aChannel, aUpdateType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // It's a cross site load
+ mHasBeenCrossSite = true;
+
+ if (mIsRedirect || StaticPrefs::network_cors_preflight_block_userpass_uri()) {
+ // https://fetch.spec.whatwg.org/#http-redirect-fetch
+ // Step 9. If request’s mode is "cors", locationURL includes credentials,
+ // and request’s origin is not same origin with locationURL’s origin,
+ // then return a network error.
+
+ nsAutoCString userpass;
+ uri->GetUserPass(userpass);
+ NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
+ }
+
+ // If we have an expanded principal here, we'll reject the CORS request,
+ // because we can't send a useful Origin header which is required for CORS.
+ if (nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ LogBlockedRequest(aChannel, "CORSOriginHeaderNotAdded", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSORIGINHEADERNOTADDED,
+ httpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Add the Origin header
+ nsAutoCString origin;
+ rv = mOriginHeaderPrincipal->GetWebExposedOriginSerialization(origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
+
+ // hide the Origin header when requesting from .onion and requesting CORS
+ if (StaticPrefs::network_http_referer_hideOnionSource()) {
+ if (mOriginHeaderPrincipal->GetIsOnion()) {
+ origin.AssignLiteral("null");
+ }
+ }
+
+ rv = http->SetRequestHeader(net::nsHttp::Origin.val(), origin, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make cookie-less if needed. We don't need to do anything here if the
+ // channel was opened with AsyncOpen, since then AsyncOpen will take
+ // care of the cookie policy for us.
+ if (!mWithCredentials) {
+ nsLoadFlags flags;
+ rv = http->GetLoadFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ flags |= nsIRequest::LOAD_ANONYMOUS;
+ if (StaticPrefs::network_cors_preflight_allow_client_cert()) {
+ flags |= nsIRequest::LOAD_ANONYMOUS_ALLOW_CLIENT_CERT;
+ }
+ rv = http->SetLoadFlags(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mHttpChannel = http;
+
+ return NS_OK;
+}
+
+nsresult nsCORSListenerProxy::CheckPreflightNeeded(nsIChannel* aChannel,
+ UpdateType aUpdateType) {
+ // If this caller isn't using AsyncOpen, or if this *is* a preflight channel,
+ // then we shouldn't initiate preflight for this channel.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (loadInfo->GetSecurityMode() !=
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT ||
+ loadInfo->GetIsPreflight()) {
+ return NS_OK;
+ }
+
+ bool doPreflight = loadInfo->GetForcePreflight();
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ if (!http) {
+ // Note: A preflight is not needed for moz-extension:-requests either, but
+ // there is already a check for that in the caller of CheckPreflightNeeded,
+ // in UpdateChannel.
+ LogBlockedRequest(aChannel, "CORSRequestNotHttp", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSREQUESTNOTHTTP,
+ mHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString method;
+ Unused << http->GetRequestMethod(method);
+ if (!method.LowerCaseEqualsLiteral("get") &&
+ !method.LowerCaseEqualsLiteral("post") &&
+ !method.LowerCaseEqualsLiteral("head")) {
+ doPreflight = true;
+ }
+
+ // Avoid copying the array here
+ const nsTArray<nsCString>& loadInfoHeaders = loadInfo->CorsUnsafeHeaders();
+ if (!loadInfoHeaders.IsEmpty()) {
+ doPreflight = true;
+ }
+
+ // Add Content-Type header if needed
+ nsTArray<nsCString> headers;
+ nsAutoCString contentTypeHeader;
+ nsresult rv = http->GetRequestHeader("Content-Type"_ns, contentTypeHeader);
+ // GetRequestHeader return an error if the header is not set. Don't add
+ // "content-type" to the list if that's the case.
+ if (NS_SUCCEEDED(rv) &&
+ !nsContentUtils::IsAllowedNonCorsContentType(contentTypeHeader) &&
+ !loadInfoHeaders.Contains("content-type"_ns,
+ nsCaseInsensitiveCStringArrayComparator())) {
+ headers.AppendElements(loadInfoHeaders);
+ headers.AppendElement("content-type"_ns);
+ doPreflight = true;
+ }
+
+ if (!doPreflight) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(http);
+ if (!internal) {
+ auto statusCode = GetStatusCodeAsString(http);
+ LogBlockedRequest(aChannel, "CORSDidNotSucceed2", statusCode.get(),
+ nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
+ mHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ internal->SetCorsPreflightParameters(
+ headers.IsEmpty() ? loadInfoHeaders : headers,
+ aUpdateType == UpdateType::StripRequestBodyHeader);
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Preflight proxy
+
+// Class used as streamlistener and notification callback when
+// doing the initial OPTIONS request for a CORS check
+class nsCORSPreflightListener final : public nsIStreamListener,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink {
+ public:
+ nsCORSPreflightListener(nsIPrincipal* aReferrerPrincipal,
+ nsICorsPreflightCallback* aCallback,
+ nsILoadContext* aLoadContext, bool aWithCredentials,
+ const nsCString& aPreflightMethod,
+ const nsTArray<nsCString>& aPreflightHeaders)
+ : mPreflightMethod(aPreflightMethod),
+ mPreflightHeaders(aPreflightHeaders.Clone()),
+ mReferrerPrincipal(aReferrerPrincipal),
+ mCallback(aCallback),
+ mLoadContext(aLoadContext),
+ mWithCredentials(aWithCredentials) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ nsresult CheckPreflightRequestApproved(nsIRequest* aRequest);
+
+ private:
+ ~nsCORSPreflightListener() = default;
+
+ void AddResultToCache(nsIRequest* aRequest);
+
+ nsCString mPreflightMethod;
+ nsTArray<nsCString> mPreflightHeaders;
+ nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
+ nsCOMPtr<nsICorsPreflightCallback> mCallback;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ bool mWithCredentials;
+};
+
+NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener,
+ nsIRequestObserver, nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+void nsCORSPreflightListener::AddResultToCache(nsIRequest* aRequest) {
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ NS_ASSERTION(http, "Request was not http");
+
+ // The "Access-Control-Max-Age" header should return an age in seconds.
+ nsAutoCString headerVal;
+ uint32_t age = 0;
+ Unused << http->GetResponseHeader("Access-Control-Max-Age"_ns, headerVal);
+ if (headerVal.IsEmpty()) {
+ age = PREFLIGHT_DEFAULT_EXPIRY_SECONDS;
+ } else {
+ // Sanitize the string. We only allow 'delta-seconds' as specified by
+ // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
+ // trailing non-whitespace characters).
+ nsACString::const_char_iterator iter, end;
+ headerVal.BeginReading(iter);
+ headerVal.EndReading(end);
+ while (iter != end) {
+ if (*iter < '0' || *iter > '9') {
+ return;
+ }
+ age = age * 10 + (*iter - '0');
+ // Cap at 24 hours. This also avoids overflow
+ age = std::min(age, 86400U);
+ ++iter;
+ }
+ }
+
+ if (!age || !EnsurePreflightCache()) {
+ return;
+ }
+
+ // String seems fine, go ahead and cache.
+ // Note that we have already checked that these headers follow the correct
+ // syntax.
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(http, getter_AddRefs(uri));
+
+ TimeStamp expirationTime =
+ TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age);
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(http, attrs);
+
+ nsPreflightCache::CacheEntry* entry = sPreflightCache->GetEntry(
+ uri, mReferrerPrincipal, mWithCredentials, attrs, true);
+ if (!entry) {
+ return;
+ }
+
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Methods"_ns,
+ headerVal);
+
+ for (const nsACString& method :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (method.IsEmpty()) {
+ continue;
+ }
+ uint32_t i;
+ for (i = 0; i < entry->mMethods.Length(); ++i) {
+ if (entry->mMethods[i].token.Equals(method)) {
+ entry->mMethods[i].expirationTime = expirationTime;
+ break;
+ }
+ }
+ if (i == entry->mMethods.Length()) {
+ nsPreflightCache::TokenTime* newMethod = entry->mMethods.AppendElement();
+ if (!newMethod) {
+ return;
+ }
+
+ newMethod->token = method;
+ newMethod->expirationTime = expirationTime;
+ }
+ }
+
+ // The "Access-Control-Allow-Headers" header contains a comma separated
+ // list of method names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Headers"_ns,
+ headerVal);
+
+ for (const nsACString& header :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (header.IsEmpty()) {
+ continue;
+ }
+ uint32_t i;
+ for (i = 0; i < entry->mHeaders.Length(); ++i) {
+ if (entry->mHeaders[i].token.Equals(header)) {
+ entry->mHeaders[i].expirationTime = expirationTime;
+ break;
+ }
+ }
+ if (i == entry->mHeaders.Length()) {
+ nsPreflightCache::TokenTime* newHeader = entry->mHeaders.AppendElement();
+ if (!newHeader) {
+ return;
+ }
+
+ newHeader->token = header;
+ newHeader->expirationTime = expirationTime;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnStartRequest(nsIRequest* aRequest) {
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsILoadInfo> loadInfo = channel ? channel->LoadInfo() : nullptr;
+ MOZ_ASSERT(!loadInfo || !loadInfo->GetServiceWorkerTaintingSynthesized());
+ }
+#endif
+
+ nsresult rv = CheckPreflightRequestApproved(aRequest);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Everything worked, try to cache and then fire off the actual request.
+ AddResultToCache(aRequest);
+
+ mCallback->OnPreflightSucceeded();
+ } else {
+ mCallback->OnPreflightFailed(rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ mCallback = nullptr;
+ return NS_OK;
+}
+
+/** nsIStreamListener methods **/
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ uint32_t totalRead;
+ return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ // Only internal redirects allowed for now.
+ if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) &&
+ !NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel);
+ LogBlockedRequest(
+ aOldChannel, "CORSExternalRedirectNotAllowed", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSEXTERNALREDIRECTNOTALLOWED,
+ httpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult nsCORSPreflightListener::CheckPreflightRequestApproved(
+ nsIRequest* aRequest) {
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(status, status);
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(internal);
+ nsCOMPtr<nsIHttpChannel> parentHttpChannel = do_QueryInterface(mCallback);
+
+ bool succeedded;
+ rv = http->GetRequestSucceeded(&succeedded);
+ if (NS_FAILED(rv) || !succeedded) {
+ auto statusCode = GetStatusCodeAsString(http);
+ LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed3", statusCode.get(),
+ nsILoadInfo::BLOCKING_REASON_CORSPREFLIGHTDIDNOTSUCCEED,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString headerVal;
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Methods"_ns,
+ headerVal);
+ bool foundMethod = mPreflightMethod.EqualsLiteral("GET") ||
+ mPreflightMethod.EqualsLiteral("HEAD") ||
+ mPreflightMethod.EqualsLiteral("POST");
+ for (const nsACString& method :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (method.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(method)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowMethod",
+ NS_ConvertUTF8toUTF16(method).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSINVALIDALLOWMETHOD,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (method.EqualsLiteral("*") && !mWithCredentials) {
+ foundMethod = true;
+ } else {
+ foundMethod |= mPreflightMethod.Equals(method);
+ }
+ }
+ if (!foundMethod) {
+ LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSMETHODNOTFOUND,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // The "Access-Control-Allow-Headers" header contains a comma separated
+ // list of header names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Headers"_ns,
+ headerVal);
+ nsTArray<nsCString> headers;
+ bool wildcard = false;
+ bool hasAuthorizationHeader = false;
+ for (const nsACString& header :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (header.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(header)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowHeader",
+ NS_ConvertUTF8toUTF16(header).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSINVALIDALLOWHEADER,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ if (header.EqualsLiteral("*") && !mWithCredentials) {
+ wildcard = true;
+ } else {
+ headers.AppendElement(header);
+ }
+
+ if (header.LowerCaseEqualsASCII("authorization")) {
+ hasAuthorizationHeader = true;
+ }
+ }
+
+ bool authorizationInPreflightHeaders = false;
+ bool authorizationCoveredByWildcard = false;
+ for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
+ // Cache the result of the authorization header.
+ bool isAuthorization =
+ mPreflightHeaders[i].LowerCaseEqualsASCII("authorization");
+ if (wildcard) {
+ if (!isAuthorization) {
+ continue;
+ } else {
+ authorizationInPreflightHeaders = true;
+ if (StaticPrefs::
+ network_cors_preflight_authorization_covered_by_wildcard() &&
+ !hasAuthorizationHeader) {
+ // When `Access-Control-Allow-Headers` is `*` and there is no
+ // `Authorization` header listed, we send a deprecation warning to the
+ // console.
+ LogBlockedRequest(aRequest, "CORSAllowHeaderFromPreflightDeprecation",
+ nullptr, 0, parentHttpChannel, true);
+ glean::network::cors_authorization_header
+ .Get("covered_by_wildcard"_ns)
+ .Add(1);
+ authorizationCoveredByWildcard = true;
+ continue;
+ }
+ }
+ }
+
+ const auto& comparator = nsCaseInsensitiveCStringArrayComparator();
+ if (!headers.Contains(mPreflightHeaders[i], comparator)) {
+ LogBlockedRequest(
+ aRequest, "CORSMissingAllowHeaderFromPreflight2",
+ NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWHEADERFROMPREFLIGHT,
+ parentHttpChannel);
+ if (isAuthorization) {
+ glean::network::cors_authorization_header.Get("disallowed"_ns).Add(1);
+ }
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ if (authorizationInPreflightHeaders && !authorizationCoveredByWildcard) {
+ glean::network::cors_authorization_header.Get("allowed"_ns).Add(1);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(aResult);
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+void nsCORSListenerProxy::RemoveFromCorsPreflightCache(
+ nsIURI* aURI, nsIPrincipal* aRequestingPrincipal,
+ const OriginAttributes& aOriginAttributes) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sPreflightCache) {
+ sPreflightCache->RemoveEntries(aURI, aRequestingPrincipal,
+ aOriginAttributes);
+ }
+}
+
+// static
+nsresult nsCORSListenerProxy::StartCORSPreflight(
+ nsIChannel* aRequestChannel, nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aUnsafeHeaders, nsIChannel** aPreflightChannel) {
+ *aPreflightChannel = nullptr;
+
+ if (StaticPrefs::content_cors_disable()) {
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequestChannel);
+ LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDISABLED, http);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString method;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
+ NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
+ Unused << httpChannel->GetRequestMethod(method);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->LoadInfo();
+ MOZ_ASSERT(originalLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT,
+ "how did we end up here?");
+
+ nsCOMPtr<nsIPrincipal> principal = originalLoadInfo->GetLoadingPrincipal();
+ MOZ_ASSERT(principal && originalLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT,
+ "Should not do CORS loads for top-level loads, so a "
+ "loadingPrincipal should always exist.");
+ bool withCredentials =
+ originalLoadInfo->GetCookiePolicy() == nsILoadInfo::SEC_COOKIES_INCLUDE;
+
+ nsPreflightCache::CacheEntry* entry = nullptr;
+
+ nsLoadFlags loadFlags;
+ rv = aRequestChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Disable preflight cache if devtools says so or on other force reloads
+ bool disableCache = (loadFlags & nsIRequest::LOAD_BYPASS_CACHE);
+
+ if (sPreflightCache && !disableCache) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aRequestChannel,
+ attrs);
+ entry = sPreflightCache->GetEntry(uri, principal, withCredentials, attrs,
+ false);
+ }
+
+ if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
+ aCallback->OnPreflightSucceeded();
+ return NS_OK;
+ }
+
+ // Either it wasn't cached or the cached result has expired. Build a
+ // channel for the OPTIONS request.
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<mozilla::net::LoadInfo*>(originalLoadInfo.get())
+ ->CloneForNewRequest();
+ static_cast<mozilla::net::LoadInfo*>(loadInfo.get())->SetIsPreflight();
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We want to give the preflight channel's notification callbacks the same
+ // load context as the original channel's notification callbacks had. We
+ // don't worry about a load context provided via the loadgroup here, since
+ // they have the same loadgroup.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = aRequestChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+
+ // Preflight requests should never be intercepted by service workers and
+ // are always anonymous.
+ // NOTE: We ignore CORS checks on synthesized responses (see the CORS
+ // preflights, then we need to extend the GetResponseSynthesized() check in
+ // nsCORSListenerProxy::CheckRequestApproved()). If we change our behavior
+ // here and allow service workers to intercept CORS preflights, then that
+ // check won't be safe any more.
+ loadFlags |=
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER | nsIRequest::LOAD_ANONYMOUS;
+
+ if (StaticPrefs::network_cors_preflight_allow_client_cert()) {
+ loadFlags |= nsIRequest::LOAD_ANONYMOUS_ALLOW_CLIENT_CERT;
+ }
+
+ nsCOMPtr<nsIChannel> preflightChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(preflightChannel), uri, loadInfo,
+ nullptr, // PerformanceStorage
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set method and headers
+ nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel);
+ NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!");
+
+ rv = preHttp->SetRequestMethod("OPTIONS"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = preHttp->SetRequestHeader("Access-Control-Request-Method"_ns, method,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the CORS preflight channel's warning reporter to be the same as the
+ // requesting channel so that all log messages are able to be reported through
+ // the warning reporter.
+ RefPtr<nsHttpChannel> reqCh = do_QueryObject(aRequestChannel);
+ RefPtr<nsHttpChannel> preCh = do_QueryObject(preHttp);
+ if (preCh && reqCh) { // there are other implementers of nsIHttpChannel
+ preCh->SetWarningReporter(reqCh->GetWarningReporter());
+ }
+
+ nsTArray<nsCString> preflightHeaders;
+ if (!aUnsafeHeaders.IsEmpty()) {
+ for (uint32_t i = 0; i < aUnsafeHeaders.Length(); ++i) {
+ preflightHeaders.AppendElement();
+ ToLowerCase(aUnsafeHeaders[i], preflightHeaders[i]);
+ }
+ preflightHeaders.Sort();
+ nsAutoCString headers;
+ for (uint32_t i = 0; i < preflightHeaders.Length(); ++i) {
+ if (i != 0) {
+ headers += ',';
+ }
+ headers += preflightHeaders[i];
+ }
+ rv = preHttp->SetRequestHeader("Access-Control-Request-Headers"_ns, headers,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set up listener which will start the original channel
+ RefPtr<nsCORSPreflightListener> preflightListener =
+ new nsCORSPreflightListener(principal, aCallback, loadContext,
+ withCredentials, method, preflightHeaders);
+
+ rv = preflightChannel->SetNotificationCallbacks(preflightListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (preCh && reqCh) {
+ // Per https://fetch.spec.whatwg.org/#cors-preflight-fetch step 1, the
+ // request's referrer and referrer policy should match the original request.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ rv = reqCh->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (referrerInfo) {
+ nsCOMPtr<nsIReferrerInfo> newReferrerInfo =
+ static_cast<dom::ReferrerInfo*>(referrerInfo.get())->Clone();
+ rv = preCh->SetReferrerInfo(newReferrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Start preflight
+ rv = preflightChannel->AsyncOpen(preflightListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Return newly created preflight channel
+ preflightChannel.forget(aPreflightChannel);
+
+ return NS_OK;
+}
+
+// static
+void nsCORSListenerProxy::LogBlockedCORSRequest(
+ uint64_t aInnerWindowID, bool aPrivateBrowsing, bool aFromChromeContext,
+ const nsAString& aMessage, const nsACString& aCategory, bool aIsWarning) {
+ nsresult rv = NS_OK;
+
+ // Build the error object and log it to the console
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no console)");
+ return;
+ }
+
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
+ return;
+ }
+
+ uint32_t errorFlag =
+ aIsWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag;
+
+ // query innerWindowID and log to web console, otherwise log to
+ // the error to the browser console.
+ if (aInnerWindowID > 0) {
+ rv = scriptError->InitWithSanitizedSource(aMessage,
+ u""_ns, // sourceName
+ u""_ns, // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ errorFlag, aCategory,
+ aInnerWindowID);
+ } else {
+ rv = scriptError->Init(aMessage,
+ u""_ns, // sourceName
+ u""_ns, // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ errorFlag, aCategory, aPrivateBrowsing,
+ aFromChromeContext); // From chrome context
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Failed to log blocked cross-site request (scriptError init failed)");
+ return;
+ }
+ console->LogMessage(scriptError);
+}
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.h b/netwerk/protocol/http/nsCORSListenerProxy.h
new file mode 100644
index 0000000000..e96b0a8aca
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -0,0 +1,130 @@
+/* -*- 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 nsCORSListenerProxy_h__
+#define nsCORSListenerProxy_h__
+
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsTArray.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+
+class nsIHttpChannel;
+class nsIURI;
+class nsIPrincipal;
+class nsINetworkInterceptController;
+class nsICorsPreflightCallback;
+
+namespace mozilla {
+namespace net {
+class HttpChannelParent;
+class nsHttpChannel;
+} // namespace net
+} // namespace mozilla
+
+enum class DataURIHandling { Allow, Disallow };
+
+enum class UpdateType {
+ Default,
+ StripRequestBodyHeader,
+ InternalOrHSTSRedirect
+};
+
+class nsCORSListenerProxy final : public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ static void Shutdown();
+ static void ClearCache();
+ static void ClearPrivateBrowsingCache();
+
+ [[nodiscard]] nsresult Init(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI);
+
+ void SetInterceptController(
+ nsINetworkInterceptController* aInterceptController);
+
+ // When CORS blocks a request, log the message to the web console, or the
+ // browser console if no valid inner window ID is found.
+ static void LogBlockedCORSRequest(uint64_t aInnerWindowID,
+ bool aPrivateBrowsing,
+ bool aFromChromeContext,
+ const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning = false);
+
+ private:
+ // Only HttpChannelParent can call RemoveFromCorsPreflightCache
+ friend class mozilla::net::HttpChannelParent;
+ // Only nsHttpChannel can invoke CORS preflights
+ friend class mozilla::net::nsHttpChannel;
+
+ static void RemoveFromCorsPreflightCache(
+ nsIURI* aURI, nsIPrincipal* aRequestingPrincipal,
+ const mozilla::OriginAttributes& aOriginAttributes);
+ [[nodiscard]] static nsresult StartCORSPreflight(
+ nsIChannel* aRequestChannel, nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aUnsafeHeaders, nsIChannel** aPreflightChannel);
+
+ ~nsCORSListenerProxy() = default;
+
+ [[nodiscard]] nsresult UpdateChannel(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI,
+ UpdateType aUpdateType);
+ [[nodiscard]] nsresult CheckRequestApproved(nsIRequest* aRequest);
+ [[nodiscard]] nsresult CheckPreflightNeeded(nsIChannel* aChannel,
+ UpdateType aUpdateType);
+
+ nsCOMPtr<nsIStreamListener> mOuterListener;
+ // The principal that originally kicked off the request
+ nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
+ // The principal to use for our Origin header ("source origin" in spec terms).
+ // This can get changed during redirects, unlike mRequestingPrincipal.
+ nsCOMPtr<nsIPrincipal> mOriginHeaderPrincipal;
+ nsCOMPtr<nsIInterfaceRequestor> mOuterNotificationCallbacks;
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+ bool mWithCredentials;
+ mozilla::Atomic<bool, mozilla::Relaxed> mRequestApproved;
+ // Please note that the member variable mHasBeenCrossSite may rely on the
+ // promise that the CSP directive 'upgrade-insecure-requests' upgrades
+ // an http: request to https: in nsHttpChannel::Connect() and hence
+ // a request might not be marked as cross site request based on that promise.
+ bool mHasBeenCrossSite;
+ bool mIsRedirect = false;
+ // Under e10s, logging happens in the child process. Keep a reference to the
+ // creator nsIHttpChannel in order to find the way back to the child. Released
+ // in OnStopRequest().
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+#ifdef DEBUG
+ bool mInited;
+#endif
+
+ // only locking mOuterListener, because it can be used on different threads.
+ // We guarantee that OnStartRequest, OnDataAvailable and OnStopReques will be
+ // called in order, but to make tsan happy we will lock mOuterListener.
+ mutable mozilla::Mutex mMutex MOZ_UNANNOTATED;
+};
+
+#endif
diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp
new file mode 100644
index 0000000000..768ad91729
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -0,0 +1,1160 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "CacheControlParser.h"
+#include "PLDHashTable.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsHttpHandler.h"
+#include "nsICacheEntry.h"
+#include "nsIRequest.h"
+#include "nsIStandardURL.h"
+#include "nsJSUtils.h"
+#include "nsStandardURL.h"
+#include "sslerr.h"
+#include <errno.h>
+#include <functional>
+#include "nsLiteralString.h"
+#include <string.h>
+
+namespace mozilla {
+namespace net {
+
+const uint32_t kHttp3VersionCount = 5;
+const nsCString kHttp3Versions[] = {"h3-29"_ns, "h3-30"_ns, "h3-31"_ns,
+ "h3-32"_ns, "h3"_ns};
+
+// https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.3
+constexpr uint64_t kWebTransportErrorCodeStart = 0x52e4a40fa8db;
+constexpr uint64_t kWebTransportErrorCodeEnd = 0x52e4a40fa9e2;
+
+// find out how many atoms we have
+#define HTTP_ATOM(_name, _value) Unused_##_name,
+enum {
+#include "nsHttpAtomList.h"
+ NUM_HTTP_ATOMS
+};
+#undef HTTP_ATOM
+
+static StaticDataMutex<nsTHashtable<nsCStringASCIICaseInsensitiveHashKey>>
+ sAtomTable("nsHttp::sAtomTable");
+
+// This is set to true in DestroyAtomTable so we don't try to repopulate the
+// table if ResolveAtom gets called during shutdown for some reason.
+static Atomic<bool> sTableDestroyed{false};
+
+// We put the atoms in a hash table for speedy lookup.. see ResolveAtom.
+namespace nsHttp {
+
+nsresult CreateAtomTable(
+ nsTHashtable<nsCStringASCIICaseInsensitiveHashKey>& base) {
+ if (sTableDestroyed) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+ // fill the table with our known atoms
+ const nsHttpAtomLiteral* atoms[] = {
+#define HTTP_ATOM(_name, _value) &(_name),
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+ };
+
+ if (!base.IsEmpty()) {
+ return NS_OK;
+ }
+ for (const auto* atom : atoms) {
+ Unused << base.PutEntry(atom->val(), fallible);
+ }
+
+ LOG(("Added static atoms to atomTable"));
+ return NS_OK;
+}
+
+nsresult CreateAtomTable() {
+ LOG(("CreateAtomTable"));
+ auto atomTable = sAtomTable.Lock();
+ return CreateAtomTable(atomTable.ref());
+}
+
+void DestroyAtomTable() {
+ LOG(("DestroyAtomTable"));
+ sTableDestroyed = true;
+ auto atomTable = sAtomTable.Lock();
+ atomTable.ref().Clear();
+}
+
+// this function may be called from multiple threads
+nsHttpAtom ResolveAtom(const nsACString& str) {
+ if (str.IsEmpty()) {
+ return {};
+ }
+
+ auto atomTable = sAtomTable.Lock();
+
+ if (atomTable.ref().IsEmpty()) {
+ if (sTableDestroyed) {
+ NS_WARNING("ResolveAtom called during shutdown");
+ return {};
+ }
+
+ NS_WARNING("ResolveAtom called before CreateAtomTable");
+ if (NS_FAILED(CreateAtomTable(atomTable.ref()))) {
+ return {};
+ }
+ }
+
+ // Check if we already have an entry in the table
+ auto* entry = atomTable.ref().GetEntry(str);
+ if (entry) {
+ return nsHttpAtom(entry->GetKey());
+ }
+
+ LOG(("Putting %s header into atom table", nsPromiseFlatCString(str).get()));
+ // Put the string in the table. If it works create the atom.
+ entry = atomTable.ref().PutEntry(str, fallible);
+ if (entry) {
+ return nsHttpAtom(entry->GetKey());
+ }
+ return {};
+}
+
+//
+// From section 2.2 of RFC 2616, a token is defined as:
+//
+// token = 1*<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (octets 0 - 127)>
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+// CTL = <any US-ASCII control character
+// (octets 0 - 31) and DEL (127)>
+// SP = <US-ASCII SP, space (32)>
+// HT = <US-ASCII HT, horizontal-tab (9)>
+//
+static const char kValidTokenMap[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24
+
+ 0, 1, 0, 1, 1, 1, 1, 1, // 32
+ 0, 0, 1, 1, 0, 1, 1, 0, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, // 48
+ 1, 1, 0, 0, 0, 0, 0, 0, // 56
+
+ 0, 1, 1, 1, 1, 1, 1, 1, // 64
+ 1, 1, 1, 1, 1, 1, 1, 1, // 72
+ 1, 1, 1, 1, 1, 1, 1, 1, // 80
+ 1, 1, 1, 0, 0, 0, 1, 1, // 88
+
+ 1, 1, 1, 1, 1, 1, 1, 1, // 96
+ 1, 1, 1, 1, 1, 1, 1, 1, // 104
+ 1, 1, 1, 1, 1, 1, 1, 1, // 112
+ 1, 1, 1, 0, 1, 0, 1, 0 // 120
+};
+bool IsValidToken(const char* start, const char* end) {
+ if (start == end) return false;
+
+ for (; start != end; ++start) {
+ const unsigned char idx = *start;
+ if (idx > 127 || !kValidTokenMap[idx]) return false;
+ }
+
+ return true;
+}
+
+const char* GetProtocolVersion(HttpVersion pv) {
+ switch (pv) {
+ case HttpVersion::v3_0:
+ return "h3";
+ case HttpVersion::v2_0:
+ return "h2";
+ case HttpVersion::v1_0:
+ return "http/1.0";
+ case HttpVersion::v1_1:
+ return "http/1.1";
+ default:
+ NS_WARNING(nsPrintfCString("Unkown protocol version: 0x%X. "
+ "Please file a bug",
+ static_cast<uint32_t>(pv))
+ .get());
+ return "http/1.1";
+ }
+}
+
+// static
+void TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest) {
+ nsAutoCString str(aSource);
+
+ // HTTP whitespace 0x09: '\t', 0x0A: '\n', 0x0D: '\r', 0x20: ' '
+ static const char kHTTPWhitespace[] = "\t\n\r ";
+ str.Trim(kHTTPWhitespace);
+ aDest.Assign(str);
+}
+
+// static
+bool IsReasonableHeaderValue(const nsACString& s) {
+ // Header values MUST NOT contain line-breaks. RFC 2616 technically
+ // permits CTL characters, including CR and LF, in header values provided
+ // they are quoted. However, this can lead to problems if servers do not
+ // interpret quoted strings properly. Disallowing CR and LF here seems
+ // reasonable and keeps things simple. We also disallow a null byte.
+ const nsACString::char_type* end = s.EndReading();
+ for (const nsACString::char_type* i = s.BeginReading(); i != end; ++i) {
+ if (*i == '\r' || *i == '\n' || *i == '\0') {
+ return false;
+ }
+ }
+ return true;
+}
+
+const char* FindToken(const char* input, const char* token, const char* seps) {
+ if (!input) return nullptr;
+
+ int inputLen = strlen(input);
+ int tokenLen = strlen(token);
+
+ if (inputLen < tokenLen) return nullptr;
+
+ const char* inputTop = input;
+ const char* inputEnd = input + inputLen - tokenLen;
+ for (; input <= inputEnd; ++input) {
+ if (nsCRT::strncasecmp(input, token, tokenLen) == 0) {
+ if (input > inputTop && !strchr(seps, *(input - 1))) continue;
+ if (input < inputEnd && !strchr(seps, *(input + tokenLen))) continue;
+ return input;
+ }
+ }
+
+ return nullptr;
+}
+
+bool ParseInt64(const char* input, const char** next, int64_t* r) {
+ MOZ_ASSERT(input);
+ MOZ_ASSERT(r);
+
+ char* end = nullptr;
+ errno = 0; // Clear errno to make sure its value is set by strtoll
+ int64_t value = strtoll(input, &end, /* base */ 10);
+
+ // Fail if: - the parsed number overflows.
+ // - the end points to the start of the input string.
+ // - we parsed a negative value. Consumers don't expect that.
+ if (errno != 0 || end == input || value < 0) {
+ LOG(("nsHttp::ParseInt64 value=%" PRId64 " errno=%d", value, errno));
+ return false;
+ }
+
+ if (next) {
+ *next = end;
+ }
+ *r = value;
+ return true;
+}
+
+bool IsPermanentRedirect(uint32_t httpStatus) {
+ return httpStatus == 301 || httpStatus == 308;
+}
+
+bool ValidationRequired(bool isForcedValid,
+ nsHttpResponseHead* cachedResponseHead,
+ uint32_t loadFlags, bool allowStaleCacheContent,
+ bool forceValidateCacheContent, bool isImmutable,
+ bool customConditionalRequest,
+ nsHttpRequestHead& requestHead, nsICacheEntry* entry,
+ CacheControlParser& cacheControlRequest,
+ bool fromPreviousSession,
+ bool* performBackgroundRevalidation) {
+ if (performBackgroundRevalidation) {
+ *performBackgroundRevalidation = false;
+ }
+
+ // Check isForcedValid to see if it is possible to skip validation.
+ // Don't skip validation if we have serious reason to believe that this
+ // content is invalid (it's expired).
+ // See netwerk/cache2/nsICacheEntry.idl for details
+ if (isForcedValid && (!cachedResponseHead->ExpiresInPast() ||
+ !cachedResponseHead->MustValidateIfExpired())) {
+ LOG(("NOT validating based on isForcedValid being true.\n"));
+ return false;
+ }
+
+ // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
+ if (loadFlags & nsIRequest::LOAD_FROM_CACHE || allowStaleCacheContent) {
+ LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
+ return false;
+ }
+
+ // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
+ // it's revalidated with the server.
+ if (((loadFlags & nsIRequest::VALIDATE_ALWAYS) ||
+ forceValidateCacheContent) &&
+ !isImmutable) {
+ LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
+ return true;
+ }
+
+ // Even if the VALIDATE_NEVER flag is set, there are still some cases in
+ // which we must validate the cached response with the server.
+ if (loadFlags & nsIRequest::VALIDATE_NEVER) {
+ LOG(("VALIDATE_NEVER set\n"));
+ // if no-store validate cached response (see bug 112564)
+ if (cachedResponseHead->NoStore()) {
+ LOG(("Validating based on no-store logic\n"));
+ return true;
+ }
+ LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
+ return false;
+ }
+
+ // check if validation is strictly required...
+ if (cachedResponseHead->MustValidate()) {
+ LOG(("Validating based on MustValidate() returning TRUE\n"));
+ return true;
+ }
+
+ // possibly serve from cache for a custom If-Match/If-Unmodified-Since
+ // conditional request
+ if (customConditionalRequest && !requestHead.HasHeader(nsHttp::If_Match) &&
+ !requestHead.HasHeader(nsHttp::If_Unmodified_Since)) {
+ LOG(("Validating based on a custom conditional request\n"));
+ return true;
+ }
+
+ // previously we also checked for a query-url w/out expiration
+ // and didn't do heuristic on it. but defacto that is allowed now.
+ //
+ // Check if the cache entry has expired...
+
+ bool doValidation = true;
+ uint32_t now = NowInSeconds();
+
+ uint32_t age = 0;
+ nsresult rv = cachedResponseHead->ComputeCurrentAge(now, now, &age);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ uint32_t freshness = 0;
+ rv = cachedResponseHead->ComputeFreshnessLifetime(&freshness);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ uint32_t expiration = 0;
+ rv = entry->GetExpirationTime(&expiration);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ uint32_t maxAgeRequest, maxStaleRequest, minFreshRequest;
+
+ LOG((" NowInSeconds()=%u, expiration time=%u, freshness lifetime=%u, age=%u",
+ now, expiration, freshness, age));
+
+ if (cacheControlRequest.NoCache()) {
+ LOG((" validating, no-cache request"));
+ doValidation = true;
+ } else if (cacheControlRequest.MaxStale(&maxStaleRequest)) {
+ uint32_t staleTime = age > freshness ? age - freshness : 0;
+ doValidation = staleTime > maxStaleRequest;
+ LOG((" validating=%d, max-stale=%u requested", doValidation,
+ maxStaleRequest));
+ } else if (cacheControlRequest.MaxAge(&maxAgeRequest)) {
+ // The input information for age and freshness calculation are in seconds.
+ // Hence, the internal logic can't have better resolution than seconds too.
+ // To make max-age=0 case work even for requests made in less than a second
+ // after the last response has been received, we use >= for compare. This
+ // is correct because of the rounding down of the age calculated value.
+ doValidation = age >= maxAgeRequest;
+ LOG((" validating=%d, max-age=%u requested", doValidation, maxAgeRequest));
+ } else if (cacheControlRequest.MinFresh(&minFreshRequest)) {
+ uint32_t freshTime = freshness > age ? freshness - age : 0;
+ doValidation = freshTime < minFreshRequest;
+ LOG((" validating=%d, min-fresh=%u requested", doValidation,
+ minFreshRequest));
+ } else if (now < expiration) {
+ doValidation = false;
+ LOG((" not validating, expire time not in the past"));
+ } else if (cachedResponseHead->MustValidateIfExpired()) {
+ doValidation = true;
+ } else if (cachedResponseHead->StaleWhileRevalidate(now, expiration) &&
+ StaticPrefs::network_http_stale_while_revalidate_enabled()) {
+ LOG((" not validating, in the stall-while-revalidate window"));
+ doValidation = false;
+ if (performBackgroundRevalidation) {
+ *performBackgroundRevalidation = true;
+ }
+ } else if (loadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
+ // If the cached response does not include expiration infor-
+ // mation, then we must validate the response, despite whether
+ // or not this is the first access this session. This behavior
+ // is consistent with existing browsers and is generally expected
+ // by web authors.
+ if (freshness == 0) {
+ doValidation = true;
+ } else {
+ doValidation = fromPreviousSession;
+ }
+ } else {
+ doValidation = true;
+ }
+
+ LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
+ return doValidation;
+}
+
+nsresult GetHttpResponseHeadFromCacheEntry(
+ nsICacheEntry* entry, nsHttpResponseHead* cachedResponseHead) {
+ nsCString buf;
+ // A "original-response-headers" metadata element holds network original
+ // headers, i.e. the headers in the form as they arrieved from the network. We
+ // need to get the network original headers first, because we need to keep
+ // them in order.
+ nsresult rv = entry->GetMetaDataElement("original-response-headers",
+ getter_Copies(buf));
+ if (NS_SUCCEEDED(rv)) {
+ rv = cachedResponseHead->ParseCachedOriginalHeaders((char*)buf.get());
+ if (NS_FAILED(rv)) {
+ LOG((" failed to parse original-response-headers\n"));
+ }
+ }
+
+ buf.Adopt(nullptr);
+ // A "response-head" metadata element holds response head, e.g. response
+ // status line and headers in the form Firefox uses them internally (no
+ // dupicate headers, etc.).
+ rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Parse string stored in a "response-head" metadata element.
+ // These response headers will be merged with the orignal headers (i.e. the
+ // headers stored in a "original-response-headers" metadata element).
+ rv = cachedResponseHead->ParseCachedHead(buf.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ buf.Adopt(nullptr);
+
+ return NS_OK;
+}
+
+nsresult CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength,
+ nsHttpResponseHead* responseHead) {
+ nsresult rv;
+
+ rv = aEntry->GetDataSize(aSize);
+
+ if (NS_ERROR_IN_PROGRESS == rv) {
+ *aSize = -1;
+ rv = NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!responseHead) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aContentLength = responseHead->ContentLength();
+
+ return NS_OK;
+}
+
+void DetermineFramingAndImmutability(nsICacheEntry* entry,
+ nsHttpResponseHead* responseHead,
+ bool isHttps, bool* weaklyFramed,
+ bool* isImmutable) {
+ nsCString framedBuf;
+ nsresult rv =
+ entry->GetMetaDataElement("strongly-framed", getter_Copies(framedBuf));
+ // describe this in terms of explicitly weakly framed so as to be backwards
+ // compatible with old cache contents which dont have strongly-framed makers
+ *weaklyFramed = NS_SUCCEEDED(rv) && framedBuf.EqualsLiteral("0");
+ *isImmutable = !*weaklyFramed && isHttps && responseHead->Immutable();
+}
+
+bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when) {
+ return gHttpHandler &&
+ gHttpHandler->IsBeforeLastActiveTabLoadOptimization(when);
+}
+
+nsCString ConvertRequestHeadToString(nsHttpRequestHead& aRequestHead,
+ bool aHasRequestBody,
+ bool aRequestBodyHasHeaders,
+ bool aUsingConnect) {
+ // Make sure that there is "Content-Length: 0" header in the requestHead
+ // in case of POST and PUT methods when there is no requestBody and
+ // requestHead doesn't contain "Transfer-Encoding" header.
+ //
+ // RFC1945 section 7.2.2:
+ // HTTP/1.0 requests containing an entity body must include a valid
+ // Content-Length header field.
+ //
+ // RFC2616 section 4.4:
+ // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
+ // containing a message-body MUST include a valid Content-Length header
+ // field unless the server is known to be HTTP/1.1 compliant.
+ if ((aRequestHead.IsPost() || aRequestHead.IsPut()) && !aHasRequestBody &&
+ !aRequestHead.HasHeader(nsHttp::Transfer_Encoding)) {
+ DebugOnly<nsresult> rv =
+ aRequestHead.SetHeader(nsHttp::Content_Length, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCString reqHeaderBuf;
+ reqHeaderBuf.Truncate();
+
+ // make sure we eliminate any proxy specific headers from
+ // the request if we are using CONNECT
+ aRequestHead.Flatten(reqHeaderBuf, aUsingConnect);
+
+ if (!aRequestBodyHasHeaders || !aHasRequestBody) {
+ reqHeaderBuf.AppendLiteral("\r\n");
+ }
+
+ return reqHeaderBuf;
+}
+
+void NotifyActiveTabLoadOptimization() {
+ if (gHttpHandler) {
+ gHttpHandler->NotifyActiveTabLoadOptimization();
+ }
+}
+
+TimeStamp GetLastActiveTabLoadOptimizationHit() {
+ return gHttpHandler ? gHttpHandler->GetLastActiveTabLoadOptimizationHit()
+ : TimeStamp();
+}
+
+void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when) {
+ if (gHttpHandler) {
+ gHttpHandler->SetLastActiveTabLoadOptimizationHit(when);
+ }
+}
+
+} // namespace nsHttp
+
+template <typename T>
+void localEnsureBuffer(UniquePtr<T[]>& buf, uint32_t newSize, uint32_t preserve,
+ uint32_t& objSize) {
+ if (objSize >= newSize) return;
+
+ // Leave a little slop on the new allocation - add 2KB to
+ // what we need and then round the result up to a 4KB (page)
+ // boundary.
+
+ objSize = (newSize + 2048 + 4095) & ~4095;
+
+ static_assert(sizeof(T) == 1, "sizeof(T) must be 1");
+ auto tmp = MakeUnique<T[]>(objSize);
+ if (preserve) {
+ memcpy(tmp.get(), buf.get(), preserve);
+ }
+ buf = std::move(tmp);
+}
+
+void EnsureBuffer(UniquePtr<char[]>& buf, uint32_t newSize, uint32_t preserve,
+ uint32_t& objSize) {
+ localEnsureBuffer<char>(buf, newSize, preserve, objSize);
+}
+
+void EnsureBuffer(UniquePtr<uint8_t[]>& buf, uint32_t newSize,
+ uint32_t preserve, uint32_t& objSize) {
+ localEnsureBuffer<uint8_t>(buf, newSize, preserve, objSize);
+}
+
+static bool IsTokenSymbol(signed char chr) {
+ return !(chr < 33 || chr == 127 || chr == '(' || chr == ')' || chr == '<' ||
+ chr == '>' || chr == '@' || chr == ',' || chr == ';' || chr == ':' ||
+ chr == '"' || chr == '/' || chr == '[' || chr == ']' || chr == '?' ||
+ chr == '=' || chr == '{' || chr == '}' || chr == '\\');
+}
+
+ParsedHeaderPair::ParsedHeaderPair(const char* name, int32_t nameLen,
+ const char* val, int32_t valLen,
+ bool isQuotedValue)
+ : mName(nsDependentCSubstring(nullptr, size_t(0))),
+ mValue(nsDependentCSubstring(nullptr, size_t(0))),
+ mIsQuotedValue(isQuotedValue) {
+ if (nameLen > 0) {
+ mName.Rebind(name, name + nameLen);
+ }
+ if (valLen > 0) {
+ if (mIsQuotedValue) {
+ RemoveQuotedStringEscapes(val, valLen);
+ mValue.Rebind(mUnquotedValue.BeginWriting(), mUnquotedValue.Length());
+ } else {
+ mValue.Rebind(val, val + valLen);
+ }
+ }
+}
+
+void ParsedHeaderPair::RemoveQuotedStringEscapes(const char* val,
+ int32_t valLen) {
+ mUnquotedValue.Truncate();
+ const char* c = val;
+ for (int32_t i = 0; i < valLen; ++i) {
+ if (c[i] == '\\' && c[i + 1]) {
+ ++i;
+ }
+ mUnquotedValue.Append(c[i]);
+ }
+}
+
+static void Tokenize(
+ const char* input, uint32_t inputLen, const char token,
+ const std::function<void(const char*, uint32_t)>& consumer) {
+ auto trimWhitespace = [](const char* in, uint32_t inLen, const char** out,
+ uint32_t* outLen) {
+ *out = in;
+ *outLen = inLen;
+ if (inLen == 0) {
+ return;
+ }
+
+ // Trim leading space
+ while (nsCRT::IsAsciiSpace(**out)) {
+ (*out)++;
+ --(*outLen);
+ }
+
+ // Trim tailing space
+ for (const char* i = *out + *outLen - 1; i >= *out; --i) {
+ if (!nsCRT::IsAsciiSpace(*i)) {
+ break;
+ }
+ --(*outLen);
+ }
+ };
+
+ const char* first = input;
+ bool inQuote = false;
+ const char* result = nullptr;
+ uint32_t resultLen = 0;
+ for (uint32_t index = 0; index < inputLen; ++index) {
+ if (inQuote && input[index] == '\\' && input[index + 1]) {
+ index++;
+ continue;
+ }
+ if (input[index] == '"') {
+ inQuote = !inQuote;
+ continue;
+ }
+ if (inQuote) {
+ continue;
+ }
+ if (input[index] == token) {
+ trimWhitespace(first, (input + index) - first, &result, &resultLen);
+ consumer(result, resultLen);
+ first = input + index + 1;
+ }
+ }
+
+ trimWhitespace(first, (input + inputLen) - first, &result, &resultLen);
+ consumer(result, resultLen);
+}
+
+ParsedHeaderValueList::ParsedHeaderValueList(const char* t, uint32_t len,
+ bool allowInvalidValue) {
+ if (!len) {
+ return;
+ }
+
+ ParsedHeaderValueList* self = this;
+ auto consumer = [=](const char* output, uint32_t outputLength) {
+ self->ParseNameAndValue(output, allowInvalidValue);
+ };
+
+ Tokenize(t, len, ';', consumer);
+}
+
+void ParsedHeaderValueList::ParseNameAndValue(const char* input,
+ bool allowInvalidValue) {
+ const char* nameStart = input;
+ const char* nameEnd = nullptr;
+ const char* valueStart = input;
+ const char* valueEnd = nullptr;
+ bool isQuotedString = false;
+ bool invalidValue = false;
+
+ for (; *input && *input != ';' && *input != ',' &&
+ !nsCRT::IsAsciiSpace(*input) && *input != '=';
+ input++) {
+ ;
+ }
+
+ nameEnd = input;
+
+ if (!(nameEnd - nameStart)) {
+ return;
+ }
+
+ // Check whether param name is a valid token.
+ for (const char* c = nameStart; c < nameEnd; c++) {
+ if (!IsTokenSymbol(*c)) {
+ nameEnd = c;
+ break;
+ }
+ }
+
+ if (!(nameEnd - nameStart)) {
+ return;
+ }
+
+ while (nsCRT::IsAsciiSpace(*input)) {
+ ++input;
+ }
+
+ if (!*input || *input++ != '=') {
+ mValues.AppendElement(
+ ParsedHeaderPair(nameStart, nameEnd - nameStart, nullptr, 0, false));
+ return;
+ }
+
+ while (nsCRT::IsAsciiSpace(*input)) {
+ ++input;
+ }
+
+ if (*input != '"') {
+ // The value is a token, not a quoted string.
+ valueStart = input;
+ for (valueEnd = input; *valueEnd && !nsCRT::IsAsciiSpace(*valueEnd) &&
+ *valueEnd != ';' && *valueEnd != ',';
+ valueEnd++) {
+ ;
+ }
+ if (!allowInvalidValue) {
+ for (const char* c = valueStart; c < valueEnd; c++) {
+ if (!IsTokenSymbol(*c)) {
+ valueEnd = c;
+ break;
+ }
+ }
+ }
+ } else {
+ bool foundQuotedEnd = false;
+ isQuotedString = true;
+
+ ++input;
+ valueStart = input;
+ for (valueEnd = input; *valueEnd; ++valueEnd) {
+ if (*valueEnd == '\\' && *(valueEnd + 1)) {
+ ++valueEnd;
+ } else if (*valueEnd == '"') {
+ foundQuotedEnd = true;
+ break;
+ }
+ }
+ if (!foundQuotedEnd) {
+ invalidValue = true;
+ }
+
+ input = valueEnd;
+ // *valueEnd != null means that *valueEnd is quote character.
+ if (*valueEnd) {
+ input++;
+ }
+ }
+
+ if (invalidValue) {
+ valueEnd = valueStart;
+ }
+
+ mValues.AppendElement(ParsedHeaderPair(nameStart, nameEnd - nameStart,
+ valueStart, valueEnd - valueStart,
+ isQuotedString));
+}
+
+ParsedHeaderValueListList::ParsedHeaderValueListList(
+ const nsCString& fullHeader, bool allowInvalidValue)
+ : mFull(fullHeader) {
+ auto& values = mValues;
+ auto consumer = [&values, allowInvalidValue](const char* output,
+ uint32_t outputLength) {
+ values.AppendElement(
+ ParsedHeaderValueList(output, outputLength, allowInvalidValue));
+ };
+
+ Tokenize(mFull.BeginReading(), mFull.Length(), ',', consumer);
+}
+
+Maybe<nsCString> CallingScriptLocationString() {
+ if (!LOG4_ENABLED() && !xpc::IsInAutomation()) {
+ return Nothing();
+ }
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ return Nothing();
+ }
+
+ nsAutoCString fileNameString;
+ uint32_t line = 0, col = 0;
+ if (!nsJSUtils::GetCallingLocation(cx, fileNameString, &line, &col)) {
+ return Nothing();
+ }
+
+ nsCString logString = ""_ns;
+ logString.AppendPrintf("%s:%u:%u", fileNameString.get(), line, col);
+ return Some(logString);
+}
+
+void LogCallingScriptLocation(void* instance) {
+ Maybe<nsCString> logLocation = CallingScriptLocationString();
+ LogCallingScriptLocation(instance, logLocation);
+}
+
+void LogCallingScriptLocation(void* instance,
+ const Maybe<nsCString>& aLogLocation) {
+ if (aLogLocation.isNothing()) {
+ return;
+ }
+
+ nsCString logString;
+ logString.AppendPrintf("%p called from script: ", instance);
+ logString.AppendPrintf("%s", aLogLocation->get());
+ LOG(("%s", logString.get()));
+}
+
+void LogHeaders(const char* lineStart) {
+ nsAutoCString buf;
+ const char* endOfLine;
+ while ((endOfLine = strstr(lineStart, "\r\n"))) {
+ buf.Assign(lineStart, endOfLine - lineStart);
+ if (StaticPrefs::network_http_sanitize_headers_in_logs() &&
+ (nsCRT::strcasestr(buf.get(), "authorization: ") ||
+ nsCRT::strcasestr(buf.get(), "proxy-authorization: "))) {
+ char* p = strchr(buf.BeginWriting(), ' ');
+ while (p && *++p) {
+ *p = '*';
+ }
+ }
+ LOG1((" %s\n", buf.get()));
+ lineStart = endOfLine + 2;
+ }
+}
+
+nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode) {
+ // In proxy CONNECT case, we treat every response code except 200 as an error.
+ // Even if the proxy server returns other 2xx codes (i.e. 206), this function
+ // still returns an error code.
+ MOZ_ASSERT(aStatusCode != 200);
+
+ nsresult rv;
+ switch (aStatusCode) {
+ case 300:
+ case 301:
+ case 302:
+ case 303:
+ case 307:
+ case 308:
+ // Bad redirect: not top-level, or it's a POST, bad/missing Location,
+ // or ProcessRedirect() failed for some other reason. Legal
+ // redirects that fail because site not available, etc., are handled
+ // elsewhere, in the regular codepath.
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
+ case 404: // HTTP/1.1: "Not Found"
+ // RFC 2616: "some deployed proxies are known to return 400 or
+ // 500 when DNS lookups time out." (Squid uses 500 if it runs
+ // out of sockets: so we have a conflict here).
+ case 400: // HTTP/1.1 "Bad Request"
+ case 500: // HTTP/1.1: "Internal Server Error"
+ rv = NS_ERROR_UNKNOWN_HOST;
+ break;
+ case 401:
+ rv = NS_ERROR_PROXY_UNAUTHORIZED;
+ break;
+ case 402:
+ rv = NS_ERROR_PROXY_PAYMENT_REQUIRED;
+ break;
+ case 403:
+ rv = NS_ERROR_PROXY_FORBIDDEN;
+ break;
+ case 405:
+ rv = NS_ERROR_PROXY_METHOD_NOT_ALLOWED;
+ break;
+ case 406:
+ rv = NS_ERROR_PROXY_NOT_ACCEPTABLE;
+ break;
+ case 407: // ProcessAuthentication() failed (e.g. no header)
+ rv = NS_ERROR_PROXY_AUTHENTICATION_FAILED;
+ break;
+ case 408:
+ rv = NS_ERROR_PROXY_REQUEST_TIMEOUT;
+ break;
+ case 409:
+ rv = NS_ERROR_PROXY_CONFLICT;
+ break;
+ case 410:
+ rv = NS_ERROR_PROXY_GONE;
+ break;
+ case 411:
+ rv = NS_ERROR_PROXY_LENGTH_REQUIRED;
+ break;
+ case 412:
+ rv = NS_ERROR_PROXY_PRECONDITION_FAILED;
+ break;
+ case 413:
+ rv = NS_ERROR_PROXY_REQUEST_ENTITY_TOO_LARGE;
+ break;
+ case 414:
+ rv = NS_ERROR_PROXY_REQUEST_URI_TOO_LONG;
+ break;
+ case 415:
+ rv = NS_ERROR_PROXY_UNSUPPORTED_MEDIA_TYPE;
+ break;
+ case 416:
+ rv = NS_ERROR_PROXY_REQUESTED_RANGE_NOT_SATISFIABLE;
+ break;
+ case 417:
+ rv = NS_ERROR_PROXY_EXPECTATION_FAILED;
+ break;
+ case 421:
+ rv = NS_ERROR_PROXY_MISDIRECTED_REQUEST;
+ break;
+ case 425:
+ rv = NS_ERROR_PROXY_TOO_EARLY;
+ break;
+ case 426:
+ rv = NS_ERROR_PROXY_UPGRADE_REQUIRED;
+ break;
+ case 428:
+ rv = NS_ERROR_PROXY_PRECONDITION_REQUIRED;
+ break;
+ case 429:
+ rv = NS_ERROR_PROXY_TOO_MANY_REQUESTS;
+ break;
+ case 431:
+ rv = NS_ERROR_PROXY_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ break;
+ case 451:
+ rv = NS_ERROR_PROXY_UNAVAILABLE_FOR_LEGAL_REASONS;
+ break;
+ case 501:
+ rv = NS_ERROR_PROXY_NOT_IMPLEMENTED;
+ break;
+ case 502:
+ rv = NS_ERROR_PROXY_BAD_GATEWAY;
+ break;
+ case 503:
+ // Squid returns 503 if target request fails for anything but DNS.
+ /* User sees: "Failed to Connect:
+ * Firefox can't establish a connection to the server at
+ * www.foo.com. Though the site seems valid, the browser
+ * was unable to establish a connection."
+ */
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
+ // do here: picking target timeout, as DNS covered by 400/404/500
+ case 504:
+ rv = NS_ERROR_PROXY_GATEWAY_TIMEOUT;
+ break;
+ case 505:
+ rv = NS_ERROR_PROXY_VERSION_NOT_SUPPORTED;
+ break;
+ case 506:
+ rv = NS_ERROR_PROXY_VARIANT_ALSO_NEGOTIATES;
+ break;
+ case 510:
+ rv = NS_ERROR_PROXY_NOT_EXTENDED;
+ break;
+ case 511:
+ rv = NS_ERROR_PROXY_NETWORK_AUTHENTICATION_REQUIRED;
+ break;
+ default:
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ break;
+ }
+
+ return rv;
+}
+
+SupportedAlpnRank H3VersionToRank(const nsACString& aVersion) {
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ if (aVersion.Equals(kHttp3Versions[i])) {
+ return static_cast<SupportedAlpnRank>(
+ static_cast<uint32_t>(SupportedAlpnRank::HTTP_3_DRAFT_29) + i);
+ }
+ }
+
+ return SupportedAlpnRank::NOT_SUPPORTED;
+}
+
+SupportedAlpnRank IsAlpnSupported(const nsACString& aAlpn) {
+ if (nsHttpHandler::IsHttp3Enabled() &&
+ gHttpHandler->IsHttp3VersionSupported(aAlpn)) {
+ return H3VersionToRank(aAlpn);
+ }
+
+ if (StaticPrefs::network_http_http2_enabled()) {
+ SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
+ if (aAlpn.Equals(spdyInfo->VersionString)) {
+ return SupportedAlpnRank::HTTP_2;
+ }
+ }
+
+ if (aAlpn.LowerCaseEqualsASCII("http/1.1")) {
+ return SupportedAlpnRank::HTTP_1_1;
+ }
+
+ return SupportedAlpnRank::NOT_SUPPORTED;
+}
+
+// On some security error when 0RTT is used we want to restart transactions
+// without 0RTT. Some firewalls do not behave well with 0RTT and cause this
+// errors.
+bool SecurityErrorThatMayNeedRestart(nsresult aReason) {
+ return (aReason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) ||
+ (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_MAC_ALERT));
+}
+
+nsresult MakeOriginURL(const nsACString& origin, nsCOMPtr<nsIURI>& url) {
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(origin, scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return MakeOriginURL(scheme, origin, url);
+}
+
+nsresult MakeOriginURL(const nsACString& scheme, const nsACString& origin,
+ nsCOMPtr<nsIURI>& url) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY,
+ scheme.EqualsLiteral("http") ? NS_HTTP_DEFAULT_PORT
+ : NS_HTTPS_DEFAULT_PORT,
+ origin, nullptr, nullptr, nullptr)
+ .Finalize(url);
+}
+
+void CreatePushHashKey(const nsCString& scheme, const nsCString& hostHeader,
+ const mozilla::OriginAttributes& originAttributes,
+ uint64_t serial, const nsACString& pathInfo,
+ nsCString& outOrigin, nsCString& outKey) {
+ nsCString fullOrigin = scheme;
+ fullOrigin.AppendLiteral("://");
+ fullOrigin.Append(hostHeader);
+
+ nsCOMPtr<nsIURI> origin;
+ nsresult rv = MakeOriginURL(scheme, fullOrigin, origin);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = origin->GetAsciiSpec(outOrigin);
+ outOrigin.Trim("/", false, true, false);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Fallback to plain text copy - this may end up behaving poorly
+ outOrigin = fullOrigin;
+ }
+
+ outKey = outOrigin;
+ outKey.AppendLiteral("/[");
+ nsAutoCString suffix;
+ originAttributes.CreateSuffix(suffix);
+ outKey.Append(suffix);
+ outKey.Append(']');
+ outKey.AppendLiteral("/[http2.");
+ outKey.AppendInt(serial);
+ outKey.Append(']');
+ outKey.Append(pathInfo);
+}
+
+nsresult GetNSResultFromWebTransportError(uint8_t aErrorCode) {
+ return static_cast<nsresult>((uint32_t)NS_ERROR_WEBTRANSPORT_CODE_BASE +
+ (uint32_t)aErrorCode);
+}
+
+uint8_t GetWebTransportErrorFromNSResult(nsresult aResult) {
+ if (aResult < NS_ERROR_WEBTRANSPORT_CODE_BASE ||
+ aResult > NS_ERROR_WEBTRANSPORT_CODE_END) {
+ return 0;
+ }
+
+ return static_cast<uint8_t>((uint32_t)aResult -
+ (uint32_t)NS_ERROR_WEBTRANSPORT_CODE_BASE);
+}
+
+uint64_t WebTransportErrorToHttp3Error(uint8_t aErrorCode) {
+ return kWebTransportErrorCodeStart + aErrorCode + aErrorCode / 0x1e;
+}
+
+uint8_t Http3ErrorToWebTransportError(uint64_t aErrorCode) {
+ // Ensure the code is within the valid range.
+ if (aErrorCode < kWebTransportErrorCodeStart ||
+ aErrorCode > kWebTransportErrorCodeEnd) {
+ return 0;
+ }
+
+ uint64_t shifted = aErrorCode - kWebTransportErrorCodeStart;
+ uint64_t result = shifted - shifted / 0x1f;
+
+ if (result <= std::numeric_limits<uint8_t>::max()) {
+ return (uint8_t)result;
+ }
+
+ return 0;
+}
+
+ConnectionCloseReason ToCloseReason(nsresult aErrorCode) {
+ if (NS_SUCCEEDED(aErrorCode)) {
+ return ConnectionCloseReason::OK;
+ }
+
+ if (aErrorCode == NS_ERROR_UNKNOWN_HOST) {
+ return ConnectionCloseReason::DNS_ERROR;
+ }
+ if (aErrorCode == NS_ERROR_NET_RESET) {
+ return ConnectionCloseReason::NET_RESET;
+ }
+ if (aErrorCode == NS_ERROR_CONNECTION_REFUSED) {
+ return ConnectionCloseReason::NET_REFUSED;
+ }
+ if (aErrorCode == NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED) {
+ return ConnectionCloseReason::SOCKET_ADDRESS_NOT_SUPPORTED;
+ }
+ if (aErrorCode == NS_ERROR_NET_TIMEOUT) {
+ return ConnectionCloseReason::NET_TIMEOUT;
+ }
+ if (aErrorCode == NS_ERROR_OUT_OF_MEMORY) {
+ return ConnectionCloseReason::OUT_OF_MEMORY;
+ }
+ if (aErrorCode == NS_ERROR_SOCKET_ADDRESS_IN_USE) {
+ return ConnectionCloseReason::SOCKET_ADDRESS_IN_USE;
+ }
+ if (aErrorCode == NS_BINDING_ABORTED) {
+ return ConnectionCloseReason::BINDING_ABORTED;
+ }
+ if (aErrorCode == NS_BINDING_REDIRECTED) {
+ return ConnectionCloseReason::BINDING_REDIRECTED;
+ }
+ if (aErrorCode == NS_ERROR_ABORT) {
+ return ConnectionCloseReason::ERROR_ABORT;
+ }
+
+ int32_t code = -1 * NS_ERROR_GET_CODE(aErrorCode);
+ if (mozilla::psm::IsNSSErrorCode(code)) {
+ return ConnectionCloseReason::SECURITY_ERROR;
+ }
+
+ return ConnectionCloseReason::OTHER_NET_ERROR;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h
new file mode 100644
index 0000000000..d0bd4cfe67
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.h
@@ -0,0 +1,527 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttp_h__
+#define nsHttp_h__
+
+#include <stdint.h>
+#include "prtime.h"
+#include "nsString.h"
+#include "nsError.h"
+#include "nsTArray.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/UniquePtr.h"
+#include "NSSErrorsService.h"
+
+class nsICacheEntry;
+
+namespace mozilla {
+
+namespace net {
+class nsHttpResponseHead;
+class nsHttpRequestHead;
+class CacheControlParser;
+
+enum class HttpVersion {
+ UNKNOWN = 0,
+ v0_9 = 9,
+ v1_0 = 10,
+ v1_1 = 11,
+ v2_0 = 20,
+ v3_0 = 30
+};
+
+enum class SpdyVersion { NONE = 0, HTTP_2 = 5 };
+
+enum class SupportedAlpnRank : uint8_t {
+ NOT_SUPPORTED = 0,
+ HTTP_1_1 = 1,
+ HTTP_2 = 2,
+ // Note that the order here MUST be the same as the order in kHttp3Versions.
+ HTTP_3_DRAFT_29 = 3,
+ HTTP_3_DRAFT_30 = 4,
+ HTTP_3_DRAFT_31 = 5,
+ HTTP_3_DRAFT_32 = 6,
+ HTTP_3_VER_1 = 7,
+};
+
+// IMPORTANT: when adding new values, always add them to the end, otherwise
+// it will mess up telemetry.
+enum class ConnectionCloseReason : uint32_t {
+ UNSET = 0,
+ OK,
+ IDLE_TIMEOUT,
+ TLS_TIMEOUT,
+ GO_AWAY,
+ DNS_ERROR,
+ NET_RESET,
+ NET_TIMEOUT,
+ NET_REFUSED,
+ NET_INTERRUPT,
+ NET_INADEQ_SEQURITY,
+ SOCKET_ADDRESS_NOT_SUPPORTED,
+ OUT_OF_MEMORY,
+ SOCKET_ADDRESS_IN_USE,
+ BINDING_ABORTED,
+ BINDING_REDIRECTED,
+ ERROR_ABORT,
+ CLOSE_EXISTING_CONN_FOR_COALESCING,
+ CLOSE_NEW_CONN_FOR_COALESCING,
+ CANT_REUSED,
+ OTHER_NET_ERROR,
+ SECURITY_ERROR,
+};
+
+ConnectionCloseReason ToCloseReason(nsresult aErrorCode);
+
+inline bool IsHttp3(SupportedAlpnRank aRank) {
+ return aRank >= SupportedAlpnRank::HTTP_3_DRAFT_29;
+}
+
+extern const uint32_t kHttp3VersionCount;
+extern const nsCString kHttp3Versions[];
+
+//-----------------------------------------------------------------------------
+// http connection capabilities
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_ALLOW_KEEPALIVE (1 << 0)
+#define NS_HTTP_LARGE_KEEPALIVE (1 << 1)
+
+// a transaction with this caps flag will continue to own the connection,
+// preventing it from being reclaimed, even after the transaction completes.
+#define NS_HTTP_STICKY_CONNECTION (1 << 2)
+
+// a transaction with this caps flag will, upon opening a new connection,
+// bypass the local DNS cache
+#define NS_HTTP_REFRESH_DNS (1 << 3)
+
+// a transaction with this caps flag will not pass SSL client-certificates
+// to the server (see bug #466080), but is may also be used for other things
+#define NS_HTTP_LOAD_ANONYMOUS (1 << 4)
+
+// a transaction with this caps flag keeps timing information
+#define NS_HTTP_TIMING_ENABLED (1 << 5)
+
+// a transaction with this flag blocks the initiation of other transactons
+// in the same load group until it is complete
+#define NS_HTTP_LOAD_AS_BLOCKING (1 << 6)
+
+// Disallow the use of the SPDY protocol. This is meant for the contexts
+// such as HTTP upgrade which are nonsensical for SPDY, it is not the
+// SPDY configuration variable.
+#define NS_HTTP_DISALLOW_SPDY (1 << 7)
+
+// a transaction with this flag loads without respect to whether the load
+// group is currently blocking on some resources
+#define NS_HTTP_LOAD_UNBLOCKED (1 << 8)
+
+// This flag indicates the transaction should accept associated pushes
+#define NS_HTTP_ONPUSH_LISTENER (1 << 9)
+
+// Transactions with this flag should react to errors without side effects
+// First user is to prevent clearing of alt-svc cache on failed probe
+#define NS_HTTP_ERROR_SOFTLY (1 << 10)
+
+// This corresponds to nsIHttpChannelInternal.beConservative
+// it disables any cutting edge features that we are worried might result in
+// interop problems with critical infrastructure
+#define NS_HTTP_BE_CONSERVATIVE (1 << 11)
+
+// Transactions with this flag should be processed first.
+#define NS_HTTP_URGENT_START (1 << 12)
+
+// A sticky connection of the transaction is explicitly allowed to be restarted
+// on ERROR_NET_RESET.
+#define NS_HTTP_CONNECTION_RESTARTABLE (1 << 13)
+
+// Allow re-using a spdy/http2 connection with NS_HTTP_ALLOW_KEEPALIVE not set.
+// This is primarily used to allow connection sharing for websockets over http/2
+// without accidentally allowing it for websockets not over http/2
+#define NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE (1 << 15)
+
+// Only permit CONNECTing to a proxy. A channel with this flag will not send an
+// http request after CONNECT or setup tls. An http upgrade handler MUST be
+// set. An ALPN header is set using the upgrade protocol.
+#define NS_HTTP_CONNECT_ONLY (1 << 16)
+
+// The connection should not use IPv4.
+#define NS_HTTP_DISABLE_IPV4 (1 << 17)
+
+// The connection should not use IPv6
+#define NS_HTTP_DISABLE_IPV6 (1 << 18)
+
+// Encodes the TRR mode.
+#define NS_HTTP_TRR_MODE_MASK ((1 << 19) | (1 << 20))
+
+// Disallow the use of the HTTP3 protocol. This is meant for the contexts
+// such as HTTP upgrade which are not supported by HTTP3.
+#define NS_HTTP_DISALLOW_HTTP3 (1 << 21)
+
+// Force a transaction to stay in pending queue until the HTTPS RR is
+// available.
+#define NS_HTTP_FORCE_WAIT_HTTP_RR (1 << 22)
+
+// This is used for a temporary workaround for a web-compat issue. The flag is
+// only set on CORS preflight request to allowed sending client certificates
+// on a connection for an anonymous request.
+#define NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT (1 << 23)
+
+#define NS_HTTP_DISALLOW_HTTPS_RR (1 << 24)
+
+#define NS_HTTP_DISALLOW_ECH (1 << 25)
+
+// Used to indicate that an HTTP Connection should obey Resist Fingerprinting
+// and set the User-Agent accordingly.
+#define NS_HTTP_USE_RFP (1 << 26)
+
+// If set, then the initial TLS handshake failed.
+#define NS_HTTP_IS_RETRY (1 << 27)
+
+// When set, disallow to connect to a HTTP/2 proxy.
+#define NS_HTTP_DISALLOW_HTTP2_PROXY (1 << 28)
+
+#define NS_HTTP_TRR_FLAGS_FROM_MODE(x) ((static_cast<uint32_t>(x) & 3) << 19)
+
+#define NS_HTTP_TRR_MODE_FROM_FLAGS(x) \
+ (static_cast<nsIRequest::TRRMode>((((x) & NS_HTTP_TRR_MODE_MASK) >> 19) & 3))
+
+//-----------------------------------------------------------------------------
+// some default values
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_DEFAULT_PORT 80
+#define NS_HTTPS_DEFAULT_PORT 443
+
+#define NS_HTTP_HEADER_SEP ','
+
+//-----------------------------------------------------------------------------
+// http atoms...
+//-----------------------------------------------------------------------------
+
+struct nsHttpAtom;
+struct nsHttpAtomLiteral;
+
+namespace nsHttp {
+[[nodiscard]] nsresult CreateAtomTable();
+void DestroyAtomTable();
+
+// will dynamically add atoms to the table if they don't already exist
+nsHttpAtom ResolveAtom(const nsACString& s);
+
+// returns true if the specified token [start,end) is valid per RFC 2616
+// section 2.2
+bool IsValidToken(const char* start, const char* end);
+
+inline bool IsValidToken(const nsACString& s) {
+ return IsValidToken(s.BeginReading(), s.EndReading());
+}
+
+// Strip the leading or trailing HTTP whitespace per fetch spec section 2.2.
+void TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest);
+
+// Returns true if the specified value is reasonable given the defintion
+// in RFC 2616 section 4.2. Full strict validation is not performed
+// currently as it would require full parsing of the value.
+bool IsReasonableHeaderValue(const nsACString& s);
+
+// find the first instance (case-insensitive comparison) of the given
+// |token| in the |input| string. the |token| is bounded by elements of
+// |separators| and may appear at the beginning or end of the |input|
+// string. null is returned if the |token| is not found. |input| may be
+// null, in which case null is returned.
+const char* FindToken(const char* input, const char* token, const char* seps);
+
+// This function parses a string containing a decimal-valued, non-negative
+// 64-bit integer. If the value would exceed INT64_MAX, then false is
+// returned. Otherwise, this function returns true and stores the
+// parsed value in |result|. The next unparsed character in |input| is
+// optionally returned via |next| if |next| is non-null.
+//
+// TODO(darin): Replace this with something generic.
+//
+[[nodiscard]] bool ParseInt64(const char* input, const char** next,
+ int64_t* result);
+
+// Variant on ParseInt64 that expects the input string to contain nothing
+// more than the value being parsed.
+[[nodiscard]] inline bool ParseInt64(const char* input, int64_t* result) {
+ const char* next;
+ return ParseInt64(input, &next, result) && *next == '\0';
+}
+
+// Return whether the HTTP status code represents a permanent redirect
+bool IsPermanentRedirect(uint32_t httpStatus);
+
+// Returns the APLN token which represents the used protocol version.
+const char* GetProtocolVersion(HttpVersion pv);
+
+bool ValidationRequired(bool isForcedValid,
+ nsHttpResponseHead* cachedResponseHead,
+ uint32_t loadFlags, bool allowStaleCacheContent,
+ bool forceValidateCacheContent, bool isImmutable,
+ bool customConditionalRequest,
+ nsHttpRequestHead& requestHead, nsICacheEntry* entry,
+ CacheControlParser& cacheControlRequest,
+ bool fromPreviousSession,
+ bool* performBackgroundRevalidation = nullptr);
+
+nsresult GetHttpResponseHeadFromCacheEntry(
+ nsICacheEntry* entry, nsHttpResponseHead* cachedResponseHead);
+
+nsresult CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength,
+ nsHttpResponseHead* responseHead);
+
+void DetermineFramingAndImmutability(nsICacheEntry* entry,
+ nsHttpResponseHead* cachedResponseHead,
+ bool isHttps, bool* weaklyFramed,
+ bool* isImmutable);
+
+// Called when an optimization feature affecting active vs background tab load
+// took place. Called only on the parent process and only updates
+// mLastActiveTabLoadOptimizationHit timestamp to now.
+void NotifyActiveTabLoadOptimization();
+TimeStamp GetLastActiveTabLoadOptimizationHit();
+void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when);
+bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when);
+
+nsCString ConvertRequestHeadToString(nsHttpRequestHead& aRequestHead,
+ bool aHasRequestBody,
+ bool aRequestBodyHasHeaders,
+ bool aUsingConnect);
+
+template <typename T>
+using SendFunc = std::function<bool(const T&, uint64_t, uint32_t)>;
+
+template <typename T>
+bool SendDataInChunks(const nsCString& aData, uint64_t aOffset, uint32_t aCount,
+ const SendFunc<T>& aSendFunc) {
+ static uint32_t const kCopyChunkSize = 128 * 1024;
+ uint32_t toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+
+ uint32_t start = 0;
+ while (aCount) {
+ T data(Substring(aData, start, toRead));
+
+ if (!aSendFunc(data, aOffset, toRead)) {
+ return false;
+ }
+
+ aOffset += toRead;
+ start += toRead;
+ aCount -= toRead;
+ toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+ }
+
+ return true;
+}
+
+} // namespace nsHttp
+
+struct nsHttpAtomLiteral;
+struct nsHttpAtom {
+ nsHttpAtom() = default;
+ nsHttpAtom(const nsHttpAtom& other) = default;
+
+ explicit operator bool() const { return !_val.IsEmpty(); }
+
+ const char* get() const {
+ if (_val.IsEmpty()) {
+ return nullptr;
+ }
+ return _val.BeginReading();
+ }
+
+ const nsCString& val() const { return _val; }
+
+ void operator=(const nsHttpAtom& a) { _val = a._val; }
+
+ // This constructor is mainly used to build the static atom list
+ // Avoid using it for anything else.
+ explicit nsHttpAtom(const nsACString& val) : _val(val) {}
+
+ private:
+ nsCString _val;
+ friend nsHttpAtom nsHttp::ResolveAtom(const nsACString& s);
+};
+
+struct nsHttpAtomLiteral {
+ const char* get() const { return _data.get(); }
+ nsLiteralCString const& val() const { return _data; }
+
+ template <size_t N>
+ constexpr explicit nsHttpAtomLiteral(const char (&val)[N]) : _data(val) {}
+
+ operator nsHttpAtom() const { return nsHttpAtom(_data); }
+
+ private:
+ nsLiteralCString _data;
+};
+
+inline bool operator==(nsHttpAtomLiteral const& self,
+ nsHttpAtomLiteral const& other) {
+ return self.get() == other.get();
+}
+inline bool operator!=(nsHttpAtomLiteral const& self,
+ nsHttpAtomLiteral const& other) {
+ return self.get() != other.get();
+}
+
+inline bool operator==(nsHttpAtom const& self, nsHttpAtomLiteral const& other) {
+ return self.val() == other.val();
+}
+inline bool operator!=(nsHttpAtom const& self, nsHttpAtomLiteral const& other) {
+ return self.val() != other.val();
+}
+
+inline bool operator==(nsHttpAtomLiteral const& self, nsHttpAtom const& other) {
+ return self.val() == other.val();
+}
+inline bool operator!=(nsHttpAtomLiteral const& self, nsHttpAtom const& other) {
+ return self.val() != other.val();
+}
+
+inline bool operator==(nsHttpAtom const& self, nsHttpAtom const& other) {
+ return self.val() == other.val();
+}
+inline bool operator!=(nsHttpAtom const& self, nsHttpAtom const& other) {
+ return self.val() != other.val();
+}
+
+namespace nsHttp {
+
+// Declare all atoms
+//
+// The atom names and values are stored in nsHttpAtomList.h and are brought
+// to you by the magic of C preprocessing. Add new atoms to nsHttpAtomList
+// and all support logic will be auto-generated.
+//
+#define HTTP_ATOM(_name, _value) \
+ inline constexpr nsHttpAtomLiteral _name(_value);
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+} // namespace nsHttp
+
+//-----------------------------------------------------------------------------
+// utilities...
+//-----------------------------------------------------------------------------
+
+static inline uint32_t PRTimeToSeconds(PRTime t_usec) {
+ return uint32_t(t_usec / PR_USEC_PER_SEC);
+}
+
+#define NowInSeconds() PRTimeToSeconds(PR_Now())
+
+// Round q-value to 2 decimal places; return 2 most significant digits as uint.
+#define QVAL_TO_UINT(q) ((unsigned int)(((q) + 0.005) * 100.0))
+
+#define HTTP_LWS " \t"
+#define HTTP_HEADER_VALUE_SEPS HTTP_LWS ","
+
+void EnsureBuffer(UniquePtr<char[]>& buf, uint32_t newSize, uint32_t preserve,
+ uint32_t& objSize);
+void EnsureBuffer(UniquePtr<uint8_t[]>& buf, uint32_t newSize,
+ uint32_t preserve, uint32_t& objSize);
+
+// h2=":443"; ma=60; single
+// results in 3 mValues = {{h2, :443}, {ma, 60}, {single}}
+
+class ParsedHeaderPair {
+ public:
+ ParsedHeaderPair(const char* name, int32_t nameLen, const char* val,
+ int32_t valLen, bool isQuotedValue);
+
+ ParsedHeaderPair(ParsedHeaderPair const& copy)
+ : mName(copy.mName),
+ mValue(copy.mValue),
+ mUnquotedValue(copy.mUnquotedValue),
+ mIsQuotedValue(copy.mIsQuotedValue) {
+ if (mIsQuotedValue) {
+ mValue.Rebind(mUnquotedValue.BeginReading(), mUnquotedValue.Length());
+ }
+ }
+
+ nsDependentCSubstring mName;
+ nsDependentCSubstring mValue;
+
+ private:
+ nsCString mUnquotedValue;
+ bool mIsQuotedValue;
+
+ void RemoveQuotedStringEscapes(const char* val, int32_t valLen);
+};
+
+class ParsedHeaderValueList {
+ public:
+ ParsedHeaderValueList(const char* t, uint32_t len, bool allowInvalidValue);
+ nsTArray<ParsedHeaderPair> mValues;
+
+ private:
+ void ParseNameAndValue(const char* input, bool allowInvalidValue);
+};
+
+class ParsedHeaderValueListList {
+ public:
+ // RFC 7231 section 3.2.6 defines the syntax of the header field values.
+ // |allowInvalidValue| indicates whether the rule will be used to check
+ // the input text.
+ // Note that ParsedHeaderValueListList is currently used to parse
+ // Alt-Svc and Server-Timing header. |allowInvalidValue| is set to true
+ // when parsing Alt-Svc for historical reasons.
+ explicit ParsedHeaderValueListList(const nsCString& fullHeader,
+ bool allowInvalidValue = true);
+ nsTArray<ParsedHeaderValueList> mValues;
+
+ private:
+ nsCString mFull;
+};
+
+void LogHeaders(const char* lineStart);
+
+// Convert HTTP response codes returned by a proxy to nsresult.
+// This function should be only used when we get a failed response to the
+// CONNECT method.
+nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode);
+
+// Convert an alpn string to SupportedAlpnType.
+SupportedAlpnRank IsAlpnSupported(const nsACString& aAlpn);
+
+static inline bool AllowedErrorForHTTPSRRFallback(nsresult aError) {
+ return psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aError)) ||
+ aError == NS_ERROR_NET_RESET ||
+ aError == NS_ERROR_CONNECTION_REFUSED ||
+ aError == NS_ERROR_UNKNOWN_HOST || aError == NS_ERROR_NET_TIMEOUT;
+}
+
+bool SecurityErrorThatMayNeedRestart(nsresult aReason);
+
+[[nodiscard]] nsresult MakeOriginURL(const nsACString& origin,
+ nsCOMPtr<nsIURI>& url);
+
+[[nodiscard]] nsresult MakeOriginURL(const nsACString& scheme,
+ const nsACString& origin,
+ nsCOMPtr<nsIURI>& url);
+
+void CreatePushHashKey(const nsCString& scheme, const nsCString& hostHeader,
+ const mozilla::OriginAttributes& originAttributes,
+ uint64_t serial, const nsACString& pathInfo,
+ nsCString& outOrigin, nsCString& outKey);
+
+nsresult GetNSResultFromWebTransportError(uint8_t aErrorCode);
+
+uint8_t GetWebTransportErrorFromNSResult(nsresult aResult);
+
+uint64_t WebTransportErrorToHttp3Error(uint8_t aErrorCode);
+
+uint8_t Http3ErrorToWebTransportError(uint64_t aErrorCode);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttp_h__
diff --git a/netwerk/protocol/http/nsHttpActivityDistributor.cpp b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
new file mode 100644
index 0000000000..b432bb5ea8
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
@@ -0,0 +1,298 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttpActivityDistributor.h"
+#include "nsHttpHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIOService.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+#include "NullHttpChannel.h"
+
+namespace mozilla {
+namespace net {
+
+using ObserverHolder = nsMainThreadPtrHolder<nsIHttpActivityObserver>;
+using ObserverHandle = nsMainThreadPtrHandle<nsIHttpActivityObserver>;
+
+NS_IMPL_ISUPPORTS(nsHttpActivityDistributor, nsIHttpActivityDistributor,
+ nsIHttpActivityObserver)
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveActivity(nsISupports* aHttpChannel,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData) {
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+
+ for (size_t i = 0; i < mObservers.Length(); i++) {
+ Unused << mObservers[i]->ObserveActivity(aHttpChannel, aActivityType,
+ aActivitySubtype, aTimestamp,
+ aExtraSizeData, aExtraStringData);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveConnectionActivity(
+ const nsACString& aHost, int32_t aPort, bool aSSL, bool aHasECH,
+ bool aIsHttp3, uint32_t aActivityType, uint32_t aActivitySubtype,
+ PRTime aTimestamp, const nsACString& aExtraStringData) {
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+
+ for (size_t i = 0; i < mObservers.Length(); i++) {
+ Unused << mObservers[i]->ObserveConnectionActivity(
+ aHost, aPort, aSSL, aHasECH, aIsHttp3, aActivityType, aActivitySubtype,
+ aTimestamp, aExtraStringData);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveActivityWithArgs(
+ const HttpActivityArgs& aArgs, uint32_t aActivityType,
+ uint32_t aActivitySubtype, PRTime aTimestamp, uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData) {
+ HttpActivityArgs args(aArgs);
+ nsCString extraStringData(aExtraStringData);
+ if (XRE_IsSocketProcess()) {
+ auto task = [args{std::move(args)}, aActivityType, aActivitySubtype,
+ aTimestamp, aExtraSizeData,
+ extraStringData{std::move(extraStringData)}]() {
+ SocketProcessChild::GetSingleton()->SendObserveHttpActivity(
+ args, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData,
+ extraStringData);
+ };
+
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::nsHttpActivityDistributor::ObserveActivityWithArgs", task));
+ }
+
+ task();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<nsHttpActivityDistributor> self = this;
+ auto task = [args{std::move(args)}, aActivityType, aActivitySubtype,
+ aTimestamp, aExtraSizeData,
+ extraStringData{std::move(extraStringData)},
+ self{std::move(self)}]() {
+ if (args.type() == HttpActivityArgs::Tuint64_t) {
+ nsWeakPtr weakPtr = gHttpHandler->GetWeakHttpChannel(args.get_uint64_t());
+ if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtr)) {
+ Unused << self->ObserveActivity(channel, aActivityType,
+ aActivitySubtype, aTimestamp,
+ aExtraSizeData, extraStringData);
+ }
+ } else if (args.type() == HttpActivityArgs::THttpActivity) {
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString portStr(""_ns);
+ int32_t port = args.get_HttpActivity().port();
+ bool endToEndSSL = args.get_HttpActivity().endToEndSSL();
+ if (port != -1 &&
+ ((endToEndSSL && port != 443) || (!endToEndSSL && port != 80))) {
+ portStr.AppendInt(port);
+ }
+
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ (endToEndSSL ? "https://"_ns : "http://"_ns) +
+ args.get_HttpActivity().host() + portStr);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<NullHttpChannel> channel = new NullHttpChannel();
+ rv = channel->Init(uri, 0, nullptr, 0, nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ Unused << self->ObserveActivity(
+ static_cast<nsIChannel*>(channel), aActivityType, aActivitySubtype,
+ aTimestamp, aExtraSizeData, extraStringData);
+ } else if (args.type() == HttpActivityArgs::THttpConnectionActivity) {
+ const HttpConnectionActivity& activity =
+ args.get_HttpConnectionActivity();
+ Unused << self->ObserveConnectionActivity(
+ activity.host(), activity.port(), activity.ssl(), activity.hasECH(),
+ activity.isHttp3(), aActivityType, aActivitySubtype, aTimestamp,
+ activity.connInfoKey());
+ }
+ };
+
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::nsHttpActivityDistributor::ObserveActivityWithArgs", task));
+ }
+
+ task();
+ return NS_OK;
+}
+
+bool nsHttpActivityDistributor::Activated() { return mActivated; }
+
+bool nsHttpActivityDistributor::ObserveProxyResponseEnabled() {
+ return mObserveProxyResponse;
+}
+
+bool nsHttpActivityDistributor::ObserveConnectionEnabled() {
+ return mObserveConnection;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetIsActive(bool* isActive) {
+ NS_ENSURE_ARG_POINTER(isActive);
+ if (XRE_IsSocketProcess()) {
+ *isActive = mActivated;
+ return NS_OK;
+ }
+
+ MutexAutoLock lock(mLock);
+ *isActive = mActivated = !!mObservers.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpActivityDistributor::SetIsActive(bool aActived) {
+ MOZ_RELEASE_ASSERT(XRE_IsSocketProcess());
+
+ mActivated = aActived;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::AddObserver(nsIHttpActivityObserver* aObserver) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ ObserverHandle observer(
+ new ObserverHolder("nsIHttpActivityObserver", aObserver));
+
+ bool wasEmpty = false;
+ {
+ MutexAutoLock lock(mLock);
+ wasEmpty = mObservers.IsEmpty();
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mObservers.AppendElement(observer);
+ }
+
+ if (wasEmpty) {
+ mActivated = true;
+ if (nsIOService::UseSocketProcess()) {
+ auto task = []() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorActivated(true);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::RemoveObserver(nsIHttpActivityObserver* aObserver) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ ObserverHandle observer(
+ new ObserverHolder("nsIHttpActivityObserver", aObserver));
+
+ {
+ MutexAutoLock lock(mLock);
+ if (!mObservers.RemoveElement(observer)) {
+ return NS_ERROR_FAILURE;
+ }
+ mActivated = mObservers.IsEmpty();
+ }
+
+ if (nsIOService::UseSocketProcess() && !mActivated) {
+ auto task = []() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorActivated(false);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetObserveProxyResponse(
+ bool* aObserveProxyResponse) {
+ NS_ENSURE_ARG_POINTER(aObserveProxyResponse);
+ bool result = mObserveProxyResponse;
+ *aObserveProxyResponse = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::SetObserveProxyResponse(bool aObserveProxyResponse) {
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserveProxyResponse = aObserveProxyResponse;
+ if (nsIOService::UseSocketProcess()) {
+ auto task = [aObserveProxyResponse]() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorObserveProxyResponse(
+ aObserveProxyResponse);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetObserveConnection(bool* aObserveConnection) {
+ NS_ENSURE_ARG_POINTER(aObserveConnection);
+
+ *aObserveConnection = mObserveConnection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::SetObserveConnection(bool aObserveConnection) {
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserveConnection = aObserveConnection;
+ if (nsIOService::UseSocketProcess()) {
+ auto task = [aObserveConnection]() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorObserveConnection(
+ aObserveConnection);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpActivityDistributor.h b/netwerk/protocol/http/nsHttpActivityDistributor.h
new file mode 100644
index 0000000000..b2d9ee20a4
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.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 nsHttpActivityDistributor_h__
+#define nsHttpActivityDistributor_h__
+
+#include "nsIHttpActivityObserver.h"
+#include "nsTArray.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpActivityDistributor : public nsIHttpActivityDistributor {
+ public:
+ using ObserverArray =
+ nsTArray<nsMainThreadPtrHandle<nsIHttpActivityObserver>>;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPACTIVITYOBSERVER
+ NS_DECL_NSIHTTPACTIVITYDISTRIBUTOR
+
+ nsHttpActivityDistributor() = default;
+
+ protected:
+ virtual ~nsHttpActivityDistributor() = default;
+
+ ObserverArray mObservers;
+ Mutex mLock MOZ_UNANNOTATED{"nsHttpActivityDistributor.mLock"};
+ Atomic<bool, Relaxed> mActivated{false};
+ Atomic<bool, Relaxed> mObserveProxyResponse{false};
+ Atomic<bool, Relaxed> mObserveConnection{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpActivityDistributor_h__
diff --git a/netwerk/protocol/http/nsHttpAtomList.h b/netwerk/protocol/http/nsHttpAtomList.h
new file mode 100644
index 0000000000..02e51e5ab1
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******
+ This file contains the list of all HTTP atoms
+ See nsHttp.h for access to the atoms.
+
+ It is designed to be used as inline input to nsHttp.cpp *only*
+ through the magic of C preprocessing.
+
+ All entries must be enclosed in the macro HTTP_ATOM which will have cruel
+ and unusual things done to it.
+
+ The first argument to HTTP_ATOM is the C++ name of the atom.
+ The second argument to HTTP_ATOM is the string value of the atom.
+ ******/
+
+HTTP_ATOM(Accept, "Accept")
+HTTP_ATOM(Accept_Encoding, "Accept-Encoding")
+HTTP_ATOM(Accept_Language, "Accept-Language")
+HTTP_ATOM(Accept_Ranges, "Accept-Ranges")
+HTTP_ATOM(Access_Control_Allow_Origin, "Access-Control-Allow-Origin")
+HTTP_ATOM(Age, "Age")
+HTTP_ATOM(Allow, "Allow")
+HTTP_ATOM(Alternate_Service, "Alt-Svc")
+HTTP_ATOM(Alternate_Service_Used, "Alt-Used")
+HTTP_ATOM(Assoc_Req, "Assoc-Req")
+HTTP_ATOM(Authentication, "Authentication")
+HTTP_ATOM(Authorization, "Authorization")
+HTTP_ATOM(Cache_Control, "Cache-Control")
+HTTP_ATOM(Connection, "Connection")
+HTTP_ATOM(Content_Disposition, "Content-Disposition")
+HTTP_ATOM(Content_Encoding, "Content-Encoding")
+HTTP_ATOM(Content_Language, "Content-Language")
+HTTP_ATOM(Content_Length, "Content-Length")
+HTTP_ATOM(Content_Location, "Content-Location")
+HTTP_ATOM(Content_MD5, "Content-MD5")
+HTTP_ATOM(Content_Range, "Content-Range")
+HTTP_ATOM(Content_Security_Policy, "Content-Security-Policy")
+HTTP_ATOM(Content_Type, "Content-Type")
+HTTP_ATOM(Cookie, "Cookie")
+HTTP_ATOM(Cross_Origin_Embedder_Policy, "Cross-Origin-Embedder-Policy")
+HTTP_ATOM(Cross_Origin_Opener_Policy, "Cross-Origin-Opener-Policy")
+HTTP_ATOM(Cross_Origin_Resource_Policy, "Cross-Origin-Resource-Policy")
+HTTP_ATOM(Date, "Date")
+HTTP_ATOM(DAV, "DAV")
+HTTP_ATOM(Depth, "Depth")
+HTTP_ATOM(Destination, "Destination")
+HTTP_ATOM(DoNotTrack, "DNT")
+HTTP_ATOM(ETag, "Etag")
+HTTP_ATOM(Expect, "Expect")
+HTTP_ATOM(Expires, "Expires")
+HTTP_ATOM(From, "From")
+HTTP_ATOM(GlobalPrivacyControl, "Sec-GPC")
+HTTP_ATOM(Host, "Host")
+HTTP_ATOM(If, "If")
+HTTP_ATOM(If_Match, "If-Match")
+HTTP_ATOM(If_Modified_Since, "If-Modified-Since")
+HTTP_ATOM(If_None_Match, "If-None-Match")
+HTTP_ATOM(If_None_Match_Any, "If-None-Match-Any")
+HTTP_ATOM(If_Range, "If-Range")
+HTTP_ATOM(If_Unmodified_Since, "If-Unmodified-Since")
+HTTP_ATOM(Keep_Alive, "Keep-Alive")
+HTTP_ATOM(Last_Modified, "Last-Modified")
+HTTP_ATOM(Lock_Token, "Lock-Token")
+HTTP_ATOM(Link, "Link")
+HTTP_ATOM(Location, "Location")
+HTTP_ATOM(Max_Forwards, "Max-Forwards")
+HTTP_ATOM(Origin, "Origin")
+HTTP_ATOM(OriginTrial, "Origin-Trial")
+HTTP_ATOM(Overwrite, "Overwrite")
+HTTP_ATOM(Pragma, "Pragma")
+HTTP_ATOM(Prefer, "Prefer")
+HTTP_ATOM(Proxy_Authenticate, "Proxy-Authenticate")
+HTTP_ATOM(Proxy_Authorization, "Proxy-Authorization")
+HTTP_ATOM(Proxy_Connection, "Proxy-Connection")
+HTTP_ATOM(Range, "Range")
+HTTP_ATOM(Referer, "Referer")
+HTTP_ATOM(Referrer_Policy, "Referrer-Policy")
+HTTP_ATOM(Retry_After, "Retry-After")
+HTTP_ATOM(Sec_WebSocket_Extensions, "Sec-WebSocket-Extensions")
+HTTP_ATOM(Sec_WebSocket_Protocol, "Sec-WebSocket-Protocol")
+HTTP_ATOM(Sec_WebSocket_Version, "Sec-WebSocket-Version")
+HTTP_ATOM(Server, "Server")
+HTTP_ATOM(Server_Timing, "Server-Timing")
+HTTP_ATOM(Service_Worker_Allowed, "Service-Worker-Allowed")
+HTTP_ATOM(Set_Cookie, "Set-Cookie")
+HTTP_ATOM(Status_URI, "Status-URI")
+HTTP_ATOM(Strict_Transport_Security, "Strict-Transport-Security")
+HTTP_ATOM(TE, "TE")
+HTTP_ATOM(Title, "Title")
+HTTP_ATOM(Timeout, "Timeout")
+HTTP_ATOM(Trailer, "Trailer")
+HTTP_ATOM(Transfer_Encoding, "Transfer-Encoding")
+HTTP_ATOM(URI, "URI")
+HTTP_ATOM(Upgrade, "Upgrade")
+HTTP_ATOM(User_Agent, "User-Agent")
+HTTP_ATOM(Vary, "Vary")
+HTTP_ATOM(Version, "Version")
+HTTP_ATOM(WWW_Authenticate, "WWW-Authenticate")
+HTTP_ATOM(Warning, "Warning")
+HTTP_ATOM(X_Content_Type_Options, "X-Content-Type-Options")
+HTTP_ATOM(X_Firefox_Spdy, "X-Firefox-Spdy")
+HTTP_ATOM(X_Firefox_Spdy_Proxy, "X-Firefox-Spdy-Proxy")
+HTTP_ATOM(X_Firefox_Early_Data, "X-Firefox-Early-Data")
+HTTP_ATOM(X_Firefox_Http3, "X-Firefox-Http3")
+HTTP_ATOM(X_Frame_Options, "X-Frame-Options")
+
+// methods are case sensitive and do not use atom table
diff --git a/netwerk/protocol/http/nsHttpAuthCache.cpp b/netwerk/protocol/http/nsHttpAuthCache.cpp
new file mode 100644
index 0000000000..6b2eb81d9e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.cpp
@@ -0,0 +1,349 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpAuthCache.h"
+
+#include <algorithm>
+#include <stdlib.h>
+
+#include "mozilla/Attributes.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/DebugOnly.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+static inline void GetAuthKey(const nsACString& scheme, const nsACString& host,
+ int32_t port, nsACString const& originSuffix,
+ nsCString& key) {
+ key.Truncate();
+ key.Append(originSuffix);
+ key.Append(':');
+ key.Append(scheme);
+ key.AppendLiteral("://");
+ key.Append(host);
+ key.Append(':');
+ key.AppendInt(port);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache <public>
+//-----------------------------------------------------------------------------
+
+nsHttpAuthCache::nsHttpAuthCache()
+ : mDB(128), mObserver(new OriginClearObserver(this)) {
+ LOG(("nsHttpAuthCache::nsHttpAuthCache %p", this));
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(mObserver, "clear-origin-attributes-data", false);
+ }
+}
+
+nsHttpAuthCache::~nsHttpAuthCache() {
+ LOG(("nsHttpAuthCache::~nsHttpAuthCache %p", this));
+
+ ClearAll();
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(mObserver, "clear-origin-attributes-data");
+ mObserver->mOwner = nullptr;
+ }
+}
+
+nsresult nsHttpAuthCache::GetAuthEntryForPath(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& path,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry) {
+ LOG(("nsHttpAuthCache::GetAuthEntryForPath %p [path=%s]\n", this,
+ path.BeginReading()));
+
+ nsAutoCString key;
+ nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node) return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByPath(path);
+ LOG((" returning %p", *entry));
+ return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpAuthCache::GetAuthEntryForDomain(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& realm,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry)
+
+{
+ LOG(("nsHttpAuthCache::GetAuthEntryForDomain %p [realm=%s]\n", this,
+ realm.BeginReading()));
+
+ nsAutoCString key;
+ nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node) return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByRealm(realm);
+ LOG((" returning %p", *entry));
+ return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpAuthCache::SetAuthEntry(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ const nsACString& path, const nsACString& realm, const nsACString& creds,
+ const nsACString& challenge, nsACString const& originSuffix,
+ const nsHttpAuthIdentity* ident, nsISupports* metadata) {
+ nsresult rv;
+
+ LOG(("nsHttpAuthCache::SetAuthEntry %p [realm=%s path=%s metadata=%p]\n",
+ this, realm.BeginReading(), path.BeginReading(), metadata));
+
+ nsAutoCString key;
+ nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key);
+
+ if (!node) {
+ // create a new entry node and set the given entry
+ auto node = UniquePtr<nsHttpAuthNode>(new nsHttpAuthNode);
+ LOG((" new nsHttpAuthNode %p for key='%s'", node.get(), key.get()));
+ rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDB.InsertOrUpdate(key, std::move(node));
+ return NS_OK;
+ }
+
+ return node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+}
+
+void nsHttpAuthCache::ClearAuthEntry(const nsACString& scheme,
+ const nsACString& host, int32_t port,
+ const nsACString& realm,
+ nsACString const& originSuffix) {
+ nsAutoCString key;
+ GetAuthKey(scheme, host, port, originSuffix, key);
+ LOG(("nsHttpAuthCache::ClearAuthEntry %p key='%s'\n", this, key.get()));
+ mDB.Remove(key);
+}
+
+void nsHttpAuthCache::ClearAll() {
+ LOG(("nsHttpAuthCache::ClearAll %p\n", this));
+ mDB.Clear();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache <private>
+//-----------------------------------------------------------------------------
+
+nsHttpAuthNode* nsHttpAuthCache::LookupAuthNode(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ nsACString const& originSuffix,
+ nsCString& key) {
+ GetAuthKey(scheme, host, port, originSuffix, key);
+ nsHttpAuthNode* result = mDB.Get(key);
+
+ LOG(("nsHttpAuthCache::LookupAuthNode %p key='%s' found node=%p", this,
+ key.get(), result));
+ return result;
+}
+
+NS_IMPL_ISUPPORTS(nsHttpAuthCache::OriginClearObserver, nsIObserver)
+
+NS_IMETHODIMP
+nsHttpAuthCache::OriginClearObserver::Observe(nsISupports* subject,
+ const char* topic,
+ const char16_t* data_unicode) {
+ NS_ENSURE_TRUE(mOwner, NS_ERROR_NOT_AVAILABLE);
+
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(nsDependentString(data_unicode))) {
+ NS_ERROR("Cannot parse origin attributes pattern");
+ return NS_ERROR_FAILURE;
+ }
+
+ mOwner->ClearOriginData(pattern);
+ return NS_OK;
+}
+
+void nsHttpAuthCache::ClearOriginData(OriginAttributesPattern const& pattern) {
+ LOG(("nsHttpAuthCache::ClearOriginData %p", this));
+
+ for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
+ const nsACString& key = iter.Key();
+
+ // Extract the origin attributes suffix from the key.
+ int32_t colon = key.FindChar(':');
+ MOZ_ASSERT(colon != kNotFound);
+ nsDependentCSubstring oaSuffix = StringHead(key, colon);
+
+ // Build the OriginAttributes object of it...
+ OriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(oaSuffix);
+ MOZ_ASSERT(rv);
+
+ // ...and match it against the given pattern.
+ if (pattern.Matches(oa)) {
+ iter.Remove();
+ }
+ }
+}
+
+void nsHttpAuthCache::CollectKeys(nsTArray<nsCString>& aValue) {
+ AppendToArray(aValue, mDB.Keys());
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+void nsHttpAuthIdentity::Clear() {
+ mUser.Truncate();
+ mPass.Truncate();
+ mDomain.Truncate();
+}
+
+bool nsHttpAuthIdentity::Equals(const nsHttpAuthIdentity& ident) const {
+ // we could probably optimize this with a single loop, but why bother?
+ return mUser == ident.mUser && mPass == ident.mPass &&
+ mDomain == ident.mDomain;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpAuthEntry::AddPath(const nsACString& aPath) {
+ for (const auto& p : mPaths) {
+ if (StringBeginsWith(aPath, p)) {
+ return NS_OK; // subpath already exists in the list
+ }
+ }
+
+ mPaths.AppendElement(aPath);
+ return NS_OK;
+}
+
+nsresult nsHttpAuthEntry::Set(const nsACString& path, const nsACString& realm,
+ const nsACString& creds, const nsACString& chall,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata) {
+ if (ident) {
+ mIdent = *ident;
+ } else if (mIdent.IsEmpty()) {
+ // If we are not given an identity and our cached identity has not been
+ // initialized yet (so is currently empty), initialize it now by
+ // filling it with nulls. We need to do that because consumers expect
+ // that mIdent is initialized after this function returns.
+ mIdent.Clear();
+ }
+
+ nsresult rv = AddPath(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mRealm = realm;
+ mCreds = creds;
+ mChallenge = chall;
+ mMetaData = metadata;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthNode
+//-----------------------------------------------------------------------------
+
+nsHttpAuthNode::nsHttpAuthNode() {
+ LOG(("Creating nsHttpAuthNode @%p\n", this));
+}
+
+nsHttpAuthNode::~nsHttpAuthNode() {
+ LOG(("Destroying nsHttpAuthNode @%p\n", this));
+
+ mList.Clear();
+}
+
+nsHttpAuthEntry* nsHttpAuthNode::LookupEntryByPath(const nsACString& aPath) {
+ // look for an entry that either matches or contains this directory.
+ // ie. we'll give out credentials if the given directory is a sub-
+ // directory of an existing entry.
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ const auto& entry = mList[i];
+
+ for (const auto& entryPath : entry->mPaths) {
+ // proxy auth entries have no path, so require exact match on
+ // empty path string.
+ if (entryPath.IsEmpty()) {
+ if (aPath.IsEmpty()) {
+ return entry.get();
+ }
+ } else if (StringBeginsWith(aPath, entryPath)) {
+ return entry.get();
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsHttpAuthNode::EntryList::const_iterator nsHttpAuthNode::LookupEntryItrByRealm(
+ const nsACString& realm) const {
+ return std::find_if(mList.cbegin(), mList.cend(), [&realm](const auto& val) {
+ return realm.Equals(val->Realm());
+ });
+}
+
+nsHttpAuthEntry* nsHttpAuthNode::LookupEntryByRealm(const nsACString& realm) {
+ auto itr = LookupEntryItrByRealm(realm);
+ if (itr != mList.cend()) {
+ return itr->get();
+ }
+
+ return nullptr;
+}
+
+nsresult nsHttpAuthNode::SetAuthEntry(const nsACString& path,
+ const nsACString& realm,
+ const nsACString& creds,
+ const nsACString& challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata) {
+ // look for an entry with a matching realm
+ nsHttpAuthEntry* entry = LookupEntryByRealm(realm);
+ if (!entry) {
+ // We want the latest identity be at the begining of the list so that
+ // the newest working credentials are sent first on new requests.
+ // Changing a realm is sometimes used to "timeout" authrozization.
+ mList.InsertElementAt(
+ 0, WrapUnique(new nsHttpAuthEntry(path, realm, creds, challenge, ident,
+ metadata)));
+ } else {
+ // update the entry...
+ nsresult rv = entry->Set(path, realm, creds, challenge, ident, metadata);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+void nsHttpAuthNode::ClearAuthEntry(const nsACString& realm) {
+ auto idx = LookupEntryItrByRealm(realm);
+ if (idx != mList.cend()) {
+ mList.RemoveElementAt(idx);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpAuthCache.h b/netwerk/protocol/http/nsHttpAuthCache.h
new file mode 100644
index 0000000000..66bcfb36e2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.h
@@ -0,0 +1,219 @@
+/* -*- 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 nsHttpAuthCache_h__
+#define nsHttpAuthCache_h__
+
+#include "nsError.h"
+#include "nsTArray.h"
+#include "nsClassHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsStringFwd.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+
+class OriginAttributesPattern;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthIdentity {
+ public:
+ nsHttpAuthIdentity() = default;
+ nsHttpAuthIdentity(const nsAString& domain, const nsAString& user,
+ const nsAString& password)
+ : mUser(user), mPass(password), mDomain(domain) {}
+ ~nsHttpAuthIdentity() { Clear(); }
+
+ const nsString& Domain() const { return mDomain; }
+ const nsString& User() const { return mUser; }
+ const nsString& Password() const { return mPass; }
+
+ void Clear();
+
+ bool Equals(const nsHttpAuthIdentity& ident) const;
+ bool IsEmpty() const {
+ return mUser.IsEmpty() && mPass.IsEmpty() && mDomain.IsEmpty();
+ }
+
+ private:
+ nsString mUser;
+ nsString mPass;
+ nsString mDomain;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthEntry {
+ public:
+ const nsCString& Realm() const { return mRealm; }
+ const nsCString& Creds() const { return mCreds; }
+ const nsCString& Challenge() const { return mChallenge; }
+ const nsString& Domain() const { return mIdent.Domain(); }
+ const nsString& User() const { return mIdent.User(); }
+ const nsString& Pass() const { return mIdent.Password(); }
+
+ const nsHttpAuthIdentity& Identity() const { return mIdent; }
+
+ [[nodiscard]] nsresult AddPath(const nsACString& aPath);
+
+ nsCOMPtr<nsISupports> mMetaData;
+
+ private:
+ nsHttpAuthEntry(const nsACString& path, const nsACString& realm,
+ const nsACString& creds, const nsACString& challenge,
+ const nsHttpAuthIdentity* ident, nsISupports* metadata) {
+ DebugOnly<nsresult> rv =
+ Set(path, realm, creds, challenge, ident, metadata);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ ~nsHttpAuthEntry() = default;
+
+ [[nodiscard]] nsresult Set(const nsACString& path, const nsACString& realm,
+ const nsACString& creds,
+ const nsACString& challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata);
+
+ nsHttpAuthIdentity mIdent;
+
+ nsTArray<nsCString> mPaths;
+
+ nsCString mRealm;
+ nsCString mCreds;
+ nsCString mChallenge;
+
+ friend class nsHttpAuthNode;
+ friend class nsHttpAuthCache;
+ friend class mozilla::DefaultDelete<nsHttpAuthEntry>; // needs to call the
+ // destructor
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthNode
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthNode {
+ private:
+ using EntryList = nsTArray<UniquePtr<nsHttpAuthEntry>>;
+
+ nsHttpAuthNode();
+ ~nsHttpAuthNode();
+
+ // path can be null, in which case we'll search for an entry
+ // with a null path.
+ nsHttpAuthEntry* LookupEntryByPath(const nsACString& path);
+
+ // realm must not be null
+ nsHttpAuthEntry* LookupEntryByRealm(const nsACString& realm);
+ EntryList::const_iterator LookupEntryItrByRealm(
+ const nsACString& realm) const;
+
+ // if a matching entry is found, then credentials will be changed.
+ [[nodiscard]] nsresult SetAuthEntry(const nsACString& path,
+ const nsACString& realm,
+ const nsACString& creds,
+ const nsACString& challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata);
+
+ void ClearAuthEntry(const nsACString& realm);
+
+ uint32_t EntryCount() { return mList.Length(); }
+
+ private:
+ EntryList mList;
+
+ friend class nsHttpAuthCache;
+ friend class mozilla::DefaultDelete<nsHttpAuthNode>; // needs to call the
+ // destructor
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache
+// (holds a hash table from host:port to nsHttpAuthNode)
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthCache {
+ public:
+ nsHttpAuthCache();
+ ~nsHttpAuthCache();
+
+ // |scheme|, |host|, and |port| are required
+ // |path| can be null
+ // |entry| is either null or a weak reference
+ [[nodiscard]] nsresult GetAuthEntryForPath(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& path,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry);
+
+ // |scheme|, |host|, and |port| are required
+ // |realm| must not be null
+ // |entry| is either null or a weak reference
+ [[nodiscard]] nsresult GetAuthEntryForDomain(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& realm,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry);
+
+ // |scheme|, |host|, and |port| are required
+ // |path| can be null
+ // |realm| must not be null
+ // if |credentials|, |user|, |pass|, and |challenge| are each
+ // null, then the entry is deleted.
+ [[nodiscard]] nsresult SetAuthEntry(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ const nsACString& path, const nsACString& realm, const nsACString& creds,
+ const nsACString& challenge, nsACString const& originSuffix,
+ const nsHttpAuthIdentity* ident, nsISupports* metadata);
+
+ void ClearAuthEntry(const nsACString& scheme, const nsACString& host,
+ int32_t port, const nsACString& realm,
+ nsACString const& originSuffix);
+
+ // expire all existing auth list entries including proxy auths.
+ void ClearAll();
+
+ // For testing only.
+ void CollectKeys(nsTArray<nsCString>& aValue);
+
+ private:
+ nsHttpAuthNode* LookupAuthNode(const nsACString& scheme,
+ const nsACString& host, int32_t port,
+ nsACString const& originSuffix,
+ nsCString& key);
+
+ class OriginClearObserver : public nsIObserver {
+ virtual ~OriginClearObserver() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ explicit OriginClearObserver(nsHttpAuthCache* aOwner) : mOwner(aOwner) {}
+ nsHttpAuthCache* mOwner;
+ };
+
+ void ClearOriginData(OriginAttributesPattern const& pattern);
+
+ private:
+ using AuthNodeTable = nsClassHashtable<nsCStringHashKey, nsHttpAuthNode>;
+ AuthNodeTable mDB; // "host:port" --> nsHttpAuthNode
+ RefPtr<OriginClearObserver> mObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpAuthCache_h__
diff --git a/netwerk/protocol/http/nsHttpAuthManager.cpp b/netwerk/protocol/http/nsHttpAuthManager.cpp
new file mode 100644
index 0000000000..14c4e46fce
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.cpp
@@ -0,0 +1,105 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHandler.h"
+#include "nsHttpAuthManager.h"
+#include "nsNetUtil.h"
+#include "nsIPrincipal.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsHttpAuthManager, nsIHttpAuthManager)
+
+nsresult nsHttpAuthManager::Init() {
+ // get reference to the auth cache. we assume that we will live
+ // as long as gHttpHandler. instantiate it if necessary.
+
+ if (!gHttpHandler) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // maybe someone is overriding our HTTP handler implementation?
+ NS_ENSURE_TRUE(gHttpHandler, NS_ERROR_UNEXPECTED);
+ }
+
+ mAuthCache = gHttpHandler->AuthCache(false);
+ mPrivateAuthCache = gHttpHandler->AuthCache(true);
+ NS_ENSURE_TRUE(mAuthCache, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mPrivateAuthCache, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::GetAuthIdentity(
+ const nsACString& aScheme, const nsACString& aHost, int32_t aPort,
+ const nsACString& aAuthType, const nsACString& aRealm,
+ const nsACString& aPath, nsAString& aUserDomain, nsAString& aUserName,
+ nsAString& aUserPassword, bool aIsPrivate, nsIPrincipal* aPrincipal) {
+ nsHttpAuthCache* auth_cache = aIsPrivate ? mPrivateAuthCache : mAuthCache;
+ nsHttpAuthEntry* entry = nullptr;
+ nsresult rv;
+
+ nsAutoCString originSuffix;
+ if (aPrincipal) {
+ aPrincipal->OriginAttributesRef().CreateSuffix(originSuffix);
+ }
+
+ if (!aPath.IsEmpty()) {
+ rv = auth_cache->GetAuthEntryForPath(aScheme, aHost, aPort, aPath,
+ originSuffix, &entry);
+ } else {
+ rv = auth_cache->GetAuthEntryForDomain(aScheme, aHost, aPort, aRealm,
+ originSuffix, &entry);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+ if (!entry) return NS_ERROR_UNEXPECTED;
+
+ aUserDomain.Assign(entry->Domain());
+ aUserName.Assign(entry->User());
+ aUserPassword.Assign(entry->Pass());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::SetAuthIdentity(
+ const nsACString& aScheme, const nsACString& aHost, int32_t aPort,
+ const nsACString& aAuthType, const nsACString& aRealm,
+ const nsACString& aPath, const nsAString& aUserDomain,
+ const nsAString& aUserName, const nsAString& aUserPassword, bool aIsPrivate,
+ nsIPrincipal* aPrincipal) {
+ nsHttpAuthIdentity ident(aUserDomain, aUserName, aUserPassword);
+
+ nsAutoCString originSuffix;
+ if (aPrincipal) {
+ aPrincipal->OriginAttributesRef().CreateSuffix(originSuffix);
+ }
+
+ nsHttpAuthCache* auth_cache = aIsPrivate ? mPrivateAuthCache : mAuthCache;
+ return auth_cache->SetAuthEntry(aScheme, aHost, aPort, aPath, aRealm,
+ ""_ns, // credentials
+ ""_ns, // challenge
+ originSuffix, &ident,
+ nullptr); // metadata
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::ClearAll() {
+ mAuthCache->ClearAll();
+ mPrivateAuthCache->ClearAll();
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpAuthManager.h b/netwerk/protocol/http/nsHttpAuthManager.h
new file mode 100644
index 0000000000..b49c4a44e6
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.h
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; 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 nsHttpAuthManager_h__
+#define nsHttpAuthManager_h__
+
+#include "nsIHttpAuthManager.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpAuthCache;
+
+class nsHttpAuthManager : public nsIHttpAuthManager {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHMANAGER
+
+ nsHttpAuthManager() = default;
+ [[nodiscard]] nsresult Init();
+
+ protected:
+ virtual ~nsHttpAuthManager() = default;
+
+ nsHttpAuthCache* mAuthCache{nullptr};
+ nsHttpAuthCache* mPrivateAuthCache{nullptr};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpAuthManager_h__
diff --git a/netwerk/protocol/http/nsHttpBasicAuth.cpp b/netwerk/protocol/http/nsHttpBasicAuth.cpp
new file mode 100644
index 0000000000..c3de5cb5e8
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpBasicAuth.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "mozilla/Base64.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<nsHttpBasicAuth> nsHttpBasicAuth::gSingleton;
+
+already_AddRefed<nsIHttpAuthenticator> nsHttpBasicAuth::GetOrCreate() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (gSingleton) {
+ authenticator = gSingleton;
+ } else {
+ gSingleton = new nsHttpBasicAuth();
+ ClearOnShutdown(&gSingleton);
+ authenticator = gSingleton;
+ }
+
+ return authenticator.forget();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpBasicAuth, nsIHttpAuthenticator)
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth::nsIHttpAuthenticator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpBasicAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel,
+ const nsACString& challenge,
+ bool isProxyAuth, nsISupports** sessionState,
+ nsISupports** continuationState,
+ bool* identityInvalid) {
+ // if challenged, then the username:password that was sent must
+ // have been wrong.
+ *identityInvalid = true;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* authChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& username,
+ const nsAString& password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& user,
+ const nsAString& password, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, nsACString& creds) {
+ LOG(("nsHttpBasicAuth::GenerateCredentials [challenge=%s]\n",
+ aChallenge.BeginReading()));
+
+ *aFlags = 0;
+
+ // we only know how to deal with Basic auth for http.
+ bool isBasicAuth = StringBeginsWith(aChallenge, "basic"_ns,
+ nsCaseInsensitiveCStringComparator);
+ NS_ENSURE_TRUE(isBasicAuth, NS_ERROR_UNEXPECTED);
+
+ // we work with UTF-8 around here
+ nsAutoCString userpass;
+ CopyUTF16toUTF8(user, userpass);
+ userpass.Append(':'); // always send a ':' (see bug 129565)
+ AppendUTF16toUTF8(password, userpass);
+
+ nsAutoCString authString{"Basic "_ns};
+ nsresult rv = Base64EncodeAppend(userpass, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ creds = authString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpBasicAuth::GetAuthFlags(uint32_t* flags) {
+ *flags = REQUEST_BASED | REUSABLE_CREDENTIALS | REUSABLE_CHALLENGE;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpBasicAuth.h b/netwerk/protocol/http/nsHttpBasicAuth.h
new file mode 100644
index 0000000000..cac5db2c49
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsBasicAuth_h__
+#define nsBasicAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// The nsHttpBasicAuth class produces HTTP Basic-auth responses for a username/
+// (optional)password pair, BASE64("user:pass").
+//-----------------------------------------------------------------------------
+
+class nsHttpBasicAuth : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpBasicAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> GetOrCreate();
+
+ private:
+ virtual ~nsHttpBasicAuth() = default;
+
+ static StaticRefPtr<nsHttpBasicAuth> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpBasicAuth_h__
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
new file mode 100644
index 0000000000..b3806f761b
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -0,0 +1,10430 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include <inttypes.h>
+
+#include "DocumentChannelParent.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/StoragePrincipalHelper.h"
+
+#include "nsContentSecurityUtils.h"
+#include "nsHttp.h"
+#include "nsHttpChannel.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsHttpHandler.h"
+#include "nsString.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntry.h"
+#include "nsICryptoHash.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsINetworkInterceptController.h"
+#include "nsINSSErrorsService.h"
+#include "nsIStringBundle.h"
+#include "nsIStreamListenerTee.h"
+#include "nsISeekableStream.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIURLQueryStringStripper.h"
+#include "nsIWebTransport.h"
+#include "nsCRT.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURL.h"
+#include "nsIStreamTransportService.h"
+#include "prnetdb.h"
+#include "nsEscape.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsIOService.h"
+#include "nsDNSPrefetch.h"
+#include "nsChannelClassifier.h"
+#include "nsIRedirectResultListener.h"
+#include "mozilla/TimeStamp.h"
+#include "nsError.h"
+#include "nsPrintfCString.h"
+#include "nsAlgorithm.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+#include "nsIConsoleService.h"
+#include "mozilla/AntiTrackingRedirectHeuristic.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "sslt.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIClassOfService.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIWebProgressListener.h"
+#include "LoadContextInfo.h"
+#include "netCore.h"
+#include "nsHttpTransaction.h"
+#include "nsICancelable.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPrompt.h"
+#include "nsInputStreamPump.h"
+#include "nsURLHelper.h"
+#include "nsISocketTransport.h"
+#include "nsIStreamConverterService.h"
+#include "nsISiteSecurityService.h"
+#include "nsString.h"
+#include "CacheObserver.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/Telemetry.h"
+#include "AlternateServices.h"
+#include "NetworkMarker.h"
+#include "nsIHttpPushListener.h"
+#include "nsIX509Cert.h"
+#include "ScopedNSSTypes.h"
+#include "nsIDNSRecord.h"
+#include "mozilla/dom/Document.h"
+#include "nsICompressConvStats.h"
+#include "nsCORSListenerProxy.h"
+#include "nsISocketProvider.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/Predictor.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/NullPrincipal.h"
+#include "CacheControlParser.h"
+#include "nsMixedContentBlocker.h"
+#include "CacheStorageService.h"
+#include "HttpChannelParent.h"
+#include "HttpTransactionParent.h"
+#include "ParentChannelListener.h"
+#include "ThirdPartyUtil.h"
+#include "InterceptedHttpChannel.h"
+#include "../../cache2/CacheFileUtils.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyStreamListener.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/net/AsyncUrlChannelClassifier.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/OpaqueResponseUtils.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "HttpTrafficAnalyzer.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "js/Conversions.h"
+#include "mozilla/dom/SecFetch.h"
+#include "mozilla/net/TRRService.h"
+#include "nsUnknownDecoder.h"
+#ifdef XP_WIN
+# include "HttpWinUtils.h"
+#endif
+#ifdef FUZZING
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+namespace mozilla {
+
+using namespace dom;
+
+namespace net {
+
+namespace {
+
+// True if the local cache should be bypassed when processing a request.
+#define BYPASS_LOCAL_CACHE(loadFlags, isPreferCacheLoadOverBypass) \
+ ((loadFlags) & (nsIRequest::LOAD_BYPASS_CACHE | \
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE) && \
+ !(((loadFlags) & nsIRequest::LOAD_FROM_CACHE) && \
+ (isPreferCacheLoadOverBypass)))
+
+#define RECOVER_FROM_CACHE_FILE_ERROR(result) \
+ ((result) == NS_ERROR_FILE_NOT_FOUND || \
+ (result) == NS_ERROR_FILE_CORRUPTED || (result) == NS_ERROR_OUT_OF_MEMORY)
+
+#define WRONG_RACING_RESPONSE_SOURCE(req) \
+ (mRaceCacheWithNetwork && \
+ (((mFirstResponseSource == RESPONSE_FROM_CACHE) && \
+ ((req) != mCachePump)) || \
+ ((mFirstResponseSource == RESPONSE_FROM_NETWORK) && \
+ ((req) != mTransactionPump))))
+
+static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
+
+void AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss,
+ nsIChannel* aChannel) {
+ nsCString key("UNKNOWN");
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsAutoCString contentType;
+ if (NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
+ if (nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(contentType))) {
+ key.AssignLiteral("JAVASCRIPT");
+ } else if (StringBeginsWith(contentType, "text/css"_ns) ||
+ (loadInfo && loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_STYLESHEET)) {
+ key.AssignLiteral("STYLESHEET");
+ } else if (StringBeginsWith(contentType, "application/wasm"_ns)) {
+ key.AssignLiteral("WASM");
+ } else if (StringBeginsWith(contentType, "image/"_ns)) {
+ key.AssignLiteral("IMAGE");
+ } else if (StringBeginsWith(contentType, "video/"_ns)) {
+ key.AssignLiteral("MEDIA");
+ } else if (StringBeginsWith(contentType, "audio/"_ns)) {
+ key.AssignLiteral("MEDIA");
+ } else if (!StringBeginsWith(contentType,
+ nsLiteralCString(UNKNOWN_CONTENT_TYPE))) {
+ key.AssignLiteral("OTHER");
+ }
+ }
+
+ Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3 label =
+ Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unresolved;
+ switch (hitOrMiss) {
+ case kCacheUnresolved:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unresolved;
+ break;
+ case kCacheHit:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Hit;
+ break;
+ case kCacheHitViaReval:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::HitViaReval;
+ break;
+ case kCacheMissedViaReval:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::MissedViaReval;
+ break;
+ case kCacheMissed:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Missed;
+ break;
+ case kCacheUnknown:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unknown;
+ break;
+ }
+
+ Telemetry::AccumulateCategoricalKeyed(key, label);
+ Telemetry::AccumulateCategoricalKeyed("ALL"_ns, label);
+}
+
+// Computes and returns a SHA1 hash of the input buffer. The input buffer
+// must be a null-terminated string.
+nsresult Hash(const char* buf, nsACString& hash) {
+ nsresult rv;
+
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update(reinterpret_cast<unsigned const char*>(buf), strlen(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(true, hash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // unnamed namespace
+
+// We only treat 3xx responses as redirects if they have a Location header and
+// the status code is in a whitelist.
+bool nsHttpChannel::WillRedirect(const nsHttpResponseHead& response) {
+ return IsRedirectStatus(response.Status()) &&
+ response.HasHeader(nsHttp::Location);
+}
+
+nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead);
+
+class MOZ_STACK_CLASS AutoRedirectVetoNotifier {
+ public:
+ explicit AutoRedirectVetoNotifier(nsHttpChannel* channel, nsresult& aRv)
+ : mChannel(channel), mRv(aRv) {
+ if (mChannel->LoadHasAutoRedirectVetoNotifier()) {
+ MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
+ mChannel = nullptr;
+ return;
+ }
+
+ mChannel->StoreHasAutoRedirectVetoNotifier(true);
+ }
+ ~AutoRedirectVetoNotifier() { ReportRedirectResult(mRv); }
+ void RedirectSucceeded() { ReportRedirectResult(NS_OK); }
+
+ private:
+ nsHttpChannel* mChannel;
+ bool mCalledReport = false;
+ nsresult& mRv;
+ void ReportRedirectResult(nsresult aRv);
+};
+
+void AutoRedirectVetoNotifier::ReportRedirectResult(nsresult aRv) {
+ if (!mChannel) return;
+
+ if (mCalledReport) {
+ return;
+ }
+ mCalledReport = true;
+
+ mChannel->mRedirectChannel = nullptr;
+
+ if (NS_SUCCEEDED(aRv)) {
+ mChannel->RemoveAsNonTailRequest();
+ }
+
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
+ getter_AddRefs(vetoHook));
+
+ nsHttpChannel* channel = mChannel;
+ mChannel = nullptr;
+
+ if (vetoHook) vetoHook->OnRedirectResult(aRv);
+
+ // Drop after the notification
+ channel->StoreHasAutoRedirectVetoNotifier(false);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <public>
+//-----------------------------------------------------------------------------
+
+nsHttpChannel::nsHttpChannel() : HttpAsyncAborter<nsHttpChannel>(this) {
+ LOG(("Creating nsHttpChannel [this=%p, nsIChannel=%p]\n", this,
+ static_cast<nsIChannel*>(this)));
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+}
+
+nsHttpChannel::~nsHttpChannel() {
+ LOG(("Destroying nsHttpChannel [this=%p, nsIChannel=%p]\n", this,
+ static_cast<nsIChannel*>(this)));
+
+ if (LOG_ENABLED()) {
+ nsCString webExtension;
+ this->GetPropertyAsACString(u"cancelledByExtension"_ns, webExtension);
+ if (!webExtension.IsEmpty()) {
+ LOG(("channel [%p] cancelled by extension [id=%s]", this,
+ webExtension.get()));
+ }
+ }
+
+ if (mAuthProvider) {
+ DebugOnly<nsresult> rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ ReleaseMainThreadOnlyReferences();
+ if (gHttpHandler) {
+ gHttpHandler->RemoveHttpChannel(mChannelId);
+ }
+}
+
+void nsHttpChannel::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
+ arrayToRelease.AppendElement(mAuthProvider.forget());
+ arrayToRelease.AppendElement(mRedirectChannel.forget());
+ arrayToRelease.AppendElement(mPreflightChannel.forget());
+ arrayToRelease.AppendElement(mDNSPrefetch.forget());
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mEarlyHintObserver,
+ "Early hint observer should have been released in ReleaseListeners()");
+ arrayToRelease.AppendElement(mEarlyHintObserver.forget());
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mChannelClassifier,
+ "Channel classifier should have been released in ReleaseListeners()");
+ arrayToRelease.AppendElement(
+ mChannelClassifier.forget().downcast<nsIURIClassifierCallback>());
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mWarningReporter,
+ "Warning reporter should have been released in ReleaseListeners()");
+ arrayToRelease.AppendElement(mWarningReporter.forget());
+
+ NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
+}
+
+nsresult nsHttpChannel::Init(nsIURI* uri, uint32_t caps, nsProxyInfo* proxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ uint64_t channelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo) {
+ nsresult rv =
+ HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI,
+ channelId, aContentPolicyType, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG1(("nsHttpChannel::Init [this=%p]\n", this));
+
+ return rv;
+}
+
+nsresult nsHttpChannel::AddSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) {
+ if (mWarningReporter) {
+ return mWarningReporter->ReportSecurityMessage(aMessageTag,
+ aMessageCategory);
+ }
+ return HttpBaseChannel::AddSecurityMessage(aMessageTag, aMessageCategory);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ if (mWarningReporter) {
+ return mWarningReporter->LogBlockedCORSRequest(aMessage, aCategory,
+ aIsWarning);
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ if (mWarningReporter) {
+ return mWarningReporter->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
+ aContentType);
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <private>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChannel::PrepareToConnect() {
+ LOG(("nsHttpChannel::PrepareToConnect [this=%p]\n", this));
+
+ // notify "http-on-modify-request-before-cookies" observers
+ gHttpHandler->OnModifyRequestBeforeCookies(this);
+
+ AddCookiesToRequest();
+
+#ifdef XP_WIN
+
+ auto prefEnabledForCurrentContainer = [&]() {
+ uint32_t containerId = mLoadInfo->GetOriginAttributes().mUserContextId;
+ // Make sure that the default container ID is 0
+ static_assert(nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID == 0);
+ nsPrintfCString prefName("network.http.windows-sso.container-enabled.%u",
+ containerId);
+
+ bool enabled = false;
+ Preferences::GetBool(prefName.get(), &enabled);
+
+ LOG(("Pref for %s is %d\n", prefName.get(), enabled));
+
+ return enabled;
+ };
+
+ // If Windows 10 SSO is enabled, we potentially add auth information to
+ // secure top level loads (DOCUMENTs) and iframes (SUBDOCUMENTs) that
+ // aren't anonymous or private browsing.
+ if (StaticPrefs::network_http_windows_sso_enabled() &&
+ mURI->SchemeIs("https") && !(mLoadFlags & LOAD_ANONYMOUS) &&
+ !mPrivateBrowsing) {
+ ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
+ if ((type == ExtContentPolicy::TYPE_DOCUMENT ||
+ type == ExtContentPolicy::TYPE_SUBDOCUMENT) &&
+ prefEnabledForCurrentContainer()) {
+ AddWindowsSSO(this);
+ }
+ }
+#endif
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ return CallOrWaitForResume(
+ [](auto* self) { return self->OnBeforeConnect(); });
+}
+
+void nsHttpChannel::HandleContinueCancellingByURLClassifier(
+ nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(
+ ("Waiting until resume HandleContinueCancellingByURLClassifier "
+ "[this=%p]\n",
+ this));
+ mCallOnResume = [aErrorCode](nsHttpChannel* self) {
+ self->HandleContinueCancellingByURLClassifier(aErrorCode);
+ return NS_OK;
+ };
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleContinueCancellingByURLClassifier [this=%p]\n",
+ this));
+ ContinueCancellingByURLClassifier(aErrorCode);
+}
+
+nsresult nsHttpChannel::OnBeforeConnect() {
+ nsresult rv = NS_OK;
+
+ // Check if request was cancelled during suspend AFTER on-modify-request
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // Check to see if we should redirect this channel elsewhere by
+ // nsIHttpChannel.redirectTo API request
+ if (mAPIRedirectToURI) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+ }
+
+ // Note that we are only setting the "Upgrade-Insecure-Requests" request
+ // header for *all* navigational requests instead of all requests as
+ // defined in the spec, see:
+ // https://www.w3.org/TR/upgrade-insecure-requests/#preference
+ ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
+
+ if (type == ExtContentPolicy::TYPE_DOCUMENT ||
+ type == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ rv = SetRequestHeader("Upgrade-Insecure-Requests"_ns, "1"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (LoadAuthRedirectedChannel()) {
+ // This channel is a result of a redirect due to auth retry
+ // We have already checked for HSTS upgarde in the redirecting channel.
+ // We can safely skip those checks
+ return ContinueOnBeforeConnect(false, rv);
+ }
+
+ SecFetch::AddSecFetchHeader(this);
+
+ // Check to see if we should redirect this channel to the unstripped URI. To
+ // revert the query stripping if the loading channel is in the content
+ // blocking allow list.
+ if (ContentBlockingAllowList::Check(this)) {
+ nsCOMPtr<nsIURI> unstrippedURI;
+ mLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ if (unstrippedURI) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirectToUnstrippedURI);
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ if (!mURI->SchemeIs("https")) {
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultPrincipal));
+ }
+
+ // Check if we already know about the HSTS status of the host
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+ bool isSecureURI;
+ OriginAttributes originAttributes;
+ if (!StoragePrincipalHelper::GetOriginAttributesForHSTS(this,
+ originAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = sss->IsSecureURI(mURI, originAttributes, &isSecureURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Save that on the loadInfo so it can later be consumed by SecurityInfo.jsm
+ mLoadInfo->SetHstsStatus(isSecureURI);
+
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
+ if (bc && bc->Top()->GetForceOffline()) {
+ return NS_ERROR_OFFLINE;
+ }
+
+ // At this point it is no longer possible to call
+ // HttpBaseChannel::UpgradeToSecure.
+ StoreUpgradableToSecure(false);
+ bool shouldUpgrade = LoadUpgradeToSecure();
+ if (mURI->SchemeIs("http")) {
+ OriginAttributes originAttributes;
+ if (!StoragePrincipalHelper::GetOriginAttributesForHSTS(this,
+ originAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!shouldUpgrade) {
+ // Make sure http channel is released on main thread.
+ // See bug 1539148 for details.
+ nsMainThreadPtrHandle<nsHttpChannel> self(
+ new nsMainThreadPtrHolder<nsHttpChannel>(
+ "nsHttpChannel::OnBeforeConnect::self", this));
+ auto resultCallback = [self(self)](bool aResult, nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = self->MaybeUseHTTPSRRForUpgrade(aResult, aStatus);
+ if (NS_FAILED(rv)) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(rv);
+ }
+ };
+
+ bool willCallback = false;
+ rv = NS_ShouldSecureUpgrade(
+ mURI, mLoadInfo, resultPrincipal, LoadAllowSTS(), originAttributes,
+ shouldUpgrade, std::move(resultCallback), willCallback);
+ // If the request gets upgraded because of the HTTPS-Only mode, but no
+ // event listener has been registered so far, we want to do that here.
+ uint32_t httpOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
+ if (httpOnlyStatus &
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED) {
+ RefPtr<nsHTTPSOnlyStreamListener> httpsOnlyListener =
+ new nsHTTPSOnlyStreamListener(mListener, mLoadInfo);
+ mListener = httpsOnlyListener;
+
+ httpOnlyStatus ^=
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
+ httpOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED;
+ mLoadInfo->SetHttpsOnlyStatus(httpOnlyStatus);
+ }
+ LOG(
+ ("nsHttpChannel::OnBeforeConnect "
+ "[this=%p willCallback=%d rv=%" PRIx32 "]\n",
+ this, willCallback, static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv) || MOZ_UNLIKELY(willCallback)) {
+ return rv;
+ }
+ }
+ }
+
+ return MaybeUseHTTPSRRForUpgrade(shouldUpgrade, NS_OK);
+}
+
+nsresult nsHttpChannel::MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade,
+ nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (mURI->SchemeIs("https") || aShouldUpgrade || !LoadUseHTTPSSVC()) {
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ auto shouldSkipUpgradeWithHTTPSRR = [&]() -> bool {
+ // Skip using HTTPS RR to upgrade when this is not a top-level load and the
+ // loading principal is http.
+ if ((mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) &&
+ (mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->SchemeIs("http"))) {
+ return true;
+ }
+
+ nsAutoCString uriHost;
+ mURI->GetAsciiHost(uriHost);
+
+ if (gHttpHandler->IsHostExcludedForHTTPSRR(uriHost)) {
+ return true;
+ }
+
+ if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
+ mURI, mLoadInfo,
+ {nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
+ EnforceForHTTPSRR})) {
+ // Add the host to a excluded list because:
+ // 1. We don't need to do the same check again.
+ // 2. Other subresources in the same host will be also excluded.
+ gHttpHandler->ExcludeHTTPSRRHost(uriHost);
+ LOG(("[%p] skip HTTPS upgrade for host [%s]", this, uriHost.get()));
+ return true;
+ }
+
+ return false;
+ };
+
+ if (shouldSkipUpgradeWithHTTPSRR()) {
+ StoreUseHTTPSSVC(false);
+ // If the website does not want to use HTTPS RR, we should set
+ // NS_HTTP_DISALLOW_HTTPS_RR. This is for avoiding HTTPS RR being used by
+ // the transaction.
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ if (mHTTPSSVCRecord.isSome()) {
+ LOG((
+ "nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] mHTTPSSVCRecord is some",
+ this));
+ StoreWaitHTTPSSVCRecord(false);
+ bool hasHTTPSRR = (mHTTPSSVCRecord.ref() != nullptr);
+ return ContinueOnBeforeConnect(hasHTTPSRR, aStatus, hasHTTPSRR);
+ }
+
+ auto dnsStrategy = GetProxyDNSStrategy();
+ if (!(dnsStrategy & DNS_PREFETCH_ORIGIN)) {
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ LOG(("nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] wait for HTTPS RR",
+ this));
+
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(this, originAttributes);
+
+ RefPtr<nsDNSPrefetch> resolver =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode());
+ nsWeakPtr weakPtrThis(
+ do_GetWeakReference(static_cast<nsIHttpChannel*>(this)));
+ nsresult rv = resolver->FetchHTTPSSVC(
+ mCaps & NS_HTTP_REFRESH_DNS, !LoadUseHTTPSSVC(),
+ [weakPtrThis](nsIDNSHTTPSSVCRecord* aRecord) {
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis);
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(channel);
+ if (httpChannelImpl) {
+ httpChannelImpl->OnHTTPSRRAvailable(aRecord);
+ }
+ });
+ if (NS_FAILED(rv)) {
+ LOG((" FetchHTTPSSVC failed with 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ StoreWaitHTTPSSVCRecord(true);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
+ nsresult aStatus,
+ bool aUpgradeWithHTTPSRR) {
+ LOG(
+ ("nsHttpChannel::ContinueOnBeforeConnect "
+ "[this=%p aShouldUpgrade=%d rv=%" PRIx32 "]\n",
+ this, aShouldUpgrade, static_cast<uint32_t>(aStatus)));
+
+ MOZ_ASSERT(!LoadWaitHTTPSSVCRecord());
+
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (aShouldUpgrade && !mURI->SchemeIs("https")) {
+ mozilla::glean::networking::https_upgrade_with_https_rr
+ .Get(aUpgradeWithHTTPSRR ? "https_rr"_ns : "others"_ns)
+ .Add(1);
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
+ }
+
+ // ensure that we are using a valid hostname
+ if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin()))) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (mUpgradeProtocolCallback) {
+ // Websockets can run over HTTP/2, but other upgrades can't.
+ if (mUpgradeProtocol.EqualsLiteral("websocket") &&
+ StaticPrefs::network_http_http2_websockets()) {
+ // Need to tell the conn manager that we're ok with http/2 even with
+ // the allow keepalive bit not set. That bit needs to stay off,
+ // though, in case we end up having to fallback to http/1.1 (where
+ // we absolutely do want to disable keepalive).
+ mCaps |= NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE;
+ } else {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ // Upgrades cannot use HTTP/3.
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ // Because NS_HTTP_STICKY_CONNECTION breaks HTTPS RR fallabck mecnahism, we
+ // can not use HTTPS RR for upgrade requests.
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+
+ if (LoadIsTRRServiceChannel()) {
+ mCaps |= NS_HTTP_LARGE_KEEPALIVE;
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+
+ if (mTransactionSticky) {
+ MOZ_ASSERT(LoadAuthRedirectedChannel());
+ // this means this is a redirected channel channel due to auth retry and a
+ // connection based auth scheme was used
+ // we have a reference to the old-transaction with sticky connection which
+ // we need to use
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ }
+
+ mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
+ mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
+ LoadBeConservative());
+ mConnectionInfo->SetTlsFlags(mTlsFlags);
+ mConnectionInfo->SetIsTrrServiceChannel(LoadIsTRRServiceChannel());
+ mConnectionInfo->SetTRRMode(nsIRequest::GetTRRMode());
+ mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
+ mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
+ mConnectionInfo->SetAnonymousAllowClientCert(
+ (mLoadFlags & LOAD_ANONYMOUS_ALLOW_CLIENT_CERT) != 0);
+
+ // notify "http-on-before-connect" observers
+ gHttpHandler->OnBeforeConnect(this);
+
+ return CallOrWaitForResume([](auto* self) { return self->Connect(); });
+}
+
+nsresult nsHttpChannel::Connect() {
+ LOG(("nsHttpChannel::Connect [this=%p]\n", this));
+
+ // Don't allow resuming when cache must be used
+ if (LoadResuming() && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ LOG(("Resuming from cache is not supported yet"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (ShouldIntercept()) {
+ return RedirectToInterceptedChannel();
+ }
+
+ // Step 8.18 of HTTP-network-or-cache fetch
+ // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
+ nsAutoCString rangeVal;
+ if (NS_SUCCEEDED(GetRequestHeader("Range"_ns, rangeVal))) {
+ SetRequestHeader("Accept-Encoding"_ns, "identity"_ns, true);
+ }
+
+ bool isTrackingResource = IsThirdPartyTrackingResource();
+ LOG(("nsHttpChannel %p tracking resource=%d, cos=%lu, inc=%d", this,
+ isTrackingResource, mClassOfService.Flags(),
+ mClassOfService.Incremental()));
+
+ if (isTrackingResource) {
+ AddClassFlags(nsIClassOfService::Tail);
+ }
+
+ if (WaitingForTailUnblock()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
+ mOnTailUnblock = &nsHttpChannel::ConnectOnTailUnblock;
+ return NS_OK;
+ }
+
+ return ConnectOnTailUnblock();
+}
+
+nsresult nsHttpChannel::ConnectOnTailUnblock() {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::ConnectOnTailUnblock [this=%p]\n", this));
+
+ // Consider opening a TCP connection right away.
+ SpeculativeConnect();
+
+ // open a cache entry for this channel...
+ rv = OpenCacheEntry(mURI->SchemeIs("https"));
+
+ // do not continue if asyncOpenCacheEntry is in progress
+ if (AwaitingCacheCallbacks()) {
+ LOG(("nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n",
+ this));
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
+
+ if (mNetworkTriggered && mWaitingForProxy) {
+ // Someone has called TriggerNetwork(), meaning we are racing the
+ // network with the cache.
+ mWaitingForProxy = false;
+ return ContinueConnect();
+ }
+
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("OpenCacheEntry failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ // if this channel is only allowed to pull from the cache, then
+ // we must fail if we were unable to open a cache entry.
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ // otherwise, let's just proceed without using the cache.
+ }
+
+ if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
+ (mDidReval || LoadCachedContentIsPartial())) ||
+ mIgnoreCacheEntry)) {
+ // We won't send the conditional request because the unconditional
+ // request was already sent (see bug 1377223).
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
+ }
+
+ // When racing, if OnCacheEntryAvailable is called before AsyncOpenURI
+ // returns, then we may not have started reading from the cache.
+ // If the content is valid, we should attempt to do so, as technically the
+ // cache has won the race.
+ if (mRaceCacheWithNetwork && mCachedContentIsValid) {
+ Unused << ReadFromCache(true);
+ }
+
+ return TriggerNetwork();
+}
+
+nsresult nsHttpChannel::ContinueConnect() {
+ // If we need to start a CORS preflight, do it now!
+ // Note that it is important to do this before the early returns below.
+ if (!LoadIsCorsPreflightDone() && LoadRequireCORSPreflight()) {
+ MOZ_ASSERT(!mPreflightChannel);
+ nsresult rv = nsCORSListenerProxy::StartCORSPreflight(
+ this, this, mUnsafeHeaders, getter_AddRefs(mPreflightChannel));
+ return rv;
+ }
+
+ MOZ_RELEASE_ASSERT(!LoadRequireCORSPreflight() || LoadIsCorsPreflightDone(),
+ "CORS preflight must have been finished by the time we "
+ "do the rest of ContinueConnect");
+
+ // we may or may not have a cache entry at this point
+ if (mCacheEntry) {
+ // read straight from the cache if possible...
+ if (mCachedContentIsValid) {
+ nsRunnableMethod<nsHttpChannel>* event = nullptr;
+ nsresult rv;
+ if (!LoadCachedContentIsPartial()) {
+ rv = AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
+ if (NS_FAILED(rv)) {
+ LOG((" AsyncCall failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ rv = ReadFromCache(true);
+ if (NS_FAILED(rv) && event) {
+ event->Revoke();
+ }
+
+ AccumulateCacheHitTelemetry(kCacheHit, this);
+ mCacheDisposition = kCacheHit;
+
+ return rv;
+ }
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // the cache contains the requested resource, but it must be
+ // validated before we can reuse it. since we are not allowed
+ // to hit the net, there's nothing more to do. the document
+ // is effectively not in the cache.
+ LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ } else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ LOG((" !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (mLoadFlags & LOAD_NO_NETWORK_IO) {
+ LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ // hit the net...
+ nsresult rv = DoConnect(mTransactionSticky);
+ mTransactionSticky = nullptr;
+ return rv;
+}
+
+nsresult nsHttpChannel::DoConnect(HttpTransactionShell* aTransWithStickyConn) {
+ LOG(("nsHttpChannel::DoConnect [this=%p]\n", this));
+
+ if (!mDNSBlockingPromise.IsEmpty()) {
+ LOG((" waiting for DNS prefetch"));
+
+ // Transaction is passed only from auth retry for which we will definitely
+ // not block on DNS to alter the origin server name for IP; it has already
+ // been done.
+ MOZ_ASSERT(!aTransWithStickyConn);
+ MOZ_ASSERT(mDNSBlockingThenable);
+
+ nsCOMPtr<nsISerialEventTarget> target(do_GetMainThread());
+ RefPtr<nsHttpChannel> self(this);
+ mDNSBlockingThenable->Then(
+ target, __func__,
+ [self](const nsCOMPtr<nsIDNSRecord>& aRec) {
+ nsresult rv = self->DoConnectActual(nullptr);
+ if (NS_FAILED(rv)) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(rv);
+ }
+ },
+ [self](nsresult err) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(err);
+ });
+
+ // The connection will continue when the promise is resolved in
+ // OnLookupComplete.
+ return NS_OK;
+ }
+
+ return DoConnectActual(aTransWithStickyConn);
+}
+
+nsresult nsHttpChannel::DoConnectActual(
+ HttpTransactionShell* aTransWithStickyConn) {
+ LOG(("nsHttpChannel::DoConnectActual [this=%p, aTransWithStickyConn=%p]\n",
+ this, aTransWithStickyConn));
+
+ nsresult rv = SetupTransaction();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aTransWithStickyConn) {
+ rv = gHttpHandler->InitiateTransactionWithStickyConn(
+ mTransaction, mPriority, aTransWithStickyConn);
+ } else {
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mTransaction->AsyncRead(this, getter_AddRefs(mTransactionPump));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t suspendCount = mSuspendCount;
+ if (LoadAsyncResumePending()) {
+ LOG(
+ (" Suspend()'ing transaction pump once because of async resume pending"
+ ", sc=%u, pump=%p, this=%p",
+ suspendCount, mTransactionPump.get(), this));
+ ++suspendCount;
+ }
+ while (suspendCount--) {
+ mTransactionPump->Suspend();
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::SpeculativeConnect() {
+ // Before we take the latency hit of dealing with the cache, try and
+ // get the TCP (and SSL) handshakes going so they can overlap.
+
+ // don't speculate if we are offline, when doing http upgrade (i.e.
+ // websockets bootstrap), or if we can't do keep-alive (because then we
+ // couldn't reuse the speculative connection anyhow).
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
+
+ if (gIOService->IsOffline() || mUpgradeProtocolCallback ||
+ !(mCaps & NS_HTTP_ALLOW_KEEPALIVE) ||
+ (bc && bc->Top()->GetForceOffline())) {
+ return;
+ }
+
+ // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
+ // LOAD_FROM_CACHE is unlikely to hit network, so skip preconnects for it.
+ if (mLoadFlags &
+ (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
+ return;
+ }
+
+ if (LoadAllowStaleCacheContent()) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (!callbacks) return;
+
+ Unused << gHttpHandler->SpeculativeConnect(
+ mConnectionInfo, callbacks,
+ mCaps & (NS_HTTP_DISALLOW_SPDY | NS_HTTP_TRR_MODE_MASK |
+ NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6 |
+ NS_HTTP_DISALLOW_HTTP3 | NS_HTTP_REFRESH_DNS),
+ gHttpHandler->EchConfigEnabled());
+}
+
+void nsHttpChannel::DoNotifyListenerCleanup() {
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+}
+
+void nsHttpChannel::ReleaseListeners() {
+ HttpBaseChannel::ReleaseListeners();
+ mChannelClassifier = nullptr;
+ mWarningReporter = nullptr;
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+
+ for (StreamFilterRequest& request : mStreamFilterRequests) {
+ request.mPromise->Reject(false, __func__);
+ }
+ mStreamFilterRequests.Clear();
+}
+
+void nsHttpChannel::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+void nsHttpChannel::HandleAsyncRedirect() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncRedirect();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
+
+ // since this event is handled asynchronously, it is possible that this
+ // channel could have been canceled, in which case there would be no point
+ // in processing the redirect.
+ if (NS_SUCCEEDED(mStatus)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
+ rv = AsyncProcessRedirection(mResponseHead->Status());
+ if (NS_FAILED(rv)) {
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
+ // TODO: if !DoNotRender3xxBody(), render redirect body instead.
+ // But first we need to cache 3xx bodies (bug 748510)
+ rv = ContinueHandleAsyncRedirect(rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ } else {
+ rv = ContinueHandleAsyncRedirect(mStatus);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+nsresult nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) {
+ if (NS_FAILED(rv)) {
+ // If AsyncProcessRedirection fails, then we have to send out the
+ // OnStart/OnStop notifications.
+ LOG(("ContinueHandleAsyncRedirect got failure result [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+
+ bool redirectsEnabled = !mLoadInfo->GetDontFollowRedirects();
+
+ if (redirectsEnabled) {
+ // TODO: stop failing original channel if redirect vetoed?
+ mStatus = rv;
+
+ DoNotifyListener();
+
+ // Blow away cache entry if we couldn't process the redirect
+ // for some reason (the cache entry might be corrupt).
+ if (mCacheEntry) {
+ mCacheEntry->AsyncDoom(nullptr);
+ }
+ } else {
+ DoNotifyListener();
+ }
+ }
+
+ CloseCacheEntry(true);
+
+ StoreIsPending(false);
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return NS_OK;
+}
+
+void nsHttpChannel::HandleAsyncNotModified() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async not-modified [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncNotModified();
+ return NS_OK;
+ };
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
+
+ DoNotifyListener();
+
+ CloseCacheEntry(false);
+
+ StoreIsPending(false);
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+nsresult nsHttpChannel::SetupTransaction() {
+ LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%lu, inc=%d prio=%d]\n",
+ this, mClassOfService.Flags(), mClassOfService.Incremental(),
+ mPriority));
+
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ mozilla::MutexAutoLock lock(mRCWNLock);
+
+ // If we're racing cache with network, conditional or byte range header
+ // could be added in OnCacheEntryCheck. We cannot send conditional request
+ // without having the entry, so we need to remove the headers here and
+ // ignore the cache entry in OnCacheEntryAvailable.
+ if (mRaceCacheWithNetwork && AwaitingCacheCallbacks()) {
+ if (mDidReval) {
+ LOG((" Removing conditional request headers"));
+ UntieValidationRequest();
+ mDidReval = false;
+ mIgnoreCacheEntry = true;
+ }
+
+ if (LoadCachedContentIsPartial()) {
+ LOG((" Removing byte range request headers"));
+ UntieByteRangeRequest();
+ StoreCachedContentIsPartial(false);
+ mIgnoreCacheEntry = true;
+ }
+
+ if (mIgnoreCacheEntry) {
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+ mAltDataLength = -1;
+ mCacheInputStream.CloseAndRelease();
+ }
+ }
+
+ StoreUsedNetwork(1);
+
+ if (!LoadAllowSpdy()) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (!LoadAllowHttp3()) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ if (LoadBeConservative()) {
+ mCaps |= NS_HTTP_BE_CONSERVATIVE;
+ }
+
+ if (mLoadFlags & LOAD_ANONYMOUS_ALLOW_CLIENT_CERT) {
+ mCaps |= NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
+ }
+
+ if (nsContentUtils::ShouldResistFingerprinting(this,
+ RFPTarget::HttpUserAgent)) {
+ mCaps |= NS_HTTP_USE_RFP;
+ }
+
+ // Use the URI path if not proxying (transparent proxying such as proxy
+ // CONNECT does not count here). Also figure out what HTTP version to use.
+ nsAutoCString buf, path;
+ nsCString* requestURI;
+
+ // This is the normal e2e H1 path syntax "/index.html"
+ rv = mURI->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // path may contain UTF-8 characters, so ensure that they're escaped.
+ if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII | esc_Spaces,
+ buf)) {
+ requestURI = &buf;
+ } else {
+ requestURI = &path;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref1 = requestURI->FindChar('#');
+ if (ref1 != kNotFound) {
+ requestURI->SetLength(ref1);
+ }
+
+ if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
+ mRequestHead.SetVersion(gHttpHandler->HttpVersion());
+ } else {
+ mRequestHead.SetPath(*requestURI);
+
+ // RequestURI should be the absolute uri H1 proxy syntax
+ // "http://foo/index.html" so we will overwrite the relative version in
+ // requestURI
+ rv = mURI->GetUserPass(buf);
+ if (NS_FAILED(rv)) return rv;
+ if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
+ strncmp(mSpec.get(), "https:", 6) == 0)) {
+ nsCOMPtr<nsIURI> tempURI = nsIOService::CreateExposableURI(mURI);
+ rv = tempURI->GetAsciiSpec(path);
+ if (NS_FAILED(rv)) return rv;
+ requestURI = &path;
+ } else {
+ requestURI = &mSpec;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref2 = requestURI->FindChar('#');
+ if (ref2 != kNotFound) {
+ requestURI->SetLength(ref2);
+ }
+
+ mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
+ }
+
+ mRequestHead.SetRequestURI(*requestURI);
+
+ // set the request time for cache expiration calculations
+ mRequestTime = NowInSeconds();
+ StoreRequestTimeInitialized(true);
+
+ // if doing a reload, force end-to-end
+ if (mLoadFlags & LOAD_BYPASS_CACHE) {
+ // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
+ // no proxy is configured since we might be talking with a transparent
+ // proxy, i.e. one that operates at the network level. See bug #14772.
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
+ // no-cache'
+ if (mRequestHead.Version() >= HttpVersion::v1_1) {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ } else if ((mLoadFlags & VALIDATE_ALWAYS) && !LoadCacheEntryIsWriteOnly()) {
+ // We need to send 'Cache-Control: max-age=0' to force each cache along
+ // the path to the origin server to revalidate its own entry, if any,
+ // with the next cache or server. See bug #84847.
+ //
+ // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
+ if (mRequestHead.Version() >= HttpVersion::v1_1) {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
+ } else {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (LoadResuming()) {
+ char byteRange[32];
+ SprintfLiteral(byteRange, "bytes=%" PRIu64 "-", mStartPos);
+ rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!mEntityID.IsEmpty()) {
+ // Also, we want an error if this resource changed in the meantime
+ // Format of the entity id is: escaped_etag/size/lastmod
+ nsCString::const_iterator start, end, slash;
+ mEntityID.BeginReading(start);
+ mEntityID.EndReading(end);
+ mEntityID.BeginReading(slash);
+
+ if (FindCharInReadable('/', slash, end)) {
+ nsAutoCString ifMatch;
+ rv = mRequestHead.SetHeader(
+ nsHttp::If_Match,
+ NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ ++slash; // Incrementing, so that searching for '/' won't find
+ // the same slash again
+ }
+
+ if (FindCharInReadable('/', slash, end)) {
+ rv = mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
+ Substring(++slash, end));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ }
+
+ // create wrapper for this channel's notification callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ // create the transaction object
+ if (nsIOService::UseSocketProcess()) {
+ if (NS_WARN_IF(!gIOService->SocketProcessReady())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ SocketProcessParent* socketProcess = SocketProcessParent::GetSingleton();
+ if (!socketProcess->CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<DocumentLoadListener> documentChannelParent =
+ do_QueryObject(parentChannel);
+ // See HttpTransactionChild::CanSendODAToContentProcessDirectly() and
+ // nsHttpChannel::CallOnStartRequest() for the reason why we need to know if
+ // this is a document load. We only send ODA directly to child process for
+ // non document loads.
+ RefPtr<HttpTransactionParent> transParent =
+ new HttpTransactionParent(!!documentChannelParent);
+ LOG1(("nsHttpChannel %p created HttpTransactionParent %p\n", this,
+ transParent.get()));
+
+ // Since OnStopRequest could be sent to child process from socket process
+ // directly, we need to store these two values in HttpTransactionChild and
+ // forward to child process until HttpTransactionChild::OnStopRequest is
+ // called.
+ transParent->SetRedirectTimestamp(mRedirectStartTimeStamp,
+ mRedirectEndTimeStamp);
+
+ if (socketProcess) {
+ MOZ_ALWAYS_TRUE(
+ socketProcess->SendPHttpTransactionConstructor(transParent));
+ }
+ mTransaction = transParent;
+ } else {
+ mTransaction = new nsHttpTransaction();
+ LOG1(("nsHttpChannel %p created nsHttpTransaction %p\n", this,
+ mTransaction.get()));
+ }
+
+ // Save the mapping of channel id and the channel. We need this mapping for
+ // nsIHttpActivityObserver.
+ gHttpHandler->AddHttpChannel(mChannelId, ToSupports(this));
+
+ // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
+ if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
+
+ if (LoadTimingEnabled()) mCaps |= NS_HTTP_TIMING_ENABLED;
+
+ if (mUpgradeProtocolCallback) {
+ rv = mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Connection, nsHttp::Upgrade.get(),
+ true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+ }
+
+ if (mWebTransportSessionEventListener) {
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ }
+
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+ HttpTransactionShell::OnPushCallback pushCallback = nullptr;
+ if (pushListener) {
+ mCaps |= NS_HTTP_ONPUSH_LISTENER;
+ nsWeakPtr weakPtrThis(
+ do_GetWeakReference(static_cast<nsIHttpChannel*>(this)));
+ pushCallback = [weakPtrThis](uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis)) {
+ return static_cast<nsHttpChannel*>(channel.get())
+ ->OnPush(aPushedStreamId, aUrl, aRequestString, aTransaction);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ };
+ }
+
+ EnsureBrowserId();
+ EnsureRequestContext();
+
+ HttpTrafficCategory category = CreateTrafficCategory();
+ std::function<void(TransactionObserverResult&&)> observer;
+ if (mTransactionObserver) {
+ observer = [transactionObserver{std::move(mTransactionObserver)}](
+ TransactionObserverResult&& aResult) {
+ transactionObserver->Complete(aResult.versionOk(), aResult.authOk(),
+ aResult.closeReason());
+ };
+ }
+ mTransaction->SetIsForWebTransport(!!mWebTransportSessionEventListener);
+ rv = mTransaction->Init(
+ mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
+ LoadUploadStreamHasHeaders(), GetCurrentSerialEventTarget(), callbacks,
+ this, mBrowserId, category, mRequestContext, mClassOfService,
+ mInitialRwin, LoadResponseTimeoutEnabled(), mChannelId,
+ std::move(observer), std::move(pushCallback), mTransWithPushedStream,
+ mPushedStreamId);
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ return rv;
+}
+
+HttpTrafficCategory nsHttpChannel::CreateTrafficCategory() {
+ MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+ !mThirdPartyClassificationFlags);
+
+ if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+ return HttpTrafficCategory::eInvalid;
+ }
+
+ HttpTrafficAnalyzer::ClassOfService cos;
+ {
+ if ((mClassOfService.Flags() & nsIClassOfService::Leader) &&
+ mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SCRIPT) {
+ cos = HttpTrafficAnalyzer::ClassOfService::eLeader;
+ } else if (mLoadFlags & nsIRequest::LOAD_BACKGROUND) {
+ cos = HttpTrafficAnalyzer::ClassOfService::eBackground;
+ } else {
+ cos = HttpTrafficAnalyzer::ClassOfService::eOther;
+ }
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(this);
+
+ HttpTrafficAnalyzer::TrackingClassification tc;
+ {
+ uint32_t flags = isThirdParty ? mThirdPartyClassificationFlags
+ : mFirstPartyClassificationFlags;
+
+ using CF = nsIClassifiedChannel::ClassificationFlags;
+ using TC = HttpTrafficAnalyzer::TrackingClassification;
+
+ if (flags & CF::CLASSIFIED_TRACKING_CONTENT) {
+ tc = TC::eContent;
+ } else if (flags & CF::CLASSIFIED_FINGERPRINTING_CONTENT) {
+ tc = TC::eFingerprinting;
+ } else if (flags & CF::CLASSIFIED_ANY_BASIC_TRACKING) {
+ tc = TC::eBasic;
+ } else {
+ tc = TC::eNone;
+ }
+ }
+
+ bool isSystemPrincipal =
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal();
+ return HttpTrafficAnalyzer::CreateTrafficCategory(
+ NS_UsePrivateBrowsing(this), isSystemPrincipal, isThirdParty, cos, tc);
+}
+
+void nsHttpChannel::SetCachedContentType() {
+ if (!mResponseHead) {
+ return;
+ }
+
+ nsAutoCString contentTypeStr;
+ mResponseHead->ContentType(contentTypeStr);
+
+ uint8_t contentType = nsICacheEntry::CONTENT_TYPE_OTHER;
+ if (nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(contentTypeStr))) {
+ contentType = nsICacheEntry::CONTENT_TYPE_JAVASCRIPT;
+ } else if (StringBeginsWith(contentTypeStr, "text/css"_ns) ||
+ (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_STYLESHEET)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_STYLESHEET;
+ } else if (StringBeginsWith(contentTypeStr, "application/wasm"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_WASM;
+ } else if (StringBeginsWith(contentTypeStr, "image/"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_IMAGE;
+ } else if (StringBeginsWith(contentTypeStr, "video/"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
+ } else if (StringBeginsWith(contentTypeStr, "audio/"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
+ }
+
+ mCacheEntry->SetContentType(contentType);
+}
+
+nsresult nsHttpChannel::CallOnStartRequest() {
+ LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));
+
+ MOZ_RELEASE_ASSERT(!LoadRequireCORSPreflight() || LoadIsCorsPreflightDone(),
+ "CORS preflight must have been finished by the time we "
+ "call OnStartRequest");
+
+ MOZ_RELEASE_ASSERT(mCanceled || LoadProcessCrossOriginSecurityHeadersCalled(),
+ "Security headers need to have been processed before "
+ "calling CallOnStartRequest");
+
+ mEarlyHintObserver = nullptr;
+
+ if (LoadOnStartRequestCalled()) {
+ // This can only happen when a range request loading rest of the data
+ // after interrupted concurrent cache read asynchronously failed, e.g.
+ // the response range bytes are not as expected or this channel has
+ // been externally canceled.
+ //
+ // It's legal to bypass CallOnStartRequest for that case since we've
+ // already called OnStartRequest on our listener and also added all
+ // content converters before.
+ MOZ_ASSERT(LoadConcurrentCacheAccess());
+ LOG(("CallOnStartRequest already invoked before"));
+ return mStatus;
+ }
+
+ // Ensure mListener->OnStartRequest will be invoked before exiting
+ // this function.
+ auto onStartGuard = MakeScopeExit([&] {
+ LOG(
+ (" calling mListener->OnStartRequest by ScopeExit [this=%p, "
+ "listener=%p]\n",
+ this, mListener.get()));
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ deleteProtector->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+ });
+
+ nsresult rv = ValidateMIMEType();
+ // Since ODA and OnStopRequest could be sent from socket process directly, we
+ // need to update the channel status before calling mListener->OnStartRequest.
+ // This is the only way to let child process discard the already received ODA
+ // messages.
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ return mStatus;
+ }
+
+ // EnsureOpaqueResponseIsAllowed and EnsureOpauqeResponseIsAllowedAfterSniff
+ // are the checks for Opaque Response Blocking to ensure that we block as many
+ // cross-origin responses with CORS headers as possible that are not either
+ // Javascript or media to avoid leaking their contents through side channels.
+ OpaqueResponse opaqueResponse =
+ PerformOpaqueResponseSafelistCheckBeforeSniff();
+ if (opaqueResponse == OpaqueResponse::Block) {
+ SetChannelBlockedByOpaqueResponse();
+ CancelWithReason(NS_ERROR_FAILURE,
+ "OpaqueResponseBlocker::BlockResponse"_ns);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Allow consumers to override our content type
+ if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ // NOTE: We can have both a txn pump and a cache pump when the cache
+ // content is partial. In that case, we need to read from the cache,
+ // because that's the one that has the initial contents. If that fails
+ // then give the transaction pump a shot.
+
+ nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
+
+ bool typeSniffersCalled = false;
+ if (mCachePump) {
+ typeSniffersCalled =
+ NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
+ }
+
+ if (!typeSniffersCalled && mTransactionPump) {
+ RefPtr<nsInputStreamPump> pump = do_QueryObject(mTransactionPump);
+ if (pump) {
+ pump->PeekStream(CallTypeSniffers, thisChannel);
+ } else {
+ MOZ_ASSERT(nsIOService::UseSocketProcess());
+ RefPtr<HttpTransactionParent> trans = do_QueryObject(mTransactionPump);
+ MOZ_ASSERT(trans);
+ trans->SetSniffedTypeToChannel(CallTypeSniffers, thisChannel);
+ }
+ }
+ }
+
+ // Note that the code below should be synced with the code in
+ // HttpTransactionChild::CanSendODAToContentProcessDirectly(). We MUST make
+ // sure HttpTransactionChild::CanSendODAToContentProcessDirectly() returns
+ // false when a stream converter is applied.
+ bool unknownDecoderStarted = false;
+ if (mResponseHead && !mResponseHead->HasContentType()) {
+ MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
+ if (!mContentTypeHint.IsEmpty()) {
+ mResponseHead->SetContentType(mContentTypeHint);
+ } else if (mResponseHead->Version() == HttpVersion::v0_9 &&
+ mConnectionInfo->OriginPort() !=
+ mConnectionInfo->DefaultPort()) {
+ mResponseHead->SetContentType(nsLiteralCString(TEXT_PLAIN));
+ } else {
+ // Uh-oh. We had better find out what type we are!
+ mListener = new nsUnknownDecoder(mListener);
+ unknownDecoderStarted = true;
+ }
+ }
+
+ // If unknownDecoder is not going to be launched, call
+ // EnsureOpaqueResponseIsAllowedAfterSniff immediately.
+ if (!unknownDecoderStarted) {
+ if (opaqueResponse == OpaqueResponse::SniffCompressed) {
+ mListener = new nsCompressedAudioVideoImageDetector(
+ mListener, &HttpBaseChannel::CallTypeSniffers);
+ } else if (opaqueResponse == OpaqueResponse::Sniff) {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ nsresult rv = mORB->EnsureOpaqueResponseIsAllowedAfterSniff(this);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ // If the content is multipart/x-mixed-replace, we'll insert a MIME decoder
+ // in the pipeline to handle the content and pass it along to our
+ // original listener. nsUnknownDecoder doesn't support detecting this type,
+ // so we only need to insert this using the response header's mime type.
+ //
+ // We only do this for unwrapped document loads, since we might want to send
+ // parts to the external protocol handler without leaving the parent process.
+ bool mustRunStreamFilterInParent = false;
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<DocumentLoadListener> docListener = do_QueryObject(parentChannel);
+ if (mResponseHead && docListener && docListener->GetChannel() == this) {
+ nsAutoCString contentType;
+ mResponseHead->ContentType(contentType);
+
+ if (contentType.Equals("multipart/x-mixed-replace"_ns)) {
+ nsCOMPtr<nsIStreamConverterService> convServ(
+ do_GetService("@mozilla.org/streamConverters;1", &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIStreamListener> toListener(mListener);
+ nsCOMPtr<nsIStreamListener> fromListener;
+
+ rv = convServ->AsyncConvertData("multipart/x-mixed-replace", "*/*",
+ toListener, nullptr,
+ getter_AddRefs(fromListener));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = fromListener;
+ mustRunStreamFilterInParent = true;
+ }
+ }
+ }
+ }
+
+ // If we installed a multipart converter, then we need to add StreamFilter
+ // object before it, so that extensions see the un-parsed original stream.
+ // We may want to add an option for extensions to opt-in to proper multipart
+ // handling.
+ // If not, then pass the StreamFilter promise on to DocumentLoadListener,
+ // where it'll be added in the content process.
+ for (StreamFilterRequest& request : mStreamFilterRequests) {
+ if (mustRunStreamFilterInParent) {
+ mozilla::ipc::Endpoint<extensions::PStreamFilterParent> parent;
+ mozilla::ipc::Endpoint<extensions::PStreamFilterChild> child;
+ nsresult rv = extensions::PStreamFilter::CreateEndpoints(&parent, &child);
+ if (NS_FAILED(rv)) {
+ request.mPromise->Reject(false, __func__);
+ } else {
+ extensions::StreamFilterParent::Attach(this, std::move(parent));
+ request.mPromise->Resolve(std::move(child), __func__);
+ }
+ } else {
+ if (docListener) {
+ docListener->AttachStreamFilter()->ChainTo(request.mPromise.forget(),
+ __func__);
+ } else {
+ request.mPromise->Reject(false, __func__);
+ }
+ }
+ request.mPromise = nullptr;
+ }
+ mStreamFilterRequests.Clear();
+ StoreTracingEnabled(false);
+
+ if (mResponseHead && !mResponseHead->HasContentCharset()) {
+ mResponseHead->SetContentCharset(mContentCharsetHint);
+ }
+
+ if (mCacheEntry && LoadCacheEntryIsWriteOnly()) {
+ SetCachedContentType();
+ }
+
+ LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
+ mListener.get()));
+
+ // About to call OnStartRequest, dismiss the guard object.
+ onStartGuard.release();
+
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OsStartRequest twice");
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ rv = deleteProtector->OnStartRequest(this);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ StoreOnStartRequestCalled(true);
+ }
+
+ // Install stream converter if required.
+ // Normally, we expect the listener to disable content conversion during
+ // OnStartRequest if it wants to handle it itself (which is common case with
+ // HttpChannelParent, disabling so that it can be done in the content
+ // process). If we've installed an nsUnknownDecoder, then we won't yet have
+ // called OnStartRequest on the final listener (that happens after we send
+ // OnDataAvailable to the nsUnknownDecoder), so it can't yet have disabled
+ // content conversion.
+ // In that case, assume that the listener will disable content conversion,
+ // unless it's specifically told us that it won't.
+ if (!unknownDecoderStarted || LoadListenerRequiresContentConversion()) {
+ nsCOMPtr<nsIStreamListener> listener;
+ rv =
+ DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (listener) {
+ MOZ_ASSERT(!LoadDataSentToChildProcess(),
+ "DataSentToChildProcess being true means ODAs are sent to "
+ "the child process directly. We MUST NOT apply content "
+ "converter in this case.");
+ mListener = listener;
+ mCompressListener = listener;
+ StoreHasAppliedConversion(true);
+ }
+ }
+
+ // if this channel is for a download, close off access to the cache.
+ if (mCacheEntry && LoadChannelIsForDownload()) {
+ mCacheEntry->AsyncDoom(nullptr);
+
+ // We must keep the cache entry in case of partial request.
+ // Concurrent access is the same, we need the entry in
+ // OnStopRequest.
+ // We also need the cache entry when racing cache with network to find
+ // out what is the source of the data.
+ if (!LoadCachedContentIsPartial() && !LoadConcurrentCacheAccess() &&
+ !(mRaceCacheWithNetwork &&
+ mFirstResponseSource == RESPONSE_FROM_CACHE)) {
+ CloseCacheEntry(false);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ NS_ENSURE_ARG_POINTER(aResponseCode);
+
+ if (mConnectionInfo && mConnectionInfo->UsingConnect()) {
+ *aResponseCode = mProxyConnectResponseCode;
+ } else {
+ *aResponseCode = -1;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
+ // Failure to set up a proxy tunnel via CONNECT means one of the following:
+ // 1) Proxy wants authorization, or forbids.
+ // 2) DNS at proxy couldn't resolve target URL.
+ // 3) Proxy connection to target failed or timed out.
+ // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
+ //
+ // Our current architecture would parse the proxy's response content with
+ // the permission of the target URL. Given #4, we must avoid rendering the
+ // body of the reply, and instead give the user a (hopefully helpful)
+ // boilerplate error page, based on just the HTTP status of the reply.
+
+ MOZ_ASSERT(mConnectionInfo->UsingConnect(),
+ "proxy connect failed but not using CONNECT?");
+ nsresult rv = HttpProxyResponseToErrorCode(httpStatus);
+ LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n", this,
+ httpStatus));
+
+ // Make sure the connection is thrown away as it can be in a bad state
+ // and the proxy may just hang on the next request.
+ MOZ_ASSERT(mTransaction);
+ mTransaction->DontReuseConnection();
+
+ Cancel(rv);
+ {
+ nsresult rv = CallOnStartRequest();
+ if (NS_FAILED(rv)) {
+ LOG(("CallOnStartRequest failed [this=%p httpStatus=%u rv=%08x]\n", this,
+ httpStatus, static_cast<uint32_t>(rv)));
+ }
+ }
+ return rv;
+}
+
+static void GetSTSConsoleErrorTag(uint32_t failureResult,
+ nsAString& consoleErrorTag) {
+ switch (failureResult) {
+ case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+ consoleErrorTag = u"STSCouldNotParseHeader"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+ consoleErrorTag = u"STSNoMaxAge"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+ consoleErrorTag = u"STSMultipleMaxAges"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+ consoleErrorTag = u"STSInvalidMaxAge"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = u"STSMultipleIncludeSubdomains"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = u"STSInvalidIncludeSubdomains"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+ consoleErrorTag = u"STSCouldNotSaveState"_ns;
+ break;
+ default:
+ consoleErrorTag = u"STSUnknownError"_ns;
+ break;
+ }
+}
+
+/**
+ * Process an HTTP Strict Transport Security (HSTS) header.
+ */
+nsresult nsHttpChannel::ProcessHSTSHeader(nsITransportSecurityInfo* aSecInfo) {
+ nsHttpAtom atom(nsHttp::ResolveAtom("Strict-Transport-Security"_ns));
+
+ nsAutoCString securityHeader;
+ nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(("nsHttpChannel: No %s header, continuing load.\n", atom.get()));
+ return NS_OK;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!aSecInfo) {
+ LOG(("nsHttpChannel::ProcessHSTSHeader: no securityInfo?"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory;
+ rv = aSecInfo->GetOverridableErrorCategory(&overridableErrorCategory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (overridableErrorCategory !=
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) {
+ LOG(
+ ("nsHttpChannel::ProcessHSTSHeader: untrustworthy connection - not "
+ "processing header"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+
+ OriginAttributes originAttributes;
+ if (NS_WARN_IF(!StoragePrincipalHelper::GetOriginAttributesForHSTS(
+ this, originAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t failureResult;
+ rv = sss->ProcessHeader(mURI, securityHeader, originAttributes, nullptr,
+ nullptr, &failureResult);
+ if (NS_FAILED(rv)) {
+ nsAutoString consoleErrorCategory(u"Invalid HSTS Headers"_ns);
+ nsAutoString consoleErrorTag;
+ GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
+ atom.get()));
+ }
+ return NS_OK;
+}
+
+/**
+ * Decide whether or not to remember Strict-Transport-Security, and whether
+ * or not to enforce channel integrity.
+ *
+ * @return NS_ERROR_FAILURE if there's security information missing even though
+ * it's an HTTPS connection.
+ */
+nsresult nsHttpChannel::ProcessSecurityHeaders() {
+ // If this channel is not loading securely, STS or PKP doesn't do anything.
+ // In the case of HSTS, the upgrade to HTTPS takes place earlier in the
+ // channel load process.
+ if (!mURI->SchemeIs("https")) {
+ return NS_OK;
+ }
+
+ if (IsBrowsingContextDiscarded()) {
+ return NS_OK;
+ }
+
+ nsAutoCString asciiHost;
+ nsresult rv = mURI->GetAsciiHost(asciiHost);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ // If the channel is not a hostname, but rather an IP, do not process STS
+ // or PKP headers
+ if (HostIsIPLiteral(asciiHost)) {
+ return NS_OK;
+ }
+
+ // mSecurityInfo may not always be present, and if it's not then it is okay
+ // to just disregard any security headers since we know nothing about the
+ // security of the connection.
+ NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
+
+ // Only process HSTS headers for first-party loads. This prevents a
+ // proliferation of useless HSTS state for partitioned third parties.
+ if (!mLoadInfo->GetIsThirdPartyContextToTopWindow()) {
+ rv = ProcessHSTSHeader(mSecurityInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+bool nsHttpChannel::IsHTTPS() { return mURI->SchemeIs("https"); }
+
+void nsHttpChannel::ProcessSSLInformation() {
+ // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
+ // can be whitelisted for TLS False Start in future sessions. We could
+ // do the same for DH but its rarity doesn't justify the lookup.
+
+ if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo || !IsHTTPS() ||
+ mPrivateBrowsing) {
+ return;
+ }
+
+ if (!mSecurityInfo) {
+ return;
+ }
+
+ uint32_t state;
+ if (NS_SUCCEEDED(mSecurityInfo->GetSecurityState(&state)) &&
+ (state & nsIWebProgressListener::STATE_IS_BROKEN)) {
+ // Send weak crypto warnings to the web console
+ if (state & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
+ nsString consoleErrorTag = u"WeakCipherSuiteWarning"_ns;
+ nsString consoleErrorCategory = u"SSL"_ns;
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ }
+ }
+
+ uint16_t tlsVersion;
+ nsresult rv = mSecurityInfo->GetProtocolVersion(&tlsVersion);
+ if (NS_SUCCEEDED(rv) &&
+ tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_2 &&
+ tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_3) {
+ nsString consoleErrorTag = u"DeprecatedTLSVersion2"_ns;
+ nsString consoleErrorCategory = u"TLS"_ns;
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ }
+}
+
+void nsHttpChannel::ProcessAltService() {
+ // e.g. Alt-Svc: h2=":443"; ma=60
+ // e.g. Alt-Svc: h2="otherhost:443"
+ // Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
+ // alternative = protocol-id "=" alt-authority
+ // protocol-id = token ; percent-encoded ALPN protocol identifier
+ // alt-authority = quoted-string ; containing [ uri-host ] ":" port
+
+ if (!LoadAllowAltSvc()) { // per channel opt out
+ return;
+ }
+
+ if (mWebTransportSessionEventListener) {
+ return;
+ }
+
+ if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
+ return;
+ }
+
+ if (IsBrowsingContextDiscarded()) {
+ return;
+ }
+
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ bool isHttp = scheme.EqualsLiteral("http");
+ if (!isHttp && !scheme.EqualsLiteral("https")) {
+ return;
+ }
+
+ nsAutoCString altSvc;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (altSvc.IsEmpty()) {
+ return;
+ }
+
+ if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
+ LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
+ return;
+ }
+
+ nsAutoCString originHost;
+ int32_t originPort = 80;
+ mURI->GetPort(&originPort);
+ if (NS_FAILED(mURI->GetAsciiHost(originHost))) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ if (mProxyInfo) {
+ proxyInfo = do_QueryInterface(mProxyInfo);
+ }
+
+ OriginAttributes originAttributes;
+ // Regular principal in case we have a proxy.
+ if (proxyInfo &&
+ !StaticPrefs::privacy_partition_network_state_connection_with_proxy()) {
+ StoragePrincipalHelper::GetOriginAttributes(
+ this, originAttributes, StoragePrincipalHelper::eRegularPrincipal);
+ } else {
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ this, originAttributes);
+ }
+
+ AltSvcMapping::ProcessHeader(
+ altSvc, scheme, originHost, originPort, mUsername, mPrivateBrowsing,
+ callbacks, proxyInfo, mCaps & NS_HTTP_DISALLOW_SPDY, originAttributes);
+}
+
+nsresult nsHttpChannel::ProcessResponse() {
+ uint32_t httpStatus = mResponseHead->Status();
+
+ LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n", this,
+ httpStatus));
+
+ // Gather data on whether the transaction and page (if this is
+ // the initial page load) is being loaded with SSL.
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
+ mConnectionInfo->EndToEndSSL());
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
+ mConnectionInfo->EndToEndSSL());
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ // how often do we see something like Alt-Svc: "443:quic,p=1"
+ // and Alt-Svc: "h3-****"
+ nsAutoCString alt_service;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, alt_service);
+ uint32_t saw_quic = 0;
+ if (!alt_service.IsEmpty()) {
+ if (strstr(alt_service.get(), "h3-")) {
+ saw_quic = 1;
+ } else if (strstr(alt_service.get(), "quic")) {
+ saw_quic = 2;
+ }
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL_2, saw_quic);
+
+ // Gather data on how many URLS get redirected
+ switch (httpStatus) {
+ case 200:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 0);
+ break;
+ case 301:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 1);
+ break;
+ case 302:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 2);
+ break;
+ case 304:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 3);
+ break;
+ case 307:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 4);
+ break;
+ case 308:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 5);
+ break;
+ case 400:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 6);
+ break;
+ case 401:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 7);
+ break;
+ case 403:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 8);
+ break;
+ case 404:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 9);
+ break;
+ case 500:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 10);
+ break;
+ default:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 11);
+ break;
+ }
+ }
+
+ // Let the predictor know whether this was a cacheable response or not so
+ // that it knows whether or not to possibly prefetch this resource in the
+ // future.
+ // We use GetReferringPage because mReferrerInfo may not be set at all(this is
+ // especially useful in xpcshell tests, where we don't have an actual pageload
+ // to get a referrer from).
+ nsCOMPtr<nsIURI> referrer = GetReferringPage();
+ if (!referrer && mReferrerInfo) {
+ referrer = mReferrerInfo->GetOriginalReferrer();
+ }
+
+ if (referrer) {
+ nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
+ mozilla::net::Predictor::UpdateCacheability(
+ referrer, mURI, httpStatus, mRequestHead, mResponseHead.get(), lci,
+ IsThirdPartyTrackingResource());
+ }
+
+ // Only allow 407 (authentication required) to continue
+ if (mTransaction && mTransaction->ProxyConnectFailed() && httpStatus != 407) {
+ return ProcessFailedProxyConnect(httpStatus);
+ }
+
+ MOZ_ASSERT(!mCachedContentIsValid || mRaceCacheWithNetwork,
+ "We should not be hitting the network if we have valid cached "
+ "content unless we are racing the network and cache");
+
+ ProcessSSLInformation();
+
+ // notify "http-on-examine-response" observers
+ gHttpHandler->OnExamineResponse(this);
+
+ return ContinueProcessResponse1();
+}
+
+void nsHttpChannel::AsyncContinueProcessResponse() {
+ nsresult rv;
+ rv = ContinueProcessResponse1();
+ if (NS_FAILED(rv)) {
+ // A synchronous failure here would normally be passed as the return
+ // value from OnStartRequest, which would in turn cancel the request.
+ // If we're continuing asynchronously, we need to cancel the request
+ // ourselves.
+ Unused << Cancel(rv);
+ }
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse1() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+ nsresult rv = NS_OK;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to finish processing response [this=%p]\n",
+ this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->AsyncContinueProcessResponse();
+ return NS_OK;
+ };
+ return NS_OK;
+ }
+
+ // Check if request was cancelled during http-on-examine-response.
+ if (mCanceled) {
+ return CallOnStartRequest();
+ }
+
+ uint32_t httpStatus = mResponseHead->Status();
+
+ // STS, Cookies and Alt-Service should not be handled on proxy failure.
+ // If proxy CONNECT response needs to complete, wait to process connection
+ // for Strict-Transport-Security.
+ if (!(mTransaction && mTransaction->ProxyConnectFailed()) &&
+ (httpStatus != 407)) {
+ if (nsAutoCString cookie;
+ NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
+ SetCookie(cookie);
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ if (RefPtr<HttpChannelParent> httpParent =
+ do_QueryObject(parentChannel)) {
+ httpParent->SetCookie(std::move(cookie));
+ }
+ }
+
+ // Given a successful connection, process any STS or PKP data that's
+ // relevant.
+ DebugOnly<nsresult> rv = ProcessSecurityHeaders();
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
+
+ if ((httpStatus < 500) && (httpStatus != 421)) {
+ ProcessAltService();
+ }
+ }
+
+ if (LoadConcurrentCacheAccess() && LoadCachedContentIsPartial() &&
+ httpStatus != 206) {
+ LOG(
+ (" only expecting 206 when doing partial request during "
+ "interrupted cache concurrent read"));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // handle unused username and password in url (see bug 232567)
+ if (httpStatus != 401 && httpStatus != 407) {
+ if (!mAuthRetryPending) {
+ rv = mAuthProvider->CheckForSuperfluousAuth();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ LOG((" CheckForSuperfluousAuth failed (%08x)",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ if (mCanceled) return CallOnStartRequest();
+
+ // reset the authentication's current continuation state because ourvr
+ // last authentication attempt has been completed successfully
+ rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
+ if (NS_FAILED(rv)) {
+ LOG((" Disconnect failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ mAuthProvider = nullptr;
+ LOG((" continuation state has been reset"));
+ }
+
+ // No process switch needed, continue as normal.
+ return ContinueProcessResponse2(rv);
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse2(nsresult rv) {
+ if (NS_FAILED(rv) && !mCanceled) {
+ // The process switch failed, cancel this channel.
+ Cancel(rv);
+ return CallOnStartRequest();
+ }
+
+ if (mAPIRedirectToURI && !mCanceled) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
+ rv = StartRedirectChannelToURI(redirectTo,
+ nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
+ }
+
+ // Hack: ContinueProcessResponse3 uses NS_OK to detect successful
+ // redirects, so we distinguish this codepath (a non-redirect that's
+ // processing normally) by passing in a bogus error code.
+ return ContinueProcessResponse3(NS_BINDING_FAILED);
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
+ LOG(("nsHttpChannel::ContinueProcessResponse3 [this=%p, rv=%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+
+ if (NS_SUCCEEDED(rv)) {
+ // redirectTo() has passed through, we don't want to go on with
+ // this channel. It will now be canceled by the redirect handling
+ // code that called this function.
+ return NS_OK;
+ }
+
+ rv = NS_OK;
+
+ uint32_t httpStatus = mResponseHead->Status();
+ bool transactionRestarted = mTransaction->TakeRestartedState();
+
+ // handle different server response categories. Note that we handle
+ // caching or not caching of error pages in
+ // nsHttpResponseHead::MustValidate; if you change this switch, update that
+ // one
+ switch (httpStatus) {
+ case 200:
+ case 203:
+ // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
+ // So if a server does that and sends 200 instead of 206 that we
+ // expect, notify our caller.
+ // However, if we wanted to start from the beginning, let it go through
+ if (LoadResuming() && mStartPos != 0) {
+ LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
+ Cancel(NS_ERROR_NOT_RESUMABLE);
+ rv = CallOnStartRequest();
+ break;
+ }
+ // these can normally be cached
+ rv = ProcessNormal();
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ break;
+ case 206:
+ if (LoadCachedContentIsPartial()) { // an internal byte range request...
+ auto func = [](auto* self, nsresult aRv) {
+ return self->ContinueProcessResponseAfterPartialContent(aRv);
+ };
+ rv = ProcessPartialContent(func);
+ // Directly call ContinueProcessResponseAfterPartialContent if channel
+ // is not suspended or ProcessPartialContent throws.
+ if (!mSuspendCount || NS_FAILED(rv)) {
+ return ContinueProcessResponseAfterPartialContent(rv);
+ }
+ return NS_OK;
+ } else {
+ mCacheInputStream.CloseAndRelease();
+ rv = ProcessNormal();
+ }
+ break;
+ case 300:
+ case 301:
+ case 302:
+ case 307:
+ case 308:
+ case 303:
+#if 0
+ case 305: // disabled as a security measure (see bug 187996).
+#endif
+ // don't store the response body for redirects
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
+ rv = AsyncProcessRedirection(httpStatus);
+ if (NS_FAILED(rv)) {
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
+ LOG(("AsyncProcessRedirection failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ // don't cache failed redirect responses.
+ if (mCacheEntry) mCacheEntry->AsyncDoom(nullptr);
+ if (DoNotRender3xxBody(rv)) {
+ mStatus = rv;
+ DoNotifyListener();
+ } else {
+ rv = ContinueProcessResponse4(rv);
+ }
+ }
+ break;
+ case 304:
+ if (!ShouldBypassProcessNotModified()) {
+ auto func = [](auto* self, nsresult aRv) {
+ return self->ContinueProcessResponseAfterNotModified(aRv);
+ };
+ rv = ProcessNotModified(func);
+ // Directly call ContinueProcessResponseAfterNotModified if channel
+ // is not suspended or ProcessNotModified throws.
+ if (!mSuspendCount || NS_FAILED(rv)) {
+ return ContinueProcessResponseAfterNotModified(rv);
+ }
+ return NS_OK;
+ }
+
+ // Don't cache uninformative 304
+ if (LoadCustomConditionalRequest()) {
+ CloseCacheEntry(false);
+ }
+
+ if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
+ rv = ProcessNormal();
+ }
+ break;
+ case 401:
+ case 407:
+ if (MOZ_UNLIKELY(httpStatus == 407 && transactionRestarted)) {
+ // The transaction has been internally restarted. We want to
+ // authenticate to the proxy again, so reuse either cached credentials
+ // or use default credentials for NTLM/Negotiate. This prevents
+ // considering the previously used credentials as invalid.
+ mAuthProvider->ClearProxyIdent();
+ }
+ if (!LoadAuthRedirectedChannel() &&
+ MOZ_UNLIKELY(LoadCustomAuthHeader()) && httpStatus == 401) {
+ // When a custom auth header fails, we don't want to try
+ // any cached credentials, nor we want to ask the user.
+ // It's up to the consumer to re-try w/o setting a custom
+ // auth header if cached credentials should be attempted.
+ rv = NS_ERROR_FAILURE;
+ } else if (httpStatus == 401 &&
+ StaticPrefs::
+ network_auth_supress_auth_prompt_for_XFO_failures() &&
+ !nsContentSecurityUtils::CheckCSPFrameAncestorAndXFO(this)) {
+ // CSP Frame Ancestor and X-Frame-Options check has failed
+ // Do not prompt http auth - Bug 1629307
+ rv = NS_ERROR_FAILURE;
+ } else {
+ rv = mAuthProvider->ProcessAuthentication(
+ httpStatus, mConnectionInfo->EndToEndSSL() && mTransaction &&
+ mTransaction->ProxyConnectFailed());
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result
+ // is expected asynchronously
+ mIsAuthChannel = true;
+ mAuthRetryPending = true;
+ if (httpStatus == 407 ||
+ (mTransaction && mTransaction->ProxyConnectFailed())) {
+ StoreProxyAuthPending(true);
+ }
+
+ // suspend the transaction pump to stop receiving the
+ // unauthenticated content data. We will throw that data
+ // away when user provides credentials or resume the pump
+ // when user refuses to authenticate.
+ LOG(
+ ("Suspending the transaction, asynchronously prompting for "
+ "credentials"));
+ mTransactionPump->Suspend();
+
+#ifdef DEBUG
+ // This is for test purposes only. See bug 1683176 for details.
+ gHttpHandler->OnTransactionSuspendedDueToAuthentication(this);
+#endif
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ LOG(("ProcessAuthentication failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ if (mTransaction && mTransaction->ProxyConnectFailed()) {
+ return ProcessFailedProxyConnect(httpStatus);
+ }
+ if (!mAuthRetryPending) {
+ rv = mAuthProvider->CheckForSuperfluousAuth();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ LOG(("CheckForSuperfluousAuth failed [rv=%x]\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ rv = ProcessNormal();
+ } else {
+ mIsAuthChannel = true;
+ mAuthRetryPending = true;
+ if (StaticPrefs::network_auth_use_redirect_for_retries()) {
+ if (NS_SUCCEEDED(RedirectToNewChannelForAuthRetry())) {
+ return NS_OK;
+ }
+ mAuthRetryPending = false;
+ rv = ProcessNormal();
+ }
+ }
+ break;
+
+ case 408:
+ case 425:
+ case 429:
+ // Do not cache 408, 425 and 429.
+ CloseCacheEntry(false);
+ [[fallthrough]]; // process normally
+ default:
+ rv = ProcessNormal();
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ break;
+ }
+
+ UpdateCacheDisposition(false, false);
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueProcessResponseAfterPartialContent(
+ nsresult aRv) {
+ LOG(
+ ("nsHttpChannel::ContinueProcessResponseAfterPartialContent "
+ "[this=%p, rv=%" PRIx32 "]",
+ this, static_cast<uint32_t>(aRv)));
+
+ UpdateCacheDisposition(false, NS_SUCCEEDED(aRv));
+ return aRv;
+}
+
+nsresult nsHttpChannel::ContinueProcessResponseAfterNotModified(nsresult aRv) {
+ LOG(
+ ("nsHttpChannel::ContinueProcessResponseAfterNotModified "
+ "[this=%p, rv=%" PRIx32 "]",
+ this, static_cast<uint32_t>(aRv)));
+
+ if (NS_SUCCEEDED(aRv)) {
+ StoreTransactionReplaced(true);
+ UpdateCacheDisposition(true, false);
+ return NS_OK;
+ }
+
+ LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(aRv)));
+
+ // We cannot read from the cache entry, it might be in an
+ // incosistent state. Doom it and redirect the channel
+ // to the same URI to reload from the network.
+ mCacheInputStream.CloseAndRelease();
+ if (mCacheEntry) {
+ mCacheEntry->AsyncDoom(nullptr);
+ mCacheEntry = nullptr;
+ }
+
+ nsresult rv =
+ StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+
+ // Don't cache uninformative 304
+ if (LoadCustomConditionalRequest()) {
+ CloseCacheEntry(false);
+ }
+
+ if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
+ rv = ProcessNormal();
+ }
+
+ UpdateCacheDisposition(false, false);
+ return rv;
+}
+
+static void ReportHttpResponseVersion(HttpVersion version) {
+ if (Telemetry::CanRecordPrereleaseData()) {
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
+ static_cast<uint32_t>(version));
+ }
+
+ nsAutoCString versionLabel;
+ switch (version) {
+ case HttpVersion::v0_9:
+ case HttpVersion::v1_0:
+ case HttpVersion::v1_1:
+ versionLabel = "http_1"_ns;
+ break;
+ case HttpVersion::v2_0:
+ versionLabel = "http_2"_ns;
+ break;
+ case HttpVersion::v3_0:
+ versionLabel = "http_3"_ns;
+ break;
+ default:
+ versionLabel = "unknown"_ns;
+ break;
+ }
+ mozilla::glean::networking::http_response_version.Get(versionLabel).Add(1);
+}
+
+void nsHttpChannel::UpdateCacheDisposition(bool aSuccessfulReval,
+ bool aPartialContentUsed) {
+ if (mRaceDelay && !mRaceCacheWithNetwork &&
+ (LoadCachedContentIsPartial() || mDidReval)) {
+ if (aSuccessfulReval || aPartialContentUsed) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
+ } else {
+ AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::
+ CachedContentNotUsed);
+ }
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ CacheDisposition cacheDisposition;
+ if (!mDidReval) {
+ cacheDisposition = kCacheMissed;
+ } else if (aSuccessfulReval) {
+ cacheDisposition = kCacheHitViaReval;
+ } else {
+ cacheDisposition = kCacheMissedViaReval;
+ }
+ AccumulateCacheHitTelemetry(cacheDisposition, this);
+ mCacheDisposition = cacheDisposition;
+
+ if (mResponseHead->Version() == HttpVersion::v0_9) {
+ // DefaultPortTopLevel = 0, DefaultPortSubResource = 1,
+ // NonDefaultPortTopLevel = 2, NonDefaultPortSubResource = 3
+ uint32_t v09Info = 0;
+ if (!(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
+ v09Info += 1;
+ }
+ if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
+ v09Info += 2;
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
+ }
+ }
+
+ ReportHttpResponseVersion(mResponseHead->Version());
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse4(nsresult rv) {
+ bool doNotRender = DoNotRender3xxBody(rv);
+
+ if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
+ bool isHTTP =
+ mRedirectURI->SchemeIs("http") || mRedirectURI->SchemeIs("https");
+ if (!isHTTP) {
+ // This was a blocked attempt to redirect and subvert the system by
+ // redirecting to another protocol (perhaps javascript:)
+ // In that case we want to throw an error instead of displaying the
+ // non-redirected response body.
+ LOG(("ContinueProcessResponse4 detected rejected Non-HTTP Redirection"));
+ doNotRender = true;
+ rv = NS_ERROR_CORRUPTED_CONTENT;
+ }
+ }
+
+ if (doNotRender) {
+ Cancel(rv);
+ DoNotifyListener();
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ UpdateInhibitPersistentCachingFlag();
+
+ MaybeCreateCacheEntryWhenRCWN();
+
+ rv = InitCacheEntry();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ContinueProcessResponse4 "
+ "failed to init cache entry [rv=%x]\n",
+ static_cast<uint32_t>(rv)));
+ }
+ CloseCacheEntry(false);
+ return NS_OK;
+ }
+
+ LOG(("ContinueProcessResponse4 got failure result [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ if (mTransaction && mTransaction->ProxyConnectFailed()) {
+ return ProcessFailedProxyConnect(mRedirectType);
+ }
+ return ProcessNormal();
+}
+
+nsresult nsHttpChannel::ProcessNormal() {
+ LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
+
+ return ContinueProcessNormal(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) {
+ LOG(("nsHttpChannel::ContinueProcessNormal [this=%p]", this));
+
+ if (NS_FAILED(rv)) {
+ // Fill the failure status here, we have failed to fall back, thus we
+ // have to report our status as failed.
+ mStatus = rv;
+ DoNotifyListener();
+ return rv;
+ }
+
+ rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ // if we're here, then any byte-range requests failed to result in a partial
+ // response. we must clear this flag to prevent BufferPartialContent from
+ // being called inside our OnDataAvailable (see bug 136678).
+ StoreCachedContentIsPartial(false);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ MaybeCreateCacheEntryWhenRCWN();
+
+ // this must be called before firing OnStartRequest, since http clients,
+ // such as imagelib, expect our cache entry to already have the correct
+ // expiration time (bug 87710).
+ if (mCacheEntry) {
+ rv = InitCacheEntry();
+ if (NS_FAILED(rv)) CloseCacheEntry(true);
+ }
+
+ // Check that the server sent us what we were asking for
+ if (LoadResuming()) {
+ // Create an entity id from the response
+ nsAutoCString id;
+ rv = GetEntityID(id);
+ if (NS_FAILED(rv)) {
+ // If creating an entity id is not possible -> error
+ Cancel(NS_ERROR_NOT_RESUMABLE);
+ } else if (mResponseHead->Status() != 206 &&
+ mResponseHead->Status() != 200) {
+ // Probably 404 Not Found, 412 Precondition Failed or
+ // 416 Invalid Range -> error
+ LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
+ this));
+ Cancel(NS_ERROR_ENTITY_CHANGED);
+ }
+ // If we were passed an entity id, verify it's equal to the server's
+ else if (!mEntityID.IsEmpty()) {
+ if (!mEntityID.Equals(id)) {
+ LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
+ mEntityID.get(), id.get(), this));
+ Cancel(NS_ERROR_ENTITY_CHANGED);
+ }
+ }
+ }
+
+ rv = CallOnStartRequest();
+ if (NS_FAILED(rv)) return rv;
+
+ // install cache listener if we still have a cache entry open
+ if (mCacheEntry && !LoadCacheEntryIsReadOnly()) {
+ rv = InstallCacheListener();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::PromptTempRedirect() {
+ if (!gHttpHandler->PromptTempRedirect()) {
+ return NS_OK;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ rv =
+ bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString messageString;
+ rv = stringBundle->GetStringFromName("RepostFormData", messageString);
+ if (NS_SUCCEEDED(rv)) {
+ bool repost = false;
+
+ nsCOMPtr<nsIPrompt> prompt;
+ GetCallback(prompt);
+ if (!prompt) return NS_ERROR_NO_INTERFACE;
+
+ prompt->Confirm(nullptr, messageString.get(), &repost);
+ if (!repost) return NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ProxyFailover() {
+ LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
+ getter_AddRefs(pi));
+#ifdef MOZ_PROXY_DIRECT_FAILOVER
+ if (NS_FAILED(rv)) {
+ if (!StaticPrefs::network_proxy_failover_direct()) {
+ return rv;
+ }
+ // If this request used a failed proxy and there is no failover available,
+ // fallback to DIRECT connections for conservative requests.
+ if (LoadBeConservative()) {
+ rv = pps->NewProxyInfo("direct"_ns, ""_ns, 0, ""_ns, ""_ns, 0, UINT32_MAX,
+ nullptr, getter_AddRefs(pi));
+ }
+#endif
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+#ifdef MOZ_PROXY_DIRECT_FAILOVER
+ }
+#endif
+
+ // XXXbz so where does this codepath remove us from the loadgroup,
+ // exactly?
+ return AsyncDoReplaceWithProxy(pi);
+}
+
+void nsHttpChannel::SetHTTPSSVCRecord(
+ already_AddRefed<nsIDNSHTTPSSVCRecord>&& aRecord) {
+ LOG(("nsHttpChannel::SetHTTPSSVCRecord [this=%p]\n", this));
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = aRecord;
+ MOZ_ASSERT(!mHTTPSSVCRecord);
+ mHTTPSSVCRecord.emplace(std::move(record));
+}
+
+void nsHttpChannel::HandleAsyncRedirectChannelToHttps() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect to https [this=%p]\n",
+ this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncRedirectChannelToHttps();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToHttps();
+ if (NS_FAILED(rv)) {
+ rv = ContinueAsyncRedirectChannelToURI(rv);
+ if (NS_FAILED(rv)) {
+ LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+}
+
+nsresult nsHttpChannel::StartRedirectChannelToHttps() {
+ LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ nsresult rv = NS_GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return StartRedirectChannelToURI(
+ upgradedURI, nsIChannelEventSink::REDIRECT_PERMANENT |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE);
+}
+
+void nsHttpChannel::HandleAsyncAPIRedirect() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+ MOZ_ASSERT(mAPIRedirectToURI, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncAPIRedirect();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToURI(
+ mAPIRedirectToURI, nsIChannelEventSink::REDIRECT_PERMANENT);
+ if (NS_FAILED(rv)) {
+ rv = ContinueAsyncRedirectChannelToURI(rv);
+ if (NS_FAILED(rv)) {
+ LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+}
+
+void nsHttpChannel::HandleAsyncRedirectToUnstrippedURI() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(
+ ("Waiting until resume to do async redirect to unstripped URI "
+ "[this=%p]\n",
+ this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncRedirectToUnstrippedURI();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsCOMPtr<nsIURI> unstrippedURI;
+ mLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ // Clear the unstripped URI from the loadInfo before starting redirect in case
+ // endless redirect.
+ mLoadInfo->SetUnstrippedURI(nullptr);
+
+ nsresult rv = StartRedirectChannelToURI(
+ unstrippedURI, nsIChannelEventSink::REDIRECT_PERMANENT);
+
+ if (NS_FAILED(rv)) {
+ rv = ContinueAsyncRedirectChannelToURI(rv);
+ if (NS_FAILED(rv)) {
+ LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+}
+nsresult nsHttpChannel::RedirectToNewChannelForAuthRetry() {
+ LOG(("nsHttpChannel::RedirectToNewChannelForAuthRetry %p", this));
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo = CloneLoadInfoForRedirect(
+ mURI, nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+
+ nsCOMPtr<nsIIOService> ioService;
+
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = gHttpHandler->NewProxiedChannel(mURI, mProxyInfo, mProxyResolveFlags,
+ mProxyURI, mLoadInfo,
+ getter_AddRefs(newChannel));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(mURI, newChannel, true,
+ nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // rewind the upload stream
+ if (mUploadStream) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+ if (seekable) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+
+ // This should not normally happen, but it's possible that big memory
+ // blobs originating in the other process can't be rewinded.
+ // In that case we just fail the request, otherwise the content length
+ // will not match and this load will never complete.
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(newChannel);
+
+ MOZ_ASSERT(mAuthProvider);
+ httpChannelImpl->mAuthProvider = std::move(mAuthProvider);
+
+ httpChannelImpl->mProxyInfo = mProxyInfo;
+
+ if ((mCaps & NS_HTTP_STICKY_CONNECTION) ||
+ mTransaction->HasStickyConnection()) {
+ mConnectionInfo = mTransaction->GetConnInfo();
+
+ httpChannelImpl->mTransactionSticky = mTransaction;
+
+ if (mTransaction->Http2Disabled()) {
+ httpChannelImpl->mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (mTransaction->Http3Disabled()) {
+ httpChannelImpl->mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ }
+ httpChannelImpl->mCaps |= NS_HTTP_STICKY_CONNECTION;
+ if (LoadAuthConnectionRestartable()) {
+ httpChannelImpl->mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
+ } else {
+ httpChannelImpl->mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
+ }
+
+ MOZ_ASSERT(mConnectionInfo);
+ httpChannelImpl->mConnectionInfo = mConnectionInfo->Clone();
+
+ // we need to store the state to skip unnecessary checks in the new channel
+ httpChannelImpl->StoreAuthRedirectedChannel(true);
+
+ // We must copy proxy and auth header to the new channel.
+ // Although the new channel can populate auth headers from auth cache, we
+ // would still like to use the auth headers generated in this channel. The
+ // main reason for doing this is that certain connection-based/stateful auth
+ // schemes like NTLM will fail when we try generate the credentials more than
+ // the number of times the server has presented us the challenge due to the
+ // usage of nonce in generating the credentials Copying the auth header will
+ // bypass generation of the credentials
+ nsAutoCString authVal;
+ if (NS_SUCCEEDED(GetRequestHeader("Proxy-Authorization"_ns, authVal))) {
+ httpChannelImpl->SetRequestHeader("Proxy-Authorization"_ns, authVal, false);
+ }
+ if (NS_SUCCEEDED(GetRequestHeader("Authorization"_ns, authVal))) {
+ httpChannelImpl->SetRequestHeader("Authorization"_ns, authVal, false);
+ }
+
+ httpChannelImpl->SetBlockAuthPrompt(LoadBlockAuthPrompt());
+ mRedirectChannel = newChannel;
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(
+ this, newChannel,
+ nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ // redirected channel will be opened after we receive the OnStopRequest
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+ mRedirectChannel = nullptr;
+ }
+
+ return rv;
+}
+nsresult nsHttpChannel::StartRedirectChannelToURI(nsIURI* upgradedURI,
+ uint32_t flags) {
+ nsresult rv = NS_OK;
+ LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(upgradedURI, flags);
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), upgradedURI,
+ redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mHTTPSSVCRecord) {
+ RefPtr<nsHttpChannel> httpChan = do_QueryObject(newChannel);
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> rec = mHTTPSSVCRecord.ref();
+ if (httpChan && rec) {
+ httpChan->SetHTTPSSVCRecord(rec.forget());
+ }
+ }
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ /* Remove the async call to ContinueAsyncRedirectChannelToURI().
+ * It is called directly by our callers upon return (to clean up
+ * the failed redirect). */
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) {
+ LOG(("nsHttpChannel::ContinueAsyncRedirectChannelToURI [this=%p]", this));
+
+ // Since we handle mAPIRedirectToURI also after on-examine-response handler
+ // rather drop it here to avoid any redirect loops, even just hypothetical.
+ mAPIRedirectToURI = nullptr;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OpenRedirectChannel(rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Cancel the channel here, the update to https had been vetoed
+ // but from the security reasons we have to discard the whole channel
+ // load.
+ Cancel(rv);
+ }
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+
+ if (NS_FAILED(rv) && !mCachePump && !mTransactionPump) {
+ // We have to manually notify the listener because there is not any pump
+ // that would call our OnStart/StopRequest after resume from waiting for
+ // the redirect callback.
+ DoNotifyListener();
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::OpenRedirectChannel(nsresult rv) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mRedirectChannel) {
+ LOG((
+ "nsHttpChannel::OpenRedirectChannel unexpected null redirect channel"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // open new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi) {
+ LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
+ nsresult rv;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags, mProxyURI,
+ mLoadInfo, getter_AddRefs(newChannel));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ rv = SetupReplacementChannel(mURI, newChannel, true, flags);
+ if (NS_FAILED(rv)) return rv;
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+ PopRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ResolveProxy() {
+ LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // using the nsIProtocolProxyService2 allows a minor performance
+ // optimization, but if an add-on has only provided the original interface
+ // then it is ok to use that version.
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ rv = pps2->AsyncResolve2(this, mProxyResolveFlags, this, nullptr,
+ getter_AddRefs(mProxyRequest));
+ } else {
+ rv = pps->AsyncResolve(static_cast<nsIChannel*>(this), mProxyResolveFlags,
+ this, nullptr, getter_AddRefs(mProxyRequest));
+ }
+
+ return rv;
+}
+
+bool nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) {
+ nsresult rv;
+ nsAutoCString buf, metaKey;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
+
+ constexpr auto prefix = "request-"_ns;
+
+ // enumerate the elements of the Vary header...
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(buf, NS_HTTP_HEADER_SEP).ToRange()) {
+ LOG(
+ ("nsHttpChannel::ResponseWouldVary [channel=%p] "
+ "processing %s\n",
+ this, nsPromiseFlatCString(token).get()));
+ //
+ // if "*", then assume response would vary. technically speaking,
+ // "Vary: header, *" is not permitted, but we allow it anyways.
+ //
+ // We hash values of cookie-headers for the following reasons:
+ //
+ // 1- cookies can be very large in size
+ //
+ // 2- cookies may contain sensitive information. (for parity with
+ // out policy of not storing Set-cookie headers in the cache
+ // meta data, we likewise do not want to store cookie headers
+ // here.)
+ //
+ if (token.EqualsLiteral("*")) {
+ return true; // if we encounter this, just get out of here
+ }
+
+ // build cache meta data key...
+ metaKey = prefix + token;
+
+ // check the last value of the given request header to see if it has
+ // since changed. if so, then indeed the cached response is invalid.
+ nsCString lastVal;
+ entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
+ LOG(
+ ("nsHttpChannel::ResponseWouldVary [channel=%p] "
+ "stored value = \"%s\"\n",
+ this, lastVal.get()));
+
+ // Look for value of "Cookie" in the request headers
+ nsHttpAtom atom = nsHttp::ResolveAtom(token);
+ nsAutoCString newVal;
+ bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, newVal));
+ if (!lastVal.IsEmpty()) {
+ // value for this header in cache, but no value in request
+ if (!hasHeader) {
+ return true; // yes - response would vary
+ }
+
+ // If this is a cookie-header, stored metadata is not
+ // the value itself but the hash. So we also hash the
+ // outgoing value here in order to compare the hashes
+ nsAutoCString hash;
+ if (atom == nsHttp::Cookie) {
+ rv = Hash(newVal.get(), hash);
+ // If hash failed, be conservative (the cached hash
+ // exists at this point) and claim response would vary
+ if (NS_FAILED(rv)) return true;
+ newVal = hash;
+
+ LOG(
+ ("nsHttpChannel::ResponseWouldVary [this=%p] "
+ "set-cookie value hashed to %s\n",
+ this, newVal.get()));
+ }
+
+ if (!newVal.Equals(lastVal)) {
+ return true; // yes, response would vary
+ }
+
+ } else if (hasHeader) { // old value is empty, but newVal is set
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// We need to have an implementation of this function just so that we can keep
+// all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
+// to set a member function ptr to a base class function.
+void nsHttpChannel::HandleAsyncAbort() {
+ HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <byte-range>
+//-----------------------------------------------------------------------------
+
+bool nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen) const {
+ bool hasContentEncoding =
+ mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);
+
+ nsAutoCString etag;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
+ bool hasWeakEtag = !etag.IsEmpty() && StringBeginsWith(etag, "W/"_ns);
+
+ return (partialLen < contentLength) &&
+ (partialLen > 0 || ignoreMissingPartialLen) && !hasContentEncoding &&
+ !hasWeakEtag && mCachedResponseHead->IsResumable() &&
+ !LoadCustomConditionalRequest() && !mCachedResponseHead->NoStore();
+}
+
+nsresult nsHttpChannel::MaybeSetupByteRangeRequest(
+ int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen) {
+ // Be pesimistic
+ StoreIsPartialRequest(false);
+
+ if (!IsResumable(partialLen, contentLength, ignoreMissingPartialLen)) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ // looks like a partial entry we can reuse; add If-Range
+ // and Range headers.
+ nsresult rv = SetupByteRangeRequest(partialLen);
+ if (NS_FAILED(rv)) {
+ // Make the request unconditional again.
+ UntieByteRangeRequest();
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::SetupByteRangeRequest(int64_t partialLen) {
+ // cached content has been found to be partial, add necessary request
+ // headers to complete cache entry.
+
+ // use strongest validator available...
+ nsAutoCString val;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (val.IsEmpty()) {
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
+ }
+ if (val.IsEmpty()) {
+ // if we hit this code it means mCachedResponseHead->IsResumable() is
+ // either broken or not being called.
+ MOZ_ASSERT_UNREACHABLE("no cache validator");
+ StoreIsPartialRequest(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ char buf[64];
+ SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);
+
+ DebugOnly<nsresult> rv{};
+ rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.SetHeader(nsHttp::If_Range, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ StoreIsPartialRequest(true);
+
+ return NS_OK;
+}
+
+void nsHttpChannel::UntieByteRangeRequest() {
+ DebugOnly<nsresult> rv{};
+ rv = mRequestHead.ClearHeader(nsHttp::Range);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.ClearHeader(nsHttp::If_Range);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult nsHttpChannel::ProcessPartialContent(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc) {
+ // ok, we've just received a 206
+ //
+ // we need to stream whatever data is in the cache out first, and then
+ // pick up whatever data is on the wire, writing it into the cache.
+
+ LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
+
+ // Check if the content-encoding we now got is different from the one we
+ // got before
+ nsAutoCString contentEncoding, cachedContentEncoding;
+ // It is possible that there is not such headers
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Content_Encoding,
+ cachedContentEncoding);
+ if (nsCRT::strcasecmp(contentEncoding.get(), cachedContentEncoding.get()) !=
+ 0) {
+ Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
+ return CallOnStartRequest();
+ }
+
+ nsresult rv;
+
+ int64_t cachedContentLength = mCachedResponseHead->ContentLength();
+ int64_t entitySize = mResponseHead->TotalEntitySize();
+
+ nsAutoCString contentRange;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
+ LOG(
+ ("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
+ "original content-length %" PRId64 ", entity-size %" PRId64
+ ", content-range %s\n",
+ this, mTransaction.get(), cachedContentLength, entitySize,
+ contentRange.get()));
+
+ if ((entitySize >= 0) && (cachedContentLength >= 0) &&
+ (entitySize != cachedContentLength)) {
+ LOG(
+ ("nsHttpChannel::ProcessPartialContent [this=%p] "
+ "206 has different total entity size than the content length "
+ "of the original partially cached entity.\n",
+ this));
+
+ mCacheEntry->AsyncDoom(nullptr);
+ Cancel(NS_ERROR_CORRUPTED_CONTENT);
+ return CallOnStartRequest();
+ }
+
+ if (LoadConcurrentCacheAccess()) {
+ // We started to read cached data sooner than its write has been done.
+ // But the concurrent write has not finished completely, so we had to
+ // do a range request. Now let the content coming from the network
+ // be presented to consumers and also stored to the cache entry.
+
+ rv = InstallCacheListener(mLogicalOffset);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // suspend the current transaction
+ rv = mTransactionPump->Suspend();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // merge any new headers with the cached response headers
+ mCachedResponseHead->UpdateHeaders(mResponseHead.get());
+
+ // update the cached response head
+ nsAutoCString head;
+ mCachedResponseHead->Flatten(head, true);
+ rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // make the cached response be the current response
+ mResponseHead = std::move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ // notify observers interested in looking at a response that has been
+ // merged with any cached headers (http-on-examine-merged-response).
+ gHttpHandler->OnExamineMergedResponse(this);
+
+ if (LoadConcurrentCacheAccess()) {
+ StoreCachedContentIsPartial(false);
+ // Leave the ConcurrentCacheAccess flag set, we want to use it
+ // to prevent duplicate OnStartRequest call on the target listener
+ // in case this channel is canceled before it gets its OnStartRequest
+ // from the http transaction.
+ return rv;
+ }
+
+ // Now we continue reading the network response.
+ // the cached content is valid, although incomplete.
+ mCachedContentIsValid = true;
+ return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
+ nsresult rv = self->ReadFromCache(false);
+ return aContinueProcessResponseFunc(self, rv);
+ });
+}
+
+nsresult nsHttpChannel::OnDoneReadingPartialCacheEntry(bool* streamDone) {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
+
+ // by default, assume we would have streamed all data or failed...
+ *streamDone = true;
+
+ // setup cache listener to append to cache entry
+ int64_t size;
+ rv = mCacheEntry->GetDataSize(&size);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = InstallCacheListener(size);
+ if (NS_FAILED(rv)) return rv;
+
+ // Entry is valid, do it now, after the output stream has been opened,
+ // otherwise when done earlier, pending readers would consider the cache
+ // entry still as partial (CacheEntry::GetDataSize would return the partial
+ // data size) and consumers would do the conditional request again.
+ rv = mCacheEntry->SetValid();
+ if (NS_FAILED(rv)) return rv;
+
+ // need to track the logical offset of the data being sent to our listener
+ mLogicalOffset = size;
+
+ // we're now completing the cached content, so we can clear this flag.
+ // this puts us in the state of a regular download.
+ StoreCachedContentIsPartial(false);
+ // The cache input stream pump is finished, we do not need it any more.
+ // (see bug 1313923)
+ mCachePump = nullptr;
+
+ // resume the transaction if it exists, otherwise the pipe contained the
+ // remaining part of the document and we've now streamed all of the data.
+ if (mTransactionPump) {
+ rv = mTransactionPump->Resume();
+ if (NS_SUCCEEDED(rv)) *streamDone = false;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("no transaction");
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <cache>
+//-----------------------------------------------------------------------------
+
+bool nsHttpChannel::ShouldBypassProcessNotModified() {
+ if (LoadCustomConditionalRequest()) {
+ LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
+ return true;
+ }
+
+ if (!mDidReval) {
+ LOG(
+ ("Server returned a 304 response even though we did not send a "
+ "conditional request"));
+ return true;
+ }
+
+ return false;
+}
+
+nsresult nsHttpChannel::ProcessNotModified(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc) {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
+
+ // Assert ShouldBypassProcessNotModified() has been checked before call to
+ // ProcessNotModified().
+ MOZ_ASSERT(!ShouldBypassProcessNotModified());
+
+ MOZ_ASSERT(mCachedResponseHead);
+ MOZ_ASSERT(mCacheEntry);
+ NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
+
+ // If the 304 response contains a Last-Modified different than the
+ // one in our cache that is pretty suspicious and is, in at least the
+ // case of bug 716840, a sign of the server having previously corrupted
+ // our cache with a bad response. Take the minor step here of just dooming
+ // that cache entry so there is a fighting chance of getting things on the
+ // right track.
+
+ nsAutoCString lastModifiedCached;
+ nsAutoCString lastModified304;
+
+ rv =
+ mCachedResponseHead->GetHeader(nsHttp::Last_Modified, lastModifiedCached);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mResponseHead->GetHeader(nsHttp::Last_Modified, lastModified304);
+ }
+
+ if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
+ LOG(
+ ("Cache Entry and 304 Last-Modified Headers Do Not Match "
+ "[%s] and [%s]\n",
+ lastModifiedCached.get(), lastModified304.get()));
+
+ mCacheEntry->AsyncDoom(nullptr);
+ Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
+ }
+
+ // merge any new headers with the cached response headers
+ mCachedResponseHead->UpdateHeaders(mResponseHead.get());
+
+ // update the cached response head
+ nsAutoCString head;
+ mCachedResponseHead->Flatten(head, true);
+ rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // make the cached response be the current response
+ mResponseHead = std::move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddCacheEntryHeaders(mCacheEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ // notify observers interested in looking at a reponse that has been
+ // merged with any cached headers
+ gHttpHandler->OnExamineMergedResponse(this);
+
+ mCachedContentIsValid = true;
+
+ // Tell other consumers the entry is OK to use
+ rv = mCacheEntry->SetValid();
+ if (NS_FAILED(rv)) return rv;
+
+ return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
+ nsresult rv = self->ReadFromCache(false);
+ return aContinueProcessResponseFunc(self, rv);
+ });
+}
+
+// Determines if a request is a byte range request for a subrange,
+// i.e. is a byte range request, but not a 0- byte range request.
+static bool IsSubRangeRequest(nsHttpRequestHead& aRequestHead) {
+ nsAutoCString byteRange;
+ if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) {
+ return false;
+ }
+ return !byteRange.EqualsLiteral("bytes=0-");
+}
+
+nsresult nsHttpChannel::OpenCacheEntry(bool isHttps) {
+ // Drop this flag here
+ StoreConcurrentCacheAccess(0);
+
+ LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
+
+ // make sure we're not abusing this function
+ MOZ_ASSERT(!mCacheEntry, "cache entry already open");
+
+ if (mRequestHead.IsPost()) {
+ // If the post id is already set then this is an attempt to replay
+ // a post transaction via the cache. Otherwise, we need a unique
+ // post id for this transaction.
+ if (mPostID == 0) mPostID = gHttpHandler->GenerateUniqueID();
+ } else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
+ // don't use the cache for other types of requests
+ return NS_OK;
+ }
+
+ return OpenCacheEntryInternal(isHttps);
+}
+
+nsresult nsHttpChannel::OpenCacheEntryInternal(bool isHttps) {
+ nsresult rv;
+
+ if (LoadResuming()) {
+ // We don't support caching for requests initiated
+ // via nsIResumableChannel.
+ return NS_OK;
+ }
+
+ // Don't cache byte range requests which are subranges, only cache 0-
+ // byte range requests.
+ if (IsSubRangeRequest(mRequestHead)) {
+ return NS_OK;
+ }
+
+ // Handle correctly WaitForCacheEntry
+ AutoCacheWaitFlags waitFlags(this);
+
+ nsAutoCString cacheKey;
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ components::CacheStorage::Service());
+ if (!cacheStorageService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ mCacheEntryURI = mURI;
+
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t cacheEntryOpenFlags;
+ bool offline = gIOService->IsOffline();
+
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
+
+ bool maybeRCWN = false;
+
+ nsAutoCString cacheControlRequestHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
+ cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+ if (cacheControlRequest.NoStore()) {
+ return NS_OK;
+ }
+
+ if (offline || (mLoadFlags & INHIBIT_CACHING) ||
+ (bc && bc->Top()->GetForceOffline())) {
+ if (BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass()) &&
+ !offline) {
+ return NS_OK;
+ }
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
+ StoreCacheEntryIsReadOnly(true);
+ } else if (BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass())) {
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
+ } else {
+ cacheEntryOpenFlags =
+ nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
+ }
+
+ // Remember the request is a custom conditional request so that we can
+ // process any 304 response correctly.
+ StoreCustomConditionalRequest(
+ mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
+ mRequestHead.HasHeader(nsHttp::If_None_Match) ||
+ mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
+ mRequestHead.HasHeader(nsHttp::If_Match) ||
+ mRequestHead.HasHeader(nsHttp::If_Range));
+
+ if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ rv = cacheStorageService->MemoryCacheStorage(
+ info, // ? choose app cache as well...
+ getter_AddRefs(cacheStorage));
+ } else if (LoadPinCacheContent()) {
+ rv = cacheStorageService->PinningCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ } else {
+ // Try to race only if we use disk cache storage
+ maybeRCWN = mRequestHead.IsSafeMethod();
+ rv = cacheStorageService->DiskCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((mClassOfService.Flags() & nsIClassOfService::Leader) ||
+ (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
+ }
+
+ // Only for backward compatibility with the old cache back end.
+ // When removed, remove the flags and related code snippets.
+ if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
+ }
+
+ if (mPostID) {
+ mCacheIdExtension.Append(nsPrintfCString("%d", mPostID));
+ }
+ if (LoadIsTRRServiceChannel()) {
+ mCacheIdExtension.Append("TRR");
+ }
+ if (mRequestHead.IsHead()) {
+ mCacheIdExtension.Append("HEAD");
+ }
+ bool isThirdParty = false;
+ if (StaticPrefs::network_fetch_cache_partition_cross_origin() &&
+ (NS_FAILED(mLoadInfo->TriggeringPrincipal()->IsThirdPartyChannel(
+ this, &isThirdParty)) ||
+ isThirdParty) &&
+ (mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH ||
+ mLoadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_XMLHTTPREQUEST ||
+ mLoadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST)) {
+ mCacheIdExtension.Append("FETCH");
+ }
+
+ mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
+ mCacheQueueSizeWhenOpen =
+ CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
+
+ if ((mNetworkTriggerDelay || StaticPrefs::network_http_rcwn_enabled()) &&
+ maybeRCWN) {
+ bool hasAltData = false;
+ uint32_t sizeInKb = 0;
+ rv = cacheStorage->GetCacheIndexEntryAttrs(
+ mCacheEntryURI, mCacheIdExtension, &hasAltData, &sizeInKb);
+
+ // We will attempt to race the network vs the cache if we've found
+ // this entry in the cache index, and it has appropriate attributes
+ // (doesn't have alt-data, and has a small size)
+ if (NS_SUCCEEDED(rv) && !hasAltData &&
+ sizeInKb < StaticPrefs::network_http_rcwn_small_resource_size_kb()) {
+ MaybeRaceCacheWithNetwork();
+ }
+ }
+
+ if (!mCacheOpenDelay) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
+ if (mNetworkTriggered) {
+ mRaceCacheWithNetwork = StaticPrefs::network_http_rcwn_enabled();
+ }
+ rv = cacheStorage->AsyncOpenURI(mCacheEntryURI, mCacheIdExtension,
+ cacheEntryOpenFlags, this);
+ } else {
+ // We pass `this` explicitly as a parameter due to the raw pointer
+ // to refcounted object in lambda analysis.
+ mCacheOpenFunc = [cacheEntryOpenFlags,
+ cacheStorage](nsHttpChannel* self) -> void {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
+ cacheStorage->AsyncOpenURI(self->mCacheEntryURI, self->mCacheIdExtension,
+ cacheEntryOpenFlags, self);
+ };
+
+ // calls nsHttpChannel::Notify after `mCacheOpenDelay` milliseconds
+ auto callback = MakeRefPtr<TimerCallback>(this);
+ NS_NewTimerWithCallback(getter_AddRefs(mCacheOpenTimer), callback,
+ mCacheOpenDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength) {
+ return nsHttp::CheckPartial(
+ aEntry, aSize, aContentLength,
+ mCachedResponseHead ? mCachedResponseHead.get() : mResponseHead.get());
+}
+
+void nsHttpChannel::UntieValidationRequest() {
+ DebugOnly<nsresult> rv{};
+ // Make the request unconditional again.
+ rv = mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.ClearHeader(nsHttp::If_None_Match);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.ClearHeader(nsHttp::ETag);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* aResult) {
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]", this,
+ entry));
+
+ mozilla::MutexAutoLock lock(mRCWNLock);
+
+ if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_FROM_NETWORK) {
+ LOG(
+ ("Not using cached response because we've already got one from the "
+ "network\n"));
+ *aResult = ENTRY_NOT_WANTED;
+
+ // Net-win indicates that mOnStartRequestTimestamp is from net.
+ int64_t savedTime =
+ (TimeStamp::Now() - mOnStartRequestTimestamp).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME,
+ savedTime);
+ return NS_OK;
+ }
+ if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_PENDING) {
+ mOnCacheEntryCheckTimestamp = TimeStamp::Now();
+ }
+
+ nsAutoCString cacheControlRequestHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
+ cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+
+ if (cacheControlRequest.NoStore()) {
+ LOG(
+ ("Not using cached response based on no-store request cache "
+ "directive\n"));
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ // Be pessimistic: assume the cache entry has no useful data.
+ *aResult = ENTRY_WANTED;
+ mCachedContentIsValid = false;
+
+ nsCString buf;
+
+ // Get the method that was used to generate the cached response
+ rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool methodWasHead = buf.EqualsLiteral("HEAD");
+ bool methodWasGet = buf.EqualsLiteral("GET");
+
+ if (methodWasHead) {
+ // The cached response does not contain an entity. We can only reuse
+ // the response if the current request is also HEAD.
+ if (!mRequestHead.IsHead()) {
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+ }
+ buf.Adopt(nullptr);
+
+ // We'll need this value in later computations...
+ uint32_t lastModifiedTime;
+ rv = entry->GetLastModified(&lastModifiedTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Determine if this is the first time that this cache entry
+ // has been accessed during this session.
+ bool fromPreviousSession =
+ (gHttpHandler->SessionStartTime() > lastModifiedTime);
+
+ // Get the cached HTTP response headers
+ mCachedResponseHead = MakeUnique<nsHttpResponseHead>();
+
+ rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry,
+ mCachedResponseHead.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isCachedRedirect = WillRedirect(*mCachedResponseHead);
+
+ // Do not return 304 responses from the cache, and also do not return
+ // any other non-redirect 3xx responses from the cache (see bug 759043).
+ NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) || isCachedRedirect,
+ NS_ERROR_ABORT);
+
+ if (mCachedResponseHead->NoStore() && LoadCacheEntryIsReadOnly()) {
+ // This prevents loading no-store responses when navigating back
+ // while the browser is set to work offline.
+ LOG((" entry loading as read-only but is no-store, set INHIBIT_CACHING"));
+ mLoadFlags |= nsIRequest::INHIBIT_CACHING;
+ }
+
+ // Don't bother to validate items that are read-only,
+ // unless they are read-only because of INHIBIT_CACHING
+ if ((LoadCacheEntryIsReadOnly() &&
+ !(mLoadFlags & nsIRequest::INHIBIT_CACHING))) {
+ int64_t size, contentLength;
+ rv = CheckPartial(entry, &size, &contentLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (contentLength != int64_t(-1) && contentLength != size) {
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ rv = OpenCacheInputStream(entry, true);
+ if (NS_SUCCEEDED(rv)) {
+ mCachedContentIsValid = true;
+ entry->MaybeMarkValid();
+ }
+ return rv;
+ }
+
+ bool wantCompleteEntry = false;
+
+ if (!methodWasHead && !isCachedRedirect) {
+ // If the cached content-length is set and it does not match the data
+ // size of the cached content, then the cached response is partial...
+ // either we need to issue a byte range request or we need to refetch
+ // the entire document.
+ //
+ // We exclude redirects from this check because we (usually) strip the
+ // entity when we store the cache entry, and even if we didn't, we
+ // always ignore a cached redirect's entity anyway. See bug 759043.
+ int64_t size, contentLength;
+ rv = CheckPartial(entry, &size, &contentLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (size == int64_t(-1)) {
+ LOG((" write is in progress"));
+ if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+ LOG(
+ (" not interested in the entry, "
+ "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
+
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ // Ignore !(size > 0) from the resumability condition
+ if (!IsResumable(size, contentLength, true)) {
+ if (IsNavigation()) {
+ LOG(
+ (" bypassing wait for the entry, "
+ "this is a navigational load"));
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ LOG(
+ (" wait for entry completion, "
+ "response is not resumable"));
+
+ wantCompleteEntry = true;
+ } else {
+ StoreConcurrentCacheAccess(1);
+ }
+ } else if (contentLength != int64_t(-1) && contentLength != size) {
+ LOG(
+ ("Cached data size does not match the Content-Length header "
+ "[content-length=%" PRId64 " size=%" PRId64 "]\n",
+ contentLength, size));
+
+ rv = MaybeSetupByteRangeRequest(size, contentLength);
+ StoreCachedContentIsPartial(NS_SUCCEEDED(rv) && LoadIsPartialRequest());
+ if (LoadCachedContentIsPartial()) {
+ rv = OpenCacheInputStream(entry, false);
+ if (NS_FAILED(rv)) {
+ UntieByteRangeRequest();
+ return rv;
+ }
+
+ *aResult = ENTRY_NEEDS_REVALIDATION;
+ return NS_OK;
+ }
+
+ if (size == 0 && LoadCacheOnlyMetadata()) {
+ // Don't break cache entry load when the entry's data size
+ // is 0 and CacheOnlyMetadata flag is set. In that case we
+ // want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
+ // also set.
+ MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
+ } else {
+ return rv;
+ }
+ }
+ }
+
+ bool isHttps = mURI->SchemeIs("https");
+
+ bool doValidation = false;
+ bool doBackgroundValidation = false;
+ bool canAddImsHeader = true;
+
+ bool isForcedValid = false;
+ entry->GetIsForcedValid(&isForcedValid);
+ auto prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Used;
+
+ bool weaklyFramed, isImmutable;
+ nsHttp::DetermineFramingAndImmutability(entry, mCachedResponseHead.get(),
+ isHttps, &weaklyFramed, &isImmutable);
+
+ // Cached entry is not the entity we request (see bug #633743)
+ if (ResponseWouldVary(entry)) {
+ LOG(("Validating based on Vary headers returning TRUE\n"));
+ canAddImsHeader = false;
+ doValidation = true;
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WouldVary;
+ } else {
+ if (mCachedResponseHead->ExpiresInPast() ||
+ mCachedResponseHead->MustValidateIfExpired()) {
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Expired;
+ }
+ doValidation = nsHttp::ValidationRequired(
+ isForcedValid, mCachedResponseHead.get(), mLoadFlags,
+ LoadAllowStaleCacheContent(), LoadForceValidateCacheContent(),
+ isImmutable, LoadCustomConditionalRequest(), mRequestHead, entry,
+ cacheControlRequest, fromPreviousSession, &doBackgroundValidation);
+ }
+
+ nsAutoCString requestedETag;
+ if (!doValidation &&
+ NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
+ (methodWasGet || methodWasHead)) {
+ nsAutoCString cachedETag;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
+ if (!cachedETag.IsEmpty() && (StringBeginsWith(cachedETag, "W/"_ns) ||
+ !requestedETag.Equals(cachedETag))) {
+ // User has defined If-Match header, if the cached entry is not
+ // matching the provided header value or the cached ETag is weak,
+ // force validation.
+ doValidation = true;
+ }
+ }
+
+ // Previous error should not be propagated.
+ rv = NS_OK;
+
+ if (!doValidation) {
+ //
+ // Check the authorization headers used to generate the cache entry.
+ // We must validate the cache entry if:
+ //
+ // 1) the cache entry was generated prior to this session w/
+ // credentials (see bug 103402).
+ // 2) the cache entry was generated w/o credentials, but would now
+ // require credentials (see bug 96705).
+ //
+ // NOTE: this does not apply to proxy authentication.
+ //
+ entry->GetMetaDataElement("auth", getter_Copies(buf));
+ doValidation =
+ (fromPreviousSession && !buf.IsEmpty()) ||
+ (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization));
+ if (doValidation) {
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Auth;
+ }
+ }
+
+ // Bug #561276: We maintain a chain of cache-keys which returns cached
+ // 3xx-responses (redirects) in order to detect cycles. If a cycle is
+ // found, ignore the cached response and hit the net. Otherwise, use
+ // the cached response and add the cache-key to the chain. Note that
+ // a limited number of redirects (cached or not) is allowed and is
+ // enforced independently of this mechanism
+ if (!doValidation && isCachedRedirect) {
+ nsAutoCString cacheKey;
+ rv = GenerateCacheKey(mPostID, cacheKey);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ auto redirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = redirectedCachekeys.ref();
+ if (!ref) {
+ ref = MakeUnique<nsTArray<nsCString>>();
+ } else if (ref->Contains(cacheKey)) {
+ doValidation = true;
+ }
+
+ LOG(("Redirection-chain %s key %s\n",
+ doValidation ? "contains" : "does not contain", cacheKey.get()));
+
+ // Append cacheKey if not in the chain already
+ if (!doValidation) {
+ ref->AppendElement(cacheKey);
+ } else {
+ prefetchStatus =
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Redirect;
+ }
+ }
+
+ mCachedContentIsValid = !doValidation;
+
+ if (isForcedValid) {
+ // Telemetry value is only useful if this was a prefetched item
+ if (!doValidation) {
+ // Could have gotten to a funky state with some of the if chain above
+ // and in nsHttp::ValidationRequired. Make sure we get it right here.
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Used;
+
+ entry->MarkForcedValidUse();
+ }
+ Telemetry::AccumulateCategorical(prefetchStatus);
+ }
+
+ if (doValidation) {
+ //
+ // now, we are definitely going to issue a HTTP request to the server.
+ // make it conditional if possible.
+ //
+ // do not attempt to validate no-store content, since servers will not
+ // expect it to be cached. (we only keep it in our cache for the
+ // purposes of back/forward, etc.)
+ //
+ // the request method MUST be either GET or HEAD (see bug 175641) and
+ // the cached response code must be < 400
+ //
+ // the cached content must not be weakly framed
+ //
+ // do not override conditional headers when consumer has defined its own
+ if (!mCachedResponseHead->NoStore() &&
+ (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
+ !LoadCustomConditionalRequest() && !weaklyFramed &&
+ (mCachedResponseHead->Status() < 400)) {
+ if (LoadConcurrentCacheAccess()) {
+ // In case of concurrent read and also validation request we
+ // must wait for the current writer to close the output stream
+ // first. Otherwise, when the writer's job would have been interrupted
+ // before all the data were downloaded, we'd have to do a range request
+ // which would be a second request in line during this channel's
+ // life-time. nsHttpChannel is not designed to do that, so rather
+ // turn off concurrent read and wait for entry's completion.
+ // Then only re-validation or range-re-validation request will go out.
+ StoreConcurrentCacheAccess(0);
+ // This will cause that OnCacheEntryCheck is called again with the same
+ // entry after the writer is done.
+ wantCompleteEntry = true;
+ } else {
+ nsAutoCString val;
+ // Add If-Modified-Since header if a Last-Modified was given
+ // and we are allowed to do this (see bugs 510359 and 269303)
+ if (canAddImsHeader) {
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
+ if (!val.IsEmpty()) {
+ rv = mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ // Add If-None-Match header if an ETag was given in the response
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (!val.IsEmpty()) {
+ rv = mRequestHead.SetHeader(nsHttp::If_None_Match, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mDidReval = true;
+ }
+ }
+ }
+
+ if (mCachedContentIsValid || mDidReval) {
+ rv = OpenCacheInputStream(entry, mCachedContentIsValid);
+ if (NS_FAILED(rv)) {
+ // If we can't get the entity then we have to act as though we
+ // don't have the cache entry.
+ if (mDidReval) {
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+ mCachedContentIsValid = false;
+ }
+ }
+
+ if (mDidReval) {
+ *aResult = ENTRY_NEEDS_REVALIDATION;
+ } else if (wantCompleteEntry) {
+ *aResult = RECHECK_AFTER_WRITE_FINISHED;
+ } else {
+ *aResult = ENTRY_WANTED;
+
+ if (doBackgroundValidation) {
+ PerformBackgroundCacheRevalidation();
+ }
+ }
+
+ if (mCachedContentIsValid) {
+ entry->MaybeMarkValid();
+ }
+
+ LOG(
+ ("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d "
+ "result=%d]\n",
+ this, doValidation, *aResult));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
+ nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ LOG(
+ ("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
+ "new=%d status=%" PRIx32 "]\n",
+ this, entry, aNew, static_cast<uint32_t>(status)));
+
+ // if the channel's already fired onStopRequest, then we should ignore
+ // this event.
+ if (!LoadIsPending()) {
+ mCacheInputStream.CloseAndRelease();
+ return NS_OK;
+ }
+
+ rv = OnCacheEntryAvailableInternal(entry, aNew, status);
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ if (mRaceCacheWithNetwork && mNetworkTriggered &&
+ mFirstResponseSource != RESPONSE_FROM_CACHE) {
+ // Ignore the error if we're racing cache with network and the cache
+ // didn't win, The network part will handle cancelation or any other
+ // error. Otherwise we could end up calling the listener twice, see
+ // bug 1397593.
+ LOG(
+ (" not calling AsyncAbort() because we're racing cache with "
+ "network"));
+ } else {
+ Unused << AsyncAbort(rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntry* entry,
+ bool aNew,
+ nsresult status) {
+ nsresult rv;
+
+ if (mCanceled) {
+ LOG(("channel was canceled [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+ return mStatus;
+ }
+
+ if (mIgnoreCacheEntry) {
+ if (!entry || aNew) {
+ // We use this flag later to decide whether to report
+ // LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent. We didn't have
+ // an usable entry, so drop the flag.
+ mIgnoreCacheEntry = false;
+ }
+ entry = nullptr;
+ status = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = OnNormalCacheEntryAvailable(entry, aNew, status);
+
+ if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We may be waiting for more callbacks...
+ if (AwaitingCacheCallbacks()) {
+ return NS_OK;
+ }
+
+ if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
+ (mDidReval || LoadCachedContentIsPartial())) ||
+ mIgnoreCacheEntry)) {
+ // We won't send the conditional request because the unconditional
+ // request was already sent (see bug 1377223).
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
+ }
+
+ if (mRaceCacheWithNetwork && mCachedContentIsValid) {
+ Unused << ReadFromCache(true);
+ }
+
+ return TriggerNetwork();
+}
+
+nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry* aEntry,
+ bool aNew,
+ nsresult aEntryStatus) {
+ StoreWaitForCacheEntry(LoadWaitForCacheEntry() & ~WAIT_FOR_CACHE_ENTRY);
+
+ if (NS_FAILED(aEntryStatus) || aNew) {
+ // Make sure this flag is dropped. It may happen the entry is doomed
+ // between OnCacheEntryCheck and OnCacheEntryAvailable.
+ mCachedContentIsValid = false;
+
+ // From the same reason remove any conditional headers added
+ // in OnCacheEntryCheck.
+ if (mDidReval) {
+ LOG((" Removing conditional request headers"));
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+
+ if (LoadCachedContentIsPartial()) {
+ LOG((" Removing byte range request headers"));
+ UntieByteRangeRequest();
+ StoreCachedContentIsPartial(false);
+ }
+
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // if this channel is only allowed to pull from the cache, then
+ // we must fail if we were unable to open a cache entry for read.
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ }
+
+ if (NS_SUCCEEDED(aEntryStatus)) {
+ mCacheEntry = aEntry;
+ StoreCacheEntryIsWriteOnly(aNew);
+
+ if (!aNew && !mAsyncOpenTime.IsNull()) {
+ // We use microseconds for IO operations. For consistency let's use
+ // microseconds here too.
+ uint32_t duration = (TimeStamp::Now() - mAsyncOpenTime).ToMicroseconds();
+ bool isSlow = false;
+ if ((mCacheOpenWithPriority &&
+ mCacheQueueSizeWhenOpen >=
+ StaticPrefs::
+ network_http_rcwn_cache_queue_priority_threshold()) ||
+ (!mCacheOpenWithPriority &&
+ mCacheQueueSizeWhenOpen >=
+ StaticPrefs::network_http_rcwn_cache_queue_normal_threshold())) {
+ isSlow = true;
+ }
+ CacheFileUtils::CachePerfStats::AddValue(
+ CacheFileUtils::CachePerfStats::ENTRY_OPEN, duration, isSlow);
+ }
+ }
+
+ return NS_OK;
+}
+
+// Generates the proper cache-key for this instance of nsHttpChannel
+nsresult nsHttpChannel::GenerateCacheKey(uint32_t postID,
+ nsACString& cacheKey) {
+ AssembleCacheKey(mSpec.get(), postID, cacheKey);
+ return NS_OK;
+}
+
+// Assembles a cache-key from the given pieces of information and |mLoadFlags|
+void nsHttpChannel::AssembleCacheKey(const char* spec, uint32_t postID,
+ nsACString& cacheKey) {
+ cacheKey.Truncate();
+
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ cacheKey.AssignLiteral("anon&");
+ }
+
+ if (postID) {
+ char buf[32];
+ SprintfLiteral(buf, "id=%x&", postID);
+ cacheKey.Append(buf);
+ }
+
+ if (!cacheKey.IsEmpty()) {
+ cacheKey.AppendLiteral("uri=");
+ }
+
+ // Strip any trailing #ref from the URL before using it as the key
+ const char* p = strchr(spec, '#');
+ if (p) {
+ cacheKey.Append(spec, p - spec);
+ } else {
+ cacheKey.Append(spec);
+ }
+}
+
+nsresult DoUpdateExpirationTime(nsHttpChannel* aSelf,
+ nsICacheEntry* aCacheEntry,
+ nsHttpResponseHead* aResponseHead,
+ uint32_t& aExpirationTime) {
+ MOZ_ASSERT(aExpirationTime == 0);
+ NS_ENSURE_TRUE(aResponseHead, NS_ERROR_FAILURE);
+
+ nsresult rv;
+
+ if (!aResponseHead->MustValidate()) {
+ // For stale-while-revalidate we use expiration time as the absolute base
+ // for calculation of the stale window absolute end time. Hence, when the
+ // entry may be served w/o revalidation, we need a non-zero value for the
+ // expiration time. Let's set it to |now|, which basicly means "expired",
+ // same as when set to 0.
+ uint32_t now = NowInSeconds();
+ aExpirationTime = now;
+
+ uint32_t freshnessLifetime = 0;
+
+ rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
+ if (NS_FAILED(rv)) return rv;
+
+ if (freshnessLifetime > 0) {
+ uint32_t currentAge = 0;
+
+ rv = aResponseHead->ComputeCurrentAge(now, aSelf->GetRequestTime(),
+ &currentAge);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("freshnessLifetime = %u, currentAge = %u\n", freshnessLifetime,
+ currentAge));
+
+ if (freshnessLifetime > currentAge) {
+ uint32_t timeRemaining = freshnessLifetime - currentAge;
+ // be careful... now + timeRemaining may overflow
+ if (now + timeRemaining < now) {
+ aExpirationTime = uint32_t(-1);
+ } else {
+ aExpirationTime = now + timeRemaining;
+ }
+ }
+ }
+ }
+
+ rv = aCacheEntry->SetExpirationTime(aExpirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+// UpdateExpirationTime is called when a new response comes in from the server.
+// It updates the stored response-time and sets the expiration time on the
+// cache entry.
+//
+// From section 13.2.4 of RFC2616, we compute expiration time as follows:
+//
+// timeRemaining = freshnessLifetime - currentAge
+// expirationTime = now + timeRemaining
+//
+nsresult nsHttpChannel::UpdateExpirationTime() {
+ uint32_t expirationTime = 0;
+ nsresult rv = DoUpdateExpirationTime(this, mCacheEntry, mResponseHead.get(),
+ expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry,
+ bool startBuffering) {
+ nsresult rv;
+
+ if (mURI->SchemeIs("https")) {
+ rv = cacheEntry->GetSecurityInfo(getter_AddRefs(mCachedSecurityInfo));
+ if (NS_FAILED(rv)) {
+ LOG(("failed to parse security-info [channel=%p, entry=%p]", this,
+ cacheEntry));
+ NS_WARNING("failed to parse security-info");
+ cacheEntry->AsyncDoom(nullptr);
+ return rv;
+ }
+
+ MOZ_ASSERT(mCachedSecurityInfo);
+ if (!mCachedSecurityInfo) {
+ LOG(
+ ("mCacheEntry->GetSecurityInfo returned success but did not "
+ "return the security info [channel=%p, entry=%p]",
+ this, cacheEntry));
+ cacheEntry->AsyncDoom(nullptr);
+ return NS_ERROR_UNEXPECTED; // XXX error code
+ }
+ }
+
+ // Keep the conditions below in sync with the conditions in ReadFromCache.
+
+ rv = NS_OK;
+
+ if (WillRedirect(*mCachedResponseHead)) {
+ // Do not even try to read the entity for a redirect because we do not
+ // return an entity to the application when we process redirects.
+ LOG(("Will skip read of cached redirect entity\n"));
+ return NS_OK;
+ }
+
+ if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
+ !LoadCachedContentIsPartial()) {
+ // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
+ // cached entity.
+ LOG(
+ ("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ return NS_OK;
+ }
+
+ // Open an input stream for the entity, so that the call to OpenInputStream
+ // happens off the main thread.
+ nsCOMPtr<nsIInputStream> stream;
+
+ // If an alternate representation was requested, try to open the alt
+ // input stream.
+ // If the entry has a "is-from-child" metadata, then only open the altdata
+ // stream if the consumer is also from child.
+ bool altDataFromChild = false;
+ {
+ nsCString value;
+ rv = cacheEntry->GetMetaDataElement("alt-data-from-child",
+ getter_Copies(value));
+ altDataFromChild = !value.IsEmpty();
+ }
+
+ nsAutoCString altDataType;
+ Unused << cacheEntry->GetAltDataType(altDataType);
+
+ nsAutoCString contentType;
+ mCachedResponseHead->ContentType(contentType);
+
+ bool foundAltData = false;
+ bool deliverAltData = true;
+ if (!LoadDisableAltDataCache() && !altDataType.IsEmpty() &&
+ !mPreferredCachedAltDataTypes.IsEmpty() &&
+ altDataFromChild == LoadAltDataForChild()) {
+ for (auto& pref : mPreferredCachedAltDataTypes) {
+ if (pref.type() == altDataType &&
+ (pref.contentType().IsEmpty() || pref.contentType() == contentType)) {
+ foundAltData = true;
+ deliverAltData =
+ pref.deliverAltData() ==
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC;
+ break;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> altData;
+ int64_t altDataSize = -1;
+ if (foundAltData) {
+ rv = cacheEntry->OpenAlternativeInputStream(altDataType,
+ getter_AddRefs(altData));
+ if (NS_SUCCEEDED(rv)) {
+ // We have succeeded.
+ mAvailableCachedAltDataType = altDataType;
+ StoreDeliveringAltData(deliverAltData);
+
+ // Set the correct data size on the channel.
+ Unused << cacheEntry->GetAltDataSize(&altDataSize);
+ mAltDataLength = altDataSize;
+
+ LOG(("Opened alt-data input stream [type=%s, size=%" PRId64
+ ", deliverAltData=%d]",
+ altDataType.get(), mAltDataLength, deliverAltData));
+
+ if (deliverAltData) {
+ stream = altData;
+ }
+ }
+ }
+
+ if (!stream) {
+ rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("Failed to open cache input stream [channel=%p, "
+ "mCacheEntry=%p]",
+ this, cacheEntry));
+ return rv;
+ }
+
+ if (startBuffering) {
+ bool nonBlocking;
+ rv = stream->IsNonBlocking(&nonBlocking);
+ if (NS_SUCCEEDED(rv) && nonBlocking) startBuffering = false;
+ }
+
+ if (!startBuffering) {
+ // Bypass wrapping the input stream for the new cache back-end since
+ // nsIStreamTransportService expects a blocking stream. Preloading of
+ // the data must be done on the level of the cache backend, internally.
+ //
+ // We do not connect the stream to the stream transport service if we
+ // have to validate the entry with the server. If we did, we would get
+ // into a race condition between the stream transport service reading
+ // the existing contents and the opening of the cache entry's output
+ // stream to write the new contents in the case where we get a non-304
+ // response.
+ LOG(
+ ("Opened cache input stream without buffering [channel=%p, "
+ "mCacheEntry=%p, stream=%p]",
+ this, cacheEntry, stream.get()));
+ mCacheInputStream.takeOver(stream);
+ return rv;
+ }
+
+ // Have the stream transport service start reading the entity on one of its
+ // background threads.
+
+ nsCOMPtr<nsITransport> transport;
+ nsCOMPtr<nsIInputStream> wrapper;
+
+ nsCOMPtr<nsIStreamTransportService> sts(
+ components::StreamTransport::Service());
+ rv = sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+ if (NS_SUCCEEDED(rv)) {
+ rv = sts->CreateInputTransport(stream, true, getter_AddRefs(transport));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ LOG(
+ ("Opened cache input stream [channel=%p, wrapper=%p, "
+ "transport=%p, stream=%p]",
+ this, wrapper.get(), transport.get(), stream.get()));
+ } else {
+ LOG(
+ ("Failed to open cache input stream [channel=%p, "
+ "wrapper=%p, transport=%p, stream=%p]",
+ this, wrapper.get(), transport.get(), stream.get()));
+
+ stream->Close();
+ return rv;
+ }
+
+ mCacheInputStream.takeOver(wrapper);
+
+ return NS_OK;
+}
+
+// Actually process the cached response that we started to handle in CheckCache
+// and/or StartBufferingCachedEntity.
+nsresult nsHttpChannel::ReadFromCache(bool alreadyMarkedValid) {
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(!mCachePump, NS_OK); // already opened
+
+ LOG(
+ ("nsHttpChannel::ReadFromCache [this=%p] "
+ "Using cached copy of: %s\n",
+ this, mSpec.get()));
+
+ // When racing the cache with the network with a timer, and we get data from
+ // the cache, we should prevent the timer from triggering a network request.
+ if (mNetworkTriggerTimer) {
+ mNetworkTriggerTimer->Cancel();
+ mNetworkTriggerTimer = nullptr;
+ }
+
+ if (mRaceCacheWithNetwork) {
+ MOZ_ASSERT(mFirstResponseSource != RESPONSE_FROM_CACHE);
+ if (mFirstResponseSource == RESPONSE_PENDING) {
+ LOG(("First response from cache\n"));
+ mFirstResponseSource = RESPONSE_FROM_CACHE;
+
+ // Cancel the transaction because we will serve the request from the cache
+ CancelNetworkRequest(NS_BINDING_ABORTED);
+ if (mTransactionPump && mSuspendCount) {
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mTransactionPump->Resume();
+ }
+ }
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+ } else {
+ MOZ_ASSERT(mFirstResponseSource == RESPONSE_FROM_NETWORK);
+ LOG(
+ ("Skipping read from cache because first response was from "
+ "network\n"));
+
+ if (!mOnCacheEntryCheckTimestamp.IsNull()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ int64_t savedTime =
+ (currentTime - mOnStartRequestTimestamp).ToMilliseconds();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);
+
+ int64_t diffTime =
+ (currentTime - mOnCacheEntryCheckTimestamp).ToMilliseconds();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_OCEC_ON_START_DIFF,
+ diffTime);
+ }
+ return NS_OK;
+ }
+ }
+
+ if (mCachedResponseHead) mResponseHead = std::move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ // if we don't already have security info, try to get it from the cache
+ // entry. there are two cases to consider here: 1) we are just reading
+ // from the cache, or 2) this may be due to a 304 not modified response,
+ // in which case we could have security info from a socket transport.
+ if (!mSecurityInfo) mSecurityInfo = mCachedSecurityInfo;
+
+ if (!alreadyMarkedValid && !LoadCachedContentIsPartial()) {
+ // We validated the entry, and we have write access to the cache, so
+ // mark the cache entry as valid in order to allow others access to
+ // this cache entry.
+ //
+ // TODO: This should be done asynchronously so we don't take the cache
+ // service lock on the main thread.
+ mCacheEntry->MaybeMarkValid();
+ }
+
+ nsresult rv;
+
+ // Keep the conditions below in sync with the conditions in
+ // StartBufferingCachedEntity.
+
+ if (WillRedirect(*mResponseHead)) {
+ // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
+ // to avoid event dispatching latency.
+ MOZ_ASSERT(!mCacheInputStream);
+ LOG(("Skipping skip read of cached redirect entity\n"));
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
+ }
+
+ if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !LoadCachedContentIsPartial()) {
+ LOG(
+ ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ MOZ_ASSERT(!mCacheInputStream);
+ // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
+ // here, to avoid event dispatching latency.
+ return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
+ }
+
+ MOZ_ASSERT(mCacheInputStream);
+ if (!mCacheInputStream) {
+ NS_ERROR(
+ "mCacheInputStream is null but we're expecting to "
+ "be able to read from it.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream, 0, 0,
+ true);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ rv = mCachePump->AsyncRead(this);
+ if (NS_FAILED(rv)) return rv;
+
+ if (LoadTimingEnabled()) mCacheReadStart = TimeStamp::Now();
+
+ uint32_t suspendCount = mSuspendCount;
+ if (LoadAsyncResumePending()) {
+ LOG(
+ (" Suspend()'ing cache pump once because of async resume pending"
+ ", sc=%u, pump=%p, this=%p",
+ suspendCount, mCachePump.get(), this));
+ ++suspendCount;
+ }
+ while (suspendCount--) {
+ mCachePump->Suspend();
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::CloseCacheEntry(bool doomOnFailure) {
+ mCacheInputStream.CloseAndRelease();
+
+ if (!mCacheEntry) return;
+
+ LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%" PRIx32
+ " CacheEntryIsWriteOnly=%x",
+ this, static_cast<uint32_t>(static_cast<nsresult>(mStatus)),
+ LoadCacheEntryIsWriteOnly()));
+
+ // If we have begun to create or replace a cache entry, and that cache
+ // entry is not complete and not resumable, then it needs to be doomed.
+ // Otherwise, CheckCache will make the mistake of thinking that the
+ // partial cache entry is complete.
+
+ bool doom = false;
+ if (LoadInitedCacheEntry()) {
+ MOZ_ASSERT(mResponseHead, "oops");
+ if (NS_FAILED(mStatus) && doomOnFailure && LoadCacheEntryIsWriteOnly() &&
+ !mResponseHead->IsResumable()) {
+ doom = true;
+ }
+ } else if (LoadCacheEntryIsWriteOnly()) {
+ doom = true;
+ }
+
+ if (doom) {
+ LOG((" dooming cache entry!!"));
+ mCacheEntry->AsyncDoom(nullptr);
+ } else {
+ // Store updated security info, makes cached EV status race less likely
+ // (see bug 1040086)
+ if (mSecurityInfo) {
+ mCacheEntry->SetSecurityInfo(mSecurityInfo);
+ }
+ }
+
+ mCachedResponseHead = nullptr;
+
+ mCachePump = nullptr;
+ // This releases the entry for other consumers to use.
+ // We call Dismiss() in case someone still keeps a reference
+ // to this entry handle.
+ mCacheEntry->Dismiss();
+ mCacheEntry = nullptr;
+ StoreCacheEntryIsWriteOnly(false);
+ StoreInitedCacheEntry(false);
+}
+
+void nsHttpChannel::MaybeCreateCacheEntryWhenRCWN() {
+ mozilla::MutexAutoLock lock(mRCWNLock);
+
+ // Create cache entry for writing only when we're racing cache with network
+ // and we don't have the entry because network won.
+ if (mCacheEntry || !mRaceCacheWithNetwork ||
+ mFirstResponseSource != RESPONSE_FROM_NETWORK ||
+ LoadCacheEntryIsReadOnly()) {
+ return;
+ }
+
+ LOG(("nsHttpChannel::MaybeCreateCacheEntryWhenRCWN [this=%p]", this));
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ components::CacheStorage::Service());
+ if (!cacheStorageService) {
+ return;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ Unused << cacheStorageService->DiskCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ if (!cacheStorage) {
+ return;
+ }
+
+ Unused << cacheStorage->OpenTruncate(mCacheEntryURI, mCacheIdExtension,
+ getter_AddRefs(mCacheEntry));
+
+ LOG((" created entry %p", mCacheEntry.get()));
+
+ if (AwaitingCacheCallbacks()) {
+ // Setting mIgnoreCacheEntry to true ensures that we won't close this
+ // write-only entry in OnCacheEntryAvailable() if this method was called
+ // after OnCacheEntryCheck().
+ mIgnoreCacheEntry = true;
+ }
+
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+ mAltDataLength = -1;
+ mCacheInputStream.CloseAndRelease();
+ mCachedContentIsValid = false;
+}
+
+// Initialize the cache entry for writing.
+// - finalize storage policy
+// - store security info
+// - update expiration time
+// - store headers and other meta data
+nsresult nsHttpChannel::InitCacheEntry() {
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
+ // if only reading, nothing to be done here.
+ if (LoadCacheEntryIsReadOnly()) return NS_OK;
+
+ // Don't cache the response again if already cached...
+ if (mCachedContentIsValid) return NS_OK;
+
+ LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n", this,
+ mCacheEntry.get()));
+
+ bool recreate = !LoadCacheEntryIsWriteOnly();
+ bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
+
+ if (!recreate && dontPersist) {
+ // If the current entry is persistent but we inhibit peristence
+ // then force recreation of the entry as memory/only.
+ rv = mCacheEntry->GetPersistent(&recreate);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (recreate) {
+ LOG(
+ (" we have a ready entry, but reading it again from the server -> "
+ "recreating cache entry\n"));
+ // clean the altData cache and reset this to avoid wrong content length
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+
+ nsCOMPtr<nsICacheEntry> currentEntry;
+ currentEntry.swap(mCacheEntry);
+ rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
+ if (NS_FAILED(rv)) {
+ LOG((" recreation failed, the response will not be cached"));
+ return NS_OK;
+ }
+
+ StoreCacheEntryIsWriteOnly(true);
+ }
+
+ // Set the expiration time for this cache entry
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ // mark this weakly framed until a response body is seen
+ mCacheEntry->SetMetaDataElement("strongly-framed", "0");
+
+ rv = AddCacheEntryHeaders(mCacheEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ StoreInitedCacheEntry(true);
+
+ // Don't perform the check when writing (doesn't make sense)
+ StoreConcurrentCacheAccess(0);
+
+ return NS_OK;
+}
+
+void nsHttpChannel::UpdateInhibitPersistentCachingFlag() {
+ // The no-store directive within the 'Cache-Control:' header indicates
+ // that we must not store the response in a persistent cache.
+ if (mResponseHead->NoStore()) mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+
+ // Only cache SSL content on disk if the pref is set
+ if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
+ mURI->SchemeIs("https")) {
+ mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+ }
+}
+
+nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ nsITransportSecurityInfo* securityInfo) {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", self));
+ // Store secure data in memory only
+ if (securityInfo) {
+ entry->SetSecurityInfo(securityInfo);
+ }
+
+ // Store the HTTP request method with the cache entry so we can distinguish
+ // for example GET and HEAD responses.
+ nsAutoCString method;
+ requestHead->Method(method);
+ rv = entry->SetMetaDataElement("request-method", method.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // Store the HTTP authorization scheme used if any...
+ rv = StoreAuthorizationMetaData(entry, requestHead);
+ if (NS_FAILED(rv)) return rv;
+
+ // Iterate over the headers listed in the Vary response header, and
+ // store the value of the corresponding request header so we can verify
+ // that it has not varied when we try to re-use the cached response at
+ // a later time. Take care to store "Cookie" headers only as hashes
+ // due to security considerations and the fact that they can be pretty
+ // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
+ //
+ // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
+ // in the cache. we could try to avoid needlessly storing the "accept"
+ // header in this case, but it doesn't seem worth the extra code to perform
+ // the check.
+ {
+ nsAutoCString buf, metaKey;
+ Unused << responseHead->GetHeader(nsHttp::Vary, buf);
+
+ constexpr auto prefix = "request-"_ns;
+
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(buf, NS_HTTP_HEADER_SEP).ToRange()) {
+ LOG(
+ ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
+ "processing %s",
+ self, nsPromiseFlatCString(token).get()));
+ if (!token.EqualsLiteral("*")) {
+ nsHttpAtom atom = nsHttp::ResolveAtom(token);
+ nsAutoCString val;
+ nsAutoCString hash;
+ if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) {
+ // If cookie-header, store a hash of the value
+ if (atom == nsHttp::Cookie) {
+ LOG(
+ ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
+ "cookie-value %s",
+ self, val.get()));
+ rv = Hash(val.get(), hash);
+ // If hash failed, store a string not very likely
+ // to be the result of subsequent hashes
+ if (NS_FAILED(rv)) {
+ val = "<hash failed>"_ns;
+ } else {
+ val = hash;
+ }
+
+ LOG((" hashed to %s\n", val.get()));
+ }
+
+ // build cache meta data key and set meta data element...
+ metaKey = prefix + token;
+ entry->SetMetaDataElement(metaKey.get(), val.get());
+ } else {
+ LOG(
+ ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
+ "clearing metadata for %s",
+ self, nsPromiseFlatCString(token).get()));
+ metaKey = prefix + token;
+ entry->SetMetaDataElement(metaKey.get(), nullptr);
+ }
+ }
+ }
+ }
+
+ // Store the received HTTP head with the cache entry as an element of
+ // the meta data.
+ nsAutoCString head;
+ responseHead->Flatten(head, true);
+ rv = entry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+ head.Truncate();
+ responseHead->FlattenNetworkOriginalHeaders(head);
+ rv = entry->SetMetaDataElement("original-response-headers", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // Indicate we have successfully finished setting metadata on the cache entry.
+ rv = entry->MetaDataReady();
+
+ return rv;
+}
+
+nsresult nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry* entry) {
+ return DoAddCacheEntryHeaders(this, entry, &mRequestHead, mResponseHead.get(),
+ mSecurityInfo);
+}
+
+inline void GetAuthType(const char* challenge, nsCString& authType) {
+ const char* p;
+
+ // get the challenge type
+ if ((p = strchr(challenge, ' ')) != nullptr) {
+ authType.Assign(challenge, p - challenge);
+ } else {
+ authType.Assign(challenge);
+ }
+}
+
+nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead) {
+ // Not applicable to proxy authorization...
+ nsAutoCString val;
+ if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) {
+ return NS_OK;
+ }
+
+ // eg. [Basic realm="wally world"]
+ nsAutoCString buf;
+ GetAuthType(val.get(), buf);
+ return entry->SetMetaDataElement("auth", buf.get());
+}
+
+// Finalize the cache entry
+// - may need to rewrite response headers if any headers changed
+// - may need to recalculate the expiration time if any headers changed
+// - called only for freshly written cache entries
+nsresult nsHttpChannel::FinalizeCacheEntry() {
+ LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
+
+ // Don't update this meta-data on 304
+ if (LoadStronglyFramed() && !mCachedContentIsValid && mCacheEntry) {
+ LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n",
+ this));
+ mCacheEntry->SetMetaDataElement("strongly-framed", "1");
+ }
+
+ if (mResponseHead && LoadResponseHeadersModified()) {
+ // Set the expiration time for this cache entry
+ nsresult rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+// Open an output stream to the cache entry and insert a listener tee into
+// the chain of response listeners.
+nsresult nsHttpChannel::InstallCacheListener(int64_t offset) {
+ nsresult rv;
+
+ LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
+
+ MOZ_ASSERT(mCacheEntry);
+ MOZ_ASSERT(LoadCacheEntryIsWriteOnly() || LoadCachedContentIsPartial() ||
+ mRaceCacheWithNetwork);
+ MOZ_ASSERT(mListener);
+
+ nsAutoCString contentEncoding, contentType;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ mResponseHead->ContentType(contentType);
+ // If the content is compressible and the server has not compressed it,
+ // mark the cache entry for compression.
+ if (contentEncoding.IsEmpty() &&
+ (contentType.EqualsLiteral(TEXT_HTML) ||
+ contentType.EqualsLiteral(TEXT_PLAIN) ||
+ contentType.EqualsLiteral(TEXT_CSS) ||
+ contentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
+ contentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
+ contentType.EqualsLiteral(TEXT_XML) ||
+ contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XHTML_XML))) {
+ rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
+ if (NS_FAILED(rv)) {
+ LOG(("unable to mark cache entry for compression"));
+ }
+ }
+
+ LOG(("Trading cache input stream for output stream [channel=%p]", this));
+
+ // We must close the input stream first because cache entries do not
+ // correctly handle having an output stream and input streams open at
+ // the same time.
+ mCacheInputStream.CloseAndRelease();
+
+ int64_t predictedSize = mResponseHead->TotalEntitySize();
+ if (predictedSize != -1) {
+ predictedSize -= offset;
+ }
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv =
+ mCacheEntry->OpenOutputStream(offset, predictedSize, getter_AddRefs(out));
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG((" entry doomed, not writing it [channel=%p]", this));
+ // Entry is already doomed.
+ // This may happen when expiration time is set to past and the entry
+ // has been removed by the background eviction logic.
+ return NS_OK;
+ }
+ if (rv == NS_ERROR_FILE_TOO_BIG) {
+ LOG((" entry would exceed max allowed size, not writing it [channel=%p]",
+ this));
+ mCacheEntry->AsyncDoom(nullptr);
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ if (LoadCacheOnlyMetadata()) {
+ LOG(("Not storing content, cacheOnlyMetadata set"));
+ // We must open and then close the output stream of the cache entry.
+ // This way we indicate the content has been written (despite with zero
+ // length) and the entry is now in the ready state with "having data".
+
+ out->Close();
+ return NS_OK;
+ }
+
+ // XXX disk cache does not support overlapped i/o yet
+#if 0
+ // Mark entry valid inorder to allow simultaneous reading...
+ rv = mCacheEntry->MarkValid();
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+ nsCOMPtr<nsIStreamListenerTee> tee =
+ do_CreateInstance(kStreamListenerTeeCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%" PRIx32, tee.get(),
+ static_cast<uint32_t>(rv)));
+ rv = tee->Init(mListener, out, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ mListener = tee;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <redirect>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChannel::SetupReplacementChannel(nsIURI* newURI,
+ nsIChannel* newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags) {
+ LOG(
+ ("nsHttpChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ if (!mEndMarkerAdded && profiler_thread_is_being_profiled_for_markers()) {
+ mEndMarkerAdded = true;
+
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ TimingStruct timings;
+ if (mTransaction) {
+ timings = mTransaction->Timings();
+ }
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+
+ RefPtr<nsIIdentChannel> newIdentChannel = do_QueryObject(newChannel);
+ uint64_t channelId = 0;
+ if (newIdentChannel) {
+ channelId = newIdentChannel->ChannelId();
+ }
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ size, mCacheDisposition, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0, &timings,
+ std::move(mSource), Some(nsDependentCString(contentType.get())), newURI,
+ redirectFlags, channelId);
+ }
+
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ newURI, newChannel, preserveMethod, redirectFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CheckRedirectLimit(redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass on the early hint observer to be able to process `103 Early Hints`
+ // responses after cross origin redirects
+ if (mEarlyHintObserver) {
+ if (RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(newChannel)) {
+ httpChannelImpl->SetEarlyHintObserver(mEarlyHintObserver);
+ }
+ mEarlyHintObserver = nullptr;
+ }
+
+ // We don't support redirection for WebTransport for now.
+ mWebTransportSessionEventListener = nullptr;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel) return NS_OK; // no other options to set
+
+ // convey the ApplyConversion flag (bug 91862)
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel) encodedChannel->SetApplyConversion(LoadApplyConversion());
+
+ // transfer the resume information
+ if (LoadResuming()) {
+ nsCOMPtr<nsIResumableChannel> resumableChannel(
+ do_QueryInterface(newChannel));
+ if (!resumableChannel) {
+ NS_WARNING(
+ "Got asked to resume, but redirected to non-resumable channel!");
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+ resumableChannel->ResumeAt(mStartPos, mEntityID);
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(newChannel, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ TimeStamp timestamp;
+ rv = GetNavigationStartTimeStamp(&timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (timestamp) {
+ Unused << internalChannel->SetNavigationStartTimeStamp(timestamp);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) {
+ LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", this,
+ redirectType));
+
+ nsresult rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ nsAutoCString location;
+
+ // if a location header was not given, then we can't perform the redirect,
+ // so just carry on as though this were a normal response.
+ if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we were told to not follow redirects automatically, then again
+ // carry on as though this were a normal response.
+ if (mLoadInfo->GetDontFollowRedirects()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
+ locationBuf)) {
+ location = locationBuf;
+ }
+
+ mRedirectType = redirectType;
+
+ LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
+ uint32_t(mRedirectionLimit)));
+
+ rv = CreateNewURI(location.get(), getter_AddRefs(mRedirectURI));
+
+ if (NS_FAILED(rv)) {
+ LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (!StaticPrefs::network_allow_redirect_to_data() &&
+ !mLoadInfo->GetAllowInsecureRedirectToDataURI() &&
+ mRedirectURI->SchemeIs("data")) {
+ LOG(("Invalid data URI for redirect!"));
+ nsContentSecurityManager::ReportBlockedDataURI(mRedirectURI, mLoadInfo,
+ true);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Perform the URL query string stripping for redirects. We will only strip
+ // the query string if it is redirecting to a third-party URI in the top
+ // level.
+ if (StaticPrefs::privacy_query_stripping_redirect()) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ bool isThirdPartyRedirectURI = true;
+ thirdPartyUtil->IsThirdPartyURI(mURI, mRedirectURI,
+ &isThirdPartyRedirectURI);
+ if (isThirdPartyRedirectURI && mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::Redirect);
+
+ nsCOMPtr<nsIPrincipal> prin;
+ ContentBlockingAllowList::RecomputePrincipal(
+ mRedirectURI, mLoadInfo->GetOriginAttributes(), getter_AddRefs(prin));
+
+ bool isRedirectURIInAllowList = false;
+ if (prin) {
+ ContentBlockingAllowList::Check(prin, mPrivateBrowsing,
+ isRedirectURIInAllowList);
+ }
+
+ if (!isRedirectURIInAllowList) {
+ nsCOMPtr<nsIURI> strippedURI;
+
+ nsCOMPtr<nsIURLQueryStringStripper> queryStripper =
+ components::URLQueryStringStripper::Service(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numStripped;
+
+ rv = queryStripper->Strip(mRedirectURI, mPrivateBrowsing,
+ getter_AddRefs(strippedURI), &numStripped);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numStripped) {
+ mUnstrippedRedirectURI = mRedirectURI;
+ mRedirectURI = strippedURI;
+
+ // Record telemetry, but only if we stripped any query params.
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::StripForRedirect);
+ Telemetry::Accumulate(Telemetry::QUERY_STRIPPING_PARAM_COUNT,
+ numStripped);
+ }
+ }
+ }
+ }
+
+ if (NS_WARN_IF(!mRedirectURI)) {
+ LOG(("Invalid redirect URI after performaing query string stripping"));
+ return NS_ERROR_FAILURE;
+ }
+
+ return ContinueProcessRedirectionAfterFallback(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) {
+ // Kill the current cache entry if we are redirecting
+ // back to ourself.
+ bool redirectingBackToSameURI = false;
+ if (mCacheEntry && LoadCacheEntryIsWriteOnly() &&
+ NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
+ redirectingBackToSameURI) {
+ mCacheEntry->AsyncDoom(nullptr);
+ }
+
+ // move the reference of the old location to the new one if the new
+ // one has none.
+ PropagateReferenceIfNeeded(mURI, mRedirectURI);
+
+ bool rewriteToGET =
+ ShouldRewriteRedirectToGET(mRedirectType, mRequestHead.ParsedMethod());
+
+ // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
+ if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
+ rv = PromptTempRedirect();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ uint32_t redirectFlags;
+ if (nsHttp::IsPermanentRedirect(mRedirectType)) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ } else {
+ redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+ }
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
+
+ // Propagate the unstripped redirect URI.
+ redirectLoadInfo->SetUnstrippedURI(mUnstrippedRedirectURI);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), mRedirectURI,
+ redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET,
+ redirectFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // verify that this is a legal redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueProcessRedirection(nsresult rv) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%" PRIx32 ",this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // XXX we used to talk directly with the script security manager, but that
+ // should really be handled by the event sink implementation.
+
+ // begin loading the new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ LOG((" new channel AsyncOpen returned %" PRIX32, static_cast<uint32_t>(rv)));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <auth>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
+ LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
+
+ // setting mAuthRetryPending flag and resuming the transaction
+ // triggers process of throwing away the unauthenticated data already
+ // coming from the network
+ mIsAuthChannel = true;
+ mAuthRetryPending = true;
+ StoreProxyAuthPending(false);
+ LOG(("Resuming the transaction, we got credentials from user"));
+ if (mTransactionPump) {
+ mTransactionPump->Resume();
+ }
+
+ if (StaticPrefs::network_auth_use_redirect_for_retries()) {
+ return CallOrWaitForResume(
+ [](auto* self) { return self->RedirectToNewChannelForAuthRetry(); });
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) {
+ LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
+ MOZ_ASSERT(mAuthRetryPending, "OnAuthCancelled should not be called twice");
+
+ if (mTransactionPump) {
+ // If the channel is trying to authenticate to a proxy and
+ // that was canceled we cannot show the http response body
+ // from the 40x as that might mislead the user into thinking
+ // it was a end host response instead of a proxy reponse.
+ // This must check explicitly whether a proxy auth was being done
+ // because we do want to show the content if this is an error from
+ // the origin server.
+ if (LoadProxyAuthPending()) Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
+
+ // Make sure to process security headers before calling CallOnStartRequest.
+ nsresult rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ // ensure call of OnStartRequest of the current listener here,
+ // it would not be called otherwise at all
+ rv = CallOnStartRequest();
+
+ // drop mAuthRetryPending flag and resume the transaction
+ // this resumes load of the unauthenticated content data (which
+ // may have been canceled if we don't want to show it)
+ mAuthRetryPending = false;
+ LOG(("Resuming the transaction, user cancelled the auth dialog"));
+ mTransactionPump->Resume();
+
+ if (NS_FAILED(rv)) mTransactionPump->Cancel(rv);
+ }
+
+ StoreProxyAuthPending(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::CloseStickyConnection() {
+ LOG(("nsHttpChannel::CloseStickyConnection this=%p", this));
+
+ // Require we are between OnStartRequest and OnStopRequest, because
+ // what we do here takes effect in OnStopRequest (not reusing the
+ // connection for next authentication round).
+ if (!LoadIsPending()) {
+ LOG((" channel not pending"));
+ NS_ERROR(
+ "CloseStickyConnection not called before OnStopRequest, won't have any "
+ "effect");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mTransaction);
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!(mCaps & NS_HTTP_STICKY_CONNECTION ||
+ mTransaction->HasStickyConnection())) {
+ LOG((" not sticky"));
+ return NS_OK;
+ }
+
+ mTransaction->DontReuseConnection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable) {
+ LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d", this,
+ aRestartable));
+ StoreAuthConnectionRestartable(aRestartable);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
+NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
+ NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIRaceCacheWithNetwork)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP nsHttpChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsHttpChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::CancelWithReason(nsresult aStatus, const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Cancel(nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // We should never have a pump open while a CORS preflight is in progress.
+ MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+#ifdef DEBUG
+ // We want to perform this check only when the chanel is being cancelled the
+ // first time with a URL classifier blocking error code. If mStatus is
+ // already set to such an error code then Cancel() may be called for some
+ // other reason, for example because we've received notification about our
+ // parent process side channel being canceled, in which case we cannot expect
+ // that CancelByURLClassifier() would have handled this case.
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) &&
+ !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(mStatus)) {
+ MOZ_CRASH_UNSAFE_PRINTF("Blocking classifier error %" PRIx32
+ " need to be handled by CancelByURLClassifier()",
+ static_cast<uint32_t>(status));
+ }
+#endif
+
+ LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 ", reason=%s]\n", this,
+ static_cast<uint32_t>(status), mCanceledReason.get()));
+ MOZ_ASSERT_IF(!(mConnectionInfo && mConnectionInfo->UsingConnect()) &&
+ NS_SUCCEEDED(mStatus),
+ !AllowedErrorForHTTPSRRFallback(status));
+
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+
+ LogCallingScriptLocation(this);
+
+ if (LoadWaitingForRedirectCallback()) {
+ LOG(("channel canceled during wait for redirect callback"));
+ }
+
+ return CancelInternal(status);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::CancelByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ MOZ_ASSERT(NS_IsMainThread());
+ // We should never have a pump open while a CORS preflight is in progress.
+ MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+
+ LOG(("nsHttpChannel::CancelByURLClassifier [this=%p]\n", this));
+
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+
+ // We are being canceled by the channel classifier because of tracking
+ // protection, but we haven't yet had a chance to dispatch the
+ // "http-on-modify-request" notifications yet (this would normally be
+ // done in PrepareToConnect()). So do that now, before proceeding to
+ // cancel.
+ //
+ // Note that running these observers can itself result in the channel
+ // being canceled. In that case, we accept that cancelation code as
+ // the cause of the cancelation, as if the classification of the channel
+ // would have occurred past this point!
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ // Check if request was cancelled during on-modify-request
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume in Cancel [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ StoreChannelClassifierCancellationPending(1);
+ mCallOnResume = [aErrorCode](nsHttpChannel* self) {
+ self->HandleContinueCancellingByURLClassifier(aErrorCode);
+ return NS_OK;
+ };
+ return NS_OK;
+ }
+
+ // Check to see if we should redirect this channel elsewhere by
+ // nsIHttpChannel.redirectTo API request
+ if (mAPIRedirectToURI) {
+ StoreChannelClassifierCancellationPending(1);
+ return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+ }
+
+ return CancelInternal(aErrorCode);
+}
+
+void nsHttpChannel::ContinueCancellingByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ MOZ_ASSERT(NS_IsMainThread());
+ // We should never have a pump open while a CORS preflight is in progress.
+ MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+
+ LOG(("nsHttpChannel::ContinueCancellingByURLClassifier [this=%p]\n", this));
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return;
+ }
+
+ // Check to see if we should redirect this channel elsewhere by
+ // nsIHttpChannel.redirectTo API request
+ if (mAPIRedirectToURI) {
+ Unused << AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+ return;
+ }
+
+ Unused << CancelInternal(aErrorCode);
+}
+
+nsresult nsHttpChannel::CancelInternal(nsresult status) {
+ LOG(("nsHttpChannel::CancelInternal [this=%p]\n", this));
+ bool channelClassifierCancellationPending =
+ !!LoadChannelClassifierCancellationPending();
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
+ StoreChannelClassifierCancellationPending(0);
+ }
+
+ // We don't want the content process to see any header values
+ // when the request is blocked by ORB
+ if (mChannelBlockedByOpaqueResponse && mCachedOpaqueResponseBlockingPref) {
+ mResponseHead->ClearHeaders();
+ }
+
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+ mCanceled = true;
+ mStatus = NS_FAILED(status) ? status : NS_ERROR_ABORT;
+
+ if (mLastStatusReported && !mEndMarkerAdded &&
+ profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ // mLastStatusReported can be null if Cancel is called before we added the
+ // start marker.
+ mEndMarkerAdded = true;
+
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_CANCEL,
+ mLastStatusReported, TimeStamp::Now(), size, mCacheDisposition,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource));
+ }
+
+ // If we don't have mTransactionPump and mCachePump, we need to call
+ // AsyncAbort to make sure this channel's listener got notified.
+ bool needAsyncAbort = !mTransactionPump && !mCachePump;
+
+ if (mProxyRequest) mProxyRequest->Cancel(status);
+ CancelNetworkRequest(status);
+ mCacheInputStream.CloseAndRelease();
+ if (mCachePump) mCachePump->Cancel(status);
+ if (mAuthProvider) mAuthProvider->Cancel(status);
+ if (mPreflightChannel) mPreflightChannel->Cancel(status);
+ if (mRequestContext && mOnTailUnblock) {
+ mOnTailUnblock = nullptr;
+ mRequestContext->CancelTailedRequest(this);
+ CloseCacheEntry(false);
+ needAsyncAbort = false;
+ Unused << AsyncAbort(status);
+ } else if (channelClassifierCancellationPending) {
+ // If mCallOnResume is not null here, it's set in
+ // nsHttpChannel::CancelByURLClassifier. We can override mCallOnResume since
+ // mCanceled is true and nsHttpChannel::ContinueCancellingByURLClassifier
+ // does nothing.
+ if (mCallOnResume) {
+ mCallOnResume = nullptr;
+ }
+ // If we're coming from an asynchronous path when canceling a channel due
+ // to safe-browsing protection, we need to AsyncAbort the channel now.
+ needAsyncAbort = false;
+ Unused << AsyncAbort(status);
+ }
+
+ // If we already have mCallOnResume, AsyncAbort will be called in
+ // ResumeInternal.
+ if (needAsyncAbort && !mCallOnResume && !mSuspendCount) {
+ LOG(("nsHttpChannel::CancelInternal do AsyncAbort [this=%p]\n", this));
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(status);
+ }
+ return NS_OK;
+}
+
+void nsHttpChannel::CancelNetworkRequest(nsresult aStatus) {
+ if (mTransaction) {
+ nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
+ if (NS_FAILED(rv)) {
+ LOG(("failed to cancel the transaction\n"));
+ }
+ }
+ if (mTransactionPump) mTransactionPump->Cancel(aStatus);
+
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Suspend() {
+ NS_ENSURE_TRUE(LoadIsPending(), NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
+ LogCallingScriptLocation(this);
+
+ ++mSuspendCount;
+
+ if (mSuspendCount == 1) {
+ mSuspendTimestamp = TimeStamp::NowLoRes();
+ }
+
+ nsresult rvTransaction = NS_OK;
+ if (mTransactionPump) {
+ rvTransaction = mTransactionPump->Suspend();
+ }
+ nsresult rvCache = NS_OK;
+ if (mCachePump) {
+ rvCache = mCachePump->Suspend();
+ }
+
+ return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Resume() {
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
+ LogCallingScriptLocation(this);
+
+ if (--mSuspendCount == 0) {
+ mSuspendTotalTime +=
+ (TimeStamp::NowLoRes() - mSuspendTimestamp).ToMilliseconds();
+
+ if (mCallOnResume) {
+ // Resume the interrupted procedure first, then resume
+ // the pump to continue process the input stream.
+ // Any newly created pump MUST be suspended to prevent calling
+ // its OnStartRequest before OnStopRequest of any pre-existing
+ // pump. AsyncResumePending ensures that.
+ MOZ_ASSERT(!LoadAsyncResumePending());
+ StoreAsyncResumePending(1);
+
+ std::function<nsresult(nsHttpChannel*)> callOnResume = nullptr;
+ std::swap(callOnResume, mCallOnResume);
+
+ RefPtr<nsHttpChannel> self(this);
+ nsCOMPtr<nsIRequest> transactionPump = mTransactionPump;
+ RefPtr<nsInputStreamPump> cachePump = mCachePump;
+
+ nsresult rv = NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "nsHttpChannel::CallOnResume",
+ [callOnResume{std::move(callOnResume)}, self{std::move(self)},
+ transactionPump{std::move(transactionPump)},
+ cachePump{std::move(cachePump)}]() {
+ MOZ_ASSERT(self->LoadAsyncResumePending());
+ nsresult rv = self->CallOrWaitForResume(callOnResume);
+ if (NS_FAILED(rv)) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(rv);
+ }
+ MOZ_ASSERT(self->LoadAsyncResumePending());
+
+ self->StoreAsyncResumePending(0);
+
+ // And now actually resume the previously existing pumps.
+ if (transactionPump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume resuming previous transaction "
+ "pump %p, this=%p",
+ transactionPump.get(), self.get()));
+ transactionPump->Resume();
+ }
+ if (cachePump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume resuming previous cache pump "
+ "%p, this=%p",
+ cachePump.get(), self.get()));
+ cachePump->Resume();
+ }
+
+ // Any newly created pumps were suspended once because of
+ // AsyncResumePending. Problem is that the stream listener
+ // notification is already pending in the queue right now, because
+ // AsyncRead doesn't (regardless if called after Suspend) respect
+ // the suspend coutner and the right order would not be preserved.
+ // Hence, we do another dispatch round to actually Resume after
+ // the notification from the original pump.
+ if (transactionPump != self->mTransactionPump &&
+ self->mTransactionPump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume async-resuming new "
+ "transaction "
+ "pump %p, this=%p",
+ self->mTransactionPump.get(), self.get()));
+
+ nsCOMPtr<nsIRequest> pump = self->mTransactionPump;
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "nsHttpChannel::CallOnResume new transaction",
+ [pump{std::move(pump)}]() { pump->Resume(); }));
+ }
+ if (cachePump != self->mCachePump && self->mCachePump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume async-resuming new cache pump "
+ "%p, this=%p",
+ self->mCachePump.get(), self.get()));
+
+ RefPtr<nsInputStreamPump> pump = self->mCachePump;
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "nsHttpChannel::CallOnResume new pump",
+ [pump{std::move(pump)}]() { pump->Resume(); }));
+ }
+ }));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+ }
+ }
+
+ nsresult rvTransaction = NS_OK;
+ if (mTransactionPump) {
+ rvTransaction = mTransactionPump->Resume();
+ }
+
+ nsresult rvCache = NS_OK;
+ if (mCachePump) {
+ rvCache = mCachePump->Resume();
+ }
+
+ return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetSecurityInfo(nsITransportSecurityInfo** securityInfo) {
+ NS_ENSURE_ARG_POINTER(securityInfo);
+ *securityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+// If any of the functions that AsyncOpen calls returns immediately an error
+// AsyncAbort(which calls onStart/onStopRequest) does not need to be call.
+// To be sure that they are not call ReleaseListeners() is called.
+// If AsyncOpen returns NS_OK, after that point AsyncAbort must be called on
+// any error.
+NS_IMETHODIMP
+nsHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
+ mOpenerCallingScriptLocation = CallingScriptLocationString();
+ LogCallingScriptLocation(this, mOpenerCallingScriptLocation);
+ NS_CompareLoadInfoAndLoadContext(this);
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ if (mCanceled) {
+ ReleaseListeners();
+ return NS_FAILED(mStatus) ? mStatus : NS_ERROR_FAILURE;
+ }
+
+ if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHttpHandler->Active()) {
+ LOG((" after HTTP shutdown..."));
+ ReleaseListeners();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ // If no one called SetLoadGroup or SetNotificationCallbacks, the private
+ // state has not been updated on PrivateBrowsingChannel (which we derive
+ // from) Same if the loadinfo has changed since the creation of the channel.
+ // Hence, we have to call UpdatePrivateBrowsing() here
+ UpdatePrivateBrowsing();
+
+ AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(this);
+
+ // Recalculate the default userAgent header after the AntiTrackingInfo gets
+ // updated because we can only know whether the site is exempted from
+ // fingerprinting protection after we have the AntiTracking Info.
+ //
+ // Note that we don't recalculate the header if it has been modified since the
+ // channel was created because we want to preserve the modified header.
+ if (!LoadIsUserAgentHeaderModified()) {
+ rv = mRequestHead.SetHeader(
+ nsHttp::User_Agent,
+ gHttpHandler->UserAgent(nsContentUtils::ShouldResistFingerprinting(
+ this, RFPTarget::HttpUserAgent)),
+ false, nsHttpHeaderArray::eVarietyRequestEnforceDefault);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (WaitingForTailUnblock()) {
+ // This channel is marked as Tail and is part of a request context
+ // that has positive number of non-tailed requestst, hence this channel
+ // has been put to a queue.
+ // When tail is unblocked, OnTailUnblock on this channel will be called
+ // to continue AsyncOpen.
+ mListener = listener;
+ MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
+ mOnTailUnblock = &nsHttpChannel::AsyncOpenOnTailUnblock;
+
+ LOG((" put on hold until tail is unblocked"));
+ return NS_OK;
+ }
+
+ // Remember the cookie header that was set, if any
+ nsAutoCString cookieHeader;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
+ mUserSetCookieHeader = cookieHeader;
+ }
+
+ // Set user agent override, do so before OnOpeningRequest notification
+ // since we want to allow consumers of that notification change or remove
+ // the User-Agent request header.
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ // After we notify any observers (on-opening-request, loadGroup, etc) we
+ // must return NS_OK and return any errors asynchronously via
+ // OnStart/OnStopRequest. Observers may add a reference to the channel
+ // and expect to get OnStopRequest so they know when to drop the reference,
+ // etc.
+
+ // notify "http-on-opening-request" observers, but not if this is a redirect
+ if (!(mLoadFlags & LOAD_REPLACE)) {
+ gHttpHandler->OnOpeningRequest(this);
+ }
+
+ StoreIsPending(true);
+ StoreWasOpened(true);
+
+ mListener = listener;
+
+ if (nsIOService::UseSocketProcess() &&
+ !gIOService->IsSocketProcessLaunchComplete()) {
+ RefPtr<nsHttpChannel> self = this;
+ gIOService->CallOrWaitForSocketProcess(
+ [self]() { self->AsyncOpenFinal(TimeStamp::Now()); });
+ return NS_OK;
+ }
+
+ AsyncOpenFinal(TimeStamp::Now());
+
+ return NS_OK;
+}
+
+void nsHttpChannel::AsyncOpenFinal(TimeStamp aTimeStamp) {
+ // We save this timestamp from outside of the if block in case we enable the
+ // profiler after AsyncOpen().
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, mCacheDisposition,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+
+ // Added due to PauseTask/DelayHttpChannel
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ // record asyncopen time unconditionally and clear it if we
+ // don't want it after OnModifyRequest() weighs in. But waiting for
+ // that to complete would mean we don't include proxy resolution in the
+ // timing.
+ if (!LoadAsyncOpenTimeOverriden()) {
+ mAsyncOpenTime = aTimeStamp;
+ }
+
+ // Remember we have Authorization header set here. We need to check on it
+ // just once and early, AsyncOpen is the best place.
+ StoreCustomAuthHeader(mRequestHead.HasHeader(nsHttp::Authorization));
+
+ bool willCallback = false;
+ // We are about to do an async lookup to check if the URI is a tracker. If
+ // yes, this channel will be canceled by channel classifier. Chances are the
+ // lookup is not needed so CheckIsTrackerWithLocalTable() will return an
+ // error and then we can MaybeResolveProxyAndBeginConnect() right away.
+ // We skip the check in case this is an internal redirected channel
+ if (!LoadAuthRedirectedChannel() && NS_ShouldClassifyChannel(this)) {
+ RefPtr<nsHttpChannel> self = this;
+ willCallback = NS_SUCCEEDED(
+ AsyncUrlChannelClassifier::CheckChannel(this, [self]() -> void {
+ nsCOMPtr<nsIURI> uri;
+ self->GetURI(getter_AddRefs(uri));
+ MOZ_ASSERT(uri);
+
+ // Finish the AntiTracking Heuristic before
+ // MaybeResolveProxyAndBeginConnect().
+ FinishAntiTrackingRedirectHeuristic(self, uri);
+
+ self->MaybeResolveProxyAndBeginConnect();
+ }));
+ }
+
+ if (!willCallback) {
+ // We can do MaybeResolveProxyAndBeginConnect immediately if
+ // CheckIsTrackerWithLocalTable is failed. Note that we don't need to
+ // handle the failure because BeginConnect() will return synchronously and
+ // the caller will be responsible for handling it.
+ MaybeResolveProxyAndBeginConnect();
+ }
+}
+
+void nsHttpChannel::MaybeResolveProxyAndBeginConnect() {
+ nsresult rv;
+
+ // The common case for HTTP channels is to begin proxy resolution and return
+ // at this point. The only time we know mProxyInfo already is if we're
+ // proxying a non-http protocol like ftp. We don't need to discover proxy
+ // settings if we are never going to make a network connection.
+ if (!mProxyInfo &&
+ !(mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) &&
+ !BypassProxy() && NS_SUCCEEDED(ResolveProxy())) {
+ return;
+ }
+
+ if (!gHttpHandler->Active()) {
+ LOG(
+ ("nsHttpChannel::MaybeResolveProxyAndBeginConnect [this=%p] "
+ "Handler no longer active.\n",
+ this));
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ rv = BeginConnect();
+ }
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+nsresult nsHttpChannel::AsyncOpenOnTailUnblock() {
+ return AsyncOpen(mListener);
+}
+
+already_AddRefed<nsChannelClassifier>
+nsHttpChannel::GetOrCreateChannelClassifier() {
+ if (!mChannelClassifier) {
+ mChannelClassifier = new nsChannelClassifier(this);
+ LOG(("nsHttpChannel [%p] created nsChannelClassifier [%p]\n", this,
+ mChannelClassifier.get()));
+ }
+
+ RefPtr<nsChannelClassifier> classifier = mChannelClassifier;
+ return classifier.forget();
+}
+
+uint16_t nsHttpChannel::GetProxyDNSStrategy() {
+ // This function currently only supports returning DNS_PREFETCH_ORIGIN.
+ // Support for the rest of the DNS_* flags will be added later.
+
+ if (!mProxyInfo) {
+ return DNS_PREFETCH_ORIGIN;
+ }
+
+ nsAutoCString type;
+ mProxyInfo->GetType(type);
+
+ if (!StaticPrefs::network_proxy_socks_remote_dns()) {
+ if (type.EqualsLiteral("socks")) {
+ return DNS_PREFETCH_ORIGIN;
+ }
+ }
+
+ return 0;
+}
+
+// BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
+// functions that called BeginConnect if needed. Only
+// MaybeResolveProxyAndBeginConnect and OnProxyAvailable ever call
+// BeginConnect.
+nsresult nsHttpChannel::BeginConnect() {
+ LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
+ nsresult rv;
+
+ // It is the caller's responsibility to not call us late in shutdown.
+ MOZ_ASSERT(gHttpHandler->Active());
+
+ // Construct connection info object
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+ bool isHttps = mURI->SchemeIs("https");
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Just a warning here because some nsIURIs do not implement this method.
+ Unused << NS_WARN_IF(NS_FAILED(mURI->GetUsername(mUsername)));
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) {
+ rv = NS_ERROR_MALFORMED_URI;
+ return rv;
+ }
+ LOG(("host=%s port=%d\n", host.get(), port));
+ LOG(("uri=%s\n", mSpec.get()));
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (mProxyInfo) proxyInfo = do_QueryInterface(mProxyInfo);
+
+ if (mCaps & NS_HTTP_CONNECT_ONLY) {
+ if (!proxyInfo) {
+ LOG(("return failure: no proxy for connect-only channel\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!proxyInfo->IsHTTP() && !proxyInfo->IsHTTPS()) {
+ LOG(("return failure: non-http proxy for connect-only channel\n"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mRequestHead.SetHTTPS(isHttps);
+ mRequestHead.SetOrigin(scheme, host, port);
+
+ SetOriginHeader();
+ SetDoNotTrack();
+ SetGlobalPrivacyControl();
+
+ OriginAttributes originAttributes;
+ // Regular principal in case we have a proxy.
+ if (proxyInfo &&
+ !StaticPrefs::privacy_partition_network_state_connection_with_proxy()) {
+ StoragePrincipalHelper::GetOriginAttributes(
+ this, originAttributes, StoragePrincipalHelper::eRegularPrincipal);
+ } else {
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ this, originAttributes);
+ }
+
+ // Adjust mCaps according to our request headers:
+ // - If "Connection: close" is set as a request header, then do not bother
+ // trying to establish a keep-alive connection.
+ if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close")) {
+ mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE);
+ StoreAllowHttp3(false);
+ }
+
+ gHttpHandler->MaybeAddAltSvcForTesting(mURI, mUsername, mPrivateBrowsing,
+ mCallbacks, originAttributes);
+
+ RefPtr<nsHttpConnectionInfo> connInfo;
+#ifdef FUZZING
+ if (StaticPrefs::fuzzing_necko_http3()) {
+ connInfo =
+ new nsHttpConnectionInfo(host, port, "h3"_ns, mUsername, proxyInfo,
+ originAttributes, host, port, true);
+ } else {
+#endif
+ if (mWebTransportSessionEventListener) {
+ connInfo =
+ new nsHttpConnectionInfo(host, port, "h3"_ns, mUsername, proxyInfo,
+ originAttributes, isHttps, true, true);
+ } else {
+ connInfo = new nsHttpConnectionInfo(host, port, ""_ns, mUsername,
+ proxyInfo, originAttributes, isHttps);
+ }
+#ifdef FUZZING
+ }
+#endif
+
+ bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo);
+
+ bool http3Allowed = Http3Allowed();
+ if (!http3Allowed) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+
+ RefPtr<AltSvcMapping> mapping;
+ if (!mConnectionInfo && LoadAllowAltSvc() && // per channel
+ !mWebTransportSessionEventListener && (http2Allowed || http3Allowed) &&
+ !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
+ AltSvcMapping::AcceptableProxy(proxyInfo) &&
+ (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
+ (mapping = gHttpHandler->GetAltServiceMapping(
+ scheme, host, port, mPrivateBrowsing, originAttributes, http2Allowed,
+ http3Allowed))) {
+ LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n", this,
+ scheme.get(), mapping->AlternateHost().get(), mapping->AlternatePort(),
+ mapping->HashKey().get()));
+
+ if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
+ nsAutoCString altUsedLine(mapping->AlternateHost());
+ bool defaultPort =
+ mapping->AlternatePort() ==
+ (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
+ if (!defaultPort) {
+ altUsedLine.AppendLiteral(":");
+ altUsedLine.AppendInt(mapping->AlternatePort());
+ }
+ // Like what we did for 'Authorization' header, we need to do the same for
+ // 'Alt-Used' for avoiding this header being shown in the ServiceWorker
+ // FetchEvent.
+ Unused << mRequestHead.ClearHeader(nsHttp::Alternate_Service_Used);
+ rv = mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine,
+ false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService && !host.Equals(mapping->AlternateHost())) {
+ nsAutoString message(u"Alternate Service Mapping found: "_ns);
+ AppendASCIItoUTF16(scheme, message);
+ message.AppendLiteral(u"://");
+ AppendASCIItoUTF16(host, message);
+ message.AppendLiteral(u":");
+ message.AppendInt(port);
+ message.AppendLiteral(u" to ");
+ AppendASCIItoUTF16(scheme, message);
+ message.AppendLiteral(u"://");
+ AppendASCIItoUTF16(mapping->AlternateHost(), message);
+ message.AppendLiteral(u":");
+ message.AppendInt(mapping->AlternatePort());
+ consoleService->LogStringMessage(message.get());
+ }
+
+ LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
+ mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo,
+ originAttributes);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+ } else if (mConnectionInfo) {
+ LOG(("nsHttpChannel %p Using channel supplied connection info", this));
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ } else {
+ LOG(("nsHttpChannel %p Using default connection info", this));
+
+ mConnectionInfo = connInfo;
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ }
+
+ bool httpsRRAllowed =
+ !LoadBeConservative() && !(mCaps & NS_HTTP_BE_CONSERVATIVE) &&
+ !(mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
+ mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) &&
+ !mConnectionInfo->UsingConnect();
+ if (!httpsRRAllowed) {
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+ // No need to lookup HTTPSSVC record if mHTTPSSVCRecord already contains a
+ // value.
+ StoreUseHTTPSSVC(StaticPrefs::network_dns_upgrade_with_https_rr() &&
+ httpsRRAllowed && mHTTPSSVCRecord.isNothing());
+
+ // Need to re-ask the handler, since mConnectionInfo may not be the connInfo
+ // we used earlier
+ if (!mConnectionInfo->IsHttp3() &&
+ gHttpHandler->IsHttp2Excluded(mConnectionInfo)) {
+ StoreAllowSpdy(0);
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ mConnectionInfo->SetNoSpdy(true);
+ }
+
+ // We can be passed with the auth provider if this channel was
+ // a result of redirect due to auth retry
+ if (!mAuthProvider) {
+ mAuthProvider = new nsHttpChannelAuthProvider();
+ }
+
+ rv = mAuthProvider->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // check to see if authorization headers should be included
+ // CustomAuthHeader is set in AsyncOpen if we find Authorization header
+ rv = mAuthProvider->AddAuthorizationHeaders(LoadCustomAuthHeader());
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpChannel %p AddAuthorizationHeaders failed (%08x)", this,
+ static_cast<uint32_t>(rv)));
+ }
+
+ // If TimingEnabled flag is not set after OnModifyRequest() then
+ // clear the already recorded AsyncOpen value for consistency.
+ if (!LoadTimingEnabled()) mAsyncOpenTime = TimeStamp();
+
+ // if this somehow fails we can go on without it
+ Unused << gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
+
+ if (!LoadIsTRRServiceChannel() &&
+ (mLoadFlags & VALIDATE_ALWAYS ||
+ BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass()))) {
+ mCaps |= NS_HTTP_REFRESH_DNS;
+ }
+
+ if (gHttpHandler->CriticalRequestPrioritization()) {
+ if (mClassOfService.Flags() & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::UrgentStart &&
+ gHttpHandler->IsUrgentStartEnabled()) {
+ mCaps |= NS_HTTP_URGENT_START;
+ SetPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+ }
+
+ // Force-Reload should reset the persistent connection pool for this host
+ if (mLoadFlags & LOAD_FRESH_CONNECTION) {
+ // just the initial document resets the whole pool
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ gHttpHandler->AltServiceCache()->ClearAltServiceMappings();
+ rv = gHttpHandler->DoShiftReloadConnectionCleanupWithConnInfo(
+ mConnectionInfo);
+ if (NS_FAILED(rv)) {
+ LOG((
+ "nsHttpChannel::BeginConnect "
+ "DoShiftReloadConnectionCleanupWithConnInfo failed: %08x [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+ }
+
+ // We may have been cancelled already, either by on-modify-request
+ // listeners or load group observers; in that case, we should not send the
+ // request to the server
+ if (mCanceled) {
+ return mStatus;
+ }
+ // skip classifier checks if this channel was the result of internal auth
+ // redirect
+ bool shouldBeClassified =
+ !LoadAuthRedirectedChannel() && NS_ShouldClassifyChannel(this);
+
+ if (shouldBeClassified) {
+ if (LoadChannelClassifierCancellationPending()) {
+ LOG(
+ ("Waiting for safe-browsing protection cancellation in BeginConnect "
+ "[this=%p]\n",
+ this));
+ return NS_OK;
+ }
+
+ ReEvaluateReferrerAfterTrackingStatusIsKnown();
+ }
+
+ rv = MaybeStartDNSPrefetch();
+ if (NS_FAILED(rv)) {
+ auto dnsStrategy = GetProxyDNSStrategy();
+ if (dnsStrategy & DNS_BLOCK_ON_ORIGIN_RESOLVE) {
+ // TODO: Should this be fatal?
+ return rv;
+ }
+ // Otherwise this shouldn't be fatal.
+ return NS_OK;
+ }
+
+ rv = CallOrWaitForResume(
+ [](nsHttpChannel* self) { return self->PrepareToConnect(); });
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (shouldBeClassified) {
+ // Start nsChannelClassifier to catch phishing and malware URIs.
+ RefPtr<nsChannelClassifier> channelClassifier =
+ GetOrCreateChannelClassifier();
+ LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]",
+ channelClassifier.get(), this));
+ channelClassifier->Start();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::MaybeStartDNSPrefetch() {
+ // Start a DNS lookup very early in case the real open is queued the DNS can
+ // happen in parallel. Do not do so in the presence of an HTTP proxy as
+ // all lookups other than for the proxy itself are done by the proxy.
+ // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or
+ // LOAD_ONLY_FROM_CACHE flags are set.
+ //
+ // We keep the DNS prefetch object around so that we can retrieve
+ // timing information from it. There is no guarantee that we actually
+ // use the DNS prefetch data for the real connection, but as we keep
+ // this data around for 3 minutes by default, this should almost always
+ // be correct, and even when it isn't, the timing still represents _a_
+ // valid DNS lookup timing for the site, even if it is not _the_
+ // timing we used.
+ if ((mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE)) ||
+ LoadAuthRedirectedChannel()) {
+ return NS_OK;
+ }
+
+ auto dnsStrategy = GetProxyDNSStrategy();
+
+ LOG(
+ ("nsHttpChannel::MaybeStartDNSPrefetch [this=%p, strategy=%u] "
+ "prefetching%s\n",
+ this, dnsStrategy,
+ mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
+
+ if (dnsStrategy & DNS_PREFETCH_ORIGIN) {
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ this, originAttributes);
+
+ mDNSPrefetch =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode(),
+ this, LoadTimingEnabled());
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ dnsFlags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ nsresult rv = mDNSPrefetch->PrefetchHigh(dnsFlags);
+
+ if (dnsStrategy & DNS_BLOCK_ON_ORIGIN_RESOLVE) {
+ LOG((" blocking on prefetching origin"));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG((" lookup failed with 0x%08" PRIx32 ", aborting request",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ // Resolved in OnLookupComplete.
+ mDNSBlockingThenable = mDNSBlockingPromise.Ensure(__func__);
+ }
+
+ if (gHttpHandler->UseHTTPSRRAsAltSvcEnabled() && !mHTTPSSVCRecord &&
+ !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR)) {
+ MOZ_ASSERT(!mHTTPSSVCRecord);
+
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(this,
+ originAttributes);
+
+ RefPtr<nsDNSPrefetch> resolver =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode());
+ Unused << resolver->FetchHTTPSSVC(mCaps & NS_HTTP_REFRESH_DNS, true,
+ [](nsIDNSHTTPSSVCRecord*) {
+ // Do nothing. This is a DNS prefetch.
+ });
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ if (mCacheEntry && !LoadCacheEntryIsWriteOnly()) {
+ int64_t dataSize = 0;
+ mCacheEntry->GetDataSize(&dataSize);
+ *aEncodedBodySize = dataSize;
+ } else {
+ *aEncodedBodySize = mLogicalOffset;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ *aIsAuthChannel = mIsAuthChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
+ if (aChannelIsForDownload) {
+ AddClassFlags(nsIClassOfService::Throttleable);
+ } else {
+ ClearClassFlags(nsIClassOfService::Throttleable);
+ }
+
+ return HttpBaseChannel::SetChannelIsForDownload(aChannelIsForDownload);
+}
+
+base::ProcessId nsHttpChannel::ProcessId() {
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel)) {
+ return httpParent->OtherPid();
+ }
+ if (RefPtr<DocumentLoadListener> docParent = do_QueryObject(parentChannel)) {
+ return docParent->OtherPid();
+ }
+ return base::GetCurrentProcId();
+}
+
+auto nsHttpChannel::AttachStreamFilter() -> RefPtr<ChildEndpointPromise> {
+ LOG(("nsHttpChannel::AttachStreamFilter [this=%p]", this));
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ if (!ProcessId()) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+
+ // If our listener is a DocumentLoadListener, then we might handle
+ // multi-part responses here in the parent process. The current extension
+ // API doesn't understand the parsed multipart format, so we defer responding
+ // here until CallOnStartRequest, and attach the StreamFilter before the
+ // multipart handler (in the parent process!) if applicable.
+ if (RefPtr<DocumentLoadListener> docParent = do_QueryObject(parentChannel)) {
+ StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
+ request->mPromise = new ChildEndpointPromise::Private(__func__);
+ return request->mPromise;
+ }
+
+ mozilla::ipc::Endpoint<extensions::PStreamFilterParent> parent;
+ mozilla::ipc::Endpoint<extensions::PStreamFilterChild> child;
+ nsresult rv = extensions::PStreamFilter::CreateEndpoints(&parent, &child);
+ if (NS_FAILED(rv)) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel)) {
+ return httpParent->AttachStreamFilter(std::move(parent), std::move(child));
+ }
+
+ extensions::StreamFilterParent::Attach(this, std::move(parent));
+ return ChildEndpointPromise::CreateAndResolve(std::move(child), __func__);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) {
+ LOG(("nsHttpChannel::GetNavigationStartTimeStamp [this=%p]", this));
+ MOZ_ASSERT(aTimeStamp);
+ *aTimeStamp = mNavigationStartTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
+ LOG(("nsHttpChannel::SetNavigationStartTimeStamp [this=%p]", this));
+ mNavigationStartTimeStamp = aTimeStamp;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::SetPriority(int32_t value) {
+ int16_t newValue = clamped<int32_t>(value, INT16_MIN, INT16_MAX);
+ if (mPriority == newValue) return NS_OK;
+
+ LOG(("nsHttpChannel::SetPriority %p p=%d", this, newValue));
+
+ mPriority = newValue;
+ if (mTransaction) {
+ nsresult rv = gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpChannel::SetPriority [this=%p] "
+ "RescheduleTransaction failed (%08x)",
+ this, static_cast<uint32_t>(rv)));
+ }
+ }
+
+ // If this channel is the real channel for an e10s channel, notify the
+ // child side about the priority change as well.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
+ if (httpParent) {
+ httpParent->DoSendSetPriority(newValue);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannel::nsIClassOfService
+//-----------------------------------------------------------------------------
+
+void nsHttpChannel::OnClassOfServiceUpdated() {
+ LOG(("nsHttpChannel::OnClassOfServiceUpdated this=%p, cos=%lu, inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (mTransaction) {
+ gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
+ mClassOfService);
+ }
+ if (EligibleForTailing()) {
+ RemoveAsNonTailRequest();
+ } else {
+ AddAsNonTailRequest();
+ }
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags);
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::AddClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags | mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ClearClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(~inFlags & mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetClassOfService(ClassOfService cos) {
+ ClassOfService previous = mClassOfService;
+ mClassOfService = cos;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetIncremental(bool incremental) {
+ bool previous = mClassOfService.Incremental();
+ mClassOfService.SetIncremental(incremental);
+ if (previous != mClassOfService.Incremental()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIProtocolProxyCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnProxyAvailable(nsICancelable* request, nsIChannel* channel,
+ nsIProxyInfo* pi, nsresult status) {
+ LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%" PRIx32
+ " mStatus=%" PRIx32 "]\n",
+ this, pi, static_cast<uint32_t>(status),
+ static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+ mProxyRequest = nullptr;
+
+ nsresult rv;
+
+ // If status is a failure code, then it means that we failed to resolve
+ // proxy info. That is a non-fatal error assuming it wasn't because the
+ // request was canceled. We just failover to DIRECT when proxy resolution
+ // fails (failure can mean that the PAC URL could not be loaded).
+
+ if (NS_SUCCEEDED(status)) mProxyInfo = pi;
+
+ if (!gHttpHandler->Active()) {
+ LOG(
+ ("nsHttpChannel::OnProxyAvailable [this=%p] "
+ "Handler no longer active.\n",
+ this));
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ rv = BeginConnect();
+ }
+
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyInfo(nsIProxyInfo** result) {
+ if (!mConnectionInfo) {
+ *result = do_AddRef(mProxyInfo).take();
+ } else {
+ *result = do_AddRef(mConnectionInfo->ProxyInfo()).take();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupStart();
+ } else {
+ *_retval = mTransactionTimings.domainLookupStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupEnd();
+ } else {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetConnectStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectStart();
+ } else {
+ *_retval = mTransactionTimings.connectStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetTcpConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetTcpConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.tcpConnectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetSecureConnectionStart();
+ } else {
+ *_retval = mTransactionTimings.secureConnectionStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.connectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetRequestStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetRequestStart();
+ } else {
+ *_retval = mTransactionTimings.requestStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseStart();
+ } else {
+ *_retval = mTransactionTimings.responseStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseEnd();
+ } else {
+ *_retval = mTransactionTimings.responseEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetTransactionPending(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetPendingTime();
+ } else {
+ *_retval = mTransactionTimings.transactionPending;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIHttpAuthenticableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetIsSSL(bool* aIsSSL) {
+ // this attribute is really misnamed - it wants to know if
+ // https:// is being used. SSL might be used to cover http://
+ // in some circumstances (proxies, http/2, etc..)
+ return mURI->SchemeIs("https", aIsSSL);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyMethodIsConnect(bool* aProxyMethodIsConnect) {
+ *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetServerResponseHeader(nsACString& value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ return mResponseHead->GetHeader(nsHttp::Server, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyChallenges(nsACString& value) {
+ if (!mResponseHead) return NS_ERROR_UNEXPECTED;
+ return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetWWWChallenges(nsACString& value) {
+ if (!mResponseHead) return NS_ERROR_UNEXPECTED;
+ return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetProxyCredentials(const nsACString& value) {
+ return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetWWWCredentials(const nsACString& value) {
+ // This method is called when various browser initiated authorization
+ // code sets the credentials. We need to flag this header as the
+ // "browser default" so it does not show up in the ServiceWorker
+ // FetchEvent. This may actually get called more than once, though,
+ // so we clear the header first since "default" headers are not
+ // allowed to overwrite normally.
+ Unused << mRequestHead.ClearHeader(nsHttp::Authorization);
+ return mRequestHead.SetHeader(nsHttp::Authorization, value, false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+}
+
+//-----------------------------------------------------------------------------
+// Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
+// get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ return HttpBaseChannel::GetLoadFlags(aLoadFlags);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetURI(nsIURI** aURI) { return HttpBaseChannel::GetURI(aURI); }
+
+NS_IMETHODIMP
+nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ return HttpBaseChannel::GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetRequestMethod(nsACString& aMethod) {
+ return HttpBaseChannel::GetRequestMethod(aMethod);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+void nsHttpChannel::RecordOnStartTelemetry(nsresult aStatus,
+ bool aIsNavigation) {
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_ONSTART_SUCCESS,
+ NS_SUCCEEDED(aStatus));
+
+ if (mTransaction) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_CHANNEL_ONSTART_SUCCESS,
+ (mTransaction->IsHttp3Used()) ? "http3"_ns : "no_http3"_ns,
+ NS_SUCCEEDED(aStatus));
+ }
+
+ enum class HttpOnStartState : uint32_t {
+ Success = 0,
+ DNSError = 1,
+ Others = 2,
+ };
+
+ if (TRRService::Get() && TRRService::Get()->IsConfirmed()) {
+ // Note this telemetry probe is not working when DNS resolution is done in
+ // the socket process.
+ HttpOnStartState state = HttpOnStartState::Others;
+ if (NS_SUCCEEDED(aStatus)) {
+ state = HttpOnStartState::Success;
+ } else if (aStatus == NS_ERROR_UNKNOWN_HOST ||
+ aStatus == NS_ERROR_UNKNOWN_PROXY_HOST) {
+ state = HttpOnStartState::DNSError;
+ }
+
+ if (aIsNavigation) {
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_PAGE_ONSTART_SUCCESS_TRR3,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(state));
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_SUB_ONSTART_SUCCESS_TRR3,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(state));
+ }
+ }
+
+ if (nsIOService::UseSocketProcess() && mTransaction) {
+ const TimeStamp now = TimeStamp::Now();
+ TimeStamp responseEnd = mTransaction->GetResponseEnd();
+ if (!responseEnd.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::ResponseEndSocketToParent,
+ now - responseEnd);
+ }
+
+ mOnStartRequestStartTime = mTransaction->GetOnStartRequestStartTime();
+ if (!mOnStartRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::OnStartRequestSocketToParent,
+ now - mOnStartRequestStartTime);
+ }
+ } else {
+ mOnStartRequestStartTime = TimeStamp::Now();
+ }
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnStartRequest(nsIRequest* request) {
+ nsresult rv;
+
+ MOZ_ASSERT(LoadRequestObserversCalled());
+
+ AUTO_PROFILER_LABEL("nsHttpChannel::OnStartRequest", NETWORK);
+
+ if (!(mCanceled || NS_FAILED(mStatus)) &&
+ !WRONG_RACING_RESPONSE_SOURCE(request)) {
+ // capture the request's status, so our consumers will know ASAP of any
+ // connection failures, etc - bug 93581
+ nsresult status;
+ request->GetStatus(&status);
+ mStatus = status;
+ }
+
+ if (mStatus == NS_ERROR_NON_LOCAL_CONNECTION_REFUSED) {
+ MOZ_CRASH_UNSAFE(nsPrintfCString("Attempting to connect to non-local "
+ "address! opener is [%s], uri is "
+ "[%s]",
+ mOpenerCallingScriptLocation
+ ? mOpenerCallingScriptLocation->get()
+ : "unknown",
+ mURI->GetSpecOrDefault().get())
+ .get());
+ }
+
+ LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+
+ RecordOnStartTelemetry(mStatus, IsNavigation());
+
+ if (mRaceCacheWithNetwork) {
+ LOG(
+ (" racingNetAndCache - mFirstResponseSource:%d fromCache:%d "
+ "fromNet:%d\n",
+ static_cast<int32_t>(mFirstResponseSource), request == mCachePump,
+ request == mTransactionPump));
+ if (mFirstResponseSource == RESPONSE_PENDING) {
+ // When the cache wins mFirstResponseSource is set to
+ // RESPONSE_FROM_CACHE earlier in ReadFromCache, so this must be a
+ // response from the network.
+ MOZ_ASSERT(request == mTransactionPump);
+ LOG((" First response from network\n"));
+ {
+ // Race condition with OnCacheEntryCheck, which is not limited
+ // to main thread.
+ mozilla::MutexAutoLock lock(mRCWNLock);
+ mFirstResponseSource = RESPONSE_FROM_NETWORK;
+ mOnStartRequestTimestamp = TimeStamp::Now();
+
+ // Conditional or byte range header could be added in
+ // OnCacheEntryCheck. We need to remove them because the
+ // request might be sent again due to auth retry and we must
+ // not send these headers without having the entry.
+ if (mDidReval) {
+ LOG((" Removing conditional request headers"));
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+ if (LoadCachedContentIsPartial()) {
+ LOG((" Removing byte range request headers"));
+ UntieByteRangeRequest();
+ StoreCachedContentIsPartial(false);
+ }
+ }
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+ } else if (WRONG_RACING_RESPONSE_SOURCE(request)) {
+ LOG((" Early return when racing. This response not needed."));
+ return NS_OK;
+ }
+ }
+
+ // Make sure things are what we expect them to be...
+ MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
+ "Unexpected request");
+
+ MOZ_ASSERT(mRaceCacheWithNetwork || !(mTransactionPump && mCachePump) ||
+ LoadCachedContentIsPartial() || LoadTransactionReplaced(),
+ "If we have both pumps, we're racing cache with network, the cache"
+ " content is partial, or the cache entry was revalidated and "
+ "OnStopRequest was not called yet for the transaction pump.");
+
+ StoreAfterOnStartRequestBegun(true);
+ if (mOnStartRequestTimestamp.IsNull()) {
+ mOnStartRequestTimestamp = TimeStamp::Now();
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP_ONSTART_SUSPEND_TOTAL_TIME,
+ mSuspendTotalTime);
+
+ if (mTransaction) {
+ mProxyConnectResponseCode = mTransaction->GetProxyConnectResponseCode();
+ if (request == mTransactionPump) {
+ StoreDataSentToChildProcess(mTransaction->DataSentToChildProcess());
+ }
+
+ if (!mSecurityInfo && !mCachePump) {
+ // grab the security info from the connection object; the transaction
+ // is guaranteed to own a reference to the connection.
+ mSecurityInfo = mTransaction->SecurityInfo();
+ }
+
+ uint32_t stage = mTransaction->HTTPSSVCReceivedStage();
+ if (!LoadHTTPSSVCTelemetryReported() && stage != HTTPSSVC_NOT_USED) {
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_RECORD_RECEIVING_STAGE,
+ stage);
+ }
+
+ if (HTTPS_RR_IS_USED(stage)) {
+ nsAutoCString suffix(LoadEchConfigUsed() ? "_ech_used" : "");
+ // Determine the result string based on the status.
+ nsAutoCString result(NS_SUCCEEDED(mStatus) ? "success" : "failure");
+ result.Append(suffix);
+
+ mozilla::glean::networking::http_channel_onstart_success_https_rr
+ .Get(result)
+ .Add(1);
+ StoreHasHTTPSRR(true);
+ }
+
+ StoreLoadedBySocketProcess(mTransaction->AsHttpTransactionParent() !=
+ nullptr);
+
+ bool isTrr;
+ bool echConfigUsed;
+ mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr, isTrr,
+ mEffectiveTRRMode, mTRRSkipReason,
+ echConfigUsed);
+ StoreResolvedByTRR(isTrr);
+ StoreEchConfigUsed(echConfigUsed);
+ }
+
+ // don't enter this block if we're reading from the cache...
+ if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
+ // mTransactionPump doesn't hit OnInputStreamReady and call this until
+ // all of the response headers have been acquired, so we can take
+ // ownership of them from the transaction.
+ mResponseHead = mTransaction->TakeResponseHead();
+ mSupportsHTTP3 = mTransaction->GetSupportsHTTP3();
+ // the response head may be null if the transaction was cancelled. in
+ // which case we just need to call OnStartRequest/OnStopRequest.
+ if (mResponseHead) return ProcessResponse();
+
+ NS_WARNING("No response head in OnStartRequest");
+ }
+
+ // cache file could be deleted on our behalf, it could contain errors or
+ // it failed to allocate memory, reload from network here.
+ if (mCacheEntry && mCachePump && RECOVER_FROM_CACHE_FILE_ERROR(mStatus)) {
+ LOG((" cache file error, reloading from server"));
+ mCacheEntry->AsyncDoom(nullptr);
+ rv =
+ StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ if (NS_SUCCEEDED(rv)) return NS_OK;
+ }
+
+ // avoid crashing if mListener happens to be null...
+ if (!mListener) {
+ MOZ_ASSERT_UNREACHABLE("mListener is null");
+ return NS_OK;
+ }
+
+ rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ // No process change is needed, so continue on to ContinueOnStartRequest1.
+ return ContinueOnStartRequest1(rv);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest1(nsresult result) {
+ nsresult rv;
+
+ // if process selection failed, cancel this load.
+ if (NS_FAILED(result) && !mCanceled) {
+ Cancel(result);
+ return CallOnStartRequest();
+ }
+
+ // before we start any content load, check for redirectTo being called
+ // this code is executed mainly before we start load from the cache
+ if (mAPIRedirectToURI && !mCanceled) {
+ nsAutoCString redirectToSpec;
+ mAPIRedirectToURI->GetAsciiSpec(redirectToSpec);
+ LOG((" redirectTo called with uri=%s", redirectToSpec.BeginReading()));
+
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
+ rv = StartRedirectChannelToURI(redirectTo,
+ nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
+ }
+
+ // Hack: ContinueOnStartRequest2 uses NS_OK to detect successful redirects,
+ // so we distinguish this codepath (a non-redirect that's processing
+ // normally) by passing in a bogus error code.
+ return ContinueOnStartRequest2(NS_BINDING_FAILED);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest2(nsresult result) {
+ if (NS_SUCCEEDED(result)) {
+ // Redirect has passed through, we don't want to go on with this
+ // channel. It will now be canceled by the redirect handling code
+ // that called this function.
+ return NS_OK;
+ }
+
+ // on proxy errors, try to failover
+ if (mConnectionInfo->ProxyInfo() &&
+ (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
+ mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
+ mStatus == NS_ERROR_NET_TIMEOUT || mStatus == NS_ERROR_NET_RESET)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ if (NS_SUCCEEDED(ProxyFailover())) {
+ mProxyConnectResponseCode = 0;
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ }
+
+ // Hack: ContinueOnStartRequest3 uses NS_OK to detect successful redirects,
+ // so we distinguish this codepath (a non-redirect that's processing
+ // normally) by passing in a bogus error code.
+ return ContinueOnStartRequest3(NS_BINDING_FAILED);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest3(nsresult result) {
+ if (NS_SUCCEEDED(result)) {
+ // Redirect has passed through, we don't want to go on with this
+ // channel. It will now be canceled by the redirect handling code
+ // that called this function.
+ return NS_OK;
+ }
+
+ return ContinueOnStartRequest4(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest4(nsresult result) {
+ LOG(("nsHttpChannel::ContinueOnStartRequest4 [this=%p]", this));
+
+ if (NS_SUCCEEDED(mStatus) && mResponseHead && mAuthProvider) {
+ uint32_t httpStatus = mResponseHead->Status();
+ if (httpStatus != 401 && httpStatus != 407) {
+ nsresult rv = mAuthProvider->CheckForSuperfluousAuth();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ LOG((" CheckForSuperfluousAuth failed (%08x)",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ return CallOnStartRequest();
+}
+
+static void ReportHTTPSRRTelemetry(
+ const Maybe<nsCOMPtr<nsIDNSHTTPSSVCRecord>>& aMaybeRecord) {
+ bool hasHTTPSRR = aMaybeRecord && (aMaybeRecord.ref() != nullptr);
+ if (!hasHTTPSRR) {
+ mozilla::glean::networking::https_rr_presented.Get("none"_ns).Add(1);
+ return;
+ }
+
+ const nsCOMPtr<nsIDNSHTTPSSVCRecord>& record = aMaybeRecord.ref();
+ nsCOMPtr<nsISVCBRecord> svcbRecord;
+ if (NS_SUCCEEDED(record->GetServiceModeRecord(false, false,
+ getter_AddRefs(svcbRecord)))) {
+ MOZ_ASSERT(svcbRecord);
+
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> alpn =
+ svcbRecord->GetAlpn();
+ bool isHttp3 = alpn ? IsHttp3(std::get<1>(*alpn)) : false;
+ mozilla::glean::networking::https_rr_presented
+ .Get(isHttp3 ? "presented_with_http3"_ns : "presented"_ns)
+ .Add(1);
+ }
+}
+
+static nsLiteralCString ContentTypeToTelemetryLabel(nsHttpChannel* aChannel) {
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+
+ if (StringBeginsWith(contentType, "text/"_ns)) {
+ if (contentType.EqualsLiteral(TEXT_HTML)) {
+ return "text_html"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_CSS)) {
+ return "text_css"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_JSON)) {
+ return "text_json"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_PLAIN)) {
+ return "text_plain"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_JAVASCRIPT)) {
+ return "text_javascript"_ns;
+ }
+ return "text_other"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "audio/"_ns)) {
+ return "audio"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "video/"_ns)) {
+ return "video"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "multipart/"_ns)) {
+ return "multipart"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "image/"_ns)) {
+ if (contentType.EqualsLiteral(IMAGE_ICO) ||
+ contentType.EqualsLiteral(IMAGE_ICO_MS) ||
+ contentType.EqualsLiteral(IMAGE_ICON_MS)) {
+ return "icon"_ns;
+ }
+ return "image"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "application/"_ns)) {
+ if (contentType.EqualsLiteral(APPLICATION_JSON)) {
+ return "text_json"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_OGG)) {
+ return "video"_ns;
+ }
+ if (contentType.EqualsLiteral("application/ocsp-response")) {
+ return "ocsp"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_XPINSTALL)) {
+ return "xpinstall"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_WASM)) {
+ return "wasm"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_PDF) ||
+ contentType.EqualsLiteral(APPLICATION_POSTSCRIPT)) {
+ return "pdf"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_OCTET_STREAM)) {
+ return "octet_stream"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT)) {
+ return "text_javascript"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_NS_PROXY_AUTOCONFIG) ||
+ contentType.EqualsLiteral(APPLICATION_NS_JAVASCRIPT_AUTOCONFIG)) {
+ return "proxy"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_BROTLI) ||
+ contentType.Find("zip") != kNotFound ||
+ contentType.Find("compress") != kNotFound) {
+ return "compressed"_ns;
+ }
+ if (contentType.Find("x509") != kNotFound) {
+ return "x509"_ns;
+ }
+ return "application_other"_ns;
+ }
+
+ if (contentType.EqualsLiteral(BINARY_OCTET_STREAM)) {
+ return "octet_stream"_ns;
+ }
+
+ return "other"_ns;
+}
+
+nsresult nsHttpChannel::LogConsoleError(const char* aTag) {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY);
+ uint64_t innerWindowID = loadInfo->GetInnerWindowID();
+
+ nsAutoString errorText;
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eNECKO_PROPERTIES, aTag, errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = error->InitWithSourceURI(errorText, mURI, u""_ns, 0, 0,
+ nsIScriptError::errorFlag,
+ "Invalid HTTP Status Lines"_ns, innerWindowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ console->LogMessage(error);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
+ AUTO_PROFILER_LABEL("nsHttpChannel::OnStopRequest", NETWORK);
+
+ LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%" PRIx32 "]\n",
+ this, request, static_cast<uint32_t>(status)));
+
+ LOG(("OnStopRequest %p requestFromCache: %d mFirstResponseSource: %d\n", this,
+ request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "OnStopRequest should only be called from the main thread");
+
+ if (mStatus == NS_ERROR_PARSING_HTTP_STATUS_LINE) {
+ Unused << LogConsoleError("InvalidHTTPResponseStatusLine");
+ }
+
+ if (WRONG_RACING_RESPONSE_SOURCE(request)) {
+ return NS_OK;
+ }
+
+ // It's possible that LoadUseHTTPSSVC() is false, but we already have
+ // mHTTPSSVCRecord.
+ if (LoadUseHTTPSSVC() || mHTTPSSVCRecord) {
+ ReportHTTPSRRTelemetry(mHTTPSSVCRecord);
+ }
+
+ // If this load failed because of a security error, it may be because we
+ // are in a captive portal - trigger an async check to make sure.
+ int32_t nsprError = -1 * NS_ERROR_GET_CODE(status);
+ if (mozilla::psm::IsNSSErrorCode(nsprError) && IsHTTPS()) {
+ gIOService->RecheckCaptivePortal();
+ }
+
+ if (LoadTimingEnabled() && request == mCachePump) {
+ mCacheReadEnd = TimeStamp::Now();
+
+ ReportNetVSCacheTelemetry();
+ }
+
+ // allow content to be cached if it was loaded successfully (bug #482935)
+ bool contentComplete = NS_SUCCEEDED(status);
+
+ // honor the cancelation status even if the underlying transaction
+ // completed.
+ if (mCanceled || NS_FAILED(mStatus)) status = mStatus;
+
+ if (LoadCachedContentIsPartial()) {
+ if (NS_SUCCEEDED(status)) {
+ // mTransactionPump should be suspended
+ MOZ_ASSERT(request != mTransactionPump,
+ "byte-range transaction finished prematurely");
+
+ if (request == mCachePump) {
+ bool streamDone;
+ status = OnDoneReadingPartialCacheEntry(&streamDone);
+ if (NS_SUCCEEDED(status) && !streamDone) return status;
+ // otherwise, fall through and fire OnStopRequest...
+ } else if (request == mTransactionPump) {
+ MOZ_ASSERT(LoadConcurrentCacheAccess());
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected request");
+ }
+ }
+ // Do not to leave the transaction in a suspended state in error cases.
+ if (NS_FAILED(status) && mTransaction) {
+ nsresult rv = gHttpHandler->CancelTransaction(mTransaction, status);
+ if (NS_FAILED(rv)) {
+ LOG((" CancelTransaction failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ bool isFromNet = request == mTransactionPump;
+
+ if (mTransaction) {
+ // determine if we should call DoAuthRetry
+ bool authRetry = (mAuthRetryPending && NS_SUCCEEDED(status) &&
+ // we should only auth retry in this channel if are not
+ // redirecting a new channel for authentication retries
+ !StaticPrefs::network_auth_use_redirect_for_retries());
+
+ StoreStronglyFramed(mTransaction->ResponseIsComplete());
+ LOG(("nsHttpChannel %p has a strongly framed transaction: %d", this,
+ LoadStronglyFramed()));
+
+ // Save the reference of |mTransaction| to |transactionWithStickyConn|
+ // when it has a sticky connection.
+ // In the case we need to retry an authentication request, we need to
+ // reuse the connection of |transactionWithStickyConn|.
+ RefPtr<HttpTransactionShell> transactionWithStickyConn;
+ if (mCaps & NS_HTTP_STICKY_CONNECTION ||
+ mTransaction->HasStickyConnection()) {
+ transactionWithStickyConn = mTransaction;
+ // Make sure we use the updated caps and connection info from transaction.
+ // We read these values when the transaction is already closed, so there
+ // should be no race.
+ if (mTransaction->Http2Disabled()) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (mTransaction->Http3Disabled()) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ mConnectionInfo = mTransaction->GetConnInfo();
+ LOG((" transaction %p has sticky connection",
+ transactionWithStickyConn.get()));
+ }
+
+ // this code relies on the code in nsHttpTransaction::Close, which
+ // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
+ // keep the connection around after the transaction is finished.
+ //
+ LOG((" mAuthRetryPending=%d, status=%" PRIx32 ", sticky conn cap=%d",
+ static_cast<bool>(mAuthRetryPending), static_cast<uint32_t>(status),
+ mCaps & NS_HTTP_STICKY_CONNECTION));
+ // We must check caps for stickinness also on the transaction because it
+ // might have been updated by the transaction itself during inspection of
+ // the reposnse headers yet on the socket thread (found connection based
+ // auth schema).
+
+ if ((NS_FAILED(status)) && transactionWithStickyConn) {
+ // Close (don't reuse) the sticky connection if this channel has been
+ // cancelled. There are proxy servers known to get confused when we send
+ // a new request over such a half-stated connection.
+ if (!LoadAuthConnectionRestartable()) {
+ LOG((" not reusing a half-authenticated sticky connection"));
+ transactionWithStickyConn->DontReuseConnection();
+ }
+ }
+
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ mTransaction->SetH2WSConnRefTaken();
+ }
+
+ mTransferSize = mTransaction->GetTransferSize();
+ mRequestSize = mTransaction->GetRequestSize();
+
+ // Make sure the size does not overflow.
+ int32_t totalSize = static_cast<int32_t>(
+ std::clamp<uint64_t>(mRequestSize + mTransferSize, 0LU,
+ std::numeric_limits<int32_t>::max()));
+
+ // Record telemetry for transferred size keyed by contentType
+ nsLiteralCString label = ContentTypeToTelemetryLabel(this);
+ if (mPrivateBrowsing) {
+ mozilla::glean::network::data_size_pb_per_type.Get(label).Add(totalSize);
+ } else {
+ mozilla::glean::network::data_size_per_type.Get(label).Add(totalSize);
+ }
+
+ // If we are using the transaction to serve content, we also save the
+ // time since async open in the cache entry so we can compare telemetry
+ // between cache and net response.
+ // Do not store the time of conditional requests because even if we
+ // fetch the data from the server, the time includes loading of the old
+ // cache entry which would skew the network load time.
+ if (request == mTransactionPump && mCacheEntry && !mDidReval &&
+ !LoadCustomConditionalRequest() && !mAsyncOpenTime.IsNull() &&
+ !mOnStartRequestTimestamp.IsNull()) {
+ uint64_t onStartTime =
+ (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
+ uint64_t onStopTime =
+ (TimeStamp::Now() - mAsyncOpenTime).ToMilliseconds();
+ Unused << mCacheEntry->SetNetworkTimes(onStartTime, onStopTime);
+ }
+
+ mResponseTrailers = mTransaction->TakeResponseTrailers();
+
+ if (nsIOService::UseSocketProcess() && mTransaction) {
+ mOnStopRequestStartTime = mTransaction->GetOnStopRequestStartTime();
+ if (!mOnStopRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::OnStopRequestSocketToParent,
+ TimeStamp::Now() - mOnStopRequestStartTime);
+ }
+ } else {
+ mOnStopRequestStartTime = TimeStamp::Now();
+ }
+
+ // at this point, we're done with the transaction
+ mTransactionTimings = mTransaction->Timings();
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+
+ // We no longer need the dns prefetch object
+ if (mDNSPrefetch && mDNSPrefetch->TimingsValid() &&
+ !mTransactionTimings.requestStart.IsNull() &&
+ !mTransactionTimings.connectStart.IsNull() &&
+ mDNSPrefetch->EndTimestamp() <= mTransactionTimings.connectStart) {
+ // We only need the domainLookup timestamps when not using a
+ // persistent connection, meaning if the endTimestamp < connectStart
+ mTransactionTimings.domainLookupStart = mDNSPrefetch->StartTimestamp();
+ mTransactionTimings.domainLookupEnd = mDNSPrefetch->EndTimestamp();
+ }
+ mDNSPrefetch = nullptr;
+
+ // handle auth retry...
+ if (authRetry) {
+ mAuthRetryPending = false;
+ auto continueOSR = [authRetry, isFromNet, contentComplete,
+ transactionWithStickyConn](auto* self,
+ nsresult aStatus) {
+ return self->ContinueOnStopRequestAfterAuthRetry(
+ aStatus, authRetry, isFromNet, contentComplete,
+ transactionWithStickyConn);
+ };
+ status = DoAuthRetry(transactionWithStickyConn, continueOSR);
+ if (NS_SUCCEEDED(status)) {
+ return NS_OK;
+ }
+ }
+ return ContinueOnStopRequestAfterAuthRetry(status, authRetry, isFromNet,
+ contentComplete,
+ transactionWithStickyConn);
+ }
+
+ return ContinueOnStopRequest(status, isFromNet, contentComplete);
+}
+
+nsresult nsHttpChannel::ContinueOnStopRequestAfterAuthRetry(
+ nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
+ HttpTransactionShell* aTransWithStickyConn) {
+ LOG(
+ ("nsHttpChannel::ContinueOnStopRequestAfterAuthRetry "
+ "[this=%p, aStatus=%" PRIx32
+ " aAuthRetry=%d, aIsFromNet=%d, aTransWithStickyConn=%p]\n",
+ this, static_cast<uint32_t>(aStatus), aAuthRetry, aIsFromNet,
+ aTransWithStickyConn));
+
+ if (aAuthRetry && NS_SUCCEEDED(aStatus)) {
+ return NS_OK;
+ }
+
+ // If DoAuthRetry failed, or if we have been cancelled since showing
+ // the auth. dialog, then we need to send OnStartRequest now
+ if (aAuthRetry || (mAuthRetryPending && NS_FAILED(aStatus))) {
+ MOZ_ASSERT(NS_FAILED(aStatus), "should have a failure code here");
+ // NOTE: since we have a failure status, we can ignore the return
+ // value from onStartRequest.
+ LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
+ mListener.get()));
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OnStartRequest twice.");
+ if (!LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ } else {
+ StoreOnStartRequestCalled(true);
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ }
+ mAuthRetryPending = false;
+ }
+
+ // if this transaction has been replaced, then bail.
+ if (LoadTransactionReplaced()) {
+ LOG(("Transaction replaced\n"));
+ // This was just the network check for a 304 response.
+ mFirstResponseSource = RESPONSE_PENDING;
+ return NS_OK;
+ }
+
+ bool upgradeWebsocket = mUpgradeProtocolCallback && aTransWithStickyConn &&
+ mResponseHead &&
+ ((mResponseHead->Status() == 101 &&
+ mResponseHead->Version() == HttpVersion::v1_1) ||
+ (mResponseHead->Status() == 200 &&
+ mResponseHead->Version() == HttpVersion::v2_0));
+
+ bool upgradeConnect = mUpgradeProtocolCallback && aTransWithStickyConn &&
+ (mCaps & NS_HTTP_CONNECT_ONLY) && mResponseHead &&
+ mResponseHead->Status() == 200;
+
+ if (upgradeWebsocket || upgradeConnect) {
+ if (nsIOService::UseSocketProcess() && upgradeConnect) {
+ // TODO: Support connection upgrade for socket process in bug 1632809.
+ Unused << mUpgradeProtocolCallback->OnUpgradeFailed(
+ NS_ERROR_NOT_IMPLEMENTED);
+ return ContinueOnStopRequest(aStatus, aIsFromNet, aContentComplete);
+ }
+
+ nsresult rv = gHttpHandler->CompleteUpgrade(aTransWithStickyConn,
+ mUpgradeProtocolCallback);
+ mUpgradeProtocolCallback = nullptr;
+ if (NS_FAILED(rv)) {
+ LOG((" CompleteUpgrade failed with %" PRIx32,
+ static_cast<uint32_t>(rv)));
+
+ // This ensures that WebSocketChannel::OnStopRequest will be
+ // called with an error so the session is properly aborted.
+ aStatus = rv;
+ }
+ }
+
+ return ContinueOnStopRequest(aStatus, aIsFromNet, aContentComplete);
+}
+
+nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
+ bool aContentComplete) {
+ LOG(
+ ("nsHttpChannel::ContinueOnStopRequest "
+ "[this=%p aStatus=%" PRIx32 ", aIsFromNet=%d]\n",
+ this, static_cast<uint32_t>(aStatus), aIsFromNet));
+
+ // HTTP_CHANNEL_DISPOSITION TELEMETRY
+ enum ChannelDisposition {
+ kHttpCanceled = 0,
+ kHttpDisk = 1,
+ kHttpNetOK = 2,
+ kHttpNetEarlyFail = 3,
+ kHttpNetLateFail = 4,
+ kHttpsCanceled = 8,
+ kHttpsDisk = 9,
+ kHttpsNetOK = 10,
+ kHttpsNetEarlyFail = 11,
+ kHttpsNetLateFail = 12
+ } chanDisposition = kHttpCanceled;
+ // HTTP_CHANNEL_DISPOSITION_UPGRADE TELEMETRY
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::cancel;
+
+ // HTTP 0.9 is more likely to be an error than really 0.9, so count it that
+ // way
+ if (mCanceled) {
+ chanDisposition = kHttpCanceled;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::cancel;
+ } else if (!LoadUsedNetwork() ||
+ (mRaceCacheWithNetwork &&
+ mFirstResponseSource == RESPONSE_FROM_CACHE)) {
+ chanDisposition = kHttpDisk;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::disk;
+ } else if (NS_SUCCEEDED(aStatus) && mResponseHead &&
+ mResponseHead->Version() != HttpVersion::v0_9) {
+ chanDisposition = kHttpNetOK;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netOk;
+ } else if (!mTransferSize) {
+ chanDisposition = kHttpNetEarlyFail;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netEarlyFail;
+ } else {
+ chanDisposition = kHttpNetLateFail;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netLateFail;
+ }
+ // Browser upgrading only happens on HTTPS pages for mixed passive content
+ // when upgrading is enabled.
+ nsCString upgradeKey;
+ if (IsHTTPS()) {
+ // Browser upgrading is disabled and the content is already HTTPS
+ upgradeKey = "disabledNoReason"_ns;
+ // Checks "security.mixed_content.upgrade_display_content" is true
+ if (StaticPrefs::security_mixed_content_upgrade_display_content()) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ // HTTP content the browser has upgraded to HTTPS
+ upgradeKey = "enabledUpgrade"_ns;
+ } else {
+ // Content wasn't upgraded but is already HTTPS
+ upgradeKey = "enabledNoReason"_ns;
+ }
+ }
+ // shift http to https disposition enums
+ chanDisposition =
+ static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ // HTTP content the browser would upgrade to HTTPS if upgrading was
+ // enabled
+ upgradeKey = "disabledUpgrade"_ns;
+ } else {
+ // HTTP content that wouldn't upgrade
+ upgradeKey = StaticPrefs::security_mixed_content_upgrade_display_content()
+ ? "enabledWont"_ns
+ : "disabledWont"_ns;
+ }
+ Telemetry::AccumulateCategoricalKeyed(upgradeKey, upgradeChanDisposition);
+ LOG((" nsHttpChannel::OnStopRequest ChannelDisposition %d\n",
+ chanDisposition));
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_DISPOSITION, chanDisposition);
+
+ // Collect specific telemetry for measuring image, video, audio
+ // success/failure rates in regular browsing mode and when auto upgrading of
+ // subresources is enabled. Note that we only evaluate actual image types, not
+ // favicons.
+ nsContentPolicyType internalLoadType;
+ mLoadInfo->GetInternalContentPolicyType(&internalLoadType);
+ bool statusIsSuccess = NS_SUCCEEDED(aStatus);
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE ||
+ internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD) {
+ if (mLoadInfo->GetBrowserDidUpgradeInsecureRequests()) {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgUpFailure);
+ } else {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgNoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgNoUpFailure);
+ }
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_VIDEO) {
+ if (mLoadInfo->GetBrowserDidUpgradeInsecureRequests()) {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoUpFailure);
+ } else {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoNoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoNoUpFailure);
+ }
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_AUDIO) {
+ if (mLoadInfo->GetBrowserDidUpgradeInsecureRequests()) {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioUpFailure);
+ } else {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioNoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioNoUpFailure);
+ }
+ }
+
+ // if needed, check cache entry has all data we expect
+ if (mCacheEntry && mCachePump && LoadConcurrentCacheAccess() &&
+ aContentComplete) {
+ int64_t size, contentLength;
+ nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
+ if (NS_SUCCEEDED(rv)) {
+ if (size == int64_t(-1)) {
+ // mayhemer TODO - we have to restart read from cache here at the size
+ // offset
+ MOZ_ASSERT(false);
+ LOG(
+ (" cache entry write is still in progress, but we just "
+ "finished reading the cache entry"));
+ } else if (contentLength != int64_t(-1) && contentLength != size) {
+ LOG((" concurrent cache entry write has been interrupted"));
+ mCachedResponseHead = std::move(mResponseHead);
+ // Ignore zero partial length because we also want to resume when
+ // no data at all has been read from the cache.
+ rv = MaybeSetupByteRangeRequest(size, contentLength, true);
+ if (NS_SUCCEEDED(rv) && LoadIsPartialRequest()) {
+ // Prevent read from cache again
+ mCachedContentIsValid = false;
+ StoreCachedContentIsPartial(1);
+
+ // We are about to perform a different network request.
+ // We must set mRaceCacheWithNetwork to false because otherwise
+ // we would ignore the network response thinking we didn't need it.
+ mRaceCacheWithNetwork = false;
+
+ // Perform the range request
+ rv = ContinueConnect();
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" performing range request"));
+ mCachePump = nullptr;
+ return NS_OK;
+ }
+ LOG((" but range request perform failed 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ aStatus = NS_ERROR_NET_INTERRUPT;
+ } else {
+ LOG((" but range request setup failed rv=0x%08" PRIx32
+ ", failing load",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ StoreIsPending(false);
+ mStatus = aStatus;
+
+ // perform any final cache operations before we close the cache entry.
+ if (mCacheEntry && LoadRequestTimeInitialized()) {
+ bool writeAccess;
+ // New implementation just returns value of the !LoadCacheEntryIsReadOnly()
+ // flag passed in. Old implementation checks on nsICache::ACCESS_WRITE
+ // flag.
+ mCacheEntry->HasWriteAccess(!LoadCacheEntryIsReadOnly(), &writeAccess);
+ if (writeAccess) {
+ nsresult rv = FinalizeCacheEntry();
+ if (NS_FAILED(rv)) {
+ LOG(("FinalizeCacheEntry failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ ReportRcwnStats(aIsFromNet);
+
+ // Register entry to the PerformanceStorage resource timing
+ MaybeReportTimingData();
+
+ MaybeFlushConsoleReports();
+
+ if (!mEndMarkerAdded && profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ mEndMarkerAdded = true;
+
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, TimeStamp::Now(), size, mCacheDisposition,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ if (mAuthRetryPending &&
+ StaticPrefs::network_auth_use_redirect_for_retries()) {
+ nsresult rv = OpenRedirectChannel(aStatus);
+ LOG(("Opening redirect channel for auth retry %x",
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OnStartRequest twice.");
+ if (!LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ } else {
+ StoreOnStartRequestCalled(true);
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ }
+ }
+ mAuthRetryPending = false;
+ }
+ if (mListener) {
+ LOG(("nsHttpChannel %p calling OnStopRequest\n", this));
+ MOZ_ASSERT(LoadOnStartRequestCalled(),
+ "OnStartRequest should be called before OnStopRequest");
+ MOZ_ASSERT(!LoadOnStopRequestCalled(),
+ "We should not call OnStopRequest twice");
+ StoreOnStopRequestCalled(true);
+ mListener->OnStopRequest(this, aStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // The prefetch needs to be released on the main thread
+ mDNSPrefetch = nullptr;
+
+ mRedirectChannel = nullptr;
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ RemoveAsNonTailRequest();
+
+ if (mChannelBlockedByOpaqueResponse && mCachedOpaqueResponseBlockingPref) {
+ mResponseHead->ClearHeaders();
+ }
+ // If a preferred alt-data type was set, this signals the consumer is
+ // interested in reading and/or writing the alt-data representation.
+ // We need to hold a reference to the cache entry in case the listener calls
+ // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntry = mCacheEntry;
+ }
+
+ CloseCacheEntry(!aContentComplete);
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+class OnTransportStatusAsyncEvent : public Runnable {
+ public:
+ OnTransportStatusAsyncEvent(nsITransportEventSink* aEventSink,
+ nsresult aTransportStatus, int64_t aProgress,
+ int64_t aProgressMax)
+ : Runnable("net::OnTransportStatusAsyncEvent"),
+ mEventSink(aEventSink),
+ mTransportStatus(aTransportStatus),
+ mProgress(aProgress),
+ mProgressMax(aProgressMax) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
+ if (mEventSink) {
+ mEventSink->OnTransportStatus(nullptr, mTransportStatus, mProgress,
+ mProgressMax);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsresult mTransportStatus;
+ int64_t mProgress;
+ int64_t mProgressMax;
+};
+
+NS_IMETHODIMP
+nsHttpChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* input,
+ uint64_t offset, uint32_t count) {
+ nsresult rv;
+ AUTO_PROFILER_LABEL("nsHttpChannel::OnDataAvailable", NETWORK);
+
+ LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%" PRIu64
+ " count=%" PRIu32 "]\n",
+ this, request, offset, count));
+
+ LOG((" requestFromCache: %d mFirstResponseSource: %d\n",
+ request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
+
+ // don't send out OnDataAvailable notifications if we've been canceled.
+ if (mCanceled) return mStatus;
+
+ if (mAuthRetryPending || WRONG_RACING_RESPONSE_SOURCE(request) ||
+ (request == mTransactionPump && LoadTransactionReplaced())) {
+ uint32_t n;
+ return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
+ }
+
+ MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
+
+ MOZ_ASSERT(!(LoadCachedContentIsPartial() && (request == mTransactionPump)),
+ "transaction pump not suspended");
+
+ mIsReadingFromCache = (request == mCachePump);
+
+ if (mListener) {
+ //
+ // synthesize transport progress event. we do this here since we want
+ // to delay OnProgress events until we start streaming data. this is
+ // crucially important since it impacts the lock icon (see bug 240053).
+ //
+ nsresult transportStatus;
+ if (request == mCachePump) {
+ transportStatus = NS_NET_STATUS_READING;
+ } else {
+ transportStatus = NS_NET_STATUS_RECEIVING_FROM;
+ }
+
+ // mResponseHead may reference new or cached headers, but either way it
+ // holds our best estimate of the total content length. Even in the case
+ // of a byte range request, the content length stored in the cached
+ // response headers is what we want to use here.
+
+ int64_t progressMax = -1;
+ rv = GetContentLength(&progressMax);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("GetContentLength failed");
+ }
+ int64_t progress = mLogicalOffset + count;
+
+ if ((progress > progressMax) && (progressMax != -1)) {
+ NS_WARNING(
+ "unexpected progress values - "
+ "is server exceeding content length?");
+ }
+
+ // make sure params are in range for js
+ if (!InScriptableRange(progressMax)) {
+ progressMax = -1;
+ }
+
+ if (!InScriptableRange(progress)) {
+ progress = -1;
+ }
+
+ if (NS_IsMainThread()) {
+ OnTransportStatus(nullptr, transportStatus, progress, progressMax);
+ } else {
+ rv = NS_DispatchToMainThread(new OnTransportStatusAsyncEvent(
+ this, transportStatus, progress, progressMax));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ //
+ // we have to manually keep the logical offset of the stream up-to-date.
+ // we cannot depend solely on the offset provided, since we may have
+ // already streamed some data from another source (see, for example,
+ // OnDoneReadingPartialCacheEntry).
+ //
+ int64_t offsetBefore = 0;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(input);
+ if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
+ seekable = nullptr;
+ }
+
+ if (nsIOService::UseSocketProcess() && mTransaction) {
+ mOnDataAvailableStartTime = mTransaction->GetDataAvailableStartTime();
+ if (!mOnDataAvailableStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::OnDataAvailableSocketToParent,
+ TimeStamp::Now() - mOnDataAvailableStartTime);
+ }
+ } else {
+ mOnDataAvailableStartTime = TimeStamp::Now();
+ }
+ nsresult rv =
+ mListener->OnDataAvailable(this, input, mLogicalOffset, count);
+ if (NS_SUCCEEDED(rv)) {
+ // by contract mListener must read all of "count" bytes, but
+ // nsInputStreamPump is tolerant to seekable streams that violate that
+ // and it will redeliver incompletely read data. So we need to do
+ // the same thing when updating the progress counter to stay in sync.
+ int64_t offsetAfter, delta;
+ if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) {
+ delta = offsetAfter - offsetBefore;
+ if (delta != count) {
+ count = delta;
+
+ NS_WARNING("Listener OnDataAvailable contract violation");
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ nsAutoString message(nsLiteralString(
+ u"http channel Listener OnDataAvailable contract violation"));
+ if (consoleService) {
+ consoleService->LogStringMessage(message.get());
+ }
+ }
+ }
+ mLogicalOffset += count;
+ }
+
+ return rv;
+ }
+
+ return NS_ERROR_ABORT;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+
+ NS_ENSURE_ARG(aNewTarget);
+ if (aNewTarget->IsOnCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+ if (!mTransactionPump && !mCachePump) {
+ LOG(("nsHttpChannel::RetargetDeliveryTo %p %p no pump available\n", this,
+ aNewTarget));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_OK;
+ // If both cache pump and transaction pump exist, we're probably dealing
+ // with partially cached content. So, we must be able to retarget both.
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
+ if (mCachePump) {
+ retargetableCachePump = do_QueryObject(mCachePump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableCachePump);
+ rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
+ }
+ if (NS_SUCCEEDED(rv) && mTransactionPump) {
+ retargetableTransactionPump = do_QueryObject(mTransactionPump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableTransactionPump);
+ rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
+
+ // If retarget fails for transaction pump, we must restore mCachePump.
+ if (NS_FAILED(rv) && retargetableCachePump) {
+ nsCOMPtr<nsISerialEventTarget> main = GetMainThreadSerialEventTarget();
+ NS_ENSURE_TRUE(main, NS_ERROR_UNEXPECTED);
+ rv = retargetableCachePump->RetargetDeliveryTo(main);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ if (mCachePump) {
+ return mCachePump->GetDeliveryTarget(aEventTarget);
+ }
+ if (mTransactionPump) {
+ nsCOMPtr<nsIThreadRetargetableRequest> request =
+ do_QueryInterface(mTransactionPump);
+ return request->GetDeliveryTarget(aEventTarget);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsThreadRetargetableStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::CheckListenerChain() {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITransportEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only");
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ if (status == NS_NET_STATUS_CONNECTED_TO ||
+ status == NS_NET_STATUS_WAITING_FOR) {
+ bool isTrr = false;
+ bool echConfigUsed = false;
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr, isTrr,
+ mEffectiveTRRMode, mTRRSkipReason,
+ echConfigUsed);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(trans);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&mSelfAddr);
+ socketTransport->GetPeerAddr(&mPeerAddr);
+ socketTransport->ResolvedByTRR(&isTrr);
+ socketTransport->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ socketTransport->GetEchConfigUsed(&echConfigUsed);
+ }
+ }
+
+ StoreResolvedByTRR(isTrr);
+ StoreEchConfigUsed(echConfigUsed);
+ }
+
+ // block socket status event after Cancel or OnStopRequest has been called.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending()) {
+ LOG(("sending progress%s notification [this=%p status=%" PRIx32
+ " progress=%" PRId64 "/%" PRId64 "]\n",
+ (mLoadFlags & LOAD_BACKGROUND) ? "" : " and status", this,
+ static_cast<uint32_t>(status), progress, progressMax));
+
+ nsAutoCString host;
+ mURI->GetHost(host);
+ if (!(mLoadFlags & LOAD_BACKGROUND)) {
+ mProgressSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get());
+ } else {
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ // If the event sink is |HttpChannelParent|, we have to send status
+ // events to it even if LOAD_BACKGROUND is set. |HttpChannelParent|
+ // needs to be aware of whether the status is
+ // |NS_NET_STATUS_RECEIVING_FROM| or |NS_NET_STATUS_READING|.
+ // LOAD_BACKGROUND is checked again in |HttpChannelChild|, so the final
+ // consumer won't get this event.
+ if (SameCOMIdentity(parentChannel, mProgressSink)) {
+ mProgressSink->OnStatus(this, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+ }
+
+ if (progress > 0) {
+ if ((progress > progressMax) && (progressMax != -1)) {
+ NS_WARNING("unexpected progress values");
+ }
+
+ // Try to get mProgressSink if it was nulled out during OnStatus.
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+ if (mProgressSink) {
+ mProgressSink->OnProgress(this, progress, progressMax);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::IsFromCache(bool* value) {
+ if (!LoadIsPending()) return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mRaceCacheWithNetwork) {
+ // return false if reading a partial cache entry; the data isn't
+ // entirely from the cache!
+ *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
+ mCachedContentIsValid && !LoadCachedContentIsPartial();
+ return NS_OK;
+ }
+
+ // If we are racing network and cache (or skipping the cache)
+ // we just return the first response source.
+ *value = mFirstResponseSource == RESPONSE_FROM_CACHE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ bool fromCache = false;
+ if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache || !mCacheEntry ||
+ NS_FAILED(mCacheEntry->GetCacheEntryId(aCacheEntryId))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (!cacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return cacheEntry->GetFetchCount(_retval);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->GetExpirationTime(_retval);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) {
+ LOG(("nsHttpChannel::SetAllowStaleCacheContent [this=%p, allow=%d]", this,
+ aAllowStaleCacheContent));
+ StoreAllowStaleCacheContent(aAllowStaleCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetAllowStaleCacheContent(bool* aAllowStaleCacheContent) {
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = LoadAllowStaleCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetForceValidateCacheContent(bool aForceValidateCacheContent) {
+ LOG(("nsHttpChannel::SetForceValidateCacheContent [this=%p, allow=%d]", this,
+ aForceValidateCacheContent));
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetForceValidateCacheContent(bool* aForceValidateCacheContent) {
+ NS_ENSURE_ARG(aForceValidateCacheContent);
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPreferCacheLoadOverBypass(bool aPreferCacheLoadOverBypass) {
+ StorePreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetPreferCacheLoadOverBypass(bool* aPreferCacheLoadOverBypass) {
+ NS_ENSURE_ARG(aPreferCacheLoadOverBypass);
+ *aPreferCacheLoadOverBypass = LoadPreferCacheLoadOverBypass();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+nsHttpChannel::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetAlternativeDataType(nsACString& aType) {
+ // must be called during or after OnStartRequest
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OpenAlternativeOutputStream(const nsACString& type,
+ int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ // OnStopRequest will clear mCacheEntry, but we may use mAltDataCacheEntry
+ // if the consumer called PreferAlternativeDataType()
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (!cacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv =
+ cacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
+ if (NS_SUCCEEDED(rv)) {
+ // Clear this metadata flag in case it exists.
+ // The caller of this method may set it again.
+ cacheEntry->SetMetaDataElement("alt-data-from-child", nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetOriginalInputStream(nsIInputStreamReceiver* aReceiver) {
+ if (aReceiver == nullptr) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (cacheEntry) {
+ cacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+ }
+ aReceiver->OnInputStreamReady(inputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetAlternativeDataInputStream(nsIInputStream** aInputStream) {
+ NS_ENSURE_ARG_POINTER(aInputStream);
+
+ *aInputStream = nullptr;
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (!mAvailableCachedAltDataType.IsEmpty() && cacheEntry) {
+ nsresult rv = cacheEntry->OpenAlternativeInputStream(
+ mAvailableCachedAltDataType, aInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICachingChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::IsRacing(bool* aIsRacing) {
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsRacing = mRaceCacheWithNetwork;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheToken(nsISupports** token) {
+ NS_ENSURE_ARG_POINTER(token);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ return CallQueryInterface(mCacheEntry, token);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheToken(nsISupports* token) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheKey(uint32_t* key) {
+ NS_ENSURE_ARG_POINTER(key);
+
+ LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
+
+ *key = mPostID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheKey(uint32_t key) {
+ LOG(("nsHttpChannel::SetCacheKey [this=%p key=%u]\n", this, key));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mPostID = key;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheOnlyMetadata(bool* aOnlyMetadata) {
+ NS_ENSURE_ARG(aOnlyMetadata);
+ *aOnlyMetadata = LoadCacheOnlyMetadata();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheOnlyMetadata(bool aOnlyMetadata) {
+ LOG(("nsHttpChannel::SetCacheOnlyMetadata [this=%p only-metadata=%d]\n", this,
+ aOnlyMetadata));
+
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ StoreCacheOnlyMetadata(aOnlyMetadata);
+ if (aOnlyMetadata) {
+ mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetPin(bool* aPin) {
+ NS_ENSURE_ARG(aPin);
+ *aPin = LoadPinCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPin(bool aPin) {
+ LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n", this, aPin));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ StorePinCacheContent(aPin);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ForceCacheEntryValidFor(uint32_t aSecondsToTheFuture) {
+ if (!mCacheEntry) {
+ LOG(
+ ("nsHttpChannel::ForceCacheEntryValidFor found no cache entry "
+ "for this channel [this=%p].",
+ this));
+ } else {
+ mCacheEntry->ForceValidFor(aSecondsToTheFuture);
+
+ nsAutoCString key;
+ mCacheEntry->GetKey(key);
+
+ LOG(
+ ("nsHttpChannel::ForceCacheEntryValidFor successfully forced valid "
+ "entry with key %s for %d seconds. [this=%p]",
+ key.get(), aSecondsToTheFuture, this));
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) {
+ LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%" PRIu64 " id='%s']\n", this,
+ aStartPos, PromiseFlatCString(aEntityID).get()));
+ mEntityID = aEntityID;
+ mStartPos = aStartPos;
+ StoreResuming(true);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::DoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc) {
+ LOG(("nsHttpChannel::DoAuthRetry [this=%p, aTransWithStickyConn=%p]\n", this,
+ aTransWithStickyConn));
+
+ MOZ_ASSERT(!mTransaction, "should not have a transaction");
+
+ // Note that we don't have to toggle |IsPending| anymore. See the reasons
+ // below.
+ // 1. We can't suspend the channel during "http-on-modify-request"
+ // when |IsPending| is false.
+ // 2. We don't check |IsPending| in SetRequestHeader now.
+
+ // Reset RequestObserversCalled because we've probably called the request
+ // observers once already.
+ StoreRequestObserversCalled(false);
+
+ // fetch cookies, and add them to the request header.
+ // the server response could have included cookies that must be sent with
+ // this authentication attempt (bug 84794).
+ // TODO: save cookies from auth response and send them here (bug 572151).
+ AddCookiesToRequest();
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ RefPtr<HttpTransactionShell> trans(aTransWithStickyConn);
+ return CallOrWaitForResume(
+ [trans{std::move(trans)}, aContinueOnStopRequestFunc](auto* self) {
+ return self->ContinueDoAuthRetry(trans, aContinueOnStopRequestFunc);
+ });
+}
+
+nsresult nsHttpChannel::ContinueDoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc) {
+ LOG(("nsHttpChannel::ContinueDoAuthRetry [this=%p]\n", this));
+ StoreIsPending(true);
+
+ // get rid of the old response headers
+ mResponseHead = nullptr;
+
+ // rewind the upload stream
+ if (mUploadStream) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+ if (seekable) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+
+ // This should not normally happen, but it's possible that big memory
+ // blobs originating in the other process can't be rewinded.
+ // In that case we just fail the request, otherwise the content length
+ // will not match and this load will never complete.
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // always set sticky connection flag
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ // and when needed, allow restart regardless the sticky flag
+ if (LoadAuthConnectionRestartable()) {
+ LOG((" connection made restartable"));
+ mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
+ StoreAuthConnectionRestartable(false);
+ } else {
+ LOG((" connection made non-restartable"));
+ mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
+ }
+
+ // notify "http-on-before-connect" observers
+ gHttpHandler->OnBeforeConnect(this);
+
+ RefPtr<HttpTransactionShell> trans(aTransWithStickyConn);
+ return CallOrWaitForResume(
+ [trans{std::move(trans)}, aContinueOnStopRequestFunc](auto* self) {
+ nsresult rv = self->DoConnect(trans);
+ return aContinueOnStopRequestFunc(self, rv);
+ });
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIAsyncVerifyRedirectCallback
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChannel::WaitForRedirectCallback() {
+ nsresult rv;
+ LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ rv = mTransactionPump->Suspend();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mCachePump) {
+ rv = mCachePump->Suspend();
+ if (NS_FAILED(rv) && mTransactionPump) {
+#ifdef DEBUG
+ nsresult resume =
+#endif
+ mTransactionPump->Resume();
+ MOZ_ASSERT(NS_SUCCEEDED(resume), "Failed to resume transaction pump");
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ StoreWaitingForRedirectCallback(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnRedirectVerifyCallback(nsresult result) {
+ LOG(
+ ("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
+ "result=%" PRIx32 " stack=%zu WaitingForRedirectCallback=%u\n",
+ this, static_cast<uint32_t>(result), mRedirectFuncStack.Length(),
+ LoadWaitingForRedirectCallback()));
+ MOZ_ASSERT(LoadWaitingForRedirectCallback(),
+ "Someone forgot to call WaitForRedirectCallback() ?!");
+ StoreWaitingForRedirectCallback(false);
+
+ if (mCanceled && NS_SUCCEEDED(result)) result = NS_BINDING_ABORTED;
+
+ for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) {
+ --i;
+ // Pop the last function pushed to the stack
+ nsContinueRedirectionFunc func = mRedirectFuncStack.PopLastElement();
+
+ // Call it with the result we got from the callback or the deeper
+ // function call.
+ result = (this->*func)(result);
+
+ // If a new function has been pushed to the stack and placed us in the
+ // waiting state, we need to break the chain and wait for the callback
+ // again.
+ if (LoadWaitingForRedirectCallback()) break;
+ }
+
+ if (NS_FAILED(result) && !mCanceled) {
+ // First, cancel this channel if we are in failure state to set mStatus
+ // and let it be propagated to pumps.
+ Cancel(result);
+ }
+
+ if (!LoadWaitingForRedirectCallback()) {
+ // We are not waiting for the callback. At this moment we must release
+ // reference to the redirect target channel, otherwise we may leak.
+ // However, if we are redirecting due to auth retries, we open the
+ // redirected channel after OnStopRequest. In that case we should not
+ // release the reference
+ if (!StaticPrefs::network_auth_use_redirect_for_retries() ||
+ !mAuthRetryPending) {
+ mRedirectChannel = nullptr;
+ }
+ }
+
+ // We always resume the pumps here. If all functions on stack have been
+ // called we need OnStopRequest to be triggered, and if we broke out of the
+ // loop above (and are thus waiting for a new callback) the suspension
+ // count must be balanced in the pumps.
+ if (mTransactionPump) mTransactionPump->Resume();
+ if (mCachePump) mCachePump->Resume();
+
+ return result;
+}
+
+void nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func) {
+ mRedirectFuncStack.AppendElement(func);
+}
+
+void nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func) {
+ MOZ_ASSERT(func == mRedirectFuncStack.LastElement(),
+ "Trying to pop wrong method from redirect async stack!");
+
+ mRedirectFuncStack.RemoveLastElement();
+}
+
+//-----------------------------------------------------------------------------
+// nsIDNSListener functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+ LOG(
+ ("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: "
+ "%s status[0x%" PRIx32 "]\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
+ NS_SUCCEEDED(status) ? "success" : "failure",
+ static_cast<uint32_t>(status)));
+
+ // Unset DNS cache refresh if it was requested,
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ mCaps &= ~NS_HTTP_REFRESH_DNS;
+ if (mTransaction) {
+ mTransaction->SetDNSWasRefreshed();
+ }
+ }
+
+ if (!mDNSBlockingPromise.IsEmpty()) {
+ if (NS_SUCCEEDED(status)) {
+ nsCOMPtr<nsIDNSRecord> record(rec);
+ mDNSBlockingPromise.Resolve(record, __func__);
+ } else {
+ mDNSBlockingPromise.Reject(status, __func__);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::OnHTTPSRRAvailable(nsIDNSHTTPSSVCRecord* aRecord) {
+ MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+ LOG(("nsHttpChannel::OnHTTPSRRAvailable [this=%p, aRecord=%p]\n", this,
+ aRecord));
+
+ if (mHTTPSSVCRecord) {
+ MOZ_ASSERT(false, "OnHTTPSRRAvailable called twice!");
+ return;
+ }
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = aRecord;
+ mHTTPSSVCRecord.emplace(std::move(record));
+ const nsCOMPtr<nsIDNSHTTPSSVCRecord>& httprr = mHTTPSSVCRecord.ref();
+
+ if (LoadWaitHTTPSSVCRecord()) {
+ MOZ_ASSERT(mURI->SchemeIs("http"));
+
+ StoreWaitHTTPSSVCRecord(false);
+ nsresult rv = ContinueOnBeforeConnect(!!httprr, mStatus, !!httprr);
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+ } else {
+ // This channel is not canceled and the transaction is not created.
+ if (httprr && NS_SUCCEEDED(mStatus) && !mTransaction &&
+ (mFirstResponseSource != RESPONSE_FROM_CACHE)) {
+ bool hasIPAddress = false;
+ Unused << httprr->GetHasIPAddresses(&hasIPAddress);
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_RECORD_RECEIVING_STAGE,
+ hasIPAddress
+ ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_0
+ : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_0);
+ StoreHTTPSSVCTelemetryReported(true);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel internal functions
+//-----------------------------------------------------------------------------
+
+// Creates an URI to the given location using current URI for base and charset
+nsresult nsHttpChannel::CreateNewURI(const char* loc, nsIURI** newURI) {
+ nsCOMPtr<nsIIOService> ioService;
+ nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ return ioService->NewURI(nsDependentCString(loc), nullptr, mURI, newURI);
+}
+
+void nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet() {
+ // See RFC 2616 section 5.1.1. These are considered valid
+ // methods which DO NOT invalidate cache-entries for the
+ // referred resource. POST, PUT and DELETE as well as any
+ // other method not listed here will potentially invalidate
+ // any cached copy of the resource
+ if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
+ mRequestHead.IsHead() || mRequestHead.IsTrace() ||
+ mRequestHead.IsConnect()) {
+ return;
+ }
+
+ // Invalidate the request-uri.
+ if (LOG_ENABLED()) {
+ nsAutoCString key;
+ mURI->GetAsciiSpec(key);
+ LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n", this,
+ key.get()));
+ }
+
+ DoInvalidateCacheEntry(mURI);
+
+ // Invalidate Location-header if set
+ nsAutoCString location;
+ Unused << mResponseHead->GetHeader(nsHttp::Location, location);
+ if (!location.IsEmpty()) {
+ LOG((" Location-header=%s\n", location.get()));
+ InvalidateCacheEntryForLocation(location.get());
+ }
+
+ // Invalidate Content-Location-header if set
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Location, location);
+ if (!location.IsEmpty()) {
+ LOG((" Content-Location-header=%s\n", location.get()));
+ InvalidateCacheEntryForLocation(location.get());
+ }
+}
+
+void nsHttpChannel::InvalidateCacheEntryForLocation(const char* location) {
+ nsAutoCString tmpCacheKey, tmpSpec;
+ nsCOMPtr<nsIURI> resultingURI;
+ nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
+ if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
+ DoInvalidateCacheEntry(resultingURI);
+ } else {
+ LOG((" hosts not matching\n"));
+ }
+}
+
+void nsHttpChannel::DoInvalidateCacheEntry(nsIURI* aURI) {
+ // NOTE:
+ // Following comments 24,32 and 33 in bug #327765, we only care about
+ // the cache in the protocol-handler.
+ // The logic below deviates from the original logic in OpenCacheEntry on
+ // one point by using only READ_ONLY access-policy. I think this is safe.
+
+ nsresult rv;
+
+ nsAutoCString key;
+ if (LOG_ENABLED()) {
+ aURI->GetAsciiSpec(key);
+ }
+
+ LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get()));
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ components::CacheStorage::Service());
+ rv = cacheStorageService ? NS_OK : NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ rv = cacheStorageService->DiskCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheStorage->AsyncDoomURI(aURI, ""_ns, nullptr);
+ }
+
+ LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(),
+ int(rv)));
+}
+
+void nsHttpChannel::AsyncOnExamineCachedResponse() {
+ gHttpHandler->OnExamineCachedResponse(this);
+}
+
+void nsHttpChannel::UpdateAggregateCallbacks() {
+ if (!mTransaction) {
+ return;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ GetCurrentSerialEventTarget(),
+ getter_AddRefs(callbacks));
+ mTransaction->SetSecurityCallbacks(callbacks);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
+
+ nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup);
+ if (NS_SUCCEEDED(rv)) {
+ UpdateAggregateCallbacks();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
+
+ nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
+ if (NS_SUCCEEDED(rv)) {
+ UpdateAggregateCallbacks();
+ }
+ return rv;
+}
+
+bool nsHttpChannel::AwaitingCacheCallbacks() {
+ return LoadWaitForCacheEntry() != 0;
+}
+
+void nsHttpChannel::SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) {
+ MOZ_ASSERT(!mTransWithPushedStream);
+ LOG(("nsHttpChannel::SetPushedStreamTransaction [this=%p] trans=%p", this,
+ aTransWithPushedStream));
+
+ mTransWithPushedStream = aTransWithPushedStream;
+ mPushedStreamId = aPushedStreamId;
+}
+
+nsresult nsHttpChannel::OnPush(uint32_t aPushedStreamId, const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTransaction);
+ LOG(("nsHttpChannel::OnPush [this=%p, trans=%p]\n", this, aTransaction));
+
+ MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+
+ if (!pushListener) {
+ LOG(
+ ("nsHttpChannel::OnPush [this=%p] notification callbacks do not "
+ "implement nsIHttpPushListener\n",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIURI> pushResource;
+ nsresult rv;
+
+ // Create a Channel for the Push Resource
+ rv = NS_NewURI(getter_AddRefs(pushResource), aUrl);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> pushChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(pushChannel), pushResource,
+ mLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> pushHttpChannel = do_QueryInterface(pushChannel);
+ MOZ_ASSERT(pushHttpChannel);
+ if (!pushHttpChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<nsHttpChannel> channel;
+ CallQueryInterface(pushHttpChannel, channel.StartAssignment());
+ MOZ_ASSERT(channel);
+ if (!channel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // new channel needs mrqeuesthead and headers from pushedStream
+ channel->mRequestHead.ParseHeaderSet(aRequestString.BeginReading());
+ channel->mLoadGroup = mLoadGroup;
+ channel->mLoadInfo = mLoadInfo;
+ channel->mCallbacks = mCallbacks;
+
+ // Link the trans with pushed stream and the new channel and call listener
+ channel->SetPushedStreamTransactionAndId(aTransaction, aPushedStreamId);
+ rv = pushListener->OnPush(this, pushHttpChannel);
+ return rv;
+}
+
+// static
+bool nsHttpChannel::IsRedirectStatus(uint32_t status) {
+ // 305 disabled as a security measure (see bug 187996).
+ return status == 300 || status == 301 || status == 302 || status == 303 ||
+ status == 307 || status == 308;
+}
+
+void nsHttpChannel::SetCouldBeSynthesized() {
+ MOZ_ASSERT(!BypassServiceWorker());
+ StoreResponseCouldBeSynthesized(true);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightSucceeded() {
+ MOZ_ASSERT(LoadRequireCORSPreflight(), "Why did a preflight happen?");
+ StoreIsCorsPreflightDone(1);
+ mPreflightChannel = nullptr;
+
+ return ContinueConnect();
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightFailed(nsresult aError) {
+ MOZ_ASSERT(LoadRequireCORSPreflight(), "Why did a preflight happen?");
+ StoreIsCorsPreflightDone(1);
+ mPreflightChannel = nullptr;
+
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(aError);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::CallOrWaitForResume(
+ const std::function<nsresult(nsHttpChannel*)>& aFunc) {
+ if (mCanceled) {
+ MOZ_ASSERT(NS_FAILED(mStatus));
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = aFunc;
+ return NS_OK;
+ }
+
+ return aFunc(this);
+}
+
+// This is loosely based on:
+// https://fetch.spec.whatwg.org/#serializing-a-request-origin
+static bool HasNullRequestOrigin(nsHttpChannel* aChannel, nsIURI* aURI,
+ bool isAddonRequest) {
+ // Step 1. If request has a redirect-tainted origin, then return "null".
+ if (aChannel->HasRedirectTaintedOrigin()) {
+ if (StaticPrefs::network_http_origin_redirectTainted()) {
+ return true;
+ }
+ }
+
+ // Non-standard: Only allow HTTP and HTTPS origins.
+ if (!ReferrerInfo::IsReferrerSchemeAllowed(aURI)) {
+ // And moz-extension: for add-on initiated requests.
+ if (!aURI->SchemeIs("moz-extension") || !isAddonRequest) {
+ return true;
+ }
+ }
+
+ // Non-standard: Hide onion URLs.
+ if (StaticPrefs::network_http_referer_hideOnionSource()) {
+ nsAutoCString host;
+ if (NS_SUCCEEDED(aURI->GetAsciiHost(host)) &&
+ StringEndsWith(host, ".onion"_ns)) {
+ return ReferrerInfo::IsCrossOriginRequest(aChannel);
+ }
+ }
+
+ // Step 2. Return request’s origin, serialized.
+ return false;
+}
+
+// Step 8.12. of HTTP-network-or-cache fetch
+//
+// https://fetch.spec.whatwg.org/#append-a-request-origin-header
+void nsHttpChannel::SetOriginHeader() {
+ auto* triggeringPrincipal =
+ BasePrincipal::Cast(mLoadInfo->TriggeringPrincipal());
+
+ if (triggeringPrincipal->IsSystemPrincipal()) {
+ // We can't infer an Origin header from the system principal,
+ // this means system requests use whatever Origin header was specified.
+ return;
+ }
+ bool isAddonRequest = triggeringPrincipal->AddonPolicy() ||
+ triggeringPrincipal->ContentScriptAddonPolicy();
+
+ // Non-standard: Handle already existing Origin header.
+ nsAutoCString existingHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Origin, existingHeader);
+ if (!existingHeader.IsEmpty()) {
+ LOG(("nsHttpChannel::SetOriginHeader Origin header already present"));
+ auto const shouldNullifyOriginHeader =
+ [&existingHeader, isAddonRequest](nsHttpChannel* aChannel) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), existingHeader);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (HasNullRequestOrigin(aChannel, uri, isAddonRequest)) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo();
+ if (info->GetTainting() == mozilla::LoadTainting::CORS) {
+ return false;
+ }
+
+ return ReferrerInfo::ShouldSetNullOriginHeader(aChannel, uri);
+ };
+
+ if (!existingHeader.EqualsLiteral("null") &&
+ shouldNullifyOriginHeader(this)) {
+ LOG(("nsHttpChannel::SetOriginHeader null Origin by Referrer-Policy"));
+ MOZ_ALWAYS_SUCCEEDS(
+ mRequestHead.SetHeader(nsHttp::Origin, "null"_ns, false /* merge */));
+ }
+ return;
+ }
+
+ if (StaticPrefs::network_http_sendOriginHeader() == 0) {
+ // Custom user setting: 0 means never send Origin header.
+ return;
+ }
+
+ // Step 1. Let serializedOrigin be the result of byte-serializing a request
+ // origin with request.
+ nsAutoCString serializedOrigin;
+ nsCOMPtr<nsIURI> uri;
+ {
+ if (NS_FAILED(triggeringPrincipal->GetURI(getter_AddRefs(uri)))) {
+ return;
+ }
+
+ if (!uri) {
+ if (isAddonRequest) {
+ // For add-on compatibility prefer sending no header at all
+ // instead of `Origin: null`.
+ return;
+ }
+
+ // Otherwise use "null" when the triggeringPrincipal's URI is nullptr.
+ serializedOrigin.AssignLiteral("null");
+ } else if (HasNullRequestOrigin(this, uri, isAddonRequest)) {
+ serializedOrigin.AssignLiteral("null");
+ } else {
+ nsContentUtils::GetWebExposedOriginSerialization(uri, serializedOrigin);
+ }
+ }
+
+ // Step 2. If request’s response tainting is "cors" or request’s mode is
+ // "websocket", then append (`Origin`, serializedOrigin) to request’s header
+ // list.
+ //
+ // Note: We don't handle "websocket" here (yet?).
+ if (mLoadInfo->GetTainting() == mozilla::LoadTainting::CORS) {
+ MOZ_ALWAYS_SUCCEEDS(mRequestHead.SetHeader(nsHttp::Origin, serializedOrigin,
+ false /* merge */));
+ return;
+ }
+
+ // Step 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
+ if (mRequestHead.IsGet() || mRequestHead.IsHead()) {
+ return;
+ }
+
+ if (!serializedOrigin.EqualsLiteral("null")) {
+ // Step 3.1. (Implemented by ReferrerInfo::ShouldSetNullOriginHeader)
+ if (ReferrerInfo::ShouldSetNullOriginHeader(this, uri)) {
+ serializedOrigin.AssignLiteral("null");
+ } else if (StaticPrefs::network_http_sendOriginHeader() == 1) {
+ // Non-standard: Restrict Origin to same-origin loads if requested by user
+ nsAutoCString currentOrigin;
+ nsContentUtils::GetWebExposedOriginSerialization(mURI, currentOrigin);
+ if (!serializedOrigin.EqualsIgnoreCase(currentOrigin.get())) {
+ // Origin header suppressed by user setting.
+ serializedOrigin.AssignLiteral("null");
+ }
+ }
+ }
+
+ // Step 3.2. Append (`Origin`, serializedOrigin) to request’s header list.
+ MOZ_ALWAYS_SUCCEEDS(mRequestHead.SetHeader(nsHttp::Origin, serializedOrigin,
+ false /* merge */));
+}
+
+void nsHttpChannel::SetDoNotTrack() {
+ /**
+ * 'DoNotTrack' header should be added if 'privacy.donottrackheader.enabled'
+ * is true or tracking protection is enabled. See bug 1258033.
+ */
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+
+ if ((loadContext && loadContext->UseTrackingProtection()) ||
+ StaticPrefs::privacy_donottrackheader_enabled()) {
+ DebugOnly<nsresult> rv =
+ mRequestHead.SetHeader(nsHttp::DoNotTrack, "1"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void nsHttpChannel::SetGlobalPrivacyControl() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ if (StaticPrefs::privacy_globalprivacycontrol_functionality_enabled() &&
+ (StaticPrefs::privacy_globalprivacycontrol_enabled() ||
+ (StaticPrefs::privacy_globalprivacycontrol_pbmode_enabled() &&
+ NS_UsePrivateBrowsing(this)))) {
+ // Send the header with a value of 1 to indicate opting-out
+ DebugOnly<nsresult> rv =
+ mRequestHead.SetHeader(nsHttp::GlobalPrivacyControl, "1"_ns, false);
+ }
+}
+
+void nsHttpChannel::ReportRcwnStats(bool isFromNet) {
+ if (!StaticPrefs::network_http_rcwn_enabled()) {
+ return;
+ }
+
+ if (isFromNet) {
+ if (mRaceCacheWithNetwork) {
+ gIOService->IncrementNetWonRequestNumber();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_NETWORK_WIN,
+ mTransferSize);
+ if (mRaceDelay) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ NetworkDelayedRace);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ NetworkRace);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
+ mTransferSize);
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ NetworkNoRace);
+ }
+ } else {
+ if (mRaceCacheWithNetwork || mRaceDelay) {
+ gIOService->IncrementCacheWonRequestNumber();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_CACHE_WIN,
+ mTransferSize);
+ if (mRaceDelay) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ CacheDelayedRace);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ CacheRace);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
+ mTransferSize);
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ CacheNoRace);
+ }
+ }
+
+ gIOService->IncrementRequestNumber();
+}
+
+static const size_t kPositiveBucketNumbers = 34;
+static const int64_t kPositiveBucketLevels[kPositiveBucketNumbers] = {
+ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200,
+ 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000,
+ 6000, 7000, 8000, 9000, 10000, 20000, 30000, 40000, 50000, 60000};
+
+/**
+ * For space efficiency, we collect finer resolution for small difference
+ * between net and cache time, coarser for larger.
+ * Bucket #40 for a tie.
+ * #41 to #50 indicates cache wins by 1ms to 100ms, split equally.
+ * #51 to #59 indicates cache wins by 101ms to 1000ms.
+ * #60 to #68 indicates cache wins by 1s to 10s.
+ * #69 to #73 indicates cache wins by 11s to 60s.
+ * #74 indicates cache wins by more than 1 minute.
+ *
+ * #39 to #30 indicates network wins by 1ms to 100ms, split equally.
+ * #29 to #21 indicates network wins by 101ms to 1000ms.
+ * #20 to #12 indicates network wins by 1s to 10s.
+ * #11 to #7 indicates network wins by 11s to 60s.
+ * #6 indicates network wins by more than 1 minute.
+ *
+ * Other bucket numbers are reserved.
+ */
+inline int64_t nsHttpChannel::ComputeTelemetryBucketNumber(
+ int64_t difftime_ms) {
+ int64_t absBucketIndex =
+ std::lower_bound(kPositiveBucketLevels,
+ kPositiveBucketLevels + kPositiveBucketNumbers,
+ static_cast<int64_t>(mozilla::Abs(difftime_ms))) -
+ kPositiveBucketLevels;
+
+ return difftime_ms >= 0 ? 40 + absBucketIndex : 40 - absBucketIndex;
+}
+
+void nsHttpChannel::ReportNetVSCacheTelemetry() {
+ nsresult rv;
+ if (!mCacheEntry) {
+ return;
+ }
+
+ // We only report telemetry if the entry is persistent (on disk)
+ bool persistent;
+ rv = mCacheEntry->GetPersistent(&persistent);
+ if (NS_FAILED(rv) || !persistent) {
+ return;
+ }
+
+ uint64_t onStartNetTime = 0;
+ if (NS_FAILED(mCacheEntry->GetOnStartTime(&onStartNetTime))) {
+ return;
+ }
+
+ uint64_t onStopNetTime = 0;
+ if (NS_FAILED(mCacheEntry->GetOnStopTime(&onStopNetTime))) {
+ return;
+ }
+
+ uint64_t onStartCacheTime =
+ (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStartDiff = onStartNetTime - onStartCacheTime;
+ onStartDiff = ComputeTelemetryBucketNumber(onStartDiff);
+
+ uint64_t onStopCacheTime = (mCacheReadEnd - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStopDiff = onStopNetTime - onStopCacheTime;
+ onStopDiff = ComputeTelemetryBucketNumber(onStopDiff);
+
+ if (mDidReval) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_REVALIDATED_V2,
+ onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_REVALIDATED_V2,
+ onStopDiff);
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTREVALIDATED_V2, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTREVALIDATED_V2,
+ onStopDiff);
+ }
+
+ if (mDidReval) {
+ // We don't report revalidated probes as the data would be skewed.
+ return;
+ }
+
+ if (mCacheOpenWithPriority) {
+ if (mCacheQueueSizeWhenOpen < 5) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_HIGHPRI_V2, onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_HIGHPRI_V2, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_HIGHPRI_V2, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_HIGHPRI_V2,
+ onStopDiff);
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_HIGHPRI_V2, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_HIGHPRI_V2,
+ onStopDiff);
+ }
+ } else { // The limits are higher for normal priority cache queues
+ if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_NORMALPRI_V2,
+ onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_NORMALPRI_V2, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 50) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_NORMALPRI_V2, onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_NORMALPRI_V2, onStopDiff);
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_NORMALPRI_V2, onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_NORMALPRI_V2, onStopDiff);
+ }
+ }
+
+ uint32_t diskStorageSizeK = 0;
+ rv = mCacheEntry->GetDiskStorageSizeInKB(&diskStorageSizeK);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // No significant difference was observed between different sizes for
+ // |onStartDiff|
+ if (diskStorageSizeK < 256) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_V2,
+ onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_V2,
+ onStopDiff);
+ }
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_delayCacheEntryOpeningBy(int32_t aTimeout) {
+ LOG(("nsHttpChannel::Test_delayCacheEntryOpeningBy this=%p timeout=%d", this,
+ aTimeout));
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ mRaceCacheWithNetwork = true;
+ mCacheOpenDelay = aTimeout;
+ if (mCacheOpenTimer) {
+ mCacheOpenTimer->SetDelay(aTimeout);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_triggerDelayedOpenCacheEntry() {
+ LOG(("nsHttpChannel::Test_triggerDelayedOpenCacheEntry this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ nsresult rv;
+ if (!mCacheOpenDelay) {
+ // No delay was set.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!mCacheOpenFunc) {
+ // There should be a runnable.
+ return NS_ERROR_FAILURE;
+ }
+ if (mCacheOpenTimer) {
+ rv = mCacheOpenTimer->Cancel();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCacheOpenTimer = nullptr;
+ }
+ mCacheOpenDelay = 0;
+ // Avoid re-entrancy issues by nulling our mCacheOpenFunc before calling it.
+ std::function<void(nsHttpChannel*)> cacheOpenFunc = nullptr;
+ std::swap(cacheOpenFunc, mCacheOpenFunc);
+ cacheOpenFunc(this);
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::TriggerNetworkWithDelay(uint32_t aDelay) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ LOG(("nsHttpChannel::TriggerNetworkWithDelay [this=%p, delay=%u]\n", this,
+ aDelay));
+
+ if (mCanceled) {
+ LOG((" channel was canceled.\n"));
+ return mStatus;
+ }
+
+ // If a network request has already gone out, there is no point in
+ // doing this again.
+ if (mNetworkTriggered) {
+ LOG((" network already triggered. Returning.\n"));
+ return NS_OK;
+ }
+
+ if (mNetworkTriggerDelay) {
+ aDelay = mNetworkTriggerDelay;
+ }
+
+ if (!aDelay) {
+ // We cannot call TriggerNetwork() directly here, because it would
+ // cause performance regression in tp6 tests, see bug 1398847.
+ return NS_DispatchToMainThread(
+ NewRunnableMethod("net::nsHttpChannel::TriggerNetworkWithDelay", this,
+ &nsHttpChannel::TriggerNetwork),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(!mNetworkTriggerTimer);
+ mNetworkTriggerTimer = NS_NewTimer();
+ auto callback = MakeRefPtr<TimerCallback>(this);
+ LOG(("Creating new networkTriggertimer for delay"));
+ mNetworkTriggerTimer->InitWithCallback(callback, aDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::TriggerNetwork() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ LOG(("nsHttpChannel::TriggerNetwork [this=%p]\n", this));
+
+ if (mCanceled) {
+ LOG((" channel was canceled.\n"));
+ return mStatus;
+ }
+
+ // If a network request has already gone out, there is no point in
+ // doing this again.
+ if (mNetworkTriggered) {
+ LOG((" network already triggered. Returning.\n"));
+ return NS_OK;
+ }
+
+ mNetworkTriggered = true;
+ if (mNetworkTriggerTimer) {
+ mNetworkTriggerTimer->Cancel();
+ mNetworkTriggerTimer = nullptr;
+ }
+
+ // If we are waiting for a proxy request, that means we can't trigger
+ // the next step just yet. We need for mConnectionInfo to be non-null
+ // before we call ContinueConnect. OnProxyAvailable will trigger
+ // BeginConnect, and Connect will call ContinueConnect even if it's
+ // for the cache callbacks.
+ if (mProxyRequest) {
+ LOG((" proxy request in progress. Delaying network trigger.\n"));
+ mWaitingForProxy = true;
+ return NS_OK;
+ }
+
+ // If |mCacheOpenFunc| is assigned, we're delaying opening the entry to
+ // simulate racing. Although cache entry opening hasn't started yet, we're
+ // actually racing, so we must set mRaceCacheWithNetwork to true now.
+ mRaceCacheWithNetwork =
+ AwaitingCacheCallbacks() &&
+ (mCacheOpenFunc || StaticPrefs::network_http_rcwn_enabled());
+
+ LOG((" triggering network rcwn=%d\n", bool(mRaceCacheWithNetwork)));
+ return ContinueConnect();
+}
+
+void nsHttpChannel::MaybeRaceCacheWithNetwork() {
+ nsresult rv;
+
+ nsCOMPtr<nsINetworkLinkService> netLinkSvc =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ uint32_t linkType;
+ rv = netLinkSvc->GetLinkType(&linkType);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (!(linkType == nsINetworkLinkService::LINK_TYPE_ETHERNET ||
+#ifndef MOZ_WIDGET_ANDROID
+ // On Android we don't assume an unknown link type is unmetered
+ linkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN ||
+#endif
+ linkType == nsINetworkLinkService::LINK_TYPE_USB ||
+ linkType == nsINetworkLinkService::LINK_TYPE_WIFI)) {
+ return;
+ }
+
+ // Don't trigger the network if the load flags say so.
+ if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
+ return;
+ }
+
+ // We must not race if the channel has a failure status code.
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ // If a CORS Preflight is required we must not race.
+ if (LoadRequireCORSPreflight() && !LoadIsCorsPreflightDone()) {
+ return;
+ }
+
+ if (CacheFileUtils::CachePerfStats::IsCacheSlow()) {
+ // If the cache is slow, trigger the network request immediately.
+ mRaceDelay = 0;
+ } else {
+ // Give cache a headstart of 3 times the average cache entry open time.
+ mRaceDelay = CacheFileUtils::CachePerfStats::GetAverage(
+ CacheFileUtils::CachePerfStats::ENTRY_OPEN, true) *
+ 3;
+ // We use microseconds in CachePerfStats but we need milliseconds
+ // for TriggerNetwork.
+ mRaceDelay /= 1000;
+ }
+
+ mRaceDelay = clamped<uint32_t>(
+ mRaceDelay, StaticPrefs::network_http_rcwn_min_wait_before_racing_ms(),
+ StaticPrefs::network_http_rcwn_max_wait_before_racing_ms());
+
+ MOZ_ASSERT(StaticPrefs::network_http_rcwn_enabled() || mNetworkTriggerDelay,
+ "The pref must be turned on.");
+ LOG(("nsHttpChannel::MaybeRaceCacheWithNetwork [this=%p, delay=%u]\n", this,
+ mRaceDelay));
+
+ TriggerNetworkWithDelay(mRaceDelay);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_triggerNetwork(int32_t aTimeout) {
+ LOG(("nsHttpChannel::Test_triggerNetwork this=%p timeout=%d", this,
+ aTimeout));
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ // We set the trigger delay to the specified timeout.
+ mRaceCacheWithNetwork = true;
+ mNetworkTriggerDelay = aTimeout;
+
+ // If we already have a timer, set the delay/
+ if (mNetworkTriggerTimer) {
+ // If the timeout is 0 and there is a timer, we can trigger
+ // the network immediately.
+ MOZ_ASSERT(LoadWasOpened(), "Must have been opened before");
+ if (!aTimeout) {
+ return TriggerNetwork();
+ }
+ mNetworkTriggerTimer->SetDelay(aTimeout);
+ }
+ return NS_OK;
+}
+
+nsHttpChannel::TimerCallback::TimerCallback(nsHttpChannel* aChannel)
+ : mChannel(aChannel) {}
+
+NS_IMPL_ISUPPORTS(nsHttpChannel::TimerCallback, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP
+nsHttpChannel::TimerCallback::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsHttpChannel");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::TimerCallback::Notify(nsITimer* aTimer) {
+ if (aTimer == mChannel->mCacheOpenTimer) {
+ return mChannel->Test_triggerDelayedOpenCacheEntry();
+ }
+ if (aTimer == mChannel->mNetworkTriggerTimer) {
+ return mChannel->TriggerNetwork();
+ }
+ MOZ_CRASH("Unknown timer");
+
+ return NS_OK;
+}
+
+bool nsHttpChannel::EligibleForTailing() {
+ if (!(mClassOfService.Flags() & nsIClassOfService::Tail)) {
+ return false;
+ }
+
+ if (mClassOfService.Flags() &
+ (nsIClassOfService::UrgentStart | nsIClassOfService::Leader |
+ nsIClassOfService::TailForbidden)) {
+ return false;
+ }
+
+ if (mClassOfService.Flags() & nsIClassOfService::Unblocked &&
+ !(mClassOfService.Flags() & nsIClassOfService::TailAllowed)) {
+ return false;
+ }
+
+ if (IsNavigation()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsHttpChannel::WaitingForTailUnblock() {
+ nsresult rv;
+
+ if (!gHttpHandler->IsTailBlockingEnabled()) {
+ LOG(("nsHttpChannel %p tail-blocking disabled", this));
+ return false;
+ }
+
+ if (!EligibleForTailing()) {
+ LOG(("nsHttpChannel %p not eligible for tail-blocking", this));
+ AddAsNonTailRequest();
+ return false;
+ }
+
+ if (!EnsureRequestContext()) {
+ LOG(("nsHttpChannel %p no request context", this));
+ return false;
+ }
+
+ LOG(("nsHttpChannel::WaitingForTailUnblock this=%p, rc=%p", this,
+ mRequestContext.get()));
+
+ bool blocked;
+ rv = mRequestContext->IsContextTailBlocked(this, &blocked);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ LOG((" blocked=%d", blocked));
+
+ return blocked;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequestTailUnblockCallback
+//-----------------------------------------------------------------------------
+
+// Must be implemented in the leaf class because we don't have
+// AsyncAbort in HttpBaseChannel.
+NS_IMETHODIMP
+nsHttpChannel::OnTailUnblock(nsresult rv) {
+ LOG(("nsHttpChannel::OnTailUnblock this=%p rv=%" PRIx32 " rc=%p", this,
+ static_cast<uint32_t>(rv), mRequestContext.get()));
+
+ MOZ_RELEASE_ASSERT(mOnTailUnblock);
+
+ if (NS_FAILED(mStatus)) {
+ rv = mStatus;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ auto callback = mOnTailUnblock;
+ mOnTailUnblock = nullptr;
+ rv = (this->*callback)();
+ }
+
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::SetWarningReporter(
+ HttpChannelSecurityWarningReporter* aReporter) {
+ LOG(("nsHttpChannel [this=%p] SetWarningReporter [%p]", this, aReporter));
+ mWarningReporter = aReporter;
+}
+
+HttpChannelSecurityWarningReporter* nsHttpChannel::GetWarningReporter() {
+ LOG(("nsHttpChannel [this=%p] GetWarningReporter [%p]", this,
+ mWarningReporter.get()));
+ return mWarningReporter.get();
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+//
+// Should only be called by nsMediaSniffer::GetMIMETypeFromContent and
+// imageLoader::GetMIMETypeFromContent when the content type can be
+// recognized by these sniffers.
+void nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck(
+ SnifferType aType) {
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ // This method covers steps, 8 and 10.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (NeedOpaqueResponseAllowedCheckAfterSniff()) {
+ MOZ_ASSERT(mCachedOpaqueResponseBlockingPref);
+
+ // If the sniffer type is media and the request comes from a media element,
+ // we would like to check:
+ // - Whether the information provided by the media element shows it's an
+ // initial request.
+ // - Whether the response's status is either 200 or 206.
+ //
+ // If any of the results is false, then we set
+ // mBlockOpaqueResponseAfterSniff to true and block the response later.
+ if (aType == SnifferType::Media) {
+ // Step 8
+ MOZ_ASSERT(mLoadInfo);
+
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ bool isInitialRequest;
+ mLoadInfo->GetIsMediaInitialRequest(&isInitialRequest);
+ MOZ_ASSERT(isInitialRequest);
+
+ if (!isInitialRequest) {
+ // Step 8.1
+ BlockOpaqueResponseAfterSniff(
+ u"media request after sniffing, but not initial request"_ns,
+ OpaqueResponseBlockedTelemetryReason::MEDIA_NOT_INITIAL);
+ return;
+ }
+
+ if (mResponseHead->Status() != 200 && mResponseHead->Status() != 206) {
+ // Step 8.2
+ BlockOpaqueResponseAfterSniff(
+ u"media request's response status is neither 200 nor 206"_ns,
+ OpaqueResponseBlockedTelemetryReason::MEDIA_INCORRECT_RESP);
+ return;
+ }
+ }
+ }
+
+ // Step 8.3 if `aType == SnifferType::Media`
+ // Step 9 can be skipped, only `HTMLMediaElement` ever sets isMediaRequest.
+ // Step 10 if `aType == SnifferType::Image`
+ AllowOpaqueResponseAfterSniff();
+ }
+}
+
+namespace {
+
+class CopyNonDefaultHeaderVisitor final : public nsIHttpHeaderVisitor {
+ nsCOMPtr<nsIHttpChannel> mTarget;
+
+ ~CopyNonDefaultHeaderVisitor() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ if (aValue.IsEmpty()) {
+ return mTarget->SetEmptyRequestHeader(aHeader);
+ }
+ return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */);
+ }
+
+ public:
+ explicit CopyNonDefaultHeaderVisitor(nsIHttpChannel* aTarget)
+ : mTarget(aTarget) {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(CopyNonDefaultHeaderVisitor, nsIHttpHeaderVisitor)
+
+} // anonymous namespace
+
+nsresult nsHttpChannel::RedirectToInterceptedChannel() {
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ RefPtr<InterceptedHttpChannel> intercepted =
+ InterceptedHttpChannel::CreateForInterception(
+ mChannelCreationTime, mChannelCreationTimestamp, mAsyncOpenTime);
+
+ ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+
+ nsresult rv = intercepted->Init(
+ mURI, mCaps, static_cast<nsProxyInfo*>(mProxyInfo.get()),
+ mProxyResolveFlags, mProxyURI, mChannelId, type, redirectLoadInfo);
+
+ rv = SetupReplacementChannel(mURI, intercepted, true,
+ nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Some APIs, like fetch(), allow content to set non-standard headers.
+ // Normally these APIs are responsible for copying these headers across
+ // redirects. In the e10s parent-side intercept case, though, we currently
+ // "hide" the internal redirect to the InterceptedHttpChannel. So the
+ // fetch() API does not have the opportunity to move headers over.
+ // Therefore, we do it automatically here.
+ //
+ // Once child-side interception is removed and the internal redirect no
+ // longer needs to be "hidden", then this header copying code can be
+ // removed.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new CopyNonDefaultHeaderVisitor(intercepted);
+ rv = VisitNonDefaultRequestHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = intercepted;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(
+ this, intercepted, nsIChannelEventSink::REDIRECT_INTERNAL);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = WaitForRedirectCallback();
+ }
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ }
+
+ return rv;
+}
+
+void nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown() {
+ nsCOMPtr<nsICookieJarSettings> cjs;
+ if (mLoadInfo) {
+ Unused << mLoadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
+ }
+ if (!cjs) {
+ cjs = net::CookieJarSettings::Create(mLoadInfo->GetLoadingPrincipal());
+ }
+ if (cjs->GetRejectThirdPartyContexts()) {
+ bool isPrivate = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ // If our referrer has been set before, and our referrer policy is unset
+ // (default policy) if we thought the channel wasn't a third-party
+ // tracking channel, we may need to set our referrer with referrer policy
+ // once again to ensure our defaults properly take effect now.
+ if (mReferrerInfo) {
+ ReferrerInfo* referrerInfo =
+ static_cast<ReferrerInfo*>(mReferrerInfo.get());
+
+ if (referrerInfo->IsPolicyOverrided() &&
+ referrerInfo->ReferrerPolicy() ==
+ ReferrerInfo::GetDefaultReferrerPolicy(nullptr, nullptr,
+ isPrivate)) {
+ nsCOMPtr<nsIReferrerInfo> newReferrerInfo =
+ referrerInfo->CloneWithNewPolicy(
+ ReferrerInfo::GetDefaultReferrerPolicy(this, mURI, isPrivate));
+ // The arguments passed to SetReferrerInfoInternal here should mirror
+ // the arguments passed in
+ // HttpChannelChild::RecvOverrideReferrerInfoDuringBeginConnect().
+ SetReferrerInfoInternal(newReferrerInfo, false, true, true);
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
+ if (httpParent) {
+ httpParent->OverrideReferrerInfoDuringBeginConnect(newReferrerInfo);
+ }
+ }
+ }
+ }
+}
+
+namespace {
+
+class BackgroundRevalidatingListener : public nsIStreamListener {
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ private:
+ virtual ~BackgroundRevalidatingListener() = default;
+};
+
+NS_IMETHODIMP
+BackgroundRevalidatingListener::OnStartRequest(nsIRequest* request) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundRevalidatingListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* input,
+ uint64_t offset,
+ uint32_t count) {
+ uint32_t bytesRead = 0;
+ return input->ReadSegments(NS_DiscardSegment, nullptr, count, &bytesRead);
+}
+
+NS_IMETHODIMP
+BackgroundRevalidatingListener::OnStopRequest(nsIRequest* request,
+ nsresult status) {
+ if (NS_FAILED(status)) {
+ return status;
+ }
+
+ nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(request));
+ if (gHttpHandler) {
+ gHttpHandler->OnBackgroundRevalidation(channel);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(BackgroundRevalidatingListener, nsIStreamListener,
+ nsIRequestObserver)
+
+} // namespace
+
+void nsHttpChannel::PerformBackgroundCacheRevalidation() {
+ if (!StaticPrefs::network_http_stale_while_revalidate_enabled()) {
+ return;
+ }
+
+ // This is a channel doing a revalidation. It shouldn't do it again.
+ if (mStaleRevalidation) {
+ return;
+ }
+
+ LOG(("nsHttpChannel::PerformBackgroundCacheRevalidation %p", this));
+
+ Unused << NS_DispatchToMainThreadQueue(
+ NewIdleRunnableMethod(
+ "nsHttpChannel::PerformBackgroundCacheRevalidation", this,
+ &nsHttpChannel::PerformBackgroundCacheRevalidationNow),
+ EventQueuePriority::Idle);
+}
+
+void nsHttpChannel::PerformBackgroundCacheRevalidationNow() {
+ LOG(("nsHttpChannel::PerformBackgroundCacheRevalidationNow %p", this));
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ nsLoadFlags loadFlags = mLoadFlags | LOAD_ONLY_IF_MODIFIED | VALIDATE_ALWAYS |
+ LOAD_BACKGROUND | LOAD_BYPASS_SERVICE_WORKER;
+
+ nsCOMPtr<nsIChannel> validatingChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(validatingChannel), mURI, mLoadInfo,
+ nullptr /* performance storage */, mLoadGroup,
+ mCallbacks, loadFlags);
+ if (NS_FAILED(rv)) {
+ LOG((" failed to created the channel, rv=0x%08x",
+ static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ nsCOMPtr<nsISupportsPriority> priority(do_QueryInterface(validatingChannel));
+ if (priority) {
+ priority->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(validatingChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Tail);
+ }
+
+ RefPtr<nsHttpChannel> httpChan = do_QueryObject(validatingChannel);
+ if (httpChan) {
+ httpChan->mStaleRevalidation = true;
+ }
+
+ RefPtr<BackgroundRevalidatingListener> listener =
+ new BackgroundRevalidatingListener();
+ rv = validatingChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ LOG((" failed to open the channel, rv=0x%08x", static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ LOG((" %p is re-validating with a new channel %p", this,
+ validatingChannel.get()));
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) {
+ mEarlyHintObserver = aObserver;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::EarlyHint(const nsACString& aLinkHeader,
+ const nsACString& aReferrerPolicy,
+ const nsACString& aCspHeader) {
+ LOG(("nsHttpChannel::EarlyHint.\n"));
+
+ if (mEarlyHintObserver && nsContentUtils::ComputeIsSecureContext(this)) {
+ LOG(("nsHttpChannel::EarlyHint propagated.\n"));
+ mEarlyHintObserver->EarlyHint(aLinkHeader, aReferrerPolicy, aCspHeader);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) {
+ mWebTransportSessionEventListener = aListener;
+ return NS_OK;
+}
+
+already_AddRefed<WebTransportSessionEventListener>
+nsHttpChannel::GetWebTransportSessionEventListener() {
+ RefPtr<WebTransportSessionEventListener> wt =
+ mWebTransportSessionEventListener;
+ return wt.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h
new file mode 100644
index 0000000000..dede167f87
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -0,0 +1,869 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et cin ts=4 sw=2 sts=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 nsHttpChannel_h__
+#define nsHttpChannel_h__
+
+#include "AlternateServices.h"
+#include "AutoClose.h"
+#include "HttpBaseChannel.h"
+#include "TimingStruct.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/extensions/PStreamFilterParent.h"
+#include "mozilla/net/DocumentLoadListener.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICachingChannel.h"
+#include "nsICorsPreflightCallback.h"
+#include "nsIDNSListener.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIRaceCacheWithNetwork.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+class nsDNSPrefetch;
+class nsICancelable;
+class nsIDNSRecord;
+class nsIDNSHTTPSSVCRecord;
+class nsIHttpChannelAuthProvider;
+class nsInputStreamPump;
+class nsITransportSecurityInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsChannelClassifier;
+class HttpChannelSecurityWarningReporter;
+
+using DNSPromise = MozPromise<nsCOMPtr<nsIDNSRecord>, nsresult, false>;
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel
+//-----------------------------------------------------------------------------
+
+// Use to support QI nsIChannel to nsHttpChannel
+#define NS_HTTPCHANNEL_IID \
+ { \
+ 0x301bf95b, 0x7bb3, 0x4ae1, { \
+ 0xa9, 0x71, 0x40, 0xbc, 0xfa, 0x81, 0xde, 0x12 \
+ } \
+ }
+
+class nsHttpChannel final : public HttpBaseChannel,
+ public HttpAsyncAborter<nsHttpChannel>,
+ public nsICachingChannel,
+ public nsICacheEntryOpenCallback,
+ public nsITransportEventSink,
+ public nsIProtocolProxyCallback,
+ public nsIHttpAuthenticableChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIThreadRetargetableRequest,
+ public nsIThreadRetargetableStreamListener,
+ public nsIDNSListener,
+ public nsSupportsWeakReference,
+ public nsICorsPreflightCallback,
+ public nsIRaceCacheWithNetwork,
+ public nsIRequestTailUnblockCallback,
+ public nsIEarlyHintObserver {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSICACHINGCHANNEL
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSIDNSLISTENER
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCHANNEL_IID)
+ NS_DECL_NSIRACECACHEWITHNETWORK
+ NS_DECL_NSIREQUESTTAILUNBLOCKCALLBACK
+ NS_DECL_NSIEARLYHINTOBSERVER
+
+ // nsIHttpAuthenticableChannel. We can't use
+ // NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
+ // others.
+ NS_IMETHOD GetIsSSL(bool* aIsSSL) override;
+ NS_IMETHOD GetProxyMethodIsConnect(bool* aProxyMethodIsConnect) override;
+ NS_IMETHOD GetServerResponseHeader(
+ nsACString& aServerResponseHeader) override;
+ NS_IMETHOD GetProxyChallenges(nsACString& aChallenges) override;
+ NS_IMETHOD GetWWWChallenges(nsACString& aChallenges) override;
+ NS_IMETHOD SetProxyCredentials(const nsACString& aCredentials) override;
+ NS_IMETHOD SetWWWCredentials(const nsACString& aCredentials) override;
+ NS_IMETHOD OnAuthAvailable() override;
+ NS_IMETHOD OnAuthCancelled(bool userCancel) override;
+ NS_IMETHOD CloseStickyConnection() override;
+ NS_IMETHOD ConnectionRestartable(bool) override;
+ // Functions we implement from nsIHttpAuthenticableChannel but are
+ // declared in HttpBaseChannel must be implemented in this class. We
+ // just call the HttpBaseChannel:: impls.
+ NS_IMETHOD GetLoadFlags(nsLoadFlags* aLoadFlags) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+
+ nsHttpChannel();
+
+ [[nodiscard]] virtual nsresult Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI* aProxyURI, uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo) override;
+
+ [[nodiscard]] nsresult OnPush(uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction);
+
+ static bool IsRedirectStatus(uint32_t status);
+ static bool WillRedirect(const nsHttpResponseHead& response);
+
+ // Methods HttpBaseChannel didn't implement for us or that we override.
+ //
+ // nsIRequest
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ // nsIHttpChannel
+ NS_IMETHOD GetEncodedBodySize(uint64_t* aEncodedBodySize) override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
+ NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;
+ NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override;
+ NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
+ NS_IMETHOD CancelByURLClassifier(nsresult aErrorCode) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD SetClassOfService(ClassOfService cos) override;
+ NS_IMETHOD SetIncremental(bool incremental) override;
+
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aCallbacks) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ // nsITimedChannel
+ NS_IMETHOD GetDomainLookupStart(
+ mozilla::TimeStamp* aDomainLookupStart) override;
+ NS_IMETHOD GetDomainLookupEnd(mozilla::TimeStamp* aDomainLookupEnd) override;
+ NS_IMETHOD GetConnectStart(mozilla::TimeStamp* aConnectStart) override;
+ NS_IMETHOD GetTcpConnectEnd(mozilla::TimeStamp* aTcpConnectEnd) override;
+ NS_IMETHOD GetSecureConnectionStart(
+ mozilla::TimeStamp* aSecureConnectionStart) override;
+ NS_IMETHOD GetConnectEnd(mozilla::TimeStamp* aConnectEnd) override;
+ NS_IMETHOD GetRequestStart(mozilla::TimeStamp* aRequestStart) override;
+ NS_IMETHOD GetResponseStart(mozilla::TimeStamp* aResponseStart) override;
+ NS_IMETHOD GetResponseEnd(mozilla::TimeStamp* aResponseEnd) override;
+
+ NS_IMETHOD GetTransactionPending(
+ mozilla::TimeStamp* aTransactionPending) override;
+
+ // nsICorsPreflightCallback
+ NS_IMETHOD OnPreflightSucceeded() override;
+ NS_IMETHOD OnPreflightFailed(nsresult aError) override;
+
+ [[nodiscard]] nsresult AddSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) override;
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override;
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override;
+
+ void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter);
+ HttpChannelSecurityWarningReporter* GetWarningReporter();
+
+ bool DataSentToChildProcess() { return LoadDataSentToChildProcess(); }
+
+ enum class SnifferType { Media, Image };
+ void DisableIsOpaqueResponseAllowedAfterSniffCheck(SnifferType aType);
+
+ public: /* internal necko use only */
+ uint32_t GetRequestTime() const { return mRequestTime; }
+
+ void AsyncOpenFinal(TimeStamp aTimeStamp);
+
+ [[nodiscard]] nsresult OpenCacheEntry(bool isHttps);
+ [[nodiscard]] nsresult OpenCacheEntryInternal(bool isHttps);
+ [[nodiscard]] nsresult ContinueConnect();
+
+ [[nodiscard]] nsresult StartRedirectChannelToURI(nsIURI*, uint32_t);
+
+ SnifferCategoryType GetSnifferCategoryType() const {
+ return mSnifferCategoryType;
+ }
+
+ // Helper to keep cache callbacks wait flags consistent
+ class AutoCacheWaitFlags {
+ public:
+ explicit AutoCacheWaitFlags(nsHttpChannel* channel)
+ : mChannel(channel), mKeep(0) {
+ // Flags must be set before entering any AsyncOpenCacheEntry call.
+ mChannel->StoreWaitForCacheEntry(nsHttpChannel::WAIT_FOR_CACHE_ENTRY);
+ }
+
+ void Keep(uint32_t flags) {
+ // Called after successful call to appropriate AsyncOpenCacheEntry call.
+ mKeep |= flags;
+ }
+
+ ~AutoCacheWaitFlags() {
+ // Keep only flags those are left to be wait for.
+ mChannel->StoreWaitForCacheEntry(mChannel->LoadWaitForCacheEntry() &
+ mKeep);
+ }
+
+ private:
+ nsHttpChannel* mChannel;
+ uint32_t mKeep : 1;
+ };
+
+ bool AwaitingCacheCallbacks();
+ void SetCouldBeSynthesized();
+
+ // Return true if the latest ODA is invoked by mCachePump.
+ // Should only be called on the same thread as ODA.
+ bool IsReadingFromCache() const { return mIsReadingFromCache; }
+
+ base::ProcessId ProcessId();
+
+ using ChildEndpointPromise =
+ MozPromise<mozilla::ipc::Endpoint<extensions::PStreamFilterChild>, bool,
+ true>;
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter();
+
+ already_AddRefed<WebTransportSessionEventListener>
+ GetWebTransportSessionEventListener();
+
+ private: // used for alternate service validation
+ RefPtr<TransactionObserver> mTransactionObserver;
+
+ public:
+ void SetTransactionObserver(TransactionObserver* arg) {
+ mTransactionObserver = arg;
+ }
+ TransactionObserver* GetTransactionObserver() { return mTransactionObserver; }
+
+ CacheDisposition mCacheDisposition{kCacheUnresolved};
+
+ protected:
+ virtual ~nsHttpChannel();
+
+ private:
+ using nsContinueRedirectionFunc = nsresult (nsHttpChannel::*)(nsresult);
+
+ // Directly call |aFunc| if the channel is not canceled and not suspended.
+ // Otherwise, set |aFunc| to |mCallOnResume| and wait until the channel
+ // resumes.
+ nsresult CallOrWaitForResume(
+ const std::function<nsresult(nsHttpChannel*)>& aFunc);
+
+ bool RequestIsConditional();
+ void HandleContinueCancellingByURLClassifier(nsresult aErrorCode);
+ nsresult CancelInternal(nsresult status);
+ void ContinueCancellingByURLClassifier(nsresult aErrorCode);
+
+ // Connections will only be established in this function.
+ // (including DNS prefetch and speculative connection.)
+ void MaybeResolveProxyAndBeginConnect();
+ nsresult MaybeStartDNSPrefetch();
+
+ // Tells the channel to resolve the origin of the end server we are connecting
+ // to.
+ static uint16_t const DNS_PREFETCH_ORIGIN = 1 << 0;
+ // Tells the channel to resolve the host name of the proxy.
+ static uint16_t const DNS_PREFETCH_PROXY = 1 << 1;
+ // Will be set if the current channel uses an HTTP/HTTPS proxy.
+ static uint16_t const DNS_PROXY_IS_HTTP = 1 << 2;
+ // Tells the channel to wait for the result of the origin server resolution
+ // before any connection attempts are made.
+ static uint16_t const DNS_BLOCK_ON_ORIGIN_RESOLVE = 1 << 3;
+
+ // Based on the proxy configuration determine the strategy for resolving the
+ // end server host name.
+ // Returns a combination of the above flags.
+ uint16_t GetProxyDNSStrategy();
+
+ // We might synchronously or asynchronously call BeginConnect,
+ // which includes DNS prefetch and speculative connection, according to
+ // whether an async tracker lookup is required. If the tracker lookup
+ // is required, this funciton will just return NS_OK and BeginConnect()
+ // will be called when callback. See Bug 1325054 for more information.
+ nsresult BeginConnect();
+ [[nodiscard]] nsresult PrepareToConnect();
+ [[nodiscard]] nsresult OnBeforeConnect();
+ [[nodiscard]] nsresult ContinueOnBeforeConnect(
+ bool aShouldUpgrade, nsresult aStatus, bool aUpgradeWithHTTPSRR = false);
+ nsresult MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade, nsresult aStatus);
+ void OnHTTPSRRAvailable(nsIDNSHTTPSSVCRecord* aRecord);
+ [[nodiscard]] nsresult Connect();
+ void SpeculativeConnect();
+ [[nodiscard]] nsresult SetupTransaction();
+ [[nodiscard]] nsresult CallOnStartRequest();
+ [[nodiscard]] nsresult ProcessResponse();
+ void AsyncContinueProcessResponse();
+ [[nodiscard]] nsresult ContinueProcessResponse1();
+ [[nodiscard]] nsresult ContinueProcessResponse2(nsresult);
+
+ public:
+ void UpdateCacheDisposition(bool aSuccessfulReval, bool aPartialContentUsed);
+ [[nodiscard]] nsresult ContinueProcessResponse3(nsresult);
+ [[nodiscard]] nsresult ContinueProcessResponse4(nsresult);
+ [[nodiscard]] nsresult ProcessNormal();
+ [[nodiscard]] nsresult ContinueProcessNormal(nsresult);
+ void ProcessAltService();
+ bool ShouldBypassProcessNotModified();
+ [[nodiscard]] nsresult ProcessNotModified(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc);
+ [[nodiscard]] nsresult ContinueProcessResponseAfterNotModified(nsresult aRv);
+
+ [[nodiscard]] nsresult AsyncProcessRedirection(uint32_t redirectType);
+ [[nodiscard]] nsresult ContinueProcessRedirection(nsresult);
+ [[nodiscard]] nsresult ContinueProcessRedirectionAfterFallback(nsresult);
+ [[nodiscard]] nsresult ProcessFailedProxyConnect(uint32_t httpStatus);
+ void HandleAsyncAbort();
+ [[nodiscard]] nsresult EnsureAssocReq();
+ void ProcessSSLInformation();
+ bool IsHTTPS();
+
+ [[nodiscard]] nsresult ContinueOnStartRequest1(nsresult);
+ [[nodiscard]] nsresult ContinueOnStartRequest2(nsresult);
+ [[nodiscard]] nsresult ContinueOnStartRequest3(nsresult);
+ [[nodiscard]] nsresult ContinueOnStartRequest4(nsresult);
+
+ void OnClassOfServiceUpdated();
+
+ // redirection specific methods
+ void HandleAsyncRedirect();
+ void HandleAsyncAPIRedirect();
+ [[nodiscard]] nsresult ContinueHandleAsyncRedirect(nsresult);
+ void HandleAsyncNotModified();
+ [[nodiscard]] nsresult PromptTempRedirect();
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI*, nsIChannel*, bool preserveMethod,
+ uint32_t redirectFlags) override;
+ void HandleAsyncRedirectToUnstrippedURI();
+
+ // proxy specific methods
+ [[nodiscard]] nsresult ProxyFailover();
+ [[nodiscard]] nsresult AsyncDoReplaceWithProxy(nsIProxyInfo*);
+ [[nodiscard]] nsresult ResolveProxy();
+
+ // cache specific methods
+ [[nodiscard]] nsresult OnNormalCacheEntryAvailable(nsICacheEntry* aEntry,
+ bool aNew,
+ nsresult aEntryStatus);
+ [[nodiscard]] nsresult OnCacheEntryAvailableInternal(nsICacheEntry* entry,
+ bool aNew,
+ nsresult status);
+ [[nodiscard]] nsresult GenerateCacheKey(uint32_t postID, nsACString& key);
+ [[nodiscard]] nsresult UpdateExpirationTime();
+ [[nodiscard]] nsresult CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength);
+ [[nodiscard]] nsresult ReadFromCache(bool alreadyMarkedValid);
+ void CloseCacheEntry(bool doomOnFailure);
+ [[nodiscard]] nsresult InitCacheEntry();
+ void UpdateInhibitPersistentCachingFlag();
+ [[nodiscard]] nsresult AddCacheEntryHeaders(nsICacheEntry* entry);
+ [[nodiscard]] nsresult FinalizeCacheEntry();
+ [[nodiscard]] nsresult InstallCacheListener(int64_t offset = 0);
+ void MaybeInvalidateCacheEntryForSubsequentGet();
+ void AsyncOnExamineCachedResponse();
+
+ // byte range request specific methods
+ [[nodiscard]] nsresult ProcessPartialContent(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc);
+ [[nodiscard]] nsresult ContinueProcessResponseAfterPartialContent(
+ nsresult aRv);
+ [[nodiscard]] nsresult OnDoneReadingPartialCacheEntry(bool* streamDone);
+
+ [[nodiscard]] nsresult DoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc);
+ [[nodiscard]] nsresult ContinueDoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc);
+ [[nodiscard]] MOZ_NEVER_INLINE nsresult
+ DoConnect(HttpTransactionShell* aTransWithStickyConn = nullptr);
+ [[nodiscard]] nsresult DoConnectActual(
+ HttpTransactionShell* aTransWithStickyConn);
+ [[nodiscard]] nsresult ContinueOnStopRequestAfterAuthRetry(
+ nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
+ HttpTransactionShell* aTransWithStickyConn);
+ [[nodiscard]] nsresult ContinueOnStopRequest(nsresult status, bool aIsFromNet,
+ bool aContentComplete);
+
+ void HandleAsyncRedirectChannelToHttps();
+ [[nodiscard]] nsresult StartRedirectChannelToHttps();
+ [[nodiscard]] nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
+ [[nodiscard]] nsresult OpenRedirectChannel(nsresult rv);
+
+ HttpTrafficCategory CreateTrafficCategory();
+
+ /**
+ * A function that takes care of reading STS and PKP headers and enforcing
+ * STS and PKP load rules. After a secure channel is erected, STS and PKP
+ * requires the channel to be trusted or any STS or PKP header data on
+ * the channel is ignored. This is called from ProcessResponse.
+ */
+ [[nodiscard]] nsresult ProcessSecurityHeaders();
+
+ /**
+ * Taking care of the Content-Signature header and fail the channel if
+ * the signature verification fails or is required but the header is not
+ * present.
+ * This sets mListener to ContentVerifier, which buffers the entire response
+ * before verifying the Content-Signature header. If the verification is
+ * successful, the load proceeds as usual. If the verification fails, a
+ * NS_ERROR_INVALID_SIGNATURE is thrown and a fallback loaded in nsDocShell
+ */
+ [[nodiscard]] nsresult ProcessContentSignatureHeader(
+ nsHttpResponseHead* aResponseHead);
+
+ /**
+ * A function to process HTTP Strict Transport Security (HSTS) headers.
+ * Some basic consistency checks have been applied to the channel. Called
+ * from ProcessSecurityHeaders.
+ */
+ [[nodiscard]] nsresult ProcessHSTSHeader(nsITransportSecurityInfo* aSecInfo);
+
+ void InvalidateCacheEntryForLocation(const char* location);
+ void AssembleCacheKey(const char* spec, uint32_t postID, nsACString& key);
+ [[nodiscard]] nsresult CreateNewURI(const char* loc, nsIURI** newURI);
+ void DoInvalidateCacheEntry(nsIURI* aURI);
+
+ // Ref RFC2616 13.10: "invalidation... MUST only be performed if
+ // the host part is the same as in the Request-URI"
+ inline bool HostPartIsTheSame(nsIURI* uri) {
+ nsAutoCString tmpHost1, tmpHost2;
+ return (NS_SUCCEEDED(mURI->GetAsciiHost(tmpHost1)) &&
+ NS_SUCCEEDED(uri->GetAsciiHost(tmpHost2)) &&
+ (tmpHost1 == tmpHost2));
+ }
+
+ inline static bool DoNotRender3xxBody(nsresult rv) {
+ return rv == NS_ERROR_REDIRECT_LOOP || rv == NS_ERROR_CORRUPTED_CONTENT ||
+ rv == NS_ERROR_UNKNOWN_PROTOCOL || rv == NS_ERROR_MALFORMED_URI ||
+ rv == NS_ERROR_PORT_ACCESS_NOT_ALLOWED;
+ }
+
+ // Report net vs cache time telemetry
+ void ReportNetVSCacheTelemetry();
+ int64_t ComputeTelemetryBucketNumber(int64_t difftime_ms);
+
+ // Report telemetry and stats to about:networking
+ void ReportRcwnStats(bool isFromNet);
+
+ // Create a aggregate set of the current notification callbacks
+ // and ensure the transaction is updated to use it.
+ void UpdateAggregateCallbacks();
+
+ static bool HasQueryString(nsHttpRequestHead::ParsedMethodType method,
+ nsIURI* uri);
+ bool ResponseWouldVary(nsICacheEntry* entry);
+ bool IsResumable(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen = false) const;
+ [[nodiscard]] nsresult MaybeSetupByteRangeRequest(
+ int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen = false);
+ [[nodiscard]] nsresult SetupByteRangeRequest(int64_t partialLen);
+ void UntieByteRangeRequest();
+ void UntieValidationRequest();
+ [[nodiscard]] nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry,
+ bool startBuffering);
+
+ void SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId);
+
+ void SetOriginHeader();
+ void SetDoNotTrack();
+ void SetGlobalPrivacyControl();
+
+ already_AddRefed<nsChannelClassifier> GetOrCreateChannelClassifier();
+
+ // Start an internal redirect to a new InterceptedHttpChannel which will
+ // resolve in firing a ServiceWorker FetchEvent.
+ [[nodiscard]] nsresult RedirectToInterceptedChannel();
+
+ // Start an internal redirect to a new channel for auth retry
+ [[nodiscard]] nsresult RedirectToNewChannelForAuthRetry();
+
+ // Determines and sets content type in the cache entry. It's called when
+ // writing a new entry. The content type is used in cache internally only.
+ void SetCachedContentType();
+
+ private:
+ // this section is for main-thread-only object
+ // all the references need to be proxy released on main thread.
+ // auth specific data
+ nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
+ nsCOMPtr<nsIURI> mRedirectURI;
+ nsCOMPtr<nsIURI> mUnstrippedRedirectURI;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIChannel> mPreflightChannel;
+
+ // nsChannelClassifier checks this channel's URI against
+ // the URI classifier service.
+ // nsChannelClassifier will be invoked twice in InitLocalBlockList() and
+ // BeginConnect(), so save the nsChannelClassifier here to keep the
+ // state of whether tracking protection is enabled or not.
+ RefPtr<nsChannelClassifier> mChannelClassifier;
+
+ // Proxy release all members above on main thread.
+ void ReleaseMainThreadOnlyReferences();
+
+ // Called after the channel is made aware of its tracking status in order
+ // to readjust the referrer if needed according to the referrer default
+ // policy preferences.
+ void ReEvaluateReferrerAfterTrackingStatusIsKnown();
+
+ // Create a dummy channel for the same principal, out of the load group
+ // just to revalidate the cache entry. We don't care if this fails.
+ // This method can be called on any thread, and creates an idle task
+ // to perform the revalidation with delay.
+ void PerformBackgroundCacheRevalidation();
+ // This method can only be called on the main thread.
+ void PerformBackgroundCacheRevalidationNow();
+
+ private:
+ nsCOMPtr<nsICancelable> mProxyRequest;
+
+ nsCOMPtr<nsIRequest> mTransactionPump;
+ RefPtr<HttpTransactionShell> mTransaction;
+ RefPtr<HttpTransactionShell> mTransactionSticky;
+
+ uint64_t mLogicalOffset{0};
+
+ // cache specific data
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ // This will be set during OnStopRequest() before calling CloseCacheEntry(),
+ // but only if the listener wants to use alt-data (signaled by
+ // HttpBaseChannel::mPreferredCachedAltDataType being not empty)
+ // Needed because calling openAlternativeOutputStream needs a reference
+ // to the cache entry.
+ nsCOMPtr<nsICacheEntry> mAltDataCacheEntry;
+
+ nsCOMPtr<nsIURI> mCacheEntryURI;
+ nsCString mCacheIdExtension;
+
+ // We must close mCacheInputStream explicitly to avoid leaks.
+ AutoClose<nsIInputStream> mCacheInputStream;
+ RefPtr<nsInputStreamPump> mCachePump;
+ UniquePtr<nsHttpResponseHead> mCachedResponseHead;
+ nsCOMPtr<nsITransportSecurityInfo> mCachedSecurityInfo;
+ uint32_t mPostID{0};
+ uint32_t mRequestTime{0};
+
+ nsTArray<StreamFilterRequest> mStreamFilterRequests;
+
+ mozilla::TimeStamp mOnStartRequestTimestamp;
+ // Timestamp of the time the channel was suspended.
+ mozilla::TimeStamp mSuspendTimestamp;
+ mozilla::TimeStamp mOnCacheEntryCheckTimestamp;
+
+ // Properties used for the profiler markers
+ // This keeps the timestamp for the start marker, to be reused for the end
+ // marker.
+ mozilla::TimeStamp mLastStatusReported;
+ // This is true when one end marker is output, so that we never output more
+ // than one.
+ bool mEndMarkerAdded = false;
+
+ // Total time the channel spent suspended. This value is reported to
+ // telemetry in nsHttpChannel::OnStartRequest().
+ uint32_t mSuspendTotalTime{0};
+
+ friend class AutoRedirectVetoNotifier;
+ friend class HttpAsyncAborter<nsHttpChannel>;
+
+ uint32_t mRedirectType{0};
+
+ static const uint32_t WAIT_FOR_CACHE_ENTRY = 1;
+
+ bool mCacheOpenWithPriority{false};
+ uint32_t mCacheQueueSizeWhenOpen{0};
+
+ Atomic<bool, Relaxed> mCachedContentIsValid{false};
+ Atomic<bool> mIsAuthChannel{false};
+ Atomic<bool> mAuthRetryPending{false};
+
+ // clang-format off
+ // state flags
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields5, 32, (
+ (uint32_t, CachedContentIsPartial, 1),
+ (uint32_t, CacheOnlyMetadata, 1),
+ (uint32_t, TransactionReplaced, 1),
+ (uint32_t, ProxyAuthPending, 1),
+ // Set if before the first authentication attempt a custom authorization
+ // header has been set on the channel. This will make that custom header
+ // go to the server instead of any cached credentials.
+ (uint32_t, CustomAuthHeader, 1),
+ (uint32_t, Resuming, 1),
+ (uint32_t, InitedCacheEntry, 1),
+ // True if consumer added its own If-None-Match or If-Modified-Since
+ // headers. In such a case we must not override them in the cache code
+ // and also we want to pass possible 304 code response through.
+ (uint32_t, CustomConditionalRequest, 1),
+ (uint32_t, WaitingForRedirectCallback, 1),
+ // True if mRequestTime has been set. In such a case it is safe to update
+ // the cache entry's expiration time. Otherwise, it is not(see bug 567360).
+ (uint32_t, RequestTimeInitialized, 1),
+ (uint32_t, CacheEntryIsReadOnly, 1),
+ (uint32_t, CacheEntryIsWriteOnly, 1),
+ // see WAIT_FOR_* constants above
+ (uint32_t, WaitForCacheEntry, 1),
+ // whether cache entry data write was in progress during cache entry check
+ // when true, after we finish read from cache we must check all data
+ // had been loaded from cache. If not, then an error has to be propagated
+ // to the consumer.
+ (uint32_t, ConcurrentCacheAccess, 1),
+ // whether the request is setup be byte-range
+ (uint32_t, IsPartialRequest, 1),
+ // true iff there is AutoRedirectVetoNotifier on the stack
+ (uint32_t, HasAutoRedirectVetoNotifier, 1),
+ // consumers set this to true to use cache pinning, this has effect
+ // only when the channel is in an app context
+ (uint32_t, PinCacheContent, 1),
+ // True if CORS preflight has been performed
+ (uint32_t, IsCorsPreflightDone, 1),
+
+ // if the http transaction was performed (i.e. not cached) and
+ // the result in OnStopRequest was known to be correctly delimited
+ // by chunking, content-length, or h2 end-stream framing
+ (uint32_t, StronglyFramed, 1),
+
+ // true if an HTTP transaction is created for the socket thread
+ (uint32_t, UsedNetwork, 1),
+
+ // the next authentication request can be sent on a whole new connection
+ (uint32_t, AuthConnectionRestartable, 1),
+
+ // True if the channel classifier has marked the channel to be cancelled due
+ // to the safe-browsing classifier rules, but the asynchronous cancellation
+ // process hasn't finished yet.
+ (uint32_t, ChannelClassifierCancellationPending, 1),
+
+ // True only when we are between Resume and async fire of mCallOnResume.
+ // Used to suspend any newly created pumps in mCallOnResume handler.
+ (uint32_t, AsyncResumePending, 1),
+
+ // True if the data will be sent from the socket process to the
+ // content process directly.
+ (uint32_t, DataSentToChildProcess, 1),
+
+ (uint32_t, UseHTTPSSVC, 1),
+ (uint32_t, WaitHTTPSSVCRecord, 1)
+ ))
+
+ // Broken up into two bitfields to avoid alignment requirements of uint64_t.
+ // (Too many bits used for one uint32_t.)
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields6, 32, (
+ // Only set to true when we receive an HTTPSSVC record before the
+ // transaction is created.
+ (uint32_t, HTTPSSVCTelemetryReported, 1),
+ (uint32_t, EchConfigUsed, 1),
+ (uint32_t, AuthRedirectedChannel, 1)
+ ))
+ // clang-format on
+
+ nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ uint32_t mPushedStreamId{0};
+ RefPtr<HttpTransactionShell> mTransWithPushedStream;
+
+ // True if the channel's principal was found on a phishing, malware, or
+ // tracking (if tracking protection is enabled) blocklist
+ bool mLocalBlocklist{false};
+
+ [[nodiscard]] nsresult WaitForRedirectCallback();
+ void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
+ void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
+
+ // If this resource is eligible for tailing based on class-of-service flags
+ // and load flags. We don't tail Leaders/Unblocked/UrgentStart and top-level
+ // loads.
+ bool EligibleForTailing();
+
+ // Called exclusively only from AsyncOpen or after all classification
+ // callbacks. If this channel is 1) Tail, 2) assigned a request context, 3)
+ // the context is still in the tail-blocked phase, then the method will queue
+ // this channel. OnTailUnblock will be called after the context is
+ // tail-unblocked or canceled.
+ bool WaitingForTailUnblock();
+
+ // A function we trigger when untail callback is triggered by our request
+ // context in case this channel was tail-blocked.
+ using TailUnblockCallback = nsresult (nsHttpChannel::*)();
+ TailUnblockCallback mOnTailUnblock{nullptr};
+ // Called on untail when tailed during AsyncOpen execution.
+ nsresult AsyncOpenOnTailUnblock();
+ // Called on untail when tailed because of being a tracking resource.
+ nsresult ConnectOnTailUnblock();
+
+ nsCString mUsername;
+
+ // If non-null, warnings should be reported to this object.
+ RefPtr<HttpChannelSecurityWarningReporter> mWarningReporter;
+
+ // True if the channel is reading from cache.
+ Atomic<bool> mIsReadingFromCache{false};
+
+ // nsITimerCallback is implemented on a subclass so that the name attribute
+ // doesn't conflict with the name attribute of the nsIRequest interface that
+ // might be present on the same object (as seen from JavaScript code).
+ class TimerCallback final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit TimerCallback(nsHttpChannel* aChannel);
+
+ private:
+ ~TimerCallback() = default;
+
+ RefPtr<nsHttpChannel> mChannel;
+ };
+
+ // These next members are only used in unit tests to delay the call to
+ // cache->AsyncOpenURI in order to race the cache with the network.
+ nsCOMPtr<nsITimer> mCacheOpenTimer;
+ std::function<void(nsHttpChannel*)> mCacheOpenFunc;
+ uint32_t mCacheOpenDelay = 0;
+ uint32_t mNetworkTriggerDelay = 0;
+
+ // We need to remember which is the source of the response we are using.
+ enum ResponseSource {
+ RESPONSE_PENDING = 0, // response is pending
+ RESPONSE_FROM_CACHE = 1, // response coming from cache. no network.
+ RESPONSE_FROM_NETWORK = 2 // response coming from the network
+ };
+ Atomic<ResponseSource, Relaxed> mFirstResponseSource{RESPONSE_PENDING};
+
+ // Determines if it's possible and advisable to race the network request
+ // with the cache fetch, and proceeds to do so.
+ void MaybeRaceCacheWithNetwork();
+
+ // Creates a new cache entry when network wins the race to ensure we have
+ // the latest version of the resource in the cache. Otherwise we might return
+ // an old content when navigating back in history.
+ void MaybeCreateCacheEntryWhenRCWN();
+
+ nsresult TriggerNetworkWithDelay(uint32_t aDelay);
+ nsresult TriggerNetwork();
+ void CancelNetworkRequest(nsresult aStatus);
+
+ nsresult LogConsoleError(const char* aTag);
+
+ void SetHTTPSSVCRecord(already_AddRefed<nsIDNSHTTPSSVCRecord>&& aRecord);
+
+ void RecordOnStartTelemetry(nsresult aStatus, bool aIsNavigation);
+
+ // Timer used to delay the network request, or to trigger the network
+ // request if retrieving the cache entry takes too long.
+ nsCOMPtr<nsITimer> mNetworkTriggerTimer;
+ // Is true if the network request has been triggered.
+ bool mNetworkTriggered = false;
+ bool mWaitingForProxy = false;
+ bool mStaleRevalidation = false;
+ // Will be true if the onCacheEntryAvailable callback is not called by the
+ // time we send the network request
+ Atomic<bool> mRaceCacheWithNetwork{false};
+ uint32_t mRaceDelay{0};
+ // If true then OnCacheEntryAvailable should ignore the entry, because
+ // SetupTransaction removed conditional headers and decisions made in
+ // OnCacheEntryCheck are no longer valid.
+ bool mIgnoreCacheEntry{false};
+ // Lock preventing SetupTransaction/MaybeCreateCacheEntryWhenRCWN and
+ // OnCacheEntryCheck being called at the same time.
+ mozilla::Mutex mRCWNLock MOZ_UNANNOTATED{"nsHttpChannel.mRCWNLock"};
+
+ TimeStamp mNavigationStartTimeStamp;
+
+ // Promise that blocks connection creation when we want to resolve the origin
+ // host name to be able to give the configured proxy only the resolved IP
+ // to not leak names.
+ MozPromiseHolder<DNSPromise> mDNSBlockingPromise;
+ // When we hit DoConnect before the resolution is done, Then() will be set
+ // here to resume DoConnect.
+ RefPtr<DNSPromise> mDNSBlockingThenable;
+
+ // We update the value of mProxyConnectResponseCode when OnStartRequest is
+ // called and reset the value when we switch to another failover proxy.
+ int32_t mProxyConnectResponseCode{0};
+
+ // If mHTTPSSVCRecord has value, it means OnHTTPSRRAvailable() is called and
+ // we got the result of HTTPS RR query. Otherwise, it means we are still
+ // waiting for the result or the query is not performed.
+ Maybe<nsCOMPtr<nsIDNSHTTPSSVCRecord>> mHTTPSSVCRecord;
+
+ protected:
+ virtual void DoNotifyListenerCleanup() override;
+
+ // Override ReleaseListeners() because mChannelClassifier only exists
+ // in nsHttpChannel and it will be released in ReleaseListeners().
+ virtual void ReleaseListeners() override;
+
+ virtual void DoAsyncAbort(nsresult aStatus) override;
+
+ private: // cache telemetry
+ bool mDidReval{false};
+
+ RefPtr<nsIEarlyHintObserver> mEarlyHintObserver;
+ Maybe<nsCString> mOpenerCallingScriptLocation;
+ RefPtr<WebTransportSessionEventListener> mWebTransportSessionEventListener;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpChannel, NS_HTTPCHANNEL_IID)
+} // namespace net
+} // namespace mozilla
+
+inline nsISupports* ToSupports(mozilla::net::nsHttpChannel* aChannel) {
+ return static_cast<nsIHttpChannel*>(aChannel);
+}
+
+#endif // nsHttpChannel_h__
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
new file mode 100644
index 0000000000..8e7df0ae3d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -0,0 +1,1913 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Tokenizer.h"
+#include "MockHttpAuth.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsEscape.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "netCore.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsHttp.h"
+#include "nsHttpBasicAuth.h"
+#include "nsHttpDigestAuth.h"
+#ifdef MOZ_AUTH_EXTENSION
+# include "nsHttpNegotiateAuth.h"
+#endif
+#include "nsHttpNTLMAuth.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURL.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_prompts.h"
+#include "mozilla/Telemetry.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+
+namespace mozilla::net {
+
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL 0
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN 1
+#define SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL 2
+
+#define HTTP_AUTH_DIALOG_TOP_LEVEL_DOC 29
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE 30
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR 31
+#define HTTP_AUTH_DIALOG_NON_WEB_CONTENT 32
+
+#define HTTP_AUTH_BASIC_INSECURE 0
+#define HTTP_AUTH_BASIC_SECURE 1
+#define HTTP_AUTH_DIGEST_INSECURE 2
+#define HTTP_AUTH_DIGEST_SECURE 3
+#define HTTP_AUTH_NTLM_INSECURE 4
+#define HTTP_AUTH_NTLM_SECURE 5
+#define HTTP_AUTH_NEGOTIATE_INSECURE 6
+#define HTTP_AUTH_NEGOTIATE_SECURE 7
+
+#define MAX_DISPLAYED_USER_LENGTH 64
+#define MAX_DISPLAYED_HOST_LENGTH 64
+
+static void GetOriginAttributesSuffix(nsIChannel* aChan, nsACString& aSuffix) {
+ OriginAttributes oa;
+
+ // Deliberately ignoring the result and going with defaults
+ if (aChan) {
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChan, oa);
+ }
+
+ oa.CreateSuffix(aSuffix);
+}
+
+nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
+ : mProxyAuth(false),
+ mTriedProxyAuth(false),
+ mTriedHostAuth(false),
+ mSuppressDefensiveAuth(false),
+ mCrossOrigin(false),
+ mConnectionBased(false),
+ mHttpHandler(gHttpHandler) {}
+
+nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called");
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel* channel) {
+ MOZ_ASSERT(channel, "channel expected!");
+
+ mAuthChannel = channel;
+
+ nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mAuthChannel->GetIsSSL(&mUsingSSL);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxiedChannel> proxied(channel);
+ if (proxied) {
+ nsCOMPtr<nsIProxyInfo> pi;
+ rv = proxied->GetProxyInfo(getter_AddRefs(pi));
+ if (NS_FAILED(rv)) return rv;
+
+ if (pi) {
+ nsAutoCString proxyType;
+ rv = pi->GetType(proxyType);
+ if (NS_FAILED(rv)) return rv;
+
+ mProxyUsingSSL = proxyType.EqualsLiteral("https");
+ }
+ }
+
+ rv = mURI->GetAsciiHost(mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ // reject the URL if it doesn't specify a host
+ if (mHost.IsEmpty()) return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
+ mIsPrivate = NS_UsePrivateBrowsing(bareChannel);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus,
+ bool SSLConnectFailed) {
+ LOG(
+ ("nsHttpChannelAuthProvider::ProcessAuthentication "
+ "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n",
+ this, mAuthChannel, httpStatus, SSLConnectFailed));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsAutoCString challenges;
+ mProxyAuth = (httpStatus == 407);
+
+ rv = PrepareForAuthentication(mProxyAuth);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mProxyAuth) {
+ // only allow a proxy challenge if we have a proxy server configured.
+ // otherwise, we could inadvertently expose the user's proxy
+ // credentials to an origin server. We could attempt to proceed as
+ // if we had received a 401 from the server, but why risk flirting
+ // with trouble? IE similarly rejects 407s when a proxy server is
+ // not configured, so there's no reason not to do the same.
+ if (!UsingHttpProxy()) {
+ LOG(("rejecting 407 when proxy server not configured!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (UsingSSL() && !SSLConnectFailed) {
+ // we need to verify that this challenge came from the proxy
+ // server itself, and not some server on the other side of the
+ // SSL tunnel.
+ LOG(("rejecting 407 from origin server!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ } else {
+ rv = mAuthChannel->GetWWWChallenges(challenges);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString creds;
+ rv = GetCredentials(challenges, mProxyAuth, creds);
+ if (rv == NS_ERROR_IN_PROGRESS) return rv;
+ if (NS_FAILED(rv)) {
+ LOG(("unable to authenticate\n"));
+ } else {
+ // set the authentication credentials
+ if (mProxyAuth) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::AddAuthorizationHeaders(
+ bool aDontUseCachedWWWCreds) {
+ LOG(
+ ("nsHttpChannelAuthProvider::AddAuthorizationHeaders? "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ // check if proxy credentials should be sent
+ if (!ProxyHost().IsEmpty() && UsingHttpProxy()) {
+ SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, "http"_ns,
+ ProxyHost(), ProxyPort(),
+ ""_ns, // proxy has no path
+ mProxyIdent);
+ }
+
+ if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+ LOG(("Skipping Authorization header for anonymous load\n"));
+ return NS_OK;
+ }
+
+ if (aDontUseCachedWWWCreds) {
+ LOG(
+ ("Authorization header already present:"
+ " skipping adding auth header from cache\n"));
+ return NS_OK;
+ }
+
+ // check if server credentials should be sent
+ nsAutoCString path, scheme;
+ if (NS_SUCCEEDED(GetCurrentPath(path)) &&
+ NS_SUCCEEDED(mURI->GetScheme(scheme))) {
+ SetAuthorizationHeader(authCache, nsHttp::Authorization, scheme, Host(),
+ Port(), path, mIdent);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::CheckForSuperfluousAuth() {
+ LOG(
+ ("nsHttpChannelAuthProvider::CheckForSuperfluousAuth? "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ // we've been called because it has been determined that this channel is
+ // getting loaded without taking the userpass from the URL. if the URL
+ // contained a userpass, then (provided some other conditions are true),
+ // we'll give the user an opportunity to abort the channel as this might be
+ // an attempt to spoof a different site (see bug 232567).
+ if (!ConfirmAuth("SuperfluousAuth", true)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ Unused << mAuthChannel->Cancel(NS_ERROR_SUPERFLUOS_AUTH);
+ return NS_ERROR_SUPERFLUOS_AUTH;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Cancel(nsresult status) {
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Disconnect(nsresult status) {
+ mAuthChannel = nullptr;
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ NS_IF_RELEASE(mAuthContinuationState);
+
+ return NS_OK;
+}
+
+// helper function for getting an auth prompt from an interface requestor
+static void GetAuthPrompt(nsIInterfaceRequestor* ifreq, bool proxyAuth,
+ nsIAuthPrompt2** result) {
+ if (!ifreq) return;
+
+ uint32_t promptReason;
+ if (proxyAuth) {
+ promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
+ } else {
+ promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
+ }
+
+ nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
+ if (promptProvider) {
+ promptProvider->GetAuthPrompt(promptReason, NS_GET_IID(nsIAuthPrompt2),
+ reinterpret_cast<void**>(result));
+ } else {
+ NS_QueryAuthPrompt2(ifreq, result);
+ }
+}
+
+// generate credentials for the given challenge, and update the auth cache.
+nsresult nsHttpChannelAuthProvider::GenCredsAndSetEntry(
+ nsIHttpAuthenticator* auth, bool proxyAuth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& directory,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, nsCOMPtr<nsISupports>& sessionState,
+ nsACString& result) {
+ nsresult rv;
+ nsISupports* ss = sessionState;
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsISupports** continuationState;
+
+ if (proxyAuth) {
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ continuationState = &mAuthContinuationState;
+ }
+
+ rv = auth->GenerateCredentialsAsync(
+ mAuthChannel, this, challenge, proxyAuth, ident.Domain(), ident.User(),
+ ident.Password(), ss, *continuationState,
+ getter_AddRefs(mGenerateCredentialsCancelable));
+ if (NS_SUCCEEDED(rv)) {
+ // Calling generate credentials async, results will be dispatched to the
+ // main thread by calling OnCredsGenerated method
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ uint32_t generateFlags;
+ rv = auth->GenerateCredentials(
+ mAuthChannel, challenge, proxyAuth, ident.Domain(), ident.User(),
+ ident.Password(), &ss, &*continuationState, &generateFlags, result);
+
+ sessionState.swap(ss);
+ if (NS_FAILED(rv)) return rv;
+
+ // don't log this in release build since it could contain sensitive info.
+#ifdef DEBUG
+ LOG(("generated creds: %s\n", result.BeginReading()));
+#endif
+
+ return UpdateCache(auth, scheme, host, port, directory, realm, challenge,
+ ident, result, generateFlags, sessionState, proxyAuth);
+}
+
+nsresult nsHttpChannelAuthProvider::UpdateCache(
+ nsIHttpAuthenticator* auth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& directory,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, const nsACString& creds,
+ uint32_t generateFlags, nsISupports* sessionState, bool aProxyAuth) {
+ nsresult rv;
+
+ uint32_t authFlags;
+ rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // find out if this authenticator allows reuse of credentials and/or
+ // challenge.
+ bool saveCreds =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
+ bool saveChallenge =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
+
+ bool saveIdentity =
+ 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY);
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ nsAutoCString suffix;
+ if (!aProxyAuth) {
+ // We don't isolate proxy credentials cache entries with the origin suffix
+ // as it would only annoy users with authentication dialogs popping up.
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ // create a cache entry. we do this even though we don't yet know that
+ // these credentials are valid b/c we need to avoid prompting the user
+ // more than once in case the credentials are valid.
+ //
+ // if the credentials are not reusable, then we don't bother sticking
+ // them in the auth cache.
+ rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
+ saveCreds ? creds : ""_ns,
+ saveChallenge ? challenge : ""_ns, suffix,
+ saveIdentity ? &ident : nullptr, sessionState);
+ return rv;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::ClearProxyIdent() {
+ LOG(("nsHttpChannelAuthProvider::ClearProxyIdent [this=%p]\n", this));
+
+ mProxyIdent.Clear();
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth) {
+ LOG(
+ ("nsHttpChannelAuthProvider::PrepareForAuthentication "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ if (!proxyAuth) {
+ // reset the current proxy continuation state because our last
+ // authentication attempt was completed successfully.
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ LOG((" proxy continuation state has been reset"));
+ }
+
+ if (!UsingHttpProxy() || mProxyAuthType.IsEmpty()) return NS_OK;
+
+ // We need to remove any Proxy_Authorization header left over from a
+ // non-request based authentication handshake (e.g., for NTLM auth).
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpAuthenticator> precedingAuth;
+ nsCString proxyAuthType;
+ rv = GetAuthenticator(mProxyAuthType, proxyAuthType,
+ getter_AddRefs(precedingAuth));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t precedingAuthFlags;
+ rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
+ nsAutoCString challenges;
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ if (NS_FAILED(rv)) {
+ // delete the proxy authorization header because we weren't
+ // asked to authenticate
+ rv = mAuthChannel->SetProxyCredentials(""_ns);
+ if (NS_FAILED(rv)) return rv;
+ LOG((" cleared proxy authorization header"));
+ }
+ }
+
+ return NS_OK;
+}
+
+class MOZ_STACK_CLASS ChallengeParser final : Tokenizer {
+ public:
+ explicit ChallengeParser(const nsACString& aChallenges)
+ : Tokenizer(aChallenges, nullptr, "") {
+ Record();
+ }
+
+ Maybe<nsDependentCSubstring> GetNext() {
+ Token t;
+ nsDependentCSubstring result;
+
+ bool inQuote = false;
+
+ while (Next(t)) {
+ if (t.Type() == TOKEN_EOL) {
+ Claim(result, ClaimInclusion::EXCLUDE_LAST);
+ SkipWhites(WhiteSkipping::INCLUDE_NEW_LINE);
+ Record();
+ inQuote = false;
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ } else if (t.Equals(Token::Char(',')) && !inQuote) {
+ // Sometimes we get multiple challenges separated by a comma.
+ // This is not great, as it's slightly ambiguous. We check if something
+ // is a new challenge by matching agains <param_name> =
+ // If the , isn't followed by a word and = then most likely
+ // it is the name of an authType.
+
+ const char* prevCursorPos = mCursor;
+ const char* prevRollbackPos = mRollback;
+
+ auto hasWordAndEqual = [&]() {
+ SkipWhites();
+ nsDependentCSubstring word;
+ if (!ReadWord(word)) {
+ return false;
+ }
+ SkipWhites();
+ return Check(Token::Char('='));
+ };
+ if (!hasWordAndEqual()) {
+ // This is not a parameter. It means the `,` character starts a
+ // different challenge.
+ // We'll revert the cursor and return the contents so far.
+ mCursor = prevCursorPos;
+ mRollback = prevRollbackPos;
+ Claim(result, ClaimInclusion::EXCLUDE_LAST);
+ SkipWhites();
+ Record();
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ }
+ } else if (t.Equals(Token::Char('"'))) {
+ inQuote = !inQuote;
+ }
+ }
+
+ Claim(result, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ SkipWhites();
+ Record();
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ return Nothing{};
+ }
+};
+
+enum ChallengeRank {
+ Unknown = 0,
+ Basic = 1,
+ Digest = 2,
+ NTLM = 3,
+ Negotiate = 4,
+};
+
+ChallengeRank Rank(const nsACString& aChallenge) {
+ if (StringBeginsWith(aChallenge, "Negotiate"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Negotiate;
+ }
+
+ if (StringBeginsWith(aChallenge, "NTLM"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::NTLM;
+ }
+
+ if (StringBeginsWith(aChallenge, "Digest"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Digest;
+ }
+
+ if (StringBeginsWith(aChallenge, "Basic"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Basic;
+ }
+
+ return ChallengeRank::Unknown;
+}
+
+nsresult nsHttpChannelAuthProvider::GetCredentials(
+ const nsACString& aChallenges, bool proxyAuth, nsCString& creds) {
+ LOG(("nsHttpChannelAuthProvider::GetCredentials"));
+ nsAutoCString challenges(aChallenges);
+
+ using AuthChallenge = struct AuthChallenge {
+ nsDependentCSubstring challenge;
+ uint16_t algorithm = 0;
+ ChallengeRank rank = ChallengeRank::Unknown;
+
+ void operator=(const AuthChallenge& aOther) {
+ challenge.Rebind(aOther.challenge, 0);
+ algorithm = aOther.algorithm;
+ rank = aOther.rank;
+ }
+ };
+
+ nsTArray<AuthChallenge> cc;
+
+ ChallengeParser p(challenges);
+ while (true) {
+ auto next = p.GetNext();
+ if (next.isNothing()) {
+ break;
+ }
+ AuthChallenge ac{next.ref(), 0};
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale = false;
+ uint16_t qop = 0;
+ ac.rank = Rank(ac.challenge);
+ if (StringBeginsWith(ac.challenge, "Digest"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ Unused << nsHttpDigestAuth::ParseChallenge(ac.challenge, realm, domain,
+ nonce, opaque, &stale,
+ &ac.algorithm, &qop);
+ }
+ cc.AppendElement(ac);
+ }
+
+ cc.StableSort([](const AuthChallenge& lhs, const AuthChallenge& rhs) {
+ if (StaticPrefs::network_auth_choose_most_secure_challenge()) {
+ // Different auth types
+ if (lhs.rank != rhs.rank) {
+ return lhs.rank < rhs.rank ? 1 : -1;
+ }
+
+ // If they're the same auth type, and not a Digest, then we treat them
+ // as equal (don't reorder them).
+ if (lhs.rank != ChallengeRank::Digest) {
+ return 0;
+ }
+ } else {
+ // Non-digest challenges should not be reordered when the pref is off.
+ if (lhs.algorithm == 0 || rhs.algorithm == 0) {
+ return 0;
+ }
+ }
+
+ // Same algorithm.
+ if (lhs.algorithm == rhs.algorithm) {
+ return 0;
+ }
+ return lhs.algorithm < rhs.algorithm ? 1 : -1;
+ });
+
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsCString authType; // force heap allocation to enable string sharing since
+ // we'll be assigning this value into mAuthType.
+
+ // set informations that depend on whether we're authenticating against a
+ // proxy or a webserver
+ nsISupports** currentContinuationState;
+ nsCString* currentAuthType;
+
+ if (proxyAuth) {
+ currentContinuationState = &mProxyAuthContinuationState;
+ currentAuthType = &mProxyAuthType;
+ } else {
+ currentContinuationState = &mAuthContinuationState;
+ currentAuthType = &mAuthType;
+ }
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ bool gotCreds = false;
+
+ // figure out which challenge we can handle and which authenticator to use.
+ for (size_t i = 0; i < cc.Length(); i++) {
+ rv = GetAuthenticator(cc[i].challenge, authType, getter_AddRefs(auth));
+ LOG(("trying auth for %s", authType.get()));
+ if (NS_SUCCEEDED(rv)) {
+ //
+ // if we've already selected an auth type from a previous challenge
+ // received while processing this channel, then skip others until
+ // we find a challenge corresponding to the previously tried auth
+ // type.
+ //
+ if (!currentAuthType->IsEmpty() && authType != *currentAuthType) continue;
+
+ //
+ // we allow the routines to run all the way through before we
+ // decide if they are valid.
+ //
+ // we don't worry about the auth cache being altered because that
+ // would have been the last step, and if the error is from updating
+ // the authcache it wasn't really altered anyway. -CTN
+ //
+ // at this point the code is really only useful for client side
+ // errors (it will not automatically fail over to do a different
+ // auth type if the server keeps rejecting what is being sent, even
+ // if a particular auth method only knows 1 thing, like a
+ // non-identity based authentication method)
+ //
+ rv = GetCredentialsForChallenge(cc[i].challenge, authType, proxyAuth,
+ auth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ gotCreds = true;
+ *currentAuthType = authType;
+
+ break;
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result is
+ // expected asynchronously, save current challenge being
+ // processed and all remaining challenges to use later in
+ // OnAuthAvailable and now immediately return
+ mCurrentChallenge = cc[i].challenge;
+ // imperfect; does not save server-side preference ordering.
+ // instead, continues with remaining string as provided by client
+ mRemainingChallenges.Truncate();
+ while (i + 1 < cc.Length()) {
+ i++;
+ mRemainingChallenges.Append(cc[i].challenge);
+ mRemainingChallenges.Append("\n"_ns);
+ }
+ return rv;
+ }
+
+ // reset the auth type and continuation state
+ NS_IF_RELEASE(*currentContinuationState);
+ currentAuthType->Truncate();
+ }
+ }
+
+ if (!gotCreds && !currentAuthType->IsEmpty()) {
+ // looks like we never found the auth type we were looking for.
+ // reset the auth type and continuation state, and try again.
+ currentAuthType->Truncate();
+ NS_IF_RELEASE(*currentContinuationState);
+
+ rv = GetCredentials(challenges, proxyAuth, creds);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannelAuthProvider::GetAuthorizationMembers(
+ bool proxyAuth, nsACString& scheme, nsCString& host, int32_t& port,
+ nsACString& path, nsHttpAuthIdentity*& ident,
+ nsISupports**& continuationState) {
+ if (proxyAuth) {
+ MOZ_ASSERT(UsingHttpProxy(),
+ "proxyAuth is true, but no HTTP proxy is configured!");
+
+ host = ProxyHost();
+ port = ProxyPort();
+ ident = &mProxyIdent;
+ scheme.AssignLiteral("http");
+
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ host = Host();
+ port = Port();
+ ident = &mIdent;
+
+ nsresult rv;
+ rv = GetCurrentPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ continuationState = &mAuthContinuationState;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::GetCredentialsForChallenge(
+ const nsACString& aChallenge, const nsACString& aAuthType, bool proxyAuth,
+ nsIHttpAuthenticator* auth, nsCString& creds) {
+ LOG(
+ ("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
+ "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
+ this, mAuthChannel, proxyAuth, nsCString(aChallenge).get()));
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ uint32_t authFlags;
+ nsresult rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString realm;
+ ParseRealm(aChallenge, realm);
+
+ // if no realm, then use the auth type as the realm. ToUpperCase so the
+ // ficticious realm stands out a bit more.
+ // XXX this will cause some single signon misses!
+ // XXX this was meant to be used with NTLM, which supplies no realm.
+ /*
+ if (realm.IsEmpty()) {
+ realm = authType;
+ ToUpperCase(realm);
+ }
+ */
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString path, scheme;
+ bool identFromURI = false;
+ nsISupports** continuationState;
+
+ rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, path, ident,
+ continuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
+ nsAutoCString suffix;
+
+ if (!proxyAuth) {
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+
+ // if this is the first challenge, then try using the identity
+ // specified in the URL.
+ if (mIdent.IsEmpty()) {
+ GetIdentityFromURI(authFlags, mIdent);
+ identFromURI = !mIdent.IsEmpty();
+ }
+
+ if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Let explicit URL credentials pass
+ // regardless of the LOAD_ANONYMOUS flag
+ } else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ //
+ // if we already tried some credentials for this transaction, then
+ // we need to possibly clear them from the cache, unless the credentials
+ // in the cache have changed, in which case we'd want to give them a
+ // try instead.
+ //
+ nsHttpAuthEntry* entry = nullptr;
+ Unused << authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
+ &entry);
+
+ // hold reference to the auth session state (in case we clear our
+ // reference to the entry).
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry) sessionStateGrip = entry->mMetaData;
+
+ // remember if we already had the continuation state. it means we are in
+ // the middle of the authentication exchange and the connection must be
+ // kept sticky then (and only then).
+ bool authAtProgress = !!*continuationState;
+
+ // for digest auth, maybe our cached nonce value simply timed out...
+ bool identityInvalid;
+ nsISupports* sessionState = sessionStateGrip;
+ rv = auth->ChallengeReceived(mAuthChannel, aChallenge, proxyAuth,
+ &sessionState, &*continuationState,
+ &identityInvalid);
+ sessionStateGrip.swap(sessionState);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG((" identity invalid = %d\n", identityInvalid));
+
+ if (mConnectionBased && identityInvalid) {
+ // If the flag is set and identity is invalid, it means we received the
+ // first challange for a new negotiation round after negotiating a
+ // connection based auth failed (invalid password). The mConnectionBased
+ // flag is set later for the newly received challenge, so here it reflects
+ // the previous 401/7 response schema.
+ rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!proxyAuth) {
+ // We must clear proxy ident in the following scenario + explanation:
+ // - we are authenticating to an NTLM proxy and an NTLM server
+ // - we successfully authenticated to the proxy, mProxyIdent keeps
+ // the user name/domain and password, the identity has also been cached
+ // - we just threw away the connection because we are now asking for
+ // creds for the server (WWW auth)
+ // - hence, we will have to auth to the proxy again as well
+ // - if we didn't clear the proxy identity, it would be considered
+ // as non-valid and we would ask the user again ; clearing it forces
+ // use of the cached identity and not asking the user again
+ ClearProxyIdent();
+ }
+ }
+
+ mConnectionBased = !!(authFlags & nsIHttpAuthenticator::CONNECTION_BASED);
+
+ // It's legal if the peer closes the connection after the first 401/7.
+ // Making the connection sticky will prevent its restart giving the user
+ // a 'network reset' error every time. Hence, we mark the connection
+ // as restartable.
+ mAuthChannel->ConnectionRestartable(!authAtProgress);
+
+ if (identityInvalid) {
+ if (entry) {
+ if (ident->Equals(entry->Identity())) {
+ if (!identFromURI) {
+ LOG((" clearing bad auth cache entry\n"));
+ // ok, we've already tried this user identity, so clear the
+ // corresponding entry from the auth cache.
+ authCache->ClearAuthEntry(scheme, host, port, realm, suffix);
+ entry = nullptr;
+ ident->Clear();
+ }
+ } else if (!identFromURI ||
+ (ident->User() == entry->Identity().User() &&
+ !(loadFlags & (nsIChannel::LOAD_ANONYMOUS |
+ nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) {
+ LOG((" taking identity from auth cache\n"));
+ // the password from the auth cache is more likely to be
+ // correct than the one in the URL. at least, we know that it
+ // works with the given username. it is possible for a server
+ // to distinguish logons based on the supplied password alone,
+ // but that would be quite unusual... and i don't think we need
+ // to worry about such unorthodox cases.
+ *ident = entry->Identity();
+ identFromURI = false;
+ if (entry->Creds()[0] != '\0') {
+ LOG((" using cached credentials!\n"));
+ creds.Assign(entry->Creds());
+ return entry->AddPath(path);
+ }
+ }
+ } else if (!identFromURI) {
+ // hmm... identity invalid, but no auth entry! the realm probably
+ // changed (see bug 201986).
+ ident->Clear();
+ }
+
+ if (!entry && ident->IsEmpty()) {
+ uint32_t level = nsIAuthPrompt2::LEVEL_NONE;
+ if ((!proxyAuth && mUsingSSL) || (proxyAuth && mProxyUsingSSL)) {
+ level = nsIAuthPrompt2::LEVEL_SECURE;
+ } else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) {
+ level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
+ }
+
+ // Collect statistics on how frequently the various types of HTTP
+ // authentication are used over SSL and non-SSL connections.
+ if (Telemetry::CanRecordPrereleaseData()) {
+ if ("basic"_ns.Equals(aAuthType, nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_BASIC_SECURE : HTTP_AUTH_BASIC_INSECURE);
+ } else if ("digest"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_DIGEST_SECURE : HTTP_AUTH_DIGEST_INSECURE);
+ } else if ("ntlm"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NTLM_SECURE : HTTP_AUTH_NTLM_INSECURE);
+ } else if ("negotiate"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NEGOTIATE_SECURE
+ : HTTP_AUTH_NEGOTIATE_INSECURE);
+ }
+ }
+
+ // Depending on the pref setting, the authentication dialog may be
+ // blocked for all sub-resources, blocked for cross-origin
+ // sub-resources, or always allowed for sub-resources.
+ // For more details look at the bug 647010.
+ // BlockPrompt will set mCrossOrigin parameter as well.
+ if (BlockPrompt(proxyAuth)) {
+ LOG((
+ "nsHttpChannelAuthProvider::GetCredentialsForChallenge: "
+ "Prompt is blocked [this=%p pref=%d img-pref=%d "
+ "non-web-content-triggered-pref=%d]\n",
+ this, StaticPrefs::network_auth_subresource_http_auth_allow(),
+ StaticPrefs::
+ network_auth_subresource_img_cross_origin_http_auth_allow(),
+ StaticPrefs::
+ network_auth_non_web_content_triggered_resources_http_auth_allow()));
+ return NS_ERROR_ABORT;
+ }
+
+ // at this point we are forced to interact with the user to get
+ // their username and password for this domain.
+ rv = PromptForIdentity(level, proxyAuth, realm, aAuthType, authFlags,
+ *ident);
+ if (NS_FAILED(rv)) return rv;
+ identFromURI = false;
+ }
+ }
+
+ if (identFromURI) {
+ // Warn the user before automatically using the identity from the URL
+ // to automatically log them into a site (see bug 232567).
+ if (!ConfirmAuth("AutomaticAuth", false)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ rv = mAuthChannel->Cancel(NS_ERROR_ABORT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // this return code alone is not equivalent to Cancel, since
+ // it only instructs our caller that authentication failed.
+ // without an explicit call to Cancel, our caller would just
+ // load the page that accompanies the HTTP auth challenge.
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // get credentials for the given user:pass
+ //
+ // always store the credentials we're trying now so that they will be used
+ // on subsequent links. This will potentially remove good credentials from
+ // the cache. This is ok as we don't want to use cached credentials if the
+ // user specified something on the URI or in another manner. This is so
+ // that we don't transparently authenticate as someone they're not
+ // expecting to authenticate as.
+ //
+ nsCString result;
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path, realm,
+ aChallenge, *ident, sessionStateGrip, creds);
+ return rv;
+}
+
+bool nsHttpChannelAuthProvider::BlockPrompt(bool proxyAuth) {
+ // Verify that it's ok to prompt for credentials here, per spec
+ // http://xhr.spec.whatwg.org/#the-send%28%29-method
+
+ nsCOMPtr<nsIHttpChannelInternal> chanInternal =
+ do_QueryInterface(mAuthChannel);
+ MOZ_ASSERT(chanInternal);
+
+ if (chanInternal->GetBlockAuthPrompt()) {
+ LOG(
+ ("nsHttpChannelAuthProvider::BlockPrompt: Prompt is blocked "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+ return true;
+ }
+
+ if (proxyAuth) {
+ // Do not block auth-dialog if this is a proxy authentication.
+ return false;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+
+ // We will treat loads w/o loadInfo as a top level document.
+ bool topDoc = true;
+ bool xhr = false;
+ bool nonWebContent = false;
+
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ topDoc = false;
+ }
+
+ if (!topDoc) {
+ nsCOMPtr<nsIPrincipal> triggeringPrinc = loadInfo->TriggeringPrincipal();
+ if (triggeringPrinc->IsSystemPrincipal()) {
+ nonWebContent = true;
+ }
+ }
+
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ xhr = true;
+ }
+
+ if (!topDoc && !xhr) {
+ nsCOMPtr<nsIURI> topURI;
+ Unused << chanInternal->GetTopWindowURI(getter_AddRefs(topURI));
+ if (topURI) {
+ mCrossOrigin = !NS_SecurityCompareURIs(topURI, mURI, true);
+ } else {
+ nsIPrincipal* loadingPrinc = loadInfo->GetLoadingPrincipal();
+ MOZ_ASSERT(loadingPrinc);
+ mCrossOrigin = !loadingPrinc->IsSameOrigin(mURI);
+ }
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ if (topDoc) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_TOP_LEVEL_DOC);
+ } else if (nonWebContent) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_NON_WEB_CONTENT);
+ } else if (!mCrossOrigin) {
+ if (xhr) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE);
+ }
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ static_cast<uint32_t>(loadInfo->GetExternalContentPolicyType()));
+ }
+ }
+
+ if (!topDoc &&
+ !StaticPrefs::
+ network_auth_non_web_content_triggered_resources_http_auth_allow() &&
+ nonWebContent) {
+ return true;
+ }
+
+ switch (StaticPrefs::network_auth_subresource_http_auth_allow()) {
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL:
+ // Do not open the http-authentication credentials dialog for
+ // the sub-resources.
+ return !topDoc && !xhr;
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN:
+ // Open the http-authentication credentials dialog for
+ // the sub-resources only if they are not cross-origin.
+ return !topDoc && !xhr && mCrossOrigin;
+ case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL:
+ // Allow the http-authentication dialog for subresources.
+ // If pref network.auth.subresource-img-cross-origin-http-auth-allow
+ // is set, http-authentication dialog for image subresources is
+ // blocked.
+ if (mCrossOrigin &&
+ !StaticPrefs::
+ network_auth_subresource_img_cross_origin_http_auth_allow() &&
+ loadInfo &&
+ ((loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGE) ||
+ (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGESET))) {
+ return true;
+ }
+ return false;
+ default:
+ // This is an invalid value.
+ MOZ_ASSERT(false, "A non valid value!");
+ }
+ return false;
+}
+
+inline void GetAuthType(const nsACString& aChallenge, nsCString& authType) {
+ auto spaceIndex = aChallenge.FindChar(' ');
+ authType = Substring(aChallenge, 0, spaceIndex);
+ // normalize to lowercase
+ ToLowerCase(authType);
+}
+
+nsresult nsHttpChannelAuthProvider::GetAuthenticator(
+ const nsACString& aChallenge, nsCString& authType,
+ nsIHttpAuthenticator** auth) {
+ LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ GetAuthType(aChallenge, authType);
+
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+#ifdef MOZ_AUTH_EXTENSION
+ if (authType.EqualsLiteral("negotiate")) {
+ authenticator = nsHttpNegotiateAuth::GetOrCreate();
+ } else
+#endif
+ if (authType.EqualsLiteral("basic")) {
+ authenticator = nsHttpBasicAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("digest")) {
+ authenticator = nsHttpDigestAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("ntlm")) {
+ authenticator = nsHttpNTLMAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("mock_auth") &&
+ PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
+ authenticator = MockHttpAuth::Create();
+ } else {
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+
+ if (!authenticator) {
+ // If called during shutdown it's possible that the singleton authenticator
+ // was already cleared so we have a null one here.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(authenticator);
+ authenticator.forget(auth);
+
+ return NS_OK;
+}
+
+// buf contains "domain\user"
+static void ParseUserDomain(const nsAString& buf, nsDependentSubstring& user,
+ nsDependentSubstring& domain) {
+ auto backslashPos = buf.FindChar(u'\\');
+ if (backslashPos != kNotFound) {
+ domain.Rebind(buf, 0, backslashPos);
+ user.Rebind(buf, backslashPos + 1);
+ }
+}
+
+void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
+ nsHttpAuthIdentity& ident) {
+ LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ bool hasUserPass;
+ if (NS_FAILED(mURI->GetHasUserPass(&hasUserPass)) || !hasUserPass) {
+ return;
+ }
+
+ nsAutoString userBuf;
+ nsAutoString passBuf;
+
+ // XXX i18n
+ nsAutoCString buf;
+ nsresult rv = mURI->GetUsername(buf);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, userBuf);
+
+ rv = mURI->GetPassword(buf);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, passBuf);
+
+ nsDependentSubstring user(userBuf, 0);
+ nsDependentSubstring domain(u""_ns, 0);
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
+ ParseUserDomain(userBuf, user, domain);
+ }
+
+ ident = nsHttpAuthIdentity(domain, user, passBuf);
+}
+
+void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
+ nsACString& realm) {
+ //
+ // From RFC2617 section 1.2, the realm value is defined as such:
+ //
+ // realm = "realm" "=" realm-value
+ // realm-value = quoted-string
+ //
+ // but, we'll accept anything after the the "=" up to the first space, or
+ // end-of-line, if the string is not quoted.
+ //
+
+ Tokenizer t(aChallenge);
+
+ // The challenge begins with the authType.
+ // If we can't find that something has probably gone wrong.
+ t.SkipWhites();
+ nsDependentCSubstring authType;
+ if (!t.ReadWord(authType)) {
+ return;
+ }
+
+ // Will return true if the tokenizer advanced the cursor - false otherwise.
+ auto readParam = [&](nsDependentCSubstring& key, nsAutoCString& value) {
+ key.Rebind(EmptyCString(), 0);
+ value.Truncate();
+
+ t.SkipWhites();
+ if (!t.ReadWord(key)) {
+ return false;
+ }
+ t.SkipWhites();
+ if (!t.CheckChar('=')) {
+ return true;
+ }
+ t.SkipWhites();
+
+ Tokenizer::Token token1;
+
+ t.Record();
+ if (!t.Next(token1)) {
+ return true;
+ }
+ nsDependentCSubstring sub;
+ bool hasQuote = false;
+ if (token1.Equals(Tokenizer::Token::Char('"'))) {
+ hasQuote = true;
+ } else {
+ t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ value.Append(sub);
+ }
+ t.Record();
+ Tokenizer::Token token2;
+ while (t.Next(token2)) {
+ if (hasQuote && token2.Equals(Tokenizer::Token::Char('"')) &&
+ !token1.Equals(Tokenizer::Token::Char('\\'))) {
+ break;
+ }
+ if (!hasQuote && (token2.Type() == Tokenizer::TokenType::TOKEN_WS ||
+ token2.Type() == Tokenizer::TokenType::TOKEN_EOL)) {
+ break;
+ }
+
+ t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ if (!sub.Equals(R"(\)")) {
+ value.Append(sub);
+ }
+ t.Record();
+ token1 = token2;
+ }
+ return true;
+ };
+
+ while (!t.CheckEOF()) {
+ nsDependentCSubstring key;
+ nsAutoCString value;
+ // If we couldn't read anything, and the input isn't followed by a ,
+ // then we exit.
+ if (!readParam(key, value) && !t.Check(Tokenizer::Token::Char(','))) {
+ break;
+ }
+ // When we find the first instance of realm we exit.
+ // Theoretically there should be only one instance and we should fail
+ // if there are more, but we're trying to preserve existing behaviour.
+ if (key.Equals("realm"_ns, nsCaseInsensitiveCStringComparator)) {
+ realm = value;
+ break;
+ }
+ }
+}
+
+class nsHTTPAuthInformation : public nsAuthInformationHolder {
+ public:
+ nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
+ const nsACString& aAuthType)
+ : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
+
+ void SetToHttpAuthIdentity(uint32_t authFlags, nsHttpAuthIdentity& identity);
+};
+
+void nsHTTPAuthInformation::SetToHttpAuthIdentity(
+ uint32_t authFlags, nsHttpAuthIdentity& identity) {
+ identity = nsHttpAuthIdentity(Domain(), User(), Password());
+}
+
+nsresult nsHttpChannelAuthProvider::PromptForIdentity(
+ uint32_t level, bool proxyAuth, const nsACString& realm,
+ const nsACString& authType, uint32_t authFlags, nsHttpAuthIdentity& ident) {
+ LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIAuthPrompt2> authPrompt;
+ GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt));
+ if (!authPrompt && loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
+ }
+ if (!authPrompt) return NS_ERROR_NO_INTERFACE;
+
+ // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
+ NS_ConvertASCIItoUTF16 realmU(realm);
+
+ // prompt the user...
+ uint32_t promptFlags = 0;
+ if (proxyAuth) {
+ promptFlags |= nsIAuthInformation::AUTH_PROXY;
+ if (mTriedProxyAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedProxyAuth = true;
+ } else {
+ promptFlags |= nsIAuthInformation::AUTH_HOST;
+ if (mTriedHostAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedHostAuth = true;
+ }
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
+ promptFlags |= nsIAuthInformation::NEED_DOMAIN;
+ }
+
+ if (mCrossOrigin) {
+ promptFlags |= nsIAuthInformation::CROSS_ORIGIN_SUB_RESOURCE;
+ }
+
+ RefPtr<nsHTTPAuthInformation> holder =
+ new nsHTTPAuthInformation(promptFlags, realmU, authType);
+ if (!holder) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(mAuthChannel, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder,
+ getter_AddRefs(mAsyncPromptAuthCancelable));
+
+ if (NS_SUCCEEDED(rv)) {
+ // indicate using this error code that authentication prompt
+ // result is expected asynchronously
+ rv = NS_ERROR_IN_PROGRESS;
+ } else {
+ // Fall back to synchronous prompt
+ bool retval = false;
+ rv = authPrompt->PromptAuth(channel, level, holder, &retval);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!retval) {
+ rv = NS_ERROR_ABORT;
+ } else {
+ holder->SetToHttpAuthIdentity(authFlags, ident);
+ }
+ }
+
+ // remember that we successfully showed the user an auth dialog
+ if (!proxyAuth) mSuppressDefensiveAuth = true;
+
+ if (mConnectionBased) {
+ // Connection can be reset by the server in the meantime user is entering
+ // the credentials. Result would be just a "Connection was reset" error.
+ // Hence, we drop the current regardless if the user would make it on time
+ // to provide credentials.
+ // It's OK to send the NTLM type 1 message (response to the plain "NTLM"
+ // challenge) on a new connection.
+ {
+ DebugOnly<nsresult> rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(
+ nsISupports* aContext, nsIAuthInformation* aAuthInfo) {
+ LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", this,
+ mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel) return NS_OK;
+
+ nsresult rv;
+
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString path, scheme;
+ nsISupports** continuationState;
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, path, ident,
+ continuationState);
+ if (NS_FAILED(rv)) OnAuthCancelled(aContext, false);
+
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge, realm);
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ if (!mProxyAuth) {
+ // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+ nsHttpAuthEntry* entry = nullptr;
+ Unused << authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
+ &entry);
+
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry) sessionStateGrip = entry->mMetaData;
+
+ nsAuthInformationHolder* holder =
+ static_cast<nsAuthInformationHolder*>(aAuthInfo);
+ *ident =
+ nsHttpAuthIdentity(holder->Domain(), holder->User(), holder->Password());
+
+ nsAutoCString unused;
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "GetAuthenticator failed");
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ nsCString creds;
+ rv = GenCredsAndSetEntry(auth, mProxyAuth, scheme, host, port, path, realm,
+ mCurrentChallenge, *ident, sessionStateGrip, creds);
+
+ mCurrentChallenge.Truncate();
+ if (NS_FAILED(rv)) {
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ return ContinueOnAuthAvailable(creds);
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports* aContext,
+ bool userCancel) {
+ LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", this,
+ mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel) return NS_OK;
+
+ // When user cancels or auth fails we want to close the connection for
+ // connection based schemes like NTLM. Some servers don't like re-negotiation
+ // on the same connection.
+ nsresult rv;
+ if (mConnectionBased) {
+ rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mConnectionBased = false;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(mAuthChannel);
+ if (channel) {
+ nsresult status;
+ Unused << channel->GetStatus(&status);
+ if (NS_FAILED(status)) {
+ // If the channel is already cancelled, there is no need to deal with the
+ // rest challenges.
+ LOG((" Clear mRemainingChallenges, since mAuthChannel is cancelled"));
+ mRemainingChallenges.Truncate();
+ }
+ }
+
+ if (userCancel) {
+ if (!mRemainingChallenges.IsEmpty()) {
+ // there are still some challenges to process, do so
+
+ // Get rid of current continuationState to avoid reusing it in
+ // next challenges since it is no longer relevant.
+ if (mProxyAuth) {
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ } else {
+ NS_IF_RELEASE(mAuthContinuationState);
+ }
+ nsAutoCString creds;
+ rv = GetCredentials(mRemainingChallenges, mProxyAuth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ // GetCredentials loaded the credentials from the cache or
+ // some other way in a synchronous manner, process those
+ // credentials now
+ mRemainingChallenges.Truncate();
+ return ContinueOnAuthAvailable(creds);
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // GetCredentials successfully queued another authprompt for
+ // a challenge from the list, we are now waiting for the user
+ // to provide the credentials
+ return NS_OK;
+ }
+
+ // otherwise, we failed...
+ }
+
+ mRemainingChallenges.Truncate();
+ }
+
+ rv = mAuthChannel->OnAuthCancelled(userCancel);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnCredsGenerated(
+ const nsACString& aGeneratedCreds, uint32_t aFlags, nsresult aResult,
+ nsISupports* aSessionState, nsISupports* aContinuationState) {
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // When channel is closed, do not proceed
+ if (!mAuthChannel) {
+ return NS_OK;
+ }
+
+ mGenerateCredentialsCancelable = nullptr;
+
+ if (NS_FAILED(aResult)) {
+ return OnAuthCancelled(nullptr, true);
+ }
+
+ // We want to update m(Proxy)AuthContinuationState in case it was changed by
+ // nsHttpNegotiateAuth::GenerateCredentials
+ nsCOMPtr<nsISupports> contState(aContinuationState);
+ if (mProxyAuth) {
+ contState.swap(mProxyAuthContinuationState);
+ } else {
+ contState.swap(mAuthContinuationState);
+ }
+
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString directory, scheme;
+ nsISupports** unusedContinuationState;
+
+ // Get realm from challenge
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge, realm);
+
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, directory, ident,
+ unusedContinuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ rv =
+ UpdateCache(auth, scheme, host, port, directory, realm, mCurrentChallenge,
+ *ident, aGeneratedCreds, aFlags, aSessionState, mProxyAuth);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mCurrentChallenge.Truncate();
+
+ rv = ContinueOnAuthAvailable(aGeneratedCreds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::ContinueOnAuthAvailable(
+ const nsACString& creds) {
+ nsresult rv;
+ if (mProxyAuth) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // drop our remaining list of challenges. We don't need them, because we
+ // have now authenticated against a challenge and will be sending that
+ // information to the server (or proxy). If it doesn't accept our
+ // authentication it'll respond with failure and resend the challenge list
+ mRemainingChallenges.Truncate();
+
+ Unused << mAuthChannel->OnAuthAvailable();
+
+ return NS_OK;
+}
+
+bool nsHttpChannelAuthProvider::ConfirmAuth(const char* bundleKey,
+ bool doYesNoPrompt) {
+ // skip prompting the user if
+ // 1) prompts are disabled by pref
+ // 2) we've already prompted the user
+ // 3) we're not a toplevel channel
+ // 4) the userpass length is less than the "phishy" threshold
+
+ if (!StaticPrefs::network_auth_confirmAuth_enabled()) {
+ return true;
+ }
+
+ uint32_t loadFlags;
+ nsresult rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return true;
+
+ if (mSuppressDefensiveAuth ||
+ !(loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI)) {
+ return true;
+ }
+
+ nsAutoCString userPass;
+ rv = mURI->GetUserPass(userPass);
+ if (NS_FAILED(rv) ||
+ (userPass.Length() < gHttpHandler->PhishyUserPassLength())) {
+ return true;
+ }
+
+ // we try to confirm by prompting the user. if we cannot do so, then
+ // assume the user said ok. this is done to keep things working in
+ // embedded builds, where the string bundle might not be present, etc.
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService) return true;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
+ if (!bundle) return true;
+
+ nsAutoCString host;
+ rv = mURI->GetHost(host);
+ if (NS_FAILED(rv)) return true;
+
+ nsAutoCString user;
+ rv = mURI->GetUsername(user);
+ if (NS_FAILED(rv)) return true;
+
+ NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
+
+ size_t userLength = ucsUser.Length();
+ if (userLength > MAX_DISPLAYED_USER_LENGTH) {
+ size_t desiredLength = MAX_DISPLAYED_USER_LENGTH;
+ // Don't cut off right before a low surrogate. Just include it.
+ if (NS_IS_LOW_SURROGATE(ucsUser[desiredLength])) {
+ desiredLength++;
+ }
+ ucsUser.Replace(desiredLength, userLength - desiredLength,
+ nsContentUtils::GetLocalizedEllipsis());
+ }
+
+ size_t hostLen = ucsHost.Length();
+ if (hostLen > MAX_DISPLAYED_HOST_LENGTH) {
+ size_t cutPoint = hostLen - MAX_DISPLAYED_HOST_LENGTH;
+ // Likewise, don't cut off right before a low surrogate here.
+ // Keep the low surrogate
+ if (NS_IS_LOW_SURROGATE(ucsHost[cutPoint])) {
+ cutPoint--;
+ }
+ // It's possible cutPoint was 1 and is now 0. Only insert the ellipsis
+ // if we're actually removing anything.
+ if (cutPoint > 0) {
+ ucsHost.Replace(0, cutPoint, nsContentUtils::GetLocalizedEllipsis());
+ }
+ }
+
+ AutoTArray<nsString, 2> strs = {ucsHost, ucsUser};
+
+ nsAutoString msg;
+ rv = bundle->FormatStringFromName(bundleKey, strs, msg);
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsIPromptService> promptSvc =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ if (NS_FAILED(rv) || !promptSvc) {
+ return true;
+ }
+
+ // do not prompt again
+ mSuppressDefensiveAuth = true;
+
+ // Get current browsing context to use as prompt parent
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ if (!chan) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ RefPtr<mozilla::dom::BrowsingContext> browsingContext;
+ loadInfo->GetBrowsingContext(getter_AddRefs(browsingContext));
+
+ bool confirmed;
+ if (doYesNoPrompt) {
+ int32_t choice;
+ bool checkState = false;
+ rv = promptSvc->ConfirmExBC(
+ browsingContext, StaticPrefs::prompts_modalType_confirmAuth(), nullptr,
+ msg.get(),
+ nsIPromptService::BUTTON_POS_1_DEFAULT +
+ nsIPromptService::STD_YES_NO_BUTTONS,
+ nullptr, nullptr, nullptr, nullptr, &checkState, &choice);
+ if (NS_FAILED(rv)) return true;
+
+ confirmed = choice == 0;
+ } else {
+ rv = promptSvc->ConfirmBC(browsingContext,
+ StaticPrefs::prompts_modalType_confirmAuth(),
+ nullptr, msg.get(), &confirmed);
+ if (NS_FAILED(rv)) return true;
+ }
+
+ return confirmed;
+}
+
+void nsHttpChannelAuthProvider::SetAuthorizationHeader(
+ nsHttpAuthCache* authCache, const nsHttpAtom& header,
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ const nsACString& path, nsHttpAuthIdentity& ident) {
+ nsHttpAuthEntry* entry = nullptr;
+ nsresult rv;
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsISupports** continuationState;
+
+ nsAutoCString suffix;
+ if (header == nsHttp::Proxy_Authorization) {
+ continuationState = &mProxyAuthContinuationState;
+
+ if (mProxyInfo) {
+ nsAutoCString type;
+ mProxyInfo->GetType(type);
+ if (type.EqualsLiteral("https")) {
+ // Let this be overriden by anything from the cache.
+ auto const& pa = mProxyInfo->ProxyAuthorizationHeader();
+ if (!pa.IsEmpty()) {
+ rv = mAuthChannel->SetProxyCredentials(pa);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ }
+ } else {
+ continuationState = &mAuthContinuationState;
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ rv = authCache->GetAuthEntryForPath(scheme, host, port, path, suffix, &entry);
+ if (NS_SUCCEEDED(rv)) {
+ // if we are trying to add a header for origin server auth and if the
+ // URL contains an explicit username, then try the given username first.
+ // we only want to do this, however, if we know the URL requires auth
+ // based on the presence of an auth cache entry for this URL (which is
+ // true since we are here). but, if the username from the URL matches
+ // the username from the cache, then we should prefer the password
+ // stored in the cache since that is most likely to be valid.
+ if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
+ GetIdentityFromURI(0, ident);
+ // if the usernames match, then clear the ident so we will pick
+ // up the one from the auth cache instead.
+ // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load
+ // flag.
+ if (ident.User() == entry->User()) {
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
+ !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
+ ident.Clear();
+ }
+ }
+ }
+ bool identFromURI;
+ if (ident.IsEmpty()) {
+ ident = entry->Identity();
+ identFromURI = false;
+ } else {
+ identFromURI = true;
+ }
+
+ nsCString temp; // this must have the same lifetime as creds
+ nsAutoCString creds(entry->Creds());
+ // we can only send a preemptive Authorization header if we have either
+ // stored credentials or a stored challenge from which to derive
+ // credentials. if the identity is from the URI, then we cannot use
+ // the stored credentials.
+ if ((creds.IsEmpty() || identFromURI) && !entry->Challenge().IsEmpty()) {
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(entry->Challenge(), unused, getter_AddRefs(auth));
+ if (NS_SUCCEEDED(rv)) {
+ bool proxyAuth = (header == nsHttp::Proxy_Authorization);
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path,
+ entry->Realm(), entry->Challenge(), ident,
+ entry->mMetaData, temp);
+ if (NS_SUCCEEDED(rv)) creds = temp;
+
+ // make sure the continuation state is null since we do not
+ // support mixing preemptive and 'multirequest' authentication.
+ NS_IF_RELEASE(*continuationState);
+ }
+ }
+ if (!creds.IsEmpty()) {
+ LOG((" adding \"%s\" request header\n", header.get()));
+ if (header == nsHttp::Proxy_Authorization) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // suppress defensive auth prompting for this channel since we know
+ // that we already prompted at least once this session. we only do
+ // this for non-proxy auth since the URL's userpass is not used for
+ // proxy auth.
+ if (header == nsHttp::Authorization) mSuppressDefensiveAuth = true;
+ } else {
+ ident.Clear(); // don't remember the identity
+ }
+ }
+}
+
+nsresult nsHttpChannelAuthProvider::GetCurrentPath(nsACString& path) {
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
+ if (url) {
+ rv = url->GetDirectory(path);
+ } else {
+ rv = mURI->GetPathQueryRef(path);
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
+ nsIHttpChannelAuthProvider, nsIAuthPromptCallback,
+ nsIHttpAuthenticatorCallback)
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.h b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
new file mode 100644
index 0000000000..22f9a27ede
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et cin ts=4 sw=2 sts=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 nsHttpChannelAuthProvider_h__
+#define nsHttpChannelAuthProvider_h__
+
+#include "nsIHttpChannelAuthProvider.h"
+#include "nsIAuthPromptCallback.h"
+#include "nsIHttpAuthenticatorCallback.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsHttpAuthCache.h"
+#include "nsProxyInfo.h"
+#include "nsICancelable.h"
+
+class nsIHttpAuthenticableChannel;
+class nsIHttpAuthenticator;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+struct nsHttpAtom;
+
+class nsHttpChannelAuthProvider final : public nsIHttpChannelAuthProvider,
+ public nsIAuthPromptCallback,
+ public nsIHttpAuthenticatorCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSIHTTPCHANNELAUTHPROVIDER
+ NS_DECL_NSIAUTHPROMPTCALLBACK
+ NS_DECL_NSIHTTPAUTHENTICATORCALLBACK
+
+ nsHttpChannelAuthProvider();
+
+ private:
+ virtual ~nsHttpChannelAuthProvider();
+
+ const nsCString& ProxyHost() const {
+ return mProxyInfo ? mProxyInfo->Host() : EmptyCString();
+ }
+
+ int32_t ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
+
+ const nsCString& Host() const { return mHost; }
+ int32_t Port() const { return mPort; }
+ bool UsingSSL() const { return mUsingSSL; }
+
+ bool UsingHttpProxy() const {
+ return mProxyInfo && (mProxyInfo->IsHTTP() || mProxyInfo->IsHTTPS());
+ }
+
+ [[nodiscard]] nsresult PrepareForAuthentication(bool proxyAuth);
+ [[nodiscard]] nsresult GenCredsAndSetEntry(
+ nsIHttpAuthenticator*, bool proxyAuth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& dir,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, nsCOMPtr<nsISupports>& session,
+ nsACString& result);
+ [[nodiscard]] nsresult GetAuthenticator(const nsACString& aChallenge,
+ nsCString& authType,
+ nsIHttpAuthenticator** auth);
+ void ParseRealm(const nsACString&, nsACString& realm);
+ void GetIdentityFromURI(uint32_t authFlags, nsHttpAuthIdentity&);
+
+ /**
+ * Following three methods return NS_ERROR_IN_PROGRESS when
+ * nsIAuthPrompt2.asyncPromptAuth method is called. This result indicates
+ * the user's decision will be gathered in a callback and is not an actual
+ * error.
+ */
+ [[nodiscard]] nsresult GetCredentials(const nsACString& challenges,
+ bool proxyAuth, nsCString& creds);
+ [[nodiscard]] nsresult GetCredentialsForChallenge(
+ const nsACString& aChallenge, const nsACString& aAuthType, bool proxyAuth,
+ nsIHttpAuthenticator* auth, nsCString& creds);
+ [[nodiscard]] nsresult PromptForIdentity(uint32_t level, bool proxyAuth,
+ const nsACString& realm,
+ const nsACString& authType,
+ uint32_t authFlags,
+ nsHttpAuthIdentity&);
+
+ bool ConfirmAuth(const char* bundleKey, bool doYesNoPrompt);
+ void SetAuthorizationHeader(nsHttpAuthCache*, const nsHttpAtom& header,
+ const nsACString& scheme, const nsACString& host,
+ int32_t port, const nsACString& path,
+ nsHttpAuthIdentity& ident);
+ [[nodiscard]] nsresult GetCurrentPath(nsACString&);
+ /**
+ * Return all information needed to build authorization information,
+ * all parameters except proxyAuth are out parameters. proxyAuth specifies
+ * with what authorization we work (WWW or proxy).
+ */
+ [[nodiscard]] nsresult GetAuthorizationMembers(
+ bool proxyAuth, nsACString& scheme, nsCString& host, int32_t& port,
+ nsACString& path, nsHttpAuthIdentity*& ident,
+ nsISupports**& continuationState);
+ /**
+ * Method called to resume suspended transaction after we got credentials
+ * from the user. Called from OnAuthAvailable callback or OnAuthCancelled
+ * when credentials for next challenge were obtained synchronously.
+ */
+ [[nodiscard]] nsresult ContinueOnAuthAvailable(const nsACString& creds);
+
+ [[nodiscard]] nsresult DoRedirectChannelToHttps();
+
+ /**
+ * A function that takes care of reading STS headers and enforcing STS
+ * load rules. After a secure channel is erected, STS requires the channel
+ * to be trusted or any STS header data on the channel is ignored.
+ * This is called from ProcessResponse.
+ */
+ [[nodiscard]] nsresult ProcessSTSHeader();
+
+ // Depending on the pref setting, the authentication dialog may be blocked
+ // for all sub-resources, blocked for cross-origin sub-resources, or
+ // always allowed for sub-resources.
+ // For more details look at the bug 647010.
+ bool BlockPrompt(bool proxyAuth);
+
+ // Store credentials to the cache when appropriate aFlags are set.
+ [[nodiscard]] nsresult UpdateCache(
+ nsIHttpAuthenticator* aAuth, const nsACString& aScheme,
+ const nsACString& aHost, int32_t aPort, const nsACString& aDirectory,
+ const nsACString& aRealm, const nsACString& aChallenge,
+ const nsHttpAuthIdentity& aIdent, const nsACString& aCreds,
+ uint32_t aGenerateFlags, nsISupports* aSessionState, bool aProxyAuth);
+
+ private:
+ nsIHttpAuthenticableChannel* mAuthChannel{nullptr}; // weak ref
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ nsCString mHost;
+ int32_t mPort{-1};
+ bool mUsingSSL{false};
+ bool mProxyUsingSSL{false};
+ bool mIsPrivate{false};
+
+ nsISupports* mProxyAuthContinuationState{nullptr};
+ nsCString mProxyAuthType;
+ nsISupports* mAuthContinuationState{nullptr};
+ nsCString mAuthType;
+ nsHttpAuthIdentity mIdent;
+ nsHttpAuthIdentity mProxyIdent;
+
+ // Reference to the prompt waiting in prompt queue. The channel is
+ // responsible to call its cancel method when user in any way cancels
+ // this request.
+ nsCOMPtr<nsICancelable> mAsyncPromptAuthCancelable;
+ // Saved in GetCredentials when prompt is asynchronous, the first challenge
+ // we obtained from the server with 401/407 response, will be processed in
+ // OnAuthAvailable callback.
+ nsCString mCurrentChallenge;
+ // Saved in GetCredentials when prompt is asynchronous, remaning challenges
+ // we have to process when user cancels the auth dialog for the current
+ // challenge.
+ nsCString mRemainingChallenges;
+
+ // True when we need to authenticate to proxy, i.e. when we get 407
+ // response. Used in OnAuthAvailable and OnAuthCancelled callbacks.
+ uint32_t mProxyAuth : 1;
+ uint32_t mTriedProxyAuth : 1;
+ uint32_t mTriedHostAuth : 1;
+ uint32_t mSuppressDefensiveAuth : 1;
+
+ // If a cross-origin sub-resource is being loaded, this flag will be set.
+ // In that case, the prompt text will be different to warn users.
+ uint32_t mCrossOrigin : 1;
+ uint32_t mConnectionBased : 1;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ nsCOMPtr<nsICancelable> mGenerateCredentialsCancelable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpChannelAuthProvider_h__
diff --git a/netwerk/protocol/http/nsHttpChunkedDecoder.cpp b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
new file mode 100644
index 0000000000..5ee8abc088
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -0,0 +1,170 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include <errno.h>
+#include "nsHttpChunkedDecoder.h"
+#include <algorithm>
+#include <string.h>
+
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpChunkedDecoder <public>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChunkedDecoder::HandleChunkedContent(
+ char* buf, uint32_t count, uint32_t* contentRead,
+ uint32_t* contentRemaining) {
+ LOG(("nsHttpChunkedDecoder::HandleChunkedContent [count=%u]\n", count));
+
+ *contentRead = 0;
+
+ // from RFC2616 section 3.6.1, the chunked transfer coding is defined as:
+ //
+ // Chunked-Body = *chunk
+ // last-chunk
+ // trailer
+ // CRLF
+ // chunk = chunk-size [ chunk-extension ] CRLF
+ // chunk-data CRLF
+ // chunk-size = 1*HEX
+ // last-chunk = 1*("0") [ chunk-extension ] CRLF
+ //
+ // chunk-extension = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+ // chunk-ext-name = token
+ // chunk-ext-val = token | quoted-string
+ // chunk-data = chunk-size(OCTET)
+ // trailer = *(entity-header CRLF)
+ //
+ // the chunk-size field is a string of hex digits indicating the size of the
+ // chunk. the chunked encoding is ended by any chunk whose size is zero,
+ // followed by the trailer, which is terminated by an empty line.
+
+ while (count) {
+ if (mChunkRemaining) {
+ uint32_t amt = std::min(mChunkRemaining, count);
+
+ count -= amt;
+ mChunkRemaining -= amt;
+
+ *contentRead += amt;
+ buf += amt;
+ } else if (mReachedEOF) {
+ break; // done
+ } else {
+ uint32_t bytesConsumed = 0;
+
+ nsresult rv = ParseChunkRemaining(buf, count, &bytesConsumed);
+ if (NS_FAILED(rv)) return rv;
+
+ count -= bytesConsumed;
+
+ if (count) {
+ // shift buf by bytesConsumed
+ memmove(buf, buf + bytesConsumed, count);
+ }
+ }
+ }
+
+ *contentRemaining = count;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChunkedDecoder <private>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChunkedDecoder::ParseChunkRemaining(char* buf, uint32_t count,
+ uint32_t* bytesConsumed) {
+ MOZ_ASSERT(mChunkRemaining == 0, "chunk remaining should be zero");
+ MOZ_ASSERT(count, "unexpected");
+
+ *bytesConsumed = 0;
+
+ char* p = static_cast<char*>(memchr(buf, '\n', count));
+ if (p) {
+ *p = 0;
+ count = p - buf; // new length
+ *bytesConsumed = count + 1; // length + newline
+ if ((p > buf) && (*(p - 1) == '\r')) { // eliminate a preceding CR
+ *(p - 1) = 0;
+ count--;
+ }
+
+ // make buf point to the full line buffer to parse
+ if (!mLineBuf.IsEmpty()) {
+ mLineBuf.Append(buf, count);
+ buf = (char*)mLineBuf.get();
+ count = mLineBuf.Length();
+ }
+
+ if (mWaitEOF) {
+ if (*buf) {
+ LOG(("got trailer: %s\n", buf));
+ // allocate a header array for the trailers on demand
+ if (!mTrailers) {
+ mTrailers = MakeUnique<nsHttpHeaderArray>();
+ }
+
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+ if (NS_SUCCEEDED(
+ mTrailers->ParseHeaderLine(nsDependentCSubstring(buf, count),
+ &hdr, &headerNameOriginal, &val))) {
+ if (hdr == nsHttp::Server_Timing) {
+ Unused << mTrailers->SetHeaderFromNet(hdr, headerNameOriginal, val,
+ true);
+ }
+ }
+ } else {
+ mWaitEOF = false;
+ mReachedEOF = true;
+ LOG(("reached end of chunked-body\n"));
+ }
+ } else if (*buf) {
+ char* endptr;
+ unsigned long parsedval; // could be 64 bit, could be 32
+
+ // ignore any chunk-extensions
+ if ((p = strchr(buf, ';')) != nullptr) {
+ *p = 0;
+ }
+
+ // mChunkRemaining is an uint32_t!
+ parsedval = strtoul(buf, &endptr, 16);
+ mChunkRemaining = (uint32_t)parsedval;
+
+ if ((endptr == buf) || ((errno == ERANGE) && (parsedval == ULONG_MAX)) ||
+ (parsedval != mChunkRemaining)) {
+ LOG(("failed parsing hex on string [%s]\n", buf));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // we've discovered the last chunk
+ if (mChunkRemaining == 0) mWaitEOF = true;
+ }
+
+ // ensure that the line buffer is clear
+ mLineBuf.Truncate();
+ } else {
+ // save the partial line; wait for more data
+ *bytesConsumed = count;
+ // ignore a trailing CR
+ if (buf[count - 1] == '\r') count--;
+ mLineBuf.Append(buf, count);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChunkedDecoder.h b/netwerk/protocol/http/nsHttpChunkedDecoder.h
new file mode 100644
index 0000000000..0e627087dc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.h
@@ -0,0 +1,50 @@
+/* -*- 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 nsHttpChunkedDecoder_h__
+#define nsHttpChunkedDecoder_h__
+
+#include "nsError.h"
+#include "nsString.h"
+#include "nsHttpHeaderArray.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpChunkedDecoder {
+ public:
+ nsHttpChunkedDecoder() = default;
+ ~nsHttpChunkedDecoder() = default;
+
+ bool ReachedEOF() { return mReachedEOF; }
+
+ // called by the transaction to handle chunked content.
+ [[nodiscard]] nsresult HandleChunkedContent(char* buf, uint32_t count,
+ uint32_t* contentRead,
+ uint32_t* contentRemaining);
+
+ nsHttpHeaderArray* Trailers() { return mTrailers.get(); }
+
+ UniquePtr<nsHttpHeaderArray> TakeTrailers() { return std::move(mTrailers); }
+
+ // How mush data is still missing(needs to arrive) from the current chunk.
+ uint32_t GetChunkRemaining() { return mChunkRemaining; }
+
+ private:
+ [[nodiscard]] nsresult ParseChunkRemaining(char* buf, uint32_t count,
+ uint32_t* bytesConsumed);
+
+ private:
+ UniquePtr<nsHttpHeaderArray> mTrailers;
+ uint32_t mChunkRemaining{0};
+ nsCString mLineBuf; // may hold a partial line
+ bool mReachedEOF{false};
+ bool mWaitEOF{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp
new file mode 100644
index 0000000000..023c06f3f3
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -0,0 +1,2609 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "ASpdySession.h"
+#include "NSSErrorsService.h"
+#include "TLSTransportLayer.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozpkix/pkixnss.h"
+#include "nsCRT.h"
+#include "nsHttpConnection.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsIClassOfService.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsPreloadedStream.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransport2.h"
+#include "nsSocketTransportService2.h"
+#include "nsStringStream.h"
+#include "sslerr.h"
+#include "sslt.h"
+
+namespace mozilla::net {
+
+enum TlsHandshakeResult : uint32_t {
+ EchConfigSuccessful = 0,
+ EchConfigFailed,
+ NoEchConfigSuccessful,
+ NoEchConfigFailed,
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <public>
+//-----------------------------------------------------------------------------
+
+nsHttpConnection::nsHttpConnection() : mHttpHandler(gHttpHandler) {
+ LOG(("Creating nsHttpConnection @%p\n", this));
+
+ // the default timeout is for when this connection has not yet processed a
+ // transaction
+ static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
+ mIdleTimeout = (k5Sec < gHttpHandler->IdleTimeout())
+ ? k5Sec
+ : gHttpHandler->IdleTimeout();
+
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
+}
+
+nsHttpConnection::~nsHttpConnection() {
+ LOG(("Destroying nsHttpConnection @%p\n", this));
+
+ if (!mEverUsedSpdy) {
+ LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n", this,
+ mHttp1xTransactionCount));
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_CONN,
+ mHttp1xTransactionCount);
+ nsHttpConnectionInfo* ci = nullptr;
+ if (mTransaction) {
+ ci = mTransaction->ConnectionInfo();
+ }
+ if (!ci) {
+ ci = mConnInfo;
+ }
+
+ MOZ_ASSERT(ci);
+ if (ci->GetIsTrrServiceChannel()) {
+ Telemetry::Accumulate(Telemetry::DNS_TRR_REQUEST_PER_CONN,
+ mHttp1xTransactionCount);
+ }
+ }
+
+ if (mTotalBytesRead) {
+ uint32_t totalKBRead = static_cast<uint32_t>(mTotalBytesRead >> 10);
+ LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n", this,
+ totalKBRead, mEverUsedSpdy));
+ Telemetry::Accumulate(mEverUsedSpdy ? Telemetry::SPDY_KBREAD_PER_CONN2
+ : Telemetry::HTTP_KBREAD_PER_CONN2,
+ totalKBRead);
+ }
+
+ if (mThroughCaptivePortal) {
+ if (mTotalBytesRead || mTotalBytesWritten) {
+ auto total =
+ Clamp<uint32_t>((mTotalBytesRead >> 10) + (mTotalBytesWritten >> 10),
+ 0, std::numeric_limits<uint32_t>::max());
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_DATA_TRANSFERRED_CAPTIVE_PORTAL,
+ total);
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_HTTP_CONNECTIONS_CAPTIVE_PORTAL, 1);
+ }
+
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ auto ReleaseSocketTransport =
+ [socketTransport(std::move(mSocketTransport))]() mutable {
+ socketTransport = nullptr;
+ };
+ if (OnSocketThread()) {
+ ReleaseSocketTransport();
+ } else {
+ gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnection::~nsHttpConnection", ReleaseSocketTransport));
+ }
+}
+
+nsresult nsHttpConnection::Init(
+ nsHttpConnectionInfo* info, uint16_t maxHangTime,
+ nsISocketTransport* transport, nsIAsyncInputStream* instream,
+ nsIAsyncOutputStream* outstream, bool connectedTransport, nsresult status,
+ nsIInterfaceRequestor* callbacks, PRIntervalTime rtt, bool forWebSocket) {
+ LOG1(("nsHttpConnection::Init this=%p sockettransport=%p forWebSocket=%d",
+ this, transport, forWebSocket));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+ MOZ_ASSERT(NS_SUCCEEDED(status) || !connectedTransport);
+
+ mConnectedTransport = connectedTransport;
+ mConnInfo = info;
+ MOZ_ASSERT(mConnInfo);
+
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+ mRtt = rtt;
+ mMaxHangTime = PR_SecondsToInterval(maxHangTime);
+
+ mSocketTransport = transport;
+ mSocketIn = instream;
+ mSocketOut = outstream;
+ mForWebSocket = forWebSocket;
+
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "nsHttpConnection::mCallbacks", callbacks, false);
+
+ mErrorBeforeConnect = status;
+ if (NS_SUCCEEDED(mErrorBeforeConnect)) {
+ mSocketTransport->SetEventSink(this, nullptr);
+ mSocketTransport->SetSecurityCallbacks(this);
+ ChangeConnectionState(ConnectionState::INITED);
+ } else {
+ SetCloseReason(ToCloseReason(mErrorBeforeConnect));
+ }
+
+ mTlsHandshaker = new TlsHandshaker(mConnInfo, this);
+ return NS_OK;
+}
+
+void nsHttpConnection::ChangeState(HttpConnectionState newState) {
+ LOG(("nsHttpConnection::ChangeState %d -> %d [this=%p]", mState, newState,
+ this));
+ mState = newState;
+}
+
+nsresult nsHttpConnection::TryTakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& list) {
+ nsresult rv = mTransaction->TakeSubTransactions(list);
+
+ if (rv == NS_ERROR_ALREADY_OPENED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(
+ ("TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing\n"));
+ MOZ_ASSERT(false,
+ "TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return rv;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
+ MOZ_ASSERT(false,
+ "unexpected result from "
+ "nsAHttpTransaction::TakeSubTransactions()");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return rv;
+ }
+
+ return rv;
+}
+
+void nsHttpConnection::ResetTransaction(RefPtr<nsAHttpTransaction>&& trans) {
+ MOZ_ASSERT(trans);
+ mSpdySession->SetConnection(trans->Connection());
+ trans->SetConnection(nullptr);
+ trans->DoNotRemoveAltSvc();
+ trans->Close(NS_ERROR_NET_RESET);
+}
+
+nsresult nsHttpConnection::MoveTransactionsToSpdy(
+ nsresult status, nsTArray<RefPtr<nsAHttpTransaction> >& list) {
+ if (NS_FAILED(status)) { // includes NS_ERROR_NOT_IMPLEMENTED
+ MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
+
+ // If this transaction is used to drive websocket, we reset it to put it in
+ // the pending queue. Once we know if the server supports websocket or not,
+ // the pending queue will be processed.
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans && trans->IsWebsocketUpgrade()) {
+ LOG(("nsHttpConnection resetting transaction for websocket upgrade"));
+ // websocket upgrade needs NonSticky for transaction reset
+ mTransaction->MakeNonSticky();
+ ResetTransaction(std::move(mTransaction));
+ mTransaction = nullptr;
+ return NS_OK;
+ }
+
+ // This is ok - treat mTransaction as a single real request.
+ // Wrap the old http transaction into the new spdy session
+ // as the first stream.
+ LOG(
+ ("nsHttpConnection::MoveTransactionsToSpdy moves single transaction %p "
+ "into SpdySession %p\n",
+ mTransaction.get(), mSpdySession.get()));
+ nsresult rv = AddTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ int32_t count = list.Length();
+
+ LOG(
+ ("nsHttpConnection::MoveTransactionsToSpdy moving transaction list "
+ "len=%d "
+ "into SpdySession %p\n",
+ count, mSpdySession.get()));
+
+ if (!count) {
+ mTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+ }
+
+ for (int32_t index = 0; index < count; ++index) {
+ RefPtr<nsAHttpTransaction> transaction = list[index];
+ nsHttpTransaction* trans = transaction->QueryHttpTransaction();
+ if (trans && trans->IsWebsocketUpgrade()) {
+ LOG(("nsHttpConnection resetting a transaction for websocket upgrade"));
+ // websocket upgrade needs NonSticky for transaction reset
+ transaction->MakeNonSticky();
+ ResetTransaction(std::move(transaction));
+ transaction = nullptr;
+ continue;
+ }
+ nsresult rv = AddTransaction(list[index], mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsHttpConnection::Start0RTTSpdy(SpdyVersion spdyVersion) {
+ LOG(("nsHttpConnection::Start0RTTSpdy [this=%p]", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mDid0RTTSpdy = true;
+ mUsingSpdyVersion = spdyVersion;
+ mEverUsedSpdy = true;
+ mSpdySession =
+ ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, true);
+
+ if (mTransaction) {
+ nsTArray<RefPtr<nsAHttpTransaction> > list;
+ nsresult rv = TryTakeSubTransactions(list);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ LOG(
+ ("nsHttpConnection::Start0RTTSpdy [this=%p] failed taking "
+ "subtransactions rv=%" PRIx32,
+ this, static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ rv = MoveTransactionsToSpdy(rv, list);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::Start0RTTSpdy [this=%p] failed moving "
+ "transactions rv=%" PRIx32,
+ this, static_cast<uint32_t>(rv)));
+ return;
+ }
+ }
+
+ mTransaction = mSpdySession;
+}
+
+void nsHttpConnection::StartSpdy(nsITLSSocketControl* sslControl,
+ SpdyVersion spdyVersion) {
+ LOG(("nsHttpConnection::StartSpdy [this=%p, mDid0RTTSpdy=%d]\n", this,
+ mDid0RTTSpdy));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mSpdySession || mDid0RTTSpdy);
+
+ mUsingSpdyVersion = spdyVersion;
+ mEverUsedSpdy = true;
+ if (sslControl) {
+ sslControl->SetDenyClientCert(true);
+ }
+
+ if (!mDid0RTTSpdy) {
+ mSpdySession =
+ ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, false);
+ }
+
+ if (!mReportedSpdy) {
+ mReportedSpdy = true;
+ // See bug 1797729.
+ // It's possible that we already have a HTTP/3 connection that can be
+ // coleased with this connection. We should avoid coalescing with the
+ // existing HTTP/3 connection if the transaction doesn't allow to use
+ // HTTP/3.
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, true,
+ mTransactionDisallowHttp3);
+ }
+
+ // Setting the connection as reused allows some transactions that fail
+ // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
+ // to handle clean rejections (such as those that arrived after
+ // a server goaway was generated).
+ mIsReused = true;
+
+ // If mTransaction is a muxed object it might represent
+ // several requests. If so, we need to unpack that and
+ // pack them all into a new spdy session.
+
+ nsTArray<RefPtr<nsAHttpTransaction> > list;
+ nsresult status = NS_OK;
+ if (!mDid0RTTSpdy && mTransaction) {
+ status = TryTakeSubTransactions(list);
+
+ if (NS_FAILED(status) && status != NS_ERROR_NOT_IMPLEMENTED) {
+ return;
+ }
+ }
+
+ if (NeedSpdyTunnel()) {
+ LOG3(
+ ("nsHttpConnection::StartSpdy %p Connecting To a HTTP/2 "
+ "Proxy and Need Connect",
+ this));
+ SetTunnelSetupDone();
+ }
+
+ nsresult rv = NS_OK;
+ bool spdyProxy = mConnInfo->UsingHttpsProxy() && mConnInfo->UsingConnect() &&
+ !mHasTLSTransportLayer;
+ if (spdyProxy) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCi;
+ rv = mConnInfo->CreateWildCard(getter_AddRefs(wildCardProxyCi));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ gHttpHandler->ConnMgr()->MoveToWildCardConnEntry(mConnInfo, wildCardProxyCi,
+ this);
+ mConnInfo = wildCardProxyCi;
+ MOZ_ASSERT(mConnInfo);
+ }
+
+ if (!mDid0RTTSpdy && mTransaction) {
+ if (spdyProxy) {
+ if (NS_FAILED(status)) {
+ // proxy upgrade needs Restartable for transaction reset
+ // note that using NonSticky here won't work because it breaks
+ // netwerk/test/unit/test_websocket_server.js - h1 ws with h2 proxy
+ mTransaction->MakeRestartable();
+ ResetTransaction(std::move(mTransaction));
+ mTransaction = nullptr;
+ } else {
+ for (auto trans : list) {
+ if (!mSpdySession->Connection()) {
+ mSpdySession->SetConnection(trans->Connection());
+ }
+ trans->SetConnection(nullptr);
+ trans->DoNotRemoveAltSvc();
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+ }
+ } else {
+ rv = MoveTransactionsToSpdy(status, list);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ }
+
+ // Disable TCP Keepalives - use SPDY ping instead.
+ rv = DisableTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
+ "rv[0x%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+ }
+
+ mIdleTimeout = gHttpHandler->SpdyTimeout() * mDefaultTimeoutFactor;
+
+ mTransaction = mSpdySession;
+
+ if (mDontReuse) {
+ mSpdySession->DontReuse();
+ }
+}
+
+void nsHttpConnection::PostProcessNPNSetup(bool handshakeSucceeded,
+ bool hasSecurityInfo,
+ bool earlyDataUsed) {
+ if (mTransaction) {
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_TLS_HANDSHAKE_ENDED, 0);
+ }
+
+ // this is happening after the bootstrap was originally written to. so update
+ // it.
+ if (mTransaction && mTransaction->QueryNullTransaction() &&
+ (mBootstrappedTimings.secureConnectionStart.IsNull() ||
+ mBootstrappedTimings.tcpConnectEnd.IsNull())) {
+ mBootstrappedTimings.secureConnectionStart =
+ mTransaction->QueryNullTransaction()->GetSecureConnectionStart();
+ mBootstrappedTimings.tcpConnectEnd =
+ mTransaction->QueryNullTransaction()->GetTcpConnectEnd();
+ }
+
+ if (hasSecurityInfo) {
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ }
+
+ if (earlyDataUsed) {
+ // Didn't get 0RTT OK, back out of the "attempting 0RTT" state
+ LOG(("nsHttpConnection::PostProcessNPNSetup [this=%p] 0rtt failed", this));
+ if (mTransaction && NS_FAILED(mTransaction->Finish0RTT(true, true))) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ }
+ mContentBytesWritten0RTT = 0;
+ if (mDid0RTTSpdy) {
+ Reset0RttForSpdy();
+ }
+ }
+
+ if (hasSecurityInfo) {
+ // Telemetry for tls failure rate with and without esni;
+ bool echConfigUsed = false;
+ mSocketTransport->GetEchConfigUsed(&echConfigUsed);
+ TlsHandshakeResult result =
+ echConfigUsed
+ ? (handshakeSucceeded ? TlsHandshakeResult::EchConfigSuccessful
+ : TlsHandshakeResult::EchConfigFailed)
+ : (handshakeSucceeded ? TlsHandshakeResult::NoEchConfigSuccessful
+ : TlsHandshakeResult::NoEchConfigFailed);
+ Telemetry::Accumulate(Telemetry::ECHCONFIG_SUCCESS_RATE, result);
+ }
+}
+
+void nsHttpConnection::Reset0RttForSpdy() {
+ // Reset the work done by Start0RTTSpdy
+ mUsingSpdyVersion = SpdyVersion::NONE;
+ mTransaction = nullptr;
+ mSpdySession = nullptr;
+ // We have to reset this here, just in case we end up starting spdy again,
+ // so it can actually do everything it needs to do.
+ mDid0RTTSpdy = false;
+}
+
+// called on the socket thread
+nsresult nsHttpConnection::Activate(nsAHttpTransaction* trans, uint32_t caps,
+ int32_t pri) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG1(("nsHttpConnection::Activate [this=%p trans=%p caps=%x]\n", this, trans,
+ caps));
+
+ if (!mExperienced && !trans->IsNullTransaction()) {
+ mHasFirstHttpTransaction = true;
+ if (mTlsHandshaker->NPNComplete()) {
+ mExperienced = true;
+ }
+ if (mBootstrappedTimingsSet) {
+ mBootstrappedTimingsSet = false;
+ nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
+ if (hTrans) {
+ hTrans->BootstrapTimings(mBootstrappedTimings);
+ SetUrgentStartPreferred(hTrans->GetClassOfService().Flags() &
+ nsIClassOfService::UrgentStart);
+ }
+ }
+ mBootstrappedTimings = TimingStruct();
+ }
+
+ if (caps & NS_HTTP_LARGE_KEEPALIVE) {
+ mDefaultTimeoutFactor = StaticPrefs::network_http_largeKeepaliveFactor();
+ }
+
+ mTransactionCaps = caps;
+ mPriority = pri;
+
+ if (mHasFirstHttpTransaction && mExperienced) {
+ mHasFirstHttpTransaction = false;
+ mExperienceState |= ConnectionExperienceState::Experienced;
+ }
+
+ if (mTransaction && (mUsingSpdyVersion != SpdyVersion::NONE)) {
+ return AddTransaction(trans, pri);
+ }
+
+ NS_ENSURE_ARG_POINTER(trans);
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
+
+ // reset the read timers to wash away any idle time
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+
+ // Connection failures are Activated() just like regular transacions.
+ // If we don't have a confirmation of a connected socket then test it
+ // with a write() to get relevant error code.
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ mSocketOutCondition = mErrorBeforeConnect;
+ mTransaction = trans;
+ CloseTransaction(mTransaction, mSocketOutCondition);
+ return mSocketOutCondition;
+ }
+
+ if (!mConnectedTransport) {
+ uint32_t count;
+ mSocketOutCondition = NS_ERROR_FAILURE;
+ if (mSocketOut) {
+ mSocketOutCondition = mSocketOut->Write("", 0, &count);
+ }
+ if (NS_FAILED(mSocketOutCondition) &&
+ mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(("nsHttpConnection::Activate [this=%p] Bad Socket %" PRIx32 "\n",
+ this, static_cast<uint32_t>(mSocketOutCondition)));
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mTransaction = trans;
+ CloseTransaction(mTransaction, mSocketOutCondition);
+ return mSocketOutCondition;
+ }
+ }
+
+ // Update security callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ trans->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ SetSecurityCallbacks(callbacks);
+ mTlsHandshaker->SetupSSL(mInSpdyTunnel, mForcePlainText);
+ if (mTlsHandshaker->NPNComplete()) {
+ // For non-HTTPS connection, change the state to TRANSFERING directly.
+ ChangeConnectionState(ConnectionState::TRANSFERING);
+ } else {
+ ChangeConnectionState(ConnectionState::TLS_HANDSHAKING);
+ }
+
+ // take ownership of the transaction
+ mTransaction = trans;
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (NS_SUCCEEDED(mSocketTransport->GetTlsSocketControl(
+ getter_AddRefs(tlsSocketControl))) &&
+ tlsSocketControl) {
+ tlsSocketControl->SetBrowserId(mTransaction->BrowserId());
+ }
+
+ MOZ_ASSERT(!mIdleMonitoring, "Activating a connection with an Idle Monitor");
+ mIdleMonitoring = false;
+
+ // set mKeepAlive according to what will be requested
+ mKeepAliveMask = mKeepAlive = (caps & NS_HTTP_ALLOW_KEEPALIVE);
+
+ mTransactionDisallowHttp3 |= (caps & NS_HTTP_DISALLOW_HTTP3);
+
+ // need to handle HTTP CONNECT tunnels if this is the first time if
+ // we are tunneling through a proxy
+ nsresult rv = CheckTunnelIsNeeded();
+ if (NS_FAILED(rv)) goto failed_activation;
+
+ // Clear the per activation counter
+ mCurrentBytesRead = 0;
+
+ // The overflow state is not needed between activations
+ mInputOverflow = nullptr;
+
+ mResponseTimeoutEnabled = gHttpHandler->ResponseTimeoutEnabled() &&
+ mTransaction->ResponseTimeout() > 0 &&
+ mTransaction->ResponseTimeoutEnabled();
+
+ rv = StartShortLivedTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::Activate [%p] "
+ "StartShortLivedTCPKeepalives failed rv[0x%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+ }
+
+ trans->OnActivated();
+
+ rv = OnOutputStreamReady(mSocketOut);
+
+ if (NS_SUCCEEDED(rv) && mContinueHandshakeDone) {
+ mContinueHandshakeDone();
+ }
+ mContinueHandshakeDone = nullptr;
+
+failed_activation:
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ }
+
+ return rv;
+}
+
+nsresult nsHttpConnection::AddTransaction(nsAHttpTransaction* httpTransaction,
+ int32_t priority) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSpdySession && (mUsingSpdyVersion != SpdyVersion::NONE),
+ "AddTransaction to live http connection without spdy/quic");
+
+ // If this is a wild card nshttpconnection (i.e. a spdy proxy) then
+ // it is important to start the stream using the specific connection
+ // info of the transaction to ensure it is routed on the right tunnel
+
+ nsHttpConnectionInfo* transCI = httpTransaction->ConnectionInfo();
+
+ bool needTunnel = transCI->UsingHttpsProxy();
+ needTunnel = needTunnel && !mHasTLSTransportLayer;
+ needTunnel = needTunnel && transCI->UsingConnect();
+ needTunnel = needTunnel && httpTransaction->QueryHttpTransaction();
+
+ // Let the transaction know that the tunnel is already established and we
+ // don't need to setup the tunnel again.
+ if (transCI->UsingConnect() && mEverUsedSpdy && mHasTLSTransportLayer) {
+ httpTransaction->OnProxyConnectComplete(200);
+ }
+
+ LOG(("nsHttpConnection::AddTransaction [this=%p] for %s%s", this,
+ mSpdySession ? "SPDY" : "QUIC", needTunnel ? " over tunnel" : ""));
+
+ if (mSpdySession) {
+ if (!mSpdySession->AddStream(httpTransaction, priority, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ httpTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ Unused << ResumeSend();
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::CreateTunnelStream(
+ nsAHttpTransaction* httpTransaction, nsHttpConnection** aHttpConnection,
+ bool aIsWebSocket) {
+ if (!mSpdySession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<nsHttpConnection> conn = mSpdySession->CreateTunnelStream(
+ httpTransaction, mCallbacks, mRtt, aIsWebSocket);
+ // We need to store the refrence of the Http2Session in the tunneled
+ // connection, so when nsHttpConnection::DontReuse is called the Http2Session
+ // can't be reused.
+ if (aIsWebSocket) {
+ LOG(
+ ("nsHttpConnection::CreateTunnelStream %p Set h2 session %p to "
+ "tunneled conn %p",
+ this, mSpdySession.get(), conn.get()));
+ conn->mWebSocketHttp2Session = mSpdySession;
+ }
+ conn.forget(aHttpConnection);
+ return NS_OK;
+}
+
+void nsHttpConnection::Close(nsresult reason, bool aIsShutdown) {
+ LOG(("nsHttpConnection::Close [this=%p reason=%" PRIx32
+ " mExperienceState=%x]\n",
+ this, static_cast<uint32_t>(reason),
+ static_cast<uint32_t>(mExperienceState)));
+
+ if (mConnectionState != ConnectionState::CLOSED) {
+ RecordConnectionCloseTelemetry(reason);
+ ChangeConnectionState(ConnectionState::CLOSED);
+ }
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mTlsHandshaker->NotifyClose();
+ mContinueHandshakeDone = nullptr;
+ mWebSocketHttp2Session = nullptr;
+ // Ensure TCP keepalive timer is stopped.
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ if (!mTrafficCategory.IsEmpty()) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->IncrementHttpConnection(std::move(mTrafficCategory));
+ MOZ_ASSERT(mTrafficCategory.IsEmpty());
+ }
+ }
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (tlsSocketControl) {
+ tlsSocketControl->SetHandshakeCallbackListener(nullptr);
+ }
+
+ if (NS_FAILED(reason)) {
+ if (mIdleMonitoring) EndIdleMonitoring();
+
+ // The connection and security errors clear out alt-svc mappings
+ // in case any previously validated ones are now invalid
+ if (((reason == NS_ERROR_NET_RESET) ||
+ (NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY)) &&
+ mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
+ gHttpHandler->ClearHostMapping(mConnInfo);
+ }
+ if (mTlsHandshaker->EarlyDataWasAvailable() &&
+ SecurityErrorThatMayNeedRestart(reason)) {
+ gHttpHandler->Exclude0RttTcp(mConnInfo);
+ }
+
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+
+ // If there are bytes sitting in the input queue then read them
+ // into a junk buffer to avoid generating a tcp rst by closing a
+ // socket with data pending. TLS is a classic case of this where
+ // a Alert record might be superfulous to a clean HTTP/SPDY shutdown.
+ // Never block to do this and limit it to a small amount of data.
+ // During shutdown just be fast!
+ if (mSocketIn && !aIsShutdown && !mInSpdyTunnel) {
+ char buffer[4000];
+ uint32_t count, total = 0;
+ nsresult rv;
+ do {
+ rv = mSocketIn->Read(buffer, 4000, &count);
+ if (NS_SUCCEEDED(rv)) total += count;
+ } while (NS_SUCCEEDED(rv) && count > 0 && total < 64000);
+ LOG(("nsHttpConnection::Close drained %d bytes\n", total));
+ }
+
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->Close(reason);
+ if (mSocketOut) mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mKeepAlive = false;
+ }
+}
+
+void nsHttpConnection::MarkAsDontReuse() {
+ LOG(("nsHttpConnection::MarkAsDontReuse %p\n", this));
+ mKeepAliveMask = false;
+ mKeepAlive = false;
+ mDontReuse = true;
+ mIdleTimeout = 0;
+}
+
+void nsHttpConnection::DontReuse() {
+ LOG(("nsHttpConnection::DontReuse %p spdysession=%p\n", this,
+ mSpdySession.get()));
+ MarkAsDontReuse();
+ if (mSpdySession) {
+ mSpdySession->DontReuse();
+ } else if (mWebSocketHttp2Session) {
+ LOG(("nsHttpConnection::DontReuse %p mWebSocketHttp2Session=%p\n", this,
+ mWebSocketHttp2Session.get()));
+ mWebSocketHttp2Session->DontReuse();
+ }
+}
+
+bool nsHttpConnection::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mSpdySession && CanDirectlyActivate()) {
+ return mSpdySession->TestJoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool nsHttpConnection::JoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mSpdySession && CanDirectlyActivate()) {
+ return mSpdySession->JoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool nsHttpConnection::CanReuse() {
+ if (mDontReuse || !mRemainingConnectionUses) {
+ return false;
+ }
+
+ if ((mTransaction ? (mTransaction->IsDone() ? 0U : 1U) : 0U) >=
+ mRemainingConnectionUses) {
+ return false;
+ }
+
+ bool canReuse;
+ if (mSpdySession) {
+ canReuse = mSpdySession->CanReuse();
+ } else {
+ canReuse = IsKeepAlive();
+ }
+
+ canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive();
+
+ // An idle persistent connection should not have data waiting to be read
+ // before a request is sent. Data here is likely a 408 timeout response
+ // which we would deal with later on through the restart logic, but that
+ // path is more expensive than just closing the socket now.
+
+ uint64_t dataSize;
+ if (canReuse && mSocketIn && (mUsingSpdyVersion == SpdyVersion::NONE) &&
+ mHttp1xTransactionCount &&
+ NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
+ LOG(
+ ("nsHttpConnection::CanReuse %p %s"
+ "Socket not reusable because read data pending (%" PRIu64 ") on it.\n",
+ this, mConnInfo->Origin(), dataSize));
+ canReuse = false;
+ }
+ return canReuse;
+}
+
+bool nsHttpConnection::CanDirectlyActivate() {
+ // return true if a new transaction can be addded to ths connection at any
+ // time through Activate(). In practice this means this is a healthy SPDY
+ // connection with room for more concurrent streams.
+
+ return UsingSpdy() && CanReuse() && mSpdySession &&
+ mSpdySession->RoomForMoreStreams();
+}
+
+PRIntervalTime nsHttpConnection::IdleTime() {
+ return mSpdySession ? mSpdySession->IdleTime()
+ : (PR_IntervalNow() - mLastReadTime);
+}
+
+// returns the number of seconds left before the allowable idle period
+// expires, or 0 if the period has already expied.
+uint32_t nsHttpConnection::TimeToLive() {
+ LOG(("nsHttpConnection::TTL: %p %s idle %d timeout %d\n", this,
+ mConnInfo->Origin(), IdleTime(), mIdleTimeout));
+
+ if (IdleTime() >= mIdleTimeout) {
+ return 0;
+ }
+
+ uint32_t timeToLive = PR_IntervalToSeconds(mIdleTimeout - IdleTime());
+
+ // a positive amount of time can be rounded to 0. Because 0 is used
+ // as the expiration signal, round all values from 0 to 1 up to 1.
+ if (!timeToLive) {
+ timeToLive = 1;
+ }
+ return timeToLive;
+}
+
+bool nsHttpConnection::IsAlive() {
+ if (!mSocketTransport || !mConnectedTransport) return false;
+
+ // SocketTransport::IsAlive can run the SSL state machine, so make sure
+ // the NPN options are set before that happens.
+ mTlsHandshaker->SetupSSL(mInSpdyTunnel, mForcePlainText);
+
+ bool alive;
+ nsresult rv = mSocketTransport->IsAlive(&alive);
+ if (NS_FAILED(rv)) alive = false;
+
+// #define TEST_RESTART_LOGIC
+#ifdef TEST_RESTART_LOGIC
+ if (!alive) {
+ LOG(("pretending socket is still alive to test restart logic\n"));
+ alive = true;
+ }
+#endif
+
+ return alive;
+}
+
+void nsHttpConnection::SetUrgentStartPreferred(bool urgent) {
+ if (mExperienced && !mUrgentStartPreferredKnown) {
+ // Set only according the first ever dispatched non-null transaction
+ mUrgentStartPreferredKnown = true;
+ mUrgentStartPreferred = urgent;
+ LOG(("nsHttpConnection::SetUrgentStartPreferred [this=%p urgent=%d]", this,
+ urgent));
+ }
+}
+
+//----------------------------------------------------------------------------
+// nsHttpConnection::nsAHttpConnection compatible methods
+//----------------------------------------------------------------------------
+
+nsresult nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction* trans,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ LOG(
+ ("nsHttpConnection::OnHeadersAvailable [this=%p trans=%p "
+ "response-head=%p]\n",
+ this, trans, responseHead));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NS_ENSURE_ARG_POINTER(trans);
+ MOZ_ASSERT(responseHead, "No response head?");
+
+ if (mInSpdyTunnel) {
+ DebugOnly<nsresult> rv =
+ responseHead->SetHeader(nsHttp::X_Firefox_Spdy_Proxy, "true"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // we won't change our keep-alive policy unless the server has explicitly
+ // told us to do so.
+
+ // inspect the connection headers for keep-alive info provided the
+ // transaction completed successfully. In the case of a non-sensical close
+ // and keep-alive favor the close out of conservatism.
+
+ bool explicitKeepAlive = false;
+ bool explicitClose =
+ responseHead->HasHeaderValue(nsHttp::Connection, "close") ||
+ responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "close");
+ if (!explicitClose) {
+ explicitKeepAlive =
+ responseHead->HasHeaderValue(nsHttp::Connection, "keep-alive") ||
+ responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "keep-alive");
+ }
+
+ // deal with 408 Server Timeouts
+ uint16_t responseStatus = responseHead->Status();
+ if (responseStatus == 408) {
+ // timeouts that are not caused by persistent connection reuse should
+ // not be retried for browser compatibility reasons. bug 907800. The
+ // server driven close is implicit in the 408.
+ explicitClose = true;
+ explicitKeepAlive = false;
+ }
+
+ if ((responseHead->Version() < HttpVersion::v1_1) ||
+ (requestHead->Version() < HttpVersion::v1_1)) {
+ // HTTP/1.0 connections are by default NOT persistent
+ mKeepAlive = explicitKeepAlive;
+ } else {
+ // HTTP/1.1 connections are by default persistent
+ mKeepAlive = !explicitClose;
+ }
+ mKeepAliveMask = mKeepAlive;
+
+ // if this connection is persistent, then the server may send a "Keep-Alive"
+ // header specifying the maximum number of times the connection can be
+ // reused as well as the maximum amount of time the connection can be idle
+ // before the server will close it. we ignore the max reuse count, because
+ // a "keep-alive" connection is by definition capable of being reused, and
+ // we only care about being able to reuse it once. if a timeout is not
+ // specified then we use our advertized timeout value.
+ bool foundKeepAliveMax = false;
+ if (mKeepAlive) {
+ nsAutoCString keepAlive;
+ Unused << responseHead->GetHeader(nsHttp::Keep_Alive, keepAlive);
+
+ if (mUsingSpdyVersion == SpdyVersion::NONE) {
+ const char* cp = nsCRT::strcasestr(keepAlive.get(), "timeout=");
+ if (cp) {
+ mIdleTimeout = PR_SecondsToInterval((uint32_t)atoi(cp + 8));
+ } else {
+ mIdleTimeout = gHttpHandler->IdleTimeout() * mDefaultTimeoutFactor;
+ }
+
+ cp = nsCRT::strcasestr(keepAlive.get(), "max=");
+ if (cp) {
+ int maxUses = atoi(cp + 4);
+ if (maxUses > 0) {
+ foundKeepAliveMax = true;
+ mRemainingConnectionUses = static_cast<uint32_t>(maxUses);
+ }
+ }
+ }
+
+ LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n", this,
+ PR_IntervalToSeconds(mIdleTimeout)));
+ }
+
+ if (!foundKeepAliveMax && mRemainingConnectionUses &&
+ (mUsingSpdyVersion == SpdyVersion::NONE)) {
+ --mRemainingConnectionUses;
+ }
+
+ switch (mState) {
+ case HttpConnectionState::SETTING_UP_TUNNEL: {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ // Distinguish SETTING_UP_TUNNEL for proxy or websocket via proxy
+ // See bug 1848013. Do not call HandleTunnelResponse for a tunnel
+ // connection created for WebSocket.
+ if (trans && trans->IsWebsocketUpgrade() &&
+ (trans->GetProxyConnectResponseCode() == 200 ||
+ (mForWebSocket && mInSpdyTunnel))) {
+ HandleWebSocketResponse(requestHead, responseHead, responseStatus);
+ } else {
+ HandleTunnelResponse(responseStatus, reset);
+ }
+ break;
+ }
+ default:
+ if (requestHead->HasHeader(nsHttp::Upgrade)) {
+ HandleWebSocketResponse(requestHead, responseHead, responseStatus);
+ } else if (responseStatus == 101) {
+ // We got an 101 but we are not asking of a WebSsocket?
+ Close(NS_ERROR_ABORT);
+ }
+ }
+
+ mLastHttpResponseVersion = responseHead->Version();
+
+ return NS_OK;
+}
+
+void nsHttpConnection::HandleTunnelResponse(uint16_t responseStatus,
+ bool* reset) {
+ LOG(("nsHttpConnection::HandleTunnelResponse()"));
+ MOZ_ASSERT(TunnelSetupInProgress());
+ MOZ_ASSERT(mProxyConnectStream);
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "SPDY NPN Complete while using proxy connect stream");
+ // If we're doing a proxy connect, we need to check whether or not
+ // it was successful. If so, we have to reset the transaction and step-up
+ // the socket connection if using SSL. Finally, we have to wake up the
+ // socket write request.
+
+ if (responseStatus == 200) {
+ ChangeState(HttpConnectionState::REQUEST);
+ }
+ mProxyConnectStream = nullptr;
+ bool isHttps = mTransaction ? mTransaction->ConnectionInfo()->EndToEndSSL()
+ : mConnInfo->EndToEndSSL();
+ bool onlyConnect = mTransactionCaps & NS_HTTP_CONNECT_ONLY;
+
+ mTransaction->OnProxyConnectComplete(responseStatus);
+ if (responseStatus == 200) {
+ LOG(("proxy CONNECT succeeded! endtoendssl=%d onlyconnect=%d\n", isHttps,
+ onlyConnect));
+ // If we're only connecting, we don't need to reset the transaction
+ // state. We need to upgrade the socket now without doing the actual
+ // http request.
+ if (!onlyConnect) {
+ *reset = true;
+ }
+ nsresult rv;
+ // CONNECT only flag doesn't do the tls setup. https here only
+ // ensures a proxy tunnel was used not that tls is setup.
+ if (isHttps) {
+ if (!onlyConnect) {
+ if (mConnInfo->UsingHttpsProxy()) {
+ LOG(("%p new TLSFilterTransaction %s %d\n", this, mConnInfo->Origin(),
+ mConnInfo->OriginPort()));
+ SetupSecondaryTLS();
+ }
+
+ rv = mTlsHandshaker->InitSSLParams(false, true);
+ LOG(("InitSSLParams [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
+ } else {
+ // We have an https protocol but the CONNECT only flag was
+ // specified. The consumer only wants a raw socket to the
+ // proxy. We have to mark this as complete to finish the
+ // transaction and be upgraded. OnSocketReadable() uses this
+ // to detect an inactive tunnel and blocks completion.
+ mTlsHandshaker->SetNPNComplete();
+ }
+ }
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ // XXX what if this fails -- need to handle this error
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed");
+ } else {
+ LOG(("proxy CONNECT failed! endtoendssl=%d onlyconnect=%d\n", isHttps,
+ onlyConnect));
+ mTransaction->SetProxyConnectFailed();
+ }
+}
+
+void nsHttpConnection::HandleWebSocketResponse(nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ uint16_t responseStatus) {
+ LOG(("nsHttpConnection::HandleWebSocketResponse()"));
+
+ // Don't use persistent connection for Upgrade unless there's an auth failure:
+ // some proxies expect to see auth response on persistent connection.
+ // Also allow persistent conn for h2, as we don't want to waste connections
+ // for multiplexed upgrades.
+ if (responseStatus != 401 && responseStatus != 407 && !mSpdySession) {
+ LOG(("HTTP Upgrade in play - disable keepalive for http/1.x\n"));
+ MarkAsDontReuse();
+ }
+
+ // the new Http2StreamWebSocket breaks wpt on
+ // h2 basic authentication 401, due to MakeSticky() work around
+ // so we DontReuse() in this circumstance
+ if (mInSpdyTunnel && (responseStatus == 401 || responseStatus == 407)) {
+ MarkAsDontReuse();
+ return;
+ }
+
+ if (responseStatus == 101) {
+ nsAutoCString upgradeReq;
+ bool hasUpgradeReq =
+ NS_SUCCEEDED(requestHead->GetHeader(nsHttp::Upgrade, upgradeReq));
+ nsAutoCString upgradeResp;
+ bool hasUpgradeResp =
+ NS_SUCCEEDED(responseHead->GetHeader(nsHttp::Upgrade, upgradeResp));
+ if (!hasUpgradeReq || !hasUpgradeResp ||
+ !nsHttp::FindToken(upgradeResp.get(), upgradeReq.get(),
+ HTTP_HEADER_VALUE_SEPS)) {
+ LOG(("HTTP 101 Upgrade header mismatch req = %s, resp = %s\n",
+ upgradeReq.get(),
+ !upgradeResp.IsEmpty() ? upgradeResp.get()
+ : "RESPONSE's nsHttp::Upgrade is empty"));
+ Close(NS_ERROR_ABORT);
+ } else {
+ LOG(("HTTP Upgrade Response to %s\n", upgradeResp.get()));
+ }
+ }
+}
+
+bool nsHttpConnection::IsReused() {
+ if (mIsReused) return true;
+ if (!mConsiderReusedAfterInterval) return false;
+
+ // ReusedAfter allows a socket to be consider reused only after a certain
+ // interval of time has passed
+ return (PR_IntervalNow() - mConsiderReusedAfterEpoch) >=
+ mConsiderReusedAfterInterval;
+}
+
+void nsHttpConnection::SetIsReusedAfter(uint32_t afterMilliseconds) {
+ mConsiderReusedAfterEpoch = PR_IntervalNow();
+ mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds);
+}
+
+nsresult nsHttpConnection::TakeTransport(nsISocketTransport** aTransport,
+ nsIAsyncInputStream** aInputStream,
+ nsIAsyncOutputStream** aOutputStream) {
+ if (mUsingSpdyVersion != SpdyVersion::NONE) return NS_ERROR_FAILURE;
+ if (mTransaction && !mTransaction->IsDone()) return NS_ERROR_IN_PROGRESS;
+ if (!(mSocketTransport && mSocketIn && mSocketOut)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mInputOverflow) mSocketIn = mInputOverflow.forget();
+
+ // Change TCP Keepalive frequency to long-lived if currently short-lived.
+ if (mTCPKeepaliveConfig == kTCPKeepaliveShortLivedConfig) {
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ nsresult rv = StartLongLivedTCPKeepalives();
+ LOG(
+ ("nsHttpConnection::TakeTransport [%p] calling "
+ "StartLongLivedTCPKeepalives",
+ this));
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::TakeTransport [%p] "
+ "StartLongLivedTCPKeepalives failed rv[0x%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+ }
+ }
+
+ if (mHasTLSTransportLayer) {
+ RefPtr<TLSTransportLayer> tlsTransportLayer =
+ do_QueryObject(mSocketTransport);
+ if (tlsTransportLayer) {
+ // This transport layer is no longer owned by this connection.
+ tlsTransportLayer->ReleaseOwner();
+ }
+ }
+
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+
+ mSocketTransport.forget(aTransport);
+ mSocketIn.forget(aInputStream);
+ mSocketOut.forget(aOutputStream);
+
+ return NS_OK;
+}
+
+uint32_t nsHttpConnection::ReadTimeoutTick(PRIntervalTime now) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // make sure timer didn't tick before Activate()
+ if (!mTransaction) return UINT32_MAX;
+
+ // Spdy implements some timeout handling using the SPDY ping frame.
+ if (mSpdySession) {
+ return mSpdySession->ReadTimeoutTick(now);
+ }
+
+ uint32_t nextTickAfter = UINT32_MAX;
+ // Timeout if the response is taking too long to arrive.
+ if (mResponseTimeoutEnabled) {
+ NS_WARNING_ASSERTION(
+ gHttpHandler->ResponseTimeoutEnabled(),
+ "Timing out a response, but response timeout is disabled!");
+
+ PRIntervalTime initialResponseDelta = now - mLastWriteTime;
+
+ if (initialResponseDelta > mTransaction->ResponseTimeout()) {
+ LOG(("canceling transaction: no response for %ums: timeout is %dms\n",
+ PR_IntervalToMilliseconds(initialResponseDelta),
+ PR_IntervalToMilliseconds(mTransaction->ResponseTimeout())));
+
+ mResponseTimeoutEnabled = false;
+ SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
+ // This will also close the connection
+ CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+ }
+ nextTickAfter = PR_IntervalToSeconds(mTransaction->ResponseTimeout()) -
+ PR_IntervalToSeconds(initialResponseDelta);
+ nextTickAfter = std::max(nextTickAfter, 1U);
+ }
+
+ if (!mTlsHandshaker->NPNComplete()) {
+ // We can reuse mLastWriteTime here, because it is set when the
+ // connection is activated and only change when a transaction
+ // succesfullu write to the socket and this can only happen after
+ // the TLS handshake is done.
+ PRIntervalTime initialTLSDelta = now - mLastWriteTime;
+ if (initialTLSDelta >
+ PR_MillisecondsToInterval(gHttpHandler->TLSHandshakeTimeout())) {
+ LOG(
+ ("canceling transaction: tls handshake takes too long: tls handshake "
+ "last %ums, timeout is %dms.",
+ PR_IntervalToMilliseconds(initialTLSDelta),
+ gHttpHandler->TLSHandshakeTimeout()));
+
+ // This will also close the connection
+ SetCloseReason(ConnectionCloseReason::TLS_TIMEOUT);
+ CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+ }
+ }
+
+ return nextTickAfter;
+}
+
+void nsHttpConnection::UpdateTCPKeepalive(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(aTimer);
+ MOZ_ASSERT(aClosure);
+
+ nsHttpConnection* self = static_cast<nsHttpConnection*>(aClosure);
+
+ if (NS_WARN_IF(self->mUsingSpdyVersion != SpdyVersion::NONE)) {
+ return;
+ }
+
+ // Do not reduce keepalive probe frequency for idle connections.
+ if (self->mIdleMonitoring) {
+ return;
+ }
+
+ nsresult rv = self->StartLongLivedTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::UpdateTCPKeepalive [%p] "
+ "StartLongLivedTCPKeepalives failed rv[0x%" PRIx32 "]",
+ self, static_cast<uint32_t>(rv)));
+ }
+}
+
+void nsHttpConnection::GetTLSSocketControl(
+ nsITLSSocketControl** tlsSocketControl) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnection::GetTLSSocketControl trans=%p socket=%p\n",
+ mTransaction.get(), mSocketTransport.get()));
+
+ *tlsSocketControl = nullptr;
+
+ if (mTransaction && NS_SUCCEEDED(mTransaction->GetTransactionTLSSocketControl(
+ tlsSocketControl))) {
+ return;
+ }
+
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetTlsSocketControl(tlsSocketControl))) {
+ return;
+ }
+}
+
+nsresult nsHttpConnection::PushBack(const char* data, uint32_t length) {
+ LOG(("nsHttpConnection::PushBack [this=%p, length=%d]\n", this, length));
+
+ if (mInputOverflow) {
+ NS_ERROR("nsHttpConnection::PushBack only one buffer supported");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mInputOverflow = new nsPreloadedStream(mSocketIn, data, length);
+ return NS_OK;
+}
+
+class HttpConnectionForceIO : public Runnable {
+ public:
+ HttpConnectionForceIO(nsHttpConnection* aConn, bool doRecv)
+ : Runnable("net::HttpConnectionForceIO"), mConn(aConn), mDoRecv(doRecv) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mDoRecv) {
+ if (!mConn->mSocketIn) return NS_OK;
+ return mConn->OnInputStreamReady(mConn->mSocketIn);
+ }
+
+ MOZ_ASSERT(mConn->mForceSendPending);
+ mConn->mForceSendPending = false;
+
+ if (!mConn->mSocketOut) {
+ return NS_OK;
+ }
+ return mConn->OnOutputStreamReady(mConn->mSocketOut);
+ }
+
+ private:
+ RefPtr<nsHttpConnection> mConn;
+ bool mDoRecv;
+};
+
+nsresult nsHttpConnection::ResumeSend() {
+ LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mSocketOut) {
+ return mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("no socket output stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult nsHttpConnection::ResumeRecv() {
+ LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // the mLastReadTime timestamp is used for finding slowish readers
+ // and can be pretty sensitive. For that reason we actually reset it
+ // when we ask to read (resume recv()) so that when we get called back
+ // with actual read data in OnSocketReadable() we are only measuring
+ // the latency between those two acts and not all the processing that
+ // may get done before the ResumeRecv() call
+ mLastReadTime = PR_IntervalNow();
+
+ if (mSocketIn) {
+ if (mHasTLSTransportLayer) {
+ RefPtr<TLSTransportLayer> tlsTransportLayer =
+ do_QueryObject(mSocketTransport);
+ if (tlsTransportLayer) {
+ bool hasDataToRecv = tlsTransportLayer->HasDataToRecv();
+ if (hasDataToRecv && NS_SUCCEEDED(ForceRecv())) {
+ return NS_OK;
+ }
+ Unused << mSocketIn->AsyncWait(this, 0, 0, nullptr);
+ // We have to return an error here to let the underlying layer know this
+ // connection doesn't read any data.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+ return mSocketIn->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("no socket input stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsHttpConnection::ForceSendIO(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpConnection* self = static_cast<nsHttpConnection*>(aClosure);
+ MOZ_ASSERT(aTimer == self->mForceSendTimer);
+ self->mForceSendTimer = nullptr;
+ NS_DispatchToCurrentThread(new HttpConnectionForceIO(self, false));
+}
+
+nsresult nsHttpConnection::MaybeForceSendIO() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // due to bug 1213084 sometimes real I/O events do not get serviced when
+ // NSPR derived I/O events are ready and this can cause a deadlock with
+ // https over https proxying. Normally we would expect the write callback to
+ // be invoked before this timer goes off, but set it at the old windows
+ // tick interval (kForceDelay) as a backup for those circumstances.
+ static const uint32_t kForceDelay = 17; // ms
+
+ if (mForceSendPending) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(!mForceSendTimer);
+ mForceSendPending = true;
+ return NS_NewTimerWithFuncCallback(getter_AddRefs(mForceSendTimer),
+ nsHttpConnection::ForceSendIO, this,
+ kForceDelay, nsITimer::TYPE_ONE_SHOT,
+ "net::nsHttpConnection::MaybeForceSendIO");
+}
+
+// trigger an asynchronous read
+nsresult nsHttpConnection::ForceRecv() {
+ LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return NS_DispatchToCurrentThread(new HttpConnectionForceIO(this, true));
+}
+
+// trigger an asynchronous write
+nsresult nsHttpConnection::ForceSend() {
+ LOG(("nsHttpConnection::ForceSend [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return MaybeForceSendIO();
+}
+
+void nsHttpConnection::BeginIdleMonitoring() {
+ LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTransaction, "BeginIdleMonitoring() while active");
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "Idle monitoring of spdy not allowed");
+
+ LOG(("Entering Idle Monitoring Mode [this=%p]", this));
+ mIdleMonitoring = true;
+ if (mSocketIn) mSocketIn->AsyncWait(this, 0, 0, nullptr);
+}
+
+void nsHttpConnection::EndIdleMonitoring() {
+ LOG(("nsHttpConnection::EndIdleMonitoring [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTransaction, "EndIdleMonitoring() while active");
+
+ if (mIdleMonitoring) {
+ LOG(("Leaving Idle Monitoring Mode [this=%p]", this));
+ mIdleMonitoring = false;
+ if (mSocketIn) mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+}
+
+HttpVersion nsHttpConnection::Version() {
+ if (mUsingSpdyVersion != SpdyVersion::NONE) {
+ return HttpVersion::v2_0;
+ }
+ return mLastHttpResponseVersion;
+}
+
+PRIntervalTime nsHttpConnection::LastWriteTime() { return mLastWriteTime; }
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <private>
+//-----------------------------------------------------------------------------
+
+void nsHttpConnection::CloseTransaction(nsAHttpTransaction* trans,
+ nsresult reason, bool aIsShutdown) {
+ LOG(("nsHttpConnection::CloseTransaction[this=%p trans=%p reason=%" PRIx32
+ "]\n",
+ this, trans, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mCurrentBytesRead > mMaxBytesRead) mMaxBytesRead = mCurrentBytesRead;
+
+ // mask this error code because its not a real error.
+ if (reason == NS_BASE_STREAM_CLOSED) reason = NS_OK;
+
+ if (mUsingSpdyVersion != SpdyVersion::NONE) {
+ DontReuse();
+ // if !mSpdySession then mUsingSpdyVersion must be false for canreuse()
+ mSpdySession->SetCleanShutdown(aIsShutdown);
+ mUsingSpdyVersion = SpdyVersion::NONE;
+ mSpdySession = nullptr;
+ }
+
+ if (mTransaction) {
+ LOG((" closing associated mTransaction"));
+ mHttp1xTransactionCount += mTransaction->Http1xTransactionCount();
+
+ mTransaction->Close(reason);
+ mTransaction = nullptr;
+ }
+
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ mCallbacks = nullptr;
+ }
+
+ if (NS_FAILED(reason) && (reason != NS_BINDING_RETARGETED)) {
+ Close(reason, aIsShutdown);
+ }
+
+ // flag the connection as reused here for convenience sake. certainly
+ // it might be going away instead ;-)
+ mIsReused = true;
+}
+
+bool nsHttpConnection::CheckCanWrite0RTTData() {
+ MOZ_ASSERT(mTlsHandshaker->EarlyDataAvailable());
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (!tlsSocketControl) {
+ return false;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ if (NS_FAILED(
+ tlsSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)))) {
+ return false;
+ }
+ if (!securityInfo) {
+ return false;
+ }
+ nsAutoCString negotiatedNPN;
+ // If the following code fails means that the handshake is not done
+ // yet, so continue writing 0RTT data.
+ nsresult rv = securityInfo->GetNegotiatedNPN(negotiatedNPN);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ bool earlyDataAccepted = false;
+ rv = tlsSocketControl->GetEarlyDataAccepted(&earlyDataAccepted);
+ // If 0RTT data is accepted we can continue writing data,
+ // if it is reject stop writing more data.
+ return NS_SUCCEEDED(rv) && earlyDataAccepted;
+}
+
+nsresult nsHttpConnection::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG(("nsHttpConnection::OnReadSegment [this=%p]\n", this));
+ if (count == 0) {
+ // some ReadSegments implementations will erroneously call the writer
+ // to consume 0 bytes worth of data. we must protect against this case
+ // or else we'd end up closing the socket prematurely.
+ NS_ERROR("bad ReadSegments implementation");
+ return NS_ERROR_FAILURE; // stop iterating
+ }
+
+ // If we are waiting for 0RTT Response, check maybe nss has finished
+ // handshake already.
+ // IsAlive() calls drive the handshake and that may cause nss and necko
+ // to be out of sync.
+ if (mTlsHandshaker->EarlyDataAvailable() && !CheckCanWrite0RTTData()) {
+ MOZ_DIAGNOSTIC_ASSERT(mTlsHandshaker->TlsHandshakeComplitionPending());
+ LOG(
+ ("nsHttpConnection::OnReadSegment Do not write any data, wait"
+ " for EnsureNPNComplete to be called [this=%p]",
+ this));
+ *countRead = 0;
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv = mSocketOut->Write(buf, count, countRead);
+ if (NS_FAILED(rv)) {
+ mSocketOutCondition = rv;
+ } else if (*countRead == 0) {
+ mSocketOutCondition = NS_BASE_STREAM_CLOSED;
+ } else {
+ mLastWriteTime = PR_IntervalNow();
+ mSocketOutCondition = NS_OK; // reset condition
+ if (!TunnelSetupInProgress()) {
+ mTotalBytesWritten += *countRead;
+ mExperienceState |= ConnectionExperienceState::First_Request_Sent;
+ }
+ }
+
+ return mSocketOutCondition;
+}
+
+nsresult nsHttpConnection::OnSocketWritable() {
+ LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n", this,
+ mConnInfo->Origin()));
+
+ nsresult rv;
+ uint32_t transactionBytes;
+ bool again = true;
+
+ // Prevent STS thread from being blocked by single OnOutputStreamReady
+ // callback.
+ const uint32_t maxWriteAttempts = 128;
+ uint32_t writeAttempts = 0;
+
+ if (mTransactionCaps & NS_HTTP_CONNECT_ONLY) {
+ if (!mConnInfo->UsingConnect()) {
+ // A CONNECT has been requested for this connection but will never
+ // be performed. This should never happen.
+ MOZ_ASSERT(false, "proxy connect will never happen");
+ LOG(("return failure because proxy connect will never happen\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mState == HttpConnectionState::REQUEST) {
+ // Don't need to check this each write attempt since it is only
+ // updated after OnSocketWritable completes.
+ // We've already done primary tls (if needed) and sent our CONNECT.
+ // If we're doing a CONNECT only request there's no need to write
+ // the http transaction or do the SSL handshake here.
+ LOG(("return ok because proxy connect successful\n"));
+ return NS_OK;
+ }
+ }
+
+ do {
+ ++writeAttempts;
+ rv = mSocketOutCondition = NS_OK;
+ transactionBytes = 0;
+
+ switch (mState) {
+ case HttpConnectionState::SETTING_UP_TUNNEL:
+ if (mConnInfo->UsingHttpsProxy() &&
+ !mTlsHandshaker->EnsureNPNComplete()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mTlsHandshaker->EarlyDataAvailable());
+ mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ rv = SendConnectRequest(this, &transactionBytes);
+ }
+ break;
+ default: {
+ // The SSL handshake must be completed before the
+ // transaction->readsegments() processing can proceed because we need to
+ // know how to format the request differently for http/1, http/2, spdy,
+ // etc.. and that is negotiated with NPN/ALPN in the SSL handshake.
+ if (!mTlsHandshaker->EnsureNPNComplete() &&
+ (!mTlsHandshaker->EarlyDataUsed() ||
+ mTlsHandshaker->TlsHandshakeComplitionPending())) {
+ // The handshake is not done and we cannot write 0RTT data or nss has
+ // already finished 0RTT data.
+ mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+ } else if (!mTransaction) {
+ rv = NS_ERROR_FAILURE;
+ LOG((" No Transaction In OnSocketWritable\n"));
+ } else if (NS_SUCCEEDED(rv)) {
+ // for non spdy sessions let the connection manager know
+ if (!mReportedSpdy && mTlsHandshaker->NPNComplete()) {
+ mReportedSpdy = true;
+ MOZ_ASSERT(!mEverUsedSpdy);
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false, false);
+ }
+
+ LOG((" writing transaction request stream\n"));
+ rv = mTransaction->ReadSegmentsAgain(this,
+ nsIOService::gDefaultSegmentSize,
+ &transactionBytes, &again);
+ if (mTlsHandshaker->EarlyDataUsed()) {
+ mContentBytesWritten0RTT += transactionBytes;
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ // If an error happens while writting 0RTT data, restart
+ // the transactiions without 0RTT.
+ mTlsHandshaker->FinishNPNSetup(false, true);
+ }
+ } else {
+ mContentBytesWritten += transactionBytes;
+ }
+ }
+ }
+ }
+
+ LOG(
+ ("nsHttpConnection::OnSocketWritable %p "
+ "ReadSegments returned [rv=%" PRIx32 " read=%u "
+ "sock-cond=%" PRIx32 " again=%d]\n",
+ this, static_cast<uint32_t>(rv), transactionBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ if (!mTlsHandshaker->EarlyDataCanNotBeUsed()) {
+ // continue writing
+ // We are not going to poll for write if the handshake is in progress,
+ // but early data cannot be used.
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ }
+ } else {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ rv = NS_OK;
+
+ if (mTransaction) { // in case the ReadSegments stack called
+ // CloseTransaction()
+ //
+ // at this point we've written out the entire transaction, and now we
+ // must wait for the server's response. we manufacture a status message
+ // here to reflect the fact that we are waiting. this message will be
+ // trumped (overwritten) if the server responds quickly.
+ //
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR, 0);
+
+ rv = ResumeRecv(); // start reading
+ }
+ // When Spdy tunnel is used we need to explicitly set when a request is
+ // done.
+ if ((mState != HttpConnectionState::SETTING_UP_TUNNEL) && !mSpdySession) {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ // needed for websocket over h2 (direct)
+ if (!trans || !trans->IsWebsocketUpgrade()) {
+ mRequestDone = true;
+ }
+ }
+ again = false;
+ } else if (writeAttempts >= maxWriteAttempts) {
+ LOG((" yield for other transactions\n"));
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
+ again = false;
+ }
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+nsresult nsHttpConnection::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ if (count == 0) {
+ // some WriteSegments implementations will erroneously call the reader
+ // to provide 0 bytes worth of data. we must protect against this case
+ // or else we'd end up closing the socket prematurely.
+ NS_ERROR("bad WriteSegments implementation");
+ return NS_ERROR_FAILURE; // stop iterating
+ }
+
+ if (ChaosMode::isActive(ChaosFeature::IOAmounts) &&
+ ChaosMode::randomUint32LessThan(2)) {
+ // read 1...count bytes
+ count = ChaosMode::randomUint32LessThan(count) + 1;
+ }
+
+ nsresult rv = mSocketIn->Read(buf, count, countWritten);
+ if (NS_FAILED(rv)) {
+ mSocketInCondition = rv;
+ } else if (*countWritten == 0) {
+ mSocketInCondition = NS_BASE_STREAM_CLOSED;
+ } else {
+ mSocketInCondition = NS_OK; // reset condition
+ mExperienceState |= ConnectionExperienceState::First_Response_Received;
+ }
+
+ return mSocketInCondition;
+}
+
+nsresult nsHttpConnection::OnSocketReadable() {
+ LOG(("nsHttpConnection::OnSocketReadable [this=%p]\n", this));
+
+ PRIntervalTime now = PR_IntervalNow();
+ PRIntervalTime delta = now - mLastReadTime;
+
+ // Reset mResponseTimeoutEnabled to stop response timeout checks.
+ mResponseTimeoutEnabled = false;
+
+ if ((mTransactionCaps & NS_HTTP_CONNECT_ONLY) && !mConnInfo->UsingConnect()) {
+ // A CONNECT has been requested for this connection but will never
+ // be performed. This should never happen.
+ MOZ_ASSERT(false, "proxy connect will never happen");
+ LOG(("return failure because proxy connect will never happen\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mKeepAliveMask && (delta >= mMaxHangTime)) {
+ LOG(("max hang time exceeded!\n"));
+ // give the handler a chance to create a new persistent connection to
+ // this host if we've been busy for too long.
+ mKeepAliveMask = false;
+ Unused << gHttpHandler->ProcessPendingQ(mConnInfo);
+ }
+
+ // Reduce the estimate of the time since last read by up to 1 RTT to
+ // accommodate exhausted sender TCP congestion windows or minor I/O delays.
+ mLastReadTime = now;
+
+ nsresult rv = NS_OK;
+ uint32_t n;
+ bool again = true;
+
+ do {
+ if (!TunnelSetupInProgress() && !mTlsHandshaker->EnsureNPNComplete()) {
+ // Unless we are setting up a tunnel via CONNECT, prevent reading
+ // from the socket until the results of NPN
+ // negotiation are known (which is determined from the write path).
+ // If the server speaks SPDY it is likely the readable data here is
+ // a spdy settings frame and without NPN it would be misinterpreted
+ // as HTTP/*
+
+ LOG(
+ ("nsHttpConnection::OnSocketReadable %p return due to inactive "
+ "tunnel setup but incomplete NPN state\n",
+ this));
+ if (mTlsHandshaker->EarlyDataAvailable() || mHasTLSTransportLayer) {
+ rv = ResumeRecv();
+ }
+ break;
+ }
+
+ mSocketInCondition = NS_OK;
+ if (!mTransaction) {
+ rv = NS_ERROR_FAILURE;
+ LOG((" No Transaction In OnSocketWritable\n"));
+ } else {
+ rv = mTransaction->WriteSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &n, &again);
+ }
+ LOG(("nsHttpConnection::OnSocketReadable %p trans->ws rv=%" PRIx32
+ " n=%d socketin=%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv), n,
+ static_cast<uint32_t>(mSocketInCondition)));
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else {
+ mCurrentBytesRead += n;
+ mTotalBytesRead += n;
+ if (NS_FAILED(mSocketInCondition)) {
+ // continue waiting for the socket if necessary...
+ if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = ResumeRecv();
+ } else {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+void nsHttpConnection::SetupSecondaryTLS() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mHasTLSTransportLayer);
+ LOG(("nsHttpConnection %p SetupSecondaryTLS %s %d\n", this,
+ mConnInfo->Origin(), mConnInfo->OriginPort()));
+
+ nsHttpConnectionInfo* ci = nullptr;
+ if (mTransaction) {
+ ci = mTransaction->ConnectionInfo();
+ }
+ if (!ci) {
+ ci = mConnInfo;
+ }
+ MOZ_ASSERT(ci);
+
+ RefPtr<TLSTransportLayer> transportLayer =
+ new TLSTransportLayer(mSocketTransport, mSocketIn, mSocketOut, this);
+ if (transportLayer->Init(ci->Origin(), ci->OriginPort())) {
+ mSocketIn = transportLayer->GetInputStreamWrapper();
+ mSocketOut = transportLayer->GetOutputStreamWrapper();
+ mSocketTransport = transportLayer;
+ mHasTLSTransportLayer = true;
+ LOG(("Create mTLSTransportLayer %p", this));
+ }
+}
+
+void nsHttpConnection::SetInSpdyTunnel() {
+ mInSpdyTunnel = true;
+ mForcePlainText = true;
+}
+
+// static
+nsresult nsHttpConnection::MakeConnectString(nsAHttpTransaction* trans,
+ nsHttpRequestHead* request,
+ nsACString& result, bool h2ws,
+ bool aShouldResistFingerprinting) {
+ result.Truncate();
+ if (!trans->ConnectionInfo()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DebugOnly<nsresult> rv{};
+
+ rv = nsHttpHandler::GenerateHostPort(
+ nsDependentCString(trans->ConnectionInfo()->Origin()),
+ trans->ConnectionInfo()->OriginPort(), result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // CONNECT host:port HTTP/1.1
+ request->SetMethod("CONNECT"_ns);
+ request->SetVersion(gHttpHandler->HttpVersion());
+ if (h2ws) {
+ // HTTP/2 websocket CONNECT forms need the full request URI
+ nsAutoCString requestURI;
+ trans->RequestHead()->RequestURI(requestURI);
+ request->SetRequestURI(requestURI);
+
+ request->SetHTTPS(trans->RequestHead()->IsHTTPS());
+
+ nsAutoCString val;
+ if (NS_SUCCEEDED(trans->RequestHead()->GetHeader(
+ nsHttp::Sec_WebSocket_Extensions, val))) {
+ rv = request->SetHeader(nsHttp::Sec_WebSocket_Extensions, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if (NS_SUCCEEDED(trans->RequestHead()->GetHeader(
+ nsHttp::Sec_WebSocket_Protocol, val))) {
+ rv = request->SetHeader(nsHttp::Sec_WebSocket_Protocol, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if (NS_SUCCEEDED(trans->RequestHead()->GetHeader(
+ nsHttp::Sec_WebSocket_Version, val))) {
+ rv = request->SetHeader(nsHttp::Sec_WebSocket_Version, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ } else {
+ request->SetRequestURI(result);
+ }
+ rv = request->SetHeader(nsHttp::User_Agent,
+ gHttpHandler->UserAgent(aShouldResistFingerprinting));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // a CONNECT is always persistent
+ rv = request->SetHeader(nsHttp::Proxy_Connection, "keep-alive"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = request->SetHeader(nsHttp::Connection, "keep-alive"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // all HTTP/1.1 requests must include a Host header (even though it
+ // may seem redundant in this case; see bug 82388).
+ rv = request->SetHeader(nsHttp::Host, result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString val;
+ if (NS_SUCCEEDED(
+ trans->RequestHead()->GetHeader(nsHttp::Proxy_Authorization, val))) {
+ // we don't know for sure if this authorization is intended for the
+ // SSL proxy, so we add it just in case.
+ rv = request->SetHeader(nsHttp::Proxy_Authorization, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if ((trans->Caps() & NS_HTTP_CONNECT_ONLY) &&
+ NS_SUCCEEDED(trans->RequestHead()->GetHeader(nsHttp::Upgrade, val))) {
+ // rfc7639 proposes using the ALPN header to indicate the protocol used
+ // in CONNECT when not used for TLS. The protocol is stored in Upgrade.
+ // We have to copy this header here since a new HEAD request is created
+ // for the CONNECT.
+ rv = request->SetHeader("ALPN"_ns, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ result.Truncate();
+ request->Flatten(result, false);
+
+ if (LOG1_ENABLED()) {
+ LOG(("nsHttpConnection::MakeConnectString for transaction=%p h2ws=%d[",
+ trans->QueryHttpTransaction(), h2ws));
+ LogHeaders(result.BeginReading());
+ LOG(("]"));
+ }
+
+ result.AppendLiteral("\r\n");
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::StartShortLivedTCPKeepalives() {
+ if (mUsingSpdyVersion != SpdyVersion::NONE) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = NS_OK;
+ int32_t idleTimeS = -1;
+ int32_t retryIntervalS = -1;
+ if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
+ // Set the idle time.
+ idleTimeS = gHttpHandler->GetTCPKeepaliveShortLivedIdleTime();
+ LOG(
+ ("nsHttpConnection::StartShortLivedTCPKeepalives[%p] "
+ "idle time[%ds].",
+ this, idleTimeS));
+
+ retryIntervalS = std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
+ rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mSocketTransport->SetKeepaliveEnabled(true);
+ mTCPKeepaliveConfig = kTCPKeepaliveShortLivedConfig;
+ } else {
+ rv = mSocketTransport->SetKeepaliveEnabled(false);
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Start a timer to move to long-lived keepalive config.
+ if (!mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer = NS_NewTimer();
+ }
+
+ if (mTCPKeepaliveTransitionTimer) {
+ int32_t time = gHttpHandler->GetTCPKeepaliveShortLivedTime();
+
+ // Adjust |time| to ensure a full set of keepalive probes can be sent
+ // at the end of the short-lived phase.
+ if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
+ if (NS_WARN_IF(!gSocketTransportService)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ int32_t probeCount = -1;
+ rv = gSocketTransportService->GetKeepaliveProbeCount(&probeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (NS_WARN_IF(probeCount <= 0)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Add time for final keepalive probes, and 2 seconds for a buffer.
+ time += ((probeCount)*retryIntervalS) - (time % idleTimeS) + 2;
+ }
+ mTCPKeepaliveTransitionTimer->InitWithNamedFuncCallback(
+ nsHttpConnection::UpdateTCPKeepalive, this, (uint32_t)time * 1000,
+ nsITimer::TYPE_ONE_SHOT,
+ "net::nsHttpConnection::StartShortLivedTCPKeepalives");
+ } else {
+ NS_WARNING(
+ "nsHttpConnection::StartShortLivedTCPKeepalives failed to "
+ "create timer.");
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::StartLongLivedTCPKeepalives() {
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "Don't use TCP Keepalive with SPDY!");
+ if (NS_WARN_IF(mUsingSpdyVersion != SpdyVersion::NONE)) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = NS_OK;
+ if (gHttpHandler->TCPKeepaliveEnabledForLongLivedConns()) {
+ // Increase the idle time.
+ int32_t idleTimeS = gHttpHandler->GetTCPKeepaliveLongLivedIdleTime();
+ LOG(("nsHttpConnection::StartLongLivedTCPKeepalives[%p] idle time[%ds]",
+ this, idleTimeS));
+
+ int32_t retryIntervalS =
+ std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
+ rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Ensure keepalive is enabled, if current status is disabled.
+ if (mTCPKeepaliveConfig == kTCPKeepaliveDisabled) {
+ rv = mSocketTransport->SetKeepaliveEnabled(true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ mTCPKeepaliveConfig = kTCPKeepaliveLongLivedConfig;
+ } else {
+ rv = mSocketTransport->SetKeepaliveEnabled(false);
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::DisableTCPKeepalives() {
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LOG(("nsHttpConnection::DisableTCPKeepalives [%p]", this));
+ if (mTCPKeepaliveConfig != kTCPKeepaliveDisabled) {
+ nsresult rv = mSocketTransport->SetKeepaliveEnabled(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpConnection)
+NS_IMPL_RELEASE(nsHttpConnection)
+
+NS_INTERFACE_MAP_BEGIN(nsHttpConnection)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(HttpConnectionBase)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpConnection)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket transport thread
+NS_IMETHODIMP
+nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream* in) {
+ MOZ_ASSERT(in == mSocketIn, "unexpected stream");
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mIdleMonitoring) {
+ MOZ_ASSERT(!mTransaction, "Idle Input Event While Active");
+
+ // The only read event that is protocol compliant for an idle connection
+ // is an EOF, which we check for with CanReuse(). If the data is
+ // something else then just ignore it and suspend checking for EOF -
+ // our normal timers or protocol stack are the place to deal with
+ // any exception logic.
+
+ if (!CanReuse()) {
+ LOG(("Server initiated close of idle conn %p\n", this));
+ Unused << gHttpHandler->ConnMgr()->CloseIdleConnection(this);
+ return NS_OK;
+ }
+
+ LOG(("Input data on idle conn %p, but not closing yet\n", this));
+ return NS_OK;
+ }
+
+ // if the transaction was dropped...
+ if (!mTransaction) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketReadable();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ CloseTransaction(mTransaction, rv);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIOutputStreamCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(out == mSocketOut, "unexpected socket");
+ // if the transaction was dropped...
+ if (!mTransaction) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketWritable();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) CloseTransaction(mTransaction, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsITransportEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnection::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ if (mTransaction) mTransaction->OnTransportStatus(trans, status, progress);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+// not called on the socket transport thread
+NS_IMETHODIMP
+nsHttpConnection::GetInterface(const nsIID& iid, void** result) {
+ // NOTE: This function is only called on the UI thread via sync proxy from
+ // the socket transport thread. If that weren't the case, then we'd
+ // have to worry about the possibility of mTransaction going away
+ // part-way through this function call. See CloseTransaction.
+
+ // NOTE - there is a bug here, the call to getinterface is proxied off the
+ // nss thread, not the ui thread as the above comment says. So there is
+ // indeed a chance of mTransaction going away. bug 615342
+
+ MOZ_ASSERT(!OnSocketThread(), "on socket thread");
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ callbacks = mCallbacks;
+ }
+ if (callbacks) return callbacks->GetInterface(iid, result);
+ return NS_ERROR_NO_INTERFACE;
+}
+
+void nsHttpConnection::CheckForTraffic(bool check) {
+ if (check) {
+ LOG((" CheckForTraffic conn %p\n", this));
+ if (mSpdySession) {
+ if (PR_IntervalToMilliseconds(IdleTime()) >= 500) {
+ // Send a ping to verify it is still alive if it has been idle
+ // more than half a second, the network changed events are
+ // rate-limited to one per 1000 ms.
+ LOG((" SendPing\n"));
+ mSpdySession->SendPing();
+ } else {
+ LOG((" SendPing skipped due to network activity\n"));
+ }
+ } else {
+ // If not SPDY, Store snapshot amount of data right now
+ mTrafficCount = mTotalBytesWritten + mTotalBytesRead;
+ mTrafficStamp = true;
+ }
+ } else {
+ // mark it as not checked
+ mTrafficStamp = false;
+ }
+}
+
+void nsHttpConnection::SetEvent(nsresult aStatus) {
+ switch (aStatus) {
+ case NS_NET_STATUS_RESOLVING_HOST:
+ mBootstrappedTimings.domainLookupStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_RESOLVED_HOST:
+ mBootstrappedTimings.domainLookupEnd = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTING_TO:
+ mBootstrappedTimings.connectStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTED_TO: {
+ TimeStamp tnow = TimeStamp::Now();
+ mBootstrappedTimings.tcpConnectEnd = tnow;
+ mBootstrappedTimings.connectEnd = tnow;
+ mBootstrappedTimings.secureConnectionStart = tnow;
+ break;
+ }
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ mBootstrappedTimings.secureConnectionStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ break;
+ default:
+ break;
+ }
+}
+
+bool nsHttpConnection::NoClientCertAuth() const {
+ if (!mSocketTransport) {
+ return false;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ mSocketTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ if (!tlsSocketControl) {
+ return false;
+ }
+
+ return !tlsSocketControl->GetClientCertSent();
+}
+
+WebSocketSupport nsHttpConnection::GetWebSocketSupport() {
+ LOG3(("nsHttpConnection::GetWebSocketSupport"));
+ if (!UsingSpdy()) {
+ return WebSocketSupport::SUPPORTED;
+ }
+ LOG3(("nsHttpConnection::GetWebSocketSupport checking spdy session"));
+ if (mSpdySession) {
+ return mSpdySession->GetWebSocketSupport();
+ }
+
+ return WebSocketSupport::NO_SUPPORT;
+}
+
+bool nsHttpConnection::IsProxyConnectInProgress() {
+ return mState == SETTING_UP_TUNNEL;
+}
+
+bool nsHttpConnection::LastTransactionExpectedNoContent() {
+ return mLastTransactionExpectedNoContent;
+}
+
+void nsHttpConnection::SetLastTransactionExpectedNoContent(bool val) {
+ mLastTransactionExpectedNoContent = val;
+}
+
+bool nsHttpConnection::IsPersistent() { return IsKeepAlive() && !mDontReuse; }
+
+nsAHttpTransaction* nsHttpConnection::Transaction() { return mTransaction; }
+
+nsresult nsHttpConnection::GetSelfAddr(NetAddr* addr) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetSelfAddr(addr);
+}
+
+nsresult nsHttpConnection::GetPeerAddr(NetAddr* addr) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetPeerAddr(addr);
+}
+
+bool nsHttpConnection::ResolvedByTRR() {
+ bool val = false;
+ if (mSocketTransport) {
+ mSocketTransport->ResolvedByTRR(&val);
+ }
+ return val;
+}
+
+nsIRequest::TRRMode nsHttpConnection::EffectiveTRRMode() {
+ nsIRequest::TRRMode mode = nsIRequest::TRR_DEFAULT_MODE;
+ if (mSocketTransport) {
+ mSocketTransport->GetEffectiveTRRMode(&mode);
+ }
+ return mode;
+}
+
+TRRSkippedReason nsHttpConnection::TRRSkipReason() {
+ TRRSkippedReason reason = nsITRRSkipReason::TRR_UNSET;
+ if (mSocketTransport) {
+ mSocketTransport->GetTrrSkipReason(&reason);
+ }
+ return reason;
+}
+
+bool nsHttpConnection::GetEchConfigUsed() {
+ bool val = false;
+ if (mSocketTransport) {
+ mSocketTransport->GetEchConfigUsed(&val);
+ }
+ return val;
+}
+
+void nsHttpConnection::HandshakeDoneInternal() {
+ LOG(("nsHttpConnection::HandshakeDoneInternal [this=%p]\n", this));
+ if (mTlsHandshaker->NPNComplete()) {
+ return;
+ }
+
+ ChangeConnectionState(ConnectionState::TRANSFERING);
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (!tlsSocketControl) {
+ mTlsHandshaker->FinishNPNSetup(false, false);
+ return;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ if (NS_FAILED(
+ tlsSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)))) {
+ mTlsHandshaker->FinishNPNSetup(false, false);
+ return;
+ }
+ if (!securityInfo) {
+ mTlsHandshaker->FinishNPNSetup(false, false);
+ return;
+ }
+
+ nsAutoCString negotiatedNPN;
+ DebugOnly<nsresult> rvDebug = securityInfo->GetNegotiatedNPN(negotiatedNPN);
+ MOZ_ASSERT(NS_SUCCEEDED(rvDebug));
+
+ bool earlyDataAccepted = false;
+ if (mTlsHandshaker->EarlyDataUsed()) {
+ // Check if early data has been accepted.
+ nsresult rvEarlyData =
+ tlsSocketControl->GetEarlyDataAccepted(&earlyDataAccepted);
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] - early data "
+ "that was sent during 0RTT %s been accepted [rv=%" PRIx32 "].",
+ this, earlyDataAccepted ? "has" : "has not",
+ static_cast<uint32_t>(rvEarlyData)));
+
+ if (NS_FAILED(rvEarlyData) ||
+ (mTransaction &&
+ NS_FAILED(mTransaction->Finish0RTT(
+ !earlyDataAccepted,
+ negotiatedNPN != mTlsHandshaker->EarlyNegotiatedALPN())))) {
+ LOG(
+ ("nsHttpConection::HandshakeDone [this=%p] closing transaction "
+ "%p",
+ this, mTransaction.get()));
+ if (mTransaction) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ }
+ mTlsHandshaker->FinishNPNSetup(false, true);
+ return;
+ }
+ if (mDid0RTTSpdy &&
+ (negotiatedNPN != mTlsHandshaker->EarlyNegotiatedALPN())) {
+ Reset0RttForSpdy();
+ }
+ }
+
+ if (mTlsHandshaker->EarlyDataAvailable() && !earlyDataAccepted) {
+ // When the early-data were used but not accepted, we need to start
+ // from the begining here and start writing the request again.
+ // The same is true if 0RTT was available but not used.
+ if (mSocketIn) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ Unused << ResumeSend();
+ }
+
+ int16_t tlsVersion;
+ tlsSocketControl->GetSSLVersionUsed(&tlsVersion);
+ mConnInfo->SetLessThanTls13(
+ (tlsVersion < nsITLSSocketControl::TLS_VERSION_1_3) &&
+ (tlsVersion != nsITLSSocketControl::SSL_VERSION_UNKNOWN));
+
+ mTlsHandshaker->EarlyDataTelemetry(tlsVersion, earlyDataAccepted,
+ mContentBytesWritten0RTT);
+ mTlsHandshaker->EarlyDataDone();
+
+ if (!earlyDataAccepted) {
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] early data not "
+ "accepted or early data were not used",
+ this));
+
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (negotiatedNPN.Equals(info->VersionString)) {
+ if (mTransaction) {
+ StartSpdy(tlsSocketControl, info->Version);
+ } else {
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] set "
+ "mContinueHandshakeDone",
+ this));
+ RefPtr<nsHttpConnection> self = this;
+ mContinueHandshakeDone = [self = RefPtr{this},
+ tlsSocketControl(tlsSocketControl),
+ info(info->Version)]() {
+ LOG(("nsHttpConnection do mContinueHandshakeDone [this=%p]",
+ self.get()));
+ self->StartSpdy(tlsSocketControl, info);
+ self->mTlsHandshaker->FinishNPNSetup(true, true);
+ };
+ return;
+ }
+ }
+ } else {
+ LOG(("nsHttpConnection::HandshakeDone [this=%p] - %" PRId64 " bytes "
+ "has been sent during 0RTT.",
+ this, mContentBytesWritten0RTT));
+ mContentBytesWritten = mContentBytesWritten0RTT;
+
+ if (mSpdySession) {
+ // We had already started 0RTT-spdy, now we need to fully set up
+ // spdy, since we know we're sticking with it.
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] - finishing "
+ "StartSpdy for 0rtt spdy session %p",
+ this, mSpdySession.get()));
+ StartSpdy(tlsSocketControl, mSpdySession->SpdyVersion());
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
+
+ mTlsHandshaker->FinishNPNSetup(true, true);
+ Unused << ResumeSend();
+}
+
+void nsHttpConnection::SetTunnelSetupDone() {
+ MOZ_ASSERT(mProxyConnectStream);
+ MOZ_ASSERT(mState == HttpConnectionState::SETTING_UP_TUNNEL);
+
+ ChangeState(HttpConnectionState::REQUEST);
+ mProxyConnectStream = nullptr;
+}
+
+nsresult nsHttpConnection::CheckTunnelIsNeeded() {
+ switch (mState) {
+ case HttpConnectionState::UNINITIALIZED: {
+ // This is is called first time. Check if we need a tunnel.
+ if (!mTransaction->ConnectionInfo()->UsingConnect()) {
+ ChangeState(HttpConnectionState::REQUEST);
+ return NS_OK;
+ }
+ ChangeState(HttpConnectionState::SETTING_UP_TUNNEL);
+ }
+ [[fallthrough]];
+ case HttpConnectionState::SETTING_UP_TUNNEL: {
+ // When a nsHttpConnection is in this state that means that an
+ // authentication was needed and we are resending a CONNECT
+ // request. This request will include authentication headers.
+ nsresult rv = SetupProxyConnectStream();
+ if (NS_FAILED(rv)) {
+ ChangeState(HttpConnectionState::UNINITIALIZED);
+ }
+ return rv;
+ }
+ case HttpConnectionState::REQUEST:
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::SetupProxyConnectStream() {
+ LOG(("nsHttpConnection::SetupStream\n"));
+ NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED);
+ MOZ_ASSERT(mState == HttpConnectionState::SETTING_UP_TUNNEL);
+
+ nsAutoCString buf;
+ nsHttpRequestHead request;
+ nsresult rv = MakeConnectString(mTransaction, &request, buf,
+ mForWebSocket && mInSpdyTunnel,
+ mTransactionCaps & NS_HTTP_USE_RFP);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_NewCStringInputStream(getter_AddRefs(mProxyConnectStream),
+ std::move(buf));
+ return rv;
+}
+
+nsresult nsHttpConnection::ReadFromStream(nsIInputStream* input, void* closure,
+ const char* buf, uint32_t offset,
+ uint32_t count, uint32_t* countRead) {
+ // thunk for nsIInputStream instance
+ nsHttpConnection* conn = (nsHttpConnection*)closure;
+ return conn->OnReadSegment(buf, count, countRead);
+}
+
+nsresult nsHttpConnection::SendConnectRequest(void* closure,
+ uint32_t* transactionBytes) {
+ LOG((" writing CONNECT request stream\n"));
+ return mProxyConnectStream->ReadSegments(ReadFromStream, closure,
+ nsIOService::gDefaultSegmentSize,
+ transactionBytes);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h
new file mode 100644
index 0000000000..6f00591a2e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -0,0 +1,388 @@
+/* -*- 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 nsHttpConnection_h__
+#define nsHttpConnection_h__
+
+#include <functional>
+#include "HttpConnectionBase.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+#include "TlsHandshaker.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+#include "nsITimer.h"
+#include "nsITlsHandshakeListener.h"
+
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+
+// 1dcc863e-db90-4652-a1fe-13fea0b54e46
+#define NS_HTTPCONNECTION_IID \
+ { \
+ 0x1dcc863e, 0xdb90, 0x4652, { \
+ 0xa1, 0xfe, 0x13, 0xfe, 0xa0, 0xb5, 0x4e, 0x46 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection - represents a connection to a HTTP server (or proxy)
+//
+// NOTE: this objects lives on the socket thread only. it should not be
+// accessed from any other thread.
+//-----------------------------------------------------------------------------
+
+class nsHttpConnection final : public HttpConnectionBase,
+ public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor {
+ private:
+ virtual ~nsHttpConnection();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCONNECTION_IID)
+ NS_DECL_HTTPCONNECTIONBASE
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsHttpConnection();
+
+ // Initialize the connection:
+ // info - specifies the connection parameters.
+ // maxHangTime - limits the amount of time this connection can spend on a
+ // single transaction before it should no longer be kept
+ // alive. a value of 0xffff indicates no limit.
+ [[nodiscard]] virtual nsresult Init(nsHttpConnectionInfo* info,
+ uint16_t maxHangTime, nsISocketTransport*,
+ nsIAsyncInputStream*,
+ nsIAsyncOutputStream*,
+ bool connectedTransport, nsresult status,
+ nsIInterfaceRequestor*, PRIntervalTime,
+ bool forWebSocket);
+
+ //-------------------------------------------------------------------------
+ // XXX document when these are ok to call
+
+ bool IsKeepAlive() {
+ return (mUsingSpdyVersion != SpdyVersion::NONE) ||
+ (mKeepAliveMask && mKeepAlive);
+ }
+
+ // Returns time in seconds for how long connection can be reused.
+ uint32_t TimeToLive();
+
+ bool NeedSpdyTunnel() {
+ return mConnInfo->UsingHttpsProxy() && !mHasTLSTransportLayer &&
+ mConnInfo->UsingConnect();
+ }
+
+ // A connection is forced into plaintext when it is intended to be used as a
+ // CONNECT tunnel but the setup fails. The plaintext only carries the CONNECT
+ // error.
+ void ForcePlainText() { mForcePlainText = true; }
+
+ bool IsUrgentStartPreferred() const {
+ return mUrgentStartPreferredKnown && mUrgentStartPreferred;
+ }
+ void SetUrgentStartPreferred(bool urgent);
+
+ void SetIsReusedAfter(uint32_t afterMilliseconds);
+
+ int64_t MaxBytesRead() { return mMaxBytesRead; }
+ HttpVersion GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
+
+ friend class HttpConnectionForceIO;
+ friend class TlsHandshaker;
+
+ // When a persistent connection is in the connection manager idle
+ // connection pool, the nsHttpConnection still reads errors and hangups
+ // on the socket so that it can be proactively released if the server
+ // initiates a termination. Only call on socket thread.
+ void BeginIdleMonitoring();
+ void EndIdleMonitoring();
+
+ bool UsingSpdy() override { return (mUsingSpdyVersion != SpdyVersion::NONE); }
+ SpdyVersion GetSpdyVersion() { return mUsingSpdyVersion; }
+ bool EverUsedSpdy() { return mEverUsedSpdy; }
+ bool UsingHttp3() override { return false; }
+
+ // true when connection SSL NPN phase is complete and we know
+ // authoritatively whether UsingSpdy() or not.
+ bool ReportedNPN() { return mReportedSpdy; }
+
+ // When the connection is active this is called up to once every 1 second
+ // return the interval (in seconds) that the connection next wants to
+ // have this invoked. It might happen sooner depending on the needs of
+ // other connections.
+ uint32_t ReadTimeoutTick(PRIntervalTime now);
+
+ // For Active and Idle connections, this will be called when
+ // mTCPKeepaliveTransitionTimer fires, to check if the TCP keepalive config
+ // should move from short-lived (fast-detect) to long-lived.
+ static void UpdateTCPKeepalive(nsITimer* aTimer, void* aClosure);
+
+ // When the connection is active this is called every second
+ void ReadTimeoutTick();
+
+ int64_t ContentBytesWritten() { return mContentBytesWritten; }
+
+ void SetupSecondaryTLS();
+ void SetInSpdyTunnel();
+
+ // Check active connections for traffic (or not). SPDY connections send a
+ // ping, ordinary HTTP connections get some time to get traffic to be
+ // considered alive.
+ void CheckForTraffic(bool check);
+
+ // NoTraffic() returns true if there's been no traffic on the (non-spdy)
+ // connection since CheckForTraffic() was called.
+ bool NoTraffic() {
+ return mTrafficStamp &&
+ (mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
+ }
+
+ // Return true when the socket this connection is using has not been
+ // authenticated using a client certificate. Before SSL negotiation
+ // has finished this returns false.
+ bool NoClientCertAuth() const override;
+
+ WebSocketSupport GetWebSocketSupport() override;
+
+ int64_t BytesWritten() override { return mTotalBytesWritten; }
+
+ nsISocketTransport* Transport() override { return mSocketTransport; }
+
+ nsresult GetSelfAddr(NetAddr* addr) override;
+ nsresult GetPeerAddr(NetAddr* addr) override;
+ bool ResolvedByTRR() override;
+ bool GetEchConfigUsed() override;
+ nsIRequest::TRRMode EffectiveTRRMode() override;
+ TRRSkippedReason TRRSkipReason() override;
+ bool IsForWebSocket() { return mForWebSocket; }
+
+ // The following functions are related to setting up a tunnel.
+ [[nodiscard]] static nsresult MakeConnectString(
+ nsAHttpTransaction* trans, nsHttpRequestHead* request, nsACString& result,
+ bool h2ws, bool aShouldResistFingerprinting);
+ [[nodiscard]] static nsresult ReadFromStream(nsIInputStream*, void*,
+ const char*, uint32_t, uint32_t,
+ uint32_t*);
+
+ nsresult CreateTunnelStream(nsAHttpTransaction* httpTransaction,
+ nsHttpConnection** aHttpConnection,
+ bool aIsWebSocket = false);
+
+ bool RequestDone() { return mRequestDone; }
+
+ private:
+ enum HttpConnectionState {
+ UNINITIALIZED,
+ SETTING_UP_TUNNEL,
+ REQUEST,
+ } mState{HttpConnectionState::UNINITIALIZED};
+ void ChangeState(HttpConnectionState newState);
+
+ // Tunnel retated functions:
+ bool TunnelSetupInProgress() { return mState == SETTING_UP_TUNNEL; }
+ void SetTunnelSetupDone();
+ nsresult CheckTunnelIsNeeded();
+ nsresult SetupProxyConnectStream();
+ nsresult SendConnectRequest(void* closure, uint32_t* transactionBytes);
+
+ void HandleTunnelResponse(uint16_t responseStatus, bool* reset);
+ void HandleWebSocketResponse(nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ uint16_t responseStatus);
+ void ResetTransaction(RefPtr<nsAHttpTransaction>&& trans);
+
+ // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
+ enum TCPKeepaliveConfig {
+ kTCPKeepaliveDisabled = 0,
+ kTCPKeepaliveShortLivedConfig,
+ kTCPKeepaliveLongLivedConfig
+ };
+
+ [[nodiscard]] nsresult OnTransactionDone(nsresult reason);
+ [[nodiscard]] nsresult OnSocketWritable();
+ [[nodiscard]] nsresult OnSocketReadable();
+
+ PRIntervalTime IdleTime();
+ bool IsAlive();
+
+ // Start the Spdy transaction handler when NPN indicates spdy/*
+ void StartSpdy(nsITLSSocketControl* ssl, SpdyVersion spdyVersion);
+ // Like the above, but do the bare minimum to do 0RTT data, so we can back
+ // it out, if necessary
+ void Start0RTTSpdy(SpdyVersion spdyVersion);
+
+ // Helpers for Start*Spdy
+ nsresult TryTakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> >& list);
+ nsresult MoveTransactionsToSpdy(nsresult status,
+ nsTArray<RefPtr<nsAHttpTransaction> >& list);
+
+ // Directly Add a transaction to an active connection for SPDY
+ [[nodiscard]] nsresult AddTransaction(nsAHttpTransaction*, int32_t);
+
+ // Used to set TCP keepalives for fast detection of dead connections during
+ // an initial period, and slower detection for long-lived connections.
+ [[nodiscard]] nsresult StartShortLivedTCPKeepalives();
+ [[nodiscard]] nsresult StartLongLivedTCPKeepalives();
+ [[nodiscard]] nsresult DisableTCPKeepalives();
+
+ bool CheckCanWrite0RTTData();
+ void PostProcessNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo,
+ bool earlyDataUsed);
+ void Reset0RttForSpdy();
+ void HandshakeDoneInternal();
+ uint32_t TransactionCaps() const { return mTransactionCaps; }
+
+ void MarkAsDontReuse();
+
+ private:
+ // mTransaction only points to the HTTP Transaction callbacks if the
+ // transaction is open, otherwise it is null.
+ RefPtr<nsAHttpTransaction> mTransaction;
+
+ RefPtr<TlsHandshaker> mTlsHandshaker;
+
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsresult mSocketInCondition{NS_ERROR_NOT_INITIALIZED};
+ nsresult mSocketOutCondition{NS_ERROR_NOT_INITIALIZED};
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ PRIntervalTime mLastReadTime{0};
+ PRIntervalTime mLastWriteTime{0};
+ // max download time before dropping keep-alive status
+ PRIntervalTime mMaxHangTime{0};
+ PRIntervalTime mIdleTimeout; // value of keep-alive: timeout=
+ PRIntervalTime mConsiderReusedAfterInterval{0};
+ PRIntervalTime mConsiderReusedAfterEpoch{0};
+ int64_t mCurrentBytesRead{0}; // data read per activation
+ int64_t mMaxBytesRead{0}; // max read in 1 activation
+ int64_t mTotalBytesRead{0}; // total data read
+ int64_t mContentBytesWritten{0}; // does not include CONNECT tunnel or TLS
+
+ RefPtr<nsIAsyncInputStream> mInputOverflow;
+
+ // Whether the first non-null transaction dispatched on this connection was
+ // urgent-start or not
+ bool mUrgentStartPreferred{false};
+ // A flag to prevent reset of mUrgentStartPreferred by subsequent transactions
+ bool mUrgentStartPreferredKnown{false};
+ bool mConnectedTransport{false};
+ // assume to keep-alive by default
+ bool mKeepAlive{true};
+ bool mKeepAliveMask{true};
+ bool mDontReuse{false};
+ bool mIsReused{false};
+ bool mLastTransactionExpectedNoContent{false};
+ bool mIdleMonitoring{false};
+ bool mInSpdyTunnel{false};
+ bool mForcePlainText{false};
+
+ // A snapshot of current number of transfered bytes
+ int64_t mTrafficCount{0};
+ bool mTrafficStamp{false}; // true then the above is set
+
+ // The number of <= HTTP/1.1 transactions performed on this connection. This
+ // excludes spdy transactions.
+ uint32_t mHttp1xTransactionCount{0};
+
+ // Keep-Alive: max="mRemainingConnectionUses" provides the number of future
+ // transactions (including the current one) that the server expects to allow
+ // on this persistent connection.
+ uint32_t mRemainingConnectionUses{0xffffffff};
+
+ // version level in use, 0 if unused
+ SpdyVersion mUsingSpdyVersion{SpdyVersion::NONE};
+
+ RefPtr<ASpdySession> mSpdySession;
+ RefPtr<ASpdySession> mWebSocketHttp2Session;
+ int32_t mPriority{nsISupportsPriority::PRIORITY_NORMAL};
+ bool mReportedSpdy{false};
+
+ // mUsingSpdyVersion is cleared when mSpdySession is freed, this is permanent
+ bool mEverUsedSpdy{false};
+
+ // mLastHttpResponseVersion stores the last response's http version seen.
+ HttpVersion mLastHttpResponseVersion{HttpVersion::v1_1};
+
+ // If a large keepalive has been requested for any trans,
+ // scale the default by this factor
+ uint32_t mDefaultTimeoutFactor{1};
+
+ bool mResponseTimeoutEnabled{false};
+
+ // Flag to indicate connection is in inital keepalive period (fast detect).
+ uint32_t mTCPKeepaliveConfig{kTCPKeepaliveDisabled};
+ nsCOMPtr<nsITimer> mTCPKeepaliveTransitionTimer;
+
+ private:
+ // For ForceSend()
+ static void ForceSendIO(nsITimer* aTimer, void* aClosure);
+ [[nodiscard]] nsresult MaybeForceSendIO();
+ bool mForceSendPending{false};
+ nsCOMPtr<nsITimer> mForceSendTimer;
+
+ int64_t mContentBytesWritten0RTT{0};
+ bool mDid0RTTSpdy{false};
+
+ nsresult mErrorBeforeConnect = NS_OK;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+
+ // This flag indicates if the connection is used for WebSocket.
+ // - When true and mInSpdyTunnel is also true: WebSocket over HTTP/2.
+ // - When true and mInSpdyTunnel is false: WebSocket over HTTP/1.1.
+ bool mForWebSocket{false};
+
+ std::function<void()> mContinueHandshakeDone{nullptr};
+
+ private:
+ bool mThroughCaptivePortal;
+ int64_t mTotalBytesWritten = 0; // does not include CONNECT tunnel
+
+ nsCOMPtr<nsIInputStream> mProxyConnectStream;
+
+ bool mRequestDone{false};
+ bool mHasTLSTransportLayer{false};
+ bool mTransactionDisallowHttp3{false};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpConnection_h__
diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.cpp b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
new file mode 100644
index 0000000000..e91128d85a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -0,0 +1,570 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "nsHttpConnectionInfo.h"
+
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIProtocolProxyService.h"
+#include "nsHttpHandler.h"
+#include "nsNetCID.h"
+#include "nsProxyInfo.h"
+#include "prnetdb.h"
+
+static nsresult SHA256(const char* aPlainText, nsAutoCString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpDigestAuth: no crypto hash!\n"));
+ return rv;
+ }
+ rv = hasher->Init(nsICryptoHash::SHA256);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Update((unsigned char*)aPlainText, strlen(aPlainText));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hasher->Finish(false, aResult);
+}
+
+namespace mozilla {
+namespace net {
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes,
+ bool endToEndSSL, bool aIsHttp3, bool aWebTransport)
+ : mRoutedPort(443), mLessThanTls13(false) {
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes,
+ endToEndSSL, aIsHttp3, aWebTransport);
+}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes,
+ const nsACString& routedHost, int32_t routedPort, bool aIsHttp3,
+ bool aWebTransport)
+ : mLessThanTls13(false) {
+ mEndToEndSSL = true; // so DefaultPort() works
+ mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort;
+
+ if (!originHost.Equals(routedHost) || (originPort != routedPort) ||
+ aIsHttp3) {
+ mRoutedHost = routedHost;
+ }
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes,
+ true, aIsHttp3, aWebTransport);
+}
+
+void nsHttpConnectionInfo::Init(const nsACString& host, int32_t port,
+ const nsACString& npnToken,
+ const nsACString& username,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ bool e2eSSL, bool aIsHttp3,
+ bool aWebTransport) {
+ LOG(("Init nsHttpConnectionInfo @%p\n", this));
+
+ MOZ_RELEASE_ASSERT(!aWebTransport || aIsHttp3);
+
+ mUsername = username;
+ mProxyInfo = proxyInfo;
+ mEndToEndSSL = e2eSSL;
+ mUsingConnect = false;
+ mNPNToken = npnToken;
+ mIsHttp3 = aIsHttp3;
+ mWebTransport = aWebTransport;
+ mOriginAttributes = originAttributes;
+ mTlsFlags = 0x0;
+ mIsTrrServiceChannel = false;
+ mTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ mIPv4Disabled = false;
+ mIPv6Disabled = false;
+ mHasIPHintAddress = false;
+
+ mUsingHttpsProxy = (proxyInfo && proxyInfo->IsHTTPS());
+ mUsingHttpProxy = mUsingHttpsProxy || (proxyInfo && proxyInfo->IsHTTP());
+
+ if (mUsingHttpProxy) {
+ mUsingConnect = mEndToEndSSL; // SSL always uses CONNECT
+ uint32_t resolveFlags = 0;
+ if (NS_SUCCEEDED(mProxyInfo->GetResolveFlags(&resolveFlags)) &&
+ resolveFlags & nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL) {
+ mUsingConnect = true;
+ }
+ }
+
+ SetOriginServer(host, port);
+}
+
+void nsHttpConnectionInfo::BuildHashKey() {
+ //
+ // build hash key:
+ //
+ // the hash key uniquely identifies the connection type. two connections
+ // are "equal" if they end up talking the same protocol to the same server
+ // and are both used for anonymous or non-anonymous connection only;
+ // anonymity of the connection is setup later from nsHttpChannel::AsyncOpen
+ // where we know we use anonymous connection (LOAD_ANONYMOUS load flag)
+ //
+
+ const char* keyHost;
+ int32_t keyPort;
+
+ if (mUsingHttpProxy && !mUsingConnect) {
+ keyHost = ProxyHost();
+ keyPort = ProxyPort();
+ } else {
+ keyHost = Origin();
+ keyPort = OriginPort();
+ }
+
+ // The hashkey has 9 fields followed by host connection info
+ // byte 0 is P/T/. {P,T} for Plaintext/TLS Proxy over HTTP
+ // byte 1 is S/. S is for end to end ssl such as https:// uris
+ // byte 2 is A/. A is for an anonymous channel (no cookies, etc..)
+ // byte 3 is P/. P is for a private browising channel
+ // byte 4 is I/. I is for insecure scheme on TLS for http:// uris
+ // byte 5 is X/. X is for disallow_spdy flag
+ // byte 6 is C/. C is for be Conservative
+ // byte 7 is B/. B is for allowing client certs on an anonymous channel
+ // byte 8 is F/. F is for indicating a fallback connection
+ // byte 9 is W/. W is for indicating a webTransport
+ // Note: when adding/removing fields from this list which do not have
+ // corresponding data fields on the object itself, you may also need to
+ // modify RebuildHashKey.
+
+ const auto keyTemplate =
+ std::string(UnderlyingIndex(HashKeyIndex::End), '.') +
+ std::string("[tlsflags0x00000000]");
+ mHashKey.Assign(keyTemplate.c_str());
+
+ mHashKey.Append(keyHost);
+ mHashKey.Append(':');
+ mHashKey.AppendInt(keyPort);
+ if (!mUsername.IsEmpty()) {
+ mHashKey.Append('[');
+ mHashKey.Append(mUsername);
+ mHashKey.Append(']');
+ }
+
+ if (mUsingHttpsProxy) {
+ SetHashCharAt('T', HashKeyIndex::Proxy);
+ } else if (mUsingHttpProxy) {
+ SetHashCharAt('P', HashKeyIndex::Proxy);
+ }
+ if (mEndToEndSSL) {
+ SetHashCharAt('S', HashKeyIndex::EndToEndSSL);
+ }
+
+ if (mWebTransport) {
+ SetHashCharAt('W', HashKeyIndex::WebTransport);
+ }
+
+ // NOTE: for transparent proxies (e.g., SOCKS) we need to encode the proxy
+ // info in the hash key (this ensures that we will continue to speak the
+ // right protocol even if our proxy preferences change).
+ //
+ // NOTE: for SSL tunnels add the proxy information to the cache key.
+ // We cannot use the proxy as the host parameter (as we do for non SSL)
+ // because this is a single host tunnel, but we need to include the proxy
+ // information so that a change in proxy config will mean this connection
+ // is not reused
+
+ // NOTE: Adding the username and the password provides a means to isolate
+ // keep-alive to the URL bar domain as well: If the username is the URL bar
+ // domain, keep-alive connections are not reused by resources bound to
+ // different URL bar domains as the respective hash keys are not matching.
+
+ if ((!mUsingHttpProxy && ProxyHost()) || (mUsingHttpProxy && mUsingConnect)) {
+ mHashKey.AppendLiteral(" (");
+ mHashKey.Append(ProxyType());
+ mHashKey.Append(':');
+ mHashKey.Append(ProxyHost());
+ mHashKey.Append(':');
+ mHashKey.AppendInt(ProxyPort());
+ mHashKey.Append(')');
+ mHashKey.Append('[');
+ mHashKey.Append(ProxyUsername());
+ mHashKey.Append(':');
+ const char* password = ProxyPassword();
+ if (strlen(password) > 0) {
+ nsAutoCString digestedPassword;
+ nsresult rv = SHA256(password, digestedPassword);
+ if (rv == NS_OK) {
+ mHashKey.Append(digestedPassword);
+ }
+ }
+ mHashKey.Append(']');
+ }
+
+ if (!mRoutedHost.IsEmpty()) {
+ mHashKey.AppendLiteral(" <ROUTE-via ");
+ mHashKey.Append(mRoutedHost);
+ mHashKey.Append(':');
+ mHashKey.AppendInt(mRoutedPort);
+ mHashKey.Append('>');
+ }
+
+ if (!mNPNToken.IsEmpty()) {
+ mHashKey.AppendLiteral(" {NPN-TOKEN ");
+ mHashKey.Append(mNPNToken);
+ mHashKey.AppendLiteral("}");
+ }
+
+ if (GetTRRMode() != nsIRequest::TRR_DEFAULT_MODE) {
+ // When connecting with another TRR mode, we enforce a separate connection
+ // hashkey so that we also can trigger a fresh DNS resolver that then
+ // doesn't use TRR as the previous connection might have.
+ mHashKey.AppendLiteral("[TRR:");
+ mHashKey.AppendInt(GetTRRMode());
+ mHashKey.AppendLiteral("]");
+ }
+
+ if (GetIPv4Disabled()) {
+ mHashKey.AppendLiteral("[!v4]");
+ }
+
+ if (GetIPv6Disabled()) {
+ mHashKey.AppendLiteral("[!v6]");
+ }
+
+ if (mProxyInfo) {
+ const nsCString& connectionIsolationKey =
+ mProxyInfo->ConnectionIsolationKey();
+ if (!connectionIsolationKey.IsEmpty()) {
+ mHashKey.AppendLiteral("{CIK ");
+ mHashKey.Append(connectionIsolationKey);
+ mHashKey.AppendLiteral("}");
+ }
+ if (mProxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ mHashKey.AppendLiteral("{TPRH}");
+ }
+ }
+
+ nsAutoCString originAttributes;
+ mOriginAttributes.CreateSuffix(originAttributes);
+ mHashKey.Append(originAttributes);
+}
+
+void nsHttpConnectionInfo::RebuildHashKey() {
+ // Create copies of all properties stored in our hash key.
+ bool isAnonymous = GetAnonymous();
+ bool isPrivate = GetPrivate();
+ bool isInsecureScheme = GetInsecureScheme();
+ bool isNoSpdy = GetNoSpdy();
+ bool isBeConservative = GetBeConservative();
+ bool isAnonymousAllowClientCert = GetAnonymousAllowClientCert();
+ bool isFallback = GetFallbackConnection();
+
+ BuildHashKey();
+
+ // Restore all of those properties.
+ SetAnonymous(isAnonymous);
+ SetPrivate(isPrivate);
+ SetInsecureScheme(isInsecureScheme);
+ SetNoSpdy(isNoSpdy);
+ SetBeConservative(isBeConservative);
+ SetAnonymousAllowClientCert(isAnonymousAllowClientCert);
+ SetFallbackConnection(isFallback);
+}
+
+void nsHttpConnectionInfo::SetOriginServer(const nsACString& host,
+ int32_t port) {
+ mOrigin = host;
+ mOriginPort = port == -1 ? DefaultPort() : port;
+ // Use BuildHashKey() since this can only be called when constructing an
+ // nsHttpConnectionInfo object.
+ MOZ_DIAGNOSTIC_ASSERT(mHashKey.IsEmpty());
+ BuildHashKey();
+}
+
+// Note that this function needs to be synced with
+// nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs to make sure
+// nsHttpConnectionInfo can be serialized/deserialized.
+already_AddRefed<nsHttpConnectionInfo> nsHttpConnectionInfo::Clone() const {
+ RefPtr<nsHttpConnectionInfo> clone;
+ if (mRoutedHost.IsEmpty()) {
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername,
+ mProxyInfo, mOriginAttributes,
+ mEndToEndSSL, mIsHttp3, mWebTransport);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername,
+ mProxyInfo, mOriginAttributes, mRoutedHost,
+ mRoutedPort, mIsHttp3, mWebTransport);
+ }
+
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert());
+ clone->SetFallbackConnection(GetFallbackConnection());
+ clone->SetTlsFlags(GetTlsFlags());
+ clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel());
+ clone->SetTRRMode(GetTRRMode());
+ clone->SetIPv4Disabled(GetIPv4Disabled());
+ clone->SetIPv6Disabled(GetIPv6Disabled());
+ clone->SetHasIPHintAddress(HasIPHintAddress());
+ clone->SetEchConfig(GetEchConfig());
+ MOZ_ASSERT(clone->Equals(this));
+
+ return clone.forget();
+}
+
+already_AddRefed<nsHttpConnectionInfo>
+nsHttpConnectionInfo::CloneAndAdoptHTTPSSVCRecord(
+ nsISVCBRecord* aRecord) const {
+ MOZ_ASSERT(aRecord);
+
+ // Get the domain name of this HTTPS RR. This name will be assigned to
+ // mRoutedHost in the new connection info.
+ nsAutoCString name;
+ aRecord->GetName(name);
+
+ // Try to get the port and Alpn. If this record has SvcParamKeyPort defined,
+ // the new port will be used as mRoutedPort.
+ Maybe<uint16_t> port = aRecord->GetPort();
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> alpn = aRecord->GetAlpn();
+
+ // Let the new conn info learn h3 will be used.
+ bool isHttp3 = alpn ? mozilla::net::IsHttp3(std::get<1>(*alpn)) : false;
+
+ LOG(("HTTPSSVC: use new routed host (%s) and new npnToken (%s)", name.get(),
+ alpn ? std::get<0>(*alpn).get() : "None"));
+
+ RefPtr<nsHttpConnectionInfo> clone;
+ if (name.IsEmpty()) {
+ clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort, alpn ? std::get<0>(*alpn) : EmptyCString(),
+ mUsername, mProxyInfo, mOriginAttributes, mEndToEndSSL, isHttp3);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort, alpn ? std::get<0>(*alpn) : EmptyCString(),
+ mUsername, mProxyInfo, mOriginAttributes, name,
+ port ? *port : mOriginPort, isHttp3, mWebTransport);
+ }
+
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert());
+ clone->SetFallbackConnection(GetFallbackConnection());
+ clone->SetTlsFlags(GetTlsFlags());
+ clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel());
+ clone->SetTRRMode(GetTRRMode());
+ clone->SetIPv4Disabled(GetIPv4Disabled());
+ clone->SetIPv6Disabled(GetIPv6Disabled());
+
+ bool hasIPHint = false;
+ Unused << aRecord->GetHasIPHintAddress(&hasIPHint);
+ if (hasIPHint) {
+ clone->SetHasIPHintAddress(hasIPHint);
+ }
+
+ nsAutoCString echConfig;
+ Unused << aRecord->GetEchConfig(echConfig);
+ clone->SetEchConfig(echConfig);
+
+ return clone.forget();
+}
+
+/* static */
+void nsHttpConnectionInfo::SerializeHttpConnectionInfo(
+ nsHttpConnectionInfo* aInfo, HttpConnectionInfoCloneArgs& aArgs) {
+ aArgs.host() = aInfo->GetOrigin();
+ aArgs.port() = aInfo->OriginPort();
+ aArgs.npnToken() = aInfo->GetNPNToken();
+ aArgs.username() = aInfo->GetUsername();
+ aArgs.originAttributes() = aInfo->GetOriginAttributes();
+ aArgs.endToEndSSL() = aInfo->EndToEndSSL();
+ aArgs.routedHost() = aInfo->GetRoutedHost();
+ aArgs.routedPort() = aInfo->RoutedPort();
+ aArgs.anonymous() = aInfo->GetAnonymous();
+ aArgs.aPrivate() = aInfo->GetPrivate();
+ aArgs.insecureScheme() = aInfo->GetInsecureScheme();
+ aArgs.noSpdy() = aInfo->GetNoSpdy();
+ aArgs.beConservative() = aInfo->GetBeConservative();
+ aArgs.anonymousAllowClientCert() = aInfo->GetAnonymousAllowClientCert();
+ aArgs.tlsFlags() = aInfo->GetTlsFlags();
+ aArgs.isTrrServiceChannel() = aInfo->GetTRRMode();
+ aArgs.trrMode() = aInfo->GetTRRMode();
+ aArgs.isIPv4Disabled() = aInfo->GetIPv4Disabled();
+ aArgs.isIPv6Disabled() = aInfo->GetIPv6Disabled();
+ aArgs.isHttp3() = aInfo->IsHttp3();
+ aArgs.hasIPHintAddress() = aInfo->HasIPHintAddress();
+ aArgs.echConfig() = aInfo->GetEchConfig();
+ aArgs.webTransport() = aInfo->GetWebTransport();
+
+ if (!aInfo->ProxyInfo()) {
+ return;
+ }
+
+ nsTArray<ProxyInfoCloneArgs> proxyInfoArray;
+ nsProxyInfo::SerializeProxyInfo(aInfo->ProxyInfo(), proxyInfoArray);
+ aArgs.proxyInfo() = std::move(proxyInfoArray);
+}
+
+// This function needs to be synced with nsHttpConnectionInfo::Clone.
+/* static */
+already_AddRefed<nsHttpConnectionInfo>
+nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(
+ const HttpConnectionInfoCloneArgs& aInfoArgs) {
+ nsProxyInfo* pi = nsProxyInfo::DeserializeProxyInfo(aInfoArgs.proxyInfo());
+ RefPtr<nsHttpConnectionInfo> cinfo;
+ if (aInfoArgs.routedHost().IsEmpty()) {
+ cinfo = new nsHttpConnectionInfo(
+ aInfoArgs.host(), aInfoArgs.port(), aInfoArgs.npnToken(),
+ aInfoArgs.username(), pi, aInfoArgs.originAttributes(),
+ aInfoArgs.endToEndSSL(), aInfoArgs.isHttp3(), aInfoArgs.webTransport());
+ } else {
+ MOZ_ASSERT(aInfoArgs.endToEndSSL());
+ cinfo = new nsHttpConnectionInfo(
+ aInfoArgs.host(), aInfoArgs.port(), aInfoArgs.npnToken(),
+ aInfoArgs.username(), pi, aInfoArgs.originAttributes(),
+ aInfoArgs.routedHost(), aInfoArgs.routedPort(), aInfoArgs.isHttp3(),
+ aInfoArgs.webTransport());
+ }
+
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ cinfo->SetAnonymous(aInfoArgs.anonymous());
+ cinfo->SetPrivate(aInfoArgs.aPrivate());
+ cinfo->SetInsecureScheme(aInfoArgs.insecureScheme());
+ cinfo->SetNoSpdy(aInfoArgs.noSpdy());
+ cinfo->SetBeConservative(aInfoArgs.beConservative());
+ cinfo->SetAnonymousAllowClientCert(aInfoArgs.anonymousAllowClientCert());
+ cinfo->SetFallbackConnection(aInfoArgs.fallbackConnection());
+ cinfo->SetTlsFlags(aInfoArgs.tlsFlags());
+ cinfo->SetIsTrrServiceChannel(aInfoArgs.isTrrServiceChannel());
+ cinfo->SetTRRMode(static_cast<nsIRequest::TRRMode>(aInfoArgs.trrMode()));
+ cinfo->SetIPv4Disabled(aInfoArgs.isIPv4Disabled());
+ cinfo->SetIPv6Disabled(aInfoArgs.isIPv6Disabled());
+ cinfo->SetHasIPHintAddress(aInfoArgs.hasIPHintAddress());
+ cinfo->SetEchConfig(aInfoArgs.echConfig());
+
+ return cinfo.forget();
+}
+
+void nsHttpConnectionInfo::CloneAsDirectRoute(nsHttpConnectionInfo** outCI) {
+ // Explicitly use an empty npnToken when |mIsHttp3| is true, since we want to
+ // create a non-http3 connection info.
+ RefPtr<nsHttpConnectionInfo> clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort,
+ (mRoutedHost.IsEmpty() && !mIsHttp3) ? mNPNToken : ""_ns, mUsername,
+ mProxyInfo, mOriginAttributes, mEndToEndSSL, false, mWebTransport);
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert());
+ clone->SetFallbackConnection(GetFallbackConnection());
+ clone->SetTlsFlags(GetTlsFlags());
+ clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel());
+ clone->SetTRRMode(GetTRRMode());
+ clone->SetIPv4Disabled(GetIPv4Disabled());
+ clone->SetIPv6Disabled(GetIPv6Disabled());
+ clone->SetHasIPHintAddress(HasIPHintAddress());
+ clone->SetEchConfig(GetEchConfig());
+
+ clone.forget(outCI);
+}
+
+nsresult nsHttpConnectionInfo::CreateWildCard(nsHttpConnectionInfo** outParam) {
+ // T???mozilla.org:443 (https:proxy.ducksong.com:3128) [specifc form]
+ // TS??*:0 (https:proxy.ducksong.com:3128) [wildcard form]
+
+ if (!mUsingHttpsProxy) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<nsHttpConnectionInfo> clone;
+ clone = new nsHttpConnectionInfo("*"_ns, 0, mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, true, mIsHttp3,
+ mWebTransport);
+ // Make sure the anonymous and private flags are transferred!
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone.forget(outParam);
+ return NS_OK;
+}
+
+void nsHttpConnectionInfo::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ if (mTRRMode != aTRRMode) {
+ mTRRMode = aTRRMode;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetIPv4Disabled(bool aNoIPv4) {
+ if (mIPv4Disabled != aNoIPv4) {
+ mIPv4Disabled = aNoIPv4;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetIPv6Disabled(bool aNoIPv6) {
+ if (mIPv6Disabled != aNoIPv6) {
+ mIPv6Disabled = aNoIPv6;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetWebTransport(bool aWebTransport) {
+ if (mWebTransport != aWebTransport) {
+ mWebTransport = aWebTransport;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetTlsFlags(uint32_t aTlsFlags) {
+ mTlsFlags = aTlsFlags;
+ const uint32_t tlsFlagsLength = 8;
+ const uint32_t tlsFlagsIndex =
+ UnderlyingIndex(HashKeyIndex::End) + strlen("[tlsflags0x");
+ mHashKey.Replace(tlsFlagsIndex, tlsFlagsLength,
+ nsPrintfCString("%08x", mTlsFlags));
+}
+
+bool nsHttpConnectionInfo::UsingProxy() {
+ if (!mProxyInfo) return false;
+ return !mProxyInfo->IsDirect();
+}
+
+bool nsHttpConnectionInfo::HostIsLocalIPLiteral() const {
+ NetAddr netAddr;
+ // If the host/proxy host is not an IP address literal, return false.
+ nsAutoCString host(ProxyHost() ? ProxyHost() : Origin());
+ if (NS_FAILED(netAddr.InitFromString(host))) {
+ return false;
+ }
+ return netAddr.IsIPAddrLocal();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h
new file mode 100644
index 0000000000..892d795b10
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -0,0 +1,317 @@
+/* -*- 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 nsHttpConnectionInfo_h__
+#define nsHttpConnectionInfo_h__
+
+#include "nsHttp.h"
+#include "nsProxyInfo.h"
+#include "nsCOMPtr.h"
+#include "nsStringFwd.h"
+#include "mozilla/Logging.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "ARefBase.h"
+#include "nsIRequest.h"
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionInfo - holds the properties of a connection
+//-----------------------------------------------------------------------------
+
+// http:// uris through a proxy will all share the same CI, because they can
+// all use the same connection. (modulo pb and anonymous flags). They just use
+// the proxy as the origin host name.
+// however, https:// uris tunnel through the proxy so they will have different
+// CIs - the CI reflects both the proxy and the origin.
+// however, proxy conenctions made with http/2 (or spdy) can tunnel to the
+// origin and multiplex non tunneled transactions at the same time, so they have
+// a special wildcard CI that accepts all origins through that proxy.
+
+class nsISVCBRecord;
+
+namespace mozilla {
+namespace net {
+
+extern LazyLogModule gHttpLog;
+class HttpConnectionInfoCloneArgs;
+class nsHttpTransaction;
+
+class nsHttpConnectionInfo final : public ARefBase {
+ public:
+ nsHttpConnectionInfo(const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ bool endToEndSSL = false, bool aIsHttp3 = false,
+ bool aWebTransport = false);
+
+ // this version must use TLS and you may supply separate
+ // connection (aka routing) information than the authenticated
+ // origin information
+ nsHttpConnectionInfo(const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ const nsACString& routedHost, int32_t routedPort,
+ bool aIsHttp3, bool aWebTransport = false);
+
+ static void SerializeHttpConnectionInfo(nsHttpConnectionInfo* aInfo,
+ HttpConnectionInfoCloneArgs& aArgs);
+ static already_AddRefed<nsHttpConnectionInfo>
+ DeserializeHttpConnectionInfoCloneArgs(
+ const HttpConnectionInfoCloneArgs& aInfoArgs);
+
+ private:
+ virtual ~nsHttpConnectionInfo() {
+ MOZ_LOG(gHttpLog, LogLevel::Debug,
+ ("Destroying nsHttpConnectionInfo @%p\n", this));
+ }
+
+ void BuildHashKey();
+ void RebuildHashKey();
+
+ // See comments in nsHttpConnectionInfo::BuildHashKey for the meaning of each
+ // field.
+ enum class HashKeyIndex : uint32_t {
+ Proxy = 0,
+ EndToEndSSL,
+ Anonymous,
+ Private,
+ InsecureScheme,
+ NoSpdy,
+ BeConservative,
+ AnonymousAllowClientCert,
+ FallbackConnection,
+ WebTransport,
+ End,
+ };
+ constexpr inline auto UnderlyingIndex(HashKeyIndex aIndex) const {
+ return std::underlying_type_t<HashKeyIndex>(aIndex);
+ }
+
+ public:
+ const nsCString& HashKey() const { return mHashKey; }
+
+ const nsCString& GetOrigin() const { return mOrigin; }
+ const char* Origin() const { return mOrigin.get(); }
+ int32_t OriginPort() const { return mOriginPort; }
+
+ const nsCString& GetRoutedHost() const { return mRoutedHost; }
+ const char* RoutedHost() const { return mRoutedHost.get(); }
+ int32_t RoutedPort() const { return mRoutedPort; }
+
+ // OK to treat these as an infalible allocation
+ already_AddRefed<nsHttpConnectionInfo> Clone() const;
+ // This main prupose of this function is to clone this connection info, but
+ // replace mRoutedHost with SvcDomainName in the given SVCB record. Note that
+ // if SvcParamKeyPort and SvcParamKeyAlpn are presented in the SVCB record,
+ // mRoutedPort and mNPNToken will be replaced as well.
+ already_AddRefed<nsHttpConnectionInfo> CloneAndAdoptHTTPSSVCRecord(
+ nsISVCBRecord* aRecord) const;
+ void CloneAsDirectRoute(nsHttpConnectionInfo** outCI);
+ [[nodiscard]] nsresult CreateWildCard(nsHttpConnectionInfo** outParam);
+
+ const char* ProxyHost() const {
+ return mProxyInfo ? mProxyInfo->Host().get() : nullptr;
+ }
+ int32_t ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
+ const char* ProxyType() const {
+ return mProxyInfo ? mProxyInfo->Type() : nullptr;
+ }
+ const char* ProxyUsername() const {
+ return mProxyInfo ? mProxyInfo->Username().get() : nullptr;
+ }
+ const char* ProxyPassword() const {
+ return mProxyInfo ? mProxyInfo->Password().get() : nullptr;
+ }
+
+ const nsCString& ProxyAuthorizationHeader() const {
+ return mProxyInfo ? mProxyInfo->ProxyAuthorizationHeader() : EmptyCString();
+ }
+ const nsCString& ConnectionIsolationKey() const {
+ return mProxyInfo ? mProxyInfo->ConnectionIsolationKey() : EmptyCString();
+ }
+
+ // Compare this connection info to another...
+ // Two connections are 'equal' if they end up talking the same
+ // protocol to the same server. This is needed to properly manage
+ // persistent connections to proxies
+ // Note that we don't care about transparent proxies -
+ // it doesn't matter if we're talking via socks or not, since
+ // a request will end up at the same host.
+ bool Equals(const nsHttpConnectionInfo* info) {
+ return mHashKey.Equals(info->HashKey());
+ }
+
+ const char* Username() const { return mUsername.get(); }
+ nsProxyInfo* ProxyInfo() const { return mProxyInfo; }
+ int32_t DefaultPort() const {
+ return mEndToEndSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+ void SetAnonymous(bool anon) {
+ SetHashCharAt(anon ? 'A' : '.', HashKeyIndex::Anonymous);
+ }
+ bool GetAnonymous() const {
+ return GetHashCharAt(HashKeyIndex::Anonymous) == 'A';
+ }
+ void SetPrivate(bool priv) {
+ SetHashCharAt(priv ? 'P' : '.', HashKeyIndex::Private);
+ }
+ bool GetPrivate() const {
+ return GetHashCharAt(HashKeyIndex::Private) == 'P';
+ }
+ void SetInsecureScheme(bool insecureScheme) {
+ SetHashCharAt(insecureScheme ? 'I' : '.', HashKeyIndex::InsecureScheme);
+ }
+ bool GetInsecureScheme() const {
+ return GetHashCharAt(HashKeyIndex::InsecureScheme) == 'I';
+ }
+
+ void SetNoSpdy(bool aNoSpdy) {
+ SetHashCharAt(aNoSpdy ? 'X' : '.', HashKeyIndex::NoSpdy);
+ if (aNoSpdy && mNPNToken == "h2"_ns) {
+ mNPNToken.Truncate();
+ RebuildHashKey();
+ }
+ }
+ bool GetNoSpdy() const { return GetHashCharAt(HashKeyIndex::NoSpdy) == 'X'; }
+
+ void SetBeConservative(bool aBeConservative) {
+ SetHashCharAt(aBeConservative ? 'C' : '.', HashKeyIndex::BeConservative);
+ }
+ bool GetBeConservative() const {
+ return GetHashCharAt(HashKeyIndex::BeConservative) == 'C';
+ }
+
+ void SetAnonymousAllowClientCert(bool anon) {
+ SetHashCharAt(anon ? 'B' : '.', HashKeyIndex::AnonymousAllowClientCert);
+ }
+ bool GetAnonymousAllowClientCert() const {
+ return GetHashCharAt(HashKeyIndex::AnonymousAllowClientCert) == 'B';
+ }
+
+ void SetFallbackConnection(bool aFallback) {
+ SetHashCharAt(aFallback ? 'F' : '.', HashKeyIndex::FallbackConnection);
+ }
+ bool GetFallbackConnection() const {
+ return GetHashCharAt(HashKeyIndex::FallbackConnection) == 'F';
+ }
+
+ void SetTlsFlags(uint32_t aTlsFlags);
+ uint32_t GetTlsFlags() const { return mTlsFlags; }
+
+ // IsTrrServiceChannel means that this connection is used to send TRR requests
+ // over
+ void SetIsTrrServiceChannel(bool aIsTRRChannel) {
+ mIsTrrServiceChannel = aIsTRRChannel;
+ }
+ bool GetIsTrrServiceChannel() const { return mIsTrrServiceChannel; }
+
+ void SetTRRMode(nsIRequest::TRRMode aTRRMode);
+ nsIRequest::TRRMode GetTRRMode() const { return mTRRMode; }
+
+ void SetIPv4Disabled(bool aNoIPv4);
+ bool GetIPv4Disabled() const { return mIPv4Disabled; }
+
+ void SetIPv6Disabled(bool aNoIPv6);
+ bool GetIPv6Disabled() const { return mIPv6Disabled; }
+
+ void SetWebTransport(bool aWebTransport);
+ bool GetWebTransport() const { return mWebTransport; }
+
+ const nsCString& GetNPNToken() { return mNPNToken; }
+ const nsCString& GetUsername() { return mUsername; }
+
+ const OriginAttributes& GetOriginAttributes() { return mOriginAttributes; }
+
+ // Returns true for any kind of proxy (http, socks, https, etc..)
+ bool UsingProxy();
+
+ // Returns true when proxying over HTTP or HTTPS
+ bool UsingHttpProxy() const { return mUsingHttpProxy || mUsingHttpsProxy; }
+
+ // Returns true when proxying over HTTPS
+ bool UsingHttpsProxy() const { return mUsingHttpsProxy; }
+
+ // Returns true when a resource is in SSL end to end (e.g. https:// uri)
+ bool EndToEndSSL() const { return mEndToEndSSL; }
+
+ // Returns true when at least first hop is SSL (e.g. proxy over https or https
+ // uri)
+ bool FirstHopSSL() const { return mEndToEndSSL || mUsingHttpsProxy; }
+
+ // Returns true when CONNECT is used to tunnel through the proxy (e.g.
+ // https:// or ws://)
+ bool UsingConnect() const { return mUsingConnect; }
+
+ // Returns true when origin/proxy is an RFC1918 literal.
+ bool HostIsLocalIPLiteral() const;
+
+ bool GetLessThanTls13() const { return mLessThanTls13; }
+ void SetLessThanTls13(bool aLessThanTls13) {
+ mLessThanTls13 = aLessThanTls13;
+ }
+
+ bool IsHttp3() const { return mIsHttp3; }
+
+ void SetHasIPHintAddress(bool aHasIPHint) { mHasIPHintAddress = aHasIPHint; }
+ bool HasIPHintAddress() const { return mHasIPHintAddress; }
+
+ void SetEchConfig(const nsACString& aEchConfig) { mEchConfig = aEchConfig; }
+ const nsCString& GetEchConfig() const { return mEchConfig; }
+
+ private:
+ void Init(const nsACString& host, int32_t port, const nsACString& npnToken,
+ const nsACString& username, nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes, bool e2eSSL,
+ bool aIsHttp3, bool aWebTransport);
+ void SetOriginServer(const nsACString& host, int32_t port);
+ nsCString::char_type GetHashCharAt(HashKeyIndex aIndex) const {
+ return mHashKey.CharAt(UnderlyingIndex(aIndex));
+ }
+ void SetHashCharAt(nsCString::char_type aValue, HashKeyIndex aIndex) {
+ mHashKey.SetCharAt(aValue, UnderlyingIndex(aIndex));
+ }
+
+ nsCString mOrigin;
+ int32_t mOriginPort = 0;
+ nsCString mRoutedHost;
+ int32_t mRoutedPort;
+
+ nsCString mHashKey;
+ nsCString mUsername;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ bool mUsingHttpProxy = false;
+ bool mUsingHttpsProxy = false;
+ bool mEndToEndSSL = false;
+ // if will use CONNECT with http proxy
+ bool mUsingConnect = false;
+ nsCString mNPNToken;
+ OriginAttributes mOriginAttributes;
+ nsIRequest::TRRMode mTRRMode;
+
+ uint32_t mTlsFlags = 0;
+ uint16_t mIsTrrServiceChannel : 1;
+ uint16_t mIPv4Disabled : 1;
+ uint16_t mIPv6Disabled : 1;
+
+ bool mLessThanTls13; // This will be set to true if we negotiate less than
+ // tls1.3. If the tls version is till not know or it
+ // is 1.3 or greater the value will be false.
+ bool mIsHttp3 = false;
+ bool mWebTransport = false;
+
+ bool mHasIPHintAddress = false;
+ nsCString mEchConfig;
+
+ // for RefPtr
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo, override)
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpConnectionInfo_h__
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
new file mode 100644
index 0000000000..2e937d0f2a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -0,0 +1,3863 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+#include <utility>
+
+#include "ConnectionHandle.h"
+#include "HttpConnectionUDP.h"
+#include "NullHttpTransaction.h"
+#include "SpeculativeTransaction.h"
+#include "mozilla/Components.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsCOMPtr.h"
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpHandler.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPipe.h"
+#include "nsIRequestContext.h"
+#include "nsISocketTransport.h"
+#include "nsISocketTransportService.h"
+#include "nsITransport.h"
+#include "nsIXPConnect.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsNetCID.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsStreamUtils.h"
+
+using namespace mozilla;
+
+namespace geckoprofiler::markers {
+
+struct UrlMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("Url");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString8View& aURL) {
+ if (aURL.Length() != 0) {
+ aWriter.StringProperty("url", aURL);
+ }
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema(MS::Location::MarkerChart, MS::Location::MarkerTable);
+ schema.SetTableLabel("{marker.name} - {marker.data.url}");
+ schema.AddKeyFormat("url", MS::Format::Url);
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver, nsINamed)
+
+//-----------------------------------------------------------------------------
+
+nsHttpConnectionMgr::nsHttpConnectionMgr() {
+ LOG(("Creating nsHttpConnectionMgr @%p\n", this));
+}
+
+nsHttpConnectionMgr::~nsHttpConnectionMgr() {
+ LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
+ MOZ_ASSERT(mCoalescingHash.Count() == 0);
+ if (mTimeoutTick) mTimeoutTick->Cancel();
+}
+
+nsresult nsHttpConnectionMgr::EnsureSocketThreadTarget() {
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = components::IO::Service();
+ if (ioService) {
+ nsCOMPtr<nsISocketTransportService> realSTS =
+ components::SocketTransport::Service();
+ sts = do_QueryInterface(realSTS);
+ }
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already initialized or if we've shut down
+ if (mSocketThreadTarget || mIsShuttingDown) return NS_OK;
+
+ mSocketThreadTarget = sts;
+
+ return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpConnectionMgr::Init(
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConns,
+ uint16_t maxPersistConnsPerHost, uint16_t maxPersistConnsPerProxy,
+ uint16_t maxRequestDelay, bool throttleEnabled, uint32_t throttleVersion,
+ uint32_t throttleSuspendFor, uint32_t throttleResumeFor,
+ uint32_t throttleReadLimit, uint32_t throttleReadInterval,
+ uint32_t throttleHoldTime, uint32_t throttleMaxTime,
+ bool beConservativeForProxy) {
+ LOG(("nsHttpConnectionMgr::Init\n"));
+
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
+ mMaxConns = maxConns;
+ mMaxPersistConnsPerHost = maxPersistConnsPerHost;
+ mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
+ mMaxRequestDelay = maxRequestDelay;
+
+ mThrottleEnabled = throttleEnabled;
+ mThrottleVersion = throttleVersion;
+ mThrottleSuspendFor = throttleSuspendFor;
+ mThrottleResumeFor = throttleResumeFor;
+ mThrottleReadLimit = throttleReadLimit;
+ mThrottleReadInterval = throttleReadInterval;
+ mThrottleHoldTime = throttleHoldTime;
+ mThrottleMaxTime = TimeDuration::FromMilliseconds(throttleMaxTime);
+
+ mBeConservativeForProxy = beConservativeForProxy;
+
+ mIsShuttingDown = false;
+ }
+
+ return EnsureSocketThreadTarget();
+}
+
+class BoolWrapper : public ARefBase {
+ public:
+ BoolWrapper() = default;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override)
+
+ public: // intentional!
+ bool mBool{false};
+
+ private:
+ virtual ~BoolWrapper() = default;
+};
+
+nsresult nsHttpConnectionMgr::Shutdown() {
+ LOG(("nsHttpConnectionMgr::Shutdown\n"));
+
+ RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already shutdown
+ if (!mSocketThreadTarget) return NS_OK;
+
+ nsresult rv =
+ PostEvent(&nsHttpConnectionMgr::OnMsgShutdown, 0, shutdownWrapper);
+
+ // release our reference to the STS to prevent further events
+ // from being posted. this is how we indicate that we are
+ // shutting down.
+ mIsShuttingDown = true;
+ mSocketThreadTarget = nullptr;
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post SHUTDOWN message");
+ return rv;
+ }
+ }
+
+ // wait for shutdown event to complete
+ SpinEventLoopUntil("nsHttpConnectionMgr::Shutdown"_ns,
+ [&, shutdownWrapper]() { return shutdownWrapper->mBool; });
+
+ return NS_OK;
+}
+
+class ConnEvent : public Runnable {
+ public:
+ ConnEvent(nsHttpConnectionMgr* mgr, nsConnEventHandler handler,
+ int32_t iparam, ARefBase* vparam)
+ : Runnable("net::ConnEvent"),
+ mMgr(mgr),
+ mHandler(handler),
+ mIParam(iparam),
+ mVParam(vparam) {}
+
+ NS_IMETHOD Run() override {
+ (mMgr->*mHandler)(mIParam, mVParam);
+ return NS_OK;
+ }
+
+ private:
+ virtual ~ConnEvent() = default;
+
+ RefPtr<nsHttpConnectionMgr> mMgr;
+ nsConnEventHandler mHandler;
+ int32_t mIParam;
+ RefPtr<ARefBase> mVParam;
+};
+
+nsresult nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
+ int32_t iparam, ARefBase* vparam) {
+ Unused << EnsureSocketThreadTarget();
+
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ target = mSocketThreadTarget;
+ }
+
+ if (!target) {
+ NS_WARNING("cannot post event if not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
+ return target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds) {
+ LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
+
+ if (!mTimer) mTimer = NS_NewTimer();
+
+ // failure to create a timer is not a fatal error, but idle connections
+ // will not be cleaned up until we try to use them.
+ if (mTimer) {
+ mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
+ mTimer->Init(this, timeInSeconds * 1000, nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create: timer for pruning the dead connections!");
+ }
+}
+
+void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() {
+ // Leave the timer in place if there are connections that potentially
+ // need management
+ if (mNumIdleConns ||
+ (mNumActiveConns && StaticPrefs::network_http_http2_enabled())) {
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void nsHttpConnectionMgr::ConditionallyStopTimeoutTick() {
+ LOG(
+ ("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
+ "armed=%d active=%d\n",
+ mTimeoutTickArmed, mNumActiveConns));
+
+ if (!mTimeoutTickArmed) return;
+
+ if (mNumActiveConns) return;
+
+ LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
+
+ mTimeoutTick->Cancel();
+ mTimeoutTickArmed = false;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionMgr::nsINamed
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnectionMgr::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsHttpConnectionMgr");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionMgr::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnectionMgr::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
+
+ if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mTimer) {
+ Unused << PruneDeadConnections();
+ } else if (timer == mTimeoutTick) {
+ TimeoutTick();
+ } else if (timer == mTrafficTimer) {
+ Unused << PruneNoTraffic();
+ } else if (timer == mThrottleTicker) {
+ ThrottlerTick();
+ } else if (timer == mDelayedResumeReadTimer) {
+ ResumeBackgroundThrottledTransactions();
+ } else {
+ MOZ_ASSERT(false, "unexpected timer-callback");
+ LOG(("Unexpected timer object\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpConnectionMgr::AddTransaction(HttpTransactionShell* trans,
+ int32_t priority) {
+ LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
+ // Make sure a transaction is not in a pending queue.
+ CheckTransInPendingQueue(trans->AsHttpTransaction());
+ return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority,
+ trans->AsHttpTransaction());
+}
+
+class NewTransactionData : public ARefBase {
+ public:
+ NewTransactionData(nsHttpTransaction* trans, int32_t priority,
+ nsHttpTransaction* transWithStickyConn)
+ : mTrans(trans),
+ mPriority(priority),
+ mTransWithStickyConn(transWithStickyConn) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData, override)
+
+ RefPtr<nsHttpTransaction> mTrans;
+ int32_t mPriority;
+ RefPtr<nsHttpTransaction> mTransWithStickyConn;
+
+ private:
+ virtual ~NewTransactionData() = default;
+};
+
+nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn(
+ HttpTransactionShell* trans, int32_t priority,
+ HttpTransactionShell* transWithStickyConn) {
+ LOG(
+ ("nsHttpConnectionMgr::AddTransactionWithStickyConn "
+ "[trans=%p %d transWithStickyConn=%p]\n",
+ trans, priority, transWithStickyConn));
+ // Make sure a transaction is not in a pending queue.
+ CheckTransInPendingQueue(trans->AsHttpTransaction());
+
+ RefPtr<NewTransactionData> data =
+ new NewTransactionData(trans->AsHttpTransaction(), priority,
+ transWithStickyConn->AsHttpTransaction());
+ return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0,
+ data);
+}
+
+nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans,
+ int32_t priority) {
+ LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans,
+ priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority,
+ trans->AsHttpTransaction());
+}
+
+void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell* trans, const ClassOfService& classOfService) {
+ LOG(
+ ("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p "
+ "classOfService flags=%" PRIu32 " inc=%d]\n",
+ trans, static_cast<uint32_t>(classOfService.Flags()),
+ classOfService.Incremental()));
+
+ Unused << EnsureSocketThreadTarget();
+
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ target = mSocketThreadTarget;
+ }
+
+ if (!target) {
+ NS_WARNING("cannot post event if not initialized");
+ return;
+ }
+
+ RefPtr<nsHttpConnectionMgr> self(this);
+ Unused << target->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnectionMgr::CallUpdateClassOfServiceOnTransaction",
+ [cos{classOfService}, self{std::move(self)}, trans = RefPtr{trans}]() {
+ self->OnMsgUpdateClassOfServiceOnTransaction(
+ cos, trans->AsHttpTransaction());
+ }));
+}
+
+nsresult nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell* trans,
+ nsresult reason) {
+ LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
+ trans, static_cast<uint32_t>(reason)));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
+ static_cast<int32_t>(reason), trans->AsHttpTransaction());
+}
+
+nsresult nsHttpConnectionMgr::PruneDeadConnections() {
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
+}
+
+//
+// Called after a timeout. Check for active connections that have had no
+// traffic since they were "marked" and nuke them.
+nsresult nsHttpConnectionMgr::PruneNoTraffic() {
+ LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
+ mPruningNoTraffic = true;
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
+}
+
+nsresult nsHttpConnectionMgr::VerifyTraffic() {
+ LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
+}
+
+nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanup() {
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
+ nullptr);
+}
+
+nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCI) {
+ if (!aCI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
+ ci);
+}
+
+nsresult nsHttpConnectionMgr::DoSingleConnectionCleanup(
+ nsHttpConnectionInfo* aCI) {
+ if (!aCI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup, 0, ci);
+}
+
+class SpeculativeConnectArgs : public ARefBase {
+ public:
+ SpeculativeConnectArgs() = default;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override)
+
+ public: // intentional!
+ RefPtr<SpeculativeTransaction> mTrans;
+
+ bool mFetchHTTPSRR{false};
+
+ private:
+ virtual ~SpeculativeConnectArgs() = default;
+ NS_DECL_OWNINGTHREAD
+};
+
+nsresult nsHttpConnectionMgr::SpeculativeConnect(
+ nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
+ SpeculativeTransaction* aTransaction, bool aFetchHTTPSRR) {
+ if (!IsNeckoChild() && NS_IsMainThread()) {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+
+ LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
+ ci->HashKey().get()));
+
+ nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+ do_GetInterface(callbacks);
+
+ bool allow1918 = overrider ? overrider->GetAllow1918() : false;
+
+ // Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
+ LOG(
+ ("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
+ "address [%s]",
+ ci->Origin()));
+ return NS_OK;
+ }
+
+ nsCString url = ci->EndToEndSSL() ? "https://"_ns : "http://"_ns;
+ url += ci->GetOrigin();
+ PROFILER_MARKER("SpeculativeConnect", NETWORK, {}, UrlMarker, url);
+
+ RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
+
+ // Wrap up the callbacks and the target to ensure they're released on the
+ // target thread properly.
+ nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
+ NS_NewInterfaceRequestorAggregation(callbacks, nullptr,
+ getter_AddRefs(wrappedCallbacks));
+
+ caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+ caps |= NS_HTTP_ERROR_SOFTLY;
+ args->mTrans = aTransaction
+ ? aTransaction
+ : new SpeculativeTransaction(ci, wrappedCallbacks, caps);
+ args->mFetchHTTPSRR = aFetchHTTPSRR;
+
+ if (overrider) {
+ args->mTrans->SetParallelSpeculativeConnectLimit(
+ overrider->GetParallelSpeculativeConnectLimit());
+ args->mTrans->SetIgnoreIdle(overrider->GetIgnoreIdle());
+ args->mTrans->SetIsFromPredictor(overrider->GetIsFromPredictor());
+ args->mTrans->SetAllow1918(overrider->GetAllow1918());
+ }
+
+ return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
+}
+
+nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget** target) {
+ Unused << EnsureSocketThreadTarget();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
+ temp.forget(target);
+ return NS_OK;
+}
+
+nsresult nsHttpConnectionMgr::ReclaimConnection(HttpConnectionBase* conn) {
+ LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
+
+ Unused << EnsureSocketThreadTarget();
+
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ target = mSocketThreadTarget;
+ }
+
+ if (!target) {
+ NS_WARNING("cannot post event if not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<HttpConnectionBase> connRef(conn);
+ RefPtr<nsHttpConnectionMgr> self(this);
+ return target->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnectionMgr::CallReclaimConnection",
+ [conn{std::move(connRef)}, self{std::move(self)}]() {
+ self->OnMsgReclaimConnection(conn);
+ }));
+}
+
+// A structure used to marshall 6 pointers across the various necessary
+// threads to complete an HTTP upgrade.
+class nsCompleteUpgradeData : public ARefBase {
+ public:
+ nsCompleteUpgradeData(nsHttpTransaction* aTrans,
+ nsIHttpUpgradeListener* aListener, bool aJsWrapped)
+ : mTrans(aTrans), mUpgradeListener(aListener), mJsWrapped(aJsWrapped) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData, override)
+
+ RefPtr<nsHttpTransaction> mTrans;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ bool mJsWrapped;
+
+ private:
+ virtual ~nsCompleteUpgradeData() {
+ NS_ReleaseOnMainThread("nsCompleteUpgradeData.mUpgradeListener",
+ mUpgradeListener.forget());
+ }
+};
+
+nsresult nsHttpConnectionMgr::CompleteUpgrade(
+ HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
+ // test if aUpgradeListener is a wrapped JsObject
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapper = do_QueryInterface(aUpgradeListener);
+
+ bool wrapped = !!wrapper;
+
+ RefPtr<nsCompleteUpgradeData> data = new nsCompleteUpgradeData(
+ aTrans->AsHttpTransaction(), aUpgradeListener, wrapped);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
+}
+
+nsresult nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value) {
+ uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
+ static_cast<int32_t>(param), nullptr);
+}
+
+nsresult nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo* aCI) {
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", aCI->HashKey().get()));
+ RefPtr<nsHttpConnectionInfo> ci;
+ if (aCI) {
+ ci = aCI->Clone();
+ }
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
+}
+
+nsresult nsHttpConnectionMgr::ProcessPendingQ() {
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t,
+ ARefBase* param) {
+ EventTokenBucket* tokenBucket = static_cast<EventTokenBucket*>(param);
+ gHttpHandler->SetRequestTokenBucket(tokenBucket);
+}
+
+nsresult nsHttpConnectionMgr::UpdateRequestTokenBucket(
+ EventTokenBucket* aBucket) {
+ // Call From main thread when a new EventTokenBucket has been made in order
+ // to post the new value to the socket thread.
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket, 0,
+ aBucket);
+}
+
+nsresult nsHttpConnectionMgr::ClearConnectionHistory() {
+ return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory, 0,
+ nullptr);
+}
+
+void nsHttpConnectionMgr::OnMsgClearConnectionHistory(int32_t,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::OnMsgClearConnectionHistory"));
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+ if (ent->IdleConnectionsLength() == 0 && ent->ActiveConnsLength() == 0 &&
+ ent->DnsAndConnectSocketsLength() == 0 &&
+ ent->UrgentStartQueueLength() == 0 && ent->PendingQueueLength() == 0 &&
+ !ent->mDoNotDestroy) {
+ iter.Remove();
+ }
+ }
+}
+
+nsresult nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn));
+
+ if (!conn->ConnectionInfo()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+
+ if (!ent || NS_FAILED(ent->CloseIdleConnection(conn))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpConnectionMgr::RemoveIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p", this, conn));
+
+ if (!conn->ConnectionInfo()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+
+ if (!ent || NS_FAILED(ent->RemoveIdleConnection(conn))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(
+ ConnectionEntry* ent, const nsCString& key, bool justKidding, bool aNoHttp2,
+ bool aNoHttp3) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!aNoHttp2 || !aNoHttp3);
+ MOZ_ASSERT(ent->mConnInfo);
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+
+ nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(key);
+ if (!listOfWeakConns) {
+ return nullptr;
+ }
+
+ uint32_t listLen = listOfWeakConns->Length();
+ for (uint32_t j = 0; j < listLen;) {
+ RefPtr<HttpConnectionBase> potentialMatch =
+ do_QueryReferent(listOfWeakConns->ElementAt(j));
+ if (!potentialMatch) {
+ // This is a connection that needs to be removed from the list
+ LOG(
+ ("FindCoalescableConnectionByHashKey() found old conn %p that has "
+ "null weak ptr - removing\n",
+ listOfWeakConns->ElementAt(j).get()));
+ if (j != listLen - 1) {
+ listOfWeakConns->Elements()[j] =
+ listOfWeakConns->Elements()[listLen - 1];
+ }
+ listOfWeakConns->RemoveLastElement();
+ MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
+ listLen--;
+ continue; // without adjusting iterator
+ }
+
+ if (aNoHttp3 && potentialMatch->UsingHttp3()) {
+ j++;
+ continue;
+ }
+ if (aNoHttp2 && potentialMatch->UsingSpdy()) {
+ j++;
+ continue;
+ }
+ bool couldJoin;
+ if (justKidding) {
+ couldJoin =
+ potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
+ } else {
+ couldJoin =
+ potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
+ }
+ if (couldJoin) {
+ LOG(
+ ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
+ "newCI=%s matchedCI=%s join ok\n",
+ potentialMatch.get(), key.get(), ci->HashKey().get(),
+ potentialMatch->ConnectionInfo()->HashKey().get()));
+ return potentialMatch.get();
+ }
+ LOG(
+ ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
+ "newCI=%s matchedCI=%s join failed\n",
+ potentialMatch.get(), key.get(), ci->HashKey().get(),
+ potentialMatch->ConnectionInfo()->HashKey().get()));
+
+ ++j; // bypassed by continue when weakptr fails
+ }
+
+ if (!listLen) { // shrunk to 0 while iterating
+ LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
+ mCoalescingHash.Remove(key);
+ }
+ return nullptr;
+}
+
+static void BuildOriginFrameHashKey(nsACString& newKey,
+ nsHttpConnectionInfo* ci,
+ const nsACString& host, int32_t port) {
+ newKey.Assign(host);
+ if (ci->GetAnonymous()) {
+ newKey.AppendLiteral("~A:");
+ } else {
+ newKey.AppendLiteral("~.:");
+ }
+ if (ci->GetFallbackConnection()) {
+ newKey.AppendLiteral("~F:");
+ } else {
+ newKey.AppendLiteral("~.:");
+ }
+ newKey.AppendInt(port);
+ newKey.AppendLiteral("/[");
+ nsAutoCString suffix;
+ ci->GetOriginAttributes().CreateSuffix(suffix);
+ newKey.Append(suffix);
+ newKey.AppendLiteral("]viaORIGIN.FRAME");
+}
+
+HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnection(
+ ConnectionEntry* ent, bool justKidding, bool aNoHttp2, bool aNoHttp3) {
+ MOZ_ASSERT(!aNoHttp2 || !aNoHttp3);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(ent->mConnInfo);
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+ LOG(("FindCoalescableConnection %s\n", ci->HashKey().get()));
+ // First try and look it up by origin frame
+ nsCString newKey;
+ BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
+ HttpConnectionBase* conn = FindCoalescableConnectionByHashKey(
+ ent, newKey, justKidding, aNoHttp2, aNoHttp3);
+ if (conn) {
+ LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
+ ci->HashKey().get(), conn, newKey.get()));
+ return conn;
+ }
+
+ // now check for DNS based keys
+ // deleted conns (null weak pointers) are removed from list
+ uint32_t keyLen = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < keyLen; ++i) {
+ conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i],
+ justKidding, aNoHttp2, aNoHttp3);
+ if (conn) {
+ LOG(("FindCoalescableConnection(%s) match conn %p on dns key %s\n",
+ ci->HashKey().get(), conn, ent->mCoalescingKeys[i].get()));
+ return conn;
+ }
+ }
+
+ LOG(("FindCoalescableConnection(%s) no matching conn\n",
+ ci->HashKey().get()));
+ return nullptr;
+}
+
+void nsHttpConnectionMgr::UpdateCoalescingForNewConn(
+ HttpConnectionBase* newConn, ConnectionEntry* ent, bool aNoHttp3) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(newConn);
+ MOZ_ASSERT(newConn->ConnectionInfo());
+ MOZ_ASSERT(ent);
+ MOZ_ASSERT(mCT.GetWeak(newConn->ConnectionInfo()->HashKey()) == ent);
+ LOG(("UpdateCoalescingForNewConn newConn=%p aNoHttp3=%d", newConn, aNoHttp3));
+ if (newConn->ConnectionInfo()->GetWebTransport()) {
+ LOG(("Don't coalesce a WebTransport conn %p", newConn));
+ // TODO: implement this properly in bug 1815735.
+ return;
+ }
+
+ HttpConnectionBase* existingConn =
+ FindCoalescableConnection(ent, true, false, false);
+ if (existingConn) {
+ // Prefer http3 connection, but allow an HTTP/2 connection if it is used for
+ // WebSocket.
+ if (newConn->UsingHttp3() && existingConn->UsingSpdy()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(existingConn);
+ if (connTCP && !connTCP->IsForWebSocket()) {
+ LOG(
+ ("UpdateCoalescingForNewConn() found existing active H2 conn that "
+ "could have served newConn, but new connection is H3, therefore "
+ "close the H2 conncetion"));
+ existingConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING);
+ existingConn->DontReuse();
+ }
+ } else if (existingConn->UsingHttp3() && newConn->UsingSpdy()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(newConn);
+ if (connTCP && !connTCP->IsForWebSocket() && !aNoHttp3) {
+ LOG(
+ ("UpdateCoalescingForNewConn() found existing active H3 conn that "
+ "could have served H2 newConn graceful close of newConn=%p to "
+ "migrate to existingConn %p\n",
+ newConn, existingConn));
+ existingConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
+ newConn->DontReuse();
+ return;
+ }
+ } else {
+ LOG(
+ ("UpdateCoalescingForNewConn() found existing active conn that could "
+ "have served newConn "
+ "graceful close of newConn=%p to migrate to existingConn %p\n",
+ newConn, existingConn));
+ existingConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
+ newConn->DontReuse();
+ return;
+ }
+ }
+
+ // This connection might go into the mCoalescingHash for new transactions to
+ // be coalesced onto if it can accept new transactions
+ if (!newConn->CanDirectlyActivate()) {
+ return;
+ }
+
+ uint32_t keyLen = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < keyLen; ++i) {
+ LOG((
+ "UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n",
+ newConn, newConn->ConnectionInfo()->HashKey().get(),
+ ent->mCoalescingKeys[i].get()));
+
+ mCoalescingHash
+ .LookupOrInsertWith(
+ ent->mCoalescingKeys[i],
+ [] {
+ LOG(("UpdateCoalescingForNewConn() need new list element\n"));
+ return MakeUnique<nsTArray<nsWeakPtr>>(1);
+ })
+ ->AppendElement(do_GetWeakReference(
+ static_cast<nsISupportsWeakReference*>(newConn)));
+ }
+
+ // this is a new connection that can be coalesced onto. hooray!
+ // if there are other connection to this entry (e.g.
+ // some could still be handshaking, shutting down, etc..) then close
+ // them down after any transactions that are on them are complete.
+ // This probably happened due to the parallel connection algorithm
+ // that is used only before the host is known to speak h2.
+ ent->MakeAllDontReuseExcept(newConn);
+}
+
+// This function lets a connection, after completing the NPN phase,
+// report whether or not it is using spdy through the usingSpdy
+// argument. It would not be necessary if NPN were driven out of
+// the connection manager. The connection entry associated with the
+// connection is then updated to indicate whether or not we want to use
+// spdy with that host and update the coalescing hash
+// entries used for de-sharding hostsnames.
+void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection* conn,
+ bool usingSpdy,
+ bool disallowHttp3) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!conn->ConnectionInfo()) {
+ return;
+ }
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+ if (!ent || !usingSpdy) {
+ return;
+ }
+
+ ent->mUsingSpdy = true;
+ mNumSpdyHttp3ActiveConns++;
+
+ // adjust timeout timer
+ uint32_t ttl = conn->TimeToLive();
+ uint64_t timeOfExpire = NowInSeconds() + ttl;
+ if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(ttl);
+ }
+
+ UpdateCoalescingForNewConn(conn, ent, disallowHttp3);
+
+ nsresult rv = ProcessPendingQ(ent->mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportSpdyConnection conn=%p ent=%p "
+ "failed to process pending queue (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+ rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportSpdyConnection conn=%p ent=%p "
+ "failed to post event (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+}
+
+void nsHttpConnectionMgr::ReportHttp3Connection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!conn->ConnectionInfo()) {
+ return;
+ }
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+ if (!ent) {
+ return;
+ }
+
+ mNumSpdyHttp3ActiveConns++;
+
+ UpdateCoalescingForNewConn(conn, ent, false);
+ nsresult rv = ProcessPendingQ(ent->mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportHttp3Connection conn=%p ent=%p "
+ "failed to process pending queue (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+ rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportHttp3Connection conn=%p ent=%p "
+ "failed to post event (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool nsHttpConnectionMgr::DispatchPendingQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent,
+ bool considerAll) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ PendingTransactionInfo* pendingTransInfo = nullptr;
+ nsresult rv;
+ bool dispatchedSuccessfully = false;
+
+ // if !considerAll iterate the pending list until one is dispatched
+ // successfully. Keep iterating afterwards only until a transaction fails to
+ // dispatch. if considerAll == true then try and dispatch all items.
+ for (uint32_t i = 0; i < pendingQ.Length();) {
+ pendingTransInfo = pendingQ[i];
+
+ bool alreadyDnsAndConnectSocketOrWaitingForTLS =
+ pendingTransInfo->IsAlreadyClaimedInitializingConn();
+
+ rv = TryDispatchTransaction(ent, alreadyDnsAndConnectSocketOrWaitingForTLS,
+ pendingTransInfo);
+ if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" dispatching pending transaction...\n"));
+ } else {
+ LOG(
+ (" removing pending transaction based on "
+ "TryDispatchTransaction returning hard error %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ }
+ if (pendingQ.RemoveElement(pendingTransInfo)) {
+ // pendingTransInfo is now potentially destroyed
+ dispatchedSuccessfully = true;
+ continue; // dont ++i as we just made the array shorter
+ }
+
+ LOG((" transaction not found in pending queue\n"));
+ }
+
+ if (dispatchedSuccessfully && !considerAll) break;
+
+ ++i;
+ }
+ return dispatchedSuccessfully;
+}
+
+uint32_t nsHttpConnectionMgr::MaxPersistConnections(
+ ConnectionEntry* ent) const {
+ if (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) {
+ return static_cast<uint32_t>(mMaxPersistConnsPerProxy);
+ }
+
+ return static_cast<uint32_t>(mMaxPersistConnsPerHost);
+}
+
+void nsHttpConnectionMgr::PreparePendingQForDispatching(
+ ConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ bool considerAll) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ pendingQ.Clear();
+
+ uint32_t totalCount = ent->TotalActiveConnections();
+ uint32_t maxPersistConns = MaxPersistConnections(ent);
+ uint32_t availableConnections =
+ maxPersistConns > totalCount ? maxPersistConns - totalCount : 0;
+
+ // No need to try dispatching if we reach the active connection limit.
+ if (!availableConnections) {
+ return;
+ }
+
+ // Only have to get transactions from the queue whose window id is 0.
+ if (!gHttpHandler->ActiveTabPriority()) {
+ ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections);
+ return;
+ }
+
+ uint32_t maxFocusedWindowConnections =
+ availableConnections * gHttpHandler->FocusedWindowTransactionRatio();
+ MOZ_ASSERT(maxFocusedWindowConnections < availableConnections);
+
+ if (!maxFocusedWindowConnections) {
+ maxFocusedWindowConnections = 1;
+ }
+
+ // Only need to dispatch transactions for either focused or
+ // non-focused window because considerAll is false.
+ if (!considerAll) {
+ ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
+ maxFocusedWindowConnections);
+
+ if (pendingQ.IsEmpty()) {
+ ent->AppendPendingQForNonFocusedWindows(mCurrentBrowserId, pendingQ,
+ availableConnections);
+ }
+ return;
+ }
+
+ uint32_t maxNonFocusedWindowConnections =
+ availableConnections - maxFocusedWindowConnections;
+ nsTArray<RefPtr<PendingTransactionInfo>> remainingPendingQ;
+
+ ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
+ maxFocusedWindowConnections);
+
+ if (maxNonFocusedWindowConnections) {
+ ent->AppendPendingQForNonFocusedWindows(
+ mCurrentBrowserId, remainingPendingQ, maxNonFocusedWindowConnections);
+ }
+
+ // If the slots for either focused or non-focused window are not filled up
+ // to the availability, try to use the remaining available connections
+ // for the other slot (with preference for the focused window).
+ if (remainingPendingQ.Length() < maxNonFocusedWindowConnections) {
+ ent->AppendPendingQForFocusedWindow(
+ mCurrentBrowserId, pendingQ,
+ maxNonFocusedWindowConnections - remainingPendingQ.Length());
+ } else if (pendingQ.Length() < maxFocusedWindowConnections) {
+ ent->AppendPendingQForNonFocusedWindows(
+ mCurrentBrowserId, remainingPendingQ,
+ maxFocusedWindowConnections - pendingQ.Length());
+ }
+
+ MOZ_ASSERT(pendingQ.Length() + remainingPendingQ.Length() <=
+ availableConnections);
+
+ LOG(
+ ("nsHttpConnectionMgr::PreparePendingQForDispatching "
+ "focused window pendingQ.Length()=%zu"
+ ", remainingPendingQ.Length()=%zu\n",
+ pendingQ.Length(), remainingPendingQ.Length()));
+
+ // Append elements in |remainingPendingQ| to |pendingQ|. The order in
+ // |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans].
+ pendingQ.AppendElements(std::move(remainingPendingQ));
+}
+
+bool nsHttpConnectionMgr::ProcessPendingQForEntry(ConnectionEntry* ent,
+ bool considerAll) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(
+ ("nsHttpConnectionMgr::ProcessPendingQForEntry "
+ "[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu"
+ " queued=%zu]\n",
+ ent->mConnInfo->HashKey().get(), ent, ent->ActiveConnsLength(),
+ ent->IdleConnectionsLength(), ent->UrgentStartQueueLength(),
+ ent->PendingQueueLength()));
+
+ if (LOG_ENABLED()) {
+ ent->PrintPendingQ();
+ ent->LogConnections();
+ }
+
+ if (!ent->PendingQueueLength() && !ent->UrgentStartQueueLength()) {
+ return false;
+ }
+ ProcessSpdyPendingQ(ent);
+
+ bool dispatchedSuccessfully = false;
+
+ if (ent->UrgentStartQueueLength()) {
+ nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
+ ent->AppendPendingUrgentStartQ(pendingQ);
+ dispatchedSuccessfully = DispatchPendingQ(pendingQ, ent, considerAll);
+ for (const auto& transactionInfo : Reversed(pendingQ)) {
+ ent->InsertTransaction(transactionInfo);
+ }
+ }
+
+ if (dispatchedSuccessfully && !considerAll) {
+ return dispatchedSuccessfully;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
+ PreparePendingQForDispatching(ent, pendingQ, considerAll);
+
+ // The only case that |pendingQ| is empty is when there is no
+ // connection available for dispatching.
+ if (pendingQ.IsEmpty()) {
+ return dispatchedSuccessfully;
+ }
+
+ dispatchedSuccessfully |= DispatchPendingQ(pendingQ, ent, considerAll);
+
+ // Put the leftovers into connection entry, in the same order as they
+ // were before to keep the natural ordering.
+ for (const auto& transactionInfo : Reversed(pendingQ)) {
+ ent->InsertTransaction(transactionInfo, true);
+ }
+
+ // Only remove empty pendingQ when considerAll is true.
+ if (considerAll) {
+ ent->RemoveEmptyPendingQ();
+ }
+
+ return dispatchedSuccessfully;
+}
+
+bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (ent) return ProcessPendingQForEntry(ent, false);
+ return false;
+}
+
+// we're at the active connection limit if any one of the following conditions
+// is true:
+// (1) at max-connections
+// (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
+// (3) keep-alive disabled and at max-connections-per-server
+bool nsHttpConnectionMgr::AtActiveConnectionLimit(ConnectionEntry* ent,
+ uint32_t caps) {
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+ uint32_t totalCount = ent->TotalActiveConnections();
+
+ if (ci->IsHttp3()) {
+ if (ci->GetWebTransport()) {
+ // TODO: implement this properly in bug 1815735.
+ return false;
+ }
+ return totalCount > 0;
+ }
+
+ uint32_t maxPersistConns = MaxPersistConnections(ent);
+
+ LOG(
+ ("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x,"
+ "totalCount=%u, maxPersistConns=%u]\n",
+ ci->HashKey().get(), caps, totalCount, maxPersistConns));
+
+ if (caps & NS_HTTP_URGENT_START) {
+ if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) {
+ LOG((
+ "The number of total connections are greater than or equal to sum of "
+ "max urgent-start queue length and the number of max persistent "
+ "connections.\n"));
+ return true;
+ }
+ return false;
+ }
+
+ // update maxconns if potentially limited by the max socket count
+ // this requires a dynamic reduction in the max socket count to a point
+ // lower than the max-connections pref.
+ uint32_t maxSocketCount = gHttpHandler->MaxSocketCount();
+ if (mMaxConns > maxSocketCount) {
+ mMaxConns = maxSocketCount;
+ LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this,
+ mMaxConns));
+ }
+
+ // If there are more active connections than the global limit, then we're
+ // done. Purging idle connections won't get us below it.
+ if (mNumActiveConns >= mMaxConns) {
+ LOG((" num active conns == max conns\n"));
+ return true;
+ }
+
+ bool result = (totalCount >= maxPersistConns);
+ LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false"));
+ return result;
+}
+
+// returns NS_OK if a connection was started
+// return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
+// ephemeral limits
+// returns other NS_ERROR on hard failure conditions
+nsresult nsHttpConnectionMgr::MakeNewConnection(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo) {
+ LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent,
+ pendingTransInfo->Transaction()));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (ent->FindConnToClaim(pendingTransInfo)) {
+ return NS_OK;
+ }
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ // If this host is trying to negotiate a SPDY session right now,
+ // don't create any new connections until the result of the
+ // negotiation is known.
+ if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
+ (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && ent->RestrictConnections()) {
+ LOG(
+ ("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
+ "Not Available Due to RestrictConnections()\n",
+ ent->mConnInfo->HashKey().get()));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We need to make a new connection. If that is going to exceed the
+ // global connection limit then try and free up some room by closing
+ // an idle connection to another host. We know it won't select "ent"
+ // because we have already determined there are no idle connections
+ // to our destination
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close them
+ // regardless of their TTL.
+ auto iter = mCT.ConstIter();
+ while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns && !iter.Done()) {
+ RefPtr<ConnectionEntry> entry = iter.Data();
+ entry->CloseIdleConnections((mNumIdleConns + mNumActiveConns + 1) -
+ mMaxConns);
+ iter.Next();
+ }
+ }
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumActiveConns &&
+ StaticPrefs::network_http_http2_enabled()) {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close any spdy
+ // ASAP.
+ for (const RefPtr<ConnectionEntry>& entry : mCT.Values()) {
+ while (entry->MakeFirstActiveSpdyConnDontReuse()) {
+ // Stop on <= (particularly =) because this dontreuse
+ // causes async close.
+ if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) {
+ goto outerLoopEnd;
+ }
+ }
+ }
+ outerLoopEnd:;
+ }
+
+ if (AtActiveConnectionLimit(ent, trans->Caps())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = ent->CreateDnsAndConnectSocket(
+ trans, trans->Caps(), false, false,
+ trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart, true,
+ pendingTransInfo);
+ if (NS_FAILED(rv)) {
+ /* hard failure */
+ LOG(
+ ("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
+ "CreateDnsAndConnectSocket() hard failure.\n",
+ ent->mConnInfo->HashKey().get(), trans));
+ trans->Close(rv);
+ if (rv == NS_ERROR_NOT_AVAILABLE) rv = NS_ERROR_FAILURE;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// returns OK if a connection is found for the transaction
+// and the transaction is started.
+// returns ERROR_NOT_AVAILABLE if no connection can be found and it
+// should be queued until circumstances change
+// returns other ERROR when transaction has a hard failure and should
+// not remain in the pending queue
+nsresult nsHttpConnectionMgr::TryDispatchTransaction(
+ ConnectionEntry* ent, bool onlyReusedConnection,
+ PendingTransactionInfo* pendingTransInfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ LOG(
+ ("nsHttpConnectionMgr::TryDispatchTransaction without conn "
+ "[trans=%p ci=%p ci=%s caps=%x onlyreused=%d active=%zu "
+ "idle=%zu]\n",
+ trans, ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
+ uint32_t(trans->Caps()), onlyReusedConnection, ent->ActiveConnsLength(),
+ ent->IdleConnectionsLength()));
+
+ uint32_t caps = trans->Caps();
+
+ // 0 - If this should use spdy then dispatch it post haste.
+ // 1 - If there is connection pressure then see if we can pipeline this on
+ // a connection of a matching type instead of using a new conn
+ // 2 - If there is an idle connection, use it!
+ // 3 - if class == reval or script and there is an open conn of that type
+ // then pipeline onto shortest pipeline of that class if limits allow
+ // 4 - If we aren't up against our connection limit,
+ // then open a new one
+ // 5 - Try a pipeline if we haven't already - this will be unusual because
+ // it implies a low connection pressure situation where
+ // MakeNewConnection() failed.. that is possible, but unlikely, due to
+ // global limits
+ // 6 - no connection is available - queue it
+
+ RefPtr<HttpConnectionBase> unusedSpdyPersistentConnection;
+
+ // step 0
+ // look for existing spdy connection - that's always best because it is
+ // essentially pipelining without head of line blocking
+
+ RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(
+ ent,
+ (!StaticPrefs::network_http_http2_enabled() ||
+ (caps & NS_HTTP_DISALLOW_SPDY)),
+ (!nsHttpHandler::IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3)));
+ if (conn) {
+ LOG(("TryingDispatchTransaction: an active h2 connection exists"));
+ WebSocketSupport wsSupp = conn->GetWebSocketSupport();
+ if (trans->IsWebsocketUpgrade()) {
+ LOG(("TryingDispatchTransaction: this is a websocket upgrade"));
+ if (wsSupp == WebSocketSupport::NO_SUPPORT) {
+ LOG((
+ "TryingDispatchTransaction: no support for websockets over Http2"));
+ // This is a websocket transaction and we already have a h2 connection
+ // that do not support websockets, we should disable h2 for this
+ // transaction.
+ trans->DisableSpdy();
+ caps &= NS_HTTP_DISALLOW_SPDY;
+ trans->MakeSticky();
+ } else if (wsSupp == WebSocketSupport::SUPPORTED) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ LOG(("TryingDispatchTransaction: websockets over Http2"));
+
+ // No limit for number of websockets, dispatch transaction to the tunnel
+ RefPtr<nsHttpConnection> connToTunnel;
+ connTCP->CreateTunnelStream(trans, getter_AddRefs(connToTunnel), true);
+ ent->InsertIntoH2WebsocketConns(connToTunnel);
+ trans->SetConnection(nullptr);
+ connToTunnel->SetInSpdyTunnel(); // tells conn it is already in tunnel
+ trans->SetIsHttp2Websocket(true);
+ nsresult rv = DispatchTransaction(ent, trans, connToTunnel);
+ // need to undo NonSticky bypass for transaction reset to continue
+ // for correct websocket upgrade handling
+ trans->MakeSticky();
+ return rv;
+ } else {
+ // if we aren't sure that websockets are supported yet or we are
+ // already at the connection limit then we queue the transaction
+ LOG(("TryingDispatchTransaction: unsure if websockets over Http2"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ } else {
+ if ((caps & NS_HTTP_ALLOW_KEEPALIVE) ||
+ (caps & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE) ||
+ !conn->IsExperienced()) {
+ LOG((" dispatch to spdy: [conn=%p]\n", conn.get()));
+ trans->RemoveDispatchedAsBlocking(); /* just in case */
+ nsresult rv = DispatchTransaction(ent, trans, conn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ unusedSpdyPersistentConnection = conn;
+ }
+ }
+
+ // If this is not a blocking transaction and the request context for it is
+ // currently processing one or more blocking transactions then we
+ // need to just leave it in the queue until those are complete unless it is
+ // explicitly marked as unblocked.
+ if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
+ if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
+ nsIRequestContext* requestContext = trans->RequestContext();
+ if (requestContext) {
+ uint32_t blockers = 0;
+ if (NS_SUCCEEDED(
+ requestContext->GetBlockingTransactionCount(&blockers)) &&
+ blockers) {
+ // need to wait for blockers to clear
+ LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
+ requestContext, trans, blockers));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ }
+ } else {
+ // Mark the transaction and its load group as blocking right now to prevent
+ // other transactions from being reordered in the queue due to slow syns.
+ trans->DispatchedAsBlocking();
+ }
+
+ // step 1
+ // If connection pressure, then we want to favor pipelining of any kind
+ // h1 pipelining has been removed
+
+ // Subject most transactions at high parallelism to rate pacing.
+ // It will only be actually submitted to the
+ // token bucket once, and if possible it is granted admission synchronously.
+ // It is important to leave a transaction in the pending queue when blocked by
+ // pacing so it can be found on cancel if necessary.
+ // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
+ // limited.
+ if (gHttpHandler->UseRequestTokenBucket()) {
+ // submit even whitelisted transactions to the token bucket though they will
+ // not be slowed by it
+ bool runNow = trans->TryToRunPacedRequest();
+ if (!runNow) {
+ if ((mNumActiveConns - mNumSpdyHttp3ActiveConns) <=
+ gHttpHandler->RequestTokenBucketMinParallelism()) {
+ runNow = true; // white list it
+ } else if (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
+ runNow = true; // white list it
+ }
+ }
+ if (!runNow) {
+ LOG((" blocked due to rate pacing trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ // step 2
+ // consider an idle persistent connection
+ bool idleConnsAllUrgent = false;
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ nsresult rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, true,
+ &idleConnsAllUrgent);
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" dispatched step 2 (idle) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 3
+ // consider pipelining scripts and revalidations
+ // h1 pipelining has been removed
+
+ // Don't dispatch if this transaction is waiting for HTTPS RR.
+ // This usually happens when the pref "network.dns.force_waiting_https_rr" is
+ // true or when echConfig is enabled.
+ if (trans->WaitingForHTTPSRR()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // step 4
+ if (!onlyReusedConnection) {
+ nsresult rv = MakeNewConnection(ent, pendingTransInfo);
+ if (NS_SUCCEEDED(rv)) {
+ // this function returns NOT_AVAILABLE for asynchronous connects
+ LOG((" dispatched step 4 (async new conn) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // not available return codes should try next step as they are
+ // not hard errors. Other codes should stop now
+ LOG((" failed step 4 (%" PRIx32 ") trans=%p\n",
+ static_cast<uint32_t>(rv), trans));
+ return rv;
+ }
+
+ // repeat step 2 when there are only idle connections and all are urgent,
+ // don't respect urgency so that non-urgent transaction will be allowed
+ // to dispatch on an urgent-start-only marked connection to avoid
+ // dispatch deadlocks
+ if (!(trans->GetClassOfService().Flags() &
+ nsIClassOfService::UrgentStart) &&
+ idleConnsAllUrgent &&
+ ent->ActiveConnsLength() < MaxPersistConnections(ent)) {
+ rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, false);
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" dispatched step 2a (idle, reuse urgent) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+ }
+
+ // step 5
+ // previously pipelined anything here if allowed but h1 pipelining has been
+ // removed
+
+ // step 6
+ if (unusedSpdyPersistentConnection) {
+ // to avoid deadlocks, we need to throw away this perfectly valid SPDY
+ // connection to make room for a new one that can service a no KEEPALIVE
+ // request
+ unusedSpdyPersistentConnection->DontReuse();
+ }
+
+ LOG((" not dispatched (queued) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE; /* queue it */
+}
+
+nsresult nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo,
+ bool respectUrgency, bool* allUrgent) {
+ bool onlyUrgent = !!ent->IdleConnectionsLength();
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+ bool urgentTrans =
+ trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart;
+
+ LOG(
+ ("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, "
+ "trans=%p, urgent=%d",
+ ent, trans, urgentTrans));
+
+ RefPtr<nsHttpConnection> conn =
+ ent->GetIdleConnection(respectUrgency, urgentTrans, &onlyUrgent);
+
+ if (allUrgent) {
+ *allUrgent = onlyUrgent;
+ }
+
+ if (conn) {
+ // This will update the class of the connection to be the class of
+ // the transaction dispatched on it.
+ ent->InsertIntoActiveConns(conn);
+ nsresult rv = DispatchTransaction(ent, trans, conn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpConnectionMgr::DispatchTransaction(ConnectionEntry* ent,
+ nsHttpTransaction* trans,
+ HttpConnectionBase* conn) {
+ uint32_t caps = trans->Caps();
+ int32_t priority = trans->Priority();
+ nsresult rv;
+
+ LOG(
+ ("nsHttpConnectionMgr::DispatchTransaction "
+ "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d isHttp2=%d "
+ "isHttp3=%d]\n",
+ ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority,
+ conn->UsingSpdy(), conn->UsingHttp3()));
+
+ // It is possible for a rate-paced transaction to be dispatched independent
+ // of the token bucket when the amount of parallelization has changed or
+ // when a muxed connection (e.g. h2) becomes available.
+ trans->CancelPacing(NS_OK);
+
+ TimeStamp now = TimeStamp::Now();
+ auto recordPendingTimeForHTTPSRR = [&](nsCString& aKey) {
+ uint32_t stage = trans->HTTPSSVCReceivedStage();
+ TimeDuration elapsed = now - trans->GetPendingTime();
+ if (HTTPS_RR_IS_USED(stage)) {
+ glean::networking::transaction_wait_time_https_rr.AccumulateRawDuration(
+ elapsed);
+
+ } else {
+ glean::networking::transaction_wait_time.AccumulateRawDuration(elapsed);
+ }
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpTransactionWaitTime,
+ elapsed);
+ };
+
+ nsAutoCString httpVersionkey("h1"_ns);
+ if (conn->UsingSpdy() || conn->UsingHttp3()) {
+ LOG(
+ ("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
+ "Connection host = %s\n",
+ trans->ConnectionInfo()->Origin(), conn->ConnectionInfo()->Origin()));
+ rv = conn->Activate(trans, caps, priority);
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ if (conn->UsingSpdy()) {
+ httpVersionkey = "h2"_ns;
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
+ trans->GetPendingTime(), now);
+ } else {
+ httpVersionkey = "h3"_ns;
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP3,
+ trans->GetPendingTime(), now);
+ }
+ recordPendingTimeForHTTPSRR(httpVersionkey);
+ trans->SetPendingTime(false);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(conn && !conn->Transaction(),
+ "DispatchTranaction() on non spdy active connection");
+
+ rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
+
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), now);
+ recordPendingTimeForHTTPSRR(httpVersionkey);
+ trans->SetPendingTime(false);
+ }
+ return rv;
+}
+
+// Use this method for dispatching nsAHttpTransction's. It can only safely be
+// used upon first use of a connection when NPN has not negotiated SPDY vs
+// HTTP/1 yet as multiplexing onto an existing SPDY session requires a
+// concrete nsHttpTransaction
+nsresult nsHttpConnectionMgr::DispatchAbstractTransaction(
+ ConnectionEntry* ent, nsAHttpTransaction* aTrans, uint32_t caps,
+ HttpConnectionBase* conn, int32_t priority) {
+ MOZ_ASSERT(ent);
+
+ nsresult rv;
+ MOZ_ASSERT(!conn->UsingSpdy(),
+ "Spdy Must Not Use DispatchAbstractTransaction");
+ LOG(
+ ("nsHttpConnectionMgr::DispatchAbstractTransaction "
+ "[ci=%s trans=%p caps=%x conn=%p]\n",
+ ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
+
+ RefPtr<nsAHttpTransaction> transaction(aTrans);
+ RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
+
+ // give the transaction the indirect reference to the connection.
+ transaction->SetConnection(handle);
+
+ rv = conn->Activate(transaction, caps, priority);
+ if (NS_FAILED(rv)) {
+ LOG((" conn->Activate failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ DebugOnly<nsresult> rv_remove = ent->RemoveActiveConnection(conn);
+ MOZ_ASSERT(NS_SUCCEEDED(rv_remove));
+
+ // sever back references to connection, and do so without triggering
+ // a call to ReclaimConnection ;-)
+ transaction->SetConnection(nullptr);
+ handle->Reset(); // destroy the connection
+ }
+
+ return rv;
+}
+
+void nsHttpConnectionMgr::ReportProxyTelemetry(ConnectionEntry* ent) {
+ enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3, PROXY_HTTPS = 4 };
+
+ if (!ent->mConnInfo->UsingProxy()) {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
+ } else if (ent->mConnInfo->UsingHttpsProxy()) {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTPS);
+ } else if (ent->mConnInfo->UsingHttpProxy()) {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS);
+ }
+}
+
+nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // since "adds" and "cancels" are processed asynchronously and because
+ // various events might trigger an "add" directly on the socket thread,
+ // we must take care to avoid dispatching a transaction that has already
+ // been canceled (see bug 190001).
+ if (NS_FAILED(trans->Status())) {
+ LOG((" transaction was canceled... dropping event!\n"));
+ return NS_OK;
+ }
+
+ // Make sure a transaction is not in a pending queue.
+ CheckTransInPendingQueue(trans);
+
+ trans->SetPendingTime();
+
+ RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper =
+ trans->GetPushedStream();
+ if (pushedStreamWrapper) {
+ Http2PushedStream* pushedStream = pushedStreamWrapper->GetStream();
+ if (pushedStream) {
+ RefPtr<Http2Session> session = pushedStream->Session();
+ LOG((" ProcessNewTransaction %p tied to h2 session push %p\n", trans,
+ session.get()));
+ return session->AddStream(trans, trans->Priority(), nullptr)
+ ? NS_OK
+ : NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsresult rv = NS_OK;
+ nsHttpConnectionInfo* ci = trans->ConnectionInfo();
+ MOZ_ASSERT(ci);
+ MOZ_ASSERT(!ci->IsHttp3() || !(trans->Caps() & NS_HTTP_DISALLOW_HTTP3));
+
+ bool isWildcard = false;
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ ci, trans->Caps() & NS_HTTP_DISALLOW_HTTP2_PROXY,
+ trans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ trans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard);
+ MOZ_ASSERT(ent);
+
+ if (gHttpHandler->EchConfigEnabled(ci->IsHttp3())) {
+ ent->MaybeUpdateEchConfig(ci);
+ }
+
+ ReportProxyTelemetry(ent);
+
+ // Check if the transaction already has a sticky reference to a connection.
+ // If so, then we can just use it directly by transferring its reference
+ // to the new connection variable instead of searching for a new one
+
+ nsAHttpConnection* wrappedConnection = trans->Connection();
+ RefPtr<HttpConnectionBase> conn;
+ RefPtr<PendingTransactionInfo> pendingTransInfo;
+ if (wrappedConnection) conn = wrappedConnection->TakeHttpConnection();
+
+ if (conn) {
+ MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
+ LOG(
+ ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p\n",
+ trans, conn.get()));
+
+ if (!ent->IsInActiveConns(conn)) {
+ LOG(
+ ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p needs to go on the active list\n",
+ trans, conn.get()));
+
+ // make sure it isn't on the idle list - we expect this to be an
+ // unknown fresh connection
+ MOZ_ASSERT(!ent->IsInIdleConnections(conn));
+ MOZ_ASSERT(!conn->IsExperienced());
+
+ ent->InsertIntoActiveConns(conn); // make it active
+ }
+
+ trans->SetConnection(nullptr);
+ rv = DispatchTransaction(ent, trans, conn);
+ } else if (isWildcard) {
+ // We have a HTTP/2 session to the proxy, create a new tunneled
+ // connection.
+ RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(ent, false, true);
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (ci->UsingHttpsProxy() && ci->UsingConnect()) {
+ LOG(("About to create new tunnel conn from [%p]", connTCP.get()));
+ ConnectionEntry* specificEnt = mCT.GetWeak(ci->HashKey());
+
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(ci->Clone());
+ specificEnt = new ConnectionEntry(clone);
+ mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
+ }
+
+ ent = specificEnt;
+ bool atLimit = AtActiveConnectionLimit(ent, trans->Caps());
+ if (atLimit) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ RefPtr<nsHttpConnection> newTunnel;
+ connTCP->CreateTunnelStream(trans, getter_AddRefs(newTunnel));
+
+ ent->InsertIntoActiveConns(newTunnel);
+ trans->SetConnection(nullptr);
+ newTunnel->SetInSpdyTunnel();
+ rv = DispatchTransaction(ent, trans, newTunnel);
+ // need to undo the bypass for transaction reset for proxy
+ trans->MakeNonRestartable();
+ }
+ } else {
+ rv = DispatchTransaction(ent, trans, connTCP);
+ }
+ } else {
+ if (!ent->AllowHttp2()) {
+ trans->DisableSpdy();
+ }
+ pendingTransInfo = new PendingTransactionInfo(trans);
+ rv = TryDispatchTransaction(ent, false, pendingTransInfo);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
+ return rv;
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ if (!pendingTransInfo) {
+ pendingTransInfo = new PendingTransactionInfo(trans);
+ }
+
+ ent->InsertTransaction(pendingTransInfo);
+ return NS_OK;
+ }
+
+ LOG((" ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n", trans,
+ static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+void nsHttpConnectionMgr::IncrementActiveConnCount() {
+ mNumActiveConns++;
+ ActivateTimeoutTick();
+}
+
+void nsHttpConnectionMgr::DecrementActiveConnCount(HttpConnectionBase* conn) {
+ MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
+ if (mNumActiveConns > 0) {
+ mNumActiveConns--;
+ }
+
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (!connTCP || connTCP->EverUsedSpdy()) mNumSpdyHttp3ActiveConns--;
+ ConditionallyStopTimeoutTick();
+}
+
+void nsHttpConnectionMgr::StartedConnect() {
+ mNumActiveConns++;
+ ActivateTimeoutTick(); // likely disabled by RecvdConnect()
+}
+
+void nsHttpConnectionMgr::RecvdConnect() {
+ MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
+ if (mNumActiveConns > 0) {
+ mNumActiveConns--;
+ }
+
+ ConditionallyStopTimeoutTick();
+}
+
+void nsHttpConnectionMgr::DispatchSpdyPendingQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent,
+ HttpConnectionBase* connH2, HttpConnectionBase* connH3) {
+ if (pendingQ.Length() == 0) {
+ return;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> leftovers;
+ uint32_t index;
+ // Dispatch all the transactions we can
+ for (index = 0; index < pendingQ.Length() &&
+ ((connH3 && connH3->CanDirectlyActivate()) ||
+ (connH2 && connH2->CanDirectlyActivate()));
+ ++index) {
+ PendingTransactionInfo* pendingTransInfo = pendingQ[index];
+
+ // We can not dispatch NS_HTTP_ALLOW_KEEPALIVE transactions.
+ if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
+ leftovers.AppendElement(pendingTransInfo);
+ continue;
+ }
+
+ // Try dispatching on HTTP3 first
+ HttpConnectionBase* conn = nullptr;
+ if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_DISALLOW_HTTP3) &&
+ connH3 && connH3->CanDirectlyActivate()) {
+ conn = connH3;
+ } else if (!(pendingTransInfo->Transaction()->Caps() &
+ NS_HTTP_DISALLOW_SPDY) &&
+ connH2 && connH2->CanDirectlyActivate()) {
+ conn = connH2;
+ } else {
+ leftovers.AppendElement(pendingTransInfo);
+ continue;
+ }
+
+ nsresult rv =
+ DispatchTransaction(ent, pendingTransInfo->Transaction(), conn);
+ if (NS_FAILED(rv)) {
+ // this cannot happen, but if due to some bug it does then
+ // close the transaction
+ MOZ_ASSERT(false, "Dispatch SPDY Transaction");
+ LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
+ pendingTransInfo->Transaction()));
+ pendingTransInfo->Transaction()->Close(rv);
+ }
+ }
+
+ // Slurp up the rest of the pending queue into our leftovers bucket (we
+ // might have some left if conn->CanDirectlyActivate returned false)
+ for (; index < pendingQ.Length(); ++index) {
+ PendingTransactionInfo* pendingTransInfo = pendingQ[index];
+ leftovers.AppendElement(pendingTransInfo);
+ }
+
+ // Put the leftovers back in the pending queue and get rid of the
+ // transactions we dispatched
+ pendingQ = std::move(leftovers);
+}
+
+// This function tries to dispatch the pending h2 or h3 transactions on
+// the connection entry sent in as an argument. It will do so on the
+// active h2 or h3 connection either in that same entry or from the
+// coalescing hash table
+void nsHttpConnectionMgr::ProcessSpdyPendingQ(ConnectionEntry* ent) {
+ // Look for one HTTP2 and one HTTP3 connection.
+ // We may have transactions that have NS_HTTP_DISALLOW_SPDY/HTTP3 set
+ // and we may need an alternative.
+ HttpConnectionBase* connH3 = GetH2orH3ActiveConn(ent, true, false);
+ HttpConnectionBase* connH2 = GetH2orH3ActiveConn(ent, false, true);
+ if ((!connH3 || !connH3->CanDirectlyActivate()) &&
+ (!connH2 || !connH2->CanDirectlyActivate())) {
+ return;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> urgentQ;
+ ent->AppendPendingUrgentStartQ(urgentQ);
+ DispatchSpdyPendingQ(urgentQ, ent, connH2, connH3);
+ for (const auto& transactionInfo : Reversed(urgentQ)) {
+ ent->InsertTransaction(transactionInfo);
+ }
+
+ if ((!connH3 || !connH3->CanDirectlyActivate()) &&
+ (!connH2 || !connH2->CanDirectlyActivate())) {
+ return;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
+ // XXX Get all transactions for SPDY currently.
+ ent->AppendPendingQForNonFocusedWindows(0, pendingQ);
+ DispatchSpdyPendingQ(pendingQ, ent, connH2, connH3);
+
+ // Put the leftovers back in the pending queue.
+ for (const auto& transactionInfo : pendingQ) {
+ ent->InsertTransaction(transactionInfo);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
+ for (const auto& entry : mCT.Values()) {
+ ProcessSpdyPendingQ(entry.get());
+ }
+}
+
+// Given a connection entry, return an active h2 or h3 connection
+// that can be directly activated or null.
+HttpConnectionBase* nsHttpConnectionMgr::GetH2orH3ActiveConn(
+ ConnectionEntry* ent, bool aNoHttp2, bool aNoHttp3) {
+ if (aNoHttp2 && aNoHttp3) {
+ return nullptr;
+ }
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(ent);
+
+ // First look at ent. If protocol that ent provides is no forbidden,
+ // i.e. ent use HTTP3 and !aNoHttp3 or en uses HTTP over TCP and !aNoHttp2.
+ if ((!aNoHttp3 && ent->IsHttp3()) || (!aNoHttp2 && !ent->IsHttp3())) {
+ HttpConnectionBase* conn = ent->GetH2orH3ActiveConn();
+ if (conn) {
+ return conn;
+ }
+ }
+
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+
+ // there was no active HTTP2/3 connection in the connection entry, but
+ // there might be one in the hash table for coalescing
+ HttpConnectionBase* existingConn =
+ FindCoalescableConnection(ent, false, aNoHttp2, aNoHttp3);
+ if (existingConn) {
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "found an active connection %p in the coalescing hashtable\n",
+ ent, ci->HashKey().get(), existingConn));
+ return existingConn;
+ }
+
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "did not find an active connection\n",
+ ent, ci->HashKey().get()));
+ return nullptr;
+}
+
+//-----------------------------------------------------------------------------
+
+void nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase*) {
+ if (!OnSocketThread()) {
+ Unused << PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections);
+ return;
+ }
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n"));
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+
+ // Close all active connections.
+ ent->CloseActiveConnections();
+
+ // Close all idle connections.
+ ent->CloseIdleConnections();
+
+ // Close websocket "fake" connections
+ ent->CloseH2WebsocketConnections();
+
+ ent->ClosePendingConnections();
+
+ // Close all pending transactions.
+ ent->CancelAllTransactions(NS_ERROR_ABORT);
+
+ // Close all half open tcp connections.
+ ent->CloseAllDnsAndConnectSockets();
+
+ MOZ_ASSERT(!ent->mDoNotDestroy);
+ iter.Remove();
+ }
+
+ mActiveTransactions[false].Clear();
+ mActiveTransactions[true].Clear();
+}
+
+void nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
+
+ gHttpHandler->StopRequestTokenBucket();
+ AbortAndCloseAllConnections(0, nullptr);
+
+ // If all idle connections are removed we can stop pruning dead
+ // connections.
+ ConditionallyStopPruneDeadConnectionsTimer();
+
+ if (mTimeoutTick) {
+ mTimeoutTick->Cancel();
+ mTimeoutTick = nullptr;
+ mTimeoutTickArmed = false;
+ }
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (mTrafficTimer) {
+ mTrafficTimer->Cancel();
+ mTrafficTimer = nullptr;
+ }
+ DestroyThrottleTicker();
+
+ mCoalescingHash.Clear();
+
+ // signal shutdown complete
+ nsCOMPtr<nsIRunnable> runnable =
+ new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm, 0, param);
+ NS_DispatchToMainThread(runnable);
+}
+
+void nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority,
+ ARefBase* param) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
+
+ BoolWrapper* shutdown = static_cast<BoolWrapper*>(param);
+ shutdown->mBool = true;
+}
+
+void nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority,
+ ARefBase* param) {
+ nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
+
+ LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", trans));
+ trans->SetPriority(priority);
+ nsresult rv = ProcessNewTransaction(trans);
+ if (NS_FAILED(rv)) trans->Close(rv); // for whatever its worth
+}
+
+void nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn(int32_t priority,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NewTransactionData* data = static_cast<NewTransactionData*>(param);
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn "
+ "[trans=%p, transWithStickyConn=%p, conn=%p]\n",
+ data->mTrans.get(), data->mTransWithStickyConn.get(),
+ data->mTransWithStickyConn->Connection()));
+
+ MOZ_ASSERT(data->mTransWithStickyConn &&
+ data->mTransWithStickyConn->Caps() & NS_HTTP_STICKY_CONNECTION);
+
+ data->mTrans->SetPriority(data->mPriority);
+
+ RefPtr<nsAHttpConnection> conn = data->mTransWithStickyConn->Connection();
+ if (conn && conn->IsPersistent()) {
+ // This is so far a workaround to only reuse persistent
+ // connection for authentication retry. See bug 459620 comment 4
+ // for details.
+ LOG((" Reuse connection [%p] for transaction [%p]", conn.get(),
+ data->mTrans.get()));
+ data->mTrans->SetConnection(conn);
+ }
+
+ nsresult rv = ProcessNewTransaction(data->mTrans);
+ if (NS_FAILED(rv)) {
+ data->mTrans->Close(rv); // for whatever its worth
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
+
+ RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction*>(param);
+ trans->SetPriority(priority);
+
+ if (!trans->ConnectionInfo()) {
+ return;
+ }
+ ConnectionEntry* ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
+
+ if (ent) {
+ ent->ReschedTransaction(trans);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(
+ ClassOfService cos, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction "
+ "[trans=%p]\n",
+ param));
+
+ nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
+
+ ClassOfService previous = trans->GetClassOfService();
+ trans->SetClassOfService(cos);
+
+ // incremental change alone will not trigger a reschedule
+ if ((previous.Flags() ^ cos.Flags()) &
+ (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
+ Unused << RescheduleTransaction(trans, trans->Priority());
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
+
+ nsresult closeCode = static_cast<nsresult>(reason);
+
+ // caller holds a ref to param/trans on stack
+ nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
+
+ //
+ // if the transaction owns a connection and the transaction is not done,
+ // then ask the connection to close the transaction. otherwise, close the
+ // transaction directly (removing it from the pending queue first).
+ //
+ RefPtr<nsAHttpConnection> conn(trans->Connection());
+ if (conn && !trans->IsDone()) {
+ conn->CloseTransaction(trans, closeCode);
+ } else {
+ ConnectionEntry* ent = nullptr;
+ if (trans->ConnectionInfo()) {
+ ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
+ }
+ if (ent && ent->RemoveTransFromPendingQ(trans)) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
+ " removed from pending queue\n",
+ trans));
+ }
+
+ trans->Close(closeCode);
+
+ // Cancel is a pretty strong signal that things might be hanging
+ // so we want to cancel any null transactions related to this connection
+ // entry. They are just optimizations, but they aren't hooked up to
+ // anything that might get canceled from the rest of gecko, so best
+ // to assume that's what was meant by the cancel we did receive if
+ // it only applied to something in the queue.
+ if (ent) {
+ ent->CloseAllActiveConnsWithNullTransactcion(closeCode);
+ }
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+
+ if (!ci) {
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
+ // Try and dispatch everything
+ for (const auto& entry : mCT.Values()) {
+ Unused << ProcessPendingQForEntry(entry.get(), true);
+ }
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
+ ci->HashKey().get()));
+
+ // start by processing the queue identified by the given connection info.
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!(ent && ProcessPendingQForEntry(ent, false))) {
+ // if we reach here, it means that we couldn't dispatch a transaction
+ // for the specified connection info. walk the connection table...
+ for (const auto& entry : mCT.Values()) {
+ if (ProcessPendingQForEntry(entry.get(), false)) {
+ break;
+ }
+ }
+ }
+}
+
+nsresult nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo* ci,
+ nsresult code) {
+ LOG(("nsHttpConnectionMgr::CancelTransactions %s\n", ci->HashKey().get()));
+
+ int32_t intReason = static_cast<int32_t>(code);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason,
+ ci);
+}
+
+void nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code,
+ ARefBase* param) {
+ nsresult reason = static_cast<nsresult>(code);
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
+ ci->HashKey().get(), ent));
+ if (ent) {
+ ent->CancelAllTransactions(reason);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+
+ // check canreuse() for all idle connections plus any active connections on
+ // connection entries that are using spdy.
+ if (mNumIdleConns ||
+ (mNumActiveConns && StaticPrefs::network_http_http2_enabled())) {
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+
+ LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
+
+ // Find out how long it will take for next idle connection to not
+ // be reusable anymore.
+ uint32_t timeToNextExpire = ent->PruneDeadConnections();
+
+ // If time to next expire found is shorter than time to next
+ // wake-up, we need to change the time for next wake-up.
+ if (timeToNextExpire != UINT32_MAX) {
+ uint32_t now = NowInSeconds();
+ uint64_t timeOfNextExpire = now + timeToNextExpire;
+ // If pruning of dead connections is not already scheduled to
+ // happen or time found for next connection to expire is is
+ // before mTimeOfNextWakeUp, we need to schedule the pruning to
+ // happen after timeToNextExpire.
+ if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(timeToNextExpire);
+ }
+ } else {
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+
+ ent->RemoveEmptyPendingQ();
+
+ // If this entry is empty, we have too many entries busy then
+ // we can clean it up and restart
+ if (mCT.Count() > 125 && ent->IdleConnectionsLength() == 0 &&
+ ent->ActiveConnsLength() == 0 &&
+ ent->DnsAndConnectSocketsLength() == 0 &&
+ ent->PendingQueueLength() == 0 &&
+ ent->UrgentStartQueueLength() == 0 && !ent->mDoNotDestroy &&
+ (!ent->mUsingSpdy || mCT.Count() > 300)) {
+ LOG((" removing empty connection entry\n"));
+ iter.Remove();
+ continue;
+ }
+
+ // Otherwise use this opportunity to compact our arrays...
+ ent->Compact();
+ }
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
+
+ // Prune connections without traffic
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ // Close the connections with no registered traffic.
+ ent->PruneNoTraffic();
+ }
+
+ mPruningNoTraffic = false; // not pruning anymore
+}
+
+void nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
+
+ if (mPruningNoTraffic) {
+ // Called in the time gap when the timeout to prune notraffic
+ // connections has triggered but the pruning hasn't happened yet.
+ return;
+ }
+
+ mCoalescingHash.Clear();
+
+ // Mark connections for traffic verification
+ for (const auto& entry : mCT.Values()) {
+ entry->VerifyTraffic();
+ }
+
+ // If the timer is already there. we just re-init it
+ if (!mTrafficTimer) {
+ mTrafficTimer = NS_NewTimer();
+ }
+
+ // failure to create a timer is not a fatal error, but dead
+ // connections will not be cleaned up as nicely
+ if (mTrafficTimer) {
+ // Give active connections time to get more traffic before killing
+ // them off. Default: 5000 milliseconds
+ mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(),
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create timer for VerifyTraffic!");
+ }
+ // Calling ActivateTimeoutTick to ensure the next timeout tick is 1s.
+ ActivateTimeoutTick();
+}
+
+void nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t,
+ ARefBase* param) {
+ LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCoalescingHash.Clear();
+
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+
+ for (const auto& entry : mCT.Values()) {
+ entry->ClosePersistentConnections();
+ }
+
+ if (ci) ResetIPFamilyPreference(ci);
+}
+
+void nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup(int32_t,
+ ARefBase* param) {
+ LOG(("nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup\n"));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+
+ if (!ci) {
+ return;
+ }
+
+ ConnectionEntry* entry = mCT.GetWeak(ci->HashKey());
+ if (entry) {
+ entry->ClosePersistentConnections();
+ }
+
+ ResetIPFamilyPreference(ci);
+}
+
+void nsHttpConnectionMgr::OnMsgReclaimConnection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ //
+ // 1) remove the connection from the active list
+ // 2) if keep-alive, add connection to idle list
+ // 3) post event to process the pending transaction queue
+ //
+
+ MOZ_ASSERT(conn);
+ ConnectionEntry* ent = conn->ConnectionInfo()
+ ? mCT.GetWeak(conn->ConnectionInfo()->HashKey())
+ : nullptr;
+
+ if (!ent) {
+ // this can happen if the connection is made outside of the
+ // connection manager and is being "reclaimed" for use with
+ // future transactions. HTTP/2 tunnels work like this.
+ bool isWildcard = false;
+ ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true, false, false,
+ &isWildcard);
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
+ "forced new hash entry %s\n",
+ conn, conn->ConnectionInfo()->HashKey().get()));
+ }
+
+ MOZ_ASSERT(ent);
+ RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
+
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent,
+ conn));
+
+ // If the connection is in the active list, remove that entry
+ // and the reference held by the mActiveConns list.
+ // This is never the final reference on conn as the event context
+ // is also holding one that is released at the end of this function.
+
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (!connTCP || connTCP->EverUsedSpdy()) {
+ // Spdyand Http3 connections aren't reused in the traditional HTTP way in
+ // the idleconns list, they are actively multplexed as active
+ // conns. Even when they have 0 transactions on them they are
+ // considered active connections. So when one is reclaimed it
+ // is really complete and is meant to be shut down and not
+ // reused.
+ conn->DontReuse();
+ }
+
+ // a connection that still holds a reference to a transaction was
+ // not closed naturally (i.e. it was reset or aborted) and is
+ // therefore not something that should be reused.
+ if (conn->Transaction()) {
+ conn->DontReuse();
+ }
+
+ if (NS_SUCCEEDED(ent->RemoveActiveConnection(conn)) ||
+ NS_SUCCEEDED(ent->RemovePendingConnection(conn))) {
+ } else if (!connTCP || connTCP->EverUsedSpdy()) {
+ LOG(("HttpConnectionBase %p not found in its connection entry, try ^anon",
+ conn));
+ // repeat for flipped anon flag as we share connection entries for spdy
+ // connections.
+ RefPtr<nsHttpConnectionInfo> anonInvertedCI(ci->Clone());
+ anonInvertedCI->SetAnonymous(!ci->GetAnonymous());
+
+ ConnectionEntry* ent = mCT.GetWeak(anonInvertedCI->HashKey());
+ if (ent) {
+ if (NS_SUCCEEDED(ent->RemoveActiveConnection(conn))) {
+ } else {
+ LOG(
+ ("HttpConnectionBase %p could not be removed from its entry's "
+ "active list",
+ conn));
+ }
+ }
+ }
+
+ if (connTCP && connTCP->CanReuse()) {
+ LOG((" adding connection to idle list\n"));
+ // Keep The idle connection list sorted with the connections that
+ // have moved the largest data pipelines at the front because these
+ // connections have the largest cwnds on the server.
+
+ // The linear search is ok here because the number of idleconns
+ // in a single entry is generally limited to a small number (i.e. 6)
+
+ ent->InsertIntoIdleConnections(connTCP);
+ } else {
+ if (ent->IsInH2WebsocketConns(conn)) {
+ ent->RemoveH2WebsocketConns(conn);
+ }
+ LOG((" connection cannot be reused; closing connection\n"));
+ conn->SetCloseReason(ConnectionCloseReason::CANT_REUSED);
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ OnMsgProcessPendingQ(0, ci);
+}
+
+void nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv = NS_OK;
+ nsCompleteUpgradeData* data = static_cast<nsCompleteUpgradeData*>(param);
+ MOZ_ASSERT(data->mTrans && data->mTrans->Caps() & NS_HTTP_STICKY_CONNECTION);
+
+ RefPtr<nsAHttpConnection> conn(data->mTrans->Connection());
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+ "conn=%p listener=%p wrapped=%d\n",
+ conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
+
+ if (!conn) {
+ // Delay any error reporting to happen in transportAvailableFunc
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ MOZ_ASSERT(!data->mSocketTransport);
+ rv = conn->TakeTransport(getter_AddRefs(data->mSocketTransport),
+ getter_AddRefs(data->mSocketIn),
+ getter_AddRefs(data->mSocketOut));
+
+ if (NS_FAILED(rv)) {
+ LOG((" conn->TakeTransport failed with %" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ RefPtr<nsCompleteUpgradeData> upgradeData(data);
+
+ nsCOMPtr<nsIAsyncInputStream> socketIn;
+ nsCOMPtr<nsIAsyncOutputStream> socketOut;
+
+ // If this is for JS, the input and output sockets need to be piped over the
+ // socket thread. Otherwise, the JS may attempt to read and/or write the
+ // sockets on the main thread, which could cause network I/O on the main
+ // thread. This is particularly bad in the case of TLS connections, because
+ // PSM and NSS rely on those connections only being used on the socket
+ // thread.
+ if (data->mJsWrapped) {
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ uint32_t segsize = 0;
+ uint32_t segcount = 0;
+ net_ResolveSegmentParams(segsize, segcount);
+ if (NS_SUCCEEDED(rv)) {
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(socketOut), true, true,
+ segsize, segcount);
+ rv = NS_AsyncCopy(pipeIn, data->mSocketOut, gSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ if (NS_SUCCEEDED(rv)) {
+ NS_NewPipe2(getter_AddRefs(socketIn), getter_AddRefs(pipeOut), true, true,
+ segsize, segcount);
+ rv = NS_AsyncCopy(data->mSocketIn, pipeOut, gSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
+ }
+ } else {
+ socketIn = upgradeData->mSocketIn;
+ socketOut = upgradeData->mSocketOut;
+ }
+
+ auto transportAvailableFunc = [upgradeData{std::move(upgradeData)}, socketIn,
+ socketOut, aRv(rv)]() {
+ // Handle any potential previous errors first
+ // and call OnUpgradeFailed if necessary.
+ nsresult rv = aRv;
+
+ if (NS_FAILED(rv)) {
+ rv = upgradeData->mUpgradeListener->OnUpgradeFailed(rv);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnUpgradeFailed failed."
+ " listener=%p\n",
+ upgradeData->mUpgradeListener.get()));
+ }
+ return;
+ }
+
+ rv = upgradeData->mUpgradeListener->OnTransportAvailable(
+ upgradeData->mSocketTransport, socketIn, socketOut);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnTransportAvailable "
+ "failed. listener=%p\n",
+ upgradeData->mUpgradeListener.get()));
+ }
+ };
+
+ if (data->mJsWrapped) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+ "conn=%p listener=%p wrapped=%d pass to main thread\n",
+ conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("net::nsHttpConnectionMgr::OnMsgCompleteUpgrade",
+ transportAvailableFunc));
+ } else {
+ transportAvailableFunc();
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ uint32_t param = static_cast<uint32_t>(inParam);
+ uint16_t name = ((param) & 0xFFFF0000) >> 16;
+ uint16_t value = param & 0x0000FFFF;
+
+ switch (name) {
+ case MAX_CONNECTIONS:
+ mMaxConns = value;
+ break;
+ case MAX_URGENT_START_Q:
+ mMaxUrgentExcessiveConns = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
+ mMaxPersistConnsPerHost = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
+ mMaxPersistConnsPerProxy = value;
+ break;
+ case MAX_REQUEST_DELAY:
+ mMaxRequestDelay = value;
+ break;
+ case THROTTLING_ENABLED:
+ SetThrottlingEnabled(!!value);
+ break;
+ case THROTTLING_SUSPEND_FOR:
+ mThrottleSuspendFor = value;
+ break;
+ case THROTTLING_RESUME_FOR:
+ mThrottleResumeFor = value;
+ break;
+ case THROTTLING_READ_LIMIT:
+ mThrottleReadLimit = value;
+ break;
+ case THROTTLING_READ_INTERVAL:
+ mThrottleReadInterval = value;
+ break;
+ case THROTTLING_HOLD_TIME:
+ mThrottleHoldTime = value;
+ break;
+ case THROTTLING_MAX_TIME:
+ mThrottleMaxTime = TimeDuration::FromMilliseconds(value);
+ break;
+ case PROXY_BE_CONSERVATIVE:
+ mBeConservativeForProxy = !!value;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected parameter name");
+ }
+}
+
+// Read Timeout Tick handlers
+
+void nsHttpConnectionMgr::ActivateTimeoutTick() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(
+ ("nsHttpConnectionMgr::ActivateTimeoutTick() "
+ "this=%p mTimeoutTick=%p\n",
+ this, mTimeoutTick.get()));
+
+ // The timer tick should be enabled if it is not already pending.
+ // Upon running the tick will rearm itself if there are active
+ // connections available.
+
+ if (mTimeoutTick && mTimeoutTickArmed) {
+ // make sure we get one iteration on a quick tick
+ if (mTimeoutTickNext > 1) {
+ mTimeoutTickNext = 1;
+ mTimeoutTick->SetDelay(1000);
+ }
+ return;
+ }
+
+ if (!mTimeoutTick) {
+ mTimeoutTick = NS_NewTimer();
+ if (!mTimeoutTick) {
+ NS_WARNING("failed to create timer for http timeout management");
+ return;
+ }
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ if (!mSocketThreadTarget) {
+ NS_WARNING("cannot activate timout if not initialized or shutdown");
+ return;
+ }
+ mTimeoutTick->SetTarget(mSocketThreadTarget);
+ }
+
+ if (mIsShuttingDown) { // Atomic
+ // don't set a timer to generate an event if we're shutting down
+ // (and mSocketThreadTarget might be null or garbage if we're shutting down)
+ return;
+ }
+ MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
+ mTimeoutTickArmed = true;
+ mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
+}
+
+class UINT64Wrapper : public ARefBase {
+ public:
+ explicit UINT64Wrapper(uint64_t aUint64) : mUint64(aUint64) {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper, override)
+
+ uint64_t GetValue() { return mUint64; }
+
+ private:
+ uint64_t mUint64;
+ virtual ~UINT64Wrapper() = default;
+};
+
+nsresult nsHttpConnectionMgr::UpdateCurrentBrowserId(uint64_t aId) {
+ RefPtr<UINT64Wrapper> idWrapper = new UINT64Wrapper(aId);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId, 0,
+ idWrapper);
+}
+
+void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable) {
+ LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mThrottleEnabled = aEnable;
+
+ if (mThrottleEnabled) {
+ EnsureThrottleTickerIfNeeded();
+ } else {
+ DestroyThrottleTicker();
+ ResumeReadOf(mActiveTransactions[false]);
+ ResumeReadOf(mActiveTransactions[true]);
+ }
+}
+
+bool nsHttpConnectionMgr::InThrottlingTimeWindow() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mThrottlingWindowEndsAt.IsNull()) {
+ return true;
+ }
+ return TimeStamp::NowLoRes() <= mThrottlingWindowEndsAt;
+}
+
+void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker) {
+ LOG(("nsHttpConnectionMgr::TouchThrottlingTimeWindow"));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleMaxTime;
+
+ if (!mThrottleTicker && MOZ_LIKELY(aEnsureTicker) &&
+ MOZ_LIKELY(mThrottleEnabled)) {
+ EnsureThrottleTickerIfNeeded();
+ }
+}
+
+void nsHttpConnectionMgr::LogActiveTransactions(char operation) {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ nsTArray<RefPtr<nsHttpTransaction>>* trs = nullptr;
+ uint32_t au, at, bu = 0, bt = 0;
+
+ trs = mActiveTransactions[false].Get(mCurrentBrowserId);
+ au = trs ? trs->Length() : 0;
+ trs = mActiveTransactions[true].Get(mCurrentBrowserId);
+ at = trs ? trs->Length() : 0;
+
+ for (const auto& data : mActiveTransactions[false].Values()) {
+ bu += data->Length();
+ }
+ bu -= au;
+ for (const auto& data : mActiveTransactions[true].Values()) {
+ bt += data->Length();
+ }
+ bt -= at;
+
+ // Shows counts of:
+ // - unthrottled transaction for the active tab
+ // - throttled transaction for the active tab
+ // - unthrottled transaction for background tabs
+ // - throttled transaction for background tabs
+ LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
+}
+
+void nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction* aTrans) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t tabId = aTrans->BrowserId();
+ bool throttled = aTrans->EligibleForThrottling();
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions =
+ mActiveTransactions[throttled].GetOrInsertNew(tabId);
+
+ MOZ_ASSERT(!transactions->Contains(aTrans));
+
+ transactions->AppendElement(aTrans);
+
+ LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64
+ "(%d) thr=%d",
+ aTrans, tabId, tabId == mCurrentBrowserId, throttled));
+ LogActiveTransactions('+');
+
+ if (tabId == mCurrentBrowserId) {
+ mActiveTabTransactionsExist = true;
+ if (!throttled) {
+ mActiveTabUnthrottledTransactionsExist = true;
+ }
+ }
+
+ // Shift the throttling window to the future (actually, makes sure
+ // that throttling will engage when there is anything to throttle.)
+ // The |false| argument means we don't need this call to ensure
+ // the ticker, since we do it just below. Calling
+ // EnsureThrottleTickerIfNeeded directly does a bit more than call
+ // from inside of TouchThrottlingTimeWindow.
+ TouchThrottlingTimeWindow(false);
+
+ if (!mThrottleEnabled) {
+ return;
+ }
+
+ EnsureThrottleTickerIfNeeded();
+}
+
+void nsHttpConnectionMgr::RemoveActiveTransaction(
+ nsHttpTransaction* aTrans, Maybe<bool> const& aOverride) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t tabId = aTrans->BrowserId();
+ bool forActiveTab = tabId == mCurrentBrowserId;
+ bool throttled = aOverride.valueOr(aTrans->EligibleForThrottling());
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions =
+ mActiveTransactions[throttled].Get(tabId);
+
+ if (!transactions || !transactions->RemoveElement(aTrans)) {
+ // Was not tracked as active, probably just ignore.
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64
+ "(%d) thr=%d",
+ aTrans, tabId, forActiveTab, throttled));
+
+ if (!transactions->IsEmpty()) {
+ // There are still transactions of the type, hence nothing in the throttling
+ // conditions has changed and we don't need to update "Exists" caches nor we
+ // need to wake any now throttled transactions.
+ LogActiveTransactions('-');
+ return;
+ }
+
+ // To optimize the following logic, always remove the entry when the array is
+ // empty.
+ mActiveTransactions[throttled].Remove(tabId);
+ LogActiveTransactions('-');
+
+ if (forActiveTab) {
+ // Update caches of the active tab transaction existence, since it's now
+ // affected
+ if (!throttled) {
+ mActiveTabUnthrottledTransactionsExist = false;
+ }
+ if (mActiveTabTransactionsExist) {
+ mActiveTabTransactionsExist =
+ mActiveTransactions[!throttled].Contains(tabId);
+ }
+ }
+
+ if (!mThrottleEnabled) {
+ return;
+ }
+
+ bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
+ bool throttledExist = !mActiveTransactions[true].IsEmpty();
+
+ if (!unthrottledExist && !throttledExist) {
+ // Nothing active globally, just get rid of the timer completely and we are
+ // done.
+ MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
+ MOZ_ASSERT(!mActiveTabTransactionsExist);
+
+ DestroyThrottleTicker();
+ return;
+ }
+
+ if (mThrottleVersion == 1) {
+ if (!mThrottlingInhibitsReading) {
+ // There is then nothing to wake up. Affected transactions will not be
+ // put to sleep automatically on next tick.
+ LOG((" reading not currently inhibited"));
+ return;
+ }
+ }
+
+ if (mActiveTabUnthrottledTransactionsExist) {
+ // There are still unthrottled transactions for the active tab, hence the
+ // state is unaffected and we don't need to do anything (nothing to wake).
+ LOG((" there are unthrottled for the active tab"));
+ return;
+ }
+
+ if (mActiveTabTransactionsExist) {
+ // There are only trottled transactions for the active tab.
+ // If the last transaction we just removed was a non-throttled for the
+ // active tab we can wake the throttled transactions for the active tab.
+ if (forActiveTab && !throttled) {
+ LOG((" resuming throttled for active tab"));
+ ResumeReadOf(mActiveTransactions[true].Get(mCurrentBrowserId));
+ }
+ return;
+ }
+
+ if (!unthrottledExist) {
+ // There are no unthrottled transactions for any tab. Resume all throttled,
+ // all are only for background tabs.
+ LOG((" delay resuming throttled for background tabs"));
+ DelayedResumeBackgroundThrottledTransactions();
+ return;
+ }
+
+ if (forActiveTab) {
+ // Removing the last transaction for the active tab frees up the unthrottled
+ // background tabs transactions.
+ LOG((" delay resuming unthrottled for background tabs"));
+ DelayedResumeBackgroundThrottledTransactions();
+ return;
+ }
+
+ LOG((" not resuming anything"));
+}
+
+void nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction* aTrans) {
+ LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans));
+
+ // First remove then add. In case of a download that is the only active
+ // transaction and has just been marked as download (goes unthrottled to
+ // throttled), adding first would cause it to be throttled for first few
+ // milliseconds - becuause it would appear as if there were both throttled
+ // and unthrottled transactions at the time.
+
+ Maybe<bool> reversed;
+ reversed.emplace(!aTrans->EligibleForThrottling());
+ RemoveActiveTransaction(aTrans, reversed);
+
+ AddActiveTransaction(aTrans);
+
+ LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans));
+}
+
+bool nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction* aTrans) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans));
+
+ if (mThrottleVersion == 1) {
+ if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
+ return false;
+ }
+ } else {
+ if (!mThrottleEnabled) {
+ return false;
+ }
+ }
+
+ uint64_t tabId = aTrans->BrowserId();
+ bool forActiveTab = tabId == mCurrentBrowserId;
+ bool throttled = aTrans->EligibleForThrottling();
+
+ bool stop = [=]() {
+ if (mActiveTabTransactionsExist) {
+ if (!tabId) {
+ // Chrome initiated and unidentified transactions just respect
+ // their throttle flag, when something for the active tab is happening.
+ // This also includes downloads.
+ LOG((" active tab loads, trans is tab-less, throttled=%d", throttled));
+ return throttled;
+ }
+ if (!forActiveTab) {
+ // This is a background tab request, we want them to always throttle
+ // when there are transactions running for the ative tab.
+ LOG((" active tab loads, trans not of the active tab"));
+ return true;
+ }
+
+ if (mActiveTabUnthrottledTransactionsExist) {
+ // Unthrottled transactions for the active tab take precedence
+ LOG((" active tab loads unthrottled, trans throttled=%d", throttled));
+ return throttled;
+ }
+
+ LOG((" trans for active tab, don't throttle"));
+ return false;
+ }
+
+ MOZ_ASSERT(!forActiveTab);
+
+ if (!mActiveTransactions[false].IsEmpty()) {
+ // This means there are unthrottled active transactions for background
+ // tabs. If we are here, there can't be any transactions for the active
+ // tab. (If there is no transaction for a tab id, there is no entry for it
+ // in the hashtable.)
+ LOG((" backround tab(s) load unthrottled, trans throttled=%d",
+ throttled));
+ return throttled;
+ }
+
+ // There are only unthrottled transactions for background tabs: don't
+ // throttle.
+ LOG((" backround tab(s) load throttled, don't throttle"));
+ return false;
+ }();
+
+ if (forActiveTab && !stop) {
+ // This is an active-tab transaction and is allowed to read. Hence,
+ // prolong the throttle time window to make sure all 'lower-decks'
+ // transactions will actually throttle.
+ TouchThrottlingTimeWindow();
+ return false;
+ }
+
+ // Only stop reading when in the configured throttle max-time (aka time
+ // window). This window is prolonged (restarted) by a call to
+ // TouchThrottlingTimeWindow called on new transaction activation or on
+ // receive of response bytes of an active tab transaction.
+ bool inWindow = InThrottlingTimeWindow();
+
+ LOG((" stop=%d, in-window=%d, delayed-bck-timer=%d", stop, inWindow,
+ !!mDelayedResumeReadTimer));
+
+ if (!forActiveTab) {
+ // If the delayed background resume timer exists, background transactions
+ // are scheduled to be woken after a delay, hence leave them throttled.
+ inWindow = inWindow || mDelayedResumeReadTimer;
+ }
+
+ return stop && inWindow;
+}
+
+bool nsHttpConnectionMgr::IsConnEntryUnderPressure(
+ nsHttpConnectionInfo* connInfo) {
+ ConnectionEntry* ent = mCT.GetWeak(connInfo->HashKey());
+ if (!ent) {
+ // No entry, no pressure.
+ return false;
+ }
+
+ return ent->PendingQueueLengthForWindow(mCurrentBrowserId) > 0;
+}
+
+bool nsHttpConnectionMgr::IsThrottleTickerNeeded() {
+ LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
+
+ if (mActiveTabUnthrottledTransactionsExist &&
+ mActiveTransactions[false].Count() > 1) {
+ LOG((" there are unthrottled transactions for both active and bck"));
+ return true;
+ }
+
+ if (mActiveTabTransactionsExist && mActiveTransactions[true].Count() > 1) {
+ LOG((" there are throttled transactions for both active and bck"));
+ return true;
+ }
+
+ if (!mActiveTransactions[true].IsEmpty() &&
+ !mActiveTransactions[false].IsEmpty()) {
+ LOG((" there are both throttled and unthrottled transactions"));
+ return true;
+ }
+
+ LOG((" nothing to throttle"));
+ return false;
+}
+
+void nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
+ if (!IsThrottleTickerNeeded()) {
+ return;
+ }
+
+ // There is a new demand to throttle, hence unschedule delayed resume
+ // of background throttled transastions.
+ CancelDelayedResumeBackgroundThrottledTransactions();
+
+ if (mThrottleTicker) {
+ return;
+ }
+
+ mThrottleTicker = NS_NewTimer();
+ if (mThrottleTicker) {
+ if (mThrottleVersion == 1) {
+ MOZ_ASSERT(!mThrottlingInhibitsReading);
+
+ mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
+ mThrottlingInhibitsReading = true;
+ } else {
+ mThrottleTicker->Init(this, mThrottleReadInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ LogActiveTransactions('^');
+}
+
+// Can be called with or without the monitor held
+void nsHttpConnectionMgr::DestroyThrottleTicker() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Nothing to throttle, hence no need for this timer anymore.
+ CancelDelayedResumeBackgroundThrottledTransactions();
+
+ MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
+
+ if (!mThrottleTicker) {
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
+ mThrottleTicker->Cancel();
+ mThrottleTicker = nullptr;
+
+ if (mThrottleVersion == 1) {
+ mThrottlingInhibitsReading = false;
+ }
+
+ LogActiveTransactions('v');
+}
+
+void nsHttpConnectionMgr::ThrottlerTick() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mThrottleVersion == 1) {
+ mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
+
+ LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d",
+ mThrottlingInhibitsReading));
+
+ // If there are only background transactions to be woken after a delay, keep
+ // the ticker so that we woke them only for the resume-for interval and then
+ // throttle them again until the background-resume delay passes.
+ if (!mThrottlingInhibitsReading && !mDelayedResumeReadTimer &&
+ (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
+ LOG((" last tick"));
+ mThrottleTicker = nullptr;
+ }
+
+ if (mThrottlingInhibitsReading) {
+ if (mThrottleTicker) {
+ mThrottleTicker->Init(this, mThrottleSuspendFor,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else {
+ if (mThrottleTicker) {
+ mThrottleTicker->Init(this, mThrottleResumeFor,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ ResumeReadOf(mActiveTransactions[false], true);
+ ResumeReadOf(mActiveTransactions[true]);
+ }
+ } else {
+ LOG(("nsHttpConnectionMgr::ThrottlerTick"));
+
+ // If there are only background transactions to be woken after a delay, keep
+ // the ticker so that we still keep the low read limit for that time.
+ if (!mDelayedResumeReadTimer &&
+ (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
+ LOG((" last tick"));
+ mThrottleTicker = nullptr;
+ }
+
+ if (mThrottleTicker) {
+ mThrottleTicker->Init(this, mThrottleReadInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ ResumeReadOf(mActiveTransactions[false], true);
+ ResumeReadOf(mActiveTransactions[true]);
+ }
+}
+
+void nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mThrottleVersion == 1) {
+ if (mDelayedResumeReadTimer) {
+ return;
+ }
+ } else {
+ // If the mThrottleTicker doesn't exist, there is nothing currently
+ // being throttled. Hence, don't invoke the hold time interval.
+ // This is called also when a single download transaction becomes
+ // marked as throttleable. We would otherwise block it unnecessarily.
+ if (mDelayedResumeReadTimer || !mThrottleTicker) {
+ return;
+ }
+ }
+
+ LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
+ NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer), this,
+ mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT);
+}
+
+void nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions() {
+ if (!mDelayedResumeReadTimer) {
+ return;
+ }
+
+ LOG(
+ ("nsHttpConnectionMgr::"
+ "CancelDelayedResumeBackgroundThrottledTransactions"));
+ mDelayedResumeReadTimer->Cancel();
+ mDelayedResumeReadTimer = nullptr;
+}
+
+void nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
+ mDelayedResumeReadTimer = nullptr;
+
+ if (!IsThrottleTickerNeeded()) {
+ DestroyThrottleTicker();
+ }
+
+ if (!mActiveTransactions[false].IsEmpty()) {
+ ResumeReadOf(mActiveTransactions[false], true);
+ } else {
+ ResumeReadOf(mActiveTransactions[true], true);
+ }
+}
+
+void nsHttpConnectionMgr::ResumeReadOf(
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&
+ hashtable,
+ bool excludeForActiveTab) {
+ for (const auto& entry : hashtable) {
+ if (excludeForActiveTab && entry.GetKey() == mCurrentBrowserId) {
+ // These have never been throttled (never stopped reading)
+ continue;
+ }
+ ResumeReadOf(entry.GetWeak());
+ }
+}
+
+void nsHttpConnectionMgr::ResumeReadOf(
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions) {
+ MOZ_ASSERT(transactions);
+
+ for (const auto& trans : *transactions) {
+ trans->ResumeReading();
+ }
+}
+
+void nsHttpConnectionMgr::NotifyConnectionOfBrowserIdChange(
+ uint64_t previousId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
+ nsTArray<RefPtr<nsAHttpConnection>> connections;
+
+ auto addConnectionHelper =
+ [&connections](nsTArray<RefPtr<nsHttpTransaction>>* trans) {
+ if (!trans) {
+ return;
+ }
+
+ for (const auto& t : *trans) {
+ RefPtr<nsAHttpConnection> conn = t->Connection();
+ if (conn && !connections.Contains(conn)) {
+ connections.AppendElement(conn);
+ }
+ }
+ };
+
+ // Get unthrottled transactions with the previous and current window id.
+ transactions = mActiveTransactions[false].Get(previousId);
+ addConnectionHelper(transactions);
+ transactions = mActiveTransactions[false].Get(mCurrentBrowserId);
+ addConnectionHelper(transactions);
+
+ // Get throttled transactions with the previous and current window id.
+ transactions = mActiveTransactions[true].Get(previousId);
+ addConnectionHelper(transactions);
+ transactions = mActiveTransactions[true].Get(mCurrentBrowserId);
+ addConnectionHelper(transactions);
+
+ for (const auto& conn : connections) {
+ conn->CurrentBrowserIdChanged(mCurrentBrowserId);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId(int32_t aLoading,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t id = static_cast<UINT64Wrapper*>(param)->GetValue();
+
+ if (mCurrentBrowserId == id) {
+ // duplicate notification
+ return;
+ }
+
+ bool activeTabWasLoading = mActiveTabTransactionsExist;
+
+ uint64_t previousId = mCurrentBrowserId;
+ mCurrentBrowserId = id;
+
+ if (gHttpHandler->ActiveTabPriority()) {
+ NotifyConnectionOfBrowserIdChange(previousId);
+ }
+
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId"
+ " id=%" PRIx64 "\n",
+ mCurrentBrowserId));
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
+
+ // Update the "Exists" caches and resume any transactions that now deserve it,
+ // changing the active tab changes the conditions for throttling.
+ transactions = mActiveTransactions[false].Get(mCurrentBrowserId);
+ mActiveTabUnthrottledTransactionsExist = !!transactions;
+
+ if (!mActiveTabUnthrottledTransactionsExist) {
+ transactions = mActiveTransactions[true].Get(mCurrentBrowserId);
+ }
+ mActiveTabTransactionsExist = !!transactions;
+
+ if (transactions) {
+ // This means there are some transactions for this newly activated tab,
+ // resume them but anything else.
+ LOG((" resuming newly activated tab transactions"));
+ ResumeReadOf(transactions);
+ return;
+ }
+
+ if (!activeTabWasLoading) {
+ // There were no transactions for the previously active tab, hence
+ // all remaning transactions, if there were, were all unthrottled,
+ // no need to wake them.
+ return;
+ }
+
+ if (!mActiveTransactions[false].IsEmpty()) {
+ LOG((" resuming unthrottled background transactions"));
+ ResumeReadOf(mActiveTransactions[false]);
+ return;
+ }
+
+ if (!mActiveTransactions[true].IsEmpty()) {
+ LOG((" resuming throttled background transactions"));
+ ResumeReadOf(mActiveTransactions[true]);
+ return;
+ }
+
+ DestroyThrottleTicker();
+}
+
+void nsHttpConnectionMgr::TimeoutTick() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
+
+ LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns));
+ // The next tick will be between 1 second and 1 hr
+ // Set it to the max value here, and the TimeoutTick()s can
+ // reduce it to their local needs.
+ mTimeoutTickNext = 3600; // 1hr
+
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ uint32_t timeoutTickNext = ent->TimeoutTick();
+ mTimeoutTickNext = std::min(mTimeoutTickNext, timeoutTickNext);
+ }
+
+ if (mTimeoutTick) {
+ mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
+ mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
+ }
+}
+
+// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
+// dispatching a transaction according to these rules
+// 1] use an ent that matches the ci that can be dispatched immediately
+// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
+// 3] otherwise create an ent that matches ci and make new conn on it
+
+ConnectionEntry* nsHttpConnectionMgr::GetOrCreateConnectionEntry(
+ nsHttpConnectionInfo* specificCI, bool prohibitWildCard, bool aNoHttp2,
+ bool aNoHttp3, bool* aIsWildcard, bool* aAvailableForDispatchNow) {
+ if (aAvailableForDispatchNow) {
+ *aAvailableForDispatchNow = false;
+ }
+ *aIsWildcard = false;
+
+ // step 1
+ ConnectionEntry* specificEnt = mCT.GetWeak(specificCI->HashKey());
+ if (specificEnt && specificEnt->AvailableForDispatchNow()) {
+ if (aAvailableForDispatchNow) {
+ *aAvailableForDispatchNow = true;
+ }
+ return specificEnt;
+ }
+
+ // step 1 repeated for an inverted anonymous flag; we return an entry
+ // only when it has an h2 established connection that is not authenticated
+ // with a client certificate.
+ RefPtr<nsHttpConnectionInfo> anonInvertedCI(specificCI->Clone());
+ anonInvertedCI->SetAnonymous(!specificCI->GetAnonymous());
+ ConnectionEntry* invertedEnt = mCT.GetWeak(anonInvertedCI->HashKey());
+ if (invertedEnt) {
+ HttpConnectionBase* h2orh3conn =
+ GetH2orH3ActiveConn(invertedEnt, aNoHttp2, aNoHttp3);
+ if (h2orh3conn && h2orh3conn->IsExperienced() &&
+ h2orh3conn->NoClientCertAuth()) {
+ MOZ_ASSERT(h2orh3conn->UsingSpdy() || h2orh3conn->UsingHttp3());
+ LOG(
+ ("GetOrCreateConnectionEntry is coalescing h2/3 an/onymous "
+ "connections, ent=%p",
+ invertedEnt));
+ return invertedEnt;
+ }
+ }
+
+ if (!specificCI->UsingHttpsProxy()) {
+ prohibitWildCard = true;
+ }
+
+ // step 2
+ if (!prohibitWildCard && aNoHttp3) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
+ DebugOnly<nsresult> rv =
+ specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ ConnectionEntry* wildCardEnt = mCT.GetWeak(wildCardProxyCI->HashKey());
+ if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
+ if (aAvailableForDispatchNow) {
+ *aAvailableForDispatchNow = true;
+ }
+ *aIsWildcard = true;
+ return wildCardEnt;
+ }
+ }
+
+ // step 3
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
+ specificEnt = new ConnectionEntry(clone);
+ mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
+ }
+ return specificEnt;
+}
+
+void nsHttpConnectionMgr::DoSpeculativeConnection(
+ SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aTrans);
+
+ bool isWildcard = false;
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard);
+ if (!aFetchHTTPSRR &&
+ gHttpHandler->EchConfigEnabled(aTrans->ConnectionInfo()->IsHttp3())) {
+ // This happens when this is called from
+ // SpeculativeTransaction::OnHTTPSRRAvailable. We have to update this
+ // entry's echConfig so that the newly created connection can use the latest
+ // echConfig.
+ ent->MaybeUpdateEchConfig(aTrans->ConnectionInfo());
+ }
+ DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR);
+}
+
+void nsHttpConnectionMgr::DoSpeculativeConnectionInternal(
+ ConnectionEntry* aEnt, SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aTrans);
+ MOZ_ASSERT(aEnt);
+ if (!gHttpHandler->Active()) {
+ // Do nothing if we are shutting down.
+ return;
+ }
+
+ if (aFetchHTTPSRR && NS_SUCCEEDED(aTrans->FetchHTTPSRR())) {
+ // nsHttpConnectionMgr::DoSpeculativeConnection will be called again when
+ // HTTPS RR is available.
+ return;
+ }
+
+ uint32_t parallelSpeculativeConnectLimit =
+ aTrans->ParallelSpeculativeConnectLimit()
+ ? *aTrans->ParallelSpeculativeConnectLimit()
+ : gHttpHandler->ParallelSpeculativeConnectLimit();
+ bool ignoreIdle = aTrans->IgnoreIdle() ? *aTrans->IgnoreIdle() : false;
+ bool isFromPredictor =
+ aTrans->IsFromPredictor() ? *aTrans->IsFromPredictor() : false;
+ bool allow1918 = aTrans->Allow1918() ? *aTrans->Allow1918() : false;
+
+ bool keepAlive = aTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
+ if (mNumDnsAndConnectSockets < parallelSpeculativeConnectLimit &&
+ ((ignoreIdle &&
+ (aEnt->IdleConnectionsLength() < parallelSpeculativeConnectLimit)) ||
+ !aEnt->IdleConnectionsLength()) &&
+ !(keepAlive && aEnt->RestrictConnections()) &&
+ !AtActiveConnectionLimit(aEnt, aTrans->Caps())) {
+ nsresult rv = aEnt->CreateDnsAndConnectSocket(aTrans, aTrans->Caps(), true,
+ isFromPredictor, false,
+ allow1918, nullptr);
+ if (NS_FAILED(rv)) {
+ glean::networking::speculative_connect_outcome
+ .Get("aborted_socket_fail"_ns)
+ .Add(1);
+ LOG(
+ ("DoSpeculativeConnectionInternal Transport socket creation "
+ "failure: %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ } else {
+ glean::networking::speculative_connect_outcome.Get("successful"_ns)
+ .Add(1);
+ }
+ } else {
+ glean::networking::speculative_connect_outcome
+ .Get("aborted_socket_limit"_ns)
+ .Add(1);
+ LOG(
+ ("DoSpeculativeConnectionInternal Transport ci=%s "
+ "not created due to existing connection count:%d",
+ aEnt->mConnInfo->HashKey().get(), parallelSpeculativeConnectLimit));
+ }
+}
+
+void nsHttpConnectionMgr::DoFallbackConnection(SpeculativeTransaction* aTrans,
+ bool aFetchHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aTrans);
+
+ LOG(("nsHttpConnectionMgr::DoFallbackConnection"));
+
+ bool availableForDispatchNow = false;
+ bool aIsWildcard = false;
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &aIsWildcard,
+ &availableForDispatchNow);
+
+ if (availableForDispatchNow) {
+ LOG(
+ ("nsHttpConnectionMgr::DoFallbackConnection fallback connection is "
+ "ready for dispatching ent=%p",
+ ent));
+ aTrans->InvokeCallback();
+ return;
+ }
+
+ DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR);
+}
+
+void nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ SpeculativeConnectArgs* args = static_cast<SpeculativeConnectArgs*>(param);
+
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s, "
+ "mFetchHTTPSRR=%d]\n",
+ args->mTrans->ConnectionInfo()->HashKey().get(), args->mFetchHTTPSRR));
+ DoSpeculativeConnection(args->mTrans, args->mFetchHTTPSRR);
+}
+
+bool nsHttpConnectionMgr::BeConservativeIfProxied(nsIProxyInfo* proxy) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mBeConservativeForProxy) {
+ // The pref says to be conservative for proxies.
+ return true;
+ }
+
+ if (!proxy) {
+ // There is no proxy, so be conservative by default.
+ return true;
+ }
+
+ // Be conservative only if there is no proxy host set either.
+ // This logic was copied from nsSSLIOLayerAddToSocket.
+ nsAutoCString proxyHost;
+ proxy->GetHost(proxyHost);
+ return proxyHost.IsEmpty();
+}
+
+// register a connection to receive CanJoinConnection() for particular
+// origin keys
+void nsHttpConnectionMgr::RegisterOriginCoalescingKey(HttpConnectionBase* conn,
+ const nsACString& host,
+ int32_t port) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpConnectionInfo* ci = conn ? conn->ConnectionInfo() : nullptr;
+ if (!ci || !conn->CanDirectlyActivate()) {
+ return;
+ }
+
+ nsCString newKey;
+ BuildOriginFrameHashKey(newKey, ci, host, port);
+ mCoalescingHash.GetOrInsertNew(newKey, 1)->AppendElement(
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
+
+ LOG(
+ ("nsHttpConnectionMgr::RegisterOriginCoalescingKey "
+ "Established New Coalescing Key %s to %p %s\n",
+ newKey.get(), conn, ci->HashKey().get()));
+}
+
+bool nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams>* aArg) {
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ if (ent->mConnInfo->GetPrivate()) {
+ continue;
+ }
+ aArg->AppendElement(ent->GetConnectionData());
+ }
+
+ return true;
+}
+
+void nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (ent) {
+ ent->ResetIPFamilyPreference();
+ }
+}
+
+void nsHttpConnectionMgr::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp2 excluding ci %s",
+ ci->HashKey().BeginReading()));
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp2 no entry found?!"));
+ return;
+ }
+
+ ent->DisallowHttp2();
+}
+
+void nsHttpConnectionMgr::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp3 exclude ci %s",
+ ci->HashKey().BeginReading()));
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp3 no entry found?!"));
+ return;
+ }
+
+ ent->DontReuseHttp3Conn();
+}
+
+void nsHttpConnectionMgr::MoveToWildCardConnEntry(
+ nsHttpConnectionInfo* specificCI, nsHttpConnectionInfo* wildCardCI,
+ HttpConnectionBase* proxyConn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(specificCI->UsingHttpsProxy());
+
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
+ "change CI from %s to %s\n",
+ proxyConn, specificCI->HashKey().get(), wildCardCI->HashKey().get()));
+
+ ConnectionEntry* ent = mCT.GetWeak(specificCI->HashKey());
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy "
+ "%d)\n",
+ proxyConn, ent, ent ? ent->mUsingSpdy : 0));
+
+ if (!ent || !ent->mUsingSpdy) {
+ return;
+ }
+
+ bool isWildcard = false;
+ ConnectionEntry* wcEnt =
+ GetOrCreateConnectionEntry(wildCardCI, true, false, false, &isWildcard);
+ if (wcEnt == ent) {
+ // nothing to do!
+ return;
+ }
+ wcEnt->mUsingSpdy = true;
+
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
+ "idle=%zu active=%zu half=%zu pending=%zu\n",
+ ent, ent->IdleConnectionsLength(), ent->ActiveConnsLength(),
+ ent->DnsAndConnectSocketsLength(), ent->PendingQueueLength()));
+
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
+ "idle=%zu active=%zu half=%zu pending=%zu\n",
+ wcEnt, wcEnt->IdleConnectionsLength(), wcEnt->ActiveConnsLength(),
+ wcEnt->DnsAndConnectSocketsLength(), wcEnt->PendingQueueLength()));
+
+ ent->MoveConnection(proxyConn, wcEnt);
+}
+
+bool nsHttpConnectionMgr::RemoveTransFromConnEntry(nsHttpTransaction* aTrans,
+ const nsACString& aHashKey) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::RemoveTransFromConnEntry: trans=%p ci=%s", aTrans,
+ PromiseFlatCString(aHashKey).get()));
+
+ if (aHashKey.IsEmpty()) {
+ return false;
+ }
+
+ // Step 1: Get the transaction's connection entry.
+ ConnectionEntry* entry = mCT.GetWeak(aHashKey);
+ if (!entry) {
+ return false;
+ }
+
+ // Step 2: Try to find the undispatched transaction.
+ return entry->RemoveTransFromPendingQ(aTrans);
+}
+
+void nsHttpConnectionMgr::IncreaseNumDnsAndConnectSockets() {
+ mNumDnsAndConnectSockets++;
+}
+
+void nsHttpConnectionMgr::DecreaseNumDnsAndConnectSockets() {
+ MOZ_ASSERT(mNumDnsAndConnectSockets);
+ if (mNumDnsAndConnectSockets) { // just in case
+ mNumDnsAndConnectSockets--;
+ }
+}
+
+already_AddRefed<PendingTransactionInfo>
+nsHttpConnectionMgr::FindTransactionHelper(bool removeWhenFound,
+ ConnectionEntry* aEnt,
+ nsAHttpTransaction* aTrans) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ =
+ aEnt->GetTransactionPendingQHelper(aTrans);
+
+ int32_t index =
+ pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1;
+
+ RefPtr<PendingTransactionInfo> info;
+ if (index != -1) {
+ info = (*pendingQ)[index];
+ if (removeWhenFound) {
+ pendingQ->RemoveElementAt(index);
+ }
+ }
+ return info.forget();
+}
+
+already_AddRefed<ConnectionEntry> nsHttpConnectionMgr::FindConnectionEntry(
+ const nsHttpConnectionInfo* ci) {
+ return mCT.Get(ci->HashKey());
+}
+
+nsHttpConnectionMgr* nsHttpConnectionMgr::AsHttpConnectionMgr() { return this; }
+
+HttpConnectionMgrParent* nsHttpConnectionMgr::AsHttpConnectionMgrParent() {
+ return nullptr;
+}
+
+void nsHttpConnectionMgr::NewIdleConnectionAdded(uint32_t timeToLive) {
+ mNumIdleConns++;
+
+ // If the added connection was first idle connection or has shortest
+ // time to live among the watched connections, pruning dead
+ // connections needs to be done when it can't be reused anymore.
+ if (!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(timeToLive);
+ }
+}
+
+void nsHttpConnectionMgr::DecrementNumIdleConns() {
+ MOZ_ASSERT(mNumIdleConns);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+}
+
+void nsHttpConnectionMgr::CheckTransInPendingQueue(nsHttpTransaction* aTrans) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // We only do this check on socket thread. When this function is called on
+ // main thread, the transaction is newly created, so we can skip this check.
+ if (!OnSocketThread()) {
+ return;
+ }
+
+ nsAutoCString hashKey;
+ aTrans->GetHashKeyOfConnectionEntry(hashKey);
+ if (hashKey.IsEmpty()) {
+ return;
+ }
+
+ bool foundInPendingQ = RemoveTransFromConnEntry(aTrans, hashKey);
+ MOZ_DIAGNOSTIC_ASSERT(!foundInPendingQ);
+#endif
+}
+
+bool nsHttpConnectionMgr::AllowToRetryDifferentIPFamilyForHttp3(
+ nsHttpConnectionInfo* ci, nsresult aError) {
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ return false;
+ }
+
+ return ent->AllowToRetryDifferentIPFamilyForHttp3(aError);
+}
+
+void nsHttpConnectionMgr::SetRetryDifferentIPFamilyForHttp3(
+ nsHttpConnectionInfo* ci, uint16_t aIPFamily) {
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ return;
+ }
+
+ ent->SetRetryDifferentIPFamilyForHttp3(aIPFamily);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h
new file mode 100644
index 0000000000..2cf4ab7568
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -0,0 +1,469 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpConnectionMgr_h__
+#define nsHttpConnectionMgr_h__
+
+#include "DnsAndConnectSocket.h"
+#include "HttpConnectionBase.h"
+#include "HttpConnectionMgrShell.h"
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsClassHashtable.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "ARefBase.h"
+#include "nsWeakReference.h"
+#include "ConnectionEntry.h"
+
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+
+class nsIHttpUpgradeListener;
+
+namespace mozilla::net {
+class EventTokenBucket;
+class NullHttpTransaction;
+struct HttpRetParams;
+
+//-----------------------------------------------------------------------------
+
+// message handlers have this signature
+class nsHttpConnectionMgr;
+using nsConnEventHandler = void (nsHttpConnectionMgr::*)(int32_t, ARefBase*);
+
+class nsHttpConnectionMgr final : public HttpConnectionMgrShell,
+ public nsIObserver,
+ nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_HTTPCONNECTIONMGRSHELL
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may only be called on the main thread.
+ //-------------------------------------------------------------------------
+
+ nsHttpConnectionMgr();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called on any thread.
+ //-------------------------------------------------------------------------
+
+ [[nodiscard]] nsresult CancelTransactions(nsHttpConnectionInfo*,
+ nsresult code);
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called only on the socket thread.
+ //-------------------------------------------------------------------------
+
+ // called to change the connection entry associated with conn from specific
+ // into a wildcard (i.e. http2 proxy friendy) mapping
+ void MoveToWildCardConnEntry(nsHttpConnectionInfo* specificCI,
+ nsHttpConnectionInfo* wildcardCI,
+ HttpConnectionBase* conn);
+
+ // Remove a transaction from the pendingQ of it's connection entry. Returns
+ // true if the transaction is removed successfully, otherwise returns false.
+ bool RemoveTransFromConnEntry(nsHttpTransaction* aTrans,
+ const nsACString& aHashKey);
+
+ // Directly dispatch the transaction or insert it in to the pendingQ.
+ [[nodiscard]] nsresult ProcessNewTransaction(nsHttpTransaction* aTrans);
+
+ // This is used to force an idle connection to be closed and removed from
+ // the idle connection list. It is called when the idle connection detects
+ // that the network peer has closed the transport.
+ [[nodiscard]] nsresult CloseIdleConnection(nsHttpConnection*);
+ [[nodiscard]] nsresult RemoveIdleConnection(nsHttpConnection*);
+
+ // Close a single connection and prevent it from being reused.
+ [[nodiscard]] nsresult DoSingleConnectionCleanup(nsHttpConnectionInfo*);
+
+ // The connection manager needs to know when a normal HTTP connection has been
+ // upgraded to SPDY because the dispatch and idle semantics are a little
+ // bit different.
+ void ReportSpdyConnection(nsHttpConnection*, bool usingSpdy,
+ bool disallowHttp3);
+
+ void ReportHttp3Connection(HttpConnectionBase*);
+
+ bool GetConnectionData(nsTArray<HttpRetParams>*);
+
+ void ResetIPFamilyPreference(nsHttpConnectionInfo*);
+
+ uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
+
+ // tracks and untracks active transactions according their throttle status
+ void AddActiveTransaction(nsHttpTransaction* aTrans);
+ void RemoveActiveTransaction(nsHttpTransaction* aTrans,
+ Maybe<bool> const& aOverride = Nothing());
+ void UpdateActiveTransaction(nsHttpTransaction* aTrans);
+
+ // called by nsHttpTransaction::WriteSegments. decides whether the
+ // transaction should limit reading its reponse data. There are various
+ // conditions this methods evaluates. If called by an active-tab
+ // non-throttled transaction, the throttling window time will be prolonged.
+ bool ShouldThrottle(nsHttpTransaction* aTrans);
+
+ // prolongs the throttling time window to now + the window preferred delay.
+ // called when:
+ // - any transaction is activated
+ // - or when a currently unthrottled transaction for the active window
+ // receives data
+ void TouchThrottlingTimeWindow(bool aEnsureTicker = true);
+
+ // return true iff the connection has pending transactions for the active tab.
+ // it's mainly used to disallow throttling (limit reading) of a response
+ // belonging to the same conn info to free up a connection ASAP.
+ // NOTE: relatively expensive to call, there are two hashtable lookups.
+ bool IsConnEntryUnderPressure(nsHttpConnectionInfo*);
+
+ uint64_t CurrentBrowserId() { return mCurrentBrowserId; }
+
+ void DoFallbackConnection(SpeculativeTransaction* aTrans, bool aFetchHTTPSRR);
+ void DoSpeculativeConnection(SpeculativeTransaction* aTrans,
+ bool aFetchHTTPSRR);
+
+ HttpConnectionBase* GetH2orH3ActiveConn(ConnectionEntry* ent, bool aNoHttp2,
+ bool aNoHttp3);
+
+ void IncreaseNumDnsAndConnectSockets();
+ void DecreaseNumDnsAndConnectSockets();
+
+ // Wen a new idle connection has been added, this function is called to
+ // increment mNumIdleConns and update PruneDeadConnections timer.
+ void NewIdleConnectionAdded(uint32_t timeToLive);
+ void DecrementNumIdleConns();
+
+ private:
+ virtual ~nsHttpConnectionMgr();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called on any thread.
+ //-------------------------------------------------------------------------
+
+ // Schedules next pruning of dead connection to happen after
+ // given time.
+ void PruneDeadConnectionsAfter(uint32_t time);
+
+ // Stops timer scheduled for next pruning of dead connections if
+ // there are no more idle connections or active spdy ones
+ void ConditionallyStopPruneDeadConnectionsTimer();
+
+ // Stops timer used for the read timeout tick if there are no currently
+ // active connections.
+ void ConditionallyStopTimeoutTick();
+
+ // called to close active connections with no registered "traffic"
+ [[nodiscard]] nsresult PruneNoTraffic();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called only on the socket thread.
+ //-------------------------------------------------------------------------
+
+ [[nodiscard]] bool ProcessPendingQForEntry(nsHttpConnectionInfo*);
+
+ // public, so that the SPDY/http2 seesions can activate
+ void ActivateTimeoutTick();
+
+ already_AddRefed<PendingTransactionInfo> FindTransactionHelper(
+ bool removeWhenFound, ConnectionEntry* aEnt, nsAHttpTransaction* aTrans);
+
+ void DoSpeculativeConnectionInternal(ConnectionEntry* aEnt,
+ SpeculativeTransaction* aTrans,
+ bool aFetchHTTPSRR);
+
+ already_AddRefed<ConnectionEntry> FindConnectionEntry(
+ const nsHttpConnectionInfo* ci);
+
+ public:
+ void RegisterOriginCoalescingKey(HttpConnectionBase*, const nsACString& host,
+ int32_t port);
+ // A test if be-conservative should be used when proxy is setup for the
+ // connection
+ bool BeConservativeIfProxied(nsIProxyInfo* proxy);
+
+ bool AllowToRetryDifferentIPFamilyForHttp3(nsHttpConnectionInfo* ci,
+ nsresult aError);
+ void SetRetryDifferentIPFamilyForHttp3(nsHttpConnectionInfo* ci,
+ uint16_t aIPFamily);
+
+ protected:
+ friend class ConnectionEntry;
+ void IncrementActiveConnCount();
+ void DecrementActiveConnCount(HttpConnectionBase*);
+
+ private:
+ friend class DnsAndConnectSocket;
+ friend class PendingTransactionInfo;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members may be accessed from any thread (use mReentrantMonitor)
+ //-------------------------------------------------------------------------
+
+ ReentrantMonitor mReentrantMonitor{"nsHttpConnectionMgr.mReentrantMonitor"};
+ // This is used as a flag that we're shut down, and no new events should be
+ // dispatched.
+ nsCOMPtr<nsIEventTarget> mSocketThreadTarget
+ MOZ_GUARDED_BY(mReentrantMonitor);
+
+ Atomic<bool, mozilla::Relaxed> mIsShuttingDown{false};
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members are only accessed on the socket transport thread
+ //-------------------------------------------------------------------------
+ // connection limits
+ uint16_t mMaxUrgentExcessiveConns{0};
+ uint16_t mMaxConns{0};
+ uint16_t mMaxPersistConnsPerHost{0};
+ uint16_t mMaxPersistConnsPerProxy{0};
+ uint16_t mMaxRequestDelay{0}; // in seconds
+ bool mThrottleEnabled{false};
+ uint32_t mThrottleVersion{2};
+ uint32_t mThrottleSuspendFor{0};
+ uint32_t mThrottleResumeFor{0};
+ uint32_t mThrottleReadLimit{0};
+ uint32_t mThrottleReadInterval{0};
+ uint32_t mThrottleHoldTime{0};
+ TimeDuration mThrottleMaxTime;
+ bool mBeConservativeForProxy{true};
+
+ [[nodiscard]] bool ProcessPendingQForEntry(ConnectionEntry*,
+ bool considerAll);
+ bool DispatchPendingQ(nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ ConnectionEntry* ent, bool considerAll);
+
+ // This function selects transactions from mPendingTransactionTable to
+ // dispatch according to the following conditions:
+ // 1. When ActiveTabPriority() is false, only get transactions from the
+ // queue whose window id is 0.
+ // 2. If |considerAll| is false, either get transactions from the focused
+ // window queue or non-focused ones.
+ // 3. If |considerAll| is true, fill the |pendingQ| with the transactions from
+ // both focused window and non-focused window queues.
+ void PreparePendingQForDispatching(
+ ConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ bool considerAll);
+
+ // Return |mMaxPersistConnsPerProxy| or |mMaxPersistConnsPerHost|,
+ // depending whether the proxy is used.
+ uint32_t MaxPersistConnections(ConnectionEntry* ent) const;
+
+ bool AtActiveConnectionLimit(ConnectionEntry*, uint32_t caps);
+ [[nodiscard]] nsresult TryDispatchTransaction(
+ ConnectionEntry* ent, bool onlyReusedConnection,
+ PendingTransactionInfo* pendingTransInfo);
+ [[nodiscard]] nsresult TryDispatchTransactionOnIdleConn(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo,
+ bool respectUrgency, bool* allUrgent = nullptr);
+ [[nodiscard]] nsresult DispatchTransaction(ConnectionEntry*,
+ nsHttpTransaction*,
+ HttpConnectionBase*);
+ [[nodiscard]] nsresult DispatchAbstractTransaction(ConnectionEntry*,
+ nsAHttpTransaction*,
+ uint32_t,
+ HttpConnectionBase*,
+ int32_t);
+ [[nodiscard]] nsresult EnsureSocketThreadTarget();
+ void ReportProxyTelemetry(ConnectionEntry* ent);
+ void StartedConnect();
+ void RecvdConnect();
+
+ ConnectionEntry* GetOrCreateConnectionEntry(
+ nsHttpConnectionInfo*, bool prohibitWildCard, bool aNoHttp2,
+ bool aNoHttp3, bool* aIsWildcard,
+ bool* aAvailableForDispatchNow = nullptr);
+
+ [[nodiscard]] nsresult MakeNewConnection(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo);
+
+ // Manage h2/3 connection coalescing
+ // The hashtable contains arrays of weak pointers to HttpConnectionBases
+ nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr>> mCoalescingHash;
+
+ HttpConnectionBase* FindCoalescableConnection(ConnectionEntry* ent,
+ bool justKidding, bool aNoHttp2,
+ bool aNoHttp3);
+ HttpConnectionBase* FindCoalescableConnectionByHashKey(ConnectionEntry* ent,
+ const nsCString& key,
+ bool justKidding,
+ bool aNoHttp2,
+ bool aNoHttp3);
+ void UpdateCoalescingForNewConn(HttpConnectionBase* conn,
+ ConnectionEntry* ent, bool aNoHttp3);
+
+ void ProcessSpdyPendingQ(ConnectionEntry* ent);
+ void DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ ConnectionEntry* ent, HttpConnectionBase* connH2,
+ HttpConnectionBase* connH3);
+ // used to marshall events to the socket transport thread.
+ [[nodiscard]] nsresult PostEvent(nsConnEventHandler handler,
+ int32_t iparam = 0,
+ ARefBase* vparam = nullptr);
+
+ void OnMsgReclaimConnection(HttpConnectionBase*);
+
+ // message handlers
+ void OnMsgShutdown(int32_t, ARefBase*);
+ void OnMsgShutdownConfirm(int32_t, ARefBase*);
+ void OnMsgNewTransaction(int32_t, ARefBase*);
+ void OnMsgNewTransactionWithStickyConn(int32_t, ARefBase*);
+ void OnMsgReschedTransaction(int32_t, ARefBase*);
+ void OnMsgUpdateClassOfServiceOnTransaction(ClassOfService, ARefBase*);
+ void OnMsgCancelTransaction(int32_t, ARefBase*);
+ void OnMsgCancelTransactions(int32_t, ARefBase*);
+ void OnMsgProcessPendingQ(int32_t, ARefBase*);
+ void OnMsgPruneDeadConnections(int32_t, ARefBase*);
+ void OnMsgSpeculativeConnect(int32_t, ARefBase*);
+ void OnMsgCompleteUpgrade(int32_t, ARefBase*);
+ void OnMsgUpdateParam(int32_t, ARefBase*);
+ void OnMsgDoShiftReloadConnectionCleanup(int32_t, ARefBase*);
+ void OnMsgDoSingleConnectionCleanup(int32_t, ARefBase*);
+ void OnMsgProcessFeedback(int32_t, ARefBase*);
+ void OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*);
+ void OnMsgUpdateRequestTokenBucket(int32_t, ARefBase*);
+ void OnMsgVerifyTraffic(int32_t, ARefBase*);
+ void OnMsgPruneNoTraffic(int32_t, ARefBase*);
+ void OnMsgUpdateCurrentBrowserId(int32_t, ARefBase*);
+ void OnMsgClearConnectionHistory(int32_t, ARefBase*);
+
+ // Total number of active connections in all of the ConnectionEntry objects
+ // that are accessed from mCT connection table.
+ uint16_t mNumActiveConns{0};
+ // Total number of idle connections in all of the ConnectionEntry objects
+ // that are accessed from mCT connection table.
+ uint16_t mNumIdleConns{0};
+ // Total number of spdy or http3 connections which are a subset of the active
+ // conns
+ uint16_t mNumSpdyHttp3ActiveConns{0};
+ // Total number of connections in DnsAndConnectSockets ConnectionEntry objects
+ // that are accessed from mCT connection table
+ uint32_t mNumDnsAndConnectSockets{0};
+
+ // Holds time in seconds for next wake-up to prune dead connections.
+ uint64_t mTimeOfNextWakeUp{UINT64_MAX};
+ // Timer for next pruning of dead connections.
+ nsCOMPtr<nsITimer> mTimer;
+ // Timer for pruning stalled connections after changed network.
+ nsCOMPtr<nsITimer> mTrafficTimer;
+ bool mPruningNoTraffic{false};
+
+ // A 1s tick to call nsHttpConnection::ReadTimeoutTick on
+ // active http/1 connections and check for orphaned half opens.
+ // Disabled when there are no active or half open connections.
+ nsCOMPtr<nsITimer> mTimeoutTick;
+ bool mTimeoutTickArmed{false};
+ uint32_t mTimeoutTickNext{1};
+
+ //
+ // the connection table
+ //
+ // this table is indexed by connection key. each entry is a
+ // ConnectionEntry object. It is unlocked and therefore must only
+ // be accessed from the socket thread.
+ //
+ nsRefPtrHashtable<nsCStringHashKey, ConnectionEntry> mCT;
+
+ // Read Timeout Tick handlers
+ void TimeoutTick();
+
+ // For diagnostics
+ void OnMsgPrintDiagnostics(int32_t, ARefBase*);
+
+ nsCString mLogData;
+ uint64_t mCurrentBrowserId{0};
+
+ // Called on a pref change
+ void SetThrottlingEnabled(bool aEnable);
+
+ // we only want to throttle for a limited amount of time after a new
+ // active transaction is added so that we don't block downloads on comet,
+ // socket and any kind of longstanding requests that don't need bandwidth.
+ // these methods track this time.
+ bool InThrottlingTimeWindow();
+
+ // Two hashtalbes keeping track of active transactions regarding window id and
+ // throttling. Used by the throttling algorithm to obtain number of
+ // transactions for the active tab and for inactive tabs according their
+ // throttle status. mActiveTransactions[0] are all unthrottled transactions,
+ // mActiveTransactions[1] throttled.
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>
+ mActiveTransactions[2];
+
+ // V1 specific
+ // Whether we are inside the "stop reading" interval, altered by the throttle
+ // ticker
+ bool mThrottlingInhibitsReading{false};
+
+ TimeStamp mThrottlingWindowEndsAt;
+
+ // ticker for the 'stop reading'/'resume reading' signal
+ nsCOMPtr<nsITimer> mThrottleTicker;
+ // Checks if the combination of active transactions requires the ticker.
+ bool IsThrottleTickerNeeded();
+ // The method also unschedules the delayed resume of background tabs timer
+ // if the ticker was about to be scheduled.
+ void EnsureThrottleTickerIfNeeded();
+ // V1:
+ // Drops also the mThrottlingInhibitsReading flag. Immediate or delayed
+ // resume of currently throttled transactions is not affected by this method.
+ // V2:
+ // Immediate or delayed resume of currently throttled transactions is not
+ // affected by this method.
+ void DestroyThrottleTicker();
+ // V1:
+ // Handler for the ticker: alters the mThrottlingInhibitsReading flag.
+ // V2:
+ // Handler for the ticker: calls ResumeReading() for all throttled
+ // transactions.
+ void ThrottlerTick();
+
+ // mechanism to delay immediate resume of background tabs and chrome initiated
+ // throttled transactions after the last transaction blocking their unthrottle
+ // has been removed. Needs to be delayed because during a page load there is
+ // a number of intervals when there is no transaction that would cause
+ // throttling. Hence, throttling of long standing responses, like downloads,
+ // would be mostly ineffective if resumed during every such interval.
+ nsCOMPtr<nsITimer> mDelayedResumeReadTimer;
+ // Schedule the resume
+ void DelayedResumeBackgroundThrottledTransactions();
+ // Simply destroys the timer
+ void CancelDelayedResumeBackgroundThrottledTransactions();
+ // Handler for the timer: resumes all background throttled transactions
+ void ResumeBackgroundThrottledTransactions();
+
+ // Simple helpers, iterates the given hash/array and resume.
+ // @param excludeActive: skip active tabid transactions.
+ void ResumeReadOf(
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&,
+ bool excludeForActiveTab = false);
+ void ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>*);
+
+ // Cached status of the active tab active transactions existence,
+ // saves a lot of hashtable lookups
+ bool mActiveTabTransactionsExist{false};
+ bool mActiveTabUnthrottledTransactionsExist{false};
+
+ void LogActiveTransactions(char);
+
+ // When current active tab is changed, this function uses
+ // |previousId| to select background transactions and
+ // |mCurrentBrowserId| to select foreground transactions.
+ // Then, it notifies selected transactions' connection of the new active tab
+ // id.
+ void NotifyConnectionOfBrowserIdChange(uint64_t previousId);
+
+ void CheckTransInPendingQueue(nsHttpTransaction* aTrans);
+};
+
+} // namespace mozilla::net
+
+#endif // !nsHttpConnectionMgr_h__
diff --git a/netwerk/protocol/http/nsHttpDigestAuth.cpp b/netwerk/protocol/http/nsHttpDigestAuth.cpp
new file mode 100644
index 0000000000..2a98301942
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp
@@ -0,0 +1,743 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+
+#include "nsHttp.h"
+#include "nsHttpDigestAuth.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsEscape.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsICryptoHash.h"
+#include "nsComponentManagerUtils.h"
+
+constexpr uint16_t DigestLength(uint16_t aAlgorithm) {
+ if (aAlgorithm & (ALGO_SHA256 | ALGO_SHA256_SESS)) {
+ return SHA256_DIGEST_LENGTH;
+ }
+ return MD5_DIGEST_LENGTH;
+}
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<nsHttpDigestAuth> nsHttpDigestAuth::gSingleton;
+
+already_AddRefed<nsIHttpAuthenticator> nsHttpDigestAuth::GetOrCreate() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (gSingleton) {
+ authenticator = gSingleton;
+ } else {
+ gSingleton = new nsHttpDigestAuth();
+ ClearOnShutdown(&gSingleton);
+ authenticator = gSingleton;
+ }
+
+ return authenticator.forget();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpDigestAuth, nsIHttpAuthenticator)
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth <protected>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpDigestAuth::DigestHash(const char* buf, uint32_t len,
+ uint16_t algorithm) {
+ nsresult rv;
+
+ // Cache a reference to the nsICryptoHash instance since we'll be calling
+ // this function frequently.
+ if (!mVerifier) {
+ mVerifier = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpDigestAuth: no crypto hash!\n"));
+ return rv;
+ }
+ }
+
+ uint32_t dlen;
+ if (algorithm & (ALGO_SHA256 | ALGO_SHA256_SESS)) {
+ rv = mVerifier->Init(nsICryptoHash::SHA256);
+ dlen = SHA256_DIGEST_LENGTH;
+ } else {
+ rv = mVerifier->Init(nsICryptoHash::MD5);
+ dlen = MD5_DIGEST_LENGTH;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mVerifier->Update((unsigned char*)buf, len);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString hashString;
+ rv = mVerifier->Finish(false, hashString);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_STATE(hashString.Length() == dlen);
+ memcpy(mHashBuf, hashString.get(), hashString.Length());
+
+ return rv;
+}
+
+nsresult nsHttpDigestAuth::GetMethodAndPath(
+ nsIHttpAuthenticableChannel* authChannel, bool isProxyAuth,
+ nsCString& httpMethod, nsCString& path) {
+ nsresult rv, rv2;
+ nsCOMPtr<nsIURI> uri;
+ rv = authChannel->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ bool proxyMethodIsConnect;
+ rv = authChannel->GetProxyMethodIsConnect(&proxyMethodIsConnect);
+ if (NS_SUCCEEDED(rv)) {
+ if (proxyMethodIsConnect && isProxyAuth) {
+ httpMethod.AssignLiteral("CONNECT");
+ //
+ // generate hostname:port string. (unfortunately uri->GetHostPort
+ // leaves out the port if it matches the default value, so we can't
+ // just call it.)
+ //
+ int32_t port;
+ rv = uri->GetAsciiHost(path);
+ rv2 = uri->GetPort(&port);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2)) {
+ path.Append(':');
+ path.AppendInt(port < 0 ? NS_HTTPS_DEFAULT_PORT : port);
+ }
+ } else {
+ rv = authChannel->GetRequestMethod(httpMethod);
+ rv2 = uri->GetPathQueryRef(path);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2)) {
+ //
+ // strip any fragment identifier from the URL path.
+ //
+ int32_t ref = path.RFindChar('#');
+ if (ref != kNotFound) path.Truncate(ref);
+ //
+ // make sure we escape any UTF-8 characters in the URI path. the
+ // digest auth uri attribute needs to match the request-URI.
+ //
+ // XXX we should really ask the HTTP channel for this string
+ // instead of regenerating it here.
+ //
+ nsAutoCString buf;
+ rv = NS_EscapeURL(path, esc_OnlyNonASCII | esc_Spaces, buf,
+ mozilla::fallible);
+ if (NS_SUCCEEDED(rv)) {
+ path = buf;
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth::nsIHttpAuthenticator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpDigestAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel,
+ const nsACString& challenge,
+ bool isProxyAuth,
+ nsISupports** sessionState,
+ nsISupports** continuationState,
+ bool* result) {
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale;
+ uint16_t algorithm, qop;
+
+ nsresult rv = ParseChallenge(challenge, realm, domain, nonce, opaque, &stale,
+ &algorithm, &qop);
+
+ if (!(algorithm &
+ (ALGO_MD5 | ALGO_MD5_SESS | ALGO_SHA256 | ALGO_SHA256_SESS))) {
+ // they asked for an algorithm that we do not support yet (like SHA-512/256)
+ NS_WARNING("unsupported algorithm requested by Digest authentication");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ // if the challenge has the "stale" flag set, then the user identity is not
+ // necessarily invalid. by returning FALSE here we can suppress username
+ // and password prompting that usually accompanies a 401/407 challenge.
+ *result = !stale;
+
+ // clear any existing nonce_count since we have a new challenge.
+ NS_IF_RELEASE(*sessionState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* authChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& challenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& username,
+ const nsAString& password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& userdomain, const nsAString& username,
+ const nsAString& password, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, nsACString& creds)
+
+{
+ LOG(("nsHttpDigestAuth::GenerateCredentials [challenge=%s]\n",
+ aChallenge.BeginReading()));
+
+ *aFlags = 0;
+
+ bool isDigestAuth = StringBeginsWith(aChallenge, "digest "_ns,
+ nsCaseInsensitiveCStringComparator);
+ NS_ENSURE_TRUE(isDigestAuth, NS_ERROR_UNEXPECTED);
+
+ // IIS implementation requires extra quotes
+ bool requireExtraQuotes = false;
+ {
+ nsAutoCString serverVal;
+ Unused << authChannel->GetServerResponseHeader(serverVal);
+ if (!serverVal.IsEmpty()) {
+ requireExtraQuotes =
+ !nsCRT::strncasecmp(serverVal.get(), "Microsoft-IIS", 13);
+ }
+ }
+
+ nsresult rv;
+ nsAutoCString httpMethod;
+ nsAutoCString path;
+ rv = GetMethodAndPath(authChannel, isProxyAuth, httpMethod, path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale;
+ uint16_t algorithm, qop;
+
+ rv = ParseChallenge(aChallenge, realm, domain, nonce, opaque, &stale,
+ &algorithm, &qop);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpDigestAuth::GenerateCredentials [ParseChallenge failed "
+ "rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ const uint32_t dhexlen = 2 * DigestLength(algorithm) + 1;
+ char ha1_digest[dhexlen];
+ char ha2_digest[dhexlen];
+ char response_digest[dhexlen];
+ char upload_data_digest[dhexlen];
+
+ if (qop & QOP_AUTH_INT) {
+ // we do not support auth-int "quality of protection" currently
+ qop &= ~QOP_AUTH_INT;
+
+ NS_WARNING(
+ "no support for Digest authentication with data integrity quality of "
+ "protection");
+
+ /* TODO: to support auth-int, we need to get an MD5 digest of
+ * TODO: the data uploaded with this request.
+ * TODO: however, i am not sure how to read in the file in without
+ * TODO: disturbing the channel''s use of it. do i need to copy it
+ * TODO: somehow?
+ */
+#if 0
+ if (http_channel != nullptr)
+ {
+ nsIInputStream * upload;
+ nsCOMPtr<nsIUploadChannel> uc = do_QueryInterface(http_channel);
+ NS_ENSURE_TRUE(uc, NS_ERROR_UNEXPECTED);
+ uc->GetUploadStream(&upload);
+ if (upload) {
+ char * upload_buffer;
+ int upload_buffer_length = 0;
+ //TODO: read input stream into buffer
+ const char * digest = (const char*)
+ nsNetwerkMD5Digest(upload_buffer, upload_buffer_length);
+ ExpandToHex(digest, upload_data_digest);
+ NS_RELEASE(upload);
+ }
+ }
+#endif
+ }
+
+ if (!(algorithm &
+ (ALGO_MD5 | ALGO_MD5_SESS | ALGO_SHA256 | ALGO_SHA256_SESS))) {
+ // they asked only for algorithms that we do not support
+ NS_WARNING("unsupported algorithm requested by Digest authentication");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ //
+ // the following are for increasing security. see RFC 2617 for more
+ // information.
+ //
+ // nonce_count allows the server to keep track of auth challenges (to help
+ // prevent spoofing). we increase this count every time.
+ //
+ char nonce_count[NONCE_COUNT_LENGTH + 1] = "00000001"; // in hex
+ if (*sessionState) {
+ nsCOMPtr<nsISupportsPRUint32> v(do_QueryInterface(*sessionState));
+ if (v) {
+ uint32_t nc;
+ v->GetData(&nc);
+ SprintfLiteral(nonce_count, "%08x", ++nc);
+ v->SetData(nc);
+ }
+ } else {
+ nsCOMPtr<nsISupportsPRUint32> v(
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID));
+ if (v) {
+ v->SetData(1);
+ v.forget(sessionState);
+ }
+ }
+ LOG((" nonce_count=%s\n", nonce_count));
+
+ //
+ // this lets the client verify the server response (via a server
+ // returned Authentication-Info header). also used for session info.
+ //
+ nsAutoCString cnonce;
+ static const char hexChar[] = "0123456789abcdef";
+ for (int i = 0; i < 16; ++i) {
+ cnonce.Append(hexChar[(int)(15.0 * rand() / (RAND_MAX + 1.0))]);
+ }
+ LOG((" cnonce=%s\n", cnonce.get()));
+
+ //
+ // calculate credentials
+ //
+
+ NS_ConvertUTF16toUTF8 cUser(username), cPass(password);
+ rv = CalculateHA1(cUser, cPass, realm, algorithm, nonce, cnonce, ha1_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CalculateHA2(httpMethod, path, algorithm, qop, upload_data_digest,
+ ha2_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CalculateResponse(ha1_digest, ha2_digest, algorithm, nonce, qop,
+ nonce_count, cnonce, response_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ //
+ // Values that need to match the quoted-string production from RFC 2616:
+ //
+ // username
+ // realm
+ // nonce
+ // opaque
+ // cnonce
+ //
+
+ nsAutoCString authString;
+
+ authString.AssignLiteral("Digest username=");
+ rv = AppendQuotedString(cUser, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", realm=");
+ rv = AppendQuotedString(realm, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", nonce=");
+ rv = AppendQuotedString(nonce, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", uri=\"");
+ authString += path;
+ if (algorithm & ALGO_SPECIFIED) {
+ authString.AppendLiteral("\", algorithm=");
+ if (algorithm & ALGO_MD5_SESS) {
+ authString.AppendLiteral("MD5-sess");
+ } else if (algorithm & ALGO_SHA256) {
+ authString.AppendLiteral("SHA-256");
+ } else if (algorithm & ALGO_SHA256_SESS) {
+ authString.AppendLiteral("SHA-256-sess");
+ } else {
+ authString.AppendLiteral("MD5");
+ }
+ } else {
+ authString += '\"';
+ }
+ authString.AppendLiteral(", response=\"");
+ authString += response_digest;
+ authString += '\"';
+
+ if (!opaque.IsEmpty()) {
+ authString.AppendLiteral(", opaque=");
+ rv = AppendQuotedString(opaque, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (qop) {
+ authString.AppendLiteral(", qop=");
+ if (requireExtraQuotes) authString += '\"';
+ authString.AppendLiteral("auth");
+ if (qop & QOP_AUTH_INT) authString.AppendLiteral("-int");
+ if (requireExtraQuotes) authString += '\"';
+ authString.AppendLiteral(", nc=");
+ authString += nonce_count;
+
+ authString.AppendLiteral(", cnonce=");
+ rv = AppendQuotedString(cnonce, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ creds = authString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GetAuthFlags(uint32_t* flags) {
+ *flags = REQUEST_BASED | REUSABLE_CHALLENGE | IDENTITY_ENCRYPTED;
+ //
+ // NOTE: digest auth credentials must be uniquely computed for each request,
+ // so we do not set the REUSABLE_CREDENTIALS flag.
+ //
+ return NS_OK;
+}
+
+nsresult nsHttpDigestAuth::CalculateResponse(
+ const char* ha1_digest, const char* ha2_digest, uint16_t algorithm,
+ const nsCString& nonce, uint16_t qop, const char* nonce_count,
+ const nsCString& cnonce, char* result) {
+ const uint32_t dhexlen = 2 * DigestLength(algorithm);
+ uint32_t len = 2 * dhexlen + nonce.Length() + 2;
+
+ if (qop & QOP_AUTH || qop & QOP_AUTH_INT) {
+ len += cnonce.Length() + NONCE_COUNT_LENGTH + 3;
+ if (qop & QOP_AUTH_INT) {
+ len += 8; // length of "auth-int"
+ } else {
+ len += 4; // length of "auth"
+ }
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Append(ha1_digest, dhexlen);
+ contents.Append(':');
+ contents.Append(nonce);
+ contents.Append(':');
+
+ if (qop & QOP_AUTH || qop & QOP_AUTH_INT) {
+ contents.Append(nonce_count, NONCE_COUNT_LENGTH);
+ contents.Append(':');
+ contents.Append(cnonce);
+ contents.Append(':');
+ if (qop & QOP_AUTH_INT) {
+ contents.AppendLiteral("auth-int:");
+ } else {
+ contents.AppendLiteral("auth:");
+ }
+ }
+
+ contents.Append(ha2_digest, dhexlen);
+
+ nsresult rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result, algorithm);
+ return rv;
+}
+
+nsresult nsHttpDigestAuth::ExpandToHex(const char* digest, char* result,
+ uint16_t algorithm) {
+ int16_t index, value;
+ const int16_t dlen = DigestLength(algorithm);
+
+ for (index = 0; index < dlen; index++) {
+ value = (digest[index] >> 4) & 0xf;
+ if (value < 10) {
+ result[index * 2] = value + '0';
+ } else {
+ result[index * 2] = value - 10 + 'a';
+ }
+
+ value = digest[index] & 0xf;
+ if (value < 10) {
+ result[(index * 2) + 1] = value + '0';
+ } else {
+ result[(index * 2) + 1] = value - 10 + 'a';
+ }
+ }
+
+ result[2 * dlen] = 0;
+ return NS_OK;
+}
+
+nsresult nsHttpDigestAuth::CalculateHA1(const nsCString& username,
+ const nsCString& password,
+ const nsCString& realm,
+ uint16_t algorithm,
+ const nsCString& nonce,
+ const nsCString& cnonce, char* result) {
+ const int16_t dhexlen = 2 * DigestLength(algorithm);
+ int16_t len = username.Length() + password.Length() + realm.Length() + 2;
+ if (algorithm & (ALGO_MD5_SESS | ALGO_SHA256_SESS)) {
+ int16_t exlen = dhexlen + nonce.Length() + cnonce.Length() + 2;
+ if (exlen > len) len = exlen;
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Append(username);
+ contents.Append(':');
+ contents.Append(realm);
+ contents.Append(':');
+ contents.Append(password);
+
+ nsresult rv;
+ rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_FAILED(rv)) return rv;
+
+ if (algorithm & (ALGO_MD5_SESS | ALGO_SHA256_SESS)) {
+ char part1[dhexlen + 1];
+ rv = ExpandToHex(mHashBuf, part1, algorithm);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ contents.Assign(part1, dhexlen);
+ contents.Append(':');
+ contents.Append(nonce);
+ contents.Append(':');
+ contents.Append(cnonce);
+
+ rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return ExpandToHex(mHashBuf, result, algorithm);
+}
+
+nsresult nsHttpDigestAuth::CalculateHA2(const nsCString& method,
+ const nsCString& path,
+ uint16_t algorithm, uint16_t qop,
+ const char* bodyDigest, char* result) {
+ uint16_t methodLen = method.Length();
+ uint32_t pathLen = path.Length();
+ uint32_t len = methodLen + pathLen + 1;
+ const uint32_t dhexlen = 2 * DigestLength(algorithm);
+
+ if (qop & QOP_AUTH_INT) {
+ len += dhexlen + 1;
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Assign(method);
+ contents.Append(':');
+ contents.Append(path);
+
+ if (qop & QOP_AUTH_INT) {
+ contents.Append(':');
+ contents.Append(bodyDigest, dhexlen);
+ }
+
+ nsresult rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ExpandToHex(mHashBuf, result, algorithm);
+}
+
+nsresult nsHttpDigestAuth::ParseChallenge(const nsACString& aChallenge,
+ nsACString& realm, nsACString& domain,
+ nsACString& nonce, nsACString& opaque,
+ bool* stale, uint16_t* algorithm,
+ uint16_t* qop) {
+ // put an absurd, but maximum, length cap on the challenge so
+ // that calculations are 32 bit safe
+ if (aChallenge.Length() > 16000000) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const char* challenge = aChallenge.BeginReading();
+ const char* end = aChallenge.EndReading();
+ const char* p = challenge + 6; // first 6 characters are "Digest"
+ if (p >= end) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *stale = false;
+ *algorithm = ALGO_MD5; // default is MD5
+ *qop = 0;
+
+ for (;;) {
+ while (p < end && (*p == ',' || nsCRT::IsAsciiSpace(*p))) {
+ ++p;
+ }
+ if (p >= end) {
+ break;
+ }
+
+ // name
+ int32_t nameStart = (p - challenge);
+ while (p < end && !nsCRT::IsAsciiSpace(*p) && *p != '=') {
+ ++p;
+ }
+ if (p >= end) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ int32_t nameLength = (p - challenge) - nameStart;
+
+ while (p < end && (nsCRT::IsAsciiSpace(*p) || *p == '=')) {
+ ++p;
+ }
+ if (p >= end) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool quoted = false;
+ if (*p == '"') {
+ ++p;
+ quoted = true;
+ }
+
+ // value
+ int32_t valueStart = (p - challenge);
+ int32_t valueLength = 0;
+ if (quoted) {
+ while (p < end && *p != '"') {
+ ++p;
+ }
+ if (p >= end || *p != '"') {
+ return NS_ERROR_INVALID_ARG;
+ }
+ valueLength = (p - challenge) - valueStart;
+ ++p;
+ } else {
+ while (p < end && !nsCRT::IsAsciiSpace(*p) && *p != ',') {
+ ++p;
+ }
+ valueLength = (p - challenge) - valueStart;
+ }
+
+ // extract information
+ if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge + nameStart, "realm", 5) == 0) {
+ realm.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 6 &&
+ nsCRT::strncasecmp(challenge + nameStart, "domain", 6) == 0) {
+ domain.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge + nameStart, "nonce", 5) == 0) {
+ nonce.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 6 &&
+ nsCRT::strncasecmp(challenge + nameStart, "opaque", 6) == 0) {
+ opaque.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge + nameStart, "stale", 5) == 0) {
+ if (nsCRT::strncasecmp(challenge + valueStart, "true", 4) == 0) {
+ *stale = true;
+ } else {
+ *stale = false;
+ }
+ } else if (nameLength == 9 &&
+ nsCRT::strncasecmp(challenge + nameStart, "algorithm", 9) == 0) {
+ // we want to clear the default, so we use = not |= here
+ *algorithm = ALGO_SPECIFIED;
+ if (valueLength == 3 &&
+ nsCRT::strncasecmp(challenge + valueStart, "MD5", 3) == 0) {
+ *algorithm |= ALGO_MD5;
+ } else if (valueLength == 8 && nsCRT::strncasecmp(challenge + valueStart,
+ "MD5-sess", 8) == 0) {
+ *algorithm |= ALGO_MD5_SESS;
+ } else if (valueLength == 7 && nsCRT::strncasecmp(challenge + valueStart,
+ "SHA-256", 7) == 0) {
+ *algorithm |= ALGO_SHA256;
+ } else if (valueLength == 12 &&
+ nsCRT::strncasecmp(challenge + valueStart, "SHA-256-sess",
+ 12) == 0) {
+ *algorithm |= ALGO_SHA256_SESS;
+ }
+ } else if (nameLength == 3 &&
+ nsCRT::strncasecmp(challenge + nameStart, "qop", 3) == 0) {
+ int32_t ipos = valueStart;
+ while (ipos < valueStart + valueLength) {
+ while (
+ ipos < valueStart + valueLength &&
+ (nsCRT::IsAsciiSpace(challenge[ipos]) || challenge[ipos] == ',')) {
+ ipos++;
+ }
+ int32_t algostart = ipos;
+ while (ipos < valueStart + valueLength &&
+ !nsCRT::IsAsciiSpace(challenge[ipos]) &&
+ challenge[ipos] != ',') {
+ ipos++;
+ }
+ if ((ipos - algostart) == 4 &&
+ nsCRT::strncasecmp(challenge + algostart, "auth", 4) == 0) {
+ *qop |= QOP_AUTH;
+ } else if ((ipos - algostart) == 8 &&
+ nsCRT::strncasecmp(challenge + algostart, "auth-int", 8) ==
+ 0) {
+ *qop |= QOP_AUTH_INT;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpDigestAuth::AppendQuotedString(const nsACString& value,
+ nsACString& aHeaderLine) {
+ nsAutoCString quoted;
+ nsACString::const_iterator s, e;
+ value.BeginReading(s);
+ value.EndReading(e);
+
+ //
+ // Encode string according to RFC 2616 quoted-string production
+ //
+ quoted.Append('"');
+ for (; s != e; ++s) {
+ //
+ // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+ //
+ if (*s <= 31 || *s == 127) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Escape two syntactically significant characters
+ if (*s == '"' || *s == '\\') {
+ quoted.Append('\\');
+ }
+
+ quoted.Append(*s);
+ }
+ // FIXME: bug 41489
+ // We should RFC2047-encode non-Latin-1 values according to spec
+ quoted.Append('"');
+ aHeaderLine.Append(quoted);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+// vim: ts=2 sw=2
diff --git a/netwerk/protocol/http/nsHttpDigestAuth.h b/netwerk/protocol/http/nsHttpDigestAuth.h
new file mode 100644
index 0000000000..d9830a17dc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.h
@@ -0,0 +1,98 @@
+/* -*- 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 nsDigestAuth_h__
+#define nsDigestAuth_h__
+
+#include "nsICryptoHash.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsStringFwd.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+#define ALGO_SPECIFIED 0x01
+#define ALGO_MD5 0x02
+#define ALGO_MD5_SESS 0x04
+#define ALGO_SHA256 0x08
+#define ALGO_SHA256_SESS 0x10
+#define QOP_AUTH 0x01
+#define QOP_AUTH_INT 0x02
+
+#define NONCE_COUNT_LENGTH 8
+#ifndef MD5_DIGEST_LENGTH
+# define MD5_DIGEST_LENGTH 16
+#endif
+#ifndef SHA256_DIGEST_LENGTH
+# define SHA256_DIGEST_LENGTH 32
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth
+//-----------------------------------------------------------------------------
+
+class nsHttpDigestAuth final : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpDigestAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> GetOrCreate();
+
+ [[nodiscard]] static nsresult ParseChallenge(
+ const nsACString& aChallenge, nsACString& realm, nsACString& domain,
+ nsACString& nonce, nsACString& opaque, bool* stale, uint16_t* algorithm,
+ uint16_t* qop);
+
+ protected:
+ ~nsHttpDigestAuth() = default;
+
+ [[nodiscard]] nsresult ExpandToHex(const char* digest, char* result,
+ uint16_t algorithm);
+
+ [[nodiscard]] nsresult CalculateResponse(
+ const char* ha1_digest, const char* ha2_digest, uint16_t algorithm,
+ const nsCString& nonce, uint16_t qop, const char* nonce_count,
+ const nsCString& cnonce, char* result);
+
+ [[nodiscard]] nsresult CalculateHA1(const nsCString& username,
+ const nsCString& password,
+ const nsCString& realm,
+ uint16_t algorithm,
+ const nsCString& nonce,
+ const nsCString& cnonce, char* result);
+
+ [[nodiscard]] nsresult CalculateHA2(const nsCString& http_method,
+ const nsCString& http_uri_path,
+ uint16_t algorithm, uint16_t qop,
+ const char* bodyDigest, char* result);
+
+ // result is in mHashBuf
+ [[nodiscard]] nsresult DigestHash(const char* buf, uint32_t len,
+ uint16_t algorithm);
+
+ [[nodiscard]] nsresult GetMethodAndPath(nsIHttpAuthenticableChannel*, bool,
+ nsCString&, nsCString&);
+
+ // append the quoted version of value to aHeaderLine
+ [[nodiscard]] nsresult AppendQuotedString(const nsACString& value,
+ nsACString& aHeaderLine);
+
+ protected:
+ nsCOMPtr<nsICryptoHash> mVerifier;
+ char mHashBuf[SHA256_DIGEST_LENGTH]{0};
+
+ static StaticRefPtr<nsHttpDigestAuth> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpDigestAuth_h__
diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp
new file mode 100644
index 0000000000..dcc5307984
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -0,0 +1,2812 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "prsystem.h"
+
+#include "AltServiceChild.h"
+#include "nsCORSListenerProxy.h"
+#include "nsError.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpChannel.h"
+#include "nsHTTPCompressConv.h"
+#include "nsHttpAuthCache.h"
+#include "nsStandardURL.h"
+#include "LoadContextInfo.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsSocketProviderService.h"
+#include "nsISocketProvider.h"
+#include "nsPrintfCString.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/Printf.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsSocketTransportService2.h"
+#include "nsAlgorithm.h"
+#include "ASpdySession.h"
+#include "EventTokenBucket.h"
+#include "Tickler.h"
+#include "nsIXULAppInfo.h"
+#include "nsICookieService.h"
+#include "nsIObserverService.h"
+#include "nsISiteSecurityService.h"
+#include "nsIStreamConverterService.h"
+#include "nsCRT.h"
+#include "nsIParentalControlsService.h"
+#include "nsPIDOMWindow.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsINetworkLinkService.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsIOService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIXULRuntime.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsRFPService.h"
+#include "mozilla/net/rust_helper.h"
+
+#include "mozilla/net/HttpConnectionMgrParent.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/RequestContextService.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/AntiTrackingRedirectHeuristic.h"
+#include "mozilla/DynamicFpiRedirectHeuristic.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/SyncRunnable.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/network/Connection.h"
+
+#include "nsNSSComponent.h"
+#include "TRRServiceChannel.h"
+
+#include <bitset>
+
+#if defined(XP_UNIX)
+# include <sys/utsname.h>
+#endif
+
+#if defined(MOZ_WIDGET_GTK)
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include "mozilla/WindowsVersion.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include <CoreServices/CoreServices.h>
+#endif
+
+//-----------------------------------------------------------------------------
+#include "mozilla/net/HttpChannelChild.h"
+
+#define UA_PREF_PREFIX "general.useragent."
+#ifdef XP_WIN
+# define UA_SPARE_PLATFORM
+#endif
+
+#define HTTP_PREF_PREFIX "network.http."
+#define INTL_ACCEPT_LANGUAGES "intl.accept_languages"
+#define BROWSER_PREF_PREFIX "browser.cache."
+#define H2MANDATORY_SUITE "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256"
+#define SAFE_HINT_HEADER_VALUE "safeHint.enabled"
+#define SECURITY_PREFIX "security."
+#define DOM_SECURITY_PREFIX "dom.security"
+
+#define ACCEPT_HEADER_STYLE "text/css,*/*;q=0.1"
+#define ACCEPT_HEADER_ALL "*/*"
+
+#define UA_PREF(_pref) UA_PREF_PREFIX _pref
+#define HTTP_PREF(_pref) HTTP_PREF_PREFIX _pref
+#define BROWSER_PREF(_pref) BROWSER_PREF_PREFIX _pref
+
+#define NS_HTTP_PROTOCOL_FLAGS \
+ (URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP | URI_LOADABLE_BY_ANYONE)
+
+//-----------------------------------------------------------------------------
+
+using mozilla::dom::Promise;
+
+namespace mozilla::net {
+
+LazyLogModule gHttpLog("nsHttp");
+
+#ifdef ANDROID
+static nsCString GetDeviceModelId() {
+ // Assumed to be running on the main thread
+ // We need the device property in either case
+ nsAutoCString deviceModelId;
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService("@mozilla.org/system-info;1");
+ MOZ_ASSERT(infoService, "Could not find a system info service");
+ nsAutoString androidDevice;
+ nsresult rv = infoService->GetPropertyAsAString(u"device"_ns, androidDevice);
+ if (NS_SUCCEEDED(rv)) {
+ deviceModelId = NS_LossyConvertUTF16toASCII(androidDevice);
+ }
+ nsAutoCString deviceString;
+ rv = Preferences::GetCString(UA_PREF("device_string"), deviceString);
+ if (NS_SUCCEEDED(rv)) {
+ deviceString.Trim(" ", true, true);
+ deviceString.ReplaceSubstring("%DEVICEID%"_ns, deviceModelId);
+ return std::move(deviceString);
+ }
+ return std::move(deviceModelId);
+}
+#endif
+
+#ifdef XP_UNIX
+static bool IsRunningUnderUbuntuSnap() {
+# if defined(MOZ_WIDGET_GTK)
+ if (!widget::IsRunningUnderSnap()) {
+ return false;
+ }
+
+ char version[100];
+ if (PR_GetSystemInfo(PR_SI_RELEASE_BUILD, version, sizeof(version)) ==
+ PR_SUCCESS) {
+ if (strstr(version, "Ubuntu")) {
+ return true;
+ }
+ }
+# endif
+ return false;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <public>
+//-----------------------------------------------------------------------------
+
+StaticRefPtr<nsHttpHandler> gHttpHandler;
+
+/* static */
+already_AddRefed<nsHttpHandler> nsHttpHandler::GetInstance() {
+ if (!gHttpHandler) {
+ gHttpHandler = new nsHttpHandler();
+ DebugOnly<nsresult> rv = gHttpHandler->Init();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // There is code that may be executed during the final cycle collection
+ // shutdown and still referencing gHttpHandler.
+ ClearOnShutdown(&gHttpHandler, ShutdownPhase::CCPostLastCycleCollection);
+ }
+ RefPtr<nsHttpHandler> httpHandler = gHttpHandler;
+ return httpHandler.forget();
+}
+
+/// Derive the HTTP Accept header for image requests based on the enabled prefs
+/// for non-universal image types. This may be overridden in its entirety by
+/// the image.http.accept pref.
+static nsCString ImageAcceptHeader() {
+ nsCString mimeTypes;
+
+ if (mozilla::StaticPrefs::image_avif_enabled()) {
+ mimeTypes.Append("image/avif,");
+ }
+
+ if (mozilla::StaticPrefs::image_jxl_enabled()) {
+ mimeTypes.Append("image/jxl,");
+ }
+
+ mimeTypes.Append("image/webp,*/*");
+
+ return mimeTypes;
+}
+
+static nsCString DocumentAcceptHeader(const nsCString& aImageAcceptHeader) {
+ nsPrintfCString mimeTypes(
+ "text/html,application/xhtml+xml,application/xml;q=0.9,%s;q=0.8",
+ aImageAcceptHeader.get());
+
+ return std::move(mimeTypes);
+}
+
+nsHttpHandler::nsHttpHandler()
+ : mIdleTimeout(PR_SecondsToInterval(10)),
+ mSpdyTimeout(
+ PR_SecondsToInterval(StaticPrefs::network_http_http2_timeout())),
+ mResponseTimeout(PR_SecondsToInterval(300)),
+ mImageAcceptHeader(ImageAcceptHeader()),
+ mDocumentAcceptHeader(DocumentAcceptHeader(ImageAcceptHeader())),
+ mLastUniqueID(NowInSeconds()),
+ mDebugObservations(false),
+ mEnableAltSvc(false),
+ mEnableAltSvcOE(false),
+ mEnableOriginExtension(false),
+ mSpdyPingThreshold(PR_SecondsToInterval(
+ StaticPrefs::network_http_http2_ping_threshold())),
+ mSpdyPingTimeout(PR_SecondsToInterval(
+ StaticPrefs::network_http_http2_ping_timeout())) {
+ LOG(("Creating nsHttpHandler [this=%p].\n", this));
+
+ mUserAgentOverride.SetIsVoid(true);
+
+ MOZ_ASSERT(!gHttpHandler, "HTTP handler already created!");
+
+ nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+ if (runtime) {
+ runtime->GetProcessID(&mProcessId);
+ }
+}
+
+nsHttpHandler::~nsHttpHandler() {
+ LOG(("Deleting nsHttpHandler [this=%p]\n", this));
+
+ // make sure the connection manager is shutdown
+ if (mConnMgr) {
+ nsresult rv = mConnMgr->Shutdown();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler [this=%p] "
+ "failed to shutdown connection manager (%08x)\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ mConnMgr = nullptr;
+ }
+
+ // Note: don't call NeckoChild::DestroyNeckoChild() here, as it's too late
+ // and it'll segfault. NeckoChild will get cleaned up by process exit.
+
+ nsHttp::DestroyAtomTable();
+}
+
+static const char* gCallbackPrefs[] = {
+ HTTP_PREF_PREFIX,
+ UA_PREF_PREFIX,
+ INTL_ACCEPT_LANGUAGES,
+ BROWSER_PREF("disk_cache_ssl"),
+ H2MANDATORY_SUITE,
+ HTTP_PREF("tcp_keepalive.short_lived_connections"),
+ HTTP_PREF("tcp_keepalive.long_lived_connections"),
+ SAFE_HINT_HEADER_VALUE,
+ SECURITY_PREFIX,
+ DOM_SECURITY_PREFIX,
+ "image.http.accept",
+ "image.avif.enabled",
+ "image.jxl.enabled",
+ nullptr,
+};
+
+nsresult nsHttpHandler::Init() {
+ nsresult rv;
+
+ LOG(("nsHttpHandler::Init\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We should not create nsHttpHandler during shutdown, but we have some
+ // xpcshell tests doing this.
+ if (MOZ_UNLIKELY(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown) &&
+ !PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR"))) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Try to init HttpHandler after shutdown");
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ rv = nsHttp::CreateAtomTable();
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIIOService> service = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without io service");
+ return rv;
+ }
+ mIOService = new nsMainThreadPtrHolder<nsIIOService>(
+ "nsHttpHandler::mIOService", service);
+
+ gIOService->LaunchSocketProcess();
+
+ if (IsNeckoChild()) NeckoChild::InitNeckoChild();
+
+ InitUserAgentComponents();
+
+ // This perference is only used in parent process.
+ if (!IsNeckoChild()) {
+ mActiveTabPriority =
+ Preferences::GetBool(HTTP_PREF("active_tab_priority"), true);
+ std::bitset<3> usageOfHTTPSRRPrefs;
+ usageOfHTTPSRRPrefs[0] = StaticPrefs::network_dns_upgrade_with_https_rr();
+ usageOfHTTPSRRPrefs[1] = StaticPrefs::network_dns_use_https_rr_as_altsvc();
+ usageOfHTTPSRRPrefs[2] = StaticPrefs::network_dns_echconfig_enabled();
+ Telemetry::ScalarSet(Telemetry::ScalarID::NETWORKING_HTTPS_RR_PREFS_USAGE,
+ static_cast<uint32_t>(usageOfHTTPSRRPrefs.to_ulong()));
+ mActivityDistributor = components::HttpActivityDistributor::Service();
+
+ auto initQLogDir = [&]() {
+ if (!StaticPrefs::network_http_http3_enable_qlog()) {
+ return EmptyCString();
+ }
+
+ nsCOMPtr<nsIFile> qlogDir;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(qlogDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EmptyCString();
+ }
+
+ nsAutoCString dirName("qlog_");
+ dirName.AppendInt(mProcessId);
+ rv = qlogDir->AppendNative(dirName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EmptyCString();
+ }
+
+ return qlogDir->HumanReadablePath();
+ };
+ mHttp3QlogDir = initQLogDir();
+ }
+
+ // monitor some preference changes
+ Preferences::RegisterPrefixCallbacks(nsHttpHandler::PrefsChanged,
+ gCallbackPrefs, this);
+ PrefsChanged(nullptr);
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::NETWORKING_HTTP3_ENABLED,
+ StaticPrefs::network_http_http3_enable());
+
+ mCompatFirefox.AssignLiteral("Firefox/" MOZILLA_UAVERSION);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+
+ mAppName.AssignLiteral(MOZ_APP_UA_NAME);
+ if (mAppName.Length() == 0 && appInfo) {
+ // Try to get the UA name from appInfo, falling back to the name
+ appInfo->GetUAName(mAppName);
+ if (mAppName.Length() == 0) {
+ appInfo->GetName(mAppName);
+ }
+ appInfo->GetVersion(mAppVersion);
+ mAppName.StripChars(R"( ()<>@,;:\"/[]?={})");
+ } else {
+ mAppVersion.AssignLiteral(MOZ_APP_UA_VERSION);
+ }
+
+ mMisc.AssignLiteral("rv:" MOZILLA_UAVERSION);
+
+ // Generate the spoofed User Agent for fingerprinting resistance.
+ nsRFPService::GetSpoofedUserAgent(mSpoofedUserAgent, true);
+
+ mSessionStartTime = NowInSeconds();
+ mHandlerActive = true;
+
+ rv = InitConnectionMgr();
+ if (NS_FAILED(rv)) return rv;
+
+ mAltSvcCache = MakeUnique<AltSvcCache>();
+
+ mRequestContextService = RequestContextService::GetOrCreate();
+
+#if defined(ANDROID)
+ mProductSub.AssignLiteral(MOZILLA_UAVERSION);
+#else
+ mProductSub.AssignLiteral(LEGACY_UA_GECKO_TRAIL);
+#endif
+
+#if DEBUG
+ // dump user agent prefs
+ LOG(("> legacy-app-name = %s\n", mLegacyAppName.get()));
+ LOG(("> legacy-app-version = %s\n", mLegacyAppVersion.get()));
+ LOG(("> platform = %s\n", mPlatform.get()));
+ LOG(("> oscpu = %s\n", mOscpu.get()));
+ LOG(("> misc = %s\n", mMisc.get()));
+ LOG(("> product = %s\n", mProduct.get()));
+ LOG(("> product-sub = %s\n", mProductSub.get()));
+ LOG(("> app-name = %s\n", mAppName.get()));
+ LOG(("> app-version = %s\n", mAppVersion.get()));
+ LOG(("> compat-firefox = %s\n", mCompatFirefox.get()));
+ LOG(("> user-agent = %s\n", UserAgent(false).get()));
+#endif
+
+ // Startup the http category
+ // Bring alive the objects in the http-protocol-startup category
+ NS_CreateServicesFromCategory(
+ NS_HTTP_STARTUP_CATEGORY,
+ static_cast<nsISupports*>(static_cast<void*>(this)),
+ NS_HTTP_STARTUP_TOPIC);
+
+ nsCOMPtr<nsIObserverService> obsService =
+ static_cast<nsIObserverService*>(gIOService);
+ if (obsService) {
+ // register the handler object as a weak callback as we don't need to worry
+ // about shutdown ordering.
+ obsService->AddObserver(this, "profile-change-net-teardown", true);
+ obsService->AddObserver(this, "profile-change-net-restore", true);
+ obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ obsService->AddObserver(this, "net:clear-active-logins", true);
+ obsService->AddObserver(this, "net:prune-dead-connections", true);
+ // Sent by the TorButton add-on in the Tor Browser
+ obsService->AddObserver(this, "net:prune-all-connections", true);
+ obsService->AddObserver(this, "net:cancel-all-connections", true);
+ obsService->AddObserver(this, "last-pb-context-exited", true);
+ obsService->AddObserver(this, "browser:purge-session-history", true);
+ obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ obsService->AddObserver(this, "application-background", true);
+ obsService->AddObserver(this, "psm:user-certificate-added", true);
+ obsService->AddObserver(this, "psm:user-certificate-deleted", true);
+ obsService->AddObserver(this, "intl:app-locales-changed", true);
+ obsService->AddObserver(this, "browser-delayed-startup-finished", true);
+ obsService->AddObserver(this, "network:captive-portal-connectivity", true);
+ obsService->AddObserver(this, "network:reset-http3-excluded-list", true);
+ obsService->AddObserver(this, "network:socket-process-crashed", true);
+
+ if (!IsNeckoChild()) {
+ obsService->AddObserver(this, "net:current-browser-id", true);
+ }
+
+ // disabled as its a nop right now
+ // obsService->AddObserver(this, "net:failed-to-process-uri-content", true);
+ }
+
+ MakeNewRequestTokenBucket();
+ mWifiTickler = new Tickler();
+ if (NS_FAILED(mWifiTickler->Init())) mWifiTickler = nullptr;
+
+ nsCOMPtr<nsIParentalControlsService> pc =
+ do_CreateInstance("@mozilla.org/parental-controls-service;1");
+ if (pc) {
+ pc->GetParentalControlsEnabled(&mParentalControlEnabled);
+ }
+
+ return NS_OK;
+}
+
+const nsCString& nsHttpHandler::Http3QlogDir() {
+ if (StaticPrefs::network_http_http3_enable_qlog()) {
+ return mHttp3QlogDir;
+ }
+
+ return EmptyCString();
+}
+
+void nsHttpHandler::MakeNewRequestTokenBucket() {
+ LOG(("nsHttpHandler::MakeNewRequestTokenBucket this=%p child=%d\n", this,
+ IsNeckoChild()));
+ if (!mConnMgr || IsNeckoChild()) {
+ return;
+ }
+ RefPtr<EventTokenBucket> tokenBucket =
+ new EventTokenBucket(RequestTokenBucketHz(), RequestTokenBucketBurst());
+ // NOTE The thread or socket may be gone already.
+ nsresult rv = mConnMgr->UpdateRequestTokenBucket(tokenBucket);
+ if (NS_FAILED(rv)) {
+ LOG((" failed to update request token bucket\n"));
+ }
+}
+
+nsresult nsHttpHandler::InitConnectionMgr() {
+ // Init ConnectionManager only on parent!
+ if (IsNeckoChild()) {
+ return NS_OK;
+ }
+
+ if (mConnMgr) {
+ return NS_OK;
+ }
+
+ if (nsIOService::UseSocketProcess(true) && XRE_IsParentProcess()) {
+ mConnMgr = new HttpConnectionMgrParent();
+ RefPtr<nsHttpHandler> self = this;
+ auto task = [self]() {
+ RefPtr<HttpConnectionMgrParent> parent =
+ self->mConnMgr->AsHttpConnectionMgrParent();
+ Unused << SocketProcessParent::GetSingleton()
+ ->SendPHttpConnectionMgrConstructor(
+ parent,
+ HttpHandlerInitArgs(
+ self->mLegacyAppName, self->mLegacyAppVersion,
+ self->mPlatform, self->mOscpu, self->mMisc,
+ self->mProduct, self->mProductSub, self->mAppName,
+ self->mAppVersion, self->mCompatFirefox,
+ self->mCompatDevice, self->mDeviceModelId));
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ } else {
+ MOZ_ASSERT(XRE_IsSocketProcess() || !nsIOService::UseSocketProcess());
+ mConnMgr = new nsHttpConnectionMgr();
+ }
+
+ return mConnMgr->Init(
+ mMaxUrgentExcessiveConns, mMaxConnections,
+ mMaxPersistentConnectionsPerServer, mMaxPersistentConnectionsPerProxy,
+ mMaxRequestDelay, mThrottleEnabled, mThrottleVersion, mThrottleSuspendFor,
+ mThrottleResumeFor, mThrottleReadLimit, mThrottleReadInterval,
+ mThrottleHoldTime, mThrottleMaxTime, mBeConservativeForProxy);
+}
+
+nsresult nsHttpHandler::AddStandardRequestHeaders(
+ nsHttpRequestHead* request, bool isSecure,
+ ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting) {
+ nsresult rv;
+
+ // Add the "User-Agent" header
+ rv = request->SetHeader(nsHttp::User_Agent,
+ UserAgent(aShouldResistFingerprinting), false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ if (NS_FAILED(rv)) return rv;
+
+ // MIME based content negotiation lives!
+ // Add the "Accept" header. Note, this is set as an override because the
+ // service worker expects to see it. The other "default" headers are
+ // hidden from service worker interception.
+ nsAutoCString accept;
+ if (aContentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ aContentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ accept.Assign(mDocumentAcceptHeader);
+ } else if (aContentPolicyType == ExtContentPolicy::TYPE_IMAGE ||
+ aContentPolicyType == ExtContentPolicy::TYPE_IMAGESET) {
+ accept.Assign(mImageAcceptHeader);
+ } else if (aContentPolicyType == ExtContentPolicy::TYPE_STYLESHEET) {
+ accept.Assign(ACCEPT_HEADER_STYLE);
+ } else {
+ accept.Assign(ACCEPT_HEADER_ALL);
+ }
+
+ rv = request->SetHeader(nsHttp::Accept, accept, false,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+
+ // Add the "Accept-Language" header. This header is also exposed to the
+ // service worker.
+ if (mAcceptLanguagesIsDirty) {
+ rv = SetAcceptLanguages();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // Add the "Accept-Language" header
+ if (!mAcceptLanguages.IsEmpty()) {
+ rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages, false,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Add the "Accept-Encoding" header
+ if (isSecure) {
+ rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings,
+ false, nsHttpHeaderArray::eVarietyRequestDefault);
+ } else {
+ rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings,
+ false, nsHttpHeaderArray::eVarietyRequestDefault);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // add the "Send Hint" header
+ if (mSafeHintEnabled || mParentalControlEnabled) {
+ rv = request->SetHeader(nsHttp::Prefer, "safe"_ns, false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpHandler::AddConnectionHeader(nsHttpRequestHead* request,
+ uint32_t caps) {
+ // RFC2616 section 19.6.2 states that the "Connection: keep-alive"
+ // and "Keep-alive" request headers should not be sent by HTTP/1.1
+ // user-agents. But this is not a problem in practice, and the
+ // alternative proxy-connection is worse. see 570283
+
+ constexpr auto close = "close"_ns;
+ constexpr auto keepAlive = "keep-alive"_ns;
+
+ const nsLiteralCString* connectionType = &close;
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ connectionType = &keepAlive;
+ }
+
+ return request->SetHeader(nsHttp::Connection, *connectionType);
+}
+
+bool nsHttpHandler::IsAcceptableEncoding(const char* enc, bool isSecure) {
+ if (!enc) return false;
+
+ // we used to accept x-foo anytime foo was acceptable, but that's just
+ // continuing bad behavior.. so limit it to known x-* patterns
+ bool rv;
+ if (isSecure) {
+ rv = nsHttp::FindToken(mHttpsAcceptEncodings.get(), enc, HTTP_LWS ",") !=
+ nullptr;
+ } else {
+ rv = nsHttp::FindToken(mHttpAcceptEncodings.get(), enc, HTTP_LWS ",") !=
+ nullptr;
+ }
+ // gzip and deflate are inherently acceptable in modern HTTP - always
+ // process them if a stream converter can also be found.
+ if (!rv &&
+ (!nsCRT::strcasecmp(enc, "gzip") || !nsCRT::strcasecmp(enc, "deflate") ||
+ !nsCRT::strcasecmp(enc, "x-gzip") ||
+ !nsCRT::strcasecmp(enc, "x-deflate"))) {
+ rv = true;
+ }
+ LOG(("nsHttpHandler::IsAceptableEncoding %s https=%d %d\n", enc, isSecure,
+ rv));
+ return rv;
+}
+
+nsISiteSecurityService* nsHttpHandler::GetSSService() {
+ if (!mSSService) {
+ nsCOMPtr<nsISiteSecurityService> service =
+ do_GetService(NS_SSSERVICE_CONTRACTID);
+ mSSService = new nsMainThreadPtrHolder<nsISiteSecurityService>(
+ "nsHttpHandler::mSSService", service);
+ }
+ return mSSService;
+}
+
+nsICookieService* nsHttpHandler::GetCookieService() {
+ if (!mCookieService) {
+ nsCOMPtr<nsICookieService> service =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ mCookieService = new nsMainThreadPtrHolder<nsICookieService>(
+ "nsHttpHandler::mCookieService", service);
+ }
+ return mCookieService;
+}
+
+nsresult nsHttpHandler::GetIOService(nsIIOService** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = do_AddRef(mIOService.get()).take();
+ return NS_OK;
+}
+
+void nsHttpHandler::NotifyObservers(nsIChannel* chan, const char* event) {
+ LOG(("nsHttpHandler::NotifyObservers [this=%p chan=%p event=\"%s\"]\n", this,
+ chan, event));
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) obsService->NotifyObservers(chan, event, nullptr);
+}
+
+nsresult nsHttpHandler::AsyncOnChannelRedirect(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread() && (oldChan && newChan));
+
+ nsCOMPtr<nsIURI> oldURI;
+ oldChan->GetURI(getter_AddRefs(oldURI));
+ MOZ_ASSERT(oldURI);
+
+ nsCOMPtr<nsIURI> newURI;
+ newChan->GetURI(getter_AddRefs(newURI));
+ MOZ_ASSERT(newURI);
+
+ PrepareForAntiTrackingRedirectHeuristic(oldChan, oldURI, newChan, newURI);
+
+ DynamicFpiRedirectHeuristic(oldChan, oldURI, newChan, newURI);
+
+ // TODO E10S This helper has to be initialized on the other process
+ RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
+ new nsAsyncRedirectVerifyHelper();
+
+ return redirectCallbackHelper->Init(oldChan, newChan, flags,
+ mainThreadEventTarget);
+}
+
+/* static */
+nsresult nsHttpHandler::GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine) {
+ return NS_GenerateHostPort(host, port, hostLine);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <private>
+//-----------------------------------------------------------------------------
+
+const nsCString& nsHttpHandler::UserAgent(bool aShouldResistFingerprinting) {
+ if (aShouldResistFingerprinting && !mSpoofedUserAgent.IsEmpty()) {
+ LOG(("using spoofed userAgent : %s\n", mSpoofedUserAgent.get()));
+ return mSpoofedUserAgent;
+ }
+
+ if (!mUserAgentOverride.IsVoid()) {
+ LOG(("using general.useragent.override : %s\n", mUserAgentOverride.get()));
+ return mUserAgentOverride;
+ }
+
+ if (mUserAgentIsDirty) {
+ BuildUserAgent();
+ mUserAgentIsDirty = false;
+ }
+
+ return mUserAgent;
+}
+
+void nsHttpHandler::BuildUserAgent() {
+ LOG(("nsHttpHandler::BuildUserAgent\n"));
+
+ MOZ_ASSERT(!mLegacyAppName.IsEmpty() && !mLegacyAppVersion.IsEmpty(),
+ "HTTP cannot send practical requests without this much");
+
+ // preallocate to worst-case size, which should always be better
+ // than if we didn't preallocate at all.
+ mUserAgent.SetCapacity(mLegacyAppName.Length() + mLegacyAppVersion.Length() +
+#ifndef UA_SPARE_PLATFORM
+ mPlatform.Length() +
+#endif
+ mOscpu.Length() + mMisc.Length() + mProduct.Length() +
+ mProductSub.Length() + mAppName.Length() +
+ mAppVersion.Length() + mCompatFirefox.Length() +
+ mCompatDevice.Length() + mDeviceModelId.Length() + 13);
+
+ // Application portion
+ mUserAgent.Assign(mLegacyAppName);
+ mUserAgent += '/';
+ mUserAgent += mLegacyAppVersion;
+ mUserAgent += ' ';
+
+ // Application comment
+ mUserAgent += '(';
+#ifndef UA_SPARE_PLATFORM
+ if (!mPlatform.IsEmpty()) {
+ mUserAgent += mPlatform;
+ mUserAgent.AppendLiteral("; ");
+ }
+#endif
+ if (!mCompatDevice.IsEmpty()) {
+ mUserAgent += mCompatDevice;
+ mUserAgent.AppendLiteral("; ");
+ } else if (!mOscpu.IsEmpty()) {
+ mUserAgent += mOscpu;
+ mUserAgent.AppendLiteral("; ");
+ }
+ if (!mDeviceModelId.IsEmpty()) {
+ mUserAgent += mDeviceModelId;
+ mUserAgent.AppendLiteral("; ");
+ }
+ mUserAgent += mMisc;
+ mUserAgent += ')';
+
+ // Product portion
+ mUserAgent += ' ';
+ mUserAgent += mProduct;
+ mUserAgent += '/';
+ mUserAgent += mProductSub;
+
+ bool isFirefox = mAppName.EqualsLiteral("Firefox");
+ if (isFirefox || mCompatFirefoxEnabled) {
+ // "Firefox/x.y" (compatibility) app token
+ mUserAgent += ' ';
+ mUserAgent += mCompatFirefox;
+ }
+ if (!isFirefox) {
+ // App portion
+ mUserAgent += ' ';
+ mUserAgent += mAppName;
+ mUserAgent += '/';
+ mUserAgent += mAppVersion;
+ }
+}
+
+#ifdef XP_WIN
+// Hardcode the reported Windows version to 10.0. This way, Microsoft doesn't
+// get to change Web compat-sensitive values without our veto. The compat-
+// sensitivity keeps going up as 10.0 stays as the current value for longer
+// and longer. If the system-reported version ever changes, we'll be able to
+// take our time to evaluate the Web compat impact instead of having to
+// scramble to react like happened with macOS changing from 10.x to 11.x.
+# define OSCPU_WINDOWS "Windows NT 10.0"
+# define OSCPU_WIN64 OSCPU_WINDOWS "; Win64; x64"
+#endif
+
+void nsHttpHandler::InitUserAgentComponents() {
+ // Don't build user agent components in socket process, since the system info
+ // is not available.
+ if (XRE_IsSocketProcess()) {
+ mUserAgentIsDirty = true;
+ return;
+ }
+
+ // Gather platform.
+ mPlatform.AssignLiteral(
+#if defined(ANDROID)
+ "Android"
+#elif defined(XP_WIN)
+ "Windows"
+#elif defined(XP_MACOSX)
+ "Macintosh"
+#elif defined(XP_UNIX)
+ // We historically have always had X11 here,
+ // and there seems little a webpage can sensibly do
+ // based on it being something else, so use X11 for
+ // backwards compatibility in all cases.
+ "X11"
+#endif
+ );
+
+#ifdef XP_UNIX
+ if (IsRunningUnderUbuntuSnap()) {
+ mPlatform.AppendLiteral("; Ubuntu");
+ }
+#endif
+
+#ifdef ANDROID
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService("@mozilla.org/system-info;1");
+ MOZ_ASSERT(infoService, "Could not find a system info service");
+ nsresult rv;
+ // Add the Android version number to the Fennec platform identifier.
+ nsAutoString androidVersion;
+ rv = infoService->GetPropertyAsAString(u"release_version"_ns, androidVersion);
+ if (NS_SUCCEEDED(rv)) {
+ mPlatform += " ";
+ mPlatform += NS_LossyConvertUTF16toASCII(androidVersion);
+ }
+
+ // Add the `Mobile` or `TV` token when running on device.
+ bool isTV;
+ rv = infoService->GetPropertyAsBool(u"tv"_ns, &isTV);
+ if (NS_SUCCEEDED(rv) && isTV) {
+ mCompatDevice.AssignLiteral("TV");
+ } else {
+ mCompatDevice.AssignLiteral("Mobile");
+ }
+
+ if (Preferences::GetBool(UA_PREF("use_device"), false)) {
+ mDeviceModelId = mozilla::net::GetDeviceModelId();
+ }
+#endif // ANDROID
+
+ // Gather OS/CPU.
+#if defined(XP_WIN)
+
+# if defined _M_X64 || defined _M_AMD64
+ mOscpu.AssignLiteral(OSCPU_WIN64);
+# elif defined(_ARM64_)
+ // Report ARM64 Windows 11+ as x86_64 and Windows 10 as x86. Windows 11+
+ // supports x86_64 emulation, but Windows 10 only supports x86 emulation.
+ if (IsWin11OrLater()) {
+ mOscpu.AssignLiteral(OSCPU_WIN64);
+ } else {
+ mOscpu.AssignLiteral(OSCPU_WINDOWS);
+ }
+# else
+ BOOL isWow64 = FALSE;
+ if (!IsWow64Process(GetCurrentProcess(), &isWow64)) {
+ isWow64 = FALSE;
+ }
+ if (isWow64) {
+ mOscpu.AssignLiteral(OSCPU_WIN64);
+ } else {
+ mOscpu.AssignLiteral(OSCPU_WINDOWS);
+ }
+# endif
+
+#elif defined(XP_MACOSX)
+ mOscpu.AssignLiteral("Intel Mac OS X 10.15");
+#elif defined(XP_UNIX)
+ if (mozilla::StaticPrefs::network_http_useragent_freezeCpu()) {
+# ifdef ANDROID
+ mOscpu.AssignLiteral("Linux armv81");
+# else
+ mOscpu.AssignLiteral("Linux x86_64");
+# endif
+ } else {
+ struct utsname name {};
+ int ret = uname(&name);
+ if (ret >= 0) {
+ nsAutoCString buf;
+ buf = (char*)name.sysname;
+ buf += ' ';
+
+# ifdef AIX
+ // AIX uname returns machine specific info in the uname.machine
+ // field and does not return the cpu type like other platforms.
+ // We use the AIX version and release numbers instead.
+ buf += (char*)name.version;
+ buf += '.';
+ buf += (char*)name.release;
+# else
+ buf += (char*)name.machine;
+# endif
+
+ mOscpu.Assign(buf);
+ }
+ }
+#endif
+
+ mUserAgentIsDirty = true;
+}
+
+uint32_t nsHttpHandler::MaxSocketCount() {
+ PR_CallOnce(&nsSocketTransportService::gMaxCountInitOnce,
+ nsSocketTransportService::DiscoverMaxCount);
+ // Don't use the full max count because sockets can be held in
+ // the persistent connection pool for a long time and that could
+ // starve other users.
+
+ uint32_t maxCount = nsSocketTransportService::gMaxCount;
+ if (maxCount <= 8) {
+ maxCount = 1;
+ } else {
+ maxCount -= 8;
+ }
+
+ return maxCount;
+}
+
+// static
+void nsHttpHandler::PrefsChanged(const char* pref, void* self) {
+ static_cast<nsHttpHandler*>(self)->PrefsChanged(pref);
+}
+
+void nsHttpHandler::PrefsChanged(const char* pref) {
+ nsresult rv = NS_OK;
+ int32_t val;
+
+ LOG(("nsHttpHandler::PrefsChanged [pref=%s]\n", pref));
+
+ if (pref) {
+ gIOService->NotifySocketProcessPrefsChanged(pref);
+ }
+
+#define PREF_CHANGED(p) ((pref == nullptr) || !strcmp(pref, p))
+#define MULTI_PREF_CHANGED(p) \
+ ((pref == nullptr) || !strncmp(pref, p, sizeof(p) - 1))
+
+ // If a security pref changed, lets clear our connection pool reuse
+ if (MULTI_PREF_CHANGED(SECURITY_PREFIX)) {
+ LOG(("nsHttpHandler::PrefsChanged Security Pref Changed %s\n", pref));
+ if (mConnMgr) {
+ rv = mConnMgr->DoShiftReloadConnectionCleanup();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "DoShiftReloadConnectionCleanup failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ //
+ // UA components
+ //
+
+ bool cVar = false;
+
+ if (PREF_CHANGED(UA_PREF("compatMode.firefox"))) {
+ rv = Preferences::GetBool(UA_PREF("compatMode.firefox"), &cVar);
+ mCompatFirefoxEnabled = (NS_SUCCEEDED(rv) && cVar);
+ mUserAgentIsDirty = true;
+ }
+
+ // general.useragent.override
+ if (PREF_CHANGED(UA_PREF("override"))) {
+ Preferences::GetCString(UA_PREF("override"), mUserAgentOverride);
+ mUserAgentIsDirty = true;
+ }
+
+#ifdef ANDROID
+ // general.useragent.use_device
+ if (PREF_CHANGED(UA_PREF("use_device"))) {
+ if (Preferences::GetBool(UA_PREF("use_device"), false)) {
+ if (!XRE_IsSocketProcess()) {
+ mDeviceModelId = mozilla::net::GetDeviceModelId();
+ if (gIOService->SocketProcessReady()) {
+ Unused << SocketProcessParent::GetSingleton()
+ ->SendUpdateDeviceModelId(mDeviceModelId);
+ }
+ }
+ } else {
+ mDeviceModelId.Truncate();
+ }
+ mUserAgentIsDirty = true;
+ }
+#endif
+
+ //
+ // HTTP options
+ //
+
+ if (PREF_CHANGED(HTTP_PREF("keep-alive.timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("keep-alive.timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mIdleTimeout = PR_SecondsToInterval(clamped(val, 1, 0xffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("request.max-attempts"))) {
+ rv = Preferences::GetInt(HTTP_PREF("request.max-attempts"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxRequestAttempts = (uint16_t)clamped(val, 1, 0xffff);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("request.max-start-delay"))) {
+ rv = Preferences::GetInt(HTTP_PREF("request.max-start-delay"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxRequestDelay = (uint16_t)clamped(val, 0, 0xffff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_REQUEST_DELAY,
+ mMaxRequestDelay);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged (request.max-start-delay)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("response.timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("response.timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mResponseTimeout = PR_SecondsToInterval(clamped(val, 0, 0xffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("network-changed.timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("network-changed.timeout"), &val);
+ if (NS_SUCCEEDED(rv)) mNetworkChangedTimeout = clamped(val, 1, 600) * 1000;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-connections"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max-connections"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxConnections =
+ (uint16_t)clamped((uint32_t)val, (uint32_t)1, MaxSocketCount());
+
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_CONNECTIONS,
+ mMaxConnections);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged (max-connections)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(
+ HTTP_PREF("max-urgent-start-excessive-connections-per-host"))) {
+ rv = Preferences::GetInt(
+ HTTP_PREF("max-urgent-start-excessive-connections-per-host"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxUrgentExcessiveConns = (uint8_t)clamped(val, 1, 0xff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_URGENT_START_Q,
+ mMaxUrgentExcessiveConns);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "(max-urgent-start-excessive-connections-per-host)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-server"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max-persistent-connections-per-server"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerServer = (uint8_t)clamped(val, 1, 0xff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ mMaxPersistentConnectionsPerServer);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "(max-persistent-connections-per-server)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-proxy"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max-persistent-connections-per-proxy"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerProxy = (uint8_t)clamped(val, 1, 0xff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ mMaxPersistentConnectionsPerProxy);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "(max-persistent-connections-per-proxy)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("redirection-limit"))) {
+ rv = Preferences::GetInt(HTTP_PREF("redirection-limit"), &val);
+ if (NS_SUCCEEDED(rv)) mRedirectionLimit = (uint8_t)clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("connection-retry-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("connection-retry-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) mIdleSynTimeout = (uint16_t)clamped(val, 0, 3000);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("fast-fallback-to-IPv4"))) {
+ rv = Preferences::GetBool(HTTP_PREF("fast-fallback-to-IPv4"), &cVar);
+ if (NS_SUCCEEDED(rv)) mFastFallbackToIPv4 = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("fallback-connection-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("fallback-connection-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mFallbackSynTimeout = (uint16_t)clamped(val, 0, 10 * 60);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("version"))) {
+ nsAutoCString httpVersion;
+ Preferences::GetCString(HTTP_PREF("version"), httpVersion);
+ if (!httpVersion.IsVoid()) {
+ if (httpVersion.EqualsLiteral("1.1")) {
+ mHttpVersion = HttpVersion::v1_1;
+ } else if (httpVersion.EqualsLiteral("0.9")) {
+ mHttpVersion = HttpVersion::v0_9;
+ } else {
+ mHttpVersion = HttpVersion::v1_0;
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.version"))) {
+ nsAutoCString httpVersion;
+ Preferences::GetCString(HTTP_PREF("proxy.version"), httpVersion);
+ if (!httpVersion.IsVoid()) {
+ if (httpVersion.EqualsLiteral("1.1")) {
+ mProxyHttpVersion = HttpVersion::v1_1;
+ } else {
+ mProxyHttpVersion = HttpVersion::v1_0;
+ }
+ // it does not make sense to issue a HTTP/0.9 request to a proxy server
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.respect-be-conservative"))) {
+ rv =
+ Preferences::GetBool(HTTP_PREF("proxy.respect-be-conservative"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mBeConservativeForProxy = cVar;
+ if (mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::PROXY_BE_CONSERVATIVE,
+ static_cast<int32_t>(mBeConservativeForProxy));
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("qos"))) {
+ rv = Preferences::GetInt(HTTP_PREF("qos"), &val);
+ if (NS_SUCCEEDED(rv)) mQoSBits = (uint8_t)clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding"))) {
+ nsAutoCString acceptEncodings;
+ rv = Preferences::GetCString(HTTP_PREF("accept-encoding"), acceptEncodings);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetAcceptEncodings(acceptEncodings.get(), false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding.secure"))) {
+ nsAutoCString acceptEncodings;
+ rv = Preferences::GetCString(HTTP_PREF("accept-encoding.secure"),
+ acceptEncodings);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetAcceptEncodings(acceptEncodings.get(), true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("default-socket-type"))) {
+ nsAutoCString sval;
+ rv = Preferences::GetCString(HTTP_PREF("default-socket-type"), sval);
+ if (NS_SUCCEEDED(rv)) {
+ if (sval.IsEmpty()) {
+ mDefaultSocketType.SetIsVoid(true);
+ } else {
+ // verify that this socket type is actually valid
+ nsCOMPtr<nsISocketProviderService> sps =
+ nsSocketProviderService::GetOrCreate();
+ if (sps) {
+ nsCOMPtr<nsISocketProvider> sp;
+ rv = sps->GetSocketProvider(sval.get(), getter_AddRefs(sp));
+ if (NS_SUCCEEDED(rv)) {
+ // OK, this looks like a valid socket provider.
+ mDefaultSocketType.Assign(sval);
+ }
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("prompt-temp-redirect"))) {
+ rv = Preferences::GetBool(HTTP_PREF("prompt-temp-redirect"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mPromptTempRedirect = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) {
+ cVar = false;
+ rv = Preferences::GetBool(HTTP_PREF("assoc-req.enforce"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnforceAssocReq = cVar;
+ }
+
+ // enable Persistent caching for HTTPS - bug#205921
+ if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) {
+ cVar = false;
+ rv = Preferences::GetBool(BROWSER_PREF("disk_cache_ssl"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnablePersistentHttpsCaching = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) {
+ rv = Preferences::GetInt(HTTP_PREF("phishy-userpass-length"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mPhishyUserPassLength = (uint8_t)clamped(val, 0, 0xff);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.timeout"))) {
+ mSpdyTimeout = PR_SecondsToInterval(
+ clamped(StaticPrefs::network_http_http2_timeout(), 1, 0xffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.chunk-size"))) {
+ // keep this within http/2 ranges of 1 to 2^24-1
+ mSpdySendingChunkSize = (uint32_t)clamped(
+ StaticPrefs::network_http_http2_chunk_size(), 1, 0xffffff);
+ }
+
+ // The amount of idle seconds on a http2 connection before initiating a
+ // server ping. 0 will disable.
+ if (PREF_CHANGED(HTTP_PREF("http2.ping-threshold"))) {
+ mSpdyPingThreshold = PR_SecondsToInterval((uint16_t)clamped(
+ StaticPrefs::network_http_http2_ping_threshold(), 0, 0x7fffffff));
+ }
+
+ // The amount of seconds to wait for a http2 ping response before
+ // closing the session.
+ if (PREF_CHANGED(HTTP_PREF("http2.ping-timeout"))) {
+ mSpdyPingTimeout = PR_SecondsToInterval((uint16_t)clamped(
+ StaticPrefs::network_http_http2_ping_timeout(), 0, 0x7fffffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.enabled"))) {
+ rv = Preferences::GetBool(HTTP_PREF("altsvc.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnableAltSvc = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.oe"))) {
+ rv = Preferences::GetBool(HTTP_PREF("altsvc.oe"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnableAltSvcOE = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("originextension"))) {
+ rv = Preferences::GetBool(HTTP_PREF("originextension"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnableOriginExtension = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.push-allowance"))) {
+ mSpdyPushAllowance = static_cast<uint32_t>(
+ clamped(StaticPrefs::network_http_http2_push_allowance(), 1024,
+ static_cast<int32_t>(ASpdySession::kInitialRwin)));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.pull-allowance"))) {
+ mSpdyPullAllowance = static_cast<uint32_t>(clamped(
+ StaticPrefs::network_http_http2_pull_allowance(), 1024, 0x7fffffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.default-concurrent"))) {
+ mDefaultSpdyConcurrent = static_cast<uint32_t>(std::max<int32_t>(
+ std::min<int32_t>(StaticPrefs::network_http_http2_default_concurrent(),
+ 9999),
+ 1));
+ }
+
+ // If http2.send-buffer-size is non-zero, the size to set the TCP
+ // sendbuffer to once the stream has surpassed this number of bytes uploaded
+ if (PREF_CHANGED(HTTP_PREF("http2.send-buffer-size"))) {
+ mSpdySendBufferSize = (uint32_t)clamped(
+ StaticPrefs::network_http_http2_send_buffer_size(), 1500, 0x7fffffff);
+ }
+
+ // The maximum amount of time to wait for socket transport to be
+ // established
+ if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("connection-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ // the pref is in seconds, but the variable is in milliseconds
+ mConnectTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
+ }
+ }
+
+ // The maximum amount of time to wait for a tls handshake to finish.
+ if (PREF_CHANGED(HTTP_PREF("tls-handshake-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tls-handshake-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ // the pref is in seconds, but the variable is in milliseconds
+ mTLSHandshakeTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
+ }
+ }
+
+ // The maximum number of current global half open sockets allowable
+ // for starting a new speculative connection.
+ if (PREF_CHANGED(HTTP_PREF("speculative-parallel-limit"))) {
+ rv = Preferences::GetInt(HTTP_PREF("speculative-parallel-limit"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mParallelSpeculativeConnectLimit = (uint32_t)clamped(val, 0, 1024);
+ }
+ }
+
+ // Whether or not to block requests for non head js/css items (e.g. media)
+ // while those elements load.
+ if (PREF_CHANGED(HTTP_PREF("rendering-critical-requests-prioritization"))) {
+ rv = Preferences::GetBool(
+ HTTP_PREF("rendering-critical-requests-prioritization"), &cVar);
+ if (NS_SUCCEEDED(rv)) mCriticalRequestPrioritization = cVar;
+ }
+
+ // on transition of network.http.diagnostics to true print
+ // a bunch of information to the console
+ if (pref && PREF_CHANGED(HTTP_PREF("diagnostics"))) {
+ rv = Preferences::GetBool(HTTP_PREF("diagnostics"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ if (mConnMgr) mConnMgr->PrintDiagnostics();
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max_response_header_size"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxHttpResponseHeaderSize = val;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.enable"))) {
+ rv = Preferences::GetBool(HTTP_PREF("throttle.enable"), &mThrottleEnabled);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_ENABLED,
+ static_cast<int32_t>(mThrottleEnabled));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.version"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("throttle.version"), &val);
+ mThrottleVersion = (uint32_t)clamped(val, 1, 2);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.suspend-for"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.suspend-for"), &val);
+ mThrottleSuspendFor = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_SUSPEND_FOR, mThrottleSuspendFor);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.resume-for"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.resume-for"), &val);
+ mThrottleResumeFor = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_RESUME_FOR, mThrottleResumeFor);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.read-limit-bytes"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.read-limit-bytes"), &val);
+ mThrottleReadLimit = (uint32_t)clamped(val, 0, 500000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_READ_LIMIT, mThrottleReadLimit);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.read-interval-ms"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.read-interval-ms"), &val);
+ mThrottleReadInterval = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_READ_INTERVAL, mThrottleReadInterval);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.hold-time-ms"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.hold-time-ms"), &val);
+ mThrottleHoldTime = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_HOLD_TIME,
+ mThrottleHoldTime);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.max-time-ms"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.max-time-ms"), &val);
+ mThrottleMaxTime = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_MAX_TIME,
+ mThrottleMaxTime);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("send_window_size"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("send_window_size"), &val);
+ mSendWindowSize = val >= 0 ? val : 0;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("on_click_priority"))) {
+ Unused << Preferences::GetBool(HTTP_PREF("on_click_priority"),
+ &mUrgentStartEnabled);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tailing.enabled"))) {
+ Unused << Preferences::GetBool(HTTP_PREF("tailing.enabled"),
+ &mTailBlockingEnabled);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.delay-quantum"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("tailing.delay-quantum"), &val);
+ mTailDelayQuantum = (uint32_t)clamped(val, 0, 60000);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.delay-quantum-after-domcontentloaded"))) {
+ Unused << Preferences::GetInt(
+ HTTP_PREF("tailing.delay-quantum-after-domcontentloaded"), &val);
+ mTailDelayQuantumAfterDCL = (uint32_t)clamped(val, 0, 60000);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.delay-max"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("tailing.delay-max"), &val);
+ mTailDelayMax = (uint32_t)clamped(val, 0, 60000);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.total-max"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("tailing.total-max"), &val);
+ mTailTotalMax = (uint32_t)clamped(val, 0, 60000);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("focused_window_transaction_ratio"))) {
+ float ratio = 0;
+ rv = Preferences::GetFloat(HTTP_PREF("focused_window_transaction_ratio"),
+ &ratio);
+ if (NS_SUCCEEDED(rv)) {
+ if (ratio > 0 && ratio < 1) {
+ mFocusedWindowTransactionRatio = ratio;
+ } else {
+ NS_WARNING("Wrong value for focused_window_transaction_ratio");
+ }
+ }
+ }
+
+ //
+ // INTL options
+ //
+
+ if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) {
+ // We don't want to set the new accept languages here since
+ // this pref is a complex type and it may be racy with flushing
+ // string resources.
+ mAcceptLanguagesIsDirty = true;
+ }
+
+ //
+ // Tracking options
+ //
+
+ // Hint option
+ if (PREF_CHANGED(SAFE_HINT_HEADER_VALUE)) {
+ cVar = false;
+ rv = Preferences::GetBool(SAFE_HINT_HEADER_VALUE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mSafeHintEnabled = cVar;
+ }
+ }
+
+ // toggle to true anytime a token bucket related pref is changed.. that
+ // includes telemetry and allow-experiments because of the abtest profile
+ bool requestTokenBucketUpdated = false;
+
+ // "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256" is the required h2 interop
+ // suite.
+
+ if (PREF_CHANGED(H2MANDATORY_SUITE)) {
+ cVar = false;
+ rv = Preferences::GetBool(H2MANDATORY_SUITE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mH2MandatorySuiteEnabled = cVar;
+ }
+ }
+
+ // network.http.debug-observations
+ if (PREF_CHANGED("network.http.debug-observations")) {
+ cVar = false;
+ rv = Preferences::GetBool("network.http.debug-observations", &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mDebugObservations = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.enabled"))) {
+ rv = Preferences::GetBool(HTTP_PREF("pacing.requests.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketEnabled = cVar;
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.min-parallelism"))) {
+ rv =
+ Preferences::GetInt(HTTP_PREF("pacing.requests.min-parallelism"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketMinParallelism =
+ static_cast<uint16_t>(clamped(val, 1, 1024));
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.hz"))) {
+ rv = Preferences::GetInt(HTTP_PREF("pacing.requests.hz"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketHz = static_cast<uint32_t>(clamped(val, 1, 10000));
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.burst"))) {
+ rv = Preferences::GetInt(HTTP_PREF("pacing.requests.burst"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketBurst = val ? val : 1;
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (requestTokenBucketUpdated) {
+ MakeNewRequestTokenBucket();
+ }
+
+ // Keepalive values for initial and idle connections.
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_connections"))) {
+ rv = Preferences::GetBool(
+ HTTP_PREF("tcp_keepalive.short_lived_connections"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveShortLivedEnabled) {
+ mTCPKeepaliveShortLivedEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_time"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tcp_keepalive.short_lived_time"), &val);
+ if (NS_SUCCEEDED(rv) && val > 0) {
+ mTCPKeepaliveShortLivedTimeS = clamped(val, 1, 300); // Max 5 mins.
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_idle_time"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tcp_keepalive.short_lived_idle_time"),
+ &val);
+ if (NS_SUCCEEDED(rv) && val > 0) {
+ mTCPKeepaliveShortLivedIdleTimeS = clamped(val, 1, kMaxTCPKeepIdle);
+ }
+ }
+
+ // Keepalive values for Long-lived Connections.
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_connections"))) {
+ rv = Preferences::GetBool(HTTP_PREF("tcp_keepalive.long_lived_connections"),
+ &cVar);
+ if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveLongLivedEnabled) {
+ mTCPKeepaliveLongLivedEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_idle_time"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tcp_keepalive.long_lived_idle_time"),
+ &val);
+ if (NS_SUCCEEDED(rv) && val > 0) {
+ mTCPKeepaliveLongLivedIdleTimeS = clamped(val, 1, kMaxTCPKeepIdle);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("enforce-framing.http1")) ||
+ PREF_CHANGED(HTTP_PREF("enforce-framing.soft")) ||
+ PREF_CHANGED(HTTP_PREF("enforce-framing.strict_chunked_encoding"))) {
+ rv = Preferences::GetBool(HTTP_PREF("enforce-framing.http1"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_STRICT;
+ } else {
+ rv = Preferences::GetBool(
+ HTTP_PREF("enforce-framing.strict_chunked_encoding"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_STRICT_CHUNKED;
+ } else {
+ rv = Preferences::GetBool(HTTP_PREF("enforce-framing.soft"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_BARELY;
+ } else {
+ mEnforceH1Framing = FRAMECHECK_LAX;
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.default-hpack-buffer"))) {
+ mDefaultHpackBuffer =
+ StaticPrefs::network_http_http2_default_hpack_buffer();
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.default-qpack-table-size"))) {
+ rv = Preferences::GetInt(HTTP_PREF("http3.default-qpack-table-size"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mQpackTableSize = val;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.default-max-stream-blocked"))) {
+ rv = Preferences::GetInt(HTTP_PREF("http3.default-max-stream-blocked"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mHttp3MaxBlockedStreams = clamped(val, 0, 0xffff);
+ }
+ }
+
+ const bool imageAcceptPrefChanged = PREF_CHANGED("image.http.accept") ||
+ PREF_CHANGED("image.avif.enabled") ||
+ PREF_CHANGED("image.jxl.enabled");
+
+ if (imageAcceptPrefChanged) {
+ nsAutoCString userSetImageAcceptHeader;
+
+ if (Preferences::HasUserValue("image.http.accept")) {
+ rv = Preferences::GetCString("image.http.accept",
+ userSetImageAcceptHeader);
+ if (NS_FAILED(rv)) {
+ userSetImageAcceptHeader.Truncate();
+ }
+ }
+
+ if (userSetImageAcceptHeader.IsEmpty()) {
+ mImageAcceptHeader.Assign(ImageAcceptHeader());
+ } else {
+ mImageAcceptHeader.Assign(userSetImageAcceptHeader);
+ }
+ }
+
+ if (PREF_CHANGED("network.http.accept") || imageAcceptPrefChanged) {
+ nsAutoCString userSetDocumentAcceptHeader;
+
+ if (Preferences::HasUserValue("network.http.accept")) {
+ rv = Preferences::GetCString("network.http.accept",
+ userSetDocumentAcceptHeader);
+ if (NS_FAILED(rv)) {
+ userSetDocumentAcceptHeader.Truncate();
+ }
+ }
+
+ if (userSetDocumentAcceptHeader.IsEmpty()) {
+ mDocumentAcceptHeader.Assign(DocumentAcceptHeader(mImageAcceptHeader));
+ } else {
+ mDocumentAcceptHeader.Assign(userSetDocumentAcceptHeader);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.alt-svc-mapping-for-testing"))) {
+ nsAutoCString altSvcMappings;
+ rv = Preferences::GetCString(HTTP_PREF("http3.alt-svc-mapping-for-testing"),
+ altSvcMappings);
+ if (NS_SUCCEEDED(rv)) {
+ for (const nsACString& tokenSubstring :
+ nsCCharSeparatedTokenizer(altSvcMappings, ',').ToRange()) {
+ nsAutoCString token{tokenSubstring};
+ int32_t index = token.Find(";");
+ if (index != kNotFound) {
+ mAltSvcMappingTemptativeMap.InsertOrUpdate(
+ Substring(token, 0, index),
+ MakeUnique<nsCString>(Substring(token, index + 1)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.enable_qlog"))) {
+ // Initialize the directory.
+ nsCOMPtr<nsIFile> qlogDir;
+ if (Preferences::GetBool(HTTP_PREF("http3.enable_qlog")) &&
+ !mHttp3QlogDir.IsEmpty() &&
+ NS_SUCCEEDED(NS_NewNativeLocalFile(mHttp3QlogDir, false,
+ getter_AddRefs(qlogDir)))) {
+ // Here we do main thread IO, but since this only happens
+ // when enabling a developer feature it's not a problem for users.
+ rv = qlogDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ NS_WARNING("Creating qlog dir failed");
+ }
+ }
+ }
+
+ // Enable HTTP response timeout if TCP Keepalives are disabled.
+ mResponseTimeoutEnabled =
+ !mTCPKeepaliveShortLivedEnabled && !mTCPKeepaliveLongLivedEnabled;
+
+#undef PREF_CHANGED
+#undef MULTI_PREF_CHANGED
+}
+
+/**
+ * Allocates a C string into that contains a ISO 639 language list
+ * notated with HTTP "q" values for output with a HTTP Accept-Language
+ * header. Previous q values will be stripped because the order of
+ * the langs imply the q value. The q values are calculated by dividing
+ * 1.0 amongst the number of languages present.
+ *
+ * Ex: passing: "en, ja"
+ * returns: "en,ja;q=0.5"
+ *
+ * passing: "en, ja, fr_CA"
+ * returns: "en,ja;q=0.7,fr_CA;q=0.3"
+ */
+static nsresult PrepareAcceptLanguages(const char* i_AcceptLanguages,
+ nsACString& o_AcceptLanguages) {
+ if (!i_AcceptLanguages) return NS_OK;
+
+ const nsAutoCString ns_accept_languages(i_AcceptLanguages);
+ return rust_prepare_accept_languages(&ns_accept_languages,
+ &o_AcceptLanguages);
+}
+
+// Ensure that we've fetched the AcceptLanguages setting
+/* static */
+void nsHttpHandler::PresetAcceptLanguages() {
+ if (!gHttpHandler) {
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ Unused << handler.get();
+ }
+ [[maybe_unused]] nsresult rv = gHttpHandler->SetAcceptLanguages();
+}
+
+nsresult nsHttpHandler::SetAcceptLanguages() {
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIThread> mainThread;
+ nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Forward to the main thread synchronously.
+ SyncRunnable::DispatchToThread(
+ mainThread,
+ NS_NewRunnableFunction("nsHttpHandler::SetAcceptLanguages", [&rv]() {
+ rv = gHttpHandler->SetAcceptLanguages();
+ }));
+ return rv;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mAcceptLanguagesIsDirty = false;
+
+ nsAutoCString acceptLanguages;
+ Preferences::GetLocalizedCString(INTL_ACCEPT_LANGUAGES, acceptLanguages);
+
+ nsAutoCString buf;
+ nsresult rv = PrepareAcceptLanguages(acceptLanguages.get(), buf);
+ if (NS_SUCCEEDED(rv)) {
+ mAcceptLanguages.Assign(buf);
+ }
+ return rv;
+}
+
+nsresult nsHttpHandler::SetAcceptEncodings(const char* aAcceptEncodings,
+ bool isSecure) {
+ if (isSecure) {
+ mHttpsAcceptEncodings = aAcceptEncodings;
+ } else {
+ // use legacy list if a secure override is not specified
+ mHttpAcceptEncodings = aAcceptEncodings;
+ if (mHttpsAcceptEncodings.IsEmpty()) {
+ mHttpsAcceptEncodings = aAcceptEncodings;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpHandler, nsIHttpProtocolHandler,
+ nsIProxiedProtocolHandler, nsIProtocolHandler, nsIObserver,
+ nsISupportsWeakReference, nsISpeculativeConnect)
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral("http");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ LOG(("nsHttpHandler::NewChannel\n"));
+
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(result);
+
+ // Verify that we have been given a valid scheme
+ if (!uri->SchemeIs("http") && !uri->SchemeIs("https")) {
+ NS_WARNING("Invalid URI scheme");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NewProxiedChannel(uri, nullptr, 0, nullptr, aLoadInfo, result);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::AllowPort(int32_t port, const char* scheme, bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIProxiedProtocolHandler
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpHandler::SetupChannelInternal(
+ HttpBaseChannel* aChannel, nsIURI* uri, nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ RefPtr<HttpBaseChannel> httpChannel = aChannel;
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ uint32_t caps = mCapabilities;
+
+ uint64_t channelId;
+ nsresult rv = NewChannelId(channelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpChannel->Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI,
+ channelId, aLoadInfo->GetExternalContentPolicyType(),
+ aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ httpChannel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ nsILoadInfo* aLoadInfo, nsIChannel** result) {
+ HttpBaseChannel* httpChannel;
+
+ LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n", givenProxyInfo));
+
+ if (IsNeckoChild()) {
+ httpChannel = new HttpChannelChild();
+ } else {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ httpChannel = new nsHttpChannel();
+ }
+
+ return SetupChannelInternal(httpChannel, uri, givenProxyInfo,
+ proxyResolveFlags, proxyURI, aLoadInfo, result);
+}
+
+nsresult nsHttpHandler::CreateTRRServiceChannel(
+ nsIURI* uri, nsIProxyInfo* givenProxyInfo, uint32_t proxyResolveFlags,
+ nsIURI* proxyURI, nsILoadInfo* aLoadInfo, nsIChannel** result) {
+ HttpBaseChannel* httpChannel = new TRRServiceChannel();
+
+ LOG(("nsHttpHandler::CreateTRRServiceChannel [proxyInfo=%p]\n",
+ givenProxyInfo));
+
+ return SetupChannelInternal(httpChannel, uri, givenProxyInfo,
+ proxyResolveFlags, proxyURI, aLoadInfo, result);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIHttpProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::GetUserAgent(nsACString& value) {
+ value = UserAgent(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetRfpUserAgent(nsACString& value) {
+ value = UserAgent(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAppName(nsACString& value) {
+ value = mLegacyAppName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAppVersion(nsACString& value) {
+ value = mLegacyAppVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetPlatform(nsACString& value) {
+ value = mPlatform;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetOscpu(nsACString& value) {
+ value = mOscpu;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetMisc(nsACString& value) {
+ value = mMisc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAltSvcCacheKeys(nsTArray<nsCString>& value) {
+ return mAltSvcCache->GetAltSvcCacheKeys(value);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAuthCacheKeys(nsTArray<nsCString>& aValues) {
+ mAuthCache.CollectKeys(aValues);
+ mPrivateAuthCache.CollectKeys(aValues);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpHandler::Observe [topic=\"%s\"]\n", topic));
+
+ nsresult rv;
+ if (!strcmp(topic, "profile-change-net-teardown") ||
+ !strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ mHandlerActive = false;
+
+ // clear cache of all authentication credentials.
+ mAuthCache.ClearAll();
+ mPrivateAuthCache.ClearAll();
+ if (mWifiTickler) mWifiTickler->Cancel();
+
+ // Inform nsIOService that network is tearing down.
+ gIOService->SetHttpHandlerAlreadyShutingDown();
+
+ ShutdownConnectionManager();
+
+ // need to reset the session start time since cache validation may
+ // depend on this value.
+ mSessionStartTime = NowInSeconds();
+
+ // Since nsHttpHandler::Observe() is also called in socket process, we don't
+ // want to do telemetry twice.
+ if (XRE_IsParentProcess()) {
+ if (!StaticPrefs::privacy_donottrackheader_enabled()) {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 2);
+ } else {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 1);
+ }
+ }
+
+ mActivityDistributor = nullptr;
+ } else if (!strcmp(topic, "profile-change-net-restore")) {
+ // initialize connection manager
+ rv = InitConnectionMgr();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mAltSvcCache = MakeUnique<AltSvcCache>();
+ } else if (!strcmp(topic, "net:clear-active-logins")) {
+ mAuthCache.ClearAll();
+ mPrivateAuthCache.ClearAll();
+ } else if (!strcmp(topic, "net:cancel-all-connections")) {
+ if (mConnMgr) {
+ mConnMgr->AbortAndCloseAllConnections(0, nullptr);
+ }
+ } else if (!strcmp(topic, "net:prune-dead-connections")) {
+ if (mConnMgr) {
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG((" PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ } else if (!strcmp(topic, "net:prune-all-connections")) {
+ if (mConnMgr) {
+ rv = mConnMgr->DoShiftReloadConnectionCleanup();
+ if (NS_FAILED(rv)) {
+ LOG((" DoShiftReloadConnectionCleanup failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG((" PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+#if 0
+ } else if (!strcmp(topic, "net:failed-to-process-uri-content")) {
+ // nop right now - we used to cancel h1 pipelines based on this,
+ // but those are no longer implemented
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
+#endif
+ } else if (!strcmp(topic, "last-pb-context-exited")) {
+ mPrivateAuthCache.ClearAll();
+ if (mAltSvcCache) {
+ mAltSvcCache->ClearAltServiceMappings();
+ }
+ nsCORSListenerProxy::ClearPrivateBrowsingCache();
+ } else if (!strcmp(topic, "browser:purge-session-history")) {
+ if (mConnMgr) {
+ Unused << mConnMgr->ClearConnectionHistory();
+ }
+ if (mAltSvcCache) {
+ mAltSvcCache->ClearAltServiceMappings();
+ }
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ if (mConnMgr) {
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG((" PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ rv = mConnMgr->VerifyTraffic();
+ if (NS_FAILED(rv)) {
+ LOG((" VerifyTraffic failed (%08x)\n", static_cast<uint32_t>(rv)));
+ }
+ }
+ } else if (!strcmp(topic, "application-background")) {
+ // going to the background on android means we should close
+ // down idle connections for power conservation
+ if (mConnMgr) {
+ rv = mConnMgr->DoShiftReloadConnectionCleanup();
+ if (NS_FAILED(rv)) {
+ LOG((" DoShiftReloadConnectionCleanup failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ } else if (!strcmp(topic, "net:current-browser-id")) {
+ // The window id will be updated by HttpConnectionMgrParent.
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(subject);
+ MOZ_RELEASE_ASSERT(wrapper);
+
+ uint64_t id = 0;
+ wrapper->GetData(&id);
+ MOZ_ASSERT(id);
+
+ static uint64_t sCurrentBrowserId = 0;
+ if (sCurrentBrowserId != id) {
+ sCurrentBrowserId = id;
+ if (mConnMgr) {
+ mConnMgr->UpdateCurrentBrowserId(sCurrentBrowserId);
+ }
+ }
+ }
+ } else if (!strcmp(topic, "intl:app-locales-changed")) {
+ // If the locale changed, there's a chance the accept language did too
+ mAcceptLanguagesIsDirty = true;
+ } else if (!strcmp(topic, "network:captive-portal-connectivity")) {
+ nsAutoCString data8 = NS_ConvertUTF16toUTF8(data);
+ mThroughCaptivePortal = data8.EqualsLiteral("captive");
+ } else if (!strcmp(topic, "network:reset-http3-excluded-list")) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp3Origins.Clear();
+ } else if (!strcmp(topic, "network:socket-process-crashed")) {
+ ShutdownConnectionManager();
+ mConnMgr = nullptr;
+ Unused << InitConnectionMgr();
+ }
+
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+
+nsresult nsHttpHandler::SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool anonymous) {
+ if (IsNeckoChild()) {
+ gNeckoChild->SendSpeculativeConnect(
+ aURI, aPrincipal, std::move(aOriginAttributes), anonymous);
+ return NS_OK;
+ }
+
+ if (!mHandlerActive) return NS_OK;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ bool isStsHost = false;
+ if (!sss) return NS_OK;
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ OriginAttributes originAttributes;
+ // If the principal is given, we use the originAttributes from this
+ // principal. Otherwise, we use the originAttributes from the loadContext.
+ if (aOriginAttributes) {
+ originAttributes = std::move(aOriginAttributes.ref());
+ } else if (aPrincipal) {
+ originAttributes = aPrincipal->OriginAttributesRef();
+ StoragePrincipalHelper::UpdateOriginAttributesForNetworkState(
+ aURI, originAttributes);
+ } else if (loadContext) {
+ loadContext->GetOriginAttributes(originAttributes);
+ StoragePrincipalHelper::UpdateOriginAttributesForNetworkState(
+ aURI, originAttributes);
+ }
+
+ nsCOMPtr<nsIURI> clone;
+ if (NS_SUCCEEDED(sss->IsSecureURI(aURI, originAttributes, &isStsHost)) &&
+ isStsHost) {
+ if (NS_SUCCEEDED(NS_GetSecureUpgradedURI(aURI, getter_AddRefs(clone)))) {
+ aURI = clone.get();
+ // (NOTE: We better make sure |clone| stays alive until the end
+ // of the function now, since our aURI arg now points to it!)
+ }
+ }
+
+ nsAutoCString scheme;
+ nsresult rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // If this is HTTPS, make sure PSM is initialized as the channel
+ // creation path may have been bypassed
+ if (scheme.EqualsLiteral("https")) {
+ if (!IsNeckoChild()) {
+ // make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+ }
+ // Ensure that this is HTTP or HTTPS, otherwise we don't do preconnect here
+ else if (!scheme.EqualsLiteral("http")) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString host;
+ rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t port = -1;
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString username;
+ aURI->GetUsername(username);
+
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(host, port, ""_ns, username, nullptr,
+ originAttributes, aURI->SchemeIs("https"));
+ ci->SetAnonymous(anonymous);
+ if (originAttributes.mPrivateBrowsingId > 0) {
+ ci->SetPrivate(true);
+ }
+
+ if (mDebugObservations) {
+ // this is basically used for test coverage of an otherwise 'hintable'
+ // feature
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ // This is used to test if the speculative connection has the right
+ // connection info.
+ nsPrintfCString debugHashKey("%s", ci->HashKey().get());
+ obsService->NotifyObservers(nullptr, "speculative-connect-request",
+ NS_ConvertUTF8toUTF16(debugHashKey).get());
+ for (auto* cp :
+ dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent =
+ SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ Unused << neckoParent->SendSpeculativeConnectRequest();
+ }
+ }
+ }
+
+ return SpeculativeConnect(ci, aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aAnonymous) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, Nothing(), aCallbacks,
+ aAnonymous);
+}
+
+NS_IMETHODIMP nsHttpHandler::SpeculativeConnectWithOriginAttributes(
+ nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SpeculativeConnectWithOriginAttributesNative(aURI, std::move(attrs),
+ aCallbacks, aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsHttpHandler::SpeculativeConnectWithOriginAttributesNative(
+ nsIURI* aURI, OriginAttributes&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) {
+ Maybe<OriginAttributes> originAttributes;
+ originAttributes.emplace(aOriginAttributes);
+ Unused << SpeculativeConnectInternal(
+ aURI, nullptr, std::move(originAttributes), aCallbacks, aAnonymous);
+}
+
+void nsHttpHandler::TickleWifi(nsIInterfaceRequestor* cb) {
+ if (!cb || !mWifiTickler) return;
+
+ // If B2G requires a similar mechanism nsINetworkManager, currently only avail
+ // on B2G, contains the necessary information on wifi and gateway
+
+ nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(cb);
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = do_QueryInterface(domWindow);
+ if (!piWindow) return;
+
+ RefPtr<dom::Navigator> navigator = piWindow->GetNavigator();
+ if (!navigator) return;
+
+ RefPtr<dom::network::Connection> networkProperties =
+ navigator->GetConnection(IgnoreErrors());
+ if (!networkProperties) return;
+
+ uint32_t gwAddress = networkProperties->GetDhcpGateway();
+ bool isWifi = networkProperties->GetIsWifi();
+ if (!gwAddress || !isWifi) return;
+
+ mWifiTickler->SetIPV4Address(gwAddress);
+ mWifiTickler->Tickle();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpsHandler implementation
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpsHandler, nsIHttpProtocolHandler,
+ nsIProxiedProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference, nsISpeculativeConnect)
+
+nsresult nsHttpsHandler::Init() {
+ nsCOMPtr<nsIProtocolHandler> httpHandler(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http"));
+ MOZ_ASSERT(httpHandler.get() != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral("https");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** _retval) {
+ MOZ_ASSERT(gHttpHandler);
+ if (!gHttpHandler) return NS_ERROR_UNEXPECTED;
+ return gHttpHandler->NewChannel(aURI, aLoadInfo, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::AllowPort(int32_t aPort, const char* aScheme, bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::EnsureHSTSDataReadyNative(
+ RefPtr<mozilla::net::HSTSDataCallbackWrapper> aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), "http://example.com");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool shouldUpgrade = false;
+ bool willCallback = false;
+ OriginAttributes originAttributes;
+ auto func = [callback(aCallback)](bool aResult, nsresult aStatus) {
+ callback->DoCallback(aResult);
+ };
+ rv = NS_ShouldSecureUpgrade(uri, nullptr, nullptr, false, originAttributes,
+ shouldUpgrade, std::move(func), willCallback);
+ if (NS_FAILED(rv) || !willCallback) {
+ aCallback->DoCallback(false);
+ return rv;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::EnsureHSTSDataReady(JSContext* aCx, Promise** aPromise) {
+ if (NS_WARN_IF(!aCx)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ if (IsNeckoChild()) {
+ gNeckoChild->SendEnsureHSTSData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise(promise)](
+ NeckoChild::EnsureHSTSDataPromise::ResolveOrRejectValue&& aResult) {
+ if (aResult.IsResolve()) {
+ promise->MaybeResolve(aResult.ResolveValue());
+ } else {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ }
+ });
+ promise.forget(aPromise);
+ return NS_OK;
+ }
+
+ auto callback = [promise(promise)](bool aResult) {
+ promise->MaybeResolve(aResult);
+ };
+
+ RefPtr<HSTSDataCallbackWrapper> wrapper =
+ new HSTSDataCallbackWrapper(std::move(callback));
+ promise.forget(aPromise);
+ return EnsureHSTSDataReadyNative(wrapper);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::ClearCORSPreflightCache() {
+ nsCORSListenerProxy::ClearCache();
+ return NS_OK;
+}
+
+void nsHttpHandler::ShutdownConnectionManager() {
+ // ensure connection manager is shutdown
+ if (mConnMgr) {
+ nsresult rv = mConnMgr->Shutdown();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::ShutdownConnectionManager\n"
+ " failed to shutdown connection manager\n"));
+ }
+ }
+}
+
+nsresult nsHttpHandler::NewChannelId(uint64_t& channelId) {
+ channelId =
+ // channelId is sometimes passed to JavaScript code (e.g. devtools),
+ // and since on Linux PID_MAX_LIMIT is 2^22 we cannot
+ // shift PID more than 31 bits left. Otherwise resulting values
+ // will be exceed safe JavaScript integer range.
+ ((static_cast<uint64_t>(mProcessId) << 31) & 0xFFFFFFFF80000000LL) |
+ mNextChannelId++;
+ return NS_OK;
+}
+
+void nsHttpHandler::NotifyActiveTabLoadOptimization() {
+ SetLastActiveTabLoadOptimizationHit(TimeStamp::Now());
+}
+
+TimeStamp nsHttpHandler::GetLastActiveTabLoadOptimizationHit() {
+ MutexAutoLock lock(mLastActiveTabLoadOptimizationLock);
+
+ return mLastActiveTabLoadOptimizationHit;
+}
+
+void nsHttpHandler::SetLastActiveTabLoadOptimizationHit(TimeStamp const& when) {
+ MutexAutoLock lock(mLastActiveTabLoadOptimizationLock);
+
+ if (mLastActiveTabLoadOptimizationHit.IsNull() ||
+ (!when.IsNull() && mLastActiveTabLoadOptimizationHit < when)) {
+ mLastActiveTabLoadOptimizationHit = when;
+ }
+}
+
+bool nsHttpHandler::IsBeforeLastActiveTabLoadOptimization(
+ TimeStamp const& when) {
+ MutexAutoLock lock(mLastActiveTabLoadOptimizationLock);
+
+ return !mLastActiveTabLoadOptimizationHit.IsNull() &&
+ when <= mLastActiveTabLoadOptimizationHit;
+}
+
+void nsHttpHandler::ExcludeHttp2OrHttp3Internal(
+ const nsHttpConnectionInfo* ci) {
+ LOG(("nsHttpHandler::ExcludeHttp2OrHttp3Internal ci=%s",
+ ci->HashKey().get()));
+ // The excluded list needs to be stayed synced between parent process and
+ // socket process, so we send this information to the parent process here.
+ if (XRE_IsSocketProcess()) {
+ MOZ_ASSERT(OnSocketThread());
+
+ RefPtr<nsHttpConnectionInfo> cinfo = ci->Clone();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsHttpHandler::ExcludeHttp2OrHttp3Internal",
+ [cinfo{std::move(cinfo)}]() {
+ HttpConnectionInfoCloneArgs connInfoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(cinfo,
+ connInfoArgs);
+ Unused << SocketProcessChild::GetSingleton()->SendExcludeHttp2OrHttp3(
+ connInfoArgs);
+ }));
+ }
+
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess() && XRE_IsParentProcess(),
+ NS_IsMainThread());
+ MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(), OnSocketThread());
+
+ if (ci->IsHttp3()) {
+ if (!mExcludedHttp3Origins.Contains(ci->GetRoutedHost())) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp3Origins.Insert(ci->GetRoutedHost());
+ }
+ mConnMgr->ExcludeHttp3(ci);
+ } else {
+ if (!mExcludedHttp2Origins.Contains(ci->GetOrigin())) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp2Origins.Insert(ci->GetOrigin());
+ }
+ mConnMgr->ExcludeHttp2(ci);
+ }
+}
+
+void nsHttpHandler::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
+ ExcludeHttp2OrHttp3Internal(ci);
+}
+
+bool nsHttpHandler::IsHttp2Excluded(const nsHttpConnectionInfo* ci) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ return mExcludedHttp2Origins.Contains(ci->GetOrigin());
+}
+
+void nsHttpHandler::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(ci->IsHttp3());
+ ExcludeHttp2OrHttp3Internal(ci);
+}
+
+bool nsHttpHandler::IsHttp3Excluded(const nsACString& aRoutedHost) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ return mExcludedHttp3Origins.Contains(aRoutedHost);
+}
+
+HttpTrafficAnalyzer* nsHttpHandler::GetHttpTrafficAnalyzer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+ return nullptr;
+ }
+
+ return &mHttpTrafficAnalyzer;
+}
+
+bool nsHttpHandler::IsHttp3Enabled() {
+ static const uint32_t TLS3_PREF_VALUE = 4;
+
+ return StaticPrefs::network_http_http3_enable() &&
+ (StaticPrefs::security_tls_version_max() >= TLS3_PREF_VALUE);
+}
+
+bool nsHttpHandler::IsHttp3VersionSupported(const nsACString& version) {
+ if (!StaticPrefs::network_http_http3_support_version1() &&
+ version.EqualsLiteral("h3")) {
+ return false;
+ }
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ if (version.Equals(kHttp3Versions[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool nsHttpHandler::IsHttp3SupportedByServer(
+ nsHttpResponseHead* aResponseHead) {
+ if ((aResponseHead->Version() != HttpVersion::v2_0) ||
+ (aResponseHead->Status() >= 500) || (aResponseHead->Status() == 421)) {
+ return false;
+ }
+
+ nsAutoCString altSvc;
+ Unused << aResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (altSvc.IsEmpty() || !nsHttp::IsReasonableHeaderValue(altSvc)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ nsAutoCString value(kHttp3Versions[i]);
+ value.Append("="_ns);
+ if (strstr(altSvc.get(), value.get())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult nsHttpHandler::InitiateTransaction(HttpTransactionShell* aTrans,
+ int32_t aPriority) {
+ return mConnMgr->AddTransaction(aTrans, aPriority);
+}
+nsresult nsHttpHandler::InitiateTransactionWithStickyConn(
+ HttpTransactionShell* aTrans, int32_t aPriority,
+ HttpTransactionShell* aTransWithStickyConn) {
+ return mConnMgr->AddTransactionWithStickyConn(aTrans, aPriority,
+ aTransWithStickyConn);
+}
+
+nsresult nsHttpHandler::RescheduleTransaction(HttpTransactionShell* trans,
+ int32_t priority) {
+ return mConnMgr->RescheduleTransaction(trans, priority);
+}
+
+void nsHttpHandler::UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell* trans, const ClassOfService& classOfService) {
+ mConnMgr->UpdateClassOfServiceOnTransaction(trans, classOfService);
+}
+
+nsresult nsHttpHandler::CancelTransaction(HttpTransactionShell* trans,
+ nsresult reason) {
+ return mConnMgr->CancelTransaction(trans, reason);
+}
+
+void nsHttpHandler::AddHttpChannel(uint64_t aId, nsISupports* aChannel) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsWeakPtr channel(do_GetWeakReference(aChannel));
+ mIDToHttpChannelMap.InsertOrUpdate(aId, std::move(channel));
+}
+
+void nsHttpHandler::RemoveHttpChannel(uint64_t aId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod<uint64_t>(
+ "nsHttpHandler::RemoveHttpChannel", this,
+ &nsHttpHandler::RemoveHttpChannel, aId));
+
+ NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable),
+ EventQueuePriority::Idle);
+ return;
+ }
+
+ auto entry = mIDToHttpChannelMap.Lookup(aId);
+ if (entry) {
+ entry.Remove();
+ }
+}
+
+nsWeakPtr nsHttpHandler::GetWeakHttpChannel(uint64_t aId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mIDToHttpChannelMap.Get(aId);
+}
+
+nsresult nsHttpHandler::CompleteUpgrade(
+ HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
+ return mConnMgr->CompleteUpgrade(aTrans, aUpgradeListener);
+}
+
+nsresult nsHttpHandler::DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCi) {
+ MOZ_ASSERT(aCi);
+ return mConnMgr->DoShiftReloadConnectionCleanupWithConnInfo(aCi);
+}
+
+void nsHttpHandler::ClearHostMapping(nsHttpConnectionInfo* aConnInfo) {
+ if (XRE_IsSocketProcess()) {
+ AltServiceChild::ClearHostMapping(aConnInfo);
+ return;
+ }
+
+ AltServiceCache()->ClearHostMapping(aConnInfo);
+}
+
+void nsHttpHandler::SetHttpHandlerInitArgs(const HttpHandlerInitArgs& aArgs) {
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ mLegacyAppName = aArgs.mLegacyAppName();
+ mLegacyAppVersion = aArgs.mLegacyAppVersion();
+ mPlatform = aArgs.mPlatform();
+ mOscpu = aArgs.mOscpu();
+ mMisc = aArgs.mMisc();
+ mProduct = aArgs.mProduct();
+ mProductSub = aArgs.mProductSub();
+ mAppName = aArgs.mAppName();
+ mAppVersion = aArgs.mAppVersion();
+ mCompatFirefox = aArgs.mCompatFirefox();
+ mCompatDevice = aArgs.mCompatDevice();
+ mDeviceModelId = aArgs.mDeviceModelId();
+}
+
+void nsHttpHandler::SetDeviceModelId(const nsACString& aModelId) {
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ mDeviceModelId = aModelId;
+}
+
+void nsHttpHandler::MaybeAddAltSvcForTesting(
+ nsIURI* aUri, const nsACString& aUsername, bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks,
+ const OriginAttributes& aOriginAttributes) {
+ if (!IsHttp3Enabled() || mAltSvcMappingTemptativeMap.IsEmpty()) {
+ return;
+ }
+
+ bool isHttps = false;
+ if (NS_FAILED(aUri->SchemeIs("https", &isHttps)) || !isHttps) {
+ // Only set for HTTPS.
+ return;
+ }
+
+ nsAutoCString originHost;
+ if (NS_FAILED(aUri->GetAsciiHost(originHost))) {
+ return;
+ }
+
+ nsCString* map = mAltSvcMappingTemptativeMap.Get(originHost);
+ if (map) {
+ int32_t originPort = 80;
+ aUri->GetPort(&originPort);
+ LOG(("nsHttpHandler::MaybeAddAltSvcForTesting for %s map: %s",
+ originHost.get(), PromiseFlatCString(*map).get()));
+ AltSvcMapping::ProcessHeader(
+ *map, nsCString("https"), originHost, originPort, aUsername,
+ aPrivateBrowsing, aCallbacks, nullptr, 0, aOriginAttributes, true);
+ }
+}
+
+bool nsHttpHandler::UseHTTPSRRAsAltSvcEnabled() const {
+ return StaticPrefs::network_dns_use_https_rr_as_altsvc();
+}
+
+bool nsHttpHandler::EchConfigEnabled(bool aIsHttp3) const {
+ if (!aIsHttp3) {
+ return StaticPrefs::network_dns_echconfig_enabled();
+ }
+
+ return StaticPrefs::network_dns_echconfig_enabled() &&
+ StaticPrefs::network_dns_http3_echconfig_enabled();
+}
+
+bool nsHttpHandler::FallbackToOriginIfConfigsAreECHAndAllFailed() const {
+ return StaticPrefs::
+ network_dns_echconfig_fallback_to_origin_when_all_failed();
+}
+
+void nsHttpHandler::ExcludeHTTPSRRHost(const nsACString& aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mExcludedHostsForHTTPSRRUpgrade.Insert(aHost);
+}
+
+bool nsHttpHandler::IsHostExcludedForHTTPSRR(const nsACString& aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mExcludedHostsForHTTPSRRUpgrade.Contains(aHost);
+}
+
+void nsHttpHandler::Exclude0RttTcp(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!StaticPrefs::network_http_early_data_disable_on_error() ||
+ (mExcluded0RttTcpOrigins.Count() >=
+ StaticPrefs::network_http_early_data_max_error())) {
+ return;
+ }
+
+ mExcluded0RttTcpOrigins.Insert(ci->GetOrigin());
+}
+
+bool nsHttpHandler::Is0RttTcpExcluded(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread());
+ if (!StaticPrefs::network_http_early_data_disable_on_error()) {
+ return false;
+ }
+
+ if (mExcluded0RttTcpOrigins.Count() >=
+ StaticPrefs::network_http_early_data_max_error()) {
+ return true;
+ }
+
+ return mExcluded0RttTcpOrigins.Contains(ci->GetOrigin());
+}
+
+bool nsHttpHandler::HttpActivityDistributorActivated() {
+ if (!mActivityDistributor) {
+ return false;
+ }
+
+ return mActivityDistributor->Activated();
+}
+
+void nsHttpHandler::ObserveHttpActivityWithArgs(
+ const HttpActivityArgs& aArgs, uint32_t aActivityType,
+ uint32_t aActivitySubtype, PRTime aTimestamp, uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData) {
+ if (!HttpActivityDistributorActivated()) {
+ return;
+ }
+
+ if (aActivitySubtype == NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER &&
+ !mActivityDistributor->ObserveProxyResponseEnabled()) {
+ return;
+ }
+
+ if (aActivityType == NS_ACTIVITY_TYPE_HTTP_CONNECTION &&
+ !mActivityDistributor->ObserveConnectionEnabled()) {
+ return;
+ }
+
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ aArgs, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData,
+ aExtraStringData);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
new file mode 100644
index 0000000000..cf745347ee
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -0,0 +1,918 @@
+/* -*- 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 nsHttpHandler_h__
+#define nsHttpHandler_h__
+
+#include <functional>
+
+#include "nsHttp.h"
+#include "nsHttpAuthCache.h"
+#include "nsHttpConnectionMgr.h"
+#include "AlternateServices.h"
+#include "ASpdySession.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+#include "nsIHttpProtocolHandler.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+#include "nsTHashMap.h"
+#include "nsTHashSet.h"
+
+#ifdef DEBUG
+# include "nsIOService.h"
+#endif
+
+// XXX These includes can be replaced by forward declarations by moving the On*
+// method implementations to the cpp file
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsSocketTransportService2.h"
+
+class nsIHttpActivityDistributor;
+class nsIHttpUpgradeListener;
+class nsIPrefBranch;
+class nsICancelable;
+class nsICookieService;
+class nsIIOService;
+class nsIRequestContextService;
+class nsISiteSecurityService;
+class nsIStreamConverterService;
+
+namespace mozilla::net {
+
+class ATokenBucketEvent;
+class EventTokenBucket;
+class Tickler;
+class nsHttpConnection;
+class nsHttpConnectionInfo;
+class HttpBaseChannel;
+class HttpHandlerInitArgs;
+class HttpTransactionShell;
+class AltSvcMapping;
+class DNSUtils;
+class TRRServiceChannel;
+class SocketProcessChild;
+
+/*
+ * FRAMECHECK_LAX - no check
+ * FRAMECHECK_BARELY - allows:
+ * 1) that chunk-encoding does not have the last 0-size
+ * chunk. So, if a chunked-encoded transfer ends on exactly
+ * a chunk boundary we consider that fine. This will allows
+ * us to accept buggy servers that do not send the last
+ * chunk. It will make us not detect a certain amount of
+ * cut-offs.
+ * 2) When receiving a gzipped response, we consider a
+ * gzip stream that doesn't end fine according to the gzip
+ * decompressing state machine to be a partial transfer.
+ * If a gzipped transfer ends fine according to the
+ * decompressor, we do not check for size unalignments.
+ * This allows to allow HTTP gzipped responses where the
+ * Content-Length is not the same as the actual contents.
+ * 3) When receiving HTTP that isn't
+ * content-encoded/compressed (like in case 2) and not
+ * chunked (like in case 1), perform the size comparison
+ * between Content-Length: and the actual size received
+ * and consider a mismatch to mean a
+ * NS_ERROR_NET_PARTIAL_TRANSFER error.
+ * FRAMECHECK_STRICT_CHUNKED - This is the same as FRAMECHECK_BARELY only we
+ * enforce that the last 0-size chunk is received
+ * in case 1).
+ * FRAMECHECK_STRICT - we also do not allow case 2) and 3) from
+ * FRAMECHECK_BARELY.
+ */
+enum FrameCheckLevel {
+ FRAMECHECK_LAX,
+ FRAMECHECK_BARELY,
+ FRAMECHECK_STRICT_CHUNKED,
+ FRAMECHECK_STRICT
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler - protocol handler for HTTP and HTTPS
+//-----------------------------------------------------------------------------
+
+class nsHttpHandler final : public nsIHttpProtocolHandler,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsISpeculativeConnect {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROXIEDPROTOCOLHANDLER
+ NS_DECL_NSIHTTPPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISPECULATIVECONNECT
+
+ static already_AddRefed<nsHttpHandler> GetInstance();
+
+ [[nodiscard]] nsresult AddStandardRequestHeaders(
+ nsHttpRequestHead*, bool isSecure,
+ ExtContentPolicyType aContentPolicyType,
+ bool aShouldResistFingerprinting);
+ [[nodiscard]] nsresult AddConnectionHeader(nsHttpRequestHead*, uint32_t caps);
+ bool IsAcceptableEncoding(const char* encoding, bool isSecure);
+
+ const nsCString& UserAgent(bool aShouldResistFingerprinting);
+
+ enum HttpVersion HttpVersion() { return mHttpVersion; }
+ enum HttpVersion ProxyHttpVersion() { return mProxyHttpVersion; }
+ uint8_t RedirectionLimit() { return mRedirectionLimit; }
+ PRIntervalTime IdleTimeout() { return mIdleTimeout; }
+ PRIntervalTime SpdyTimeout() { return mSpdyTimeout; }
+ PRIntervalTime ResponseTimeout() {
+ return mResponseTimeoutEnabled ? mResponseTimeout : 0;
+ }
+ PRIntervalTime ResponseTimeoutEnabled() { return mResponseTimeoutEnabled; }
+ uint32_t NetworkChangedTimeout() { return mNetworkChangedTimeout; }
+ uint16_t MaxRequestAttempts() { return mMaxRequestAttempts; }
+ const nsCString& DefaultSocketType() { return mDefaultSocketType; }
+ uint32_t PhishyUserPassLength() { return mPhishyUserPassLength; }
+ uint8_t GetQoSBits() { return mQoSBits; }
+ uint16_t GetIdleSynTimeout() { return mIdleSynTimeout; }
+ uint16_t GetFallbackSynTimeout() { return mFallbackSynTimeout; }
+ bool FastFallbackToIPv4() { return mFastFallbackToIPv4; }
+ uint32_t MaxSocketCount();
+ bool EnforceAssocReq() { return mEnforceAssocReq; }
+
+ bool IsPersistentHttpsCachingEnabled() {
+ return mEnablePersistentHttpsCaching;
+ }
+
+ uint32_t SpdySendingChunkSize() { return mSpdySendingChunkSize; }
+ uint32_t SpdySendBufferSize() { return mSpdySendBufferSize; }
+ uint32_t SpdyPushAllowance() { return mSpdyPushAllowance; }
+ uint32_t SpdyPullAllowance() { return mSpdyPullAllowance; }
+ uint32_t DefaultSpdyConcurrent() { return mDefaultSpdyConcurrent; }
+ PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
+ PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
+ bool AllowAltSvc() { return mEnableAltSvc; }
+ bool AllowAltSvcOE() { return mEnableAltSvcOE; }
+ bool AllowOriginExtension() { return mEnableOriginExtension; }
+ uint32_t ConnectTimeout() { return mConnectTimeout; }
+ uint32_t TLSHandshakeTimeout() { return mTLSHandshakeTimeout; }
+ uint32_t ParallelSpeculativeConnectLimit() {
+ return mParallelSpeculativeConnectLimit;
+ }
+ bool CriticalRequestPrioritization() {
+ return mCriticalRequestPrioritization;
+ }
+
+ uint32_t MaxConnectionsPerOrigin() {
+ return mMaxPersistentConnectionsPerServer;
+ }
+ bool UseRequestTokenBucket() { return mRequestTokenBucketEnabled; }
+ uint16_t RequestTokenBucketMinParallelism() {
+ return mRequestTokenBucketMinParallelism;
+ }
+ uint32_t RequestTokenBucketHz() { return mRequestTokenBucketHz; }
+ uint32_t RequestTokenBucketBurst() { return mRequestTokenBucketBurst; }
+
+ bool PromptTempRedirect() { return mPromptTempRedirect; }
+ bool IsUrgentStartEnabled() { return mUrgentStartEnabled; }
+ bool IsTailBlockingEnabled() { return mTailBlockingEnabled; }
+ uint32_t TailBlockingDelayQuantum(bool aAfterDOMContentLoaded) {
+ return aAfterDOMContentLoaded ? mTailDelayQuantumAfterDCL
+ : mTailDelayQuantum;
+ }
+ uint32_t TailBlockingDelayMax() { return mTailDelayMax; }
+ uint32_t TailBlockingTotalMax() { return mTailTotalMax; }
+
+ uint32_t ThrottlingReadLimit() {
+ return mThrottleVersion == 1 ? 0 : mThrottleReadLimit;
+ }
+ int32_t SendWindowSize() { return mSendWindowSize * 1024; }
+
+ // TCP Keepalive configuration values.
+
+ // Returns true if TCP keepalive should be enabled for short-lived conns.
+ bool TCPKeepaliveEnabledForShortLivedConns() {
+ return mTCPKeepaliveShortLivedEnabled;
+ }
+ // Return time (secs) that a connection is consider short lived (for TCP
+ // keepalive purposes). After this time, the connection is long-lived.
+ int32_t GetTCPKeepaliveShortLivedTime() {
+ return mTCPKeepaliveShortLivedTimeS;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveShortLivedIdleTime() {
+ return mTCPKeepaliveShortLivedIdleTimeS;
+ }
+
+ // Returns true if TCP keepalive should be enabled for long-lived conns.
+ bool TCPKeepaliveEnabledForLongLivedConns() {
+ return mTCPKeepaliveLongLivedEnabled;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveLongLivedIdleTime() {
+ return mTCPKeepaliveLongLivedIdleTimeS;
+ }
+
+ // returns the HTTP framing check level preference, as controlled with
+ // network.http.enforce-framing.http1 and network.http.enforce-framing.soft
+ FrameCheckLevel GetEnforceH1Framing() { return mEnforceH1Framing; }
+
+ nsHttpAuthCache* AuthCache(bool aPrivate) {
+ return aPrivate ? &mPrivateAuthCache : &mAuthCache;
+ }
+ nsHttpConnectionMgr* ConnMgr() {
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess());
+ return mConnMgr->AsHttpConnectionMgr();
+ }
+
+ AltSvcCache* AltServiceCache() const {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return mAltSvcCache.get();
+ }
+
+ void ClearHostMapping(nsHttpConnectionInfo* aConnInfo);
+
+ // cache support
+ uint32_t GenerateUniqueID() { return ++mLastUniqueID; }
+ uint32_t SessionStartTime() { return mSessionStartTime; }
+
+ //
+ // Connection management methods:
+ //
+ // - the handler only owns idle connections; it does not own active
+ // connections.
+ //
+ // - the handler keeps a count of active connections to enforce the
+ // steady-state max-connections pref.
+ //
+
+ // Called to kick-off a new transaction, by default the transaction
+ // will be put on the pending transaction queue if it cannot be
+ // initiated at this time. Callable from any thread.
+ [[nodiscard]] nsresult InitiateTransaction(HttpTransactionShell* trans,
+ int32_t priority);
+
+ // This function is also called to kick-off a new transaction. But the new
+ // transaction will take a sticky connection from |transWithStickyConn|
+ // and reuse it.
+ [[nodiscard]] nsresult InitiateTransactionWithStickyConn(
+ HttpTransactionShell* trans, int32_t priority,
+ HttpTransactionShell* transWithStickyConn);
+
+ // Called to change the priority of an existing transaction that has
+ // already been initiated.
+ [[nodiscard]] nsresult RescheduleTransaction(HttpTransactionShell* trans,
+ int32_t priority);
+
+ void UpdateClassOfServiceOnTransaction(HttpTransactionShell* trans,
+ const ClassOfService& classOfService);
+
+ // Called to cancel a transaction, which may or may not be assigned to
+ // a connection. Callable from any thread.
+ [[nodiscard]] nsresult CancelTransaction(HttpTransactionShell* trans,
+ nsresult reason);
+
+ // Called when a connection is done processing a transaction. Callable
+ // from any thread.
+ [[nodiscard]] nsresult ReclaimConnection(HttpConnectionBase* conn) {
+ return mConnMgr->ReclaimConnection(conn);
+ }
+
+ [[nodiscard]] nsresult ProcessPendingQ(nsHttpConnectionInfo* cinfo) {
+ return mConnMgr->ProcessPendingQ(cinfo);
+ }
+
+ [[nodiscard]] nsresult ProcessPendingQ() {
+ return mConnMgr->ProcessPendingQ();
+ }
+
+ [[nodiscard]] nsresult GetSocketThreadTarget(nsIEventTarget** target) {
+ return mConnMgr->GetSocketThreadTarget(target);
+ }
+
+ [[nodiscard]] nsresult SpeculativeConnect(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps = 0,
+ bool aFetchHTTPSRR = false) {
+ TickleWifi(callbacks);
+ RefPtr<nsHttpConnectionInfo> clone = ci->Clone();
+ return mConnMgr->SpeculativeConnect(clone, callbacks, caps, nullptr,
+ aFetchHTTPSRR | EchConfigEnabled());
+ }
+
+ [[nodiscard]] nsresult SpeculativeConnect(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps,
+ SpeculativeTransaction* aTrans) {
+ RefPtr<nsHttpConnectionInfo> clone = ci->Clone();
+ return mConnMgr->SpeculativeConnect(clone, callbacks, caps, aTrans);
+ }
+
+ // Alternate Services Maps are main thread only
+ void UpdateAltServiceMapping(AltSvcMapping* map, nsProxyInfo* proxyInfo,
+ nsIInterfaceRequestor* callbacks, uint32_t caps,
+ const OriginAttributes& originAttributes) {
+ mAltSvcCache->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps,
+ originAttributes);
+ }
+
+ void UpdateAltServiceMappingWithoutValidation(
+ AltSvcMapping* map, nsProxyInfo* proxyInfo,
+ nsIInterfaceRequestor* callbacks, uint32_t caps,
+ const OriginAttributes& originAttributes) {
+ mAltSvcCache->UpdateAltServiceMappingWithoutValidation(
+ map, proxyInfo, callbacks, caps, originAttributes);
+ }
+
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(
+ const nsACString& scheme, const nsACString& host, int32_t port, bool pb,
+ const OriginAttributes& originAttributes, bool aHttp2Allowed,
+ bool aHttp3Allowed) {
+ return mAltSvcCache->GetAltServiceMapping(
+ scheme, host, port, pb, originAttributes, aHttp2Allowed, aHttp3Allowed);
+ }
+
+ //
+ // The HTTP handler caches pointers to specific XPCOM services, and
+ // provides the following helper routines for accessing those services:
+ //
+ [[nodiscard]] nsresult GetIOService(nsIIOService** result);
+ nsICookieService* GetCookieService(); // not addrefed
+ nsISiteSecurityService* GetSSService();
+
+ // Called by the channel synchronously during asyncOpen
+ void OnFailedOpeningRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_FAILED_OPENING_REQUEST_TOPIC);
+ }
+
+ // Called by the channel synchronously during asyncOpen
+ void OnOpeningRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_OPENING_REQUEST_TOPIC);
+ }
+
+ void OnOpeningDocumentRequest(nsIIdentChannel* chan) {
+ NotifyObservers(chan, NS_DOCUMENT_ON_OPENING_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before writing a request
+ void OnModifyRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_MODIFY_REQUEST_TOPIC);
+ }
+
+ // Same as OnModifyRequest but before cookie headers are written.
+ void OnModifyRequestBeforeCookies(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_MODIFY_REQUEST_BEFORE_COOKIES_TOPIC);
+ }
+
+ void OnModifyDocumentRequest(nsIIdentChannel* chan) {
+ NotifyObservers(chan, NS_DOCUMENT_ON_MODIFY_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before writing a request
+ void OnStopRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_STOP_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before setting up the transaction
+ void OnBeforeConnect(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_BEFORE_CONNECT_TOPIC);
+ }
+
+ // Called by the channel once headers are available
+ void OnExamineResponse(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC);
+ }
+
+ // Called by the channel once headers have been merged with cached headers
+ void OnExamineMergedResponse(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC);
+ }
+
+ // Called by the channel once it made background cache revalidation
+ void OnBackgroundRevalidation(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_BACKGROUND_REVALIDATION);
+ }
+
+ // Called by channels before a redirect happens. This notifies both the
+ // channel's and the global redirect observers.
+ [[nodiscard]] nsresult AsyncOnChannelRedirect(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget = nullptr);
+
+ // Called by the channel when the response is read from the cache without
+ // communicating with the server.
+ void OnExamineCachedResponse(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC);
+ }
+
+ // Called by the channel when the transaction pump is suspended because of
+ // trying to get credentials asynchronously.
+ void OnTransactionSuspendedDueToAuthentication(nsIHttpChannel* chan) {
+ NotifyObservers(chan, "http-on-transaction-suspended-authentication");
+ }
+
+ // Generates the host:port string for use in the Host: header as well as the
+ // CONNECT line for proxies. This handles IPv6 literals correctly.
+ [[nodiscard]] static nsresult GenerateHostPort(const nsCString& host,
+ int32_t port,
+ nsACString& hostLine);
+
+ SpdyInformation* SpdyInfo() { return &mSpdyInfo; }
+ bool IsH2MandatorySuiteEnabled() { return mH2MandatorySuiteEnabled; }
+
+ // returns true in between Init and Shutdown states
+ bool Active() { return mHandlerActive; }
+
+ nsIRequestContextService* GetRequestContextService() {
+ return mRequestContextService.get();
+ }
+
+ void ShutdownConnectionManager();
+
+ uint32_t DefaultHpackBuffer() const { return mDefaultHpackBuffer; }
+
+ static bool IsHttp3Enabled();
+ bool IsHttp3VersionSupported(const nsACString& version);
+
+ static bool IsHttp3SupportedByServer(nsHttpResponseHead* aResponseHead);
+ uint32_t DefaultQpackTableSize() const { return mQpackTableSize; }
+ uint16_t DefaultHttp3MaxBlockedStreams() const {
+ return (uint16_t)mHttp3MaxBlockedStreams;
+ }
+
+ uint32_t MaxHttpResponseHeaderSize() const {
+ return mMaxHttpResponseHeaderSize;
+ }
+
+ const nsCString& Http3QlogDir();
+
+ float FocusedWindowTransactionRatio() const {
+ return mFocusedWindowTransactionRatio;
+ }
+
+ bool ActiveTabPriority() const { return mActiveTabPriority; }
+
+ // Called when an optimization feature affecting active vs background tab load
+ // took place. Called only on the parent process and only updates
+ // mLastActiveTabLoadOptimizationHit timestamp to now.
+ void NotifyActiveTabLoadOptimization();
+ TimeStamp GetLastActiveTabLoadOptimizationHit();
+ void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when);
+ bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when);
+
+ HttpTrafficAnalyzer* GetHttpTrafficAnalyzer();
+
+ bool GetThroughCaptivePortal() { return mThroughCaptivePortal; }
+
+ nsresult CompleteUpgrade(HttpTransactionShell* aTrans,
+ nsIHttpUpgradeListener* aUpgradeListener);
+
+ nsresult DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCI);
+
+ void MaybeAddAltSvcForTesting(nsIURI* aUri, const nsACString& aUsername,
+ bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks,
+ const OriginAttributes& aOriginAttributes);
+
+ bool UseHTTPSRRAsAltSvcEnabled() const;
+
+ bool EchConfigEnabled(bool aIsHttp3 = false) const;
+ // When EchConfig is enabled and all records with echConfig are failed, this
+ // functon indicate whether we can fallback to the origin server.
+ // In the case an HTTPS RRSet contains some RRs with echConfig and some
+ // without, we always fallback to the origin one.
+ bool FallbackToOriginIfConfigsAreECHAndAllFailed() const;
+
+ // So we can ensure that this is done during process preallocation to
+ // avoid first-use overhead
+ static void PresetAcceptLanguages();
+
+ bool HttpActivityDistributorActivated();
+ void ObserveHttpActivityWithArgs(const HttpActivityArgs& aArgs,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype, PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData);
+
+ private:
+ nsHttpHandler();
+
+ virtual ~nsHttpHandler();
+
+ [[nodiscard]] nsresult Init();
+
+ //
+ // Useragent/prefs helper methods
+ //
+ void BuildUserAgent();
+ void InitUserAgentComponents();
+ static void PrefsChanged(const char* pref, void* self);
+ void PrefsChanged(const char* pref);
+
+ [[nodiscard]] nsresult SetAcceptLanguages();
+ [[nodiscard]] nsresult SetAcceptEncodings(const char*, bool mIsSecure);
+
+ [[nodiscard]] nsresult InitConnectionMgr();
+
+ void NotifyObservers(nsIChannel* chan, const char* event);
+
+ friend class SocketProcessChild;
+ void SetHttpHandlerInitArgs(const HttpHandlerInitArgs& aArgs);
+ void SetDeviceModelId(const nsACString& aModelId);
+
+ // We only allow DNSUtils and TRRServiceChannel itself to create
+ // TRRServiceChannel.
+ friend class TRRServiceChannel;
+ friend class DNSUtils;
+ nsresult CreateTRRServiceChannel(nsIURI* uri, nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ nsILoadInfo* aLoadInfo, nsIChannel** result);
+ nsresult SetupChannelInternal(HttpBaseChannel* aChannel, nsIURI* uri,
+ nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ nsILoadInfo* aLoadInfo, nsIChannel** result);
+
+ private:
+ // cached services
+ nsMainThreadPtrHandle<nsIIOService> mIOService;
+ nsMainThreadPtrHandle<nsICookieService> mCookieService;
+ nsMainThreadPtrHandle<nsISiteSecurityService> mSSService;
+
+ // the authentication credentials cache
+ nsHttpAuthCache mAuthCache;
+ nsHttpAuthCache mPrivateAuthCache;
+
+ // the connection manager
+ RefPtr<HttpConnectionMgrShell> mConnMgr;
+
+ UniquePtr<AltSvcCache> mAltSvcCache;
+
+ //
+ // prefs
+ //
+
+ enum HttpVersion mHttpVersion { HttpVersion::v1_1 };
+ enum HttpVersion mProxyHttpVersion { HttpVersion::v1_1 };
+ uint32_t mCapabilities{NS_HTTP_ALLOW_KEEPALIVE};
+
+ bool mFastFallbackToIPv4{false};
+ PRIntervalTime mIdleTimeout;
+ PRIntervalTime mSpdyTimeout;
+ PRIntervalTime mResponseTimeout;
+ Atomic<bool, Relaxed> mResponseTimeoutEnabled{false};
+ uint32_t mNetworkChangedTimeout{5000}; // milliseconds
+ uint16_t mMaxRequestAttempts{6};
+ uint16_t mMaxRequestDelay{10};
+ uint16_t mIdleSynTimeout{250};
+ uint16_t mFallbackSynTimeout{5}; // seconds
+
+ bool mH2MandatorySuiteEnabled{false};
+ uint16_t mMaxUrgentExcessiveConns{3};
+ uint16_t mMaxConnections{24};
+ uint8_t mMaxPersistentConnectionsPerServer{2};
+ uint8_t mMaxPersistentConnectionsPerProxy{4};
+
+ bool mThrottleEnabled{true};
+ uint32_t mThrottleVersion{2};
+ uint32_t mThrottleSuspendFor{3000};
+ uint32_t mThrottleResumeFor{200};
+ uint32_t mThrottleReadLimit{8000};
+ uint32_t mThrottleReadInterval{500};
+ uint32_t mThrottleHoldTime{600};
+ uint32_t mThrottleMaxTime{3000};
+
+ int32_t mSendWindowSize{1024};
+
+ bool mUrgentStartEnabled{true};
+ bool mTailBlockingEnabled{true};
+ uint32_t mTailDelayQuantum{600};
+ uint32_t mTailDelayQuantumAfterDCL{100};
+ uint32_t mTailDelayMax{6000};
+ uint32_t mTailTotalMax{0};
+
+ uint8_t mRedirectionLimit{10};
+
+ bool mBeConservativeForProxy{true};
+
+ // we'll warn the user if we load an URL containing a userpass field
+ // unless its length is less than this threshold. this warning is
+ // intended to protect the user against spoofing attempts that use
+ // the userpass field of the URL to obscure the actual origin server.
+ uint8_t mPhishyUserPassLength{1};
+
+ uint8_t mQoSBits{0x00};
+
+ bool mEnforceAssocReq{false};
+
+ nsCString mImageAcceptHeader;
+ nsCString mDocumentAcceptHeader;
+
+ nsCString mAcceptLanguages;
+ nsCString mHttpAcceptEncodings;
+ nsCString mHttpsAcceptEncodings;
+
+ nsCString mDefaultSocketType;
+
+ // cache support
+ uint32_t mLastUniqueID;
+ Atomic<uint32_t, Relaxed> mSessionStartTime{0};
+
+ // useragent components
+ nsCString mLegacyAppName{"Mozilla"};
+ nsCString mLegacyAppVersion{"5.0"};
+ nsCString mPlatform;
+ nsCString mOscpu;
+ nsCString mMisc;
+ nsCString mProduct{"Gecko"};
+ nsCString mProductSub;
+ nsCString mAppName;
+ nsCString mAppVersion;
+ nsCString mCompatFirefox;
+ bool mCompatFirefoxEnabled{false};
+ nsCString mCompatDevice;
+ nsCString mDeviceModelId;
+
+ nsCString mUserAgent;
+ nsCString mSpoofedUserAgent;
+ nsCString mUserAgentOverride;
+ bool mUserAgentIsDirty{true}; // true if mUserAgent should be rebuilt
+ bool mAcceptLanguagesIsDirty{true};
+
+ bool mPromptTempRedirect{true};
+
+ // Persistent HTTPS caching flag
+ bool mEnablePersistentHttpsCaching{false};
+
+ // for broadcasting safe hint;
+ bool mSafeHintEnabled{false};
+ bool mParentalControlEnabled{false};
+
+ // true in between init and shutdown states
+ Atomic<bool, Relaxed> mHandlerActive{false};
+
+ // The value of 'hidden' network.http.debug-observations : 1;
+ uint32_t mDebugObservations : 1;
+
+ uint32_t mEnableAltSvc : 1;
+ uint32_t mEnableAltSvcOE : 1;
+ uint32_t mEnableOriginExtension : 1;
+
+ // Try to use SPDY features instead of HTTP/1.1 over SSL
+ SpdyInformation mSpdyInfo;
+
+ uint32_t mSpdySendingChunkSize{ASpdySession::kSendingChunkSize};
+ uint32_t mSpdySendBufferSize{ASpdySession::kTCPSendBufferSize};
+ uint32_t mSpdyPushAllowance{
+ ASpdySession::kInitialPushAllowance}; // match default pref
+ uint32_t mSpdyPullAllowance{ASpdySession::kInitialRwin};
+ uint32_t mDefaultSpdyConcurrent{ASpdySession::kDefaultMaxConcurrent};
+ PRIntervalTime mSpdyPingThreshold;
+ PRIntervalTime mSpdyPingTimeout;
+
+ // The maximum amount of time to wait for socket transport to be
+ // established. In milliseconds.
+ uint32_t mConnectTimeout{90000};
+
+ // The maximum amount of time to wait for a tls handshake to be
+ // established. In milliseconds.
+ uint32_t mTLSHandshakeTimeout{30000};
+
+ // The maximum number of current global half open sockets allowable
+ // when starting a new speculative connection.
+ uint32_t mParallelSpeculativeConnectLimit{6};
+
+ // For Rate Pacing of HTTP/1 requests through a netwerk/base/EventTokenBucket
+ // Active requests <= *MinParallelism are not subject to the rate pacing
+ bool mRequestTokenBucketEnabled{true};
+ uint16_t mRequestTokenBucketMinParallelism{6};
+ uint32_t mRequestTokenBucketHz{100}; // EventTokenBucket HZ
+ uint32_t mRequestTokenBucketBurst{32}; // EventTokenBucket Burst
+
+ // Whether or not to block requests for non head js/css items (e.g. media)
+ // while those elements load.
+ bool mCriticalRequestPrioritization{true};
+
+ // TCP Keepalive configuration values.
+
+ // True if TCP keepalive is enabled for short-lived conns.
+ bool mTCPKeepaliveShortLivedEnabled{false};
+ // Time (secs) indicating how long a conn is considered short-lived.
+ int32_t mTCPKeepaliveShortLivedTimeS{60};
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveShortLivedIdleTimeS{10};
+
+ // True if TCP keepalive is enabled for long-lived conns.
+ bool mTCPKeepaliveLongLivedEnabled{false};
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveLongLivedIdleTimeS{600};
+
+ // if true, generate NS_ERROR_PARTIAL_TRANSFER for h1 responses with
+ // incorrect content lengths or malformed chunked encodings
+ FrameCheckLevel mEnforceH1Framing{FRAMECHECK_BARELY};
+
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ // The default size (in bytes) of the HPACK decompressor table.
+ uint32_t mDefaultHpackBuffer{4096};
+
+ // Http3 parameters
+ Atomic<uint32_t, Relaxed> mQpackTableSize{4096};
+ // uint16_t is enough here, but Atomic only supports uint32_t or uint64_t.
+ Atomic<uint32_t, Relaxed> mHttp3MaxBlockedStreams{10};
+
+ nsCString mHttp3QlogDir;
+
+ // The max size (in bytes) for received Http response header.
+ uint32_t mMaxHttpResponseHeaderSize{393216};
+
+ // The ratio for dispatching transactions from the focused window.
+ float mFocusedWindowTransactionRatio{0.9f};
+
+ // If true, the transactions from active tab will be dispatched first.
+ bool mActiveTabPriority{true};
+
+ HttpTrafficAnalyzer mHttpTrafficAnalyzer;
+
+ private:
+ // For Rate Pacing Certain Network Events. Only assign this pointer on
+ // socket thread.
+ void MakeNewRequestTokenBucket();
+ RefPtr<EventTokenBucket> mRequestTokenBucket;
+
+ public:
+ // Socket thread only
+ [[nodiscard]] nsresult SubmitPacedRequest(ATokenBucketEvent* event,
+ nsICancelable** cancel) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mRequestTokenBucket) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mRequestTokenBucket->SubmitEvent(event, cancel);
+ }
+
+ // Socket thread only
+ void SetRequestTokenBucket(EventTokenBucket* aTokenBucket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mRequestTokenBucket = aTokenBucket;
+ }
+
+ void StopRequestTokenBucket() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mRequestTokenBucket) {
+ mRequestTokenBucket->Stop();
+ mRequestTokenBucket = nullptr;
+ }
+ }
+
+ private:
+ RefPtr<Tickler> mWifiTickler;
+ void TickleWifi(nsIInterfaceRequestor* cb);
+
+ private:
+ [[nodiscard]] nsresult SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool anonymous);
+ void ExcludeHttp2OrHttp3Internal(const nsHttpConnectionInfo* ci);
+
+ // State for generating channelIds
+ uint32_t mProcessId{0};
+ Atomic<uint32_t, Relaxed> mNextChannelId{1};
+
+ // The last time any of the active tab page load optimization took place.
+ // This is accessed on multiple threads, hence a lock is needed.
+ // On the parent process this is updated to now every time a scheduling
+ // or rate optimization related to the active/background tab is hit.
+ // We carry this value through each http channel's onstoprequest notification
+ // to the parent process. On the content process then we just update this
+ // value from ipc onstoprequest arguments. This is a sufficent way of passing
+ // it down to the content process, since the value will be used only after
+ // onstoprequest notification coming from an http channel.
+ Mutex mLastActiveTabLoadOptimizationLock{
+ "nsHttpConnectionMgr::LastActiveTabLoadOptimization"};
+ TimeStamp mLastActiveTabLoadOptimizationHit;
+
+ Mutex mHttpExclusionLock MOZ_UNANNOTATED{"nsHttpHandler::HttpExclusion"};
+
+ public:
+ [[nodiscard]] nsresult NewChannelId(uint64_t& channelId);
+ void AddHttpChannel(uint64_t aId, nsISupports* aChannel);
+ void RemoveHttpChannel(uint64_t aId);
+ nsWeakPtr GetWeakHttpChannel(uint64_t aId);
+
+ void ExcludeHttp2(const nsHttpConnectionInfo* ci);
+ [[nodiscard]] bool IsHttp2Excluded(const nsHttpConnectionInfo* ci);
+ void ExcludeHttp3(const nsHttpConnectionInfo* ci);
+ [[nodiscard]] bool IsHttp3Excluded(const nsACString& aRoutedHost);
+ void Exclude0RttTcp(const nsHttpConnectionInfo* ci);
+ [[nodiscard]] bool Is0RttTcpExcluded(const nsHttpConnectionInfo* ci);
+
+ void ExcludeHTTPSRRHost(const nsACString& aHost);
+ [[nodiscard]] bool IsHostExcludedForHTTPSRR(const nsACString& aHost);
+
+ private:
+ nsTHashSet<nsCString> mExcludedHttp2Origins;
+ nsTHashSet<nsCString> mExcludedHttp3Origins;
+ nsTHashSet<nsCString> mExcluded0RttTcpOrigins;
+ // A set of hosts that we should not upgrade to HTTPS with HTTPS RR.
+ nsTHashSet<nsCString> mExcludedHostsForHTTPSRRUpgrade;
+
+ Atomic<bool, Relaxed> mThroughCaptivePortal{false};
+
+ // The mapping of channel id and the weak pointer of nsHttpChannel.
+ nsTHashMap<nsUint64HashKey, nsWeakPtr> mIDToHttpChannelMap;
+
+ // This is parsed pref network.http.http3.alt-svc-mapping-for-testing.
+ // The pref set artificial altSvc-s for origin for testing.
+ // This maps an origin to an altSvc.
+ nsClassHashtable<nsCStringHashKey, nsCString> mAltSvcMappingTemptativeMap;
+
+ nsCOMPtr<nsIHttpActivityDistributor> mActivityDistributor;
+};
+
+extern StaticRefPtr<nsHttpHandler> gHttpHandler;
+
+//-----------------------------------------------------------------------------
+// nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
+// HTTPS handler (even though they share the same impl).
+//-----------------------------------------------------------------------------
+
+class nsHttpsHandler : public nsIHttpProtocolHandler,
+ public nsSupportsWeakReference,
+ public nsISpeculativeConnect {
+ virtual ~nsHttpsHandler() = default;
+
+ public:
+ // we basically just want to override GetScheme and GetDefaultPort...
+ // all other methods should be forwarded to the nsHttpHandler instance.
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_FORWARD_NSIPROXIEDPROTOCOLHANDLER(gHttpHandler->)
+ NS_FORWARD_NSIHTTPPROTOCOLHANDLER(gHttpHandler->)
+
+ NS_IMETHOD SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aAnonymous) override {
+ return gHttpHandler->SpeculativeConnect(aURI, aPrincipal, aCallbacks,
+ aAnonymous);
+ }
+
+ NS_IMETHOD SpeculativeConnectWithOriginAttributes(
+ nsIURI* aURI, JS::Handle<JS::Value> originAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous,
+ JSContext* cx) override {
+ return gHttpHandler->SpeculativeConnectWithOriginAttributes(
+ aURI, originAttributes, aCallbacks, aAnonymous, cx);
+ }
+
+ NS_IMETHOD_(void)
+ SpeculativeConnectWithOriginAttributesNative(
+ nsIURI* aURI, mozilla::OriginAttributes&& originAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) override {
+ gHttpHandler->SpeculativeConnectWithOriginAttributesNative(
+ aURI, std::move(originAttributes), aCallbacks, aAnonymous);
+ }
+
+ nsHttpsHandler() = default;
+
+ [[nodiscard]] nsresult Init();
+};
+
+//-----------------------------------------------------------------------------
+// HSTSDataCallbackWrapper - A threadsafe helper class to wrap the callback.
+//
+// We need this because dom::promise and EnsureHSTSDataResolver are not
+// threadsafe.
+//-----------------------------------------------------------------------------
+class HSTSDataCallbackWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HSTSDataCallbackWrapper)
+
+ explicit HSTSDataCallbackWrapper(std::function<void(bool)>&& aCallback)
+ : mCallback(std::move(aCallback)) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void DoCallback(bool aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mCallback(aResult);
+ }
+
+ private:
+ ~HSTSDataCallbackWrapper() = default;
+
+ std::function<void(bool)> mCallback;
+};
+
+} // namespace mozilla::net
+
+#endif // nsHttpHandler_h__
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp
new file mode 100644
index 0000000000..8da421e3fa
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -0,0 +1,475 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHeaderArray.h"
+#include "nsURLHelper.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpHeaderArray <public>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpHeaderArray::SetHeader(
+ const nsACString& headerName, const nsACString& value, bool merge,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ nsHttpAtom header = nsHttp::ResolveAtom(headerName);
+ if (!header) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SetHeader(header, headerName, value, merge, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeader(
+ const nsHttpAtom& header, const nsACString& value, bool merge,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ return SetHeader(header, ""_ns, value, merge, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeader(
+ const nsHttpAtom& header, const nsACString& headerName,
+ const nsACString& value, bool merge,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ MOZ_ASSERT(
+ (variety == eVarietyResponse) || (variety == eVarietyRequestDefault) ||
+ (variety == eVarietyRequestOverride) ||
+ (variety == eVarietyRequestEnforceDefault),
+ "Net original headers can only be set using SetHeader_internal().");
+
+ nsEntry* entry = nullptr;
+ int32_t index = LookupEntry(header, &entry);
+
+ // If an empty value is received and we aren't merging headers discard it
+ if (value.IsEmpty() && header != nsHttp::X_Frame_Options) {
+ if (!merge && entry) {
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ } else {
+ mHeaders.RemoveElementAt(index);
+ }
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT((variety == eVarietyRequestEnforceDefault) ||
+ (!entry || variety != eVarietyRequestDefault),
+ "Cannot set default entry which overrides existing entry!");
+
+ // Set the variety to default if we are enforcing it.
+ if (variety == eVarietyRequestEnforceDefault) {
+ variety = eVarietyRequestDefault;
+ }
+ if (!entry) {
+ return SetHeader_internal(header, headerName, value, variety);
+ }
+ if (merge && !IsSingletonHeader(header)) {
+ return MergeHeader(header, entry, value, variety);
+ }
+ if (!IsIgnoreMultipleHeader(header)) {
+ // Replace the existing string with the new value
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ return SetHeader_internal(header, headerName, value, variety);
+ }
+ entry->value = value;
+ entry->variety = variety;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::SetHeader_internal(
+ const nsHttpAtom& header, const nsACString& headerName,
+ const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
+ nsEntry* entry = mHeaders.AppendElement();
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ entry->header = header;
+ // Only save original form of a header if it is different than the header
+ // atom string.
+ if (!headerName.Equals(header.get())) {
+ entry->headerNameOriginal = headerName;
+ }
+ entry->value = value;
+ entry->variety = variety;
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::SetEmptyHeader(const nsACString& headerName,
+ HeaderVariety variety) {
+ nsHttpAtom header = nsHttp::ResolveAtom(headerName);
+ if (!header) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT((variety == eVarietyResponse) ||
+ (variety == eVarietyRequestDefault) ||
+ (variety == eVarietyRequestOverride),
+ "Original headers can only be set using SetHeader_internal().");
+ nsEntry* entry = nullptr;
+
+ LookupEntry(header, &entry);
+
+ if (entry && entry->variety != eVarietyResponseNetOriginalAndResponse) {
+ entry->value.Truncate();
+ return NS_OK;
+ }
+ if (entry) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ }
+
+ return SetHeader_internal(header, headerName, ""_ns, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeaderFromNet(
+ const nsHttpAtom& header, const nsACString& headerNameOriginal,
+ const nsACString& value, bool response) {
+ // mHeader holds the consolidated (merged or updated) headers.
+ // mHeader for response header will keep the original heades as well.
+ nsEntry* entry = nullptr;
+
+ LookupEntry(header, &entry);
+
+ if (!entry) {
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponseNetOriginalAndResponse;
+ }
+ return SetHeader_internal(header, headerNameOriginal, value, variety);
+ }
+ if (!IsSingletonHeader(header)) {
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponse;
+ }
+ nsresult rv = MergeHeader(header, entry, value, variety);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (response) {
+ rv = SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponseNetOriginal);
+ }
+ return rv;
+ }
+ if (!IsIgnoreMultipleHeader(header)) {
+ // Multiple instances of non-mergeable header received from network
+ // - ignore if same value
+ if (header == nsHttp::Content_Length) {
+ // Content length header needs special handling.
+ // For e.g. for CL all the below headers evaluates to value of X
+ // Content-Length: X
+ // Content-Length: X, X, X
+ // Content-Length: X \n\r Content-Length: X
+ // remove duplicate values from the header-values for comparison
+
+ nsAutoCString headerValue;
+ RemoveDuplicateHeaderValues(value, headerValue);
+
+ nsAutoCString entryValue;
+ RemoveDuplicateHeaderValues(entry->value, entryValue);
+ if (entryValue != headerValue) {
+ // reply may be corrupt/hacked (ex: CLRF injection attacks)
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ } else if (!entry->value.Equals(value)) { // compare remaining headers
+ if (IsSuspectDuplicateHeader(header)) {
+ // reply may be corrupt/hacked (ex: CLRF injection attacks)
+ return NS_ERROR_CORRUPTED_CONTENT;
+ } // else silently drop value: keep value from 1st header seen
+ LOG(("Header %s silently dropped as non mergeable header\n",
+ header.get()));
+ }
+
+ if (response) {
+ return SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponseNetOriginal);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::SetResponseHeaderFromCache(
+ const nsHttpAtom& header, const nsACString& headerNameOriginal,
+ const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
+ MOZ_ASSERT(
+ (variety == eVarietyResponse) || (variety == eVarietyResponseNetOriginal),
+ "Headers from cache can only be eVarietyResponse and "
+ "eVarietyResponseNetOriginal");
+
+ if (variety == eVarietyResponseNetOriginal) {
+ return SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponseNetOriginal);
+ }
+ nsTArray<nsEntry>::index_type index = 0;
+ do {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index !=
+ CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex) {
+ nsEntry& entry = mHeaders[index];
+ if (value.Equals(entry.value)) {
+ MOZ_ASSERT(
+ (entry.variety == eVarietyResponseNetOriginal) ||
+ (entry.variety == eVarietyResponseNetOriginalAndResponse),
+ "This array must contain only eVarietyResponseNetOriginal"
+ " and eVarietyResponseNetOriginalAndRespons headers!");
+ entry.variety = eVarietyResponseNetOriginalAndResponse;
+ return NS_OK;
+ }
+ index++;
+ }
+ } while (index !=
+ CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex);
+ // If we are here, we have not found an entry so add a new one.
+ return SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponse);
+}
+
+void nsHttpHeaderArray::ClearHeader(const nsHttpAtom& header) {
+ nsEntry* entry = nullptr;
+ int32_t index = LookupEntry(header, &entry);
+ if (entry) {
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ entry->variety = eVarietyResponseNetOriginal;
+ } else {
+ mHeaders.RemoveElementAt(index);
+ }
+ }
+}
+
+const char* nsHttpHeaderArray::PeekHeader(const nsHttpAtom& header) const {
+ const nsEntry* entry = nullptr;
+ LookupEntry(header, &entry);
+ return entry ? entry->value.get() : nullptr;
+}
+
+nsresult nsHttpHeaderArray::GetHeader(const nsHttpAtom& header,
+ nsACString& result) const {
+ const nsEntry* entry = nullptr;
+ LookupEntry(header, &entry);
+ if (!entry) return NS_ERROR_NOT_AVAILABLE;
+ result = entry->value;
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ NS_ENSURE_ARG_POINTER(aVisitor);
+ uint32_t index = 0;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ while (true) {
+ index = mHeaders.IndexOf(aHeader, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ const nsEntry& entry = mHeaders[index];
+
+ MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginalAndResponse) ||
+ (entry.variety == eVarietyResponseNetOriginal) ||
+ (entry.variety == eVarietyResponse),
+ "This must be a response header.");
+ index++;
+ if (entry.variety == eVarietyResponse) {
+ continue;
+ }
+
+ const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
+ ? entry.header.val()
+ : entry.headerNameOriginal;
+
+ rv = NS_OK;
+ if (NS_FAILED(aVisitor->VisitHeader(hdr, entry.value))) {
+ break;
+ }
+ } else {
+ // if there is no such a header, it will return
+ // NS_ERROR_NOT_AVAILABLE or NS_OK otherwise.
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+bool nsHttpHeaderArray::HasHeader(const nsHttpAtom& header) const {
+ const nsEntry* entry = nullptr;
+ LookupEntry(header, &entry);
+ return entry;
+}
+
+nsresult nsHttpHeaderArray::VisitHeaders(
+ nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
+ NS_ENSURE_ARG_POINTER(visitor);
+ nsresult rv;
+
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry& entry = mHeaders[i];
+ if (filter == eFilterSkipDefault &&
+ entry.variety == eVarietyRequestDefault) {
+ continue;
+ }
+ if (filter == eFilterResponse &&
+ entry.variety == eVarietyResponseNetOriginal) {
+ continue;
+ }
+ if (filter == eFilterResponseOriginal &&
+ entry.variety == eVarietyResponse) {
+ continue;
+ }
+
+ const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
+ ? entry.header.val()
+ : entry.headerNameOriginal;
+ rv = visitor->VisitHeader(hdr, entry.value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+/*static*/
+nsresult nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
+ nsHttpAtom* hdr,
+ nsACString* headerName,
+ nsACString* val) {
+ //
+ // BNF from section 4.2 of RFC 2616:
+ //
+ // message-header = field-name ":" [ field-value ]
+ // field-name = token
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ //
+
+ // We skip over mal-formed headers in the hope that we'll still be able to
+ // do something useful with the response.
+ int32_t split = line.FindChar(':');
+
+ if (split == kNotFound) {
+ LOG(("malformed header [%s]: no colon\n", PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsACString& sub = Substring(line, 0, split);
+ const nsACString& sub2 =
+ Substring(line, split + 1, line.Length() - split - 1);
+
+ // make sure we have a valid token for the field-name
+ if (!nsHttp::IsValidToken(sub)) {
+ LOG(("malformed header [%s]: field-name not a token\n",
+ PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(sub);
+ if (!atom) {
+ LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // skip over whitespace
+ char* p =
+ net_FindCharNotInSet(sub2.BeginReading(), sub2.EndReading(), HTTP_LWS);
+
+ // trim trailing whitespace - bug 86608
+ char* p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS);
+
+ // assign return values
+ if (hdr) *hdr = atom;
+ if (val) val->Assign(p, p2 - p + 1);
+ if (headerName) headerName->Assign(sub);
+
+ return NS_OK;
+}
+
+void nsHttpHeaderArray::Flatten(nsACString& buf, bool pruneProxyHeaders,
+ bool pruneTransients) {
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry& entry = mHeaders[i];
+ // Skip original header.
+ if (entry.variety == eVarietyResponseNetOriginal) {
+ continue;
+ }
+ // prune proxy headers if requested
+ if (pruneProxyHeaders && ((entry.header == nsHttp::Proxy_Authorization) ||
+ (entry.header == nsHttp::Proxy_Connection))) {
+ continue;
+ }
+ if (pruneTransients &&
+ (entry.value.IsEmpty() || entry.header == nsHttp::Connection ||
+ entry.header == nsHttp::Proxy_Connection ||
+ entry.header == nsHttp::Keep_Alive ||
+ entry.header == nsHttp::WWW_Authenticate ||
+ entry.header == nsHttp::Proxy_Authenticate ||
+ entry.header == nsHttp::Trailer ||
+ entry.header == nsHttp::Transfer_Encoding ||
+ entry.header == nsHttp::Upgrade ||
+ // XXX this will cause problems when we start honoring
+ // Cache-Control: no-cache="set-cookie", what to do?
+ entry.header == nsHttp::Set_Cookie)) {
+ continue;
+ }
+
+ if (entry.headerNameOriginal.IsEmpty()) {
+ buf.Append(entry.header.val());
+ } else {
+ buf.Append(entry.headerNameOriginal);
+ }
+ buf.AppendLiteral(": ");
+ buf.Append(entry.value);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+void nsHttpHeaderArray::FlattenOriginalHeader(nsACString& buf) {
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry& entry = mHeaders[i];
+ // Skip changed header.
+ if (entry.variety == eVarietyResponse) {
+ continue;
+ }
+
+ if (entry.headerNameOriginal.IsEmpty()) {
+ buf.Append(entry.header.val());
+ } else {
+ buf.Append(entry.headerNameOriginal);
+ }
+
+ buf.AppendLiteral(": ");
+ buf.Append(entry.value);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+const char* nsHttpHeaderArray::PeekHeaderAt(
+ uint32_t index, nsHttpAtom& header, nsACString& headerNameOriginal) const {
+ const nsEntry& entry = mHeaders[index];
+
+ header = entry.header;
+ headerNameOriginal = entry.headerNameOriginal;
+ return entry.value.get();
+}
+
+void nsHttpHeaderArray::Clear() { mHeaders.Clear(); }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.h b/netwerk/protocol/http/nsHttpHeaderArray.h
new file mode 100644
index 0000000000..e9aa0a8e7e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 4; 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 nsHttpHeaderArray_h__
+#define nsHttpHeaderArray_h__
+
+#include "nsHttp.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHeaderArray {
+ public:
+ const char* PeekHeader(const nsHttpAtom& header) const;
+
+ // For nsHttpResponseHead nsHttpHeaderArray will keep track of the original
+ // headers as they come from the network and the parse headers used in
+ // firefox.
+ // If the original and the firefox header are the same, we will keep just
+ // one copy and marked it as eVarietyResponseNetOriginalAndResponse.
+ // If firefox header representation changes a header coming from the
+ // network (e.g. merged it) or a eVarietyResponseNetOriginalAndResponse
+ // header has been changed by SetHeader method, we will keep the original
+ // header as eVarietyResponseNetOriginal and make a copy for the new header
+ // and mark it as eVarietyResponse.
+ enum HeaderVariety {
+ eVarietyUnknown,
+ // Used only for request header.
+ eVarietyRequestOverride,
+ eVarietyRequestDefault,
+ eVarietyRequestEnforceDefault,
+ // Used only for response header.
+ eVarietyResponseNetOriginalAndResponse,
+ eVarietyResponseNetOriginal,
+ eVarietyResponse
+ };
+
+ // Used by internal setters: to set header from network use SetHeaderFromNet
+ [[nodiscard]] nsresult SetHeader(const nsACString& headerName,
+ const nsACString& value, bool merge,
+ HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& header,
+ const nsACString& value, bool merge,
+ HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& header,
+ const nsACString& headerName,
+ const nsACString& value, bool merge,
+ HeaderVariety variety);
+
+ // Used by internal setters to set an empty header
+ [[nodiscard]] nsresult SetEmptyHeader(const nsACString& headerName,
+ HeaderVariety variety);
+
+ // Merges supported headers. For other duplicate values, determines if error
+ // needs to be thrown or 1st value kept.
+ // For the response header we keep the original headers as well.
+ [[nodiscard]] nsresult SetHeaderFromNet(const nsHttpAtom& header,
+ const nsACString& headerNameOriginal,
+ const nsACString& value,
+ bool response);
+
+ [[nodiscard]] nsresult SetResponseHeaderFromCache(
+ const nsHttpAtom& header, const nsACString& headerNameOriginal,
+ const nsACString& value, HeaderVariety variety);
+
+ [[nodiscard]] nsresult GetHeader(const nsHttpAtom& header,
+ nsACString& result) const;
+ [[nodiscard]] nsresult GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor);
+ void ClearHeader(const nsHttpAtom& h);
+
+ // Find the location of the given header value, or null if none exists.
+ const char* FindHeaderValue(const nsHttpAtom& header,
+ const char* value) const {
+ return nsHttp::FindToken(PeekHeader(header), value, HTTP_HEADER_VALUE_SEPS);
+ }
+
+ // Determine if the given header value exists.
+ bool HasHeaderValue(const nsHttpAtom& header, const char* value) const {
+ return FindHeaderValue(header, value) != nullptr;
+ }
+
+ bool HasHeader(const nsHttpAtom& header) const;
+
+ enum VisitorFilter {
+ eFilterAll,
+ eFilterSkipDefault,
+ eFilterResponse,
+ eFilterResponseOriginal
+ };
+
+ [[nodiscard]] nsresult VisitHeaders(nsIHttpHeaderVisitor* visitor,
+ VisitorFilter filter = eFilterAll);
+
+ // parse a header line, return the header atom, the header name, and the
+ // header value
+ [[nodiscard]] static nsresult ParseHeaderLine(
+ const nsACString& line, nsHttpAtom* hdr = nullptr,
+ nsACString* headerNameOriginal = nullptr, nsACString* value = nullptr);
+
+ void Flatten(nsACString&, bool pruneProxyHeaders, bool pruneTransients);
+ void FlattenOriginalHeader(nsACString&);
+
+ uint32_t Count() const { return mHeaders.Length(); }
+
+ const char* PeekHeaderAt(uint32_t i, nsHttpAtom& header,
+ nsACString& headerNameOriginal) const;
+
+ void Clear();
+
+ // Must be copy-constructable and assignable
+ struct nsEntry {
+ nsHttpAtom header;
+ nsCString headerNameOriginal;
+ nsCString value;
+ HeaderVariety variety = eVarietyUnknown;
+
+ struct MatchHeader {
+ bool Equals(const nsEntry& aEntry, const nsHttpAtom& aHeader) const {
+ return aEntry.header == aHeader;
+ }
+ };
+
+ bool operator==(const nsEntry& aOther) const {
+ return header == aOther.header && value == aOther.value;
+ }
+ };
+
+ bool operator==(const nsHttpHeaderArray& aOther) const {
+ return mHeaders == aOther.mHeaders;
+ }
+
+ private:
+ // LookupEntry function will never return eVarietyResponseNetOriginal.
+ // It will ignore original headers from the network.
+ int32_t LookupEntry(const nsHttpAtom& header, const nsEntry**) const;
+ int32_t LookupEntry(const nsHttpAtom& header, nsEntry**);
+ [[nodiscard]] nsresult MergeHeader(const nsHttpAtom& header, nsEntry* entry,
+ const nsACString& value,
+ HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader_internal(const nsHttpAtom& header,
+ const nsACString& headerName,
+ const nsACString& value,
+ HeaderVariety variety);
+
+ // Header cannot be merged: only one value possible
+ bool IsSingletonHeader(const nsHttpAtom& header);
+ // Header cannot be merged, and subsequent values should be ignored
+ bool IsIgnoreMultipleHeader(const nsHttpAtom& header);
+
+ // Subset of singleton headers: should never see multiple, different
+ // instances of these, else something fishy may be going on (like CLRF
+ // injection)
+ bool IsSuspectDuplicateHeader(const nsHttpAtom& header);
+
+ // Removes duplicate header values entries
+ // Will return unmodified header value if the header values contains
+ // non-duplicate entries
+ void RemoveDuplicateHeaderValues(const nsACString& aHeaderValue,
+ nsACString& aResult);
+
+ // All members must be copy-constructable and assignable
+ CopyableTArray<nsEntry> mHeaders;
+
+ friend struct IPC::ParamTraits<nsHttpHeaderArray>;
+ friend class nsHttpRequestHead;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpHeaderArray <private>: inline functions
+//-----------------------------------------------------------------------------
+
+inline int32_t nsHttpHeaderArray::LookupEntry(const nsHttpAtom& header,
+ const nsEntry** entry) const {
+ uint32_t index = 0;
+ while (index != UINT32_MAX) {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+ *entry = &mHeaders[index];
+ return index;
+ }
+ index++;
+ }
+ }
+
+ return index;
+}
+
+inline int32_t nsHttpHeaderArray::LookupEntry(const nsHttpAtom& header,
+ nsEntry** entry) {
+ uint32_t index = 0;
+ while (index != UINT32_MAX) {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+ *entry = &mHeaders[index];
+ return index;
+ }
+ index++;
+ }
+ }
+ return index;
+}
+
+inline bool nsHttpHeaderArray::IsSingletonHeader(const nsHttpAtom& header) {
+ return header == nsHttp::Content_Type ||
+ header == nsHttp::Content_Disposition ||
+ header == nsHttp::Content_Length || header == nsHttp::User_Agent ||
+ header == nsHttp::Referer || header == nsHttp::Host ||
+ header == nsHttp::Authorization ||
+ header == nsHttp::Proxy_Authorization ||
+ header == nsHttp::If_Modified_Since ||
+ header == nsHttp::If_Unmodified_Since || header == nsHttp::From ||
+ header == nsHttp::Location || header == nsHttp::Max_Forwards ||
+ header == nsHttp::GlobalPrivacyControl ||
+ // Ignore-multiple-headers are singletons in the sense that they
+ // shouldn't be merged.
+ IsIgnoreMultipleHeader(header);
+}
+
+// These are headers for which, in the presence of multiple values, we only
+// consider the first.
+inline bool nsHttpHeaderArray::IsIgnoreMultipleHeader(
+ const nsHttpAtom& header) {
+ // https://tools.ietf.org/html/rfc6797#section-8:
+ //
+ // If a UA receives more than one STS header field in an HTTP
+ // response message over secure transport, then the UA MUST process
+ // only the first such header field.
+ return header == nsHttp::Strict_Transport_Security;
+}
+
+[[nodiscard]] inline nsresult nsHttpHeaderArray::MergeHeader(
+ const nsHttpAtom& header, nsEntry* entry, const nsACString& value,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ // merge of empty header = no-op
+ if (value.IsEmpty() && header != nsHttp::X_Frame_Options) {
+ return NS_OK;
+ }
+
+ // x-frame-options having an empty header value still has an effect so we make
+ // sure that we retain encountering it
+ nsCString newValue = entry->value;
+ if (!newValue.IsEmpty() || header == nsHttp::X_Frame_Options) {
+ // Append the new value to the existing value
+ if (header == nsHttp::Set_Cookie || header == nsHttp::WWW_Authenticate ||
+ header == nsHttp::Proxy_Authenticate) {
+ // Special case these headers and use a newline delimiter to
+ // delimit the values from one another as commas may appear
+ // in the values of these headers contrary to what the spec says.
+ newValue.Append('\n');
+ } else {
+ // Delimit each value from the others using a comma (per HTTP spec)
+ newValue.AppendLiteral(", ");
+ }
+ }
+
+ newValue.Append(value);
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ // Copy entry->headerNameOriginal because in SetHeader_internal we are going
+ // to a new one and a realocation can happen.
+ nsCString headerNameOriginal = entry->headerNameOriginal;
+ nsresult rv = SetHeader_internal(header, headerNameOriginal, newValue,
+ eVarietyResponse);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ entry->value = newValue;
+ entry->variety = variety;
+ }
+ return NS_OK;
+}
+
+inline bool nsHttpHeaderArray::IsSuspectDuplicateHeader(
+ const nsHttpAtom& header) {
+ bool retval = header == nsHttp::Content_Length ||
+ header == nsHttp::Content_Disposition ||
+ header == nsHttp::Location;
+
+ MOZ_ASSERT(!retval || IsSingletonHeader(header),
+ "Only non-mergeable headers should be in this list\n");
+
+ return retval;
+}
+
+inline void nsHttpHeaderArray::RemoveDuplicateHeaderValues(
+ const nsACString& aHeaderValue, nsACString& aResult) {
+ mozilla::Maybe<nsAutoCString> result;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
+ if (result.isNothing()) {
+ // assign the first value
+ result.emplace(token);
+ continue;
+ }
+ if (*result != token) {
+ // non-identical header values. Do not change the header values
+ result.reset();
+ break;
+ }
+ }
+
+ if (result.isSome()) {
+ aResult = *result;
+ } else {
+ // either header values do not have multiple values or
+ // has unequal multiple values
+ // for both the cases restore the original header value
+ aResult = aHeaderValue;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.cpp b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
new file mode 100644
index 0000000000..0d8f5f083a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
@@ -0,0 +1,404 @@
+/* vim:set ts=4 sw=2 sts=2 et ci: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpNTLMAuth.h"
+#include "nsIAuthModule.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "plbase64.h"
+#include "prnetdb.h"
+
+//-----------------------------------------------------------------------------
+
+#include "nsIPrefBranch.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#ifdef XP_WIN
+# include "nsIChannel.h"
+# include "nsIX509Cert.h"
+# include "nsITransportSecurityInfo.h"
+#endif
+#include "mozilla/Attributes.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/net/HttpAuthUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/StaticPrefs_browser.h"
+
+namespace mozilla {
+namespace net {
+
+static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
+static const char kAllowNonFqdn[] =
+ "network.automatic-ntlm-auth.allow-non-fqdn";
+static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris";
+static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
+static const char kSSOinPBmode[] = "network.auth.private-browsing-sso";
+
+StaticRefPtr<nsHttpNTLMAuth> nsHttpNTLMAuth::gSingleton;
+
+static bool IsNonFqdn(nsIURI* uri) {
+ nsAutoCString host;
+ if (NS_FAILED(uri->GetAsciiHost(host))) {
+ return false;
+ }
+
+ // return true if host does not contain a dot and is not an ip address
+ return !host.IsEmpty() && !host.Contains('.') && !HostIsIPLiteral(host);
+}
+
+// Check to see if we should use our generic (internal) NTLM auth module.
+static bool ForceGenericNTLM() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) return false;
+ bool flag = false;
+
+ if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag))) flag = false;
+
+ LOG(("Force use of generic ntlm auth module: %d\n", flag));
+ return flag;
+}
+
+// Check to see if we should use default credentials for this host or proxy.
+static bool CanUseDefaultCredentials(nsIHttpAuthenticableChannel* channel,
+ bool isProxyAuth) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return false;
+ }
+
+ // Proxy should go all the time, it's not considered a privacy leak
+ // to send default credentials to a proxy.
+ if (isProxyAuth) {
+ bool val;
+ if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val))) val = false;
+ LOG(("Default credentials allowed for proxy: %d\n", val));
+ return val;
+ }
+
+ // Prevent using default credentials for authentication when we are in the
+ // private browsing mode (but not in "never remember history" mode) and when
+ // not explicitely allowed. Otherwise, it would cause a privacy data leak.
+ nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
+ MOZ_ASSERT(bareChannel);
+
+ if (NS_UsePrivateBrowsing(bareChannel)) {
+ bool ssoInPb;
+ if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) && ssoInPb) {
+ return true;
+ }
+
+ if (!StaticPrefs::browser_privatebrowsing_autostart()) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ Unused << channel->GetURI(getter_AddRefs(uri));
+
+ bool allowNonFqdn;
+ if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn))) {
+ allowNonFqdn = false;
+ }
+ if (allowNonFqdn && uri && IsNonFqdn(uri)) {
+ LOG(("Host is non-fqdn, default credentials are allowed\n"));
+ return true;
+ }
+
+ bool isTrustedHost = (uri && auth::URIMatchesPrefPattern(uri, kTrustedURIs));
+ LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
+ return isTrustedHost;
+}
+
+// Dummy class for session state object. This class doesn't hold any data.
+// Instead we use its existence as a flag. See ChallengeReceived.
+class nsNTLMSessionState final : public nsISupports {
+ ~nsNTLMSessionState() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+};
+NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
+
+//-----------------------------------------------------------------------------
+
+already_AddRefed<nsIHttpAuthenticator> nsHttpNTLMAuth::GetOrCreate() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (gSingleton) {
+ authenticator = gSingleton;
+ } else {
+ gSingleton = new nsHttpNTLMAuth();
+ ClearOnShutdown(&gSingleton);
+ authenticator = gSingleton;
+ }
+
+ return authenticator.forget();
+}
+
+NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator)
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel* channel,
+ const nsACString& challenge, bool isProxyAuth,
+ nsISupports** sessionState,
+ nsISupports** continuationState,
+ bool* identityInvalid) {
+ LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n", *sessionState,
+ *continuationState));
+
+ // Use the native NTLM if available
+ mUseNative = true;
+
+ // NOTE: we don't define any session state, but we do use the pointer.
+
+ *identityInvalid = false;
+
+ // Start a new auth sequence if the challenge is exactly "NTLM".
+ // If native NTLM auth apis are available and enabled through prefs,
+ // try to use them.
+ if (challenge.Equals("NTLM"_ns, nsCaseInsensitiveCStringComparator)) {
+ nsCOMPtr<nsIAuthModule> module;
+
+#ifdef MOZ_AUTH_EXTENSION
+ // Check to see if we should default to our generic NTLM auth module
+ // through UseGenericNTLM. (We use native auth by default if the
+ // system provides it.) If *sessionState is non-null, we failed to
+ // instantiate a native NTLM module the last time, so skip trying again.
+ bool forceGeneric = ForceGenericNTLM();
+ if (!forceGeneric && !*sessionState) {
+ // Check for approved default credentials hosts and proxies. If
+ // *continuationState is non-null, the last authentication attempt
+ // failed so skip default credential use.
+ if (!*continuationState &&
+ CanUseDefaultCredentials(channel, isProxyAuth)) {
+ // Try logging in with the user's default credentials. If
+ // successful, |identityInvalid| is false, which will trigger
+ // a default credentials attempt once we return.
+ module = nsIAuthModule::CreateInstance("sys-ntlm");
+ }
+# ifdef XP_WIN
+ else {
+ // Try to use native NTLM and prompt the user for their domain,
+ // username, and password. (only supported by windows nsAuthSSPI
+ // module.) Note, for servers that use LMv1 a weak hash of the user's
+ // password will be sent. We rely on windows internal apis to decide
+ // whether we should support this older, less secure version of the
+ // protocol.
+ module = nsIAuthModule::CreateInstance("sys-ntlm");
+ *identityInvalid = true;
+ }
+# endif // XP_WIN
+ if (!module) LOG(("Native sys-ntlm auth module not found.\n"));
+ }
+
+# ifdef XP_WIN
+ // On windows, never fall back unless the user has specifically requested
+ // so.
+ if (!forceGeneric && !module) return NS_ERROR_UNEXPECTED;
+# endif
+
+ // If no native support was available. Fall back on our internal NTLM
+ // implementation.
+ if (!module) {
+ if (!*sessionState) {
+ // Remember the fact that we cannot use the "sys-ntlm" module,
+ // so we don't ever bother trying again for this auth domain.
+ RefPtr<nsNTLMSessionState> state = new nsNTLMSessionState();
+ state.forget(sessionState);
+ }
+
+ // Use our internal NTLM implementation. Note, this is less secure,
+ // see bug 520607 for details.
+ LOG(("Trying to fall back on internal ntlm auth.\n"));
+ module = nsIAuthModule::CreateInstance("ntlm");
+
+ mUseNative = false;
+
+ // Prompt user for domain, username, and password.
+ *identityInvalid = true;
+ }
+#endif
+
+ // If this fails, then it means that we cannot do NTLM auth.
+ if (!module) {
+ LOG(("No ntlm auth modules available.\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // A non-null continuation state implies that we failed to authenticate.
+ // Blow away the old authentication state, and use the new one.
+ module.forget(continuationState);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* authChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& challenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& username,
+ const nsAString& password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& user,
+ const nsAString& pass, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, nsACString& creds)
+
+{
+ LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
+
+ creds.Truncate();
+ *aFlags = 0;
+
+ // if user or password is empty, ChallengeReceived returned
+ // identityInvalid = false, that means we are using default user
+ // credentials; see nsAuthSSPI::Init method for explanation of this
+ // condition
+ if (user.IsEmpty() || pass.IsEmpty()) *aFlags = USING_INTERNAL_IDENTITY;
+
+ nsresult rv;
+ nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ void *inBuf, *outBuf;
+ uint32_t inBufLen, outBufLen;
+ Maybe<nsTArray<uint8_t>> certArray;
+
+ // initial challenge
+ if (aChallenge.Equals("NTLM"_ns, nsCaseInsensitiveCStringComparator)) {
+ // NTLM service name format is 'HTTP@host' for both http and https
+ nsCOMPtr<nsIURI> uri;
+ rv = authChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString serviceName, host;
+ rv = uri->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+ serviceName.AppendLiteral("HTTP@");
+ serviceName.Append(host);
+ // initialize auth module
+ uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT;
+ if (isProxyAuth) reqFlags |= nsIAuthModule::REQ_PROXY_AUTH;
+
+ rv = module->Init(serviceName, reqFlags, domain, user, pass);
+ if (NS_FAILED(rv)) return rv;
+
+ inBufLen = 0;
+ inBuf = nullptr;
+// This update enables updated Windows machines (Win7 or patched previous
+// versions) and Linux machines running Samba (updated for Channel
+// Binding), to perform Channel Binding when authenticating using NTLMv2
+// and an outer secure channel.
+//
+// Currently only implemented for Windows, linux support will be landing in
+// a separate patch, update this #ifdef accordingly then.
+// Extended protection update is just for Linux and Windows machines.
+#if defined(XP_WIN) /* || defined (LINUX) */
+ // We should retrieve the server certificate and compute the CBT,
+ // but only when we are using the native NTLM implementation and
+ // not the internal one.
+ // It is a valid case not having the security info object. This
+ // occures when we connect an https site through an ntlm proxy.
+ // After the ssl tunnel has been created, we get here the second
+ // time and now generate the CBT from now valid security info.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ rv = channel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (NS_FAILED(rv)) return rv;
+
+ if (mUseNative && securityInfo) {
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = securityInfo->GetServerCert(getter_AddRefs(cert));
+ if (NS_FAILED(rv)) return rv;
+
+ if (cert) {
+ certArray.emplace();
+ rv = cert->GetRawDER(*certArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If there is a server certificate, we pass it along the
+ // first time we call GetNextToken().
+ inBufLen = certArray->Length();
+ inBuf = certArray->Elements();
+ }
+ }
+#endif
+ } else {
+ // decode challenge; skip past "NTLM " to the start of the base64
+ // encoded data.
+ if (aChallenge.Length() < 6) {
+ return NS_ERROR_UNEXPECTED; // bogus challenge
+ }
+
+ // strip off any padding (see bug 230351)
+ nsDependentCSubstring challenge(aChallenge, 5);
+ uint32_t len = challenge.Length();
+ while (len > 0 && challenge[len - 1] == '=') {
+ len--;
+ }
+
+ // decode into the input secbuffer
+ rv = Base64Decode(challenge.BeginReading(), len, (char**)&inBuf, &inBufLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
+ if (NS_SUCCEEDED(rv)) {
+ // base64 encode data in output buffer and prepend "NTLM "
+ CheckedUint32 credsLen = ((CheckedUint32(outBufLen) + 2) / 3) * 4;
+ credsLen += 5; // "NTLM "
+ credsLen += 1; // null terminate
+
+ if (!credsLen.isValid()) {
+ rv = NS_ERROR_FAILURE;
+ } else {
+ nsAutoCString encoded;
+ (void)Base64Encode(nsDependentCSubstring((char*)outBuf, outBufLen),
+ encoded);
+ creds = nsPrintfCString("NTLM %s", encoded.get());
+ }
+
+ // OK, we are done with |outBuf|
+ free(outBuf);
+ }
+
+ // inBuf needs to be freed if it's not pointing into certArray
+ if (inBuf && !certArray) {
+ free(inBuf);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GetAuthFlags(uint32_t* flags) {
+ *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.h b/netwerk/protocol/http/nsHttpNTLMAuth.h
new file mode 100644
index 0000000000..a1fafc4a10
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.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 nsHttpNTLMAuth_h__
+#define nsHttpNTLMAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpNTLMAuth : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpNTLMAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> GetOrCreate();
+
+ private:
+ virtual ~nsHttpNTLMAuth() = default;
+
+ // This flag indicates whether we are using the native NTLM implementation
+ // or the internal one.
+ bool mUseNative{false};
+
+ static StaticRefPtr<nsHttpNTLMAuth> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpNTLMAuth_h__
diff --git a/netwerk/protocol/http/nsHttpRequestHead.cpp b/netwerk/protocol/http/nsHttpRequestHead.cpp
new file mode 100644
index 0000000000..0ce7a14f8a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -0,0 +1,367 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpRequestHead.h"
+#include "nsIHttpHeaderVisitor.h"
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsHttpRequestHead::nsHttpRequestHead() { MOZ_COUNT_CTOR(nsHttpRequestHead); }
+
+nsHttpRequestHead::nsHttpRequestHead(const nsHttpRequestHead& aRequestHead) {
+ MOZ_COUNT_CTOR(nsHttpRequestHead);
+ nsHttpRequestHead& other = const_cast<nsHttpRequestHead&>(aRequestHead);
+ RecursiveMutexAutoLock monitor(other.mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mMethod = other.mMethod;
+ mVersion = other.mVersion;
+ mRequestURI = other.mRequestURI;
+ mPath = other.mPath;
+ mOrigin = other.mOrigin;
+ mParsedMethod = other.mParsedMethod;
+ mHTTPS = other.mHTTPS;
+ mInVisitHeaders = false;
+}
+
+nsHttpRequestHead::nsHttpRequestHead(nsHttpRequestHead&& aRequestHead) {
+ MOZ_COUNT_CTOR(nsHttpRequestHead);
+ nsHttpRequestHead& other = aRequestHead;
+ RecursiveMutexAutoLock monitor(other.mRecursiveMutex);
+
+ mHeaders = std::move(other.mHeaders);
+ mMethod = std::move(other.mMethod);
+ mVersion = std::move(other.mVersion);
+ mRequestURI = std::move(other.mRequestURI);
+ mPath = std::move(other.mPath);
+ mOrigin = std::move(other.mOrigin);
+ mParsedMethod = std::move(other.mParsedMethod);
+ mHTTPS = std::move(other.mHTTPS);
+ mInVisitHeaders = false;
+}
+
+nsHttpRequestHead::~nsHttpRequestHead() { MOZ_COUNT_DTOR(nsHttpRequestHead); }
+
+nsHttpRequestHead& nsHttpRequestHead::operator=(
+ const nsHttpRequestHead& aRequestHead) {
+ nsHttpRequestHead& other = const_cast<nsHttpRequestHead&>(aRequestHead);
+ RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mMethod = other.mMethod;
+ mVersion = other.mVersion;
+ mRequestURI = other.mRequestURI;
+ mPath = other.mPath;
+ mOrigin = other.mOrigin;
+ mParsedMethod = other.mParsedMethod;
+ mHTTPS = other.mHTTPS;
+ mInVisitHeaders = false;
+ return *this;
+}
+
+// Don't use this function. It is only used by HttpChannelParent to avoid
+// copying of request headers!!!
+const nsHttpHeaderArray& nsHttpRequestHead::Headers() const {
+ nsHttpRequestHead& curr = const_cast<nsHttpRequestHead&>(*this);
+ curr.mRecursiveMutex.AssertCurrentThreadIn();
+ return mHeaders;
+}
+
+void nsHttpRequestHead::SetHeaders(const nsHttpHeaderArray& aHeaders) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mHeaders = aHeaders;
+}
+
+void nsHttpRequestHead::SetVersion(HttpVersion version) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mVersion = version;
+}
+
+void nsHttpRequestHead::SetRequestURI(const nsACString& s) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mRequestURI = s;
+}
+
+void nsHttpRequestHead::SetPath(const nsACString& s) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mPath = s;
+}
+
+uint32_t nsHttpRequestHead::HeaderCount() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.Count();
+}
+
+nsresult nsHttpRequestHead::VisitHeaders(
+ nsIHttpHeaderVisitor* visitor,
+ nsHttpHeaderArray::VisitorFilter
+ filter /* = nsHttpHeaderArray::eFilterAll*/) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+void nsHttpRequestHead::Method(nsACString& aMethod) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aMethod = mMethod;
+}
+
+HttpVersion nsHttpRequestHead::Version() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mVersion;
+}
+
+void nsHttpRequestHead::RequestURI(nsACString& aRequestURI) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aRequestURI = mRequestURI;
+}
+
+void nsHttpRequestHead::Path(nsACString& aPath) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aPath = mPath.IsEmpty() ? mRequestURI : mPath;
+}
+
+void nsHttpRequestHead::SetHTTPS(bool val) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mHTTPS = val;
+}
+
+void nsHttpRequestHead::Origin(nsACString& aOrigin) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aOrigin = mOrigin;
+}
+
+nsresult nsHttpRequestHead::SetHeader(const nsACString& h, const nsACString& v,
+ bool m /*= false*/) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult nsHttpRequestHead::SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m /*= false*/) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult nsHttpRequestHead::SetHeader(
+ const nsHttpAtom& h, const nsACString& v, bool m,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m, variety);
+}
+
+nsresult nsHttpRequestHead::SetEmptyHeader(const nsACString& h) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetEmptyHeader(h, nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult nsHttpRequestHead::GetHeader(const nsHttpAtom& h, nsACString& v) {
+ v.Truncate();
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.GetHeader(h, v);
+}
+
+nsresult nsHttpRequestHead::ClearHeader(const nsHttpAtom& h) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mHeaders.ClearHeader(h);
+ return NS_OK;
+}
+
+void nsHttpRequestHead::ClearHeaders() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return;
+ }
+
+ mHeaders.Clear();
+}
+
+bool nsHttpRequestHead::HasHeader(const nsHttpAtom& h) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.HasHeader(h);
+}
+
+bool nsHttpRequestHead::HasHeaderValue(const nsHttpAtom& h, const char* v) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+nsresult nsHttpRequestHead::SetHeaderOnce(const nsHttpAtom& h, const char* v,
+ bool merge /*= false */) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!merge || !mHeaders.HasHeaderValue(h, v)) {
+ return mHeaders.SetHeader(h, nsDependentCString(v), merge,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ }
+ return NS_OK;
+}
+
+nsHttpRequestHead::ParsedMethodType nsHttpRequestHead::ParsedMethod() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mParsedMethod;
+}
+
+bool nsHttpRequestHead::EqualsMethod(ParsedMethodType aType) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mParsedMethod == aType;
+}
+
+void nsHttpRequestHead::ParseHeaderSet(const char* buffer) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+ while (buffer) {
+ const char* eof = strchr(buffer, '\r');
+ if (!eof) {
+ break;
+ }
+ if (NS_SUCCEEDED(nsHttpHeaderArray::ParseHeaderLine(
+ nsDependentCSubstring(buffer, eof - buffer), &hdr,
+ &headerNameOriginal, &val))) {
+ DebugOnly<nsresult> rv =
+ mHeaders.SetHeaderFromNet(hdr, headerNameOriginal, val, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ buffer = eof + 1;
+ if (*buffer == '\n') {
+ buffer++;
+ }
+ }
+}
+
+bool nsHttpRequestHead::IsHTTPS() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHTTPS;
+}
+
+void nsHttpRequestHead::SetMethod(const nsACString& method) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ mMethod = method;
+ ParseMethod(mMethod, mParsedMethod);
+}
+
+// static
+void nsHttpRequestHead::ParseMethod(const nsCString& aRawMethod,
+ ParsedMethodType& aParsedMethod) {
+ aParsedMethod = kMethod_Custom;
+ if (!strcmp(aRawMethod.get(), "GET")) {
+ aParsedMethod = kMethod_Get;
+ } else if (!strcmp(aRawMethod.get(), "POST")) {
+ aParsedMethod = kMethod_Post;
+ } else if (!strcmp(aRawMethod.get(), "OPTIONS")) {
+ aParsedMethod = kMethod_Options;
+ } else if (!strcmp(aRawMethod.get(), "CONNECT")) {
+ aParsedMethod = kMethod_Connect;
+ } else if (!strcmp(aRawMethod.get(), "HEAD")) {
+ aParsedMethod = kMethod_Head;
+ } else if (!strcmp(aRawMethod.get(), "PUT")) {
+ aParsedMethod = kMethod_Put;
+ } else if (!strcmp(aRawMethod.get(), "TRACE")) {
+ aParsedMethod = kMethod_Trace;
+ }
+}
+
+void nsHttpRequestHead::SetOrigin(const nsACString& scheme,
+ const nsACString& host, int32_t port) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mOrigin.Assign(scheme);
+ mOrigin.AppendLiteral("://");
+ mOrigin.Append(host);
+ if (port >= 0) {
+ mOrigin.AppendLiteral(":");
+ mOrigin.AppendInt(port);
+ }
+}
+
+bool nsHttpRequestHead::IsSafeMethod() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ // This code will need to be extended for new safe methods, otherwise
+ // they'll default to "not safe".
+ if ((mParsedMethod == kMethod_Get) || (mParsedMethod == kMethod_Head) ||
+ (mParsedMethod == kMethod_Options) || (mParsedMethod == kMethod_Trace)) {
+ return true;
+ }
+
+ if (mParsedMethod != kMethod_Custom) {
+ return false;
+ }
+
+ return (!strcmp(mMethod.get(), "PROPFIND") ||
+ !strcmp(mMethod.get(), "REPORT") || !strcmp(mMethod.get(), "SEARCH"));
+}
+
+void nsHttpRequestHead::Flatten(nsACString& buf, bool pruneProxyHeaders) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ // note: the first append is intentional.
+
+ buf.Append(mMethod);
+ buf.Append(' ');
+ buf.Append(mRequestURI);
+ buf.AppendLiteral(" HTTP/");
+
+ switch (mVersion) {
+ case HttpVersion::v1_1:
+ buf.AppendLiteral("1.1");
+ break;
+ case HttpVersion::v0_9:
+ buf.AppendLiteral("0.9");
+ break;
+ default:
+ buf.AppendLiteral("1.0");
+ }
+
+ buf.AppendLiteral("\r\n");
+
+ mHeaders.Flatten(buf, pruneProxyHeaders, false);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpRequestHead.h b/netwerk/protocol/http/nsHttpRequestHead.h
new file mode 100644
index 0000000000..e308790bd4
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -0,0 +1,155 @@
+/* -*- 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 nsHttpRequestHead_h__
+#define nsHttpRequestHead_h__
+
+#include "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsString.h"
+#include "mozilla/RecursiveMutex.h"
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead represents the request line and headers from an HTTP
+// request.
+//-----------------------------------------------------------------------------
+
+class nsHttpRequestHead {
+ public:
+ nsHttpRequestHead();
+ explicit nsHttpRequestHead(const nsHttpRequestHead& aRequestHead);
+ nsHttpRequestHead(nsHttpRequestHead&& aRequestHead);
+ ~nsHttpRequestHead();
+
+ nsHttpRequestHead& operator=(const nsHttpRequestHead& aRequestHead);
+
+ // The following function is only used in HttpChannelParent to avoid
+ // copying headers. If you use it be careful to do it only under
+ // nsHttpRequestHead lock!!!
+ const nsHttpHeaderArray& Headers() const MOZ_REQUIRES(mRecursiveMutex);
+ void Enter() const MOZ_CAPABILITY_ACQUIRE(mRecursiveMutex) {
+ mRecursiveMutex.Lock();
+ }
+ void Exit() const MOZ_CAPABILITY_RELEASE(mRecursiveMutex) {
+ mRecursiveMutex.Unlock();
+ }
+
+ void SetHeaders(const nsHttpHeaderArray& aHeaders);
+
+ void SetMethod(const nsACString& method);
+ void SetVersion(HttpVersion version);
+ void SetRequestURI(const nsACString& s);
+ void SetPath(const nsACString& s);
+ uint32_t HeaderCount();
+
+ // Using this function it is possible to itereate through all headers
+ // automatically under one lock.
+ [[nodiscard]] nsresult VisitHeaders(
+ nsIHttpHeaderVisitor* visitor,
+ nsHttpHeaderArray::VisitorFilter filter = nsHttpHeaderArray::eFilterAll);
+ void Method(nsACString& aMethod);
+ HttpVersion Version();
+ void RequestURI(nsACString& RequestURI);
+ void Path(nsACString& aPath);
+ void SetHTTPS(bool val);
+ bool IsHTTPS();
+
+ void SetOrigin(const nsACString& scheme, const nsACString& host,
+ int32_t port);
+ void Origin(nsACString& aOrigin);
+
+ [[nodiscard]] nsresult SetHeader(const nsACString& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m,
+ nsHttpHeaderArray::HeaderVariety variety);
+ [[nodiscard]] nsresult SetEmptyHeader(const nsACString& h);
+ [[nodiscard]] nsresult GetHeader(const nsHttpAtom& h, nsACString& v);
+
+ [[nodiscard]] nsresult ClearHeader(const nsHttpAtom& h);
+ void ClearHeaders();
+
+ bool HasHeaderValue(const nsHttpAtom& h, const char* v);
+ // This function returns true if header is set even if it is an empty
+ // header.
+ bool HasHeader(const nsHttpAtom& h);
+ void Flatten(nsACString&, bool pruneProxyHeaders = false);
+
+ // Don't allow duplicate values
+ [[nodiscard]] nsresult SetHeaderOnce(const nsHttpAtom& h, const char* v,
+ bool merge = false);
+
+ bool IsSafeMethod();
+
+ enum ParsedMethodType {
+ kMethod_Custom,
+ kMethod_Get,
+ kMethod_Post,
+ kMethod_Options,
+ kMethod_Connect,
+ kMethod_Head,
+ kMethod_Put,
+ kMethod_Trace
+ };
+
+ static void ParseMethod(const nsCString& aRawMethod,
+ ParsedMethodType& aParsedMethod);
+
+ ParsedMethodType ParsedMethod();
+ bool EqualsMethod(ParsedMethodType aType);
+ bool IsGet() { return EqualsMethod(kMethod_Get); }
+ bool IsPost() { return EqualsMethod(kMethod_Post); }
+ bool IsOptions() { return EqualsMethod(kMethod_Options); }
+ bool IsConnect() { return EqualsMethod(kMethod_Connect); }
+ bool IsHead() { return EqualsMethod(kMethod_Head); }
+ bool IsPut() { return EqualsMethod(kMethod_Put); }
+ bool IsTrace() { return EqualsMethod(kMethod_Trace); }
+ void ParseHeaderSet(const char* buffer);
+
+ private:
+ // All members must be copy-constructable and assignable
+ nsHttpHeaderArray mHeaders MOZ_GUARDED_BY(mRecursiveMutex);
+ nsCString mMethod MOZ_GUARDED_BY(mRecursiveMutex){"GET"_ns};
+ HttpVersion mVersion MOZ_GUARDED_BY(mRecursiveMutex){HttpVersion::v1_1};
+
+ // mRequestURI and mPath are strings instead of an nsIURI
+ // because this is used off the main thread
+ // TODO: nsIURI is thread-safe now, should be fixable.
+ nsCString mRequestURI MOZ_GUARDED_BY(mRecursiveMutex);
+ nsCString mPath MOZ_GUARDED_BY(mRecursiveMutex);
+
+ nsCString mOrigin MOZ_GUARDED_BY(mRecursiveMutex);
+ ParsedMethodType mParsedMethod MOZ_GUARDED_BY(mRecursiveMutex){kMethod_Get};
+ bool mHTTPS MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ // We are using RecursiveMutex instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ mutable RecursiveMutex mRecursiveMutex MOZ_UNANNOTATED{
+ "nsHttpRequestHead.mRecursiveMutex"};
+
+ // During VisitHeader we sould not allow call to SetHeader.
+ bool mInVisitHeaders MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ friend struct IPC::ParamTraits<nsHttpRequestHead>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpRequestHead_h__
diff --git a/netwerk/protocol/http/nsHttpResponseHead.cpp b/netwerk/protocol/http/nsHttpResponseHead.cpp
new file mode 100644
index 0000000000..18f7beed3a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -0,0 +1,1220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/dom/MimeType.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Unused.h"
+#include "nsHttpResponseHead.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsPrintfCString.h"
+#include "prtime.h"
+#include "nsCRT.h"
+#include "nsURLHelper.h"
+#include "CacheControlParser.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead <public>
+//-----------------------------------------------------------------------------
+
+// Note that the code below MUST be synchronized with the IPC
+// serialization/deserialization in PHttpChannelParams.h.
+nsHttpResponseHead::nsHttpResponseHead(const nsHttpResponseHead& aOther) {
+ nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
+ RecursiveMutexAutoLock monitor(other.mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mHasCacheControl = other.mHasCacheControl;
+ mCacheControlPublic = other.mCacheControlPublic;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mCacheControlStaleWhileRevalidateSet =
+ other.mCacheControlStaleWhileRevalidateSet;
+ mCacheControlStaleWhileRevalidate = other.mCacheControlStaleWhileRevalidate;
+ mCacheControlMaxAgeSet = other.mCacheControlMaxAgeSet;
+ mCacheControlMaxAge = other.mCacheControlMaxAge;
+ mPragmaNoCache = other.mPragmaNoCache;
+}
+
+nsHttpResponseHead& nsHttpResponseHead::operator=(
+ const nsHttpResponseHead& aOther) {
+ nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
+ RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mHasCacheControl = other.mHasCacheControl;
+ mCacheControlPublic = other.mCacheControlPublic;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mCacheControlStaleWhileRevalidateSet =
+ other.mCacheControlStaleWhileRevalidateSet;
+ mCacheControlStaleWhileRevalidate = other.mCacheControlStaleWhileRevalidate;
+ mCacheControlMaxAgeSet = other.mCacheControlMaxAgeSet;
+ mCacheControlMaxAge = other.mCacheControlMaxAge;
+ mPragmaNoCache = other.mPragmaNoCache;
+
+ return *this;
+}
+
+HttpVersion nsHttpResponseHead::Version() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mVersion;
+}
+
+uint16_t nsHttpResponseHead::Status() const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mStatus;
+}
+
+void nsHttpResponseHead::StatusText(nsACString& aStatusText) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ aStatusText = mStatusText;
+}
+
+int64_t nsHttpResponseHead::ContentLength() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mContentLength;
+}
+
+void nsHttpResponseHead::ContentType(nsACString& aContentType) const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ aContentType = mContentType;
+}
+
+void nsHttpResponseHead::ContentCharset(nsACString& aContentCharset) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ aContentCharset = mContentCharset;
+}
+
+bool nsHttpResponseHead::Public() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlPublic;
+}
+
+bool nsHttpResponseHead::Private() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlPrivate;
+}
+
+bool nsHttpResponseHead::NoStore() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlNoStore;
+}
+
+bool nsHttpResponseHead::NoCache() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return NoCache_locked();
+}
+
+bool nsHttpResponseHead::Immutable() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlImmutable;
+}
+
+nsresult nsHttpResponseHead::SetHeader(const nsACString& hdr,
+ const nsACString& val, bool merge) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(hdr);
+ if (!atom.get()) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return SetHeader_locked(atom, hdr, val, merge);
+}
+
+nsresult nsHttpResponseHead::SetHeader(const nsHttpAtom& hdr,
+ const nsACString& val, bool merge) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return SetHeader_locked(hdr, ""_ns, val, merge);
+}
+
+nsresult nsHttpResponseHead::SetHeader_locked(const nsHttpAtom& atom,
+ const nsACString& hdr,
+ const nsACString& val,
+ bool merge) {
+ nsresult rv = mHeaders.SetHeader(atom, hdr, val, merge,
+ nsHttpHeaderArray::eVarietyResponse);
+ if (NS_FAILED(rv)) return rv;
+
+ // respond to changes in these headers. we need to reparse the entire
+ // header since the change may have merged in additional values.
+ if (atom == nsHttp::Cache_Control) {
+ ParseCacheControl(mHeaders.PeekHeader(atom));
+ } else if (atom == nsHttp::Pragma) {
+ ParsePragma(mHeaders.PeekHeader(atom));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetHeader(const nsHttpAtom& h,
+ nsACString& v) const {
+ v.Truncate();
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.GetHeader(h, v);
+}
+
+void nsHttpResponseHead::ClearHeader(const nsHttpAtom& h) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mHeaders.ClearHeader(h);
+}
+
+void nsHttpResponseHead::ClearHeaders() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mHeaders.Clear();
+}
+
+bool nsHttpResponseHead::HasHeaderValue(const nsHttpAtom& h, const char* v) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+bool nsHttpResponseHead::HasHeader(const nsHttpAtom& h) const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.HasHeader(h);
+}
+
+void nsHttpResponseHead::SetContentType(const nsACString& s) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mContentType = s;
+}
+
+void nsHttpResponseHead::SetContentCharset(const nsACString& s) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mContentCharset = s;
+}
+
+void nsHttpResponseHead::SetContentLength(int64_t len) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mContentLength = len;
+ if (len < 0) {
+ mHeaders.ClearHeader(nsHttp::Content_Length);
+ } else {
+ DebugOnly<nsresult> rv = mHeaders.SetHeader(
+ nsHttp::Content_Length, nsPrintfCString("%" PRId64, len), false,
+ nsHttpHeaderArray::eVarietyResponse);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void nsHttpResponseHead::Flatten(nsACString& buf, bool pruneTransients) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ if (mVersion == HttpVersion::v0_9) return;
+
+ buf.AppendLiteral("HTTP/");
+ if (mVersion == HttpVersion::v3_0) {
+ buf.AppendLiteral("3 ");
+ } else if (mVersion == HttpVersion::v2_0) {
+ buf.AppendLiteral("2 ");
+ } else if (mVersion == HttpVersion::v1_1) {
+ buf.AppendLiteral("1.1 ");
+ } else {
+ buf.AppendLiteral("1.0 ");
+ }
+
+ buf.Append(nsPrintfCString("%u", unsigned(mStatus)) + " "_ns + mStatusText +
+ "\r\n"_ns);
+
+ mHeaders.Flatten(buf, false, pruneTransients);
+}
+
+void nsHttpResponseHead::FlattenNetworkOriginalHeaders(nsACString& buf) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ if (mVersion == HttpVersion::v0_9) {
+ return;
+ }
+
+ mHeaders.FlattenOriginalHeader(buf);
+}
+
+nsresult nsHttpResponseHead::ParseCachedHead(const char* block) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ LOG(("nsHttpResponseHead::ParseCachedHead [this=%p]\n", this));
+
+ // this command works on a buffer as prepared by Flatten, as such it is
+ // not very forgiving ;-)
+
+ const char* p = strstr(block, "\r\n");
+ if (!p) return NS_ERROR_UNEXPECTED;
+
+ ParseStatusLine_locked(nsDependentCSubstring(block, p - block));
+
+ do {
+ block = p + 2;
+
+ if (*block == 0) break;
+
+ p = strstr(block, "\r\n");
+ if (!p) return NS_ERROR_UNEXPECTED;
+
+ Unused << ParseHeaderLine_locked(nsDependentCSubstring(block, p - block),
+ false);
+
+ } while (true);
+
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::ParseCachedOriginalHeaders(char* block) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ LOG(("nsHttpResponseHead::ParseCachedOriginalHeader [this=%p]\n", this));
+
+ // this command works on a buffer as prepared by FlattenOriginalHeader,
+ // as such it is not very forgiving ;-)
+
+ if (!block) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ char* p = block;
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+ nsresult rv;
+
+ do {
+ block = p;
+
+ if (*block == 0) break;
+
+ p = strstr(block, "\r\n");
+ if (!p) return NS_ERROR_UNEXPECTED;
+
+ *p = 0;
+ if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
+ nsDependentCString(block, p - block), &hdr, &headerNameOriginal,
+ &val))) {
+ return NS_OK;
+ }
+
+ rv = mHeaders.SetResponseHeaderFromCache(
+ hdr, headerNameOriginal, val,
+ nsHttpHeaderArray::eVarietyResponseNetOriginal);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ p = p + 2;
+ } while (true);
+
+ return NS_OK;
+}
+
+void nsHttpResponseHead::AssignDefaultStatusText() {
+ LOG(("response status line needs default reason phrase\n"));
+
+ // if a http response doesn't contain a reason phrase, put one in based
+ // on the status code. The reason phrase is totally meaningless so its
+ // ok to have a default catch all here - but this makes debuggers and addons
+ // a little saner to use if we don't map things to "404 OK" or other nonsense.
+ // In particular, HTTP/2 does not use reason phrases at all so they need to
+ // always be injected.
+
+ net_GetDefaultStatusTextForCode(mStatus, mStatusText);
+}
+
+nsresult nsHttpResponseHead::ParseStatusLine(const nsACString& line) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ParseStatusLine_locked(line);
+}
+
+nsresult nsHttpResponseHead::ParseStatusLine_locked(const nsACString& line) {
+ //
+ // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+ //
+
+ const char* start = line.BeginReading();
+ const char* end = line.EndReading();
+
+ // HTTP-Version
+ ParseVersion(start);
+
+ int32_t index = line.FindChar(' ');
+
+ if ((mVersion == HttpVersion::v0_9) || (index == -1)) {
+ mStatus = 200;
+ AssignDefaultStatusText();
+ } else if (StaticPrefs::network_http_strict_response_status_line_parsing()) {
+ // Status-Code: all ASCII digits after any whitespace
+ const char* p = start + index + 1;
+ while (p < end && NS_IsHTTPWhitespace(*p)) ++p;
+ if (p == end || !mozilla::IsAsciiDigit(*p)) {
+ return NS_ERROR_PARSING_HTTP_STATUS_LINE;
+ }
+ const char* codeStart = p;
+ while (p < end && mozilla::IsAsciiDigit(*p)) ++p;
+
+ // Only accept 3 digits (including all leading zeros)
+ // Also if next char isn't whitespace, fail (ie, code is 0x2)
+ if (p - codeStart > 3 || (p < end && !NS_IsHTTPWhitespace(*p))) {
+ return NS_ERROR_PARSING_HTTP_STATUS_LINE;
+ }
+
+ // At this point the code is between 0 and 999 inclusive
+ nsDependentCSubstring strCode(codeStart, p - codeStart);
+ nsresult rv;
+ mStatus = strCode.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_PARSING_HTTP_STATUS_LINE;
+ }
+
+ // Reason-Phrase: whatever remains after any whitespace (even empty)
+ while (p < end && NS_IsHTTPWhitespace(*p)) ++p;
+ if (p != end) {
+ mStatusText = nsDependentCSubstring(p, end - p);
+ }
+ } else {
+ // Status-Code
+ const char* p = start + index + 1;
+ mStatus = (uint16_t)atoi(p);
+ if (mStatus == 0) {
+ LOG(("mal-formed response status; assuming status = 200\n"));
+ mStatus = 200;
+ }
+
+ // Reason-Phrase is whatever is remaining of the line
+ index = line.FindChar(' ', p - start);
+ if (index == -1) {
+ AssignDefaultStatusText();
+ } else {
+ p = start + index + 1;
+ mStatusText = nsDependentCSubstring(p, end - p);
+ }
+ }
+
+ LOG1(("Have status line [version=%u status=%u statusText=%s]\n",
+ unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::ParseHeaderLine(const nsACString& line) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ParseHeaderLine_locked(line, true);
+}
+
+nsresult nsHttpResponseHead::ParseHeaderLine_locked(
+ const nsACString& line, bool originalFromNetHeaders) {
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+
+ if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
+ line, &hdr, &headerNameOriginal, &val))) {
+ return NS_OK;
+ }
+
+ // reject the header if there are 0x00 bytes in the value.
+ // (see https://github.com/httpwg/http-core/issues/215 for details).
+ if (StaticPrefs::network_http_reject_NULs_in_response_header_values() &&
+ val.FindChar('\0') >= 0) {
+ return NS_ERROR_DOM_INVALID_HEADER_VALUE;
+ }
+
+ nsresult rv;
+ if (originalFromNetHeaders) {
+ rv = mHeaders.SetHeaderFromNet(hdr, headerNameOriginal, val, true);
+ } else {
+ rv = mHeaders.SetResponseHeaderFromCache(
+ hdr, headerNameOriginal, val, nsHttpHeaderArray::eVarietyResponse);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // leading and trailing LWS has been removed from |val|
+
+ // handle some special case headers...
+ if (hdr == nsHttp::Content_Length) {
+ rv = ParseResponseContentLength(val);
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ LOG(("illegal content-length! %s\n", val.get()));
+ return rv;
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(("content-length value ignored! %s\n", val.get()));
+ }
+
+ } else if (hdr == nsHttp::Content_Type) {
+ if (StaticPrefs::network_standard_content_type_parsing_response_headers() &&
+ CMimeType::Parse(val, mContentType, mContentCharset)) {
+ } else {
+ bool dummy;
+ net_ParseContentType(val, mContentType, mContentCharset, &dummy);
+ }
+ LOG(("ParseContentType [input=%s, type=%s, charset=%s]\n", val.get(),
+ mContentType.get(), mContentCharset.get()));
+ } else if (hdr == nsHttp::Cache_Control) {
+ ParseCacheControl(val.get());
+ } else if (hdr == nsHttp::Pragma) {
+ ParsePragma(val.get());
+ }
+ return NS_OK;
+}
+
+// From section 13.2.3 of RFC2616, we compute the current age of a cached
+// response as follows:
+//
+// currentAge = max(max(0, responseTime - dateValue), ageValue)
+// + now - requestTime
+//
+// where responseTime == now
+//
+// This is typically a very small number.
+//
+nsresult nsHttpResponseHead::ComputeCurrentAge(uint32_t now,
+ uint32_t requestTime,
+ uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ uint32_t dateValue;
+ uint32_t ageValue;
+
+ *result = 0;
+
+ if (requestTime > now) {
+ // for calculation purposes lets not allow the request to happen in the
+ // future
+ requestTime = now;
+ }
+
+ if (NS_FAILED(GetDateValue_locked(&dateValue))) {
+ LOG(
+ ("nsHttpResponseHead::ComputeCurrentAge [this=%p] "
+ "Date response header not set!\n",
+ this));
+ // Assume we have a fast connection and that our clock
+ // is in sync with the server.
+ dateValue = now;
+ }
+
+ // Compute apparent age
+ if (now > dateValue) *result = now - dateValue;
+
+ // Compute corrected received age
+ if (NS_SUCCEEDED(GetAgeValue_locked(&ageValue))) {
+ *result = std::max(*result, ageValue);
+ }
+
+ // Compute current age
+ *result += (now - requestTime);
+ return NS_OK;
+}
+
+// From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
+// response as follows:
+//
+// freshnessLifetime = max_age_value
+// <or>
+// freshnessLifetime = expires_value - date_value
+// <or>
+// freshnessLifetime = min(one-week,
+// (date_value - last_modified_value) * 0.10)
+// <or>
+// freshnessLifetime = 0
+//
+nsresult nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ *result = 0;
+
+ // Try HTTP/1.1 style max-age directive...
+ if (NS_SUCCEEDED(GetMaxAgeValue_locked(result))) return NS_OK;
+
+ *result = 0;
+
+ uint32_t date = 0, date2 = 0;
+ if (NS_FAILED(GetDateValue_locked(&date))) {
+ date = NowInSeconds(); // synthesize a date header if none exists
+ }
+
+ // Try HTTP/1.0 style expires header...
+ if (NS_SUCCEEDED(GetExpiresValue_locked(&date2))) {
+ if (date2 > date) *result = date2 - date;
+ // the Expires header can specify a date in the past.
+ return NS_OK;
+ }
+
+ // These responses can be cached indefinitely.
+ if ((mStatus == 300) || (mStatus == 410) ||
+ nsHttp::IsPermanentRedirect(mStatus)) {
+ LOG(
+ ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Assign an infinite heuristic lifetime\n",
+ this));
+ *result = uint32_t(-1);
+ return NS_OK;
+ }
+
+ if (mStatus >= 400) {
+ LOG(
+ ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Do not calculate heuristic max-age for most responses >= 400\n",
+ this));
+ return NS_OK;
+ }
+
+ // From RFC 7234 Section 4.2.2, heuristics can only be used on responses
+ // without explicit freshness whose status codes are defined as cacheable
+ // by default, and those responses without explicit freshness that have been
+ // marked as explicitly cacheable.
+ // Note that |MustValidate| handled most of non-cacheable status codes.
+ if ((mStatus == 302 || mStatus == 304 || mStatus == 307) &&
+ !mCacheControlPublic && !mCacheControlPrivate) {
+ LOG((
+ "nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Do not calculate heuristic max-age for non-cacheable status code %u\n",
+ this, unsigned(mStatus)));
+ return NS_OK;
+ }
+
+ // Fallback on heuristic using last modified header...
+ if (NS_SUCCEEDED(GetLastModifiedValue_locked(&date2))) {
+ LOG(("using last-modified to determine freshness-lifetime\n"));
+ LOG(("last-modified = %u, date = %u\n", date2, date));
+ if (date2 <= date) {
+ // this only makes sense if last-modified is actually in the past
+ *result = (date - date2) / 10;
+ const uint32_t kOneWeek = 60 * 60 * 24 * 7;
+ *result = std::min(kOneWeek, *result);
+ return NS_OK;
+ }
+ }
+
+ LOG(
+ ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Insufficient information to compute a non-zero freshness "
+ "lifetime!\n",
+ this));
+
+ return NS_OK;
+}
+
+bool nsHttpResponseHead::MustValidate() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ LOG(("nsHttpResponseHead::MustValidate ??\n"));
+
+ // Some response codes are cacheable, but the rest are not. This switch should
+ // stay in sync with the list in nsHttpChannel::ContinueProcessResponse3
+ switch (mStatus) {
+ // Success codes
+ case 200:
+ case 203:
+ case 204:
+ case 206:
+ // Cacheable redirects
+ case 300:
+ case 301:
+ case 302:
+ case 304:
+ case 307:
+ case 308:
+ // Gone forever
+ case 410:
+ break;
+ // Uncacheable redirects
+ case 303:
+ case 305:
+ // Other known errors
+ case 401:
+ case 407:
+ case 412:
+ case 416:
+ case 425:
+ case 429:
+ default: // revalidate unknown error pages
+ LOG(("Must validate since response is an uncacheable error page\n"));
+ return true;
+ }
+
+ // The no-cache response header indicates that we must validate this
+ // cached response before reusing.
+ if (NoCache_locked()) {
+ LOG(("Must validate since response contains 'no-cache' header\n"));
+ return true;
+ }
+
+ // Likewise, if the response is no-store, then we must validate this
+ // cached response before reusing. NOTE: it may seem odd that a no-store
+ // response may be cached, but indeed all responses are cached in order
+ // to support File->SaveAs, View->PageSource, and other browser features.
+ if (mCacheControlNoStore) {
+ LOG(("Must validate since response contains 'no-store' header\n"));
+ return true;
+ }
+
+ // Compare the Expires header to the Date header. If the server sent an
+ // Expires header with a timestamp in the past, then we must validate this
+ // cached response before reusing.
+ if (ExpiresInPast_locked()) {
+ LOG(("Must validate since Expires < Date\n"));
+ return true;
+ }
+
+ LOG(("no mandatory validation requirement\n"));
+ return false;
+}
+
+bool nsHttpResponseHead::MustValidateIfExpired() {
+ // according to RFC2616, section 14.9.4:
+ //
+ // When the must-revalidate directive is present in a response received by a
+ // cache, that cache MUST NOT use the entry after it becomes stale to respond
+ // to a subsequent request without first revalidating it with the origin
+ // server.
+ //
+ return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate");
+}
+
+bool nsHttpResponseHead::StaleWhileRevalidate(uint32_t now,
+ uint32_t expiration) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ if (expiration <= 0 || !mCacheControlStaleWhileRevalidateSet) {
+ return false;
+ }
+
+ // 'expiration' is the expiration time (an absolute unit), the swr window
+ // extends the expiration time.
+ CheckedInt<uint32_t> stallValidUntil = expiration;
+ stallValidUntil += mCacheControlStaleWhileRevalidate;
+ if (!stallValidUntil.isValid()) {
+ // overflow means an indefinite stale window
+ return true;
+ }
+
+ return now <= stallValidUntil.value();
+}
+
+bool nsHttpResponseHead::IsResumable() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ // even though some HTTP/1.0 servers may support byte range requests, we're
+ // not going to bother with them, since those servers wouldn't understand
+ // If-Range. Also, while in theory it may be possible to resume when the
+ // status code is not 200, it is unlikely to be worth the trouble, especially
+ // for non-2xx responses.
+ return mStatus == 200 && mVersion >= HttpVersion::v1_1 &&
+ mHeaders.PeekHeader(nsHttp::Content_Length) &&
+ (mHeaders.PeekHeader(nsHttp::ETag) ||
+ mHeaders.PeekHeader(nsHttp::Last_Modified)) &&
+ mHeaders.HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
+}
+
+bool nsHttpResponseHead::ExpiresInPast() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ExpiresInPast_locked();
+}
+
+bool nsHttpResponseHead::ExpiresInPast_locked() const {
+ uint32_t maxAgeVal, expiresVal, dateVal;
+
+ // Bug #203271. Ensure max-age directive takes precedence over Expires
+ if (NS_SUCCEEDED(GetMaxAgeValue_locked(&maxAgeVal))) {
+ return false;
+ }
+
+ return NS_SUCCEEDED(GetExpiresValue_locked(&expiresVal)) &&
+ NS_SUCCEEDED(GetDateValue_locked(&dateVal)) && expiresVal < dateVal;
+}
+
+void nsHttpResponseHead::UpdateHeaders(nsHttpResponseHead* aOther) {
+ LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this));
+
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ RecursiveMutexAutoLock monitorOther(aOther->mRecursiveMutex);
+
+ uint32_t i, count = aOther->mHeaders.Count();
+ for (i = 0; i < count; ++i) {
+ nsHttpAtom header;
+ nsAutoCString headerNameOriginal;
+
+ if (!aOther->mHeaders.PeekHeaderAt(i, header, headerNameOriginal)) {
+ continue;
+ }
+
+ nsAutoCString val;
+ if (NS_FAILED(aOther->GetHeader(header, val))) {
+ continue;
+ }
+
+ // Ignore any hop-by-hop headers...
+ if (header == nsHttp::Connection || header == nsHttp::Proxy_Connection ||
+ header == nsHttp::Keep_Alive || header == nsHttp::Proxy_Authenticate ||
+ header == nsHttp::Proxy_Authorization || // not a response header!
+ header == nsHttp::TE || header == nsHttp::Trailer ||
+ header == nsHttp::Transfer_Encoding || header == nsHttp::Upgrade ||
+ // Ignore any non-modifiable headers...
+ header == nsHttp::Content_Location || header == nsHttp::Content_MD5 ||
+ header == nsHttp::ETag ||
+ // Assume Cache-Control: "no-transform"
+ header == nsHttp::Content_Encoding || header == nsHttp::Content_Range ||
+ header == nsHttp::Content_Type ||
+ // Ignore wacky headers too...
+ // this one is for MS servers that send "Content-Length: 0"
+ // on 304 responses
+ header == nsHttp::Content_Length) {
+ LOG(("ignoring response header [%s: %s]\n", header.get(), val.get()));
+ } else {
+ LOG(("new response header [%s: %s]\n", header.get(), val.get()));
+
+ // overwrite the current header value with the new value...
+ DebugOnly<nsresult> rv =
+ SetHeader_locked(header, headerNameOriginal, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+}
+
+void nsHttpResponseHead::Reset() {
+ LOG(("nsHttpResponseHead::Reset\n"));
+
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mHeaders.Clear();
+
+ mVersion = HttpVersion::v1_1;
+ mStatus = 200;
+ mContentLength = -1;
+ mHasCacheControl = false;
+ mCacheControlPublic = false;
+ mCacheControlPrivate = false;
+ mCacheControlNoStore = false;
+ mCacheControlNoCache = false;
+ mCacheControlImmutable = false;
+ mCacheControlStaleWhileRevalidateSet = false;
+ mCacheControlStaleWhileRevalidate = 0;
+ mCacheControlMaxAgeSet = false;
+ mCacheControlMaxAge = 0;
+ mPragmaNoCache = false;
+ mStatusText.Truncate();
+ mContentType.Truncate();
+ mContentCharset.Truncate();
+}
+
+nsresult nsHttpResponseHead::ParseDateHeader(const nsHttpAtom& header,
+ uint32_t* result) const {
+ const char* val = mHeaders.PeekHeader(header);
+ if (!val) return NS_ERROR_NOT_AVAILABLE;
+
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(val, true, &time);
+ if (st != PR_SUCCESS) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = PRTimeToSeconds(time);
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetAgeValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetAgeValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetAgeValue_locked(uint32_t* result) const {
+ const char* val = mHeaders.PeekHeader(nsHttp::Age);
+ if (!val) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = (uint32_t)atoi(val);
+ return NS_OK;
+}
+
+// Return the value of the (HTTP 1.1) max-age directive, which itself is a
+// component of the Cache-Control response header
+nsresult nsHttpResponseHead::GetMaxAgeValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetMaxAgeValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetMaxAgeValue_locked(uint32_t* result) const {
+ if (!mCacheControlMaxAgeSet) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *result = mCacheControlMaxAge;
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetDateValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetDateValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetExpiresValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetExpiresValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetExpiresValue_locked(uint32_t* result) const {
+ const char* val = mHeaders.PeekHeader(nsHttp::Expires);
+ if (!val) return NS_ERROR_NOT_AVAILABLE;
+
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(val, true, &time);
+ if (st != PR_SUCCESS) {
+ // parsing failed... RFC 2616 section 14.21 says we should treat this
+ // as an expiration time in the past.
+ *result = 0;
+ return NS_OK;
+ }
+
+ if (time < 0) {
+ *result = 0;
+ } else {
+ *result = PRTimeToSeconds(time);
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetLastModifiedValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+}
+
+bool nsHttpResponseHead::operator==(const nsHttpResponseHead& aOther) const
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ nsHttpResponseHead& curr = const_cast<nsHttpResponseHead&>(*this);
+ nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
+ RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
+ RecursiveMutexAutoLock monitor(curr.mRecursiveMutex);
+
+ return mHeaders == aOther.mHeaders && mVersion == aOther.mVersion &&
+ mStatus == aOther.mStatus && mStatusText == aOther.mStatusText &&
+ mContentLength == aOther.mContentLength &&
+ mContentType == aOther.mContentType &&
+ mContentCharset == aOther.mContentCharset &&
+ mHasCacheControl == aOther.mHasCacheControl &&
+ mCacheControlPublic == aOther.mCacheControlPublic &&
+ mCacheControlPrivate == aOther.mCacheControlPrivate &&
+ mCacheControlNoCache == aOther.mCacheControlNoCache &&
+ mCacheControlNoStore == aOther.mCacheControlNoStore &&
+ mCacheControlImmutable == aOther.mCacheControlImmutable &&
+ mCacheControlStaleWhileRevalidateSet ==
+ aOther.mCacheControlStaleWhileRevalidateSet &&
+ mCacheControlStaleWhileRevalidate ==
+ aOther.mCacheControlStaleWhileRevalidate &&
+ mCacheControlMaxAgeSet == aOther.mCacheControlMaxAgeSet &&
+ mCacheControlMaxAge == aOther.mCacheControlMaxAge &&
+ mPragmaNoCache == aOther.mPragmaNoCache;
+}
+
+int64_t nsHttpResponseHead::TotalEntitySize() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ const char* contentRange = mHeaders.PeekHeader(nsHttp::Content_Range);
+ if (!contentRange) return mContentLength;
+
+ // Total length is after a slash
+ const char* slash = strrchr(contentRange, '/');
+ if (!slash) return -1; // No idea what the length is
+
+ slash++;
+ if (*slash == '*') { // Server doesn't know the length
+ return -1;
+ }
+
+ int64_t size;
+ if (!nsHttp::ParseInt64(slash, &size)) size = UINT64_MAX;
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead <private>
+//-----------------------------------------------------------------------------
+
+void nsHttpResponseHead::ParseVersion(const char* str) {
+ // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
+
+ LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str));
+
+ Tokenizer t(str, nullptr, "");
+ // make sure we have HTTP at the beginning
+ if (!t.CheckWord("HTTP")) {
+ if (nsCRT::strncasecmp(str, "ICY ", 4) == 0) {
+ // ShoutCast ICY is HTTP/1.0-like. Assume it is HTTP/1.0.
+ LOG(("Treating ICY as HTTP 1.0\n"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+ LOG(("looks like a HTTP/0.9 response\n"));
+ mVersion = HttpVersion::v0_9;
+ return;
+ }
+
+ if (!t.CheckChar('/')) {
+ LOG(("server did not send a version number; assuming HTTP/1.0\n"));
+ // NCSA/1.5.2 has a bug in which it fails to send a version number
+ // if the request version is HTTP/1.1, so we fall back on HTTP/1.0
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ uint32_t major;
+ if (!t.ReadInteger(&major)) {
+ LOG(("server did not send a correct version number; assuming HTTP/1.0"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ if (major == 3) {
+ mVersion = HttpVersion::v3_0;
+ return;
+ }
+
+ if (major == 2) {
+ mVersion = HttpVersion::v2_0;
+ return;
+ }
+
+ if (major != 1) {
+ LOG(("server did not send a correct version number; assuming HTTP/1.0"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ if (!t.CheckChar('.')) {
+ LOG(("mal-formed server version; assuming HTTP/1.0\n"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ uint32_t minor;
+ if (!t.ReadInteger(&minor)) {
+ LOG(("server did not send a correct version number; assuming HTTP/1.0"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ if (minor >= 1) {
+ // at least HTTP/1.1
+ mVersion = HttpVersion::v1_1;
+ } else {
+ // treat anything else as version 1.0
+ mVersion = HttpVersion::v1_0;
+ }
+}
+
+void nsHttpResponseHead::ParseCacheControl(const char* val) {
+ if (!(val && *val)) {
+ // clear flags
+ mHasCacheControl = false;
+ mCacheControlPublic = false;
+ mCacheControlPrivate = false;
+ mCacheControlNoCache = false;
+ mCacheControlNoStore = false;
+ mCacheControlImmutable = false;
+ mCacheControlStaleWhileRevalidateSet = false;
+ mCacheControlStaleWhileRevalidate = 0;
+ mCacheControlMaxAgeSet = false;
+ mCacheControlMaxAge = 0;
+ return;
+ }
+
+ nsDependentCString cacheControlRequestHeader(val);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+
+ mHasCacheControl = true;
+ mCacheControlPublic = cacheControlRequest.Public();
+ mCacheControlPrivate = cacheControlRequest.Private();
+ mCacheControlNoCache = cacheControlRequest.NoCache();
+ mCacheControlNoStore = cacheControlRequest.NoStore();
+ mCacheControlImmutable = cacheControlRequest.Immutable();
+ mCacheControlStaleWhileRevalidateSet =
+ cacheControlRequest.StaleWhileRevalidate(
+ &mCacheControlStaleWhileRevalidate);
+ mCacheControlMaxAgeSet = cacheControlRequest.MaxAge(&mCacheControlMaxAge);
+}
+
+void nsHttpResponseHead::ParsePragma(const char* val) {
+ LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val));
+
+ if (!(val && *val)) {
+ // clear no-cache flag
+ mPragmaNoCache = false;
+ return;
+ }
+
+ // Although 'Pragma: no-cache' is not a standard HTTP response header (it's a
+ // request header), caching is inhibited when this header is present so as to
+ // match existing Navigator behavior.
+ mPragmaNoCache = nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS);
+}
+
+nsresult nsHttpResponseHead::ParseResponseContentLength(
+ const nsACString& aHeaderStr) {
+ int64_t contentLength = 0;
+ // Ref: https://fetch.spec.whatwg.org/#content-length-header
+ // Step 1. Let values be the result of getting, decoding, and splitting
+ // `Content - Length` from headers.
+ // Step 1 is done by the caller
+ // Step 2. If values is null, then return null.
+ if (aHeaderStr.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Step 3 Let candidateValue be null.
+ Maybe<nsAutoCString> candidateValue;
+ // Step 4 For each value of values
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizerTemplate<
+ NS_IsAsciiWhitespace, nsTokenizerFlags::IncludeEmptyTokenAtEnd>(
+ aHeaderStr, ',')
+ .ToRange()) {
+ // Step 4.1 If candidateValue is null, then set candidateValue to value.
+ if (candidateValue.isNothing()) {
+ candidateValue.emplace(token);
+ }
+ // Step 4.2 Otherwise, if value is not candidateValue, return failure.
+ if (candidateValue.value() != token) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ // Step 5 If candidateValue is the empty string or has a code point that is
+ // not an ASCII digit, then return null.
+ if (candidateValue.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Step 6 Return candidateValue, interpreted as decimal number contentLength
+ const char* end = nullptr;
+ if (!net::nsHttp::ParseInt64(candidateValue->get(), &end, &contentLength)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (*end != '\0') {
+ // a number was parsed by ParseInt64 but candidateValue contains non-numeric
+ // characters
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mContentLength = contentLength;
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::VisitHeaders(
+ nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+namespace {
+class ContentTypeOptionsVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ ContentTypeOptionsVisitor() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ if (!mHeaderPresent) {
+ mHeaderPresent = true;
+ } else {
+ // multiple XCTO headers in response, merge them
+ mContentTypeOptionsHeader.Append(", "_ns);
+ }
+ mContentTypeOptionsHeader.Append(aValue);
+ return NS_OK;
+ }
+
+ void GetMergedHeader(nsACString& aValue) {
+ aValue = mContentTypeOptionsHeader;
+ }
+
+ private:
+ ~ContentTypeOptionsVisitor() = default;
+ bool mHeaderPresent{false};
+ nsAutoCString mContentTypeOptionsHeader;
+};
+
+NS_IMPL_ISUPPORTS(ContentTypeOptionsVisitor, nsIHttpHeaderVisitor)
+} // namespace
+
+nsresult nsHttpResponseHead::GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.GetOriginalHeader(aHeader, aVisitor);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+bool nsHttpResponseHead::HasContentType() const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return !mContentType.IsEmpty();
+}
+
+bool nsHttpResponseHead::HasContentCharset() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return !mContentCharset.IsEmpty();
+}
+
+bool nsHttpResponseHead::GetContentTypeOptionsHeader(nsACString& aOutput) {
+ aOutput.Truncate();
+
+ nsAutoCString contentTypeOptionsHeader;
+ // We need to fetch original headers and manually merge them because empty
+ // header values are not retrieved with GetHeader. Ref - Bug 1819642
+ RefPtr<ContentTypeOptionsVisitor> visitor = new ContentTypeOptionsVisitor();
+ Unused << GetOriginalHeader(nsHttp::X_Content_Type_Options, visitor);
+ visitor->GetMergedHeader(contentTypeOptionsHeader);
+ if (contentTypeOptionsHeader.IsEmpty()) {
+ // if there is no XCTO header, then there is nothing to do.
+ return false;
+ }
+
+ // XCTO header might contain multiple values which are comma separated, so:
+ // a) let's skip all subsequent values
+ // e.g. " NoSniFF , foo " will be " NoSniFF "
+ int32_t idx = contentTypeOptionsHeader.Find(",");
+ if (idx >= 0) {
+ contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
+ }
+ // b) let's trim all surrounding whitespace
+ // e.g. " NoSniFF " -> "NoSniFF"
+ nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
+ contentTypeOptionsHeader);
+
+ aOutput.Assign(contentTypeOptionsHeader);
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpResponseHead.h b/netwerk/protocol/http/nsHttpResponseHead.h
new file mode 100644
index 0000000000..9caf92b3e1
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -0,0 +1,239 @@
+/* -*- 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 nsHttpResponseHead_h__
+#define nsHttpResponseHead_h__
+
+#include "nsHttpHeaderArray.h"
+#include "nsHttp.h"
+#include "nsString.h"
+#include "mozilla/RecursiveMutex.h"
+
+#ifdef Status
+/* Xlib headers insist on this for some reason... Nuke it because
+ it'll override our member name */
+typedef Status __StatusTmp;
+# undef Status
+typedef __StatusTmp Status;
+#endif
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead represents the status line and headers from an HTTP
+// response.
+//-----------------------------------------------------------------------------
+
+class nsHttpResponseHead {
+ public:
+ nsHttpResponseHead() = default;
+
+ nsHttpResponseHead(const nsHttpResponseHead& aOther);
+ nsHttpResponseHead& operator=(const nsHttpResponseHead& aOther);
+
+ void Enter() const MOZ_CAPABILITY_ACQUIRE(mRecursiveMutex) {
+ mRecursiveMutex.Lock();
+ }
+ void Exit() const MOZ_CAPABILITY_RELEASE(mRecursiveMutex) {
+ mRecursiveMutex.Unlock();
+ }
+ void AssertMutexOwned() const { mRecursiveMutex.AssertCurrentThreadIn(); }
+
+ HttpVersion Version();
+ uint16_t Status() const;
+ void StatusText(nsACString& aStatusText);
+ int64_t ContentLength();
+ void ContentType(nsACString& aContentType) const;
+ void ContentCharset(nsACString& aContentCharset);
+ bool Public();
+ bool Private();
+ bool NoStore();
+ bool NoCache();
+ bool Immutable();
+ /**
+ * Full length of the entity. For byte-range requests, this may be larger
+ * than ContentLength(), which will only represent the requested part of the
+ * entity.
+ */
+ int64_t TotalEntitySize();
+
+ [[nodiscard]] nsresult SetHeader(const nsACString& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult GetHeader(const nsHttpAtom& h, nsACString& v) const;
+ void ClearHeader(const nsHttpAtom& h);
+ void ClearHeaders();
+ bool HasHeaderValue(const nsHttpAtom& h, const char* v);
+ bool HasHeader(const nsHttpAtom& h) const;
+
+ void SetContentType(const nsACString& s);
+ void SetContentCharset(const nsACString& s);
+ void SetContentLength(int64_t);
+
+ // write out the response status line and headers as a single text block,
+ // optionally pruning out transient headers (ie. headers that only make
+ // sense the first time the response is handled).
+ // Both functions append to the string supplied string.
+ void Flatten(nsACString&, bool pruneTransients);
+ void FlattenNetworkOriginalHeaders(nsACString& buf);
+
+ // The next 2 functions parse flattened response head and original net
+ // headers. They are used when we are reading an entry from the cache.
+ //
+ // To keep proper order of the original headers we MUST call
+ // ParseCachedOriginalHeaders FIRST and then ParseCachedHead.
+ //
+ // block must be null terminated.
+ [[nodiscard]] nsresult ParseCachedHead(const char* block);
+ [[nodiscard]] nsresult ParseCachedOriginalHeaders(char* block);
+
+ // parse the status line.
+ nsresult ParseStatusLine(const nsACString& line);
+
+ // parse a header line.
+ [[nodiscard]] nsresult ParseHeaderLine(const nsACString& line);
+
+ // cache validation support methods
+ [[nodiscard]] nsresult ComputeFreshnessLifetime(uint32_t*);
+ [[nodiscard]] nsresult ComputeCurrentAge(uint32_t now, uint32_t requestTime,
+ uint32_t* result);
+ bool MustValidate();
+ bool MustValidateIfExpired();
+
+ // return true if the response contains a valid Cache-control:
+ // stale-while-revalidate and |now| is less than or equal |expiration +
+ // stale-while-revalidate|. Otherwise false.
+ bool StaleWhileRevalidate(uint32_t now, uint32_t expiration);
+
+ // returns true if the server appears to support byte range requests.
+ bool IsResumable();
+
+ // returns true if the Expires header has a value in the past relative to the
+ // value of the Date header.
+ bool ExpiresInPast();
+
+ // update headers...
+ void UpdateHeaders(nsHttpResponseHead* aOther);
+
+ // reset the response head to it's initial state
+ void Reset();
+
+ [[nodiscard]] nsresult GetLastModifiedValue(uint32_t* result);
+
+ bool operator==(const nsHttpResponseHead& aOther) const;
+
+ // Using this function it is possible to itereate through all headers
+ // automatically under one lock.
+ [[nodiscard]] nsresult VisitHeaders(nsIHttpHeaderVisitor* visitor,
+ nsHttpHeaderArray::VisitorFilter filter);
+ [[nodiscard]] nsresult GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor);
+
+ bool HasContentType() const;
+ bool HasContentCharset();
+ bool GetContentTypeOptionsHeader(nsACString& aOutput);
+
+ private:
+ [[nodiscard]] nsresult SetHeader_locked(const nsHttpAtom& atom,
+ const nsACString& h,
+ const nsACString& v, bool m = false)
+ MOZ_REQUIRES(mRecursiveMutex);
+ void AssignDefaultStatusText() MOZ_REQUIRES(mRecursiveMutex);
+ void ParseVersion(const char*) MOZ_REQUIRES(mRecursiveMutex);
+ void ParseCacheControl(const char*) MOZ_REQUIRES(mRecursiveMutex);
+ void ParsePragma(const char*) MOZ_REQUIRES(mRecursiveMutex);
+ // Parses a content-length header-value as described in
+ // https://fetch.spec.whatwg.org/#content-length-header
+ nsresult ParseResponseContentLength(const nsACString& aHeaderStr)
+ MOZ_REQUIRES(mRecursiveMutex);
+
+ nsresult ParseStatusLine_locked(const nsACString& line)
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult ParseHeaderLine_locked(const nsACString& line,
+ bool originalFromNetHeaders)
+ MOZ_REQUIRES(mRecursiveMutex);
+
+ // these return failure if the header does not exist.
+ [[nodiscard]] nsresult ParseDateHeader(const nsHttpAtom& header,
+ uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetAgeValue(uint32_t* result);
+ [[nodiscard]] nsresult GetMaxAgeValue(uint32_t* result);
+ [[nodiscard]] nsresult GetStaleWhileRevalidateValue(uint32_t* result);
+ [[nodiscard]] nsresult GetDateValue(uint32_t* result);
+ [[nodiscard]] nsresult GetExpiresValue(uint32_t* result);
+
+ bool ExpiresInPast_locked() const MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetAgeValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetExpiresValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetMaxAgeValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetStaleWhileRevalidateValue_locked(
+ uint32_t* result) const MOZ_REQUIRES(mRecursiveMutex);
+
+ [[nodiscard]] nsresult GetDateValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex) {
+ return ParseDateHeader(nsHttp::Date, result);
+ }
+
+ [[nodiscard]] nsresult GetLastModifiedValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex) {
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+ }
+
+ bool NoCache_locked() const MOZ_REQUIRES(mRecursiveMutex) {
+ // We ignore Pragma: no-cache if Cache-Control is set.
+ MOZ_ASSERT_IF(mCacheControlNoCache, mHasCacheControl);
+ return mHasCacheControl ? mCacheControlNoCache : mPragmaNoCache;
+ }
+
+ private:
+ // All members must be copy-constructable and assignable
+ nsHttpHeaderArray mHeaders MOZ_GUARDED_BY(mRecursiveMutex);
+ HttpVersion mVersion MOZ_GUARDED_BY(mRecursiveMutex){HttpVersion::v1_1};
+ uint16_t mStatus MOZ_GUARDED_BY(mRecursiveMutex){200};
+ nsCString mStatusText MOZ_GUARDED_BY(mRecursiveMutex);
+ int64_t mContentLength MOZ_GUARDED_BY(mRecursiveMutex){-1};
+ nsCString mContentType MOZ_GUARDED_BY(mRecursiveMutex);
+ nsCString mContentCharset MOZ_GUARDED_BY(mRecursiveMutex);
+ bool mHasCacheControl MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlPublic MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlPrivate MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlNoStore MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlNoCache MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlImmutable MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlStaleWhileRevalidateSet MOZ_GUARDED_BY(mRecursiveMutex){
+ false};
+ uint32_t mCacheControlStaleWhileRevalidate MOZ_GUARDED_BY(mRecursiveMutex){0};
+ bool mCacheControlMaxAgeSet MOZ_GUARDED_BY(mRecursiveMutex){false};
+ uint32_t mCacheControlMaxAge MOZ_GUARDED_BY(mRecursiveMutex){0};
+ bool mPragmaNoCache MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ // We are using RecursiveMutex instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ mutable RecursiveMutex mRecursiveMutex{"nsHttpResponseHead.mRecursiveMutex"};
+ // During VisitHeader we sould not allow call to SetHeader.
+ bool mInVisitHeaders MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ friend struct IPC::ParamTraits<nsHttpResponseHead>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpResponseHead_h__
diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp
new file mode 100644
index 0000000000..580ea35841
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -0,0 +1,3628 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "nsHttpTransaction.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "HttpLog.h"
+#include "HTTPSRecordResolver.h"
+#include "NSSErrorsService.h"
+#include "base/basictypes.h"
+#include "mozilla/Components.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "MockHttpAuth.h"
+#include "nsCRT.h"
+#include "nsComponentManagerUtils.h" // do_CreateInstance
+#include "nsHttpBasicAuth.h"
+#include "nsHttpChannel.h"
+#include "nsHttpChunkedDecoder.h"
+#include "nsHttpDigestAuth.h"
+#include "nsHttpHandler.h"
+#include "nsHttpNTLMAuth.h"
+#ifdef MOZ_AUTH_EXTENSION
+# include "nsHttpNegotiateAuth.h"
+#endif
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsICancelable.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsIInputStream.h"
+#include "nsIInputStreamPriority.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsIRequestContext.h"
+#include "nsISeekableStream.h"
+#include "nsITLSSocketControl.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITransport.h"
+#include "nsMultiplexInputStream.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsStringStream.h"
+#include "nsTransportUtils.h"
+#include "sslerr.h"
+#include "SpeculativeTransaction.h"
+
+//-----------------------------------------------------------------------------
+
+// Place a limit on how much non-compliant HTTP can be skipped while
+// looking for a response header
+#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
+
+using namespace mozilla::net;
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <public>
+//-----------------------------------------------------------------------------
+
+nsHttpTransaction::nsHttpTransaction() {
+ LOG(("Creating nsHttpTransaction @%p\n", this));
+
+#ifdef MOZ_VALGRIND
+ memset(&mSelfAddr, 0, sizeof(NetAddr));
+ memset(&mPeerAddr, 0, sizeof(NetAddr));
+#endif
+ mSelfAddr.raw.family = PR_AF_UNSPEC;
+ mPeerAddr.raw.family = PR_AF_UNSPEC;
+
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
+}
+
+void nsHttpTransaction::ResumeReading() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mReadingStopped) {
+ return;
+ }
+
+ LOG(("nsHttpTransaction::ResumeReading %p", this));
+
+ mReadingStopped = false;
+
+ // This with either reengage the limit when still throttled in WriteSegments
+ // or simply reset to allow unlimeted reading again.
+ mThrottlingReadAllowance = THROTTLE_NO_LIMIT;
+
+ if (mConnection) {
+ mConnection->TransactionHasDataToRecv(this);
+ nsresult rv = mConnection->ResumeRecv();
+ if (NS_FAILED(rv)) {
+ LOG((" resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+bool nsHttpTransaction::EligibleForThrottling() const {
+ return (mClassOfServiceFlags &
+ (nsIClassOfService::Throttleable | nsIClassOfService::DontThrottle |
+ nsIClassOfService::Leader | nsIClassOfService::Unblocked)) ==
+ nsIClassOfService::Throttleable;
+}
+
+void nsHttpTransaction::SetClassOfService(ClassOfService cos) {
+ if (mClosed) {
+ return;
+ }
+
+ bool wasThrottling = EligibleForThrottling();
+ mClassOfServiceFlags = cos.Flags();
+ mClassOfServiceIncremental = cos.Incremental();
+ bool isThrottling = EligibleForThrottling();
+
+ if (mConnection && wasThrottling != isThrottling) {
+ // Do nothing until we are actually activated. For now
+ // only remember the throttle flag. Call to UpdateActiveTransaction
+ // would add this transaction to the list too early.
+ gHttpHandler->ConnMgr()->UpdateActiveTransaction(this);
+
+ if (mReadingStopped && !isThrottling) {
+ ResumeReading();
+ }
+ }
+}
+
+class ReleaseOnSocketThread final : public mozilla::Runnable {
+ public:
+ explicit ReleaseOnSocketThread(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed)
+ : Runnable("ReleaseOnSocketThread"), mDoomed(std::move(aDoomed)) {}
+
+ NS_IMETHOD
+ Run() override {
+ mDoomed.Clear();
+ return NS_OK;
+ }
+
+ void Dispatch() {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ Unused << sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ virtual ~ReleaseOnSocketThread() = default;
+
+ nsTArray<nsCOMPtr<nsISupports>> mDoomed;
+};
+
+nsHttpTransaction::~nsHttpTransaction() {
+ LOG(("Destroying nsHttpTransaction @%p\n", this));
+
+ if (mPushedStream) {
+ mPushedStream->OnPushFailed();
+ mPushedStream = nullptr;
+ }
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
+ mTokenBucketCancel = nullptr;
+ }
+
+ // Force the callbacks and connection to be released right now
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = nullptr;
+ }
+
+ mEarlyHintObserver = nullptr;
+
+ delete mResponseHead;
+ delete mChunkedDecoder;
+ ReleaseBlockingTransaction();
+
+ nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
+ if (mConnection) {
+ arrayToRelease.AppendElement(mConnection.forget());
+ }
+
+ if (!arrayToRelease.IsEmpty()) {
+ RefPtr<ReleaseOnSocketThread> r =
+ new ReleaseOnSocketThread(std::move(arrayToRelease));
+ r->Dispatch();
+ }
+}
+
+nsresult nsHttpTransaction::Init(
+ uint32_t caps, nsHttpConnectionInfo* cinfo, nsHttpRequestHead* requestHead,
+ nsIInputStream* requestBody, uint64_t requestContentLength,
+ bool requestBodyHasHeaders, nsIEventTarget* target,
+ nsIInterfaceRequestor* callbacks, nsITransportEventSink* eventsink,
+ uint64_t browserId, HttpTrafficCategory trafficCategory,
+ nsIRequestContext* requestContext, ClassOfService classOfService,
+ uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId,
+ TransactionObserverFunc&& transactionObserver,
+ OnPushCallback&& aOnPushCallback,
+ HttpTransactionShell* transWithPushedStream, uint32_t aPushedStreamId) {
+ nsresult rv;
+
+ LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
+
+ MOZ_ASSERT(cinfo);
+ MOZ_ASSERT(requestHead);
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->IsOnCurrentThread());
+
+ mChannelId = channelId;
+ mTransactionObserver = std::move(transactionObserver);
+ mOnPushCallback = std::move(aOnPushCallback);
+ mBrowserId = browserId;
+
+ mTrafficCategory = trafficCategory;
+
+ LOG1(("nsHttpTransaction %p SetRequestContext %p\n", this, requestContext));
+ mRequestContext = requestContext;
+
+ SetClassOfService(classOfService);
+ mResponseTimeoutEnabled = responseTimeoutEnabled;
+ mInitialRwin = initialRwin;
+
+ // create transport event sink proxy. it coalesces consecutive
+ // events of the same status type.
+ rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink), eventsink,
+ target);
+
+ if (NS_FAILED(rv)) return rv;
+
+ mConnInfo = cinfo;
+ mCallbacks = callbacks;
+ mConsumerTarget = target;
+ mCaps = caps;
+ // eventsink is a nsHttpChannel when we expect "103 Early Hints" responses.
+ // We expect it in document requests and not e.g. in TRR requests.
+ mEarlyHintObserver = do_QueryInterface(eventsink);
+
+ if (requestHead->IsHead()) {
+ mNoContent = true;
+ }
+
+ // grab a weak reference to the request head
+ mRequestHead = requestHead;
+
+ mReqHeaderBuf = nsHttp::ConvertRequestHeadToString(
+ *requestHead, !!requestBody, requestBodyHasHeaders,
+ cinfo->UsingConnect());
+
+ if (LOG1_ENABLED()) {
+ LOG1(("http request [\n"));
+ LogHeaders(mReqHeaderBuf.get());
+ LOG1(("]\n"));
+ }
+
+ // report the request header
+ if (gHttpHandler->HttpActivityDistributorActivated()) {
+ nsCString requestBuf(mReqHeaderBuf);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "ObserveHttpActivityWithArgs", [channelId(mChannelId), requestBuf]() {
+ if (!gHttpHandler) {
+ return;
+ }
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(channelId),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, requestBuf);
+ }));
+ }
+
+ // Create a string stream for the request header buf (the stream holds
+ // a non-owning reference to the request header data, so we MUST keep
+ // mReqHeaderBuf around).
+ nsCOMPtr<nsIInputStream> headers;
+ rv = NS_NewByteInputStream(getter_AddRefs(headers), mReqHeaderBuf,
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) return rv;
+
+ mHasRequestBody = !!requestBody;
+ if (mHasRequestBody && !requestContentLength) {
+ mHasRequestBody = false;
+ }
+
+ requestContentLength += mReqHeaderBuf.Length();
+
+ if (mHasRequestBody) {
+ // wrap the headers and request body in a multiplexed input stream.
+ nsCOMPtr<nsIMultiplexInputStream> multi;
+ rv = nsMultiplexInputStreamConstructor(NS_GET_IID(nsIMultiplexInputStream),
+ getter_AddRefs(multi));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = multi->AppendStream(headers);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = multi->AppendStream(requestBody);
+ if (NS_FAILED(rv)) return rv;
+
+ // wrap the multiplexed input stream with a buffered input stream, so
+ // that we write data in the largest chunks possible. this is actually
+ // necessary to workaround some common server bugs (see bug 137155).
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multi));
+ rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream),
+ stream.forget(),
+ nsIOService::gDefaultSegmentSize);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mRequestStream = headers;
+ }
+
+ nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(eventsink);
+ if (throttled) {
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue;
+ rv = throttled->GetThrottleQueue(getter_AddRefs(queue));
+ // In case of failure, just carry on without throttling.
+ if (NS_SUCCEEDED(rv) && queue) {
+ nsCOMPtr<nsIAsyncInputStream> wrappedStream;
+ rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream));
+ // Failure to throttle isn't sufficient reason to fail
+ // initialization
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(wrappedStream != nullptr);
+ LOG(
+ ("nsHttpTransaction::Init %p wrapping input stream using throttle "
+ "queue %p\n",
+ this, queue.get()));
+ mRequestStream = wrappedStream;
+ }
+ }
+ }
+
+ // make sure request content-length fits within js MAX_SAFE_INTEGER
+ mRequestSize = InScriptableRange(requestContentLength)
+ ? static_cast<int64_t>(requestContentLength)
+ : -1;
+
+ // create pipe for response stream
+ NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut), true, true,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+
+ if (transWithPushedStream && aPushedStreamId) {
+ RefPtr<nsHttpTransaction> trans =
+ transWithPushedStream->AsHttpTransaction();
+ MOZ_ASSERT(trans);
+ mPushedStream = trans->TakePushedStreamById(aPushedStreamId);
+ }
+
+ bool forceUseHTTPSRR = StaticPrefs::network_dns_force_use_https_rr();
+ if ((gHttpHandler->UseHTTPSRRAsAltSvcEnabled() &&
+ !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR)) ||
+ forceUseHTTPSRR) {
+ nsCOMPtr<nsIEventTarget> target;
+ Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ if (StaticPrefs::network_dns_force_waiting_https_rr() ||
+ StaticPrefs::network_dns_echconfig_enabled() || forceUseHTTPSRR) {
+ mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR;
+ }
+
+ mResolver = new HTTPSRecordResolver(this);
+ nsCOMPtr<nsICancelable> dnsRequest;
+ rv = mResolver->FetchHTTPSRRInternal(target, getter_AddRefs(dnsRequest));
+ if (NS_SUCCEEDED(rv)) {
+ mHTTPSSVCReceivedStage = HTTPSSVC_NOT_PRESENT;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ mDNSRequest.swap(dnsRequest);
+ if (NS_FAILED(rv)) {
+ MakeDontWaitHTTPSRR();
+ }
+ }
+ }
+ }
+
+ RefPtr<nsHttpChannel> httpChannel = do_QueryObject(eventsink);
+ RefPtr<WebTransportSessionEventListener> listener =
+ httpChannel ? httpChannel->GetWebTransportSessionEventListener()
+ : nullptr;
+ if (listener) {
+ mWebTransportSessionEventListener = std::move(listener);
+ }
+
+ return NS_OK;
+}
+
+static inline void CreateAndStartTimer(nsCOMPtr<nsITimer>& aTimer,
+ nsITimerCallback* aCallback,
+ uint32_t aTimeout) {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!aTimer);
+
+ if (!aTimeout) {
+ return;
+ }
+
+ NS_NewTimerWithCallback(getter_AddRefs(aTimer), aCallback, aTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void nsHttpTransaction::OnPendingQueueInserted(
+ const nsACString& aConnectionHashKey) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ {
+ MutexAutoLock lock(mLock);
+ mHashKeyOfConnectionEntry.Assign(aConnectionHashKey);
+ }
+
+ // Don't create mHttp3BackupTimer if HTTPS RR is in play.
+ if (mConnInfo->IsHttp3() && !mOrigConnInfo && !mConnInfo->GetWebTransport()) {
+ // Backup timer should only be created once.
+ if (!mHttp3BackupTimerCreated) {
+ CreateAndStartTimer(mHttp3BackupTimer, this,
+ StaticPrefs::network_http_http3_backup_timer_delay());
+ mHttp3BackupTimerCreated = true;
+ }
+ }
+}
+
+nsresult nsHttpTransaction::AsyncRead(nsIStreamListener* listener,
+ nsIRequest** pump) {
+ RefPtr<nsInputStreamPump> transactionPump;
+ nsresult rv =
+ nsInputStreamPump::Create(getter_AddRefs(transactionPump), mPipeIn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transactionPump->AsyncRead(listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transactionPump.forget(pump);
+ return NS_OK;
+}
+
+// This method should only be used on the socket thread
+nsAHttpConnection* nsHttpTransaction::Connection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mConnection.get();
+}
+
+void nsHttpTransaction::SetH2WSConnRefTaken() {
+ if (!OnSocketThread()) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsHttpTransaction::SetH2WSConnRefTaken", this,
+ &nsHttpTransaction::SetH2WSConnRefTaken);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+}
+
+UniquePtr<nsHttpResponseHead> nsHttpTransaction::TakeResponseHead() {
+ MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
+
+ // Lock TakeResponseHead() against main thread
+ MutexAutoLock lock(mLock);
+
+ mResponseHeadTaken = true;
+
+ // Even in OnStartRequest() the headers won't be available if we were
+ // canceled
+ if (!mHaveAllHeaders) {
+ NS_WARNING("response headers not available or incomplete");
+ return nullptr;
+ }
+
+ return WrapUnique(std::exchange(mResponseHead, nullptr));
+}
+
+UniquePtr<nsHttpHeaderArray> nsHttpTransaction::TakeResponseTrailers() {
+ MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
+
+ // Lock TakeResponseTrailers() against main thread
+ MutexAutoLock lock(mLock);
+
+ mResponseTrailersTaken = true;
+ return std::move(mForTakeResponseTrailers);
+}
+
+void nsHttpTransaction::SetProxyConnectFailed() { mProxyConnectFailed = true; }
+
+nsHttpRequestHead* nsHttpTransaction::RequestHead() { return mRequestHead; }
+
+uint32_t nsHttpTransaction::Http1xTransactionCount() { return 1; }
+
+nsresult nsHttpTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//----------------------------------------------------------------------------
+// nsHttpTransaction::nsAHttpTransaction
+//----------------------------------------------------------------------------
+
+void nsHttpTransaction::SetConnection(nsAHttpConnection* conn) {
+ {
+ MutexAutoLock lock(mLock);
+ mConnection = conn;
+ if (mConnection) {
+ mIsHttp3Used = mConnection->Version() == HttpVersion::v3_0;
+ }
+ }
+}
+
+void nsHttpTransaction::OnActivated() {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mActivated) {
+ return;
+ }
+
+ if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->IncrementHttpTransaction(mTrafficCategory);
+ }
+ if (mConnection) {
+ mConnection->SetTrafficCategory(mTrafficCategory);
+ }
+ }
+
+ if (mConnection && mRequestHead &&
+ mConnection->Version() >= HttpVersion::v2_0) {
+ // So this is fun. On http/2, we want to send TE: trailers, to be
+ // spec-compliant. So we add it to the request head here. The fun part
+ // is that adding a header to the request head at this point has no
+ // effect on what we send on the wire, as the headers are already
+ // flattened (in Init()) by the time we get here. So the *real* adding
+ // of the header happens in the h2 compression code. We still have to
+ // add the header to the request head here, though, so that devtools can
+ // show that we sent the header. FUN!
+ Unused << mRequestHead->SetHeader(nsHttp::TE, "trailers"_ns);
+ }
+
+ mActivated = true;
+ gHttpHandler->ConnMgr()->AddActiveTransaction(this);
+}
+
+void nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** cb) {
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
+ tmp.forget(cb);
+}
+
+void nsHttpTransaction::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aCallbacks) {
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = aCallbacks;
+ }
+
+ if (gSocketTransportService) {
+ RefPtr<UpdateSecurityCallbacks> event =
+ new UpdateSecurityCallbacks(this, aCallbacks);
+ gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+ }
+}
+
+void nsHttpTransaction::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress) {
+ LOG1(("nsHttpTransaction::OnSocketStatus [this=%p status=%" PRIx32
+ " progress=%" PRId64 "]\n",
+ this, static_cast<uint32_t>(status), progress));
+
+ if (status == NS_NET_STATUS_CONNECTED_TO ||
+ status == NS_NET_STATUS_WAITING_FOR) {
+ if (mConnection) {
+ MutexAutoLock lock(mLock);
+ mConnection->GetSelfAddr(&mSelfAddr);
+ mConnection->GetPeerAddr(&mPeerAddr);
+ mResolvedByTRR = mConnection->ResolvedByTRR();
+ mEffectiveTRRMode = mConnection->EffectiveTRRMode();
+ mTRRSkipReason = mConnection->TRRSkipReason();
+ mEchConfigUsed = mConnection->GetEchConfigUsed();
+ }
+ }
+
+ // If the timing is enabled, and we are not using a persistent connection
+ // then the requestStart timestamp will be null, so we mark the timestamps
+ // for domainLookupStart/End and connectStart/End
+ // If we are using a persistent connection they will remain null,
+ // and the correct value will be returned in Performance.
+ if (TimingEnabled() && GetRequestStart().IsNull()) {
+ if (status == NS_NET_STATUS_RESOLVING_HOST) {
+ SetDomainLookupStart(TimeStamp::Now(), true);
+ } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
+ SetDomainLookupEnd(TimeStamp::Now());
+ } else if (status == NS_NET_STATUS_CONNECTING_TO) {
+ SetConnectStart(TimeStamp::Now());
+ } else if (status == NS_NET_STATUS_CONNECTED_TO) {
+ TimeStamp tnow = TimeStamp::Now();
+ SetConnectEnd(tnow, true);
+ {
+ MutexAutoLock lock(mLock);
+ mTimings.tcpConnectEnd = tnow;
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) {
+ {
+ MutexAutoLock lock(mLock);
+ mTimings.secureConnectionStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+ SetConnectEnd(TimeStamp::Now(), false);
+ } else if (status == NS_NET_STATUS_SENDING_TO) {
+ // Set the timestamp to Now(), only if it null
+ SetRequestStart(TimeStamp::Now(), true);
+ }
+ }
+
+ if (!mTransportSink) return;
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Need to do this before the STATUS_RECEIVING_FROM check below, to make
+ // sure that the activity distributor gets told about all status events.
+
+ // upon STATUS_WAITING_FOR; report request body sent
+ if ((mHasRequestBody) && (status == NS_NET_STATUS_WAITING_FOR)) {
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, PR_Now(), 0, ""_ns);
+ }
+
+ // report the status and progress
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+ static_cast<uint32_t>(status), PR_Now(), progress, ""_ns);
+
+ // nsHttpChannel synthesizes progress events in OnDataAvailable
+ if (status == NS_NET_STATUS_RECEIVING_FROM) return;
+
+ int64_t progressMax;
+
+ if (status == NS_NET_STATUS_SENDING_TO) {
+ // suppress progress when only writing request headers
+ if (!mHasRequestBody) {
+ LOG1(
+ ("nsHttpTransaction::OnTransportStatus %p "
+ "SENDING_TO without request body\n",
+ this));
+ return;
+ }
+
+ if (mReader) {
+ // A mRequestStream method is on the stack - wait.
+ LOG(
+ ("nsHttpTransaction::OnSocketStatus [this=%p] "
+ "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n",
+ this));
+ // its ok to coalesce several of these into one deferred event
+ mDeferredSendProgress = true;
+ return;
+ }
+
+ nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mRequestStream);
+ if (!tellable) {
+ LOG1(
+ ("nsHttpTransaction::OnTransportStatus %p "
+ "SENDING_TO without tellable request stream\n",
+ this));
+ MOZ_ASSERT(
+ !mRequestStream,
+ "mRequestStream should be tellable as it was wrapped in "
+ "nsBufferedInputStream, which provides the tellable interface even "
+ "when wrapping non-tellable streams.");
+ progress = 0;
+ } else {
+ int64_t prog = 0;
+ tellable->Tell(&prog);
+ progress = prog;
+ }
+
+ // when uploading, we include the request headers in the progress
+ // notifications.
+ progressMax = mRequestSize;
+ } else {
+ progress = 0;
+ progressMax = 0;
+ }
+
+ mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
+}
+
+bool nsHttpTransaction::IsDone() { return mTransactionDone; }
+
+nsresult nsHttpTransaction::Status() { return mStatus; }
+
+uint32_t nsHttpTransaction::Caps() { return mCaps & ~mCapsToClear; }
+
+void nsHttpTransaction::SetDNSWasRefreshed() {
+ MOZ_ASSERT(mConsumerTarget->IsOnCurrentThread(),
+ "SetDNSWasRefreshed on target thread only!");
+ mCapsToClear |= NS_HTTP_REFRESH_DNS;
+}
+
+nsresult nsHttpTransaction::ReadRequestSegment(nsIInputStream* stream,
+ void* closure, const char* buf,
+ uint32_t offset, uint32_t count,
+ uint32_t* countRead) {
+ // For the tracking of sent bytes that we used to do for the networkstats
+ // API, please see bug 1318883 where it was removed.
+
+ nsHttpTransaction* trans = (nsHttpTransaction*)closure;
+ nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
+ if (NS_FAILED(rv)) {
+ trans->MaybeRefreshSecurityInfo();
+ return rv;
+ }
+
+ LOG(("nsHttpTransaction::ReadRequestSegment %p read=%u", trans, *countRead));
+
+ trans->mSentData = true;
+ return NS_OK;
+}
+
+nsresult nsHttpTransaction::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ LOG(("nsHttpTransaction::ReadSegments %p", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mTransactionDone) {
+ *countRead = 0;
+ return mStatus;
+ }
+
+ if (!m0RTTInProgress) {
+ MaybeCancelFallbackTimer();
+ }
+
+ if (!mConnected && !m0RTTInProgress) {
+ mConnected = true;
+ MaybeRefreshSecurityInfo();
+ }
+
+ mDeferredSendProgress = false;
+ mReader = reader;
+ nsresult rv =
+ mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
+ mReader = nullptr;
+
+ if (m0RTTInProgress && (mEarlyDataDisposition == EARLY_NONE) &&
+ NS_SUCCEEDED(rv) && (*countRead > 0)) {
+ LOG(("mEarlyDataDisposition = EARLY_SENT"));
+ mEarlyDataDisposition = EARLY_SENT;
+ }
+
+ if (mDeferredSendProgress && mConnection) {
+ // to avoid using mRequestStream concurrently, OnTransportStatus()
+ // did not report upload status off the ReadSegments() stack from
+ // nsSocketTransport do it now.
+ OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0);
+ }
+ mDeferredSendProgress = false;
+
+ if (mForceRestart) {
+ // The forceRestart condition was dealt with on the stack, but it did not
+ // clear the flag because nsPipe in the readsegment stack clears out
+ // return codes, so we need to use the flag here as a cue to return
+ // ERETARGETED
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_BINDING_RETARGETED;
+ }
+ mForceRestart = false;
+ }
+
+ // if read would block then we need to AsyncWait on the request stream.
+ // have callback occur on socket thread so we stay synchronized.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(mRequestStream);
+ if (asyncIn) {
+ nsCOMPtr<nsIEventTarget> target;
+ Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ asyncIn->AsyncWait(this, 0, 0, target);
+ } else {
+ NS_ERROR("no socket thread event target");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsHttpTransaction::WritePipeSegment(nsIOutputStream* stream,
+ void* closure, char* buf,
+ uint32_t offset, uint32_t count,
+ uint32_t* countWritten) {
+ nsHttpTransaction* trans = (nsHttpTransaction*)closure;
+
+ if (trans->mTransactionDone) return NS_BASE_STREAM_CLOSED; // stop iterating
+
+ if (trans->TimingEnabled()) {
+ // Set the timestamp to Now(), only if it null
+ trans->SetResponseStart(TimeStamp::Now(), true);
+ }
+
+ // Bug 1153929 - add checks to fix windows crash
+ MOZ_ASSERT(trans->mWriter);
+ if (!trans->mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ //
+ // OK, now let the caller fill this segment with data.
+ //
+ rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
+ if (NS_FAILED(rv)) {
+ trans->MaybeRefreshSecurityInfo();
+ return rv; // caller didn't want to write anything
+ }
+
+ LOG(("nsHttpTransaction::WritePipeSegment %p written=%u", trans,
+ *countWritten));
+
+ MOZ_ASSERT(*countWritten > 0, "bad writer");
+ trans->mReceivedData = true;
+ trans->mTransferSize += *countWritten;
+
+ // Let the transaction "play" with the buffer. It is free to modify
+ // the contents of the buffer and/or modify countWritten.
+ // - Bytes in HTTP headers don't count towards countWritten, so the input
+ // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
+ // OnInputStreamReady until all headers have been parsed.
+ //
+ rv = trans->ProcessData(buf, *countWritten, countWritten);
+ if (NS_FAILED(rv)) trans->Close(rv);
+
+ return rv; // failure code only stops WriteSegments; it is not propagated.
+}
+
+bool nsHttpTransaction::ShouldThrottle() {
+ if (mClassOfServiceFlags & nsIClassOfService::DontThrottle) {
+ // We deliberately don't touch the throttling window here since
+ // DontThrottle requests are expected to be long-standing media
+ // streams and would just unnecessarily block running downloads.
+ // If we want to ballance bandwidth for media responses against
+ // running downloads, we need to find something smarter like
+ // changing the suspend/resume throttling intervals at-runtime.
+ return false;
+ }
+
+ if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) {
+ // We are not obligated to throttle
+ return false;
+ }
+
+ if (mContentRead < 16000) {
+ // Let the first bytes go, it may also well be all the content we get
+ LOG(("nsHttpTransaction::ShouldThrottle too few content (%" PRIi64
+ ") this=%p",
+ mContentRead, this));
+ return false;
+ }
+
+ if (!(mClassOfServiceFlags & nsIClassOfService::Throttleable) &&
+ gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)) {
+ LOG(("nsHttpTransaction::ShouldThrottle entry pressure this=%p", this));
+ // This is expensive to check (two hashtable lookups) but may help
+ // freeing connections for active tab transactions.
+ // Checking this only for transactions that are not explicitly marked
+ // as throttleable because trackers and (specially) downloads should
+ // keep throttling even under pressure.
+ return false;
+ }
+
+ return true;
+}
+
+void nsHttpTransaction::DontReuseConnection() {
+ LOG(("nsHttpTransaction::DontReuseConnection %p\n", this));
+ if (!OnSocketThread()) {
+ LOG(("DontReuseConnection %p not on socket thread\n", this));
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsHttpTransaction::DontReuseConnection", this,
+ &nsHttpTransaction::DontReuseConnection);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mConnection) {
+ mConnection->DontReuse();
+ }
+}
+
+nsresult nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("nsHttpTransaction::WriteSegments %p", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mTransactionDone) {
+ return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
+ }
+
+ if (ShouldThrottle()) {
+ if (mThrottlingReadAllowance == THROTTLE_NO_LIMIT) { // no limit set
+ // V1: ThrottlingReadLimit() returns 0
+ mThrottlingReadAllowance = gHttpHandler->ThrottlingReadLimit();
+ }
+ } else {
+ mThrottlingReadAllowance = THROTTLE_NO_LIMIT; // don't limit
+ }
+
+ if (mThrottlingReadAllowance == 0) { // depleted
+ if (gHttpHandler->ConnMgr()->CurrentBrowserId() != mBrowserId) {
+ nsHttp::NotifyActiveTabLoadOptimization();
+ }
+
+ // Must remember that we have to call ResumeRecv() on our connection when
+ // called back by the conn manager to resume reading.
+ LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
+ mReadingStopped = true;
+ // This makes the underlaying connection or stream wait for explicit resume.
+ // For h1 this means we stop reading from the socket.
+ // For h2 this means we stop updating recv window for the stream.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ mWriter = writer;
+
+ if (!mPipeOut) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mThrottlingReadAllowance > 0) {
+ LOG(("nsHttpTransaction::WriteSegments %p limiting read from %u to %d",
+ this, count, mThrottlingReadAllowance));
+ count = std::min(count, static_cast<uint32_t>(mThrottlingReadAllowance));
+ }
+
+ nsresult rv =
+ mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
+
+ mWriter = nullptr;
+
+ if (mForceRestart) {
+ // The forceRestart condition was dealt with on the stack, but it did not
+ // clear the flag because nsPipe in the writesegment stack clears out
+ // return codes, so we need to use the flag here as a cue to return
+ // ERETARGETED
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_BINDING_RETARGETED;
+ }
+ mForceRestart = false;
+ }
+
+ // if pipe would block then we need to AsyncWait on it. have callback
+ // occur on socket thread so we stay synchronized.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ nsCOMPtr<nsIEventTarget> target;
+ Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ mPipeOut->AsyncWait(this, 0, 0, target);
+ mWaitingOnPipeOut = true;
+ } else {
+ NS_ERROR("no socket thread event target");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else if (mThrottlingReadAllowance > 0 && NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(count >= *countWritten);
+ mThrottlingReadAllowance -= *countWritten;
+ }
+
+ return rv;
+}
+
+bool nsHttpTransaction::ProxyConnectFailed() { return mProxyConnectFailed; }
+
+bool nsHttpTransaction::DataSentToChildProcess() { return false; }
+
+already_AddRefed<nsITransportSecurityInfo> nsHttpTransaction::SecurityInfo() {
+ MutexAutoLock lock(mLock);
+ return do_AddRef(mSecurityInfo);
+}
+
+bool nsHttpTransaction::HasStickyConnection() const {
+ return mCaps & NS_HTTP_STICKY_CONNECTION;
+}
+
+bool nsHttpTransaction::ResponseIsComplete() { return mResponseIsComplete; }
+
+int64_t nsHttpTransaction::GetTransferSize() { return mTransferSize; }
+
+int64_t nsHttpTransaction::GetRequestSize() { return mRequestSize; }
+
+bool nsHttpTransaction::IsHttp3Used() { return mIsHttp3Used; }
+
+bool nsHttpTransaction::Http2Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_SPDY;
+}
+
+bool nsHttpTransaction::Http3Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_HTTP3;
+}
+
+already_AddRefed<nsHttpConnectionInfo> nsHttpTransaction::GetConnInfo() const {
+ RefPtr<nsHttpConnectionInfo> connInfo = mConnInfo->Clone();
+ return connInfo.forget();
+}
+
+already_AddRefed<Http2PushedStreamWrapper>
+nsHttpTransaction::TakePushedStreamById(uint32_t aStreamId) {
+ MOZ_ASSERT(mConsumerTarget->IsOnCurrentThread());
+ MOZ_ASSERT(aStreamId);
+
+ auto entry = mIDToStreamMap.Lookup(aStreamId);
+ if (entry) {
+ RefPtr<Http2PushedStreamWrapper> stream = entry.Data();
+ entry.Remove();
+ return stream.forget();
+ }
+
+ return nullptr;
+}
+
+void nsHttpTransaction::OnPush(Http2PushedStreamWrapper* aStream) {
+ LOG(("nsHttpTransaction::OnPush %p aStream=%p", this, aStream));
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(mOnPushCallback);
+ MOZ_ASSERT(mConsumerTarget);
+
+ RefPtr<Http2PushedStreamWrapper> stream = aStream;
+ if (!mConsumerTarget->IsOnCurrentThread()) {
+ RefPtr<nsHttpTransaction> self = this;
+ if (NS_FAILED(mConsumerTarget->Dispatch(
+ NS_NewRunnableFunction("nsHttpTransaction::OnPush",
+ [self, stream]() { self->OnPush(stream); }),
+ NS_DISPATCH_NORMAL))) {
+ stream->OnPushFailed();
+ }
+ return;
+ }
+
+ mIDToStreamMap.WithEntryHandle(stream->StreamID(), [&](auto&& entry) {
+ MOZ_ASSERT(!entry);
+ entry.OrInsert(stream);
+ });
+
+ if (NS_FAILED(mOnPushCallback(stream->StreamID(), stream->GetResourceUrl(),
+ stream->GetRequestString(), this))) {
+ stream->OnPushFailed();
+ mIDToStreamMap.Remove(stream->StreamID());
+ }
+}
+
+nsHttpTransaction* nsHttpTransaction::AsHttpTransaction() { return this; }
+
+HttpTransactionParent* nsHttpTransaction::AsHttpTransactionParent() {
+ return nullptr;
+}
+
+nsHttpTransaction::HTTPSSVC_CONNECTION_FAILED_REASON
+nsHttpTransaction::ErrorCodeToFailedReason(nsresult aErrorCode) {
+ HTTPSSVC_CONNECTION_FAILED_REASON reason = HTTPSSVC_CONNECTION_OTHERS;
+ switch (aErrorCode) {
+ case NS_ERROR_UNKNOWN_HOST:
+ reason = HTTPSSVC_CONNECTION_UNKNOWN_HOST;
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ reason = HTTPSSVC_CONNECTION_UNREACHABLE;
+ break;
+ default:
+ if (m421Received) {
+ reason = HTTPSSVC_CONNECTION_421_RECEIVED;
+ } else if (NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_SECURITY) {
+ reason = HTTPSSVC_CONNECTION_SECURITY_ERROR;
+ }
+ break;
+ }
+ return reason;
+}
+
+bool nsHttpTransaction::PrepareSVCBRecordsForRetry(
+ const nsACString& aFailedDomainName, const nsACString& aFailedAlpn,
+ bool& aAllRecordsHaveEchConfig) {
+ MOZ_ASSERT(mRecordsForRetry.IsEmpty());
+ if (!mHTTPSSVCRecord) {
+ return false;
+ }
+
+ // If we already failed to connect with h3, don't select records that supports
+ // h3.
+ bool noHttp3 = mCaps & NS_HTTP_DISALLOW_HTTP3;
+
+ bool unused;
+ nsTArray<RefPtr<nsISVCBRecord>> records;
+ Unused << mHTTPSSVCRecord->GetAllRecordsWithEchConfig(
+ mCaps & NS_HTTP_DISALLOW_SPDY, noHttp3, &aAllRecordsHaveEchConfig,
+ &unused, records);
+
+ // Note that it's possible that we can't get any usable record here. For
+ // example, when http3 connection is failed, we won't select records with
+ // http3 alpn.
+
+ // If not all records have echConfig, we'll directly fallback to the origin
+ // server.
+ if (!aAllRecordsHaveEchConfig) {
+ return false;
+ }
+
+ // Take the records behind the failed one and put them into mRecordsForRetry.
+ for (const auto& record : records) {
+ nsAutoCString name;
+ record->GetName(name);
+ nsAutoCString alpn;
+ nsresult rv = record->GetSelectedAlpn(alpn);
+
+ if (name == aFailedDomainName) {
+ // If the record has no alpn or the alpn is already tried, we skip this
+ // record.
+ if (NS_FAILED(rv) || alpn == aFailedAlpn) {
+ continue;
+ }
+ }
+
+ mRecordsForRetry.InsertElementAt(0, record);
+ }
+
+ // Set mHTTPSSVCRecord to null to avoid this function being executed twice.
+ mHTTPSSVCRecord = nullptr;
+ return !mRecordsForRetry.IsEmpty();
+}
+
+already_AddRefed<nsHttpConnectionInfo>
+nsHttpTransaction::PrepareFastFallbackConnInfo(bool aEchConfigUsed) {
+ MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo);
+
+ RefPtr<nsHttpConnectionInfo> fallbackConnInfo;
+ nsCOMPtr<nsISVCBRecord> fastFallbackRecord;
+ Unused << mHTTPSSVCRecord->GetServiceModeRecord(
+ mCaps & NS_HTTP_DISALLOW_SPDY, true, getter_AddRefs(fastFallbackRecord));
+
+ if (fastFallbackRecord && aEchConfigUsed) {
+ nsAutoCString echConfig;
+ Unused << fastFallbackRecord->GetEchConfig(echConfig);
+ if (echConfig.IsEmpty()) {
+ fastFallbackRecord = nullptr;
+ }
+ }
+
+ if (!fastFallbackRecord) {
+ if (aEchConfigUsed) {
+ LOG(
+ ("nsHttpTransaction::PrepareFastFallbackConnInfo [this=%p] no record "
+ "can be used",
+ this));
+ return nullptr;
+ }
+
+ if (mOrigConnInfo->IsHttp3()) {
+ mOrigConnInfo->CloneAsDirectRoute(getter_AddRefs(fallbackConnInfo));
+ } else {
+ fallbackConnInfo = mOrigConnInfo;
+ }
+ return fallbackConnInfo.forget();
+ }
+
+ fallbackConnInfo =
+ mOrigConnInfo->CloneAndAdoptHTTPSSVCRecord(fastFallbackRecord);
+ return fallbackConnInfo.forget();
+}
+
+void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
+ LOG(("nsHttpTransaction::PrepareConnInfoForRetry [this=%p reason=%" PRIx32
+ "]",
+ this, static_cast<uint32_t>(aReason)));
+ RefPtr<nsHttpConnectionInfo> failedConnInfo = mConnInfo->Clone();
+ mConnInfo = nullptr;
+ bool echConfigUsed =
+ gHttpHandler->EchConfigEnabled(failedConnInfo->IsHttp3()) &&
+ !failedConnInfo->GetEchConfig().IsEmpty();
+
+ if (mFastFallbackTriggered) {
+ mFastFallbackTriggered = false;
+ MOZ_ASSERT(mBackupConnInfo);
+ mConnInfo.swap(mBackupConnInfo);
+ return;
+ }
+
+ auto useOrigConnInfoToRetry = [&]() {
+ mOrigConnInfo.swap(mConnInfo);
+ if (mConnInfo->IsHttp3() &&
+ ((mCaps & NS_HTTP_DISALLOW_HTTP3) ||
+ gHttpHandler->IsHttp3Excluded(mConnInfo->GetRoutedHost().IsEmpty()
+ ? mConnInfo->GetOrigin()
+ : mConnInfo->GetRoutedHost()))) {
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ }
+ };
+
+ if (!echConfigUsed) {
+ LOG((" echConfig is not used, fallback to origin conn info"));
+ useOrigConnInfoToRetry();
+ return;
+ }
+
+ Telemetry::HistogramID id = Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT;
+ auto updateCount = MakeScopeExit([&] {
+ auto entry = mEchRetryCounterMap.Lookup(id);
+ MOZ_ASSERT(entry, "table not initialized");
+ if (entry) {
+ *entry += 1;
+ }
+ });
+
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) {
+ LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use empty echConfig to retry"));
+ failedConnInfo->SetEchConfig(EmptyCString());
+ failedConnInfo.swap(mConnInfo);
+ id = Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT;
+ return;
+ }
+
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
+ LOG((" Got SSL_ERROR_ECH_RETRY_WITH_ECH, use retry echConfig"));
+ MOZ_ASSERT(mConnection);
+
+ nsCOMPtr<nsITLSSocketControl> socketControl;
+ if (mConnection) {
+ mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
+ }
+ MOZ_ASSERT(socketControl);
+
+ nsAutoCString retryEchConfig;
+ if (socketControl &&
+ NS_SUCCEEDED(socketControl->GetRetryEchConfig(retryEchConfig))) {
+ MOZ_ASSERT(!retryEchConfig.IsEmpty());
+
+ failedConnInfo->SetEchConfig(retryEchConfig);
+ failedConnInfo.swap(mConnInfo);
+ }
+ id = Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT;
+ return;
+ }
+
+ // Note that we retry the connection not only for SSL_ERROR_ECH_FAILED, but
+ // also for all failure cases.
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED) ||
+ NS_FAILED(aReason)) {
+ LOG((" Got SSL_ERROR_ECH_FAILED, try other records"));
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED)) {
+ id = Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT;
+ }
+ if (mRecordsForRetry.IsEmpty()) {
+ if (mHTTPSSVCRecord) {
+ bool allRecordsHaveEchConfig = true;
+ if (!PrepareSVCBRecordsForRetry(failedConnInfo->GetRoutedHost(),
+ failedConnInfo->GetNPNToken(),
+ allRecordsHaveEchConfig)) {
+ LOG(
+ (" Can't find other records with echConfig, "
+ "allRecordsHaveEchConfig=%d",
+ allRecordsHaveEchConfig));
+ if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed() ||
+ !allRecordsHaveEchConfig) {
+ useOrigConnInfoToRetry();
+ }
+ return;
+ }
+ } else {
+ LOG((" No available records to retry"));
+ if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed()) {
+ useOrigConnInfoToRetry();
+ }
+ return;
+ }
+ }
+
+ if (LOG5_ENABLED()) {
+ LOG(("SvcDomainName to retry: ["));
+ for (const auto& r : mRecordsForRetry) {
+ nsAutoCString name;
+ r->GetName(name);
+ nsAutoCString alpn;
+ r->GetSelectedAlpn(alpn);
+ LOG((" name=%s alpn=%s", name.get(), alpn.get()));
+ }
+ LOG(("]"));
+ }
+
+ RefPtr<nsISVCBRecord> recordsForRetry =
+ mRecordsForRetry.PopLastElement().forget();
+ mConnInfo = mOrigConnInfo->CloneAndAdoptHTTPSSVCRecord(recordsForRetry);
+ }
+}
+
+void nsHttpTransaction::MaybeReportFailedSVCDomain(
+ nsresult aReason, nsHttpConnectionInfo* aFailedConnInfo) {
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH) ||
+ aReason != psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
+ ErrorCodeToFailedReason(aReason));
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (dns) {
+ const nsCString& failedHost = aFailedConnInfo->GetRoutedHost().IsEmpty()
+ ? aFailedConnInfo->GetOrigin()
+ : aFailedConnInfo->GetRoutedHost();
+ LOG(("add failed domain name [%s] -> [%s] to exclusion list",
+ aFailedConnInfo->GetOrigin().get(), failedHost.get()));
+ Unused << dns->ReportFailedSVCDomainName(aFailedConnInfo->GetOrigin(),
+ failedHost);
+ }
+}
+
+bool nsHttpTransaction::ShouldRestartOn0RttError(nsresult reason) {
+ LOG(
+ ("nsHttpTransaction::ShouldRestartOn0RttError [this=%p, "
+ "mEarlyDataWasAvailable=%d error=%" PRIx32 "]\n",
+ this, mEarlyDataWasAvailable, static_cast<uint32_t>(reason)));
+ return StaticPrefs::network_http_early_data_disable_on_error() &&
+ mEarlyDataWasAvailable && SecurityErrorThatMayNeedRestart(reason);
+}
+
+static void MaybeRemoveSSLToken(nsITransportSecurityInfo* aSecurityInfo) {
+ if (!StaticPrefs::
+ network_http_remove_resumption_token_when_early_data_failed()) {
+ return;
+ }
+ if (!aSecurityInfo) {
+ return;
+ }
+ nsAutoCString key;
+ aSecurityInfo->GetPeerId(key);
+ nsresult rv = SSLTokensCache::RemoveAll(key);
+ LOG(("RemoveSSLToken [key=%s, rv=%" PRIx32 "]", key.get(),
+ static_cast<uint32_t>(rv)));
+}
+
+void nsHttpTransaction::Close(nsresult reason) {
+ LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ {
+ MutexAutoLock lock(mLock);
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+ }
+
+ if (!mClosed) {
+ gHttpHandler->ConnMgr()->RemoveActiveTransaction(this);
+ mActivated = false;
+ }
+
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ MaybeCancelFallbackTimer();
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (reason == NS_BINDING_RETARGETED) {
+ LOG((" close %p skipped due to ERETARGETED\n", this));
+ return;
+ }
+
+ if (mClosed) {
+ LOG((" already closed\n"));
+ return;
+ }
+
+ NotifyTransactionObserver(reason);
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+
+ // report the reponse is complete if not already reported
+ if (!mResponseIsComplete) {
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
+ static_cast<uint64_t>(mContentRead), ""_ns);
+ }
+
+ // report that this transaction is closing
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0, ""_ns);
+
+ // we must no longer reference the connection! find out if the
+ // connection was being reused before letting it go.
+ bool connReused = false;
+ bool isHttp2or3 = false;
+ if (mConnection) {
+ connReused = mConnection->IsReused();
+ isHttp2or3 = mConnection->Version() >= HttpVersion::v2_0;
+ if (!mConnected) {
+ MaybeRefreshSecurityInfo();
+ }
+ }
+ mConnected = false;
+
+ // When mDoNotRemoveAltSvc is true, this means we want to keep the AltSvc in
+ // in the conncetion info. In this case, let's not apply HTTPS RR retry logic
+ // to make sure this transaction can be restarted with the same conncetion
+ // info.
+ bool shouldRestartTransactionForHTTPSRR =
+ mOrigConnInfo && AllowedErrorForHTTPSRRFallback(reason) &&
+ !mDoNotRemoveAltSvc;
+
+ //
+ // if the connection was reset or closed before we wrote any part of the
+ // request or if we wrote the request but didn't receive any part of the
+ // response and the connection was being reused, then we can (and really
+ // should) assume that we wrote to a stale connection and we must therefore
+ // repeat the request over a new connection.
+ //
+ // We have decided to retry not only in case of the reused connections, but
+ // all safe methods(bug 1236277).
+ //
+ // NOTE: the conditions under which we will automatically retry the HTTP
+ // request have to be carefully selected to avoid duplication of the
+ // request from the point-of-view of the server. such duplication could
+ // have dire consequences including repeated purchases, etc.
+ //
+ // NOTE: because of the way SSL proxy CONNECT is implemented, it is
+ // possible that the transaction may have received data without having
+ // sent any data. for this reason, mSendData == FALSE does not imply
+ // mReceivedData == FALSE. (see bug 203057 for more info.)
+ //
+ // Never restart transactions that are marked as sticky to their conenction.
+ // We use that capability to identify transactions bound to connection based
+ // authentication. Reissuing them on a different connections will break
+ // this bondage. Major issue may arise when there is an NTLM message auth
+ // header on the transaction and we send it to a different NTLM authenticated
+ // connection. It will break that connection and also confuse the channel's
+ // auth provider, beliving the cached credentials are wrong and asking for
+ // the password mistakenly again from the user.
+ if ((reason == NS_ERROR_NET_RESET || reason == NS_OK ||
+ reason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
+ ShouldRestartOn0RttError(reason) ||
+ shouldRestartTransactionForHTTPSRR) &&
+ (!(mCaps & NS_HTTP_STICKY_CONNECTION) ||
+ (mCaps & NS_HTTP_CONNECTION_RESTARTABLE) ||
+ (mEarlyDataDisposition == EARLY_425))) {
+ if (mForceRestart) {
+ SetRestartReason(TRANSACTION_RESTART_FORCED);
+ if (NS_SUCCEEDED(Restart())) {
+ if (mResponseHead) {
+ mResponseHead->Reset();
+ }
+ mContentRead = 0;
+ mContentLength = -1;
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ mHaveStatusLine = false;
+ mHaveAllHeaders = false;
+ mHttpResponseMatched = false;
+ mResponseIsComplete = false;
+ mDidContentStart = false;
+ mNoContent = false;
+ mSentData = false;
+ mReceivedData = false;
+ mSupportsHTTP3 = false;
+ LOG(("transaction force restarted\n"));
+ return;
+ }
+ }
+
+ mDoNotTryEarlyData = true;
+
+ // reallySentData is meant to separate the instances where data has
+ // been sent by this transaction but buffered at a higher level while
+ // a TLS session (perhaps via a tunnel) is setup.
+ bool reallySentData =
+ mSentData && (!mConnection || mConnection->BytesWritten());
+
+ // If this is true, it means we failed to use the HTTPSSVC connection info
+ // to connect to the server. We need to retry with the original connection
+ // info.
+ shouldRestartTransactionForHTTPSRR &= !reallySentData;
+
+ if (reason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
+ reason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT) ||
+ (!mReceivedData && ((mRequestHead && mRequestHead->IsSafeMethod()) ||
+ !reallySentData || connReused)) ||
+ shouldRestartTransactionForHTTPSRR) {
+ if (shouldRestartTransactionForHTTPSRR) {
+ MaybeReportFailedSVCDomain(reason, mConnInfo);
+ PrepareConnInfoForRetry(reason);
+ mDontRetryWithDirectRoute = true;
+ LOG(
+ ("transaction will be restarted with the fallback connection info "
+ "key=%s",
+ mConnInfo ? mConnInfo->HashKey().get() : "None"));
+ }
+
+ if (shouldRestartTransactionForHTTPSRR) {
+ auto toRestartReason =
+ [](nsresult aStatus) -> TRANSACTION_RESTART_REASON {
+ if (aStatus == NS_ERROR_NET_RESET) {
+ return TRANSACTION_RESTART_HTTPS_RR_NET_RESET;
+ }
+ if (aStatus == NS_ERROR_CONNECTION_REFUSED) {
+ return TRANSACTION_RESTART_HTTPS_RR_CONNECTION_REFUSED;
+ }
+ if (aStatus == NS_ERROR_UNKNOWN_HOST) {
+ return TRANSACTION_RESTART_HTTPS_RR_UNKNOWN_HOST;
+ }
+ if (aStatus == NS_ERROR_NET_TIMEOUT) {
+ return TRANSACTION_RESTART_HTTPS_RR_NET_TIMEOUT;
+ }
+ if (psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aStatus))) {
+ return TRANSACTION_RESTART_HTTPS_RR_SEC_ERROR;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unexpected reason");
+ return TRANSACTION_RESTART_OTHERS;
+ };
+ SetRestartReason(toRestartReason(reason));
+ } else if (!reallySentData) {
+ SetRestartReason(TRANSACTION_RESTART_NO_DATA_SENT);
+ } else if (reason == psm::GetXPCOMFromNSSError(
+ SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) {
+ SetRestartReason(TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA);
+ } else if (reason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) {
+ SetRestartReason(TRANSACTION_RESTART_PROTOCOL_VERSION_ALERT);
+ }
+ // if restarting fails, then we must proceed to close the pipe,
+ // which will notify the channel that the transaction failed.
+ // Note that when echConfig is enabled, it's possible that we don't have a
+ // usable connection info to retry.
+ if (mConnInfo && NS_SUCCEEDED(Restart())) {
+ return;
+ }
+ // mConnInfo could be set to null in PrepareConnInfoForRetry() when we
+ // can't find an available https rr to retry. We have to set mConnInfo
+ // back to mOrigConnInfo to make sure no crash when mConnInfo being
+ // accessed again.
+ if (!mConnInfo) {
+ mConnInfo.swap(mOrigConnInfo);
+ MOZ_ASSERT(mConnInfo);
+ }
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ mRestartReason);
+
+ if (!mResponseIsComplete && NS_SUCCEEDED(reason) && isHttp2or3) {
+ // Responses without content-length header field are still complete if
+ // they are transfered over http2 or http3 and the stream is properly
+ // closed.
+ mResponseIsComplete = true;
+ }
+
+ if ((mChunkedDecoder || (mContentLength >= int64_t(0))) &&
+ (NS_SUCCEEDED(reason) && !mResponseIsComplete)) {
+ NS_WARNING("Partial transfer, incomplete HTTP response received");
+
+ if ((mHttpResponseCode / 100 == 2) && (mHttpVersion >= HttpVersion::v1_1)) {
+ FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing();
+ if (clevel >= FRAMECHECK_BARELY) {
+ // If clevel == FRAMECHECK_STRICT mark any incomplete response as
+ // partial.
+ // if clevel == FRAMECHECK_BARELY: 1) mark a chunked-encoded response
+ // that do not ends on exactly a chunk boundary as partial; We are not
+ // strict about the last 0-size chunk and do not mark as parial
+ // responses that do not have the last 0-size chunk but do end on a
+ // chunk boundary. (check mChunkedDecoder->GetChunkRemaining() != 0)
+ // 2) mark a transfer that is partial and it is not chunk-encoded or
+ // gzip-encoded or other content-encoding as partial. (check
+ // !mChunkedDecoder && !mContentDecoding && mContentDecodingCheck))
+ // if clevel == FRAMECHECK_STRICT_CHUNKED mark a chunked-encoded
+ // response that ends on exactly a chunk boundary also as partial.
+ // Here a response must have the last 0-size chunk.
+ if ((clevel == FRAMECHECK_STRICT) ||
+ (mChunkedDecoder && (mChunkedDecoder->GetChunkRemaining() ||
+ (clevel == FRAMECHECK_STRICT_CHUNKED))) ||
+ (!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck)) {
+ reason = NS_ERROR_NET_PARTIAL_TRANSFER;
+ LOG(("Partial transfer, incomplete HTTP response received: %s",
+ mChunkedDecoder ? "broken chunk" : "c-l underrun"));
+ }
+ }
+ }
+
+ if (mConnection) {
+ // whether or not we generate an error for the transaction
+ // bad framing means we don't want a pconn
+ mConnection->DontReuse();
+ }
+ }
+
+ bool relConn = true;
+ if (NS_SUCCEEDED(reason)) {
+ // the server has not sent the final \r\n terminating the header
+ // section, and there may still be a header line unparsed. let's make
+ // sure we parse the remaining header line, and then hopefully, the
+ // response will be usable (see bug 88792).
+ if (!mHaveAllHeaders) {
+ char data[] = "\n\n";
+ uint32_t unused = 0;
+ // If we have a partial line already, we actually need two \ns to finish
+ // the headers section.
+ Unused << ParseHead(data, mLineBuf.IsEmpty() ? 1 : 2, &unused);
+
+ if (mResponseHead->Version() == HttpVersion::v0_9) {
+ // Reject 0 byte HTTP/0.9 Responses - bug 423506
+ LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this));
+ reason = NS_ERROR_NET_RESET;
+ }
+ }
+
+ // honor the sticky connection flag...
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ LOG((" keeping the connection because of STICKY_CONNECTION flag"));
+ relConn = false;
+ }
+
+ // if the proxy connection has failed, we want the connection be held
+ // to allow the upper layers (think nsHttpChannel) to close it when
+ // the failure is unrecoverable.
+ // we can't just close it here, because mProxyConnectFailed is to a general
+ // flag and is also set for e.g. 407 which doesn't mean to kill the
+ // connection, specifically when connection oriented auth may be involved.
+ if (mProxyConnectFailed) {
+ LOG((" keeping the connection because of mProxyConnectFailed"));
+ relConn = false;
+ }
+
+ // Use mOrigConnInfo as an indicator that this transaction is completed
+ // successfully with an HTTPSSVC record.
+ if (mOrigConnInfo) {
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
+ HTTPSSVC_CONNECTION_OK);
+ }
+ }
+
+ // mTimings.responseEnd is normally recorded based on the end of a
+ // HTTP delimiter such as chunked-encodings or content-length. However,
+ // EOF or an error still require an end time be recorded.
+ if (TimingEnabled()) {
+ const TimingStruct timings = Timings();
+ if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) {
+ SetResponseEnd(TimeStamp::Now());
+ }
+
+ // Accumulate download throughput telemetry
+ const int64_t TELEMETRY_DOWNLOAD_SIZE_GREATER_THAN_10MB =
+ (int64_t)10 * (int64_t)(1 << 20);
+ if ((mContentRead > TELEMETRY_DOWNLOAD_SIZE_GREATER_THAN_10MB) &&
+ !timings.requestStart.IsNull() && !timings.responseEnd.IsNull()) {
+ TimeDuration elapsed = timings.responseEnd - timings.requestStart;
+ double megabits = static_cast<double>(mContentRead) * 8.0 / 1000000.0;
+ uint32_t mpbs = static_cast<uint32_t>(megabits / elapsed.ToSeconds());
+
+ switch (mHttpVersion) {
+ case HttpVersion::v1_0:
+ case HttpVersion::v1_1:
+ glean::networking::http_1_download_throughput.AccumulateSamples(
+ {mpbs});
+ break;
+ case HttpVersion::v2_0:
+ glean::networking::http_2_download_throughput.AccumulateSamples(
+ {mpbs});
+ break;
+ case HttpVersion::v3_0:
+ glean::networking::http_3_download_throughput.AccumulateSamples(
+ {mpbs});
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->AccumulateHttpTransferredSize(mTrafficCategory, mTransferSize,
+ mContentRead);
+ }
+ }
+
+ if (mThroughCaptivePortal) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_HTTP_TRANSACTIONS_CAPTIVE_PORTAL, 1);
+ }
+
+ if (relConn && mConnection) {
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+ }
+
+ if (isHttp2or3 &&
+ reason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) {
+ // Change reason to NS_ERROR_ABORT, so we avoid showing a missleading
+ // error page tthat TLS1.0 is disabled. H2 or H3 is used here so the
+ // TLS version is not a problem.
+ reason = NS_ERROR_ABORT;
+ }
+ mStatus = reason;
+ mTransactionDone = true; // forcibly flag the transaction as complete
+ mClosed = true;
+ if (mResolver) {
+ mResolver->Close();
+ mResolver = nullptr;
+ }
+ ReleaseBlockingTransaction();
+
+ // release some resources that we no longer need
+ mRequestStream = nullptr;
+ mReqHeaderBuf.Truncate();
+ mLineBuf.Truncate();
+ if (mChunkedDecoder) {
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ }
+
+ for (const auto& entry : mEchRetryCounterMap) {
+ Telemetry::Accumulate(static_cast<Telemetry::HistogramID>(entry.GetKey()),
+ entry.GetData());
+ }
+
+ // closing this pipe triggers the channel's OnStopRequest method.
+ mPipeOut->CloseWithStatus(reason);
+}
+
+nsHttpConnectionInfo* nsHttpTransaction::ConnectionInfo() {
+ return mConnInfo.get();
+}
+
+bool // NOTE BASE CLASS
+nsAHttpTransaction::ResponseTimeoutEnabled() const {
+ return false;
+}
+
+PRIntervalTime // NOTE BASE CLASS
+nsAHttpTransaction::ResponseTimeout() {
+ return gHttpHandler->ResponseTimeout();
+}
+
+bool nsHttpTransaction::ResponseTimeoutEnabled() const {
+ return mResponseTimeoutEnabled;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <private>
+//-----------------------------------------------------------------------------
+
+static inline void RemoveAlternateServiceUsedHeader(
+ nsHttpRequestHead* aRequestHead) {
+ if (aRequestHead) {
+ DebugOnly<nsresult> rv =
+ aRequestHead->SetHeader(nsHttp::Alternate_Service_Used, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void nsHttpTransaction::SetRestartReason(TRANSACTION_RESTART_REASON aReason) {
+ if (mRestartReason == TRANSACTION_RESTART_NONE) {
+ mRestartReason = aReason;
+ }
+}
+
+nsresult nsHttpTransaction::Restart() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // limit the number of restart attempts - bug 92224
+ if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
+ LOG(("reached max request attempts, failing transaction @%p\n", this));
+ return NS_ERROR_NET_RESET;
+ }
+
+ LOG(("restarting transaction @%p\n", this));
+
+ if (mRequestHead) {
+ // Dispatching on a new connection better w/o an ambient connection proxy
+ // auth request header to not confuse the proxy authenticator.
+ nsAutoCString proxyAuth;
+ if (NS_SUCCEEDED(
+ mRequestHead->GetHeader(nsHttp::Proxy_Authorization, proxyAuth)) &&
+ IsStickyAuthSchemeAt(proxyAuth)) {
+ Unused << mRequestHead->ClearHeader(nsHttp::Proxy_Authorization);
+ }
+ }
+
+ // rewind streams in case we already wrote out the request
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (seekable) seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+ if (mDoNotTryEarlyData) {
+ MutexAutoLock lock(mLock);
+ MaybeRemoveSSLToken(mSecurityInfo);
+ }
+
+ // clear old connection state...
+ {
+ MutexAutoLock lock(mLock);
+ mSecurityInfo = nullptr;
+ }
+
+ if (mConnection) {
+ if (!mReuseOnRestart) {
+ mConnection->DontReuse();
+ }
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+ }
+
+ // Reset this to our default state, since this may change from one restart
+ // to the next
+ mReuseOnRestart = false;
+
+ if (!mDoNotRemoveAltSvc &&
+ (!mConnInfo->GetRoutedHost().IsEmpty() || mConnInfo->IsHttp3()) &&
+ !mDontRetryWithDirectRoute) {
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ RemoveAlternateServiceUsedHeader(mRequestHead);
+ }
+
+ // Reset mDoNotRemoveAltSvc for the next try.
+ mDoNotRemoveAltSvc = false;
+ mEarlyDataWasAvailable = false;
+ mRestarted = true;
+
+ // If we weren't trying to do 'proper' ECH, disable ECH GREASE when retrying.
+ if (mConnInfo->GetEchConfig().IsEmpty() &&
+ StaticPrefs::security_tls_ech_disable_grease_on_fallback()) {
+ mCaps |= NS_HTTP_DISALLOW_ECH;
+ }
+
+ mCaps |= NS_HTTP_IS_RETRY;
+
+ // Use TRANSACTION_RESTART_OTHERS as a catch-all.
+ SetRestartReason(TRANSACTION_RESTART_OTHERS);
+
+ if (!mDoNotResetIPFamilyPreference) {
+ // Reset the IP family preferences, so the new connection can try to use
+ // another IPv4 or IPv6 address.
+ gHttpHandler->ConnMgr()->ResetIPFamilyPreference(mConnInfo);
+ }
+
+ return gHttpHandler->InitiateTransaction(this, mPriority);
+}
+
+bool nsHttpTransaction::TakeRestartedState() {
+ // This return true if the transaction has been restarted internally. Used to
+ // let the consuming nsHttpChannel reset proxy authentication. The flag is
+ // reset to false by this method.
+ return mRestarted.exchange(false);
+}
+
+char* nsHttpTransaction::LocateHttpStart(char* buf, uint32_t len,
+ bool aAllowPartialMatch) {
+ MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty());
+
+ static const char HTTPHeader[] = "HTTP/1.";
+ static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1;
+ static const char HTTP2Header[] = "HTTP/2";
+ static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1;
+ static const char HTTP3Header[] = "HTTP/3";
+ static const uint32_t HTTP3HeaderLen = sizeof(HTTP3Header) - 1;
+ // ShoutCast ICY is treated as HTTP/1.0
+ static const char ICYHeader[] = "ICY ";
+ static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1;
+
+ if (aAllowPartialMatch && (len < HTTPHeaderLen)) {
+ return (nsCRT::strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr;
+ }
+
+ // mLineBuf can contain partial match from previous search
+ if (!mLineBuf.IsEmpty()) {
+ MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen);
+ int32_t checkChars =
+ std::min<uint32_t>(len, HTTPHeaderLen - mLineBuf.Length());
+ if (nsCRT::strncasecmp(buf, HTTPHeader + mLineBuf.Length(), checkChars) ==
+ 0) {
+ mLineBuf.Append(buf, checkChars);
+ if (mLineBuf.Length() == HTTPHeaderLen) {
+ // We've found whole HTTPHeader sequence. Return pointer at the
+ // end of matched sequence since it is stored in mLineBuf.
+ return (buf + checkChars);
+ }
+ // Response matches pattern but is still incomplete.
+ return nullptr;
+ }
+ // Previous partial match together with new data doesn't match the
+ // pattern. Start the search again.
+ mLineBuf.Truncate();
+ }
+
+ bool firstByte = true;
+ while (len > 0) {
+ if (nsCRT::strncasecmp(buf, HTTPHeader,
+ std::min<uint32_t>(len, HTTPHeaderLen)) == 0) {
+ if (len < HTTPHeaderLen) {
+ // partial HTTPHeader sequence found
+ // save partial match to mLineBuf
+ mLineBuf.Assign(buf, len);
+ return nullptr;
+ }
+
+ // whole HTTPHeader sequence found
+ return buf;
+ }
+
+ // At least "SmarterTools/2.0.3974.16813" generates nonsensical
+ // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of
+ // it as HTTP/1.1 to be compatible with old versions of ourselves and
+ // other browsers
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen &&
+ (nsCRT::strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));
+ return buf;
+ }
+
+ // HTTP/3.0 responses to our HTTP/1 requests. Treat the minimal case of
+ // it as HTTP/1.1 to be compatible with old versions of ourselves and
+ // other browsers
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= HTTP3HeaderLen &&
+ (nsCRT::strncasecmp(buf, HTTP3Header, HTTP3HeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified HTTP/3.0 treating as 1.x\n"));
+ return buf;
+ }
+
+ // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion
+ // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted
+ // as HTTP/1.0 in nsHttpResponseHead::ParseVersion
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen &&
+ (nsCRT::strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n"));
+ return buf;
+ }
+
+ if (!nsCRT::IsAsciiSpace(*buf)) firstByte = false;
+ buf++;
+ len--;
+ }
+ return nullptr;
+}
+
+nsresult nsHttpTransaction::ParseLine(nsACString& line) {
+ LOG1(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line).get()));
+ nsresult rv = NS_OK;
+
+ if (!mHaveStatusLine) {
+ rv = mResponseHead->ParseStatusLine(line);
+ if (NS_SUCCEEDED(rv)) {
+ mHaveStatusLine = true;
+ }
+ // XXX this should probably never happen
+ if (mResponseHead->Version() == HttpVersion::v0_9) mHaveAllHeaders = true;
+ } else {
+ rv = mResponseHead->ParseHeaderLine(line);
+ }
+ return rv;
+}
+
+nsresult nsHttpTransaction::ParseLineSegment(char* segment, uint32_t len) {
+ MOZ_ASSERT(!mHaveAllHeaders, "already have all headers");
+
+ if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
+ // trim off the new line char, and if this segment is
+ // not a continuation of the previous or if we haven't
+ // parsed the status line yet, then parse the contents
+ // of mLineBuf.
+ mLineBuf.Truncate(mLineBuf.Length() - 1);
+ if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
+ nsresult rv = ParseLine(mLineBuf);
+ mLineBuf.Truncate();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ // append segment to mLineBuf...
+ mLineBuf.Append(segment, len);
+
+ // a line buf with only a new line char signifies the end of headers.
+ if (mLineBuf.First() == '\n') {
+ mLineBuf.Truncate();
+ // discard this response if it is a 100 continue or other 1xx status.
+ uint16_t status = mResponseHead->Status();
+ if (status == 103) {
+ nsCString linkHeader;
+ nsresult rv = mResponseHead->GetHeader(nsHttp::Link, linkHeader);
+
+ nsCString referrerPolicy;
+ Unused << mResponseHead->GetHeader(nsHttp::Referrer_Policy,
+ referrerPolicy);
+
+ if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) {
+ nsCString cspHeader;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Security_Policy,
+ cspHeader);
+
+ nsCOMPtr<nsIEarlyHintObserver> earlyHint;
+ {
+ MutexAutoLock lock(mLock);
+ earlyHint = mEarlyHintObserver;
+ }
+ if (earlyHint) {
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "nsIEarlyHintObserver->EarlyHint",
+ [obs{std::move(earlyHint)}, header{std::move(linkHeader)},
+ referrerPolicy{std::move(referrerPolicy)},
+ cspHeader{std::move(cspHeader)}]() {
+ obs->EarlyHint(header, referrerPolicy, cspHeader);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ }
+ if ((status != 101) && (status / 100 == 1)) {
+ LOG(("ignoring 1xx response except 101 and 103\n"));
+ mHaveStatusLine = false;
+ mHttpResponseMatched = false;
+ mConnection->SetLastTransactionExpectedNoContent(true);
+ mResponseHead->Reset();
+ return NS_OK;
+ }
+ if (!mConnection->IsProxyConnectInProgress()) {
+ MutexAutoLock lock(mLock);
+ mEarlyHintObserver = nullptr;
+ }
+ mHaveAllHeaders = true;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpTransaction::ParseHead(char* buf, uint32_t count,
+ uint32_t* countRead) {
+ nsresult rv;
+ uint32_t len;
+ char* eol;
+
+ LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
+
+ *countRead = 0;
+
+ MOZ_ASSERT(!mHaveAllHeaders, "oops");
+
+ // allocate the response head object if necessary
+ if (!mResponseHead) {
+ mResponseHead = new nsHttpResponseHead();
+ if (!mResponseHead) return NS_ERROR_OUT_OF_MEMORY;
+
+ // report that we have a least some of the response
+ if (!mReportedStart) {
+ mReportedStart = true;
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, PR_Now(), 0, ""_ns);
+ }
+ }
+
+ if (!mHttpResponseMatched) {
+ // Normally we insist on seeing HTTP/1.x in the first few bytes,
+ // but if we are on a persistent connection and the previous transaction
+ // was not supposed to have any content then we need to be prepared
+ // to skip over a response body that the server may have sent even
+ // though it wasn't allowed.
+ if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) {
+ // tolerate only minor junk before the status line
+ mHttpResponseMatched = true;
+ char* p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true);
+ if (!p) {
+ // Treat any 0.9 style response of a put as a failure.
+ if (mRequestHead->IsPut()) return NS_ERROR_ABORT;
+
+ if (NS_FAILED(mResponseHead->ParseStatusLine(""_ns))) {
+ return NS_ERROR_FAILURE;
+ }
+ mHaveStatusLine = true;
+ mHaveAllHeaders = true;
+ return NS_OK;
+ }
+ if (p > buf) {
+ // skip over the junk
+ mInvalidResponseBytesRead += p - buf;
+ *countRead = p - buf;
+ buf = p;
+ }
+ } else {
+ char* p = LocateHttpStart(buf, count, false);
+ if (p) {
+ mInvalidResponseBytesRead += p - buf;
+ *countRead = p - buf;
+ buf = p;
+ mHttpResponseMatched = true;
+ } else {
+ mInvalidResponseBytesRead += count;
+ *countRead = count;
+ if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) {
+ LOG(
+ ("nsHttpTransaction::ParseHead() "
+ "Cannot find Response Header\n"));
+ // cannot go back and call this 0.9 anymore as we
+ // have thrown away a lot of the leading junk
+ return NS_ERROR_ABORT;
+ }
+ return NS_OK;
+ }
+ }
+ }
+ // otherwise we can assume that we don't have a HTTP/0.9 response.
+
+ MOZ_ASSERT(mHttpResponseMatched);
+ while ((eol = static_cast<char*>(memchr(buf, '\n', count - *countRead))) !=
+ nullptr) {
+ // found line in range [buf:eol]
+ len = eol - buf + 1;
+
+ *countRead += len;
+
+ // actually, the line is in the range [buf:eol-1]
+ if ((eol > buf) && (*(eol - 1) == '\r')) len--;
+
+ buf[len - 1] = '\n';
+ rv = ParseLineSegment(buf, len);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mHaveAllHeaders) return NS_OK;
+
+ // skip over line
+ buf = eol + 1;
+
+ if (!mHttpResponseMatched) {
+ // a 100 class response has caused us to throw away that set of
+ // response headers and look for the next response
+ return NS_ERROR_NET_INTERRUPT;
+ }
+ }
+
+ // do something about a partial header line
+ if (!mHaveAllHeaders && (len = count - *countRead)) {
+ *countRead = count;
+ // ignore a trailing carriage return, and don't bother calling
+ // ParseLineSegment if buf only contains a carriage return.
+ if ((buf[len - 1] == '\r') && (--len == 0)) return NS_OK;
+ rv = ParseLineSegment(buf, len);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+bool nsHttpTransaction::HandleWebTransportResponse(uint16_t aStatus) {
+ MOZ_ASSERT(mIsForWebTransport);
+ if (!(aStatus >= 200 && aStatus < 300)) {
+ return false;
+ }
+
+ RefPtr<Http3WebTransportSession> wtSession =
+ mConnection->GetWebTransportSession(this);
+ if (!wtSession) {
+ return false;
+ }
+
+ nsCOMPtr<WebTransportSessionEventListener> webTransportListener;
+ {
+ MutexAutoLock lock(mLock);
+ webTransportListener = mWebTransportSessionEventListener;
+ mWebTransportSessionEventListener = nullptr;
+ }
+ if (webTransportListener) {
+ webTransportListener->OnSessionReadyInternal(wtSession);
+ wtSession->SetWebTransportSessionEventListener(webTransportListener);
+ }
+
+ return true;
+}
+
+nsresult nsHttpTransaction::HandleContentStart() {
+ LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mResponseHead) {
+ if (mEarlyDataDisposition == EARLY_ACCEPTED) {
+ if (mResponseHead->Status() == 425) {
+ // We will report this state when the final responce arrives.
+ mEarlyDataDisposition = EARLY_425;
+ } else {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
+ "accepted"_ns);
+ }
+ } else if (mEarlyDataDisposition == EARLY_SENT) {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
+ "sent"_ns);
+ } else if (mEarlyDataDisposition == EARLY_425) {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
+ "received 425"_ns);
+ mEarlyDataDisposition = EARLY_NONE;
+ } // no header on NONE case
+
+ if (LOG3_ENABLED()) {
+ LOG3(("http response [\n"));
+ nsAutoCString headers;
+ mResponseHead->Flatten(headers, false);
+ headers.AppendLiteral(" OriginalHeaders");
+ headers.AppendLiteral("\r\n");
+ mResponseHead->FlattenNetworkOriginalHeaders(headers);
+ LogHeaders(headers.get());
+ LOG3(("]\n"));
+ }
+
+ CheckForStickyAuthScheme();
+
+ // Save http version, mResponseHead isn't available anymore after
+ // TakeResponseHead() is called
+ mHttpVersion = mResponseHead->Version();
+ mHttpResponseCode = mResponseHead->Status();
+
+ // notify the connection, give it a chance to cause a reset.
+ bool reset = false;
+ nsresult rv = mConnection->OnHeadersAvailable(this, mRequestHead,
+ mResponseHead, &reset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // looks like we should ignore this response, resetting...
+ if (reset) {
+ LOG(("resetting transaction's response head\n"));
+ mHaveAllHeaders = false;
+ mHaveStatusLine = false;
+ mReceivedData = false;
+ mSentData = false;
+ mHttpResponseMatched = false;
+ mResponseHead->Reset();
+ // wait to be called again...
+ return NS_OK;
+ }
+
+ bool responseChecked = false;
+ if (mIsForWebTransport) {
+ responseChecked = HandleWebTransportResponse(mResponseHead->Status());
+ LOG(("HandleWebTransportResponse res=%d", responseChecked));
+ if (responseChecked) {
+ mNoContent = true;
+ mPreserveStream = true;
+ }
+ }
+
+ if (!responseChecked) {
+ // check if this is a no-content response
+ switch (mResponseHead->Status()) {
+ case 101:
+ mPreserveStream = true;
+ [[fallthrough]]; // to other no content cases:
+ case 204:
+ case 205:
+ case 304:
+ mNoContent = true;
+ LOG(("this response should not contain a body.\n"));
+ break;
+ case 408:
+ LOG(("408 Server Timeouts"));
+
+ if (mConnection->Version() >= HttpVersion::v2_0) {
+ mForceRestart = true;
+ return NS_ERROR_NET_RESET;
+ }
+
+ // If this error could be due to a persistent connection
+ // reuse then we pass an error code of NS_ERROR_NET_RESET
+ // to trigger the transaction 'restart' mechanism. We
+ // tell it to reset its response headers so that it will
+ // be ready to receive the new response.
+ LOG(("408 Server Timeouts now=%d lastWrite=%d", PR_IntervalNow(),
+ mConnection->LastWriteTime()));
+ if ((PR_IntervalNow() - mConnection->LastWriteTime()) >=
+ PR_MillisecondsToInterval(1000)) {
+ mForceRestart = true;
+ return NS_ERROR_NET_RESET;
+ }
+ break;
+ case 421:
+ LOG(("Misdirected Request.\n"));
+ gHttpHandler->ClearHostMapping(mConnInfo);
+
+ m421Received = true;
+ mCaps |= NS_HTTP_REFRESH_DNS;
+
+ // retry on a new connection - just in case
+ // See bug 1609410, we can't restart the transaction when
+ // NS_HTTP_STICKY_CONNECTION is set. In the case that a connection
+ // already passed NTLM authentication, restarting the transaction will
+ // cause the connection to be closed.
+ if (!mRestartCount && !(mCaps & NS_HTTP_STICKY_CONNECTION)) {
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+ mForceRestart = true; // force restart has built in loop protection
+ return NS_ERROR_NET_RESET;
+ }
+ break;
+ case 425:
+ LOG(("Too Early."));
+ if ((mEarlyDataDisposition == EARLY_425) && !mDoNotTryEarlyData) {
+ mDoNotTryEarlyData = true;
+ mForceRestart = true; // force restart has built in loop protection
+ if (mConnection->Version() >= HttpVersion::v2_0) {
+ mReuseOnRestart = true;
+ }
+ return NS_ERROR_NET_RESET;
+ }
+ break;
+ }
+ }
+
+ // Remember whether HTTP3 is supported
+ mSupportsHTTP3 = nsHttpHandler::IsHttp3SupportedByServer(mResponseHead);
+
+ CollectTelemetryForUploads();
+
+ // Report telemetry
+ if (mSupportsHTTP3) {
+ Accumulate(Telemetry::TRANSACTION_WAIT_TIME_HTTP2_SUP_HTTP3,
+ mPendingDurationTime.ToMilliseconds());
+ }
+
+ // If we're only connecting then we're going to be upgrading this
+ // connection since we were successful. Any data from now on belongs to
+ // the upgrade handler. If we're not successful the content body doesn't
+ // matter. Proxy http errors are treated as network errors. This
+ // connection won't be reused since it's marked sticky and no
+ // keep-alive.
+ if (mCaps & NS_HTTP_CONNECT_ONLY) {
+ MOZ_ASSERT(!(mCaps & NS_HTTP_ALLOW_KEEPALIVE) &&
+ (mCaps & NS_HTTP_STICKY_CONNECTION),
+ "connection should be sticky and no keep-alive");
+ // The transaction will expect the server to close the socket if
+ // there's no content length instead of doing the upgrade.
+ mNoContent = true;
+ }
+
+ // preserve connection for tunnel setup - h2 websocket upgrade only
+ if (mIsHttp2Websocket && mResponseHead->Status() == 200) {
+ LOG(("nsHttpTransaction::HandleContentStart websocket upgrade resp 200"));
+ mNoContent = true;
+ }
+
+ if (mResponseHead->Status() == 200 &&
+ mConnection->IsProxyConnectInProgress()) {
+ // successful CONNECTs do not have response bodies
+ mNoContent = true;
+ }
+ mConnection->SetLastTransactionExpectedNoContent(mNoContent);
+
+ if (mNoContent) {
+ mContentLength = 0;
+ } else {
+ // grab the content-length from the response headers
+ mContentLength = mResponseHead->ContentLength();
+
+ // handle chunked encoding here, so we'll know immediately when
+ // we're done with the socket. please note that _all_ other
+ // decoding is done when the channel receives the content data
+ // so as not to block the socket transport thread too much.
+ if (mResponseHead->Version() >= HttpVersion::v1_0 &&
+ mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
+ // we only support the "chunked" transfer encoding right now.
+ mChunkedDecoder = new nsHttpChunkedDecoder();
+ LOG(("nsHttpTransaction %p chunked decoder created\n", this));
+ // Ignore server specified Content-Length.
+ if (mContentLength != int64_t(-1)) {
+ LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n", this));
+ mContentLength = -1;
+ if (mConnection) {
+ mConnection->DontReuse();
+ }
+ }
+ } else if (mContentLength == int64_t(-1)) {
+ LOG(("waiting for the server to close the connection.\n"));
+ }
+ }
+ }
+
+ mDidContentStart = true;
+ return NS_OK;
+}
+
+// called on the socket thread
+nsresult nsHttpTransaction::HandleContent(char* buf, uint32_t count,
+ uint32_t* contentRead,
+ uint32_t* contentRemaining) {
+ nsresult rv;
+
+ LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count));
+
+ *contentRead = 0;
+ *contentRemaining = 0;
+
+ MOZ_ASSERT(mConnection);
+
+ if (!mDidContentStart) {
+ rv = HandleContentStart();
+ if (NS_FAILED(rv)) return rv;
+ // Do not write content to the pipe if we haven't started streaming yet
+ if (!mDidContentStart) return NS_OK;
+ }
+
+ if (mChunkedDecoder) {
+ // give the buf over to the chunked decoder so it can reformat the
+ // data and tell us how much is really there.
+ rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead,
+ contentRemaining);
+ if (NS_FAILED(rv)) return rv;
+ } else if (mContentLength >= int64_t(0)) {
+ // HTTP/1.0 servers have been known to send erroneous Content-Length
+ // headers. So, unless the connection is persistent, we must make
+ // allowances for a possibly invalid Content-Length header. Thus, if
+ // NOT persistent, we simply accept everything in |buf|.
+ if (mConnection->IsPersistent() || mPreserveStream ||
+ mHttpVersion >= HttpVersion::v1_1) {
+ int64_t remaining = mContentLength - mContentRead;
+ *contentRead = uint32_t(std::min<int64_t>(count, remaining));
+ *contentRemaining = count - *contentRead;
+ } else {
+ *contentRead = count;
+ // mContentLength might need to be increased...
+ int64_t position = mContentRead + int64_t(count);
+ if (position > mContentLength) {
+ mContentLength = position;
+ // mResponseHead->SetContentLength(mContentLength);
+ }
+ }
+ } else {
+ // when we are just waiting for the server to close the connection...
+ // (no explicit content-length given)
+ *contentRead = count;
+ }
+
+ if (*contentRead) {
+ // update count of content bytes read and report progress...
+ mContentRead += *contentRead;
+ }
+
+ LOG1(
+ ("nsHttpTransaction::HandleContent [this=%p count=%u read=%u "
+ "mContentRead=%" PRId64 " mContentLength=%" PRId64 "]\n",
+ this, count, *contentRead, mContentRead, mContentLength));
+
+ // check for end-of-file
+ if ((mContentRead == mContentLength) ||
+ (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
+ {
+ MutexAutoLock lock(mLock);
+ if (mChunkedDecoder) {
+ mForTakeResponseTrailers = mChunkedDecoder->TakeTrailers();
+ }
+
+ // the transaction is done with a complete response.
+ mTransactionDone = true;
+ mResponseIsComplete = true;
+ }
+ ReleaseBlockingTransaction();
+
+ if (TimingEnabled()) {
+ SetResponseEnd(TimeStamp::Now());
+ }
+
+ // report the entire response has arrived
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
+ static_cast<uint64_t>(mContentRead), ""_ns);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpTransaction::ProcessData(char* buf, uint32_t count,
+ uint32_t* countRead) {
+ nsresult rv;
+
+ LOG1(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ // we may not have read all of the headers yet...
+ if (!mHaveAllHeaders) {
+ uint32_t bytesConsumed = 0;
+
+ do {
+ uint32_t localBytesConsumed = 0;
+ char* localBuf = buf + bytesConsumed;
+ uint32_t localCount = count - bytesConsumed;
+
+ rv = ParseHead(localBuf, localCount, &localBytesConsumed);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT) return rv;
+ bytesConsumed += localBytesConsumed;
+ } while (rv == NS_ERROR_NET_INTERRUPT);
+
+ mCurrentHttpResponseHeaderSize += bytesConsumed;
+ if (mCurrentHttpResponseHeaderSize >
+ gHttpHandler->MaxHttpResponseHeaderSize()) {
+ LOG(("nsHttpTransaction %p The response header exceeds the limit.\n",
+ this));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ count -= bytesConsumed;
+
+ // if buf has some content in it, shift bytes to top of buf.
+ if (count && bytesConsumed) memmove(buf, buf + bytesConsumed, count);
+
+ if (mResponseHead && mHaveAllHeaders) {
+ auto reportResponseHeader = [&](uint32_t aSubType) {
+ nsAutoCString completeResponseHeaders;
+ mResponseHead->Flatten(completeResponseHeaders, false);
+ completeResponseHeaders.AppendLiteral("\r\n");
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, aSubType, PR_Now(), 0,
+ completeResponseHeaders);
+ };
+
+ if (mConnection->IsProxyConnectInProgress()) {
+ reportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER);
+ } else if (!mReportedResponseHeader) {
+ mReportedResponseHeader = true;
+ reportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER);
+ }
+ }
+ }
+
+ // even though count may be 0, we still want to call HandleContent
+ // so it can complete the transaction if this is a "no-content" response.
+ if (mHaveAllHeaders) {
+ uint32_t countRemaining = 0;
+ //
+ // buf layout:
+ //
+ // +--------------------------------------+----------------+-----+
+ // | countRead | countRemaining | |
+ // +--------------------------------------+----------------+-----+
+ //
+ // count : bytes read from the socket
+ // countRead : bytes corresponding to this transaction
+ // countRemaining : bytes corresponding to next transaction on conn
+ //
+ // NOTE:
+ // count > countRead + countRemaining <==> chunked transfer encoding
+ //
+ rv = HandleContent(buf, count, countRead, &countRemaining);
+ if (NS_FAILED(rv)) return rv;
+ // we may have read more than our share, in which case we must give
+ // the excess bytes back to the connection
+ if (mResponseIsComplete && countRemaining &&
+ (mConnection->Version() != HttpVersion::v3_0)) {
+ MOZ_ASSERT(mConnection);
+ rv = mConnection->PushBack(buf + *countRead, countRemaining);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mContentDecodingCheck && mResponseHead) {
+ mContentDecoding = mResponseHead->HasHeader(nsHttp::Content_Encoding);
+ mContentDecodingCheck = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Called when the transaction marked for blocking is associated with a
+// connection (i.e. added to a new h1 conn, an idle http connection, etc..) It
+// is safe to call this multiple times with it only having an effect once.
+void nsHttpTransaction::DispatchedAsBlocking() {
+ if (mDispatchedAsBlocking) return;
+
+ LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
+
+ if (!mRequestContext) return;
+
+ LOG(
+ ("nsHttpTransaction adding blocking transaction %p from "
+ "request context %p\n",
+ this, mRequestContext.get()));
+
+ mRequestContext->AddBlockingTransaction();
+ mDispatchedAsBlocking = true;
+}
+
+void nsHttpTransaction::RemoveDispatchedAsBlocking() {
+ if (!mRequestContext || !mDispatchedAsBlocking) {
+ LOG(("nsHttpTransaction::RemoveDispatchedAsBlocking this=%p not blocking",
+ this));
+ return;
+ }
+
+ uint32_t blockers = 0;
+ nsresult rv = mRequestContext->RemoveBlockingTransaction(&blockers);
+
+ LOG(
+ ("nsHttpTransaction removing blocking transaction %p from "
+ "request context %p. %d blockers remain.\n",
+ this, mRequestContext.get(), blockers));
+
+ if (NS_SUCCEEDED(rv) && !blockers) {
+ LOG(
+ ("nsHttpTransaction %p triggering release of blocked channels "
+ " with request context=%p\n",
+ this, mRequestContext.get()));
+ rv = gHttpHandler->ConnMgr()->ProcessPendingQ();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpTransaction::RemoveDispatchedAsBlocking\n"
+ " failed to process pending queue\n"));
+ }
+ }
+
+ mDispatchedAsBlocking = false;
+}
+
+void nsHttpTransaction::ReleaseBlockingTransaction() {
+ RemoveDispatchedAsBlocking();
+ LOG(
+ ("nsHttpTransaction %p request context set to null "
+ "in ReleaseBlockingTransaction() - was %p\n",
+ this, mRequestContext.get()));
+ mRequestContext = nullptr;
+}
+
+void nsHttpTransaction::DisableSpdy() {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ if (mConnInfo) {
+ // This is our clone of the connection info, not the persistent one that
+ // is owned by the connection manager, so we're safe to change this here
+ mConnInfo->SetNoSpdy(true);
+ }
+}
+
+void nsHttpTransaction::DisableHttp2ForProxy() {
+ mCaps |= NS_HTTP_DISALLOW_HTTP2_PROXY;
+}
+
+void nsHttpTransaction::DisableHttp3(bool aAllowRetryHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // mOrigConnInfo is an indicator that HTTPS RR is used, so don't mess up the
+ // connection info.
+ // When HTTPS RR is used, PrepareConnInfoForRetry() could select other h3
+ // record to connect.
+ if (mOrigConnInfo) {
+ LOG(
+ ("nsHttpTransaction::DisableHttp3 this=%p mOrigConnInfo=%s "
+ "aAllowRetryHTTPSRR=%d",
+ this, mOrigConnInfo->HashKey().get(), aAllowRetryHTTPSRR));
+ if (!aAllowRetryHTTPSRR) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ return;
+ }
+
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+
+ MOZ_ASSERT(mConnInfo);
+ if (mConnInfo) {
+ // After CloneAsDirectRoute(), http3 will be disabled.
+ RefPtr<nsHttpConnectionInfo> connInfo;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(connInfo));
+ RemoveAlternateServiceUsedHeader(mRequestHead);
+ MOZ_ASSERT(!connInfo->IsHttp3());
+ mConnInfo.swap(connInfo);
+ }
+}
+
+void nsHttpTransaction::CheckForStickyAuthScheme() {
+ LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p", this));
+
+ MOZ_ASSERT(mHaveAllHeaders);
+ MOZ_ASSERT(mResponseHead);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate);
+ CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate);
+}
+
+void nsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtom const& header) {
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ LOG((" already sticky"));
+ return;
+ }
+
+ nsAutoCString auth;
+ if (NS_FAILED(mResponseHead->GetHeader(header, auth))) {
+ return;
+ }
+
+ if (IsStickyAuthSchemeAt(auth)) {
+ LOG((" connection made sticky"));
+ // This is enough to make this transaction keep it's current connection,
+ // prevents the connection from being released back to the pool.
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ }
+}
+
+bool nsHttpTransaction::IsStickyAuthSchemeAt(nsACString const& auth) {
+ Tokenizer p(auth);
+ nsAutoCString schema;
+ while (p.ReadWord(schema)) {
+ ToLowerCase(schema);
+
+ // using a new instance because of thread safety of auth modules refcnt
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (schema.EqualsLiteral("negotiate")) {
+#ifdef MOZ_AUTH_EXTENSION
+ authenticator = new nsHttpNegotiateAuth();
+#endif
+ } else if (schema.EqualsLiteral("basic")) {
+ authenticator = new nsHttpBasicAuth();
+ } else if (schema.EqualsLiteral("digest")) {
+ authenticator = new nsHttpDigestAuth();
+ } else if (schema.EqualsLiteral("ntlm")) {
+ authenticator = new nsHttpNTLMAuth();
+ } else if (schema.EqualsLiteral("mock_auth") &&
+ PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
+ authenticator = new MockHttpAuth();
+ }
+ if (authenticator) {
+ uint32_t flags;
+ nsresult rv = authenticator->GetAuthFlags(&flags);
+ if (NS_SUCCEEDED(rv) &&
+ (flags & nsIHttpAuthenticator::CONNECTION_BASED)) {
+ return true;
+ }
+ }
+
+ // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader
+ p.SkipUntil(Tokenizer::Token::NewLine());
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+ }
+
+ return false;
+}
+
+TimingStruct nsHttpTransaction::Timings() {
+ mozilla::MutexAutoLock lock(mLock);
+ TimingStruct timings = mTimings;
+ return timings;
+}
+
+void nsHttpTransaction::BootstrapTimings(TimingStruct times) {
+ mozilla::MutexAutoLock lock(mLock);
+ mTimings = times;
+}
+
+void nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.domainLookupStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.domainLookupStart = timeStamp;
+}
+
+void nsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.domainLookupEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.domainLookupEnd = timeStamp;
+}
+
+void nsHttpTransaction::SetConnectStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.connectStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.connectStart = timeStamp;
+}
+
+void nsHttpTransaction::SetConnectEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.connectEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.connectEnd = timeStamp;
+}
+
+void nsHttpTransaction::SetRequestStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.requestStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.requestStart = timeStamp;
+}
+
+void nsHttpTransaction::SetResponseStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.responseStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.responseStart = timeStamp;
+}
+
+void nsHttpTransaction::SetResponseEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.responseEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.responseEnd = timeStamp;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetDomainLookupStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.domainLookupStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetDomainLookupEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.domainLookupEnd;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetConnectStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.connectStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetTcpConnectEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.tcpConnectEnd;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetSecureConnectionStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.secureConnectionStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetConnectEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.connectEnd;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetRequestStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.requestStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetResponseStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.responseStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetResponseEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.responseEnd;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction deletion event
+//-----------------------------------------------------------------------------
+
+class DeleteHttpTransaction : public Runnable {
+ public:
+ explicit DeleteHttpTransaction(nsHttpTransaction* trans)
+ : Runnable("net::DeleteHttpTransaction"), mTrans(trans) {}
+
+ NS_IMETHOD Run() override {
+ delete mTrans;
+ return NS_OK;
+ }
+
+ private:
+ nsHttpTransaction* mTrans;
+};
+
+void nsHttpTransaction::DeleteSelfOnConsumerThread() {
+ LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this));
+
+ bool val;
+ if (!mConsumerTarget ||
+ (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) {
+ delete this;
+ } else {
+ LOG(("proxying delete to consumer thread...\n"));
+ nsCOMPtr<nsIRunnable> event = new DeleteHttpTransaction(this);
+ if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL))) {
+ NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
+ }
+ }
+}
+
+bool nsHttpTransaction::TryToRunPacedRequest() {
+ if (mSubmittedRatePacing) return mPassedRatePacing;
+
+ mSubmittedRatePacing = true;
+ mSynchronousRatePaceRequest = true;
+ Unused << gHttpHandler->SubmitPacedRequest(
+ this, getter_AddRefs(mTokenBucketCancel));
+ mSynchronousRatePaceRequest = false;
+ return mPassedRatePacing;
+}
+
+void nsHttpTransaction::OnTokenBucketAdmitted() {
+ mPassedRatePacing = true;
+ mTokenBucketCancel = nullptr;
+
+ if (!mSynchronousRatePaceRequest) {
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpTransaction::OnTokenBucketAdmitted\n"
+ " failed to process pending queue\n"));
+ }
+ }
+}
+
+void nsHttpTransaction::CancelPacing(nsresult reason) {
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpTransaction)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsHttpTransaction::Release() {
+ nsrefcnt count;
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsHttpTransaction");
+ if (0 == count) {
+ mRefCnt = 1; /* stablize */
+ // it is essential that the transaction be destroyed on the consumer
+ // thread (we could be holding the last reference to our consumer).
+ DeleteSelfOnConsumerThread();
+ return 0;
+ }
+ return count;
+}
+
+NS_IMPL_QUERY_INTERFACE(nsHttpTransaction, nsIInputStreamCallback,
+ nsIOutputStreamCallback, nsITimerCallback, nsINamed)
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket thread
+NS_IMETHODIMP
+nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mConnection) {
+ mConnection->TransactionHasDataToWrite(this);
+ nsresult rv = mConnection->ResumeSend();
+ if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed");
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsIOutputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket thread
+NS_IMETHODIMP
+nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mWaitingOnPipeOut = false;
+ if (mConnection) {
+ mConnection->TransactionHasDataToRecv(this);
+ nsresult rv = mConnection->ResumeRecv();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ NS_ERROR("ResumeRecv failed");
+ }
+ }
+ return NS_OK;
+}
+
+void nsHttpTransaction::GetNetworkAddresses(
+ NetAddr& self, NetAddr& peer, bool& aResolvedByTRR,
+ nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason,
+ bool& aEchConfigUsed) {
+ MutexAutoLock lock(mLock);
+ self = mSelfAddr;
+ peer = mPeerAddr;
+ aResolvedByTRR = mResolvedByTRR;
+ aEffectiveTRRMode = mEffectiveTRRMode;
+ aSkipReason = mTRRSkipReason;
+ aEchConfigUsed = mEchConfigUsed;
+}
+
+bool nsHttpTransaction::Do0RTT() {
+ LOG(("nsHttpTransaction::Do0RTT"));
+ mEarlyDataWasAvailable = true;
+ if (mRequestHead->IsSafeMethod() && !mDoNotTryEarlyData &&
+ (!mConnection || !mConnection->IsProxyConnectInProgress())) {
+ m0RTTInProgress = true;
+ }
+ return m0RTTInProgress;
+}
+
+nsresult nsHttpTransaction::Finish0RTT(bool aRestart,
+ bool aAlpnChanged /* ignored */) {
+ LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n", this, aRestart,
+ aAlpnChanged));
+ MOZ_ASSERT(m0RTTInProgress);
+ m0RTTInProgress = false;
+
+ MaybeCancelFallbackTimer();
+
+ if (!aRestart && (mEarlyDataDisposition == EARLY_SENT)) {
+ // note that if this is invoked by a 3 param version of finish0rtt this
+ // disposition might be reverted
+ mEarlyDataDisposition = EARLY_ACCEPTED;
+ }
+ if (aRestart) {
+ // Not to use 0RTT when this transaction is restarted next time.
+ mDoNotTryEarlyData = true;
+
+ // Reset request headers to be sent again.
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (!mConnected) {
+ // this is code that was skipped in ::ReadSegments while in 0RTT
+ mConnected = true;
+ MaybeRefreshSecurityInfo();
+ }
+ return NS_OK;
+}
+
+void nsHttpTransaction::Refused0RTT() {
+ LOG(("nsHttpTransaction::Refused0RTT %p\n", this));
+ if (mEarlyDataDisposition == EARLY_ACCEPTED) {
+ mEarlyDataDisposition = EARLY_SENT; // undo accepted state
+ }
+}
+
+void nsHttpTransaction::SetHttpTrailers(nsCString& aTrailers) {
+ LOG(("nsHttpTransaction::SetHttpTrailers %p", this));
+ LOG(("[\n %s\n]", aTrailers.BeginReading()));
+
+ // Introduce a local variable to minimize the critical section.
+ UniquePtr<nsHttpHeaderArray> httpTrailers(new nsHttpHeaderArray());
+ // Given it's usually null, use double-check locking for performance.
+ if (mForTakeResponseTrailers) {
+ MutexAutoLock lock(mLock);
+ if (mForTakeResponseTrailers) {
+ // Copy the trailer. |TakeResponseTrailers| gets the original trailer
+ // until the final swap.
+ *httpTrailers = *mForTakeResponseTrailers;
+ }
+ }
+
+ int32_t cur = 0;
+ int32_t len = aTrailers.Length();
+ while (cur < len) {
+ int32_t newline = aTrailers.FindCharInSet("\n", cur);
+ if (newline == -1) {
+ newline = len;
+ }
+
+ int32_t end =
+ (newline && aTrailers[newline - 1] == '\r') ? newline - 1 : newline;
+ nsDependentCSubstring line(aTrailers, cur, end);
+ nsHttpAtom hdr;
+ nsAutoCString hdrNameOriginal;
+ nsAutoCString val;
+ if (NS_SUCCEEDED(httpTrailers->ParseHeaderLine(line, &hdr, &hdrNameOriginal,
+ &val))) {
+ if (hdr == nsHttp::Server_Timing) {
+ Unused << httpTrailers->SetHeaderFromNet(hdr, hdrNameOriginal, val,
+ true);
+ }
+ }
+
+ cur = newline + 1;
+ }
+
+ if (httpTrailers->Count() == 0) {
+ // Didn't find a Server-Timing header, so get rid of this.
+ httpTrailers = nullptr;
+ }
+
+ MutexAutoLock lock(mLock);
+ std::swap(mForTakeResponseTrailers, httpTrailers);
+}
+
+bool nsHttpTransaction::IsWebsocketUpgrade() {
+ if (mRequestHead) {
+ nsAutoCString upgradeHeader;
+ if (NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Upgrade, upgradeHeader)) &&
+ upgradeHeader.LowerCaseEqualsLiteral("websocket")) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsHttpTransaction::OnProxyConnectComplete(int32_t aResponseCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mConnInfo->UsingConnect());
+
+ LOG(("nsHttpTransaction::OnProxyConnectComplete %p aResponseCode=%d", this,
+ aResponseCode));
+
+ mProxyConnectResponseCode = aResponseCode;
+}
+
+int32_t nsHttpTransaction::GetProxyConnectResponseCode() {
+ return mProxyConnectResponseCode;
+}
+
+void nsHttpTransaction::SetFlat407Headers(const nsACString& aHeaders) {
+ MOZ_ASSERT(mProxyConnectResponseCode == 407);
+ MOZ_ASSERT(!mResponseHead);
+
+ LOG(("nsHttpTransaction::SetFlat407Headers %p", this));
+ mFlat407Headers = aHeaders;
+}
+
+void nsHttpTransaction::NotifyTransactionObserver(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mTransactionObserver) {
+ return;
+ }
+
+ bool versionOk = false;
+ bool authOk = false;
+
+ LOG(("nsHttpTransaction::NotifyTransactionObserver %p reason %" PRIx32
+ " conn %p\n",
+ this, static_cast<uint32_t>(reason), mConnection.get()));
+
+ if (mConnection) {
+ HttpVersion version = mConnection->Version();
+ versionOk = (((reason == NS_BASE_STREAM_CLOSED) || (reason == NS_OK)) &&
+ ((mConnection->Version() == HttpVersion::v2_0) ||
+ (mConnection->Version() == HttpVersion::v3_0)));
+
+ nsCOMPtr<nsITLSSocketControl> socketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
+ LOG(
+ ("nsHttpTransaction::NotifyTransactionObserver"
+ " version %u socketControl %p\n",
+ static_cast<int32_t>(version), socketControl.get()));
+ if (socketControl) {
+ authOk = !socketControl->GetFailedVerification();
+ }
+ }
+
+ TransactionObserverResult result;
+ result.versionOk() = versionOk;
+ result.authOk() = authOk;
+ result.closeReason() = reason;
+
+ TransactionObserverFunc obs = nullptr;
+ std::swap(obs, mTransactionObserver);
+ obs(std::move(result));
+}
+
+void nsHttpTransaction::UpdateConnectionInfo(nsHttpConnectionInfo* aConnInfo) {
+ MOZ_ASSERT(aConnInfo);
+
+ if (mActivated) {
+ MOZ_ASSERT(false, "Should not update conn info after activated");
+ return;
+ }
+
+ mOrigConnInfo = mConnInfo->Clone();
+ mConnInfo = aConnInfo;
+}
+
+nsresult nsHttpTransaction::OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) {
+ LOG(("nsHttpTransaction::OnHTTPSRRAvailable [this=%p] mActivated=%d", this,
+ mActivated));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ {
+ MutexAutoLock lock(mLock);
+ MakeDontWaitHTTPSRR();
+ mDNSRequest = nullptr;
+ }
+
+ if (!mResolver) {
+ LOG(("The transaction is not interested in HTTPS record anymore."));
+ return NS_OK;
+ }
+
+ RefPtr<nsHttpTransaction> deleteProtector(this);
+
+ uint32_t receivedStage = HTTPSSVC_NO_USABLE_RECORD;
+ // Make sure we set the correct value to |mHTTPSSVCReceivedStage|, since we
+ // also use this value to indicate whether HTTPS RR is used or not.
+ auto updateHTTPSSVCReceivedStage = MakeScopeExit([&] {
+ mHTTPSSVCReceivedStage = receivedStage;
+
+ // In the case that an HTTPS RR is unavailable, we should call
+ // ProcessPendingQ to make sure this transition to be processed soon.
+ if (!mHTTPSSVCRecord) {
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ }
+ });
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = aHTTPSSVCRecord;
+ if (!record) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool hasIPAddress = false;
+ Unused << record->GetHasIPAddresses(&hasIPAddress);
+
+ if (mActivated) {
+ receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_2
+ : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_2;
+ return NS_OK;
+ }
+
+ receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_1
+ : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_1;
+
+ nsCOMPtr<nsISVCBRecord> svcbRecord = aHighestPriorityRecord;
+ if (!svcbRecord) {
+ LOG((" no usable record!"));
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ bool allRecordsExcluded = false;
+ Unused << record->GetAllRecordsExcluded(&allRecordsExcluded);
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
+ allRecordsExcluded
+ ? HTTPSSVC_CONNECTION_ALL_RECORDS_EXCLUDED
+ : HTTPSSVC_CONNECTION_NO_USABLE_RECORD);
+ if (allRecordsExcluded &&
+ StaticPrefs::network_dns_httpssvc_reset_exclustion_list() && dns) {
+ Unused << dns->ResetExcludedSVCDomainName(mConnInfo->GetOrigin());
+ if (NS_FAILED(record->GetServiceModeRecord(mCaps & NS_HTTP_DISALLOW_SPDY,
+ mCaps & NS_HTTP_DISALLOW_HTTP3,
+ getter_AddRefs(svcbRecord)))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Remember this RR set. In the case that the connection establishment failed,
+ // we will use other records to retry.
+ mHTTPSSVCRecord = record;
+
+ RefPtr<nsHttpConnectionInfo> newInfo =
+ mConnInfo->CloneAndAdoptHTTPSSVCRecord(svcbRecord);
+ bool needFastFallback = newInfo->IsHttp3();
+ bool foundInPendingQ = gHttpHandler->ConnMgr()->RemoveTransFromConnEntry(
+ this, mHashKeyOfConnectionEntry);
+
+ // Adopt the new connection info, so this transaction will be added into the
+ // new connection entry.
+ UpdateConnectionInfo(newInfo);
+
+ // If this transaction is sucessfully removed from a connection entry, we call
+ // ProcessNewTransaction to process it immediately.
+ // If not, this means that nsHttpTransaction::OnHTTPSRRAvailable happens
+ // before ProcessNewTransaction and this transaction will be processed later.
+ if (foundInPendingQ) {
+ if (NS_FAILED(gHttpHandler->ConnMgr()->ProcessNewTransaction(this))) {
+ LOG(("Failed to process this transaction."));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // In case we already have mHttp3BackupTimer, cancel it.
+ MaybeCancelFallbackTimer();
+
+ if (needFastFallback) {
+ CreateAndStartTimer(
+ mFastFallbackTimer, this,
+ StaticPrefs::network_dns_httpssvc_http3_fast_fallback_timeout());
+ }
+
+ // Prefetch the A/AAAA records of the target name.
+ nsAutoCString targetName;
+ Unused << svcbRecord->GetName(targetName);
+ if (mResolver) {
+ mResolver->PrefetchAddrRecord(targetName, mCaps & NS_HTTP_REFRESH_DNS);
+ }
+
+ // echConfig is used, so initialize the retry counters to 0.
+ if (!mConnInfo->GetEchConfig().IsEmpty()) {
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT, 0);
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT, 0);
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT, 0);
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsHttpTransaction::HTTPSSVCReceivedStage() {
+ return mHTTPSSVCReceivedStage;
+}
+
+void nsHttpTransaction::MaybeCancelFallbackTimer() {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mFastFallbackTimer) {
+ mFastFallbackTimer->Cancel();
+ mFastFallbackTimer = nullptr;
+ }
+
+ if (mHttp3BackupTimer) {
+ mHttp3BackupTimer->Cancel();
+ mHttp3BackupTimer = nullptr;
+ }
+}
+
+void nsHttpTransaction::OnBackupConnectionReady(bool aTriggeredByHTTPSRR) {
+ LOG(
+ ("nsHttpTransaction::OnBackupConnectionReady [%p] mConnected=%d "
+ "aTriggeredByHTTPSRR=%d",
+ this, mConnected, aTriggeredByHTTPSRR));
+ if (mConnected || mClosed || mRestarted) {
+ return;
+ }
+
+ // If HTTPS RR is in play, don't mess up the fallback mechansim of HTTPS RR.
+ if (!aTriggeredByHTTPSRR && mOrigConnInfo) {
+ return;
+ }
+
+ if (mConnection) {
+ // The transaction will only be restarted when we already have a connection.
+ // When there is no connection, this transaction will be moved to another
+ // connection entry.
+ SetRestartReason(aTriggeredByHTTPSRR
+ ? TRANSACTION_RESTART_HTTPS_RR_FAST_FALLBACK
+ : TRANSACTION_RESTART_HTTP3_FAST_FALLBACK);
+ }
+
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+
+ // Need to backup the origin conn info, since UpdateConnectionInfo() will be
+ // called in HandleFallback() and mOrigConnInfo will be
+ // replaced.
+ RefPtr<nsHttpConnectionInfo> backup = mOrigConnInfo;
+ HandleFallback(mBackupConnInfo);
+ mOrigConnInfo.swap(backup);
+
+ RemoveAlternateServiceUsedHeader(mRequestHead);
+
+ if (mResolver) {
+ if (mBackupConnInfo) {
+ const nsCString& host = mBackupConnInfo->GetRoutedHost().IsEmpty()
+ ? mBackupConnInfo->GetOrigin()
+ : mBackupConnInfo->GetRoutedHost();
+ mResolver->PrefetchAddrRecord(host, Caps() & NS_HTTP_REFRESH_DNS);
+ }
+
+ if (!aTriggeredByHTTPSRR) {
+ // We are about to use this backup connection. We shoud not try to use
+ // HTTPS RR at this point.
+ mResolver->Close();
+ mResolver = nullptr;
+ }
+ }
+}
+
+static void CreateBackupConnection(
+ nsHttpConnectionInfo* aBackupConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, std::function<void(bool)>&& aResultCallback) {
+ aBackupConnInfo->SetFallbackConnection(true);
+ RefPtr<SpeculativeTransaction> trans = new SpeculativeTransaction(
+ aBackupConnInfo, aCallbacks, aCaps | NS_HTTP_DISALLOW_HTTP3,
+ std::move(aResultCallback));
+ uint32_t limit =
+ StaticPrefs::network_http_http3_parallel_fallback_conn_limit();
+ if (limit) {
+ trans->SetParallelSpeculativeConnectLimit(limit);
+ trans->SetIgnoreIdle(true);
+ }
+ gHttpHandler->ConnMgr()->DoFallbackConnection(trans, false);
+}
+
+void nsHttpTransaction::OnHttp3BackupTimer() {
+ LOG(("nsHttpTransaction::OnHttp3BackupTimer [%p]", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mConnInfo->IsHttp3());
+
+ mHttp3BackupTimer = nullptr;
+
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(mBackupConnInfo));
+ MOZ_ASSERT(!mBackupConnInfo->IsHttp3());
+
+ RefPtr<nsHttpTransaction> self = this;
+ auto callback = [self](bool aSucceded) {
+ if (aSucceded) {
+ self->OnBackupConnectionReady(false);
+ }
+ };
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ callbacks = mCallbacks;
+ }
+ CreateBackupConnection(mBackupConnInfo, callbacks, mCaps,
+ std::move(callback));
+}
+
+void nsHttpTransaction::OnFastFallbackTimer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpTransaction::OnFastFallbackTimer [%p] mConnected=%d", this,
+ mConnected));
+
+ mFastFallbackTimer = nullptr;
+
+ MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo);
+ if (!mHTTPSSVCRecord || !mOrigConnInfo) {
+ return;
+ }
+
+ bool echConfigUsed = gHttpHandler->EchConfigEnabled(mConnInfo->IsHttp3()) &&
+ !mConnInfo->GetEchConfig().IsEmpty();
+ mBackupConnInfo = PrepareFastFallbackConnInfo(echConfigUsed);
+ if (!mBackupConnInfo) {
+ return;
+ }
+
+ MOZ_ASSERT(!mBackupConnInfo->IsHttp3());
+
+ RefPtr<nsHttpTransaction> self = this;
+ auto callback = [self](bool aSucceded) {
+ if (!aSucceded) {
+ return;
+ }
+
+ self->mFastFallbackTriggered = true;
+ self->OnBackupConnectionReady(true);
+ };
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ callbacks = mCallbacks;
+ }
+ CreateBackupConnection(mBackupConnInfo, callbacks, mCaps,
+ std::move(callback));
+}
+
+void nsHttpTransaction::HandleFallback(
+ nsHttpConnectionInfo* aFallbackConnInfo) {
+ if (mConnection) {
+ MOZ_ASSERT(mActivated);
+ // Close the transaction with NS_ERROR_NET_RESET, since we know doing this
+ // will make transaction to be restarted.
+ mConnection->CloseTransaction(this, NS_ERROR_NET_RESET);
+ return;
+ }
+
+ if (!aFallbackConnInfo) {
+ // Nothing to do here.
+ return;
+ }
+
+ LOG(("nsHttpTransaction %p HandleFallback to connInfo[%s]", this,
+ aFallbackConnInfo->HashKey().get()));
+
+ bool foundInPendingQ = gHttpHandler->ConnMgr()->RemoveTransFromConnEntry(
+ this, mHashKeyOfConnectionEntry);
+ if (!foundInPendingQ) {
+ MOZ_ASSERT(false, "transaction not in entry");
+ return;
+ }
+
+ // rewind streams in case we already wrote out the request
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+
+ UpdateConnectionInfo(aFallbackConnInfo);
+ Unused << gHttpHandler->ConnMgr()->ProcessNewTransaction(this);
+}
+
+NS_IMETHODIMP
+nsHttpTransaction::Notify(nsITimer* aTimer) {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!gHttpHandler || !gHttpHandler->ConnMgr()) {
+ return NS_OK;
+ }
+
+ if (aTimer == mFastFallbackTimer) {
+ OnFastFallbackTimer();
+ } else if (aTimer == mHttp3BackupTimer) {
+ OnHttp3BackupTimer();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpTransaction::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsHttpTransaction");
+ return NS_OK;
+}
+
+bool nsHttpTransaction::GetSupportsHTTP3() { return mSupportsHTTP3; }
+
+const int64_t TELEMETRY_REQUEST_SIZE_10M = (int64_t)10 * (int64_t)(1 << 20);
+const int64_t TELEMETRY_REQUEST_SIZE_50M = (int64_t)50 * (int64_t)(1 << 20);
+const int64_t TELEMETRY_REQUEST_SIZE_100M = (int64_t)100 * (int64_t)(1 << 20);
+
+void nsHttpTransaction::CollectTelemetryForUploads() {
+ if ((mRequestSize < TELEMETRY_REQUEST_SIZE_10M) ||
+ mTimings.requestStart.IsNull() || mTimings.responseStart.IsNull()) {
+ return;
+ }
+
+ // We will briefly continue to collect HTTP_UPLOAD_BANDWIDTH_MBPS
+ // (a keyed histogram) while live experiments depend on it.
+ // Once complete, we can remove and use the glean probes,
+ // http_1/2/3_upload_throughput.
+ nsAutoCString protocolVersion(nsHttp::GetProtocolVersion(mHttpVersion));
+ TimeDuration sendTime = mTimings.responseStart - mTimings.requestStart;
+ double megabits = static_cast<double>(mRequestSize) * 8.0 / 1000000.0;
+ uint32_t mpbs = static_cast<uint32_t>(megabits / sendTime.ToSeconds());
+ Telemetry::Accumulate(Telemetry::HTTP_UPLOAD_BANDWIDTH_MBPS, protocolVersion,
+ mpbs);
+
+ switch (mHttpVersion) {
+ case HttpVersion::v1_0:
+ case HttpVersion::v1_1:
+ glean::networking::http_1_upload_throughput.AccumulateSamples({mpbs});
+ if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) {
+ glean::networking::http_1_upload_throughput_10_50.AccumulateSamples(
+ {mpbs});
+ } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) {
+ glean::networking::http_1_upload_throughput_50_100.AccumulateSamples(
+ {mpbs});
+ } else {
+ glean::networking::http_1_upload_throughput_100.AccumulateSamples(
+ {mpbs});
+ }
+ break;
+ case HttpVersion::v2_0:
+ glean::networking::http_2_upload_throughput.AccumulateSamples({mpbs});
+ if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) {
+ glean::networking::http_2_upload_throughput_10_50.AccumulateSamples(
+ {mpbs});
+ } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) {
+ glean::networking::http_2_upload_throughput_50_100.AccumulateSamples(
+ {mpbs});
+ } else {
+ glean::networking::http_2_upload_throughput_100.AccumulateSamples(
+ {mpbs});
+ }
+ break;
+ case HttpVersion::v3_0:
+ glean::networking::http_3_upload_throughput.AccumulateSamples({mpbs});
+ if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) {
+ glean::networking::http_3_upload_throughput_10_50.AccumulateSamples(
+ {mpbs});
+ } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) {
+ glean::networking::http_3_upload_throughput_50_100.AccumulateSamples(
+ {mpbs});
+ } else {
+ glean::networking::http_3_upload_throughput_100.AccumulateSamples(
+ {mpbs});
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void nsHttpTransaction::GetHashKeyOfConnectionEntry(nsACString& aResult) {
+ MutexAutoLock lock(mLock);
+ aResult.Assign(mHashKeyOfConnectionEntry);
+}
+
+void nsHttpTransaction::SetIsForWebTransport(bool aIsForWebTransport) {
+ mIsForWebTransport = aIsForWebTransport;
+}
+
+void nsHttpTransaction::RemoveConnection() {
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h
new file mode 100644
index 0000000000..354fa2c4d2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -0,0 +1,599 @@
+/* -*- 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 nsHttpTransaction_h__
+#define nsHttpTransaction_h__
+
+#include "ARefBase.h"
+#include "EventTokenBucket.h"
+#include "Http2Push.h"
+#include "HttpTransactionShell.h"
+#include "TimingStruct.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsAHttpConnection.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsHttp.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIClassOfService.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITLSSocketControl.h"
+#include "nsITimer.h"
+#include "nsIWebTransport.h"
+#include "nsTHashMap.h"
+#include "nsThreadUtils.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIDNSHTTPSSVCRecord;
+class nsIEventTarget;
+class nsIInputStream;
+class nsIOutputStream;
+class nsIRequestContext;
+class nsISVCBRecord;
+
+namespace mozilla::net {
+
+class HTTPSRecordResolver;
+class nsHttpChunkedDecoder;
+class nsHttpHeaderArray;
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+class NullHttpTransaction;
+class Http2ConnectTransaction;
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction represents a single HTTP transaction. It is thread-safe,
+// intended to run on the socket thread.
+//-----------------------------------------------------------------------------
+
+class nsHttpTransaction final : public nsAHttpTransaction,
+ public HttpTransactionShell,
+ public ATokenBucketEvent,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public ARefBase,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_HTTPTRANSACTIONSHELL
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsHttpTransaction();
+
+ void OnActivated() override;
+
+ // attributes
+ nsHttpResponseHead* ResponseHead() {
+ return mHaveAllHeaders ? mResponseHead : nullptr;
+ }
+
+ nsIEventTarget* ConsumerTarget() { return mConsumerTarget; }
+
+ // Called to set/find out if the transaction generated a complete response.
+ void SetResponseIsComplete() { mResponseIsComplete = true; }
+
+ void EnableKeepAlive() { mCaps |= NS_HTTP_ALLOW_KEEPALIVE; }
+ void MakeSticky() { mCaps |= NS_HTTP_STICKY_CONNECTION; }
+ void MakeNonSticky() override { mCaps &= ~NS_HTTP_STICKY_CONNECTION; }
+ void MakeRestartable() override { mCaps |= NS_HTTP_CONNECTION_RESTARTABLE; }
+ void MakeNonRestartable() { mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE; }
+ void RemoveConnection();
+ void SetIsHttp2Websocket(bool h2ws) override { mIsHttp2Websocket = h2ws; }
+ bool IsHttp2Websocket() override { return mIsHttp2Websocket; }
+
+ void SetTRRInfo(nsIRequest::TRRMode aMode,
+ TRRSkippedReason aSkipReason) override {
+ mEffectiveTRRMode = aMode;
+ mTRRSkipReason = aSkipReason;
+ }
+
+ bool WaitingForHTTPSRR() const { return mCaps & NS_HTTP_FORCE_WAIT_HTTP_RR; }
+ void MakeDontWaitHTTPSRR() { mCaps &= ~NS_HTTP_FORCE_WAIT_HTTP_RR; }
+
+ // SetPriority() may only be used by the connection manager.
+ void SetPriority(int32_t priority) { mPriority = priority; }
+ int32_t Priority() { return mPriority; }
+
+ void PrintDiagnostics(nsCString& log);
+
+ // Sets mTimings.transactionPending to the current time stamp or to a null
+ // time stamp (if now is false)
+ void SetPendingTime(bool now = true) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (!now && !mTimings.transactionPending.IsNull()) {
+ // Remember how long it took. We will use this value to record
+ // TRANSACTION_WAIT_TIME_HTTP2_SUP_HTTP3 telemetry, but we need to wait
+ // for the response headers.
+ mPendingDurationTime = TimeStamp::Now() - mTimings.transactionPending;
+ }
+ // Note that the transaction could be added in to a pending queue multiple
+ // times (when the transaction is restarted or moved to a new conn entry due
+ // to HTTPS RR), so we should only set the pending time once.
+ if (mTimings.transactionPending.IsNull()) {
+ mTimings.transactionPending = now ? TimeStamp::Now() : TimeStamp();
+ }
+ }
+ TimeStamp GetPendingTime() override {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.transactionPending;
+ }
+
+ // overload of nsAHttpTransaction::RequestContext()
+ nsIRequestContext* RequestContext() override { return mRequestContext.get(); }
+ void DispatchedAsBlocking();
+ void RemoveDispatchedAsBlocking();
+
+ void DisableSpdy() override;
+ void DisableHttp2ForProxy() override;
+ void DoNotRemoveAltSvc() override { mDoNotRemoveAltSvc = true; }
+ void DoNotResetIPFamilyPreference() override {
+ mDoNotResetIPFamilyPreference = true;
+ }
+ void DisableHttp3(bool aAllowRetryHTTPSRR) override;
+
+ nsHttpTransaction* QueryHttpTransaction() override { return this; }
+
+ already_AddRefed<Http2PushedStreamWrapper> GetPushedStream() {
+ return do_AddRef(mPushedStream);
+ }
+ already_AddRefed<Http2PushedStreamWrapper> TakePushedStream() {
+ return mPushedStream.forget();
+ }
+
+ uint32_t InitialRwin() const { return mInitialRwin; };
+ bool ChannelPipeFull() { return mWaitingOnPipeOut; }
+
+ // Locked methods to get and set timing info
+ void BootstrapTimings(TimingStruct times);
+ void SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetResponseStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetResponseEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+
+ [[nodiscard]] bool Do0RTT() override;
+ [[nodiscard]] nsresult Finish0RTT(bool aRestart,
+ bool aAlpnChanged /* ignored */) override;
+
+ // After Finish0RTT early data may have failed but the caller did not request
+ // restart - this indicates that state for dev tools
+ void Refused0RTT();
+
+ uint64_t BrowserId() override { return mBrowserId; }
+
+ void SetHttpTrailers(nsCString& aTrailers);
+
+ bool IsWebsocketUpgrade();
+
+ void OnProxyConnectComplete(int32_t aResponseCode) override;
+ void SetFlat407Headers(const nsACString& aHeaders);
+
+ // This is only called by Http2PushedStream::TryOnPush when a new pushed
+ // stream is available. The newly added stream will be taken by another
+ // transaction.
+ void OnPush(Http2PushedStreamWrapper* aStream);
+
+ void UpdateConnectionInfo(nsHttpConnectionInfo* aConnInfo);
+
+ void SetClassOfService(ClassOfService cos);
+
+ virtual nsresult OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) override;
+
+ void GetHashKeyOfConnectionEntry(nsACString& aResult);
+
+ bool IsForWebTransport() { return mIsForWebTransport; }
+
+ private:
+ friend class DeleteHttpTransaction;
+ virtual ~nsHttpTransaction();
+
+ [[nodiscard]] nsresult Restart();
+ char* LocateHttpStart(char* buf, uint32_t len, bool aAllowPartialMatch);
+ [[nodiscard]] nsresult ParseLine(nsACString& line);
+ [[nodiscard]] nsresult ParseLineSegment(char* seg, uint32_t len);
+ [[nodiscard]] nsresult ParseHead(char*, uint32_t count, uint32_t* countRead);
+ [[nodiscard]] nsresult HandleContentStart();
+ [[nodiscard]] nsresult HandleContent(char*, uint32_t count,
+ uint32_t* contentRead,
+ uint32_t* contentRemaining);
+ [[nodiscard]] nsresult ProcessData(char*, uint32_t, uint32_t*);
+ void DeleteSelfOnConsumerThread();
+ void ReleaseBlockingTransaction();
+
+ [[nodiscard]] static nsresult ReadRequestSegment(nsIInputStream*, void*,
+ const char*, uint32_t,
+ uint32_t, uint32_t*);
+ [[nodiscard]] static nsresult WritePipeSegment(nsIOutputStream*, void*, char*,
+ uint32_t, uint32_t, uint32_t*);
+
+ bool TimingEnabled() const { return mCaps & NS_HTTP_TIMING_ENABLED; }
+
+ bool ResponseTimeoutEnabled() const final;
+
+ void ReuseConnectionOnRestartOK(bool reuseOk) override {
+ mReuseOnRestart = reuseOk;
+ }
+
+ // Called right after we parsed the response head. Checks for connection
+ // based authentication schemes in reponse headers for WWW and Proxy
+ // authentication. If such is found in any of them, NS_HTTP_STICKY_CONNECTION
+ // is set in mCaps. We need the sticky flag be set early to keep the
+ // connection from very start of the authentication process.
+ void CheckForStickyAuthScheme();
+ void CheckForStickyAuthSchemeAt(nsHttpAtom const& header);
+ bool IsStickyAuthSchemeAt(nsACString const& auth);
+
+ // Called from WriteSegments. Checks for conditions whether to throttle
+ // reading the content. When this returns true, WriteSegments returns
+ // WOULD_BLOCK.
+ bool ShouldThrottle();
+
+ void NotifyTransactionObserver(nsresult reason);
+
+ // When echConfig is enabled, this function put other available records
+ // in mRecordsForRetry. Returns true when mRecordsForRetry is not empty,
+ // otherwise returns false.
+ bool PrepareSVCBRecordsForRetry(const nsACString& aFailedDomainName,
+ const nsACString& aFailedAlpn,
+ bool& aAllRecordsHaveEchConfig);
+ // This function setups a new connection info for restarting this transaction.
+ void PrepareConnInfoForRetry(nsresult aReason);
+ // This function is used to select the next non http3 record and is only
+ // executed when the fast fallback timer is triggered.
+ already_AddRefed<nsHttpConnectionInfo> PrepareFastFallbackConnInfo(
+ bool aEchConfigUsed);
+
+ void MaybeReportFailedSVCDomain(nsresult aReason,
+ nsHttpConnectionInfo* aFailedConnInfo);
+
+ already_AddRefed<Http2PushedStreamWrapper> TakePushedStreamById(
+ uint32_t aStreamId);
+
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ enum HTTPSSVC_CONNECTION_FAILED_REASON : uint32_t {
+ HTTPSSVC_CONNECTION_OK = 0,
+ HTTPSSVC_CONNECTION_UNKNOWN_HOST = 1,
+ HTTPSSVC_CONNECTION_UNREACHABLE = 2,
+ HTTPSSVC_CONNECTION_421_RECEIVED = 3,
+ HTTPSSVC_CONNECTION_SECURITY_ERROR = 4,
+ HTTPSSVC_CONNECTION_NO_USABLE_RECORD = 5,
+ HTTPSSVC_CONNECTION_ALL_RECORDS_EXCLUDED = 6,
+ HTTPSSVC_CONNECTION_OTHERS = 7,
+ };
+ HTTPSSVC_CONNECTION_FAILED_REASON ErrorCodeToFailedReason(
+ nsresult aErrorCode);
+
+ void OnHttp3BackupTimer();
+ void OnBackupConnectionReady(bool aTriggeredByHTTPSRR);
+ void OnFastFallbackTimer();
+ void HandleFallback(nsHttpConnectionInfo* aFallbackConnInfo);
+ void MaybeCancelFallbackTimer();
+
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ enum TRANSACTION_RESTART_REASON : uint32_t {
+ TRANSACTION_RESTART_NONE = 0, // The transacion was not restarted.
+ TRANSACTION_RESTART_FORCED, // The transaction was forced to restart.
+ TRANSACTION_RESTART_NO_DATA_SENT,
+ TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA,
+ TRANSACTION_RESTART_HTTPS_RR_NET_RESET,
+ TRANSACTION_RESTART_HTTPS_RR_CONNECTION_REFUSED,
+ TRANSACTION_RESTART_HTTPS_RR_UNKNOWN_HOST,
+ TRANSACTION_RESTART_HTTPS_RR_NET_TIMEOUT,
+ TRANSACTION_RESTART_HTTPS_RR_SEC_ERROR,
+ TRANSACTION_RESTART_HTTPS_RR_FAST_FALLBACK,
+ TRANSACTION_RESTART_HTTP3_FAST_FALLBACK,
+ TRANSACTION_RESTART_OTHERS,
+ TRANSACTION_RESTART_PROTOCOL_VERSION_ALERT,
+ };
+ void SetRestartReason(TRANSACTION_RESTART_REASON aReason);
+
+ bool HandleWebTransportResponse(uint16_t aStatus);
+
+ void MaybeRefreshSecurityInfo() {
+ MutexAutoLock lock(mLock);
+ if (mConnection) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (tlsSocketControl) {
+ tlsSocketControl->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+ }
+ }
+ }
+
+ private:
+ class UpdateSecurityCallbacks : public Runnable {
+ public:
+ UpdateSecurityCallbacks(nsHttpTransaction* aTrans,
+ nsIInterfaceRequestor* aCallbacks)
+ : Runnable("net::nsHttpTransaction::UpdateSecurityCallbacks"),
+ mTrans(aTrans),
+ mCallbacks(aCallbacks) {}
+
+ NS_IMETHOD Run() override {
+ if (mTrans->mConnection) {
+ mTrans->mConnection->SetSecurityCallbacks(mCallbacks);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsHttpTransaction> mTrans;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ };
+
+ Mutex mLock MOZ_UNANNOTATED{"transaction lock"};
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mTransportSink;
+ nsCOMPtr<nsIEventTarget> mConsumerTarget;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ uint64_t mChannelId{0};
+
+ nsCString mReqHeaderBuf; // flattened request headers
+ nsCOMPtr<nsIInputStream> mRequestStream;
+ int64_t mRequestSize{0};
+
+ RefPtr<nsAHttpConnection> mConnection;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ // This is only set in UpdateConnectionInfo() when we have received a SVCB RR.
+ // When echConfig is not used and the connection is failed, this transaction
+ // will be restarted with this origin connection info directly.
+ // When echConfig is enabled, there are two cases below.
+ // 1. If all records have echConfig, we will retry other records except the
+ // failed one. In the case all other records with echConfig are failed and the
+ // pref network.dns.echconfig.fallback_to_origin_when_all_failed is true, this
+ // origin connection info will be used.
+ // 2. If only some records have echConfig and some not, we always fallback to
+ // this origin conn info.
+ RefPtr<nsHttpConnectionInfo> mOrigConnInfo;
+ nsHttpRequestHead* mRequestHead{nullptr}; // weak ref
+ nsHttpResponseHead* mResponseHead{nullptr}; // owning pointer
+
+ nsAHttpSegmentReader* mReader{nullptr};
+ nsAHttpSegmentWriter* mWriter{nullptr};
+
+ nsCString mLineBuf; // may contain a partial line
+
+ int64_t mContentLength{-1}; // equals -1 if unknown
+ int64_t mContentRead{0}; // count of consumed content bytes
+ Atomic<int64_t, ReleaseAcquire> mTransferSize{0}; // count of received bytes
+
+ // After a 304/204 or other "no-content" style response we will skip over
+ // up to MAX_INVALID_RESPONSE_BODY_SZ bytes when looking for the next
+ // response header to deal with servers that actually sent a response
+ // body where they should not have. This member tracks how many bytes have
+ // so far been skipped.
+ uint32_t mInvalidResponseBytesRead{0};
+
+ RefPtr<Http2PushedStreamWrapper> mPushedStream;
+ uint32_t mInitialRwin{0};
+
+ nsHttpChunkedDecoder* mChunkedDecoder{nullptr};
+
+ TimingStruct mTimings;
+
+ nsresult mStatus{NS_OK};
+
+ int16_t mPriority{0};
+
+ // the number of times this transaction has been restarted
+ uint16_t mRestartCount{0};
+ Atomic<uint32_t, ReleaseAcquire> mCaps{0};
+
+ HttpVersion mHttpVersion{HttpVersion::UNKNOWN};
+ uint16_t mHttpResponseCode{0};
+ nsCString mFlat407Headers;
+
+ uint32_t mCurrentHttpResponseHeaderSize{0};
+
+ int32_t const THROTTLE_NO_LIMIT = -1;
+ // This can have 3 possible values:
+ // * THROTTLE_NO_LIMIT - this means the transaction is not in any way limited
+ // to read the response, this is the default
+ // * a positive number - a limit is set because the transaction is obligated
+ // to throttle the response read, this is decresed with
+ // every piece of data the transaction receives
+ // * zero - when the transaction depletes the limit for reading, this makes it
+ // stop reading and return WOULD_BLOCK from WriteSegments;
+ // transaction then waits for a call of ResumeReading that resets
+ // this member back to THROTTLE_NO_LIMIT
+ int32_t mThrottlingReadAllowance{THROTTLE_NO_LIMIT};
+
+ // mCapsToClear holds flags that should be cleared in mCaps, e.g. unset
+ // NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid
+ // redundant requests on the network. The member itself is atomic, but
+ // access to it from the networking thread may happen either before or
+ // after the main thread modifies it. To deal with raciness, only unsetting
+ // bitfields should be allowed: 'lost races' will thus err on the
+ // conservative side, e.g. by going ahead with a 2nd DNS refresh.
+ Atomic<uint32_t> mCapsToClear{0};
+ Atomic<bool, ReleaseAcquire> mResponseIsComplete{false};
+ Atomic<bool, ReleaseAcquire> mClosed{false};
+ Atomic<bool, Relaxed> mIsHttp3Used{false};
+
+ // True iff WriteSegments was called while this transaction should be
+ // throttled (stop reading) Used to resume read on unblock of reading. Conn
+ // manager is responsible for calling back to resume reading.
+ bool mReadingStopped{false};
+
+ // state flags, all logically boolean, but not packed together into a
+ // bitfield so as to avoid bitfield-induced races. See bug 560579.
+ bool mConnected{false};
+ bool mActivated{false};
+ bool mHaveStatusLine{false};
+ bool mHaveAllHeaders{false};
+ bool mTransactionDone{false};
+ bool mDidContentStart{false};
+ bool mNoContent{false}; // expecting an empty entity body
+ bool mSentData{false};
+ bool mReceivedData{false};
+ bool mStatusEventPending{false};
+ bool mHasRequestBody{false};
+ bool mProxyConnectFailed{false};
+ bool mHttpResponseMatched{false};
+ bool mPreserveStream{false};
+ bool mDispatchedAsBlocking{false};
+ bool mResponseTimeoutEnabled{true};
+ bool mForceRestart{false};
+ bool mReuseOnRestart{false};
+ bool mContentDecoding{false};
+ bool mContentDecodingCheck{false};
+ bool mDeferredSendProgress{false};
+ bool mWaitingOnPipeOut{false};
+ bool mDoNotRemoveAltSvc{false};
+ bool mDoNotResetIPFamilyPreference{false};
+ bool mIsHttp2Websocket{false};
+
+ // mClosed := transaction has been explicitly closed
+ // mTransactionDone := transaction ran to completion or was interrupted
+ // mResponseComplete := transaction ran to completion
+
+ // For Restart-In-Progress Functionality
+ bool mReportedStart{false};
+ bool mReportedResponseHeader{false};
+
+ // protected by nsHttp::GetLock()
+ bool mResponseHeadTaken{false};
+ UniquePtr<nsHttpHeaderArray> mForTakeResponseTrailers;
+ bool mResponseTrailersTaken{false};
+
+ // Set when this transaction was restarted by call to Restart(). Used to tell
+ // the http channel to reset proxy authentication.
+ Atomic<bool> mRestarted{false};
+
+ // The time when the transaction was submitted to the Connection Manager
+ TimeDuration mPendingDurationTime;
+
+ uint64_t mBrowserId{0};
+
+ // For Rate Pacing via an EventTokenBucket
+ public:
+ // called by the connection manager to run this transaction through the
+ // token bucket. If the token bucket admits the transaction immediately it
+ // returns true. The function is called repeatedly until it returns true.
+ bool TryToRunPacedRequest();
+
+ // ATokenBucketEvent pure virtual implementation. Called by the token bucket
+ // when the transaction is ready to run. If this happens asynchrounously to
+ // token bucket submission the transaction just posts an event that causes
+ // the pending transaction queue to be rerun (and TryToRunPacedRequest() to
+ // be run again.
+ void OnTokenBucketAdmitted() override; // ATokenBucketEvent
+
+ // CancelPacing() can be used to tell the token bucket to remove this
+ // transaction from the list of pending transactions. This is used when a
+ // transaction is believed to be HTTP/1 (and thus subject to rate pacing)
+ // but later can be dispatched via spdy (not subject to rate pacing).
+ void CancelPacing(nsresult reason);
+
+ // Called by the connetion manager on the socket thread when reading for this
+ // previously throttled transaction has to be resumed.
+ void ResumeReading();
+
+ // This examins classification of this transaction whether the Throttleable
+ // class has been set while Leader, Unblocked, DontThrottle has not.
+ bool EligibleForThrottling() const;
+
+ private:
+ bool mSubmittedRatePacing{false};
+ bool mPassedRatePacing{false};
+ bool mSynchronousRatePaceRequest{false};
+ nsCOMPtr<nsICancelable> mTokenBucketCancel;
+
+ void CollectTelemetryForUploads();
+
+ public:
+ ClassOfService GetClassOfService() {
+ return {mClassOfServiceFlags, mClassOfServiceIncremental};
+ }
+
+ private:
+ Atomic<uint32_t, Relaxed> mClassOfServiceFlags{0};
+ Atomic<bool, Relaxed> mClassOfServiceIncremental{false};
+
+ public:
+ nsIInterfaceRequestor* SecurityCallbacks() { return mCallbacks; }
+ // Called when this transaction is inserted in the pending queue.
+ void OnPendingQueueInserted(const nsACString& aConnectionHashKey);
+
+ private:
+ TransactionObserverFunc mTransactionObserver;
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ bool mResolvedByTRR{false};
+ Atomic<nsIRequest::TRRMode, Relaxed> mEffectiveTRRMode{
+ nsIRequest::TRR_DEFAULT_MODE};
+ Atomic<TRRSkippedReason, Relaxed> mTRRSkipReason{nsITRRSkipReason::TRR_UNSET};
+ bool mEchConfigUsed = false;
+
+ bool m0RTTInProgress{false};
+ bool mDoNotTryEarlyData{false};
+ enum {
+ EARLY_NONE,
+ EARLY_SENT,
+ EARLY_ACCEPTED,
+ EARLY_425
+ } mEarlyDataDisposition{EARLY_NONE};
+
+ HttpTrafficCategory mTrafficCategory{HttpTrafficCategory::eInvalid};
+ bool mThroughCaptivePortal;
+ Atomic<int32_t> mProxyConnectResponseCode{0};
+
+ OnPushCallback mOnPushCallback;
+ nsTHashMap<uint32_t, RefPtr<Http2PushedStreamWrapper>> mIDToStreamMap;
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ Atomic<uint32_t, Relaxed> mHTTPSSVCReceivedStage{HTTPSSVC_NOT_USED};
+ bool m421Received = false;
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> mHTTPSSVCRecord;
+ nsTArray<RefPtr<nsISVCBRecord>> mRecordsForRetry;
+ bool mDontRetryWithDirectRoute = false;
+ bool mFastFallbackTriggered = false;
+ bool mHttp3BackupTimerCreated = false;
+ nsCOMPtr<nsITimer> mFastFallbackTimer;
+ nsCOMPtr<nsITimer> mHttp3BackupTimer;
+ RefPtr<nsHttpConnectionInfo> mBackupConnInfo;
+ RefPtr<HTTPSRecordResolver> mResolver;
+ TRANSACTION_RESTART_REASON mRestartReason = TRANSACTION_RESTART_NONE;
+
+ nsTHashMap<nsUint32HashKey, uint32_t> mEchRetryCounterMap;
+
+ bool mSupportsHTTP3 = false;
+ Atomic<bool, Relaxed> mIsForWebTransport{false};
+
+ bool mEarlyDataWasAvailable = false;
+ bool ShouldRestartOn0RttError(nsresult reason);
+
+ nsCOMPtr<nsIEarlyHintObserver> mEarlyHintObserver;
+ // This hash key is set when a transaction is inserted into the connection
+ // entry's pending queue.
+ // See nsHttpConnectionMgr::GetOrCreateConnectionEntry(). A transaction could
+ // be associated with the connection entry whose hash key is not the same as
+ // this transaction's.
+ nsCString mHashKeyOfConnectionEntry;
+
+ nsCOMPtr<WebTransportSessionEventListener> mWebTransportSessionEventListener;
+};
+
+} // namespace mozilla::net
+
+#endif // nsHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl b/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl
new file mode 100644
index 0000000000..025dcebf51
--- /dev/null
+++ b/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+class HttpBackgroundChannelParent;
+class HttpChannelParent;
+}
+}
+%}
+
+[ptr] native HttpChannelParent(mozilla::net::HttpChannelParent);
+[ptr] native HttpBackgroundChannelParent(mozilla::net::HttpBackgroundChannelParent);
+
+/*
+ * Registrar for pairing HttpChannelParent and HttpBackgroundChannelParent via
+ * channel Id. HttpChannelParent::OnBackgroundParentReady and
+ * HttpBackgroundChannelParent::LinkToChannel will be invoked to notify the
+ * existence of associated channel object.
+ */
+[uuid(8acaa9b1-f0c4-4ade-baeb-39b0d4b96e5b)]
+interface nsIBackgroundChannelRegistrar : nsISupports
+{
+ /*
+ * Link the provided channel parent actor with the given channel Id.
+ * callbacks will be invoked immediately when the HttpBackgroundChannelParent
+ * associated with the same channel Id is found. Store the HttpChannelParent
+ * until a matched linkBackgroundChannel is invoked.
+ *
+ * @param aKey the channel Id
+ * @param aChannel the channel parent actor to be paired
+ */
+ [noscript,notxpcom,nostdcall] void linkHttpChannel(in uint64_t aKey,
+ in HttpChannelParent aChannel);
+
+ /*
+ * Link the provided background channel with the given channel Id.
+ * callbacks will be invoked immediately when the HttpChannelParent associated
+ * with the same channel Id is found. Store the HttpBackgroundChannelParent
+ * until a matched linkHttpChannel is invoked.
+ *
+ * @param aKey the channel Id
+ * @param aBgChannel the background channel to be paired
+ */
+ [noscript,notxpcom,nostdcall] void linkBackgroundChannel(in uint64_t aKey,
+ in HttpBackgroundChannelParent aBgChannel);
+
+ /*
+ * Delete previous stored HttpChannelParent or HttpBackgroundChannelParent
+ * if no need to wait for the paired channel object, e.g. background channel
+ * is destroyed before pairing is completed.
+ *
+ * @param aKey the channel Id
+ */
+ [noscript,notxpcom,nostdcall] void deleteChannel(in uint64_t aKey);
+
+};
diff --git a/netwerk/protocol/http/nsIBinaryHttp.idl b/netwerk/protocol/http/nsIBinaryHttp.idl
new file mode 100644
index 0000000000..895f25f2d2
--- /dev/null
+++ b/netwerk/protocol/http/nsIBinaryHttp.idl
@@ -0,0 +1,40 @@
+/* -*- 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(f6f899cc-683a-43da-9206-0eb0c09cc758)]
+interface nsIBinaryHttpRequest : nsISupports {
+ readonly attribute ACString method;
+ readonly attribute ACString scheme;
+ readonly attribute ACString authority;
+ readonly attribute ACString path;
+ readonly attribute Array<ACString> headerNames;
+ readonly attribute Array<ACString> headerValues;
+ readonly attribute Array<octet> content;
+};
+
+[scriptable, uuid(6ca85d9c-cdc5-45d4-9adc-005abedce9c9)]
+interface nsIBinaryHttpResponse : nsISupports {
+ readonly attribute uint16_t status;
+ readonly attribute Array<ACString> headerNames;
+ readonly attribute Array<ACString> headerValues;
+ readonly attribute Array<octet> content;
+};
+
+// Implements Binary Representation of HTTP Messages (RFC 9292).
+// In normal operation, encodeRequest and decodeResponse are expected to be
+// used. For testing, decodeRequest and encodeResponse are available as well.
+// Thread safety: this interface may be used on any thread, but objects
+// returned by it are not inherently thread-safe and should only be used on the
+// threads they were created on.
+[scriptable, builtinclass, uuid(b43b3f73-8160-4ab2-9f5d-4129a9708081)]
+interface nsIBinaryHttp : nsISupports {
+ Array<octet> encodeRequest(in nsIBinaryHttpRequest request);
+ nsIBinaryHttpRequest decodeRequest(in Array<octet> request);
+
+ nsIBinaryHttpResponse decodeResponse(in Array<octet> response);
+ Array<octet> encodeResponse(in nsIBinaryHttpResponse response);
+};
diff --git a/netwerk/protocol/http/nsICorsPreflightCallback.h b/netwerk/protocol/http/nsICorsPreflightCallback.h
new file mode 100644
index 0000000000..aa16dbf12d
--- /dev/null
+++ b/netwerk/protocol/http/nsICorsPreflightCallback.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsICorsPreflightCallback_h__
+#define nsICorsPreflightCallback_h__
+
+#include "nsISupports.h"
+#include "nsID.h"
+#include "nsError.h"
+
+#define NS_ICORSPREFLIGHTCALLBACK_IID \
+ { \
+ 0x3758cfbb, 0x259f, 0x4074, { \
+ 0xa8, 0xc0, 0x98, 0xe0, 0x4b, 0x3c, 0xc0, 0xe3 \
+ } \
+ }
+
+class nsICorsPreflightCallback : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(nsICorsPreflightCallback);
+ NS_IMETHOD OnPreflightSucceeded() = 0;
+ NS_IMETHOD OnPreflightFailed(nsresult aError) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICorsPreflightCallback,
+ NS_ICORSPREFLIGHTCALLBACK_IID);
+
+#endif
diff --git a/netwerk/protocol/http/nsIEarlyHintObserver.idl b/netwerk/protocol/http/nsIEarlyHintObserver.idl
new file mode 100644
index 0000000000..1cc206ae56
--- /dev/null
+++ b/netwerk/protocol/http/nsIEarlyHintObserver.idl
@@ -0,0 +1,16 @@
+/* -*- 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"
+
+[scriptable, uuid(97b6be6f-283c-45dd-81a7-4bb2d87d42f8)]
+interface nsIEarlyHintObserver : nsISupports
+{
+ /**
+ * This method is called when the transaction has early hint (i.e. the
+ * '103 Early Hint' informational response) headers.
+ */
+ void earlyHint(in ACString linkHeader, in ACString referrerPolicy, in ACString cspHeader);
+};
diff --git a/netwerk/protocol/http/nsIHttpActivityObserver.idl b/netwerk/protocol/http/nsIHttpActivityObserver.idl
new file mode 100644
index 0000000000..533e6af135
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpActivityObserver.idl
@@ -0,0 +1,214 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 HttpActivityArgs;
+} // namespace net
+} // namespace mozilla
+%}
+
+[ref] native HttpActivityArgs(const mozilla::net::HttpActivityArgs);
+
+/**
+ * nsIHttpActivityObserver
+ *
+ * This interface provides a way for http activities to be reported
+ * to observers.
+ */
+[scriptable, uuid(412880C8-6C36-48d8-BF8F-84F91F892503)]
+interface nsIHttpActivityObserver : nsISupports
+{
+ /**
+ * observe activity from the http transport
+ *
+ * @param aHttpChannel
+ * nsISupports interface for the the http channel that
+ * generated this activity
+ * @param aActivityType
+ * The value of this aActivityType will be one of
+ * ACTIVITY_TYPE_SOCKET_TRANSPORT or
+ * ACTIVITY_TYPE_HTTP_TRANSACTION
+ * @param aActivitySubtype
+ * The value of this aActivitySubtype, will be depend
+ * on the value of aActivityType. When aActivityType
+ * is ACTIVITY_TYPE_SOCKET_TRANSPORT
+ * aActivitySubtype will be one of the
+ * nsISocketTransport::STATUS_???? values defined in
+ * nsISocketTransport.idl
+ * OR when aActivityType
+ * is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * aActivitySubtype will be one of the
+ * nsIHttpActivityObserver::ACTIVITY_SUBTYPE_???? values
+ * defined below
+ * @param aTimestamp
+ * microseconds past the epoch of Jan 1, 1970
+ * @param aExtraSizeData
+ * Any extra size data optionally available with
+ * this activity
+ * @param aExtraStringData
+ * Any extra string data optionally available with
+ * this activity
+ */
+ [must_use]
+ void observeActivity(in nsISupports aHttpChannel,
+ in uint32_t aActivityType,
+ in uint32_t aActivitySubtype,
+ in PRTime aTimestamp,
+ in uint64_t aExtraSizeData,
+ in ACString aExtraStringData);
+
+ /**
+ * This attribute is true when this interface is active and should
+ * observe http activities. When false, observeActivity() should not
+ * be called. It is present for compatibility reasons and should be
+ * implemented only by nsHttpActivityDistributor.
+ */
+ [must_use] readonly attribute boolean isActive;
+
+ /**
+ * This function is for internal use only. Every time a http transaction
+ * is created in socket process, we use this function to set the value of
+ * |isActive|. We need this since the real value of |isActive| is
+ * only available in parent process.
+ */
+ [noscript] void setIsActive(in boolean aActived);
+
+ /**
+ * This function is used when the real http channel is not available.
+ * We use the information in |HttpActivityArgs| to get the http channel or
+ * create a |NullHttpChannel|.
+ *
+ * @param aArgs
+ * See the definition of |HttpActivityArgs| in PSocketProcess.ipdl.
+ */
+ [noscript, must_use]
+ void observeActivityWithArgs(in HttpActivityArgs aArgs,
+ in uint32_t aActivityType,
+ in uint32_t aActivitySubtype,
+ in PRTime aTimestamp,
+ in uint64_t aExtraSizeData,
+ in ACString aExtraStringData);
+
+ /**
+ * This function is for testing only. We use this function to observe the
+ * activities of HTTP connections. To receive this notification,
+ * observeConnection should be set to true.
+ */
+ [must_use]
+ void observeConnectionActivity(in ACString aHost,
+ in int32_t aPort,
+ in boolean aSSL,
+ in boolean aHasECH,
+ in boolean aIsHttp3,
+ in uint32_t aActivityType,
+ in uint32_t aActivitySubtype,
+ in PRTime aTimestamp,
+ in ACString aExtraStringData);
+
+ const unsigned long ACTIVITY_TYPE_SOCKET_TRANSPORT = 0x0001;
+ const unsigned long ACTIVITY_TYPE_HTTP_TRANSACTION = 0x0002;
+ const unsigned long ACTIVITY_TYPE_HTTP_CONNECTION = 0x0003;
+
+ const unsigned long ACTIVITY_SUBTYPE_REQUEST_HEADER = 0x5001;
+ const unsigned long ACTIVITY_SUBTYPE_REQUEST_BODY_SENT = 0x5002;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_START = 0x5003;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_HEADER = 0x5004;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_COMPLETE = 0x5005;
+ const unsigned long ACTIVITY_SUBTYPE_TRANSACTION_CLOSE = 0x5006;
+ const unsigned long ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER = 0x5007;
+ const unsigned long ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED = 0x5008;
+ const unsigned long ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED = 0x5009;
+ const unsigned long ACTIVITY_SUBTYPE_ECH_SET = 0x500A;
+ const unsigned long ACTIVITY_SUBTYPE_CONNECTION_CREATED = 0x500B;
+
+ /**
+ * When aActivityType is ACTIVITY_TYPE_SOCKET_TRANSPORT
+ * and aActivitySubtype is STATUS_SENDING_TO
+ * aExtraSizeData will contain the count of bytes sent
+ * There may be more than one of these activities reported
+ * for a single http transaction, each aExtraSizeData
+ * represents only that portion of the total bytes sent
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_REQUEST_HEADER
+ * aExtraStringData will contain the text of the header
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_RESPONSE_HEADER
+ * aExtraStringData will contain the text of the header
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
+ * aExtraSizeData will contain the count of total bytes received
+ */
+};
+
+%{C++
+
+#define NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_SOCKET_TRANSPORT
+#define NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_HTTP_TRANSACTION
+#define NS_ACTIVITY_TYPE_HTTP_CONNECTION \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_HTTP_CONNECTION
+
+#define NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_REQUEST_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_REQUEST_BODY_SENT
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_START
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
+#define NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
+#define NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED
+#define NS_HTTP_ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED
+#define NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_ECH_SET
+#define NS_HTTP_ACTIVITY_SUBTYPE_CONNECTION_CREATED \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_CONNECTION_CREATED
+%}
+
+/**
+ * nsIHttpActivityDistributor
+ *
+ * This interface provides a way to register and unregister observers to the
+ * http activities.
+ */
+[scriptable, builtinclass, uuid(7C512CB8-582A-4625-B5B6-8639755271B5)]
+interface nsIHttpActivityDistributor : nsIHttpActivityObserver
+{
+ void addObserver(in nsIHttpActivityObserver aObserver);
+ void removeObserver(in nsIHttpActivityObserver aObserver);
+
+ /**
+ * C++ friendly getter
+ */
+ [noscript, notxpcom] bool Activated();
+ [noscript, notxpcom] bool ObserveProxyResponseEnabled();
+ [noscript, notxpcom] bool ObserveConnectionEnabled();
+
+ /**
+ * When true, the ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER will be sent to
+ * the observers.
+ */
+ [must_use] attribute boolean observeProxyResponse;
+
+ /**
+ * When true, the ACTIVITY_TYPE_HTTP_CONNECTION will be sent to
+ * the observers.
+ */
+ [must_use] attribute boolean observeConnection;
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthManager.idl b/netwerk/protocol/http/nsIHttpAuthManager.idl
new file mode 100644
index 0000000000..5acd652942
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthManager.idl
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+
+/**
+ * nsIHttpAuthManager
+ *
+ * This service provides access to cached HTTP authentication
+ * user credentials (domain, username, password) for sites
+ * visited during the current browser session.
+ *
+ * This interface exists to provide other HTTP stacks with the
+ * ability to share HTTP authentication credentials with Necko.
+ * This is currently used by the Java plugin (version 1.5 and
+ * higher) to avoid duplicate authentication prompts when the
+ * Java client fetches content from a HTTP site that the user
+ * has already logged into.
+ */
+[scriptable, builtinclass, uuid(54f90444-c52b-4d2d-8916-c59a2bb25938)]
+interface nsIHttpAuthManager : nsISupports
+{
+ /**
+ * Lookup auth identity.
+ *
+ * @param aScheme
+ * the URL scheme (e.g., "http"). NOTE: for proxy authentication,
+ * this should be "http" (this includes authentication for CONNECT
+ * tunneling).
+ * @param aHost
+ * the host of the server issuing a challenge (ASCII only).
+ * @param aPort
+ * the port of the server issuing a challenge.
+ * @param aAuthType
+ * optional string identifying auth type used (e.g., "basic")
+ * @param aRealm
+ * optional string identifying auth realm.
+ * @param aPath
+ * optional string identifying auth path. empty for proxy auth.
+ * @param aUserDomain
+ * return value containing user domain.
+ * @param aUserName
+ * return value containing user name.
+ * @param aUserPassword
+ * return value containing user password.
+ * @param aIsPrivate
+ * whether to look up a private or public identity (they are
+ * stored separately, for use by private browsing)
+ * @param aPrincipal
+ * the principal from which to derive information about which
+ * app/mozbrowser is in use for this request
+ */
+ [must_use] void getAuthIdentity(in ACString aScheme,
+ in ACString aHost,
+ in int32_t aPort,
+ in ACString aAuthType,
+ in ACString aRealm,
+ in ACString aPath,
+ out AString aUserDomain,
+ out AString aUserName,
+ out AString aUserPassword,
+ [optional] in bool aIsPrivate,
+ [optional] in nsIPrincipal aPrincipal);
+
+ /**
+ * Store auth identity.
+ *
+ * @param aScheme
+ * the URL scheme (e.g., "http"). NOTE: for proxy authentication,
+ * this should be "http" (this includes authentication for CONNECT
+ * tunneling).
+ * @param aHost
+ * the host of the server issuing a challenge (ASCII only).
+ * @param aPort
+ * the port of the server issuing a challenge.
+ * @param aAuthType
+ * optional string identifying auth type used (e.g., "basic")
+ * @param aRealm
+ * optional string identifying auth realm.
+ * @param aPath
+ * optional string identifying auth path. empty for proxy auth.
+ * @param aUserDomain
+ * optional string containing user domain.
+ * @param aUserName
+ * optional string containing user name.
+ * @param aUserPassword
+ * optional string containing user password.
+ * @param aIsPrivate
+ * whether to store a private or public identity (they are
+ * stored separately, for use by private browsing)
+ * @param aPrincipal
+ * the principal from which to derive information about which
+ * app/mozbrowser is in use for this request
+ */
+ [must_use] void setAuthIdentity(in ACString aScheme,
+ in ACString aHost,
+ in int32_t aPort,
+ in ACString aAuthType,
+ in ACString aRealm,
+ in ACString aPath,
+ in AString aUserDomain,
+ in AString aUserName,
+ in AString aUserPassword,
+ [optional] in boolean aIsPrivate,
+ [optional] in nsIPrincipal aPrincipal);
+
+ /**
+ * Clear all auth cache.
+ */
+ [must_use] void clearAll();
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl
new file mode 100644
index 0000000000..f479b6ff58
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl
@@ -0,0 +1,122 @@
+/* -*- 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 "nsIProxiedChannel.idl"
+#include "nsIRequest.idl"
+
+interface nsILoadGroup;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+[uuid(701093ac-5c7f-429c-99e3-423b041fccb4)]
+interface nsIHttpAuthenticableChannel : nsIProxiedChannel
+{
+ /**
+ * If the channel being authenticated is using SSL.
+ */
+ [must_use] readonly attribute boolean isSSL;
+
+ /**
+ * Returns if the proxy HTTP method used is CONNECT. If no proxy is being
+ * used it must return PR_FALSE.
+ */
+ [must_use] readonly attribute boolean proxyMethodIsConnect;
+
+ /**
+ * Cancels the current request. See nsIRequest.
+ */
+ [must_use] void cancel(in nsresult aStatus);
+
+ /**
+ * The load flags of this request. See nsIRequest.
+ */
+ [must_use] readonly attribute nsLoadFlags loadFlags;
+
+ /**
+ * The URI corresponding to the channel. See nsIChannel.
+ */
+ [must_use] readonly attribute nsIURI URI;
+
+ /**
+ * The load group of this request. It is here for querying its
+ * notificationCallbacks. See nsIRequest.
+ */
+ [must_use] readonly attribute nsILoadGroup loadGroup;
+
+ /**
+ * The notification callbacks for the channel. See nsIChannel.
+ */
+ [must_use] readonly attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * The HTTP request method. See nsIHttpChannel.
+ */
+ [must_use] readonly attribute ACString requestMethod;
+
+ /**
+ * The "Server" response header.
+ * Return NS_ERROR_NOT_AVAILABLE if not available.
+ */
+ [must_use] readonly attribute ACString serverResponseHeader;
+
+ /**
+ * The Proxy-Authenticate response header.
+ */
+ [must_use] readonly attribute ACString proxyChallenges;
+
+ /**
+ * The WWW-Authenticate response header.
+ */
+ [must_use] readonly attribute ACString WWWChallenges;
+
+ /**
+ * Sets the Proxy-Authorization request header. An empty string
+ * will clear it.
+ */
+ [must_use] void setProxyCredentials(in ACString credentials);
+
+ /**
+ * Sets the Authorization request header. An empty string
+ * will clear it.
+ */
+ [must_use] void setWWWCredentials(in ACString credentials);
+
+ /**
+ * Called when authentication information is ready and has been set on this
+ * object using setWWWCredentials/setProxyCredentials. Implementations can
+ * continue with the request and send the given information to the server.
+ *
+ * It is called asynchronously from
+ * nsIHttpChannelAuthProvider::processAuthentication if that method returns
+ * NS_ERROR_IN_PROGRESS.
+ *
+ * @note Any exceptions thrown from this method should be ignored.
+ */
+ [must_use] void onAuthAvailable();
+
+ /**
+ * Notifies that the prompt was cancelled. It is called asynchronously
+ * from nsIHttpChannelAuthProvider::processAuthentication if that method
+ * returns NS_ERROR_IN_PROGRESS.
+ *
+ * @param userCancel
+ * If the user was cancelled has cancelled the authentication prompt.
+ */
+ [must_use] void onAuthCancelled(in boolean userCancel);
+
+ /**
+ * Tells the channel to drop and close any sticky connection, since this
+ * connection oriented schema cannot be negotiated second time on
+ * the same connection.
+ */
+ [must_use] void closeStickyConnection();
+
+ /**
+ * Tells the channel to mark the connection as allowed to restart on
+ * authentication retry. This is allowed when the request is a start
+ * of a new authentication round.
+ */
+ void connectionRestartable(in boolean restartable);
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthenticator.idl b/netwerk/protocol/http/nsIHttpAuthenticator.idl
new file mode 100644
index 0000000000..222bd3837c
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthenticator.idl
@@ -0,0 +1,221 @@
+/* -*- 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 nsIHttpAuthenticableChannel;
+interface nsIHttpAuthenticatorCallback;
+interface nsICancelable;
+
+/**
+ * nsIHttpAuthenticator
+ *
+ * Interface designed to allow for pluggable HTTP authentication modules.
+ * Implementations are registered under the ContractID:
+ *
+ * "@mozilla.org/network/http-authenticator;1?scheme=<auth-scheme>"
+ *
+ * where <auth-scheme> is the lower-cased value of the authentication scheme
+ * found in the server challenge per the rules of RFC 2617.
+ */
+[uuid(fef7db8a-a4e2-49d1-9685-19ed7e309b7d)]
+interface nsIHttpAuthenticator : nsISupports
+{
+ /**
+ * Upon receipt of a server challenge, this function is called to determine
+ * whether or not the current user identity has been rejected. If true,
+ * then the user will be prompted by the channel to enter (or revise) their
+ * identity. Following this, generateCredentials will be called.
+ *
+ * If the IDENTITY_IGNORED auth flag is set, then the aInvalidateIdentity
+ * return value will be ignored, and user prompting will be suppressed.
+ *
+ * @param aChannel
+ * the http channel that received the challenge.
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aSessionState
+ * see description below for generateCredentials.
+ * @param aContinuationState
+ * see description below for generateCredentials.
+ * @param aInvalidateIdentity
+ * return value indicating whether or not to prompt the user for a
+ * revised identity.
+ */
+ [must_use]
+ void challengeReceived(in nsIHttpAuthenticableChannel aChannel,
+ in ACString aChallenge,
+ in boolean aProxyAuth,
+ inout nsISupports aSessionState,
+ inout nsISupports aContinuationState,
+ out boolean aInvalidatesIdentity);
+
+ /**
+ * Called to generate the authentication credentials for a particular
+ * server/proxy challenge asynchronously. Credentials will be sent back
+ * to the server via an Authorization/Proxy-Authorization header.
+ *
+ * @param aChannel
+ * the http channel requesting credentials
+ * @param aCallback
+ * callback function to be called when credentials are available
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aDomain
+ * string containing the domain name (if appropriate)
+ * @param aUser
+ * string containing the user name
+ * @param aPassword
+ * string containing the password
+ * @param aSessionState
+ * state stored along side the user's identity in the auth cache
+ * for the lifetime of the browser session. if a new auth cache
+ * entry is created for this challenge, then this parameter will
+ * be null. on return, the result will be stored in the new auth
+ * cache entry. this parameter is non-null when an auth cache entry
+ * is being reused. currently modification of session state is not
+ * communicated to caller, thus caching credentials obtained by
+ * asynchronous way is not supported.
+ * @param aContinuationState
+ * state held by the channel between consecutive calls to
+ * generateCredentials, assuming multiple calls are required
+ * to authenticate. this state is held for at most the lifetime of
+ * the channel.
+ * @pram aCancel
+ * returns cancellable runnable object which caller can use to cancel
+ * calling aCallback when finished.
+ */
+ [must_use]
+ void generateCredentialsAsync(in nsIHttpAuthenticableChannel aChannel,
+ in nsIHttpAuthenticatorCallback aCallback,
+ in ACString aChallenge,
+ in boolean aProxyAuth,
+ in AString aDomain,
+ in AString aUser,
+ in AString aPassword,
+ in nsISupports aSessionState,
+ in nsISupports aContinuationState,
+ out nsICancelable aCancel);
+ /**
+ * Called to generate the authentication credentials for a particular
+ * server/proxy challenge. This is the value that will be sent back
+ * to the server via an Authorization/Proxy-Authorization header.
+ *
+ * This function may be called using a cached challenge provided the
+ * authenticator sets the REUSABLE_CHALLENGE flag.
+ *
+ * @param aChannel
+ * the http channel requesting credentials
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aDomain
+ * string containing the domain name (if appropriate)
+ * @param aUser
+ * string containing the user name
+ * @param aPassword
+ * string containing the password
+ * @param aSessionState
+ * state stored along side the user's identity in the auth cache
+ * for the lifetime of the browser session. if a new auth cache
+ * entry is created for this challenge, then this parameter will
+ * be null. on return, the result will be stored in the new auth
+ * cache entry. this parameter is non-null when an auth cache entry
+ * is being reused.
+ * @param aContinuationState
+ * state held by the channel between consecutive calls to
+ * generateCredentials, assuming multiple calls are required
+ * to authenticate. this state is held for at most the lifetime of
+ * the channel.
+ * @param aFlags
+ * authenticator may return one of the generate flags bellow.
+ */
+ [must_use]
+ ACString generateCredentials(in nsIHttpAuthenticableChannel aChannel,
+ in ACString aChallenge,
+ in boolean aProxyAuth,
+ in AString aDomain,
+ in AString aUser,
+ in AString aPassword,
+ inout nsISupports aSessionState,
+ inout nsISupports aContinuationState,
+ out unsigned long aFlags);
+
+ /**
+ * Generate flags
+ */
+
+ /**
+ * Indicates that the authenticator has used an out-of-band or internal
+ * source of identity and tells the consumer that it must not cache
+ * the returned identity because it might not be valid and would overwrite
+ * the cached identity. See bug 542318 comment 32.
+ */
+ const unsigned long USING_INTERNAL_IDENTITY = (1<<0);
+
+ /**
+ * Flags defining various properties of the authenticator.
+ */
+ [must_use] readonly attribute unsigned long authFlags;
+
+ /**
+ * A request based authentication scheme only authenticates an individual
+ * request (or a set of requests under the same authentication domain as
+ * defined by RFC 2617). BASIC and DIGEST are request based authentication
+ * schemes.
+ */
+ const unsigned long REQUEST_BASED = (1<<0);
+
+ /**
+ * A connection based authentication scheme authenticates an individual
+ * connection. Multiple requests may be issued over the connection without
+ * repeating the authentication steps. Connection based authentication
+ * schemes can associate state with the connection being authenticated via
+ * the aContinuationState parameter (see generateCredentials).
+ */
+ const unsigned long CONNECTION_BASED = (1<<1);
+
+ /**
+ * The credentials returned from generateCredentials may be reused with any
+ * other URLs within "the protection space" as defined by RFC 2617 section
+ * 1.2. If this flag is not set, then generateCredentials must be called
+ * for each request within the protection space. REUSABLE_CREDENTIALS
+ * implies REUSABLE_CHALLENGE.
+ */
+ const unsigned long REUSABLE_CREDENTIALS = (1<<2);
+
+ /**
+ * A challenge may be reused to later generate credentials in anticipation
+ * of a duplicate server challenge for URLs within "the protection space"
+ * as defined by RFC 2617 section 1.2.
+ */
+ const unsigned long REUSABLE_CHALLENGE = (1<<3);
+
+ /**
+ * This flag indicates that the identity of the user is not required by
+ * this authentication scheme.
+ */
+ const unsigned long IDENTITY_IGNORED = (1<<10);
+
+ /**
+ * This flag indicates that the identity of the user includes a domain
+ * attribute that the user must supply.
+ */
+ const unsigned long IDENTITY_INCLUDES_DOMAIN = (1<<11);
+
+ /**
+ * This flag indicates that the identity will be sent encrypted. It does
+ * not make sense to combine this flag with IDENTITY_IGNORED.
+ */
+ const unsigned long IDENTITY_ENCRYPTED = (1<<12);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl
new file mode 100644
index 0000000000..7ed707eacf
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -0,0 +1,497 @@
+/* -*- 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 "nsIChannel.idl"
+
+interface nsIHttpHeaderVisitor;
+interface nsIReferrerInfo;
+
+%{C++
+#include "GeckoProfiler.h"
+%}
+
+native UniqueProfileChunkedBuffer(mozilla::UniquePtr<mozilla::ProfileChunkedBuffer>);
+
+/**
+ * nsIHttpChannel
+ *
+ * This interface allows for the modification of HTTP request parameters and
+ * the inspection of the resulting HTTP response status and headers when they
+ * become available.
+ */
+[builtinclass, scriptable, uuid(c5a4a073-4539-49c7-a3f2-cec3f0619c6c)]
+interface nsIHttpChannel : nsIIdentChannel
+{
+ /**************************************************************************
+ * REQUEST CONFIGURATION
+ *
+ * Modifying request parameters after asyncOpen has been called is an error.
+ */
+
+ /**
+ * Set/get the HTTP request method (default is "GET"). Both setter and
+ * getter are case sensitive.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * NOTE: The data for a "POST" or "PUT" request can be configured via
+ * nsIUploadChannel; however, after setting the upload data, it may be
+ * necessary to set the request method explicitly. The documentation
+ * for nsIUploadChannel has further details.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if set after the channel has been opened.
+ */
+ [must_use] attribute ACString requestMethod;
+
+ /**
+ * Get/set the referrer information. This contains the referrer (URI) of the
+ * resource from which this channel's URI was obtained (see RFC2616 section
+ * 14.36) and the referrer policy applied to the referrer.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * Setting this attribute will clone new referrerInfo object by default.
+ *
+ * NOTE: The channel may silently refuse to set the Referer header if the
+ * URI does not pass certain security checks (e.g., a "https://" URL will
+ * never be sent as the referrer for a plaintext HTTP request). The
+ * implementation is not required to throw an exception when the referrer
+ * URI is rejected.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if set after the channel has been opened.
+ * @throws NS_ERROR_FAILURE if used for setting referrer during
+ * visitRequestHeaders. Getting the value will not throw.
+ */
+ [must_use, infallible] attribute nsIReferrerInfo referrerInfo;
+
+ /**
+ * Set referrer Info without clone new object.
+ * Use this api only when you are passing a referrerInfo to the channel with
+ * 1-1 relationship. Don't use this api if you will reuse the referrer info
+ * object later. For example when to use:
+ * channel.setReferrerInfoWithoutClone(new ReferrerInfo());
+ *
+ */
+ [must_use, noscript]
+ void setReferrerInfoWithoutClone(in nsIReferrerInfo aReferrerInfo);
+
+ /**
+ * Returns the network protocol used to fetch the resource as identified
+ * by the ALPN Protocol ID.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute ACString protocolVersion;
+
+ /**
+ * size consumed by the response header fields and the response payload body
+ */
+ [must_use] readonly attribute uint64_t transferSize;
+
+ /**
+ * size consumed by the request header fields and the request payload body
+ */
+ [must_use] readonly attribute uint64_t requestSize;
+
+ /**
+ * The size of the message body received by the client,
+ * after removing any applied content-codings
+ */
+ [must_use] readonly attribute uint64_t decodedBodySize;
+
+ /**
+ * The size in octets of the payload body, prior to removing content-codings
+ */
+ [must_use] readonly attribute uint64_t encodedBodySize;
+
+ /**
+ * Get the value of a particular request header.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to query (e.g.,
+ * "Cache-Control").
+ *
+ * @return the value of the request header.
+ * @throws NS_ERROR_NOT_AVAILABLE if the header is not set.
+ */
+ [must_use] ACString getRequestHeader(in ACString aHeader);
+
+ /**
+ * Set the value of a particular request header.
+ *
+ * This method allows, for example, the cookies module to add "Cookie"
+ * headers to the outgoing HTTP request.
+ *
+ * This method may only be called before the channel is opened.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to set (e.g.,
+ * "Cookie").
+ * @param aValue
+ * The request header value to set (e.g., "X=1").
+ * @param aMerge
+ * If true, the new header value will be merged with any existing
+ * values for the specified header. This flag is ignored if the
+ * specified header does not support merging (e.g., the "Content-
+ * Type" header can only have one value). The list of headers for
+ * which this flag is ignored is an implementation detail. If this
+ * flag is false, then the header value will be replaced with the
+ * contents of |aValue|.
+ *
+ * If aValue is empty and aMerge is false, the header will be cleared.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if called after the channel has been
+ * opened.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ [must_use] void setRequestHeader(in ACString aHeader,
+ in ACString aValue,
+ in boolean aMerge);
+
+ /**
+ * Creates and sets new ReferrerInfo object
+ * @param aUrl referrer url
+ * @param aPolicy referrer policy of the created object
+ * @param aSendReferrer indicates if the referrer should not be sent or not
+ * even when it's available.
+ */
+ [must_use] void setNewReferrerInfo(in ACString aUrl,
+ in nsIReferrerInfo_ReferrerPolicyIDL aPolicy,
+ in boolean aSendReferrer);
+
+ /**
+ * Set a request header with empty value.
+ *
+ * This should be used with caution in the cases where the behavior of
+ * setRequestHeader ignoring empty header values is undesirable.
+ *
+ * This method may only be called before the channel is opened.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to set (e.g.,
+ * "Cookie").
+ *
+ * @throws NS_ERROR_IN_PROGRESS if called after the channel has been
+ * opened.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ [must_use] void setEmptyRequestHeader(in ACString aHeader);
+
+ /**
+ * Call this method to visit all request headers. Calling setRequestHeader
+ * while visiting request headers has undefined behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ */
+ [must_use] void visitRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to visit all non-default (UA-provided) request headers.
+ * Calling setRequestHeader while visiting request headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ */
+ [must_use]
+ void visitNonDefaultRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to see if we need to strip the request body headers
+ * for the new http channel. This should be called during redirection.
+ */
+ [must_use] bool ShouldStripRequestBodyHeader(in ACString aMethod);
+
+ /**
+ * This attribute of the channel indicates whether or not
+ * the underlying HTTP transaction should be honor stored Strict Transport
+ * Security directives for its principal. It defaults to true. Using
+ * OCSP to bootstrap the HTTPs is the likely use case for setting it to
+ * false.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * @throws NS_ERROR_IN_PROGRESS or NS_ERROR_ALREADY_OPENED
+ * if called after the channel has been opened.
+ */
+ [must_use] attribute boolean allowSTS;
+
+ /**
+ * This attribute specifies the number of redirects this channel is allowed
+ * to make. If zero, the channel will fail to redirect and will generate
+ * a NS_ERROR_REDIRECT_LOOP failure status.
+ *
+ * NOTE: An HTTP redirect results in a new channel being created. If the
+ * new channel supports nsIHttpChannel, then it will be assigned a value
+ * to its |redirectionLimit| attribute one less than the value of the
+ * redirected channel's |redirectionLimit| attribute. The initial value
+ * for this attribute may be a configurable preference (depending on the
+ * implementation).
+ */
+ [must_use] attribute unsigned long redirectionLimit;
+
+ /**************************************************************************
+ * RESPONSE INFO
+ *
+ * Accessing response info before the onStartRequest event is an error.
+ */
+
+ /**
+ * Get the HTTP response code (e.g., 200).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute unsigned long responseStatus;
+
+ /**
+ * Get the HTTP response status text (e.g., "OK").
+ *
+ * NOTE: This returns the raw (possibly 8-bit) text from the server. There
+ * are no assumptions made about the charset of the returned text. You
+ * have been warned!
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute ACString responseStatusText;
+
+ /**
+ * Returns true if the HTTP response code indicates success. The value of
+ * nsIRequest::status will be NS_OK even when processing a 404 response
+ * because a 404 response may include a message body that (in some cases)
+ * should be shown to the user.
+ *
+ * Use this attribute to distinguish server error pages from normal pages,
+ * instead of comparing the response status manually against the set of
+ * valid response codes, if that is required by your application.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute boolean requestSucceeded;
+
+ /** Indicates whether channel should be treated as the main one for the
+ * current document. If manually set to true, will always remain true. Otherwise,
+ * will be true if LOAD_DOCUMENT_URI is set in the channel's loadflags.
+ */
+ [must_use] attribute boolean isMainDocumentChannel;
+
+ /**
+ * Get the value of a particular response header.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to query (e.g.,
+ * "Set-Cookie").
+ *
+ * @return the value of the response header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest) or if the header is
+ * not set in the response.
+ */
+ [must_use] ACString getResponseHeader(in ACString header);
+
+ /**
+ * Set the value of a particular response header.
+ *
+ * This method allows, for example, the HTML content sink to inform the HTTP
+ * channel about HTTP-EQUIV headers found in HTML <META> tags.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to set (e.g.,
+ * "Cache-control").
+ * @param aValue
+ * The response header value to set (e.g., "no-cache").
+ * @param aMerge
+ * If true, the new header value will be merged with any existing
+ * values for the specified header. This flag is ignored if the
+ * specified header does not support merging (e.g., the "Content-
+ * Type" header can only have one value). The list of headers for
+ * which this flag is ignored is an implementation detail. If this
+ * flag is false, then the header value will be replaced with the
+ * contents of |aValue|.
+ *
+ * If aValue is empty and aMerge is false, the header will be cleared.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ * @throws NS_ERROR_ILLEGAL_VALUE if changing the value of this response
+ * header is not allowed.
+ * @throws NS_ERROR_FAILURE if called during visitResponseHeaders,
+ * VisitOriginalResponseHeaders or getOriginalResponseHeader.
+ */
+ [must_use] void setResponseHeader(in ACString header,
+ in ACString value,
+ in boolean merge);
+
+ /**
+ * Call this method to visit all response headers. Calling
+ * setResponseHeader while visiting response headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] void visitResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Get the value(s) of a particular response header in the form and order
+ * it has been received from the remote peer. There can be multiple headers
+ * with the same name.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to query (e.g.,
+ * "Set-Cookie").
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest) or if the header is
+ * not set in the response.
+ */
+ [must_use] void getOriginalResponseHeader(in ACString aHeader,
+ in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to visit all response headers in the form and order as
+ * they have been received from the remote peer.
+ * Calling setResponseHeader while visiting response headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use]
+ void visitOriginalResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Returns true if the server sent a "Cache-Control: no-store" response
+ * header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] boolean isNoStoreResponse();
+
+ /**
+ * Returns true if the server sent the equivalent of a "Cache-control:
+ * no-cache" response header. Equivalent response headers include:
+ * "Pragma: no-cache", "Expires: 0", and "Expires" with a date value
+ * in the past relative to the value of the "Date" header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] boolean isNoCacheResponse();
+
+ /**
+ * Returns true if the server sent a "Cache-Control: private" response
+ * header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] boolean isPrivateResponse();
+
+ /**
+ * Instructs the channel to immediately redirect to a new destination.
+ * Can only be called on channels that have not yet called their
+ * listener's OnStartRequest(). Generally that means the latest time
+ * this can be used is one of:
+ * "http-on-examine-response"
+ * "http-on-examine-merged-response"
+ * "http-on-examine-cached-response"
+ *
+ * When non-null URL is set before AsyncOpen:
+ * we attempt to redirect to the targetURI before we even start building
+ * and sending the request to the cache or the origin server.
+ * If the redirect is vetoed, we fail the channel.
+ *
+ * When set between AsyncOpen and first call to OnStartRequest being called:
+ * we attempt to redirect before we start delivery of network or cached
+ * response to the listener. If vetoed, we continue with delivery of
+ * the original content to the channel listener.
+ *
+ * When passed aTargetURI is null the channel behaves normally (can be
+ * rewritten).
+ *
+ * This method provides no explicit conflict resolution. The last
+ * caller to call it wins.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called after the channel has already
+ * started to deliver the content to its listener.
+ */
+ [must_use] void redirectTo(in nsIURI aTargetURI);
+
+ /**
+ * Flags a channel to be upgraded to HTTPS.
+ *
+ * Upgrading to a secure channel must happen before or during
+ * "http-on-modify-request". If redirectTo is called early as well, it
+ * will win and upgradeToSecure will be a no-op.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called after the channel has already
+ * started to deliver the content to its listener.
+ */
+ [must_use] void upgradeToSecure();
+
+ /**
+ * Identifies the request context for this load.
+ */
+ [noscript, must_use] attribute uint64_t requestContextID;
+
+ /**
+ * ID of the top-level document's inner window. Identifies the content
+ * this channels is being load in.
+ */
+ [must_use] attribute uint64_t topLevelContentWindowId;
+
+ /**
+ * ID of the browser for this channel.
+ *
+ * NOTE: The setter of this attribute is currently for xpcshell test only.
+ * Don't alter it otherwise.
+ */
+ [must_use] attribute uint64_t browserId;
+
+ /**
+ * In e10s, the information that the CORS response blocks the load is in the
+ * parent, which doesn't know the true window id of the request, so we may
+ * need to proxy the request to the child.
+ *
+ * @param aMessage
+ * The message to print in the console.
+ *
+ * @param aCategory
+ * The category under which the message should be displayed.
+ *
+ * @param aIsWarning
+ * When true, this is a warning message.
+ */
+ void logBlockedCORSRequest(in AString aMessage,
+ in ACString aCategory,
+ in boolean aIsWarning);
+
+ void logMimeTypeMismatch(in ACString aMessageName,
+ in boolean aWarning,
+ in AString aURL,
+ in AString aContentType);
+
+ [notxpcom, nostdcall] void setSource(in UniqueProfileChunkedBuffer aSource);
+
+ [must_use] attribute AString classicScriptHintCharset;
+
+ [must_use] attribute AString documentCharacterSet;
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
new file mode 100644
index 0000000000..67a1ae217d
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
@@ -0,0 +1,86 @@
+/* -*- 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 "nsICancelable.idl"
+
+interface nsIHttpChannel;
+interface nsIHttpAuthenticableChannel;
+
+/**
+ * nsIHttpChannelAuthProvider
+ *
+ * This interface is intended for providing authentication for http-style
+ * channels, like nsIHttpChannel and nsIWebSocket, which implement the
+ * nsIHttpAuthenticableChannel interface.
+ *
+ * When requesting pages AddAuthorizationHeaders MUST be called
+ * in order to get the http cached headers credentials. When the request is
+ * unsuccessful because of receiving either a 401 or 407 http response code
+ * ProcessAuthentication MUST be called and the page MUST be requested again
+ * with the new credentials that the user has provided. After a successful
+ * request, checkForSuperfluousAuth MAY be called, and disconnect MUST be
+ * called.
+ */
+
+[uuid(788f331b-2e1f-436c-b405-4f88a31a105b)]
+interface nsIHttpChannelAuthProvider : nsICancelable
+{
+ /**
+ * Initializes the http authentication support for the channel.
+ * Implementations must hold a weak reference of the channel.
+ */
+ [must_use] void init(in nsIHttpAuthenticableChannel channel);
+
+ /**
+ * Upon receipt of a server challenge, this function is called to determine
+ * the credentials to send.
+ *
+ * @param httpStatus
+ * the http status received.
+ * @param sslConnectFailed
+ * if the last ssl tunnel connection attempt was or not successful.
+ * @param callback
+ * the callback to be called when it returns NS_ERROR_IN_PROGRESS.
+ * The implementation must hold a weak reference.
+ *
+ * @returns NS_OK if the credentials were got and set successfully.
+ * NS_ERROR_IN_PROGRESS if the credentials are going to be asked to
+ * the user. The channel reference must be
+ * alive until the feedback from
+ * nsIHttpAuthenticableChannel's methods or
+ * until disconnect be called.
+ */
+ [must_use] void processAuthentication(in unsigned long httpStatus,
+ in boolean sslConnectFailed);
+
+ /**
+ * Add credentials from the http auth cache.
+ *
+ * @param dontUseCachedWWWCreds
+ * When true, the method will not add any Authorization headers from
+ * the auth cache.
+ */
+ [must_use] void addAuthorizationHeaders(in boolean dontUseCachedWWWCreds);
+
+ /**
+ * Check if an unnecessary(and maybe malicious) url authentication has been
+ * provided.
+ */
+ [must_use] void checkForSuperfluousAuth();
+
+ /**
+ * Cancel pending user auth prompts and release the callback and channel
+ * weak references.
+ */
+ [must_use] void disconnect(in nsresult status);
+
+ /**
+ * Clear the proxy ident to not consider it invalid on re-athentication.
+ * Called when the channel finds out its transaction has been internally
+ * restarted.
+ */
+ void clearProxyIdent();
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelChild.idl b/netwerk/protocol/http/nsIHttpChannelChild.idl
new file mode 100644
index 0000000000..13ff5661b8
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelChild.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+class OriginAttributes;
+} // mozilla namespace
+%}
+
+[ptr] native RequestHeaderTuples(mozilla::net::RequestHeaderTuples);
+[ref] native MaybeCorsPreflightArgsRef(mozilla::Maybe<mozilla::net::CorsPreflightArgs>);
+[ref] native const_OriginAttributes(const mozilla::OriginAttributes);
+
+interface nsIPrincipal;
+interface nsIURI;
+
+[uuid(d02b96ed-2789-4f42-a25c-7abe63de7c18)]
+interface nsIHttpChannelChild : nsISupports
+{
+ [must_use] void addCookiesToRequest();
+
+ // Headers that the channel client has set via SetRequestHeader.
+ [must_use] readonly attribute RequestHeaderTuples clientSetRequestHeaders;
+
+ // Headers that the channel client has set via SetRequestHeader.
+ [notxpcom, nostdcall]
+ void GetClientSetCorsPreflightParameters(in MaybeCorsPreflightArgsRef args);
+
+ // This method is called by nsCORSListenerProxy if we need to remove
+ // an entry from the CORS preflight cache in the parent process.
+ [must_use]
+ void removeCorsPreflightCacheEntry(in nsIURI aURI, in nsIPrincipal aRequestingPrincipal,
+ in const_OriginAttributes aOriginAttributes);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl
new file mode 100644
index 0000000000..1650b8f35c
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -0,0 +1,522 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsILoadInfo.idl"
+#include "nsIRequest.idl"
+#include "nsITRRSkipReason.idl"
+
+%{C++
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+template<class T> class nsCOMArray;
+namespace mozilla {
+class TimeStamp;
+namespace net {
+class nsHttpConnectionInfo;
+class WebSocketConnectionBase;
+class EarlyHintConnectArgs;
+}
+namespace dom {
+enum class RequestMode : uint8_t;
+}
+}
+%}
+[ptr] native nsHttpConnectionInfo(mozilla::net::nsHttpConnectionInfo);
+[ptr] native StringArray(nsTArray<nsCString>);
+[ref] native CStringArrayRef(const nsTArray<nsCString>);
+[ref] native securityMessagesArray(nsCOMArray<nsISecurityConsoleMessage>);
+[ptr] native WebSocketConnectionBase(mozilla::net::WebSocketConnectionBase);
+
+native TimeStamp(mozilla::TimeStamp);
+native RequestMode(mozilla::dom::RequestMode);
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+interface nsIEarlyHintObserver;
+interface nsIPrincipal;
+interface nsIProxyInfo;
+interface nsISecurityConsoleMessage;
+interface nsISocketTransport;
+interface nsIURI;
+interface WebTransportSessionEventListener;
+
+/**
+ * The callback interface for nsIHttpChannelInternal::HTTPUpgrade()
+ */
+
+[scriptable, uuid(5b515449-ab64-4dba-b3cd-da8fc2f83064)]
+interface nsIHttpUpgradeListener : nsISupports
+{
+ [must_use] void onTransportAvailable(in nsISocketTransport aTransport,
+ in nsIAsyncInputStream aSocketIn,
+ in nsIAsyncOutputStream aSocketOut);
+
+ [must_use] void onUpgradeFailed(in nsresult aErrorCode);
+
+ void onWebSocketConnectionAvailable(in WebSocketConnectionBase aConnection);
+};
+
+/**
+ * Dumping ground for http. This interface will never be frozen. If you are
+ * using any feature exposed by this interface, be aware that this interface
+ * will change and you will be broken. You have been warned.
+ */
+[builtinclass, scriptable, uuid(4e28263d-1e03-46f4-aa5c-9512f91957f9)]
+interface nsIHttpChannelInternal : nsISupports
+{
+ /**
+ * An http channel can own a reference to the document URI
+ */
+ [must_use] attribute nsIURI documentURI;
+
+ /**
+ * Get the major/minor version numbers for the request
+ */
+ [must_use]
+ void getRequestVersion(out unsigned long major, out unsigned long minor);
+
+ /**
+ * Get the major/minor version numbers for the response
+ */
+ [must_use]
+ void getResponseVersion(out unsigned long major, out unsigned long minor);
+
+ /*
+ * Retrieves all security messages from the security message queue
+ * and empties the queue after retrieval
+ */
+ [noscript, must_use]
+ void takeAllSecurityMessages(in securityMessagesArray aMessages);
+
+ /**
+ * Helper method to set a cookie with a consumer-provided
+ * cookie header, _but_ using the channel's other information
+ * (URI's, prompters, date headers etc).
+ *
+ * @param aCookieHeader
+ * The cookie header to be parsed.
+ */
+ [must_use] void setCookie(in ACString aCookieHeader);
+
+ /**
+ * Returns true in case this channel is used for auth;
+ * (the response header includes 'www-authenticate').
+ */
+ [noscript, must_use] readonly attribute bool isAuthChannel;
+
+ /**
+ * This flag is set to force relevant cookies to be sent with this load
+ * even if normally they wouldn't be.
+ */
+ const unsigned long THIRD_PARTY_FORCE_ALLOW = 1 << 0;
+
+ /**
+ * When set, these flags modify the algorithm used to decide whether to
+ * send 3rd party cookies for a given channel.
+ */
+ [must_use] attribute unsigned long thirdPartyFlags;
+
+ /**
+ * This attribute was added before the "flags" above and is retained here
+ * for compatibility. When set to true, has the same effect as
+ * THIRD_PARTY_FORCE_ALLOW, described above.
+ */
+ [must_use] attribute boolean forceAllowThirdPartyCookie;
+
+ /**
+ * External handlers may set this to true to notify the channel
+ * that it is open on behalf of a download.
+ */
+ [must_use] attribute boolean channelIsForDownload;
+
+ /**
+ * The local IP address to which this channel is bound, in the
+ * format produced by PR_NetAddrToString. May be IPv4 or IPv6.
+ * Note: in the presence of NAT, this may not be the same as the
+ * address that the remote host thinks it's talking to.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute AUTF8String localAddress;
+
+ /**
+ * The local port number to which this channel is bound.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute int32_t localPort;
+
+ /**
+ * The IP address of the remote host that this channel is
+ * connected to, in the format produced by PR_NetAddrToString.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute AUTF8String remoteAddress;
+
+ /**
+ * The remote port number that this channel is connected to.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute int32_t remotePort;
+
+ /**
+ * Transfer chain of redirected cache-keys.
+ */
+ [noscript, must_use]
+ void setCacheKeysRedirectChain(in StringArray cacheKeys);
+
+ /**
+ * HTTPUpgrade allows for the use of HTTP to bootstrap another protocol
+ * via the RFC 2616 Upgrade request header in conjunction with a 101 level
+ * response. The nsIHttpUpgradeListener will have its
+ * onTransportAvailable() method invoked if a matching 101 is processed.
+ * The arguments to onTransportAvailable provide the new protocol the low
+ * level tranport streams that are no longer used by HTTP. If any errors
+ * occur during the upgrade but the original request has (potentially)
+ * already received onStopRequest, the nsIHttpUpgradeListener will have its
+ * onUpgradeFailed() method invoked instead of onTransportAvailable().
+ *
+ * The onStartRequest and onStopRequest events are still delivered and the
+ * listener gets full control over the socket if and when onTransportAvailable
+ * is delivered. Note that if onStopRequest is called with an error, no
+ * methods on the nsIHttpUpgradeListener might be invoked at all.
+ *
+ * @param aProtocolName
+ * The value of the HTTP Upgrade request header
+ * @param aListener
+ * The callback object used to handle a successful upgrade
+ */
+ [must_use] void HTTPUpgrade(in ACString aProtocolName,
+ in nsIHttpUpgradeListener aListener);
+
+ /**
+ * Enable only CONNECT to a proxy. Fails if no HTTPUpgrade listener
+ * has been defined. An ALPN header is set using the upgrade protocol.
+ *
+ * Load flags are set with INHIBIT_CACHING, LOAD_ANONYMOUS,
+ * LOAD_BYPASS_CACHE, and LOAD_BYPASS_SERVICE_WORKER.
+ *
+ * Proxy resolve flags are set with RESOLVE_PREFER_HTTPS_PROXY and
+ * RESOLVE_ALWAYS_TUNNEL.
+ */
+ [must_use] void setConnectOnly();
+
+ /**
+ * True iff the channel is CONNECT only.
+ */
+ [must_use] readonly attribute boolean onlyConnect;
+
+ /**
+ * Enable/Disable Spdy negotiation on per channel basis.
+ * The network.http.http2.enabled preference is still a pre-requisite
+ * for starting spdy.
+ */
+ [must_use] attribute boolean allowSpdy;
+
+ /**
+ * Enable/Disable HTTP3 negotiation on per channel basis.
+ * The network.http.http3.enable preference is still a pre-requisite
+ * for starting HTTP3.
+ */
+ [must_use] attribute boolean allowHttp3;
+
+ /**
+ * This attribute en/disables the timeout for the first byte of an HTTP
+ * response. Enabled by default.
+ */
+ [must_use] attribute boolean responseTimeoutEnabled;
+
+ /**
+ * If the underlying transport supports RWIN manipulation, this is the
+ * intiial window value for the channel. HTTP/2 implements this.
+ * 0 means no override from system default. Set before opening channel.
+ */
+ [must_use] attribute unsigned long initialRwin;
+
+ /**
+ * Get value of the URI passed to nsIHttpChannel.redirectTo() if any.
+ * May return null when redirectTo() has not been called.
+ */
+ [must_use] readonly attribute nsIURI apiRedirectToURI;
+
+ /**
+ * Enable/Disable use of Alternate Services with this channel.
+ * The network.http.altsvc.enabled preference is still a pre-requisite.
+ */
+ [must_use] attribute boolean allowAltSvc;
+
+ /**
+ * If true, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ [must_use] attribute boolean beConservative;
+
+ /**
+ * If true, do not resolve any proxy for this request. Intended only for use with
+ * critical infra like the updater.
+ * default is false.
+ */
+ [must_use] attribute boolean bypassProxy;
+
+ /**
+ * True if channel is used by the internal trusted recursive resolver
+ * This flag places data for the request in a cache segment specific to TRR
+ */
+ [noscript, must_use] attribute boolean isTRRServiceChannel;
+
+ /**
+ * If the channel's remote IP was resolved using TRR.
+ * Is false for resources loaded from the cache or resources that have an
+ * IP literal host.
+ */
+ [must_use] readonly attribute boolean isResolvedByTRR;
+
+
+ /**
+ * The effective TRR mode used to resolve this channel.
+ * This is computed by taking the value returned by nsIRequest.getTRRMode()
+ * and the state of the TRRService. If the domain is excluded from TRR
+ * or the TRRService is disabled, the effective mode would be TRR_DISABLED_MODE
+ * even if the initial mode set on the request was TRR_ONLY_MODE.
+ */
+ [must_use] readonly attribute nsIRequest_TRRMode effectiveTRRMode;
+
+ /**
+ * If the DNS request triggered by this channel didn't use TRR, this value
+ * contains the reason why that was skipped.
+ */
+ [must_use] readonly attribute nsITRRSkipReason_value trrSkipReason;
+
+ /**
+ * True if channel is loaded by socket process.
+ */
+ [must_use] readonly attribute boolean isLoadedBySocketProcess;
+
+ /**
+ * Set to true if the channel is an OCSP check.
+ * Channels with this flag set will skip TRR in mode3 (because the circular
+ * dependency with checking OCSP for the TRR server will cause a failure)
+ */
+ [must_use] attribute boolean isOCSP;
+
+ /**
+ * An opaque flags for non-standard behavior of the TLS system.
+ * It is unlikely this will need to be set outside of telemetry studies
+ * relating to the TLS implementation.
+ */
+ [must_use] attribute unsigned long tlsFlags;
+
+ [must_use] readonly attribute PRTime lastModifiedTime;
+
+ /**
+ * Set by nsCORSListenerProxy if credentials should be included in
+ * cross-origin requests. false indicates "same-origin", users should still
+ * check flag LOAD_ANONYMOUS!
+ */
+ [must_use] attribute boolean corsIncludeCredentials;
+
+ /**
+ * Set by nsCORSListenerProxy to indicate CORS load type. Defaults to CORS_MODE_NO_CORS.
+ */
+ [must_use, noscript] attribute RequestMode requestMode;
+
+ const unsigned long REDIRECT_MODE_FOLLOW = 0;
+ const unsigned long REDIRECT_MODE_ERROR = 1;
+ const unsigned long REDIRECT_MODE_MANUAL = 2;
+ /**
+ * Set to indicate Request.redirect mode exposed during ServiceWorker
+ * interception. No policy enforcement is performed by the channel for this
+ * value.
+ */
+ [must_use] attribute unsigned long redirectMode;
+
+ const unsigned long FETCH_CACHE_MODE_DEFAULT = 0;
+ const unsigned long FETCH_CACHE_MODE_NO_STORE = 1;
+ const unsigned long FETCH_CACHE_MODE_RELOAD = 2;
+ const unsigned long FETCH_CACHE_MODE_NO_CACHE = 3;
+ const unsigned long FETCH_CACHE_MODE_FORCE_CACHE = 4;
+ const unsigned long FETCH_CACHE_MODE_ONLY_IF_CACHED = 5;
+ /**
+ * Set to indicate Request.cache mode, which simulates the fetch API
+ * semantics, and is also used for exposing this value to the Web page
+ * during service worker interception.
+ */
+ [must_use] attribute unsigned long fetchCacheMode;
+
+ /**
+ * The URI of the top-level window that's associated with this channel.
+ */
+ [must_use] readonly attribute nsIURI topWindowURI;
+
+ /**
+ * Set top-level window URI to this channel only when the topWindowURI
+ * is null and there is no window associated to this channel.
+ * Note that the current usage of this method is only for xpcshell test.
+ */
+ [must_use] void setTopWindowURIIfUnknown(in nsIURI topWindowURI);
+
+ /**
+ * Read the proxy URI, which, if non-null, will be used to resolve
+ * proxies for this channel.
+ */
+ [must_use] readonly attribute nsIURI proxyURI;
+
+ /**
+ * Make cross-origin CORS loads happen with a CORS preflight, and specify
+ * the CORS preflight parameters.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setCorsPreflightParameters(in CStringArrayRef unsafeHeaders,
+ in boolean shouldStripRequestBodyHeader);
+
+ [noscript, notxpcom, nostdcall]
+ void setAltDataForChild(in boolean aIsForChild);
+
+ /**
+ * Prevent the use of alt-data cache for this request. Use by the
+ * extension StreamFilter class to force use of the regular cache.
+ */
+ [noscript, notxpcom, nostdcall]
+ void disableAltDataCache();
+
+ /**
+ * When set to true, the channel will not pop any authentication prompts up
+ * to the user. When provided or cached credentials lead to an
+ * authentication failure, that failure will be propagated to the channel
+ * listener. Must be called before opening the channel, otherwise throws.
+ */
+ [infallible]
+ attribute boolean blockAuthPrompt;
+
+ /**
+ * Set to indicate Request.integrity.
+ */
+ [must_use] attribute AString integrityMetadata;
+
+ /**
+ * The connection info's hash key. We use it to test connection separation.
+ */
+ [must_use] readonly attribute ACString connectionInfoHashKey;
+
+ /**
+ * If this channel was created as the result of a redirect, then this
+ * value will reflect the redirect flags passed to the
+ * SetupReplacementChannel() method.
+ */
+ [noscript, infallible]
+ attribute unsigned long lastRedirectFlags;
+
+ // This is use to determine the duration since navigation started.
+ [noscript] attribute TimeStamp navigationStartTimeStamp;
+
+ /**
+ * Cancel a channel because we have determined that it needs to be blocked
+ * for safe-browsing protection. This is an internal API that is meant to
+ * be called by the channel classifier. Please DO NOT use this API if you
+ * don't know whether you should be using it.
+ */
+ [noscript] void cancelByURLClassifier(in nsresult aErrorCode);
+
+ /**
+ * The channel will be loaded over IPv6, disabling IPv4.
+ */
+ void setIPv4Disabled();
+
+ /**
+ * The channel will be loaded over IPv4, disabling IPv6.
+ */
+ void setIPv6Disabled();
+
+ /**
+ * Returns a cached CrossOriginOpenerPolicy that is computed just before we
+ * determine if there is a policy mismatch.
+ * @throws NS_ERROR_NOT_AVAILABLE if it has not been computed yet
+ */
+ readonly attribute nsILoadInfo_CrossOriginOpenerPolicy crossOriginOpenerPolicy;
+
+ /**
+ * Called during onStartRequest to compute the cross-origin-opener-policy
+ * for a given channel.
+ */
+ [noscript]
+ nsILoadInfo_CrossOriginOpenerPolicy computeCrossOriginOpenerPolicy(
+ in nsILoadInfo_CrossOriginOpenerPolicy aInitiatorPolicy);
+
+ [noscript]
+ bool hasCrossOriginOpenerPolicyMismatch();
+
+ [noscript]
+ nsILoadInfo_CrossOriginEmbedderPolicy getResponseEmbedderPolicy(in boolean aIsOriginTrialCoepCredentiallessEnabled);
+
+ [noscript, notxpcom, nostdcall]
+ void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+
+ /**
+ * If this is called, this channel's transaction will not be dispatched
+ * until the HTTPSSVC record is available.
+ */
+ [must_use] void setWaitForHTTPSSVCRecord();
+
+ /**
+ * This attribute indicates if the channel has support for HTTP3
+ */
+ [must_use] readonly attribute boolean supportsHTTP3;
+
+ /**
+ * This attribute indicates if the HTTPS RR is used for this channel.
+ */
+ [must_use] readonly attribute boolean hasHTTPSRR;
+
+ /**
+ * Set Early Hint Observer.
+ */
+ [must_use] void setEarlyHintObserver(in nsIEarlyHintObserver aObserver);
+
+ /*
+ * id of the EarlyHintPreloader to connect back from PreloadService to
+ * EarlyHintPreloader.
+ */
+ [must_use] attribute unsigned long long earlyHintPreloaderId;
+
+ [notxpcom, nostdcall] void setConnectionInfo(in nsHttpConnectionInfo aInfo);
+
+ /*
+ * This attribute indicates if the channel was loaded via Proxy.
+ */
+ [must_use] readonly attribute boolean isProxyUsed;
+
+ /**
+ * Set mWebTransportSessionEventListener.
+ */
+ [must_use] void setWebTransportSessionEventListener(
+ in WebTransportSessionEventListener aListener);
+
+ /**
+ * This attribute indicates the type of Link header in the received
+ * 103 response.
+ */
+ [must_use] attribute unsigned long earlyHintLinkType;
+
+ /**
+ * Indicates whether the User-Agent request header has been modified since
+ * the channel was created. This value will be used to decide if we need to
+ * recalculate the User-Agent header for fingerprinting protection. We won't
+ * recalculate the User-Agent header if it has been modified to preserve the
+ * overridden header value.
+ */
+ [must_use] attribute boolean isUserAgentHeaderModified;
+};
diff --git a/netwerk/protocol/http/nsIHttpHeaderVisitor.idl b/netwerk/protocol/http/nsIHttpHeaderVisitor.idl
new file mode 100644
index 0000000000..b09974de5d
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpHeaderVisitor.idl
@@ -0,0 +1,26 @@
+/* -*- 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"
+
+/**
+ * Implement this interface to visit http headers.
+ */
+[scriptable, function, uuid(35412859-b9d9-423c-8866-2d4559fdd2be)]
+interface nsIHttpHeaderVisitor : nsISupports
+{
+ /**
+ * Called by the nsIHttpChannel implementation when visiting request and
+ * response headers.
+ *
+ * @param aHeader
+ * the header being visited.
+ * @param aValue
+ * the header value (possibly a comma delimited list).
+ *
+ * @throw any exception to terminate enumeration
+ */
+ [must_use] void visitHeader(in ACString aHeader, in ACString aValue);
+};
diff --git a/netwerk/protocol/http/nsIHttpProtocolHandler.idl b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
new file mode 100644
index 0000000000..d6a6f8a0c1
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
@@ -0,0 +1,215 @@
+/* -*- 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 "nsIProxiedProtocolHandler.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class HSTSDataCallbackWrapper;
+}
+}
+%}
+
+native HSTSDataCallbackWrapperAlreadyAddRefed(RefPtr<mozilla::net::HSTSDataCallbackWrapper>);
+
+[scriptable, builtinclass, uuid(c48126d9-2ddd-485b-a51a-378e917e75f8)]
+interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
+{
+ /**
+ * Get the HTTP advertised user agent string.
+ */
+ [must_use] readonly attribute ACString userAgent;
+
+ /**
+ * Get the HTTP advertised user agent string.
+ */
+ [must_use] readonly attribute ACString rfpUserAgent;
+
+ /**
+ * Get the application name.
+ *
+ * @return The name of this application (eg. "Mozilla").
+ */
+ [must_use] readonly attribute ACString appName;
+
+ /**
+ * Get the application version string.
+ *
+ * @return The complete version (major and minor) string. (eg. "5.0")
+ */
+ [must_use] readonly attribute ACString appVersion;
+
+ /**
+ * Get the current platform.
+ *
+ * @return The platform this application is running on
+ * (eg. "Windows", "Macintosh", "X11")
+ */
+ [must_use] readonly attribute ACString platform;
+
+ /**
+ * Get the current oscpu.
+ *
+ * @return The oscpu this application is running on
+ */
+ [must_use] readonly attribute ACString oscpu;
+
+ /**
+ * Get the application comment misc portion.
+ */
+ [must_use] readonly attribute ACString misc;
+
+ /**
+ * Get the Alt-Svc cache keys (used for testing).
+ */
+ [must_use] readonly attribute Array<ACString> altSvcCacheKeys;
+
+ /**
+ * Get the auth cache keys (used for testing).
+ */
+ [must_use] readonly attribute Array<ACString> authCacheKeys;
+
+ /**
+ * This function is used to ensure HSTS data storage is ready to read after
+ * the returned promise is resolved.
+ * Note that this function should only used for testing.
+ * See bug 1521729 for more details.
+ */
+ [implicit_jscontext]
+ Promise EnsureHSTSDataReady();
+
+ /**
+ * A C++ friendly version of EnsureHSTSDataReady
+ */
+ [noscript]
+ void EnsureHSTSDataReadyNative(in HSTSDataCallbackWrapperAlreadyAddRefed aCallback);
+
+ /*
+ * Clears the CORS preflight cache.
+ */
+ void clearCORSPreflightCache();
+};
+
+%{C++
+// ----------- Categories -----------
+/**
+ * At initialization time, the HTTP handler will initialize each service
+ * registered under this category:
+ */
+#define NS_HTTP_STARTUP_CATEGORY "http-startup-category"
+
+// ----------- Observer topics -----------
+/**
+ * nsIObserver notification corresponding to startup category. Services
+ * registered under the startup category will receive this observer topic at
+ * startup if they implement nsIObserver. The "subject" of the notification
+ * is the nsIHttpProtocolHandler instance.
+ */
+#define NS_HTTP_STARTUP_TOPIC "http-startup"
+
+/**
+ * Called when asyncOpen synchronously failes e.g. because of any synchronously
+ * performed security checks. This only fires on the child process, but if
+ * needed can be implemented also on the parent process.
+ */
+#define NS_HTTP_ON_FAILED_OPENING_REQUEST_TOPIC "http-on-failed-opening-request"
+
+ /**
+ * This observer topic is notified when an HTTP channel is opened.
+ * It is similar to http-on-modify-request, except that
+ * 1) The notification is guaranteed to occur before on-modify-request, during
+ * the AsyncOpen call itself.
+ * 2) It only occurs for the initial open of a channel, not for internal
+ * asyncOpens that happen during redirects, etc.
+ * 3) Some information (most notably nsIProxiedChannel.proxyInfo) may not be set
+ * on the channel object yet.
+ *
+ * The "subject" of the notification is the nsIHttpChannel instance.
+ *
+ * Generally the 'http-on-modify-request' notification is preferred unless the
+ * synchronous, during-asyncOpen behavior that this notification provides is
+ * required.
+ */
+#define NS_HTTP_ON_OPENING_REQUEST_TOPIC "http-on-opening-request"
+
+ /**
+ * This observer topic is notified when a document channel is opened.
+ * It is similar to http-on-opening-request.
+ */
+#define NS_DOCUMENT_ON_OPENING_REQUEST_TOPIC "document-on-opening-request"
+
+/**
+ * Before an HTTP request is sent to the server, this observer topic is
+ * notified. The observer of this topic can then choose to set any additional
+ * headers for this request before the request is actually sent to the server.
+ * The "subject" of the notification is the nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_MODIFY_REQUEST_TOPIC "http-on-modify-request"
+
+/**
+ * Same as http-on-modify-request, but called before the cookie header is set on
+ * the channel.
+ * This allows observers to set cookies via the cookie service to be included in
+ * the request header.
+ * http-on-modify-request is too late for this use-case since the cookie header
+ * has already been populated at that point.
+ */
+#define NS_HTTP_ON_MODIFY_REQUEST_BEFORE_COOKIES_TOPIC "http-on-modify-request-before-cookies"
+
+/**
+ * Before an HTTP request is sent to the server via a document channel this
+ * observer topic is notified.
+ * It is similar to http-on-modify-request.
+*/
+#define NS_DOCUMENT_ON_MODIFY_REQUEST_TOPIC "document-on-modify-request"
+
+/**
+ * Before an HTTP connection to the server is created, this observer topic is
+ * notified. This observer happens after HSTS upgrades, etc. are set, providing
+ * access to the full set of request headers. The observer of this topic can
+ * choose to set any additional headers for this request before the request is
+ * actually sent to the server. The "subject" of the notification is the
+ * nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_BEFORE_CONNECT_TOPIC "http-on-before-connect"
+
+/**
+ * After an HTTP server response is received, this observer topic is notified.
+ * The observer of this topic can interrogate the response. The "subject" of
+ * the notification is the nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC "http-on-examine-response"
+
+/**
+ * The observer of this topic is notified after the received HTTP response
+ * is merged with data from the browser cache. This means that, among other
+ * things, the Content-Type header will be set correctly.
+ */
+#define NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC "http-on-examine-merged-response"
+
+/**
+ * The observer of this topic is notified about a background revalidation that
+ * started by hitting a request that fell into stale-while-revalidate window.
+ * This notification points to the channel that performed the revalidation and
+ * after this notification the cache entry has been validated or updated.
+ */
+#define NS_HTTP_ON_BACKGROUND_REVALIDATION "http-on-background-revalidation"
+
+/**
+ * The observer of this topic is notified before data is read from the cache.
+ * The notification is sent if and only if there is no network communication
+ * at all.
+ */
+#define NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC "http-on-examine-cached-response"
+
+/**
+ * This topic is notified for every http channel right after it called
+ * OnStopRequest on its listener, regardless whether it was finished
+ * successfully, failed or has been canceled.
+ */
+#define NS_HTTP_ON_STOP_REQUEST_TOPIC "http-on-stop-request"
+
+%}
diff --git a/netwerk/protocol/http/nsIObliviousHttp.idl b/netwerk/protocol/http/nsIObliviousHttp.idl
new file mode 100644
index 0000000000..84bc30d640
--- /dev/null
+++ b/netwerk/protocol/http/nsIObliviousHttp.idl
@@ -0,0 +1,78 @@
+/* -*- 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 nsIChannel;
+interface nsIURI;
+
+[scriptable, builtinclass, uuid(f2a4aaa4-046a-439e-beef-893b15a90cff)]
+interface nsIObliviousHttpClientResponse : nsISupports {
+ // Decrypt an encrypted response ("enc_response" in the RFC).
+ // Can only be called once.
+ Array<octet> decapsulate(in Array<octet> encResponse);
+};
+
+[scriptable, builtinclass, uuid(403af7f9-4a76-49fc-a622-38d6ba3ee496)]
+interface nsIObliviousHttpClientRequest : nsISupports {
+ // The encrypted request ("enc_request" in the RFC).
+ readonly attribute Array<octet> encRequest;
+ // The context for decrypting the eventual response.
+ readonly attribute nsIObliviousHttpClientResponse response;
+};
+
+[scriptable, builtinclass, uuid(105deb62-45b4-407a-b330-550433279111)]
+interface nsIObliviousHttpServerResponse : nsISupports {
+ readonly attribute Array<octet> request;
+
+ Array<octet> encapsulate(in Array<octet> response);
+};
+
+[scriptable, builtinclass, uuid(fb1abc56-b525-4e1a-a4c6-341a9b32084e)]
+interface nsIObliviousHttpServer : nsISupports {
+ readonly attribute Array<octet> encodedConfig;
+
+ nsIObliviousHttpServerResponse decapsulate(in Array<octet> encRequest);
+};
+
+
+// IDL bindings for the rust implementation of oblivious http.
+// Client code will generally call `encapsulateRequest` given an encoded
+// oblivious gateway key configuration and an encoded binary http request.
+// This function returns a nsIObliviousHttpClientRequest. The `encRequest`
+// attribute of that object is the encapsulated request that can be sent to an
+// oblivious relay to be forwarded on to the oblivious gateway and then to the
+// actual target. The `response` attribute is used to decapsulate the response
+// returned by the oblivious relay.
+// For tests, this implementation provides a facility for decapsulating
+// requests and encapsulating responses. Call `server` to get an
+// `nsIObliviousHttpServer`, which has an attribute `encodedConfig` for use
+// with `encapsulateRequest`. It also has a function `decapsulate`, which
+// decapsulates an encapsulated client request and returns an
+// `nsIObliviousHttpServerResponse`. This object can `encapsulate` a response,
+// which the `nsIObliviousHttpClientResponse` from the original request should
+// be able to `decapsulate`.
+// Thread safety: nsIObliviousHttp may be used on any thread, but any objects
+// created by it must only be used on the threads they are created on.
+[scriptable, builtinclass, uuid(d581149e-3319-4563-b95e-46c64af5c4e8)]
+interface nsIObliviousHttp : nsISupports
+{
+ nsIObliviousHttpClientRequest encapsulateRequest(
+ in Array<octet> encodedConfig,
+ in Array<octet> request);
+
+ nsIObliviousHttpServer server();
+};
+
+[scriptable, builtinclass, uuid(b1f08d56-fca6-4290-9500-d5168dc9d8c3)]
+interface nsIObliviousHttpService : nsISupports
+{
+ nsIChannel newChannel(in nsIURI relayURI, in nsIURI targetURI, in Array<octet> encodedConfig);
+
+ void getTRRSettings(out nsIURI relayURI, out Array<octet> encodedConfig);
+
+ // Clears the config
+ void clearTRRConfig();
+};
diff --git a/netwerk/protocol/http/nsIObliviousHttpChannel.idl b/netwerk/protocol/http/nsIObliviousHttpChannel.idl
new file mode 100644
index 0000000000..0373693294
--- /dev/null
+++ b/netwerk/protocol/http/nsIObliviousHttpChannel.idl
@@ -0,0 +1,26 @@
+/* -*- 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 "nsIHttpChannel.idl"
+
+/**
+ * nsIObliviousHttpChannel
+ *
+ * This interface allows consumers to differentiate between the
+ * relayChannel request that transports the OHTTP payload
+ * and the virtual OHTTP channel represented by the
+ * nsIObliviousHttpChannel implementation.
+ */
+[builtinclass, scriptable, uuid(f829f761-0744-4d1c-9c2d-8931c22ae8d5)]
+interface nsIObliviousHttpChannel: nsIHttpChannel
+{
+ /**
+ * Returns the channel used to transport the binary serialization
+ * of the request and response to and from the OHTTP relay.
+ * This can be useful to determine if an HTTP status code or failure
+ * is due to the relay or the gateway response.
+ */
+ readonly attribute nsIHttpChannel relayChannel;
+};
diff --git a/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl b/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl
new file mode 100644
index 0000000000..93f361b198
--- /dev/null
+++ b/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl
@@ -0,0 +1,57 @@
+/* -*- 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"
+
+/**
+ * This holds methods used to race the cache with the network for a specific
+ * channel. This interface is was designed with nsHttpChannel in mind, and it's
+ * expected this will be the only class implementing it.
+ */
+[scriptable, builtinclass, uuid(4d963475-8b16-4c58-b804-8a23d49436c5)]
+interface nsIRaceCacheWithNetwork : nsISupports
+{
+
+ /****************************************************************************
+ * TEST ONLY: The following methods are for testing purposes only. Do not use
+ * them to do anything important in your code.
+ ****************************************************************************
+
+ /**
+ * Triggers network activity after given timeout. If timeout is 0, network
+ * activity is triggered immediately if asyncOpen has already been called.
+ * Otherwise the delayed timer will be set when the normal call to
+ * TriggerNetwork is made. If the cache.asyncOpenURI callbacks have already
+ * been called, the network activity may have already been triggered
+ * or the content may have already been delivered from the cache, so this
+ * operation will have no effect.
+ *
+ * @param timeout
+ * - the delay in milliseconds until the network will be triggered.
+ */
+ void test_triggerNetwork(in long timeout);
+
+ /**
+ * Normally a HTTP channel would immediately call AsyncOpenURI leading to the
+ * cache storage to lookup the cache entry and return it. In order to
+ * simmulate real life conditions where fetching a cache entry takes a long
+ * time, we set a timer to delay the operation.
+ * Can only be called on the main thread.
+ *
+ * @param timeout
+ * - the delay in milliseconds until the cache open will be triggered.
+ */
+ void test_delayCacheEntryOpeningBy(in long timeout);
+
+ /**
+ * Immediatelly triggers AsyncOpenURI if the timer hasn't fired.
+ * Can only be called on the main thread.
+ * This is only called in tests to reliably trigger the opening of the cache
+ * entry.
+ * @throws NS_ERROR_NOT_AVAILABLE if opening the cache wasn't delayed.
+ */
+ void test_triggerDelayedOpenCacheEntry();
+};
diff --git a/netwerk/protocol/http/nsITlsHandshakeListener.idl b/netwerk/protocol/http/nsITlsHandshakeListener.idl
new file mode 100644
index 0000000000..0d5806be24
--- /dev/null
+++ b/netwerk/protocol/http/nsITlsHandshakeListener.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[uuid(b4bbe824-ec4c-48be-9a40-6a7339347f40)]
+interface nsITlsHandshakeCallbackListener : nsISupports {
+ [noscript] void handshakeDone();
+ [noscript] void certVerificationDone();
+ [noscript] void clientAuthCertificateSelected();
+};
diff --git a/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
new file mode 100644
index 0000000000..fa90891172
--- /dev/null
+++ b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
@@ -0,0 +1,23 @@
+/* -*- 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/. */
+
+/*
+ For parsing JSON from http://httpwg.org/http-extensions/opsec.html
+*/
+
+#include "nsISupports.idl"
+
+%{C++
+#define NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID "@mozilla.org/network/well-known-opportunistic-utils;1"
+%}
+
+[scriptable, uuid(b4f96c89-5238-450c-8bda-e12c26f1d150)]
+interface nsIWellKnownOpportunisticUtils : nsISupports
+{
+ [must_use] void verify(in ACString aJSON,
+ in ACString aOrigin);
+
+ [must_use] readonly attribute bool valid;
+};
diff --git a/netwerk/protocol/http/nsServerTiming.cpp b/netwerk/protocol/http/nsServerTiming.cpp
new file mode 100644
index 0000000000..b1b2fe54d1
--- /dev/null
+++ b/netwerk/protocol/http/nsServerTiming.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "nsServerTiming.h"
+
+#include "nsHttp.h"
+
+NS_IMPL_ISUPPORTS(nsServerTiming, nsIServerTiming)
+
+NS_IMETHODIMP
+nsServerTiming::GetName(nsACString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerTiming::GetDuration(double* aDuration) {
+ *aDuration = mDuration;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerTiming::GetDescription(nsACString& aDescription) {
+ aDescription.Assign(mDescription);
+ return NS_OK;
+}
+
+namespace mozilla {
+namespace net {
+
+static double ParseDouble(const nsACString& aString) {
+ nsresult rv;
+ double val = PromiseFlatCString(aString).ToDouble(&rv);
+ return NS_FAILED(rv) ? 0.0f : val;
+}
+
+void ServerTimingParser::Parse() {
+ // https://w3c.github.io/server-timing/#the-server-timing-header-field
+ // Server-Timing = #server-timing-metric
+ // server-timing-metric = metric-name *( OWS ";" OWS server-timing-param
+ // ) metric-name = token server-timing-param =
+ // server-timing-param-name OWS "=" OWS
+ // server-timing-param-value
+ // server-timing-param-name = token
+ // server-timing-param-value = token / quoted-string
+
+ ParsedHeaderValueListList parsedHeader(mValue, false);
+ for (uint32_t index = 0; index < parsedHeader.mValues.Length(); ++index) {
+ if (parsedHeader.mValues[index].mValues.IsEmpty()) {
+ continue;
+ }
+
+ // According to spec, the first ParsedHeaderPair's name is metric-name.
+ RefPtr<nsServerTiming> timingHeader = new nsServerTiming();
+ mServerTimingHeaders.AppendElement(timingHeader);
+ timingHeader->SetName(parsedHeader.mValues[index].mValues[0].mName);
+
+ if (parsedHeader.mValues[index].mValues.Length() == 1) {
+ continue;
+ }
+
+ // Try to find duration and description from the rest ParsedHeaderPairs.
+ bool foundDuration = false;
+ bool foundDescription = false;
+ for (uint32_t pairIndex = 1;
+ pairIndex < parsedHeader.mValues[index].mValues.Length();
+ ++pairIndex) {
+ nsDependentCSubstring& currentName =
+ parsedHeader.mValues[index].mValues[pairIndex].mName;
+ nsDependentCSubstring& currentValue =
+ parsedHeader.mValues[index].mValues[pairIndex].mValue;
+
+ // We should only take the value from the first
+ // occurrence of server-timing-param-name ("dur" and "desc").
+ // This is true whether or not the value makes any sense (or, indeed, if
+ // there even is a value).
+ if (currentName.LowerCaseEqualsASCII("dur") && !foundDuration) {
+ if (currentValue.BeginReading()) {
+ timingHeader->SetDuration(ParseDouble(currentValue));
+ } else {
+ timingHeader->SetDuration(0.0);
+ }
+ foundDuration = true;
+ } else if (currentName.LowerCaseEqualsASCII("desc") &&
+ !foundDescription) {
+ if (!currentValue.IsEmpty()) {
+ timingHeader->SetDescription(currentValue);
+ } else {
+ timingHeader->SetDescription(""_ns);
+ }
+ foundDescription = true;
+ }
+
+ if (foundDuration && foundDescription) {
+ break;
+ }
+ }
+ }
+}
+
+nsTArray<nsCOMPtr<nsIServerTiming>>&&
+ServerTimingParser::TakeServerTimingHeaders() {
+ return std::move(mServerTimingHeaders);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsServerTiming.h b/netwerk/protocol/http/nsServerTiming.h
new file mode 100644
index 0000000000..2db1887b80
--- /dev/null
+++ b/netwerk/protocol/http/nsServerTiming.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsServerTiming_h__
+#define nsServerTiming_h__
+
+#include "nsITimedChannel.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsServerTiming final : public nsIServerTiming {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERTIMING
+
+ nsServerTiming() = default;
+
+ void SetName(const nsACString& aName) { mName = aName; }
+
+ void SetDuration(double aDuration) { mDuration = aDuration; }
+
+ void SetDescription(const nsACString& aDescription) {
+ mDescription = aDescription;
+ }
+
+ private:
+ virtual ~nsServerTiming() = default;
+
+ nsCString mName;
+ double mDuration = 0;
+ nsCString mDescription;
+};
+
+namespace mozilla {
+namespace net {
+
+class ServerTimingParser {
+ public:
+ explicit ServerTimingParser(const nsCString& value) : mValue(value) {}
+ void Parse();
+ nsTArray<nsCOMPtr<nsIServerTiming>>&& TakeServerTimingHeaders();
+
+ private:
+ nsCString mValue;
+ nsTArray<nsCOMPtr<nsIServerTiming>> mServerTimingHeaders;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/oblivious_http/Cargo.toml b/netwerk/protocol/http/oblivious_http/Cargo.toml
new file mode 100644
index 0000000000..e15cd2f74f
--- /dev/null
+++ b/netwerk/protocol/http/oblivious_http/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "oblivious_http"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+nserror = { path = "../../../../xpcom/rust/nserror" }
+ohttp = { version = "0.3", default-features = false, features = ["gecko", "nss", "client", "server"] }
+rand = "0.8"
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+xpcom = { path = "../../../../xpcom/rust/xpcom" }
diff --git a/netwerk/protocol/http/oblivious_http/src/lib.rs b/netwerk/protocol/http/oblivious_http/src/lib.rs
new file mode 100644
index 0000000000..948139ab68
--- /dev/null
+++ b/netwerk/protocol/http/oblivious_http/src/lib.rs
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate nserror;
+extern crate ohttp;
+extern crate rand;
+extern crate thin_vec;
+#[macro_use]
+extern crate xpcom;
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NOT_AVAILABLE, NS_OK};
+use ohttp::hpke::{Aead, Kdf, Kem};
+use ohttp::{
+ ClientRequest, ClientResponse, KeyConfig, KeyId, Server, ServerResponse, SymmetricSuite,
+};
+use thin_vec::ThinVec;
+use xpcom::interfaces::{
+ nsIObliviousHttpClientRequest, nsIObliviousHttpClientResponse, nsIObliviousHttpServer,
+ nsIObliviousHttpServerResponse,
+};
+use xpcom::{xpcom_method, RefPtr};
+
+use std::cell::RefCell;
+
+#[xpcom(implement(nsIObliviousHttpClientResponse), atomic)]
+struct ObliviousHttpClientResponse {
+ response: RefCell<Option<ClientResponse>>,
+}
+
+impl ObliviousHttpClientResponse {
+ xpcom_method!(decapsulate => Decapsulate(enc_response: *const ThinVec<u8>) -> ThinVec<u8>);
+ fn decapsulate(&self, enc_response: &ThinVec<u8>) -> Result<ThinVec<u8>, nsresult> {
+ let response = self
+ .response
+ .borrow_mut()
+ .take()
+ .ok_or(NS_ERROR_NOT_AVAILABLE)?;
+ let decapsulated = response
+ .decapsulate(enc_response)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(decapsulated.into_iter().collect())
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttpClientRequest), atomic)]
+struct ObliviousHttpClientRequest {
+ enc_request: Vec<u8>,
+ response: RefPtr<nsIObliviousHttpClientResponse>,
+}
+
+impl ObliviousHttpClientRequest {
+ xpcom_method!(get_enc_request => GetEncRequest() -> ThinVec<u8>);
+ fn get_enc_request(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.enc_request.clone().into_iter().collect())
+ }
+
+ xpcom_method!(get_response => GetResponse() -> *const nsIObliviousHttpClientResponse);
+ fn get_response(&self) -> Result<RefPtr<nsIObliviousHttpClientResponse>, nsresult> {
+ Ok(self.response.clone())
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttpServerResponse), atomic)]
+struct ObliviousHttpServerResponse {
+ request: Vec<u8>,
+ server_response: RefCell<Option<ServerResponse>>,
+}
+
+impl ObliviousHttpServerResponse {
+ xpcom_method!(get_request => GetRequest() -> ThinVec<u8>);
+ fn get_request(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.request.clone().into_iter().collect())
+ }
+
+ xpcom_method!(encapsulate => Encapsulate(response: *const ThinVec<u8>) -> ThinVec<u8>);
+ fn encapsulate(&self, response: &ThinVec<u8>) -> Result<ThinVec<u8>, nsresult> {
+ let server_response = self
+ .server_response
+ .borrow_mut()
+ .take()
+ .ok_or(NS_ERROR_NOT_AVAILABLE)?;
+ Ok(server_response
+ .encapsulate(response)
+ .map_err(|_| NS_ERROR_FAILURE)?
+ .into_iter()
+ .collect())
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttpServer), atomic)]
+struct ObliviousHttpServer {
+ server: RefCell<Server>,
+}
+
+impl ObliviousHttpServer {
+ xpcom_method!(get_encoded_config => GetEncodedConfig() -> ThinVec<u8>);
+ fn get_encoded_config(&self) -> Result<ThinVec<u8>, nsresult> {
+ let server = self.server.borrow_mut();
+ Ok(server
+ .config()
+ .encode()
+ .map_err(|_| NS_ERROR_FAILURE)?
+ .into_iter()
+ .collect())
+ }
+
+ xpcom_method!(decapsulate => Decapsulate(enc_request: *const ThinVec<u8>) -> *const nsIObliviousHttpServerResponse);
+ fn decapsulate(
+ &self,
+ enc_request: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIObliviousHttpServerResponse>, nsresult> {
+ let mut server = self.server.borrow_mut();
+ let (request, server_response) = server
+ .decapsulate(enc_request)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ let oblivious_http_server_response =
+ ObliviousHttpServerResponse::allocate(InitObliviousHttpServerResponse {
+ request,
+ server_response: RefCell::new(Some(server_response)),
+ });
+ oblivious_http_server_response
+ .query_interface::<nsIObliviousHttpServerResponse>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttp), atomic)]
+struct ObliviousHttp {}
+
+impl ObliviousHttp {
+ xpcom_method!(encapsulate_request => EncapsulateRequest(encoded_config: *const ThinVec<u8>,
+ request: *const ThinVec<u8>) -> *const nsIObliviousHttpClientRequest);
+ fn encapsulate_request(
+ &self,
+ encoded_config: &ThinVec<u8>,
+ request: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIObliviousHttpClientRequest>, nsresult> {
+ ohttp::init();
+
+ let client = ClientRequest::new(encoded_config).map_err(|_| NS_ERROR_FAILURE)?;
+ let (enc_request, response) = client.encapsulate(request).map_err(|_| NS_ERROR_FAILURE)?;
+ let oblivious_http_client_response =
+ ObliviousHttpClientResponse::allocate(InitObliviousHttpClientResponse {
+ response: RefCell::new(Some(response)),
+ });
+ let response = oblivious_http_client_response
+ .query_interface::<nsIObliviousHttpClientResponse>()
+ .ok_or(NS_ERROR_FAILURE)?;
+ let oblivious_http_client_request =
+ ObliviousHttpClientRequest::allocate(InitObliviousHttpClientRequest {
+ enc_request,
+ response,
+ });
+ oblivious_http_client_request
+ .query_interface::<nsIObliviousHttpClientRequest>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+
+ xpcom_method!(server => Server() -> *const nsIObliviousHttpServer);
+ fn server(&self) -> Result<RefPtr<nsIObliviousHttpServer>, nsresult> {
+ ohttp::init();
+
+ let key_id: KeyId = rand::random::<u8>();
+ let kem: Kem = Kem::X25519Sha256;
+ let symmetric = vec![
+ SymmetricSuite::new(Kdf::HkdfSha256, Aead::Aes128Gcm),
+ SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305),
+ ];
+ let key_config = KeyConfig::new(key_id, kem, symmetric).map_err(|_| NS_ERROR_FAILURE)?;
+ let server = Server::new(key_config).map_err(|_| NS_ERROR_FAILURE)?;
+ let oblivious_http_server = ObliviousHttpServer::allocate(InitObliviousHttpServer {
+ server: RefCell::new(server),
+ });
+ oblivious_http_server
+ .query_interface::<nsIObliviousHttpServer>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn oblivious_http_constructor(
+ iid: *const xpcom::nsIID,
+ result: *mut *mut xpcom::reexports::libc::c_void,
+) -> nserror::nsresult {
+ let oblivious_http = ObliviousHttp::allocate(InitObliviousHttp {});
+ unsafe { oblivious_http.QueryInterface(iid, result) }
+}
diff --git a/netwerk/protocol/http/oblivious_http/src/oblivious_http.h b/netwerk/protocol/http/oblivious_http/src/oblivious_http.h
new file mode 100644
index 0000000000..176b4909c1
--- /dev/null
+++ b/netwerk/protocol/http/oblivious_http/src/oblivious_http.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _oblivious_http_h_
+#define _oblivious_http_h_
+
+#include "nsISupportsUtils.h" // for nsresult, etc.
+
+// {d581149e-3319-4563-b95e-46c64af5c4e8}
+#define NS_OBLIVIOUS_HTTP_CID \
+ { \
+ 0xd581149e, 0x3319, 0x4563, { \
+ 0xb9, 0x5e, 0x46, 0xc6, 0x4a, 0xf5, 0xc4, 0xe8 \
+ } \
+ }
+
+extern "C" {
+nsresult oblivious_http_constructor(REFNSIID iid, void** result);
+};
+
+#endif // _oblivious_http_h_
diff --git a/netwerk/protocol/moz.build b/netwerk/protocol/moz.build
new file mode 100644
index 0000000000..aa3200c856
--- /dev/null
+++ b/netwerk/protocol/moz.build
@@ -0,0 +1,10 @@
+# -*- 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/.
+
+DIRS += ["about", "data", "file"]
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DIRS += ["gio"]
+DIRS += ["http", "res", "viewsource", "websocket", "webtransport"]
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
new file mode 100644
index 0000000000..d3b1a39164
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -0,0 +1,1039 @@
+/* -*- 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 "ExtensionProtocolHandler.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Try.h"
+
+#include "FileDescriptorFile.h"
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsICancelable.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIJARChannel.h"
+#include "nsIMIMEService.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIJARURI.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsURLHelper.h"
+#include "prio.h"
+#include "SimpleChannel.h"
+
+#if defined(XP_WIN)
+# include "nsILocalFileWin.h"
+# include "WinUtils.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+#define EXTENSION_SCHEME "moz-extension"
+using mozilla::dom::Promise;
+using mozilla::ipc::FileDescriptor;
+
+// A list of file extensions containing purely static data, which can be loaded
+// from an extension before the extension is fully ready. The main purpose of
+// this is to allow image resources from theme XPIs to load quickly during
+// browser startup.
+//
+// The layout of this array is chosen in order to prevent the need for runtime
+// relocation, which an array of char* pointers would require. It also has the
+// benefit of being more compact when the difference in length between the
+// longest and average string is less than 8 bytes. The length of the
+// char[] array must match the size of the longest entry in the list.
+//
+// This list must be kept sorted.
+static const char sStaticFileExtensions[][5] = {
+ // clang-format off
+ "bmp",
+ "gif",
+ "ico",
+ "jpeg",
+ "jpg",
+ "png",
+ "svg",
+ // clang-format on
+};
+
+namespace mozilla {
+
+namespace net {
+
+using extensions::URLInfo;
+
+LazyLogModule gExtProtocolLog("ExtProtocol");
+#undef LOG
+#define LOG(...) MOZ_LOG(gExtProtocolLog, LogLevel::Debug, (__VA_ARGS__))
+
+StaticRefPtr<ExtensionProtocolHandler> ExtensionProtocolHandler::sSingleton;
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream or file descriptor from the parent for a remote moz-extension load
+ * from the child.
+ */
+class ExtensionStreamGetter final : public nsICancelable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ public:
+ // To use when getting a remote input stream for a resource
+ // in an unpacked extension.
+ ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mURI(aURI), mLoadInfo(aLoadInfo), mIsJarChannel(false) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+
+ SetupEventTarget();
+ }
+
+ // To use when getting an FD for a packed extension JAR file
+ // in order to load a resource.
+ ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ already_AddRefed<nsIJARChannel>&& aJarChannel,
+ nsIFile* aJarFile)
+ : mURI(aURI),
+ mLoadInfo(aLoadInfo),
+ mJarChannel(std::move(aJarChannel)),
+ mJarFile(aJarFile),
+ mIsJarChannel(true) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+ MOZ_ASSERT(mJarChannel);
+ MOZ_ASSERT(aJarFile);
+
+ SetupEventTarget();
+ }
+
+ void SetupEventTarget() {
+ mMainThreadEventTarget = GetMainThreadSerialEventTarget();
+ }
+
+ // Get an input stream or file descriptor from the parent asynchronously.
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel);
+
+ // Handle an input stream being returned from the parent
+ void OnStream(already_AddRefed<nsIInputStream> aStream);
+
+ // Handle file descriptor being returned from the parent
+ void OnFD(const FileDescriptor& aFD);
+
+ static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
+ nsresult aResult);
+
+ private:
+ ~ExtensionStreamGetter() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIJARChannel> mJarChannel;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIFile> mJarFile;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+ bool mIsJarChannel;
+ bool mCanceled{false};
+ nsresult mStatus{NS_OK};
+};
+
+NS_IMPL_ISUPPORTS(ExtensionStreamGetter, nsICancelable)
+
+class ExtensionJARFileOpener final : public nsISupports {
+ public:
+ ExtensionJARFileOpener(nsIFile* aFile,
+ NeckoParent::GetExtensionFDResolver& aResolve)
+ : mFile(aFile), mResolve(aResolve) {
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(aResolve);
+ }
+
+ NS_IMETHOD OpenFile() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ AutoFDClose prFileDesc;
+
+#if defined(XP_WIN)
+ nsresult rv;
+ nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(mFile, &rv);
+ MOZ_ASSERT(winFile);
+ if (NS_SUCCEEDED(rv)) {
+ rv = winFile->OpenNSPRFileDescShareDelete(PR_RDONLY, 0,
+ getter_Transfers(prFileDesc));
+ }
+#else
+ nsresult rv =
+ mFile->OpenNSPRFileDesc(PR_RDONLY, 0, getter_Transfers(prFileDesc));
+#endif /* XP_WIN */
+
+ if (NS_SUCCEEDED(rv)) {
+ mFD = FileDescriptor(FileDescriptor::PlatformHandleType(
+ PR_FileDesc2NativeHandle(prFileDesc.get())));
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ mozilla::NewRunnableMethod("ExtensionJarFileFDResolver", this,
+ &ExtensionJARFileOpener::SendBackFD);
+
+ rv = NS_DispatchToMainThread(event, nsIEventTarget::DISPATCH_NORMAL);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread");
+ return NS_OK;
+ }
+
+ NS_IMETHOD SendBackFD() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mResolve(mFD);
+ mResolve = nullptr;
+ return NS_OK;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ virtual ~ExtensionJARFileOpener() = default;
+
+ nsCOMPtr<nsIFile> mFile;
+ NeckoParent::GetExtensionFDResolver mResolve;
+ FileDescriptor mFD;
+};
+
+NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports)
+
+// The amount of time, in milliseconds, that the file opener thread will remain
+// allocated after it is used. This value chosen because to match other uses
+// of LazyIdleThread.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+// Request an FD or input stream from the parent.
+RequestOrReason ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
+ RefPtr<ExtensionStreamGetter> self = this;
+ if (mIsJarChannel) {
+ // Request an FD for this moz-extension URI
+ gNeckoChild->SendGetExtensionFD(mURI)->Then(
+ mMainThreadEventTarget, __func__,
+ [self](const FileDescriptor& fd) { self->OnFD(fd); },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnFD(FileDescriptor());
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+ }
+
+ // Request an input stream for this moz-extension URI
+ gNeckoChild->SendGetExtensionStream(mURI)->Then(
+ mMainThreadEventTarget, __func__,
+ [self](const RefPtr<nsIInputStream>& stream) {
+ self->OnStream(do_AddRef(stream));
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnStream(nullptr);
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+}
+
+// Called to cancel the ongoing async request.
+NS_IMETHODIMP
+ExtensionStreamGetter::Cancel(nsresult aStatus) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+
+ if (mPump) {
+ mPump->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns);
+ mPump = nullptr;
+ }
+
+ if (mIsJarChannel && mJarChannel) {
+ mJarChannel->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns);
+ }
+
+ return NS_OK;
+}
+
+// static
+void ExtensionStreamGetter::CancelRequest(nsIStreamListener* aListener,
+ nsIChannel* aChannel,
+ nsresult aResult) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aChannel);
+
+ aListener->OnStartRequest(aChannel);
+ aListener->OnStopRequest(aChannel, aResult);
+ aChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "ExtensionStreamGetter::CancelRequest"_ns);
+}
+
+// Handle an input stream sent from the parent.
+void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ if (!stream) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0,
+ 0, false, mMainThreadEventTarget);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ mPump = pump;
+}
+
+// Handle an FD sent from the parent.
+void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncOpen.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ if (!aFD.IsValid()) {
+ // The parent didn't send us back a valid file descriptor.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile);
+ mJarChannel->SetJarFile(fdFile);
+ nsresult rv = mJarChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ }
+}
+
+NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+
+already_AddRefed<ExtensionProtocolHandler>
+ExtensionProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new ExtensionProtocolHandler();
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+ExtensionProtocolHandler::ExtensionProtocolHandler()
+ : SubstitutingProtocolHandler(EXTENSION_SCHEME) {
+ // Note, extensions.webextensions.protocol.remote=false is for
+ // debugging purposes only. With process-level sandboxing, child
+ // processes (specifically content and extension processes), will
+ // not be able to load most moz-extension URI's when the pref is
+ // set to false.
+ mUseRemoteFileChannels =
+ IsNeckoChild() &&
+ Preferences::GetBool("extensions.webextensions.protocol.remote");
+}
+
+static inline ExtensionPolicyService& EPS() {
+ return ExtensionPolicyService::GetSingleton();
+}
+
+nsresult ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI,
+ uint32_t* aFlags) {
+ uint32_t flags =
+ URI_STD | URI_IS_LOCAL_RESOURCE | URI_IS_POTENTIALLY_TRUSTWORTHY;
+
+ URLInfo url(aURI);
+ if (auto* policy = EPS().GetByURL(url)) {
+ // In general a moz-extension URI is only loadable by chrome, but an
+ // allowlist subset are web-accessible (and cross-origin fetchable).
+ // The allowlist is checked using EPS.SourceMayLoadExtensionURI in
+ // BasePrincipal and nsScriptSecurityManager.
+ if (policy->IsWebAccessiblePath(url.FilePath())) {
+ if (policy->ManifestVersion() < 3) {
+ flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE;
+ } else {
+ flags |= WEBEXT_URI_WEB_ACCESSIBLE;
+ }
+ } else if (policy->Type() == nsGkAtoms::theme) {
+ // Static themes cannot set web accessible resources, however using this
+ // flag here triggers SourceMayAccessPath calls necessary to allow another
+ // extension to access static theme resources in this extension.
+ flags |= WEBEXT_URI_WEB_ACCESSIBLE;
+ } else {
+ flags |= URI_DANGEROUS_TO_LOAD;
+ }
+
+ // Disallow in private windows if the extension does not have permission.
+ if (!policy->PrivateBrowsingAllowed()) {
+ flags |= URI_DISALLOW_IN_PRIVATE_CONTEXT;
+ }
+ } else {
+ // In case there is no policy, then default to treating moz-extension URIs
+ // as unsafe and generally only allow chrome: to load such.
+ flags |= URI_DANGEROUS_TO_LOAD;
+ }
+
+ *aFlags = flags;
+ return NS_OK;
+}
+
+bool ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "The ExtensionPolicyService is not thread safe");
+ // Create special moz-extension://foo/_generated_background_page.html page
+ // for all registered extensions. We can't just do this as a substitution
+ // because substitutions can only match on host.
+ if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) {
+ return false;
+ }
+
+ if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
+ Unused << EPS().GetGeneratedBackgroundPageUrl(aHost, aResult);
+ return !aResult.IsEmpty();
+ }
+
+ return false;
+}
+
+// For file or JAR URI's, substitute in a remote channel.
+Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ nsAutoCString unResolvedSpec;
+ MOZ_TRY(aURI->GetSpec(unResolvedSpec));
+
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ // Use the target URI scheme to determine if this is a packed or unpacked
+ // extension URI. For unpacked extensions, we'll request an input stream
+ // from the parent. For a packed extension, we'll request a file descriptor
+ // for the JAR file.
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ if (scheme.EqualsLiteral("file")) {
+ // Unpacked extension
+ SubstituteRemoteFileChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+ return Ok();
+ }
+
+ if (scheme.EqualsLiteral("jar")) {
+ // Packed extension
+ return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+ }
+
+ // Only unpacked resource files and JAR files are remoted.
+ // No other moz-extension loads should be reading from the filesystem.
+ return Ok();
+}
+
+void OpenWhenReady(
+ Promise* aPromise, nsIStreamListener* aListener, nsIChannel* aChannel,
+ const std::function<nsresult(nsIStreamListener*, nsIChannel*)>& aCallback) {
+ nsCOMPtr<nsIStreamListener> listener(aListener);
+ nsCOMPtr<nsIChannel> channel(aChannel);
+
+ Unused << aPromise->ThenWithCycleCollectedArgs(
+ [channel, aCallback](
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ nsIStreamListener* aListener) -> already_AddRefed<Promise> {
+ nsresult rv = aCallback(aListener, channel);
+ if (NS_FAILED(rv)) {
+ ExtensionStreamGetter::CancelRequest(aListener, channel, rv);
+ }
+ return nullptr;
+ },
+ listener);
+}
+
+nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ if (mUseRemoteFileChannels) {
+ MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result));
+ }
+
+ auto* policy = EPS().GetByURL(aURI);
+ NS_ENSURE_TRUE(policy, NS_ERROR_UNEXPECTED);
+
+ RefPtr<dom::Promise> readyPromise(policy->ReadyPromise());
+
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
+ MOZ_TRY(rv);
+
+ nsAutoCString ext;
+ MOZ_TRY(url->GetFileExtension(ext));
+ ToLowerCase(ext);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (ext.EqualsLiteral("css")) {
+ // Filter CSS files to replace locale message tokens with localized strings.
+ static const auto convert = [](nsIStreamListener* listener,
+ nsIChannel* channel,
+ nsIChannel* origChannel) -> nsresult {
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> convService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_TRY(channel->GetURI(getter_AddRefs(uri)));
+
+ const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
+ const char* kToType = "text/css";
+
+ nsCOMPtr<nsIStreamListener> converter;
+ MOZ_TRY(convService->AsyncConvertData(kFromType, kToType, listener, uri,
+ getter_AddRefs(converter)));
+
+ return origChannel->AsyncOpen(converter);
+ };
+
+ channel = NS_NewSimpleChannel(
+ aURI, aLoadInfo, *result,
+ [readyPromise](nsIStreamListener* listener, nsIChannel* channel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ if (readyPromise) {
+ nsCOMPtr<nsIChannel> chan(channel);
+ OpenWhenReady(
+ readyPromise, listener, origChannel,
+ [chan](nsIStreamListener* aListener, nsIChannel* aChannel) {
+ return convert(aListener, chan, aChannel);
+ });
+ } else {
+ MOZ_TRY(convert(listener, channel, origChannel));
+ }
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+ } else if (readyPromise) {
+ size_t matchIdx;
+ if (BinarySearchIf(
+ sStaticFileExtensions, 0, ArrayLength(sStaticFileExtensions),
+ [&ext](const char* aOther) {
+ return Compare(ext, nsDependentCString(aOther));
+ },
+ &matchIdx)) {
+ // This is a static resource that shouldn't depend on the extension being
+ // ready. Don't bother waiting for it.
+ return NS_OK;
+ }
+
+ channel = NS_NewSimpleChannel(
+ aURI, aLoadInfo, *result,
+ [readyPromise](nsIStreamListener* listener, nsIChannel* channel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ OpenWhenReady(readyPromise, listener, origChannel,
+ [](nsIStreamListener* aListener, nsIChannel* aChannel) {
+ return aChannel->AsyncOpen(aListener);
+ });
+
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+ } else {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY);
+ if (aLoadInfo) {
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
+ (*result)->SetLoadInfo(loadInfo);
+ }
+
+ channel.swap(*result);
+ return NS_OK;
+}
+
+Result<bool, nsresult> ExtensionProtocolHandler::AllowExternalResource(
+ nsIFile* aExtensionDir, nsIFile* aRequestedFile) {
+ MOZ_ASSERT(!IsNeckoChild());
+
+#if defined(XP_WIN)
+ // On Windows, non-package builds don't use symlinks so we never need to
+ // allow a resource from outside of the extension dir.
+ return false;
+#else
+ if (mozilla::IsPackagedBuild()) {
+ return false;
+ }
+
+ // On Mac and Linux unpackaged dev builds, system extensions use
+ // symlinks to point to resources in the repo dir which we have to
+ // allow loading. Before we allow an unpacked extension to load a
+ // resource outside of the extension dir, we make sure the extension
+ // dir is within the app directory.
+ bool result;
+ MOZ_TRY_VAR(result, AppDirContains(aExtensionDir));
+ if (!result) {
+ return false;
+ }
+
+# if defined(XP_MACOSX)
+ // Additionally, on Mac dev builds, we make sure that the requested
+ // resource is within the repo dir. We don't perform this check on Linux
+ // because we don't have a reliable path to the repo dir on Linux.
+ return DevRepoContains(aRequestedFile);
+# else /* XP_MACOSX */
+ return true;
+# endif
+#endif /* defined(XP_WIN) */
+}
+
+#if defined(XP_MACOSX)
+// The |aRequestedFile| argument must already be Normalize()'d
+Result<bool, nsresult> ExtensionProtocolHandler::DevRepoContains(
+ nsIFile* aRequestedFile) {
+ MOZ_ASSERT(!mozilla::IsPackagedBuild());
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // On the first invocation, set mDevRepo
+ if (!mAlreadyCheckedDevRepo) {
+ mAlreadyCheckedDevRepo = true;
+ MOZ_TRY(nsMacUtilsImpl::GetRepoDir(getter_AddRefs(mDevRepo)));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ nsAutoCString repoPath;
+ Unused << mDevRepo->GetNativePath(repoPath);
+ LOG("Repo path: %s", repoPath.get());
+ }
+ }
+
+ bool result = false;
+ if (mDevRepo) {
+ MOZ_TRY(mDevRepo->Contains(aRequestedFile, &result));
+ }
+ return result;
+}
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+Result<bool, nsresult> ExtensionProtocolHandler::AppDirContains(
+ nsIFile* aExtensionDir) {
+ MOZ_ASSERT(!mozilla::IsPackagedBuild());
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // On the first invocation, set mAppDir
+ if (!mAlreadyCheckedAppDir) {
+ mAlreadyCheckedAppDir = true;
+ MOZ_TRY(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(mAppDir)));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ nsAutoCString appDirPath;
+ Unused << mAppDir->GetNativePath(appDirPath);
+ LOG("AppDir path: %s", appDirPath.get());
+ }
+ }
+
+ bool result = false;
+ if (mAppDir) {
+ MOZ_TRY(mAppDir->Contains(aExtensionDir, &result));
+ }
+ return result;
+}
+#endif /* !defined(XP_WIN) */
+
+static void LogExternalResourceError(nsIFile* aExtensionDir,
+ nsIFile* aRequestedFile) {
+ MOZ_ASSERT(aExtensionDir);
+ MOZ_ASSERT(aRequestedFile);
+
+ LOG("Rejecting external unpacked extension resource [%s] from "
+ "extension directory [%s]",
+ aRequestedFile->HumanReadablePath().get(),
+ aExtensionDir->HumanReadablePath().get());
+}
+
+Result<nsCOMPtr<nsIInputStream>, nsresult> ExtensionProtocolHandler::NewStream(
+ nsIURI* aChildURI, bool* aTerminateSender) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // We should never receive a URI that isn't for a moz-extension because
+ // these requests ordinarily come from the child's ExtensionProtocolHandler.
+ // Ensure this request is for a moz-extension URI. A rogue child process
+ // could send us any URI.
+ if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) {
+ return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child to be terminated because
+ // the error is likely to be due to a bug in the extension.
+ *aTerminateSender = false;
+
+ /*
+ * Make sure there is a substitution installed for the host found
+ * in the child's request URI and make sure the host resolves to
+ * a directory.
+ */
+
+ nsAutoCString host;
+ MOZ_TRY(aChildURI->GetAsciiHost(host));
+
+ // Lookup the directory this host string resolves to
+ nsCOMPtr<nsIURI> baseURI;
+ MOZ_TRY(GetSubstitution(host, getter_AddRefs(baseURI)));
+
+ // The result should be a file URL for the extension base dir
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(baseURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> extensionDir;
+ MOZ_TRY(fileURL->GetFile(getter_AddRefs(extensionDir)));
+
+ bool isDirectory = false;
+ MOZ_TRY(extensionDir->IsDirectory(&isDirectory));
+ if (!isDirectory) {
+ // The host should map to a directory for unpacked extensions
+ return Err(NS_ERROR_FILE_NOT_DIRECTORY);
+ }
+
+ // Make sure the child URI resolves to a file URI then get a file
+ // channel for the request. The resultant channel should be a
+ // file channel because we only request remote streams for unpacked
+ // extension resource loads where the URI resolves to a file.
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aChildURI, resolvedSpec));
+
+ nsAutoCString resolvedScheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, resolvedScheme));
+ if (!resolvedScheme.EqualsLiteral("file")) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ MOZ_TRY(ioService->NewURI(resolvedSpec, nullptr, nullptr,
+ getter_AddRefs(resolvedURI)));
+
+ // We use the system principal to get a file channel for the request,
+ // but only after we've checked (above) that the child URI is of
+ // moz-extension scheme and that the URI host maps to a directory.
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_TRY(NS_NewChannel(getter_AddRefs(channel), resolvedURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER));
+
+ nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> requestedFile;
+ MOZ_TRY(fileChannel->GetFile(getter_AddRefs(requestedFile)));
+
+ /*
+ * Make sure the file we resolved to is within the extension directory.
+ */
+
+ // Normalize paths for sane comparisons. nsIFile::Contains depends on
+ // it for reliable subpath checks.
+ MOZ_TRY(extensionDir->Normalize());
+ MOZ_TRY(requestedFile->Normalize());
+#if defined(XP_WIN)
+ if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(extensionDir) ||
+ !widget::WinUtils::ResolveJunctionPointsAndSymLinks(requestedFile)) {
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ }
+#endif
+
+ bool isResourceFromExtensionDir = false;
+ MOZ_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir));
+ if (!isResourceFromExtensionDir) {
+ bool isAllowed;
+ MOZ_TRY_VAR(isAllowed, AllowExternalResource(extensionDir, requestedFile));
+ if (!isAllowed) {
+ LogExternalResourceError(extensionDir, requestedFile);
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ MOZ_TRY_VAR(inputStream,
+ NS_NewLocalFileInputStream(requestedFile, PR_RDONLY, -1,
+ nsIFileInputStream::DEFER_OPEN));
+
+ return inputStream;
+}
+
+Result<Ok, nsresult> ExtensionProtocolHandler::NewFD(
+ nsIURI* aChildURI, bool* aTerminateSender,
+ NeckoParent::GetExtensionFDResolver& aResolve) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // Ensure this is a moz-extension URI
+ if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) {
+ return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child to be terminated.
+ *aTerminateSender = false;
+
+ nsAutoCString host;
+ MOZ_TRY(aChildURI->GetAsciiHost(host));
+
+ // We expect the host string to map to a JAR file because the URI
+ // should refer to a web accessible resource for an enabled extension.
+ nsCOMPtr<nsIURI> subURI;
+ MOZ_TRY(GetSubstitution(host, getter_AddRefs(subURI)));
+
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(subURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ if (!mFileOpenerThread) {
+ mFileOpenerThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+ "ExtensionProtocolHandler");
+ }
+
+ RefPtr<ExtensionJARFileOpener> fileOpener =
+ new ExtensionJARFileOpener(jarFile, aResolve);
+
+ nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
+ "ExtensionJarFileOpener", fileOpener, &ExtensionJARFileOpener::OpenFile);
+
+ MOZ_TRY(mFileOpenerThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL));
+
+ return Ok();
+}
+
+// Set the channel's content type using the provided URI's type
+
+// static
+void ExtensionProtocolHandler::SetContentType(nsIURI* aURI,
+ nsIChannel* aChannel) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString contentType;
+ rv = mime->GetTypeFromURI(aURI, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ Unused << aChannel->SetContentType(contentType);
+ }
+ }
+}
+
+// Gets a SimpleChannel that wraps the provided ExtensionStreamGetter
+
+// static
+void ExtensionProtocolHandler::NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, ExtensionStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aStreamGetter,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ ExtensionStreamGetter* getter) -> RequestOrReason {
+ return getter->GetAsync(listener, simpleChannel);
+ });
+
+ SetContentType(aURI, channel);
+ channel.swap(*aRetVal);
+}
+
+// Gets a SimpleChannel that wraps the provided channel
+
+// static
+void ExtensionProtocolHandler::NewSimpleChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadinfo,
+ nsIChannel* aChannel,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aChannel,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ nsresult rv = origChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ simpleChannel->Cancel(NS_BINDING_ABORTED);
+ return Err(rv);
+ }
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+
+ SetContentType(aURI, channel);
+ channel.swap(*aRetVal);
+}
+
+void ExtensionProtocolHandler::SubstituteRemoteFileChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedFileSpec,
+ nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+
+ RefPtr<ExtensionStreamGetter> streamGetter =
+ new ExtensionStreamGetter(aURI, aLoadinfo);
+
+ NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+}
+
+static Result<Ok, nsresult> LogCacheCheck(const nsIJARChannel* aJarChannel,
+ nsIJARURI* aJarURI, bool aIsCached) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(aJarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ nsAutoCString uriSpec, jarSpec;
+ Unused << aJarURI->GetSpec(uriSpec);
+ Unused << innerFileURI->GetSpec(jarSpec);
+ LOG("[JARChannel %p] Cache %s: %s (%s)", aJarChannel,
+ aIsCached ? "hit" : "miss", uriSpec.get(), jarSpec.get());
+
+ return Ok();
+}
+
+Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteJarChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedSpec,
+ nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ nsresult rv;
+
+ // Build a JAR URI for this jar:file:// URI and use it to extract the
+ // inner file URI.
+ nsCOMPtr<nsIURI> uri;
+ MOZ_TRY(NS_NewURI(getter_AddRefs(uri), aResolvedSpec));
+
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(*aRetVal, &rv);
+ MOZ_TRY(rv);
+
+ bool isCached = false;
+ MOZ_TRY(jarChannel->EnsureCached(&isCached));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ Unused << LogCacheCheck(jarChannel, jarURI, isCached);
+ }
+
+ if (isCached) {
+ // Using a SimpleChannel with an ExtensionStreamGetter here (like the
+ // non-cached JAR case) isn't needed to load the extension resource
+ // because we don't need to ask the parent for an FD for the JAR, but
+ // wrapping the JARChannel in a SimpleChannel allows HTTP forwarding to
+ // moz-extension URI's to work because HTTP forwarding requires the
+ // target channel implement nsIChildChannel.
+ NewSimpleChannel(aURI, aLoadinfo, jarChannel.get(), aRetVal);
+ return Ok();
+ }
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ RefPtr<ExtensionStreamGetter> streamGetter =
+ new ExtensionStreamGetter(aURI, aLoadinfo, jarChannel.forget(), jarFile);
+
+ NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+ return Ok();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.h b/netwerk/protocol/res/ExtensionProtocolHandler.h
new file mode 100644
index 0000000000..0824d7dd99
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -0,0 +1,236 @@
+/* -*- 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 ExtensionProtocolHandler_h___
+#define ExtensionProtocolHandler_h___
+
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/Result.h"
+#include "SubstitutingProtocolHandler.h"
+
+namespace mozilla {
+namespace net {
+
+class ExtensionStreamGetter;
+
+class ExtensionProtocolHandler final
+ : public nsISubstitutingProtocolHandler,
+ public nsIProtocolHandlerWithDynamicFlags,
+ public SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+ NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+ NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+
+ static already_AddRefed<ExtensionProtocolHandler> GetSingleton();
+
+ /**
+ * To be called in the parent process to obtain an input stream for a
+ * a web accessible resource from an unpacked WebExtension dir.
+ *
+ * @param aChildURI a moz-extension URI sent from the child that refers
+ * to a web accessible resource file in an enabled unpacked extension
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-extension URI, the child is in an invalid state and
+ * should be terminated.
+ * @return NS_OK with |aTerminateSender| set to false on success. On
+ * failure, returns an error and sets |aTerminateSender| to indicate
+ * whether or not the child process should be terminated.
+ * A moz-extension URI from the child that doesn't resolve to a
+ * resource file within the extension could be the result of a bug
+ * in the extension and doesn't result in |aTerminateSender| being
+ * set to true.
+ */
+ Result<nsCOMPtr<nsIInputStream>, nsresult> NewStream(nsIURI* aChildURI,
+ bool* aTerminateSender);
+
+ /**
+ * To be called in the parent process to obtain a file descriptor for an
+ * enabled WebExtension JAR file.
+ *
+ * @param aChildURI a moz-extension URI sent from the child that refers
+ * to a web accessible resource file in an enabled unpacked extension
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-extension URI, the child is in an invalid state and
+ * should be terminated.
+ * @param aPromise a promise that will be resolved asynchronously when the
+ * file descriptor is available.
+ * @return NS_OK with |aTerminateSender| set to false on success. On
+ * failure, returns an error and sets |aTerminateSender| to indicate
+ * whether or not the child process should be terminated.
+ * A moz-extension URI from the child that doesn't resolve to an
+ * enabled WebExtension JAR could be the result of a bug in the
+ * extension and doesn't result in |aTerminateSender| being
+ * set to true.
+ */
+ Result<Ok, nsresult> NewFD(nsIURI* aChildURI, bool* aTerminateSender,
+ NeckoParent::GetExtensionFDResolver& aResolve);
+
+ protected:
+ ~ExtensionProtocolHandler() = default;
+
+ private:
+ explicit ExtensionProtocolHandler();
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ // |result| is an inout param. On entry to this function, *result
+ // is expected to be non-null and already addrefed. This function
+ // may release the object stored in *result on entry and write
+ // a new pointer to an already addrefed channel to *result.
+ [[nodiscard]] virtual nsresult SubstituteChannel(
+ nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) override;
+
+ /**
+ * For moz-extension URI's that resolve to file or JAR URI's, replaces
+ * the provided channel with a channel that will proxy the load to the
+ * parent process. For moz-extension URI's that resolve to other types
+ * of URI's (not file or JAR), the provide channel is not replaced and
+ * NS_OK is returned.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if the channel does not need to be substituted or
+ * or the replacement channel was created successfully.
+ * Otherwise returns an error.
+ */
+ Result<Ok, nsresult> SubstituteRemoteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal);
+
+ /**
+ * Replaces a file channel with a remote file channel for loading a
+ * web accessible resource for an unpacked extension from the parent.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aResolvedFileSpec the resolved URI spec for the file.
+ * @param aRetVal in/out param referring to the new remote channel.
+ * The reference to the input param file channel is dropped and
+ * replaced with a reference to a new channel that remotes
+ * the file access. The new channel encapsulates a request to
+ * the parent for an IPCStream for the file.
+ */
+ void SubstituteRemoteFileChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ nsACString& aResolvedFileSpec,
+ nsIChannel** aRetVal);
+
+ /**
+ * Replaces a JAR channel with a remote JAR channel for loading a
+ * an extension JAR file from the parent.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aResolvedFileSpec the resolved URI spec for the file.
+ * @param aRetVal in/out param referring to the new remote channel.
+ * The input param JAR channel is replaced with a new channel
+ * that remotes the JAR file access. The new channel encapsulates
+ * a request to the parent for the JAR file FD.
+ */
+ Result<Ok, nsresult> SubstituteRemoteJarChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadinfo,
+ nsACString& aResolvedSpec,
+ nsIChannel** aRetVal);
+
+ /**
+ * Sets the aResult outparam to true if this unpacked extension load of
+ * a resource that is outside the extension dir should be allowed. This
+ * is only allowed for system extensions on Mac and Linux dev builds.
+ *
+ * @param aExtensionDir the extension directory. Argument must be an
+ * nsIFile for which Normalize() has already been called.
+ * @param aRequestedFile the requested web-accessible resource file. Argument
+ * must be an nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> AllowExternalResource(nsIFile* aExtensionDir,
+ nsIFile* aRequestedFile);
+
+ // Set the channel's content type using the provided URI's type
+ static void SetContentType(nsIURI* aURI, nsIChannel* aChannel);
+
+ // Gets a SimpleChannel that wraps the provided ExtensionStreamGetter
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ ExtensionStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal);
+
+ // Gets a SimpleChannel that wraps the provided channel
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ nsIChannel* aChannel, nsIChannel** aRetVal);
+
+#if defined(XP_MACOSX)
+ /**
+ * Sets the aResult outparam to true if we are a developer build with the
+ * repo dir environment variable set and the requested file resides in the
+ * repo dir. Developer builds may load system extensions with web-accessible
+ * resources that are symlinks to files in the repo dir. This method is for
+ * checking if an unpacked resource requested by the child is from the repo.
+ * The requested file must be already Normalized(). Only compile this for
+ * Mac because the repo dir isn't always available on Linux.
+ *
+ * @param aRequestedFile the requested web-accessible resource file. Argument
+ * must be an nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> DevRepoContains(nsIFile* aRequestedFile);
+
+ // On development builds, this points to development repo. Lazily set.
+ nsCOMPtr<nsIFile> mDevRepo;
+
+ // Set to true once we've already tried to load the dev repo path,
+ // allowing for lazy initialization of |mDevRepo|.
+ bool mAlreadyCheckedDevRepo{false};
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+ /**
+ * Sets the aResult outparam to true if we are a developer build and the
+ * provided directory is within the NS_GRE_DIR directory. Developer builds
+ * may load system extensions with web-accessible resources that are symlinks
+ * to files outside of the extension dir to the repo dir. This method is for
+ * checking if an extension directory is within NS_GRE_DIR. In that case, we
+ * consider the extension a system extension and allow it to use symlinks to
+ * resources outside of the extension dir. This exception is only applied
+ * to loads for unpacked extensions in unpackaged developer builds.
+ * The requested dir must be already Normalized().
+ *
+ * @param aExtensionDir the extension directory. Argument must be an
+ * nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> AppDirContains(nsIFile* aExtensionDir);
+
+ // On development builds, cache the NS_GRE_DIR repo. Lazily set.
+ nsCOMPtr<nsIFile> mAppDir;
+
+ // Set to true once we've already read the AppDir, allowing for lazy
+ // initialization of |mAppDir|.
+ bool mAlreadyCheckedAppDir{false};
+#endif /* !defined(XP_WIN) */
+
+ // Used for opening JAR files off the main thread when we just need to
+ // obtain a file descriptor to send back to the child.
+ RefPtr<mozilla::LazyIdleThread> mFileOpenerThread;
+
+ // To allow parent IPDL actors to invoke methods on this handler when
+ // handling moz-extension requests from the child.
+ static StaticRefPtr<ExtensionProtocolHandler> sSingleton;
+
+ // Set to true when this instance of the handler must proxy loads of
+ // extension web-accessible resources to the parent process.
+ bool mUseRemoteFileChannels;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* ExtensionProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/PageThumbProtocolHandler.cpp b/netwerk/protocol/res/PageThumbProtocolHandler.cpp
new file mode 100644
index 0000000000..5918b40fad
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.cpp
@@ -0,0 +1,361 @@
+/* -*- 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 "PageThumbProtocolHandler.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ipc/URIParams.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Try.h"
+
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIMIMEService.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIPageThumbsStorageService.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "prio.h"
+#include "SimpleChannel.h"
+#include "nsICancelable.h"
+
+#ifdef MOZ_PLACES
+# include "nsIPlacesPreviewsHelperService.h"
+#endif
+
+#define PAGE_THUMB_HOST "thumbnails"
+#define PLACES_PREVIEWS_HOST "places-previews"
+#define PAGE_THUMB_SCHEME "moz-page-thumb"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gPageThumbProtocolLog("PageThumbProtocol");
+
+#undef LOG
+#define LOG(level, ...) \
+ MOZ_LOG(gPageThumbProtocolLog, LogLevel::level, (__VA_ARGS__))
+
+StaticRefPtr<PageThumbProtocolHandler> PageThumbProtocolHandler::sSingleton;
+
+NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
+
+already_AddRefed<PageThumbProtocolHandler>
+PageThumbProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new PageThumbProtocolHandler();
+ ClearOnShutdown(&sSingleton);
+ }
+
+ return do_AddRef(sSingleton);
+}
+
+// A moz-page-thumb URI is only loadable by chrome pages in the parent process,
+// or privileged content running in the privileged about content process.
+PageThumbProtocolHandler::PageThumbProtocolHandler()
+ : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME) {}
+
+RefPtr<RemoteStreamPromise> PageThumbProtocolHandler::NewStream(
+ nsIURI* aChildURI, bool* aTerminateSender) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aChildURI || !aTerminateSender) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
+ }
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // We should never receive a URI that isn't for a moz-page-thumb because
+ // these requests ordinarily come from the child's PageThumbProtocolHandler.
+ // Ensure this request is for a moz-page-thumb URI. A compromised child
+ // process could send us any URI.
+ bool isPageThumbScheme = false;
+ if (NS_FAILED(aChildURI->SchemeIs(PAGE_THUMB_SCHEME, &isPageThumbScheme)) ||
+ !isPageThumbScheme) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL,
+ __func__);
+ }
+
+ // We should never receive a URI that does not have "thumbnails" as the host.
+ nsAutoCString host;
+ if (NS_FAILED(aChildURI->GetAsciiHost(host)) ||
+ !(host.EqualsLiteral(PAGE_THUMB_HOST) ||
+ host.EqualsLiteral(PLACES_PREVIEWS_HOST))) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child process to be terminated.
+ *aTerminateSender = false;
+
+ // Make sure the child URI resolves to a file URI. We will then get a file
+ // channel for the request. The resultant channel should be a file channel
+ // because we only request remote streams for resource loads where the URI
+ // resolves to a file.
+ nsAutoCString resolvedSpec;
+ rv = ResolveURI(aChildURI, resolvedSpec);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsAutoCString resolvedScheme;
+ rv = net_ExtractURLScheme(resolvedSpec, resolvedScheme);
+ if (NS_FAILED(rv) || !resolvedScheme.EqualsLiteral("file")) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = ioService->NewURI(resolvedSpec, nullptr, nullptr,
+ getter_AddRefs(resolvedURI));
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ // We use the system principal to get a file channel for the request,
+ // but only after we've checked (above) that the child URI is of
+ // moz-page-thumb scheme and that the URI host matches PAGE_THUMB_HOST.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), resolvedURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ auto promiseHolder = MakeUnique<MozPromiseHolder<RemoteStreamPromise>>();
+ RefPtr<RemoteStreamPromise> promise = promiseHolder->Ensure(__func__);
+
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsAutoCString contentType;
+ rv = mime->GetTypeFromURI(aChildURI, contentType);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ rv = NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "PageThumbProtocolHandler::NewStream",
+ [contentType, channel, holder = std::move(promiseHolder)]() {
+ nsresult rv;
+
+ nsCOMPtr<nsIFileChannel> fileChannel =
+ do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> requestedFile;
+ rv = fileChannel->GetFile(getter_AddRefs(requestedFile));
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ requestedFile, PR_RDONLY, -1);
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ RemoteStreamInfo info(inputStream, contentType, -1);
+
+ holder->Resolve(std::move(info), __func__);
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ return promise;
+}
+
+bool PageThumbProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ // This should match the scheme in PageThumbs.jsm. We will only resolve
+ // URIs for thumbnails generated by PageThumbs here.
+ if (!aHost.EqualsLiteral(PAGE_THUMB_HOST) &&
+ !aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
+ // moz-page-thumb should always have a "thumbnails" host. We do not intend
+ // to allow substitution rules to be created for moz-page-thumb.
+ return false;
+ }
+
+ // Regardless of the outcome, the scheme will be resolved to file://.
+ aResult.Assign("file://");
+
+ if (IsNeckoChild()) {
+ // We will resolve the URI in the parent if load is performed in the child
+ // because the child does not have access to the profile directory path.
+ // Technically we could retrieve the path from dom::ContentChild, but I
+ // would prefer to obtain the path from PageThumbsStorageService (which
+ // depends on OS.Path). Here, we resolve to the same URI, with the file://
+ // scheme. This won't ever be accessed directly by the content process,
+ // and is mainly used to keep the substitution protocol handler mechanism
+ // happy.
+ aResult.Append(aHost);
+ aResult.Append(aPath);
+ } else {
+ // Resolve the URI in the parent to the thumbnail file URI since we will
+ // attempt to open the channel to load the file after this.
+ nsAutoString thumbnailUrl;
+ nsresult rv = GetThumbnailPath(aPath, aHost, thumbnailUrl);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ aResult.Append(NS_ConvertUTF16toUTF8(thumbnailUrl));
+ }
+
+ return true;
+}
+
+nsresult PageThumbProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal) {
+ // Check if URI resolves to a file URI.
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ if (!scheme.EqualsLiteral("file")) {
+ NS_WARNING("moz-page-thumb URIs should only resolve to file URIs.");
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // Load the URI remotely if accessed from a child.
+ if (IsNeckoChild()) {
+ MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, aRetVal));
+ }
+
+ return NS_OK;
+}
+
+Result<Ok, nsresult> PageThumbProtocolHandler::SubstituteRemoteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+
+#ifdef DEBUG
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ MOZ_ASSERT(scheme.EqualsLiteral("file"));
+#endif /* DEBUG */
+
+ RefPtr<RemoteStreamGetter> streamGetter =
+ new RemoteStreamGetter(aURI, aLoadInfo);
+
+ NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
+ return Ok();
+}
+
+nsresult PageThumbProtocolHandler::GetThumbnailPath(const nsACString& aPath,
+ const nsACString& aHost,
+ nsString& aThumbnailPath) {
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // Ensures that the provided path has a query string. We will start parsing
+ // from there.
+ int32_t queryIndex = aPath.FindChar('?');
+ if (queryIndex <= 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Extract URL from query string.
+ nsAutoString url;
+ bool found =
+ URLParams::Extract(Substring(aPath, queryIndex + 1), u"url"_ns, url);
+ if (!found || url.IsVoid()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ if (aHost.EqualsLiteral(PAGE_THUMB_HOST)) {
+ nsCOMPtr<nsIPageThumbsStorageService> pageThumbsStorage =
+ do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // Use PageThumbsStorageService to get the local file path of the screenshot
+ // for the given URL.
+ rv = pageThumbsStorage->GetFilePathForURL(url, aThumbnailPath);
+#ifdef MOZ_PLACES
+ } else if (aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
+ nsCOMPtr<nsIPlacesPreviewsHelperService> helper =
+ do_GetService("@mozilla.org/places/previews-helper;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = helper->GetFilePathForURL(url, aThumbnailPath);
+#endif
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+// static
+void PageThumbProtocolHandler::NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, RemoteStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aStreamGetter,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ RemoteStreamGetter* getter) -> RequestOrReason {
+ return getter->GetAsync(listener, simpleChannel,
+ &NeckoChild::SendGetPageThumbStream);
+ });
+
+ channel.swap(*aRetVal);
+}
+
+#undef LOG
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/PageThumbProtocolHandler.h b/netwerk/protocol/res/PageThumbProtocolHandler.h
new file mode 100644
index 0000000000..da670d52d4
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.h
@@ -0,0 +1,120 @@
+/* -*- 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 PageThumbProtocolHandler_h___
+#define PageThumbProtocolHandler_h___
+
+#include "mozilla/Result.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/RemoteStreamGetter.h"
+#include "SubstitutingProtocolHandler.h"
+#include "nsIInputStream.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+class RemoteStreamGetter;
+
+class PageThumbProtocolHandler final : public nsISubstitutingProtocolHandler,
+ public SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+ NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+
+ static already_AddRefed<PageThumbProtocolHandler> GetSingleton();
+
+ /**
+ * To be called in the parent process to obtain an input stream for the
+ * given thumbnail.
+ *
+ * @param aChildURI a moz-page-thumb URI sent from the child.
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-page-thumb URI, the child is in an invalid state and
+ * should be terminated. This outparam will be set synchronously.
+ *
+ * @return RemoteStreamPromise
+ * The RemoteStreamPromise will resolve with an RemoteStreamInfo on
+ * success, and reject with an nsresult on failure.
+ */
+ RefPtr<RemoteStreamPromise> NewStream(nsIURI* aChildURI,
+ bool* aTerminateSender);
+
+ protected:
+ ~PageThumbProtocolHandler() = default;
+
+ private:
+ explicit PageThumbProtocolHandler();
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ /**
+ * On entry to this function, *aRetVal is expected to be non-null and already
+ * addrefed. This function may release the object stored in *aRetVal on entry
+ * and write a new pointer to an already addrefed channel to *aRetVal.
+ *
+ * @param aURI the moz-page-thumb URI.
+ * @param aLoadInfo the loadinfo for the request.
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if channel has been substituted successfully or no
+ * substitution at all. Otherwise, returns an error. This function
+ * will return NS_ERROR_NO_INTERFACE if the URI resolves to a
+ * non file:// URI.
+ */
+ [[nodiscard]] virtual nsresult SubstituteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) override;
+
+ /**
+ * This replaces the provided channel with a channel that will proxy the load
+ * to the parent process.
+ *
+ * @param aURI the moz-page-thumb URI.
+ * @param aLoadInfo the loadinfo for the request.
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if the replacement channel was created successfully.
+ * Otherwise, returns an error.
+ */
+ Result<Ok, nsresult> SubstituteRemoteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal);
+
+ /*
+ * Extracts the URL from the query string in the given moz-page-thumb URI
+ * and queries PageThumbsStorageService using the extracted URL to obtain
+ * the local file path of the screenshot. This should only be called from
+ * the parent because PageThumbsStorageService relies on the path of the
+ * profile directory, which is unavailable in the child.
+ *
+ * @param aPath the path of the moz-page-thumb URI.
+ * @param aHost the host of the moz-page-thumb URI.
+ * @param aThumbnailPath in/out string param referring to the thumbnail path.
+ * @return NS_OK if the thumbnail path was obtained successfully. Otherwise
+ * returns an error.
+ */
+ nsresult GetThumbnailPath(const nsACString& aPath, const nsACString& aHost,
+ nsString& aThumbnailPath);
+
+ // To allow parent IPDL actors to invoke methods on this handler when
+ // handling moz-page-thumb requests from the child.
+ static StaticRefPtr<PageThumbProtocolHandler> sSingleton;
+
+ // Gets a SimpleChannel that wraps the provided channel.
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ RemoteStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* PageThumbProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/RemoteStreamGetter.cpp b/netwerk/protocol/res/RemoteStreamGetter.cpp
new file mode 100644
index 0000000000..8bad84ef5e
--- /dev/null
+++ b/netwerk/protocol/res/RemoteStreamGetter.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "RemoteStreamGetter.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsContentUtils.h"
+#include "nsIInputStreamPump.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(RemoteStreamGetter, nsICancelable)
+
+RemoteStreamGetter::RemoteStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mURI(aURI), mLoadInfo(aLoadInfo) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+}
+
+// Request an input stream from the parent.
+RequestOrReason RemoteStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel,
+ Method aMethod) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(aMethod);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
+ RefPtr<RemoteStreamGetter> self = this;
+ LoadInfoArgs loadInfoArgs;
+ nsresult rv = ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ (gNeckoChild->*aMethod)(mURI, loadInfoArgs)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](const Maybe<RemoteStreamInfo>& info) { self->OnStream(info); },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnStream(Nothing());
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+}
+
+// Called to cancel the ongoing async request.
+NS_IMETHODIMP
+RemoteStreamGetter::Cancel(nsresult aStatus) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+
+ if (mPump) {
+ mPump->Cancel(aStatus);
+ mPump = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// static
+void RemoteStreamGetter::CancelRequest(nsIStreamListener* aListener,
+ nsIChannel* aChannel, nsresult aResult) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aChannel);
+
+ aListener->OnStartRequest(aChannel);
+ aListener->OnStopRequest(aChannel, aResult);
+ aChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "RemoteStreamGetter::CancelRequest"_ns);
+}
+
+// Handle an input stream sent from the parent.
+void RemoteStreamGetter::OnStream(const Maybe<RemoteStreamInfo>& aStreamInfo) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener until we pass it on
+ // to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = mListener.forget();
+
+ if (aStreamInfo.isNothing()) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStreamInfo.ref().inputStream());
+ if (!stream) {
+ // We somehow failed to get a stream, so just cancel the request.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ nsresult rv =
+ NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0, 0, false,
+ GetMainThreadSerialEventTarget());
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ channel->SetContentType(aStreamInfo.ref().contentType());
+ channel->SetContentLength(aStreamInfo.ref().contentLength());
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ mPump = pump;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/RemoteStreamGetter.h b/netwerk/protocol/res/RemoteStreamGetter.h
new file mode 100644
index 0000000000..de2bd6b601
--- /dev/null
+++ b/netwerk/protocol/res/RemoteStreamGetter.h
@@ -0,0 +1,68 @@
+/* -*- 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 RemoteStreamGetter_h___
+#define RemoteStreamGetter_h___
+
+#include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsICancelable.h"
+#include "SimpleChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/Maybe.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+using RemoteStreamPromise =
+ mozilla::MozPromise<RemoteStreamInfo, nsresult, false>;
+using Method = RefPtr<
+ MozPromise<Maybe<RemoteStreamInfo>, ipc::ResponseRejectReason, true>> (
+ PNeckoChild::*)(nsIURI*, const LoadInfoArgs&);
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream and metadata from the parent for a remote protocol load from the
+ * child.
+ */
+class RemoteStreamGetter final : public nsICancelable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ public:
+ RemoteStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+
+ // Get an input stream from the parent asynchronously.
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel,
+ Method aMethod);
+
+ // Handle an input stream being returned from the parent
+ void OnStream(const Maybe<RemoteStreamInfo>& aStreamInfo);
+
+ static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
+ nsresult aResult);
+
+ private:
+ ~RemoteStreamGetter() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ bool mCanceled{false};
+ nsresult mStatus{NS_OK};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* RemoteStreamGetter_h___ */
diff --git a/netwerk/protocol/res/SubstitutingJARURI.h b/netwerk/protocol/res/SubstitutingJARURI.h
new file mode 100644
index 0000000000..85976d4908
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingJARURI.h
@@ -0,0 +1,229 @@
+/* -*- 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 SubstitutingJARURI_h
+#define SubstitutingJARURI_h
+
+#include "nsIStandardURL.h"
+#include "nsIURL.h"
+#include "nsJARURI.h"
+#include "nsISerializable.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_SUBSTITUTINGJARURI_IMPL_CID \
+ { /* 8f8c54ed-aba7-4ebf-ba6f-e58aec0aba4c */ \
+ 0x8f8c54ed, 0xaba7, 0x4ebf, { \
+ 0xba, 0x6f, 0xe5, 0x8a, 0xec, 0x0a, 0xba, 0x4c \
+ } \
+ }
+
+// Provides a Substituting URI for resource://-like substitutions which
+// allows consumers to access the underlying jar resource.
+class SubstitutingJARURI : public nsIJARURI,
+ public nsIStandardURL,
+ public nsISerializable {
+ protected:
+ // Contains the resource://-like URI to be mapped. nsIURI and nsIURL will
+ // forward to this.
+ nsCOMPtr<nsIURL> mSource;
+ // Contains the resolved jar resource, nsIJARURI forwards to this to let
+ // consumer acccess the underlying jar resource.
+ nsCOMPtr<nsIJARURI> mResolved;
+ virtual ~SubstitutingJARURI() = default;
+
+ public:
+ SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved);
+ // For deserializing
+ explicit SubstitutingJARURI() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERIALIZABLE
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+ NS_FORWARD_SAFE_NSIURL(mSource)
+ NS_FORWARD_SAFE_NSIJARURI(mResolved)
+
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum { eIgnoreRef, eHonorRef };
+
+ // We cannot forward Equals* methods to |mSource| so we override them here
+ NS_IMETHOD EqualsExceptRef(nsIURI* aOther, bool* aResult) override;
+ NS_IMETHOD Equals(nsIURI* aOther, bool* aResult) override;
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* aOther,
+ RefHandlingEnum aRefHandlingMode,
+ bool* aResult);
+
+ // Forward the rest of nsIURI to mSource
+ NS_IMETHOD GetSpec(nsACString& aSpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetSpec(aSpec);
+ }
+ NS_IMETHOD GetPrePath(nsACString& aPrePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPrePath(aPrePath);
+ }
+ NS_IMETHOD GetScheme(nsACString& aScheme) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetScheme(aScheme);
+ }
+ NS_IMETHOD GetUserPass(nsACString& aUserPass) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUserPass(aUserPass);
+ }
+ NS_IMETHOD GetUsername(nsACString& aUsername) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUsername(aUsername);
+ }
+ NS_IMETHOD GetPassword(nsACString& aPassword) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPassword(aPassword);
+ }
+ NS_IMETHOD GetHostPort(nsACString& aHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHostPort(aHostPort);
+ }
+ NS_IMETHOD GetHost(nsACString& aHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHost(aHost);
+ }
+ NS_IMETHOD GetPort(int32_t* aPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPort(aPort);
+ }
+ NS_IMETHOD GetPathQueryRef(nsACString& aPathQueryRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetPathQueryRef(aPathQueryRef);
+ }
+ NS_IMETHOD SchemeIs(const char* scheme, bool* _retval) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->SchemeIs(scheme, _retval);
+ }
+ NS_IMETHOD Resolve(const nsACString& relativePath,
+ nsACString& _retval) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->Resolve(relativePath, _retval);
+ }
+ NS_IMETHOD GetAsciiSpec(nsACString& aAsciiSpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiSpec(aAsciiSpec);
+ }
+ NS_IMETHOD GetAsciiHostPort(nsACString& aAsciiHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetAsciiHostPort(aAsciiHostPort);
+ }
+ NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiHost(aAsciiHost);
+ }
+ NS_IMETHOD GetRef(nsACString& aRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetRef(aRef);
+ }
+ NS_IMETHOD GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetSpecIgnoringRef(aSpecIgnoringRef);
+ }
+ NS_IMETHOD GetHasRef(bool* aHasRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHasRef(aHasRef);
+ }
+ NS_IMETHOD GetHasUserPass(bool* aHasUserPass) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetHasUserPass(aHasUserPass);
+ }
+ NS_IMETHOD GetHasQuery(bool* aHasQuery) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHasQuery(aHasQuery);
+ }
+ NS_IMETHOD GetFilePath(nsACString& aFilePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetFilePath(aFilePath);
+ }
+ NS_IMETHOD GetQuery(nsACString& aQuery) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetQuery(aQuery);
+ }
+ NS_IMETHOD GetDisplayHost(nsACString& aDisplayHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayHost(aDisplayHost);
+ }
+ NS_IMETHOD GetDisplayHostPort(nsACString& aDisplayHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayHostPort(aDisplayHostPort);
+ }
+ NS_IMETHOD GetDisplaySpec(nsACString& aDisplaySpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplaySpec(aDisplaySpec);
+ }
+ NS_IMETHOD GetDisplayPrePath(nsACString& aDisplayPrePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayPrePath(aDisplayPrePath);
+ }
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override;
+ NS_IMETHOD_(void) Serialize(mozilla::ipc::URIParams& aParams) override;
+
+ private:
+ nsresult Clone(nsIURI** aURI);
+ nsresult SetSpecInternal(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetScheme(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetUserPass(const nsACString& aInput);
+ nsresult SetUsername(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetPassword(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetHostPort(const nsACString& aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetHost(const nsACString& input) { return NS_ERROR_NOT_IMPLEMENTED; }
+ nsresult SetPort(int32_t aPort);
+ nsresult SetPathQueryRef(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetRef(const nsACString& input) { return NS_ERROR_NOT_IMPLEMENTED; }
+ nsresult SetFilePath(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetQuery(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetQueryWithEncoding(const nsACString& input,
+ const mozilla::Encoding* encoding) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ bool Deserialize(const mozilla::ipc::URIParams& aParams);
+ nsresult ReadPrivate(nsIObjectInputStream* aStream);
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<SubstitutingJARURI>,
+ public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+ NS_DEFINE_NSIMUTATOR_COMMON
+
+ // nsISerializable overrides
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ friend SubstitutingJARURI;
+ };
+
+ friend BaseURIMutator<SubstitutingJARURI>;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SubstitutingJARURI,
+ NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingJARURI_h */
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
new file mode 100644
index 0000000000..523aaa435d
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -0,0 +1,723 @@
+/* -*- 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/ModuleUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/URIUtils.h"
+
+#include "SubstitutingProtocolHandler.h"
+#include "SubstitutingURL.h"
+#include "SubstitutingJARURI.h"
+#include "nsIChannel.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIClassInfoImpl.h"
+
+using mozilla::dom::ContentParent;
+
+namespace mozilla {
+namespace net {
+
+// Log module for Substituting Protocol logging. We keep the pre-existing module
+// name of "nsResProtocol" to avoid disruption.
+static LazyLogModule gResLog("nsResProtocol");
+
+static NS_DEFINE_CID(kSubstitutingJARURIImplCID,
+ NS_SUBSTITUTINGJARURI_IMPL_CID);
+
+//---------------------------------------------------------------------------------
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
+// resolution
+//---------------------------------------------------------------------------------
+
+// The list of interfaces should be in sync with nsStandardURL
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(SubstitutingURL::Mutator, nsIURISetters,
+ nsIURIMutator, nsIStandardURLMutator,
+ nsIURLMutator, nsIFileURLMutator,
+ nsISerializable)
+
+NS_IMPL_CLASSINFO(SubstitutingURL, nullptr, nsIClassInfo::THREADSAFE,
+ NS_SUBSTITUTINGURL_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(SubstitutingURL)
+
+NS_IMPL_ADDREF_INHERITED(SubstitutingURL, nsStandardURL)
+NS_IMPL_RELEASE_INHERITED(SubstitutingURL, nsStandardURL)
+NS_IMPL_QUERY_INTERFACE_CI_INHERITED0(SubstitutingURL, nsStandardURL)
+
+nsresult SubstitutingURL::EnsureFile() {
+ nsAutoCString ourScheme;
+ nsresult rv = GetScheme(ourScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the handler associated with this scheme. It would be nice to just
+ // pass this in when constructing SubstitutingURLs, but we need a generic
+ // factory constructor.
+ nsCOMPtr<nsIIOService> io = do_GetIOService(&rv);
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = io->GetProtocolHandler(ourScheme.get(), getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISubstitutingProtocolHandler> substHandler =
+ do_QueryInterface(handler);
+ if (!substHandler) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsAutoCString spec;
+ rv = substHandler->ResolveURI(this, spec);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(spec, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // Bug 585869:
+ // In most cases, the scheme is jar if it's not file.
+ // Regardless, net_GetFileFromURLSpec should be avoided
+ // when the scheme isn't file.
+ if (!scheme.EqualsLiteral("file")) return NS_ERROR_NO_INTERFACE;
+
+ return net_GetFileFromURLSpec(spec, getter_AddRefs(mFile));
+}
+
+/* virtual */
+nsStandardURL* SubstitutingURL::StartClone() {
+ SubstitutingURL* clone = new SubstitutingURL();
+ return clone;
+}
+
+void SubstitutingURL::Serialize(ipc::URIParams& aParams) {
+ nsStandardURL::Serialize(aParams);
+ aParams.get_StandardURLParams().isSubstituting() = true;
+}
+
+// SubstitutingJARURI
+
+SubstitutingJARURI::SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved)
+ : mSource(source), mResolved(resolved) {}
+
+// SubstitutingJARURI::nsIURI
+
+NS_IMETHODIMP
+SubstitutingJARURI::Equals(nsIURI* aOther, bool* aResult) {
+ return EqualsInternal(aOther, eHonorRef, aResult);
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::EqualsExceptRef(nsIURI* aOther, bool* aResult) {
+ return EqualsInternal(aOther, eIgnoreRef, aResult);
+}
+
+nsresult SubstitutingJARURI::EqualsInternal(nsIURI* aOther,
+ RefHandlingEnum aRefHandlingMode,
+ bool* aResult) {
+ *aResult = false;
+ if (!aOther) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ RefPtr<SubstitutingJARURI> other;
+ rv =
+ aOther->QueryInterface(kSubstitutingJARURIImplCID, getter_AddRefs(other));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // We only need to check the source as the resolved URI is the same for a
+ // given source
+ return aRefHandlingMode == eHonorRef
+ ? mSource->Equals(other->mSource, aResult)
+ : mSource->EqualsExceptRef(other->mSource, aResult);
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<SubstitutingJARURI::Mutator> mutator =
+ new SubstitutingJARURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+void SubstitutingJARURI::Serialize(mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ SubstitutingJARURIParams params;
+ URIParams source;
+ URIParams resolved;
+
+ mSource->Serialize(source);
+ mResolved->Serialize(resolved);
+ params.source() = source;
+ params.resolved() = resolved;
+ aParams = params;
+}
+
+// SubstitutingJARURI::nsISerializable
+
+NS_IMETHODIMP
+SubstitutingJARURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT(!mSource);
+ MOZ_ASSERT(!mResolved);
+ NS_ENSURE_ARG_POINTER(aStream);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> source;
+ rv = aStream->ReadObject(true, getter_AddRefs(source));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSource = do_QueryInterface(source, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> resolved;
+ rv = aStream->ReadObject(true, getter_AddRefs(resolved));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mResolved = do_QueryInterface(resolved, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::Write(nsIObjectOutputStream* aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+
+ nsresult rv;
+ rv = aStream->WriteCompoundObject(mSource, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteCompoundObject(mResolved, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult SubstitutingJARURI::Clone(nsIURI** aURI) {
+ RefPtr<SubstitutingJARURI> uri = new SubstitutingJARURI();
+ // SubstitutingJARURI's mSource/mResolved isn't mutable.
+ uri->mSource = mSource;
+ uri->mResolved = mResolved;
+ uri.forget(aURI);
+
+ return NS_OK;
+}
+
+nsresult SubstitutingJARURI::SetUserPass(const nsACString& aInput) {
+ // If setting same value in mSource, return NS_OK;
+ if (!mSource) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsAutoCString sourceUserPass;
+ nsresult rv = mSource->GetUserPass(sourceUserPass);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (aInput.Equals(sourceUserPass)) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult SubstitutingJARURI::SetPort(int32_t aPort) {
+ // If setting same value in mSource, return NS_OK;
+ if (!mSource) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ int32_t sourcePort = -1;
+ nsresult rv = mSource->GetPort(&sourcePort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (aPort == sourcePort) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+bool SubstitutingJARURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TSubstitutingJARURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SubstitutingJARURIParams& jarUriParams =
+ aParams.get_SubstitutingJARURIParams();
+
+ nsCOMPtr<nsIURI> source = DeserializeURI(jarUriParams.source());
+ nsresult rv;
+ mSource = do_QueryInterface(source, &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ nsCOMPtr<nsIURI> jarUri = DeserializeURI(jarUriParams.resolved());
+ mResolved = do_QueryInterface(jarUri, &rv);
+ return NS_SUCCEEDED(rv);
+}
+
+nsresult SubstitutingJARURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ return Read(aStream);
+}
+
+NS_IMPL_CLASSINFO(SubstitutingJARURI, nullptr, 0, NS_SUBSTITUTINGJARURI_CID)
+
+NS_IMPL_ADDREF(SubstitutingJARURI)
+NS_IMPL_RELEASE(SubstitutingJARURI)
+
+NS_INTERFACE_MAP_BEGIN(SubstitutingJARURI)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIJARURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ if (aIID.Equals(kSubstitutingJARURIImplCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_IMPL_QUERY_CLASSINFO(SubstitutingJARURI)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(SubstitutingJARURI, nsIURI, nsIJARURI, nsIURL,
+ nsIStandardURL, nsISerializable)
+
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(SubstitutingJARURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable)
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme,
+ bool aEnforceFileOrJar)
+ : mScheme(aScheme),
+ mSubstitutionsLock("SubstitutingProtocolHandler::mSubstitutions"),
+ mSubstitutions(16),
+ mEnforceFileOrJar(aEnforceFileOrJar) {
+ ConstructInternal();
+}
+
+void SubstitutingProtocolHandler::ConstructInternal() {
+ nsresult rv;
+ mIOService = do_GetIOService(&rv);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOService);
+}
+
+//
+// IPC marshalling.
+//
+
+nsresult SubstitutingProtocolHandler::CollectSubstitutions(
+ nsTArray<SubstitutionMapping>& aMappings) {
+ AutoReadLock lock(mSubstitutionsLock);
+ for (const auto& substitutionEntry : mSubstitutions) {
+ const SubstitutionEntry& entry = substitutionEntry.GetData();
+ nsCOMPtr<nsIURI> uri = entry.baseURI;
+ SerializedURI serialized;
+ if (uri) {
+ nsresult rv = uri->GetSpec(serialized.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ SubstitutionMapping substitution = {mScheme,
+ nsCString(substitutionEntry.GetKey()),
+ serialized, entry.flags};
+ aMappings.AppendElement(substitution);
+ }
+
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI,
+ uint32_t aFlags) {
+ if (GeckoProcessType_Content == XRE_GetProcessType()) {
+ return NS_OK;
+ }
+
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (!parents.Length()) {
+ return NS_OK;
+ }
+
+ SubstitutionMapping mapping;
+ mapping.scheme = mScheme;
+ mapping.path = aRoot;
+ if (aBaseURI) {
+ nsresult rv = aBaseURI->GetSpec(mapping.resolvedURI.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mapping.flags = aFlags;
+
+ for (uint32_t i = 0; i < parents.Length(); i++) {
+ Unused << parents[i]->SendRegisterChromeItem(mapping);
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsIProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult SubstitutingProtocolHandler::GetScheme(nsACString& result) {
+ result = mScheme;
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::NewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** aResult) {
+ // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
+ // Later net_GetFileFromURLSpec() will do a full unescape and we want to
+ // treat them the same way the file system will. (bugs 380994, 394075)
+ nsresult rv;
+ nsAutoCString spec;
+ const char* src = aSpec.BeginReading();
+ const char* end = aSpec.EndReading();
+ const char* last = src;
+
+ spec.SetCapacity(aSpec.Length() + 1);
+ for (; src < end; ++src) {
+ if (*src == '%' && (src < end - 2) && *(src + 1) == '2') {
+ char ch = '\0';
+ if (*(src + 2) == 'f' || *(src + 2) == 'F') {
+ ch = '/';
+ } else if (*(src + 2) == 'e' || *(src + 2) == 'E') {
+ ch = '.';
+ }
+
+ if (ch) {
+ if (last < src) {
+ spec.Append(last, src - last);
+ }
+ spec.Append(ch);
+ src += 2;
+ last = src + 1; // src will be incremented by the loop
+ }
+ }
+ if (*src == '?' || *src == '#') {
+ break; // Don't escape %2f and %2e in the query or ref parts of the URI
+ }
+ }
+
+ if (last < end) {
+ spec.Append(last, end - last);
+ }
+
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ nsCOMPtr<nsIURL> uri;
+ rv =
+ NS_MutateURI(new SubstitutingURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ -1, spec, aCharset, base, nullptr)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ // "android" is the only root that would return the RESOLVE_JAR_URI flag
+ // see nsResProtocolHandler::GetSubstitutionInternal
+ if (MustResolveJAR(host)) {
+ return ResolveJARURI(uri, aResult);
+ }
+
+ uri.forget(aResult);
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::ResolveJARURI(nsIURL* aURL,
+ nsIURI** aResult) {
+ nsAutoCString spec;
+ nsresult rv = ResolveURI(aURL, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = NS_NewURI(getter_AddRefs(resolvedURI), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(resolvedURI);
+ nsAutoCString scheme;
+ innermostURI->GetScheme(scheme);
+
+ // We only ever want to resolve to a local jar.
+ NS_ENSURE_TRUE(scheme.EqualsLiteral("file"), NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIJARURI> jarURI(do_QueryInterface(resolvedURI));
+ if (!jarURI) {
+ // This substitution does not resolve to a jar: URL, so we just
+ // return the plain SubstitutionURL
+ nsCOMPtr<nsIURI> url = aURL;
+ url.forget(aResult);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIJARURI> result = new SubstitutingJARURI(aURL, jarURI);
+ result.forget(aResult);
+
+ return rv;
+}
+
+nsresult SubstitutingProtocolHandler::NewChannel(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+
+ nsAutoCString spec;
+ nsresult rv = ResolveURI(uri, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = NS_NewURI(getter_AddRefs(newURI), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't want to allow the inner protocol handler to modify the result
+ // principal URI since we want either |uri| or anything pre-set by upper
+ // layers to prevail.
+ nsCOMPtr<nsIURI> savedResultPrincipalURI;
+ rv =
+ aLoadInfo->GetResultPrincipalURI(getter_AddRefs(savedResultPrincipalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetResultPrincipalURI(savedResultPrincipalURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = (*result)->SetOriginalURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SubstituteChannel(uri, aLoadInfo, result);
+}
+
+nsresult SubstitutingProtocolHandler::AllowPort(int32_t port,
+ const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsISubstitutingProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult SubstitutingProtocolHandler::SetSubstitution(const nsACString& root,
+ nsIURI* baseURI) {
+ // Add-ons use this API but they should not be able to make anything
+ // content-accessible.
+ return SetSubstitutionWithFlags(root, baseURI, 0);
+}
+
+nsresult SubstitutingProtocolHandler::SetSubstitutionWithFlags(
+ const nsACString& origRoot, nsIURI* baseURI, uint32_t flags) {
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ if (!baseURI) {
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.Remove(root);
+ }
+
+ return SendSubstitution(root, baseURI, flags);
+ }
+
+ // If baseURI isn't a same-scheme URI, we can set the substitution
+ // immediately.
+ nsAutoCString scheme;
+ nsresult rv = baseURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!scheme.Equals(mScheme)) {
+ if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") &&
+ !scheme.EqualsLiteral("jar") && !scheme.EqualsLiteral("app") &&
+ !scheme.EqualsLiteral("resource")) {
+ NS_WARNING("Refusing to create substituting URI to non-file:// target");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{baseURI, flags});
+ }
+
+ return SendSubstitution(root, baseURI, flags);
+ }
+
+ // baseURI is a same-type substituting URI, let's resolve it first.
+ nsAutoCString newBase;
+ rv = ResolveURI(baseURI, newBase);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> newBaseURI;
+ rv =
+ mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{newBaseURI, flags});
+ }
+
+ return SendSubstitution(root, newBaseURI, flags);
+}
+
+nsresult SubstitutingProtocolHandler::GetSubstitution(
+ const nsACString& origRoot, nsIURI** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ {
+ AutoReadLock lock(mSubstitutionsLock);
+ SubstitutionEntry entry;
+ if (mSubstitutions.Get(root, &entry)) {
+ nsCOMPtr<nsIURI> baseURI = entry.baseURI;
+ baseURI.forget(result);
+ return NS_OK;
+ }
+ }
+
+ uint32_t flags;
+ return GetSubstitutionInternal(root, result, &flags);
+}
+
+nsresult SubstitutingProtocolHandler::GetSubstitutionFlags(
+ const nsACString& root, uint32_t* flags) {
+#ifdef DEBUG
+ nsAutoCString lcRoot;
+ ToLowerCase(root, lcRoot);
+ MOZ_ASSERT(root.Equals(lcRoot),
+ "GetSubstitutionFlags should never receive mixed-case root name");
+#endif
+
+ *flags = 0;
+
+ {
+ AutoReadLock lock(mSubstitutionsLock);
+
+ SubstitutionEntry entry;
+ if (mSubstitutions.Get(root, &entry)) {
+ *flags = entry.flags;
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+ return GetSubstitutionInternal(root, getter_AddRefs(baseURI), flags);
+}
+
+nsresult SubstitutingProtocolHandler::HasSubstitution(
+ const nsACString& origRoot, bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ *result = HasSubstitution(root);
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::ResolveURI(nsIURI* uri,
+ nsACString& result) {
+ nsresult rv;
+
+ nsAutoCString host;
+ nsAutoCString path;
+ nsAutoCString pathname;
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+ if (!url) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ rv = uri->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->GetFilePath(pathname);
+ if (NS_FAILED(rv)) return rv;
+
+ if (ResolveSpecialCases(host, path, pathname, result)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+ rv = GetSubstitution(host, getter_AddRefs(baseURI));
+ if (NS_FAILED(rv)) return rv;
+
+ // Unescape the path so we can perform some checks on it.
+ NS_UnescapeURL(pathname);
+ if (pathname.FindChar('\\') != -1) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Some code relies on an empty path resolving to a file rather than a
+ // directory.
+ NS_ASSERTION(path.CharAt(0) == '/', "Path must begin with '/'");
+ if (path.Length() == 1) {
+ rv = baseURI->GetSpec(result);
+ } else {
+ // Make sure we always resolve the path as file-relative to our target URI.
+ // When the baseURI is a nsIFileURL, and the directory it points to doesn't
+ // exist, it doesn't end with a /. In that case, a file-relative resolution
+ // is going to pick something in the parent directory, so we resolve using
+ // an absolute path derived from the full path in that case.
+ nsCOMPtr<nsIFileURL> baseDir = do_QueryInterface(baseURI);
+ if (baseDir) {
+ nsAutoCString basePath;
+ rv = baseURI->GetFilePath(basePath);
+ if (NS_SUCCEEDED(rv) && !StringEndsWith(basePath, "/"_ns)) {
+ // Cf. the assertion above, path already starts with a /, so prefixing
+ // with a string that doesn't end with one will leave us wit the right
+ // amount of /.
+ path.Insert(basePath, 0);
+ } else {
+ // Allow to fall through below.
+ baseDir = nullptr;
+ }
+ }
+ if (!baseDir) {
+ path.Insert('.', 0);
+ }
+ rv = baseURI->Resolve(path, result);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ MOZ_LOG(gResLog, LogLevel::Debug,
+ ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.h b/netwerk/protocol/res/SubstitutingProtocolHandler.h
new file mode 100644
index 0000000000..6bdb27f38a
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -0,0 +1,129 @@
+/* -*- 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 SubstitutingProtocolHandler_h___
+#define SubstitutingProtocolHandler_h___
+
+#include "nsISubstitutingProtocolHandler.h"
+
+#include "nsTHashMap.h"
+#include "nsStandardURL.h"
+#include "nsJARURI.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RWLock.h"
+
+class nsIIOService;
+
+namespace mozilla {
+namespace net {
+
+//
+// Base class for resource://-like substitution protocols.
+//
+// If you add a new protocol, make sure to change nsChromeRegistryChrome
+// to properly invoke CollectSubstitutions at the right time.
+class SubstitutingProtocolHandler {
+ public:
+ explicit SubstitutingProtocolHandler(const char* aScheme,
+ bool aEnforceFileOrJar = true);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SubstitutingProtocolHandler);
+ NS_DECL_NON_VIRTUAL_NSIPROTOCOLHANDLER;
+ NS_DECL_NON_VIRTUAL_NSISUBSTITUTINGPROTOCOLHANDLER;
+
+ bool HasSubstitution(const nsACString& aRoot) const {
+ AutoReadLock lock(const_cast<RWLock&>(mSubstitutionsLock));
+ return mSubstitutions.Get(aRoot, nullptr);
+ }
+
+ nsresult NewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** aResult);
+
+ [[nodiscard]] nsresult CollectSubstitutions(
+ nsTArray<SubstitutionMapping>& aMappings);
+
+ protected:
+ virtual ~SubstitutingProtocolHandler() = default;
+ void ConstructInternal();
+
+ [[nodiscard]] nsresult SendSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI, uint32_t aFlags);
+
+ nsresult GetSubstitutionFlags(const nsACString& root, uint32_t* flags);
+
+ // Override this in the subclass to try additional lookups after checking
+ // mSubstitutions.
+ [[nodiscard]] virtual nsresult GetSubstitutionInternal(
+ const nsACString& aRoot, nsIURI** aResult, uint32_t* aFlags) {
+ *aResult = nullptr;
+ *aFlags = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Override this in the subclass to check for special case when resolving URIs
+ // _before_ checking substitutions.
+ [[nodiscard]] virtual bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ return false;
+ }
+
+ // This method should only return true if GetSubstitutionInternal would
+ // return the RESOLVE_JAR_URI flag.
+ [[nodiscard]] virtual bool MustResolveJAR(const nsACString& aRoot) {
+ return false;
+ }
+
+ // Override this in the subclass to check for special case when opening
+ // channels.
+ [[nodiscard]] virtual nsresult SubstituteChannel(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ return NS_OK;
+ }
+
+ nsIIOService* IOService() { return mIOService; }
+
+ private:
+ struct SubstitutionEntry {
+ nsCOMPtr<nsIURI> baseURI;
+ uint32_t flags = 0;
+ };
+
+ // Notifies all observers that a new substitution from |aRoot| to
+ // |aBaseURI| has been set/installed for this protocol handler.
+ void NotifyObservers(const nsACString& aRoot, nsIURI* aBaseURI);
+
+ nsCString mScheme;
+
+ RWLock mSubstitutionsLock;
+ nsTHashMap<nsCStringHashKey, SubstitutionEntry> mSubstitutions
+ MOZ_GUARDED_BY(mSubstitutionsLock);
+ nsCOMPtr<nsIIOService> mIOService;
+
+ // Returns a SubstitutingJARURI if |aUrl| maps to a |jar:| URI,
+ // otherwise will return |aURL|
+ nsresult ResolveJARURI(nsIURL* aURL, nsIURI** aResult);
+
+ // In general, we expect the principal of a document loaded from a
+ // substituting URI to be a content principal for that URI (rather than
+ // a principal for whatever is underneath). However, this only works if
+ // the protocol handler for the underlying URI doesn't set an explicit
+ // owner (which chrome:// does, for example). So we want to require that
+ // substituting URIs only map to other URIs of the same type, or to
+ // file:// and jar:// URIs.
+ //
+ // Enforcing this for ye olde resource:// URIs could carry compat risks, so
+ // we just try to enforce it on new protocols going forward.
+ bool mEnforceFileOrJar;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/SubstitutingURL.h b/netwerk/protocol/res/SubstitutingURL.h
new file mode 100644
index 0000000000..b9012f1665
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingURL.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SubstitutingURL_h
+#define SubstitutingURL_h
+
+#include "nsStandardURL.h"
+
+class nsIIOService;
+
+namespace mozilla {
+namespace net {
+
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
+// resolution
+class SubstitutingURL : public nsStandardURL {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ virtual nsStandardURL* StartClone() override;
+ [[nodiscard]] virtual nsresult EnsureFile() override;
+
+ private:
+ explicit SubstitutingURL() : nsStandardURL(true) {}
+ explicit SubstitutingURL(bool aSupportsFileURL) : nsStandardURL(true) {
+ MOZ_ASSERT(aSupportsFileURL);
+ }
+ virtual nsresult Clone(nsIURI** aURI) override {
+ return nsStandardURL::Clone(aURI);
+ }
+ virtual ~SubstitutingURL() = default;
+
+ public:
+ class Mutator : public TemplatedMutator<SubstitutingURL> {
+ NS_DECL_ISUPPORTS
+ public:
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ SubstitutingURL* Create() override { return new SubstitutingURL(); }
+ };
+
+ NS_IMETHOD Mutate(nsIURIMutator** aMutator) override {
+ RefPtr<SubstitutingURL::Mutator> mutator = new SubstitutingURL::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+ }
+
+ NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override;
+
+ friend BaseURIMutator<SubstitutingURL>;
+ friend TemplatedMutator<SubstitutingURL>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingURL_h */
diff --git a/netwerk/protocol/res/moz.build b/netwerk/protocol/res/moz.build
new file mode 100644
index 0000000000..63407eebd8
--- /dev/null
+++ b/netwerk/protocol/res/moz.build
@@ -0,0 +1,42 @@
+# -*- 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 += [
+ "nsIResProtocolHandler.idl",
+ "nsISubstitutingProtocolHandler.idl",
+]
+
+XPIDL_MODULE = "necko_res"
+
+EXPORTS.mozilla.net += [
+ "ExtensionProtocolHandler.h",
+ "PageThumbProtocolHandler.h",
+ "RemoteStreamGetter.h",
+ "SubstitutingJARURI.h",
+ "SubstitutingProtocolHandler.h",
+ "SubstitutingURL.h",
+]
+
+EXPORTS += [
+ "nsResProtocolHandler.h",
+]
+
+UNIFIED_SOURCES += [
+ "ExtensionProtocolHandler.cpp",
+ "nsResProtocolHandler.cpp",
+ "PageThumbProtocolHandler.cpp",
+ "RemoteStreamGetter.cpp",
+ "SubstitutingProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/xpcom/base",
+]
diff --git a/netwerk/protocol/res/nsIResProtocolHandler.idl b/netwerk/protocol/res/nsIResProtocolHandler.idl
new file mode 100644
index 0000000000..7046f2f1d4
--- /dev/null
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -0,0 +1,15 @@
+/* -*- 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 "nsISubstitutingProtocolHandler.idl"
+
+/**
+ * Protocol handler interface for the resource:// protocol
+ */
+[scriptable, uuid(241d34ac-9ed5-46d7-910c-7a9d914aa0c5)]
+interface nsIResProtocolHandler : nsISubstitutingProtocolHandler
+{
+ boolean allowContentToAccess(in nsIURI url);
+};
diff --git a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
new file mode 100644
index 0000000000..cf2e0d42ab
--- /dev/null
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -0,0 +1,62 @@
+/* -*- 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 "nsIProtocolHandler.idl"
+
+
+/**
+ * Protocol handler superinterface for a protocol which performs substitutions
+ * from URIs of its scheme to URIs of another scheme.
+ */
+[scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
+interface nsISubstitutingProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * Content script may access files in this package.
+ */
+ const short ALLOW_CONTENT_ACCESS = 1 << 0;
+ /**
+ * This substitution exposes nsIJARURI instead of a nsIFileURL. By default
+ * NewURI will always return a nsIFileURL even when the URL is jar:
+ */
+ const short RESOLVE_JAR_URI = 1 << 1;
+
+ /**
+ * Sets the substitution for the root key:
+ * resource://root/path ==> baseURI.resolve(path)
+ *
+ * A null baseURI removes the specified substitution.
+ *
+ * The root key will be converted to lower-case to conform to
+ * case-insensitive URI hostname matching behavior.
+ */
+ [must_use] void setSubstitution(in ACString root, in nsIURI baseURI);
+
+ /**
+ * Same as setSubstitution, but with specific flags.
+ */
+ [must_use] void setSubstitutionWithFlags(in ACString root, in nsIURI baseURI, in uint32_t flags);
+
+ /**
+ * Gets the substitution for the root key.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if none exists.
+ */
+ [must_use] nsIURI getSubstitution(in ACString root);
+
+ /**
+ * Returns TRUE if the substitution exists and FALSE otherwise.
+ */
+ [must_use] boolean hasSubstitution(in ACString root);
+
+ /**
+ * Utility function to resolve a substituted URI. A resolved URI is not
+ * guaranteed to reference a resource that exists (ie. opening a channel to
+ * the resolved URI may fail).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key.
+ */
+ [must_use] AUTF8String resolveURI(in nsIURI resURI);
+};
diff --git a/netwerk/protocol/res/nsResProtocolHandler.cpp b/netwerk/protocol/res/nsResProtocolHandler.cpp
new file mode 100644
index 0000000000..208baedf2b
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -0,0 +1,195 @@
+/* -*- 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/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Unused.h"
+
+#include "nsResProtocolHandler.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+
+#include "mozilla/Omnijar.h"
+
+using mozilla::LogLevel;
+using mozilla::Unused;
+using mozilla::dom::ContentParent;
+
+#define kAPP "app"
+#define kGRE "gre"
+#define kAndroid "android"
+
+mozilla::StaticRefPtr<nsResProtocolHandler> nsResProtocolHandler::sSingleton;
+
+already_AddRefed<nsResProtocolHandler> nsResProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ RefPtr<nsResProtocolHandler> handler = new nsResProtocolHandler();
+ if (NS_WARN_IF(NS_FAILED(handler->Init()))) {
+ return nullptr;
+ }
+ sSingleton = handler;
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+nsresult nsResProtocolHandler::Init() {
+ nsresult rv;
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, mAppURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, mGREURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mozilla::Omnijar::GetURIString always returns a string ending with /,
+ // and we want to remove it.
+ mGREURI.Truncate(mGREURI.Length() - 1);
+ if (mAppURI.Length()) {
+ mAppURI.Truncate(mAppURI.Length() - 1);
+ } else {
+ mAppURI = mGREURI;
+ }
+
+#ifdef ANDROID
+ rv = GetApkURI(mApkURI);
+#endif
+
+ // XXXbsmedberg Neil wants a resource://pchrome/ for the profile chrome dir...
+ // but once I finish multiple chrome registration I'm not sure that it is
+ // needed
+
+ // XXX dveditz: resource://pchrome/ defeats profile directory salting
+ // if web content can load it. Tread carefully.
+
+ return rv;
+}
+
+#ifdef ANDROID
+nsresult nsResProtocolHandler::GetApkURI(nsACString& aResult) {
+ nsCString::const_iterator start, iter;
+ mGREURI.BeginReading(start);
+ mGREURI.EndReading(iter);
+ nsCString::const_iterator start_iter = start;
+
+ // This is like jar:jar:file://path/to/apk/base.apk!/path/to/omni.ja!/
+ bool found = FindInReadable("!/"_ns, start_iter, iter);
+ NS_ENSURE_TRUE(found, NS_ERROR_UNEXPECTED);
+
+ // like jar:jar:file://path/to/apk/base.apk!/
+ const nsDependentCSubstring& withoutPath = Substring(start, iter);
+ NS_ENSURE_TRUE(withoutPath.Length() >= 4, NS_ERROR_UNEXPECTED);
+
+ // Let's make sure we're removing what we expect to remove
+ NS_ENSURE_TRUE(Substring(withoutPath, 0, 4).EqualsLiteral("jar:"),
+ NS_ERROR_UNEXPECTED);
+
+ // like jar:file://path/to/apk/base.apk!/
+ aResult = ToNewCString(Substring(withoutPath, 4));
+
+ // Remove the trailing /
+ NS_ENSURE_TRUE(aResult.Length() >= 1, NS_ERROR_UNEXPECTED);
+ aResult.Truncate(aResult.Length() - 1);
+ return NS_OK;
+}
+#endif
+
+//----------------------------------------------------------------------------
+// nsResProtocolHandler::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+
+NS_IMETHODIMP
+nsResProtocolHandler::AllowContentToAccess(nsIURI* aURI, bool* aResult) {
+ *aResult = false;
+
+ nsAutoCString host;
+ nsresult rv = aURI->GetAsciiHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags;
+ rv = GetSubstitutionFlags(host, &flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = flags & nsISubstitutingProtocolHandler::ALLOW_CONTENT_ACCESS;
+ return NS_OK;
+}
+
+nsresult nsResProtocolHandler::GetSubstitutionInternal(const nsACString& aRoot,
+ nsIURI** aResult,
+ uint32_t* aFlags) {
+ nsAutoCString uri;
+
+ if (!ResolveSpecialCases(aRoot, "/"_ns, "/"_ns, uri)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aRoot.Equals(kAndroid)) {
+ *aFlags = nsISubstitutingProtocolHandler::RESOLVE_JAR_URI;
+ } else {
+ *aFlags = 0; // No content access.
+ }
+ return NS_NewURI(aResult, uri);
+}
+
+bool nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ if (aHost.EqualsLiteral("") || aHost.EqualsLiteral(kAPP)) {
+ aResult.Assign(mAppURI);
+ } else if (aHost.Equals(kGRE)) {
+ aResult.Assign(mGREURI);
+#ifdef ANDROID
+ } else if (aHost.Equals(kAndroid)) {
+ aResult.Assign(mApkURI);
+#endif
+ } else {
+ return false;
+ }
+ aResult.Append(aPath);
+ return true;
+}
+
+nsresult nsResProtocolHandler::SetSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI) {
+ MOZ_ASSERT(!aRoot.EqualsLiteral(""));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAPP));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kGRE));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAndroid));
+ return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI);
+}
+
+nsresult nsResProtocolHandler::SetSubstitutionWithFlags(const nsACString& aRoot,
+ nsIURI* aBaseURI,
+ uint32_t aFlags) {
+ MOZ_ASSERT(!aRoot.EqualsLiteral(""));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAPP));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kGRE));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAndroid));
+ return SubstitutingProtocolHandler::SetSubstitutionWithFlags(aRoot, aBaseURI,
+ aFlags);
+}
+
+nsresult nsResProtocolHandler::HasSubstitution(const nsACString& aRoot,
+ bool* aResult) {
+ if (aRoot.EqualsLiteral(kAPP) || aRoot.EqualsLiteral(kGRE)
+#ifdef ANDROID
+ || aRoot.EqualsLiteral(kAndroid)
+#endif
+ ) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ return mozilla::net::SubstitutingProtocolHandler::HasSubstitution(aRoot,
+ aResult);
+}
diff --git a/netwerk/protocol/res/nsResProtocolHandler.h b/netwerk/protocol/res/nsResProtocolHandler.h
new file mode 100644
index 0000000000..50e790a53a
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -0,0 +1,78 @@
+/* -*- 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 nsResProtocolHandler_h___
+#define nsResProtocolHandler_h___
+
+#include "mozilla/net/SubstitutingProtocolHandler.h"
+
+#include "nsIResProtocolHandler.h"
+#include "nsInterfaceHashtable.h"
+#include "nsWeakReference.h"
+
+struct SubstitutionMapping;
+class nsResProtocolHandler final
+ : public nsIResProtocolHandler,
+ public mozilla::net::SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRESPROTOCOLHANDLER
+
+ static already_AddRefed<nsResProtocolHandler> GetSingleton();
+
+ NS_FORWARD_NSIPROTOCOLHANDLER(mozilla::net::SubstitutingProtocolHandler::)
+
+ nsResProtocolHandler()
+ : mozilla::net::SubstitutingProtocolHandler(
+ "resource",
+ /* aEnforceFileOrJar = */ false) {}
+
+ NS_IMETHOD SetSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI) override;
+ NS_IMETHOD SetSubstitutionWithFlags(const nsACString& aRoot, nsIURI* aBaseURI,
+ uint32_t aFlags) override;
+ NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override;
+
+ NS_IMETHOD GetSubstitution(const nsACString& aRoot,
+ nsIURI** aResult) override {
+ return mozilla::net::SubstitutingProtocolHandler::GetSubstitution(aRoot,
+ aResult);
+ }
+
+ NS_IMETHOD ResolveURI(nsIURI* aResURI, nsACString& aResult) override {
+ return mozilla::net::SubstitutingProtocolHandler::ResolveURI(aResURI,
+ aResult);
+ }
+
+ protected:
+ [[nodiscard]] nsresult GetSubstitutionInternal(const nsACString& aRoot,
+ nsIURI** aResult,
+ uint32_t* aFlags) override;
+ virtual ~nsResProtocolHandler() = default;
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ [[nodiscard]] virtual bool MustResolveJAR(const nsACString& aRoot) override {
+ return aRoot.EqualsLiteral("android");
+ }
+
+ private:
+ [[nodiscard]] nsresult Init();
+ static mozilla::StaticRefPtr<nsResProtocolHandler> sSingleton;
+
+ nsCString mAppURI;
+ nsCString mGREURI;
+#ifdef ANDROID
+ // Used for resource://android URIs
+ nsCString mApkURI;
+ nsresult GetApkURI(nsACString& aResult);
+#endif
+};
+
+#endif /* nsResProtocolHandler_h___ */
diff --git a/netwerk/protocol/viewsource/moz.build b/netwerk/protocol/viewsource/moz.build
new file mode 100644
index 0000000000..adf19615f0
--- /dev/null
+++ b/netwerk/protocol/viewsource/moz.build
@@ -0,0 +1,29 @@
+# -*- 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 += [
+ "nsIViewSourceChannel.idl",
+]
+
+XPIDL_MODULE = "necko_viewsource"
+
+UNIFIED_SOURCES += [
+ "nsViewSourceChannel.cpp",
+ "nsViewSourceHandler.cpp",
+]
+
+EXPORTS += [
+ "nsViewSourceHandler.h",
+]
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ # For nsHttpChannel.h
+ "/netwerk/protocol/http",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/protocol/viewsource/nsIViewSourceChannel.idl b/netwerk/protocol/viewsource/nsIViewSourceChannel.idl
new file mode 100644
index 0000000000..036142ba3d
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsIViewSourceChannel.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIChannel.idl"
+
+[uuid(3e9800f8-edb7-4c9a-9285-09b4f045b019)]
+interface nsIViewSourceChannel : nsIChannel
+{
+ /**
+ * The actual (MIME) content type of the data.
+ *
+ * nsIViewSourceChannel returns a content type of
+ * "application/x-view-source" if you ask it for the contentType
+ * attribute.
+ *
+ * However, callers interested in finding out or setting the
+ * actual content type can utilize this attribute.
+ */
+ [must_use] attribute ACString originalContentType;
+
+ /**
+ * Whether the channel was created to view the source of a srcdoc document.
+ */
+ [must_use] readonly attribute boolean isSrcdocChannel;
+
+ /**
+ * Set to indicate the base URI. If this channel is a srcdoc channel, it
+ * returns the base URI provided by the embedded channel. It is used to
+ * provide an indication of the base URI in circumstances where it isn't
+ * otherwise recoverable. Returns null when it isn't set and isn't a
+ * srcdoc channel.
+ */
+ [must_use] attribute nsIURI baseURI;
+
+ /**
+ * Get the inner channel wrapped by this nsIViewSourceChannel.
+ */
+ [notxpcom, nostdcall] nsIChannel getInnerChannel();
+};
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
new file mode 100644
index 0000000000..f2428a5744
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -0,0 +1,1220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceChannel.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "nsHttpChannel.h"
+#include "nsIExternalProtocolHandler.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIIOService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIReferrerInfo.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+NS_IMPL_ADDREF(nsViewSourceChannel)
+NS_IMPL_RELEASE(nsViewSourceChannel)
+/*
+ This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
+ non-nullness of mHttpChannel, mCachingChannel, and mUploadChannel.
+*/
+NS_INTERFACE_MAP_BEGIN(nsViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannel, mHttpChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIdentChannel, mHttpChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannelInternal,
+ mHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICachingChannel, mCachingChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICacheInfoChannel, mCacheInfoChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel, mUploadChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFormPOSTActionChannel, mPostChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIChildChannel, mChildChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIRequest, nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIChannel, nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIViewSourceChannel)
+NS_INTERFACE_MAP_END
+
+static nsresult WillUseExternalProtocolHandler(nsIIOService* aIOService,
+ const char* aScheme) {
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv =
+ aIOService->GetProtocolHandler(aScheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIExternalProtocolHandler> externalHandler =
+ do_QueryInterface(handler);
+ // We should not allow view-source to open any external app.
+ if (externalHandler) {
+ NS_WARNING(nsPrintfCString("blocking view-source:%s:", aScheme).get());
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsViewSourceChannel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo) {
+ mOriginalURI = uri;
+
+ nsAutoCString path;
+ nsresult rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIIOService> pService(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString scheme;
+ rv = pService->ExtractScheme(path, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // prevent viewing source of javascript URIs (see bug 204779)
+ if (scheme.EqualsLiteral("javascript")) {
+ NS_WARNING("blocking view-source:javascript:");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = WillUseExternalProtocolHandler(pService, scheme.get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> newChannelURI;
+ rv = pService->NewURI(path, nullptr, nullptr, getter_AddRefs(newChannelURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pService->NewChannelFromURIWithLoadInfo(newChannelURI, aLoadInfo,
+ getter_AddRefs(mChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsSrcdocChannel = false;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ UpdateChannelInterfaces();
+
+ return NS_OK;
+}
+
+nsresult nsViewSourceChannel::InitSrcdoc(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> inStreamURI;
+ // Need to strip view-source: from the URI. Hardcoded to
+ // about:srcdoc as this is the only permissible URI for srcdoc
+ // loads
+ rv = NS_NewURI(getter_AddRefs(inStreamURI), u"about:srcdoc"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), inStreamURI,
+ aSrcdoc, "text/html"_ns, aLoadInfo,
+ true);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOriginalURI = aURI;
+ mIsSrcdocChannel = true;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ UpdateChannelInterfaces();
+
+ rv = UpdateLoadInfoResultPrincipalURI();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(mChannel);
+ MOZ_ASSERT(isc);
+ isc->SetBaseURI(aBaseURI);
+ return NS_OK;
+}
+
+void nsViewSourceChannel::UpdateChannelInterfaces() {
+ mHttpChannel = do_QueryInterface(mChannel);
+ mHttpChannelInternal = do_QueryInterface(mChannel);
+ mCachingChannel = do_QueryInterface(mChannel);
+ mCacheInfoChannel = do_QueryInterface(mChannel);
+ mUploadChannel = do_QueryInterface(mChannel);
+ mPostChannel = do_QueryInterface(mChannel);
+ mChildChannel = do_QueryInterface(mChannel);
+}
+
+void nsViewSourceChannel::ReleaseListeners() {
+ mListener = nullptr;
+ mCallbacks = nullptr;
+}
+
+nsresult nsViewSourceChannel::UpdateLoadInfoResultPrincipalURI() {
+ nsresult rv;
+
+ MOZ_ASSERT(mChannel);
+
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
+ nsCOMPtr<nsIURI> channelResultPrincipalURI;
+ rv = channelLoadInfo->GetResultPrincipalURI(
+ getter_AddRefs(channelResultPrincipalURI));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!channelResultPrincipalURI) {
+ mChannel->GetOriginalURI(getter_AddRefs(channelResultPrincipalURI));
+ return NS_OK;
+ }
+
+ if (!channelResultPrincipalURI) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool alreadyViewSource;
+ if (NS_SUCCEEDED(channelResultPrincipalURI->SchemeIs("view-source",
+ &alreadyViewSource)) &&
+ alreadyViewSource) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> updatedResultPrincipalURI;
+ rv = BuildViewSourceURI(channelResultPrincipalURI,
+ getter_AddRefs(updatedResultPrincipalURI));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = channelLoadInfo->SetResultPrincipalURI(updatedResultPrincipalURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsViewSourceChannel::BuildViewSourceURI(nsIURI* aURI,
+ nsIURI** aResult) {
+ nsresult rv;
+
+ // protect ourselves against broken channel implementations
+ if (!aURI) {
+ NS_ERROR("no URI to build view-source uri!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_NewURI(aResult, "view-source:"_ns + spec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetName(nsACString& result) {
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ if (uri) {
+ return uri->GetSpec(result);
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTransferSize(uint64_t* aTransferSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestSize(uint64_t* aRequestSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsPending(bool* result) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->IsPending(result);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetStatus(nsresult* status) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetStatus(status);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetCanceledReason(
+ const nsACString& aReason) {
+ return nsIViewSourceChannel::SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::GetCanceledReason(nsACString& aReason) {
+ return nsIViewSourceChannel::GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return nsIViewSourceChannel::CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Cancel(nsresult status) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetCanceled(bool* aCanceled) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetCanceled(aCanceled);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Suspend(void) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Suspend();
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Resume(void) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Resume();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOriginalURI(nsIURI* aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetURI(nsIURI** aURI) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return BuildViewSourceURI(uri, aURI);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Open(nsIInputStream** aStream) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOpen(nsIStreamListener* aListener) {
+ // We can't ensure GetInitialSecurityCheckDone here
+
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ mListener = aListener;
+
+ /*
+ * We want to add ourselves to the loadgroup before opening
+ * mChannel, since we want to make sure we're in the loadgroup
+ * when mChannel finishes and fires OnStopRequest()
+ */
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->AddRequest(static_cast<nsIViewSourceChannel*>(this), nullptr);
+ }
+
+ nsresult rv = NS_OK;
+ rv = mChannel->AsyncOpen(this);
+
+ if (NS_FAILED(rv) && loadGroup) {
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>(this), nullptr,
+ rv);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // We do this here to make sure all notification callbacks changes have been
+ // made first before we inject this view-source channel.
+ mChannel->GetNotificationCallbacks(getter_AddRefs(mCallbacks));
+ mChannel->SetNotificationCallbacks(this);
+
+ MOZ_ASSERT(mCallbacks != this, "We have a cycle");
+
+ mOpened = true;
+ }
+
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ }
+
+ return rv;
+}
+
+/*
+ * Both the view source channel and mChannel are added to the
+ * loadgroup. There should never be more than one request in the
+ * loadgroup that has LOAD_DOCUMENT_URI set. The one that has this
+ * flag set is the request whose URI is used to refetch the document,
+ * so it better be the viewsource channel.
+ *
+ * Therefore, we need to make sure that
+ * 1) The load flags on mChannel _never_ include LOAD_DOCUMENT_URI
+ * 2) The load flags on |this| include LOAD_DOCUMENT_URI when it was
+ * set via SetLoadFlags (mIsDocument keeps track of this flag).
+ */
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadFlags(uint32_t* aLoadFlags) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsresult rv = mChannel->GetLoadFlags(aLoadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // This should actually be just LOAD_DOCUMENT_URI but the win32 compiler
+ // fails to deal due to amiguous inheritance. nsIChannel::LOAD_DOCUMENT_URI
+ // also fails; the Win32 compiler thinks that's supposed to be a method.
+ if (mIsDocument) *aLoadFlags |= ::nsIChannel::LOAD_DOCUMENT_URI;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadFlags(uint32_t aLoadFlags) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ // "View source" always wants the currently cached content.
+ // We also want to have _this_ channel, not mChannel to be the
+ // 'document' channel in the loadgroup.
+
+ // These should actually be just LOAD_FROM_CACHE and LOAD_DOCUMENT_URI but
+ // the win32 compiler fails to deal due to amiguous inheritance.
+ // nsIChannel::LOAD_DOCUMENT_URI/nsIRequest::LOAD_FROM_CACHE also fails; the
+ // Win32 compiler thinks that's supposed to be a method.
+ mIsDocument = (aLoadFlags & ::nsIChannel::LOAD_DOCUMENT_URI) != 0;
+
+ nsresult rv =
+ mChannel->SetLoadFlags((aLoadFlags | ::nsIRequest::LOAD_FROM_CACHE) &
+ ~::nsIChannel::LOAD_DOCUMENT_URI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mHttpChannel) {
+ rv = mHttpChannel->SetIsMainDocumentChannel(
+ aLoadFlags & ::nsIChannel::LOAD_DOCUMENT_URI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return nsIViewSourceChannel::GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return nsIViewSourceChannel::SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentType(nsACString& aContentType) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ aContentType.Truncate();
+
+ if (mContentType.IsEmpty()) {
+ // Get the current content type
+ nsresult rv;
+ nsAutoCString contentType;
+ rv = mChannel->GetContentType(contentType);
+ if (NS_FAILED(rv)) return rv;
+
+ // If we don't know our type, just say so. The unknown
+ // content decoder will then kick in automatically, and it
+ // will call our SetOriginalContentType method instead of our
+ // SetContentType method to set the type it determines.
+ if (!contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) &&
+ !contentType.IsEmpty()) {
+ contentType = VIEWSOURCE_CONTENT_TYPE;
+ }
+
+ mContentType = contentType;
+ }
+
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentType(const nsACString& aContentType) {
+ // Our GetContentType() currently returns VIEWSOURCE_CONTENT_TYPE
+ //
+ // However, during the parsing phase the parser calls our
+ // channel's GetContentType(). Returning the string above trips up
+ // the parser. In order to avoid messy changes and not to have the
+ // parser depend on nsIViewSourceChannel Vidur proposed the
+ // following solution:
+ //
+ // The ViewSourceChannel initially returns a content type of
+ // VIEWSOURCE_CONTENT_TYPE. Based on this type decisions to
+ // create a viewer for doing a view source are made. After the
+ // viewer is created, nsLayoutDLF::CreateInstance() calls this
+ // SetContentType() with the original content type. When it's
+ // time for the parser to find out the content type it will call
+ // our channel's GetContentType() and it will get the original
+ // content type, such as, text/html and everything is kosher from
+ // then on.
+
+ if (!mOpened) {
+ // We do not take hints
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentCharset(nsACString& aContentCharset) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentCharset(aContentCharset);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentCharset(const nsACString& aContentCharset) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetContentCharset(aContentCharset);
+}
+
+// We don't forward these methods becacuse content-disposition isn't whitelisted
+// (see GetResponseHeader/VisitResponseHeaders).
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentLength(int64_t* aContentLength) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentLength(int64_t aContentLength) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOwner(nsISupports** aOwner) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOwner(nsISupports* aOwner) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ return mChannel->SetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsDocument(bool* aIsDocument) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetIsDocument(aIsDocument);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetSecurityInfo(aSecurityInfo);
+}
+
+// nsIViewSourceChannel methods
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalContentType(nsACString& aContentType) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentType(aContentType);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOriginalContentType(const nsACString& aContentType) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ // clear our cached content-type value
+ mContentType.Truncate();
+
+ return mChannel->SetContentType(aContentType);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsSrcdocChannel(bool* aIsSrcdocChannel) {
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetBaseURI(nsIURI** aBaseURI) {
+ if (mIsSrcdocChannel) {
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(mChannel);
+ if (isc) {
+ return isc->GetBaseURI(aBaseURI);
+ }
+ }
+ *aBaseURI = do_AddRef(mBaseURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetBaseURI(nsIURI* aBaseURI) {
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+nsIChannel* nsViewSourceChannel::GetInnerChannel() { return mChannel; }
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnStartRequest(nsIRequest* aRequest) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ // The channel may have gotten redirected... Time to update our info
+ mChannel = do_QueryInterface(aRequest);
+ UpdateChannelInterfaces();
+
+ nsresult rv = UpdateLoadInfoResultPrincipalURI();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ return mListener->OnStartRequest(static_cast<nsIViewSourceChannel*>(this));
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ if (mChannel) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>(this),
+ nullptr, aStatus);
+ }
+ }
+
+ nsresult rv = mListener->OnStopRequest(
+ static_cast<nsIViewSourceChannel*>(this), aStatus);
+
+ ReleaseListeners();
+
+ return rv;
+}
+
+// nsIStreamListener methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aSourceOffset, uint32_t aLength) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ return mListener->OnDataAvailable(static_cast<nsIViewSourceChannel*>(this),
+ aInputStream, aSourceOffset, aLength);
+}
+
+// nsIHttpChannel methods
+
+// We want to forward most of nsIHttpChannel over to mHttpChannel, but we want
+// to override GetRequestHeader and VisitHeaders. The reason is that we don't
+// want various headers like Link: and Refresh: applying to view-source.
+NS_IMETHODIMP
+nsViewSourceChannel::GetChannelId(uint64_t* aChannelId) {
+ NS_ENSURE_ARG_POINTER(aChannelId);
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetChannelId(aChannelId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetChannelId(uint64_t aChannelId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetChannelId(aChannelId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetBrowserId(uint64_t* aId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetBrowserId(aId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetBrowserId(uint64_t aId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetBrowserId(aId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestMethod(nsACString& aRequestMethod) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestMethod(aRequestMethod);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestMethod(const nsACString& aRequestMethod) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRequestMethod(aRequestMethod);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetReferrerInfoWithoutClone(
+ nsIReferrerInfo* aReferrerInfo) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetReferrerInfoWithoutClone(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue) {
+ aValue.Truncate();
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestHeader(aHeader, aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRequestHeader(aHeader, aValue, aMerge);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetNewReferrerInfo(
+ const nsACString& aUrl, nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetNewReferrerInfo(aUrl, aPolicy, aSendReferrer);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetEmptyRequestHeader(aHeader);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->VisitRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitNonDefaultRequestHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->VisitNonDefaultRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->ShouldStripRequestBodyHeader(aMethod, aResult);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetAllowSTS(bool* aAllowSTS) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetAllowSTS(bool aAllowSTS) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRedirectionLimit(uint32_t* aRedirectionLimit) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRedirectionLimit(uint32_t aRedirectionLimit) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseStatus(uint32_t* aResponseStatus) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetResponseStatus(aResponseStatus);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseStatusText(nsACString& aResponseStatusText) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetResponseStatusText(aResponseStatusText);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestSucceeded(bool* aRequestSucceeded) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestSucceeded(aRequestSucceeded);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseHeader(const nsACString& aHeader,
+ nsACString& aValue) {
+ aValue.Truncate();
+ if (!mHttpChannel) return NS_ERROR_NULL_POINTER;
+
+ if (!aHeader.Equals("Content-Type"_ns, nsCaseInsensitiveCStringComparator) &&
+ !aHeader.Equals("Content-Security-Policy"_ns,
+ nsCaseInsensitiveCStringComparator) &&
+ !aHeader.Equals("Content-Security-Policy-Report-Only"_ns,
+ nsCaseInsensitiveCStringComparator) &&
+ !aHeader.Equals("X-Frame-Options"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ // We simulate the NS_ERROR_NOT_AVAILABLE error which is produced by
+ // GetResponseHeader via nsHttpHeaderArray::GetHeader when the entry is
+ // not present, such that it appears as though no headers except for the
+ // whitelisted ones were set on this channel.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mHttpChannel->GetResponseHeader(aHeader, aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetResponseHeader(header, value, merge);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ if (!mHttpChannel) return NS_ERROR_NULL_POINTER;
+
+ constexpr auto contentTypeStr = "Content-Type"_ns;
+ nsAutoCString contentType;
+ nsresult rv = mHttpChannel->GetResponseHeader(contentTypeStr, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ return aVisitor->VisitHeader(contentTypeStr, contentType);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ nsAutoCString value;
+ nsresult rv = GetResponseHeader(aHeader, value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return aVisitor->VisitHeader(aHeader, value);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitOriginalResponseHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ return VisitResponseHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsNoStoreResponse(bool* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->IsNoStoreResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsNoCacheResponse(bool* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->IsNoCacheResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsPrivateResponse(bool* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->IsPrivateResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::RedirectTo(nsIURI* uri) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER : mHttpChannel->RedirectTo(uri);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::UpgradeToSecure() {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->UpgradeToSecure();
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestContextID(uint64_t* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestContextID(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestContextID(uint64_t rcid) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRequestContextID(rcid);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsMainDocumentChannel(bool* aValue) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetIsMainDocumentChannel(bool aValue) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetClassicScriptHintCharset(
+ aClassicScriptHintCharset);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetClassicScriptHintCharset(
+ aClassicScriptHintCharset);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetDocumentCharacterSet(aDocumentCharacterSet);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::GetDocumentCharacterSet(
+ nsAString& aDocumentCharacterSet) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetDocumentCharacterSet(aDocumentCharacterSet);
+}
+// Have to manually forward SetCorsPreflightParameters since it's [notxpcom]
+void nsViewSourceChannel::SetCorsPreflightParameters(
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {
+ mHttpChannelInternal->SetCorsPreflightParameters(
+ aUnsafeHeaders, aShouldStripRequestBodyHeader);
+}
+
+void nsViewSourceChannel::SetAltDataForChild(bool aIsForChild) {
+ mHttpChannelInternal->SetAltDataForChild(aIsForChild);
+}
+
+void nsViewSourceChannel::DisableAltDataCache() {
+ mHttpChannelInternal->DisableAltDataCache();
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ if (!mHttpChannel) {
+ NS_WARNING(
+ "nsViewSourceChannel::LogBlockedCORSRequest mHttpChannel is null");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mHttpChannel->LogBlockedCORSRequest(aMessage, aCategory, aIsWarning);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ if (!mHttpChannel) {
+ NS_WARNING("nsViewSourceChannel::LogMimeTypeMismatch mHttpChannel is null");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mHttpChannel->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
+ aContentType);
+}
+
+// FIXME: Should this forward to mHttpChannel? This was previously handled by a
+// default empty implementation.
+void nsViewSourceChannel::SetSource(
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource) {}
+
+const nsTArray<mozilla::net::PreferredAlternativeDataTypeParams>&
+nsViewSourceChannel::PreferredAlternativeDataTypes() {
+ if (mCacheInfoChannel) {
+ return mCacheInfoChannel->PreferredAlternativeDataTypes();
+ }
+ return mEmptyArray;
+}
+
+void nsViewSourceChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+ if (mHttpChannelInternal) {
+ mHttpChannelInternal->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+ }
+}
+
+// FIXME: Should this forward to mHttpChannelInternal? This was previously
+// handled by a default empty implementation.
+void nsViewSourceChannel::SetConnectionInfo(
+ mozilla::net::nsHttpConnectionInfo* aInfo) {}
+
+// nsIChildChannel methods
+
+NS_IMETHODIMP
+nsViewSourceChannel::ConnectParent(uint32_t aRegistarId) {
+ NS_ENSURE_TRUE(mChildChannel, NS_ERROR_NULL_POINTER);
+
+ return mChildChannel->ConnectParent(aRegistarId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ NS_ENSURE_TRUE(mChildChannel, NS_ERROR_NULL_POINTER);
+
+ mListener = aListener;
+
+ /*
+ * We want to add ourselves to the loadgroup before opening
+ * mChannel, since we want to make sure we're in the loadgroup
+ * when mChannel finishes and fires OnStopRequest()
+ */
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->AddRequest(static_cast<nsIViewSourceChannel*>(this), nullptr);
+ }
+
+ nsresult rv = NS_OK;
+ rv = mChildChannel->CompleteRedirectSetup(this);
+
+ if (NS_FAILED(rv) && loadGroup) {
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>(this), nullptr,
+ rv);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mOpened = true;
+ }
+
+ return rv;
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ nsresult rv;
+
+ // We want consumers of the new inner channel be able to recognize it's
+ // actually used for view-source. Hence we modify its original URI here.
+ // This will not affect security checks because those have already been
+ // synchronously done before our event sink is called. Note that result
+ // principal URI is still modified at the OnStartRequest notification.
+ nsCOMPtr<nsIURI> newChannelOrigURI;
+ rv = newChannel->GetOriginalURI(getter_AddRefs(newChannelOrigURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme;
+ rv = newChannelOrigURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIIOService> ioService(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WillUseExternalProtocolHandler(ioService, scheme.get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> newChannelUpdatedOrigURI;
+ rv = BuildViewSourceURI(newChannelOrigURI,
+ getter_AddRefs(newChannelUpdatedOrigURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newChannel->SetOriginalURI(newChannelUpdatedOrigURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannelEventSink> sink(do_QueryInterface(mCallbacks));
+ if (sink) {
+ return sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
+ callback);
+ }
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ nsCOMPtr<nsIChannelEventSink> self(this);
+ self.forget(aResult);
+ return NS_OK;
+ }
+
+ if (mCallbacks) {
+ return mCallbacks->GetInterface(aIID, aResult);
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.h b/netwerk/protocol/viewsource/nsViewSourceChannel.h
new file mode 100644
index 0000000000..87c490f6b7
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.h
@@ -0,0 +1,98 @@
+/* -*- 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 nsViewSourceChannel_h___
+#define nsViewSourceChannel_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsCOMPtr.h"
+#include "nsICachingChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIViewSourceChannel.h"
+#include "nsIChildChannel.h"
+#include "nsString.h"
+
+class nsViewSourceChannel final : public nsIViewSourceChannel,
+ public nsIStreamListener,
+ public nsIHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsICachingChannel,
+ public nsIFormPOSTActionChannel,
+ public nsIChildChannel,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIIDENTCHANNEL
+ NS_DECL_NSIVIEWSOURCECHANNEL
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIHTTPCHANNEL
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_FORWARD_SAFE_NSICACHEINFOCHANNEL(mCacheInfoChannel)
+ NS_FORWARD_SAFE_NSICACHINGCHANNEL(mCachingChannel)
+ NS_FORWARD_SAFE_NSIUPLOADCHANNEL(mUploadChannel)
+ NS_FORWARD_SAFE_NSIFORMPOSTACTIONCHANNEL(mPostChannel)
+ NS_FORWARD_SAFE_NSIHTTPCHANNELINTERNAL(mHttpChannelInternal)
+
+ // nsViewSourceChannel methods:
+ nsViewSourceChannel() = default;
+
+ [[nodiscard]] nsresult Init(nsIURI* uri, nsILoadInfo* aLoadInfo);
+
+ [[nodiscard]] nsresult InitSrcdoc(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo);
+
+ // Updates or sets the result principal URI of the underlying channel's
+ // loadinfo to be prefixed with the "view-source:" schema as:
+ //
+ // mChannel.loadInfo.resultPrincipalURI = "view-source:" +
+ // (mChannel.loadInfo.resultPrincipalURI | mChannel.orignalURI);
+ nsresult UpdateLoadInfoResultPrincipalURI();
+
+ protected:
+ ~nsViewSourceChannel() = default;
+ void ReleaseListeners();
+
+ nsTArray<mozilla::net::PreferredAlternativeDataTypeParams> mEmptyArray;
+
+ // Clones aURI and prefixes it with "view-source:" schema,
+ nsresult BuildViewSourceURI(nsIURI* aURI, nsIURI** aResult);
+
+ // Called to update the forwarding channel members after the `mChannel` field
+ // has been changed to reflect the new inner channel.
+ void UpdateChannelInterfaces();
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ nsCOMPtr<nsIHttpChannelInternal> mHttpChannelInternal;
+ nsCOMPtr<nsICachingChannel> mCachingChannel;
+ nsCOMPtr<nsICacheInfoChannel> mCacheInfoChannel;
+ nsCOMPtr<nsIUploadChannel> mUploadChannel;
+ nsCOMPtr<nsIFormPOSTActionChannel> mPostChannel;
+ nsCOMPtr<nsIChildChannel> mChildChannel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCString mContentType;
+ bool mIsDocument{false}; // keeps track of the LOAD_DOCUMENT_URI flag
+ bool mOpened{false};
+ bool mIsSrcdocChannel{false};
+};
+
+#endif /* nsViewSourceChannel_h___ */
diff --git a/netwerk/protocol/viewsource/nsViewSourceHandler.cpp b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
new file mode 100644
index 0000000000..f7b440d4d7
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceHandler.h"
+#include "nsViewSourceChannel.h"
+#include "nsNetUtil.h"
+#include "nsSimpleNestedURI.h"
+
+#define VIEW_SOURCE "view-source"
+
+#define DEFAULT_FLAGS \
+ (URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD | URI_NON_PERSISTABLE)
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsViewSourceHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral(VIEW_SOURCE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* result) {
+ *result = DEFAULT_FLAGS;
+ nsCOMPtr<nsINestedURI> nestedURI(do_QueryInterface(aURI));
+ if (!nestedURI) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> innerURI;
+ nestedURI->GetInnerURI(getter_AddRefs(innerURI));
+ nsCOMPtr<nsINetUtil> netUtil = do_GetNetUtil();
+ bool isLoadable = false;
+ nsresult rv =
+ netUtil->ProtocolHasFlags(innerURI, URI_LOADABLE_BY_ANYONE, &isLoadable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isLoadable) {
+ *result |= URI_LOADABLE_BY_EXTENSIONS;
+ }
+ return NS_OK;
+}
+
+// static
+nsresult nsViewSourceHandler::CreateNewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** aResult) {
+ *aResult = nullptr;
+
+ // Extract inner URL and normalize to ASCII. This is done to properly
+ // support IDN in cases like "view-source:http://www.szalagavató.hu/"
+
+ int32_t colon = aSpec.FindChar(':');
+ if (colon == kNotFound) return NS_ERROR_MALFORMED_URI;
+
+ nsCOMPtr<nsIURI> innerURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(innerURI), Substring(aSpec, colon + 1),
+ aCharset, aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString asciiSpec;
+ rv = innerURI->GetAsciiSpec(asciiSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ // put back our scheme and construct a simple-uri wrapper
+
+ asciiSpec.Insert(VIEW_SOURCE ":", 0);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_MutateURI(new nsSimpleNestedURI::Mutator())
+ .Apply(&nsINestedURIMutator::Init, innerURI)
+ .SetSpec(asciiSpec)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uri.swap(*aResult);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ RefPtr<nsViewSourceChannel> channel = new nsViewSourceChannel();
+
+ nsresult rv = channel->Init(uri, aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *result = channel.forget().downcast<nsIViewSourceChannel>().take();
+ return NS_OK;
+}
+
+nsresult nsViewSourceHandler::NewSrcdocChannel(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ RefPtr<nsViewSourceChannel> channel = new nsViewSourceChannel();
+
+ nsresult rv = channel->InitSrcdoc(aURI, aBaseURI, aSrcdoc, aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *outChannel = static_cast<nsIViewSourceChannel*>(channel.forget().take());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+nsViewSourceHandler::nsViewSourceHandler() { gInstance = this; }
+
+nsViewSourceHandler::~nsViewSourceHandler() { gInstance = nullptr; }
+
+nsViewSourceHandler* nsViewSourceHandler::gInstance = nullptr;
+
+nsViewSourceHandler* nsViewSourceHandler::GetInstance() { return gInstance; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/viewsource/nsViewSourceHandler.h b/netwerk/protocol/viewsource/nsViewSourceHandler.h
new file mode 100644
index 0000000000..b9e9f3ab7c
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.h
@@ -0,0 +1,48 @@
+/* -*- 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 nsViewSourceHandler_h___
+#define nsViewSourceHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsNetUtil.h"
+#include "mozilla/Attributes.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsViewSourceHandler final : public nsIProtocolHandlerWithDynamicFlags,
+ public nsIProtocolHandler {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+
+ nsViewSourceHandler();
+
+ // Creates a new nsViewSourceChannel to view the source of an about:srcdoc
+ // URI with contents specified by srcdoc.
+ [[nodiscard]] nsresult NewSrcdocChannel(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel);
+
+ static nsViewSourceHandler* GetInstance();
+
+ static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** aResult);
+
+ private:
+ ~nsViewSourceHandler();
+
+ static nsViewSourceHandler* gInstance;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* !defined( nsViewSourceHandler_h___ ) */
diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.cpp b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
new file mode 100644
index 0000000000..6e9589afb2
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
@@ -0,0 +1,378 @@
+/* -*- 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 "WebSocketLog.h"
+#include "BaseWebSocketChannel.h"
+#include "MainThreadUtils.h"
+#include "nsILoadGroup.h"
+#include "nsINode.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsProxyRelease.h"
+#include "nsStandardURL.h"
+#include "LoadInfo.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsITransportProvider.h"
+
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule webSocketLog("nsWebSocket");
+static uint64_t gNextWebSocketID = 0;
+
+// We use only 53 bits for the WebSocket serial ID so that it can be converted
+// to and from a JS value without loss of precision. The upper bits of the
+// WebSocket serial ID hold the process ID. The lower bits identify the
+// WebSocket.
+static const uint64_t kWebSocketIDTotalBits = 53;
+static const uint64_t kWebSocketIDProcessBits = 22;
+static const uint64_t kWebSocketIDWebSocketBits =
+ kWebSocketIDTotalBits - kWebSocketIDProcessBits;
+
+BaseWebSocketChannel::BaseWebSocketChannel()
+ : mWasOpened(0),
+ mClientSetPingInterval(0),
+ mClientSetPingTimeout(0),
+ mEncrypted(false),
+ mPingForced(false),
+ mIsServerSide(false),
+ mPingInterval(0),
+ mPingResponseTimeout(10000),
+ mHttpChannelId(0) {
+ // Generation of a unique serial ID.
+ uint64_t processID = 0;
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ processID = cc->GetID();
+ }
+
+ uint64_t processBits =
+ processID & ((uint64_t(1) << kWebSocketIDProcessBits) - 1);
+
+ // Make sure no actual webSocket ends up with mWebSocketID == 0 but less then
+ // what the kWebSocketIDProcessBits allows.
+ if (++gNextWebSocketID >= (uint64_t(1) << kWebSocketIDWebSocketBits)) {
+ gNextWebSocketID = 1;
+ }
+
+ uint64_t webSocketBits =
+ gNextWebSocketID & ((uint64_t(1) << kWebSocketIDWebSocketBits) - 1);
+ mSerial = (processBits << kWebSocketIDWebSocketBits) | webSocketBits;
+}
+
+BaseWebSocketChannel::~BaseWebSocketChannel() {
+ NS_ReleaseOnMainThread("BaseWebSocketChannel::mLoadGroup",
+ mLoadGroup.forget());
+ NS_ReleaseOnMainThread("BaseWebSocketChannel::mLoadInfo", mLoadInfo.forget());
+ nsCOMPtr<nsISerialEventTarget> target;
+ {
+ auto lock = mTargetThread.Lock();
+ target.swap(*lock);
+ }
+ NS_ReleaseOnMainThread("BaseWebSocketChannel::mTargetThread",
+ target.forget());
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIWebSocketChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ LOG(("BaseWebSocketChannel::GetOriginalURI() %p\n", this));
+
+ if (!mOriginalURI) return NS_ERROR_NOT_INITIALIZED;
+ *aOriginalURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetURI(nsIURI** aURI) {
+ LOG(("BaseWebSocketChannel::GetURI() %p\n", this));
+
+ if (!mOriginalURI) return NS_ERROR_NOT_INITIALIZED;
+ if (mURI) {
+ *aURI = do_AddRef(mURI).take();
+ } else {
+ *aURI = do_AddRef(mOriginalURI).take();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ LOG(("BaseWebSocketChannel::GetNotificationCallbacks() %p\n", this));
+ *aNotificationCallbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ LOG(("BaseWebSocketChannel::SetNotificationCallbacks() %p\n", this));
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ LOG(("BaseWebSocketChannel::GetLoadGroup() %p\n", this));
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ LOG(("BaseWebSocketChannel::SetLoadGroup() %p\n", this));
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ *aLoadInfo = do_AddRef(mLoadInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetExtensions(nsACString& aExtensions) {
+ LOG(("BaseWebSocketChannel::GetExtensions() %p\n", this));
+ aExtensions = mNegotiatedExtensions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetProtocol(nsACString& aProtocol) {
+ LOG(("BaseWebSocketChannel::GetProtocol() %p\n", this));
+ aProtocol = mProtocol;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetProtocol(const nsACString& aProtocol) {
+ LOG(("BaseWebSocketChannel::SetProtocol() %p\n", this));
+ mProtocol = aProtocol; /* the sub protocol */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingInterval(uint32_t* aSeconds) {
+ // stored in ms but should only have second resolution
+ MOZ_ASSERT(!(mPingInterval % 1000));
+
+ *aSeconds = mPingInterval / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingInterval(uint32_t aSeconds) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mWasOpened) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ mPingInterval = aSeconds * 1000;
+ mClientSetPingInterval = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingTimeout(uint32_t* aSeconds) {
+ // stored in ms but should only have second resolution
+ MOZ_ASSERT(!(mPingResponseTimeout % 1000));
+
+ *aSeconds = mPingResponseTimeout / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingTimeout(uint32_t aSeconds) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mWasOpened) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ mPingResponseTimeout = aSeconds * 1000;
+ mClientSetPingTimeout = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::InitLoadInfoNative(
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsICookieJarSettings* aCookieJarSettings, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags) {
+ mLoadInfo = new LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType, Maybe<mozilla::dom::ClientInfo>(),
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(), aSandboxFlags);
+ if (aCookieJarSettings) {
+ mLoadInfo->SetCookieJarSettings(aCookieJarSettings);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::InitLoadInfo(nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType) {
+ return InitLoadInfoNative(aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, nullptr, aSecurityFlags,
+ aContentPolicyType, 0);
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetSerial(uint32_t* aSerial) {
+ if (!aSerial) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aSerial = mSerial;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetSerial(uint32_t aSerial) {
+ mSerial = aSerial;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetServerParameters(
+ nsITransportProvider* aProvider, const nsACString& aNegotiatedExtensions) {
+ MOZ_ASSERT(aProvider);
+ mServerTransportProvider = aProvider;
+ mNegotiatedExtensions = aNegotiatedExtensions;
+ mIsServerSide = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetHttpChannelId(uint64_t* aHttpChannelId) {
+ *aHttpChannelId = mHttpChannelId;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetScheme(nsACString& aScheme) {
+ LOG(("BaseWebSocketChannel::GetScheme() %p\n", this));
+
+ if (mEncrypted) {
+ aScheme.AssignLiteral("wss");
+ } else {
+ aScheme.AssignLiteral("ws");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel) {
+ LOG(("BaseWebSocketChannel::NewChannel() %p\n", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ LOG(("BaseWebSocketChannel::AllowPort() %p\n", this));
+
+ // do not override any blacklisted ports
+ *_retval = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::RetargetDeliveryTo(nsISerialEventTarget* aTargetThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTargetThread);
+ MOZ_ASSERT(!mWasOpened, "Should not be called after AsyncOpen!");
+ MOZ_ASSERT(aTargetThread);
+
+ auto lock = mTargetThread.Lock();
+ MOZ_ASSERT(!lock.ref(),
+ "Delivery target should be set once, before AsyncOpen");
+ lock.ref() = aTargetThread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetDeliveryTarget(nsISerialEventTarget** aTargetThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsISerialEventTarget> target = GetTargetThread();
+ if (!target) {
+ target = GetCurrentSerialEventTarget();
+ }
+ target.forget(aTargetThread);
+ return NS_OK;
+}
+
+already_AddRefed<nsISerialEventTarget> BaseWebSocketChannel::GetTargetThread() {
+ nsCOMPtr<nsISerialEventTarget> target;
+ auto lock = mTargetThread.Lock();
+ target = *lock;
+ return target.forget();
+}
+
+bool BaseWebSocketChannel::IsOnTargetThread() {
+ nsCOMPtr<nsISerialEventTarget> target = GetTargetThread();
+ if (!target) {
+ MOZ_ASSERT(false);
+ return false;
+ }
+
+ bool isOnTargetThread = false;
+ nsresult rv = target->IsOnCurrentThread(&isOnTargetThread);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_FAILED(rv) ? false : isOnTargetThread;
+}
+
+BaseWebSocketChannel::ListenerAndContextContainer::ListenerAndContextContainer(
+ nsIWebSocketListener* aListener, nsISupports* aContext)
+ : mListener(aListener), mContext(aContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListener);
+}
+
+BaseWebSocketChannel::ListenerAndContextContainer::
+ ~ListenerAndContextContainer() {
+ MOZ_ASSERT(mListener);
+
+ NS_ReleaseOnMainThread(
+ "BaseWebSocketChannel::ListenerAndContextContainer::mListener",
+ mListener.forget());
+ NS_ReleaseOnMainThread(
+ "BaseWebSocketChannel::ListenerAndContextContainer::mContext",
+ mContext.forget());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.h b/netwerk/protocol/websocket/BaseWebSocketChannel.h
new file mode 100644
index 0000000000..ee05e5bf4c
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -0,0 +1,137 @@
+/* -*- 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_BaseWebSocketChannel_h
+#define mozilla_net_BaseWebSocketChannel_h
+
+#include "mozilla/DataMutex.h"
+#include "nsIWebSocketChannel.h"
+#include "nsIWebSocketListener.h"
+#include "nsIProtocolHandler.h"
+#include "nsIThread.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+const static int32_t kDefaultWSPort = 80;
+const static int32_t kDefaultWSSPort = 443;
+
+class BaseWebSocketChannel : public nsIWebSocketChannel,
+ public nsIProtocolHandler,
+ public nsIThreadRetargetableRequest {
+ public:
+ BaseWebSocketChannel();
+
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+
+ NS_IMETHOD QueryInterface(const nsIID& uuid, void** result) override = 0;
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override = 0;
+
+ // Partial implementation of nsIWebSocketChannel
+ //
+ NS_IMETHOD GetOriginalURI(nsIURI** aOriginalURI) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) override;
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override;
+ NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override;
+ NS_IMETHOD GetExtensions(nsACString& aExtensions) override;
+ NS_IMETHOD GetProtocol(nsACString& aProtocol) override;
+ NS_IMETHOD SetProtocol(const nsACString& aProtocol) override;
+ NS_IMETHOD GetPingInterval(uint32_t* aSeconds) override;
+ NS_IMETHOD SetPingInterval(uint32_t aSeconds) override;
+ NS_IMETHOD GetPingTimeout(uint32_t* aSeconds) override;
+ NS_IMETHOD SetPingTimeout(uint32_t aSeconds) override;
+ NS_IMETHOD InitLoadInfoNative(nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ uint32_t aSandboxFlags) override;
+ NS_IMETHOD InitLoadInfo(nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType) override;
+ NS_IMETHOD GetSerial(uint32_t* aSerial) override;
+ NS_IMETHOD SetSerial(uint32_t aSerial) override;
+ NS_IMETHOD SetServerParameters(
+ nsITransportProvider* aProvider,
+ const nsACString& aNegotiatedExtensions) override;
+ NS_IMETHOD GetHttpChannelId(uint64_t* aHttpChannelId) override;
+
+ // Off main thread URI access.
+ virtual void GetEffectiveURL(nsAString& aEffectiveURL) const = 0;
+ virtual bool IsEncrypted() const = 0;
+
+ already_AddRefed<nsISerialEventTarget> GetTargetThread();
+ bool IsOnTargetThread();
+
+ class ListenerAndContextContainer final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ListenerAndContextContainer)
+
+ ListenerAndContextContainer(nsIWebSocketListener* aListener,
+ nsISupports* aContext);
+
+ nsCOMPtr<nsIWebSocketListener> mListener;
+ nsCOMPtr<nsISupports> mContext;
+
+ private:
+ ~ListenerAndContextContainer();
+ };
+
+ protected:
+ virtual ~BaseWebSocketChannel();
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<ListenerAndContextContainer> mListenerMT;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsITransportProvider> mServerTransportProvider;
+
+ // Used to ensure atomicity of mTargetThread.
+ // Set before AsyncOpen via RetargetDeliveryTo or in AsyncOpen, never changed
+ // after AsyncOpen
+ DataMutex<nsCOMPtr<nsISerialEventTarget>> mTargetThread{
+ "BaseWebSocketChannel::EventTargetMutex"};
+
+ nsCString mProtocol;
+ nsCString mOrigin;
+
+ nsCString mNegotiatedExtensions;
+
+ uint32_t mWasOpened : 1;
+ uint32_t mClientSetPingInterval : 1;
+ uint32_t mClientSetPingTimeout : 1;
+
+ Atomic<bool> mEncrypted;
+ bool mPingForced;
+ bool mIsServerSide;
+
+ Atomic<uint32_t> mPingInterval; /* milliseconds */
+ uint32_t mPingResponseTimeout; /* milliseconds */
+
+ uint32_t mSerial;
+
+ uint64_t mHttpChannelId;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BaseWebSocketChannel_h
diff --git a/netwerk/protocol/websocket/IPCTransportProvider.cpp b/netwerk/protocol/websocket/IPCTransportProvider.cpp
new file mode 100644
index 0000000000..ff902ae835
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/IPCTransportProvider.h"
+
+#include "IPCTransportProvider.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(TransportProviderParent, nsITransportProvider,
+ nsIHttpUpgradeListener)
+
+NS_IMETHODIMP
+TransportProviderParent::SetListener(nsIHttpUpgradeListener* aListener) {
+ MOZ_ASSERT(aListener);
+ mListener = aListener;
+
+ MaybeNotify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::GetIPCChild(
+ mozilla::net::PTransportProviderChild** aChild) {
+ MOZ_CRASH("Don't call this in parent process");
+ *aChild = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::OnTransportAvailable(
+ nsISocketTransport* aTransport, nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut) {
+ MOZ_ASSERT(aTransport && aSocketOut && aSocketOut);
+ mTransport = aTransport;
+ mSocketIn = aSocketIn;
+ mSocketOut = aSocketOut;
+
+ MaybeNotify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::OnUpgradeFailed(nsresult aErrorCode) { return NS_OK; }
+
+NS_IMETHODIMP
+TransportProviderParent::OnWebSocketConnectionAvailable(
+ WebSocketConnectionBase* aConnection) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TransportProviderParent::MaybeNotify() {
+ if (!mListener || !mTransport) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv =
+ mListener->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMPL_ISUPPORTS(TransportProviderChild, nsITransportProvider)
+
+TransportProviderChild::~TransportProviderChild() { Send__delete__(this); }
+
+NS_IMETHODIMP
+TransportProviderChild::SetListener(nsIHttpUpgradeListener* aListener) {
+ MOZ_CRASH("Don't call this in child process");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderChild::GetIPCChild(
+ mozilla::net::PTransportProviderChild** aChild) {
+ *aChild = this;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/IPCTransportProvider.h b/netwerk/protocol/websocket/IPCTransportProvider.h
new file mode 100644
index 0000000000..de54be127a
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_IPCTransportProvider_h
+#define mozilla_net_IPCTransportProvider_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/net/PTransportProviderParent.h"
+#include "mozilla/net/PTransportProviderChild.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsITransportProvider.h"
+
+/*
+ * No, the ownership model for TransportProvider is that the child object is
+ * refcounted "normally". I.e. ipdl code doesn't hold a strong reference to
+ * TransportProviderChild.
+ *
+ * When TransportProviderChild goes away, it sends a __delete__ message to the
+ * parent.
+ *
+ * On the parent side, ipdl holds a strong reference to TransportProviderParent.
+ * When the actor is deallocatde it releases the reference to the
+ * TransportProviderParent.
+ *
+ * So effectively the child holds a strong reference to the parent, and are
+ * otherwise normally refcounted and have their lifetime determined by that
+ * refcount.
+ *
+ * The only other caveat is that the creation happens from the parent.
+ * So to create a TransportProvider, a constructor is sent from the parent to
+ * the child. At this time the child gets its first addref.
+ *
+ * A reference to the TransportProvider is then sent as part of some other
+ * message from the parent to the child.
+ *
+ * The receiver of that message can then grab the TransportProviderChild and
+ * without addreffing it, effectively using the refcount that the
+ * TransportProviderChild got on creation.
+ */
+
+class nsISocketTransport;
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class TransportProviderParent final : public PTransportProviderParent,
+ public nsITransportProvider,
+ public nsIHttpUpgradeListener {
+ public:
+ TransportProviderParent() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+ NS_DECL_NSIHTTPUPGRADELISTENER
+
+ void ActorDestroy(ActorDestroyReason aWhy) override {}
+
+ private:
+ ~TransportProviderParent() = default;
+
+ void MaybeNotify();
+
+ nsCOMPtr<nsIHttpUpgradeListener> mListener;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+
+class TransportProviderChild final : public PTransportProviderChild,
+ public nsITransportProvider {
+ public:
+ TransportProviderChild() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+
+ private:
+ ~TransportProviderChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/websocket/PTransportProvider.ipdl b/netwerk/protocol/websocket/PTransportProvider.ipdl
new file mode 100644
index 0000000000..42ff3589a5
--- /dev/null
+++ b/netwerk/protocol/websocket/PTransportProvider.ipdl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+include "mozilla/net/IPCTransportProvider.h";
+
+namespace mozilla {
+namespace net {
+
+/*
+ * The only thing this protocol manages is used for is passing a
+ * PTransportProvider object from parent to child and then back to the parent
+ * again. Hence there's no need for any messages on the protocol itself.
+ */
+
+[ManualDealloc, ChildImpl="TransportProviderChild", ParentImpl="TransportProviderParent"]
+async protocol PTransportProvider
+{
+ manager PNecko;
+
+parent:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocket.ipdl b/netwerk/protocol/websocket/PWebSocket.ipdl
new file mode 100644
index 0000000000..bb263049f0
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocket.ipdl
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PBrowser;
+include protocol PTransportProvider;
+include IPCStream;
+include NeckoChannelParams;
+
+include "mozilla/net/WebSocketChannelParent.h";
+include "mozilla/net/WebSocketChannelChild.h";
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc, ChildImpl="WebSocketChannelChild", ParentImpl="WebSocketChannelParent"]
+async protocol PWebSocket
+{
+ manager PNecko;
+
+parent:
+ // Forwarded methods corresponding to methods on nsIWebSocketChannel
+ async AsyncOpen(nullable nsIURI aURI,
+ nsCString aOrigin,
+ OriginAttributes aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsCString aProtocol,
+ bool aSecure,
+ // ping values only meaningful if client set them
+ uint32_t aPingInterval,
+ bool aClientSetPingInterval,
+ uint32_t aPingTimeout,
+ bool aClientSetPingTimeout,
+ LoadInfoArgs aLoadInfoArgs,
+ PTransportProvider? aProvider,
+ nsCString aNegotiatedExtensions);
+ async Close(uint16_t code, nsCString reason);
+ async SendMsg(nsCString aMsg);
+ async SendBinaryMsg(nsCString aMsg);
+ async SendBinaryStream(IPCStream aStream, uint32_t aLength);
+
+ async DeleteSelf();
+
+child:
+ // Forwarded notifications corresponding to the nsIWebSocketListener interface
+ async OnStart(nsCString aProtocol, nsCString aExtensions,
+ nsString aEffectiveURL, bool aEncrypted,
+ uint64_t aHttpChannelId);
+ async OnStop(nsresult aStatusCode);
+ async OnMessageAvailable(nsCString aMsg, bool aMoreData);
+ async OnBinaryMessageAvailable(nsCString aMsg, bool aMoreData);
+ async OnAcknowledge(uint32_t aSize);
+ async OnServerClose(uint16_t code, nsCString aReason);
+
+ async __delete__();
+
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocketConnection.ipdl b/netwerk/protocol/websocket/PWebSocketConnection.ipdl
new file mode 100644
index 0000000000..b378320a2b
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocketConnection.ipdl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/ipc/TransportSecurityInfoUtils.h";
+
+[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
+
+namespace mozilla {
+namespace net {
+
+[ChildProc=Socket]
+protocol PWebSocketConnection
+{
+parent:
+ async OnTransportAvailable(nullable nsITransportSecurityInfo aSecurityInfo);
+ async OnError(nsresult aStatus);
+ async OnTCPClosed();
+ async OnDataReceived(uint8_t[] aData);
+ async OnUpgradeFailed(nsresult aReason);
+
+child:
+ async WriteOutputData(uint8_t[] aData);
+ async StartReading();
+ async DrainSocketData();
+
+ async __delete__();
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocketEventListener.ipdl b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
new file mode 100644
index 0000000000..d48224b337
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+using class mozilla::net::WebSocketFrameData from "mozilla/net/WebSocketFrame.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc]
+async protocol PWebSocketEventListener
+{
+ manager PNecko;
+
+child:
+ async WebSocketCreated(uint32_t awebSocketSerialID,
+ nsString aURI,
+ nsCString aProtocols);
+
+ async WebSocketOpened(uint32_t awebSocketSerialID,
+ nsString aEffectiveURI,
+ nsCString aProtocols,
+ nsCString aExtensions,
+ uint64_t aHttpChannelId);
+
+ async WebSocketMessageAvailable(uint32_t awebSocketSerialID,
+ nsCString aData,
+ uint16_t aMessageType);
+
+ async WebSocketClosed(uint32_t awebSocketSerialID,
+ bool aWasClean,
+ uint16_t aCode,
+ nsString aReason);
+
+ async FrameReceived(uint32_t aWebSocketSerialID,
+ WebSocketFrameData aFrameData);
+
+ async FrameSent(uint32_t aWebSocketSerialID,
+ WebSocketFrameData aFrameData);
+
+ async __delete__();
+
+parent:
+ async Close();
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp
new file mode 100644
index 0000000000..a1ebf85d8e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -0,0 +1,4336 @@
+/* -*- 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 <algorithm>
+
+#include "WebSocketChannel.h"
+
+#include "WebSocketConnectionBase.h"
+#include "WebSocketFrame.h"
+#include "WebSocketLog.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Base64.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/net/WebSocketEventService.h"
+#include "nsAlgorithm.h"
+#include "nsCRT.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsError.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsIClassOfService.h"
+#include "nsICryptoHash.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIDashboardEventNotifier.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpChannel.h"
+#include "nsIIOService.h"
+#include "nsINSSErrorsService.h"
+#include "nsINetworkLinkService.h"
+#include "nsINode.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+#include "nsIRandomGenerator.h"
+#include "nsIRunnable.h"
+#include "nsISocketTransport.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportProvider.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURI.h"
+#include "nsIURIMutator.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "plbase64.h"
+#include "prmem.h"
+#include "prnetdb.h"
+#include "zlib.h"
+
+// rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just
+// dupe one constant we need from it
+#define CLOSE_GOING_AWAY 1001
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(WebSocketChannel, nsIWebSocketChannel, nsIHttpUpgradeListener,
+ nsIRequestObserver, nsIStreamListener, nsIProtocolHandler,
+ nsIInputStreamCallback, nsIOutputStreamCallback,
+ nsITimerCallback, nsIDNSListener, nsIProtocolProxyCallback,
+ nsIInterfaceRequestor, nsIChannelEventSink,
+ nsIThreadRetargetableRequest, nsIObserver, nsINamed)
+
+// We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
+#define SEC_WEBSOCKET_VERSION "13"
+
+/*
+ * About SSL unsigned certificates
+ *
+ * wss will not work to a host using an unsigned certificate unless there
+ * is already an exception (i.e. it cannot popup a dialog asking for
+ * a security exception). This is similar to how an inlined img will
+ * fail without a dialog if fails for the same reason. This should not
+ * be a problem in practice as it is expected the websocket javascript
+ * is served from the same host as the websocket server (or of course,
+ * a valid cert could just be provided).
+ *
+ */
+
+// some helper classes
+
+//-----------------------------------------------------------------------------
+// FailDelayManager
+//
+// Stores entries (searchable by {host, port}) of connections that have recently
+// failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3
+//-----------------------------------------------------------------------------
+
+// Initial reconnect delay is randomly chosen between 200-400 ms.
+// This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests.
+const uint32_t kWSReconnectInitialBaseDelay = 200;
+const uint32_t kWSReconnectInitialRandomDelay = 200;
+
+// Base lifetime (in ms) of a FailDelay: kept longer if more failures occur
+const uint32_t kWSReconnectBaseLifeTime = 60 * 1000;
+// Maximum reconnect delay (in ms)
+const uint32_t kWSReconnectMaxDelay = 60 * 1000;
+
+// hold record of failed connections, and calculates needed delay for reconnects
+// to same host/path/port.
+class FailDelay {
+ public:
+ FailDelay(nsCString address, nsCString path, int32_t port)
+ : mAddress(std::move(address)), mPath(std::move(path)), mPort(port) {
+ mLastFailure = TimeStamp::Now();
+ mNextDelay = kWSReconnectInitialBaseDelay +
+ (rand() % kWSReconnectInitialRandomDelay);
+ }
+
+ // Called to update settings when connection fails again.
+ void FailedAgain() {
+ mLastFailure = TimeStamp::Now();
+ // We use a truncated exponential backoff as suggested by RFC 6455,
+ // but multiply by 1.5 instead of 2 to be more gradual.
+ mNextDelay = static_cast<uint32_t>(
+ std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
+ LOG(
+ ("WebSocket: FailedAgain: host=%s, path=%s, port=%d: incremented delay "
+ "to "
+ "%" PRIu32,
+ mAddress.get(), mPath.get(), mPort, mNextDelay));
+ }
+
+ // returns 0 if there is no need to delay (i.e. delay interval is over)
+ uint32_t RemainingDelay(TimeStamp rightNow) {
+ TimeDuration dur = rightNow - mLastFailure;
+ uint32_t sinceFail = (uint32_t)dur.ToMilliseconds();
+ if (sinceFail > mNextDelay) return 0;
+
+ return mNextDelay - sinceFail;
+ }
+
+ bool IsExpired(TimeStamp rightNow) {
+ return (mLastFailure + TimeDuration::FromMilliseconds(
+ kWSReconnectBaseLifeTime + mNextDelay)) <=
+ rightNow;
+ }
+
+ nsCString mAddress; // IP address (or hostname if using proxy)
+ nsCString mPath;
+ int32_t mPort;
+
+ private:
+ TimeStamp mLastFailure; // Time of last failed attempt
+ // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
+ uint32_t mNextDelay; // milliseconds
+};
+
+class FailDelayManager {
+ public:
+ FailDelayManager() {
+ MOZ_COUNT_CTOR(FailDelayManager);
+
+ mDelaysDisabled = false;
+
+ nsCOMPtr<nsIPrefBranch> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefService) {
+ return;
+ }
+ bool boolpref = true;
+ nsresult rv;
+ rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects",
+ &boolpref);
+ if (NS_SUCCEEDED(rv) && !boolpref) {
+ mDelaysDisabled = true;
+ }
+ }
+
+ ~FailDelayManager() { MOZ_COUNT_DTOR(FailDelayManager); }
+
+ void Add(nsCString& address, nsCString& path, int32_t port) {
+ if (mDelaysDisabled) return;
+
+ UniquePtr<FailDelay> record(new FailDelay(address, path, port));
+ mEntries.AppendElement(std::move(record));
+ }
+
+ // Element returned may not be valid after next main thread event: don't keep
+ // pointer to it around
+ FailDelay* Lookup(nsCString& address, nsCString& path, int32_t port,
+ uint32_t* outIndex = nullptr) {
+ if (mDelaysDisabled) return nullptr;
+
+ FailDelay* result = nullptr;
+ TimeStamp rightNow = TimeStamp::Now();
+
+ // We also remove expired entries during search: iterate from end to make
+ // indexing simpler
+ for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
+ FailDelay* fail = mEntries[i].get();
+ if (fail->mAddress.Equals(address) && fail->mPath.Equals(path) &&
+ fail->mPort == port) {
+ if (outIndex) *outIndex = i;
+ result = fail;
+ // break here: removing more entries would mess up *outIndex.
+ // Any remaining expired entries will be deleted next time Lookup
+ // finds nothing, which is the most common case anyway.
+ break;
+ }
+ if (fail->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(i);
+ }
+ }
+ return result;
+ }
+
+ // returns true if channel connects immediately, or false if it's delayed
+ void DelayOrBegin(WebSocketChannel* ws) {
+ if (!mDelaysDisabled) {
+ uint32_t failIndex = 0;
+ FailDelay* fail = Lookup(ws->mAddress, ws->mPath, ws->mPort, &failIndex);
+
+ if (fail) {
+ TimeStamp rightNow = TimeStamp::Now();
+
+ uint32_t remainingDelay = fail->RemainingDelay(rightNow);
+ if (remainingDelay) {
+ // reconnecting within delay interval: delay by remaining time
+ nsresult rv;
+ MutexAutoLock lock(ws->mMutex);
+ rv = NS_NewTimerWithCallback(getter_AddRefs(ws->mReconnectDelayTimer),
+ ws, remainingDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(
+ ("WebSocket: delaying websocket [this=%p] by %lu ms, changing"
+ " state to CONNECTING_DELAYED",
+ ws, (unsigned long)remainingDelay));
+ ws->mConnecting = CONNECTING_DELAYED;
+ return;
+ }
+ // if timer fails (which is very unlikely), drop down to BeginOpen
+ // call
+ } else if (fail->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(failIndex);
+ }
+ }
+ }
+
+ // Delays disabled, or no previous failure, or we're reconnecting after
+ // scheduled delay interval has passed: connect.
+ ws->BeginOpen(true);
+ }
+
+ // Remove() also deletes all expired entries as it iterates: better for
+ // battery life than using a periodic timer.
+ void Remove(nsCString& address, nsCString& path, int32_t port) {
+ TimeStamp rightNow = TimeStamp::Now();
+
+ // iterate from end, to make deletion indexing easier
+ for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
+ FailDelay* entry = mEntries[i].get();
+ if ((entry->mAddress.Equals(address) && entry->mPath.Equals(path) &&
+ entry->mPort == port) ||
+ entry->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(i);
+ }
+ }
+ }
+
+ private:
+ nsTArray<UniquePtr<FailDelay>> mEntries;
+ bool mDelaysDisabled;
+};
+
+//-----------------------------------------------------------------------------
+// nsWSAdmissionManager
+//
+// 1) Ensures that only one websocket at a time is CONNECTING to a given IP
+// address (or hostname, if using proxy), per RFC 6455 Section 4.1.
+// 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3
+//-----------------------------------------------------------------------------
+
+class nsWSAdmissionManager {
+ public:
+ static void Init() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ sManager = new nsWSAdmissionManager();
+ }
+ }
+
+ static void Shutdown() {
+ StaticMutexAutoLock lock(sLock);
+ delete sManager;
+ sManager = nullptr;
+ }
+
+ // Determine if we will open connection immediately (returns true), or
+ // delay/queue the connection (returns false)
+ static void ConditionallyConnect(WebSocketChannel* ws) {
+ LOG(("Websocket: ConditionallyConnect: [this=%p]", ws));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(ws->mConnecting == NOT_CONNECTING, "opening state");
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ // If there is already another WS channel connecting to this IP address,
+ // defer BeginOpen and mark as waiting in queue.
+ bool hostFound = (sManager->IndexOf(ws->mAddress, ws->mOriginSuffix) >= 0);
+
+ uint32_t failIndex = 0;
+ FailDelay* fail = sManager->mFailures.Lookup(ws->mAddress, ws->mPath,
+ ws->mPort, &failIndex);
+ bool existingFail = fail != nullptr;
+
+ // Always add ourselves to queue, even if we'll connect immediately
+ UniquePtr<nsOpenConn> newdata(
+ new nsOpenConn(ws->mAddress, ws->mOriginSuffix, existingFail, ws));
+
+ // If a connection has not previously failed then prioritize it over
+ // connections that have
+ if (existingFail) {
+ sManager->mQueue.AppendElement(std::move(newdata));
+ } else {
+ uint32_t insertionIndex = sManager->IndexOfFirstFailure();
+ MOZ_ASSERT(insertionIndex <= sManager->mQueue.Length(),
+ "Insertion index outside bounds");
+ sManager->mQueue.InsertElementAt(insertionIndex, std::move(newdata));
+ }
+
+ if (hostFound) {
+ LOG(
+ ("Websocket: some other channel is connecting, changing state to "
+ "CONNECTING_QUEUED"));
+ ws->mConnecting = CONNECTING_QUEUED;
+ } else {
+ sManager->mFailures.DelayOrBegin(ws);
+ }
+ }
+
+ static void OnConnected(WebSocketChannel* aChannel) {
+ LOG(("Websocket: OnConnected: [this=%p]", aChannel));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(aChannel->mConnecting == CONNECTING_IN_PROGRESS,
+ "Channel completed connect, but not connecting?");
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ LOG(("Websocket: changing state to NOT_CONNECTING"));
+ aChannel->mConnecting = NOT_CONNECTING;
+
+ // Remove from queue
+ sManager->RemoveFromQueue(aChannel);
+
+ // Connection succeeded, so stop keeping track of any previous failures
+ sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPath,
+ aChannel->mPort);
+
+ // Check for queued connections to same host.
+ // Note: still need to check for failures, since next websocket with same
+ // host may have different port
+ sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
+ }
+
+ // Called every time a websocket channel ends its session (including going
+ // away w/o ever successfully creating a connection)
+ static void OnStopSession(WebSocketChannel* aChannel, nsresult aReason) {
+ LOG(("Websocket: OnStopSession: [this=%p, reason=0x%08" PRIx32 "]",
+ aChannel, static_cast<uint32_t>(aReason)));
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ if (NS_FAILED(aReason)) {
+ // Have we seen this failure before?
+ FailDelay* knownFailure = sManager->mFailures.Lookup(
+ aChannel->mAddress, aChannel->mPath, aChannel->mPort);
+ if (knownFailure) {
+ if (aReason == NS_ERROR_NOT_CONNECTED) {
+ // Don't count close() before connection as a network error
+ LOG(
+ ("Websocket close() before connection to %s, %s, %d completed"
+ " [this=%p]",
+ aChannel->mAddress.get(), aChannel->mPath.get(),
+ (int)aChannel->mPort, aChannel));
+ } else {
+ // repeated failure to connect: increase delay for next connection
+ knownFailure->FailedAgain();
+ }
+ } else {
+ // new connection failure: record it.
+ LOG(("WebSocket: connection to %s, %s, %d failed: [this=%p]",
+ aChannel->mAddress.get(), aChannel->mPath.get(),
+ (int)aChannel->mPort, aChannel));
+ sManager->mFailures.Add(aChannel->mAddress, aChannel->mPath,
+ aChannel->mPort);
+ }
+ }
+
+ if (NS_IsMainThread()) {
+ ContinueOnStopSession(aChannel, aReason);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsWSAdmissionManager::ContinueOnStopSession",
+ [channel = RefPtr{aChannel}, reason = aReason]() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ nsWSAdmissionManager::ContinueOnStopSession(channel, reason);
+ }));
+ }
+ }
+
+ static void ContinueOnStopSession(WebSocketChannel* aChannel,
+ nsresult aReason) {
+ sLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ if (!aChannel->mConnecting) {
+ return;
+ }
+
+ // Only way a connecting channel may get here w/o failing is if it
+ // was closed with GOING_AWAY (1001) because of navigation, tab
+ // close, etc.
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(aChannel->mMutex);
+ MOZ_ASSERT(
+ NS_FAILED(aReason) || aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
+ "websocket closed while connecting w/o failing?");
+ }
+#endif
+ Unused << aReason;
+
+ sManager->RemoveFromQueue(aChannel);
+
+ bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
+ LOG(("Websocket: changing state to NOT_CONNECTING"));
+ aChannel->mConnecting = NOT_CONNECTING;
+ if (wasNotQueued) {
+ sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
+ }
+ }
+
+ static void IncrementSessionCount() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ sManager->mSessionCount++;
+ }
+
+ static void DecrementSessionCount() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ sManager->mSessionCount--;
+ }
+
+ static void GetSessionCount(int32_t& aSessionCount) {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ aSessionCount = sManager->mSessionCount;
+ }
+
+ private:
+ nsWSAdmissionManager() : mSessionCount(0) {
+ MOZ_COUNT_CTOR(nsWSAdmissionManager);
+ }
+
+ ~nsWSAdmissionManager() { MOZ_COUNT_DTOR(nsWSAdmissionManager); }
+
+ class nsOpenConn {
+ public:
+ nsOpenConn(nsCString& addr, nsCString& originSuffix, bool failed,
+ WebSocketChannel* channel)
+ : mAddress(addr),
+ mOriginSuffix(originSuffix),
+ mFailed(failed),
+ mChannel(channel) {
+ MOZ_COUNT_CTOR(nsOpenConn);
+ }
+ MOZ_COUNTED_DTOR(nsOpenConn)
+
+ nsCString mAddress;
+ nsCString mOriginSuffix;
+ bool mFailed = false;
+ RefPtr<WebSocketChannel> mChannel;
+ };
+
+ void ConnectNext(nsCString& hostName, nsCString& originSuffix) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ int32_t index = IndexOf(hostName, originSuffix);
+ if (index >= 0) {
+ WebSocketChannel* chan = mQueue[index]->mChannel;
+
+ MOZ_ASSERT(chan->mConnecting == CONNECTING_QUEUED,
+ "transaction not queued but in queue");
+ LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
+
+ mFailures.DelayOrBegin(chan);
+ }
+ }
+
+ void RemoveFromQueue(WebSocketChannel* aChannel) {
+ LOG(("Websocket: RemoveFromQueue: [this=%p]", aChannel));
+ int32_t index = IndexOf(aChannel);
+ MOZ_ASSERT(index >= 0, "connection to remove not in queue");
+ if (index >= 0) {
+ mQueue.RemoveElementAt(index);
+ }
+ }
+
+ int32_t IndexOf(nsCString& aAddress, nsCString& aOriginSuffix) {
+ for (uint32_t i = 0; i < mQueue.Length(); i++) {
+ bool isPartitioned = StaticPrefs::privacy_partition_network_state() ||
+ StaticPrefs::privacy_firstparty_isolate();
+ if (aAddress == (mQueue[i])->mAddress &&
+ (!isPartitioned || aOriginSuffix == (mQueue[i])->mOriginSuffix)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ int32_t IndexOf(WebSocketChannel* aChannel) {
+ for (uint32_t i = 0; i < mQueue.Length(); i++) {
+ if (aChannel == (mQueue[i])->mChannel) return i;
+ }
+ return -1;
+ }
+
+ // Returns the index of the first entry that failed, or else the last entry if
+ // none found
+ uint32_t IndexOfFirstFailure() {
+ for (uint32_t i = 0; i < mQueue.Length(); i++) {
+ if (mQueue[i]->mFailed) return i;
+ }
+ return mQueue.Length();
+ }
+
+ // SessionCount might be decremented from the main or the socket
+ // thread, so manage it with atomic counters
+ Atomic<int32_t> mSessionCount;
+
+ // Queue for websockets that have not completed connecting yet.
+ // The first nsOpenConn with a given address will be either be
+ // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same
+ // hostname must be CONNECTING_QUEUED.
+ //
+ // We could hash hostnames instead of using a single big vector here, but the
+ // dataset is expected to be small.
+ nsTArray<UniquePtr<nsOpenConn>> mQueue;
+
+ FailDelayManager mFailures;
+
+ static nsWSAdmissionManager* sManager MOZ_GUARDED_BY(sLock);
+ static StaticMutex sLock;
+};
+
+nsWSAdmissionManager* nsWSAdmissionManager::sManager;
+StaticMutex nsWSAdmissionManager::sLock;
+
+//-----------------------------------------------------------------------------
+// CallOnMessageAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnMessageAvailable final : public Runnable {
+ public:
+ CallOnMessageAvailable(WebSocketChannel* aChannel, nsACString& aData,
+ int32_t aLen)
+ : Runnable("net::CallOnMessageAvailable"),
+ mChannel(aChannel),
+ mListenerMT(aChannel->mListenerMT),
+ mData(aData),
+ mLen(aLen) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ nsresult rv;
+ if (mLen < 0) {
+ rv = mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext,
+ mData);
+ } else {
+ rv = mListenerMT->mListener->OnBinaryMessageAvailable(
+ mListenerMT->mContext, mData);
+ }
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("OnMessageAvailable or OnBinaryMessageAvailable "
+ "failed with 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~CallOnMessageAvailable() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsCString mData;
+ int32_t mLen;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnStop
+//-----------------------------------------------------------------------------
+
+class CallOnStop final : public Runnable {
+ public:
+ CallOnStop(WebSocketChannel* aChannel, nsresult aReason)
+ : Runnable("net::CallOnStop"),
+ mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mReason(aReason) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ nsresult rv =
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, mReason);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::CallOnStop "
+ "OnStop failed (%08" PRIx32 ")\n",
+ static_cast<uint32_t>(rv)));
+ }
+ mChannel->mListenerMT = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~CallOnStop() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsresult mReason;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnServerClose
+//-----------------------------------------------------------------------------
+
+class CallOnServerClose final : public Runnable {
+ public:
+ CallOnServerClose(WebSocketChannel* aChannel, uint16_t aCode,
+ nsACString& aReason)
+ : Runnable("net::CallOnServerClose"),
+ mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mCode(aCode),
+ mReason(aReason) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ nsresult rv = mListenerMT->mListener->OnServerClose(mListenerMT->mContext,
+ mCode, mReason);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::CallOnServerClose "
+ "OnServerClose failed (%08" PRIx32 ")\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~CallOnServerClose() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+//-----------------------------------------------------------------------------
+// CallAcknowledge
+//-----------------------------------------------------------------------------
+
+class CallAcknowledge final : public Runnable {
+ public:
+ CallAcknowledge(WebSocketChannel* aChannel, uint32_t aSize)
+ : Runnable("net::CallAcknowledge"),
+ mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mSize(aSize) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
+ if (mListenerMT) {
+ nsresult rv =
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, mSize);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::CallAcknowledge: Acknowledge failed (%08" PRIx32
+ ")\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~CallAcknowledge() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint32_t mSize;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnTransportAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnTransportAvailable final : public Runnable {
+ public:
+ CallOnTransportAvailable(WebSocketChannel* aChannel,
+ nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut)
+ : Runnable("net::CallOnTransportAvailble"),
+ mChannel(aChannel),
+ mTransport(aTransport),
+ mSocketIn(aSocketIn),
+ mSocketOut(aSocketOut) {}
+
+ NS_IMETHOD Run() override {
+ LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
+ return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+ }
+
+ private:
+ ~CallOnTransportAvailable() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+
+//-----------------------------------------------------------------------------
+// PMCECompression
+//-----------------------------------------------------------------------------
+
+class PMCECompression {
+ public:
+ PMCECompression(bool aNoContextTakeover, int32_t aLocalMaxWindowBits,
+ int32_t aRemoteMaxWindowBits)
+ : mActive(false),
+ mNoContextTakeover(aNoContextTakeover),
+ mResetDeflater(false),
+ mMessageDeflated(false) {
+ this->mDeflater.next_in = nullptr;
+ this->mDeflater.avail_in = 0;
+ this->mDeflater.total_in = 0;
+ this->mDeflater.next_out = nullptr;
+ this->mDeflater.avail_out = 0;
+ this->mDeflater.total_out = 0;
+ this->mDeflater.msg = nullptr;
+ this->mDeflater.state = nullptr;
+ this->mDeflater.data_type = 0;
+ this->mDeflater.adler = 0;
+ this->mDeflater.reserved = 0;
+ this->mInflater.next_in = nullptr;
+ this->mInflater.avail_in = 0;
+ this->mInflater.total_in = 0;
+ this->mInflater.next_out = nullptr;
+ this->mInflater.avail_out = 0;
+ this->mInflater.total_out = 0;
+ this->mInflater.msg = nullptr;
+ this->mInflater.state = nullptr;
+ this->mInflater.data_type = 0;
+ this->mInflater.adler = 0;
+ this->mInflater.reserved = 0;
+ MOZ_COUNT_CTOR(PMCECompression);
+
+ mDeflater.zalloc = mInflater.zalloc = Z_NULL;
+ mDeflater.zfree = mInflater.zfree = Z_NULL;
+ mDeflater.opaque = mInflater.opaque = Z_NULL;
+
+ if (deflateInit2(&mDeflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -aLocalMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
+ if (inflateInit2(&mInflater, -aRemoteMaxWindowBits) == Z_OK) {
+ mActive = true;
+ } else {
+ deflateEnd(&mDeflater);
+ }
+ }
+ }
+
+ ~PMCECompression() {
+ MOZ_COUNT_DTOR(PMCECompression);
+
+ if (mActive) {
+ inflateEnd(&mInflater);
+ deflateEnd(&mDeflater);
+ }
+ }
+
+ bool Active() { return mActive; }
+
+ void SetMessageDeflated() {
+ MOZ_ASSERT(!mMessageDeflated);
+ mMessageDeflated = true;
+ }
+ bool IsMessageDeflated() { return mMessageDeflated; }
+
+ bool UsingContextTakeover() { return !mNoContextTakeover; }
+
+ nsresult Deflate(uint8_t* data, uint32_t dataLen, nsACString& _retval) {
+ if (mResetDeflater || mNoContextTakeover) {
+ if (deflateReset(&mDeflater) != Z_OK) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mResetDeflater = false;
+ }
+
+ mDeflater.avail_out = kBufferLen;
+ mDeflater.next_out = mBuffer;
+ mDeflater.avail_in = dataLen;
+ mDeflater.next_in = data;
+
+ while (true) {
+ int zerr = deflate(&mDeflater, Z_SYNC_FLUSH);
+
+ if (zerr != Z_OK) {
+ mResetDeflater = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t deflated = kBufferLen - mDeflater.avail_out;
+ if (deflated > 0) {
+ _retval.Append(reinterpret_cast<char*>(mBuffer), deflated);
+ }
+
+ mDeflater.avail_out = kBufferLen;
+ mDeflater.next_out = mBuffer;
+
+ if (mDeflater.avail_in > 0) {
+ continue; // There is still some data to deflate
+ }
+
+ if (deflated == kBufferLen) {
+ continue; // There was not enough space in the buffer
+ }
+
+ break;
+ }
+
+ if (_retval.Length() < 4) {
+ MOZ_ASSERT(false, "Expected trailing not found in deflated data!");
+ mResetDeflater = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ _retval.Truncate(_retval.Length() - 4);
+
+ return NS_OK;
+ }
+
+ nsresult Inflate(uint8_t* data, uint32_t dataLen, nsACString& _retval) {
+ mMessageDeflated = false;
+
+ Bytef trailingData[] = {0x00, 0x00, 0xFF, 0xFF};
+ bool trailingDataUsed = false;
+
+ mInflater.avail_out = kBufferLen;
+ mInflater.next_out = mBuffer;
+ mInflater.avail_in = dataLen;
+ mInflater.next_in = data;
+
+ while (true) {
+ int zerr = inflate(&mInflater, Z_NO_FLUSH);
+
+ if (zerr == Z_STREAM_END) {
+ Bytef* saveNextIn = mInflater.next_in;
+ uint32_t saveAvailIn = mInflater.avail_in;
+ Bytef* saveNextOut = mInflater.next_out;
+ uint32_t saveAvailOut = mInflater.avail_out;
+
+ inflateReset(&mInflater);
+
+ mInflater.next_in = saveNextIn;
+ mInflater.avail_in = saveAvailIn;
+ mInflater.next_out = saveNextOut;
+ mInflater.avail_out = saveAvailOut;
+ } else if (zerr != Z_OK && zerr != Z_BUF_ERROR) {
+ return NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+
+ uint32_t inflated = kBufferLen - mInflater.avail_out;
+ if (inflated > 0) {
+ if (!_retval.Append(reinterpret_cast<char*>(mBuffer), inflated,
+ fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mInflater.avail_out = kBufferLen;
+ mInflater.next_out = mBuffer;
+
+ if (mInflater.avail_in > 0) {
+ continue; // There is still some data to inflate
+ }
+
+ if (inflated == kBufferLen) {
+ continue; // There was not enough space in the buffer
+ }
+
+ if (!trailingDataUsed) {
+ trailingDataUsed = true;
+ mInflater.avail_in = sizeof(trailingData);
+ mInflater.next_in = trailingData;
+ continue;
+ }
+
+ return NS_OK;
+ }
+ }
+
+ private:
+ bool mActive;
+ bool mNoContextTakeover;
+ bool mResetDeflater;
+ bool mMessageDeflated;
+ z_stream mDeflater{};
+ z_stream mInflater{};
+ const static uint32_t kBufferLen = 4096;
+ uint8_t mBuffer[kBufferLen]{0};
+};
+
+//-----------------------------------------------------------------------------
+// OutboundMessage
+//-----------------------------------------------------------------------------
+
+enum WsMsgType {
+ kMsgTypeString = 0,
+ kMsgTypeBinaryString,
+ kMsgTypeStream,
+ kMsgTypePing,
+ kMsgTypePong,
+ kMsgTypeFin
+};
+
+static const char* msgNames[] = {"text", "binaryString", "binaryStream",
+ "ping", "pong", "close"};
+
+class OutboundMessage {
+ public:
+ OutboundMessage(WsMsgType type, const nsACString& str)
+ : mMsg(mozilla::AsVariant(pString(str))),
+ mMsgType(type),
+ mDeflated(false) {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ }
+
+ OutboundMessage(nsIInputStream* stream, uint32_t length)
+ : mMsg(mozilla::AsVariant(StreamWithLength(stream, length))),
+ mMsgType(kMsgTypeStream),
+ mDeflated(false) {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ }
+
+ ~OutboundMessage() {
+ MOZ_COUNT_DTOR(OutboundMessage);
+ switch (mMsgType) {
+ case kMsgTypeString:
+ case kMsgTypeBinaryString:
+ case kMsgTypePing:
+ case kMsgTypePong:
+ break;
+ case kMsgTypeStream:
+ // for now this only gets hit if msg deleted w/o being sent
+ if (mMsg.as<StreamWithLength>().mStream) {
+ mMsg.as<StreamWithLength>().mStream->Close();
+ }
+ break;
+ case kMsgTypeFin:
+ break; // do-nothing: avoid compiler warning
+ }
+ }
+
+ WsMsgType GetMsgType() const { return mMsgType; }
+ int32_t Length() {
+ if (mMsg.is<pString>()) {
+ return mMsg.as<pString>().mValue.Length();
+ }
+
+ return mMsg.as<StreamWithLength>().mLength;
+ }
+ int32_t OrigLength() {
+ if (mMsg.is<pString>()) {
+ pString& ref = mMsg.as<pString>();
+ return mDeflated ? ref.mOrigValue.Length() : ref.mValue.Length();
+ }
+
+ return mMsg.as<StreamWithLength>().mLength;
+ }
+
+ uint8_t* BeginWriting() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mMsg.as<pString>().mValue.IsVoid()) {
+ return (uint8_t*)mMsg.as<pString>().mValue.BeginWriting();
+ }
+ return nullptr;
+ }
+
+ uint8_t* BeginReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mMsg.as<pString>().mValue.IsVoid()) {
+ return (uint8_t*)mMsg.as<pString>().mValue.BeginReading();
+ }
+ return nullptr;
+ }
+
+ uint8_t* BeginOrigReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mDeflated) return BeginReading();
+ if (!mMsg.as<pString>().mOrigValue.IsVoid()) {
+ return (uint8_t*)mMsg.as<pString>().mOrigValue.BeginReading();
+ }
+ return nullptr;
+ }
+
+ nsresult ConvertStreamToString() {
+ MOZ_ASSERT(mMsgType == kMsgTypeStream, "Not a stream!");
+ nsAutoCString temp;
+ {
+ StreamWithLength& ref = mMsg.as<StreamWithLength>();
+ nsresult rv = NS_ReadInputStreamToString(ref.mStream, temp, ref.mLength);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (temp.Length() != ref.mLength) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ ref.mStream->Close();
+ }
+
+ mMsg = mozilla::AsVariant(pString(temp));
+ mMsgType = kMsgTypeBinaryString;
+
+ return NS_OK;
+ }
+
+ bool DeflatePayload(PMCECompression* aCompressor) {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ MOZ_ASSERT(!mDeflated);
+
+ nsresult rv;
+ pString& ref = mMsg.as<pString>();
+ if (ref.mValue.Length() == 0) {
+ // Empty message
+ return false;
+ }
+
+ nsAutoCString temp;
+ rv = aCompressor->Deflate(BeginReading(), ref.mValue.Length(), temp);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OutboundMessage: Deflating payload failed "
+ "[rv=0x%08" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return false;
+ }
+
+ if (!aCompressor->UsingContextTakeover() &&
+ temp.Length() > ref.mValue.Length()) {
+ // When "<local>_no_context_takeover" was negotiated, do not send deflated
+ // payload if it's larger that the original one. OTOH, it makes sense
+ // to send the larger deflated payload when the sliding window is not
+ // reset between messages because if we would skip some deflated block
+ // we would need to empty the sliding window which could affect the
+ // compression of the subsequent messages.
+ LOG(
+ ("WebSocketChannel::OutboundMessage: Not deflating message since the "
+ "deflated payload is larger than the original one [deflated=%zd, "
+ "original=%zd]",
+ temp.Length(), ref.mValue.Length()));
+ return false;
+ }
+
+ mDeflated = true;
+ mMsg.as<pString>().mOrigValue = mMsg.as<pString>().mValue;
+ mMsg.as<pString>().mValue = temp;
+ return true;
+ }
+
+ private:
+ struct pString {
+ nsCString mValue;
+ nsCString mOrigValue;
+ explicit pString(const nsACString& value)
+ : mValue(value), mOrigValue(VoidCString()) {}
+ };
+ struct StreamWithLength {
+ nsCOMPtr<nsIInputStream> mStream;
+ uint32_t mLength;
+ explicit StreamWithLength(nsIInputStream* stream, uint32_t Length)
+ : mStream(stream), mLength(Length) {}
+ };
+ mozilla::Variant<pString, StreamWithLength> mMsg;
+ WsMsgType mMsgType;
+ bool mDeflated;
+};
+
+//-----------------------------------------------------------------------------
+// OutboundEnqueuer
+//-----------------------------------------------------------------------------
+
+class OutboundEnqueuer final : public Runnable {
+ public:
+ OutboundEnqueuer(WebSocketChannel* aChannel, OutboundMessage* aMsg)
+ : Runnable("OutboundEnquerer"), mChannel(aChannel), mMessage(aMsg) {}
+
+ NS_IMETHOD Run() override {
+ mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
+ return NS_OK;
+ }
+
+ private:
+ ~OutboundEnqueuer() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ OutboundMessage* mMessage;
+};
+
+//-----------------------------------------------------------------------------
+// WebSocketChannel
+//-----------------------------------------------------------------------------
+
+WebSocketChannel::WebSocketChannel()
+ : mPort(0),
+ mCloseTimeout(20000),
+ mOpenTimeout(20000),
+ mConnecting(NOT_CONNECTING),
+ mMaxConcurrentConnections(200),
+ mInnerWindowID(0),
+ mGotUpgradeOK(0),
+ mRecvdHttpUpgradeTransport(0),
+ mAllowPMCE(1),
+ mPingOutstanding(0),
+ mReleaseOnTransmit(0),
+ mDataStarted(false),
+ mRequestedClose(false),
+ mClientClosed(false),
+ mServerClosed(false),
+ mStopped(false),
+ mCalledOnStop(false),
+ mTCPClosed(false),
+ mOpenedHttpChannel(false),
+ mIncrementedSessionCount(false),
+ mDecrementedSessionCount(false),
+ mMaxMessageSize(INT32_MAX),
+ mStopOnClose(NS_OK),
+ mServerCloseCode(CLOSE_ABNORMAL),
+ mScriptCloseCode(0),
+ mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION),
+ mFragmentAccumulator(0),
+ mBuffered(0),
+ mBufferSize(kIncomingBufferInitialSize),
+ mCurrentOut(nullptr),
+ mCurrentOutSent(0),
+ mHdrOutToSend(0),
+ mHdrOut(nullptr),
+ mCompressorMutex("WebSocketChannel::mCompressorMutex"),
+ mDynamicOutputSize(0),
+ mDynamicOutput(nullptr),
+ mPrivateBrowsing(false),
+ mConnectionLogService(nullptr),
+ mMutex("WebSocketChannel::mMutex") {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
+
+ nsWSAdmissionManager::Init();
+
+ mFramePtr = mBuffer = static_cast<uint8_t*>(moz_xmalloc(mBufferSize));
+
+ nsresult rv;
+ mConnectionLogService =
+ do_GetService("@mozilla.org/network/dashboard;1", &rv);
+ if (NS_FAILED(rv)) LOG(("Failed to initiate dashboard service."));
+
+ mService = WebSocketEventService::GetOrCreate();
+}
+
+WebSocketChannel::~WebSocketChannel() {
+ LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
+
+ if (mWasOpened) {
+ MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
+ MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
+ }
+ MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
+ MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
+
+ free(mBuffer);
+ free(mDynamicOutput);
+ delete mCurrentOut;
+
+ while ((mCurrentOut = mOutgoingPingMessages.PopFront())) {
+ delete mCurrentOut;
+ }
+ while ((mCurrentOut = mOutgoingPongMessages.PopFront())) {
+ delete mCurrentOut;
+ }
+ while ((mCurrentOut = mOutgoingMessages.PopFront())) {
+ delete mCurrentOut;
+ }
+
+ mListenerMT = nullptr;
+
+ NS_ReleaseOnMainThread("WebSocketChannel::mService", mService.forget());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ LOG(("WebSocketChannel::Observe [topic=\"%s\"]\n", topic));
+
+ if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(data);
+ const char* state = converted.get();
+
+ if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) {
+ LOG(("WebSocket: received network CHANGED event"));
+
+ if (!mIOThread) {
+ // there has not been an asyncopen yet on the object and then we need
+ // no ping.
+ LOG(("WebSocket: early object, no ping needed"));
+ } else {
+ mIOThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::OnNetworkChanged", this,
+ &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::OnNetworkChanged() {
+ if (!mDataStarted) {
+ LOG(("WebSocket: data not started yet, no ping needed"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ LOG(("WebSocketChannel::OnNetworkChanged() - on socket thread %p", this));
+
+ if (mPingOutstanding) {
+ // If there's an outstanding ping that's expected to get a pong back
+ // we let that do its thing.
+ LOG(("WebSocket: pong already pending"));
+ return NS_OK;
+ }
+
+ if (mPingForced) {
+ // avoid more than one
+ LOG(("WebSocket: forced ping timer already fired"));
+ return NS_OK;
+ }
+
+ LOG(("nsWebSocketChannel:: Generating Ping as network changed\n"));
+
+ if (!mPingTimer) {
+ // The ping timer is only conditionally running already. If it wasn't
+ // already created do it here.
+ mPingTimer = NS_NewTimer();
+ if (!mPingTimer) {
+ LOG(("WebSocket: unable to create ping timer!"));
+ NS_WARNING("unable to create ping timer!");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ // Trigger the ping timeout asap to fire off a new ping. Wait just
+ // a little bit to better avoid multi-triggers.
+ mPingForced = true;
+ mPingTimer->InitWithCallback(this, 200, nsITimer::TYPE_ONE_SHOT);
+
+ return NS_OK;
+}
+
+void WebSocketChannel::Shutdown() { nsWSAdmissionManager::Shutdown(); }
+
+void WebSocketChannel::GetEffectiveURL(nsAString& aEffectiveURL) const {
+ aEffectiveURL = mEffectiveURL;
+}
+
+bool WebSocketChannel::IsEncrypted() const { return mEncrypted; }
+
+void WebSocketChannel::BeginOpen(bool aCalledFromAdmissionManager) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannel::BeginOpen() %p\n", this));
+
+ // Important that we set CONNECTING_IN_PROGRESS before any call to
+ // AbortSession here: ensures that any remaining queued connection(s) are
+ // scheduled in OnStopSession
+ LOG(("Websocket: changing state to CONNECTING_IN_PROGRESS"));
+ mConnecting = CONNECTING_IN_PROGRESS;
+
+ if (aCalledFromAdmissionManager) {
+ // When called from nsWSAdmissionManager post an event to avoid potential
+ // re-entering of nsWSAdmissionManager and its lock.
+ NS_DispatchToMainThread(
+ NewRunnableMethod("net::WebSocketChannel::BeginOpenInternal", this,
+ &WebSocketChannel::BeginOpenInternal),
+ NS_DISPATCH_NORMAL);
+ } else {
+ BeginOpenInternal();
+ }
+}
+
+// MainThread
+void WebSocketChannel::BeginOpenInternal() {
+ LOG(("WebSocketChannel::BeginOpenInternal() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ nsresult rv;
+
+ if (mRedirectCallback) {
+ LOG(("WebSocketChannel::BeginOpenInternal: Resuming Redirect\n"));
+ rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
+ mRedirectCallback = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ rv = localChannel->AsyncOpen(this);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED);
+ return;
+ }
+ mOpenedHttpChannel = true;
+
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mOpenTimer), this, mOpenTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::BeginOpenInternal: cannot initialize open "
+ "timer\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+}
+
+bool WebSocketChannel::IsPersistentFramePtr() {
+ return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
+}
+
+// Extends the internal buffer by count and returns the total
+// amount of data available for read
+//
+// Accumulated fragment size is passed in instead of using the member
+// variable beacuse when transitioning from the stack to the persistent
+// read buffer we want to explicitly include them in the buffer instead
+// of as already existing data.
+bool WebSocketChannel::UpdateReadBuffer(uint8_t* buffer, uint32_t count,
+ uint32_t accumulatedFragments,
+ uint32_t* available) {
+ LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n", this, buffer,
+ count));
+
+ if (!mBuffered) mFramePtr = mBuffer;
+
+ MOZ_ASSERT(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
+ MOZ_ASSERT(mFramePtr - accumulatedFragments >= mBuffer,
+ "reserved FramePtr bad");
+
+ if (mBuffered + count <= mBufferSize) {
+ // append to existing buffer
+ LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
+ } else if (mBuffered + count - (mFramePtr - accumulatedFragments - mBuffer) <=
+ mBufferSize) {
+ // make room in existing buffer by shifting unused data to start
+ mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
+ LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
+ ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
+ mFramePtr = mBuffer + accumulatedFragments;
+ } else {
+ // existing buffer is not sufficient, extend it
+ mBufferSize += count + 8192 + mBufferSize / 3;
+ LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize));
+ uint8_t* old = mBuffer;
+ mBuffer = (uint8_t*)realloc(mBuffer, mBufferSize);
+ if (!mBuffer) {
+ mBuffer = old;
+ return false;
+ }
+ mFramePtr = mBuffer + (mFramePtr - old);
+ }
+
+ ::memcpy(mBuffer + mBuffered, buffer, count);
+ mBuffered += count;
+
+ if (available) *available = mBuffered - (mFramePtr - mBuffer);
+
+ return true;
+}
+
+nsresult WebSocketChannel::ProcessInput(uint8_t* buffer, uint32_t count) {
+ LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ nsresult rv;
+
+ // The purpose of ping/pong is to actively probe the peer so that an
+ // unreachable peer is not mistaken for a period of idleness. This
+ // implementation accepts any application level read activity as a sign of
+ // life, it does not necessarily have to be a pong.
+ ResetPingTimer();
+
+ uint32_t avail;
+
+ if (!mBuffered) {
+ // Most of the time we can process right off the stack buffer without
+ // having to accumulate anything
+ mFramePtr = buffer;
+ avail = count;
+ } else {
+ if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+
+ uint8_t* payload;
+ uint32_t totalAvail = avail;
+
+ while (avail >= 2) {
+ int64_t payloadLength64 = mFramePtr[1] & kPayloadLengthBitsMask;
+ uint8_t finBit = mFramePtr[0] & kFinalFragBit;
+ uint8_t rsvBits = mFramePtr[0] & kRsvBitsMask;
+ uint8_t rsvBit1 = mFramePtr[0] & kRsv1Bit;
+ uint8_t rsvBit2 = mFramePtr[0] & kRsv2Bit;
+ uint8_t rsvBit3 = mFramePtr[0] & kRsv3Bit;
+ uint8_t opcode = mFramePtr[0] & kOpcodeBitsMask;
+ uint8_t maskBit = mFramePtr[1] & kMaskBit;
+ uint32_t mask = 0;
+
+ uint32_t framingLength = 2;
+ if (maskBit) framingLength += 4;
+
+ if (payloadLength64 < 126) {
+ if (avail < framingLength) break;
+ } else if (payloadLength64 == 126) {
+ // 16 bit length field
+ framingLength += 2;
+ if (avail < framingLength) break;
+
+ payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3];
+
+ if (payloadLength64 < 126) {
+ // Section 5.2 says that the minimal number of bytes MUST
+ // be used to encode the length in all cases
+ LOG(("WebSocketChannel:: non-minimal-encoded payload length"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ } else {
+ // 64 bit length
+ framingLength += 8;
+ if (avail < framingLength) break;
+
+ if (mFramePtr[2] & 0x80) {
+ // Section 4.2 says that the most significant bit MUST be
+ // 0. (i.e. this is really a 63 bit value)
+ LOG(("WebSocketChannel:: high bit of 64 bit length set"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // copy this in case it is unaligned
+ payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
+
+ if (payloadLength64 <= 0xffff) {
+ // Section 5.2 says that the minimal number of bytes MUST
+ // be used to encode the length in all cases
+ LOG(("WebSocketChannel:: non-minimal-encoded payload length"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ payload = mFramePtr + framingLength;
+ avail -= framingLength;
+
+ LOG(("WebSocketChannel::ProcessInput: payload %" PRId64 " avail %" PRIu32
+ "\n",
+ payloadLength64, avail));
+
+ CheckedInt<int64_t> payloadLengthChecked(payloadLength64);
+ payloadLengthChecked += mFragmentAccumulator;
+ if (!payloadLengthChecked.isValid() ||
+ payloadLengthChecked.value() > mMaxMessageSize) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
+
+ if (avail < payloadLength) break;
+
+ LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
+ opcode));
+
+ if (!maskBit && mIsServerSide) {
+ LOG(
+ ("WebSocketChannel::ProcessInput: unmasked frame received "
+ "from client\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (maskBit) {
+ if (!mIsServerSide) {
+ // The server should not be allowed to send masked frames to clients.
+ // But we've been allowing it for some time, so this should be
+ // deprecated with care.
+ LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
+ }
+
+ mask = NetworkEndian::readUint32(payload - 4);
+ }
+
+ if (mask) {
+ ApplyMask(mask, payload, payloadLength);
+ } else if (mIsServerSide) {
+ LOG(
+ ("WebSocketChannel::ProcessInput: masked frame with mask 0 received"
+ "from client\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Control codes are required to have the fin bit set
+ if (!finBit && (opcode & kControlFrameMask)) {
+ LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (rsvBits) {
+ // PMCE sets RSV1 bit in the first fragment when the non-control frame
+ // is deflated
+ MutexAutoLock lock(mCompressorMutex);
+ if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 &&
+ !(opcode & kControlFrameMask)) {
+ mPMCECompressor->SetMessageDeflated();
+ LOG(("WebSocketChannel::ProcessInput: received deflated frame\n"));
+ } else {
+ LOG(("WebSocketChannel::ProcessInput: unexpected reserved bits %x\n",
+ rsvBits));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ if (!finBit || opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ // This is part of a fragment response
+
+ // Only the first frame has a non zero op code: Make sure we don't see a
+ // first frame while some old fragments are open
+ if ((mFragmentAccumulator != 0) &&
+ (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION)) {
+ LOG(("WebSocketChannel:: nested fragments\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ LOG(("WebSocketChannel:: Accumulating Fragment %" PRIu32 "\n",
+ payloadLength));
+
+ if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ // Make sure this continuation fragment isn't the first fragment
+ if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // For frag > 1 move the data body back on top of the headers
+ // so we have contiguous stream of data
+ MOZ_ASSERT(mFramePtr + framingLength == payload,
+ "payload offset from frameptr wrong");
+ ::memmove(mFramePtr, payload, avail);
+ payload = mFramePtr;
+ if (mBuffered) mBuffered -= framingLength;
+ } else {
+ mFragmentOpcode = opcode;
+ }
+
+ if (finBit) {
+ LOG(("WebSocketChannel:: Finalizing Fragment\n"));
+ payload -= mFragmentAccumulator;
+ payloadLength += mFragmentAccumulator;
+ avail += mFragmentAccumulator;
+ mFragmentAccumulator = 0;
+ opcode = mFragmentOpcode;
+ // reset to detect if next message illegally starts with continuation
+ mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
+ } else {
+ opcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
+ mFragmentAccumulator += payloadLength;
+ }
+ } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
+ // This frame is not part of a fragment sequence but we
+ // have an open fragment.. it must be a control code or else
+ // we have a problem
+ LOG(("WebSocketChannel:: illegal fragment sequence\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mServerClosed) {
+ LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
+ opcode));
+ // nop
+ } else if (mStopped) {
+ LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
+ opcode));
+ } else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) {
+ if (mListenerMT) {
+ nsCString utf8Data;
+ {
+ MutexAutoLock lock(mCompressorMutex);
+ bool isDeflated =
+ mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %stext frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(
+ ("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%zd]\n",
+ payloadLength, utf8Data.Length()));
+ } else {
+ if (!utf8Data.Assign((const char*)payload, payloadLength,
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ // Section 8.1 says to fail connection if invalid utf-8 in text message
+ if (!IsUtf8(utf8Data)) {
+ LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded(
+ finBit, rsvBit1, rsvBit2, rsvBit3, opcode, maskBit, mask, utf8Data);
+
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
+ LOG(("Added new msg received for %s", mHost.get()));
+ }
+ }
+ } else if (opcode & kControlFrameMask) {
+ // control frames
+ if (payloadLength > 125) {
+ LOG(("WebSocketChannel:: bad control frame code %d length %d\n", opcode,
+ payloadLength));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded(
+ finBit, rsvBit1, rsvBit2, rsvBit3, opcode, maskBit, mask, payload,
+ payloadLength);
+
+ if (opcode == nsIWebSocketFrame::OPCODE_CLOSE) {
+ LOG(("WebSocketChannel:: close received\n"));
+ mServerClosed = true;
+
+ mServerCloseCode = CLOSE_NO_STATUS;
+ if (payloadLength >= 2) {
+ mServerCloseCode = NetworkEndian::readUint16(payload);
+ LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
+ uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
+ if (msglen > 0) {
+ mServerCloseReason.SetLength(msglen);
+ memcpy(mServerCloseReason.BeginWriting(), (const char*)payload + 2,
+ msglen);
+
+ // section 8.1 says to replace received non utf-8 sequences
+ // (which are non-conformant to send) with u+fffd,
+ // but secteam feels that silently rewriting messages is
+ // inappropriate - so we will fail the connection instead.
+ if (!IsUtf8(mServerCloseReason)) {
+ LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ LOG(("WebSocketChannel:: close msg %s\n",
+ mServerCloseReason.get()));
+ }
+ }
+
+ if (mCloseTimer) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ }
+
+ if (frame) {
+ // We send the frame immediately becuase we want to have it dispatched
+ // before the CallOnServerClose.
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ frame = nullptr;
+ }
+
+ if (mListenerMT) {
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(new CallOnServerClose(this, mServerCloseCode,
+ mServerCloseReason),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (mClientClosed) ReleaseSession();
+ } else if (opcode == nsIWebSocketFrame::OPCODE_PING) {
+ LOG(("WebSocketChannel:: ping received\n"));
+ GeneratePong(payload, payloadLength);
+ } else if (opcode == nsIWebSocketFrame::OPCODE_PONG) {
+ // opcode OPCODE_PONG: the mere act of receiving the packet is all we
+ // need to do for the pong to trigger the activity timers
+ LOG(("WebSocketChannel:: pong received\n"));
+ } else {
+ /* unknown control frame opcode */
+ LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mFragmentAccumulator) {
+ // Remove the control frame from the stream so we have a contiguous
+ // data buffer of reassembled fragments
+ LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
+ MOZ_ASSERT(mFramePtr + framingLength == payload,
+ "payload offset from frameptr wrong");
+ ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
+ payload = mFramePtr;
+ avail -= payloadLength;
+ if (mBuffered) mBuffered -= framingLength + payloadLength;
+ payloadLength = 0;
+ }
+
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+ } else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) {
+ if (mListenerMT) {
+ nsCString binaryData;
+ {
+ MutexAutoLock lock(mCompressorMutex);
+ bool isDeflated =
+ mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %sbinary frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(
+ ("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%zd]\n",
+ payloadLength, binaryData.Length()));
+ } else {
+ if (!binaryData.Assign((const char*)payload, payloadLength,
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ RefPtr<WebSocketFrame> frame =
+ mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
+ opcode, maskBit, mask, binaryData);
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(
+ new CallOnMessageAvailable(this, binaryData, binaryData.Length()),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // To add the header to 'Networking Dashboard' log
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
+ LOG(("Added new received msg for %s", mHost.get()));
+ }
+ }
+ } else if (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ /* unknown opcode */
+ LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mFramePtr = payload + payloadLength;
+ avail -= payloadLength;
+ totalAvail = avail;
+ }
+
+ // Adjust the stateful buffer. If we were operating off the stack and
+ // now have a partial message then transition to the buffer, or if
+ // we were working off the buffer but no longer have any active state
+ // then transition to the stack
+ if (!IsPersistentFramePtr()) {
+ mBuffered = 0;
+
+ if (mFragmentAccumulator) {
+ LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
+
+ if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
+ totalAvail + mFragmentAccumulator, 0, nullptr)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ // UpdateReadBuffer will reset the frameptr to the beginning
+ // of new saved state, so we need to skip past processed framgents
+ mFramePtr += mFragmentAccumulator;
+ } else if (totalAvail) {
+ LOG(("WebSocketChannel:: Setup Buffer due to partial frame"));
+ if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+ } else if (!mFragmentAccumulator && !totalAvail) {
+ // If we were working off a saved buffer state and there is no partial
+ // frame or fragment in process, then revert to stack behavior
+ LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
+ mBuffered = 0;
+
+ // release memory if we've been processing a large message
+ if (mBufferSize > kIncomingBufferStableSize) {
+ mBufferSize = kIncomingBufferStableSize;
+ free(mBuffer);
+ mBuffer = (uint8_t*)moz_xmalloc(mBufferSize);
+ }
+ }
+ return NS_OK;
+}
+
+/* static */
+void WebSocketChannel::ApplyMask(uint32_t mask, uint8_t* data, uint64_t len) {
+ if (!data || len == 0) return;
+
+ // Optimally we want to apply the mask 32 bits at a time,
+ // but the buffer might not be alligned. So we first deal with
+ // 0 to 3 bytes of preamble individually
+
+ while (len && (reinterpret_cast<uintptr_t>(data) & 3)) {
+ *data ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ data++;
+ len--;
+ }
+
+ // perform mask on full words of data
+
+ uint32_t* iData = (uint32_t*)data;
+ uint32_t* end = iData + (len / 4);
+ NetworkEndian::writeUint32(&mask, mask);
+ for (; iData < end; iData++) *iData ^= mask;
+ mask = NetworkEndian::readUint32(&mask);
+ data = (uint8_t*)iData;
+ len = len % 4;
+
+ // There maybe up to 3 trailing bytes that need to be dealt with
+ // individually
+
+ while (len) {
+ *data ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ data++;
+ len--;
+ }
+}
+
+void WebSocketChannel::GeneratePing() {
+ nsAutoCString buf;
+ buf.AssignLiteral("PING");
+ EnqueueOutgoingMessage(mOutgoingPingMessages,
+ new OutboundMessage(kMsgTypePing, buf));
+}
+
+void WebSocketChannel::GeneratePong(uint8_t* payload, uint32_t len) {
+ nsAutoCString buf;
+ buf.SetLength(len);
+ if (buf.Length() < len) {
+ LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
+ return;
+ }
+
+ memcpy(buf.BeginWriting(), payload, len);
+ EnqueueOutgoingMessage(mOutgoingPongMessages,
+ new OutboundMessage(kMsgTypePong, buf));
+}
+
+void WebSocketChannel::EnqueueOutgoingMessage(nsDeque<OutboundMessage>& aQueue,
+ OutboundMessage* aMsg) {
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ LOG(
+ ("WebSocketChannel::EnqueueOutgoingMessage %p "
+ "queueing msg %p [type=%s len=%d]\n",
+ this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
+
+ aQueue.Push(aMsg);
+ if (mSocketOut) {
+ OnOutputStreamReady(mSocketOut);
+ } else {
+ DoEnqueueOutgoingMessage();
+ }
+}
+
+uint16_t WebSocketChannel::ResultToCloseCode(nsresult resultCode) {
+ if (NS_SUCCEEDED(resultCode)) return CLOSE_NORMAL;
+
+ switch (resultCode) {
+ case NS_ERROR_FILE_TOO_BIG:
+ case NS_ERROR_OUT_OF_MEMORY:
+ return CLOSE_TOO_LARGE;
+ case NS_ERROR_CANNOT_CONVERT_DATA:
+ return CLOSE_INVALID_PAYLOAD;
+ case NS_ERROR_UNEXPECTED:
+ return CLOSE_INTERNAL_ERROR;
+ default:
+ return CLOSE_PROTOCOL_ERROR;
+ }
+}
+
+void WebSocketChannel::PrimeNewOutgoingMessage() {
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+ MOZ_ASSERT(!mCurrentOut, "Current message in progress");
+
+ nsresult rv = NS_OK;
+
+ mCurrentOut = mOutgoingPongMessages.PopFront();
+ if (mCurrentOut) {
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePong, "Not pong message!");
+ } else {
+ mCurrentOut = mOutgoingPingMessages.PopFront();
+ if (mCurrentOut) {
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePing,
+ "Not ping message!");
+ } else {
+ mCurrentOut = mOutgoingMessages.PopFront();
+ }
+ }
+
+ if (!mCurrentOut) return;
+
+ auto cleanupAfterFailure =
+ MakeScopeExit([&] { DeleteCurrentOutGoingMessage(); });
+
+ WsMsgType msgType = mCurrentOut->GetMsgType();
+
+ LOG(
+ ("WebSocketChannel::PrimeNewOutgoingMessage "
+ "%p found queued msg %p [type=%s len=%d]\n",
+ this, mCurrentOut, msgNames[msgType], mCurrentOut->Length()));
+
+ mCurrentOutSent = 0;
+ mHdrOut = mOutHeader;
+
+ uint8_t maskBit = mIsServerSide ? 0 : kMaskBit;
+ uint8_t maskSize = mIsServerSide ? 0 : 4;
+
+ uint8_t* payload = nullptr;
+
+ if (msgType == kMsgTypeFin) {
+ // This is a demand to create a close message
+ if (mClientClosed) {
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ cleanupAfterFailure.release();
+ return;
+ }
+
+ mClientClosed = true;
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
+ mOutHeader[1] = maskBit;
+
+ // payload is offset 2 plus size of the mask
+ payload = mOutHeader + 2 + maskSize;
+
+ // The close reason code sits in the first 2 bytes of payload
+ // If the channel user provided a code and reason during Close()
+ // and there isn't an internal error, use that.
+ if (NS_SUCCEEDED(mStopOnClose)) {
+ MutexAutoLock lock(mMutex);
+ if (mScriptCloseCode) {
+ NetworkEndian::writeUint16(payload, mScriptCloseCode);
+ mOutHeader[1] += 2;
+ mHdrOutToSend = 4 + maskSize;
+ if (!mScriptCloseReason.IsEmpty()) {
+ MOZ_ASSERT(mScriptCloseReason.Length() <= 123,
+ "Close Reason Too Long");
+ mOutHeader[1] += mScriptCloseReason.Length();
+ mHdrOutToSend += mScriptCloseReason.Length();
+ memcpy(payload + 2, mScriptCloseReason.BeginReading(),
+ mScriptCloseReason.Length());
+ }
+ } else {
+ // No close code/reason, so payload length = 0. We must still send mask
+ // even though it's not used. Keep payload offset so we write mask
+ // below.
+ mHdrOutToSend = 2 + maskSize;
+ }
+ } else {
+ NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
+ mOutHeader[1] += 2;
+ mHdrOutToSend = 4 + maskSize;
+ }
+
+ if (mServerClosed) {
+ /* bidi close complete */
+ mReleaseOnTransmit = 1;
+ } else if (NS_FAILED(mStopOnClose)) {
+ /* result of abort session - give up */
+ StopSession(mStopOnClose);
+ } else {
+ /* wait for reciprocal close from server */
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mCloseTimer), this,
+ mCloseTimeout, nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ StopSession(rv);
+ }
+ }
+ } else {
+ switch (msgType) {
+ case kMsgTypePong:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PONG;
+ break;
+ case kMsgTypePing:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PING;
+ break;
+ case kMsgTypeString:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_TEXT;
+ break;
+ case kMsgTypeStream:
+ // HACK ALERT: read in entire stream into string.
+ // Will block socket transport thread if file is blocking.
+ // TODO: bug 704447: don't block socket thread!
+ rv = mCurrentOut->ConvertStreamToString();
+ if (NS_FAILED(rv)) {
+ AbortSession(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ // Now we're a binary string
+ msgType = kMsgTypeBinaryString;
+
+ // no break: fall down into binary string case
+ [[fallthrough]];
+
+ case kMsgTypeBinaryString:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_BINARY;
+ break;
+ case kMsgTypeFin:
+ MOZ_ASSERT(false, "unreachable"); // avoid compiler warning
+ break;
+ }
+
+ // deflate the payload if PMCE is negotiated
+ MutexAutoLock lock(mCompressorMutex);
+ if (mPMCECompressor &&
+ (msgType == kMsgTypeString || msgType == kMsgTypeBinaryString)) {
+ if (mCurrentOut->DeflatePayload(mPMCECompressor.get())) {
+ // The payload was deflated successfully, set RSV1 bit
+ mOutHeader[0] |= kRsv1Bit;
+
+ LOG(
+ ("WebSocketChannel::PrimeNewOutgoingMessage %p current msg %p was "
+ "deflated [origLength=%d, newLength=%d].\n",
+ this, mCurrentOut, mCurrentOut->OrigLength(),
+ mCurrentOut->Length()));
+ }
+ }
+
+ if (mCurrentOut->Length() < 126) {
+ mOutHeader[1] = mCurrentOut->Length() | maskBit;
+ mHdrOutToSend = 2 + maskSize;
+ } else if (mCurrentOut->Length() <= 0xffff) {
+ mOutHeader[1] = 126 | maskBit;
+ NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
+ mCurrentOut->Length());
+ mHdrOutToSend = 4 + maskSize;
+ } else {
+ mOutHeader[1] = 127 | maskBit;
+ NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
+ mHdrOutToSend = 10 + maskSize;
+ }
+ payload = mOutHeader + mHdrOutToSend;
+ }
+
+ MOZ_ASSERT(payload, "payload offset not found");
+
+ uint32_t mask = 0;
+ if (!mIsServerSide) {
+ // Perform the sending mask. Never use a zero mask
+ do {
+ static_assert(4 == sizeof(mask), "Size of the mask should be equal to 4");
+ nsresult rv = mRandomGenerator->GenerateRandomBytesInto(mask);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::PrimeNewOutgoingMessage(): "
+ "GenerateRandomBytes failure %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ AbortSession(rv);
+ return;
+ }
+ } while (!mask);
+ NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
+ }
+
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
+
+ // We don't mask the framing, but occasionally we stick a little payload
+ // data in the buffer used for the framing. Close frames are the current
+ // example. This data needs to be masked, but it is never more than a
+ // handful of bytes and might rotate the mask, so we can just do it locally.
+ // For real data frames we ship the bulk of the payload off to ApplyMask()
+
+ RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded(
+ mOutHeader[0] & WebSocketChannel::kFinalFragBit,
+ mOutHeader[0] & WebSocketChannel::kRsv1Bit,
+ mOutHeader[0] & WebSocketChannel::kRsv2Bit,
+ mOutHeader[0] & WebSocketChannel::kRsv3Bit,
+ mOutHeader[0] & WebSocketChannel::kOpcodeBitsMask,
+ mOutHeader[1] & WebSocketChannel::kMaskBit, mask, payload,
+ mHdrOutToSend - (payload - mOutHeader), mCurrentOut->BeginOrigReading(),
+ mCurrentOut->OrigLength());
+
+ if (frame) {
+ mService->FrameSent(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ if (mask) {
+ while (payload < (mOutHeader + mHdrOutToSend)) {
+ *payload ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ payload++;
+ }
+
+ // Mask the real message payloads
+ ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
+ }
+
+ int32_t len = mCurrentOut->Length();
+
+ // for small frames, copy it all together for a contiguous write
+ if (len && len <= kCopyBreak) {
+ memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
+ mHdrOutToSend += len;
+ mCurrentOutSent = len;
+ }
+
+ // Transmitting begins - mHdrOutToSend bytes from mOutHeader and
+ // mCurrentOut->Length() bytes from mCurrentOut. The latter may be
+ // coaleseced into the former for small messages or as the result of the
+ // compression process.
+
+ cleanupAfterFailure.release();
+}
+
+void WebSocketChannel::DeleteCurrentOutGoingMessage() {
+ delete mCurrentOut;
+ mCurrentOut = nullptr;
+ mCurrentOutSent = 0;
+}
+
+void WebSocketChannel::EnsureHdrOut(uint32_t size) {
+ LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
+
+ if (mDynamicOutputSize < size) {
+ mDynamicOutputSize = size;
+ mDynamicOutput = (uint8_t*)moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
+ }
+
+ mHdrOut = mDynamicOutput;
+}
+
+namespace {
+
+class RemoveObserverRunnable : public Runnable {
+ RefPtr<WebSocketChannel> mChannel;
+
+ public:
+ explicit RemoveObserverRunnable(WebSocketChannel* aChannel)
+ : Runnable("net::RemoveObserverRunnable"), mChannel(aChannel) {}
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ NS_WARNING("failed to get observer service");
+ return NS_OK;
+ }
+
+ observerService->RemoveObserver(mChannel, NS_NETWORK_LINK_TOPIC);
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+void WebSocketChannel::CleanupConnection() {
+ // normally this should be called on socket thread, but it may be called
+ // on MainThread
+
+ LOG(("WebSocketChannel::CleanupConnection() %p", this));
+ // This needs to run on the IOThread so we don't need to lock a bunch of these
+ if (!mIOThread->IsOnCurrentThread()) {
+ mIOThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::CleanupConnection", this,
+ &WebSocketChannel::CleanupConnection),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mLingeringCloseTimer) {
+ mLingeringCloseTimer->Cancel();
+ mLingeringCloseTimer = nullptr;
+ }
+
+ if (mSocketIn) {
+ if (mDataStarted) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mSocketIn = nullptr;
+ }
+
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOut = nullptr;
+ }
+
+ if (mTransport) {
+ mTransport->SetSecurityCallbacks(nullptr);
+ mTransport->SetEventSink(nullptr, nullptr);
+ mTransport->Close(NS_BASE_STREAM_CLOSED);
+ mTransport = nullptr;
+ }
+
+ if (mConnection) {
+ mConnection->Close();
+ mConnection = nullptr;
+ }
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->RemoveHost(mHost, mSerial);
+ }
+
+ // The observer has to be removed on the main-thread.
+ NS_DispatchToMainThread(new RemoveObserverRunnable(this));
+
+ DecrementSessionCount();
+}
+
+void WebSocketChannel::StopSession(nsresult reason) {
+ LOG(("WebSocketChannel::StopSession() %p [%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mStopped) {
+ return;
+ }
+ mStopped = true;
+ }
+
+ DoStopSession(reason);
+}
+
+void WebSocketChannel::DoStopSession(nsresult reason) {
+ LOG(("WebSocketChannel::DoStopSession() %p [%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ // normally this should be called on socket thread, but it is ok to call it
+ // from OnStartRequest before the socket thread machine has gotten underway.
+ // If mDataStarted is false, this is called on MainThread for Close().
+ // Otherwise it should be called on the IO thread
+
+ MOZ_ASSERT(mStopped);
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread() || mTCPClosed || !mDataStarted);
+
+ if (!mOpenedHttpChannel) {
+ // The HTTP channel information will never be used in this case
+ NS_ReleaseOnMainThread("WebSocketChannel::mChannel", mChannel.forget());
+ NS_ReleaseOnMainThread("WebSocketChannel::mHttpChannel",
+ mHttpChannel.forget());
+ NS_ReleaseOnMainThread("WebSocketChannel::mLoadGroup", mLoadGroup.forget());
+ NS_ReleaseOnMainThread("WebSocketChannel::mCallbacks", mCallbacks.forget());
+ }
+
+ if (mCloseTimer) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ }
+
+ // mOpenTimer must be null if mDataStarted is true and we're not on MainThread
+ if (mOpenTimer) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mReconnectDelayTimer) {
+ mReconnectDelayTimer->Cancel();
+ NS_ReleaseOnMainThread("WebSocketChannel::mMutex",
+ mReconnectDelayTimer.forget());
+ }
+ }
+
+ if (mPingTimer) {
+ mPingTimer->Cancel();
+ mPingTimer = nullptr;
+ }
+
+ if (!mTCPClosed && mDataStarted) {
+ if (mSocketIn) {
+ // Drain, within reason, this socket. if we leave any data
+ // unconsumed (including the tcp fin) a RST will be generated
+ // The right thing to do here is shutdown(SHUT_WR) and then wait
+ // a little while to see if any data comes in.. but there is no
+ // reason to delay things for that when the websocket handshake
+ // is supposed to guarantee a quiet connection except for that fin.
+
+ char buffer[512];
+ uint32_t count = 0;
+ uint32_t total = 0;
+ nsresult rv;
+ do {
+ total += count;
+ rv = mSocketIn->Read(buffer, 512, &count);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && (NS_FAILED(rv) || count == 0)) {
+ mTCPClosed = true;
+ }
+ } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
+ } else if (mConnection) {
+ mConnection->DrainSocketData();
+ }
+ }
+
+ int32_t sessionCount = kLingeringCloseThreshold;
+ nsWSAdmissionManager::GetSessionCount(sessionCount);
+
+ if (!mTCPClosed && (mTransport || mConnection) &&
+ sessionCount < kLingeringCloseThreshold) {
+ // 7.1.1 says that the client SHOULD wait for the server to close the TCP
+ // connection. This is so we can reuse port numbers before 2 MSL expires,
+ // which is not really as much of a concern for us as the amount of state
+ // that might be accrued by keeping this channel object around waiting for
+ // the server. We handle the SHOULD by waiting a short time in the common
+ // case, but not waiting in the case of high concurrency.
+ //
+ // Normally this will be taken care of in AbortSession() after mTCPClosed
+ // is set when the server close arrives without waiting for the timeout to
+ // expire.
+
+ LOG(("WebSocketChannel::DoStopSession: Wait for Server TCP close"));
+
+ nsresult rv;
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mLingeringCloseTimer), this,
+ kLingeringCloseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) CleanupConnection();
+ } else {
+ CleanupConnection();
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mCancelable) {
+ mCancelable->Cancel(NS_ERROR_UNEXPECTED);
+ mCancelable = nullptr;
+ }
+ }
+
+ {
+ MutexAutoLock lock(mCompressorMutex);
+ mPMCECompressor = nullptr;
+ }
+ if (!mCalledOnStop) {
+ mCalledOnStop = true;
+
+ nsWSAdmissionManager::OnStopSession(this, reason);
+
+ RefPtr<CallOnStop> runnable = new CallOnStop(this, reason);
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ }
+ }
+}
+
+// Called from MainThread, and called from IOThread in
+// PrimeNewOutgoingMessage
+void WebSocketChannel::AbortSession(nsresult reason) {
+ LOG(("WebSocketChannel::AbortSession() %p [reason %" PRIx32
+ "] stopped = %d\n",
+ this, static_cast<uint32_t>(reason), !!mStopped));
+
+ MOZ_ASSERT(NS_FAILED(reason), "reason must be a failure!");
+
+ // normally this should be called on socket thread, but it is ok to call it
+ // from the main thread before StartWebsocketData() has completed
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread() || !mDataStarted);
+
+ // When we are failing we need to close the TCP connection immediately
+ // as per 7.1.1
+ mTCPClosed = true;
+
+ if (mLingeringCloseTimer) {
+ MOZ_ASSERT(mStopped, "Lingering without Stop");
+ LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
+ CleanupConnection();
+ return;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mStopped) {
+ return;
+ }
+
+ if ((mTransport || mConnection) && reason != NS_BASE_STREAM_CLOSED &&
+ !mRequestedClose && !mClientClosed && !mServerClosed && mDataStarted) {
+ mRequestedClose = true;
+ mStopOnClose = reason;
+ mIOThread->Dispatch(
+ new OutboundEnqueuer(this,
+ new OutboundMessage(kMsgTypeFin, VoidCString())),
+ nsIEventTarget::DISPATCH_NORMAL);
+ return;
+ }
+
+ mStopped = true;
+ }
+
+ DoStopSession(reason);
+}
+
+// ReleaseSession is called on orderly shutdown
+void WebSocketChannel::ReleaseSession() {
+ LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n", this,
+ !!mStopped));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ StopSession(NS_OK);
+}
+
+void WebSocketChannel::IncrementSessionCount() {
+ if (!mIncrementedSessionCount) {
+ nsWSAdmissionManager::IncrementSessionCount();
+ mIncrementedSessionCount = true;
+ }
+}
+
+void WebSocketChannel::DecrementSessionCount() {
+ // Make sure we decrement session count only once, and only if we incremented
+ // it. This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount
+ // is atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at
+ // times when they'll never be a race condition for checking/setting them.
+ if (mIncrementedSessionCount && !mDecrementedSessionCount) {
+ nsWSAdmissionManager::DecrementSessionCount();
+ mDecrementedSessionCount = true;
+ }
+}
+
+namespace {
+enum ExtensionParseMode { eParseServerSide, eParseClientSide };
+}
+
+static nsresult ParseWebSocketExtension(const nsACString& aExtension,
+ ExtensionParseMode aMode,
+ bool& aClientNoContextTakeover,
+ bool& aServerNoContextTakeover,
+ int32_t& aClientMaxWindowBits,
+ int32_t& aServerMaxWindowBits) {
+ nsCCharSeparatedTokenizer tokens(aExtension, ';');
+
+ if (!tokens.hasMoreTokens() ||
+ !tokens.nextToken().EqualsLiteral("permessage-deflate")) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: "
+ "HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n",
+ PromiseFlatCString(aExtension).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ aClientNoContextTakeover = aServerNoContextTakeover = false;
+ aClientMaxWindowBits = aServerMaxWindowBits = -1;
+
+ while (tokens.hasMoreTokens()) {
+ auto token = tokens.nextToken();
+
+ int32_t nameEnd, valueStart;
+ int32_t delimPos = token.FindChar('=');
+ if (delimPos == kNotFound) {
+ nameEnd = token.Length();
+ valueStart = token.Length();
+ } else {
+ nameEnd = delimPos;
+ valueStart = delimPos + 1;
+ }
+
+ auto paramName = Substring(token, 0, nameEnd);
+ auto paramValue = Substring(token, valueStart);
+
+ if (paramName.EqualsLiteral("client_no_context_takeover")) {
+ if (!paramValue.IsEmpty()) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: parameter "
+ "client_no_context_takeover must not have value, found %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aClientNoContextTakeover) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters client_no_context_takeover\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aClientNoContextTakeover = true;
+ } else if (paramName.EqualsLiteral("server_no_context_takeover")) {
+ if (!paramValue.IsEmpty()) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: parameter "
+ "server_no_context_takeover must not have value, found %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aServerNoContextTakeover) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters server_no_context_takeover\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aServerNoContextTakeover = true;
+ } else if (paramName.EqualsLiteral("client_max_window_bits")) {
+ if (aClientMaxWindowBits != -1) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters client_max_window_bits\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (aMode == eParseServerSide && paramValue.IsEmpty()) {
+ // Use -2 to indicate that "client_max_window_bits" has been parsed,
+ // but had no value.
+ aClientMaxWindowBits = -2;
+ } else {
+ nsresult errcode;
+ aClientMaxWindowBits =
+ PromiseFlatCString(paramValue).ToInteger(&errcode);
+ if (NS_FAILED(errcode) || aClientMaxWindowBits < 8 ||
+ aClientMaxWindowBits > 15) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found invalid "
+ "parameter client_max_window_bits %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ } else if (paramName.EqualsLiteral("server_max_window_bits")) {
+ if (aServerMaxWindowBits != -1) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters server_max_window_bits\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult errcode;
+ aServerMaxWindowBits = PromiseFlatCString(paramValue).ToInteger(&errcode);
+ if (NS_FAILED(errcode) || aServerMaxWindowBits < 8 ||
+ aServerMaxWindowBits > 15) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found invalid "
+ "parameter server_max_window_bits %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ } else {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found unknown "
+ "parameter %s\n",
+ PromiseFlatCString(paramName).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ if (aClientMaxWindowBits == -2) {
+ aClientMaxWindowBits = -1;
+ }
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::HandleExtensions() {
+ LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
+
+ nsresult rv;
+ nsAutoCString extensions;
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Extensions"_ns,
+ extensions);
+ extensions.CompressWhitespace();
+ if (extensions.IsEmpty()) {
+ return NS_OK;
+ }
+
+ LOG(
+ ("WebSocketChannel::HandleExtensions: received "
+ "Sec-WebSocket-Extensions header: %s\n",
+ extensions.get()));
+
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ rv = ParseWebSocketExtension(extensions, eParseClientSide,
+ clientNoContextTakeover, serverNoContextTakeover,
+ clientMaxWindowBits, serverMaxWindowBits);
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+
+ if (!mAllowPMCE) {
+ LOG(
+ ("WebSocketChannel::HandleExtensions: "
+ "Recvd permessage-deflate which wasn't offered\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (clientMaxWindowBits == -1) {
+ clientMaxWindowBits = 15;
+ }
+ if (serverMaxWindowBits == -1) {
+ serverMaxWindowBits = 15;
+ }
+
+ MutexAutoLock lock(mCompressorMutex);
+ mPMCECompressor = MakeUnique<PMCECompression>(
+ clientNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits);
+ if (mPMCECompressor->Active()) {
+ LOG(
+ ("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing "
+ "context takeover, clientMaxWindowBits=%d, "
+ "serverMaxWindowBits=%d\n",
+ clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits,
+ serverMaxWindowBits));
+
+ mNegotiatedExtensions = "permessage-deflate";
+ } else {
+ LOG(
+ ("WebSocketChannel::HandleExtensions: Cannot init PMCE "
+ "compression object\n"));
+ mPMCECompressor = nullptr;
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void ProcessServerWebSocketExtensions(const nsACString& aExtensions,
+ nsACString& aNegotiatedExtensions) {
+ aNegotiatedExtensions.Truncate();
+
+ nsCOMPtr<nsIPrefBranch> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefService) {
+ bool boolpref;
+ nsresult rv = prefService->GetBoolPref(
+ "network.websocket.extensions.permessage-deflate", &boolpref);
+ if (NS_SUCCEEDED(rv) && !boolpref) {
+ return;
+ }
+ }
+
+ for (const auto& ext :
+ nsCCharSeparatedTokenizer(aExtensions, ',').ToRange()) {
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ nsresult rv = ParseWebSocketExtension(
+ ext, eParseServerSide, clientNoContextTakeover, serverNoContextTakeover,
+ clientMaxWindowBits, serverMaxWindowBits);
+ if (NS_FAILED(rv)) {
+ // Ignore extensions that we can't parse
+ continue;
+ }
+
+ aNegotiatedExtensions.AssignLiteral("permessage-deflate");
+ if (clientNoContextTakeover) {
+ aNegotiatedExtensions.AppendLiteral(";client_no_context_takeover");
+ }
+ if (serverNoContextTakeover) {
+ aNegotiatedExtensions.AppendLiteral(";server_no_context_takeover");
+ }
+ if (clientMaxWindowBits != -1) {
+ aNegotiatedExtensions.AppendLiteral(";client_max_window_bits=");
+ aNegotiatedExtensions.AppendInt(clientMaxWindowBits);
+ }
+ if (serverMaxWindowBits != -1) {
+ aNegotiatedExtensions.AppendLiteral(";server_max_window_bits=");
+ aNegotiatedExtensions.AppendInt(serverMaxWindowBits);
+ }
+
+ return;
+ }
+}
+
+nsresult CalculateWebSocketHashedSecret(const nsACString& aKey,
+ nsACString& aHash) {
+ nsresult rv;
+ nsCString key = aKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"_ns;
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Update((const uint8_t*)key.BeginWriting(), key.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hasher->Finish(true, aHash);
+}
+
+nsresult WebSocketChannel::SetupRequest() {
+ LOG(("WebSocketChannel::SetupRequest() %p\n", this));
+
+ nsresult rv;
+
+ if (mLoadGroup) {
+ rv = mHttpChannel->SetLoadGroup(mLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mHttpChannel->SetLoadFlags(
+ nsIRequest::LOAD_BACKGROUND | nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we never let websockets be blocked by head CSS/JS loads to avoid
+ // potential deadlock where server generation of CSS/JS requires
+ // an XHR signal.
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+
+ // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket
+ // in lower case, so go with that. It is technically case insensitive.
+ rv = mChannel->HTTPUpgrade("websocket"_ns, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Version"_ns,
+ nsLiteralCString(SEC_WEBSOCKET_VERSION),
+ false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!mOrigin.IsEmpty()) {
+ rv = mHttpChannel->SetRequestHeader("Origin"_ns, mOrigin, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (!mProtocol.IsEmpty()) {
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Protocol"_ns, mProtocol,
+ true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (mAllowPMCE) {
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Extensions"_ns,
+ "permessage-deflate"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ uint8_t* secKey;
+ nsAutoCString secKeyString;
+
+ rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = Base64Encode(reinterpret_cast<const char*>(secKey), 16, secKeyString);
+ free(secKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Key"_ns, secKeyString,
+ false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
+
+ // prepare the value we expect to see in
+ // the sec-websocket-accept response header
+ rv = CalculateWebSocketHashedSecret(secKeyString, mHashedSecret);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
+ mHashedSecret.get()));
+
+ mHttpChannelId = mHttpChannel->ChannelId();
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::DoAdmissionDNS() {
+ nsresult rv;
+
+ nsCString hostName;
+ rv = mURI->GetHost(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mAddress = hostName;
+ nsCString path;
+ rv = mURI->GetFilePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPath = path;
+ rv = mURI->GetPort(&mPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mPort == -1) mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget();
+ nsCOMPtr<nsICancelable> cancelable;
+ rv = dns->AsyncResolveNative(hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr,
+ this, main, mLoadInfo->GetOriginAttributes(),
+ getter_AddRefs(cancelable));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mCancelable);
+ mCancelable = std::move(cancelable);
+ return rv;
+}
+
+nsresult WebSocketChannel::ApplyForAdmission() {
+ LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
+
+ // Websockets has a policy of 1 session at a time being allowed in the
+ // CONNECTING state per server IP address (not hostname)
+
+ // Check to see if a proxy is being used before making DNS call
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+
+ if (!pps) {
+ // go straight to DNS
+ // expect the callback in ::OnLookupComplete
+ LOG((
+ "WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
+ return DoAdmissionDNS();
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsICancelable> cancelable;
+ rv = pps->AsyncResolve(
+ mHttpChannel,
+ nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY |
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ this, nullptr, getter_AddRefs(cancelable));
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mCancelable);
+ mCancelable = std::move(cancelable);
+ return rv;
+}
+
+// Called after both OnStartRequest and OnTransportAvailable have
+// executed. This essentially ends the handshake and starts the websockets
+// protocol state machine.
+nsresult WebSocketChannel::CallStartWebsocketData() {
+ LOG(("WebSocketChannel::CallStartWebsocketData() %p", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ if (mOpenTimer) {
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = GetTargetThread();
+ if (target && !target->IsOnCurrentThread()) {
+ return target->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::StartWebsocketData", this,
+ &WebSocketChannel::StartWebsocketData),
+ NS_DISPATCH_NORMAL);
+ }
+
+ return StartWebsocketData();
+}
+
+nsresult WebSocketChannel::StartWebsocketData() {
+ {
+ MutexAutoLock lock(mMutex);
+ LOG(("WebSocketChannel::StartWebsocketData() %p", this));
+ MOZ_ASSERT(!mDataStarted, "StartWebsocketData twice");
+
+ if (mStopped) {
+ LOG(
+ ("WebSocketChannel::StartWebsocketData channel already closed, not "
+ "starting data"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ RefPtr<WebSocketChannel> self = this;
+ mIOThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketChannel::StartWebsocketData", [self{std::move(self)}] {
+ LOG(("WebSocketChannel::DoStartWebsocketData() %p", self.get()));
+
+ NS_DispatchToMainThread(
+ NewRunnableMethod("net::WebSocketChannel::NotifyOnStart", self,
+ &WebSocketChannel::NotifyOnStart),
+ NS_DISPATCH_NORMAL);
+
+ nsresult rv = self->mConnection ? self->mConnection->StartReading()
+ : self->mSocketIn->AsyncWait(
+ self, 0, 0, self->mIOThread);
+ if (NS_FAILED(rv)) {
+ self->AbortSession(rv);
+ }
+
+ if (self->mPingInterval) {
+ rv = self->StartPinging();
+ if (NS_FAILED(rv)) {
+ LOG((
+ "WebSocketChannel::StartWebsocketData Could not start pinging, "
+ "rv=0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ self->AbortSession(rv);
+ }
+ }
+ }));
+
+ return NS_OK;
+}
+
+void WebSocketChannel::NotifyOnStart() {
+ LOG(("WebSocketChannel::NotifyOnStart Notifying Listener %p",
+ mListenerMT ? mListenerMT->mListener.get() : nullptr));
+ mDataStarted = true;
+ if (mListenerMT) {
+ nsresult rv = mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::NotifyOnStart "
+ "mListenerMT->mListener->OnStart() failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+nsresult WebSocketChannel::StartPinging() {
+ LOG(("WebSocketChannel::StartPinging() %p", this));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+ MOZ_ASSERT(mPingInterval);
+ MOZ_ASSERT(!mPingTimer);
+
+ nsresult rv;
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mPingTimer), this, mPingInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
+ (uint32_t)mPingInterval));
+ } else {
+ NS_WARNING("unable to create ping timer. Carrying on.");
+ }
+
+ return NS_OK;
+}
+
+void WebSocketChannel::ReportConnectionTelemetry(nsresult aStatusCode) {
+ // 3 bits are used. high bit is for wss, middle bit for failed,
+ // and low bit for proxy..
+ // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy,
+ // wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy
+
+ bool didProxy = false;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel);
+ if (pc) pc->GetProxyInfo(getter_AddRefs(pi));
+ if (pi) {
+ nsAutoCString proxyType;
+ pi->GetType(proxyType);
+ if (!proxyType.IsEmpty() && !proxyType.EqualsLiteral("direct")) {
+ didProxy = true;
+ }
+ }
+
+ uint8_t value =
+ (mEncrypted ? (1 << 2) : 0) |
+ (!(mGotUpgradeOK && NS_SUCCEEDED(aStatusCode)) ? (1 << 1) : 0) |
+ (didProxy ? (1 << 0) : 0);
+
+ LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value));
+ Telemetry::Accumulate(Telemetry::WEBSOCKETS_HANDSHAKE_TYPE, value);
+}
+
+// nsIDNSListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord, nsresult aStatus) {
+ LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %" PRIx32 "]\n", this,
+ aRequest, aRecord, static_cast<uint32_t>(aStatus)));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ {
+ MutexAutoLock lock(mMutex);
+ mCancelable = nullptr;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
+ return NS_OK;
+ }
+
+ // These failures are not fatal - we just use the hostname as the key
+ if (NS_FAILED(aStatus)) {
+ LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
+
+ // set host in case we got here without calling DoAdmissionDNS()
+ mURI->GetHost(mAddress);
+ } else {
+ nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord);
+ MOZ_ASSERT(record);
+ nsresult rv = record->GetNextAddrAsString(mAddress);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
+ }
+ }
+
+ LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
+ nsWSAdmissionManager::ConditionallyConnect(this);
+
+ return NS_OK;
+}
+
+// nsIProtocolProxyCallback
+NS_IMETHODIMP
+WebSocketChannel::OnProxyAvailable(nsICancelable* aRequest,
+ nsIChannel* aChannel, nsIProxyInfo* pi,
+ nsresult status) {
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mCancelable || (aRequest == mCancelable));
+ mCancelable = nullptr;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n",
+ this));
+ return NS_OK;
+ }
+
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n",
+ this));
+ // call DNS callback directly without DNS resolver
+ OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
+ } else {
+ LOG(("WebSocketChannel::OnProxyAvailable[%p] checking DNS resolution\n",
+ this));
+ nsresult rv = DoAdmissionDNS();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocket OnProxyAvailable [%p] DNS lookup failed\n", this));
+ // call DNS callback directly without DNS resolver
+ OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
+ }
+ }
+
+ // notify listener of OnProxyAvailable
+ LOG(("WebSocketChannel::OnProxyAvailable Notifying Listener %p",
+ mListenerMT ? mListenerMT->mListener.get() : nullptr));
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyCallback> ppc(
+ do_QueryInterface(mListenerMT->mListener, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = ppc->OnProxyAvailable(aRequest, aChannel, pi, status);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnProxyAvailable notify"
+ " failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+WebSocketChannel::GetInterface(const nsIID& iid, void** result) {
+ LOG(("WebSocketChannel::GetInterface() %p\n", this));
+
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ return QueryInterface(iid, result);
+ }
+
+ if (mCallbacks) return mCallbacks->GetInterface(iid, result);
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> newuri;
+ rv = newChannel->GetURI(getter_AddRefs(newuri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // newuri is expected to be http or https
+ bool newuriIsHttps = newuri->SchemeIs("https");
+
+ // allow insecure->secure redirects for HTTP Strict Transport Security (from
+ // ws://FOO to https://FOO (mapped to wss://FOO)
+ if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
+ nsAutoCString newSpec;
+ rv = newuri->GetSpec(newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
+ newSpec.get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mEncrypted && !newuriIsHttps) {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(newuri->GetSpec(spec))) {
+ LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
+ spec.get()));
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
+ do_QueryInterface(newChannel, &rv);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
+ return rv;
+ }
+
+ // The redirect is likely OK
+
+ newChannel->SetNotificationCallbacks(this);
+
+ mEncrypted = newuriIsHttps;
+ rv = NS_MutateURI(newuri)
+ .SetScheme(mEncrypted ? "wss"_ns : "ws"_ns)
+ .Finalize(mURI);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Could not set the proper scheme\n"));
+ return rv;
+ }
+
+ mHttpChannel = newHttpChannel;
+ mChannel = newUpgradeChannel;
+ rv = SetupRequest();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
+ return rv;
+ }
+
+ // Redirected-to URI may need to be delayed by 1-connecting-per-host and
+ // delay-after-fail algorithms. So hold off calling OnRedirectVerifyCallback
+ // until BeginOpen, when we know it's OK to proceed with new channel.
+ mRedirectCallback = callback;
+
+ // Mark old channel as successfully connected so we'll clear any FailDelay
+ // associated with the old URI. Note: no need to also call OnStopSession:
+ // it's a no-op for successful, already-connected channels.
+ nsWSAdmissionManager::OnConnected(this);
+
+ // ApplyForAdmission as if we were starting from fresh...
+ mAddress.Truncate();
+ mOpenedHttpChannel = false;
+ rv = ApplyForAdmission();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
+ mRedirectCallback = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// nsITimerCallback
+
+NS_IMETHODIMP
+WebSocketChannel::Notify(nsITimer* timer) {
+ LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
+
+ if (timer == mCloseTimer) {
+ MOZ_ASSERT(mClientClosed, "Close Timeout without local close");
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ mCloseTimer = nullptr;
+ if (mStopped || mServerClosed) { /* no longer relevant */
+ return NS_OK;
+ }
+
+ LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n"));
+ AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL);
+ } else if (timer == mOpenTimer) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ mOpenTimer = nullptr;
+ LOG(("WebSocketChannel:: Connection Timed Out\n"));
+ if (mStopped || mServerClosed) { /* no longer relevant */
+ return NS_OK;
+ }
+
+ AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL);
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ // mReconnectDelayTimer is only modified on MainThread, we can read it
+ // without a lock, but ONLY if we're on MainThread! And if we're not
+ // on MainThread, it can't be mReconnectDelayTimer
+ } else if (NS_IsMainThread() && timer == mReconnectDelayTimer) {
+ MOZ_POP_THREAD_SAFETY
+ MOZ_ASSERT(mConnecting == CONNECTING_DELAYED,
+ "woke up from delay w/o being delayed?");
+
+ {
+ MutexAutoLock lock(mMutex);
+ mReconnectDelayTimer = nullptr;
+ }
+ LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
+ BeginOpen(false);
+ } else if (timer == mPingTimer) {
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ if (mClientClosed || mServerClosed || mRequestedClose) {
+ // no point in worrying about ping now
+ mPingTimer = nullptr;
+ return NS_OK;
+ }
+
+ if (!mPingOutstanding) {
+ // Ping interval must be non-null or PING was forced by OnNetworkChanged()
+ MOZ_ASSERT(mPingInterval || mPingForced);
+ LOG(("nsWebSocketChannel:: Generating Ping\n"));
+ mPingOutstanding = 1;
+ mPingForced = false;
+ mPingTimer->InitWithCallback(this, mPingResponseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ GeneratePing();
+ } else {
+ LOG(("nsWebSocketChannel:: Timed out Ping\n"));
+ mPingTimer = nullptr;
+ AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL);
+ }
+ } else if (timer == mLingeringCloseTimer) {
+ LOG(("WebSocketChannel:: Lingering Close Timer"));
+ CleanupConnection();
+ } else {
+ MOZ_ASSERT(0, "Unknown Timer");
+ }
+
+ return NS_OK;
+}
+
+// nsINamed
+
+NS_IMETHODIMP
+WebSocketChannel::GetName(nsACString& aName) {
+ aName.AssignLiteral("WebSocketChannel");
+ return NS_OK;
+}
+
+// nsIWebSocketChannel
+
+NS_IMETHODIMP
+WebSocketChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ *aSecurityInfo = nullptr;
+
+ if (mConnection) {
+ nsresult rv = mConnection->GetSecurityInfo(aSecurityInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (mTransport) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ nsresult rv =
+ mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(
+ do_QueryInterface(tlsSocketControl));
+ if (securityInfo) {
+ securityInfo.forget(aSecurityInfo);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return AsyncOpenNative(aURI, aOrigin, attrs, aInnerWindowID, aListener,
+ aContext);
+}
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext) {
+ LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
+
+ aOriginAttributes.CreateSuffix(mOriginSuffix);
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "not main thread");
+ LOG(("WebSocketChannel::AsyncOpen() called off the main thread"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if ((!aURI && !mIsServerSide) || !aListener) {
+ LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mListenerMT || mWasOpened) return NS_ERROR_ALREADY_OPENED;
+
+ nsresult rv;
+
+ // Ensure target thread is set if RetargetDeliveryTo isn't called
+ {
+ auto lock = mTargetThread.Lock();
+ if (!lock.ref()) {
+ lock.ref() = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ mIOThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without socket transport service");
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefService;
+ prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ if (prefService) {
+ int32_t intpref;
+ bool boolpref;
+ rv =
+ prefService->GetIntPref("network.websocket.max-message-size", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxMessageSize = clamped(intpref, 1024, INT32_MAX);
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
+ &intpref);
+ if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
+ mPingInterval = clamped(intpref, 0, 86400) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
+ &intpref);
+ if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
+ mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
+ }
+ rv = prefService->GetBoolPref(
+ "network.websocket.extensions.permessage-deflate", &boolpref);
+ if (NS_SUCCEEDED(rv)) {
+ mAllowPMCE = boolpref ? 1 : 0;
+ }
+ rv = prefService->GetIntPref("network.websocket.max-connections", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxConcurrentConnections = clamped(intpref, 1, 0xffff);
+ }
+ }
+
+ int32_t sessionCount = -1;
+ nsWSAdmissionManager::GetSessionCount(sessionCount);
+ if (sessionCount >= 0) {
+ LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this,
+ sessionCount, mMaxConcurrentConnections));
+ }
+
+ if (sessionCount >= mMaxConcurrentConnections) {
+ LOG(("WebSocketChannel: max concurrency %d exceeded (%d)",
+ mMaxConcurrentConnections, sessionCount));
+
+ // WebSocket connections are expected to be long lived, so return
+ // an error here instead of queueing
+ return NS_ERROR_SOCKET_CREATE_FAILED;
+ }
+
+ mInnerWindowID = aInnerWindowID;
+ mOriginalURI = aURI;
+ mURI = mOriginalURI;
+ mOrigin = aOrigin;
+
+ if (mIsServerSide) {
+ // IncrementSessionCount();
+ mWasOpened = 1;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ rv = mServerTransportProvider->SetListener(this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mServerTransportProvider = nullptr;
+
+ return NS_OK;
+ }
+
+ mURI->GetHostPort(mHost);
+
+ mRandomGenerator =
+ do_GetService("@mozilla.org/security/random-generator;1", &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without random number generator");
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> localURI;
+ nsCOMPtr<nsIChannel> localChannel;
+
+ rv = NS_MutateURI(mURI)
+ .SetScheme(mEncrypted ? "https"_ns : "http"_ns)
+ .Finalize(localURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIIOService> ioService;
+ ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without io service");
+ return rv;
+ }
+
+ // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't
+ // allow setting proxy uri/flags
+ rv = ioService->NewChannelFromURIWithProxyFlags(
+ localURI, mURI,
+ nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY |
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ mLoadInfo->LoadingNode(), mLoadInfo->GetLoadingPrincipal(),
+ mLoadInfo->TriggeringPrincipal(), mLoadInfo->GetSecurityFlags(),
+ mLoadInfo->InternalContentPolicyType(), getter_AddRefs(localChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Please note that we still call SetLoadInfo on the channel because
+ // we want the same instance of the loadInfo to be set on the channel.
+ rv = localChannel->SetLoadInfo(mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Pass most GetInterface() requests through to our instantiator, but handle
+ // nsIChannelEventSink in this object in order to deal with redirects
+ localChannel->SetNotificationCallbacks(this);
+
+ class MOZ_STACK_CLASS CleanUpOnFailure {
+ public:
+ explicit CleanUpOnFailure(WebSocketChannel* aWebSocketChannel)
+ : mWebSocketChannel(aWebSocketChannel) {}
+
+ ~CleanUpOnFailure() {
+ if (!mWebSocketChannel->mWasOpened) {
+ mWebSocketChannel->mChannel = nullptr;
+ mWebSocketChannel->mHttpChannel = nullptr;
+ }
+ }
+
+ WebSocketChannel* mWebSocketChannel;
+ };
+
+ CleanUpOnFailure cuof(this);
+
+ mChannel = do_QueryInterface(localChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel = do_QueryInterface(localChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupRequest();
+ if (NS_FAILED(rv)) return rv;
+
+ mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel);
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->AddHost(mHost, mSerial,
+ BaseWebSocketChannel::mEncrypted);
+ }
+
+ rv = ApplyForAdmission();
+ if (NS_FAILED(rv)) return rv;
+
+ // Register for prefs change notifications
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ NS_WARNING("failed to get observer service");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Only set these if the open was successful:
+ //
+ mWasOpened = 1;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ IncrementSessionCount();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::Close(uint16_t code, const nsACString& reason) {
+ LOG(("WebSocketChannel::Close() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mRequestedClose) {
+ return NS_OK;
+ }
+
+ if (mStopped) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // The API requires the UTF-8 string to be 123 or less bytes
+ if (reason.Length() > 123) return NS_ERROR_ILLEGAL_VALUE;
+
+ mRequestedClose = true;
+ mScriptCloseReason = reason;
+ mScriptCloseCode = code;
+
+ if (mDataStarted) {
+ return mIOThread->Dispatch(
+ new OutboundEnqueuer(this,
+ new OutboundMessage(kMsgTypeFin, VoidCString())),
+ nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ mStopped = true;
+ }
+
+ nsresult rv;
+ if (code == CLOSE_GOING_AWAY) {
+ // Not an error: for example, tab has closed or navigated away
+ LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
+ rv = NS_OK;
+ } else {
+ LOG(("WebSocketChannel::Close() without transport - error."));
+ rv = NS_ERROR_NOT_CONNECTED;
+ }
+
+ DoStopSession(rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendMsg(const nsACString& aMsg) {
+ LOG(("WebSocketChannel::SendMsg() %p\n", this));
+
+ return SendMsgCommon(aMsg, false, aMsg.Length());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendBinaryMsg(const nsACString& aMsg) {
+ LOG(("WebSocketChannel::SendBinaryMsg() %p len=%zu\n", this, aMsg.Length()));
+ return SendMsgCommon(aMsg, true, aMsg.Length());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendBinaryStream(nsIInputStream* aStream, uint32_t aLength) {
+ LOG(("WebSocketChannel::SendBinaryStream() %p\n", this));
+
+ return SendMsgCommon(VoidCString(), true, aLength, aStream);
+}
+
+nsresult WebSocketChannel::SendMsgCommon(const nsACString& aMsg, bool aIsBinary,
+ uint32_t aLength,
+ nsIInputStream* aStream) {
+ MOZ_ASSERT(IsOnTargetThread(), "not target thread");
+
+ if (!mDataStarted) {
+ LOG(("WebSocketChannel:: Error: data not started yet\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mRequestedClose) {
+ LOG(("WebSocketChannel:: Error: send when closed\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel:: Error: send when stopped\n"));
+ return NS_ERROR_NOT_CONNECTED;
+ }
+
+ MOZ_ASSERT(mMaxMessageSize >= 0, "max message size negative");
+ if (aLength > static_cast<uint32_t>(mMaxMessageSize)) {
+ LOG(("WebSocketChannel:: Error: message too big\n"));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgSent(mHost, mSerial, aLength);
+ LOG(("Added new msg sent for %s", mHost.get()));
+ }
+
+ return mIOThread->Dispatch(
+ aStream
+ ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
+ : new OutboundEnqueuer(
+ this,
+ new OutboundMessage(
+ aIsBinary ? kMsgTypeBinaryString : kMsgTypeString, aMsg)),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+// nsIHttpUpgradeListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnTransportAvailable(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut) {
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(
+ new CallOnTransportAvailable(this, aTransport, aSocketIn, aSocketOut));
+ }
+
+ LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
+ this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK));
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnTransportAvailable: Already stopped"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA duplicated");
+ MOZ_ASSERT(aSocketIn, "OTA with invalid socketIn");
+
+ mTransport = aTransport;
+ mSocketIn = aSocketIn;
+ mSocketOut = aSocketOut;
+
+ nsresult rv;
+ rv = mTransport->SetEventSink(nullptr, nullptr);
+ if (NS_FAILED(rv)) return rv;
+ rv = mTransport->SetSecurityCallbacks(this);
+ if (NS_FAILED(rv)) return rv;
+
+ return OnTransportAvailableInternal();
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnWebSocketConnectionAvailable(
+ WebSocketConnectionBase* aConnection) {
+ if (!NS_IsMainThread()) {
+ RefPtr<WebSocketChannel> self = this;
+ RefPtr<WebSocketConnectionBase> connection = aConnection;
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "WebSocketChannel::OnWebSocketConnectionAvailable",
+ [self, connection]() {
+ self->OnWebSocketConnectionAvailable(connection);
+ }));
+ }
+
+ LOG(
+ ("WebSocketChannel::OnWebSocketConnectionAvailable %p [%p] "
+ "rcvdonstart=%d\n",
+ this, aConnection, mGotUpgradeOK));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport,
+ "OnWebSocketConnectionAvailable duplicated");
+ MOZ_ASSERT(aConnection);
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnWebSocketConnectionAvailable: Already stopped"));
+ aConnection->Close();
+ return NS_OK;
+ }
+
+ nsresult rv = aConnection->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mConnection = aConnection;
+ // Note: mIOThread will be IPDL background thread.
+ mConnection->GetIoTarget(getter_AddRefs(mIOThread));
+ return OnTransportAvailableInternal();
+}
+
+nsresult WebSocketChannel::OnTransportAvailableInternal() {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport,
+ "OnWebSocketConnectionAvailable duplicated");
+ MOZ_ASSERT(mSocketIn || mConnection);
+
+ mRecvdHttpUpgradeTransport = 1;
+ if (mGotUpgradeOK) {
+ // We're now done CONNECTING, which means we can now open another,
+ // perhaps parallel, connection to the same host if one
+ // is pending
+ nsWSAdmissionManager::OnConnected(this);
+
+ return CallStartWebsocketData();
+ }
+
+ if (mIsServerSide) {
+ if (!mNegotiatedExtensions.IsEmpty()) {
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ nsresult rv = ParseWebSocketExtension(
+ mNegotiatedExtensions, eParseServerSide, clientNoContextTakeover,
+ serverNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "illegal value provided by server");
+
+ if (clientMaxWindowBits == -1) {
+ clientMaxWindowBits = 15;
+ }
+ if (serverMaxWindowBits == -1) {
+ serverMaxWindowBits = 15;
+ }
+
+ MutexAutoLock lock(mCompressorMutex);
+ mPMCECompressor = MakeUnique<PMCECompression>(
+ serverNoContextTakeover, serverMaxWindowBits, clientMaxWindowBits);
+ if (mPMCECompressor->Active()) {
+ LOG(
+ ("WebSocketChannel::OnTransportAvailable: PMCE negotiated, %susing "
+ "context takeover, serverMaxWindowBits=%d, "
+ "clientMaxWindowBits=%d\n",
+ serverNoContextTakeover ? "NOT " : "", serverMaxWindowBits,
+ clientMaxWindowBits));
+
+ mNegotiatedExtensions = "permessage-deflate";
+ } else {
+ LOG(
+ ("WebSocketChannel::OnTransportAvailable: Cannot init PMCE "
+ "compression object\n"));
+ mPMCECompressor = nullptr;
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return CallStartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnUpgradeFailed(nsresult aErrorCode) {
+ // When socket process is enabled, this could be called on background thread.
+
+ LOG(("WebSocketChannel::OnUpgradeFailed() %p [aErrorCode %" PRIx32 "]", this,
+ static_cast<uint32_t>(aErrorCode)));
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnUpgradeFailed: Already stopped"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA already called");
+
+ AbortSession(aErrorCode);
+ return NS_OK;
+}
+
+// nsIRequestObserver (from nsIStreamListener)
+
+NS_IMETHODIMP
+WebSocketChannel::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n",
+ this, aRequest, mHttpChannel.get(), mRecvdHttpUpgradeTransport));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mGotUpgradeOK, "OTA duplicated");
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n"));
+ AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED);
+ return NS_ERROR_WEBSOCKET_CONNECTION_REFUSED;
+ }
+
+ nsresult rv;
+ uint32_t status;
+ char *val, *token;
+
+ rv = mHttpChannel->GetResponseStatus(&status);
+ if (NS_FAILED(rv)) {
+ nsresult httpStatus;
+ rv = NS_ERROR_WEBSOCKET_CONNECTION_REFUSED;
+
+ // If we failed to connect due to unsuccessful TLS handshake, we must
+ // propagate a specific error to mozilla::dom::WebSocketImpl so it can set
+ // status code to 1015. Otherwise return
+ // NS_ERROR_WEBSOCKET_CONNECTION_REFUSED.
+ if (NS_SUCCEEDED(mHttpChannel->GetStatus(&httpStatus))) {
+ uint32_t errorClass;
+ nsCOMPtr<nsINSSErrorsService> errSvc =
+ do_GetService("@mozilla.org/nss_errors_service;1");
+ // If GetErrorClass succeeds httpStatus is TLS related failure.
+ if (errSvc &&
+ NS_SUCCEEDED(errSvc->GetErrorClass(httpStatus, &errorClass))) {
+ rv = NS_ERROR_NET_INADEQUATE_SECURITY;
+ }
+ }
+
+ LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
+ AbortSession(rv);
+ return rv;
+ }
+
+ LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(mHttpChannel);
+ uint32_t versionMajor, versionMinor;
+ rv = internalChannel->GetResponseVersion(&versionMajor, &versionMinor);
+ if (NS_FAILED(rv) ||
+ !((versionMajor == 1 && versionMinor != 0) || versionMajor == 2) ||
+ (versionMajor == 1 && status != 101) ||
+ (versionMajor == 2 && status != 200)) {
+ AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED);
+ return NS_ERROR_WEBSOCKET_CONNECTION_REFUSED;
+ }
+
+ if (versionMajor == 1) {
+ // These are only present on http/1.x websocket upgrades
+ nsAutoCString respUpgrade;
+ rv = mHttpChannel->GetResponseHeader("Upgrade"_ns, respUpgrade);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ if (!respUpgrade.IsEmpty()) {
+ val = respUpgrade.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (nsCRT::strcasecmp(token, "Websocket") == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnStartRequest: "
+ "HTTP response header Upgrade: websocket not found\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return rv;
+ }
+
+ nsAutoCString respConnection;
+ rv = mHttpChannel->GetResponseHeader("Connection"_ns, respConnection);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ if (!respConnection.IsEmpty()) {
+ val = respConnection.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (nsCRT::strcasecmp(token, "Upgrade") == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnStartRequest: "
+ "HTTP response header 'Connection: Upgrade' not found\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return rv;
+ }
+
+ nsAutoCString respAccept;
+ rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Accept"_ns, respAccept);
+
+ if (NS_FAILED(rv) || respAccept.IsEmpty() ||
+ !respAccept.Equals(mHashedSecret)) {
+ LOG(
+ ("WebSocketChannel::OnStartRequest: "
+ "HTTP response header Sec-WebSocket-Accept check failed\n"));
+ LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n",
+ mHashedSecret.get(), respAccept.get()));
+#ifdef FUZZING
+ if (NS_FAILED(rv) || respAccept.IsEmpty()) {
+#endif
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+#ifdef FUZZING
+ }
+#endif
+ }
+ }
+
+ // If we sent a sub protocol header, verify the response matches.
+ // If response contains protocol that was not in request, fail.
+ // If response contained no protocol header, set to "" so the protocol
+ // attribute of the WebSocket JS object reflects that
+ if (!mProtocol.IsEmpty()) {
+ nsAutoCString respProtocol;
+ rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Protocol"_ns,
+ respProtocol);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ val = mProtocol.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (strcmp(token, respProtocol.get()) == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
+ respProtocol.get()));
+ mProtocol = respProtocol;
+ } else {
+ LOG(
+ ("WebsocketChannel::OnStartRequest: "
+ "Server replied with non-matching subprotocol [%s]: aborting",
+ respProtocol.get()));
+ mProtocol.Truncate();
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ } else {
+ LOG(
+ ("WebsocketChannel::OnStartRequest "
+ "subprotocol [%s] not found - none returned",
+ mProtocol.get()));
+ mProtocol.Truncate();
+ }
+ }
+
+ rv = HandleExtensions();
+ if (NS_FAILED(rv)) return rv;
+
+ // Update mEffectiveURL for off main thread URI access.
+ nsCOMPtr<nsIURI> uri = mURI ? mURI : mOriginalURI;
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ CopyUTF8toUTF16(spec, mEffectiveURL);
+
+ mGotUpgradeOK = 1;
+ if (mRecvdHttpUpgradeTransport) {
+ // We're now done CONNECTING, which means we can now open another,
+ // perhaps parallel, connection to the same host if one
+ // is pending
+ nsWSAdmissionManager::OnConnected(this);
+
+ return CallStartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %" PRIx32 "]\n", this,
+ aRequest, mHttpChannel.get(), static_cast<uint32_t>(aStatusCode)));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ // OnTransportAvailable won't be called if the request is stopped with
+ // an error. Abort the session now instead of waiting for timeout.
+ if (NS_FAILED(aStatusCode) && !mRecvdHttpUpgradeTransport) {
+ AbortSession(aStatusCode);
+ }
+
+ ReportConnectionTelemetry(aStatusCode);
+
+ // This is the end of the HTTP upgrade transaction, the
+ // upgraded streams live on
+
+ mChannel = nullptr;
+ mHttpChannel = nullptr;
+ mLoadGroup = nullptr;
+ mCallbacks = nullptr;
+
+ return NS_OK;
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
+ MOZ_DIAGNOSTIC_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ if (!mSocketIn) { // did we we clean up the socket after scheduling
+ // InputReady?
+ return NS_OK;
+ }
+
+ // this is after the http upgrade - so we are speaking websockets
+ char buffer[2048];
+ uint32_t count;
+ nsresult rv;
+
+ do {
+ rv = mSocketIn->Read((char*)buffer, sizeof(buffer), &count);
+ LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %" PRIx32 "\n",
+ count, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketIn->AsyncWait(this, 0, 0, mIOThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+
+ if (count == 0) {
+ AbortSession(NS_BASE_STREAM_CLOSED);
+ return NS_OK;
+ }
+
+ if (mStopped) {
+ continue;
+ }
+
+ rv = ProcessInput((uint8_t*)buffer, count);
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+ } while (NS_SUCCEEDED(rv) && mSocketIn);
+
+ return NS_OK;
+}
+
+// nsIOutputStreamCallback
+
+NS_IMETHODIMP
+WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this));
+ MOZ_DIAGNOSTIC_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+ nsresult rv;
+
+ if (!mCurrentOut) PrimeNewOutgoingMessage();
+
+ while (mCurrentOut && mSocketOut) {
+ const char* sndBuf;
+ uint32_t toSend;
+ uint32_t amtSent;
+
+ if (mHdrOut) {
+ sndBuf = (const char*)mHdrOut;
+ toSend = mHdrOutToSend;
+ LOG(
+ ("WebSocketChannel::OnOutputStreamReady: "
+ "Try to send %u of hdr/copybreak\n",
+ toSend));
+ } else {
+ sndBuf = (char*)mCurrentOut->BeginReading() + mCurrentOutSent;
+ toSend = mCurrentOut->Length() - mCurrentOutSent;
+ if (toSend > 0) {
+ LOG(
+ ("WebSocketChannel::OnOutputStreamReady [%p]: "
+ "Try to send %u of data\n",
+ this, toSend));
+ }
+ }
+
+ if (toSend == 0) {
+ amtSent = 0;
+ } else {
+ rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
+ LOG(("WebSocketChannel::OnOutputStreamReady [%p]: write %u rv %" PRIx32
+ "\n",
+ this, amtSent, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketOut->AsyncWait(this, 0, 0, mIOThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return NS_OK;
+ }
+ }
+
+ if (mHdrOut) {
+ if (amtSent == toSend) {
+ mHdrOut = nullptr;
+ mHdrOutToSend = 0;
+ } else {
+ mHdrOut += amtSent;
+ mHdrOutToSend -= amtSent;
+ mSocketOut->AsyncWait(this, 0, 0, mIOThread);
+ }
+ } else {
+ if (amtSent == toSend) {
+ if (!mStopped) {
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(
+ new CallAcknowledge(this, mCurrentOut->OrigLength()),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ } else {
+ mCurrentOutSent += amtSent;
+ mSocketOut->AsyncWait(this, 0, 0, mIOThread);
+ }
+ }
+ }
+
+ if (mReleaseOnTransmit) ReleaseSession();
+ return NS_OK;
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %" PRIu64 " %u]\n",
+ this, aRequest, mHttpChannel.get(), aInputStream, aOffset, aCount));
+
+ // This is the HTTP OnDataAvailable Method, which means this is http data in
+ // response to the upgrade request and there should be no http response body
+ // if the upgrade succeeded. This generally should be caught by a non 101
+ // response code in OnStartRequest().. so we can ignore the data here
+
+ LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
+ aCount));
+
+ return NS_OK;
+}
+
+void WebSocketChannel::DoEnqueueOutgoingMessage() {
+ LOG(("WebSocketChannel::DoEnqueueOutgoingMessage() %p\n", this));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ if (!mCurrentOut) {
+ PrimeNewOutgoingMessage();
+ }
+
+ while (mCurrentOut && mConnection) {
+ nsresult rv = NS_OK;
+ if (mCurrentOut->Length() - mCurrentOutSent == 0) {
+ LOG(
+ ("WebSocketChannel::DoEnqueueOutgoingMessage: "
+ "Try to send %u of hdr/copybreak\n",
+ mHdrOutToSend));
+ rv = mConnection->WriteOutputData(mOutHeader, mHdrOutToSend, nullptr, 0);
+ } else {
+ LOG(
+ ("WebSocketChannel::DoEnqueueOutgoingMessage: "
+ "Try to send %u of hdr and %u of data\n",
+ mHdrOutToSend, mCurrentOut->Length()));
+ rv = mConnection->WriteOutputData(mOutHeader, mHdrOutToSend,
+ (uint8_t*)mCurrentOut->BeginReading(),
+ mCurrentOut->Length());
+ }
+
+ LOG(("WebSocketChannel::DoEnqueueOutgoingMessage: rv %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return;
+ }
+
+ if (!mStopped) {
+ // TODO: Currently, we assume that data is completely written to the
+ // socket after sending it to socket process, but it's not true. The data
+ // could be queued in socket process and waiting for the socket to be able
+ // to write. We should implement flow control for this in bug 1726552.
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(new CallAcknowledge(this, mCurrentOut->OrigLength()),
+ NS_DISPATCH_NORMAL);
+ } else {
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ }
+
+ if (mReleaseOnTransmit) {
+ ReleaseSession();
+ }
+}
+
+void WebSocketChannel::OnError(nsresult aStatus) { AbortSession(aStatus); }
+
+void WebSocketChannel::OnTCPClosed() { mTCPClosed = true; }
+
+nsresult WebSocketChannel::OnDataReceived(uint8_t* aData, uint32_t aCount) {
+ return ProcessInput(aData, aCount);
+}
+
+} // namespace mozilla::net
+
+#undef CLOSE_GOING_AWAY
diff --git a/netwerk/protocol/websocket/WebSocketChannel.h b/netwerk/protocol/websocket/WebSocketChannel.h
new file mode 100644
index 0000000000..83b1fc6cd2
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -0,0 +1,377 @@
+/* -*- 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_WebSocketChannel_h
+#define mozilla_net_WebSocketChannel_h
+
+#include "nsISupports.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsITimer.h"
+#include "nsIDNSListener.h"
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannelEventSink.h"
+#include "nsIHttpChannelInternal.h"
+#include "mozilla/net/WebSocketConnectionListener.h"
+#include "mozilla/Mutex.h"
+#include "BaseWebSocketChannel.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDeque.h"
+#include "mozilla/Atomics.h"
+
+class nsIAsyncVerifyRedirectCallback;
+class nsIDashboardEventNotifier;
+class nsIEventTarget;
+class nsIHttpChannel;
+class nsIRandomGenerator;
+class nsISocketTransport;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class OutboundMessage;
+class OutboundEnqueuer;
+class nsWSAdmissionManager;
+class PMCECompression;
+class CallOnMessageAvailable;
+class CallOnStop;
+class CallOnServerClose;
+class CallAcknowledge;
+class WebSocketEventService;
+class WebSocketConnectionBase;
+
+[[nodiscard]] extern nsresult CalculateWebSocketHashedSecret(
+ const nsACString& aKey, nsACString& aHash);
+extern void ProcessServerWebSocketExtensions(const nsACString& aExtensions,
+ nsACString& aNegotiatedExtensions);
+
+// Used to enforce "1 connecting websocket per host" rule, and reconnect delays
+enum wsConnectingState {
+ NOT_CONNECTING = 0, // Not yet (or no longer) trying to open connection
+ CONNECTING_QUEUED, // Waiting for other ws to same host to finish opening
+ CONNECTING_DELAYED, // Delayed by "reconnect after failure" algorithm
+ CONNECTING_IN_PROGRESS // Started connection: waiting for result
+};
+
+class WebSocketChannel : public BaseWebSocketChannel,
+ public nsIHttpUpgradeListener,
+ public nsIStreamListener,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public nsITimerCallback,
+ public nsIDNSListener,
+ public nsIObserver,
+ public nsIProtocolProxyCallback,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsINamed,
+ public WebSocketConnectionListener {
+ friend class WebSocketFrame;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPUPGRADELISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
+ //
+ NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aWindowID, nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) override;
+ NS_IMETHOD AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes,
+ uint64_t aWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext) override;
+ NS_IMETHOD Close(uint16_t aCode, const nsACString& aReason) override;
+ NS_IMETHOD SendMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryStream(nsIInputStream* aStream,
+ uint32_t length) override;
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ WebSocketChannel();
+ static void Shutdown();
+
+ // Off main thread URI access.
+ void GetEffectiveURL(nsAString& aEffectiveURL) const override;
+ bool IsEncrypted() const override;
+
+ nsresult OnTransportAvailableInternal();
+ void OnError(nsresult aStatus) override;
+ void OnTCPClosed() override;
+ nsresult OnDataReceived(uint8_t* aData, uint32_t aCount) override;
+
+ const static uint32_t kControlFrameMask = 0x8;
+
+ // First byte of the header
+ const static uint8_t kFinalFragBit = 0x80;
+ const static uint8_t kRsvBitsMask = 0x70;
+ const static uint8_t kRsv1Bit = 0x40;
+ const static uint8_t kRsv2Bit = 0x20;
+ const static uint8_t kRsv3Bit = 0x10;
+ const static uint8_t kOpcodeBitsMask = 0x0F;
+
+ // Second byte of the header
+ const static uint8_t kMaskBit = 0x80;
+ const static uint8_t kPayloadLengthBitsMask = 0x7F;
+
+ protected:
+ ~WebSocketChannel() override;
+
+ private:
+ friend class OutboundEnqueuer;
+ friend class nsWSAdmissionManager;
+ friend class FailDelayManager;
+ friend class CallOnMessageAvailable;
+ friend class CallOnStop;
+ friend class CallOnServerClose;
+ friend class CallAcknowledge;
+
+ // Common send code for binary + text msgs
+ [[nodiscard]] nsresult SendMsgCommon(const nsACString& aMsg, bool isBinary,
+ uint32_t length,
+ nsIInputStream* aStream = nullptr);
+
+ void EnqueueOutgoingMessage(nsDeque<OutboundMessage>& aQueue,
+ OutboundMessage* aMsg);
+ void DoEnqueueOutgoingMessage();
+
+ void PrimeNewOutgoingMessage();
+ void DeleteCurrentOutGoingMessage();
+ void GeneratePong(uint8_t* payload, uint32_t len);
+ void GeneratePing();
+
+ [[nodiscard]] nsresult OnNetworkChanged();
+ [[nodiscard]] nsresult StartPinging();
+
+ void BeginOpen(bool aCalledFromAdmissionManager);
+ void BeginOpenInternal();
+ [[nodiscard]] nsresult HandleExtensions();
+ [[nodiscard]] nsresult SetupRequest();
+ [[nodiscard]] nsresult ApplyForAdmission();
+ [[nodiscard]] nsresult DoAdmissionDNS();
+ [[nodiscard]] nsresult CallStartWebsocketData();
+ [[nodiscard]] nsresult StartWebsocketData();
+ uint16_t ResultToCloseCode(nsresult resultCode);
+ void ReportConnectionTelemetry(nsresult aStatusCode);
+
+ void StopSession(nsresult reason);
+ void DoStopSession(nsresult reason);
+ void AbortSession(nsresult reason);
+ void ReleaseSession();
+ void CleanupConnection();
+ void IncrementSessionCount();
+ void DecrementSessionCount();
+
+ void EnsureHdrOut(uint32_t size);
+
+ static void ApplyMask(uint32_t mask, uint8_t* data, uint64_t len);
+
+ bool IsPersistentFramePtr();
+ [[nodiscard]] nsresult ProcessInput(uint8_t* buffer, uint32_t count);
+ [[nodiscard]] bool UpdateReadBuffer(uint8_t* buffer, uint32_t count,
+ uint32_t accumulatedFragments,
+ uint32_t* available);
+
+ inline void ResetPingTimer() {
+ mPingOutstanding = 0;
+ if (mPingTimer) {
+ if (!mPingInterval) {
+ // The timer was created by forced ping and regular pinging is disabled,
+ // so cancel and null out mPingTimer.
+ mPingTimer->Cancel();
+ mPingTimer = nullptr;
+ } else {
+ mPingTimer->SetDelay(mPingInterval);
+ }
+ }
+ }
+
+ void NotifyOnStart();
+
+ nsCOMPtr<nsIEventTarget> mIOThread;
+ // Set in AsyncOpenNative and AsyncOnChannelRedirect, modified in
+ // DoStopSession on IO thread (.forget()). Probably ok...
+ nsCOMPtr<nsIHttpChannelInternal> mChannel;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+
+ nsCOMPtr<nsICancelable> mCancelable MOZ_GUARDED_BY(mMutex);
+ // Mainthread only
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ // Set on Mainthread during AsyncOpen, used on IO thread and Mainthread
+ nsCOMPtr<nsIRandomGenerator> mRandomGenerator;
+
+ nsCString mHashedSecret; // MainThread only
+
+ // Used as key for connection managment: Initially set to hostname from URI,
+ // then to IP address (unless we're leaving DNS resolution to a proxy server)
+ // MainThread only
+ nsCString mAddress;
+ nsCString mPath;
+ int32_t mPort; // WS server port
+ // Secondary key for the connection queue. Used by nsWSAdmissionManager.
+ nsCString mOriginSuffix; // MainThread only
+
+ // Used for off main thread access to the URI string.
+ // Set on MainThread in AsyncOpenNative, used on TargetThread and IO thread
+ nsCString mHost;
+ nsString mEffectiveURL;
+
+ // Set on MainThread before multithread use, used on IO thread, cleared on
+ // IOThread
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+ RefPtr<WebSocketConnectionBase> mConnection;
+
+ // Only used on IO Thread (accessed when known-to-be-null in DoStopSession
+ // on MainThread before mDataStarted)
+ nsCOMPtr<nsITimer> mCloseTimer;
+ // set in AsyncOpenInternal on MainThread, used on IO thread.
+ // No multithread use before it's set, no changes after that.
+ uint32_t mCloseTimeout; /* milliseconds */
+
+ nsCOMPtr<nsITimer> mOpenTimer; /* Mainthread only */
+ uint32_t mOpenTimeout; /* milliseconds, MainThread only */
+ wsConnectingState mConnecting; /* 0 if not connecting, MainThread only */
+ // Set on MainThread, deleted on MainThread, used on MainThread or
+ // IO Thread (in DoStopSession). Mutex required to access off-main-thread.
+ nsCOMPtr<nsITimer> mReconnectDelayTimer MOZ_GUARDED_BY(mMutex);
+
+ // Only touched on IOThread (DoStopSession reads it on MainThread if
+ // we haven't connected yet (mDataStarted==false), and it's always null
+ // until mDataStarted=true)
+ nsCOMPtr<nsITimer> mPingTimer;
+
+ // Created in DoStopSession on IO thread (mDataStarted=true), accessed
+ // only from IO Thread
+ nsCOMPtr<nsITimer> mLingeringCloseTimer;
+ const static int32_t kLingeringCloseTimeout = 1000;
+ const static int32_t kLingeringCloseThreshold = 50;
+
+ RefPtr<WebSocketEventService> mService; // effectively const
+
+ int32_t
+ mMaxConcurrentConnections; // only used in AsyncOpenNative on MainThread
+
+ // Set on MainThread in AsyncOpenNative; then used on IO thread
+ uint64_t mInnerWindowID;
+
+ // following members are accessed only on the main thread
+ uint32_t mGotUpgradeOK : 1;
+ uint32_t mRecvdHttpUpgradeTransport : 1;
+ uint32_t mAllowPMCE : 1;
+ uint32_t : 0; // ensure these aren't mixed with the next set
+
+ // following members are accessed only on the IO thread
+ uint32_t mPingOutstanding : 1;
+ uint32_t mReleaseOnTransmit : 1;
+ uint32_t : 0;
+
+ Atomic<bool> mDataStarted;
+ // All changes to mRequestedClose happen under mutex, but since it's atomic,
+ // it can be read anywhere without a lock
+ Atomic<bool> mRequestedClose;
+ // mServer/ClientClosed are only modified on IOThread
+ Atomic<bool> mClientClosed;
+ Atomic<bool> mServerClosed;
+ // All changes to mStopped happen under mutex, but since it's atomic, it
+ // can be read anywhere without a lock
+ Atomic<bool> mStopped;
+ Atomic<bool> mCalledOnStop;
+ Atomic<bool> mTCPClosed;
+ Atomic<bool> mOpenedHttpChannel;
+ Atomic<bool> mIncrementedSessionCount;
+ Atomic<bool> mDecrementedSessionCount;
+
+ int32_t mMaxMessageSize; // set on MainThread in AsyncOpenNative, read on IO
+ // thread
+ // Set on IOThread, or on MainThread before mDataStarted. Used on IO Thread
+ // (after mDataStarted)
+ nsresult mStopOnClose;
+ uint16_t mServerCloseCode; // only used on IO thread
+ nsCString mServerCloseReason; // only used on IO thread
+ uint16_t mScriptCloseCode MOZ_GUARDED_BY(mMutex);
+ nsCString mScriptCloseReason MOZ_GUARDED_BY(mMutex);
+
+ // These are for the read buffers
+ const static uint32_t kIncomingBufferInitialSize = 16 * 1024;
+ // We're ok with keeping a buffer this size or smaller around for the life of
+ // the websocket. If a particular message needs bigger than this we'll
+ // increase the buffer temporarily, then drop back down to this size.
+ const static uint32_t kIncomingBufferStableSize = 128 * 1024;
+
+ // Set at creation, used/modified only on IO thread
+ uint8_t* mFramePtr;
+ uint8_t* mBuffer;
+ uint8_t mFragmentOpcode;
+ uint32_t mFragmentAccumulator;
+ uint32_t mBuffered;
+ uint32_t mBufferSize;
+
+ // These are for the send buffers
+ const static int32_t kCopyBreak = 1000;
+
+ // Only used on IO thread
+ OutboundMessage* mCurrentOut;
+ uint32_t mCurrentOutSent;
+ nsDeque<OutboundMessage> mOutgoingMessages;
+ nsDeque<OutboundMessage> mOutgoingPingMessages;
+ nsDeque<OutboundMessage> mOutgoingPongMessages;
+ uint32_t mHdrOutToSend;
+ uint8_t* mHdrOut;
+ uint8_t mOutHeader[kCopyBreak + 16]{0};
+
+ // Set on MainThread in OnStartRequest (before mDataStarted), or in
+ // HandleExtensions() or OnTransportAvailableInternal(),used on IO Thread
+ // (after mDataStarted), cleared in DoStopSession on IOThread or on
+ // MainThread (if mDataStarted == false).
+ Mutex mCompressorMutex;
+ UniquePtr<PMCECompression> mPMCECompressor MOZ_GUARDED_BY(mCompressorMutex);
+
+ // Used by EnsureHdrOut, which isn't called anywhere
+ uint32_t mDynamicOutputSize;
+ uint8_t* mDynamicOutput;
+ // Set on creation and AsyncOpen, read on both threads
+ Atomic<bool> mPrivateBrowsing;
+
+ nsCOMPtr<nsIDashboardEventNotifier>
+ mConnectionLogService; // effectively const
+
+ mozilla::Mutex mMutex;
+};
+
+class WebSocketSSLChannel : public WebSocketChannel {
+ public:
+ WebSocketSSLChannel() { BaseWebSocketChannel::mEncrypted = true; }
+
+ protected:
+ virtual ~WebSocketSSLChannel() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannel_h
diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.cpp b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
new file mode 100644
index 0000000000..2931b47065
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -0,0 +1,732 @@
+/* -*- 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 "WebSocketLog.h"
+#include "base/compiler_specific.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "WebSocketChannelChild.h"
+#include "nsContentUtils.h"
+#include "nsIBrowserChild.h"
+#include "nsNetUtil.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsITransportProvider.h"
+
+using namespace mozilla::ipc;
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(WebSocketChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) WebSocketChannelChild::Release() {
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "WebSocketChannelChild");
+
+ if (mRefCnt == 1) {
+ MaybeReleaseIPCObject();
+ return mRefCnt;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(WebSocketChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+NS_INTERFACE_MAP_END
+
+WebSocketChannelChild::WebSocketChannelChild(bool aEncrypted)
+ : NeckoTargetHolder(nullptr),
+ mIPCState(Closed),
+ mMutex("WebSocketChannelChild::mMutex") {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannelChild::WebSocketChannelChild() %p\n", this));
+ mEncrypted = aEncrypted;
+ mEventQ = new ChannelEventQueue(static_cast<nsIWebSocketChannel*>(this));
+}
+
+WebSocketChannelChild::~WebSocketChannelChild() {
+ LOG(("WebSocketChannelChild::~WebSocketChannelChild() %p\n", this));
+ mEventQ->NotifyReleasingOwner();
+}
+
+void WebSocketChannelChild::AddIPDLReference() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mIPCState == Closed,
+ "Attempt to retain more than one IPDL reference");
+ mIPCState = Opened;
+ }
+
+ AddRef();
+}
+
+void WebSocketChannelChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mIPCState != Closed,
+ "Attempt to release nonexistent IPDL reference");
+ mIPCState = Closed;
+ }
+
+ Release();
+}
+
+void WebSocketChannelChild::MaybeReleaseIPCObject() {
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return;
+ }
+
+ mIPCState = Closing;
+ }
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ MOZ_ALWAYS_SUCCEEDS(target->Dispatch(
+ NewRunnableMethod("WebSocketChannelChild::MaybeReleaseIPCObject", this,
+ &WebSocketChannelChild::MaybeReleaseIPCObject),
+ NS_DISPATCH_NORMAL));
+ return;
+ }
+
+ SendDeleteSelf();
+}
+
+void WebSocketChannelChild::GetEffectiveURL(nsAString& aEffectiveURL) const {
+ aEffectiveURL = mEffectiveURL;
+}
+
+bool WebSocketChannelChild::IsEncrypted() const { return mEncrypted; }
+
+class WebSocketEvent {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(WebSocketEvent)
+ MOZ_COUNTED_DTOR_VIRTUAL(WebSocketEvent)
+ virtual void Run(WebSocketChannelChild* aChild) = 0;
+};
+
+class WrappedWebSocketEvent : public Runnable {
+ public:
+ WrappedWebSocketEvent(WebSocketChannelChild* aChild,
+ UniquePtr<WebSocketEvent>&& aWebSocketEvent)
+ : Runnable("net::WrappedWebSocketEvent"),
+ mChild(aChild),
+ mWebSocketEvent(std::move(aWebSocketEvent)) {
+ MOZ_RELEASE_ASSERT(!!mWebSocketEvent);
+ }
+ NS_IMETHOD Run() override {
+ mWebSocketEvent->Run(mChild);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ UniquePtr<WebSocketEvent> mWebSocketEvent;
+};
+
+class EventTargetDispatcher : public ChannelEvent {
+ public:
+ EventTargetDispatcher(WebSocketChannelChild* aChild,
+ WebSocketEvent* aWebSocketEvent)
+ : mChild(aChild),
+ mWebSocketEvent(aWebSocketEvent),
+ mEventTarget(mChild->GetTargetThread()) {}
+
+ void Run() override {
+ if (mEventTarget) {
+ mEventTarget->Dispatch(
+ new WrappedWebSocketEvent(mChild, std::move(mWebSocketEvent)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+ }
+
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ nsCOMPtr<nsIEventTarget> target = mEventTarget;
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+ }
+
+ private:
+ // The lifetime of the child is ensured by ChannelEventQueue.
+ WebSocketChannelChild* mChild;
+ UniquePtr<WebSocketEvent> mWebSocketEvent;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+class StartEvent : public WebSocketEvent {
+ public:
+ StartEvent(const nsACString& aProtocol, const nsACString& aExtensions,
+ const nsAString& aEffectiveURL, bool aEncrypted,
+ uint64_t aHttpChannelId)
+ : mProtocol(aProtocol),
+ mExtensions(aExtensions),
+ mEffectiveURL(aEffectiveURL),
+ mEncrypted(aEncrypted),
+ mHttpChannelId(aHttpChannelId) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnStart(mProtocol, mExtensions, mEffectiveURL, mEncrypted,
+ mHttpChannelId);
+ }
+
+ private:
+ nsCString mProtocol;
+ nsCString mExtensions;
+ nsString mEffectiveURL;
+ bool mEncrypted;
+ uint64_t mHttpChannelId;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnStart(
+ const nsACString& aProtocol, const nsACString& aExtensions,
+ const nsAString& aEffectiveURL, const bool& aEncrypted,
+ const uint64_t& aHttpChannelId) {
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(
+ this, new StartEvent(aProtocol, aExtensions, aEffectiveURL, aEncrypted,
+ aHttpChannelId)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnStart(const nsACString& aProtocol,
+ const nsACString& aExtensions,
+ const nsAString& aEffectiveURL,
+ const bool& aEncrypted,
+ const uint64_t& aHttpChannelId) {
+ LOG(("WebSocketChannelChild::RecvOnStart() %p\n", this));
+ SetProtocol(aProtocol);
+ mNegotiatedExtensions = aExtensions;
+ mEffectiveURL = aEffectiveURL;
+ mEncrypted = aEncrypted;
+ mHttpChannelId = aHttpChannelId;
+
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv = mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::OnStart "
+ "mListenerMT->mListener->OnStart() failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class StopEvent : public WebSocketEvent {
+ public:
+ explicit StopEvent(const nsresult& aStatusCode) : mStatusCode(aStatusCode) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnStop(mStatusCode);
+ }
+
+ private:
+ nsresult mStatusCode;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnStop(
+ const nsresult& aStatusCode) {
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new StopEvent(aStatusCode)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnStop(const nsresult& aStatusCode) {
+ LOG(("WebSocketChannelChild::RecvOnStop() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv =
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, aStatusCode);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnStop "
+ "mListenerMT->mListener->OnStop() failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class MessageEvent : public WebSocketEvent {
+ public:
+ MessageEvent(const nsACString& aMessage, bool aBinary)
+ : mMessage(aMessage), mBinary(aBinary) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ if (!mBinary) {
+ aChild->OnMessageAvailable(mMessage);
+ } else {
+ aChild->OnBinaryMessageAvailable(mMessage);
+ }
+ }
+
+ private:
+ nsCString mMessage;
+ bool mBinary;
+};
+
+bool WebSocketChannelChild::RecvOnMessageAvailableInternal(
+ const nsACString& aMsg, bool aMoreData, bool aBinary) {
+ if (aMoreData) {
+ return mReceivedMsgBuffer.Append(aMsg, fallible);
+ }
+
+ if (!mReceivedMsgBuffer.Append(aMsg, fallible)) {
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(
+ this, new MessageEvent(mReceivedMsgBuffer, aBinary)));
+ mReceivedMsgBuffer.Truncate();
+ return true;
+}
+
+class OnErrorEvent : public WebSocketEvent {
+ public:
+ OnErrorEvent() = default;
+
+ void Run(WebSocketChannelChild* aChild) override { aChild->OnError(); }
+};
+
+void WebSocketChannelChild::OnError() {
+ LOG(("WebSocketChannelChild::OnError() %p", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ Unused << mListenerMT->mListener->OnError();
+ }
+}
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnMessageAvailable(
+ const nsACString& aMsg, const bool& aMoreData) {
+ if (!RecvOnMessageAvailableInternal(aMsg, aMoreData, false)) {
+ LOG(("WebSocketChannelChild %p append message failed", this));
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(this, new OnErrorEvent()));
+ }
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnMessageAvailable(const nsACString& aMsg) {
+ LOG(("WebSocketChannelChild::RecvOnMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv =
+ mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext, aMsg);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::OnMessageAvailable "
+ "mListenerMT->mListener->OnMessageAvailable() "
+ "failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnBinaryMessageAvailable(
+ const nsACString& aMsg, const bool& aMoreData) {
+ if (!RecvOnMessageAvailableInternal(aMsg, aMoreData, true)) {
+ LOG(("WebSocketChannelChild %p append message failed", this));
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(this, new OnErrorEvent()));
+ }
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnBinaryMessageAvailable(const nsACString& aMsg) {
+ LOG(("WebSocketChannelChild::RecvOnBinaryMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv = mListenerMT->mListener->OnBinaryMessageAvailable(
+ mListenerMT->mContext, aMsg);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::OnBinaryMessageAvailable "
+ "mListenerMT->mListener->OnBinaryMessageAvailable() "
+ "failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class AcknowledgeEvent : public WebSocketEvent {
+ public:
+ explicit AcknowledgeEvent(const uint32_t& aSize) : mSize(aSize) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnAcknowledge(mSize);
+ }
+
+ private:
+ uint32_t mSize;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnAcknowledge(
+ const uint32_t& aSize) {
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new AcknowledgeEvent(aSize)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnAcknowledge(const uint32_t& aSize) {
+ LOG(("WebSocketChannelChild::RecvOnAcknowledge() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv =
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, aSize);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnAcknowledge "
+ "mListenerMT->mListener->OnAcknowledge() "
+ "failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class ServerCloseEvent : public WebSocketEvent {
+ public:
+ ServerCloseEvent(const uint16_t aCode, const nsACString& aReason)
+ : mCode(aCode), mReason(aReason) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnServerClose(mCode, mReason);
+ }
+
+ private:
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnServerClose(
+ const uint16_t& aCode, const nsACString& aReason) {
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new ServerCloseEvent(aCode, aReason)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnServerClose(const uint16_t& aCode,
+ const nsACString& aReason) {
+ LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ DebugOnly<nsresult> rv = mListenerMT->mListener->OnServerClose(
+ mListenerMT->mContext, aCode, aReason);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void WebSocketChannelChild::SetupNeckoTarget() {
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return AsyncOpenNative(aURI, aOrigin, attrs, aInnerWindowID, aListener,
+ aContext);
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::AsyncOpenNative(
+ nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes, uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener, nsISupports* aContext) {
+ LOG(("WebSocketChannelChild::AsyncOpen() %p\n", this));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT((aURI && !mIsServerSide) || (!aURI && mIsServerSide),
+ "Invalid aURI for WebSocketChannelChild::AsyncOpen");
+ MOZ_ASSERT(aListener && !mListenerMT,
+ "Invalid state for WebSocketChannelChild::AsyncOpen");
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Corresponding release in DeallocPWebSocket
+ AddIPDLReference();
+
+ nsCOMPtr<nsIURI> uri;
+ LoadInfoArgs loadInfoArgs;
+ Maybe<NotNull<PTransportProviderChild*>> transportProvider;
+
+ if (!mIsServerSide) {
+ uri = aURI;
+ nsresult rv = LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = Nothing();
+ } else {
+ MOZ_ASSERT(mServerTransportProvider);
+ PTransportProviderChild* ipcChild;
+ nsresult rv = mServerTransportProvider->GetIPCChild(&ipcChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = Some(WrapNotNull(ipcChild));
+ }
+
+ // This must be called before sending constructor message.
+ SetupNeckoTarget();
+
+ if (!gNeckoChild->SendPWebSocketConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), mSerial)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!SendAsyncOpen(uri, aOrigin, aOriginAttributes, aInnerWindowID, mProtocol,
+ mEncrypted, mPingInterval, mClientSetPingInterval,
+ mPingResponseTimeout, mClientSetPingTimeout, loadInfoArgs,
+ transportProvider, mNegotiatedExtensions)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mIsServerSide) {
+ mServerTransportProvider = nullptr;
+ }
+
+ mOriginalURI = aURI;
+ mURI = mOriginalURI;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ mOrigin = aOrigin;
+ mWasOpened = 1;
+
+ return NS_OK;
+}
+
+class CloseEvent : public Runnable {
+ public:
+ CloseEvent(WebSocketChannelChild* aChild, uint16_t aCode,
+ const nsACString& aReason)
+ : Runnable("net::CloseEvent"),
+ mChild(aChild),
+ mCode(aCode),
+ mReason(aReason) {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ mChild->Close(mCode, mReason);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::Close(uint16_t code, const nsACString& reason) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new CloseEvent(this, code, reason),
+ NS_DISPATCH_NORMAL);
+ }
+ LOG(("WebSocketChannelChild::Close() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendClose(code, reason)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+class MsgEvent : public Runnable {
+ public:
+ MsgEvent(WebSocketChannelChild* aChild, const nsACString& aMsg,
+ bool aBinaryMsg)
+ : Runnable("net::MsgEvent"),
+ mChild(aChild),
+ mMsg(aMsg),
+ mBinaryMsg(aBinaryMsg) {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (mBinaryMsg) {
+ mChild->SendBinaryMsg(mMsg);
+ } else {
+ mChild->SendMsg(mMsg);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCString mMsg;
+ bool mBinaryMsg;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendMsg(const nsACString& aMsg) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new MsgEvent(this, aMsg, false),
+ NS_DISPATCH_NORMAL);
+ }
+ LOG(("WebSocketChannelChild::SendMsg() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendMsg(aMsg)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendBinaryMsg(const nsACString& aMsg) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new MsgEvent(this, aMsg, true), NS_DISPATCH_NORMAL);
+ }
+ LOG(("WebSocketChannelChild::SendBinaryMsg() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryMsg(aMsg)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+class BinaryStreamEvent : public Runnable {
+ public:
+ BinaryStreamEvent(WebSocketChannelChild* aChild, nsIInputStream* aStream,
+ uint32_t aLength)
+ : Runnable("net::BinaryStreamEvent"),
+ mChild(aChild),
+ mStream(aStream),
+ mLength(aLength) {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = mChild->SendBinaryStream(mStream, mLength);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::BinaryStreamEvent %p "
+ "SendBinaryStream failed (%08" PRIx32 ")\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCOMPtr<nsIInputStream> mStream;
+ uint32_t mLength;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendBinaryStream(nsIInputStream* aStream,
+ uint32_t aLength) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new BinaryStreamEvent(this, aStream, aLength),
+ NS_DISPATCH_NORMAL);
+ }
+
+ LOG(("WebSocketChannelChild::SendBinaryStream() %p\n", this));
+
+ IPCStream ipcStream;
+ if (NS_WARN_IF(!mozilla::ipc::SerializeIPCStream(do_AddRef(aStream),
+ ipcStream,
+ /* aAllowLazy */ false))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryStream(ipcStream, aLength)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketChannelChild::GetSecurityInfo() %p\n", this));
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.h b/netwerk/protocol/websocket/WebSocketChannelChild.h
new file mode 100644
index 0000000000..0221b9fdde
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.h
@@ -0,0 +1,116 @@
+/* -*- 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_WebSocketChannelChild_h
+#define mozilla_net_WebSocketChannelChild_h
+
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PWebSocketChild.h"
+#include "mozilla/net/BaseWebSocketChannel.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+namespace net {
+
+class ChannelEvent;
+class ChannelEventQueue;
+class MessageEvent;
+
+class WebSocketChannelChild final : public BaseWebSocketChannel,
+ public PWebSocketChild,
+ public NeckoTargetHolder {
+ friend class PWebSocketChild;
+
+ public:
+ explicit WebSocketChannelChild(bool aEncrypted);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
+ //
+ NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aInnerWindowID, nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) override;
+ NS_IMETHOD AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext) override;
+ NS_IMETHOD Close(uint16_t code, const nsACString& reason) override;
+ NS_IMETHOD SendMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryStream(nsIInputStream* aStream,
+ uint32_t aLength) override;
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ // Off main thread URI access.
+ void GetEffectiveURL(nsAString& aEffectiveURL) const override;
+ bool IsEncrypted() const override;
+
+ private:
+ ~WebSocketChannelChild();
+
+ mozilla::ipc::IPCResult RecvOnStart(const nsACString& aProtocol,
+ const nsACString& aExtensions,
+ const nsAString& aEffectiveURL,
+ const bool& aEncrypted,
+ const uint64_t& aHttpChannelId);
+ mozilla::ipc::IPCResult RecvOnStop(const nsresult& aStatusCode);
+ mozilla::ipc::IPCResult RecvOnMessageAvailable(const nsACString& aMsg,
+ const bool& aMoreData);
+ mozilla::ipc::IPCResult RecvOnBinaryMessageAvailable(const nsACString& aMsg,
+ const bool& aMoreData);
+ mozilla::ipc::IPCResult RecvOnAcknowledge(const uint32_t& aSize);
+ mozilla::ipc::IPCResult RecvOnServerClose(const uint16_t& aCode,
+ const nsACString& aReason);
+
+ void OnStart(const nsACString& aProtocol, const nsACString& aExtensions,
+ const nsAString& aEffectiveURL, const bool& aEncrypted,
+ const uint64_t& aHttpChannelId);
+ void OnStop(const nsresult& aStatusCode);
+ void OnMessageAvailable(const nsACString& aMsg);
+ void OnBinaryMessageAvailable(const nsACString& aMsg);
+ void OnAcknowledge(const uint32_t& aSize);
+ void OnServerClose(const uint16_t& aCode, const nsACString& aReason);
+ void AsyncOpenFailed();
+
+ void MaybeReleaseIPCObject();
+
+ // This function tries to get a labeled event target for |mNeckoTarget|.
+ void SetupNeckoTarget();
+
+ bool RecvOnMessageAvailableInternal(const nsACString& aMsg, bool aMoreData,
+ bool aBinary);
+
+ void OnError();
+
+ RefPtr<ChannelEventQueue> mEventQ;
+ nsString mEffectiveURL;
+ nsCString mReceivedMsgBuffer;
+
+ // This variable is protected by mutex.
+ enum { Opened, Closing, Closed } mIPCState;
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+
+ friend class StartEvent;
+ friend class StopEvent;
+ friend class MessageEvent;
+ friend class AcknowledgeEvent;
+ friend class ServerCloseEvent;
+ friend class AsyncOpenFailedEvent;
+ friend class OnErrorEvent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannelChild_h
diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.cpp b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
new file mode 100644
index 0000000000..b690f3ac27
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -0,0 +1,360 @@
+/* -*- 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 "WebSocketLog.h"
+#include "WebSocketChannelParent.h"
+#include "nsIAuthPromptProvider.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/WebSocketChannel.h"
+#include "nsComponentManagerUtils.h"
+#include "IPCTransportProvider.h"
+#include "mozilla/net/ChannelEventQueue.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WebSocketChannelParent, nsIWebSocketListener,
+ nsIInterfaceRequestor)
+
+WebSocketChannelParent::WebSocketChannelParent(
+ nsIAuthPromptProvider* aAuthProvider, nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus, uint32_t aSerial)
+ : mAuthProvider(aAuthProvider),
+ mLoadContext(aLoadContext),
+ mSerial(aSerial) {
+ // Websocket channels can't have a private browsing override
+ MOZ_ASSERT_IF(!aLoadContext, aOverrideStatus == kPBOverride_Unset);
+}
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::PWebSocketChannelParent
+//-----------------------------------------------------------------------------
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvDeleteSelf() {
+ LOG(("WebSocketChannelParent::RecvDeleteSelf() %p\n", this));
+ mChannel = nullptr;
+ mAuthProvider = nullptr;
+ IProtocol* mgr = Manager();
+ if (CanRecv() && !Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvAsyncOpen(
+ nsIURI* aURI, const nsCString& aOrigin,
+ const OriginAttributes& aOriginAttributes, const uint64_t& aInnerWindowID,
+ const nsCString& aProtocol, const bool& aSecure,
+ const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const Maybe<PTransportProviderParent*>& aTransportProvider,
+ const nsCString& aNegotiatedExtensions) {
+ LOG(("WebSocketChannelParent::RecvAsyncOpen() %p\n", this));
+
+ nsresult rv;
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsCOMPtr<nsIURI> uri;
+
+ rv = LoadInfoArgsToLoadInfo(
+ aLoadInfoArgs,
+ mozilla::dom::ContentParent::Cast(Manager()->Manager())->GetRemoteType(),
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+
+ if (aSecure) {
+ mChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
+ } else {
+ mChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
+ }
+ if (NS_FAILED(rv)) goto fail;
+
+ rv = mChannel->SetSerial(mSerial);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ goto fail;
+ }
+
+ rv = mChannel->SetLoadInfo(loadInfo);
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+
+ rv = mChannel->SetNotificationCallbacks(this);
+ if (NS_FAILED(rv)) goto fail;
+
+ rv = mChannel->SetProtocol(aProtocol);
+ if (NS_FAILED(rv)) goto fail;
+
+ if (aTransportProvider.isSome()) {
+ RefPtr<TransportProviderParent> provider =
+ static_cast<TransportProviderParent*>(aTransportProvider.value());
+ rv = mChannel->SetServerParameters(provider, aNegotiatedExtensions);
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+ } else {
+ uri = aURI;
+ if (!uri) {
+ rv = NS_ERROR_FAILURE;
+ goto fail;
+ }
+ }
+
+ // only use ping values from child if they were overridden by client code.
+ if (aClientSetPingInterval) {
+ // IDL allows setting in seconds, so must be multiple of 1000 ms
+ MOZ_ASSERT(aPingInterval >= 1000 && !(aPingInterval % 1000));
+ DebugOnly<nsresult> rv = mChannel->SetPingInterval(aPingInterval / 1000);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if (aClientSetPingTimeout) {
+ MOZ_ASSERT(aPingTimeout >= 1000 && !(aPingTimeout % 1000));
+ DebugOnly<nsresult> rv = mChannel->SetPingTimeout(aPingTimeout / 1000);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = mChannel->AsyncOpenNative(uri, aOrigin, aOriginAttributes,
+ aInnerWindowID, this, nullptr);
+ if (NS_FAILED(rv)) goto fail;
+
+ return IPC_OK();
+
+fail:
+ mChannel = nullptr;
+ if (!SendOnStop(rv)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvClose(
+ const uint16_t& code, const nsCString& reason) {
+ LOG(("WebSocketChannelParent::RecvClose() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->Close(code, reason);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvSendMsg(
+ const nsCString& aMsg) {
+ LOG(("WebSocketChannelParent::RecvSendMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvSendBinaryMsg(
+ const nsCString& aMsg) {
+ LOG(("WebSocketChannelParent::RecvSendBinaryMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendBinaryMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvSendBinaryStream(
+ const IPCStream& aStream, const uint32_t& aLength) {
+ LOG(("WebSocketChannelParent::RecvSendBinaryStream() %p\n", this));
+ if (mChannel) {
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+ if (!stream) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ nsresult rv = mChannel->SendBinaryStream(stream, aLength);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStart(nsISupports* aContext) {
+ LOG(("WebSocketChannelParent::OnStart() %p\n", this));
+ nsAutoCString protocol, extensions;
+ nsString effectiveURL;
+ bool encrypted = false;
+ uint64_t httpChannelId = 0;
+ if (mChannel) {
+ DebugOnly<nsresult> rv = mChannel->GetProtocol(protocol);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mChannel->GetExtensions(extensions);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ RefPtr<WebSocketChannel> channel;
+ channel = static_cast<WebSocketChannel*>(mChannel.get());
+ MOZ_ASSERT(channel);
+
+ channel->GetEffectiveURL(effectiveURL);
+ encrypted = channel->IsEncrypted();
+ httpChannelId = channel->HttpChannelId();
+ }
+ if (!CanRecv() || !SendOnStart(protocol, extensions, effectiveURL, encrypted,
+ httpChannelId)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStop(nsISupports* aContext, nsresult aStatusCode) {
+ LOG(("WebSocketChannelParent::OnStop() %p\n", this));
+ if (!CanRecv() || !SendOnStop(aStatusCode)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+static bool SendOnMessageAvailableHelper(
+ const nsACString& aMsg,
+ const std::function<bool(const nsDependentCSubstring&, bool)>& aSendFunc) {
+ // To avoid the crash caused by too large IPC message, we have to split the
+ // data in small chunks and send them to child process. Note that the chunk
+ // size used here is the same as what we used for PHttpChannel.
+ static uint32_t const kCopyChunkSize = 128 * 1024;
+ uint32_t count = aMsg.Length();
+ if (count <= kCopyChunkSize) {
+ return aSendFunc(nsDependentCSubstring(aMsg), false);
+ }
+
+ uint32_t start = 0;
+ uint32_t toRead = std::min<uint32_t>(count, kCopyChunkSize);
+ while (count) {
+ nsDependentCSubstring data(Substring(aMsg, start, toRead));
+
+ if (!aSendFunc(data, count > kCopyChunkSize)) {
+ return false;
+ }
+
+ start += toRead;
+ count -= toRead;
+ toRead = std::min<uint32_t>(count, kCopyChunkSize);
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ LOG(("WebSocketChannelParent::OnMessageAvailable() %p\n", this));
+
+ if (!CanRecv()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto sendFunc = [self = UnsafePtr<WebSocketChannelParent>(this)](
+ const nsDependentCSubstring& aMsg, bool aMoreData) {
+ return self->SendOnMessageAvailable(aMsg, aMoreData);
+ };
+
+ if (!SendOnMessageAvailableHelper(aMsg, sendFunc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnBinaryMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ LOG(("WebSocketChannelParent::OnBinaryMessageAvailable() %p\n", this));
+
+ if (!CanRecv()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto sendFunc = [self = UnsafePtr<WebSocketChannelParent>(this)](
+ const nsDependentCSubstring& aMsg, bool aMoreData) {
+ return self->SendOnBinaryMessageAvailable(aMsg, aMoreData);
+ };
+
+ if (!SendOnMessageAvailableHelper(aMsg, sendFunc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnAcknowledge(nsISupports* aContext, uint32_t aSize) {
+ LOG(("WebSocketChannelParent::OnAcknowledge() %p\n", this));
+ if (!CanRecv() || !SendOnAcknowledge(aSize)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnServerClose(nsISupports* aContext, uint16_t code,
+ const nsACString& reason) {
+ LOG(("WebSocketChannelParent::OnServerClose() %p\n", this));
+ if (!CanRecv() || !SendOnServerClose(code, reason)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnError() { return NS_OK; }
+
+void WebSocketChannelParent::ActorDestroy(ActorDestroyReason why) {
+ LOG(("WebSocketChannelParent::ActorDestroy() %p\n", this));
+
+ // Make sure we close the channel if the content process dies without going
+ // through a clean shutdown.
+ if (mChannel) {
+ Unused << mChannel->Close(nsIWebSocketChannel::CLOSE_GOING_AWAY,
+ "Child was killed"_ns);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelParent::GetInterface(const nsIID& iid, void** result) {
+ LOG(("WebSocketChannelParent::GetInterface() %p\n", this));
+ if (mAuthProvider && iid.Equals(NS_GET_IID(nsIAuthPromptProvider))) {
+ nsresult rv = mAuthProvider->GetAuthPrompt(
+ nsIAuthPromptProvider::PROMPT_NORMAL, iid, result);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (iid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(iid, result);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.h b/netwerk/protocol/websocket/WebSocketChannelParent.h
new file mode 100644
index 0000000000..534a737dcb
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.h
@@ -0,0 +1,70 @@
+/* -*- 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_WebSocketChannelParent_h
+#define mozilla_net_WebSocketChannelParent_h
+
+#include "mozilla/net/PWebSocketParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebSocketListener.h"
+#include "nsIWebSocketChannel.h"
+#include "nsILoadContext.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsIAuthPromptProvider;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketChannelParent : public PWebSocketParent,
+ public nsIWebSocketListener,
+ public nsIInterfaceRequestor {
+ friend class PWebSocketParent;
+
+ ~WebSocketChannelParent() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus, uint32_t aSerial);
+
+ private:
+ mozilla::ipc::IPCResult RecvAsyncOpen(
+ nsIURI* aURI, const nsCString& aOrigin,
+ const OriginAttributes& aOriginAttributes, const uint64_t& aInnerWindowID,
+ const nsCString& aProtocol, const bool& aSecure,
+ const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const Maybe<PTransportProviderParent*>& aTransportProvider,
+ const nsCString& aNegotiatedExtensions);
+ mozilla::ipc::IPCResult RecvClose(const uint16_t& code,
+ const nsCString& reason);
+ mozilla::ipc::IPCResult RecvSendMsg(const nsCString& aMsg);
+ mozilla::ipc::IPCResult RecvSendBinaryMsg(const nsCString& aMsg);
+ mozilla::ipc::IPCResult RecvSendBinaryStream(const IPCStream& aStream,
+ const uint32_t& aLength);
+ mozilla::ipc::IPCResult RecvDeleteSelf();
+
+ void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ uint32_t mSerial;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannelParent_h
diff --git a/netwerk/protocol/websocket/WebSocketConnection.cpp b/netwerk/protocol/websocket/WebSocketConnection.cpp
new file mode 100644
index 0000000000..b57f4fc115
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnection.cpp
@@ -0,0 +1,261 @@
+/* -*- 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 "nsIInterfaceRequestor.h"
+#include "WebSocketConnection.h"
+
+#include "WebSocketLog.h"
+#include "mozilla/net/WebSocketConnectionListener.h"
+#include "nsIOService.h"
+#include "nsITLSSocketControl.h"
+#include "nsISocketTransport.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(WebSocketConnection, nsIInputStreamCallback,
+ nsIOutputStreamCallback)
+
+WebSocketConnection::WebSocketConnection(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream)
+ : mTransport(aTransport),
+ mSocketIn(aInputStream),
+ mSocketOut(aOutputStream) {
+ LOG(("WebSocketConnection ctor %p\n", this));
+}
+
+WebSocketConnection::~WebSocketConnection() {
+ LOG(("WebSocketConnection dtor %p\n", this));
+}
+
+nsresult WebSocketConnection::Init(WebSocketConnectionListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListener = aListener;
+ nsresult rv;
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!mTransport) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(mListener);
+ mTransport->SetSecurityCallbacks(callbacks);
+ } else {
+ // NOTE: we don't use security callbacks in socket process.
+ mTransport->SetSecurityCallbacks(nullptr);
+ }
+ return mTransport->SetEventSink(nullptr, nullptr);
+}
+
+void WebSocketConnection::GetIoTarget(nsIEventTarget** aTarget) {
+ nsCOMPtr<nsIEventTarget> target = mSocketThread;
+ return target.forget(aTarget);
+}
+
+void WebSocketConnection::Close() {
+ LOG(("WebSocketConnection::Close %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mTransport) {
+ mTransport->SetSecurityCallbacks(nullptr);
+ mTransport->SetEventSink(nullptr, nullptr);
+ mTransport->Close(NS_BASE_STREAM_CLOSED);
+ mTransport = nullptr;
+ }
+
+ if (mSocketIn) {
+ if (mStartReadingCalled) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mSocketIn = nullptr;
+ }
+
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOut = nullptr;
+ }
+}
+
+nsresult WebSocketConnection::WriteOutputData(nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mSocketOut) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mOutputQueue.emplace_back(std::move(aData));
+ return OnOutputStreamReady(mSocketOut);
+}
+
+nsresult WebSocketConnection::WriteOutputData(const uint8_t* aHdrBuf,
+ uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) {
+ MOZ_ASSERT_UNREACHABLE("Should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult WebSocketConnection::StartReading() {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mSocketIn) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(!mStartReadingCalled, "StartReading twice");
+ mStartReadingCalled = true;
+ return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+}
+
+void WebSocketConnection::DrainSocketData() {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mSocketIn || !mListener) {
+ return;
+ }
+
+ // If we leave any data unconsumed (including the tcp fin) a RST will be
+ // generated The right thing to do here is shutdown(SHUT_WR) and then wait a
+ // little while to see if any data comes in.. but there is no reason to delay
+ // things for that when the websocket handshake is supposed to guarantee a
+ // quiet connection except for that fin.
+ char buffer[512];
+ uint32_t count = 0;
+ uint32_t total = 0;
+ nsresult rv;
+ do {
+ total += count;
+ rv = mSocketIn->Read(buffer, 512, &count);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && (NS_FAILED(rv) || count == 0)) {
+ mListener->OnTCPClosed();
+ }
+ } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
+}
+
+nsresult WebSocketConnection::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketConnection::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ *aSecurityInfo = nullptr;
+
+ if (mTransport) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ nsresult rv =
+ mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(
+ do_QueryInterface(tlsSocketControl));
+ if (securityInfo) {
+ securityInfo.forget(aSecurityInfo);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnection::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ LOG(("WebSocketConnection::OnInputStreamReady() %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mListener);
+
+ // did we we clean up the socket after scheduling InputReady?
+ if (!mSocketIn) {
+ return NS_OK;
+ }
+
+ // this is after the http upgrade - so we are speaking websockets
+ uint8_t buffer[2048];
+ uint32_t count;
+ nsresult rv;
+
+ do {
+ rv = mSocketIn->Read((char*)buffer, 2048, &count);
+ LOG(("WebSocketConnection::OnInputStreamReady: read %u rv %" PRIx32 "\n",
+ count, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ mListener->OnError(rv);
+ return rv;
+ }
+
+ if (count == 0) {
+ mListener->OnError(NS_BASE_STREAM_CLOSED);
+ return NS_OK;
+ }
+
+ rv = mListener->OnDataReceived(buffer, count);
+ if (NS_FAILED(rv)) {
+ mListener->OnError(rv);
+ return rv;
+ }
+ } while (NS_SUCCEEDED(rv) && mSocketIn && mListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnection::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ LOG(("WebSocketConnection::OnOutputStreamReady() %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mListener);
+
+ if (!mSocketOut) {
+ return NS_OK;
+ }
+
+ while (!mOutputQueue.empty()) {
+ const OutputData& data = mOutputQueue.front();
+
+ char* buffer = reinterpret_cast<char*>(
+ const_cast<uint8_t*>(data.GetData().Elements())) +
+ mWriteOffset;
+ uint32_t toWrite = data.GetData().Length() - mWriteOffset;
+
+ uint32_t wrote = 0;
+ nsresult rv = mSocketOut->Write(buffer, toWrite, &wrote);
+ LOG(("WebSocketConnection::OnOutputStreamReady: write %u rv %" PRIx32,
+ wrote, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketConnection::OnOutputStreamReady %p failed %u\n", this,
+ static_cast<uint32_t>(rv)));
+ mListener->OnError(rv);
+ return NS_OK;
+ }
+
+ mWriteOffset += wrote;
+
+ if (toWrite == wrote) {
+ mWriteOffset = 0;
+ mOutputQueue.pop_front();
+ } else {
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/websocket/WebSocketConnection.h b/netwerk/protocol/websocket/WebSocketConnection.h
new file mode 100644
index 0000000000..4e6a53b013
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnection.h
@@ -0,0 +1,79 @@
+/* -*- 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_WebSocketConnection_h
+#define mozilla_net_WebSocketConnection_h
+
+#include <list>
+
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "mozilla/net/WebSocketConnectionBase.h"
+#include "nsTArray.h"
+#include "nsISocketTransport.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener;
+
+class WebSocketConnection : public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public WebSocketConnectionBase {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ explicit WebSocketConnection(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream);
+
+ nsresult Init(WebSocketConnectionListener* aListener) override;
+ void GetIoTarget(nsIEventTarget** aTarget) override;
+ void Close() override;
+ nsresult WriteOutputData(const uint8_t* aHdrBuf, uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) override;
+ nsresult WriteOutputData(nsTArray<uint8_t>&& aData);
+ nsresult StartReading() override;
+ void DrainSocketData() override;
+ nsresult GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ private:
+ virtual ~WebSocketConnection();
+
+ class OutputData {
+ public:
+ explicit OutputData(nsTArray<uint8_t>&& aData) : mData(std::move(aData)) {
+ MOZ_COUNT_CTOR(OutputData);
+ }
+
+ ~OutputData() { MOZ_COUNT_DTOR(OutputData); }
+
+ const nsTArray<uint8_t>& GetData() const { return mData; }
+
+ private:
+ nsTArray<uint8_t> mData;
+ };
+
+ RefPtr<WebSocketConnectionListener> mListener;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+ size_t mWriteOffset{0};
+ std::list<OutputData> mOutputQueue;
+ bool mStartReadingCalled{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnection_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionBase.h b/netwerk/protocol/websocket/WebSocketConnectionBase.h
new file mode 100644
index 0000000000..a462b8a346
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionBase.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_WebSocketConnectionBase_h
+#define mozilla_net_WebSocketConnectionBase_h
+
+// Architecture view:
+// Parent Process Socket Process
+// ┌─────────────────┐ IPC ┌────────────────────────┐
+// │ WebSocketChannel│ ┌─────►WebSocketConnectionChild│
+// └─────────┬───────┘ │ └─────────┬──────────────┘
+// │ │ │
+// ┌──────────▼───────────────┐ │ ┌─────────▼─────────┐
+// │ WebSocketConnectionParent├──┘ │WebSocketConnection│
+// └──────────────────────────┘ └─────────┬─────────┘
+// │
+// ┌─────────▼─────────┐
+// │ nsSocketTransport │
+// └───────────────────┘
+// The main reason that we need WebSocketConnectionBase is that we need to
+// encapsulate nsSockTransport, nsSocketOutoutStream, and nsSocketInputStream.
+// These three objects only live in socket process, so we provide the necessary
+// interfaces in WebSocketConnectionBase and WebSocketConnectionListener for
+// reading/writing the real socket.
+//
+// The output path when WebSocketChannel wants to write data to socket:
+// - WebSocketConnectionParent::WriteOutputData
+// - WebSocketConnectionParent::SendWriteOutputData
+// - WebSocketConnectionChild::RecvWriteOutputData
+// - WebSocketConnection::WriteOutputData
+// - WebSocketConnection::OnOutputStreamReady (writes data to the real socket)
+//
+// The input path when data is able to read from the socket
+// - WebSocketConnection::OnInputStreamReady
+// - WebSocketConnectionChild::OnDataReceived
+// - WebSocketConnectionChild::SendOnDataReceived
+// - WebSocketConnectionParent::RecvOnDataReceived
+// - WebSocketChannel::OnDataReceived
+//
+// The path that WebSocketConnection is constructed.
+// - nsHttpChannel::OnStopRequest
+// - HttpConnectionMgrShell::CompleteUpgrade
+// - HttpConnectionMgrParent::CompleteUpgrade (we store the
+// nsIHttpUpgradeListener in a table and send an id to socket process)
+// - HttpConnectionMgrParent::SendStartWebSocketConnection
+// - HttpConnectionMgrChild::RecvStartWebSocketConnection (the listener id is
+// saved in WebSocketConnectionChild and will be used when the socket
+// transport is available)
+// - WebSocketConnectionChild::Init (an IPC channel between socket thread in
+// socket process and background thread in parent process is created)
+// - nsHttpConnectionMgr::CompleteUpgrade
+// - WebSocketConnectionChild::OnTransportAvailable (WebSocketConnection is
+// created)
+// - WebSocketConnectionChild::SendOnTransportAvailable
+// - WebSocketConnectionParent::RecvOnTransportAvailable
+// - WebSocketChannel::OnWebSocketConnectionAvailable
+
+class nsITransportSecurityInfo;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener;
+
+class WebSocketConnectionBase : public nsISupports {
+ public:
+ virtual nsresult Init(WebSocketConnectionListener* aListener) = 0;
+ virtual void GetIoTarget(nsIEventTarget** aTarget) = 0;
+ virtual void Close() = 0;
+ virtual nsresult WriteOutputData(const uint8_t* aHdrBuf,
+ uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) = 0;
+ virtual nsresult StartReading() = 0;
+ virtual void DrainSocketData() = 0;
+ virtual nsresult GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionBase_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionChild.cpp b/netwerk/protocol/websocket/WebSocketConnectionChild.cpp
new file mode 100644
index 0000000000..045c3763b7
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionChild.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "WebSocketConnectionChild.h"
+
+#include "WebSocketConnection.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/SocketProcessBackgroundChild.h"
+#include "nsISerializable.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsNetCID.h"
+#include "nsSerializationHelper.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WebSocketConnectionChild, nsIHttpUpgradeListener)
+
+WebSocketConnectionChild::WebSocketConnectionChild() {
+ LOG(("WebSocketConnectionChild ctor %p\n", this));
+}
+
+WebSocketConnectionChild::~WebSocketConnectionChild() {
+ LOG(("WebSocketConnectionChild dtor %p\n", this));
+}
+
+void WebSocketConnectionChild::Init(uint32_t aListenerId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!mSocketThread) {
+ return;
+ }
+
+ ipc::Endpoint<PWebSocketConnectionParent> parentEndpoint;
+ ipc::Endpoint<PWebSocketConnectionChild> childEndpoint;
+ PWebSocketConnection::CreateEndpoints(&parentEndpoint, &childEndpoint);
+
+ if (NS_FAILED(SocketProcessBackgroundChild::WithActor(
+ "SendInitWebSocketConnection",
+ [aListenerId, endpoint = std::move(parentEndpoint)](
+ SocketProcessBackgroundChild* aActor) mutable {
+ Unused << aActor->SendInitWebSocketConnection(std::move(endpoint),
+ aListenerId);
+ }))) {
+ return;
+ }
+
+ mSocketThread->Dispatch(NS_NewRunnableFunction(
+ "BindWebSocketConnectionChild",
+ [self = RefPtr{this}, endpoint = std::move(childEndpoint)]() mutable {
+ endpoint.Bind(self);
+ }));
+}
+
+// nsIHttpUpgradeListener
+NS_IMETHODIMP
+WebSocketConnectionChild::OnTransportAvailable(
+ nsISocketTransport* aTransport, nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut) {
+ LOG(("WebSocketConnectionChild::OnTransportAvailable %p\n", this));
+ if (!OnSocketThread()) {
+ nsCOMPtr<nsISocketTransport> transport = aTransport;
+ nsCOMPtr<nsIAsyncInputStream> inputStream = aSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream = aSocketOut;
+ RefPtr<WebSocketConnectionChild> self = this;
+ return mSocketThread->Dispatch(
+ NS_NewRunnableFunction("WebSocketConnectionChild::OnTransportAvailable",
+ [self, transport, inputStream, outputStream]() {
+ Unused << self->OnTransportAvailable(
+ transport, inputStream, outputStream);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ LOG(("WebSocketConnectionChild::OnTransportAvailable %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(!mConnection, "already called");
+ MOZ_ASSERT(aTransport);
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ aTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(
+ do_QueryInterface(tlsSocketControl));
+
+ RefPtr<WebSocketConnection> connection =
+ new WebSocketConnection(aTransport, aSocketIn, aSocketOut);
+ nsresult rv = connection->Init(this);
+ if (NS_FAILED(rv)) {
+ Unused << OnUpgradeFailed(rv);
+ return NS_OK;
+ }
+
+ mConnection = std::move(connection);
+
+ Unused << SendOnTransportAvailable(securityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnectionChild::OnUpgradeFailed(nsresult aReason) {
+ if (!OnSocketThread()) {
+ return mSocketThread->Dispatch(NewRunnableMethod<nsresult>(
+ "WebSocketConnectionChild::OnUpgradeFailed", this,
+ &WebSocketConnectionChild::OnUpgradeFailed, aReason));
+ }
+
+ if (CanSend()) {
+ Unused << SendOnUpgradeFailed(aReason);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnectionChild::OnWebSocketConnectionAvailable(
+ WebSocketConnectionBase* aConnection) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::RecvWriteOutputData(
+ nsTArray<uint8_t>&& aData) {
+ LOG(("WebSocketConnectionChild::RecvWriteOutputData %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->WriteOutputData(std::move(aData));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::RecvStartReading() {
+ LOG(("WebSocketConnectionChild::RecvStartReading %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->StartReading();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::RecvDrainSocketData() {
+ LOG(("WebSocketConnectionChild::RecvDrainSocketData %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->DrainSocketData();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::Recv__delete__() {
+ LOG(("WebSocketConnectionChild::Recv__delete__ %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->Close();
+ mConnection = nullptr;
+ return IPC_OK();
+}
+
+void WebSocketConnectionChild::OnError(nsresult aStatus) {
+ LOG(("WebSocketConnectionChild::OnError %p\n", this));
+
+ if (CanSend()) {
+ Unused << SendOnError(aStatus);
+ }
+}
+
+void WebSocketConnectionChild::OnTCPClosed() {
+ LOG(("WebSocketConnectionChild::OnTCPClosed %p\n", this));
+
+ if (CanSend()) {
+ Unused << SendOnTCPClosed();
+ }
+}
+
+nsresult WebSocketConnectionChild::OnDataReceived(uint8_t* aData,
+ uint32_t aCount) {
+ LOG(("WebSocketConnectionChild::OnDataReceived %p\n", this));
+
+ if (CanSend()) {
+ nsTArray<uint8_t> data;
+ data.AppendElements(aData, aCount);
+ Unused << SendOnDataReceived(data);
+ }
+
+ return NS_OK;
+}
+
+void WebSocketConnectionChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("WebSocketConnectionChild::ActorDestroy %p\n", this));
+ if (mConnection) {
+ mConnection->Close();
+ mConnection = nullptr;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketConnectionChild.h b/netwerk/protocol/websocket/WebSocketConnectionChild.h
new file mode 100644
index 0000000000..a0f16b7fa8
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionChild.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_WebSocketConnectionChild_h
+#define mozilla_net_WebSocketConnectionChild_h
+
+#include "mozilla/net/PWebSocketConnectionChild.h"
+#include "mozilla/net/WebSocketConnectionListener.h"
+#include "nsIHttpChannelInternal.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnection;
+
+// WebSocketConnectionChild only lives in socket process and uses
+// WebSocketConnection to send/read data from socket. Only IPDL holds a strong
+// reference to WebSocketConnectionChild, so the life time of
+// WebSocketConnectionChild is bound to the IPC actor.
+
+class WebSocketConnectionChild final : public PWebSocketConnectionChild,
+ public nsIHttpUpgradeListener,
+ public WebSocketConnectionListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPUPGRADELISTENER
+
+ WebSocketConnectionChild();
+ void Init(uint32_t aListenerId);
+ mozilla::ipc::IPCResult RecvWriteOutputData(nsTArray<uint8_t>&& aData);
+ mozilla::ipc::IPCResult RecvStartReading();
+ mozilla::ipc::IPCResult RecvDrainSocketData();
+ mozilla::ipc::IPCResult Recv__delete__() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void OnError(nsresult aStatus) override;
+ void OnTCPClosed() override;
+ nsresult OnDataReceived(uint8_t* aData, uint32_t aCount) override;
+
+ private:
+ virtual ~WebSocketConnectionChild();
+
+ RefPtr<WebSocketConnection> mConnection;
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionChild_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionListener.h b/netwerk/protocol/websocket/WebSocketConnectionListener.h
new file mode 100644
index 0000000000..31663f17f6
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionListener.h
@@ -0,0 +1,23 @@
+/* -*- 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_WebSocketConnectionListener_h
+#define mozilla_net_WebSocketConnectionListener_h
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener : public nsISupports {
+ public:
+ virtual void OnError(nsresult aStatus) = 0;
+ virtual void OnTCPClosed() = 0;
+ virtual nsresult OnDataReceived(uint8_t* aData, uint32_t aCount) = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionListener_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionParent.cpp b/netwerk/protocol/websocket/WebSocketConnectionParent.cpp
new file mode 100644
index 0000000000..3fdf85337e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionParent.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "WebSocketConnectionParent.h"
+
+#include "nsIHttpChannelInternal.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsSerializationHelper.h"
+#include "nsThreadUtils.h"
+#include "WebSocketConnectionListener.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS0(WebSocketConnectionParent)
+
+WebSocketConnectionParent::WebSocketConnectionParent(
+ nsIHttpUpgradeListener* aListener)
+ : mUpgradeListener(aListener),
+ mBackgroundThread(GetCurrentSerialEventTarget()) {
+ LOG(("WebSocketConnectionParent ctor %p\n", this));
+ MOZ_ASSERT(mUpgradeListener);
+}
+
+WebSocketConnectionParent::~WebSocketConnectionParent() {
+ LOG(("WebSocketConnectionParent dtor %p\n", this));
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnTransportAvailable(
+ nsITransportSecurityInfo* aSecurityInfo) {
+ LOG(("WebSocketConnectionParent::RecvOnTransportAvailable %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (aSecurityInfo) {
+ MutexAutoLock lock(mMutex);
+ mSecurityInfo = aSecurityInfo;
+ }
+
+ if (mUpgradeListener) {
+ Unused << mUpgradeListener->OnWebSocketConnectionAvailable(this);
+ mUpgradeListener = nullptr;
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnError(
+ const nsresult& aStatus) {
+ LOG(("WebSocketConnectionParent::RecvOnError %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ MOZ_ASSERT(mListener);
+ mListener->OnError(aStatus);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnUpgradeFailed(
+ const nsresult& aReason) {
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (mUpgradeListener) {
+ Unused << mUpgradeListener->OnUpgradeFailed(aReason);
+ mUpgradeListener = nullptr;
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnTCPClosed() {
+ LOG(("WebSocketConnectionParent::RecvOnTCPClosed %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ MOZ_ASSERT(mListener);
+ mListener->OnTCPClosed();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnDataReceived(
+ nsTArray<uint8_t>&& aData) {
+ LOG(("WebSocketConnectionParent::RecvOnDataReceived %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ MOZ_ASSERT(mListener);
+ uint8_t* buffer = const_cast<uint8_t*>(aData.Elements());
+ nsresult rv = mListener->OnDataReceived(buffer, aData.Length());
+ if (NS_FAILED(rv)) {
+ mListener->OnError(rv);
+ }
+
+ return IPC_OK();
+}
+
+void WebSocketConnectionParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("WebSocketConnectionParent::ActorDestroy %p aWhy=%d\n", this, aWhy));
+ if (!mClosed) {
+ // Treat this as an error when IPC is closed before
+ // WebSocketConnectionParent::Close() is called.
+ RefPtr<WebSocketConnectionListener> listener;
+ listener.swap(mListener);
+ if (listener) {
+ listener->OnError(NS_ERROR_FAILURE);
+ }
+ }
+ mBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketConnectionParent::DefereredDestroy", [self = RefPtr{this}]() {
+ LOG(("WebSocketConnectionParent::DefereredDestroy"));
+ }));
+};
+
+nsresult WebSocketConnectionParent::Init(
+ WebSocketConnectionListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListener = aListener;
+ return NS_OK;
+}
+
+void WebSocketConnectionParent::GetIoTarget(nsIEventTarget** aTarget) {
+ nsCOMPtr<nsIEventTarget> target = mBackgroundThread;
+ return target.forget(aTarget);
+}
+
+void WebSocketConnectionParent::Close() {
+ LOG(("WebSocketConnectionParent::Close %p\n", this));
+
+ mClosed = true;
+
+ auto task = [self = RefPtr{this}]() {
+ self->PWebSocketConnectionParent::Close();
+ };
+
+ if (mBackgroundThread->IsOnCurrentThread()) {
+ task();
+ } else {
+ mBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketConnectionParent::Close", std::move(task)));
+ }
+}
+
+nsresult WebSocketConnectionParent::WriteOutputData(
+ const uint8_t* aHdrBuf, uint32_t aHdrBufLength, const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) {
+ LOG(("WebSocketConnectionParent::WriteOutputData %p", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<uint8_t> data;
+ data.AppendElements(aHdrBuf, aHdrBufLength);
+ data.AppendElements(aPayloadBuf, aPayloadBufLength);
+ return SendWriteOutputData(data) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult WebSocketConnectionParent::StartReading() {
+ LOG(("WebSocketConnectionParent::StartReading %p\n", this));
+
+ RefPtr<WebSocketConnectionParent> self = this;
+ auto task = [self{std::move(self)}]() {
+ if (!self->CanSend()) {
+ if (self->mListener) {
+ self->mListener->OnError(NS_ERROR_NOT_AVAILABLE);
+ }
+ return;
+ }
+
+ Unused << self->SendStartReading();
+ };
+
+ if (mBackgroundThread->IsOnCurrentThread()) {
+ task();
+ } else {
+ mBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketConnectionParent::SendStartReading", std::move(task)));
+ }
+
+ return NS_OK;
+}
+
+void WebSocketConnectionParent::DrainSocketData() {
+ LOG(("WebSocketConnectionParent::DrainSocketData %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (!CanSend()) {
+ MOZ_ASSERT(mListener);
+ mListener->OnError(NS_ERROR_NOT_AVAILABLE);
+
+ return;
+ }
+
+ Unused << SendDrainSocketData();
+}
+
+nsresult WebSocketConnectionParent::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketConnectionParent::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+
+ MutexAutoLock lock(mMutex);
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketConnectionParent.h b/netwerk/protocol/websocket/WebSocketConnectionParent.h
new file mode 100644
index 0000000000..45b031e9cb
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionParent.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_WebSocketConnectionParent_h
+#define mozilla_net_WebSocketConnectionParent_h
+
+#include "mozilla/net/PWebSocketConnectionParent.h"
+#include "mozilla/net/WebSocketConnectionBase.h"
+#include "nsISupportsImpl.h"
+#include "WebSocketConnectionBase.h"
+
+class nsIHttpUpgradeListener;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener;
+
+// WebSocketConnectionParent implements WebSocketConnectionBase and provides
+// interface for WebSocketChannel to send/receive data. The ownership model for
+// WebSocketConnectionParent is that IPDL and WebSocketChannel hold strong
+// reference of this object. When Close() is called, a __delete__ message will
+// be sent and the IPC actor will be deallocated as well.
+
+class WebSocketConnectionParent final : public PWebSocketConnectionParent,
+ public WebSocketConnectionBase {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebSocketConnectionParent(nsIHttpUpgradeListener* aListener);
+
+ mozilla::ipc::IPCResult RecvOnTransportAvailable(
+ nsITransportSecurityInfo* aSecurityInfo);
+ mozilla::ipc::IPCResult RecvOnError(const nsresult& aStatus);
+ mozilla::ipc::IPCResult RecvOnTCPClosed();
+ mozilla::ipc::IPCResult RecvOnDataReceived(nsTArray<uint8_t>&& aData);
+ mozilla::ipc::IPCResult RecvOnUpgradeFailed(const nsresult& aReason);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsresult Init(WebSocketConnectionListener* aListener) override;
+ void GetIoTarget(nsIEventTarget** aTarget) override;
+ void Close() override;
+ nsresult WriteOutputData(const uint8_t* aHdrBuf, uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) override;
+ nsresult StartReading() override;
+ void DrainSocketData() override;
+ nsresult GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ private:
+ virtual ~WebSocketConnectionParent();
+
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+ RefPtr<WebSocketConnectionListener> mListener;
+ nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ Atomic<bool> mClosed{false};
+ Mutex mMutex MOZ_UNANNOTATED{"WebSocketConnectionParent::mMutex"};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionParent_h
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp b/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp
new file mode 100644
index 0000000000..2232475382
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp
@@ -0,0 +1,109 @@
+/* -*- 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 "WebSocketEventListenerChild.h"
+
+#include "WebSocketEventService.h"
+#include "WebSocketFrame.h"
+
+namespace mozilla {
+namespace net {
+
+WebSocketEventListenerChild::WebSocketEventListenerChild(
+ uint64_t aInnerWindowID, nsISerialEventTarget* aTarget)
+ : NeckoTargetHolder(aTarget),
+ mService(WebSocketEventService::GetOrCreate()),
+ mInnerWindowID(aInnerWindowID) {}
+
+WebSocketEventListenerChild::~WebSocketEventListenerChild() {
+ MOZ_ASSERT(!mService);
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvWebSocketCreated(
+ const uint32_t& aWebSocketSerialID, const nsString& aURI,
+ const nsCString& aProtocols) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketCreated(aWebSocketSerialID, mInnerWindowID, aURI,
+ aProtocols, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvWebSocketOpened(
+ const uint32_t& aWebSocketSerialID, const nsString& aEffectiveURI,
+ const nsCString& aProtocols, const nsCString& aExtensions,
+ const uint64_t& aHttpChannelId) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketOpened(aWebSocketSerialID, mInnerWindowID, aEffectiveURI,
+ aProtocols, aExtensions, aHttpChannelId, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebSocketEventListenerChild::RecvWebSocketMessageAvailable(
+ const uint32_t& aWebSocketSerialID, const nsCString& aData,
+ const uint16_t& aMessageType) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketMessageAvailable(aWebSocketSerialID, mInnerWindowID,
+ aData, aMessageType, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvWebSocketClosed(
+ const uint32_t& aWebSocketSerialID, const bool& aWasClean,
+ const uint16_t& aCode, const nsString& aReason) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketClosed(aWebSocketSerialID, mInnerWindowID, aWasClean,
+ aCode, aReason, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvFrameReceived(
+ const uint32_t& aWebSocketSerialID, const WebSocketFrameData& aFrameData) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameReceived(aWebSocketSerialID, mInnerWindowID, frame.forget(),
+ target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvFrameSent(
+ const uint32_t& aWebSocketSerialID, const WebSocketFrameData& aFrameData) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameSent(aWebSocketSerialID, mInnerWindowID, frame.forget(),
+ target);
+ }
+
+ return IPC_OK();
+}
+
+void WebSocketEventListenerChild::Close() {
+ mService = nullptr;
+ SendClose();
+}
+
+void WebSocketEventListenerChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mService = nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerChild.h b/netwerk/protocol/websocket/WebSocketEventListenerChild.h
new file mode 100644
index 0000000000..88dddb79a5
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventListenerChild_h
+#define mozilla_net_WebSocketEventListenerChild_h
+
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PWebSocketEventListenerChild.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketEventService;
+
+class WebSocketEventListenerChild final : public PWebSocketEventListenerChild,
+ public NeckoTargetHolder {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WebSocketEventListenerChild)
+
+ explicit WebSocketEventListenerChild(uint64_t aInnerWindowID,
+ nsISerialEventTarget* aTarget);
+
+ mozilla::ipc::IPCResult RecvWebSocketCreated(
+ const uint32_t& aWebSocketSerialID, const nsString& aURI,
+ const nsCString& aProtocols);
+
+ mozilla::ipc::IPCResult RecvWebSocketOpened(
+ const uint32_t& aWebSocketSerialID, const nsString& aEffectiveURI,
+ const nsCString& aProtocols, const nsCString& aExtensions,
+ const uint64_t& aHttpChannelId);
+
+ mozilla::ipc::IPCResult RecvWebSocketMessageAvailable(
+ const uint32_t& aWebSocketSerialID, const nsCString& aData,
+ const uint16_t& aMessageType);
+
+ mozilla::ipc::IPCResult RecvWebSocketClosed(
+ const uint32_t& aWebSocketSerialID, const bool& aWasClean,
+ const uint16_t& aCode, const nsString& aReason);
+
+ mozilla::ipc::IPCResult RecvFrameReceived(
+ const uint32_t& aWebSocketSerialID, const WebSocketFrameData& aFrameData);
+
+ mozilla::ipc::IPCResult RecvFrameSent(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData);
+
+ void Close();
+
+ private:
+ ~WebSocketEventListenerChild();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<WebSocketEventService> mService;
+ uint64_t mInnerWindowID;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketEventListenerChild_h
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
new file mode 100644
index 0000000000..5b0ea16838
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "WebSocketEventService.h"
+#include "WebSocketEventListenerParent.h"
+#include "mozilla/Unused.h"
+#include "WebSocketFrame.h"
+
+namespace mozilla {
+namespace net {
+
+NS_INTERFACE_MAP_BEGIN(WebSocketEventListenerParent)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketEventListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketEventListenerParent)
+NS_IMPL_RELEASE(WebSocketEventListenerParent)
+
+WebSocketEventListenerParent::WebSocketEventListenerParent(
+ uint64_t aInnerWindowID)
+ : mService(WebSocketEventService::GetOrCreate()),
+ mInnerWindowID(aInnerWindowID) {
+ DebugOnly<nsresult> rv = mService->AddListener(mInnerWindowID, this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+WebSocketEventListenerParent::~WebSocketEventListenerParent() {
+ MOZ_ASSERT(!mService);
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerParent::RecvClose() {
+ if (mService) {
+ UnregisterListener();
+ Unused << Send__delete__(this);
+ }
+
+ return IPC_OK();
+}
+
+void WebSocketEventListenerParent::ActorDestroy(ActorDestroyReason aWhy) {
+ UnregisterListener();
+}
+
+void WebSocketEventListenerParent::UnregisterListener() {
+ if (mService) {
+ DebugOnly<nsresult> rv = mService->RemoveListener(mInnerWindowID, this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mService = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketCreated(uint32_t aWebSocketSerialID,
+ const nsAString& aURI,
+ const nsACString& aProtocols) {
+ Unused << SendWebSocketCreated(aWebSocketSerialID, aURI, aProtocols);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketOpened(uint32_t aWebSocketSerialID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions,
+ uint64_t aHttpChannelId) {
+ Unused << SendWebSocketOpened(aWebSocketSerialID, aEffectiveURI, aProtocols,
+ aExtensions, aHttpChannelId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketClosed(uint32_t aWebSocketSerialID,
+ bool aWasClean, uint16_t aCode,
+ const nsAString& aReason) {
+ Unused << SendWebSocketClosed(aWebSocketSerialID, aWasClean, aCode, aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketMessageAvailable(
+ uint32_t aWebSocketSerialID, const nsACString& aData,
+ uint16_t aMessageType) {
+ Unused << SendWebSocketMessageAvailable(aWebSocketSerialID, aData,
+ aMessageType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::FrameReceived(uint32_t aWebSocketSerialID,
+ nsIWebSocketFrame* aFrame) {
+ if (!aFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebSocketFrame* frame = static_cast<WebSocketFrame*>(aFrame);
+ Unused << SendFrameReceived(aWebSocketSerialID, frame->Data());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::FrameSent(uint32_t aWebSocketSerialID,
+ nsIWebSocketFrame* aFrame) {
+ if (!aFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebSocketFrame* frame = static_cast<WebSocketFrame*>(aFrame);
+ Unused << SendFrameSent(aWebSocketSerialID, frame->Data());
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerParent.h b/netwerk/protocol/websocket/WebSocketEventListenerParent.h
new file mode 100644
index 0000000000..eb5e02679b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventListenerParent_h
+#define mozilla_net_WebSocketEventListenerParent_h
+
+#include "mozilla/net/PWebSocketEventListenerParent.h"
+#include "nsIWebSocketEventService.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketEventService;
+
+class WebSocketEventListenerParent final : public PWebSocketEventListenerParent,
+ public nsIWebSocketEventListener {
+ friend class PWebSocketEventListenerParent;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETEVENTLISTENER
+
+ explicit WebSocketEventListenerParent(uint64_t aInnerWindowID);
+
+ private:
+ ~WebSocketEventListenerParent();
+
+ mozilla::ipc::IPCResult RecvClose();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void UnregisterListener();
+
+ RefPtr<WebSocketEventService> mService;
+ uint64_t mInnerWindowID;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketEventListenerParent_h
diff --git a/netwerk/protocol/websocket/WebSocketEventService.cpp b/netwerk/protocol/websocket/WebSocketEventService.cpp
new file mode 100644
index 0000000000..6e9a89a005
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.cpp
@@ -0,0 +1,566 @@
+/* -*- 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 "WebSocketEventListenerChild.h"
+#include "WebSocketEventService.h"
+#include "WebSocketFrame.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/StaticPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "nsIWebSocketImpl.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+StaticRefPtr<WebSocketEventService> gWebSocketEventService;
+
+bool IsChildProcess() {
+ return XRE_GetProcessType() != GeckoProcessType_Default;
+}
+
+} // anonymous namespace
+
+class WebSocketBaseRunnable : public Runnable {
+ public:
+ WebSocketBaseRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID)
+ : Runnable("net::WebSocketBaseRunnable"),
+ mWebSocketSerialID(aWebSocketSerialID),
+ mInnerWindowID(aInnerWindowID) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<WebSocketEventService> service =
+ WebSocketEventService::GetOrCreate();
+ MOZ_ASSERT(service);
+
+ WebSocketEventService::WindowListeners listeners;
+ service->GetListeners(mInnerWindowID, listeners);
+
+ for (uint32_t i = 0; i < listeners.Length(); ++i) {
+ DoWork(listeners[i]);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ ~WebSocketBaseRunnable() = default;
+
+ virtual void DoWork(nsIWebSocketEventListener* aListener) = 0;
+
+ uint32_t mWebSocketSerialID;
+ uint64_t mInnerWindowID;
+};
+
+class WebSocketFrameRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketFrameRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ bool aFrameSent)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mFrame(std::move(aFrame)),
+ mFrameSent(aFrameSent) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv{};
+ if (mFrameSent) {
+ rv = aListener->FrameSent(mWebSocketSerialID, mFrame);
+ } else {
+ rv = aListener->FrameReceived(mWebSocketSerialID, mFrame);
+ }
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Frame op failed");
+ }
+
+ RefPtr<WebSocketFrame> mFrame;
+ bool mFrameSent;
+};
+
+class WebSocketCreatedRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketCreatedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aURI, const nsACString& aProtocols)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mURI(aURI),
+ mProtocols(aProtocols) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv =
+ aListener->WebSocketCreated(mWebSocketSerialID, mURI, mProtocols);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketCreated failed");
+ }
+
+ const nsString mURI;
+ const nsCString mProtocols;
+};
+
+class WebSocketOpenedRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketOpenedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions,
+ uint64_t aHttpChannelId)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mEffectiveURI(aEffectiveURI),
+ mProtocols(aProtocols),
+ mExtensions(aExtensions),
+ mHttpChannelId(aHttpChannelId) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv =
+ aListener->WebSocketOpened(mWebSocketSerialID, mEffectiveURI,
+ mProtocols, mExtensions, mHttpChannelId);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketOpened failed");
+ }
+
+ const nsString mEffectiveURI;
+ const nsCString mProtocols;
+ const nsCString mExtensions;
+ uint64_t mHttpChannelId;
+};
+
+class WebSocketMessageAvailableRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketMessageAvailableRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData,
+ uint16_t aMessageType)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mData(aData),
+ mMessageType(aMessageType) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv = aListener->WebSocketMessageAvailable(
+ mWebSocketSerialID, mData, mMessageType);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketMessageAvailable failed");
+ }
+
+ const nsCString mData;
+ uint16_t mMessageType;
+};
+
+class WebSocketClosedRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketClosedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ bool aWasClean, uint16_t aCode,
+ const nsAString& aReason)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mWasClean(aWasClean),
+ mCode(aCode),
+ mReason(aReason) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv = aListener->WebSocketClosed(
+ mWebSocketSerialID, mWasClean, mCode, mReason);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketClosed failed");
+ }
+
+ bool mWasClean;
+ uint16_t mCode;
+ const nsString mReason;
+};
+
+/* static */
+already_AddRefed<WebSocketEventService> WebSocketEventService::Get() {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<WebSocketEventService> service = gWebSocketEventService.get();
+ return service.forget();
+}
+
+/* static */
+already_AddRefed<WebSocketEventService> WebSocketEventService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gWebSocketEventService) {
+ gWebSocketEventService = new WebSocketEventService();
+ }
+
+ RefPtr<WebSocketEventService> service = gWebSocketEventService.get();
+ return service.forget();
+}
+
+NS_INTERFACE_MAP_BEGIN(WebSocketEventService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketEventService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketEventService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketEventService)
+NS_IMPL_RELEASE(WebSocketEventService)
+
+WebSocketEventService::WebSocketEventService() : mCountListeners(0) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+}
+
+WebSocketEventService::~WebSocketEventService() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void WebSocketEventService::WebSocketCreated(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aURI,
+ const nsACString& aProtocols,
+ nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketCreatedRunnable> runnable = new WebSocketCreatedRunnable(
+ aWebSocketSerialID, aInnerWindowID, aURI, aProtocols);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::WebSocketOpened(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions,
+ uint64_t aHttpChannelId,
+ nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketOpenedRunnable> runnable = new WebSocketOpenedRunnable(
+ aWebSocketSerialID, aInnerWindowID, aEffectiveURI, aProtocols,
+ aExtensions, aHttpChannelId);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::WebSocketMessageAvailable(
+ uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsACString& aData, uint16_t aMessageType, nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketMessageAvailableRunnable> runnable =
+ new WebSocketMessageAvailableRunnable(aWebSocketSerialID, aInnerWindowID,
+ aData, aMessageType);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::WebSocketClosed(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ bool aWasClean, uint16_t aCode,
+ const nsAString& aReason,
+ nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketClosedRunnable> runnable = new WebSocketClosedRunnable(
+ aWebSocketSerialID, aInnerWindowID, aWasClean, aCode, aReason);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::FrameReceived(
+ uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame, nsIEventTarget* aTarget) {
+ RefPtr<WebSocketFrame> frame(std::move(aFrame));
+ MOZ_ASSERT(frame);
+
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketFrameRunnable> runnable =
+ new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
+ frame.forget(), false /* frameSent */);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::FrameSent(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ nsIEventTarget* aTarget) {
+ RefPtr<WebSocketFrame> frame(std::move(aFrame));
+ MOZ_ASSERT(frame);
+
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketFrameRunnable> runnable = new WebSocketFrameRunnable(
+ aWebSocketSerialID, aInnerWindowID, frame.forget(), true /* frameSent */);
+
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::AssociateWebSocketImplWithSerialID(
+ nsIWebSocketImpl* aWebSocketImpl, uint32_t aWebSocketSerialID) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mWebSocketImplMap.InsertOrUpdate(aWebSocketSerialID,
+ do_GetWeakReference(aWebSocketImpl));
+}
+
+NS_IMETHODIMP
+WebSocketEventService::SendMessage(uint32_t aWebSocketSerialID,
+ const nsAString& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsWeakPtr weakPtr = mWebSocketImplMap.Get(aWebSocketSerialID);
+ nsCOMPtr<nsIWebSocketImpl> webSocketImpl = do_QueryReferent(weakPtr);
+
+ if (!webSocketImpl) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return webSocketImpl->SendMessage(aMessage);
+}
+
+NS_IMETHODIMP
+WebSocketEventService::AddListener(uint64_t aInnerWindowID,
+ nsIWebSocketEventListener* aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ++mCountListeners;
+
+ mWindows
+ .LookupOrInsertWith(
+ aInnerWindowID,
+ [&] {
+ auto listener = MakeUnique<WindowListener>();
+
+ if (IsChildProcess()) {
+ PWebSocketEventListenerChild* actor =
+ gNeckoChild->SendPWebSocketEventListenerConstructor(
+ aInnerWindowID);
+
+ listener->mActor =
+ static_cast<WebSocketEventListenerChild*>(actor);
+ MOZ_ASSERT(listener->mActor);
+ }
+
+ return listener;
+ })
+ ->mListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::RemoveListener(uint64_t aInnerWindowID,
+ nsIWebSocketEventListener* aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!listener->mListeners.RemoveElement(aListener)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The last listener for this window.
+ if (listener->mListeners.IsEmpty()) {
+ if (IsChildProcess()) {
+ ShutdownActorListener(listener);
+ }
+
+ mWindows.Remove(aInnerWindowID);
+ }
+
+ MOZ_ASSERT(mCountListeners);
+ --mCountListeners;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::HasListenerFor(uint64_t aInnerWindowID, bool* aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ *aResult = mWindows.Get(aInnerWindowID);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ WindowListener* listener = mWindows.Get(innerID);
+ if (!listener) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mCountListeners >= listener->mListeners.Length());
+ mCountListeners -= listener->mListeners.Length();
+
+ if (IsChildProcess()) {
+ ShutdownActorListener(listener);
+ }
+
+ mWindows.Remove(innerID);
+ }
+
+ // This should not happen.
+ return NS_ERROR_FAILURE;
+}
+
+void WebSocketEventService::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gWebSocketEventService) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(gWebSocketEventService, "xpcom-shutdown");
+ obs->RemoveObserver(gWebSocketEventService, "inner-window-destroyed");
+ }
+
+ mWindows.Clear();
+ gWebSocketEventService = nullptr;
+ }
+}
+
+bool WebSocketEventService::HasListeners() const { return !!mCountListeners; }
+
+void WebSocketEventService::GetListeners(
+ uint64_t aInnerWindowID,
+ WebSocketEventService::WindowListeners& aListeners) const {
+ aListeners.Clear();
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return;
+ }
+
+ aListeners.AppendElements(listener->mListeners);
+}
+
+void WebSocketEventService::ShutdownActorListener(WindowListener* aListener) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aListener->mActor);
+ aListener->mActor->Close();
+ aListener->mActor = nullptr;
+}
+
+already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode,
+ bool aMaskBit, uint32_t aMask, const nsCString& aPayload) {
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, aPayload);
+}
+
+already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode,
+ bool aMaskBit, uint32_t aMask, uint8_t* aPayload, uint32_t aPayloadLength) {
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ nsAutoCString payloadStr;
+ if (NS_WARN_IF(!(payloadStr.Assign((const char*)aPayload, aPayloadLength,
+ mozilla::fallible)))) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, payloadStr);
+}
+
+already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode,
+ bool aMaskBit, uint32_t aMask, uint8_t* aPayloadInHdr,
+ uint32_t aPayloadInHdrLength, uint8_t* aPayload, uint32_t aPayloadLength) {
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ uint32_t payloadLength = aPayloadLength + aPayloadInHdrLength;
+
+ nsAutoCString payload;
+ if (NS_WARN_IF(!payload.SetLength(payloadLength, fallible))) {
+ return nullptr;
+ }
+
+ char* payloadPtr = payload.BeginWriting();
+ if (aPayloadInHdrLength) {
+ memcpy(payloadPtr, aPayloadInHdr, aPayloadInHdrLength);
+ }
+
+ memcpy(payloadPtr + aPayloadInHdrLength, aPayload, aPayloadLength);
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, payload);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventService.h b/netwerk/protocol/websocket/WebSocketEventService.h
new file mode 100644
index 0000000000..0f15e5058b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.h
@@ -0,0 +1,125 @@
+/* -*- 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_WebSocketEventService_h
+#define mozilla_net_WebSocketEventService_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Atomics.h"
+#include "nsIWebSocketEventService.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTHashMap.h"
+
+class nsIWebSocketImpl;
+class nsIEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketFrame;
+class WebSocketEventListenerChild;
+
+class WebSocketEventService final : public nsIWebSocketEventService,
+ public nsIObserver {
+ friend class WebSocketBaseRunnable;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWEBSOCKETEVENTSERVICE
+
+ static already_AddRefed<WebSocketEventService> Get();
+ static already_AddRefed<WebSocketEventService> GetOrCreate();
+
+ void WebSocketCreated(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aURI, const nsACString& aProtocols,
+ nsIEventTarget* aTarget = nullptr);
+
+ void WebSocketOpened(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions, uint64_t aHttpChannelId,
+ nsIEventTarget* aTarget = nullptr);
+
+ void WebSocketMessageAvailable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData, uint16_t aMessageType,
+ nsIEventTarget* aTarget = nullptr);
+
+ void WebSocketClosed(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ bool aWasClean, uint16_t aCode, const nsAString& aReason,
+ nsIEventTarget* aTarget = nullptr);
+
+ void FrameReceived(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ nsIEventTarget* aTarget = nullptr);
+
+ void FrameSent(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ nsIEventTarget* aTarget = nullptr);
+
+ void AssociateWebSocketImplWithSerialID(nsIWebSocketImpl* aWebSocketImpl,
+ uint32_t aWebSocketSerialID);
+
+ already_AddRefed<WebSocketFrame> CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ already_AddRefed<WebSocketFrame> CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask, uint8_t* aPayload,
+ uint32_t aPayloadLength);
+
+ already_AddRefed<WebSocketFrame> CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask, uint8_t* aPayloadInHdr,
+ uint32_t aPayloadInHdrLength, uint8_t* aPayload, uint32_t aPayloadLength);
+
+ private:
+ WebSocketEventService();
+ ~WebSocketEventService();
+
+ bool HasListeners() const;
+ void Shutdown();
+
+ using WindowListeners = nsTArray<nsCOMPtr<nsIWebSocketEventListener>>;
+
+ nsTHashMap<nsUint32HashKey, nsWeakPtr> mWebSocketImplMap;
+
+ struct WindowListener {
+ WindowListeners mListeners;
+ RefPtr<WebSocketEventListenerChild> mActor;
+ };
+
+ void GetListeners(uint64_t aInnerWindowID, WindowListeners& aListeners) const;
+
+ void ShutdownActorListener(WindowListener* aListener);
+
+ // Used only on the main-thread.
+ nsClassHashtable<nsUint64HashKey, WindowListener> mWindows;
+
+ Atomic<uint64_t> mCountListeners;
+};
+
+} // namespace net
+} // namespace mozilla
+
+/**
+ * Casting WebSocketEventService to nsISupports is ambiguous.
+ * This method handles that.
+ */
+inline nsISupports* ToSupports(mozilla::net::WebSocketEventService* p) {
+ return NS_ISUPPORTS_CAST(nsIWebSocketEventService*, p);
+}
+
+#endif // mozilla_net_WebSocketEventService_h
diff --git a/netwerk/protocol/websocket/WebSocketFrame.cpp b/netwerk/protocol/websocket/WebSocketFrame.cpp
new file mode 100644
index 0000000000..745b598ba9
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.cpp
@@ -0,0 +1,133 @@
+/* -*- 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 "WebSocketFrame.h"
+
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "mozilla/Assertions.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsSocketTransportService2.h"
+#include "nscore.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace net {
+
+NS_INTERFACE_MAP_BEGIN(WebSocketFrame)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketFrame)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketFrame)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketFrame)
+NS_IMPL_RELEASE(WebSocketFrame)
+
+WebSocketFrame::WebSocketFrame(const WebSocketFrameData& aData)
+ : mData(aData) {}
+
+WebSocketFrame::WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2,
+ bool aRsvBit3, uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask, const nsCString& aPayload)
+ : mData(PR_Now(), aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode, aMaskBit,
+ aMask, aPayload) {
+ // This could be called on the background thread when socket process is
+ // enabled.
+ mData.mTimeStamp = PR_Now();
+}
+
+#define WSF_GETTER(method, value, type) \
+ NS_IMETHODIMP \
+ WebSocketFrame::method(type* aValue) { \
+ MOZ_ASSERT(NS_IsMainThread()); \
+ if (!aValue) { \
+ return NS_ERROR_FAILURE; \
+ } \
+ *aValue = value; \
+ return NS_OK; \
+ }
+
+WSF_GETTER(GetTimeStamp, mData.mTimeStamp, DOMHighResTimeStamp);
+WSF_GETTER(GetFinBit, mData.mFinBit, bool);
+WSF_GETTER(GetRsvBit1, mData.mRsvBit1, bool);
+WSF_GETTER(GetRsvBit2, mData.mRsvBit2, bool);
+WSF_GETTER(GetRsvBit3, mData.mRsvBit3, bool);
+WSF_GETTER(GetOpCode, mData.mOpCode, uint16_t);
+WSF_GETTER(GetMaskBit, mData.mMaskBit, bool);
+WSF_GETTER(GetMask, mData.mMask, uint32_t);
+
+#undef WSF_GETTER
+
+NS_IMETHODIMP
+WebSocketFrame::GetPayload(nsACString& aValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ aValue = mData.mPayload;
+ return NS_OK;
+}
+
+WebSocketFrameData::WebSocketFrameData()
+ : mFinBit(false),
+ mRsvBit1(false),
+ mRsvBit2(false),
+ mRsvBit3(false),
+ mMaskBit(false) {}
+
+WebSocketFrameData::WebSocketFrameData(DOMHighResTimeStamp aTimeStamp,
+ bool aFinBit, bool aRsvBit1,
+ bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask,
+ const nsCString& aPayload)
+ : mTimeStamp(aTimeStamp),
+ mFinBit(aFinBit),
+ mRsvBit1(aRsvBit1),
+ mRsvBit2(aRsvBit2),
+ mRsvBit3(aRsvBit3),
+ mMaskBit(aMaskBit),
+ mOpCode(aOpCode),
+ mMask(aMask),
+ mPayload(aPayload) {}
+
+void WebSocketFrameData::WriteIPCParams(IPC::MessageWriter* aWriter) const {
+ WriteParam(aWriter, mTimeStamp);
+ WriteParam(aWriter, mFinBit);
+ WriteParam(aWriter, mRsvBit1);
+ WriteParam(aWriter, mRsvBit2);
+ WriteParam(aWriter, mRsvBit3);
+ WriteParam(aWriter, mMaskBit);
+ WriteParam(aWriter, mOpCode);
+ WriteParam(aWriter, mMask);
+ WriteParam(aWriter, mPayload);
+}
+
+bool WebSocketFrameData::ReadIPCParams(IPC::MessageReader* aReader) {
+ if (!ReadParam(aReader, &mTimeStamp)) {
+ return false;
+ }
+
+#define ReadParamHelper(x) \
+ { \
+ bool bit; \
+ if (!ReadParam(aReader, &bit)) { \
+ return false; \
+ } \
+ (x) = bit; \
+ }
+
+ ReadParamHelper(mFinBit);
+ ReadParamHelper(mRsvBit1);
+ ReadParamHelper(mRsvBit2);
+ ReadParamHelper(mRsvBit3);
+ ReadParamHelper(mMaskBit);
+
+#undef ReadParamHelper
+
+ return ReadParam(aReader, &mOpCode) && ReadParam(aReader, &mMask) &&
+ ReadParam(aReader, &mPayload);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketFrame.h b/netwerk/protocol/websocket/WebSocketFrame.h
new file mode 100644
index 0000000000..8bcb672692
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.h
@@ -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/. */
+
+#ifndef mozilla_net_WebSocketFrame_h
+#define mozilla_net_WebSocketFrame_h
+
+#include <cstdint>
+#include "nsISupports.h"
+#include "nsIWebSocketEventService.h"
+#include "nsString.h"
+
+class PickleIterator;
+
+// Avoid including nsDOMNavigationTiming.h here, where the canonical definition
+// of DOMHighResTimeStamp resides.
+using DOMHighResTimeStamp = double;
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+template <class P>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+class WebSocketFrameData final {
+ public:
+ WebSocketFrameData();
+
+ explicit WebSocketFrameData(const WebSocketFrameData&) = default;
+ WebSocketFrameData(WebSocketFrameData&&) = default;
+ WebSocketFrameData& operator=(WebSocketFrameData&&) = default;
+ WebSocketFrameData& operator=(const WebSocketFrameData&) = default;
+
+ WebSocketFrameData(DOMHighResTimeStamp aTimeStamp, bool aFinBit,
+ bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ ~WebSocketFrameData() = default;
+
+ // For IPC serialization
+ void WriteIPCParams(IPC::MessageWriter* aWriter) const;
+ bool ReadIPCParams(IPC::MessageReader* aReader);
+
+ DOMHighResTimeStamp mTimeStamp{0};
+
+ bool mFinBit : 1;
+ bool mRsvBit1 : 1;
+ bool mRsvBit2 : 1;
+ bool mRsvBit3 : 1;
+ bool mMaskBit : 1;
+ uint8_t mOpCode{0};
+
+ uint32_t mMask{0};
+
+ nsCString mPayload;
+};
+
+class WebSocketFrame final : public nsIWebSocketFrame {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETFRAME
+
+ explicit WebSocketFrame(const WebSocketFrameData& aData);
+
+ WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ const WebSocketFrameData& Data() const { return mData; }
+
+ private:
+ ~WebSocketFrame() = default;
+
+ WebSocketFrameData mData;
+};
+
+} // namespace net
+} // namespace mozilla
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::net::WebSocketFrameData> {
+ using paramType = mozilla::net::WebSocketFrameData;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aParam.WriteIPCParams(aWriter);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return aResult->ReadIPCParams(aReader);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_WebSocketFrame_h
diff --git a/netwerk/protocol/websocket/WebSocketLog.h b/netwerk/protocol/websocket/WebSocketLog.h
new file mode 100644
index 0000000000..80122249c2
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketLog.h
@@ -0,0 +1,24 @@
+/* -*- 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 WebSocketLog_h
+#define WebSocketLog_h
+
+#include "base/basictypes.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+extern LazyLogModule webSocketLog;
+}
+} // namespace mozilla
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(mozilla::net::webSocketLog, mozilla::LogLevel::Debug, args)
+
+#endif
diff --git a/netwerk/protocol/websocket/moz.build b/netwerk/protocol/websocket/moz.build
new file mode 100644
index 0000000000..123d364b70
--- /dev/null
+++ b/netwerk/protocol/websocket/moz.build
@@ -0,0 +1,67 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: WebSockets")
+
+XPIDL_SOURCES += [
+ "nsITransportProvider.idl",
+ "nsIWebSocketChannel.idl",
+ "nsIWebSocketEventService.idl",
+ "nsIWebSocketImpl.idl",
+ "nsIWebSocketListener.idl",
+]
+
+XPIDL_MODULE = "necko_websocket"
+
+EXPORTS.mozilla.net += [
+ "BaseWebSocketChannel.h",
+ "IPCTransportProvider.h",
+ "WebSocketChannel.h",
+ "WebSocketChannelChild.h",
+ "WebSocketChannelParent.h",
+ "WebSocketConnectionBase.h",
+ "WebSocketConnectionChild.h",
+ "WebSocketConnectionListener.h",
+ "WebSocketConnectionParent.h",
+ "WebSocketEventListenerChild.h",
+ "WebSocketEventListenerParent.h",
+ "WebSocketEventService.h",
+ "WebSocketFrame.h",
+]
+
+UNIFIED_SOURCES += [
+ "BaseWebSocketChannel.cpp",
+ "IPCTransportProvider.cpp",
+ "WebSocketChannel.cpp",
+ "WebSocketChannelChild.cpp",
+ "WebSocketChannelParent.cpp",
+ "WebSocketConnection.cpp",
+ "WebSocketConnectionChild.cpp",
+ "WebSocketConnectionParent.cpp",
+ "WebSocketEventListenerChild.cpp",
+ "WebSocketEventListenerParent.cpp",
+ "WebSocketEventService.cpp",
+ "WebSocketFrame.cpp",
+]
+
+IPDL_SOURCES += [
+ "PTransportProvider.ipdl",
+ "PWebSocket.ipdl",
+ "PWebSocketConnection.ipdl",
+ "PWebSocketEventListener.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/netwerk/base",
+]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/netwerk/protocol/websocket/nsITransportProvider.idl b/netwerk/protocol/websocket/nsITransportProvider.idl
new file mode 100644
index 0000000000..8b60eabd03
--- /dev/null
+++ b/netwerk/protocol/websocket/nsITransportProvider.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsIHttpUpgradeListener;
+
+#include "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class PTransportProviderChild;
+}
+}
+%}
+
+[ptr] native PTransportProviderChild(mozilla::net::PTransportProviderChild);
+
+/**
+ * An interface which can be used to asynchronously request a nsITransport
+ * together with the input and output streams that go together with it.
+ */
+[scriptable, uuid(6fcec704-cfd2-46ef-a394-a64d5cb1475c)]
+interface nsITransportProvider : nsISupports
+{
+ // This must not be called in a child process since transport
+ // objects are not accessible there. Call getIPCChild instead.
+ [must_use] void setListener(in nsIHttpUpgradeListener listener);
+
+ // This must be implemented by nsITransportProvider objects running
+ // in the child process. It must return null when called in the parent
+ // process.
+ [noscript, must_use] PTransportProviderChild getIPCChild();
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketChannel.idl b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
new file mode 100644
index 0000000000..7092c3c6b1
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsICookieJarSettings;
+interface nsIInputStream;
+interface nsIInterfaceRequestor;
+interface nsILoadGroup;
+interface nsILoadInfo;
+interface nsIPrincipal;
+interface nsITransportProvider;
+interface nsITransportSecurityInfo;
+interface nsIURI;
+interface nsIWebSocketListener;
+
+webidl Node;
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+
+
+[ref] native OriginAttributes(const mozilla::OriginAttributes);
+
+/**
+ * Low-level websocket API: handles network protocol.
+ *
+ * This is primarly intended for use by the higher-level nsIWebSocket.idl.
+ * We are also making it scriptable for now, but this may change once we have
+ * WebSockets for Workers.
+ */
+[scriptable, builtinclass, uuid(ce71d028-322a-4105-a947-a894689b52bf)]
+interface nsIWebSocketChannel : nsISupports
+{
+ /**
+ * The original URI used to construct the protocol connection. 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.
+ */
+ [must_use] readonly attribute nsIURI originalURI;
+
+ /**
+ * The readonly URI corresponding to the protocol connection after any
+ * redirections are completed.
+ */
+ [must_use] readonly attribute nsIURI URI;
+
+ /**
+ * The notification callbacks for authorization, etc..
+ */
+ [must_use] attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any)
+ */
+ [must_use] readonly attribute nsITransportSecurityInfo securityInfo;
+
+ /**
+ * The load group of of the websocket
+ */
+ [must_use] attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load info of the websocket
+ */
+ [must_use] attribute nsILoadInfo loadInfo;
+
+ /**
+ * Sec-Websocket-Protocol value
+ */
+ [must_use] attribute ACString protocol;
+
+ /**
+ * Sec-Websocket-Extensions response header value
+ */
+ [must_use] readonly attribute ACString extensions;
+
+ /**
+ * The channelId of the underlying http channel.
+ * It's available only after nsIWebSocketListener::onStart
+ */
+ [must_use] readonly attribute uint64_t httpChannelId;
+
+ /**
+ * Init the WebSocketChannel with LoadInfo arguments.
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aCookieJarSettings
+ * @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
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ *
+ * 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.
+ */
+ [notxpcom] nsresult initLoadInfoNative(in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in nsICookieJarSettings aCookieJarSettings,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType,
+ in unsigned long aSandboxFlags);
+
+ /**
+ * Similar to the previous one but without nsICookieJarSettings.
+ * This method is used by JS code where nsICookieJarSettings is not exposed.
+ */
+ [must_use] void initLoadInfo(in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Asynchronously open the websocket connection. Received messages are fed
+ * to the socket listener as they arrive. The socket listener's methods
+ * are called on the thread that calls asyncOpen and are not called until
+ * after asyncOpen returns. If asyncOpen returns successfully, the
+ * protocol implementation promises to call at least onStop on the listener.
+ *
+ * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the
+ * websocket connection is reopened.
+ *
+ * @param aURI the uri of the websocket protocol - may be redirected
+ * @param aOrigin the uri of the originating resource
+ * @param aOriginAttributes attributes of the originating resource.
+ * @param aInnerWindowID the inner window ID
+ * @param aListener the nsIWebSocketListener implementation
+ * @param aContext an opaque parameter forwarded to aListener's methods
+ */
+ [implicit_jscontext]
+ void asyncOpen(in nsIURI aURI,
+ in ACString aOrigin,
+ in jsval aOriginAttributes,
+ in unsigned long long aInnerWindowID,
+ in nsIWebSocketListener aListener,
+ in nsISupports aContext);
+
+ [must_use]
+ void asyncOpenNative(in nsIURI aURI,
+ in ACString aOrigin,
+ in OriginAttributes aOriginAttributes,
+ in unsigned long long aInnerWindowID,
+ in nsIWebSocketListener aListener,
+ in nsISupports aContext);
+
+ /*
+ * Close the websocket connection for writing - no more calls to sendMsg
+ * or sendBinaryMsg should be made after calling this. The listener object
+ * may receive more messages if a server close has not yet been received.
+ *
+ * @param aCode the websocket closing handshake close code. Set to 0 if
+ * you are not providing a code.
+ * @param aReason the websocket closing handshake close reason
+ */
+ [must_use] void close(in unsigned short aCode, in AUTF8String aReason);
+
+ // section 7.4.1 defines these close codes
+ const unsigned short CLOSE_NORMAL = 1000;
+ const unsigned short CLOSE_GOING_AWAY = 1001;
+ const unsigned short CLOSE_PROTOCOL_ERROR = 1002;
+ const unsigned short CLOSE_UNSUPPORTED_DATATYPE = 1003;
+ // code 1004 is reserved
+ const unsigned short CLOSE_NO_STATUS = 1005;
+ const unsigned short CLOSE_ABNORMAL = 1006;
+ const unsigned short CLOSE_INVALID_PAYLOAD = 1007;
+ const unsigned short CLOSE_POLICY_VIOLATION = 1008;
+ const unsigned short CLOSE_TOO_LARGE = 1009;
+ const unsigned short CLOSE_EXTENSION_MISSING = 1010;
+ // Initially used just for server-side internal errors: adopted later for
+ // client-side errors too (not clear if will make into spec: see
+ // http://www.ietf.org/mail-archive/web/hybi/current/msg09372.html
+ const unsigned short CLOSE_INTERNAL_ERROR = 1011;
+ // MUST NOT be set as a status code in Close control frame by an endpoint:
+ // To be used if TLS handshake failed (ex: server certificate unverifiable)
+ const unsigned short CLOSE_TLS_FAILED = 1015;
+
+ /**
+ * Use to send text message down the connection to WebSocket peer.
+ *
+ * @param aMsg the utf8 string to send
+ */
+ [must_use] void sendMsg(in AUTF8String aMsg);
+
+ /**
+ * Use to send binary message down the connection to WebSocket peer.
+ *
+ * @param aMsg the data to send
+ */
+ [must_use] void sendBinaryMsg(in ACString aMsg);
+
+ /**
+ * Use to send a binary stream (Blob) to Websocket peer.
+ *
+ * @param aStream The input stream to be sent.
+ */
+ [must_use] void sendBinaryStream(in nsIInputStream aStream,
+ in unsigned long length);
+
+ /**
+ * This value determines how often (in seconds) websocket keepalive
+ * pings are sent. If set to 0 (the default), no pings are ever sent.
+ *
+ * This value can currently only be set before asyncOpen is called, else
+ * NS_ERROR_IN_PROGRESS is thrown.
+ *
+ * Be careful using this setting: ping traffic can consume lots of power and
+ * bandwidth over time.
+ */
+ [must_use] attribute unsigned long pingInterval;
+
+ /**
+ * This value determines how long (in seconds) the websocket waits for
+ * the server to reply to a ping that has been sent before considering the
+ * connection broken.
+ *
+ * This value can currently only be set before asyncOpen is called, else
+ * NS_ERROR_IN_PROGRESS is thrown.
+ */
+ [must_use] attribute unsigned long pingTimeout;
+
+ /**
+ * Unique ID for this channel. It's not readonly because when the channel is
+ * created via IPC, the serial number is received from the child process.
+ */
+ [must_use] attribute unsigned long serial;
+
+ /**
+ * Set a nsITransportProvider and negotated extensions to be used by this
+ * channel. Calling this function also means that this channel will
+ * implement the server-side part of a websocket connection rather than the
+ * client-side part.
+ */
+ [must_use] void setServerParameters(in nsITransportProvider aProvider,
+ in ACString aNegotiatedExtensions);
+
+%{C++
+ inline uint32_t Serial()
+ {
+ uint32_t serial;
+ nsresult rv = GetSerial(&serial);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 0;
+ }
+ return serial;
+ }
+
+ inline uint64_t HttpChannelId()
+ {
+ uint64_t httpChannelId;
+ nsresult rv = GetHttpChannelId(&httpChannelId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 0;
+ }
+ return httpChannelId;
+ }
+%}
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketEventService.idl b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
new file mode 100644
index 0000000000..9763850609
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
@@ -0,0 +1,87 @@
+/* -*- Mode: IDL; 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 "domstubs.idl"
+#include "nsISupports.idl"
+
+interface nsIWeakReference;
+
+[scriptable, builtinclass, uuid(6714a6be-2265-4f73-a988-d78a12416037)]
+interface nsIWebSocketFrame : nsISupports
+{
+ [must_use] readonly attribute DOMHighResTimeStamp timeStamp;
+
+ [must_use] readonly attribute boolean finBit;
+
+ [must_use] readonly attribute boolean rsvBit1;
+ [must_use] readonly attribute boolean rsvBit2;
+ [must_use] readonly attribute boolean rsvBit3;
+
+ [must_use] readonly attribute unsigned short opCode;
+
+ [must_use] readonly attribute boolean maskBit;
+
+ [must_use] readonly attribute unsigned long mask;
+
+ [must_use] readonly attribute ACString payload;
+
+ // Non-Control opCode values:
+ const unsigned short OPCODE_CONTINUATION = 0x0;
+ const unsigned short OPCODE_TEXT = 0x1;
+ const unsigned short OPCODE_BINARY = 0x2;
+
+ // Control opCode values:
+ const unsigned short OPCODE_CLOSE = 0x8;
+ const unsigned short OPCODE_PING = 0x9;
+ const unsigned short OPCODE_PONG = 0xA;
+};
+
+[scriptable, uuid(e7c005ab-e694-489b-b741-96db43ffb16f)]
+interface nsIWebSocketEventListener : nsISupports
+{
+ [must_use] void webSocketCreated(in unsigned long aWebSocketSerialID,
+ in AString aURI,
+ in ACString aProtocols);
+
+ [must_use] void webSocketOpened(in unsigned long aWebSocketSerialID,
+ in AString aEffectiveURI,
+ in ACString aProtocols,
+ in ACString aExtensions,
+ in uint64_t aHttpChannelId);
+
+ const unsigned short TYPE_STRING = 0x0;
+ const unsigned short TYPE_BLOB = 0x1;
+ const unsigned short TYPE_ARRAYBUFFER = 0x2;
+
+ [must_use] void webSocketMessageAvailable(in unsigned long aWebSocketSerialID,
+ in ACString aMessage,
+ in unsigned short aType);
+
+ [must_use] void webSocketClosed(in unsigned long aWebSocketSerialID,
+ in boolean aWasClean,
+ in unsigned short aCode,
+ in AString aReason);
+
+ [must_use] void frameReceived(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+
+ [must_use] void frameSent(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+};
+
+[scriptable, builtinclass, uuid(b89d1b90-2cf3-4d8f-ac21-5aedfb25c760)]
+interface nsIWebSocketEventService : nsISupports
+{
+ [must_use] void sendMessage(in unsigned long aWebSocketSerialID,
+ in AString aMessage);
+
+ [must_use] void addListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+
+ [must_use] void removeListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+
+ [must_use] bool hasListenerFor(in unsigned long long aInnerWindowID);
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketImpl.idl b/netwerk/protocol/websocket/nsIWebSocketImpl.idl
new file mode 100644
index 0000000000..ab4177a7e6
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketImpl.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(db1f4e2b-3cff-4615-a03c-341fda66c53d)]
+interface nsIWebSocketImpl : nsISupports
+{
+ /**
+ * Called to send message of type string through web socket
+ *
+ * @param aMessage the message to send
+ */
+ [must_use] void sendMessage(in AString aMessage);
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketListener.idl b/netwerk/protocol/websocket/nsIWebSocketListener.idl
new file mode 100644
index 0000000000..31c588630b
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketListener.idl
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIWebSocketListener: passed to nsIWebSocketChannel::AsyncOpen. Receives
+ * websocket traffic events as they arrive.
+ */
+[scriptable, uuid(d74c96b2-65b3-4e39-9e39-c577de5d7a73)]
+interface nsIWebSocketListener : nsISupports
+{
+ /**
+ * Called to signify the establishment of the message stream.
+ *
+ * Unlike most other networking channels (which use nsIRequestObserver
+ * instead of this class), we do not guarantee that OnStart is always
+ * called: OnStop is called without calling this function if errors occur
+ * during connection setup. If the websocket connection is successful,
+ * OnStart will be called before any other calls to this API.
+ *
+ * @param aContext user defined context
+ */
+ [must_use] void onStart(in nsISupports aContext);
+
+ /**
+ * Called to signify the completion of the message stream.
+ * OnStop is the final notification the listener will receive and it
+ * completes the WebSocket connection: after it returns the
+ * nsIWebSocketChannel will release its reference to the listener.
+ *
+ * Note: this event can be received in error cases even if
+ * nsIWebSocketChannel::Close() has not been called.
+ *
+ * @param aContext user defined context
+ * @param aStatusCode reason for stopping (NS_OK if completed successfully)
+ */
+ [must_use] void onStop(in nsISupports aContext,
+ in nsresult aStatusCode);
+
+ /**
+ * Called to deliver text message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ [must_use] void onMessageAvailable(in nsISupports aContext,
+ in AUTF8String aMsg);
+
+ /**
+ * Called to deliver binary message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ [must_use] void onBinaryMessageAvailable(in nsISupports aContext,
+ in ACString aMsg);
+
+ /**
+ * Called to acknowledge message sent via sendMsg() or sendBinaryMsg.
+ *
+ * @param aContext user defined context
+ * @param aSize number of bytes placed in OS send buffer
+ */
+ [must_use] void onAcknowledge(in nsISupports aContext, in uint32_t aSize);
+
+ /**
+ * Called to inform receipt of WebSocket Close message from server.
+ * In the case of errors onStop() can be called without ever
+ * receiving server close.
+ *
+ * No additional messages through onMessageAvailable(),
+ * onBinaryMessageAvailable() or onAcknowledge() will be delievered
+ * to the listener after onServerClose(), though outgoing messages can still
+ * be sent through the nsIWebSocketChannel connection.
+ *
+ * @param aContext user defined context
+ * @param aCode the websocket closing handshake close code.
+ * @param aReason the websocket closing handshake close reason
+ */
+ [must_use] void onServerClose(in nsISupports aContext,
+ in unsigned short aCode,
+ in AUTF8String aReason);
+
+ /**
+ * Called to inform an error is happened. The connection will be closed
+ * when this is called.
+ */
+ [must_use] void OnError();
+
+};
diff --git a/netwerk/protocol/webtransport/WebTransportHash.cpp b/netwerk/protocol/webtransport/WebTransportHash.cpp
new file mode 100644
index 0000000000..f53fbacbcf
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportHash.cpp
@@ -0,0 +1,24 @@
+/* -*- 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 "WebTransportHash.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(WebTransportHash, nsIWebTransportHash);
+
+NS_IMETHODIMP
+WebTransportHash::GetValue(nsTArray<uint8_t>& aValue) {
+ aValue.Clear();
+ aValue.AppendElements(mValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportHash::GetAlgorithm(nsACString& algorithm) {
+ algorithm.Assign(mAlgorithm);
+ return NS_OK;
+}
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportHash.h b/netwerk/protocol/webtransport/WebTransportHash.h
new file mode 100644
index 0000000000..b3037f560a
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportHash.h
@@ -0,0 +1,30 @@
+/* -*- 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 mozilla_net_WebTransportHash_h
+#define mozilla_net_WebTransportHash_h
+
+#include "nsIWebTransport.h"
+
+namespace mozilla::net {
+
+class WebTransportHash final : public nsIWebTransportHash {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBTRANSPORTHASH
+
+ explicit WebTransportHash(const nsACString& algorithm,
+ const nsTArray<uint8_t>& value)
+ : mAlgorithm(algorithm), mValue(Span(value)) {}
+
+ private:
+ ~WebTransportHash() = default;
+ nsCString mAlgorithm;
+ nsTArray<uint8_t> mValue;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_WebTransportHash_h
diff --git a/netwerk/protocol/webtransport/WebTransportLog.h b/netwerk/protocol/webtransport/WebTransportLog.h
new file mode 100644
index 0000000000..99f211b893
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportLog.h
@@ -0,0 +1,21 @@
+/* -*- 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 WebTransportLog_h
+#define WebTransportLog_h
+
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla::net {
+extern LazyLogModule webTransportLog;
+} // namespace mozilla::net
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(mozilla::net::webTransportLog, mozilla::LogLevel::Debug, args)
+
+#endif
diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
new file mode 100644
index 0000000000..e650a74d5b
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
@@ -0,0 +1,1269 @@
+/* -*- 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 "WebTransportLog.h"
+#include "Http3WebTransportSession.h"
+#include "Http3WebTransportStream.h"
+#include "ScopedNSSTypes.h"
+#include "WebTransportSessionProxy.h"
+#include "WebTransportStreamProxy.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIRequest.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIX509Cert.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla::net {
+
+LazyLogModule webTransportLog("nsWebTransport");
+
+NS_IMPL_ISUPPORTS(WebTransportSessionProxy, WebTransportSessionEventListener,
+ nsIWebTransport, nsIRedirectResultListener, nsIStreamListener,
+ nsIChannelEventSink, nsIInterfaceRequestor);
+
+WebTransportSessionProxy::WebTransportSessionProxy()
+ : mMutex("WebTransportSessionProxy::mMutex"),
+ mTarget(GetMainThreadSerialEventTarget()) {
+ LOG(("WebTransportSessionProxy constructor"));
+}
+
+WebTransportSessionProxy::~WebTransportSessionProxy() {
+ if (OnSocketThread()) {
+ return;
+ }
+
+ MutexAutoLock lock(mMutex);
+ if ((mState != WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) &&
+ (mState != WebTransportSessionProxyState::ACTIVE) &&
+ (mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING)) {
+ return;
+ }
+
+ MOZ_ASSERT(mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING,
+ "We can not be in the SESSION_CLOSE_PENDING state in destructor, "
+ "because should e an runnable that holds reference to this"
+ "object.");
+
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::ProxyHttp3WebTransportSessionRelease",
+ [self{std::move(mWebTransportSession)}]() {}));
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIWebTransport
+//-----------------------------------------------------------------------------
+
+nsresult WebTransportSessionProxy::AsyncConnect(
+ nsIURI* aURI,
+ const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes,
+ nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
+ WebTransportSessionEventListener* aListener) {
+ return AsyncConnectWithClient(aURI, std::move(aServerCertHashes), aPrincipal,
+ aSecurityFlags, aListener,
+ Maybe<dom::ClientInfo>());
+}
+
+nsresult WebTransportSessionProxy::AsyncConnectWithClient(
+ nsIURI* aURI,
+ const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes,
+ nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
+ WebTransportSessionEventListener* aListener,
+ const Maybe<dom::ClientInfo>& aClientInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("WebTransportSessionProxy::AsyncConnect"));
+ {
+ MutexAutoLock lock(mMutex);
+ mListener = aListener;
+ }
+ auto cleanup = MakeScopeExit([self = RefPtr<WebTransportSessionProxy>(this)] {
+ MutexAutoLock lock(self->mMutex);
+ self->mListener->OnSessionClosed(false, 0,
+ ""_ns); // TODO: find a better error.
+ self->mChannel = nullptr;
+ self->mListener = nullptr;
+ self->ChangeState(WebTransportSessionProxyState::DONE);
+ });
+
+ nsSecurityFlags flags = nsILoadInfo::SEC_COOKIES_OMIT | aSecurityFlags;
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::INHIBIT_CACHING;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aClientInfo.isSome()) {
+ rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal,
+ aClientInfo.ref(), Maybe<dom::ServiceWorkerDescriptor>(),
+ flags, nsContentPolicyType::TYPE_WEB_TRANSPORT,
+ /* aCookieJarSettings */ nullptr,
+ /* aPerformanceStorage */ nullptr,
+ /* aLoadGroup */ nullptr,
+ /* aCallbacks */ this, loadFlags);
+ } else {
+ rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal, flags,
+ nsContentPolicyType::TYPE_WEB_TRANSPORT,
+ /* aCookieJarSettings */ nullptr,
+ /* aPerformanceStorage */ nullptr,
+ /* aLoadGroup */ nullptr,
+ /* aCallbacks */ this, loadFlags);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+
+ if (!aServerCertHashes.IsEmpty()) {
+ mServerCertHashes.AppendElements(aServerCertHashes);
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ ChangeState(WebTransportSessionProxyState::NEGOTIATING);
+ }
+
+ // https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-04.html#section-6
+ rv = httpChannel->SetRequestHeader("Sec-Webtransport-Http3-Draft02"_ns,
+ "1"_ns, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // To establish a WebTransport session with an origin origin, follow
+ // [WEB-TRANSPORT-HTTP3] section 3.3, with using origin, serialized and
+ // isomorphic encoded, as the `Origin` header of the request.
+ // https://www.w3.org/TR/webtransport/#protocol-concepts
+ nsAutoCString serializedOrigin;
+ if (NS_FAILED(
+ aPrincipal->GetWebExposedOriginSerialization(serializedOrigin))) {
+ // origin/URI will be missing for system principals
+ // assign null origin
+ serializedOrigin = "null"_ns;
+ }
+
+ rv = httpChannel->SetRequestHeader("Origin"_ns, serializedOrigin, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(mChannel);
+ if (!internalChannel) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+ Unused << internalChannel->SetWebTransportSessionEventListener(this);
+
+ rv = mChannel->AsyncOpen(this);
+ if (NS_SUCCEEDED(rv)) {
+ cleanup.release();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::RetargetTo(nsIEventTarget* aTarget) {
+ if (!aTarget) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ LOG(("WebTransportSessionProxy::RetargetTo mState=%d", mState));
+ // RetargetTo should be only called after the session is ready.
+ if (mState != WebTransportSessionProxyState::ACTIVE) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mTarget = aTarget;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetStats() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CloseSession(uint32_t status,
+ const nsACString& reason) {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ mCloseStatus = status;
+ mReason = reason;
+ mListener = nullptr;
+ mPendingEvents.Clear();
+ mServerCertHashes.Clear();
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::DONE:
+ return NS_ERROR_NOT_INITIALIZED;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ mChannel->Cancel(NS_ERROR_ABORT);
+ mChannel = nullptr;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ mChannel->Cancel(NS_ERROR_ABORT);
+ mChannel = nullptr;
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal();
+ break;
+ case WebTransportSessionProxyState::ACTIVE:
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal();
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case SESSION_CLOSE_PENDING:
+ break;
+ }
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::CloseSessionInternalLocked() {
+ MutexAutoLock lock(mMutex);
+ CloseSessionInternal();
+}
+
+void WebTransportSessionProxy::CloseSessionInternal() {
+ if (!OnSocketThread()) {
+ mMutex.AssertCurrentThreadOwns();
+ RefPtr<WebTransportSessionProxy> self(this);
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CallCloseWebTransportSession",
+ [self{std::move(self)}]() { self->CloseSessionInternalLocked(); }));
+ return;
+ }
+
+ mMutex.AssertCurrentThreadOwns();
+
+ RefPtr<Http3WebTransportSession> wt;
+ uint32_t closeStatus = 0;
+ nsCString reason;
+
+ if (mState == WebTransportSessionProxyState::SESSION_CLOSE_PENDING) {
+ MOZ_ASSERT(mWebTransportSession);
+ wt = mWebTransportSession;
+ mWebTransportSession = nullptr;
+ closeStatus = mCloseStatus;
+ reason = mReason;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ } else {
+ MOZ_ASSERT(mState == WebTransportSessionProxyState::DONE);
+ }
+
+ if (wt) {
+ MutexAutoUnlock unlock(mMutex);
+ wt->CloseSession(closeStatus, reason);
+ }
+}
+
+class WebTransportStreamCallbackWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebTransportStreamCallbackWrapper)
+
+ explicit WebTransportStreamCallbackWrapper(
+ nsIWebTransportStreamCallback* aCallback, bool aBidi)
+ : mCallback(aCallback),
+ mTarget(GetCurrentSerialEventTarget()),
+ mBidi(aBidi) {}
+
+ void CallOnError(nsresult aError) {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportStreamCallbackWrapper> self(this);
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamCallbackWrapper::CallOnError",
+ [self{std::move(self)}, error{aError}]() {
+ self->CallOnError(error);
+ }));
+ return;
+ }
+
+ LOG(("WebTransportStreamCallbackWrapper::OnError aError=0x%" PRIx32,
+ static_cast<uint32_t>(aError)));
+ Unused << mCallback->OnError(nsIWebTransport::INVALID_STATE_ERROR);
+ }
+
+ void CallOnStreamReady(WebTransportStreamProxy* aStream) {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportStreamCallbackWrapper> self(this);
+ RefPtr<WebTransportStreamProxy> stream = aStream;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamCallbackWrapper::CallOnStreamReady",
+ [self{std::move(self)}, stream{std::move(stream)}]() {
+ self->CallOnStreamReady(stream);
+ }));
+ return;
+ }
+
+ if (mBidi) {
+ Unused << mCallback->OnBidirectionalStreamReady(aStream);
+ return;
+ }
+
+ Unused << mCallback->OnUnidirectionalStreamReady(aStream);
+ }
+
+ private:
+ virtual ~WebTransportStreamCallbackWrapper() {
+ NS_ProxyRelease(
+ "WebTransportStreamCallbackWrapper::~WebTransportStreamCallbackWrapper",
+ mTarget, mCallback.forget());
+ }
+
+ nsCOMPtr<nsIWebTransportStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ bool mBidi = false;
+};
+
+void WebTransportSessionProxy::CreateStreamInternal(
+ nsIWebTransportStreamCallback* callback, bool aBidi) {
+ mMutex.AssertCurrentThreadOwns();
+ LOG(
+ ("WebTransportSessionProxy::CreateStreamInternal %p "
+ "mState=%d, bidi=%d",
+ this, mState, aBidi));
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ case WebTransportSessionProxyState::ACTIVE: {
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper =
+ new WebTransportStreamCallbackWrapper(callback, aBidi);
+ if (mState == WebTransportSessionProxyState::ACTIVE &&
+ mWebTransportSession) {
+ DoCreateStream(wrapper, mWebTransportSession, aBidi);
+ } else {
+ LOG(
+ ("WebTransportSessionProxy::CreateStreamInternal %p "
+ " queue create stream event",
+ this));
+ auto task = [self = RefPtr{this}, wrapper{std::move(wrapper)},
+ bidi(aBidi)](nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ wrapper->CallOnError(aStatus);
+ return;
+ }
+
+ self->DoCreateStream(wrapper, nullptr, bidi);
+ };
+ // TODO: we should do this properly in bug 1830362.
+ mPendingCreateStreamEvents.AppendElement(std::move(task));
+ }
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::DONE: {
+ nsCOMPtr<nsIWebTransportStreamCallback> cb(callback);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CreateStreamInternal",
+ [cb{std::move(cb)}]() {
+ cb->OnError(nsIWebTransport::INVALID_STATE_ERROR);
+ }));
+ } break;
+ }
+}
+
+void WebTransportSessionProxy::DoCreateStream(
+ WebTransportStreamCallbackWrapper* aCallback,
+ Http3WebTransportSession* aSession, bool aBidi) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper(aCallback);
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DoCreateStream",
+ [self{std::move(self)}, wrapper{std::move(wrapper)}, bidi(aBidi)]() {
+ self->DoCreateStream(wrapper, nullptr, bidi);
+ }));
+ return;
+ }
+
+ LOG(("WebTransportSessionProxy::DoCreateStream %p bidi=%d", this, aBidi));
+
+ RefPtr<Http3WebTransportSession> session = aSession;
+ // Having no session here means that this is called by dispatching tasks.
+ // The mState may be already changed, so we need to check it again.
+ if (!aSession) {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ MOZ_ASSERT(false, "DoCreateStream called with invalid state");
+ aCallback->CallOnError(NS_ERROR_UNEXPECTED);
+ return;
+ case WebTransportSessionProxyState::ACTIVE: {
+ session = mWebTransportSession;
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::DONE:
+ // Session is going to be closed.
+ aCallback->CallOnError(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+ }
+
+ if (!session) {
+ MOZ_ASSERT_UNREACHABLE("This should not happen");
+ aCallback->CallOnError(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper(aCallback);
+ auto callback =
+ [wrapper{std::move(wrapper)}](
+ Result<RefPtr<Http3WebTransportStream>, nsresult>&& aResult) {
+ if (aResult.isErr()) {
+ wrapper->CallOnError(aResult.unwrapErr());
+ return;
+ }
+
+ RefPtr<Http3WebTransportStream> stream = aResult.unwrap();
+ RefPtr<WebTransportStreamProxy> streamProxy =
+ new WebTransportStreamProxy(stream);
+ wrapper->CallOnStreamReady(streamProxy);
+ };
+
+ if (aBidi) {
+ session->CreateOutgoingBidirectionalStream(std::move(callback));
+ } else {
+ session->CreateOutgoingUnidirectionalStream(std::move(callback));
+ }
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CreateOutgoingUnidirectionalStream(
+ nsIWebTransportStreamCallback* callback) {
+ if (!callback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MutexAutoLock lock(mMutex);
+ CreateStreamInternal(callback, false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CreateOutgoingBidirectionalStream(
+ nsIWebTransportStreamCallback* callback) {
+ if (!callback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MutexAutoLock lock(mMutex);
+ CreateStreamInternal(callback, true);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::SendDatagramInternal(
+ const RefPtr<Http3WebTransportSession>& aSession, nsTArray<uint8_t>&& aData,
+ uint64_t aTrackingId) {
+ MOZ_ASSERT(OnSocketThread());
+
+ aSession->SendDatagram(std::move(aData), aTrackingId);
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::SendDatagram(const nsTArray<uint8_t>& aData,
+ uint64_t aTrackingId) {
+ RefPtr<Http3WebTransportSession> session;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mState != WebTransportSessionProxyState::ACTIVE ||
+ !mWebTransportSession) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ session = mWebTransportSession;
+ }
+
+ nsTArray<uint8_t> copied;
+ copied.Assign(aData);
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::SendDatagramInternal",
+ [self = RefPtr{this}, session{std::move(session)},
+ data{std::move(copied)}, trackingId(aTrackingId)]() mutable {
+ self->SendDatagramInternal(session, std::move(data), trackingId);
+ }));
+ }
+
+ SendDatagramInternal(session, std::move(copied), aTrackingId);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::GetMaxDatagramSizeInternal(
+ const RefPtr<Http3WebTransportSession>& aSession) {
+ MOZ_ASSERT(OnSocketThread());
+
+ aSession->GetMaxDatagramSize();
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetMaxDatagramSize() {
+ RefPtr<Http3WebTransportSession> session;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mState != WebTransportSessionProxyState::ACTIVE ||
+ !mWebTransportSession) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ session = mWebTransportSession;
+ }
+
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::GetMaxDatagramSizeInternal",
+ [self = RefPtr{this}, session{std::move(session)}]() {
+ self->GetMaxDatagramSizeInternal(session);
+ }));
+ }
+
+ GetMaxDatagramSizeInternal(session);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("WebTransportSessionProxy::OnStartRequest\n"));
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ nsAutoCString reason;
+ uint32_t closeStatus = 0;
+ {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::DONE:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ MOZ_ASSERT(false, "OnStartRequest cannot be called in this state.");
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ listener = mListener;
+ mListener = nullptr;
+ mChannel = nullptr;
+ reason = mReason;
+ closeStatus = mCloseStatus;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: {
+ uint32_t status;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel ||
+ NS_FAILED(httpChannel->GetResponseStatus(&status)) ||
+ !(status >= 200 && status < 300) ||
+ !CheckServerCertificateIfNeeded()) {
+ listener = mListener;
+ mListener = nullptr;
+ mChannel = nullptr;
+ mReason = ""_ns;
+ reason = ""_ns;
+ mCloseStatus =
+ 0; // TODO: find a better error. Currently error code 0 is used
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal(); // TODO: find a better error. Currently error
+ // code 0 is used.
+ }
+ // The success cases will be handled in OnStopRequest.
+ } break;
+ }
+ }
+ if (listener) {
+ listener->OnSessionClosed(false, closeStatus, reason);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(
+ false, "WebTransportSessionProxy::OnDataAvailable should not be called");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mChannel = nullptr;
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ nsAutoCString reason;
+ uint32_t closeStatus = 0;
+ uint64_t sessionId;
+ bool succeeded = false;
+ nsTArray<std::function<void()>> pendingEvents;
+ nsTArray<std::function<void(nsresult)>> pendingCreateStreamEvents;
+ {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ MOZ_ASSERT(false, "OnStopRequest cannot be called in this state.");
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ reason = mReason;
+ closeStatus = mCloseStatus;
+ listener = mListener;
+ mListener = nullptr;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ if (NS_FAILED(aStatus)) {
+ listener = mListener;
+ mListener = nullptr;
+ mReason = ""_ns;
+ reason = ""_ns;
+ mCloseStatus = 0;
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal(); // TODO: find a better error. Currently error
+ // code 0 is used.
+ } else {
+ succeeded = true;
+ sessionId = mSessionId;
+ listener = mListener;
+ ChangeState(WebTransportSessionProxyState::ACTIVE);
+ }
+ break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::DONE:
+ break;
+ }
+ pendingEvents = std::move(mPendingEvents);
+ pendingCreateStreamEvents = std::move(mPendingCreateStreamEvents);
+ if (!pendingCreateStreamEvents.IsEmpty()) {
+ if (NS_SUCCEEDED(aStatus) &&
+ (mState == WebTransportSessionProxyState::DONE ||
+ mState == WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING ||
+ mState == WebTransportSessionProxyState::SESSION_CLOSE_PENDING)) {
+ aStatus = NS_ERROR_FAILURE;
+ }
+ }
+
+ mStopRequestCalled = true;
+ }
+
+ if (!pendingCreateStreamEvents.IsEmpty()) {
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DispatchPendingCreateStreamEvents",
+ [pendingCreateStreamEvents = std::move(pendingCreateStreamEvents),
+ status(aStatus)]() {
+ for (const auto& event : pendingCreateStreamEvents) {
+ event(status);
+ }
+ }));
+ } // otherwise let the CreateStreams just go away
+
+ if (listener) {
+ if (succeeded) {
+ listener->OnSessionReady(sessionId);
+ if (!pendingEvents.IsEmpty()) {
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DispatchPendingEvents",
+ [pendingEvents = std::move(pendingEvents)]() {
+ for (const auto& event : pendingEvents) {
+ event();
+ }
+ }));
+ }
+ } else {
+ listener->OnSessionClosed(false, closeStatus,
+ reason); // TODO: find a better error.
+ // Currently error code 0 is used.
+ }
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ // Currently implementation we do not reach this part of the code
+ // as location headers are not forwarded by the http3 stack to the applicaion.
+ // Hence, the channel is aborted due to the location header check in
+ // nsHttpChannel::AsyncProcessRedirection This comment must be removed after
+ // the following neqo bug is resolved
+ // https://github.com/mozilla/neqo/issues/1364
+ if (!StaticPrefs::network_webtransport_redirect_enabled()) {
+ LOG(("Channel Redirects are disabled for WebTransport sessions"));
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ callback->OnRedirectVerifyCallback(rv);
+ return NS_OK;
+ }
+
+ // abort the request if redirecting to insecure context
+ if (!newURI->SchemeIs("https")) {
+ callback->OnRedirectVerifyCallback(NS_ERROR_ABORT);
+ return NS_OK;
+ }
+
+ // Assign to mChannel after we get notification about success of the
+ // redirect in OnRedirectResult.
+ mRedirectChannel = aNewChannel;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnRedirectResult(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus) && mRedirectChannel) {
+ mChannel = mRedirectChannel;
+ }
+
+ mRedirectChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIRedirectResultListener*>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::WebTransportSessionEventListener
+//-----------------------------------------------------------------------------
+
+// This function is called when the Http3WebTransportSession is ready. After
+// this call WebTransportSessionProxy is responsible for the
+// Http3WebTransportSession, i.e. it is responsible for closing it.
+// The listener of the WebTransportSessionProxy will be informed during
+// OnStopRequest call.
+NS_IMETHODIMP
+WebTransportSessionProxy::OnSessionReadyInternal(
+ Http3WebTransportSession* aSession) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("WebTransportSessionProxy::OnSessionReadyInternal"));
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ MOZ_ASSERT(false,
+ "OnSessionReadyInternal cannot be called in this state.");
+ return NS_ERROR_ABORT;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ mWebTransportSession = aSession;
+ mSessionId = aSession->StreamId();
+ ChangeState(WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ // The session has been canceled. We do not need to set
+ // mWebTransportSession.
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingStreamAvailableInternal(
+ Http3WebTransportStream* aStream) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+
+ LOG(
+ ("WebTransportSessionProxy::OnIncomingStreamAvailableInternal %p "
+ "mState=%d "
+ "mStopRequestCalled=%d",
+ this, mState, mStopRequestCalled));
+ // Since OnSessionReady on the listener is called on the main thread,
+ // OnIncomingStreamAvailableInternal and OnSessionReady can be racy. If
+ // OnStopRequest is not called yet, OnIncomingStreamAvailableInternal needs
+ // to wait.
+ if (!mStopRequestCalled) {
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, stream = RefPtr{aStream}]() {
+ self->OnIncomingStreamAvailableInternal(stream);
+ });
+ return NS_OK;
+ }
+
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ RefPtr<Http3WebTransportStream> stream = aStream;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnIncomingStreamAvailableInternal",
+ [self{std::move(self)}, stream{std::move(stream)}]() {
+ self->OnIncomingStreamAvailableInternal(stream);
+ }));
+ return NS_OK;
+ }
+
+ LOG(
+ ("WebTransportSessionProxy::OnIncomingStreamAvailableInternal %p "
+ "mState=%d mListener=%p",
+ this, mState, mListener.get()));
+ if (mState == WebTransportSessionProxyState::ACTIVE) {
+ listener = mListener;
+ }
+ }
+
+ if (!listener) {
+ // Session can be already closed.
+ return NS_OK;
+ }
+
+ RefPtr<WebTransportStreamProxy> streamProxy =
+ new WebTransportStreamProxy(aStream);
+ if (aStream->StreamType() == WebTransportStreamType::BiDi) {
+ Unused << listener->OnIncomingBidirectionalStreamAvailable(streamProxy);
+ } else {
+ Unused << listener->OnIncomingUnidirectionalStreamAvailable(streamProxy);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingBidirectionalStreamAvailable(
+ nsIWebTransportBidirectionalStream* aStream) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingUnidirectionalStreamAvailable(
+ nsIWebTransportReceiveStream* aStream) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnSessionReady(uint64_t ready) {
+ MOZ_ASSERT(false, "Should not b called");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnSessionClosed(bool aCleanly, uint32_t aStatus,
+ const nsACString& aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MutexAutoLock lock(mMutex);
+ LOG(
+ ("WebTransportSessionProxy::OnSessionClosed %p mState=%d "
+ "mStopRequestCalled=%d",
+ this, mState, mStopRequestCalled));
+ // Since OnSessionReady on the listener is called on the main thread,
+ // OnSessionClosed and OnSessionReady can be racy. If OnStopRequest is not
+ // called yet, OnSessionClosed needs to wait.
+ if (!mStopRequestCalled) {
+ nsCString closeReason(aReason);
+ mPendingEvents.AppendElement([self = RefPtr{this}, status(aStatus),
+ closeReason(std::move(closeReason)),
+ cleanly(aCleanly)]() {
+ Unused << self->OnSessionClosed(cleanly, status, closeReason);
+ });
+ return NS_OK;
+ }
+
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ MOZ_ASSERT(false, "OnSessionClosed cannot be called in this state.");
+ return NS_ERROR_ABORT;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ case WebTransportSessionProxyState::ACTIVE: {
+ mCleanly = aCleanly;
+ mCloseStatus = aStatus;
+ mReason = aReason;
+ mWebTransportSession = nullptr;
+ ChangeState(WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING);
+ CallOnSessionClosed();
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ // The session has been canceled. We do not need to set
+ // mWebTransportSession.
+ break;
+ }
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::CallOnSessionClosedLocked() {
+ MutexAutoLock lock(mMutex);
+ CallOnSessionClosed();
+}
+
+void WebTransportSessionProxy::CallOnSessionClosed() {
+ mMutex.AssertCurrentThreadOwns();
+
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CallOnSessionClosed",
+ [self{std::move(self)}]() { self->CallOnSessionClosedLocked(); }));
+ return;
+ }
+
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ bool cleanly = false;
+ nsAutoCString reason;
+ uint32_t closeStatus = 0;
+
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ MOZ_ASSERT(false, "CallOnSessionClosed cannot be called in this state.");
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ listener = mListener;
+ mListener = nullptr;
+ cleanly = mCleanly;
+ reason = mReason;
+ closeStatus = mCloseStatus;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ break;
+ }
+
+ if (listener) {
+ // Don't invoke the callback under the lock.
+ MutexAutoUnlock unlock(mMutex);
+ listener->OnSessionClosed(cleanly, closeStatus, reason);
+ }
+}
+
+bool WebTransportSessionProxy::CheckServerCertificateIfNeeded() {
+ if (mServerCertHashes.IsEmpty()) {
+ return true;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ MOZ_ASSERT(httpChannel, "Not a http channel ?");
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ httpChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ MOZ_ASSERT(tsi,
+ "We shouln't reach this code before setting the security info.");
+ nsCOMPtr<nsIX509Cert> cert;
+ nsresult rv = tsi->GetServerCert(getter_AddRefs(cert));
+ if (!cert || NS_WARN_IF(NS_FAILED(rv))) return true;
+ nsTArray<uint8_t> certDER;
+ if (NS_FAILED(cert->GetRawDER(certDER))) {
+ return false;
+ }
+ // https://w3c.github.io/webtransport/#compute-a-certificate-hash
+ nsTArray<uint8_t> certHash;
+ if (NS_FAILED(Digest::DigestBuf(SEC_OID_SHA256, certDER.Elements(),
+ certDER.Length(), certHash)) ||
+ certHash.Length() != SHA256_LENGTH) {
+ return false;
+ }
+ auto verifyCertDer = [&certHash](const auto& hash) {
+ return certHash.Length() == hash.Length() &&
+ memcmp(certHash.Elements(), hash.Elements(), certHash.Length()) == 0;
+ };
+
+ // https://w3c.github.io/webtransport/#verify-a-certificate-hash
+ for (const auto& hash : mServerCertHashes) {
+ nsCString algorithm;
+ if (NS_FAILED(hash->GetAlgorithm(algorithm)) || algorithm != "sha-256") {
+ continue;
+ LOG(("Unexpected non-SHA-256 hash"));
+ }
+
+ nsTArray<uint8_t> value;
+ if (NS_FAILED(hash->GetValue(value))) {
+ continue;
+ LOG(("Unexpected corrupted hash"));
+ }
+
+ if (verifyCertDer(value)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void WebTransportSessionProxy::ChangeState(
+ WebTransportSessionProxyState newState) {
+ mMutex.AssertCurrentThreadOwns();
+ LOG(("WebTransportSessionProxy::ChangeState %d -> %d [this=%p]", mState,
+ newState, this));
+ switch (newState) {
+ case WebTransportSessionProxyState::INIT:
+ MOZ_ASSERT(false, "Cannot change into INIT sate.");
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ MOZ_ASSERT(mState == WebTransportSessionProxyState::INIT,
+ "Only from INIT can be change into NEGOTIATING");
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ MOZ_ASSERT(
+ mState == WebTransportSessionProxyState::NEGOTIATING,
+ "Only from NEGOTIATING can be change into NEGOTIATING_SUCCEEDED");
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mWebTransportSession);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::ACTIVE:
+ MOZ_ASSERT(mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED,
+ "Only from NEGOTIATING_SUCCEEDED can be change into ACTIVE");
+ MOZ_ASSERT(!mChannel);
+ MOZ_ASSERT(mWebTransportSession);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ MOZ_ASSERT(
+ (mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) ||
+ (mState == WebTransportSessionProxyState::ACTIVE),
+ "Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into"
+ " SESSION_CLOSE_PENDING");
+ MOZ_ASSERT(!mChannel);
+ MOZ_ASSERT(mWebTransportSession);
+ MOZ_ASSERT(!mListener);
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ MOZ_ASSERT(
+ (mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) ||
+ (mState == WebTransportSessionProxyState::ACTIVE),
+ "Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into"
+ " CLOSE_CALLBACK_PENDING");
+ MOZ_ASSERT(!mWebTransportSession);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ MOZ_ASSERT(
+ (mState == WebTransportSessionProxyState::NEGOTIATING) ||
+ (mState ==
+ WebTransportSessionProxyState::SESSION_CLOSE_PENDING) ||
+ (mState == WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING),
+ "Only from NEGOTIATING, SESSION_CLOSE_PENDING and "
+ "CLOSE_CALLBACK_PENDING can be change into DONE");
+ MOZ_ASSERT(!mChannel);
+ MOZ_ASSERT(!mWebTransportSession);
+ MOZ_ASSERT(!mListener);
+ break;
+ }
+ mState = newState;
+}
+
+void WebTransportSessionProxy::NotifyDatagramReceived(
+ nsTArray<uint8_t>&& aData) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (!mStopRequestCalled) {
+ CopyableTArray<uint8_t> copied(aData);
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, data = std::move(copied)]() mutable {
+ self->NotifyDatagramReceived(std::move(data));
+ });
+ return;
+ }
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnDatagramReceived(aData);
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceivedInternal(
+ nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnDatagramReceived",
+ [self = RefPtr{this}, data{std::move(aData)}]() mutable {
+ self->NotifyDatagramReceived(std::move(data));
+ }));
+ }
+ }
+
+ NotifyDatagramReceived(std::move(aData));
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceived(
+ const nsTArray<uint8_t>& aData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void WebTransportSessionProxy::OnMaxDatagramSizeInternal(uint64_t aSize) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (!mStopRequestCalled) {
+ mPendingEvents.AppendElement([self = RefPtr{this}, size(aSize)]() {
+ self->OnMaxDatagramSizeInternal(size);
+ });
+ return;
+ }
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnMaxDatagramSize(aSize);
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnMaxDatagramSize(uint64_t aSize) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(
+ NS_NewRunnableFunction("WebTransportSessionProxy::OnMaxDatagramSize",
+ [self = RefPtr{this}, size(aSize)] {
+ self->OnMaxDatagramSizeInternal(size);
+ }));
+ }
+ }
+
+ OnMaxDatagramSizeInternal(aSize);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::OnOutgoingDatagramOutComeInternal(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnOutgoingDatagramOutCome(aId, aOutCome);
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnOutgoingDatagramOutCome",
+ [self = RefPtr{this}, id(aId), outcome(aOutCome)] {
+ self->OnOutgoingDatagramOutComeInternal(id, outcome);
+ }));
+ }
+ }
+
+ OnOutgoingDatagramOutComeInternal(aId, aOutCome);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnStopSending(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(OnSocketThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return NS_OK;
+ }
+ listener = mListener;
+ }
+
+ listener->OnStopSending(aStreamId, aError);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnResetReceived(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(OnSocketThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return NS_OK;
+ }
+ listener = mListener;
+ }
+
+ listener->OnResetReceived(aStreamId, aError);
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.h b/netwerk/protocol/webtransport/WebTransportSessionProxy.h
new file mode 100644
index 0000000000..aebb446e21
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebTransportProxy_h
+#define mozilla_net_WebTransportProxy_h
+
+#include <functional>
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRedirectResultListener.h"
+#include "nsIStreamListener.h"
+#include "nsIWebTransport.h"
+
+/*
+ * WebTransportSessionProxy is introduced to enable the creation of a
+ * Http3WebTransportSession and coordination of actions that are performed on
+ * the main thread and on the socket thread.
+ *
+ * mChannel, mRedirectChannel, and mListener are used only on the main thread.
+ *
+ * mWebTransportSession is used only on the socket thread.
+ *
+ * mState and mSessionId are used on both threads, socket and main thread and it
+ * is only used with a lock.
+ *
+ *
+ * WebTransportSessionProxyState:
+ * - INIT: before AsyncConnect is called.
+ *
+ * - NEGOTIATING: It is set during AsyncConnect. During this state HttpChannel
+ * is open but OnStartRequest has not been called yet. This state can
+ * transfer into:
+ * - NEGOTIATING_SUCCEEDED: when a Http3WebTransportSession has been
+ * negotiated.
+ * - DONE: when a WebTransport session has been canceled.
+ *
+ * - NEGOTIATING_SUCCEEDED: It is set during parsing of
+ * Http3WebTransportSession response when the response has been successful.
+ * mWebTransport is set to the Http3WebTransportSession at the same time the
+ * session changes to this state. This state can transfer into:
+ * - ACTIVE: during the OnStopRequest call if the WebTransport has not been
+ * canceled or failed for other reason, e.g. a browser shutdown or content
+ * blocking policies.
+ * - SESSION_CLOSE_PENDING: if the WebTransport has been canceled via an API
+ * call or content blocking policies. (the main thread initiated close).
+ * - CLOSE_CALLBACK_PENDING: if Http3WebTransportSession has been canceled
+ * due to a shutdown or a server closing a session. (the socket thread
+ * initiated close).
+ *
+ * - ACTIVE: In this state the session is negotiated and ready to use. This
+ * state can transfer into:
+ * - SESSION_CLOSE_PENDING: if the WebTransport has been canceled via an API
+ * call(nsIWebTransport::closeSession) or content blocking policies. (the
+ * main thread initiated close).
+ * - CLOSE_CALLBACK_PENDING: if Http3WebTransportSession has been canceled
+ * due to a shutdown or a server closing a session. (the socket thread
+ * initiated close).
+ *
+ * - CLOSE_CALLBACK_PENDING: This is the socket thread initiated close. In this
+ * state, the Http3WebTransportSession has been closed and a
+ * CallOnSessionClosed call is dispatched to the main thread to call the
+ * appropriate listener.
+ *
+ * - SESSION_CLOSE_PENDING: This is the main thread initiated close. In this
+ * state, the WebTransport has been closed via an API call
+ * (nsIWebTransport::closeSession) and a CloseSessionInternal call is
+ * dispatched to the socket thread to close the appropriate
+ * Http3WebTransportSession.
+ *
+ * - DONE: everything has been cleaned up on both threads.
+ *
+ *
+ * AsyncConnect creates mChannel on the main thread. Redirect callbacks are also
+ * performed on the main thread (mRedirectChannel set and access only on the
+ * main thread). Before this point, there are no activities on the socket thread
+ * and Http3WebTransportSession is nullptr. mChannel is going to create a
+ * nsHttpTransaction. The transaction will be dispatched on a nsAHttpConnection,
+ * i.e. currently only the HTTP/3 version is implemented, therefore this will be
+ * a HttpConnectionUDP and a Http3Session. The Http3Session creates a
+ * Http3WebTransportSession. Until a response is received
+ * Http3WebTransportSession is only accessed by Http3Session. During parsing of
+ * a successful received from a server on the socket thread,
+ * WebTransportSessionProxy::mWebTransportSession will take a reference to
+ * Http3WebTransportSession and mState will be set to NEGOTIATING_SUCCEEDED.
+ * From now on WebTransportSessionProxy is responsible for closing
+ * Http3WebTransportSession if the closing of the session is initiated on the
+ * main thread. OnStartRequest and OnStopRequest will be called on the main
+ * thread. The session negotiation can have 2 outcomes:
+ * - If both calls, i.e. OnStartRequest an OnStopRequest, indicate that the
+ * request has succeeded and mState is NEGOTIATING_SUCCEEDED, the
+ * mListener->OnSessionReady will be called during OnStopRequest.
+ * - Otherwise, mListener->OnSessionClosed will be called, the state transferred
+ * into SESSION_CLOSE_PENDING, and CloseSessionInternal will be dispatched to
+ * the socket thread.
+ *
+ * CloseSession is called on the main thread. If the session is already closed
+ * it returns an error. If the session is in state NEGOTIATING or
+ * NEGOTIATING_SUCCEEDED mChannel will be canceled. If the session is in state
+ * NEGOTIATING_SUCCEEDED or ACTIVE the state transferred into
+ * SESSION_CLOSE_PENDING, and CloseSessionInternal will be dispatched to the
+ * socket thread
+ *
+ * OnSessionReadyInternal is called on the socket thread. If mState is
+ * NEGOTIATING the state will be set to NEGOTIATING_SUCCEEDED and mWebTransport
+ * will be set to the newly negotiated Http3WebTransportSession. If mState is
+ * DONE, the Http3WebTransportSession will be close.
+ *
+ * OnSessionClosed is called on the socket thread. mState will be set to
+ * CLOSE_CALLBACK_PENDING and CallOnSessionClosed will be dispatched to the main
+ * thread.
+ *
+ * mWebTransport is set during states NEGOTIATING_SUCCEEDED, ACTIVE and
+ * SESSION_CLOSE_PENDING.
+ */
+
+namespace mozilla::net {
+
+class WebTransportStreamCallbackWrapper;
+
+class WebTransportSessionProxy final : public nsIWebTransport,
+ public WebTransportSessionEventListener,
+ public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIRedirectResultListener,
+ public nsIInterfaceRequestor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBTRANSPORT
+ NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WebTransportSessionProxy();
+
+ private:
+ ~WebTransportSessionProxy();
+
+ void CloseSessionInternal();
+ void CloseSessionInternalLocked();
+ void CallOnSessionClosed();
+ void CallOnSessionClosedLocked();
+
+ enum WebTransportSessionProxyState {
+ INIT,
+ NEGOTIATING,
+ NEGOTIATING_SUCCEEDED,
+ ACTIVE,
+ CLOSE_CALLBACK_PENDING,
+ SESSION_CLOSE_PENDING,
+ DONE,
+ };
+ mozilla::Mutex mMutex;
+ WebTransportSessionProxyState mState MOZ_GUARDED_BY(mMutex) =
+ WebTransportSessionProxyState::INIT;
+ void ChangeState(WebTransportSessionProxyState newState);
+ void CreateStreamInternal(nsIWebTransportStreamCallback* callback,
+ bool aBidi);
+ void DoCreateStream(WebTransportStreamCallbackWrapper* aCallback,
+ Http3WebTransportSession* aSession, bool aBidi);
+ void SendDatagramInternal(const RefPtr<Http3WebTransportSession>& aSession,
+ nsTArray<uint8_t>&& aData, uint64_t aTrackingId);
+ void NotifyDatagramReceived(nsTArray<uint8_t>&& aData);
+ void GetMaxDatagramSizeInternal(
+ const RefPtr<Http3WebTransportSession>& aSession);
+ void OnMaxDatagramSizeInternal(uint64_t aSize);
+ void OnOutgoingDatagramOutComeInternal(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome);
+ bool CheckServerCertificateIfNeeded();
+
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes;
+ nsCOMPtr<WebTransportSessionEventListener> mListener MOZ_GUARDED_BY(mMutex);
+ RefPtr<Http3WebTransportSession> mWebTransportSession MOZ_GUARDED_BY(mMutex);
+ uint64_t mSessionId MOZ_GUARDED_BY(mMutex) = UINT64_MAX;
+ uint32_t mCloseStatus MOZ_GUARDED_BY(mMutex) = 0;
+ nsCString mReason MOZ_GUARDED_BY(mMutex);
+ bool mCleanly MOZ_GUARDED_BY(mMutex) = false;
+ bool mStopRequestCalled MOZ_GUARDED_BY(mMutex) = false;
+ // This is used to store events happened before OnSessionReady.
+ // Note that these events will be dispatched to the socket thread.
+ nsTArray<std::function<void()>> mPendingEvents MOZ_GUARDED_BY(mMutex);
+ nsTArray<std::function<void(nsresult)>> mPendingCreateStreamEvents
+ MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIEventTarget> mTarget MOZ_GUARDED_BY(mMutex);
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_WebTransportProxy_h
diff --git a/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp
new file mode 100644
index 0000000000..6192bda0f7
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp
@@ -0,0 +1,386 @@
+/* -*- 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 "WebTransportStreamProxy.h"
+
+#include "WebTransportLog.h"
+#include "Http3WebTransportStream.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(WebTransportStreamProxy)
+NS_IMPL_RELEASE(WebTransportStreamProxy)
+
+NS_INTERFACE_MAP_BEGIN(WebTransportStreamProxy)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebTransportReceiveStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportReceiveStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportSendStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportBidirectionalStream)
+NS_INTERFACE_MAP_END
+
+WebTransportStreamProxy::WebTransportStreamProxy(
+ Http3WebTransportStream* aStream)
+ : mWebTransportStream(aStream) {
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ mWebTransportStream->GetWriterAndReader(getter_AddRefs(outputStream),
+ getter_AddRefs(inputStream));
+ if (outputStream) {
+ mWriter = new AsyncOutputStreamWrapper(outputStream);
+ }
+ if (inputStream) {
+ mReader = new AsyncInputStreamWrapper(inputStream, mWebTransportStream);
+ }
+}
+
+WebTransportStreamProxy::~WebTransportStreamProxy() {
+ // mWebTransportStream needs to be destroyed on the socket thread.
+ NS_ProxyRelease("WebTransportStreamProxy::~WebTransportStreamProxy",
+ gSocketTransportService, mWebTransportStream.forget());
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SendStopSending(uint8_t aError) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("WebTransportStreamProxy::SendStopSending",
+ [self{std::move(self)}, error(aError)]() {
+ self->SendStopSending(error);
+ }));
+ }
+
+ mWebTransportStream->SendStopSending(aError);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SendFin(void) {
+ if (!mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWriter->Close();
+
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::SendFin",
+ [self{std::move(self)}]() { self->mWebTransportStream->SendFin(); }));
+ }
+
+ mWebTransportStream->SendFin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::Reset(uint8_t aErrorCode) {
+ if (!mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWriter->Close();
+
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("WebTransportStreamProxy::Reset",
+ [self{std::move(self)}, error(aErrorCode)]() {
+ self->mWebTransportStream->Reset(error);
+ }));
+ }
+
+ mWebTransportStream->Reset(aErrorCode);
+ return NS_OK;
+}
+
+namespace {
+
+class StatsCallbackWrapper : public nsIWebTransportStreamStatsCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit StatsCallbackWrapper(nsIWebTransportStreamStatsCallback* aCallback)
+ : mCallback(aCallback), mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_IMETHOD OnSendStatsAvailable(
+ nsIWebTransportSendStreamStats* aStats) override {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<StatsCallbackWrapper> self(this);
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats = aStats;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "StatsCallbackWrapper::OnSendStatsAvailable",
+ [self{std::move(self)}, stats{std::move(stats)}]() {
+ self->OnSendStatsAvailable(stats);
+ }));
+ return NS_OK;
+ }
+
+ mCallback->OnSendStatsAvailable(aStats);
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnReceiveStatsAvailable(
+ nsIWebTransportReceiveStreamStats* aStats) override {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<StatsCallbackWrapper> self(this);
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats = aStats;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "StatsCallbackWrapper::OnReceiveStatsAvailable",
+ [self{std::move(self)}, stats{std::move(stats)}]() {
+ self->OnReceiveStatsAvailable(stats);
+ }));
+ return NS_OK;
+ }
+
+ mCallback->OnReceiveStatsAvailable(aStats);
+ return NS_OK;
+ }
+
+ private:
+ virtual ~StatsCallbackWrapper() {
+ NS_ProxyRelease("StatsCallbackWrapper::~StatsCallbackWrapper", mTarget,
+ mCallback.forget());
+ }
+
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(StatsCallbackWrapper, nsIWebTransportStreamStatsCallback)
+
+} // namespace
+
+NS_IMETHODIMP WebTransportStreamProxy::GetSendStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> callback =
+ new StatsCallbackWrapper(aCallback);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::GetSendStreamStats",
+ [self{std::move(self)}, callback{std::move(callback)}]() {
+ self->GetSendStreamStats(callback);
+ }));
+ }
+
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats =
+ mWebTransportStream->GetSendStreamStats();
+ aCallback->OnSendStatsAvailable(stats);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetReceiveStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> callback =
+ new StatsCallbackWrapper(aCallback);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::GetReceiveStreamStats",
+ [self{std::move(self)}, callback{std::move(callback)}]() {
+ self->GetReceiveStreamStats(callback);
+ }));
+ }
+
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats =
+ mWebTransportStream->GetReceiveStreamStats();
+ aCallback->OnReceiveStatsAvailable(stats);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetHasReceivedFIN(
+ bool* aHasReceivedFIN) {
+ *aHasReceivedFIN = mWebTransportStream->RecvDone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetInputStream(
+ nsIAsyncInputStream** aOut) {
+ if (!mReader) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<AsyncInputStreamWrapper> stream = mReader;
+ stream.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetOutputStream(
+ nsIAsyncOutputStream** aOut) {
+ if (!mWriter) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<AsyncOutputStreamWrapper> stream = mWriter;
+ stream.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetStreamId(uint64_t* aId) {
+ *aId = mWebTransportStream->StreamId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SetSendOrder(Maybe<int64_t> aSendOrder) {
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "SetSendOrder", [stream = mWebTransportStream, aSendOrder]() {
+ stream->SetSendOrder(aSendOrder);
+ }));
+ }
+ mWebTransportStream->SetSendOrder(aSendOrder);
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+// WebTransportStreamProxy::AsyncInputStreamWrapper
+//------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WebTransportStreamProxy::AsyncInputStreamWrapper,
+ nsIInputStream, nsIAsyncInputStream)
+
+WebTransportStreamProxy::AsyncInputStreamWrapper::AsyncInputStreamWrapper(
+ nsIAsyncInputStream* aStream, Http3WebTransportStream* aWebTransportStream)
+ : mStream(aStream), mWebTransportStream(aWebTransportStream) {}
+
+WebTransportStreamProxy::AsyncInputStreamWrapper::~AsyncInputStreamWrapper() =
+ default;
+
+void WebTransportStreamProxy::AsyncInputStreamWrapper::MaybeCloseStream() {
+ if (!mWebTransportStream->RecvDone()) {
+ return;
+ }
+
+ uint64_t available = 0;
+ Unused << Available(&available);
+ if (available) {
+ // Don't close the InputStream if there's unread data available, since it
+ // would be lost. We exit above unless we know no more data will be received
+ // for the stream.
+ return;
+ }
+
+ LOG(
+ ("AsyncInputStreamWrapper::MaybeCloseStream close stream due to FIN "
+ "stream=%p",
+ mWebTransportStream.get()));
+ Close();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Close() {
+ return mStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Available(
+ uint64_t* aAvailable) {
+ return mStream->Available(aAvailable);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::StreamStatus() {
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Read(
+ char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ LOG(("WebTransportStreamProxy::AsyncInputStreamWrapper::Read %p", this));
+ nsresult rv = mStream->Read(aBuf, aCount, aResult);
+ MaybeCloseStream();
+ return rv;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::ReadSegments(
+ nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ LOG(("WebTransportStreamProxy::AsyncInputStreamWrapper::ReadSegments %p",
+ this));
+ nsresult rv = mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+ if (*aResult > 0) {
+ LOG((" Read %u bytes", *aResult));
+ }
+ MaybeCloseStream();
+ return rv;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::IsNonBlocking(
+ bool* aResult) {
+ return mStream->IsNonBlocking(aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::CloseWithStatus(
+ nsresult aStatus) {
+ return mStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::AsyncWait(
+ nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget);
+}
+
+//------------------------------------------------------------------------------
+// WebTransportStreamProxy::AsyncOutputStreamWrapper
+//------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WebTransportStreamProxy::AsyncOutputStreamWrapper,
+ nsIOutputStream, nsIAsyncOutputStream)
+
+WebTransportStreamProxy::AsyncOutputStreamWrapper::AsyncOutputStreamWrapper(
+ nsIAsyncOutputStream* aStream)
+ : mStream(aStream) {}
+
+WebTransportStreamProxy::AsyncOutputStreamWrapper::~AsyncOutputStreamWrapper() =
+ default;
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Flush() {
+ return mStream->Flush();
+}
+
+NS_IMETHODIMP
+WebTransportStreamProxy::AsyncOutputStreamWrapper::StreamStatus() {
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Write(
+ const char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ LOG(
+ ("WebTransportStreamProxy::AsyncOutputStreamWrapper::Write %p %u bytes, "
+ "first byte %c",
+ this, aCount, aBuf[0]));
+ return mStream->Write(aBuf, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::WriteFrom(
+ nsIInputStream* aFromStream, uint32_t aCount, uint32_t* aResult) {
+ return mStream->WriteFrom(aFromStream, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::WriteSegments(
+ nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ return mStream->WriteSegments(aReader, aClosure, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::AsyncWait(
+ nsIOutputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget);
+}
+
+NS_IMETHODIMP
+WebTransportStreamProxy::AsyncOutputStreamWrapper::CloseWithStatus(
+ nsresult aStatus) {
+ return mStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Close() {
+ return mStream->Close();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::IsNonBlocking(
+ bool* aResult) {
+ return mStream->IsNonBlocking(aResult);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportStreamProxy.h b/netwerk/protocol/webtransport/WebTransportStreamProxy.h
new file mode 100644
index 0000000000..e6ccc573fa
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.h
@@ -0,0 +1,84 @@
+/* -*- 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 mozilla_net_WebTransportStreamProxy_h
+#define mozilla_net_WebTransportStreamProxy_h
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIWebTransportStream.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::net {
+
+class Http3WebTransportStream;
+
+class WebTransportStreamProxy final
+ : public nsIWebTransportReceiveStream,
+ public nsIWebTransportSendStream,
+ public nsIWebTransportBidirectionalStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebTransportStreamProxy(Http3WebTransportStream* aStream);
+
+ NS_IMETHOD SendStopSending(uint8_t aError) override;
+ NS_IMETHOD SendFin() override;
+ NS_IMETHOD Reset(uint8_t aErrorCode) override;
+ NS_IMETHOD GetSendStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) override;
+ NS_IMETHOD GetReceiveStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) override;
+
+ NS_IMETHOD GetHasReceivedFIN(bool* aHasReceivedFIN) override;
+
+ NS_IMETHOD GetInputStream(nsIAsyncInputStream** aOut) override;
+ NS_IMETHOD GetOutputStream(nsIAsyncOutputStream** aOut) override;
+
+ NS_IMETHOD GetStreamId(uint64_t* aId) override;
+ NS_IMETHOD SetSendOrder(Maybe<int64_t> aSendOrder) override;
+
+ private:
+ virtual ~WebTransportStreamProxy();
+
+ class AsyncInputStreamWrapper : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ AsyncInputStreamWrapper(nsIAsyncInputStream* aStream,
+ Http3WebTransportStream* aWebTransportStream);
+
+ private:
+ virtual ~AsyncInputStreamWrapper();
+ void MaybeCloseStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ RefPtr<Http3WebTransportStream> mWebTransportStream;
+ };
+
+ class AsyncOutputStreamWrapper : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit AsyncOutputStreamWrapper(nsIAsyncOutputStream* aStream);
+
+ private:
+ virtual ~AsyncOutputStreamWrapper();
+
+ nsCOMPtr<nsIAsyncOutputStream> mStream;
+ };
+
+ RefPtr<Http3WebTransportStream> mWebTransportStream;
+ RefPtr<AsyncOutputStreamWrapper> mWriter;
+ RefPtr<AsyncInputStreamWrapper> mReader;
+};
+
+} // namespace mozilla::net
+
+#endif
diff --git a/netwerk/protocol/webtransport/moz.build b/netwerk/protocol/webtransport/moz.build
new file mode 100644
index 0000000000..2dd99978ac
--- /dev/null
+++ b/netwerk/protocol/webtransport/moz.build
@@ -0,0 +1,36 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: WebTransport")
+
+XPIDL_SOURCES += [
+ "nsIWebTransport.idl",
+ "nsIWebTransportStream.idl",
+]
+
+XPIDL_MODULE = "necko_webtransport"
+
+EXPORTS.mozilla.net += [
+ "WebTransportHash.h",
+ "WebTransportSessionProxy.h",
+ "WebTransportStreamProxy.h",
+]
+
+UNIFIED_SOURCES += [
+ "WebTransportHash.cpp",
+ "WebTransportSessionProxy.cpp",
+ "WebTransportStreamProxy.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/protocol/webtransport/nsIWebTransport.idl b/netwerk/protocol/webtransport/nsIWebTransport.idl
new file mode 100644
index 0000000000..faf99e61b6
--- /dev/null
+++ b/netwerk/protocol/webtransport/nsIWebTransport.idl
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIURI.idl"
+#include "nsIPrincipal.idl"
+
+interface WebTransportSessionEventListener;
+interface nsIWebTransportStreamCallback;
+interface nsIWebTransportBidirectionalStream;
+interface nsIWebTransportSendStream;
+interface nsIWebTransportReceiveStream;
+interface nsIWebTransportHash;
+
+%{C++
+namespace mozilla::dom {
+class ClientInfo;
+}
+namespace mozilla::net {
+class Http3WebTransportSession;
+class Http3WebTransportStream;
+}
+%}
+
+[ptr] native Http3WebTransportSessionPtr(mozilla::net::Http3WebTransportSession);
+[ptr] native Http3WebTransportStreamPtr(mozilla::net::Http3WebTransportStream);
+native Datagram(nsTArray<uint8_t>&&);
+[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>);
+
+[builtinclass, scriptable, uuid(c20d6e77-8cb1-4838-a88d-fff826080aa3)]
+interface nsIWebTransport : nsISupports {
+ cenum WebTransportError : 16 {
+ UNKNOWN_ERROR,
+ INVALID_STATE_ERROR,
+ };
+
+ // When called, perform steps in "Initialization WebTransport over HTTP".
+ void asyncConnect(in nsIURI aURI,
+ in Array<nsIWebTransportHash> aServerCertHashes,
+ in nsIPrincipal aLoadingPrincipal,
+ in unsigned long aSecurityFlags,
+ in WebTransportSessionEventListener aListener);
+
+ void asyncConnectWithClient(in nsIURI aURI,
+ in Array<nsIWebTransportHash> aServerCertHashes,
+ in nsIPrincipal aLoadingPrincipal,
+ in unsigned long aSecurityFlags,
+ in WebTransportSessionEventListener aListener,
+ in const_MaybeClientInfoRef aClientInfo);
+
+ // Asynchronously get stats.
+ void getStats();
+
+ // Close the session.
+ void closeSession(in uint32_t aErrorCode,
+ in ACString aReason);
+
+ // Create and open a new WebTransport stream.
+ void createOutgoingBidirectionalStream(in nsIWebTransportStreamCallback aListener);
+ void createOutgoingUnidirectionalStream(in nsIWebTransportStreamCallback aListener);
+
+ void sendDatagram(in Array<uint8_t> aData, in uint64_t aTrackingId);
+
+ void getMaxDatagramSize();
+
+ // This can be only called after onSessionReady().
+ // After this point, we can retarget the underlying WebTransportSessionProxy
+ // object off main thread.
+ [noscript] void retargetTo(in nsIEventTarget aTarget);
+};
+
+// Events related to a WebTransport session.
+[scriptable, uuid(0e3cb269-f318-43c8-959e-897f57894b71)]
+interface WebTransportSessionEventListener : nsISupports {
+ // This is used to let the consumer of nsIWebTransport know that the
+ // underlying WebTransportSession object is ready to use.
+ void onSessionReady(in uint64_t aSessionId);
+ // This is used internally to pass the reference of WebTransportSession
+ // object to WebTransportSessionProxy.
+ void onSessionReadyInternal(in Http3WebTransportSessionPtr aSession);
+ void onSessionClosed(in bool aCleanly,
+ in uint32_t aErrorCode,
+ in ACString aReason);
+
+ // When a new stream has been received.
+ void onIncomingBidirectionalStreamAvailable(in nsIWebTransportBidirectionalStream aStream);
+ void onIncomingUnidirectionalStreamAvailable(in nsIWebTransportReceiveStream aStream);
+
+ // This is used internally to pass the reference of Http3WebTransportStream
+ // object to WebTransportSessionProxy.
+ void onIncomingStreamAvailableInternal(in Http3WebTransportStreamPtr aStream);
+
+ void onStopSending(in uint64_t aStreamId, in nsresult aError);
+ void onResetReceived(in uint64_t aStreamId, in nsresult aError);
+
+ // When a new datagram has been received.
+ void onDatagramReceived(in Array<uint8_t> aData);
+
+ // This is used internally to pass the datagram to WebTransportSessionProxy.
+ void onDatagramReceivedInternal(in Datagram aData);
+
+ void onMaxDatagramSize(in uint64_t aSize);
+
+ cenum DatagramOutcome: 32 {
+ UNKNOWN = 0,
+ DROPPED_TOO_MUCH_DATA = 1,
+ SENT = 2,
+ };
+
+ void onOutgoingDatagramOutCome(
+ in uint64_t aId,
+ in WebTransportSessionEventListener_DatagramOutcome aOutCome);
+
+ // void onStatsAvailable(in WebTransportStats aStats);
+};
+
+// This interface is used as a callback when creating an outgoing
+// unidirectional or bidirectional stream.
+[scriptable, uuid(c6eeff1d-599b-40a8-9157-c7a40c3d51a2)]
+interface nsIWebTransportStreamCallback : nsISupports {
+ void onBidirectionalStreamReady(in nsIWebTransportBidirectionalStream aStream);
+ void onUnidirectionalStreamReady(in nsIWebTransportSendStream aStream);
+ void onError(in uint8_t aError);
+};
+
+[scriptable, uuid(2523a26e-94be-4de6-8c27-9b4ffff742f0)]
+interface nsIWebTransportHash : nsISupports {
+ readonly attribute ACString algorithm;
+ readonly attribute Array<uint8_t> value;
+};
diff --git a/netwerk/protocol/webtransport/nsIWebTransportStream.idl b/netwerk/protocol/webtransport/nsIWebTransportStream.idl
new file mode 100644
index 0000000000..9283f4aa30
--- /dev/null
+++ b/netwerk/protocol/webtransport/nsIWebTransportStream.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+interface nsIInputStreamCallback;
+interface nsIEventTarget;
+
+%{C++
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+class TimeStamp;
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+native MaybeInt64(mozilla::Maybe<int64_t>);
+
+[builtinclass, scriptable, uuid(ccc3e685-8411-48f0-8b3e-ff6d1fae4809)]
+interface nsIWebTransportSendStreamStats : nsISupports {
+ [noscript] readonly attribute TimeStamp timestamp;
+ readonly attribute unsigned long long bytesSent;
+ readonly attribute unsigned long long bytesAcknowledged;
+};
+
+[builtinclass, scriptable, uuid(43ce1145-30ef-41a7-b97d-fa797f7f7d18)]
+interface nsIWebTransportReceiveStreamStats : nsISupports {
+ [noscript] readonly attribute TimeStamp timestamp;
+ readonly attribute unsigned long long bytesReceived;
+};
+
+[scriptable, uuid(9c1df3f5-bf04-46b6-9977-eb6389076db8)]
+interface nsIWebTransportStreamStatsCallback : nsISupports {
+ void onSendStatsAvailable(in nsIWebTransportSendStreamStats aStats);
+ void onReceiveStatsAvailable(in nsIWebTransportReceiveStreamStats aStats);
+};
+
+[builtinclass, scriptable, uuid(d461b235-6291-4817-adcc-a2a3b3dfc10b)]
+interface nsIWebTransportReceiveStream : nsISupports {
+ // Sends the STOP_SENDING on the stream.
+ void sendStopSending(in uint8_t aError);
+
+ void getReceiveStreamStats(
+ in nsIWebTransportStreamStatsCallback aCallback);
+
+ // When true, this indicates that FIN had been received.
+ readonly attribute boolean hasReceivedFIN;
+ readonly attribute nsIAsyncInputStream inputStream;
+ readonly attribute uint64_t streamId;
+};
+
+[builtinclass, scriptable, uuid(804f245c-52ea-403c-8a78-f751533bdd70)]
+interface nsIWebTransportSendStream : nsISupports {
+ // Sends the FIN on the stream.
+ void sendFin();
+
+ // Reset the stream with the specified error code.
+ void reset(in uint8_t aErrorCode);
+
+ void getSendStreamStats(in nsIWebTransportStreamStatsCallback aCallback);
+ readonly attribute nsIAsyncOutputStream outputStream;
+ readonly attribute uint64_t streamId;
+ [noscript] void setSendOrder(in MaybeInt64 aSendOrder);
+};
+
+[builtinclass, scriptable, uuid(f9ecb509-36db-4689-97d6-137639a08750)]
+interface nsIWebTransportBidirectionalStream : nsISupports {
+ // Sends the STOP_SENDING on the stream.
+ void sendStopSending(in uint8_t aError);
+
+ // Sends the FIN on the stream.
+ void sendFin();
+
+ // Reset the stream with the specified error code.
+ void reset(in uint8_t aErrorCode);
+
+ // When true, this indicates that FIN had been received.
+ readonly attribute boolean hasReceivedFIN;
+
+ readonly attribute nsIAsyncInputStream inputStream;
+ readonly attribute nsIAsyncOutputStream outputStream;
+ readonly attribute uint64_t streamId;
+ [noscript] void setSendOrder(in MaybeInt64 aSendOrder);
+};
diff --git a/netwerk/sctp/datachannel/DataChannel.cpp b/netwerk/sctp/datachannel/DataChannel.cpp
new file mode 100644
index 0000000000..8af9a558b8
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -0,0 +1,3548 @@
+/* -*- 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 <algorithm>
+#include <stdio.h>
+#include <stdlib.h>
+#if !defined(__Userspace_os_Windows)
+# include <arpa/inet.h>
+#endif
+// usrsctp.h expects to have errno definitions prior to its inclusion.
+#include <errno.h>
+
+#define SCTP_DEBUG 1
+#define SCTP_STDINT_INCLUDE <stdint.h>
+
+#ifdef _MSC_VER
+// Disable "warning C4200: nonstandard extension used : zero-sized array in
+// struct/union"
+// ...which the third-party file usrsctp.h runs afoul of.
+# pragma warning(push)
+# pragma warning(disable : 4200)
+#endif
+
+#include "usrsctp.h"
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+#include "nsServiceManagerUtils.h"
+#include "nsIInputStream.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Sprintf.h"
+#include "nsProxyRelease.h"
+#include "nsThread.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "mozilla/RandomNum.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/RTCDataChannelBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/media/MediaUtils.h"
+#ifdef MOZ_PEERCONNECTION
+# include "transport/runnable_utils.h"
+# include "jsapi/MediaTransportHandler.h"
+# include "mediapacket.h"
+#endif
+
+#include "DataChannel.h"
+#include "DataChannelProtocol.h"
+
+// Let us turn on and off important assertions in non-debug builds
+#ifdef DEBUG
+# define ASSERT_WEBRTC(x) MOZ_ASSERT((x))
+#elif defined(MOZ_WEBRTC_ASSERT_ALWAYS)
+# define ASSERT_WEBRTC(x) \
+ do { \
+ if (!(x)) { \
+ MOZ_CRASH(); \
+ } \
+ } while (0)
+#endif
+
+namespace mozilla {
+
+LazyLogModule gDataChannelLog("DataChannel");
+static LazyLogModule gSCTPLog("SCTP");
+
+#define SCTP_LOG(args) \
+ MOZ_LOG(mozilla::gSCTPLog, mozilla::LogLevel::Debug, args)
+
+static void debug_printf(const char* format, ...) {
+ va_list ap;
+ char buffer[1024];
+
+ if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
+ va_start(ap, format);
+#ifdef _WIN32
+ if (vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, format, ap) > 0) {
+#else
+ if (VsprintfLiteral(buffer, format, ap) > 0) {
+#endif
+ SCTP_LOG(("%s", buffer));
+ }
+ va_end(ap);
+ }
+}
+
+static constexpr const char* ToString(DataChannelState state) {
+ switch (state) {
+ case DataChannelState::Connecting:
+ return "CONNECTING";
+ case DataChannelState::Open:
+ return "OPEN";
+ case DataChannelState::Closing:
+ return "CLOSING";
+ case DataChannelState::Closed:
+ return "CLOSED";
+ }
+ return "";
+};
+
+static constexpr const char* ToString(DataChannelConnectionState state) {
+ switch (state) {
+ case DataChannelConnectionState::Connecting:
+ return "CONNECTING";
+ case DataChannelConnectionState::Open:
+ return "OPEN";
+ case DataChannelConnectionState::Closed:
+ return "CLOSED";
+ }
+ return "";
+};
+
+static constexpr const char* ToString(
+ DataChannelOnMessageAvailable::EventType type) {
+ switch (type) {
+ case DataChannelOnMessageAvailable::EventType::OnConnection:
+ return "ON_CONNECTION";
+ case DataChannelOnMessageAvailable::EventType::OnDisconnected:
+ return "ON_DISCONNECTED";
+ case DataChannelOnMessageAvailable::EventType::OnChannelCreated:
+ return "ON_CHANNEL_CREATED";
+ case DataChannelOnMessageAvailable::EventType::OnDataString:
+ return "ON_DATA_STRING";
+ case DataChannelOnMessageAvailable::EventType::OnDataBinary:
+ return "ON_DATA_BINARY";
+ }
+ return "";
+};
+
+static constexpr const char* ToString(DataChannelConnection::PendingType type) {
+ switch (type) {
+ case DataChannelConnection::PendingType::None:
+ return "NONE";
+ case DataChannelConnection::PendingType::Dcep:
+ return "DCEP";
+ case DataChannelConnection::PendingType::Data:
+ return "DATA";
+ }
+ return "";
+};
+
+static constexpr const char* ToString(DataChannelReliabilityPolicy type) {
+ switch (type) {
+ case DataChannelReliabilityPolicy::Reliable:
+ return "RELIABLE";
+ case DataChannelReliabilityPolicy::LimitedRetransmissions:
+ return "LIMITED_RETRANSMISSIONS";
+ case DataChannelReliabilityPolicy::LimitedLifetime:
+ return "LIMITED_LIFETIME";
+ }
+ return "";
+};
+
+static constexpr uint16_t ToUsrsctpValue(DataChannelReliabilityPolicy type) {
+ switch (type) {
+ case DataChannelReliabilityPolicy::Reliable:
+ return SCTP_PR_SCTP_NONE;
+ case DataChannelReliabilityPolicy::LimitedRetransmissions:
+ return SCTP_PR_SCTP_RTX;
+ case DataChannelReliabilityPolicy::LimitedLifetime:
+ return SCTP_PR_SCTP_TTL;
+ }
+ return SCTP_PR_SCTP_NONE;
+};
+
+class DataChannelRegistry {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannelRegistry)
+
+ static uintptr_t Register(DataChannelConnection* aConnection) {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ uintptr_t result = EnsureInstance()->RegisterImpl(aConnection);
+ DC_DEBUG(
+ ("Registering connection %p as ulp %p", aConnection, (void*)result));
+ return result;
+ }
+
+ static void Deregister(uintptr_t aId) {
+ RefPtr<DataChannelRegistry> maybeTrash;
+
+ {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ DC_DEBUG(("Deregistering connection ulp = %p", (void*)aId));
+ if (NS_WARN_IF(!Instance())) {
+ return;
+ }
+ Instance()->DeregisterImpl(aId);
+ if (Instance()->Empty()) {
+ // Unset singleton inside mutex lock, but don't call Shutdown until we
+ // unlock, since that involves calling into libusrsctp, which invites
+ // deadlock.
+ maybeTrash = Instance().forget();
+ }
+ }
+ }
+
+ static RefPtr<DataChannelConnection> Lookup(uintptr_t aId) {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ if (NS_WARN_IF(!Instance())) {
+ return nullptr;
+ }
+ return Instance()->LookupImpl(aId);
+ }
+
+ private:
+ // This is a singleton class, so don't let just anyone create one of these
+ DataChannelRegistry() {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ mShutdownBlocker = media::ShutdownBlockingTicket::Create(
+ u"DataChannelRegistry::mShutdownBlocker"_ns,
+ NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);
+ InitUsrSctp();
+ }
+
+ static RefPtr<DataChannelRegistry>& Instance() {
+ static RefPtr<DataChannelRegistry> sRegistry;
+ return sRegistry;
+ }
+
+ static RefPtr<DataChannelRegistry>& EnsureInstance() {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ if (!Instance()) {
+ Instance() = new DataChannelRegistry();
+ }
+ return Instance();
+ }
+
+ uintptr_t RegisterImpl(DataChannelConnection* aConnection) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ mConnections.emplace(mNextId, aConnection);
+ return mNextId++;
+ }
+
+ void DeregisterImpl(uintptr_t aId) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ mConnections.erase(aId);
+ }
+
+ bool Empty() const { return mConnections.empty(); }
+
+ RefPtr<DataChannelConnection> LookupImpl(uintptr_t aId) {
+ auto it = mConnections.find(aId);
+ if (NS_WARN_IF(it == mConnections.end())) {
+ DC_DEBUG(("Can't find connection ulp %p", (void*)aId));
+ return nullptr;
+ }
+ return it->second;
+ }
+
+ virtual ~DataChannelRegistry() {
+ ASSERT_WEBRTC(NS_IsMainThread());
+
+ if (NS_WARN_IF(!mConnections.empty())) {
+ MOZ_ASSERT(false);
+ mConnections.clear();
+ }
+
+ DeinitUsrSctp();
+ }
+
+#ifdef SCTP_DTLS_SUPPORTED
+ static int SctpDtlsOutput(void* addr, void* buffer, size_t length,
+ uint8_t tos, uint8_t set_df) {
+ uintptr_t id = reinterpret_cast<uintptr_t>(addr);
+ RefPtr<DataChannelConnection> connection = DataChannelRegistry::Lookup(id);
+ if (NS_WARN_IF(!connection) || connection->InShutdown()) {
+ return 0;
+ }
+ return connection->SctpDtlsOutput(addr, buffer, length, tos, set_df);
+ }
+#endif
+
+ void InitUsrSctp() {
+#ifndef MOZ_PEERCONNECTION
+ MOZ_CRASH("Trying to use SCTP/DTLS without dom/media/webrtc/transport");
+#endif
+
+ DC_DEBUG(("Calling usrsctp_init %p", this));
+
+ usrsctp_init(0, DataChannelRegistry::SctpDtlsOutput, debug_printf);
+
+ // Set logging to SCTP:LogLevel::Debug to get SCTP debugs
+ if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
+ usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+ }
+
+ // Do not send ABORTs in response to INITs (1).
+ // Do not send ABORTs for received Out of the Blue packets (2).
+ usrsctp_sysctl_set_sctp_blackhole(2);
+
+ // Disable the Explicit Congestion Notification extension (currently not
+ // supported by the Firefox code)
+ usrsctp_sysctl_set_sctp_ecn_enable(0);
+
+ // Enable interleaving messages for different streams (incoming)
+ // See: https://tools.ietf.org/html/rfc6458#section-8.1.20
+ usrsctp_sysctl_set_sctp_default_frag_interleave(2);
+
+ // Disabling authentication and dynamic address reconfiguration as neither
+ // of them are used for data channel and only result in additional code
+ // paths being used.
+ usrsctp_sysctl_set_sctp_asconf_enable(0);
+ usrsctp_sysctl_set_sctp_auth_enable(0);
+ }
+
+ void DeinitUsrSctp() {
+ DC_DEBUG(("Calling usrsctp_finish %p", this));
+ usrsctp_finish();
+ }
+
+ uintptr_t mNextId = 1;
+ std::map<uintptr_t, RefPtr<DataChannelConnection>> mConnections;
+ UniquePtr<media::ShutdownBlockingTicket> mShutdownBlocker;
+ static StaticMutex sInstanceMutex MOZ_UNANNOTATED;
+};
+
+StaticMutex DataChannelRegistry::sInstanceMutex;
+
+OutgoingMsg::OutgoingMsg(struct sctp_sendv_spa& info, const uint8_t* data,
+ size_t length)
+ : mLength(length), mData(data) {
+ mInfo = &info;
+ mPos = 0;
+}
+
+void OutgoingMsg::Advance(size_t offset) {
+ mPos += offset;
+ if (mPos > mLength) {
+ mPos = mLength;
+ }
+}
+
+BufferedOutgoingMsg::BufferedOutgoingMsg(OutgoingMsg& msg) {
+ size_t length = msg.GetLeft();
+ auto* tmp = new uint8_t[length]; // infallible malloc!
+ memcpy(tmp, msg.GetData(), length);
+ mLength = length;
+ mData = tmp;
+ mInfo = new sctp_sendv_spa;
+ *mInfo = msg.GetInfo();
+ mPos = 0;
+}
+
+BufferedOutgoingMsg::~BufferedOutgoingMsg() {
+ delete mInfo;
+ delete[] mData;
+}
+
+static int receive_cb(struct socket* sock, union sctp_sockstore addr,
+ void* data, size_t datalen, struct sctp_rcvinfo rcv,
+ int flags, void* ulp_info) {
+ DC_DEBUG(("In receive_cb, ulp_info=%p", ulp_info));
+ uintptr_t id = reinterpret_cast<uintptr_t>(ulp_info);
+ RefPtr<DataChannelConnection> connection = DataChannelRegistry::Lookup(id);
+ if (!connection) {
+ // Unfortunately, we can get callbacks after calling
+ // usrsctp_close(socket), so we need to simply ignore them if we've
+ // already killed the DataChannelConnection object
+ DC_DEBUG((
+ "Ignoring receive callback for terminated Connection ulp=%p, %zu bytes",
+ ulp_info, datalen));
+ return 0;
+ }
+ return connection->ReceiveCallback(sock, data, datalen, rcv, flags);
+}
+
+static RefPtr<DataChannelConnection> GetConnectionFromSocket(
+ struct socket* sock) {
+ struct sockaddr* addrs = nullptr;
+ int naddrs = usrsctp_getladdrs(sock, 0, &addrs);
+ if (naddrs <= 0 || addrs[0].sa_family != AF_CONN) {
+ return nullptr;
+ }
+ // usrsctp_getladdrs() returns the addresses bound to this socket, which
+ // contains the SctpDataMediaChannel* as sconn_addr. Read the pointer,
+ // then free the list of addresses once we have the pointer. We only open
+ // AF_CONN sockets, and they should all have the sconn_addr set to the
+ // pointer that created them, so [0] is as good as any other.
+ struct sockaddr_conn* sconn =
+ reinterpret_cast<struct sockaddr_conn*>(&addrs[0]);
+ uintptr_t id = reinterpret_cast<uintptr_t>(sconn->sconn_addr);
+ RefPtr<DataChannelConnection> connection = DataChannelRegistry::Lookup(id);
+ usrsctp_freeladdrs(addrs);
+
+ return connection;
+}
+
+// Called when the buffer empties to the threshold value. This is called
+// from SctpDtlsInput() through the sctp stack. SctpDtlsInput() calls
+// usrsctp_conninput() under lock
+int DataChannelConnection::OnThresholdEvent(struct socket* sock,
+ uint32_t sb_free, void* ulp_info) {
+ RefPtr<DataChannelConnection> connection = GetConnectionFromSocket(sock);
+ connection->mLock.AssertCurrentThreadOwns();
+ if (connection) {
+ connection->SendDeferredMessages();
+ } else {
+ DC_ERROR(("Can't find connection for socket %p", sock));
+ }
+ return 0;
+}
+
+DataChannelConnection::~DataChannelConnection() {
+ DC_DEBUG(("Deleting DataChannelConnection %p", (void*)this));
+ // This may die on the MainThread, or on the STS thread, or on an
+ // sctp thread if we were in a callback when the DOM side shut things down.
+ ASSERT_WEBRTC(mState == DataChannelConnectionState::Closed);
+ MOZ_ASSERT(!mMasterSocket);
+ MOZ_ASSERT(mPending.GetSize() == 0);
+
+ if (!IsSTSThread()) {
+ // We may be on MainThread *or* on an sctp thread (being called from
+ // receive_cb() or SctpDtlsOutput())
+ if (mInternalIOThread) {
+ // Avoid spinning the event thread from here (which if we're mainthread
+ // is in the event loop already)
+ nsCOMPtr<nsIRunnable> r = WrapRunnable(
+ nsCOMPtr<nsIThread>(mInternalIOThread), &nsIThread::AsyncShutdown);
+ Dispatch(r.forget());
+ }
+ } else {
+ // on STS, safe to call shutdown
+ if (mInternalIOThread) {
+ mInternalIOThread->Shutdown();
+ }
+ }
+}
+
+void DataChannelConnection::Destroy() {
+ // Though it's probably ok to do this and close the sockets;
+ // if we really want it to do true clean shutdowns it can
+ // create a dependant Internal object that would remain around
+ // until the network shut down the association or timed out.
+ DC_DEBUG(("Destroying DataChannelConnection %p", (void*)this));
+ ASSERT_WEBRTC(NS_IsMainThread());
+ CloseAll();
+
+ MutexAutoLock lock(mLock);
+ // If we had a pending reset, we aren't waiting for it - clear the list so
+ // we can deregister this DataChannelConnection without leaking.
+ ClearResets();
+
+ MOZ_ASSERT(mSTS);
+ ASSERT_WEBRTC(NS_IsMainThread());
+ mListener = nullptr;
+ // Finish Destroy on STS thread to avoid bug 876167 - once that's fixed,
+ // the usrsctp_close() calls can move back here (and just proxy the
+ // disconnect_all())
+ RUN_ON_THREAD(mSTS,
+ WrapRunnable(RefPtr<DataChannelConnection>(this),
+ &DataChannelConnection::DestroyOnSTS, mSocket,
+ mMasterSocket),
+ NS_DISPATCH_NORMAL);
+
+ // These will be released on STS
+ mSocket = nullptr;
+ mMasterSocket = nullptr; // also a flag that we've Destroyed this connection
+
+ // We can't get any more *new* callbacks from the SCTP library
+
+ // All existing callbacks have refs to DataChannelConnection - however,
+ // we need to handle their destroying the object off mainthread/STS
+
+ // nsDOMDataChannel objects have refs to DataChannels that have refs to us
+}
+
+void DataChannelConnection::DestroyOnSTS(struct socket* aMasterSocket,
+ struct socket* aSocket) {
+ if (aSocket && aSocket != aMasterSocket) usrsctp_close(aSocket);
+ if (aMasterSocket) usrsctp_close(aMasterSocket);
+
+ usrsctp_deregister_address(reinterpret_cast<void*>(mId));
+ DC_DEBUG(
+ ("Deregistered %p from the SCTP stack.", reinterpret_cast<void*>(mId)));
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mShutdown = true;
+ DC_DEBUG(("Shutting down connection %p, id %p", this, (void*)mId));
+#endif
+
+ disconnect_all();
+ mTransportHandler = nullptr;
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "DataChannelConnection::Destroy",
+ [id = mId]() { DataChannelRegistry::Deregister(id); }));
+}
+
+Maybe<RefPtr<DataChannelConnection>> DataChannelConnection::Create(
+ DataChannelConnection::DataConnectionListener* aListener,
+ nsISerialEventTarget* aTarget, MediaTransportHandler* aHandler,
+ const uint16_t aLocalPort, const uint16_t aNumStreams,
+ const Maybe<uint64_t>& aMaxMessageSize) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+
+ RefPtr<DataChannelConnection> connection = new DataChannelConnection(
+ aListener, aTarget, aHandler); // Walks into a bar
+ return connection->Init(aLocalPort, aNumStreams, aMaxMessageSize)
+ ? Some(connection)
+ : Nothing();
+}
+
+DataChannelConnection::DataChannelConnection(
+ DataChannelConnection::DataConnectionListener* aListener,
+ nsISerialEventTarget* aTarget, MediaTransportHandler* aHandler)
+ : NeckoTargetHolder(aTarget),
+ mLock("netwerk::sctp::DataChannelConnection"),
+ mListener(aListener),
+ mTransportHandler(aHandler) {
+ DC_VERBOSE(("Constructor DataChannelConnection=%p, listener=%p", this,
+ mListener.get()));
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mShutdown = false;
+#endif
+}
+
+bool DataChannelConnection::Init(const uint16_t aLocalPort,
+ const uint16_t aNumStreams,
+ const Maybe<uint64_t>& aMaxMessageSize) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+
+ struct sctp_initmsg initmsg = {};
+ struct sctp_assoc_value av = {};
+ struct sctp_event event = {};
+ socklen_t len;
+
+ uint16_t event_types[] = {
+ SCTP_ASSOC_CHANGE, SCTP_PEER_ADDR_CHANGE,
+ SCTP_REMOTE_ERROR, SCTP_SHUTDOWN_EVENT,
+ SCTP_ADAPTATION_INDICATION, SCTP_PARTIAL_DELIVERY_EVENT,
+ SCTP_SEND_FAILED_EVENT, SCTP_STREAM_RESET_EVENT,
+ SCTP_STREAM_CHANGE_EVENT};
+ {
+ // MutexAutoLock lock(mLock); Not needed since we're on mainthread always
+ mLocalPort = aLocalPort;
+ SetMaxMessageSize(aMaxMessageSize.isSome(), aMaxMessageSize.valueOr(0));
+ }
+
+ mId = DataChannelRegistry::Register(this);
+
+ // XXX FIX! make this a global we get once
+ // Find the STS thread
+ nsresult rv;
+ mSTS = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Open sctp with a callback
+ if ((mMasterSocket =
+ usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb,
+ &DataChannelConnection::OnThresholdEvent,
+ usrsctp_sysctl_get_sctp_sendspace() / 2,
+ reinterpret_cast<void*>(mId))) == nullptr) {
+ return false;
+ }
+
+ int buf_size = 1024 * 1024;
+
+ if (usrsctp_setsockopt(mMasterSocket, SOL_SOCKET, SO_RCVBUF,
+ (const void*)&buf_size, sizeof(buf_size)) < 0) {
+ DC_ERROR(("Couldn't change receive buffer size on SCTP socket"));
+ goto error_cleanup;
+ }
+ if (usrsctp_setsockopt(mMasterSocket, SOL_SOCKET, SO_SNDBUF,
+ (const void*)&buf_size, sizeof(buf_size)) < 0) {
+ DC_ERROR(("Couldn't change send buffer size on SCTP socket"));
+ goto error_cleanup;
+ }
+
+ // Make non-blocking for bind/connect. SCTP over UDP defaults to non-blocking
+ // in associations for normal IO
+ if (usrsctp_set_non_blocking(mMasterSocket, 1) < 0) {
+ DC_ERROR(("Couldn't set non_blocking on SCTP socket"));
+ // We can't handle connect() safely if it will block, not that this will
+ // even happen.
+ goto error_cleanup;
+ }
+
+ // Make sure when we close the socket, make sure it doesn't call us back
+ // again! This would cause it try to use an invalid DataChannelConnection
+ // pointer
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+ if (usrsctp_setsockopt(mMasterSocket, SOL_SOCKET, SO_LINGER, (const void*)&l,
+ (socklen_t)sizeof(struct linger)) < 0) {
+ DC_ERROR(("Couldn't set SO_LINGER on SCTP socket"));
+ // unsafe to allow it to continue if this fails
+ goto error_cleanup;
+ }
+
+ // XXX Consider disabling this when we add proper SDP negotiation.
+ // We may want to leave enabled for supporting 'cloning' of SDP offers, which
+ // implies re-use of the same pseudo-port number, or forcing a renegotiation.
+ {
+ const int option_value = 1;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_REUSE_PORT,
+ (const void*)&option_value,
+ (socklen_t)sizeof(option_value)) < 0) {
+ DC_WARN(("Couldn't set SCTP_REUSE_PORT on SCTP socket"));
+ }
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_NODELAY,
+ (const void*)&option_value,
+ (socklen_t)sizeof(option_value)) < 0) {
+ DC_WARN(("Couldn't set SCTP_NODELAY on SCTP socket"));
+ }
+ }
+
+ // Set explicit EOR
+ {
+ const int option_value = 1;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_EXPLICIT_EOR,
+ (const void*)&option_value,
+ (socklen_t)sizeof(option_value)) < 0) {
+ DC_ERROR(("*** failed to enable explicit EOR mode %d", errno));
+ goto error_cleanup;
+ }
+ }
+
+ // Enable ndata
+ // TODO: Bug 1381145, enable this once ndata has been deployed
+#if 0
+ av.assoc_id = SCTP_FUTURE_ASSOC;
+ av.assoc_value = 1;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INTERLEAVING_SUPPORTED, &av,
+ (socklen_t)sizeof(struct sctp_assoc_value)) < 0) {
+ DC_ERROR(("*** failed enable ndata errno %d", errno));
+ goto error_cleanup;
+ }
+#endif
+
+ av.assoc_id = SCTP_ALL_ASSOC;
+ av.assoc_value = SCTP_ENABLE_RESET_STREAM_REQ | SCTP_ENABLE_CHANGE_ASSOC_REQ;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET,
+ &av, (socklen_t)sizeof(struct sctp_assoc_value)) < 0) {
+ DC_ERROR(("*** failed enable stream reset errno %d", errno));
+ goto error_cleanup;
+ }
+
+ /* Enable the events of interest. */
+ event.se_assoc_id = SCTP_ALL_ASSOC;
+ event.se_on = 1;
+ for (unsigned short event_type : event_types) {
+ event.se_type = event_type;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_EVENT, &event,
+ sizeof(event)) < 0) {
+ DC_ERROR(("*** failed setsockopt SCTP_EVENT errno %d", errno));
+ goto error_cleanup;
+ }
+ }
+
+ len = sizeof(initmsg);
+ if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg,
+ &len) < 0) {
+ DC_ERROR(("*** failed getsockopt SCTP_INITMSG"));
+ goto error_cleanup;
+ }
+ DC_DEBUG(("Setting number of SCTP streams to %u, was %u/%u", aNumStreams,
+ initmsg.sinit_num_ostreams, initmsg.sinit_max_instreams));
+ initmsg.sinit_num_ostreams = aNumStreams;
+ initmsg.sinit_max_instreams = MAX_NUM_STREAMS;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg,
+ (socklen_t)sizeof(initmsg)) < 0) {
+ DC_ERROR(("*** failed setsockopt SCTP_INITMSG, errno %d", errno));
+ goto error_cleanup;
+ }
+
+ mSocket = nullptr;
+ mSTS->Dispatch(
+ NS_NewRunnableFunction("DataChannelConnection::Init", [id = mId]() {
+ usrsctp_register_address(reinterpret_cast<void*>(id));
+ DC_DEBUG(("Registered %p within the SCTP stack.",
+ reinterpret_cast<void*>(id)));
+ }));
+
+ return true;
+
+error_cleanup:
+ usrsctp_close(mMasterSocket);
+ mMasterSocket = nullptr;
+ return false;
+}
+
+// Only called on MainThread, mMaxMessageSize is read on other threads
+void DataChannelConnection::SetMaxMessageSize(bool aMaxMessageSizeSet,
+ uint64_t aMaxMessageSize) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ MutexAutoLock lock(mLock);
+
+ if (mMaxMessageSizeSet && !aMaxMessageSizeSet) {
+ // Don't overwrite already set MMS with default values
+ return;
+ }
+
+ mMaxMessageSizeSet = aMaxMessageSizeSet;
+ mMaxMessageSize = aMaxMessageSize;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService("@mozilla.org/preferences-service;1", &rv);
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
+
+ if (branch) {
+ int32_t temp;
+ if (!NS_FAILED(branch->GetIntPref(
+ "media.peerconnection.sctp.force_maximum_message_size", &temp))) {
+ if (temp >= 0) {
+ mMaxMessageSize = (uint64_t)temp;
+ }
+ }
+ }
+ }
+
+ // Fix remote MMS. This code exists, so future implementations of
+ // RTCSctpTransport.maxMessageSize can simply provide that value from
+ // GetMaxMessageSize.
+
+ // TODO: Bug 1382779, once resolved, can be increased to
+ // min(Uint8ArrayMaxSize, UINT32_MAX)
+ // TODO: Bug 1381146, once resolved, can be increased to whatever we support
+ // then (hopefully
+ // SIZE_MAX)
+ if (mMaxMessageSize == 0 ||
+ mMaxMessageSize > WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE) {
+ mMaxMessageSize = WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE;
+ }
+
+ DC_DEBUG(("Maximum message size (outgoing data): %" PRIu64
+ " (set=%s, enforced=%s)",
+ mMaxMessageSize, mMaxMessageSizeSet ? "yes" : "no",
+ aMaxMessageSize != mMaxMessageSize ? "yes" : "no"));
+}
+
+uint64_t DataChannelConnection::GetMaxMessageSize() {
+ MutexAutoLock lock(mLock);
+ return mMaxMessageSize;
+}
+
+void DataChannelConnection::AppendStatsToReport(
+ const UniquePtr<dom::RTCStatsCollection>& aReport,
+ const DOMHighResTimeStamp aTimestamp) const {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ nsString temp;
+ for (const RefPtr<DataChannel>& chan : mChannels.GetAll()) {
+ // If channel is empty, ignore
+ if (!chan) {
+ continue;
+ }
+ mozilla::dom::RTCDataChannelStats stats;
+ nsString id = u"dc"_ns;
+ id.AppendInt(chan->GetStream());
+ stats.mId.Construct(id);
+ chan->GetLabel(temp);
+ stats.mTimestamp.Construct(aTimestamp);
+ stats.mType.Construct(mozilla::dom::RTCStatsType::Data_channel);
+ stats.mLabel.Construct(temp);
+ chan->GetProtocol(temp);
+ stats.mProtocol.Construct(temp);
+ stats.mDataChannelIdentifier.Construct(chan->GetStream());
+ {
+ using State = mozilla::dom::RTCDataChannelState;
+ State state;
+ switch (chan->GetReadyState()) {
+ case DataChannelState::Connecting:
+ state = State::Connecting;
+ break;
+ case DataChannelState::Open:
+ state = State::Open;
+ break;
+ case DataChannelState::Closing:
+ state = State::Closing;
+ break;
+ case DataChannelState::Closed:
+ state = State::Closed;
+ break;
+ };
+ stats.mState.Construct(state);
+ }
+ auto counters = chan->GetTrafficCounters();
+ stats.mMessagesSent.Construct(counters.mMessagesSent);
+ stats.mBytesSent.Construct(counters.mBytesSent);
+ stats.mMessagesReceived.Construct(counters.mMessagesReceived);
+ stats.mBytesReceived.Construct(counters.mBytesReceived);
+ if (!aReport->mDataChannelStats.AppendElement(stats, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+}
+
+#ifdef MOZ_PEERCONNECTION
+
+bool DataChannelConnection::ConnectToTransport(const std::string& aTransportId,
+ const bool aClient,
+ const uint16_t aLocalPort,
+ const uint16_t aRemotePort) {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mMasterSocket,
+ "SCTP wasn't initialized before ConnectToTransport!");
+ static const auto paramString =
+ [](const std::string& tId, const Maybe<bool>& client,
+ const uint16_t localPort, const uint16_t remotePort) -> std::string {
+ std::ostringstream stream;
+ stream << "Transport ID: '" << tId << "', Role: '"
+ << (client ? (client.value() ? "client" : "server") : "")
+ << "', Local Port: '" << localPort << "', Remote Port: '"
+ << remotePort << "'";
+ return stream.str();
+ };
+
+ const auto params =
+ paramString(aTransportId, Some(aClient), aLocalPort, aRemotePort);
+ DC_DEBUG(("ConnectToTransport connecting DTLS transport with parameters: %s",
+ params.c_str()));
+
+ DataChannelConnectionState state = GetState();
+ if (state == DataChannelConnectionState::Open) {
+ if (aTransportId == mTransportId && mAllocateEven.isSome() &&
+ mAllocateEven.value() == aClient && mLocalPort == aLocalPort &&
+ mRemotePort == aRemotePort) {
+ DC_WARN(
+ ("Skipping attempt to connect to an already OPEN transport with "
+ "identical parameters."));
+ return true;
+ }
+ DC_WARN(
+ ("Attempting to connect to an already OPEN transport, because "
+ "different parameters were provided."));
+ DC_WARN(("Original transport parameters: %s",
+ paramString(mTransportId, mAllocateEven, mLocalPort, aRemotePort)
+ .c_str()));
+ DC_WARN(("New transport parameters: %s", params.c_str()));
+ }
+ if (NS_WARN_IF(aTransportId.empty())) {
+ return false;
+ }
+
+ mLocalPort = aLocalPort;
+ mRemotePort = aRemotePort;
+ SetState(DataChannelConnectionState::Connecting);
+ mAllocateEven = Some(aClient);
+
+ // Could be faster. Probably doesn't matter.
+ while (auto channel = mChannels.Get(INVALID_STREAM)) {
+ mChannels.Remove(channel);
+ channel->mStream = FindFreeStream();
+ if (channel->mStream != INVALID_STREAM) {
+ mChannels.Insert(channel);
+ }
+ }
+ RUN_ON_THREAD(mSTS,
+ WrapRunnable(RefPtr<DataChannelConnection>(this),
+ &DataChannelConnection::SetSignals, aTransportId),
+ NS_DISPATCH_NORMAL);
+ return true;
+}
+
+void DataChannelConnection::SetSignals(const std::string& aTransportId) {
+ ASSERT_WEBRTC(IsSTSThread());
+ {
+ MutexAutoLock lock(mLock);
+ mTransportId = aTransportId;
+ }
+ if (!mConnectedToTransportHandler) {
+ mTransportHandler->SignalPacketReceived.connect(
+ this, &DataChannelConnection::SctpDtlsInput);
+ mTransportHandler->SignalStateChange.connect(
+ this, &DataChannelConnection::TransportStateChange);
+ mConnectedToTransportHandler = true;
+ }
+ // SignalStateChange() doesn't call you with the initial state
+ if (mTransportHandler->GetState(mTransportId, false) ==
+ TransportLayer::TS_OPEN) {
+ DC_DEBUG(("Setting transport signals, dtls already open"));
+ CompleteConnect();
+ } else {
+ DC_DEBUG(("Setting transport signals, dtls not open yet"));
+ }
+}
+
+void DataChannelConnection::TransportStateChange(
+ const std::string& aTransportId, TransportLayer::State aState) {
+ ASSERT_WEBRTC(IsSTSThread());
+ if (aTransportId == mTransportId) {
+ if (aState == TransportLayer::TS_OPEN) {
+ DC_DEBUG(("Transport is open!"));
+ CompleteConnect();
+ } else if (aState == TransportLayer::TS_CLOSED ||
+ aState == TransportLayer::TS_NONE ||
+ aState == TransportLayer::TS_ERROR) {
+ DC_DEBUG(("Transport is closed!"));
+ Stop();
+ }
+ }
+}
+
+void DataChannelConnection::CompleteConnect() {
+ MutexAutoLock lock(mLock);
+
+ DC_DEBUG(("dtls open"));
+ ASSERT_WEBRTC(IsSTSThread());
+ if (!mMasterSocket) {
+ return;
+ }
+
+ struct sockaddr_conn addr = {};
+ addr.sconn_family = AF_CONN;
+# if defined(__Userspace_os_Darwin)
+ addr.sconn_len = sizeof(addr);
+# endif
+ addr.sconn_port = htons(mLocalPort);
+ addr.sconn_addr = reinterpret_cast<void*>(mId);
+
+ DC_DEBUG(("Calling usrsctp_bind"));
+ int r = usrsctp_bind(mMasterSocket, reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr));
+ if (r < 0) {
+ DC_ERROR(("usrsctp_bind failed: %d", r));
+ } else {
+ // This is the remote addr
+ addr.sconn_port = htons(mRemotePort);
+ DC_DEBUG(("Calling usrsctp_connect"));
+ r = usrsctp_connect(
+ mMasterSocket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
+ if (r >= 0 || errno == EINPROGRESS) {
+ struct sctp_paddrparams paddrparams = {};
+ socklen_t opt_len;
+
+ memcpy(&paddrparams.spp_address, &addr, sizeof(struct sockaddr_conn));
+ opt_len = (socklen_t)sizeof(struct sctp_paddrparams);
+ r = usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS,
+ &paddrparams, &opt_len);
+ if (r < 0) {
+ DC_ERROR(("usrsctp_getsockopt failed: %d", r));
+ } else {
+ // This field is misnamed. |spp_pathmtu| represents the maximum
+ // _payload_ size in libusrsctp. So:
+ // 1280 (a reasonable IPV6 MTU according to RFC 8831)
+ // -12 (sctp header)
+ // -24 (GCM sipher)
+ // -13 (DTLS record header)
+ // -8 (UDP header)
+ // -4 (TURN ChannelData)
+ // -40 (IPV6 header)
+ // = 1179
+ // We could further restrict this, because RFC 8831 suggests a starting
+ // IPV4 path MTU of 1200, which would lead to a value of 1115.
+ // I suspect that in practice the path MTU for IPV4 is substantially
+ // larger than 1200.
+ paddrparams.spp_pathmtu = 1179;
+ paddrparams.spp_flags &= ~SPP_PMTUD_ENABLE;
+ paddrparams.spp_flags |= SPP_PMTUD_DISABLE;
+ opt_len = (socklen_t)sizeof(struct sctp_paddrparams);
+ r = usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP,
+ SCTP_PEER_ADDR_PARAMS, &paddrparams, opt_len);
+ if (r < 0) {
+ DC_ERROR(("usrsctp_getsockopt failed: %d", r));
+ } else {
+ DC_ERROR(("usrsctp: PMTUD disabled, MTU set to %u",
+ paddrparams.spp_pathmtu));
+ }
+ }
+ }
+ if (r < 0) {
+ if (errno == EINPROGRESS) {
+ // non-blocking
+ return;
+ }
+ DC_ERROR(("usrsctp_connect failed: %d", errno));
+ SetState(DataChannelConnectionState::Closed);
+ } else {
+ // We fire ON_CONNECTION via SCTP_COMM_UP when we get that
+ return;
+ }
+ }
+ // Note: currently this doesn't actually notify the application
+ Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::EventType::OnConnection, this)));
+}
+
+// Process any pending Opens
+void DataChannelConnection::ProcessQueuedOpens() {
+ // The nsDeque holds channels with an AddRef applied. Another reference
+ // (may) be held by the DOMDataChannel, unless it's been GC'd. No other
+ // references should exist.
+
+ // Can't copy nsDeque's. Move into temp array since any that fail will
+ // go back to mPending
+ nsRefPtrDeque<DataChannel> temp;
+ RefPtr<DataChannel> temp_channel;
+ while (nullptr != (temp_channel = mPending.PopFront())) {
+ temp.Push(temp_channel.forget());
+ }
+
+ RefPtr<DataChannel> channel;
+
+ while (nullptr != (channel = temp.PopFront())) {
+ if (channel->mHasFinishedOpen) {
+ DC_DEBUG(("Processing queued open for %p (%u)", channel.get(),
+ channel->mStream));
+ channel->mHasFinishedOpen = false;
+ // OpenFinish returns a reference itself, so we need to take it can
+ // Release it
+ channel = OpenFinish(channel.forget()); // may reset the flag and re-push
+ } else {
+ NS_ASSERTION(false,
+ "How did a DataChannel get queued without the "
+ "mHasFinishedOpen flag?");
+ }
+ }
+}
+
+void DataChannelConnection::SctpDtlsInput(const std::string& aTransportId,
+ const MediaPacket& packet) {
+ MutexAutoLock lock(mLock);
+ if ((packet.type() != MediaPacket::SCTP) || (mTransportId != aTransportId)) {
+ return;
+ }
+
+ if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
+ char* buf;
+
+ if ((buf = usrsctp_dumppacket((void*)packet.data(), packet.len(),
+ SCTP_DUMP_INBOUND)) != nullptr) {
+ SCTP_LOG(("%s", buf));
+ usrsctp_freedumpbuffer(buf);
+ }
+ }
+ // Pass the data to SCTP
+ usrsctp_conninput(reinterpret_cast<void*>(mId), packet.data(), packet.len(),
+ 0);
+}
+
+void DataChannelConnection::SendPacket(std::unique_ptr<MediaPacket>&& packet) {
+ mSTS->Dispatch(NS_NewRunnableFunction(
+ "DataChannelConnection::SendPacket",
+ [this, self = RefPtr<DataChannelConnection>(this),
+ packet = std::move(packet)]() mutable {
+ // DC_DEBUG(("%p: SCTP/DTLS sent %ld bytes", this, len));
+ if (!mTransportId.empty() && mTransportHandler) {
+ mTransportHandler->SendPacket(mTransportId, std::move(*packet));
+ }
+ }));
+}
+
+int DataChannelConnection::SctpDtlsOutput(void* addr, void* buffer,
+ size_t length, uint8_t tos,
+ uint8_t set_df) {
+ if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
+ char* buf;
+
+ if ((buf = usrsctp_dumppacket(buffer, length, SCTP_DUMP_OUTBOUND)) !=
+ nullptr) {
+ SCTP_LOG(("%s", buf));
+ usrsctp_freedumpbuffer(buf);
+ }
+ }
+
+ // We're async proxying even if on the STSThread because this is called
+ // with internal SCTP locks held in some cases (such as in usrsctp_connect()).
+ // SCTP has an option for Apple, on IP connections only, to release at least
+ // one of the locks before calling a packet output routine; with changes to
+ // the underlying SCTP stack this might remove the need to use an async proxy.
+ std::unique_ptr<MediaPacket> packet(new MediaPacket);
+ packet->SetType(MediaPacket::SCTP);
+ packet->Copy(static_cast<const uint8_t*>(buffer), length);
+
+ if (NS_IsMainThread() && mDeferSend) {
+ mDeferredSend.emplace_back(std::move(packet));
+ return 0;
+ }
+
+ SendPacket(std::move(packet));
+ return 0; // cheat! Packets can always be dropped later anyways
+}
+#endif
+
+#ifdef ALLOW_DIRECT_SCTP_LISTEN_CONNECT
+// listen for incoming associations
+// Blocks! - Don't call this from main thread!
+
+bool DataChannelConnection::Listen(unsigned short port) {
+ struct sockaddr_in addr = {};
+ socklen_t addr_len;
+
+ NS_WARNING_ASSERTION(!NS_IsMainThread(),
+ "Blocks, do not call from main thread!!!");
+
+ /* Acting as the 'server' */
+# ifdef HAVE_SIN_LEN
+ addr.sin_len = sizeof(struct sockaddr_in);
+# endif
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ DC_DEBUG(("Waiting for connections on port %u", ntohs(addr.sin_port)));
+ {
+ MutexAutoLock lock(mLock);
+ SetState(DataChannelConnectionState::Connecting);
+ }
+ if (usrsctp_bind(mMasterSocket, reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(struct sockaddr_in)) < 0) {
+ DC_ERROR(("***Failed userspace_bind"));
+ return false;
+ }
+ if (usrsctp_listen(mMasterSocket, 1) < 0) {
+ DC_ERROR(("***Failed userspace_listen"));
+ return false;
+ }
+
+ DC_DEBUG(("Accepting connection"));
+ addr_len = 0;
+ if ((mSocket = usrsctp_accept(mMasterSocket, nullptr, &addr_len)) ==
+ nullptr) {
+ DC_ERROR(("***Failed accept"));
+ return false;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ SetState(DataChannelConnectionState::Open);
+ }
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+ if (usrsctp_setsockopt(mSocket, SOL_SOCKET, SO_LINGER, (const void*)&l,
+ (socklen_t)sizeof(struct linger)) < 0) {
+ DC_WARN(("Couldn't set SO_LINGER on SCTP socket"));
+ }
+
+ // Notify Connection open
+ // XXX We need to make sure connection sticks around until the message is
+ // delivered
+ DC_DEBUG(("%s: sending ON_CONNECTION for %p", __FUNCTION__, this));
+ Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::EventType::OnConnection, this,
+ (DataChannel*)nullptr)));
+ return true;
+}
+
+// Blocks! - Don't call this from main thread!
+bool DataChannelConnection::Connect(const char* addr, unsigned short port) {
+ struct sockaddr_in addr4 = {};
+ struct sockaddr_in6 addr6 = {};
+
+ NS_WARNING_ASSERTION(!NS_IsMainThread(),
+ "Blocks, do not call from main thread!!!");
+
+ /* Acting as the connector */
+ DC_DEBUG(("Connecting to %s, port %u", addr, port));
+# ifdef HAVE_SIN_LEN
+ addr4.sin_len = sizeof(struct sockaddr_in);
+# endif
+# ifdef HAVE_SIN6_LEN
+ addr6.sin6_len = sizeof(struct sockaddr_in6);
+# endif
+ addr4.sin_family = AF_INET;
+ addr6.sin6_family = AF_INET6;
+ addr4.sin_port = htons(port);
+ addr6.sin6_port = htons(port);
+ {
+ MutexAutoLock lock(mLock);
+ SetState(DataChannelConnectionState::Connecting);
+ }
+# if !defined(__Userspace_os_Windows)
+ if (inet_pton(AF_INET6, addr, &addr6.sin6_addr) == 1) {
+ if (usrsctp_connect(mMasterSocket,
+ reinterpret_cast<struct sockaddr*>(&addr6),
+ sizeof(struct sockaddr_in6)) < 0) {
+ DC_ERROR(("*** Failed userspace_connect"));
+ return false;
+ }
+ } else if (inet_pton(AF_INET, addr, &addr4.sin_addr) == 1) {
+ if (usrsctp_connect(mMasterSocket,
+ reinterpret_cast<struct sockaddr*>(&addr4),
+ sizeof(struct sockaddr_in)) < 0) {
+ DC_ERROR(("*** Failed userspace_connect"));
+ return false;
+ }
+ } else {
+ DC_ERROR(("*** Illegal destination address."));
+ }
+# else
+ {
+ struct sockaddr_storage ss;
+ int sslen = sizeof(ss);
+
+ if (!WSAStringToAddressA(const_cast<char*>(addr), AF_INET6, nullptr,
+ (struct sockaddr*)&ss, &sslen)) {
+ addr6.sin6_addr =
+ (reinterpret_cast<struct sockaddr_in6*>(&ss))->sin6_addr;
+ if (usrsctp_connect(mMasterSocket,
+ reinterpret_cast<struct sockaddr*>(&addr6),
+ sizeof(struct sockaddr_in6)) < 0) {
+ DC_ERROR(("*** Failed userspace_connect"));
+ return false;
+ }
+ } else if (!WSAStringToAddressA(const_cast<char*>(addr), AF_INET, nullptr,
+ (struct sockaddr*)&ss, &sslen)) {
+ addr4.sin_addr = (reinterpret_cast<struct sockaddr_in*>(&ss))->sin_addr;
+ if (usrsctp_connect(mMasterSocket,
+ reinterpret_cast<struct sockaddr*>(&addr4),
+ sizeof(struct sockaddr_in)) < 0) {
+ DC_ERROR(("*** Failed userspace_connect"));
+ return false;
+ }
+ } else {
+ DC_ERROR(("*** Illegal destination address."));
+ }
+ }
+# endif
+
+ mSocket = mMasterSocket;
+
+ DC_DEBUG(("connect() succeeded! Entering connected mode"));
+ {
+ MutexAutoLock lock(mLock);
+ SetState(DataChannelConnectionState::Open);
+ }
+ // Notify Connection open
+ // XXX We need to make sure connection sticks around until the message is
+ // delivered
+ DC_DEBUG(("%s: sending ON_CONNECTION for %p", __FUNCTION__, this));
+ Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::EventType::OnConnection, this,
+ (DataChannel*)nullptr)));
+ return true;
+}
+#endif
+
+DataChannel* DataChannelConnection::FindChannelByStream(uint16_t stream) {
+ return mChannels.Get(stream).get();
+}
+
+uint16_t DataChannelConnection::FindFreeStream() const {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ uint16_t i, limit;
+
+ limit = MAX_NUM_STREAMS;
+
+ MOZ_ASSERT(mAllocateEven.isSome());
+ for (i = (*mAllocateEven ? 0 : 1); i < limit; i += 2) {
+ if (mChannels.Get(i)) {
+ continue;
+ }
+
+ // Verify it's not still in the process of closing
+ size_t j;
+ for (j = 0; j < mStreamsResetting.Length(); ++j) {
+ if (mStreamsResetting[j] == i) {
+ break;
+ }
+ }
+
+ if (j == mStreamsResetting.Length()) {
+ return i;
+ }
+ }
+ return INVALID_STREAM;
+}
+
+uint32_t DataChannelConnection::UpdateCurrentStreamIndex() {
+ RefPtr<DataChannel> channel = mChannels.GetNextChannel(mCurrentStream);
+ if (!channel) {
+ mCurrentStream = 0;
+ } else {
+ mCurrentStream = channel->mStream;
+ }
+ return mCurrentStream;
+}
+
+uint32_t DataChannelConnection::GetCurrentStreamIndex() {
+ if (!mChannels.Get(mCurrentStream)) {
+ // The stream muse have been removed, reset
+ DC_DEBUG(("Reset mCurrentChannel"));
+ mCurrentStream = 0;
+ }
+ return mCurrentStream;
+}
+
+bool DataChannelConnection::RequestMoreStreams(int32_t aNeeded) {
+ struct sctp_status status = {};
+ struct sctp_add_streams sas = {};
+ uint32_t outStreamsNeeded;
+ socklen_t len;
+
+ if (aNeeded + mNegotiatedIdLimit > MAX_NUM_STREAMS) {
+ aNeeded = MAX_NUM_STREAMS - mNegotiatedIdLimit;
+ }
+ if (aNeeded <= 0) {
+ return false;
+ }
+
+ len = (socklen_t)sizeof(struct sctp_status);
+ if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_STATUS, &status,
+ &len) < 0) {
+ DC_ERROR(("***failed: getsockopt SCTP_STATUS"));
+ return false;
+ }
+ outStreamsNeeded = aNeeded; // number to add
+
+ // Note: if multiple channel opens happen when we don't have enough space,
+ // we'll call RequestMoreStreams() multiple times
+ sas.sas_instrms = 0;
+ sas.sas_outstrms = (uint16_t)outStreamsNeeded; /* XXX error handling */
+ // Doesn't block, we get an event when it succeeds or fails
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_ADD_STREAMS, &sas,
+ (socklen_t)sizeof(struct sctp_add_streams)) < 0) {
+ if (errno == EALREADY) {
+ DC_DEBUG(("Already have %u output streams", outStreamsNeeded));
+ return true;
+ }
+
+ DC_ERROR(("***failed: setsockopt ADD errno=%d", errno));
+ return false;
+ }
+ DC_DEBUG(("Requested %u more streams", outStreamsNeeded));
+ // We add to mNegotiatedIdLimit when we get a SCTP_STREAM_CHANGE_EVENT and the
+ // values are larger than mNegotiatedIdLimit
+ return true;
+}
+
+// Returns a POSIX error code.
+int DataChannelConnection::SendControlMessage(const uint8_t* data, uint32_t len,
+ uint16_t stream) {
+ struct sctp_sendv_spa info = {};
+
+ // General flags
+ info.sendv_flags = SCTP_SEND_SNDINFO_VALID;
+
+ // Set stream identifier, protocol identifier and flags
+ info.sendv_sndinfo.snd_sid = stream;
+ info.sendv_sndinfo.snd_flags = SCTP_EOR;
+ info.sendv_sndinfo.snd_ppid = htonl(DATA_CHANNEL_PPID_CONTROL);
+
+ // Create message instance and send
+ // Note: Main-thread IO, but doesn't block
+#if (UINT32_MAX > SIZE_MAX)
+ if (len > SIZE_MAX) {
+ return EMSGSIZE;
+ }
+#endif
+ OutgoingMsg msg(info, data, (size_t)len);
+ bool buffered;
+ int error = SendMsgInternalOrBuffer(mBufferedControl, msg, buffered, nullptr);
+
+ // Set pending type (if buffered)
+ if (!error && buffered && mPendingType == PendingType::None) {
+ mPendingType = PendingType::Dcep;
+ }
+ return error;
+}
+
+// Returns a POSIX error code.
+int DataChannelConnection::SendOpenAckMessage(uint16_t stream) {
+ struct rtcweb_datachannel_ack ack = {};
+ ack.msg_type = DATA_CHANNEL_ACK;
+
+ return SendControlMessage((const uint8_t*)&ack, sizeof(ack), stream);
+}
+
+// Returns a POSIX error code.
+int DataChannelConnection::SendOpenRequestMessage(
+ const nsACString& label, const nsACString& protocol, uint16_t stream,
+ bool unordered, DataChannelReliabilityPolicy prPolicy, uint32_t prValue) {
+ const size_t label_len = label.Length(); // not including nul
+ const size_t proto_len = protocol.Length(); // not including nul
+ // careful - request struct include one char for the label
+ const size_t req_size = sizeof(struct rtcweb_datachannel_open_request) - 1 +
+ label_len + proto_len;
+ UniqueFreePtr<struct rtcweb_datachannel_open_request> req(
+ (struct rtcweb_datachannel_open_request*)moz_xmalloc(req_size));
+
+ memset(req.get(), 0, req_size);
+ req->msg_type = DATA_CHANNEL_OPEN_REQUEST;
+ switch (prPolicy) {
+ case DataChannelReliabilityPolicy::Reliable:
+ req->channel_type = DATA_CHANNEL_RELIABLE;
+ break;
+ case DataChannelReliabilityPolicy::LimitedLifetime:
+ req->channel_type = DATA_CHANNEL_PARTIAL_RELIABLE_TIMED;
+ break;
+ case DataChannelReliabilityPolicy::LimitedRetransmissions:
+ req->channel_type = DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT;
+ break;
+ default:
+ return EINVAL;
+ }
+ if (unordered) {
+ // Per the current types, all differ by 0x80 between ordered and unordered
+ req->channel_type |=
+ 0x80; // NOTE: be careful if new types are added in the future
+ }
+
+ req->reliability_param = htonl(prValue);
+ req->priority = htons(0); /* XXX: add support */
+ req->label_length = htons(label_len);
+ req->protocol_length = htons(proto_len);
+ memcpy(&req->label[0], PromiseFlatCString(label).get(), label_len);
+ memcpy(&req->label[label_len], PromiseFlatCString(protocol).get(), proto_len);
+
+ // TODO: req_size is an int... that looks hairy
+ int error = SendControlMessage((const uint8_t*)req.get(), req_size, stream);
+ return error;
+}
+
+// XXX This should use a separate thread (outbound queue) which should
+// select() to know when to *try* to send data to the socket again.
+// Alternatively, it can use a timeout, but that's guaranteed to be wrong
+// (just not sure in what direction). We could re-implement NSPR's
+// PR_POLL_WRITE/etc handling... with a lot of work.
+
+// Better yet, use the SCTP stack's notifications on buffer state to avoid
+// filling the SCTP's buffers.
+
+// returns if we're still blocked (true)
+bool DataChannelConnection::SendDeferredMessages() {
+ RefPtr<DataChannel> channel; // we may null out the refs to this
+
+ // This may block while something is modifying channels, but should not block
+ // for IO
+ ASSERT_WEBRTC(!NS_IsMainThread());
+ mLock.AssertCurrentThreadOwns();
+
+ DC_DEBUG(("SendDeferredMessages called, pending type: %s",
+ ToString(mPendingType)));
+ if (mPendingType == PendingType::None) {
+ return false;
+ }
+
+ // Send pending control messages
+ // Note: If ndata is not active, check if DCEP messages are currently
+ // outstanding. These need to
+ // be sent first before other streams can be used for sending.
+ if (!mBufferedControl.IsEmpty() &&
+ (mSendInterleaved || mPendingType == PendingType::Dcep)) {
+ if (SendBufferedMessages(mBufferedControl, nullptr)) {
+ return true;
+ }
+
+ // Note: There may or may not be pending data messages
+ mPendingType = PendingType::Data;
+ }
+
+ bool blocked = false;
+ uint32_t i = GetCurrentStreamIndex();
+ uint32_t end = i;
+ do {
+ channel = mChannels.Get(i);
+ // Should already be cleared if closing/closed
+ if (!channel || channel->mBufferedData.IsEmpty()) {
+ i = UpdateCurrentStreamIndex();
+ continue;
+ }
+
+ // Send buffered data messages
+ // Warning: This will fail in case ndata is inactive and a previously
+ // deallocated data channel has not been closed properly. If you
+ // ever see that no messages can be sent on any channel, this is
+ // likely the cause (an explicit EOR message partially sent whose
+ // remaining chunks are still being waited for).
+ size_t written = 0;
+ mDeferSend = true;
+ blocked = SendBufferedMessages(channel->mBufferedData, &written);
+ mDeferSend = false;
+ if (written) {
+ channel->DecrementBufferedAmount(written);
+ }
+
+ for (auto&& packet : mDeferredSend) {
+ MOZ_ASSERT(written);
+ SendPacket(std::move(packet));
+ }
+ mDeferredSend.clear();
+
+ // Update current stream index
+ // Note: If ndata is not active, the outstanding data messages on this
+ // stream need to be sent first before other streams can be used for
+ // sending.
+ if (mSendInterleaved || !blocked) {
+ i = UpdateCurrentStreamIndex();
+ }
+ } while (!blocked && i != end);
+
+ if (!blocked) {
+ mPendingType =
+ mBufferedControl.IsEmpty() ? PendingType::None : PendingType::Dcep;
+ }
+ return blocked;
+}
+
+// Called with mLock locked!
+// buffer MUST have at least one item!
+// returns if we're still blocked (true)
+bool DataChannelConnection::SendBufferedMessages(
+ nsTArray<UniquePtr<BufferedOutgoingMsg>>& buffer, size_t* aWritten) {
+ do {
+ // Re-send message
+ int error = SendMsgInternal(*buffer[0], aWritten);
+ switch (error) {
+ case 0:
+ buffer.RemoveElementAt(0);
+ break;
+ case EAGAIN:
+#if (EAGAIN != EWOULDBLOCK)
+ case EWOULDBLOCK:
+#endif
+ return true;
+ default:
+ buffer.RemoveElementAt(0);
+ DC_ERROR(("error on sending: %d", error));
+ break;
+ }
+ } while (!buffer.IsEmpty());
+
+ return false;
+}
+
+// Caller must ensure that length <= SIZE_MAX
+void DataChannelConnection::HandleOpenRequestMessage(
+ const struct rtcweb_datachannel_open_request* req, uint32_t length,
+ uint16_t stream) {
+ RefPtr<DataChannel> channel;
+ uint32_t prValue;
+ DataChannelReliabilityPolicy prPolicy;
+
+ ASSERT_WEBRTC(!NS_IsMainThread());
+ mLock.AssertCurrentThreadOwns();
+
+ const size_t requiredLength = (sizeof(*req) - 1) + ntohs(req->label_length) +
+ ntohs(req->protocol_length);
+ if (((size_t)length) != requiredLength) {
+ if (((size_t)length) < requiredLength) {
+ DC_ERROR(
+ ("%s: insufficient length: %u, should be %zu. Unable to continue.",
+ __FUNCTION__, length, requiredLength));
+ return;
+ }
+ DC_WARN(("%s: Inconsistent length: %u, should be %zu", __FUNCTION__, length,
+ requiredLength));
+ }
+
+ DC_DEBUG(("%s: length %u, sizeof(*req) = %zu", __FUNCTION__, length,
+ sizeof(*req)));
+
+ switch (req->channel_type) {
+ case DATA_CHANNEL_RELIABLE:
+ case DATA_CHANNEL_RELIABLE_UNORDERED:
+ prPolicy = DataChannelReliabilityPolicy::Reliable;
+ break;
+ case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT:
+ case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED:
+ prPolicy = DataChannelReliabilityPolicy::LimitedRetransmissions;
+ break;
+ case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED:
+ case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED:
+ prPolicy = DataChannelReliabilityPolicy::LimitedLifetime;
+ break;
+ default:
+ DC_ERROR(("Unknown channel type %d", req->channel_type));
+ /* XXX error handling */
+ return;
+ }
+ prValue = ntohl(req->reliability_param);
+ bool ordered = !(req->channel_type & 0x80);
+
+ if ((channel = FindChannelByStream(stream))) {
+ if (!channel->mNegotiated) {
+ DC_ERROR(
+ ("HandleOpenRequestMessage: channel for pre-existing stream "
+ "%u that was not externally negotiated. JS is lying to us, or "
+ "there's an id collision.",
+ stream));
+ /* XXX: some error handling */
+ } else {
+ DC_DEBUG(("Open for externally negotiated channel %u", stream));
+ // XXX should also check protocol, maybe label
+ if (prPolicy != channel->mPrPolicy || prValue != channel->mPrValue ||
+ ordered != channel->mOrdered) {
+ DC_WARN(
+ ("external negotiation mismatch with OpenRequest:"
+ "channel %u, policy %s/%s, value %u/%u, ordered %d/%d",
+ stream, ToString(prPolicy), ToString(channel->mPrPolicy), prValue,
+ channel->mPrValue, static_cast<int>(ordered),
+ static_cast<int>(channel->mOrdered)));
+ }
+ }
+ return;
+ }
+ if (stream >= mNegotiatedIdLimit) {
+ DC_ERROR(("%s: stream %u out of bounds (%zu)", __FUNCTION__, stream,
+ mNegotiatedIdLimit));
+ return;
+ }
+
+ nsCString label(
+ nsDependentCSubstring(&req->label[0], ntohs(req->label_length)));
+ nsCString protocol(nsDependentCSubstring(
+ &req->label[ntohs(req->label_length)], ntohs(req->protocol_length)));
+
+ channel =
+ new DataChannel(this, stream, DataChannelState::Open, label, protocol,
+ prPolicy, prValue, ordered, false, nullptr, nullptr);
+ mChannels.Insert(channel);
+
+ DC_DEBUG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u", __FUNCTION__,
+ channel->mLabel.get(), channel->mProtocol.get(), stream));
+ Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::EventType::OnChannelCreated, this,
+ channel)));
+
+ DC_DEBUG(("%s: deferring sending ON_CHANNEL_OPEN for %p", __FUNCTION__,
+ channel.get()));
+ channel->AnnounceOpen();
+
+ // Note that any message can be buffered; SendOpenAckMessage may
+ // error later than this check.
+ const auto error = SendOpenAckMessage(channel->mStream);
+ if (error) {
+ DC_ERROR(("SendOpenRequest failed, error = %d", error));
+ Dispatch(NS_NewRunnableFunction(
+ "DataChannelConnection::HandleOpenRequestMessage",
+ [channel, connection = RefPtr<DataChannelConnection>(this)]() {
+ // Close the channel on failure
+ connection->Close(channel);
+ }));
+ return;
+ }
+ DeliverQueuedData(channel->mStream);
+}
+
+// NOTE: the updated spec from the IETF says we should set in-order until we
+// receive an ACK. That would make this code moot. Keep it for now for
+// backwards compatibility.
+void DataChannelConnection::DeliverQueuedData(uint16_t stream) {
+ mLock.AssertCurrentThreadOwns();
+
+ mQueuedData.RemoveElementsBy([stream, this](const auto& dataItem) {
+ mLock.AssertCurrentThreadOwns();
+ const bool match = dataItem->mStream == stream;
+ if (match) {
+ DC_DEBUG(("Delivering queued data for stream %u, length %u", stream,
+ dataItem->mLength));
+ // Deliver the queued data
+ HandleDataMessage(dataItem->mData, dataItem->mLength, dataItem->mPpid,
+ dataItem->mStream, dataItem->mFlags);
+ }
+ return match;
+ });
+}
+
+// Caller must ensure that length <= SIZE_MAX
+void DataChannelConnection::HandleOpenAckMessage(
+ const struct rtcweb_datachannel_ack* ack, uint32_t length,
+ uint16_t stream) {
+ DataChannel* channel;
+
+ mLock.AssertCurrentThreadOwns();
+
+ channel = FindChannelByStream(stream);
+ if (NS_WARN_IF(!channel)) {
+ return;
+ }
+
+ DC_DEBUG(("OpenAck received for stream %u, waiting=%d", stream,
+ channel->mWaitingForAck ? 1 : 0));
+
+ channel->mWaitingForAck = false;
+}
+
+// Caller must ensure that length <= SIZE_MAX
+void DataChannelConnection::HandleUnknownMessage(uint32_t ppid, uint32_t length,
+ uint16_t stream) {
+ /* XXX: Send an error message? */
+ DC_ERROR(("unknown DataChannel message received: %u, len %u on stream %d",
+ ppid, length, stream));
+ // XXX Log to JS error console if possible
+}
+
+uint8_t DataChannelConnection::BufferMessage(nsACString& recvBuffer,
+ const void* data, uint32_t length,
+ uint32_t ppid, int flags) {
+ const char* buffer = (const char*)data;
+ uint8_t bufferFlags = 0;
+
+ if ((flags & MSG_EOR) && ppid != DATA_CHANNEL_PPID_BINARY_PARTIAL &&
+ ppid != DATA_CHANNEL_PPID_DOMSTRING_PARTIAL) {
+ bufferFlags |= DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_COMPLETE;
+
+ // Return directly if nothing has been buffered
+ if (recvBuffer.IsEmpty()) {
+ return bufferFlags;
+ }
+ }
+
+ // Ensure it doesn't blow up our buffer
+ // TODO: Change 'WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL' to whatever the
+ // new buffer is capable of holding.
+ if (((uint64_t)recvBuffer.Length()) + ((uint64_t)length) >
+ WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL) {
+ bufferFlags |= DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE;
+ return bufferFlags;
+ }
+
+ // Copy & add to receive buffer
+ recvBuffer.Append(buffer, length);
+ bufferFlags |= DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED;
+ return bufferFlags;
+}
+
+void DataChannelConnection::HandleDataMessage(const void* data, size_t length,
+ uint32_t ppid, uint16_t stream,
+ int flags) {
+ DataChannel* channel;
+ const char* buffer = (const char*)data;
+
+ mLock.AssertCurrentThreadOwns();
+ channel = FindChannelByStream(stream);
+
+ // Note: Until we support SIZE_MAX sized messages, we need this check
+#if (SIZE_MAX > UINT32_MAX)
+ if (length > UINT32_MAX) {
+ DC_ERROR(("DataChannel: Cannot handle message of size %zu (max=%" PRIu32
+ ")",
+ length, UINT32_MAX));
+ CloseLocked(channel);
+ return;
+ }
+#endif
+ uint32_t data_length = (uint32_t)length;
+
+ // XXX A closed channel may trip this... check
+ // NOTE: the updated spec from the IETF says we should set in-order until we
+ // receive an ACK. That would make this code moot. Keep it for now for
+ // backwards compatibility.
+ if (!channel) {
+ // In the updated 0-RTT open case, the sender can send data immediately
+ // after Open, and doesn't set the in-order bit (since we don't have a
+ // response or ack). Also, with external negotiation, data can come in
+ // before we're told about the external negotiation. We need to buffer
+ // data until either a) Open comes in, if the ordering get messed up,
+ // or b) the app tells us this channel was externally negotiated. When
+ // these occur, we deliver the data.
+
+ // Since this is rare and non-performance, keep a single list of queued
+ // data messages to deliver once the channel opens.
+ DC_DEBUG(("Queuing data for stream %u, length %u", stream, data_length));
+ // Copies data
+ mQueuedData.AppendElement(
+ new QueuedDataMessage(stream, ppid, flags, data, data_length));
+ return;
+ }
+
+ // RFC8832: "MUST be sent ordered, ... After the DATA_CHANNEL_ACK **or any
+ // other message** has been received on the data channel".
+ // If the channel was opened on this side, and a message is received, this
+ // indicates that the peer has already received the DATA_CHANNEL_ACK, as the
+ // channel is ordered initially.
+ channel->mWaitingForAck = false;
+
+ bool is_binary = true;
+ uint8_t bufferFlags;
+ DataChannelOnMessageAvailable::EventType type;
+ const char* info = "";
+
+ if (ppid == DATA_CHANNEL_PPID_DOMSTRING_PARTIAL ||
+ ppid == DATA_CHANNEL_PPID_DOMSTRING ||
+ ppid == DATA_CHANNEL_PPID_DOMSTRING_EMPTY) {
+ is_binary = false;
+ }
+ if (is_binary != channel->mIsRecvBinary && !channel->mRecvBuffer.IsEmpty()) {
+ NS_WARNING("DataChannel message aborted by fragment type change!");
+ // TODO: Maybe closing would be better as this is a hard to detect protocol
+ // violation?
+ channel->mRecvBuffer.Truncate(0);
+ }
+ channel->mIsRecvBinary = is_binary;
+
+ // Remaining chunks of previously truncated message (due to the buffer being
+ // full)?
+ if (channel->mClosingTooLarge) {
+ DC_ERROR(
+ ("DataChannel: Ignoring partial message of length %u, buffer full and "
+ "closing",
+ data_length));
+ // Only unblock if unordered
+ if (!channel->mOrdered && (flags & MSG_EOR)) {
+ channel->mClosingTooLarge = false;
+ }
+ }
+
+ // Buffer message until complete
+ bufferFlags =
+ BufferMessage(channel->mRecvBuffer, buffer, data_length, ppid, flags);
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE) {
+ DC_ERROR(
+ ("DataChannel: Buffered message would become too large to handle, "
+ "closing channel"));
+ channel->mRecvBuffer.Truncate(0);
+ channel->mClosingTooLarge = true;
+ CloseLocked(channel);
+ return;
+ }
+ if (!(bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_COMPLETE)) {
+ DC_DEBUG(
+ ("DataChannel: Partial %s message of length %u (total %zu) on channel "
+ "id %u",
+ is_binary ? "binary" : "string", data_length,
+ channel->mRecvBuffer.Length(), channel->mStream));
+ return; // Not ready to notify application
+ }
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ data_length = channel->mRecvBuffer.Length();
+ }
+
+ // Complain about large messages (only complain - we can handle it)
+ if (data_length > WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL) {
+ DC_WARN(
+ ("DataChannel: Received message of length %u is > announced maximum "
+ "message size (%u)",
+ data_length, WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL));
+ }
+
+ bool is_empty = false;
+
+ switch (ppid) {
+ case DATA_CHANNEL_PPID_DOMSTRING:
+ DC_DEBUG(
+ ("DataChannel: Received string message of length %u on channel %u",
+ data_length, channel->mStream));
+ type = DataChannelOnMessageAvailable::EventType::OnDataString;
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ info = " (string fragmented)";
+ }
+ // else send using recvData normally
+
+ // WebSockets checks IsUTF8() here; we can try to deliver it
+ break;
+
+ case DATA_CHANNEL_PPID_DOMSTRING_EMPTY:
+ DC_DEBUG(
+ ("DataChannel: Received empty string message of length %u on channel "
+ "%u",
+ data_length, channel->mStream));
+ type = DataChannelOnMessageAvailable::EventType::OnDataString;
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ info = " (string fragmented)";
+ }
+ is_empty = true;
+ break;
+
+ case DATA_CHANNEL_PPID_BINARY:
+ DC_DEBUG(
+ ("DataChannel: Received binary message of length %u on channel id %u",
+ data_length, channel->mStream));
+ type = DataChannelOnMessageAvailable::EventType::OnDataBinary;
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ info = " (binary fragmented)";
+ }
+
+ // else send using recvData normally
+ break;
+
+ case DATA_CHANNEL_PPID_BINARY_EMPTY:
+ DC_DEBUG(
+ ("DataChannel: Received empty binary message of length %u on channel "
+ "id %u",
+ data_length, channel->mStream));
+ type = DataChannelOnMessageAvailable::EventType::OnDataBinary;
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ info = " (binary fragmented)";
+ }
+ is_empty = true;
+ break;
+
+ default:
+ NS_ERROR("Unknown data PPID");
+ DC_ERROR(("Unknown data PPID %" PRIu32, ppid));
+ return;
+ }
+
+ channel->WithTrafficCounters(
+ [&data_length](DataChannel::TrafficCounters& counters) {
+ counters.mMessagesReceived++;
+ counters.mBytesReceived += data_length;
+ });
+
+ // Notify onmessage
+ DC_DEBUG(
+ ("%s: sending %s%s for %p", __FUNCTION__, ToString(type), info, channel));
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ channel->SendOrQueue(new DataChannelOnMessageAvailable(
+ type, this, channel, channel->mRecvBuffer));
+ channel->mRecvBuffer.Truncate(0);
+ } else {
+ nsAutoCString recvData(is_empty ? "" : buffer,
+ is_empty ? 0 : data_length); // allocates >64
+ channel->SendOrQueue(
+ new DataChannelOnMessageAvailable(type, this, channel, recvData));
+ }
+}
+
+void DataChannelConnection::HandleDCEPMessage(const void* buffer, size_t length,
+ uint32_t ppid, uint16_t stream,
+ int flags) {
+ const struct rtcweb_datachannel_open_request* req;
+ const struct rtcweb_datachannel_ack* ack;
+
+ // Note: Until we support SIZE_MAX sized messages, we need this check
+#if (SIZE_MAX > UINT32_MAX)
+ if (length > UINT32_MAX) {
+ DC_ERROR(("DataChannel: Cannot handle message of size %zu (max=%u)", length,
+ UINT32_MAX));
+ Stop();
+ return;
+ }
+#endif
+ uint32_t data_length = (uint32_t)length;
+
+ mLock.AssertCurrentThreadOwns();
+
+ // Buffer message until complete
+ const uint8_t bufferFlags =
+ BufferMessage(mRecvBuffer, buffer, data_length, ppid, flags);
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE) {
+ DC_ERROR(
+ ("DataChannel: Buffered message would become too large to handle, "
+ "closing connection"));
+ mRecvBuffer.Truncate(0);
+ Stop();
+ return;
+ }
+ if (!(bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_COMPLETE)) {
+ DC_DEBUG(("Buffered partial DCEP message of length %u", data_length));
+ return;
+ }
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ buffer = reinterpret_cast<const void*>(mRecvBuffer.BeginReading());
+ data_length = mRecvBuffer.Length();
+ }
+
+ req = static_cast<const struct rtcweb_datachannel_open_request*>(buffer);
+ DC_DEBUG(("Handling DCEP message of length %u", data_length));
+
+ // Ensure minimum message size (ack is the smallest DCEP message)
+ if ((size_t)data_length < sizeof(*ack)) {
+ DC_WARN(("Ignored invalid DCEP message (too short)"));
+ return;
+ }
+
+ switch (req->msg_type) {
+ case DATA_CHANNEL_OPEN_REQUEST:
+ // structure includes a possibly-unused char label[1] (in a packed
+ // structure)
+ if (NS_WARN_IF((size_t)data_length < sizeof(*req) - 1)) {
+ return;
+ }
+
+ HandleOpenRequestMessage(req, data_length, stream);
+ break;
+ case DATA_CHANNEL_ACK:
+ // >= sizeof(*ack) checked above
+
+ ack = static_cast<const struct rtcweb_datachannel_ack*>(buffer);
+ HandleOpenAckMessage(ack, data_length, stream);
+ break;
+ default:
+ HandleUnknownMessage(ppid, data_length, stream);
+ break;
+ }
+
+ // Reset buffer
+ mRecvBuffer.Truncate(0);
+}
+
+// Called with mLock locked!
+void DataChannelConnection::HandleMessage(const void* buffer, size_t length,
+ uint32_t ppid, uint16_t stream,
+ int flags) {
+ mLock.AssertCurrentThreadOwns();
+
+ switch (ppid) {
+ case DATA_CHANNEL_PPID_CONTROL:
+ HandleDCEPMessage(buffer, length, ppid, stream, flags);
+ break;
+ case DATA_CHANNEL_PPID_DOMSTRING_PARTIAL:
+ case DATA_CHANNEL_PPID_DOMSTRING:
+ case DATA_CHANNEL_PPID_DOMSTRING_EMPTY:
+ case DATA_CHANNEL_PPID_BINARY_PARTIAL:
+ case DATA_CHANNEL_PPID_BINARY:
+ case DATA_CHANNEL_PPID_BINARY_EMPTY:
+ HandleDataMessage(buffer, length, ppid, stream, flags);
+ break;
+ default:
+ DC_ERROR((
+ "Unhandled message of length %zu PPID %u on stream %u received (%s).",
+ length, ppid, stream, (flags & MSG_EOR) ? "complete" : "partial"));
+ break;
+ }
+}
+
+void DataChannelConnection::HandleAssociationChangeEvent(
+ const struct sctp_assoc_change* sac) {
+ mLock.AssertCurrentThreadOwns();
+
+ uint32_t i, n;
+ DataChannelConnectionState state = GetState();
+ switch (sac->sac_state) {
+ case SCTP_COMM_UP:
+ DC_DEBUG(("Association change: SCTP_COMM_UP"));
+ if (state == DataChannelConnectionState::Connecting) {
+ mSocket = mMasterSocket;
+ SetState(DataChannelConnectionState::Open);
+
+ DC_DEBUG(("Negotiated number of incoming streams: %" PRIu16,
+ sac->sac_inbound_streams));
+ DC_DEBUG(("Negotiated number of outgoing streams: %" PRIu16,
+ sac->sac_outbound_streams));
+ mNegotiatedIdLimit =
+ std::max(mNegotiatedIdLimit,
+ static_cast<size_t>(std::max(sac->sac_outbound_streams,
+ sac->sac_inbound_streams)));
+
+ Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::EventType::OnConnection, this)));
+ DC_DEBUG(("DTLS connect() succeeded! Entering connected mode"));
+
+ // Open any streams pending...
+ ProcessQueuedOpens();
+
+ } else if (state == DataChannelConnectionState::Open) {
+ DC_DEBUG(("DataConnection Already OPEN"));
+ } else {
+ DC_ERROR(("Unexpected state: %s", ToString(state)));
+ }
+ break;
+ case SCTP_COMM_LOST:
+ DC_DEBUG(("Association change: SCTP_COMM_LOST"));
+ // This association is toast, so also close all the channels -- from
+ // mainthread!
+ Stop();
+ break;
+ case SCTP_RESTART:
+ DC_DEBUG(("Association change: SCTP_RESTART"));
+ break;
+ case SCTP_SHUTDOWN_COMP:
+ DC_DEBUG(("Association change: SCTP_SHUTDOWN_COMP"));
+ Stop();
+ break;
+ case SCTP_CANT_STR_ASSOC:
+ DC_DEBUG(("Association change: SCTP_CANT_STR_ASSOC"));
+ break;
+ default:
+ DC_DEBUG(("Association change: UNKNOWN"));
+ break;
+ }
+ DC_DEBUG(("Association change: streams (in/out) = (%u/%u)",
+ sac->sac_inbound_streams, sac->sac_outbound_streams));
+
+ if (NS_WARN_IF(!sac)) {
+ return;
+ }
+
+ n = sac->sac_length - sizeof(*sac);
+ if ((sac->sac_state == SCTP_COMM_UP) || (sac->sac_state == SCTP_RESTART)) {
+ if (n > 0) {
+ for (i = 0; i < n; ++i) {
+ switch (sac->sac_info[i]) {
+ case SCTP_ASSOC_SUPPORTS_PR:
+ DC_DEBUG(("Supports: PR"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_AUTH:
+ DC_DEBUG(("Supports: AUTH"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_ASCONF:
+ DC_DEBUG(("Supports: ASCONF"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_MULTIBUF:
+ DC_DEBUG(("Supports: MULTIBUF"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_RE_CONFIG:
+ DC_DEBUG(("Supports: RE-CONFIG"));
+ break;
+#if defined(SCTP_ASSOC_SUPPORTS_INTERLEAVING)
+ case SCTP_ASSOC_SUPPORTS_INTERLEAVING:
+ DC_DEBUG(("Supports: NDATA"));
+ // TODO: This should probably be set earlier above in 'case
+ // SCTP_COMM_UP' but we also need this for 'SCTP_RESTART'.
+ mSendInterleaved = true;
+ break;
+#endif
+ default:
+ DC_ERROR(("Supports: UNKNOWN(0x%02x)", sac->sac_info[i]));
+ break;
+ }
+ }
+ }
+ } else if (((sac->sac_state == SCTP_COMM_LOST) ||
+ (sac->sac_state == SCTP_CANT_STR_ASSOC)) &&
+ (n > 0)) {
+ DC_DEBUG(("Association: ABORT ="));
+ for (i = 0; i < n; ++i) {
+ DC_DEBUG((" 0x%02x", sac->sac_info[i]));
+ }
+ }
+ if ((sac->sac_state == SCTP_CANT_STR_ASSOC) ||
+ (sac->sac_state == SCTP_SHUTDOWN_COMP) ||
+ (sac->sac_state == SCTP_COMM_LOST)) {
+ return;
+ }
+}
+
+void DataChannelConnection::HandlePeerAddressChangeEvent(
+ const struct sctp_paddr_change* spc) {
+ const char* addr = "";
+#if !defined(__Userspace_os_Windows)
+ char addr_buf[INET6_ADDRSTRLEN];
+ struct sockaddr_in* sin;
+ struct sockaddr_in6* sin6;
+#endif
+
+ switch (spc->spc_aaddr.ss_family) {
+ case AF_INET:
+#if !defined(__Userspace_os_Windows)
+ sin = (struct sockaddr_in*)&spc->spc_aaddr;
+ addr = inet_ntop(AF_INET, &sin->sin_addr, addr_buf, INET6_ADDRSTRLEN);
+#endif
+ break;
+ case AF_INET6:
+#if !defined(__Userspace_os_Windows)
+ sin6 = (struct sockaddr_in6*)&spc->spc_aaddr;
+ addr = inet_ntop(AF_INET6, &sin6->sin6_addr, addr_buf, INET6_ADDRSTRLEN);
+#endif
+ break;
+ case AF_CONN:
+ addr = "DTLS connection";
+ break;
+ default:
+ break;
+ }
+ DC_DEBUG(("Peer address %s is now ", addr));
+ switch (spc->spc_state) {
+ case SCTP_ADDR_AVAILABLE:
+ DC_DEBUG(("SCTP_ADDR_AVAILABLE"));
+ break;
+ case SCTP_ADDR_UNREACHABLE:
+ DC_DEBUG(("SCTP_ADDR_UNREACHABLE"));
+ break;
+ case SCTP_ADDR_REMOVED:
+ DC_DEBUG(("SCTP_ADDR_REMOVED"));
+ break;
+ case SCTP_ADDR_ADDED:
+ DC_DEBUG(("SCTP_ADDR_ADDED"));
+ break;
+ case SCTP_ADDR_MADE_PRIM:
+ DC_DEBUG(("SCTP_ADDR_MADE_PRIM"));
+ break;
+ case SCTP_ADDR_CONFIRMED:
+ DC_DEBUG(("SCTP_ADDR_CONFIRMED"));
+ break;
+ default:
+ DC_ERROR(("UNKNOWN SCP STATE"));
+ break;
+ }
+ if (spc->spc_error) {
+ DC_ERROR((" (error = 0x%08x).\n", spc->spc_error));
+ }
+}
+
+void DataChannelConnection::HandleRemoteErrorEvent(
+ const struct sctp_remote_error* sre) {
+ size_t i, n;
+
+ n = sre->sre_length - sizeof(struct sctp_remote_error);
+ DC_WARN(("Remote Error (error = 0x%04x): ", sre->sre_error));
+ for (i = 0; i < n; ++i) {
+ DC_WARN((" 0x%02x", sre->sre_data[i]));
+ }
+}
+
+void DataChannelConnection::HandleShutdownEvent(
+ const struct sctp_shutdown_event* sse) {
+ DC_DEBUG(("Shutdown event."));
+ /* XXX: notify all channels. */
+ // Attempts to actually send anything will fail
+}
+
+void DataChannelConnection::HandleAdaptationIndication(
+ const struct sctp_adaptation_event* sai) {
+ DC_DEBUG(("Adaptation indication: %x.", sai->sai_adaptation_ind));
+}
+
+void DataChannelConnection::HandlePartialDeliveryEvent(
+ const struct sctp_pdapi_event* spde) {
+ // Note: Be aware that stream and sequence number being u32 instead of u16 is
+ // a bug in the SCTP API. This may change in the future.
+
+ DC_DEBUG(("Partial delivery event: "));
+ switch (spde->pdapi_indication) {
+ case SCTP_PARTIAL_DELIVERY_ABORTED:
+ DC_DEBUG(("delivery aborted "));
+ break;
+ default:
+ DC_ERROR(("??? "));
+ break;
+ }
+ DC_DEBUG(("(flags = %x), stream = %" PRIu32 ", sn = %" PRIu32,
+ spde->pdapi_flags, spde->pdapi_stream, spde->pdapi_seq));
+
+ // Validate stream ID
+ if (spde->pdapi_stream >= UINT16_MAX) {
+ DC_ERROR(("Invalid stream id in partial delivery event: %" PRIu32 "\n",
+ spde->pdapi_stream));
+ return;
+ }
+
+ // Find channel and reset buffer
+ DataChannel* channel = FindChannelByStream((uint16_t)spde->pdapi_stream);
+ if (channel) {
+ DC_WARN(("Abort partially delivered message of %zu bytes\n",
+ channel->mRecvBuffer.Length()));
+ channel->mRecvBuffer.Truncate(0);
+ }
+}
+
+void DataChannelConnection::HandleSendFailedEvent(
+ const struct sctp_send_failed_event* ssfe) {
+ size_t i, n;
+
+ if (ssfe->ssfe_flags & SCTP_DATA_UNSENT) {
+ DC_DEBUG(("Unsent "));
+ }
+ if (ssfe->ssfe_flags & SCTP_DATA_SENT) {
+ DC_DEBUG(("Sent "));
+ }
+ if (ssfe->ssfe_flags & ~(SCTP_DATA_SENT | SCTP_DATA_UNSENT)) {
+ DC_DEBUG(("(flags = %x) ", ssfe->ssfe_flags));
+ }
+#ifdef XP_WIN
+# define PRIPPID "lu"
+#else
+# define PRIPPID "u"
+#endif
+ DC_DEBUG(("message with PPID = %" PRIPPID
+ ", SID = %d, flags: 0x%04x due to error = 0x%08x",
+ ntohl(ssfe->ssfe_info.snd_ppid), ssfe->ssfe_info.snd_sid,
+ ssfe->ssfe_info.snd_flags, ssfe->ssfe_error));
+#undef PRIPPID
+ n = ssfe->ssfe_length - sizeof(struct sctp_send_failed_event);
+ for (i = 0; i < n; ++i) {
+ DC_DEBUG((" 0x%02x", ssfe->ssfe_data[i]));
+ }
+}
+
+void DataChannelConnection::ClearResets() {
+ // Clear all pending resets
+ if (!mStreamsResetting.IsEmpty()) {
+ DC_DEBUG(("Clearing resets for %zu streams", mStreamsResetting.Length()));
+ }
+
+ for (uint32_t i = 0; i < mStreamsResetting.Length(); ++i) {
+ RefPtr<DataChannel> channel;
+ channel = FindChannelByStream(mStreamsResetting[i]);
+ if (channel) {
+ DC_DEBUG(("Forgetting channel %u (%p) with pending reset",
+ channel->mStream, channel.get()));
+ // TODO: Do we _really_ want to remove this? Are we allowed to reuse the
+ // id?
+ mChannels.Remove(channel);
+ }
+ }
+ mStreamsResetting.Clear();
+}
+
+void DataChannelConnection::ResetOutgoingStream(uint16_t stream) {
+ uint32_t i;
+
+ mLock.AssertCurrentThreadOwns();
+ DC_DEBUG(
+ ("Connection %p: Resetting outgoing stream %u", (void*)this, stream));
+ // Rarely has more than a couple items and only for a short time
+ for (i = 0; i < mStreamsResetting.Length(); ++i) {
+ if (mStreamsResetting[i] == stream) {
+ return;
+ }
+ }
+ mStreamsResetting.AppendElement(stream);
+}
+
+void DataChannelConnection::SendOutgoingStreamReset() {
+ struct sctp_reset_streams* srs;
+ uint32_t i;
+ size_t len;
+
+ DC_DEBUG(("Connection %p: Sending outgoing stream reset for %zu streams",
+ (void*)this, mStreamsResetting.Length()));
+ mLock.AssertCurrentThreadOwns();
+ if (mStreamsResetting.IsEmpty()) {
+ DC_DEBUG(("No streams to reset"));
+ return;
+ }
+ len = sizeof(sctp_assoc_t) +
+ (2 + mStreamsResetting.Length()) * sizeof(uint16_t);
+ srs = static_cast<struct sctp_reset_streams*>(
+ moz_xmalloc(len)); // infallible malloc
+ memset(srs, 0, len);
+ srs->srs_flags = SCTP_STREAM_RESET_OUTGOING;
+ srs->srs_number_streams = mStreamsResetting.Length();
+ for (i = 0; i < mStreamsResetting.Length(); ++i) {
+ srs->srs_stream_list[i] = mStreamsResetting[i];
+ }
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_RESET_STREAMS, srs,
+ (socklen_t)len) < 0) {
+ DC_ERROR(("***failed: setsockopt RESET, errno %d", errno));
+ // if errno == EALREADY, this is normal - we can't send another reset
+ // with one pending.
+ // When we get an incoming reset (which may be a response to our
+ // outstanding one), see if we have any pending outgoing resets and
+ // send them
+ } else {
+ mStreamsResetting.Clear();
+ }
+ free(srs);
+}
+
+void DataChannelConnection::HandleStreamResetEvent(
+ const struct sctp_stream_reset_event* strrst) {
+ uint32_t n, i;
+ RefPtr<DataChannel> channel; // since we may null out the ref to the channel
+
+ if (!(strrst->strreset_flags & SCTP_STREAM_RESET_DENIED) &&
+ !(strrst->strreset_flags & SCTP_STREAM_RESET_FAILED)) {
+ n = (strrst->strreset_length - sizeof(struct sctp_stream_reset_event)) /
+ sizeof(uint16_t);
+ for (i = 0; i < n; ++i) {
+ if (strrst->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) {
+ channel = FindChannelByStream(strrst->strreset_stream_list[i]);
+ if (channel) {
+ // The other side closed the channel
+ // We could be in three states:
+ // 1. Normal state (input and output streams (OPEN)
+ // Notify application, send a RESET in response on our
+ // outbound channel. Go to CLOSED
+ // 2. We sent our own reset (CLOSING); either they crossed on the
+ // wire, or this is a response to our Reset.
+ // Go to CLOSED
+ // 3. We've sent a open but haven't gotten a response yet (CONNECTING)
+ // I believe this is impossible, as we don't have an input stream
+ // yet.
+
+ DC_DEBUG(("Incoming: Channel %u closed", channel->mStream));
+ if (mChannels.Remove(channel)) {
+ // Mark the stream for reset (the reset is sent below)
+ ResetOutgoingStream(channel->mStream);
+ }
+
+ DC_DEBUG(("Disconnected DataChannel %p from connection %p",
+ (void*)channel.get(), (void*)channel->mConnection.get()));
+ channel->StreamClosedLocked();
+ } else {
+ DC_WARN(("Can't find incoming channel %d", i));
+ }
+ }
+ }
+ }
+
+ // Process any pending resets now:
+ if (!mStreamsResetting.IsEmpty()) {
+ DC_DEBUG(("Sending %zu pending resets", mStreamsResetting.Length()));
+ SendOutgoingStreamReset();
+ }
+}
+
+void DataChannelConnection::HandleStreamChangeEvent(
+ const struct sctp_stream_change_event* strchg) {
+ ASSERT_WEBRTC(!NS_IsMainThread());
+ if (strchg->strchange_flags == SCTP_STREAM_CHANGE_DENIED) {
+ DC_ERROR(("*** Failed increasing number of streams from %zu (%u/%u)",
+ mNegotiatedIdLimit, strchg->strchange_instrms,
+ strchg->strchange_outstrms));
+ // XXX FIX! notify pending opens of failure
+ return;
+ }
+ if (strchg->strchange_instrms > mNegotiatedIdLimit) {
+ DC_DEBUG(("Other side increased streams from %zu to %u", mNegotiatedIdLimit,
+ strchg->strchange_instrms));
+ }
+ uint16_t old_limit = mNegotiatedIdLimit;
+ uint16_t new_limit =
+ std::max(strchg->strchange_outstrms, strchg->strchange_instrms);
+ if (new_limit > mNegotiatedIdLimit) {
+ DC_DEBUG(("Increasing number of streams from %u to %u - adding %u (in: %u)",
+ old_limit, new_limit, new_limit - old_limit,
+ strchg->strchange_instrms));
+ // make sure both are the same length
+ mNegotiatedIdLimit = new_limit;
+ DC_DEBUG(("New length = %zu (was %d)", mNegotiatedIdLimit, old_limit));
+ // Re-process any channels waiting for streams.
+ // Linear search, but we don't increase channels often and
+ // the array would only get long in case of an app error normally
+
+ // Make sure we request enough streams if there's a big jump in streams
+ // Could make a more complex API for OpenXxxFinish() and avoid this loop
+ auto channels = mChannels.GetAll();
+ size_t num_needed =
+ channels.Length() ? (channels.LastElement()->mStream + 1) : 0;
+ MOZ_ASSERT(num_needed != INVALID_STREAM);
+ if (num_needed > new_limit) {
+ int32_t more_needed = num_needed - ((int32_t)mNegotiatedIdLimit) + 16;
+ DC_DEBUG(("Not enough new streams, asking for %d more", more_needed));
+ // TODO: parameter is an int32_t but we pass size_t
+ RequestMoreStreams(more_needed);
+ } else if (strchg->strchange_outstrms < strchg->strchange_instrms) {
+ DC_DEBUG(("Requesting %d output streams to match partner",
+ strchg->strchange_instrms - strchg->strchange_outstrms));
+ RequestMoreStreams(strchg->strchange_instrms -
+ strchg->strchange_outstrms);
+ }
+
+ ProcessQueuedOpens();
+ }
+ // else probably not a change in # of streams
+
+ if ((strchg->strchange_flags & SCTP_STREAM_CHANGE_DENIED) ||
+ (strchg->strchange_flags & SCTP_STREAM_CHANGE_FAILED)) {
+ // Other side denied our request. Need to AnnounceClosed some stuff.
+ for (auto& channel : mChannels.GetAll()) {
+ if (channel->mStream >= mNegotiatedIdLimit) {
+ /* XXX: Signal to the other end. */
+ channel->AnnounceClosed();
+ // maybe fire onError (bug 843625)
+ }
+ }
+ }
+}
+
+// Called with mLock locked!
+void DataChannelConnection::HandleNotification(
+ const union sctp_notification* notif, size_t n) {
+ mLock.AssertCurrentThreadOwns();
+ if (notif->sn_header.sn_length != (uint32_t)n) {
+ return;
+ }
+ switch (notif->sn_header.sn_type) {
+ case SCTP_ASSOC_CHANGE:
+ HandleAssociationChangeEvent(&(notif->sn_assoc_change));
+ break;
+ case SCTP_PEER_ADDR_CHANGE:
+ HandlePeerAddressChangeEvent(&(notif->sn_paddr_change));
+ break;
+ case SCTP_REMOTE_ERROR:
+ HandleRemoteErrorEvent(&(notif->sn_remote_error));
+ break;
+ case SCTP_SHUTDOWN_EVENT:
+ HandleShutdownEvent(&(notif->sn_shutdown_event));
+ break;
+ case SCTP_ADAPTATION_INDICATION:
+ HandleAdaptationIndication(&(notif->sn_adaptation_event));
+ break;
+ case SCTP_AUTHENTICATION_EVENT:
+ DC_DEBUG(("SCTP_AUTHENTICATION_EVENT"));
+ break;
+ case SCTP_SENDER_DRY_EVENT:
+ // DC_DEBUG(("SCTP_SENDER_DRY_EVENT"));
+ break;
+ case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+ DC_DEBUG(("SCTP_NOTIFICATIONS_STOPPED_EVENT"));
+ break;
+ case SCTP_PARTIAL_DELIVERY_EVENT:
+ HandlePartialDeliveryEvent(&(notif->sn_pdapi_event));
+ break;
+ case SCTP_SEND_FAILED_EVENT:
+ HandleSendFailedEvent(&(notif->sn_send_failed_event));
+ break;
+ case SCTP_STREAM_RESET_EVENT:
+ HandleStreamResetEvent(&(notif->sn_strreset_event));
+ break;
+ case SCTP_ASSOC_RESET_EVENT:
+ DC_DEBUG(("SCTP_ASSOC_RESET_EVENT"));
+ break;
+ case SCTP_STREAM_CHANGE_EVENT:
+ HandleStreamChangeEvent(&(notif->sn_strchange_event));
+ break;
+ default:
+ DC_ERROR(("unknown SCTP event: %u", (uint32_t)notif->sn_header.sn_type));
+ break;
+ }
+}
+
+int DataChannelConnection::ReceiveCallback(
+ struct socket* sock, void* data, size_t datalen, struct sctp_rcvinfo rcv,
+ int flags) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ ASSERT_WEBRTC(!NS_IsMainThread());
+ DC_DEBUG(("In ReceiveCallback"));
+
+ // libusrsctp just went reentrant on us. Put a stop to this.
+ mSTS->Dispatch(NS_NewRunnableFunction(
+ "DataChannelConnection::ReceiveCallback",
+ [data, datalen, rcv, flags, this,
+ self = RefPtr<DataChannelConnection>(this)]() mutable {
+ if (!data) {
+ DC_DEBUG(("ReceiveCallback: SCTP has finished shutting down"));
+ } else {
+ mLock.Lock();
+ if (flags & MSG_NOTIFICATION) {
+ HandleNotification(static_cast<union sctp_notification*>(data),
+ datalen);
+ } else {
+ HandleMessage(data, datalen, ntohl(rcv.rcv_ppid), rcv.rcv_sid,
+ flags);
+ }
+ mLock.Unlock();
+ // sctp allocates 'data' with malloc(), and expects the receiver to
+ // free it (presumably with free).
+ // XXX future optimization: try to deliver messages without an
+ // internal alloc/copy, and if so delay the free until later.
+ free(data);
+ }
+ }));
+
+ // usrsctp defines the callback as returning an int, but doesn't use it
+ return 1;
+}
+
+already_AddRefed<DataChannel> DataChannelConnection::Open(
+ const nsACString& label, const nsACString& protocol,
+ DataChannelReliabilityPolicy prPolicy, bool inOrder, uint32_t prValue,
+ DataChannelListener* aListener, nsISupports* aContext,
+ bool aExternalNegotiated, uint16_t aStream) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ MutexAutoLock lock(mLock); // OpenFinish assumes this
+ if (!aExternalNegotiated) {
+ if (mAllocateEven.isSome()) {
+ aStream = FindFreeStream();
+ if (aStream == INVALID_STREAM) {
+ return nullptr;
+ }
+ } else {
+ // We do not yet know whether we are client or server, and an id has not
+ // been chosen for us. We will need to choose later.
+ aStream = INVALID_STREAM;
+ }
+ }
+
+ DC_DEBUG(
+ ("DC Open: label %s/%s, type %s, inorder %d, prValue %u, listener %p, "
+ "context %p, external: %s, stream %u",
+ PromiseFlatCString(label).get(), PromiseFlatCString(protocol).get(),
+ ToString(prPolicy), inOrder, prValue, aListener, aContext,
+ aExternalNegotiated ? "true" : "false", aStream));
+
+ if ((prPolicy == DataChannelReliabilityPolicy::Reliable) && (prValue != 0)) {
+ return nullptr;
+ }
+
+ if (aStream != INVALID_STREAM && mChannels.Get(aStream)) {
+ DC_ERROR(("external negotiation of already-open channel %u", aStream));
+ return nullptr;
+ }
+
+ RefPtr<DataChannel> channel(new DataChannel(
+ this, aStream, DataChannelState::Connecting, label, protocol, prPolicy,
+ prValue, inOrder, aExternalNegotiated, aListener, aContext));
+ mChannels.Insert(channel);
+
+ return OpenFinish(channel.forget());
+}
+
+// Separate routine so we can also call it to finish up from pending opens
+already_AddRefed<DataChannel> DataChannelConnection::OpenFinish(
+ already_AddRefed<DataChannel>&& aChannel) {
+ RefPtr<DataChannel> channel(aChannel); // takes the reference passed in
+ // Normally 1 reference if called from ::Open(), or 2 if called from
+ // ProcessQueuedOpens() unless the DOMDataChannel was gc'd
+ const uint16_t stream = channel->mStream;
+
+ mLock.AssertCurrentThreadOwns();
+
+ // Cases we care about:
+ // Pre-negotiated:
+ // Not Open:
+ // Doesn't fit:
+ // -> change initial ask or renegotiate after open
+ // -> queue open
+ // Open:
+ // Doesn't fit:
+ // -> RequestMoreStreams && queue
+ // Does fit:
+ // -> open
+ // Not negotiated:
+ // Not Open:
+ // -> queue open
+ // Open:
+ // -> Try to get a stream
+ // Doesn't fit:
+ // -> RequestMoreStreams && queue
+ // Does fit:
+ // -> open
+ // So the Open cases are basically the same
+ // Not Open cases are simply queue for non-negotiated, and
+ // either change the initial ask or possibly renegotiate after open.
+ DataChannelConnectionState state = GetState();
+ if (state != DataChannelConnectionState::Open ||
+ stream >= mNegotiatedIdLimit) {
+ if (state == DataChannelConnectionState::Open) {
+ MOZ_ASSERT(stream != INVALID_STREAM);
+ // RequestMoreStreams() limits to MAX_NUM_STREAMS -- allocate extra
+ // streams to avoid going back immediately for more if the ask to N, N+1,
+ // etc
+ int32_t more_needed = stream - ((int32_t)mNegotiatedIdLimit) + 16;
+ if (!RequestMoreStreams(more_needed)) {
+ // Something bad happened... we're done
+ goto request_error_cleanup;
+ }
+ }
+ DC_DEBUG(("Queuing channel %p (%u) to finish open", channel.get(), stream));
+ // Also serves to mark we told the app
+ channel->mHasFinishedOpen = true;
+ mPending.Push(channel);
+ return channel.forget();
+ }
+
+ MOZ_ASSERT(stream != INVALID_STREAM);
+ MOZ_ASSERT(stream < mNegotiatedIdLimit);
+
+#ifdef TEST_QUEUED_DATA
+ // It's painful to write a test for this...
+ channel->AnnounceOpen();
+ SendDataMsgInternalOrBuffer(channel, "Help me!", 8,
+ DATA_CHANNEL_PPID_DOMSTRING);
+#endif
+
+ if (!channel->mNegotiated) {
+ if (!channel->mOrdered) {
+ // Don't send unordered until this gets cleared.
+ channel->mWaitingForAck = true;
+ }
+
+ int error = SendOpenRequestMessage(channel->mLabel, channel->mProtocol,
+ stream, !channel->mOrdered,
+ channel->mPrPolicy, channel->mPrValue);
+ if (error) {
+ DC_ERROR(("SendOpenRequest failed, error = %d", error));
+ if (channel->mHasFinishedOpen) {
+ // We already returned the channel to the app.
+ NS_ERROR("Failed to send open request");
+ channel->AnnounceClosed();
+ }
+ // If we haven't returned the channel yet, it will get destroyed when we
+ // exit this function.
+ mChannels.Remove(channel);
+ // we'll be destroying the channel
+ return nullptr;
+ /* NOTREACHED */
+ }
+ }
+
+ // Either externally negotiated or we sent Open
+ // FIX? Move into DOMDataChannel? I don't think we can send it yet here
+ channel->AnnounceOpen();
+
+ return channel.forget();
+
+request_error_cleanup:
+ if (channel->mHasFinishedOpen) {
+ // We already returned the channel to the app.
+ NS_ERROR("Failed to request more streams");
+ channel->AnnounceClosed();
+ return channel.forget();
+ }
+ // we'll be destroying the channel, but it never really got set up
+ // Alternative would be to RUN_ON_THREAD(channel.forget(),::Destroy,...) and
+ // Dispatch it to ourselves
+ return nullptr;
+}
+
+// Requires mLock to be locked!
+// Returns a POSIX error code directly instead of setting errno.
+int DataChannelConnection::SendMsgInternal(OutgoingMsg& msg, size_t* aWritten) {
+ struct sctp_sndinfo& info = msg.GetInfo().sendv_sndinfo;
+ int error;
+
+ // EOR set?
+ bool eor_set = (info.snd_flags & SCTP_EOR) != 0;
+
+ // Send until buffer is empty
+ size_t left = msg.GetLeft();
+ do {
+ size_t length;
+
+ // Carefully chunk the buffer
+ if (left > DATA_CHANNEL_MAX_BINARY_FRAGMENT) {
+ length = DATA_CHANNEL_MAX_BINARY_FRAGMENT;
+
+ // Unset EOR flag
+ info.snd_flags &= ~SCTP_EOR;
+ } else {
+ length = left;
+
+ // Set EOR flag
+ if (eor_set) {
+ info.snd_flags |= SCTP_EOR;
+ }
+ }
+
+ // Send (or try at least)
+ // SCTP will return EMSGSIZE if the message is bigger than the buffer
+ // size (or EAGAIN if there isn't space). However, we can avoid EMSGSIZE
+ // by carefully crafting small enough message chunks.
+ ssize_t written = usrsctp_sendv(
+ mSocket, msg.GetData(), length, nullptr, 0, (void*)&msg.GetInfo(),
+ (socklen_t)sizeof(struct sctp_sendv_spa), SCTP_SENDV_SPA, 0);
+
+ if (written < 0) {
+ error = errno;
+ goto out;
+ }
+
+ if (aWritten) {
+ *aWritten += written;
+ }
+ DC_DEBUG(("Sent buffer (written=%zu, len=%zu, left=%zu)", (size_t)written,
+ length, left - (size_t)written));
+
+ // TODO: Remove once resolved
+ // (https://github.com/sctplab/usrsctp/issues/132)
+ if (written == 0) {
+ DC_ERROR(("@tuexen: usrsctp_sendv returned 0"));
+ error = EAGAIN;
+ goto out;
+ }
+
+ // If not all bytes have been written, this obviously means that usrsctp's
+ // buffer is full and we need to try again later.
+ if ((size_t)written < length) {
+ msg.Advance((size_t)written);
+ error = EAGAIN;
+ goto out;
+ }
+
+ // Update buffer position
+ msg.Advance((size_t)written);
+
+ // Get amount of bytes left in the buffer
+ left = msg.GetLeft();
+ } while (left > 0);
+
+ // Done
+ error = 0;
+
+out:
+ // Reset EOR flag
+ if (eor_set) {
+ info.snd_flags |= SCTP_EOR;
+ }
+
+ return error;
+}
+
+// Requires mLock to be locked!
+// Returns a POSIX error code directly instead of setting errno.
+// IMPORTANT: Ensure that the buffer passed is guarded by mLock!
+int DataChannelConnection::SendMsgInternalOrBuffer(
+ nsTArray<UniquePtr<BufferedOutgoingMsg>>& buffer, OutgoingMsg& msg,
+ bool& buffered, size_t* aWritten) {
+ NS_WARNING_ASSERTION(msg.GetLength() > 0, "Length is 0?!");
+
+ int error = 0;
+ bool need_buffering = false;
+
+ // Note: Main-thread IO, but doesn't block!
+ // XXX FIX! to deal with heavy overruns of JS trying to pass data in
+ // (more than the buffersize) queue data onto another thread to do the
+ // actual sends. See netwerk/protocol/websocket/WebSocketChannel.cpp
+
+ // Avoid a race between buffer-full-failure (where we have to add the
+ // packet to the buffered-data queue) and the buffer-now-only-half-full
+ // callback, which happens on a different thread. Otherwise we might
+ // fail here, then before we add it to the queue get the half-full
+ // callback, find nothing to do, then on this thread add it to the
+ // queue - which would sit there. Also, if we later send more data, it
+ // would arrive ahead of the buffered message, but if the buffer ever
+ // got to 1/2 full, the message would get sent - but at a semi-random
+ // time, after other data it was supposed to be in front of.
+
+ // Must lock before empty check for similar reasons!
+ mLock.AssertCurrentThreadOwns();
+ if (buffer.IsEmpty() &&
+ (mSendInterleaved || mPendingType == PendingType::None)) {
+ error = SendMsgInternal(msg, aWritten);
+ switch (error) {
+ case 0:
+ break;
+ case EAGAIN:
+#if (EAGAIN != EWOULDBLOCK)
+ case EWOULDBLOCK:
+#endif
+ need_buffering = true;
+ break;
+ default:
+ DC_ERROR(("error %d on sending", error));
+ break;
+ }
+ } else {
+ need_buffering = true;
+ }
+
+ if (need_buffering) {
+ // queue data for resend! And queue any further data for the stream until
+ // it is...
+ auto* bufferedMsg = new BufferedOutgoingMsg(msg); // infallible malloc
+ buffer.AppendElement(bufferedMsg); // owned by mBufferedData array
+ DC_DEBUG(("Queued %zu buffers (left=%zu, total=%zu)", buffer.Length(),
+ msg.GetLeft(), msg.GetLength()));
+ buffered = true;
+ return 0;
+ }
+
+ buffered = false;
+ return error;
+}
+
+// Caller must ensure that length <= UINT32_MAX
+// Returns a POSIX error code.
+int DataChannelConnection::SendDataMsgInternalOrBuffer(DataChannel& channel,
+ const uint8_t* data,
+ size_t len,
+ uint32_t ppid) {
+ if (NS_WARN_IF(channel.GetReadyState() != DataChannelState::Open)) {
+ return EINVAL; // TODO: Find a better error code
+ }
+
+ struct sctp_sendv_spa info = {};
+
+ // General flags
+ info.sendv_flags = SCTP_SEND_SNDINFO_VALID;
+
+ // Set stream identifier, protocol identifier and flags
+ info.sendv_sndinfo.snd_sid = channel.mStream;
+ info.sendv_sndinfo.snd_flags = SCTP_EOR;
+ info.sendv_sndinfo.snd_ppid = htonl(ppid);
+
+ // Unordered?
+ // To avoid problems where an in-order OPEN is lost and an
+ // out-of-order data message "beats" it, require data to be in-order
+ // until we get an ACK.
+ if (!channel.mOrdered && !channel.mWaitingForAck) {
+ info.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;
+ }
+
+ // Partial reliability policy
+ if (channel.mPrPolicy != DataChannelReliabilityPolicy::Reliable) {
+ info.sendv_prinfo.pr_policy = ToUsrsctpValue(channel.mPrPolicy);
+ info.sendv_prinfo.pr_value = channel.mPrValue;
+ info.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+ }
+
+ // Create message instance and send
+ OutgoingMsg msg(info, data, len);
+ bool buffered;
+ size_t written = 0;
+ mDeferSend = true;
+ int error =
+ SendMsgInternalOrBuffer(channel.mBufferedData, msg, buffered, &written);
+ mDeferSend = false;
+ if (written && ppid != DATA_CHANNEL_PPID_DOMSTRING_EMPTY &&
+ ppid != DATA_CHANNEL_PPID_BINARY_EMPTY) {
+ channel.DecrementBufferedAmount(written);
+ }
+
+ for (auto&& packet : mDeferredSend) {
+ MOZ_ASSERT(written);
+ SendPacket(std::move(packet));
+ }
+ mDeferredSend.clear();
+
+ // Set pending type and stream index (if buffered)
+ if (!error && buffered && mPendingType == PendingType::None) {
+ mPendingType = PendingType::Data;
+ mCurrentStream = channel.mStream;
+ }
+ return error;
+}
+
+// Caller must ensure that length <= UINT32_MAX
+// Returns a POSIX error code.
+int DataChannelConnection::SendDataMsg(DataChannel& channel,
+ const uint8_t* data, size_t len,
+ uint32_t ppidPartial,
+ uint32_t ppidFinal) {
+ // We *really* don't want to do this from main thread! - and
+ // SendDataMsgInternalOrBuffer avoids blocking.
+ mLock.AssertCurrentThreadOwns();
+
+ if (mMaxMessageSize != 0 && len > mMaxMessageSize) {
+ DC_ERROR(("Message rejected, too large (%zu > %" PRIu64 ")", len,
+ mMaxMessageSize));
+ return EMSGSIZE;
+ }
+
+ // This will use EOR-based fragmentation if the message is too large (> 64
+ // KiB)
+ return SendDataMsgInternalOrBuffer(channel, data, len, ppidFinal);
+}
+
+class ReadBlobRunnable : public Runnable {
+ public:
+ ReadBlobRunnable(DataChannelConnection* aConnection, uint16_t aStream,
+ nsIInputStream* aBlob)
+ : Runnable("ReadBlobRunnable"),
+ mConnection(aConnection),
+ mStream(aStream),
+ mBlob(aBlob) {}
+
+ NS_IMETHOD Run() override {
+ // ReadBlob() is responsible to releasing the reference
+ DataChannelConnection* self = mConnection;
+ self->ReadBlob(mConnection.forget(), mStream, mBlob);
+ return NS_OK;
+ }
+
+ private:
+ // Make sure the Connection doesn't die while there are jobs outstanding.
+ // Let it die (if released by PeerConnectionImpl while we're running)
+ // when we send our runnable back to MainThread. Then ~DataChannelConnection
+ // can send the IOThread to MainThread to die in a runnable, avoiding
+ // unsafe event loop recursion. Evil.
+ RefPtr<DataChannelConnection> mConnection;
+ uint16_t mStream;
+ // Use RefCount for preventing the object is deleted when SendBlob returns.
+ RefPtr<nsIInputStream> mBlob;
+};
+
+// Returns a POSIX error code.
+int DataChannelConnection::SendBlob(uint16_t stream, nsIInputStream* aBlob) {
+ MutexAutoLock lock(mLock);
+ RefPtr<DataChannel> channel = mChannels.Get(stream);
+ if (NS_WARN_IF(!channel)) {
+ return EINVAL; // TODO: Find a better error code
+ }
+
+ // Spawn a thread to send the data
+ if (!mInternalIOThread) {
+ nsresult rv =
+ NS_NewNamedThread("DataChannel IO", getter_AddRefs(mInternalIOThread));
+ if (NS_FAILED(rv)) {
+ return EINVAL; // TODO: Find a better error code
+ }
+ }
+
+ mInternalIOThread->Dispatch(
+ do_AddRef(new ReadBlobRunnable(this, stream, aBlob)), NS_DISPATCH_NORMAL);
+ return 0;
+}
+
+class DataChannelBlobSendRunnable : public Runnable {
+ public:
+ DataChannelBlobSendRunnable(
+ already_AddRefed<DataChannelConnection>& aConnection, uint16_t aStream)
+ : Runnable("DataChannelBlobSendRunnable"),
+ mConnection(aConnection),
+ mStream(aStream) {}
+
+ ~DataChannelBlobSendRunnable() override {
+ if (!NS_IsMainThread() && mConnection) {
+ MOZ_ASSERT(false);
+ // explicitly leak the connection if destroyed off mainthread
+ Unused << mConnection.forget().take();
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ ASSERT_WEBRTC(NS_IsMainThread());
+
+ mConnection->SendBinaryMsg(mStream, mData);
+ mConnection = nullptr;
+ return NS_OK;
+ }
+
+ // explicitly public so we can avoid allocating twice and copying
+ nsCString mData;
+
+ private:
+ // Note: we can be destroyed off the target thread, so be careful not to let
+ // this get Released()ed on the temp thread!
+ RefPtr<DataChannelConnection> mConnection;
+ uint16_t mStream;
+};
+
+void DataChannelConnection::SetState(DataChannelConnectionState aState) {
+ mLock.AssertCurrentThreadOwns();
+
+ DC_DEBUG(
+ ("DataChannelConnection labeled %s (%p) switching connection state %s -> "
+ "%s",
+ mTransportId.c_str(), this, ToString(mState), ToString(aState)));
+
+ mState = aState;
+}
+
+void DataChannelConnection::ReadBlob(
+ already_AddRefed<DataChannelConnection> aThis, uint16_t aStream,
+ nsIInputStream* aBlob) {
+ // NOTE: 'aThis' has been forgotten by the caller to avoid releasing
+ // it off mainthread; if PeerConnectionImpl has released then we want
+ // ~DataChannelConnection() to run on MainThread
+
+ // XXX to do this safely, we must enqueue these atomically onto the
+ // output socket. We need a sender thread(s?) to enqueue data into the
+ // socket and to avoid main-thread IO that might block. Even on a
+ // background thread, we may not want to block on one stream's data.
+ // I.e. run non-blocking and service multiple channels.
+
+ // Must not let Dispatching it cause the DataChannelConnection to get
+ // released on the wrong thread. Using
+ // WrapRunnable(RefPtr<DataChannelConnection>(aThis),... will occasionally
+ // cause aThis to get released on this thread. Also, an explicit Runnable
+ // lets us avoid copying the blob data an extra time.
+ RefPtr<DataChannelBlobSendRunnable> runnable =
+ new DataChannelBlobSendRunnable(aThis, aStream);
+ // avoid copying the blob data by passing the mData from the runnable
+ if (NS_FAILED(NS_ReadInputStreamToString(aBlob, runnable->mData, -1))) {
+ // Bug 966602: Doesn't return an error to the caller via onerror.
+ // We must release DataChannelConnection on MainThread to avoid issues (bug
+ // 876167) aThis is now owned by the runnable; release it there
+ NS_ReleaseOnMainThread("DataChannelBlobSendRunnable", runnable.forget());
+ return;
+ }
+ aBlob->Close();
+ Dispatch(runnable.forget());
+}
+
+// Returns a POSIX error code.
+int DataChannelConnection::SendDataMsgCommon(uint16_t stream,
+ const nsACString& aMsg,
+ bool isBinary) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ // We really could allow this from other threads, so long as we deal with
+ // asynchronosity issues with channels closing, in particular access to
+ // mChannels, and issues with the association closing (access to mSocket).
+
+ const uint8_t* data = (const uint8_t*)aMsg.BeginReading();
+ uint32_t len = aMsg.Length();
+#if (UINT32_MAX > SIZE_MAX)
+ if (len > SIZE_MAX) {
+ return EMSGSIZE;
+ }
+#endif
+
+ DC_DEBUG(("Sending %sto stream %u: %u bytes", isBinary ? "binary " : "",
+ stream, len));
+ // XXX if we want more efficiency, translate flags once at open time
+ RefPtr<DataChannel> channelPtr = mChannels.Get(stream);
+ if (NS_WARN_IF(!channelPtr)) {
+ return EINVAL; // TODO: Find a better error code
+ }
+ bool is_empty = len == 0;
+ const uint8_t byte = 0;
+ if (is_empty) {
+ data = &byte;
+ len = 1;
+ }
+ auto& channel = *channelPtr;
+ int err = 0;
+ MutexAutoLock lock(mLock);
+ if (isBinary) {
+ err = SendDataMsg(
+ channel, data, len, DATA_CHANNEL_PPID_BINARY_PARTIAL,
+ is_empty ? DATA_CHANNEL_PPID_BINARY_EMPTY : DATA_CHANNEL_PPID_BINARY);
+ } else {
+ err = SendDataMsg(channel, data, len, DATA_CHANNEL_PPID_DOMSTRING_PARTIAL,
+ is_empty ? DATA_CHANNEL_PPID_DOMSTRING_EMPTY
+ : DATA_CHANNEL_PPID_DOMSTRING);
+ }
+ if (!err) {
+ channel.WithTrafficCounters([&len](DataChannel::TrafficCounters& counters) {
+ counters.mMessagesSent++;
+ counters.mBytesSent += len;
+ });
+ }
+
+ return err;
+}
+
+void DataChannelConnection::Stop() {
+ // Note: This will call 'CloseAll' from the main thread
+ Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::EventType::OnDisconnected, this)));
+}
+
+void DataChannelConnection::Close(DataChannel* aChannel) {
+ MutexAutoLock lock(mLock);
+ CloseLocked(aChannel);
+}
+
+// So we can call Close() with the lock already held
+// Called from someone who holds a ref via ::Close(), or from ~DataChannel
+void DataChannelConnection::CloseLocked(DataChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ RefPtr<DataChannel> channel(aChannel); // make sure it doesn't go away on us
+
+ mLock.AssertCurrentThreadOwns();
+ DC_DEBUG(("Connection %p/Channel %p: Closing stream %u",
+ channel->mConnection.get(), channel.get(), channel->mStream));
+
+ aChannel->mBufferedData.Clear();
+ if (GetState() == DataChannelConnectionState::Closed) {
+ // If we're CLOSING, we might leave this in place until we can send a
+ // reset.
+ mChannels.Remove(channel);
+ }
+
+ // This is supposed to only be accessed from Main thread, but this has
+ // been accessed here from the STS thread for a long time now.
+ // See Bug 1586475
+ DataChannelState channelState = aChannel->GetReadyState();
+ // re-test since it may have closed before the lock was grabbed
+ if (channelState == DataChannelState::Closed ||
+ channelState == DataChannelState::Closing) {
+ DC_DEBUG(("Channel already closing/closed (%s)", ToString(channelState)));
+ return;
+ }
+
+ if (channel->mStream != INVALID_STREAM) {
+ ResetOutgoingStream(channel->mStream);
+ if (GetState() != DataChannelConnectionState::Closed) {
+ // Individual channel is being closed, send reset now.
+ SendOutgoingStreamReset();
+ }
+ }
+ aChannel->SetReadyState(DataChannelState::Closing);
+ if (GetState() == DataChannelConnectionState::Closed) {
+ // we're not going to hang around waiting
+ channel->StreamClosedLocked();
+ }
+ // At this point when we leave here, the object is a zombie held alive only by
+ // the DOM object
+}
+
+void DataChannelConnection::CloseAll() {
+ DC_DEBUG(("Closing all channels (connection %p)", (void*)this));
+
+ // Make sure no more channels will be opened
+ MutexAutoLock lock(mLock);
+ SetState(DataChannelConnectionState::Closed);
+
+ // Close current channels
+ // If there are runnables, they hold a strong ref and keep the channel
+ // and/or connection alive (even if in a CLOSED state)
+ for (auto& channel : mChannels.GetAll()) {
+ MutexAutoUnlock lock(mLock);
+ channel->Close();
+ }
+
+ // Clean up any pending opens for channels
+ RefPtr<DataChannel> channel;
+ while (nullptr != (channel = mPending.PopFront())) {
+ DC_DEBUG(("closing pending channel %p, stream %u", channel.get(),
+ channel->mStream));
+ MutexAutoUnlock lock(mLock);
+ channel->Close(); // also releases the ref on each iteration
+ }
+ // It's more efficient to let the Resets queue in shutdown and then
+ // SendOutgoingStreamReset() here.
+ SendOutgoingStreamReset();
+}
+
+bool DataChannelConnection::Channels::IdComparator::Equals(
+ const RefPtr<DataChannel>& aChannel, uint16_t aId) const {
+ return aChannel->mStream == aId;
+}
+
+bool DataChannelConnection::Channels::IdComparator::LessThan(
+ const RefPtr<DataChannel>& aChannel, uint16_t aId) const {
+ return aChannel->mStream < aId;
+}
+
+bool DataChannelConnection::Channels::IdComparator::Equals(
+ const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const {
+ return Equals(a1, a2->mStream);
+}
+
+bool DataChannelConnection::Channels::IdComparator::LessThan(
+ const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const {
+ return LessThan(a1, a2->mStream);
+}
+
+void DataChannelConnection::Channels::Insert(
+ const RefPtr<DataChannel>& aChannel) {
+ DC_DEBUG(("Inserting channel %u : %p", aChannel->mStream, aChannel.get()));
+ MutexAutoLock lock(mMutex);
+ if (aChannel->mStream != INVALID_STREAM) {
+ MOZ_ASSERT(!mChannels.ContainsSorted(aChannel, IdComparator()));
+ }
+
+ MOZ_ASSERT(!mChannels.Contains(aChannel));
+
+ mChannels.InsertElementSorted(aChannel, IdComparator());
+}
+
+bool DataChannelConnection::Channels::Remove(
+ const RefPtr<DataChannel>& aChannel) {
+ DC_DEBUG(("Removing channel %u : %p", aChannel->mStream, aChannel.get()));
+ MutexAutoLock lock(mMutex);
+ if (aChannel->mStream == INVALID_STREAM) {
+ return mChannels.RemoveElement(aChannel);
+ }
+
+ return mChannels.RemoveElementSorted(aChannel, IdComparator());
+}
+
+RefPtr<DataChannel> DataChannelConnection::Channels::Get(uint16_t aId) const {
+ MutexAutoLock lock(mMutex);
+ auto index = mChannels.BinaryIndexOf(aId, IdComparator());
+ if (index == ChannelArray::NoIndex) {
+ return nullptr;
+ }
+ return mChannels[index];
+}
+
+RefPtr<DataChannel> DataChannelConnection::Channels::GetNextChannel(
+ uint16_t aCurrentId) const {
+ MutexAutoLock lock(mMutex);
+ if (mChannels.IsEmpty()) {
+ return nullptr;
+ }
+
+ auto index = mChannels.IndexOfFirstElementGt(aCurrentId, IdComparator());
+ if (index == mChannels.Length()) {
+ index = 0;
+ }
+ return mChannels[index];
+}
+
+DataChannel::~DataChannel() {
+ // NS_ASSERTION since this is more "I think I caught all the cases that
+ // can cause this" than a true kill-the-program assertion. If this is
+ // wrong, nothing bad happens. A worst it's a leak.
+ NS_ASSERTION(mReadyState == DataChannelState::Closed ||
+ mReadyState == DataChannelState::Closing,
+ "unexpected state in ~DataChannel");
+}
+
+void DataChannel::Close() {
+ if (mConnection) {
+ // ensure we don't get deleted
+ RefPtr<DataChannelConnection> connection(mConnection);
+ connection->Close(this);
+ }
+}
+
+// Used when disconnecting from the DataChannelConnection
+void DataChannel::StreamClosedLocked() {
+ MOZ_ASSERT(mConnection);
+ if (!mConnection) {
+ return;
+ }
+ mConnection->mLock.AssertCurrentThreadOwns();
+
+ DC_DEBUG(("Destroying Data channel %u", mStream));
+ MOZ_ASSERT_IF(mStream != INVALID_STREAM,
+ !mConnection->FindChannelByStream(mStream));
+ AnnounceClosed();
+ // We leave mConnection live until the DOM releases us, to avoid races
+}
+
+void DataChannel::ReleaseConnection() {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ mConnection = nullptr;
+}
+
+void DataChannel::SetListener(DataChannelListener* aListener,
+ nsISupports* aContext) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ mContext = aContext;
+ mListener = aListener;
+}
+
+void DataChannel::SendErrnoToErrorResult(int error, size_t aMessageSize,
+ ErrorResult& aRv) {
+ switch (error) {
+ case 0:
+ break;
+ case EMSGSIZE: {
+ nsPrintfCString err("Message size (%zu) exceeds maxMessageSize",
+ aMessageSize);
+ aRv.ThrowTypeError(err);
+ break;
+ }
+ default:
+ aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
+ break;
+ }
+}
+
+void DataChannel::IncrementBufferedAmount(uint32_t aSize, ErrorResult& aRv) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ if (mBufferedAmount > UINT32_MAX - aSize) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ mBufferedAmount += aSize;
+}
+
+void DataChannel::DecrementBufferedAmount(uint32_t aSize) {
+ mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
+ "DataChannel::DecrementBufferedAmount",
+ [this, self = RefPtr<DataChannel>(this), aSize] {
+ MOZ_ASSERT(aSize <= mBufferedAmount);
+ bool wasLow = mBufferedAmount <= mBufferedThreshold;
+ mBufferedAmount -= aSize;
+ if (!wasLow && mBufferedAmount <= mBufferedThreshold) {
+ DC_DEBUG(("%s: sending BUFFER_LOW_THRESHOLD for %s/%s: %u",
+ __FUNCTION__, mLabel.get(), mProtocol.get(), mStream));
+ mListener->OnBufferLow(mContext);
+ }
+ if (mBufferedAmount == 0) {
+ DC_DEBUG(("%s: sending NO_LONGER_BUFFERED for %s/%s: %u",
+ __FUNCTION__, mLabel.get(), mProtocol.get(), mStream));
+ mListener->NotBuffered(mContext);
+ }
+ }));
+}
+
+void DataChannel::AnnounceOpen() {
+ mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
+ "DataChannel::AnnounceOpen", [this, self = RefPtr<DataChannel>(this)] {
+ DataChannelState state = GetReadyState();
+ // Special-case; spec says to put brand-new remote-created DataChannel
+ // in "open", but queue the firing of the "open" event.
+ if (state != DataChannelState::Closing &&
+ state != DataChannelState::Closed) {
+ if (!mEverOpened && mConnection && mConnection->mListener) {
+ mEverOpened = true;
+ mConnection->mListener->NotifyDataChannelOpen(this);
+ }
+ SetReadyState(DataChannelState::Open);
+ DC_DEBUG(("%s: sending ON_CHANNEL_OPEN for %s/%s: %u", __FUNCTION__,
+ mLabel.get(), mProtocol.get(), mStream));
+ if (mListener) {
+ mListener->OnChannelConnected(mContext);
+ }
+ }
+ }));
+}
+
+void DataChannel::AnnounceClosed() {
+ mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
+ "DataChannel::AnnounceClosed", [this, self = RefPtr<DataChannel>(this)] {
+ if (GetReadyState() == DataChannelState::Closed) {
+ return;
+ }
+ if (mEverOpened && mConnection && mConnection->mListener) {
+ mConnection->mListener->NotifyDataChannelClosed(this);
+ }
+ SetReadyState(DataChannelState::Closed);
+ mBufferedData.Clear();
+ if (mListener) {
+ DC_DEBUG(("%s: sending ON_CHANNEL_CLOSED for %s/%s: %u", __FUNCTION__,
+ mLabel.get(), mProtocol.get(), mStream));
+ mListener->OnChannelClosed(mContext);
+ }
+ }));
+}
+
+// Set ready state
+void DataChannel::SetReadyState(const DataChannelState aState) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DC_DEBUG(
+ ("DataChannelConnection labeled %s(%p) (stream %d) changing ready state "
+ "%s -> %s",
+ mLabel.get(), this, mStream, ToString(mReadyState), ToString(aState)));
+
+ mReadyState = aState;
+}
+
+void DataChannel::SendMsg(const nsACString& aMsg, ErrorResult& aRv) {
+ if (!EnsureValidStream(aRv)) {
+ return;
+ }
+
+ SendErrnoToErrorResult(mConnection->SendMsg(mStream, aMsg), aMsg.Length(),
+ aRv);
+ if (!aRv.Failed()) {
+ IncrementBufferedAmount(aMsg.Length(), aRv);
+ }
+}
+
+void DataChannel::SendBinaryMsg(const nsACString& aMsg, ErrorResult& aRv) {
+ if (!EnsureValidStream(aRv)) {
+ return;
+ }
+
+ SendErrnoToErrorResult(mConnection->SendBinaryMsg(mStream, aMsg),
+ aMsg.Length(), aRv);
+ if (!aRv.Failed()) {
+ IncrementBufferedAmount(aMsg.Length(), aRv);
+ }
+}
+
+void DataChannel::SendBinaryBlob(dom::Blob& aBlob, ErrorResult& aRv) {
+ if (!EnsureValidStream(aRv)) {
+ return;
+ }
+
+ uint64_t msgLength = aBlob.GetSize(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (msgLength > UINT32_MAX) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ // We convert to an nsIInputStream here, because Blob is not threadsafe, and
+ // we don't convert it earlier because we need to know how large this is so we
+ // can update bufferedAmount.
+ nsCOMPtr<nsIInputStream> msgStream;
+ aBlob.CreateInputStream(getter_AddRefs(msgStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendErrnoToErrorResult(mConnection->SendBlob(mStream, msgStream), msgLength,
+ aRv);
+ if (!aRv.Failed()) {
+ IncrementBufferedAmount(msgLength, aRv);
+ }
+}
+
+dom::Nullable<uint16_t> DataChannel::GetMaxPacketLifeTime() const {
+ if (mPrPolicy == DataChannelReliabilityPolicy::LimitedLifetime) {
+ return dom::Nullable<uint16_t>(mPrValue);
+ }
+ return dom::Nullable<uint16_t>();
+}
+
+dom::Nullable<uint16_t> DataChannel::GetMaxRetransmits() const {
+ if (mPrPolicy == DataChannelReliabilityPolicy::LimitedRetransmissions) {
+ return dom::Nullable<uint16_t>(mPrValue);
+ }
+ return dom::Nullable<uint16_t>();
+}
+
+uint32_t DataChannel::GetBufferedAmountLowThreshold() const {
+ return mBufferedThreshold;
+}
+
+// Never fire immediately, as it's defined to fire on transitions, not state
+void DataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold) {
+ mBufferedThreshold = aThreshold;
+}
+
+// Called with mLock locked!
+void DataChannel::SendOrQueue(DataChannelOnMessageAvailable* aMessage) {
+ nsCOMPtr<nsIRunnable> runnable = aMessage;
+ mMainThreadEventTarget->Dispatch(runnable.forget());
+}
+
+DataChannel::TrafficCounters DataChannel::GetTrafficCounters() const {
+ MutexAutoLock lock(mStatsLock);
+ return mTrafficCounters;
+}
+
+bool DataChannel::EnsureValidStream(ErrorResult& aRv) {
+ MOZ_ASSERT(mConnection);
+ if (mConnection && mStream != INVALID_STREAM) {
+ return true;
+ }
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+}
+
+void DataChannel::WithTrafficCounters(
+ const std::function<void(TrafficCounters&)>& aFn) {
+ MutexAutoLock lock(mStatsLock);
+ aFn(mTrafficCounters);
+}
+
+nsresult DataChannelOnMessageAvailable::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Note: calling the listeners can indirectly cause the listeners to be
+ // made available for GC (by removing event listeners), especially for
+ // OnChannelClosed(). We hold a ref to the Channel and the listener
+ // while calling this.
+ switch (mType) {
+ case EventType::OnDataString:
+ case EventType::OnDataBinary:
+ if (!mChannel->mListener) {
+ DC_ERROR(("DataChannelOnMessageAvailable (%s) with null Listener!",
+ ToString(mType)));
+ return NS_OK;
+ }
+
+ if (mChannel->GetReadyState() == DataChannelState::Closed ||
+ mChannel->GetReadyState() == DataChannelState::Closing) {
+ // Closed by JS, probably
+ return NS_OK;
+ }
+
+ if (mType == EventType::OnDataString) {
+ mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData);
+ } else {
+ mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext,
+ mData);
+ }
+ break;
+ case EventType::OnDisconnected:
+ // If we've disconnected, make sure we close all the streams - from
+ // mainthread!
+ if (mConnection->mListener) {
+ mConnection->mListener->NotifySctpClosed();
+ }
+ mConnection->CloseAll();
+ break;
+ case EventType::OnChannelCreated:
+ if (!mConnection->mListener) {
+ DC_ERROR(("DataChannelOnMessageAvailable (%s) with null Listener!",
+ ToString(mType)));
+ return NS_OK;
+ }
+
+ // important to give it an already_AddRefed pointer!
+ mConnection->mListener->NotifyDataChannel(mChannel.forget());
+ break;
+ case EventType::OnConnection:
+ if (mConnection->mListener) {
+ mConnection->mListener->NotifySctpConnected();
+ }
+ break;
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/netwerk/sctp/datachannel/DataChannel.h b/netwerk/sctp/datachannel/DataChannel.h
new file mode 100644
index 0000000000..76fb6aeced
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannel.h
@@ -0,0 +1,670 @@
+/* -*- 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 NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_
+#define NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_
+
+#ifdef MOZ_WEBRTC_SIGNALING
+# define SCTP_DTLS_SUPPORTED 1
+#endif
+
+#include <memory>
+#include <string>
+#include <vector>
+#include <errno.h>
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsTArray.h"
+#include "nsDeque.h"
+#include "mozilla/dom/Blob.h"
+#include "mozilla/Mutex.h"
+#include "DataChannelProtocol.h"
+#include "DataChannelListener.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "DataChannelLog.h"
+
+#ifdef SCTP_DTLS_SUPPORTED
+# include "transport/sigslot.h"
+# include "transport/transportlayer.h" // For TransportLayer::State
+#endif
+
+#ifndef EALREADY
+# define EALREADY WSAEALREADY
+#endif
+
+extern "C" {
+struct socket;
+struct sctp_rcvinfo;
+}
+
+namespace mozilla {
+
+class DataChannelConnection;
+class DataChannel;
+class DataChannelOnMessageAvailable;
+class MediaPacket;
+class MediaTransportHandler;
+namespace dom {
+struct RTCStatsCollection;
+};
+
+enum class DataChannelState { Connecting, Open, Closing, Closed };
+enum class DataChannelConnectionState { Connecting, Open, Closed };
+enum class DataChannelReliabilityPolicy {
+ Reliable,
+ LimitedRetransmissions,
+ LimitedLifetime
+};
+
+// For sending outgoing messages.
+// This class only holds a reference to the data and the info structure but does
+// not copy it.
+class OutgoingMsg {
+ public:
+ OutgoingMsg(struct sctp_sendv_spa& info, const uint8_t* data, size_t length);
+ OutgoingMsg(OutgoingMsg&& other) = default;
+ OutgoingMsg& operator=(OutgoingMsg&& other) = default;
+ ~OutgoingMsg() = default;
+
+ void Advance(size_t offset);
+ struct sctp_sendv_spa& GetInfo() const { return *mInfo; };
+ size_t GetLength() const { return mLength; };
+ size_t GetLeft() const { return mLength - mPos; };
+ const uint8_t* GetData() const { return (const uint8_t*)(mData + mPos); };
+
+ protected:
+ OutgoingMsg() // Use this for inheritance only
+ : mLength(0), mData(nullptr), mInfo(nullptr), mPos(0){};
+ size_t mLength;
+ const uint8_t* mData;
+ struct sctp_sendv_spa* mInfo;
+ size_t mPos;
+};
+
+// For queuing outgoing messages
+// This class copies data of an outgoing message.
+class BufferedOutgoingMsg : public OutgoingMsg {
+ public:
+ explicit BufferedOutgoingMsg(OutgoingMsg& msg);
+ BufferedOutgoingMsg(BufferedOutgoingMsg&& other) = default;
+ BufferedOutgoingMsg& operator=(BufferedOutgoingMsg&& other) = default;
+ ~BufferedOutgoingMsg();
+};
+
+// for queuing incoming data messages before the Open or
+// external negotiation is indicated to us
+class QueuedDataMessage {
+ public:
+ QueuedDataMessage(uint16_t stream, uint32_t ppid, int flags, const void* data,
+ uint32_t length)
+ : mStream(stream), mPpid(ppid), mFlags(flags), mLength(length) {
+ mData = static_cast<uint8_t*>(moz_xmalloc((size_t)length)); // infallible
+ memcpy(mData, data, (size_t)length);
+ }
+ QueuedDataMessage(QueuedDataMessage&& other) = default;
+ QueuedDataMessage& operator=(QueuedDataMessage&& other) = default;
+ ~QueuedDataMessage() { free(mData); }
+
+ uint16_t mStream;
+ uint32_t mPpid;
+ int mFlags;
+ uint32_t mLength;
+ uint8_t* mData;
+};
+
+// One per PeerConnection
+class DataChannelConnection final : public net::NeckoTargetHolder
+#ifdef SCTP_DTLS_SUPPORTED
+ ,
+ public sigslot::has_slots<>
+#endif
+{
+ friend class DataChannel;
+ friend class DataChannelOnMessageAvailable;
+ friend class DataChannelConnectRunnable;
+
+ virtual ~DataChannelConnection();
+
+ public:
+ enum class PendingType {
+ None, // No outgoing messages are pending.
+ Dcep, // Outgoing DCEP messages are pending.
+ Data, // Outgoing data channel messages are pending.
+ };
+
+ class DataConnectionListener : public SupportsWeakPtr {
+ public:
+ virtual ~DataConnectionListener() = default;
+
+ // Called when a new DataChannel has been opened by the other side.
+ virtual void NotifyDataChannel(already_AddRefed<DataChannel> channel) = 0;
+
+ // Called when a DataChannel transitions to state open
+ virtual void NotifyDataChannelOpen(DataChannel* aChannel) = 0;
+
+ // Called when a DataChannel (that was open at some point in the past)
+ // transitions to state closed
+ virtual void NotifyDataChannelClosed(DataChannel* aChannel) = 0;
+
+ // Called when SCTP connects
+ virtual void NotifySctpConnected() = 0;
+
+ // Called when SCTP closes
+ virtual void NotifySctpClosed() = 0;
+ };
+
+ // Create a new DataChannel Connection
+ // Must be called on Main thread
+ static Maybe<RefPtr<DataChannelConnection>> Create(
+ DataConnectionListener* aListener, nsISerialEventTarget* aTarget,
+ MediaTransportHandler* aHandler, const uint16_t aLocalPort,
+ const uint16_t aNumStreams, const Maybe<uint64_t>& aMaxMessageSize);
+
+ DataChannelConnection(const DataChannelConnection&) = delete;
+ DataChannelConnection& operator=(const DataChannelConnection&) = delete;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannelConnection)
+
+ void Destroy(); // So we can spawn refs tied to runnables in shutdown
+ // Finish Destroy on STS to avoid SCTP race condition with ABORT from far end
+ void DestroyOnSTS(struct socket* aMasterSocket, struct socket* aSocket);
+ void DestroyOnSTSFinal();
+
+ void SetMaxMessageSize(bool aMaxMessageSizeSet, uint64_t aMaxMessageSize);
+ uint64_t GetMaxMessageSize();
+
+ void AppendStatsToReport(const UniquePtr<dom::RTCStatsCollection>& aReport,
+ const DOMHighResTimeStamp aTimestamp) const;
+#ifdef ALLOW_DIRECT_SCTP_LISTEN_CONNECT
+ // These block; they require something to decide on listener/connector
+ // (though you can do simultaneous Connect()). Do not call these from
+ // the main thread!
+ bool Listen(unsigned short port);
+ bool Connect(const char* addr, unsigned short port);
+#endif
+
+#ifdef SCTP_DTLS_SUPPORTED
+ bool ConnectToTransport(const std::string& aTransportId, const bool aClient,
+ const uint16_t aLocalPort,
+ const uint16_t aRemotePort);
+ void TransportStateChange(const std::string& aTransportId,
+ TransportLayer::State aState);
+ void CompleteConnect();
+ void SetSignals(const std::string& aTransportId);
+#endif
+
+ [[nodiscard]] already_AddRefed<DataChannel> Open(
+ const nsACString& label, const nsACString& protocol,
+ DataChannelReliabilityPolicy prPolicy, bool inOrder, uint32_t prValue,
+ DataChannelListener* aListener, nsISupports* aContext,
+ bool aExternalNegotiated, uint16_t aStream);
+
+ void Stop();
+ void Close(DataChannel* aChannel);
+ void CloseLocked(DataChannel* aChannel) MOZ_REQUIRES(mLock);
+ void CloseAll();
+
+ // Returns a POSIX error code.
+ int SendMsg(uint16_t stream, const nsACString& aMsg) {
+ return SendDataMsgCommon(stream, aMsg, false);
+ }
+
+ // Returns a POSIX error code.
+ int SendBinaryMsg(uint16_t stream, const nsACString& aMsg) {
+ return SendDataMsgCommon(stream, aMsg, true);
+ }
+
+ // Returns a POSIX error code.
+ int SendBlob(uint16_t stream, nsIInputStream* aBlob);
+
+ // Called on data reception from the SCTP library
+ // must(?) be public so my c->c++ trampoline can call it
+ // May be called with (STS thread) or without the lock
+ int ReceiveCallback(struct socket* sock, void* data, size_t datalen,
+ struct sctp_rcvinfo rcv, int flags);
+
+ void ReadBlob(already_AddRefed<DataChannelConnection> aThis, uint16_t aStream,
+ nsIInputStream* aBlob);
+
+ bool SendDeferredMessages() MOZ_REQUIRES(mLock);
+
+#ifdef SCTP_DTLS_SUPPORTED
+ int SctpDtlsOutput(void* addr, void* buffer, size_t length, uint8_t tos,
+ uint8_t set_df);
+#endif
+
+ bool InShutdown() const {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ return mShutdown;
+#else
+ return false;
+#endif
+ }
+
+ private:
+ class Channels {
+ public:
+ using ChannelArray = AutoTArray<RefPtr<DataChannel>, 16>;
+
+ Channels() : mMutex("DataChannelConnection::Channels::mMutex") {}
+ Channels(const Channels&) = delete;
+ Channels& operator=(const Channels&) = delete;
+
+ void Insert(const RefPtr<DataChannel>& aChannel);
+ bool Remove(const RefPtr<DataChannel>& aChannel);
+ RefPtr<DataChannel> Get(uint16_t aId) const;
+ ChannelArray GetAll() const {
+ MutexAutoLock lock(mMutex);
+ return mChannels.Clone();
+ }
+ RefPtr<DataChannel> GetNextChannel(uint16_t aCurrentId) const;
+
+ private:
+ struct IdComparator {
+ bool Equals(const RefPtr<DataChannel>& aChannel, uint16_t aId) const;
+ bool LessThan(const RefPtr<DataChannel>& aChannel, uint16_t aId) const;
+ bool Equals(const RefPtr<DataChannel>& a1,
+ const RefPtr<DataChannel>& a2) const;
+ bool LessThan(const RefPtr<DataChannel>& a1,
+ const RefPtr<DataChannel>& a2) const;
+ };
+ mutable Mutex mMutex;
+ ChannelArray mChannels MOZ_GUARDED_BY(mMutex);
+ };
+
+ DataChannelConnection(DataConnectionListener* aListener,
+ nsISerialEventTarget* aTarget,
+ MediaTransportHandler* aHandler);
+
+ bool Init(const uint16_t aLocalPort, const uint16_t aNumStreams,
+ const Maybe<uint64_t>& aMaxMessageSize);
+
+ DataChannelConnectionState GetState() const MOZ_REQUIRES(mLock) {
+ mLock.AssertCurrentThreadOwns();
+
+ return mState;
+ }
+
+ void SetState(DataChannelConnectionState aState) MOZ_REQUIRES(mLock);
+ static int OnThresholdEvent(struct socket* sock, uint32_t sb_free,
+ void* ulp_info);
+
+#ifdef SCTP_DTLS_SUPPORTED
+ static void DTLSConnectThread(void* data);
+ void SendPacket(std::unique_ptr<MediaPacket>&& packet);
+ void SctpDtlsInput(const std::string& aTransportId,
+ const MediaPacket& packet);
+#endif
+ DataChannel* FindChannelByStream(uint16_t stream) MOZ_REQUIRES(mLock);
+ uint16_t FindFreeStream() const MOZ_REQUIRES(mLock);
+ bool RequestMoreStreams(int32_t aNeeded = 16) MOZ_REQUIRES(mLock);
+ uint32_t UpdateCurrentStreamIndex() MOZ_REQUIRES(mLock);
+ uint32_t GetCurrentStreamIndex() MOZ_REQUIRES(mLock);
+ int SendControlMessage(const uint8_t* data, uint32_t len, uint16_t stream)
+ MOZ_REQUIRES(mLock);
+ int SendOpenAckMessage(uint16_t stream) MOZ_REQUIRES(mLock);
+ int SendOpenRequestMessage(const nsACString& label,
+ const nsACString& protocol, uint16_t stream,
+ bool unordered,
+ DataChannelReliabilityPolicy prPolicy,
+ uint32_t prValue) MOZ_REQUIRES(mLock);
+ bool SendBufferedMessages(nsTArray<UniquePtr<BufferedOutgoingMsg>>& buffer,
+ size_t* aWritten);
+ int SendMsgInternal(OutgoingMsg& msg, size_t* aWritten);
+ int SendMsgInternalOrBuffer(nsTArray<UniquePtr<BufferedOutgoingMsg>>& buffer,
+ OutgoingMsg& msg, bool& buffered,
+ size_t* aWritten) MOZ_REQUIRES(mLock);
+ int SendDataMsgInternalOrBuffer(DataChannel& channel, const uint8_t* data,
+ size_t len, uint32_t ppid)
+ MOZ_REQUIRES(mLock);
+ int SendDataMsg(DataChannel& channel, const uint8_t* data, size_t len,
+ uint32_t ppidPartial, uint32_t ppidFinal) MOZ_REQUIRES(mLock);
+ int SendDataMsgCommon(uint16_t stream, const nsACString& aMsg, bool isBinary);
+
+ void DeliverQueuedData(uint16_t stream) MOZ_REQUIRES(mLock);
+
+ already_AddRefed<DataChannel> OpenFinish(
+ already_AddRefed<DataChannel>&& aChannel) MOZ_REQUIRES(mLock);
+
+ void ProcessQueuedOpens() MOZ_REQUIRES(mLock);
+ void ClearResets() MOZ_REQUIRES(mLock);
+ void SendOutgoingStreamReset() MOZ_REQUIRES(mLock);
+ void ResetOutgoingStream(uint16_t stream) MOZ_REQUIRES(mLock);
+ void HandleOpenRequestMessage(
+ const struct rtcweb_datachannel_open_request* req, uint32_t length,
+ uint16_t stream) MOZ_REQUIRES(mLock);
+ void HandleOpenAckMessage(const struct rtcweb_datachannel_ack* ack,
+ uint32_t length, uint16_t stream);
+ void HandleUnknownMessage(uint32_t ppid, uint32_t length, uint16_t stream)
+ MOZ_REQUIRES(mLock);
+ uint8_t BufferMessage(nsACString& recvBuffer, const void* data,
+ uint32_t length, uint32_t ppid, int flags);
+ void HandleDataMessage(const void* data, size_t length, uint32_t ppid,
+ uint16_t stream, int flags) MOZ_REQUIRES(mLock);
+ void HandleDCEPMessage(const void* buffer, size_t length, uint32_t ppid,
+ uint16_t stream, int flags) MOZ_REQUIRES(mLock);
+ void HandleMessage(const void* buffer, size_t length, uint32_t ppid,
+ uint16_t stream, int flags) MOZ_REQUIRES(mLock);
+ void HandleAssociationChangeEvent(const struct sctp_assoc_change* sac)
+ MOZ_REQUIRES(mLock);
+ void HandlePeerAddressChangeEvent(const struct sctp_paddr_change* spc)
+ MOZ_REQUIRES(mLock);
+ void HandleRemoteErrorEvent(const struct sctp_remote_error* sre)
+ MOZ_REQUIRES(mLock);
+ void HandleShutdownEvent(const struct sctp_shutdown_event* sse)
+ MOZ_REQUIRES(mLock);
+ void HandleAdaptationIndication(const struct sctp_adaptation_event* sai)
+ MOZ_REQUIRES(mLock);
+ void HandlePartialDeliveryEvent(const struct sctp_pdapi_event* spde)
+ MOZ_REQUIRES(mLock);
+ void HandleSendFailedEvent(const struct sctp_send_failed_event* ssfe)
+ MOZ_REQUIRES(mLock);
+ void HandleStreamResetEvent(const struct sctp_stream_reset_event* strrst)
+ MOZ_REQUIRES(mLock);
+ void HandleStreamChangeEvent(const struct sctp_stream_change_event* strchg)
+ MOZ_REQUIRES(mLock);
+ void HandleNotification(const union sctp_notification* notif, size_t n)
+ MOZ_REQUIRES(mLock);
+
+#ifdef SCTP_DTLS_SUPPORTED
+ bool IsSTSThread() const {
+ bool on = false;
+ if (mSTS) {
+ mSTS->IsOnCurrentThread(&on);
+ }
+ return on;
+ }
+#endif
+
+ mutable Mutex mLock;
+ // Avoid cycles with PeerConnectionImpl
+ // Use from main thread only as WeakPtr is not threadsafe
+ WeakPtr<DataConnectionListener> mListener;
+ bool mSendInterleaved MOZ_GUARDED_BY(mLock) = false;
+ // MainThread only
+ bool mMaxMessageSizeSet = false;
+ // mMaxMessageSize is only set on MainThread, but read off-main-thread
+ uint64_t mMaxMessageSize MOZ_GUARDED_BY(mLock) = 0;
+ // Main thread only
+ Maybe<bool> mAllocateEven;
+ // Data:
+ // NOTE: while this container will auto-expand, increases in the number of
+ // channels available from the stack must be negotiated!
+ // Accessed from both main and sts, API is threadsafe
+ Channels mChannels;
+ // STS only
+ uint32_t mCurrentStream = 0;
+ nsRefPtrDeque<DataChannel> mPending;
+ // STS and main
+ size_t mNegotiatedIdLimit MOZ_GUARDED_BY(mLock) = 0;
+ PendingType mPendingType MOZ_GUARDED_BY(mLock) = PendingType::None;
+ // holds data that's come in before a channel is open
+ nsTArray<UniquePtr<QueuedDataMessage>> mQueuedData MOZ_GUARDED_BY(mLock);
+ // holds outgoing control messages
+ nsTArray<UniquePtr<BufferedOutgoingMsg>> mBufferedControl
+ MOZ_GUARDED_BY(mLock);
+
+ // Streams pending reset. Accessed from main and STS.
+ AutoTArray<uint16_t, 4> mStreamsResetting MOZ_GUARDED_BY(mLock);
+ // accessed from STS thread
+ struct socket* mMasterSocket = nullptr;
+ // cloned from mMasterSocket on successful Connect on STS thread
+ struct socket* mSocket = nullptr;
+ DataChannelConnectionState mState MOZ_GUARDED_BY(mLock) =
+ DataChannelConnectionState::Closed;
+
+#ifdef SCTP_DTLS_SUPPORTED
+ std::string mTransportId;
+ bool mConnectedToTransportHandler = false;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+ nsCOMPtr<nsIEventTarget> mSTS;
+#endif
+ uint16_t mLocalPort = 0; // Accessed from connect thread
+ uint16_t mRemotePort = 0;
+
+ nsCOMPtr<nsIThread> mInternalIOThread = nullptr;
+ nsCString mRecvBuffer;
+
+ // Workaround to prevent a message from being received on main before the
+ // sender sees the decrease in bufferedAmount.
+ bool mDeferSend = false;
+ std::vector<std::unique_ptr<MediaPacket>> mDeferredSend;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool mShutdown;
+#endif
+ uintptr_t mId = 0;
+};
+
+class DataChannel {
+ friend class DataChannelOnMessageAvailable;
+ friend class DataChannelConnection;
+
+ public:
+ struct TrafficCounters {
+ uint32_t mMessagesSent = 0;
+ uint64_t mBytesSent = 0;
+ uint32_t mMessagesReceived = 0;
+ uint64_t mBytesReceived = 0;
+ };
+
+ DataChannel(DataChannelConnection* connection, uint16_t stream,
+ DataChannelState state, const nsACString& label,
+ const nsACString& protocol, DataChannelReliabilityPolicy policy,
+ uint32_t value, bool ordered, bool negotiated,
+ DataChannelListener* aListener, nsISupports* aContext)
+ : mListener(aListener),
+ mContext(aContext),
+ mConnection(connection),
+ mLabel(label),
+ mProtocol(protocol),
+ mReadyState(state),
+ mStream(stream),
+ mPrPolicy(policy),
+ mPrValue(value),
+ mNegotiated(negotiated),
+ mOrdered(ordered),
+ mIsRecvBinary(false),
+ mBufferedThreshold(0), // default from spec
+ mBufferedAmount(0),
+ mMainThreadEventTarget(connection->GetNeckoTarget()),
+ mStatsLock("netwer::sctp::DataChannel::mStatsLock") {
+ NS_ASSERTION(mConnection, "NULL connection");
+ }
+ DataChannel(const DataChannel&) = delete;
+ DataChannel& operator=(const DataChannel&) = delete;
+
+ private:
+ ~DataChannel();
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannel)
+
+ // when we disconnect from the connection after stream RESET
+ void StreamClosedLocked();
+
+ // Complete dropping of the link between DataChannel and the connection.
+ // After this, except for a few methods below listed to be safe, you can't
+ // call into DataChannel.
+ void ReleaseConnection();
+
+ // Close this DataChannel. Can be called multiple times. MUST be called
+ // before destroying the DataChannel (state must be CLOSED or CLOSING).
+ void Close();
+
+ // Set the listener (especially for channels created from the other side)
+ void SetListener(DataChannelListener* aListener, nsISupports* aContext);
+
+ // Helper for send methods that converts POSIX error codes to an ErrorResult.
+ static void SendErrnoToErrorResult(int error, size_t aMessageSize,
+ ErrorResult& aRv);
+
+ // Send a string
+ void SendMsg(const nsACString& aMsg, ErrorResult& aRv);
+
+ // Send a binary message (TypedArray)
+ void SendBinaryMsg(const nsACString& aMsg, ErrorResult& aRv);
+
+ // Send a binary blob
+ void SendBinaryBlob(dom::Blob& aBlob, ErrorResult& aRv);
+
+ DataChannelReliabilityPolicy GetType() const { return mPrPolicy; }
+
+ dom::Nullable<uint16_t> GetMaxPacketLifeTime() const;
+
+ dom::Nullable<uint16_t> GetMaxRetransmits() const;
+
+ bool GetNegotiated() const { return mNegotiated; }
+
+ bool GetOrdered() const { return mOrdered; }
+
+ void IncrementBufferedAmount(uint32_t aSize, ErrorResult& aRv);
+ void DecrementBufferedAmount(uint32_t aSize);
+
+ // Amount of data buffered to send
+ uint32_t GetBufferedAmount() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mBufferedAmount;
+ }
+
+ // Trigger amount for generating BufferedAmountLow events
+ uint32_t GetBufferedAmountLowThreshold() const;
+ void SetBufferedAmountLowThreshold(uint32_t aThreshold);
+
+ void AnnounceOpen();
+ // TODO(bug 843625): Optionally pass an error here.
+ void AnnounceClosed();
+
+ // Find out state
+ DataChannelState GetReadyState() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mReadyState;
+ }
+
+ // Set ready state
+ void SetReadyState(DataChannelState aState);
+
+ void GetLabel(nsAString& aLabel) { CopyUTF8toUTF16(mLabel, aLabel); }
+ void GetProtocol(nsAString& aProtocol) {
+ CopyUTF8toUTF16(mProtocol, aProtocol);
+ }
+ uint16_t GetStream() const { return mStream; }
+
+ void SendOrQueue(DataChannelOnMessageAvailable* aMessage);
+
+ TrafficCounters GetTrafficCounters() const;
+
+ private:
+ nsresult AddDataToBinaryMsg(const char* data, uint32_t size);
+ bool EnsureValidStream(ErrorResult& aRv);
+ void WithTrafficCounters(const std::function<void(TrafficCounters&)>&);
+
+ // These are both mainthread only
+ DataChannelListener* mListener;
+ nsCOMPtr<nsISupports> mContext;
+
+ RefPtr<DataChannelConnection> mConnection;
+ // mainthread only
+ bool mEverOpened = false;
+ const nsCString mLabel;
+ const nsCString mProtocol;
+ // This is mainthread only
+ DataChannelState mReadyState;
+ uint16_t mStream;
+ const DataChannelReliabilityPolicy mPrPolicy;
+ const uint32_t mPrValue;
+ // Accessed on main and STS
+ const bool mNegotiated;
+ const bool mOrdered;
+ // The data channel has completed the open procedure and the client has been
+ // notified about it.
+ bool mHasFinishedOpen = false;
+ // The channel has been opened, but the peer has not yet acked - ensures that
+ // the messages are sent ordered until this is cleared.
+ bool mWaitingForAck = false;
+ // A too large message was attempted to be sent - closing data channel.
+ bool mClosingTooLarge = false;
+ bool mIsRecvBinary;
+ size_t mBufferedThreshold;
+ // Read/written on main only. Decremented via message-passing, because the
+ // spec requires us to queue a task for this.
+ size_t mBufferedAmount;
+ nsCString mRecvBuffer;
+ nsTArray<UniquePtr<BufferedOutgoingMsg>>
+ mBufferedData; // MOZ_GUARDED_BY(mConnection->mLock)
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+ mutable Mutex mStatsLock;
+ TrafficCounters mTrafficCounters MOZ_GUARDED_BY(mStatsLock);
+};
+
+// used to dispatch notifications of incoming data to the main thread
+// Patterned on CallOnMessageAvailable in WebSockets
+// Also used to proxy other items to MainThread
+class DataChannelOnMessageAvailable : public Runnable {
+ public:
+ enum class EventType {
+ OnConnection,
+ OnDisconnected,
+ OnChannelCreated,
+ OnDataString,
+ OnDataBinary,
+ };
+
+ DataChannelOnMessageAvailable(
+ EventType aType, DataChannelConnection* aConnection,
+ DataChannel* aChannel,
+ nsCString& aData) // XXX this causes inefficiency
+ : Runnable("DataChannelOnMessageAvailable"),
+ mType(aType),
+ mChannel(aChannel),
+ mConnection(aConnection),
+ mData(aData) {}
+
+ DataChannelOnMessageAvailable(EventType aType, DataChannel* aChannel)
+ : Runnable("DataChannelOnMessageAvailable"),
+ mType(aType),
+ mChannel(aChannel) {}
+ // XXX is it safe to leave mData uninitialized? This should only be
+ // used for notifications that don't use them, but I'd like more
+ // bulletproof compile-time checking.
+
+ DataChannelOnMessageAvailable(EventType aType,
+ DataChannelConnection* aConnection,
+ DataChannel* aChannel)
+ : Runnable("DataChannelOnMessageAvailable"),
+ mType(aType),
+ mChannel(aChannel),
+ mConnection(aConnection) {}
+
+ // for ON_CONNECTION/ON_DISCONNECTED
+ DataChannelOnMessageAvailable(EventType aType,
+ DataChannelConnection* aConnection)
+ : Runnable("DataChannelOnMessageAvailable"),
+ mType(aType),
+ mConnection(aConnection) {}
+ DataChannelOnMessageAvailable(const DataChannelOnMessageAvailable&) = delete;
+ DataChannelOnMessageAvailable& operator=(
+ const DataChannelOnMessageAvailable&) = delete;
+
+ NS_IMETHOD Run() override;
+
+ private:
+ ~DataChannelOnMessageAvailable() = default;
+
+ EventType mType;
+ // XXX should use union
+ RefPtr<DataChannel> mChannel;
+ RefPtr<DataChannelConnection> mConnection;
+ nsCString mData;
+};
+
+} // namespace mozilla
+
+#endif // NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_
diff --git a/netwerk/sctp/datachannel/DataChannelListener.h b/netwerk/sctp/datachannel/DataChannelListener.h
new file mode 100644
index 0000000000..06564b6cc7
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannelListener.h
@@ -0,0 +1,45 @@
+/* -*- 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 NETWERK_SCTP_DATACHANNEL_DATACHANNELLISTENER_H_
+#define NETWERK_SCTP_DATACHANNEL_DATACHANNELLISTENER_H_
+
+#include "nsISupports.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+// Implemented by consumers of a Channel to receive messages.
+// Can't nest it in DataChannelConnection because C++ doesn't allow forward
+// refs to embedded classes
+class DataChannelListener {
+ public:
+ virtual ~DataChannelListener() = default;
+
+ // Called when a DOMString message is received.
+ virtual nsresult OnMessageAvailable(nsISupports* aContext,
+ const nsACString& message) = 0;
+
+ // Called when a binary message is received.
+ virtual nsresult OnBinaryMessageAvailable(nsISupports* aContext,
+ const nsACString& message) = 0;
+
+ // Called when the channel is connected
+ virtual nsresult OnChannelConnected(nsISupports* aContext) = 0;
+
+ // Called when the channel is closed
+ virtual nsresult OnChannelClosed(nsISupports* aContext) = 0;
+
+ // Called when the BufferedAmount drops below the BufferedAmountLowThreshold
+ virtual nsresult OnBufferLow(nsISupports* aContext) = 0;
+
+ // Called when the BufferedAmount drops to 0
+ virtual nsresult NotBuffered(nsISupports* aContext) = 0;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/sctp/datachannel/DataChannelLog.h b/netwerk/sctp/datachannel/DataChannelLog.h
new file mode 100644
index 0000000000..c77fe2e2d8
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannelLog.h
@@ -0,0 +1,32 @@
+/* -*- 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 DataChannelLog_h
+#define DataChannelLog_h
+
+#include "base/basictypes.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+extern mozilla::LazyLogModule gDataChannelLog;
+}
+
+#define DC_ERROR(args) \
+ MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Error, args)
+
+#define DC_WARN(args) \
+ MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Warning, args)
+
+#define DC_INFO(args) \
+ MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Info, args)
+
+#define DC_DEBUG(args) \
+ MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Debug, args)
+
+#define DC_VERBOSE(args) \
+ MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Verbose, args)
+
+#endif
diff --git a/netwerk/sctp/datachannel/DataChannelProtocol.h b/netwerk/sctp/datachannel/DataChannelProtocol.h
new file mode 100644
index 0000000000..47fe48cdd9
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannelProtocol.h
@@ -0,0 +1,85 @@
+/* -*- 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 NETWERK_SCTP_DATACHANNEL_DATACHANNELPROTOCOL_H_
+#define NETWERK_SCTP_DATACHANNEL_DATACHANNELPROTOCOL_H_
+
+#if defined(__GNUC__)
+# define SCTP_PACKED __attribute__((packed))
+#elif defined(_MSC_VER)
+# pragma pack(push, 1)
+# define SCTP_PACKED
+#else
+# error "Unsupported compiler"
+#endif
+
+#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT 256
+// Do not change this value!
+#define WEBRTC_DATACHANNEL_PORT_DEFAULT 5000
+// TODO: Bug 1381146, change once we resolve the nsCString limitation
+#define WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL 1073741823
+#define WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT 65536
+// TODO: Bug 1382779, once resolved, can be increased to min(Uint8ArrayMaxSize,
+// UINT32_MAX)
+// TODO: Bug 1381146, once resolved, can be increased to whatever we support
+// then (hopefully SIZE_MAX) or be removed
+#define WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE 2147483637
+
+#define DATA_CHANNEL_PPID_CONTROL 50
+#define DATA_CHANNEL_PPID_BINARY_PARTIAL 52
+#define DATA_CHANNEL_PPID_BINARY 53
+#define DATA_CHANNEL_PPID_DOMSTRING_PARTIAL 54
+#define DATA_CHANNEL_PPID_DOMSTRING 51
+#define DATA_CHANNEL_PPID_DOMSTRING_EMPTY 56
+#define DATA_CHANNEL_PPID_BINARY_EMPTY 57
+
+#define DATA_CHANNEL_MAX_BINARY_FRAGMENT 0x4000
+
+#define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE 0x01
+#define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED 0x02
+#define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_COMPLETE 0x04
+
+#define INVALID_STREAM (0xFFFF)
+// max is 0xFFFF: Streams 0 to 0xFFFE = 0xFFFF streams
+#define MAX_NUM_STREAMS (2048)
+
+struct rtcweb_datachannel_open_request {
+ uint8_t msg_type; // DATA_CHANNEL_OPEN
+ uint8_t channel_type;
+ int16_t priority;
+ uint32_t reliability_param;
+ uint16_t label_length;
+ uint16_t protocol_length;
+ char label[1]; // (and protocol) keep VC++ happy...
+} SCTP_PACKED;
+
+struct rtcweb_datachannel_ack {
+ uint8_t msg_type; // DATA_CHANNEL_ACK
+} SCTP_PACKED;
+
+/* msg_type values: */
+/* 0-1 were used in an early version of the protocol with 3-way handshakes */
+#define DATA_CHANNEL_ACK 2
+#define DATA_CHANNEL_OPEN_REQUEST 3
+
+/* channel_type values: */
+#define DATA_CHANNEL_RELIABLE 0x00
+#define DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT 0x01
+#define DATA_CHANNEL_PARTIAL_RELIABLE_TIMED 0x02
+
+#define DATA_CHANNEL_RELIABLE_UNORDERED 0x80
+#define DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED 0x81
+#define DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED 0x82
+
+#define ERR_DATA_CHANNEL_ALREADY_OPEN 1
+#define ERR_DATA_CHANNEL_NONE_AVAILABLE 2
+
+#if defined(_MSC_VER)
+# pragma pack(pop)
+# undef SCTP_PACKED
+#endif
+
+#endif
diff --git a/netwerk/sctp/datachannel/moz.build b/netwerk/sctp/datachannel/moz.build
new file mode 100644
index 0000000000..94656f084e
--- /dev/null
+++ b/netwerk/sctp/datachannel/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "DataChannel.h",
+ "DataChannelListener.h",
+ "DataChannelLog.h",
+ "DataChannelProtocol.h",
+]
+
+SOURCES += [
+ "DataChannel.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc",
+ "/dom/media/webrtc/transport",
+ "/media/webrtc",
+ "/netwerk/sctp/src",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+]
+
+DEFINES["SCTP_DEBUG"] = 1
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["__Userspace_os_Windows"] = 1
+else:
+ DEFINES["__Userspace_os_%s" % CONFIG["OS_TARGET"]] = 1
diff --git a/netwerk/sctp/src/LICENSE.md b/netwerk/sctp/src/LICENSE.md
new file mode 100644
index 0000000000..a2d1f989cf
--- /dev/null
+++ b/netwerk/sctp/src/LICENSE.md
@@ -0,0 +1,27 @@
+Copyright (c) 2015, Randall Stewart and Michael Tuexen
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of usrsctp nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/netwerk/sctp/src/README.md b/netwerk/sctp/src/README.md
new file mode 100644
index 0000000000..3d5ba38f35
--- /dev/null
+++ b/netwerk/sctp/src/README.md
@@ -0,0 +1,10 @@
+# usrsctp
+[![Coverity Scan Build Status](https://scan.coverity.com/projects/13430/badge.svg)](https://scan.coverity.com/projects/usrsctp)
+
+![GitHub Actions Build Status (CMake-based build only)](https://github.com/sctplab/usrsctp/workflows/Build%20with%20CMake/badge.svg)
+
+This is a userland SCTP stack supporting FreeBSD, OpenBSD, Linux, Mac OS X and Windows.
+
+See [manual](Manual.md) for more information.
+
+The status of continuous integration testing is available from our [Buildbot](http://buildbot.nplab.de:18010/#/console).
diff --git a/netwerk/sctp/src/moz.build b/netwerk/sctp/src/moz.build
new file mode 100644
index 0000000000..9fa9641276
--- /dev/null
+++ b/netwerk/sctp/src/moz.build
@@ -0,0 +1,58 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ 'usrsctp.h',
+]
+
+UNIFIED_SOURCES += [
+ 'netinet/sctp_asconf.c',
+ 'netinet/sctp_auth.c',
+ 'netinet/sctp_bsd_addr.c',
+ 'netinet/sctp_callout.c',
+ 'netinet/sctp_cc_functions.c',
+ 'netinet/sctp_crc32.c',
+ 'netinet/sctp_indata.c',
+ 'netinet/sctp_input.c',
+ 'netinet/sctp_output.c',
+ 'netinet/sctp_pcb.c',
+ 'netinet/sctp_peeloff.c',
+ 'netinet/sctp_sha1.c',
+ 'netinet/sctp_ss_functions.c',
+ 'netinet/sctp_sysctl.c',
+ 'netinet/sctp_timer.c',
+ 'netinet/sctp_userspace.c',
+ 'netinet/sctp_usrreq.c',
+ 'netinet/sctputil.c',
+ 'netinet6/sctp6_usrreq.c',
+ 'user_environment.c',
+ 'user_mbuf.c',
+ 'user_recv_thread.c',
+ 'user_socket.c',
+]
+
+Library('nksctp_s')
+
+# We allow warnings for third-party code that can be updated from upstream.
+AllowCompilerWarnings()
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/netwerk/base',
+]
+
+for var in ('__Userspace__',
+ 'SCTP_SIMPLE_ALLOCATOR',
+ 'SCTP_PROCESS_LEVEL_LOCKS',
+ 'SCTP_DEBUG'):
+ DEFINES[var] = 1
+
+if CONFIG['OS_TARGET'] in ('Linux', 'Android'):
+ DEFINES['_GNU_SOURCE'] = 1
+elif CONFIG['OS_TARGET'] == 'Darwin':
+ DEFINES['__APPLE_USE_RFC_2292'] = 1
diff --git a/netwerk/sctp/src/moz.yaml b/netwerk/sctp/src/moz.yaml
new file mode 100644
index 0000000000..14b840547d
--- /dev/null
+++ b/netwerk/sctp/src/moz.yaml
@@ -0,0 +1,63 @@
+schema: 1
+
+bugzilla:
+ product: Core
+ component: Networking
+
+origin:
+ name: sctp
+ description: userland SCTP stack
+
+ url: https://github.com/sctplab/usrsctp/blob/master/Manual.md
+
+ release: a0cbf4681474fab1e89d9e9e2d5c3694fce50359 (2023-09-13T13:37:16Z).
+ revision: a0cbf4681474fab1e89d9e9e2d5c3694fce50359
+
+ license: BSD-3-Clause
+
+vendoring:
+ url: https://github.com/sctplab/usrsctp
+ source-hosting: github
+ tracking: commit
+
+ exclude:
+ - "meson*"
+ - "Makefile.*"
+ - "Manual.*"
+ - "CMake*"
+ - ".*"
+ - "*.py"
+ - "*.in"
+ - "*.ac"
+ - bootstrap
+ - cmake/
+ - fuzzer/
+ - programs/
+ - 'usrsctplib/Makefile.*'
+ - 'usrsctplib/meson.*'
+ - 'usrsctplib/CMake*'
+ - 'usrsctplib/netinet*/meson.*'
+
+ keep:
+ - moz.build
+ - restore_mod.sh
+
+ update-actions:
+ - action: move-dir
+ from: '{vendor_dir}/usrsctplib'
+ to: '{vendor_dir}'
+ - action: run-script
+ script: '{yaml_dir}/restore_mod.sh'
+ cwd: '{yaml_dir}'
+
+ patches:
+ - win32-rands.patch
+ - win32-free.patch
+
+updatebot:
+ maintainer-phab: "#webrtc-reviewers"
+ maintainer-bz: rjesup@jesup.org
+ tasks:
+ - type: vendoring
+ enabled: true
+ frequency: release
diff --git a/netwerk/sctp/src/netinet/sctp.h b/netwerk/sctp/src/netinet/sctp.h
new file mode 100644
index 0000000000..6129e77f96
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp.h
@@ -0,0 +1,674 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_H_
+#define _NETINET_SCTP_H_
+
+#if defined(__APPLE__) || defined(__linux__)
+#include <stdint.h>
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+#endif
+#include <sys/types.h>
+
+#if !defined(_WIN32)
+#define SCTP_PACKED __attribute__((packed))
+#else
+#pragma pack (push, 1)
+#define SCTP_PACKED
+#endif
+
+/*
+ * SCTP protocol - RFC4960.
+ */
+struct sctphdr {
+ uint16_t src_port; /* source port */
+ uint16_t dest_port; /* destination port */
+ uint32_t v_tag; /* verification tag of packet */
+ uint32_t checksum; /* CRC32C checksum */
+ /* chunks follow... */
+} SCTP_PACKED;
+
+/*
+ * SCTP Chunks
+ */
+struct sctp_chunkhdr {
+ uint8_t chunk_type; /* chunk type */
+ uint8_t chunk_flags; /* chunk flags */
+ uint16_t chunk_length; /* chunk length */
+ /* optional params follow */
+} SCTP_PACKED;
+
+/*
+ * SCTP chunk parameters
+ */
+struct sctp_paramhdr {
+ uint16_t param_type; /* parameter type */
+ uint16_t param_length; /* parameter length */
+} SCTP_PACKED;
+
+/*
+ * user socket options: socket API defined
+ */
+/*
+ * read-write options
+ */
+#define SCTP_RTOINFO 0x00000001
+#define SCTP_ASSOCINFO 0x00000002
+#define SCTP_INITMSG 0x00000003
+#define SCTP_NODELAY 0x00000004
+#define SCTP_AUTOCLOSE 0x00000005
+#define SCTP_SET_PEER_PRIMARY_ADDR 0x00000006
+#define SCTP_PRIMARY_ADDR 0x00000007
+#define SCTP_ADAPTATION_LAYER 0x00000008
+/* same as above */
+#define SCTP_ADAPTION_LAYER 0x00000008
+#define SCTP_DISABLE_FRAGMENTS 0x00000009
+#define SCTP_PEER_ADDR_PARAMS 0x0000000a
+#define SCTP_DEFAULT_SEND_PARAM 0x0000000b
+/* ancillary data/notification interest options */
+#define SCTP_EVENTS 0x0000000c /* deprecated */
+/* Without this applied we will give V4 and V6 addresses on a V6 socket */
+#define SCTP_I_WANT_MAPPED_V4_ADDR 0x0000000d
+#define SCTP_MAXSEG 0x0000000e
+#define SCTP_DELAYED_SACK 0x0000000f
+#define SCTP_FRAGMENT_INTERLEAVE 0x00000010
+#define SCTP_PARTIAL_DELIVERY_POINT 0x00000011
+/* authentication support */
+#define SCTP_AUTH_CHUNK 0x00000012
+#define SCTP_AUTH_KEY 0x00000013
+#define SCTP_HMAC_IDENT 0x00000014
+#define SCTP_AUTH_ACTIVE_KEY 0x00000015
+#define SCTP_AUTH_DELETE_KEY 0x00000016
+#define SCTP_USE_EXT_RCVINFO 0x00000017
+#define SCTP_AUTO_ASCONF 0x00000018 /* rw */
+#define SCTP_MAXBURST 0x00000019 /* rw */
+#define SCTP_MAX_BURST 0x00000019 /* rw */
+/* assoc level context */
+#define SCTP_CONTEXT 0x0000001a /* rw */
+/* explicit EOR signalling */
+#define SCTP_EXPLICIT_EOR 0x0000001b
+#define SCTP_REUSE_PORT 0x0000001c /* rw */
+#define SCTP_AUTH_DEACTIVATE_KEY 0x0000001d
+#define SCTP_EVENT 0x0000001e
+#define SCTP_RECVRCVINFO 0x0000001f
+#define SCTP_RECVNXTINFO 0x00000020
+#define SCTP_DEFAULT_SNDINFO 0x00000021
+#define SCTP_DEFAULT_PRINFO 0x00000022
+#define SCTP_PEER_ADDR_THLDS 0x00000023
+#define SCTP_REMOTE_UDP_ENCAPS_PORT 0x00000024
+#define SCTP_ECN_SUPPORTED 0x00000025
+#define SCTP_PR_SUPPORTED 0x00000026
+#define SCTP_AUTH_SUPPORTED 0x00000027
+#define SCTP_ASCONF_SUPPORTED 0x00000028
+#define SCTP_RECONFIG_SUPPORTED 0x00000029
+#define SCTP_NRSACK_SUPPORTED 0x00000030
+#define SCTP_PKTDROP_SUPPORTED 0x00000031
+#define SCTP_MAX_CWND 0x00000032
+#define SCTP_ACCEPT_ZERO_CHECKSUM 0x00000033
+
+/*
+ * read-only options
+ */
+#define SCTP_STATUS 0x00000100
+#define SCTP_GET_PEER_ADDR_INFO 0x00000101
+/* authentication support */
+#define SCTP_PEER_AUTH_CHUNKS 0x00000102
+#define SCTP_LOCAL_AUTH_CHUNKS 0x00000103
+#define SCTP_GET_ASSOC_NUMBER 0x00000104 /* ro */
+#define SCTP_GET_ASSOC_ID_LIST 0x00000105 /* ro */
+#define SCTP_TIMEOUTS 0x00000106
+#define SCTP_PR_STREAM_STATUS 0x00000107
+#define SCTP_PR_ASSOC_STATUS 0x00000108
+
+/*
+ * user socket options: BSD implementation specific
+ */
+/*
+ * Blocking I/O is enabled on any TCP type socket by default. For the UDP
+ * model if this is turned on then the socket buffer is shared for send
+ * resources amongst all associations. The default for the UDP model is that
+ * is SS_NBIO is set. Which means all associations have a separate send
+ * limit BUT they will NOT ever BLOCK instead you will get an error back
+ * EAGAIN if you try to send too much. If you want the blocking semantics you
+ * set this option at the cost of sharing one socket send buffer size amongst
+ * all associations. Peeled off sockets turn this option off and block. But
+ * since both TCP and peeled off sockets have only one assoc per socket this
+ * is fine. It probably does NOT make sense to set this on SS_NBIO on a TCP
+ * model OR peeled off UDP model, but we do allow you to do so. You just use
+ * the normal syscall to toggle SS_NBIO the way you want.
+ *
+ * Blocking I/O is controlled by the SS_NBIO flag on the socket state so_state
+ * field.
+ */
+
+#define SCTP_ENABLE_STREAM_RESET 0x00000900 /* struct sctp_assoc_value */
+#define SCTP_RESET_STREAMS 0x00000901 /* struct sctp_reset_streams */
+#define SCTP_RESET_ASSOC 0x00000902 /* sctp_assoc_t */
+#define SCTP_ADD_STREAMS 0x00000903 /* struct sctp_add_streams */
+
+/* For enable stream reset */
+#define SCTP_ENABLE_RESET_STREAM_REQ 0x00000001
+#define SCTP_ENABLE_RESET_ASSOC_REQ 0x00000002
+#define SCTP_ENABLE_CHANGE_ASSOC_REQ 0x00000004
+#define SCTP_ENABLE_VALUE_MASK 0x00000007
+/* For reset streams */
+#define SCTP_STREAM_RESET_INCOMING 0x00000001
+#define SCTP_STREAM_RESET_OUTGOING 0x00000002
+
+/* here on down are more implementation specific */
+#define SCTP_SET_DEBUG_LEVEL 0x00001005
+#define SCTP_CLR_STAT_LOG 0x00001007
+/* CMT ON/OFF socket option */
+#define SCTP_CMT_ON_OFF 0x00001200
+#define SCTP_CMT_USE_DAC 0x00001201
+/* JRS - Pluggable Congestion Control Socket option */
+#define SCTP_PLUGGABLE_CC 0x00001202
+/* RS - Pluggable Stream Scheduling Socket option */
+#define SCTP_STREAM_SCHEDULER 0x00001203
+#define SCTP_STREAM_SCHEDULER_VALUE 0x00001204
+/* The next two are for backwards compatibility. */
+#define SCTP_PLUGGABLE_SS SCTP_STREAM_SCHEDULER
+#define SCTP_SS_VALUE SCTP_STREAM_SCHEDULER_VALUE
+#define SCTP_CC_OPTION 0x00001205 /* Options for CC modules */
+/* For I-DATA */
+#define SCTP_INTERLEAVING_SUPPORTED 0x00001206
+
+/* read only */
+#define SCTP_GET_SNDBUF_USE 0x00001101
+#define SCTP_GET_STAT_LOG 0x00001103
+#define SCTP_PCB_STATUS 0x00001104
+#define SCTP_GET_NONCE_VALUES 0x00001105
+
+/* Special hook for dynamically setting primary for all assoc's,
+ * this is a write only option that requires root privilege.
+ */
+#define SCTP_SET_DYNAMIC_PRIMARY 0x00002001
+
+/* VRF (virtual router feature) and multi-VRF support
+ * options. VRF's provide splits within a router
+ * that give the views of multiple routers. A
+ * standard host, without VRF support, is just
+ * a single VRF. If VRF's are supported then
+ * the transport must be VRF aware. This means
+ * that every socket call coming in must be directed
+ * within the endpoint to one of the VRF's it belongs
+ * to. The endpoint, before binding, may select
+ * the "default" VRF it is in by using a set socket
+ * option with SCTP_VRF_ID. This will also
+ * get propagated to the default VRF. Once the
+ * endpoint binds an address then it CANNOT add
+ * additional VRF's to become a Multi-VRF endpoint.
+ *
+ * Before BINDING additional VRF's can be added with
+ * the SCTP_ADD_VRF_ID call or deleted with
+ * SCTP_DEL_VRF_ID.
+ *
+ * Associations are ALWAYS contained inside a single
+ * VRF. They cannot reside in two (or more) VRF's. Incoming
+ * packets, assuming the router is VRF aware, can always
+ * tell us what VRF they arrived on. A host not supporting
+ * any VRF's will find that the packets always arrived on the
+ * single VRF that the host has.
+ *
+ */
+
+#define SCTP_VRF_ID 0x00003001
+#define SCTP_ADD_VRF_ID 0x00003002
+#define SCTP_GET_VRF_IDS 0x00003003
+#define SCTP_GET_ASOC_VRF 0x00003004
+#define SCTP_DEL_VRF_ID 0x00003005
+
+/*
+ * If you enable packet logging you can get
+ * a poor mans ethereal output in binary
+ * form. Note this is a compile option to
+ * the kernel, SCTP_PACKET_LOGGING, and
+ * without it in your kernel you
+ * will get a EOPNOTSUPP
+ */
+#define SCTP_GET_PACKET_LOG 0x00004001
+
+/*
+ * hidden implementation specific options these are NOT user visible (should
+ * move out of sctp.h)
+ */
+/* sctp_bindx() flags as hidden socket options */
+#define SCTP_BINDX_ADD_ADDR 0x00008001
+#define SCTP_BINDX_REM_ADDR 0x00008002
+/* Hidden socket option that gets the addresses */
+#define SCTP_GET_PEER_ADDRESSES 0x00008003
+#define SCTP_GET_LOCAL_ADDRESSES 0x00008004
+/* return the total count in bytes needed to hold all local addresses bound */
+#define SCTP_GET_LOCAL_ADDR_SIZE 0x00008005
+/* Return the total count in bytes needed to hold the remote address */
+#define SCTP_GET_REMOTE_ADDR_SIZE 0x00008006
+/* hidden option for connectx */
+#define SCTP_CONNECT_X 0x00008007
+/* hidden option for connectx_delayed, part of sendx */
+#define SCTP_CONNECT_X_DELAYED 0x00008008
+#define SCTP_CONNECT_X_COMPLETE 0x00008009
+/* hidden socket option based sctp_peeloff */
+#define SCTP_PEELOFF 0x0000800a
+/* the real worker for sctp_getaddrlen() */
+#define SCTP_GET_ADDR_LEN 0x0000800b
+#if defined(__APPLE__) && !defined(__Userspace__)
+/* temporary workaround for Apple listen() issue, no args used */
+#define SCTP_LISTEN_FIX 0x0000800c
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+/* workaround for Cygwin on Windows: returns the SOCKET handle */
+#define SCTP_GET_HANDLE 0x0000800d
+#endif
+/* Debug things that need to be purged */
+#define SCTP_SET_INITIAL_DBG_SEQ 0x00009f00
+
+/* JRS - Supported congestion control modules for pluggable
+ * congestion control
+ */
+/* Standard TCP Congestion Control */
+#define SCTP_CC_RFC2581 0x00000000
+/* High Speed TCP Congestion Control (Floyd) */
+#define SCTP_CC_HSTCP 0x00000001
+/* HTCP Congestion Control */
+#define SCTP_CC_HTCP 0x00000002
+/* RTCC Congestion Control - RFC2581 plus */
+#define SCTP_CC_RTCC 0x00000003
+
+#define SCTP_CC_OPT_RTCC_SETMODE 0x00002000
+#define SCTP_CC_OPT_USE_DCCC_ECN 0x00002001
+#define SCTP_CC_OPT_STEADY_STEP 0x00002002
+
+#define SCTP_CMT_OFF 0
+#define SCTP_CMT_BASE 1
+#define SCTP_CMT_RPV1 2
+#define SCTP_CMT_RPV2 3
+#define SCTP_CMT_MPTCP 4
+#define SCTP_CMT_MAX SCTP_CMT_MPTCP
+
+/* RS - Supported stream scheduling modules for pluggable
+ * stream scheduling
+ */
+/* Default simple round-robin */
+#define SCTP_SS_DEFAULT 0x00000000
+/* Real round-robin */
+#define SCTP_SS_RR 0x00000001
+/* Real round-robin per packet */
+#define SCTP_SS_RR_PKT 0x00000002
+/* Priority */
+#define SCTP_SS_PRIO 0x00000003
+/* Fair Bandwidth */
+#define SCTP_SS_FB 0x00000004
+/* First-come, first-serve */
+#define SCTP_SS_FCFS 0x00000005
+/* The next five are for backwards compatibility. */
+#define SCTP_SS_ROUND_ROBIN SCTP_SS_RR
+#define SCTP_SS_ROUND_ROBIN_PACKET SCTP_SS_RR_PKT
+#define SCTP_SS_PRIORITY SCTP_SS_PRIO
+#define SCTP_SS_FAIR_BANDWITH SCTP_SS_FB
+#define SCTP_SS_FIRST_COME SCTP_SS_FCFS
+
+/* fragment interleave constants
+ * setting must be one of these or
+ * EINVAL returned.
+ */
+#define SCTP_FRAG_LEVEL_0 0x00000000
+#define SCTP_FRAG_LEVEL_1 0x00000001
+#define SCTP_FRAG_LEVEL_2 0x00000002
+
+/*
+ * user state values
+ */
+#define SCTP_CLOSED 0x0000
+#define SCTP_BOUND 0x1000
+#define SCTP_LISTEN 0x2000
+#define SCTP_COOKIE_WAIT 0x0002
+#define SCTP_COOKIE_ECHOED 0x0004
+#define SCTP_ESTABLISHED 0x0008
+#define SCTP_SHUTDOWN_SENT 0x0010
+#define SCTP_SHUTDOWN_RECEIVED 0x0020
+#define SCTP_SHUTDOWN_ACK_SENT 0x0040
+#define SCTP_SHUTDOWN_PENDING 0x0080
+
+/*
+ * SCTP operational error codes (user visible)
+ */
+#define SCTP_CAUSE_NO_ERROR 0x0000
+#define SCTP_CAUSE_INVALID_STREAM 0x0001
+#define SCTP_CAUSE_MISSING_PARAM 0x0002
+#define SCTP_CAUSE_STALE_COOKIE 0x0003
+#define SCTP_CAUSE_OUT_OF_RESC 0x0004
+#define SCTP_CAUSE_UNRESOLVABLE_ADDR 0x0005
+#define SCTP_CAUSE_UNRECOG_CHUNK 0x0006
+#define SCTP_CAUSE_INVALID_PARAM 0x0007
+#define SCTP_CAUSE_UNRECOG_PARAM 0x0008
+#define SCTP_CAUSE_NO_USER_DATA 0x0009
+#define SCTP_CAUSE_COOKIE_IN_SHUTDOWN 0x000a
+#define SCTP_CAUSE_RESTART_W_NEWADDR 0x000b
+#define SCTP_CAUSE_USER_INITIATED_ABT 0x000c
+#define SCTP_CAUSE_PROTOCOL_VIOLATION 0x000d
+
+/* Error causes from RFC5061 */
+#define SCTP_CAUSE_DELETING_LAST_ADDR 0x00a0
+#define SCTP_CAUSE_RESOURCE_SHORTAGE 0x00a1
+#define SCTP_CAUSE_DELETING_SRC_ADDR 0x00a2
+#define SCTP_CAUSE_ILLEGAL_ASCONF_ACK 0x00a3
+#define SCTP_CAUSE_REQUEST_REFUSED 0x00a4
+
+/* Error causes from nat-draft */
+#define SCTP_CAUSE_NAT_COLLIDING_STATE 0x00b0
+#define SCTP_CAUSE_NAT_MISSING_STATE 0x00b1
+
+/* Error causes from RFC4895 */
+#define SCTP_CAUSE_UNSUPPORTED_HMACID 0x0105
+
+/*
+ * error cause parameters (user visible)
+ */
+struct sctp_gen_error_cause {
+ uint16_t code;
+ uint16_t length;
+ uint8_t info[];
+} SCTP_PACKED;
+
+struct sctp_error_cause {
+ uint16_t code;
+ uint16_t length;
+ /* optional cause-specific info may follow */
+} SCTP_PACKED;
+
+struct sctp_error_invalid_stream {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_INVALID_STREAM */
+ uint16_t stream_id; /* stream id of the DATA in error */
+ uint16_t reserved;
+} SCTP_PACKED;
+
+struct sctp_error_missing_param {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_MISSING_PARAM */
+ uint32_t num_missing_params; /* number of missing parameters */
+ uint16_t type[];
+} SCTP_PACKED;
+
+struct sctp_error_stale_cookie {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_STALE_COOKIE */
+ uint32_t stale_time; /* time in usec of staleness */
+} SCTP_PACKED;
+
+struct sctp_error_out_of_resource {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_OUT_OF_RESOURCES */
+} SCTP_PACKED;
+
+struct sctp_error_unresolv_addr {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_UNRESOLVABLE_ADDR */
+} SCTP_PACKED;
+
+struct sctp_error_unrecognized_chunk {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_UNRECOG_CHUNK */
+ struct sctp_chunkhdr ch;/* header from chunk in error */
+} SCTP_PACKED;
+
+struct sctp_error_no_user_data {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_NO_USER_DATA */
+ uint32_t tsn; /* TSN of the empty data chunk */
+} SCTP_PACKED;
+
+struct sctp_error_auth_invalid_hmac {
+ struct sctp_error_cause cause; /* code=SCTP_CAUSE_UNSUPPORTED_HMACID */
+ uint16_t hmac_id;
+} SCTP_PACKED;
+
+/*
+ * Main SCTP chunk types we place these here so natd and f/w's in user land
+ * can find them.
+ */
+/************0x00 series ***********/
+#define SCTP_DATA 0x00
+#define SCTP_INITIATION 0x01
+#define SCTP_INITIATION_ACK 0x02
+#define SCTP_SELECTIVE_ACK 0x03
+#define SCTP_HEARTBEAT_REQUEST 0x04
+#define SCTP_HEARTBEAT_ACK 0x05
+#define SCTP_ABORT_ASSOCIATION 0x06
+#define SCTP_SHUTDOWN 0x07
+#define SCTP_SHUTDOWN_ACK 0x08
+#define SCTP_OPERATION_ERROR 0x09
+#define SCTP_COOKIE_ECHO 0x0a
+#define SCTP_COOKIE_ACK 0x0b
+#define SCTP_ECN_ECHO 0x0c
+#define SCTP_ECN_CWR 0x0d
+#define SCTP_SHUTDOWN_COMPLETE 0x0e
+/* RFC4895 */
+#define SCTP_AUTHENTICATION 0x0f
+/* EY nr_sack chunk id*/
+#define SCTP_NR_SELECTIVE_ACK 0x10
+/************0x40 series ***********/
+#define SCTP_IDATA 0x40
+/************0x80 series ***********/
+/* RFC5061 */
+#define SCTP_ASCONF_ACK 0x80
+/* draft-ietf-stewart-pktdrpsctp */
+#define SCTP_PACKET_DROPPED 0x81
+/* draft-ietf-stewart-strreset-xxx */
+#define SCTP_STREAM_RESET 0x82
+
+/* RFC4820 */
+#define SCTP_PAD_CHUNK 0x84
+/************0xc0 series ***********/
+/* RFC3758 */
+#define SCTP_FORWARD_CUM_TSN 0xc0
+/* RFC5061 */
+#define SCTP_ASCONF 0xc1
+#define SCTP_IFORWARD_CUM_TSN 0xc2
+
+/* ABORT and SHUTDOWN COMPLETE FLAG */
+#define SCTP_HAD_NO_TCB 0x01
+
+/* Packet dropped flags */
+#define SCTP_FROM_MIDDLE_BOX SCTP_HAD_NO_TCB
+#define SCTP_BADCRC 0x02
+#define SCTP_PACKET_TRUNCATED 0x04
+
+/* Flag for ECN -CWR */
+#define SCTP_CWR_REDUCE_OVERRIDE 0x01
+#define SCTP_CWR_IN_SAME_WINDOW 0x02
+
+#define SCTP_SAT_NETWORK_MIN 400 /* min ms for RTT to set satellite
+ * time */
+#define SCTP_SAT_NETWORK_BURST_INCR 2 /* how many times to multiply maxburst
+ * in sat */
+
+/* Data Chuck Specific Flags */
+#define SCTP_DATA_FRAG_MASK 0x03
+#define SCTP_DATA_MIDDLE_FRAG 0x00
+#define SCTP_DATA_LAST_FRAG 0x01
+#define SCTP_DATA_FIRST_FRAG 0x02
+#define SCTP_DATA_NOT_FRAG 0x03
+#define SCTP_DATA_UNORDERED 0x04
+#define SCTP_DATA_SACK_IMMEDIATELY 0x08
+/* ECN Nonce: SACK Chunk Specific Flags */
+#define SCTP_SACK_NONCE_SUM 0x01
+
+/* CMT DAC algorithm SACK flag */
+#define SCTP_SACK_CMT_DAC 0x80
+
+/*
+ * PCB flags (in sctp_flags bitmask).
+ * Note the features and flags are meant
+ * for use by netstat.
+ */
+#define SCTP_PCB_FLAGS_UDPTYPE 0x00000001
+#define SCTP_PCB_FLAGS_TCPTYPE 0x00000002
+#define SCTP_PCB_FLAGS_BOUNDALL 0x00000004
+#define SCTP_PCB_FLAGS_ACCEPTING 0x00000008
+#define SCTP_PCB_FLAGS_UNBOUND 0x00000010
+#define SCTP_PCB_FLAGS_SND_ITERATOR_UP 0x00000020
+#define SCTP_PCB_FLAGS_CLOSE_IP 0x00040000
+#define SCTP_PCB_FLAGS_WAS_CONNECTED 0x00080000
+#define SCTP_PCB_FLAGS_WAS_ABORTED 0x00100000
+/* TCP model support */
+
+#define SCTP_PCB_FLAGS_CONNECTED 0x00200000
+#define SCTP_PCB_FLAGS_IN_TCPPOOL 0x00400000
+#define SCTP_PCB_FLAGS_DONT_WAKE 0x00800000
+#define SCTP_PCB_FLAGS_WAKEOUTPUT 0x01000000
+#define SCTP_PCB_FLAGS_WAKEINPUT 0x02000000
+#define SCTP_PCB_FLAGS_BOUND_V6 0x04000000
+#define SCTP_PCB_FLAGS_BLOCKING_IO 0x08000000
+#define SCTP_PCB_FLAGS_SOCKET_GONE 0x10000000
+#define SCTP_PCB_FLAGS_SOCKET_ALLGONE 0x20000000
+#define SCTP_PCB_FLAGS_SOCKET_CANT_READ 0x40000000
+#if defined(__Userspace__)
+#define SCTP_PCB_FLAGS_BOUND_CONN 0x80000000
+
+/* flags to copy to new PCB */
+#define SCTP_PCB_COPY_FLAGS (SCTP_PCB_FLAGS_BOUNDALL|\
+ SCTP_PCB_FLAGS_WAKEINPUT|\
+ SCTP_PCB_FLAGS_BOUND_V6|\
+ SCTP_PCB_FLAGS_BOUND_CONN)
+#else
+
+/* flags to copy to new PCB */
+#define SCTP_PCB_COPY_FLAGS (SCTP_PCB_FLAGS_BOUNDALL|\
+ SCTP_PCB_FLAGS_WAKEINPUT|\
+ SCTP_PCB_FLAGS_BOUND_V6)
+#endif
+
+/*
+ * PCB Features (in sctp_features bitmask)
+ */
+#define SCTP_PCB_FLAGS_DO_NOT_PMTUD 0x0000000000000001
+#define SCTP_PCB_FLAGS_EXT_RCVINFO 0x0000000000000002 /* deprecated */
+#define SCTP_PCB_FLAGS_DONOT_HEARTBEAT 0x0000000000000004
+#define SCTP_PCB_FLAGS_FRAG_INTERLEAVE 0x0000000000000008
+#define SCTP_PCB_FLAGS_INTERLEAVE_STRMS 0x0000000000000010
+#define SCTP_PCB_FLAGS_DO_ASCONF 0x0000000000000020
+#define SCTP_PCB_FLAGS_AUTO_ASCONF 0x0000000000000040
+/* socket options */
+#define SCTP_PCB_FLAGS_NODELAY 0x0000000000000100
+#define SCTP_PCB_FLAGS_AUTOCLOSE 0x0000000000000200
+#define SCTP_PCB_FLAGS_RECVDATAIOEVNT 0x0000000000000400 /* deprecated */
+#define SCTP_PCB_FLAGS_RECVASSOCEVNT 0x0000000000000800
+#define SCTP_PCB_FLAGS_RECVPADDREVNT 0x0000000000001000
+#define SCTP_PCB_FLAGS_RECVPEERERR 0x0000000000002000
+#define SCTP_PCB_FLAGS_RECVSENDFAILEVNT 0x0000000000004000 /* deprecated */
+#define SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT 0x0000000000008000
+#define SCTP_PCB_FLAGS_ADAPTATIONEVNT 0x0000000000010000
+#define SCTP_PCB_FLAGS_PDAPIEVNT 0x0000000000020000
+#define SCTP_PCB_FLAGS_AUTHEVNT 0x0000000000040000
+#define SCTP_PCB_FLAGS_STREAM_RESETEVNT 0x0000000000080000
+#define SCTP_PCB_FLAGS_NO_FRAGMENT 0x0000000000100000
+#define SCTP_PCB_FLAGS_EXPLICIT_EOR 0x0000000000400000
+#define SCTP_PCB_FLAGS_NEEDS_MAPPED_V4 0x0000000000800000
+#define SCTP_PCB_FLAGS_MULTIPLE_ASCONFS 0x0000000001000000
+#define SCTP_PCB_FLAGS_PORTREUSE 0x0000000002000000
+#define SCTP_PCB_FLAGS_DRYEVNT 0x0000000004000000
+#define SCTP_PCB_FLAGS_RECVRCVINFO 0x0000000008000000
+#define SCTP_PCB_FLAGS_RECVNXTINFO 0x0000000010000000
+#define SCTP_PCB_FLAGS_ASSOC_RESETEVNT 0x0000000020000000
+#define SCTP_PCB_FLAGS_STREAM_CHANGEEVNT 0x0000000040000000
+#define SCTP_PCB_FLAGS_RECVNSENDFAILEVNT 0x0000000080000000
+
+/*-
+ * mobility_features parameters (by micchie).Note
+ * these features are applied against the
+ * sctp_mobility_features flags.. not the sctp_features
+ * flags.
+ */
+#define SCTP_MOBILITY_BASE 0x00000001
+#define SCTP_MOBILITY_FASTHANDOFF 0x00000002
+#define SCTP_MOBILITY_PRIM_DELETED 0x00000004
+
+/* Smallest PMTU allowed when disabling PMTU discovery */
+#define SCTP_SMALLEST_PMTU 512
+/* Largest PMTU allowed when disabling PMTU discovery */
+#define SCTP_LARGEST_PMTU 65536
+
+#if defined(_WIN32)
+#pragma pack(pop)
+#endif
+#undef SCTP_PACKED
+
+#include <netinet/sctp_uio.h>
+
+/* This dictates the size of the packet
+ * collection buffer. This only applies
+ * if SCTP_PACKET_LOGGING is enabled in
+ * your config.
+ */
+#define SCTP_PACKET_LOG_SIZE 65536
+
+/* Maximum delays and such a user can set for options that
+ * take ms.
+ */
+#define SCTP_MAX_SACK_DELAY 500 /* per RFC4960 */
+#define SCTP_MAX_HB_INTERVAL 14400000 /* 4 hours in ms */
+#define SCTP_MIN_COOKIE_LIFE 1000 /* 1 second in ms */
+#define SCTP_MAX_COOKIE_LIFE 3600000 /* 1 hour in ms */
+
+/* Types of logging/KTR tracing that can be enabled via the
+ * sysctl net.inet.sctp.sctp_logging. You must also enable
+ * SUBSYS tracing.
+ * Note that you must have the SCTP option in the kernel
+ * to enable these as well.
+ */
+#define SCTP_BLK_LOGGING_ENABLE 0x00000001
+#define SCTP_CWND_MONITOR_ENABLE 0x00000002
+#define SCTP_CWND_LOGGING_ENABLE 0x00000004
+#define SCTP_FLIGHT_LOGGING_ENABLE 0x00000020
+#define SCTP_FR_LOGGING_ENABLE 0x00000040
+#define SCTP_LOCK_LOGGING_ENABLE 0x00000080
+#define SCTP_MAP_LOGGING_ENABLE 0x00000100
+#define SCTP_MBCNT_LOGGING_ENABLE 0x00000200
+#define SCTP_MBUF_LOGGING_ENABLE 0x00000400
+#define SCTP_NAGLE_LOGGING_ENABLE 0x00000800
+#define SCTP_RECV_RWND_LOGGING_ENABLE 0x00001000
+#define SCTP_RTTVAR_LOGGING_ENABLE 0x00002000
+#define SCTP_SACK_LOGGING_ENABLE 0x00004000
+#define SCTP_SACK_RWND_LOGGING_ENABLE 0x00008000
+#define SCTP_SB_LOGGING_ENABLE 0x00010000
+#define SCTP_STR_LOGGING_ENABLE 0x00020000
+#define SCTP_WAKE_LOGGING_ENABLE 0x00040000
+#define SCTP_LOG_MAXBURST_ENABLE 0x00080000
+#define SCTP_LOG_RWND_ENABLE 0x00100000
+#define SCTP_LOG_SACK_ARRIVALS_ENABLE 0x00200000
+#define SCTP_LTRACE_CHUNK_ENABLE 0x00400000
+#define SCTP_LTRACE_ERROR_ENABLE 0x00800000
+#define SCTP_LAST_PACKET_TRACING 0x01000000
+#define SCTP_THRESHOLD_LOGGING 0x02000000
+#define SCTP_LOG_AT_SEND_2_SCTP 0x04000000
+#define SCTP_LOG_AT_SEND_2_OUTQ 0x08000000
+#define SCTP_LOG_TRY_ADVANCE 0x10000000
+
+#endif /* !_NETINET_SCTP_H_ */
diff --git a/netwerk/sctp/src/netinet/sctp_asconf.c b/netwerk/sctp/src/netinet/sctp_asconf.c
new file mode 100644
index 0000000000..a4440e5e0c
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_asconf.c
@@ -0,0 +1,3534 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(_WIN32)
+// Needed for unified build so that rand_s is available to all unified
+// sources.
+#if !defined(_CRT_RAND_S) && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+#define _CRT_RAND_S
+#endif
+#endif
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_timer.h>
+
+/*
+ * debug flags:
+ * SCTP_DEBUG_ASCONF1: protocol info, general info and errors
+ * SCTP_DEBUG_ASCONF2: detailed info
+ */
+
+/*
+ * RFC 5061
+ *
+ * An ASCONF parameter queue exists per asoc which holds the pending address
+ * operations. Lists are updated upon receipt of ASCONF-ACK.
+ *
+ * A restricted_addrs list exists per assoc to hold local addresses that are
+ * not (yet) usable by the assoc as a source address. These addresses are
+ * either pending an ASCONF operation (and exist on the ASCONF parameter
+ * queue), or they are permanently restricted (the peer has returned an
+ * ERROR indication to an ASCONF(ADD), or the peer does not support ASCONF).
+ *
+ * Deleted addresses are always immediately removed from the lists as they will
+ * (shortly) no longer exist in the kernel. We send ASCONFs as a courtesy,
+ * only if allowed.
+ */
+
+/*
+ * ASCONF parameter processing.
+ * response_required: set if a reply is required (eg. SUCCESS_REPORT).
+ * returns a mbuf to an "error" response parameter or NULL/"success" if ok.
+ * FIX: allocating this many mbufs on the fly is pretty inefficient...
+ */
+static struct mbuf *
+sctp_asconf_success_response(uint32_t id)
+{
+ struct mbuf *m_reply = NULL;
+ struct sctp_asconf_paramhdr *aph;
+
+ m_reply = sctp_get_mbuf_for_msg(sizeof(struct sctp_asconf_paramhdr),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (m_reply == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_success_response: couldn't get mbuf!\n");
+ return (NULL);
+ }
+ aph = mtod(m_reply, struct sctp_asconf_paramhdr *);
+ aph->correlation_id = id;
+ aph->ph.param_type = htons(SCTP_SUCCESS_REPORT);
+ aph->ph.param_length = sizeof(struct sctp_asconf_paramhdr);
+ SCTP_BUF_LEN(m_reply) = aph->ph.param_length;
+ aph->ph.param_length = htons(aph->ph.param_length);
+
+ return (m_reply);
+}
+
+static struct mbuf *
+sctp_asconf_error_response(uint32_t id, uint16_t cause, uint8_t *error_tlv,
+ uint16_t tlv_length)
+{
+ struct mbuf *m_reply = NULL;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_error_cause *error;
+ uint32_t buf_len;
+ uint16_t i, param_length, cause_length, padding_length;
+ uint8_t *tlv;
+
+ if (error_tlv == NULL) {
+ tlv_length = 0;
+ }
+ cause_length = sizeof(struct sctp_error_cause) + tlv_length;
+ param_length = sizeof(struct sctp_asconf_paramhdr) + cause_length;
+ padding_length = tlv_length % 4;
+ if (padding_length != 0) {
+ padding_length = 4 - padding_length;
+ }
+ buf_len = param_length + padding_length;
+ if (buf_len > MLEN) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_error_response: tlv_length (%xh) too big\n",
+ tlv_length);
+ return (NULL);
+ }
+ m_reply = sctp_get_mbuf_for_msg(buf_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_reply == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_error_response: couldn't get mbuf!\n");
+ return (NULL);
+ }
+ aph = mtod(m_reply, struct sctp_asconf_paramhdr *);
+ aph->ph.param_type = htons(SCTP_ERROR_CAUSE_IND);
+ aph->ph.param_length = htons(param_length);
+ aph->correlation_id = id;
+ error = (struct sctp_error_cause *)(aph + 1);
+ error->code = htons(cause);
+ error->length = htons(cause_length);
+ if (error_tlv != NULL) {
+ tlv = (uint8_t *) (error + 1);
+ memcpy(tlv, error_tlv, tlv_length);
+ for (i = 0; i < padding_length; i++) {
+ tlv[tlv_length + i] = 0;
+ }
+ }
+ SCTP_BUF_LEN(m_reply) = buf_len;
+ return (m_reply);
+}
+
+static struct mbuf *
+sctp_process_asconf_add_ip(struct sockaddr *src, struct sctp_asconf_paramhdr *aph,
+ struct sctp_tcb *stcb, int send_hb, int response_required)
+{
+ struct sctp_nets *net;
+ struct mbuf *m_reply = NULL;
+ union sctp_sockstore store;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type, aparam_length;
+#if defined(INET) || defined(INET6)
+ uint16_t param_length;
+#endif
+ struct sockaddr *sa;
+ int zero_address = 0;
+ int bad_address = 0;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *v4addr;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *v6addr;
+#endif
+
+ aparam_length = ntohs(aph->ph.param_length);
+ if (aparam_length < sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_paramhdr)) {
+ return (NULL);
+ }
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+ if (param_length + sizeof(struct sctp_asconf_paramhdr) != aparam_length) {
+ return (NULL);
+ }
+#endif
+ sa = &store.sa;
+ switch (param_type) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv4addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ sin = &store.sin;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_port = stcb->rport;
+ sin->sin_addr.s_addr = v4addr->addr;
+ if ((sin->sin_addr.s_addr == INADDR_BROADCAST) ||
+ IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) {
+ bad_address = 1;
+ }
+ if (sin->sin_addr.s_addr == INADDR_ANY)
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_add_ip: adding ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv6addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ sin6 = &store.sin6;
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ sin6->sin6_port = stcb->rport;
+ memcpy((caddr_t)&sin6->sin6_addr, v6addr->addr,
+ sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) {
+ bad_address = 1;
+ }
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_add_ip: adding ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+ default:
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_INVALID_PARAM, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ } /* end switch */
+
+ /* if 0.0.0.0/::0, add the source address instead */
+ if (zero_address && SCTP_BASE_SYSCTL(sctp_nat_friendly)) {
+ sa = src;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_add_ip: using source addr ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, src);
+ }
+ net = NULL;
+ /* add the address */
+ if (bad_address) {
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_INVALID_PARAM, (uint8_t *) aph,
+ aparam_length);
+ } else if (sctp_add_remote_addr(stcb, sa, &net, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE,
+ SCTP_ADDR_DYNAMIC_ADDED) != 0) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_add_ip: error adding address\n");
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_RESOURCE_SHORTAGE, (uint8_t *) aph,
+ aparam_length);
+ } else {
+ if (response_required) {
+ m_reply =
+ sctp_asconf_success_response(aph->correlation_id);
+ }
+ if (net != NULL) {
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_ADD_IP, stcb, 0, sa, SCTP_SO_NOT_LOCKED);
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, stcb->sctp_ep, stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep,
+ stcb, net);
+ if (send_hb) {
+ sctp_send_hb(stcb, net, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ }
+ return (m_reply);
+}
+
+static int
+sctp_asconf_del_remote_addrs_except(struct sctp_tcb *stcb, struct sockaddr *src)
+{
+ struct sctp_nets *src_net, *net, *nnet;
+
+ /* make sure the source address exists as a destination net */
+ src_net = sctp_findnet(stcb, src);
+ if (src_net == NULL) {
+ /* not found */
+ return (-1);
+ }
+
+ /* delete all destination addresses except the source */
+ TAILQ_FOREACH_SAFE(net, &stcb->asoc.nets, sctp_next, nnet) {
+ if (net != src_net) {
+ /* delete this address */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_del_remote_addrs_except: deleting ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1,
+ (struct sockaddr *)&net->ro._l_addr);
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_DELETE_IP, stcb, 0,
+ (struct sockaddr *)&net->ro._l_addr, SCTP_SO_NOT_LOCKED);
+ sctp_remove_net(stcb, net);
+ }
+ }
+ return (0);
+}
+
+static struct mbuf *
+sctp_process_asconf_delete_ip(struct sockaddr *src,
+ struct sctp_asconf_paramhdr *aph,
+ struct sctp_tcb *stcb, int response_required)
+{
+ struct mbuf *m_reply = NULL;
+ union sctp_sockstore store;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type, aparam_length;
+#if defined(INET) || defined(INET6)
+ uint16_t param_length;
+#endif
+ struct sockaddr *sa;
+ int zero_address = 0;
+ int result;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *v4addr;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *v6addr;
+#endif
+
+ aparam_length = ntohs(aph->ph.param_length);
+ if (aparam_length < sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_paramhdr)) {
+ return (NULL);
+ }
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+ if (param_length + sizeof(struct sctp_asconf_paramhdr) != aparam_length) {
+ return (NULL);
+ }
+#endif
+ sa = &store.sa;
+ switch (param_type) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv4addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ sin = &store.sin;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_port = stcb->rport;
+ sin->sin_addr.s_addr = v4addr->addr;
+ if (sin->sin_addr.s_addr == INADDR_ANY)
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_delete_ip: deleting ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv6addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ sin6 = &store.sin6;
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ sin6->sin6_port = stcb->rport;
+ memcpy(&sin6->sin6_addr, v6addr->addr,
+ sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_delete_ip: deleting ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+ default:
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_UNRESOLVABLE_ADDR, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ }
+
+ /* make sure the source address is not being deleted */
+ if (sctp_cmpaddr(sa, src)) {
+ /* trying to delete the source address! */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_delete_ip: tried to delete source addr\n");
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_DELETING_SRC_ADDR, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ }
+
+ /* if deleting 0.0.0.0/::0, delete all addresses except src addr */
+ if (zero_address && SCTP_BASE_SYSCTL(sctp_nat_friendly)) {
+ result = sctp_asconf_del_remote_addrs_except(stcb, src);
+
+ if (result) {
+ /* src address did not exist? */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_delete_ip: src addr does not exist?\n");
+ /* what error to reply with?? */
+ m_reply =
+ sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_REQUEST_REFUSED, (uint8_t *) aph,
+ aparam_length);
+ } else if (response_required) {
+ m_reply =
+ sctp_asconf_success_response(aph->correlation_id);
+ }
+ return (m_reply);
+ }
+
+ /* delete the address */
+ result = sctp_del_remote_addr(stcb, sa);
+ /*
+ * note if result == -2, the address doesn't exist in the asoc but
+ * since it's being deleted anyways, we just ack the delete -- but
+ * this probably means something has already gone awry
+ */
+ if (result == -1) {
+ /* only one address in the asoc */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_delete_ip: tried to delete last IP addr!\n");
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_DELETING_LAST_ADDR, (uint8_t *) aph,
+ aparam_length);
+ } else {
+ if (response_required) {
+ m_reply = sctp_asconf_success_response(aph->correlation_id);
+ }
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_DELETE_IP, stcb, 0, sa, SCTP_SO_NOT_LOCKED);
+ }
+ return (m_reply);
+}
+
+static struct mbuf *
+sctp_process_asconf_set_primary(struct sockaddr *src,
+ struct sctp_asconf_paramhdr *aph,
+ struct sctp_tcb *stcb, int response_required)
+{
+ struct mbuf *m_reply = NULL;
+ union sctp_sockstore store;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type, aparam_length;
+#if defined(INET) || defined(INET6)
+ uint16_t param_length;
+#endif
+ struct sockaddr *sa;
+ int zero_address = 0;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *v4addr;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *v6addr;
+#endif
+
+ aparam_length = ntohs(aph->ph.param_length);
+ if (aparam_length < sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_paramhdr)) {
+ return (NULL);
+ }
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+ if (param_length + sizeof(struct sctp_asconf_paramhdr) != aparam_length) {
+ return (NULL);
+ }
+#endif
+ sa = &store.sa;
+ switch (param_type) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv4addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ sin = &store.sin;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_addr.s_addr = v4addr->addr;
+ if (sin->sin_addr.s_addr == INADDR_ANY)
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_set_primary: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv6addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ sin6 = &store.sin6;
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ memcpy((caddr_t)&sin6->sin6_addr, v6addr->addr,
+ sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_set_primary: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+ default:
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_UNRESOLVABLE_ADDR, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ }
+
+ /* if 0.0.0.0/::0, use the source address instead */
+ if (zero_address && SCTP_BASE_SYSCTL(sctp_nat_friendly)) {
+ sa = src;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_set_primary: using source addr ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, src);
+ }
+ /* set the primary address */
+ if (sctp_set_primary_addr(stcb, sa, NULL) == 0) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_set_primary: primary address set\n");
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_SET_PRIMARY, stcb, 0, sa, SCTP_SO_NOT_LOCKED);
+ if ((stcb->asoc.primary_destination->dest_state & SCTP_ADDR_REACHABLE) &&
+ ((stcb->asoc.primary_destination->dest_state & SCTP_ADDR_PF) == 0) &&
+ (stcb->asoc.alternate != NULL)) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ if (response_required) {
+ m_reply = sctp_asconf_success_response(aph->correlation_id);
+ }
+ /* Mobility adaptation.
+ Ideally, when the reception of SET PRIMARY with DELETE IP
+ ADDRESS of the previous primary destination, unacknowledged
+ DATA are retransmitted immediately to the new primary
+ destination for seamless handover.
+ If the destination is UNCONFIRMED and marked to REQ_PRIM,
+ The retransmission occur when reception of the
+ HEARTBEAT-ACK. (See sctp_handle_heartbeat_ack in
+ sctp_input.c)
+ Also, when change of the primary destination, it is better
+ that all subsequent new DATA containing already queued DATA
+ are transmitted to the new primary destination. (by micchie)
+ */
+ if ((sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE) ||
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) &&
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_PRIM_DELETED) &&
+ (stcb->asoc.primary_destination->dest_state &
+ SCTP_ADDR_UNCONFIRMED) == 0) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_PRIM_DELETED,
+ stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_1);
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_assoc_immediate_retrans(stcb,
+ stcb->asoc.primary_destination);
+ }
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE)) {
+ sctp_move_chunks_from_net(stcb,
+ stcb->asoc.deleted_primary);
+ }
+ sctp_delete_prim_timer(stcb->sctp_ep, stcb);
+ }
+ } else {
+ /* couldn't set the requested primary address! */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_set_primary: set primary failed!\n");
+ /* must have been an invalid address, so report */
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_UNRESOLVABLE_ADDR, (uint8_t *) aph,
+ aparam_length);
+ }
+
+ return (m_reply);
+}
+
+/*
+ * handles an ASCONF chunk.
+ * if all parameters are processed ok, send a plain (empty) ASCONF-ACK
+ */
+void
+sctp_handle_asconf(struct mbuf *m, unsigned int offset,
+ struct sockaddr *src,
+ struct sctp_asconf_chunk *cp, struct sctp_tcb *stcb,
+ int first)
+{
+ struct sctp_association *asoc;
+ uint32_t serial_num;
+ struct mbuf *n, *m_ack, *m_result, *m_tail;
+ struct sctp_asconf_ack_chunk *ack_cp;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_ipv6addr_param *p_addr;
+ unsigned int asconf_limit, cnt;
+ int error = 0; /* did an error occur? */
+
+ /* asconf param buffer */
+ uint8_t aparam_buf[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_asconf_ack *ack, *ack_next;
+
+ /* verify minimum length */
+ if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_asconf_chunk)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: chunk too small = %xh\n",
+ ntohs(cp->ch.chunk_length));
+ return;
+ }
+ asoc = &stcb->asoc;
+ serial_num = ntohl(cp->serial_number);
+
+ if (SCTP_TSN_GE(asoc->asconf_seq_in, serial_num)) {
+ /* got a duplicate ASCONF */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: got duplicate serial number = %xh\n",
+ serial_num);
+ return;
+ } else if (serial_num != (asoc->asconf_seq_in + 1)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: incorrect serial number = %xh (expected next = %xh)\n",
+ serial_num, asoc->asconf_seq_in + 1);
+ return;
+ }
+
+ /* it's the expected "next" sequence number, so process it */
+ asoc->asconf_seq_in = serial_num; /* update sequence */
+ /* get length of all the param's in the ASCONF */
+ asconf_limit = offset + ntohs(cp->ch.chunk_length);
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: asconf_limit=%u, sequence=%xh\n",
+ asconf_limit, serial_num);
+
+ if (first) {
+ /* delete old cache */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,"handle_asconf: Now processing first ASCONF. Try to delete old cache\n");
+
+ TAILQ_FOREACH_SAFE(ack, &asoc->asconf_ack_sent, next, ack_next) {
+ if (ack->serial_number == serial_num)
+ break;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,"handle_asconf: delete old(%u) < first(%u)\n",
+ ack->serial_number, serial_num);
+ TAILQ_REMOVE(&asoc->asconf_ack_sent, ack, next);
+ if (ack->data != NULL) {
+ sctp_m_freem(ack->data);
+ }
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asconf_ack), ack);
+ }
+ }
+
+ m_ack = sctp_get_mbuf_for_msg(sizeof(struct sctp_asconf_ack_chunk), 0,
+ M_NOWAIT, 1, MT_DATA);
+ if (m_ack == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: couldn't get mbuf!\n");
+ return;
+ }
+ m_tail = m_ack; /* current reply chain's tail */
+
+ /* fill in ASCONF-ACK header */
+ ack_cp = mtod(m_ack, struct sctp_asconf_ack_chunk *);
+ ack_cp->ch.chunk_type = SCTP_ASCONF_ACK;
+ ack_cp->ch.chunk_flags = 0;
+ ack_cp->serial_number = htonl(serial_num);
+ /* set initial lengths (eg. just an ASCONF-ACK), ntohx at the end! */
+ SCTP_BUF_LEN(m_ack) = sizeof(struct sctp_asconf_ack_chunk);
+ ack_cp->ch.chunk_length = sizeof(struct sctp_asconf_ack_chunk);
+
+ /* skip the lookup address parameter */
+ offset += sizeof(struct sctp_asconf_chunk);
+ p_addr = (struct sctp_ipv6addr_param *)sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr), (uint8_t *)&aparam_buf);
+ if (p_addr == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: couldn't get lookup addr!\n");
+ /* respond with a missing/invalid mandatory parameter error */
+ sctp_m_freem(m_ack);
+ return;
+ }
+ /* skip lookup addr */
+ offset += SCTP_SIZE32(ntohs(p_addr->ph.param_length));
+ /* get pointer to first asconf param in ASCONF */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, sizeof(struct sctp_asconf_paramhdr), (uint8_t *)&aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "Empty ASCONF received?\n");
+ goto send_reply;
+ }
+ /* process through all parameters */
+ cnt = 0;
+ while (aph != NULL) {
+ unsigned int param_length, param_type;
+
+ param_type = ntohs(aph->ph.param_type);
+ param_length = ntohs(aph->ph.param_length);
+ if (offset + param_length > asconf_limit) {
+ /* parameter goes beyond end of chunk! */
+ sctp_m_freem(m_ack);
+ return;
+ }
+ m_result = NULL;
+
+ if (param_length > sizeof(aparam_buf)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: param length (%u) larger than buffer size!\n", param_length);
+ sctp_m_freem(m_ack);
+ return;
+ }
+ if (param_length < sizeof(struct sctp_asconf_paramhdr)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: param length (%u) too short\n", param_length);
+ sctp_m_freem(m_ack);
+ return;
+ }
+ /* get the entire parameter */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, param_length, aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: couldn't get entire param\n");
+ sctp_m_freem(m_ack);
+ return;
+ }
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ m_result = sctp_process_asconf_add_ip(src, aph, stcb,
+ (cnt < SCTP_BASE_SYSCTL(sctp_hb_maxburst)), error);
+ cnt++;
+ break;
+ case SCTP_DEL_IP_ADDRESS:
+ m_result = sctp_process_asconf_delete_ip(src, aph, stcb,
+ error);
+ break;
+ case SCTP_ERROR_CAUSE_IND:
+ /* not valid in an ASCONF chunk */
+ break;
+ case SCTP_SET_PRIM_ADDR:
+ m_result = sctp_process_asconf_set_primary(src, aph,
+ stcb, error);
+ break;
+ case SCTP_NAT_VTAGS:
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: sees a NAT VTAG state parameter\n");
+ break;
+ case SCTP_SUCCESS_REPORT:
+ /* not valid in an ASCONF chunk */
+ break;
+ case SCTP_ULP_ADAPTATION:
+ /* FIX */
+ break;
+ default:
+ if ((param_type & 0x8000) == 0) {
+ /* Been told to STOP at this param */
+ asconf_limit = offset;
+ /*
+ * FIX FIX - We need to call
+ * sctp_arethere_unrecognized_parameters()
+ * to get a operr and send it for any
+ * param's with the 0x4000 bit set OR do it
+ * here ourselves... note we still must STOP
+ * if the 0x8000 bit is clear.
+ */
+ }
+ /* unknown/invalid param type */
+ break;
+ } /* switch */
+
+ /* add any (error) result to the reply mbuf chain */
+ if (m_result != NULL) {
+ SCTP_BUF_NEXT(m_tail) = m_result;
+ m_tail = m_result;
+ ack_cp->ch.chunk_length += SCTP_BUF_LEN(m_result);
+ /* set flag to force success reports */
+ error = 1;
+ }
+ offset += SCTP_SIZE32(param_length);
+ /* update remaining ASCONF message length to process */
+ if (offset >= asconf_limit) {
+ /* no more data in the mbuf chain */
+ break;
+ }
+ /* get pointer to next asconf param */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_asconf_paramhdr),
+ (uint8_t *)&aparam_buf);
+ if (aph == NULL) {
+ /* can't get an asconf paramhdr */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: can't get asconf param hdr!\n");
+ /* FIX ME - add error here... */
+ }
+ }
+
+ send_reply:
+ ack_cp->ch.chunk_length = htons(ack_cp->ch.chunk_length);
+ /* save the ASCONF-ACK reply */
+ ack = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_asconf_ack),
+ struct sctp_asconf_ack);
+ if (ack == NULL) {
+ sctp_m_freem(m_ack);
+ return;
+ }
+ ack->serial_number = serial_num;
+ ack->last_sent_to = NULL;
+ ack->data = m_ack;
+ ack->len = 0;
+ for (n = m_ack; n != NULL; n = SCTP_BUF_NEXT(n)) {
+ ack->len += SCTP_BUF_LEN(n);
+ }
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_ack_sent, ack, next);
+
+ /* see if last_control_chunk_from is set properly (use IP src addr) */
+ if (stcb->asoc.last_control_chunk_from == NULL) {
+ /*
+ * this could happen if the source address was just newly
+ * added
+ */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: looking up net for IP source address\n");
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "Looking for IP source: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, src);
+ /* look up the from address */
+ stcb->asoc.last_control_chunk_from = sctp_findnet(stcb, src);
+#ifdef SCTP_DEBUG
+ if (stcb->asoc.last_control_chunk_from == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: IP source address not found?!\n");
+ }
+#endif
+ }
+}
+
+/*
+ * does the address match? returns 0 if not, 1 if so
+ */
+static uint32_t
+sctp_asconf_addr_match(struct sctp_asconf_addr *aa, struct sockaddr *sa)
+{
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* XXX scopeid */
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+
+ if ((aa->ap.addrp.ph.param_type == SCTP_IPV6_ADDRESS) &&
+ (memcmp(&aa->ap.addrp.addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+
+ if ((aa->ap.addrp.ph.param_type == SCTP_IPV4_ADDRESS) &&
+ (memcmp(&aa->ap.addrp.addr, &sin->sin_addr,
+ sizeof(struct in_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+ return (0);
+}
+
+/*
+ * does the address match? returns 0 if not, 1 if so
+ */
+static uint32_t
+sctp_addr_match(struct sctp_paramhdr *ph, struct sockaddr *sa)
+{
+#if defined(INET) || defined(INET6)
+ uint16_t param_type, param_length;
+
+ param_type = ntohs(ph->param_type);
+ param_length = ntohs(ph->param_length);
+#endif
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* XXX scopeid */
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+ struct sctp_ipv6addr_param *v6addr;
+
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ if ((param_type == SCTP_IPV6_ADDRESS) &&
+ (param_length == sizeof(struct sctp_ipv6addr_param)) &&
+ (memcmp(&v6addr->addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+ struct sctp_ipv4addr_param *v4addr;
+
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ if ((param_type == SCTP_IPV4_ADDRESS) &&
+ (param_length == sizeof(struct sctp_ipv4addr_param)) &&
+ (memcmp(&v4addr->addr, &sin->sin_addr,
+ sizeof(struct in_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+ return (0);
+}
+/*
+ * Cleanup for non-responded/OP ERR'd ASCONF
+ */
+void
+sctp_asconf_cleanup(struct sctp_tcb *stcb)
+{
+ /*
+ * clear out any existing asconfs going out
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_2);
+ stcb->asoc.asconf_seq_out_acked = stcb->asoc.asconf_seq_out;
+ /* remove the old ASCONF on our outbound queue */
+ sctp_toss_old_asconf(stcb);
+}
+
+/*
+ * cleanup any cached source addresses that may be topologically
+ * incorrect after a new address has been added to this interface.
+ */
+static void
+sctp_asconf_nets_cleanup(struct sctp_tcb *stcb, struct sctp_ifn *ifn)
+{
+ struct sctp_nets *net;
+
+ /*
+ * Ideally, we want to only clear cached routes and source addresses
+ * that are topologically incorrect. But since there is no easy way
+ * to know whether the newly added address on the ifn would cause a
+ * routing change (i.e. a new egress interface would be chosen)
+ * without doing a new routing lookup and source address selection,
+ * we will (for now) just flush any cached route using a different
+ * ifn (and cached source addrs) and let output re-choose them during
+ * the next send on that net.
+ */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /*
+ * clear any cached route (and cached source address) if the
+ * route's interface is NOT the same as the address change.
+ * If it's the same interface, just clear the cached source
+ * address.
+ */
+ if (SCTP_ROUTE_HAS_VALID_IFN(&net->ro) &&
+ ((ifn == NULL) ||
+ (SCTP_GET_IF_INDEX_FROM_ROUTE(&net->ro) != ifn->ifn_index))) {
+ /* clear any cached route */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+#endif
+ }
+ /* clear any cached source address */
+ if (net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ }
+}
+
+void
+sctp_assoc_immediate_retrans(struct sctp_tcb *stcb, struct sctp_nets *dstnet)
+{
+ int error;
+
+ if (dstnet->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ return;
+ }
+ if (stcb->asoc.deleted_primary == NULL) {
+ return;
+ }
+
+ if (!TAILQ_EMPTY(&stcb->asoc.sent_queue)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "assoc_immediate_retrans: Deleted primary is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, &stcb->asoc.deleted_primary->ro._l_addr.sa);
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "Current Primary is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, &stcb->asoc.primary_destination->ro._l_addr.sa);
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb,
+ stcb->asoc.deleted_primary,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_3);
+ stcb->asoc.num_send_timers_up--;
+ if (stcb->asoc.num_send_timers_up < 0) {
+ stcb->asoc.num_send_timers_up = 0;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ error = sctp_t3rxt_timer(stcb->sctp_ep, stcb,
+ stcb->asoc.deleted_primary);
+ if (error) {
+ SCTP_INP_DECR_REF(stcb->sctp_ep);
+ return;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, stcb->sctp_ep, stcb, stcb->asoc.deleted_primary);
+#endif
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ if ((stcb->asoc.num_send_timers_up == 0) &&
+ (stcb->asoc.sent_queue_cnt > 0)) {
+ struct sctp_tmit_chunk *chk;
+
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (chk != NULL) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, chk->whoTo);
+ }
+ }
+ }
+ return;
+}
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+static int
+sctp_asconf_queue_mgmt(struct sctp_tcb *, struct sctp_ifa *, uint16_t);
+
+void
+sctp_net_immediate_retrans(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ struct sctp_tmit_chunk *chk;
+
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "net_immediate_retrans: RTO is %d\n", net->RTO);
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_4);
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb, net);
+ net->error_count = 0;
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->whoTo == net) {
+ if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ sctp_flight_size_decrease(chk);
+ sctp_total_flight_decrease(stcb, chk);
+ net->marked_retrans++;
+ stcb->asoc.marked_retrans++;
+ }
+ }
+ }
+ if (net->marked_retrans) {
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ }
+}
+
+static void
+sctp_path_check_and_react(struct sctp_tcb *stcb, struct sctp_ifa *newifa)
+{
+ struct sctp_nets *net;
+ int addrnum, changed;
+
+ /* If number of local valid addresses is 1, the valid address is
+ probably newly added address.
+ Several valid addresses in this association. A source address
+ may not be changed. Additionally, they can be configured on a
+ same interface as "alias" addresses. (by micchie)
+ */
+ addrnum = sctp_local_addr_count(stcb);
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "p_check_react(): %d local addresses\n",
+ addrnum);
+ if (addrnum == 1) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* clear any cached route and source address */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ if (net->ro.ro_rt) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+ if (net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ /* Retransmit unacknowledged DATA chunks immediately */
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_net_immediate_retrans(stcb, net);
+ }
+ /* also, SET PRIMARY is maybe already sent */
+ }
+ return;
+ }
+
+ /* Multiple local addresses exist in the association. */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* clear any cached route and source address */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ if (net->ro.ro_rt) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+ if (net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ /* Check if the nexthop is corresponding to the new address.
+ If the new address is corresponding to the current nexthop,
+ the path will be changed.
+ If the new address is NOT corresponding to the current
+ nexthop, the path will not be changed.
+ */
+ SCTP_RTALLOC((sctp_route_t *)&net->ro,
+ stcb->sctp_ep->def_vrf_id,
+ stcb->sctp_ep->fibnum);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net->ro.ro_nh == NULL)
+#else
+ if (net->ro.ro_rt == NULL)
+#endif
+ continue;
+
+ changed = 0;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (sctp_v4src_match_nexthop(newifa, (sctp_route_t *)&net->ro)) {
+ changed = 1;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (sctp_v6src_match_nexthop(
+ &newifa->address.sin6, (sctp_route_t *)&net->ro)) {
+ changed = 1;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ /* if the newly added address does not relate routing
+ information, we skip.
+ */
+ if (changed == 0)
+ continue;
+ /* Retransmit unacknowledged DATA chunks immediately */
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_net_immediate_retrans(stcb, net);
+ }
+ /* Send SET PRIMARY for this new address */
+ if (net == stcb->asoc.primary_destination) {
+ (void)sctp_asconf_queue_mgmt(stcb, newifa,
+ SCTP_SET_PRIM_ADDR);
+ }
+ }
+}
+#endif
+
+/*
+ * process an ADD/DELETE IP ack from peer.
+ * addr: corresponding sctp_ifa to the address being added/deleted.
+ * type: SCTP_ADD_IP_ADDRESS or SCTP_DEL_IP_ADDRESS.
+ * flag: 1=success, 0=failure.
+ */
+static void
+sctp_asconf_addr_mgmt_ack(struct sctp_tcb *stcb, struct sctp_ifa *addr, uint32_t flag)
+{
+ /*
+ * do the necessary asoc list work- if we get a failure indication,
+ * leave the address on the assoc's restricted list. If we get a
+ * success indication, remove the address from the restricted list.
+ */
+ /*
+ * Note: this will only occur for ADD_IP_ADDRESS, since
+ * DEL_IP_ADDRESS is never actually added to the list...
+ */
+ if (flag) {
+ /* success case, so remove from the restricted list */
+ sctp_del_local_addr_restricted(stcb, addr);
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE) ||
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_path_check_and_react(stcb, addr);
+ return;
+ }
+#endif
+ /* clear any cached/topologically incorrect source addresses */
+ sctp_asconf_nets_cleanup(stcb, addr->ifn_p);
+ }
+ /* else, leave it on the list */
+}
+
+/*
+ * add an asconf add/delete/set primary IP address parameter to the queue.
+ * type = SCTP_ADD_IP_ADDRESS, SCTP_DEL_IP_ADDRESS, SCTP_SET_PRIM_ADDR.
+ * returns 0 if queued, -1 if not queued/removed.
+ * NOTE: if adding, but a delete for the same address is already scheduled
+ * (and not yet sent out), simply remove it from queue. Same for deleting
+ * an address already scheduled for add. If a duplicate operation is found,
+ * ignore the new one.
+ */
+static int
+sctp_asconf_queue_mgmt(struct sctp_tcb *stcb, struct sctp_ifa *ifa,
+ uint16_t type)
+{
+ struct sctp_asconf_addr *aa, *aa_next;
+
+ /* make sure the request isn't already in the queue */
+ TAILQ_FOREACH_SAFE(aa, &stcb->asoc.asconf_queue, next, aa_next) {
+ /* address match? */
+ if (sctp_asconf_addr_match(aa, &ifa->address.sa) == 0)
+ continue;
+ /* is the request already in queue but not sent?
+ * pass the request already sent in order to resolve the following case:
+ * 1. arrival of ADD, then sent
+ * 2. arrival of DEL. we can't remove the ADD request already sent
+ * 3. arrival of ADD
+ */
+ if (aa->ap.aph.ph.param_type == type && aa->sent == 0) {
+ return (-1);
+ }
+ /* is the negative request already in queue, and not sent */
+ if ((aa->sent == 0) && (type == SCTP_ADD_IP_ADDRESS) &&
+ (aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS)) {
+ /* add requested, delete already queued */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next);
+ /* remove the ifa from the restricted list */
+ sctp_del_local_addr_restricted(stcb, ifa);
+ /* free the asconf param */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "asconf_queue_mgmt: add removes queued entry\n");
+ return (-1);
+ }
+ if ((aa->sent == 0) && (type == SCTP_DEL_IP_ADDRESS) &&
+ (aa->ap.aph.ph.param_type == SCTP_ADD_IP_ADDRESS)) {
+ /* delete requested, add already queued */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next);
+ /* remove the aa->ifa from the restricted list */
+ sctp_del_local_addr_restricted(stcb, aa->ifa);
+ /* free the asconf param */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "asconf_queue_mgmt: delete removes queued entry\n");
+ return (-1);
+ }
+ } /* for each aa */
+
+ /* adding new request to the queue */
+ SCTP_MALLOC(aa, struct sctp_asconf_addr *, sizeof(*aa),
+ SCTP_M_ASC_ADDR);
+ if (aa == NULL) {
+ /* didn't get memory */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "asconf_queue_mgmt: failed to get memory!\n");
+ return (-1);
+ }
+ aa->special_del = 0;
+ /* fill in asconf address parameter fields */
+ /* top level elements are "networked" during send */
+ aa->ap.aph.ph.param_type = type;
+ aa->ifa = ifa;
+ atomic_add_int(&ifa->refcount, 1);
+ /* correlation_id filled in during send routine later... */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = &ifa->address.sin6;
+ aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv6addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) +
+ sizeof(struct sctp_ipv6addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr));
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = &ifa->address.sin;
+ aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv4addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) +
+ sizeof(struct sctp_ipv4addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin->sin_addr,
+ sizeof(struct in_addr));
+ break;
+ }
+#endif
+ default:
+ /* invalid family! */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ sctp_free_ifa(ifa);
+ return (-1);
+ }
+ aa->sent = 0; /* clear sent flag */
+
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+#ifdef SCTP_DEBUG
+ if (SCTP_BASE_SYSCTL(sctp_debug_on) & SCTP_DEBUG_ASCONF2) {
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ SCTP_PRINTF("asconf_queue_mgmt: inserted asconf ADD_IP_ADDRESS: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, &ifa->address.sa);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ SCTP_PRINTF("asconf_queue_mgmt: appended asconf DEL_IP_ADDRESS: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, &ifa->address.sa);
+ } else {
+ SCTP_PRINTF("asconf_queue_mgmt: appended asconf SET_PRIM_ADDR: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, &ifa->address.sa);
+ }
+ }
+#endif
+
+ return (0);
+}
+
+/*
+ * add an asconf operation for the given ifa and type.
+ * type = SCTP_ADD_IP_ADDRESS, SCTP_DEL_IP_ADDRESS, SCTP_SET_PRIM_ADDR.
+ * returns 0 if completed, -1 if not completed, 1 if immediate send is
+ * advisable.
+ */
+static int
+sctp_asconf_queue_add(struct sctp_tcb *stcb, struct sctp_ifa *ifa,
+ uint16_t type)
+{
+ uint32_t status;
+ int pending_delete_queued = 0;
+ int last;
+
+ /* see if peer supports ASCONF */
+ if (stcb->asoc.asconf_supported == 0) {
+ return (-1);
+ }
+
+ /*
+ * if this is deleting the last address from the assoc, mark it as
+ * pending.
+ */
+ if ((type == SCTP_DEL_IP_ADDRESS) && !stcb->asoc.asconf_del_pending) {
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ last = (sctp_local_addr_count(stcb) == 0);
+ } else {
+ last = (sctp_local_addr_count(stcb) == 1);
+ }
+ if (last) {
+ /* set the pending delete info only */
+ stcb->asoc.asconf_del_pending = 1;
+ stcb->asoc.asconf_addr_del_pending = ifa;
+ atomic_add_int(&ifa->refcount, 1);
+ SCTPDBG(SCTP_DEBUG_ASCONF2,
+ "asconf_queue_add: mark delete last address pending\n");
+ return (-1);
+ }
+ }
+
+ /* queue an asconf parameter */
+ status = sctp_asconf_queue_mgmt(stcb, ifa, type);
+
+ /*
+ * if this is an add, and there is a delete also pending (i.e. the
+ * last local address is being changed), queue the pending delete too.
+ */
+ if ((type == SCTP_ADD_IP_ADDRESS) && stcb->asoc.asconf_del_pending && (status == 0)) {
+ /* queue in the pending delete */
+ if (sctp_asconf_queue_mgmt(stcb,
+ stcb->asoc.asconf_addr_del_pending,
+ SCTP_DEL_IP_ADDRESS) == 0) {
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "asconf_queue_add: queuing pending delete\n");
+ pending_delete_queued = 1;
+ /* clear out the pending delete info */
+ stcb->asoc.asconf_del_pending = 0;
+ sctp_free_ifa(stcb->asoc.asconf_addr_del_pending);
+ stcb->asoc.asconf_addr_del_pending = NULL;
+ }
+ }
+
+ if (pending_delete_queued) {
+ struct sctp_nets *net;
+ /*
+ * since we know that the only/last address is now being
+ * changed in this case, reset the cwnd/rto on all nets to
+ * start as a new address and path. Also clear the error
+ * counts to give the assoc the best chance to complete the
+ * address change.
+ */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb,
+ net);
+ net->RTO = 0;
+ net->error_count = 0;
+ }
+ stcb->asoc.overall_error_count = 0;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_ASCONF,
+ __LINE__);
+ }
+
+ /* queue in an advisory set primary too */
+ (void)sctp_asconf_queue_mgmt(stcb, ifa, SCTP_SET_PRIM_ADDR);
+ /* let caller know we should send this out immediately */
+ status = 1;
+ }
+ return (status);
+}
+
+/*-
+ * add an asconf delete IP address parameter to the queue by sockaddr and
+ * possibly with no sctp_ifa available. This is only called by the routine
+ * that checks the addresses in an INIT-ACK against the current address list.
+ * returns 0 if completed, non-zero if not completed.
+ * NOTE: if an add is already scheduled (and not yet sent out), simply
+ * remove it from queue. If a duplicate operation is found, ignore the
+ * new one.
+ */
+static int
+sctp_asconf_queue_sa_delete(struct sctp_tcb *stcb, struct sockaddr *sa)
+{
+ struct sctp_ifa *ifa;
+ struct sctp_asconf_addr *aa, *aa_next;
+
+ if (stcb == NULL) {
+ return (-1);
+ }
+ /* see if peer supports ASCONF */
+ if (stcb->asoc.asconf_supported == 0) {
+ return (-1);
+ }
+ /* make sure the request isn't already in the queue */
+ TAILQ_FOREACH_SAFE(aa, &stcb->asoc.asconf_queue, next, aa_next) {
+ /* address match? */
+ if (sctp_asconf_addr_match(aa, sa) == 0)
+ continue;
+ /* is the request already in queue (sent or not) */
+ if (aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS) {
+ return (-1);
+ }
+ /* is the negative request already in queue, and not sent */
+ if (aa->sent == 1)
+ continue;
+ if (aa->ap.aph.ph.param_type == SCTP_ADD_IP_ADDRESS) {
+ /* add already queued, so remove existing entry */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next);
+ sctp_del_local_addr_restricted(stcb, aa->ifa);
+ /* free the entry */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ return (-1);
+ }
+ } /* for each aa */
+
+ /* find any existing ifa-- NOTE ifa CAN be allowed to be NULL */
+ ifa = sctp_find_ifa_by_addr(sa, stcb->asoc.vrf_id, SCTP_ADDR_NOT_LOCKED);
+
+ /* adding new request to the queue */
+ SCTP_MALLOC(aa, struct sctp_asconf_addr *, sizeof(*aa),
+ SCTP_M_ASC_ADDR);
+ if (aa == NULL) {
+ /* didn't get memory */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_asconf_queue_sa_delete: failed to get memory!\n");
+ return (-1);
+ }
+ aa->special_del = 0;
+ /* fill in asconf address parameter fields */
+ /* top level elements are "networked" during send */
+ aa->ap.aph.ph.param_type = SCTP_DEL_IP_ADDRESS;
+ aa->ifa = ifa;
+ if (ifa)
+ atomic_add_int(&ifa->refcount, 1);
+ /* correlation_id filled in during send routine later... */
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* IPv6 address */
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)sa;
+ aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv6addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_ipv6addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr));
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ /* IPv4 address */
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+
+ aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv4addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_ipv4addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin->sin_addr,
+ sizeof(struct in_addr));
+ break;
+ }
+#endif
+ default:
+ /* invalid family! */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ if (ifa)
+ sctp_free_ifa(ifa);
+ return (-1);
+ }
+ aa->sent = 0; /* clear sent flag */
+
+ /* delete goes to the back of the queue */
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+
+ /* sa_ignore MEMLEAK {memory is put on the tailq} */
+ return (0);
+}
+
+/*
+ * find a specific asconf param on our "sent" queue
+ */
+static struct sctp_asconf_addr *
+sctp_asconf_find_param(struct sctp_tcb *stcb, uint32_t correlation_id)
+{
+ struct sctp_asconf_addr *aa;
+
+ TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) {
+ if (aa->ap.aph.correlation_id == correlation_id &&
+ aa->sent == 1) {
+ /* found it */
+ return (aa);
+ }
+ }
+ /* didn't find it */
+ return (NULL);
+}
+
+/*
+ * process an SCTP_ERROR_CAUSE_IND for a ASCONF-ACK parameter and do
+ * notifications based on the error response
+ */
+static void
+sctp_asconf_process_error(struct sctp_tcb *stcb SCTP_UNUSED,
+ struct sctp_asconf_paramhdr *aph)
+{
+ struct sctp_error_cause *eh;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type;
+ uint16_t error_code;
+
+ eh = (struct sctp_error_cause *)(aph + 1);
+ ph = (struct sctp_paramhdr *)(eh + 1);
+ /* validate lengths */
+ if (htons(eh->length) + sizeof(struct sctp_error_cause) >
+ htons(aph->ph.param_length)) {
+ /* invalid error cause length */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_process_error: cause element too long\n");
+ return;
+ }
+ if (htons(ph->param_length) + sizeof(struct sctp_paramhdr) >
+ htons(eh->length)) {
+ /* invalid included TLV length */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_process_error: included TLV too long\n");
+ return;
+ }
+ /* which error code ? */
+ error_code = ntohs(eh->code);
+ param_type = ntohs(aph->ph.param_type);
+ /* FIX: this should go back up the REMOTE_ERROR ULP notify */
+ switch (error_code) {
+ case SCTP_CAUSE_RESOURCE_SHORTAGE:
+ /* we allow ourselves to "try again" for this error */
+ break;
+ default:
+ /* peer can't handle it... */
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ case SCTP_DEL_IP_ADDRESS:
+ case SCTP_SET_PRIM_ADDR:
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/*
+ * process an asconf queue param.
+ * aparam: parameter to process, will be removed from the queue.
+ * flag: 1=success case, 0=failure case
+ */
+static void
+sctp_asconf_process_param_ack(struct sctp_tcb *stcb,
+ struct sctp_asconf_addr *aparam, uint32_t flag)
+{
+ uint16_t param_type;
+
+ /* process this param */
+ param_type = aparam->ap.aph.ph.param_type;
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_param_ack: added IP address\n");
+ sctp_asconf_addr_mgmt_ack(stcb, aparam->ifa, flag);
+ break;
+ case SCTP_DEL_IP_ADDRESS:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_param_ack: deleted IP address\n");
+ /* nothing really to do... lists already updated */
+ break;
+ case SCTP_SET_PRIM_ADDR:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_param_ack: set primary IP address\n");
+ /* nothing to do... peer may start using this addr */
+ break;
+ default:
+ /* should NEVER happen */
+ break;
+ }
+
+ /* remove the param and free it */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aparam, next);
+ if (aparam->ifa)
+ sctp_free_ifa(aparam->ifa);
+ SCTP_FREE(aparam, SCTP_M_ASC_ADDR);
+}
+
+/*
+ * cleanup from a bad asconf ack parameter
+ */
+static void
+sctp_asconf_ack_clear(struct sctp_tcb *stcb SCTP_UNUSED)
+{
+ /* assume peer doesn't really know how to do asconfs */
+ /* XXX we could free the pending queue here */
+
+}
+
+void
+sctp_handle_asconf_ack(struct mbuf *m, int offset,
+ struct sctp_asconf_ack_chunk *cp, struct sctp_tcb *stcb,
+ struct sctp_nets *net, int *abort_no_unlock)
+{
+ struct sctp_association *asoc;
+ uint32_t serial_num;
+ uint16_t ack_length;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_asconf_addr *aa, *aa_next;
+ uint32_t last_error_id = 0; /* last error correlation id */
+ uint32_t id;
+ struct sctp_asconf_addr *ap;
+
+ /* asconf param buffer */
+ uint8_t aparam_buf[SCTP_PARAM_BUFFER_SIZE];
+
+ /* verify minimum length */
+ if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_asconf_ack_chunk)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf_ack: chunk too small = %xh\n",
+ ntohs(cp->ch.chunk_length));
+ return;
+ }
+ asoc = &stcb->asoc;
+ serial_num = ntohl(cp->serial_number);
+
+ /*
+ * NOTE: we may want to handle this differently- currently, we will
+ * abort when we get an ack for the expected serial number + 1 (eg.
+ * we didn't send it), process an ack normally if it is the expected
+ * serial number, and re-send the previous ack for *ALL* other
+ * serial numbers
+ */
+
+ /*
+ * if the serial number is the next expected, but I didn't send it,
+ * abort the asoc, since someone probably just hijacked us...
+ */
+ if (serial_num == (asoc->asconf_seq_out + 1)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf_ack: got unexpected next serial number! Aborting asoc!\n");
+ SCTP_SNPRINTF(msg, sizeof(msg), "Never sent serial number %8.8x", serial_num);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_no_unlock = 1;
+ return;
+ }
+ if (serial_num != asoc->asconf_seq_out_acked + 1) {
+ /* got a duplicate/unexpected ASCONF-ACK */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf_ack: got duplicate/unexpected serial number = %xh (expected = %xh)\n",
+ serial_num, asoc->asconf_seq_out_acked + 1);
+ return;
+ }
+
+ if (serial_num == asoc->asconf_seq_out - 1) {
+ /* stop our timer */
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_5);
+ }
+
+ /* process the ASCONF-ACK contents */
+ ack_length = ntohs(cp->ch.chunk_length) -
+ sizeof(struct sctp_asconf_ack_chunk);
+ offset += sizeof(struct sctp_asconf_ack_chunk);
+ /* process through all parameters */
+ while (ack_length >= sizeof(struct sctp_asconf_paramhdr)) {
+ unsigned int param_length, param_type;
+
+ /* get pointer to next asconf parameter */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_asconf_paramhdr), aparam_buf);
+ if (aph == NULL) {
+ /* can't get an asconf paramhdr */
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ param_type = ntohs(aph->ph.param_type);
+ param_length = ntohs(aph->ph.param_length);
+ if (param_length > ack_length) {
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ if (param_length < sizeof(struct sctp_asconf_paramhdr)) {
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ /* get the complete parameter... */
+ if (param_length > sizeof(aparam_buf)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "param length (%u) larger than buffer size!\n", param_length);
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, param_length, aparam_buf);
+ if (aph == NULL) {
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ /* correlation_id is transparent to peer, no ntohl needed */
+ id = aph->correlation_id;
+
+ switch (param_type) {
+ case SCTP_ERROR_CAUSE_IND:
+ last_error_id = id;
+ /* find the corresponding asconf param in our queue */
+ ap = sctp_asconf_find_param(stcb, id);
+ if (ap == NULL) {
+ /* hmm... can't find this in our queue! */
+ break;
+ }
+ /* process the parameter, failed flag */
+ sctp_asconf_process_param_ack(stcb, ap, 0);
+ /* process the error response */
+ sctp_asconf_process_error(stcb, aph);
+ break;
+ case SCTP_SUCCESS_REPORT:
+ /* find the corresponding asconf param in our queue */
+ ap = sctp_asconf_find_param(stcb, id);
+ if (ap == NULL) {
+ /* hmm... can't find this in our queue! */
+ break;
+ }
+ /* process the parameter, success flag */
+ sctp_asconf_process_param_ack(stcb, ap, 1);
+ break;
+ default:
+ break;
+ } /* switch */
+
+ /* update remaining ASCONF-ACK message length to process */
+ if (ack_length > SCTP_SIZE32(param_length)) {
+ ack_length -= SCTP_SIZE32(param_length);
+ } else {
+ break;
+ }
+ offset += SCTP_SIZE32(param_length);
+ } /* while */
+
+ /*
+ * if there are any "sent" params still on the queue, these are
+ * implicitly "success", or "failed" (if we got an error back) ...
+ * so process these appropriately
+ *
+ * we assume that the correlation_id's are monotonically increasing
+ * beginning from 1 and that we don't have *that* many outstanding
+ * at any given time
+ */
+ if (last_error_id == 0)
+ last_error_id--; /* set to "max" value */
+ TAILQ_FOREACH_SAFE(aa, &stcb->asoc.asconf_queue, next, aa_next) {
+ if (aa->sent == 1) {
+ /*
+ * implicitly successful or failed if correlation_id
+ * < last_error_id, then success else, failure
+ */
+ if (aa->ap.aph.correlation_id < last_error_id)
+ sctp_asconf_process_param_ack(stcb, aa, 1);
+ else
+ sctp_asconf_process_param_ack(stcb, aa, 0);
+ } else {
+ /*
+ * since we always process in order (FIFO queue) if
+ * we reach one that hasn't been sent, the rest
+ * should not have been sent either. so, we're
+ * done...
+ */
+ break;
+ }
+ }
+
+ /* update the next sequence number to use */
+ asoc->asconf_seq_out_acked++;
+ /* remove the old ASCONF on our outbound queue */
+ sctp_toss_old_asconf(stcb);
+ if (!TAILQ_EMPTY(&stcb->asoc.asconf_queue)) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ /* we have more params, so restart our timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep,
+ stcb, net);
+#else
+ /* we have more params, so send out more */
+ sctp_send_asconf(stcb, net, SCTP_ADDR_NOT_LOCKED);
+#endif
+ }
+}
+
+#ifdef INET6
+static uint32_t
+sctp_is_scopeid_in_nets(struct sctp_tcb *stcb, struct sockaddr *sa)
+{
+ struct sockaddr_in6 *sin6, *net6;
+ struct sctp_nets *net;
+
+ if (sa->sa_family != AF_INET6) {
+ /* wrong family */
+ return (0);
+ }
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) == 0) {
+ /* not link local address */
+ return (0);
+ }
+ /* hunt through our destination nets list for this scope_id */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (((struct sockaddr *)(&net->ro._l_addr))->sa_family !=
+ AF_INET6)
+ continue;
+ net6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ if (IN6_IS_ADDR_LINKLOCAL(&net6->sin6_addr) == 0)
+ continue;
+ if (sctp_is_same_scope(sin6, net6)) {
+ /* found one */
+ return (1);
+ }
+ }
+ /* didn't find one */
+ return (0);
+}
+#endif
+
+/*
+ * address management functions
+ */
+static void
+sctp_addr_mgmt_assoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_ifa *ifa, uint16_t type, int addr_locked)
+{
+ int status;
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0 ||
+ sctp_is_feature_off(inp, SCTP_PCB_FLAGS_DO_ASCONF)) {
+ /* subset bound, no ASCONF allowed case, so ignore */
+ return;
+ }
+ /*
+ * note: we know this is not the subset bound, no ASCONF case eg.
+ * this is boundall or subset bound w/ASCONF allowed
+ */
+
+ /* first, make sure that the address is IPv4 or IPv6 and not jailed */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin6.sin6_addr) != 0) {
+ return;
+ }
+#endif
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin.sin_addr) != 0) {
+ return;
+ }
+#endif
+ break;
+#endif
+ default:
+ return;
+ }
+#ifdef INET6
+ /* make sure we're "allowed" to add this type of addr */
+ if (ifa->address.sa.sa_family == AF_INET6) {
+ /* invalid if we're not a v6 endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0)
+ return;
+ /* is the v6 addr really valid ? */
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ return;
+ }
+ }
+#endif
+ /* put this address on the "pending/do not use yet" list */
+ sctp_add_local_addr_restricted(stcb, ifa);
+ /*
+ * check address scope if address is out of scope, don't queue
+ * anything... note: this would leave the address on both inp and
+ * asoc lists
+ */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = &ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* we skip unspecified addresses */
+ return;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (stcb->asoc.scope.local_scope == 0) {
+ return;
+ }
+ /* is it the right link local scope? */
+ if (sctp_is_scopeid_in_nets(stcb, &ifa->address.sa) == 0) {
+ return;
+ }
+ }
+ if (stcb->asoc.scope.site_scope == 0 &&
+ IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) {
+ return;
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp))
+ return;
+
+ sin = &ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* we skip unspecified addresses */
+ return;
+ }
+ if (stcb->asoc.scope.ipv4_local_scope == 0 &&
+ IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
+ return;
+ }
+ break;
+ }
+#endif
+ default:
+ /* else, not AF_INET or AF_INET6, so skip */
+ return;
+ }
+
+ /* queue an asconf for this address add/delete */
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF)) {
+ /* does the peer do asconf? */
+ if (stcb->asoc.asconf_supported) {
+ /* queue an asconf for this addr */
+ status = sctp_asconf_queue_add(stcb, ifa, type);
+
+ /*
+ * if queued ok, and in the open state, send out the
+ * ASCONF. If in the non-open state, these will be
+ * sent when the state goes open.
+ */
+ if (status == 0 &&
+ ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED))) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp,
+ stcb, stcb->asoc.primary_destination);
+#else
+ sctp_send_asconf(stcb, NULL, addr_locked);
+#endif
+ }
+ }
+ }
+}
+
+int
+sctp_asconf_iterator_ep(struct sctp_inpcb *inp, void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_asconf_iterator *asc;
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *l;
+ int cnt_invalid = 0;
+
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH(l, &asc->list_of_work, sctp_nxt_addr) {
+ ifa = l->ifa;
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ /* invalid if we're not a v6 endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return (1);
+ }
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return (1);
+ }
+ break;
+ }
+#endif
+ default:
+ /* invalid address family */
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static int
+sctp_asconf_iterator_ep_end(struct sctp_inpcb *inp, void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_ifa *ifa;
+ struct sctp_asconf_iterator *asc;
+ struct sctp_laddr *laddr, *nladdr, *l;
+
+ /* Only for specific case not bound all */
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH(l, &asc->list_of_work, sctp_nxt_addr) {
+ ifa = l->ifa;
+ if (l->action == SCTP_ADD_IP_ADDRESS) {
+ LIST_FOREACH(laddr, &inp->sctp_addr_list,
+ sctp_nxt_addr) {
+ if (laddr->ifa == ifa) {
+ laddr->action = 0;
+ break;
+ }
+ }
+ } else if (l->action == SCTP_DEL_IP_ADDRESS) {
+ LIST_FOREACH_SAFE(laddr, &inp->sctp_addr_list, sctp_nxt_addr, nladdr) {
+ /* remove only after all guys are done */
+ if (laddr->ifa == ifa) {
+ sctp_del_local_addr_ep(inp, ifa);
+ }
+ }
+ }
+ }
+ return (0);
+}
+
+void
+sctp_asconf_iterator_stcb(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_asconf_iterator *asc;
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *l;
+ int cnt_invalid = 0;
+ int type, status;
+ int num_queued = 0;
+
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH(l, &asc->list_of_work, sctp_nxt_addr) {
+ ifa = l->ifa;
+ type = l->action;
+
+ /* address's vrf_id must be the vrf_id of the assoc */
+ if (ifa->vrf_id != stcb->asoc.vrf_id) {
+ continue;
+ }
+
+ /* Same checks again for assoc */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* invalid if we're not a v6 endpoint */
+ struct sockaddr_in6 *sin6;
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return;
+ else
+ continue;
+ }
+ sin6 = &ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* we skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (stcb->asoc.scope.local_scope == 0) {
+ continue;
+ }
+ /* is it the right link local scope? */
+ if (sctp_is_scopeid_in_nets(stcb, &ifa->address.sa) == 0) {
+ continue;
+ }
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ /* invalid if we are a v6 only endpoint */
+ struct sockaddr_in *sin;
+
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp))
+ continue;
+
+ sin = &ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* we skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if (stcb->asoc.scope.ipv4_local_scope == 0 &&
+ IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
+ continue;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return;
+ else
+ continue;
+ }
+ break;
+ }
+#endif
+ default:
+ /* invalid address family */
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return;
+ else
+ continue;
+ }
+
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ /* prevent this address from being used as a source */
+ sctp_add_local_addr_restricted(stcb, ifa);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ struct sctp_nets *net;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* delete this address if cached */
+ if (net->ro._s_addr == ifa) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ if (net->ro.ro_rt) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+ /*
+ * Now we deleted our src address,
+ * should we not also now reset the
+ * cwnd/rto to start as if its a new
+ * address?
+ */
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb, net);
+ net->RTO = 0;
+ }
+ }
+ } else if (type == SCTP_SET_PRIM_ADDR) {
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) {
+ /* must validate the ifa is in the ep */
+ if (sctp_is_addr_in_ep(stcb->sctp_ep, ifa) == 0) {
+ continue;
+ }
+ } else {
+ /* Need to check scopes for this guy */
+ if (sctp_is_address_in_scope(ifa, &stcb->asoc.scope, 0) == 0) {
+ continue;
+ }
+ }
+ }
+ /* queue an asconf for this address add/delete */
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF) &&
+ stcb->asoc.asconf_supported == 1) {
+ /* queue an asconf for this addr */
+ status = sctp_asconf_queue_add(stcb, ifa, type);
+ /*
+ * if queued ok, and in the open state, update the
+ * count of queued params. If in the non-open state,
+ * these get sent when the assoc goes open.
+ */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ if (status >= 0) {
+ num_queued++;
+ }
+ }
+ }
+ }
+ /*
+ * If we have queued params in the open state, send out an ASCONF.
+ */
+ if (num_queued > 0) {
+ sctp_send_asconf(stcb, NULL, SCTP_ADDR_NOT_LOCKED);
+ }
+}
+
+void
+sctp_asconf_iterator_end(void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_asconf_iterator *asc;
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *l, *nl;
+
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH_SAFE(l, &asc->list_of_work, sctp_nxt_addr, nl) {
+ ifa = l->ifa;
+ if (l->action == SCTP_ADD_IP_ADDRESS) {
+ /* Clear the defer use flag */
+ ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ sctp_free_ifa(ifa);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_laddr), l);
+ SCTP_DECR_LADDR_COUNT();
+ }
+ SCTP_FREE(asc, SCTP_M_ASC_IT);
+}
+
+/*
+ * sa is the sockaddr to ask the peer to set primary to.
+ * returns: 0 = completed, -1 = error
+ */
+int32_t
+sctp_set_primary_ip_address_sa(struct sctp_tcb *stcb, struct sockaddr *sa)
+{
+ uint32_t vrf_id;
+ struct sctp_ifa *ifa;
+
+ /* find the ifa for the desired set primary */
+ vrf_id = stcb->asoc.vrf_id;
+ ifa = sctp_find_ifa_by_addr(sa, vrf_id, SCTP_ADDR_NOT_LOCKED);
+ if (ifa == NULL) {
+ /* Invalid address */
+ return (-1);
+ }
+
+ /* queue an ASCONF:SET_PRIM_ADDR to be sent */
+ if (!sctp_asconf_queue_add(stcb, ifa, SCTP_SET_PRIM_ADDR)) {
+ /* set primary queuing succeeded */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "set_primary_ip_address_sa: queued on tcb=%p, ",
+ (void *)stcb);
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF,
+ stcb->sctp_ep, stcb,
+ stcb->asoc.primary_destination);
+#else
+ sctp_send_asconf(stcb, NULL, SCTP_ADDR_NOT_LOCKED);
+#endif
+ }
+ } else {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "set_primary_ip_address_sa: failed to add to queue on tcb=%p, ",
+ (void *)stcb);
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ return (-1);
+ }
+ return (0);
+}
+
+int
+sctp_is_addr_pending(struct sctp_tcb *stcb, struct sctp_ifa *sctp_ifa)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+ unsigned int offset, asconf_limit;
+ struct sctp_asconf_chunk *acp;
+ struct sctp_asconf_paramhdr *aph;
+ uint8_t aparam_buf[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_paramhdr *ph;
+ int add_cnt, del_cnt;
+ uint16_t last_param_type;
+
+ add_cnt = del_cnt = 0;
+ last_param_type = 0;
+ TAILQ_FOREACH_SAFE(chk, &stcb->asoc.asconf_send_queue, sctp_next, nchk) {
+ if (chk->data == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: No mbuf data?\n");
+ continue;
+ }
+ offset = 0;
+ acp = mtod(chk->data, struct sctp_asconf_chunk *);
+ offset += sizeof(struct sctp_asconf_chunk);
+ asconf_limit = ntohs(acp->ch.chunk_length);
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(chk->data, offset, sizeof(struct sctp_paramhdr), aparam_buf);
+ if (ph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: couldn't get lookup addr!\n");
+ continue;
+ }
+ offset += ntohs(ph->param_length);
+
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(chk->data, offset, sizeof(struct sctp_asconf_paramhdr), aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: Empty ASCONF will be sent?\n");
+ continue;
+ }
+ while (aph != NULL) {
+ unsigned int param_length, param_type;
+
+ param_type = ntohs(aph->ph.param_type);
+ param_length = ntohs(aph->ph.param_length);
+ if (offset + param_length > asconf_limit) {
+ /* parameter goes beyond end of chunk! */
+ break;
+ }
+ if (param_length > sizeof(aparam_buf)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: param length (%u) larger than buffer size!\n", param_length);
+ break;
+ }
+ if (param_length <= sizeof(struct sctp_paramhdr)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: param length(%u) too short\n", param_length);
+ break;
+ }
+
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(chk->data, offset, param_length, aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: couldn't get entire param\n");
+ break;
+ }
+
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ if (sctp_addr_match(ph, &sctp_ifa->address.sa) != 0) {
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ add_cnt++;
+ break;
+ case SCTP_DEL_IP_ADDRESS:
+ del_cnt++;
+ break;
+ default:
+ break;
+ }
+ last_param_type = param_type;
+ }
+
+ offset += SCTP_SIZE32(param_length);
+ if (offset >= asconf_limit) {
+ /* no more data in the mbuf chain */
+ break;
+ }
+ /* get pointer to next asconf param */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(chk->data, offset, sizeof(struct sctp_asconf_paramhdr), aparam_buf);
+ }
+ }
+
+ /* we want to find the sequences which consist of ADD -> DEL -> ADD or DEL -> ADD */
+ if (add_cnt > del_cnt ||
+ (add_cnt == del_cnt && last_param_type == SCTP_ADD_IP_ADDRESS)) {
+ return (1);
+ }
+ return (0);
+}
+
+static struct sockaddr *
+sctp_find_valid_localaddr(struct sctp_tcb *stcb, int addr_locked)
+{
+ struct sctp_vrf *vrf = NULL;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(stcb->asoc.vrf_id);
+ if (vrf == NULL) {
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (NULL);
+ }
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if (stcb->asoc.scope.loopback_scope == 0 &&
+ SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* Skip if loopback_scope not set */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (stcb->asoc.scope.ipv4_addr_legal) {
+ struct sockaddr_in *sin;
+
+ sin = &sctp_ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if (stcb->asoc.scope.ipv4_local_scope == 0 &&
+ IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))
+ continue;
+
+ if (sctp_is_addr_restricted(stcb, sctp_ifa) &&
+ (!sctp_is_addr_pending(stcb, sctp_ifa)))
+ continue;
+ /* found a valid local v4 address to use */
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (&sctp_ifa->address.sa);
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (stcb->asoc.scope.ipv6_addr_legal) {
+ struct sockaddr_in6 *sin6;
+
+ if (sctp_ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ continue;
+ }
+
+ sin6 = &sctp_ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* we skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (stcb->asoc.scope.local_scope == 0 &&
+ IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ continue;
+ if (stcb->asoc.scope.site_scope == 0 &&
+ IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))
+ continue;
+
+ if (sctp_is_addr_restricted(stcb, sctp_ifa) &&
+ (!sctp_is_addr_pending(stcb, sctp_ifa)))
+ continue;
+ /* found a valid local v6 address to use */
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (&sctp_ifa->address.sa);
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+ /* no valid addresses found */
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (NULL);
+}
+
+static struct sockaddr *
+sctp_find_valid_localaddr_ep(struct sctp_tcb *stcb)
+{
+ struct sctp_laddr *laddr;
+
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ continue;
+ }
+ /* is the address restricted ? */
+ if (sctp_is_addr_restricted(stcb, laddr->ifa) &&
+ (!sctp_is_addr_pending(stcb, laddr->ifa)))
+ continue;
+
+ /* found a valid local address to use */
+ return (&laddr->ifa->address.sa);
+ }
+ /* no valid addresses found */
+ return (NULL);
+}
+
+/*
+ * builds an ASCONF chunk from queued ASCONF params.
+ * returns NULL on error (no mbuf, no ASCONF params queued, etc).
+ */
+struct mbuf *
+sctp_compose_asconf(struct sctp_tcb *stcb, int *retlen, int addr_locked)
+{
+ struct mbuf *m_asconf, *m_asconf_chk;
+ struct sctp_asconf_addr *aa;
+ struct sctp_asconf_chunk *acp;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_asconf_addr_param *aap;
+ uint32_t p_length, overhead;
+ uint32_t correlation_id = 1; /* 0 is reserved... */
+ caddr_t ptr, lookup_ptr;
+ uint8_t lookup_used = 0;
+
+ /* are there any asconf params to send? */
+ TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) {
+ if (aa->sent == 0)
+ break;
+ }
+ if (aa == NULL)
+ return (NULL);
+
+ /* Consider IP header and SCTP common header. */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ overhead = SCTP_MIN_OVERHEAD;
+ } else {
+ overhead = SCTP_MIN_V4_OVERHEAD;
+ }
+ /* Consider ASONF chunk. */
+ overhead += sizeof(struct sctp_asconf_chunk);
+ /* Consider AUTH chunk. */
+ overhead += sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ if (stcb->asoc.smallest_mtu <= overhead) {
+ /* MTU too small. */
+ return (NULL);
+ }
+ /*
+ * get a chunk header mbuf and a cluster for the asconf params since
+ * it's simpler to fill in the asconf chunk header lookup address on
+ * the fly
+ */
+ m_asconf_chk = sctp_get_mbuf_for_msg(sizeof(struct sctp_asconf_chunk), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_asconf_chk == NULL) {
+ /* no mbuf's */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: couldn't get chunk mbuf!\n");
+ return (NULL);
+ }
+ m_asconf = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_asconf == NULL) {
+ /* no mbuf's */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: couldn't get mbuf!\n");
+ sctp_m_freem(m_asconf_chk);
+ return (NULL);
+ }
+ SCTP_BUF_LEN(m_asconf_chk) = sizeof(struct sctp_asconf_chunk);
+ SCTP_BUF_LEN(m_asconf) = 0;
+ acp = mtod(m_asconf_chk, struct sctp_asconf_chunk *);
+ memset(acp, 0, sizeof(struct sctp_asconf_chunk));
+ /* save pointers to lookup address and asconf params */
+ lookup_ptr = (caddr_t)(acp + 1); /* after the header */
+ ptr = mtod(m_asconf, caddr_t); /* beginning of cluster */
+
+ /* fill in chunk header info */
+ acp->ch.chunk_type = SCTP_ASCONF;
+ acp->ch.chunk_flags = 0;
+ acp->serial_number = htonl(stcb->asoc.asconf_seq_out);
+ stcb->asoc.asconf_seq_out++;
+
+ /* add parameters... up to smallest MTU allowed */
+ TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) {
+ if (aa->sent)
+ continue;
+ /* get the parameter length */
+ p_length = SCTP_SIZE32(aa->ap.aph.ph.param_length);
+ /* will it fit in current chunk? */
+ if ((SCTP_BUF_LEN(m_asconf) + p_length > stcb->asoc.smallest_mtu - overhead) ||
+ (SCTP_BUF_LEN(m_asconf) + p_length > MCLBYTES)) {
+ /* won't fit, so we're done with this chunk */
+ break;
+ }
+ /* assign (and store) a correlation id */
+ aa->ap.aph.correlation_id = correlation_id++;
+
+ /*
+ * fill in address if we're doing a delete this is a simple
+ * way for us to fill in the correlation address, which
+ * should only be used by the peer if we're deleting our
+ * source address and adding a new address (e.g. renumbering
+ * case)
+ */
+ if (lookup_used == 0 &&
+ (aa->special_del == 0) &&
+ aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS) {
+ struct sctp_ipv6addr_param *lookup;
+ uint16_t p_size, addr_size;
+
+ lookup = (struct sctp_ipv6addr_param *)lookup_ptr;
+ lookup->ph.param_type =
+ htons(aa->ap.addrp.ph.param_type);
+ if (aa->ap.addrp.ph.param_type == SCTP_IPV6_ADDRESS) {
+ /* copy IPv6 address */
+ p_size = sizeof(struct sctp_ipv6addr_param);
+ addr_size = sizeof(struct in6_addr);
+ } else {
+ /* copy IPv4 address */
+ p_size = sizeof(struct sctp_ipv4addr_param);
+ addr_size = sizeof(struct in_addr);
+ }
+ lookup->ph.param_length = htons(SCTP_SIZE32(p_size));
+ memcpy(lookup->addr, &aa->ap.addrp.addr, addr_size);
+ SCTP_BUF_LEN(m_asconf_chk) += SCTP_SIZE32(p_size);
+ lookup_used = 1;
+ }
+ /* copy into current space */
+ memcpy(ptr, &aa->ap, p_length);
+
+ /* network elements and update lengths */
+ aph = (struct sctp_asconf_paramhdr *)ptr;
+ aap = (struct sctp_asconf_addr_param *)ptr;
+ /* correlation_id is transparent to peer, no htonl needed */
+ aph->ph.param_type = htons(aph->ph.param_type);
+ aph->ph.param_length = htons(aph->ph.param_length);
+ aap->addrp.ph.param_type = htons(aap->addrp.ph.param_type);
+ aap->addrp.ph.param_length = htons(aap->addrp.ph.param_length);
+
+ SCTP_BUF_LEN(m_asconf) += SCTP_SIZE32(p_length);
+ ptr += SCTP_SIZE32(p_length);
+
+ /*
+ * these params are removed off the pending list upon
+ * getting an ASCONF-ACK back from the peer, just set flag
+ */
+ aa->sent = 1;
+ }
+ /* check to see if the lookup addr has been populated yet */
+ if (lookup_used == 0) {
+ /* NOTE: if the address param is optional, can skip this... */
+ /* add any valid (existing) address... */
+ struct sctp_ipv6addr_param *lookup;
+ uint16_t p_size, addr_size;
+ struct sockaddr *found_addr;
+ caddr_t addr_ptr;
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL)
+ found_addr = sctp_find_valid_localaddr(stcb,
+ addr_locked);
+ else
+ found_addr = sctp_find_valid_localaddr_ep(stcb);
+
+ lookup = (struct sctp_ipv6addr_param *)lookup_ptr;
+ if (found_addr != NULL) {
+ switch (found_addr->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ /* copy IPv6 address */
+ lookup->ph.param_type =
+ htons(SCTP_IPV6_ADDRESS);
+ p_size = sizeof(struct sctp_ipv6addr_param);
+ addr_size = sizeof(struct in6_addr);
+ addr_ptr = (caddr_t)&((struct sockaddr_in6 *)
+ found_addr)->sin6_addr;
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+ /* copy IPv4 address */
+ lookup->ph.param_type =
+ htons(SCTP_IPV4_ADDRESS);
+ p_size = sizeof(struct sctp_ipv4addr_param);
+ addr_size = sizeof(struct in_addr);
+ addr_ptr = (caddr_t)&((struct sockaddr_in *)
+ found_addr)->sin_addr;
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: no usable lookup addr (family = %d)!\n",
+ found_addr->sa_family);
+ sctp_m_freem(m_asconf_chk);
+ sctp_m_freem(m_asconf);
+ return (NULL);
+ }
+ lookup->ph.param_length = htons(SCTP_SIZE32(p_size));
+ memcpy(lookup->addr, addr_ptr, addr_size);
+ SCTP_BUF_LEN(m_asconf_chk) += SCTP_SIZE32(p_size);
+ } else {
+ /* uh oh... don't have any address?? */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: no lookup addr!\n");
+ sctp_m_freem(m_asconf_chk);
+ sctp_m_freem(m_asconf);
+ return (NULL);
+ }
+ }
+ /* chain it all together */
+ SCTP_BUF_NEXT(m_asconf_chk) = m_asconf;
+ *retlen = SCTP_BUF_LEN(m_asconf_chk) + SCTP_BUF_LEN(m_asconf);
+ acp->ch.chunk_length = htons(*retlen);
+
+ return (m_asconf_chk);
+}
+
+/*
+ * section to handle address changes before an association is up eg. changes
+ * during INIT/INIT-ACK/COOKIE-ECHO handshake
+ */
+
+/*
+ * processes the (local) addresses in the INIT-ACK chunk
+ */
+static void
+sctp_process_initack_addresses(struct sctp_tcb *stcb, struct mbuf *m,
+ unsigned int offset, unsigned int length)
+{
+ struct sctp_paramhdr tmp_param, *ph;
+ uint16_t plen, ptype;
+ struct sctp_ifa *sctp_ifa;
+ union sctp_sockstore store;
+#ifdef INET6
+ struct sctp_ipv6addr_param addr6_store;
+#endif
+#ifdef INET
+ struct sctp_ipv4addr_param addr4_store;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "processing init-ack addresses\n");
+ if (stcb == NULL) /* Un-needed check for SA */
+ return;
+
+ /* convert to upper bound */
+ length += offset;
+
+ if ((offset + sizeof(struct sctp_paramhdr)) > length) {
+ return;
+ }
+ /* go through the addresses in the init-ack */
+ ph = (struct sctp_paramhdr *)
+ sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr),
+ (uint8_t *)&tmp_param);
+ while (ph != NULL) {
+ ptype = ntohs(ph->param_type);
+ plen = ntohs(ph->param_length);
+ switch (ptype) {
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ {
+ struct sctp_ipv6addr_param *a6p;
+
+ /* get the entire IPv6 address param */
+ a6p = (struct sctp_ipv6addr_param *)
+ sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv6addr_param),
+ (uint8_t *)&addr6_store);
+ if (plen != sizeof(struct sctp_ipv6addr_param) ||
+ a6p == NULL) {
+ return;
+ }
+ memset(&store, 0, sizeof(union sctp_sockstore));
+ store.sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ store.sin6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ store.sin6.sin6_port = stcb->rport;
+ memcpy(&store.sin6.sin6_addr, a6p->addr, sizeof(struct in6_addr));
+ break;
+ }
+#endif
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ {
+ struct sctp_ipv4addr_param *a4p;
+
+ /* get the entire IPv4 address param */
+ a4p = (struct sctp_ipv4addr_param *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv4addr_param),
+ (uint8_t *)&addr4_store);
+ if (plen != sizeof(struct sctp_ipv4addr_param) ||
+ a4p == NULL) {
+ return;
+ }
+ memset(&store, 0, sizeof(union sctp_sockstore));
+ store.sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ store.sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+ store.sin.sin_port = stcb->rport;
+ store.sin.sin_addr.s_addr = a4p->addr;
+ break;
+ }
+#endif
+ default:
+ goto next_addr;
+ }
+
+ /* see if this address really (still) exists */
+ sctp_ifa = sctp_find_ifa_by_addr(&store.sa, stcb->asoc.vrf_id,
+ SCTP_ADDR_NOT_LOCKED);
+ if (sctp_ifa == NULL) {
+ /* address doesn't exist anymore */
+ int status;
+
+ /* are ASCONFs allowed ? */
+ if ((sctp_is_feature_on(stcb->sctp_ep,
+ SCTP_PCB_FLAGS_DO_ASCONF)) &&
+ stcb->asoc.asconf_supported) {
+ /* queue an ASCONF DEL_IP_ADDRESS */
+ status = sctp_asconf_queue_sa_delete(stcb, &store.sa);
+ /*
+ * if queued ok, and in correct state, send
+ * out the ASCONF.
+ */
+ if (status == 0 &&
+ SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF,
+ stcb->sctp_ep, stcb,
+ stcb->asoc.primary_destination);
+#else
+ sctp_send_asconf(stcb, NULL, SCTP_ADDR_NOT_LOCKED);
+#endif
+ }
+ }
+ }
+
+next_addr:
+ /*
+ * Sanity check: Make sure the length isn't 0, otherwise
+ * we'll be stuck in this loop for a long time...
+ */
+ if (SCTP_SIZE32(plen) == 0) {
+ SCTP_PRINTF("process_initack_addrs: bad len (%d) type=%xh\n",
+ plen, ptype);
+ return;
+ }
+ /* get next parameter */
+ offset += SCTP_SIZE32(plen);
+ if ((offset + sizeof(struct sctp_paramhdr)) > length)
+ return;
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_paramhdr), (uint8_t *)&tmp_param);
+ } /* while */
+}
+
+/* FIX ME: need to verify return result for v6 address type if v6 disabled */
+/*
+ * checks to see if a specific address is in the initack address list returns
+ * 1 if found, 0 if not
+ */
+static uint32_t
+sctp_addr_in_initack(struct mbuf *m, uint32_t offset, uint32_t length, struct sockaddr *sa)
+{
+ struct sctp_paramhdr tmp_param, *ph;
+ uint16_t plen, ptype;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *a4p;
+ struct sctp_ipv6addr_param addr4_store;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *a6p;
+ struct sctp_ipv6addr_param addr6_store;
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ struct sockaddr_in6 sin6_tmp;
+#endif
+#endif
+
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ break;
+#endif
+ default:
+ return (0);
+ }
+
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "find_initack_addr: starting search for ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, sa);
+ /* convert to upper bound */
+ length += offset;
+
+ if ((offset + sizeof(struct sctp_paramhdr)) > length) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "find_initack_addr: invalid offset?\n");
+ return (0);
+ }
+ /* go through the addresses in the init-ack */
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_paramhdr), (uint8_t *) & tmp_param);
+ while (ph != NULL) {
+ ptype = ntohs(ph->param_type);
+ plen = ntohs(ph->param_length);
+ switch (ptype) {
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (sa->sa_family == AF_INET6) {
+ /* get the entire IPv6 address param */
+ if (plen != sizeof(struct sctp_ipv6addr_param)) {
+ break;
+ }
+ /* get the entire IPv6 address param */
+ a6p = (struct sctp_ipv6addr_param *)
+ sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv6addr_param),
+ (uint8_t *)&addr6_store);
+ if (a6p == NULL) {
+ return (0);
+ }
+ sin6 = (struct sockaddr_in6 *)sa;
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) {
+ /* create a copy and clear scope */
+ memcpy(&sin6_tmp, sin6,
+ sizeof(struct sockaddr_in6));
+ sin6 = &sin6_tmp;
+ in6_clearscope(&sin6->sin6_addr);
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ if (memcmp(&sin6->sin6_addr, a6p->addr,
+ sizeof(struct in6_addr)) == 0) {
+ /* found it */
+ return (1);
+ }
+ }
+ break;
+#endif /* INET6 */
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (sa->sa_family == AF_INET) {
+ if (plen != sizeof(struct sctp_ipv4addr_param)) {
+ break;
+ }
+ /* get the entire IPv4 address param */
+ a4p = (struct sctp_ipv4addr_param *)
+ sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv4addr_param),
+ (uint8_t *)&addr4_store);
+ if (a4p == NULL) {
+ return (0);
+ }
+ sin = (struct sockaddr_in *)sa;
+ if (sin->sin_addr.s_addr == a4p->addr) {
+ /* found it */
+ return (1);
+ }
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ /* get next parameter */
+ offset += SCTP_SIZE32(plen);
+ if (offset + sizeof(struct sctp_paramhdr) > length) {
+ return (0);
+ }
+ ph = (struct sctp_paramhdr *)
+ sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr),
+ (uint8_t *) & tmp_param);
+ } /* while */
+ /* not found! */
+ return (0);
+}
+
+/*
+ * makes sure that the current endpoint local addr list is consistent with
+ * the new association (eg. subset bound, asconf allowed) adds addresses as
+ * necessary
+ */
+static void
+sctp_check_address_list_ep(struct sctp_tcb *stcb, struct mbuf *m, int offset,
+ int length, struct sockaddr *init_addr)
+{
+ struct sctp_laddr *laddr;
+
+ /* go through the endpoint list */
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) {
+ /* be paranoid and validate the laddr */
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "check_addr_list_ep: laddr->ifa is NULL");
+ continue;
+ }
+ /* do i have it implicitly? */
+ if (sctp_cmpaddr(&laddr->ifa->address.sa, init_addr)) {
+ continue;
+ }
+ /* check to see if in the init-ack */
+ if (!sctp_addr_in_initack(m, offset, length, &laddr->ifa->address.sa)) {
+ /* try to add it */
+ sctp_addr_mgmt_assoc(stcb->sctp_ep, stcb, laddr->ifa,
+ SCTP_ADD_IP_ADDRESS, SCTP_ADDR_NOT_LOCKED);
+ }
+ }
+}
+
+/*
+ * makes sure that the current kernel address list is consistent with the new
+ * association (with all addrs bound) adds addresses as necessary
+ */
+static void
+sctp_check_address_list_all(struct sctp_tcb *stcb, struct mbuf *m, int offset,
+ int length, struct sockaddr *init_addr,
+ uint16_t local_scope, uint16_t site_scope,
+ uint16_t ipv4_scope, uint16_t loopback_scope)
+{
+ struct sctp_vrf *vrf = NULL;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+ uint32_t vrf_id;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+
+ if (stcb) {
+ vrf_id = stcb->asoc.vrf_id;
+ } else {
+ return;
+ }
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return;
+ }
+ /* go through all our known interfaces */
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if (loopback_scope == 0 && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* skip loopback interface */
+ continue;
+ }
+ /* go through each interface address */
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ /* do i have it implicitly? */
+ if (sctp_cmpaddr(&sctp_ifa->address.sa, init_addr)) {
+ continue;
+ }
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ sin = &sctp_ifa->address.sin;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if ((ipv4_scope == 0) &&
+ (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) {
+ /* private address not in scope */
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = &sctp_ifa->address.sin6;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if ((local_scope == 0) &&
+ (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ if ((site_scope == 0) &&
+ (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ /* check to see if in the init-ack */
+ if (!sctp_addr_in_initack(m, offset, length, &sctp_ifa->address.sa)) {
+ /* try to add it */
+ sctp_addr_mgmt_assoc(stcb->sctp_ep, stcb,
+ sctp_ifa, SCTP_ADD_IP_ADDRESS,
+ SCTP_ADDR_LOCKED);
+ }
+ } /* end foreach ifa */
+ } /* end foreach ifn */
+ SCTP_IPI_ADDR_RUNLOCK();
+}
+
+/*
+ * validates an init-ack chunk (from a cookie-echo) with current addresses
+ * adds addresses from the init-ack into our local address list, if needed
+ * queues asconf adds/deletes addresses as needed and makes appropriate list
+ * changes for source address selection m, offset: points to the start of the
+ * address list in an init-ack chunk length: total length of the address
+ * params only init_addr: address where my INIT-ACK was sent from
+ */
+void
+sctp_check_address_list(struct sctp_tcb *stcb, struct mbuf *m, int offset,
+ int length, struct sockaddr *init_addr,
+ uint16_t local_scope, uint16_t site_scope,
+ uint16_t ipv4_scope, uint16_t loopback_scope)
+{
+ /* process the local addresses in the initack */
+ sctp_process_initack_addresses(stcb, m, offset, length);
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* bound all case */
+ sctp_check_address_list_all(stcb, m, offset, length, init_addr,
+ local_scope, site_scope, ipv4_scope, loopback_scope);
+ } else {
+ /* subset bound case */
+ if (sctp_is_feature_on(stcb->sctp_ep,
+ SCTP_PCB_FLAGS_DO_ASCONF)) {
+ /* asconf's allowed */
+ sctp_check_address_list_ep(stcb, m, offset, length,
+ init_addr);
+ }
+ /* else, no asconfs allowed, so what we sent is what we get */
+ }
+}
+
+/*
+ * sctp_bindx() support
+ */
+uint32_t
+sctp_addr_mgmt_ep_sa(struct sctp_inpcb *inp, struct sockaddr *sa,
+ uint32_t type, uint32_t vrf_id)
+{
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *laddr, *nladdr;
+
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len == 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ /* For an add the address MUST be on the system */
+ ifa = sctp_find_ifa_by_addr(sa, vrf_id, SCTP_ADDR_NOT_LOCKED);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ /* For a delete we need to find it in the inp */
+ ifa = sctp_find_ifa_in_ep(inp, sa, SCTP_ADDR_NOT_LOCKED);
+ } else {
+ ifa = NULL;
+ }
+ if (ifa != NULL) {
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ sctp_add_local_addr_ep(inp, ifa, type);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ if (inp->laddr_count < 2) {
+ /* can't delete the last local address */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EINVAL);
+ return (EINVAL);
+ }
+ LIST_FOREACH(laddr, &inp->sctp_addr_list,
+ sctp_nxt_addr) {
+ if (ifa == laddr->ifa) {
+ /* Mark in the delete */
+ laddr->action = type;
+ }
+ }
+ }
+ if (LIST_EMPTY(&inp->sctp_asoc_list)) {
+ /*
+ * There is no need to start the iterator if
+ * the inp has no associations.
+ */
+ if (type == SCTP_DEL_IP_ADDRESS) {
+ LIST_FOREACH_SAFE(laddr, &inp->sctp_addr_list, sctp_nxt_addr, nladdr) {
+ if (laddr->ifa == ifa) {
+ sctp_del_local_addr_ep(inp, ifa);
+ }
+ }
+ }
+ } else {
+ struct sctp_asconf_iterator *asc;
+ struct sctp_laddr *wi;
+ int ret;
+
+ SCTP_MALLOC(asc, struct sctp_asconf_iterator *,
+ sizeof(struct sctp_asconf_iterator),
+ SCTP_M_ASC_IT);
+ if (asc == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, ENOMEM);
+ return (ENOMEM);
+ }
+ wi = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr);
+ if (wi == NULL) {
+ SCTP_FREE(asc, SCTP_M_ASC_IT);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, ENOMEM);
+ return (ENOMEM);
+ }
+ LIST_INIT(&asc->list_of_work);
+ asc->cnt = 1;
+ SCTP_INCR_LADDR_COUNT();
+ wi->ifa = ifa;
+ wi->action = type;
+ atomic_add_int(&ifa->refcount, 1);
+ LIST_INSERT_HEAD(&asc->list_of_work, wi, sctp_nxt_addr);
+ ret = sctp_initiate_iterator(sctp_asconf_iterator_ep,
+ sctp_asconf_iterator_stcb,
+ sctp_asconf_iterator_ep_end,
+ SCTP_PCB_ANY_FLAGS,
+ SCTP_PCB_ANY_FEATURES,
+ SCTP_ASOC_ANY_STATE,
+ (void *)asc, 0,
+ sctp_asconf_iterator_end, inp, 0);
+ if (ret) {
+ SCTP_PRINTF("Failed to initiate iterator for addr_mgmt_ep_sa\n");
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EFAULT);
+ sctp_asconf_iterator_end(asc, 0);
+ return (EFAULT);
+ }
+ }
+ return (0);
+ } else {
+ /* invalid address! */
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EADDRNOTAVAIL);
+ return (EADDRNOTAVAIL);
+ }
+}
+
+void
+sctp_asconf_send_nat_state_update(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ struct sctp_asconf_addr *aa_vtag, *aa_add, *aa_del;
+ struct sctp_ifa *sctp_ifap;
+ struct sctp_asconf_tag_param *vtag;
+#ifdef INET
+ struct sockaddr_in *to;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *to6;
+#endif
+
+ if (net == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "sctp_asconf_send_nat_state_update: Missing net\n");
+ return;
+ }
+ if (stcb == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "sctp_asconf_send_nat_state_update: Missing stcb\n");
+ return;
+ }
+ /* Need to have in the ASCONF:
+ * - VTAG(my_vtag/peer_vtag)
+ * - ADD(wildcard)
+ * - DEL(wildcard)
+ * - ADD(Any global addresses)
+ */
+ SCTP_MALLOC(aa_vtag, struct sctp_asconf_addr *, sizeof(struct sctp_asconf_addr), SCTP_M_ASC_ADDR);
+ SCTP_MALLOC(aa_add, struct sctp_asconf_addr *, sizeof(struct sctp_asconf_addr), SCTP_M_ASC_ADDR);
+ SCTP_MALLOC(aa_del, struct sctp_asconf_addr *, sizeof(struct sctp_asconf_addr), SCTP_M_ASC_ADDR);
+
+ if ((aa_vtag == NULL) || (aa_add == NULL) || (aa_del == NULL)) {
+ /* Didn't get memory */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "sctp_asconf_send_nat_state_update: failed to get memory!\n");
+out:
+ if (aa_vtag != NULL) {
+ SCTP_FREE(aa_vtag, SCTP_M_ASC_ADDR);
+ }
+ if (aa_add != NULL) {
+ SCTP_FREE(aa_add, SCTP_M_ASC_ADDR);
+ }
+ if (aa_del != NULL) {
+ SCTP_FREE(aa_del, SCTP_M_ASC_ADDR);
+ }
+ return;
+ }
+ memset(aa_vtag, 0, sizeof(struct sctp_asconf_addr));
+ aa_vtag->special_del = 0;
+ /* Fill in ASCONF address parameter fields. */
+ /* Top level elements are "networked" during send. */
+ aa_vtag->ifa = NULL;
+ aa_vtag->sent = 0; /* clear sent flag */
+ vtag = (struct sctp_asconf_tag_param *)&aa_vtag->ap.aph;
+ vtag->aph.ph.param_type = SCTP_NAT_VTAGS;
+ vtag->aph.ph.param_length = sizeof(struct sctp_asconf_tag_param);
+ vtag->local_vtag = htonl(stcb->asoc.my_vtag);
+ vtag->remote_vtag = htonl(stcb->asoc.peer_vtag);
+
+ memset(aa_add, 0, sizeof(struct sctp_asconf_addr));
+ memset(aa_del, 0, sizeof(struct sctp_asconf_addr));
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ aa_add->ap.aph.ph.param_type = SCTP_ADD_IP_ADDRESS;
+ aa_add->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addrv4_param);
+ aa_add->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa_add->ap.addrp.ph.param_length = sizeof (struct sctp_ipv4addr_param);
+ /* No need to fill the address, we are using 0.0.0.0 */
+ aa_del->ap.aph.ph.param_type = SCTP_DEL_IP_ADDRESS;
+ aa_del->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addrv4_param);
+ aa_del->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa_del->ap.addrp.ph.param_length = sizeof (struct sctp_ipv4addr_param);
+ /* No need to fill the address, we are using 0.0.0.0 */
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ aa_add->ap.aph.ph.param_type = SCTP_ADD_IP_ADDRESS;
+ aa_add->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addr_param);
+ aa_add->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa_add->ap.addrp.ph.param_length = sizeof (struct sctp_ipv6addr_param);
+ /* No need to fill the address, we are using ::0 */
+ aa_del->ap.aph.ph.param_type = SCTP_DEL_IP_ADDRESS;
+ aa_del->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addr_param);
+ aa_del->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa_del->ap.addrp.ph.param_length = sizeof (struct sctp_ipv6addr_param);
+ /* No need to fill the address, we are using ::0 */
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_asconf_send_nat_state_update: unknown address family %d\n",
+ net->ro._l_addr.sa.sa_family);
+ goto out;
+ }
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa_vtag, next);
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa_add, next);
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa_del, next);
+
+ /* Now we must hunt the addresses and add all global addresses */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ struct sctp_vrf *vrf = NULL;
+ struct sctp_ifn *sctp_ifnp;
+ uint32_t vrf_id;
+
+ vrf_id = stcb->sctp_ep->def_vrf_id;
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ goto skip_rest;
+ }
+
+ SCTP_IPI_ADDR_RLOCK();
+ LIST_FOREACH(sctp_ifnp, &vrf->ifnlist, next_ifn) {
+ LIST_FOREACH(sctp_ifap, &sctp_ifnp->ifalist, next_ifa) {
+ switch (sctp_ifap->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ to = &sctp_ifap->address.sin;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &to->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN4_ISPRIVATE_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ if (IN4_ISLOOPBACK_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ to6 = &sctp_ifap->address.sin6;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &to6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LOOPBACK(&to6->sin6_addr)) {
+ continue;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&to6->sin6_addr)) {
+ continue;
+ }
+ break;
+#endif
+ default:
+ continue;
+ }
+ sctp_asconf_queue_mgmt(stcb, sctp_ifap, SCTP_ADD_IP_ADDRESS);
+ }
+ }
+ SCTP_IPI_ADDR_RUNLOCK();
+ } else {
+ struct sctp_laddr *laddr;
+
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ continue;
+ }
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED)
+ /* Address being deleted by the system, dont
+ * list.
+ */
+ continue;
+ if (laddr->action == SCTP_DEL_IP_ADDRESS) {
+ /* Address being deleted on this ep
+ * don't list.
+ */
+ continue;
+ }
+ sctp_ifap = laddr->ifa;
+ switch (sctp_ifap->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ to = &sctp_ifap->address.sin;
+ if (IN4_ISPRIVATE_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ if (IN4_ISLOOPBACK_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ to6 = &sctp_ifap->address.sin6;
+ if (IN6_IS_ADDR_LOOPBACK(&to6->sin6_addr)) {
+ continue;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&to6->sin6_addr)) {
+ continue;
+ }
+ break;
+#endif
+ default:
+ continue;
+ }
+ sctp_asconf_queue_mgmt(stcb, sctp_ifap, SCTP_ADD_IP_ADDRESS);
+ }
+ }
+ skip_rest:
+ /* Now we must send the asconf into the queue */
+ sctp_send_asconf(stcb, net, SCTP_ADDR_NOT_LOCKED);
+}
diff --git a/netwerk/sctp/src/netinet/sctp_asconf.h b/netwerk/sctp/src/netinet/sctp_asconf.h
new file mode 100644
index 0000000000..a7429aeca4
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_asconf.h
@@ -0,0 +1,89 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_ASCONF_H_
+#define _NETINET_SCTP_ASCONF_H_
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+/*
+ * function prototypes
+ */
+extern void sctp_asconf_cleanup(struct sctp_tcb *);
+
+extern struct mbuf *sctp_compose_asconf(struct sctp_tcb *, int *, int);
+
+extern void
+sctp_handle_asconf(struct mbuf *, unsigned int, struct sockaddr *,
+ struct sctp_asconf_chunk *, struct sctp_tcb *, int);
+
+extern void
+sctp_handle_asconf_ack(struct mbuf *, int, struct sctp_asconf_ack_chunk *,
+ struct sctp_tcb *, struct sctp_nets *, int *);
+
+extern uint32_t
+sctp_addr_mgmt_ep_sa(struct sctp_inpcb *, struct sockaddr *, uint32_t,
+ uint32_t);
+
+extern int sctp_asconf_iterator_ep(struct sctp_inpcb *inp, void *ptr,
+ uint32_t val);
+extern void sctp_asconf_iterator_stcb(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ void *ptr, uint32_t type);
+extern void sctp_asconf_iterator_end(void *ptr, uint32_t val);
+
+extern int32_t
+sctp_set_primary_ip_address_sa(struct sctp_tcb *,
+ struct sockaddr *);
+
+extern void
+sctp_check_address_list(struct sctp_tcb *, struct mbuf *, int, int,
+ struct sockaddr *, uint16_t, uint16_t, uint16_t, uint16_t);
+
+extern void
+sctp_assoc_immediate_retrans(struct sctp_tcb *, struct sctp_nets *);
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+extern void
+sctp_net_immediate_retrans(struct sctp_tcb *, struct sctp_nets *);
+#endif
+
+extern void
+sctp_asconf_send_nat_state_update(struct sctp_tcb *stcb,
+ struct sctp_nets *net);
+
+extern int
+sctp_is_addr_pending(struct sctp_tcb *, struct sctp_ifa *);
+#endif /* _KERNEL */
+
+#endif /* !_NETINET_SCTP_ASCONF_H_ */
diff --git a/netwerk/sctp/src/netinet/sctp_auth.c b/netwerk/sctp/src/netinet/sctp_auth.c
new file mode 100644
index 0000000000..6fc3807cc1
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_auth.c
@@ -0,0 +1,2293 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_auth.h>
+
+#ifdef SCTP_DEBUG
+#define SCTP_AUTH_DEBUG (SCTP_BASE_SYSCTL(sctp_debug_on) & SCTP_DEBUG_AUTH1)
+#define SCTP_AUTH_DEBUG2 (SCTP_BASE_SYSCTL(sctp_debug_on) & SCTP_DEBUG_AUTH2)
+#endif /* SCTP_DEBUG */
+
+void
+sctp_clear_chunklist(sctp_auth_chklist_t *chklist)
+{
+ memset(chklist, 0, sizeof(*chklist));
+ /* chklist->num_chunks = 0; */
+}
+
+sctp_auth_chklist_t *
+sctp_alloc_chunklist(void)
+{
+ sctp_auth_chklist_t *chklist;
+
+ SCTP_MALLOC(chklist, sctp_auth_chklist_t *, sizeof(*chklist),
+ SCTP_M_AUTH_CL);
+ if (chklist == NULL) {
+ SCTPDBG(SCTP_DEBUG_AUTH1, "sctp_alloc_chunklist: failed to get memory!\n");
+ } else {
+ sctp_clear_chunklist(chklist);
+ }
+ return (chklist);
+}
+
+void
+sctp_free_chunklist(sctp_auth_chklist_t *list)
+{
+ if (list != NULL)
+ SCTP_FREE(list, SCTP_M_AUTH_CL);
+}
+
+sctp_auth_chklist_t *
+sctp_copy_chunklist(sctp_auth_chklist_t *list)
+{
+ sctp_auth_chklist_t *new_list;
+
+ if (list == NULL)
+ return (NULL);
+
+ /* get a new list */
+ new_list = sctp_alloc_chunklist();
+ if (new_list == NULL)
+ return (NULL);
+ /* copy it */
+ memcpy(new_list, list, sizeof(*new_list));
+
+ return (new_list);
+}
+
+/*
+ * add a chunk to the required chunks list
+ */
+int
+sctp_auth_add_chunk(uint8_t chunk, sctp_auth_chklist_t *list)
+{
+ if (list == NULL)
+ return (-1);
+
+ /* is chunk restricted? */
+ if ((chunk == SCTP_INITIATION) ||
+ (chunk == SCTP_INITIATION_ACK) ||
+ (chunk == SCTP_SHUTDOWN_COMPLETE) ||
+ (chunk == SCTP_AUTHENTICATION)) {
+ return (-1);
+ }
+ if (list->chunks[chunk] == 0) {
+ list->chunks[chunk] = 1;
+ list->num_chunks++;
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: added chunk %u (0x%02x) to Auth list\n",
+ chunk, chunk);
+ }
+ return (0);
+}
+
+/*
+ * delete a chunk from the required chunks list
+ */
+int
+sctp_auth_delete_chunk(uint8_t chunk, sctp_auth_chklist_t *list)
+{
+ if (list == NULL)
+ return (-1);
+
+ if (list->chunks[chunk] == 1) {
+ list->chunks[chunk] = 0;
+ list->num_chunks--;
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: deleted chunk %u (0x%02x) from Auth list\n",
+ chunk, chunk);
+ }
+ return (0);
+}
+
+size_t
+sctp_auth_get_chklist_size(const sctp_auth_chklist_t *list)
+{
+ if (list == NULL)
+ return (0);
+ else
+ return (list->num_chunks);
+}
+
+/*
+ * return the current number and list of required chunks caller must
+ * guarantee ptr has space for up to 256 bytes
+ */
+int
+sctp_serialize_auth_chunks(const sctp_auth_chklist_t *list, uint8_t *ptr)
+{
+ int i, count = 0;
+
+ if (list == NULL)
+ return (0);
+
+ for (i = 0; i < 256; i++) {
+ if (list->chunks[i] != 0) {
+ *ptr++ = i;
+ count++;
+ }
+ }
+ return (count);
+}
+
+int
+sctp_pack_auth_chunks(const sctp_auth_chklist_t *list, uint8_t *ptr)
+{
+ int i, size = 0;
+
+ if (list == NULL)
+ return (0);
+
+ if (list->num_chunks <= 32) {
+ /* just list them, one byte each */
+ for (i = 0; i < 256; i++) {
+ if (list->chunks[i] != 0) {
+ *ptr++ = i;
+ size++;
+ }
+ }
+ } else {
+ int index, offset;
+
+ /* pack into a 32 byte bitfield */
+ for (i = 0; i < 256; i++) {
+ if (list->chunks[i] != 0) {
+ index = i / 8;
+ offset = i % 8;
+ ptr[index] |= (1 << offset);
+ }
+ }
+ size = 32;
+ }
+ return (size);
+}
+
+int
+sctp_unpack_auth_chunks(const uint8_t *ptr, uint8_t num_chunks,
+ sctp_auth_chklist_t *list)
+{
+ int i;
+ int size;
+
+ if (list == NULL)
+ return (0);
+
+ if (num_chunks <= 32) {
+ /* just pull them, one byte each */
+ for (i = 0; i < num_chunks; i++) {
+ (void)sctp_auth_add_chunk(*ptr++, list);
+ }
+ size = num_chunks;
+ } else {
+ int index, offset;
+
+ /* unpack from a 32 byte bitfield */
+ for (index = 0; index < 32; index++) {
+ for (offset = 0; offset < 8; offset++) {
+ if (ptr[index] & (1 << offset)) {
+ (void)sctp_auth_add_chunk((index * 8) + offset, list);
+ }
+ }
+ }
+ size = 32;
+ }
+ return (size);
+}
+
+/*
+ * allocate structure space for a key of length keylen
+ */
+sctp_key_t *
+sctp_alloc_key(uint32_t keylen)
+{
+ sctp_key_t *new_key;
+
+ SCTP_MALLOC(new_key, sctp_key_t *, sizeof(*new_key) + keylen,
+ SCTP_M_AUTH_KY);
+ if (new_key == NULL) {
+ /* out of memory */
+ return (NULL);
+ }
+ new_key->keylen = keylen;
+ return (new_key);
+}
+
+void
+sctp_free_key(sctp_key_t *key)
+{
+ if (key != NULL)
+ SCTP_FREE(key,SCTP_M_AUTH_KY);
+}
+
+void
+sctp_print_key(sctp_key_t *key, const char *str)
+{
+ uint32_t i;
+
+ if (key == NULL) {
+ SCTP_PRINTF("%s: [Null key]\n", str);
+ return;
+ }
+ SCTP_PRINTF("%s: len %u, ", str, key->keylen);
+ if (key->keylen) {
+ for (i = 0; i < key->keylen; i++)
+ SCTP_PRINTF("%02x", key->key[i]);
+ SCTP_PRINTF("\n");
+ } else {
+ SCTP_PRINTF("[Null key]\n");
+ }
+}
+
+void
+sctp_show_key(sctp_key_t *key, const char *str)
+{
+ uint32_t i;
+
+ if (key == NULL) {
+ SCTP_PRINTF("%s: [Null key]\n", str);
+ return;
+ }
+ SCTP_PRINTF("%s: len %u, ", str, key->keylen);
+ if (key->keylen) {
+ for (i = 0; i < key->keylen; i++)
+ SCTP_PRINTF("%02x", key->key[i]);
+ SCTP_PRINTF("\n");
+ } else {
+ SCTP_PRINTF("[Null key]\n");
+ }
+}
+
+static uint32_t
+sctp_get_keylen(sctp_key_t *key)
+{
+ if (key != NULL)
+ return (key->keylen);
+ else
+ return (0);
+}
+
+/*
+ * generate a new random key of length 'keylen'
+ */
+sctp_key_t *
+sctp_generate_random_key(uint32_t keylen)
+{
+ sctp_key_t *new_key;
+
+ new_key = sctp_alloc_key(keylen);
+ if (new_key == NULL) {
+ /* out of memory */
+ return (NULL);
+ }
+ SCTP_READ_RANDOM(new_key->key, keylen);
+ new_key->keylen = keylen;
+ return (new_key);
+}
+
+sctp_key_t *
+sctp_set_key(uint8_t *key, uint32_t keylen)
+{
+ sctp_key_t *new_key;
+
+ new_key = sctp_alloc_key(keylen);
+ if (new_key == NULL) {
+ /* out of memory */
+ return (NULL);
+ }
+ memcpy(new_key->key, key, keylen);
+ return (new_key);
+}
+
+/*-
+ * given two keys of variable size, compute which key is "larger/smaller"
+ * returns: 1 if key1 > key2
+ * -1 if key1 < key2
+ * 0 if key1 = key2
+ */
+static int
+sctp_compare_key(sctp_key_t *key1, sctp_key_t *key2)
+{
+ uint32_t maxlen;
+ uint32_t i;
+ uint32_t key1len, key2len;
+ uint8_t *key_1, *key_2;
+ uint8_t val1, val2;
+
+ /* sanity/length check */
+ key1len = sctp_get_keylen(key1);
+ key2len = sctp_get_keylen(key2);
+ if ((key1len == 0) && (key2len == 0))
+ return (0);
+ else if (key1len == 0)
+ return (-1);
+ else if (key2len == 0)
+ return (1);
+
+ if (key1len < key2len) {
+ maxlen = key2len;
+ } else {
+ maxlen = key1len;
+ }
+ key_1 = key1->key;
+ key_2 = key2->key;
+ /* check for numeric equality */
+ for (i = 0; i < maxlen; i++) {
+ /* left-pad with zeros */
+ val1 = (i < (maxlen - key1len)) ? 0 : *(key_1++);
+ val2 = (i < (maxlen - key2len)) ? 0 : *(key_2++);
+ if (val1 > val2) {
+ return (1);
+ } else if (val1 < val2) {
+ return (-1);
+ }
+ }
+ /* keys are equal value, so check lengths */
+ if (key1len == key2len)
+ return (0);
+ else if (key1len < key2len)
+ return (-1);
+ else
+ return (1);
+}
+
+/*
+ * generate the concatenated keying material based on the two keys and the
+ * shared key (if available). draft-ietf-tsvwg-auth specifies the specific
+ * order for concatenation
+ */
+sctp_key_t *
+sctp_compute_hashkey(sctp_key_t *key1, sctp_key_t *key2, sctp_key_t *shared)
+{
+ uint32_t keylen;
+ sctp_key_t *new_key;
+ uint8_t *key_ptr;
+
+ keylen = sctp_get_keylen(key1) + sctp_get_keylen(key2) +
+ sctp_get_keylen(shared);
+
+ if (keylen > 0) {
+ /* get space for the new key */
+ new_key = sctp_alloc_key(keylen);
+ if (new_key == NULL) {
+ /* out of memory */
+ return (NULL);
+ }
+ new_key->keylen = keylen;
+ key_ptr = new_key->key;
+ } else {
+ /* all keys empty/null?! */
+ return (NULL);
+ }
+
+ /* concatenate the keys */
+ if (sctp_compare_key(key1, key2) <= 0) {
+ /* key is shared + key1 + key2 */
+ if (sctp_get_keylen(shared)) {
+ memcpy(key_ptr, shared->key, shared->keylen);
+ key_ptr += shared->keylen;
+ }
+ if (sctp_get_keylen(key1)) {
+ memcpy(key_ptr, key1->key, key1->keylen);
+ key_ptr += key1->keylen;
+ }
+ if (sctp_get_keylen(key2)) {
+ memcpy(key_ptr, key2->key, key2->keylen);
+ }
+ } else {
+ /* key is shared + key2 + key1 */
+ if (sctp_get_keylen(shared)) {
+ memcpy(key_ptr, shared->key, shared->keylen);
+ key_ptr += shared->keylen;
+ }
+ if (sctp_get_keylen(key2)) {
+ memcpy(key_ptr, key2->key, key2->keylen);
+ key_ptr += key2->keylen;
+ }
+ if (sctp_get_keylen(key1)) {
+ memcpy(key_ptr, key1->key, key1->keylen);
+ }
+ }
+ return (new_key);
+}
+
+sctp_sharedkey_t *
+sctp_alloc_sharedkey(void)
+{
+ sctp_sharedkey_t *new_key;
+
+ SCTP_MALLOC(new_key, sctp_sharedkey_t *, sizeof(*new_key),
+ SCTP_M_AUTH_KY);
+ if (new_key == NULL) {
+ /* out of memory */
+ return (NULL);
+ }
+ new_key->keyid = 0;
+ new_key->key = NULL;
+ new_key->refcount = 1;
+ new_key->deactivated = 0;
+ return (new_key);
+}
+
+void
+sctp_free_sharedkey(sctp_sharedkey_t *skey)
+{
+ if (skey == NULL)
+ return;
+
+ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(&skey->refcount)) {
+ if (skey->key != NULL)
+ sctp_free_key(skey->key);
+ SCTP_FREE(skey, SCTP_M_AUTH_KY);
+ }
+}
+
+sctp_sharedkey_t *
+sctp_find_sharedkey(struct sctp_keyhead *shared_keys, uint16_t key_id)
+{
+ sctp_sharedkey_t *skey;
+
+ LIST_FOREACH(skey, shared_keys, next) {
+ if (skey->keyid == key_id)
+ return (skey);
+ }
+ return (NULL);
+}
+
+int
+sctp_insert_sharedkey(struct sctp_keyhead *shared_keys,
+ sctp_sharedkey_t *new_skey)
+{
+ sctp_sharedkey_t *skey;
+
+ if ((shared_keys == NULL) || (new_skey == NULL))
+ return (EINVAL);
+
+ /* insert into an empty list? */
+ if (LIST_EMPTY(shared_keys)) {
+ LIST_INSERT_HEAD(shared_keys, new_skey, next);
+ return (0);
+ }
+ /* insert into the existing list, ordered by key id */
+ LIST_FOREACH(skey, shared_keys, next) {
+ if (new_skey->keyid < skey->keyid) {
+ /* insert it before here */
+ LIST_INSERT_BEFORE(skey, new_skey, next);
+ return (0);
+ } else if (new_skey->keyid == skey->keyid) {
+ /* replace the existing key */
+ /* verify this key *can* be replaced */
+ if ((skey->deactivated) || (skey->refcount > 1)) {
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "can't replace shared key id %u\n",
+ new_skey->keyid);
+ return (EBUSY);
+ }
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "replacing shared key id %u\n",
+ new_skey->keyid);
+ LIST_INSERT_BEFORE(skey, new_skey, next);
+ LIST_REMOVE(skey, next);
+ sctp_free_sharedkey(skey);
+ return (0);
+ }
+ if (LIST_NEXT(skey, next) == NULL) {
+ /* belongs at the end of the list */
+ LIST_INSERT_AFTER(skey, new_skey, next);
+ return (0);
+ }
+ }
+ /* shouldn't reach here */
+ return (EINVAL);
+}
+
+void
+sctp_auth_key_acquire(struct sctp_tcb *stcb, uint16_t key_id)
+{
+ sctp_sharedkey_t *skey;
+
+ /* find the shared key */
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, key_id);
+
+ /* bump the ref count */
+ if (skey) {
+ atomic_add_int(&skey->refcount, 1);
+ SCTPDBG(SCTP_DEBUG_AUTH2,
+ "%s: stcb %p key %u refcount acquire to %d\n",
+ __func__, (void *)stcb, key_id, skey->refcount);
+ }
+}
+
+void
+sctp_auth_key_release(struct sctp_tcb *stcb, uint16_t key_id, int so_locked)
+{
+ sctp_sharedkey_t *skey;
+
+ /* find the shared key */
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, key_id);
+
+ /* decrement the ref count */
+ if (skey) {
+ SCTPDBG(SCTP_DEBUG_AUTH2,
+ "%s: stcb %p key %u refcount release to %d\n",
+ __func__, (void *)stcb, key_id, skey->refcount);
+
+ /* see if a notification should be generated */
+ if ((skey->refcount <= 2) && (skey->deactivated)) {
+ /* notify ULP that key is no longer used */
+ sctp_ulp_notify(SCTP_NOTIFY_AUTH_FREE_KEY, stcb,
+ 0, &key_id, so_locked);
+ SCTPDBG(SCTP_DEBUG_AUTH2,
+ "%s: stcb %p key %u no longer used, %d\n",
+ __func__, (void *)stcb, key_id, skey->refcount);
+ }
+ sctp_free_sharedkey(skey);
+ }
+}
+
+static sctp_sharedkey_t *
+sctp_copy_sharedkey(const sctp_sharedkey_t *skey)
+{
+ sctp_sharedkey_t *new_skey;
+
+ if (skey == NULL)
+ return (NULL);
+ new_skey = sctp_alloc_sharedkey();
+ if (new_skey == NULL)
+ return (NULL);
+ if (skey->key != NULL)
+ new_skey->key = sctp_set_key(skey->key->key, skey->key->keylen);
+ else
+ new_skey->key = NULL;
+ new_skey->keyid = skey->keyid;
+ return (new_skey);
+}
+
+int
+sctp_copy_skeylist(const struct sctp_keyhead *src, struct sctp_keyhead *dest)
+{
+ sctp_sharedkey_t *skey, *new_skey;
+ int count = 0;
+
+ if ((src == NULL) || (dest == NULL))
+ return (0);
+ LIST_FOREACH(skey, src, next) {
+ new_skey = sctp_copy_sharedkey(skey);
+ if (new_skey != NULL) {
+ if (sctp_insert_sharedkey(dest, new_skey)) {
+ sctp_free_sharedkey(new_skey);
+ } else {
+ count++;
+ }
+ }
+ }
+ return (count);
+}
+
+sctp_hmaclist_t *
+sctp_alloc_hmaclist(uint16_t num_hmacs)
+{
+ sctp_hmaclist_t *new_list;
+ int alloc_size;
+
+ alloc_size = sizeof(*new_list) + num_hmacs * sizeof(new_list->hmac[0]);
+ SCTP_MALLOC(new_list, sctp_hmaclist_t *, alloc_size,
+ SCTP_M_AUTH_HL);
+ if (new_list == NULL) {
+ /* out of memory */
+ return (NULL);
+ }
+ new_list->max_algo = num_hmacs;
+ new_list->num_algo = 0;
+ return (new_list);
+}
+
+void
+sctp_free_hmaclist(sctp_hmaclist_t *list)
+{
+ if (list != NULL) {
+ SCTP_FREE(list,SCTP_M_AUTH_HL);
+ }
+}
+
+int
+sctp_auth_add_hmacid(sctp_hmaclist_t *list, uint16_t hmac_id)
+{
+ int i;
+ if (list == NULL)
+ return (-1);
+ if (list->num_algo == list->max_algo) {
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: HMAC id list full, ignoring add %u\n", hmac_id);
+ return (-1);
+ }
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ if ((hmac_id != SCTP_AUTH_HMAC_ID_SHA1) &&
+ (hmac_id != SCTP_AUTH_HMAC_ID_SHA256)) {
+#else
+ if (hmac_id != SCTP_AUTH_HMAC_ID_SHA1) {
+#endif
+ return (-1);
+ }
+ /* Now is it already in the list */
+ for (i = 0; i < list->num_algo; i++) {
+ if (list->hmac[i] == hmac_id) {
+ /* already in list */
+ return (-1);
+ }
+ }
+ SCTPDBG(SCTP_DEBUG_AUTH1, "SCTP: add HMAC id %u to list\n", hmac_id);
+ list->hmac[list->num_algo++] = hmac_id;
+ return (0);
+}
+
+sctp_hmaclist_t *
+sctp_copy_hmaclist(sctp_hmaclist_t *list)
+{
+ sctp_hmaclist_t *new_list;
+ int i;
+
+ if (list == NULL)
+ return (NULL);
+ /* get a new list */
+ new_list = sctp_alloc_hmaclist(list->max_algo);
+ if (new_list == NULL)
+ return (NULL);
+ /* copy it */
+ new_list->max_algo = list->max_algo;
+ new_list->num_algo = list->num_algo;
+ for (i = 0; i < list->num_algo; i++)
+ new_list->hmac[i] = list->hmac[i];
+ return (new_list);
+}
+
+sctp_hmaclist_t *
+sctp_default_supported_hmaclist(void)
+{
+ sctp_hmaclist_t *new_list;
+
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ new_list = sctp_alloc_hmaclist(2);
+#else
+ new_list = sctp_alloc_hmaclist(1);
+#endif
+ if (new_list == NULL)
+ return (NULL);
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ /* We prefer SHA256, so list it first */
+ (void)sctp_auth_add_hmacid(new_list, SCTP_AUTH_HMAC_ID_SHA256);
+#endif
+ (void)sctp_auth_add_hmacid(new_list, SCTP_AUTH_HMAC_ID_SHA1);
+ return (new_list);
+}
+
+/*-
+ * HMAC algos are listed in priority/preference order
+ * find the best HMAC id to use for the peer based on local support
+ */
+uint16_t
+sctp_negotiate_hmacid(sctp_hmaclist_t *peer, sctp_hmaclist_t *local)
+{
+ int i, j;
+
+ if ((local == NULL) || (peer == NULL))
+ return (SCTP_AUTH_HMAC_ID_RSVD);
+
+ for (i = 0; i < peer->num_algo; i++) {
+ for (j = 0; j < local->num_algo; j++) {
+ if (peer->hmac[i] == local->hmac[j]) {
+ /* found the "best" one */
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: negotiated peer HMAC id %u\n",
+ peer->hmac[i]);
+ return (peer->hmac[i]);
+ }
+ }
+ }
+ /* didn't find one! */
+ return (SCTP_AUTH_HMAC_ID_RSVD);
+}
+
+/*-
+ * serialize the HMAC algo list and return space used
+ * caller must guarantee ptr has appropriate space
+ */
+int
+sctp_serialize_hmaclist(sctp_hmaclist_t *list, uint8_t *ptr)
+{
+ int i;
+ uint16_t hmac_id;
+
+ if (list == NULL)
+ return (0);
+
+ for (i = 0; i < list->num_algo; i++) {
+ hmac_id = htons(list->hmac[i]);
+ memcpy(ptr, &hmac_id, sizeof(hmac_id));
+ ptr += sizeof(hmac_id);
+ }
+ return (list->num_algo * sizeof(hmac_id));
+}
+
+int
+sctp_verify_hmac_param (struct sctp_auth_hmac_algo *hmacs, uint32_t num_hmacs)
+{
+ uint32_t i;
+
+ for (i = 0; i < num_hmacs; i++) {
+ if (ntohs(hmacs->hmac_ids[i]) == SCTP_AUTH_HMAC_ID_SHA1) {
+ return (0);
+ }
+ }
+ return (-1);
+}
+
+sctp_authinfo_t *
+sctp_alloc_authinfo(void)
+{
+ sctp_authinfo_t *new_authinfo;
+
+ SCTP_MALLOC(new_authinfo, sctp_authinfo_t *, sizeof(*new_authinfo),
+ SCTP_M_AUTH_IF);
+
+ if (new_authinfo == NULL) {
+ /* out of memory */
+ return (NULL);
+ }
+ memset(new_authinfo, 0, sizeof(*new_authinfo));
+ return (new_authinfo);
+}
+
+void
+sctp_free_authinfo(sctp_authinfo_t *authinfo)
+{
+ if (authinfo == NULL)
+ return;
+
+ if (authinfo->random != NULL)
+ sctp_free_key(authinfo->random);
+ if (authinfo->peer_random != NULL)
+ sctp_free_key(authinfo->peer_random);
+ if (authinfo->assoc_key != NULL)
+ sctp_free_key(authinfo->assoc_key);
+ if (authinfo->recv_key != NULL)
+ sctp_free_key(authinfo->recv_key);
+
+ /* We are NOT dynamically allocating authinfo's right now... */
+ /* SCTP_FREE(authinfo, SCTP_M_AUTH_??); */
+}
+
+uint32_t
+sctp_get_auth_chunk_len(uint16_t hmac_algo)
+{
+ int size;
+
+ size = sizeof(struct sctp_auth_chunk) + sctp_get_hmac_digest_len(hmac_algo);
+ return (SCTP_SIZE32(size));
+}
+
+uint32_t
+sctp_get_hmac_digest_len(uint16_t hmac_algo)
+{
+ switch (hmac_algo) {
+ case SCTP_AUTH_HMAC_ID_SHA1:
+ return (SCTP_AUTH_DIGEST_LEN_SHA1);
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ case SCTP_AUTH_HMAC_ID_SHA256:
+ return (SCTP_AUTH_DIGEST_LEN_SHA256);
+#endif
+ default:
+ /* unknown HMAC algorithm: can't do anything */
+ return (0);
+ } /* end switch */
+}
+
+static inline int
+sctp_get_hmac_block_len(uint16_t hmac_algo)
+{
+ switch (hmac_algo) {
+ case SCTP_AUTH_HMAC_ID_SHA1:
+ return (64);
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ case SCTP_AUTH_HMAC_ID_SHA256:
+ return (64);
+#endif
+ case SCTP_AUTH_HMAC_ID_RSVD:
+ default:
+ /* unknown HMAC algorithm: can't do anything */
+ return (0);
+ } /* end switch */
+}
+
+#if defined(__Userspace__)
+/* __Userspace__ SHA1_Init is defined in libcrypto.a (libssl-dev on Ubuntu) */
+#endif
+static void
+sctp_hmac_init(uint16_t hmac_algo, sctp_hash_context_t *ctx)
+{
+ switch (hmac_algo) {
+ case SCTP_AUTH_HMAC_ID_SHA1:
+ SCTP_SHA1_INIT(&ctx->sha1);
+ break;
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ case SCTP_AUTH_HMAC_ID_SHA256:
+ SCTP_SHA256_INIT(&ctx->sha256);
+ break;
+#endif
+ case SCTP_AUTH_HMAC_ID_RSVD:
+ default:
+ /* unknown HMAC algorithm: can't do anything */
+ return;
+ } /* end switch */
+}
+
+static void
+sctp_hmac_update(uint16_t hmac_algo, sctp_hash_context_t *ctx,
+ uint8_t *text, uint32_t textlen)
+{
+ switch (hmac_algo) {
+ case SCTP_AUTH_HMAC_ID_SHA1:
+ SCTP_SHA1_UPDATE(&ctx->sha1, text, textlen);
+ break;
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ case SCTP_AUTH_HMAC_ID_SHA256:
+ SCTP_SHA256_UPDATE(&ctx->sha256, text, textlen);
+ break;
+#endif
+ case SCTP_AUTH_HMAC_ID_RSVD:
+ default:
+ /* unknown HMAC algorithm: can't do anything */
+ return;
+ } /* end switch */
+}
+
+static void
+sctp_hmac_final(uint16_t hmac_algo, sctp_hash_context_t *ctx,
+ uint8_t *digest)
+{
+ switch (hmac_algo) {
+ case SCTP_AUTH_HMAC_ID_SHA1:
+ SCTP_SHA1_FINAL(digest, &ctx->sha1);
+ break;
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ case SCTP_AUTH_HMAC_ID_SHA256:
+ SCTP_SHA256_FINAL(digest, &ctx->sha256);
+ break;
+#endif
+ case SCTP_AUTH_HMAC_ID_RSVD:
+ default:
+ /* unknown HMAC algorithm: can't do anything */
+ return;
+ } /* end switch */
+}
+
+/*-
+ * Keyed-Hashing for Message Authentication: FIPS 198 (RFC 2104)
+ *
+ * Compute the HMAC digest using the desired hash key, text, and HMAC
+ * algorithm. Resulting digest is placed in 'digest' and digest length
+ * is returned, if the HMAC was performed.
+ *
+ * WARNING: it is up to the caller to supply sufficient space to hold the
+ * resultant digest.
+ */
+uint32_t
+sctp_hmac(uint16_t hmac_algo, uint8_t *key, uint32_t keylen,
+ uint8_t *text, uint32_t textlen, uint8_t *digest)
+{
+ uint32_t digestlen;
+ uint32_t blocklen;
+ sctp_hash_context_t ctx;
+ uint8_t ipad[128], opad[128]; /* keyed hash inner/outer pads */
+ uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX];
+ uint32_t i;
+
+ /* sanity check the material and length */
+ if ((key == NULL) || (keylen == 0) || (text == NULL) ||
+ (textlen == 0) || (digest == NULL)) {
+ /* can't do HMAC with empty key or text or digest store */
+ return (0);
+ }
+ /* validate the hmac algo and get the digest length */
+ digestlen = sctp_get_hmac_digest_len(hmac_algo);
+ if (digestlen == 0)
+ return (0);
+
+ /* hash the key if it is longer than the hash block size */
+ blocklen = sctp_get_hmac_block_len(hmac_algo);
+ if (keylen > blocklen) {
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, key, keylen);
+ sctp_hmac_final(hmac_algo, &ctx, temp);
+ /* set the hashed key as the key */
+ keylen = digestlen;
+ key = temp;
+ }
+ /* initialize the inner/outer pads with the key and "append" zeroes */
+ memset(ipad, 0, blocklen);
+ memset(opad, 0, blocklen);
+ memcpy(ipad, key, keylen);
+ memcpy(opad, key, keylen);
+
+ /* XOR the key with ipad and opad values */
+ for (i = 0; i < blocklen; i++) {
+ ipad[i] ^= 0x36;
+ opad[i] ^= 0x5c;
+ }
+
+ /* perform inner hash */
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, ipad, blocklen);
+ sctp_hmac_update(hmac_algo, &ctx, text, textlen);
+ sctp_hmac_final(hmac_algo, &ctx, temp);
+
+ /* perform outer hash */
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, opad, blocklen);
+ sctp_hmac_update(hmac_algo, &ctx, temp, digestlen);
+ sctp_hmac_final(hmac_algo, &ctx, digest);
+
+ return (digestlen);
+}
+
+/* mbuf version */
+uint32_t
+sctp_hmac_m(uint16_t hmac_algo, uint8_t *key, uint32_t keylen,
+ struct mbuf *m, uint32_t m_offset, uint8_t *digest, uint32_t trailer)
+{
+ uint32_t digestlen;
+ uint32_t blocklen;
+ sctp_hash_context_t ctx;
+ uint8_t ipad[128], opad[128]; /* keyed hash inner/outer pads */
+ uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX];
+ uint32_t i;
+ struct mbuf *m_tmp;
+
+ /* sanity check the material and length */
+ if ((key == NULL) || (keylen == 0) || (m == NULL) || (digest == NULL)) {
+ /* can't do HMAC with empty key or text or digest store */
+ return (0);
+ }
+ /* validate the hmac algo and get the digest length */
+ digestlen = sctp_get_hmac_digest_len(hmac_algo);
+ if (digestlen == 0)
+ return (0);
+
+ /* hash the key if it is longer than the hash block size */
+ blocklen = sctp_get_hmac_block_len(hmac_algo);
+ if (keylen > blocklen) {
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, key, keylen);
+ sctp_hmac_final(hmac_algo, &ctx, temp);
+ /* set the hashed key as the key */
+ keylen = digestlen;
+ key = temp;
+ }
+ /* initialize the inner/outer pads with the key and "append" zeroes */
+ memset(ipad, 0, blocklen);
+ memset(opad, 0, blocklen);
+ memcpy(ipad, key, keylen);
+ memcpy(opad, key, keylen);
+
+ /* XOR the key with ipad and opad values */
+ for (i = 0; i < blocklen; i++) {
+ ipad[i] ^= 0x36;
+ opad[i] ^= 0x5c;
+ }
+
+ /* perform inner hash */
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, ipad, blocklen);
+ /* find the correct starting mbuf and offset (get start of text) */
+ m_tmp = m;
+ while ((m_tmp != NULL) && (m_offset >= (uint32_t) SCTP_BUF_LEN(m_tmp))) {
+ m_offset -= SCTP_BUF_LEN(m_tmp);
+ m_tmp = SCTP_BUF_NEXT(m_tmp);
+ }
+ /* now use the rest of the mbuf chain for the text */
+ while (m_tmp != NULL) {
+ if ((SCTP_BUF_NEXT(m_tmp) == NULL) && trailer) {
+ sctp_hmac_update(hmac_algo, &ctx, mtod(m_tmp, uint8_t *) + m_offset,
+ SCTP_BUF_LEN(m_tmp) - (trailer+m_offset));
+ } else {
+ sctp_hmac_update(hmac_algo, &ctx, mtod(m_tmp, uint8_t *) + m_offset,
+ SCTP_BUF_LEN(m_tmp) - m_offset);
+ }
+
+ /* clear the offset since it's only for the first mbuf */
+ m_offset = 0;
+ m_tmp = SCTP_BUF_NEXT(m_tmp);
+ }
+ sctp_hmac_final(hmac_algo, &ctx, temp);
+
+ /* perform outer hash */
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, opad, blocklen);
+ sctp_hmac_update(hmac_algo, &ctx, temp, digestlen);
+ sctp_hmac_final(hmac_algo, &ctx, digest);
+
+ return (digestlen);
+}
+
+/*
+ * computes the requested HMAC using a key struct (which may be modified if
+ * the keylen exceeds the HMAC block len).
+ */
+uint32_t
+sctp_compute_hmac(uint16_t hmac_algo, sctp_key_t *key, uint8_t *text,
+ uint32_t textlen, uint8_t *digest)
+{
+ uint32_t digestlen;
+ uint32_t blocklen;
+ sctp_hash_context_t ctx;
+ uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX];
+
+ /* sanity check */
+ if ((key == NULL) || (text == NULL) || (textlen == 0) ||
+ (digest == NULL)) {
+ /* can't do HMAC with empty key or text or digest store */
+ return (0);
+ }
+ /* validate the hmac algo and get the digest length */
+ digestlen = sctp_get_hmac_digest_len(hmac_algo);
+ if (digestlen == 0)
+ return (0);
+
+ /* hash the key if it is longer than the hash block size */
+ blocklen = sctp_get_hmac_block_len(hmac_algo);
+ if (key->keylen > blocklen) {
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, key->key, key->keylen);
+ sctp_hmac_final(hmac_algo, &ctx, temp);
+ /* save the hashed key as the new key */
+ key->keylen = digestlen;
+ memcpy(key->key, temp, key->keylen);
+ }
+ return (sctp_hmac(hmac_algo, key->key, key->keylen, text, textlen,
+ digest));
+}
+
+/* mbuf version */
+uint32_t
+sctp_compute_hmac_m(uint16_t hmac_algo, sctp_key_t *key, struct mbuf *m,
+ uint32_t m_offset, uint8_t *digest)
+{
+ uint32_t digestlen;
+ uint32_t blocklen;
+ sctp_hash_context_t ctx;
+ uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX];
+
+ /* sanity check */
+ if ((key == NULL) || (m == NULL) || (digest == NULL)) {
+ /* can't do HMAC with empty key or text or digest store */
+ return (0);
+ }
+ /* validate the hmac algo and get the digest length */
+ digestlen = sctp_get_hmac_digest_len(hmac_algo);
+ if (digestlen == 0)
+ return (0);
+
+ /* hash the key if it is longer than the hash block size */
+ blocklen = sctp_get_hmac_block_len(hmac_algo);
+ if (key->keylen > blocklen) {
+ sctp_hmac_init(hmac_algo, &ctx);
+ sctp_hmac_update(hmac_algo, &ctx, key->key, key->keylen);
+ sctp_hmac_final(hmac_algo, &ctx, temp);
+ /* save the hashed key as the new key */
+ key->keylen = digestlen;
+ memcpy(key->key, temp, key->keylen);
+ }
+ return (sctp_hmac_m(hmac_algo, key->key, key->keylen, m, m_offset, digest, 0));
+}
+
+int
+sctp_auth_is_supported_hmac(sctp_hmaclist_t *list, uint16_t id)
+{
+ int i;
+
+ if ((list == NULL) || (id == SCTP_AUTH_HMAC_ID_RSVD))
+ return (0);
+
+ for (i = 0; i < list->num_algo; i++)
+ if (list->hmac[i] == id)
+ return (1);
+
+ /* not in the list */
+ return (0);
+}
+
+/*-
+ * clear any cached key(s) if they match the given key id on an association.
+ * the cached key(s) will be recomputed and re-cached at next use.
+ * ASSUMES TCB_LOCK is already held
+ */
+void
+sctp_clear_cachedkeys(struct sctp_tcb *stcb, uint16_t keyid)
+{
+ if (stcb == NULL)
+ return;
+
+ if (keyid == stcb->asoc.authinfo.assoc_keyid) {
+ sctp_free_key(stcb->asoc.authinfo.assoc_key);
+ stcb->asoc.authinfo.assoc_key = NULL;
+ }
+ if (keyid == stcb->asoc.authinfo.recv_keyid) {
+ sctp_free_key(stcb->asoc.authinfo.recv_key);
+ stcb->asoc.authinfo.recv_key = NULL;
+ }
+}
+
+/*-
+ * clear any cached key(s) if they match the given key id for all assocs on
+ * an endpoint.
+ * ASSUMES INP_WLOCK is already held
+ */
+void
+sctp_clear_cachedkeys_ep(struct sctp_inpcb *inp, uint16_t keyid)
+{
+ struct sctp_tcb *stcb;
+
+ if (inp == NULL)
+ return;
+
+ /* clear the cached keys on all assocs on this instance */
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ sctp_clear_cachedkeys(stcb, keyid);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+}
+
+/*-
+ * delete a shared key from an association
+ * ASSUMES TCB_LOCK is already held
+ */
+int
+sctp_delete_sharedkey(struct sctp_tcb *stcb, uint16_t keyid)
+{
+ sctp_sharedkey_t *skey;
+
+ if (stcb == NULL)
+ return (-1);
+
+ /* is the keyid the assoc active sending key */
+ if (keyid == stcb->asoc.authinfo.active_keyid)
+ return (-1);
+
+ /* does the key exist? */
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, keyid);
+ if (skey == NULL)
+ return (-1);
+
+ /* are there other refcount holders on the key? */
+ if (skey->refcount > 1)
+ return (-1);
+
+ /* remove it */
+ LIST_REMOVE(skey, next);
+ sctp_free_sharedkey(skey); /* frees skey->key as well */
+
+ /* clear any cached keys */
+ sctp_clear_cachedkeys(stcb, keyid);
+ return (0);
+}
+
+/*-
+ * deletes a shared key from the endpoint
+ * ASSUMES INP_WLOCK is already held
+ */
+int
+sctp_delete_sharedkey_ep(struct sctp_inpcb *inp, uint16_t keyid)
+{
+ sctp_sharedkey_t *skey;
+
+ if (inp == NULL)
+ return (-1);
+
+ /* is the keyid the active sending key on the endpoint */
+ if (keyid == inp->sctp_ep.default_keyid)
+ return (-1);
+
+ /* does the key exist? */
+ skey = sctp_find_sharedkey(&inp->sctp_ep.shared_keys, keyid);
+ if (skey == NULL)
+ return (-1);
+
+ /* endpoint keys are not refcounted */
+
+ /* remove it */
+ LIST_REMOVE(skey, next);
+ sctp_free_sharedkey(skey); /* frees skey->key as well */
+
+ /* clear any cached keys */
+ sctp_clear_cachedkeys_ep(inp, keyid);
+ return (0);
+}
+
+/*-
+ * set the active key on an association
+ * ASSUMES TCB_LOCK is already held
+ */
+int
+sctp_auth_setactivekey(struct sctp_tcb *stcb, uint16_t keyid)
+{
+ sctp_sharedkey_t *skey = NULL;
+
+ /* find the key on the assoc */
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, keyid);
+ if (skey == NULL) {
+ /* that key doesn't exist */
+ return (-1);
+ }
+ if ((skey->deactivated) && (skey->refcount > 1)) {
+ /* can't reactivate a deactivated key with other refcounts */
+ return (-1);
+ }
+
+ /* set the (new) active key */
+ stcb->asoc.authinfo.active_keyid = keyid;
+ /* reset the deactivated flag */
+ skey->deactivated = 0;
+
+ return (0);
+}
+
+/*-
+ * set the active key on an endpoint
+ * ASSUMES INP_WLOCK is already held
+ */
+int
+sctp_auth_setactivekey_ep(struct sctp_inpcb *inp, uint16_t keyid)
+{
+ sctp_sharedkey_t *skey;
+
+ /* find the key */
+ skey = sctp_find_sharedkey(&inp->sctp_ep.shared_keys, keyid);
+ if (skey == NULL) {
+ /* that key doesn't exist */
+ return (-1);
+ }
+ inp->sctp_ep.default_keyid = keyid;
+ return (0);
+}
+
+/*-
+ * deactivates a shared key from the association
+ * ASSUMES INP_WLOCK is already held
+ */
+int
+sctp_deact_sharedkey(struct sctp_tcb *stcb, uint16_t keyid)
+{
+ sctp_sharedkey_t *skey;
+
+ if (stcb == NULL)
+ return (-1);
+
+ /* is the keyid the assoc active sending key */
+ if (keyid == stcb->asoc.authinfo.active_keyid)
+ return (-1);
+
+ /* does the key exist? */
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, keyid);
+ if (skey == NULL)
+ return (-1);
+
+ /* are there other refcount holders on the key? */
+ if (skey->refcount == 1) {
+ /* no other users, send a notification for this key */
+ sctp_ulp_notify(SCTP_NOTIFY_AUTH_FREE_KEY, stcb, 0, &keyid,
+ SCTP_SO_LOCKED);
+ }
+
+ /* mark the key as deactivated */
+ skey->deactivated = 1;
+
+ return (0);
+}
+
+/*-
+ * deactivates a shared key from the endpoint
+ * ASSUMES INP_WLOCK is already held
+ */
+int
+sctp_deact_sharedkey_ep(struct sctp_inpcb *inp, uint16_t keyid)
+{
+ sctp_sharedkey_t *skey;
+
+ if (inp == NULL)
+ return (-1);
+
+ /* is the keyid the active sending key on the endpoint */
+ if (keyid == inp->sctp_ep.default_keyid)
+ return (-1);
+
+ /* does the key exist? */
+ skey = sctp_find_sharedkey(&inp->sctp_ep.shared_keys, keyid);
+ if (skey == NULL)
+ return (-1);
+
+ /* endpoint keys are not refcounted */
+
+ /* remove it */
+ LIST_REMOVE(skey, next);
+ sctp_free_sharedkey(skey); /* frees skey->key as well */
+
+ return (0);
+}
+
+/*
+ * get local authentication parameters from cookie (from INIT-ACK)
+ */
+void
+sctp_auth_get_cookie_params(struct sctp_tcb *stcb, struct mbuf *m,
+ uint32_t offset, uint32_t length)
+{
+ struct sctp_paramhdr *phdr, tmp_param;
+ uint16_t plen, ptype;
+ uint8_t random_store[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_auth_random *p_random = NULL;
+ uint16_t random_len = 0;
+ uint8_t hmacs_store[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_auth_hmac_algo *hmacs = NULL;
+ uint16_t hmacs_len = 0;
+ uint8_t chunks_store[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_auth_chunk_list *chunks = NULL;
+ uint16_t num_chunks = 0;
+ sctp_key_t *new_key;
+ uint32_t keylen;
+
+ /* convert to upper bound */
+ length += offset;
+
+ phdr = (struct sctp_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_paramhdr), (uint8_t *)&tmp_param);
+ while (phdr != NULL) {
+ ptype = ntohs(phdr->param_type);
+ plen = ntohs(phdr->param_length);
+
+ if ((plen < sizeof(struct sctp_paramhdr)) ||
+ (offset + plen > length))
+ break;
+
+ if (ptype == SCTP_RANDOM) {
+ if (plen > sizeof(random_store))
+ break;
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)random_store, plen);
+ if (phdr == NULL)
+ return;
+ /* save the random and length for the key */
+ p_random = (struct sctp_auth_random *)phdr;
+ random_len = plen - sizeof(*p_random);
+ } else if (ptype == SCTP_HMAC_LIST) {
+ uint16_t num_hmacs;
+ uint16_t i;
+
+ if (plen > sizeof(hmacs_store))
+ break;
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)hmacs_store, plen);
+ if (phdr == NULL)
+ return;
+ /* save the hmacs list and num for the key */
+ hmacs = (struct sctp_auth_hmac_algo *)phdr;
+ hmacs_len = plen - sizeof(*hmacs);
+ num_hmacs = hmacs_len / sizeof(hmacs->hmac_ids[0]);
+ if (stcb->asoc.local_hmacs != NULL)
+ sctp_free_hmaclist(stcb->asoc.local_hmacs);
+ stcb->asoc.local_hmacs = sctp_alloc_hmaclist(num_hmacs);
+ if (stcb->asoc.local_hmacs != NULL) {
+ for (i = 0; i < num_hmacs; i++) {
+ (void)sctp_auth_add_hmacid(stcb->asoc.local_hmacs,
+ ntohs(hmacs->hmac_ids[i]));
+ }
+ }
+ } else if (ptype == SCTP_CHUNK_LIST) {
+ int i;
+
+ if (plen > sizeof(chunks_store))
+ break;
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)chunks_store, plen);
+ if (phdr == NULL)
+ return;
+ chunks = (struct sctp_auth_chunk_list *)phdr;
+ num_chunks = plen - sizeof(*chunks);
+ /* save chunks list and num for the key */
+ if (stcb->asoc.local_auth_chunks != NULL)
+ sctp_clear_chunklist(stcb->asoc.local_auth_chunks);
+ else
+ stcb->asoc.local_auth_chunks = sctp_alloc_chunklist();
+ for (i = 0; i < num_chunks; i++) {
+ (void)sctp_auth_add_chunk(chunks->chunk_types[i],
+ stcb->asoc.local_auth_chunks);
+ }
+ }
+ /* get next parameter */
+ offset += SCTP_SIZE32(plen);
+ if (offset + sizeof(struct sctp_paramhdr) > length)
+ break;
+ phdr = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr),
+ (uint8_t *)&tmp_param);
+ }
+ /* concatenate the full random key */
+ keylen = sizeof(*p_random) + random_len + sizeof(*hmacs) + hmacs_len;
+ if (chunks != NULL) {
+ keylen += sizeof(*chunks) + num_chunks;
+ }
+ new_key = sctp_alloc_key(keylen);
+ if (new_key != NULL) {
+ /* copy in the RANDOM */
+ if (p_random != NULL) {
+ keylen = sizeof(*p_random) + random_len;
+ memcpy(new_key->key, p_random, keylen);
+ } else {
+ keylen = 0;
+ }
+ /* append in the AUTH chunks */
+ if (chunks != NULL) {
+ memcpy(new_key->key + keylen, chunks,
+ sizeof(*chunks) + num_chunks);
+ keylen += sizeof(*chunks) + num_chunks;
+ }
+ /* append in the HMACs */
+ if (hmacs != NULL) {
+ memcpy(new_key->key + keylen, hmacs,
+ sizeof(*hmacs) + hmacs_len);
+ }
+ }
+ if (stcb->asoc.authinfo.random != NULL)
+ sctp_free_key(stcb->asoc.authinfo.random);
+ stcb->asoc.authinfo.random = new_key;
+ stcb->asoc.authinfo.random_len = random_len;
+ sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.assoc_keyid);
+ sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.recv_keyid);
+
+ /* negotiate what HMAC to use for the peer */
+ stcb->asoc.peer_hmac_id = sctp_negotiate_hmacid(stcb->asoc.peer_hmacs,
+ stcb->asoc.local_hmacs);
+
+ /* copy defaults from the endpoint */
+ /* FIX ME: put in cookie? */
+ stcb->asoc.authinfo.active_keyid = stcb->sctp_ep->sctp_ep.default_keyid;
+ /* copy out the shared key list (by reference) from the endpoint */
+ (void)sctp_copy_skeylist(&stcb->sctp_ep->sctp_ep.shared_keys,
+ &stcb->asoc.shared_keys);
+}
+
+/*
+ * compute and fill in the HMAC digest for a packet
+ */
+void
+sctp_fill_hmac_digest_m(struct mbuf *m, uint32_t auth_offset,
+ struct sctp_auth_chunk *auth, struct sctp_tcb *stcb, uint16_t keyid)
+{
+ uint32_t digestlen;
+ sctp_sharedkey_t *skey;
+ sctp_key_t *key;
+
+ if ((stcb == NULL) || (auth == NULL))
+ return;
+
+ /* zero the digest + chunk padding */
+ digestlen = sctp_get_hmac_digest_len(stcb->asoc.peer_hmac_id);
+ memset(auth->hmac, 0, SCTP_SIZE32(digestlen));
+
+ /* is the desired key cached? */
+ if ((keyid != stcb->asoc.authinfo.assoc_keyid) ||
+ (stcb->asoc.authinfo.assoc_key == NULL)) {
+ if (stcb->asoc.authinfo.assoc_key != NULL) {
+ /* free the old cached key */
+ sctp_free_key(stcb->asoc.authinfo.assoc_key);
+ }
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, keyid);
+ /* the only way skey is NULL is if null key id 0 is used */
+ if (skey != NULL)
+ key = skey->key;
+ else
+ key = NULL;
+ /* compute a new assoc key and cache it */
+ stcb->asoc.authinfo.assoc_key =
+ sctp_compute_hashkey(stcb->asoc.authinfo.random,
+ stcb->asoc.authinfo.peer_random, key);
+ stcb->asoc.authinfo.assoc_keyid = keyid;
+ SCTPDBG(SCTP_DEBUG_AUTH1, "caching key id %u\n",
+ stcb->asoc.authinfo.assoc_keyid);
+#ifdef SCTP_DEBUG
+ if (SCTP_AUTH_DEBUG)
+ sctp_print_key(stcb->asoc.authinfo.assoc_key,
+ "Assoc Key");
+#endif
+ }
+
+ /* set in the active key id */
+ auth->shared_key_id = htons(keyid);
+
+ /* compute and fill in the digest */
+ (void)sctp_compute_hmac_m(stcb->asoc.peer_hmac_id, stcb->asoc.authinfo.assoc_key,
+ m, auth_offset, auth->hmac);
+}
+
+static void
+sctp_zero_m(struct mbuf *m, uint32_t m_offset, uint32_t size)
+{
+ struct mbuf *m_tmp;
+ uint8_t *data;
+
+ /* sanity check */
+ if (m == NULL)
+ return;
+
+ /* find the correct starting mbuf and offset (get start position) */
+ m_tmp = m;
+ while ((m_tmp != NULL) && (m_offset >= (uint32_t) SCTP_BUF_LEN(m_tmp))) {
+ m_offset -= SCTP_BUF_LEN(m_tmp);
+ m_tmp = SCTP_BUF_NEXT(m_tmp);
+ }
+ /* now use the rest of the mbuf chain */
+ while ((m_tmp != NULL) && (size > 0)) {
+ data = mtod(m_tmp, uint8_t *) + m_offset;
+ if (size > (uint32_t)(SCTP_BUF_LEN(m_tmp) - m_offset)) {
+ memset(data, 0, SCTP_BUF_LEN(m_tmp) - m_offset);
+ size -= SCTP_BUF_LEN(m_tmp) - m_offset;
+ } else {
+ memset(data, 0, size);
+ size = 0;
+ }
+ /* clear the offset since it's only for the first mbuf */
+ m_offset = 0;
+ m_tmp = SCTP_BUF_NEXT(m_tmp);
+ }
+}
+
+/*-
+ * process the incoming Authentication chunk
+ * return codes:
+ * -1 on any authentication error
+ * 0 on authentication verification
+ */
+int
+sctp_handle_auth(struct sctp_tcb *stcb, struct sctp_auth_chunk *auth,
+ struct mbuf *m, uint32_t offset)
+{
+ uint16_t chunklen;
+ uint16_t shared_key_id;
+ uint16_t hmac_id;
+ sctp_sharedkey_t *skey;
+ uint32_t digestlen;
+ uint8_t digest[SCTP_AUTH_DIGEST_LEN_MAX];
+ uint8_t computed_digest[SCTP_AUTH_DIGEST_LEN_MAX];
+
+ /* auth is checked for NULL by caller */
+ chunklen = ntohs(auth->ch.chunk_length);
+ if (chunklen < sizeof(*auth)) {
+ SCTP_STAT_INCR(sctps_recvauthfailed);
+ return (-1);
+ }
+ SCTP_STAT_INCR(sctps_recvauth);
+
+ /* get the auth params */
+ shared_key_id = ntohs(auth->shared_key_id);
+ hmac_id = ntohs(auth->hmac_id);
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP AUTH Chunk: shared key %u, HMAC id %u\n",
+ shared_key_id, hmac_id);
+
+#if defined(__Userspace__) && defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ return (0);
+#endif
+ /* is the indicated HMAC supported? */
+ if (!sctp_auth_is_supported_hmac(stcb->asoc.local_hmacs, hmac_id)) {
+ struct mbuf *op_err;
+ struct sctp_error_auth_invalid_hmac *cause;
+
+ SCTP_STAT_INCR(sctps_recvivalhmacid);
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP Auth: unsupported HMAC id %u\n",
+ hmac_id);
+ /*
+ * report this in an Error Chunk: Unsupported HMAC
+ * Identifier
+ */
+ op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_error_auth_invalid_hmac),
+ 0, M_NOWAIT, 1, MT_HEADER);
+ if (op_err != NULL) {
+ /* pre-reserve some space */
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr));
+ /* fill in the error */
+ cause = mtod(op_err, struct sctp_error_auth_invalid_hmac *);
+ cause->cause.code = htons(SCTP_CAUSE_UNSUPPORTED_HMACID);
+ cause->cause.length = htons(sizeof(struct sctp_error_auth_invalid_hmac));
+ cause->hmac_id = ntohs(hmac_id);
+ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_error_auth_invalid_hmac);
+ /* queue it */
+ sctp_queue_op_err(stcb, op_err);
+ }
+ return (-1);
+ }
+ /* get the indicated shared key, if available */
+ if ((stcb->asoc.authinfo.recv_key == NULL) ||
+ (stcb->asoc.authinfo.recv_keyid != shared_key_id)) {
+ /* find the shared key on the assoc first */
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys,
+ shared_key_id);
+ /* if the shared key isn't found, discard the chunk */
+ if (skey == NULL) {
+ SCTP_STAT_INCR(sctps_recvivalkeyid);
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP Auth: unknown key id %u\n",
+ shared_key_id);
+ return (-1);
+ }
+ /* generate a notification if this is a new key id */
+ if (stcb->asoc.authinfo.recv_keyid != shared_key_id) {
+ sctp_ulp_notify(SCTP_NOTIFY_AUTH_NEW_KEY, stcb, 0,
+ &shared_key_id, SCTP_SO_NOT_LOCKED);
+ }
+ /* compute a new recv assoc key and cache it */
+ if (stcb->asoc.authinfo.recv_key != NULL)
+ sctp_free_key(stcb->asoc.authinfo.recv_key);
+ stcb->asoc.authinfo.recv_key =
+ sctp_compute_hashkey(stcb->asoc.authinfo.random,
+ stcb->asoc.authinfo.peer_random, skey->key);
+ stcb->asoc.authinfo.recv_keyid = shared_key_id;
+#ifdef SCTP_DEBUG
+ if (SCTP_AUTH_DEBUG)
+ sctp_print_key(stcb->asoc.authinfo.recv_key, "Recv Key");
+#endif
+ }
+ /* validate the digest length */
+ digestlen = sctp_get_hmac_digest_len(hmac_id);
+ if (chunklen < (sizeof(*auth) + digestlen)) {
+ /* invalid digest length */
+ SCTP_STAT_INCR(sctps_recvauthfailed);
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP Auth: chunk too short for HMAC\n");
+ return (-1);
+ }
+ /* save a copy of the digest, zero the pseudo header, and validate */
+ memcpy(digest, auth->hmac, digestlen);
+ sctp_zero_m(m, offset + sizeof(*auth), SCTP_SIZE32(digestlen));
+ (void)sctp_compute_hmac_m(hmac_id, stcb->asoc.authinfo.recv_key,
+ m, offset, computed_digest);
+
+ /* compare the computed digest with the one in the AUTH chunk */
+ if (timingsafe_bcmp(digest, computed_digest, digestlen) != 0) {
+ SCTP_STAT_INCR(sctps_recvauthfailed);
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP Auth: HMAC digest check failed\n");
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Generate NOTIFICATION
+ */
+void
+sctp_notify_authentication(struct sctp_tcb *stcb, uint32_t indication,
+ uint16_t keyid, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_authkey_event *auth;
+ struct sctp_queued_to_read *control;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_AUTHEVNT))
+ /* event not enabled */
+ return;
+
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_authkey_event),
+ 0, M_NOWAIT, 1, MT_HEADER);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+
+ SCTP_BUF_LEN(m_notify) = 0;
+ auth = mtod(m_notify, struct sctp_authkey_event *);
+ memset(auth, 0, sizeof(struct sctp_authkey_event));
+ auth->auth_type = SCTP_AUTHENTICATION_EVENT;
+ auth->auth_flags = 0;
+ auth->auth_length = sizeof(*auth);
+ auth->auth_keynumber = keyid;
+ /* XXXMT: The following is BSD specific. */
+ if (indication == SCTP_AUTH_NEW_KEY) {
+ auth->auth_altkeynumber = stcb->asoc.authinfo.recv_keyid;
+ } else {
+ auth->auth_altkeynumber = 0;
+ }
+ auth->auth_indication = indication;
+ auth->auth_assoc_id = sctp_get_associd(stcb);
+
+ SCTP_BUF_LEN(m_notify) = sizeof(*auth);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0, m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+/*-
+ * validates the AUTHentication related parameters in an INIT/INIT-ACK
+ * Note: currently only used for INIT as INIT-ACK is handled inline
+ * with sctp_load_addresses_from_init()
+ */
+int
+sctp_validate_init_auth_params(struct mbuf *m, int offset, int limit)
+{
+ struct sctp_paramhdr *phdr, param_buf;
+ uint16_t ptype, plen;
+ int peer_supports_asconf = 0;
+ int peer_supports_auth = 0;
+ int got_random = 0, got_hmacs = 0, got_chklist = 0;
+ uint8_t saw_asconf = 0;
+ uint8_t saw_asconf_ack = 0;
+
+ /* go through each of the params. */
+ phdr = sctp_get_next_param(m, offset, &param_buf, sizeof(param_buf));
+ while (phdr) {
+ ptype = ntohs(phdr->param_type);
+ plen = ntohs(phdr->param_length);
+
+ if (offset + plen > limit) {
+ break;
+ }
+ if (plen < sizeof(struct sctp_paramhdr)) {
+ break;
+ }
+ if (ptype == SCTP_SUPPORTED_CHUNK_EXT) {
+ /* A supported extension chunk */
+ struct sctp_supported_chunk_types_param *pr_supported;
+ uint8_t local_store[SCTP_SMALL_CHUNK_STORE];
+ int num_ent, i;
+
+ if (plen > sizeof(local_store)) {
+ break;
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&local_store,
+ plen);
+ if (phdr == NULL) {
+ return (-1);
+ }
+ pr_supported = (struct sctp_supported_chunk_types_param *)phdr;
+ num_ent = plen - sizeof(struct sctp_paramhdr);
+ for (i = 0; i < num_ent; i++) {
+ switch (pr_supported->chunk_types[i]) {
+ case SCTP_ASCONF:
+ case SCTP_ASCONF_ACK:
+ peer_supports_asconf = 1;
+ break;
+ default:
+ /* one we don't care about */
+ break;
+ }
+ }
+ } else if (ptype == SCTP_RANDOM) {
+ /* enforce the random length */
+ if (plen != (sizeof(struct sctp_auth_random) +
+ SCTP_AUTH_RANDOM_SIZE_REQUIRED)) {
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: invalid RANDOM len\n");
+ return (-1);
+ }
+ got_random = 1;
+ } else if (ptype == SCTP_HMAC_LIST) {
+ struct sctp_auth_hmac_algo *hmacs;
+ uint8_t store[SCTP_PARAM_BUFFER_SIZE];
+ int num_hmacs;
+
+ if (plen > sizeof(store)) {
+ break;
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)store,
+ plen);
+ if (phdr == NULL) {
+ return (-1);
+ }
+ hmacs = (struct sctp_auth_hmac_algo *)phdr;
+ num_hmacs = (plen - sizeof(*hmacs)) / sizeof(hmacs->hmac_ids[0]);
+ /* validate the hmac list */
+ if (sctp_verify_hmac_param(hmacs, num_hmacs)) {
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: invalid HMAC param\n");
+ return (-1);
+ }
+ got_hmacs = 1;
+ } else if (ptype == SCTP_CHUNK_LIST) {
+ struct sctp_auth_chunk_list *chunks;
+ uint8_t chunks_store[SCTP_SMALL_CHUNK_STORE];
+ int i, num_chunks;
+
+ if (plen > sizeof(chunks_store)) {
+ break;
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)chunks_store,
+ plen);
+ if (phdr == NULL) {
+ return (-1);
+ }
+ /*-
+ * Flip through the list and mark that the
+ * peer supports asconf/asconf_ack.
+ */
+ chunks = (struct sctp_auth_chunk_list *)phdr;
+ num_chunks = plen - sizeof(*chunks);
+ for (i = 0; i < num_chunks; i++) {
+ /* record asconf/asconf-ack if listed */
+ if (chunks->chunk_types[i] == SCTP_ASCONF)
+ saw_asconf = 1;
+ if (chunks->chunk_types[i] == SCTP_ASCONF_ACK)
+ saw_asconf_ack = 1;
+ }
+ if (num_chunks)
+ got_chklist = 1;
+ }
+
+ offset += SCTP_SIZE32(plen);
+ if (offset >= limit) {
+ break;
+ }
+ phdr = sctp_get_next_param(m, offset, &param_buf,
+ sizeof(param_buf));
+ }
+ /* validate authentication required parameters */
+ if (got_random && got_hmacs) {
+ peer_supports_auth = 1;
+ } else {
+ peer_supports_auth = 0;
+ }
+ if (!peer_supports_auth && got_chklist) {
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: peer sent chunk list w/o AUTH\n");
+ return (-1);
+ }
+ if (peer_supports_asconf && !peer_supports_auth) {
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "SCTP: peer supports ASCONF but not AUTH\n");
+ return (-1);
+ } else if ((peer_supports_asconf) && (peer_supports_auth) &&
+ ((saw_asconf == 0) || (saw_asconf_ack == 0))) {
+ return (-2);
+ }
+ return (0);
+}
+
+void
+sctp_initialize_auth_params(struct sctp_inpcb *inp, struct sctp_tcb *stcb)
+{
+ uint16_t chunks_len = 0;
+ uint16_t hmacs_len = 0;
+ uint16_t random_len = SCTP_AUTH_RANDOM_SIZE_DEFAULT;
+ sctp_key_t *new_key;
+ uint16_t keylen;
+
+ /* initialize hmac list from endpoint */
+ stcb->asoc.local_hmacs = sctp_copy_hmaclist(inp->sctp_ep.local_hmacs);
+ if (stcb->asoc.local_hmacs != NULL) {
+ hmacs_len = stcb->asoc.local_hmacs->num_algo *
+ sizeof(stcb->asoc.local_hmacs->hmac[0]);
+ }
+ /* initialize auth chunks list from endpoint */
+ stcb->asoc.local_auth_chunks =
+ sctp_copy_chunklist(inp->sctp_ep.local_auth_chunks);
+ if (stcb->asoc.local_auth_chunks != NULL) {
+ int i;
+ for (i = 0; i < 256; i++) {
+ if (stcb->asoc.local_auth_chunks->chunks[i])
+ chunks_len++;
+ }
+ }
+ /* copy defaults from the endpoint */
+ stcb->asoc.authinfo.active_keyid = inp->sctp_ep.default_keyid;
+
+ /* copy out the shared key list (by reference) from the endpoint */
+ (void)sctp_copy_skeylist(&inp->sctp_ep.shared_keys,
+ &stcb->asoc.shared_keys);
+
+ /* now set the concatenated key (random + chunks + hmacs) */
+ /* key includes parameter headers */
+ keylen = (3 * sizeof(struct sctp_paramhdr)) + random_len + chunks_len +
+ hmacs_len;
+ new_key = sctp_alloc_key(keylen);
+ if (new_key != NULL) {
+ struct sctp_paramhdr *ph;
+ int plen;
+ /* generate and copy in the RANDOM */
+ ph = (struct sctp_paramhdr *)new_key->key;
+ ph->param_type = htons(SCTP_RANDOM);
+ plen = sizeof(*ph) + random_len;
+ ph->param_length = htons(plen);
+ SCTP_READ_RANDOM(new_key->key + sizeof(*ph), random_len);
+ keylen = plen;
+
+ /* append in the AUTH chunks */
+ /* NOTE: currently we always have chunks to list */
+ ph = (struct sctp_paramhdr *)(new_key->key + keylen);
+ ph->param_type = htons(SCTP_CHUNK_LIST);
+ plen = sizeof(*ph) + chunks_len;
+ ph->param_length = htons(plen);
+ keylen += sizeof(*ph);
+ if (stcb->asoc.local_auth_chunks) {
+ int i;
+ for (i = 0; i < 256; i++) {
+ if (stcb->asoc.local_auth_chunks->chunks[i])
+ new_key->key[keylen++] = i;
+ }
+ }
+
+ /* append in the HMACs */
+ ph = (struct sctp_paramhdr *)(new_key->key + keylen);
+ ph->param_type = htons(SCTP_HMAC_LIST);
+ plen = sizeof(*ph) + hmacs_len;
+ ph->param_length = htons(plen);
+ keylen += sizeof(*ph);
+ (void)sctp_serialize_hmaclist(stcb->asoc.local_hmacs,
+ new_key->key + keylen);
+ }
+ if (stcb->asoc.authinfo.random != NULL)
+ sctp_free_key(stcb->asoc.authinfo.random);
+ stcb->asoc.authinfo.random = new_key;
+ stcb->asoc.authinfo.random_len = random_len;
+}
+
+
+#ifdef SCTP_HMAC_TEST
+/*
+ * HMAC and key concatenation tests
+ */
+static void
+sctp_print_digest(uint8_t *digest, uint32_t digestlen, const char *str)
+{
+ uint32_t i;
+
+ SCTP_PRINTF("\n%s: 0x", str);
+ if (digest == NULL)
+ return;
+
+ for (i = 0; i < digestlen; i++)
+ SCTP_PRINTF("%02x", digest[i]);
+}
+
+static int
+sctp_test_hmac(const char *str, uint16_t hmac_id, uint8_t *key,
+ uint32_t keylen, uint8_t *text, uint32_t textlen,
+ uint8_t *digest, uint32_t digestlen)
+{
+ uint8_t computed_digest[SCTP_AUTH_DIGEST_LEN_MAX];
+
+ SCTP_PRINTF("\n%s:", str);
+ sctp_hmac(hmac_id, key, keylen, text, textlen, computed_digest);
+ sctp_print_digest(digest, digestlen, "Expected digest");
+ sctp_print_digest(computed_digest, digestlen, "Computed digest");
+ if (memcmp(digest, computed_digest, digestlen) != 0) {
+ SCTP_PRINTF("\nFAILED");
+ return (-1);
+ } else {
+ SCTP_PRINTF("\nPASSED");
+ return (0);
+ }
+}
+
+
+/*
+ * RFC 2202: HMAC-SHA1 test cases
+ */
+void
+sctp_test_hmac_sha1(void)
+{
+ uint8_t *digest;
+ uint8_t key[128];
+ uint32_t keylen;
+ uint8_t text[128];
+ uint32_t textlen;
+ uint32_t digestlen = 20;
+ int failed = 0;
+
+ /*-
+ * test_case = 1
+ * key = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+ * key_len = 20
+ * data = "Hi There"
+ * data_len = 8
+ * digest = 0xb617318655057264e28bc0b6fb378c8ef146be00
+ */
+ keylen = 20;
+ memset(key, 0x0b, keylen);
+ textlen = 8;
+ strcpy(text, "Hi There");
+ digest = "\xb6\x17\x31\x86\x55\x05\x72\x64\xe2\x8b\xc0\xb6\xfb\x37\x8c\x8e\xf1\x46\xbe\x00";
+ if (sctp_test_hmac("SHA1 test case 1", SCTP_AUTH_HMAC_ID_SHA1, key, keylen,
+ text, textlen, digest, digestlen) < 0)
+ failed++;
+
+ /*-
+ * test_case = 2
+ * key = "Jefe"
+ * key_len = 4
+ * data = "what do ya want for nothing?"
+ * data_len = 28
+ * digest = 0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79
+ */
+ keylen = 4;
+ strcpy(key, "Jefe");
+ textlen = 28;
+ strcpy(text, "what do ya want for nothing?");
+ digest = "\xef\xfc\xdf\x6a\xe5\xeb\x2f\xa2\xd2\x74\x16\xd5\xf1\x84\xdf\x9c\x25\x9a\x7c\x79";
+ if (sctp_test_hmac("SHA1 test case 2", SCTP_AUTH_HMAC_ID_SHA1, key, keylen,
+ text, textlen, digest, digestlen) < 0)
+ failed++;
+
+ /*-
+ * test_case = 3
+ * key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ * key_len = 20
+ * data = 0xdd repeated 50 times
+ * data_len = 50
+ * digest = 0x125d7342b9ac11cd91a39af48aa17b4f63f175d3
+ */
+ keylen = 20;
+ memset(key, 0xaa, keylen);
+ textlen = 50;
+ memset(text, 0xdd, textlen);
+ digest = "\x12\x5d\x73\x42\xb9\xac\x11\xcd\x91\xa3\x9a\xf4\x8a\xa1\x7b\x4f\x63\xf1\x75\xd3";
+ if (sctp_test_hmac("SHA1 test case 3", SCTP_AUTH_HMAC_ID_SHA1, key, keylen,
+ text, textlen, digest, digestlen) < 0)
+ failed++;
+
+ /*-
+ * test_case = 4
+ * key = 0x0102030405060708090a0b0c0d0e0f10111213141516171819
+ * key_len = 25
+ * data = 0xcd repeated 50 times
+ * data_len = 50
+ * digest = 0x4c9007f4026250c6bc8414f9bf50c86c2d7235da
+ */
+ keylen = 25;
+ memcpy(key, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", keylen);
+ textlen = 50;
+ memset(text, 0xcd, textlen);
+ digest = "\x4c\x90\x07\xf4\x02\x62\x50\xc6\xbc\x84\x14\xf9\xbf\x50\xc8\x6c\x2d\x72\x35\xda";
+ if (sctp_test_hmac("SHA1 test case 4", SCTP_AUTH_HMAC_ID_SHA1, key, keylen,
+ text, textlen, digest, digestlen) < 0)
+ failed++;
+
+ /*-
+ * test_case = 5
+ * key = 0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c
+ * key_len = 20
+ * data = "Test With Truncation"
+ * data_len = 20
+ * digest = 0x4c1a03424b55e07fe7f27be1d58bb9324a9a5a04
+ * digest-96 = 0x4c1a03424b55e07fe7f27be1
+ */
+ keylen = 20;
+ memset(key, 0x0c, keylen);
+ textlen = 20;
+ strcpy(text, "Test With Truncation");
+ digest = "\x4c\x1a\x03\x42\x4b\x55\xe0\x7f\xe7\xf2\x7b\xe1\xd5\x8b\xb9\x32\x4a\x9a\x5a\x04";
+ if (sctp_test_hmac("SHA1 test case 5", SCTP_AUTH_HMAC_ID_SHA1, key, keylen,
+ text, textlen, digest, digestlen) < 0)
+ failed++;
+
+ /*-
+ * test_case = 6
+ * key = 0xaa repeated 80 times
+ * key_len = 80
+ * data = "Test Using Larger Than Block-Size Key - Hash Key First"
+ * data_len = 54
+ * digest = 0xaa4ae5e15272d00e95705637ce8a3b55ed402112
+ */
+ keylen = 80;
+ memset(key, 0xaa, keylen);
+ textlen = 54;
+ strcpy(text, "Test Using Larger Than Block-Size Key - Hash Key First");
+ digest = "\xaa\x4a\xe5\xe1\x52\x72\xd0\x0e\x95\x70\x56\x37\xce\x8a\x3b\x55\xed\x40\x21\x12";
+ if (sctp_test_hmac("SHA1 test case 6", SCTP_AUTH_HMAC_ID_SHA1, key, keylen,
+ text, textlen, digest, digestlen) < 0)
+ failed++;
+
+ /*-
+ * test_case = 7
+ * key = 0xaa repeated 80 times
+ * key_len = 80
+ * data = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"
+ * data_len = 73
+ * digest = 0xe8e99d0f45237d786d6bbaa7965c7808bbff1a91
+ */
+ keylen = 80;
+ memset(key, 0xaa, keylen);
+ textlen = 73;
+ strcpy(text, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data");
+ digest = "\xe8\xe9\x9d\x0f\x45\x23\x7d\x78\x6d\x6b\xba\xa7\x96\x5c\x78\x08\xbb\xff\x1a\x91";
+ if (sctp_test_hmac("SHA1 test case 7", SCTP_AUTH_HMAC_ID_SHA1, key, keylen,
+ text, textlen, digest, digestlen) < 0)
+ failed++;
+
+ /* done with all tests */
+ if (failed)
+ SCTP_PRINTF("\nSHA1 test results: %d cases failed", failed);
+ else
+ SCTP_PRINTF("\nSHA1 test results: all test cases passed");
+}
+
+/*
+ * test assoc key concatenation
+ */
+static int
+sctp_test_key_concatenation(sctp_key_t *key1, sctp_key_t *key2,
+ sctp_key_t *expected_key)
+{
+ sctp_key_t *key;
+ int ret_val;
+
+ sctp_show_key(key1, "\nkey1");
+ sctp_show_key(key2, "\nkey2");
+ key = sctp_compute_hashkey(key1, key2, NULL);
+ sctp_show_key(expected_key, "\nExpected");
+ sctp_show_key(key, "\nComputed");
+ if (memcmp(key, expected_key, expected_key->keylen) != 0) {
+ SCTP_PRINTF("\nFAILED");
+ ret_val = -1;
+ } else {
+ SCTP_PRINTF("\nPASSED");
+ ret_val = 0;
+ }
+ sctp_free_key(key1);
+ sctp_free_key(key2);
+ sctp_free_key(expected_key);
+ sctp_free_key(key);
+ return (ret_val);
+}
+
+
+void
+sctp_test_authkey(void)
+{
+ sctp_key_t *key1, *key2, *expected_key;
+ int failed = 0;
+
+ /* test case 1 */
+ key1 = sctp_set_key("\x01\x01\x01\x01", 4);
+ key2 = sctp_set_key("\x01\x02\x03\x04", 4);
+ expected_key = sctp_set_key("\x01\x01\x01\x01\x01\x02\x03\x04", 8);
+ if (sctp_test_key_concatenation(key1, key2, expected_key) < 0)
+ failed++;
+
+ /* test case 2 */
+ key1 = sctp_set_key("\x00\x00\x00\x01", 4);
+ key2 = sctp_set_key("\x02", 1);
+ expected_key = sctp_set_key("\x00\x00\x00\x01\x02", 5);
+ if (sctp_test_key_concatenation(key1, key2, expected_key) < 0)
+ failed++;
+
+ /* test case 3 */
+ key1 = sctp_set_key("\x01", 1);
+ key2 = sctp_set_key("\x00\x00\x00\x02", 4);
+ expected_key = sctp_set_key("\x01\x00\x00\x00\x02", 5);
+ if (sctp_test_key_concatenation(key1, key2, expected_key) < 0)
+ failed++;
+
+ /* test case 4 */
+ key1 = sctp_set_key("\x00\x00\x00\x01", 4);
+ key2 = sctp_set_key("\x01", 1);
+ expected_key = sctp_set_key("\x01\x00\x00\x00\x01", 5);
+ if (sctp_test_key_concatenation(key1, key2, expected_key) < 0)
+ failed++;
+
+ /* test case 5 */
+ key1 = sctp_set_key("\x01", 1);
+ key2 = sctp_set_key("\x00\x00\x00\x01", 4);
+ expected_key = sctp_set_key("\x01\x00\x00\x00\x01", 5);
+ if (sctp_test_key_concatenation(key1, key2, expected_key) < 0)
+ failed++;
+
+ /* test case 6 */
+ key1 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07", 11);
+ key2 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 11);
+ expected_key = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 22);
+ if (sctp_test_key_concatenation(key1, key2, expected_key) < 0)
+ failed++;
+
+ /* test case 7 */
+ key1 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 11);
+ key2 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07", 11);
+ expected_key = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 22);
+ if (sctp_test_key_concatenation(key1, key2, expected_key) < 0)
+ failed++;
+
+ /* done with all tests */
+ if (failed)
+ SCTP_PRINTF("\nKey concatenation test results: %d cases failed", failed);
+ else
+ SCTP_PRINTF("\nKey concatenation test results: all test cases passed");
+}
+
+
+#if defined(STANDALONE_HMAC_TEST)
+int
+main(void)
+{
+ sctp_test_hmac_sha1();
+ sctp_test_authkey();
+}
+
+#endif /* STANDALONE_HMAC_TEST */
+
+#endif /* SCTP_HMAC_TEST */
diff --git a/netwerk/sctp/src/netinet/sctp_auth.h b/netwerk/sctp/src/netinet/sctp_auth.h
new file mode 100644
index 0000000000..00682ae78e
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_auth.h
@@ -0,0 +1,208 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_AUTH_H_
+#define _NETINET_SCTP_AUTH_H_
+
+#include <netinet/sctp_os.h>
+
+/* digest lengths */
+#define SCTP_AUTH_DIGEST_LEN_SHA1 20
+#define SCTP_AUTH_DIGEST_LEN_SHA256 32
+#define SCTP_AUTH_DIGEST_LEN_MAX SCTP_AUTH_DIGEST_LEN_SHA256
+
+/* random sizes */
+#define SCTP_AUTH_RANDOM_SIZE_DEFAULT 32
+#define SCTP_AUTH_RANDOM_SIZE_REQUIRED 32
+
+/* union of all supported HMAC algorithm contexts */
+typedef union sctp_hash_context {
+ SCTP_SHA1_CTX sha1;
+#if defined(SCTP_SUPPORT_HMAC_SHA256)
+ SCTP_SHA256_CTX sha256;
+#endif
+} sctp_hash_context_t;
+
+typedef struct sctp_key {
+ uint32_t keylen;
+ uint8_t key[];
+} sctp_key_t;
+
+typedef struct sctp_shared_key {
+ LIST_ENTRY(sctp_shared_key) next;
+ sctp_key_t *key; /* key text */
+ uint32_t refcount; /* reference count */
+ uint16_t keyid; /* shared key ID */
+ uint8_t deactivated; /* key is deactivated */
+} sctp_sharedkey_t;
+
+LIST_HEAD(sctp_keyhead, sctp_shared_key);
+
+/* authentication chunks list */
+typedef struct sctp_auth_chklist {
+ uint8_t chunks[256];
+ uint8_t num_chunks;
+} sctp_auth_chklist_t;
+
+/* hmac algos supported list */
+typedef struct sctp_hmaclist {
+ uint16_t max_algo; /* max algorithms allocated */
+ uint16_t num_algo; /* num algorithms used */
+ uint16_t hmac[];
+} sctp_hmaclist_t;
+
+/* authentication info */
+typedef struct sctp_authinformation {
+ sctp_key_t *random; /* local random key (concatenated) */
+ uint32_t random_len; /* local random number length for param */
+ sctp_key_t *peer_random;/* peer's random key (concatenated) */
+ sctp_key_t *assoc_key; /* cached concatenated send key */
+ sctp_key_t *recv_key; /* cached concatenated recv key */
+ uint16_t active_keyid; /* active send keyid */
+ uint16_t assoc_keyid; /* current send keyid (cached) */
+ uint16_t recv_keyid; /* last recv keyid (cached) */
+} sctp_authinfo_t;
+
+/*
+ * Macros
+ */
+#define sctp_auth_is_required_chunk(chunk, list) ((list == NULL) ? (0) : (list->chunks[chunk] != 0))
+
+/*
+ * function prototypes
+ */
+
+/* socket option api functions */
+extern sctp_auth_chklist_t *sctp_alloc_chunklist(void);
+extern void sctp_free_chunklist(sctp_auth_chklist_t *chklist);
+extern void sctp_clear_chunklist(sctp_auth_chklist_t *chklist);
+extern sctp_auth_chklist_t *sctp_copy_chunklist(sctp_auth_chklist_t *chklist);
+extern int sctp_auth_add_chunk(uint8_t chunk, sctp_auth_chklist_t *list);
+extern int sctp_auth_delete_chunk(uint8_t chunk, sctp_auth_chklist_t *list);
+extern size_t sctp_auth_get_chklist_size(const sctp_auth_chklist_t *list);
+extern int sctp_serialize_auth_chunks(const sctp_auth_chklist_t *list,
+ uint8_t *ptr);
+extern int sctp_pack_auth_chunks(const sctp_auth_chklist_t *list,
+ uint8_t *ptr);
+extern int sctp_unpack_auth_chunks(const uint8_t *ptr, uint8_t num_chunks,
+ sctp_auth_chklist_t *list);
+
+/* key handling */
+extern sctp_key_t *sctp_alloc_key(uint32_t keylen);
+extern void sctp_free_key(sctp_key_t *key);
+extern void sctp_print_key(sctp_key_t *key, const char *str);
+extern void sctp_show_key(sctp_key_t *key, const char *str);
+extern sctp_key_t *sctp_generate_random_key(uint32_t keylen);
+extern sctp_key_t *sctp_set_key(uint8_t *key, uint32_t keylen);
+extern sctp_key_t *sctp_compute_hashkey(sctp_key_t *key1, sctp_key_t *key2,
+ sctp_key_t *shared);
+
+/* shared key handling */
+extern sctp_sharedkey_t *sctp_alloc_sharedkey(void);
+extern void sctp_free_sharedkey(sctp_sharedkey_t *skey);
+extern sctp_sharedkey_t *sctp_find_sharedkey(struct sctp_keyhead *shared_keys,
+ uint16_t key_id);
+extern int sctp_insert_sharedkey(struct sctp_keyhead *shared_keys,
+ sctp_sharedkey_t *new_skey);
+extern int sctp_copy_skeylist(const struct sctp_keyhead *src,
+ struct sctp_keyhead *dest);
+/* ref counts on shared keys, by key id */
+extern void sctp_auth_key_acquire(struct sctp_tcb *stcb, uint16_t keyid);
+extern void sctp_auth_key_release(struct sctp_tcb *stcb, uint16_t keyid,
+ int so_locked);
+
+/* hmac list handling */
+extern sctp_hmaclist_t *sctp_alloc_hmaclist(uint16_t num_hmacs);
+extern void sctp_free_hmaclist(sctp_hmaclist_t *list);
+extern int sctp_auth_add_hmacid(sctp_hmaclist_t *list, uint16_t hmac_id);
+extern sctp_hmaclist_t *sctp_copy_hmaclist(sctp_hmaclist_t *list);
+extern sctp_hmaclist_t *sctp_default_supported_hmaclist(void);
+extern uint16_t sctp_negotiate_hmacid(sctp_hmaclist_t *peer,
+ sctp_hmaclist_t *local);
+extern int sctp_serialize_hmaclist(sctp_hmaclist_t *list, uint8_t *ptr);
+extern int sctp_verify_hmac_param(struct sctp_auth_hmac_algo *hmacs,
+ uint32_t num_hmacs);
+
+extern sctp_authinfo_t *sctp_alloc_authinfo(void);
+extern void sctp_free_authinfo(sctp_authinfo_t *authinfo);
+
+/* keyed-HMAC functions */
+extern uint32_t sctp_get_auth_chunk_len(uint16_t hmac_algo);
+extern uint32_t sctp_get_hmac_digest_len(uint16_t hmac_algo);
+extern uint32_t sctp_hmac(uint16_t hmac_algo, uint8_t *key, uint32_t keylen,
+ uint8_t *text, uint32_t textlen, uint8_t *digest);
+extern uint32_t sctp_compute_hmac(uint16_t hmac_algo, sctp_key_t *key,
+ uint8_t *text, uint32_t textlen, uint8_t *digest);
+extern int sctp_auth_is_supported_hmac(sctp_hmaclist_t *list, uint16_t id);
+
+/* mbuf versions */
+extern uint32_t sctp_hmac_m(uint16_t hmac_algo, uint8_t *key, uint32_t keylen,
+ struct mbuf *m, uint32_t m_offset, uint8_t *digest, uint32_t trailer);
+extern uint32_t sctp_compute_hmac_m(uint16_t hmac_algo, sctp_key_t *key,
+ struct mbuf *m, uint32_t m_offset, uint8_t *digest);
+
+/*
+ * authentication routines
+ */
+extern void sctp_clear_cachedkeys(struct sctp_tcb *stcb, uint16_t keyid);
+extern void sctp_clear_cachedkeys_ep(struct sctp_inpcb *inp, uint16_t keyid);
+extern int sctp_delete_sharedkey(struct sctp_tcb *stcb, uint16_t keyid);
+extern int sctp_delete_sharedkey_ep(struct sctp_inpcb *inp, uint16_t keyid);
+extern int sctp_auth_setactivekey(struct sctp_tcb *stcb, uint16_t keyid);
+extern int sctp_auth_setactivekey_ep(struct sctp_inpcb *inp, uint16_t keyid);
+extern int sctp_deact_sharedkey(struct sctp_tcb *stcb, uint16_t keyid);
+extern int sctp_deact_sharedkey_ep(struct sctp_inpcb *inp, uint16_t keyid);
+
+extern void sctp_auth_get_cookie_params(struct sctp_tcb *stcb, struct mbuf *m,
+ uint32_t offset, uint32_t length);
+extern void sctp_fill_hmac_digest_m(struct mbuf *m, uint32_t auth_offset,
+ struct sctp_auth_chunk *auth, struct sctp_tcb *stcb, uint16_t key_id);
+extern struct mbuf *sctp_add_auth_chunk(struct mbuf *m, struct mbuf **m_end,
+ struct sctp_auth_chunk **auth_ret, uint32_t *offset,
+ struct sctp_tcb *stcb, uint8_t chunk);
+extern int sctp_handle_auth(struct sctp_tcb *stcb, struct sctp_auth_chunk *ch,
+ struct mbuf *m, uint32_t offset);
+extern void sctp_notify_authentication(struct sctp_tcb *stcb,
+ uint32_t indication, uint16_t keyid, int so_locked);
+extern int sctp_validate_init_auth_params(struct mbuf *m, int offset,
+ int limit);
+extern void sctp_initialize_auth_params(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb);
+
+/* test functions */
+#ifdef SCTP_HMAC_TEST
+extern void sctp_test_hmac_sha1(void);
+extern void sctp_test_authkey(void);
+#endif
+#endif /* __SCTP_AUTH_H__ */
diff --git a/netwerk/sctp/src/netinet/sctp_bsd_addr.c b/netwerk/sctp/src/netinet/sctp_bsd_addr.c
new file mode 100644
index 0000000000..4e6a8890bd
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_bsd_addr.c
@@ -0,0 +1,977 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_bsd_addr.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_indata.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/unistd.h>
+#endif
+
+/* Declare all of our malloc named types */
+MALLOC_DEFINE(SCTP_M_MAP, "sctp_map", "sctp asoc map descriptor");
+MALLOC_DEFINE(SCTP_M_STRMI, "sctp_stri", "sctp stream in array");
+MALLOC_DEFINE(SCTP_M_STRMO, "sctp_stro", "sctp stream out array");
+MALLOC_DEFINE(SCTP_M_ASC_ADDR, "sctp_aadr", "sctp asconf address");
+MALLOC_DEFINE(SCTP_M_ASC_IT, "sctp_a_it", "sctp asconf iterator");
+MALLOC_DEFINE(SCTP_M_AUTH_CL, "sctp_atcl", "sctp auth chunklist");
+MALLOC_DEFINE(SCTP_M_AUTH_KY, "sctp_atky", "sctp auth key");
+MALLOC_DEFINE(SCTP_M_AUTH_HL, "sctp_athm", "sctp auth hmac list");
+MALLOC_DEFINE(SCTP_M_AUTH_IF, "sctp_athi", "sctp auth info");
+MALLOC_DEFINE(SCTP_M_STRESET, "sctp_stre", "sctp stream reset");
+MALLOC_DEFINE(SCTP_M_CMSG, "sctp_cmsg", "sctp CMSG buffer");
+MALLOC_DEFINE(SCTP_M_COPYAL, "sctp_cpal", "sctp copy all");
+MALLOC_DEFINE(SCTP_M_VRF, "sctp_vrf", "sctp vrf struct");
+MALLOC_DEFINE(SCTP_M_IFA, "sctp_ifa", "sctp ifa struct");
+MALLOC_DEFINE(SCTP_M_IFN, "sctp_ifn", "sctp ifn struct");
+MALLOC_DEFINE(SCTP_M_TIMW, "sctp_timw", "sctp time block");
+MALLOC_DEFINE(SCTP_M_MVRF, "sctp_mvrf", "sctp mvrf pcb list");
+MALLOC_DEFINE(SCTP_M_ITER, "sctp_iter", "sctp iterator control");
+MALLOC_DEFINE(SCTP_M_SOCKOPT, "sctp_socko", "sctp socket option");
+MALLOC_DEFINE(SCTP_M_MCORE, "sctp_mcore", "sctp mcore queue");
+
+/* Global NON-VNET structure that controls the iterator */
+struct iterator_control sctp_it_ctl;
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+
+static void
+sctp_cleanup_itqueue(void)
+{
+ struct sctp_iterator *it, *nit;
+
+ TAILQ_FOREACH_SAFE(it, &sctp_it_ctl.iteratorhead, sctp_nxt_itr, nit) {
+ if (it->function_atend != NULL) {
+ (*it->function_atend) (it->pointer, it->val);
+ }
+ TAILQ_REMOVE(&sctp_it_ctl.iteratorhead, it, sctp_nxt_itr);
+ SCTP_FREE(it, SCTP_M_ITER);
+ }
+}
+#endif
+#if defined(__Userspace__)
+/*__Userspace__ TODO if we use thread based iterator
+ * then the implementation of wakeup will need to change.
+ * Currently we are using timeo_cond for ident so_timeo
+ * but that is not sufficient if we need to use another ident
+ * like wakeup(&sctppcbinfo.iterator_running);
+ */
+#endif
+
+void
+sctp_wakeup_iterator(void)
+{
+#if defined(SCTP_PROCESS_LEVEL_LOCKS)
+#if defined(_WIN32)
+ WakeAllConditionVariable(&sctp_it_ctl.iterator_wakeup);
+#else
+ pthread_cond_broadcast(&sctp_it_ctl.iterator_wakeup);
+#endif
+#else
+ wakeup(&sctp_it_ctl.iterator_running);
+#endif
+}
+
+#if defined(__Userspace__)
+static void *
+#else
+static void
+#endif
+sctp_iterator_thread(void *v SCTP_UNUSED)
+{
+#if defined(__Userspace__)
+ sctp_userspace_set_threadname("SCTP iterator");
+#endif
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+ /* In FreeBSD this thread never terminates. */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ for (;;) {
+#else
+ while ((sctp_it_ctl.iterator_flags & SCTP_ITERATOR_MUST_EXIT) == 0) {
+#endif
+#if !defined(__Userspace__)
+ msleep(&sctp_it_ctl.iterator_running,
+#if defined(__FreeBSD__)
+ &sctp_it_ctl.ipi_iterator_wq_mtx,
+#elif defined(__APPLE__)
+ sctp_it_ctl.ipi_iterator_wq_mtx,
+#endif
+ 0, "waiting_for_work", 0);
+#else
+#if defined(_WIN32)
+ SleepConditionVariableCS(&sctp_it_ctl.iterator_wakeup, &sctp_it_ctl.ipi_iterator_wq_mtx, INFINITE);
+#else
+ pthread_cond_wait(&sctp_it_ctl.iterator_wakeup, &sctp_it_ctl.ipi_iterator_wq_mtx);
+#endif
+#endif
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (sctp_it_ctl.iterator_flags & SCTP_ITERATOR_MUST_EXIT) {
+ break;
+ }
+#endif
+ sctp_iterator_worker();
+ }
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ /* Now this thread needs to be terminated */
+ sctp_cleanup_itqueue();
+ sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_EXITED;
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+#if defined(__Userspace__)
+ sctp_wakeup_iterator();
+ return (NULL);
+#else
+ wakeup(&sctp_it_ctl.iterator_flags);
+ thread_terminate(current_thread());
+#ifdef INVARIANTS
+ panic("Hmm. thread_terminate() continues...");
+#endif
+#endif
+#endif
+}
+
+void
+sctp_startup_iterator(void)
+{
+ if (sctp_it_ctl.thread_proc) {
+ /* You only get one */
+ return;
+ }
+ /* Initialize global locks here, thus only once. */
+ SCTP_ITERATOR_LOCK_INIT();
+ SCTP_IPI_ITERATOR_WQ_INIT();
+ TAILQ_INIT(&sctp_it_ctl.iteratorhead);
+#if defined(__Userspace__)
+ if (sctp_userspace_thread_create(&sctp_it_ctl.thread_proc, &sctp_iterator_thread)) {
+ SCTP_PRINTF("ERROR: Creating sctp_iterator_thread failed.\n");
+ } else {
+ SCTP_BASE_VAR(iterator_thread_started) = 1;
+ }
+#elif defined(__FreeBSD__)
+ kproc_create(sctp_iterator_thread,
+ (void *)NULL,
+ &sctp_it_ctl.thread_proc,
+ 0,
+ SCTP_KTHREAD_PAGES,
+ SCTP_KTRHEAD_NAME);
+#elif defined(__APPLE__)
+ kernel_thread_start((thread_continue_t)sctp_iterator_thread, NULL, &sctp_it_ctl.thread_proc);
+#endif
+}
+
+#ifdef INET6
+
+#if defined(__Userspace__)
+/* __Userspace__ TODO. struct in6_ifaddr is defined in sys/netinet6/in6_var.h
+ ip6_use_deprecated is defined as int ip6_use_deprecated = 1; in /src/sys/netinet6/in6_proto.c
+ */
+void
+sctp_gather_internal_ifa_flags(struct sctp_ifa *ifa)
+{
+ return; /* stub */
+}
+#else
+void
+sctp_gather_internal_ifa_flags(struct sctp_ifa *ifa)
+{
+ struct in6_ifaddr *ifa6;
+
+ ifa6 = (struct in6_ifaddr *)ifa->ifa;
+ ifa->flags = ifa6->ia6_flags;
+ if (!MODULE_GLOBAL(ip6_use_deprecated)) {
+ if (ifa->flags &
+ IN6_IFF_DEPRECATED) {
+ ifa->localifa_flags |= SCTP_ADDR_IFA_UNUSEABLE;
+ } else {
+ ifa->localifa_flags &= ~SCTP_ADDR_IFA_UNUSEABLE;
+ }
+ } else {
+ ifa->localifa_flags &= ~SCTP_ADDR_IFA_UNUSEABLE;
+ }
+ if (ifa->flags &
+ (IN6_IFF_DETACHED |
+ IN6_IFF_ANYCAST |
+ IN6_IFF_NOTREADY)) {
+ ifa->localifa_flags |= SCTP_ADDR_IFA_UNUSEABLE;
+ } else {
+ ifa->localifa_flags &= ~SCTP_ADDR_IFA_UNUSEABLE;
+ }
+}
+#endif /* __Userspace__ */
+#endif /* INET6 */
+
+#if !defined(__Userspace__)
+static uint32_t
+sctp_is_desired_interface_type(struct ifnet *ifn)
+{
+ int result;
+
+ /* check the interface type to see if it's one we care about */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ switch(ifnet_type(ifn)) {
+#else
+ switch (ifn->if_type) {
+#endif
+ case IFT_ETHER:
+ case IFT_ISO88023:
+ case IFT_ISO88024:
+ case IFT_ISO88025:
+ case IFT_ISO88026:
+ case IFT_STARLAN:
+ case IFT_P10:
+ case IFT_P80:
+ case IFT_HY:
+ case IFT_FDDI:
+ case IFT_XETHER:
+ case IFT_ISDNBASIC:
+ case IFT_ISDNPRIMARY:
+ case IFT_PTPSERIAL:
+ case IFT_OTHER:
+ case IFT_PPP:
+ case IFT_LOOP:
+ case IFT_SLIP:
+ case IFT_GIF:
+ case IFT_L2VLAN:
+ case IFT_STF:
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+ case IFT_IP:
+ case IFT_IPOVERCDLC:
+ case IFT_IPOVERCLAW:
+ case IFT_PROPVIRTUAL: /* NetGraph Virtual too */
+ case IFT_VIRTUALIPADDRESS:
+#endif
+ result = 1;
+ break;
+ default:
+ result = 0;
+ }
+
+ return (result);
+}
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+
+int
+sctp_is_vmware_interface(struct ifnet *ifn)
+{
+ return (strncmp(ifnet_name(ifn), "vmnet", 5) == 0);
+}
+
+#endif
+
+#if defined(_WIN32) && defined(__Userspace__)
+#define SCTP_BSD_FREE(x) HeapFree(GetProcessHeap(), 0, (x))
+static void
+sctp_init_ifns_for_vrf(int vrfid)
+{
+#if defined(INET) || defined(INET6)
+ struct sctp_ifa *sctp_ifa;
+ DWORD Err, AdapterAddrsSize;
+ PIP_ADAPTER_ADDRESSES pAdapterAddrs, pAdapt;
+ PIP_ADAPTER_UNICAST_ADDRESS pUnicast;
+#endif
+
+#ifdef INET
+ AdapterAddrsSize = 0;
+
+ if ((Err = GetAdaptersAddresses(AF_INET, 0, NULL, NULL, &AdapterAddrsSize)) != 0) {
+ if ((Err != ERROR_BUFFER_OVERFLOW) && (Err != ERROR_INSUFFICIENT_BUFFER)) {
+ SCTP_PRINTF("GetAdaptersV4Addresses() sizing failed with error code %d\n", Err);
+ SCTP_PRINTF("err = %d; AdapterAddrsSize = %d\n", Err, AdapterAddrsSize);
+ return;
+ }
+ }
+
+ /* Allocate memory from sizing information */
+ if ((pAdapterAddrs = (PIP_ADAPTER_ADDRESSES) GlobalAlloc(GPTR, AdapterAddrsSize)) == NULL) {
+ SCTP_PRINTF("Memory allocation error!\n");
+ return;
+ }
+ /* Get actual adapter information */
+ if ((Err = GetAdaptersAddresses(AF_INET, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTP_PRINTF("GetAdaptersV4Addresses() failed with error code %d\n", Err);
+ SCTP_BSD_FREE(pAdapterAddrs);
+ return;
+ }
+ /* Enumerate through each returned adapter and save its information */
+ for (pAdapt = pAdapterAddrs; pAdapt; pAdapt = pAdapt->Next) {
+ if (pAdapt->IfType == IF_TYPE_IEEE80211 || pAdapt->IfType == IF_TYPE_ETHERNET_CSMACD) {
+ for (pUnicast = pAdapt->FirstUnicastAddress; pUnicast; pUnicast = pUnicast->Next) {
+ if (IN4_ISLINKLOCAL_ADDRESS(&(((struct sockaddr_in *)(pUnicast->Address.lpSockaddr))->sin_addr))) {
+ continue;
+ }
+ sctp_ifa = sctp_add_addr_to_vrf(0,
+ NULL,
+ pAdapt->IfIndex,
+ (pAdapt->IfType == IF_TYPE_IEEE80211)?MIB_IF_TYPE_ETHERNET:pAdapt->IfType,
+ pAdapt->AdapterName,
+ NULL,
+ pUnicast->Address.lpSockaddr,
+ pAdapt->Flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+ }
+ }
+ SCTP_BSD_FREE(pAdapterAddrs);
+#endif
+#ifdef INET6
+ AdapterAddrsSize = 0;
+
+ if ((Err = GetAdaptersAddresses(AF_INET6, 0, NULL, NULL, &AdapterAddrsSize)) != 0) {
+ if ((Err != ERROR_BUFFER_OVERFLOW) && (Err != ERROR_INSUFFICIENT_BUFFER)) {
+ SCTP_PRINTF("GetAdaptersV6Addresses() sizing failed with error code %d\n", Err);
+ SCTP_PRINTF("err = %d; AdapterAddrsSize = %d\n", Err, AdapterAddrsSize);
+ return;
+ }
+ }
+ /* Allocate memory from sizing information */
+ if ((pAdapterAddrs = (PIP_ADAPTER_ADDRESSES) GlobalAlloc(GPTR, AdapterAddrsSize)) == NULL) {
+ SCTP_PRINTF("Memory allocation error!\n");
+ return;
+ }
+ /* Get actual adapter information */
+ if ((Err = GetAdaptersAddresses(AF_INET6, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTP_PRINTF("GetAdaptersV6Addresses() failed with error code %d\n", Err);
+ SCTP_BSD_FREE(pAdapterAddrs);
+ return;
+ }
+ /* Enumerate through each returned adapter and save its information */
+ for (pAdapt = pAdapterAddrs; pAdapt; pAdapt = pAdapt->Next) {
+ if (pAdapt->IfType == IF_TYPE_IEEE80211 || pAdapt->IfType == IF_TYPE_ETHERNET_CSMACD) {
+ for (pUnicast = pAdapt->FirstUnicastAddress; pUnicast; pUnicast = pUnicast->Next) {
+ sctp_ifa = sctp_add_addr_to_vrf(0,
+ NULL,
+ pAdapt->Ipv6IfIndex,
+ (pAdapt->IfType == IF_TYPE_IEEE80211)?MIB_IF_TYPE_ETHERNET:pAdapt->IfType,
+ pAdapt->AdapterName,
+ NULL,
+ pUnicast->Address.lpSockaddr,
+ pAdapt->Flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+ }
+ }
+ SCTP_BSD_FREE(pAdapterAddrs);
+#endif
+}
+#elif defined(__Userspace__)
+static void
+sctp_init_ifns_for_vrf(int vrfid)
+{
+#if defined(INET) || defined(INET6)
+ int rc;
+ struct ifaddrs *ifa, *ifas;
+ struct sctp_ifa *sctp_ifa;
+ uint32_t ifa_flags;
+
+ rc = getifaddrs(&ifas);
+ if (rc != 0) {
+ return;
+ }
+ for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL) {
+ continue;
+ }
+#if !defined(INET)
+ if (ifa->ifa_addr->sa_family != AF_INET6) {
+ /* non inet6 skip */
+ continue;
+ }
+#elif !defined(INET6)
+ if (ifa->ifa_addr->sa_family != AF_INET) {
+ /* non inet skip */
+ continue;
+ }
+#else
+ if ((ifa->ifa_addr->sa_family != AF_INET) && (ifa->ifa_addr->sa_family != AF_INET6)) {
+ /* non inet/inet6 skip */
+ continue;
+ }
+#endif
+#if defined(INET6)
+ if ((ifa->ifa_addr->sa_family == AF_INET6) &&
+ IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr)) {
+ /* skip unspecified addresses */
+ continue;
+ }
+#endif
+#if defined(INET)
+ if (ifa->ifa_addr->sa_family == AF_INET &&
+ ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr == 0) {
+ continue;
+ }
+#endif
+ ifa_flags = 0;
+ sctp_ifa = sctp_add_addr_to_vrf(vrfid,
+ NULL,
+ if_nametoindex(ifa->ifa_name),
+ 0,
+ ifa->ifa_name,
+ NULL,
+ ifa->ifa_addr,
+ ifa_flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+ freeifaddrs(ifas);
+#endif
+}
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+static void
+sctp_init_ifns_for_vrf(int vrfid)
+{
+ /* Here we must apply ANY locks needed by the
+ * IFN we access and also make sure we lock
+ * any IFA that exists as we float through the
+ * list of IFA's
+ */
+ struct ifnet **ifnetlist;
+ uint32_t i, j, count;
+ char name[SCTP_IFNAMSIZ];
+ struct ifnet *ifn;
+ struct ifaddr **ifaddrlist;
+ struct ifaddr *ifa;
+ struct in6_ifaddr *ifa6;
+ struct sctp_ifa *sctp_ifa;
+ uint32_t ifa_flags;
+
+ if (ifnet_list_get(IFNET_FAMILY_ANY, &ifnetlist, &count) != 0) {
+ return;
+ }
+ for (i = 0; i < count; i++) {
+ ifn = ifnetlist[i];
+ if (SCTP_BASE_SYSCTL(sctp_ignore_vmware_interfaces) && sctp_is_vmware_interface(ifn)) {
+ continue;
+ }
+ if (sctp_is_desired_interface_type(ifn) == 0) {
+ /* non desired type */
+ continue;
+ }
+ if (ifnet_get_address_list(ifn, &ifaddrlist) != 0) {
+ continue;
+ }
+ for (j = 0; ifaddrlist[j] != NULL; j++) {
+ ifa = ifaddrlist[j];
+ if (ifa->ifa_addr == NULL) {
+ continue;
+ }
+ if ((ifa->ifa_addr->sa_family != AF_INET) && (ifa->ifa_addr->sa_family != AF_INET6)) {
+ /* non inet/inet6 skip */
+ continue;
+ }
+ if (ifa->ifa_addr->sa_family == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr)) {
+ /* skip unspecified addresses */
+ continue;
+ }
+ } else {
+ if (((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr == INADDR_ANY) {
+ continue;
+ }
+ }
+ if (ifa->ifa_addr->sa_family == AF_INET6) {
+ ifa6 = (struct in6_ifaddr *)ifa;
+ ifa_flags = ifa6->ia6_flags;
+ } else {
+ ifa_flags = 0;
+ }
+ SCTP_SNPRINTF(name, SCTP_IFNAMSIZ, "%s%d", ifnet_name(ifn), ifnet_unit(ifn));
+ sctp_ifa = sctp_add_addr_to_vrf(vrfid,
+ (void *)ifn, /* XXX */
+ ifnet_index(ifn),
+ ifnet_type(ifn),
+ name,
+ (void *)ifa, /* XXX */
+ ifa->ifa_addr,
+ ifa_flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+ ifnet_free_address_list(ifaddrlist);
+ }
+ ifnet_list_free(ifnetlist);
+}
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static void
+sctp_init_ifns_for_vrf(int vrfid)
+{
+ /* Here we must apply ANY locks needed by the
+ * IFN we access and also make sure we lock
+ * any IFA that exists as we float through the
+ * list of IFA's
+ */
+ struct epoch_tracker et;
+ struct ifnet *ifn;
+ struct ifaddr *ifa;
+ struct sctp_ifa *sctp_ifa;
+ uint32_t ifa_flags;
+#ifdef INET6
+ struct in6_ifaddr *ifa6;
+#endif
+
+ IFNET_RLOCK();
+ NET_EPOCH_ENTER(et);
+ CK_STAILQ_FOREACH(ifn, &MODULE_GLOBAL(ifnet), if_link) {
+ if (sctp_is_desired_interface_type(ifn) == 0) {
+ /* non desired type */
+ continue;
+ }
+ CK_STAILQ_FOREACH(ifa, &ifn->if_addrhead, ifa_link) {
+ if (ifa->ifa_addr == NULL) {
+ continue;
+ }
+ switch (ifa->ifa_addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr == 0) {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr)) {
+ /* skip unspecified addresses */
+ continue;
+ }
+ break;
+#endif
+ default:
+ continue;
+ }
+ switch (ifa->ifa_addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ ifa_flags = 0;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ifa6 = (struct in6_ifaddr *)ifa;
+ ifa_flags = ifa6->ia6_flags;
+ break;
+#endif
+ default:
+ ifa_flags = 0;
+ break;
+ }
+ sctp_ifa = sctp_add_addr_to_vrf(vrfid,
+ (void *)ifn,
+ ifn->if_index,
+ ifn->if_type,
+ ifn->if_xname,
+ (void *)ifa,
+ ifa->ifa_addr,
+ ifa_flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+ }
+ NET_EPOCH_EXIT(et);
+ IFNET_RUNLOCK();
+}
+#endif
+
+void
+sctp_init_vrf_list(int vrfid)
+{
+ if (vrfid > SCTP_MAX_VRF_ID)
+ /* can't do that */
+ return;
+
+ /* Don't care about return here */
+ (void)sctp_allocate_vrf(vrfid);
+
+ /* Now we need to build all the ifn's
+ * for this vrf and there addresses
+ */
+ sctp_init_ifns_for_vrf(vrfid);
+}
+
+void
+sctp_addr_change(struct ifaddr *ifa, int cmd)
+{
+#if defined(__Userspace__)
+ return;
+#else
+ uint32_t ifa_flags = 0;
+
+ if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
+ return;
+ }
+ /* BSD only has one VRF, if this changes
+ * we will need to hook in the right
+ * things here to get the id to pass to
+ * the address management routine.
+ */
+ if (SCTP_BASE_VAR(first_time) == 0) {
+ /* Special test to see if my ::1 will showup with this */
+ SCTP_BASE_VAR(first_time) = 1;
+ sctp_init_ifns_for_vrf(SCTP_DEFAULT_VRFID);
+ }
+
+ if ((cmd != RTM_ADD) && (cmd != RTM_DELETE)) {
+ /* don't know what to do with this */
+ return;
+ }
+
+ if (ifa->ifa_addr == NULL) {
+ return;
+ }
+ if (sctp_is_desired_interface_type(ifa->ifa_ifp) == 0) {
+ /* non desired type */
+ return;
+ }
+ switch (ifa->ifa_addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr == 0) {
+ return;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ifa_flags = ((struct in6_ifaddr *)ifa)->ia6_flags;
+ if (IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr)) {
+ /* skip unspecified addresses */
+ return;
+ }
+ break;
+#endif
+ default:
+ /* non inet/inet6 skip */
+ return;
+ }
+ if (cmd == RTM_ADD) {
+ (void)sctp_add_addr_to_vrf(SCTP_DEFAULT_VRFID, (void *)ifa->ifa_ifp,
+#if defined(__APPLE__) && !defined(__Userspace__)
+ ifnet_index(ifa->ifa_ifp), ifnet_type(ifa->ifa_ifp), ifnet_name(ifa->ifa_ifp),
+#else
+ ifa->ifa_ifp->if_index, ifa->ifa_ifp->if_type, ifa->ifa_ifp->if_xname,
+#endif
+ (void *)ifa, ifa->ifa_addr, ifa_flags, 1);
+ } else {
+ sctp_del_addr_from_vrf(SCTP_DEFAULT_VRFID, ifa->ifa_addr,
+#if defined(__APPLE__) && !defined(__Userspace__)
+ ifnet_index(ifa->ifa_ifp),
+ ifnet_name(ifa->ifa_ifp));
+#else
+ ifa->ifa_ifp->if_index,
+ ifa->ifa_ifp->if_xname);
+#endif
+
+ /* We don't bump refcount here so when it completes
+ * the final delete will happen.
+ */
+ }
+#endif
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+void
+sctp_addr_change_event_handler(void *arg __unused, struct ifaddr *ifa, int cmd) {
+ sctp_addr_change(ifa, cmd);
+}
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+void
+sctp_add_or_del_interfaces(int (*pred)(struct ifnet *), int add)
+{
+ struct ifnet **ifnetlist;
+ struct ifaddr **ifaddrlist;
+ uint32_t i, j, count;
+
+ if (ifnet_list_get(IFNET_FAMILY_ANY, &ifnetlist, &count) != 0) {
+ return;
+ }
+ for (i = 0; i < count; i++) {
+ if (!(*pred)(ifnetlist[i])) {
+ continue;
+ }
+ if (ifnet_get_address_list(ifnetlist[i], &ifaddrlist) != 0) {
+ continue;
+ }
+ for (j = 0; ifaddrlist[j] != NULL; j++) {
+ sctp_addr_change(ifaddrlist[j], add ? RTM_ADD : RTM_DELETE);
+ }
+ ifnet_free_address_list(ifaddrlist);
+ }
+ ifnet_list_free(ifnetlist);
+ return;
+}
+#endif
+
+struct mbuf *
+sctp_get_mbuf_for_msg(unsigned int space_needed, int want_header,
+ int how, int allonebuf, int type)
+{
+ struct mbuf *m = NULL;
+#if defined(__FreeBSD__) || defined(__Userspace__)
+#if defined(__Userspace__)
+ m = m_getm2(NULL, space_needed, how, type, want_header ? M_PKTHDR : 0, allonebuf);
+#else
+ m = m_getm2(NULL, space_needed, how, type, want_header ? M_PKTHDR : 0);
+#endif
+ if (m == NULL) {
+ /* bad, no memory */
+ return (m);
+ }
+#if !defined(__Userspace__)
+ if (allonebuf) {
+ if (SCTP_BUF_SIZE(m) < space_needed) {
+ m_freem(m);
+ return (NULL);
+ }
+ KASSERT(SCTP_BUF_NEXT(m) == NULL, ("%s: no chain allowed", __func__));
+ }
+#endif
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mb(m, SCTP_MBUF_IALLOC);
+ }
+#endif
+#else
+ int mbuf_threshold;
+ unsigned int size;
+
+ if (want_header) {
+ MGETHDR(m, how, type);
+ size = MHLEN;
+ } else {
+ MGET(m, how, type);
+ size = MLEN;
+ }
+ if (m == NULL) {
+ return (NULL);
+ }
+ if (allonebuf == 0) {
+ mbuf_threshold = SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count);
+ } else {
+ mbuf_threshold = 1;
+ }
+
+ if (space_needed > (unsigned int)(((mbuf_threshold - 1) * MLEN) + MHLEN)) {
+ MCLGET(m, how);
+ if (m == NULL) {
+ return (NULL);
+ }
+ if (SCTP_BUF_IS_EXTENDED(m) == 0) {
+ sctp_m_freem(m);
+ return (NULL);
+ }
+ size = SCTP_BUF_EXTEND_SIZE(m);
+ }
+ if (allonebuf != 0 && size < space_needed) {
+ m_freem(m);
+ return (NULL);
+ }
+ SCTP_BUF_LEN(m) = 0;
+ SCTP_BUF_NEXT(m) = SCTP_BUF_NEXT_PKT(m) = NULL;
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mb(m, SCTP_MBUF_IALLOC);
+ }
+#endif
+#endif
+ return (m);
+}
+
+#ifdef SCTP_PACKET_LOGGING
+void
+sctp_packet_log(struct mbuf *m)
+{
+ int *lenat, thisone;
+ void *copyto;
+ uint32_t *tick_tock;
+ int length;
+ int total_len;
+ int grabbed_lock = 0;
+ int value, newval, thisend, thisbegin;
+ /*
+ * Buffer layout.
+ * -sizeof this entry (total_len)
+ * -previous end (value)
+ * -ticks of log (ticks)
+ * o -ip packet
+ * o -as logged
+ * - where this started (thisbegin)
+ * x <--end points here
+ */
+ length = SCTP_HEADER_LEN(m);
+ total_len = SCTP_SIZE32((length + (4 * sizeof(int))));
+ /* Log a packet to the buffer. */
+ if (total_len> SCTP_PACKET_LOG_SIZE) {
+ /* Can't log this packet I have not a buffer big enough */
+ return;
+ }
+ if (length < (int)(SCTP_MIN_V4_OVERHEAD + sizeof(struct sctp_cookie_ack_chunk))) {
+ return;
+ }
+ atomic_add_int(&SCTP_BASE_VAR(packet_log_writers), 1);
+ try_again:
+ if (SCTP_BASE_VAR(packet_log_writers) > SCTP_PKTLOG_WRITERS_NEED_LOCK) {
+ SCTP_IP_PKTLOG_LOCK();
+ grabbed_lock = 1;
+ again_locked:
+ value = SCTP_BASE_VAR(packet_log_end);
+ newval = SCTP_BASE_VAR(packet_log_end) + total_len;
+ if (newval >= SCTP_PACKET_LOG_SIZE) {
+ /* we wrapped */
+ thisbegin = 0;
+ thisend = total_len;
+ } else {
+ thisbegin = SCTP_BASE_VAR(packet_log_end);
+ thisend = newval;
+ }
+ if (!(atomic_cmpset_int(&SCTP_BASE_VAR(packet_log_end), value, thisend))) {
+ goto again_locked;
+ }
+ } else {
+ value = SCTP_BASE_VAR(packet_log_end);
+ newval = SCTP_BASE_VAR(packet_log_end) + total_len;
+ if (newval >= SCTP_PACKET_LOG_SIZE) {
+ /* we wrapped */
+ thisbegin = 0;
+ thisend = total_len;
+ } else {
+ thisbegin = SCTP_BASE_VAR(packet_log_end);
+ thisend = newval;
+ }
+ if (!(atomic_cmpset_int(&SCTP_BASE_VAR(packet_log_end), value, thisend))) {
+ goto try_again;
+ }
+ }
+ /* Sanity check */
+ if (thisend >= SCTP_PACKET_LOG_SIZE) {
+ SCTP_PRINTF("Insanity stops a log thisbegin:%d thisend:%d writers:%d lock:%d end:%d\n",
+ thisbegin,
+ thisend,
+ SCTP_BASE_VAR(packet_log_writers),
+ grabbed_lock,
+ SCTP_BASE_VAR(packet_log_end));
+ SCTP_BASE_VAR(packet_log_end) = 0;
+ goto no_log;
+ }
+ lenat = (int *)&SCTP_BASE_VAR(packet_log_buffer)[thisbegin];
+ *lenat = total_len;
+ lenat++;
+ *lenat = value;
+ lenat++;
+ tick_tock = (uint32_t *)lenat;
+ lenat++;
+ *tick_tock = sctp_get_tick_count();
+ copyto = (void *)lenat;
+ thisone = thisend - sizeof(int);
+ lenat = (int *)&SCTP_BASE_VAR(packet_log_buffer)[thisone];
+ *lenat = thisbegin;
+ if (grabbed_lock) {
+ SCTP_IP_PKTLOG_UNLOCK();
+ grabbed_lock = 0;
+ }
+ m_copydata(m, 0, length, (caddr_t)copyto);
+ no_log:
+ if (grabbed_lock) {
+ SCTP_IP_PKTLOG_UNLOCK();
+ }
+ atomic_subtract_int(&SCTP_BASE_VAR(packet_log_writers), 1);
+}
+
+int
+sctp_copy_out_packet_log(uint8_t *target, int length)
+{
+ /* We wind through the packet log starting at
+ * start copying up to length bytes out.
+ * We return the number of bytes copied.
+ */
+ int this_copy;
+ int *lenat;
+ int did_delay = 0;
+
+ if (length < (int)(2 * sizeof(int))) {
+ /* not enough room */
+ return (0);
+ }
+ if (SCTP_PKTLOG_WRITERS_NEED_LOCK) {
+ atomic_add_int(&SCTP_BASE_VAR(packet_log_writers), SCTP_PKTLOG_WRITERS_NEED_LOCK);
+ again:
+ if ((did_delay == 0) && (SCTP_BASE_VAR(packet_log_writers) != SCTP_PKTLOG_WRITERS_NEED_LOCK)) {
+ /* we delay here for just a moment hoping the writer(s) that were
+ * present when we entered will have left and we only have
+ * locking ones that will contend with us for the lock. This
+ * does not assure 100% access, but its good enough for
+ * a logging facility like this.
+ */
+ did_delay = 1;
+ DELAY(10);
+ goto again;
+ }
+ }
+ SCTP_IP_PKTLOG_LOCK();
+ lenat = (int *)target;
+ *lenat = SCTP_BASE_VAR(packet_log_end);
+ lenat++;
+ this_copy = min((length - sizeof(int)), SCTP_PACKET_LOG_SIZE);
+ memcpy((void *)lenat, (void *)SCTP_BASE_VAR(packet_log_buffer), this_copy);
+ if (SCTP_PKTLOG_WRITERS_NEED_LOCK) {
+ atomic_subtract_int(&SCTP_BASE_VAR(packet_log_writers),
+ SCTP_PKTLOG_WRITERS_NEED_LOCK);
+ }
+ SCTP_IP_PKTLOG_UNLOCK();
+ return (this_copy + sizeof(int));
+}
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_bsd_addr.h b/netwerk/sctp/src/netinet/sctp_bsd_addr.h
new file mode 100644
index 0000000000..5e8549eec5
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_bsd_addr.h
@@ -0,0 +1,67 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_BSD_ADDR_H_
+#define _NETINET_SCTP_BSD_ADDR_H_
+
+#include <netinet/sctp_pcb.h>
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+extern struct iterator_control sctp_it_ctl;
+void sctp_wakeup_iterator(void);
+
+void sctp_startup_iterator(void);
+
+#ifdef INET6
+void sctp_gather_internal_ifa_flags(struct sctp_ifa *ifa);
+#endif
+
+#ifdef SCTP_PACKET_LOGGING
+
+void sctp_packet_log(struct mbuf *m);
+int sctp_copy_out_packet_log(uint8_t *target, int length);
+
+#endif
+
+void sctp_addr_change(struct ifaddr *ifa, int cmd);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+
+void sctp_addr_change_event_handler(void *, struct ifaddr *, int);
+#endif
+
+void sctp_add_or_del_interfaces(int (*pred)(struct ifnet *), int add);
+
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_callout.c b/netwerk/sctp/src/netinet/sctp_callout.c
new file mode 100644
index 0000000000..ee6cd4cddd
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_callout.c
@@ -0,0 +1,249 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(__Userspace__)
+#include <sys/types.h>
+#if !defined(_WIN32)
+#include <sys/wait.h>
+#include <unistd.h>
+#include <pthread.h>
+#endif
+#if defined(__native_client__)
+#include <sys/select.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <user_atomic.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#else
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_callout.h>
+#include <netinet/sctp_pcb.h>
+#endif
+#include <netinet/sctputil.h>
+
+/*
+ * Callout/Timer routines for OS that doesn't have them
+ */
+#if defined(__APPLE__) || defined(__Userspace__)
+static uint32_t ticks = 0;
+#else
+extern int ticks;
+#endif
+
+uint32_t sctp_get_tick_count(void) {
+ uint32_t ret;
+
+ SCTP_TIMERQ_LOCK();
+ ret = ticks;
+ SCTP_TIMERQ_UNLOCK();
+ return ret;
+}
+
+/*
+ * SCTP_TIMERQ_LOCK protects:
+ * - SCTP_BASE_INFO(callqueue)
+ * - sctp_os_timer_next: next timer to check
+ */
+static sctp_os_timer_t *sctp_os_timer_next = NULL;
+
+void
+sctp_os_timer_init(sctp_os_timer_t *c)
+{
+ memset(c, 0, sizeof(*c));
+}
+
+int
+sctp_os_timer_start(sctp_os_timer_t *c, uint32_t to_ticks, void (*ftn) (void *),
+ void *arg)
+{
+ int ret = 0;
+
+ /* paranoia */
+ if ((c == NULL) || (ftn == NULL))
+ return (ret);
+
+ SCTP_TIMERQ_LOCK();
+ /* check to see if we're rescheduling a timer */
+ if (c->c_flags & SCTP_CALLOUT_PENDING) {
+ ret = 1;
+ if (c == sctp_os_timer_next) {
+ sctp_os_timer_next = TAILQ_NEXT(c, tqe);
+ }
+ TAILQ_REMOVE(&SCTP_BASE_INFO(callqueue), c, tqe);
+ /*
+ * part of the normal "stop a pending callout" process
+ * is to clear the CALLOUT_ACTIVE and CALLOUT_PENDING
+ * flags. We don't bother since we are setting these
+ * below and we still hold the lock.
+ */
+ }
+
+ /*
+ * We could unlock/splx here and lock/spl at the TAILQ_INSERT_TAIL,
+ * but there's no point since doing this setup doesn't take much time.
+ */
+ if (to_ticks == 0)
+ to_ticks = 1;
+
+ c->c_arg = arg;
+ c->c_flags = (SCTP_CALLOUT_ACTIVE | SCTP_CALLOUT_PENDING);
+ c->c_func = ftn;
+ c->c_time = ticks + to_ticks;
+ TAILQ_INSERT_TAIL(&SCTP_BASE_INFO(callqueue), c, tqe);
+ SCTP_TIMERQ_UNLOCK();
+ return (ret);
+}
+
+int
+sctp_os_timer_stop(sctp_os_timer_t *c)
+{
+ SCTP_TIMERQ_LOCK();
+ /*
+ * Don't attempt to delete a callout that's not on the queue.
+ */
+ if ((c->c_flags & SCTP_CALLOUT_PENDING) == 0) {
+ c->c_flags &= ~SCTP_CALLOUT_ACTIVE;
+ SCTP_TIMERQ_UNLOCK();
+ return (0);
+ }
+ c->c_flags &= ~(SCTP_CALLOUT_ACTIVE | SCTP_CALLOUT_PENDING);
+ if (c == sctp_os_timer_next) {
+ sctp_os_timer_next = TAILQ_NEXT(c, tqe);
+ }
+ TAILQ_REMOVE(&SCTP_BASE_INFO(callqueue), c, tqe);
+ SCTP_TIMERQ_UNLOCK();
+ return (1);
+}
+
+void
+sctp_handle_tick(uint32_t elapsed_ticks)
+{
+ sctp_os_timer_t *c;
+ void (*c_func)(void *);
+ void *c_arg;
+
+ SCTP_TIMERQ_LOCK();
+ /* update our tick count */
+ ticks += elapsed_ticks;
+ c = TAILQ_FIRST(&SCTP_BASE_INFO(callqueue));
+ while (c) {
+ if (SCTP_UINT32_GE(ticks, c->c_time)) {
+ sctp_os_timer_next = TAILQ_NEXT(c, tqe);
+ TAILQ_REMOVE(&SCTP_BASE_INFO(callqueue), c, tqe);
+ c_func = c->c_func;
+ c_arg = c->c_arg;
+ c->c_flags &= ~SCTP_CALLOUT_PENDING;
+ SCTP_TIMERQ_UNLOCK();
+ c_func(c_arg);
+ SCTP_TIMERQ_LOCK();
+ c = sctp_os_timer_next;
+ } else {
+ c = TAILQ_NEXT(c, tqe);
+ }
+ }
+ sctp_os_timer_next = NULL;
+ SCTP_TIMERQ_UNLOCK();
+}
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+void
+sctp_timeout(void *arg SCTP_UNUSED)
+{
+ sctp_handle_tick(SCTP_BASE_VAR(sctp_main_timer_ticks));
+ sctp_start_main_timer();
+}
+#endif
+
+#if defined(__Userspace__)
+#define TIMEOUT_INTERVAL 10
+
+void *
+user_sctp_timer_iterate(void *arg)
+{
+ sctp_userspace_set_threadname("SCTP timer");
+ for (;;) {
+#if defined(_WIN32)
+ Sleep(TIMEOUT_INTERVAL);
+#else
+ struct timespec amount, remaining;
+
+ remaining.tv_sec = 0;
+ remaining.tv_nsec = TIMEOUT_INTERVAL * 1000 * 1000;
+ do {
+ amount = remaining;
+ } while (nanosleep(&amount, &remaining) == -1);
+#endif
+ if (atomic_cmpset_int(&SCTP_BASE_VAR(timer_thread_should_exit), 1, 1)) {
+ break;
+ }
+ sctp_handle_tick(sctp_msecs_to_ticks(TIMEOUT_INTERVAL));
+ }
+ return (NULL);
+}
+
+void
+sctp_start_timer_thread(void)
+{
+ /*
+ * No need to do SCTP_TIMERQ_LOCK_INIT();
+ * here, it is being done in sctp_pcb_init()
+ */
+ int rc;
+
+ rc = sctp_userspace_thread_create(&SCTP_BASE_VAR(timer_thread), user_sctp_timer_iterate);
+ if (rc) {
+ SCTP_PRINTF("ERROR; return code from sctp_thread_create() is %d\n", rc);
+ } else {
+ SCTP_BASE_VAR(timer_thread_started) = 1;
+ }
+}
+
+void
+sctp_stop_timer_thread(void)
+{
+ atomic_cmpset_int(&SCTP_BASE_VAR(timer_thread_should_exit), 0, 1);
+ if (SCTP_BASE_VAR(timer_thread_started)) {
+#if defined(_WIN32)
+ WaitForSingleObject(SCTP_BASE_VAR(timer_thread), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(timer_thread));
+#else
+ pthread_join(SCTP_BASE_VAR(timer_thread), NULL);
+#endif
+ }
+}
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_callout.h b/netwerk/sctp/src/netinet/sctp_callout.h
new file mode 100644
index 0000000000..2a78e414fb
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_callout.h
@@ -0,0 +1,114 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_CALLOUT_
+#define _NETINET_SCTP_CALLOUT_
+
+/*
+ * NOTE: the following MACROS are required for locking the callout
+ * queue along with a lock/mutex in the OS specific headers and
+ * implementation files::
+ * - SCTP_TIMERQ_LOCK()
+ * - SCTP_TIMERQ_UNLOCK()
+ * - SCTP_TIMERQ_LOCK_INIT()
+ * - SCTP_TIMERQ_LOCK_DESTROY()
+ */
+
+#define _SCTP_NEEDS_CALLOUT_ 1
+
+#define SCTP_TICKS_PER_FASTTIMO 20 /* called about every 20ms */
+
+#if defined(__Userspace__)
+#if defined(_WIN32)
+#define SCTP_TIMERQ_LOCK() EnterCriticalSection(&SCTP_BASE_VAR(timer_mtx))
+#define SCTP_TIMERQ_UNLOCK() LeaveCriticalSection(&SCTP_BASE_VAR(timer_mtx))
+#define SCTP_TIMERQ_LOCK_INIT() InitializeCriticalSection(&SCTP_BASE_VAR(timer_mtx))
+#define SCTP_TIMERQ_LOCK_DESTROY() DeleteCriticalSection(&SCTP_BASE_VAR(timer_mtx))
+#else
+#ifdef INVARIANTS
+#define SCTP_TIMERQ_LOCK() KASSERT(pthread_mutex_lock(&SCTP_BASE_VAR(timer_mtx)) == 0, ("%s: timer_mtx already locked", __func__))
+#define SCTP_TIMERQ_UNLOCK() KASSERT(pthread_mutex_unlock(&SCTP_BASE_VAR(timer_mtx)) == 0, ("%s: timer_mtx not locked", __func__))
+#else
+#define SCTP_TIMERQ_LOCK() (void)pthread_mutex_lock(&SCTP_BASE_VAR(timer_mtx))
+#define SCTP_TIMERQ_UNLOCK() (void)pthread_mutex_unlock(&SCTP_BASE_VAR(timer_mtx))
+#endif
+#define SCTP_TIMERQ_LOCK_INIT() (void)pthread_mutex_init(&SCTP_BASE_VAR(timer_mtx), &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_TIMERQ_LOCK_DESTROY() (void)pthread_mutex_destroy(&SCTP_BASE_VAR(timer_mtx))
+#endif
+#endif
+
+uint32_t sctp_get_tick_count(void);
+
+TAILQ_HEAD(calloutlist, sctp_callout);
+
+struct sctp_callout {
+ TAILQ_ENTRY(sctp_callout) tqe;
+ uint32_t c_time; /* ticks to the event */
+ void *c_arg; /* function argument */
+ void (*c_func)(void *); /* function to call */
+ int c_flags; /* state of this entry */
+};
+typedef struct sctp_callout sctp_os_timer_t;
+
+#define SCTP_CALLOUT_ACTIVE 0x0002 /* callout is currently active */
+#define SCTP_CALLOUT_PENDING 0x0004 /* callout is waiting for timeout */
+
+void sctp_os_timer_init(sctp_os_timer_t *tmr);
+/* Returns 1 if pending timer was rescheduled, 0 otherwise. */
+int sctp_os_timer_start(sctp_os_timer_t *, uint32_t, void (*)(void *), void *);
+/* Returns 1 if pending timer was stopped, 0 otherwise. */
+int sctp_os_timer_stop(sctp_os_timer_t *);
+void sctp_handle_tick(uint32_t);
+
+#define SCTP_OS_TIMER_INIT sctp_os_timer_init
+/*
+ * NOTE: The next two shouldn't be called directly outside of sctp_timer_start()
+ * and sctp_timer_stop(), since they don't handle incrementing/decrementing
+ * relevant reference counts.
+ */
+#define SCTP_OS_TIMER_START sctp_os_timer_start
+#define SCTP_OS_TIMER_STOP sctp_os_timer_stop
+/* MT FIXME: Is the following correct? */
+#define SCTP_OS_TIMER_STOP_DRAIN SCTP_OS_TIMER_STOP
+#define SCTP_OS_TIMER_PENDING(tmr) ((tmr)->c_flags & SCTP_CALLOUT_PENDING)
+#define SCTP_OS_TIMER_ACTIVE(tmr) ((tmr)->c_flags & SCTP_CALLOUT_ACTIVE)
+#define SCTP_OS_TIMER_DEACTIVATE(tmr) ((tmr)->c_flags &= ~SCTP_CALLOUT_ACTIVE)
+
+#if defined(__Userspace__)
+void sctp_start_timer_thread(void);
+void sctp_stop_timer_thread(void);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+void sctp_timeout(void *);
+#endif
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_cc_functions.c b/netwerk/sctp/src/netinet/sctp_cc_functions.c
new file mode 100644
index 0000000000..176338a05e
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_cc_functions.c
@@ -0,0 +1,2494 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_asconf.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/sctp_kdtrace.h>
+#endif
+
+#define SHIFT_MPTCP_MULTI_N 40
+#define SHIFT_MPTCP_MULTI_Z 16
+#define SHIFT_MPTCP_MULTI 8
+
+#ifdef KDTRACE_HOOKS
+#define __dtrace
+#else
+#define __dtrace __unused
+#endif
+
+static void
+sctp_enforce_cwnd_limit(struct sctp_association *assoc, struct sctp_nets *net)
+{
+ if ((assoc->max_cwnd > 0) &&
+ (net->cwnd > assoc->max_cwnd) &&
+ (net->cwnd > (net->mtu - sizeof(struct sctphdr)))) {
+ net->cwnd = assoc->max_cwnd;
+ if (net->cwnd < (net->mtu - sizeof(struct sctphdr))) {
+ net->cwnd = net->mtu - sizeof(struct sctphdr);
+ }
+ }
+}
+
+static void
+sctp_set_initial_cc_param(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ struct sctp_association *assoc;
+ uint32_t cwnd_in_mtu;
+
+ assoc = &stcb->asoc;
+ cwnd_in_mtu = SCTP_BASE_SYSCTL(sctp_initial_cwnd);
+ if (cwnd_in_mtu == 0) {
+ /* Using 0 means that the value of RFC 4960 is used. */
+ net->cwnd = min((net->mtu * 4), max((2 * net->mtu), SCTP_INITIAL_CWND));
+ } else {
+ /*
+ * We take the minimum of the burst limit and the
+ * initial congestion window.
+ */
+ if ((assoc->max_burst > 0) && (cwnd_in_mtu > assoc->max_burst))
+ cwnd_in_mtu = assoc->max_burst;
+ net->cwnd = (net->mtu - sizeof(struct sctphdr)) * cwnd_in_mtu;
+ }
+ if ((stcb->asoc.sctp_cmt_on_off == SCTP_CMT_RPV1) ||
+ (stcb->asoc.sctp_cmt_on_off == SCTP_CMT_RPV2)) {
+ /* In case of resource pooling initialize appropriately */
+ net->cwnd /= assoc->numnets;
+ if (net->cwnd < (net->mtu - sizeof(struct sctphdr))) {
+ net->cwnd = net->mtu - sizeof(struct sctphdr);
+ }
+ }
+ sctp_enforce_cwnd_limit(assoc, net);
+ net->ssthresh = assoc->peers_rwnd;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, init,
+ stcb->asoc.my_vtag, ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)), net,
+ 0, net->cwnd);
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) &
+ (SCTP_CWND_MONITOR_ENABLE|SCTP_CWND_LOGGING_ENABLE)) {
+ sctp_log_cwnd(stcb, net, 0, SCTP_CWND_INITIALIZATION);
+ }
+}
+
+static void
+sctp_cwnd_update_after_fr(struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ struct sctp_nets *net;
+ uint32_t t_ssthresh, t_cwnd;
+ uint64_t t_ucwnd_sbw;
+
+ /* MT FIXME: Don't compute this over and over again */
+ t_ssthresh = 0;
+ t_cwnd = 0;
+ t_ucwnd_sbw = 0;
+ if ((asoc->sctp_cmt_on_off == SCTP_CMT_RPV1) ||
+ (asoc->sctp_cmt_on_off == SCTP_CMT_RPV2)) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ t_ssthresh += net->ssthresh;
+ t_cwnd += net->cwnd;
+ if (net->lastsa > 0) {
+ t_ucwnd_sbw += (uint64_t)net->cwnd / (uint64_t)net->lastsa;
+ }
+ }
+ if (t_ucwnd_sbw == 0) {
+ t_ucwnd_sbw = 1;
+ }
+ }
+
+ /*-
+ * CMT fast recovery code. Need to debug. ((sctp_cmt_on_off > 0) &&
+ * (net->fast_retran_loss_recovery == 0)))
+ */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if ((asoc->fast_retran_loss_recovery == 0) ||
+ (asoc->sctp_cmt_on_off > 0)) {
+ /* out of a RFC2582 Fast recovery window? */
+ if (net->net_ack > 0) {
+ /*
+ * per section 7.2.3, are there any
+ * destinations that had a fast retransmit
+ * to them. If so what we need to do is
+ * adjust ssthresh and cwnd.
+ */
+ struct sctp_tmit_chunk *lchk;
+ int old_cwnd = net->cwnd;
+
+ if ((asoc->sctp_cmt_on_off == SCTP_CMT_RPV1) ||
+ (asoc->sctp_cmt_on_off == SCTP_CMT_RPV2)) {
+ if (asoc->sctp_cmt_on_off == SCTP_CMT_RPV1) {
+ net->ssthresh = (uint32_t)(((uint64_t)4 *
+ (uint64_t)net->mtu *
+ (uint64_t)net->ssthresh) /
+ (uint64_t)t_ssthresh);
+ }
+ if (asoc->sctp_cmt_on_off == SCTP_CMT_RPV2) {
+ uint32_t srtt;
+
+ srtt = net->lastsa;
+ /* lastsa>>3; we don't need to devide ...*/
+ if (srtt == 0) {
+ srtt = 1;
+ }
+ /* Short Version => Equal to Contel Version MBe */
+ net->ssthresh = (uint32_t) (((uint64_t)4 *
+ (uint64_t)net->mtu *
+ (uint64_t)net->cwnd) /
+ ((uint64_t)srtt *
+ t_ucwnd_sbw));
+ /* INCREASE FACTOR */;
+ }
+ if ((net->cwnd > t_cwnd / 2) &&
+ (net->ssthresh < net->cwnd - t_cwnd / 2)) {
+ net->ssthresh = net->cwnd - t_cwnd / 2;
+ }
+ if (net->ssthresh < net->mtu) {
+ net->ssthresh = net->mtu;
+ }
+ } else {
+ net->ssthresh = net->cwnd / 2;
+ if (net->ssthresh < (net->mtu * 2)) {
+ net->ssthresh = 2 * net->mtu;
+ }
+ }
+ net->cwnd = net->ssthresh;
+ sctp_enforce_cwnd_limit(asoc, net);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, fr,
+ stcb->asoc.my_vtag, ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)), net,
+ old_cwnd, net->cwnd);
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd),
+ SCTP_CWND_LOG_FROM_FR);
+ }
+ lchk = TAILQ_FIRST(&asoc->send_queue);
+
+ net->partial_bytes_acked = 0;
+ /* Turn on fast recovery window */
+ asoc->fast_retran_loss_recovery = 1;
+ if (lchk == NULL) {
+ /* Mark end of the window */
+ asoc->fast_recovery_tsn = asoc->sending_seq - 1;
+ } else {
+ asoc->fast_recovery_tsn = lchk->rec.data.tsn - 1;
+ }
+
+ /*
+ * CMT fast recovery -- per destination
+ * recovery variable.
+ */
+ net->fast_retran_loss_recovery = 1;
+
+ if (lchk == NULL) {
+ /* Mark end of the window */
+ net->fast_recovery_tsn = asoc->sending_seq - 1;
+ } else {
+ net->fast_recovery_tsn = lchk->rec.data.tsn - 1;
+ }
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_CC_FUNCTIONS + SCTP_LOC_1);
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net);
+ }
+ } else if (net->net_ack > 0) {
+ /*
+ * Mark a peg that we WOULD have done a cwnd
+ * reduction but RFC2582 prevented this action.
+ */
+ SCTP_STAT_INCR(sctps_fastretransinrtt);
+ }
+ }
+}
+
+/* Defines for instantaneous bw decisions */
+#define SCTP_INST_LOOSING 1 /* Losing to other flows */
+#define SCTP_INST_NEUTRAL 2 /* Neutral, no indication */
+#define SCTP_INST_GAINING 3 /* Gaining, step down possible */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static int
+cc_bw_same(struct sctp_tcb *stcb, struct sctp_nets *net, uint64_t nbw,
+ uint64_t rtt_offset, uint64_t vtag, uint8_t inst_ind)
+#else
+static int
+cc_bw_same(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, uint64_t nbw,
+ uint64_t rtt_offset, uint8_t inst_ind)
+#endif
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint64_t oth __dtrace, probepoint __dtrace;
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ probepoint = (((uint64_t)net->cwnd) << 32);
+#endif
+ if (net->rtt > net->cc_mod.rtcc.lbw_rtt + rtt_offset) {
+ /*
+ * rtt increased
+ * we don't update bw.. so we don't
+ * update the rtt either.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Probe point 5 */
+ probepoint |= ((5 << 16) | 1);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ if ((net->cc_mod.rtcc.steady_step) && (inst_ind != SCTP_INST_LOOSING)) {
+ if (net->cc_mod.rtcc.last_step_state == 5)
+ net->cc_mod.rtcc.step_cnt++;
+ else
+ net->cc_mod.rtcc.step_cnt = 1;
+ net->cc_mod.rtcc.last_step_state = 5;
+ if ((net->cc_mod.rtcc.step_cnt == net->cc_mod.rtcc.steady_step) ||
+ ((net->cc_mod.rtcc.step_cnt > net->cc_mod.rtcc.steady_step) &&
+ ((net->cc_mod.rtcc.step_cnt % net->cc_mod.rtcc.steady_step) == 0))) {
+ /* Try a step down */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ oth = net->cc_mod.rtcc.vol_reduce;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.step_cnt;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.last_step_state;
+ SDT_PROBE5(sctp, cwnd, net, rttstep,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ oth,
+ probepoint);
+#endif
+ if (net->cwnd > (4 * net->mtu)) {
+ net->cwnd -= net->mtu;
+ net->cc_mod.rtcc.vol_reduce++;
+ } else {
+ net->cc_mod.rtcc.step_cnt = 0;
+ }
+ }
+ }
+ return (1);
+ }
+ if (net->rtt < net->cc_mod.rtcc.lbw_rtt-rtt_offset) {
+ /*
+ * rtt decreased, there could be more room.
+ * we update both the bw and the rtt here to
+ * lock this in as a good step down.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Probe point 6 */
+ probepoint |= ((6 << 16) | 0);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ if (net->cc_mod.rtcc.steady_step) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ oth = net->cc_mod.rtcc.vol_reduce;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.step_cnt;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.last_step_state;
+ SDT_PROBE5(sctp, cwnd, net, rttstep,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ oth,
+ probepoint);
+#endif
+ if ((net->cc_mod.rtcc.last_step_state == 5) &&
+ (net->cc_mod.rtcc.step_cnt > net->cc_mod.rtcc.steady_step)) {
+ /* Step down worked */
+ net->cc_mod.rtcc.step_cnt = 0;
+ return (1);
+ } else {
+ net->cc_mod.rtcc.last_step_state = 6;
+ net->cc_mod.rtcc.step_cnt = 0;
+ }
+ }
+ net->cc_mod.rtcc.lbw = nbw;
+ net->cc_mod.rtcc.lbw_rtt = net->rtt;
+ net->cc_mod.rtcc.cwnd_at_bw_set = net->cwnd;
+ if (inst_ind == SCTP_INST_GAINING)
+ return (1);
+ else if (inst_ind == SCTP_INST_NEUTRAL)
+ return (1);
+ else
+ return (0);
+ }
+ /* Ok bw and rtt remained the same .. no update to any
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Probe point 7 */
+ probepoint |= ((7 << 16) | net->cc_mod.rtcc.ret_from_eq);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ if ((net->cc_mod.rtcc.steady_step) && (inst_ind != SCTP_INST_LOOSING)) {
+ if (net->cc_mod.rtcc.last_step_state == 5)
+ net->cc_mod.rtcc.step_cnt++;
+ else
+ net->cc_mod.rtcc.step_cnt = 1;
+ net->cc_mod.rtcc.last_step_state = 5;
+ if ((net->cc_mod.rtcc.step_cnt == net->cc_mod.rtcc.steady_step) ||
+ ((net->cc_mod.rtcc.step_cnt > net->cc_mod.rtcc.steady_step) &&
+ ((net->cc_mod.rtcc.step_cnt % net->cc_mod.rtcc.steady_step) == 0))) {
+ /* Try a step down */
+ if (net->cwnd > (4 * net->mtu)) {
+ net->cwnd -= net->mtu;
+ net->cc_mod.rtcc.vol_reduce++;
+ return (1);
+ } else {
+ net->cc_mod.rtcc.step_cnt = 0;
+ }
+ }
+ }
+ if (inst_ind == SCTP_INST_GAINING)
+ return (1);
+ else if (inst_ind == SCTP_INST_NEUTRAL)
+ return (1);
+ else
+ return ((int)net->cc_mod.rtcc.ret_from_eq);
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static int
+cc_bw_decrease(struct sctp_tcb *stcb, struct sctp_nets *net, uint64_t nbw, uint64_t rtt_offset,
+ uint64_t vtag, uint8_t inst_ind)
+#else
+static int
+cc_bw_decrease(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, uint64_t nbw, uint64_t rtt_offset,
+ uint8_t inst_ind)
+#endif
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint64_t oth __dtrace, probepoint __dtrace;
+#endif
+
+ /* Bandwidth decreased.*/
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ probepoint = (((uint64_t)net->cwnd) << 32);
+#endif
+ if (net->rtt > net->cc_mod.rtcc.lbw_rtt+rtt_offset) {
+ /* rtt increased */
+ /* Did we add more */
+ if ((net->cwnd > net->cc_mod.rtcc.cwnd_at_bw_set) &&
+ (inst_ind != SCTP_INST_LOOSING)) {
+ /* We caused it maybe.. back off? */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* PROBE POINT 1 */
+ probepoint |= ((1 << 16) | 1);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ if (net->cc_mod.rtcc.ret_from_eq) {
+ /* Switch over to CA if we are less aggressive */
+ net->ssthresh = net->cwnd-1;
+ net->partial_bytes_acked = 0;
+ }
+ return (1);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Probe point 2 */
+ probepoint |= ((2 << 16) | 0);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ /* Someone else - fight for more? */
+ if (net->cc_mod.rtcc.steady_step) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ oth = net->cc_mod.rtcc.vol_reduce;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.step_cnt;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.last_step_state;
+ SDT_PROBE5(sctp, cwnd, net, rttstep,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ oth,
+ probepoint);
+#endif
+ /* Did we voluntarily give up some? if so take
+ * one back please
+ */
+ if ((net->cc_mod.rtcc.vol_reduce) &&
+ (inst_ind != SCTP_INST_GAINING)) {
+ net->cwnd += net->mtu;
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ net->cc_mod.rtcc.vol_reduce--;
+ }
+ net->cc_mod.rtcc.last_step_state = 2;
+ net->cc_mod.rtcc.step_cnt = 0;
+ }
+ goto out_decision;
+ } else if (net->rtt < net->cc_mod.rtcc.lbw_rtt-rtt_offset) {
+ /* bw & rtt decreased */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Probe point 3 */
+ probepoint |= ((3 << 16) | 0);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ if (net->cc_mod.rtcc.steady_step) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ oth = net->cc_mod.rtcc.vol_reduce;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.step_cnt;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.last_step_state;
+ SDT_PROBE5(sctp, cwnd, net, rttstep,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ oth,
+ probepoint);
+#endif
+ if ((net->cc_mod.rtcc.vol_reduce) &&
+ (inst_ind != SCTP_INST_GAINING)) {
+ net->cwnd += net->mtu;
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ net->cc_mod.rtcc.vol_reduce--;
+ }
+ net->cc_mod.rtcc.last_step_state = 3;
+ net->cc_mod.rtcc.step_cnt = 0;
+ }
+ goto out_decision;
+ }
+ /* The bw decreased but rtt stayed the same */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Probe point 4 */
+ probepoint |= ((4 << 16) | 0);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ if (net->cc_mod.rtcc.steady_step) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ oth = net->cc_mod.rtcc.vol_reduce;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.step_cnt;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.last_step_state;
+ SDT_PROBE5(sctp, cwnd, net, rttstep,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ oth,
+ probepoint);
+#endif
+ if ((net->cc_mod.rtcc.vol_reduce) &&
+ (inst_ind != SCTP_INST_GAINING)) {
+ net->cwnd += net->mtu;
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ net->cc_mod.rtcc.vol_reduce--;
+ }
+ net->cc_mod.rtcc.last_step_state = 4;
+ net->cc_mod.rtcc.step_cnt = 0;
+ }
+out_decision:
+ net->cc_mod.rtcc.lbw = nbw;
+ net->cc_mod.rtcc.lbw_rtt = net->rtt;
+ net->cc_mod.rtcc.cwnd_at_bw_set = net->cwnd;
+ if (inst_ind == SCTP_INST_GAINING) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static int
+cc_bw_increase(struct sctp_tcb *stcb, struct sctp_nets *net, uint64_t nbw, uint64_t vtag)
+#else
+static int
+cc_bw_increase(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, uint64_t nbw)
+#endif
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint64_t oth __dtrace, probepoint __dtrace;
+
+#endif
+ /* BW increased, so update and
+ * return 0, since all actions in
+ * our table say to do the normal CC
+ * update. Note that we pay no attention to
+ * the inst_ind since our overall sum is increasing.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* PROBE POINT 0 */
+ probepoint = (((uint64_t)net->cwnd) << 32);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ if (net->cc_mod.rtcc.steady_step) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ oth = net->cc_mod.rtcc.vol_reduce;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.step_cnt;
+ oth <<= 16;
+ oth |= net->cc_mod.rtcc.last_step_state;
+ SDT_PROBE5(sctp, cwnd, net, rttstep,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | nbw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ oth,
+ probepoint);
+#endif
+ net->cc_mod.rtcc.last_step_state = 0;
+ net->cc_mod.rtcc.step_cnt = 0;
+ net->cc_mod.rtcc.vol_reduce = 0;
+ }
+ net->cc_mod.rtcc.lbw = nbw;
+ net->cc_mod.rtcc.lbw_rtt = net->rtt;
+ net->cc_mod.rtcc.cwnd_at_bw_set = net->cwnd;
+ return (0);
+}
+
+/* RTCC Algorithm to limit growth of cwnd, return
+ * true if you want to NOT allow cwnd growth
+ */
+static int
+cc_bw_limit(struct sctp_tcb *stcb, struct sctp_nets *net, uint64_t nbw)
+{
+ uint64_t bw_offset, rtt_offset;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint64_t probepoint __dtrace, rtt, vtag;
+#endif
+ uint64_t bytes_for_this_rtt, inst_bw;
+ uint64_t div, inst_off;
+ int bw_shift;
+ uint8_t inst_ind;
+ int ret;
+ /*-
+ * Here we need to see if we want
+ * to limit cwnd growth due to increase
+ * in overall rtt but no increase in bw.
+ * We use the following table to figure
+ * out what we should do. When we return
+ * 0, cc update goes on as planned. If we
+ * return 1, then no cc update happens and cwnd
+ * stays where it is at.
+ * ----------------------------------
+ * BW | RTT | Action
+ * *********************************
+ * INC | INC | return 0
+ * ----------------------------------
+ * INC | SAME | return 0
+ * ----------------------------------
+ * INC | DECR | return 0
+ * ----------------------------------
+ * SAME | INC | return 1
+ * ----------------------------------
+ * SAME | SAME | return 1
+ * ----------------------------------
+ * SAME | DECR | return 0
+ * ----------------------------------
+ * DECR | INC | return 0 or 1 based on if we caused.
+ * ----------------------------------
+ * DECR | SAME | return 0
+ * ----------------------------------
+ * DECR | DECR | return 0
+ * ----------------------------------
+ *
+ * We are a bit fuzz on what an increase or
+ * decrease is. For BW it is the same if
+ * it did not change within 1/64th. For
+ * RTT it stayed the same if it did not
+ * change within 1/32nd
+ */
+ bw_shift = SCTP_BASE_SYSCTL(sctp_rttvar_bw);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ rtt = stcb->asoc.my_vtag;
+ vtag = (rtt << 32) | (((uint32_t)(stcb->sctp_ep->sctp_lport)) << 16) | (stcb->rport);
+ probepoint = (((uint64_t)net->cwnd) << 32);
+ rtt = net->rtt;
+#endif
+ if (net->cc_mod.rtcc.rtt_set_this_sack) {
+ net->cc_mod.rtcc.rtt_set_this_sack = 0;
+ bytes_for_this_rtt = net->cc_mod.rtcc.bw_bytes - net->cc_mod.rtcc.bw_bytes_at_last_rttc;
+ net->cc_mod.rtcc.bw_bytes_at_last_rttc = net->cc_mod.rtcc.bw_bytes;
+ if (net->rtt) {
+ div = net->rtt / 1000;
+ if (div) {
+ inst_bw = bytes_for_this_rtt / div;
+ inst_off = inst_bw >> bw_shift;
+ if (inst_bw > nbw)
+ inst_ind = SCTP_INST_GAINING;
+ else if ((inst_bw+inst_off) < nbw)
+ inst_ind = SCTP_INST_LOOSING;
+ else
+ inst_ind = SCTP_INST_NEUTRAL;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ probepoint |= ((0xb << 16) | inst_ind);
+#endif
+ } else {
+ inst_ind = net->cc_mod.rtcc.last_inst_ind;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ inst_bw = bytes_for_this_rtt / (uint64_t)(net->rtt);
+ /* Can't determine do not change */
+ probepoint |= ((0xc << 16) | inst_ind);
+#endif
+ }
+ } else {
+ inst_ind = net->cc_mod.rtcc.last_inst_ind;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ inst_bw = bytes_for_this_rtt;
+ /* Can't determine do not change */
+ probepoint |= ((0xd << 16) | inst_ind);
+#endif
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((nbw << 32) | inst_bw),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ } else {
+ /* No rtt measurement, use last one */
+ inst_ind = net->cc_mod.rtcc.last_inst_ind;
+ }
+ bw_offset = net->cc_mod.rtcc.lbw >> bw_shift;
+ if (nbw > net->cc_mod.rtcc.lbw + bw_offset) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ret = cc_bw_increase(stcb, net, nbw, vtag);
+#else
+ ret = cc_bw_increase(stcb, net, nbw);
+#endif
+ goto out;
+ }
+ rtt_offset = net->cc_mod.rtcc.lbw_rtt >> SCTP_BASE_SYSCTL(sctp_rttvar_rtt);
+ if (nbw < net->cc_mod.rtcc.lbw - bw_offset) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ret = cc_bw_decrease(stcb, net, nbw, rtt_offset, vtag, inst_ind);
+#else
+ ret = cc_bw_decrease(stcb, net, nbw, rtt_offset, inst_ind);
+#endif
+ goto out;
+ }
+ /* If we reach here then
+ * we are in a situation where
+ * the bw stayed the same.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ret = cc_bw_same(stcb, net, nbw, rtt_offset, vtag, inst_ind);
+#else
+ ret = cc_bw_same(stcb, net, nbw, rtt_offset, inst_ind);
+#endif
+out:
+ net->cc_mod.rtcc.last_inst_ind = inst_ind;
+ return (ret);
+}
+
+static void
+sctp_cwnd_update_after_sack_common(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int accum_moved, int reneged_all SCTP_UNUSED, int will_exit, int use_rtcc)
+{
+ struct sctp_nets *net;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ int old_cwnd __dtrace;
+#endif
+ uint32_t t_ssthresh, incr;
+ uint64_t t_ucwnd_sbw;
+ uint64_t t_path_mptcp;
+ uint64_t mptcp_like_alpha;
+ uint32_t srtt;
+ uint64_t max_path;
+
+ /* MT FIXME: Don't compute this over and over again */
+ t_ssthresh = 0;
+ t_ucwnd_sbw = 0;
+ t_path_mptcp = 0;
+ mptcp_like_alpha = 1;
+ if ((stcb->asoc.sctp_cmt_on_off == SCTP_CMT_RPV1) ||
+ (stcb->asoc.sctp_cmt_on_off == SCTP_CMT_RPV2) ||
+ (stcb->asoc.sctp_cmt_on_off == SCTP_CMT_MPTCP)) {
+ max_path = 0;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ t_ssthresh += net->ssthresh;
+ /* lastsa>>3; we don't need to devide ...*/
+ srtt = net->lastsa;
+ if (srtt > 0) {
+ uint64_t tmp;
+
+ t_ucwnd_sbw += (uint64_t)net->cwnd / (uint64_t)srtt;
+ t_path_mptcp += (((uint64_t)net->cwnd) << SHIFT_MPTCP_MULTI_Z) /
+ (((uint64_t)net->mtu) * (uint64_t)srtt);
+ tmp = (((uint64_t)net->cwnd) << SHIFT_MPTCP_MULTI_N) /
+ ((uint64_t)net->mtu * (uint64_t)(srtt * srtt));
+ if (tmp > max_path) {
+ max_path = tmp;
+ }
+ }
+ }
+ if (t_path_mptcp > 0) {
+ mptcp_like_alpha = max_path / (t_path_mptcp * t_path_mptcp);
+ } else {
+ mptcp_like_alpha = 1;
+ }
+ }
+ if (t_ssthresh == 0) {
+ t_ssthresh = 1;
+ }
+ if (t_ucwnd_sbw == 0) {
+ t_ucwnd_sbw = 1;
+ }
+ /******************************/
+ /* update cwnd and Early FR */
+ /******************************/
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+#ifdef JANA_CMT_FAST_RECOVERY
+ /*
+ * CMT fast recovery code. Need to debug.
+ */
+ if (net->fast_retran_loss_recovery && net->new_pseudo_cumack) {
+ if (SCTP_TSN_GE(asoc->last_acked_seq, net->fast_recovery_tsn) ||
+ SCTP_TSN_GE(net->pseudo_cumack,net->fast_recovery_tsn)) {
+ net->will_exit_fast_recovery = 1;
+ }
+ }
+#endif
+ /* if nothing was acked on this destination skip it */
+ if (net->net_ack == 0) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, 0, SCTP_CWND_LOG_FROM_SACK);
+ }
+ continue;
+ }
+#ifdef JANA_CMT_FAST_RECOVERY
+ /* CMT fast recovery code
+ */
+ /*
+ if (sctp_cmt_on_off > 0 && net->fast_retran_loss_recovery && net->will_exit_fast_recovery == 0) {
+ @@@ Do something
+ }
+ else if (sctp_cmt_on_off == 0 && asoc->fast_retran_loss_recovery && will_exit == 0) {
+ */
+#endif
+
+ if (asoc->fast_retran_loss_recovery &&
+ (will_exit == 0) &&
+ (asoc->sctp_cmt_on_off == 0)) {
+ /*
+ * If we are in loss recovery we skip any cwnd
+ * update
+ */
+ return;
+ }
+ /*
+ * Did any measurements go on for this network?
+ */
+ if (use_rtcc && (net->cc_mod.rtcc.tls_needs_set > 0)) {
+ uint64_t nbw;
+ /*
+ * At this point our bw_bytes has been updated
+ * by incoming sack information.
+ *
+ * But our bw may not yet be set.
+ *
+ */
+ if ((net->cc_mod.rtcc.new_tot_time/1000) > 0) {
+ nbw = net->cc_mod.rtcc.bw_bytes/(net->cc_mod.rtcc.new_tot_time/1000);
+ } else {
+ nbw = net->cc_mod.rtcc.bw_bytes;
+ }
+ if (net->cc_mod.rtcc.lbw) {
+ if (cc_bw_limit(stcb, net, nbw)) {
+ /* Hold here, no update */
+ continue;
+ }
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint64_t vtag __dtrace, probepoint __dtrace;
+
+ probepoint = (((uint64_t)net->cwnd) << 32);
+ probepoint |= ((0xa << 16) | 0);
+ vtag = (net->rtt << 32) |
+ (((uint32_t)(stcb->sctp_ep->sctp_lport)) << 16) |
+ (stcb->rport);
+
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ nbw,
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ net->cc_mod.rtcc.lbw = nbw;
+ net->cc_mod.rtcc.lbw_rtt = net->rtt;
+ if (net->cc_mod.rtcc.rtt_set_this_sack) {
+ net->cc_mod.rtcc.rtt_set_this_sack = 0;
+ net->cc_mod.rtcc.bw_bytes_at_last_rttc = net->cc_mod.rtcc.bw_bytes;
+ }
+ }
+ }
+ /*
+ * CMT: CUC algorithm. Update cwnd if pseudo-cumack has
+ * moved.
+ */
+ if (accum_moved ||
+ ((asoc->sctp_cmt_on_off > 0) && net->new_pseudo_cumack)) {
+ /* If the cumulative ack moved we can proceed */
+ if (net->cwnd <= net->ssthresh) {
+ /* We are in slow start */
+ if (net->flight_size + net->net_ack >= net->cwnd) {
+ uint32_t limit;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ old_cwnd = net->cwnd;
+#endif
+ switch (asoc->sctp_cmt_on_off) {
+ case SCTP_CMT_RPV1:
+ limit = (uint32_t)(((uint64_t)net->mtu *
+ (uint64_t)SCTP_BASE_SYSCTL(sctp_L2_abc_variable) *
+ (uint64_t)net->ssthresh) /
+ (uint64_t)t_ssthresh);
+ incr = (uint32_t)(((uint64_t)net->net_ack *
+ (uint64_t)net->ssthresh) /
+ (uint64_t)t_ssthresh);
+ if (incr > limit) {
+ incr = limit;
+ }
+ if (incr == 0) {
+ incr = 1;
+ }
+ break;
+ case SCTP_CMT_RPV2:
+ /* lastsa>>3; we don't need to divide ...*/
+ srtt = net->lastsa;
+ if (srtt == 0) {
+ srtt = 1;
+ }
+ limit = (uint32_t)(((uint64_t)net->mtu *
+ (uint64_t)SCTP_BASE_SYSCTL(sctp_L2_abc_variable) *
+ (uint64_t)net->cwnd) /
+ ((uint64_t)srtt * t_ucwnd_sbw));
+ /* INCREASE FACTOR */
+ incr = (uint32_t)(((uint64_t)net->net_ack *
+ (uint64_t)net->cwnd) /
+ ((uint64_t)srtt * t_ucwnd_sbw));
+ /* INCREASE FACTOR */
+ if (incr > limit) {
+ incr = limit;
+ }
+ if (incr == 0) {
+ incr = 1;
+ }
+ break;
+ case SCTP_CMT_MPTCP:
+ limit = (uint32_t)(((uint64_t)net->mtu *
+ mptcp_like_alpha *
+ (uint64_t)SCTP_BASE_SYSCTL(sctp_L2_abc_variable)) >>
+ SHIFT_MPTCP_MULTI);
+ incr = (uint32_t)(((uint64_t)net->net_ack *
+ mptcp_like_alpha) >>
+ SHIFT_MPTCP_MULTI);
+ if (incr > limit) {
+ incr = limit;
+ }
+ if (incr > net->net_ack) {
+ incr = net->net_ack;
+ }
+ if (incr > net->mtu) {
+ incr = net->mtu;
+ }
+ break;
+ default:
+ incr = net->net_ack;
+ if (incr > net->mtu * SCTP_BASE_SYSCTL(sctp_L2_abc_variable)) {
+ incr = net->mtu * SCTP_BASE_SYSCTL(sctp_L2_abc_variable);
+ }
+ break;
+ }
+ net->cwnd += incr;
+ sctp_enforce_cwnd_limit(asoc, net);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, incr,
+ SCTP_CWND_LOG_FROM_SS);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, ack,
+ stcb->asoc.my_vtag,
+ ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)),
+ net,
+ old_cwnd, net->cwnd);
+#endif
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->net_ack,
+ SCTP_CWND_LOG_NOADV_SS);
+ }
+ }
+ } else {
+ /* We are in congestion avoidance */
+ /*
+ * Add to pba
+ */
+ net->partial_bytes_acked += net->net_ack;
+
+ if ((net->flight_size + net->net_ack >= net->cwnd) &&
+ (net->partial_bytes_acked >= net->cwnd)) {
+ net->partial_bytes_acked -= net->cwnd;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ old_cwnd = net->cwnd;
+#endif
+ switch (asoc->sctp_cmt_on_off) {
+ case SCTP_CMT_RPV1:
+ incr = (uint32_t)(((uint64_t)net->mtu *
+ (uint64_t)net->ssthresh) /
+ (uint64_t)t_ssthresh);
+ if (incr == 0) {
+ incr = 1;
+ }
+ break;
+ case SCTP_CMT_RPV2:
+ /* lastsa>>3; we don't need to divide ... */
+ srtt = net->lastsa;
+ if (srtt == 0) {
+ srtt = 1;
+ }
+ incr = (uint32_t)((uint64_t)net->mtu *
+ (uint64_t)net->cwnd /
+ ((uint64_t)srtt *
+ t_ucwnd_sbw));
+ /* INCREASE FACTOR */
+ if (incr == 0) {
+ incr = 1;
+ }
+ break;
+ case SCTP_CMT_MPTCP:
+ incr = (uint32_t)((mptcp_like_alpha *
+ (uint64_t) net->cwnd) >>
+ SHIFT_MPTCP_MULTI);
+ if (incr > net->mtu) {
+ incr = net->mtu;
+ }
+ break;
+ default:
+ incr = net->mtu;
+ break;
+ }
+ net->cwnd += incr;
+ sctp_enforce_cwnd_limit(asoc, net);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, ack,
+ stcb->asoc.my_vtag,
+ ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)),
+ net,
+ old_cwnd, net->cwnd);
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->mtu,
+ SCTP_CWND_LOG_FROM_CA);
+ }
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->net_ack,
+ SCTP_CWND_LOG_NOADV_CA);
+ }
+ }
+ }
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->mtu,
+ SCTP_CWND_LOG_NO_CUMACK);
+ }
+ }
+ }
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static void
+sctp_cwnd_update_exit_pf_common(struct sctp_tcb *stcb, struct sctp_nets *net)
+#else
+static void
+sctp_cwnd_update_exit_pf_common(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net)
+#endif
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ int old_cwnd __dtrace;
+
+ old_cwnd = net->cwnd;
+#endif
+ net->cwnd = net->mtu;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, ack,
+ stcb->asoc.my_vtag, ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)), net,
+ old_cwnd, net->cwnd);
+#endif
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Destination %p moved from PF to reachable with cwnd %d.\n",
+ (void *)net, net->cwnd);
+}
+
+static void
+sctp_cwnd_update_after_timeout(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ int old_cwnd = net->cwnd;
+ uint32_t t_ssthresh, t_cwnd;
+ uint64_t t_ucwnd_sbw;
+
+ /* MT FIXME: Don't compute this over and over again */
+ t_ssthresh = 0;
+ t_cwnd = 0;
+ if ((stcb->asoc.sctp_cmt_on_off == SCTP_CMT_RPV1) ||
+ (stcb->asoc.sctp_cmt_on_off == SCTP_CMT_RPV2)) {
+ struct sctp_nets *lnet;
+ uint32_t srtt;
+
+ t_ucwnd_sbw = 0;
+ TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) {
+ t_ssthresh += lnet->ssthresh;
+ t_cwnd += lnet->cwnd;
+ srtt = lnet->lastsa;
+ /* lastsa>>3; we don't need to divide ... */
+ if (srtt > 0) {
+ t_ucwnd_sbw += (uint64_t)lnet->cwnd / (uint64_t)srtt;
+ }
+ }
+ if (t_ssthresh < 1) {
+ t_ssthresh = 1;
+ }
+ if (t_ucwnd_sbw < 1) {
+ t_ucwnd_sbw = 1;
+ }
+ if (stcb->asoc.sctp_cmt_on_off == SCTP_CMT_RPV1) {
+ net->ssthresh = (uint32_t)(((uint64_t)4 *
+ (uint64_t)net->mtu *
+ (uint64_t)net->ssthresh) /
+ (uint64_t)t_ssthresh);
+ } else {
+ uint64_t cc_delta;
+
+ srtt = net->lastsa;
+ /* lastsa>>3; we don't need to divide ... */
+ if (srtt == 0) {
+ srtt = 1;
+ }
+ cc_delta = t_ucwnd_sbw * (uint64_t)srtt / 2;
+ if (cc_delta < t_cwnd) {
+ net->ssthresh = (uint32_t)((uint64_t)t_cwnd - cc_delta);
+ } else {
+ net->ssthresh = net->mtu;
+ }
+ }
+ if ((net->cwnd > t_cwnd / 2) &&
+ (net->ssthresh < net->cwnd - t_cwnd / 2)) {
+ net->ssthresh = net->cwnd - t_cwnd / 2;
+ }
+ if (net->ssthresh < net->mtu) {
+ net->ssthresh = net->mtu;
+ }
+ } else {
+ net->ssthresh = max(net->cwnd / 2, 4 * net->mtu);
+ }
+ net->cwnd = net->mtu;
+ net->partial_bytes_acked = 0;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, to,
+ stcb->asoc.my_vtag,
+ ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)),
+ net,
+ old_cwnd, net->cwnd);
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->cwnd - old_cwnd, SCTP_CWND_LOG_FROM_RTX);
+ }
+}
+
+static void
+sctp_cwnd_update_after_ecn_echo_common(struct sctp_tcb *stcb, struct sctp_nets *net,
+ int in_window, int num_pkt_lost, int use_rtcc)
+{
+ int old_cwnd = net->cwnd;
+ if ((use_rtcc) && (net->lan_type == SCTP_LAN_LOCAL) && (net->cc_mod.rtcc.use_dccc_ecn)) {
+ /* Data center Congestion Control */
+ if (in_window == 0) {
+ /* Go to CA with the cwnd at the point we sent
+ * the TSN that was marked with a CE.
+ */
+ if (net->ecn_prev_cwnd < net->cwnd) {
+ /* Restore to prev cwnd */
+ net->cwnd = net->ecn_prev_cwnd - (net->mtu * num_pkt_lost);
+ } else {
+ /* Just cut in 1/2 */
+ net->cwnd /= 2;
+ }
+ /* Drop to CA */
+ net->ssthresh = net->cwnd - (num_pkt_lost * net->mtu);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_SAT);
+ }
+ } else {
+ /* Further tuning down required over the drastic original cut */
+ net->ssthresh -= (net->mtu * num_pkt_lost);
+ net->cwnd -= (net->mtu * num_pkt_lost);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_SAT);
+ }
+ }
+ SCTP_STAT_INCR(sctps_ecnereducedcwnd);
+ } else {
+ if (in_window == 0) {
+ SCTP_STAT_INCR(sctps_ecnereducedcwnd);
+ net->ssthresh = net->cwnd / 2;
+ if (net->ssthresh < net->mtu) {
+ net->ssthresh = net->mtu;
+ /* here back off the timer as well, to slow us down */
+ net->RTO <<= 1;
+ }
+ net->cwnd = net->ssthresh;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, ecn,
+ stcb->asoc.my_vtag,
+ ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)),
+ net,
+ old_cwnd, net->cwnd);
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_SAT);
+ }
+ }
+ }
+
+}
+
+static void
+sctp_cwnd_update_after_packet_dropped(struct sctp_tcb *stcb,
+ struct sctp_nets *net, struct sctp_pktdrop_chunk *cp,
+ uint32_t *bottle_bw, uint32_t *on_queue)
+{
+ uint32_t bw_avail;
+ unsigned int incr;
+ int old_cwnd = net->cwnd;
+
+ /* get bottle neck bw */
+ *bottle_bw = ntohl(cp->bottle_bw);
+ /* and whats on queue */
+ *on_queue = ntohl(cp->current_onq);
+ /*
+ * adjust the on-queue if our flight is more it could be
+ * that the router has not yet gotten data "in-flight" to it
+ */
+ if (*on_queue < net->flight_size) {
+ *on_queue = net->flight_size;
+ }
+ /* rtt is measured in micro seconds, bottle_bw in bytes per second */
+ bw_avail = (uint32_t)(((uint64_t)(*bottle_bw) * net->rtt) / (uint64_t)1000000);
+ if (bw_avail > *bottle_bw) {
+ /*
+ * Cap the growth to no more than the bottle neck.
+ * This can happen as RTT slides up due to queues.
+ * It also means if you have more than a 1 second
+ * RTT with a empty queue you will be limited to the
+ * bottle_bw per second no matter if other points
+ * have 1/2 the RTT and you could get more out...
+ */
+ bw_avail = *bottle_bw;
+ }
+ if (*on_queue > bw_avail) {
+ /*
+ * No room for anything else don't allow anything
+ * else to be "added to the fire".
+ */
+ int seg_inflight, seg_onqueue, my_portion;
+
+ net->partial_bytes_acked = 0;
+ /* how much are we over queue size? */
+ incr = *on_queue - bw_avail;
+ if (stcb->asoc.seen_a_sack_this_pkt) {
+ /*
+ * undo any cwnd adjustment that the sack
+ * might have made
+ */
+ net->cwnd = net->prev_cwnd;
+ }
+ /* Now how much of that is mine? */
+ seg_inflight = net->flight_size / net->mtu;
+ seg_onqueue = *on_queue / net->mtu;
+ my_portion = (incr * seg_inflight) / seg_onqueue;
+
+ /* Have I made an adjustment already */
+ if (net->cwnd > net->flight_size) {
+ /*
+ * for this flight I made an adjustment we
+ * need to decrease the portion by a share
+ * our previous adjustment.
+ */
+ int diff_adj;
+
+ diff_adj = net->cwnd - net->flight_size;
+ if (diff_adj > my_portion)
+ my_portion = 0;
+ else
+ my_portion -= diff_adj;
+ }
+ /*
+ * back down to the previous cwnd (assume we have
+ * had a sack before this packet). minus what ever
+ * portion of the overage is my fault.
+ */
+ net->cwnd -= my_portion;
+
+ /* we will NOT back down more than 1 MTU */
+ if (net->cwnd <= net->mtu) {
+ net->cwnd = net->mtu;
+ }
+ /* force into CA */
+ net->ssthresh = net->cwnd - 1;
+ } else {
+ /*
+ * Take 1/4 of the space left or max burst up ..
+ * whichever is less.
+ */
+ incr = (bw_avail - *on_queue) >> 2;
+ if ((stcb->asoc.max_burst > 0) &&
+ (stcb->asoc.max_burst * net->mtu < incr)) {
+ incr = stcb->asoc.max_burst * net->mtu;
+ }
+ net->cwnd += incr;
+ }
+ if (net->cwnd > bw_avail) {
+ /* We can't exceed the pipe size */
+ net->cwnd = bw_avail;
+ }
+ if (net->cwnd < net->mtu) {
+ /* We always have 1 MTU */
+ net->cwnd = net->mtu;
+ }
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ if (net->cwnd - old_cwnd != 0) {
+ /* log only changes */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, pd,
+ stcb->asoc.my_vtag,
+ ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)),
+ net,
+ old_cwnd, net->cwnd);
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd),
+ SCTP_CWND_LOG_FROM_SAT);
+ }
+ }
+}
+
+static void
+sctp_cwnd_update_after_output(struct sctp_tcb *stcb,
+ struct sctp_nets *net, int burst_limit)
+{
+ int old_cwnd = net->cwnd;
+
+ if (net->ssthresh < net->cwnd)
+ net->ssthresh = net->cwnd;
+ if (burst_limit) {
+ net->cwnd = (net->flight_size + (burst_limit * net->mtu));
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SDT_PROBE5(sctp, cwnd, net, bl,
+ stcb->asoc.my_vtag,
+ ((stcb->sctp_ep->sctp_lport << 16) | (stcb->rport)),
+ net,
+ old_cwnd, net->cwnd);
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_BRST);
+ }
+ }
+}
+
+static void
+sctp_cwnd_update_after_sack(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int accum_moved, int reneged_all, int will_exit)
+{
+ /* Passing a zero argument in last disables the rtcc algorithm */
+ sctp_cwnd_update_after_sack_common(stcb, asoc, accum_moved, reneged_all, will_exit, 0);
+}
+
+static void
+sctp_cwnd_update_after_ecn_echo(struct sctp_tcb *stcb, struct sctp_nets *net,
+ int in_window, int num_pkt_lost)
+{
+ /* Passing a zero argument in last disables the rtcc algorithm */
+ sctp_cwnd_update_after_ecn_echo_common(stcb, net, in_window, num_pkt_lost, 0);
+}
+
+/* Here starts the RTCCVAR type CC invented by RRS which
+ * is a slight mod to RFC2581. We reuse a common routine or
+ * two since these algorithms are so close and need to
+ * remain the same.
+ */
+static void
+sctp_cwnd_update_rtcc_after_ecn_echo(struct sctp_tcb *stcb, struct sctp_nets *net,
+ int in_window, int num_pkt_lost)
+{
+ sctp_cwnd_update_after_ecn_echo_common(stcb, net, in_window, num_pkt_lost, 1);
+}
+
+static void sctp_cwnd_update_rtcc_tsn_acknowledged(struct sctp_nets *net,
+ struct sctp_tmit_chunk *tp1)
+{
+ net->cc_mod.rtcc.bw_bytes += tp1->send_size;
+}
+
+static void
+sctp_cwnd_prepare_rtcc_net_for_sack(struct sctp_tcb *stcb SCTP_UNUSED,
+ struct sctp_nets *net)
+{
+ if (net->cc_mod.rtcc.tls_needs_set > 0) {
+ /* We had a bw measurement going on */
+ struct timeval ltls;
+ SCTP_GETPTIME_TIMEVAL(&ltls);
+ timevalsub(&ltls, &net->cc_mod.rtcc.tls);
+ net->cc_mod.rtcc.new_tot_time = (ltls.tv_sec * 1000000) + ltls.tv_usec;
+ }
+}
+
+static void
+sctp_cwnd_new_rtcc_transmission_begins(struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint64_t vtag __dtrace, probepoint __dtrace;
+
+#endif
+ if (net->cc_mod.rtcc.lbw) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Clear the old bw.. we went to 0 in-flight */
+ vtag = (net->rtt << 32) | (((uint32_t)(stcb->sctp_ep->sctp_lport)) << 16) |
+ (stcb->rport);
+ probepoint = (((uint64_t)net->cwnd) << 32);
+ /* Probe point 8 */
+ probepoint |= ((8 << 16) | 0);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ ((net->cc_mod.rtcc.lbw << 32) | 0),
+ ((net->cc_mod.rtcc.lbw_rtt << 32) | net->rtt),
+ net->flight_size,
+ probepoint);
+#endif
+ net->cc_mod.rtcc.lbw_rtt = 0;
+ net->cc_mod.rtcc.cwnd_at_bw_set = 0;
+ net->cc_mod.rtcc.lbw = 0;
+ net->cc_mod.rtcc.bw_bytes_at_last_rttc = 0;
+ net->cc_mod.rtcc.vol_reduce = 0;
+ net->cc_mod.rtcc.bw_tot_time = 0;
+ net->cc_mod.rtcc.bw_bytes = 0;
+ net->cc_mod.rtcc.tls_needs_set = 0;
+ if (net->cc_mod.rtcc.steady_step) {
+ net->cc_mod.rtcc.vol_reduce = 0;
+ net->cc_mod.rtcc.step_cnt = 0;
+ net->cc_mod.rtcc.last_step_state = 0;
+ }
+ if (net->cc_mod.rtcc.ret_from_eq) {
+ /* less aggressive one - reset cwnd too */
+ uint32_t cwnd_in_mtu, cwnd;
+
+ cwnd_in_mtu = SCTP_BASE_SYSCTL(sctp_initial_cwnd);
+ if (cwnd_in_mtu == 0) {
+ /* Using 0 means that the value of RFC 4960 is used. */
+ cwnd = min((net->mtu * 4), max((2 * net->mtu), SCTP_INITIAL_CWND));
+ } else {
+ /*
+ * We take the minimum of the burst limit and the
+ * initial congestion window.
+ */
+ if ((stcb->asoc.max_burst > 0) && (cwnd_in_mtu > stcb->asoc.max_burst))
+ cwnd_in_mtu = stcb->asoc.max_burst;
+ cwnd = (net->mtu - sizeof(struct sctphdr)) * cwnd_in_mtu;
+ }
+ if (net->cwnd > cwnd) {
+ /* Only set if we are not a timeout (i.e. down to 1 mtu) */
+ net->cwnd = cwnd;
+ }
+ }
+ }
+}
+
+static void
+sctp_set_rtcc_initial_cc_param(struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint64_t vtag __dtrace, probepoint __dtrace;
+
+#endif
+ sctp_set_initial_cc_param(stcb, net);
+ stcb->asoc.use_precise_time = 1;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ probepoint = (((uint64_t)net->cwnd) << 32);
+ probepoint |= ((9 << 16) | 0);
+ vtag = (net->rtt << 32) |
+ (((uint32_t)(stcb->sctp_ep->sctp_lport)) << 16) |
+ (stcb->rport);
+ SDT_PROBE5(sctp, cwnd, net, rttvar,
+ vtag,
+ 0,
+ 0,
+ 0,
+ probepoint);
+#endif
+ net->cc_mod.rtcc.lbw_rtt = 0;
+ net->cc_mod.rtcc.cwnd_at_bw_set = 0;
+ net->cc_mod.rtcc.vol_reduce = 0;
+ net->cc_mod.rtcc.lbw = 0;
+ net->cc_mod.rtcc.vol_reduce = 0;
+ net->cc_mod.rtcc.bw_bytes_at_last_rttc = 0;
+ net->cc_mod.rtcc.bw_tot_time = 0;
+ net->cc_mod.rtcc.bw_bytes = 0;
+ net->cc_mod.rtcc.tls_needs_set = 0;
+ net->cc_mod.rtcc.ret_from_eq = SCTP_BASE_SYSCTL(sctp_rttvar_eqret);
+ net->cc_mod.rtcc.steady_step = SCTP_BASE_SYSCTL(sctp_steady_step);
+ net->cc_mod.rtcc.use_dccc_ecn = SCTP_BASE_SYSCTL(sctp_use_dccc_ecn);
+ net->cc_mod.rtcc.step_cnt = 0;
+ net->cc_mod.rtcc.last_step_state = 0;
+}
+
+static int
+sctp_cwnd_rtcc_socket_option(struct sctp_tcb *stcb, int setorget,
+ struct sctp_cc_option *cc_opt)
+{
+ struct sctp_nets *net;
+
+ if (setorget == 1) {
+ /* a set */
+ if (cc_opt->option == SCTP_CC_OPT_RTCC_SETMODE) {
+ if ((cc_opt->aid_value.assoc_value != 0) &&
+ (cc_opt->aid_value.assoc_value != 1)) {
+ return (EINVAL);
+ }
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ net->cc_mod.rtcc.ret_from_eq = cc_opt->aid_value.assoc_value;
+ }
+ } else if (cc_opt->option == SCTP_CC_OPT_USE_DCCC_ECN) {
+ if ((cc_opt->aid_value.assoc_value != 0) &&
+ (cc_opt->aid_value.assoc_value != 1)) {
+ return (EINVAL);
+ }
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ net->cc_mod.rtcc.use_dccc_ecn = cc_opt->aid_value.assoc_value;
+ }
+ } else if (cc_opt->option == SCTP_CC_OPT_STEADY_STEP) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ net->cc_mod.rtcc.steady_step = cc_opt->aid_value.assoc_value;
+ }
+ } else {
+ return (EINVAL);
+ }
+ } else {
+ /* a get */
+ if (cc_opt->option == SCTP_CC_OPT_RTCC_SETMODE) {
+ net = TAILQ_FIRST(&stcb->asoc.nets);
+ if (net == NULL) {
+ return (EFAULT);
+ }
+ cc_opt->aid_value.assoc_value = net->cc_mod.rtcc.ret_from_eq;
+ } else if (cc_opt->option == SCTP_CC_OPT_USE_DCCC_ECN) {
+ net = TAILQ_FIRST(&stcb->asoc.nets);
+ if (net == NULL) {
+ return (EFAULT);
+ }
+ cc_opt->aid_value.assoc_value = net->cc_mod.rtcc.use_dccc_ecn;
+ } else if (cc_opt->option == SCTP_CC_OPT_STEADY_STEP) {
+ net = TAILQ_FIRST(&stcb->asoc.nets);
+ if (net == NULL) {
+ return (EFAULT);
+ }
+ cc_opt->aid_value.assoc_value = net->cc_mod.rtcc.steady_step;
+ } else {
+ return (EINVAL);
+ }
+ }
+ return (0);
+}
+
+static void
+sctp_cwnd_update_rtcc_packet_transmitted(struct sctp_tcb *stcb SCTP_UNUSED,
+ struct sctp_nets *net)
+{
+ if (net->cc_mod.rtcc.tls_needs_set == 0) {
+ SCTP_GETPTIME_TIMEVAL(&net->cc_mod.rtcc.tls);
+ net->cc_mod.rtcc.tls_needs_set = 2;
+ }
+}
+
+static void
+sctp_cwnd_update_rtcc_after_sack(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int accum_moved, int reneged_all, int will_exit)
+{
+ /* Passing a one argument at the last enables the rtcc algorithm */
+ sctp_cwnd_update_after_sack_common(stcb, asoc, accum_moved, reneged_all, will_exit, 1);
+}
+
+static void
+sctp_rtt_rtcc_calculated(struct sctp_tcb *stcb SCTP_UNUSED,
+ struct sctp_nets *net,
+ struct timeval *now SCTP_UNUSED)
+{
+ net->cc_mod.rtcc.rtt_set_this_sack = 1;
+}
+
+/* Here starts Sally Floyds HS-TCP */
+
+struct sctp_hs_raise_drop {
+ int32_t cwnd;
+ int8_t increase;
+ int8_t drop_percent;
+};
+
+#define SCTP_HS_TABLE_SIZE 73
+
+static const struct sctp_hs_raise_drop sctp_cwnd_adjust[SCTP_HS_TABLE_SIZE] = {
+ {38, 1, 50}, /* 0 */
+ {118, 2, 44}, /* 1 */
+ {221, 3, 41}, /* 2 */
+ {347, 4, 38}, /* 3 */
+ {495, 5, 37}, /* 4 */
+ {663, 6, 35}, /* 5 */
+ {851, 7, 34}, /* 6 */
+ {1058, 8, 33}, /* 7 */
+ {1284, 9, 32}, /* 8 */
+ {1529, 10, 31}, /* 9 */
+ {1793, 11, 30}, /* 10 */
+ {2076, 12, 29}, /* 11 */
+ {2378, 13, 28}, /* 12 */
+ {2699, 14, 28}, /* 13 */
+ {3039, 15, 27}, /* 14 */
+ {3399, 16, 27}, /* 15 */
+ {3778, 17, 26}, /* 16 */
+ {4177, 18, 26}, /* 17 */
+ {4596, 19, 25}, /* 18 */
+ {5036, 20, 25}, /* 19 */
+ {5497, 21, 24}, /* 20 */
+ {5979, 22, 24}, /* 21 */
+ {6483, 23, 23}, /* 22 */
+ {7009, 24, 23}, /* 23 */
+ {7558, 25, 22}, /* 24 */
+ {8130, 26, 22}, /* 25 */
+ {8726, 27, 22}, /* 26 */
+ {9346, 28, 21}, /* 27 */
+ {9991, 29, 21}, /* 28 */
+ {10661, 30, 21}, /* 29 */
+ {11358, 31, 20}, /* 30 */
+ {12082, 32, 20}, /* 31 */
+ {12834, 33, 20}, /* 32 */
+ {13614, 34, 19}, /* 33 */
+ {14424, 35, 19}, /* 34 */
+ {15265, 36, 19}, /* 35 */
+ {16137, 37, 19}, /* 36 */
+ {17042, 38, 18}, /* 37 */
+ {17981, 39, 18}, /* 38 */
+ {18955, 40, 18}, /* 39 */
+ {19965, 41, 17}, /* 40 */
+ {21013, 42, 17}, /* 41 */
+ {22101, 43, 17}, /* 42 */
+ {23230, 44, 17}, /* 43 */
+ {24402, 45, 16}, /* 44 */
+ {25618, 46, 16}, /* 45 */
+ {26881, 47, 16}, /* 46 */
+ {28193, 48, 16}, /* 47 */
+ {29557, 49, 15}, /* 48 */
+ {30975, 50, 15}, /* 49 */
+ {32450, 51, 15}, /* 50 */
+ {33986, 52, 15}, /* 51 */
+ {35586, 53, 14}, /* 52 */
+ {37253, 54, 14}, /* 53 */
+ {38992, 55, 14}, /* 54 */
+ {40808, 56, 14}, /* 55 */
+ {42707, 57, 13}, /* 56 */
+ {44694, 58, 13}, /* 57 */
+ {46776, 59, 13}, /* 58 */
+ {48961, 60, 13}, /* 59 */
+ {51258, 61, 13}, /* 60 */
+ {53677, 62, 12}, /* 61 */
+ {56230, 63, 12}, /* 62 */
+ {58932, 64, 12}, /* 63 */
+ {61799, 65, 12}, /* 64 */
+ {64851, 66, 11}, /* 65 */
+ {68113, 67, 11}, /* 66 */
+ {71617, 68, 11}, /* 67 */
+ {75401, 69, 10}, /* 68 */
+ {79517, 70, 10}, /* 69 */
+ {84035, 71, 10}, /* 70 */
+ {89053, 72, 10}, /* 71 */
+ {94717, 73, 9} /* 72 */
+};
+
+static void
+sctp_hs_cwnd_increase(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ int cur_val, i, indx, incr;
+ int old_cwnd = net->cwnd;
+
+ cur_val = net->cwnd >> 10;
+ indx = SCTP_HS_TABLE_SIZE - 1;
+
+ if (cur_val < sctp_cwnd_adjust[0].cwnd) {
+ /* normal mode */
+ if (net->net_ack > net->mtu) {
+ net->cwnd += net->mtu;
+ } else {
+ net->cwnd += net->net_ack;
+ }
+ } else {
+ for (i = net->last_hs_used; i < SCTP_HS_TABLE_SIZE; i++) {
+ if (cur_val < sctp_cwnd_adjust[i].cwnd) {
+ indx = i;
+ break;
+ }
+ }
+ net->last_hs_used = indx;
+ incr = (((int32_t)sctp_cwnd_adjust[indx].increase) << 10);
+ net->cwnd += incr;
+ }
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_SS);
+ }
+}
+
+static void
+sctp_hs_cwnd_decrease(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ int cur_val, i, indx;
+ int old_cwnd = net->cwnd;
+
+ cur_val = net->cwnd >> 10;
+ if (cur_val < sctp_cwnd_adjust[0].cwnd) {
+ /* normal mode */
+ net->ssthresh = net->cwnd / 2;
+ if (net->ssthresh < (net->mtu * 2)) {
+ net->ssthresh = 2 * net->mtu;
+ }
+ net->cwnd = net->ssthresh;
+ } else {
+ /* drop by the proper amount */
+ net->ssthresh = net->cwnd - (int)((net->cwnd / 100) *
+ (int32_t)sctp_cwnd_adjust[net->last_hs_used].drop_percent);
+ net->cwnd = net->ssthresh;
+ /* now where are we */
+ indx = net->last_hs_used;
+ cur_val = net->cwnd >> 10;
+ /* reset where we are in the table */
+ if (cur_val < sctp_cwnd_adjust[0].cwnd) {
+ /* feel out of hs */
+ net->last_hs_used = 0;
+ } else {
+ for (i = indx; i >= 1; i--) {
+ if (cur_val > sctp_cwnd_adjust[i - 1].cwnd) {
+ break;
+ }
+ }
+ net->last_hs_used = indx;
+ }
+ }
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_FR);
+ }
+}
+
+static void
+sctp_hs_cwnd_update_after_fr(struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ struct sctp_nets *net;
+ /*
+ * CMT fast recovery code. Need to debug. ((sctp_cmt_on_off > 0) &&
+ * (net->fast_retran_loss_recovery == 0)))
+ */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if ((asoc->fast_retran_loss_recovery == 0) ||
+ (asoc->sctp_cmt_on_off > 0)) {
+ /* out of a RFC2582 Fast recovery window? */
+ if (net->net_ack > 0) {
+ /*
+ * per section 7.2.3, are there any
+ * destinations that had a fast retransmit
+ * to them. If so what we need to do is
+ * adjust ssthresh and cwnd.
+ */
+ struct sctp_tmit_chunk *lchk;
+
+ sctp_hs_cwnd_decrease(stcb, net);
+
+ lchk = TAILQ_FIRST(&asoc->send_queue);
+
+ net->partial_bytes_acked = 0;
+ /* Turn on fast recovery window */
+ asoc->fast_retran_loss_recovery = 1;
+ if (lchk == NULL) {
+ /* Mark end of the window */
+ asoc->fast_recovery_tsn = asoc->sending_seq - 1;
+ } else {
+ asoc->fast_recovery_tsn = lchk->rec.data.tsn - 1;
+ }
+
+ /*
+ * CMT fast recovery -- per destination
+ * recovery variable.
+ */
+ net->fast_retran_loss_recovery = 1;
+
+ if (lchk == NULL) {
+ /* Mark end of the window */
+ net->fast_recovery_tsn = asoc->sending_seq - 1;
+ } else {
+ net->fast_recovery_tsn = lchk->rec.data.tsn - 1;
+ }
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_CC_FUNCTIONS + SCTP_LOC_2);
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net);
+ }
+ } else if (net->net_ack > 0) {
+ /*
+ * Mark a peg that we WOULD have done a cwnd
+ * reduction but RFC2582 prevented this action.
+ */
+ SCTP_STAT_INCR(sctps_fastretransinrtt);
+ }
+ }
+}
+
+static void
+sctp_hs_cwnd_update_after_sack(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int accum_moved, int reneged_all SCTP_UNUSED, int will_exit)
+{
+ struct sctp_nets *net;
+ /******************************/
+ /* update cwnd and Early FR */
+ /******************************/
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+#ifdef JANA_CMT_FAST_RECOVERY
+ /*
+ * CMT fast recovery code. Need to debug.
+ */
+ if (net->fast_retran_loss_recovery && net->new_pseudo_cumack) {
+ if (SCTP_TSN_GE(asoc->last_acked_seq, net->fast_recovery_tsn) ||
+ SCTP_TSN_GE(net->pseudo_cumack,net->fast_recovery_tsn)) {
+ net->will_exit_fast_recovery = 1;
+ }
+ }
+#endif
+ /* if nothing was acked on this destination skip it */
+ if (net->net_ack == 0) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, 0, SCTP_CWND_LOG_FROM_SACK);
+ }
+ continue;
+ }
+#ifdef JANA_CMT_FAST_RECOVERY
+ /* CMT fast recovery code
+ */
+ /*
+ if (sctp_cmt_on_off > 0 && net->fast_retran_loss_recovery && net->will_exit_fast_recovery == 0) {
+ @@@ Do something
+ }
+ else if (sctp_cmt_on_off == 0 && asoc->fast_retran_loss_recovery && will_exit == 0) {
+ */
+#endif
+
+ if (asoc->fast_retran_loss_recovery &&
+ (will_exit == 0) &&
+ (asoc->sctp_cmt_on_off == 0)) {
+ /*
+ * If we are in loss recovery we skip any cwnd
+ * update
+ */
+ return;
+ }
+ /*
+ * CMT: CUC algorithm. Update cwnd if pseudo-cumack has
+ * moved.
+ */
+ if (accum_moved ||
+ ((asoc->sctp_cmt_on_off > 0) && net->new_pseudo_cumack)) {
+ /* If the cumulative ack moved we can proceed */
+ if (net->cwnd <= net->ssthresh) {
+ /* We are in slow start */
+ if (net->flight_size + net->net_ack >= net->cwnd) {
+ sctp_hs_cwnd_increase(stcb, net);
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->net_ack,
+ SCTP_CWND_LOG_NOADV_SS);
+ }
+ }
+ } else {
+ /* We are in congestion avoidance */
+ net->partial_bytes_acked += net->net_ack;
+ if ((net->flight_size + net->net_ack >= net->cwnd) &&
+ (net->partial_bytes_acked >= net->cwnd)) {
+ net->partial_bytes_acked -= net->cwnd;
+ net->cwnd += net->mtu;
+ sctp_enforce_cwnd_limit(asoc, net);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->mtu,
+ SCTP_CWND_LOG_FROM_CA);
+ }
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->net_ack,
+ SCTP_CWND_LOG_NOADV_CA);
+ }
+ }
+ }
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->mtu,
+ SCTP_CWND_LOG_NO_CUMACK);
+ }
+ }
+ }
+}
+
+/*
+ * H-TCP congestion control. The algorithm is detailed in:
+ * R.N.Shorten, D.J.Leith:
+ * "H-TCP: TCP for high-speed and long-distance networks"
+ * Proc. PFLDnet, Argonne, 2004.
+ * http://www.hamilton.ie/net/htcp3.pdf
+ */
+
+static int use_rtt_scaling = 1;
+static int use_bandwidth_switch = 1;
+
+static inline int
+between(uint32_t seq1, uint32_t seq2, uint32_t seq3)
+{
+ return (seq3 - seq2 >= seq1 - seq2);
+}
+
+static inline uint32_t
+htcp_cong_time(struct htcp *ca)
+{
+ return (sctp_get_tick_count() - ca->last_cong);
+}
+
+static inline uint32_t
+htcp_ccount(struct htcp *ca)
+{
+ return (ca->minRTT == 0 ? htcp_cong_time(ca) : htcp_cong_time(ca)/ca->minRTT);
+}
+
+static inline void
+htcp_reset(struct htcp *ca)
+{
+ ca->undo_last_cong = ca->last_cong;
+ ca->undo_maxRTT = ca->maxRTT;
+ ca->undo_old_maxB = ca->old_maxB;
+ ca->last_cong = sctp_get_tick_count();
+}
+
+#ifdef SCTP_NOT_USED
+
+static uint32_t
+htcp_cwnd_undo(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ net->cc_mod.htcp_ca.last_cong = net->cc_mod.htcp_ca.undo_last_cong;
+ net->cc_mod.htcp_ca.maxRTT = net->cc_mod.htcp_ca.undo_maxRTT;
+ net->cc_mod.htcp_ca.old_maxB = net->cc_mod.htcp_ca.undo_old_maxB;
+ return (max(net->cwnd, ((net->ssthresh/net->mtu<<7)/net->cc_mod.htcp_ca.beta)*net->mtu));
+}
+
+#endif
+
+static inline void
+measure_rtt(struct sctp_nets *net)
+{
+ uint32_t srtt = net->lastsa>>SCTP_RTT_SHIFT;
+
+ /* keep track of minimum RTT seen so far, minRTT is zero at first */
+ if (net->cc_mod.htcp_ca.minRTT > srtt || !net->cc_mod.htcp_ca.minRTT)
+ net->cc_mod.htcp_ca.minRTT = srtt;
+
+ /* max RTT */
+ if (net->fast_retran_ip == 0 && net->ssthresh < 0xFFFF && htcp_ccount(&net->cc_mod.htcp_ca) > 3) {
+ if (net->cc_mod.htcp_ca.maxRTT < net->cc_mod.htcp_ca.minRTT)
+ net->cc_mod.htcp_ca.maxRTT = net->cc_mod.htcp_ca.minRTT;
+ if (net->cc_mod.htcp_ca.maxRTT < srtt && srtt <= net->cc_mod.htcp_ca.maxRTT+sctp_msecs_to_ticks(20))
+ net->cc_mod.htcp_ca.maxRTT = srtt;
+ }
+}
+
+static void
+measure_achieved_throughput(struct sctp_nets *net)
+{
+ uint32_t now = sctp_get_tick_count();
+
+ if (net->fast_retran_ip == 0)
+ net->cc_mod.htcp_ca.bytes_acked = net->net_ack;
+
+ if (!use_bandwidth_switch)
+ return;
+
+ /* achieved throughput calculations */
+ /* JRS - not 100% sure of this statement */
+ if (net->fast_retran_ip == 1) {
+ net->cc_mod.htcp_ca.bytecount = 0;
+ net->cc_mod.htcp_ca.lasttime = now;
+ return;
+ }
+
+ net->cc_mod.htcp_ca.bytecount += net->net_ack;
+ if ((net->cc_mod.htcp_ca.bytecount >= net->cwnd - (((net->cc_mod.htcp_ca.alpha >> 7) ? (net->cc_mod.htcp_ca.alpha >> 7) : 1) * net->mtu)) &&
+ (now - net->cc_mod.htcp_ca.lasttime >= net->cc_mod.htcp_ca.minRTT) &&
+ (net->cc_mod.htcp_ca.minRTT > 0)) {
+ uint32_t cur_Bi = net->cc_mod.htcp_ca.bytecount/net->mtu*hz/(now - net->cc_mod.htcp_ca.lasttime);
+
+ if (htcp_ccount(&net->cc_mod.htcp_ca) <= 3) {
+ /* just after backoff */
+ net->cc_mod.htcp_ca.minB = net->cc_mod.htcp_ca.maxB = net->cc_mod.htcp_ca.Bi = cur_Bi;
+ } else {
+ net->cc_mod.htcp_ca.Bi = (3*net->cc_mod.htcp_ca.Bi + cur_Bi)/4;
+ if (net->cc_mod.htcp_ca.Bi > net->cc_mod.htcp_ca.maxB)
+ net->cc_mod.htcp_ca.maxB = net->cc_mod.htcp_ca.Bi;
+ if (net->cc_mod.htcp_ca.minB > net->cc_mod.htcp_ca.maxB)
+ net->cc_mod.htcp_ca.minB = net->cc_mod.htcp_ca.maxB;
+ }
+ net->cc_mod.htcp_ca.bytecount = 0;
+ net->cc_mod.htcp_ca.lasttime = now;
+ }
+}
+
+static inline void
+htcp_beta_update(struct htcp *ca, uint32_t minRTT, uint32_t maxRTT)
+{
+ if (use_bandwidth_switch) {
+ uint32_t maxB = ca->maxB;
+ uint32_t old_maxB = ca->old_maxB;
+ ca->old_maxB = ca->maxB;
+
+ if (!between(5*maxB, 4*old_maxB, 6*old_maxB)) {
+ ca->beta = BETA_MIN;
+ ca->modeswitch = 0;
+ return;
+ }
+ }
+
+ if (ca->modeswitch && minRTT > sctp_msecs_to_ticks(10) && maxRTT) {
+ ca->beta = (minRTT<<7)/maxRTT;
+ if (ca->beta < BETA_MIN)
+ ca->beta = BETA_MIN;
+ else if (ca->beta > BETA_MAX)
+ ca->beta = BETA_MAX;
+ } else {
+ ca->beta = BETA_MIN;
+ ca->modeswitch = 1;
+ }
+}
+
+static inline void
+htcp_alpha_update(struct htcp *ca)
+{
+ uint32_t minRTT = ca->minRTT;
+ uint32_t factor = 1;
+ uint32_t diff = htcp_cong_time(ca);
+
+ if (diff > (uint32_t)hz) {
+ diff -= hz;
+ factor = 1+ (10 * diff + ((diff / 2) * (diff / 2) / hz)) / hz;
+ }
+
+ if (use_rtt_scaling && minRTT) {
+ uint32_t scale = (hz << 3) / (10 * minRTT);
+ scale = min(max(scale, 1U << 2), 10U << 3); /* clamping ratio to interval [0.5,10]<<3 */
+ factor = (factor << 3) / scale;
+ if (factor != 0)
+ factor = 1;
+ }
+
+ ca->alpha = 2 * factor * ((1 << 7) - ca->beta);
+ if (ca->alpha != 0)
+ ca->alpha = ALPHA_BASE;
+}
+
+/* After we have the rtt data to calculate beta, we'd still prefer to wait one
+ * rtt before we adjust our beta to ensure we are working from a consistent
+ * data.
+ *
+ * This function should be called when we hit a congestion event since only at
+ * that point do we really have a real sense of maxRTT (the queues en route
+ * were getting just too full now).
+ */
+static void
+htcp_param_update(struct sctp_nets *net)
+{
+ uint32_t minRTT = net->cc_mod.htcp_ca.minRTT;
+ uint32_t maxRTT = net->cc_mod.htcp_ca.maxRTT;
+
+ htcp_beta_update(&net->cc_mod.htcp_ca, minRTT, maxRTT);
+ htcp_alpha_update(&net->cc_mod.htcp_ca);
+
+ /* add slowly fading memory for maxRTT to accommodate routing changes etc */
+ if (minRTT > 0 && maxRTT > minRTT)
+ net->cc_mod.htcp_ca.maxRTT = minRTT + ((maxRTT-minRTT)*95)/100;
+}
+
+static uint32_t
+htcp_recalc_ssthresh(struct sctp_nets *net)
+{
+ htcp_param_update(net);
+ return (max(((net->cwnd/net->mtu * net->cc_mod.htcp_ca.beta) >> 7)*net->mtu, 2U*net->mtu));
+}
+
+static void
+htcp_cong_avoid(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ /*-
+ * How to handle these functions?
+ * if (!tcp_is_cwnd_limited(sk, in_flight)) RRS - good question.
+ * return;
+ */
+ if (net->cwnd <= net->ssthresh) {
+ /* We are in slow start */
+ if (net->flight_size + net->net_ack >= net->cwnd) {
+ if (net->net_ack > (net->mtu * SCTP_BASE_SYSCTL(sctp_L2_abc_variable))) {
+ net->cwnd += (net->mtu * SCTP_BASE_SYSCTL(sctp_L2_abc_variable));
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->mtu,
+ SCTP_CWND_LOG_FROM_SS);
+ }
+
+ } else {
+ net->cwnd += net->net_ack;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->net_ack,
+ SCTP_CWND_LOG_FROM_SS);
+ }
+ }
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->net_ack,
+ SCTP_CWND_LOG_NOADV_SS);
+ }
+ }
+ } else {
+ measure_rtt(net);
+
+ /* In dangerous area, increase slowly.
+ * In theory this is net->cwnd += alpha / net->cwnd
+ */
+ /* What is snd_cwnd_cnt?? */
+ if (((net->partial_bytes_acked/net->mtu * net->cc_mod.htcp_ca.alpha) >> 7)*net->mtu >= net->cwnd) {
+ /*-
+ * Does SCTP have a cwnd clamp?
+ * if (net->snd_cwnd < net->snd_cwnd_clamp) - Nope (RRS).
+ */
+ net->cwnd += net->mtu;
+ net->partial_bytes_acked = 0;
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ htcp_alpha_update(&net->cc_mod.htcp_ca);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->mtu,
+ SCTP_CWND_LOG_FROM_CA);
+ }
+ } else {
+ net->partial_bytes_acked += net->net_ack;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->net_ack,
+ SCTP_CWND_LOG_NOADV_CA);
+ }
+ }
+
+ net->cc_mod.htcp_ca.bytes_acked = net->mtu;
+ }
+}
+
+#ifdef SCTP_NOT_USED
+/* Lower bound on congestion window. */
+static uint32_t
+htcp_min_cwnd(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ return (net->ssthresh);
+}
+#endif
+
+static void
+htcp_init(struct sctp_nets *net)
+{
+ memset(&net->cc_mod.htcp_ca, 0, sizeof(struct htcp));
+ net->cc_mod.htcp_ca.alpha = ALPHA_BASE;
+ net->cc_mod.htcp_ca.beta = BETA_MIN;
+ net->cc_mod.htcp_ca.bytes_acked = net->mtu;
+ net->cc_mod.htcp_ca.last_cong = sctp_get_tick_count();
+}
+
+static void
+sctp_htcp_set_initial_cc_param(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ /*
+ * We take the max of the burst limit times a MTU or the
+ * INITIAL_CWND. We then limit this to 4 MTU's of sending.
+ */
+ net->cwnd = min((net->mtu * 4), max((2 * net->mtu), SCTP_INITIAL_CWND));
+ net->ssthresh = stcb->asoc.peers_rwnd;
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ htcp_init(net);
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & (SCTP_CWND_MONITOR_ENABLE|SCTP_CWND_LOGGING_ENABLE)) {
+ sctp_log_cwnd(stcb, net, 0, SCTP_CWND_INITIALIZATION);
+ }
+}
+
+static void
+sctp_htcp_cwnd_update_after_sack(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int accum_moved, int reneged_all SCTP_UNUSED, int will_exit)
+{
+ struct sctp_nets *net;
+
+ /******************************/
+ /* update cwnd and Early FR */
+ /******************************/
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+#ifdef JANA_CMT_FAST_RECOVERY
+ /*
+ * CMT fast recovery code. Need to debug.
+ */
+ if (net->fast_retran_loss_recovery && net->new_pseudo_cumack) {
+ if (SCTP_TSN_GE(asoc->last_acked_seq, net->fast_recovery_tsn) ||
+ SCTP_TSN_GE(net->pseudo_cumack,net->fast_recovery_tsn)) {
+ net->will_exit_fast_recovery = 1;
+ }
+ }
+#endif
+ /* if nothing was acked on this destination skip it */
+ if (net->net_ack == 0) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, 0, SCTP_CWND_LOG_FROM_SACK);
+ }
+ continue;
+ }
+#ifdef JANA_CMT_FAST_RECOVERY
+ /* CMT fast recovery code
+ */
+ /*
+ if (sctp_cmt_on_off > 0 && net->fast_retran_loss_recovery && net->will_exit_fast_recovery == 0) {
+ @@@ Do something
+ }
+ else if (sctp_cmt_on_off == 0 && asoc->fast_retran_loss_recovery && will_exit == 0) {
+ */
+#endif
+
+ if (asoc->fast_retran_loss_recovery &&
+ will_exit == 0 &&
+ (asoc->sctp_cmt_on_off == 0)) {
+ /*
+ * If we are in loss recovery we skip any cwnd
+ * update
+ */
+ return;
+ }
+ /*
+ * CMT: CUC algorithm. Update cwnd if pseudo-cumack has
+ * moved.
+ */
+ if (accum_moved ||
+ ((asoc->sctp_cmt_on_off > 0) && net->new_pseudo_cumack)) {
+ htcp_cong_avoid(stcb, net);
+ measure_achieved_throughput(net);
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->mtu,
+ SCTP_CWND_LOG_NO_CUMACK);
+ }
+ }
+ }
+}
+
+static void
+sctp_htcp_cwnd_update_after_fr(struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ struct sctp_nets *net;
+ /*
+ * CMT fast recovery code. Need to debug. ((sctp_cmt_on_off > 0) &&
+ * (net->fast_retran_loss_recovery == 0)))
+ */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if ((asoc->fast_retran_loss_recovery == 0) ||
+ (asoc->sctp_cmt_on_off > 0)) {
+ /* out of a RFC2582 Fast recovery window? */
+ if (net->net_ack > 0) {
+ /*
+ * per section 7.2.3, are there any
+ * destinations that had a fast retransmit
+ * to them. If so what we need to do is
+ * adjust ssthresh and cwnd.
+ */
+ struct sctp_tmit_chunk *lchk;
+ int old_cwnd = net->cwnd;
+
+ /* JRS - reset as if state were changed */
+ htcp_reset(&net->cc_mod.htcp_ca);
+ net->ssthresh = htcp_recalc_ssthresh(net);
+ net->cwnd = net->ssthresh;
+ sctp_enforce_cwnd_limit(asoc, net);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd),
+ SCTP_CWND_LOG_FROM_FR);
+ }
+ lchk = TAILQ_FIRST(&asoc->send_queue);
+
+ net->partial_bytes_acked = 0;
+ /* Turn on fast recovery window */
+ asoc->fast_retran_loss_recovery = 1;
+ if (lchk == NULL) {
+ /* Mark end of the window */
+ asoc->fast_recovery_tsn = asoc->sending_seq - 1;
+ } else {
+ asoc->fast_recovery_tsn = lchk->rec.data.tsn - 1;
+ }
+
+ /*
+ * CMT fast recovery -- per destination
+ * recovery variable.
+ */
+ net->fast_retran_loss_recovery = 1;
+
+ if (lchk == NULL) {
+ /* Mark end of the window */
+ net->fast_recovery_tsn = asoc->sending_seq - 1;
+ } else {
+ net->fast_recovery_tsn = lchk->rec.data.tsn - 1;
+ }
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_CC_FUNCTIONS + SCTP_LOC_3);
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net);
+ }
+ } else if (net->net_ack > 0) {
+ /*
+ * Mark a peg that we WOULD have done a cwnd
+ * reduction but RFC2582 prevented this action.
+ */
+ SCTP_STAT_INCR(sctps_fastretransinrtt);
+ }
+ }
+}
+
+static void
+sctp_htcp_cwnd_update_after_timeout(struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ int old_cwnd = net->cwnd;
+
+ /* JRS - reset as if the state were being changed to timeout */
+ htcp_reset(&net->cc_mod.htcp_ca);
+ net->ssthresh = htcp_recalc_ssthresh(net);
+ net->cwnd = net->mtu;
+ net->partial_bytes_acked = 0;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->cwnd - old_cwnd, SCTP_CWND_LOG_FROM_RTX);
+ }
+}
+
+static void
+sctp_htcp_cwnd_update_after_ecn_echo(struct sctp_tcb *stcb,
+ struct sctp_nets *net, int in_window, int num_pkt_lost SCTP_UNUSED)
+{
+ int old_cwnd;
+ old_cwnd = net->cwnd;
+
+ /* JRS - reset hctp as if state changed */
+ if (in_window == 0) {
+ htcp_reset(&net->cc_mod.htcp_ca);
+ SCTP_STAT_INCR(sctps_ecnereducedcwnd);
+ net->ssthresh = htcp_recalc_ssthresh(net);
+ if (net->ssthresh < net->mtu) {
+ net->ssthresh = net->mtu;
+ /* here back off the timer as well, to slow us down */
+ net->RTO <<= 1;
+ }
+ net->cwnd = net->ssthresh;
+ sctp_enforce_cwnd_limit(&stcb->asoc, net);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_SAT);
+ }
+ }
+}
+
+const struct sctp_cc_functions sctp_cc_functions[] = {
+{
+#if defined(_WIN32) && !defined(__MINGW32__)
+ sctp_set_initial_cc_param,
+ sctp_cwnd_update_after_sack,
+ sctp_cwnd_update_exit_pf_common,
+ sctp_cwnd_update_after_fr,
+ sctp_cwnd_update_after_timeout,
+ sctp_cwnd_update_after_ecn_echo,
+ sctp_cwnd_update_after_packet_dropped,
+ sctp_cwnd_update_after_output,
+#else
+ .sctp_set_initial_cc_param = sctp_set_initial_cc_param,
+ .sctp_cwnd_update_after_sack = sctp_cwnd_update_after_sack,
+ .sctp_cwnd_update_exit_pf = sctp_cwnd_update_exit_pf_common,
+ .sctp_cwnd_update_after_fr = sctp_cwnd_update_after_fr,
+ .sctp_cwnd_update_after_timeout = sctp_cwnd_update_after_timeout,
+ .sctp_cwnd_update_after_ecn_echo = sctp_cwnd_update_after_ecn_echo,
+ .sctp_cwnd_update_after_packet_dropped = sctp_cwnd_update_after_packet_dropped,
+ .sctp_cwnd_update_after_output = sctp_cwnd_update_after_output,
+#endif
+},
+{
+#if defined(_WIN32) && !defined(__MINGW32__)
+ sctp_set_initial_cc_param,
+ sctp_hs_cwnd_update_after_sack,
+ sctp_cwnd_update_exit_pf_common,
+ sctp_hs_cwnd_update_after_fr,
+ sctp_cwnd_update_after_timeout,
+ sctp_cwnd_update_after_ecn_echo,
+ sctp_cwnd_update_after_packet_dropped,
+ sctp_cwnd_update_after_output,
+#else
+ .sctp_set_initial_cc_param = sctp_set_initial_cc_param,
+ .sctp_cwnd_update_after_sack = sctp_hs_cwnd_update_after_sack,
+ .sctp_cwnd_update_exit_pf = sctp_cwnd_update_exit_pf_common,
+ .sctp_cwnd_update_after_fr = sctp_hs_cwnd_update_after_fr,
+ .sctp_cwnd_update_after_timeout = sctp_cwnd_update_after_timeout,
+ .sctp_cwnd_update_after_ecn_echo = sctp_cwnd_update_after_ecn_echo,
+ .sctp_cwnd_update_after_packet_dropped = sctp_cwnd_update_after_packet_dropped,
+ .sctp_cwnd_update_after_output = sctp_cwnd_update_after_output,
+#endif
+},
+{
+#if defined(_WIN32) && !defined(__MINGW32__)
+ sctp_htcp_set_initial_cc_param,
+ sctp_htcp_cwnd_update_after_sack,
+ sctp_cwnd_update_exit_pf_common,
+ sctp_htcp_cwnd_update_after_fr,
+ sctp_htcp_cwnd_update_after_timeout,
+ sctp_htcp_cwnd_update_after_ecn_echo,
+ sctp_cwnd_update_after_packet_dropped,
+ sctp_cwnd_update_after_output,
+#else
+ .sctp_set_initial_cc_param = sctp_htcp_set_initial_cc_param,
+ .sctp_cwnd_update_after_sack = sctp_htcp_cwnd_update_after_sack,
+ .sctp_cwnd_update_exit_pf = sctp_cwnd_update_exit_pf_common,
+ .sctp_cwnd_update_after_fr = sctp_htcp_cwnd_update_after_fr,
+ .sctp_cwnd_update_after_timeout = sctp_htcp_cwnd_update_after_timeout,
+ .sctp_cwnd_update_after_ecn_echo = sctp_htcp_cwnd_update_after_ecn_echo,
+ .sctp_cwnd_update_after_packet_dropped = sctp_cwnd_update_after_packet_dropped,
+ .sctp_cwnd_update_after_output = sctp_cwnd_update_after_output,
+#endif
+},
+{
+#if defined(_WIN32) && !defined(__MINGW32__)
+ sctp_set_rtcc_initial_cc_param,
+ sctp_cwnd_update_rtcc_after_sack,
+ sctp_cwnd_update_exit_pf_common,
+ sctp_cwnd_update_after_fr,
+ sctp_cwnd_update_after_timeout,
+ sctp_cwnd_update_rtcc_after_ecn_echo,
+ sctp_cwnd_update_after_packet_dropped,
+ sctp_cwnd_update_after_output,
+ sctp_cwnd_update_rtcc_packet_transmitted,
+ sctp_cwnd_update_rtcc_tsn_acknowledged,
+ sctp_cwnd_new_rtcc_transmission_begins,
+ sctp_cwnd_prepare_rtcc_net_for_sack,
+ sctp_cwnd_rtcc_socket_option,
+ sctp_rtt_rtcc_calculated
+#else
+ .sctp_set_initial_cc_param = sctp_set_rtcc_initial_cc_param,
+ .sctp_cwnd_update_after_sack = sctp_cwnd_update_rtcc_after_sack,
+ .sctp_cwnd_update_exit_pf = sctp_cwnd_update_exit_pf_common,
+ .sctp_cwnd_update_after_fr = sctp_cwnd_update_after_fr,
+ .sctp_cwnd_update_after_timeout = sctp_cwnd_update_after_timeout,
+ .sctp_cwnd_update_after_ecn_echo = sctp_cwnd_update_rtcc_after_ecn_echo,
+ .sctp_cwnd_update_after_packet_dropped = sctp_cwnd_update_after_packet_dropped,
+ .sctp_cwnd_update_after_output = sctp_cwnd_update_after_output,
+ .sctp_cwnd_update_packet_transmitted = sctp_cwnd_update_rtcc_packet_transmitted,
+ .sctp_cwnd_update_tsn_acknowledged = sctp_cwnd_update_rtcc_tsn_acknowledged,
+ .sctp_cwnd_new_transmission_begins = sctp_cwnd_new_rtcc_transmission_begins,
+ .sctp_cwnd_prepare_net_for_sack = sctp_cwnd_prepare_rtcc_net_for_sack,
+ .sctp_cwnd_socket_option = sctp_cwnd_rtcc_socket_option,
+ .sctp_rtt_calculated = sctp_rtt_rtcc_calculated
+#endif
+}
+};
diff --git a/netwerk/sctp/src/netinet/sctp_constants.h b/netwerk/sctp/src/netinet/sctp_constants.h
new file mode 100644
index 0000000000..69953e9b9e
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_constants.h
@@ -0,0 +1,1065 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_CONSTANTS_H_
+#define _NETINET_SCTP_CONSTANTS_H_
+
+#if defined(_WIN32) && defined(__Userspace__)
+extern void getwintimeofday(struct timeval *tv);
+
+#endif
+/* IANA assigned port number for SCTP over UDP encapsulation */
+#define SCTP_OVER_UDP_TUNNELING_PORT 9899
+
+/* Number of packets to get before sack sent by default */
+#define SCTP_DEFAULT_SACK_FREQ 2
+
+/* Address limit - This variable is calculated
+ * based on an 65535 byte max ip packet. We take out 100 bytes
+ * for the cookie, 40 bytes for a v6 header and 32
+ * bytes for the init structure. A second init structure
+ * for the init-ack and then finally a third one for the
+ * imbedded init. This yeilds 100+40+(3 * 32) = 236 bytes.
+ * This leaves 65299 bytes for addresses. We throw out the 299 bytes.
+ * Now whatever we send in the INIT() we need to allow to get back in the
+ * INIT-ACK plus all the values from INIT and INIT-ACK
+ * listed in the cookie. Plus we need some overhead for
+ * maybe copied parameters in the COOKIE. If we
+ * allow 1080 addresses, and each side has 1080 V6 addresses
+ * that will be 21600 bytes. In the INIT-ACK we will
+ * see the INIT-ACK 21600 + 43200 in the cookie. This leaves
+ * about 500 bytes slack for misc things in the cookie.
+ */
+#define SCTP_ADDRESS_LIMIT 1080
+
+/* We need at least 2k of space for us, inits
+ * larger than that lets abort.
+ */
+#define SCTP_LARGEST_INIT_ACCEPTED (65535 - 2048)
+
+/* Largest length of a chunk */
+#define SCTP_MAX_CHUNK_LENGTH 0xffff
+/* Largest length of an error cause */
+#define SCTP_MAX_CAUSE_LENGTH 0xffff
+/* Number of addresses where we just skip the counting */
+#define SCTP_COUNT_LIMIT 40
+
+#define SCTP_ZERO_COPY_TICK_DELAY (((100 * hz) + 999) / 1000)
+#define SCTP_ZERO_COPY_SENDQ_TICK_DELAY (((100 * hz) + 999) / 1000)
+
+/* Number of ticks to delay before running
+ * iterator on an address change.
+ */
+#define SCTP_ADDRESS_TICK_DELAY 2
+
+#define SCTP_VERSION_STRING "KAME-BSD 1.1"
+/* #define SCTP_AUDITING_ENABLED 1 used for debug/auditing */
+#define SCTP_AUDIT_SIZE 256
+
+#define SCTP_KTRHEAD_NAME "sctp_iterator"
+#define SCTP_KTHREAD_PAGES 0
+
+#define SCTP_MCORE_NAME "sctp_core_worker"
+
+/* If you support Multi-VRF how big to
+ * make the initial array of VRF's to.
+ */
+#define SCTP_DEFAULT_VRF_SIZE 4
+
+/* JRS - Values defined for the HTCP algorithm */
+#define ALPHA_BASE (1<<7) /* 1.0 with shift << 7 */
+#define BETA_MIN (1<<6) /* 0.5 with shift << 7 */
+#define BETA_MAX 102 /* 0.8 with shift << 7 */
+
+/* Places that CWND log can happen from */
+#define SCTP_CWND_LOG_FROM_FR 1
+#define SCTP_CWND_LOG_FROM_RTX 2
+#define SCTP_CWND_LOG_FROM_BRST 3
+#define SCTP_CWND_LOG_FROM_SS 4
+#define SCTP_CWND_LOG_FROM_CA 5
+#define SCTP_CWND_LOG_FROM_SAT 6
+#define SCTP_BLOCK_LOG_INTO_BLK 7
+#define SCTP_BLOCK_LOG_OUTOF_BLK 8
+#define SCTP_BLOCK_LOG_CHECK 9
+#define SCTP_STR_LOG_FROM_INTO_STRD 10
+#define SCTP_STR_LOG_FROM_IMMED_DEL 11
+#define SCTP_STR_LOG_FROM_INSERT_HD 12
+#define SCTP_STR_LOG_FROM_INSERT_MD 13
+#define SCTP_STR_LOG_FROM_INSERT_TL 14
+#define SCTP_STR_LOG_FROM_MARK_TSN 15
+#define SCTP_STR_LOG_FROM_EXPRS_DEL 16
+#define SCTP_FR_LOG_BIGGEST_TSNS 17
+#define SCTP_FR_LOG_STRIKE_TEST 18
+#define SCTP_FR_LOG_STRIKE_CHUNK 19
+#define SCTP_FR_T3_TIMEOUT 20
+#define SCTP_MAP_PREPARE_SLIDE 21
+#define SCTP_MAP_SLIDE_FROM 22
+#define SCTP_MAP_SLIDE_RESULT 23
+#define SCTP_MAP_SLIDE_CLEARED 24
+#define SCTP_MAP_SLIDE_NONE 25
+#define SCTP_FR_T3_MARK_TIME 26
+#define SCTP_FR_T3_MARKED 27
+#define SCTP_FR_T3_STOPPED 28
+#define SCTP_FR_MARKED 30
+#define SCTP_CWND_LOG_NOADV_SS 31
+#define SCTP_CWND_LOG_NOADV_CA 32
+#define SCTP_MAX_BURST_APPLIED 33
+#define SCTP_MAX_IFP_APPLIED 34
+#define SCTP_MAX_BURST_ERROR_STOP 35
+#define SCTP_INCREASE_PEER_RWND 36
+#define SCTP_DECREASE_PEER_RWND 37
+#define SCTP_SET_PEER_RWND_VIA_SACK 38
+#define SCTP_LOG_MBCNT_INCREASE 39
+#define SCTP_LOG_MBCNT_DECREASE 40
+#define SCTP_LOG_MBCNT_CHKSET 41
+#define SCTP_LOG_NEW_SACK 42
+#define SCTP_LOG_TSN_ACKED 43
+#define SCTP_LOG_TSN_REVOKED 44
+#define SCTP_LOG_LOCK_TCB 45
+#define SCTP_LOG_LOCK_INP 46
+#define SCTP_LOG_LOCK_SOCK 47
+#define SCTP_LOG_LOCK_SOCKBUF_R 48
+#define SCTP_LOG_LOCK_SOCKBUF_S 49
+#define SCTP_LOG_LOCK_CREATE 50
+#define SCTP_LOG_INITIAL_RTT 51
+#define SCTP_LOG_RTTVAR 52
+#define SCTP_LOG_SBALLOC 53
+#define SCTP_LOG_SBFREE 54
+#define SCTP_LOG_SBRESULT 55
+#define SCTP_FR_DUPED 56
+#define SCTP_FR_MARKED_EARLY 57
+#define SCTP_FR_CWND_REPORT 58
+#define SCTP_FR_CWND_REPORT_START 59
+#define SCTP_FR_CWND_REPORT_STOP 60
+#define SCTP_CWND_LOG_FROM_SEND 61
+#define SCTP_CWND_INITIALIZATION 62
+#define SCTP_CWND_LOG_FROM_T3 63
+#define SCTP_CWND_LOG_FROM_SACK 64
+#define SCTP_CWND_LOG_NO_CUMACK 65
+#define SCTP_CWND_LOG_FROM_RESEND 66
+#define SCTP_FR_LOG_CHECK_STRIKE 67
+#define SCTP_SEND_NOW_COMPLETES 68
+#define SCTP_CWND_LOG_FILL_OUTQ_CALLED 69
+#define SCTP_CWND_LOG_FILL_OUTQ_FILLS 70
+#define SCTP_LOG_FREE_SENT 71
+#define SCTP_NAGLE_APPLIED 72
+#define SCTP_NAGLE_SKIPPED 73
+#define SCTP_WAKESND_FROM_SACK 74
+#define SCTP_WAKESND_FROM_FWDTSN 75
+#define SCTP_NOWAKE_FROM_SACK 76
+#define SCTP_CWNDLOG_PRESEND 77
+#define SCTP_CWNDLOG_ENDSEND 78
+#define SCTP_AT_END_OF_SACK 79
+#define SCTP_REASON_FOR_SC 80
+#define SCTP_BLOCK_LOG_INTO_BLKA 81
+#define SCTP_ENTER_USER_RECV 82
+#define SCTP_USER_RECV_SACKS 83
+#define SCTP_SORECV_BLOCKSA 84
+#define SCTP_SORECV_BLOCKSB 85
+#define SCTP_SORECV_DONE 86
+#define SCTP_SACK_RWND_UPDATE 87
+#define SCTP_SORECV_ENTER 88
+#define SCTP_SORECV_ENTERPL 89
+#define SCTP_MBUF_INPUT 90
+#define SCTP_MBUF_IALLOC 91
+#define SCTP_MBUF_IFREE 92
+#define SCTP_MBUF_ICOPY 93
+#define SCTP_MBUF_SPLIT 94
+#define SCTP_SORCV_FREECTL 95
+#define SCTP_SORCV_DOESCPY 96
+#define SCTP_SORCV_DOESLCK 97
+#define SCTP_SORCV_DOESADJ 98
+#define SCTP_SORCV_BOTWHILE 99
+#define SCTP_SORCV_PASSBF 100
+#define SCTP_SORCV_ADJD 101
+#define SCTP_UNKNOWN_MAX 102
+#define SCTP_RANDY_STUFF 103
+#define SCTP_RANDY_STUFF1 104
+#define SCTP_STRMOUT_LOG_ASSIGN 105
+#define SCTP_STRMOUT_LOG_SEND 106
+#define SCTP_FLIGHT_LOG_DOWN_CA 107
+#define SCTP_FLIGHT_LOG_UP 108
+#define SCTP_FLIGHT_LOG_DOWN_GAP 109
+#define SCTP_FLIGHT_LOG_DOWN_RSND 110
+#define SCTP_FLIGHT_LOG_UP_RSND 111
+#define SCTP_FLIGHT_LOG_DOWN_RSND_TO 112
+#define SCTP_FLIGHT_LOG_DOWN_WP 113
+#define SCTP_FLIGHT_LOG_UP_REVOKE 114
+#define SCTP_FLIGHT_LOG_DOWN_PDRP 115
+#define SCTP_FLIGHT_LOG_DOWN_PMTU 116
+#define SCTP_SACK_LOG_NORMAL 117
+#define SCTP_SACK_LOG_EXPRESS 118
+#define SCTP_MAP_TSN_ENTERS 119
+#define SCTP_THRESHOLD_CLEAR 120
+#define SCTP_THRESHOLD_INCR 121
+#define SCTP_FLIGHT_LOG_DWN_WP_FWD 122
+#define SCTP_FWD_TSN_CHECK 123
+#define SCTP_LOG_MAX_TYPES 124
+/*
+ * To turn on various logging, you must first enable 'options KTR' and
+ * you might want to bump the entires 'options KTR_ENTRIES=80000'.
+ * To get something to log you define one of the logging defines.
+ * (see LINT).
+ *
+ * This gets the compile in place, but you still need to turn the
+ * logging flag on too in the sysctl (see in sctp.h).
+ */
+
+#define SCTP_LOG_EVENT_UNKNOWN 0
+#define SCTP_LOG_EVENT_CWND 1
+#define SCTP_LOG_EVENT_BLOCK 2
+#define SCTP_LOG_EVENT_STRM 3
+#define SCTP_LOG_EVENT_FR 4
+#define SCTP_LOG_EVENT_MAP 5
+#define SCTP_LOG_EVENT_MAXBURST 6
+#define SCTP_LOG_EVENT_RWND 7
+#define SCTP_LOG_EVENT_MBCNT 8
+#define SCTP_LOG_EVENT_SACK 9
+#define SCTP_LOG_LOCK_EVENT 10
+#define SCTP_LOG_EVENT_RTT 11
+#define SCTP_LOG_EVENT_SB 12
+#define SCTP_LOG_EVENT_NAGLE 13
+#define SCTP_LOG_EVENT_WAKE 14
+#define SCTP_LOG_MISC_EVENT 15
+#define SCTP_LOG_EVENT_CLOSE 16
+#define SCTP_LOG_EVENT_MBUF 17
+#define SCTP_LOG_CHUNK_PROC 18
+#define SCTP_LOG_ERROR_RET 19
+
+#define SCTP_LOG_MAX_EVENT 20
+
+#define SCTP_LOCK_UNKNOWN 2
+
+/* number of associations by default for zone allocation */
+#define SCTP_MAX_NUM_OF_ASOC 40000
+/* how many addresses per assoc remote and local */
+#define SCTP_SCALE_FOR_ADDR 2
+
+/* default MULTIPLE_ASCONF mode enable(1)/disable(0) value (sysctl) */
+#define SCTP_DEFAULT_MULTIPLE_ASCONFS 0
+
+/*
+ * Threshold for rwnd updates, we have to read (sb_hiwat >>
+ * SCTP_RWND_HIWAT_SHIFT) before we will look to see if we need to send a
+ * window update sack. When we look, we compare the last rwnd we sent vs the
+ * current rwnd. It too must be greater than this value. Using 3 divdes the
+ * hiwat by 8, so for 200k rwnd we need to read 24k. For a 64k rwnd we need
+ * to read 8k. This seems about right.. I hope :-D.. we do set a
+ * min of a MTU on it so if the rwnd is real small we will insist
+ * on a full MTU of 1500 bytes.
+ */
+#define SCTP_RWND_HIWAT_SHIFT 3
+
+/* How much of the rwnd must the
+ * message be taking up to start partial delivery.
+ * We calculate this by shifing the hi_water (recv_win)
+ * left the following .. set to 1, when a message holds
+ * 1/2 the rwnd. If we set it to 2 when a message holds
+ * 1/4 the rwnd...etc..
+ */
+
+#define SCTP_PARTIAL_DELIVERY_SHIFT 1
+
+/*
+ * default HMAC for cookies, etc... use one of the AUTH HMAC id's
+ * SCTP_HMAC is the HMAC_ID to use
+ * SCTP_SIGNATURE_SIZE is the digest length
+ */
+#define SCTP_HMAC SCTP_AUTH_HMAC_ID_SHA1
+#define SCTP_SIGNATURE_SIZE SCTP_AUTH_DIGEST_LEN_SHA1
+#define SCTP_SIGNATURE_ALOC_SIZE SCTP_SIGNATURE_SIZE
+
+/*
+ * the SCTP protocol signature this includes the version number encoded in
+ * the last 4 bits of the signature.
+ */
+#define PROTO_SIGNATURE_A 0x30000000
+#define SCTP_VERSION_NUMBER 0x3
+
+#define MAX_TSN 0xffffffff
+
+/* how many executions every N tick's */
+#define SCTP_ITERATOR_MAX_AT_ONCE 20
+
+/* number of clock ticks between iterator executions */
+#define SCTP_ITERATOR_TICKS 1
+
+/*
+ * option: If you comment out the following you will receive the old behavior
+ * of obeying cwnd for the fast retransmit algorithm. With this defined a FR
+ * happens right away with-out waiting for the flightsize to drop below the
+ * cwnd value (which is reduced by the FR to 1/2 the inflight packets).
+ */
+#define SCTP_IGNORE_CWND_ON_FR 1
+
+/*
+ * Adds implementors guide behavior to only use newest highest update in SACK
+ * gap ack's to figure out if you need to stroke a chunk for FR.
+ */
+#define SCTP_NO_FR_UNLESS_SEGMENT_SMALLER 1
+
+/* default max I can burst out after a fast retransmit, 0 disables it */
+#define SCTP_DEF_MAX_BURST 4
+#define SCTP_DEF_HBMAX_BURST 4
+#define SCTP_DEF_FRMAX_BURST 4
+
+/* RTO calculation flag to say if it
+ * is safe to determine local lan or not.
+ */
+#define SCTP_RTT_FROM_NON_DATA 0
+#define SCTP_RTT_FROM_DATA 1
+
+#define PR_SCTP_UNORDERED_FLAG 0x0001
+
+/* IP hdr (20/40) + 12+2+2 (enet) + sctp common 12 */
+#define SCTP_FIRST_MBUF_RESV 68
+/* Packet transmit states in the sent field */
+#define SCTP_DATAGRAM_UNSENT 0
+#define SCTP_DATAGRAM_SENT 1
+#define SCTP_DATAGRAM_RESEND1 2 /* not used (in code, but may
+ * hit this value) */
+#define SCTP_DATAGRAM_RESEND2 3 /* not used (in code, but may
+ * hit this value) */
+#define SCTP_DATAGRAM_RESEND 4
+#define SCTP_DATAGRAM_ACKED 10010
+#define SCTP_DATAGRAM_MARKED 20010
+#define SCTP_FORWARD_TSN_SKIP 30010
+#define SCTP_DATAGRAM_NR_ACKED 40010
+
+/* chunk output send from locations */
+#define SCTP_OUTPUT_FROM_USR_SEND 0
+#define SCTP_OUTPUT_FROM_T3 1
+#define SCTP_OUTPUT_FROM_INPUT_ERROR 2
+#define SCTP_OUTPUT_FROM_CONTROL_PROC 3
+#define SCTP_OUTPUT_FROM_SACK_TMR 4
+#define SCTP_OUTPUT_FROM_SHUT_TMR 5
+#define SCTP_OUTPUT_FROM_HB_TMR 6
+#define SCTP_OUTPUT_FROM_SHUT_ACK_TMR 7
+#define SCTP_OUTPUT_FROM_ASCONF_TMR 8
+#define SCTP_OUTPUT_FROM_STRRST_TMR 9
+#define SCTP_OUTPUT_FROM_AUTOCLOSE_TMR 10
+#define SCTP_OUTPUT_FROM_EARLY_FR_TMR 11
+#define SCTP_OUTPUT_FROM_STRRST_REQ 12
+#define SCTP_OUTPUT_FROM_USR_RCVD 13
+#define SCTP_OUTPUT_FROM_COOKIE_ACK 14
+#define SCTP_OUTPUT_FROM_DRAIN 15
+#define SCTP_OUTPUT_FROM_CLOSING 16
+#define SCTP_OUTPUT_FROM_SOCKOPT 17
+
+/* SCTP chunk types are moved sctp.h for application (NAT, FW) use */
+
+/* align to 32-bit sizes */
+#define SCTP_SIZE32(x) ((((x) + 3) >> 2) << 2)
+
+#define IS_SCTP_CONTROL(a) (((a)->chunk_type != SCTP_DATA) && ((a)->chunk_type != SCTP_IDATA))
+#define IS_SCTP_DATA(a) (((a)->chunk_type == SCTP_DATA) || ((a)->chunk_type == SCTP_IDATA))
+
+/* SCTP parameter types */
+/*************0x0000 series*************/
+#define SCTP_HEARTBEAT_INFO 0x0001
+#if defined(__Userspace__)
+#define SCTP_CONN_ADDRESS 0x0004
+#endif
+#define SCTP_IPV4_ADDRESS 0x0005
+#define SCTP_IPV6_ADDRESS 0x0006
+#define SCTP_STATE_COOKIE 0x0007
+#define SCTP_UNRECOG_PARAM 0x0008
+#define SCTP_COOKIE_PRESERVE 0x0009
+#define SCTP_HOSTNAME_ADDRESS 0x000b
+#define SCTP_SUPPORTED_ADDRTYPE 0x000c
+
+/* RFC 6525 */
+#define SCTP_STR_RESET_OUT_REQUEST 0x000d
+#define SCTP_STR_RESET_IN_REQUEST 0x000e
+#define SCTP_STR_RESET_TSN_REQUEST 0x000f
+#define SCTP_STR_RESET_RESPONSE 0x0010
+#define SCTP_STR_RESET_ADD_OUT_STREAMS 0x0011
+#define SCTP_STR_RESET_ADD_IN_STREAMS 0x0012
+
+#define SCTP_MAX_RESET_PARAMS 2
+#define SCTP_STREAM_RESET_TSN_DELTA 0x1000
+
+/*************0x4000 series*************/
+
+/*************0x8000 series*************/
+#define SCTP_ECN_CAPABLE 0x8000
+#define SCTP_ZERO_CHECKSUM_ACCEPTABLE 0x8001
+/* RFC 4895 */
+#define SCTP_RANDOM 0x8002
+#define SCTP_CHUNK_LIST 0x8003
+#define SCTP_HMAC_LIST 0x8004
+/* RFC 4820 */
+#define SCTP_PAD 0x8005
+/* RFC 5061 */
+#define SCTP_SUPPORTED_CHUNK_EXT 0x8008
+
+/*************0xC000 series*************/
+#define SCTP_PRSCTP_SUPPORTED 0xc000
+/* RFC 5061 */
+#define SCTP_ADD_IP_ADDRESS 0xc001
+#define SCTP_DEL_IP_ADDRESS 0xc002
+#define SCTP_ERROR_CAUSE_IND 0xc003
+#define SCTP_SET_PRIM_ADDR 0xc004
+#define SCTP_SUCCESS_REPORT 0xc005
+#define SCTP_ULP_ADAPTATION 0xc006
+/* behave-nat-draft */
+#define SCTP_HAS_NAT_SUPPORT 0xc007
+#define SCTP_NAT_VTAGS 0xc008
+
+/* bits for TOS field */
+#define SCTP_ECT0_BIT 0x02
+#define SCTP_ECT1_BIT 0x01
+#define SCTP_CE_BITS 0x03
+
+/* below turns off above */
+#define SCTP_FLEXIBLE_ADDRESS 0x20
+#define SCTP_NO_HEARTBEAT 0x40
+
+/* mask to get sticky */
+#define SCTP_STICKY_OPTIONS_MASK 0x0c
+
+/*
+ * SCTP states for internal state machine
+ */
+#define SCTP_STATE_EMPTY 0x0000
+#define SCTP_STATE_INUSE 0x0001
+#define SCTP_STATE_COOKIE_WAIT 0x0002
+#define SCTP_STATE_COOKIE_ECHOED 0x0004
+#define SCTP_STATE_OPEN 0x0008
+#define SCTP_STATE_SHUTDOWN_SENT 0x0010
+#define SCTP_STATE_SHUTDOWN_RECEIVED 0x0020
+#define SCTP_STATE_SHUTDOWN_ACK_SENT 0x0040
+#define SCTP_STATE_SHUTDOWN_PENDING 0x0080
+#define SCTP_STATE_CLOSED_SOCKET 0x0100
+#define SCTP_STATE_ABOUT_TO_BE_FREED 0x0200
+#define SCTP_STATE_PARTIAL_MSG_LEFT 0x0400
+#define SCTP_STATE_WAS_ABORTED 0x0800
+#define SCTP_STATE_IN_ACCEPT_QUEUE 0x1000
+#define SCTP_STATE_MASK 0x007f
+
+#define SCTP_GET_STATE(_stcb) \
+ ((_stcb)->asoc.state & SCTP_STATE_MASK)
+#define SCTP_SET_STATE(_stcb, _state) \
+ sctp_set_state(_stcb, _state)
+#define SCTP_CLEAR_SUBSTATE(_stcb, _substate) \
+ (_stcb)->asoc.state &= ~(_substate)
+#define SCTP_ADD_SUBSTATE(_stcb, _substate) \
+ sctp_add_substate(_stcb, _substate)
+
+/* SCTP reachability state for each address */
+#define SCTP_ADDR_REACHABLE 0x001
+#define SCTP_ADDR_NO_PMTUD 0x002
+#define SCTP_ADDR_NOHB 0x004
+#define SCTP_ADDR_BEING_DELETED 0x008
+#define SCTP_ADDR_NOT_IN_ASSOC 0x010
+#define SCTP_ADDR_OUT_OF_SCOPE 0x080
+#define SCTP_ADDR_UNCONFIRMED 0x200
+#define SCTP_ADDR_REQ_PRIMARY 0x400
+/* JRS 5/13/07 - Added potentially failed state for CMT PF */
+#define SCTP_ADDR_PF 0x800
+
+/* bound address types (e.g. valid address types to allow) */
+#define SCTP_BOUND_V6 0x01
+#define SCTP_BOUND_V4 0x02
+
+/*
+ * what is the default number of mbufs in a chain I allow before switching to
+ * a cluster
+ */
+#define SCTP_DEFAULT_MBUFS_IN_CHAIN 5
+
+/* How long a cookie lives in milli-seconds */
+#define SCTP_DEFAULT_COOKIE_LIFE 60000
+
+/* Maximum the mapping array will grow to (TSN mapping array) */
+#define SCTP_MAPPING_ARRAY 512
+
+/* size of the initial malloc on the mapping array */
+#define SCTP_INITIAL_MAPPING_ARRAY 16
+/* how much we grow the mapping array each call */
+#define SCTP_MAPPING_ARRAY_INCR 32
+
+/*
+ * Here we define the timer types used by the implementation as arguments in
+ * the set/get timer type calls.
+ */
+#define SCTP_TIMER_INIT 0
+#define SCTP_TIMER_RECV 1
+#define SCTP_TIMER_SEND 2
+#define SCTP_TIMER_HEARTBEAT 3
+#define SCTP_TIMER_PMTU 4
+#define SCTP_TIMER_MAXSHUTDOWN 5
+#define SCTP_TIMER_SIGNATURE 6
+/*
+ * number of timer types in the base SCTP structure used in the set/get and
+ * has the base default.
+ */
+#define SCTP_NUM_TMRS 7
+
+/* timer types */
+#define SCTP_TIMER_TYPE_NONE 0
+#define SCTP_TIMER_TYPE_SEND 1
+#define SCTP_TIMER_TYPE_INIT 2
+#define SCTP_TIMER_TYPE_RECV 3
+#define SCTP_TIMER_TYPE_SHUTDOWN 4
+#define SCTP_TIMER_TYPE_HEARTBEAT 5
+#define SCTP_TIMER_TYPE_COOKIE 6
+#define SCTP_TIMER_TYPE_NEWCOOKIE 7
+#define SCTP_TIMER_TYPE_PATHMTURAISE 8
+#define SCTP_TIMER_TYPE_SHUTDOWNACK 9
+#define SCTP_TIMER_TYPE_ASCONF 10
+#define SCTP_TIMER_TYPE_SHUTDOWNGUARD 11
+#define SCTP_TIMER_TYPE_AUTOCLOSE 12
+#define SCTP_TIMER_TYPE_STRRESET 13
+#define SCTP_TIMER_TYPE_INPKILL 14
+#define SCTP_TIMER_TYPE_ASOCKILL 15
+#define SCTP_TIMER_TYPE_ADDR_WQ 16
+#define SCTP_TIMER_TYPE_PRIM_DELETED 17
+/* add new timers here - and increment LAST */
+#define SCTP_TIMER_TYPE_LAST 18
+
+#define SCTP_IS_TIMER_TYPE_VALID(t) (((t) > SCTP_TIMER_TYPE_NONE) && \
+ ((t) < SCTP_TIMER_TYPE_LAST))
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+/* Number of ticks to run the main timer at in msec */
+#define SCTP_MAIN_TIMER_DEFAULT 10
+
+#endif
+/* max number of TSN's dup'd that I will hold */
+#define SCTP_MAX_DUP_TSNS 20
+
+/*
+ * Here we define the types used when setting the retry amounts.
+ */
+/* How many drop re-attempts we make on INIT/COOKIE-ECHO */
+#define SCTP_RETRY_DROPPED_THRESH 4
+
+/*
+ * Maxmium number of chunks a single association can have on it. Note that
+ * this is a squishy number since the count can run over this if the user
+ * sends a large message down .. the fragmented chunks don't count until
+ * AFTER the message is on queue.. it would be the next send that blocks
+ * things. This number will get tuned up at boot in the sctp_init and use the
+ * number of clusters as a base. This way high bandwidth environments will
+ * not get impacted by the lower bandwidth sending a bunch of 1 byte chunks
+ */
+#define SCTP_ASOC_MAX_CHUNKS_ON_QUEUE 512
+
+/*
+ * Basically the minimum amount of time before I do a early FR. Making this
+ * value to low will cause duplicate retransmissions.
+ */
+#define SCTP_MINFR_MSEC_TIMER 250
+/* The floor this value is allowed to fall to when starting a timer. */
+#define SCTP_MINFR_MSEC_FLOOR 20
+
+/* init timer def = 1 sec */
+#define SCTP_INIT_SEC 1
+
+/* send timer def = 1 seconds */
+#define SCTP_SEND_SEC 1
+
+/* recv timer def = 200ms */
+#define SCTP_RECV_MSEC 200
+
+/* 30 seconds + RTO (in ms) */
+#define SCTP_HB_DEFAULT_MSEC 30000
+
+/*
+ * This is how long a secret lives, NOT how long a cookie lives how many
+ * ticks the current secret will live.
+ */
+#define SCTP_DEFAULT_SECRET_LIFE_SEC 3600
+
+#define SCTP_RTO_UPPER_BOUND (60000) /* 60 sec in ms */
+#define SCTP_RTO_LOWER_BOUND (1000) /* 1 sec is ms */
+#define SCTP_RTO_INITIAL (1000) /* 1 sec in ms */
+
+#define SCTP_INP_KILL_TIMEOUT 20 /* number of ms to retry kill of inpcb */
+#define SCTP_ASOC_KILL_TIMEOUT 10 /* number of ms to retry kill of inpcb */
+
+#define SCTP_DEF_MAX_INIT 8
+#define SCTP_DEF_MAX_SEND 10
+#define SCTP_DEF_MAX_PATH_RTX 5
+#define SCTP_DEF_PATH_PF_THRESHOLD SCTP_DEF_MAX_PATH_RTX
+
+#define SCTP_DEF_PMTU_RAISE_SEC 600 /* 10 min between raise attempts */
+
+/* How many streams I request initially by default */
+#define SCTP_OSTREAM_INITIAL 10
+#define SCTP_ISTREAM_INITIAL 2048
+
+/*
+ * How many smallest_mtu's need to increase before a window update sack is
+ * sent (should be a power of 2).
+ */
+/* Send window update (incr * this > hiwat). Should be a power of 2 */
+#define SCTP_MINIMAL_RWND (4096) /* minimal rwnd */
+
+#define SCTP_ADDRMAX 16
+
+/* SCTP DEBUG Switch parameters */
+#define SCTP_DEBUG_TIMER1 0x00000001
+#define SCTP_DEBUG_TIMER2 0x00000002 /* unused */
+#define SCTP_DEBUG_TIMER3 0x00000004 /* unused */
+#define SCTP_DEBUG_TIMER4 0x00000008
+#define SCTP_DEBUG_OUTPUT1 0x00000010
+#define SCTP_DEBUG_OUTPUT2 0x00000020
+#define SCTP_DEBUG_OUTPUT3 0x00000040
+#define SCTP_DEBUG_OUTPUT4 0x00000080
+#define SCTP_DEBUG_UTIL1 0x00000100
+#define SCTP_DEBUG_UTIL2 0x00000200 /* unused */
+#define SCTP_DEBUG_AUTH1 0x00000400
+#define SCTP_DEBUG_AUTH2 0x00000800 /* unused */
+#define SCTP_DEBUG_INPUT1 0x00001000
+#define SCTP_DEBUG_INPUT2 0x00002000
+#define SCTP_DEBUG_INPUT3 0x00004000
+#define SCTP_DEBUG_INPUT4 0x00008000 /* unused */
+#define SCTP_DEBUG_ASCONF1 0x00010000
+#define SCTP_DEBUG_ASCONF2 0x00020000
+#define SCTP_DEBUG_OUTPUT5 0x00040000 /* unused */
+#define SCTP_DEBUG_XXX 0x00080000 /* unused */
+#define SCTP_DEBUG_PCB1 0x00100000
+#define SCTP_DEBUG_PCB2 0x00200000 /* unused */
+#define SCTP_DEBUG_PCB3 0x00400000
+#define SCTP_DEBUG_PCB4 0x00800000
+#define SCTP_DEBUG_INDATA1 0x01000000
+#define SCTP_DEBUG_INDATA2 0x02000000 /* unused */
+#define SCTP_DEBUG_INDATA3 0x04000000 /* unused */
+#define SCTP_DEBUG_CRCOFFLOAD 0x08000000 /* unused */
+#define SCTP_DEBUG_USRREQ1 0x10000000 /* unused */
+#define SCTP_DEBUG_USRREQ2 0x20000000 /* unused */
+#define SCTP_DEBUG_PEEL1 0x40000000
+#if defined(__Userspace__)
+#define SCTP_DEBUG_USR 0x80000000
+#else
+#define SCTP_DEBUG_XXXXX 0x80000000 /* unused */
+#endif
+#define SCTP_DEBUG_ALL 0x7ff3ffff
+#define SCTP_DEBUG_NOISY 0x00040000
+
+/* What sender needs to see to avoid SWS or we consider peers rwnd 0 */
+#define SCTP_SWS_SENDER_DEF 1420
+
+/*
+ * SWS is scaled to the sb_hiwat of the socket. A value of 2 is hiwat/4, 1
+ * would be hiwat/2 etc.
+ */
+/* What receiver needs to see in sockbuf or we tell peer its 1 */
+#define SCTP_SWS_RECEIVER_DEF 3000
+
+#define SCTP_INITIAL_CWND 4380
+
+#define SCTP_DEFAULT_MTU 1500 /* emergency default MTU */
+/* amount peer is obligated to have in rwnd or I will abort */
+#define SCTP_MIN_RWND 1500
+
+#define SCTP_CHUNK_BUFFER_SIZE 512
+#define SCTP_PARAM_BUFFER_SIZE 512
+
+/* small chunk store for looking at chunk_list in auth */
+#define SCTP_SMALL_CHUNK_STORE 260
+
+#define SCTP_HOW_MANY_SECRETS 2 /* how many secrets I keep */
+
+#define SCTP_NUMBER_OF_SECRETS 8 /* or 8 * 4 = 32 octets */
+#define SCTP_SECRET_SIZE 32 /* number of octets in a 256 bits */
+
+/*
+ * SCTP upper layer notifications
+ */
+#define SCTP_NOTIFY_ASSOC_UP 1
+#define SCTP_NOTIFY_ASSOC_DOWN 2
+#define SCTP_NOTIFY_INTERFACE_DOWN 3
+#define SCTP_NOTIFY_INTERFACE_UP 4
+#define SCTP_NOTIFY_SENT_DG_FAIL 5
+#define SCTP_NOTIFY_UNSENT_DG_FAIL 6
+#define SCTP_NOTIFY_SPECIAL_SP_FAIL 7
+#define SCTP_NOTIFY_ASSOC_LOC_ABORTED 8
+#define SCTP_NOTIFY_ASSOC_REM_ABORTED 9
+#define SCTP_NOTIFY_ASSOC_RESTART 10
+#define SCTP_NOTIFY_PEER_SHUTDOWN 11
+#define SCTP_NOTIFY_ASCONF_ADD_IP 12
+#define SCTP_NOTIFY_ASCONF_DELETE_IP 13
+#define SCTP_NOTIFY_ASCONF_SET_PRIMARY 14
+#define SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION 15
+#define SCTP_NOTIFY_INTERFACE_CONFIRMED 16
+#define SCTP_NOTIFY_STR_RESET_RECV 17
+#define SCTP_NOTIFY_STR_RESET_SEND 18
+#define SCTP_NOTIFY_STR_RESET_FAILED_OUT 19
+#define SCTP_NOTIFY_STR_RESET_FAILED_IN 20
+#define SCTP_NOTIFY_STR_RESET_DENIED_OUT 21
+#define SCTP_NOTIFY_STR_RESET_DENIED_IN 22
+#define SCTP_NOTIFY_STR_RESET_ADD 23
+#define SCTP_NOTIFY_STR_RESET_TSN 24
+#define SCTP_NOTIFY_AUTH_NEW_KEY 25
+#define SCTP_NOTIFY_AUTH_FREE_KEY 26
+#define SCTP_NOTIFY_NO_PEER_AUTH 27
+#define SCTP_NOTIFY_SENDER_DRY 28
+#define SCTP_NOTIFY_REMOTE_ERROR 29
+#define SCTP_NOTIFY_ASSOC_TIMEDOUT 30
+
+/* This is the value for messages that are NOT completely
+ * copied down where we will start to split the message.
+ * So, with our default, we split only if the piece we
+ * want to take will fill up a full MTU (assuming
+ * a 1500 byte MTU).
+ */
+#define SCTP_DEFAULT_SPLIT_POINT_MIN 2904
+
+/* Maximum length of diagnostic information in error causes */
+#if defined(__Userspace__)
+#define SCTP_DIAG_INFO_LEN 256
+#else
+#define SCTP_DIAG_INFO_LEN 128
+#endif
+
+/* ABORT CODES and other tell-tale location
+ * codes are generated by adding the below
+ * to the instance id.
+ */
+
+/* File defines */
+#define SCTP_FROM_SCTP_INPUT 0x10000000
+#define SCTP_FROM_SCTP_PCB 0x20000000
+#define SCTP_FROM_SCTP_INDATA 0x30000000
+#define SCTP_FROM_SCTP_TIMER 0x40000000
+#define SCTP_FROM_SCTP_USRREQ 0x50000000
+#define SCTP_FROM_SCTPUTIL 0x60000000
+#define SCTP_FROM_SCTP6_USRREQ 0x70000000
+#define SCTP_FROM_SCTP_ASCONF 0x80000000
+#define SCTP_FROM_SCTP_OUTPUT 0x90000000
+#define SCTP_FROM_SCTP_PEELOFF 0xa0000000
+#define SCTP_FROM_SCTP_SYSCTL 0xb0000000
+#define SCTP_FROM_SCTP_CC_FUNCTIONS 0xc0000000
+
+/* Location ID's */
+#define SCTP_LOC_1 0x00000001
+#define SCTP_LOC_2 0x00000002
+#define SCTP_LOC_3 0x00000003
+#define SCTP_LOC_4 0x00000004
+#define SCTP_LOC_5 0x00000005
+#define SCTP_LOC_6 0x00000006
+#define SCTP_LOC_7 0x00000007
+#define SCTP_LOC_8 0x00000008
+#define SCTP_LOC_9 0x00000009
+#define SCTP_LOC_10 0x0000000a
+#define SCTP_LOC_11 0x0000000b
+#define SCTP_LOC_12 0x0000000c
+#define SCTP_LOC_13 0x0000000d
+#define SCTP_LOC_14 0x0000000e
+#define SCTP_LOC_15 0x0000000f
+#define SCTP_LOC_16 0x00000010
+#define SCTP_LOC_17 0x00000011
+#define SCTP_LOC_18 0x00000012
+#define SCTP_LOC_19 0x00000013
+#define SCTP_LOC_20 0x00000014
+#define SCTP_LOC_21 0x00000015
+#define SCTP_LOC_22 0x00000016
+#define SCTP_LOC_23 0x00000017
+#define SCTP_LOC_24 0x00000018
+#define SCTP_LOC_25 0x00000019
+#define SCTP_LOC_26 0x0000001a
+#define SCTP_LOC_27 0x0000001b
+#define SCTP_LOC_28 0x0000001c
+#define SCTP_LOC_29 0x0000001d
+#define SCTP_LOC_30 0x0000001e
+#define SCTP_LOC_31 0x0000001f
+#define SCTP_LOC_32 0x00000020
+#define SCTP_LOC_33 0x00000021
+#define SCTP_LOC_34 0x00000022
+#define SCTP_LOC_35 0x00000023
+#define SCTP_LOC_36 0x00000024
+#define SCTP_LOC_37 0x00000025
+
+/* Free assoc codes */
+#define SCTP_NORMAL_PROC 0
+#define SCTP_PCBFREE_NOFORCE 1
+#define SCTP_PCBFREE_FORCE 2
+
+/* From codes for adding addresses */
+#define SCTP_ADDR_IS_CONFIRMED 8
+#define SCTP_ADDR_DYNAMIC_ADDED 6
+#define SCTP_IN_COOKIE_PROC 100
+#define SCTP_ALLOC_ASOC 1
+#define SCTP_LOAD_ADDR_2 2
+#define SCTP_LOAD_ADDR_3 3
+#define SCTP_LOAD_ADDR_4 4
+#define SCTP_LOAD_ADDR_5 5
+
+#define SCTP_DONOT_SETSCOPE 0
+#define SCTP_DO_SETSCOPE 1
+
+/* This value determines the default for when
+ * we try to add more on the send queue., if
+ * there is room. This prevents us from cycling
+ * into the copy_resume routine to often if
+ * we have not got enough space to add a decent
+ * enough size message. Note that if we have enough
+ * space to complete the message copy we will always
+ * add to the message, no matter what the size. Its
+ * only when we reach the point that we have some left
+ * to add, there is only room for part of it that we
+ * will use this threshold. Its also a sysctl.
+ */
+#define SCTP_DEFAULT_ADD_MORE 1452
+
+#ifndef SCTP_PCBHASHSIZE
+/* default number of association hash buckets in each endpoint */
+#define SCTP_PCBHASHSIZE 256
+#endif
+#ifndef SCTP_TCBHASHSIZE
+#define SCTP_TCBHASHSIZE 1024
+#endif
+
+#ifndef SCTP_CHUNKQUEUE_SCALE
+#define SCTP_CHUNKQUEUE_SCALE 10
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+/* clock variance is 1 ms */
+#define SCTP_CLOCK_GRANULARITY 1
+#else
+/* clock variance is 10 ms */
+#define SCTP_CLOCK_GRANULARITY 10
+#endif
+#define IP_HDR_SIZE 40 /* we use the size of a IP6 header here this
+ * detracts a small amount for ipv4 but it
+ * simplifies the ipv6 addition */
+
+/* Argument magic number for sctp_inpcb_free() */
+
+/* third argument */
+#define SCTP_CALLED_DIRECTLY_NOCMPSET 0
+#define SCTP_CALLED_AFTER_CMPSET_OFCLOSE 1
+#define SCTP_CALLED_FROM_INPKILL_TIMER 2
+/* second argument */
+#define SCTP_FREE_SHOULD_USE_ABORT 1
+#define SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE 0
+
+#ifndef IPPROTO_SCTP
+#define IPPROTO_SCTP 132 /* the Official IANA number :-) */
+#endif /* !IPPROTO_SCTP */
+
+#define SCTP_MAX_DATA_BUNDLING 256
+
+/* modular comparison */
+/* See RFC 1982 for details. */
+#define SCTP_UINT16_GT(a, b) (((a < b) && ((uint16_t)(b - a) > (1U<<15))) || \
+ ((a > b) && ((uint16_t)(a - b) < (1U<<15))))
+#define SCTP_UINT16_GE(a, b) (SCTP_UINT16_GT(a, b) || (a == b))
+#define SCTP_UINT32_GT(a, b) (((a < b) && ((uint32_t)(b - a) > (1U<<31))) || \
+ ((a > b) && ((uint32_t)(a - b) < (1U<<31))))
+#define SCTP_UINT32_GE(a, b) (SCTP_UINT32_GT(a, b) || (a == b))
+
+#define SCTP_SSN_GT(a, b) SCTP_UINT16_GT(a, b)
+#define SCTP_SSN_GE(a, b) SCTP_UINT16_GE(a, b)
+#define SCTP_TSN_GT(a, b) SCTP_UINT32_GT(a, b)
+#define SCTP_TSN_GE(a, b) SCTP_UINT32_GE(a, b)
+#define SCTP_MID_GT(i, a, b) (((i) == 1) ? SCTP_UINT32_GT(a, b) : SCTP_UINT16_GT((uint16_t)a, (uint16_t)b))
+#define SCTP_MID_GE(i, a, b) (((i) == 1) ? SCTP_UINT32_GE(a, b) : SCTP_UINT16_GE((uint16_t)a, (uint16_t)b))
+#define SCTP_MID_EQ(i, a, b) (((i) == 1) ? a == b : (uint16_t)a == (uint16_t)b)
+
+/* Mapping array manipulation routines */
+#define SCTP_IS_TSN_PRESENT(arry, gap) ((arry[(gap >> 3)] >> (gap & 0x07)) & 0x01)
+#define SCTP_SET_TSN_PRESENT(arry, gap) (arry[(gap >> 3)] |= (0x01 << ((gap & 0x07))))
+#define SCTP_UNSET_TSN_PRESENT(arry, gap) (arry[(gap >> 3)] &= ((~(0x01 << ((gap & 0x07)))) & 0xff))
+#define SCTP_CALC_TSN_TO_GAP(gap, tsn, mapping_tsn) do { \
+ if (tsn >= mapping_tsn) { \
+ gap = tsn - mapping_tsn; \
+ } else { \
+ gap = (MAX_TSN - mapping_tsn) + tsn + 1; \
+ } \
+ } while (0)
+
+#define SCTP_RETRAN_DONE -1
+#define SCTP_RETRAN_EXIT -2
+
+/*
+ * This value defines the number of vtag block time wait entry's per list
+ * element. Each entry will take 2 4 byte ints (and of course the overhead
+ * of the next pointer as well). Using 15 as an example will yield * ((8 *
+ * 15) + 8) or 128 bytes of overhead for each timewait block that gets
+ * initialized. Increasing it to 31 would yield 256 bytes per block.
+ */
+#define SCTP_NUMBER_IN_VTAG_BLOCK 15
+/*
+ * If we use the STACK option, we have an array of this size head pointers.
+ * This array is mod'd the with the size to find which bucket and then all
+ * entries must be searched to see if the tag is in timed wait. If so we
+ * reject it.
+ */
+#define SCTP_STACK_VTAG_HASH_SIZE 32
+
+/*
+ * Number of seconds of time wait for a vtag.
+ */
+#define SCTP_TIME_WAIT 60
+
+/* How many micro seconds is the cutoff from
+ * local lan type rtt's
+ */
+ /*
+ * We allow 900us for the rtt.
+ */
+#define SCTP_LOCAL_LAN_RTT 900
+#define SCTP_LAN_UNKNOWN 0
+#define SCTP_LAN_LOCAL 1
+#define SCTP_LAN_INTERNET 2
+
+#define SCTP_SEND_BUFFER_SPLITTING 0x00000001
+#define SCTP_RECV_BUFFER_SPLITTING 0x00000002
+
+/* The system retains a cache of free chunks such to
+ * cut down on calls the memory allocation system. There
+ * is a per association limit of free items and a overall
+ * system limit. If either one gets hit then the resource
+ * stops being cached.
+ */
+
+#define SCTP_DEF_ASOC_RESC_LIMIT 10
+#define SCTP_DEF_SYSTEM_RESC_LIMIT 1000
+
+/*-
+ * defines for socket lock states.
+ * Used by __APPLE__
+ */
+#define SCTP_SO_LOCKED 1
+#define SCTP_SO_NOT_LOCKED 0
+
+/*-
+ * For address locks, do we hold the lock?
+ */
+#define SCTP_ADDR_LOCKED 1
+#define SCTP_ADDR_NOT_LOCKED 0
+
+#define IN4_ISPRIVATE_ADDRESS(a) \
+ ((((uint8_t *)&(a)->s_addr)[0] == 10) || \
+ ((((uint8_t *)&(a)->s_addr)[0] == 172) && \
+ (((uint8_t *)&(a)->s_addr)[1] >= 16) && \
+ (((uint8_t *)&(a)->s_addr)[1] <= 32)) || \
+ ((((uint8_t *)&(a)->s_addr)[0] == 192) && \
+ (((uint8_t *)&(a)->s_addr)[1] == 168)))
+
+#define IN4_ISLOOPBACK_ADDRESS(a) \
+ (((uint8_t *)&(a)->s_addr)[0] == 127)
+
+#define IN4_ISLINKLOCAL_ADDRESS(a) \
+ ((((uint8_t *)&(a)->s_addr)[0] == 169) && \
+ (((uint8_t *)&(a)->s_addr)[1] == 254))
+
+/* Maximum size of optval for IPPROTO_SCTP level socket options. */
+#define SCTP_SOCKET_OPTION_LIMIT (64 * 1024)
+
+#if defined(__Userspace__)
+#if defined(_WIN32)
+#define SCTP_GETTIME_TIMEVAL(x) getwintimeofday(x)
+#define SCTP_GETPTIME_TIMEVAL(x) getwintimeofday(x) /* this doesn't seem to ever be used.. */
+#else
+#define SCTP_GETTIME_TIMEVAL(x) gettimeofday(x, NULL)
+#define SCTP_GETPTIME_TIMEVAL(x) gettimeofday(x, NULL)
+#endif
+#endif
+#if defined(_KERNEL)
+#define SCTP_GETTIME_TIMEVAL(x) (getmicrouptime(x))
+#define SCTP_GETPTIME_TIMEVAL(x) (microuptime(x))
+#endif
+
+#if defined(_KERNEL) || defined(__Userspace__)
+#define sctp_sowwakeup(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ sctp_pcb_add_flags(inp, SCTP_PCB_FLAGS_WAKEOUTPUT); \
+ } else { \
+ sowwakeup(so); \
+ } \
+} while (0)
+
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+#define sctp_sowwakeup_locked(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ sctp_pcb_add_flags(inp, SCTP_PCB_FLAGS_WAKEOUTPUT); \
+ SOCKBUF_UNLOCK(&((so)->so_snd)); \
+ } else { \
+ sowwakeup_locked(so); \
+ } \
+} while (0)
+#else
+#define sctp_sowwakeup_locked(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ sctp_pcb_add_flags(inp, SCTP_PCB_FLAGS_WAKEOUTPUT); \
+ SOCKBUF_UNLOCK(&((so)->so_snd)); \
+ } else { \
+ sowwakeup(so); \
+ } \
+} while (0)
+#endif
+
+#define sctp_sorwakeup(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ sctp_pcb_add_flags(inp, SCTP_PCB_FLAGS_WAKEINPUT); \
+ } else { \
+ sorwakeup(so); \
+ } \
+} while (0)
+
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+#define sctp_sorwakeup_locked(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ sctp_pcb_add_flags(inp, SCTP_PCB_FLAGS_WAKEINPUT); \
+ SOCKBUF_UNLOCK(&((so)->so_rcv)); \
+ } else { \
+ sorwakeup_locked(so); \
+ } \
+} while (0)
+#else
+
+#define sctp_sorwakeup_locked(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ sctp_pcb_add_flags(inp, SCTP_PCB_FLAGS_WAKEINPUT); \
+ SOCKBUF_UNLOCK(&((so)->so_rcv)); \
+ } else { \
+ sorwakeup(so); \
+ } \
+} while (0)
+#endif
+
+#endif /* _KERNEL || __Userspace__*/
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_crc32.c b/netwerk/sctp/src/netinet/sctp_crc32.c
new file mode 100644
index 0000000000..0370fb1381
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_crc32.c
@@ -0,0 +1,819 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+#include "opt_sctp.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/gsb_crc32.h>
+#include <sys/mbuf.h>
+
+#include <netinet/sctp.h>
+#include <netinet/sctp_crc32.h>
+#if defined(SCTP) || defined(SCTP_SUPPORT)
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_pcb.h>
+#endif
+#else
+#include <netinet/sctp_os.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_crc32.h>
+#include <netinet/sctp_pcb.h>
+#endif
+
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+/**
+ *
+ * Routine Description:
+ *
+ * Computes the CRC32c checksum for the specified buffer using the slicing by 8
+ * algorithm over 64 bit quantities.
+ *
+ * Arguments:
+ *
+ * p_running_crc - pointer to the initial or final remainder value
+ * used in CRC computations. It should be set to
+ * non-NULL if the mode argument is equal to CONT or END
+ * p_buf - the packet buffer where crc computations are being performed
+ * length - the length of p_buf in bytes
+ * init_bytes - the number of initial bytes that need to be procesed before
+ * aligning p_buf to multiples of 4 bytes
+ * mode - can be any of the following: BEGIN, CONT, END, BODY, ALIGN
+ *
+ * Return value:
+ *
+ * The computed CRC32c value
+ */
+
+
+/*
+ * Copyright (c) 2004-2006 Intel Corporation - All Rights Reserved
+ *
+ *
+ * This software program is licensed subject to the BSD License, available at
+ * http://www.opensource.org/licenses/bsd-license.html.
+ *
+ * Abstract:
+ *
+ * Tables for software CRC generation
+ */
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o32[256] =
+{
+ 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
+ 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
+ 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
+ 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
+ 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
+ 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
+ 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
+ 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
+ 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
+ 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
+ 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
+ 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
+ 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
+ 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
+ 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
+ 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
+ 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
+ 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
+ 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
+ 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
+ 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
+ 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
+ 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
+ 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
+ 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
+ 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
+ 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
+ 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
+ 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
+ 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
+ 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
+ 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o32
+ */
+
+
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o40[256] =
+{
+ 0x00000000, 0x13A29877, 0x274530EE, 0x34E7A899, 0x4E8A61DC, 0x5D28F9AB, 0x69CF5132, 0x7A6DC945,
+ 0x9D14C3B8, 0x8EB65BCF, 0xBA51F356, 0xA9F36B21, 0xD39EA264, 0xC03C3A13, 0xF4DB928A, 0xE7790AFD,
+ 0x3FC5F181, 0x2C6769F6, 0x1880C16F, 0x0B225918, 0x714F905D, 0x62ED082A, 0x560AA0B3, 0x45A838C4,
+ 0xA2D13239, 0xB173AA4E, 0x859402D7, 0x96369AA0, 0xEC5B53E5, 0xFFF9CB92, 0xCB1E630B, 0xD8BCFB7C,
+ 0x7F8BE302, 0x6C297B75, 0x58CED3EC, 0x4B6C4B9B, 0x310182DE, 0x22A31AA9, 0x1644B230, 0x05E62A47,
+ 0xE29F20BA, 0xF13DB8CD, 0xC5DA1054, 0xD6788823, 0xAC154166, 0xBFB7D911, 0x8B507188, 0x98F2E9FF,
+ 0x404E1283, 0x53EC8AF4, 0x670B226D, 0x74A9BA1A, 0x0EC4735F, 0x1D66EB28, 0x298143B1, 0x3A23DBC6,
+ 0xDD5AD13B, 0xCEF8494C, 0xFA1FE1D5, 0xE9BD79A2, 0x93D0B0E7, 0x80722890, 0xB4958009, 0xA737187E,
+ 0xFF17C604, 0xECB55E73, 0xD852F6EA, 0xCBF06E9D, 0xB19DA7D8, 0xA23F3FAF, 0x96D89736, 0x857A0F41,
+ 0x620305BC, 0x71A19DCB, 0x45463552, 0x56E4AD25, 0x2C896460, 0x3F2BFC17, 0x0BCC548E, 0x186ECCF9,
+ 0xC0D23785, 0xD370AFF2, 0xE797076B, 0xF4359F1C, 0x8E585659, 0x9DFACE2E, 0xA91D66B7, 0xBABFFEC0,
+ 0x5DC6F43D, 0x4E646C4A, 0x7A83C4D3, 0x69215CA4, 0x134C95E1, 0x00EE0D96, 0x3409A50F, 0x27AB3D78,
+ 0x809C2506, 0x933EBD71, 0xA7D915E8, 0xB47B8D9F, 0xCE1644DA, 0xDDB4DCAD, 0xE9537434, 0xFAF1EC43,
+ 0x1D88E6BE, 0x0E2A7EC9, 0x3ACDD650, 0x296F4E27, 0x53028762, 0x40A01F15, 0x7447B78C, 0x67E52FFB,
+ 0xBF59D487, 0xACFB4CF0, 0x981CE469, 0x8BBE7C1E, 0xF1D3B55B, 0xE2712D2C, 0xD69685B5, 0xC5341DC2,
+ 0x224D173F, 0x31EF8F48, 0x050827D1, 0x16AABFA6, 0x6CC776E3, 0x7F65EE94, 0x4B82460D, 0x5820DE7A,
+ 0xFBC3FAF9, 0xE861628E, 0xDC86CA17, 0xCF245260, 0xB5499B25, 0xA6EB0352, 0x920CABCB, 0x81AE33BC,
+ 0x66D73941, 0x7575A136, 0x419209AF, 0x523091D8, 0x285D589D, 0x3BFFC0EA, 0x0F186873, 0x1CBAF004,
+ 0xC4060B78, 0xD7A4930F, 0xE3433B96, 0xF0E1A3E1, 0x8A8C6AA4, 0x992EF2D3, 0xADC95A4A, 0xBE6BC23D,
+ 0x5912C8C0, 0x4AB050B7, 0x7E57F82E, 0x6DF56059, 0x1798A91C, 0x043A316B, 0x30DD99F2, 0x237F0185,
+ 0x844819FB, 0x97EA818C, 0xA30D2915, 0xB0AFB162, 0xCAC27827, 0xD960E050, 0xED8748C9, 0xFE25D0BE,
+ 0x195CDA43, 0x0AFE4234, 0x3E19EAAD, 0x2DBB72DA, 0x57D6BB9F, 0x447423E8, 0x70938B71, 0x63311306,
+ 0xBB8DE87A, 0xA82F700D, 0x9CC8D894, 0x8F6A40E3, 0xF50789A6, 0xE6A511D1, 0xD242B948, 0xC1E0213F,
+ 0x26992BC2, 0x353BB3B5, 0x01DC1B2C, 0x127E835B, 0x68134A1E, 0x7BB1D269, 0x4F567AF0, 0x5CF4E287,
+ 0x04D43CFD, 0x1776A48A, 0x23910C13, 0x30339464, 0x4A5E5D21, 0x59FCC556, 0x6D1B6DCF, 0x7EB9F5B8,
+ 0x99C0FF45, 0x8A626732, 0xBE85CFAB, 0xAD2757DC, 0xD74A9E99, 0xC4E806EE, 0xF00FAE77, 0xE3AD3600,
+ 0x3B11CD7C, 0x28B3550B, 0x1C54FD92, 0x0FF665E5, 0x759BACA0, 0x663934D7, 0x52DE9C4E, 0x417C0439,
+ 0xA6050EC4, 0xB5A796B3, 0x81403E2A, 0x92E2A65D, 0xE88F6F18, 0xFB2DF76F, 0xCFCA5FF6, 0xDC68C781,
+ 0x7B5FDFFF, 0x68FD4788, 0x5C1AEF11, 0x4FB87766, 0x35D5BE23, 0x26772654, 0x12908ECD, 0x013216BA,
+ 0xE64B1C47, 0xF5E98430, 0xC10E2CA9, 0xD2ACB4DE, 0xA8C17D9B, 0xBB63E5EC, 0x8F844D75, 0x9C26D502,
+ 0x449A2E7E, 0x5738B609, 0x63DF1E90, 0x707D86E7, 0x0A104FA2, 0x19B2D7D5, 0x2D557F4C, 0x3EF7E73B,
+ 0xD98EEDC6, 0xCA2C75B1, 0xFECBDD28, 0xED69455F, 0x97048C1A, 0x84A6146D, 0xB041BCF4, 0xA3E32483
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o40
+ */
+
+
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o48[256] =
+{
+ 0x00000000, 0xA541927E, 0x4F6F520D, 0xEA2EC073, 0x9EDEA41A, 0x3B9F3664, 0xD1B1F617, 0x74F06469,
+ 0x38513EC5, 0x9D10ACBB, 0x773E6CC8, 0xD27FFEB6, 0xA68F9ADF, 0x03CE08A1, 0xE9E0C8D2, 0x4CA15AAC,
+ 0x70A27D8A, 0xD5E3EFF4, 0x3FCD2F87, 0x9A8CBDF9, 0xEE7CD990, 0x4B3D4BEE, 0xA1138B9D, 0x045219E3,
+ 0x48F3434F, 0xEDB2D131, 0x079C1142, 0xA2DD833C, 0xD62DE755, 0x736C752B, 0x9942B558, 0x3C032726,
+ 0xE144FB14, 0x4405696A, 0xAE2BA919, 0x0B6A3B67, 0x7F9A5F0E, 0xDADBCD70, 0x30F50D03, 0x95B49F7D,
+ 0xD915C5D1, 0x7C5457AF, 0x967A97DC, 0x333B05A2, 0x47CB61CB, 0xE28AF3B5, 0x08A433C6, 0xADE5A1B8,
+ 0x91E6869E, 0x34A714E0, 0xDE89D493, 0x7BC846ED, 0x0F382284, 0xAA79B0FA, 0x40577089, 0xE516E2F7,
+ 0xA9B7B85B, 0x0CF62A25, 0xE6D8EA56, 0x43997828, 0x37691C41, 0x92288E3F, 0x78064E4C, 0xDD47DC32,
+ 0xC76580D9, 0x622412A7, 0x880AD2D4, 0x2D4B40AA, 0x59BB24C3, 0xFCFAB6BD, 0x16D476CE, 0xB395E4B0,
+ 0xFF34BE1C, 0x5A752C62, 0xB05BEC11, 0x151A7E6F, 0x61EA1A06, 0xC4AB8878, 0x2E85480B, 0x8BC4DA75,
+ 0xB7C7FD53, 0x12866F2D, 0xF8A8AF5E, 0x5DE93D20, 0x29195949, 0x8C58CB37, 0x66760B44, 0xC337993A,
+ 0x8F96C396, 0x2AD751E8, 0xC0F9919B, 0x65B803E5, 0x1148678C, 0xB409F5F2, 0x5E273581, 0xFB66A7FF,
+ 0x26217BCD, 0x8360E9B3, 0x694E29C0, 0xCC0FBBBE, 0xB8FFDFD7, 0x1DBE4DA9, 0xF7908DDA, 0x52D11FA4,
+ 0x1E704508, 0xBB31D776, 0x511F1705, 0xF45E857B, 0x80AEE112, 0x25EF736C, 0xCFC1B31F, 0x6A802161,
+ 0x56830647, 0xF3C29439, 0x19EC544A, 0xBCADC634, 0xC85DA25D, 0x6D1C3023, 0x8732F050, 0x2273622E,
+ 0x6ED23882, 0xCB93AAFC, 0x21BD6A8F, 0x84FCF8F1, 0xF00C9C98, 0x554D0EE6, 0xBF63CE95, 0x1A225CEB,
+ 0x8B277743, 0x2E66E53D, 0xC448254E, 0x6109B730, 0x15F9D359, 0xB0B84127, 0x5A968154, 0xFFD7132A,
+ 0xB3764986, 0x1637DBF8, 0xFC191B8B, 0x595889F5, 0x2DA8ED9C, 0x88E97FE2, 0x62C7BF91, 0xC7862DEF,
+ 0xFB850AC9, 0x5EC498B7, 0xB4EA58C4, 0x11ABCABA, 0x655BAED3, 0xC01A3CAD, 0x2A34FCDE, 0x8F756EA0,
+ 0xC3D4340C, 0x6695A672, 0x8CBB6601, 0x29FAF47F, 0x5D0A9016, 0xF84B0268, 0x1265C21B, 0xB7245065,
+ 0x6A638C57, 0xCF221E29, 0x250CDE5A, 0x804D4C24, 0xF4BD284D, 0x51FCBA33, 0xBBD27A40, 0x1E93E83E,
+ 0x5232B292, 0xF77320EC, 0x1D5DE09F, 0xB81C72E1, 0xCCEC1688, 0x69AD84F6, 0x83834485, 0x26C2D6FB,
+ 0x1AC1F1DD, 0xBF8063A3, 0x55AEA3D0, 0xF0EF31AE, 0x841F55C7, 0x215EC7B9, 0xCB7007CA, 0x6E3195B4,
+ 0x2290CF18, 0x87D15D66, 0x6DFF9D15, 0xC8BE0F6B, 0xBC4E6B02, 0x190FF97C, 0xF321390F, 0x5660AB71,
+ 0x4C42F79A, 0xE90365E4, 0x032DA597, 0xA66C37E9, 0xD29C5380, 0x77DDC1FE, 0x9DF3018D, 0x38B293F3,
+ 0x7413C95F, 0xD1525B21, 0x3B7C9B52, 0x9E3D092C, 0xEACD6D45, 0x4F8CFF3B, 0xA5A23F48, 0x00E3AD36,
+ 0x3CE08A10, 0x99A1186E, 0x738FD81D, 0xD6CE4A63, 0xA23E2E0A, 0x077FBC74, 0xED517C07, 0x4810EE79,
+ 0x04B1B4D5, 0xA1F026AB, 0x4BDEE6D8, 0xEE9F74A6, 0x9A6F10CF, 0x3F2E82B1, 0xD50042C2, 0x7041D0BC,
+ 0xAD060C8E, 0x08479EF0, 0xE2695E83, 0x4728CCFD, 0x33D8A894, 0x96993AEA, 0x7CB7FA99, 0xD9F668E7,
+ 0x9557324B, 0x3016A035, 0xDA386046, 0x7F79F238, 0x0B899651, 0xAEC8042F, 0x44E6C45C, 0xE1A75622,
+ 0xDDA47104, 0x78E5E37A, 0x92CB2309, 0x378AB177, 0x437AD51E, 0xE63B4760, 0x0C158713, 0xA954156D,
+ 0xE5F54FC1, 0x40B4DDBF, 0xAA9A1DCC, 0x0FDB8FB2, 0x7B2BEBDB, 0xDE6A79A5, 0x3444B9D6, 0x91052BA8
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o48
+ */
+
+
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o56[256] =
+{
+ 0x00000000, 0xDD45AAB8, 0xBF672381, 0x62228939, 0x7B2231F3, 0xA6679B4B, 0xC4451272, 0x1900B8CA,
+ 0xF64463E6, 0x2B01C95E, 0x49234067, 0x9466EADF, 0x8D665215, 0x5023F8AD, 0x32017194, 0xEF44DB2C,
+ 0xE964B13D, 0x34211B85, 0x560392BC, 0x8B463804, 0x924680CE, 0x4F032A76, 0x2D21A34F, 0xF06409F7,
+ 0x1F20D2DB, 0xC2657863, 0xA047F15A, 0x7D025BE2, 0x6402E328, 0xB9474990, 0xDB65C0A9, 0x06206A11,
+ 0xD725148B, 0x0A60BE33, 0x6842370A, 0xB5079DB2, 0xAC072578, 0x71428FC0, 0x136006F9, 0xCE25AC41,
+ 0x2161776D, 0xFC24DDD5, 0x9E0654EC, 0x4343FE54, 0x5A43469E, 0x8706EC26, 0xE524651F, 0x3861CFA7,
+ 0x3E41A5B6, 0xE3040F0E, 0x81268637, 0x5C632C8F, 0x45639445, 0x98263EFD, 0xFA04B7C4, 0x27411D7C,
+ 0xC805C650, 0x15406CE8, 0x7762E5D1, 0xAA274F69, 0xB327F7A3, 0x6E625D1B, 0x0C40D422, 0xD1057E9A,
+ 0xABA65FE7, 0x76E3F55F, 0x14C17C66, 0xC984D6DE, 0xD0846E14, 0x0DC1C4AC, 0x6FE34D95, 0xB2A6E72D,
+ 0x5DE23C01, 0x80A796B9, 0xE2851F80, 0x3FC0B538, 0x26C00DF2, 0xFB85A74A, 0x99A72E73, 0x44E284CB,
+ 0x42C2EEDA, 0x9F874462, 0xFDA5CD5B, 0x20E067E3, 0x39E0DF29, 0xE4A57591, 0x8687FCA8, 0x5BC25610,
+ 0xB4868D3C, 0x69C32784, 0x0BE1AEBD, 0xD6A40405, 0xCFA4BCCF, 0x12E11677, 0x70C39F4E, 0xAD8635F6,
+ 0x7C834B6C, 0xA1C6E1D4, 0xC3E468ED, 0x1EA1C255, 0x07A17A9F, 0xDAE4D027, 0xB8C6591E, 0x6583F3A6,
+ 0x8AC7288A, 0x57828232, 0x35A00B0B, 0xE8E5A1B3, 0xF1E51979, 0x2CA0B3C1, 0x4E823AF8, 0x93C79040,
+ 0x95E7FA51, 0x48A250E9, 0x2A80D9D0, 0xF7C57368, 0xEEC5CBA2, 0x3380611A, 0x51A2E823, 0x8CE7429B,
+ 0x63A399B7, 0xBEE6330F, 0xDCC4BA36, 0x0181108E, 0x1881A844, 0xC5C402FC, 0xA7E68BC5, 0x7AA3217D,
+ 0x52A0C93F, 0x8FE56387, 0xEDC7EABE, 0x30824006, 0x2982F8CC, 0xF4C75274, 0x96E5DB4D, 0x4BA071F5,
+ 0xA4E4AAD9, 0x79A10061, 0x1B838958, 0xC6C623E0, 0xDFC69B2A, 0x02833192, 0x60A1B8AB, 0xBDE41213,
+ 0xBBC47802, 0x6681D2BA, 0x04A35B83, 0xD9E6F13B, 0xC0E649F1, 0x1DA3E349, 0x7F816A70, 0xA2C4C0C8,
+ 0x4D801BE4, 0x90C5B15C, 0xF2E73865, 0x2FA292DD, 0x36A22A17, 0xEBE780AF, 0x89C50996, 0x5480A32E,
+ 0x8585DDB4, 0x58C0770C, 0x3AE2FE35, 0xE7A7548D, 0xFEA7EC47, 0x23E246FF, 0x41C0CFC6, 0x9C85657E,
+ 0x73C1BE52, 0xAE8414EA, 0xCCA69DD3, 0x11E3376B, 0x08E38FA1, 0xD5A62519, 0xB784AC20, 0x6AC10698,
+ 0x6CE16C89, 0xB1A4C631, 0xD3864F08, 0x0EC3E5B0, 0x17C35D7A, 0xCA86F7C2, 0xA8A47EFB, 0x75E1D443,
+ 0x9AA50F6F, 0x47E0A5D7, 0x25C22CEE, 0xF8878656, 0xE1873E9C, 0x3CC29424, 0x5EE01D1D, 0x83A5B7A5,
+ 0xF90696D8, 0x24433C60, 0x4661B559, 0x9B241FE1, 0x8224A72B, 0x5F610D93, 0x3D4384AA, 0xE0062E12,
+ 0x0F42F53E, 0xD2075F86, 0xB025D6BF, 0x6D607C07, 0x7460C4CD, 0xA9256E75, 0xCB07E74C, 0x16424DF4,
+ 0x106227E5, 0xCD278D5D, 0xAF050464, 0x7240AEDC, 0x6B401616, 0xB605BCAE, 0xD4273597, 0x09629F2F,
+ 0xE6264403, 0x3B63EEBB, 0x59416782, 0x8404CD3A, 0x9D0475F0, 0x4041DF48, 0x22635671, 0xFF26FCC9,
+ 0x2E238253, 0xF36628EB, 0x9144A1D2, 0x4C010B6A, 0x5501B3A0, 0x88441918, 0xEA669021, 0x37233A99,
+ 0xD867E1B5, 0x05224B0D, 0x6700C234, 0xBA45688C, 0xA345D046, 0x7E007AFE, 0x1C22F3C7, 0xC167597F,
+ 0xC747336E, 0x1A0299D6, 0x782010EF, 0xA565BA57, 0xBC65029D, 0x6120A825, 0x0302211C, 0xDE478BA4,
+ 0x31035088, 0xEC46FA30, 0x8E647309, 0x5321D9B1, 0x4A21617B, 0x9764CBC3, 0xF54642FA, 0x2803E842
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o56
+ */
+
+
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o64[256] =
+{
+ 0x00000000, 0x38116FAC, 0x7022DF58, 0x4833B0F4, 0xE045BEB0, 0xD854D11C, 0x906761E8, 0xA8760E44,
+ 0xC5670B91, 0xFD76643D, 0xB545D4C9, 0x8D54BB65, 0x2522B521, 0x1D33DA8D, 0x55006A79, 0x6D1105D5,
+ 0x8F2261D3, 0xB7330E7F, 0xFF00BE8B, 0xC711D127, 0x6F67DF63, 0x5776B0CF, 0x1F45003B, 0x27546F97,
+ 0x4A456A42, 0x725405EE, 0x3A67B51A, 0x0276DAB6, 0xAA00D4F2, 0x9211BB5E, 0xDA220BAA, 0xE2336406,
+ 0x1BA8B557, 0x23B9DAFB, 0x6B8A6A0F, 0x539B05A3, 0xFBED0BE7, 0xC3FC644B, 0x8BCFD4BF, 0xB3DEBB13,
+ 0xDECFBEC6, 0xE6DED16A, 0xAEED619E, 0x96FC0E32, 0x3E8A0076, 0x069B6FDA, 0x4EA8DF2E, 0x76B9B082,
+ 0x948AD484, 0xAC9BBB28, 0xE4A80BDC, 0xDCB96470, 0x74CF6A34, 0x4CDE0598, 0x04EDB56C, 0x3CFCDAC0,
+ 0x51EDDF15, 0x69FCB0B9, 0x21CF004D, 0x19DE6FE1, 0xB1A861A5, 0x89B90E09, 0xC18ABEFD, 0xF99BD151,
+ 0x37516AAE, 0x0F400502, 0x4773B5F6, 0x7F62DA5A, 0xD714D41E, 0xEF05BBB2, 0xA7360B46, 0x9F2764EA,
+ 0xF236613F, 0xCA270E93, 0x8214BE67, 0xBA05D1CB, 0x1273DF8F, 0x2A62B023, 0x625100D7, 0x5A406F7B,
+ 0xB8730B7D, 0x806264D1, 0xC851D425, 0xF040BB89, 0x5836B5CD, 0x6027DA61, 0x28146A95, 0x10050539,
+ 0x7D1400EC, 0x45056F40, 0x0D36DFB4, 0x3527B018, 0x9D51BE5C, 0xA540D1F0, 0xED736104, 0xD5620EA8,
+ 0x2CF9DFF9, 0x14E8B055, 0x5CDB00A1, 0x64CA6F0D, 0xCCBC6149, 0xF4AD0EE5, 0xBC9EBE11, 0x848FD1BD,
+ 0xE99ED468, 0xD18FBBC4, 0x99BC0B30, 0xA1AD649C, 0x09DB6AD8, 0x31CA0574, 0x79F9B580, 0x41E8DA2C,
+ 0xA3DBBE2A, 0x9BCAD186, 0xD3F96172, 0xEBE80EDE, 0x439E009A, 0x7B8F6F36, 0x33BCDFC2, 0x0BADB06E,
+ 0x66BCB5BB, 0x5EADDA17, 0x169E6AE3, 0x2E8F054F, 0x86F90B0B, 0xBEE864A7, 0xF6DBD453, 0xCECABBFF,
+ 0x6EA2D55C, 0x56B3BAF0, 0x1E800A04, 0x269165A8, 0x8EE76BEC, 0xB6F60440, 0xFEC5B4B4, 0xC6D4DB18,
+ 0xABC5DECD, 0x93D4B161, 0xDBE70195, 0xE3F66E39, 0x4B80607D, 0x73910FD1, 0x3BA2BF25, 0x03B3D089,
+ 0xE180B48F, 0xD991DB23, 0x91A26BD7, 0xA9B3047B, 0x01C50A3F, 0x39D46593, 0x71E7D567, 0x49F6BACB,
+ 0x24E7BF1E, 0x1CF6D0B2, 0x54C56046, 0x6CD40FEA, 0xC4A201AE, 0xFCB36E02, 0xB480DEF6, 0x8C91B15A,
+ 0x750A600B, 0x4D1B0FA7, 0x0528BF53, 0x3D39D0FF, 0x954FDEBB, 0xAD5EB117, 0xE56D01E3, 0xDD7C6E4F,
+ 0xB06D6B9A, 0x887C0436, 0xC04FB4C2, 0xF85EDB6E, 0x5028D52A, 0x6839BA86, 0x200A0A72, 0x181B65DE,
+ 0xFA2801D8, 0xC2396E74, 0x8A0ADE80, 0xB21BB12C, 0x1A6DBF68, 0x227CD0C4, 0x6A4F6030, 0x525E0F9C,
+ 0x3F4F0A49, 0x075E65E5, 0x4F6DD511, 0x777CBABD, 0xDF0AB4F9, 0xE71BDB55, 0xAF286BA1, 0x9739040D,
+ 0x59F3BFF2, 0x61E2D05E, 0x29D160AA, 0x11C00F06, 0xB9B60142, 0x81A76EEE, 0xC994DE1A, 0xF185B1B6,
+ 0x9C94B463, 0xA485DBCF, 0xECB66B3B, 0xD4A70497, 0x7CD10AD3, 0x44C0657F, 0x0CF3D58B, 0x34E2BA27,
+ 0xD6D1DE21, 0xEEC0B18D, 0xA6F30179, 0x9EE26ED5, 0x36946091, 0x0E850F3D, 0x46B6BFC9, 0x7EA7D065,
+ 0x13B6D5B0, 0x2BA7BA1C, 0x63940AE8, 0x5B856544, 0xF3F36B00, 0xCBE204AC, 0x83D1B458, 0xBBC0DBF4,
+ 0x425B0AA5, 0x7A4A6509, 0x3279D5FD, 0x0A68BA51, 0xA21EB415, 0x9A0FDBB9, 0xD23C6B4D, 0xEA2D04E1,
+ 0x873C0134, 0xBF2D6E98, 0xF71EDE6C, 0xCF0FB1C0, 0x6779BF84, 0x5F68D028, 0x175B60DC, 0x2F4A0F70,
+ 0xCD796B76, 0xF56804DA, 0xBD5BB42E, 0x854ADB82, 0x2D3CD5C6, 0x152DBA6A, 0x5D1E0A9E, 0x650F6532,
+ 0x081E60E7, 0x300F0F4B, 0x783CBFBF, 0x402DD013, 0xE85BDE57, 0xD04AB1FB, 0x9879010F, 0xA0686EA3
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o64
+ */
+
+
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o72[256] =
+{
+ 0x00000000, 0xEF306B19, 0xDB8CA0C3, 0x34BCCBDA, 0xB2F53777, 0x5DC55C6E, 0x697997B4, 0x8649FCAD,
+ 0x6006181F, 0x8F367306, 0xBB8AB8DC, 0x54BAD3C5, 0xD2F32F68, 0x3DC34471, 0x097F8FAB, 0xE64FE4B2,
+ 0xC00C303E, 0x2F3C5B27, 0x1B8090FD, 0xF4B0FBE4, 0x72F90749, 0x9DC96C50, 0xA975A78A, 0x4645CC93,
+ 0xA00A2821, 0x4F3A4338, 0x7B8688E2, 0x94B6E3FB, 0x12FF1F56, 0xFDCF744F, 0xC973BF95, 0x2643D48C,
+ 0x85F4168D, 0x6AC47D94, 0x5E78B64E, 0xB148DD57, 0x370121FA, 0xD8314AE3, 0xEC8D8139, 0x03BDEA20,
+ 0xE5F20E92, 0x0AC2658B, 0x3E7EAE51, 0xD14EC548, 0x570739E5, 0xB83752FC, 0x8C8B9926, 0x63BBF23F,
+ 0x45F826B3, 0xAAC84DAA, 0x9E748670, 0x7144ED69, 0xF70D11C4, 0x183D7ADD, 0x2C81B107, 0xC3B1DA1E,
+ 0x25FE3EAC, 0xCACE55B5, 0xFE729E6F, 0x1142F576, 0x970B09DB, 0x783B62C2, 0x4C87A918, 0xA3B7C201,
+ 0x0E045BEB, 0xE13430F2, 0xD588FB28, 0x3AB89031, 0xBCF16C9C, 0x53C10785, 0x677DCC5F, 0x884DA746,
+ 0x6E0243F4, 0x813228ED, 0xB58EE337, 0x5ABE882E, 0xDCF77483, 0x33C71F9A, 0x077BD440, 0xE84BBF59,
+ 0xCE086BD5, 0x213800CC, 0x1584CB16, 0xFAB4A00F, 0x7CFD5CA2, 0x93CD37BB, 0xA771FC61, 0x48419778,
+ 0xAE0E73CA, 0x413E18D3, 0x7582D309, 0x9AB2B810, 0x1CFB44BD, 0xF3CB2FA4, 0xC777E47E, 0x28478F67,
+ 0x8BF04D66, 0x64C0267F, 0x507CEDA5, 0xBF4C86BC, 0x39057A11, 0xD6351108, 0xE289DAD2, 0x0DB9B1CB,
+ 0xEBF65579, 0x04C63E60, 0x307AF5BA, 0xDF4A9EA3, 0x5903620E, 0xB6330917, 0x828FC2CD, 0x6DBFA9D4,
+ 0x4BFC7D58, 0xA4CC1641, 0x9070DD9B, 0x7F40B682, 0xF9094A2F, 0x16392136, 0x2285EAEC, 0xCDB581F5,
+ 0x2BFA6547, 0xC4CA0E5E, 0xF076C584, 0x1F46AE9D, 0x990F5230, 0x763F3929, 0x4283F2F3, 0xADB399EA,
+ 0x1C08B7D6, 0xF338DCCF, 0xC7841715, 0x28B47C0C, 0xAEFD80A1, 0x41CDEBB8, 0x75712062, 0x9A414B7B,
+ 0x7C0EAFC9, 0x933EC4D0, 0xA7820F0A, 0x48B26413, 0xCEFB98BE, 0x21CBF3A7, 0x1577387D, 0xFA475364,
+ 0xDC0487E8, 0x3334ECF1, 0x0788272B, 0xE8B84C32, 0x6EF1B09F, 0x81C1DB86, 0xB57D105C, 0x5A4D7B45,
+ 0xBC029FF7, 0x5332F4EE, 0x678E3F34, 0x88BE542D, 0x0EF7A880, 0xE1C7C399, 0xD57B0843, 0x3A4B635A,
+ 0x99FCA15B, 0x76CCCA42, 0x42700198, 0xAD406A81, 0x2B09962C, 0xC439FD35, 0xF08536EF, 0x1FB55DF6,
+ 0xF9FAB944, 0x16CAD25D, 0x22761987, 0xCD46729E, 0x4B0F8E33, 0xA43FE52A, 0x90832EF0, 0x7FB345E9,
+ 0x59F09165, 0xB6C0FA7C, 0x827C31A6, 0x6D4C5ABF, 0xEB05A612, 0x0435CD0B, 0x308906D1, 0xDFB96DC8,
+ 0x39F6897A, 0xD6C6E263, 0xE27A29B9, 0x0D4A42A0, 0x8B03BE0D, 0x6433D514, 0x508F1ECE, 0xBFBF75D7,
+ 0x120CEC3D, 0xFD3C8724, 0xC9804CFE, 0x26B027E7, 0xA0F9DB4A, 0x4FC9B053, 0x7B757B89, 0x94451090,
+ 0x720AF422, 0x9D3A9F3B, 0xA98654E1, 0x46B63FF8, 0xC0FFC355, 0x2FCFA84C, 0x1B736396, 0xF443088F,
+ 0xD200DC03, 0x3D30B71A, 0x098C7CC0, 0xE6BC17D9, 0x60F5EB74, 0x8FC5806D, 0xBB794BB7, 0x544920AE,
+ 0xB206C41C, 0x5D36AF05, 0x698A64DF, 0x86BA0FC6, 0x00F3F36B, 0xEFC39872, 0xDB7F53A8, 0x344F38B1,
+ 0x97F8FAB0, 0x78C891A9, 0x4C745A73, 0xA344316A, 0x250DCDC7, 0xCA3DA6DE, 0xFE816D04, 0x11B1061D,
+ 0xF7FEE2AF, 0x18CE89B6, 0x2C72426C, 0xC3422975, 0x450BD5D8, 0xAA3BBEC1, 0x9E87751B, 0x71B71E02,
+ 0x57F4CA8E, 0xB8C4A197, 0x8C786A4D, 0x63480154, 0xE501FDF9, 0x0A3196E0, 0x3E8D5D3A, 0xD1BD3623,
+ 0x37F2D291, 0xD8C2B988, 0xEC7E7252, 0x034E194B, 0x8507E5E6, 0x6A378EFF, 0x5E8B4525, 0xB1BB2E3C
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o72
+ */
+
+
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o80[256] =
+{
+ 0x00000000, 0x68032CC8, 0xD0065990, 0xB8057558, 0xA5E0C5D1, 0xCDE3E919, 0x75E69C41, 0x1DE5B089,
+ 0x4E2DFD53, 0x262ED19B, 0x9E2BA4C3, 0xF628880B, 0xEBCD3882, 0x83CE144A, 0x3BCB6112, 0x53C84DDA,
+ 0x9C5BFAA6, 0xF458D66E, 0x4C5DA336, 0x245E8FFE, 0x39BB3F77, 0x51B813BF, 0xE9BD66E7, 0x81BE4A2F,
+ 0xD27607F5, 0xBA752B3D, 0x02705E65, 0x6A7372AD, 0x7796C224, 0x1F95EEEC, 0xA7909BB4, 0xCF93B77C,
+ 0x3D5B83BD, 0x5558AF75, 0xED5DDA2D, 0x855EF6E5, 0x98BB466C, 0xF0B86AA4, 0x48BD1FFC, 0x20BE3334,
+ 0x73767EEE, 0x1B755226, 0xA370277E, 0xCB730BB6, 0xD696BB3F, 0xBE9597F7, 0x0690E2AF, 0x6E93CE67,
+ 0xA100791B, 0xC90355D3, 0x7106208B, 0x19050C43, 0x04E0BCCA, 0x6CE39002, 0xD4E6E55A, 0xBCE5C992,
+ 0xEF2D8448, 0x872EA880, 0x3F2BDDD8, 0x5728F110, 0x4ACD4199, 0x22CE6D51, 0x9ACB1809, 0xF2C834C1,
+ 0x7AB7077A, 0x12B42BB2, 0xAAB15EEA, 0xC2B27222, 0xDF57C2AB, 0xB754EE63, 0x0F519B3B, 0x6752B7F3,
+ 0x349AFA29, 0x5C99D6E1, 0xE49CA3B9, 0x8C9F8F71, 0x917A3FF8, 0xF9791330, 0x417C6668, 0x297F4AA0,
+ 0xE6ECFDDC, 0x8EEFD114, 0x36EAA44C, 0x5EE98884, 0x430C380D, 0x2B0F14C5, 0x930A619D, 0xFB094D55,
+ 0xA8C1008F, 0xC0C22C47, 0x78C7591F, 0x10C475D7, 0x0D21C55E, 0x6522E996, 0xDD279CCE, 0xB524B006,
+ 0x47EC84C7, 0x2FEFA80F, 0x97EADD57, 0xFFE9F19F, 0xE20C4116, 0x8A0F6DDE, 0x320A1886, 0x5A09344E,
+ 0x09C17994, 0x61C2555C, 0xD9C72004, 0xB1C40CCC, 0xAC21BC45, 0xC422908D, 0x7C27E5D5, 0x1424C91D,
+ 0xDBB77E61, 0xB3B452A9, 0x0BB127F1, 0x63B20B39, 0x7E57BBB0, 0x16549778, 0xAE51E220, 0xC652CEE8,
+ 0x959A8332, 0xFD99AFFA, 0x459CDAA2, 0x2D9FF66A, 0x307A46E3, 0x58796A2B, 0xE07C1F73, 0x887F33BB,
+ 0xF56E0EF4, 0x9D6D223C, 0x25685764, 0x4D6B7BAC, 0x508ECB25, 0x388DE7ED, 0x808892B5, 0xE88BBE7D,
+ 0xBB43F3A7, 0xD340DF6F, 0x6B45AA37, 0x034686FF, 0x1EA33676, 0x76A01ABE, 0xCEA56FE6, 0xA6A6432E,
+ 0x6935F452, 0x0136D89A, 0xB933ADC2, 0xD130810A, 0xCCD53183, 0xA4D61D4B, 0x1CD36813, 0x74D044DB,
+ 0x27180901, 0x4F1B25C9, 0xF71E5091, 0x9F1D7C59, 0x82F8CCD0, 0xEAFBE018, 0x52FE9540, 0x3AFDB988,
+ 0xC8358D49, 0xA036A181, 0x1833D4D9, 0x7030F811, 0x6DD54898, 0x05D66450, 0xBDD31108, 0xD5D03DC0,
+ 0x8618701A, 0xEE1B5CD2, 0x561E298A, 0x3E1D0542, 0x23F8B5CB, 0x4BFB9903, 0xF3FEEC5B, 0x9BFDC093,
+ 0x546E77EF, 0x3C6D5B27, 0x84682E7F, 0xEC6B02B7, 0xF18EB23E, 0x998D9EF6, 0x2188EBAE, 0x498BC766,
+ 0x1A438ABC, 0x7240A674, 0xCA45D32C, 0xA246FFE4, 0xBFA34F6D, 0xD7A063A5, 0x6FA516FD, 0x07A63A35,
+ 0x8FD9098E, 0xE7DA2546, 0x5FDF501E, 0x37DC7CD6, 0x2A39CC5F, 0x423AE097, 0xFA3F95CF, 0x923CB907,
+ 0xC1F4F4DD, 0xA9F7D815, 0x11F2AD4D, 0x79F18185, 0x6414310C, 0x0C171DC4, 0xB412689C, 0xDC114454,
+ 0x1382F328, 0x7B81DFE0, 0xC384AAB8, 0xAB878670, 0xB66236F9, 0xDE611A31, 0x66646F69, 0x0E6743A1,
+ 0x5DAF0E7B, 0x35AC22B3, 0x8DA957EB, 0xE5AA7B23, 0xF84FCBAA, 0x904CE762, 0x2849923A, 0x404ABEF2,
+ 0xB2828A33, 0xDA81A6FB, 0x6284D3A3, 0x0A87FF6B, 0x17624FE2, 0x7F61632A, 0xC7641672, 0xAF673ABA,
+ 0xFCAF7760, 0x94AC5BA8, 0x2CA92EF0, 0x44AA0238, 0x594FB2B1, 0x314C9E79, 0x8949EB21, 0xE14AC7E9,
+ 0x2ED97095, 0x46DA5C5D, 0xFEDF2905, 0x96DC05CD, 0x8B39B544, 0xE33A998C, 0x5B3FECD4, 0x333CC01C,
+ 0x60F48DC6, 0x08F7A10E, 0xB0F2D456, 0xD8F1F89E, 0xC5144817, 0xAD1764DF, 0x15121187, 0x7D113D4F
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o80
+ */
+
+
+
+/*
+ * The following CRC lookup table was generated automagically using the
+ * following model parameters:
+ *
+ * Generator Polynomial = ................. 0x1EDC6F41
+ * Generator Polynomial Length = .......... 32 bits
+ * Reflected Bits = ....................... TRUE
+ * Table Generation Offset = .............. 32 bits
+ * Number of Slices = ..................... 8 slices
+ * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
+ * Directory Name = ....................... .\
+ * File Name = ............................ 8x256_tables.c
+ */
+
+static const uint32_t sctp_crc_tableil8_o88[256] =
+{
+ 0x00000000, 0x493C7D27, 0x9278FA4E, 0xDB448769, 0x211D826D, 0x6821FF4A, 0xB3657823, 0xFA590504,
+ 0x423B04DA, 0x0B0779FD, 0xD043FE94, 0x997F83B3, 0x632686B7, 0x2A1AFB90, 0xF15E7CF9, 0xB86201DE,
+ 0x847609B4, 0xCD4A7493, 0x160EF3FA, 0x5F328EDD, 0xA56B8BD9, 0xEC57F6FE, 0x37137197, 0x7E2F0CB0,
+ 0xC64D0D6E, 0x8F717049, 0x5435F720, 0x1D098A07, 0xE7508F03, 0xAE6CF224, 0x7528754D, 0x3C14086A,
+ 0x0D006599, 0x443C18BE, 0x9F789FD7, 0xD644E2F0, 0x2C1DE7F4, 0x65219AD3, 0xBE651DBA, 0xF759609D,
+ 0x4F3B6143, 0x06071C64, 0xDD439B0D, 0x947FE62A, 0x6E26E32E, 0x271A9E09, 0xFC5E1960, 0xB5626447,
+ 0x89766C2D, 0xC04A110A, 0x1B0E9663, 0x5232EB44, 0xA86BEE40, 0xE1579367, 0x3A13140E, 0x732F6929,
+ 0xCB4D68F7, 0x827115D0, 0x593592B9, 0x1009EF9E, 0xEA50EA9A, 0xA36C97BD, 0x782810D4, 0x31146DF3,
+ 0x1A00CB32, 0x533CB615, 0x8878317C, 0xC1444C5B, 0x3B1D495F, 0x72213478, 0xA965B311, 0xE059CE36,
+ 0x583BCFE8, 0x1107B2CF, 0xCA4335A6, 0x837F4881, 0x79264D85, 0x301A30A2, 0xEB5EB7CB, 0xA262CAEC,
+ 0x9E76C286, 0xD74ABFA1, 0x0C0E38C8, 0x453245EF, 0xBF6B40EB, 0xF6573DCC, 0x2D13BAA5, 0x642FC782,
+ 0xDC4DC65C, 0x9571BB7B, 0x4E353C12, 0x07094135, 0xFD504431, 0xB46C3916, 0x6F28BE7F, 0x2614C358,
+ 0x1700AEAB, 0x5E3CD38C, 0x857854E5, 0xCC4429C2, 0x361D2CC6, 0x7F2151E1, 0xA465D688, 0xED59ABAF,
+ 0x553BAA71, 0x1C07D756, 0xC743503F, 0x8E7F2D18, 0x7426281C, 0x3D1A553B, 0xE65ED252, 0xAF62AF75,
+ 0x9376A71F, 0xDA4ADA38, 0x010E5D51, 0x48322076, 0xB26B2572, 0xFB575855, 0x2013DF3C, 0x692FA21B,
+ 0xD14DA3C5, 0x9871DEE2, 0x4335598B, 0x0A0924AC, 0xF05021A8, 0xB96C5C8F, 0x6228DBE6, 0x2B14A6C1,
+ 0x34019664, 0x7D3DEB43, 0xA6796C2A, 0xEF45110D, 0x151C1409, 0x5C20692E, 0x8764EE47, 0xCE589360,
+ 0x763A92BE, 0x3F06EF99, 0xE44268F0, 0xAD7E15D7, 0x572710D3, 0x1E1B6DF4, 0xC55FEA9D, 0x8C6397BA,
+ 0xB0779FD0, 0xF94BE2F7, 0x220F659E, 0x6B3318B9, 0x916A1DBD, 0xD856609A, 0x0312E7F3, 0x4A2E9AD4,
+ 0xF24C9B0A, 0xBB70E62D, 0x60346144, 0x29081C63, 0xD3511967, 0x9A6D6440, 0x4129E329, 0x08159E0E,
+ 0x3901F3FD, 0x703D8EDA, 0xAB7909B3, 0xE2457494, 0x181C7190, 0x51200CB7, 0x8A648BDE, 0xC358F6F9,
+ 0x7B3AF727, 0x32068A00, 0xE9420D69, 0xA07E704E, 0x5A27754A, 0x131B086D, 0xC85F8F04, 0x8163F223,
+ 0xBD77FA49, 0xF44B876E, 0x2F0F0007, 0x66337D20, 0x9C6A7824, 0xD5560503, 0x0E12826A, 0x472EFF4D,
+ 0xFF4CFE93, 0xB67083B4, 0x6D3404DD, 0x240879FA, 0xDE517CFE, 0x976D01D9, 0x4C2986B0, 0x0515FB97,
+ 0x2E015D56, 0x673D2071, 0xBC79A718, 0xF545DA3F, 0x0F1CDF3B, 0x4620A21C, 0x9D642575, 0xD4585852,
+ 0x6C3A598C, 0x250624AB, 0xFE42A3C2, 0xB77EDEE5, 0x4D27DBE1, 0x041BA6C6, 0xDF5F21AF, 0x96635C88,
+ 0xAA7754E2, 0xE34B29C5, 0x380FAEAC, 0x7133D38B, 0x8B6AD68F, 0xC256ABA8, 0x19122CC1, 0x502E51E6,
+ 0xE84C5038, 0xA1702D1F, 0x7A34AA76, 0x3308D751, 0xC951D255, 0x806DAF72, 0x5B29281B, 0x1215553C,
+ 0x230138CF, 0x6A3D45E8, 0xB179C281, 0xF845BFA6, 0x021CBAA2, 0x4B20C785, 0x906440EC, 0xD9583DCB,
+ 0x613A3C15, 0x28064132, 0xF342C65B, 0xBA7EBB7C, 0x4027BE78, 0x091BC35F, 0xD25F4436, 0x9B633911,
+ 0xA777317B, 0xEE4B4C5C, 0x350FCB35, 0x7C33B612, 0x866AB316, 0xCF56CE31, 0x14124958, 0x5D2E347F,
+ 0xE54C35A1, 0xAC704886, 0x7734CFEF, 0x3E08B2C8, 0xC451B7CC, 0x8D6DCAEB, 0x56294D82, 0x1F1530A5
+};
+
+/*
+ * end of the CRC lookup table crc_tableil8_o88
+ */
+
+
+static uint32_t
+sctp_crc32c_sb8_64_bit(uint32_t crc,
+ const unsigned char *p_buf,
+ uint32_t length,
+ uint32_t init_bytes)
+{
+ uint32_t li;
+ uint32_t term1, term2;
+ uint32_t running_length;
+ uint32_t end_bytes;
+
+ running_length = ((length - init_bytes) / 8) * 8;
+ end_bytes = length - init_bytes - running_length;
+
+ for (li = 0; li < init_bytes; li++)
+ crc = sctp_crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^
+ (crc >> 8);
+ for (li = 0; li < running_length / 8; li++) {
+#if BYTE_ORDER == BIG_ENDIAN
+ crc ^= *p_buf++;
+ crc ^= (*p_buf++) << 8;
+ crc ^= (*p_buf++) << 16;
+ crc ^= (*p_buf++) << 24;
+#else
+ crc ^= *(const uint32_t *) p_buf;
+ p_buf += 4;
+#endif
+ term1 = sctp_crc_tableil8_o88[crc & 0x000000FF] ^
+ sctp_crc_tableil8_o80[(crc >> 8) & 0x000000FF];
+ term2 = crc >> 16;
+ crc = term1 ^
+ sctp_crc_tableil8_o72[term2 & 0x000000FF] ^
+ sctp_crc_tableil8_o64[(term2 >> 8) & 0x000000FF];
+
+#if BYTE_ORDER == BIG_ENDIAN
+ crc ^= sctp_crc_tableil8_o56[*p_buf++];
+ crc ^= sctp_crc_tableil8_o48[*p_buf++];
+ crc ^= sctp_crc_tableil8_o40[*p_buf++];
+ crc ^= sctp_crc_tableil8_o32[*p_buf++];
+#else
+ term1 = sctp_crc_tableil8_o56[(*(const uint32_t *) p_buf) & 0x000000FF] ^
+ sctp_crc_tableil8_o48[((*(const uint32_t *) p_buf) >> 8) & 0x000000FF];
+
+ term2 = (*(const uint32_t *) p_buf) >> 16;
+ crc = crc ^
+ term1 ^
+ sctp_crc_tableil8_o40[term2 & 0x000000FF] ^
+ sctp_crc_tableil8_o32[(term2 >> 8) & 0x000000FF];
+ p_buf += 4;
+#endif
+ }
+ for (li = 0; li < end_bytes; li++)
+ crc = sctp_crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^
+ (crc >> 8);
+ return (crc);
+}
+
+
+/**
+ *
+ * Routine Description:
+ *
+ * warms the tables
+ *
+ * Arguments:
+ *
+ * none
+ *
+ * Return value:
+ *
+ * none
+ */
+static uint32_t
+multitable_crc32c(uint32_t crc32c,
+ const unsigned char *buffer,
+ unsigned int length)
+{
+ uint32_t to_even_word;
+
+ if (length == 0) {
+ return (crc32c);
+ }
+ to_even_word = (4 - (((uintptr_t) buffer) & 0x3));
+ return (sctp_crc32c_sb8_64_bit(crc32c, buffer, length, to_even_word));
+}
+
+static const uint32_t sctp_crc_c[256] = {
+ 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4,
+ 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
+ 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
+ 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
+ 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B,
+ 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
+ 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54,
+ 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
+ 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
+ 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
+ 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5,
+ 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
+ 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45,
+ 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
+ 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
+ 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
+ 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48,
+ 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
+ 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687,
+ 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
+ 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
+ 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
+ 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8,
+ 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
+ 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096,
+ 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
+ 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
+ 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
+ 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9,
+ 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
+ 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36,
+ 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
+ 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
+ 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
+ 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043,
+ 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
+ 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3,
+ 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
+ 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
+ 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
+ 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652,
+ 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
+ 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D,
+ 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
+ 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
+ 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
+ 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2,
+ 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
+ 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530,
+ 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
+ 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
+ 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
+ 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F,
+ 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
+ 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90,
+ 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
+ 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
+ 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
+ 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321,
+ 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
+ 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81,
+ 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
+ 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
+ 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
+};
+
+
+#define SCTP_CRC32C(c,d) (c=(c>>8)^sctp_crc_c[(c^(d))&0xFF])
+
+static uint32_t
+singletable_crc32c(uint32_t crc32c,
+ const unsigned char *buffer,
+ unsigned int length)
+{
+ unsigned int i;
+
+ for (i = 0; i < length; i++) {
+ SCTP_CRC32C(crc32c, buffer[i]);
+ }
+ return (crc32c);
+}
+
+#if defined(__Userspace__)
+uint32_t
+#else
+static uint32_t
+#endif
+calculate_crc32c(uint32_t crc32c,
+ const unsigned char *buffer,
+ unsigned int length)
+{
+ if (length < 4) {
+ return (singletable_crc32c(crc32c, buffer, length));
+ } else {
+ return (multitable_crc32c(crc32c, buffer, length));
+ }
+}
+
+#endif
+#if defined(__Userspace__)
+uint32_t
+#else
+static uint32_t
+#endif
+sctp_finalize_crc32c(uint32_t crc32c)
+{
+#if BYTE_ORDER == BIG_ENDIAN
+ uint32_t byte0, byte1, byte2, byte3;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ /*
+ * For BIG-ENDIAN platforms, the result is in LITTLE-ENDIAN byte order.
+ * For LITTLE-ENDIAN platforms, the result is in in BIG-ENDIAN byte
+ * order. So for BIG-ENDIAN platforms the bytes must be swapped to
+ * return the result always in network byte order (aka BIG-ENDIAN).
+ */
+ byte0 = crc32c & 0x000000ff;
+ byte1 = (crc32c >> 8) & 0x000000ff;
+ byte2 = (crc32c >> 16) & 0x000000ff;
+ byte3 = (crc32c >> 24) & 0x000000ff;
+ crc32c = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
+#endif
+ return (~crc32c);
+}
+
+static int
+sctp_calculate_cksum_cb(void *arg, void *data, u_int len)
+{
+ uint32_t *basep;
+
+ basep = arg;
+ *basep = calculate_crc32c(*basep, data, len);
+ return (0);
+}
+
+/*
+ * Compute the SCTP checksum in network byte order for a given mbuf chain m
+ * which contains an SCTP packet starting at offset.
+ * Since this function is also called by ipfw, don't assume that
+ * it is compiled on a kernel with SCTP support.
+ */
+uint32_t
+sctp_calculate_cksum(struct mbuf *m, int32_t offset)
+{
+ uint32_t base;
+ int len;
+
+ M_ASSERTPKTHDR(m);
+ KASSERT(offset < m->m_pkthdr.len,
+ ("%s: invalid offset %u into mbuf %p", __func__, offset, m));
+
+ base = 0xffffffff;
+ len = m->m_pkthdr.len - offset;
+ (void)m_apply(m, offset, len, sctp_calculate_cksum_cb, &base);
+ return (sctp_finalize_crc32c(base));
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP) || defined(SCTP_SUPPORT)
+
+VNET_DEFINE(struct sctp_base_info, system_base_info);
+
+/*
+ * Compute and insert the SCTP checksum in network byte order for a given
+ * mbuf chain m which contains an SCTP packet starting at offset.
+ */
+void
+sctp_delayed_cksum(struct mbuf *m, uint32_t offset)
+{
+ uint32_t checksum;
+
+ checksum = sctp_calculate_cksum(m, offset);
+ SCTP_STAT_DECR(sctps_sendhwcrc);
+ SCTP_STAT_INCR(sctps_sendswcrc);
+ offset += offsetof(struct sctphdr, checksum);
+
+ if (offset + sizeof(uint32_t) > (uint32_t)(m->m_pkthdr.len)) {
+#ifdef INVARIANTS
+ panic("sctp_delayed_cksum(): m->m_pkthdr.len: %d, offset: %u.",
+ m->m_pkthdr.len, offset);
+#else
+ SCTP_PRINTF("sctp_delayed_cksum(): m->m_pkthdr.len: %d, offset: %u.\n",
+ m->m_pkthdr.len, offset);
+#endif
+ return;
+ }
+ m_copyback(m, (int)offset, (int)sizeof(uint32_t), (caddr_t)&checksum);
+}
+#endif
+#endif
+
diff --git a/netwerk/sctp/src/netinet/sctp_crc32.h b/netwerk/sctp/src/netinet/sctp_crc32.h
new file mode 100644
index 0000000000..e8315dc345
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_crc32.h
@@ -0,0 +1,51 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_CRC32_H_
+#define _NETINET_SCTP_CRC32_H_
+
+#if defined(_KERNEL)
+uint32_t sctp_calculate_cksum(struct mbuf *, int32_t);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP) || defined(SCTP_SUPPORT)
+void sctp_delayed_cksum(struct mbuf *, uint32_t offset);
+#endif
+#endif
+#endif /* _KERNEL */
+#if defined(__Userspace__)
+uint32_t calculate_crc32c(uint32_t, const unsigned char *, unsigned int);
+uint32_t sctp_finalize_crc32c(uint32_t);
+uint32_t sctp_calculate_cksum(struct mbuf *, int32_t);
+#endif
+#endif /* __crc32c_h__ */
diff --git a/netwerk/sctp/src/netinet/sctp_header.h b/netwerk/sctp/src/netinet/sctp_header.h
new file mode 100644
index 0000000000..78206581af
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_header.h
@@ -0,0 +1,598 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_HEADER_H_
+#define _NETINET_SCTP_HEADER_H_
+
+#if defined(_WIN32) && !defined(__Userspace__)
+#include <packon.h>
+#endif
+#if !defined(_WIN32)
+#include <sys/time.h>
+#endif
+#include <netinet/sctp.h>
+#include <netinet/sctp_constants.h>
+
+#if !defined(_WIN32)
+#define SCTP_PACKED __attribute__((packed))
+#else
+#pragma pack (push, 1)
+#define SCTP_PACKED
+#endif
+
+/*
+ * Parameter structures
+ */
+struct sctp_ipv4addr_param {
+ struct sctp_paramhdr ph;/* type=SCTP_IPV4_PARAM_TYPE, len=8 */
+ uint32_t addr; /* IPV4 address */
+} SCTP_PACKED;
+
+#define SCTP_V6_ADDR_BYTES 16
+
+struct sctp_ipv6addr_param {
+ struct sctp_paramhdr ph;/* type=SCTP_IPV6_PARAM_TYPE, len=20 */
+ uint8_t addr[SCTP_V6_ADDR_BYTES]; /* IPV6 address */
+} SCTP_PACKED;
+
+/* Cookie Preservative */
+struct sctp_cookie_perserve_param {
+ struct sctp_paramhdr ph;/* type=SCTP_COOKIE_PRESERVE, len=8 */
+ uint32_t time; /* time in ms to extend cookie */
+} SCTP_PACKED;
+
+#define SCTP_ARRAY_MIN_LEN 1
+/* Host Name Address */
+struct sctp_host_name_param {
+ struct sctp_paramhdr ph;/* type=SCTP_HOSTNAME_ADDRESS */
+ char name[SCTP_ARRAY_MIN_LEN]; /* host name */
+} SCTP_PACKED;
+
+/*
+ * This is the maximum padded size of a s-a-p
+ * so paramheadr + 3 address types (6 bytes) + 2 byte pad = 12
+ */
+#define SCTP_MAX_ADDR_PARAMS_SIZE 12
+/* supported address type */
+struct sctp_supported_addr_param {
+ struct sctp_paramhdr ph;/* type=SCTP_SUPPORTED_ADDRTYPE */
+ uint16_t addr_type[2]; /* array of supported address types */
+} SCTP_PACKED;
+
+/* heartbeat info parameter */
+struct sctp_heartbeat_info_param {
+ struct sctp_paramhdr ph;
+ uint32_t time_value_1;
+ uint32_t time_value_2;
+ uint32_t random_value1;
+ uint32_t random_value2;
+ uint8_t addr_family;
+ uint8_t addr_len;
+ /* make sure that this structure is 4 byte aligned */
+ uint8_t padding[2];
+ char address[SCTP_ADDRMAX];
+} SCTP_PACKED;
+
+/* draft-ietf-tsvwg-prsctp */
+/* PR-SCTP supported parameter */
+struct sctp_prsctp_supported_param {
+ struct sctp_paramhdr ph;
+} SCTP_PACKED;
+
+/* draft-ietf-tsvwg-addip-sctp */
+struct sctp_asconf_paramhdr { /* an ASCONF "parameter" */
+ struct sctp_paramhdr ph;/* a SCTP parameter header */
+ uint32_t correlation_id;/* correlation id for this param */
+} SCTP_PACKED;
+
+struct sctp_asconf_addr_param { /* an ASCONF address parameter */
+ struct sctp_asconf_paramhdr aph; /* asconf "parameter" */
+ struct sctp_ipv6addr_param addrp; /* max storage size */
+} SCTP_PACKED;
+
+struct sctp_asconf_tag_param { /* an ASCONF NAT-Vtag parameter */
+ struct sctp_asconf_paramhdr aph; /* asconf "parameter" */
+ uint32_t local_vtag;
+ uint32_t remote_vtag;
+} SCTP_PACKED;
+
+struct sctp_asconf_addrv4_param { /* an ASCONF address (v4) parameter */
+ struct sctp_asconf_paramhdr aph; /* asconf "parameter" */
+ struct sctp_ipv4addr_param addrp; /* max storage size */
+} SCTP_PACKED;
+
+#define SCTP_MAX_SUPPORTED_EXT 256
+
+struct sctp_supported_chunk_types_param {
+ struct sctp_paramhdr ph;/* type = 0x8008 len = x */
+ uint8_t chunk_types[];
+} SCTP_PACKED;
+
+/*
+ * Structures for DATA chunks
+ */
+struct sctp_data {
+ uint32_t tsn;
+ uint16_t sid;
+ uint16_t ssn;
+ uint32_t ppid;
+ /* user data follows */
+} SCTP_PACKED;
+
+struct sctp_data_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_data dp;
+} SCTP_PACKED;
+
+struct sctp_idata {
+ uint32_t tsn;
+ uint16_t sid;
+ uint16_t reserved; /* Where does the SSN go? */
+ uint32_t mid;
+ union {
+ uint32_t ppid;
+ uint32_t fsn; /* Fragment Sequence Number */
+ } ppid_fsn;
+ /* user data follows */
+} SCTP_PACKED;
+
+struct sctp_idata_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_idata dp;
+} SCTP_PACKED;
+
+/*
+ * Structures for the control chunks
+ */
+
+/* Initiate (INIT)/Initiate Ack (INIT ACK) */
+struct sctp_init {
+ uint32_t initiate_tag; /* initiate tag */
+ uint32_t a_rwnd; /* a_rwnd */
+ uint16_t num_outbound_streams; /* OS */
+ uint16_t num_inbound_streams; /* MIS */
+ uint32_t initial_tsn; /* I-TSN */
+ /* optional param's follow */
+} SCTP_PACKED;
+#define SCTP_IDENTIFICATION_SIZE 16
+#define SCTP_ADDRESS_SIZE 4
+#if defined(__Userspace__)
+#define SCTP_RESERVE_SPACE 4
+#else
+#define SCTP_RESERVE_SPACE 5
+#endif
+/* state cookie header */
+struct sctp_state_cookie { /* this is our definition... */
+ uint8_t identification[SCTP_IDENTIFICATION_SIZE];/* id of who we are */
+ struct timeval time_entered; /* the time I built cookie */
+ uint32_t cookie_life; /* life I will award this cookie */
+ uint32_t tie_tag_my_vtag; /* my tag in old association */
+
+ uint32_t tie_tag_peer_vtag; /* peers tag in old association */
+ uint32_t peers_vtag; /* peers tag in INIT (for quick ref) */
+
+ uint32_t my_vtag; /* my tag in INIT-ACK (for quick ref) */
+ uint32_t address[SCTP_ADDRESS_SIZE]; /* 4 ints/128 bits */
+ uint32_t addr_type; /* address type */
+ uint32_t laddress[SCTP_ADDRESS_SIZE]; /* my local from address */
+ uint32_t laddr_type; /* my local from address type */
+ uint32_t scope_id; /* v6 scope id for link-locals */
+
+ uint16_t peerport; /* port address of the peer in the INIT */
+ uint16_t myport; /* my port address used in the INIT */
+ uint8_t ipv4_addr_legal;/* Are V4 addr legal? */
+ uint8_t ipv6_addr_legal;/* Are V6 addr legal? */
+#if defined(__Userspace__)
+ uint8_t conn_addr_legal;
+#endif
+ uint8_t local_scope; /* IPv6 local scope flag */
+ uint8_t site_scope; /* IPv6 site scope flag */
+
+ uint8_t ipv4_scope; /* IPv4 private addr scope */
+ uint8_t loopback_scope; /* loopback scope information */
+ uint8_t rcv_edmid; /* copy of the inp value */
+ uint8_t reserved[SCTP_RESERVE_SPACE]; /* Align to 64 bits */
+ /*
+ * at the end is tacked on the INIT chunk and the INIT-ACK chunk
+ * (minus the cookie).
+ */
+} SCTP_PACKED;
+
+/* state cookie parameter */
+struct sctp_state_cookie_param {
+ struct sctp_paramhdr ph;
+ struct sctp_state_cookie cookie;
+} SCTP_PACKED;
+
+struct sctp_init_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_init init;
+} SCTP_PACKED;
+
+struct sctp_init_msg {
+ struct sctphdr sh;
+ struct sctp_init_chunk msg;
+} SCTP_PACKED;
+
+/* ... used for both INIT and INIT ACK */
+#define sctp_init_ack sctp_init
+#define sctp_init_ack_chunk sctp_init_chunk
+#define sctp_init_ack_msg sctp_init_msg
+
+/* Selective Ack (SACK) */
+struct sctp_gap_ack_block {
+ uint16_t start; /* Gap Ack block start */
+ uint16_t end; /* Gap Ack block end */
+} SCTP_PACKED;
+
+struct sctp_sack {
+ uint32_t cum_tsn_ack; /* cumulative TSN Ack */
+ uint32_t a_rwnd; /* updated a_rwnd of sender */
+ uint16_t num_gap_ack_blks; /* number of Gap Ack blocks */
+ uint16_t num_dup_tsns; /* number of duplicate TSNs */
+ /* struct sctp_gap_ack_block's follow */
+ /* uint32_t duplicate_tsn's follow */
+} SCTP_PACKED;
+
+struct sctp_sack_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_sack sack;
+} SCTP_PACKED;
+
+struct sctp_nr_sack {
+ uint32_t cum_tsn_ack; /* cumulative TSN Ack */
+ uint32_t a_rwnd; /* updated a_rwnd of sender */
+ uint16_t num_gap_ack_blks; /* number of Gap Ack blocks */
+ uint16_t num_nr_gap_ack_blks; /* number of NR Gap Ack blocks */
+ uint16_t num_dup_tsns; /* number of duplicate TSNs */
+ uint16_t reserved; /* not currently used*/
+ /* struct sctp_gap_ack_block's follow */
+ /* uint32_t duplicate_tsn's follow */
+} SCTP_PACKED;
+
+struct sctp_nr_sack_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_nr_sack nr_sack;
+} SCTP_PACKED;
+
+/* Heartbeat Request (HEARTBEAT) */
+struct sctp_heartbeat {
+ struct sctp_heartbeat_info_param hb_info;
+} SCTP_PACKED;
+
+struct sctp_heartbeat_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_heartbeat heartbeat;
+} SCTP_PACKED;
+
+/* ... used for Heartbeat Ack (HEARTBEAT ACK) */
+#define sctp_heartbeat_ack sctp_heartbeat
+#define sctp_heartbeat_ack_chunk sctp_heartbeat_chunk
+
+/* Abort Asssociation (ABORT) */
+struct sctp_abort_chunk {
+ struct sctp_chunkhdr ch;
+ /* optional error cause may follow */
+} SCTP_PACKED;
+
+struct sctp_abort_msg {
+ struct sctphdr sh;
+ struct sctp_abort_chunk msg;
+} SCTP_PACKED;
+
+/* Shutdown Association (SHUTDOWN) */
+struct sctp_shutdown_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t cumulative_tsn_ack;
+} SCTP_PACKED;
+
+/* Shutdown Acknowledgment (SHUTDOWN ACK) */
+struct sctp_shutdown_ack_chunk {
+ struct sctp_chunkhdr ch;
+} SCTP_PACKED;
+
+/* Operation Error (ERROR) */
+struct sctp_error_chunk {
+ struct sctp_chunkhdr ch;
+ /* optional error causes follow */
+} SCTP_PACKED;
+
+/* Cookie Echo (COOKIE ECHO) */
+struct sctp_cookie_echo_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_state_cookie cookie;
+} SCTP_PACKED;
+
+/* Cookie Acknowledgment (COOKIE ACK) */
+struct sctp_cookie_ack_chunk {
+ struct sctp_chunkhdr ch;
+} SCTP_PACKED;
+
+/* Explicit Congestion Notification Echo (ECNE) */
+struct old_sctp_ecne_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t tsn;
+} SCTP_PACKED;
+
+struct sctp_ecne_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t tsn;
+ uint32_t num_pkts_since_cwr;
+} SCTP_PACKED;
+
+/* Congestion Window Reduced (CWR) */
+struct sctp_cwr_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t tsn;
+} SCTP_PACKED;
+
+/* Shutdown Complete (SHUTDOWN COMPLETE) */
+struct sctp_shutdown_complete_chunk {
+ struct sctp_chunkhdr ch;
+} SCTP_PACKED;
+
+struct sctp_adaptation_layer_indication {
+ struct sctp_paramhdr ph;
+ uint32_t indication;
+} SCTP_PACKED;
+
+/*
+ * draft-ietf-tsvwg-addip-sctp
+ */
+/* Address/Stream Configuration Change (ASCONF) */
+struct sctp_asconf_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t serial_number;
+ /* lookup address parameter (mandatory) */
+ /* asconf parameters follow */
+} SCTP_PACKED;
+
+/* Address/Stream Configuration Acknowledge (ASCONF ACK) */
+struct sctp_asconf_ack_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t serial_number;
+ /* asconf parameters follow */
+} SCTP_PACKED;
+
+/* draft-ietf-tsvwg-prsctp */
+/* Forward Cumulative TSN (FORWARD TSN) */
+struct sctp_forward_tsn_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t new_cumulative_tsn;
+ /* stream/sequence pairs (sctp_strseq) follow */
+} SCTP_PACKED;
+
+struct sctp_strseq {
+ uint16_t sid;
+ uint16_t ssn;
+} SCTP_PACKED;
+
+struct sctp_strseq_mid {
+ uint16_t sid;
+ uint16_t flags;
+ uint32_t mid;
+};
+
+struct sctp_forward_tsn_msg {
+ struct sctphdr sh;
+ struct sctp_forward_tsn_chunk msg;
+} SCTP_PACKED;
+
+/* should be a multiple of 4 - 1 aka 3/7/11 etc. */
+
+#define SCTP_NUM_DB_TO_VERIFY 31
+
+struct sctp_chunk_desc {
+ uint8_t chunk_type;
+ uint8_t data_bytes[SCTP_NUM_DB_TO_VERIFY];
+ uint32_t tsn_ifany;
+} SCTP_PACKED;
+
+struct sctp_pktdrop_chunk {
+ struct sctp_chunkhdr ch;
+ uint32_t bottle_bw;
+ uint32_t current_onq;
+ uint16_t trunc_len;
+ uint16_t reserved;
+ uint8_t data[];
+} SCTP_PACKED;
+
+/**********STREAM RESET STUFF ******************/
+
+struct sctp_stream_reset_request {
+ struct sctp_paramhdr ph;
+ uint32_t request_seq;
+} SCTP_PACKED;
+
+struct sctp_stream_reset_out_request {
+ struct sctp_paramhdr ph;
+ uint32_t request_seq; /* monotonically increasing seq no */
+ uint32_t response_seq; /* if a response, the resp seq no */
+ uint32_t send_reset_at_tsn; /* last TSN I assigned outbound */
+ uint16_t list_of_streams[]; /* if not all list of streams */
+} SCTP_PACKED;
+
+struct sctp_stream_reset_in_request {
+ struct sctp_paramhdr ph;
+ uint32_t request_seq;
+ uint16_t list_of_streams[]; /* if not all list of streams */
+} SCTP_PACKED;
+
+struct sctp_stream_reset_tsn_request {
+ struct sctp_paramhdr ph;
+ uint32_t request_seq;
+} SCTP_PACKED;
+
+struct sctp_stream_reset_response {
+ struct sctp_paramhdr ph;
+ uint32_t response_seq; /* if a response, the resp seq no */
+ uint32_t result;
+} SCTP_PACKED;
+
+struct sctp_stream_reset_response_tsn {
+ struct sctp_paramhdr ph;
+ uint32_t response_seq; /* if a response, the resp seq no */
+ uint32_t result;
+ uint32_t senders_next_tsn;
+ uint32_t receivers_next_tsn;
+} SCTP_PACKED;
+
+struct sctp_stream_reset_add_strm {
+ struct sctp_paramhdr ph;
+ uint32_t request_seq;
+ uint16_t number_of_streams;
+ uint16_t reserved;
+} SCTP_PACKED;
+
+#define SCTP_STREAM_RESET_RESULT_NOTHING_TO_DO 0x00000000 /* XXX: unused */
+#define SCTP_STREAM_RESET_RESULT_PERFORMED 0x00000001
+#define SCTP_STREAM_RESET_RESULT_DENIED 0x00000002
+#define SCTP_STREAM_RESET_RESULT_ERR__WRONG_SSN 0x00000003 /* XXX: unused */
+#define SCTP_STREAM_RESET_RESULT_ERR_IN_PROGRESS 0x00000004
+#define SCTP_STREAM_RESET_RESULT_ERR_BAD_SEQNO 0x00000005
+#define SCTP_STREAM_RESET_RESULT_IN_PROGRESS 0x00000006 /* XXX: unused */
+
+/*
+ * convience structures, note that if you are making a request for specific
+ * streams then the request will need to be an overlay structure.
+ */
+
+struct sctp_stream_reset_tsn_req {
+ struct sctp_chunkhdr ch;
+ struct sctp_stream_reset_tsn_request sr_req;
+} SCTP_PACKED;
+
+struct sctp_stream_reset_resp {
+ struct sctp_chunkhdr ch;
+ struct sctp_stream_reset_response sr_resp;
+} SCTP_PACKED;
+
+/* respone only valid with a TSN request */
+struct sctp_stream_reset_resp_tsn {
+ struct sctp_chunkhdr ch;
+ struct sctp_stream_reset_response_tsn sr_resp;
+} SCTP_PACKED;
+
+/****************************************************/
+
+/*
+ * Authenticated chunks support draft-ietf-tsvwg-sctp-auth
+ */
+
+/* Should we make the max be 32? */
+#define SCTP_RANDOM_MAX_SIZE 256
+struct sctp_auth_random {
+ struct sctp_paramhdr ph;/* type = 0x8002 */
+ uint8_t random_data[];
+} SCTP_PACKED;
+
+struct sctp_auth_chunk_list {
+ struct sctp_paramhdr ph;/* type = 0x8003 */
+ uint8_t chunk_types[];
+} SCTP_PACKED;
+
+struct sctp_auth_hmac_algo {
+ struct sctp_paramhdr ph;/* type = 0x8004 */
+ uint16_t hmac_ids[];
+} SCTP_PACKED;
+
+struct sctp_auth_chunk {
+ struct sctp_chunkhdr ch;
+ uint16_t shared_key_id;
+ uint16_t hmac_id;
+ uint8_t hmac[];
+} SCTP_PACKED;
+
+/* Zero checksum support draft-ietf-tsvwg-sctp-zero-checksum */
+
+struct sctp_zero_checksum_acceptable {
+ struct sctp_paramhdr ph;
+ uint32_t edmid;
+} SCTP_PACKED;
+
+/*
+ * we pre-reserve enough room for a ECNE or CWR AND a SACK with no missing
+ * pieces. If ENCE is missing we could have a couple of blocks. This way we
+ * optimize so we MOST likely can bundle a SACK/ECN with the smallest size
+ * data chunk I will split into. We could increase throughput slightly by
+ * taking out these two but the 24-sack/8-CWR i.e. 32 bytes I pre-reserve I
+ * feel is worth it for now.
+ */
+#ifndef SCTP_MAX_OVERHEAD
+#ifdef INET6
+#define SCTP_MAX_OVERHEAD (sizeof(struct sctp_data_chunk) + \
+ sizeof(struct sctphdr) + \
+ sizeof(struct sctp_ecne_chunk) + \
+ sizeof(struct sctp_sack_chunk) + \
+ sizeof(struct ip6_hdr))
+
+#define SCTP_MED_OVERHEAD (sizeof(struct sctp_data_chunk) + \
+ sizeof(struct sctphdr) + \
+ sizeof(struct ip6_hdr))
+
+#define SCTP_MIN_OVERHEAD (sizeof(struct ip6_hdr) + \
+ sizeof(struct sctphdr))
+
+#else
+#define SCTP_MAX_OVERHEAD (sizeof(struct sctp_data_chunk) + \
+ sizeof(struct sctphdr) + \
+ sizeof(struct sctp_ecne_chunk) + \
+ sizeof(struct sctp_sack_chunk) + \
+ sizeof(struct ip))
+
+#define SCTP_MED_OVERHEAD (sizeof(struct sctp_data_chunk) + \
+ sizeof(struct sctphdr) + \
+ sizeof(struct ip))
+
+#define SCTP_MIN_OVERHEAD (sizeof(struct ip) + \
+ sizeof(struct sctphdr))
+
+#endif /* INET6 */
+#endif /* !SCTP_MAX_OVERHEAD */
+
+#define SCTP_MED_V4_OVERHEAD (sizeof(struct sctp_data_chunk) + \
+ sizeof(struct sctphdr) + \
+ sizeof(struct ip))
+
+#define SCTP_MIN_V4_OVERHEAD (sizeof(struct ip) + \
+ sizeof(struct sctphdr))
+
+#if defined(_WIN32) && !defined(__Userspace__)
+#include <packoff.h>
+#endif
+#if defined(_WIN32) && defined(__Userspace__)
+#pragma pack(pop)
+#endif
+#undef SCTP_PACKED
+#endif /* !__sctp_header_h__ */
diff --git a/netwerk/sctp/src/netinet/sctp_indata.c b/netwerk/sctp/src/netinet/sctp_indata.c
new file mode 100644
index 0000000000..91980d3a24
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_indata.c
@@ -0,0 +1,5791 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/proc.h>
+#endif
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_bsd_addr.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp_crc32.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/sctp_lock_bsd.h>
+#endif
+/*
+ * NOTES: On the outbound side of things I need to check the sack timer to
+ * see if I should generate a sack into the chunk queue (if I have data to
+ * send that is and will be sending it .. for bundling.
+ *
+ * The callback in sctp_usrreq.c will get called when the socket is read from.
+ * This will cause sctp_service_queues() to get called on the top entry in
+ * the list.
+ */
+static uint32_t
+sctp_add_chk_to_control(struct sctp_queued_to_read *control,
+ struct sctp_stream_in *strm,
+ struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_tmit_chunk *chk, int hold_rlock);
+
+void
+sctp_set_rwnd(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ asoc->my_rwnd = sctp_calc_rwnd(stcb, asoc);
+}
+
+/* Calculate what the rwnd would be */
+uint32_t
+sctp_calc_rwnd(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ uint32_t calc = 0;
+
+ /*
+ * This is really set wrong with respect to a 1-2-m socket. Since
+ * the sb_cc is the count that everyone as put up. When we re-write
+ * sctp_soreceive then we will fix this so that ONLY this
+ * associations data is taken into account.
+ */
+ if (stcb->sctp_socket == NULL) {
+ return (calc);
+ }
+
+ KASSERT(asoc->cnt_on_reasm_queue > 0 || asoc->size_on_reasm_queue == 0,
+ ("size_on_reasm_queue is %u", asoc->size_on_reasm_queue));
+ KASSERT(asoc->cnt_on_all_streams > 0 || asoc->size_on_all_streams == 0,
+ ("size_on_all_streams is %u", asoc->size_on_all_streams));
+ if (stcb->asoc.sb_cc == 0 &&
+ asoc->cnt_on_reasm_queue == 0 &&
+ asoc->cnt_on_all_streams == 0) {
+ /* Full rwnd granted */
+ calc = max(SCTP_SB_LIMIT_RCV(stcb->sctp_socket), SCTP_MINIMAL_RWND);
+ return (calc);
+ }
+ /* get actual space */
+ calc = (uint32_t) sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv);
+ /*
+ * take out what has NOT been put on socket queue and we yet hold
+ * for putting up.
+ */
+ calc = sctp_sbspace_sub(calc, (uint32_t)(asoc->size_on_reasm_queue +
+ asoc->cnt_on_reasm_queue * MSIZE));
+ calc = sctp_sbspace_sub(calc, (uint32_t)(asoc->size_on_all_streams +
+ asoc->cnt_on_all_streams * MSIZE));
+ if (calc == 0) {
+ /* out of space */
+ return (calc);
+ }
+
+ /* what is the overhead of all these rwnd's */
+ calc = sctp_sbspace_sub(calc, stcb->asoc.my_rwnd_control_len);
+ /* If the window gets too small due to ctrl-stuff, reduce it
+ * to 1, even it is 0. SWS engaged
+ */
+ if (calc < stcb->asoc.my_rwnd_control_len) {
+ calc = 1;
+ }
+ return (calc);
+}
+
+/*
+ * Build out our readq entry based on the incoming packet.
+ */
+struct sctp_queued_to_read *
+sctp_build_readq_entry(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ uint32_t tsn, uint32_t ppid,
+ uint32_t context, uint16_t sid,
+ uint32_t mid, uint8_t flags,
+ struct mbuf *dm)
+{
+ struct sctp_queued_to_read *read_queue_e = NULL;
+
+ sctp_alloc_a_readq(stcb, read_queue_e);
+ if (read_queue_e == NULL) {
+ goto failed_build;
+ }
+ memset(read_queue_e, 0, sizeof(struct sctp_queued_to_read));
+ read_queue_e->sinfo_stream = sid;
+ read_queue_e->sinfo_flags = (flags << 8);
+ read_queue_e->sinfo_ppid = ppid;
+ read_queue_e->sinfo_context = context;
+ read_queue_e->sinfo_tsn = tsn;
+ read_queue_e->sinfo_cumtsn = tsn;
+ read_queue_e->sinfo_assoc_id = sctp_get_associd(stcb);
+ read_queue_e->mid = mid;
+ read_queue_e->top_fsn = read_queue_e->fsn_included = 0xffffffff;
+ TAILQ_INIT(&read_queue_e->reasm);
+ read_queue_e->whoFrom = net;
+ atomic_add_int(&net->ref_count, 1);
+ read_queue_e->data = dm;
+ read_queue_e->stcb = stcb;
+ read_queue_e->port_from = stcb->rport;
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ read_queue_e->do_not_ref_stcb = 1;
+ }
+failed_build:
+ return (read_queue_e);
+}
+
+struct mbuf *
+sctp_build_ctl_nchunk(struct sctp_inpcb *inp, struct sctp_sndrcvinfo *sinfo)
+{
+ struct sctp_extrcvinfo *seinfo;
+ struct sctp_sndrcvinfo *outinfo;
+ struct sctp_rcvinfo *rcvinfo;
+ struct sctp_nxtinfo *nxtinfo;
+#if defined(_WIN32)
+ WSACMSGHDR *cmh;
+#else
+ struct cmsghdr *cmh;
+#endif
+ struct mbuf *ret;
+ int len;
+ int use_extended;
+ int provide_nxt;
+
+ if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT) &&
+ sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVRCVINFO) &&
+ sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVNXTINFO)) {
+ /* user does not want any ancillary data */
+ return (NULL);
+ }
+
+ len = 0;
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVRCVINFO)) {
+ len += CMSG_SPACE(sizeof(struct sctp_rcvinfo));
+ }
+ seinfo = (struct sctp_extrcvinfo *)sinfo;
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVNXTINFO) &&
+ (seinfo->serinfo_next_flags & SCTP_NEXT_MSG_AVAIL)) {
+ provide_nxt = 1;
+ len += CMSG_SPACE(sizeof(struct sctp_nxtinfo));
+ } else {
+ provide_nxt = 0;
+ }
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT)) {
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO)) {
+ use_extended = 1;
+ len += CMSG_SPACE(sizeof(struct sctp_extrcvinfo));
+ } else {
+ use_extended = 0;
+ len += CMSG_SPACE(sizeof(struct sctp_sndrcvinfo));
+ }
+ } else {
+ use_extended = 0;
+ }
+
+ ret = sctp_get_mbuf_for_msg(len, 0, M_NOWAIT, 1, MT_DATA);
+ if (ret == NULL) {
+ /* No space */
+ return (ret);
+ }
+ SCTP_BUF_LEN(ret) = 0;
+
+ /* We need a CMSG header followed by the struct */
+#if defined(_WIN32)
+ cmh = mtod(ret, WSACMSGHDR *);
+#else
+ cmh = mtod(ret, struct cmsghdr *);
+#endif
+ /*
+ * Make sure that there is no un-initialized padding between
+ * the cmsg header and cmsg data and after the cmsg data.
+ */
+ memset(cmh, 0, len);
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVRCVINFO)) {
+ cmh->cmsg_level = IPPROTO_SCTP;
+ cmh->cmsg_len = CMSG_LEN(sizeof(struct sctp_rcvinfo));
+ cmh->cmsg_type = SCTP_RCVINFO;
+ rcvinfo = (struct sctp_rcvinfo *)CMSG_DATA(cmh);
+ rcvinfo->rcv_sid = sinfo->sinfo_stream;
+ rcvinfo->rcv_ssn = sinfo->sinfo_ssn;
+ rcvinfo->rcv_flags = sinfo->sinfo_flags;
+ rcvinfo->rcv_ppid = sinfo->sinfo_ppid;
+ rcvinfo->rcv_tsn = sinfo->sinfo_tsn;
+ rcvinfo->rcv_cumtsn = sinfo->sinfo_cumtsn;
+ rcvinfo->rcv_context = sinfo->sinfo_context;
+ rcvinfo->rcv_assoc_id = sinfo->sinfo_assoc_id;
+#if defined(_WIN32)
+ cmh = (WSACMSGHDR *)((caddr_t)cmh + CMSG_SPACE(sizeof(struct sctp_rcvinfo)));
+#else
+ cmh = (struct cmsghdr *)((caddr_t)cmh + CMSG_SPACE(sizeof(struct sctp_rcvinfo)));
+#endif
+ SCTP_BUF_LEN(ret) += CMSG_SPACE(sizeof(struct sctp_rcvinfo));
+ }
+ if (provide_nxt) {
+ cmh->cmsg_level = IPPROTO_SCTP;
+ cmh->cmsg_len = CMSG_LEN(sizeof(struct sctp_nxtinfo));
+ cmh->cmsg_type = SCTP_NXTINFO;
+ nxtinfo = (struct sctp_nxtinfo *)CMSG_DATA(cmh);
+ nxtinfo->nxt_sid = seinfo->serinfo_next_stream;
+ nxtinfo->nxt_flags = 0;
+ if (seinfo->serinfo_next_flags & SCTP_NEXT_MSG_IS_UNORDERED) {
+ nxtinfo->nxt_flags |= SCTP_UNORDERED;
+ }
+ if (seinfo->serinfo_next_flags & SCTP_NEXT_MSG_IS_NOTIFICATION) {
+ nxtinfo->nxt_flags |= SCTP_NOTIFICATION;
+ }
+ if (seinfo->serinfo_next_flags & SCTP_NEXT_MSG_ISCOMPLETE) {
+ nxtinfo->nxt_flags |= SCTP_COMPLETE;
+ }
+ nxtinfo->nxt_ppid = seinfo->serinfo_next_ppid;
+ nxtinfo->nxt_length = seinfo->serinfo_next_length;
+ nxtinfo->nxt_assoc_id = seinfo->serinfo_next_aid;
+#if defined(_WIN32)
+ cmh = (WSACMSGHDR *)((caddr_t)cmh + CMSG_SPACE(sizeof(struct sctp_nxtinfo)));
+#else
+ cmh = (struct cmsghdr *)((caddr_t)cmh + CMSG_SPACE(sizeof(struct sctp_nxtinfo)));
+#endif
+ SCTP_BUF_LEN(ret) += CMSG_SPACE(sizeof(struct sctp_nxtinfo));
+ }
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT)) {
+ cmh->cmsg_level = IPPROTO_SCTP;
+ outinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmh);
+ if (use_extended) {
+ cmh->cmsg_len = CMSG_LEN(sizeof(struct sctp_extrcvinfo));
+ cmh->cmsg_type = SCTP_EXTRCV;
+ memcpy(outinfo, sinfo, sizeof(struct sctp_extrcvinfo));
+ SCTP_BUF_LEN(ret) += CMSG_SPACE(sizeof(struct sctp_extrcvinfo));
+ } else {
+ cmh->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo));
+ cmh->cmsg_type = SCTP_SNDRCV;
+ *outinfo = *sinfo;
+ SCTP_BUF_LEN(ret) += CMSG_SPACE(sizeof(struct sctp_sndrcvinfo));
+ }
+ }
+ return (ret);
+}
+
+static void
+sctp_mark_non_revokable(struct sctp_association *asoc, uint32_t tsn)
+{
+ uint32_t gap, i;
+ int in_r, in_nr;
+
+ if (SCTP_BASE_SYSCTL(sctp_do_drain) == 0) {
+ return;
+ }
+ if (SCTP_TSN_GE(asoc->cumulative_tsn, tsn)) {
+ /*
+ * This tsn is behind the cum ack and thus we don't
+ * need to worry about it being moved from one to the other.
+ */
+ return;
+ }
+ SCTP_CALC_TSN_TO_GAP(gap, tsn, asoc->mapping_array_base_tsn);
+ in_r = SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap);
+ in_nr = SCTP_IS_TSN_PRESENT(asoc->nr_mapping_array, gap);
+ KASSERT(in_r || in_nr, ("%s: Things are really messed up now", __func__));
+ if (!in_nr) {
+ SCTP_SET_TSN_PRESENT(asoc->nr_mapping_array, gap);
+ if (SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_nr_map)) {
+ asoc->highest_tsn_inside_nr_map = tsn;
+ }
+ }
+ if (in_r) {
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ if (tsn == asoc->highest_tsn_inside_map) {
+ /* We must back down to see what the new highest is. */
+ for (i = tsn - 1; SCTP_TSN_GE(i, asoc->mapping_array_base_tsn); i--) {
+ SCTP_CALC_TSN_TO_GAP(gap, i, asoc->mapping_array_base_tsn);
+ if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap)) {
+ asoc->highest_tsn_inside_map = i;
+ break;
+ }
+ }
+ if (!SCTP_TSN_GE(i, asoc->mapping_array_base_tsn)) {
+ asoc->highest_tsn_inside_map = asoc->mapping_array_base_tsn - 1;
+ }
+ }
+ }
+}
+
+static int
+sctp_place_control_in_stream(struct sctp_stream_in *strm,
+ struct sctp_association *asoc,
+ struct sctp_queued_to_read *control)
+{
+ struct sctp_queued_to_read *at;
+ struct sctp_readhead *q;
+ uint8_t flags, unordered;
+
+ flags = (control->sinfo_flags >> 8);
+ unordered = flags & SCTP_DATA_UNORDERED;
+ if (unordered) {
+ q = &strm->uno_inqueue;
+ if (asoc->idata_supported == 0) {
+ if (!TAILQ_EMPTY(q)) {
+ /* Only one stream can be here in old style -- abort */
+ return (-1);
+ }
+ TAILQ_INSERT_TAIL(q, control, next_instrm);
+ control->on_strm_q = SCTP_ON_UNORDERED;
+ return (0);
+ }
+ } else {
+ q = &strm->inqueue;
+ }
+ if ((flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG) {
+ control->end_added = 1;
+ control->first_frag_seen = 1;
+ control->last_frag_seen = 1;
+ }
+ if (TAILQ_EMPTY(q)) {
+ /* Empty queue */
+ TAILQ_INSERT_HEAD(q, control, next_instrm);
+ if (unordered) {
+ control->on_strm_q = SCTP_ON_UNORDERED;
+ } else {
+ control->on_strm_q = SCTP_ON_ORDERED;
+ }
+ return (0);
+ } else {
+ TAILQ_FOREACH(at, q, next_instrm) {
+ if (SCTP_MID_GT(asoc->idata_supported, at->mid, control->mid)) {
+ /*
+ * one in queue is bigger than the
+ * new one, insert before this one
+ */
+ TAILQ_INSERT_BEFORE(at, control, next_instrm);
+ if (unordered) {
+ control->on_strm_q = SCTP_ON_UNORDERED;
+ } else {
+ control->on_strm_q = SCTP_ON_ORDERED;
+ }
+ break;
+ } else if (SCTP_MID_EQ(asoc->idata_supported, at->mid, control->mid)) {
+ /*
+ * Gak, He sent me a duplicate msg
+ * id number?? return -1 to abort.
+ */
+ return (-1);
+ } else {
+ if (TAILQ_NEXT(at, next_instrm) == NULL) {
+ /*
+ * We are at the end, insert
+ * it after this one
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del(control, at,
+ SCTP_STR_LOG_FROM_INSERT_TL);
+ }
+ TAILQ_INSERT_AFTER(q, at, control, next_instrm);
+ if (unordered) {
+ control->on_strm_q = SCTP_ON_UNORDERED;
+ } else {
+ control->on_strm_q = SCTP_ON_ORDERED;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return (0);
+}
+
+static void
+sctp_abort_in_reasm(struct sctp_tcb *stcb,
+ struct sctp_queued_to_read *control,
+ struct sctp_tmit_chunk *chk,
+ int *abort_flag, int opspot)
+{
+ char msg[SCTP_DIAG_INFO_LEN];
+ struct mbuf *oper;
+
+ if (stcb->asoc.idata_supported) {
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "Reass %x,CF:%x,TSN=%8.8x,SID=%4.4x,FSN=%8.8x,MID:%8.8x",
+ opspot,
+ control->fsn_included,
+ chk->rec.data.tsn,
+ chk->rec.data.sid,
+ chk->rec.data.fsn, chk->rec.data.mid);
+ } else {
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "Reass %x,CI:%x,TSN=%8.8x,SID=%4.4x,FSN=%4.4x,SSN:%4.4x",
+ opspot,
+ control->fsn_included,
+ chk->rec.data.tsn,
+ chk->rec.data.sid,
+ chk->rec.data.fsn,
+ (uint16_t)chk->rec.data.mid);
+ }
+ oper = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_1;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, oper, false, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+}
+
+static void
+sctp_clean_up_control(struct sctp_tcb *stcb, struct sctp_queued_to_read *control)
+{
+ /*
+ * The control could not be placed and must be cleaned.
+ */
+ struct sctp_tmit_chunk *chk, *nchk;
+ TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
+ TAILQ_REMOVE(&control->reasm, chk, sctp_next);
+ if (chk->data)
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ sctp_free_remote_addr(control->whoFrom);
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+}
+
+/*
+ * Queue the chunk either right into the socket buffer if it is the next one
+ * to go OR put it in the correct place in the delivery queue. If we do
+ * append to the so_buf, keep doing so until we are out of order as
+ * long as the control's entered are non-fragmented.
+ */
+static void
+sctp_queue_data_to_stream(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_queued_to_read *control, int *abort_flag, int *need_reasm)
+{
+ /*
+ * FIX-ME maybe? What happens when the ssn wraps? If we are getting
+ * all the data in one stream this could happen quite rapidly. One
+ * could use the TSN to keep track of things, but this scheme breaks
+ * down in the other type of stream usage that could occur. Send a
+ * single msg to stream 0, send 4Billion messages to stream 1, now
+ * send a message to stream 0. You have a situation where the TSN
+ * has wrapped but not in the stream. Is this worth worrying about
+ * or should we just change our queue sort at the bottom to be by
+ * TSN.
+ *
+ * Could it also be legal for a peer to send ssn 1 with TSN 2 and ssn 2
+ * with TSN 1? If the peer is doing some sort of funky TSN/SSN
+ * assignment this could happen... and I don't see how this would be
+ * a violation. So for now I am undecided an will leave the sort by
+ * SSN alone. Maybe a hybrid approach is the answer
+ *
+ */
+ struct sctp_queued_to_read *at;
+ int queue_needed;
+ uint32_t nxt_todel;
+ struct mbuf *op_err;
+ struct sctp_stream_in *strm;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ strm = &asoc->strmin[control->sinfo_stream];
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_INTO_STRD);
+ }
+ if (SCTP_MID_GT((asoc->idata_supported), strm->last_mid_delivered, control->mid)) {
+ /* The incoming sseq is behind where we last delivered? */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Duplicate S-SEQ: %u delivered: %u from peer, Abort association\n",
+ strm->last_mid_delivered, control->mid);
+ /*
+ * throw it in the stream so it gets cleaned up in
+ * association destruction
+ */
+ TAILQ_INSERT_HEAD(&strm->inqueue, control, next_instrm);
+ if (asoc->idata_supported) {
+ SCTP_SNPRINTF(msg, sizeof(msg), "Delivered MID=%8.8x, got TSN=%8.8x, SID=%4.4x, MID=%8.8x",
+ strm->last_mid_delivered, control->sinfo_tsn,
+ control->sinfo_stream, control->mid);
+ } else {
+ SCTP_SNPRINTF(msg, sizeof(msg), "Delivered SSN=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ (uint16_t)strm->last_mid_delivered,
+ control->sinfo_tsn,
+ control->sinfo_stream,
+ (uint16_t)control->mid);
+ }
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_2;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ queue_needed = 1;
+ asoc->size_on_all_streams += control->length;
+ sctp_ucount_incr(asoc->cnt_on_all_streams);
+ nxt_todel = strm->last_mid_delivered + 1;
+ if (SCTP_MID_EQ(asoc->idata_supported, nxt_todel, control->mid)) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+#endif
+ /* can be delivered right away? */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_IMMED_DEL);
+ }
+ /* EY it wont be queued if it could be delivered directly */
+ queue_needed = 0;
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ strm->last_mid_delivered++;
+ sctp_mark_non_revokable(asoc, control->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_NOT_HELD, SCTP_SO_LOCKED);
+ TAILQ_FOREACH_SAFE(control, &strm->inqueue, next_instrm, at) {
+ /* all delivered */
+ nxt_todel = strm->last_mid_delivered + 1;
+ if (SCTP_MID_EQ(asoc->idata_supported, nxt_todel, control->mid) &&
+ (((control->sinfo_flags >> 8) & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG)) {
+ if (control->on_strm_q == SCTP_ON_ORDERED) {
+ TAILQ_REMOVE(&strm->inqueue, control, next_instrm);
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+#ifdef INVARIANTS
+ } else {
+ panic("Huh control: %p is on_strm_q: %d",
+ control, control->on_strm_q);
+#endif
+ }
+ control->on_strm_q = 0;
+ strm->last_mid_delivered++;
+ /*
+ * We ignore the return of deliver_data here
+ * since we always can hold the chunk on the
+ * d-queue. And we have a finite number that
+ * can be delivered from the strq.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del(control, NULL,
+ SCTP_STR_LOG_FROM_IMMED_DEL);
+ }
+ sctp_mark_non_revokable(asoc, control->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_NOT_HELD,
+ SCTP_SO_LOCKED);
+ continue;
+ } else if (SCTP_MID_EQ(asoc->idata_supported, nxt_todel, control->mid)) {
+ *need_reasm = 1;
+ }
+ break;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ if (queue_needed) {
+ /*
+ * Ok, we did not deliver this guy, find the correct place
+ * to put it on the queue.
+ */
+ if (sctp_place_control_in_stream(strm, asoc, control)) {
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "Queue to str MID: %u duplicate", control->mid);
+ sctp_clean_up_control(stcb, control);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_3;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ }
+ }
+}
+
+static void
+sctp_setup_tail_pointer(struct sctp_queued_to_read *control)
+{
+ struct mbuf *m, *prev = NULL;
+ struct sctp_tcb *stcb;
+
+ stcb = control->stcb;
+ control->held_length = 0;
+ control->length = 0;
+ m = control->data;
+ while (m) {
+ if (SCTP_BUF_LEN(m) == 0) {
+ /* Skip mbufs with NO length */
+ if (prev == NULL) {
+ /* First one */
+ control->data = sctp_m_free(m);
+ m = control->data;
+ } else {
+ SCTP_BUF_NEXT(prev) = sctp_m_free(m);
+ m = SCTP_BUF_NEXT(prev);
+ }
+ if (m == NULL) {
+ control->tail_mbuf = prev;
+ }
+ continue;
+ }
+ prev = m;
+ atomic_add_int(&control->length, SCTP_BUF_LEN(m));
+ if (control->on_read_q) {
+ /*
+ * On read queue so we must increment the
+ * SB stuff, we assume caller has done any locks of SB.
+ */
+ sctp_sballoc(stcb, &stcb->sctp_socket->so_rcv, m);
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ if (prev) {
+ control->tail_mbuf = prev;
+ }
+}
+
+static void
+sctp_add_to_tail_pointer(struct sctp_queued_to_read *control, struct mbuf *m, uint32_t *added)
+{
+ struct mbuf *prev=NULL;
+ struct sctp_tcb *stcb;
+
+ stcb = control->stcb;
+ if (stcb == NULL) {
+#ifdef INVARIANTS
+ panic("Control broken");
+#else
+ return;
+#endif
+ }
+ if (control->tail_mbuf == NULL) {
+ /* TSNH */
+ sctp_m_freem(control->data);
+ control->data = m;
+ sctp_setup_tail_pointer(control);
+ return;
+ }
+ control->tail_mbuf->m_next = m;
+ while (m) {
+ if (SCTP_BUF_LEN(m) == 0) {
+ /* Skip mbufs with NO length */
+ if (prev == NULL) {
+ /* First one */
+ control->tail_mbuf->m_next = sctp_m_free(m);
+ m = control->tail_mbuf->m_next;
+ } else {
+ SCTP_BUF_NEXT(prev) = sctp_m_free(m);
+ m = SCTP_BUF_NEXT(prev);
+ }
+ if (m == NULL) {
+ control->tail_mbuf = prev;
+ }
+ continue;
+ }
+ prev = m;
+ if (control->on_read_q) {
+ /*
+ * On read queue so we must increment the
+ * SB stuff, we assume caller has done any locks of SB.
+ */
+ sctp_sballoc(stcb, &stcb->sctp_socket->so_rcv, m);
+ }
+ *added += SCTP_BUF_LEN(m);
+ atomic_add_int(&control->length, SCTP_BUF_LEN(m));
+ m = SCTP_BUF_NEXT(m);
+ }
+ if (prev) {
+ control->tail_mbuf = prev;
+ }
+}
+
+static void
+sctp_build_readq_entry_from_ctl(struct sctp_queued_to_read *nc, struct sctp_queued_to_read *control)
+{
+ memset(nc, 0, sizeof(struct sctp_queued_to_read));
+ nc->sinfo_stream = control->sinfo_stream;
+ nc->mid = control->mid;
+ TAILQ_INIT(&nc->reasm);
+ nc->top_fsn = control->top_fsn;
+ nc->mid = control->mid;
+ nc->sinfo_flags = control->sinfo_flags;
+ nc->sinfo_ppid = control->sinfo_ppid;
+ nc->sinfo_context = control->sinfo_context;
+ nc->fsn_included = 0xffffffff;
+ nc->sinfo_tsn = control->sinfo_tsn;
+ nc->sinfo_cumtsn = control->sinfo_cumtsn;
+ nc->sinfo_assoc_id = control->sinfo_assoc_id;
+ nc->whoFrom = control->whoFrom;
+ atomic_add_int(&nc->whoFrom->ref_count, 1);
+ nc->stcb = control->stcb;
+ nc->port_from = control->port_from;
+ nc->do_not_ref_stcb = control->do_not_ref_stcb;
+}
+
+static void
+sctp_reset_a_control(struct sctp_queued_to_read *control,
+ struct sctp_inpcb *inp, uint32_t tsn)
+{
+ control->fsn_included = tsn;
+ if (control->on_read_q) {
+ /*
+ * We have to purge it from there,
+ * hopefully this will work :-)
+ */
+ TAILQ_REMOVE(&inp->read_queue, control, next);
+ control->on_read_q = 0;
+ }
+}
+
+static int
+sctp_handle_old_unordered_data(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_stream_in *strm,
+ struct sctp_queued_to_read *control,
+ uint32_t pd_point,
+ int inp_read_lock_held)
+{
+ /* Special handling for the old un-ordered data chunk.
+ * All the chunks/TSN's go to mid 0. So
+ * we have to do the old style watching to see
+ * if we have it all. If you return one, no other
+ * control entries on the un-ordered queue will
+ * be looked at. In theory there should be no others
+ * entries in reality, unless the guy is sending both
+ * unordered NDATA and unordered DATA...
+ */
+ struct sctp_tmit_chunk *chk, *lchk, *tchk;
+ uint32_t fsn;
+ struct sctp_queued_to_read *nc;
+ int cnt_added;
+
+ if (control->first_frag_seen == 0) {
+ /* Nothing we can do, we have not seen the first piece yet */
+ return (1);
+ }
+ /* Collapse any we can */
+ cnt_added = 0;
+restart:
+ fsn = control->fsn_included + 1;
+ /* Now what can we add? */
+ TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, lchk) {
+ if (chk->rec.data.fsn == fsn) {
+ /* Ok lets add it */
+ sctp_alloc_a_readq(stcb, nc);
+ if (nc == NULL) {
+ break;
+ }
+ memset(nc, 0, sizeof(struct sctp_queued_to_read));
+ TAILQ_REMOVE(&control->reasm, chk, sctp_next);
+ sctp_add_chk_to_control(control, strm, stcb, asoc, chk, inp_read_lock_held);
+ fsn++;
+ cnt_added++;
+ chk = NULL;
+ if (control->end_added) {
+ /* We are done */
+ if (!TAILQ_EMPTY(&control->reasm)) {
+ /*
+ * Ok we have to move anything left on
+ * the control queue to a new control.
+ */
+ sctp_build_readq_entry_from_ctl(nc, control);
+ tchk = TAILQ_FIRST(&control->reasm);
+ if (tchk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+ TAILQ_REMOVE(&control->reasm, tchk, sctp_next);
+ if (asoc->size_on_reasm_queue >= tchk->send_size) {
+ asoc->size_on_reasm_queue -= tchk->send_size;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_reasm_queue = %u smaller than chunk length %u", asoc->size_on_reasm_queue, tchk->send_size);
+#else
+ asoc->size_on_reasm_queue = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ nc->first_frag_seen = 1;
+ nc->fsn_included = tchk->rec.data.fsn;
+ nc->data = tchk->data;
+ nc->sinfo_ppid = tchk->rec.data.ppid;
+ nc->sinfo_tsn = tchk->rec.data.tsn;
+ sctp_mark_non_revokable(asoc, tchk->rec.data.tsn);
+ tchk->data = NULL;
+ sctp_free_a_chunk(stcb, tchk, SCTP_SO_NOT_LOCKED);
+ sctp_setup_tail_pointer(nc);
+ tchk = TAILQ_FIRST(&control->reasm);
+ }
+ /* Spin the rest onto the queue */
+ while (tchk) {
+ TAILQ_REMOVE(&control->reasm, tchk, sctp_next);
+ TAILQ_INSERT_TAIL(&nc->reasm, tchk, sctp_next);
+ tchk = TAILQ_FIRST(&control->reasm);
+ }
+ /* Now lets add it to the queue after removing control */
+ TAILQ_INSERT_TAIL(&strm->uno_inqueue, nc, next_instrm);
+ nc->on_strm_q = SCTP_ON_UNORDERED;
+ if (control->on_strm_q) {
+ TAILQ_REMOVE(&strm->uno_inqueue, control, next_instrm);
+ control->on_strm_q = 0;
+ }
+ }
+ if (control->pdapi_started) {
+ strm->pd_api_started = 0;
+ control->pdapi_started = 0;
+ }
+ if (control->on_strm_q) {
+ TAILQ_REMOVE(&strm->uno_inqueue, control, next_instrm);
+ control->on_strm_q = 0;
+ SCTP_STAT_INCR_COUNTER64(sctps_reasmusrmsgs);
+ }
+ if (control->on_read_q == 0) {
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, control->end_added,
+ inp_read_lock_held, SCTP_SO_NOT_LOCKED);
+#if defined(__Userspace__)
+ } else {
+ sctp_invoke_recv_callback(stcb->sctp_ep, stcb, control, inp_read_lock_held);
+#endif
+ }
+ sctp_wakeup_the_read_socket(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ if ((nc->first_frag_seen) && !TAILQ_EMPTY(&nc->reasm)) {
+ /* Switch to the new guy and continue */
+ control = nc;
+ goto restart;
+ } else {
+ if (nc->on_strm_q == 0) {
+ sctp_free_a_readq(stcb, nc);
+ }
+ }
+ return (1);
+ } else {
+ sctp_free_a_readq(stcb, nc);
+ }
+ } else {
+ /* Can't add more */
+ break;
+ }
+ }
+ if (cnt_added && strm->pd_api_started) {
+#if defined(__Userspace__)
+ sctp_invoke_recv_callback(stcb->sctp_ep, stcb, control, inp_read_lock_held);
+#endif
+ sctp_wakeup_the_read_socket(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ }
+ if ((control->length > pd_point) && (strm->pd_api_started == 0)) {
+ strm->pd_api_started = 1;
+ control->pdapi_started = 1;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, control->end_added,
+ inp_read_lock_held, SCTP_SO_NOT_LOCKED);
+ sctp_wakeup_the_read_socket(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ return (0);
+ } else {
+ return (1);
+ }
+}
+
+static void
+sctp_inject_old_unordered_data(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_queued_to_read *control,
+ struct sctp_tmit_chunk *chk,
+ int *abort_flag)
+{
+ struct sctp_tmit_chunk *at;
+ int inserted;
+ /*
+ * Here we need to place the chunk into the control structure
+ * sorted in the correct order.
+ */
+ if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+ /* Its the very first one. */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "chunk is a first fsn: %u becomes fsn_included\n",
+ chk->rec.data.fsn);
+ at = TAILQ_FIRST(&control->reasm);
+ if (at && SCTP_TSN_GT(chk->rec.data.fsn, at->rec.data.fsn)) {
+ /*
+ * The first chunk in the reassembly is
+ * a smaller TSN than this one, even though
+ * this has a first, it must be from a subsequent
+ * msg.
+ */
+ goto place_chunk;
+ }
+ if (control->first_frag_seen) {
+ /*
+ * In old un-ordered we can reassembly on
+ * one control multiple messages. As long
+ * as the next FIRST is greater then the old
+ * first (TSN i.e. FSN wise)
+ */
+ struct mbuf *tdata;
+ uint32_t tmp;
+
+ if (SCTP_TSN_GT(chk->rec.data.fsn, control->fsn_included)) {
+ /* Easy way the start of a new guy beyond the lowest */
+ goto place_chunk;
+ }
+ if ((chk->rec.data.fsn == control->fsn_included) ||
+ (control->pdapi_started)) {
+ /*
+ * Ok this should not happen, if it does
+ * we started the pd-api on the higher TSN (since
+ * the equals part is a TSN failure it must be that).
+ *
+ * We are completely hosed in that case since I have
+ * no way to recover. This really will only happen
+ * if we can get more TSN's higher before the pd-api-point.
+ */
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_4);
+
+ return;
+ }
+ /*
+ * Ok we have two firsts and the one we just got
+ * is smaller than the one we previously placed.. yuck!
+ * We must swap them out.
+ */
+ /* swap the mbufs */
+ tdata = control->data;
+ control->data = chk->data;
+ chk->data = tdata;
+ /* Save the lengths */
+ chk->send_size = control->length;
+ /* Recompute length of control and tail pointer */
+ sctp_setup_tail_pointer(control);
+ /* Fix the FSN included */
+ tmp = control->fsn_included;
+ control->fsn_included = chk->rec.data.fsn;
+ chk->rec.data.fsn = tmp;
+ /* Fix the TSN included */
+ tmp = control->sinfo_tsn;
+ control->sinfo_tsn = chk->rec.data.tsn;
+ chk->rec.data.tsn = tmp;
+ /* Fix the PPID included */
+ tmp = control->sinfo_ppid;
+ control->sinfo_ppid = chk->rec.data.ppid;
+ chk->rec.data.ppid = tmp;
+ /* Fix tail pointer */
+ goto place_chunk;
+ }
+ control->first_frag_seen = 1;
+ control->fsn_included = chk->rec.data.fsn;
+ control->top_fsn = chk->rec.data.fsn;
+ control->sinfo_tsn = chk->rec.data.tsn;
+ control->sinfo_ppid = chk->rec.data.ppid;
+ control->data = chk->data;
+ sctp_mark_non_revokable(asoc, chk->rec.data.tsn);
+ chk->data = NULL;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ sctp_setup_tail_pointer(control);
+ return;
+ }
+place_chunk:
+ inserted = 0;
+ TAILQ_FOREACH(at, &control->reasm, sctp_next) {
+ if (SCTP_TSN_GT(at->rec.data.fsn, chk->rec.data.fsn)) {
+ /*
+ * This one in queue is bigger than the new one, insert
+ * the new one before at.
+ */
+ asoc->size_on_reasm_queue += chk->send_size;
+ sctp_ucount_incr(asoc->cnt_on_reasm_queue);
+ inserted = 1;
+ TAILQ_INSERT_BEFORE(at, chk, sctp_next);
+ break;
+ } else if (at->rec.data.fsn == chk->rec.data.fsn) {
+ /*
+ * They sent a duplicate fsn number. This
+ * really should not happen since the FSN is
+ * a TSN and it should have been dropped earlier.
+ */
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_5);
+ return;
+ }
+ }
+ if (inserted == 0) {
+ /* Its at the end */
+ asoc->size_on_reasm_queue += chk->send_size;
+ sctp_ucount_incr(asoc->cnt_on_reasm_queue);
+ control->top_fsn = chk->rec.data.fsn;
+ TAILQ_INSERT_TAIL(&control->reasm, chk, sctp_next);
+ }
+}
+
+static int
+sctp_deliver_reasm_check(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_in *strm, int inp_read_lock_held)
+{
+ /*
+ * Given a stream, strm, see if any of
+ * the SSN's on it that are fragmented
+ * are ready to deliver. If so go ahead
+ * and place them on the read queue. In
+ * so placing if we have hit the end, then
+ * we need to remove them from the stream's queue.
+ */
+ struct sctp_queued_to_read *control, *nctl = NULL;
+ uint32_t next_to_del;
+ uint32_t pd_point;
+ int ret = 0;
+
+ if (stcb->sctp_socket) {
+ pd_point = min(SCTP_SB_LIMIT_RCV(stcb->sctp_socket) >> SCTP_PARTIAL_DELIVERY_SHIFT,
+ stcb->sctp_ep->partial_delivery_point);
+ } else {
+ pd_point = stcb->sctp_ep->partial_delivery_point;
+ }
+ control = TAILQ_FIRST(&strm->uno_inqueue);
+
+ if ((control != NULL) &&
+ (asoc->idata_supported == 0)) {
+ /* Special handling needed for "old" data format */
+ if (sctp_handle_old_unordered_data(stcb, asoc, strm, control, pd_point, inp_read_lock_held)) {
+ goto done_un;
+ }
+ }
+ if (strm->pd_api_started) {
+ /* Can't add more */
+ return (0);
+ }
+ while (control) {
+ SCTPDBG(SCTP_DEBUG_XXX, "Looking at control: %p e(%d) ssn: %u top_fsn: %u inc_fsn: %u -uo\n",
+ control, control->end_added, control->mid, control->top_fsn, control->fsn_included);
+ nctl = TAILQ_NEXT(control, next_instrm);
+ if (control->end_added) {
+ /* We just put the last bit on */
+ if (control->on_strm_q) {
+#ifdef INVARIANTS
+ if (control->on_strm_q != SCTP_ON_UNORDERED) {
+ panic("Huh control: %p on_q: %d -- not unordered?",
+ control, control->on_strm_q);
+ }
+#endif
+ SCTP_STAT_INCR_COUNTER64(sctps_reasmusrmsgs);
+ TAILQ_REMOVE(&strm->uno_inqueue, control, next_instrm);
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ control->on_strm_q = 0;
+ }
+ if (control->on_read_q == 0) {
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, control->end_added,
+ inp_read_lock_held, SCTP_SO_NOT_LOCKED);
+ }
+ } else {
+ /* Can we do a PD-API for this un-ordered guy? */
+ if ((control->length >= pd_point) && (strm->pd_api_started == 0)) {
+ strm->pd_api_started = 1;
+ control->pdapi_started = 1;
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, control->end_added,
+ inp_read_lock_held, SCTP_SO_NOT_LOCKED);
+
+ break;
+ }
+ }
+ control = nctl;
+ }
+done_un:
+ control = TAILQ_FIRST(&strm->inqueue);
+ if (strm->pd_api_started) {
+ /* Can't add more */
+ return (0);
+ }
+ if (control == NULL) {
+ return (ret);
+ }
+ if (SCTP_MID_EQ(asoc->idata_supported, strm->last_mid_delivered, control->mid)) {
+ /* Ok the guy at the top was being partially delivered
+ * completed, so we remove it. Note
+ * the pd_api flag was taken off when the
+ * chunk was merged on in sctp_queue_data_for_reasm below.
+ */
+ nctl = TAILQ_NEXT(control, next_instrm);
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Looking at control: %p e(%d) ssn: %u top_fsn: %u inc_fsn: %u (lastdel: %u)- o\n",
+ control, control->end_added, control->mid,
+ control->top_fsn, control->fsn_included,
+ strm->last_mid_delivered);
+ if (control->end_added) {
+ if (control->on_strm_q) {
+#ifdef INVARIANTS
+ if (control->on_strm_q != SCTP_ON_ORDERED) {
+ panic("Huh control: %p on_q: %d -- not ordered?",
+ control, control->on_strm_q);
+ }
+#endif
+ SCTP_STAT_INCR_COUNTER64(sctps_reasmusrmsgs);
+ TAILQ_REMOVE(&strm->inqueue, control, next_instrm);
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ control->on_strm_q = 0;
+ }
+ if (strm->pd_api_started && control->pdapi_started) {
+ control->pdapi_started = 0;
+ strm->pd_api_started = 0;
+ }
+ if (control->on_read_q == 0) {
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, control->end_added,
+ inp_read_lock_held, SCTP_SO_NOT_LOCKED);
+ }
+ control = nctl;
+ }
+ }
+ if (strm->pd_api_started) {
+ /* Can't add more must have gotten an un-ordered above being partially delivered. */
+ return (0);
+ }
+deliver_more:
+ next_to_del = strm->last_mid_delivered + 1;
+ if (control) {
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Looking at control: %p e(%d) ssn: %u top_fsn: %u inc_fsn: %u (nxtdel: %u)- o\n",
+ control, control->end_added, control->mid, control->top_fsn, control->fsn_included,
+ next_to_del);
+ nctl = TAILQ_NEXT(control, next_instrm);
+ if (SCTP_MID_EQ(asoc->idata_supported, control->mid, next_to_del) &&
+ (control->first_frag_seen)) {
+ int done;
+
+ /* Ok we can deliver it onto the stream. */
+ if (control->end_added) {
+ /* We are done with it afterwards */
+ if (control->on_strm_q) {
+#ifdef INVARIANTS
+ if (control->on_strm_q != SCTP_ON_ORDERED) {
+ panic("Huh control: %p on_q: %d -- not ordered?",
+ control, control->on_strm_q);
+ }
+#endif
+ SCTP_STAT_INCR_COUNTER64(sctps_reasmusrmsgs);
+ TAILQ_REMOVE(&strm->inqueue, control, next_instrm);
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ control->on_strm_q = 0;
+ }
+ ret++;
+ }
+ if (((control->sinfo_flags >> 8) & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG) {
+ /* A singleton now slipping through - mark it non-revokable too */
+ sctp_mark_non_revokable(asoc, control->sinfo_tsn);
+ } else if (control->end_added == 0) {
+ /* Check if we can defer adding until its all there */
+ if ((control->length < pd_point) || (strm->pd_api_started)) {
+ /* Don't need it or cannot add more (one being delivered that way) */
+ goto out;
+ }
+ }
+ done = (control->end_added) && (control->last_frag_seen);
+ if (control->on_read_q == 0) {
+ if (!done) {
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ strm->pd_api_started = 1;
+ control->pdapi_started = 1;
+ }
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, control->end_added,
+ inp_read_lock_held, SCTP_SO_NOT_LOCKED);
+ }
+ strm->last_mid_delivered = next_to_del;
+ if (done) {
+ control = nctl;
+ goto deliver_more;
+ }
+ }
+ }
+out:
+ return (ret);
+}
+
+uint32_t
+sctp_add_chk_to_control(struct sctp_queued_to_read *control,
+ struct sctp_stream_in *strm,
+ struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_tmit_chunk *chk, int hold_rlock)
+{
+ /*
+ * Given a control and a chunk, merge the
+ * data from the chk onto the control and free
+ * up the chunk resources.
+ */
+ uint32_t added = 0;
+ bool i_locked = false;
+
+ if (control->on_read_q) {
+ if (hold_rlock == 0) {
+ /* Its being pd-api'd so we must do some locks. */
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ i_locked = true;
+ }
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_CANT_READ) {
+ goto out;
+ }
+ }
+ if (control->data == NULL) {
+ control->data = chk->data;
+ sctp_setup_tail_pointer(control);
+ } else {
+ sctp_add_to_tail_pointer(control, chk->data, &added);
+ }
+ control->fsn_included = chk->rec.data.fsn;
+ asoc->size_on_reasm_queue -= chk->send_size;
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ sctp_mark_non_revokable(asoc, chk->rec.data.tsn);
+ chk->data = NULL;
+ if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+ control->first_frag_seen = 1;
+ control->sinfo_tsn = chk->rec.data.tsn;
+ control->sinfo_ppid = chk->rec.data.ppid;
+ }
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ /* Its complete */
+ if ((control->on_strm_q) && (control->on_read_q)) {
+ if (control->pdapi_started) {
+ control->pdapi_started = 0;
+ strm->pd_api_started = 0;
+ }
+ if (control->on_strm_q == SCTP_ON_UNORDERED) {
+ /* Unordered */
+ TAILQ_REMOVE(&strm->uno_inqueue, control, next_instrm);
+ control->on_strm_q = 0;
+ } else if (control->on_strm_q == SCTP_ON_ORDERED) {
+ /* Ordered */
+ TAILQ_REMOVE(&strm->inqueue, control, next_instrm);
+ /*
+ * Don't need to decrement size_on_all_streams,
+ * since control is on the read queue.
+ */
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ control->on_strm_q = 0;
+#ifdef INVARIANTS
+ } else if (control->on_strm_q) {
+ panic("Unknown state on ctrl: %p on_strm_q: %d", control,
+ control->on_strm_q);
+#endif
+ }
+ }
+ control->end_added = 1;
+ control->last_frag_seen = 1;
+ }
+out:
+ if (i_locked) {
+ SCTP_INP_READ_UNLOCK(stcb->sctp_ep);
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return (added);
+}
+
+/*
+ * Dump onto the re-assembly queue, in its proper place. After dumping on the
+ * queue, see if anthing can be delivered. If so pull it off (or as much as
+ * we can. If we run out of space then we must dump what we can and set the
+ * appropriate flag to say we queued what we could.
+ */
+static void
+sctp_queue_data_for_reasm(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_queued_to_read *control,
+ struct sctp_tmit_chunk *chk,
+ int created_control,
+ int *abort_flag, uint32_t tsn)
+{
+ uint32_t next_fsn;
+ struct sctp_tmit_chunk *at, *nat;
+ struct sctp_stream_in *strm;
+ int do_wakeup, unordered;
+ uint32_t lenadded;
+
+ strm = &asoc->strmin[control->sinfo_stream];
+ /*
+ * For old un-ordered data chunks.
+ */
+ if ((control->sinfo_flags >> 8) & SCTP_DATA_UNORDERED) {
+ unordered = 1;
+ } else {
+ unordered = 0;
+ }
+ /* Must be added to the stream-in queue */
+ if (created_control) {
+ if ((unordered == 0) || (asoc->idata_supported)) {
+ sctp_ucount_incr(asoc->cnt_on_all_streams);
+ }
+ if (sctp_place_control_in_stream(strm, asoc, control)) {
+ /* Duplicate SSN? */
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_6);
+ sctp_clean_up_control(stcb, control);
+ return;
+ }
+ if ((tsn == (asoc->cumulative_tsn + 1) && (asoc->idata_supported == 0))) {
+ /* Ok we created this control and now
+ * lets validate that its legal i.e. there
+ * is a B bit set, if not and we have
+ * up to the cum-ack then its invalid.
+ */
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0) {
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_7);
+ return;
+ }
+ }
+ }
+ if ((asoc->idata_supported == 0) && (unordered == 1)) {
+ sctp_inject_old_unordered_data(stcb, asoc, control, chk, abort_flag);
+ return;
+ }
+ /*
+ * Ok we must queue the chunk into the reasembly portion:
+ * o if its the first it goes to the control mbuf.
+ * o if its not first but the next in sequence it goes to the control,
+ * and each succeeding one in order also goes.
+ * o if its not in order we place it on the list in its place.
+ */
+ if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+ /* Its the very first one. */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "chunk is a first fsn: %u becomes fsn_included\n",
+ chk->rec.data.fsn);
+ if (control->first_frag_seen) {
+ /*
+ * Error on senders part, they either
+ * sent us two data chunks with FIRST,
+ * or they sent two un-ordered chunks that
+ * were fragmented at the same time in the same stream.
+ */
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_8);
+ return;
+ }
+ control->first_frag_seen = 1;
+ control->sinfo_ppid = chk->rec.data.ppid;
+ control->sinfo_tsn = chk->rec.data.tsn;
+ control->fsn_included = chk->rec.data.fsn;
+ control->data = chk->data;
+ sctp_mark_non_revokable(asoc, chk->rec.data.tsn);
+ chk->data = NULL;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ sctp_setup_tail_pointer(control);
+ asoc->size_on_all_streams += control->length;
+ } else {
+ /* Place the chunk in our list */
+ int inserted=0;
+ if (control->last_frag_seen == 0) {
+ /* Still willing to raise highest FSN seen */
+ if (SCTP_TSN_GT(chk->rec.data.fsn, control->top_fsn)) {
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "We have a new top_fsn: %u\n",
+ chk->rec.data.fsn);
+ control->top_fsn = chk->rec.data.fsn;
+ }
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "The last fsn is now in place fsn: %u\n",
+ chk->rec.data.fsn);
+ control->last_frag_seen = 1;
+ if (SCTP_TSN_GT(control->top_fsn, chk->rec.data.fsn)) {
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "New fsn: %u is not at top_fsn: %u -- abort\n",
+ chk->rec.data.fsn,
+ control->top_fsn);
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_9);
+ return;
+ }
+ }
+ if (asoc->idata_supported || control->first_frag_seen) {
+ /*
+ * For IDATA we always check since we know that
+ * the first fragment is 0. For old DATA we have
+ * to receive the first before we know the first FSN
+ * (which is the TSN).
+ */
+ if (SCTP_TSN_GE(control->fsn_included, chk->rec.data.fsn)) {
+ /* We have already delivered up to this so its a dup */
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_10);
+ return;
+ }
+ }
+ } else {
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ /* Second last? huh? */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Duplicate last fsn: %u (top: %u) -- abort\n",
+ chk->rec.data.fsn, control->top_fsn);
+ sctp_abort_in_reasm(stcb, control,
+ chk, abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_11);
+ return;
+ }
+ if (asoc->idata_supported || control->first_frag_seen) {
+ /*
+ * For IDATA we always check since we know that
+ * the first fragment is 0. For old DATA we have
+ * to receive the first before we know the first FSN
+ * (which is the TSN).
+ */
+
+ if (SCTP_TSN_GE(control->fsn_included, chk->rec.data.fsn)) {
+ /* We have already delivered up to this so its a dup */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "New fsn: %u is already seen in included_fsn: %u -- abort\n",
+ chk->rec.data.fsn, control->fsn_included);
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_12);
+ return;
+ }
+ }
+ /* validate not beyond top FSN if we have seen last one */
+ if (SCTP_TSN_GT(chk->rec.data.fsn, control->top_fsn)) {
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "New fsn: %u is beyond or at top_fsn: %u -- abort\n",
+ chk->rec.data.fsn,
+ control->top_fsn);
+ sctp_abort_in_reasm(stcb, control, chk,
+ abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_13);
+ return;
+ }
+ }
+ /*
+ * If we reach here, we need to place the
+ * new chunk in the reassembly for this
+ * control.
+ */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "chunk is a not first fsn: %u needs to be inserted\n",
+ chk->rec.data.fsn);
+ TAILQ_FOREACH(at, &control->reasm, sctp_next) {
+ if (SCTP_TSN_GT(at->rec.data.fsn, chk->rec.data.fsn)) {
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ /* Last not at the end? huh? */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Last fragment not last in list: -- abort\n");
+ sctp_abort_in_reasm(stcb, control,
+ chk, abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_14);
+ return;
+ }
+ /*
+ * This one in queue is bigger than the new one, insert
+ * the new one before at.
+ */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Insert it before fsn: %u\n",
+ at->rec.data.fsn);
+ asoc->size_on_reasm_queue += chk->send_size;
+ sctp_ucount_incr(asoc->cnt_on_reasm_queue);
+ TAILQ_INSERT_BEFORE(at, chk, sctp_next);
+ inserted = 1;
+ break;
+ } else if (at->rec.data.fsn == chk->rec.data.fsn) {
+ /* Gak, He sent me a duplicate str seq number */
+ /*
+ * foo bar, I guess I will just free this new guy,
+ * should we abort too? FIX ME MAYBE? Or it COULD be
+ * that the SSN's have wrapped. Maybe I should
+ * compare to TSN somehow... sigh for now just blow
+ * away the chunk!
+ */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Duplicate to fsn: %u -- abort\n",
+ at->rec.data.fsn);
+ sctp_abort_in_reasm(stcb, control,
+ chk, abort_flag,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_15);
+ return;
+ }
+ }
+ if (inserted == 0) {
+ /* Goes on the end */
+ SCTPDBG(SCTP_DEBUG_XXX, "Inserting at tail of list fsn: %u\n",
+ chk->rec.data.fsn);
+ asoc->size_on_reasm_queue += chk->send_size;
+ sctp_ucount_incr(asoc->cnt_on_reasm_queue);
+ TAILQ_INSERT_TAIL(&control->reasm, chk, sctp_next);
+ }
+ }
+ /*
+ * Ok lets see if we can suck any up into the control
+ * structure that are in seq if it makes sense.
+ */
+ do_wakeup = 0;
+ /*
+ * If the first fragment has not been
+ * seen there is no sense in looking.
+ */
+ if (control->first_frag_seen) {
+ next_fsn = control->fsn_included + 1;
+ TAILQ_FOREACH_SAFE(at, &control->reasm, sctp_next, nat) {
+ if (at->rec.data.fsn == next_fsn) {
+ /* We can add this one now to the control */
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Adding more to control: %p at: %p fsn: %u next_fsn: %u included: %u\n",
+ control, at,
+ at->rec.data.fsn,
+ next_fsn, control->fsn_included);
+ TAILQ_REMOVE(&control->reasm, at, sctp_next);
+ lenadded = sctp_add_chk_to_control(control, strm, stcb, asoc, at, SCTP_READ_LOCK_NOT_HELD);
+ if (control->on_read_q) {
+ do_wakeup = 1;
+ } else {
+ /*
+ * We only add to the size-on-all-streams
+ * if its not on the read q. The read q
+ * flag will cause a sballoc so its accounted
+ * for there.
+ */
+ asoc->size_on_all_streams += lenadded;
+ }
+ next_fsn++;
+ if (control->end_added && control->pdapi_started) {
+ if (strm->pd_api_started) {
+ strm->pd_api_started = 0;
+ control->pdapi_started = 0;
+ }
+ if (control->on_read_q == 0) {
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, control->end_added,
+ SCTP_READ_LOCK_NOT_HELD, SCTP_SO_NOT_LOCKED);
+ }
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ if (do_wakeup) {
+#if defined(__Userspace__)
+ sctp_invoke_recv_callback(stcb->sctp_ep, stcb, control, SCTP_READ_LOCK_NOT_HELD);
+#endif
+ /* Need to wakeup the reader */
+ sctp_wakeup_the_read_socket(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ }
+}
+
+static struct sctp_queued_to_read *
+sctp_find_reasm_entry(struct sctp_stream_in *strm, uint32_t mid, int ordered, int idata_supported)
+{
+ struct sctp_queued_to_read *control;
+
+ if (ordered) {
+ TAILQ_FOREACH(control, &strm->inqueue, next_instrm) {
+ if (SCTP_MID_EQ(idata_supported, control->mid, mid)) {
+ break;
+ }
+ }
+ } else {
+ if (idata_supported) {
+ TAILQ_FOREACH(control, &strm->uno_inqueue, next_instrm) {
+ if (SCTP_MID_EQ(idata_supported, control->mid, mid)) {
+ break;
+ }
+ }
+ } else {
+ control = TAILQ_FIRST(&strm->uno_inqueue);
+ }
+ }
+ return (control);
+}
+
+static int
+sctp_process_a_data_chunk(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct mbuf **m, int offset, int chk_length,
+ struct sctp_nets *net, uint32_t *high_tsn, int *abort_flag,
+ int *break_flag, int last_chunk, uint8_t chk_type)
+{
+ struct sctp_tmit_chunk *chk = NULL; /* make gcc happy */
+ struct sctp_stream_in *strm;
+ uint32_t tsn, fsn, gap, mid;
+ struct mbuf *dmbuf;
+ int the_len;
+ int need_reasm_check = 0;
+ uint16_t sid;
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+ struct sctp_queued_to_read *control, *ncontrol;
+ uint32_t ppid;
+ uint8_t chk_flags;
+ struct sctp_stream_reset_list *liste;
+ int ordered;
+ size_t clen;
+ int created_control = 0;
+
+ if (chk_type == SCTP_IDATA) {
+ struct sctp_idata_chunk *chunk, chunk_buf;
+
+ chunk = (struct sctp_idata_chunk *)sctp_m_getptr(*m, offset,
+ sizeof(struct sctp_idata_chunk), (uint8_t *)&chunk_buf);
+ chk_flags = chunk->ch.chunk_flags;
+ clen = sizeof(struct sctp_idata_chunk);
+ tsn = ntohl(chunk->dp.tsn);
+ sid = ntohs(chunk->dp.sid);
+ mid = ntohl(chunk->dp.mid);
+ if (chk_flags & SCTP_DATA_FIRST_FRAG) {
+ fsn = 0;
+ ppid = chunk->dp.ppid_fsn.ppid;
+ } else {
+ fsn = ntohl(chunk->dp.ppid_fsn.fsn);
+ ppid = 0xffffffff; /* Use as an invalid value. */
+ }
+ } else {
+ struct sctp_data_chunk *chunk, chunk_buf;
+
+ chunk = (struct sctp_data_chunk *)sctp_m_getptr(*m, offset,
+ sizeof(struct sctp_data_chunk), (uint8_t *)&chunk_buf);
+ chk_flags = chunk->ch.chunk_flags;
+ clen = sizeof(struct sctp_data_chunk);
+ tsn = ntohl(chunk->dp.tsn);
+ sid = ntohs(chunk->dp.sid);
+ mid = (uint32_t)(ntohs(chunk->dp.ssn));
+ fsn = tsn;
+ ppid = chunk->dp.ppid;
+ }
+ if ((size_t)chk_length == clen) {
+ /*
+ * Need to send an abort since we had a
+ * empty data chunk.
+ */
+ op_err = sctp_generate_no_user_data_cause(tsn);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_16;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return (0);
+ }
+ if ((chk_flags & SCTP_DATA_SACK_IMMEDIATELY) == SCTP_DATA_SACK_IMMEDIATELY) {
+ asoc->send_sack = 1;
+ }
+ ordered = ((chk_flags & SCTP_DATA_UNORDERED) == 0);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(tsn, asoc->cumulative_tsn, asoc->highest_tsn_inside_map, SCTP_MAP_TSN_ENTERS);
+ }
+ if (stcb == NULL) {
+ return (0);
+ }
+ SCTP_LTRACE_CHK(stcb->sctp_ep, stcb, chk_type, tsn);
+ if (SCTP_TSN_GE(asoc->cumulative_tsn, tsn)) {
+ /* It is a duplicate */
+ SCTP_STAT_INCR(sctps_recvdupdata);
+ if (asoc->numduptsns < SCTP_MAX_DUP_TSNS) {
+ /* Record a dup for the next outbound sack */
+ asoc->dup_tsns[asoc->numduptsns] = tsn;
+ asoc->numduptsns++;
+ }
+ asoc->send_sack = 1;
+ return (0);
+ }
+ /* Calculate the number of TSN's between the base and this TSN */
+ SCTP_CALC_TSN_TO_GAP(gap, tsn, asoc->mapping_array_base_tsn);
+ if (gap >= (SCTP_MAPPING_ARRAY << 3)) {
+ /* Can't hold the bit in the mapping at max array, toss it */
+ return (0);
+ }
+ if (gap >= (uint32_t) (asoc->mapping_array_size << 3)) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (sctp_expand_mapping_array(asoc, gap)) {
+ /* Can't expand, drop it */
+ return (0);
+ }
+ }
+ if (SCTP_TSN_GT(tsn, *high_tsn)) {
+ *high_tsn = tsn;
+ }
+ /* See if we have received this one already */
+ if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap) ||
+ SCTP_IS_TSN_PRESENT(asoc->nr_mapping_array, gap)) {
+ SCTP_STAT_INCR(sctps_recvdupdata);
+ if (asoc->numduptsns < SCTP_MAX_DUP_TSNS) {
+ /* Record a dup for the next outbound sack */
+ asoc->dup_tsns[asoc->numduptsns] = tsn;
+ asoc->numduptsns++;
+ }
+ asoc->send_sack = 1;
+ return (0);
+ }
+ /*
+ * Check to see about the GONE flag, duplicates would cause a sack
+ * to be sent up above
+ */
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET))) {
+ /*
+ * wait a minute, this guy is gone, there is no longer a
+ * receiver. Send peer an ABORT!
+ */
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return (0);
+ }
+ /*
+ * Now before going further we see if there is room. If NOT then we
+ * MAY let one through only IF this TSN is the one we are waiting
+ * for on a partial delivery API.
+ */
+
+ /* Is the stream valid? */
+ if (sid >= asoc->streamincnt) {
+ struct sctp_error_invalid_stream *cause;
+
+ op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_error_invalid_stream),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err != NULL) {
+ /* add some space up front so prepend will work well */
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr));
+ cause = mtod(op_err, struct sctp_error_invalid_stream *);
+ /*
+ * Error causes are just param's and this one has
+ * two back to back phdr, one with the error type
+ * and size, the other with the streamid and a rsvd
+ */
+ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_error_invalid_stream);
+ cause->cause.code = htons(SCTP_CAUSE_INVALID_STREAM);
+ cause->cause.length = htons(sizeof(struct sctp_error_invalid_stream));
+ cause->stream_id = htons(sid);
+ cause->reserved = htons(0);
+ sctp_queue_op_err(stcb, op_err);
+ }
+ SCTP_STAT_INCR(sctps_badsid);
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_SET_TSN_PRESENT(asoc->nr_mapping_array, gap);
+ if (SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_nr_map)) {
+ asoc->highest_tsn_inside_nr_map = tsn;
+ }
+ if (tsn == (asoc->cumulative_tsn + 1)) {
+ /* Update cum-ack */
+ asoc->cumulative_tsn = tsn;
+ }
+ return (0);
+ }
+ /*
+ * If its a fragmented message, lets see if we can
+ * find the control on the reassembly queues.
+ */
+ if ((chk_type == SCTP_IDATA) &&
+ ((chk_flags & SCTP_DATA_FIRST_FRAG) == 0) &&
+ (fsn == 0)) {
+ /*
+ * The first *must* be fsn 0, and other
+ * (middle/end) pieces can *not* be fsn 0.
+ * XXX: This can happen in case of a wrap around.
+ * Ignore is for now.
+ */
+ SCTP_SNPRINTF(msg, sizeof(msg), "FSN zero for MID=%8.8x, but flags=%2.2x", mid, chk_flags);
+ goto err_out;
+ }
+ control = sctp_find_reasm_entry(&asoc->strmin[sid], mid, ordered, asoc->idata_supported);
+ SCTPDBG(SCTP_DEBUG_XXX, "chunk_flags:0x%x look for control on queues %p\n",
+ chk_flags, control);
+ if ((chk_flags & SCTP_DATA_NOT_FRAG) != SCTP_DATA_NOT_FRAG) {
+ /* See if we can find the re-assembly entity */
+ if (control != NULL) {
+ /* We found something, does it belong? */
+ if (ordered && (mid != control->mid)) {
+ SCTP_SNPRINTF(msg, sizeof(msg), "Reassembly problem (MID=%8.8x)", mid);
+ err_out:
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_17;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return (0);
+ }
+ if (ordered && ((control->sinfo_flags >> 8) & SCTP_DATA_UNORDERED)) {
+ /* We can't have a switched order with an unordered chunk */
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "All fragments of a user message must be ordered or unordered (TSN=%8.8x)",
+ tsn);
+ goto err_out;
+ }
+ if (!ordered && (((control->sinfo_flags >> 8) & SCTP_DATA_UNORDERED) == 0)) {
+ /* We can't have a switched unordered with a ordered chunk */
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "All fragments of a user message must be ordered or unordered (TSN=%8.8x)",
+ tsn);
+ goto err_out;
+ }
+ }
+ } else {
+ /* Its a complete segment. Lets validate we
+ * don't have a re-assembly going on with
+ * the same Stream/Seq (for ordered) or in
+ * the same Stream for unordered.
+ */
+ if (control != NULL) {
+ if (ordered || asoc->idata_supported) {
+ SCTPDBG(SCTP_DEBUG_XXX, "chunk_flags: 0x%x dup detected on MID: %u\n",
+ chk_flags, mid);
+ SCTP_SNPRINTF(msg, sizeof(msg), "Duplicate MID=%8.8x detected.", mid);
+ goto err_out;
+ } else {
+ if ((tsn == control->fsn_included + 1) &&
+ (control->end_added == 0)) {
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "Illegal message sequence, missing end for MID: %8.8x",
+ control->fsn_included);
+ goto err_out;
+ } else {
+ control = NULL;
+ }
+ }
+ }
+ }
+ /* now do the tests */
+ if (((asoc->cnt_on_all_streams +
+ asoc->cnt_on_reasm_queue +
+ asoc->cnt_msg_on_sb) >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue)) ||
+ (((int)asoc->my_rwnd) <= 0)) {
+ /*
+ * When we have NO room in the rwnd we check to make sure
+ * the reader is doing its job...
+ */
+ if (SCTP_SBAVAIL(&stcb->sctp_socket->so_rcv) > 0) {
+ /* some to read, wake-up */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ /* assoc was freed while we were unlocked */
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return (0);
+ }
+#endif
+ sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ /* now is it in the mapping array of what we have accepted? */
+ if (chk_type == SCTP_DATA) {
+ if (SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_map) &&
+ SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_nr_map)) {
+ /* Nope not in the valid range dump it */
+ dump_packet:
+ sctp_set_rwnd(stcb, asoc);
+ if ((asoc->cnt_on_all_streams +
+ asoc->cnt_on_reasm_queue +
+ asoc->cnt_msg_on_sb) >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue)) {
+ SCTP_STAT_INCR(sctps_datadropchklmt);
+ } else {
+ SCTP_STAT_INCR(sctps_datadroprwnd);
+ }
+ *break_flag = 1;
+ return (0);
+ }
+ } else {
+ if (control == NULL) {
+ goto dump_packet;
+ }
+ if (SCTP_TSN_GT(fsn, control->top_fsn)) {
+ goto dump_packet;
+ }
+ }
+ }
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (asoc->tsn_in_at >= SCTP_TSN_LOG_SIZE) {
+ asoc->tsn_in_at = 0;
+ asoc->tsn_in_wrapped = 1;
+ }
+ asoc->in_tsnlog[asoc->tsn_in_at].tsn = tsn;
+ asoc->in_tsnlog[asoc->tsn_in_at].strm = sid;
+ asoc->in_tsnlog[asoc->tsn_in_at].seq = mid;
+ asoc->in_tsnlog[asoc->tsn_in_at].sz = chk_length;
+ asoc->in_tsnlog[asoc->tsn_in_at].flgs = chunk_flags;
+ asoc->in_tsnlog[asoc->tsn_in_at].stcb = (void *)stcb;
+ asoc->in_tsnlog[asoc->tsn_in_at].in_pos = asoc->tsn_in_at;
+ asoc->in_tsnlog[asoc->tsn_in_at].in_out = 1;
+ asoc->tsn_in_at++;
+#endif
+ /*
+ * Before we continue lets validate that we are not being fooled by
+ * an evil attacker. We can only have Nk chunks based on our TSN
+ * spread allowed by the mapping array N * 8 bits, so there is no
+ * way our stream sequence numbers could have wrapped. We of course
+ * only validate the FIRST fragment so the bit must be set.
+ */
+ if ((chk_flags & SCTP_DATA_FIRST_FRAG) &&
+ (TAILQ_EMPTY(&asoc->resetHead)) &&
+ (chk_flags & SCTP_DATA_UNORDERED) == 0 &&
+ SCTP_MID_GE(asoc->idata_supported, asoc->strmin[sid].last_mid_delivered, mid)) {
+ /* The incoming sseq is behind where we last delivered? */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "EVIL/Broken-Dup S-SEQ: %u delivered: %u from peer, Abort!\n",
+ mid, asoc->strmin[sid].last_mid_delivered);
+
+ if (asoc->idata_supported) {
+ SCTP_SNPRINTF(msg, sizeof(msg), "Delivered MID=%8.8x, got TSN=%8.8x, SID=%4.4x, MID=%8.8x",
+ asoc->strmin[sid].last_mid_delivered,
+ tsn,
+ sid,
+ mid);
+ } else {
+ SCTP_SNPRINTF(msg, sizeof(msg), "Delivered SSN=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ (uint16_t)asoc->strmin[sid].last_mid_delivered,
+ tsn,
+ sid,
+ (uint16_t)mid);
+ }
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_18;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return (0);
+ }
+ if (chk_type == SCTP_IDATA) {
+ the_len = (chk_length - sizeof(struct sctp_idata_chunk));
+ } else {
+ the_len = (chk_length - sizeof(struct sctp_data_chunk));
+ }
+ if (last_chunk == 0) {
+ if (chk_type == SCTP_IDATA) {
+ dmbuf = SCTP_M_COPYM(*m,
+ (offset + sizeof(struct sctp_idata_chunk)),
+ the_len, M_NOWAIT);
+ } else {
+ dmbuf = SCTP_M_COPYM(*m,
+ (offset + sizeof(struct sctp_data_chunk)),
+ the_len, M_NOWAIT);
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(dmbuf, SCTP_MBUF_ICOPY);
+ }
+#endif
+ } else {
+ /* We can steal the last chunk */
+ int l_len;
+ dmbuf = *m;
+ /* lop off the top part */
+ if (chk_type == SCTP_IDATA) {
+ m_adj(dmbuf, (offset + sizeof(struct sctp_idata_chunk)));
+ } else {
+ m_adj(dmbuf, (offset + sizeof(struct sctp_data_chunk)));
+ }
+ if (SCTP_BUF_NEXT(dmbuf) == NULL) {
+ l_len = SCTP_BUF_LEN(dmbuf);
+ } else {
+ /* need to count up the size hopefully
+ * does not hit this to often :-0
+ */
+ struct mbuf *lat;
+
+ l_len = 0;
+ for (lat = dmbuf; lat; lat = SCTP_BUF_NEXT(lat)) {
+ l_len += SCTP_BUF_LEN(lat);
+ }
+ }
+ if (l_len > the_len) {
+ /* Trim the end round bytes off too */
+ m_adj(dmbuf, -(l_len - the_len));
+ }
+ }
+ if (dmbuf == NULL) {
+ SCTP_STAT_INCR(sctps_nomem);
+ return (0);
+ }
+ /*
+ * Now no matter what, we need a control, get one
+ * if we don't have one (we may have gotten it
+ * above when we found the message was fragmented
+ */
+ if (control == NULL) {
+ sctp_alloc_a_readq(stcb, control);
+ sctp_build_readq_entry_mac(control, stcb, asoc->context, net, tsn,
+ ppid,
+ sid,
+ chk_flags,
+ NULL, fsn, mid);
+ if (control == NULL) {
+ SCTP_STAT_INCR(sctps_nomem);
+ return (0);
+ }
+ if ((chk_flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG) {
+ struct mbuf *mm;
+
+ control->data = dmbuf;
+ control->tail_mbuf = NULL;
+ for (mm = control->data; mm; mm = mm->m_next) {
+ control->length += SCTP_BUF_LEN(mm);
+ if (SCTP_BUF_NEXT(mm) == NULL) {
+ control->tail_mbuf = mm;
+ }
+ }
+ control->end_added = 1;
+ control->last_frag_seen = 1;
+ control->first_frag_seen = 1;
+ control->fsn_included = fsn;
+ control->top_fsn = fsn;
+ }
+ created_control = 1;
+ }
+ SCTPDBG(SCTP_DEBUG_XXX, "chunk_flags: 0x%x ordered: %d MID: %u control: %p\n",
+ chk_flags, ordered, mid, control);
+ if ((chk_flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG &&
+ TAILQ_EMPTY(&asoc->resetHead) &&
+ ((ordered == 0) ||
+ (SCTP_MID_EQ(asoc->idata_supported, asoc->strmin[sid].last_mid_delivered + 1, mid) &&
+ TAILQ_EMPTY(&asoc->strmin[sid].inqueue)))) {
+ /* Candidate for express delivery */
+ /*
+ * Its not fragmented, No PD-API is up, Nothing in the
+ * delivery queue, Its un-ordered OR ordered and the next to
+ * deliver AND nothing else is stuck on the stream queue,
+ * And there is room for it in the socket buffer. Lets just
+ * stuff it up the buffer....
+ */
+ SCTP_SET_TSN_PRESENT(asoc->nr_mapping_array, gap);
+ if (SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_nr_map)) {
+ asoc->highest_tsn_inside_nr_map = tsn;
+ }
+ SCTPDBG(SCTP_DEBUG_XXX, "Injecting control: %p to be read (MID: %u)\n",
+ control, mid);
+
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control, &stcb->sctp_socket->so_rcv,
+ 1, SCTP_READ_LOCK_NOT_HELD, SCTP_SO_NOT_LOCKED);
+
+ if ((chk_flags & SCTP_DATA_UNORDERED) == 0) {
+ /* for ordered, bump what we delivered */
+ asoc->strmin[sid].last_mid_delivered++;
+ }
+ SCTP_STAT_INCR(sctps_recvexpress);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del_alt(stcb, tsn, mid, sid,
+ SCTP_STR_LOG_FROM_EXPRS_DEL);
+ }
+ control = NULL;
+ goto finish_express_del;
+ }
+
+ /* Now will we need a chunk too? */
+ if ((chk_flags & SCTP_DATA_NOT_FRAG) != SCTP_DATA_NOT_FRAG) {
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* No memory so we drop the chunk */
+ SCTP_STAT_INCR(sctps_nomem);
+ if (last_chunk == 0) {
+ /* we copied it, free the copy */
+ sctp_m_freem(dmbuf);
+ }
+ return (0);
+ }
+ chk->rec.data.tsn = tsn;
+ chk->no_fr_allowed = 0;
+ chk->rec.data.fsn = fsn;
+ chk->rec.data.mid = mid;
+ chk->rec.data.sid = sid;
+ chk->rec.data.ppid = ppid;
+ chk->rec.data.context = stcb->asoc.context;
+ chk->rec.data.doing_fast_retransmit = 0;
+ chk->rec.data.rcv_flags = chk_flags;
+ chk->asoc = asoc;
+ chk->send_size = the_len;
+ chk->whoTo = net;
+ SCTPDBG(SCTP_DEBUG_XXX, "Building ck: %p for control: %p to be read (MID: %u)\n",
+ chk,
+ control, mid);
+ atomic_add_int(&net->ref_count, 1);
+ chk->data = dmbuf;
+ }
+ /* Set the appropriate TSN mark */
+ if (SCTP_BASE_SYSCTL(sctp_do_drain) == 0) {
+ SCTP_SET_TSN_PRESENT(asoc->nr_mapping_array, gap);
+ if (SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_nr_map)) {
+ asoc->highest_tsn_inside_nr_map = tsn;
+ }
+ } else {
+ SCTP_SET_TSN_PRESENT(asoc->mapping_array, gap);
+ if (SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_map)) {
+ asoc->highest_tsn_inside_map = tsn;
+ }
+ }
+ /* Now is it complete (i.e. not fragmented)? */
+ if ((chk_flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG) {
+ /*
+ * Special check for when streams are resetting. We
+ * could be more smart about this and check the
+ * actual stream to see if it is not being reset..
+ * that way we would not create a HOLB when amongst
+ * streams being reset and those not being reset.
+ *
+ */
+ if (((liste = TAILQ_FIRST(&asoc->resetHead)) != NULL) &&
+ SCTP_TSN_GT(tsn, liste->tsn)) {
+ /*
+ * yep its past where we need to reset... go
+ * ahead and queue it.
+ */
+ if (TAILQ_EMPTY(&asoc->pending_reply_queue)) {
+ /* first one on */
+ TAILQ_INSERT_TAIL(&asoc->pending_reply_queue, control, next);
+ } else {
+ struct sctp_queued_to_read *lcontrol, *nlcontrol;
+ unsigned char inserted = 0;
+ TAILQ_FOREACH_SAFE(lcontrol, &asoc->pending_reply_queue, next, nlcontrol) {
+ if (SCTP_TSN_GT(control->sinfo_tsn, lcontrol->sinfo_tsn)) {
+ continue;
+ } else {
+ /* found it */
+ TAILQ_INSERT_BEFORE(lcontrol, control, next);
+ inserted = 1;
+ break;
+ }
+ }
+ if (inserted == 0) {
+ /*
+ * must be put at end, use
+ * prevP (all setup from
+ * loop) to setup nextP.
+ */
+ TAILQ_INSERT_TAIL(&asoc->pending_reply_queue, control, next);
+ }
+ }
+ goto finish_express_del;
+ }
+ if (chk_flags & SCTP_DATA_UNORDERED) {
+ /* queue directly into socket buffer */
+ SCTPDBG(SCTP_DEBUG_XXX, "Unordered data to be read control: %p MID: %u\n",
+ control, mid);
+ sctp_mark_non_revokable(asoc, control->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_NOT_HELD, SCTP_SO_NOT_LOCKED);
+
+ } else {
+ SCTPDBG(SCTP_DEBUG_XXX, "Queue control: %p for reordering MID: %u\n", control,
+ mid);
+ sctp_queue_data_to_stream(stcb, asoc, control, abort_flag, &need_reasm_check);
+ if (*abort_flag) {
+ if (last_chunk) {
+ *m = NULL;
+ }
+ return (0);
+ }
+ }
+ goto finish_express_del;
+ }
+ /* If we reach here its a reassembly */
+ need_reasm_check = 1;
+ SCTPDBG(SCTP_DEBUG_XXX,
+ "Queue data to stream for reasm control: %p MID: %u\n",
+ control, mid);
+ sctp_queue_data_for_reasm(stcb, asoc, control, chk, created_control, abort_flag, tsn);
+ if (*abort_flag) {
+ /*
+ * the assoc is now gone and chk was put onto the
+ * reasm queue, which has all been freed.
+ */
+ if (last_chunk) {
+ *m = NULL;
+ }
+ return (0);
+ }
+finish_express_del:
+ /* Here we tidy up things */
+ if (tsn == (asoc->cumulative_tsn + 1)) {
+ /* Update cum-ack */
+ asoc->cumulative_tsn = tsn;
+ }
+ if (last_chunk) {
+ *m = NULL;
+ }
+ if (ordered) {
+ SCTP_STAT_INCR_COUNTER64(sctps_inorderchunks);
+ } else {
+ SCTP_STAT_INCR_COUNTER64(sctps_inunorderchunks);
+ }
+ SCTP_STAT_INCR(sctps_recvdata);
+ /* Set it present please */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del_alt(stcb, tsn, mid, sid, SCTP_STR_LOG_FROM_MARK_TSN);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(asoc->mapping_array_base_tsn, asoc->cumulative_tsn,
+ asoc->highest_tsn_inside_map, SCTP_MAP_PREPARE_SLIDE);
+ }
+ if (need_reasm_check) {
+ (void)sctp_deliver_reasm_check(stcb, asoc, &asoc->strmin[sid], SCTP_READ_LOCK_NOT_HELD);
+ need_reasm_check = 0;
+ }
+ /* check the special flag for stream resets */
+ if (((liste = TAILQ_FIRST(&asoc->resetHead)) != NULL) &&
+ SCTP_TSN_GE(asoc->cumulative_tsn, liste->tsn)) {
+ /*
+ * we have finished working through the backlogged TSN's now
+ * time to reset streams. 1: call reset function. 2: free
+ * pending_reply space 3: distribute any chunks in
+ * pending_reply_queue.
+ */
+ sctp_reset_in_stream(stcb, liste->number_entries, liste->list_of_streams);
+ TAILQ_REMOVE(&asoc->resetHead, liste, next_resp);
+ sctp_send_deferred_reset_response(stcb, liste, SCTP_STREAM_RESET_RESULT_PERFORMED);
+ SCTP_FREE(liste, SCTP_M_STRESET);
+ /*sa_ignore FREED_MEMORY*/
+ liste = TAILQ_FIRST(&asoc->resetHead);
+ if (TAILQ_EMPTY(&asoc->resetHead)) {
+ /* All can be removed */
+ TAILQ_FOREACH_SAFE(control, &asoc->pending_reply_queue, next, ncontrol) {
+ TAILQ_REMOVE(&asoc->pending_reply_queue, control, next);
+ strm = &asoc->strmin[control->sinfo_stream];
+ sctp_queue_data_to_stream(stcb, asoc, control, abort_flag, &need_reasm_check);
+ if (*abort_flag) {
+ return (0);
+ }
+ if (need_reasm_check) {
+ (void)sctp_deliver_reasm_check(stcb, asoc, strm, SCTP_READ_LOCK_NOT_HELD);
+ need_reasm_check = 0;
+ }
+ }
+ } else {
+ TAILQ_FOREACH_SAFE(control, &asoc->pending_reply_queue, next, ncontrol) {
+ if (SCTP_TSN_GT(control->sinfo_tsn, liste->tsn)) {
+ break;
+ }
+ /*
+ * if control->sinfo_tsn is <= liste->tsn we can
+ * process it which is the NOT of
+ * control->sinfo_tsn > liste->tsn
+ */
+ TAILQ_REMOVE(&asoc->pending_reply_queue, control, next);
+ strm = &asoc->strmin[control->sinfo_stream];
+ sctp_queue_data_to_stream(stcb, asoc, control, abort_flag, &need_reasm_check);
+ if (*abort_flag) {
+ return (0);
+ }
+ if (need_reasm_check) {
+ (void)sctp_deliver_reasm_check(stcb, asoc, strm, SCTP_READ_LOCK_NOT_HELD);
+ need_reasm_check = 0;
+ }
+ }
+ }
+ }
+ return (1);
+}
+
+static const int8_t sctp_map_lookup_tab[256] = {
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 5,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 6,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 5,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 7,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 5,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 6,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 5,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 8
+};
+
+void
+sctp_slide_mapping_arrays(struct sctp_tcb *stcb)
+{
+ /*
+ * Now we also need to check the mapping array in a couple of ways.
+ * 1) Did we move the cum-ack point?
+ *
+ * When you first glance at this you might think
+ * that all entries that make up the position
+ * of the cum-ack would be in the nr-mapping array
+ * only.. i.e. things up to the cum-ack are always
+ * deliverable. Thats true with one exception, when
+ * its a fragmented message we may not deliver the data
+ * until some threshold (or all of it) is in place. So
+ * we must OR the nr_mapping_array and mapping_array to
+ * get a true picture of the cum-ack.
+ */
+ struct sctp_association *asoc;
+ int at;
+ uint8_t val;
+ int slide_from, slide_end, lgap, distance;
+ uint32_t old_cumack, old_base, old_highest, highest_tsn;
+
+ asoc = &stcb->asoc;
+
+ old_cumack = asoc->cumulative_tsn;
+ old_base = asoc->mapping_array_base_tsn;
+ old_highest = asoc->highest_tsn_inside_map;
+ /*
+ * We could probably improve this a small bit by calculating the
+ * offset of the current cum-ack as the starting point.
+ */
+ at = 0;
+ for (slide_from = 0; slide_from < stcb->asoc.mapping_array_size; slide_from++) {
+ val = asoc->nr_mapping_array[slide_from] | asoc->mapping_array[slide_from];
+ if (val == 0xff) {
+ at += 8;
+ } else {
+ /* there is a 0 bit */
+ at += sctp_map_lookup_tab[val];
+ break;
+ }
+ }
+ asoc->cumulative_tsn = asoc->mapping_array_base_tsn + (at-1);
+
+ if (SCTP_TSN_GT(asoc->cumulative_tsn, asoc->highest_tsn_inside_map) &&
+ SCTP_TSN_GT(asoc->cumulative_tsn, asoc->highest_tsn_inside_nr_map)) {
+#ifdef INVARIANTS
+ panic("huh, cumack 0x%x greater than high-tsn 0x%x in map",
+ asoc->cumulative_tsn, asoc->highest_tsn_inside_map);
+#else
+ SCTP_PRINTF("huh, cumack 0x%x greater than high-tsn 0x%x in map - should panic?\n",
+ asoc->cumulative_tsn, asoc->highest_tsn_inside_map);
+ sctp_print_mapping_array(asoc);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(0, 6, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT);
+ }
+ asoc->highest_tsn_inside_map = asoc->cumulative_tsn;
+ asoc->highest_tsn_inside_nr_map = asoc->cumulative_tsn;
+#endif
+ }
+ if (SCTP_TSN_GT(asoc->highest_tsn_inside_nr_map, asoc->highest_tsn_inside_map)) {
+ highest_tsn = asoc->highest_tsn_inside_nr_map;
+ } else {
+ highest_tsn = asoc->highest_tsn_inside_map;
+ }
+ if ((asoc->cumulative_tsn == highest_tsn) && (at >= 8)) {
+ /* The complete array was completed by a single FR */
+ /* highest becomes the cum-ack */
+ int clr;
+#ifdef INVARIANTS
+ unsigned int i;
+#endif
+
+ /* clear the array */
+ clr = ((at+7) >> 3);
+ if (clr > asoc->mapping_array_size) {
+ clr = asoc->mapping_array_size;
+ }
+ memset(asoc->mapping_array, 0, clr);
+ memset(asoc->nr_mapping_array, 0, clr);
+#ifdef INVARIANTS
+ for (i = 0; i < asoc->mapping_array_size; i++) {
+ if ((asoc->mapping_array[i]) || (asoc->nr_mapping_array[i])) {
+ SCTP_PRINTF("Error Mapping array's not clean at clear\n");
+ sctp_print_mapping_array(asoc);
+ }
+ }
+#endif
+ asoc->mapping_array_base_tsn = asoc->cumulative_tsn + 1;
+ asoc->highest_tsn_inside_nr_map = asoc->highest_tsn_inside_map = asoc->cumulative_tsn;
+ } else if (at >= 8) {
+ /* we can slide the mapping array down */
+ /* slide_from holds where we hit the first NON 0xff byte */
+
+ /*
+ * now calculate the ceiling of the move using our highest
+ * TSN value
+ */
+ SCTP_CALC_TSN_TO_GAP(lgap, highest_tsn, asoc->mapping_array_base_tsn);
+ slide_end = (lgap >> 3);
+ if (slide_end < slide_from) {
+ sctp_print_mapping_array(asoc);
+#ifdef INVARIANTS
+ panic("impossible slide");
+#else
+ SCTP_PRINTF("impossible slide lgap: %x slide_end: %x slide_from: %x? at: %d\n",
+ lgap, slide_end, slide_from, at);
+ return;
+#endif
+ }
+ if (slide_end > asoc->mapping_array_size) {
+#ifdef INVARIANTS
+ panic("would overrun buffer");
+#else
+ SCTP_PRINTF("Gak, would have overrun map end: %d slide_end: %d\n",
+ asoc->mapping_array_size, slide_end);
+ slide_end = asoc->mapping_array_size;
+#endif
+ }
+ distance = (slide_end - slide_from) + 1;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(old_base, old_cumack, old_highest,
+ SCTP_MAP_PREPARE_SLIDE);
+ sctp_log_map((uint32_t) slide_from, (uint32_t) slide_end,
+ (uint32_t) lgap, SCTP_MAP_SLIDE_FROM);
+ }
+ if (distance + slide_from > asoc->mapping_array_size ||
+ distance < 0) {
+ /*
+ * Here we do NOT slide forward the array so that
+ * hopefully when more data comes in to fill it up
+ * we will be able to slide it forward. Really I
+ * don't think this should happen :-0
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map((uint32_t) distance, (uint32_t) slide_from,
+ (uint32_t) asoc->mapping_array_size,
+ SCTP_MAP_SLIDE_NONE);
+ }
+ } else {
+ int ii;
+
+ for (ii = 0; ii < distance; ii++) {
+ asoc->mapping_array[ii] = asoc->mapping_array[slide_from + ii];
+ asoc->nr_mapping_array[ii] = asoc->nr_mapping_array[slide_from + ii];
+ }
+ for (ii = distance; ii < asoc->mapping_array_size; ii++) {
+ asoc->mapping_array[ii] = 0;
+ asoc->nr_mapping_array[ii] = 0;
+ }
+ if (asoc->highest_tsn_inside_map + 1 == asoc->mapping_array_base_tsn) {
+ asoc->highest_tsn_inside_map += (slide_from << 3);
+ }
+ if (asoc->highest_tsn_inside_nr_map + 1 == asoc->mapping_array_base_tsn) {
+ asoc->highest_tsn_inside_nr_map += (slide_from << 3);
+ }
+ asoc->mapping_array_base_tsn += (slide_from << 3);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(asoc->mapping_array_base_tsn,
+ asoc->cumulative_tsn, asoc->highest_tsn_inside_map,
+ SCTP_MAP_SLIDE_RESULT);
+ }
+ }
+ }
+}
+
+void
+sctp_sack_check(struct sctp_tcb *stcb, int was_a_gap)
+{
+ struct sctp_association *asoc;
+ uint32_t highest_tsn;
+ int is_a_gap;
+
+ sctp_slide_mapping_arrays(stcb);
+ asoc = &stcb->asoc;
+ if (SCTP_TSN_GT(asoc->highest_tsn_inside_nr_map, asoc->highest_tsn_inside_map)) {
+ highest_tsn = asoc->highest_tsn_inside_nr_map;
+ } else {
+ highest_tsn = asoc->highest_tsn_inside_map;
+ }
+ /* Is there a gap now? */
+ is_a_gap = SCTP_TSN_GT(highest_tsn, stcb->asoc.cumulative_tsn);
+
+ /*
+ * Now we need to see if we need to queue a sack or just start the
+ * timer (if allowed).
+ */
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_SENT) {
+ /*
+ * Ok special case, in SHUTDOWN-SENT case. here we
+ * maker sure SACK timer is off and instead send a
+ * SHUTDOWN and a SACK
+ */
+ if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV,
+ stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_19);
+ }
+ sctp_send_shutdown(stcb,
+ ((stcb->asoc.alternate) ? stcb->asoc.alternate : stcb->asoc.primary_destination));
+ if (is_a_gap) {
+ sctp_send_sack(stcb, SCTP_SO_NOT_LOCKED);
+ }
+ } else {
+ /*
+ * CMT DAC algorithm: increase number of packets
+ * received since last ack
+ */
+ stcb->asoc.cmt_dac_pkts_rcvd++;
+
+ if ((stcb->asoc.send_sack == 1) || /* We need to send a SACK */
+ ((was_a_gap) && (is_a_gap == 0)) || /* was a gap, but no
+ * longer is one */
+ (stcb->asoc.numduptsns) || /* we have dup's */
+ (is_a_gap) || /* is still a gap */
+ (stcb->asoc.delayed_ack == 0) || /* Delayed sack disabled */
+ (stcb->asoc.data_pkts_seen >= stcb->asoc.sack_freq)) { /* hit limit of pkts */
+ if ((stcb->asoc.sctp_cmt_on_off > 0) &&
+ (SCTP_BASE_SYSCTL(sctp_cmt_use_dac)) &&
+ (stcb->asoc.send_sack == 0) &&
+ (stcb->asoc.numduptsns == 0) &&
+ (stcb->asoc.delayed_ack) &&
+ (!SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer))) {
+ /*
+ * CMT DAC algorithm: With CMT,
+ * delay acks even in the face of
+ * reordering. Therefore, if acks
+ * that do not have to be sent
+ * because of the above reasons,
+ * will be delayed. That is, acks
+ * that would have been sent due to
+ * gap reports will be delayed with
+ * DAC. Start the delayed ack timer.
+ */
+ sctp_timer_start(SCTP_TIMER_TYPE_RECV,
+ stcb->sctp_ep, stcb, NULL);
+ } else {
+ /*
+ * Ok we must build a SACK since the
+ * timer is pending, we got our
+ * first packet OR there are gaps or
+ * duplicates.
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_20);
+ sctp_send_sack(stcb, SCTP_SO_NOT_LOCKED);
+ }
+ } else {
+ if (!SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_RECV,
+ stcb->sctp_ep, stcb, NULL);
+ }
+ }
+ }
+}
+
+int
+sctp_process_data(struct mbuf **mm, int iphlen, int *offset, int length,
+ struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net, uint32_t *high_tsn)
+{
+ struct sctp_chunkhdr *ch, chunk_buf;
+ struct sctp_association *asoc;
+ int num_chunks = 0; /* number of control chunks processed */
+ int stop_proc = 0;
+ int break_flag, last_chunk;
+ int abort_flag = 0, was_a_gap;
+ struct mbuf *m;
+ uint32_t highest_tsn;
+ uint16_t chk_length;
+
+ /* set the rwnd */
+ sctp_set_rwnd(stcb, &stcb->asoc);
+
+ m = *mm;
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ asoc = &stcb->asoc;
+ if (SCTP_TSN_GT(asoc->highest_tsn_inside_nr_map, asoc->highest_tsn_inside_map)) {
+ highest_tsn = asoc->highest_tsn_inside_nr_map;
+ } else {
+ highest_tsn = asoc->highest_tsn_inside_map;
+ }
+ was_a_gap = SCTP_TSN_GT(highest_tsn, stcb->asoc.cumulative_tsn);
+ /*
+ * setup where we got the last DATA packet from for any SACK that
+ * may need to go out. Don't bump the net. This is done ONLY when a
+ * chunk is assigned.
+ */
+ asoc->last_data_chunk_from = net;
+
+ /*-
+ * Now before we proceed we must figure out if this is a wasted
+ * cluster... i.e. it is a small packet sent in and yet the driver
+ * underneath allocated a full cluster for it. If so we must copy it
+ * to a smaller mbuf and free up the cluster mbuf. This will help
+ * with cluster starvation.
+ */
+ if (SCTP_BUF_LEN(m) < (long)MLEN && SCTP_BUF_NEXT(m) == NULL) {
+ /* we only handle mbufs that are singletons.. not chains */
+ m = sctp_get_mbuf_for_msg(SCTP_BUF_LEN(m), 0, M_NOWAIT, 1, MT_DATA);
+ if (m) {
+ /* ok lets see if we can copy the data up */
+ caddr_t *from, *to;
+ /* get the pointers and copy */
+ to = mtod(m, caddr_t *);
+ from = mtod((*mm), caddr_t *);
+ memcpy(to, from, SCTP_BUF_LEN((*mm)));
+ /* copy the length and free up the old */
+ SCTP_BUF_LEN(m) = SCTP_BUF_LEN((*mm));
+ sctp_m_freem(*mm);
+ /* success, back copy */
+ *mm = m;
+ } else {
+ /* We are in trouble in the mbuf world .. yikes */
+ m = *mm;
+ }
+ }
+ /* get pointer to the first chunk header */
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_chunkhdr),
+ (uint8_t *)&chunk_buf);
+ if (ch == NULL) {
+ return (1);
+ }
+ /*
+ * process all DATA chunks...
+ */
+ *high_tsn = asoc->cumulative_tsn;
+ break_flag = 0;
+ asoc->data_pkts_seen++;
+ while (stop_proc == 0) {
+ /* validate chunk length */
+ chk_length = ntohs(ch->chunk_length);
+ if (length - *offset < chk_length) {
+ /* all done, mutulated chunk */
+ stop_proc = 1;
+ continue;
+ }
+ if ((asoc->idata_supported == 1) &&
+ (ch->chunk_type == SCTP_DATA)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTP_SNPRINTF(msg, sizeof(msg), "%s", "DATA chunk received when I-DATA was negotiated");
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_21;
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return (2);
+ }
+ if ((asoc->idata_supported == 0) &&
+ (ch->chunk_type == SCTP_IDATA)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTP_SNPRINTF(msg, sizeof(msg), "%s", "I-DATA chunk received when DATA was negotiated");
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_22;
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return (2);
+ }
+ if ((ch->chunk_type == SCTP_DATA) ||
+ (ch->chunk_type == SCTP_IDATA)) {
+ uint16_t clen;
+
+ if (ch->chunk_type == SCTP_DATA) {
+ clen = sizeof(struct sctp_data_chunk);
+ } else {
+ clen = sizeof(struct sctp_idata_chunk);
+ }
+ if (chk_length < clen) {
+ /*
+ * Need to send an abort since we had a
+ * invalid data chunk.
+ */
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTP_SNPRINTF(msg, sizeof(msg), "%s chunk of length %u",
+ ch->chunk_type == SCTP_DATA ? "DATA" : "I-DATA",
+ chk_length);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_23;
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return (2);
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xB1, 0);
+#endif
+ if (SCTP_SIZE32(chk_length) == (length - *offset)) {
+ last_chunk = 1;
+ } else {
+ last_chunk = 0;
+ }
+ if (sctp_process_a_data_chunk(stcb, asoc, mm, *offset,
+ chk_length, net, high_tsn, &abort_flag, &break_flag,
+ last_chunk, ch->chunk_type)) {
+ num_chunks++;
+ }
+ if (abort_flag)
+ return (2);
+
+ if (break_flag) {
+ /*
+ * Set because of out of rwnd space and no
+ * drop rep space left.
+ */
+ stop_proc = 1;
+ continue;
+ }
+ } else {
+ /* not a data chunk in the data region */
+ switch (ch->chunk_type) {
+ case SCTP_INITIATION:
+ case SCTP_INITIATION_ACK:
+ case SCTP_SELECTIVE_ACK:
+ case SCTP_NR_SELECTIVE_ACK:
+ case SCTP_HEARTBEAT_REQUEST:
+ case SCTP_HEARTBEAT_ACK:
+ case SCTP_ABORT_ASSOCIATION:
+ case SCTP_SHUTDOWN:
+ case SCTP_SHUTDOWN_ACK:
+ case SCTP_OPERATION_ERROR:
+ case SCTP_COOKIE_ECHO:
+ case SCTP_COOKIE_ACK:
+ case SCTP_ECN_ECHO:
+ case SCTP_ECN_CWR:
+ case SCTP_SHUTDOWN_COMPLETE:
+ case SCTP_AUTHENTICATION:
+ case SCTP_ASCONF_ACK:
+ case SCTP_PACKET_DROPPED:
+ case SCTP_STREAM_RESET:
+ case SCTP_FORWARD_CUM_TSN:
+ case SCTP_ASCONF:
+ {
+ /*
+ * Now, what do we do with KNOWN chunks that
+ * are NOT in the right place?
+ *
+ * For now, I do nothing but ignore them. We
+ * may later want to add sysctl stuff to
+ * switch out and do either an ABORT() or
+ * possibly process them.
+ */
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTP_SNPRINTF(msg, sizeof(msg), "DATA chunk followed by chunk of type %2.2x",
+ ch->chunk_type);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return (2);
+ }
+ default:
+ /*
+ * Unknown chunk type: use bit rules after
+ * checking length
+ */
+ if (chk_length < sizeof(struct sctp_chunkhdr)) {
+ /*
+ * Need to send an abort since we had a
+ * invalid chunk.
+ */
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTP_SNPRINTF(msg, sizeof(msg), "Chunk of length %u", chk_length);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_24;
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return (2);
+ }
+ if (ch->chunk_type & 0x40) {
+ /* Add a error report to the queue */
+ struct mbuf *op_err;
+ struct sctp_gen_error_cause *cause;
+
+ op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_gen_error_cause),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err != NULL) {
+ cause = mtod(op_err, struct sctp_gen_error_cause *);
+ cause->code = htons(SCTP_CAUSE_UNRECOG_CHUNK);
+ cause->length = htons((uint16_t)(chk_length + sizeof(struct sctp_gen_error_cause)));
+ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_gen_error_cause);
+ SCTP_BUF_NEXT(op_err) = SCTP_M_COPYM(m, *offset, chk_length, M_NOWAIT);
+ if (SCTP_BUF_NEXT(op_err) != NULL) {
+ sctp_queue_op_err(stcb, op_err);
+ } else {
+ sctp_m_freem(op_err);
+ }
+ }
+ }
+ if ((ch->chunk_type & 0x80) == 0) {
+ /* discard the rest of this packet */
+ stop_proc = 1;
+ } /* else skip this bad chunk and
+ * continue... */
+ break;
+ } /* switch of chunk type */
+ }
+ *offset += SCTP_SIZE32(chk_length);
+ if ((*offset >= length) || stop_proc) {
+ /* no more data left in the mbuf chain */
+ stop_proc = 1;
+ continue;
+ }
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_chunkhdr),
+ (uint8_t *)&chunk_buf);
+ if (ch == NULL) {
+ *offset = length;
+ stop_proc = 1;
+ continue;
+ }
+ }
+ if (break_flag) {
+ /*
+ * we need to report rwnd overrun drops.
+ */
+ sctp_send_packet_dropped(stcb, net, *mm, length, iphlen, 0);
+ }
+ if (num_chunks) {
+ /*
+ * Did we get data, if so update the time for auto-close and
+ * give peer credit for being alive.
+ */
+ SCTP_STAT_INCR(sctps_recvpktwithdata);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INDATA,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_last_rcvd);
+ }
+ /* now service all of the reassm queue if needed */
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_SENT) {
+ /* Assure that we ack right away */
+ stcb->asoc.send_sack = 1;
+ }
+ /* Start a sack timer or QUEUE a SACK for sending */
+ sctp_sack_check(stcb, was_a_gap);
+ return (0);
+}
+
+static int
+sctp_process_segment_range(struct sctp_tcb *stcb, struct sctp_tmit_chunk **p_tp1, uint32_t last_tsn,
+ uint16_t frag_strt, uint16_t frag_end, int nr_sacking,
+ int *num_frs,
+ uint32_t *biggest_newly_acked_tsn,
+ uint32_t *this_sack_lowest_newack,
+ int *rto_ok)
+{
+ struct sctp_tmit_chunk *tp1;
+ unsigned int theTSN;
+ int j, wake_him = 0, circled = 0;
+
+ /* Recover the tp1 we last saw */
+ tp1 = *p_tp1;
+ if (tp1 == NULL) {
+ tp1 = TAILQ_FIRST(&stcb->asoc.sent_queue);
+ }
+ for (j = frag_strt; j <= frag_end; j++) {
+ theTSN = j + last_tsn;
+ while (tp1) {
+ if (tp1->rec.data.doing_fast_retransmit)
+ (*num_frs) += 1;
+
+ /*-
+ * CMT: CUCv2 algorithm. For each TSN being
+ * processed from the sent queue, track the
+ * next expected pseudo-cumack, or
+ * rtx_pseudo_cumack, if required. Separate
+ * cumack trackers for first transmissions,
+ * and retransmissions.
+ */
+ if ((tp1->sent < SCTP_DATAGRAM_RESEND) &&
+ (tp1->whoTo->find_pseudo_cumack == 1) &&
+ (tp1->snd_count == 1)) {
+ tp1->whoTo->pseudo_cumack = tp1->rec.data.tsn;
+ tp1->whoTo->find_pseudo_cumack = 0;
+ }
+ if ((tp1->sent < SCTP_DATAGRAM_RESEND) &&
+ (tp1->whoTo->find_rtx_pseudo_cumack == 1) &&
+ (tp1->snd_count > 1)) {
+ tp1->whoTo->rtx_pseudo_cumack = tp1->rec.data.tsn;
+ tp1->whoTo->find_rtx_pseudo_cumack = 0;
+ }
+ if (tp1->rec.data.tsn == theTSN) {
+ if (tp1->sent != SCTP_DATAGRAM_UNSENT) {
+ /*-
+ * must be held until
+ * cum-ack passes
+ */
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ /*-
+ * If it is less than RESEND, it is
+ * now no-longer in flight.
+ * Higher values may already be set
+ * via previous Gap Ack Blocks...
+ * i.e. ACKED or RESEND.
+ */
+ if (SCTP_TSN_GT(tp1->rec.data.tsn,
+ *biggest_newly_acked_tsn)) {
+ *biggest_newly_acked_tsn = tp1->rec.data.tsn;
+ }
+ /*-
+ * CMT: SFR algo (and HTNA) - set
+ * saw_newack to 1 for dest being
+ * newly acked. update
+ * this_sack_highest_newack if
+ * appropriate.
+ */
+ if (tp1->rec.data.chunk_was_revoked == 0)
+ tp1->whoTo->saw_newack = 1;
+
+ if (SCTP_TSN_GT(tp1->rec.data.tsn,
+ tp1->whoTo->this_sack_highest_newack)) {
+ tp1->whoTo->this_sack_highest_newack =
+ tp1->rec.data.tsn;
+ }
+ /*-
+ * CMT DAC algo: also update
+ * this_sack_lowest_newack
+ */
+ if (*this_sack_lowest_newack == 0) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_LOGGING_ENABLE) {
+ sctp_log_sack(*this_sack_lowest_newack,
+ last_tsn,
+ tp1->rec.data.tsn,
+ 0,
+ 0,
+ SCTP_LOG_TSN_ACKED);
+ }
+ *this_sack_lowest_newack = tp1->rec.data.tsn;
+ }
+ /*-
+ * CMT: CUCv2 algorithm. If (rtx-)pseudo-cumack for corresp
+ * dest is being acked, then we have a new (rtx-)pseudo-cumack. Set
+ * new_(rtx_)pseudo_cumack to TRUE so that the cwnd for this dest can be
+ * updated. Also trigger search for the next expected (rtx-)pseudo-cumack.
+ * Separate pseudo_cumack trackers for first transmissions and
+ * retransmissions.
+ */
+ if (tp1->rec.data.tsn == tp1->whoTo->pseudo_cumack) {
+ if (tp1->rec.data.chunk_was_revoked == 0) {
+ tp1->whoTo->new_pseudo_cumack = 1;
+ }
+ tp1->whoTo->find_pseudo_cumack = 1;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, tp1->whoTo, tp1->rec.data.tsn, SCTP_CWND_LOG_FROM_SACK);
+ }
+ if (tp1->rec.data.tsn == tp1->whoTo->rtx_pseudo_cumack) {
+ if (tp1->rec.data.chunk_was_revoked == 0) {
+ tp1->whoTo->new_pseudo_cumack = 1;
+ }
+ tp1->whoTo->find_rtx_pseudo_cumack = 1;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_LOGGING_ENABLE) {
+ sctp_log_sack(*biggest_newly_acked_tsn,
+ last_tsn,
+ tp1->rec.data.tsn,
+ frag_strt,
+ frag_end,
+ SCTP_LOG_TSN_ACKED);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_GAP,
+ tp1->whoTo->flight_size,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ }
+ sctp_flight_size_decrease(tp1);
+ if (stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged)(tp1->whoTo,
+ tp1);
+ }
+ sctp_total_flight_decrease(stcb, tp1);
+
+ tp1->whoTo->net_ack += tp1->send_size;
+ if (tp1->snd_count < 2) {
+ /*-
+ * True non-retransmitted chunk
+ */
+ tp1->whoTo->net_ack2 += tp1->send_size;
+
+ /*-
+ * update RTO too ?
+ */
+ if (tp1->do_rtt) {
+ if (*rto_ok &&
+ sctp_calculate_rto(stcb,
+ &stcb->asoc,
+ tp1->whoTo,
+ &tp1->sent_rcv_time,
+ SCTP_RTT_FROM_DATA)) {
+ *rto_ok = 0;
+ }
+ if (tp1->whoTo->rto_needed == 0) {
+ tp1->whoTo->rto_needed = 1;
+ }
+ tp1->do_rtt = 0;
+ }
+ }
+ }
+ if (tp1->sent <= SCTP_DATAGRAM_RESEND) {
+ if (SCTP_TSN_GT(tp1->rec.data.tsn,
+ stcb->asoc.this_sack_highest_gap)) {
+ stcb->asoc.this_sack_highest_gap =
+ tp1->rec.data.tsn;
+ }
+ if (tp1->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_decr(stcb->asoc.sent_queue_retran_cnt);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xB2,
+ (stcb->asoc.sent_queue_retran_cnt & 0x000000ff));
+#endif
+ }
+ }
+ /*-
+ * All chunks NOT UNSENT fall through here and are marked
+ * (leave PR-SCTP ones that are to skip alone though)
+ */
+ if ((tp1->sent != SCTP_FORWARD_TSN_SKIP) &&
+ (tp1->sent != SCTP_DATAGRAM_NR_ACKED)) {
+ tp1->sent = SCTP_DATAGRAM_MARKED;
+ }
+ if (tp1->rec.data.chunk_was_revoked) {
+ /* deflate the cwnd */
+ tp1->whoTo->cwnd -= tp1->book_size;
+ tp1->rec.data.chunk_was_revoked = 0;
+ }
+ /* NR Sack code here */
+ if (nr_sacking &&
+ (tp1->sent != SCTP_DATAGRAM_NR_ACKED)) {
+ if (stcb->asoc.strmout[tp1->rec.data.sid].chunks_on_queues > 0) {
+ stcb->asoc.strmout[tp1->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", tp1->rec.data.sid);
+#endif
+ }
+ if ((stcb->asoc.strmout[tp1->rec.data.sid].chunks_on_queues == 0) &&
+ (stcb->asoc.strmout[tp1->rec.data.sid].state == SCTP_STREAM_RESET_PENDING) &&
+ TAILQ_EMPTY(&stcb->asoc.strmout[tp1->rec.data.sid].outqueue)) {
+ stcb->asoc.trigger_reset = 1;
+ }
+ tp1->sent = SCTP_DATAGRAM_NR_ACKED;
+ if (tp1->data) {
+ /* sa_ignore NO_NULL_CHK */
+ sctp_free_bufspace(stcb, &stcb->asoc, tp1, 1);
+ sctp_m_freem(tp1->data);
+ tp1->data = NULL;
+ }
+ wake_him++;
+ }
+ }
+ break;
+ } /* if (tp1->tsn == theTSN) */
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, theTSN)) {
+ break;
+ }
+ tp1 = TAILQ_NEXT(tp1, sctp_next);
+ if ((tp1 == NULL) && (circled == 0)) {
+ circled++;
+ tp1 = TAILQ_FIRST(&stcb->asoc.sent_queue);
+ }
+ } /* end while (tp1) */
+ if (tp1 == NULL) {
+ circled = 0;
+ tp1 = TAILQ_FIRST(&stcb->asoc.sent_queue);
+ }
+ /* In case the fragments were not in order we must reset */
+ } /* end for (j = fragStart */
+ *p_tp1 = tp1;
+ return (wake_him); /* Return value only used for nr-sack */
+}
+
+static int
+sctp_handle_segments(struct mbuf *m, int *offset, struct sctp_tcb *stcb, struct sctp_association *asoc,
+ uint32_t last_tsn, uint32_t *biggest_tsn_acked,
+ uint32_t *biggest_newly_acked_tsn, uint32_t *this_sack_lowest_newack,
+ int num_seg, int num_nr_seg, int *rto_ok)
+{
+ struct sctp_gap_ack_block *frag, block;
+ struct sctp_tmit_chunk *tp1;
+ int i;
+ int num_frs = 0;
+ int chunk_freed;
+ int non_revocable;
+ uint16_t frag_strt, frag_end, prev_frag_end;
+
+ tp1 = TAILQ_FIRST(&asoc->sent_queue);
+ prev_frag_end = 0;
+ chunk_freed = 0;
+
+ for (i = 0; i < (num_seg + num_nr_seg); i++) {
+ if (i == num_seg) {
+ prev_frag_end = 0;
+ tp1 = TAILQ_FIRST(&asoc->sent_queue);
+ }
+ frag = (struct sctp_gap_ack_block *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_gap_ack_block), (uint8_t *) &block);
+ *offset += sizeof(block);
+ if (frag == NULL) {
+ return (chunk_freed);
+ }
+ frag_strt = ntohs(frag->start);
+ frag_end = ntohs(frag->end);
+
+ if (frag_strt > frag_end) {
+ /* This gap report is malformed, skip it. */
+ continue;
+ }
+ if (frag_strt <= prev_frag_end) {
+ /* This gap report is not in order, so restart. */
+ tp1 = TAILQ_FIRST(&asoc->sent_queue);
+ }
+ if (SCTP_TSN_GT((last_tsn + frag_end), *biggest_tsn_acked)) {
+ *biggest_tsn_acked = last_tsn + frag_end;
+ }
+ if (i < num_seg) {
+ non_revocable = 0;
+ } else {
+ non_revocable = 1;
+ }
+ if (sctp_process_segment_range(stcb, &tp1, last_tsn, frag_strt, frag_end,
+ non_revocable, &num_frs, biggest_newly_acked_tsn,
+ this_sack_lowest_newack, rto_ok)) {
+ chunk_freed = 1;
+ }
+ prev_frag_end = frag_end;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ if (num_frs)
+ sctp_log_fr(*biggest_tsn_acked,
+ *biggest_newly_acked_tsn,
+ last_tsn, SCTP_FR_LOG_BIGGEST_TSNS);
+ }
+ return (chunk_freed);
+}
+
+static void
+sctp_check_for_revoked(struct sctp_tcb *stcb,
+ struct sctp_association *asoc, uint32_t cumack,
+ uint32_t biggest_tsn_acked)
+{
+ struct sctp_tmit_chunk *tp1;
+
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, cumack)) {
+ /*
+ * ok this guy is either ACK or MARKED. If it is
+ * ACKED it has been previously acked but not this
+ * time i.e. revoked. If it is MARKED it was ACK'ed
+ * again.
+ */
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, biggest_tsn_acked)) {
+ break;
+ }
+ if (tp1->sent == SCTP_DATAGRAM_ACKED) {
+ /* it has been revoked */
+ tp1->sent = SCTP_DATAGRAM_SENT;
+ tp1->rec.data.chunk_was_revoked = 1;
+ /* We must add this stuff back in to
+ * assure timers and such get started.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_UP_REVOKE,
+ tp1->whoTo->flight_size,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ }
+ sctp_flight_size_increase(tp1);
+ sctp_total_flight_increase(stcb, tp1);
+ /* We inflate the cwnd to compensate for our
+ * artificial inflation of the flight_size.
+ */
+ tp1->whoTo->cwnd += tp1->book_size;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_LOGGING_ENABLE) {
+ sctp_log_sack(asoc->last_acked_seq,
+ cumack,
+ tp1->rec.data.tsn,
+ 0,
+ 0,
+ SCTP_LOG_TSN_REVOKED);
+ }
+ } else if (tp1->sent == SCTP_DATAGRAM_MARKED) {
+ /* it has been re-acked in this SACK */
+ tp1->sent = SCTP_DATAGRAM_ACKED;
+ }
+ }
+ if (tp1->sent == SCTP_DATAGRAM_UNSENT)
+ break;
+ }
+}
+
+static void
+sctp_strike_gap_ack_chunks(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ uint32_t biggest_tsn_acked, uint32_t biggest_tsn_newly_acked, uint32_t this_sack_lowest_newack, int accum_moved)
+{
+ struct sctp_tmit_chunk *tp1;
+ int strike_flag = 0;
+ struct timeval now;
+ uint32_t sending_seq;
+ struct sctp_nets *net;
+ int num_dests_sacked = 0;
+
+ /*
+ * select the sending_seq, this is either the next thing ready to be
+ * sent but not transmitted, OR, the next seq we assign.
+ */
+ tp1 = TAILQ_FIRST(&stcb->asoc.send_queue);
+ if (tp1 == NULL) {
+ sending_seq = asoc->sending_seq;
+ } else {
+ sending_seq = tp1->rec.data.tsn;
+ }
+
+ /* CMT DAC algo: finding out if SACK is a mixed SACK */
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ SCTP_BASE_SYSCTL(sctp_cmt_use_dac)) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (net->saw_newack)
+ num_dests_sacked++;
+ }
+ }
+ if (stcb->asoc.prsctp_supported) {
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ }
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ strike_flag = 0;
+ if (tp1->no_fr_allowed) {
+ /* this one had a timeout or something */
+ continue;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ if (tp1->sent < SCTP_DATAGRAM_RESEND)
+ sctp_log_fr(biggest_tsn_newly_acked,
+ tp1->rec.data.tsn,
+ tp1->sent,
+ SCTP_FR_LOG_CHECK_STRIKE);
+ }
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, biggest_tsn_acked) ||
+ tp1->sent == SCTP_DATAGRAM_UNSENT) {
+ /* done */
+ break;
+ }
+ if (stcb->asoc.prsctp_supported) {
+ if ((PR_SCTP_TTL_ENABLED(tp1->flags)) && tp1->sent < SCTP_DATAGRAM_ACKED) {
+ /* Is it expired? */
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (timercmp(&now, &tp1->rec.data.timetodrop, >)) {
+#else
+ if (timevalcmp(&now, &tp1->rec.data.timetodrop, >)) {
+#endif
+ /* Yes so drop it */
+ if (tp1->data != NULL) {
+ (void)sctp_release_pr_sctp_chunk(stcb, tp1, 1,
+ SCTP_SO_NOT_LOCKED);
+ }
+ continue;
+ }
+ }
+ }
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, asoc->this_sack_highest_gap) &&
+ !(accum_moved && asoc->fast_retran_loss_recovery)) {
+ /* we are beyond the tsn in the sack */
+ break;
+ }
+ if (tp1->sent >= SCTP_DATAGRAM_RESEND) {
+ /* either a RESEND, ACKED, or MARKED */
+ /* skip */
+ if (tp1->sent == SCTP_FORWARD_TSN_SKIP) {
+ /* Continue strikin FWD-TSN chunks */
+ tp1->rec.data.fwd_tsn_cnt++;
+ }
+ continue;
+ }
+ /*
+ * CMT : SFR algo (covers part of DAC and HTNA as well)
+ */
+ if (tp1->whoTo && tp1->whoTo->saw_newack == 0) {
+ /*
+ * No new acks were received for data sent to this
+ * dest. Therefore, according to the SFR algo for
+ * CMT, no data sent to this dest can be marked for
+ * FR using this SACK.
+ */
+ continue;
+ } else if (tp1->whoTo &&
+ SCTP_TSN_GT(tp1->rec.data.tsn,
+ tp1->whoTo->this_sack_highest_newack) &&
+ !(accum_moved && asoc->fast_retran_loss_recovery)) {
+ /*
+ * CMT: New acks were received for data sent to
+ * this dest. But no new acks were seen for data
+ * sent after tp1. Therefore, according to the SFR
+ * algo for CMT, tp1 cannot be marked for FR using
+ * this SACK. This step covers part of the DAC algo
+ * and the HTNA algo as well.
+ */
+ continue;
+ }
+ /*
+ * Here we check to see if we were have already done a FR
+ * and if so we see if the biggest TSN we saw in the sack is
+ * smaller than the recovery point. If so we don't strike
+ * the tsn... otherwise we CAN strike the TSN.
+ */
+ /*
+ * @@@ JRI: Check for CMT
+ * if (accum_moved && asoc->fast_retran_loss_recovery && (sctp_cmt_on_off == 0)) {
+ */
+ if (accum_moved && asoc->fast_retran_loss_recovery) {
+ /*
+ * Strike the TSN if in fast-recovery and cum-ack
+ * moved.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(biggest_tsn_newly_acked,
+ tp1->rec.data.tsn,
+ tp1->sent,
+ SCTP_FR_LOG_STRIKE_CHUNK);
+ }
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ tp1->sent++;
+ }
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ SCTP_BASE_SYSCTL(sctp_cmt_use_dac)) {
+ /*
+ * CMT DAC algorithm: If SACK flag is set to
+ * 0, then lowest_newack test will not pass
+ * because it would have been set to the
+ * cumack earlier. If not already to be
+ * rtx'd, If not a mixed sack and if tp1 is
+ * not between two sacked TSNs, then mark by
+ * one more.
+ * NOTE that we are marking by one additional time since the SACK DAC flag indicates that
+ * two packets have been received after this missing TSN.
+ */
+ if ((tp1->sent < SCTP_DATAGRAM_RESEND) && (num_dests_sacked == 1) &&
+ SCTP_TSN_GT(this_sack_lowest_newack, tp1->rec.data.tsn)) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(16 + num_dests_sacked,
+ tp1->rec.data.tsn,
+ tp1->sent,
+ SCTP_FR_LOG_STRIKE_CHUNK);
+ }
+ tp1->sent++;
+ }
+ }
+ } else if ((tp1->rec.data.doing_fast_retransmit) &&
+ (asoc->sctp_cmt_on_off == 0)) {
+ /*
+ * For those that have done a FR we must take
+ * special consideration if we strike. I.e the
+ * biggest_newly_acked must be higher than the
+ * sending_seq at the time we did the FR.
+ */
+ if (
+#ifdef SCTP_FR_TO_ALTERNATE
+ /*
+ * If FR's go to new networks, then we must only do
+ * this for singly homed asoc's. However if the FR's
+ * go to the same network (Armando's work) then its
+ * ok to FR multiple times.
+ */
+ (asoc->numnets < 2)
+#else
+ (1)
+#endif
+ ) {
+ if (SCTP_TSN_GE(biggest_tsn_newly_acked,
+ tp1->rec.data.fast_retran_tsn)) {
+ /*
+ * Strike the TSN, since this ack is
+ * beyond where things were when we
+ * did a FR.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(biggest_tsn_newly_acked,
+ tp1->rec.data.tsn,
+ tp1->sent,
+ SCTP_FR_LOG_STRIKE_CHUNK);
+ }
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ tp1->sent++;
+ }
+ strike_flag = 1;
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ SCTP_BASE_SYSCTL(sctp_cmt_use_dac)) {
+ /*
+ * CMT DAC algorithm: If
+ * SACK flag is set to 0,
+ * then lowest_newack test
+ * will not pass because it
+ * would have been set to
+ * the cumack earlier. If
+ * not already to be rtx'd,
+ * If not a mixed sack and
+ * if tp1 is not between two
+ * sacked TSNs, then mark by
+ * one more.
+ * NOTE that we are marking by one additional time since the SACK DAC flag indicates that
+ * two packets have been received after this missing TSN.
+ */
+ if ((tp1->sent < SCTP_DATAGRAM_RESEND) &&
+ (num_dests_sacked == 1) &&
+ SCTP_TSN_GT(this_sack_lowest_newack,
+ tp1->rec.data.tsn)) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(32 + num_dests_sacked,
+ tp1->rec.data.tsn,
+ tp1->sent,
+ SCTP_FR_LOG_STRIKE_CHUNK);
+ }
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ tp1->sent++;
+ }
+ }
+ }
+ }
+ }
+ /*
+ * JRI: TODO: remove code for HTNA algo. CMT's
+ * SFR algo covers HTNA.
+ */
+ } else if (SCTP_TSN_GT(tp1->rec.data.tsn,
+ biggest_tsn_newly_acked)) {
+ /*
+ * We don't strike these: This is the HTNA
+ * algorithm i.e. we don't strike If our TSN is
+ * larger than the Highest TSN Newly Acked.
+ */
+ ;
+ } else {
+ /* Strike the TSN */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(biggest_tsn_newly_acked,
+ tp1->rec.data.tsn,
+ tp1->sent,
+ SCTP_FR_LOG_STRIKE_CHUNK);
+ }
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ tp1->sent++;
+ }
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ SCTP_BASE_SYSCTL(sctp_cmt_use_dac)) {
+ /*
+ * CMT DAC algorithm: If SACK flag is set to
+ * 0, then lowest_newack test will not pass
+ * because it would have been set to the
+ * cumack earlier. If not already to be
+ * rtx'd, If not a mixed sack and if tp1 is
+ * not between two sacked TSNs, then mark by
+ * one more.
+ * NOTE that we are marking by one additional time since the SACK DAC flag indicates that
+ * two packets have been received after this missing TSN.
+ */
+ if ((tp1->sent < SCTP_DATAGRAM_RESEND) && (num_dests_sacked == 1) &&
+ SCTP_TSN_GT(this_sack_lowest_newack, tp1->rec.data.tsn)) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(48 + num_dests_sacked,
+ tp1->rec.data.tsn,
+ tp1->sent,
+ SCTP_FR_LOG_STRIKE_CHUNK);
+ }
+ tp1->sent++;
+ }
+ }
+ }
+ if (tp1->sent == SCTP_DATAGRAM_RESEND) {
+ struct sctp_nets *alt;
+
+ /* fix counts and things */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_RSND,
+ (tp1->whoTo ? (tp1->whoTo->flight_size) : 0),
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ }
+ if (tp1->whoTo) {
+ tp1->whoTo->net_ack++;
+ sctp_flight_size_decrease(tp1);
+ if (stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged)(tp1->whoTo,
+ tp1);
+ }
+ }
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_RWND_ENABLE) {
+ sctp_log_rwnd(SCTP_INCREASE_PEER_RWND,
+ asoc->peers_rwnd, tp1->send_size, SCTP_BASE_SYSCTL(sctp_peer_chunk_oh));
+ }
+ /* add back to the rwnd */
+ asoc->peers_rwnd += (tp1->send_size + SCTP_BASE_SYSCTL(sctp_peer_chunk_oh));
+
+ /* remove from the total flight */
+ sctp_total_flight_decrease(stcb, tp1);
+
+ if ((stcb->asoc.prsctp_supported) &&
+ (PR_SCTP_RTX_ENABLED(tp1->flags))) {
+ /* Has it been retransmitted tv_sec times? - we store the retran count there. */
+ if (tp1->snd_count > tp1->rec.data.timetodrop.tv_sec) {
+ /* Yes, so drop it */
+ if (tp1->data != NULL) {
+ (void)sctp_release_pr_sctp_chunk(stcb, tp1, 1,
+ SCTP_SO_NOT_LOCKED);
+ }
+ /* Make sure to flag we had a FR */
+ if (tp1->whoTo != NULL) {
+ tp1->whoTo->net_ack++;
+ }
+ continue;
+ }
+ }
+ /* SCTP_PRINTF("OK, we are now ready to FR this guy\n"); */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(tp1->rec.data.tsn, tp1->snd_count,
+ 0, SCTP_FR_MARKED);
+ }
+ if (strike_flag) {
+ /* This is a subsequent FR */
+ SCTP_STAT_INCR(sctps_sendmultfastretrans);
+ }
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ if (asoc->sctp_cmt_on_off > 0) {
+ /*
+ * CMT: Using RTX_SSTHRESH policy for CMT.
+ * If CMT is being used, then pick dest with
+ * largest ssthresh for any retransmission.
+ */
+ tp1->no_fr_allowed = 1;
+ alt = tp1->whoTo;
+ /*sa_ignore NO_NULL_CHK*/
+ if (asoc->sctp_cmt_pf > 0) {
+ /* JRS 5/18/07 - If CMT PF is on, use the PF version of find_alt_net() */
+ alt = sctp_find_alternate_net(stcb, alt, 2);
+ } else {
+ /* JRS 5/18/07 - If only CMT is on, use the CMT version of find_alt_net() */
+ /*sa_ignore NO_NULL_CHK*/
+ alt = sctp_find_alternate_net(stcb, alt, 1);
+ }
+ if (alt == NULL) {
+ alt = tp1->whoTo;
+ }
+ /*
+ * CUCv2: If a different dest is picked for
+ * the retransmission, then new
+ * (rtx-)pseudo_cumack needs to be tracked
+ * for orig dest. Let CUCv2 track new (rtx-)
+ * pseudo-cumack always.
+ */
+ if (tp1->whoTo) {
+ tp1->whoTo->find_pseudo_cumack = 1;
+ tp1->whoTo->find_rtx_pseudo_cumack = 1;
+ }
+ } else {/* CMT is OFF */
+#ifdef SCTP_FR_TO_ALTERNATE
+ /* Can we find an alternate? */
+ alt = sctp_find_alternate_net(stcb, tp1->whoTo, 0);
+#else
+ /*
+ * default behavior is to NOT retransmit
+ * FR's to an alternate. Armando Caro's
+ * paper details why.
+ */
+ alt = tp1->whoTo;
+#endif
+ }
+
+ tp1->rec.data.doing_fast_retransmit = 1;
+ /* mark the sending seq for possible subsequent FR's */
+ /*
+ * SCTP_PRINTF("Marking TSN for FR new value %x\n",
+ * (uint32_t)tpi->rec.data.tsn);
+ */
+ if (TAILQ_EMPTY(&asoc->send_queue)) {
+ /*
+ * If the queue of send is empty then its
+ * the next sequence number that will be
+ * assigned so we subtract one from this to
+ * get the one we last sent.
+ */
+ tp1->rec.data.fast_retran_tsn = sending_seq;
+ } else {
+ /*
+ * If there are chunks on the send queue
+ * (unsent data that has made it from the
+ * stream queues but not out the door, we
+ * take the first one (which will have the
+ * lowest TSN) and subtract one to get the
+ * one we last sent.
+ */
+ struct sctp_tmit_chunk *ttt;
+
+ ttt = TAILQ_FIRST(&asoc->send_queue);
+ tp1->rec.data.fast_retran_tsn =
+ ttt->rec.data.tsn;
+ }
+
+ if (tp1->do_rtt) {
+ /*
+ * this guy had a RTO calculation pending on
+ * it, cancel it
+ */
+ if ((tp1->whoTo != NULL) &&
+ (tp1->whoTo->rto_needed == 0)) {
+ tp1->whoTo->rto_needed = 1;
+ }
+ tp1->do_rtt = 0;
+ }
+ if (alt != tp1->whoTo) {
+ /* yes, there is an alternate. */
+ sctp_free_remote_addr(tp1->whoTo);
+ /*sa_ignore FREED_MEMORY*/
+ tp1->whoTo = alt;
+ atomic_add_int(&alt->ref_count, 1);
+ }
+ }
+ }
+}
+
+struct sctp_tmit_chunk *
+sctp_try_advance_peer_ack_point(struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *tp1, *tp2, *a_adv = NULL;
+ struct timeval now;
+ int now_filled = 0;
+
+ if (asoc->prsctp_supported == 0) {
+ return (NULL);
+ }
+ TAILQ_FOREACH_SAFE(tp1, &asoc->sent_queue, sctp_next, tp2) {
+ if (tp1->sent != SCTP_FORWARD_TSN_SKIP &&
+ tp1->sent != SCTP_DATAGRAM_RESEND &&
+ tp1->sent != SCTP_DATAGRAM_NR_ACKED) {
+ /* no chance to advance, out of here */
+ break;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_TRY_ADVANCE) {
+ if ((tp1->sent == SCTP_FORWARD_TSN_SKIP) ||
+ (tp1->sent == SCTP_DATAGRAM_NR_ACKED)) {
+ sctp_misc_ints(SCTP_FWD_TSN_CHECK,
+ asoc->advanced_peer_ack_point,
+ tp1->rec.data.tsn, 0, 0);
+ }
+ }
+ if (!PR_SCTP_ENABLED(tp1->flags)) {
+ /*
+ * We can't fwd-tsn past any that are reliable aka
+ * retransmitted until the asoc fails.
+ */
+ break;
+ }
+ if (!now_filled) {
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ now_filled = 1;
+ }
+ /*
+ * now we got a chunk which is marked for another
+ * retransmission to a PR-stream but has run out its chances
+ * already maybe OR has been marked to skip now. Can we skip
+ * it if its a resend?
+ */
+ if (tp1->sent == SCTP_DATAGRAM_RESEND &&
+ (PR_SCTP_TTL_ENABLED(tp1->flags))) {
+ /*
+ * Now is this one marked for resend and its time is
+ * now up?
+ */
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (timercmp(&now, &tp1->rec.data.timetodrop, >)) {
+#else
+ if (timevalcmp(&now, &tp1->rec.data.timetodrop, >)) {
+#endif
+ /* Yes so drop it */
+ if (tp1->data) {
+ (void)sctp_release_pr_sctp_chunk(stcb, tp1,
+ 1, SCTP_SO_NOT_LOCKED);
+ }
+ } else {
+ /*
+ * No, we are done when hit one for resend
+ * whos time as not expired.
+ */
+ break;
+ }
+ }
+ /*
+ * Ok now if this chunk is marked to drop it we can clean up
+ * the chunk, advance our peer ack point and we can check
+ * the next chunk.
+ */
+ if ((tp1->sent == SCTP_FORWARD_TSN_SKIP) ||
+ (tp1->sent == SCTP_DATAGRAM_NR_ACKED)) {
+ /* advance PeerAckPoint goes forward */
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, asoc->advanced_peer_ack_point)) {
+ asoc->advanced_peer_ack_point = tp1->rec.data.tsn;
+ a_adv = tp1;
+ } else if (tp1->rec.data.tsn == asoc->advanced_peer_ack_point) {
+ /* No update but we do save the chk */
+ a_adv = tp1;
+ }
+ } else {
+ /*
+ * If it is still in RESEND we can advance no
+ * further
+ */
+ break;
+ }
+ }
+ return (a_adv);
+}
+
+static int
+sctp_fs_audit(struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk;
+ int inflight = 0, resend = 0, inbetween = 0, acked = 0, above = 0;
+ int ret;
+#ifndef INVARIANTS
+ int entry_flight, entry_cnt;
+#endif
+
+ ret = 0;
+#ifndef INVARIANTS
+ entry_flight = asoc->total_flight;
+ entry_cnt = asoc->total_flight_count;
+#endif
+ if (asoc->pr_sctp_cnt >= asoc->sent_queue_cnt)
+ return (0);
+
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ SCTP_PRINTF("Chk TSN: %u size: %d inflight cnt: %d\n",
+ chk->rec.data.tsn,
+ chk->send_size,
+ chk->snd_count);
+ inflight++;
+ } else if (chk->sent == SCTP_DATAGRAM_RESEND) {
+ resend++;
+ } else if (chk->sent < SCTP_DATAGRAM_ACKED) {
+ inbetween++;
+ } else if (chk->sent > SCTP_DATAGRAM_ACKED) {
+ above++;
+ } else {
+ acked++;
+ }
+ }
+
+ if ((inflight > 0) || (inbetween > 0)) {
+#ifdef INVARIANTS
+ panic("Flight size-express incorrect F: %d I: %d R: %d Ab: %d ACK: %d",
+ inflight, inbetween, resend, above, acked);
+#else
+ SCTP_PRINTF("asoc->total_flight: %d cnt: %d\n",
+ entry_flight, entry_cnt);
+ SCTP_PRINTF("Flight size-express incorrect F: %d I: %d R: %d Ab: %d ACK: %d\n",
+ inflight, inbetween, resend, above, acked);
+ ret = 1;
+#endif
+ }
+ return (ret);
+}
+
+static void
+sctp_window_probe_recovery(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_tmit_chunk *tp1)
+{
+ tp1->window_probe = 0;
+ if ((tp1->sent >= SCTP_DATAGRAM_ACKED) || (tp1->data == NULL)) {
+ /* TSN's skipped we do NOT move back. */
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DWN_WP_FWD,
+ tp1->whoTo ? tp1->whoTo->flight_size : 0,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ return;
+ }
+ /* First setup this by shrinking flight */
+ if (stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged)(tp1->whoTo,
+ tp1);
+ }
+ sctp_flight_size_decrease(tp1);
+ sctp_total_flight_decrease(stcb, tp1);
+ /* Now mark for resend */
+ tp1->sent = SCTP_DATAGRAM_RESEND;
+ sctp_ucount_incr(asoc->sent_queue_retran_cnt);
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_WP,
+ tp1->whoTo->flight_size,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ }
+}
+
+void
+sctp_express_handle_sack(struct sctp_tcb *stcb, uint32_t cumack,
+ uint32_t rwnd, int *abort_now, int ecne_seen)
+{
+ struct sctp_nets *net;
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *tp1, *tp2;
+ uint32_t old_rwnd;
+ int win_probe_recovery = 0;
+ int win_probe_recovered = 0;
+ int j, done_once = 0;
+ int rto_ok = 1;
+ uint32_t send_s;
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_SACK_ARRIVALS_ENABLE) {
+ sctp_misc_ints(SCTP_SACK_LOG_EXPRESS, cumack,
+ rwnd, stcb->asoc.last_acked_seq, stcb->asoc.peers_rwnd);
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ stcb->asoc.cumack_log[stcb->asoc.cumack_log_at] = cumack;
+ stcb->asoc.cumack_log_at++;
+ if (stcb->asoc.cumack_log_at > SCTP_TSN_LOG_SIZE) {
+ stcb->asoc.cumack_log_at = 0;
+ }
+#endif
+ asoc = &stcb->asoc;
+ old_rwnd = asoc->peers_rwnd;
+ if (SCTP_TSN_GT(asoc->last_acked_seq, cumack)) {
+ /* old ack */
+ return;
+ } else if (asoc->last_acked_seq == cumack) {
+ /* Window update sack */
+ asoc->peers_rwnd = sctp_sbspace_sub(rwnd,
+ (uint32_t) (asoc->total_flight + (asoc->total_flight_count * SCTP_BASE_SYSCTL(sctp_peer_chunk_oh))));
+ if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ asoc->peers_rwnd = 0;
+ }
+ if (asoc->peers_rwnd > old_rwnd) {
+ goto again;
+ }
+ return;
+ }
+
+ /* First setup for CC stuff */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (SCTP_TSN_GT(cumack, net->cwr_window_tsn)) {
+ /* Drag along the window_tsn for cwr's */
+ net->cwr_window_tsn = cumack;
+ }
+ net->prev_cwnd = net->cwnd;
+ net->net_ack = 0;
+ net->net_ack2 = 0;
+
+ /*
+ * CMT: Reset CUC and Fast recovery algo variables before
+ * SACK processing
+ */
+ net->new_pseudo_cumack = 0;
+ net->will_exit_fast_recovery = 0;
+ if (stcb->asoc.cc_functions.sctp_cwnd_prepare_net_for_sack) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_prepare_net_for_sack)(stcb, net);
+ }
+ }
+ if (!TAILQ_EMPTY(&asoc->sent_queue)) {
+ tp1 = TAILQ_LAST(&asoc->sent_queue,
+ sctpchunk_listhead);
+ send_s = tp1->rec.data.tsn + 1;
+ } else {
+ send_s = asoc->sending_seq;
+ }
+ if (SCTP_TSN_GE(cumack, send_s)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ *abort_now = 1;
+ /* XXX */
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "Cum ack %8.8x greater or equal than TSN %8.8x",
+ cumack, send_s);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_25;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ asoc->this_sack_highest_gap = cumack;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INDATA,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ if (SCTP_TSN_GT(cumack, asoc->last_acked_seq)) {
+ /* process the new consecutive TSN first */
+ TAILQ_FOREACH_SAFE(tp1, &asoc->sent_queue, sctp_next, tp2) {
+ if (SCTP_TSN_GE(cumack, tp1->rec.data.tsn)) {
+ if (tp1->sent == SCTP_DATAGRAM_UNSENT) {
+ SCTP_PRINTF("Warning, an unsent is now acked?\n");
+ }
+ if (tp1->sent < SCTP_DATAGRAM_ACKED) {
+ /*
+ * If it is less than ACKED, it is
+ * now no-longer in flight. Higher
+ * values may occur during marking
+ */
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_CA,
+ tp1->whoTo->flight_size,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ }
+ sctp_flight_size_decrease(tp1);
+ if (stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged)(tp1->whoTo,
+ tp1);
+ }
+ /* sa_ignore NO_NULL_CHK */
+ sctp_total_flight_decrease(stcb, tp1);
+ }
+ tp1->whoTo->net_ack += tp1->send_size;
+ if (tp1->snd_count < 2) {
+ /*
+ * True non-retransmitted
+ * chunk
+ */
+ tp1->whoTo->net_ack2 +=
+ tp1->send_size;
+
+ /* update RTO too? */
+ if (tp1->do_rtt) {
+ if (rto_ok &&
+ sctp_calculate_rto(stcb,
+ &stcb->asoc,
+ tp1->whoTo,
+ &tp1->sent_rcv_time,
+ SCTP_RTT_FROM_DATA)) {
+ rto_ok = 0;
+ }
+ if (tp1->whoTo->rto_needed == 0) {
+ tp1->whoTo->rto_needed = 1;
+ }
+ tp1->do_rtt = 0;
+ }
+ }
+ /*
+ * CMT: CUCv2 algorithm. From the
+ * cumack'd TSNs, for each TSN being
+ * acked for the first time, set the
+ * following variables for the
+ * corresp destination.
+ * new_pseudo_cumack will trigger a
+ * cwnd update.
+ * find_(rtx_)pseudo_cumack will
+ * trigger search for the next
+ * expected (rtx-)pseudo-cumack.
+ */
+ tp1->whoTo->new_pseudo_cumack = 1;
+ tp1->whoTo->find_pseudo_cumack = 1;
+ tp1->whoTo->find_rtx_pseudo_cumack = 1;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ /* sa_ignore NO_NULL_CHK */
+ sctp_log_cwnd(stcb, tp1->whoTo, tp1->rec.data.tsn, SCTP_CWND_LOG_FROM_SACK);
+ }
+ }
+ if (tp1->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_decr(asoc->sent_queue_retran_cnt);
+ }
+ if (tp1->rec.data.chunk_was_revoked) {
+ /* deflate the cwnd */
+ tp1->whoTo->cwnd -= tp1->book_size;
+ tp1->rec.data.chunk_was_revoked = 0;
+ }
+ if (tp1->sent != SCTP_DATAGRAM_NR_ACKED) {
+ if (asoc->strmout[tp1->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[tp1->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", tp1->rec.data.sid);
+#endif
+ }
+ }
+ if ((asoc->strmout[tp1->rec.data.sid].chunks_on_queues == 0) &&
+ (asoc->strmout[tp1->rec.data.sid].state == SCTP_STREAM_RESET_PENDING) &&
+ TAILQ_EMPTY(&asoc->strmout[tp1->rec.data.sid].outqueue)) {
+ asoc->trigger_reset = 1;
+ }
+ TAILQ_REMOVE(&asoc->sent_queue, tp1, sctp_next);
+ if (tp1->data) {
+ /* sa_ignore NO_NULL_CHK */
+ sctp_free_bufspace(stcb, asoc, tp1, 1);
+ sctp_m_freem(tp1->data);
+ tp1->data = NULL;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_LOGGING_ENABLE) {
+ sctp_log_sack(asoc->last_acked_seq,
+ cumack,
+ tp1->rec.data.tsn,
+ 0,
+ 0,
+ SCTP_LOG_FREE_SENT);
+ }
+ asoc->sent_queue_cnt--;
+ sctp_free_a_chunk(stcb, tp1, SCTP_SO_NOT_LOCKED);
+ } else {
+ break;
+ }
+ }
+ }
+#if defined(__Userspace__)
+ if (stcb->sctp_ep->recv_callback) {
+ if (stcb->sctp_socket) {
+ uint32_t inqueue_bytes, sb_free_now;
+ struct sctp_inpcb *inp;
+
+ inp = stcb->sctp_ep;
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * sizeof(struct sctp_data_chunk));
+ sb_free_now = SCTP_SB_LIMIT_SND(stcb->sctp_socket) - (inqueue_bytes + stcb->asoc.sb_send_resv);
+
+ /* check if the amount free in the send socket buffer crossed the threshold */
+ if (inp->send_callback &&
+ (((inp->send_sb_threshold > 0) &&
+ (sb_free_now >= inp->send_sb_threshold) &&
+ (stcb->asoc.chunks_on_out_queue <= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) ||
+ (inp->send_sb_threshold == 0))) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ inp->send_callback(stcb->sctp_socket, sb_free_now, inp->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ }
+ } else if (stcb->sctp_socket) {
+#else
+ /* sa_ignore NO_NULL_CHK */
+ if (stcb->sctp_socket) {
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+#endif
+ SOCKBUF_LOCK(&stcb->sctp_socket->so_snd);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_WAKE_LOGGING_ENABLE) {
+ /* sa_ignore NO_NULL_CHK */
+ sctp_wakeup_log(stcb, 1, SCTP_WAKESND_FROM_SACK);
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ /* assoc was freed while we were unlocked */
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+#endif
+ sctp_sowwakeup_locked(stcb->sctp_ep, stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_WAKE_LOGGING_ENABLE) {
+ sctp_wakeup_log(stcb, 1, SCTP_NOWAKE_FROM_SACK);
+ }
+ }
+
+ /* JRS - Use the congestion control given in the CC module */
+ if ((asoc->last_acked_seq != cumack) && (ecne_seen == 0)) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (net->net_ack2 > 0) {
+ /*
+ * Karn's rule applies to clearing error count, this
+ * is optional.
+ */
+ net->error_count = 0;
+ if ((net->dest_state & SCTP_ADDR_REACHABLE) == 0) {
+ /* addr came good */
+ net->dest_state |= SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb,
+ 0, (void *)net, SCTP_SO_NOT_LOCKED);
+ }
+ if (net == stcb->asoc.primary_destination) {
+ if (stcb->asoc.alternate) {
+ /* release the alternate, primary is good */
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ }
+ if (net->dest_state & SCTP_ADDR_PF) {
+ net->dest_state &= ~SCTP_ADDR_PF;
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_26);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net);
+ asoc->cc_functions.sctp_cwnd_update_exit_pf(stcb, net);
+ /* Done with this net */
+ net->net_ack = 0;
+ }
+ /* restore any doubled timers */
+ net->RTO = (net->lastsa >> SCTP_RTT_SHIFT) + net->lastsv;
+ if (net->RTO < stcb->asoc.minrto) {
+ net->RTO = stcb->asoc.minrto;
+ }
+ if (net->RTO > stcb->asoc.maxrto) {
+ net->RTO = stcb->asoc.maxrto;
+ }
+ }
+ }
+ asoc->cc_functions.sctp_cwnd_update_after_sack(stcb, asoc, 1, 0, 0);
+ }
+ asoc->last_acked_seq = cumack;
+
+ if (TAILQ_EMPTY(&asoc->sent_queue)) {
+ /* nothing left in-flight */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ net->flight_size = 0;
+ net->partial_bytes_acked = 0;
+ }
+ asoc->total_flight = 0;
+ asoc->total_flight_count = 0;
+ }
+
+ /* RWND update */
+ asoc->peers_rwnd = sctp_sbspace_sub(rwnd,
+ (uint32_t) (asoc->total_flight + (asoc->total_flight_count * SCTP_BASE_SYSCTL(sctp_peer_chunk_oh))));
+ if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ asoc->peers_rwnd = 0;
+ }
+ if (asoc->peers_rwnd > old_rwnd) {
+ win_probe_recovery = 1;
+ }
+ /* Now assure a timer where data is queued at */
+again:
+ j = 0;
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (win_probe_recovery && (net->window_probe)) {
+ win_probe_recovered = 1;
+ /*
+ * Find first chunk that was used with window probe
+ * and clear the sent
+ */
+ /* sa_ignore FREED_MEMORY */
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (tp1->window_probe) {
+ /* move back to data send queue */
+ sctp_window_probe_recovery(stcb, asoc, tp1);
+ break;
+ }
+ }
+ }
+ if (net->flight_size) {
+ j++;
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net);
+ if (net->window_probe) {
+ net->window_probe = 0;
+ }
+ } else {
+ if (net->window_probe) {
+ /* In window probes we must assure a timer is still running there */
+ net->window_probe = 0;
+ if (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net);
+ }
+ } else if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, net,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_27);
+ }
+ }
+ }
+ if ((j == 0) &&
+ (!TAILQ_EMPTY(&asoc->sent_queue)) &&
+ (asoc->sent_queue_retran_cnt == 0) &&
+ (win_probe_recovered == 0) &&
+ (done_once == 0)) {
+ /* huh, this should not happen unless all packets
+ * are PR-SCTP and marked to skip of course.
+ */
+ if (sctp_fs_audit(asoc)) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ net->flight_size = 0;
+ }
+ asoc->total_flight = 0;
+ asoc->total_flight_count = 0;
+ asoc->sent_queue_retran_cnt = 0;
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ sctp_flight_size_increase(tp1);
+ sctp_total_flight_increase(stcb, tp1);
+ } else if (tp1->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_incr(asoc->sent_queue_retran_cnt);
+ }
+ }
+ }
+ done_once = 1;
+ goto again;
+ }
+ /**********************************/
+ /* Now what about shutdown issues */
+ /**********************************/
+ if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue)) {
+ /* nothing left on sendqueue.. consider done */
+ /* clean up */
+ if ((asoc->stream_queue_cnt == 1) &&
+ ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) &&
+ ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc))) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ if (((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) &&
+ (asoc->stream_queue_cnt == 1) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+
+ *abort_now = 1;
+ /* XXX */
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_28;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) &&
+ (asoc->stream_queue_cnt == 0)) {
+ struct sctp_nets *netp;
+
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (asoc->alternate) {
+ netp = asoc->alternate;
+ } else {
+ netp = asoc->primary_destination;
+ }
+ sctp_send_shutdown(stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN,
+ stcb->sctp_ep, stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ } else if ((SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (asoc->stream_queue_cnt == 0)) {
+ struct sctp_nets *netp;
+
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_ACK_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (asoc->alternate) {
+ netp = asoc->alternate;
+ } else {
+ netp = asoc->primary_destination;
+ }
+ sctp_send_shutdown_ack(stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK,
+ stcb->sctp_ep, stcb, netp);
+ }
+ }
+ /*********************************************/
+ /* Here we perform PR-SCTP procedures */
+ /* (section 4.2) */
+ /*********************************************/
+ /* C1. update advancedPeerAckPoint */
+ if (SCTP_TSN_GT(cumack, asoc->advanced_peer_ack_point)) {
+ asoc->advanced_peer_ack_point = cumack;
+ }
+ /* PR-Sctp issues need to be addressed too */
+ if ((asoc->prsctp_supported) && (asoc->pr_sctp_cnt > 0)) {
+ struct sctp_tmit_chunk *lchk;
+ uint32_t old_adv_peer_ack_point;
+
+ old_adv_peer_ack_point = asoc->advanced_peer_ack_point;
+ lchk = sctp_try_advance_peer_ack_point(stcb, asoc);
+ /* C3. See if we need to send a Fwd-TSN */
+ if (SCTP_TSN_GT(asoc->advanced_peer_ack_point, cumack)) {
+ /*
+ * ISSUE with ECN, see FWD-TSN processing.
+ */
+ if (SCTP_TSN_GT(asoc->advanced_peer_ack_point, old_adv_peer_ack_point)) {
+ send_forward_tsn(stcb, asoc);
+ } else if (lchk) {
+ /* try to FR fwd-tsn's that get lost too */
+ if (lchk->rec.data.fwd_tsn_cnt >= 3) {
+ send_forward_tsn(stcb, asoc);
+ }
+ }
+ }
+ for (; lchk != NULL; lchk = TAILQ_NEXT(lchk, sctp_next)) {
+ if (lchk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (lchk != NULL) {
+ /* Assure a timer is up */
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, lchk->whoTo);
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_RWND_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_SACK_RWND_UPDATE,
+ rwnd,
+ stcb->asoc.peers_rwnd,
+ stcb->asoc.total_flight,
+ stcb->asoc.total_output_queue_size);
+ }
+}
+
+void
+sctp_handle_sack(struct mbuf *m, int offset_seg, int offset_dup,
+ struct sctp_tcb *stcb,
+ uint16_t num_seg, uint16_t num_nr_seg, uint16_t num_dup,
+ int *abort_now, uint8_t flags,
+ uint32_t cum_ack, uint32_t rwnd, int ecne_seen)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *tp1, *tp2;
+ uint32_t last_tsn, biggest_tsn_acked, biggest_tsn_newly_acked, this_sack_lowest_newack;
+ uint16_t wake_him = 0;
+ uint32_t send_s = 0;
+ long j;
+ int accum_moved = 0;
+ int will_exit_fast_recovery = 0;
+ uint32_t a_rwnd, old_rwnd;
+ int win_probe_recovery = 0;
+ int win_probe_recovered = 0;
+ struct sctp_nets *net = NULL;
+ int done_once;
+ int rto_ok = 1;
+ uint8_t reneged_all = 0;
+ uint8_t cmt_dac_flag;
+ /*
+ * we take any chance we can to service our queues since we cannot
+ * get awoken when the socket is read from :<
+ */
+ /*
+ * Now perform the actual SACK handling: 1) Verify that it is not an
+ * old sack, if so discard. 2) If there is nothing left in the send
+ * queue (cum-ack is equal to last acked) then you have a duplicate
+ * too, update any rwnd change and verify no timers are running.
+ * then return. 3) Process any new consecutive data i.e. cum-ack
+ * moved process these first and note that it moved. 4) Process any
+ * sack blocks. 5) Drop any acked from the queue. 6) Check for any
+ * revoked blocks and mark. 7) Update the cwnd. 8) Nothing left,
+ * sync up flightsizes and things, stop all timers and also check
+ * for shutdown_pending state. If so then go ahead and send off the
+ * shutdown. If in shutdown recv, send off the shutdown-ack and
+ * start that timer, Ret. 9) Strike any non-acked things and do FR
+ * procedure if needed being sure to set the FR flag. 10) Do pr-sctp
+ * procedures. 11) Apply any FR penalties. 12) Assure we will SACK
+ * if in shutdown_recv state.
+ */
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ /* CMT DAC algo */
+ this_sack_lowest_newack = 0;
+ SCTP_STAT_INCR(sctps_slowpath_sack);
+ last_tsn = cum_ack;
+ cmt_dac_flag = flags & SCTP_SACK_CMT_DAC;
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ stcb->asoc.cumack_log[stcb->asoc.cumack_log_at] = cum_ack;
+ stcb->asoc.cumack_log_at++;
+ if (stcb->asoc.cumack_log_at > SCTP_TSN_LOG_SIZE) {
+ stcb->asoc.cumack_log_at = 0;
+ }
+#endif
+ a_rwnd = rwnd;
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_SACK_ARRIVALS_ENABLE) {
+ sctp_misc_ints(SCTP_SACK_LOG_NORMAL, cum_ack,
+ rwnd, stcb->asoc.last_acked_seq, stcb->asoc.peers_rwnd);
+ }
+
+ old_rwnd = stcb->asoc.peers_rwnd;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INDATA,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ asoc = &stcb->asoc;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_LOGGING_ENABLE) {
+ sctp_log_sack(asoc->last_acked_seq,
+ cum_ack,
+ 0,
+ num_seg,
+ num_dup,
+ SCTP_LOG_NEW_SACK);
+ }
+ if ((num_dup) && (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE)) {
+ uint16_t i;
+ uint32_t *dupdata, dblock;
+
+ for (i = 0; i < num_dup; i++) {
+ dupdata = (uint32_t *)sctp_m_getptr(m, offset_dup + i * sizeof(uint32_t),
+ sizeof(uint32_t), (uint8_t *)&dblock);
+ if (dupdata == NULL) {
+ break;
+ }
+ sctp_log_fr(*dupdata, 0, 0, SCTP_FR_DUPED);
+ }
+ }
+ /* reality check */
+ if (!TAILQ_EMPTY(&asoc->sent_queue)) {
+ tp1 = TAILQ_LAST(&asoc->sent_queue,
+ sctpchunk_listhead);
+ send_s = tp1->rec.data.tsn + 1;
+ } else {
+ tp1 = NULL;
+ send_s = asoc->sending_seq;
+ }
+ if (SCTP_TSN_GE(cum_ack, send_s)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ /*
+ * no way, we have not even sent this TSN out yet.
+ * Peer is hopelessly messed up with us.
+ */
+ SCTP_PRINTF("NEW cum_ack:%x send_s:%x is smaller or equal\n",
+ cum_ack, send_s);
+ if (tp1) {
+ SCTP_PRINTF("Got send_s from tsn:%x + 1 of tp1: %p\n",
+ tp1->rec.data.tsn, (void *)tp1);
+ }
+ hopeless_peer:
+ *abort_now = 1;
+ /* XXX */
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "Cum ack %8.8x greater or equal than TSN %8.8x",
+ cum_ack, send_s);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_29;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ /**********************/
+ /* 1) check the range */
+ /**********************/
+ if (SCTP_TSN_GT(asoc->last_acked_seq, last_tsn)) {
+ /* acking something behind */
+ return;
+ }
+
+ /* update the Rwnd of the peer */
+ if (TAILQ_EMPTY(&asoc->sent_queue) &&
+ TAILQ_EMPTY(&asoc->send_queue) &&
+ (asoc->stream_queue_cnt == 0)) {
+ /* nothing left on send/sent and strmq */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_RWND_ENABLE) {
+ sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK,
+ asoc->peers_rwnd, 0, 0, a_rwnd);
+ }
+ asoc->peers_rwnd = a_rwnd;
+ if (asoc->sent_queue_retran_cnt) {
+ asoc->sent_queue_retran_cnt = 0;
+ }
+ if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ asoc->peers_rwnd = 0;
+ }
+ /* stop any timers */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_30);
+ net->partial_bytes_acked = 0;
+ net->flight_size = 0;
+ }
+ asoc->total_flight = 0;
+ asoc->total_flight_count = 0;
+ return;
+ }
+ /*
+ * We init netAckSz and netAckSz2 to 0. These are used to track 2
+ * things. The total byte count acked is tracked in netAckSz AND
+ * netAck2 is used to track the total bytes acked that are un-
+ * ambiguous and were never retransmitted. We track these on a per
+ * destination address basis.
+ */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (SCTP_TSN_GT(cum_ack, net->cwr_window_tsn)) {
+ /* Drag along the window_tsn for cwr's */
+ net->cwr_window_tsn = cum_ack;
+ }
+ net->prev_cwnd = net->cwnd;
+ net->net_ack = 0;
+ net->net_ack2 = 0;
+
+ /*
+ * CMT: Reset CUC and Fast recovery algo variables before
+ * SACK processing
+ */
+ net->new_pseudo_cumack = 0;
+ net->will_exit_fast_recovery = 0;
+ if (stcb->asoc.cc_functions.sctp_cwnd_prepare_net_for_sack) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_prepare_net_for_sack)(stcb, net);
+ }
+
+ /*
+ * CMT: SFR algo (and HTNA) - this_sack_highest_newack has
+ * to be greater than the cumack. Also reset saw_newack to 0
+ * for all dests.
+ */
+ net->saw_newack = 0;
+ net->this_sack_highest_newack = last_tsn;
+ }
+ /* process the new consecutive TSN first */
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (SCTP_TSN_GE(last_tsn, tp1->rec.data.tsn)) {
+ if (tp1->sent != SCTP_DATAGRAM_UNSENT) {
+ accum_moved = 1;
+ if (tp1->sent < SCTP_DATAGRAM_ACKED) {
+ /*
+ * If it is less than ACKED, it is
+ * now no-longer in flight. Higher
+ * values may occur during marking
+ */
+ if ((tp1->whoTo->dest_state &
+ SCTP_ADDR_UNCONFIRMED) &&
+ (tp1->snd_count < 2)) {
+ /*
+ * If there was no retran
+ * and the address is
+ * un-confirmed and we sent
+ * there and are now
+ * sacked.. its confirmed,
+ * mark it so.
+ */
+ tp1->whoTo->dest_state &=
+ ~SCTP_ADDR_UNCONFIRMED;
+ }
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_CA,
+ tp1->whoTo->flight_size,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ }
+ sctp_flight_size_decrease(tp1);
+ sctp_total_flight_decrease(stcb, tp1);
+ if (stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_update_tsn_acknowledged)(tp1->whoTo,
+ tp1);
+ }
+ }
+ tp1->whoTo->net_ack += tp1->send_size;
+
+ /* CMT SFR and DAC algos */
+ this_sack_lowest_newack = tp1->rec.data.tsn;
+ tp1->whoTo->saw_newack = 1;
+
+ if (tp1->snd_count < 2) {
+ /*
+ * True non-retransmitted
+ * chunk
+ */
+ tp1->whoTo->net_ack2 +=
+ tp1->send_size;
+
+ /* update RTO too? */
+ if (tp1->do_rtt) {
+ if (rto_ok &&
+ sctp_calculate_rto(stcb,
+ &stcb->asoc,
+ tp1->whoTo,
+ &tp1->sent_rcv_time,
+ SCTP_RTT_FROM_DATA)) {
+ rto_ok = 0;
+ }
+ if (tp1->whoTo->rto_needed == 0) {
+ tp1->whoTo->rto_needed = 1;
+ }
+ tp1->do_rtt = 0;
+ }
+ }
+ /*
+ * CMT: CUCv2 algorithm. From the
+ * cumack'd TSNs, for each TSN being
+ * acked for the first time, set the
+ * following variables for the
+ * corresp destination.
+ * new_pseudo_cumack will trigger a
+ * cwnd update.
+ * find_(rtx_)pseudo_cumack will
+ * trigger search for the next
+ * expected (rtx-)pseudo-cumack.
+ */
+ tp1->whoTo->new_pseudo_cumack = 1;
+ tp1->whoTo->find_pseudo_cumack = 1;
+ tp1->whoTo->find_rtx_pseudo_cumack = 1;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_LOGGING_ENABLE) {
+ sctp_log_sack(asoc->last_acked_seq,
+ cum_ack,
+ tp1->rec.data.tsn,
+ 0,
+ 0,
+ SCTP_LOG_TSN_ACKED);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, tp1->whoTo, tp1->rec.data.tsn, SCTP_CWND_LOG_FROM_SACK);
+ }
+ }
+ if (tp1->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_decr(asoc->sent_queue_retran_cnt);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xB3,
+ (asoc->sent_queue_retran_cnt & 0x000000ff));
+#endif
+ }
+ if (tp1->rec.data.chunk_was_revoked) {
+ /* deflate the cwnd */
+ tp1->whoTo->cwnd -= tp1->book_size;
+ tp1->rec.data.chunk_was_revoked = 0;
+ }
+ if (tp1->sent != SCTP_DATAGRAM_NR_ACKED) {
+ tp1->sent = SCTP_DATAGRAM_ACKED;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ biggest_tsn_newly_acked = biggest_tsn_acked = last_tsn;
+ /* always set this up to cum-ack */
+ asoc->this_sack_highest_gap = last_tsn;
+
+ if ((num_seg > 0) || (num_nr_seg > 0)) {
+ /*
+ * thisSackHighestGap will increase while handling NEW
+ * segments this_sack_highest_newack will increase while
+ * handling NEWLY ACKED chunks. this_sack_lowest_newack is
+ * used for CMT DAC algo. saw_newack will also change.
+ */
+ if (sctp_handle_segments(m, &offset_seg, stcb, asoc, last_tsn, &biggest_tsn_acked,
+ &biggest_tsn_newly_acked, &this_sack_lowest_newack,
+ num_seg, num_nr_seg, &rto_ok)) {
+ wake_him++;
+ }
+ /*
+ * validate the biggest_tsn_acked in the gap acks if
+ * strict adherence is wanted.
+ */
+ if (SCTP_TSN_GE(biggest_tsn_acked, send_s)) {
+ /*
+ * peer is either confused or we are under
+ * attack. We must abort.
+ */
+ SCTP_PRINTF("Hopeless peer! biggest_tsn_acked:%x largest seq:%x\n",
+ biggest_tsn_acked, send_s);
+ goto hopeless_peer;
+ }
+ }
+ /*******************************************/
+ /* cancel ALL T3-send timer if accum moved */
+ /*******************************************/
+ if (asoc->sctp_cmt_on_off > 0) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (net->new_pseudo_cumack)
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, net,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_31);
+ }
+ } else {
+ if (accum_moved) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_32);
+ }
+ }
+ }
+ /********************************************/
+ /* drop the acked chunks from the sentqueue */
+ /********************************************/
+ asoc->last_acked_seq = cum_ack;
+
+ TAILQ_FOREACH_SAFE(tp1, &asoc->sent_queue, sctp_next, tp2) {
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, cum_ack)) {
+ break;
+ }
+ if (tp1->sent != SCTP_DATAGRAM_NR_ACKED) {
+ if (asoc->strmout[tp1->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[tp1->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", tp1->rec.data.sid);
+#endif
+ }
+ }
+ if ((asoc->strmout[tp1->rec.data.sid].chunks_on_queues == 0) &&
+ (asoc->strmout[tp1->rec.data.sid].state == SCTP_STREAM_RESET_PENDING) &&
+ TAILQ_EMPTY(&asoc->strmout[tp1->rec.data.sid].outqueue)) {
+ asoc->trigger_reset = 1;
+ }
+ TAILQ_REMOVE(&asoc->sent_queue, tp1, sctp_next);
+ if (PR_SCTP_ENABLED(tp1->flags)) {
+ if (asoc->pr_sctp_cnt != 0)
+ asoc->pr_sctp_cnt--;
+ }
+ asoc->sent_queue_cnt--;
+ if (tp1->data) {
+ /* sa_ignore NO_NULL_CHK */
+ sctp_free_bufspace(stcb, asoc, tp1, 1);
+ sctp_m_freem(tp1->data);
+ tp1->data = NULL;
+ if (asoc->prsctp_supported && PR_SCTP_BUF_ENABLED(tp1->flags)) {
+ asoc->sent_queue_cnt_removeable--;
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_LOGGING_ENABLE) {
+ sctp_log_sack(asoc->last_acked_seq,
+ cum_ack,
+ tp1->rec.data.tsn,
+ 0,
+ 0,
+ SCTP_LOG_FREE_SENT);
+ }
+ sctp_free_a_chunk(stcb, tp1, SCTP_SO_NOT_LOCKED);
+ wake_him++;
+ }
+ if (TAILQ_EMPTY(&asoc->sent_queue) && (asoc->total_flight > 0)) {
+#ifdef INVARIANTS
+ panic("Warning flight size is positive and should be 0");
+#else
+ SCTP_PRINTF("Warning flight size incorrect should be 0 is %d\n",
+ asoc->total_flight);
+#endif
+ asoc->total_flight = 0;
+ }
+
+#if defined(__Userspace__)
+ if (stcb->sctp_ep->recv_callback) {
+ if (stcb->sctp_socket) {
+ uint32_t inqueue_bytes, sb_free_now;
+ struct sctp_inpcb *inp;
+
+ inp = stcb->sctp_ep;
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * sizeof(struct sctp_data_chunk));
+ sb_free_now = SCTP_SB_LIMIT_SND(stcb->sctp_socket) - (inqueue_bytes + stcb->asoc.sb_send_resv);
+
+ /* check if the amount free in the send socket buffer crossed the threshold */
+ if (inp->send_callback &&
+ (((inp->send_sb_threshold > 0) && (sb_free_now >= inp->send_sb_threshold)) ||
+ (inp->send_sb_threshold == 0))) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ inp->send_callback(stcb->sctp_socket, sb_free_now, inp->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ }
+ } else if ((wake_him) && (stcb->sctp_socket)) {
+#else
+ /* sa_ignore NO_NULL_CHK */
+ if ((wake_him) && (stcb->sctp_socket)) {
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+#endif
+ SOCKBUF_LOCK(&stcb->sctp_socket->so_snd);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_WAKE_LOGGING_ENABLE) {
+ sctp_wakeup_log(stcb, wake_him, SCTP_WAKESND_FROM_SACK);
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ /* assoc was freed while we were unlocked */
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+#endif
+ sctp_sowwakeup_locked(stcb->sctp_ep, stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_WAKE_LOGGING_ENABLE) {
+ sctp_wakeup_log(stcb, wake_him, SCTP_NOWAKE_FROM_SACK);
+ }
+ }
+
+ if (asoc->fast_retran_loss_recovery && accum_moved) {
+ if (SCTP_TSN_GE(asoc->last_acked_seq, asoc->fast_recovery_tsn)) {
+ /* Setup so we will exit RFC2582 fast recovery */
+ will_exit_fast_recovery = 1;
+ }
+ }
+ /*
+ * Check for revoked fragments:
+ *
+ * if Previous sack - Had no frags then we can't have any revoked if
+ * Previous sack - Had frag's then - If we now have frags aka
+ * num_seg > 0 call sctp_check_for_revoked() to tell if peer revoked
+ * some of them. else - The peer revoked all ACKED fragments, since
+ * we had some before and now we have NONE.
+ */
+
+ if (num_seg) {
+ sctp_check_for_revoked(stcb, asoc, cum_ack, biggest_tsn_acked);
+ asoc->saw_sack_with_frags = 1;
+ } else if (asoc->saw_sack_with_frags) {
+ int cnt_revoked = 0;
+
+ /* Peer revoked all dg's marked or acked */
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (tp1->sent == SCTP_DATAGRAM_ACKED) {
+ tp1->sent = SCTP_DATAGRAM_SENT;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_UP_REVOKE,
+ tp1->whoTo->flight_size,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)tp1->whoTo,
+ tp1->rec.data.tsn);
+ }
+ sctp_flight_size_increase(tp1);
+ sctp_total_flight_increase(stcb, tp1);
+ tp1->rec.data.chunk_was_revoked = 1;
+ /*
+ * To ensure that this increase in
+ * flightsize, which is artificial,
+ * does not throttle the sender, we
+ * also increase the cwnd
+ * artificially.
+ */
+ tp1->whoTo->cwnd += tp1->book_size;
+ cnt_revoked++;
+ }
+ }
+ if (cnt_revoked) {
+ reneged_all = 1;
+ }
+ asoc->saw_sack_with_frags = 0;
+ }
+ if (num_nr_seg > 0)
+ asoc->saw_sack_with_nr_frags = 1;
+ else
+ asoc->saw_sack_with_nr_frags = 0;
+
+ /* JRS - Use the congestion control given in the CC module */
+ if (ecne_seen == 0) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (net->net_ack2 > 0) {
+ /*
+ * Karn's rule applies to clearing error count, this
+ * is optional.
+ */
+ net->error_count = 0;
+ if ((net->dest_state & SCTP_ADDR_REACHABLE) == 0) {
+ /* addr came good */
+ net->dest_state |= SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb,
+ 0, (void *)net, SCTP_SO_NOT_LOCKED);
+ }
+
+ if (net == stcb->asoc.primary_destination) {
+ if (stcb->asoc.alternate) {
+ /* release the alternate, primary is good */
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ }
+
+ if (net->dest_state & SCTP_ADDR_PF) {
+ net->dest_state &= ~SCTP_ADDR_PF;
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_33);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net);
+ asoc->cc_functions.sctp_cwnd_update_exit_pf(stcb, net);
+ /* Done with this net */
+ net->net_ack = 0;
+ }
+ /* restore any doubled timers */
+ net->RTO = (net->lastsa >> SCTP_RTT_SHIFT) + net->lastsv;
+ if (net->RTO < stcb->asoc.minrto) {
+ net->RTO = stcb->asoc.minrto;
+ }
+ if (net->RTO > stcb->asoc.maxrto) {
+ net->RTO = stcb->asoc.maxrto;
+ }
+ }
+ }
+ asoc->cc_functions.sctp_cwnd_update_after_sack(stcb, asoc, accum_moved, reneged_all, will_exit_fast_recovery);
+ }
+
+ if (TAILQ_EMPTY(&asoc->sent_queue)) {
+ /* nothing left in-flight */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ /* stop all timers */
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, net,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_34);
+ net->flight_size = 0;
+ net->partial_bytes_acked = 0;
+ }
+ asoc->total_flight = 0;
+ asoc->total_flight_count = 0;
+ }
+
+ /**********************************/
+ /* Now what about shutdown issues */
+ /**********************************/
+ if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue)) {
+ /* nothing left on sendqueue.. consider done */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_RWND_ENABLE) {
+ sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK,
+ asoc->peers_rwnd, 0, 0, a_rwnd);
+ }
+ asoc->peers_rwnd = a_rwnd;
+ if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ asoc->peers_rwnd = 0;
+ }
+ /* clean up */
+ if ((asoc->stream_queue_cnt == 1) &&
+ ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) &&
+ ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc))) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ if (((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) &&
+ (asoc->stream_queue_cnt == 1) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+
+ *abort_now = 1;
+ /* XXX */
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_35;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) &&
+ (asoc->stream_queue_cnt == 0)) {
+ struct sctp_nets *netp;
+
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (asoc->alternate) {
+ netp = asoc->alternate;
+ } else {
+ netp = asoc->primary_destination;
+ }
+ sctp_send_shutdown(stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN,
+ stcb->sctp_ep, stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ return;
+ } else if ((SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (asoc->stream_queue_cnt == 0)) {
+ struct sctp_nets *netp;
+
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_ACK_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (asoc->alternate) {
+ netp = asoc->alternate;
+ } else {
+ netp = asoc->primary_destination;
+ }
+ sctp_send_shutdown_ack(stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK,
+ stcb->sctp_ep, stcb, netp);
+ return;
+ }
+ }
+ /*
+ * Now here we are going to recycle net_ack for a different use...
+ * HEADS UP.
+ */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ net->net_ack = 0;
+ }
+
+ /*
+ * CMT DAC algorithm: If SACK DAC flag was 0, then no extra marking
+ * to be done. Setting this_sack_lowest_newack to the cum_ack will
+ * automatically ensure that.
+ */
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ SCTP_BASE_SYSCTL(sctp_cmt_use_dac) &&
+ (cmt_dac_flag == 0)) {
+ this_sack_lowest_newack = cum_ack;
+ }
+ if ((num_seg > 0) || (num_nr_seg > 0)) {
+ sctp_strike_gap_ack_chunks(stcb, asoc, biggest_tsn_acked,
+ biggest_tsn_newly_acked, this_sack_lowest_newack, accum_moved);
+ }
+ /* JRS - Use the congestion control given in the CC module */
+ asoc->cc_functions.sctp_cwnd_update_after_fr(stcb, asoc);
+
+ /* Now are we exiting loss recovery ? */
+ if (will_exit_fast_recovery) {
+ /* Ok, we must exit fast recovery */
+ asoc->fast_retran_loss_recovery = 0;
+ }
+ if ((asoc->sat_t3_loss_recovery) &&
+ SCTP_TSN_GE(asoc->last_acked_seq, asoc->sat_t3_recovery_tsn)) {
+ /* end satellite t3 loss recovery */
+ asoc->sat_t3_loss_recovery = 0;
+ }
+ /*
+ * CMT Fast recovery
+ */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (net->will_exit_fast_recovery) {
+ /* Ok, we must exit fast recovery */
+ net->fast_retran_loss_recovery = 0;
+ }
+ }
+
+ /* Adjust and set the new rwnd value */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_RWND_ENABLE) {
+ sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK,
+ asoc->peers_rwnd, asoc->total_flight, (asoc->total_flight_count * SCTP_BASE_SYSCTL(sctp_peer_chunk_oh)), a_rwnd);
+ }
+ asoc->peers_rwnd = sctp_sbspace_sub(a_rwnd,
+ (uint32_t) (asoc->total_flight + (asoc->total_flight_count * SCTP_BASE_SYSCTL(sctp_peer_chunk_oh))));
+ if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ asoc->peers_rwnd = 0;
+ }
+ if (asoc->peers_rwnd > old_rwnd) {
+ win_probe_recovery = 1;
+ }
+
+ /*
+ * Now we must setup so we have a timer up for anyone with
+ * outstanding data.
+ */
+ done_once = 0;
+again:
+ j = 0;
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (win_probe_recovery && (net->window_probe)) {
+ win_probe_recovered = 1;
+ /*-
+ * Find first chunk that was used with
+ * window probe and clear the event. Put
+ * it back into the send queue as if has
+ * not been sent.
+ */
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (tp1->window_probe) {
+ sctp_window_probe_recovery(stcb, asoc, tp1);
+ break;
+ }
+ }
+ }
+ if (net->flight_size) {
+ j++;
+ if (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net);
+ }
+ if (net->window_probe) {
+ net->window_probe = 0;
+ }
+ } else {
+ if (net->window_probe) {
+ /* In window probes we must assure a timer is still running there */
+ if (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net);
+ }
+ } else if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, net,
+ SCTP_FROM_SCTP_INDATA + SCTP_LOC_36);
+ }
+ }
+ }
+ if ((j == 0) &&
+ (!TAILQ_EMPTY(&asoc->sent_queue)) &&
+ (asoc->sent_queue_retran_cnt == 0) &&
+ (win_probe_recovered == 0) &&
+ (done_once == 0)) {
+ /* huh, this should not happen unless all packets
+ * are PR-SCTP and marked to skip of course.
+ */
+ if (sctp_fs_audit(asoc)) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ net->flight_size = 0;
+ }
+ asoc->total_flight = 0;
+ asoc->total_flight_count = 0;
+ asoc->sent_queue_retran_cnt = 0;
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ sctp_flight_size_increase(tp1);
+ sctp_total_flight_increase(stcb, tp1);
+ } else if (tp1->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_incr(asoc->sent_queue_retran_cnt);
+ }
+ }
+ }
+ done_once = 1;
+ goto again;
+ }
+ /*********************************************/
+ /* Here we perform PR-SCTP procedures */
+ /* (section 4.2) */
+ /*********************************************/
+ /* C1. update advancedPeerAckPoint */
+ if (SCTP_TSN_GT(cum_ack, asoc->advanced_peer_ack_point)) {
+ asoc->advanced_peer_ack_point = cum_ack;
+ }
+ /* C2. try to further move advancedPeerAckPoint ahead */
+ if ((asoc->prsctp_supported) && (asoc->pr_sctp_cnt > 0)) {
+ struct sctp_tmit_chunk *lchk;
+ uint32_t old_adv_peer_ack_point;
+
+ old_adv_peer_ack_point = asoc->advanced_peer_ack_point;
+ lchk = sctp_try_advance_peer_ack_point(stcb, asoc);
+ /* C3. See if we need to send a Fwd-TSN */
+ if (SCTP_TSN_GT(asoc->advanced_peer_ack_point, cum_ack)) {
+ /*
+ * ISSUE with ECN, see FWD-TSN processing.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_TRY_ADVANCE) {
+ sctp_misc_ints(SCTP_FWD_TSN_CHECK,
+ 0xee, cum_ack, asoc->advanced_peer_ack_point,
+ old_adv_peer_ack_point);
+ }
+ if (SCTP_TSN_GT(asoc->advanced_peer_ack_point, old_adv_peer_ack_point)) {
+ send_forward_tsn(stcb, asoc);
+ } else if (lchk) {
+ /* try to FR fwd-tsn's that get lost too */
+ if (lchk->rec.data.fwd_tsn_cnt >= 3) {
+ send_forward_tsn(stcb, asoc);
+ }
+ }
+ }
+ for (; lchk != NULL; lchk = TAILQ_NEXT(lchk, sctp_next)) {
+ if (lchk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (lchk != NULL) {
+ /* Assure a timer is up */
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, lchk->whoTo);
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SACK_RWND_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_SACK_RWND_UPDATE,
+ a_rwnd,
+ stcb->asoc.peers_rwnd,
+ stcb->asoc.total_flight,
+ stcb->asoc.total_output_queue_size);
+ }
+}
+
+void
+sctp_update_acked(struct sctp_tcb *stcb, struct sctp_shutdown_chunk *cp, int *abort_flag)
+{
+ /* Copy cum-ack */
+ uint32_t cum_ack, a_rwnd;
+
+ cum_ack = ntohl(cp->cumulative_tsn_ack);
+ /* Arrange so a_rwnd does NOT change */
+ a_rwnd = stcb->asoc.peers_rwnd + stcb->asoc.total_flight;
+
+ /* Now call the express sack handling */
+ sctp_express_handle_sack(stcb, cum_ack, a_rwnd, abort_flag, 0);
+}
+
+static void
+sctp_kick_prsctp_reorder_queue(struct sctp_tcb *stcb,
+ struct sctp_stream_in *strmin)
+{
+ struct sctp_queued_to_read *control, *ncontrol;
+ struct sctp_association *asoc;
+ uint32_t mid;
+ int need_reasm_check = 0;
+
+ asoc = &stcb->asoc;
+ mid = strmin->last_mid_delivered;
+ /*
+ * First deliver anything prior to and including the stream no that
+ * came in.
+ */
+ TAILQ_FOREACH_SAFE(control, &strmin->inqueue, next_instrm, ncontrol) {
+ if (SCTP_MID_GE(asoc->idata_supported, mid, control->mid)) {
+ /* this is deliverable now */
+ if (((control->sinfo_flags >> 8) & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG) {
+ if (control->on_strm_q) {
+ if (control->on_strm_q == SCTP_ON_ORDERED) {
+ TAILQ_REMOVE(&strmin->inqueue, control, next_instrm);
+ } else if (control->on_strm_q == SCTP_ON_UNORDERED) {
+ TAILQ_REMOVE(&strmin->uno_inqueue, control, next_instrm);
+#ifdef INVARIANTS
+ } else {
+ panic("strmin: %p ctl: %p unknown %d",
+ strmin, control, control->on_strm_q);
+#endif
+ }
+ control->on_strm_q = 0;
+ }
+ /* subtract pending on streams */
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ /* deliver it to at least the delivery-q */
+ if (stcb->sctp_socket) {
+ sctp_mark_non_revokable(asoc, control->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv,
+ 1, SCTP_READ_LOCK_HELD,
+ SCTP_SO_NOT_LOCKED);
+ }
+ } else {
+ /* Its a fragmented message */
+ if (control->first_frag_seen) {
+ /* Make it so this is next to deliver, we restore later */
+ strmin->last_mid_delivered = control->mid - 1;
+ need_reasm_check = 1;
+ break;
+ }
+ }
+ } else {
+ /* no more delivery now. */
+ break;
+ }
+ }
+ if (need_reasm_check) {
+ int ret;
+ ret = sctp_deliver_reasm_check(stcb, &stcb->asoc, strmin, SCTP_READ_LOCK_HELD);
+ if (SCTP_MID_GT(asoc->idata_supported, mid, strmin->last_mid_delivered)) {
+ /* Restore the next to deliver unless we are ahead */
+ strmin->last_mid_delivered = mid;
+ }
+ if (ret == 0) {
+ /* Left the front Partial one on */
+ return;
+ }
+ need_reasm_check = 0;
+ }
+ /*
+ * now we must deliver things in queue the normal way if any are
+ * now ready.
+ */
+ mid = strmin->last_mid_delivered + 1;
+ TAILQ_FOREACH_SAFE(control, &strmin->inqueue, next_instrm, ncontrol) {
+ if (SCTP_MID_EQ(asoc->idata_supported, mid, control->mid)) {
+ if (((control->sinfo_flags >> 8) & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG) {
+ /* this is deliverable now */
+ if (control->on_strm_q) {
+ if (control->on_strm_q == SCTP_ON_ORDERED) {
+ TAILQ_REMOVE(&strmin->inqueue, control, next_instrm);
+ } else if (control->on_strm_q == SCTP_ON_UNORDERED) {
+ TAILQ_REMOVE(&strmin->uno_inqueue, control, next_instrm);
+#ifdef INVARIANTS
+ } else {
+ panic("strmin: %p ctl: %p unknown %d",
+ strmin, control, control->on_strm_q);
+#endif
+ }
+ control->on_strm_q = 0;
+ }
+ /* subtract pending on streams */
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ /* deliver it to at least the delivery-q */
+ strmin->last_mid_delivered = control->mid;
+ if (stcb->sctp_socket) {
+ sctp_mark_non_revokable(asoc, control->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, SCTP_SO_NOT_LOCKED);
+ }
+ mid = strmin->last_mid_delivered + 1;
+ } else {
+ /* Its a fragmented message */
+ if (control->first_frag_seen) {
+ /* Make it so this is next to deliver */
+ strmin->last_mid_delivered = control->mid - 1;
+ need_reasm_check = 1;
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ if (need_reasm_check) {
+ (void)sctp_deliver_reasm_check(stcb, &stcb->asoc, strmin, SCTP_READ_LOCK_HELD);
+ }
+}
+
+static void
+sctp_flush_reassm_for_str_seq(struct sctp_tcb *stcb,
+ struct sctp_association *asoc, struct sctp_stream_in *strm,
+ struct sctp_queued_to_read *control, int ordered, uint32_t cumtsn)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+
+ /*
+ * For now large messages held on the stream reasm that are
+ * complete will be tossed too. We could in theory do more
+ * work to spin through and stop after dumping one msg aka
+ * seeing the start of a new msg at the head, and call the
+ * delivery function... to see if it can be delivered... But
+ * for now we just dump everything on the queue.
+ */
+ if (!asoc->idata_supported && !ordered &&
+ control->first_frag_seen &&
+ SCTP_TSN_GT(control->fsn_included, cumtsn)) {
+ return;
+ }
+ TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
+ /* Purge hanging chunks */
+ if (!asoc->idata_supported && !ordered) {
+ if (SCTP_TSN_GT(chk->rec.data.tsn, cumtsn)) {
+ break;
+ }
+ }
+ TAILQ_REMOVE(&control->reasm, chk, sctp_next);
+ if (asoc->size_on_reasm_queue >= chk->send_size) {
+ asoc->size_on_reasm_queue -= chk->send_size;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_reasm_queue = %u smaller than chunk length %u", asoc->size_on_reasm_queue, chk->send_size);
+#else
+ asoc->size_on_reasm_queue = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ if (!TAILQ_EMPTY(&control->reasm)) {
+ /* This has to be old data, unordered */
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ sctp_reset_a_control(control, stcb->sctp_ep, cumtsn);
+ chk = TAILQ_FIRST(&control->reasm);
+ if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+ TAILQ_REMOVE(&control->reasm, chk, sctp_next);
+ sctp_add_chk_to_control(control, strm, stcb, asoc,
+ chk, SCTP_READ_LOCK_HELD);
+ }
+ sctp_deliver_reasm_check(stcb, asoc, strm, SCTP_READ_LOCK_HELD);
+ return;
+ }
+ if (control->on_strm_q == SCTP_ON_ORDERED) {
+ TAILQ_REMOVE(&strm->inqueue, control, next_instrm);
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ control->on_strm_q = 0;
+ } else if (control->on_strm_q == SCTP_ON_UNORDERED) {
+ TAILQ_REMOVE(&strm->uno_inqueue, control, next_instrm);
+ control->on_strm_q = 0;
+#ifdef INVARIANTS
+ } else if (control->on_strm_q) {
+ panic("strm: %p ctl: %p unknown %d",
+ strm, control, control->on_strm_q);
+#endif
+ }
+ control->on_strm_q = 0;
+ if (control->on_read_q == 0) {
+ sctp_free_remote_addr(control->whoFrom);
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+ }
+}
+
+void
+sctp_handle_forward_tsn(struct sctp_tcb *stcb,
+ struct sctp_forward_tsn_chunk *fwd,
+ int *abort_flag, struct mbuf *m , int offset)
+{
+ /* The pr-sctp fwd tsn */
+ /*
+ * here we will perform all the data receiver side steps for
+ * processing FwdTSN, as required in by pr-sctp draft:
+ *
+ * Assume we get FwdTSN(x):
+ *
+ * 1) update local cumTSN to x
+ * 2) try to further advance cumTSN to x + others we have
+ * 3) examine and update re-ordering queue on pr-in-streams
+ * 4) clean up re-assembly queue
+ * 5) Send a sack to report where we are.
+ */
+ struct sctp_association *asoc;
+ uint32_t new_cum_tsn, gap;
+ unsigned int i, fwd_sz, m_size;
+ struct sctp_stream_in *strm;
+ struct sctp_queued_to_read *control, *ncontrol;
+
+ asoc = &stcb->asoc;
+ if ((fwd_sz = ntohs(fwd->ch.chunk_length)) < sizeof(struct sctp_forward_tsn_chunk)) {
+ SCTPDBG(SCTP_DEBUG_INDATA1,
+ "Bad size too small/big fwd-tsn\n");
+ return;
+ }
+ m_size = (stcb->asoc.mapping_array_size << 3);
+ /*************************************************************/
+ /* 1. Here we update local cumTSN and shift the bitmap array */
+ /*************************************************************/
+ new_cum_tsn = ntohl(fwd->new_cumulative_tsn);
+
+ if (SCTP_TSN_GE(asoc->cumulative_tsn, new_cum_tsn)) {
+ /* Already got there ... */
+ return;
+ }
+ /*
+ * now we know the new TSN is more advanced, let's find the actual
+ * gap
+ */
+ SCTP_CALC_TSN_TO_GAP(gap, new_cum_tsn, asoc->mapping_array_base_tsn);
+ asoc->cumulative_tsn = new_cum_tsn;
+ if (gap >= m_size) {
+ if ((long)gap > sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ /*
+ * out of range (of single byte chunks in the rwnd I
+ * give out). This must be an attacker.
+ */
+ *abort_flag = 1;
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "New cum ack %8.8x too high, highest TSN %8.8x",
+ new_cum_tsn, asoc->highest_tsn_inside_map);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_37;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ SCTP_STAT_INCR(sctps_fwdtsn_map_over);
+
+ memset(stcb->asoc.mapping_array, 0, stcb->asoc.mapping_array_size);
+ asoc->mapping_array_base_tsn = new_cum_tsn + 1;
+ asoc->highest_tsn_inside_map = new_cum_tsn;
+
+ memset(stcb->asoc.nr_mapping_array, 0, stcb->asoc.mapping_array_size);
+ asoc->highest_tsn_inside_nr_map = new_cum_tsn;
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(0, 3, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT);
+ }
+ } else {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ for (i = 0; i <= gap; i++) {
+ if (!SCTP_IS_TSN_PRESENT(asoc->mapping_array, i) &&
+ !SCTP_IS_TSN_PRESENT(asoc->nr_mapping_array, i)) {
+ SCTP_SET_TSN_PRESENT(asoc->nr_mapping_array, i);
+ if (SCTP_TSN_GT(asoc->mapping_array_base_tsn + i, asoc->highest_tsn_inside_nr_map)) {
+ asoc->highest_tsn_inside_nr_map = asoc->mapping_array_base_tsn + i;
+ }
+ }
+ }
+ }
+ /*************************************************************/
+ /* 2. Clear up re-assembly queue */
+ /*************************************************************/
+
+ /* This is now done as part of clearing up the stream/seq */
+ if (asoc->idata_supported == 0) {
+ uint16_t sid;
+
+ /* Flush all the un-ordered data based on cum-tsn */
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ for (sid = 0; sid < asoc->streamincnt; sid++) {
+ strm = &asoc->strmin[sid];
+ if (!TAILQ_EMPTY(&strm->uno_inqueue)) {
+ sctp_flush_reassm_for_str_seq(stcb, asoc, strm, TAILQ_FIRST(&strm->uno_inqueue), 0, new_cum_tsn);
+ }
+ }
+ SCTP_INP_READ_UNLOCK(stcb->sctp_ep);
+ }
+ /*******************************************************/
+ /* 3. Update the PR-stream re-ordering queues and fix */
+ /* delivery issues as needed. */
+ /*******************************************************/
+ fwd_sz -= sizeof(*fwd);
+ if (m && fwd_sz) {
+ /* New method. */
+ unsigned int num_str;
+ uint32_t mid;
+ uint16_t sid;
+ uint16_t ordered, flags;
+ struct sctp_strseq *stseq, strseqbuf;
+ struct sctp_strseq_mid *stseq_m, strseqbuf_m;
+ offset += sizeof(*fwd);
+
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ if (asoc->idata_supported) {
+ num_str = fwd_sz / sizeof(struct sctp_strseq_mid);
+ } else {
+ num_str = fwd_sz / sizeof(struct sctp_strseq);
+ }
+ for (i = 0; i < num_str; i++) {
+ if (asoc->idata_supported) {
+ stseq_m = (struct sctp_strseq_mid *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_strseq_mid),
+ (uint8_t *)&strseqbuf_m);
+ offset += sizeof(struct sctp_strseq_mid);
+ if (stseq_m == NULL) {
+ break;
+ }
+ sid = ntohs(stseq_m->sid);
+ mid = ntohl(stseq_m->mid);
+ flags = ntohs(stseq_m->flags);
+ if (flags & PR_SCTP_UNORDERED_FLAG) {
+ ordered = 0;
+ } else {
+ ordered = 1;
+ }
+ } else {
+ stseq = (struct sctp_strseq *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_strseq),
+ (uint8_t *)&strseqbuf);
+ offset += sizeof(struct sctp_strseq);
+ if (stseq == NULL) {
+ break;
+ }
+ sid = ntohs(stseq->sid);
+ mid = (uint32_t)ntohs(stseq->ssn);
+ ordered = 1;
+ }
+ /* Convert */
+
+ /* now process */
+
+ /*
+ * Ok we now look for the stream/seq on the read queue
+ * where its not all delivered. If we find it we transmute the
+ * read entry into a PDI_ABORTED.
+ */
+ if (sid >= asoc->streamincnt) {
+ /* screwed up streams, stop! */
+ break;
+ }
+ if ((asoc->str_of_pdapi == sid) &&
+ (asoc->ssn_of_pdapi == mid)) {
+ /* If this is the one we were partially delivering
+ * now then we no longer are. Note this will change
+ * with the reassembly re-write.
+ */
+ asoc->fragmented_delivery_inprogress = 0;
+ }
+ strm = &asoc->strmin[sid];
+ if (ordered) {
+ TAILQ_FOREACH_SAFE(control, &strm->inqueue, next_instrm, ncontrol) {
+ if (SCTP_MID_GE(asoc->idata_supported, mid, control->mid)) {
+ sctp_flush_reassm_for_str_seq(stcb, asoc, strm, control, ordered, new_cum_tsn);
+ }
+ }
+ } else {
+ if (asoc->idata_supported) {
+ TAILQ_FOREACH_SAFE(control, &strm->uno_inqueue, next_instrm, ncontrol) {
+ if (SCTP_MID_GE(asoc->idata_supported, mid, control->mid)) {
+ sctp_flush_reassm_for_str_seq(stcb, asoc, strm, control, ordered, new_cum_tsn);
+ }
+ }
+ } else {
+ if (!TAILQ_EMPTY(&strm->uno_inqueue)) {
+ sctp_flush_reassm_for_str_seq(stcb, asoc, strm, TAILQ_FIRST(&strm->uno_inqueue), ordered, new_cum_tsn);
+ }
+ }
+ }
+ TAILQ_FOREACH(control, &stcb->sctp_ep->read_queue, next) {
+ if ((control->sinfo_stream == sid) &&
+ (SCTP_MID_EQ(asoc->idata_supported, control->mid, mid))) {
+ control->pdapi_aborted = 1;
+ control->end_added = 1;
+ if (control->on_strm_q == SCTP_ON_ORDERED) {
+ TAILQ_REMOVE(&strm->inqueue, control, next_instrm);
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ } else if (control->on_strm_q == SCTP_ON_UNORDERED) {
+ TAILQ_REMOVE(&strm->uno_inqueue, control, next_instrm);
+#ifdef INVARIANTS
+ } else if (control->on_strm_q) {
+ panic("strm: %p ctl: %p unknown %d",
+ strm, control, control->on_strm_q);
+#endif
+ }
+ control->on_strm_q = 0;
+ sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION,
+ stcb,
+ SCTP_PARTIAL_DELIVERY_ABORTED,
+ (void *)control,
+ SCTP_SO_NOT_LOCKED);
+ break;
+ } else if ((control->sinfo_stream == sid) &&
+ SCTP_MID_GT(asoc->idata_supported, control->mid, mid)) {
+ /* We are past our victim SSN */
+ break;
+ }
+ }
+ if (SCTP_MID_GT(asoc->idata_supported, mid, strm->last_mid_delivered)) {
+ /* Update the sequence number */
+ strm->last_mid_delivered = mid;
+ }
+ /* now kick the stream the new way */
+ /*sa_ignore NO_NULL_CHK*/
+ sctp_kick_prsctp_reorder_queue(stcb, strm);
+ }
+ SCTP_INP_READ_UNLOCK(stcb->sctp_ep);
+ }
+ /*
+ * Now slide thing forward.
+ */
+ sctp_slide_mapping_arrays(stcb);
+}
diff --git a/netwerk/sctp/src/netinet/sctp_indata.h b/netwerk/sctp/src/netinet/sctp_indata.h
new file mode 100644
index 0000000000..523d4f6d34
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_indata.h
@@ -0,0 +1,116 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_INDATA_H_
+#define _NETINET_SCTP_INDATA_H_
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+struct sctp_queued_to_read *
+sctp_build_readq_entry(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ uint32_t tsn, uint32_t ppid,
+ uint32_t context, uint16_t sid,
+ uint32_t mid, uint8_t flags,
+ struct mbuf *dm);
+
+#define sctp_build_readq_entry_mac(_ctl, in_it, context, net, tsn, ppid, sid, flags, dm, tfsn, mid) do { \
+ if (_ctl) { \
+ atomic_add_int(&((net)->ref_count), 1); \
+ memset(_ctl, 0, sizeof(struct sctp_queued_to_read)); \
+ (_ctl)->sinfo_stream = sid; \
+ TAILQ_INIT(&_ctl->reasm); \
+ (_ctl)->top_fsn = tfsn; \
+ (_ctl)->mid = mid; \
+ (_ctl)->sinfo_flags = (flags << 8); \
+ (_ctl)->sinfo_ppid = ppid; \
+ (_ctl)->sinfo_context = context; \
+ (_ctl)->fsn_included = 0xffffffff; \
+ (_ctl)->sinfo_tsn = tsn; \
+ (_ctl)->sinfo_cumtsn = tsn; \
+ (_ctl)->sinfo_assoc_id = sctp_get_associd((in_it)); \
+ (_ctl)->whoFrom = net; \
+ (_ctl)->data = dm; \
+ (_ctl)->stcb = (in_it); \
+ (_ctl)->port_from = (in_it)->rport; \
+ if ((in_it)->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { \
+ (_ctl)->do_not_ref_stcb = 1; \
+ }\
+ } \
+} while (0)
+
+struct mbuf *
+sctp_build_ctl_nchunk(struct sctp_inpcb *inp,
+ struct sctp_sndrcvinfo *sinfo);
+
+void sctp_set_rwnd(struct sctp_tcb *, struct sctp_association *);
+
+uint32_t
+sctp_calc_rwnd(struct sctp_tcb *stcb, struct sctp_association *asoc);
+
+void
+sctp_express_handle_sack(struct sctp_tcb *stcb, uint32_t cumack,
+ uint32_t rwnd, int *abort_now, int ecne_seen);
+
+void
+sctp_handle_sack(struct mbuf *m, int offset_seg, int offset_dup,
+ struct sctp_tcb *stcb,
+ uint16_t num_seg, uint16_t num_nr_seg, uint16_t num_dup,
+ int *abort_now, uint8_t flags,
+ uint32_t cum_ack, uint32_t rwnd, int ecne_seen);
+
+/* draft-ietf-tsvwg-usctp */
+void
+sctp_handle_forward_tsn(struct sctp_tcb *,
+ struct sctp_forward_tsn_chunk *, int *, struct mbuf *, int);
+
+struct sctp_tmit_chunk *
+sctp_try_advance_peer_ack_point(struct sctp_tcb *, struct sctp_association *);
+
+void sctp_service_queues(struct sctp_tcb *, struct sctp_association *);
+
+void
+sctp_update_acked(struct sctp_tcb *, struct sctp_shutdown_chunk *, int *);
+
+int
+sctp_process_data(struct mbuf **, int, int *, int,
+ struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *, uint32_t *);
+
+void sctp_slide_mapping_arrays(struct sctp_tcb *stcb);
+
+void sctp_sack_check(struct sctp_tcb *, int);
+
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_input.c b/netwerk/sctp/src/netinet/sctp_input.c
new file mode 100644
index 0000000000..361b8b22e2
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_input.c
@@ -0,0 +1,6436 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_bsd_addr.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_crc32.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/sctp_kdtrace.h>
+#endif
+#if defined(INET) || defined(INET6)
+#if !defined(_WIN32)
+#include <netinet/udp.h>
+#endif
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/smp.h>
+#endif
+
+static void
+sctp_stop_all_cookie_timers(struct sctp_tcb *stcb)
+{
+ struct sctp_nets *net;
+
+ /* This now not only stops all cookie timers
+ * it also stops any INIT timers as well. This
+ * will make sure that the timers are stopped in
+ * all collision cases.
+ */
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (net->rxt_timer.type == SCTP_TIMER_TYPE_COOKIE) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE,
+ stcb->sctp_ep,
+ stcb,
+ net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_1);
+ } else if (net->rxt_timer.type == SCTP_TIMER_TYPE_INIT) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_INIT,
+ stcb->sctp_ep,
+ stcb,
+ net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_2);
+ }
+ }
+}
+
+/* INIT handler */
+static void
+sctp_handle_init(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst, struct sctphdr *sh,
+ struct sctp_init_chunk *cp, struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb, struct sctp_nets *net,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_init *init;
+ struct mbuf *op_err;
+
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_handle_init: handling INIT tcb:%p\n",
+ (void *)stcb);
+ if (stcb == NULL) {
+ SCTP_INP_RLOCK(inp);
+ }
+ /* Validate parameters */
+ init = &cp->init;
+ if (ntohl(init->initiate_tag) == 0) {
+ goto outnow;
+ }
+ if ((ntohl(init->a_rwnd) < SCTP_MIN_RWND) ||
+ (ntohs(init->num_inbound_streams) == 0) ||
+ (ntohs(init->num_outbound_streams) == 0)) {
+ /* protocol error... send abort */
+ op_err = sctp_generate_cause(SCTP_CAUSE_INVALID_PARAM, "");
+ sctp_send_abort(m, iphlen, src, dst, sh, init->initiate_tag, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ goto outnow;
+ }
+ if (sctp_validate_init_auth_params(m, offset + sizeof(*cp),
+ offset + ntohs(cp->ch.chunk_length))) {
+ /* auth parameter(s) error... send abort */
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Problem with AUTH parameters");
+ sctp_send_abort(m, iphlen, src, dst, sh, init->initiate_tag, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ goto outnow;
+ }
+ /* We are only accepting if we have a listening socket.*/
+ if ((stcb == NULL) &&
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (!SCTP_IS_LISTENING(inp)))) {
+ /*
+ * FIX ME ?? What about TCP model and we have a
+ * match/restart case? Actually no fix is needed.
+ * the lookup will always find the existing assoc so stcb
+ * would not be NULL. It may be questionable to do this
+ * since we COULD just send back the INIT-ACK and hope that
+ * the app did accept()'s by the time the COOKIE was sent. But
+ * there is a price to pay for COOKIE generation and I don't
+ * want to pay it on the chance that the app will actually do
+ * some accepts(). The App just looses and should NOT be in
+ * this state :-)
+ */
+ if (SCTP_BASE_SYSCTL(sctp_blackhole) == 0) {
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "No listener");
+ sctp_send_abort(m, iphlen, src, dst, sh, 0, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ }
+ goto outnow;
+ }
+ if ((stcb != NULL) &&
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "sctp_handle_init: sending SHUTDOWN-ACK\n");
+ sctp_send_shutdown_ack(stcb, NULL);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CONTROL_PROC, SCTP_SO_NOT_LOCKED);
+ } else {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "sctp_handle_init: sending INIT-ACK\n");
+ sctp_send_initiate_ack(inp, stcb, net, m, iphlen, offset,
+ src, dst, sh, cp,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ }
+ outnow:
+ if (stcb == NULL) {
+ SCTP_INP_RUNLOCK(inp);
+ }
+}
+
+/*
+ * process peer "INIT/INIT-ACK" chunk returns value < 0 on error
+ */
+
+int
+sctp_is_there_unsent_data(struct sctp_tcb *stcb, int so_locked)
+{
+ int unsent_data;
+ unsigned int i;
+ struct sctp_stream_queue_pending *sp;
+ struct sctp_association *asoc;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* This function returns if any stream has true unsent data on it.
+ * Note that as it looks through it will clean up any places that
+ * have old data that has been sent but left at top of stream queue.
+ */
+ asoc = &stcb->asoc;
+ unsent_data = 0;
+ if (!stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, asoc)) {
+ /* Check to see if some data queued */
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ /*sa_ignore FREED_MEMORY*/
+ sp = TAILQ_FIRST(&stcb->asoc.strmout[i].outqueue);
+ if (sp == NULL) {
+ continue;
+ }
+ if ((sp->msg_is_complete) &&
+ (sp->length == 0) &&
+ (sp->sender_all_done)) {
+ /* We are doing differed cleanup. Last
+ * time through when we took all the data
+ * the sender_all_done was not set.
+ */
+ if (sp->put_last_out == 0) {
+ SCTP_PRINTF("Gak, put out entire msg with NO end!-1\n");
+ SCTP_PRINTF("sender_done:%d len:%d msg_comp:%d put_last_out:%d\n",
+ sp->sender_all_done,
+ sp->length,
+ sp->msg_is_complete,
+ sp->put_last_out);
+ }
+ atomic_subtract_int(&stcb->asoc.stream_queue_cnt, 1);
+ TAILQ_REMOVE(&stcb->asoc.strmout[i].outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, &asoc->strmout[i], sp);
+ if (sp->net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ }
+ sctp_free_a_strmoq(stcb, sp, so_locked);
+ if (!TAILQ_EMPTY(&stcb->asoc.strmout[i].outqueue)) {
+ unsent_data++;
+ }
+ } else {
+ unsent_data++;
+ }
+ if (unsent_data > 0) {
+ break;
+ }
+ }
+ }
+ return (unsent_data);
+}
+
+static int
+sctp_process_init(struct sctp_init_chunk *cp, struct sctp_tcb *stcb)
+{
+ struct sctp_init *init;
+ struct sctp_association *asoc;
+ struct sctp_nets *lnet;
+ unsigned int i;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ init = &cp->init;
+ asoc = &stcb->asoc;
+ /* save off parameters */
+ asoc->peer_vtag = ntohl(init->initiate_tag);
+ asoc->peers_rwnd = ntohl(init->a_rwnd);
+ /* init tsn's */
+ asoc->highest_tsn_inside_map = asoc->asconf_seq_in = ntohl(init->initial_tsn) - 1;
+
+ if (!TAILQ_EMPTY(&asoc->nets)) {
+ /* update any ssthresh's that may have a default */
+ TAILQ_FOREACH(lnet, &asoc->nets, sctp_next) {
+ lnet->ssthresh = asoc->peers_rwnd;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & (SCTP_CWND_MONITOR_ENABLE|SCTP_CWND_LOGGING_ENABLE)) {
+ sctp_log_cwnd(stcb, lnet, 0, SCTP_CWND_INITIALIZATION);
+ }
+ }
+ }
+ if (asoc->pre_open_streams > ntohs(init->num_inbound_streams)) {
+ unsigned int newcnt;
+ struct sctp_stream_out *outs;
+ struct sctp_stream_queue_pending *sp, *nsp;
+ struct sctp_tmit_chunk *chk, *nchk;
+
+ /* abandon the upper streams */
+ newcnt = ntohs(init->num_inbound_streams);
+ TAILQ_FOREACH_SAFE(chk, &asoc->send_queue, sctp_next, nchk) {
+ if (chk->rec.data.sid >= newcnt) {
+ TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next);
+ asoc->send_queue_cnt--;
+ if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.sid);
+#endif
+ }
+ if (chk->data != NULL) {
+ sctp_free_bufspace(stcb, asoc, chk, 1);
+ sctp_ulp_notify(SCTP_NOTIFY_UNSENT_DG_FAIL, stcb,
+ 0, chk, SCTP_SO_NOT_LOCKED);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ /*sa_ignore FREED_MEMORY*/
+ }
+ }
+ if (asoc->strmout) {
+ for (i = newcnt; i < asoc->pre_open_streams; i++) {
+ outs = &asoc->strmout[i];
+ TAILQ_FOREACH_SAFE(sp, &outs->outqueue, next, nsp) {
+ atomic_subtract_int(&stcb->asoc.stream_queue_cnt, 1);
+ TAILQ_REMOVE(&outs->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, outs, sp);
+ sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL,
+ stcb, 0, sp, SCTP_SO_NOT_LOCKED);
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ }
+ if (sp->net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ /* Free the chunk */
+ sctp_free_a_strmoq(stcb, sp, SCTP_SO_NOT_LOCKED);
+ /*sa_ignore FREED_MEMORY*/
+ }
+ outs->state = SCTP_STREAM_CLOSED;
+ }
+ }
+ /* cut back the count */
+ asoc->pre_open_streams = newcnt;
+ }
+ asoc->streamoutcnt = asoc->pre_open_streams;
+ if (asoc->strmout) {
+ for (i = 0; i < asoc->streamoutcnt; i++) {
+ asoc->strmout[i].state = SCTP_STREAM_OPEN;
+ }
+ }
+ /* EY - nr_sack: initialize highest tsn in nr_mapping_array */
+ asoc->highest_tsn_inside_nr_map = asoc->highest_tsn_inside_map;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(0, 5, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT);
+ }
+ /* This is the next one we expect */
+ asoc->str_reset_seq_in = asoc->asconf_seq_in + 1;
+
+ asoc->mapping_array_base_tsn = ntohl(init->initial_tsn);
+ asoc->tsn_last_delivered = asoc->cumulative_tsn = asoc->asconf_seq_in;
+
+ asoc->advanced_peer_ack_point = asoc->last_acked_seq;
+ /* open the requested streams */
+
+ if (asoc->strmin != NULL) {
+ /* Free the old ones */
+ for (i = 0; i < asoc->streamincnt; i++) {
+ sctp_clean_up_stream(stcb, &asoc->strmin[i].inqueue);
+ sctp_clean_up_stream(stcb, &asoc->strmin[i].uno_inqueue);
+ }
+ SCTP_FREE(asoc->strmin, SCTP_M_STRMI);
+ }
+ if (asoc->max_inbound_streams > ntohs(init->num_outbound_streams)) {
+ asoc->streamincnt = ntohs(init->num_outbound_streams);
+ } else {
+ asoc->streamincnt = asoc->max_inbound_streams;
+ }
+ SCTP_MALLOC(asoc->strmin, struct sctp_stream_in *, asoc->streamincnt *
+ sizeof(struct sctp_stream_in), SCTP_M_STRMI);
+ if (asoc->strmin == NULL) {
+ /* we didn't get memory for the streams! */
+ SCTPDBG(SCTP_DEBUG_INPUT2, "process_init: couldn't get memory for the streams!\n");
+ return (-1);
+ }
+ for (i = 0; i < asoc->streamincnt; i++) {
+ asoc->strmin[i].sid = i;
+ asoc->strmin[i].last_mid_delivered = 0xffffffff;
+ TAILQ_INIT(&asoc->strmin[i].inqueue);
+ TAILQ_INIT(&asoc->strmin[i].uno_inqueue);
+ asoc->strmin[i].pd_api_started = 0;
+ asoc->strmin[i].delivery_started = 0;
+ }
+ /*
+ * load_address_from_init will put the addresses into the
+ * association when the COOKIE is processed or the INIT-ACK is
+ * processed. Both types of COOKIE's existing and new call this
+ * routine. It will remove addresses that are no longer in the
+ * association (for the restarting case where addresses are
+ * removed). Up front when the INIT arrives we will discard it if it
+ * is a restart and new addresses have been added.
+ */
+ /* sa_ignore MEMLEAK */
+ return (0);
+}
+
+/*
+ * INIT-ACK message processing/consumption returns value < 0 on error
+ */
+static int
+sctp_process_init_ack(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst, struct sctphdr *sh,
+ struct sctp_init_ack_chunk *cp, struct sctp_tcb *stcb,
+ struct sctp_nets *net, int *abort_no_unlock,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id)
+{
+ struct sctp_association *asoc;
+ struct mbuf *op_err;
+ int retval, abort_flag, cookie_found;
+ int initack_limit;
+ int nat_friendly = 0;
+
+ /* First verify that we have no illegal param's */
+ abort_flag = 0;
+ cookie_found = 0;
+
+ op_err = sctp_arethere_unrecognized_parameters(m,
+ (offset + sizeof(struct sctp_init_chunk)),
+ &abort_flag, (struct sctp_chunkhdr *)cp,
+ &nat_friendly, &cookie_found);
+ if (abort_flag) {
+ /* Send an abort and notify peer */
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ *abort_no_unlock = 1;
+ return (-1);
+ }
+ if (!cookie_found) {
+ uint16_t len;
+
+ /* Only report the missing cookie parameter */
+ if (op_err != NULL) {
+ sctp_m_freem(op_err);
+ }
+ len = (uint16_t)(sizeof(struct sctp_error_missing_param) + sizeof(uint16_t));
+ /* We abort with an error of missing mandatory param */
+ op_err = sctp_get_mbuf_for_msg(len, 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err != NULL) {
+ struct sctp_error_missing_param *cause;
+
+ SCTP_BUF_LEN(op_err) = len;
+ cause = mtod(op_err, struct sctp_error_missing_param *);
+ /* Subtract the reserved param */
+ cause->cause.code = htons(SCTP_CAUSE_MISSING_PARAM);
+ cause->cause.length = htons(len);
+ cause->num_missing_params = htonl(1);
+ cause->type[0] = htons(SCTP_STATE_COOKIE);
+ }
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ *abort_no_unlock = 1;
+ return (-3);
+ }
+ asoc = &stcb->asoc;
+ asoc->peer_supports_nat = (uint8_t)nat_friendly;
+ /* process the peer's parameters in the INIT-ACK */
+ if (sctp_process_init((struct sctp_init_chunk *)cp, stcb) < 0) {
+ if (op_err != NULL) {
+ sctp_m_freem(op_err);
+ }
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ SCTPDBG(SCTP_DEBUG_INPUT1, "sctp_process_init() failed\n");
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ *abort_no_unlock = 1;
+ return (-1);
+ }
+ initack_limit = offset + ntohs(cp->ch.chunk_length);
+ /* load all addresses */
+ if ((retval = sctp_load_addresses_from_init(stcb, m,
+ offset + sizeof(struct sctp_init_chunk),
+ initack_limit, src, dst, NULL, stcb->asoc.port)) < 0) {
+ if (op_err != NULL) {
+ sctp_m_freem(op_err);
+ }
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Problem with address parameters");
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "Load addresses from INIT causes an abort %d\n",
+ retval);
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ *abort_no_unlock = 1;
+ return (-1);
+ }
+ /* if the peer doesn't support asconf, flush the asconf queue */
+ if (asoc->asconf_supported == 0) {
+ struct sctp_asconf_addr *param, *nparam;
+
+ TAILQ_FOREACH_SAFE(param, &asoc->asconf_queue, next, nparam) {
+ TAILQ_REMOVE(&asoc->asconf_queue, param, next);
+ SCTP_FREE(param, SCTP_M_ASC_ADDR);
+ }
+ }
+
+ stcb->asoc.peer_hmac_id = sctp_negotiate_hmacid(stcb->asoc.peer_hmacs,
+ stcb->asoc.local_hmacs);
+ if (op_err) {
+ sctp_queue_op_err(stcb, op_err);
+ /* queuing will steal away the mbuf chain to the out queue */
+ op_err = NULL;
+ }
+ /* extract the cookie and queue it to "echo" it back... */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INPUT,
+ __LINE__);
+ }
+
+ /*
+ * Cancel the INIT timer, We do this first before queueing the
+ * cookie. We always cancel at the primary to assume that we are
+ * canceling the timer started by the INIT which always goes to the
+ * primary.
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, stcb->sctp_ep, stcb,
+ asoc->primary_destination, SCTP_FROM_SCTP_INPUT + SCTP_LOC_3);
+
+ /* calculate the RTO */
+ if (asoc->overall_error_count == 0) {
+ sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered,
+ SCTP_RTT_FROM_NON_DATA);
+ }
+ stcb->asoc.overall_error_count = 0;
+ net->error_count = 0;
+#if defined(__Userspace__)
+ if (stcb->sctp_ep->recv_callback) {
+ if (stcb->sctp_socket) {
+ uint32_t inqueue_bytes, sb_free_now;
+ struct sctp_inpcb *inp;
+
+ inp = stcb->sctp_ep;
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * sizeof(struct sctp_data_chunk));
+ sb_free_now = SCTP_SB_LIMIT_SND(stcb->sctp_socket) - (inqueue_bytes + stcb->asoc.sb_send_resv);
+
+ /* check if the amount free in the send socket buffer crossed the threshold */
+ if (inp->send_callback &&
+ (((inp->send_sb_threshold > 0) &&
+ (sb_free_now >= inp->send_sb_threshold) &&
+ (stcb->asoc.chunks_on_out_queue <= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) ||
+ (inp->send_sb_threshold == 0))) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ inp->send_callback(stcb->sctp_socket, sb_free_now, inp->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ }
+ }
+#endif
+ retval = sctp_send_cookie_echo(m, offset, initack_limit, stcb, net);
+ return (retval);
+}
+
+static void
+sctp_handle_heartbeat_ack(struct sctp_heartbeat_chunk *cp,
+ struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ union sctp_sockstore store;
+ struct sctp_nets *r_net, *f_net;
+ struct timeval tv;
+ int req_prim = 0;
+ uint16_t old_error_counter;
+
+ if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_heartbeat_chunk)) {
+ /* Invalid length */
+ return;
+ }
+
+ memset(&store, 0, sizeof(store));
+ switch (cp->heartbeat.hb_info.addr_family) {
+#ifdef INET
+ case AF_INET:
+ if (cp->heartbeat.hb_info.addr_len == sizeof(struct sockaddr_in)) {
+ store.sin.sin_family = cp->heartbeat.hb_info.addr_family;
+#ifdef HAVE_SIN_LEN
+ store.sin.sin_len = cp->heartbeat.hb_info.addr_len;
+#endif
+ store.sin.sin_port = stcb->rport;
+ memcpy(&store.sin.sin_addr, cp->heartbeat.hb_info.address,
+ sizeof(store.sin.sin_addr));
+ } else {
+ return;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (cp->heartbeat.hb_info.addr_len == sizeof(struct sockaddr_in6)) {
+ store.sin6.sin6_family = cp->heartbeat.hb_info.addr_family;
+#ifdef HAVE_SIN6_LEN
+ store.sin6.sin6_len = cp->heartbeat.hb_info.addr_len;
+#endif
+ store.sin6.sin6_port = stcb->rport;
+ memcpy(&store.sin6.sin6_addr, cp->heartbeat.hb_info.address, sizeof(struct in6_addr));
+ } else {
+ return;
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (cp->heartbeat.hb_info.addr_len == sizeof(struct sockaddr_conn)) {
+ store.sconn.sconn_family = cp->heartbeat.hb_info.addr_family;
+#ifdef HAVE_SCONN_LEN
+ store.sconn.sconn_len = cp->heartbeat.hb_info.addr_len;
+#endif
+ store.sconn.sconn_port = stcb->rport;
+ memcpy(&store.sconn.sconn_addr, cp->heartbeat.hb_info.address, sizeof(void *));
+ } else {
+ return;
+ }
+ break;
+#endif
+ default:
+ return;
+ }
+ r_net = sctp_findnet(stcb, &store.sa);
+ if (r_net == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Huh? I can't find the address I sent it to, discard\n");
+ return;
+ }
+ if ((r_net && (r_net->dest_state & SCTP_ADDR_UNCONFIRMED)) &&
+ (r_net->heartbeat_random1 == cp->heartbeat.hb_info.random_value1) &&
+ (r_net->heartbeat_random2 == cp->heartbeat.hb_info.random_value2)) {
+ /*
+ * If the its a HB and it's random value is correct when can
+ * confirm the destination.
+ */
+ r_net->dest_state &= ~SCTP_ADDR_UNCONFIRMED;
+ if (r_net->dest_state & SCTP_ADDR_REQ_PRIMARY) {
+ stcb->asoc.primary_destination = r_net;
+ r_net->dest_state &= ~SCTP_ADDR_REQ_PRIMARY;
+ f_net = TAILQ_FIRST(&stcb->asoc.nets);
+ if (f_net != r_net) {
+ /* first one on the list is NOT the primary
+ * sctp_cmpaddr() is much more efficient if
+ * the primary is the first on the list, make it
+ * so.
+ */
+ TAILQ_REMOVE(&stcb->asoc.nets, r_net, sctp_next);
+ TAILQ_INSERT_HEAD(&stcb->asoc.nets, r_net, sctp_next);
+ }
+ req_prim = 1;
+ }
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED,
+ stcb, 0, (void *)r_net, SCTP_SO_NOT_LOCKED);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb,
+ r_net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_4);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, r_net);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INPUT,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ old_error_counter = r_net->error_count;
+ r_net->error_count = 0;
+ r_net->hb_responded = 1;
+ tv.tv_sec = cp->heartbeat.hb_info.time_value_1;
+ tv.tv_usec = cp->heartbeat.hb_info.time_value_2;
+ /* Now lets do a RTO with this */
+ sctp_calculate_rto(stcb, &stcb->asoc, r_net, &tv,
+ SCTP_RTT_FROM_NON_DATA);
+ if ((r_net->dest_state & SCTP_ADDR_REACHABLE) == 0) {
+ r_net->dest_state |= SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb,
+ 0, (void *)r_net, SCTP_SO_NOT_LOCKED);
+ }
+ if (r_net->dest_state & SCTP_ADDR_PF) {
+ r_net->dest_state &= ~SCTP_ADDR_PF;
+ stcb->asoc.cc_functions.sctp_cwnd_update_exit_pf(stcb, net);
+ }
+ if (old_error_counter > 0) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep,
+ stcb, r_net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_5);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, r_net);
+ }
+ if (r_net == stcb->asoc.primary_destination) {
+ if (stcb->asoc.alternate) {
+ /* release the alternate, primary is good */
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ }
+ /* Mobility adaptation */
+ if (req_prim) {
+ if ((sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE) ||
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) &&
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_PRIM_DELETED)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_PRIM_DELETED,
+ stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_6);
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_assoc_immediate_retrans(stcb,
+ stcb->asoc.primary_destination);
+ }
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE)) {
+ sctp_move_chunks_from_net(stcb,
+ stcb->asoc.deleted_primary);
+ }
+ sctp_delete_prim_timer(stcb->sctp_ep, stcb);
+ }
+ }
+}
+
+static int
+sctp_handle_nat_colliding_state(struct sctp_tcb *stcb)
+{
+ /*
+ * Return 0 means we want you to proceed with the abort
+ * non-zero means no abort processing.
+ */
+ uint32_t new_vtag;
+ struct sctpasochead *head;
+
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_INFO_WLOCK();
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ } else {
+ return (0);
+ }
+ new_vtag = sctp_select_a_tag(stcb->sctp_ep, stcb->sctp_ep->sctp_lport, stcb->rport, 1);
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) {
+ /* generate a new vtag and send init */
+ LIST_REMOVE(stcb, sctp_asocs);
+ stcb->asoc.my_vtag = new_vtag;
+ head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag, SCTP_BASE_INFO(hashasocmark))];
+ /* put it in the bucket in the vtag hash of assoc's for the system */
+ LIST_INSERT_HEAD(head, stcb, sctp_asocs);
+ SCTP_INP_INFO_WUNLOCK();
+ sctp_send_initiate(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ return (1);
+ } else {
+ /* treat like a case where the cookie expired i.e.:
+ * - dump current cookie.
+ * - generate a new vtag.
+ * - resend init.
+ */
+ /* generate a new vtag and send init */
+ LIST_REMOVE(stcb, sctp_asocs);
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ sctp_stop_all_cookie_timers(stcb);
+ sctp_toss_old_cookies(stcb, &stcb->asoc);
+ stcb->asoc.my_vtag = new_vtag;
+ head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag, SCTP_BASE_INFO(hashasocmark))];
+ /* put it in the bucket in the vtag hash of assoc's for the system */
+ LIST_INSERT_HEAD(head, stcb, sctp_asocs);
+ SCTP_INP_INFO_WUNLOCK();
+ sctp_send_initiate(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ return (1);
+ }
+ return (0);
+}
+
+static int
+sctp_handle_nat_missing_state(struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ /* return 0 means we want you to proceed with the abort
+ * non-zero means no abort processing
+ */
+ if (stcb->asoc.auth_supported == 0) {
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_handle_nat_missing_state: Peer does not support AUTH, cannot send an asconf\n");
+ return (0);
+ }
+ sctp_asconf_send_nat_state_update(stcb, net);
+ return (1);
+}
+
+/* Returns 1 if the stcb was aborted, 0 otherwise */
+static int
+sctp_handle_abort(struct sctp_abort_chunk *abort,
+ struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+ uint16_t len;
+ uint16_t error;
+
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_handle_abort: handling ABORT\n");
+ if (stcb == NULL)
+ return (0);
+
+ len = ntohs(abort->ch.chunk_length);
+ if (len >= sizeof(struct sctp_chunkhdr) + sizeof(struct sctp_error_cause)) {
+ /* Need to check the cause codes for our
+ * two magic nat aborts which don't kill the assoc
+ * necessarily.
+ */
+ struct sctp_error_cause *cause;
+
+ cause = (struct sctp_error_cause *)(abort + 1);
+ error = ntohs(cause->code);
+ if (error == SCTP_CAUSE_NAT_COLLIDING_STATE) {
+ SCTPDBG(SCTP_DEBUG_INPUT2, "Received Colliding state, ABORT flags:%x\n",
+ abort->ch.chunk_flags);
+ if (sctp_handle_nat_colliding_state(stcb)) {
+ return (0);
+ }
+ } else if (error == SCTP_CAUSE_NAT_MISSING_STATE) {
+ SCTPDBG(SCTP_DEBUG_INPUT2, "Received missing state, ABORT flags:%x\n",
+ abort->ch.chunk_flags);
+ if (sctp_handle_nat_missing_state(stcb, net)) {
+ return (0);
+ }
+ }
+ } else {
+ error = 0;
+ }
+ /* stop any receive timers */
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_7);
+ /* notify user of the abort and clean up... */
+ sctp_abort_notification(stcb, true, false, error, abort, SCTP_SO_NOT_LOCKED);
+ /* free the tcb */
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ sctp_print_out_track_log(stcb);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_8);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_handle_abort: finished\n");
+ return (1);
+}
+
+static void
+sctp_start_net_timers(struct sctp_tcb *stcb)
+{
+ uint32_t cnt_hb_sent;
+ struct sctp_nets *net;
+
+ cnt_hb_sent = 0;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* For each network start:
+ * 1) A pmtu timer.
+ * 2) A HB timer
+ * 3) If the dest in unconfirmed send
+ * a hb as well if under max_hb_burst have
+ * been sent.
+ */
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, stcb->sctp_ep, stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net);
+ if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) &&
+ (cnt_hb_sent < SCTP_BASE_SYSCTL(sctp_hb_maxburst))) {
+ sctp_send_hb(stcb, net, SCTP_SO_NOT_LOCKED);
+ cnt_hb_sent++;
+ }
+ }
+ if (cnt_hb_sent) {
+ sctp_chunk_output(stcb->sctp_ep, stcb,
+ SCTP_OUTPUT_FROM_COOKIE_ACK,
+ SCTP_SO_NOT_LOCKED);
+ }
+}
+
+static void
+sctp_check_data_from_peer(struct sctp_tcb *stcb, int *abort_flag)
+{
+ char msg[SCTP_DIAG_INFO_LEN];
+ struct sctp_association *asoc;
+ struct mbuf *op_err;
+ unsigned int i;
+
+ *abort_flag = 0;
+ asoc = &stcb->asoc;
+ if (SCTP_TSN_GT(asoc->highest_tsn_inside_map, asoc->cumulative_tsn) ||
+ SCTP_TSN_GT(asoc->highest_tsn_inside_nr_map, asoc->cumulative_tsn)) {
+ SCTP_SNPRINTF(msg, sizeof(msg), "Missing TSN");
+ *abort_flag = 1;
+ }
+ if (!*abort_flag) {
+ for (i = 0; i < asoc->streamincnt; i++) {
+ if (!TAILQ_EMPTY(&asoc->strmin[i].inqueue) ||
+ !TAILQ_EMPTY(&asoc->strmin[i].uno_inqueue)) {
+ SCTP_SNPRINTF(msg, sizeof(msg), "Missing user data");
+ *abort_flag = 1;
+ break;
+ }
+ }
+ }
+ if (*abort_flag) {
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INPUT + SCTP_LOC_9;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ }
+}
+
+static void
+sctp_handle_shutdown(struct sctp_shutdown_chunk *cp,
+ struct sctp_tcb *stcb, struct sctp_nets *net, int *abort_flag)
+{
+ int some_on_streamwheel;
+ int old_state;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_handle_shutdown: handling SHUTDOWN\n");
+ if (stcb == NULL)
+ return;
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ return;
+ }
+ if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_shutdown_chunk)) {
+ /* Shutdown NOT the expected size */
+ return;
+ }
+ old_state = SCTP_GET_STATE(stcb);
+ sctp_update_acked(stcb, cp, abort_flag);
+ if (*abort_flag) {
+ return;
+ }
+ sctp_check_data_from_peer(stcb, abort_flag);
+ if (*abort_flag) {
+ return;
+ }
+ if (stcb->sctp_socket) {
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT)) {
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_RECEIVED);
+ /* notify upper layer that peer has initiated a shutdown */
+ sctp_ulp_notify(SCTP_NOTIFY_PEER_SHUTDOWN, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+
+ /* reset time */
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+ }
+ }
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_SENT) {
+ /*
+ * stop the shutdown timer, since we WILL move to
+ * SHUTDOWN-ACK-SENT.
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb,
+ net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_9);
+ }
+ /* Now is there unsent data on a stream somewhere? */
+ some_on_streamwheel = sctp_is_there_unsent_data(stcb, SCTP_SO_NOT_LOCKED);
+
+ if (!TAILQ_EMPTY(&stcb->asoc.send_queue) ||
+ !TAILQ_EMPTY(&stcb->asoc.sent_queue) ||
+ some_on_streamwheel) {
+ /* By returning we will push more data out */
+ return;
+ } else {
+ /* no outstanding data to send, so move on... */
+ /* send SHUTDOWN-ACK */
+ /* move to SHUTDOWN-ACK-SENT state */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT) {
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_ACK_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ sctp_send_shutdown_ack(stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK,
+ stcb->sctp_ep, stcb, net);
+ } else if (old_state == SCTP_STATE_SHUTDOWN_ACK_SENT) {
+ sctp_send_shutdown_ack(stcb, net);
+ }
+ }
+}
+
+static void
+sctp_handle_shutdown_ack(struct sctp_shutdown_ack_chunk *cp SCTP_UNUSED,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ int abort_flag;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(stcb->sctp_ep);
+#endif
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_shutdown_ack: handling SHUTDOWN ACK\n");
+ if (stcb == NULL) {
+ return;
+ }
+
+ /* process according to association state */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ /* unexpected SHUTDOWN-ACK... do OOTB handling... */
+ sctp_send_shutdown_complete(stcb, net, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ /* unexpected SHUTDOWN-ACK... so ignore... */
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ sctp_check_data_from_peer(stcb, &abort_flag);
+ if (abort_flag) {
+ return;
+ }
+#ifdef INVARIANTS
+ if (!TAILQ_EMPTY(&stcb->asoc.send_queue) ||
+ !TAILQ_EMPTY(&stcb->asoc.sent_queue) ||
+ sctp_is_there_unsent_data(stcb, SCTP_SO_NOT_LOCKED)) {
+ panic("Queues are not empty when handling SHUTDOWN-ACK");
+ }
+#endif
+ /* stop the timer */
+ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_10);
+ /* send SHUTDOWN-COMPLETE */
+ sctp_send_shutdown_complete(stcb, net, 0);
+ /* notify upper layer protocol */
+ if (stcb->sctp_socket) {
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ SCTP_SB_CLEAR(stcb->sctp_socket->so_snd);
+ }
+ sctp_ulp_notify(SCTP_NOTIFY_ASSOC_DOWN, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ SCTP_STAT_INCR_COUNTER32(sctps_shutdown);
+ /* free the TCB but first save off the ep */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_11);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+}
+
+static void
+sctp_process_unrecog_chunk(struct sctp_tcb *stcb, uint8_t chunk_type)
+{
+ switch (chunk_type) {
+ case SCTP_ASCONF_ACK:
+ case SCTP_ASCONF:
+ sctp_asconf_cleanup(stcb);
+ break;
+ case SCTP_IFORWARD_CUM_TSN:
+ case SCTP_FORWARD_CUM_TSN:
+ stcb->asoc.prsctp_supported = 0;
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "Peer does not support chunk type %d (0x%x).\n",
+ chunk_type, chunk_type);
+ break;
+ }
+}
+
+/*
+ * Skip past the param header and then we will find the param that caused the
+ * problem. There are a number of param's in a ASCONF OR the prsctp param
+ * these will turn of specific features.
+ * XXX: Is this the right thing to do?
+ */
+static void
+sctp_process_unrecog_param(struct sctp_tcb *stcb, uint16_t parameter_type)
+{
+ switch (parameter_type) {
+ /* pr-sctp draft */
+ case SCTP_PRSCTP_SUPPORTED:
+ stcb->asoc.prsctp_supported = 0;
+ break;
+ case SCTP_SUPPORTED_CHUNK_EXT:
+ break;
+ /* draft-ietf-tsvwg-addip-sctp */
+ case SCTP_HAS_NAT_SUPPORT:
+ stcb->asoc.peer_supports_nat = 0;
+ break;
+ case SCTP_ADD_IP_ADDRESS:
+ case SCTP_DEL_IP_ADDRESS:
+ case SCTP_SET_PRIM_ADDR:
+ stcb->asoc.asconf_supported = 0;
+ break;
+ case SCTP_SUCCESS_REPORT:
+ case SCTP_ERROR_CAUSE_IND:
+ SCTPDBG(SCTP_DEBUG_INPUT2, "Huh, the peer does not support success? or error cause?\n");
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "Turning off ASCONF to this strange peer\n");
+ stcb->asoc.asconf_supported = 0;
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "Peer does not support param type %d (0x%x)??\n",
+ parameter_type, parameter_type);
+ break;
+ }
+}
+
+static int
+sctp_handle_error(struct sctp_chunkhdr *ch,
+ struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t limit)
+{
+ struct sctp_error_cause *cause;
+ struct sctp_association *asoc;
+ uint32_t remaining_length, adjust;
+ uint16_t code, cause_code, cause_length;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ /* parse through all of the errors and process */
+ asoc = &stcb->asoc;
+ cause = (struct sctp_error_cause *)((caddr_t)ch +
+ sizeof(struct sctp_chunkhdr));
+ remaining_length = ntohs(ch->chunk_length);
+ if (remaining_length > limit) {
+ remaining_length = limit;
+ }
+ if (remaining_length >= sizeof(struct sctp_chunkhdr)) {
+ remaining_length -= sizeof(struct sctp_chunkhdr);
+ } else {
+ remaining_length = 0;
+ }
+ code = 0;
+ while (remaining_length >= sizeof(struct sctp_error_cause)) {
+ /* Process an Error Cause */
+ cause_code = ntohs(cause->code);
+ cause_length = ntohs(cause->length);
+ if ((cause_length > remaining_length) || (cause_length == 0)) {
+ /* Invalid cause length, possibly due to truncation. */
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Bogus length in cause - bytes left: %u cause length: %u\n",
+ remaining_length, cause_length);
+ return (0);
+ }
+ if (code == 0) {
+ /* report the first error cause */
+ code = cause_code;
+ }
+ switch (cause_code) {
+ case SCTP_CAUSE_INVALID_STREAM:
+ case SCTP_CAUSE_MISSING_PARAM:
+ case SCTP_CAUSE_INVALID_PARAM:
+ case SCTP_CAUSE_NO_USER_DATA:
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Software error we got a %u back? We have a bug :/ (or do they?)\n",
+ cause_code);
+ break;
+ case SCTP_CAUSE_NAT_COLLIDING_STATE:
+ SCTPDBG(SCTP_DEBUG_INPUT2, "Received Colliding state, ERROR flags: %x\n",
+ ch->chunk_flags);
+ if (sctp_handle_nat_colliding_state(stcb)) {
+ return (0);
+ }
+ break;
+ case SCTP_CAUSE_NAT_MISSING_STATE:
+ SCTPDBG(SCTP_DEBUG_INPUT2, "Received missing state, ERROR flags: %x\n",
+ ch->chunk_flags);
+ if (sctp_handle_nat_missing_state(stcb, net)) {
+ return (0);
+ }
+ break;
+ case SCTP_CAUSE_STALE_COOKIE:
+ /*
+ * We only act if we have echoed a cookie and are
+ * waiting.
+ */
+ if ((cause_length >= sizeof(struct sctp_error_stale_cookie)) &&
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ struct timeval now;
+ struct sctp_error_stale_cookie *stale_cookie;
+ uint64_t stale_time;
+
+ asoc->stale_cookie_count++;
+ if (asoc->stale_cookie_count > asoc->max_init_times) {
+ sctp_abort_notification(stcb, false, true, 0, NULL, SCTP_SO_NOT_LOCKED);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_12);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (-1);
+ }
+ stale_cookie = (struct sctp_error_stale_cookie *)cause;
+ stale_time = ntohl(stale_cookie->stale_time);
+ if (stale_time == 0) {
+ /* Use an RTT as an approximation. */
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ timevalsub(&now, &asoc->time_entered);
+ stale_time = (uint64_t)1000000 * (uint64_t)now.tv_sec + (uint64_t)now.tv_usec;
+ if (stale_time == 0) {
+ stale_time = 1;
+ }
+ }
+ /*
+ * stale_time is in usec, convert it to msec.
+ * Round upwards, to ensure that it is non-zero.
+ */
+ stale_time = (stale_time + 999) / 1000;
+ /* Double it, to be more robust on RTX. */
+ stale_time = 2 * stale_time;
+ asoc->cookie_preserve_req = (uint32_t)stale_time;
+ if (asoc->overall_error_count == 0) {
+ sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered,
+ SCTP_RTT_FROM_NON_DATA);
+ }
+ asoc->overall_error_count = 0;
+ /* Blast back to INIT state */
+ sctp_toss_old_cookies(stcb, &stcb->asoc);
+ sctp_stop_all_cookie_timers(stcb);
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered);
+ sctp_send_initiate(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ }
+ break;
+ case SCTP_CAUSE_UNRESOLVABLE_ADDR:
+ /*
+ * Nothing we can do here, we don't do hostname
+ * addresses so if the peer does not like my IPv6
+ * (or IPv4 for that matter) it does not matter. If
+ * they don't support that type of address, they can
+ * NOT possibly get that packet type... i.e. with no
+ * IPv6 you can't receive a IPv6 packet. so we can
+ * safely ignore this one. If we ever added support
+ * for HOSTNAME Addresses, then we would need to do
+ * something here.
+ */
+ break;
+ case SCTP_CAUSE_UNRECOG_CHUNK:
+ if (cause_length >= sizeof(struct sctp_error_unrecognized_chunk)) {
+ struct sctp_error_unrecognized_chunk *unrec_chunk;
+
+ unrec_chunk = (struct sctp_error_unrecognized_chunk *)cause;
+ sctp_process_unrecog_chunk(stcb, unrec_chunk->ch.chunk_type);
+ }
+ break;
+ case SCTP_CAUSE_UNRECOG_PARAM:
+ /* XXX: We only consider the first parameter */
+ if (cause_length >= sizeof(struct sctp_error_cause) + sizeof(struct sctp_paramhdr)) {
+ struct sctp_paramhdr *unrec_parameter;
+
+ unrec_parameter = (struct sctp_paramhdr *)(cause + 1);
+ sctp_process_unrecog_param(stcb, ntohs(unrec_parameter->param_type));
+ }
+ break;
+ case SCTP_CAUSE_COOKIE_IN_SHUTDOWN:
+ /*
+ * We ignore this since the timer will drive out a
+ * new cookie anyway and there timer will drive us
+ * to send a SHUTDOWN_COMPLETE. We can't send one
+ * here since we don't have their tag.
+ */
+ break;
+ case SCTP_CAUSE_DELETING_LAST_ADDR:
+ case SCTP_CAUSE_RESOURCE_SHORTAGE:
+ case SCTP_CAUSE_DELETING_SRC_ADDR:
+ /*
+ * We should NOT get these here, but in a
+ * ASCONF-ACK.
+ */
+ SCTPDBG(SCTP_DEBUG_INPUT2, "Peer sends ASCONF errors in a error cause with code %u.\n",
+ cause_code);
+ break;
+ case SCTP_CAUSE_OUT_OF_RESC:
+ /*
+ * And what, pray tell do we do with the fact that
+ * the peer is out of resources? Not really sure we
+ * could do anything but abort. I suspect this
+ * should have came WITH an abort instead of in a
+ * OP-ERROR.
+ */
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_INPUT1, "sctp_handle_error: unknown code 0x%x\n",
+ cause_code);
+ break;
+ }
+ adjust = SCTP_SIZE32(cause_length);
+ if (remaining_length >= adjust) {
+ remaining_length -= adjust;
+ } else {
+ remaining_length = 0;
+ }
+ cause = (struct sctp_error_cause *)((caddr_t)cause + adjust);
+ }
+ sctp_ulp_notify(SCTP_NOTIFY_REMOTE_ERROR, stcb, code, ch, SCTP_SO_NOT_LOCKED);
+ return (0);
+}
+
+static int
+sctp_handle_init_ack(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst, struct sctphdr *sh,
+ struct sctp_init_ack_chunk *cp, struct sctp_tcb *stcb,
+ struct sctp_nets *net, int *abort_no_unlock,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id)
+{
+ struct sctp_init_ack *init_ack;
+ struct mbuf *op_err;
+
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_init_ack: handling INIT-ACK\n");
+
+ if (stcb == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_init_ack: TCB is null\n");
+ return (-1);
+ }
+ /* Only process the INIT-ACK chunk in COOKIE WAIT state.*/
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) {
+ init_ack = &cp->init;
+ /* Validate parameters. */
+ if ((ntohl(init_ack->initiate_tag) == 0) ||
+ (ntohl(init_ack->a_rwnd) < SCTP_MIN_RWND) ||
+ (ntohs(init_ack->num_inbound_streams) == 0) ||
+ (ntohs(init_ack->num_outbound_streams) == 0)) {
+ /* One of the mandatory parameters is illegal. */
+ op_err = sctp_generate_cause(SCTP_CAUSE_INVALID_PARAM, "");
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+ #if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+ #endif
+ vrf_id, net->port);
+ *abort_no_unlock = 1;
+ return (-1);
+ }
+ if (stcb->asoc.primary_destination->dest_state &
+ SCTP_ADDR_UNCONFIRMED) {
+ /*
+ * The primary is where we sent the INIT, we can
+ * always consider it confirmed when the INIT-ACK is
+ * returned. Do this before we load addresses
+ * though.
+ */
+ stcb->asoc.primary_destination->dest_state &=
+ ~SCTP_ADDR_UNCONFIRMED;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED,
+ stcb, 0, (void *)stcb->asoc.primary_destination, SCTP_SO_NOT_LOCKED);
+ }
+ if (sctp_process_init_ack(m, iphlen, offset, src, dst, sh, cp, stcb,
+ net, abort_no_unlock,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id) < 0) {
+ /* error in parsing parameters */
+ return (-1);
+ }
+ /* Update our state. */
+ SCTPDBG(SCTP_DEBUG_INPUT2, "moving to COOKIE-ECHOED state\n");
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_ECHOED);
+
+ /* Reset the RTO calculation. */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INPUT,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+ /*
+ * Collapse the init timer back in case of a exponential
+ * backoff.
+ */
+ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, stcb->sctp_ep,
+ stcb, net);
+ /*
+ * The output routine at the end of the inbound data processing
+ * will cause the cookie to be sent.
+ */
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Leaving handle-init-ack end\n");
+ return (0);
+ } else {
+ return (-1);
+ }
+}
+
+static struct sctp_tcb *
+sctp_process_cookie_new(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_state_cookie *cookie, int cookie_len,
+ struct sctp_inpcb *inp, struct sctp_nets **netp,
+ struct sockaddr *init_src, int *notification,
+ int auth_skipped, uint32_t auth_offset, uint32_t auth_len,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port);
+
+/*
+ * handle a state cookie for an existing association m: input packet mbuf
+ * chain-- assumes a pullup on IP/SCTP/COOKIE-ECHO chunk note: this is a
+ * "split" mbuf and the cookie signature does not exist offset: offset into
+ * mbuf to the cookie-echo chunk
+ */
+static struct sctp_tcb *
+sctp_process_cookie_existing(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_state_cookie *cookie, int cookie_len,
+ struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets **netp,
+ struct sockaddr *init_src, int *notification,
+ int auth_skipped, uint32_t auth_offset, uint32_t auth_len,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_association *asoc;
+ struct sctp_init_chunk *init_cp, init_buf;
+ struct sctp_init_ack_chunk *initack_cp, initack_buf;
+ struct sctp_asconf_addr *aparam, *naparam;
+ struct sctp_asconf_ack *aack, *naack;
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_stream_reset_list *strrst, *nstrrst;
+ struct sctp_queued_to_read *sq, *nsq;
+ struct sctp_nets *net;
+ struct mbuf *op_err;
+ int init_offset, initack_offset, i;
+ int retval;
+ int spec_flag = 0;
+ uint32_t how_indx;
+#if defined(SCTP_DETAILED_STR_STATS)
+ int j;
+#endif
+
+ net = *netp;
+ /* I know that the TCB is non-NULL from the caller */
+ asoc = &stcb->asoc;
+ for (how_indx = 0; how_indx < sizeof(asoc->cookie_how); how_indx++) {
+ if (asoc->cookie_how[how_indx] == 0)
+ break;
+ }
+ if (how_indx < sizeof(asoc->cookie_how)) {
+ asoc->cookie_how[how_indx] = 1;
+ }
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_ACK_SENT) {
+ /* SHUTDOWN came in after sending INIT-ACK */
+ sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination);
+ op_err = sctp_generate_cause(SCTP_CAUSE_COOKIE_IN_SHUTDOWN, "");
+ sctp_send_operr_to(src, dst, sh, cookie->peers_vtag, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, net->port);
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 2;
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ /*
+ * find and validate the INIT chunk in the cookie (peer's info) the
+ * INIT should start after the cookie-echo header struct (chunk
+ * header, state cookie header struct)
+ */
+ init_offset = offset += sizeof(struct sctp_cookie_echo_chunk);
+
+ init_cp = (struct sctp_init_chunk *)
+ sctp_m_getptr(m, init_offset, sizeof(struct sctp_init_chunk),
+ (uint8_t *) & init_buf);
+ if (init_cp == NULL) {
+ /* could not pull a INIT chunk in cookie */
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ if (init_cp->ch.chunk_type != SCTP_INITIATION) {
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ /*
+ * find and validate the INIT-ACK chunk in the cookie (my info) the
+ * INIT-ACK follows the INIT chunk
+ */
+ initack_offset = init_offset + SCTP_SIZE32(ntohs(init_cp->ch.chunk_length));
+ initack_cp = (struct sctp_init_ack_chunk *)
+ sctp_m_getptr(m, initack_offset, sizeof(struct sctp_init_ack_chunk),
+ (uint8_t *) & initack_buf);
+ if (initack_cp == NULL) {
+ /* could not pull INIT-ACK chunk in cookie */
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ if (initack_cp->ch.chunk_type != SCTP_INITIATION_ACK) {
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ if ((ntohl(initack_cp->init.initiate_tag) == asoc->my_vtag) &&
+ (ntohl(init_cp->init.initiate_tag) == asoc->peer_vtag)) {
+ /*
+ * case D in Section 5.2.4 Table 2: MMAA process accordingly
+ * to get into the OPEN state
+ */
+ if (ntohl(initack_cp->init.initial_tsn) != asoc->init_seq_number) {
+ /*-
+ * Opps, this means that we somehow generated two vtag's
+ * the same. I.e. we did:
+ * Us Peer
+ * <---INIT(tag=a)------
+ * ----INIT-ACK(tag=t)-->
+ * ----INIT(tag=t)------> *1
+ * <---INIT-ACK(tag=a)---
+ * <----CE(tag=t)------------- *2
+ *
+ * At point *1 we should be generating a different
+ * tag t'. Which means we would throw away the CE and send
+ * ours instead. Basically this is case C (throw away side).
+ */
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 17;
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ switch (SCTP_GET_STATE(stcb)) {
+ case SCTP_STATE_COOKIE_WAIT:
+ case SCTP_STATE_COOKIE_ECHOED:
+ /*
+ * INIT was sent but got a COOKIE_ECHO with the
+ * correct tags... just accept it...but we must
+ * process the init so that we can make sure we
+ * have the right seq no's.
+ */
+ /* First we must process the INIT !! */
+ if (sctp_process_init(init_cp, stcb) < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 3;
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ SCTPDBG(SCTP_DEBUG_INPUT1, "sctp_process_init() failed\n");
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ return (NULL);
+ }
+ /* we have already processed the INIT so no problem */
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp,
+ stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_13);
+ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp,
+ stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_14);
+ /* update current state */
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)
+ SCTP_STAT_INCR_COUNTER32(sctps_activeestab);
+ else
+ SCTP_STAT_INCR_COUNTER32(sctps_collisionestab);
+
+ SCTP_SET_STATE(stcb, SCTP_STATE_OPEN);
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+ sctp_stop_all_cookie_timers(stcb);
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) &&
+ (!SCTP_IS_LISTENING(inp))) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+ /*
+ * Here is where collision would go if we
+ * did a connect() and instead got a
+ * init/init-ack/cookie done before the
+ * init-ack came back..
+ */
+ sctp_pcb_add_flags(stcb->sctp_ep, SCTP_PCB_FLAGS_CONNECTED);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return (NULL);
+ }
+#endif
+ soisconnected(stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ /* notify upper layer */
+ *notification = SCTP_NOTIFY_ASSOC_UP;
+ net->hb_responded = 1;
+ if (stcb->asoc.sctp_autoclose_ticks &&
+ (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE))) {
+ sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE,
+ inp, stcb, NULL);
+ }
+ break;
+ default:
+ /*
+ * we're in the OPEN state (or beyond), so
+ * peer must have simply lost the COOKIE-ACK
+ */
+ break;
+ } /* end switch */
+ sctp_stop_all_cookie_timers(stcb);
+ if ((retval = sctp_load_addresses_from_init(stcb, m,
+ init_offset + sizeof(struct sctp_init_chunk),
+ initack_offset, src, dst, init_src, stcb->asoc.port)) < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 4;
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Problem with address parameters");
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "Load addresses from INIT causes an abort %d\n",
+ retval);
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ return (NULL);
+ }
+ /* respond with a COOKIE-ACK */
+ sctp_toss_old_cookies(stcb, asoc);
+ sctp_send_cookie_ack(stcb);
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 5;
+ return (stcb);
+ }
+
+ if (ntohl(initack_cp->init.initiate_tag) != asoc->my_vtag &&
+ ntohl(init_cp->init.initiate_tag) == asoc->peer_vtag &&
+ cookie->tie_tag_my_vtag == 0 &&
+ cookie->tie_tag_peer_vtag == 0) {
+ /*
+ * case C in Section 5.2.4 Table 2: XMOO silently discard
+ */
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 6;
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ /* If nat support, and the below and stcb is established,
+ * send back a ABORT(colliding state) if we are established.
+ */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) &&
+ (asoc->peer_supports_nat) &&
+ ((ntohl(initack_cp->init.initiate_tag) == asoc->my_vtag) &&
+ ((ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag) ||
+ (asoc->peer_vtag == 0)))) {
+ /* Special case - Peer's support nat. We may have
+ * two init's that we gave out the same tag on since
+ * one was not established.. i.e. we get INIT from host-1
+ * behind the nat and we respond tag-a, we get a INIT from
+ * host-2 behind the nat and we get tag-a again. Then we
+ * bring up host-1 (or 2's) assoc, Then comes the cookie
+ * from hsot-2 (or 1). Now we have colliding state. We must
+ * send an abort here with colliding state indication.
+ */
+ op_err = sctp_generate_cause(SCTP_CAUSE_NAT_COLLIDING_STATE, "");
+ sctp_send_abort(m, iphlen, src, dst, sh, 0, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ if ((ntohl(initack_cp->init.initiate_tag) == asoc->my_vtag) &&
+ ((ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag) ||
+ (asoc->peer_vtag == 0))) {
+ /*
+ * case B in Section 5.2.4 Table 2: MXAA or MOAA my info
+ * should be ok, re-accept peer info
+ */
+ if (ntohl(initack_cp->init.initial_tsn) != asoc->init_seq_number) {
+ /* Extension of case C.
+ * If we hit this, then the random number
+ * generator returned the same vtag when we
+ * first sent our INIT-ACK and when we later sent
+ * our INIT. The side with the seq numbers that are
+ * different will be the one that normally would
+ * have hit case C. This in effect "extends" our vtags
+ * in this collision case to be 64 bits. The same collision
+ * could occur aka you get both vtag and seq number the
+ * same twice in a row.. but is much less likely. If it
+ * did happen then we would proceed through and bring
+ * up the assoc.. we may end up with the wrong stream
+ * setup however.. which would be bad.. but there is
+ * no way to tell.. until we send on a stream that does
+ * not exist :-)
+ */
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 7;
+
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+ }
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 8;
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_15);
+ sctp_stop_all_cookie_timers(stcb);
+ /*
+ * since we did not send a HB make sure we don't double
+ * things
+ */
+ net->hb_responded = 1;
+ if (stcb->asoc.sctp_autoclose_ticks &&
+ sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb,
+ NULL);
+ }
+ asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd);
+ if (asoc->pre_open_streams < asoc->streamoutcnt) {
+ asoc->pre_open_streams = asoc->streamoutcnt;
+ }
+
+ if (ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag) {
+ /* Ok the peer probably discarded our
+ * data (if we echoed a cookie+data). So anything
+ * on the sent_queue should be marked for
+ * retransmit, we may not get something to
+ * kick us so it COULD still take a timeout
+ * to move these.. but it can't hurt to mark them.
+ */
+
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ sctp_flight_size_decrease(chk);
+ sctp_total_flight_decrease(stcb, chk);
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ spec_flag++;
+ }
+ }
+ }
+ /* process the INIT info (peer's info) */
+ if (sctp_process_init(init_cp, stcb) < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 9;
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ SCTPDBG(SCTP_DEBUG_INPUT1, "sctp_process_init() failed\n");
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ return (NULL);
+ }
+ if ((retval = sctp_load_addresses_from_init(stcb, m,
+ init_offset + sizeof(struct sctp_init_chunk),
+ initack_offset, src, dst, init_src, stcb->asoc.port)) < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 10;
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Problem with address parameters");
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "Load addresses from INIT causes an abort %d\n",
+ retval);
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ return (NULL);
+ }
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ *notification = SCTP_NOTIFY_ASSOC_UP;
+
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) &&
+ (!SCTP_IS_LISTENING(inp))) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+ sctp_pcb_add_flags(stcb->sctp_ep, SCTP_PCB_FLAGS_CONNECTED);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return (NULL);
+ }
+#endif
+ soisconnected(stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)
+ SCTP_STAT_INCR_COUNTER32(sctps_activeestab);
+ else
+ SCTP_STAT_INCR_COUNTER32(sctps_collisionestab);
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+ } else if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+ SCTP_STAT_INCR_COUNTER32(sctps_restartestab);
+ } else {
+ SCTP_STAT_INCR_COUNTER32(sctps_collisionestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_OPEN);
+ sctp_stop_all_cookie_timers(stcb);
+ sctp_toss_old_cookies(stcb, asoc);
+ sctp_send_cookie_ack(stcb);
+ if (spec_flag) {
+ /* only if we have retrans set do we do this. What
+ * this call does is get only the COOKIE-ACK out
+ * and then when we return the normal call to
+ * sctp_chunk_output will get the retrans out
+ * behind this.
+ */
+ sctp_chunk_output(inp,stcb, SCTP_OUTPUT_FROM_COOKIE_ACK, SCTP_SO_NOT_LOCKED);
+ }
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 11;
+
+ return (stcb);
+ }
+ if ((ntohl(initack_cp->init.initiate_tag) != asoc->my_vtag &&
+ ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag) &&
+ cookie->tie_tag_my_vtag == asoc->my_vtag_nonce &&
+ cookie->tie_tag_peer_vtag == asoc->peer_vtag_nonce &&
+ cookie->tie_tag_peer_vtag != 0) {
+ struct sctpasochead *head;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ if (asoc->peer_supports_nat) {
+ struct sctp_tcb *local_stcb;
+
+ /* This is a gross gross hack.
+ * Just call the cookie_new code since we
+ * are allowing a duplicate association.
+ * I hope this works...
+ */
+ local_stcb = sctp_process_cookie_new(m, iphlen, offset, src, dst,
+ sh, cookie, cookie_len,
+ inp, netp, init_src,notification,
+ auth_skipped, auth_offset, auth_len,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (local_stcb == NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return (local_stcb);
+ }
+ /*
+ * case A in Section 5.2.4 Table 2: XXMM (peer restarted)
+ */
+ /* temp code */
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 12;
+ sctp_stop_association_timers(stcb, false);
+ /* notify upper layer */
+ *notification = SCTP_NOTIFY_ASSOC_RESTART;
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_OPEN) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT)) {
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+ }
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+ SCTP_STAT_INCR_GAUGE32(sctps_restartestab);
+ } else if (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) {
+ SCTP_STAT_INCR_GAUGE32(sctps_collisionestab);
+ }
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ SCTP_SET_STATE(stcb, SCTP_STATE_OPEN);
+
+ } else if (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) {
+ /* move to OPEN state, if not in SHUTDOWN_SENT */
+ SCTP_SET_STATE(stcb, SCTP_STATE_OPEN);
+ }
+ if (asoc->pre_open_streams < asoc->streamoutcnt) {
+ asoc->pre_open_streams = asoc->streamoutcnt;
+ }
+ asoc->init_seq_number = ntohl(initack_cp->init.initial_tsn);
+ asoc->sending_seq = asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number;
+ asoc->asconf_seq_out_acked = asoc->asconf_seq_out - 1;
+ asoc->asconf_seq_in = asoc->last_acked_seq = asoc->init_seq_number - 1;
+ asoc->str_reset_seq_in = asoc->init_seq_number;
+ asoc->advanced_peer_ack_point = asoc->last_acked_seq;
+ asoc->send_sack = 1;
+ asoc->data_pkts_seen = 0;
+ asoc->last_data_chunk_from = NULL;
+ asoc->last_control_chunk_from = NULL;
+ asoc->last_net_cmt_send_started = NULL;
+ if (asoc->mapping_array) {
+ memset(asoc->mapping_array, 0,
+ asoc->mapping_array_size);
+ }
+ if (asoc->nr_mapping_array) {
+ memset(asoc->nr_mapping_array, 0,
+ asoc->mapping_array_size);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(stcb->sctp_ep);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ /* send up all the data */
+ sctp_report_all_outbound(stcb, 0, SCTP_SO_LOCKED);
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ stcb->asoc.strmout[i].chunks_on_queues = 0;
+#if defined(SCTP_DETAILED_STR_STATS)
+ for (j = 0; j < SCTP_PR_SCTP_MAX + 1; j++) {
+ asoc->strmout[i].abandoned_sent[j] = 0;
+ asoc->strmout[i].abandoned_unsent[j] = 0;
+ }
+#else
+ asoc->strmout[i].abandoned_sent[0] = 0;
+ asoc->strmout[i].abandoned_unsent[0] = 0;
+#endif
+ stcb->asoc.strmout[i].next_mid_ordered = 0;
+ stcb->asoc.strmout[i].next_mid_unordered = 0;
+ stcb->asoc.strmout[i].sid = i;
+ stcb->asoc.strmout[i].last_msg_incomplete = 0;
+ }
+ TAILQ_FOREACH_SAFE(strrst, &asoc->resetHead, next_resp, nstrrst) {
+ TAILQ_REMOVE(&asoc->resetHead, strrst, next_resp);
+ SCTP_FREE(strrst, SCTP_M_STRESET);
+ }
+ TAILQ_FOREACH_SAFE(sq, &asoc->pending_reply_queue, next, nsq) {
+ TAILQ_REMOVE(&asoc->pending_reply_queue, sq, next);
+ if (sq->data) {
+ sctp_m_freem(sq->data);
+ sq->data = NULL;
+ }
+ sctp_free_remote_addr(sq->whoFrom);
+ sq->whoFrom = NULL;
+ sq->stcb = NULL;
+ sctp_free_a_readq(stcb, sq);
+ }
+ TAILQ_FOREACH_SAFE(chk, &asoc->control_send_queue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ sctp_free_remote_addr(chk->whoTo);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ }
+ asoc->ctrl_queue_cnt = 0;
+ asoc->str_reset = NULL;
+ asoc->stream_reset_outstanding = 0;
+ TAILQ_FOREACH_SAFE(chk, &asoc->asconf_send_queue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->asconf_send_queue, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ sctp_free_remote_addr(chk->whoTo);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ }
+ TAILQ_FOREACH_SAFE(aparam, &asoc->asconf_queue, next, naparam) {
+ TAILQ_REMOVE(&asoc->asconf_queue, aparam, next);
+ SCTP_FREE(aparam,SCTP_M_ASC_ADDR);
+ }
+ TAILQ_FOREACH_SAFE(aack, &asoc->asconf_ack_sent, next, naack) {
+ TAILQ_REMOVE(&asoc->asconf_ack_sent, aack, next);
+ if (aack->data != NULL) {
+ sctp_m_freem(aack->data);
+ }
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asconf_ack), aack);
+ }
+ asoc->rcv_edmid = cookie->rcv_edmid;
+
+ /* process the INIT-ACK info (my info) */
+ asoc->my_vtag = ntohl(initack_cp->init.initiate_tag);
+ asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd);
+
+ /* pull from vtag hash */
+ LIST_REMOVE(stcb, sctp_asocs);
+ /* re-insert to new vtag position */
+ head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag,
+ SCTP_BASE_INFO(hashasocmark))];
+ /*
+ * put it in the bucket in the vtag hash of assoc's for the
+ * system
+ */
+ LIST_INSERT_HEAD(head, stcb, sctp_asocs);
+
+ SCTP_INP_WUNLOCK(stcb->sctp_ep);
+ SCTP_INP_INFO_WUNLOCK();
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ asoc->total_flight = 0;
+ asoc->total_flight_count = 0;
+ /* process the INIT info (peer's info) */
+ if (sctp_process_init(init_cp, stcb) < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 13;
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ SCTPDBG(SCTP_DEBUG_INPUT1, "sctp_process_init() failed\n");
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ return (NULL);
+ }
+ /*
+ * since we did not send a HB make sure we don't double
+ * things
+ */
+ net->hb_responded = 1;
+
+ if ((retval = sctp_load_addresses_from_init(stcb, m,
+ init_offset + sizeof(struct sctp_init_chunk),
+ initack_offset, src, dst, init_src, stcb->asoc.port)) < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 14;
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Problem with address parameters");
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "Load addresses from INIT causes an abort %d\n",
+ retval);
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ return (NULL);
+ }
+ /* respond with a COOKIE-ACK */
+ sctp_send_cookie_ack(stcb);
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 15;
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE) &&
+ (asoc->sctp_autoclose_ticks > 0)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL);
+ }
+ return (stcb);
+ }
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 16;
+ /* all other cases... */
+ SCTP_TCB_UNLOCK(stcb);
+ return (NULL);
+}
+
+/*
+ * handle a state cookie for a new association m: input packet mbuf chain--
+ * assumes a pullup on IP/SCTP/COOKIE-ECHO chunk note: this is a "split" mbuf
+ * and the cookie signature does not exist offset: offset into mbuf to the
+ * cookie-echo chunk length: length of the cookie chunk to: where the init
+ * was from returns a new TCB
+ */
+static struct sctp_tcb *
+sctp_process_cookie_new(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_state_cookie *cookie, int cookie_len,
+ struct sctp_inpcb *inp, struct sctp_nets **netp,
+ struct sockaddr *init_src, int *notification,
+ int auth_skipped, uint32_t auth_offset, uint32_t auth_len,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_tcb *stcb;
+ struct sctp_init_chunk *init_cp, init_buf;
+ struct sctp_init_ack_chunk *initack_cp, initack_buf;
+ union sctp_sockstore store;
+ struct sctp_association *asoc;
+ int init_offset, initack_offset, initack_limit;
+ int error = 0;
+ uint8_t auth_chunk_buf[SCTP_CHUNK_BUFFER_SIZE];
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(inp);
+#endif
+
+ /*
+ * find and validate the INIT chunk in the cookie (peer's info) the
+ * INIT should start after the cookie-echo header struct (chunk
+ * header, state cookie header struct)
+ */
+ init_offset = offset + sizeof(struct sctp_cookie_echo_chunk);
+ init_cp = (struct sctp_init_chunk *)
+ sctp_m_getptr(m, init_offset, sizeof(struct sctp_init_chunk),
+ (uint8_t *) & init_buf);
+ if (init_cp == NULL) {
+ /* could not pull a INIT chunk in cookie */
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "process_cookie_new: could not pull INIT chunk hdr\n");
+ return (NULL);
+ }
+ if (init_cp->ch.chunk_type != SCTP_INITIATION) {
+ SCTPDBG(SCTP_DEBUG_INPUT1, "HUH? process_cookie_new: could not find INIT chunk!\n");
+ return (NULL);
+ }
+ initack_offset = init_offset + SCTP_SIZE32(ntohs(init_cp->ch.chunk_length));
+ /*
+ * find and validate the INIT-ACK chunk in the cookie (my info) the
+ * INIT-ACK follows the INIT chunk
+ */
+ initack_cp = (struct sctp_init_ack_chunk *)
+ sctp_m_getptr(m, initack_offset, sizeof(struct sctp_init_ack_chunk),
+ (uint8_t *) & initack_buf);
+ if (initack_cp == NULL) {
+ /* could not pull INIT-ACK chunk in cookie */
+ SCTPDBG(SCTP_DEBUG_INPUT1, "process_cookie_new: could not pull INIT-ACK chunk hdr\n");
+ return (NULL);
+ }
+ if (initack_cp->ch.chunk_type != SCTP_INITIATION_ACK) {
+ return (NULL);
+ }
+ /*
+ * NOTE: We can't use the INIT_ACK's chk_length to determine the
+ * "initack_limit" value. This is because the chk_length field
+ * includes the length of the cookie, but the cookie is omitted when
+ * the INIT and INIT_ACK are tacked onto the cookie...
+ */
+ initack_limit = offset + cookie_len;
+
+ /*
+ * now that we know the INIT/INIT-ACK are in place, create a new TCB
+ * and populate
+ */
+
+ /*
+ * Here we do a trick, we set in NULL for the proc/thread argument. We
+ * do this since in effect we only use the p argument when
+ * the socket is unbound and we must do an implicit bind.
+ * Since we are getting a cookie, we cannot be unbound.
+ */
+ stcb = sctp_aloc_assoc(inp, init_src, &error,
+ ntohl(initack_cp->init.initiate_tag),
+ ntohl(initack_cp->init.initial_tsn), vrf_id,
+ ntohs(initack_cp->init.num_outbound_streams),
+ port,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ (struct thread *)NULL,
+#elif defined(_WIN32) && !defined(__Userspace__)
+ (PKTHREAD)NULL,
+#else
+ (struct proc *)NULL,
+#endif
+ SCTP_DONT_INITIALIZE_AUTH_PARAMS);
+ if (stcb == NULL) {
+ struct mbuf *op_err;
+
+ /* memory problem? */
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "process_cookie_new: no room for another TCB!\n");
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ sctp_abort_association(inp, (struct sctp_tcb *)NULL, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ return (NULL);
+ }
+ asoc = &stcb->asoc;
+ /* get scope variables out of cookie */
+ asoc->scope.ipv4_local_scope = cookie->ipv4_scope;
+ asoc->scope.site_scope = cookie->site_scope;
+ asoc->scope.local_scope = cookie->local_scope;
+ asoc->scope.loopback_scope = cookie->loopback_scope;
+
+#if defined(__Userspace__)
+ if ((asoc->scope.ipv4_addr_legal != cookie->ipv4_addr_legal) ||
+ (asoc->scope.ipv6_addr_legal != cookie->ipv6_addr_legal) ||
+ (asoc->scope.conn_addr_legal != cookie->conn_addr_legal)) {
+#else
+ if ((asoc->scope.ipv4_addr_legal != cookie->ipv4_addr_legal) ||
+ (asoc->scope.ipv6_addr_legal != cookie->ipv6_addr_legal)) {
+#endif
+ struct mbuf *op_err;
+
+ /*
+ * Houston we have a problem. The EP changed while the
+ * cookie was in flight. Only recourse is to abort the
+ * association.
+ */
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ sctp_abort_association(inp, (struct sctp_tcb *)NULL, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_18);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (NULL);
+ }
+ asoc->rcv_edmid = cookie->rcv_edmid;
+ /* process the INIT-ACK info (my info) */
+ asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd);
+
+ /* process the INIT info (peer's info) */
+ if (sctp_process_init(init_cp, stcb) < 0) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_19);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (NULL);
+ }
+ /* load all addresses */
+ if (sctp_load_addresses_from_init(stcb, m,
+ init_offset + sizeof(struct sctp_init_chunk),
+ initack_offset, src, dst, init_src, port) < 0) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_20);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (NULL);
+ }
+ /*
+ * verify any preceding AUTH chunk that was skipped
+ */
+ /* pull the local authentication parameters from the cookie/init-ack */
+ sctp_auth_get_cookie_params(stcb, m,
+ initack_offset + sizeof(struct sctp_init_ack_chunk),
+ initack_limit - (initack_offset + sizeof(struct sctp_init_ack_chunk)));
+ if (auth_skipped) {
+ struct sctp_auth_chunk *auth;
+
+ if (auth_len <= SCTP_CHUNK_BUFFER_SIZE) {
+ auth = (struct sctp_auth_chunk *)sctp_m_getptr(m, auth_offset, auth_len, auth_chunk_buf);
+ } else {
+ auth = NULL;
+ }
+ if ((auth == NULL) || sctp_handle_auth(stcb, auth, m, auth_offset)) {
+ /* auth HMAC failed, dump the assoc and packet */
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "COOKIE-ECHO: AUTH failed\n");
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_21);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (NULL);
+ } else {
+ /* remaining chunks checked... good to go */
+ stcb->asoc.authenticated = 1;
+ }
+ }
+
+ /*
+ * if we're doing ASCONFs, check to see if we have any new local
+ * addresses that need to get added to the peer (eg. addresses
+ * changed while cookie echo in flight). This needs to be done
+ * after we go to the OPEN state to do the correct asconf
+ * processing. else, make sure we have the correct addresses in our
+ * lists
+ */
+
+ /* warning, we re-use sin, sin6, sa_store here! */
+ /* pull in local_address (our "from" address) */
+ switch (cookie->laddr_type) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ /* source addr is IPv4 */
+ memset(&store.sin, 0, sizeof(struct sockaddr_in));
+ store.sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ store.sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+ store.sin.sin_addr.s_addr = cookie->laddress[0];
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ /* source addr is IPv6 */
+ memset(&store.sin6, 0, sizeof(struct sockaddr_in6));
+ store.sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ store.sin6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ store.sin6.sin6_scope_id = cookie->scope_id;
+ memcpy(&store.sin6.sin6_addr, cookie->laddress, sizeof(struct in6_addr));
+ break;
+#endif
+#if defined(__Userspace__)
+ case SCTP_CONN_ADDRESS:
+ /* source addr is conn */
+ memset(&store.sconn, 0, sizeof(struct sockaddr_conn));
+ store.sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+ store.sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ memcpy(&store.sconn.sconn_addr, cookie->laddress, sizeof(void *));
+ break;
+#endif
+ default:
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_22);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (NULL);
+ }
+
+ /* update current state */
+ SCTPDBG(SCTP_DEBUG_INPUT2, "moving to OPEN state\n");
+ SCTP_SET_STATE(stcb, SCTP_STATE_OPEN);
+ sctp_stop_all_cookie_timers(stcb);
+ SCTP_STAT_INCR_COUNTER32(sctps_passiveestab);
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+
+ /* set up to notify upper layer */
+ *notification = SCTP_NOTIFY_ASSOC_UP;
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) &&
+ (!SCTP_IS_LISTENING(inp))) {
+ /*
+ * This is an endpoint that called connect() how it got a
+ * cookie that is NEW is a bit of a mystery. It must be that
+ * the INIT was sent, but before it got there.. a complete
+ * INIT/INIT-ACK/COOKIE arrived. But of course then it
+ * should have went to the other code.. not here.. oh well..
+ * a bit of protection is worth having..
+ *
+ * XXXMJ unlocked
+ */
+ sctp_pcb_add_flags(stcb->sctp_ep, SCTP_PCB_FLAGS_CONNECTED);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return (NULL);
+ }
+#endif
+ soisconnected(stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ } else if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (SCTP_IS_LISTENING(inp))) {
+ /*
+ * We don't want to do anything with this one. Since it is
+ * the listening guy. The timer will get started for
+ * accepted connections in the caller.
+ */
+ ;
+ }
+ if (stcb->asoc.sctp_autoclose_ticks &&
+ sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL);
+ }
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+ *netp = sctp_findnet(stcb, init_src);
+ if (*netp != NULL) {
+ /*
+ * Since we did not send a HB, make sure we don't double
+ * things.
+ */
+ (*netp)->hb_responded = 1;
+ }
+ /* respond with a COOKIE-ACK */
+ sctp_send_cookie_ack(stcb);
+
+ /*
+ * check the address lists for any ASCONFs that need to be sent
+ * AFTER the cookie-ack is sent
+ */
+ sctp_check_address_list(stcb, m,
+ initack_offset + sizeof(struct sctp_init_ack_chunk),
+ initack_limit - (initack_offset + sizeof(struct sctp_init_ack_chunk)),
+ &store.sa, cookie->local_scope, cookie->site_scope,
+ cookie->ipv4_scope, cookie->loopback_scope);
+
+ return (stcb);
+}
+
+/*
+ * CODE LIKE THIS NEEDS TO RUN IF the peer supports the NAT extension, i.e
+ * we NEED to make sure we are not already using the vtag. If so we
+ * need to send back an ABORT-TRY-AGAIN-WITH-NEW-TAG No middle box bit!
+ head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(tag,
+ SCTP_BASE_INFO(hashasocmark))];
+ LIST_FOREACH(stcb, head, sctp_asocs) {
+ if ((stcb->asoc.my_vtag == tag) && (stcb->rport == rport) && (inp == stcb->sctp_ep)) {
+ -- SEND ABORT - TRY AGAIN --
+ }
+ }
+*/
+
+/*
+ * handles a COOKIE-ECHO message stcb: modified to either a new or left as
+ * existing (non-NULL) TCB
+ */
+static struct mbuf *
+sctp_handle_cookie_echo(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_cookie_echo_chunk *cp,
+ struct sctp_inpcb **inp_p, struct sctp_tcb **stcb, struct sctp_nets **netp,
+ int auth_skipped, uint32_t auth_offset, uint32_t auth_len,
+ struct sctp_tcb **locked_tcb,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_state_cookie *cookie;
+ struct sctp_tcb *l_stcb = *stcb;
+ struct sctp_inpcb *l_inp;
+ struct sockaddr *to;
+ struct sctp_pcb *ep;
+ struct mbuf *m_sig;
+ uint8_t calc_sig[SCTP_SIGNATURE_SIZE], tmp_sig[SCTP_SIGNATURE_SIZE];
+ uint8_t *sig;
+#if defined(__Userspace__) && defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ uint8_t cookie_ok = 1;
+#else
+ uint8_t cookie_ok = 0;
+#endif
+ unsigned int sig_offset, cookie_offset;
+ unsigned int cookie_len;
+ struct timeval now;
+ struct timeval time_entered, time_expires;
+ int notification = 0;
+ struct sctp_nets *netl;
+ int had_a_existing_tcb = 0;
+ int send_int_conf = 0;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+#if defined(__Userspace__)
+ struct sockaddr_conn sconn;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_cookie: handling COOKIE-ECHO\n");
+
+ if (inp_p == NULL) {
+ return (NULL);
+ }
+ cookie = &cp->cookie;
+ cookie_offset = offset + sizeof(struct sctp_chunkhdr);
+ cookie_len = ntohs(cp->ch.chunk_length);
+
+ if (cookie_len < sizeof(struct sctp_cookie_echo_chunk) +
+ sizeof(struct sctp_init_chunk) +
+ sizeof(struct sctp_init_ack_chunk) + SCTP_SIGNATURE_SIZE) {
+ /* cookie too small */
+ return (NULL);
+ }
+ if ((cookie->peerport != sh->src_port) ||
+ (cookie->myport != sh->dest_port) ||
+ (cookie->my_vtag != sh->v_tag)) {
+ /*
+ * invalid ports or bad tag. Note that we always leave the
+ * v_tag in the header in network order and when we stored
+ * it in the my_vtag slot we also left it in network order.
+ * This maintains the match even though it may be in the
+ * opposite byte order of the machine :->
+ */
+ return (NULL);
+ }
+#if defined(__Userspace__)
+ /*
+ * Recover the AF_CONN addresses within the cookie.
+ * This needs to be done in the buffer provided for later processing
+ * of the cookie and in the mbuf chain for HMAC validation.
+ */
+ if ((cookie->addr_type == SCTP_CONN_ADDRESS) && (src->sa_family == AF_CONN)) {
+ struct sockaddr_conn *sconnp = (struct sockaddr_conn *)src;
+
+ memcpy(cookie->address, &sconnp->sconn_addr , sizeof(void *));
+ m_copyback(m, cookie_offset + offsetof(struct sctp_state_cookie, address),
+ (int)sizeof(void *), (caddr_t)&sconnp->sconn_addr);
+ }
+ if ((cookie->laddr_type == SCTP_CONN_ADDRESS) && (dst->sa_family == AF_CONN)) {
+ struct sockaddr_conn *sconnp = (struct sockaddr_conn *)dst;
+
+ memcpy(cookie->laddress, &sconnp->sconn_addr , sizeof(void *));
+ m_copyback(m, cookie_offset + offsetof(struct sctp_state_cookie, laddress),
+ (int)sizeof(void *), (caddr_t)&sconnp->sconn_addr);
+ }
+#endif
+ /*
+ * split off the signature into its own mbuf (since it should not be
+ * calculated in the sctp_hmac_m() call).
+ */
+ sig_offset = offset + cookie_len - SCTP_SIGNATURE_SIZE;
+ m_sig = m_split(m, sig_offset, M_NOWAIT);
+ if (m_sig == NULL) {
+ /* out of memory or ?? */
+ return (NULL);
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(m_sig, SCTP_MBUF_SPLIT);
+ }
+#endif
+
+ /*
+ * compute the signature/digest for the cookie
+ */
+ if (l_stcb != NULL) {
+ atomic_add_int(&l_stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(l_stcb);
+ }
+ l_inp = *inp_p;
+ SCTP_INP_RLOCK(l_inp);
+ if (l_stcb != NULL) {
+ SCTP_TCB_LOCK(l_stcb);
+ atomic_subtract_int(&l_stcb->asoc.refcnt, 1);
+ }
+ if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ SCTP_INP_RUNLOCK(l_inp);
+ sctp_m_freem(m_sig);
+ return (NULL);
+ }
+ ep = &(*inp_p)->sctp_ep;
+ /* which cookie is it? */
+ if ((cookie->time_entered.tv_sec < (long)ep->time_of_secret_change) &&
+ (ep->current_secret_number != ep->last_secret_number)) {
+ /* it's the old cookie */
+ (void)sctp_hmac_m(SCTP_HMAC,
+ (uint8_t *)ep->secret_key[(int)ep->last_secret_number],
+ SCTP_SECRET_SIZE, m, cookie_offset, calc_sig, 0);
+ } else {
+ /* it's the current cookie */
+ (void)sctp_hmac_m(SCTP_HMAC,
+ (uint8_t *)ep->secret_key[(int)ep->current_secret_number],
+ SCTP_SECRET_SIZE, m, cookie_offset, calc_sig, 0);
+ }
+ /* get the signature */
+ SCTP_INP_RUNLOCK(l_inp);
+ sig = (uint8_t *) sctp_m_getptr(m_sig, 0, SCTP_SIGNATURE_SIZE, (uint8_t *) & tmp_sig);
+ if (sig == NULL) {
+ /* couldn't find signature */
+ sctp_m_freem(m_sig);
+ return (NULL);
+ }
+ /* compare the received digest with the computed digest */
+ if (timingsafe_bcmp(calc_sig, sig, SCTP_SIGNATURE_SIZE) != 0) {
+ /* try the old cookie? */
+ if ((cookie->time_entered.tv_sec == (long)ep->time_of_secret_change) &&
+ (ep->current_secret_number != ep->last_secret_number)) {
+ /* compute digest with old */
+ (void)sctp_hmac_m(SCTP_HMAC,
+ (uint8_t *)ep->secret_key[(int)ep->last_secret_number],
+ SCTP_SECRET_SIZE, m, cookie_offset, calc_sig, 0);
+ /* compare */
+ if (timingsafe_bcmp(calc_sig, sig, SCTP_SIGNATURE_SIZE) == 0)
+ cookie_ok = 1;
+ }
+ } else {
+ cookie_ok = 1;
+ }
+
+ /*
+ * Now before we continue we must reconstruct our mbuf so that
+ * normal processing of any other chunks will work.
+ */
+ {
+ struct mbuf *m_at;
+
+ m_at = m;
+ while (SCTP_BUF_NEXT(m_at) != NULL) {
+ m_at = SCTP_BUF_NEXT(m_at);
+ }
+ SCTP_BUF_NEXT(m_at) = m_sig;
+ }
+
+ if (cookie_ok == 0) {
+ SCTPDBG(SCTP_DEBUG_INPUT2, "handle_cookie_echo: cookie signature validation failed!\n");
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "offset = %u, cookie_offset = %u, sig_offset = %u\n",
+ (uint32_t) offset, cookie_offset, sig_offset);
+ return (NULL);
+ }
+
+ if (sctp_ticks_to_msecs(cookie->cookie_life) > SCTP_MAX_COOKIE_LIFE) {
+ SCTPDBG(SCTP_DEBUG_INPUT2, "handle_cookie_echo: Invalid cookie lifetime\n");
+ return (NULL);
+ }
+ time_entered.tv_sec = cookie->time_entered.tv_sec;
+ time_entered.tv_usec = cookie->time_entered.tv_usec;
+ if ((time_entered.tv_sec < 0) ||
+ (time_entered.tv_usec < 0) ||
+ (time_entered.tv_usec >= 1000000)) {
+ /* Invalid time stamp. Cookie must have been modified. */
+ SCTPDBG(SCTP_DEBUG_INPUT2, "handle_cookie_echo: Invalid time stamp\n");
+ return (NULL);
+ }
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (timercmp(&now, &time_entered, <)) {
+#else
+ if (timevalcmp(&now, &time_entered, <)) {
+#endif
+ SCTPDBG(SCTP_DEBUG_INPUT2, "handle_cookie_echo: cookie generated in the future!\n");
+ return (NULL);
+ }
+ /*
+ * Check the cookie timestamps to be sure it's not stale.
+ * cookie_life is in ticks, so we convert to seconds.
+ */
+ time_expires.tv_sec = time_entered.tv_sec + sctp_ticks_to_secs(cookie->cookie_life);
+ time_expires.tv_usec = time_entered.tv_usec;
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (timercmp(&now, &time_expires, >))
+#else
+ if (timevalcmp(&now, &time_expires, >))
+#endif
+ {
+ /* cookie is stale! */
+ struct mbuf *op_err;
+ struct sctp_error_stale_cookie *cause;
+ struct timeval diff;
+ uint32_t staleness;
+
+ op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_error_stale_cookie),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err == NULL) {
+ /* FOOBAR */
+ return (NULL);
+ }
+ /* Set the len */
+ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_error_stale_cookie);
+ cause = mtod(op_err, struct sctp_error_stale_cookie *);
+ cause->cause.code = htons(SCTP_CAUSE_STALE_COOKIE);
+ cause->cause.length = htons(sizeof(struct sctp_error_stale_cookie));
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ timersub(&now, &time_expires, &diff);
+#else
+ diff = now;
+ timevalsub(&diff, &time_expires);
+#endif
+ if ((uint32_t)diff.tv_sec > UINT32_MAX / 1000000) {
+ staleness = UINT32_MAX;
+ } else {
+ staleness = (uint32_t)diff.tv_sec * 1000000;
+ }
+ if (UINT32_MAX - staleness >= (uint32_t)diff.tv_usec) {
+ staleness += (uint32_t)diff.tv_usec;
+ } else {
+ staleness = UINT32_MAX;
+ }
+ cause->stale_time = htonl(staleness);
+ sctp_send_operr_to(src, dst, sh, cookie->peers_vtag, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, l_inp->fibnum,
+#endif
+ vrf_id, port);
+ return (NULL);
+ }
+ /*
+ * Now we must see with the lookup address if we have an existing
+ * asoc. This will only happen if we were in the COOKIE-WAIT state
+ * and a INIT collided with us and somewhere the peer sent the
+ * cookie on another address besides the single address our assoc
+ * had for him. In this case we will have one of the tie-tags set at
+ * least AND the address field in the cookie can be used to look it
+ * up.
+ */
+ to = NULL;
+ switch (cookie->addr_type) {
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6.sin6_len = sizeof(sin6);
+#endif
+ sin6.sin6_port = sh->src_port;
+ sin6.sin6_scope_id = cookie->scope_id;
+ memcpy(&sin6.sin6_addr.s6_addr, cookie->address,
+ sizeof(sin6.sin6_addr.s6_addr));
+ to = (struct sockaddr *)&sin6;
+ break;
+#endif
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin.sin_len = sizeof(sin);
+#endif
+ sin.sin_port = sh->src_port;
+ sin.sin_addr.s_addr = cookie->address[0];
+ to = (struct sockaddr *)&sin;
+ break;
+#endif
+#if defined(__Userspace__)
+ case SCTP_CONN_ADDRESS:
+ memset(&sconn, 0, sizeof(struct sockaddr_conn));
+ sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+ sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ sconn.sconn_port = sh->src_port;
+ memcpy(&sconn.sconn_addr, cookie->address, sizeof(void *));
+ to = (struct sockaddr *)&sconn;
+ break;
+#endif
+ default:
+ /* This should not happen */
+ return (NULL);
+ }
+ if (*stcb == NULL) {
+ /* Yep, lets check */
+ *stcb = sctp_findassociation_ep_addr(inp_p, to, netp, dst, NULL);
+ if (*stcb == NULL) {
+ /*
+ * We should have only got back the same inp. If we
+ * got back a different ep we have a problem. The
+ * original findep got back l_inp and now
+ */
+ if (l_inp != *inp_p) {
+ SCTP_PRINTF("Bad problem find_ep got a diff inp then special_locate?\n");
+ }
+ } else {
+ if (*locked_tcb == NULL) {
+ /* In this case we found the assoc only
+ * after we locked the create lock. This means
+ * we are in a colliding case and we must make
+ * sure that we unlock the tcb if its one of the
+ * cases where we throw away the incoming packets.
+ */
+ *locked_tcb = *stcb;
+
+ /* We must also increment the inp ref count
+ * since the ref_count flags was set when we
+ * did not find the TCB, now we found it which
+ * reduces the refcount.. we must raise it back
+ * out to balance it all :-)
+ */
+ SCTP_INP_INCR_REF((*stcb)->sctp_ep);
+ if ((*stcb)->sctp_ep != l_inp) {
+ SCTP_PRINTF("Huh? ep:%p diff then l_inp:%p?\n",
+ (void *)(*stcb)->sctp_ep, (void *)l_inp);
+ }
+ }
+ }
+ }
+
+ cookie_len -= SCTP_SIGNATURE_SIZE;
+ if (*stcb == NULL) {
+ /* this is the "normal" case... get a new TCB */
+ *stcb = sctp_process_cookie_new(m, iphlen, offset, src, dst, sh,
+ cookie, cookie_len, *inp_p,
+ netp, to, &notification,
+ auth_skipped, auth_offset, auth_len,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ } else {
+ /* this is abnormal... cookie-echo on existing TCB */
+ had_a_existing_tcb = 1;
+ *stcb = sctp_process_cookie_existing(m, iphlen, offset,
+ src, dst, sh,
+ cookie, cookie_len, *inp_p, *stcb, netp, to,
+ &notification, auth_skipped, auth_offset, auth_len,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (*stcb == NULL) {
+ *locked_tcb = NULL;
+ }
+ }
+
+ if (*stcb == NULL) {
+ /* still no TCB... must be bad cookie-echo */
+ return (NULL);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (*netp != NULL) {
+ (*netp)->flowtype = mflowtype;
+ (*netp)->flowid = mflowid;
+ }
+#endif
+ /*
+ * Ok, we built an association so confirm the address we sent the
+ * INIT-ACK to.
+ */
+ netl = sctp_findnet(*stcb, to);
+ /*
+ * This code should in theory NOT run but
+ */
+ if (netl == NULL) {
+ /* TSNH! Huh, why do I need to add this address here? */
+ if (sctp_add_remote_addr(*stcb, to, NULL, port,
+ SCTP_DONOT_SETSCOPE, SCTP_IN_COOKIE_PROC)) {
+ return (NULL);
+ }
+ netl = sctp_findnet(*stcb, to);
+ }
+ if (netl) {
+ if (netl->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ netl->dest_state &= ~SCTP_ADDR_UNCONFIRMED;
+ (void)sctp_set_primary_addr((*stcb), (struct sockaddr *)NULL,
+ netl);
+ send_int_conf = 1;
+ }
+ }
+ sctp_start_net_timers(*stcb);
+ if ((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
+ if (!had_a_existing_tcb ||
+ (((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) {
+ /*
+ * If we have a NEW cookie or the connect never
+ * reached the connected state during collision we
+ * must do the TCP accept thing.
+ */
+ struct socket *so, *oso;
+ struct sctp_inpcb *inp;
+
+ if (notification == SCTP_NOTIFY_ASSOC_RESTART) {
+ /*
+ * For a restart we will keep the same
+ * socket, no need to do anything. I THINK!!
+ */
+ sctp_ulp_notify(notification, *stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ if (send_int_conf) {
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED,
+ (*stcb), 0, (void *)netl, SCTP_SO_NOT_LOCKED);
+ }
+ return (m);
+ }
+ oso = (*inp_p)->sctp_socket;
+ atomic_add_int(&(*stcb)->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK((*stcb));
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_SET(oso->so_vnet);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(oso, 1);
+#endif
+ so = sonewconn(oso, 0
+#if defined(__APPLE__) && !defined(__Userspace__)
+ ,NULL
+#endif
+ );
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(oso, 1);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+#endif
+ SCTP_TCB_LOCK((*stcb));
+ atomic_subtract_int(&(*stcb)->asoc.refcnt, 1);
+
+ if (so == NULL) {
+ struct mbuf *op_err;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *pcb_so;
+#endif
+ /* Too many sockets */
+ SCTPDBG(SCTP_DEBUG_INPUT1, "process_cookie_new: no room for another socket!\n");
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ sctp_abort_association(*inp_p, NULL, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ pcb_so = SCTP_INP_SO(*inp_p);
+ atomic_add_int(&(*stcb)->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK((*stcb));
+ SCTP_SOCKET_LOCK(pcb_so, 1);
+ SCTP_TCB_LOCK((*stcb));
+ atomic_subtract_int(&(*stcb)->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(*inp_p, *stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_23);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(pcb_so, 1);
+#endif
+ return (NULL);
+ }
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ SCTP_INP_INCR_REF(inp);
+ /*
+ * We add the unbound flag here so that
+ * if we get an soabort() before we get the
+ * move_pcb done, we will properly cleanup.
+ */
+ inp->sctp_flags = (SCTP_PCB_FLAGS_TCPTYPE |
+ SCTP_PCB_FLAGS_CONNECTED |
+ SCTP_PCB_FLAGS_IN_TCPPOOL |
+ SCTP_PCB_FLAGS_UNBOUND |
+ (SCTP_PCB_COPY_FLAGS & (*inp_p)->sctp_flags) |
+ SCTP_PCB_FLAGS_DONT_WAKE);
+ inp->sctp_features = (*inp_p)->sctp_features;
+ inp->sctp_mobility_features = (*inp_p)->sctp_mobility_features;
+ inp->sctp_socket = so;
+ inp->sctp_frag_point = (*inp_p)->sctp_frag_point;
+ inp->max_cwnd = (*inp_p)->max_cwnd;
+ inp->sctp_cmt_on_off = (*inp_p)->sctp_cmt_on_off;
+ inp->ecn_supported = (*inp_p)->ecn_supported;
+ inp->prsctp_supported = (*inp_p)->prsctp_supported;
+ inp->auth_supported = (*inp_p)->auth_supported;
+ inp->asconf_supported = (*inp_p)->asconf_supported;
+ inp->reconfig_supported = (*inp_p)->reconfig_supported;
+ inp->nrsack_supported = (*inp_p)->nrsack_supported;
+ inp->pktdrop_supported = (*inp_p)->pktdrop_supported;
+ inp->partial_delivery_point = (*inp_p)->partial_delivery_point;
+ inp->sctp_context = (*inp_p)->sctp_context;
+ inp->local_strreset_support = (*inp_p)->local_strreset_support;
+ inp->fibnum = (*inp_p)->fibnum;
+#if defined(__Userspace__)
+ inp->ulp_info = (*inp_p)->ulp_info;
+ inp->recv_callback = (*inp_p)->recv_callback;
+ inp->send_callback = (*inp_p)->send_callback;
+ inp->send_sb_threshold = (*inp_p)->send_sb_threshold;
+#endif
+ /*
+ * copy in the authentication parameters from the
+ * original endpoint
+ */
+ if (inp->sctp_ep.local_hmacs)
+ sctp_free_hmaclist(inp->sctp_ep.local_hmacs);
+ inp->sctp_ep.local_hmacs =
+ sctp_copy_hmaclist((*inp_p)->sctp_ep.local_hmacs);
+ if (inp->sctp_ep.local_auth_chunks)
+ sctp_free_chunklist(inp->sctp_ep.local_auth_chunks);
+ inp->sctp_ep.local_auth_chunks =
+ sctp_copy_chunklist((*inp_p)->sctp_ep.local_auth_chunks);
+
+ /*
+ * Now we must move it from one hash table to
+ * another and get the tcb in the right place.
+ */
+
+ /* This is where the one-2-one socket is put into
+ * the accept state waiting for the accept!
+ */
+ if (*stcb) {
+ SCTP_ADD_SUBSTATE(*stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
+ }
+ sctp_move_pcb_and_assoc(*inp_p, inp, *stcb);
+
+ atomic_add_int(&(*stcb)->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK((*stcb));
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ sctp_pull_off_control_to_new_inp((*inp_p), inp, *stcb,
+ 0);
+#else
+ sctp_pull_off_control_to_new_inp((*inp_p), inp, *stcb, M_NOWAIT);
+#endif
+ SCTP_TCB_LOCK((*stcb));
+ atomic_subtract_int(&(*stcb)->asoc.refcnt, 1);
+
+ /* now we must check to see if we were aborted while
+ * the move was going on and the lock/unlock happened.
+ */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* yep it was, we leave the
+ * assoc attached to the socket since
+ * the sctp_inpcb_free() call will send
+ * an abort for us.
+ */
+ SCTP_INP_DECR_REF(inp);
+ return (NULL);
+ }
+ SCTP_INP_DECR_REF(inp);
+ /* Switch over to the new guy */
+ *inp_p = inp;
+ sctp_ulp_notify(notification, *stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ if (send_int_conf) {
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED,
+ (*stcb), 0, (void *)netl, SCTP_SO_NOT_LOCKED);
+ }
+
+ /* Pull it from the incomplete queue and wake the guy */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ atomic_add_int(&(*stcb)->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK((*stcb));
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+ soisconnected(so);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_TCB_LOCK((*stcb));
+ atomic_subtract_int(&(*stcb)->asoc.refcnt, 1);
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (m);
+ }
+ }
+ if (notification) {
+ sctp_ulp_notify(notification, *stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ if (send_int_conf) {
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED,
+ (*stcb), 0, (void *)netl, SCTP_SO_NOT_LOCKED);
+ }
+ return (m);
+}
+
+static void
+sctp_handle_cookie_ack(struct sctp_cookie_ack_chunk *cp SCTP_UNUSED,
+ struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ /* cp must not be used, others call this without a c-ack :-) */
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk;
+
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_cookie_ack: handling COOKIE-ACK\n");
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+
+ asoc = &stcb->asoc;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ asoc->overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INPUT,
+ __LINE__);
+ }
+ sctp_stop_all_cookie_timers(stcb);
+ sctp_toss_old_cookies(stcb, asoc);
+ /* process according to association state */
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED) {
+ /* state change only needed when I am in right state */
+ SCTPDBG(SCTP_DEBUG_INPUT2, "moving to OPEN state\n");
+ SCTP_SET_STATE(stcb, SCTP_STATE_OPEN);
+ sctp_start_net_timers(stcb);
+ /* update RTO */
+ SCTP_STAT_INCR_COUNTER32(sctps_activeestab);
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+ if (asoc->overall_error_count == 0) {
+ sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered,
+ SCTP_RTT_FROM_NON_DATA);
+ }
+ /*
+ * Since we did not send a HB make sure we don't double
+ * things.
+ */
+ asoc->overall_error_count = 0;
+ net->hb_responded = 1;
+ (void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered);
+ sctp_ulp_notify(SCTP_NOTIFY_ASSOC_UP, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+#endif
+ sctp_pcb_add_flags(stcb->sctp_ep, SCTP_PCB_FLAGS_CONNECTED);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ if ((stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) == 0) {
+ soisconnected(stcb->sctp_socket);
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+
+ if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) &&
+ TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->stream_queue_cnt == 0)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ sctp_send_shutdown(stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN,
+ stcb->sctp_ep, stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_LOCKED);
+ }
+
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ /* We don't need to do the asconf thing,
+ * nor hb or autoclose if the socket is closed.
+ */
+ goto closed_socket;
+ }
+
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep,
+ stcb, net);
+
+ if (stcb->asoc.sctp_autoclose_ticks &&
+ sctp_is_feature_on(stcb->sctp_ep, SCTP_PCB_FLAGS_AUTOCLOSE)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE,
+ stcb->sctp_ep, stcb, NULL);
+ }
+ /*
+ * send ASCONF if parameters are pending and ASCONFs are
+ * allowed (eg. addresses changed when init/cookie echo were
+ * in flight)
+ */
+ if ((sctp_is_feature_on(stcb->sctp_ep, SCTP_PCB_FLAGS_DO_ASCONF)) &&
+ (stcb->asoc.asconf_supported == 1) &&
+ (!TAILQ_EMPTY(&stcb->asoc.asconf_queue))) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF,
+ stcb->sctp_ep, stcb,
+ stcb->asoc.primary_destination);
+#else
+ sctp_send_asconf(stcb, stcb->asoc.primary_destination,
+ SCTP_ADDR_NOT_LOCKED);
+#endif
+ }
+ }
+closed_socket:
+ /* Restart the timer if we have pending data */
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ if (chk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (chk != NULL) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, chk->whoTo);
+ }
+}
+
+static void
+sctp_handle_ecn_echo(struct sctp_ecne_chunk *cp,
+ struct sctp_tcb *stcb)
+{
+ struct sctp_nets *net;
+ struct sctp_tmit_chunk *lchk;
+ struct sctp_ecne_chunk bkup;
+ uint8_t override_bit;
+ uint32_t tsn, window_data_tsn;
+ int len;
+ unsigned int pkt_cnt;
+
+ len = ntohs(cp->ch.chunk_length);
+ if (len == sizeof(struct old_sctp_ecne_chunk)) {
+ /* Its the old format */
+ memcpy(&bkup, cp, sizeof(struct old_sctp_ecne_chunk));
+ bkup.num_pkts_since_cwr = htonl(1);
+ cp = &bkup;
+ }
+ SCTP_STAT_INCR(sctps_recvecne);
+ tsn = ntohl(cp->tsn);
+ pkt_cnt = ntohl(cp->num_pkts_since_cwr);
+ lchk = TAILQ_LAST(&stcb->asoc.send_queue, sctpchunk_listhead);
+ if (lchk == NULL) {
+ window_data_tsn = stcb->asoc.sending_seq - 1;
+ } else {
+ window_data_tsn = lchk->rec.data.tsn;
+ }
+
+ /* Find where it was sent to if possible. */
+ net = NULL;
+ TAILQ_FOREACH(lchk, &stcb->asoc.sent_queue, sctp_next) {
+ if (lchk->rec.data.tsn == tsn) {
+ net = lchk->whoTo;
+ net->ecn_prev_cwnd = lchk->rec.data.cwnd_at_send;
+ break;
+ }
+ if (SCTP_TSN_GT(lchk->rec.data.tsn, tsn)) {
+ break;
+ }
+ }
+ if (net == NULL) {
+ /*
+ * What to do. A previous send of a
+ * CWR was possibly lost. See how old it is, we
+ * may have it marked on the actual net.
+ */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (tsn == net->last_cwr_tsn) {
+ /* Found him, send it off */
+ break;
+ }
+ }
+ if (net == NULL) {
+ /*
+ * If we reach here, we need to send a special
+ * CWR that says hey, we did this a long time
+ * ago and you lost the response.
+ */
+ net = TAILQ_FIRST(&stcb->asoc.nets);
+ if (net == NULL) {
+ /* TSNH */
+ return;
+ }
+ override_bit = SCTP_CWR_REDUCE_OVERRIDE;
+ } else {
+ override_bit = 0;
+ }
+ } else {
+ override_bit = 0;
+ }
+ if (SCTP_TSN_GT(tsn, net->cwr_window_tsn) &&
+ ((override_bit&SCTP_CWR_REDUCE_OVERRIDE) == 0)) {
+ /* JRS - Use the congestion control given in the pluggable CC module */
+ stcb->asoc.cc_functions.sctp_cwnd_update_after_ecn_echo(stcb, net, 0, pkt_cnt);
+ /*
+ * We reduce once every RTT. So we will only lower cwnd at
+ * the next sending seq i.e. the window_data_tsn
+ */
+ net->cwr_window_tsn = window_data_tsn;
+ net->ecn_ce_pkt_cnt += pkt_cnt;
+ net->lost_cnt = pkt_cnt;
+ net->last_cwr_tsn = tsn;
+ } else {
+ override_bit |= SCTP_CWR_IN_SAME_WINDOW;
+ if (SCTP_TSN_GT(tsn, net->last_cwr_tsn) &&
+ ((override_bit&SCTP_CWR_REDUCE_OVERRIDE) == 0)) {
+ /*
+ * Another loss in the same window update how
+ * many marks/packets lost we have had.
+ */
+ int cnt = 1;
+ if (pkt_cnt > net->lost_cnt) {
+ /* Should be the case */
+ cnt = (pkt_cnt - net->lost_cnt);
+ net->ecn_ce_pkt_cnt += cnt;
+ }
+ net->lost_cnt = pkt_cnt;
+ net->last_cwr_tsn = tsn;
+ /*
+ * Most CC functions will ignore this call, since we are in-window
+ * yet of the initial CE the peer saw.
+ */
+ stcb->asoc.cc_functions.sctp_cwnd_update_after_ecn_echo(stcb, net, 1, cnt);
+ }
+ }
+ /*
+ * We always send a CWR this way if our previous one was lost our
+ * peer will get an update, or if it is not time again to reduce we
+ * still get the cwr to the peer. Note we set the override when we
+ * could not find the TSN on the chunk or the destination network.
+ */
+ sctp_send_cwr(stcb, net, net->last_cwr_tsn, override_bit);
+}
+
+static void
+sctp_handle_ecn_cwr(struct sctp_cwr_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ /*
+ * Here we get a CWR from the peer. We must look in the outqueue and
+ * make sure that we have a covered ECNE in the control chunk part.
+ * If so remove it.
+ */
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_ecne_chunk *ecne;
+ int override;
+ uint32_t cwr_tsn;
+
+ cwr_tsn = ntohl(cp->tsn);
+ override = cp->ch.chunk_flags & SCTP_CWR_REDUCE_OVERRIDE;
+ TAILQ_FOREACH_SAFE(chk, &stcb->asoc.control_send_queue, sctp_next, nchk) {
+ if (chk->rec.chunk_id.id != SCTP_ECN_ECHO) {
+ continue;
+ }
+ if ((override == 0) && (chk->whoTo != net)) {
+ /* Must be from the right src unless override is set */
+ continue;
+ }
+ ecne = mtod(chk->data, struct sctp_ecne_chunk *);
+ if (SCTP_TSN_GE(cwr_tsn, ntohl(ecne->tsn))) {
+ /* this covers this ECNE, we can remove it */
+ stcb->asoc.ecn_echo_cnt_onq--;
+ TAILQ_REMOVE(&stcb->asoc.control_send_queue, chk,
+ sctp_next);
+ stcb->asoc.ctrl_queue_cnt--;
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ if (override == 0) {
+ break;
+ }
+ }
+ }
+}
+
+static void
+sctp_handle_shutdown_complete(struct sctp_shutdown_complete_chunk *cp SCTP_UNUSED,
+ struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_shutdown_complete: handling SHUTDOWN-COMPLETE\n");
+ if (stcb == NULL)
+ return;
+
+ /* process according to association state */
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT) {
+ /* unexpected SHUTDOWN-COMPLETE... so ignore... */
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_shutdown_complete: not in SCTP_STATE_SHUTDOWN_ACK_SENT --- ignore\n");
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ /* notify upper layer protocol */
+ if (stcb->sctp_socket) {
+ sctp_ulp_notify(SCTP_NOTIFY_ASSOC_DOWN, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ }
+#ifdef INVARIANTS
+ if (!TAILQ_EMPTY(&stcb->asoc.send_queue) ||
+ !TAILQ_EMPTY(&stcb->asoc.sent_queue) ||
+ sctp_is_there_unsent_data(stcb, SCTP_SO_NOT_LOCKED)) {
+ panic("Queues are not empty when handling SHUTDOWN-COMPLETE");
+ }
+#endif
+ /* stop the timer */
+ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWNACK, stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_24);
+ SCTP_STAT_INCR_COUNTER32(sctps_shutdown);
+ /* free the TCB */
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_shutdown_complete: calls free-asoc\n");
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_25);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return;
+}
+
+static int
+process_chunk_drop(struct sctp_tcb *stcb, struct sctp_chunk_desc *desc,
+ struct sctp_nets *net, uint8_t flg)
+{
+ switch (desc->chunk_type) {
+ case SCTP_DATA:
+ case SCTP_IDATA:
+ /* find the tsn to resend (possibly) */
+ {
+ uint32_t tsn;
+ struct sctp_tmit_chunk *tp1;
+
+ tsn = ntohl(desc->tsn_ifany);
+ TAILQ_FOREACH(tp1, &stcb->asoc.sent_queue, sctp_next) {
+ if (tp1->rec.data.tsn == tsn) {
+ /* found it */
+ break;
+ }
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, tsn)) {
+ /* not found */
+ tp1 = NULL;
+ break;
+ }
+ }
+ if (tp1 == NULL) {
+ /*
+ * Do it the other way , aka without paying
+ * attention to queue seq order.
+ */
+ SCTP_STAT_INCR(sctps_pdrpdnfnd);
+ TAILQ_FOREACH(tp1, &stcb->asoc.sent_queue, sctp_next) {
+ if (tp1->rec.data.tsn == tsn) {
+ /* found it */
+ break;
+ }
+ }
+ }
+ if (tp1 == NULL) {
+ SCTP_STAT_INCR(sctps_pdrptsnnf);
+ }
+ if ((tp1) && (tp1->sent < SCTP_DATAGRAM_ACKED)) {
+ if (((flg & SCTP_BADCRC) == 0) &&
+ ((flg & SCTP_FROM_MIDDLE_BOX) == 0)) {
+ return (0);
+ }
+ if ((stcb->asoc.peers_rwnd == 0) &&
+ ((flg & SCTP_FROM_MIDDLE_BOX) == 0)) {
+ SCTP_STAT_INCR(sctps_pdrpdiwnp);
+ return (0);
+ }
+ if (stcb->asoc.peers_rwnd == 0 &&
+ (flg & SCTP_FROM_MIDDLE_BOX)) {
+ SCTP_STAT_INCR(sctps_pdrpdizrw);
+ return (0);
+ }
+ if ((uint32_t)SCTP_BUF_LEN(tp1->data) <
+ SCTP_DATA_CHUNK_OVERHEAD(stcb) + SCTP_NUM_DB_TO_VERIFY) {
+ /* Payload not matching. */
+ SCTP_STAT_INCR(sctps_pdrpbadd);
+ return (-1);
+ }
+ if (memcmp(mtod(tp1->data, caddr_t) + SCTP_DATA_CHUNK_OVERHEAD(stcb),
+ desc->data_bytes, SCTP_NUM_DB_TO_VERIFY) != 0) {
+ /* Payload not matching. */
+ SCTP_STAT_INCR(sctps_pdrpbadd);
+ return (-1);
+ }
+ if (tp1->do_rtt) {
+ /*
+ * this guy had a RTO calculation
+ * pending on it, cancel it
+ */
+ if (tp1->whoTo->rto_needed == 0) {
+ tp1->whoTo->rto_needed = 1;
+ }
+ tp1->do_rtt = 0;
+ }
+ SCTP_STAT_INCR(sctps_pdrpmark);
+ if (tp1->sent != SCTP_DATAGRAM_RESEND)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ /*
+ * mark it as if we were doing a FR, since
+ * we will be getting gap ack reports behind
+ * the info from the router.
+ */
+ tp1->rec.data.doing_fast_retransmit = 1;
+ /*
+ * mark the tsn with what sequences can
+ * cause a new FR.
+ */
+ if (TAILQ_EMPTY(&stcb->asoc.send_queue)) {
+ tp1->rec.data.fast_retran_tsn = stcb->asoc.sending_seq;
+ } else {
+ tp1->rec.data.fast_retran_tsn = (TAILQ_FIRST(&stcb->asoc.send_queue))->rec.data.tsn;
+ }
+
+ /* restart the timer */
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, tp1->whoTo,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_26);
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, tp1->whoTo);
+
+ /* fix counts and things */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_PDRP,
+ tp1->whoTo->flight_size,
+ tp1->book_size,
+ (uint32_t)(uintptr_t)stcb,
+ tp1->rec.data.tsn);
+ }
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ sctp_flight_size_decrease(tp1);
+ sctp_total_flight_decrease(stcb, tp1);
+ }
+ tp1->sent = SCTP_DATAGRAM_RESEND;
+ } {
+ /* audit code */
+ unsigned int audit;
+
+ audit = 0;
+ TAILQ_FOREACH(tp1, &stcb->asoc.sent_queue, sctp_next) {
+ if (tp1->sent == SCTP_DATAGRAM_RESEND)
+ audit++;
+ }
+ TAILQ_FOREACH(tp1, &stcb->asoc.control_send_queue,
+ sctp_next) {
+ if (tp1->sent == SCTP_DATAGRAM_RESEND)
+ audit++;
+ }
+ if (audit != stcb->asoc.sent_queue_retran_cnt) {
+ SCTP_PRINTF("**Local Audit finds cnt:%d asoc cnt:%d\n",
+ audit, stcb->asoc.sent_queue_retran_cnt);
+#ifndef SCTP_AUDITING_ENABLED
+ stcb->asoc.sent_queue_retran_cnt = audit;
+#endif
+ }
+ }
+ }
+ break;
+ case SCTP_ASCONF:
+ {
+ struct sctp_tmit_chunk *asconf;
+
+ TAILQ_FOREACH(asconf, &stcb->asoc.control_send_queue,
+ sctp_next) {
+ if (asconf->rec.chunk_id.id == SCTP_ASCONF) {
+ break;
+ }
+ }
+ if (asconf) {
+ if (asconf->sent != SCTP_DATAGRAM_RESEND)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ asconf->sent = SCTP_DATAGRAM_RESEND;
+ asconf->snd_count--;
+ }
+ }
+ break;
+ case SCTP_INITIATION:
+ /* resend the INIT */
+ stcb->asoc.dropped_special_cnt++;
+ if (stcb->asoc.dropped_special_cnt < SCTP_RETRY_DROPPED_THRESH) {
+ /*
+ * If we can get it in, in a few attempts we do
+ * this, otherwise we let the timer fire.
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, stcb->sctp_ep,
+ stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_27);
+ sctp_send_initiate(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ }
+ break;
+ case SCTP_SELECTIVE_ACK:
+ case SCTP_NR_SELECTIVE_ACK:
+ /* resend the sack */
+ sctp_send_sack(stcb, SCTP_SO_NOT_LOCKED);
+ break;
+ case SCTP_HEARTBEAT_REQUEST:
+ /* resend a demand HB */
+ if ((stcb->asoc.overall_error_count + 3) < stcb->asoc.max_send_times) {
+ /* Only retransmit if we KNOW we wont destroy the tcb */
+ sctp_send_hb(stcb, net, SCTP_SO_NOT_LOCKED);
+ }
+ break;
+ case SCTP_SHUTDOWN:
+ sctp_send_shutdown(stcb, net);
+ break;
+ case SCTP_SHUTDOWN_ACK:
+ sctp_send_shutdown_ack(stcb, net);
+ break;
+ case SCTP_COOKIE_ECHO:
+ {
+ struct sctp_tmit_chunk *cookie;
+
+ cookie = NULL;
+ TAILQ_FOREACH(cookie, &stcb->asoc.control_send_queue,
+ sctp_next) {
+ if (cookie->rec.chunk_id.id == SCTP_COOKIE_ECHO) {
+ break;
+ }
+ }
+ if (cookie) {
+ if (cookie->sent != SCTP_DATAGRAM_RESEND)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ cookie->sent = SCTP_DATAGRAM_RESEND;
+ sctp_stop_all_cookie_timers(stcb);
+ }
+ }
+ break;
+ case SCTP_COOKIE_ACK:
+ sctp_send_cookie_ack(stcb);
+ break;
+ case SCTP_ASCONF_ACK:
+ /* resend last asconf ack */
+ sctp_send_asconf_ack(stcb);
+ break;
+ case SCTP_IFORWARD_CUM_TSN:
+ case SCTP_FORWARD_CUM_TSN:
+ send_forward_tsn(stcb, &stcb->asoc);
+ break;
+ /* can't do anything with these */
+ case SCTP_PACKET_DROPPED:
+ case SCTP_INITIATION_ACK: /* this should not happen */
+ case SCTP_HEARTBEAT_ACK:
+ case SCTP_ABORT_ASSOCIATION:
+ case SCTP_OPERATION_ERROR:
+ case SCTP_SHUTDOWN_COMPLETE:
+ case SCTP_ECN_ECHO:
+ case SCTP_ECN_CWR:
+ default:
+ break;
+ }
+ return (0);
+}
+
+void
+sctp_reset_in_stream(struct sctp_tcb *stcb, uint32_t number_entries, uint16_t *list)
+{
+ uint32_t i;
+ uint16_t temp;
+
+ /*
+ * We set things to 0xffffffff since this is the last delivered sequence
+ * and we will be sending in 0 after the reset.
+ */
+
+ if (number_entries) {
+ for (i = 0; i < number_entries; i++) {
+ temp = ntohs(list[i]);
+ if (temp >= stcb->asoc.streamincnt) {
+ continue;
+ }
+ stcb->asoc.strmin[temp].last_mid_delivered = 0xffffffff;
+ }
+ } else {
+ list = NULL;
+ for (i = 0; i < stcb->asoc.streamincnt; i++) {
+ stcb->asoc.strmin[i].last_mid_delivered = 0xffffffff;
+ }
+ }
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_RECV, stcb, number_entries, (void *)list, SCTP_SO_NOT_LOCKED);
+}
+
+static void
+sctp_reset_out_streams(struct sctp_tcb *stcb, uint32_t number_entries, uint16_t *list)
+{
+ uint32_t i;
+ uint16_t temp;
+
+ if (number_entries > 0) {
+ for (i = 0; i < number_entries; i++) {
+ temp = ntohs(list[i]);
+ if (temp >= stcb->asoc.streamoutcnt) {
+ /* no such stream */
+ continue;
+ }
+ stcb->asoc.strmout[temp].next_mid_ordered = 0;
+ stcb->asoc.strmout[temp].next_mid_unordered = 0;
+ }
+ } else {
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ stcb->asoc.strmout[i].next_mid_ordered = 0;
+ stcb->asoc.strmout[i].next_mid_unordered = 0;
+ }
+ }
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_SEND, stcb, number_entries, (void *)list, SCTP_SO_NOT_LOCKED);
+}
+
+static void
+sctp_reset_clear_pending(struct sctp_tcb *stcb, uint32_t number_entries, uint16_t *list)
+{
+ uint32_t i;
+ uint16_t temp;
+
+ if (number_entries > 0) {
+ for (i = 0; i < number_entries; i++) {
+ temp = ntohs(list[i]);
+ if (temp >= stcb->asoc.streamoutcnt) {
+ /* no such stream */
+ continue;
+ }
+ stcb->asoc.strmout[temp].state = SCTP_STREAM_OPEN;
+ }
+ } else {
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ stcb->asoc.strmout[i].state = SCTP_STREAM_OPEN;
+ }
+ }
+}
+
+struct sctp_stream_reset_request *
+sctp_find_stream_reset(struct sctp_tcb *stcb, uint32_t seq, struct sctp_tmit_chunk **bchk)
+{
+ struct sctp_association *asoc;
+ struct sctp_chunkhdr *ch;
+ struct sctp_stream_reset_request *r;
+ struct sctp_tmit_chunk *chk;
+ int len, clen;
+
+ asoc = &stcb->asoc;
+ chk = asoc->str_reset;
+ if (TAILQ_EMPTY(&asoc->control_send_queue) ||
+ (chk == NULL)) {
+ asoc->stream_reset_outstanding = 0;
+ return (NULL);
+ }
+ if (chk->data == NULL) {
+ return (NULL);
+ }
+ if (bchk != NULL) {
+ /* he wants a copy of the chk pointer */
+ *bchk = chk;
+ }
+ clen = chk->send_size;
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ r = (struct sctp_stream_reset_request *)(ch + 1);
+ if (ntohl(r->request_seq) == seq) {
+ /* found it */
+ return (r);
+ }
+ len = SCTP_SIZE32(ntohs(r->ph.param_length));
+ if (clen > (len + (int)sizeof(struct sctp_chunkhdr))) {
+ /* move to the next one, there can only be a max of two */
+ r = (struct sctp_stream_reset_request *)((caddr_t)r + len);
+ if (ntohl(r->request_seq) == seq) {
+ return (r);
+ }
+ }
+ /* that seq is not here */
+ return (NULL);
+}
+
+static void
+sctp_clean_up_stream_reset(struct sctp_tcb *stcb)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk;
+
+ asoc = &stcb->asoc;
+ chk = asoc->str_reset;
+ if (chk == NULL) {
+ return;
+ }
+ asoc->str_reset = NULL;
+ sctp_timer_stop(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb,
+ NULL, SCTP_FROM_SCTP_INPUT + SCTP_LOC_28);
+ TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt--;
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+}
+
+static int
+sctp_handle_stream_reset_response(struct sctp_tcb *stcb,
+ uint32_t seq, uint32_t action,
+ struct sctp_stream_reset_response *respin)
+{
+ uint16_t type;
+ int lparam_len;
+ struct sctp_association *asoc = &stcb->asoc;
+ struct sctp_tmit_chunk *chk;
+ struct sctp_stream_reset_request *req_param;
+ struct sctp_stream_reset_out_request *req_out_param;
+ struct sctp_stream_reset_in_request *req_in_param;
+ uint32_t number_entries;
+
+ if (asoc->stream_reset_outstanding == 0) {
+ /* duplicate */
+ return (0);
+ }
+ if (seq == stcb->asoc.str_reset_seq_out) {
+ req_param = sctp_find_stream_reset(stcb, seq, &chk);
+ if (req_param != NULL) {
+ stcb->asoc.str_reset_seq_out++;
+ type = ntohs(req_param->ph.param_type);
+ lparam_len = ntohs(req_param->ph.param_length);
+ if (type == SCTP_STR_RESET_OUT_REQUEST) {
+ int no_clear = 0;
+
+ req_out_param = (struct sctp_stream_reset_out_request *)req_param;
+ number_entries = (lparam_len - sizeof(struct sctp_stream_reset_out_request)) / sizeof(uint16_t);
+ asoc->stream_reset_out_is_outstanding = 0;
+ if (asoc->stream_reset_outstanding)
+ asoc->stream_reset_outstanding--;
+ if (action == SCTP_STREAM_RESET_RESULT_PERFORMED) {
+ /* do it */
+ sctp_reset_out_streams(stcb, number_entries, req_out_param->list_of_streams);
+ } else if (action == SCTP_STREAM_RESET_RESULT_DENIED) {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_DENIED_OUT, stcb, number_entries, req_out_param->list_of_streams, SCTP_SO_NOT_LOCKED);
+ } else if (action == SCTP_STREAM_RESET_RESULT_IN_PROGRESS) {
+ /* Set it up so we don't stop retransmitting */
+ asoc->stream_reset_outstanding++;
+ stcb->asoc.str_reset_seq_out--;
+ asoc->stream_reset_out_is_outstanding = 1;
+ no_clear = 1;
+ } else {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_FAILED_OUT, stcb, number_entries, req_out_param->list_of_streams, SCTP_SO_NOT_LOCKED);
+ }
+ if (no_clear == 0) {
+ sctp_reset_clear_pending(stcb, number_entries, req_out_param->list_of_streams);
+ }
+ } else if (type == SCTP_STR_RESET_IN_REQUEST) {
+ req_in_param = (struct sctp_stream_reset_in_request *)req_param;
+ number_entries = (lparam_len - sizeof(struct sctp_stream_reset_in_request)) / sizeof(uint16_t);
+ if (asoc->stream_reset_outstanding)
+ asoc->stream_reset_outstanding--;
+ if (action == SCTP_STREAM_RESET_RESULT_DENIED) {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_DENIED_IN, stcb,
+ number_entries, req_in_param->list_of_streams, SCTP_SO_NOT_LOCKED);
+ } else if (action != SCTP_STREAM_RESET_RESULT_PERFORMED) {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_FAILED_IN, stcb,
+ number_entries, req_in_param->list_of_streams, SCTP_SO_NOT_LOCKED);
+ }
+ } else if (type == SCTP_STR_RESET_ADD_OUT_STREAMS) {
+ /* Ok we now may have more streams */
+ int num_stream;
+
+ num_stream = stcb->asoc.strm_pending_add_size;
+ if (num_stream > (stcb->asoc.strm_realoutsize - stcb->asoc.streamoutcnt)) {
+ /* TSNH */
+ num_stream = stcb->asoc.strm_realoutsize - stcb->asoc.streamoutcnt;
+ }
+ stcb->asoc.strm_pending_add_size = 0;
+ if (asoc->stream_reset_outstanding)
+ asoc->stream_reset_outstanding--;
+ if (action == SCTP_STREAM_RESET_RESULT_PERFORMED) {
+ /* Put the new streams into effect */
+ int i;
+ for (i = asoc->streamoutcnt; i < (asoc->streamoutcnt + num_stream); i++) {
+ asoc->strmout[i].state = SCTP_STREAM_OPEN;
+ }
+ asoc->streamoutcnt += num_stream;
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_ADD, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ } else if (action == SCTP_STREAM_RESET_RESULT_DENIED) {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_ADD, stcb, SCTP_STREAM_CHANGE_DENIED, NULL, SCTP_SO_NOT_LOCKED);
+ } else {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_ADD, stcb, SCTP_STREAM_CHANGE_FAILED, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ } else if (type == SCTP_STR_RESET_ADD_IN_STREAMS) {
+ if (asoc->stream_reset_outstanding)
+ asoc->stream_reset_outstanding--;
+ if (action == SCTP_STREAM_RESET_RESULT_DENIED) {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_ADD, stcb, SCTP_STREAM_CHANGE_DENIED, NULL, SCTP_SO_NOT_LOCKED);
+ } else if (action != SCTP_STREAM_RESET_RESULT_PERFORMED) {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_ADD, stcb, SCTP_STREAM_CHANGE_DENIED, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ } else if (type == SCTP_STR_RESET_TSN_REQUEST) {
+ /**
+ * a) Adopt the new in tsn.
+ * b) reset the map
+ * c) Adopt the new out-tsn
+ */
+ struct sctp_stream_reset_response_tsn *resp;
+ struct sctp_forward_tsn_chunk fwdtsn;
+ int abort_flag = 0;
+ if (respin == NULL) {
+ /* huh ? */
+ return (0);
+ }
+ if (ntohs(respin->ph.param_length) < sizeof(struct sctp_stream_reset_response_tsn)) {
+ return (0);
+ }
+ if (action == SCTP_STREAM_RESET_RESULT_PERFORMED) {
+ resp = (struct sctp_stream_reset_response_tsn *)respin;
+ asoc->stream_reset_outstanding--;
+ fwdtsn.ch.chunk_length = htons(sizeof(struct sctp_forward_tsn_chunk));
+ fwdtsn.ch.chunk_type = SCTP_FORWARD_CUM_TSN;
+ fwdtsn.new_cumulative_tsn = htonl(ntohl(resp->senders_next_tsn) - 1);
+ sctp_handle_forward_tsn(stcb, &fwdtsn, &abort_flag, NULL, 0);
+ if (abort_flag) {
+ return (1);
+ }
+ stcb->asoc.highest_tsn_inside_map = (ntohl(resp->senders_next_tsn) - 1);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(0, 7, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT);
+ }
+
+ stcb->asoc.tsn_last_delivered = stcb->asoc.cumulative_tsn = stcb->asoc.highest_tsn_inside_map;
+ stcb->asoc.mapping_array_base_tsn = ntohl(resp->senders_next_tsn);
+ memset(stcb->asoc.mapping_array, 0, stcb->asoc.mapping_array_size);
+
+ stcb->asoc.highest_tsn_inside_nr_map = stcb->asoc.highest_tsn_inside_map;
+ memset(stcb->asoc.nr_mapping_array, 0, stcb->asoc.mapping_array_size);
+
+ stcb->asoc.sending_seq = ntohl(resp->receivers_next_tsn);
+ stcb->asoc.last_acked_seq = stcb->asoc.cumulative_tsn;
+
+ sctp_reset_out_streams(stcb, 0, (uint16_t *) NULL);
+ sctp_reset_in_stream(stcb, 0, (uint16_t *) NULL);
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_TSN, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ } else if (action == SCTP_STREAM_RESET_RESULT_DENIED) {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_TSN, stcb, SCTP_ASSOC_RESET_DENIED, NULL, SCTP_SO_NOT_LOCKED);
+ } else {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_TSN, stcb, SCTP_ASSOC_RESET_FAILED, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ /* get rid of the request and get the request flags */
+ if (asoc->stream_reset_outstanding == 0) {
+ sctp_clean_up_stream_reset(stcb);
+ }
+ }
+ }
+ if (asoc->stream_reset_outstanding == 0) {
+ sctp_send_stream_reset_out_if_possible(stcb, SCTP_SO_NOT_LOCKED);
+ }
+ return (0);
+}
+
+static void
+sctp_handle_str_reset_request_in(struct sctp_tcb *stcb,
+ struct sctp_tmit_chunk *chk,
+ struct sctp_stream_reset_in_request *req, int trunc)
+{
+ uint32_t seq;
+ int len, i;
+ int number_entries;
+ uint16_t temp;
+
+ /*
+ * peer wants me to send a str-reset to him for my outgoing seq's if
+ * seq_in is right.
+ */
+ struct sctp_association *asoc = &stcb->asoc;
+
+ seq = ntohl(req->request_seq);
+ if (asoc->str_reset_seq_in == seq) {
+ asoc->last_reset_action[1] = asoc->last_reset_action[0];
+ if ((asoc->local_strreset_support & SCTP_ENABLE_RESET_STREAM_REQ) == 0) {
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else if (trunc) {
+ /* Can't do it, since they exceeded our buffer size */
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else if (stcb->asoc.stream_reset_out_is_outstanding == 0) {
+ len = ntohs(req->ph.param_length);
+ number_entries = ((len - sizeof(struct sctp_stream_reset_in_request)) / sizeof(uint16_t));
+ if (number_entries) {
+ for (i = 0; i < number_entries; i++) {
+ temp = ntohs(req->list_of_streams[i]);
+ if (temp >= stcb->asoc.streamoutcnt) {
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ goto bad_boy;
+ }
+ req->list_of_streams[i] = temp;
+ }
+ for (i = 0; i < number_entries; i++) {
+ if (stcb->asoc.strmout[req->list_of_streams[i]].state == SCTP_STREAM_OPEN) {
+ stcb->asoc.strmout[req->list_of_streams[i]].state = SCTP_STREAM_RESET_PENDING;
+ }
+ }
+ } else {
+ /* Its all */
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ if (stcb->asoc.strmout[i].state == SCTP_STREAM_OPEN)
+ stcb->asoc.strmout[i].state = SCTP_STREAM_RESET_PENDING;
+ }
+ }
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_PERFORMED;
+ } else {
+ /* Can't do it, since we have sent one out */
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_ERR_IN_PROGRESS;
+ }
+ bad_boy:
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ asoc->str_reset_seq_in++;
+ } else if (asoc->str_reset_seq_in - 1 == seq) {
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ } else if (asoc->str_reset_seq_in - 2 == seq) {
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[1]);
+ } else {
+ sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_RESULT_ERR_BAD_SEQNO);
+ }
+ sctp_send_stream_reset_out_if_possible(stcb, SCTP_SO_NOT_LOCKED);
+}
+
+static int
+sctp_handle_str_reset_request_tsn(struct sctp_tcb *stcb,
+ struct sctp_tmit_chunk *chk,
+ struct sctp_stream_reset_tsn_request *req)
+{
+ /* reset all in and out and update the tsn */
+ /*
+ * A) reset my str-seq's on in and out. B) Select a receive next,
+ * and set cum-ack to it. Also process this selected number as a
+ * fwd-tsn as well. C) set in the response my next sending seq.
+ */
+ struct sctp_forward_tsn_chunk fwdtsn;
+ struct sctp_association *asoc = &stcb->asoc;
+ int abort_flag = 0;
+ uint32_t seq;
+
+ seq = ntohl(req->request_seq);
+ if (asoc->str_reset_seq_in == seq) {
+ asoc->last_reset_action[1] = stcb->asoc.last_reset_action[0];
+ if ((asoc->local_strreset_support & SCTP_ENABLE_CHANGE_ASSOC_REQ) == 0) {
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else {
+ fwdtsn.ch.chunk_length = htons(sizeof(struct sctp_forward_tsn_chunk));
+ fwdtsn.ch.chunk_type = SCTP_FORWARD_CUM_TSN;
+ fwdtsn.ch.chunk_flags = 0;
+ fwdtsn.new_cumulative_tsn = htonl(stcb->asoc.highest_tsn_inside_map + 1);
+ sctp_handle_forward_tsn(stcb, &fwdtsn, &abort_flag, NULL, 0);
+ if (abort_flag) {
+ return (1);
+ }
+ asoc->highest_tsn_inside_map += SCTP_STREAM_RESET_TSN_DELTA;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MAP_LOGGING_ENABLE) {
+ sctp_log_map(0, 10, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT);
+ }
+ asoc->tsn_last_delivered = asoc->cumulative_tsn = asoc->highest_tsn_inside_map;
+ asoc->mapping_array_base_tsn = asoc->highest_tsn_inside_map + 1;
+ memset(asoc->mapping_array, 0, asoc->mapping_array_size);
+ asoc->highest_tsn_inside_nr_map = asoc->highest_tsn_inside_map;
+ memset(asoc->nr_mapping_array, 0, asoc->mapping_array_size);
+ atomic_add_int(&asoc->sending_seq, 1);
+ /* save off historical data for retrans */
+ asoc->last_sending_seq[1] = asoc->last_sending_seq[0];
+ asoc->last_sending_seq[0] = asoc->sending_seq;
+ asoc->last_base_tsnsent[1] = asoc->last_base_tsnsent[0];
+ asoc->last_base_tsnsent[0] = asoc->mapping_array_base_tsn;
+ sctp_reset_out_streams(stcb, 0, (uint16_t *) NULL);
+ sctp_reset_in_stream(stcb, 0, (uint16_t *) NULL);
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_PERFORMED;
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_TSN, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ sctp_add_stream_reset_result_tsn(chk, seq, asoc->last_reset_action[0],
+ asoc->last_sending_seq[0], asoc->last_base_tsnsent[0]);
+ asoc->str_reset_seq_in++;
+ } else if (asoc->str_reset_seq_in - 1 == seq) {
+ sctp_add_stream_reset_result_tsn(chk, seq, asoc->last_reset_action[0],
+ asoc->last_sending_seq[0], asoc->last_base_tsnsent[0]);
+ } else if (asoc->str_reset_seq_in - 2 == seq) {
+ sctp_add_stream_reset_result_tsn(chk, seq, asoc->last_reset_action[1],
+ asoc->last_sending_seq[1], asoc->last_base_tsnsent[1]);
+ } else {
+ sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_RESULT_ERR_BAD_SEQNO);
+ }
+ return (0);
+}
+
+static void
+sctp_handle_str_reset_request_out(struct sctp_tcb *stcb,
+ struct sctp_tmit_chunk *chk,
+ struct sctp_stream_reset_out_request *req, int trunc)
+{
+ uint32_t seq, tsn;
+ int number_entries, len;
+ struct sctp_association *asoc = &stcb->asoc;
+
+ seq = ntohl(req->request_seq);
+
+ /* now if its not a duplicate we process it */
+ if (asoc->str_reset_seq_in == seq) {
+ len = ntohs(req->ph.param_length);
+ number_entries = ((len - sizeof(struct sctp_stream_reset_out_request)) / sizeof(uint16_t));
+ /*
+ * the sender is resetting, handle the list issue.. we must
+ * a) verify if we can do the reset, if so no problem b) If
+ * we can't do the reset we must copy the request. c) queue
+ * it, and setup the data in processor to trigger it off
+ * when needed and dequeue all the queued data.
+ */
+ tsn = ntohl(req->send_reset_at_tsn);
+
+ /* move the reset action back one */
+ asoc->last_reset_action[1] = asoc->last_reset_action[0];
+ if ((asoc->local_strreset_support & SCTP_ENABLE_RESET_STREAM_REQ) == 0) {
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else if (trunc) {
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else if (SCTP_TSN_GE(asoc->cumulative_tsn, tsn)) {
+ /* we can do it now */
+ sctp_reset_in_stream(stcb, number_entries, req->list_of_streams);
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_PERFORMED;
+ } else {
+ /*
+ * we must queue it up and thus wait for the TSN's
+ * to arrive that are at or before tsn
+ */
+ struct sctp_stream_reset_list *liste;
+ int siz;
+
+ siz = sizeof(struct sctp_stream_reset_list) + (number_entries * sizeof(uint16_t));
+ SCTP_MALLOC(liste, struct sctp_stream_reset_list *,
+ siz, SCTP_M_STRESET);
+ if (liste == NULL) {
+ /* gak out of memory */
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ return;
+ }
+ liste->seq = seq;
+ liste->tsn = tsn;
+ liste->number_entries = number_entries;
+ memcpy(&liste->list_of_streams, req->list_of_streams, number_entries * sizeof(uint16_t));
+ TAILQ_INSERT_TAIL(&asoc->resetHead, liste, next_resp);
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_IN_PROGRESS;
+ }
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ asoc->str_reset_seq_in++;
+ } else if ((asoc->str_reset_seq_in - 1) == seq) {
+ /*
+ * one seq back, just echo back last action since my
+ * response was lost.
+ */
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ } else if ((asoc->str_reset_seq_in - 2) == seq) {
+ /*
+ * two seq back, just echo back last action since my
+ * response was lost.
+ */
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[1]);
+ } else {
+ sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_RESULT_ERR_BAD_SEQNO);
+ }
+}
+
+static void
+sctp_handle_str_reset_add_strm(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk,
+ struct sctp_stream_reset_add_strm *str_add)
+{
+ /*
+ * Peer is requesting to add more streams.
+ * If its within our max-streams we will
+ * allow it.
+ */
+ uint32_t num_stream, i;
+ uint32_t seq;
+ struct sctp_association *asoc = &stcb->asoc;
+ struct sctp_queued_to_read *ctl, *nctl;
+
+ /* Get the number. */
+ seq = ntohl(str_add->request_seq);
+ num_stream = ntohs(str_add->number_of_streams);
+ /* Now what would be the new total? */
+ if (asoc->str_reset_seq_in == seq) {
+ num_stream += stcb->asoc.streamincnt;
+ stcb->asoc.last_reset_action[1] = stcb->asoc.last_reset_action[0];
+ if ((asoc->local_strreset_support & SCTP_ENABLE_CHANGE_ASSOC_REQ) == 0) {
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else if ((num_stream > stcb->asoc.max_inbound_streams) ||
+ (num_stream > 0xffff)) {
+ /* We must reject it they ask for to many */
+ denied:
+ stcb->asoc.last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else {
+ /* Ok, we can do that :-) */
+ struct sctp_stream_in *oldstrm;
+
+ /* save off the old */
+ oldstrm = stcb->asoc.strmin;
+ SCTP_MALLOC(stcb->asoc.strmin, struct sctp_stream_in *,
+ (num_stream * sizeof(struct sctp_stream_in)),
+ SCTP_M_STRMI);
+ if (stcb->asoc.strmin == NULL) {
+ stcb->asoc.strmin = oldstrm;
+ goto denied;
+ }
+ /* copy off the old data */
+ for (i = 0; i < stcb->asoc.streamincnt; i++) {
+ TAILQ_INIT(&stcb->asoc.strmin[i].inqueue);
+ TAILQ_INIT(&stcb->asoc.strmin[i].uno_inqueue);
+ stcb->asoc.strmin[i].sid = i;
+ stcb->asoc.strmin[i].last_mid_delivered = oldstrm[i].last_mid_delivered;
+ stcb->asoc.strmin[i].delivery_started = oldstrm[i].delivery_started;
+ stcb->asoc.strmin[i].pd_api_started = oldstrm[i].pd_api_started;
+ /* now anything on those queues? */
+ TAILQ_FOREACH_SAFE(ctl, &oldstrm[i].inqueue, next_instrm, nctl) {
+ TAILQ_REMOVE(&oldstrm[i].inqueue, ctl, next_instrm);
+ TAILQ_INSERT_TAIL(&stcb->asoc.strmin[i].inqueue, ctl, next_instrm);
+ }
+ TAILQ_FOREACH_SAFE(ctl, &oldstrm[i].uno_inqueue, next_instrm, nctl) {
+ TAILQ_REMOVE(&oldstrm[i].uno_inqueue, ctl, next_instrm);
+ TAILQ_INSERT_TAIL(&stcb->asoc.strmin[i].uno_inqueue, ctl, next_instrm);
+ }
+ }
+ /* Init the new streams */
+ for (i = stcb->asoc.streamincnt; i < num_stream; i++) {
+ TAILQ_INIT(&stcb->asoc.strmin[i].inqueue);
+ TAILQ_INIT(&stcb->asoc.strmin[i].uno_inqueue);
+ stcb->asoc.strmin[i].sid = i;
+ stcb->asoc.strmin[i].last_mid_delivered = 0xffffffff;
+ stcb->asoc.strmin[i].pd_api_started = 0;
+ stcb->asoc.strmin[i].delivery_started = 0;
+ }
+ SCTP_FREE(oldstrm, SCTP_M_STRMI);
+ /* update the size */
+ stcb->asoc.streamincnt = num_stream;
+ stcb->asoc.last_reset_action[0] = SCTP_STREAM_RESET_RESULT_PERFORMED;
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_ADD, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ asoc->str_reset_seq_in++;
+ } else if ((asoc->str_reset_seq_in - 1) == seq) {
+ /*
+ * one seq back, just echo back last action since my
+ * response was lost.
+ */
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ } else if ((asoc->str_reset_seq_in - 2) == seq) {
+ /*
+ * two seq back, just echo back last action since my
+ * response was lost.
+ */
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[1]);
+ } else {
+ sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_RESULT_ERR_BAD_SEQNO);
+ }
+}
+
+static void
+sctp_handle_str_reset_add_out_strm(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk,
+ struct sctp_stream_reset_add_strm *str_add)
+{
+ /*
+ * Peer is requesting to add more streams.
+ * If its within our max-streams we will
+ * allow it.
+ */
+ uint16_t num_stream;
+ uint32_t seq;
+ struct sctp_association *asoc = &stcb->asoc;
+
+ /* Get the number. */
+ seq = ntohl(str_add->request_seq);
+ num_stream = ntohs(str_add->number_of_streams);
+ /* Now what would be the new total? */
+ if (asoc->str_reset_seq_in == seq) {
+ stcb->asoc.last_reset_action[1] = stcb->asoc.last_reset_action[0];
+ if ((asoc->local_strreset_support & SCTP_ENABLE_CHANGE_ASSOC_REQ) == 0) {
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ } else if (stcb->asoc.stream_reset_outstanding) {
+ /* We must reject it we have something pending */
+ stcb->asoc.last_reset_action[0] = SCTP_STREAM_RESET_RESULT_ERR_IN_PROGRESS;
+ } else {
+ /* Ok, we can do that :-) */
+ int mychk;
+ mychk = stcb->asoc.streamoutcnt;
+ mychk += num_stream;
+ if (mychk < 0x10000) {
+ stcb->asoc.last_reset_action[0] = SCTP_STREAM_RESET_RESULT_PERFORMED;
+ if (sctp_send_str_reset_req(stcb, 0, NULL, 0, 0, 1, num_stream, 0, 1)) {
+ stcb->asoc.last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ }
+ } else {
+ stcb->asoc.last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED;
+ }
+ }
+ sctp_add_stream_reset_result(chk, seq, stcb->asoc.last_reset_action[0]);
+ asoc->str_reset_seq_in++;
+ } else if ((asoc->str_reset_seq_in - 1) == seq) {
+ /*
+ * one seq back, just echo back last action since my
+ * response was lost.
+ */
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]);
+ } else if ((asoc->str_reset_seq_in - 2) == seq) {
+ /*
+ * two seq back, just echo back last action since my
+ * response was lost.
+ */
+ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[1]);
+ } else {
+ sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_RESULT_ERR_BAD_SEQNO);
+ }
+}
+
+#ifdef __GNUC__
+__attribute__ ((noinline))
+#endif
+static int
+sctp_handle_stream_reset(struct sctp_tcb *stcb, struct mbuf *m, int offset,
+ struct sctp_chunkhdr *ch_req)
+{
+ uint16_t remaining_length, param_len, ptype;
+ struct sctp_paramhdr pstore;
+ uint8_t cstore[SCTP_CHUNK_BUFFER_SIZE];
+ uint32_t seq = 0;
+ int num_req = 0;
+ int trunc = 0;
+ struct sctp_tmit_chunk *chk;
+ struct sctp_chunkhdr *ch;
+ struct sctp_paramhdr *ph;
+ int ret_code = 0;
+ int num_param = 0;
+
+ /* now it may be a reset or a reset-response */
+ remaining_length = ntohs(ch_req->chunk_length) - sizeof(struct sctp_chunkhdr);
+
+ /* setup for adding the response */
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ return (ret_code);
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_STREAM_RESET;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = 0;
+ chk->asoc = &stcb->asoc;
+ chk->no_fr_allowed = 0;
+ chk->book_size = chk->send_size = sizeof(struct sctp_chunkhdr);
+ chk->book_size_scale = 0;
+ chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (chk->data == NULL) {
+ strres_nochunk:
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return (ret_code);
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+
+ /* setup chunk parameters */
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->whoTo = NULL;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ ch->chunk_type = SCTP_STREAM_RESET;
+ ch->chunk_flags = 0;
+ ch->chunk_length = htons(chk->send_size);
+ SCTP_BUF_LEN(chk->data) = SCTP_SIZE32(chk->send_size);
+ offset += sizeof(struct sctp_chunkhdr);
+ while (remaining_length >= sizeof(struct sctp_paramhdr)) {
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, sizeof(pstore), (uint8_t *)&pstore);
+ if (ph == NULL) {
+ /* TSNH */
+ break;
+ }
+ param_len = ntohs(ph->param_length);
+ if ((param_len > remaining_length) ||
+ (param_len < (sizeof(struct sctp_paramhdr) + sizeof(uint32_t)))) {
+ /* bad parameter length */
+ break;
+ }
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, min(param_len, sizeof(cstore)),
+ (uint8_t *)&cstore);
+ if (ph == NULL) {
+ /* TSNH */
+ break;
+ }
+ ptype = ntohs(ph->param_type);
+ num_param++;
+ if (param_len > sizeof(cstore)) {
+ trunc = 1;
+ } else {
+ trunc = 0;
+ }
+ if (num_param > SCTP_MAX_RESET_PARAMS) {
+ /* hit the max of parameters already sorry.. */
+ break;
+ }
+ if (ptype == SCTP_STR_RESET_OUT_REQUEST) {
+ struct sctp_stream_reset_out_request *req_out;
+
+ if (param_len < sizeof(struct sctp_stream_reset_out_request)) {
+ break;
+ }
+ req_out = (struct sctp_stream_reset_out_request *)ph;
+ num_req++;
+ if (stcb->asoc.stream_reset_outstanding) {
+ seq = ntohl(req_out->response_seq);
+ if (seq == stcb->asoc.str_reset_seq_out) {
+ /* implicit ack */
+ (void)sctp_handle_stream_reset_response(stcb, seq, SCTP_STREAM_RESET_RESULT_PERFORMED, NULL);
+ }
+ }
+ sctp_handle_str_reset_request_out(stcb, chk, req_out, trunc);
+ } else if (ptype == SCTP_STR_RESET_ADD_OUT_STREAMS) {
+ struct sctp_stream_reset_add_strm *str_add;
+
+ if (param_len < sizeof(struct sctp_stream_reset_add_strm)) {
+ break;
+ }
+ str_add = (struct sctp_stream_reset_add_strm *)ph;
+ num_req++;
+ sctp_handle_str_reset_add_strm(stcb, chk, str_add);
+ } else if (ptype == SCTP_STR_RESET_ADD_IN_STREAMS) {
+ struct sctp_stream_reset_add_strm *str_add;
+
+ if (param_len < sizeof(struct sctp_stream_reset_add_strm)) {
+ break;
+ }
+ str_add = (struct sctp_stream_reset_add_strm *)ph;
+ num_req++;
+ sctp_handle_str_reset_add_out_strm(stcb, chk, str_add);
+ } else if (ptype == SCTP_STR_RESET_IN_REQUEST) {
+ struct sctp_stream_reset_in_request *req_in;
+
+ num_req++;
+ req_in = (struct sctp_stream_reset_in_request *)ph;
+ sctp_handle_str_reset_request_in(stcb, chk, req_in, trunc);
+ } else if (ptype == SCTP_STR_RESET_TSN_REQUEST) {
+ struct sctp_stream_reset_tsn_request *req_tsn;
+
+ num_req++;
+ req_tsn = (struct sctp_stream_reset_tsn_request *)ph;
+ if (sctp_handle_str_reset_request_tsn(stcb, chk, req_tsn)) {
+ ret_code = 1;
+ goto strres_nochunk;
+ }
+ /* no more */
+ break;
+ } else if (ptype == SCTP_STR_RESET_RESPONSE) {
+ struct sctp_stream_reset_response *resp;
+ uint32_t result;
+
+ if (param_len < sizeof(struct sctp_stream_reset_response)) {
+ break;
+ }
+ resp = (struct sctp_stream_reset_response *)ph;
+ seq = ntohl(resp->response_seq);
+ result = ntohl(resp->result);
+ if (sctp_handle_stream_reset_response(stcb, seq, result, resp)) {
+ ret_code = 1;
+ goto strres_nochunk;
+ }
+ } else {
+ break;
+ }
+ offset += SCTP_SIZE32(param_len);
+ if (remaining_length >= SCTP_SIZE32(param_len)) {
+ remaining_length -= SCTP_SIZE32(param_len);
+ } else {
+ remaining_length = 0;
+ }
+ }
+ if (num_req == 0) {
+ /* we have no response free the stuff */
+ goto strres_nochunk;
+ }
+ /* ok we have a chunk to link in */
+ TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue,
+ chk,
+ sctp_next);
+ stcb->asoc.ctrl_queue_cnt++;
+ return (ret_code);
+}
+
+/*
+ * Handle a router or endpoints report of a packet loss, there are two ways
+ * to handle this, either we get the whole packet and must disect it
+ * ourselves (possibly with truncation and or corruption) or it is a summary
+ * from a middle box that did the disecting for us.
+ */
+static void
+sctp_handle_packet_dropped(struct sctp_pktdrop_chunk *cp,
+ struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t limit)
+{
+ struct sctp_chunk_desc desc;
+ struct sctp_chunkhdr *chk_hdr;
+ struct sctp_data_chunk *data_chunk;
+ struct sctp_idata_chunk *idata_chunk;
+ uint32_t bottle_bw, on_queue;
+ uint32_t offset, chk_len;
+ uint16_t pktdrp_len;
+ uint8_t pktdrp_flags;
+
+ KASSERT(sizeof(struct sctp_pktdrop_chunk) <= limit,
+ ("PKTDROP chunk too small"));
+ pktdrp_flags = cp->ch.chunk_flags;
+ pktdrp_len = ntohs(cp->ch.chunk_length);
+ KASSERT(limit <= pktdrp_len, ("Inconsistent limit"));
+ if (pktdrp_flags & SCTP_PACKET_TRUNCATED) {
+ if (ntohs(cp->trunc_len) <= pktdrp_len - sizeof(struct sctp_pktdrop_chunk)) {
+ /* The peer plays games with us. */
+ return;
+ }
+ }
+ limit -= sizeof(struct sctp_pktdrop_chunk);
+ offset = 0;
+ if (offset == limit) {
+ if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
+ SCTP_STAT_INCR(sctps_pdrpbwrpt);
+ }
+ } else if (offset + sizeof(struct sctphdr) > limit) {
+ /* Only a partial SCTP common header. */
+ SCTP_STAT_INCR(sctps_pdrpcrupt);
+ offset = limit;
+ } else {
+ /* XXX: Check embedded SCTP common header. */
+ offset += sizeof(struct sctphdr);
+ }
+ /* Now parse through the chunks themselves. */
+ while (offset < limit) {
+ if (offset + sizeof(struct sctp_chunkhdr) > limit) {
+ SCTP_STAT_INCR(sctps_pdrpcrupt);
+ break;
+ }
+ chk_hdr = (struct sctp_chunkhdr *)(cp->data + offset);
+ desc.chunk_type = chk_hdr->chunk_type;
+ /* get amount we need to move */
+ chk_len = (uint32_t)ntohs(chk_hdr->chunk_length);
+ if (chk_len < sizeof(struct sctp_chunkhdr)) {
+ /* Someone is lying... */
+ break;
+ }
+ if (desc.chunk_type == SCTP_DATA) {
+ if (stcb->asoc.idata_supported) {
+ /* Some is playing games with us. */
+ break;
+ }
+ if (chk_len <= sizeof(struct sctp_data_chunk)) {
+ /* Some is playing games with us. */
+ break;
+ }
+ if (chk_len < sizeof(struct sctp_data_chunk) + SCTP_NUM_DB_TO_VERIFY) {
+ /* Not enough data bytes available in the chunk. */
+ SCTP_STAT_INCR(sctps_pdrpnedat);
+ goto next_chunk;
+ }
+ if (offset + sizeof(struct sctp_data_chunk) + SCTP_NUM_DB_TO_VERIFY > limit) {
+ /* Not enough data in buffer. */
+ break;
+ }
+ data_chunk = (struct sctp_data_chunk *)(cp->data + offset);
+ memcpy(desc.data_bytes, data_chunk + 1, SCTP_NUM_DB_TO_VERIFY);
+ desc.tsn_ifany = data_chunk->dp.tsn;
+ if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
+ SCTP_STAT_INCR(sctps_pdrpmbda);
+ }
+ } else if (desc.chunk_type == SCTP_IDATA) {
+ if (!stcb->asoc.idata_supported) {
+ /* Some is playing games with us. */
+ break;
+ }
+ if (chk_len <= sizeof(struct sctp_idata_chunk)) {
+ /* Some is playing games with us. */
+ break;
+ }
+ if (chk_len < sizeof(struct sctp_idata_chunk) + SCTP_NUM_DB_TO_VERIFY) {
+ /* Not enough data bytes available in the chunk. */
+ SCTP_STAT_INCR(sctps_pdrpnedat);
+ goto next_chunk;
+ }
+ if (offset + sizeof(struct sctp_idata_chunk) + SCTP_NUM_DB_TO_VERIFY > limit) {
+ /* Not enough data in buffer. */
+ break;
+ }
+ idata_chunk = (struct sctp_idata_chunk *)(cp->data + offset);
+ memcpy(desc.data_bytes, idata_chunk + 1, SCTP_NUM_DB_TO_VERIFY);
+ desc.tsn_ifany = idata_chunk->dp.tsn;
+ if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
+ SCTP_STAT_INCR(sctps_pdrpmbda);
+ }
+ } else {
+ if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
+ SCTP_STAT_INCR(sctps_pdrpmbct);
+ }
+ }
+ if (process_chunk_drop(stcb, &desc, net, pktdrp_flags)) {
+ SCTP_STAT_INCR(sctps_pdrppdbrk);
+ break;
+ }
+next_chunk:
+ offset += SCTP_SIZE32(chk_len);
+ }
+ /* Now update any rwnd --- possibly */
+ if ((pktdrp_flags & SCTP_FROM_MIDDLE_BOX) == 0) {
+ /* From a peer, we get a rwnd report */
+ uint32_t a_rwnd;
+
+ SCTP_STAT_INCR(sctps_pdrpfehos);
+
+ bottle_bw = ntohl(cp->bottle_bw);
+ on_queue = ntohl(cp->current_onq);
+ if (bottle_bw && on_queue) {
+ /* a rwnd report is in here */
+ if (bottle_bw > on_queue)
+ a_rwnd = bottle_bw - on_queue;
+ else
+ a_rwnd = 0;
+
+ if (a_rwnd == 0)
+ stcb->asoc.peers_rwnd = 0;
+ else {
+ if (a_rwnd > stcb->asoc.total_flight) {
+ stcb->asoc.peers_rwnd =
+ a_rwnd - stcb->asoc.total_flight;
+ } else {
+ stcb->asoc.peers_rwnd = 0;
+ }
+ if (stcb->asoc.peers_rwnd <
+ stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ stcb->asoc.peers_rwnd = 0;
+ }
+ }
+ }
+ } else {
+ SCTP_STAT_INCR(sctps_pdrpfmbox);
+ }
+
+ /* now middle boxes in sat networks get a cwnd bump */
+ if ((pktdrp_flags & SCTP_FROM_MIDDLE_BOX) &&
+ (stcb->asoc.sat_t3_loss_recovery == 0) &&
+ (stcb->asoc.sat_network)) {
+ /*
+ * This is debatable but for sat networks it makes sense
+ * Note if a T3 timer has went off, we will prohibit any
+ * changes to cwnd until we exit the t3 loss recovery.
+ */
+ stcb->asoc.cc_functions.sctp_cwnd_update_after_packet_dropped(stcb,
+ net, cp, &bottle_bw, &on_queue);
+ }
+}
+
+/*
+ * handles all control chunks in a packet inputs: - m: mbuf chain, assumed to
+ * still contain IP/SCTP header - stcb: is the tcb found for this packet -
+ * offset: offset into the mbuf chain to first chunkhdr - length: is the
+ * length of the complete packet outputs: - length: modified to remaining
+ * length after control processing - netp: modified to new sctp_nets after
+ * cookie-echo processing - return NULL to discard the packet (ie. no asoc,
+ * bad packet,...) otherwise return the tcb for this packet
+ */
+#ifdef __GNUC__
+__attribute__ ((noinline))
+#endif
+static struct sctp_tcb *
+sctp_process_control(struct mbuf *m, int iphlen, int *offset, int length,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_chunkhdr *ch, struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb, struct sctp_nets **netp, int *fwd_tsn_seen,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_association *asoc;
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+ uint32_t vtag_in;
+ int num_chunks = 0; /* number of control chunks processed */
+ uint32_t chk_length, contiguous;
+ int ret;
+ int abort_no_unlock = 0;
+ int ecne_seen = 0;
+ int abort_flag;
+ /*
+ * How big should this be, and should it be alloc'd? Lets try the
+ * d-mtu-ceiling for now (2k) and that should hopefully work ...
+ * until we get into jumbo grams and such..
+ */
+ uint8_t chunk_buf[SCTP_CHUNK_BUFFER_SIZE];
+ int got_auth = 0;
+ uint32_t auth_offset = 0, auth_len = 0;
+ int auth_skipped = 0;
+ int asconf_cnt = 0;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_INPUT1, "sctp_process_control: iphlen=%u, offset=%u, length=%u stcb:%p\n",
+ iphlen, *offset, length, (void *)stcb);
+
+ if (stcb) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ }
+ /* validate chunk header length... */
+ if (ntohs(ch->chunk_length) < sizeof(*ch)) {
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Invalid header length %d\n",
+ ntohs(ch->chunk_length));
+ *offset = length;
+ return (stcb);
+ }
+ /*
+ * validate the verification tag
+ */
+ vtag_in = ntohl(sh->v_tag);
+
+ if (ch->chunk_type == SCTP_INITIATION) {
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Its an INIT of len:%d vtag:%x\n",
+ ntohs(ch->chunk_length), vtag_in);
+ if (vtag_in != 0) {
+ /* protocol error- silently discard... */
+ SCTP_STAT_INCR(sctps_badvtag);
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return (NULL);
+ }
+ } else if (ch->chunk_type != SCTP_COOKIE_ECHO) {
+ /*
+ * If there is no stcb, skip the AUTH chunk and process
+ * later after a stcb is found (to validate the lookup was
+ * valid.
+ */
+ if ((ch->chunk_type == SCTP_AUTHENTICATION) &&
+ (stcb == NULL) &&
+ (inp->auth_supported == 1)) {
+ /* save this chunk for later processing */
+ auth_skipped = 1;
+ auth_offset = *offset;
+ auth_len = ntohs(ch->chunk_length);
+
+ /* (temporarily) move past this chunk */
+ *offset += SCTP_SIZE32(auth_len);
+ if (*offset >= length) {
+ /* no more data left in the mbuf chain */
+ *offset = length;
+ return (NULL);
+ }
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_chunkhdr), chunk_buf);
+ }
+ if (ch == NULL) {
+ /* Help */
+ *offset = length;
+ return (stcb);
+ }
+ if (ch->chunk_type == SCTP_COOKIE_ECHO) {
+ goto process_control_chunks;
+ }
+ /*
+ * first check if it's an ASCONF with an unknown src addr we
+ * need to look inside to find the association
+ */
+ if (ch->chunk_type == SCTP_ASCONF && stcb == NULL) {
+ struct sctp_chunkhdr *asconf_ch = ch;
+ uint32_t asconf_offset = 0, asconf_len = 0;
+
+ /* inp's refcount may be reduced */
+ SCTP_INP_INCR_REF(inp);
+
+ asconf_offset = *offset;
+ do {
+ asconf_len = ntohs(asconf_ch->chunk_length);
+ if (asconf_len < sizeof(struct sctp_asconf_paramhdr))
+ break;
+ stcb = sctp_findassociation_ep_asconf(m,
+ *offset,
+ dst,
+ sh, &inp, netp, vrf_id);
+ if (stcb != NULL)
+ break;
+ asconf_offset += SCTP_SIZE32(asconf_len);
+ asconf_ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, asconf_offset,
+ sizeof(struct sctp_chunkhdr), chunk_buf);
+ } while (asconf_ch != NULL && asconf_ch->chunk_type == SCTP_ASCONF);
+ if (stcb == NULL) {
+ /*
+ * reduce inp's refcount if not reduced in
+ * sctp_findassociation_ep_asconf().
+ */
+ SCTP_INP_DECR_REF(inp);
+ }
+
+ /* now go back and verify any auth chunk to be sure */
+ if (auth_skipped && (stcb != NULL)) {
+ struct sctp_auth_chunk *auth;
+
+ if (auth_len <= SCTP_CHUNK_BUFFER_SIZE) {
+ auth = (struct sctp_auth_chunk *)sctp_m_getptr(m, auth_offset, auth_len, chunk_buf);
+ got_auth = 1;
+ auth_skipped = 0;
+ } else {
+ auth = NULL;
+ }
+ if ((auth == NULL) || sctp_handle_auth(stcb, auth, m,
+ auth_offset)) {
+ /* auth HMAC failed so dump it */
+ *offset = length;
+ return (stcb);
+ } else {
+ /* remaining chunks are HMAC checked */
+ stcb->asoc.authenticated = 1;
+ }
+ }
+ }
+ if (stcb == NULL) {
+ SCTP_SNPRINTF(msg, sizeof(msg), "OOTB, %s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ /* no association, so it's out of the blue... */
+ sctp_handle_ootb(m, iphlen, *offset, src, dst, sh, inp, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ *offset = length;
+ return (NULL);
+ }
+ asoc = &stcb->asoc;
+ /* ABORT and SHUTDOWN can use either v_tag... */
+ if ((ch->chunk_type == SCTP_ABORT_ASSOCIATION) ||
+ (ch->chunk_type == SCTP_SHUTDOWN_COMPLETE) ||
+ (ch->chunk_type == SCTP_PACKET_DROPPED)) {
+ /* Take the T-bit always into account. */
+ if ((((ch->chunk_flags & SCTP_HAD_NO_TCB) == 0) &&
+ (vtag_in == asoc->my_vtag)) ||
+ (((ch->chunk_flags & SCTP_HAD_NO_TCB) == SCTP_HAD_NO_TCB) &&
+ (asoc->peer_vtag != htonl(0)) &&
+ (vtag_in == asoc->peer_vtag))) {
+ /* this is valid */
+ } else {
+ /* drop this packet... */
+ SCTP_STAT_INCR(sctps_badvtag);
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return (NULL);
+ }
+ } else {
+ /* for all other chunks, vtag must match */
+ if (vtag_in != asoc->my_vtag) {
+ /* invalid vtag... */
+ SCTPDBG(SCTP_DEBUG_INPUT3,
+ "invalid vtag: %xh, expect %xh\n",
+ vtag_in, asoc->my_vtag);
+ SCTP_STAT_INCR(sctps_badvtag);
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ }
+ } /* end if !SCTP_COOKIE_ECHO */
+ /*
+ * process all control chunks...
+ */
+ if (((ch->chunk_type == SCTP_SELECTIVE_ACK) ||
+ (ch->chunk_type == SCTP_NR_SELECTIVE_ACK) ||
+ (ch->chunk_type == SCTP_HEARTBEAT_REQUEST)) &&
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ /* implied cookie-ack.. we must have lost the ack */
+ sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb,
+ *netp);
+ }
+
+ process_control_chunks:
+ while (IS_SCTP_CONTROL(ch)) {
+ /* validate chunk length */
+ chk_length = ntohs(ch->chunk_length);
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_process_control: processing a chunk type=%u, len=%u\n",
+ ch->chunk_type, chk_length);
+ SCTP_LTRACE_CHK(inp, stcb, ch->chunk_type, chk_length);
+ if (chk_length < sizeof(*ch) ||
+ (*offset + (int)chk_length) > length) {
+ *offset = length;
+ return (stcb);
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_incontrolchunks);
+ /*
+ * INIT and INIT-ACK only gets the init ack "header" portion
+ * only because we don't have to process the peer's COOKIE.
+ * All others get a complete chunk.
+ */
+ switch (ch->chunk_type) {
+ case SCTP_INITIATION:
+ contiguous = sizeof(struct sctp_init_chunk);
+ break;
+ case SCTP_INITIATION_ACK:
+ contiguous = sizeof(struct sctp_init_ack_chunk);
+ break;
+ default:
+ contiguous = min(chk_length, sizeof(chunk_buf));
+ break;
+ }
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ contiguous,
+ chunk_buf);
+ if (ch == NULL) {
+ *offset = length;
+ return (stcb);
+ }
+
+ num_chunks++;
+ /* Save off the last place we got a control from */
+ if (stcb != NULL) {
+ if (((netp != NULL) && (*netp != NULL)) || (ch->chunk_type == SCTP_ASCONF)) {
+ /*
+ * allow last_control to be NULL if
+ * ASCONF... ASCONF processing will find the
+ * right net later
+ */
+ if ((netp != NULL) && (*netp != NULL))
+ stcb->asoc.last_control_chunk_from = *netp;
+ }
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xB0, ch->chunk_type);
+#endif
+
+ /* check to see if this chunk required auth, but isn't */
+ if ((stcb != NULL) &&
+ sctp_auth_is_required_chunk(ch->chunk_type, stcb->asoc.local_auth_chunks) &&
+ !stcb->asoc.authenticated) {
+ /* "silently" ignore */
+ SCTP_STAT_INCR(sctps_recvauthmissing);
+ goto next_chunk;
+ }
+ switch (ch->chunk_type) {
+ case SCTP_INITIATION:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_INIT\n");
+ /* The INIT chunk must be the only chunk. */
+ if ((num_chunks > 1) ||
+ (length - *offset > (int)SCTP_SIZE32(chk_length))) {
+ /*
+ * RFC 4960bis requires stopping the
+ * processing of the packet.
+ */
+ *offset = length;
+ return (stcb);
+ }
+ /* Honor our resource limit. */
+ if (chk_length > SCTP_LARGEST_INIT_ACCEPTED) {
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ sctp_send_abort(m, iphlen, src, dst, sh, 0, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ *offset = length;
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return (NULL);
+ }
+ sctp_handle_init(m, iphlen, *offset, src, dst, sh,
+ (struct sctp_init_chunk *)ch, inp,
+ stcb, *netp,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ *offset = length;
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return (NULL);
+ break;
+ case SCTP_PAD_CHUNK:
+ break;
+ case SCTP_INITIATION_ACK:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_INIT_ACK\n");
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* We are not interested anymore */
+ if ((stcb != NULL) && (stcb->asoc.total_output_queue_size)) {
+ ;
+ } else {
+ *offset = length;
+ if (stcb != NULL) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_29);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ return (NULL);
+ }
+ }
+ /* The INIT-ACK chunk must be the only chunk. */
+ if ((num_chunks > 1) ||
+ (length - *offset > (int)SCTP_SIZE32(chk_length))) {
+ *offset = length;
+ return (stcb);
+ }
+ if ((netp != NULL) && (*netp != NULL)) {
+ ret = sctp_handle_init_ack(m, iphlen, *offset,
+ src, dst, sh,
+ (struct sctp_init_ack_chunk *)ch,
+ stcb, *netp,
+ &abort_no_unlock,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id);
+ } else {
+ ret = -1;
+ }
+ *offset = length;
+ if (abort_no_unlock) {
+ return (NULL);
+ }
+ /*
+ * Special case, I must call the output routine to
+ * get the cookie echoed
+ */
+ if ((stcb != NULL) && (ret == 0)) {
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_CONTROL_PROC, SCTP_SO_NOT_LOCKED);
+ }
+ return (stcb);
+ break;
+ case SCTP_SELECTIVE_ACK:
+ case SCTP_NR_SELECTIVE_ACK:
+ {
+ int abort_now = 0;
+ uint32_t a_rwnd, cum_ack;
+ uint16_t num_seg, num_nr_seg, num_dup;
+ uint8_t flags;
+ int offset_seg, offset_dup;
+
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s\n",
+ ch->chunk_type == SCTP_SELECTIVE_ACK ? "SCTP_SACK" : "SCTP_NR_SACK");
+ SCTP_STAT_INCR(sctps_recvsacks);
+ if (stcb == NULL) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "No stcb when processing %s chunk\n",
+ (ch->chunk_type == SCTP_SELECTIVE_ACK) ? "SCTP_SACK" : "SCTP_NR_SACK");
+ break;
+ }
+ if (ch->chunk_type == SCTP_SELECTIVE_ACK) {
+ if (chk_length < sizeof(struct sctp_sack_chunk)) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Bad size on SACK chunk, too small\n");
+ break;
+ }
+ } else {
+ if (stcb->asoc.nrsack_supported == 0) {
+ goto unknown_chunk;
+ }
+ if (chk_length < sizeof(struct sctp_nr_sack_chunk)) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Bad size on NR_SACK chunk, too small\n");
+ break;
+ }
+ }
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_ACK_SENT) {
+ /*-
+ * If we have sent a shutdown-ack, we will pay no
+ * attention to a sack sent in to us since
+ * we don't care anymore.
+ */
+ break;
+ }
+ flags = ch->chunk_flags;
+ if (ch->chunk_type == SCTP_SELECTIVE_ACK) {
+ struct sctp_sack_chunk *sack;
+
+ sack = (struct sctp_sack_chunk *)ch;
+ cum_ack = ntohl(sack->sack.cum_tsn_ack);
+ num_seg = ntohs(sack->sack.num_gap_ack_blks);
+ num_nr_seg = 0;
+ num_dup = ntohs(sack->sack.num_dup_tsns);
+ a_rwnd = ntohl(sack->sack.a_rwnd);
+ if (sizeof(struct sctp_sack_chunk) +
+ num_seg * sizeof(struct sctp_gap_ack_block) +
+ num_dup * sizeof(uint32_t) != chk_length) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Bad size of SACK chunk\n");
+ break;
+ }
+ offset_seg = *offset + sizeof(struct sctp_sack_chunk);
+ offset_dup = offset_seg + num_seg * sizeof(struct sctp_gap_ack_block);
+ } else {
+ struct sctp_nr_sack_chunk *nr_sack;
+
+ nr_sack = (struct sctp_nr_sack_chunk *)ch;
+ cum_ack = ntohl(nr_sack->nr_sack.cum_tsn_ack);
+ num_seg = ntohs(nr_sack->nr_sack.num_gap_ack_blks);
+ num_nr_seg = ntohs(nr_sack->nr_sack.num_nr_gap_ack_blks);
+ num_dup = ntohs(nr_sack->nr_sack.num_dup_tsns);
+ a_rwnd = ntohl(nr_sack->nr_sack.a_rwnd);
+ if (sizeof(struct sctp_nr_sack_chunk) +
+ (num_seg + num_nr_seg) * sizeof(struct sctp_gap_ack_block) +
+ num_dup * sizeof(uint32_t) != chk_length) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Bad size of NR_SACK chunk\n");
+ break;
+ }
+ offset_seg = *offset + sizeof(struct sctp_nr_sack_chunk);
+ offset_dup = offset_seg + (num_seg + num_nr_seg) * sizeof(struct sctp_gap_ack_block);
+ }
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s process cum_ack:%x num_seg:%d a_rwnd:%d\n",
+ (ch->chunk_type == SCTP_SELECTIVE_ACK) ? "SCTP_SACK" : "SCTP_NR_SACK",
+ cum_ack, num_seg, a_rwnd);
+ stcb->asoc.seen_a_sack_this_pkt = 1;
+ if ((stcb->asoc.pr_sctp_cnt == 0) &&
+ (num_seg == 0) && (num_nr_seg == 0) &&
+ SCTP_TSN_GE(cum_ack, stcb->asoc.last_acked_seq) &&
+ (stcb->asoc.saw_sack_with_frags == 0) &&
+ (stcb->asoc.saw_sack_with_nr_frags == 0) &&
+ (!TAILQ_EMPTY(&stcb->asoc.sent_queue))) {
+ /*
+ * We have a SIMPLE sack having no
+ * prior segments and data on sent
+ * queue to be acked. Use the
+ * faster path sack processing. We
+ * also allow window update sacks
+ * with no missing segments to go
+ * this way too.
+ */
+ sctp_express_handle_sack(stcb, cum_ack, a_rwnd,
+ &abort_now, ecne_seen);
+ } else {
+ if ((netp != NULL) && (*netp != NULL)) {
+ sctp_handle_sack(m, offset_seg, offset_dup, stcb,
+ num_seg, num_nr_seg, num_dup, &abort_now, flags,
+ cum_ack, a_rwnd, ecne_seen);
+ }
+ }
+ if (abort_now) {
+ /* ABORT signal from sack processing */
+ *offset = length;
+ return (NULL);
+ }
+ if (TAILQ_EMPTY(&stcb->asoc.send_queue) &&
+ TAILQ_EMPTY(&stcb->asoc.sent_queue) &&
+ (stcb->asoc.stream_queue_cnt == 0)) {
+ sctp_ulp_notify(SCTP_NOTIFY_SENDER_DRY, stcb, 0, NULL, SCTP_SO_NOT_LOCKED);
+ }
+ break;
+ }
+ case SCTP_HEARTBEAT_REQUEST:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_HEARTBEAT\n");
+ if ((stcb != NULL) && (netp != NULL) && (*netp != NULL)) {
+ SCTP_STAT_INCR(sctps_recvheartbeat);
+ sctp_send_heartbeat_ack(stcb, m, *offset,
+ chk_length, *netp);
+ }
+ break;
+ case SCTP_HEARTBEAT_ACK:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_HEARTBEAT_ACK\n");
+ if ((stcb == NULL) || (chk_length != sizeof(struct sctp_heartbeat_chunk))) {
+ /* Its not ours */
+ break;
+ }
+ SCTP_STAT_INCR(sctps_recvheartbeatack);
+ if ((netp != NULL) && (*netp != NULL)) {
+ sctp_handle_heartbeat_ack((struct sctp_heartbeat_chunk *)ch,
+ stcb, *netp);
+ }
+ break;
+ case SCTP_ABORT_ASSOCIATION:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_ABORT, stcb %p\n",
+ (void *)stcb);
+ *offset = length;
+ if ((stcb != NULL) && (netp != NULL) && (*netp != NULL)) {
+ if (sctp_handle_abort((struct sctp_abort_chunk *)ch, stcb, *netp)) {
+ return (NULL);
+ } else {
+ return (stcb);
+ }
+ } else {
+ return (NULL);
+ }
+ break;
+ case SCTP_SHUTDOWN:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_SHUTDOWN, stcb %p\n",
+ (void *)stcb);
+ if ((stcb == NULL) || (chk_length != sizeof(struct sctp_shutdown_chunk))) {
+ break;
+ }
+ if ((netp != NULL) && (*netp != NULL)) {
+ abort_flag = 0;
+ sctp_handle_shutdown((struct sctp_shutdown_chunk *)ch,
+ stcb, *netp, &abort_flag);
+ if (abort_flag) {
+ *offset = length;
+ return (NULL);
+ }
+ }
+ break;
+ case SCTP_SHUTDOWN_ACK:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_SHUTDOWN_ACK, stcb %p\n", (void *)stcb);
+ if ((chk_length == sizeof(struct sctp_shutdown_ack_chunk)) &&
+ (stcb != NULL) && (netp != NULL) && (*netp != NULL)) {
+ sctp_handle_shutdown_ack((struct sctp_shutdown_ack_chunk *)ch, stcb, *netp);
+ *offset = length;
+ return (NULL);
+ }
+ break;
+ case SCTP_OPERATION_ERROR:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_OP_ERR\n");
+ if ((stcb != NULL) && (netp != NULL) && (*netp != NULL) &&
+ sctp_handle_error(ch, stcb, *netp, contiguous) < 0) {
+ *offset = length;
+ return (NULL);
+ }
+ break;
+ case SCTP_COOKIE_ECHO:
+ SCTPDBG(SCTP_DEBUG_INPUT3,
+ "SCTP_COOKIE_ECHO, stcb %p\n", (void *)stcb);
+ if ((stcb != NULL) && (stcb->asoc.total_output_queue_size > 0)) {
+ ;
+ } else {
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* We are not interested anymore */
+ abend:
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ }
+ /*-
+ * First are we accepting? We do this again here
+ * since it is possible that a previous endpoint WAS
+ * listening responded to a INIT-ACK and then
+ * closed. We opened and bound.. and are now no
+ * longer listening.
+ *
+ * XXXGL: notes on checking listen queue length.
+ * 1) SCTP_IS_LISTENING() doesn't necessarily mean
+ * SOLISTENING(), because a listening "UDP type"
+ * socket isn't listening in terms of the socket
+ * layer. It is a normal data flow socket, that
+ * can fork off new connections. Thus, we should
+ * look into sol_qlen only in case we are !UDP.
+ * 2) Checking sol_qlen in general requires locking
+ * the socket, and this code lacks that.
+ */
+ if ((stcb == NULL) &&
+ (!SCTP_IS_LISTENING(inp) ||
+ (((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) == 0) &&
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ inp->sctp_socket->sol_qlen >= inp->sctp_socket->sol_qlimit))) {
+#else
+ inp->sctp_socket->so_qlen >= inp->sctp_socket->so_qlimit))) {
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (SCTP_BASE_SYSCTL(sctp_abort_if_one_2_one_hits_limit))) {
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+ sctp_abort_association(inp, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ }
+ *offset = length;
+ return (NULL);
+ } else {
+ struct mbuf *ret_buf;
+ struct sctp_inpcb *linp;
+ struct sctp_tmit_chunk *chk;
+
+ if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE |
+ SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ goto abend;
+ }
+
+ if (stcb) {
+ linp = NULL;
+ } else {
+ linp = inp;
+ }
+
+ if (linp != NULL) {
+ SCTP_ASOC_CREATE_LOCK(linp);
+ }
+
+ if (netp != NULL) {
+ struct sctp_tcb *locked_stcb;
+
+ locked_stcb = stcb;
+ ret_buf =
+ sctp_handle_cookie_echo(m, iphlen,
+ *offset,
+ src, dst,
+ sh,
+ (struct sctp_cookie_echo_chunk *)ch,
+ &inp, &stcb, netp,
+ auth_skipped,
+ auth_offset,
+ auth_len,
+ &locked_stcb,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype,
+ mflowid,
+#endif
+ vrf_id,
+ port);
+ if ((locked_stcb != NULL) && (locked_stcb != stcb)) {
+ SCTP_TCB_UNLOCK(locked_stcb);
+ }
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ }
+ } else {
+ ret_buf = NULL;
+ }
+ if (linp != NULL) {
+ SCTP_ASOC_CREATE_UNLOCK(linp);
+ }
+ if (ret_buf == NULL) {
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTPDBG(SCTP_DEBUG_INPUT3,
+ "GAK, null buffer\n");
+ *offset = length;
+ return (NULL);
+ }
+ /* if AUTH skipped, see if it verified... */
+ if (auth_skipped) {
+ got_auth = 1;
+ auth_skipped = 0;
+ }
+ /* Restart the timer if we have pending data */
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (chk != NULL) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, chk->whoTo);
+ }
+ }
+ break;
+ case SCTP_COOKIE_ACK:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_COOKIE_ACK, stcb %p\n", (void *)stcb);
+ if ((stcb == NULL) || chk_length != sizeof(struct sctp_cookie_ack_chunk)) {
+ break;
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* We are not interested anymore */
+ if ((stcb) && (stcb->asoc.total_output_queue_size)) {
+ ;
+ } else if (stcb) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_30);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ *offset = length;
+ return (NULL);
+ }
+ }
+ if ((netp != NULL) && (*netp != NULL)) {
+ sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb, *netp);
+ }
+ break;
+ case SCTP_ECN_ECHO:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_ECN_ECHO\n");
+ if (stcb == NULL) {
+ break;
+ }
+ if (stcb->asoc.ecn_supported == 0) {
+ goto unknown_chunk;
+ }
+ if ((chk_length != sizeof(struct sctp_ecne_chunk)) &&
+ (chk_length != sizeof(struct old_sctp_ecne_chunk))) {
+ break;
+ }
+ sctp_handle_ecn_echo((struct sctp_ecne_chunk *)ch, stcb);
+ ecne_seen = 1;
+ break;
+ case SCTP_ECN_CWR:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_ECN_CWR\n");
+ if (stcb == NULL) {
+ break;
+ }
+ if (stcb->asoc.ecn_supported == 0) {
+ goto unknown_chunk;
+ }
+ if (chk_length != sizeof(struct sctp_cwr_chunk)) {
+ break;
+ }
+ sctp_handle_ecn_cwr((struct sctp_cwr_chunk *)ch, stcb, *netp);
+ break;
+ case SCTP_SHUTDOWN_COMPLETE:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_SHUTDOWN_COMPLETE, stcb %p\n", (void *)stcb);
+ /* must be first and only chunk */
+ if ((num_chunks > 1) ||
+ (length - *offset > (int)SCTP_SIZE32(chk_length))) {
+ *offset = length;
+ return (stcb);
+ }
+ if ((chk_length == sizeof(struct sctp_shutdown_complete_chunk)) &&
+ (stcb != NULL) && (netp != NULL) && (*netp != NULL)) {
+ sctp_handle_shutdown_complete((struct sctp_shutdown_complete_chunk *)ch,
+ stcb, *netp);
+ *offset = length;
+ return (NULL);
+ }
+ break;
+ case SCTP_ASCONF:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_ASCONF\n");
+ if (stcb != NULL) {
+ if (stcb->asoc.asconf_supported == 0) {
+ goto unknown_chunk;
+ }
+ sctp_handle_asconf(m, *offset, src,
+ (struct sctp_asconf_chunk *)ch, stcb, asconf_cnt == 0);
+ asconf_cnt++;
+ }
+ break;
+ case SCTP_ASCONF_ACK:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_ASCONF_ACK\n");
+ if (stcb == NULL) {
+ break;
+ }
+ if (stcb->asoc.asconf_supported == 0) {
+ goto unknown_chunk;
+ }
+ if (chk_length < sizeof(struct sctp_asconf_ack_chunk)) {
+ break;
+ }
+ if ((netp != NULL) && (*netp != NULL)) {
+ /* He's alive so give him credit */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INPUT,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ sctp_handle_asconf_ack(m, *offset,
+ (struct sctp_asconf_ack_chunk *)ch, stcb, *netp, &abort_no_unlock);
+ if (abort_no_unlock)
+ return (NULL);
+ }
+ break;
+ case SCTP_FORWARD_CUM_TSN:
+ case SCTP_IFORWARD_CUM_TSN:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s\n",
+ ch->chunk_type == SCTP_FORWARD_CUM_TSN ? "FORWARD_TSN" : "I_FORWARD_TSN");
+ if (stcb == NULL) {
+ break;
+ }
+ if (stcb->asoc.prsctp_supported == 0) {
+ goto unknown_chunk;
+ }
+ if (chk_length < sizeof(struct sctp_forward_tsn_chunk)) {
+ break;
+ }
+ if (((stcb->asoc.idata_supported == 1) && (ch->chunk_type == SCTP_FORWARD_CUM_TSN)) ||
+ ((stcb->asoc.idata_supported == 0) && (ch->chunk_type == SCTP_IFORWARD_CUM_TSN))) {
+ if (ch->chunk_type == SCTP_FORWARD_CUM_TSN) {
+ SCTP_SNPRINTF(msg, sizeof(msg), "%s", "FORWARD-TSN chunk received when I-FORWARD-TSN was negotiated");
+ } else {
+ SCTP_SNPRINTF(msg, sizeof(msg), "%s", "I-FORWARD-TSN chunk received when FORWARD-TSN was negotiated");
+ }
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *offset = length;
+ return (NULL);
+ }
+ *fwd_tsn_seen = 1;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* We are not interested anymore */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_31);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ *offset = length;
+ return (NULL);
+ }
+ /*
+ * For sending a SACK this looks like DATA
+ * chunks.
+ */
+ stcb->asoc.last_data_chunk_from = stcb->asoc.last_control_chunk_from;
+ abort_flag = 0;
+ sctp_handle_forward_tsn(stcb,
+ (struct sctp_forward_tsn_chunk *)ch, &abort_flag, m, *offset);
+ if (abort_flag) {
+ *offset = length;
+ return (NULL);
+ }
+ break;
+ case SCTP_STREAM_RESET:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_STREAM_RESET\n");
+ if (stcb == NULL) {
+ break;
+ }
+ if (stcb->asoc.reconfig_supported == 0) {
+ goto unknown_chunk;
+ }
+ if (chk_length < sizeof(struct sctp_stream_reset_tsn_req)) {
+ break;
+ }
+ if (sctp_handle_stream_reset(stcb, m, *offset, ch)) {
+ /* stop processing */
+ *offset = length;
+ return (NULL);
+ }
+ break;
+ case SCTP_PACKET_DROPPED:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_PACKET_DROPPED\n");
+ if (stcb == NULL) {
+ break;
+ }
+ if (stcb->asoc.pktdrop_supported == 0) {
+ goto unknown_chunk;
+ }
+ if (chk_length < sizeof(struct sctp_pktdrop_chunk)) {
+ break;
+ }
+ if ((netp != NULL) && (*netp != NULL)) {
+ sctp_handle_packet_dropped((struct sctp_pktdrop_chunk *)ch,
+ stcb, *netp,
+ min(chk_length, contiguous));
+ }
+ break;
+ case SCTP_AUTHENTICATION:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_AUTHENTICATION\n");
+ if (stcb == NULL) {
+ /* save the first AUTH for later processing */
+ if (auth_skipped == 0) {
+ auth_offset = *offset;
+ auth_len = chk_length;
+ auth_skipped = 1;
+ }
+ /* skip this chunk (temporarily) */
+ break;
+ }
+ if (stcb->asoc.auth_supported == 0) {
+ goto unknown_chunk;
+ }
+ if ((chk_length < (sizeof(struct sctp_auth_chunk))) ||
+ (chk_length > (sizeof(struct sctp_auth_chunk) +
+ SCTP_AUTH_DIGEST_LEN_MAX))) {
+ /* Its not ours */
+ *offset = length;
+ return (stcb);
+ }
+ if (got_auth == 1) {
+ /* skip this chunk... it's already auth'd */
+ break;
+ }
+ got_auth = 1;
+ if (sctp_handle_auth(stcb, (struct sctp_auth_chunk *)ch, m, *offset)) {
+ /* auth HMAC failed so dump the packet */
+ *offset = length;
+ return (stcb);
+ } else {
+ /* remaining chunks are HMAC checked */
+ stcb->asoc.authenticated = 1;
+ }
+ break;
+
+ default:
+ unknown_chunk:
+ /* it's an unknown chunk! */
+ if ((ch->chunk_type & 0x40) &&
+ (stcb != NULL) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_EMPTY) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_INUSE) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT)) {
+ struct sctp_gen_error_cause *cause;
+ int len;
+
+ op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_gen_error_cause),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err != NULL) {
+ len = min(SCTP_SIZE32(chk_length), (uint32_t)(length - *offset));
+ cause = mtod(op_err, struct sctp_gen_error_cause *);
+ cause->code = htons(SCTP_CAUSE_UNRECOG_CHUNK);
+ cause->length = htons((uint16_t)(len + sizeof(struct sctp_gen_error_cause)));
+ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_gen_error_cause);
+ SCTP_BUF_NEXT(op_err) = SCTP_M_COPYM(m, *offset, len, M_NOWAIT);
+ if (SCTP_BUF_NEXT(op_err) != NULL) {
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(SCTP_BUF_NEXT(op_err), SCTP_MBUF_ICOPY);
+ }
+#endif
+ sctp_queue_op_err(stcb, op_err);
+ } else {
+ sctp_m_freem(op_err);
+ }
+ }
+ }
+ if ((ch->chunk_type & 0x80) == 0) {
+ /* discard this packet */
+ *offset = length;
+ return (stcb);
+ } /* else skip this bad chunk and continue... */
+ break;
+ } /* switch (ch->chunk_type) */
+
+ next_chunk:
+ /* get the next chunk */
+ *offset += SCTP_SIZE32(chk_length);
+ if (*offset >= length) {
+ /* no more data left in the mbuf chain */
+ break;
+ }
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_chunkhdr), chunk_buf);
+ if (ch == NULL) {
+ *offset = length;
+ return (stcb);
+ }
+ } /* while */
+
+ if ((asconf_cnt > 0) && (stcb != NULL)) {
+ sctp_send_asconf_ack(stcb);
+ }
+ return (stcb);
+}
+
+/*
+ * common input chunk processing (v4 and v6)
+ */
+void
+sctp_common_input_processing(struct mbuf **mm, int iphlen, int offset, int length,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_chunkhdr *ch,
+ uint8_t compute_crc,
+ uint8_t ecn_bits,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ char msg[SCTP_DIAG_INFO_LEN];
+ struct mbuf *m = *mm, *op_err;
+ struct sctp_inpcb *inp = NULL, *inp_decr = NULL;
+ struct sctp_tcb *stcb = NULL;
+ struct sctp_nets *net = NULL;
+#if defined(__Userspace__)
+ struct socket *upcall_socket = NULL;
+#endif
+ uint32_t high_tsn;
+ uint32_t cksum_in_hdr;
+ int un_sent;
+ int cnt_ctrl_ready = 0;
+ int fwd_tsn_seen = 0, data_processed = 0;
+ bool cksum_validated, stcb_looked_up;
+
+ SCTP_STAT_INCR(sctps_recvdatagrams);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xE0, 1);
+ sctp_auditing(0, inp, stcb, net);
+#endif
+
+ stcb_looked_up = false;
+ if (compute_crc != 0) {
+ cksum_validated = false;
+ cksum_in_hdr = sh->checksum;
+ if (cksum_in_hdr != htonl(0)) {
+ uint32_t cksum_calculated;
+
+validate_cksum:
+ sh->checksum = 0;
+ cksum_calculated = sctp_calculate_cksum(m, iphlen);
+ sh->checksum = cksum_in_hdr;
+ if (cksum_calculated != cksum_in_hdr) {
+ if (stcb_looked_up) {
+ /*
+ * The packet has a zero checksum, which
+ * is not the correct CRC, no stcb has
+ * been found or an stcb has been found
+ * but an incorrect zero checksum is not
+ * acceptable.
+ */
+ KASSERT(cksum_in_hdr == htonl(0),
+ ("cksum in header not zero: %x",
+ ntohl(cksum_in_hdr)));
+ if ((inp == NULL) &&
+ (SCTP_BASE_SYSCTL(sctp_ootb_with_zero_cksum) == 1)) {
+ /*
+ * This is an OOTB packet,
+ * depending on the sysctl
+ * variable, pretend that the
+ * checksum is acceptable,
+ * to allow an appropriate
+ * response (ABORT, for examlpe)
+ * to be sent.
+ */
+ KASSERT(stcb == NULL,
+ ("stcb is %p", stcb));
+ SCTP_STAT_INCR(sctps_recvzerocrc);
+ goto cksum_validated;
+ }
+ } else {
+ stcb = sctp_findassociation_addr(m, offset, src, dst,
+ sh, ch, &inp, &net, vrf_id);
+ }
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Bad cksum in SCTP packet:%x calculated:%x m:%p mlen:%d iphlen:%d\n",
+ ntohl(cksum_in_hdr), ntohl(cksum_calculated), (void *)m, length, iphlen);
+#if defined(INET) || defined(INET6)
+ if ((ch->chunk_type != SCTP_INITIATION) &&
+ (net != NULL) && (net->port != port)) {
+ if (net->port == 0) {
+ /* UDP encapsulation turned on. */
+ net->mtu -= sizeof(struct udphdr);
+ if (stcb->asoc.smallest_mtu > net->mtu) {
+ sctp_pathmtu_adjustment(stcb, net->mtu, true);
+ }
+ } else if (port == 0) {
+ /* UDP encapsulation turned off. */
+ net->mtu += sizeof(struct udphdr);
+ /* XXX Update smallest_mtu */
+ }
+ net->port = port;
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net != NULL) {
+ net->flowtype = mflowtype;
+ net->flowid = mflowid;
+ }
+ SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+#endif
+ if ((inp != NULL) && (stcb != NULL)) {
+ if (stcb->asoc.pktdrop_supported) {
+ sctp_send_packet_dropped(stcb, net, m, length, iphlen, 1);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_INPUT_ERROR, SCTP_SO_NOT_LOCKED);
+ }
+ } else if ((inp != NULL) && (stcb == NULL)) {
+ inp_decr = inp;
+ }
+ SCTP_STAT_INCR(sctps_badsum);
+ SCTP_STAT_INCR_COUNTER32(sctps_checksumerrors);
+ goto out;
+ } else {
+ cksum_validated = true;
+ }
+ }
+ KASSERT(cksum_validated || cksum_in_hdr == htonl(0),
+ ("cksum 0x%08x not zero and not validated", ntohl(cksum_in_hdr)));
+ if (!cksum_validated) {
+ stcb = sctp_findassociation_addr(m, offset, src, dst,
+ sh, ch, &inp, &net, vrf_id);
+ stcb_looked_up = true;
+ if (stcb == NULL) {
+ goto validate_cksum;
+ }
+ if (stcb->asoc.rcv_edmid == SCTP_EDMID_NONE) {
+ goto validate_cksum;
+ }
+ KASSERT(stcb->asoc.rcv_edmid == SCTP_EDMID_LOWER_LAYER_DTLS,
+ ("Unexpected EDMID %u", stcb->asoc.rcv_edmid));
+ SCTP_STAT_INCR(sctps_recvzerocrc);
+ }
+ }
+cksum_validated:
+ /* Destination port of 0 is illegal, based on RFC4960. */
+ if (sh->dest_port == htons(0)) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ if ((stcb == NULL) && (inp != NULL)) {
+ inp_decr = inp;
+ }
+ goto out;
+ }
+ if (!stcb_looked_up) {
+ stcb = sctp_findassociation_addr(m, offset, src, dst,
+ sh, ch, &inp, &net, vrf_id);
+ }
+#if defined(INET) || defined(INET6)
+ if ((ch->chunk_type != SCTP_INITIATION) &&
+ (net != NULL) && (net->port != port)) {
+ if (net->port == 0) {
+ /* UDP encapsulation turned on. */
+ net->mtu -= sizeof(struct udphdr);
+ if (stcb->asoc.smallest_mtu > net->mtu) {
+ sctp_pathmtu_adjustment(stcb, net->mtu, true);
+ }
+ } else if (port == 0) {
+ /* UDP encapsulation turned off. */
+ net->mtu += sizeof(struct udphdr);
+ /* XXX Update smallest_mtu */
+ }
+ net->port = port;
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net != NULL) {
+ net->flowtype = mflowtype;
+ net->flowid = mflowid;
+ }
+#endif
+ if (inp == NULL) {
+ SCTP_STAT_INCR(sctps_noport);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+ if (badport_bandlim(BANDLIM_SCTP_OOTB) < 0) {
+ goto out;
+ }
+#endif
+ if (ch->chunk_type == SCTP_SHUTDOWN_ACK) {
+ SCTP_STAT_INCR_COUNTER64(sctps_incontrolchunks);
+ sctp_send_shutdown_complete2(src, dst, sh,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ goto out;
+ }
+ if (ch->chunk_type == SCTP_SHUTDOWN_COMPLETE) {
+ SCTP_STAT_INCR_COUNTER64(sctps_incontrolchunks);
+ goto out;
+ }
+ if (ch->chunk_type != SCTP_ABORT_ASSOCIATION) {
+ if ((SCTP_BASE_SYSCTL(sctp_blackhole) == 0) ||
+ ((SCTP_BASE_SYSCTL(sctp_blackhole) == 1) &&
+ (ch->chunk_type != SCTP_INIT))) {
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Out of the blue");
+ sctp_send_abort(m, iphlen, src, dst,
+ sh, 0, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ }
+ }
+ goto out;
+ } else if (stcb == NULL) {
+ inp_decr = inp;
+ }
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Ok, Common input processing called, m:%p iphlen:%d offset:%d length:%d stcb:%p\n",
+ (void *)m, iphlen, offset, length, (void *)stcb);
+ if (stcb) {
+ /* always clear this before beginning a packet */
+ stcb->asoc.authenticated = 0;
+ stcb->asoc.seen_a_sack_this_pkt = 0;
+ SCTPDBG(SCTP_DEBUG_INPUT1, "stcb:%p state:%x\n",
+ (void *)stcb, stcb->asoc.state);
+
+ if ((stcb->asoc.state & SCTP_STATE_WAS_ABORTED) ||
+ (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED)) {
+ /*-
+ * If we hit here, we had a ref count
+ * up when the assoc was aborted and the
+ * timer is clearing out the assoc, we should
+ * NOT respond to any packet.. its OOTB.
+ */
+ SCTP_TCB_UNLOCK(stcb);
+ stcb = NULL;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+#endif
+ SCTP_SNPRINTF(msg, sizeof(msg), "OOTB, %s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ sctp_handle_ootb(m, iphlen, offset, src, dst, sh, inp, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ goto out;
+ }
+ }
+#if defined(__Userspace__)
+ if ((stcb != NULL) &&
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (stcb->sctp_socket != NULL)) {
+ if (stcb->sctp_socket->so_head != NULL) {
+ upcall_socket = stcb->sctp_socket->so_head;
+ } else {
+ upcall_socket = stcb->sctp_socket;
+ }
+ SOCK_LOCK(upcall_socket);
+ soref(upcall_socket);
+ SOCK_UNLOCK(upcall_socket);
+ }
+#endif
+ if (IS_SCTP_CONTROL(ch)) {
+ /* process the control portion of the SCTP packet */
+ /* sa_ignore NO_NULL_CHK */
+ stcb = sctp_process_control(m, iphlen, &offset, length,
+ src, dst, sh, ch,
+ inp, stcb, &net, &fwd_tsn_seen,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ if (stcb) {
+ /* This covers us if the cookie-echo was there
+ * and it changes our INP.
+ */
+ inp = stcb->sctp_ep;
+#if defined(INET) || defined(INET6)
+ if ((ch->chunk_type != SCTP_INITIATION) &&
+ (net != NULL) && (net->port != port)) {
+ if (net->port == 0) {
+ /* UDP encapsulation turned on. */
+ net->mtu -= sizeof(struct udphdr);
+ if (stcb->asoc.smallest_mtu > net->mtu) {
+ sctp_pathmtu_adjustment(stcb, net->mtu, true);
+ }
+ } else if (port == 0) {
+ /* UDP encapsulation turned off. */
+ net->mtu += sizeof(struct udphdr);
+ /* XXX Update smallest_mtu */
+ }
+ net->port = port;
+ }
+#endif
+ }
+ } else {
+ /*
+ * no control chunks, so pre-process DATA chunks (these
+ * checks are taken care of by control processing)
+ */
+
+ /*
+ * if DATA only packet, and auth is required, then punt...
+ * can't have authenticated without any AUTH (control)
+ * chunks
+ */
+ if ((stcb != NULL) &&
+ sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.local_auth_chunks)) {
+ /* "silently" ignore */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+#endif
+ SCTP_STAT_INCR(sctps_recvauthmissing);
+ goto out;
+ }
+ if (stcb == NULL) {
+ /* out of the blue DATA chunk */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(receive, NULL, NULL, m, NULL, sh);
+#endif
+ SCTP_SNPRINTF(msg, sizeof(msg), "OOTB, %s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ sctp_handle_ootb(m, iphlen, offset, src, dst, sh, inp, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ goto out;
+ }
+ if (stcb->asoc.my_vtag != ntohl(sh->v_tag)) {
+ /* v_tag mismatch! */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+#endif
+ SCTP_STAT_INCR(sctps_badvtag);
+ goto out;
+ }
+ }
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+#endif
+ if (stcb == NULL) {
+ /*
+ * no valid TCB for this packet, or we found it's a bad
+ * packet while processing control, or we're done with this
+ * packet (done or skip rest of data), so we drop it...
+ */
+ goto out;
+ }
+#if defined(__Userspace__)
+ if ((upcall_socket == NULL) &&
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (stcb->sctp_socket != NULL)) {
+ if (stcb->sctp_socket->so_head != NULL) {
+ upcall_socket = stcb->sctp_socket->so_head;
+ } else {
+ upcall_socket = stcb->sctp_socket;
+ }
+ SOCK_LOCK(upcall_socket);
+ soref(upcall_socket);
+ SOCK_UNLOCK(upcall_socket);
+ }
+#endif
+
+ /*
+ * DATA chunk processing
+ */
+ /* plow through the data chunks while length > offset */
+
+ /*
+ * Rest should be DATA only. Check authentication state if AUTH for
+ * DATA is required.
+ */
+ if ((length > offset) &&
+ (stcb != NULL) &&
+ sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.local_auth_chunks) &&
+ !stcb->asoc.authenticated) {
+ /* "silently" ignore */
+ SCTP_STAT_INCR(sctps_recvauthmissing);
+ SCTPDBG(SCTP_DEBUG_AUTH1,
+ "Data chunk requires AUTH, skipped\n");
+ goto trigger_send;
+ }
+ if (length > offset) {
+ int retval;
+
+ /*
+ * First check to make sure our state is correct. We would
+ * not get here unless we really did have a tag, so we don't
+ * abort if this happens, just dump the chunk silently.
+ */
+ switch (SCTP_GET_STATE(stcb)) {
+ case SCTP_STATE_COOKIE_ECHOED:
+ /*
+ * we consider data with valid tags in this state
+ * shows us the cookie-ack was lost. Imply it was
+ * there.
+ */
+ sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb, net);
+ break;
+ case SCTP_STATE_COOKIE_WAIT:
+ /*
+ * We consider OOTB any data sent during asoc setup.
+ */
+ SCTP_SNPRINTF(msg, sizeof(msg), "OOTB, %s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ sctp_handle_ootb(m, iphlen, offset, src, dst, sh, inp, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ goto out;
+ /*sa_ignore NOTREACHED*/
+ break;
+ case SCTP_STATE_EMPTY: /* should not happen */
+ case SCTP_STATE_INUSE: /* should not happen */
+ case SCTP_STATE_SHUTDOWN_RECEIVED: /* This is a peer error */
+ case SCTP_STATE_SHUTDOWN_ACK_SENT:
+ default:
+ goto out;
+ /*sa_ignore NOTREACHED*/
+ break;
+ case SCTP_STATE_OPEN:
+ case SCTP_STATE_SHUTDOWN_SENT:
+ break;
+ }
+ /* plow through the data chunks while length > offset */
+ retval = sctp_process_data(mm, iphlen, &offset, length,
+ inp, stcb, net, &high_tsn);
+ if (retval == 2) {
+ /*
+ * The association aborted, NO UNLOCK needed since
+ * the association is destroyed.
+ */
+ stcb = NULL;
+ goto out;
+ }
+ if (retval == 0) {
+ data_processed = 1;
+ }
+ /*
+ * Anything important needs to have been m_copy'ed in
+ * process_data
+ */
+ }
+
+ /* take care of ecn */
+ if ((data_processed == 1) &&
+ (stcb->asoc.ecn_supported == 1) &&
+ ((ecn_bits & SCTP_CE_BITS) == SCTP_CE_BITS)) {
+ /* Yep, we need to add a ECNE */
+ sctp_send_ecn_echo(stcb, net, high_tsn);
+ }
+
+ if ((data_processed == 0) && (fwd_tsn_seen)) {
+ int was_a_gap;
+ uint32_t highest_tsn;
+
+ if (SCTP_TSN_GT(stcb->asoc.highest_tsn_inside_nr_map, stcb->asoc.highest_tsn_inside_map)) {
+ highest_tsn = stcb->asoc.highest_tsn_inside_nr_map;
+ } else {
+ highest_tsn = stcb->asoc.highest_tsn_inside_map;
+ }
+ was_a_gap = SCTP_TSN_GT(highest_tsn, stcb->asoc.cumulative_tsn);
+ stcb->asoc.send_sack = 1;
+ sctp_sack_check(stcb, was_a_gap);
+ } else if (fwd_tsn_seen) {
+ stcb->asoc.send_sack = 1;
+ }
+ /* trigger send of any chunks in queue... */
+trigger_send:
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xE0, 2);
+ sctp_auditing(1, inp, stcb, net);
+#endif
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "Check for chunk output prw:%d tqe:%d tf=%d\n",
+ stcb->asoc.peers_rwnd,
+ TAILQ_EMPTY(&stcb->asoc.control_send_queue),
+ stcb->asoc.total_flight);
+ un_sent = (stcb->asoc.total_output_queue_size - stcb->asoc.total_flight);
+ if (!TAILQ_EMPTY(&stcb->asoc.control_send_queue)) {
+ cnt_ctrl_ready = stcb->asoc.ctrl_queue_cnt - stcb->asoc.ecn_echo_cnt_onq;
+ }
+ if (!TAILQ_EMPTY(&stcb->asoc.asconf_send_queue) ||
+ cnt_ctrl_ready ||
+ stcb->asoc.trigger_reset ||
+ ((un_sent > 0) &&
+ (stcb->asoc.peers_rwnd > 0 || stcb->asoc.total_flight == 0))) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "Calling chunk OUTPUT\n");
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CONTROL_PROC, SCTP_SO_NOT_LOCKED);
+ SCTPDBG(SCTP_DEBUG_INPUT3, "chunk OUTPUT returns\n");
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xE0, 3);
+ sctp_auditing(2, inp, stcb, net);
+#endif
+ out:
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+#if defined(__Userspace__)
+ if (upcall_socket != NULL) {
+ if (upcall_socket->so_upcall != NULL) {
+ if (soreadable(upcall_socket) ||
+ sowriteable(upcall_socket) ||
+ upcall_socket->so_error) {
+ (*upcall_socket->so_upcall)(upcall_socket, upcall_socket->so_upcallarg, M_NOWAIT);
+ }
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(upcall_socket);
+ sorele(upcall_socket);
+ }
+#endif
+ if (inp_decr != NULL) {
+ /* reduce ref-count */
+ SCTP_INP_WLOCK(inp_decr);
+ SCTP_INP_DECR_REF(inp_decr);
+ SCTP_INP_WUNLOCK(inp_decr);
+ }
+ return;
+}
+
+#ifdef INET
+#if !defined(__Userspace__)
+void
+sctp_input_with_port(struct mbuf *i_pak, int off, uint16_t port)
+{
+ struct mbuf *m;
+ int iphlen;
+ uint32_t vrf_id = 0;
+ uint8_t ecn_bits;
+ struct sockaddr_in src, dst;
+ struct ip *ip;
+ struct sctphdr *sh;
+ struct sctp_chunkhdr *ch;
+ int length, offset;
+ uint8_t compute_crc;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint32_t mflowid;
+ uint8_t mflowtype;
+ uint16_t fibnum;
+#endif
+#if defined(__Userspace__)
+ uint16_t port = 0;
+#endif
+
+ iphlen = off;
+ if (SCTP_GET_PKT_VRFID(i_pak, vrf_id)) {
+ SCTP_RELEASE_PKT(i_pak);
+ return;
+ }
+ m = SCTP_HEADER_TO_CHAIN(i_pak);
+#ifdef SCTP_MBUF_LOGGING
+ /* Log in any input mbufs */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(m, SCTP_MBUF_INPUT);
+ }
+#endif
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING) {
+ sctp_packet_log(m);
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp_input(): Packet of length %d received on %s with csum_flags 0x%b.\n",
+ m->m_pkthdr.len,
+ if_name(m->m_pkthdr.rcvif),
+ (int)m->m_pkthdr.csum_flags, CSUM_BITS);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp_input(): Packet of length %d received on %s%d with csum_flags 0x%x.\n",
+ m->m_pkthdr.len,
+ m->m_pkthdr.rcvif->if_name,
+ m->m_pkthdr.rcvif->if_unit,
+ m->m_pkthdr.csum_flags);
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp_input(): Packet of length %d received on %s with csum_flags 0x%x.\n",
+ m->m_pkthdr.len,
+ m->m_pkthdr.rcvif->if_xname,
+ m->m_pkthdr.csum_flags);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowid = m->m_pkthdr.flowid;
+ mflowtype = M_HASHTYPE_GET(m);
+ fibnum = M_GETFIB(m);
+#endif
+ SCTP_STAT_INCR(sctps_recvpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+ /* Get IP, SCTP, and first chunk header together in the first mbuf. */
+ offset = iphlen + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ if (SCTP_BUF_LEN(m) < offset) {
+ if ((m = m_pullup(m, offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ return;
+ }
+ }
+ ip = mtod(m, struct ip *);
+ sh = (struct sctphdr *)((caddr_t)ip + iphlen);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset -= sizeof(struct sctp_chunkhdr);
+ memset(&src, 0, sizeof(struct sockaddr_in));
+ src.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ src.sin_len = sizeof(struct sockaddr_in);
+#endif
+ src.sin_port = sh->src_port;
+ src.sin_addr = ip->ip_src;
+ memset(&dst, 0, sizeof(struct sockaddr_in));
+ dst.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ dst.sin_len = sizeof(struct sockaddr_in);
+#endif
+ dst.sin_port = sh->dest_port;
+ dst.sin_addr = ip->ip_dst;
+#if defined(_WIN32) && !defined(__Userspace__)
+ NTOHS(ip->ip_len);
+#endif
+#if defined(__linux__) || (defined(_WIN32) && defined(__Userspace__))
+ ip->ip_len = ntohs(ip->ip_len);
+#endif
+#if defined(__Userspace__)
+#if defined(__linux__) || defined(_WIN32)
+ length = ip->ip_len;
+#else
+ length = ip->ip_len + iphlen;
+#endif
+#elif defined(__FreeBSD__)
+ length = ntohs(ip->ip_len);
+#elif defined(__APPLE__)
+ length = ip->ip_len + iphlen;
+#else
+ length = ip->ip_len;
+#endif
+ /* Validate mbuf chain length with IP payload length. */
+ if (SCTP_HEADER_LEN(m) != length) {
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "sctp_input() length:%d reported length:%d\n", length, SCTP_HEADER_LEN(m));
+ SCTP_STAT_INCR(sctps_hdrops);
+ goto out;
+ }
+ /* SCTP does not allow broadcasts or multicasts */
+ if (IN_MULTICAST(ntohl(dst.sin_addr.s_addr))) {
+ goto out;
+ }
+ if (SCTP_IS_IT_BROADCAST(dst.sin_addr, m)) {
+ goto out;
+ }
+ ecn_bits = ip->ip_tos;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (m->m_pkthdr.csum_flags & CSUM_SCTP_VALID) {
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ compute_crc = 0;
+ } else {
+#else
+ if (SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ ((src.sin_addr.s_addr == dst.sin_addr.s_addr) ||
+ (SCTP_IS_IT_LOOPBACK(m)))) {
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ compute_crc = 0;
+ } else {
+#endif
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ compute_crc = 1;
+ }
+ sctp_common_input_processing(&m, iphlen, offset, length,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+ compute_crc,
+ ecn_bits,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ out:
+ if (m) {
+ sctp_m_freem(m);
+ }
+ return;
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP_MCORE_INPUT) && defined(SMP)
+extern int *sctp_cpuarry;
+#endif
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+int
+sctp_input(struct mbuf **mp, int *offp, int proto SCTP_UNUSED)
+{
+ struct mbuf *m;
+ int off;
+
+ m = *mp;
+ off = *offp;
+#else
+void
+sctp_input(struct mbuf *m, int off)
+{
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP_MCORE_INPUT) && defined(SMP)
+ if (mp_ncpus > 1) {
+ struct ip *ip;
+ struct sctphdr *sh;
+ int offset;
+ int cpu_to_use;
+ uint32_t flowid, tag;
+
+ if (M_HASHTYPE_GET(m) != M_HASHTYPE_NONE) {
+ flowid = m->m_pkthdr.flowid;
+ } else {
+ /* No flow id built by lower layers
+ * fix it so we create one.
+ */
+ offset = off + sizeof(struct sctphdr);
+ if (SCTP_BUF_LEN(m) < offset) {
+ if ((m = m_pullup(m, offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ return (IPPROTO_DONE);
+ }
+ }
+ ip = mtod(m, struct ip *);
+ sh = (struct sctphdr *)((caddr_t)ip + off);
+ tag = htonl(sh->v_tag);
+ flowid = tag ^ ntohs(sh->dest_port) ^ ntohs(sh->src_port);
+ m->m_pkthdr.flowid = flowid;
+ M_HASHTYPE_SET(m, M_HASHTYPE_OPAQUE_HASH);
+ }
+ cpu_to_use = sctp_cpuarry[flowid % mp_ncpus];
+ sctp_queue_to_mcore(m, off, cpu_to_use);
+ return (IPPROTO_DONE);
+ }
+#endif
+#endif
+ sctp_input_with_port(m, off, 0);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ return (IPPROTO_DONE);
+#endif
+}
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_input.h b/netwerk/sctp/src/netinet/sctp_input.h
new file mode 100644
index 0000000000..acb2097316
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_input.h
@@ -0,0 +1,61 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_INPUT_H_
+#define _NETINET_SCTP_INPUT_H_
+
+#if defined(_KERNEL) || defined(__Userspace__)
+void
+sctp_common_input_processing(struct mbuf **, int, int, int,
+ struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, struct sctp_chunkhdr *,
+ uint8_t,
+ uint8_t,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t, uint32_t, uint16_t,
+#endif
+ uint32_t, uint16_t);
+
+struct sctp_stream_reset_request *
+sctp_find_stream_reset(struct sctp_tcb *stcb, uint32_t seq,
+ struct sctp_tmit_chunk **bchk);
+
+void
+sctp_reset_in_stream(struct sctp_tcb *stcb, uint32_t number_entries,
+ uint16_t *list);
+
+int sctp_is_there_unsent_data(struct sctp_tcb *stcb, int so_locked);
+
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_lock_userspace.h b/netwerk/sctp/src/netinet/sctp_lock_userspace.h
new file mode 100644
index 0000000000..16c4a33979
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_lock_userspace.h
@@ -0,0 +1,239 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ * Copyright (c) 2008-2012, by Brad Penoff. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_LOCK_EMPTY_H_
+#define _NETINET_SCTP_LOCK_EMPTY_H_
+
+/*
+ * Empty Lock declarations for all other platforms. Pre-process away to
+ * nothing.
+ */
+
+/* __Userspace__ putting lock macros in same order as sctp_lock_bsd.h ...*/
+
+#define SCTP_IPI_COUNT_INIT()
+
+#define SCTP_STATLOG_INIT_LOCK()
+#define SCTP_STATLOG_LOCK()
+#define SCTP_STATLOG_UNLOCK()
+#define SCTP_STATLOG_DESTROY()
+
+#define SCTP_INP_INFO_LOCK_DESTROY()
+
+#define SCTP_INP_INFO_LOCK_INIT()
+#define SCTP_INP_INFO_RLOCK()
+#define SCTP_INP_INFO_WLOCK()
+#define SCTP_INP_INFO_TRYLOCK() 1
+#define SCTP_INP_INFO_RUNLOCK()
+#define SCTP_INP_INFO_WUNLOCK()
+#define SCTP_INP_INFO_LOCK_ASSERT()
+#define SCTP_INP_INFO_RLOCK_ASSERT()
+#define SCTP_INP_INFO_WLOCK_ASSERT()
+
+#define SCTP_WQ_ADDR_INIT()
+#define SCTP_WQ_ADDR_DESTROY()
+#define SCTP_WQ_ADDR_LOCK()
+#define SCTP_WQ_ADDR_UNLOCK()
+#define SCTP_WQ_ADDR_LOCK_ASSERT()
+
+#define SCTP_IPI_ADDR_INIT()
+#define SCTP_IPI_ADDR_DESTROY()
+#define SCTP_IPI_ADDR_RLOCK()
+#define SCTP_IPI_ADDR_WLOCK()
+#define SCTP_IPI_ADDR_RUNLOCK()
+#define SCTP_IPI_ADDR_WUNLOCK()
+#define SCTP_IPI_ADDR_LOCK_ASSERT()
+#define SCTP_IPI_ADDR_WLOCK_ASSERT()
+
+#define SCTP_IPI_ITERATOR_WQ_INIT()
+#define SCTP_IPI_ITERATOR_WQ_DESTROY()
+#define SCTP_IPI_ITERATOR_WQ_LOCK()
+#define SCTP_IPI_ITERATOR_WQ_UNLOCK()
+
+#define SCTP_IP_PKTLOG_INIT()
+#define SCTP_IP_PKTLOG_LOCK()
+#define SCTP_IP_PKTLOG_UNLOCK()
+#define SCTP_IP_PKTLOG_DESTROY()
+
+#define SCTP_INP_READ_LOCK_INIT(_inp)
+#define SCTP_INP_READ_LOCK_DESTROY(_inp)
+#define SCTP_INP_READ_LOCK(_inp)
+#define SCTP_INP_READ_UNLOCK(_inp)
+#define SCTP_INP_READ_LOCK_ASSERT(_inp)
+
+#define SCTP_INP_LOCK_INIT(_inp)
+#define SCTP_ASOC_CREATE_LOCK_INIT(_inp)
+#define SCTP_INP_LOCK_DESTROY(_inp)
+#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp)
+
+#define SCTP_INP_RLOCK(_inp)
+#define SCTP_INP_WLOCK(_inp)
+#define SCTP_INP_RLOCK_ASSERT(_inp)
+#define SCTP_INP_WLOCK_ASSERT(_inp)
+
+#define SCTP_INP_LOCK_CONTENDED(_inp) (0) /* Don't know if this is possible */
+
+#define SCTP_INP_READ_CONTENDED(_inp) (0) /* Don't know if this is possible */
+
+#define SCTP_ASOC_CREATE_LOCK_CONTENDED(_inp) (0) /* Don't know if this is possible */
+
+
+#define SCTP_INP_INCR_REF(_inp)
+#define SCTP_INP_DECR_REF(_inp)
+
+#define SCTP_ASOC_CREATE_LOCK(_inp)
+
+#define SCTP_INP_RUNLOCK(_inp)
+#define SCTP_INP_WUNLOCK(_inp)
+#define SCTP_ASOC_CREATE_UNLOCK(_inp)
+
+
+#define SCTP_TCB_LOCK_INIT(_tcb)
+#define SCTP_TCB_LOCK_DESTROY(_tcb)
+#define SCTP_TCB_LOCK(_tcb)
+#define SCTP_TCB_TRYLOCK(_tcb) 1
+#define SCTP_TCB_UNLOCK(_tcb)
+#define SCTP_TCB_UNLOCK_IFOWNED(_tcb)
+#define SCTP_TCB_LOCK_ASSERT(_tcb)
+
+
+
+#define SCTP_ITERATOR_LOCK_INIT()
+#define SCTP_ITERATOR_LOCK()
+#define SCTP_ITERATOR_UNLOCK()
+#define SCTP_ITERATOR_LOCK_DESTROY()
+
+
+
+#define SCTP_INCR_EP_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_ep++; \
+ } while (0)
+
+#define SCTP_DECR_EP_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_ep--; \
+ } while (0)
+
+#define SCTP_INCR_ASOC_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_asoc++; \
+ } while (0)
+
+#define SCTP_DECR_ASOC_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_asoc--; \
+ } while (0)
+
+#define SCTP_INCR_LADDR_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_laddr++; \
+ } while (0)
+
+#define SCTP_DECR_LADDR_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_laddr--; \
+ } while (0)
+
+#define SCTP_INCR_RADDR_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_raddr++; \
+ } while (0)
+
+#define SCTP_DECR_RADDR_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_raddr--; \
+ } while (0)
+
+#define SCTP_INCR_CHK_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_chunk++; \
+ } while (0)
+
+#define SCTP_DECR_CHK_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_chunk--; \
+ } while (0)
+
+#define SCTP_INCR_READQ_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_readq++; \
+ } while (0)
+
+#define SCTP_DECR_READQ_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_readq--; \
+ } while (0)
+
+#define SCTP_INCR_STRMOQ_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_strmoq++; \
+ } while (0)
+
+#define SCTP_DECR_STRMOQ_COUNT() \
+ do { \
+ sctppcbinfo.ipi_count_strmoq--; \
+ } while (0)
+
+
+/* these were in sctp_lock_empty.h but aren't in sctp_lock_bsd.h ... */
+#if 0
+#define SCTP_IPI_ADDR_LOCK()
+#define SCTP_IPI_ADDR_UNLOCK()
+#endif
+
+
+/* These were in sctp_lock_empty.h because they were commented out within
+ * within user_include/user_socketvar.h . If they are NOT commented out
+ * in user_socketvar.h (because that seems the more natural place for them
+ * to live), then change this "if" to 0. Keep the "if" as 1 if these ARE
+ * indeed commented out in user_socketvar.h .
+ *
+ * This modularity is kept so this file can easily be chosen as an alternative
+ * to SCTP_PROCESS_LEVEL_LOCKS. If one defines SCTP_PROCESS_LEVEL_LOCKS in
+ * user_include/opt_sctp.h, then the file sctp_process_lock.h (which we didn't
+ * implement) is used, and that declares these locks already (so using
+ * SCTP_PROCESS_LEVEL_LOCKS *requires* that these defintions be commented out
+ * in user_socketvar.h).
+ */
+#if 1
+#define SOCK_LOCK(_so)
+#define SOCK_UNLOCK(_so)
+#define SOCKBUF_LOCK(_so_buf)
+#define SOCKBUF_UNLOCK(_so_buf)
+#define SOCKBUF_LOCK_ASSERT(_so_buf)
+#endif
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_os.h b/netwerk/sctp/src/netinet/sctp_os.h
new file mode 100644
index 0000000000..0a12db9bcb
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_os.h
@@ -0,0 +1,84 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2006-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_OS_H_
+#define _NETINET_SCTP_OS_H_
+
+/*
+ * General kernel memory allocation:
+ * SCTP_MALLOC(element, type, size, name)
+ * SCTP_FREE(element)
+ * Kernel memory allocation for "soname"- memory must be zeroed.
+ * SCTP_MALLOC_SONAME(name, type, size)
+ * SCTP_FREE_SONAME(name)
+ */
+
+/*
+ * Zone(pool) allocation routines: MUST be defined for each OS.
+ * zone = zone/pool pointer.
+ * name = string name of the zone/pool.
+ * size = size of each zone/pool element.
+ * number = number of elements in zone/pool.
+ * type = structure type to allocate
+ *
+ * sctp_zone_t
+ * SCTP_ZONE_INIT(zone, name, size, number)
+ * SCTP_ZONE_GET(zone, type)
+ * SCTP_ZONE_FREE(zone, element)
+ * SCTP_ZONE_DESTROY(zone)
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/sctp_os_bsd.h>
+#else
+#define MODULE_GLOBAL(_B) (_B)
+#endif
+#if defined(__Userspace__)
+#include <netinet/sctp_os_userspace.h>
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+#include <netinet/sctp_os_macosx.h>
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+#include <netinet/sctp_os_windows.h>
+#endif
+
+/* All os's must implement this address gatherer. If
+ * no VRF's exist, then vrf 0 is the only one and all
+ * addresses and ifn's live here.
+ */
+#define SCTP_DEFAULT_VRF 0
+void sctp_init_vrf_list(int vrfid);
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_os_userspace.h b/netwerk/sctp/src/netinet/sctp_os_userspace.h
new file mode 100644
index 0000000000..493ae0267f
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_os_userspace.h
@@ -0,0 +1,1153 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2006-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2011, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2011, by Michael Tuexen. All rights reserved.
+ * Copyright (c) 2008-2011, by Brad Penoff. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __sctp_os_userspace_h__
+#define __sctp_os_userspace_h__
+/*
+ * Userspace includes
+ * All the opt_xxx.h files are placed in the kernel build directory.
+ * We will place them in userspace stack build directory.
+ */
+
+#include <errno.h>
+
+#if defined(_WIN32)
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <iphlpapi.h>
+#include <mswsock.h>
+#include <windows.h>
+#include "user_environment.h"
+typedef CRITICAL_SECTION userland_mutex_t;
+#if WINVER < 0x0600
+typedef CRITICAL_SECTION userland_rwlock_t;
+enum {
+ C_SIGNAL = 0,
+ C_BROADCAST = 1,
+ C_MAX_EVENTS = 2
+};
+typedef struct
+{
+ u_int waiters_count;
+ CRITICAL_SECTION waiters_count_lock;
+ HANDLE events_[C_MAX_EVENTS];
+} userland_cond_t;
+void InitializeXPConditionVariable(userland_cond_t *);
+void DeleteXPConditionVariable(userland_cond_t *);
+int SleepXPConditionVariable(userland_cond_t *, userland_mutex_t *);
+void WakeAllXPConditionVariable(userland_cond_t *);
+#define InitializeConditionVariable(cond) InitializeXPConditionVariable(cond)
+#define DeleteConditionVariable(cond) DeleteXPConditionVariable(cond)
+#define SleepConditionVariableCS(cond, mtx, time) SleepXPConditionVariable(cond, mtx)
+#define WakeAllConditionVariable(cond) WakeAllXPConditionVariable(cond)
+#else
+typedef SRWLOCK userland_rwlock_t;
+#define DeleteConditionVariable(cond)
+typedef CONDITION_VARIABLE userland_cond_t;
+#endif
+typedef HANDLE userland_thread_t;
+#define ADDRESS_FAMILY unsigned __int8
+#define IPVERSION 4
+#define MAXTTL 255
+/* VS2010 comes with stdint.h */
+#if !defined(_MSC_VER) || (_MSC_VER >= 1600)
+#include <stdint.h>
+#else
+typedef unsigned __int64 uint64_t;
+typedef unsigned __int32 uint32_t;
+typedef __int32 int32_t;
+typedef unsigned __int16 uint16_t;
+typedef __int16 int16_t;
+typedef unsigned __int8 uint8_t;
+typedef __int8 int8_t;
+#endif
+#ifndef _SIZE_T_DEFINED
+#typedef __int32 size_t;
+#endif
+typedef unsigned __int32 u_int;
+typedef unsigned char u_char;
+typedef unsigned __int16 u_short;
+typedef unsigned __int8 sa_family_t;
+#ifndef _SSIZE_T_DEFINED
+typedef __int64 ssize_t;
+#endif
+#if !defined(__MINGW32__)
+#define __func__ __FUNCTION__
+#endif
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#endif
+#ifndef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#endif
+#ifndef EALREADY
+#define EALREADY WSAEALREADY
+#endif
+#ifndef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#endif
+#ifndef EDESTADDRREQ
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#endif
+#ifndef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#endif
+#ifndef EPROTOTYPE
+#define EPROTOTYPE WSAEPROTOTYPE
+#endif
+#ifndef ENOPROTOOPT
+#define ENOPROTOOPT WSAENOPROTOOPT
+#endif
+#ifndef EPROTONOSUPPORT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#endif
+#ifndef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#endif
+#ifndef EOPNOTSUPP
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#endif
+#ifndef ENOTSUP
+#define ENOTSUP WSAEOPNOTSUPP
+#endif
+#ifndef EPFNOSUPPORT
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#endif
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#endif
+#ifndef EADDRINUSE
+#define EADDRINUSE WSAEADDRINUSE
+#endif
+#ifndef EADDRNOTAVAIL
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#endif
+#ifndef ENETDOWN
+#define ENETDOWN WSAENETDOWN
+#endif
+#ifndef ENETUNREACH
+#define ENETUNREACH WSAENETUNREACH
+#endif
+#ifndef ENETRESET
+#define ENETRESET WSAENETRESET
+#endif
+#ifndef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#endif
+#ifndef ECONNRESET
+#define ECONNRESET WSAECONNRESET
+#endif
+#ifndef ENOBUFS
+#define ENOBUFS WSAENOBUFS
+#endif
+#ifndef EISCONN
+#define EISCONN WSAEISCONN
+#endif
+#ifndef ENOTCONN
+#define ENOTCONN WSAENOTCONN
+#endif
+#ifndef ESHUTDOWN
+#define ESHUTDOWN WSAESHUTDOWN
+#endif
+#ifndef ETOOMANYREFS
+#define ETOOMANYREFS WSAETOOMANYREFS
+#endif
+#ifndef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#endif
+#ifndef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#endif
+#ifndef ELOOP
+#define ELOOP WSAELOOP
+#endif
+#ifndef EHOSTDOWN
+#define EHOSTDOWN WSAEHOSTDOWN
+#endif
+#ifndef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#endif
+#ifndef EPROCLIM
+#define EPROCLIM WSAEPROCLIM
+#endif
+#ifndef EUSERS
+#define EUSERS WSAEUSERS
+#endif
+#ifndef EDQUOT
+#define EDQUOT WSAEDQUOT
+#endif
+#ifndef ESTALE
+#define ESTALE WSAESTALE
+#endif
+#ifndef EREMOTE
+#define EREMOTE WSAEREMOTE
+#endif
+
+typedef char* caddr_t;
+
+#define bzero(buf, len) memset(buf, 0, len)
+#define bcopy(srcKey, dstKey, len) memcpy(dstKey, srcKey, len)
+
+#if defined(_MSC_VER) && (_MSC_VER < 1900) && !defined(__MINGW32__)
+#define SCTP_SNPRINTF(data, size, format, ...) \
+ if (_snprintf_s(data, size, _TRUNCATE, format, __VA_ARGS__) < 0) { \
+ data[0] = '\0'; \
+ }
+#else
+#define SCTP_SNPRINTF(data, ...) \
+ if (snprintf(data, __VA_ARGS__) < 0 ) { \
+ data[0] = '\0'; \
+ }
+#endif
+
+#define inline __inline
+#define __inline__ __inline
+#define MSG_EOR 0x8 /* data completes record */
+#define MSG_DONTWAIT 0x80 /* this message should be nonblocking */
+
+#ifdef CMSG_DATA
+#undef CMSG_DATA
+#endif
+/*
+ * The following definitions should apply iff WINVER < 0x0600
+ * but that check doesn't work in all cases. So be more pedantic...
+ */
+#define CMSG_DATA(x) WSA_CMSG_DATA(x)
+#define CMSG_ALIGN(x) WSA_CMSGDATA_ALIGN(x)
+#ifndef CMSG_FIRSTHDR
+#define CMSG_FIRSTHDR(x) WSA_CMSG_FIRSTHDR(x)
+#endif
+#ifndef CMSG_NXTHDR
+#define CMSG_NXTHDR(x, y) WSA_CMSG_NXTHDR(x, y)
+#endif
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(x) WSA_CMSG_SPACE(x)
+#endif
+#ifndef CMSG_LEN
+#define CMSG_LEN(x) WSA_CMSG_LEN(x)
+#endif
+
+/**** from sctp_os_windows.h ***************/
+#define SCTP_IFN_IS_IFT_LOOP(ifn) ((ifn)->ifn_type == IFT_LOOP)
+#define SCTP_ROUTE_IS_REAL_LOOP(ro) ((ro)->ro_rt && (ro)->ro_rt->rt_ifa && (ro)->ro_rt->rt_ifa->ifa_ifp && (ro)->ro_rt->rt_ifa->ifa_ifp->if_type == IFT_LOOP)
+
+/*
+ * Access to IFN's to help with src-addr-selection
+ */
+/* This could return VOID if the index works but for BSD we provide both. */
+#define SCTP_GET_IFN_VOID_FROM_ROUTE(ro) \
+ ((ro)->ro_rt != NULL ? (ro)->ro_rt->rt_ifp : NULL)
+#define SCTP_ROUTE_HAS_VALID_IFN(ro) \
+ ((ro)->ro_rt && (ro)->ro_rt->rt_ifp)
+/******************************************/
+
+#define SCTP_GET_IF_INDEX_FROM_ROUTE(ro) 1 /* compiles... TODO use routing socket to determine */
+
+#define BIG_ENDIAN 1
+#define LITTLE_ENDIAN 0
+#ifdef WORDS_BIGENDIAN
+#define BYTE_ORDER BIG_ENDIAN
+#else
+#define BYTE_ORDER LITTLE_ENDIAN
+#endif
+
+#else /* !defined(Userspace_os_Windows) */
+#include <sys/socket.h>
+
+#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
+#error "Unsupported build configuration."
+#endif
+
+#include <pthread.h>
+
+typedef pthread_mutex_t userland_mutex_t;
+typedef pthread_rwlock_t userland_rwlock_t;
+typedef pthread_cond_t userland_cond_t;
+typedef pthread_t userland_thread_t;
+#endif
+
+#if defined(_WIN32) || defined(__native_client__)
+
+#define IFNAMSIZ 64
+
+#define random() rand()
+#define srandom(s) srand(s)
+
+#define timeradd(tvp, uvp, vvp) \
+ do { \
+ (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
+ if ((vvp)->tv_usec >= 1000000) { \
+ (vvp)->tv_sec++; \
+ (vvp)->tv_usec -= 1000000; \
+ } \
+ } while (0)
+
+#define timersub(tvp, uvp, vvp) \
+ do { \
+ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
+ if ((vvp)->tv_usec < 0) { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_usec += 1000000; \
+ } \
+ } while (0)
+
+/*#include <packon.h>
+#pragma pack(push, 1)*/
+struct ip {
+ u_char ip_hl:4, ip_v:4;
+ u_char ip_tos;
+ u_short ip_len;
+ u_short ip_id;
+ u_short ip_off;
+#define IP_RP 0x8000
+#define IP_DF 0x4000
+#define IP_MF 0x2000
+#define IP_OFFMASK 0x1fff
+ u_char ip_ttl;
+ u_char ip_p;
+ u_short ip_sum;
+ struct in_addr ip_src, ip_dst;
+};
+
+struct ifaddrs {
+ struct ifaddrs *ifa_next;
+ char *ifa_name;
+ unsigned int ifa_flags;
+ struct sockaddr *ifa_addr;
+ struct sockaddr *ifa_netmask;
+ struct sockaddr *ifa_dstaddr;
+ void *ifa_data;
+};
+
+struct udphdr {
+ uint16_t uh_sport;
+ uint16_t uh_dport;
+ uint16_t uh_ulen;
+ uint16_t uh_sum;
+};
+
+struct iovec {
+ size_t len;
+ char *buf;
+};
+
+#define iov_base buf
+#define iov_len len
+
+struct ifa_msghdr {
+ uint16_t ifam_msglen;
+ unsigned char ifam_version;
+ unsigned char ifam_type;
+ uint32_t ifam_addrs;
+ uint32_t ifam_flags;
+ uint16_t ifam_index;
+ uint32_t ifam_metric;
+};
+
+struct ifdevmtu {
+ int ifdm_current;
+ int ifdm_min;
+ int ifdm_max;
+};
+
+struct ifkpi {
+ unsigned int ifk_module_id;
+ unsigned int ifk_type;
+ union {
+ void *ifk_ptr;
+ int ifk_value;
+ } ifk_data;
+};
+#endif
+
+#if defined(_WIN32)
+int Win_getifaddrs(struct ifaddrs**);
+#define getifaddrs(interfaces) (int)Win_getifaddrs(interfaces)
+int win_if_nametoindex(const char *);
+#define if_nametoindex(x) win_if_nametoindex(x)
+#endif
+
+#define mtx_lock(arg1)
+#define mtx_unlock(arg1)
+#define mtx_assert(arg1,arg2)
+#define MA_OWNED 7 /* sys/mutex.h typically on FreeBSD */
+#if !defined(__FreeBSD__)
+struct mtx {int dummy;};
+#if !defined(__NetBSD__)
+struct selinfo {int dummy;};
+#endif
+struct sx {int dummy;};
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+/* #include <sys/param.h> in FreeBSD defines MSIZE */
+/* #include <sys/ktr.h> */
+/* #include <sys/systm.h> */
+#if defined(HAVE_SYS_QUEUE_H)
+#include <sys/queue.h>
+#else
+#include <user_queue.h>
+#endif
+#include <user_malloc.h>
+/* #include <sys/kernel.h> */
+/* #include <sys/sysctl.h> */
+/* #include <sys/protosw.h> */
+/* on FreeBSD, this results in a redefintion of SOCK(BUF)_(UN)LOCK and
+ * uknown type of struct mtx for sb_mtx in struct sockbuf */
+#include "user_socketvar.h" /* MALLOC_DECLARE's M_PCB. Replacement for sys/socketvar.h */
+/* #include <sys/jail.h> */
+/* #include <sys/sysctl.h> */
+#include <user_environment.h>
+#include <user_atomic.h>
+#include <user_mbuf.h>
+/* #include <sys/uio.h> */
+/* #include <sys/lock.h> */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/rwlock.h>
+#endif
+/* #include <sys/kthread.h> */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/priv.h>
+#endif
+/* #include <sys/random.h> */
+#include <limits.h>
+/* #include <machine/cpu.h> */
+
+#if defined(__APPLE__)
+/* was a 0 byte file. needed for structs if_data(64) and net_event_data */
+#include <net/if_var.h>
+#endif
+#if defined(__FreeBSD__)
+#include <net/if_types.h>
+/* #include <net/if_var.h> was a 0 byte file. causes struct mtx redefinition */
+#endif
+/* OOTB only - dummy route used at the moment. should we port route to
+ * userspace as well? */
+/* on FreeBSD, this results in a redefintion of struct route */
+/* #include <net/route.h> */
+#if !defined(_WIN32) && !defined(__native_client__)
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#endif
+#if defined(HAVE_NETINET_IP_ICMP_H)
+#include <netinet/ip_icmp.h>
+#else
+#include <user_ip_icmp.h>
+#endif
+/* #include <netinet/in_pcb.h> ported to userspace */
+#include <user_inpcb.h>
+
+/* for getifaddrs */
+#include <sys/types.h>
+#if !defined(_WIN32)
+#if defined(INET) || defined(INET6)
+#include <ifaddrs.h>
+#endif
+
+/* for ioctl */
+#include <sys/ioctl.h>
+
+/* for close, etc. */
+#include <unistd.h>
+/* for gettimeofday */
+#include <sys/time.h>
+#endif
+
+/* lots of errno's used and needed in userspace */
+
+/* for offsetof */
+#include <stddef.h>
+
+#if defined(SCTP_PROCESS_LEVEL_LOCKS) && !defined(_WIN32)
+/* for pthread_mutex_lock, pthread_mutex_unlock, etc. */
+#include <pthread.h>
+#endif
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#include <netipsec/key.h>
+#endif /* IPSEC */
+
+#ifdef INET6
+#if defined(__FreeBSD__)
+#include <sys/domain.h>
+#endif
+#ifdef IPSEC
+#include <netipsec/ipsec6.h>
+#endif
+#if !defined(_WIN32)
+#include <netinet/ip6.h>
+#endif
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__linux__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(_WIN32) || defined(__EMSCRIPTEN__)
+#include "user_ip6_var.h"
+#else
+#include <netinet6/ip6_var.h>
+#endif
+#if defined(__FreeBSD__)
+#include <netinet6/in6_pcb.h>
+#include <netinet6/scope6_var.h>
+#endif
+#endif /* INET6 */
+
+#if defined(HAVE_SCTP_PEELOFF_SOCKOPT)
+#include <sys/file.h>
+#include <sys/filedesc.h>
+#endif
+
+#include "netinet/sctp_sha1.h"
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/ip_options.h>
+#endif
+
+#define SCTP_PRINTF(...) \
+ if (SCTP_BASE_VAR(debug_printf)) { \
+ SCTP_BASE_VAR(debug_printf)(__VA_ARGS__); \
+ }
+
+/* Declare all the malloc names for all the various mallocs */
+MALLOC_DECLARE(SCTP_M_MAP);
+MALLOC_DECLARE(SCTP_M_STRMI);
+MALLOC_DECLARE(SCTP_M_STRMO);
+MALLOC_DECLARE(SCTP_M_ASC_ADDR);
+MALLOC_DECLARE(SCTP_M_ASC_IT);
+MALLOC_DECLARE(SCTP_M_AUTH_CL);
+MALLOC_DECLARE(SCTP_M_AUTH_KY);
+MALLOC_DECLARE(SCTP_M_AUTH_HL);
+MALLOC_DECLARE(SCTP_M_AUTH_IF);
+MALLOC_DECLARE(SCTP_M_STRESET);
+MALLOC_DECLARE(SCTP_M_CMSG);
+MALLOC_DECLARE(SCTP_M_COPYAL);
+MALLOC_DECLARE(SCTP_M_VRF);
+MALLOC_DECLARE(SCTP_M_IFA);
+MALLOC_DECLARE(SCTP_M_IFN);
+MALLOC_DECLARE(SCTP_M_TIMW);
+MALLOC_DECLARE(SCTP_M_MVRF);
+MALLOC_DECLARE(SCTP_M_ITER);
+MALLOC_DECLARE(SCTP_M_SOCKOPT);
+
+#if defined(SCTP_LOCAL_TRACE_BUF)
+
+#define SCTP_GET_CYCLECOUNT get_cyclecount()
+#define SCTP_CTR6 sctp_log_trace
+
+#else
+#define SCTP_CTR6 CTR6
+#endif
+
+/* Empty ktr statement for _Userspace__ (similar to what is done for mac) */
+#define CTR6(m, d, p1, p2, p3, p4, p5, p6)
+
+
+
+#define SCTP_BASE_INFO(__m) system_base_info.sctppcbinfo.__m
+#define SCTP_BASE_STATS system_base_info.sctpstat
+#define SCTP_BASE_STAT(__m) system_base_info.sctpstat.__m
+#define SCTP_BASE_SYSCTL(__m) system_base_info.sctpsysctl.__m
+#define SCTP_BASE_VAR(__m) system_base_info.__m
+
+/*
+ *
+ */
+#if !defined(__APPLE__)
+#define USER_ADDR_NULL (NULL) /* FIX ME: temp */
+#endif
+
+#include <netinet/sctp_constants.h>
+#if defined(SCTP_DEBUG)
+#define SCTPDBG(level, ...) \
+{ \
+ do { \
+ if (SCTP_BASE_SYSCTL(sctp_debug_on) & level) { \
+ SCTP_PRINTF(__VA_ARGS__); \
+ } \
+ } while (0); \
+}
+#define SCTPDBG_ADDR(level, addr) \
+{ \
+ do { \
+ if (SCTP_BASE_SYSCTL(sctp_debug_on) & level ) { \
+ sctp_print_address(addr); \
+ } \
+ } while (0); \
+}
+#else
+#define SCTPDBG(level, ...)
+#define SCTPDBG_ADDR(level, addr)
+#endif
+
+#ifdef SCTP_LTRACE_CHUNKS
+#define SCTP_LTRACE_CHK(a, b, c, d) if(sctp_logging_level & SCTP_LTRACE_CHUNK_ENABLE) CTR6(KTR_SUBSYS, "SCTP:%d[%d]:%x-%x-%x-%x", SCTP_LOG_CHUNK_PROC, 0, a, b, c, d)
+#else
+#define SCTP_LTRACE_CHK(a, b, c, d)
+#endif
+
+#ifdef SCTP_LTRACE_ERRORS
+#define SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, net, file, err) \
+ if (sctp_logging_level & SCTP_LTRACE_ERROR_ENABLE) \
+ SCTP_PRINTF("mbuf:%p inp:%p stcb:%p net:%p file:%x line:%d error:%d\n", \
+ (void *)m, (void *)inp, (void *)stcb, (void *)net, file, __LINE__, err);
+#define SCTP_LTRACE_ERR_RET(inp, stcb, net, file, err) \
+ if (sctp_logging_level & SCTP_LTRACE_ERROR_ENABLE) \
+ SCTP_PRINTF("inp:%p stcb:%p net:%p file:%x line:%d error:%d\n", \
+ (void *)inp, (void *)stcb, (void *)net, file, __LINE__, err);
+#else
+#define SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, net, file, err)
+#define SCTP_LTRACE_ERR_RET(inp, stcb, net, file, err)
+#endif
+
+
+/*
+ * Local address and interface list handling
+ */
+#define SCTP_MAX_VRF_ID 0
+#define SCTP_SIZE_OF_VRF_HASH 3
+#define SCTP_IFNAMSIZ IFNAMSIZ
+#define SCTP_DEFAULT_VRFID 0
+#define SCTP_VRF_ADDR_HASH_SIZE 16
+#define SCTP_VRF_IFN_HASH_SIZE 3
+#define SCTP_INIT_VRF_TABLEID(vrf)
+
+#if !defined(_WIN32)
+#define SCTP_IFN_IS_IFT_LOOP(ifn) (strncmp((ifn)->ifn_name, "lo", 2) == 0)
+/* BSD definition */
+/* #define SCTP_ROUTE_IS_REAL_LOOP(ro) ((ro)->ro_rt && (ro)->ro_rt->rt_ifa && (ro)->ro_rt->rt_ifa->ifa_ifp && (ro)->ro_rt->rt_ifa->ifa_ifp->if_type == IFT_LOOP) */
+/* only used in IPv6 scenario, which isn't supported yet */
+#define SCTP_ROUTE_IS_REAL_LOOP(ro) 0
+
+/*
+ * Access to IFN's to help with src-addr-selection
+ */
+/* This could return VOID if the index works but for BSD we provide both. */
+#define SCTP_GET_IFN_VOID_FROM_ROUTE(ro) (void *)ro->ro_rt->rt_ifp
+#define SCTP_GET_IF_INDEX_FROM_ROUTE(ro) 1 /* compiles... TODO use routing socket to determine */
+#define SCTP_ROUTE_HAS_VALID_IFN(ro) ((ro)->ro_rt && (ro)->ro_rt->rt_ifp)
+#endif
+
+/*
+ * general memory allocation
+ */
+#define SCTP_MALLOC(var, type, size, name) \
+ do { \
+ MALLOC(var, type, size, name, M_NOWAIT); \
+ } while (0)
+
+#define SCTP_FREE(var, type) FREE(var, type)
+
+#define SCTP_MALLOC_SONAME(var, type, size) \
+ do { \
+ MALLOC(var, type, size, M_SONAME, (M_WAITOK | M_ZERO)); \
+ } while (0)
+
+#define SCTP_FREE_SONAME(var) FREE(var, M_SONAME)
+
+#define SCTP_PROCESS_STRUCT struct proc *
+
+/*
+ * zone allocation functions
+ */
+
+
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+/*typedef size_t sctp_zone_t;*/
+#define SCTP_ZONE_INIT(zone, name, size, number) { \
+ zone = size; \
+}
+
+/* __Userspace__ SCTP_ZONE_GET: allocate element from the zone */
+#define SCTP_ZONE_GET(zone, type) \
+ (type *)malloc(zone);
+
+
+/* __Userspace__ SCTP_ZONE_FREE: free element from the zone */
+#define SCTP_ZONE_FREE(zone, element) { \
+ free(element); \
+}
+
+#define SCTP_ZONE_DESTROY(zone)
+#else
+/*__Userspace__
+ Compiling & linking notes: Needs libumem, which has been placed in ./user_lib
+ All userspace header files are in ./user_include. Makefile will need the
+ following.
+ CFLAGS = -I./ -Wall
+ LDFLAGS = -L./user_lib -R./user_lib -lumem
+*/
+#include "user_include/umem.h"
+
+/* __Userspace__ SCTP_ZONE_INIT: initialize the zone */
+/*
+ __Userspace__
+ No equivalent function to uma_zone_set_max added yet. (See SCTP_ZONE_INIT in sctp_os_bsd.h
+ for reference). It may not be required as mentioned in
+ http://nixdoc.net/man-pages/FreeBSD/uma_zalloc.9.html that
+ max limits may not enforced on systems with more than one CPU.
+*/
+#define SCTP_ZONE_INIT(zone, name, size, number) { \
+ zone = umem_cache_create(name, size, 0, NULL, NULL, NULL, NULL, NULL, 0); \
+ }
+
+/* __Userspace__ SCTP_ZONE_GET: allocate element from the zone */
+#define SCTP_ZONE_GET(zone, type) \
+ (type *)umem_cache_alloc(zone, UMEM_DEFAULT);
+
+
+/* __Userspace__ SCTP_ZONE_FREE: free element from the zone */
+#define SCTP_ZONE_FREE(zone, element) \
+ umem_cache_free(zone, element);
+
+
+/* __Userspace__ SCTP_ZONE_DESTROY: destroy the zone */
+#define SCTP_ZONE_DESTROY(zone) \
+ umem_cache_destroy(zone);
+#endif
+
+/*
+ * __Userspace__ Defining sctp_hashinit_flags() and sctp_hashdestroy() for userland.
+ */
+void *sctp_hashinit_flags(int elements, struct malloc_type *type,
+ u_long *hashmask, int flags);
+void
+sctp_hashdestroy(void *vhashtbl, struct malloc_type *type, u_long hashmask);
+
+void
+sctp_hashfreedestroy(void *vhashtbl, struct malloc_type *type, u_long hashmask);
+
+
+#define HASH_NOWAIT 0x00000001
+#define HASH_WAITOK 0x00000002
+
+/* M_PCB is MALLOC_DECLARE'd in sys/socketvar.h */
+#define SCTP_HASH_INIT(size, hashmark) sctp_hashinit_flags(size, M_PCB, hashmark, HASH_NOWAIT)
+
+#define SCTP_HASH_FREE(table, hashmark) sctp_hashdestroy(table, M_PCB, hashmark)
+
+#define SCTP_HASH_FREE_DESTROY(table, hashmark) sctp_hashfreedestroy(table, M_PCB, hashmark)
+#define SCTP_M_COPYM m_copym
+
+/*
+ * timers
+ */
+/* __Userspace__
+ * user_sctp_callout.h has typedef struct sctp_callout sctp_os_timer_t;
+ * which is used in the timer related functions such as
+ * SCTP_OS_TIMER_INIT etc.
+*/
+#include <netinet/sctp_callout.h>
+
+/* __Userspace__ Creating a receive thread */
+#include <user_recv_thread.h>
+
+/*__Userspace__ defining KTR_SUBSYS 1 as done in sctp_os_macosx.h */
+#define KTR_SUBSYS 1
+
+/* The packed define for 64 bit platforms */
+#if !defined(_WIN32)
+#define SCTP_PACKED __attribute__((packed))
+#define SCTP_UNUSED __attribute__((unused))
+#else
+#define SCTP_PACKED
+#define SCTP_UNUSED
+#endif
+
+/*
+ * Functions
+ */
+/* Mbuf manipulation and access macros */
+#define SCTP_BUF_LEN(m) (m->m_len)
+#define SCTP_BUF_NEXT(m) (m->m_next)
+#define SCTP_BUF_NEXT_PKT(m) (m->m_nextpkt)
+#define SCTP_BUF_RESV_UF(m, size) m->m_data += size
+#define SCTP_BUF_AT(m, size) m->m_data + size
+#define SCTP_BUF_IS_EXTENDED(m) (m->m_flags & M_EXT)
+#define SCTP_BUF_EXTEND_SIZE(m) (m->m_ext.ext_size)
+#define SCTP_BUF_TYPE(m) (m->m_type)
+#define SCTP_BUF_RECVIF(m) (m->m_pkthdr.rcvif)
+#define SCTP_BUF_PREPEND M_PREPEND
+
+#define SCTP_ALIGN_TO_END(m, len) if(m->m_flags & M_PKTHDR) { \
+ MH_ALIGN(m, len); \
+ } else if ((m->m_flags & M_EXT) == 0) { \
+ M_ALIGN(m, len); \
+ }
+
+#if !defined(_WIN32)
+#define SCTP_SNPRINTF(data, ...) \
+ if (snprintf(data, __VA_ARGS__) < 0) { \
+ data[0] = '\0'; \
+ }
+#endif
+
+/* We make it so if you have up to 4 threads
+ * writting based on the default size of
+ * the packet log 65 k, that would be
+ * 4 16k packets before we would hit
+ * a problem.
+ */
+#define SCTP_PKTLOG_WRITERS_NEED_LOCK 3
+
+
+/*
+ * routes, output, etc.
+ */
+
+typedef struct sctp_route sctp_route_t;
+typedef struct sctp_rtentry sctp_rtentry_t;
+
+static inline void sctp_userspace_rtalloc(sctp_route_t *ro)
+{
+ if (ro->ro_rt != NULL) {
+ ro->ro_rt->rt_refcnt++;
+ return;
+ }
+
+ ro->ro_rt = (sctp_rtentry_t *) malloc(sizeof(sctp_rtentry_t));
+ if (ro->ro_rt == NULL)
+ return;
+
+ /* initialize */
+ memset(ro->ro_rt, 0, sizeof(sctp_rtentry_t));
+ ro->ro_rt->rt_refcnt = 1;
+
+ /* set MTU */
+ /* TODO set this based on the ro->ro_dst, looking up MTU with routing socket */
+#if 0
+ if (userspace_rawroute == -1) {
+ userspace_rawroute = socket(AF_ROUTE, SOCK_RAW, 0);
+ if (userspace_rawroute == -1)
+ return;
+ }
+#endif
+ ro->ro_rt->rt_rmx.rmx_mtu = 1500; /* FIXME temporary solution */
+
+ /* TODO enable the ability to obtain interface index of route for
+ * SCTP_GET_IF_INDEX_FROM_ROUTE macro.
+ */
+}
+#define SCTP_RTALLOC(ro, vrf_id, fibnum) sctp_userspace_rtalloc((sctp_route_t *)ro)
+
+/* dummy rtfree needed once user_route.h is included */
+static inline void sctp_userspace_rtfree(sctp_rtentry_t *rt)
+{
+ if(rt == NULL) {
+ return;
+ }
+ if(--rt->rt_refcnt > 0) {
+ return;
+ }
+ free(rt);
+}
+#define rtfree(arg1) sctp_userspace_rtfree(arg1)
+
+
+/*************************/
+/* MTU */
+/*************************/
+int sctp_userspace_get_mtu_from_ifn(uint32_t if_index);
+
+#define SCTP_GATHER_MTU_FROM_IFN_INFO(ifn, ifn_index) sctp_userspace_get_mtu_from_ifn(ifn_index)
+
+#define SCTP_GATHER_MTU_FROM_ROUTE(sctp_ifa, sa, rt) ((rt != NULL) ? rt->rt_rmx.rmx_mtu : 0)
+
+#define SCTP_SET_MTU_OF_ROUTE(sa, rt, mtu) do { \
+ if (rt != NULL) \
+ rt->rt_rmx.rmx_mtu = mtu; \
+ } while(0)
+
+
+/*************************/
+/* These are for logging */
+/*************************/
+/* return the base ext data pointer */
+#define SCTP_BUF_EXTEND_BASE(m) (m->m_ext.ext_buf)
+ /* return the refcnt of the data pointer */
+#define SCTP_BUF_EXTEND_REFCNT(m) (*m->m_ext.ref_cnt)
+/* return any buffer related flags, this is
+ * used beyond logging for apple only.
+ */
+#define SCTP_BUF_GET_FLAGS(m) (m->m_flags)
+
+/* For BSD this just accesses the M_PKTHDR length
+ * so it operates on an mbuf with hdr flag. Other
+ * O/S's may have seperate packet header and mbuf
+ * chain pointers.. thus the macro.
+ */
+#define SCTP_HEADER_TO_CHAIN(m) (m)
+#define SCTP_DETACH_HEADER_FROM_CHAIN(m)
+#define SCTP_HEADER_LEN(m) ((m)->m_pkthdr.len)
+#define SCTP_GET_HEADER_FOR_OUTPUT(o_pak) 0
+#define SCTP_RELEASE_HEADER(m)
+#define SCTP_RELEASE_PKT(m) sctp_m_freem(m)
+
+#define SCTP_GET_PKT_VRFID(m, vrf_id) ((vrf_id = SCTP_DEFAULT_VRFID) != SCTP_DEFAULT_VRFID)
+
+
+
+/* Attach the chain of data into the sendable packet. */
+#define SCTP_ATTACH_CHAIN(pak, m, packet_length) do { \
+ pak = m; \
+ pak->m_pkthdr.len = packet_length; \
+ } while(0)
+
+/* Other m_pkthdr type things */
+/* FIXME need real definitions */
+#define SCTP_IS_IT_BROADCAST(dst, m) 0
+/* OOTB only #define SCTP_IS_IT_BROADCAST(dst, m) ((m->m_flags & M_PKTHDR) ? in_broadcast(dst, m->m_pkthdr.rcvif) : 0) BSD def */
+#define SCTP_IS_IT_LOOPBACK(m) 0
+/* OOTB ONLY #define SCTP_IS_IT_LOOPBACK(m) ((m->m_flags & M_PKTHDR) && ((m->m_pkthdr.rcvif == NULL) || (m->m_pkthdr.rcvif->if_type == IFT_LOOP))) BSD def */
+
+
+/* This converts any input packet header
+ * into the chain of data holders, for BSD
+ * its a NOP.
+ */
+
+/* get the v6 hop limit */
+#define SCTP_GET_HLIM(inp, ro) 128
+#define IPv6_HOP_LIMIT 128
+
+/* is the endpoint v6only? */
+#define SCTP_IPV6_V6ONLY(sctp_inpcb) ((sctp_inpcb)->ip_inp.inp.inp_flags & IN6P_IPV6_V6ONLY)
+/* is the socket non-blocking? */
+#define SCTP_SO_IS_NBIO(so) ((so)->so_state & SS_NBIO)
+#define SCTP_SET_SO_NBIO(so) ((so)->so_state |= SS_NBIO)
+#define SCTP_CLEAR_SO_NBIO(so) ((so)->so_state &= ~SS_NBIO)
+/* get the socket type */
+#define SCTP_SO_TYPE(so) ((so)->so_type)
+
+/* reserve sb space for a socket */
+#define SCTP_SORESERVE(so, send, recv) soreserve(so, send, recv)
+
+/* wakeup a socket */
+#define SCTP_SOWAKEUP(so) wakeup(&(so)->so_timeo, so)
+/* number of bytes ready to read */
+#define SCTP_SBAVAIL(sb) (sb)->sb_cc
+#define SCTP_SB_INCR(sb, incr) \
+{ \
+ atomic_add_int(&(sb)->sb_cc, incr); \
+}
+#define SCTP_SB_DECR(sb, decr) \
+{ \
+ SCTP_SAVE_ATOMIC_DECREMENT(&(sb)->sb_cc, (int)(decr)); \
+}
+/* clear the socket buffer state */
+#define SCTP_SB_CLEAR(sb) \
+ (sb).sb_cc = 0; \
+ (sb).sb_mb = NULL; \
+ (sb).sb_mbcnt = 0;
+
+#define SCTP_SB_LIMIT_RCV(so) so->so_rcv.sb_hiwat
+#define SCTP_SB_LIMIT_SND(so) so->so_snd.sb_hiwat
+
+#define SCTP_READ_RANDOM(buf, len) read_random(buf, len)
+
+#define SCTP_SHA1_CTX struct sctp_sha1_context
+#define SCTP_SHA1_INIT sctp_sha1_init
+#define SCTP_SHA1_UPDATE sctp_sha1_update
+#define SCTP_SHA1_FINAL(x,y) sctp_sha1_final((unsigned char *)x, y)
+
+/* start OOTB only stuff */
+/* TODO IFT_LOOP is in net/if_types.h on Linux */
+#define IFT_LOOP 0x18
+
+/* sctp_pcb.h */
+
+#if defined(_WIN32)
+#define SHUT_RD 1
+#define SHUT_WR 2
+#define SHUT_RDWR 3
+#endif
+#define PRU_FLUSH_RD SHUT_RD
+#define PRU_FLUSH_WR SHUT_WR
+#define PRU_FLUSH_RDWR SHUT_RDWR
+
+/* netinet/ip_var.h defintions are behind an if defined for _KERNEL on FreeBSD */
+#define IP_RAWOUTPUT 0x2
+
+
+/* end OOTB only stuff */
+
+#define AF_CONN 123
+struct sockaddr_conn {
+#ifdef HAVE_SCONN_LEN
+ uint8_t sconn_len;
+ uint8_t sconn_family;
+#else
+ uint16_t sconn_family;
+#endif
+ uint16_t sconn_port;
+ void *sconn_addr;
+};
+
+typedef void *(*start_routine_t)(void *);
+
+extern int
+sctp_userspace_thread_create(userland_thread_t *thread, start_routine_t start_routine);
+
+void
+sctp_userspace_set_threadname(const char *name);
+
+/*
+ * SCTP protocol specific mbuf flags.
+ */
+#define M_NOTIFICATION M_PROTO5 /* SCTP notification */
+
+/*
+ * IP output routines
+ */
+
+/* Defining SCTP_IP_ID macro.
+ In netinet/ip_output.c, we have u_short ip_id;
+ In netinet/ip_var.h, we have extern u_short ip_id; (enclosed within _KERNEL_)
+ See static __inline uint16_t ip_newid(void) in netinet/ip_var.h
+ */
+#define SCTP_IP_ID(inp) (ip_id)
+
+/* need sctphdr to get port in SCTP_IP_OUTPUT. sctphdr defined in sctp.h */
+#include <netinet/sctp.h>
+extern void sctp_userspace_ip_output(int *result, struct mbuf *o_pak,
+ sctp_route_t *ro, void *stcb,
+ uint32_t vrf_id);
+
+#define SCTP_IP_OUTPUT(result, o_pak, ro, inp, vrf_id) sctp_userspace_ip_output(&result, o_pak, ro, inp, vrf_id);
+
+#if defined(INET6)
+extern void sctp_userspace_ip6_output(int *result, struct mbuf *o_pak,
+ struct route_in6 *ro, void *stcb,
+ uint32_t vrf_id);
+#define SCTP_IP6_OUTPUT(result, o_pak, ro, ifp, inp, vrf_id) sctp_userspace_ip6_output(&result, o_pak, ro, inp, vrf_id);
+#endif
+
+
+
+#if 0
+#define SCTP_IP6_OUTPUT(result, o_pak, ro, ifp, stcb, vrf_id) \
+{ \
+ if (stcb && stcb->sctp_ep) \
+ result = ip6_output(o_pak, \
+ ((struct inpcb *)(stcb->sctp_ep))->in6p_outputopts, \
+ (ro), 0, 0, ifp, NULL); \
+ else \
+ result = ip6_output(o_pak, NULL, (ro), 0, 0, ifp, NULL); \
+}
+#endif
+
+struct mbuf *
+sctp_get_mbuf_for_msg(unsigned int space_needed, int want_header, int how, int allonebuf, int type);
+
+
+/* with the current included files, this is defined in Linux but
+ * in FreeBSD, it is behind a _KERNEL in sys/socket.h ...
+ */
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__native_client__)
+/* stolen from /usr/include/sys/socket.h */
+#define CMSG_ALIGN(n) _ALIGN(n)
+#elif defined(__NetBSD__)
+#define CMSG_ALIGN(n) (((n) + __ALIGNBYTES) & ~__ALIGNBYTES)
+#elif defined(__APPLE__)
+#if !defined(__DARWIN_ALIGNBYTES)
+#define __DARWIN_ALIGNBYTES (sizeof(__darwin_size_t) - 1)
+#endif
+
+#if !defined(__DARWIN_ALIGN)
+#define __DARWIN_ALIGN(p) ((__darwin_size_t)((char *)(uintptr_t)(p) + __DARWIN_ALIGNBYTES) &~ __DARWIN_ALIGNBYTES)
+#endif
+
+#if !defined(__DARWIN_ALIGNBYTES32)
+#define __DARWIN_ALIGNBYTES32 (sizeof(__uint32_t) - 1)
+#endif
+
+#if !defined(__DARWIN_ALIGN32)
+#define __DARWIN_ALIGN32(p) ((__darwin_size_t)((char *)(uintptr_t)(p) + __DARWIN_ALIGNBYTES32) &~ __DARWIN_ALIGNBYTES32)
+#endif
+#define CMSG_ALIGN(n) __DARWIN_ALIGN32(n)
+#endif
+#define I_AM_HERE \
+ do { \
+ SCTP_PRINTF("%s:%d at %s\n", __FILE__, __LINE__ , __func__); \
+ } while (0)
+
+#ifndef timevalsub
+#define timevalsub(tp1, tp2) \
+ do { \
+ (tp1)->tv_sec -= (tp2)->tv_sec; \
+ (tp1)->tv_usec -= (tp2)->tv_usec; \
+ if ((tp1)->tv_usec < 0) { \
+ (tp1)->tv_sec--; \
+ (tp1)->tv_usec += 1000000; \
+ } \
+ } while (0)
+#endif
+
+#if defined(__linux__)
+#if !defined(TAILQ_FOREACH_SAFE)
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = ((head)->tqh_first); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+#endif
+#if !defined(LIST_FOREACH_SAFE)
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = ((head)->lh_first); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+#endif
+#endif
+#if defined(__DragonFly__)
+#define TAILQ_FOREACH_SAFE TAILQ_FOREACH_MUTABLE
+#define LIST_FOREACH_SAFE LIST_FOREACH_MUTABLE
+#endif
+
+#if defined(__native_client__)
+#define timercmp(tvp, uvp, cmp) \
+ (((tvp)->tv_sec == (uvp)->tv_sec) ? \
+ ((tvp)->tv_usec cmp (uvp)->tv_usec) : \
+ ((tvp)->tv_sec cmp (uvp)->tv_sec))
+#endif
+
+#define SCTP_IS_LISTENING(inp) ((inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING) != 0)
+
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__linux__) || defined(__native_client__) || defined(__NetBSD__) || defined(_WIN32) || defined(__Fuchsia__) || defined(__EMSCRIPTEN__)
+int
+timingsafe_bcmp(const void *, const void *, size_t);
+#endif
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_output.c b/netwerk/sctp/src/netinet/sctp_output.c
new file mode 100644
index 0000000000..61fa3a5fe8
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_output.c
@@ -0,0 +1,15083 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/proc.h>
+#endif
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_bsd_addr.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp_crc32.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/sctp_kdtrace.h>
+#endif
+#if defined(__linux__)
+#define __FAVOR_BSD /* (on Ubuntu at least) enables UDP header field names like BSD in RFC 768 */
+#endif
+#if defined(INET) || defined(INET6)
+#if !defined(_WIN32)
+#include <netinet/udp.h>
+#endif
+#endif
+#if !defined(__Userspace__)
+#if defined(__APPLE__)
+#include <netinet/in.h>
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/udp_var.h>
+#include <machine/in_cksum.h>
+#endif
+#endif
+#if defined(__Userspace__) && defined(INET6)
+#include <netinet6/sctp6_var.h>
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if !(defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD))
+#define SCTP_MAX_LINKHDR 16
+#endif
+#endif
+
+#define SCTP_MAX_GAPS_INARRAY 4
+struct sack_track {
+ uint8_t right_edge; /* mergable on the right edge */
+ uint8_t left_edge; /* mergable on the left edge */
+ uint8_t num_entries;
+ uint8_t spare;
+ struct sctp_gap_ack_block gaps[SCTP_MAX_GAPS_INARRAY];
+};
+
+const struct sack_track sack_array[256] = {
+ {0, 0, 0, 0, /* 0x00 */
+ {{0, 0},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 1, 0, /* 0x01 */
+ {{0, 0},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x02 */
+ {{1, 1},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 1, 0, /* 0x03 */
+ {{0, 1},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x04 */
+ {{2, 2},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x05 */
+ {{0, 0},
+ {2, 2},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x06 */
+ {{1, 2},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 1, 0, /* 0x07 */
+ {{0, 2},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x08 */
+ {{3, 3},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x09 */
+ {{0, 0},
+ {3, 3},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x0a */
+ {{1, 1},
+ {3, 3},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x0b */
+ {{0, 1},
+ {3, 3},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x0c */
+ {{2, 3},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x0d */
+ {{0, 0},
+ {2, 3},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x0e */
+ {{1, 3},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 1, 0, /* 0x0f */
+ {{0, 3},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x10 */
+ {{4, 4},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x11 */
+ {{0, 0},
+ {4, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x12 */
+ {{1, 1},
+ {4, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x13 */
+ {{0, 1},
+ {4, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x14 */
+ {{2, 2},
+ {4, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x15 */
+ {{0, 0},
+ {2, 2},
+ {4, 4},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x16 */
+ {{1, 2},
+ {4, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x17 */
+ {{0, 2},
+ {4, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x18 */
+ {{3, 4},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x19 */
+ {{0, 0},
+ {3, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x1a */
+ {{1, 1},
+ {3, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x1b */
+ {{0, 1},
+ {3, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x1c */
+ {{2, 4},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x1d */
+ {{0, 0},
+ {2, 4},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x1e */
+ {{1, 4},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 1, 0, /* 0x1f */
+ {{0, 4},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x20 */
+ {{5, 5},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x21 */
+ {{0, 0},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x22 */
+ {{1, 1},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x23 */
+ {{0, 1},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x24 */
+ {{2, 2},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x25 */
+ {{0, 0},
+ {2, 2},
+ {5, 5},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x26 */
+ {{1, 2},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x27 */
+ {{0, 2},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x28 */
+ {{3, 3},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x29 */
+ {{0, 0},
+ {3, 3},
+ {5, 5},
+ {0, 0}
+ }
+ },
+ {0, 0, 3, 0, /* 0x2a */
+ {{1, 1},
+ {3, 3},
+ {5, 5},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x2b */
+ {{0, 1},
+ {3, 3},
+ {5, 5},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x2c */
+ {{2, 3},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x2d */
+ {{0, 0},
+ {2, 3},
+ {5, 5},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x2e */
+ {{1, 3},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x2f */
+ {{0, 3},
+ {5, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x30 */
+ {{4, 5},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x31 */
+ {{0, 0},
+ {4, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x32 */
+ {{1, 1},
+ {4, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x33 */
+ {{0, 1},
+ {4, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x34 */
+ {{2, 2},
+ {4, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x35 */
+ {{0, 0},
+ {2, 2},
+ {4, 5},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x36 */
+ {{1, 2},
+ {4, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x37 */
+ {{0, 2},
+ {4, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x38 */
+ {{3, 5},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x39 */
+ {{0, 0},
+ {3, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x3a */
+ {{1, 1},
+ {3, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x3b */
+ {{0, 1},
+ {3, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x3c */
+ {{2, 5},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x3d */
+ {{0, 0},
+ {2, 5},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x3e */
+ {{1, 5},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 1, 0, /* 0x3f */
+ {{0, 5},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x40 */
+ {{6, 6},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x41 */
+ {{0, 0},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x42 */
+ {{1, 1},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x43 */
+ {{0, 1},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x44 */
+ {{2, 2},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x45 */
+ {{0, 0},
+ {2, 2},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x46 */
+ {{1, 2},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x47 */
+ {{0, 2},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x48 */
+ {{3, 3},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x49 */
+ {{0, 0},
+ {3, 3},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 3, 0, /* 0x4a */
+ {{1, 1},
+ {3, 3},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x4b */
+ {{0, 1},
+ {3, 3},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x4c */
+ {{2, 3},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x4d */
+ {{0, 0},
+ {2, 3},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x4e */
+ {{1, 3},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x4f */
+ {{0, 3},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x50 */
+ {{4, 4},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x51 */
+ {{0, 0},
+ {4, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 3, 0, /* 0x52 */
+ {{1, 1},
+ {4, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x53 */
+ {{0, 1},
+ {4, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 3, 0, /* 0x54 */
+ {{2, 2},
+ {4, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {1, 0, 4, 0, /* 0x55 */
+ {{0, 0},
+ {2, 2},
+ {4, 4},
+ {6, 6}
+ }
+ },
+ {0, 0, 3, 0, /* 0x56 */
+ {{1, 2},
+ {4, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x57 */
+ {{0, 2},
+ {4, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x58 */
+ {{3, 4},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x59 */
+ {{0, 0},
+ {3, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 3, 0, /* 0x5a */
+ {{1, 1},
+ {3, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x5b */
+ {{0, 1},
+ {3, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x5c */
+ {{2, 4},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x5d */
+ {{0, 0},
+ {2, 4},
+ {6, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x5e */
+ {{1, 4},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x5f */
+ {{0, 4},
+ {6, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x60 */
+ {{5, 6},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x61 */
+ {{0, 0},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x62 */
+ {{1, 1},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x63 */
+ {{0, 1},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x64 */
+ {{2, 2},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x65 */
+ {{0, 0},
+ {2, 2},
+ {5, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x66 */
+ {{1, 2},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x67 */
+ {{0, 2},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x68 */
+ {{3, 3},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x69 */
+ {{0, 0},
+ {3, 3},
+ {5, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 3, 0, /* 0x6a */
+ {{1, 1},
+ {3, 3},
+ {5, 6},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x6b */
+ {{0, 1},
+ {3, 3},
+ {5, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x6c */
+ {{2, 3},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x6d */
+ {{0, 0},
+ {2, 3},
+ {5, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x6e */
+ {{1, 3},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x6f */
+ {{0, 3},
+ {5, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x70 */
+ {{4, 6},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x71 */
+ {{0, 0},
+ {4, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x72 */
+ {{1, 1},
+ {4, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x73 */
+ {{0, 1},
+ {4, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x74 */
+ {{2, 2},
+ {4, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 3, 0, /* 0x75 */
+ {{0, 0},
+ {2, 2},
+ {4, 6},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x76 */
+ {{1, 2},
+ {4, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x77 */
+ {{0, 2},
+ {4, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x78 */
+ {{3, 6},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x79 */
+ {{0, 0},
+ {3, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 2, 0, /* 0x7a */
+ {{1, 1},
+ {3, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x7b */
+ {{0, 1},
+ {3, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x7c */
+ {{2, 6},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 2, 0, /* 0x7d */
+ {{0, 0},
+ {2, 6},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 0, 1, 0, /* 0x7e */
+ {{1, 6},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 0, 1, 0, /* 0x7f */
+ {{0, 6},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 1, 0, /* 0x80 */
+ {{7, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0x81 */
+ {{0, 0},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x82 */
+ {{1, 1},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0x83 */
+ {{0, 1},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x84 */
+ {{2, 2},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x85 */
+ {{0, 0},
+ {2, 2},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x86 */
+ {{1, 2},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0x87 */
+ {{0, 2},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x88 */
+ {{3, 3},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x89 */
+ {{0, 0},
+ {3, 3},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0x8a */
+ {{1, 1},
+ {3, 3},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x8b */
+ {{0, 1},
+ {3, 3},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x8c */
+ {{2, 3},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x8d */
+ {{0, 0},
+ {2, 3},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x8e */
+ {{1, 3},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0x8f */
+ {{0, 3},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x90 */
+ {{4, 4},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x91 */
+ {{0, 0},
+ {4, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0x92 */
+ {{1, 1},
+ {4, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x93 */
+ {{0, 1},
+ {4, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0x94 */
+ {{2, 2},
+ {4, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 4, 0, /* 0x95 */
+ {{0, 0},
+ {2, 2},
+ {4, 4},
+ {7, 7}
+ }
+ },
+ {0, 1, 3, 0, /* 0x96 */
+ {{1, 2},
+ {4, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x97 */
+ {{0, 2},
+ {4, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x98 */
+ {{3, 4},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x99 */
+ {{0, 0},
+ {3, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0x9a */
+ {{1, 1},
+ {3, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x9b */
+ {{0, 1},
+ {3, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x9c */
+ {{2, 4},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0x9d */
+ {{0, 0},
+ {2, 4},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0x9e */
+ {{1, 4},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0x9f */
+ {{0, 4},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xa0 */
+ {{5, 5},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xa1 */
+ {{0, 0},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xa2 */
+ {{1, 1},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xa3 */
+ {{0, 1},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xa4 */
+ {{2, 2},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 4, 0, /* 0xa5 */
+ {{0, 0},
+ {2, 2},
+ {5, 5},
+ {7, 7}
+ }
+ },
+ {0, 1, 3, 0, /* 0xa6 */
+ {{1, 2},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xa7 */
+ {{0, 2},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xa8 */
+ {{3, 3},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 4, 0, /* 0xa9 */
+ {{0, 0},
+ {3, 3},
+ {5, 5},
+ {7, 7}
+ }
+ },
+ {0, 1, 4, 0, /* 0xaa */
+ {{1, 1},
+ {3, 3},
+ {5, 5},
+ {7, 7}
+ }
+ },
+ {1, 1, 4, 0, /* 0xab */
+ {{0, 1},
+ {3, 3},
+ {5, 5},
+ {7, 7}
+ }
+ },
+ {0, 1, 3, 0, /* 0xac */
+ {{2, 3},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 4, 0, /* 0xad */
+ {{0, 0},
+ {2, 3},
+ {5, 5},
+ {7, 7}
+ }
+ },
+ {0, 1, 3, 0, /* 0xae */
+ {{1, 3},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xaf */
+ {{0, 3},
+ {5, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xb0 */
+ {{4, 5},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xb1 */
+ {{0, 0},
+ {4, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xb2 */
+ {{1, 1},
+ {4, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xb3 */
+ {{0, 1},
+ {4, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xb4 */
+ {{2, 2},
+ {4, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 4, 0, /* 0xb5 */
+ {{0, 0},
+ {2, 2},
+ {4, 5},
+ {7, 7}
+ }
+ },
+ {0, 1, 3, 0, /* 0xb6 */
+ {{1, 2},
+ {4, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xb7 */
+ {{0, 2},
+ {4, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xb8 */
+ {{3, 5},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xb9 */
+ {{0, 0},
+ {3, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xba */
+ {{1, 1},
+ {3, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xbb */
+ {{0, 1},
+ {3, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xbc */
+ {{2, 5},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xbd */
+ {{0, 0},
+ {2, 5},
+ {7, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xbe */
+ {{1, 5},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xbf */
+ {{0, 5},
+ {7, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 1, 0, /* 0xc0 */
+ {{6, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xc1 */
+ {{0, 0},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xc2 */
+ {{1, 1},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xc3 */
+ {{0, 1},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xc4 */
+ {{2, 2},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xc5 */
+ {{0, 0},
+ {2, 2},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xc6 */
+ {{1, 2},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xc7 */
+ {{0, 2},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xc8 */
+ {{3, 3},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xc9 */
+ {{0, 0},
+ {3, 3},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xca */
+ {{1, 1},
+ {3, 3},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xcb */
+ {{0, 1},
+ {3, 3},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xcc */
+ {{2, 3},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xcd */
+ {{0, 0},
+ {2, 3},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xce */
+ {{1, 3},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xcf */
+ {{0, 3},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xd0 */
+ {{4, 4},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xd1 */
+ {{0, 0},
+ {4, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xd2 */
+ {{1, 1},
+ {4, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xd3 */
+ {{0, 1},
+ {4, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xd4 */
+ {{2, 2},
+ {4, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 4, 0, /* 0xd5 */
+ {{0, 0},
+ {2, 2},
+ {4, 4},
+ {6, 7}
+ }
+ },
+ {0, 1, 3, 0, /* 0xd6 */
+ {{1, 2},
+ {4, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xd7 */
+ {{0, 2},
+ {4, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xd8 */
+ {{3, 4},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xd9 */
+ {{0, 0},
+ {3, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xda */
+ {{1, 1},
+ {3, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xdb */
+ {{0, 1},
+ {3, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xdc */
+ {{2, 4},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xdd */
+ {{0, 0},
+ {2, 4},
+ {6, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xde */
+ {{1, 4},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xdf */
+ {{0, 4},
+ {6, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 1, 0, /* 0xe0 */
+ {{5, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xe1 */
+ {{0, 0},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xe2 */
+ {{1, 1},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xe3 */
+ {{0, 1},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xe4 */
+ {{2, 2},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xe5 */
+ {{0, 0},
+ {2, 2},
+ {5, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xe6 */
+ {{1, 2},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xe7 */
+ {{0, 2},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xe8 */
+ {{3, 3},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xe9 */
+ {{0, 0},
+ {3, 3},
+ {5, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 3, 0, /* 0xea */
+ {{1, 1},
+ {3, 3},
+ {5, 7},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xeb */
+ {{0, 1},
+ {3, 3},
+ {5, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xec */
+ {{2, 3},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xed */
+ {{0, 0},
+ {2, 3},
+ {5, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xee */
+ {{1, 3},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xef */
+ {{0, 3},
+ {5, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 1, 0, /* 0xf0 */
+ {{4, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xf1 */
+ {{0, 0},
+ {4, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xf2 */
+ {{1, 1},
+ {4, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xf3 */
+ {{0, 1},
+ {4, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xf4 */
+ {{2, 2},
+ {4, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 3, 0, /* 0xf5 */
+ {{0, 0},
+ {2, 2},
+ {4, 7},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xf6 */
+ {{1, 2},
+ {4, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xf7 */
+ {{0, 2},
+ {4, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 1, 0, /* 0xf8 */
+ {{3, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xf9 */
+ {{0, 0},
+ {3, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 2, 0, /* 0xfa */
+ {{1, 1},
+ {3, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xfb */
+ {{0, 1},
+ {3, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 1, 0, /* 0xfc */
+ {{2, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 2, 0, /* 0xfd */
+ {{0, 0},
+ {2, 7},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {0, 1, 1, 0, /* 0xfe */
+ {{1, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ },
+ {1, 1, 1, 0, /* 0xff */
+ {{0, 7},
+ {0, 0},
+ {0, 0},
+ {0, 0}
+ }
+ }
+};
+
+int
+sctp_is_address_in_scope(struct sctp_ifa *ifa,
+ struct sctp_scoping *scope,
+ int do_update)
+{
+ if ((scope->loopback_scope == 0) &&
+ (ifa->ifn_p) && SCTP_IFN_IS_IFT_LOOP(ifa->ifn_p)) {
+ /*
+ * skip loopback if not in scope *
+ */
+ return (0);
+ }
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (scope->ipv4_addr_legal) {
+ struct sockaddr_in *sin;
+
+ sin = &ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* not in scope , unspecified */
+ return (0);
+ }
+ if ((scope->ipv4_local_scope == 0) &&
+ (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) {
+ /* private address not in scope */
+ return (0);
+ }
+ } else {
+ return (0);
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (scope->ipv6_addr_legal) {
+ struct sockaddr_in6 *sin6;
+
+ /* Must update the flags, bummer, which
+ * means any IFA locks must now be applied HERE <->
+ */
+ if (do_update) {
+ sctp_gather_internal_ifa_flags(ifa);
+ }
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ return (0);
+ }
+ /* ok to use deprecated addresses? */
+ sin6 = &ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* skip unspecified addresses */
+ return (0);
+ }
+ if ( /* (local_scope == 0) && */
+ (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))) {
+ return (0);
+ }
+ if ((scope->site_scope == 0) &&
+ (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) {
+ return (0);
+ }
+ } else {
+ return (0);
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (!scope->conn_addr_legal) {
+ return (0);
+ }
+ break;
+#endif
+ default:
+ return (0);
+ }
+ return (1);
+}
+
+static struct mbuf *
+sctp_add_addr_to_mbuf(struct mbuf *m, struct sctp_ifa *ifa, uint16_t *len)
+{
+#if defined(INET) || defined(INET6)
+ struct sctp_paramhdr *paramh;
+ struct mbuf *mret;
+ uint16_t plen;
+#endif
+
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ plen = (uint16_t)sizeof(struct sctp_ipv4addr_param);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ plen = (uint16_t)sizeof(struct sctp_ipv6addr_param);
+ break;
+#endif
+ default:
+ return (m);
+ }
+#if defined(INET) || defined(INET6)
+ if (M_TRAILINGSPACE(m) >= plen) {
+ /* easy side we just drop it on the end */
+ paramh = (struct sctp_paramhdr *)(SCTP_BUF_AT(m, SCTP_BUF_LEN(m)));
+ mret = m;
+ } else {
+ /* Need more space */
+ mret = m;
+ while (SCTP_BUF_NEXT(mret) != NULL) {
+ mret = SCTP_BUF_NEXT(mret);
+ }
+ SCTP_BUF_NEXT(mret) = sctp_get_mbuf_for_msg(plen, 0, M_NOWAIT, 1, MT_DATA);
+ if (SCTP_BUF_NEXT(mret) == NULL) {
+ /* We are hosed, can't add more addresses */
+ return (m);
+ }
+ mret = SCTP_BUF_NEXT(mret);
+ paramh = mtod(mret, struct sctp_paramhdr *);
+ }
+ /* now add the parameter */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sctp_ipv4addr_param *ipv4p;
+ struct sockaddr_in *sin;
+
+ sin = &ifa->address.sin;
+ ipv4p = (struct sctp_ipv4addr_param *)paramh;
+ paramh->param_type = htons(SCTP_IPV4_ADDRESS);
+ paramh->param_length = htons(plen);
+ ipv4p->addr = sin->sin_addr.s_addr;
+ SCTP_BUF_LEN(mret) += plen;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sctp_ipv6addr_param *ipv6p;
+ struct sockaddr_in6 *sin6;
+
+ sin6 = &ifa->address.sin6;
+ ipv6p = (struct sctp_ipv6addr_param *)paramh;
+ paramh->param_type = htons(SCTP_IPV6_ADDRESS);
+ paramh->param_length = htons(plen);
+ memcpy(ipv6p->addr, &sin6->sin6_addr,
+ sizeof(ipv6p->addr));
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+ /* clear embedded scope in the address */
+ in6_clearscope((struct in6_addr *)ipv6p->addr);
+#endif
+ SCTP_BUF_LEN(mret) += plen;
+ break;
+ }
+#endif
+ default:
+ return (m);
+ }
+ if (len != NULL) {
+ *len += plen;
+ }
+ return (mret);
+#endif
+}
+
+struct mbuf *
+sctp_add_addresses_to_i_ia(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_scoping *scope,
+ struct mbuf *m_at, int cnt_inits_to,
+ uint16_t *padding_len, uint16_t *chunk_len)
+{
+ struct sctp_vrf *vrf = NULL;
+ int cnt, limit_out = 0, total_count;
+ uint32_t vrf_id;
+
+ vrf_id = inp->def_vrf_id;
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (m_at);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ struct sctp_ifa *sctp_ifap;
+ struct sctp_ifn *sctp_ifnp;
+
+ cnt = cnt_inits_to;
+ if (vrf->total_ifa_count > SCTP_COUNT_LIMIT) {
+ limit_out = 1;
+ cnt = SCTP_ADDRESS_LIMIT;
+ goto skip_count;
+ }
+ LIST_FOREACH(sctp_ifnp, &vrf->ifnlist, next_ifn) {
+ if ((scope->loopback_scope == 0) &&
+ SCTP_IFN_IS_IFT_LOOP(sctp_ifnp)) {
+ /*
+ * Skip loopback devices if loopback_scope
+ * not set
+ */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifap, &sctp_ifnp->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifap->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifap->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifap->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifap->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if (sctp_is_addr_restricted(stcb, sctp_ifap)) {
+ continue;
+ }
+#if defined(__Userspace__)
+ if (sctp_ifap->address.sa.sa_family == AF_CONN) {
+ continue;
+ }
+#endif
+ if (sctp_is_address_in_scope(sctp_ifap, scope, 1) == 0) {
+ continue;
+ }
+ cnt++;
+ if (cnt > SCTP_ADDRESS_LIMIT) {
+ break;
+ }
+ }
+ if (cnt > SCTP_ADDRESS_LIMIT) {
+ break;
+ }
+ }
+ skip_count:
+ if (cnt > 1) {
+ total_count = 0;
+ LIST_FOREACH(sctp_ifnp, &vrf->ifnlist, next_ifn) {
+ cnt = 0;
+ if ((scope->loopback_scope == 0) &&
+ SCTP_IFN_IS_IFT_LOOP(sctp_ifnp)) {
+ /*
+ * Skip loopback devices if
+ * loopback_scope not set
+ */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifap, &sctp_ifnp->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifap->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifap->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifap->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifap->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if (sctp_is_addr_restricted(stcb, sctp_ifap)) {
+ continue;
+ }
+#if defined(__Userspace__)
+ if (sctp_ifap->address.sa.sa_family == AF_CONN) {
+ continue;
+ }
+#endif
+ if (sctp_is_address_in_scope(sctp_ifap,
+ scope, 0) == 0) {
+ continue;
+ }
+ if ((chunk_len != NULL) &&
+ (padding_len != NULL) &&
+ (*padding_len > 0)) {
+ memset(mtod(m_at, caddr_t) + *chunk_len, 0, *padding_len);
+ SCTP_BUF_LEN(m_at) += *padding_len;
+ *chunk_len += *padding_len;
+ *padding_len = 0;
+ }
+ m_at = sctp_add_addr_to_mbuf(m_at, sctp_ifap, chunk_len);
+ if (limit_out) {
+ cnt++;
+ total_count++;
+ if (cnt >= 2) {
+ /* two from each address */
+ break;
+ }
+ if (total_count > SCTP_ADDRESS_LIMIT) {
+ /* No more addresses */
+ break;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ struct sctp_laddr *laddr;
+
+ cnt = cnt_inits_to;
+ /* First, how many ? */
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ continue;
+ }
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED)
+ /* Address being deleted by the system, dont
+ * list.
+ */
+ continue;
+ if (laddr->action == SCTP_DEL_IP_ADDRESS) {
+ /* Address being deleted on this ep
+ * don't list.
+ */
+ continue;
+ }
+#if defined(__Userspace__)
+ if (laddr->ifa->address.sa.sa_family == AF_CONN) {
+ continue;
+ }
+#endif
+ if (sctp_is_address_in_scope(laddr->ifa,
+ scope, 1) == 0) {
+ continue;
+ }
+ cnt++;
+ }
+ /*
+ * To get through a NAT we only list addresses if we have
+ * more than one. That way if you just bind a single address
+ * we let the source of the init dictate our address.
+ */
+ if (cnt > 1) {
+ cnt = cnt_inits_to;
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ continue;
+ }
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
+ continue;
+ }
+#if defined(__Userspace__)
+ if (laddr->ifa->address.sa.sa_family == AF_CONN) {
+ continue;
+ }
+#endif
+ if (sctp_is_address_in_scope(laddr->ifa,
+ scope, 0) == 0) {
+ continue;
+ }
+ if ((chunk_len != NULL) &&
+ (padding_len != NULL) &&
+ (*padding_len > 0)) {
+ memset(mtod(m_at, caddr_t) + *chunk_len, 0, *padding_len);
+ SCTP_BUF_LEN(m_at) += *padding_len;
+ *chunk_len += *padding_len;
+ *padding_len = 0;
+ }
+ m_at = sctp_add_addr_to_mbuf(m_at, laddr->ifa, chunk_len);
+ cnt++;
+ if (cnt >= SCTP_ADDRESS_LIMIT) {
+ break;
+ }
+ }
+ }
+ }
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (m_at);
+}
+
+static struct sctp_ifa *
+sctp_is_ifa_addr_preferred(struct sctp_ifa *ifa,
+ uint8_t dest_is_loop,
+ uint8_t dest_is_priv,
+ sa_family_t fam)
+{
+ uint8_t dest_is_global = 0;
+ /* dest_is_priv is true if destination is a private address */
+ /* dest_is_loop is true if destination is a loopback addresses */
+
+ /**
+ * Here we determine if its a preferred address. A preferred address
+ * means it is the same scope or higher scope then the destination.
+ * L = loopback, P = private, G = global
+ * -----------------------------------------
+ * src | dest | result
+ * ----------------------------------------
+ * L | L | yes
+ * -----------------------------------------
+ * P | L | yes-v4 no-v6
+ * -----------------------------------------
+ * G | L | yes-v4 no-v6
+ * -----------------------------------------
+ * L | P | no
+ * -----------------------------------------
+ * P | P | yes
+ * -----------------------------------------
+ * G | P | no
+ * -----------------------------------------
+ * L | G | no
+ * -----------------------------------------
+ * P | G | no
+ * -----------------------------------------
+ * G | G | yes
+ * -----------------------------------------
+ */
+
+ if (ifa->address.sa.sa_family != fam) {
+ /* forget mis-matched family */
+ return (NULL);
+ }
+ if ((dest_is_priv == 0) && (dest_is_loop == 0)) {
+ dest_is_global = 1;
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Is destination preferred:");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, &ifa->address.sa);
+ /* Ok the address may be ok */
+#ifdef INET6
+ if (fam == AF_INET6) {
+ /* ok to use deprecated addresses? no lets not! */
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "NO:1\n");
+ return (NULL);
+ }
+ if (ifa->src_is_priv && !ifa->src_is_loop) {
+ if (dest_is_loop) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "NO:2\n");
+ return (NULL);
+ }
+ }
+ if (ifa->src_is_glob) {
+ if (dest_is_loop) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "NO:3\n");
+ return (NULL);
+ }
+ }
+ }
+#endif
+ /* Now that we know what is what, implement or table
+ * this could in theory be done slicker (it used to be), but this
+ * is straightforward and easier to validate :-)
+ */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "src_loop:%d src_priv:%d src_glob:%d\n",
+ ifa->src_is_loop, ifa->src_is_priv, ifa->src_is_glob);
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "dest_loop:%d dest_priv:%d dest_glob:%d\n",
+ dest_is_loop, dest_is_priv, dest_is_global);
+
+ if ((ifa->src_is_loop) && (dest_is_priv)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "NO:4\n");
+ return (NULL);
+ }
+ if ((ifa->src_is_glob) && (dest_is_priv)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "NO:5\n");
+ return (NULL);
+ }
+ if ((ifa->src_is_loop) && (dest_is_global)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "NO:6\n");
+ return (NULL);
+ }
+ if ((ifa->src_is_priv) && (dest_is_global)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "NO:7\n");
+ return (NULL);
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "YES\n");
+ /* its a preferred address */
+ return (ifa);
+}
+
+static struct sctp_ifa *
+sctp_is_ifa_addr_acceptable(struct sctp_ifa *ifa,
+ uint8_t dest_is_loop,
+ uint8_t dest_is_priv,
+ sa_family_t fam)
+{
+ uint8_t dest_is_global = 0;
+
+ /**
+ * Here we determine if its a acceptable address. A acceptable
+ * address means it is the same scope or higher scope but we can
+ * allow for NAT which means its ok to have a global dest and a
+ * private src.
+ *
+ * L = loopback, P = private, G = global
+ * -----------------------------------------
+ * src | dest | result
+ * -----------------------------------------
+ * L | L | yes
+ * -----------------------------------------
+ * P | L | yes-v4 no-v6
+ * -----------------------------------------
+ * G | L | yes
+ * -----------------------------------------
+ * L | P | no
+ * -----------------------------------------
+ * P | P | yes
+ * -----------------------------------------
+ * G | P | yes - May not work
+ * -----------------------------------------
+ * L | G | no
+ * -----------------------------------------
+ * P | G | yes - May not work
+ * -----------------------------------------
+ * G | G | yes
+ * -----------------------------------------
+ */
+
+ if (ifa->address.sa.sa_family != fam) {
+ /* forget non matching family */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "ifa_fam:%d fam:%d\n",
+ ifa->address.sa.sa_family, fam);
+ return (NULL);
+ }
+ /* Ok the address may be ok */
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT3, &ifa->address.sa);
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "dst_is_loop:%d dest_is_priv:%d\n",
+ dest_is_loop, dest_is_priv);
+ if ((dest_is_loop == 0) && (dest_is_priv == 0)) {
+ dest_is_global = 1;
+ }
+#ifdef INET6
+ if (fam == AF_INET6) {
+ /* ok to use deprecated addresses? */
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ return (NULL);
+ }
+ if (ifa->src_is_priv) {
+ /* Special case, linklocal to loop */
+ if (dest_is_loop)
+ return (NULL);
+ }
+ }
+#endif
+ /*
+ * Now that we know what is what, implement our table.
+ * This could in theory be done slicker (it used to be), but this
+ * is straightforward and easier to validate :-)
+ */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "ifa->src_is_loop:%d dest_is_priv:%d\n",
+ ifa->src_is_loop,
+ dest_is_priv);
+ if ((ifa->src_is_loop == 1) && (dest_is_priv)) {
+ return (NULL);
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "ifa->src_is_loop:%d dest_is_glob:%d\n",
+ ifa->src_is_loop,
+ dest_is_global);
+ if ((ifa->src_is_loop == 1) && (dest_is_global)) {
+ return (NULL);
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "address is acceptable\n");
+ /* its an acceptable address */
+ return (ifa);
+}
+
+int
+sctp_is_addr_restricted(struct sctp_tcb *stcb, struct sctp_ifa *ifa)
+{
+ struct sctp_laddr *laddr;
+
+ if (stcb == NULL) {
+ /* There are no restrictions, no TCB :-) */
+ return (0);
+ }
+ LIST_FOREACH(laddr, &stcb->asoc.sctp_restricted_addrs, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "%s: NULL ifa\n",
+ __func__);
+ continue;
+ }
+ if (laddr->ifa == ifa) {
+ /* Yes it is on the list */
+ return (1);
+ }
+ }
+ return (0);
+}
+
+int
+sctp_is_addr_in_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa)
+{
+ struct sctp_laddr *laddr;
+
+ if (ifa == NULL)
+ return (0);
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "%s: NULL ifa\n",
+ __func__);
+ continue;
+ }
+ if ((laddr->ifa == ifa) && laddr->action == 0)
+ /* same pointer */
+ return (1);
+ }
+ return (0);
+}
+
+static struct sctp_ifa *
+sctp_choose_boundspecific_inp(struct sctp_inpcb *inp,
+ sctp_route_t *ro,
+ uint32_t vrf_id,
+ int non_asoc_addr_ok,
+ uint8_t dest_is_priv,
+ uint8_t dest_is_loop,
+ sa_family_t fam)
+{
+ struct sctp_laddr *laddr, *starting_point;
+ void *ifn;
+ int resettotop = 0;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa, *sifa;
+ struct sctp_vrf *vrf;
+ uint32_t ifn_index;
+
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL)
+ return (NULL);
+
+ ifn = SCTP_GET_IFN_VOID_FROM_ROUTE(ro);
+ ifn_index = SCTP_GET_IF_INDEX_FROM_ROUTE(ro);
+ sctp_ifn = sctp_find_ifn(ifn, ifn_index);
+ /*
+ * first question, is the ifn we will emit on in our list, if so, we
+ * want such an address. Note that we first looked for a
+ * preferred address.
+ */
+ if (sctp_ifn) {
+ /* is a preferred one on the interface we route out? */
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifa->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) &&
+ (non_asoc_addr_ok == 0))
+ continue;
+ sifa = sctp_is_ifa_addr_preferred(sctp_ifa,
+ dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+ if (sctp_is_addr_in_ep(inp, sifa)) {
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+ }
+ }
+ /*
+ * ok, now we now need to find one on the list of the addresses.
+ * We can't get one on the emitting interface so let's find first
+ * a preferred one. If not that an acceptable one otherwise...
+ * we return NULL.
+ */
+ starting_point = inp->next_addr_touse;
+ once_again:
+ if (inp->next_addr_touse == NULL) {
+ inp->next_addr_touse = LIST_FIRST(&inp->sctp_addr_list);
+ resettotop = 1;
+ }
+ for (laddr = inp->next_addr_touse; laddr;
+ laddr = LIST_NEXT(laddr, sctp_nxt_addr)) {
+ if (laddr->ifa == NULL) {
+ /* address has been removed */
+ continue;
+ }
+ if (laddr->action == SCTP_DEL_IP_ADDRESS) {
+ /* address is being deleted */
+ continue;
+ }
+ sifa = sctp_is_ifa_addr_preferred(laddr->ifa, dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+ if (resettotop == 0) {
+ inp->next_addr_touse = NULL;
+ goto once_again;
+ }
+
+ inp->next_addr_touse = starting_point;
+ resettotop = 0;
+ once_again_too:
+ if (inp->next_addr_touse == NULL) {
+ inp->next_addr_touse = LIST_FIRST(&inp->sctp_addr_list);
+ resettotop = 1;
+ }
+
+ /* ok, what about an acceptable address in the inp */
+ for (laddr = inp->next_addr_touse; laddr;
+ laddr = LIST_NEXT(laddr, sctp_nxt_addr)) {
+ if (laddr->ifa == NULL) {
+ /* address has been removed */
+ continue;
+ }
+ if (laddr->action == SCTP_DEL_IP_ADDRESS) {
+ /* address is being deleted */
+ continue;
+ }
+ sifa = sctp_is_ifa_addr_acceptable(laddr->ifa, dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+ if (resettotop == 0) {
+ inp->next_addr_touse = NULL;
+ goto once_again_too;
+ }
+
+ /*
+ * no address bound can be a source for the destination we are in
+ * trouble
+ */
+ return (NULL);
+}
+
+static struct sctp_ifa *
+sctp_choose_boundspecific_stcb(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ sctp_route_t *ro,
+ uint32_t vrf_id,
+ uint8_t dest_is_priv,
+ uint8_t dest_is_loop,
+ int non_asoc_addr_ok,
+ sa_family_t fam)
+{
+ struct sctp_laddr *laddr, *starting_point;
+ void *ifn;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa, *sifa;
+ uint8_t start_at_beginning = 0;
+ struct sctp_vrf *vrf;
+ uint32_t ifn_index;
+
+ /*
+ * first question, is the ifn we will emit on in our list, if so, we
+ * want that one.
+ */
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL)
+ return (NULL);
+
+ ifn = SCTP_GET_IFN_VOID_FROM_ROUTE(ro);
+ ifn_index = SCTP_GET_IF_INDEX_FROM_ROUTE(ro);
+ sctp_ifn = sctp_find_ifn(ifn, ifn_index);
+
+ /*
+ * first question, is the ifn we will emit on in our list? If so,
+ * we want that one. First we look for a preferred. Second, we go
+ * for an acceptable.
+ */
+ if (sctp_ifn) {
+ /* first try for a preferred address on the ep */
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifa->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0))
+ continue;
+ if (sctp_is_addr_in_ep(inp, sctp_ifa)) {
+ sifa = sctp_is_ifa_addr_preferred(sctp_ifa, dest_is_loop, dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /* on the no-no list */
+ continue;
+ }
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+ }
+ /* next try for an acceptable address on the ep */
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifa->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0))
+ continue;
+ if (sctp_is_addr_in_ep(inp, sctp_ifa)) {
+ sifa= sctp_is_ifa_addr_acceptable(sctp_ifa, dest_is_loop, dest_is_priv,fam);
+ if (sifa == NULL)
+ continue;
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /* on the no-no list */
+ continue;
+ }
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+ }
+ }
+ /*
+ * if we can't find one like that then we must look at all
+ * addresses bound to pick one at first preferable then
+ * secondly acceptable.
+ */
+ starting_point = stcb->asoc.last_used_address;
+ sctp_from_the_top:
+ if (stcb->asoc.last_used_address == NULL) {
+ start_at_beginning = 1;
+ stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list);
+ }
+ /* search beginning with the last used address */
+ for (laddr = stcb->asoc.last_used_address; laddr;
+ laddr = LIST_NEXT(laddr, sctp_nxt_addr)) {
+ if (laddr->ifa == NULL) {
+ /* address has been removed */
+ continue;
+ }
+ if (laddr->action == SCTP_DEL_IP_ADDRESS) {
+ /* address is being deleted */
+ continue;
+ }
+ sifa = sctp_is_ifa_addr_preferred(laddr->ifa, dest_is_loop, dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /* on the no-no list */
+ continue;
+ }
+ stcb->asoc.last_used_address = laddr;
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+ if (start_at_beginning == 0) {
+ stcb->asoc.last_used_address = NULL;
+ goto sctp_from_the_top;
+ }
+ /* now try for any higher scope than the destination */
+ stcb->asoc.last_used_address = starting_point;
+ start_at_beginning = 0;
+ sctp_from_the_top2:
+ if (stcb->asoc.last_used_address == NULL) {
+ start_at_beginning = 1;
+ stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list);
+ }
+ /* search beginning with the last used address */
+ for (laddr = stcb->asoc.last_used_address; laddr;
+ laddr = LIST_NEXT(laddr, sctp_nxt_addr)) {
+ if (laddr->ifa == NULL) {
+ /* address has been removed */
+ continue;
+ }
+ if (laddr->action == SCTP_DEL_IP_ADDRESS) {
+ /* address is being deleted */
+ continue;
+ }
+ sifa = sctp_is_ifa_addr_acceptable(laddr->ifa, dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /* on the no-no list */
+ continue;
+ }
+ stcb->asoc.last_used_address = laddr;
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+ if (start_at_beginning == 0) {
+ stcb->asoc.last_used_address = NULL;
+ goto sctp_from_the_top2;
+ }
+ return (NULL);
+}
+
+static struct sctp_ifa *
+sctp_select_nth_preferred_addr_from_ifn_boundall(struct sctp_ifn *ifn,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct sctp_inpcb *inp,
+#else
+ struct sctp_inpcb *inp SCTP_UNUSED,
+#endif
+ struct sctp_tcb *stcb,
+ int non_asoc_addr_ok,
+ uint8_t dest_is_loop,
+ uint8_t dest_is_priv,
+ int addr_wanted,
+ sa_family_t fam,
+ sctp_route_t *ro)
+{
+ struct sctp_ifa *ifa, *sifa;
+ int num_eligible_addr = 0;
+#ifdef INET6
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ struct sockaddr_in6 sin6, lsa6;
+
+ if (fam == AF_INET6) {
+ memcpy(&sin6, &ro->ro_dst, sizeof(struct sockaddr_in6));
+#ifdef SCTP_KAME
+ (void)sa6_recoverscope(&sin6);
+#else
+ (void)in6_recoverscope(&sin6, &sin6.sin6_addr, NULL);
+#endif /* SCTP_KAME */
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#endif /* INET6 */
+ LIST_FOREACH(ifa, &ifn->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((ifa->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if ((ifa->localifa_flags & SCTP_ADDR_DEFER_USE) &&
+ (non_asoc_addr_ok == 0))
+ continue;
+ sifa = sctp_is_ifa_addr_preferred(ifa, dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+#ifdef INET6
+ if (fam == AF_INET6 &&
+ dest_is_loop &&
+ sifa->src_is_loop && sifa->src_is_priv) {
+ /* don't allow fe80::1 to be a src on loop ::1, we don't list it
+ * to the peer so we will get an abort.
+ */
+ continue;
+ }
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ if (fam == AF_INET6 &&
+ IN6_IS_ADDR_LINKLOCAL(&sifa->address.sin6.sin6_addr) &&
+ IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) {
+ /* link-local <-> link-local must belong to the same scope. */
+ memcpy(&lsa6, &sifa->address.sin6, sizeof(struct sockaddr_in6));
+#ifdef SCTP_KAME
+ (void)sa6_recoverscope(&lsa6);
+#else
+ (void)in6_recoverscope(&lsa6, &lsa6.sin6_addr, NULL);
+#endif /* SCTP_KAME */
+ if (sin6.sin6_scope_id != lsa6.sin6_scope_id) {
+ continue;
+ }
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#endif /* INET6 */
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+ /* Check if the IPv6 address matches to next-hop.
+ In the mobile case, old IPv6 address may be not deleted
+ from the interface. Then, the interface has previous and
+ new addresses. We should use one corresponding to the
+ next-hop. (by micchie)
+ */
+#ifdef INET6
+ if (stcb && fam == AF_INET6 &&
+ sctp_is_mobility_feature_on(stcb->sctp_ep, SCTP_MOBILITY_BASE)) {
+ if (sctp_v6src_match_nexthop(&sifa->address.sin6, ro) == 0) {
+ continue;
+ }
+ }
+#endif
+#ifdef INET
+ /* Avoid topologically incorrect IPv4 address */
+ if (stcb && fam == AF_INET &&
+ sctp_is_mobility_feature_on(stcb->sctp_ep, SCTP_MOBILITY_BASE)) {
+ if (sctp_v4src_match_nexthop(sifa, ro) == 0) {
+ continue;
+ }
+ }
+#endif
+#endif
+ if (stcb) {
+ if (sctp_is_address_in_scope(ifa, &stcb->asoc.scope, 0) == 0) {
+ continue;
+ }
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /*
+ * It is restricted for some reason..
+ * probably not yet added.
+ */
+ continue;
+ }
+ }
+ if (num_eligible_addr >= addr_wanted) {
+ return (sifa);
+ }
+ num_eligible_addr++;
+ }
+ return (NULL);
+}
+
+static int
+sctp_count_num_preferred_boundall(struct sctp_ifn *ifn,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct sctp_inpcb *inp,
+#else
+ struct sctp_inpcb *inp SCTP_UNUSED,
+#endif
+ struct sctp_tcb *stcb,
+ int non_asoc_addr_ok,
+ uint8_t dest_is_loop,
+ uint8_t dest_is_priv,
+ sa_family_t fam)
+{
+ struct sctp_ifa *ifa, *sifa;
+ int num_eligible_addr = 0;
+
+ LIST_FOREACH(ifa, &ifn->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((ifa->address.sa.sa_family == AF_INET6) &&
+ (stcb != NULL) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if ((ifa->localifa_flags & SCTP_ADDR_DEFER_USE) &&
+ (non_asoc_addr_ok == 0)) {
+ continue;
+ }
+ sifa = sctp_is_ifa_addr_preferred(ifa, dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL) {
+ continue;
+ }
+ if (stcb) {
+ if (sctp_is_address_in_scope(ifa, &stcb->asoc.scope, 0) == 0) {
+ continue;
+ }
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /*
+ * It is restricted for some reason..
+ * probably not yet added.
+ */
+ continue;
+ }
+ }
+ num_eligible_addr++;
+ }
+ return (num_eligible_addr);
+}
+
+static struct sctp_ifa *
+sctp_choose_boundall(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ sctp_route_t *ro,
+ uint32_t vrf_id,
+ uint8_t dest_is_priv,
+ uint8_t dest_is_loop,
+ int non_asoc_addr_ok,
+ sa_family_t fam)
+{
+ int cur_addr_num = 0, num_preferred = 0;
+ void *ifn;
+ struct sctp_ifn *sctp_ifn, *looked_at = NULL, *emit_ifn;
+ struct sctp_ifa *sctp_ifa, *sifa;
+ uint32_t ifn_index;
+ struct sctp_vrf *vrf;
+#ifdef INET
+ int retried = 0;
+#endif
+
+ /*-
+ * For boundall we can use any address in the association.
+ * If non_asoc_addr_ok is set we can use any address (at least in
+ * theory). So we look for preferred addresses first. If we find one,
+ * we use it. Otherwise we next try to get an address on the
+ * interface, which we should be able to do (unless non_asoc_addr_ok
+ * is false and we are routed out that way). In these cases where we
+ * can't use the address of the interface we go through all the
+ * ifn's looking for an address we can use and fill that in. Punting
+ * means we send back address 0, which will probably cause problems
+ * actually since then IP will fill in the address of the route ifn,
+ * which means we probably already rejected it.. i.e. here comes an
+ * abort :-<.
+ */
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL)
+ return (NULL);
+
+ ifn = SCTP_GET_IFN_VOID_FROM_ROUTE(ro);
+ ifn_index = SCTP_GET_IF_INDEX_FROM_ROUTE(ro);
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"ifn from route:%p ifn_index:%d\n", ifn, ifn_index);
+ emit_ifn = looked_at = sctp_ifn = sctp_find_ifn(ifn, ifn_index);
+ if (sctp_ifn == NULL) {
+ /* ?? We don't have this guy ?? */
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"No ifn emit interface?\n");
+ goto bound_all_plan_b;
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"ifn_index:%d name:%s is emit interface\n",
+ ifn_index, sctp_ifn->ifn_name);
+
+ if (net) {
+ cur_addr_num = net->indx_of_eligible_next_to_use;
+ }
+ num_preferred = sctp_count_num_preferred_boundall(sctp_ifn,
+ inp, stcb,
+ non_asoc_addr_ok,
+ dest_is_loop,
+ dest_is_priv, fam);
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Found %d preferred source addresses for intf:%s\n",
+ num_preferred, sctp_ifn->ifn_name);
+ if (num_preferred == 0) {
+ /*
+ * no eligible addresses, we must use some other interface
+ * address if we can find one.
+ */
+ goto bound_all_plan_b;
+ }
+ /*
+ * Ok we have num_eligible_addr set with how many we can use, this
+ * may vary from call to call due to addresses being deprecated
+ * etc..
+ */
+ if (cur_addr_num >= num_preferred) {
+ cur_addr_num = 0;
+ }
+ /*
+ * select the nth address from the list (where cur_addr_num is the
+ * nth) and 0 is the first one, 1 is the second one etc...
+ */
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "cur_addr_num:%d\n", cur_addr_num);
+
+ sctp_ifa = sctp_select_nth_preferred_addr_from_ifn_boundall(sctp_ifn, inp, stcb, non_asoc_addr_ok, dest_is_loop,
+ dest_is_priv, cur_addr_num, fam, ro);
+
+ /* if sctp_ifa is NULL something changed??, fall to plan b. */
+ if (sctp_ifa) {
+ atomic_add_int(&sctp_ifa->refcount, 1);
+ if (net) {
+ /* save off where the next one we will want */
+ net->indx_of_eligible_next_to_use = cur_addr_num + 1;
+ }
+ return (sctp_ifa);
+ }
+ /*
+ * plan_b: Look at all interfaces and find a preferred address. If
+ * no preferred fall through to plan_c.
+ */
+ bound_all_plan_b:
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Trying Plan B\n");
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Examine interface %s\n",
+ sctp_ifn->ifn_name);
+ if (dest_is_loop == 0 && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* wrong base scope */
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "skip\n");
+ continue;
+ }
+ if ((sctp_ifn == looked_at) && looked_at) {
+ /* already looked at this guy */
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "already seen\n");
+ continue;
+ }
+ num_preferred = sctp_count_num_preferred_boundall(sctp_ifn, inp, stcb, non_asoc_addr_ok,
+ dest_is_loop, dest_is_priv, fam);
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,
+ "Found ifn:%p %d preferred source addresses\n",
+ ifn, num_preferred);
+ if (num_preferred == 0) {
+ /* None on this interface. */
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "No preferred -- skipping to next\n");
+ continue;
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,
+ "num preferred:%d on interface:%p cur_addr_num:%d\n",
+ num_preferred, (void *)sctp_ifn, cur_addr_num);
+
+ /*
+ * Ok we have num_eligible_addr set with how many we can
+ * use, this may vary from call to call due to addresses
+ * being deprecated etc..
+ */
+ if (cur_addr_num >= num_preferred) {
+ cur_addr_num = 0;
+ }
+ sifa = sctp_select_nth_preferred_addr_from_ifn_boundall(sctp_ifn, inp, stcb, non_asoc_addr_ok, dest_is_loop,
+ dest_is_priv, cur_addr_num, fam, ro);
+ if (sifa == NULL)
+ continue;
+ if (net) {
+ net->indx_of_eligible_next_to_use = cur_addr_num + 1;
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "we selected %d\n",
+ cur_addr_num);
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Source:");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, &sifa->address.sa);
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Dest:");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, &net->ro._l_addr.sa);
+ }
+ atomic_add_int(&sifa->refcount, 1);
+ return (sifa);
+ }
+#ifdef INET
+again_with_private_addresses_allowed:
+#endif
+ /* plan_c: do we have an acceptable address on the emit interface */
+ sifa = NULL;
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"Trying Plan C: find acceptable on interface\n");
+ if (emit_ifn == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"Jump to Plan D - no emit_ifn\n");
+ goto plan_d;
+ }
+ LIST_FOREACH(sctp_ifa, &emit_ifn->ifalist, next_ifa) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "ifa:%p\n", (void *)sctp_ifa);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin.sin_addr) != 0)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"Jailed\n");
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifa->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin6.sin6_addr) != 0)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"Jailed\n");
+ continue;
+ }
+#endif
+#endif
+ if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) &&
+ (non_asoc_addr_ok == 0)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2,"Defer\n");
+ continue;
+ }
+ sifa = sctp_is_ifa_addr_acceptable(sctp_ifa, dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "IFA not acceptable\n");
+ continue;
+ }
+ if (stcb) {
+ if (sctp_is_address_in_scope(sifa, &stcb->asoc.scope, 0) == 0) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "NOT in scope\n");
+ sifa = NULL;
+ continue;
+ }
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /*
+ * It is restricted for some
+ * reason.. probably not yet added.
+ */
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Its restricted\n");
+ sifa = NULL;
+ continue;
+ }
+ }
+ atomic_add_int(&sifa->refcount, 1);
+ goto out;
+ }
+ plan_d:
+ /*
+ * plan_d: We are in trouble. No preferred address on the emit
+ * interface. And not even a preferred address on all interfaces.
+ * Go out and see if we can find an acceptable address somewhere
+ * amongst all interfaces.
+ */
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Trying Plan D looked_at is %p\n", (void *)looked_at);
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if (dest_is_loop == 0 && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* wrong base scope */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifa->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) &&
+ (non_asoc_addr_ok == 0))
+ continue;
+ sifa = sctp_is_ifa_addr_acceptable(sctp_ifa,
+ dest_is_loop,
+ dest_is_priv, fam);
+ if (sifa == NULL)
+ continue;
+ if (stcb) {
+ if (sctp_is_address_in_scope(sifa, &stcb->asoc.scope, 0) == 0) {
+ sifa = NULL;
+ continue;
+ }
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, sifa)) &&
+ (!sctp_is_addr_pending(stcb, sifa)))) {
+ /*
+ * It is restricted for some
+ * reason.. probably not yet added.
+ */
+ sifa = NULL;
+ continue;
+ }
+ }
+ goto out;
+ }
+ }
+#ifdef INET
+ if (stcb) {
+ if ((retried == 0) && (stcb->asoc.scope.ipv4_local_scope == 0)) {
+ stcb->asoc.scope.ipv4_local_scope = 1;
+ retried = 1;
+ goto again_with_private_addresses_allowed;
+ } else if (retried == 1) {
+ stcb->asoc.scope.ipv4_local_scope = 0;
+ }
+ }
+#endif
+out:
+#ifdef INET
+ if (sifa) {
+ if (retried == 1) {
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if (dest_is_loop == 0 && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* wrong base scope */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ struct sctp_ifa *tmp_sifa;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ if ((sctp_ifa->address.sa.sa_family == AF_INET) &&
+ (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin.sin_addr) != 0)) {
+ continue;
+ }
+#endif
+#ifdef INET6
+ if ((sctp_ifa->address.sa.sa_family == AF_INET6) &&
+ (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sctp_ifa->address.sin6.sin6_addr) != 0)) {
+ continue;
+ }
+#endif
+#endif
+ if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) &&
+ (non_asoc_addr_ok == 0))
+ continue;
+ tmp_sifa = sctp_is_ifa_addr_acceptable(sctp_ifa,
+ dest_is_loop,
+ dest_is_priv, fam);
+ if (tmp_sifa == NULL) {
+ continue;
+ }
+ if (tmp_sifa == sifa) {
+ continue;
+ }
+ if (stcb) {
+ if (sctp_is_address_in_scope(tmp_sifa,
+ &stcb->asoc.scope, 0) == 0) {
+ continue;
+ }
+ if (((non_asoc_addr_ok == 0) &&
+ (sctp_is_addr_restricted(stcb, tmp_sifa))) ||
+ (non_asoc_addr_ok &&
+ (sctp_is_addr_restricted(stcb, tmp_sifa)) &&
+ (!sctp_is_addr_pending(stcb, tmp_sifa)))) {
+ /*
+ * It is restricted for some
+ * reason.. probably not yet added.
+ */
+ continue;
+ }
+ }
+ if ((tmp_sifa->address.sin.sin_family == AF_INET) &&
+ (IN4_ISPRIVATE_ADDRESS(&(tmp_sifa->address.sin.sin_addr)))) {
+ sctp_add_local_addr_restricted(stcb, tmp_sifa);
+ }
+ }
+ }
+ }
+ atomic_add_int(&sifa->refcount, 1);
+ }
+#endif
+ return (sifa);
+}
+
+/* tcb may be NULL */
+struct sctp_ifa *
+sctp_source_address_selection(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ sctp_route_t *ro,
+ struct sctp_nets *net,
+ int non_asoc_addr_ok, uint32_t vrf_id)
+{
+ struct sctp_ifa *answer;
+ uint8_t dest_is_priv, dest_is_loop;
+ sa_family_t fam;
+#ifdef INET
+ struct sockaddr_in *to = (struct sockaddr_in *)&ro->ro_dst;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)&ro->ro_dst;
+#endif
+
+ /**
+ * Rules:
+ * - Find the route if needed, cache if I can.
+ * - Look at interface address in route, Is it in the bound list. If so we
+ * have the best source.
+ * - If not we must rotate amongst the addresses.
+ *
+ * Caveats and issues
+ *
+ * Do we need to pay attention to scope. We can have a private address
+ * or a global address we are sourcing or sending to. So if we draw
+ * it out
+ * zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ * For V4
+ * ------------------------------------------
+ * source * dest * result
+ * -----------------------------------------
+ * <a> Private * Global * NAT
+ * -----------------------------------------
+ * <b> Private * Private * No problem
+ * -----------------------------------------
+ * <c> Global * Private * Huh, How will this work?
+ * -----------------------------------------
+ * <d> Global * Global * No Problem
+ *------------------------------------------
+ * zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ * For V6
+ *------------------------------------------
+ * source * dest * result
+ * -----------------------------------------
+ * <a> Linklocal * Global *
+ * -----------------------------------------
+ * <b> Linklocal * Linklocal * No problem
+ * -----------------------------------------
+ * <c> Global * Linklocal * Huh, How will this work?
+ * -----------------------------------------
+ * <d> Global * Global * No Problem
+ *------------------------------------------
+ * zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ *
+ * And then we add to that what happens if there are multiple addresses
+ * assigned to an interface. Remember the ifa on a ifn is a linked
+ * list of addresses. So one interface can have more than one IP
+ * address. What happens if we have both a private and a global
+ * address? Do we then use context of destination to sort out which
+ * one is best? And what about NAT's sending P->G may get you a NAT
+ * translation, or should you select the G thats on the interface in
+ * preference.
+ *
+ * Decisions:
+ *
+ * - count the number of addresses on the interface.
+ * - if it is one, no problem except case <c>.
+ * For <a> we will assume a NAT out there.
+ * - if there are more than one, then we need to worry about scope P
+ * or G. We should prefer G -> G and P -> P if possible.
+ * Then as a secondary fall back to mixed types G->P being a last
+ * ditch one.
+ * - The above all works for bound all, but bound specific we need to
+ * use the same concept but instead only consider the bound
+ * addresses. If the bound set is NOT assigned to the interface then
+ * we must use rotation amongst the bound addresses..
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (ro->ro_nh == NULL) {
+#else
+ if (ro->ro_rt == NULL) {
+#endif
+ /*
+ * Need a route to cache.
+ */
+ SCTP_RTALLOC(ro, vrf_id, inp->fibnum);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (ro->ro_nh == NULL) {
+#else
+ if (ro->ro_rt == NULL) {
+#endif
+ return (NULL);
+ }
+#if defined(_WIN32)
+ /* On Windows the sa_family is U_SHORT or ADDRESS_FAMILY */
+ fam = (sa_family_t)ro->ro_dst.sa_family;
+#else
+ fam = ro->ro_dst.sa_family;
+#endif
+ dest_is_priv = dest_is_loop = 0;
+ /* Setup our scopes for the destination */
+ switch (fam) {
+#ifdef INET
+ case AF_INET:
+ /* Scope based on outbound address */
+ if (IN4_ISLOOPBACK_ADDRESS(&to->sin_addr)) {
+ dest_is_loop = 1;
+ if (net != NULL) {
+ /* mark it as local */
+ net->addr_is_local = 1;
+ }
+ } else if ((IN4_ISPRIVATE_ADDRESS(&to->sin_addr))) {
+ dest_is_priv = 1;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ /* Scope based on outbound address */
+#if defined(_WIN32)
+ if (IN6_IS_ADDR_LOOPBACK(&to6->sin6_addr)) {
+#else
+ if (IN6_IS_ADDR_LOOPBACK(&to6->sin6_addr) ||
+ SCTP_ROUTE_IS_REAL_LOOP(ro)) {
+#endif
+ /*
+ * If the address is a loopback address, which
+ * consists of "::1" OR "fe80::1%lo0", we are loopback
+ * scope. But we don't use dest_is_priv (link local
+ * addresses).
+ */
+ dest_is_loop = 1;
+ if (net != NULL) {
+ /* mark it as local */
+ net->addr_is_local = 1;
+ }
+ } else if (IN6_IS_ADDR_LINKLOCAL(&to6->sin6_addr)) {
+ dest_is_priv = 1;
+ }
+ break;
+#endif
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "Select source addr for:");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, (struct sockaddr *)&ro->ro_dst);
+ SCTP_IPI_ADDR_RLOCK();
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /*
+ * Bound all case
+ */
+ answer = sctp_choose_boundall(inp, stcb, net, ro, vrf_id,
+ dest_is_priv, dest_is_loop,
+ non_asoc_addr_ok, fam);
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (answer);
+ }
+ /*
+ * Subset bound case
+ */
+ if (stcb) {
+ answer = sctp_choose_boundspecific_stcb(inp, stcb, ro,
+ vrf_id, dest_is_priv,
+ dest_is_loop,
+ non_asoc_addr_ok, fam);
+ } else {
+ answer = sctp_choose_boundspecific_inp(inp, ro, vrf_id,
+ non_asoc_addr_ok,
+ dest_is_priv,
+ dest_is_loop, fam);
+ }
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (answer);
+}
+
+static bool
+sctp_find_cmsg(int c_type, void *data, struct mbuf *control, size_t cpsize)
+{
+#if defined(_WIN32)
+ WSACMSGHDR cmh;
+#else
+ struct cmsghdr cmh;
+#endif
+ struct sctp_sndinfo sndinfo;
+ struct sctp_prinfo prinfo;
+ struct sctp_authinfo authinfo;
+ int tot_len, rem_len, cmsg_data_len, cmsg_data_off, off;
+ bool found;
+
+ /*
+ * Independent of how many mbufs, find the c_type inside the control
+ * structure and copy out the data.
+ */
+ found = false;
+ tot_len = SCTP_BUF_LEN(control);
+ for (off = 0; off < tot_len; off += CMSG_ALIGN(cmh.cmsg_len)) {
+ rem_len = tot_len - off;
+ if (rem_len < (int)CMSG_ALIGN(sizeof(cmh))) {
+ /* There is not enough room for one more. */
+ return (found);
+ }
+ m_copydata(control, off, sizeof(cmh), (caddr_t)&cmh);
+ if (cmh.cmsg_len < CMSG_ALIGN(sizeof(cmh))) {
+ /* We dont't have a complete CMSG header. */
+ return (found);
+ }
+ if ((cmh.cmsg_len > INT_MAX) || ((int)cmh.cmsg_len > rem_len)) {
+ /* We don't have the complete CMSG. */
+ return (found);
+ }
+ cmsg_data_len = (int)cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh));
+ cmsg_data_off = off + CMSG_ALIGN(sizeof(cmh));
+ if ((cmh.cmsg_level == IPPROTO_SCTP) &&
+ ((c_type == cmh.cmsg_type) ||
+ ((c_type == SCTP_SNDRCV) &&
+ ((cmh.cmsg_type == SCTP_SNDINFO) ||
+ (cmh.cmsg_type == SCTP_PRINFO) ||
+ (cmh.cmsg_type == SCTP_AUTHINFO))))) {
+ if (c_type == cmh.cmsg_type) {
+ if (cpsize > INT_MAX) {
+ return (found);
+ }
+ if (cmsg_data_len < (int)cpsize) {
+ return (found);
+ }
+ /* It is exactly what we want. Copy it out. */
+ m_copydata(control, cmsg_data_off, (int)cpsize, (caddr_t)data);
+ return (1);
+ } else {
+ struct sctp_sndrcvinfo *sndrcvinfo;
+
+ sndrcvinfo = (struct sctp_sndrcvinfo *)data;
+ if (!found) {
+ if (cpsize < sizeof(struct sctp_sndrcvinfo)) {
+ return (found);
+ }
+ memset(sndrcvinfo, 0, sizeof(struct sctp_sndrcvinfo));
+ }
+ switch (cmh.cmsg_type) {
+ case SCTP_SNDINFO:
+ if (cmsg_data_len < (int)sizeof(struct sctp_sndinfo)) {
+ return (found);
+ }
+ m_copydata(control, cmsg_data_off, sizeof(struct sctp_sndinfo), (caddr_t)&sndinfo);
+ sndrcvinfo->sinfo_stream = sndinfo.snd_sid;
+ sndrcvinfo->sinfo_flags = sndinfo.snd_flags;
+ sndrcvinfo->sinfo_ppid = sndinfo.snd_ppid;
+ sndrcvinfo->sinfo_context = sndinfo.snd_context;
+ sndrcvinfo->sinfo_assoc_id = sndinfo.snd_assoc_id;
+ break;
+ case SCTP_PRINFO:
+ if (cmsg_data_len < (int)sizeof(struct sctp_prinfo)) {
+ return (found);
+ }
+ m_copydata(control, cmsg_data_off, sizeof(struct sctp_prinfo), (caddr_t)&prinfo);
+ if (prinfo.pr_policy != SCTP_PR_SCTP_NONE) {
+ sndrcvinfo->sinfo_timetolive = prinfo.pr_value;
+ } else {
+ sndrcvinfo->sinfo_timetolive = 0;
+ }
+ sndrcvinfo->sinfo_flags |= prinfo.pr_policy;
+ break;
+ case SCTP_AUTHINFO:
+ if (cmsg_data_len < (int)sizeof(struct sctp_authinfo)) {
+ return (found);
+ }
+ m_copydata(control, cmsg_data_off, sizeof(struct sctp_authinfo), (caddr_t)&authinfo);
+ sndrcvinfo->sinfo_keynumber_valid = 1;
+ sndrcvinfo->sinfo_keynumber = authinfo.auth_keynumber;
+ break;
+ default:
+ return (found);
+ }
+ found = true;
+ }
+ }
+ }
+ return (found);
+}
+
+static int
+sctp_process_cmsgs_for_init(struct sctp_tcb *stcb, struct mbuf *control, int *error)
+{
+#if defined(_WIN32)
+ WSACMSGHDR cmh;
+#else
+ struct cmsghdr cmh;
+#endif
+ struct sctp_initmsg initmsg;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+ int tot_len, rem_len, cmsg_data_len, cmsg_data_off, off;
+
+ tot_len = SCTP_BUF_LEN(control);
+ for (off = 0; off < tot_len; off += CMSG_ALIGN(cmh.cmsg_len)) {
+ rem_len = tot_len - off;
+ if (rem_len < (int)CMSG_ALIGN(sizeof(cmh))) {
+ /* There is not enough room for one more. */
+ *error = EINVAL;
+ return (1);
+ }
+ m_copydata(control, off, sizeof(cmh), (caddr_t)&cmh);
+ if (cmh.cmsg_len < CMSG_ALIGN(sizeof(cmh))) {
+ /* We dont't have a complete CMSG header. */
+ *error = EINVAL;
+ return (1);
+ }
+ if ((cmh.cmsg_len > INT_MAX) || ((int)cmh.cmsg_len > rem_len)) {
+ /* We don't have the complete CMSG. */
+ *error = EINVAL;
+ return (1);
+ }
+ cmsg_data_len = (int)cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh));
+ cmsg_data_off = off + CMSG_ALIGN(sizeof(cmh));
+ if (cmh.cmsg_level == IPPROTO_SCTP) {
+ switch (cmh.cmsg_type) {
+ case SCTP_INIT:
+ if (cmsg_data_len < (int)sizeof(struct sctp_initmsg)) {
+ *error = EINVAL;
+ return (1);
+ }
+ m_copydata(control, cmsg_data_off, sizeof(struct sctp_initmsg), (caddr_t)&initmsg);
+ if (initmsg.sinit_max_attempts)
+ stcb->asoc.max_init_times = initmsg.sinit_max_attempts;
+ if (initmsg.sinit_num_ostreams)
+ stcb->asoc.pre_open_streams = initmsg.sinit_num_ostreams;
+ if (initmsg.sinit_max_instreams)
+ stcb->asoc.max_inbound_streams = initmsg.sinit_max_instreams;
+ if (initmsg.sinit_max_init_timeo)
+ stcb->asoc.initial_init_rto_max = initmsg.sinit_max_init_timeo;
+ if (stcb->asoc.streamoutcnt < stcb->asoc.pre_open_streams) {
+ struct sctp_stream_out *tmp_str;
+ unsigned int i;
+#if defined(SCTP_DETAILED_STR_STATS)
+ int j;
+#endif
+
+ /* Default is NOT correct */
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Ok, default:%d pre_open:%d\n",
+ stcb->asoc.streamoutcnt, stcb->asoc.pre_open_streams);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_MALLOC(tmp_str,
+ struct sctp_stream_out *,
+ (stcb->asoc.pre_open_streams * sizeof(struct sctp_stream_out)),
+ SCTP_M_STRMO);
+ SCTP_TCB_LOCK(stcb);
+ if (tmp_str != NULL) {
+ SCTP_FREE(stcb->asoc.strmout, SCTP_M_STRMO);
+ stcb->asoc.strmout = tmp_str;
+ stcb->asoc.strm_realoutsize = stcb->asoc.streamoutcnt = stcb->asoc.pre_open_streams;
+ } else {
+ stcb->asoc.pre_open_streams = stcb->asoc.streamoutcnt;
+ }
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ TAILQ_INIT(&stcb->asoc.strmout[i].outqueue);
+ stcb->asoc.ss_functions.sctp_ss_init_stream(stcb, &stcb->asoc.strmout[i], NULL);
+ stcb->asoc.strmout[i].chunks_on_queues = 0;
+#if defined(SCTP_DETAILED_STR_STATS)
+ for (j = 0; j < SCTP_PR_SCTP_MAX + 1; j++) {
+ stcb->asoc.strmout[i].abandoned_sent[j] = 0;
+ stcb->asoc.strmout[i].abandoned_unsent[j] = 0;
+ }
+#else
+ stcb->asoc.strmout[i].abandoned_sent[0] = 0;
+ stcb->asoc.strmout[i].abandoned_unsent[0] = 0;
+#endif
+ stcb->asoc.strmout[i].next_mid_ordered = 0;
+ stcb->asoc.strmout[i].next_mid_unordered = 0;
+ stcb->asoc.strmout[i].sid = i;
+ stcb->asoc.strmout[i].last_msg_incomplete = 0;
+ stcb->asoc.strmout[i].state = SCTP_STREAM_OPENING;
+ }
+ }
+ break;
+#ifdef INET
+ case SCTP_DSTADDRV4:
+ if (cmsg_data_len < (int)sizeof(struct in_addr)) {
+ *error = EINVAL;
+ return (1);
+ }
+ memset(&sin, 0, sizeof(struct sockaddr_in));
+ sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin.sin_port = stcb->rport;
+ m_copydata(control, cmsg_data_off, sizeof(struct in_addr), (caddr_t)&sin.sin_addr);
+ if ((sin.sin_addr.s_addr == INADDR_ANY) ||
+ (sin.sin_addr.s_addr == INADDR_BROADCAST) ||
+ IN_MULTICAST(ntohl(sin.sin_addr.s_addr))) {
+ *error = EINVAL;
+ return (1);
+ }
+ if (sctp_add_remote_addr(stcb, (struct sockaddr *)&sin, NULL, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) {
+ *error = ENOBUFS;
+ return (1);
+ }
+ break;
+#endif
+#ifdef INET6
+ case SCTP_DSTADDRV6:
+ if (cmsg_data_len < (int)sizeof(struct in6_addr)) {
+ *error = EINVAL;
+ return (1);
+ }
+ memset(&sin6, 0, sizeof(struct sockaddr_in6));
+ sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ sin6.sin6_port = stcb->rport;
+ m_copydata(control, cmsg_data_off, sizeof(struct in6_addr), (caddr_t)&sin6.sin6_addr);
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6.sin6_addr) ||
+ IN6_IS_ADDR_MULTICAST(&sin6.sin6_addr)) {
+ *error = EINVAL;
+ return (1);
+ }
+#ifdef INET
+ if (IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) {
+ in6_sin6_2_sin(&sin, &sin6);
+ if ((sin.sin_addr.s_addr == INADDR_ANY) ||
+ (sin.sin_addr.s_addr == INADDR_BROADCAST) ||
+ IN_MULTICAST(ntohl(sin.sin_addr.s_addr))) {
+ *error = EINVAL;
+ return (1);
+ }
+ if (sctp_add_remote_addr(stcb, (struct sockaddr *)&sin, NULL, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) {
+ *error = ENOBUFS;
+ return (1);
+ }
+ } else
+#endif
+ if (sctp_add_remote_addr(stcb, (struct sockaddr *)&sin6, NULL, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) {
+ *error = ENOBUFS;
+ return (1);
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+ return (0);
+}
+
+#if defined(INET) || defined(INET6)
+static struct sctp_tcb *
+sctp_findassociation_cmsgs(struct sctp_inpcb **inp_p,
+ uint16_t port,
+ struct mbuf *control,
+ struct sctp_nets **net_p,
+ int *error)
+{
+#if defined(_WIN32)
+ WSACMSGHDR cmh;
+#else
+ struct cmsghdr cmh;
+#endif
+ struct sctp_tcb *stcb;
+ struct sockaddr *addr;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+ int tot_len, rem_len, cmsg_data_len, cmsg_data_off, off;
+
+ tot_len = SCTP_BUF_LEN(control);
+ for (off = 0; off < tot_len; off += CMSG_ALIGN(cmh.cmsg_len)) {
+ rem_len = tot_len - off;
+ if (rem_len < (int)CMSG_ALIGN(sizeof(cmh))) {
+ /* There is not enough room for one more. */
+ *error = EINVAL;
+ return (NULL);
+ }
+ m_copydata(control, off, sizeof(cmh), (caddr_t)&cmh);
+ if (cmh.cmsg_len < CMSG_ALIGN(sizeof(cmh))) {
+ /* We dont't have a complete CMSG header. */
+ *error = EINVAL;
+ return (NULL);
+ }
+ if ((cmh.cmsg_len > INT_MAX) || ((int)cmh.cmsg_len > rem_len)) {
+ /* We don't have the complete CMSG. */
+ *error = EINVAL;
+ return (NULL);
+ }
+ cmsg_data_len = (int)cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh));
+ cmsg_data_off = off + CMSG_ALIGN(sizeof(cmh));
+ if (cmh.cmsg_level == IPPROTO_SCTP) {
+ switch (cmh.cmsg_type) {
+#ifdef INET
+ case SCTP_DSTADDRV4:
+ if (cmsg_data_len < (int)sizeof(struct in_addr)) {
+ *error = EINVAL;
+ return (NULL);
+ }
+ memset(&sin, 0, sizeof(struct sockaddr_in));
+ sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin.sin_port = port;
+ m_copydata(control, cmsg_data_off, sizeof(struct in_addr), (caddr_t)&sin.sin_addr);
+ addr = (struct sockaddr *)&sin;
+ break;
+#endif
+#ifdef INET6
+ case SCTP_DSTADDRV6:
+ if (cmsg_data_len < (int)sizeof(struct in6_addr)) {
+ *error = EINVAL;
+ return (NULL);
+ }
+ memset(&sin6, 0, sizeof(struct sockaddr_in6));
+ sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ sin6.sin6_port = port;
+ m_copydata(control, cmsg_data_off, sizeof(struct in6_addr), (caddr_t)&sin6.sin6_addr);
+#ifdef INET
+ if (IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) {
+ in6_sin6_2_sin(&sin, &sin6);
+ addr = (struct sockaddr *)&sin;
+ } else
+#endif
+ addr = (struct sockaddr *)&sin6;
+ break;
+#endif
+ default:
+ addr = NULL;
+ break;
+ }
+ if (addr) {
+ stcb = sctp_findassociation_ep_addr(inp_p, addr, net_p, NULL, NULL);
+ if (stcb != NULL) {
+ return (stcb);
+ }
+ }
+ }
+ }
+ return (NULL);
+}
+#endif
+
+static struct mbuf *
+sctp_add_cookie(struct mbuf *init, int init_offset,
+ struct mbuf *initack, int initack_offset, struct sctp_state_cookie *stc_in, uint8_t **signature)
+{
+ struct mbuf *copy_init, *copy_initack, *m_at, *sig, *mret;
+ struct sctp_state_cookie *stc;
+ struct sctp_paramhdr *ph;
+ uint16_t cookie_sz;
+
+ mret = sctp_get_mbuf_for_msg((sizeof(struct sctp_state_cookie) +
+ sizeof(struct sctp_paramhdr)), 0,
+ M_NOWAIT, 1, MT_DATA);
+ if (mret == NULL) {
+ return (NULL);
+ }
+ copy_init = SCTP_M_COPYM(init, init_offset, M_COPYALL, M_NOWAIT);
+ if (copy_init == NULL) {
+ sctp_m_freem(mret);
+ return (NULL);
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(copy_init, SCTP_MBUF_ICOPY);
+ }
+#endif
+ copy_initack = SCTP_M_COPYM(initack, initack_offset, M_COPYALL,
+ M_NOWAIT);
+ if (copy_initack == NULL) {
+ sctp_m_freem(mret);
+ sctp_m_freem(copy_init);
+ return (NULL);
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(copy_initack, SCTP_MBUF_ICOPY);
+ }
+#endif
+ /* easy side we just drop it on the end */
+ ph = mtod(mret, struct sctp_paramhdr *);
+ SCTP_BUF_LEN(mret) = sizeof(struct sctp_state_cookie) +
+ sizeof(struct sctp_paramhdr);
+ stc = (struct sctp_state_cookie *)((caddr_t)ph +
+ sizeof(struct sctp_paramhdr));
+ ph->param_type = htons(SCTP_STATE_COOKIE);
+ ph->param_length = 0; /* fill in at the end */
+ /* Fill in the stc cookie data */
+ memcpy(stc, stc_in, sizeof(struct sctp_state_cookie));
+
+ /* tack the INIT and then the INIT-ACK onto the chain */
+ cookie_sz = 0;
+ for (m_at = mret; m_at; m_at = SCTP_BUF_NEXT(m_at)) {
+ cookie_sz += SCTP_BUF_LEN(m_at);
+ if (SCTP_BUF_NEXT(m_at) == NULL) {
+ SCTP_BUF_NEXT(m_at) = copy_init;
+ break;
+ }
+ }
+ for (m_at = copy_init; m_at; m_at = SCTP_BUF_NEXT(m_at)) {
+ cookie_sz += SCTP_BUF_LEN(m_at);
+ if (SCTP_BUF_NEXT(m_at) == NULL) {
+ SCTP_BUF_NEXT(m_at) = copy_initack;
+ break;
+ }
+ }
+ for (m_at = copy_initack; m_at; m_at = SCTP_BUF_NEXT(m_at)) {
+ cookie_sz += SCTP_BUF_LEN(m_at);
+ if (SCTP_BUF_NEXT(m_at) == NULL) {
+ break;
+ }
+ }
+ sig = sctp_get_mbuf_for_msg(SCTP_SIGNATURE_SIZE, 0, M_NOWAIT, 1, MT_DATA);
+ if (sig == NULL) {
+ /* no space, so free the entire chain */
+ sctp_m_freem(mret);
+ return (NULL);
+ }
+ SCTP_BUF_NEXT(m_at) = sig;
+ SCTP_BUF_LEN(sig) = SCTP_SIGNATURE_SIZE;
+ cookie_sz += SCTP_SIGNATURE_SIZE;
+ ph->param_length = htons(cookie_sz);
+ *signature = (uint8_t *)mtod(sig, caddr_t);
+ memset(*signature, 0, SCTP_SIGNATURE_SIZE);
+ return (mret);
+}
+
+static uint8_t
+sctp_get_ect(struct sctp_tcb *stcb)
+{
+ if ((stcb != NULL) && (stcb->asoc.ecn_supported == 1)) {
+ return (SCTP_ECT0_BIT);
+ } else {
+ return (0);
+ }
+}
+
+#if defined(INET) || defined(INET6)
+static void
+sctp_handle_no_route(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ int so_locked)
+{
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "dropped packet - no valid source addr\n");
+
+ if (net) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Destination was ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT1, &net->ro._l_addr.sa);
+ if (net->dest_state & SCTP_ADDR_CONFIRMED) {
+ if ((net->dest_state & SCTP_ADDR_REACHABLE) && stcb) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "no route takes interface %p down\n", (void *)net);
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN,
+ stcb, 0,
+ (void *)net,
+ so_locked);
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ net->dest_state &= ~SCTP_ADDR_PF;
+ }
+ }
+ if (stcb) {
+ if (net == stcb->asoc.primary_destination) {
+ /* need a new primary */
+ struct sctp_nets *alt;
+
+ alt = sctp_find_alternate_net(stcb, net, 0);
+ if (alt != net) {
+ if (stcb->asoc.alternate) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ }
+ stcb->asoc.alternate = alt;
+ atomic_add_int(&stcb->asoc.alternate->ref_count, 1);
+ if (net->ro._s_addr) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ }
+ net->src_addr_selected = 0;
+ }
+ }
+ }
+ }
+}
+#endif
+
+static int
+sctp_lowlevel_chunk_output(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb, /* may be NULL */
+ struct sctp_nets *net,
+ struct sockaddr *to,
+ struct mbuf *m,
+ uint32_t auth_offset,
+ struct sctp_auth_chunk *auth,
+ uint16_t auth_keyid,
+ int nofragment_flag,
+ int ecn_ok,
+ int out_of_asoc_ok,
+ uint16_t src_port,
+ uint16_t dest_port,
+ uint32_t v_tag,
+ uint16_t port,
+ union sctp_sockstore *over_addr,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ bool use_zero_crc,
+ int so_locked)
+/* nofragment_flag to tell if IP_DF should be set (IPv4 only) */
+{
+ /**
+ * Given a mbuf chain (via SCTP_BUF_NEXT()) that holds a packet header
+ * WITH an SCTPHDR but no IP header, endpoint inp and sa structure:
+ * - fill in the HMAC digest of any AUTH chunk in the packet.
+ * - calculate and fill in the SCTP checksum.
+ * - prepend an IP address header.
+ * - if boundall use INADDR_ANY.
+ * - if boundspecific do source address selection.
+ * - set fragmentation option for ipV4.
+ * - On return from IP output, check/adjust mtu size of output
+ * interface and smallest_mtu size as well.
+ */
+ /* Will need ifdefs around this */
+ struct mbuf *newm;
+ struct sctphdr *sctphdr;
+ int packet_length;
+ int ret;
+#if defined(INET) || defined(INET6)
+ uint32_t vrf_id;
+#endif
+#if defined(INET) || defined(INET6)
+ struct mbuf *o_pak;
+ sctp_route_t *ro = NULL;
+ struct udphdr *udp = NULL;
+#endif
+ uint8_t tos_value;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so = NULL;
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ if ((net) && (net->dest_state & SCTP_ADDR_OUT_OF_SCOPE)) {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ sctp_m_freem(m);
+ return (EFAULT);
+ }
+#if defined(INET) || defined(INET6)
+ if (stcb) {
+ vrf_id = stcb->asoc.vrf_id;
+ } else {
+ vrf_id = inp->def_vrf_id;
+ }
+#endif
+ /* fill in the HMAC digest for any AUTH chunk in the packet */
+ if ((auth != NULL) && (stcb != NULL)) {
+ sctp_fill_hmac_digest_m(m, auth_offset, auth, stcb, auth_keyid);
+ }
+
+ if (net) {
+ tos_value = net->dscp;
+ } else if (stcb) {
+ tos_value = stcb->asoc.default_dscp;
+ } else {
+ tos_value = inp->sctp_ep.default_dscp;
+ }
+
+ switch (to->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct ip *ip = NULL;
+ sctp_route_t iproute;
+ int len;
+
+ len = SCTP_MIN_V4_OVERHEAD;
+ if (port) {
+ len += sizeof(struct udphdr);
+ }
+ newm = sctp_get_mbuf_for_msg(len, 1, M_NOWAIT, 1, MT_DATA);
+ if (newm == NULL) {
+ sctp_m_freem(m);
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_ALIGN_TO_END(newm, len);
+ SCTP_BUF_LEN(newm) = len;
+ SCTP_BUF_NEXT(newm) = m;
+ m = newm;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net != NULL) {
+ m->m_pkthdr.flowid = net->flowid;
+ M_HASHTYPE_SET(m, net->flowtype);
+ } else {
+ m->m_pkthdr.flowid = mflowid;
+ M_HASHTYPE_SET(m, mflowtype);
+ }
+#endif
+ packet_length = sctp_calculate_len(m);
+ ip = mtod(m, struct ip *);
+ ip->ip_v = IPVERSION;
+ ip->ip_hl = (sizeof(struct ip) >> 2);
+ if (tos_value == 0) {
+ /*
+ * This means especially, that it is not set at the
+ * SCTP layer. So use the value from the IP layer.
+ */
+ tos_value = inp->ip_inp.inp.inp_ip_tos;
+ }
+ tos_value &= 0xfc;
+ if (ecn_ok) {
+ tos_value |= sctp_get_ect(stcb);
+ }
+ if ((nofragment_flag) && (port == 0)) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ip->ip_off = htons(IP_DF);
+#elif defined(WITH_CONVERT_IP_OFF) || defined(__APPLE__)
+ ip->ip_off = IP_DF;
+#else
+ ip->ip_off = htons(IP_DF);
+#endif
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ip->ip_off = htons(0);
+#else
+ ip->ip_off = 0;
+#endif
+ }
+#if defined(__Userspace__)
+ ip->ip_id = htons(SCTP_IP_ID(inp)++);
+#elif defined(__FreeBSD__)
+ /* FreeBSD has a function for ip_id's */
+ ip_fillid(ip);
+#elif defined(__APPLE__)
+#if RANDOM_IP_ID
+ ip->ip_id = ip_randomid();
+#else
+ ip->ip_id = htons(ip_id++);
+#endif
+#else
+ ip->ip_id = SCTP_IP_ID(inp)++;
+#endif
+
+ ip->ip_ttl = inp->ip_inp.inp.inp_ip_ttl;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ip->ip_len = htons(packet_length);
+#else
+ ip->ip_len = packet_length;
+#endif
+ ip->ip_tos = tos_value;
+ if (port) {
+ ip->ip_p = IPPROTO_UDP;
+ } else {
+ ip->ip_p = IPPROTO_SCTP;
+ }
+ ip->ip_sum = 0;
+ if (net == NULL) {
+ ro = &iproute;
+ memset(&iproute, 0, sizeof(iproute));
+#ifdef HAVE_SA_LEN
+ memcpy(&ro->ro_dst, to, to->sa_len);
+#else
+ memcpy(&ro->ro_dst, to, sizeof(struct sockaddr_in));
+#endif
+ } else {
+ ro = (sctp_route_t *)&net->ro;
+ }
+ /* Now the address selection part */
+ ip->ip_dst.s_addr = ((struct sockaddr_in *)to)->sin_addr.s_addr;
+
+ /* call the routine to select the src address */
+ if (net && out_of_asoc_ok == 0) {
+ if (net->ro._s_addr && (net->ro._s_addr->localifa_flags & (SCTP_BEING_DELETED|SCTP_ADDR_IFA_UNUSEABLE))) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(ro);
+#else
+ if (ro->ro_rt) {
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ }
+#endif
+ }
+ if (net->src_addr_selected == 0) {
+ /* Cache the source address */
+ net->ro._s_addr = sctp_source_address_selection(inp,stcb,
+ ro, net, 0,
+ vrf_id);
+ net->src_addr_selected = 1;
+ }
+ if (net->ro._s_addr == NULL) {
+ /* No route to host */
+ net->src_addr_selected = 0;
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+ ip->ip_src = net->ro._s_addr->address.sin.sin_addr;
+ } else {
+ if (over_addr == NULL) {
+ struct sctp_ifa *_lsrc;
+
+ _lsrc = sctp_source_address_selection(inp, stcb, ro,
+ net,
+ out_of_asoc_ok,
+ vrf_id);
+ if (_lsrc == NULL) {
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+ ip->ip_src = _lsrc->address.sin.sin_addr;
+ sctp_free_ifa(_lsrc);
+ } else {
+ ip->ip_src = over_addr->sin.sin_addr;
+ SCTP_RTALLOC(ro, vrf_id, inp->fibnum);
+ }
+ }
+ if (port) {
+ if (htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port)) == 0) {
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+ udp = (struct udphdr *)((caddr_t)ip + sizeof(struct ip));
+ udp->uh_sport = htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port));
+ udp->uh_dport = port;
+ udp->uh_ulen = htons((uint16_t)(packet_length - sizeof(struct ip)));
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+ if (V_udp_cksum) {
+ udp->uh_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, udp->uh_ulen + htons(IPPROTO_UDP));
+ } else {
+ udp->uh_sum = 0;
+ }
+#else
+ udp->uh_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, udp->uh_ulen + htons(IPPROTO_UDP));
+#endif
+#else
+ udp->uh_sum = 0;
+#endif
+ sctphdr = (struct sctphdr *)((caddr_t)udp + sizeof(struct udphdr));
+ } else {
+ sctphdr = (struct sctphdr *)((caddr_t)ip + sizeof(struct ip));
+ }
+
+ sctphdr->src_port = src_port;
+ sctphdr->dest_port = dest_port;
+ sctphdr->v_tag = v_tag;
+ sctphdr->checksum = 0;
+
+ /*
+ * If source address selection fails and we find no route
+ * then the ip_output should fail as well with a
+ * NO_ROUTE_TO_HOST type error. We probably should catch
+ * that somewhere and abort the association right away
+ * (assuming this is an INIT being sent).
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (ro->ro_nh == NULL) {
+#else
+ if (ro->ro_rt == NULL) {
+#endif
+ /*
+ * src addr selection failed to find a route (or
+ * valid source addr), so we can't get there from
+ * here (yet)!
+ */
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+ if (ro != &iproute) {
+ memcpy(&iproute, ro, sizeof(*ro));
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Calling ipv4 output routine from low level src addr:%x\n",
+ (uint32_t) (ntohl(ip->ip_src.s_addr)));
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Destination is %x\n",
+ (uint32_t)(ntohl(ip->ip_dst.s_addr)));
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "RTP route is %p through\n",
+ (void *)ro->ro_nh);
+#else
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "RTP route is %p through\n",
+ (void *)ro->ro_rt);
+#endif
+
+ if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) {
+ /* failed to prepend data, give up */
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ sctp_m_freem(m);
+ return (ENOMEM);
+ }
+ SCTP_ATTACH_CHAIN(o_pak, m, packet_length);
+ if (port) {
+ if (use_zero_crc) {
+ SCTP_STAT_INCR(sctps_sendzerocrc);
+ } else {
+ sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+ }
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+ if (V_udp_cksum) {
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+ }
+#else
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+#endif
+#endif
+ } else {
+ if (use_zero_crc) {
+ SCTP_STAT_INCR(sctps_sendzerocrc);
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ m->m_pkthdr.csum_flags = CSUM_SCTP;
+ m->m_pkthdr.csum_data = offsetof(struct sctphdr, checksum);
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+#else
+ if (!(SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ (stcb) && (stcb->asoc.scope.loopback_scope))) {
+ sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+ }
+#endif
+ }
+ }
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING)
+ sctp_packet_log(o_pak);
+#endif
+ /* send it out. table id is taken from stcb */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if ((SCTP_BASE_SYSCTL(sctp_output_unlocked)) && (so_locked)) {
+ so = SCTP_INP_SO(inp);
+ SCTP_SOCKET_UNLOCK(so, 0);
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(send, NULL, stcb, ip, stcb, sctphdr);
+#endif
+ SCTP_IP_OUTPUT(ret, o_pak, ro, inp, vrf_id);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if ((SCTP_BASE_SYSCTL(sctp_output_unlocked)) && (so_locked)) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 0);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (port) {
+ UDPSTAT_INC(udps_opackets);
+ }
+#endif
+ SCTP_STAT_INCR(sctps_sendpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_outpackets);
+ if (ret)
+ SCTP_STAT_INCR(sctps_senderrors);
+
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "IP output returns %d\n", ret);
+ if (net == NULL) {
+ /* free tempy routes */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(ro);
+#else
+ if (ro->ro_rt) {
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ }
+#endif
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if ((ro->ro_nh != NULL) && (net->ro._s_addr) &&
+#else
+ if ((ro->ro_rt != NULL) && (net->ro._s_addr) &&
+#endif
+ ((net->dest_state & SCTP_ADDR_NO_PMTUD) == 0)) {
+ uint32_t mtu;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_nh);
+#else
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_rt);
+#endif
+ if (mtu > 0) {
+ if (net->port) {
+ mtu -= sizeof(struct udphdr);
+ }
+ if (mtu < net->mtu) {
+ net->mtu = mtu;
+ if ((stcb != NULL) && (stcb->asoc.smallest_mtu > mtu)) {
+ sctp_pathmtu_adjustment(stcb, mtu, true);
+ }
+ }
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ } else if (ro->ro_nh == NULL) {
+#else
+ } else if (ro->ro_rt == NULL) {
+#endif
+ /* route was freed */
+ if (net->ro._s_addr &&
+ net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ }
+ net->src_addr_selected = 0;
+ }
+ }
+ return (ret);
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ uint32_t flowlabel, flowinfo;
+ struct ip6_hdr *ip6h;
+ struct route_in6 ip6route;
+#if !defined(__Userspace__)
+ struct ifnet *ifp;
+#endif
+ struct sockaddr_in6 *sin6, tmp, *lsa6, lsa6_tmp;
+ int prev_scope = 0;
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ struct sockaddr_in6 lsa6_storage;
+ int error;
+#endif
+ u_short prev_port = 0;
+ int len;
+
+ if (net) {
+ flowlabel = net->flowlabel;
+ } else if (stcb) {
+ flowlabel = stcb->asoc.default_flowlabel;
+ } else {
+ flowlabel = inp->sctp_ep.default_flowlabel;
+ }
+ if (flowlabel == 0) {
+ /*
+ * This means especially, that it is not set at the
+ * SCTP layer. So use the value from the IP layer.
+ */
+#if defined(__APPLE__) && !defined(__Userspace__) && (!defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION))
+ flowlabel = ntohl(inp->ip_inp.inp.inp_flow);
+#else
+ flowlabel = ntohl(((struct inpcb *)inp)->inp_flow);
+#endif
+ }
+ flowlabel &= 0x000fffff;
+ len = SCTP_MIN_OVERHEAD;
+ if (port) {
+ len += sizeof(struct udphdr);
+ }
+ newm = sctp_get_mbuf_for_msg(len, 1, M_NOWAIT, 1, MT_DATA);
+ if (newm == NULL) {
+ sctp_m_freem(m);
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_ALIGN_TO_END(newm, len);
+ SCTP_BUF_LEN(newm) = len;
+ SCTP_BUF_NEXT(newm) = m;
+ m = newm;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net != NULL) {
+ m->m_pkthdr.flowid = net->flowid;
+ M_HASHTYPE_SET(m, net->flowtype);
+ } else {
+ m->m_pkthdr.flowid = mflowid;
+ M_HASHTYPE_SET(m, mflowtype);
+ }
+#endif
+ packet_length = sctp_calculate_len(m);
+
+ ip6h = mtod(m, struct ip6_hdr *);
+ /* protect *sin6 from overwrite */
+ sin6 = (struct sockaddr_in6 *)to;
+ tmp = *sin6;
+ sin6 = &tmp;
+
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ /* KAME hack: embed scopeid */
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ if (in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL) != 0)
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL, NULL) != 0)
+#endif
+#elif defined(SCTP_KAME)
+ if (sa6_embedscope(sin6, MODULE_GLOBAL(ip6_use_defzone)) != 0)
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6) != 0)
+#endif
+ {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ sctp_m_freem(m);
+ return (EINVAL);
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ if (net == NULL) {
+ memset(&ip6route, 0, sizeof(ip6route));
+ ro = (sctp_route_t *)&ip6route;
+#ifdef HAVE_SIN6_LEN
+ memcpy(&ro->ro_dst, sin6, sin6->sin6_len);
+#else
+ memcpy(&ro->ro_dst, sin6, sizeof(struct sockaddr_in6));
+#endif
+ } else {
+ ro = (sctp_route_t *)&net->ro;
+ }
+ /*
+ * We assume here that inp_flow is in host byte order within
+ * the TCB!
+ */
+ if (tos_value == 0) {
+ /*
+ * This means especially, that it is not set at the
+ * SCTP layer. So use the value from the IP layer.
+ */
+#if defined(__APPLE__) && !defined(__Userspace__) && (!defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION))
+ tos_value = (ntohl(inp->ip_inp.inp.inp_flow) >> 20) & 0xff;
+#else
+ tos_value = (ntohl(((struct inpcb *)inp)->inp_flow) >> 20) & 0xff;
+#endif
+ }
+ tos_value &= 0xfc;
+ if (ecn_ok) {
+ tos_value |= sctp_get_ect(stcb);
+ }
+ flowinfo = 0x06;
+ flowinfo <<= 8;
+ flowinfo |= tos_value;
+ flowinfo <<= 20;
+ flowinfo |= flowlabel;
+ ip6h->ip6_flow = htonl(flowinfo);
+ if (port) {
+ ip6h->ip6_nxt = IPPROTO_UDP;
+ } else {
+ ip6h->ip6_nxt = IPPROTO_SCTP;
+ }
+ ip6h->ip6_plen = htons((uint16_t)(packet_length - sizeof(struct ip6_hdr)));
+ ip6h->ip6_dst = sin6->sin6_addr;
+
+ /*
+ * Add SRC address selection here: we can only reuse to a
+ * limited degree the kame src-addr-sel, since we can try
+ * their selection but it may not be bound.
+ */
+ memset(&lsa6_tmp, 0, sizeof(lsa6_tmp));
+ lsa6_tmp.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ lsa6_tmp.sin6_len = sizeof(lsa6_tmp);
+#endif
+ lsa6 = &lsa6_tmp;
+ if (net && out_of_asoc_ok == 0) {
+ if (net->ro._s_addr && (net->ro._s_addr->localifa_flags & (SCTP_BEING_DELETED|SCTP_ADDR_IFA_UNUSEABLE))) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(ro);
+#else
+ if (ro->ro_rt) {
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ }
+#endif
+ }
+ if (net->src_addr_selected == 0) {
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ /* KAME hack: embed scopeid */
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ if (in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL) != 0)
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL, NULL) != 0)
+#endif
+#elif defined(SCTP_KAME)
+ if (sa6_embedscope(sin6, MODULE_GLOBAL(ip6_use_defzone)) != 0)
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6) != 0)
+#endif
+ {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ sctp_m_freem(m);
+ return (EINVAL);
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ /* Cache the source address */
+ net->ro._s_addr = sctp_source_address_selection(inp,
+ stcb,
+ ro,
+ net,
+ 0,
+ vrf_id);
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+#ifdef SCTP_KAME
+ (void)sa6_recoverscope(sin6);
+#else
+ (void)in6_recoverscope(sin6, &sin6->sin6_addr, NULL);
+#endif /* SCTP_KAME */
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ net->src_addr_selected = 1;
+ }
+ if (net->ro._s_addr == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "V6:No route to host\n");
+ net->src_addr_selected = 0;
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+ lsa6->sin6_addr = net->ro._s_addr->address.sin6.sin6_addr;
+ } else {
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ sin6 = (struct sockaddr_in6 *)&ro->ro_dst;
+ /* KAME hack: embed scopeid */
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ if (in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL) != 0)
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL, NULL) != 0)
+#endif
+#elif defined(SCTP_KAME)
+ if (sa6_embedscope(sin6, MODULE_GLOBAL(ip6_use_defzone)) != 0)
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6) != 0)
+#endif
+ {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ sctp_m_freem(m);
+ return (EINVAL);
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ if (over_addr == NULL) {
+ struct sctp_ifa *_lsrc;
+
+ _lsrc = sctp_source_address_selection(inp, stcb, ro,
+ net,
+ out_of_asoc_ok,
+ vrf_id);
+ if (_lsrc == NULL) {
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+ lsa6->sin6_addr = _lsrc->address.sin6.sin6_addr;
+ sctp_free_ifa(_lsrc);
+ } else {
+ lsa6->sin6_addr = over_addr->sin6.sin6_addr;
+ SCTP_RTALLOC(ro, vrf_id, inp->fibnum);
+ }
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+#ifdef SCTP_KAME
+ (void)sa6_recoverscope(sin6);
+#else
+ (void)in6_recoverscope(sin6, &sin6->sin6_addr, NULL);
+#endif /* SCTP_KAME */
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ }
+ lsa6->sin6_port = inp->sctp_lport;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (ro->ro_nh == NULL) {
+#else
+ if (ro->ro_rt == NULL) {
+#endif
+ /*
+ * src addr selection failed to find a route (or
+ * valid source addr), so we can't get there from
+ * here!
+ */
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+#ifndef SCOPEDROUTING
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ /*
+ * XXX: sa6 may not have a valid sin6_scope_id in the
+ * non-SCOPEDROUTING case.
+ */
+ memset(&lsa6_storage, 0, sizeof(lsa6_storage));
+ lsa6_storage.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ lsa6_storage.sin6_len = sizeof(lsa6_storage);
+#endif
+#ifdef SCTP_KAME
+ lsa6_storage.sin6_addr = lsa6->sin6_addr;
+ if ((error = sa6_recoverscope(&lsa6_storage)) != 0) {
+#else
+ if ((error = in6_recoverscope(&lsa6_storage, &lsa6->sin6_addr,
+ NULL)) != 0) {
+#endif /* SCTP_KAME */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "recover scope fails error %d\n", error);
+ sctp_m_freem(m);
+ return (error);
+ }
+ /* XXX */
+ lsa6_storage.sin6_addr = lsa6->sin6_addr;
+ lsa6_storage.sin6_port = inp->sctp_lport;
+ lsa6 = &lsa6_storage;
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#endif /* SCOPEDROUTING */
+ ip6h->ip6_src = lsa6->sin6_addr;
+
+ if (port) {
+ if (htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port)) == 0) {
+ sctp_handle_no_route(stcb, net, so_locked);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EHOSTUNREACH);
+ sctp_m_freem(m);
+ return (EHOSTUNREACH);
+ }
+ udp = (struct udphdr *)((caddr_t)ip6h + sizeof(struct ip6_hdr));
+ udp->uh_sport = htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port));
+ udp->uh_dport = port;
+ udp->uh_ulen = htons((uint16_t)(packet_length - sizeof(struct ip6_hdr)));
+ udp->uh_sum = 0;
+ sctphdr = (struct sctphdr *)((caddr_t)udp + sizeof(struct udphdr));
+ } else {
+ sctphdr = (struct sctphdr *)((caddr_t)ip6h + sizeof(struct ip6_hdr));
+ }
+
+ sctphdr->src_port = src_port;
+ sctphdr->dest_port = dest_port;
+ sctphdr->v_tag = v_tag;
+ sctphdr->checksum = 0;
+
+ /*
+ * We set the hop limit now since there is a good chance
+ * that our ro pointer is now filled
+ */
+ ip6h->ip6_hlim = SCTP_GET_HLIM(inp, ro);
+#if !defined(__Userspace__)
+ ifp = SCTP_GET_IFN_VOID_FROM_ROUTE(ro);
+#endif
+
+#ifdef SCTP_DEBUG
+ /* Copy to be sure something bad is not happening */
+ sin6->sin6_addr = ip6h->ip6_dst;
+ lsa6->sin6_addr = ip6h->ip6_src;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Calling ipv6 output routine from low level\n");
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "src: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT3, (struct sockaddr *)lsa6);
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "dst: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT3, (struct sockaddr *)sin6);
+ if (net) {
+ sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ /* preserve the port and scope for link local send */
+ prev_scope = sin6->sin6_scope_id;
+ prev_port = sin6->sin6_port;
+ }
+
+ if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) {
+ /* failed to prepend data, give up */
+ sctp_m_freem(m);
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_ATTACH_CHAIN(o_pak, m, packet_length);
+ if (port) {
+ sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip6_hdr) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#if !defined(__Userspace__)
+#if defined(_WIN32)
+ udp->uh_sum = 0;
+#else
+ if ((udp->uh_sum = in6_cksum(o_pak, IPPROTO_UDP, sizeof(struct ip6_hdr), packet_length - sizeof(struct ip6_hdr))) == 0) {
+ udp->uh_sum = 0xffff;
+ }
+#endif
+#endif
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ m->m_pkthdr.csum_flags = CSUM_SCTP_IPV6;
+ m->m_pkthdr.csum_data = offsetof(struct sctphdr, checksum);
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+#else
+ if (!(SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ (stcb) && (stcb->asoc.scope.loopback_scope))) {
+ sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip6_hdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+ }
+#endif
+ }
+ /* send it out. table id is taken from stcb */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if ((SCTP_BASE_SYSCTL(sctp_output_unlocked)) && (so_locked)) {
+ so = SCTP_INP_SO(inp);
+ SCTP_SOCKET_UNLOCK(so, 0);
+ }
+#endif
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING)
+ sctp_packet_log(o_pak);
+#endif
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+ SCTP_PROBE5(send, NULL, stcb, ip6h, stcb, sctphdr);
+#endif
+ SCTP_IP6_OUTPUT(ret, o_pak, (struct route_in6 *)ro, &ifp, inp, vrf_id);
+#else
+ SCTP_IP6_OUTPUT(ret, o_pak, (struct route_in6 *)ro, NULL, inp, vrf_id);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if ((SCTP_BASE_SYSCTL(sctp_output_unlocked)) && (so_locked)) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 0);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+#endif
+ if (net) {
+ /* for link local this must be done */
+ sin6->sin6_scope_id = prev_scope;
+ sin6->sin6_port = prev_port;
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "return from send is %d\n", ret);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (port) {
+ UDPSTAT_INC(udps_opackets);
+ }
+#endif
+ SCTP_STAT_INCR(sctps_sendpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_outpackets);
+ if (ret) {
+ SCTP_STAT_INCR(sctps_senderrors);
+ }
+ if (net == NULL) {
+ /* Now if we had a temp route free it */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(ro);
+#else
+ if (ro->ro_rt) {
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ }
+#endif
+ } else {
+ /* PMTU check versus smallest asoc MTU goes here */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (ro->ro_nh == NULL) {
+#else
+ if (ro->ro_rt == NULL) {
+#endif
+ /* Route was freed */
+ if (net->ro._s_addr &&
+ net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ }
+ net->src_addr_selected = 0;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if ((ro->ro_nh != NULL) && (net->ro._s_addr) &&
+#else
+ if ((ro->ro_rt != NULL) && (net->ro._s_addr) &&
+#endif
+ ((net->dest_state & SCTP_ADDR_NO_PMTUD) == 0)) {
+ uint32_t mtu;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_nh);
+#else
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_rt);
+#endif
+ if (mtu > 0) {
+ if (net->port) {
+ mtu -= sizeof(struct udphdr);
+ }
+ if (mtu < net->mtu) {
+ net->mtu = mtu;
+ if ((stcb != NULL) && (stcb->asoc.smallest_mtu > mtu)) {
+ sctp_pathmtu_adjustment(stcb, mtu, false);
+ }
+ }
+ }
+ }
+#if !defined(__Userspace__)
+ else if (ifp != NULL) {
+#if defined(_WIN32)
+#define ND_IFINFO(ifp) (ifp)
+#define linkmtu if_mtu
+#endif
+ if ((ND_IFINFO(ifp)->linkmtu > 0) &&
+ (stcb->asoc.smallest_mtu > ND_IFINFO(ifp)->linkmtu)) {
+ sctp_pathmtu_adjustment(stcb, ND_IFINFO(ifp)->linkmtu, false);
+ }
+ }
+#endif
+ }
+ return (ret);
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ char *buffer;
+ struct sockaddr_conn *sconn;
+ int len;
+
+ sconn = (struct sockaddr_conn *)to;
+ len = sizeof(struct sctphdr);
+ newm = sctp_get_mbuf_for_msg(len, 1, M_NOWAIT, 1, MT_DATA);
+ if (newm == NULL) {
+ sctp_m_freem(m);
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_ALIGN_TO_END(newm, len);
+ SCTP_BUF_LEN(newm) = len;
+ SCTP_BUF_NEXT(newm) = m;
+ m = newm;
+ packet_length = sctp_calculate_len(m);
+ m->m_pkthdr.len = packet_length;
+ sctphdr = mtod(m, struct sctphdr *);
+ sctphdr->src_port = src_port;
+ sctphdr->dest_port = dest_port;
+ sctphdr->v_tag = v_tag;
+ sctphdr->checksum = 0;
+ if (SCTP_BASE_VAR(crc32c_offloaded) == 0) {
+ sctphdr->checksum = sctp_calculate_cksum(m, 0);
+ SCTP_STAT_INCR(sctps_sendswcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+ }
+ if (tos_value == 0) {
+ tos_value = inp->ip_inp.inp.inp_ip_tos;
+ }
+ tos_value &= 0xfc;
+ if (ecn_ok) {
+ tos_value |= sctp_get_ect(stcb);
+ }
+ /* Don't alloc/free for each packet */
+ if ((buffer = malloc(packet_length)) != NULL) {
+ m_copydata(m, 0, packet_length, buffer);
+ ret = SCTP_BASE_VAR(conn_output)(sconn->sconn_addr, buffer, packet_length, tos_value, nofragment_flag);
+ free(buffer);
+ } else {
+ ret = ENOMEM;
+ }
+ sctp_m_freem(m);
+ return (ret);
+ }
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Unknown protocol (TSNH) type %d\n",
+ ((struct sockaddr *)to)->sa_family);
+ sctp_m_freem(m);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ return (EFAULT);
+ }
+}
+
+void
+sctp_send_initiate(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int so_locked)
+{
+ struct mbuf *m, *m_last;
+ struct sctp_nets *net;
+ struct sctp_init_chunk *init;
+ struct sctp_supported_addr_param *sup_addr;
+ struct sctp_adaptation_layer_indication *ali;
+ struct sctp_zero_checksum_acceptable *zero_chksum;
+ struct sctp_supported_chunk_types_param *pr_supported;
+ struct sctp_paramhdr *ph;
+ int cnt_inits_to = 0;
+ int error;
+ uint16_t num_ext, chunk_len, padding_len, parameter_len;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ /* INIT's always go to the primary (and usually ONLY address) */
+ net = stcb->asoc.primary_destination;
+ if (net == NULL) {
+ net = TAILQ_FIRST(&stcb->asoc.nets);
+ if (net == NULL) {
+ /* TSNH */
+ return;
+ }
+ /* we confirm any address we send an INIT to */
+ net->dest_state &= ~SCTP_ADDR_UNCONFIRMED;
+ (void)sctp_set_primary_addr(stcb, NULL, net);
+ } else {
+ /* we confirm any address we send an INIT to */
+ net->dest_state &= ~SCTP_ADDR_UNCONFIRMED;
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "Sending INIT\n");
+#ifdef INET6
+ if (net->ro._l_addr.sa.sa_family == AF_INET6) {
+ /*
+ * special hook, if we are sending to link local it will not
+ * show up in our private address count.
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&net->ro._l_addr.sin6.sin6_addr))
+ cnt_inits_to = 1;
+ }
+#endif
+ if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ /* This case should not happen */
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "Sending INIT - failed timer?\n");
+ return;
+ }
+ /* start the INIT timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, net);
+
+ m = sctp_get_mbuf_for_msg(MCLBYTES, 1, M_NOWAIT, 1, MT_DATA);
+ if (m == NULL) {
+ /* No memory, INIT timer will re-attempt. */
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "Sending INIT - mbuf?\n");
+ return;
+ }
+ chunk_len = (uint16_t)sizeof(struct sctp_init_chunk);
+ padding_len = 0;
+ /* Now lets put the chunk header in place */
+ init = mtod(m, struct sctp_init_chunk *);
+ /* now the chunk header */
+ init->ch.chunk_type = SCTP_INITIATION;
+ init->ch.chunk_flags = 0;
+ /* fill in later from mbuf we build */
+ init->ch.chunk_length = 0;
+ /* place in my tag */
+ init->init.initiate_tag = htonl(stcb->asoc.my_vtag);
+ /* set up some of the credits. */
+ init->init.a_rwnd = htonl(max(inp->sctp_socket?SCTP_SB_LIMIT_RCV(inp->sctp_socket):0,
+ SCTP_MINIMAL_RWND));
+ init->init.num_outbound_streams = htons(stcb->asoc.pre_open_streams);
+ init->init.num_inbound_streams = htons(stcb->asoc.max_inbound_streams);
+ init->init.initial_tsn = htonl(stcb->asoc.init_seq_number);
+
+ /* Adaptation layer indication parameter */
+ if (inp->sctp_ep.adaptation_layer_indicator_provided) {
+ parameter_len = (uint16_t)sizeof(struct sctp_adaptation_layer_indication);
+ ali = (struct sctp_adaptation_layer_indication *)(mtod(m, caddr_t) + chunk_len);
+ ali->ph.param_type = htons(SCTP_ULP_ADAPTATION);
+ ali->ph.param_length = htons(parameter_len);
+ ali->indication = htonl(inp->sctp_ep.adaptation_layer_indicator);
+ chunk_len += parameter_len;
+ }
+
+ /* ECN parameter */
+ if (stcb->asoc.ecn_supported == 1) {
+ parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+ ph = (struct sctp_paramhdr *)(mtod(m, caddr_t) + chunk_len);
+ ph->param_type = htons(SCTP_ECN_CAPABLE);
+ ph->param_length = htons(parameter_len);
+ chunk_len += parameter_len;
+ }
+
+ /* PR-SCTP supported parameter */
+ if (stcb->asoc.prsctp_supported == 1) {
+ parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+ ph = (struct sctp_paramhdr *)(mtod(m, caddr_t) + chunk_len);
+ ph->param_type = htons(SCTP_PRSCTP_SUPPORTED);
+ ph->param_length = htons(parameter_len);
+ chunk_len += parameter_len;
+ }
+
+ /* Zero checksum acceptable parameter */
+ if (stcb->asoc.rcv_edmid != SCTP_EDMID_NONE) {
+ parameter_len = (uint16_t)sizeof(struct sctp_zero_checksum_acceptable);
+ zero_chksum = (struct sctp_zero_checksum_acceptable *)(mtod(m, caddr_t) + chunk_len);
+ zero_chksum->ph.param_type = htons(SCTP_ZERO_CHECKSUM_ACCEPTABLE);
+ zero_chksum->ph.param_length = htons(parameter_len);
+ zero_chksum->edmid = htonl(stcb->asoc.rcv_edmid);
+ chunk_len += parameter_len;
+ }
+
+ /* Add NAT friendly parameter. */
+ if (SCTP_BASE_SYSCTL(sctp_inits_include_nat_friendly)) {
+ parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+ ph = (struct sctp_paramhdr *)(mtod(m, caddr_t) + chunk_len);
+ ph->param_type = htons(SCTP_HAS_NAT_SUPPORT);
+ ph->param_length = htons(parameter_len);
+ chunk_len += parameter_len;
+ }
+
+ /* And now tell the peer which extensions we support */
+ num_ext = 0;
+ pr_supported = (struct sctp_supported_chunk_types_param *)(mtod(m, caddr_t) + chunk_len);
+ if (stcb->asoc.prsctp_supported == 1) {
+ pr_supported->chunk_types[num_ext++] = SCTP_FORWARD_CUM_TSN;
+ if (stcb->asoc.idata_supported) {
+ pr_supported->chunk_types[num_ext++] = SCTP_IFORWARD_CUM_TSN;
+ }
+ }
+ if (stcb->asoc.auth_supported == 1) {
+ pr_supported->chunk_types[num_ext++] = SCTP_AUTHENTICATION;
+ }
+ if (stcb->asoc.asconf_supported == 1) {
+ pr_supported->chunk_types[num_ext++] = SCTP_ASCONF;
+ pr_supported->chunk_types[num_ext++] = SCTP_ASCONF_ACK;
+ }
+ if (stcb->asoc.reconfig_supported == 1) {
+ pr_supported->chunk_types[num_ext++] = SCTP_STREAM_RESET;
+ }
+ if (stcb->asoc.idata_supported) {
+ pr_supported->chunk_types[num_ext++] = SCTP_IDATA;
+ }
+ if (stcb->asoc.nrsack_supported == 1) {
+ pr_supported->chunk_types[num_ext++] = SCTP_NR_SELECTIVE_ACK;
+ }
+ if (stcb->asoc.pktdrop_supported == 1) {
+ pr_supported->chunk_types[num_ext++] = SCTP_PACKET_DROPPED;
+ }
+ if (num_ext > 0) {
+ parameter_len = (uint16_t)sizeof(struct sctp_supported_chunk_types_param) + num_ext;
+ pr_supported->ph.param_type = htons(SCTP_SUPPORTED_CHUNK_EXT);
+ pr_supported->ph.param_length = htons(parameter_len);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+ }
+ /* add authentication parameters */
+ if (stcb->asoc.auth_supported) {
+ /* attach RANDOM parameter, if available */
+ if (stcb->asoc.authinfo.random != NULL) {
+ struct sctp_auth_random *randp;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ randp = (struct sctp_auth_random *)(mtod(m, caddr_t) + chunk_len);
+ parameter_len = (uint16_t)sizeof(struct sctp_auth_random) + stcb->asoc.authinfo.random_len;
+ /* random key already contains the header */
+ memcpy(randp, stcb->asoc.authinfo.random->key, parameter_len);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+ }
+ /* add HMAC_ALGO parameter */
+ if (stcb->asoc.local_hmacs != NULL) {
+ struct sctp_auth_hmac_algo *hmacs;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ hmacs = (struct sctp_auth_hmac_algo *)(mtod(m, caddr_t) + chunk_len);
+ parameter_len = (uint16_t)(sizeof(struct sctp_auth_hmac_algo) +
+ stcb->asoc.local_hmacs->num_algo * sizeof(uint16_t));
+ hmacs->ph.param_type = htons(SCTP_HMAC_LIST);
+ hmacs->ph.param_length = htons(parameter_len);
+ sctp_serialize_hmaclist(stcb->asoc.local_hmacs, (uint8_t *)hmacs->hmac_ids);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+ }
+ /* add CHUNKS parameter */
+ if (stcb->asoc.local_auth_chunks != NULL) {
+ struct sctp_auth_chunk_list *chunks;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ chunks = (struct sctp_auth_chunk_list *)(mtod(m, caddr_t) + chunk_len);
+ parameter_len = (uint16_t)(sizeof(struct sctp_auth_chunk_list) +
+ sctp_auth_get_chklist_size(stcb->asoc.local_auth_chunks));
+ chunks->ph.param_type = htons(SCTP_CHUNK_LIST);
+ chunks->ph.param_length = htons(parameter_len);
+ sctp_serialize_auth_chunks(stcb->asoc.local_auth_chunks, chunks->chunk_types);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+ }
+ }
+
+ /* now any cookie time extensions */
+ if (stcb->asoc.cookie_preserve_req > 0) {
+ struct sctp_cookie_perserve_param *cookie_preserve;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ parameter_len = (uint16_t)sizeof(struct sctp_cookie_perserve_param);
+ cookie_preserve = (struct sctp_cookie_perserve_param *)(mtod(m, caddr_t) + chunk_len);
+ cookie_preserve->ph.param_type = htons(SCTP_COOKIE_PRESERVE);
+ cookie_preserve->ph.param_length = htons(parameter_len);
+ cookie_preserve->time = htonl(stcb->asoc.cookie_preserve_req);
+ stcb->asoc.cookie_preserve_req = 0;
+ chunk_len += parameter_len;
+ }
+
+ if (stcb->asoc.scope.ipv4_addr_legal || stcb->asoc.scope.ipv6_addr_legal) {
+ uint8_t i;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+ if (stcb->asoc.scope.ipv4_addr_legal) {
+ parameter_len += (uint16_t)sizeof(uint16_t);
+ }
+ if (stcb->asoc.scope.ipv6_addr_legal) {
+ parameter_len += (uint16_t)sizeof(uint16_t);
+ }
+ sup_addr = (struct sctp_supported_addr_param *)(mtod(m, caddr_t) + chunk_len);
+ sup_addr->ph.param_type = htons(SCTP_SUPPORTED_ADDRTYPE);
+ sup_addr->ph.param_length = htons(parameter_len);
+ i = 0;
+ if (stcb->asoc.scope.ipv4_addr_legal) {
+ sup_addr->addr_type[i++] = htons(SCTP_IPV4_ADDRESS);
+ }
+ if (stcb->asoc.scope.ipv6_addr_legal) {
+ sup_addr->addr_type[i++] = htons(SCTP_IPV6_ADDRESS);
+ }
+ padding_len = 4 - 2 * i;
+ chunk_len += parameter_len;
+ }
+
+ SCTP_BUF_LEN(m) = chunk_len;
+ /* now the addresses */
+ /* To optimize this we could put the scoping stuff
+ * into a structure and remove the individual uint8's from
+ * the assoc structure. Then we could just sifa in the
+ * address within the stcb. But for now this is a quick
+ * hack to get the address stuff teased apart.
+ */
+ m_last = sctp_add_addresses_to_i_ia(inp, stcb, &stcb->asoc.scope,
+ m, cnt_inits_to,
+ &padding_len, &chunk_len);
+
+ init->ch.chunk_length = htons(chunk_len);
+ if (padding_len > 0) {
+ if (sctp_add_pad_tombuf(m_last, padding_len) == NULL) {
+ sctp_m_freem(m);
+ return;
+ }
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "Sending INIT - calls lowlevel_output\n");
+ if ((error = sctp_lowlevel_chunk_output(inp, stcb, net,
+ (struct sockaddr *)&net->ro._l_addr,
+ m, 0, NULL, 0, 0, 0, 0,
+ inp->sctp_lport, stcb->rport, htonl(0),
+ net->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ false, so_locked))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "Gak send error %d\n", error);
+ if (error == ENOBUFS) {
+ stcb->asoc.ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ } else {
+ stcb->asoc.ifp_had_enobuf = 0;
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time);
+}
+
+struct mbuf *
+sctp_arethere_unrecognized_parameters(struct mbuf *in_initpkt,
+ int param_offset, int *abort_processing,
+ struct sctp_chunkhdr *cp,
+ int *nat_friendly,
+ int *cookie_found)
+{
+ /*
+ * Given a mbuf containing an INIT or INIT-ACK with the param_offset
+ * being equal to the beginning of the params i.e. (iphlen +
+ * sizeof(struct sctp_init_msg) parse through the parameters to the
+ * end of the mbuf verifying that all parameters are known.
+ *
+ * For unknown parameters build and return a mbuf with
+ * UNRECOGNIZED_PARAMETER errors. If the flags indicate to stop
+ * processing this chunk stop, and set *abort_processing to 1.
+ *
+ * By having param_offset be pre-set to where parameters begin it is
+ * hoped that this routine may be reused in the future by new
+ * features.
+ */
+ struct sctp_paramhdr *phdr, params;
+
+ struct mbuf *mat, *m_tmp, *op_err, *op_err_last;
+ int at, limit, pad_needed;
+ uint16_t ptype, plen, padded_size;
+
+ *abort_processing = 0;
+ if (cookie_found != NULL) {
+ *cookie_found = 0;
+ }
+ mat = in_initpkt;
+ limit = ntohs(cp->chunk_length) - sizeof(struct sctp_init_chunk);
+ at = param_offset;
+ op_err = NULL;
+ op_err_last = NULL;
+ pad_needed = 0;
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Check for unrecognized param's\n");
+ phdr = sctp_get_next_param(mat, at, &params, sizeof(params));
+ while ((phdr != NULL) && ((size_t)limit >= sizeof(struct sctp_paramhdr))) {
+ ptype = ntohs(phdr->param_type);
+ plen = ntohs(phdr->param_length);
+ if ((plen > limit) || (plen < sizeof(struct sctp_paramhdr))) {
+ /* wacked parameter */
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error %d\n", plen);
+ goto invalid_size;
+ }
+ limit -= SCTP_SIZE32(plen);
+ /*-
+ * All parameters for all chunks that we know/understand are
+ * listed here. We process them other places and make
+ * appropriate stop actions per the upper bits. However this
+ * is the generic routine processor's can call to get back
+ * an operr.. to either incorporate (init-ack) or send.
+ */
+ padded_size = SCTP_SIZE32(plen);
+ switch (ptype) {
+ /* Param's with variable size */
+ case SCTP_HEARTBEAT_INFO:
+ case SCTP_UNRECOG_PARAM:
+ case SCTP_ERROR_CAUSE_IND:
+ /* ok skip fwd */
+ at += padded_size;
+ break;
+ case SCTP_STATE_COOKIE:
+ if (cookie_found != NULL) {
+ *cookie_found = 1;
+ }
+ at += padded_size;
+ break;
+ /* Param's with variable size within a range */
+ case SCTP_CHUNK_LIST:
+ case SCTP_SUPPORTED_CHUNK_EXT:
+ if (padded_size > (sizeof(struct sctp_supported_chunk_types_param) + (sizeof(uint8_t) * SCTP_MAX_SUPPORTED_EXT))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error chklist %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_SUPPORTED_ADDRTYPE:
+ if (padded_size > SCTP_MAX_ADDR_PARAMS_SIZE) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error supaddrtype %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_RANDOM:
+ if (padded_size > (sizeof(struct sctp_auth_random) + SCTP_RANDOM_MAX_SIZE)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error random %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_SET_PRIM_ADDR:
+ case SCTP_DEL_IP_ADDRESS:
+ case SCTP_ADD_IP_ADDRESS:
+ if ((padded_size != sizeof(struct sctp_asconf_addrv4_param)) &&
+ (padded_size != sizeof(struct sctp_asconf_addr_param))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error setprim %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ /* Param's with a fixed size */
+ case SCTP_IPV4_ADDRESS:
+ if (padded_size != sizeof(struct sctp_ipv4addr_param)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error ipv4 addr %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_IPV6_ADDRESS:
+ if (padded_size != sizeof(struct sctp_ipv6addr_param)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error ipv6 addr %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_COOKIE_PRESERVE:
+ if (padded_size != sizeof(struct sctp_cookie_perserve_param)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error cookie-preserve %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_HAS_NAT_SUPPORT:
+ *nat_friendly = 1;
+ /* FALLTHROUGH */
+ case SCTP_PRSCTP_SUPPORTED:
+ if (padded_size != sizeof(struct sctp_paramhdr)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error prsctp/nat support %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_ECN_CAPABLE:
+ if (padded_size != sizeof(struct sctp_paramhdr)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error ecn %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_ULP_ADAPTATION:
+ if (padded_size != sizeof(struct sctp_adaptation_layer_indication)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error adapatation %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_SUCCESS_REPORT:
+ if (padded_size != sizeof(struct sctp_asconf_paramhdr)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Invalid size - error success %d\n", plen);
+ goto invalid_size;
+ }
+ at += padded_size;
+ break;
+ case SCTP_HOSTNAME_ADDRESS:
+ {
+ /* Hostname parameters are deprecated. */
+ struct sctp_gen_error_cause *cause;
+ int l_len;
+
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Can't handle hostname addresses.. abort processing\n");
+ *abort_processing = 1;
+ sctp_m_freem(op_err);
+ op_err = NULL;
+ op_err_last = NULL;
+#ifdef INET6
+ l_len = SCTP_MIN_OVERHEAD;
+#else
+ l_len = SCTP_MIN_V4_OVERHEAD;
+#endif
+ l_len += sizeof(struct sctp_chunkhdr);
+ l_len += sizeof(struct sctp_gen_error_cause);
+ op_err = sctp_get_mbuf_for_msg(l_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err != NULL) {
+ /*
+ * Pre-reserve space for IP, SCTP, and
+ * chunk header.
+ */
+#ifdef INET6
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr));
+#else
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip));
+#endif
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr));
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr));
+ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_gen_error_cause);
+ cause = mtod(op_err, struct sctp_gen_error_cause *);
+ cause->code = htons(SCTP_CAUSE_UNRESOLVABLE_ADDR);
+ cause->length = htons((uint16_t)(sizeof(struct sctp_gen_error_cause) + plen));
+ SCTP_BUF_NEXT(op_err) = SCTP_M_COPYM(mat, at, plen, M_NOWAIT);
+ if (SCTP_BUF_NEXT(op_err) == NULL) {
+ sctp_m_freem(op_err);
+ op_err = NULL;
+ op_err_last = NULL;
+ }
+ }
+ return (op_err);
+ }
+ default:
+ /*
+ * we do not recognize the parameter figure out what
+ * we do.
+ */
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Hit default param %x\n", ptype);
+ if ((ptype & 0x4000) == 0x4000) {
+ /* Report bit is set?? */
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "report op err\n");
+ if (op_err == NULL) {
+ int l_len;
+ /* Ok need to try to get an mbuf */
+#ifdef INET6
+ l_len = SCTP_MIN_OVERHEAD;
+#else
+ l_len = SCTP_MIN_V4_OVERHEAD;
+#endif
+ l_len += sizeof(struct sctp_chunkhdr);
+ l_len += sizeof(struct sctp_paramhdr);
+ op_err = sctp_get_mbuf_for_msg(l_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err) {
+ SCTP_BUF_LEN(op_err) = 0;
+#ifdef INET6
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr));
+#else
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip));
+#endif
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr));
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr));
+ op_err_last = op_err;
+ }
+ }
+ if (op_err != NULL) {
+ /* If we have space */
+ struct sctp_paramhdr *param;
+
+ if (pad_needed > 0) {
+ op_err_last = sctp_add_pad_tombuf(op_err_last, pad_needed);
+ }
+ if (op_err_last == NULL) {
+ sctp_m_freem(op_err);
+ op_err = NULL;
+ op_err_last = NULL;
+ goto more_processing;
+ }
+ if (M_TRAILINGSPACE(op_err_last) < (int)sizeof(struct sctp_paramhdr)) {
+ m_tmp = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_tmp == NULL) {
+ sctp_m_freem(op_err);
+ op_err = NULL;
+ op_err_last = NULL;
+ goto more_processing;
+ }
+ SCTP_BUF_LEN(m_tmp) = 0;
+ SCTP_BUF_NEXT(m_tmp) = NULL;
+ SCTP_BUF_NEXT(op_err_last) = m_tmp;
+ op_err_last = m_tmp;
+ }
+ param = (struct sctp_paramhdr *)(mtod(op_err_last, caddr_t) + SCTP_BUF_LEN(op_err_last));
+ param->param_type = htons(SCTP_UNRECOG_PARAM);
+ param->param_length = htons((uint16_t)sizeof(struct sctp_paramhdr) + plen);
+ SCTP_BUF_LEN(op_err_last) += sizeof(struct sctp_paramhdr);
+ SCTP_BUF_NEXT(op_err_last) = SCTP_M_COPYM(mat, at, plen, M_NOWAIT);
+ if (SCTP_BUF_NEXT(op_err_last) == NULL) {
+ sctp_m_freem(op_err);
+ op_err = NULL;
+ op_err_last = NULL;
+ goto more_processing;
+ } else {
+ while (SCTP_BUF_NEXT(op_err_last) != NULL) {
+ op_err_last = SCTP_BUF_NEXT(op_err_last);
+ }
+ }
+ if (plen % 4 != 0) {
+ pad_needed = 4 - (plen % 4);
+ } else {
+ pad_needed = 0;
+ }
+ }
+ }
+ more_processing:
+ if ((ptype & 0x8000) == 0x0000) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "stop proc\n");
+ return (op_err);
+ } else {
+ /* skip this chunk and continue processing */
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "move on\n");
+ at += SCTP_SIZE32(plen);
+ }
+ break;
+ }
+ phdr = sctp_get_next_param(mat, at, &params, sizeof(params));
+ }
+ return (op_err);
+ invalid_size:
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "abort flag set\n");
+ *abort_processing = 1;
+ sctp_m_freem(op_err);
+ op_err = NULL;
+ op_err_last = NULL;
+ if (phdr != NULL) {
+ struct sctp_paramhdr *param;
+ int l_len;
+#ifdef INET6
+ l_len = SCTP_MIN_OVERHEAD;
+#else
+ l_len = SCTP_MIN_V4_OVERHEAD;
+#endif
+ l_len += sizeof(struct sctp_chunkhdr);
+ l_len += (2 * sizeof(struct sctp_paramhdr));
+ op_err = sctp_get_mbuf_for_msg(l_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err) {
+ SCTP_BUF_LEN(op_err) = 0;
+#ifdef INET6
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr));
+#else
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip));
+#endif
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr));
+ SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr));
+ SCTP_BUF_LEN(op_err) = 2 * sizeof(struct sctp_paramhdr);
+ param = mtod(op_err, struct sctp_paramhdr *);
+ param->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION);
+ param->param_length = htons(2 * sizeof(struct sctp_paramhdr));
+ param++;
+ param->param_type = htons(ptype);
+ param->param_length = htons(plen);
+ }
+ }
+ return (op_err);
+}
+
+/*
+ * Given a INIT chunk, look through the parameters to verify that there
+ * are no new addresses.
+ * Return true, if there is a new address or there is a problem parsing
+ the parameters. Provide an optional error cause used when sending an ABORT.
+ * Return false, if there are no new addresses and there is no problem in
+ parameter processing.
+ */
+static bool
+sctp_are_there_new_addresses(struct sctp_association *asoc,
+ struct mbuf *in_initpkt, int offset, int limit, struct sockaddr *src,
+ struct mbuf **op_err)
+{
+ struct sockaddr *sa_touse;
+ struct sockaddr *sa;
+ struct sctp_paramhdr *phdr, params;
+ struct sctp_nets *net;
+#ifdef INET
+ struct sockaddr_in sin4, *sa4;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6, *sa6;
+#endif
+#if defined(__Userspace__)
+ struct sockaddr_conn *sac;
+#endif
+ uint16_t ptype, plen;
+ bool fnd, check_src;
+
+ *op_err = NULL;
+#ifdef INET
+ memset(&sin4, 0, sizeof(sin4));
+ sin4.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin4.sin_len = sizeof(sin4);
+#endif
+#endif
+#ifdef INET6
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6.sin6_len = sizeof(sin6);
+#endif
+#endif
+ /* First what about the src address of the pkt ? */
+ check_src = false;
+ switch (src->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (asoc->scope.ipv4_addr_legal) {
+ check_src = true;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (asoc->scope.ipv6_addr_legal) {
+ check_src = true;
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (asoc->scope.conn_addr_legal) {
+ check_src = true;
+ }
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ if (check_src) {
+ fnd = false;
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ sa = (struct sockaddr *)&net->ro._l_addr;
+ if (sa->sa_family == src->sa_family) {
+#ifdef INET
+ if (sa->sa_family == AF_INET) {
+ struct sockaddr_in *src4;
+
+ sa4 = (struct sockaddr_in *)sa;
+ src4 = (struct sockaddr_in *)src;
+ if (sa4->sin_addr.s_addr == src4->sin_addr.s_addr) {
+ fnd = true;
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+ struct sockaddr_in6 *src6;
+
+ sa6 = (struct sockaddr_in6 *)sa;
+ src6 = (struct sockaddr_in6 *)src;
+ if (SCTP6_ARE_ADDR_EQUAL(sa6, src6)) {
+ fnd = true;
+ break;
+ }
+ }
+#endif
+#if defined(__Userspace__)
+ if (sa->sa_family == AF_CONN) {
+ struct sockaddr_conn *srcc;
+
+ sac = (struct sockaddr_conn *)sa;
+ srcc = (struct sockaddr_conn *)src;
+ if (sac->sconn_addr == srcc->sconn_addr) {
+ fnd = true;
+ break;
+ }
+ }
+#endif
+ }
+ }
+ if (!fnd) {
+ /*
+ * If sending an ABORT in case of an additional address,
+ * don't use the new address error cause.
+ * This looks no different than if no listener was
+ * present.
+ */
+ *op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code), "Address added");
+ return (true);
+ }
+ }
+ /* Ok so far lets munge through the rest of the packet */
+ offset += sizeof(struct sctp_init_chunk);
+ phdr = sctp_get_next_param(in_initpkt, offset, &params, sizeof(params));
+ while (phdr) {
+ sa_touse = NULL;
+ ptype = ntohs(phdr->param_type);
+ plen = ntohs(phdr->param_length);
+ if (offset + plen > limit) {
+ *op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, "Partial parameter");
+ return (true);
+ }
+ if (plen < sizeof(struct sctp_paramhdr)) {
+ *op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, "Parameter length too small");
+ return (true);
+ }
+ switch (ptype) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ {
+ struct sctp_ipv4addr_param *p4, p4_buf;
+
+ if (plen != sizeof(struct sctp_ipv4addr_param)) {
+ *op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, "Parameter length illegal");
+ return (true);
+ }
+ phdr = sctp_get_next_param(in_initpkt, offset,
+ (struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf));
+ if (phdr == NULL) {
+ *op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, "");
+ return (true);
+ }
+ if (asoc->scope.ipv4_addr_legal) {
+ p4 = (struct sctp_ipv4addr_param *)phdr;
+ sin4.sin_addr.s_addr = p4->addr;
+ sa_touse = (struct sockaddr *)&sin4;
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ {
+ struct sctp_ipv6addr_param *p6, p6_buf;
+
+ if (plen != sizeof(struct sctp_ipv6addr_param)) {
+ *op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, "Parameter length illegal");
+ return (true);
+ }
+ phdr = sctp_get_next_param(in_initpkt, offset,
+ (struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf));
+ if (phdr == NULL) {
+ *op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, "");
+ return (true);
+ }
+ if (asoc->scope.ipv6_addr_legal) {
+ p6 = (struct sctp_ipv6addr_param *)phdr;
+ memcpy((caddr_t)&sin6.sin6_addr, p6->addr,
+ sizeof(p6->addr));
+ sa_touse = (struct sockaddr *)&sin6;
+ }
+ break;
+ }
+#endif
+ default:
+ sa_touse = NULL;
+ break;
+ }
+ if (sa_touse) {
+ /* ok, sa_touse points to one to check */
+ fnd = false;
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ sa = (struct sockaddr *)&net->ro._l_addr;
+ if (sa->sa_family != sa_touse->sa_family) {
+ continue;
+ }
+#ifdef INET
+ if (sa->sa_family == AF_INET) {
+ sa4 = (struct sockaddr_in *)sa;
+ if (sa4->sin_addr.s_addr ==
+ sin4.sin_addr.s_addr) {
+ fnd = true;
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+ sa6 = (struct sockaddr_in6 *)sa;
+ if (SCTP6_ARE_ADDR_EQUAL(
+ sa6, &sin6)) {
+ fnd = true;
+ break;
+ }
+ }
+#endif
+ }
+ if (!fnd) {
+ /*
+ * If sending an ABORT in case of an additional
+ * address, don't use the new address error
+ * cause.
+ * This looks no different than if no listener
+ * was present.
+ */
+ *op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code), "Address added");
+ return (true);
+ }
+ }
+ offset += SCTP_SIZE32(plen);
+ if (offset >= limit) {
+ break;
+ }
+ phdr = sctp_get_next_param(in_initpkt, offset, &params, sizeof(params));
+ }
+ return (false);
+}
+
+/*
+ * Given a MBUF chain that was sent into us containing an INIT. Build a
+ * INIT-ACK with COOKIE and send back. We assume that the in_initpkt has done
+ * a pullup to include IPv6/4header, SCTP header and initial part of INIT
+ * message (i.e. the struct sctp_init_msg).
+ */
+void
+sctp_send_initiate_ack(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *src_net, struct mbuf *init_pkt,
+ int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_init_chunk *init_chk,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_association *asoc;
+ struct mbuf *m, *m_tmp, *m_last, *m_cookie, *op_err;
+ struct sctp_init_ack_chunk *initack;
+ struct sctp_adaptation_layer_indication *ali;
+ struct sctp_zero_checksum_acceptable *zero_chksum;
+ struct sctp_supported_chunk_types_param *pr_supported;
+ struct sctp_paramhdr *ph;
+ union sctp_sockstore *over_addr;
+ struct sctp_scoping scp;
+ struct timeval now;
+#ifdef INET
+ struct sockaddr_in *dst4 = (struct sockaddr_in *)dst;
+ struct sockaddr_in *src4 = (struct sockaddr_in *)src;
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst;
+ struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)src;
+ struct sockaddr_in6 *sin6;
+#endif
+#if defined(__Userspace__)
+ struct sockaddr_conn *dstconn = (struct sockaddr_conn *)dst;
+ struct sockaddr_conn *srcconn = (struct sockaddr_conn *)src;
+ struct sockaddr_conn *sconn;
+#endif
+ struct sockaddr *to;
+ struct sctp_state_cookie stc;
+ struct sctp_nets *net = NULL;
+ uint8_t *signature = NULL;
+ int cnt_inits_to = 0;
+ uint16_t his_limit, i_want;
+ int abort_flag;
+ int nat_friendly = 0;
+ int error;
+ struct socket *so;
+ uint16_t num_ext, chunk_len, padding_len, parameter_len;
+
+ if (stcb) {
+ asoc = &stcb->asoc;
+ } else {
+ asoc = NULL;
+ }
+ if ((asoc != NULL) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT)) {
+ if (sctp_are_there_new_addresses(asoc, init_pkt, offset, offset + ntohs(init_chk->ch.chunk_length), src, &op_err)) {
+ /*
+ * new addresses, out of here in non-cookie-wait states
+ */
+ sctp_send_abort(init_pkt, iphlen, src, dst, sh, 0, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ return;
+ }
+ if (src_net != NULL && (src_net->port != port)) {
+ /*
+ * change of remote encapsulation port, out of here in
+ * non-cookie-wait states
+ *
+ * Send an ABORT, without an specific error cause.
+ * This looks no different than if no listener
+ * was present.
+ */
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Remote encapsulation port changed");
+ sctp_send_abort(init_pkt, iphlen, src, dst, sh, 0, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ return;
+ }
+ }
+ abort_flag = 0;
+ op_err = sctp_arethere_unrecognized_parameters(init_pkt,
+ (offset + sizeof(struct sctp_init_chunk)),
+ &abort_flag,
+ (struct sctp_chunkhdr *)init_chk,
+ &nat_friendly, NULL);
+ if (abort_flag) {
+ do_a_abort:
+ if (op_err == NULL) {
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTP_SNPRINTF(msg, sizeof(msg), "%s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ }
+ sctp_send_abort(init_pkt, iphlen, src, dst, sh,
+ init_chk->init.initiate_tag, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ return;
+ }
+ m = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (m == NULL) {
+ /* No memory, INIT timer will re-attempt. */
+ sctp_m_freem(op_err);
+ return;
+ }
+ chunk_len = (uint16_t)sizeof(struct sctp_init_ack_chunk);
+ padding_len = 0;
+
+ /*
+ * We might not overwrite the identification[] completely and on
+ * some platforms time_entered will contain some padding.
+ * Therefore zero out the cookie to avoid putting
+ * uninitialized memory on the wire.
+ */
+ memset(&stc, 0, sizeof(struct sctp_state_cookie));
+
+ /* the time I built cookie */
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ stc.time_entered.tv_sec = now.tv_sec;
+ stc.time_entered.tv_usec = now.tv_usec;
+
+ /* populate any tie tags */
+ if (asoc != NULL) {
+ /* unlock before tag selections */
+ stc.tie_tag_my_vtag = asoc->my_vtag_nonce;
+ stc.tie_tag_peer_vtag = asoc->peer_vtag_nonce;
+ stc.cookie_life = asoc->cookie_life;
+ net = asoc->primary_destination;
+ } else {
+ stc.tie_tag_my_vtag = 0;
+ stc.tie_tag_peer_vtag = 0;
+ /* life I will award this cookie */
+ stc.cookie_life = inp->sctp_ep.def_cookie_life;
+ }
+
+ /* copy in the ports for later check */
+ stc.myport = sh->dest_port;
+ stc.peerport = sh->src_port;
+
+ /*
+ * If we wanted to honor cookie life extensions, we would add to
+ * stc.cookie_life. For now we should NOT honor any extension
+ */
+ stc.site_scope = stc.local_scope = stc.loopback_scope = 0;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ stc.ipv6_addr_legal = 1;
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ stc.ipv4_addr_legal = 0;
+ } else {
+ stc.ipv4_addr_legal = 1;
+ }
+#if defined(__Userspace__)
+ stc.conn_addr_legal = 0;
+#endif
+ } else {
+ stc.ipv6_addr_legal = 0;
+#if defined(__Userspace__)
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ stc.conn_addr_legal = 1;
+ stc.ipv4_addr_legal = 0;
+ } else {
+ stc.conn_addr_legal = 0;
+ stc.ipv4_addr_legal = 1;
+ }
+#else
+ stc.ipv4_addr_legal = 1;
+#endif
+ }
+ stc.ipv4_scope = 0;
+ if (net == NULL) {
+ to = src;
+ switch (dst->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ /* lookup address */
+ stc.address[0] = src4->sin_addr.s_addr;
+ stc.address[1] = 0;
+ stc.address[2] = 0;
+ stc.address[3] = 0;
+ stc.addr_type = SCTP_IPV4_ADDRESS;
+ /* local from address */
+ stc.laddress[0] = dst4->sin_addr.s_addr;
+ stc.laddress[1] = 0;
+ stc.laddress[2] = 0;
+ stc.laddress[3] = 0;
+ stc.laddr_type = SCTP_IPV4_ADDRESS;
+ /* scope_id is only for v6 */
+ stc.scope_id = 0;
+ if ((IN4_ISPRIVATE_ADDRESS(&src4->sin_addr)) ||
+ (IN4_ISPRIVATE_ADDRESS(&dst4->sin_addr))) {
+ stc.ipv4_scope = 1;
+ }
+ /* Must use the address in this case */
+ if (sctp_is_address_on_local_host(src, vrf_id)) {
+ stc.loopback_scope = 1;
+ stc.ipv4_scope = 1;
+ stc.site_scope = 1;
+ stc.local_scope = 0;
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ stc.addr_type = SCTP_IPV6_ADDRESS;
+ memcpy(&stc.address, &src6->sin6_addr, sizeof(struct in6_addr));
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ stc.scope_id = ntohs(in6_getscope(&src6->sin6_addr));
+#else
+ stc.scope_id = 0;
+#endif
+ if (sctp_is_address_on_local_host(src, vrf_id)) {
+ stc.loopback_scope = 1;
+ stc.local_scope = 0;
+ stc.site_scope = 1;
+ stc.ipv4_scope = 1;
+ } else if (IN6_IS_ADDR_LINKLOCAL(&src6->sin6_addr) ||
+ IN6_IS_ADDR_LINKLOCAL(&dst6->sin6_addr)) {
+ /*
+ * If the new destination or source is a
+ * LINK_LOCAL we must have common both site and
+ * local scope. Don't set local scope though
+ * since we must depend on the source to be
+ * added implicitly. We cannot assure just
+ * because we share one link that all links are
+ * common.
+ */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ /* Mac OS X currently doesn't have in6_getscope() */
+ stc.scope_id = src6->sin6_addr.s6_addr16[1];
+#endif
+ stc.local_scope = 0;
+ stc.site_scope = 1;
+ stc.ipv4_scope = 1;
+ /*
+ * we start counting for the private address
+ * stuff at 1. since the link local we
+ * source from won't show up in our scoped
+ * count.
+ */
+ cnt_inits_to = 1;
+ /* pull out the scope_id from incoming pkt */
+ } else if (IN6_IS_ADDR_SITELOCAL(&src6->sin6_addr) ||
+ IN6_IS_ADDR_SITELOCAL(&dst6->sin6_addr)) {
+ /*
+ * If the new destination or source is
+ * SITE_LOCAL then we must have site scope in
+ * common.
+ */
+ stc.site_scope = 1;
+ }
+ memcpy(&stc.laddress, &dst6->sin6_addr, sizeof(struct in6_addr));
+ stc.laddr_type = SCTP_IPV6_ADDRESS;
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ /* lookup address */
+ stc.address[0] = 0;
+ stc.address[1] = 0;
+ stc.address[2] = 0;
+ stc.address[3] = 0;
+ memcpy(&stc.address, &srcconn->sconn_addr, sizeof(void *));
+ stc.addr_type = SCTP_CONN_ADDRESS;
+ /* local from address */
+ stc.laddress[0] = 0;
+ stc.laddress[1] = 0;
+ stc.laddress[2] = 0;
+ stc.laddress[3] = 0;
+ memcpy(&stc.laddress, &dstconn->sconn_addr, sizeof(void *));
+ stc.laddr_type = SCTP_CONN_ADDRESS;
+ /* scope_id is only for v6 */
+ stc.scope_id = 0;
+ break;
+ }
+#endif
+ default:
+ /* TSNH */
+ goto do_a_abort;
+ break;
+ }
+ } else {
+ /* set the scope per the existing tcb */
+
+#ifdef INET6
+ struct sctp_nets *lnet;
+#endif
+
+ stc.loopback_scope = asoc->scope.loopback_scope;
+ stc.ipv4_scope = asoc->scope.ipv4_local_scope;
+ stc.site_scope = asoc->scope.site_scope;
+ stc.local_scope = asoc->scope.local_scope;
+#ifdef INET6
+ /* Why do we not consider IPv4 LL addresses? */
+ TAILQ_FOREACH(lnet, &asoc->nets, sctp_next) {
+ if (lnet->ro._l_addr.sin6.sin6_family == AF_INET6) {
+ if (IN6_IS_ADDR_LINKLOCAL(&lnet->ro._l_addr.sin6.sin6_addr)) {
+ /*
+ * if we have a LL address, start
+ * counting at 1.
+ */
+ cnt_inits_to = 1;
+ }
+ }
+ }
+#endif
+ /* use the net pointer */
+ to = (struct sockaddr *)&net->ro._l_addr;
+ switch (to->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sin = (struct sockaddr_in *)to;
+ stc.address[0] = sin->sin_addr.s_addr;
+ stc.address[1] = 0;
+ stc.address[2] = 0;
+ stc.address[3] = 0;
+ stc.addr_type = SCTP_IPV4_ADDRESS;
+ if (net->src_addr_selected == 0) {
+ /*
+ * strange case here, the INIT should have
+ * did the selection.
+ */
+ net->ro._s_addr = sctp_source_address_selection(inp,
+ stcb, (sctp_route_t *)&net->ro,
+ net, 0, vrf_id);
+ if (net->ro._s_addr == NULL) {
+ sctp_m_freem(op_err);
+ sctp_m_freem(m);
+ return;
+ }
+
+ net->src_addr_selected = 1;
+ }
+ stc.laddress[0] = net->ro._s_addr->address.sin.sin_addr.s_addr;
+ stc.laddress[1] = 0;
+ stc.laddress[2] = 0;
+ stc.laddress[3] = 0;
+ stc.laddr_type = SCTP_IPV4_ADDRESS;
+ /* scope_id is only for v6 */
+ stc.scope_id = 0;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)to;
+ memcpy(&stc.address, &sin6->sin6_addr,
+ sizeof(struct in6_addr));
+ stc.addr_type = SCTP_IPV6_ADDRESS;
+ stc.scope_id = sin6->sin6_scope_id;
+ if (net->src_addr_selected == 0) {
+ /*
+ * strange case here, the INIT should have
+ * done the selection.
+ */
+ net->ro._s_addr = sctp_source_address_selection(inp,
+ stcb, (sctp_route_t *)&net->ro,
+ net, 0, vrf_id);
+ if (net->ro._s_addr == NULL) {
+ sctp_m_freem(op_err);
+ sctp_m_freem(m);
+ return;
+ }
+
+ net->src_addr_selected = 1;
+ }
+ memcpy(&stc.laddress, &net->ro._s_addr->address.sin6.sin6_addr,
+ sizeof(struct in6_addr));
+ stc.laddr_type = SCTP_IPV6_ADDRESS;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ sconn = (struct sockaddr_conn *)to;
+ stc.address[0] = 0;
+ stc.address[1] = 0;
+ stc.address[2] = 0;
+ stc.address[3] = 0;
+ memcpy(&stc.address, &sconn->sconn_addr, sizeof(void *));
+ stc.addr_type = SCTP_CONN_ADDRESS;
+ stc.laddress[0] = 0;
+ stc.laddress[1] = 0;
+ stc.laddress[2] = 0;
+ stc.laddress[3] = 0;
+ memcpy(&stc.laddress, &sconn->sconn_addr, sizeof(void *));
+ stc.laddr_type = SCTP_CONN_ADDRESS;
+ stc.scope_id = 0;
+ break;
+#endif
+ }
+ }
+ if (asoc != NULL) {
+ stc.rcv_edmid = asoc->rcv_edmid;
+ } else {
+ stc.rcv_edmid = inp->rcv_edmid;
+ }
+ /* Now lets put the SCTP header in place */
+ initack = mtod(m, struct sctp_init_ack_chunk *);
+ /* Save it off for quick ref */
+ stc.peers_vtag = ntohl(init_chk->init.initiate_tag);
+ /* who are we */
+ memcpy(stc.identification, SCTP_VERSION_STRING,
+ min(strlen(SCTP_VERSION_STRING), sizeof(stc.identification)));
+ memset(stc.reserved, 0, SCTP_RESERVE_SPACE);
+ /* now the chunk header */
+ initack->ch.chunk_type = SCTP_INITIATION_ACK;
+ initack->ch.chunk_flags = 0;
+ /* fill in later from mbuf we build */
+ initack->ch.chunk_length = 0;
+ /* place in my tag */
+ if ((asoc != NULL) &&
+ ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_INUSE) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED))) {
+ /* re-use the v-tags and init-seq here */
+ initack->init.initiate_tag = htonl(asoc->my_vtag);
+ initack->init.initial_tsn = htonl(asoc->init_seq_number);
+ } else {
+ uint32_t vtag, itsn;
+
+ if (asoc) {
+ atomic_add_int(&asoc->refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ new_tag:
+ SCTP_INP_INFO_RLOCK();
+ vtag = sctp_select_a_tag(inp, inp->sctp_lport, sh->src_port, 1);
+ SCTP_INP_INFO_RUNLOCK();
+ if ((asoc->peer_supports_nat) && (vtag == asoc->my_vtag)) {
+ /* Got a duplicate vtag on some guy behind a nat
+ * make sure we don't use it.
+ */
+ goto new_tag;
+ }
+ initack->init.initiate_tag = htonl(vtag);
+ /* get a TSN to use too */
+ itsn = sctp_select_initial_TSN(&inp->sctp_ep);
+ initack->init.initial_tsn = htonl(itsn);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&asoc->refcnt, 1);
+ } else {
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RLOCK();
+ vtag = sctp_select_a_tag(inp, inp->sctp_lport, sh->src_port, 1);
+ SCTP_INP_INFO_RUNLOCK();
+ initack->init.initiate_tag = htonl(vtag);
+ /* get a TSN to use too */
+ initack->init.initial_tsn = htonl(sctp_select_initial_TSN(&inp->sctp_ep));
+ SCTP_INP_RLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ /* save away my tag to */
+ stc.my_vtag = initack->init.initiate_tag;
+
+ /* set up some of the credits. */
+ so = inp->sctp_socket;
+ if (so == NULL) {
+ /* memory problem */
+ sctp_m_freem(op_err);
+ sctp_m_freem(m);
+ return;
+ } else {
+ initack->init.a_rwnd = htonl(max(SCTP_SB_LIMIT_RCV(so), SCTP_MINIMAL_RWND));
+ }
+ /* set what I want */
+ his_limit = ntohs(init_chk->init.num_inbound_streams);
+ /* choose what I want */
+ if (asoc != NULL) {
+ if (asoc->streamoutcnt > asoc->pre_open_streams) {
+ i_want = asoc->streamoutcnt;
+ } else {
+ i_want = asoc->pre_open_streams;
+ }
+ } else {
+ i_want = inp->sctp_ep.pre_open_stream_count;
+ }
+ if (his_limit < i_want) {
+ /* I Want more :< */
+ initack->init.num_outbound_streams = init_chk->init.num_inbound_streams;
+ } else {
+ /* I can have what I want :> */
+ initack->init.num_outbound_streams = htons(i_want);
+ }
+ /* tell him his limit. */
+ initack->init.num_inbound_streams =
+ htons(inp->sctp_ep.max_open_streams_intome);
+
+ /* adaptation layer indication parameter */
+ if (inp->sctp_ep.adaptation_layer_indicator_provided) {
+ parameter_len = (uint16_t)sizeof(struct sctp_adaptation_layer_indication);
+ ali = (struct sctp_adaptation_layer_indication *)(mtod(m, caddr_t) + chunk_len);
+ ali->ph.param_type = htons(SCTP_ULP_ADAPTATION);
+ ali->ph.param_length = htons(parameter_len);
+ ali->indication = htonl(inp->sctp_ep.adaptation_layer_indicator);
+ chunk_len += parameter_len;
+ }
+
+ /* ECN parameter */
+ if (((asoc != NULL) && (asoc->ecn_supported == 1)) ||
+ ((asoc == NULL) && (inp->ecn_supported == 1))) {
+ parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+ ph = (struct sctp_paramhdr *)(mtod(m, caddr_t) + chunk_len);
+ ph->param_type = htons(SCTP_ECN_CAPABLE);
+ ph->param_length = htons(parameter_len);
+ chunk_len += parameter_len;
+ }
+
+ /* PR-SCTP supported parameter */
+ if (((asoc != NULL) && (asoc->prsctp_supported == 1)) ||
+ ((asoc == NULL) && (inp->prsctp_supported == 1))) {
+ parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+ ph = (struct sctp_paramhdr *)(mtod(m, caddr_t) + chunk_len);
+ ph->param_type = htons(SCTP_PRSCTP_SUPPORTED);
+ ph->param_length = htons(parameter_len);
+ chunk_len += parameter_len;
+ }
+
+ /* Zero checksum acceptable parameter */
+ if (((asoc != NULL) && (asoc->rcv_edmid != SCTP_EDMID_NONE)) ||
+ ((asoc == NULL) && (inp->rcv_edmid != SCTP_EDMID_NONE))) {
+ parameter_len = (uint16_t)sizeof(struct sctp_zero_checksum_acceptable);
+ zero_chksum = (struct sctp_zero_checksum_acceptable *)(mtod(m, caddr_t) + chunk_len);
+ zero_chksum->ph.param_type = htons(SCTP_ZERO_CHECKSUM_ACCEPTABLE);
+ zero_chksum->ph.param_length = htons(parameter_len);
+ if (asoc != NULL) {
+ zero_chksum->edmid = htonl(asoc->rcv_edmid);
+ } else {
+ zero_chksum->edmid = htonl(inp->rcv_edmid);
+ }
+ chunk_len += parameter_len;
+ }
+
+ /* Add NAT friendly parameter */
+ if (nat_friendly) {
+ parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+ ph = (struct sctp_paramhdr *)(mtod(m, caddr_t) + chunk_len);
+ ph->param_type = htons(SCTP_HAS_NAT_SUPPORT);
+ ph->param_length = htons(parameter_len);
+ chunk_len += parameter_len;
+ }
+
+ /* And now tell the peer which extensions we support */
+ num_ext = 0;
+ pr_supported = (struct sctp_supported_chunk_types_param *)(mtod(m, caddr_t) + chunk_len);
+ if (((asoc != NULL) && (asoc->prsctp_supported == 1)) ||
+ ((asoc == NULL) && (inp->prsctp_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_FORWARD_CUM_TSN;
+ if (((asoc != NULL) && (asoc->idata_supported == 1)) ||
+ ((asoc == NULL) && (inp->idata_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_IFORWARD_CUM_TSN;
+ }
+ }
+ if (((asoc != NULL) && (asoc->auth_supported == 1)) ||
+ ((asoc == NULL) && (inp->auth_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_AUTHENTICATION;
+ }
+ if (((asoc != NULL) && (asoc->asconf_supported == 1)) ||
+ ((asoc == NULL) && (inp->asconf_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_ASCONF;
+ pr_supported->chunk_types[num_ext++] = SCTP_ASCONF_ACK;
+ }
+ if (((asoc != NULL) && (asoc->reconfig_supported == 1)) ||
+ ((asoc == NULL) && (inp->reconfig_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_STREAM_RESET;
+ }
+ if (((asoc != NULL) && (asoc->idata_supported == 1)) ||
+ ((asoc == NULL) && (inp->idata_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_IDATA;
+ }
+ if (((asoc != NULL) && (asoc->nrsack_supported == 1)) ||
+ ((asoc == NULL) && (inp->nrsack_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_NR_SELECTIVE_ACK;
+ }
+ if (((asoc != NULL) && (asoc->pktdrop_supported == 1)) ||
+ ((asoc == NULL) && (inp->pktdrop_supported == 1))) {
+ pr_supported->chunk_types[num_ext++] = SCTP_PACKET_DROPPED;
+ }
+ if (num_ext > 0) {
+ parameter_len = (uint16_t)sizeof(struct sctp_supported_chunk_types_param) + num_ext;
+ pr_supported->ph.param_type = htons(SCTP_SUPPORTED_CHUNK_EXT);
+ pr_supported->ph.param_length = htons(parameter_len);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+ }
+
+ /* add authentication parameters */
+ if (((asoc != NULL) && (asoc->auth_supported == 1)) ||
+ ((asoc == NULL) && (inp->auth_supported == 1))) {
+ struct sctp_auth_random *randp;
+ struct sctp_auth_hmac_algo *hmacs;
+ struct sctp_auth_chunk_list *chunks;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ /* generate and add RANDOM parameter */
+ randp = (struct sctp_auth_random *)(mtod(m, caddr_t) + chunk_len);
+ parameter_len = (uint16_t)sizeof(struct sctp_auth_random) +
+ SCTP_AUTH_RANDOM_SIZE_DEFAULT;
+ randp->ph.param_type = htons(SCTP_RANDOM);
+ randp->ph.param_length = htons(parameter_len);
+ SCTP_READ_RANDOM(randp->random_data, SCTP_AUTH_RANDOM_SIZE_DEFAULT);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ /* add HMAC_ALGO parameter */
+ hmacs = (struct sctp_auth_hmac_algo *)(mtod(m, caddr_t) + chunk_len);
+ parameter_len = (uint16_t)sizeof(struct sctp_auth_hmac_algo) +
+ sctp_serialize_hmaclist(inp->sctp_ep.local_hmacs,
+ (uint8_t *)hmacs->hmac_ids);
+ hmacs->ph.param_type = htons(SCTP_HMAC_LIST);
+ hmacs->ph.param_length = htons(parameter_len);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ /* add CHUNKS parameter */
+ chunks = (struct sctp_auth_chunk_list *)(mtod(m, caddr_t) + chunk_len);
+ parameter_len = (uint16_t)sizeof(struct sctp_auth_chunk_list) +
+ sctp_serialize_auth_chunks(inp->sctp_ep.local_auth_chunks,
+ chunks->chunk_types);
+ chunks->ph.param_type = htons(SCTP_CHUNK_LIST);
+ chunks->ph.param_length = htons(parameter_len);
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+ }
+ SCTP_BUF_LEN(m) = chunk_len;
+ m_last = m;
+ /* now the addresses */
+ /* To optimize this we could put the scoping stuff
+ * into a structure and remove the individual uint8's from
+ * the stc structure. Then we could just sifa in the
+ * address within the stc.. but for now this is a quick
+ * hack to get the address stuff teased apart.
+ */
+ scp.ipv4_addr_legal = stc.ipv4_addr_legal;
+ scp.ipv6_addr_legal = stc.ipv6_addr_legal;
+#if defined(__Userspace__)
+ scp.conn_addr_legal = stc.conn_addr_legal;
+#endif
+ scp.loopback_scope = stc.loopback_scope;
+ scp.ipv4_local_scope = stc.ipv4_scope;
+ scp.local_scope = stc.local_scope;
+ scp.site_scope = stc.site_scope;
+ m_last = sctp_add_addresses_to_i_ia(inp, stcb, &scp, m_last,
+ cnt_inits_to,
+ &padding_len, &chunk_len);
+ /* padding_len can only be positive, if no addresses have been added */
+ if (padding_len > 0) {
+ memset(mtod(m, caddr_t) + chunk_len, 0, padding_len);
+ chunk_len += padding_len;
+ SCTP_BUF_LEN(m) += padding_len;
+ padding_len = 0;
+ }
+
+ /* tack on the operational error if present */
+ if (op_err) {
+ parameter_len = 0;
+ for (m_tmp = op_err; m_tmp != NULL; m_tmp = SCTP_BUF_NEXT(m_tmp)) {
+ parameter_len += SCTP_BUF_LEN(m_tmp);
+ }
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ SCTP_BUF_NEXT(m_last) = op_err;
+ while (SCTP_BUF_NEXT(m_last) != NULL) {
+ m_last = SCTP_BUF_NEXT(m_last);
+ }
+ chunk_len += parameter_len;
+ }
+ if (padding_len > 0) {
+ m_last = sctp_add_pad_tombuf(m_last, padding_len);
+ if (m_last == NULL) {
+ /* Houston we have a problem, no space */
+ sctp_m_freem(m);
+ return;
+ }
+ chunk_len += padding_len;
+ padding_len = 0;
+ }
+ /* Now we must build a cookie */
+ m_cookie = sctp_add_cookie(init_pkt, offset, m, 0, &stc, &signature);
+ if (m_cookie == NULL) {
+ /* memory problem */
+ sctp_m_freem(m);
+ return;
+ }
+ /* Now append the cookie to the end and update the space/size */
+ SCTP_BUF_NEXT(m_last) = m_cookie;
+ parameter_len = 0;
+ for (m_tmp = m_cookie; m_tmp != NULL; m_tmp = SCTP_BUF_NEXT(m_tmp)) {
+ parameter_len += SCTP_BUF_LEN(m_tmp);
+ if (SCTP_BUF_NEXT(m_tmp) == NULL) {
+ m_last = m_tmp;
+ }
+ }
+ padding_len = SCTP_SIZE32(parameter_len) - parameter_len;
+ chunk_len += parameter_len;
+
+ /* Place in the size, but we don't include
+ * the last pad (if any) in the INIT-ACK.
+ */
+ initack->ch.chunk_length = htons(chunk_len);
+
+ /* Time to sign the cookie, we don't sign over the cookie
+ * signature though thus we set trailer.
+ */
+ (void)sctp_hmac_m(SCTP_HMAC,
+ (uint8_t *)inp->sctp_ep.secret_key[(int)(inp->sctp_ep.current_secret_number)],
+ SCTP_SECRET_SIZE, m_cookie, sizeof(struct sctp_paramhdr),
+ (uint8_t *)signature, SCTP_SIGNATURE_SIZE);
+#if defined(__Userspace__)
+ /*
+ * Don't put AF_CONN addresses on the wire, in case this is critical
+ * for the application. However, they are protected by the HMAC and
+ * need to be reconstructed before checking the HMAC.
+ * Clearing is only done in the mbuf chain, since the local stc is
+ * not used anymore.
+ */
+ if (stc.addr_type == SCTP_CONN_ADDRESS) {
+ const void *p = NULL;
+
+ m_copyback(m_cookie, sizeof(struct sctp_paramhdr) + offsetof(struct sctp_state_cookie, address),
+ (int)sizeof(void *), (caddr_t)&p);
+ }
+ if (stc.laddr_type == SCTP_CONN_ADDRESS) {
+ const void *p = NULL;
+
+ m_copyback(m_cookie, sizeof(struct sctp_paramhdr) + offsetof(struct sctp_state_cookie, laddress),
+ (int)sizeof(void *), (caddr_t)&p);
+ }
+#endif
+ /*
+ * We sifa 0 here to NOT set IP_DF if its IPv4, we ignore the return
+ * here since the timer will drive a retranmission.
+ */
+ if (padding_len > 0) {
+ if (sctp_add_pad_tombuf(m_last, padding_len) == NULL) {
+ sctp_m_freem(m);
+ return;
+ }
+ }
+ if (stc.loopback_scope) {
+ over_addr = (union sctp_sockstore *)dst;
+ } else {
+ over_addr = NULL;
+ }
+
+ if ((error = sctp_lowlevel_chunk_output(inp, NULL, NULL, to, m, 0, NULL, 0, 0,
+ 0, 0,
+ inp->sctp_lport, sh->src_port, init_chk->init.initiate_tag,
+ port, over_addr,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ false, /* XXXMT: Improve this! */
+ SCTP_SO_NOT_LOCKED))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "Gak send error %d\n", error);
+ if (error == ENOBUFS) {
+ if (asoc != NULL) {
+ asoc->ifp_had_enobuf = 1;
+ }
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ } else {
+ if (asoc != NULL) {
+ asoc->ifp_had_enobuf = 0;
+ }
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+}
+
+static void
+sctp_prune_prsctp(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_nonpad_sndrcvinfo *srcv,
+ int dataout)
+{
+ int freed_spc = 0;
+ struct sctp_tmit_chunk *chk, *nchk;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if ((asoc->prsctp_supported) &&
+ (asoc->sent_queue_cnt_removeable > 0)) {
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ /*
+ * Look for chunks marked with the PR_SCTP flag AND
+ * the buffer space flag. If the one being sent is
+ * equal or greater priority then purge the old one
+ * and free some space.
+ */
+ if (PR_SCTP_BUF_ENABLED(chk->flags)) {
+ /*
+ * This one is PR-SCTP AND buffer space
+ * limited type
+ */
+ if (chk->rec.data.timetodrop.tv_sec > (long)srcv->sinfo_timetolive) {
+ /*
+ * Lower numbers equates to higher
+ * priority. So if the one we are
+ * looking at has a larger priority,
+ * we want to drop the data and NOT
+ * retransmit it.
+ */
+ if (chk->data) {
+ /*
+ * We release the book_size
+ * if the mbuf is here
+ */
+ int ret_spc;
+ uint8_t sent;
+
+ if (chk->sent > SCTP_DATAGRAM_UNSENT)
+ sent = 1;
+ else
+ sent = 0;
+ ret_spc = sctp_release_pr_sctp_chunk(stcb, chk,
+ sent,
+ SCTP_SO_LOCKED);
+ freed_spc += ret_spc;
+ if (freed_spc >= dataout) {
+ return;
+ }
+ } /* if chunk was present */
+ } /* if of sufficient priority */
+ } /* if chunk has enabled */
+ } /* tailqforeach */
+
+ TAILQ_FOREACH_SAFE(chk, &asoc->send_queue, sctp_next, nchk) {
+ /* Here we must move to the sent queue and mark */
+ if (PR_SCTP_BUF_ENABLED(chk->flags)) {
+ if (chk->rec.data.timetodrop.tv_sec > (long)srcv->sinfo_timetolive) {
+ if (chk->data) {
+ /*
+ * We release the book_size
+ * if the mbuf is here
+ */
+ int ret_spc;
+
+ ret_spc = sctp_release_pr_sctp_chunk(stcb, chk,
+ 0, SCTP_SO_LOCKED);
+
+ freed_spc += ret_spc;
+ if (freed_spc >= dataout) {
+ return;
+ }
+ } /* end if chk->data */
+ } /* end if right class */
+ } /* end if chk pr-sctp */
+ } /* tailqforeachsafe (chk) */
+ } /* if enabled in asoc */
+}
+
+uint32_t
+sctp_get_frag_point(struct sctp_tcb *stcb)
+{
+ struct sctp_association *asoc;
+ uint32_t frag_point, overhead;
+
+ asoc = &stcb->asoc;
+ /* Consider IP header and SCTP common header. */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ overhead = SCTP_MIN_OVERHEAD;
+ } else {
+#if defined(__Userspace__)
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ overhead = sizeof(struct sctphdr);
+ } else {
+ overhead = SCTP_MIN_V4_OVERHEAD;
+ }
+#else
+ overhead = SCTP_MIN_V4_OVERHEAD;
+#endif
+ }
+ /* Consider DATA/IDATA chunk header and AUTH header, if needed. */
+ if (asoc->idata_supported) {
+ overhead += sizeof(struct sctp_idata_chunk);
+ if (sctp_auth_is_required_chunk(SCTP_IDATA, asoc->peer_auth_chunks)) {
+ overhead += sctp_get_auth_chunk_len(asoc->peer_hmac_id);
+ }
+ } else {
+ overhead += sizeof(struct sctp_data_chunk);
+ if (sctp_auth_is_required_chunk(SCTP_DATA, asoc->peer_auth_chunks)) {
+ overhead += sctp_get_auth_chunk_len(asoc->peer_hmac_id);
+ }
+ }
+ KASSERT(overhead % 4 == 0,
+ ("overhead (%u) not a multiple of 4", overhead));
+ /* Consider padding. */
+ if (asoc->smallest_mtu % 4 > 0) {
+ overhead += (asoc->smallest_mtu % 4);
+ }
+ KASSERT(asoc->smallest_mtu > overhead,
+ ("Association MTU (%u) too small for overhead (%u)",
+ asoc->smallest_mtu, overhead));
+ frag_point = asoc->smallest_mtu - overhead;
+ KASSERT(frag_point % 4 == 0,
+ ("frag_point (%u) not a multiple of 4", frag_point));
+ /* Honor MAXSEG socket option. */
+ if ((asoc->sctp_frag_point > 0) &&
+ (asoc->sctp_frag_point < frag_point)) {
+ frag_point = asoc->sctp_frag_point;
+ }
+ return (frag_point);
+}
+
+static void
+sctp_set_prsctp_policy(struct sctp_stream_queue_pending *sp)
+{
+ /*
+ * We assume that the user wants PR_SCTP_TTL if the user
+ * provides a positive lifetime but does not specify any
+ * PR_SCTP policy.
+ */
+ if (PR_SCTP_ENABLED(sp->sinfo_flags)) {
+ sp->act_flags |= PR_SCTP_POLICY(sp->sinfo_flags);
+ } else if (sp->timetolive > 0) {
+ sp->sinfo_flags |= SCTP_PR_SCTP_TTL;
+ sp->act_flags |= PR_SCTP_POLICY(sp->sinfo_flags);
+ } else {
+ return;
+ }
+ switch (PR_SCTP_POLICY(sp->sinfo_flags)) {
+ case CHUNK_FLAGS_PR_SCTP_BUF:
+ /*
+ * Time to live is a priority stored in tv_sec when
+ * doing the buffer drop thing.
+ */
+ sp->ts.tv_sec = sp->timetolive;
+ sp->ts.tv_usec = 0;
+ break;
+ case CHUNK_FLAGS_PR_SCTP_TTL:
+ {
+ struct timeval tv;
+ (void)SCTP_GETTIME_TIMEVAL(&sp->ts);
+ tv.tv_sec = sp->timetolive / 1000;
+ tv.tv_usec = (sp->timetolive * 1000) % 1000000;
+ /* TODO sctp_constants.h needs alternative time macros when
+ * _KERNEL is undefined.
+ */
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ timeradd(&sp->ts, &tv, &sp->ts);
+#else
+ timevaladd(&sp->ts, &tv);
+#endif
+ }
+ break;
+ case CHUNK_FLAGS_PR_SCTP_RTX:
+ /*
+ * Time to live is a the number or retransmissions
+ * stored in tv_sec.
+ */
+ sp->ts.tv_sec = sp->timetolive;
+ sp->ts.tv_usec = 0;
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_USRREQ1,
+ "Unknown PR_SCTP policy %u.\n",
+ PR_SCTP_POLICY(sp->sinfo_flags));
+ break;
+ }
+}
+
+static int
+sctp_msg_append(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ struct mbuf *m,
+ struct sctp_nonpad_sndrcvinfo *srcv)
+{
+ int error = 0;
+ struct mbuf *at;
+ struct sctp_stream_queue_pending *sp = NULL;
+ struct sctp_stream_out *strm;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Given an mbuf chain, put it
+ * into the association send queue and
+ * place it on the wheel
+ */
+ if (srcv->sinfo_stream >= stcb->asoc.streamoutcnt) {
+ /* Invalid stream number */
+ SCTP_LTRACE_ERR_RET_PKT(m, NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+ if ((stcb->asoc.stream_locked) &&
+ (stcb->asoc.stream_locked_on != srcv->sinfo_stream)) {
+ SCTP_LTRACE_ERR_RET_PKT(m, NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+ if ((stcb->asoc.strmout[srcv->sinfo_stream].state != SCTP_STREAM_OPEN) &&
+ (stcb->asoc.strmout[srcv->sinfo_stream].state != SCTP_STREAM_OPENING)) {
+ /*
+ * Can't queue any data while stream reset is underway.
+ */
+ if (stcb->asoc.strmout[srcv->sinfo_stream].state > SCTP_STREAM_OPEN) {
+ error = EAGAIN;
+ } else {
+ error = EINVAL;
+ }
+ goto out_now;
+ }
+ /* Now can we send this? */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_SENT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_ACK_SENT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED) ||
+ (stcb->asoc.state & SCTP_STATE_SHUTDOWN_PENDING)) {
+ /* got data while shutting down */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EPIPE);
+ error = EPIPE;
+ goto out_now;
+ }
+ sctp_alloc_a_strmoq(stcb, sp);
+ if (sp == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ error = ENOMEM;
+ goto out_now;
+ }
+ sp->sinfo_flags = srcv->sinfo_flags;
+ sp->timetolive = srcv->sinfo_timetolive;
+ sp->ppid = srcv->sinfo_ppid;
+ sp->context = srcv->sinfo_context;
+ sp->fsn = 0;
+ if (sp->sinfo_flags & SCTP_ADDR_OVER) {
+ sp->net = net;
+ atomic_add_int(&sp->net->ref_count, 1);
+ } else {
+ sp->net = NULL;
+ }
+ (void)SCTP_GETTIME_TIMEVAL(&sp->ts);
+ sp->sid = srcv->sinfo_stream;
+ sp->msg_is_complete = 1;
+ sp->sender_all_done = 1;
+ sp->some_taken = 0;
+ sp->data = m;
+ sp->tail_mbuf = NULL;
+ sctp_set_prsctp_policy(sp);
+ /* We could in theory (for sendall) sifa the length
+ * in, but we would still have to hunt through the
+ * chain since we need to setup the tail_mbuf
+ */
+ sp->length = 0;
+ for (at = m; at; at = SCTP_BUF_NEXT(at)) {
+ if (SCTP_BUF_NEXT(at) == NULL)
+ sp->tail_mbuf = at;
+ sp->length += SCTP_BUF_LEN(at);
+ }
+ if (srcv->sinfo_keynumber_valid) {
+ sp->auth_keyid = srcv->sinfo_keynumber;
+ } else {
+ sp->auth_keyid = stcb->asoc.authinfo.active_keyid;
+ }
+ if (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks)) {
+ sctp_auth_key_acquire(stcb, sp->auth_keyid);
+ sp->holds_key_ref = 1;
+ }
+ strm = &stcb->asoc.strmout[srcv->sinfo_stream];
+ sctp_snd_sb_alloc(stcb, sp->length);
+ atomic_add_int(&stcb->asoc.stream_queue_cnt, 1);
+ TAILQ_INSERT_TAIL(&strm->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, &stcb->asoc, strm, sp);
+ m = NULL;
+out_now:
+ if (m) {
+ sctp_m_freem(m);
+ }
+ return (error);
+}
+
+static struct mbuf *
+sctp_copy_mbufchain(struct mbuf *clonechain,
+ struct mbuf *outchain,
+ struct mbuf **endofchain,
+ int can_take_mbuf,
+ int sizeofcpy,
+ uint8_t copy_by_ref)
+{
+ struct mbuf *m;
+ struct mbuf *appendchain;
+ caddr_t cp;
+ int len;
+
+ if (endofchain == NULL) {
+ /* error */
+ error_out:
+ if (outchain)
+ sctp_m_freem(outchain);
+ return (NULL);
+ }
+ if (can_take_mbuf) {
+ appendchain = clonechain;
+ } else {
+ if (!copy_by_ref &&
+ (sizeofcpy <= (int)((((SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count) - 1) * MLEN) + MHLEN)))) {
+ /* Its not in a cluster */
+ if (*endofchain == NULL) {
+ /* lets get a mbuf cluster */
+ if (outchain == NULL) {
+ /* This is the general case */
+ new_mbuf:
+ outchain = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_HEADER);
+ if (outchain == NULL) {
+ goto error_out;
+ }
+ SCTP_BUF_LEN(outchain) = 0;
+ *endofchain = outchain;
+ /* get the prepend space */
+ SCTP_BUF_RESV_UF(outchain, (SCTP_FIRST_MBUF_RESV+4));
+ } else {
+ /* We really should not get a NULL in endofchain */
+ /* find end */
+ m = outchain;
+ while (m) {
+ if (SCTP_BUF_NEXT(m) == NULL) {
+ *endofchain = m;
+ break;
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ /* sanity */
+ if (*endofchain == NULL) {
+ /* huh, TSNH XXX maybe we should panic */
+ sctp_m_freem(outchain);
+ goto new_mbuf;
+ }
+ }
+ /* get the new end of length */
+ len = (int)M_TRAILINGSPACE(*endofchain);
+ } else {
+ /* how much is left at the end? */
+ len = (int)M_TRAILINGSPACE(*endofchain);
+ }
+ /* Find the end of the data, for appending */
+ cp = (mtod((*endofchain), caddr_t) + SCTP_BUF_LEN((*endofchain)));
+
+ /* Now lets copy it out */
+ if (len >= sizeofcpy) {
+ /* It all fits, copy it in */
+ m_copydata(clonechain, 0, sizeofcpy, cp);
+ SCTP_BUF_LEN((*endofchain)) += sizeofcpy;
+ } else {
+ /* fill up the end of the chain */
+ if (len > 0) {
+ m_copydata(clonechain, 0, len, cp);
+ SCTP_BUF_LEN((*endofchain)) += len;
+ /* now we need another one */
+ sizeofcpy -= len;
+ }
+ m = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_HEADER);
+ if (m == NULL) {
+ /* We failed */
+ goto error_out;
+ }
+ SCTP_BUF_NEXT((*endofchain)) = m;
+ *endofchain = m;
+ cp = mtod((*endofchain), caddr_t);
+ m_copydata(clonechain, len, sizeofcpy, cp);
+ SCTP_BUF_LEN((*endofchain)) += sizeofcpy;
+ }
+ return (outchain);
+ } else {
+ /* copy the old fashion way */
+ appendchain = SCTP_M_COPYM(clonechain, 0, M_COPYALL, M_NOWAIT);
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(appendchain, SCTP_MBUF_ICOPY);
+ }
+#endif
+ }
+ }
+ if (appendchain == NULL) {
+ /* error */
+ if (outchain)
+ sctp_m_freem(outchain);
+ return (NULL);
+ }
+ if (outchain) {
+ /* tack on to the end */
+ if (*endofchain != NULL) {
+ SCTP_BUF_NEXT(((*endofchain))) = appendchain;
+ } else {
+ m = outchain;
+ while (m) {
+ if (SCTP_BUF_NEXT(m) == NULL) {
+ SCTP_BUF_NEXT(m) = appendchain;
+ break;
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ }
+ /*
+ * save off the end and update the end-chain
+ * position
+ */
+ m = appendchain;
+ while (m) {
+ if (SCTP_BUF_NEXT(m) == NULL) {
+ *endofchain = m;
+ break;
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ return (outchain);
+ } else {
+ /* save off the end and update the end-chain position */
+ m = appendchain;
+ while (m) {
+ if (SCTP_BUF_NEXT(m) == NULL) {
+ *endofchain = m;
+ break;
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ return (appendchain);
+ }
+}
+
+static int
+sctp_med_chunk_output(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int *num_out,
+ int *reason_code,
+ int control_only, int from_where,
+ struct timeval *now, int *now_filled,
+ uint32_t frag_point, int so_locked);
+
+static void
+sctp_sendall_iterator(struct sctp_inpcb *inp, struct sctp_tcb *stcb, void *ptr,
+ uint32_t val SCTP_UNUSED)
+{
+ struct sctp_copy_all *ca;
+ struct mbuf *m;
+ int ret = 0;
+ int added_control = 0;
+ int un_sent, do_chunk_output = 1;
+ struct sctp_association *asoc;
+ struct sctp_nets *net;
+
+ ca = (struct sctp_copy_all *)ptr;
+ if (ca->m == NULL) {
+ return;
+ }
+ if (ca->inp != inp) {
+ /* TSNH */
+ return;
+ }
+ if (ca->sndlen > 0) {
+ m = SCTP_M_COPYM(ca->m, 0, M_COPYALL, M_NOWAIT);
+ if (m == NULL) {
+ /* can't copy so we are done */
+ ca->cnt_failed++;
+ return;
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(m, SCTP_MBUF_ICOPY);
+ }
+#endif
+ } else {
+ m = NULL;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (stcb->asoc.alternate) {
+ net = stcb->asoc.alternate;
+ } else {
+ net = stcb->asoc.primary_destination;
+ }
+ if (ca->sndrcv.sinfo_flags & SCTP_ABORT) {
+ /* Abort this assoc with m as the user defined reason */
+ if (m != NULL) {
+ SCTP_BUF_PREPEND(m, sizeof(struct sctp_paramhdr), M_NOWAIT);
+ } else {
+ m = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr),
+ 0, M_NOWAIT, 1, MT_DATA);
+ SCTP_BUF_LEN(m) = sizeof(struct sctp_paramhdr);
+ }
+ if (m != NULL) {
+ struct sctp_paramhdr *ph;
+
+ ph = mtod(m, struct sctp_paramhdr *);
+ ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT);
+ ph->param_length = htons((uint16_t)(sizeof(struct sctp_paramhdr) + ca->sndlen));
+ }
+ /* We add one here to keep the assoc from
+ * dis-appearing on us.
+ */
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ sctp_abort_an_association(inp, stcb, m, false, SCTP_SO_NOT_LOCKED);
+ /* sctp_abort_an_association calls sctp_free_asoc()
+ * free association will NOT free it since we
+ * incremented the refcnt .. we do this to prevent
+ * it being freed and things getting tricky since
+ * we could end up (from free_asoc) calling inpcb_free
+ * which would get a recursive lock call to the
+ * iterator lock.. But as a consequence of that the
+ * stcb will return to us un-locked.. since free_asoc
+ * returns with either no TCB or the TCB unlocked, we
+ * must relock.. to unlock in the iterator timer :-0
+ */
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ goto no_chunk_output;
+ } else {
+ if (m != NULL) {
+ ret = sctp_msg_append(stcb, net, m, &ca->sndrcv);
+ }
+ asoc = &stcb->asoc;
+ if (ca->sndrcv.sinfo_flags & SCTP_EOF) {
+ /* shutdown this assoc */
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ sctp_is_there_unsent_data(stcb, SCTP_SO_NOT_LOCKED) == 0) {
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ goto abort_anyway;
+ }
+ /* there is nothing queued to send, so I'm done... */
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ /* only send SHUTDOWN the first time through */
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ sctp_send_shutdown(stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb,
+ net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
+ NULL);
+ added_control = 1;
+ do_chunk_output = 0;
+ }
+ } else {
+ /*
+ * we still got (or just got) data to send, so set
+ * SHUTDOWN_PENDING
+ */
+ /*
+ * XXX sockets draft says that SCTP_EOF should be
+ * sent with no data. currently, we will allow user
+ * data to be sent first and move to
+ * SHUTDOWN-PENDING
+ */
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING);
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ abort_anyway:
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "%s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ sctp_abort_an_association(stcb->sctp_ep, stcb,
+ op_err, false, SCTP_SO_NOT_LOCKED);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ goto no_chunk_output;
+ }
+ }
+ }
+ }
+ }
+ un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) +
+ (stcb->asoc.stream_queue_cnt * SCTP_DATA_CHUNK_OVERHEAD(stcb)));
+
+ if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) &&
+ (stcb->asoc.total_flight > 0) &&
+ (un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD))) {
+ do_chunk_output = 0;
+ }
+ if (do_chunk_output)
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_NOT_LOCKED);
+ else if (added_control) {
+ struct timeval now;
+ int num_out, reason, now_filled = 0;
+
+ (void)sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out,
+ &reason, 1, 1, &now, &now_filled,
+ sctp_get_frag_point(stcb),
+ SCTP_SO_NOT_LOCKED);
+ }
+ no_chunk_output:
+ if (ret) {
+ ca->cnt_failed++;
+ } else {
+ ca->cnt_sent++;
+ }
+}
+
+static void
+sctp_sendall_completes(void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_copy_all *ca;
+
+ ca = (struct sctp_copy_all *)ptr;
+ /*
+ * Do a notify here? Kacheong suggests that the notify be done at
+ * the send time.. so you would push up a notification if any send
+ * failed. Don't know if this is feasible since the only failures we
+ * have is "memory" related and if you cannot get an mbuf to send
+ * the data you surely can't get an mbuf to send up to notify the
+ * user you can't send the data :->
+ */
+
+ /* now free everything */
+ if (ca->inp) {
+ /* Lets clear the flag to allow others to run. */
+ SCTP_INP_WLOCK(ca->inp);
+ ca->inp->sctp_flags &= ~SCTP_PCB_FLAGS_SND_ITERATOR_UP;
+ SCTP_INP_WUNLOCK(ca->inp);
+ }
+ sctp_m_freem(ca->m);
+ SCTP_FREE(ca, SCTP_M_COPYAL);
+}
+
+static struct mbuf *
+sctp_copy_out_all(struct uio *uio, ssize_t len)
+{
+ struct mbuf *ret, *at;
+ ssize_t left, willcpy, cancpy, error;
+
+ ret = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_WAITOK, 1, MT_DATA);
+ if (ret == NULL) {
+ /* TSNH */
+ return (NULL);
+ }
+ left = len;
+ SCTP_BUF_LEN(ret) = 0;
+ /* save space for the data chunk header */
+ cancpy = (int)M_TRAILINGSPACE(ret);
+ willcpy = min(cancpy, left);
+ at = ret;
+ while (left > 0) {
+ /* Align data to the end */
+ error = uiomove(mtod(at, caddr_t), (int)willcpy, uio);
+ if (error) {
+ err_out_now:
+ sctp_m_freem(at);
+ return (NULL);
+ }
+ SCTP_BUF_LEN(at) = (int)willcpy;
+ SCTP_BUF_NEXT_PKT(at) = SCTP_BUF_NEXT(at) = 0;
+ left -= willcpy;
+ if (left > 0) {
+ SCTP_BUF_NEXT(at) = sctp_get_mbuf_for_msg((unsigned int)left, 0, M_WAITOK, 1, MT_DATA);
+ if (SCTP_BUF_NEXT(at) == NULL) {
+ goto err_out_now;
+ }
+ at = SCTP_BUF_NEXT(at);
+ SCTP_BUF_LEN(at) = 0;
+ cancpy = (int)M_TRAILINGSPACE(at);
+ willcpy = min(cancpy, left);
+ }
+ }
+ return (ret);
+}
+
+static int
+sctp_sendall(struct sctp_inpcb *inp, struct uio *uio, struct mbuf *m,
+ struct sctp_nonpad_sndrcvinfo *srcv)
+{
+ int ret;
+ struct sctp_copy_all *ca;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if (uio->uio_resid > SCTP_BASE_SYSCTL(sctp_sendall_limit)) {
+#else
+ if (uio_resid(uio) > SCTP_BASE_SYSCTL(sctp_sendall_limit)) {
+#endif
+#else
+ if (uio->uio_resid > (ssize_t)SCTP_BASE_SYSCTL(sctp_sendall_limit)) {
+#endif
+ /* You must not be larger than the limit! */
+ return (EMSGSIZE);
+ }
+ SCTP_MALLOC(ca, struct sctp_copy_all *, sizeof(struct sctp_copy_all),
+ SCTP_M_COPYAL);
+ if (ca == NULL) {
+ sctp_m_freem(m);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ memset(ca, 0, sizeof(struct sctp_copy_all));
+
+ ca->inp = inp;
+ if (srcv != NULL) {
+ memcpy(&ca->sndrcv, srcv, sizeof(struct sctp_nonpad_sndrcvinfo));
+ }
+
+ /* Serialize. */
+ SCTP_INP_WLOCK(inp);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SND_ITERATOR_UP) != 0) {
+ SCTP_INP_WUNLOCK(inp);
+ sctp_m_freem(m);
+ SCTP_FREE(ca, SCTP_M_COPYAL);
+ return (EBUSY);
+ }
+ inp->sctp_flags |= SCTP_PCB_FLAGS_SND_ITERATOR_UP;
+ SCTP_INP_WUNLOCK(inp);
+
+ /*
+ * take off the sendall flag, it would be bad if we failed to do
+ * this :-0
+ */
+ ca->sndrcv.sinfo_flags &= ~SCTP_SENDALL;
+ /* get length and mbuf chain */
+ if (uio) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ ca->sndlen = uio->uio_resid;
+#else
+ ca->sndlen = uio_resid(uio);
+#endif
+#else
+ ca->sndlen = uio->uio_resid;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(SCTP_INP_SO(inp), 0);
+#endif
+ ca->m = sctp_copy_out_all(uio, ca->sndlen);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(SCTP_INP_SO(inp), 0);
+#endif
+ if (ca->m == NULL) {
+ SCTP_FREE(ca, SCTP_M_COPYAL);
+ sctp_m_freem(m);
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_SND_ITERATOR_UP;
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ } else {
+ /* Gather the length of the send */
+ struct mbuf *mat;
+
+ ca->sndlen = 0;
+ for (mat = m; mat; mat = SCTP_BUF_NEXT(mat)) {
+ ca->sndlen += SCTP_BUF_LEN(mat);
+ }
+ }
+ ret = sctp_initiate_iterator(NULL, sctp_sendall_iterator, NULL,
+ SCTP_PCB_ANY_FLAGS, SCTP_PCB_ANY_FEATURES,
+ SCTP_ASOC_ANY_STATE,
+ (void *)ca, 0,
+ sctp_sendall_completes, inp, 1);
+ if (ret) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_SND_ITERATOR_UP;
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_FREE(ca, SCTP_M_COPYAL);
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ return (EFAULT);
+ }
+ return (0);
+}
+
+void
+sctp_toss_old_cookies(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+
+ TAILQ_FOREACH_SAFE(chk, &asoc->control_send_queue, sctp_next, nchk) {
+ if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) {
+ TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt--;
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ }
+}
+
+void
+sctp_toss_old_asconf(struct sctp_tcb *stcb)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_asconf_chunk *acp;
+
+ asoc = &stcb->asoc;
+ TAILQ_FOREACH_SAFE(chk, &asoc->asconf_send_queue, sctp_next, nchk) {
+ /* find SCTP_ASCONF chunk in queue */
+ if (chk->rec.chunk_id.id == SCTP_ASCONF) {
+ if (chk->data) {
+ acp = mtod(chk->data, struct sctp_asconf_chunk *);
+ if (SCTP_TSN_GT(ntohl(acp->serial_number), asoc->asconf_seq_out_acked)) {
+ /* Not Acked yet */
+ break;
+ }
+ }
+ TAILQ_REMOVE(&asoc->asconf_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt--;
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ }
+}
+
+static void
+sctp_clean_up_datalist(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_tmit_chunk **data_list,
+ int bundle_at,
+ struct sctp_nets *net)
+{
+ int i;
+ struct sctp_tmit_chunk *tp1;
+
+ for (i = 0; i < bundle_at; i++) {
+ /* off of the send queue */
+ TAILQ_REMOVE(&asoc->send_queue, data_list[i], sctp_next);
+ asoc->send_queue_cnt--;
+ if (i > 0) {
+ /*
+ * Any chunk NOT 0 you zap the time chunk 0 gets
+ * zapped or set based on if a RTO measurement is
+ * needed.
+ */
+ data_list[i]->do_rtt = 0;
+ }
+ /* record time */
+ data_list[i]->sent_rcv_time = net->last_sent_time;
+ data_list[i]->rec.data.cwnd_at_send = net->cwnd;
+ data_list[i]->rec.data.fast_retran_tsn = data_list[i]->rec.data.tsn;
+ if (data_list[i]->whoTo == NULL) {
+ data_list[i]->whoTo = net;
+ atomic_add_int(&net->ref_count, 1);
+ }
+ /* on to the sent queue */
+ tp1 = TAILQ_LAST(&asoc->sent_queue, sctpchunk_listhead);
+ if ((tp1) && SCTP_TSN_GT(tp1->rec.data.tsn, data_list[i]->rec.data.tsn)) {
+ struct sctp_tmit_chunk *tpp;
+
+ /* need to move back */
+ back_up_more:
+ tpp = TAILQ_PREV(tp1, sctpchunk_listhead, sctp_next);
+ if (tpp == NULL) {
+ TAILQ_INSERT_BEFORE(tp1, data_list[i], sctp_next);
+ goto all_done;
+ }
+ tp1 = tpp;
+ if (SCTP_TSN_GT(tp1->rec.data.tsn, data_list[i]->rec.data.tsn)) {
+ goto back_up_more;
+ }
+ TAILQ_INSERT_AFTER(&asoc->sent_queue, tp1, data_list[i], sctp_next);
+ } else {
+ TAILQ_INSERT_TAIL(&asoc->sent_queue,
+ data_list[i],
+ sctp_next);
+ }
+ all_done:
+ /* This does not lower until the cum-ack passes it */
+ asoc->sent_queue_cnt++;
+ if ((asoc->peers_rwnd <= 0) &&
+ (asoc->total_flight == 0) &&
+ (bundle_at == 1)) {
+ /* Mark the chunk as being a window probe */
+ SCTP_STAT_INCR(sctps_windowprobed);
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xC2, 3);
+#endif
+ data_list[i]->sent = SCTP_DATAGRAM_SENT;
+ data_list[i]->snd_count = 1;
+ data_list[i]->rec.data.chunk_was_revoked = 0;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_UP,
+ data_list[i]->whoTo->flight_size,
+ data_list[i]->book_size,
+ (uint32_t)(uintptr_t)data_list[i]->whoTo,
+ data_list[i]->rec.data.tsn);
+ }
+ sctp_flight_size_increase(data_list[i]);
+ sctp_total_flight_increase(stcb, data_list[i]);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_RWND_ENABLE) {
+ sctp_log_rwnd(SCTP_DECREASE_PEER_RWND,
+ asoc->peers_rwnd, data_list[i]->send_size, SCTP_BASE_SYSCTL(sctp_peer_chunk_oh));
+ }
+ asoc->peers_rwnd = sctp_sbspace_sub(asoc->peers_rwnd,
+ (uint32_t) (data_list[i]->send_size + SCTP_BASE_SYSCTL(sctp_peer_chunk_oh)));
+ if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ asoc->peers_rwnd = 0;
+ }
+ }
+ if (asoc->cc_functions.sctp_cwnd_update_packet_transmitted) {
+ (*asoc->cc_functions.sctp_cwnd_update_packet_transmitted)(stcb, net);
+ }
+}
+
+static void
+sctp_clean_up_ctl(struct sctp_tcb *stcb, struct sctp_association *asoc, int so_locked)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+
+ TAILQ_FOREACH_SAFE(chk, &asoc->control_send_queue, sctp_next, nchk) {
+ if ((chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_NR_SELECTIVE_ACK) || /* EY */
+ (chk->rec.chunk_id.id == SCTP_HEARTBEAT_REQUEST) ||
+ (chk->rec.chunk_id.id == SCTP_HEARTBEAT_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN) ||
+ (chk->rec.chunk_id.id == SCTP_SHUTDOWN) ||
+ (chk->rec.chunk_id.id == SCTP_SHUTDOWN_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_OPERATION_ERROR) ||
+ (chk->rec.chunk_id.id == SCTP_PACKET_DROPPED) ||
+ (chk->rec.chunk_id.id == SCTP_COOKIE_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_ECN_CWR) ||
+ (chk->rec.chunk_id.id == SCTP_ASCONF_ACK)) {
+ /* Stray chunks must be cleaned up */
+ clean_up_anyway:
+ TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt--;
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ if (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN) {
+ asoc->fwd_tsn_cnt--;
+ }
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ } else if (chk->rec.chunk_id.id == SCTP_STREAM_RESET) {
+ /* special handling, we must look into the param */
+ if (chk != asoc->str_reset) {
+ goto clean_up_anyway;
+ }
+ }
+ }
+}
+
+static uint32_t
+sctp_can_we_split_this(struct sctp_tcb *stcb, uint32_t length,
+ uint32_t space_left, uint32_t frag_point, int eeor_on)
+{
+ /* Make a decision on if I should split a
+ * msg into multiple parts. This is only asked of
+ * incomplete messages.
+ */
+ if (eeor_on) {
+ /* If we are doing EEOR we need to always send
+ * it if its the entire thing, since it might
+ * be all the guy is putting in the hopper.
+ */
+ if (space_left >= length) {
+ /*-
+ * If we have data outstanding,
+ * we get another chance when the sack
+ * arrives to transmit - wait for more data
+ */
+ if (stcb->asoc.total_flight == 0) {
+ /* If nothing is in flight, we zero
+ * the packet counter.
+ */
+ return (length);
+ }
+ return (0);
+
+ } else {
+ /* You can fill the rest */
+ return (space_left);
+ }
+ }
+ /*-
+ * For those strange folk that make the send buffer
+ * smaller than our fragmentation point, we can't
+ * get a full msg in so we have to allow splitting.
+ */
+ if (SCTP_SB_LIMIT_SND(stcb->sctp_socket) < frag_point) {
+ return (length);
+ }
+ if ((length <= space_left) ||
+ ((length - space_left) < SCTP_BASE_SYSCTL(sctp_min_residual))) {
+ /* Sub-optimal residual don't split in non-eeor mode. */
+ return (0);
+ }
+ /* If we reach here length is larger
+ * than the space_left. Do we wish to split
+ * it for the sake of packet putting together?
+ */
+ if (space_left >= min(SCTP_BASE_SYSCTL(sctp_min_split_point), frag_point)) {
+ /* Its ok to split it */
+ return (min(space_left, frag_point));
+ }
+ /* Nope, can't split */
+ return (0);
+}
+
+static uint32_t
+sctp_move_to_outqueue(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ struct sctp_stream_out *strq,
+ uint32_t space_left,
+ uint32_t frag_point,
+ int *giveup,
+ int eeor_mode,
+ int *bail,
+ int so_locked)
+{
+ /* Move from the stream to the send_queue keeping track of the total */
+ struct sctp_association *asoc;
+ struct sctp_stream_queue_pending *sp;
+ struct sctp_tmit_chunk *chk;
+ struct sctp_data_chunk *dchkh=NULL;
+ struct sctp_idata_chunk *ndchkh=NULL;
+ uint32_t to_move, length;
+ int leading;
+ uint8_t rcv_flags = 0;
+ uint8_t some_taken;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ asoc = &stcb->asoc;
+one_more_time:
+ /*sa_ignore FREED_MEMORY*/
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if (sp == NULL) {
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if (sp) {
+ goto one_more_time;
+ }
+ if ((sctp_is_feature_on(stcb->sctp_ep, SCTP_PCB_FLAGS_EXPLICIT_EOR) == 0) &&
+ (stcb->asoc.idata_supported == 0) &&
+ (strq->last_msg_incomplete)) {
+ SCTP_PRINTF("Huh? Stream:%d lm_in_c=%d but queue is NULL\n",
+ strq->sid,
+ strq->last_msg_incomplete);
+ strq->last_msg_incomplete = 0;
+ }
+ to_move = 0;
+ goto out_of;
+ }
+ if ((sp->msg_is_complete) && (sp->length == 0)) {
+ if (sp->sender_all_done) {
+ /* We are doing deferred cleanup. Last
+ * time through when we took all the data
+ * the sender_all_done was not set.
+ */
+ if ((sp->put_last_out == 0) && (sp->discard_rest == 0)) {
+ SCTP_PRINTF("Gak, put out entire msg with NO end!-1\n");
+ SCTP_PRINTF("sender_done:%d len:%d msg_comp:%d put_last_out:%d\n",
+ sp->sender_all_done,
+ sp->length,
+ sp->msg_is_complete,
+ sp->put_last_out);
+ }
+ atomic_subtract_int(&asoc->stream_queue_cnt, 1);
+ TAILQ_REMOVE(&strq->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, strq, sp);
+ if ((strq->state == SCTP_STREAM_RESET_PENDING) &&
+ (strq->chunks_on_queues == 0) &&
+ TAILQ_EMPTY(&strq->outqueue)) {
+ stcb->asoc.trigger_reset = 1;
+ }
+ if (sp->net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ }
+ sctp_free_a_strmoq(stcb, sp, so_locked);
+ /* back to get the next msg */
+ goto one_more_time;
+ } else {
+ /* sender just finished this but
+ * still holds a reference
+ */
+ *giveup = 1;
+ to_move = 0;
+ goto out_of;
+ }
+ } else {
+ /* is there some to get */
+ if (sp->length == 0) {
+ /* no */
+ *giveup = 1;
+ to_move = 0;
+ goto out_of;
+ } else if (sp->discard_rest) {
+ /* Whack down the size */
+ atomic_subtract_int(&stcb->asoc.total_output_queue_size, sp->length);
+ if ((stcb->sctp_socket != NULL) &&
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) {
+ SCTP_SB_DECR(&stcb->sctp_socket->so_snd, sp->length);
+ }
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ sp->tail_mbuf = NULL;
+ }
+ sp->length = 0;
+ sp->some_taken = 1;
+ *giveup = 1;
+ to_move = 0;
+ goto out_of;
+ }
+ }
+ some_taken = sp->some_taken;
+ length = sp->length;
+ if (sp->msg_is_complete) {
+ /* The message is complete */
+ to_move = min(length, frag_point);
+ if (to_move == length) {
+ /* All of it fits in the MTU */
+ if (sp->some_taken) {
+ rcv_flags |= SCTP_DATA_LAST_FRAG;
+ } else {
+ rcv_flags |= SCTP_DATA_NOT_FRAG;
+ }
+ sp->put_last_out = 1;
+ if (sp->sinfo_flags & SCTP_SACK_IMMEDIATELY) {
+ rcv_flags |= SCTP_DATA_SACK_IMMEDIATELY;
+ }
+ } else {
+ /* Not all of it fits, we fragment */
+ if (sp->some_taken == 0) {
+ rcv_flags |= SCTP_DATA_FIRST_FRAG;
+ }
+ sp->some_taken = 1;
+ }
+ } else {
+ to_move = sctp_can_we_split_this(stcb, length, space_left, frag_point, eeor_mode);
+ if (to_move > 0) {
+ if (to_move >= length) {
+ to_move = length;
+ }
+ if (sp->some_taken == 0) {
+ rcv_flags |= SCTP_DATA_FIRST_FRAG;
+ sp->some_taken = 1;
+ }
+ } else {
+ /* Nothing to take. */
+ *giveup = 1;
+ to_move = 0;
+ goto out_of;
+ }
+ }
+
+ /* If we reach here, we can copy out a chunk */
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* No chunk memory */
+ *giveup = 1;
+ to_move = 0;
+ goto out_of;
+ }
+ /* Setup for unordered if needed by looking
+ * at the user sent info flags.
+ */
+ if (sp->sinfo_flags & SCTP_UNORDERED) {
+ rcv_flags |= SCTP_DATA_UNORDERED;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_enable_sack_immediately) &&
+ (sp->sinfo_flags & SCTP_EOF) == SCTP_EOF) {
+ rcv_flags |= SCTP_DATA_SACK_IMMEDIATELY;
+ }
+ /* clear out the chunk before setting up */
+ memset(chk, 0, sizeof(*chk));
+ chk->rec.data.rcv_flags = rcv_flags;
+
+ if (to_move >= length) {
+ /* we think we can steal the whole thing */
+ if (to_move < sp->length) {
+ /* bail, it changed */
+ goto dont_do_it;
+ }
+ chk->data = sp->data;
+ chk->last_mbuf = sp->tail_mbuf;
+ /* register the stealing */
+ sp->data = sp->tail_mbuf = NULL;
+ } else {
+ struct mbuf *m;
+ dont_do_it:
+ chk->data = SCTP_M_COPYM(sp->data, 0, to_move, M_NOWAIT);
+ chk->last_mbuf = NULL;
+ if (chk->data == NULL) {
+ sp->some_taken = some_taken;
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ *bail = 1;
+ to_move = 0;
+ goto out_of;
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(chk->data, SCTP_MBUF_ICOPY);
+ }
+#endif
+ /* Pull off the data */
+ m_adj(sp->data, to_move);
+ /* Now lets work our way down and compact it */
+ m = sp->data;
+ while (m && (SCTP_BUF_LEN(m) == 0)) {
+ sp->data = SCTP_BUF_NEXT(m);
+ SCTP_BUF_NEXT(m) = NULL;
+ if (sp->tail_mbuf == m) {
+ /*-
+ * Freeing tail? TSNH since
+ * we supposedly were taking less
+ * than the sp->length.
+ */
+#ifdef INVARIANTS
+ panic("Huh, freeing tail? - TSNH");
+#else
+ SCTP_PRINTF("Huh, freeing tail? - TSNH\n");
+ sp->tail_mbuf = sp->data = NULL;
+ sp->length = 0;
+#endif
+ }
+ sctp_m_free(m);
+ m = sp->data;
+ }
+ }
+ if (SCTP_BUF_IS_EXTENDED(chk->data)) {
+ chk->copy_by_ref = 1;
+ } else {
+ chk->copy_by_ref = 0;
+ }
+ /* get last_mbuf and counts of mb usage
+ * This is ugly but hopefully its only one mbuf.
+ */
+ if (chk->last_mbuf == NULL) {
+ chk->last_mbuf = chk->data;
+ while (SCTP_BUF_NEXT(chk->last_mbuf) != NULL) {
+ chk->last_mbuf = SCTP_BUF_NEXT(chk->last_mbuf);
+ }
+ }
+
+ if (to_move > length) {
+ /*- This should not happen either
+ * since we always lower to_move to the size
+ * of sp->length if its larger.
+ */
+#ifdef INVARIANTS
+ panic("Huh, how can to_move be larger?");
+#else
+ SCTP_PRINTF("Huh, how can to_move be larger?\n");
+ sp->length = 0;
+#endif
+ } else {
+ atomic_subtract_int(&sp->length, to_move);
+ }
+ leading = SCTP_DATA_CHUNK_OVERHEAD(stcb);
+ if (M_LEADINGSPACE(chk->data) < leading) {
+ /* Not enough room for a chunk header, get some */
+ struct mbuf *m;
+
+ m = sctp_get_mbuf_for_msg(1, 0, M_NOWAIT, 1, MT_DATA);
+ if (m == NULL) {
+ /*
+ * we're in trouble here. _PREPEND below will free
+ * all the data if there is no leading space, so we
+ * must put the data back and restore.
+ */
+ if (sp->data == NULL) {
+ /* unsteal the data */
+ sp->data = chk->data;
+ sp->tail_mbuf = chk->last_mbuf;
+ } else {
+ struct mbuf *m_tmp;
+ /* reassemble the data */
+ m_tmp = sp->data;
+ sp->data = chk->data;
+ SCTP_BUF_NEXT(chk->last_mbuf) = m_tmp;
+ }
+ sp->some_taken = some_taken;
+ atomic_add_int(&sp->length, to_move);
+ chk->data = NULL;
+ *bail = 1;
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ to_move = 0;
+ goto out_of;
+ } else {
+ SCTP_BUF_LEN(m) = 0;
+ SCTP_BUF_NEXT(m) = chk->data;
+ chk->data = m;
+ M_ALIGN(chk->data, 4);
+ }
+ }
+ SCTP_BUF_PREPEND(chk->data, SCTP_DATA_CHUNK_OVERHEAD(stcb), M_NOWAIT);
+ if (chk->data == NULL) {
+ /* HELP, TSNH since we assured it would not above? */
+#ifdef INVARIANTS
+ panic("prepend fails HELP?");
+#else
+ SCTP_PRINTF("prepend fails HELP?\n");
+ sctp_free_a_chunk(stcb, chk, so_locked);
+#endif
+ *bail = 1;
+ to_move = 0;
+ goto out_of;
+ }
+ sctp_snd_sb_alloc(stcb, SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ chk->book_size = chk->send_size = (uint16_t)(to_move + SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ chk->book_size_scale = 0;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+
+ chk->flags = 0;
+ chk->asoc = &stcb->asoc;
+ chk->pad_inplace = 0;
+ chk->no_fr_allowed = 0;
+ if (stcb->asoc.idata_supported == 0) {
+ if (rcv_flags & SCTP_DATA_UNORDERED) {
+ /* Just use 0. The receiver ignores the values. */
+ chk->rec.data.mid = 0;
+ } else {
+ chk->rec.data.mid = strq->next_mid_ordered;
+ if (rcv_flags & SCTP_DATA_LAST_FRAG) {
+ strq->next_mid_ordered++;
+ }
+ }
+ } else {
+ if (rcv_flags & SCTP_DATA_UNORDERED) {
+ chk->rec.data.mid = strq->next_mid_unordered;
+ if (rcv_flags & SCTP_DATA_LAST_FRAG) {
+ strq->next_mid_unordered++;
+ }
+ } else {
+ chk->rec.data.mid = strq->next_mid_ordered;
+ if (rcv_flags & SCTP_DATA_LAST_FRAG) {
+ strq->next_mid_ordered++;
+ }
+ }
+ }
+ chk->rec.data.sid = sp->sid;
+ chk->rec.data.ppid = sp->ppid;
+ chk->rec.data.context = sp->context;
+ chk->rec.data.doing_fast_retransmit = 0;
+
+ chk->rec.data.timetodrop = sp->ts;
+ chk->flags = sp->act_flags;
+
+ if (sp->net) {
+ chk->whoTo = sp->net;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ } else
+ chk->whoTo = NULL;
+
+ if (sp->holds_key_ref) {
+ chk->auth_keyid = sp->auth_keyid;
+ sctp_auth_key_acquire(stcb, chk->auth_keyid);
+ chk->holds_key_ref = 1;
+ }
+ stcb->asoc.ss_functions.sctp_ss_scheduled(stcb, net, asoc, strq, to_move);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ chk->rec.data.tsn = atomic_fetchadd_int(&asoc->sending_seq, 1);
+#else
+ chk->rec.data.tsn = asoc->sending_seq++;
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_AT_SEND_2_OUTQ) {
+ sctp_misc_ints(SCTP_STRMOUT_LOG_SEND,
+ (uint32_t)(uintptr_t)stcb, sp->length,
+ (uint32_t)((chk->rec.data.sid << 16) | (0x0000ffff & chk->rec.data.mid)),
+ chk->rec.data.tsn);
+ }
+ if (stcb->asoc.idata_supported == 0) {
+ dchkh = mtod(chk->data, struct sctp_data_chunk *);
+ } else {
+ ndchkh = mtod(chk->data, struct sctp_idata_chunk *);
+ }
+ /*
+ * Put the rest of the things in place now. Size was done
+ * earlier in previous loop prior to padding.
+ */
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ if (asoc->tsn_out_at >= SCTP_TSN_LOG_SIZE) {
+ asoc->tsn_out_at = 0;
+ asoc->tsn_out_wrapped = 1;
+ }
+ asoc->out_tsnlog[asoc->tsn_out_at].tsn = chk->rec.data.tsn;
+ asoc->out_tsnlog[asoc->tsn_out_at].strm = chk->rec.data.sid;
+ asoc->out_tsnlog[asoc->tsn_out_at].seq = chk->rec.data.mid;
+ asoc->out_tsnlog[asoc->tsn_out_at].sz = chk->send_size;
+ asoc->out_tsnlog[asoc->tsn_out_at].flgs = chk->rec.data.rcv_flags;
+ asoc->out_tsnlog[asoc->tsn_out_at].stcb = (void *)stcb;
+ asoc->out_tsnlog[asoc->tsn_out_at].in_pos = asoc->tsn_out_at;
+ asoc->out_tsnlog[asoc->tsn_out_at].in_out = 2;
+ asoc->tsn_out_at++;
+#endif
+ if (stcb->asoc.idata_supported == 0) {
+ dchkh->ch.chunk_type = SCTP_DATA;
+ dchkh->ch.chunk_flags = chk->rec.data.rcv_flags;
+ dchkh->dp.tsn = htonl(chk->rec.data.tsn);
+ dchkh->dp.sid = htons(strq->sid);
+ dchkh->dp.ssn = htons((uint16_t)chk->rec.data.mid);
+ dchkh->dp.ppid = chk->rec.data.ppid;
+ dchkh->ch.chunk_length = htons(chk->send_size);
+ } else {
+ ndchkh->ch.chunk_type = SCTP_IDATA;
+ ndchkh->ch.chunk_flags = chk->rec.data.rcv_flags;
+ ndchkh->dp.tsn = htonl(chk->rec.data.tsn);
+ ndchkh->dp.sid = htons(strq->sid);
+ ndchkh->dp.reserved = htons(0);
+ ndchkh->dp.mid = htonl(chk->rec.data.mid);
+ if (sp->fsn == 0)
+ ndchkh->dp.ppid_fsn.ppid = chk->rec.data.ppid;
+ else
+ ndchkh->dp.ppid_fsn.fsn = htonl(sp->fsn);
+ sp->fsn++;
+ ndchkh->ch.chunk_length = htons(chk->send_size);
+ }
+ /* Now advance the chk->send_size by the actual pad needed. */
+ if (chk->send_size < SCTP_SIZE32(chk->book_size)) {
+ /* need a pad */
+ struct mbuf *lm;
+ int pads;
+
+ pads = SCTP_SIZE32(chk->book_size) - chk->send_size;
+ lm = sctp_pad_lastmbuf(chk->data, pads, chk->last_mbuf);
+ if (lm != NULL) {
+ chk->last_mbuf = lm;
+ chk->pad_inplace = 1;
+ }
+ chk->send_size += pads;
+ }
+ if (PR_SCTP_ENABLED(chk->flags)) {
+ asoc->pr_sctp_cnt++;
+ }
+ if (sp->msg_is_complete && (sp->length == 0) && (sp->sender_all_done)) {
+ /* All done pull and kill the message */
+ if (sp->put_last_out == 0) {
+ SCTP_PRINTF("Gak, put out entire msg with NO end!-2\n");
+ SCTP_PRINTF("sender_done:%d len:%d msg_comp:%d put_last_out:%d\n",
+ sp->sender_all_done,
+ sp->length,
+ sp->msg_is_complete,
+ sp->put_last_out);
+ }
+ atomic_subtract_int(&asoc->stream_queue_cnt, 1);
+ TAILQ_REMOVE(&strq->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, strq, sp);
+ if ((strq->state == SCTP_STREAM_RESET_PENDING) &&
+ (strq->chunks_on_queues == 0) &&
+ TAILQ_EMPTY(&strq->outqueue)) {
+ stcb->asoc.trigger_reset = 1;
+ }
+ if (sp->net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ }
+ sctp_free_a_strmoq(stcb, sp, so_locked);
+ }
+ asoc->chunks_on_out_queue++;
+ strq->chunks_on_queues++;
+ TAILQ_INSERT_TAIL(&asoc->send_queue, chk, sctp_next);
+ asoc->send_queue_cnt++;
+out_of:
+ return (to_move);
+}
+
+static void
+sctp_fill_outqueue(struct sctp_tcb *stcb, struct sctp_nets *net,
+ uint32_t frag_point, int eeor_mode, int *quit_now,
+ int so_locked)
+{
+ struct sctp_association *asoc;
+ struct sctp_stream_out *strq;
+ uint32_t space_left, moved, total_moved;
+ int bail, giveup;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ asoc = &stcb->asoc;
+ total_moved = 0;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ space_left = net->mtu - SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ space_left = net->mtu - SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ space_left = net->mtu - sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ /* TSNH */
+ space_left = net->mtu;
+ break;
+ }
+ /* Need an allowance for the data chunk header too */
+ space_left -= SCTP_DATA_CHUNK_OVERHEAD(stcb);
+
+ /* must make even word boundary */
+ space_left &= 0xfffffffc;
+ strq = stcb->asoc.ss_functions.sctp_ss_select_stream(stcb, net, asoc);
+ giveup = 0;
+ bail = 0;
+ while ((space_left > 0) && (strq != NULL)) {
+ moved = sctp_move_to_outqueue(stcb, net, strq, space_left,
+ frag_point, &giveup, eeor_mode,
+ &bail, so_locked);
+ if ((giveup != 0) || (bail != 0)) {
+ break;
+ }
+ strq = stcb->asoc.ss_functions.sctp_ss_select_stream(stcb, net, asoc);
+ total_moved += moved;
+ if (space_left >= moved) {
+ space_left -= moved;
+ } else {
+ space_left = 0;
+ }
+ if (space_left >= SCTP_DATA_CHUNK_OVERHEAD(stcb)) {
+ space_left -= SCTP_DATA_CHUNK_OVERHEAD(stcb);
+ } else {
+ space_left = 0;
+ }
+ space_left &= 0xfffffffc;
+ }
+ if (bail != 0)
+ *quit_now = 1;
+
+ stcb->asoc.ss_functions.sctp_ss_packet_done(stcb, net, asoc);
+
+ if (total_moved == 0) {
+ if ((stcb->asoc.sctp_cmt_on_off == 0) &&
+ (net == stcb->asoc.primary_destination)) {
+ /* ran dry for primary network net */
+ SCTP_STAT_INCR(sctps_primary_randry);
+ } else if (stcb->asoc.sctp_cmt_on_off > 0) {
+ /* ran dry with CMT on */
+ SCTP_STAT_INCR(sctps_cmt_randry);
+ }
+ }
+}
+
+void
+sctp_fix_ecn_echo(struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk;
+
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if (chk->rec.chunk_id.id == SCTP_ECN_ECHO) {
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ }
+ }
+}
+
+void
+sctp_move_chunks_from_net(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk;
+ struct sctp_stream_queue_pending *sp;
+ unsigned int i;
+
+ if (net == NULL) {
+ return;
+ }
+ asoc = &stcb->asoc;
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ TAILQ_FOREACH(sp, &stcb->asoc.strmout[i].outqueue, next) {
+ if (sp->net == net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ }
+ }
+ TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) {
+ if (chk->whoTo == net) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->whoTo = NULL;
+ }
+ }
+}
+
+int
+sctp_med_chunk_output(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int *num_out,
+ int *reason_code,
+ int control_only, int from_where,
+ struct timeval *now, int *now_filled,
+ uint32_t frag_point, int so_locked)
+{
+ /**
+ * Ok this is the generic chunk service queue. we must do the
+ * following:
+ * - Service the stream queue that is next, moving any
+ * message (note I must get a complete message i.e. FIRST/MIDDLE and
+ * LAST to the out queue in one pass) and assigning TSN's. This
+ * only applies though if the peer does not support NDATA. For NDATA
+ * chunks its ok to not send the entire message ;-)
+ * - Check to see if the cwnd/rwnd allows any output, if so we go ahead and
+ * formulate and send the low level chunks. Making sure to combine
+ * any control in the control chunk queue also.
+ */
+ struct sctp_nets *net, *start_at, *sack_goes_to = NULL, *old_start_at = NULL;
+ struct mbuf *outchain, *endoutchain;
+ struct sctp_tmit_chunk *chk, *nchk;
+
+ /* temp arrays for unlinking */
+ struct sctp_tmit_chunk *data_list[SCTP_MAX_DATA_BUNDLING];
+ int no_fragmentflg, error;
+ unsigned int max_rwnd_per_dest, max_send_per_dest;
+ int one_chunk, hbflag, skip_data_for_this_net;
+ int asconf, cookie, no_out_cnt;
+ int bundle_at, ctl_cnt, no_data_chunks, eeor_mode;
+ unsigned int mtu, r_mtu, omtu, mx_mtu, to_out;
+ int tsns_sent = 0;
+ uint32_t auth_offset;
+ struct sctp_auth_chunk *auth;
+ uint16_t auth_keyid;
+ int override_ok = 1;
+ int skip_fill_up = 0;
+ int data_auth_reqd = 0;
+ /* JRS 5/14/07 - Add flag for whether a heartbeat is sent to
+ the destination. */
+ int quit_now = 0;
+ bool use_zero_crc;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ *num_out = 0;
+ *reason_code = 0;
+ auth_keyid = stcb->asoc.authinfo.active_keyid;
+ if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED) ||
+ (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR))) {
+ eeor_mode = 1;
+ } else {
+ eeor_mode = 0;
+ }
+ ctl_cnt = no_out_cnt = asconf = cookie = 0;
+ /*
+ * First lets prime the pump. For each destination, if there is room
+ * in the flight size, attempt to pull an MTU's worth out of the
+ * stream queues into the general send_queue
+ */
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xC2, 2);
+#endif
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ hbflag = 0;
+ if (control_only)
+ no_data_chunks = 1;
+ else
+ no_data_chunks = 0;
+
+ /* Nothing to possible to send? */
+ if ((TAILQ_EMPTY(&asoc->control_send_queue) ||
+ (asoc->ctrl_queue_cnt == stcb->asoc.ecn_echo_cnt_onq)) &&
+ TAILQ_EMPTY(&asoc->asconf_send_queue) &&
+ TAILQ_EMPTY(&asoc->send_queue) &&
+ sctp_is_there_unsent_data(stcb, so_locked) == 0) {
+ nothing_to_send:
+ *reason_code = 9;
+ return (0);
+ }
+ if (asoc->peers_rwnd == 0) {
+ /* No room in peers rwnd */
+ *reason_code = 1;
+ if (asoc->total_flight > 0) {
+ /* we are allowed one chunk in flight */
+ no_data_chunks = 1;
+ }
+ }
+ if (stcb->asoc.ecn_echo_cnt_onq) {
+ /* Record where a sack goes, if any */
+ if (no_data_chunks &&
+ (asoc->ctrl_queue_cnt == stcb->asoc.ecn_echo_cnt_onq)) {
+ /* Nothing but ECNe to send - we don't do that */
+ goto nothing_to_send;
+ }
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if ((chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_NR_SELECTIVE_ACK)) {
+ sack_goes_to = chk->whoTo;
+ break;
+ }
+ }
+ }
+ max_rwnd_per_dest = ((asoc->peers_rwnd + asoc->total_flight) / asoc->numnets);
+ if (stcb->sctp_socket)
+ max_send_per_dest = SCTP_SB_LIMIT_SND(stcb->sctp_socket) / asoc->numnets;
+ else
+ max_send_per_dest = 0;
+ if (no_data_chunks == 0) {
+ /* How many non-directed chunks are there? */
+ TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) {
+ if (chk->whoTo == NULL) {
+ /* We already have non-directed
+ * chunks on the queue, no need
+ * to do a fill-up.
+ */
+ skip_fill_up = 1;
+ break;
+ }
+ }
+ }
+ if ((no_data_chunks == 0) &&
+ (skip_fill_up == 0) &&
+ (!stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, asoc))) {
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ /*
+ * This for loop we are in takes in
+ * each net, if its's got space in cwnd and
+ * has data sent to it (when CMT is off) then it
+ * calls sctp_fill_outqueue for the net. This gets
+ * data on the send queue for that network.
+ *
+ * In sctp_fill_outqueue TSN's are assigned and
+ * data is copied out of the stream buffers. Note
+ * mostly copy by reference (we hope).
+ */
+ net->window_probe = 0;
+ if ((net != stcb->asoc.alternate) &&
+ ((net->dest_state & SCTP_ADDR_PF) ||
+ ((net->dest_state & SCTP_ADDR_REACHABLE) == 0) ||
+ (net->dest_state & SCTP_ADDR_UNCONFIRMED))) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, 1,
+ SCTP_CWND_LOG_FILL_OUTQ_CALLED);
+ }
+ continue;
+ }
+ if ((stcb->asoc.cc_functions.sctp_cwnd_new_transmission_begins) &&
+ (net->flight_size == 0)) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_new_transmission_begins)(stcb, net);
+ }
+ if (net->flight_size >= net->cwnd) {
+ /* skip this network, no room - can't fill */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, 3,
+ SCTP_CWND_LOG_FILL_OUTQ_CALLED);
+ }
+ continue;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, 4, SCTP_CWND_LOG_FILL_OUTQ_CALLED);
+ }
+ sctp_fill_outqueue(stcb, net, frag_point, eeor_mode, &quit_now, so_locked);
+ if (quit_now) {
+ /* memory alloc failure */
+ no_data_chunks = 1;
+ break;
+ }
+ }
+ }
+ /* now service each destination and send out what we can for it */
+ /* Nothing to send? */
+ if (TAILQ_EMPTY(&asoc->control_send_queue) &&
+ TAILQ_EMPTY(&asoc->asconf_send_queue) &&
+ TAILQ_EMPTY(&asoc->send_queue)) {
+ *reason_code = 8;
+ return (0);
+ }
+
+ if (asoc->sctp_cmt_on_off > 0) {
+ /* get the last start point */
+ start_at = asoc->last_net_cmt_send_started;
+ if (start_at == NULL) {
+ /* null so to beginning */
+ start_at = TAILQ_FIRST(&asoc->nets);
+ } else {
+ start_at = TAILQ_NEXT(asoc->last_net_cmt_send_started, sctp_next);
+ if (start_at == NULL) {
+ start_at = TAILQ_FIRST(&asoc->nets);
+ }
+ }
+ asoc->last_net_cmt_send_started = start_at;
+ } else {
+ start_at = TAILQ_FIRST(&asoc->nets);
+ }
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if (chk->whoTo == NULL) {
+ if (asoc->alternate) {
+ chk->whoTo = asoc->alternate;
+ } else {
+ chk->whoTo = asoc->primary_destination;
+ }
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ }
+ }
+ old_start_at = NULL;
+again_one_more_time:
+ for (net = start_at; net != NULL; net = TAILQ_NEXT(net, sctp_next)) {
+ /* how much can we send? */
+ /* SCTPDBG("Examine for sending net:%x\n", (uint32_t)net); */
+ if (old_start_at && (old_start_at == net)) {
+ /* through list completely. */
+ break;
+ }
+ tsns_sent = 0xa;
+ if (TAILQ_EMPTY(&asoc->control_send_queue) &&
+ TAILQ_EMPTY(&asoc->asconf_send_queue) &&
+ (net->flight_size >= net->cwnd)) {
+ /* Nothing on control or asconf and flight is full, we can skip
+ * even in the CMT case.
+ */
+ continue;
+ }
+ bundle_at = 0;
+ endoutchain = outchain = NULL;
+ auth = NULL;
+ auth_offset = 0;
+ no_fragmentflg = 1;
+ one_chunk = 0;
+ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ skip_data_for_this_net = 1;
+ } else {
+ skip_data_for_this_net = 0;
+ }
+ switch (((struct sockaddr *)&net->ro._l_addr)->sa_family) {
+#ifdef INET
+ case AF_INET:
+ mtu = net->mtu - SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ mtu = net->mtu - SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ mtu = net->mtu - sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ /* TSNH */
+ mtu = net->mtu;
+ break;
+ }
+ mx_mtu = mtu;
+ to_out = 0;
+ if (mtu > asoc->peers_rwnd) {
+ if (asoc->total_flight > 0) {
+ /* We have a packet in flight somewhere */
+ r_mtu = asoc->peers_rwnd;
+ } else {
+ /* We are always allowed to send one MTU out */
+ one_chunk = 1;
+ r_mtu = mtu;
+ }
+ } else {
+ r_mtu = mtu;
+ }
+ error = 0;
+ /************************/
+ /* ASCONF transmission */
+ /************************/
+ /* Now first lets go through the asconf queue */
+ TAILQ_FOREACH_SAFE(chk, &asoc->asconf_send_queue, sctp_next, nchk) {
+ if (chk->rec.chunk_id.id != SCTP_ASCONF) {
+ continue;
+ }
+ if (chk->whoTo == NULL) {
+ if (asoc->alternate == NULL) {
+ if (asoc->primary_destination != net) {
+ break;
+ }
+ } else {
+ if (asoc->alternate != net) {
+ break;
+ }
+ }
+ } else {
+ if (chk->whoTo != net) {
+ break;
+ }
+ }
+ if (chk->data == NULL) {
+ break;
+ }
+ if (chk->sent != SCTP_DATAGRAM_UNSENT &&
+ chk->sent != SCTP_DATAGRAM_RESEND) {
+ break;
+ }
+ /*
+ * if no AUTH is yet included and this chunk
+ * requires it, make sure to account for it. We
+ * don't apply the size until the AUTH chunk is
+ * actually added below in case there is no room for
+ * this chunk. NOTE: we overload the use of "omtu"
+ * here
+ */
+ if ((auth == NULL) &&
+ sctp_auth_is_required_chunk(chk->rec.chunk_id.id,
+ stcb->asoc.peer_auth_chunks)) {
+ omtu = sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ } else
+ omtu = 0;
+ /* Here we do NOT factor the r_mtu */
+ if ((chk->send_size < (int)(mtu - omtu)) ||
+ (chk->flags & CHUNK_FLAGS_FRAGMENT_OK)) {
+ /*
+ * We probably should glom the mbuf chain
+ * from the chk->data for control but the
+ * problem is it becomes yet one more level
+ * of tracking to do if for some reason
+ * output fails. Then I have got to
+ * reconstruct the merged control chain.. el
+ * yucko.. for now we take the easy way and
+ * do the copy
+ */
+ /*
+ * Add an AUTH chunk, if chunk requires it
+ * save the offset into the chain for AUTH
+ */
+ if ((auth == NULL) &&
+ (sctp_auth_is_required_chunk(chk->rec.chunk_id.id,
+ stcb->asoc.peer_auth_chunks))) {
+ outchain = sctp_add_auth_chunk(outchain,
+ &endoutchain,
+ &auth,
+ &auth_offset,
+ stcb,
+ chk->rec.chunk_id.id);
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ }
+ outchain = sctp_copy_mbufchain(chk->data, outchain, &endoutchain,
+ (int)chk->rec.chunk_id.can_take_data,
+ chk->send_size, chk->copy_by_ref);
+ if (outchain == NULL) {
+ *reason_code = 8;
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ /* update our MTU size */
+ if (mtu > (chk->send_size + omtu))
+ mtu -= (chk->send_size + omtu);
+ else
+ mtu = 0;
+ to_out += (chk->send_size + omtu);
+ /* Do clear IP_DF ? */
+ if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) {
+ no_fragmentflg = 0;
+ }
+ if (chk->rec.chunk_id.can_take_data)
+ chk->data = NULL;
+ /*
+ * set hb flag since we can
+ * use these for RTO
+ */
+ hbflag = 1;
+ asconf = 1;
+ /*
+ * should sysctl this: don't
+ * bundle data with ASCONF
+ * since it requires AUTH
+ */
+ no_data_chunks = 1;
+ chk->sent = SCTP_DATAGRAM_SENT;
+ if (chk->whoTo == NULL) {
+ chk->whoTo = net;
+ atomic_add_int(&net->ref_count, 1);
+ }
+ chk->snd_count++;
+ if (mtu == 0) {
+ /*
+ * Ok we are out of room but we can
+ * output without effecting the
+ * flight size since this little guy
+ * is a control only packet.
+ */
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, net);
+ /*
+ * do NOT clear the asconf
+ * flag as it is used to do
+ * appropriate source address
+ * selection.
+ */
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(now);
+ *now_filled = 1;
+ }
+ net->last_sent_time = *now;
+ hbflag = 0;
+ if ((error = sctp_lowlevel_chunk_output(inp, stcb, net,
+ (struct sockaddr *)&net->ro._l_addr,
+ outchain, auth_offset, auth,
+ stcb->asoc.authinfo.active_keyid,
+ no_fragmentflg, 0, asconf,
+ inp->sctp_lport, stcb->rport,
+ htonl(stcb->asoc.peer_vtag),
+ net->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ false, so_locked))) {
+ /* error, we could not output */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (from_where == 0) {
+ SCTP_STAT_INCR(sctps_lowlevelerrusr);
+ }
+ if (error == ENOBUFS) {
+ asoc->ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ /* error, could not output */
+ if (error == EHOSTUNREACH) {
+ /*
+ * Destination went
+ * unreachable
+ * during this send
+ */
+ sctp_move_chunks_from_net(stcb, net);
+ }
+ asconf = 0;
+ *reason_code = 7;
+ break;
+ } else {
+ asoc->ifp_had_enobuf = 0;
+ }
+ /*
+ * increase the number we sent, if a
+ * cookie is sent we don't tell them
+ * any was sent out.
+ */
+ outchain = endoutchain = NULL;
+ auth = NULL;
+ auth_offset = 0;
+ asconf = 0;
+ if (!no_out_cnt)
+ *num_out += ctl_cnt;
+ /* recalc a clean slate and setup */
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ mtu = net->mtu - SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ mtu = net->mtu - SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ mtu = net->mtu - sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ /* TSNH */
+ mtu = net->mtu;
+ break;
+ }
+ to_out = 0;
+ no_fragmentflg = 1;
+ }
+ }
+ }
+ if (error != 0) {
+ /* try next net */
+ continue;
+ }
+ /************************/
+ /* Control transmission */
+ /************************/
+ /* Now first lets go through the control queue */
+ TAILQ_FOREACH_SAFE(chk, &asoc->control_send_queue, sctp_next, nchk) {
+ if ((sack_goes_to) &&
+ (chk->rec.chunk_id.id == SCTP_ECN_ECHO) &&
+ (chk->whoTo != sack_goes_to)) {
+ /*
+ * if we have a sack in queue, and we are looking at an
+ * ecn echo that is NOT queued to where the sack is going..
+ */
+ if (chk->whoTo == net) {
+ /* Don't transmit it to where its going (current net) */
+ continue;
+ } else if (sack_goes_to == net) {
+ /* But do transmit it to this address */
+ goto skip_net_check;
+ }
+ }
+ if (chk->whoTo == NULL) {
+ if (asoc->alternate == NULL) {
+ if (asoc->primary_destination != net) {
+ continue;
+ }
+ } else {
+ if (asoc->alternate != net) {
+ continue;
+ }
+ }
+ } else {
+ if (chk->whoTo != net) {
+ continue;
+ }
+ }
+ skip_net_check:
+ if (chk->data == NULL) {
+ continue;
+ }
+ if (chk->sent != SCTP_DATAGRAM_UNSENT) {
+ /*
+ * It must be unsent. Cookies and ASCONF's
+ * hang around but there timers will force
+ * when marked for resend.
+ */
+ continue;
+ }
+ /*
+ * if no AUTH is yet included and this chunk
+ * requires it, make sure to account for it. We
+ * don't apply the size until the AUTH chunk is
+ * actually added below in case there is no room for
+ * this chunk. NOTE: we overload the use of "omtu"
+ * here
+ */
+ if ((auth == NULL) &&
+ sctp_auth_is_required_chunk(chk->rec.chunk_id.id,
+ stcb->asoc.peer_auth_chunks)) {
+ omtu = sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ } else
+ omtu = 0;
+ /* Here we do NOT factor the r_mtu */
+ if ((chk->send_size <= (int)(mtu - omtu)) ||
+ (chk->flags & CHUNK_FLAGS_FRAGMENT_OK)) {
+ /*
+ * We probably should glom the mbuf chain
+ * from the chk->data for control but the
+ * problem is it becomes yet one more level
+ * of tracking to do if for some reason
+ * output fails. Then I have got to
+ * reconstruct the merged control chain.. el
+ * yucko.. for now we take the easy way and
+ * do the copy
+ */
+ /*
+ * Add an AUTH chunk, if chunk requires it
+ * save the offset into the chain for AUTH
+ */
+ if ((auth == NULL) &&
+ (sctp_auth_is_required_chunk(chk->rec.chunk_id.id,
+ stcb->asoc.peer_auth_chunks))) {
+ outchain = sctp_add_auth_chunk(outchain,
+ &endoutchain,
+ &auth,
+ &auth_offset,
+ stcb,
+ chk->rec.chunk_id.id);
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ }
+ outchain = sctp_copy_mbufchain(chk->data, outchain, &endoutchain,
+ (int)chk->rec.chunk_id.can_take_data,
+ chk->send_size, chk->copy_by_ref);
+ if (outchain == NULL) {
+ *reason_code = 8;
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ /* update our MTU size */
+ if (mtu > (chk->send_size + omtu))
+ mtu -= (chk->send_size + omtu);
+ else
+ mtu = 0;
+ to_out += (chk->send_size + omtu);
+ /* Do clear IP_DF ? */
+ if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) {
+ no_fragmentflg = 0;
+ }
+ if (chk->rec.chunk_id.can_take_data)
+ chk->data = NULL;
+ /* Mark things to be removed, if needed */
+ if ((chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_NR_SELECTIVE_ACK) || /* EY */
+ (chk->rec.chunk_id.id == SCTP_HEARTBEAT_REQUEST) ||
+ (chk->rec.chunk_id.id == SCTP_HEARTBEAT_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_SHUTDOWN) ||
+ (chk->rec.chunk_id.id == SCTP_SHUTDOWN_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_OPERATION_ERROR) ||
+ (chk->rec.chunk_id.id == SCTP_COOKIE_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_ECN_CWR) ||
+ (chk->rec.chunk_id.id == SCTP_PACKET_DROPPED) ||
+ (chk->rec.chunk_id.id == SCTP_ASCONF_ACK)) {
+ if (chk->rec.chunk_id.id == SCTP_HEARTBEAT_REQUEST) {
+ hbflag = 1;
+ }
+ /* remove these chunks at the end */
+ if ((chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) ||
+ (chk->rec.chunk_id.id == SCTP_NR_SELECTIVE_ACK)) {
+ /* turn off the timer */
+ if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV,
+ inp, stcb, NULL,
+ SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_1);
+ }
+ }
+ ctl_cnt++;
+ } else {
+ /*
+ * Other chunks, since they have
+ * timers running (i.e. COOKIE)
+ * we just "trust" that it
+ * gets sent or retransmitted.
+ */
+ ctl_cnt++;
+ if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) {
+ cookie = 1;
+ no_out_cnt = 1;
+ } else if (chk->rec.chunk_id.id == SCTP_ECN_ECHO) {
+ /*
+ * Increment ecne send count here
+ * this means we may be over-zealous in
+ * our counting if the send fails, but its
+ * the best place to do it (we used to do
+ * it in the queue of the chunk, but that did
+ * not tell how many times it was sent.
+ */
+ SCTP_STAT_INCR(sctps_sendecne);
+ }
+ chk->sent = SCTP_DATAGRAM_SENT;
+ if (chk->whoTo == NULL) {
+ chk->whoTo = net;
+ atomic_add_int(&net->ref_count, 1);
+ }
+ chk->snd_count++;
+ }
+ if (mtu == 0) {
+ /*
+ * Ok we are out of room but we can
+ * output without effecting the
+ * flight size since this little guy
+ * is a control only packet.
+ */
+ switch (asoc->snd_edmid) {
+ case SCTP_EDMID_LOWER_LAYER_DTLS:
+ use_zero_crc = true;
+ break;
+ default:
+ use_zero_crc = false;
+ break;
+ }
+ if (asconf) {
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, net);
+ use_zero_crc = false;
+ /*
+ * do NOT clear the asconf
+ * flag as it is used to do
+ * appropriate source address
+ * selection.
+ */
+ }
+ if (cookie) {
+ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net);
+ use_zero_crc = false;
+ cookie = 0;
+ }
+ /* Only HB or ASCONF advances time */
+ if (hbflag) {
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(now);
+ *now_filled = 1;
+ }
+ net->last_sent_time = *now;
+ hbflag = 0;
+ }
+ if ((error = sctp_lowlevel_chunk_output(inp, stcb, net,
+ (struct sockaddr *)&net->ro._l_addr,
+ outchain,
+ auth_offset, auth,
+ stcb->asoc.authinfo.active_keyid,
+ no_fragmentflg, 0, asconf,
+ inp->sctp_lport, stcb->rport,
+ htonl(stcb->asoc.peer_vtag),
+ net->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ use_zero_crc, so_locked))) {
+ /* error, we could not output */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (from_where == 0) {
+ SCTP_STAT_INCR(sctps_lowlevelerrusr);
+ }
+ if (error == ENOBUFS) {
+ asoc->ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ if (error == EHOSTUNREACH) {
+ /*
+ * Destination went
+ * unreachable
+ * during this send
+ */
+ sctp_move_chunks_from_net(stcb, net);
+ }
+ asconf = 0;
+ *reason_code = 7;
+ break;
+ } else {
+ asoc->ifp_had_enobuf = 0;
+ }
+ /*
+ * increase the number we sent, if a
+ * cookie is sent we don't tell them
+ * any was sent out.
+ */
+ outchain = endoutchain = NULL;
+ auth = NULL;
+ auth_offset = 0;
+ asconf = 0;
+ if (!no_out_cnt)
+ *num_out += ctl_cnt;
+ /* recalc a clean slate and setup */
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ mtu = net->mtu - SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ mtu = net->mtu - SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ mtu = net->mtu - sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ /* TSNH */
+ mtu = net->mtu;
+ break;
+ }
+ to_out = 0;
+ no_fragmentflg = 1;
+ }
+ }
+ }
+ if (error != 0) {
+ /* try next net */
+ continue;
+ }
+ /* JRI: if dest is in PF state, do not send data to it */
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ (net != stcb->asoc.alternate) &&
+ (net->dest_state & SCTP_ADDR_PF)) {
+ goto no_data_fill;
+ }
+ if (net->flight_size >= net->cwnd) {
+ goto no_data_fill;
+ }
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ (SCTP_BASE_SYSCTL(sctp_buffer_splitting) & SCTP_RECV_BUFFER_SPLITTING) &&
+ (net->flight_size > max_rwnd_per_dest)) {
+ goto no_data_fill;
+ }
+ /*
+ * We need a specific accounting for the usage of the
+ * send buffer. We also need to check the number of messages
+ * per net. For now, this is better than nothing and it
+ * disabled by default...
+ */
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ (SCTP_BASE_SYSCTL(sctp_buffer_splitting) & SCTP_SEND_BUFFER_SPLITTING) &&
+ (max_send_per_dest > 0) &&
+ (net->flight_size > max_send_per_dest)) {
+ goto no_data_fill;
+ }
+ /*********************/
+ /* Data transmission */
+ /*********************/
+ /*
+ * if AUTH for DATA is required and no AUTH has been added
+ * yet, account for this in the mtu now... if no data can be
+ * bundled, this adjustment won't matter anyways since the
+ * packet will be going out...
+ */
+ data_auth_reqd = sctp_auth_is_required_chunk(SCTP_DATA,
+ stcb->asoc.peer_auth_chunks);
+ if (data_auth_reqd && (auth == NULL)) {
+ mtu -= sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ }
+ /* now lets add any data within the MTU constraints */
+ switch (((struct sockaddr *)&net->ro._l_addr)->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (net->mtu > SCTP_MIN_V4_OVERHEAD)
+ omtu = net->mtu - SCTP_MIN_V4_OVERHEAD;
+ else
+ omtu = 0;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (net->mtu > SCTP_MIN_OVERHEAD)
+ omtu = net->mtu - SCTP_MIN_OVERHEAD;
+ else
+ omtu = 0;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (net->mtu > sizeof(struct sctphdr)) {
+ omtu = net->mtu - sizeof(struct sctphdr);
+ } else {
+ omtu = 0;
+ }
+ break;
+#endif
+ default:
+ /* TSNH */
+ omtu = 0;
+ break;
+ }
+ if ((((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) &&
+ (skip_data_for_this_net == 0)) ||
+ (cookie)) {
+ TAILQ_FOREACH_SAFE(chk, &asoc->send_queue, sctp_next, nchk) {
+ if (no_data_chunks) {
+ /* let only control go out */
+ *reason_code = 1;
+ break;
+ }
+ if (net->flight_size >= net->cwnd) {
+ /* skip this net, no room for data */
+ *reason_code = 2;
+ break;
+ }
+ if ((chk->whoTo != NULL) &&
+ (chk->whoTo != net)) {
+ /* Don't send the chunk on this net */
+ continue;
+ }
+
+ if (asoc->sctp_cmt_on_off == 0) {
+ if ((asoc->alternate) &&
+ (asoc->alternate != net) &&
+ (chk->whoTo == NULL)) {
+ continue;
+ } else if ((net != asoc->primary_destination) &&
+ (asoc->alternate == NULL) &&
+ (chk->whoTo == NULL)) {
+ continue;
+ }
+ }
+ if ((chk->send_size > omtu) && ((chk->flags & CHUNK_FLAGS_FRAGMENT_OK) == 0)) {
+ /*-
+ * strange, we have a chunk that is
+ * to big for its destination and
+ * yet no fragment ok flag.
+ * Something went wrong when the
+ * PMTU changed...we did not mark
+ * this chunk for some reason?? I
+ * will fix it here by letting IP
+ * fragment it for now and printing
+ * a warning. This really should not
+ * happen ...
+ */
+ SCTP_PRINTF("Warning chunk of %d bytes > mtu:%d and yet PMTU disc missed\n",
+ chk->send_size, mtu);
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_enable_sack_immediately) &&
+ (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) {
+ struct sctp_data_chunk *dchkh;
+
+ dchkh = mtod(chk->data, struct sctp_data_chunk *);
+ dchkh->ch.chunk_flags |= SCTP_DATA_SACK_IMMEDIATELY;
+ }
+ if (((chk->send_size <= mtu) && (chk->send_size <= r_mtu)) ||
+ ((chk->flags & CHUNK_FLAGS_FRAGMENT_OK) && (chk->send_size <= asoc->peers_rwnd))) {
+ /* ok we will add this one */
+
+ /*
+ * Add an AUTH chunk, if chunk
+ * requires it, save the offset into
+ * the chain for AUTH
+ */
+ if (data_auth_reqd) {
+ if (auth == NULL) {
+ outchain = sctp_add_auth_chunk(outchain,
+ &endoutchain,
+ &auth,
+ &auth_offset,
+ stcb,
+ SCTP_DATA);
+ auth_keyid = chk->auth_keyid;
+ override_ok = 0;
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ } else if (override_ok) {
+ /* use this data's keyid */
+ auth_keyid = chk->auth_keyid;
+ override_ok = 0;
+ } else if (auth_keyid != chk->auth_keyid) {
+ /* different keyid, so done bundling */
+ break;
+ }
+ }
+ outchain = sctp_copy_mbufchain(chk->data, outchain, &endoutchain, 0,
+ chk->send_size, chk->copy_by_ref);
+ if (outchain == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "No memory?\n");
+ if (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net);
+ }
+ *reason_code = 3;
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ /* update our MTU size */
+ /* Do clear IP_DF ? */
+ if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) {
+ no_fragmentflg = 0;
+ }
+ /* unsigned subtraction of mtu */
+ if (mtu > chk->send_size)
+ mtu -= chk->send_size;
+ else
+ mtu = 0;
+ /* unsigned subtraction of r_mtu */
+ if (r_mtu > chk->send_size)
+ r_mtu -= chk->send_size;
+ else
+ r_mtu = 0;
+
+ to_out += chk->send_size;
+ if ((to_out > mx_mtu) && no_fragmentflg) {
+#ifdef INVARIANTS
+ panic("Exceeding mtu of %d out size is %d", mx_mtu, to_out);
+#else
+ SCTP_PRINTF("Exceeding mtu of %d out size is %d\n",
+ mx_mtu, to_out);
+#endif
+ }
+ chk->window_probe = 0;
+ data_list[bundle_at++] = chk;
+ if (bundle_at >= SCTP_MAX_DATA_BUNDLING) {
+ break;
+ }
+ if (chk->sent == SCTP_DATAGRAM_UNSENT) {
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) {
+ SCTP_STAT_INCR_COUNTER64(sctps_outorderchunks);
+ } else {
+ SCTP_STAT_INCR_COUNTER64(sctps_outunorderchunks);
+ }
+ if (((chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) == SCTP_DATA_LAST_FRAG) &&
+ ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0))
+ /* Count number of user msg's that were fragmented
+ * we do this by counting when we see a LAST fragment
+ * only.
+ */
+ SCTP_STAT_INCR_COUNTER64(sctps_fragusrmsgs);
+ }
+ if ((mtu == 0) || (r_mtu == 0) || (one_chunk)) {
+ if ((one_chunk) && (stcb->asoc.total_flight == 0)) {
+ data_list[0]->window_probe = 1;
+ net->window_probe = 1;
+ }
+ break;
+ }
+ } else {
+ /*
+ * Must be sent in order of the
+ * TSN's (on a network)
+ */
+ break;
+ }
+ } /* for (chunk gather loop for this net) */
+ } /* if asoc.state OPEN */
+ no_data_fill:
+ /* Is there something to send for this destination? */
+ if (outchain) {
+ switch (asoc->snd_edmid) {
+ case SCTP_EDMID_LOWER_LAYER_DTLS:
+ use_zero_crc = true;
+ break;
+ default:
+ use_zero_crc = false;
+ break;
+ }
+ /* We may need to start a control timer or two */
+ if (asconf) {
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp,
+ stcb, net);
+ use_zero_crc = false;
+ /*
+ * do NOT clear the asconf flag as it is used
+ * to do appropriate source address selection.
+ */
+ }
+ if (cookie) {
+ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net);
+ use_zero_crc = false;
+ cookie = 0;
+ }
+ /* must start a send timer if data is being sent */
+ if (bundle_at && (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer))) {
+ /*
+ * no timer running on this destination
+ * restart it.
+ */
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net);
+ }
+ if (bundle_at || hbflag) {
+ /* For data/asconf and hb set time */
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(now);
+ *now_filled = 1;
+ }
+ net->last_sent_time = *now;
+ }
+ /* Now send it, if there is anything to send :> */
+ if ((error = sctp_lowlevel_chunk_output(inp,
+ stcb,
+ net,
+ (struct sockaddr *)&net->ro._l_addr,
+ outchain,
+ auth_offset,
+ auth,
+ auth_keyid,
+ no_fragmentflg,
+ bundle_at,
+ asconf,
+ inp->sctp_lport, stcb->rport,
+ htonl(stcb->asoc.peer_vtag),
+ net->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ use_zero_crc,
+ so_locked))) {
+ /* error, we could not output */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (from_where == 0) {
+ SCTP_STAT_INCR(sctps_lowlevelerrusr);
+ }
+ if (error == ENOBUFS) {
+ asoc->ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ if (error == EHOSTUNREACH) {
+ /*
+ * Destination went unreachable
+ * during this send
+ */
+ sctp_move_chunks_from_net(stcb, net);
+ }
+ asconf = 0;
+ *reason_code = 6;
+ /*-
+ * I add this line to be paranoid. As far as
+ * I can tell the continue, takes us back to
+ * the top of the for, but just to make sure
+ * I will reset these again here.
+ */
+ ctl_cnt = 0;
+ continue; /* This takes us back to the for() for the nets. */
+ } else {
+ asoc->ifp_had_enobuf = 0;
+ }
+ endoutchain = NULL;
+ auth = NULL;
+ auth_offset = 0;
+ asconf = 0;
+ if (!no_out_cnt) {
+ *num_out += (ctl_cnt + bundle_at);
+ }
+ if (bundle_at) {
+ /* setup for a RTO measurement */
+ tsns_sent = data_list[0]->rec.data.tsn;
+ /* fill time if not already filled */
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&asoc->time_last_sent);
+ *now_filled = 1;
+ *now = asoc->time_last_sent;
+ } else {
+ asoc->time_last_sent = *now;
+ }
+ if (net->rto_needed) {
+ data_list[0]->do_rtt = 1;
+ net->rto_needed = 0;
+ }
+ SCTP_STAT_INCR_BY(sctps_senddata, bundle_at);
+ sctp_clean_up_datalist(stcb, asoc, data_list, bundle_at, net);
+ }
+ if (one_chunk) {
+ break;
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, tsns_sent, SCTP_CWND_LOG_FROM_SEND);
+ }
+ }
+ if (old_start_at == NULL) {
+ old_start_at = start_at;
+ start_at = TAILQ_FIRST(&asoc->nets);
+ if (old_start_at)
+ goto again_one_more_time;
+ }
+
+ /*
+ * At the end there should be no NON timed chunks hanging on this
+ * queue.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, *num_out, SCTP_CWND_LOG_FROM_SEND);
+ }
+ if ((*num_out == 0) && (*reason_code == 0)) {
+ *reason_code = 4;
+ } else {
+ *reason_code = 5;
+ }
+ sctp_clean_up_ctl(stcb, asoc, so_locked);
+ return (0);
+}
+
+void
+sctp_queue_op_err(struct sctp_tcb *stcb, struct mbuf *op_err)
+{
+ /*-
+ * Prepend a OPERATIONAL_ERROR chunk header and put on the end of
+ * the control chunk queue.
+ */
+ struct sctp_chunkhdr *hdr;
+ struct sctp_tmit_chunk *chk;
+ struct mbuf *mat, *last_mbuf;
+ uint32_t chunk_length;
+ uint16_t padding_length;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_BUF_PREPEND(op_err, sizeof(struct sctp_chunkhdr), M_NOWAIT);
+ if (op_err == NULL) {
+ return;
+ }
+ last_mbuf = NULL;
+ chunk_length = 0;
+ for (mat = op_err; mat != NULL; mat = SCTP_BUF_NEXT(mat)) {
+ chunk_length += SCTP_BUF_LEN(mat);
+ if (SCTP_BUF_NEXT(mat) == NULL) {
+ last_mbuf = mat;
+ }
+ }
+ if (chunk_length > SCTP_MAX_CHUNK_LENGTH) {
+ sctp_m_freem(op_err);
+ return;
+ }
+ padding_length = chunk_length % 4;
+ if (padding_length != 0) {
+ padding_length = 4 - padding_length;
+ }
+ if (padding_length != 0) {
+ if (sctp_add_pad_tombuf(last_mbuf, padding_length) == NULL) {
+ sctp_m_freem(op_err);
+ return;
+ }
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(op_err);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_OPERATION_ERROR;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = 0;
+ chk->send_size = (uint16_t)chunk_length;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+ chk->data = op_err;
+ chk->whoTo = NULL;
+ hdr = mtod(op_err, struct sctp_chunkhdr *);
+ hdr->chunk_type = SCTP_OPERATION_ERROR;
+ hdr->chunk_flags = 0;
+ hdr->chunk_length = htons(chk->send_size);
+ TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+}
+
+int
+sctp_send_cookie_echo(struct mbuf *m,
+ int offset, int limit,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ /*-
+ * pull out the cookie and put it at the front of the control chunk
+ * queue.
+ */
+ int at;
+ struct mbuf *cookie;
+ struct sctp_paramhdr param, *phdr;
+ struct sctp_chunkhdr *hdr;
+ struct sctp_tmit_chunk *chk;
+ uint16_t ptype, plen;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ /* First find the cookie in the param area */
+ cookie = NULL;
+ at = offset + sizeof(struct sctp_init_chunk);
+ for (;;) {
+ phdr = sctp_get_next_param(m, at, &param, sizeof(param));
+ if (phdr == NULL) {
+ return (-3);
+ }
+ ptype = ntohs(phdr->param_type);
+ plen = ntohs(phdr->param_length);
+ if (plen < sizeof(struct sctp_paramhdr)) {
+ return (-6);
+ }
+ if (ptype == SCTP_STATE_COOKIE) {
+ int pad;
+
+ /* found the cookie */
+ if (at + plen > limit) {
+ return (-7);
+ }
+ cookie = SCTP_M_COPYM(m, at, plen, M_NOWAIT);
+ if (cookie == NULL) {
+ /* No memory */
+ return (-2);
+ }
+ if ((pad = (plen % 4)) > 0) {
+ pad = 4 - pad;
+ }
+ if (pad > 0) {
+ if (sctp_pad_lastmbuf(cookie, pad, NULL) == NULL) {
+ return (-8);
+ }
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(cookie, SCTP_MBUF_ICOPY);
+ }
+#endif
+ break;
+ }
+ at += SCTP_SIZE32(plen);
+ }
+ /* ok, we got the cookie lets change it into a cookie echo chunk */
+ /* first the change from param to cookie */
+ hdr = mtod(cookie, struct sctp_chunkhdr *);
+ hdr->chunk_type = SCTP_COOKIE_ECHO;
+ hdr->chunk_flags = 0;
+ /* get the chunk stuff now and place it in the FRONT of the queue */
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(cookie);
+ return (-5);
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_COOKIE_ECHO;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = CHUNK_FLAGS_FRAGMENT_OK;
+ chk->send_size = SCTP_SIZE32(plen);
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+ chk->data = cookie;
+ chk->whoTo = net;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ TAILQ_INSERT_HEAD(&chk->asoc->control_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+ return (0);
+}
+
+void
+sctp_send_heartbeat_ack(struct sctp_tcb *stcb,
+ struct mbuf *m,
+ int offset,
+ int chk_length,
+ struct sctp_nets *net)
+{
+ /*
+ * take a HB request and make it into a HB ack and send it.
+ */
+ struct mbuf *outchain;
+ struct sctp_chunkhdr *chdr;
+ struct sctp_tmit_chunk *chk;
+
+ if (net == NULL)
+ /* must have a net pointer */
+ return;
+
+ outchain = SCTP_M_COPYM(m, offset, chk_length, M_NOWAIT);
+ if (outchain == NULL) {
+ /* gak out of memory */
+ return;
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(outchain, SCTP_MBUF_ICOPY);
+ }
+#endif
+ chdr = mtod(outchain, struct sctp_chunkhdr *);
+ chdr->chunk_type = SCTP_HEARTBEAT_ACK;
+ chdr->chunk_flags = 0;
+ if (chk_length % 4 != 0) {
+ sctp_pad_lastmbuf(outchain, 4 - (chk_length % 4), NULL);
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(outchain);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_HEARTBEAT_ACK;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = 0;
+ chk->send_size = chk_length;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+ chk->data = outchain;
+ chk->whoTo = net;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+}
+
+void
+sctp_send_cookie_ack(struct sctp_tcb *stcb)
+{
+ /* formulate and queue a cookie-ack back to sender */
+ struct mbuf *cookie_ack;
+ struct sctp_chunkhdr *hdr;
+ struct sctp_tmit_chunk *chk;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ cookie_ack = sctp_get_mbuf_for_msg(sizeof(struct sctp_chunkhdr), 0, M_NOWAIT, 1, MT_HEADER);
+ if (cookie_ack == NULL) {
+ /* no mbuf's */
+ return;
+ }
+ SCTP_BUF_RESV_UF(cookie_ack, SCTP_MIN_OVERHEAD);
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(cookie_ack);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_COOKIE_ACK;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = 0;
+ chk->send_size = sizeof(struct sctp_chunkhdr);
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+ chk->data = cookie_ack;
+ if (chk->asoc->last_control_chunk_from != NULL) {
+ chk->whoTo = chk->asoc->last_control_chunk_from;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ } else {
+ chk->whoTo = NULL;
+ }
+ hdr = mtod(cookie_ack, struct sctp_chunkhdr *);
+ hdr->chunk_type = SCTP_COOKIE_ACK;
+ hdr->chunk_flags = 0;
+ hdr->chunk_length = htons(chk->send_size);
+ SCTP_BUF_LEN(cookie_ack) = chk->send_size;
+ TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+ return;
+}
+
+void
+sctp_send_shutdown_ack(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ /* formulate and queue a SHUTDOWN-ACK back to the sender */
+ struct mbuf *m_shutdown_ack;
+ struct sctp_shutdown_ack_chunk *ack_cp;
+ struct sctp_tmit_chunk *chk;
+
+ m_shutdown_ack = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_ack_chunk), 0, M_NOWAIT, 1, MT_HEADER);
+ if (m_shutdown_ack == NULL) {
+ /* no mbuf's */
+ return;
+ }
+ SCTP_BUF_RESV_UF(m_shutdown_ack, SCTP_MIN_OVERHEAD);
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(m_shutdown_ack);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_SHUTDOWN_ACK;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = 0;
+ chk->send_size = sizeof(struct sctp_chunkhdr);
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+ chk->data = m_shutdown_ack;
+ chk->whoTo = net;
+ if (chk->whoTo) {
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ }
+ ack_cp = mtod(m_shutdown_ack, struct sctp_shutdown_ack_chunk *);
+ ack_cp->ch.chunk_type = SCTP_SHUTDOWN_ACK;
+ ack_cp->ch.chunk_flags = 0;
+ ack_cp->ch.chunk_length = htons(chk->send_size);
+ SCTP_BUF_LEN(m_shutdown_ack) = chk->send_size;
+ TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+ return;
+}
+
+void
+sctp_send_shutdown(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ /* formulate and queue a SHUTDOWN to the sender */
+ struct mbuf *m_shutdown;
+ struct sctp_shutdown_chunk *shutdown_cp;
+ struct sctp_tmit_chunk *chk;
+
+ TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) {
+ if (chk->rec.chunk_id.id == SCTP_SHUTDOWN) {
+ /* We already have a SHUTDOWN queued. Reuse it. */
+ if (chk->whoTo) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->whoTo = NULL;
+ }
+ break;
+ }
+ }
+ if (chk == NULL) {
+ m_shutdown = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_chunk), 0, M_NOWAIT, 1, MT_HEADER);
+ if (m_shutdown == NULL) {
+ /* no mbuf's */
+ return;
+ }
+ SCTP_BUF_RESV_UF(m_shutdown, SCTP_MIN_OVERHEAD);
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(m_shutdown);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_SHUTDOWN;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = 0;
+ chk->send_size = sizeof(struct sctp_shutdown_chunk);
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+ chk->data = m_shutdown;
+ chk->whoTo = net;
+ if (chk->whoTo) {
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ }
+ shutdown_cp = mtod(m_shutdown, struct sctp_shutdown_chunk *);
+ shutdown_cp->ch.chunk_type = SCTP_SHUTDOWN;
+ shutdown_cp->ch.chunk_flags = 0;
+ shutdown_cp->ch.chunk_length = htons(chk->send_size);
+ shutdown_cp->cumulative_tsn_ack = htonl(stcb->asoc.cumulative_tsn);
+ SCTP_BUF_LEN(m_shutdown) = chk->send_size;
+ TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+ } else {
+ TAILQ_REMOVE(&stcb->asoc.control_send_queue, chk, sctp_next);
+ chk->whoTo = net;
+ if (chk->whoTo) {
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ }
+ shutdown_cp = mtod(chk->data, struct sctp_shutdown_chunk *);
+ shutdown_cp->cumulative_tsn_ack = htonl(stcb->asoc.cumulative_tsn);
+ TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next);
+ }
+ return;
+}
+
+void
+sctp_send_asconf(struct sctp_tcb *stcb, struct sctp_nets *net, int addr_locked)
+{
+ /*
+ * formulate and queue an ASCONF to the peer.
+ * ASCONF parameters should be queued on the assoc queue.
+ */
+ struct sctp_tmit_chunk *chk;
+ struct mbuf *m_asconf;
+ int len;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if ((!TAILQ_EMPTY(&stcb->asoc.asconf_send_queue)) &&
+ (!sctp_is_feature_on(stcb->sctp_ep, SCTP_PCB_FLAGS_MULTIPLE_ASCONFS))) {
+ /* can't send a new one if there is one in flight already */
+ return;
+ }
+
+ /* compose an ASCONF chunk, maximum length is PMTU */
+ m_asconf = sctp_compose_asconf(stcb, &len, addr_locked);
+ if (m_asconf == NULL) {
+ return;
+ }
+
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(m_asconf);
+ return;
+ }
+
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_ASCONF;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = CHUNK_FLAGS_FRAGMENT_OK;
+ chk->data = m_asconf;
+ chk->send_size = len;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+ chk->whoTo = net;
+ if (chk->whoTo) {
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ }
+ TAILQ_INSERT_TAIL(&chk->asoc->asconf_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+ return;
+}
+
+void
+sctp_send_asconf_ack(struct sctp_tcb *stcb)
+{
+ /*
+ * formulate and queue a asconf-ack back to sender.
+ * the asconf-ack must be stored in the tcb.
+ */
+ struct sctp_tmit_chunk *chk;
+ struct sctp_asconf_ack *ack, *latest_ack;
+ struct mbuf *m_ack;
+ struct sctp_nets *net = NULL;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ /* Get the latest ASCONF-ACK */
+ latest_ack = TAILQ_LAST(&stcb->asoc.asconf_ack_sent, sctp_asconf_ackhead);
+ if (latest_ack == NULL) {
+ return;
+ }
+ if (latest_ack->last_sent_to != NULL &&
+ latest_ack->last_sent_to == stcb->asoc.last_control_chunk_from) {
+ /* we're doing a retransmission */
+ net = sctp_find_alternate_net(stcb, stcb->asoc.last_control_chunk_from, 0);
+ if (net == NULL) {
+ /* no alternate */
+ if (stcb->asoc.last_control_chunk_from == NULL) {
+ if (stcb->asoc.alternate) {
+ net = stcb->asoc.alternate;
+ } else {
+ net = stcb->asoc.primary_destination;
+ }
+ } else {
+ net = stcb->asoc.last_control_chunk_from;
+ }
+ }
+ } else {
+ /* normal case */
+ if (stcb->asoc.last_control_chunk_from == NULL) {
+ if (stcb->asoc.alternate) {
+ net = stcb->asoc.alternate;
+ } else {
+ net = stcb->asoc.primary_destination;
+ }
+ } else {
+ net = stcb->asoc.last_control_chunk_from;
+ }
+ }
+ latest_ack->last_sent_to = net;
+
+ TAILQ_FOREACH(ack, &stcb->asoc.asconf_ack_sent, next) {
+ if (ack->data == NULL) {
+ continue;
+ }
+
+ /* copy the asconf_ack */
+ m_ack = SCTP_M_COPYM(ack->data, 0, M_COPYALL, M_NOWAIT);
+ if (m_ack == NULL) {
+ /* couldn't copy it */
+ return;
+ }
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(m_ack, SCTP_MBUF_ICOPY);
+ }
+#endif
+
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ if (m_ack)
+ sctp_m_freem(m_ack);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_ASCONF_ACK;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = CHUNK_FLAGS_FRAGMENT_OK;
+ chk->whoTo = net;
+ if (chk->whoTo) {
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ }
+ chk->data = m_ack;
+ chk->send_size = ack->len;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->asoc = &stcb->asoc;
+
+ TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next);
+ chk->asoc->ctrl_queue_cnt++;
+ }
+ return;
+}
+
+static int
+sctp_chunk_retransmission(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int *cnt_out, struct timeval *now, int *now_filled, int *fr_done, int so_locked)
+{
+ /*-
+ * send out one MTU of retransmission. If fast_retransmit is
+ * happening we ignore the cwnd. Otherwise we obey the cwnd and
+ * rwnd. For a Cookie or Asconf in the control chunk queue we
+ * retransmit them by themselves.
+ *
+ * For data chunks we will pick out the lowest TSN's in the sent_queue
+ * marked for resend and bundle them all together (up to a MTU of
+ * destination). The address to send to should have been
+ * selected/changed where the retransmission was marked (i.e. in FR
+ * or t3-timeout routines).
+ */
+ struct sctp_tmit_chunk *data_list[SCTP_MAX_DATA_BUNDLING];
+ struct sctp_tmit_chunk *chk, *fwd;
+ struct mbuf *m, *endofchain;
+ struct sctp_nets *net = NULL;
+ uint32_t tsns_sent = 0;
+ int no_fragmentflg, bundle_at;
+ unsigned int mtu;
+ int error, i, one_chunk, fwd_tsn, ctl_cnt, tmr_started;
+ struct sctp_auth_chunk *auth = NULL;
+ uint32_t auth_offset = 0;
+ uint16_t auth_keyid;
+ int override_ok = 1;
+ int data_auth_reqd = 0;
+ uint32_t dmtu = 0;
+ bool use_zero_crc;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ tmr_started = ctl_cnt = 0;
+ no_fragmentflg = 1;
+ fwd_tsn = 0;
+ *cnt_out = 0;
+ fwd = NULL;
+ endofchain = m = NULL;
+ auth_keyid = stcb->asoc.authinfo.active_keyid;
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xC3, 1);
+#endif
+ if ((TAILQ_EMPTY(&asoc->sent_queue)) &&
+ (TAILQ_EMPTY(&asoc->control_send_queue))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1,"SCTP hits empty queue with cnt set to %d?\n",
+ asoc->sent_queue_retran_cnt);
+ asoc->sent_queue_cnt = 0;
+ asoc->sent_queue_cnt_removeable = 0;
+ /* send back 0/0 so we enter normal transmission */
+ *cnt_out = 0;
+ return (0);
+ }
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if ((chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) ||
+ (chk->rec.chunk_id.id == SCTP_STREAM_RESET) ||
+ (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN)) {
+ if (chk->sent != SCTP_DATAGRAM_RESEND) {
+ continue;
+ }
+ if (chk->rec.chunk_id.id == SCTP_STREAM_RESET) {
+ if (chk != asoc->str_reset) {
+ /*
+ * not eligible for retran if its
+ * not ours
+ */
+ continue;
+ }
+ }
+ ctl_cnt++;
+ if (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN) {
+ fwd_tsn = 1;
+ }
+ /*
+ * Add an AUTH chunk, if chunk requires it save the
+ * offset into the chain for AUTH
+ */
+ if ((auth == NULL) &&
+ (sctp_auth_is_required_chunk(chk->rec.chunk_id.id,
+ stcb->asoc.peer_auth_chunks))) {
+ m = sctp_add_auth_chunk(m, &endofchain,
+ &auth, &auth_offset,
+ stcb,
+ chk->rec.chunk_id.id);
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ }
+ m = sctp_copy_mbufchain(chk->data, m, &endofchain, 0, chk->send_size, chk->copy_by_ref);
+ break;
+ }
+ }
+ one_chunk = 0;
+ /* do we have control chunks to retransmit? */
+ if (m != NULL) {
+ /* Start a timer no matter if we succeed or fail */
+ switch (asoc->snd_edmid) {
+ case SCTP_EDMID_LOWER_LAYER_DTLS:
+ use_zero_crc = true;
+ break;
+ default:
+ use_zero_crc = false;
+ break;
+ }
+ if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) {
+ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, chk->whoTo);
+ use_zero_crc = false;
+ } else if (chk->rec.chunk_id.id == SCTP_ASCONF) {
+ /* XXXMT: Can this happen? */
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, chk->whoTo);
+ use_zero_crc = false;
+ }
+ chk->snd_count++; /* update our count */
+ if ((error = sctp_lowlevel_chunk_output(inp, stcb, chk->whoTo,
+ (struct sockaddr *)&chk->whoTo->ro._l_addr, m,
+ auth_offset, auth, stcb->asoc.authinfo.active_keyid,
+ no_fragmentflg, 0, 0,
+ inp->sctp_lport, stcb->rport, htonl(stcb->asoc.peer_vtag),
+ chk->whoTo->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ use_zero_crc,
+ so_locked))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (error == ENOBUFS) {
+ asoc->ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ return (error);
+ } else {
+ asoc->ifp_had_enobuf = 0;
+ }
+ endofchain = NULL;
+ auth = NULL;
+ auth_offset = 0;
+ /*
+ * We don't want to mark the net->sent time here since this
+ * we use this for HB and retrans cannot measure RTT
+ */
+ /* (void)SCTP_GETTIME_TIMEVAL(&chk->whoTo->last_sent_time); */
+ *cnt_out += 1;
+ chk->sent = SCTP_DATAGRAM_SENT;
+ sctp_ucount_decr(stcb->asoc.sent_queue_retran_cnt);
+ if (fwd_tsn == 0) {
+ return (0);
+ } else {
+ /* Clean up the fwd-tsn list */
+ sctp_clean_up_ctl(stcb, asoc, so_locked);
+ return (0);
+ }
+ }
+ /*
+ * Ok, it is just data retransmission we need to do or that and a
+ * fwd-tsn with it all.
+ */
+ if (TAILQ_EMPTY(&asoc->sent_queue)) {
+ return (SCTP_RETRAN_DONE);
+ }
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT)) {
+ /* not yet open, resend the cookie and that is it */
+ return (1);
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(20, inp, stcb, NULL);
+#endif
+ data_auth_reqd = sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks);
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ if (chk->sent != SCTP_DATAGRAM_RESEND) {
+ /* No, not sent to this net or not ready for rtx */
+ continue;
+ }
+ if (chk->data == NULL) {
+ SCTP_PRINTF("TSN:%x chk->snd_count:%d chk->sent:%d can't retran - no data\n",
+ chk->rec.data.tsn, chk->snd_count, chk->sent);
+ continue;
+ }
+ if ((SCTP_BASE_SYSCTL(sctp_max_retran_chunk)) &&
+ (chk->snd_count >= SCTP_BASE_SYSCTL(sctp_max_retran_chunk))) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTP_SNPRINTF(msg, sizeof(msg), "TSN %8.8x retransmitted %d times, giving up",
+ chk->rec.data.tsn, chk->snd_count);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err,
+ false, so_locked);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (SCTP_RETRAN_EXIT);
+ }
+ /* pick up the net */
+ net = chk->whoTo;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ mtu = net->mtu - SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ mtu = net->mtu - SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ mtu = net->mtu - sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ /* TSNH */
+ mtu = net->mtu;
+ break;
+ }
+
+ if ((asoc->peers_rwnd < mtu) && (asoc->total_flight > 0)) {
+ /* No room in peers rwnd */
+ uint32_t tsn;
+
+ tsn = asoc->last_acked_seq + 1;
+ if (tsn == chk->rec.data.tsn) {
+ /*
+ * we make a special exception for this
+ * case. The peer has no rwnd but is missing
+ * the lowest chunk.. which is probably what
+ * is holding up the rwnd.
+ */
+ goto one_chunk_around;
+ }
+ return (1);
+ }
+ one_chunk_around:
+ if (asoc->peers_rwnd < mtu) {
+ one_chunk = 1;
+ if ((asoc->peers_rwnd == 0) &&
+ (asoc->total_flight == 0)) {
+ chk->window_probe = 1;
+ chk->whoTo->window_probe = 1;
+ }
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xC3, 2);
+#endif
+ bundle_at = 0;
+ m = NULL;
+ net->fast_retran_ip = 0;
+ if (chk->rec.data.doing_fast_retransmit == 0) {
+ /*
+ * if no FR in progress skip destination that have
+ * flight_size > cwnd.
+ */
+ if (net->flight_size >= net->cwnd) {
+ continue;
+ }
+ } else {
+ /*
+ * Mark the destination net to have FR recovery
+ * limits put on it.
+ */
+ *fr_done = 1;
+ net->fast_retran_ip = 1;
+ }
+
+ /*
+ * if no AUTH is yet included and this chunk requires it,
+ * make sure to account for it. We don't apply the size
+ * until the AUTH chunk is actually added below in case
+ * there is no room for this chunk.
+ */
+ if (data_auth_reqd && (auth == NULL)) {
+ dmtu = sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ } else
+ dmtu = 0;
+
+ if ((chk->send_size <= (mtu - dmtu)) ||
+ (chk->flags & CHUNK_FLAGS_FRAGMENT_OK)) {
+ /* ok we will add this one */
+ if (data_auth_reqd) {
+ if (auth == NULL) {
+ m = sctp_add_auth_chunk(m,
+ &endofchain,
+ &auth,
+ &auth_offset,
+ stcb,
+ SCTP_DATA);
+ auth_keyid = chk->auth_keyid;
+ override_ok = 0;
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ } else if (override_ok) {
+ auth_keyid = chk->auth_keyid;
+ override_ok = 0;
+ } else if (chk->auth_keyid != auth_keyid) {
+ /* different keyid, so done bundling */
+ break;
+ }
+ }
+ m = sctp_copy_mbufchain(chk->data, m, &endofchain, 0, chk->send_size, chk->copy_by_ref);
+ if (m == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ /* Do clear IP_DF ? */
+ if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) {
+ no_fragmentflg = 0;
+ }
+ /* update our MTU size */
+ if (mtu > (chk->send_size + dmtu))
+ mtu -= (chk->send_size + dmtu);
+ else
+ mtu = 0;
+ data_list[bundle_at++] = chk;
+ if (one_chunk && (asoc->total_flight <= 0)) {
+ SCTP_STAT_INCR(sctps_windowprobed);
+ }
+ }
+ if (one_chunk == 0) {
+ /*
+ * now are there anymore forward from chk to pick
+ * up?
+ */
+ for (fwd = TAILQ_NEXT(chk, sctp_next); fwd != NULL; fwd = TAILQ_NEXT(fwd, sctp_next)) {
+ if (fwd->sent != SCTP_DATAGRAM_RESEND) {
+ /* Nope, not for retran */
+ continue;
+ }
+ if (fwd->whoTo != net) {
+ /* Nope, not the net in question */
+ continue;
+ }
+ if (data_auth_reqd && (auth == NULL)) {
+ dmtu = sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ } else
+ dmtu = 0;
+ if (fwd->send_size <= (mtu - dmtu)) {
+ if (data_auth_reqd) {
+ if (auth == NULL) {
+ m = sctp_add_auth_chunk(m,
+ &endofchain,
+ &auth,
+ &auth_offset,
+ stcb,
+ SCTP_DATA);
+ auth_keyid = fwd->auth_keyid;
+ override_ok = 0;
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ } else if (override_ok) {
+ auth_keyid = fwd->auth_keyid;
+ override_ok = 0;
+ } else if (fwd->auth_keyid != auth_keyid) {
+ /* different keyid, so done bundling */
+ break;
+ }
+ }
+ m = sctp_copy_mbufchain(fwd->data, m, &endofchain, 0, fwd->send_size, fwd->copy_by_ref);
+ if (m == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ /* Do clear IP_DF ? */
+ if (fwd->flags & CHUNK_FLAGS_FRAGMENT_OK) {
+ no_fragmentflg = 0;
+ }
+ /* update our MTU size */
+ if (mtu > (fwd->send_size + dmtu))
+ mtu -= (fwd->send_size + dmtu);
+ else
+ mtu = 0;
+ data_list[bundle_at++] = fwd;
+ if (bundle_at >= SCTP_MAX_DATA_BUNDLING) {
+ break;
+ }
+ } else {
+ /* can't fit so we are done */
+ break;
+ }
+ }
+ }
+ /* Is there something to send for this destination? */
+ if (m) {
+ /*
+ * No matter if we fail/or succeed we should start a
+ * timer. A failure is like a lost IP packet :-)
+ */
+ if (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ /*
+ * no timer running on this destination
+ * restart it.
+ */
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net);
+ tmr_started = 1;
+ }
+ switch (asoc->snd_edmid) {
+ case SCTP_EDMID_LOWER_LAYER_DTLS:
+ use_zero_crc = true;
+ break;
+ default:
+ use_zero_crc = false;
+ break;
+ }
+ /* Now lets send it, if there is anything to send :> */
+ if ((error = sctp_lowlevel_chunk_output(inp, stcb, net,
+ (struct sockaddr *)&net->ro._l_addr, m,
+ auth_offset, auth, auth_keyid,
+ no_fragmentflg, 0, 0,
+ inp->sctp_lport, stcb->rport, htonl(stcb->asoc.peer_vtag),
+ net->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ use_zero_crc,
+ so_locked))) {
+ /* error, we could not output */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (error == ENOBUFS) {
+ asoc->ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ return (error);
+ } else {
+ asoc->ifp_had_enobuf = 0;
+ }
+ endofchain = NULL;
+ auth = NULL;
+ auth_offset = 0;
+ /* For HB's */
+ /*
+ * We don't want to mark the net->sent time here
+ * since this we use this for HB and retrans cannot
+ * measure RTT
+ */
+ /* (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time); */
+
+ /* For auto-close */
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&asoc->time_last_sent);
+ *now = asoc->time_last_sent;
+ *now_filled = 1;
+ } else {
+ asoc->time_last_sent = *now;
+ }
+ *cnt_out += bundle_at;
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xC4, bundle_at);
+#endif
+ if (bundle_at) {
+ tsns_sent = data_list[0]->rec.data.tsn;
+ }
+ for (i = 0; i < bundle_at; i++) {
+ SCTP_STAT_INCR(sctps_sendretransdata);
+ data_list[i]->sent = SCTP_DATAGRAM_SENT;
+ /*
+ * When we have a revoked data, and we
+ * retransmit it, then we clear the revoked
+ * flag since this flag dictates if we
+ * subtracted from the fs
+ */
+ if (data_list[i]->rec.data.chunk_was_revoked) {
+ /* Deflate the cwnd */
+ data_list[i]->whoTo->cwnd -= data_list[i]->book_size;
+ data_list[i]->rec.data.chunk_was_revoked = 0;
+ }
+ data_list[i]->snd_count++;
+ sctp_ucount_decr(asoc->sent_queue_retran_cnt);
+ /* record the time */
+ data_list[i]->sent_rcv_time = asoc->time_last_sent;
+ if (data_list[i]->book_size_scale) {
+ /*
+ * need to double the book size on
+ * this one
+ */
+ data_list[i]->book_size_scale = 0;
+ /* Since we double the booksize, we must
+ * also double the output queue size, since this
+ * get shrunk when we free by this amount.
+ */
+ atomic_add_int(&((asoc)->total_output_queue_size), data_list[i]->book_size);
+ data_list[i]->book_size *= 2;
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_RWND_ENABLE) {
+ sctp_log_rwnd(SCTP_DECREASE_PEER_RWND,
+ asoc->peers_rwnd, data_list[i]->send_size, SCTP_BASE_SYSCTL(sctp_peer_chunk_oh));
+ }
+ asoc->peers_rwnd = sctp_sbspace_sub(asoc->peers_rwnd,
+ (uint32_t) (data_list[i]->send_size +
+ SCTP_BASE_SYSCTL(sctp_peer_chunk_oh)));
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_UP_RSND,
+ data_list[i]->whoTo->flight_size,
+ data_list[i]->book_size,
+ (uint32_t)(uintptr_t)data_list[i]->whoTo,
+ data_list[i]->rec.data.tsn);
+ }
+ sctp_flight_size_increase(data_list[i]);
+ sctp_total_flight_increase(stcb, data_list[i]);
+ if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) {
+ /* SWS sender side engages */
+ asoc->peers_rwnd = 0;
+ }
+ if ((i == 0) &&
+ (data_list[i]->rec.data.doing_fast_retransmit)) {
+ SCTP_STAT_INCR(sctps_sendfastretrans);
+ if ((data_list[i] == TAILQ_FIRST(&asoc->sent_queue)) &&
+ (tmr_started == 0)) {
+ /*-
+ * ok we just fast-retrans'd
+ * the lowest TSN, i.e the
+ * first on the list. In
+ * this case we want to give
+ * some more time to get a
+ * SACK back without a
+ * t3-expiring.
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, inp, stcb, net,
+ SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_2);
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net);
+ }
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, net, tsns_sent, SCTP_CWND_LOG_FROM_RESEND);
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(21, inp, stcb, NULL);
+#endif
+ } else {
+ /* None will fit */
+ return (1);
+ }
+ if (asoc->sent_queue_retran_cnt <= 0) {
+ /* all done we have no more to retran */
+ asoc->sent_queue_retran_cnt = 0;
+ break;
+ }
+ if (one_chunk) {
+ /* No more room in rwnd */
+ return (1);
+ }
+ /* stop the for loop here. we sent out a packet */
+ break;
+ }
+ return (0);
+}
+
+static void
+sctp_timer_validation(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ struct sctp_nets *net;
+
+ /* Validate that a timer is running somewhere */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) {
+ /* Here is a timer */
+ return;
+ }
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ /* Gak, we did not have a timer somewhere */
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Deadlock avoided starting timer on a dest at retran\n");
+ if (asoc->alternate) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, asoc->alternate);
+ } else {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, asoc->primary_destination);
+ }
+ return;
+}
+
+void
+sctp_chunk_output(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ int from_where,
+ int so_locked)
+{
+ /*-
+ * Ok this is the generic chunk service queue. we must do the
+ * following:
+ * - See if there are retransmits pending, if so we must
+ * do these first.
+ * - Service the stream queue that is next, moving any
+ * message (note I must get a complete message i.e.
+ * FIRST/MIDDLE and LAST to the out queue in one pass) and assigning
+ * TSN's
+ * - Check to see if the cwnd/rwnd allows any output, if so we
+ * go ahead and formulate and send the low level chunks. Making sure
+ * to combine any control in the control chunk queue also.
+ */
+ struct sctp_association *asoc;
+ struct sctp_nets *net;
+ int error = 0, num_out, tot_out = 0, ret = 0, reason_code;
+ unsigned int burst_cnt = 0;
+ struct timeval now;
+ int now_filled = 0;
+ int nagle_on;
+ uint32_t frag_point = sctp_get_frag_point(stcb);
+ int un_sent = 0;
+ int fr_done;
+ unsigned int tot_frs = 0;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ asoc = &stcb->asoc;
+do_it_again:
+ /* The Nagle algorithm is only applied when handling a send call. */
+ if (from_where == SCTP_OUTPUT_FROM_USR_SEND) {
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NODELAY)) {
+ nagle_on = 0;
+ } else {
+ nagle_on = 1;
+ }
+ } else {
+ nagle_on = 0;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ un_sent = (stcb->asoc.total_output_queue_size - stcb->asoc.total_flight);
+
+ if ((un_sent <= 0) &&
+ (TAILQ_EMPTY(&asoc->control_send_queue)) &&
+ (TAILQ_EMPTY(&asoc->asconf_send_queue)) &&
+ (asoc->sent_queue_retran_cnt == 0) &&
+ (asoc->trigger_reset == 0)) {
+ /* Nothing to do unless there is something to be sent left */
+ return;
+ }
+ /* Do we have something to send, data or control AND
+ * a sack timer running, if so piggy-back the sack.
+ */
+ if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) {
+ sctp_send_sack(stcb, so_locked);
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_3);
+ }
+ while (asoc->sent_queue_retran_cnt) {
+ /*-
+ * Ok, it is retransmission time only, we send out only ONE
+ * packet with a single call off to the retran code.
+ */
+ if (from_where == SCTP_OUTPUT_FROM_COOKIE_ACK) {
+ /*-
+ * Special hook for handling cookies discarded
+ * by peer that carried data. Send cookie-ack only
+ * and then the next call with get the retran's.
+ */
+ (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1,
+ from_where,
+ &now, &now_filled, frag_point, so_locked);
+ return;
+ } else if (from_where != SCTP_OUTPUT_FROM_HB_TMR) {
+ /* if its not from a HB then do it */
+ fr_done = 0;
+ ret = sctp_chunk_retransmission(inp, stcb, asoc, &num_out, &now, &now_filled, &fr_done, so_locked);
+ if (fr_done) {
+ tot_frs++;
+ }
+ } else {
+ /*
+ * its from any other place, we don't allow retran
+ * output (only control)
+ */
+ ret = 1;
+ }
+ if (ret > 0) {
+ /* Can't send anymore */
+ /*-
+ * now lets push out control by calling med-level
+ * output once. this assures that we WILL send HB's
+ * if queued too.
+ */
+ (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1,
+ from_where,
+ &now, &now_filled, frag_point, so_locked);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(8, inp, stcb, NULL);
+#endif
+ sctp_timer_validation(inp, stcb, asoc);
+ return;
+ }
+ if (ret < 0) {
+ /*-
+ * The count was off.. retran is not happening so do
+ * the normal retransmission.
+ */
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(9, inp, stcb, NULL);
+#endif
+ if (ret == SCTP_RETRAN_EXIT) {
+ return;
+ }
+ break;
+ }
+ if (from_where == SCTP_OUTPUT_FROM_T3) {
+ /* Only one transmission allowed out of a timeout */
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(10, inp, stcb, NULL);
+#endif
+ /* Push out any control */
+ (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1, from_where,
+ &now, &now_filled, frag_point, so_locked);
+ return;
+ }
+ if ((asoc->fr_max_burst > 0) && (tot_frs >= asoc->fr_max_burst)) {
+ /* Hit FR burst limit */
+ return;
+ }
+ if ((num_out == 0) && (ret == 0)) {
+ /* No more retrans to send */
+ break;
+ }
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(12, inp, stcb, NULL);
+#endif
+ /* Check for bad destinations, if they exist move chunks around. */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ if ((net->dest_state & SCTP_ADDR_REACHABLE) == 0) {
+ /*-
+ * if possible move things off of this address we
+ * still may send below due to the dormant state but
+ * we try to find an alternate address to send to
+ * and if we have one we move all queued data on the
+ * out wheel to this alternate address.
+ */
+ if (net->ref_count > 1)
+ sctp_move_chunks_from_net(stcb, net);
+ } else {
+ /*-
+ * if ((asoc->sat_network) || (net->addr_is_local))
+ * { burst_limit = asoc->max_burst *
+ * SCTP_SAT_NETWORK_BURST_INCR; }
+ */
+ if (asoc->max_burst > 0) {
+ if (SCTP_BASE_SYSCTL(sctp_use_cwnd_based_maxburst)) {
+ if ((net->flight_size + (asoc->max_burst * net->mtu)) < net->cwnd) {
+ /* JRS - Use the congestion control given in the congestion control module */
+ asoc->cc_functions.sctp_cwnd_update_after_output(stcb, net, asoc->max_burst);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_MAXBURST_ENABLE) {
+ sctp_log_maxburst(stcb, net, 0, asoc->max_burst, SCTP_MAX_BURST_APPLIED);
+ }
+ SCTP_STAT_INCR(sctps_maxburstqueued);
+ }
+ net->fast_retran_ip = 0;
+ } else {
+ if (net->flight_size == 0) {
+ /* Should be decaying the cwnd here */
+ ;
+ }
+ }
+ }
+ }
+ }
+ burst_cnt = 0;
+ do {
+ error = sctp_med_chunk_output(inp, stcb, asoc, &num_out,
+ &reason_code, 0, from_where,
+ &now, &now_filled, frag_point, so_locked);
+ if (error) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Error %d was returned from med-c-op\n", error);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_MAXBURST_ENABLE) {
+ sctp_log_maxburst(stcb, asoc->primary_destination, error, burst_cnt, SCTP_MAX_BURST_ERROR_STOP);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, NULL, error, SCTP_SEND_NOW_COMPLETES);
+ sctp_log_cwnd(stcb, NULL, 0xdeadbeef, SCTP_SEND_NOW_COMPLETES);
+ }
+ break;
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "m-c-o put out %d\n", num_out);
+
+ tot_out += num_out;
+ burst_cnt++;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, NULL, num_out, SCTP_SEND_NOW_COMPLETES);
+ if (num_out == 0) {
+ sctp_log_cwnd(stcb, NULL, reason_code, SCTP_SEND_NOW_COMPLETES);
+ }
+ }
+ if (nagle_on) {
+ /*
+ * When the Nagle algorithm is used, look at how much
+ * is unsent, then if its smaller than an MTU and we
+ * have data in flight we stop, except if we are
+ * handling a fragmented user message.
+ */
+ un_sent = stcb->asoc.total_output_queue_size - stcb->asoc.total_flight;
+ if ((un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD)) &&
+ (stcb->asoc.total_flight > 0)) {
+/* && sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR))) {*/
+ break;
+ }
+ }
+ if (TAILQ_EMPTY(&asoc->control_send_queue) &&
+ TAILQ_EMPTY(&asoc->send_queue) &&
+ sctp_is_there_unsent_data(stcb, so_locked) == 0) {
+ /* Nothing left to send */
+ break;
+ }
+ if ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) <= 0) {
+ /* Nothing left to send */
+ break;
+ }
+ } while (num_out &&
+ ((asoc->max_burst == 0) ||
+ SCTP_BASE_SYSCTL(sctp_use_cwnd_based_maxburst) ||
+ (burst_cnt < asoc->max_burst)));
+
+ if (SCTP_BASE_SYSCTL(sctp_use_cwnd_based_maxburst) == 0) {
+ if ((asoc->max_burst > 0) && (burst_cnt >= asoc->max_burst)) {
+ SCTP_STAT_INCR(sctps_maxburstqueued);
+ asoc->burst_limit_applied = 1;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_MAXBURST_ENABLE) {
+ sctp_log_maxburst(stcb, asoc->primary_destination, 0, burst_cnt, SCTP_MAX_BURST_APPLIED);
+ }
+ } else {
+ asoc->burst_limit_applied = 0;
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ sctp_log_cwnd(stcb, NULL, tot_out, SCTP_SEND_NOW_COMPLETES);
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Ok, we have put out %d chunks\n",
+ tot_out);
+
+ /*-
+ * Now we need to clean up the control chunk chain if a ECNE is on
+ * it. It must be marked as UNSENT again so next call will continue
+ * to send it until such time that we get a CWR, to remove it.
+ */
+ if (stcb->asoc.ecn_echo_cnt_onq)
+ sctp_fix_ecn_echo(asoc);
+
+ if (stcb->asoc.trigger_reset) {
+ if (sctp_send_stream_reset_out_if_possible(stcb, so_locked) == 0) {
+ goto do_it_again;
+ }
+ }
+ return;
+}
+
+int
+sctp_output(
+ struct sctp_inpcb *inp,
+ struct mbuf *m,
+ struct sockaddr *addr,
+ struct mbuf *control,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *p,
+#elif defined(_WIN32) && !defined(__Userspace__)
+ PKTHREAD p,
+#else
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct proc *p SCTP_UNUSED,
+#else
+ struct proc *p,
+#endif
+#endif
+ int flags)
+{
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ }
+
+ if (inp->sctp_socket == NULL) {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ }
+ return (sctp_sosend(inp->sctp_socket,
+ addr,
+ (struct uio *)NULL,
+ m,
+ control,
+#if defined(__APPLE__) && !defined(__Userspace__)
+ flags
+#else
+ flags, p
+#endif
+ ));
+}
+
+void
+send_forward_tsn(struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk, *at, *tp1, *last;
+ struct sctp_forward_tsn_chunk *fwdtsn;
+ struct sctp_strseq *strseq;
+ struct sctp_strseq_mid *strseq_m;
+ uint32_t advance_peer_ack_point;
+ unsigned int cnt_of_space, i, ovh;
+ unsigned int space_needed;
+ unsigned int cnt_of_skipped = 0;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN) {
+ /* mark it to unsent */
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ /* Do we correct its output location? */
+ if (chk->whoTo) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->whoTo = NULL;
+ }
+ goto sctp_fill_in_rest;
+ }
+ }
+ /* Ok if we reach here we must build one */
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ return;
+ }
+ asoc->fwd_tsn_cnt++;
+ chk->copy_by_ref = 0;
+ /*
+ * We don't do the old thing here since
+ * this is used not for on-wire but to
+ * tell if we are sending a fwd-tsn by
+ * the stack during output. And if its
+ * a IFORWARD or a FORWARD it is a fwd-tsn.
+ */
+ chk->rec.chunk_id.id = SCTP_FORWARD_CUM_TSN;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = 0;
+ chk->asoc = asoc;
+ chk->whoTo = NULL;
+ chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (chk->data == NULL) {
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ TAILQ_INSERT_TAIL(&asoc->control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt++;
+sctp_fill_in_rest:
+ /*-
+ * Here we go through and fill out the part that deals with
+ * stream/seq of the ones we skip.
+ */
+ SCTP_BUF_LEN(chk->data) = 0;
+ TAILQ_FOREACH(at, &asoc->sent_queue, sctp_next) {
+ if ((at->sent != SCTP_FORWARD_TSN_SKIP) &&
+ (at->sent != SCTP_DATAGRAM_NR_ACKED)) {
+ /* no more to look at */
+ break;
+ }
+ if (!asoc->idata_supported && (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED)) {
+ /* We don't report these */
+ continue;
+ }
+ cnt_of_skipped++;
+ }
+ if (asoc->idata_supported) {
+ space_needed = (sizeof(struct sctp_forward_tsn_chunk) +
+ (cnt_of_skipped * sizeof(struct sctp_strseq_mid)));
+ } else {
+ space_needed = (sizeof(struct sctp_forward_tsn_chunk) +
+ (cnt_of_skipped * sizeof(struct sctp_strseq)));
+ }
+ cnt_of_space = (unsigned int)M_TRAILINGSPACE(chk->data);
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ovh = SCTP_MIN_OVERHEAD;
+ } else {
+ ovh = SCTP_MIN_V4_OVERHEAD;
+ }
+ if (cnt_of_space > (asoc->smallest_mtu - ovh)) {
+ /* trim to a mtu size */
+ cnt_of_space = asoc->smallest_mtu - ovh;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_TRY_ADVANCE) {
+ sctp_misc_ints(SCTP_FWD_TSN_CHECK,
+ 0xff, 0, cnt_of_skipped,
+ asoc->advanced_peer_ack_point);
+ }
+ advance_peer_ack_point = asoc->advanced_peer_ack_point;
+ if (cnt_of_space < space_needed) {
+ /*-
+ * ok we must trim down the chunk by lowering the
+ * advance peer ack point.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_TRY_ADVANCE) {
+ sctp_misc_ints(SCTP_FWD_TSN_CHECK,
+ 0xff, 0xff, cnt_of_space,
+ space_needed);
+ }
+ cnt_of_skipped = cnt_of_space - sizeof(struct sctp_forward_tsn_chunk);
+ if (asoc->idata_supported) {
+ cnt_of_skipped /= sizeof(struct sctp_strseq_mid);
+ } else {
+ cnt_of_skipped /= sizeof(struct sctp_strseq);
+ }
+ /*-
+ * Go through and find the TSN that will be the one
+ * we report.
+ */
+ at = TAILQ_FIRST(&asoc->sent_queue);
+ if (at != NULL) {
+ for (i = 0; i < cnt_of_skipped; i++) {
+ tp1 = TAILQ_NEXT(at, sctp_next);
+ if (tp1 == NULL) {
+ break;
+ }
+ at = tp1;
+ }
+ }
+ if (at && SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_TRY_ADVANCE) {
+ sctp_misc_ints(SCTP_FWD_TSN_CHECK,
+ 0xff, cnt_of_skipped, at->rec.data.tsn,
+ asoc->advanced_peer_ack_point);
+ }
+ last = at;
+ /*-
+ * last now points to last one I can report, update
+ * peer ack point
+ */
+ if (last) {
+ advance_peer_ack_point = last->rec.data.tsn;
+ }
+ if (asoc->idata_supported) {
+ space_needed = sizeof(struct sctp_forward_tsn_chunk) +
+ cnt_of_skipped * sizeof(struct sctp_strseq_mid);
+ } else {
+ space_needed = sizeof(struct sctp_forward_tsn_chunk) +
+ cnt_of_skipped * sizeof(struct sctp_strseq);
+ }
+ }
+ chk->send_size = space_needed;
+ /* Setup the chunk */
+ fwdtsn = mtod(chk->data, struct sctp_forward_tsn_chunk *);
+ fwdtsn->ch.chunk_length = htons(chk->send_size);
+ fwdtsn->ch.chunk_flags = 0;
+ if (asoc->idata_supported) {
+ fwdtsn->ch.chunk_type = SCTP_IFORWARD_CUM_TSN;
+ } else {
+ fwdtsn->ch.chunk_type = SCTP_FORWARD_CUM_TSN;
+ }
+ fwdtsn->new_cumulative_tsn = htonl(advance_peer_ack_point);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ fwdtsn++;
+ /*-
+ * Move pointer to after the fwdtsn and transfer to the
+ * strseq pointer.
+ */
+ if (asoc->idata_supported) {
+ strseq_m = (struct sctp_strseq_mid *)fwdtsn;
+ strseq = NULL;
+ } else {
+ strseq = (struct sctp_strseq *)fwdtsn;
+ strseq_m = NULL;
+ }
+ /*-
+ * Now populate the strseq list. This is done blindly
+ * without pulling out duplicate stream info. This is
+ * inefficient but won't harm the process since the peer will
+ * look at these in sequence and will thus release anything.
+ * It could mean we exceed the PMTU and chop off some that
+ * we could have included.. but this is unlikely (aka 1432/4
+ * would mean 300+ stream seq's would have to be reported in
+ * one FWD-TSN. With a bit of work we can later FIX this to
+ * optimize and pull out duplicates.. but it does add more
+ * overhead. So for now... not!
+ */
+ i = 0;
+ TAILQ_FOREACH(at, &asoc->sent_queue, sctp_next) {
+ if (i >= cnt_of_skipped) {
+ break;
+ }
+ if (!asoc->idata_supported && (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED)) {
+ /* We don't report these */
+ continue;
+ }
+ if (at->rec.data.tsn == advance_peer_ack_point) {
+ at->rec.data.fwd_tsn_cnt = 0;
+ }
+ if (asoc->idata_supported) {
+ strseq_m->sid = htons(at->rec.data.sid);
+ if (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED) {
+ strseq_m->flags = htons(PR_SCTP_UNORDERED_FLAG);
+ } else {
+ strseq_m->flags = 0;
+ }
+ strseq_m->mid = htonl(at->rec.data.mid);
+ strseq_m++;
+ } else {
+ strseq->sid = htons(at->rec.data.sid);
+ strseq->ssn = htons((uint16_t)at->rec.data.mid);
+ strseq++;
+ }
+ i++;
+ }
+ return;
+}
+
+void
+sctp_send_sack(struct sctp_tcb *stcb, int so_locked)
+{
+ /*-
+ * Queue up a SACK or NR-SACK in the control queue.
+ * We must first check to see if a SACK or NR-SACK is
+ * somehow on the control queue.
+ * If so, we will take and and remove the old one.
+ */
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk, *a_chk;
+ struct sctp_sack_chunk *sack;
+ struct sctp_nr_sack_chunk *nr_sack;
+ struct sctp_gap_ack_block *gap_descriptor;
+ const struct sack_track *selector;
+ int mergeable = 0;
+ int offset;
+ caddr_t limit;
+ uint32_t *dup;
+ int limit_reached = 0;
+ unsigned int i, siz, j;
+ unsigned int num_gap_blocks = 0, num_nr_gap_blocks = 0, space;
+ int num_dups = 0;
+ int space_req;
+ uint32_t highest_tsn;
+ uint8_t flags;
+ uint8_t type;
+ uint8_t tsn_map;
+
+ if (stcb->asoc.nrsack_supported == 1) {
+ type = SCTP_NR_SELECTIVE_ACK;
+ } else {
+ type = SCTP_SELECTIVE_ACK;
+ }
+ a_chk = NULL;
+ asoc = &stcb->asoc;
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (asoc->last_data_chunk_from == NULL) {
+ /* Hmm we never received anything */
+ return;
+ }
+ sctp_slide_mapping_arrays(stcb);
+ sctp_set_rwnd(stcb, asoc);
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if (chk->rec.chunk_id.id == type) {
+ /* Hmm, found a sack already on queue, remove it */
+ TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt--;
+ a_chk = chk;
+ if (a_chk->data) {
+ sctp_m_freem(a_chk->data);
+ a_chk->data = NULL;
+ }
+ if (a_chk->whoTo) {
+ sctp_free_remote_addr(a_chk->whoTo);
+ a_chk->whoTo = NULL;
+ }
+ break;
+ }
+ }
+ if (a_chk == NULL) {
+ sctp_alloc_a_chunk(stcb, a_chk);
+ if (a_chk == NULL) {
+ /* No memory so we drop the idea, and set a timer */
+ if (stcb->asoc.delayed_ack) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV,
+ stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_4);
+ sctp_timer_start(SCTP_TIMER_TYPE_RECV,
+ stcb->sctp_ep, stcb, NULL);
+ } else {
+ stcb->asoc.send_sack = 1;
+ }
+ return;
+ }
+ a_chk->copy_by_ref = 0;
+ a_chk->rec.chunk_id.id = type;
+ a_chk->rec.chunk_id.can_take_data = 1;
+ }
+ /* Clear our pkt counts */
+ asoc->data_pkts_seen = 0;
+
+ a_chk->flags = 0;
+ a_chk->asoc = asoc;
+ a_chk->snd_count = 0;
+ a_chk->send_size = 0; /* fill in later */
+ a_chk->sent = SCTP_DATAGRAM_UNSENT;
+ a_chk->whoTo = NULL;
+
+ if ((asoc->last_data_chunk_from->dest_state & SCTP_ADDR_REACHABLE) == 0) {
+ /*-
+ * Ok, the destination for the SACK is unreachable, lets see if
+ * we can select an alternate to asoc->last_data_chunk_from
+ */
+ a_chk->whoTo = sctp_find_alternate_net(stcb, asoc->last_data_chunk_from, 0);
+ if (a_chk->whoTo == NULL) {
+ /* Nope, no alternate */
+ a_chk->whoTo = asoc->last_data_chunk_from;
+ }
+ } else {
+ a_chk->whoTo = asoc->last_data_chunk_from;
+ }
+ if (a_chk->whoTo) {
+ atomic_add_int(&a_chk->whoTo->ref_count, 1);
+ }
+ if (SCTP_TSN_GT(asoc->highest_tsn_inside_map, asoc->highest_tsn_inside_nr_map)) {
+ highest_tsn = asoc->highest_tsn_inside_map;
+ } else {
+ highest_tsn = asoc->highest_tsn_inside_nr_map;
+ }
+ if (highest_tsn == asoc->cumulative_tsn) {
+ /* no gaps */
+ if (type == SCTP_SELECTIVE_ACK) {
+ space_req = sizeof(struct sctp_sack_chunk);
+ } else {
+ space_req = sizeof(struct sctp_nr_sack_chunk);
+ }
+ } else {
+ /* gaps get a cluster */
+ space_req = MCLBYTES;
+ }
+ /* Ok now lets formulate a MBUF with our sack */
+ a_chk->data = sctp_get_mbuf_for_msg(space_req, 0, M_NOWAIT, 1, MT_DATA);
+ if ((a_chk->data == NULL) ||
+ (a_chk->whoTo == NULL)) {
+ /* rats, no mbuf memory */
+ if (a_chk->data) {
+ /* was a problem with the destination */
+ sctp_m_freem(a_chk->data);
+ a_chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, a_chk, so_locked);
+ /* sa_ignore NO_NULL_CHK */
+ if (stcb->asoc.delayed_ack) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV,
+ stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_5);
+ sctp_timer_start(SCTP_TIMER_TYPE_RECV,
+ stcb->sctp_ep, stcb, NULL);
+ } else {
+ stcb->asoc.send_sack = 1;
+ }
+ return;
+ }
+ /* ok, lets go through and fill it in */
+ SCTP_BUF_RESV_UF(a_chk->data, SCTP_MIN_OVERHEAD);
+ space = (unsigned int)M_TRAILINGSPACE(a_chk->data);
+ if (space > (a_chk->whoTo->mtu - SCTP_MIN_OVERHEAD)) {
+ space = (a_chk->whoTo->mtu - SCTP_MIN_OVERHEAD);
+ }
+ limit = mtod(a_chk->data, caddr_t);
+ limit += space;
+
+ flags = 0;
+
+ if ((asoc->sctp_cmt_on_off > 0) &&
+ SCTP_BASE_SYSCTL(sctp_cmt_use_dac)) {
+ /*-
+ * CMT DAC algorithm: If 2 (i.e., 0x10) packets have been
+ * received, then set high bit to 1, else 0. Reset
+ * pkts_rcvd.
+ */
+ flags |= (asoc->cmt_dac_pkts_rcvd << 6);
+ asoc->cmt_dac_pkts_rcvd = 0;
+ }
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ stcb->asoc.cumack_logsnt[stcb->asoc.cumack_log_atsnt] = asoc->cumulative_tsn;
+ stcb->asoc.cumack_log_atsnt++;
+ if (stcb->asoc.cumack_log_atsnt >= SCTP_TSN_LOG_SIZE) {
+ stcb->asoc.cumack_log_atsnt = 0;
+ }
+#endif
+ /* reset the readers interpretation */
+ stcb->freed_by_sorcv_sincelast = 0;
+
+ if (type == SCTP_SELECTIVE_ACK) {
+ sack = mtod(a_chk->data, struct sctp_sack_chunk *);
+ nr_sack = NULL;
+ gap_descriptor = (struct sctp_gap_ack_block *)((caddr_t)sack + sizeof(struct sctp_sack_chunk));
+ if (highest_tsn > asoc->mapping_array_base_tsn) {
+ siz = (((highest_tsn - asoc->mapping_array_base_tsn) + 1) + 7) / 8;
+ } else {
+ siz = (((MAX_TSN - asoc->mapping_array_base_tsn) + 1) + highest_tsn + 7) / 8;
+ }
+ } else {
+ sack = NULL;
+ nr_sack = mtod(a_chk->data, struct sctp_nr_sack_chunk *);
+ gap_descriptor = (struct sctp_gap_ack_block *)((caddr_t)nr_sack + sizeof(struct sctp_nr_sack_chunk));
+ if (asoc->highest_tsn_inside_map > asoc->mapping_array_base_tsn) {
+ siz = (((asoc->highest_tsn_inside_map - asoc->mapping_array_base_tsn) + 1) + 7) / 8;
+ } else {
+ siz = (((MAX_TSN - asoc->mapping_array_base_tsn) + 1) + asoc->highest_tsn_inside_map + 7) / 8;
+ }
+ }
+
+ if (SCTP_TSN_GT(asoc->mapping_array_base_tsn, asoc->cumulative_tsn)) {
+ offset = 1;
+ } else {
+ offset = asoc->mapping_array_base_tsn - asoc->cumulative_tsn;
+ }
+ if (((type == SCTP_SELECTIVE_ACK) &&
+ SCTP_TSN_GT(highest_tsn, asoc->cumulative_tsn)) ||
+ ((type == SCTP_NR_SELECTIVE_ACK) &&
+ SCTP_TSN_GT(asoc->highest_tsn_inside_map, asoc->cumulative_tsn))) {
+ /* we have a gap .. maybe */
+ for (i = 0; i < siz; i++) {
+ tsn_map = asoc->mapping_array[i];
+ if (type == SCTP_SELECTIVE_ACK) {
+ tsn_map |= asoc->nr_mapping_array[i];
+ }
+ if (i == 0) {
+ /*
+ * Clear all bits corresponding to TSNs
+ * smaller or equal to the cumulative TSN.
+ */
+ tsn_map &= (~0U << (1 - offset));
+ }
+ selector = &sack_array[tsn_map];
+ if (mergeable && selector->right_edge) {
+ /*
+ * Backup, left and right edges were ok to
+ * merge.
+ */
+ num_gap_blocks--;
+ gap_descriptor--;
+ }
+ if (selector->num_entries == 0)
+ mergeable = 0;
+ else {
+ for (j = 0; j < selector->num_entries; j++) {
+ if (mergeable && selector->right_edge) {
+ /*
+ * do a merge by NOT setting
+ * the left side
+ */
+ mergeable = 0;
+ } else {
+ /*
+ * no merge, set the left
+ * side
+ */
+ mergeable = 0;
+ gap_descriptor->start = htons((selector->gaps[j].start + offset));
+ }
+ gap_descriptor->end = htons((selector->gaps[j].end + offset));
+ num_gap_blocks++;
+ gap_descriptor++;
+ if (((caddr_t)gap_descriptor + sizeof(struct sctp_gap_ack_block)) > limit) {
+ /* no more room */
+ limit_reached = 1;
+ break;
+ }
+ }
+ if (selector->left_edge) {
+ mergeable = 1;
+ }
+ }
+ if (limit_reached) {
+ /* Reached the limit stop */
+ break;
+ }
+ offset += 8;
+ }
+ }
+ if ((type == SCTP_NR_SELECTIVE_ACK) &&
+ (limit_reached == 0)) {
+ mergeable = 0;
+
+ if (asoc->highest_tsn_inside_nr_map > asoc->mapping_array_base_tsn) {
+ siz = (((asoc->highest_tsn_inside_nr_map - asoc->mapping_array_base_tsn) + 1) + 7) / 8;
+ } else {
+ siz = (((MAX_TSN - asoc->mapping_array_base_tsn) + 1) + asoc->highest_tsn_inside_nr_map + 7) / 8;
+ }
+
+ if (SCTP_TSN_GT(asoc->mapping_array_base_tsn, asoc->cumulative_tsn)) {
+ offset = 1;
+ } else {
+ offset = asoc->mapping_array_base_tsn - asoc->cumulative_tsn;
+ }
+ if (SCTP_TSN_GT(asoc->highest_tsn_inside_nr_map, asoc->cumulative_tsn)) {
+ /* we have a gap .. maybe */
+ for (i = 0; i < siz; i++) {
+ tsn_map = asoc->nr_mapping_array[i];
+ if (i == 0) {
+ /*
+ * Clear all bits corresponding to TSNs
+ * smaller or equal to the cumulative TSN.
+ */
+ tsn_map &= (~0U << (1 - offset));
+ }
+ selector = &sack_array[tsn_map];
+ if (mergeable && selector->right_edge) {
+ /*
+ * Backup, left and right edges were ok to
+ * merge.
+ */
+ num_nr_gap_blocks--;
+ gap_descriptor--;
+ }
+ if (selector->num_entries == 0)
+ mergeable = 0;
+ else {
+ for (j = 0; j < selector->num_entries; j++) {
+ if (mergeable && selector->right_edge) {
+ /*
+ * do a merge by NOT setting
+ * the left side
+ */
+ mergeable = 0;
+ } else {
+ /*
+ * no merge, set the left
+ * side
+ */
+ mergeable = 0;
+ gap_descriptor->start = htons((selector->gaps[j].start + offset));
+ }
+ gap_descriptor->end = htons((selector->gaps[j].end + offset));
+ num_nr_gap_blocks++;
+ gap_descriptor++;
+ if (((caddr_t)gap_descriptor + sizeof(struct sctp_gap_ack_block)) > limit) {
+ /* no more room */
+ limit_reached = 1;
+ break;
+ }
+ }
+ if (selector->left_edge) {
+ mergeable = 1;
+ }
+ }
+ if (limit_reached) {
+ /* Reached the limit stop */
+ break;
+ }
+ offset += 8;
+ }
+ }
+ }
+ /* now we must add any dups we are going to report. */
+ if ((limit_reached == 0) && (asoc->numduptsns)) {
+ dup = (uint32_t *) gap_descriptor;
+ for (i = 0; i < asoc->numduptsns; i++) {
+ *dup = htonl(asoc->dup_tsns[i]);
+ dup++;
+ num_dups++;
+ if (((caddr_t)dup + sizeof(uint32_t)) > limit) {
+ /* no more room */
+ break;
+ }
+ }
+ asoc->numduptsns = 0;
+ }
+ /*
+ * now that the chunk is prepared queue it to the control chunk
+ * queue.
+ */
+ if (type == SCTP_SELECTIVE_ACK) {
+ a_chk->send_size = (uint16_t)(sizeof(struct sctp_sack_chunk) +
+ (num_gap_blocks + num_nr_gap_blocks) * sizeof(struct sctp_gap_ack_block) +
+ num_dups * sizeof(int32_t));
+ SCTP_BUF_LEN(a_chk->data) = a_chk->send_size;
+ sack->sack.cum_tsn_ack = htonl(asoc->cumulative_tsn);
+ sack->sack.a_rwnd = htonl(asoc->my_rwnd);
+ sack->sack.num_gap_ack_blks = htons(num_gap_blocks);
+ sack->sack.num_dup_tsns = htons(num_dups);
+ sack->ch.chunk_type = type;
+ sack->ch.chunk_flags = flags;
+ sack->ch.chunk_length = htons(a_chk->send_size);
+ } else {
+ a_chk->send_size = (uint16_t)(sizeof(struct sctp_nr_sack_chunk) +
+ (num_gap_blocks + num_nr_gap_blocks) * sizeof(struct sctp_gap_ack_block) +
+ num_dups * sizeof(int32_t));
+ SCTP_BUF_LEN(a_chk->data) = a_chk->send_size;
+ nr_sack->nr_sack.cum_tsn_ack = htonl(asoc->cumulative_tsn);
+ nr_sack->nr_sack.a_rwnd = htonl(asoc->my_rwnd);
+ nr_sack->nr_sack.num_gap_ack_blks = htons(num_gap_blocks);
+ nr_sack->nr_sack.num_nr_gap_ack_blks = htons(num_nr_gap_blocks);
+ nr_sack->nr_sack.num_dup_tsns = htons(num_dups);
+ nr_sack->nr_sack.reserved = 0;
+ nr_sack->ch.chunk_type = type;
+ nr_sack->ch.chunk_flags = flags;
+ nr_sack->ch.chunk_length = htons(a_chk->send_size);
+ }
+ TAILQ_INSERT_TAIL(&asoc->control_send_queue, a_chk, sctp_next);
+ asoc->my_last_reported_rwnd = asoc->my_rwnd;
+ asoc->ctrl_queue_cnt++;
+ asoc->send_sack = 0;
+ SCTP_STAT_INCR(sctps_sendsacks);
+ return;
+}
+
+void
+sctp_send_abort_tcb(struct sctp_tcb *stcb, struct mbuf *operr, int so_locked)
+{
+ struct mbuf *m_abort, *m, *m_last;
+ struct mbuf *m_out, *m_end = NULL;
+ struct sctp_abort_chunk *abort;
+ struct sctp_auth_chunk *auth = NULL;
+ struct sctp_nets *net;
+ uint32_t vtag;
+ uint32_t auth_offset = 0;
+ int error;
+ uint16_t cause_len, chunk_len, padding_len;
+ bool use_zero_crc;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ }
+#endif
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ /*-
+ * Add an AUTH chunk, if chunk requires it and save the offset into
+ * the chain for AUTH
+ */
+ if (sctp_auth_is_required_chunk(SCTP_ABORT_ASSOCIATION,
+ stcb->asoc.peer_auth_chunks)) {
+ m_out = sctp_add_auth_chunk(NULL, &m_end, &auth, &auth_offset,
+ stcb, SCTP_ABORT_ASSOCIATION);
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ } else {
+ m_out = NULL;
+ }
+ switch (stcb->asoc.snd_edmid) {
+ case SCTP_EDMID_LOWER_LAYER_DTLS:
+ use_zero_crc = true;
+ break;
+ default:
+ use_zero_crc = false;
+ break;
+ }
+ m_abort = sctp_get_mbuf_for_msg(sizeof(struct sctp_abort_chunk), 0, M_NOWAIT, 1, MT_HEADER);
+ if (m_abort == NULL) {
+ if (m_out) {
+ sctp_m_freem(m_out);
+ }
+ if (operr) {
+ sctp_m_freem(operr);
+ }
+ return;
+ }
+ /* link in any error */
+ SCTP_BUF_NEXT(m_abort) = operr;
+ cause_len = 0;
+ m_last = NULL;
+ for (m = operr; m; m = SCTP_BUF_NEXT(m)) {
+ cause_len += (uint16_t)SCTP_BUF_LEN(m);
+ if (SCTP_BUF_NEXT(m) == NULL) {
+ m_last = m;
+ }
+ }
+ SCTP_BUF_LEN(m_abort) = sizeof(struct sctp_abort_chunk);
+ chunk_len = (uint16_t)sizeof(struct sctp_abort_chunk) + cause_len;
+ padding_len = SCTP_SIZE32(chunk_len) - chunk_len;
+ if (m_out == NULL) {
+ /* NO Auth chunk prepended, so reserve space in front */
+ SCTP_BUF_RESV_UF(m_abort, SCTP_MIN_OVERHEAD);
+ m_out = m_abort;
+ } else {
+ /* Put AUTH chunk at the front of the chain */
+ SCTP_BUF_NEXT(m_end) = m_abort;
+ }
+ if (stcb->asoc.alternate) {
+ net = stcb->asoc.alternate;
+ } else {
+ net = stcb->asoc.primary_destination;
+ }
+ /* Fill in the ABORT chunk header. */
+ abort = mtod(m_abort, struct sctp_abort_chunk *);
+ abort->ch.chunk_type = SCTP_ABORT_ASSOCIATION;
+ if (stcb->asoc.peer_vtag == 0) {
+ /* This happens iff the assoc is in COOKIE-WAIT state. */
+ vtag = stcb->asoc.my_vtag;
+ abort->ch.chunk_flags = SCTP_HAD_NO_TCB;
+ } else {
+ vtag = stcb->asoc.peer_vtag;
+ abort->ch.chunk_flags = 0;
+ }
+ abort->ch.chunk_length = htons(chunk_len);
+ /* Add padding, if necessary. */
+ if (padding_len > 0) {
+ if ((m_last == NULL) ||
+ (sctp_add_pad_tombuf(m_last, padding_len) == NULL)) {
+ sctp_m_freem(m_out);
+ return;
+ }
+ }
+ if ((error = sctp_lowlevel_chunk_output(stcb->sctp_ep, stcb, net,
+ (struct sockaddr *)&net->ro._l_addr,
+ m_out, auth_offset, auth, stcb->asoc.authinfo.active_keyid, 1, 0, 0,
+ stcb->sctp_ep->sctp_lport, stcb->rport, htonl(vtag),
+ stcb->asoc.primary_destination->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ use_zero_crc,
+ so_locked))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (error == ENOBUFS) {
+ stcb->asoc.ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ } else {
+ stcb->asoc.ifp_had_enobuf = 0;
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+}
+
+void
+sctp_send_shutdown_complete(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ int reflect_vtag)
+{
+ /* formulate and SEND a SHUTDOWN-COMPLETE */
+ struct mbuf *m_shutdown_comp;
+ struct sctp_shutdown_complete_chunk *shutdown_complete;
+ uint32_t vtag;
+ int error;
+ uint8_t flags;
+ bool use_zero_crc;
+
+ m_shutdown_comp = sctp_get_mbuf_for_msg(sizeof(struct sctp_chunkhdr), 0, M_NOWAIT, 1, MT_HEADER);
+ if (m_shutdown_comp == NULL) {
+ /* no mbuf's */
+ return;
+ }
+ if (reflect_vtag) {
+ flags = SCTP_HAD_NO_TCB;
+ vtag = stcb->asoc.my_vtag;
+ } else {
+ flags = 0;
+ vtag = stcb->asoc.peer_vtag;
+ }
+ switch (stcb->asoc.snd_edmid) {
+ case SCTP_EDMID_LOWER_LAYER_DTLS:
+ use_zero_crc = true;
+ break;
+ default:
+ use_zero_crc = false;
+ break;
+ }
+ shutdown_complete = mtod(m_shutdown_comp, struct sctp_shutdown_complete_chunk *);
+ shutdown_complete->ch.chunk_type = SCTP_SHUTDOWN_COMPLETE;
+ shutdown_complete->ch.chunk_flags = flags;
+ shutdown_complete->ch.chunk_length = htons(sizeof(struct sctp_shutdown_complete_chunk));
+ SCTP_BUF_LEN(m_shutdown_comp) = sizeof(struct sctp_shutdown_complete_chunk);
+ if ((error = sctp_lowlevel_chunk_output(stcb->sctp_ep, stcb, net,
+ (struct sockaddr *)&net->ro._l_addr,
+ m_shutdown_comp, 0, NULL, 0, 1, 0, 0,
+ stcb->sctp_ep->sctp_lport, stcb->rport,
+ htonl(vtag),
+ net->port, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 0, 0,
+#endif
+ use_zero_crc,
+ SCTP_SO_NOT_LOCKED))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (error == ENOBUFS) {
+ stcb->asoc.ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ } else {
+ stcb->asoc.ifp_had_enobuf = 0;
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ return;
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static void
+sctp_send_resp_msg(struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, uint32_t vtag,
+ uint8_t type, struct mbuf *cause,
+ uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
+ uint32_t vrf_id, uint16_t port)
+#else
+static void
+sctp_send_resp_msg(struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, uint32_t vtag,
+ uint8_t type, struct mbuf *cause,
+ uint32_t vrf_id SCTP_UNUSED, uint16_t port)
+#endif
+{
+ struct mbuf *o_pak;
+ struct mbuf *mout;
+ struct sctphdr *shout;
+ struct sctp_chunkhdr *ch;
+#if defined(INET) || defined(INET6)
+ struct udphdr *udp;
+#endif
+ int ret, len, cause_len, padding_len;
+#ifdef INET
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sctp_route_t ro;
+#endif
+ struct sockaddr_in *src_sin, *dst_sin;
+ struct ip *ip;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *src_sin6, *dst_sin6;
+ struct ip6_hdr *ip6;
+#endif
+
+ /* Compute the length of the cause and add final padding. */
+ cause_len = 0;
+ if (cause != NULL) {
+ struct mbuf *m_at, *m_last = NULL;
+
+ for (m_at = cause; m_at; m_at = SCTP_BUF_NEXT(m_at)) {
+ if (SCTP_BUF_NEXT(m_at) == NULL)
+ m_last = m_at;
+ cause_len += SCTP_BUF_LEN(m_at);
+ }
+ padding_len = cause_len % 4;
+ if (padding_len != 0) {
+ padding_len = 4 - padding_len;
+ }
+ if (padding_len != 0) {
+ if (sctp_add_pad_tombuf(m_last, padding_len) == NULL) {
+ sctp_m_freem(cause);
+ return;
+ }
+ }
+ } else {
+ padding_len = 0;
+ }
+ /* Get an mbuf for the header. */
+ len = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ switch (dst->sa_family) {
+#ifdef INET
+ case AF_INET:
+ len += sizeof(struct ip);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ len += sizeof(struct ip6_hdr);
+ break;
+#endif
+ default:
+ break;
+ }
+#if defined(INET) || defined(INET6)
+ if (port) {
+ len += sizeof(struct udphdr);
+ }
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ mout = sctp_get_mbuf_for_msg(len + max_linkhdr, 1, M_NOWAIT, 1, MT_DATA);
+#else
+ mout = sctp_get_mbuf_for_msg(len + SCTP_MAX_LINKHDR, 1, M_NOWAIT, 1, MT_DATA);
+#endif
+#else
+ mout = sctp_get_mbuf_for_msg(len + max_linkhdr, 1, M_NOWAIT, 1, MT_DATA);
+#endif
+ if (mout == NULL) {
+ if (cause) {
+ sctp_m_freem(cause);
+ }
+ return;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ SCTP_BUF_RESV_UF(mout, max_linkhdr);
+#else
+ SCTP_BUF_RESV_UF(mout, SCTP_MAX_LINKHDR);
+#endif
+#else
+ SCTP_BUF_RESV_UF(mout, max_linkhdr);
+#endif
+ SCTP_BUF_LEN(mout) = len;
+ SCTP_BUF_NEXT(mout) = cause;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ M_SETFIB(mout, fibnum);
+ mout->m_pkthdr.flowid = mflowid;
+ M_HASHTYPE_SET(mout, mflowtype);
+#endif
+#ifdef INET
+ ip = NULL;
+#endif
+#ifdef INET6
+ ip6 = NULL;
+#endif
+ switch (dst->sa_family) {
+#ifdef INET
+ case AF_INET:
+ src_sin = (struct sockaddr_in *)src;
+ dst_sin = (struct sockaddr_in *)dst;
+ ip = mtod(mout, struct ip *);
+ ip->ip_v = IPVERSION;
+ ip->ip_hl = (sizeof(struct ip) >> 2);
+ ip->ip_tos = 0;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ip->ip_off = htons(IP_DF);
+#elif defined(WITH_CONVERT_IP_OFF) || defined(__APPLE__)
+ ip->ip_off = IP_DF;
+#else
+ ip->ip_off = htons(IP_DF);
+#endif
+#if defined(__Userspace__)
+ ip->ip_id = htons(ip_id++);
+#elif defined(__FreeBSD__)
+ ip_fillid(ip);
+#elif defined(__APPLE__)
+#if RANDOM_IP_ID
+ ip->ip_id = ip_randomid();
+#else
+ ip->ip_id = htons(ip_id++);
+#endif
+#else
+ ip->ip_id = ip_id++;
+#endif
+ ip->ip_ttl = MODULE_GLOBAL(ip_defttl);
+ if (port) {
+ ip->ip_p = IPPROTO_UDP;
+ } else {
+ ip->ip_p = IPPROTO_SCTP;
+ }
+ ip->ip_src.s_addr = dst_sin->sin_addr.s_addr;
+ ip->ip_dst.s_addr = src_sin->sin_addr.s_addr;
+ ip->ip_sum = 0;
+ len = sizeof(struct ip);
+ shout = (struct sctphdr *)((caddr_t)ip + len);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ src_sin6 = (struct sockaddr_in6 *)src;
+ dst_sin6 = (struct sockaddr_in6 *)dst;
+ ip6 = mtod(mout, struct ip6_hdr *);
+ ip6->ip6_flow = htonl(0x60000000);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (V_ip6_auto_flowlabel) {
+ ip6->ip6_flow |= (htonl(ip6_randomflowlabel()) & IPV6_FLOWLABEL_MASK);
+ }
+#endif
+#if defined(__Userspace__)
+ ip6->ip6_hlim = IPv6_HOP_LIMIT;
+#else
+ ip6->ip6_hlim = MODULE_GLOBAL(ip6_defhlim);
+#endif
+ if (port) {
+ ip6->ip6_nxt = IPPROTO_UDP;
+ } else {
+ ip6->ip6_nxt = IPPROTO_SCTP;
+ }
+ ip6->ip6_src = dst_sin6->sin6_addr;
+ ip6->ip6_dst = src_sin6->sin6_addr;
+ len = sizeof(struct ip6_hdr);
+ shout = (struct sctphdr *)((caddr_t)ip6 + len);
+ break;
+#endif
+ default:
+ len = 0;
+ shout = mtod(mout, struct sctphdr *);
+ break;
+ }
+#if defined(INET) || defined(INET6)
+ if (port) {
+ if (htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port)) == 0) {
+ sctp_m_freem(mout);
+ return;
+ }
+ udp = (struct udphdr *)shout;
+ udp->uh_sport = htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port));
+ udp->uh_dport = port;
+ udp->uh_sum = 0;
+ udp->uh_ulen = htons((uint16_t)(sizeof(struct udphdr) +
+ sizeof(struct sctphdr) +
+ sizeof(struct sctp_chunkhdr) +
+ cause_len + padding_len));
+ len += sizeof(struct udphdr);
+ shout = (struct sctphdr *)((caddr_t)shout + sizeof(struct udphdr));
+ } else {
+ udp = NULL;
+ }
+#endif
+ shout->src_port = sh->dest_port;
+ shout->dest_port = sh->src_port;
+ shout->checksum = 0;
+ if (vtag) {
+ shout->v_tag = htonl(vtag);
+ } else {
+ shout->v_tag = sh->v_tag;
+ }
+ len += sizeof(struct sctphdr);
+ ch = (struct sctp_chunkhdr *)((caddr_t)shout + sizeof(struct sctphdr));
+ ch->chunk_type = type;
+ if (vtag) {
+ ch->chunk_flags = 0;
+ } else {
+ ch->chunk_flags = SCTP_HAD_NO_TCB;
+ }
+ ch->chunk_length = htons((uint16_t)(sizeof(struct sctp_chunkhdr) + cause_len));
+ len += sizeof(struct sctp_chunkhdr);
+ len += cause_len + padding_len;
+
+ if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) {
+ sctp_m_freem(mout);
+ return;
+ }
+ SCTP_ATTACH_CHAIN(o_pak, mout, len);
+ switch (dst->sa_family) {
+#ifdef INET
+ case AF_INET:
+#if defined(__APPLE__) && !defined(__Userspace__)
+ /* zap the stack pointer to the route */
+ memset(&ro, 0, sizeof(sctp_route_t));
+#endif
+ if (port) {
+#if !defined(_WIN32) && !defined(__Userspace__)
+#if defined(__FreeBSD__)
+ if (V_udp_cksum) {
+ udp->uh_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, udp->uh_ulen + htons(IPPROTO_UDP));
+ } else {
+ udp->uh_sum = 0;
+ }
+#else
+ udp->uh_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, udp->uh_ulen + htons(IPPROTO_UDP));
+#endif
+#else
+ udp->uh_sum = 0;
+#endif
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ip->ip_len = htons(len);
+#elif defined(__APPLE__) || defined(__Userspace__)
+ ip->ip_len = len;
+#else
+ ip->ip_len = htons(len);
+#endif
+ if (port) {
+ shout->checksum = sctp_calculate_cksum(mout, sizeof(struct ip) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#if !defined(_WIN32) && !defined(__Userspace__)
+#if defined(__FreeBSD__)
+ if (V_udp_cksum) {
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+ }
+#else
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+#endif
+#endif
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mout->m_pkthdr.csum_flags = CSUM_SCTP;
+ mout->m_pkthdr.csum_data = offsetof(struct sctphdr, checksum);
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+#else
+ shout->checksum = sctp_calculate_cksum(mout, sizeof(struct ip));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+ }
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING) {
+ sctp_packet_log(o_pak);
+ }
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_IP_OUTPUT(ret, o_pak, &ro, NULL, vrf_id);
+ /* Free the route if we got one back */
+ if (ro.ro_rt) {
+ RTFREE(ro.ro_rt);
+ ro.ro_rt = NULL;
+ }
+#else
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(send, NULL, NULL, ip, NULL, shout);
+#endif
+ SCTP_IP_OUTPUT(ret, o_pak, NULL, NULL, vrf_id);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ip6->ip6_plen = htons((uint16_t)(len - sizeof(struct ip6_hdr)));
+ if (port) {
+ shout->checksum = sctp_calculate_cksum(mout, sizeof(struct ip6_hdr) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#if !defined(__Userspace__)
+#if defined(_WIN32)
+ udp->uh_sum = 0;
+#else
+ if ((udp->uh_sum = in6_cksum(o_pak, IPPROTO_UDP, sizeof(struct ip6_hdr), len - sizeof(struct ip6_hdr))) == 0) {
+ udp->uh_sum = 0xffff;
+ }
+#endif
+#endif
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mout->m_pkthdr.csum_flags = CSUM_SCTP_IPV6;
+ mout->m_pkthdr.csum_data = offsetof(struct sctphdr, checksum);
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+#else
+ shout->checksum = sctp_calculate_cksum(mout, sizeof(struct ip6_hdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+ }
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING) {
+ sctp_packet_log(o_pak);
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(send, NULL, NULL, ip6, NULL, shout);
+#endif
+ SCTP_IP6_OUTPUT(ret, o_pak, NULL, NULL, NULL, vrf_id);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ char *buffer;
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)src;
+ if (SCTP_BASE_VAR(crc32c_offloaded) == 0) {
+ shout->checksum = sctp_calculate_cksum(o_pak, 0);
+ SCTP_STAT_INCR(sctps_sendswcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+ }
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING) {
+ sctp_packet_log(mout);
+ }
+#endif
+ /* Don't alloc/free for each packet */
+ if ((buffer = malloc(len)) != NULL) {
+ m_copydata(o_pak, 0, len, buffer);
+ ret = SCTP_BASE_VAR(conn_output)(sconn->sconn_addr, buffer, len, 0, 0);
+ free(buffer);
+ } else {
+ ret = ENOMEM;
+ }
+ sctp_m_freem(o_pak);
+ break;
+ }
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Unknown protocol (TSNH) type %d\n",
+ dst->sa_family);
+ sctp_m_freem(mout);
+ SCTP_LTRACE_ERR_RET_PKT(mout, NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ return;
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "return from send is %d\n", ret);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (port) {
+ UDPSTAT_INC(udps_opackets);
+ }
+#endif
+ SCTP_STAT_INCR(sctps_sendpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_outpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ if (ret) {
+ SCTP_STAT_INCR(sctps_senderrors);
+ }
+ return;
+}
+
+void
+sctp_send_shutdown_complete2(struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ sctp_send_resp_msg(src, dst, sh, 0, SCTP_SHUTDOWN_COMPLETE, NULL,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+}
+
+void
+sctp_send_hb(struct sctp_tcb *stcb, struct sctp_nets *net,int so_locked)
+{
+ struct sctp_tmit_chunk *chk;
+ struct sctp_heartbeat_chunk *hb;
+ struct timeval now;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (net == NULL) {
+ return;
+ }
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ break;
+#endif
+ default:
+ return;
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "Gak, can't get a chunk for hb\n");
+ return;
+ }
+
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_HEARTBEAT_REQUEST;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = 0;
+ chk->asoc = &stcb->asoc;
+ chk->send_size = sizeof(struct sctp_heartbeat_chunk);
+
+ chk->data = sctp_get_mbuf_for_msg(chk->send_size, 0, M_NOWAIT, 1, MT_HEADER);
+ if (chk->data == NULL) {
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ return;
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->whoTo = net;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ /* Now we have a mbuf that we can fill in with the details */
+ hb = mtod(chk->data, struct sctp_heartbeat_chunk *);
+ memset(hb, 0, sizeof(struct sctp_heartbeat_chunk));
+ /* fill out chunk header */
+ hb->ch.chunk_type = SCTP_HEARTBEAT_REQUEST;
+ hb->ch.chunk_flags = 0;
+ hb->ch.chunk_length = htons(chk->send_size);
+ /* Fill out hb parameter */
+ hb->heartbeat.hb_info.ph.param_type = htons(SCTP_HEARTBEAT_INFO);
+ hb->heartbeat.hb_info.ph.param_length = htons(sizeof(struct sctp_heartbeat_info_param));
+ hb->heartbeat.hb_info.time_value_1 = (uint32_t)now.tv_sec;
+ hb->heartbeat.hb_info.time_value_2 = now.tv_usec;
+ /* Did our user request this one, put it in */
+ hb->heartbeat.hb_info.addr_family = (uint8_t)net->ro._l_addr.sa.sa_family;
+#ifdef HAVE_SA_LEN
+ hb->heartbeat.hb_info.addr_len = net->ro._l_addr.sa.sa_len;
+#else
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ hb->heartbeat.hb_info.addr_len = sizeof(struct sockaddr_in);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ hb->heartbeat.hb_info.addr_len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ hb->heartbeat.hb_info.addr_len = sizeof(struct sockaddr_conn);
+ break;
+#endif
+ default:
+ hb->heartbeat.hb_info.addr_len = 0;
+ break;
+ }
+#endif
+ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ /*
+ * we only take from the entropy pool if the address is not
+ * confirmed.
+ */
+ net->heartbeat_random1 = hb->heartbeat.hb_info.random_value1 = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep);
+ net->heartbeat_random2 = hb->heartbeat.hb_info.random_value2 = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep);
+ } else {
+ net->heartbeat_random1 = hb->heartbeat.hb_info.random_value1 = 0;
+ net->heartbeat_random2 = hb->heartbeat.hb_info.random_value2 = 0;
+ }
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ memcpy(hb->heartbeat.hb_info.address,
+ &net->ro._l_addr.sin.sin_addr,
+ sizeof(net->ro._l_addr.sin.sin_addr));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memcpy(hb->heartbeat.hb_info.address,
+ &net->ro._l_addr.sin6.sin6_addr,
+ sizeof(net->ro._l_addr.sin6.sin6_addr));
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(hb->heartbeat.hb_info.address,
+ &net->ro._l_addr.sconn.sconn_addr,
+ sizeof(net->ro._l_addr.sconn.sconn_addr));
+ break;
+#endif
+ default:
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ return;
+ break;
+ }
+ net->hb_responded = 0;
+ TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next);
+ stcb->asoc.ctrl_queue_cnt++;
+ SCTP_STAT_INCR(sctps_sendheartbeat);
+ return;
+}
+
+void
+sctp_send_ecn_echo(struct sctp_tcb *stcb, struct sctp_nets *net,
+ uint32_t high_tsn)
+{
+ struct sctp_association *asoc;
+ struct sctp_ecne_chunk *ecne;
+ struct sctp_tmit_chunk *chk;
+
+ if (net == NULL) {
+ return;
+ }
+ asoc = &stcb->asoc;
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if ((chk->rec.chunk_id.id == SCTP_ECN_ECHO) && (net == chk->whoTo)) {
+ /* found a previous ECN_ECHO update it if needed */
+ uint32_t cnt, ctsn;
+ ecne = mtod(chk->data, struct sctp_ecne_chunk *);
+ ctsn = ntohl(ecne->tsn);
+ if (SCTP_TSN_GT(high_tsn, ctsn)) {
+ ecne->tsn = htonl(high_tsn);
+ SCTP_STAT_INCR(sctps_queue_upd_ecne);
+ }
+ cnt = ntohl(ecne->num_pkts_since_cwr);
+ cnt++;
+ ecne->num_pkts_since_cwr = htonl(cnt);
+ return;
+ }
+ }
+ /* nope could not find one to update so we must build one */
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ return;
+ }
+ SCTP_STAT_INCR(sctps_queue_upd_ecne);
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_ECN_ECHO;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = 0;
+ chk->asoc = &stcb->asoc;
+ chk->send_size = sizeof(struct sctp_ecne_chunk);
+ chk->data = sctp_get_mbuf_for_msg(chk->send_size, 0, M_NOWAIT, 1, MT_HEADER);
+ if (chk->data == NULL) {
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->whoTo = net;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+
+ stcb->asoc.ecn_echo_cnt_onq++;
+ ecne = mtod(chk->data, struct sctp_ecne_chunk *);
+ ecne->ch.chunk_type = SCTP_ECN_ECHO;
+ ecne->ch.chunk_flags = 0;
+ ecne->ch.chunk_length = htons(sizeof(struct sctp_ecne_chunk));
+ ecne->tsn = htonl(high_tsn);
+ ecne->num_pkts_since_cwr = htonl(1);
+ TAILQ_INSERT_HEAD(&stcb->asoc.control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt++;
+}
+
+void
+sctp_send_packet_dropped(struct sctp_tcb *stcb, struct sctp_nets *net,
+ struct mbuf *m, int len, int iphlen, int bad_crc)
+{
+ struct sctp_association *asoc;
+ struct sctp_pktdrop_chunk *drp;
+ struct sctp_tmit_chunk *chk;
+ uint8_t *datap;
+ int was_trunc = 0;
+ int fullsz = 0;
+ long spc;
+ int offset;
+ struct sctp_chunkhdr *ch, chunk_buf;
+ unsigned int chk_length;
+
+ if (!stcb) {
+ return;
+ }
+ asoc = &stcb->asoc;
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (asoc->pktdrop_supported == 0) {
+ /*-
+ * peer must declare support before I send one.
+ */
+ return;
+ }
+ if (stcb->sctp_socket == NULL) {
+ return;
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_PACKET_DROPPED;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = 0;
+ len -= iphlen;
+ chk->send_size = len;
+ /* Validate that we do not have an ABORT in here. */
+ offset = iphlen + sizeof(struct sctphdr);
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset,
+ sizeof(*ch), (uint8_t *) & chunk_buf);
+ while (ch != NULL) {
+ chk_length = ntohs(ch->chunk_length);
+ if (chk_length < sizeof(*ch)) {
+ /* break to abort land */
+ break;
+ }
+ switch (ch->chunk_type) {
+ case SCTP_PACKET_DROPPED:
+ case SCTP_ABORT_ASSOCIATION:
+ case SCTP_INITIATION_ACK:
+ /**
+ * We don't respond with an PKT-DROP to an ABORT
+ * or PKT-DROP. We also do not respond to an
+ * INIT-ACK, because we can't know if the initiation
+ * tag is correct or not.
+ */
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return;
+ default:
+ break;
+ }
+ offset += SCTP_SIZE32(chk_length);
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset,
+ sizeof(*ch), (uint8_t *) & chunk_buf);
+ }
+
+ if ((len + SCTP_MAX_OVERHEAD + sizeof(struct sctp_pktdrop_chunk)) >
+ min(stcb->asoc.smallest_mtu, MCLBYTES)) {
+ /* only send 1 mtu worth, trim off the
+ * excess on the end.
+ */
+ fullsz = len;
+ len = min(stcb->asoc.smallest_mtu, MCLBYTES) - SCTP_MAX_OVERHEAD;
+ was_trunc = 1;
+ }
+ chk->asoc = &stcb->asoc;
+ chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (chk->data == NULL) {
+jump_out:
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+ drp = mtod(chk->data, struct sctp_pktdrop_chunk *);
+ if (drp == NULL) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ goto jump_out;
+ }
+ chk->book_size = SCTP_SIZE32((chk->send_size + sizeof(struct sctp_pktdrop_chunk) +
+ sizeof(struct sctphdr) + SCTP_MED_OVERHEAD));
+ chk->book_size_scale = 0;
+ if (was_trunc) {
+ drp->ch.chunk_flags = SCTP_PACKET_TRUNCATED;
+ drp->trunc_len = htons(fullsz);
+ /* Len is already adjusted to size minus overhead above
+ * take out the pkt_drop chunk itself from it.
+ */
+ chk->send_size = (uint16_t)(len - sizeof(struct sctp_pktdrop_chunk));
+ len = chk->send_size;
+ } else {
+ /* no truncation needed */
+ drp->ch.chunk_flags = 0;
+ drp->trunc_len = htons(0);
+ }
+ if (bad_crc) {
+ drp->ch.chunk_flags |= SCTP_BADCRC;
+ }
+ chk->send_size += sizeof(struct sctp_pktdrop_chunk);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ if (net) {
+ /* we should hit here */
+ chk->whoTo = net;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ } else {
+ chk->whoTo = NULL;
+ }
+ drp->ch.chunk_type = SCTP_PACKET_DROPPED;
+ drp->ch.chunk_length = htons(chk->send_size);
+ spc = SCTP_SB_LIMIT_RCV(stcb->sctp_socket);
+ if (spc < 0) {
+ spc = 0;
+ }
+ drp->bottle_bw = htonl(spc);
+ if (asoc->my_rwnd) {
+ drp->current_onq = htonl(asoc->size_on_reasm_queue +
+ asoc->size_on_all_streams +
+ asoc->my_rwnd_control_len +
+ SCTP_SBAVAIL(&stcb->sctp_socket->so_rcv));
+ } else {
+ /*-
+ * If my rwnd is 0, possibly from mbuf depletion as well as
+ * space used, tell the peer there is NO space aka onq == bw
+ */
+ drp->current_onq = htonl(spc);
+ }
+ drp->reserved = 0;
+ datap = drp->data;
+ m_copydata(m, iphlen, len, (caddr_t)datap);
+ TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt++;
+}
+
+void
+sctp_send_cwr(struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t high_tsn, uint8_t override)
+{
+ struct sctp_association *asoc;
+ struct sctp_cwr_chunk *cwr;
+ struct sctp_tmit_chunk *chk;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (net == NULL) {
+ return;
+ }
+ asoc = &stcb->asoc;
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if ((chk->rec.chunk_id.id == SCTP_ECN_CWR) && (net == chk->whoTo)) {
+ /* found a previous CWR queued to same destination update it if needed */
+ uint32_t ctsn;
+ cwr = mtod(chk->data, struct sctp_cwr_chunk *);
+ ctsn = ntohl(cwr->tsn);
+ if (SCTP_TSN_GT(high_tsn, ctsn)) {
+ cwr->tsn = htonl(high_tsn);
+ }
+ if (override & SCTP_CWR_REDUCE_OVERRIDE) {
+ /* Make sure override is carried */
+ cwr->ch.chunk_flags |= SCTP_CWR_REDUCE_OVERRIDE;
+ }
+ return;
+ }
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_ECN_CWR;
+ chk->rec.chunk_id.can_take_data = 1;
+ chk->flags = 0;
+ chk->asoc = asoc;
+ chk->send_size = sizeof(struct sctp_cwr_chunk);
+ chk->data = sctp_get_mbuf_for_msg(chk->send_size, 0, M_NOWAIT, 1, MT_HEADER);
+ if (chk->data == NULL) {
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ chk->whoTo = net;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ cwr = mtod(chk->data, struct sctp_cwr_chunk *);
+ cwr->ch.chunk_type = SCTP_ECN_CWR;
+ cwr->ch.chunk_flags = override;
+ cwr->ch.chunk_length = htons(sizeof(struct sctp_cwr_chunk));
+ cwr->tsn = htonl(high_tsn);
+ TAILQ_INSERT_TAIL(&asoc->control_send_queue, chk, sctp_next);
+ asoc->ctrl_queue_cnt++;
+}
+
+static int
+sctp_add_stream_reset_out(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk,
+ uint32_t seq, uint32_t resp_seq, uint32_t last_sent)
+{
+ uint16_t len, old_len, i;
+ struct sctp_stream_reset_out_request *req_out;
+ struct sctp_chunkhdr *ch;
+ int at;
+ int number_entries=0;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length));
+ /* get to new offset for the param. */
+ req_out = (struct sctp_stream_reset_out_request *)((caddr_t)ch + len);
+ /* now how long will this param be? */
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ if ((stcb->asoc.strmout[i].state == SCTP_STREAM_RESET_PENDING) &&
+ (stcb->asoc.strmout[i].chunks_on_queues == 0) &&
+ TAILQ_EMPTY(&stcb->asoc.strmout[i].outqueue)) {
+ number_entries++;
+ }
+ }
+ if (number_entries == 0) {
+ return (0);
+ }
+ if (number_entries == stcb->asoc.streamoutcnt) {
+ number_entries = 0;
+ }
+ if (number_entries > SCTP_MAX_STREAMS_AT_ONCE_RESET) {
+ number_entries = SCTP_MAX_STREAMS_AT_ONCE_RESET;
+ }
+ len = (uint16_t)(sizeof(struct sctp_stream_reset_out_request) + (sizeof(uint16_t) * number_entries));
+ req_out->ph.param_type = htons(SCTP_STR_RESET_OUT_REQUEST);
+ req_out->ph.param_length = htons(len);
+ req_out->request_seq = htonl(seq);
+ req_out->response_seq = htonl(resp_seq);
+ req_out->send_reset_at_tsn = htonl(last_sent);
+ at = 0;
+ if (number_entries) {
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ if ((stcb->asoc.strmout[i].state == SCTP_STREAM_RESET_PENDING) &&
+ (stcb->asoc.strmout[i].chunks_on_queues == 0) &&
+ TAILQ_EMPTY(&stcb->asoc.strmout[i].outqueue)) {
+ req_out->list_of_streams[at] = htons(i);
+ at++;
+ stcb->asoc.strmout[i].state = SCTP_STREAM_RESET_IN_FLIGHT;
+ if (at >= number_entries) {
+ break;
+ }
+ }
+ }
+ } else {
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ stcb->asoc.strmout[i].state = SCTP_STREAM_RESET_IN_FLIGHT;
+ }
+ }
+ if (SCTP_SIZE32(len) > len) {
+ /*-
+ * Need to worry about the pad we may end up adding to the
+ * end. This is easy since the struct is either aligned to 4
+ * bytes or 2 bytes off.
+ */
+ req_out->list_of_streams[number_entries] = 0;
+ }
+ /* now fix the chunk length */
+ ch->chunk_length = htons(len + old_len);
+ chk->book_size = len + old_len;
+ chk->book_size_scale = 0;
+ chk->send_size = SCTP_SIZE32(chk->book_size);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ return (1);
+}
+
+static void
+sctp_add_stream_reset_in(struct sctp_tmit_chunk *chk,
+ int number_entries, uint16_t *list,
+ uint32_t seq)
+{
+ uint16_t len, old_len, i;
+ struct sctp_stream_reset_in_request *req_in;
+ struct sctp_chunkhdr *ch;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length));
+
+ /* get to new offset for the param. */
+ req_in = (struct sctp_stream_reset_in_request *)((caddr_t)ch + len);
+ /* now how long will this param be? */
+ len = (uint16_t)(sizeof(struct sctp_stream_reset_in_request) + (sizeof(uint16_t) * number_entries));
+ req_in->ph.param_type = htons(SCTP_STR_RESET_IN_REQUEST);
+ req_in->ph.param_length = htons(len);
+ req_in->request_seq = htonl(seq);
+ if (number_entries) {
+ for (i = 0; i < number_entries; i++) {
+ req_in->list_of_streams[i] = htons(list[i]);
+ }
+ }
+ if (SCTP_SIZE32(len) > len) {
+ /*-
+ * Need to worry about the pad we may end up adding to the
+ * end. This is easy since the struct is either aligned to 4
+ * bytes or 2 bytes off.
+ */
+ req_in->list_of_streams[number_entries] = 0;
+ }
+ /* now fix the chunk length */
+ ch->chunk_length = htons(len + old_len);
+ chk->book_size = len + old_len;
+ chk->book_size_scale = 0;
+ chk->send_size = SCTP_SIZE32(chk->book_size);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ return;
+}
+
+static void
+sctp_add_stream_reset_tsn(struct sctp_tmit_chunk *chk,
+ uint32_t seq)
+{
+ uint16_t len, old_len;
+ struct sctp_stream_reset_tsn_request *req_tsn;
+ struct sctp_chunkhdr *ch;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length));
+
+ /* get to new offset for the param. */
+ req_tsn = (struct sctp_stream_reset_tsn_request *)((caddr_t)ch + len);
+ /* now how long will this param be? */
+ len = sizeof(struct sctp_stream_reset_tsn_request);
+ req_tsn->ph.param_type = htons(SCTP_STR_RESET_TSN_REQUEST);
+ req_tsn->ph.param_length = htons(len);
+ req_tsn->request_seq = htonl(seq);
+
+ /* now fix the chunk length */
+ ch->chunk_length = htons(len + old_len);
+ chk->send_size = len + old_len;
+ chk->book_size = SCTP_SIZE32(chk->send_size);
+ chk->book_size_scale = 0;
+ SCTP_BUF_LEN(chk->data) = SCTP_SIZE32(chk->send_size);
+ return;
+}
+
+void
+sctp_add_stream_reset_result(struct sctp_tmit_chunk *chk,
+ uint32_t resp_seq, uint32_t result)
+{
+ uint16_t len, old_len;
+ struct sctp_stream_reset_response *resp;
+ struct sctp_chunkhdr *ch;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length));
+
+ /* get to new offset for the param. */
+ resp = (struct sctp_stream_reset_response *)((caddr_t)ch + len);
+ /* now how long will this param be? */
+ len = sizeof(struct sctp_stream_reset_response);
+ resp->ph.param_type = htons(SCTP_STR_RESET_RESPONSE);
+ resp->ph.param_length = htons(len);
+ resp->response_seq = htonl(resp_seq);
+ resp->result = ntohl(result);
+
+ /* now fix the chunk length */
+ ch->chunk_length = htons(len + old_len);
+ chk->book_size = len + old_len;
+ chk->book_size_scale = 0;
+ chk->send_size = SCTP_SIZE32(chk->book_size);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ return;
+}
+
+void
+sctp_send_deferred_reset_response(struct sctp_tcb *stcb,
+ struct sctp_stream_reset_list *ent,
+ int response)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk;
+ struct sctp_chunkhdr *ch;
+
+ asoc = &stcb->asoc;
+
+ /*
+ * Reset our last reset action to the new one IP -> response
+ * (PERFORMED probably). This assures that if we fail to send, a
+ * retran from the peer will get the new response.
+ */
+ asoc->last_reset_action[0] = response;
+ if (asoc->stream_reset_outstanding) {
+ return;
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_STREAM_RESET;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = 0;
+ chk->asoc = &stcb->asoc;
+ chk->book_size = sizeof(struct sctp_chunkhdr);
+ chk->send_size = SCTP_SIZE32(chk->book_size);
+ chk->book_size_scale = 0;
+ chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (chk->data == NULL) {
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_LOCKED);
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return;
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+ /* setup chunk parameters */
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ if (stcb->asoc.alternate) {
+ chk->whoTo = stcb->asoc.alternate;
+ } else {
+ chk->whoTo = stcb->asoc.primary_destination;
+ }
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ ch->chunk_type = SCTP_STREAM_RESET;
+ ch->chunk_flags = 0;
+ ch->chunk_length = htons(chk->book_size);
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ sctp_add_stream_reset_result(chk, ent->seq, response);
+ /* insert the chunk for sending */
+ TAILQ_INSERT_TAIL(&asoc->control_send_queue,
+ chk,
+ sctp_next);
+ asoc->ctrl_queue_cnt++;
+}
+
+void
+sctp_add_stream_reset_result_tsn(struct sctp_tmit_chunk *chk,
+ uint32_t resp_seq, uint32_t result,
+ uint32_t send_una, uint32_t recv_next)
+{
+ uint16_t len, old_len;
+ struct sctp_stream_reset_response_tsn *resp;
+ struct sctp_chunkhdr *ch;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length));
+
+ /* get to new offset for the param. */
+ resp = (struct sctp_stream_reset_response_tsn *)((caddr_t)ch + len);
+ /* now how long will this param be? */
+ len = sizeof(struct sctp_stream_reset_response_tsn);
+ resp->ph.param_type = htons(SCTP_STR_RESET_RESPONSE);
+ resp->ph.param_length = htons(len);
+ resp->response_seq = htonl(resp_seq);
+ resp->result = htonl(result);
+ resp->senders_next_tsn = htonl(send_una);
+ resp->receivers_next_tsn = htonl(recv_next);
+
+ /* now fix the chunk length */
+ ch->chunk_length = htons(len + old_len);
+ chk->book_size = len + old_len;
+ chk->send_size = SCTP_SIZE32(chk->book_size);
+ chk->book_size_scale = 0;
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ return;
+}
+
+static void
+sctp_add_an_out_stream(struct sctp_tmit_chunk *chk,
+ uint32_t seq,
+ uint16_t adding)
+{
+ uint16_t len, old_len;
+ struct sctp_chunkhdr *ch;
+ struct sctp_stream_reset_add_strm *addstr;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length));
+
+ /* get to new offset for the param. */
+ addstr = (struct sctp_stream_reset_add_strm *)((caddr_t)ch + len);
+ /* now how long will this param be? */
+ len = sizeof(struct sctp_stream_reset_add_strm);
+
+ /* Fill it out. */
+ addstr->ph.param_type = htons(SCTP_STR_RESET_ADD_OUT_STREAMS);
+ addstr->ph.param_length = htons(len);
+ addstr->request_seq = htonl(seq);
+ addstr->number_of_streams = htons(adding);
+ addstr->reserved = 0;
+
+ /* now fix the chunk length */
+ ch->chunk_length = htons(len + old_len);
+ chk->send_size = len + old_len;
+ chk->book_size = SCTP_SIZE32(chk->send_size);
+ chk->book_size_scale = 0;
+ SCTP_BUF_LEN(chk->data) = SCTP_SIZE32(chk->send_size);
+ return;
+}
+
+static void
+sctp_add_an_in_stream(struct sctp_tmit_chunk *chk,
+ uint32_t seq,
+ uint16_t adding)
+{
+ uint16_t len, old_len;
+ struct sctp_chunkhdr *ch;
+ struct sctp_stream_reset_add_strm *addstr;
+
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length));
+
+ /* get to new offset for the param. */
+ addstr = (struct sctp_stream_reset_add_strm *)((caddr_t)ch + len);
+ /* now how long will this param be? */
+ len = sizeof(struct sctp_stream_reset_add_strm);
+ /* Fill it out. */
+ addstr->ph.param_type = htons(SCTP_STR_RESET_ADD_IN_STREAMS);
+ addstr->ph.param_length = htons(len);
+ addstr->request_seq = htonl(seq);
+ addstr->number_of_streams = htons(adding);
+ addstr->reserved = 0;
+
+ /* now fix the chunk length */
+ ch->chunk_length = htons(len + old_len);
+ chk->send_size = len + old_len;
+ chk->book_size = SCTP_SIZE32(chk->send_size);
+ chk->book_size_scale = 0;
+ SCTP_BUF_LEN(chk->data) = SCTP_SIZE32(chk->send_size);
+ return;
+}
+
+int
+sctp_send_stream_reset_out_if_possible(struct sctp_tcb *stcb, int so_locked)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk;
+ struct sctp_chunkhdr *ch;
+ uint32_t seq;
+
+ asoc = &stcb->asoc;
+ asoc->trigger_reset = 0;
+ if (asoc->stream_reset_outstanding) {
+ return (EALREADY);
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_STREAM_RESET;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = 0;
+ chk->asoc = &stcb->asoc;
+ chk->book_size = sizeof(struct sctp_chunkhdr);
+ chk->send_size = SCTP_SIZE32(chk->book_size);
+ chk->book_size_scale = 0;
+ chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (chk->data == NULL) {
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+
+ /* setup chunk parameters */
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ if (stcb->asoc.alternate) {
+ chk->whoTo = stcb->asoc.alternate;
+ } else {
+ chk->whoTo = stcb->asoc.primary_destination;
+ }
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ ch->chunk_type = SCTP_STREAM_RESET;
+ ch->chunk_flags = 0;
+ ch->chunk_length = htons(chk->book_size);
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+ seq = stcb->asoc.str_reset_seq_out;
+ if (sctp_add_stream_reset_out(stcb, chk, seq, (stcb->asoc.str_reset_seq_in - 1), (stcb->asoc.sending_seq - 1))) {
+ seq++;
+ asoc->stream_reset_outstanding++;
+ } else {
+ m_freem(chk->data);
+ chk->data = NULL;
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ return (ENOENT);
+ }
+ asoc->str_reset = chk;
+ /* insert the chunk for sending */
+ TAILQ_INSERT_TAIL(&asoc->control_send_queue,
+ chk,
+ sctp_next);
+ asoc->ctrl_queue_cnt++;
+
+ if (stcb->asoc.send_sack) {
+ sctp_send_sack(stcb, so_locked);
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo);
+ return (0);
+}
+
+int
+sctp_send_str_reset_req(struct sctp_tcb *stcb,
+ uint16_t number_entries, uint16_t *list,
+ uint8_t send_in_req,
+ uint8_t send_tsn_req,
+ uint8_t add_stream,
+ uint16_t adding_o,
+ uint16_t adding_i, uint8_t peer_asked)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk;
+ struct sctp_chunkhdr *ch;
+ int can_send_out_req=0;
+ uint32_t seq;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ asoc = &stcb->asoc;
+ if (asoc->stream_reset_outstanding) {
+ /*-
+ * Already one pending, must get ACK back to clear the flag.
+ */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EBUSY);
+ return (EBUSY);
+ }
+ if ((send_in_req == 0) && (send_tsn_req == 0) &&
+ (add_stream == 0)) {
+ /* nothing to do */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ }
+ if (send_tsn_req && send_in_req) {
+ /* error, can't do that */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ } else if (send_in_req) {
+ can_send_out_req = 1;
+ }
+ if (number_entries > (MCLBYTES -
+ SCTP_MIN_OVERHEAD -
+ sizeof(struct sctp_chunkhdr) -
+ sizeof(struct sctp_stream_reset_out_request)) /
+ sizeof(uint16_t)) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ chk->copy_by_ref = 0;
+ chk->rec.chunk_id.id = SCTP_STREAM_RESET;
+ chk->rec.chunk_id.can_take_data = 0;
+ chk->flags = 0;
+ chk->asoc = &stcb->asoc;
+ chk->book_size = sizeof(struct sctp_chunkhdr);
+ chk->send_size = SCTP_SIZE32(chk->book_size);
+ chk->book_size_scale = 0;
+ chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (chk->data == NULL) {
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_LOCKED);
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD);
+
+ /* setup chunk parameters */
+ chk->sent = SCTP_DATAGRAM_UNSENT;
+ chk->snd_count = 0;
+ if (stcb->asoc.alternate) {
+ chk->whoTo = stcb->asoc.alternate;
+ } else {
+ chk->whoTo = stcb->asoc.primary_destination;
+ }
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ ch = mtod(chk->data, struct sctp_chunkhdr *);
+ ch->chunk_type = SCTP_STREAM_RESET;
+ ch->chunk_flags = 0;
+ ch->chunk_length = htons(chk->book_size);
+ SCTP_BUF_LEN(chk->data) = chk->send_size;
+
+ seq = stcb->asoc.str_reset_seq_out;
+ if (can_send_out_req) {
+ int ret;
+
+ ret = sctp_add_stream_reset_out(stcb, chk, seq, (stcb->asoc.str_reset_seq_in - 1), (stcb->asoc.sending_seq - 1));
+ if (ret) {
+ seq++;
+ asoc->stream_reset_outstanding++;
+ }
+ }
+ if ((add_stream & 1) &&
+ ((stcb->asoc.strm_realoutsize - stcb->asoc.streamoutcnt) < adding_o)) {
+ /* Need to allocate more */
+ struct sctp_stream_out *oldstream;
+ struct sctp_stream_queue_pending *sp, *nsp;
+ int i;
+#if defined(SCTP_DETAILED_STR_STATS)
+ int j;
+#endif
+
+ oldstream = stcb->asoc.strmout;
+ /* get some more */
+ SCTP_MALLOC(stcb->asoc.strmout, struct sctp_stream_out *,
+ (stcb->asoc.streamoutcnt + adding_o) * sizeof(struct sctp_stream_out),
+ SCTP_M_STRMO);
+ if (stcb->asoc.strmout == NULL) {
+ uint8_t x;
+ stcb->asoc.strmout = oldstream;
+ /* Turn off the bit */
+ x = add_stream & 0xfe;
+ add_stream = x;
+ goto skip_stuff;
+ }
+ /* Ok now we proceed with copying the old out stuff and
+ * initializing the new stuff.
+ */
+ stcb->asoc.ss_functions.sctp_ss_clear(stcb, &stcb->asoc, false);
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ TAILQ_INIT(&stcb->asoc.strmout[i].outqueue);
+ /* FIX ME FIX ME */
+ /* This should be a SS_COPY operation FIX ME STREAM SCHEDULER EXPERT */
+ stcb->asoc.ss_functions.sctp_ss_init_stream(stcb, &stcb->asoc.strmout[i], &oldstream[i]);
+ stcb->asoc.strmout[i].chunks_on_queues = oldstream[i].chunks_on_queues;
+#if defined(SCTP_DETAILED_STR_STATS)
+ for (j = 0; j < SCTP_PR_SCTP_MAX + 1; j++) {
+ stcb->asoc.strmout[i].abandoned_sent[j] = oldstream[i].abandoned_sent[j];
+ stcb->asoc.strmout[i].abandoned_unsent[j] = oldstream[i].abandoned_unsent[j];
+ }
+#else
+ stcb->asoc.strmout[i].abandoned_sent[0] = oldstream[i].abandoned_sent[0];
+ stcb->asoc.strmout[i].abandoned_unsent[0] = oldstream[i].abandoned_unsent[0];
+#endif
+ stcb->asoc.strmout[i].next_mid_ordered = oldstream[i].next_mid_ordered;
+ stcb->asoc.strmout[i].next_mid_unordered = oldstream[i].next_mid_unordered;
+ stcb->asoc.strmout[i].last_msg_incomplete = oldstream[i].last_msg_incomplete;
+ stcb->asoc.strmout[i].sid = i;
+ stcb->asoc.strmout[i].state = oldstream[i].state;
+ /* now anything on those queues? */
+ TAILQ_FOREACH_SAFE(sp, &oldstream[i].outqueue, next, nsp) {
+ TAILQ_REMOVE(&oldstream[i].outqueue, sp, next);
+ TAILQ_INSERT_TAIL(&stcb->asoc.strmout[i].outqueue, sp, next);
+ }
+ }
+ /* now the new streams */
+ stcb->asoc.ss_functions.sctp_ss_init(stcb, &stcb->asoc);
+ for (i = stcb->asoc.streamoutcnt; i < (stcb->asoc.streamoutcnt + adding_o); i++) {
+ TAILQ_INIT(&stcb->asoc.strmout[i].outqueue);
+ stcb->asoc.strmout[i].chunks_on_queues = 0;
+#if defined(SCTP_DETAILED_STR_STATS)
+ for (j = 0; j < SCTP_PR_SCTP_MAX + 1; j++) {
+ stcb->asoc.strmout[i].abandoned_sent[j] = 0;
+ stcb->asoc.strmout[i].abandoned_unsent[j] = 0;
+ }
+#else
+ stcb->asoc.strmout[i].abandoned_sent[0] = 0;
+ stcb->asoc.strmout[i].abandoned_unsent[0] = 0;
+#endif
+ stcb->asoc.strmout[i].next_mid_ordered = 0;
+ stcb->asoc.strmout[i].next_mid_unordered = 0;
+ stcb->asoc.strmout[i].sid = i;
+ stcb->asoc.strmout[i].last_msg_incomplete = 0;
+ stcb->asoc.ss_functions.sctp_ss_init_stream(stcb, &stcb->asoc.strmout[i], NULL);
+ stcb->asoc.strmout[i].state = SCTP_STREAM_CLOSED;
+ }
+ stcb->asoc.strm_realoutsize = stcb->asoc.streamoutcnt + adding_o;
+ SCTP_FREE(oldstream, SCTP_M_STRMO);
+ }
+skip_stuff:
+ if ((add_stream & 1) && (adding_o > 0)) {
+ asoc->strm_pending_add_size = adding_o;
+ asoc->peer_req_out = peer_asked;
+ sctp_add_an_out_stream(chk, seq, adding_o);
+ seq++;
+ asoc->stream_reset_outstanding++;
+ }
+ if ((add_stream & 2) && (adding_i > 0)) {
+ sctp_add_an_in_stream(chk, seq, adding_i);
+ seq++;
+ asoc->stream_reset_outstanding++;
+ }
+ if (send_in_req) {
+ sctp_add_stream_reset_in(chk, number_entries, list, seq);
+ seq++;
+ asoc->stream_reset_outstanding++;
+ }
+ if (send_tsn_req) {
+ sctp_add_stream_reset_tsn(chk, seq);
+ asoc->stream_reset_outstanding++;
+ }
+ asoc->str_reset = chk;
+ /* insert the chunk for sending */
+ TAILQ_INSERT_TAIL(&asoc->control_send_queue,
+ chk,
+ sctp_next);
+ asoc->ctrl_queue_cnt++;
+ if (stcb->asoc.send_sack) {
+ sctp_send_sack(stcb, SCTP_SO_LOCKED);
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo);
+ return (0);
+}
+
+void
+sctp_send_abort(struct mbuf *m, int iphlen, struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, uint32_t vtag, struct mbuf *cause,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ /* Don't respond to an ABORT with an ABORT. */
+ if (sctp_is_there_an_abort_here(m, iphlen, &vtag)) {
+ if (cause)
+ sctp_m_freem(cause);
+ return;
+ }
+ sctp_send_resp_msg(src, dst, sh, vtag, SCTP_ABORT_ASSOCIATION, cause,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ return;
+}
+
+void
+sctp_send_operr_to(struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, uint32_t vtag, struct mbuf *cause,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ sctp_send_resp_msg(src, dst, sh, vtag, SCTP_OPERATION_ERROR, cause,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ return;
+}
+
+static struct mbuf *
+sctp_copy_resume(struct uio *uio,
+ int max_send_len,
+#if defined(__FreeBSD__) || defined(__Userspace__)
+ int user_marks_eor,
+#endif
+ int *error,
+ uint32_t *sndout,
+ struct mbuf **new_tail)
+{
+#if defined(__FreeBSD__) || defined(__Userspace__)
+ struct mbuf *m;
+
+ m = m_uiotombuf(uio, M_WAITOK, max_send_len, 0,
+ (M_PKTHDR | (user_marks_eor ? M_EOR : 0)));
+ if (m == NULL) {
+ /* The only possible error is EFAULT. */
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ *error = EFAULT;
+ } else {
+ *sndout = m_length(m, NULL);
+ *new_tail = m_last(m);
+ }
+ return (m);
+#else
+ int left, cancpy, willcpy;
+ struct mbuf *m, *head;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ left = (int)min(uio->uio_resid, max_send_len);
+#else
+ left = (int)min(uio_resid(uio), max_send_len);
+#endif
+#else
+ left = (int)min(uio->uio_resid, max_send_len);
+#endif
+ /* Always get a header just in case */
+ head = sctp_get_mbuf_for_msg(left, 0, M_WAITOK, 0, MT_DATA);
+ if (head == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOBUFS);
+ *error = ENOBUFS;
+ return (NULL);
+ }
+ cancpy = (int)M_TRAILINGSPACE(head);
+ willcpy = min(cancpy, left);
+ *error = uiomove(mtod(head, caddr_t), willcpy, uio);
+ if (*error != 0) {
+ sctp_m_freem(head);
+ return (NULL);
+ }
+ *sndout += willcpy;
+ left -= willcpy;
+ SCTP_BUF_LEN(head) = willcpy;
+ m = head;
+ *new_tail = head;
+ while (left > 0) {
+ /* move in user data */
+ SCTP_BUF_NEXT(m) = sctp_get_mbuf_for_msg(left, 0, M_WAITOK, 0, MT_DATA);
+ if (SCTP_BUF_NEXT(m) == NULL) {
+ sctp_m_freem(head);
+ *new_tail = NULL;
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOBUFS);
+ *error = ENOBUFS;
+ return (NULL);
+ }
+ m = SCTP_BUF_NEXT(m);
+ cancpy = (int)M_TRAILINGSPACE(m);
+ willcpy = min(cancpy, left);
+ *error = uiomove(mtod(m, caddr_t), willcpy, uio);
+ if (*error != 0) {
+ sctp_m_freem(head);
+ *new_tail = NULL;
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, *error);
+ return (NULL);
+ }
+ SCTP_BUF_LEN(m) = willcpy;
+ left -= willcpy;
+ *sndout += willcpy;
+ *new_tail = m;
+ if (left == 0) {
+ SCTP_BUF_NEXT(m) = NULL;
+ }
+ }
+ return (head);
+#endif
+}
+
+static int
+sctp_copy_one(struct sctp_stream_queue_pending *sp,
+ struct uio *uio,
+ int resv_upfront)
+{
+#if defined(__FreeBSD__) || defined(__Userspace__)
+ sp->data = m_uiotombuf(uio, M_WAITOK, sp->length, resv_upfront, 0);
+ if (sp->data == NULL) {
+ /* The only possible error is EFAULT. */
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ return (EFAULT);
+ }
+ sp->tail_mbuf = m_last(sp->data);
+ return (0);
+#else
+ int left;
+ int cancpy, willcpy, error;
+ struct mbuf *m, *head;
+ int cpsz = 0;
+
+ /* First one gets a header */
+ left = sp->length;
+ head = m = sctp_get_mbuf_for_msg((left + resv_upfront), 0, M_WAITOK, 0, MT_DATA);
+ if (m == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOBUFS);
+ return (ENOBUFS);
+ }
+ /*-
+ * Add this one for m in now, that way if the alloc fails we won't
+ * have a bad cnt.
+ */
+ SCTP_BUF_RESV_UF(m, resv_upfront);
+ cancpy = (int)M_TRAILINGSPACE(m);
+ willcpy = min(cancpy, left);
+ while (left > 0) {
+ /* move in user data */
+ error = uiomove(mtod(m, caddr_t), willcpy, uio);
+ if (error) {
+ sctp_m_freem(head);
+ return (error);
+ }
+ SCTP_BUF_LEN(m) = willcpy;
+ left -= willcpy;
+ cpsz += willcpy;
+ if (left > 0) {
+ SCTP_BUF_NEXT(m) = sctp_get_mbuf_for_msg(left, 0, M_WAITOK, 0, MT_DATA);
+ if (SCTP_BUF_NEXT(m) == NULL) {
+ /*
+ * the head goes back to caller, he can free
+ * the rest
+ */
+ sctp_m_freem(head);
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOBUFS);
+ return (ENOBUFS);
+ }
+ m = SCTP_BUF_NEXT(m);
+ cancpy = (int)M_TRAILINGSPACE(m);
+ willcpy = min(cancpy, left);
+ } else {
+ sp->tail_mbuf = m;
+ SCTP_BUF_NEXT(m) = NULL;
+ }
+ }
+ sp->data = head;
+ sp->length = cpsz;
+ return (0);
+#endif
+}
+
+static struct sctp_stream_queue_pending *
+sctp_copy_it_in(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_nonpad_sndrcvinfo *srcv,
+ struct uio *uio,
+ struct sctp_nets *net,
+ ssize_t max_send_len,
+ int user_marks_eor,
+ int *error)
+
+{
+ /*-
+ * This routine must be very careful in its work. Protocol
+ * processing is up and running so care must be taken to spl...()
+ * when you need to do something that may effect the stcb/asoc. The
+ * sb is locked however. When data is copied the protocol processing
+ * should be enabled since this is a slower operation...
+ */
+ struct sctp_stream_queue_pending *sp;
+ int resv_in_first;
+
+ *error = 0;
+ sctp_alloc_a_strmoq(stcb, sp);
+ if (sp == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ *error = ENOMEM;
+ goto out_now;
+ }
+ sp->act_flags = 0;
+ sp->sender_all_done = 0;
+ sp->sinfo_flags = srcv->sinfo_flags;
+ sp->timetolive = srcv->sinfo_timetolive;
+ sp->ppid = srcv->sinfo_ppid;
+ sp->context = srcv->sinfo_context;
+ sp->fsn = 0;
+ (void)SCTP_GETTIME_TIMEVAL(&sp->ts);
+ sp->sid = srcv->sinfo_stream;
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ sp->length = (uint32_t)min(uio->uio_resid, max_send_len);
+#else
+ sp->length = (uint32_t)min(uio_resid(uio), max_send_len);
+#endif
+#else
+ sp->length = (uint32_t)min(uio->uio_resid, max_send_len);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if ((sp->length == (uint32_t)uio->uio_resid) &&
+#else
+ if ((sp->length == (uint32_t)uio_resid(uio)) &&
+#endif
+#else
+ if ((sp->length == (uint32_t)uio->uio_resid) &&
+#endif
+ ((user_marks_eor == 0) ||
+ (srcv->sinfo_flags & SCTP_EOF) ||
+ (user_marks_eor && (srcv->sinfo_flags & SCTP_EOR)))) {
+ sp->msg_is_complete = 1;
+ } else {
+ sp->msg_is_complete = 0;
+ }
+ sp->sender_all_done = 0;
+ sp->some_taken = 0;
+ sp->put_last_out = 0;
+ resv_in_first = SCTP_DATA_CHUNK_OVERHEAD(stcb);
+ sp->data = sp->tail_mbuf = NULL;
+ if (sp->length == 0) {
+ goto skip_copy;
+ }
+ if (srcv->sinfo_keynumber_valid) {
+ sp->auth_keyid = srcv->sinfo_keynumber;
+ } else {
+ sp->auth_keyid = stcb->asoc.authinfo.active_keyid;
+ }
+ if (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks)) {
+ sctp_auth_key_acquire(stcb, sp->auth_keyid);
+ sp->holds_key_ref = 1;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(SCTP_INP_SO(stcb->sctp_ep), 0);
+#endif
+ *error = sctp_copy_one(sp, uio, resv_in_first);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(SCTP_INP_SO(stcb->sctp_ep), 0);
+#endif
+ skip_copy:
+ if (*error) {
+#if defined(__Userspace__)
+ SCTP_TCB_LOCK(stcb);
+#endif
+ sctp_free_a_strmoq(stcb, sp, SCTP_SO_LOCKED);
+#if defined(__Userspace__)
+ SCTP_TCB_UNLOCK(stcb);
+#endif
+ sp = NULL;
+ } else {
+ if (sp->sinfo_flags & SCTP_ADDR_OVER) {
+ sp->net = net;
+ atomic_add_int(&sp->net->ref_count, 1);
+ } else {
+ sp->net = NULL;
+ }
+ sctp_set_prsctp_policy(sp);
+ }
+out_now:
+ return (sp);
+}
+
+int
+sctp_sosend(struct socket *so,
+ struct sockaddr *addr,
+ struct uio *uio,
+ struct mbuf *top,
+ struct mbuf *control,
+#if defined(__APPLE__) && !defined(__Userspace__)
+ int flags)
+#else
+ int flags,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *p)
+#elif defined(_WIN32) && !defined(__Userspace__)
+ PKTHREAD p)
+#else
+#if defined(__Userspace__)
+ /*
+ * proc is a dummy in __Userspace__ and will not be passed
+ * to sctp_lower_sosend
+ */
+#endif
+ struct proc *p)
+#endif
+#endif
+{
+ struct sctp_sndrcvinfo sndrcvninfo;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin;
+#endif
+ struct sockaddr *addr_to_use;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct proc *p = current_proc();
+#endif
+ int error;
+ bool use_sndinfo;
+
+ if (control != NULL) {
+ /* process cmsg snd/rcv info (maybe a assoc-id) */
+ use_sndinfo = sctp_find_cmsg(SCTP_SNDRCV, (void *)&sndrcvninfo, control, sizeof(sndrcvninfo));
+ } else {
+ use_sndinfo = false;
+ }
+#if defined(INET) && defined(INET6)
+ if ((addr != NULL) && (addr->sa_family == AF_INET6)) {
+ struct sockaddr_in6 *sin6;
+
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin, sin6);
+ addr_to_use = (struct sockaddr *)&sin;
+ } else {
+ addr_to_use = addr;
+ }
+ } else {
+ addr_to_use = addr;
+ }
+#else
+ addr_to_use = addr;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+ error = sctp_lower_sosend(so, addr_to_use, uio, top, control, flags,
+#if defined(__Userspace__)
+ use_sndinfo ? &sndrcvninfo : NULL);
+#else
+ use_sndinfo ? &sndrcvninfo : NULL, p);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (error);
+}
+
+int
+sctp_lower_sosend(struct socket *so,
+ struct sockaddr *addr,
+ struct uio *uio,
+ struct mbuf *top,
+ struct mbuf *control,
+ int flags,
+#if defined(__Userspace__)
+ struct sctp_sndrcvinfo *srcv)
+#else
+ struct sctp_sndrcvinfo *srcv,
+#if defined(__FreeBSD__)
+ struct thread *p)
+#elif defined(_WIN32)
+ PKTHREAD p)
+#else
+ struct proc *p)
+#endif
+#endif
+{
+ struct sctp_nonpad_sndrcvinfo sndrcvninfo_buf;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct timeval now;
+ struct sctp_block_entry be;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb = NULL;
+ struct sctp_nets *net;
+ struct sctp_association *asoc;
+ struct sctp_inpcb *t_inp;
+ struct sctp_nonpad_sndrcvinfo *sndrcvninfo;
+ ssize_t sndlen = 0, max_len, local_add_more;
+ ssize_t local_soresv = 0;
+ sctp_assoc_t sinfo_assoc_id;
+ int user_marks_eor;
+ int nagle_applies = 0;
+ int error;
+ int queue_only = 0, queue_only_for_init = 0;
+ int un_sent;
+ int now_filled = 0;
+ unsigned int inqueue_bytes = 0;
+ uint16_t port;
+ uint16_t sinfo_flags;
+ uint16_t sinfo_stream;
+ bool create_lock_applied = false;
+ bool free_cnt_applied = false;
+ bool some_on_control;
+ bool got_all_of_the_send = false;
+ bool non_blocking = false;
+
+ error = 0;
+ net = NULL;
+ stcb = NULL;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sctp_lock_assert(so);
+#endif
+ if ((uio == NULL) && (top == NULL)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ if (addr != NULL) {
+ union sctp_sockstore *raddr = (union sctp_sockstore *)addr;
+
+ switch (raddr->sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SIN_LEN
+ if (raddr->sin.sin_len != sizeof(struct sockaddr_in)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+#endif
+ port = raddr->sin.sin_port;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SIN6_LEN
+ if (raddr->sin6.sin6_len != sizeof(struct sockaddr_in6)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+#endif
+ port = raddr->sin6.sin6_port;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+#ifdef HAVE_SCONN_LEN
+ if (raddr->sconn.sconn_len != sizeof(struct sockaddr_conn)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+#endif
+ port = raddr->sconn.sconn_port;
+ break;
+#endif
+ default:
+ error = EAFNOSUPPORT;
+ goto out_unlocked;
+ }
+ } else {
+ port = 0;
+ }
+ if (uio != NULL) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if (uio->uio_resid < 0) {
+#else
+ if (uio_resid(uio) < 0) {
+#endif
+#else
+ if (uio->uio_resid < 0) {
+#endif
+ error = EINVAL;
+ goto out_unlocked;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ sndlen = uio->uio_resid;
+#else
+ sndlen = uio_resid(uio);
+#endif
+#else
+ sndlen = uio->uio_resid;
+#endif
+ } else {
+ sndlen = SCTP_HEADER_LEN(top);
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Send called addr:%p send length %zd\n",
+ (void *)addr, sndlen);
+
+ t_inp = inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ user_marks_eor = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR);
+ if ((uio == NULL) && (user_marks_eor != 0)) {
+ /*-
+ * We do not support eeor mode for
+ * sending with mbuf chains (like sendfile).
+ */
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ SCTP_IS_LISTENING(inp)) {
+ /* The listener can NOT send. */
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ atomic_add_int(&inp->total_sends, 1);
+
+ if (srcv != NULL) {
+ sndrcvninfo = (struct sctp_nonpad_sndrcvinfo *)srcv;
+ sinfo_assoc_id = sndrcvninfo->sinfo_assoc_id;
+ sinfo_flags = sndrcvninfo->sinfo_flags;
+ if (INVALID_SINFO_FLAG(sinfo_flags) ||
+ PR_SCTP_INVALID_POLICY(sinfo_flags)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ if (sinfo_flags != 0) {
+ SCTP_STAT_INCR(sctps_sends_with_flags);
+ }
+ } else {
+ sndrcvninfo = NULL;
+ sinfo_flags = inp->def_send.sinfo_flags;
+ sinfo_assoc_id = inp->def_send.sinfo_assoc_id;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (flags & MSG_EOR) {
+ sinfo_flags |= SCTP_EOR;
+ }
+ if (flags & MSG_EOF) {
+ sinfo_flags |= SCTP_EOF;
+ }
+#endif
+ if ((sinfo_flags & SCTP_ADDR_OVER) && (addr == NULL)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ SCTP_INP_RLOCK(inp);
+ if ((sinfo_flags & SCTP_SENDALL) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE)) {
+ SCTP_INP_RUNLOCK(inp);
+ error = sctp_sendall(inp, uio, top, sndrcvninfo);
+ top = NULL;
+ goto out_unlocked;
+ }
+ /* Now we must find the association. */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else if (sinfo_assoc_id > SCTP_ALL_ASSOC) {
+ stcb = sctp_findasoc_ep_asocid_locked(inp, sinfo_assoc_id, 1);
+ SCTP_INP_RUNLOCK(inp);
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ }
+ } else if (addr != NULL) {
+ /*-
+ * Since we did not use findep we must
+ * increment it, and if we don't find a tcb
+ * decrement it.
+ */
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_RUNLOCK(inp);
+ stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ }
+ } else {
+ SCTP_INP_RUNLOCK(inp);
+ }
+
+#ifdef INVARIANTS
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ }
+#endif
+
+ if ((stcb == NULL) && (addr != NULL)) {
+ /* Possible implicit send? */
+ SCTP_ASOC_CREATE_LOCK(inp);
+ create_lock_applied = true;
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) &&
+ (addr->sa_family == AF_INET6)) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ /* With the lock applied look again */
+ stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL);
+#if defined(INET) || defined(INET6)
+ if ((stcb == NULL) && (control != NULL) && (port > 0)) {
+ stcb = sctp_findassociation_cmsgs(&t_inp, port, control, &net, &error);
+ }
+#endif
+ if (stcb == NULL) {
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ create_lock_applied = false;
+ }
+ if (error != 0) {
+ goto out_unlocked;
+ }
+ if (t_inp != inp) {
+ error = ENOTCONN;
+ goto out_unlocked;
+ }
+ }
+ if (stcb == NULL) {
+ if (addr == NULL) {
+ error = ENOENT;
+ goto out_unlocked;
+ } else {
+ /* We must go ahead and start the INIT process */
+ uint32_t vrf_id;
+
+ if ((sinfo_flags & SCTP_ABORT) ||
+ ((sinfo_flags & SCTP_EOF) && (sndlen == 0))) {
+ /*-
+ * User asks to abort a non-existent assoc,
+ * or EOF a non-existent assoc with no data
+ */
+ error = ENOENT;
+ goto out_unlocked;
+ }
+ /* get an asoc/stcb struct */
+ vrf_id = inp->def_vrf_id;
+ KASSERT(create_lock_applied, ("create_lock_applied is false"));
+ stcb = sctp_aloc_assoc_connected(inp, addr, &error, 0, 0, vrf_id,
+ inp->sctp_ep.pre_open_stream_count,
+ inp->sctp_ep.port,
+#if !defined(__Userspace__)
+ p,
+#else
+ (struct proc *)NULL,
+#endif
+ SCTP_INITIALIZE_AUTH_PARAMS);
+ if (stcb == NULL) {
+ /* error is setup for us in the call. */
+ KASSERT(error != 0, ("error is 0 although stcb is NULL"));
+ goto out_unlocked;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ create_lock_applied = false;
+ /* Turn on queue only flag to prevent data from being sent */
+ queue_only = 1;
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+ if (control != NULL) {
+ if (sctp_process_cmsgs_for_init(stcb, control, &error)) {
+ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_6);
+ stcb = NULL;
+ KASSERT(error != 0,
+ ("error is 0 although sctp_process_cmsgs_for_init() indicated an error"));
+ goto out_unlocked;
+ }
+ }
+ /* out with the INIT */
+ queue_only_for_init = 1;
+ /*-
+ * we may want to dig in after this call and adjust the MTU
+ * value. It defaulted to 1500 (constant) but the ro
+ * structure may now have an update and thus we may need to
+ * change it BEFORE we append the message.
+ */
+ }
+ }
+
+ KASSERT(!create_lock_applied, ("create_lock_applied is true"));
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ asoc = &stcb->asoc;
+ if ((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
+ (asoc->state & SCTP_STATE_WAS_ABORTED)) {
+ if (asoc->state & SCTP_STATE_WAS_ABORTED) {
+ /* XXX: Could also be ECONNABORTED, not enough info. */
+ error = ECONNRESET;
+ } else {
+ error = ENOTCONN;
+ }
+ goto out_unlocked;
+ }
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ queue_only = 1;
+ }
+ /* Keep the stcb from being freed under our feet. */
+ atomic_add_int(&asoc->refcnt, 1);
+ free_cnt_applied = true;
+ if (sndrcvninfo == NULL) {
+ /* Use a local copy to have a consistent view. */
+ sndrcvninfo_buf = asoc->def_send;
+ sndrcvninfo = &sndrcvninfo_buf;
+ sinfo_flags = sndrcvninfo->sinfo_flags;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (flags & MSG_EOR) {
+ sinfo_flags |= SCTP_EOR;
+ }
+ if (flags & MSG_EOF) {
+ sinfo_flags |= SCTP_EOF;
+ }
+#endif
+ }
+ /* Are we aborting? */
+ if (sinfo_flags & SCTP_ABORT) {
+ struct mbuf *mm;
+ struct sctp_paramhdr *ph;
+ ssize_t tot_demand, tot_out = 0, max_out;
+
+ SCTP_STAT_INCR(sctps_sends_with_abort);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ /* It has to be up before we abort. */
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ /* How big is the user initiated abort? */
+ if (top != NULL) {
+ struct mbuf *cntm;
+
+ if (sndlen != 0) {
+ for (cntm = top; cntm; cntm = SCTP_BUF_NEXT(cntm)) {
+ tot_out += SCTP_BUF_LEN(cntm);
+ }
+ }
+ mm = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_NOWAIT, 1, MT_DATA);
+ } else {
+ /* Must fit in a MTU */
+ tot_out = sndlen;
+ tot_demand = (tot_out + sizeof(struct sctp_paramhdr));
+ if (tot_demand > SCTP_DEFAULT_ADD_MORE) {
+ error = EMSGSIZE;
+ goto out_unlocked;
+ }
+ mm = sctp_get_mbuf_for_msg((unsigned int)tot_demand, 0, M_NOWAIT, 1, MT_DATA);
+ }
+ if (mm == NULL) {
+ error = ENOMEM;
+ goto out_unlocked;
+ }
+ max_out = asoc->smallest_mtu - sizeof(struct sctp_paramhdr);
+ max_out -= sizeof(struct sctp_abort_msg);
+ if (tot_out > max_out) {
+ tot_out = max_out;
+ }
+ ph = mtod(mm, struct sctp_paramhdr *);
+ ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT);
+ ph->param_length = htons((uint16_t)(sizeof(struct sctp_paramhdr) + tot_out));
+ ph++;
+ SCTP_BUF_LEN(mm) = (int)(tot_out + sizeof(struct sctp_paramhdr));
+ if (top == NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 0);
+#endif
+ error = uiomove((caddr_t)ph, (int)tot_out, uio);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(so, 0);
+#endif
+ SCTP_TCB_LOCK(stcb);
+ if ((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
+ (asoc->state & SCTP_STATE_WAS_ABORTED)) {
+ sctp_m_freem(mm);
+ if (asoc->state & SCTP_STATE_WAS_ABORTED) {
+ /* XXX: Could also be ECONNABORTED, not enough info. */
+ error = ECONNRESET;
+ } else {
+ error = ENOTCONN;
+ }
+ goto out_unlocked;
+ }
+ if (error != 0) {
+ /*-
+ * Here if we can't get his data we
+ * still abort we just don't get to
+ * send the users note :-0
+ */
+ sctp_m_freem(mm);
+ mm = NULL;
+ error = 0;
+ }
+ } else {
+ if (sndlen != 0) {
+ SCTP_BUF_NEXT(mm) = top;
+ }
+ }
+ atomic_subtract_int(&asoc->refcnt, 1);
+ free_cnt_applied = false;
+ /* release this lock, otherwise we hang on ourselves */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_abort_an_association(stcb->sctp_ep, stcb, mm, false, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ stcb = NULL;
+ /* In this case top is already chained to mm
+ * avoid double free, since we free it below if
+ * top != NULL and driver would free it after sending
+ * the packet out
+ */
+ if (sndlen != 0) {
+ top = NULL;
+ }
+ goto out_unlocked;
+ }
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+ if (sinfo_flags & SCTP_ADDR_OVER) {
+ if (addr != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ net = NULL;
+ }
+ if ((net == NULL) ||
+ ((port != 0) && (port != stcb->rport))) {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ } else {
+ if (asoc->alternate != NULL) {
+ net = asoc->alternate;
+ } else {
+ net = asoc->primary_destination;
+ }
+ }
+ if (sndlen == 0) {
+ if (sinfo_flags & SCTP_EOF) {
+ got_all_of_the_send = true;
+ goto dataless_eof;
+ } else {
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ }
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NO_FRAGMENT)) {
+ if (sndlen > (ssize_t)asoc->smallest_mtu) {
+ error = EMSGSIZE;
+ goto out_unlocked;
+ }
+ }
+ sinfo_stream = sndrcvninfo->sinfo_stream;
+ /* Is the stream no. valid? */
+ if (sinfo_stream >= asoc->streamoutcnt) {
+ /* Invalid stream number */
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ if ((asoc->strmout[sinfo_stream].state != SCTP_STREAM_OPEN) &&
+ (asoc->strmout[sinfo_stream].state != SCTP_STREAM_OPENING)) {
+ /*
+ * Can't queue any data while stream reset is underway.
+ */
+ if (asoc->strmout[sinfo_stream].state > SCTP_STREAM_OPEN) {
+ error = EAGAIN;
+ } else {
+ error = EINVAL;
+ }
+ goto out_unlocked;
+ }
+ atomic_add_int(&stcb->total_sends, 1);
+#if defined(__Userspace__)
+ if (inp->recv_callback != NULL) {
+ non_blocking = true;
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (SCTP_SO_IS_NBIO(so) || (flags & (MSG_NBIO | MSG_DONTWAIT)) != 0) {
+#else
+ if (SCTP_SO_IS_NBIO(so)) {
+#endif
+ non_blocking = true;
+ }
+ if (non_blocking) {
+ ssize_t amount;
+
+ inqueue_bytes = asoc->total_output_queue_size - (asoc->chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ if (user_marks_eor == 0) {
+ amount = sndlen;
+ } else {
+ amount = 1;
+ }
+ if ((SCTP_SB_LIMIT_SND(so) < (amount + inqueue_bytes + asoc->sb_send_resv)) ||
+ (asoc->chunks_on_out_queue >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
+ if ((sndlen > (ssize_t)SCTP_SB_LIMIT_SND(so)) &&
+ (user_marks_eor == 0)) {
+ error = EMSGSIZE;
+ } else {
+ error = EWOULDBLOCK;
+ }
+ goto out_unlocked;
+ }
+ }
+ atomic_add_int(&asoc->sb_send_resv, (int)sndlen);
+ local_soresv = sndlen;
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+ /* Ok, we will attempt a msgsnd :> */
+#if !(defined(_WIN32) || defined(__Userspace__))
+ if (p != NULL) {
+#if defined(__FreeBSD__)
+ p->td_ru.ru_msgsnd++;
+#else
+ p->p_stats->p_ru.ru_msgsnd++;
+#endif
+ }
+#endif
+ /* Calculate the maximum we can send */
+ inqueue_bytes = asoc->total_output_queue_size - (asoc->chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes) {
+ max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
+ } else {
+ max_len = 0;
+ }
+ /* Unless E_EOR mode is on, we must make a send FIT in one call. */
+ if ((user_marks_eor == 0) &&
+ (sndlen > (ssize_t)SCTP_SB_LIMIT_SND(stcb->sctp_socket))) {
+ /* It will NEVER fit. */
+ error = EMSGSIZE;
+ goto out_unlocked;
+ }
+ if (user_marks_eor != 0) {
+ local_add_more = (ssize_t)min(SCTP_SB_LIMIT_SND(so), SCTP_BASE_SYSCTL(sctp_add_more_threshold));
+ } else {
+ /*-
+ * For non-eeor the whole message must fit in
+ * the socket send buffer.
+ */
+ local_add_more = sndlen;
+ }
+ if (non_blocking) {
+ goto skip_preblock;
+ }
+ if (((max_len <= local_add_more) && ((ssize_t)SCTP_SB_LIMIT_SND(so) >= local_add_more)) ||
+ (max_len == 0) ||
+ ((asoc->chunks_on_out_queue + asoc->stream_queue_cnt) >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
+ /* No room right now! */
+ inqueue_bytes = asoc->total_output_queue_size - (asoc->chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ SOCKBUF_LOCK(&so->so_snd);
+ while ((SCTP_SB_LIMIT_SND(so) < (inqueue_bytes + local_add_more)) ||
+ ((asoc->stream_queue_cnt + asoc->chunks_on_out_queue) >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1,"pre_block limit:%u <(inq:%d + %zd) || (%d+%d > %d)\n",
+ (unsigned int)SCTP_SB_LIMIT_SND(so),
+ inqueue_bytes,
+ local_add_more,
+ asoc->stream_queue_cnt,
+ asoc->chunks_on_out_queue,
+ SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue));
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+ sctp_log_block(SCTP_BLOCK_LOG_INTO_BLKA, asoc, sndlen);
+ }
+ be.error = 0;
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ stcb->block_entry = &be;
+#endif
+ SCTP_TCB_UNLOCK(stcb);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ error = sbwait(so, SO_SND);
+#else
+ error = sbwait(&so->so_snd);
+#endif
+ if (error == 0) {
+ if (so->so_error != 0) {
+ error = so->so_error;
+ }
+ if (be.error != 0) {
+ error = be.error;
+ }
+ }
+ SOCKBUF_UNLOCK(&so->so_snd);
+ SCTP_TCB_LOCK(stcb);
+ stcb->block_entry = NULL;
+ if (error != 0) {
+ goto out_unlocked;
+ }
+ if ((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
+ (asoc->state & SCTP_STATE_WAS_ABORTED)) {
+ if (asoc->state & SCTP_STATE_WAS_ABORTED) {
+ /* XXX: Could also be ECONNABORTED, not enough info. */
+ error = ECONNRESET;
+ } else {
+ error = ENOTCONN;
+ }
+ goto out_unlocked;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+ sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK,
+ asoc, asoc->total_output_queue_size);
+ }
+ inqueue_bytes = asoc->total_output_queue_size - (asoc->chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ SOCKBUF_LOCK(&so->so_snd);
+ }
+ if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes) {
+ max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
+ } else {
+ max_len = 0;
+ }
+ SOCKBUF_UNLOCK(&so->so_snd);
+ }
+
+skip_preblock:
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ error = sblock(&so->so_snd, SBLOCKWAIT(flags));
+ if (error != 0) {
+ goto out_unlocked;
+ }
+#endif
+ /* sndlen covers for mbuf case
+ * uio_resid covers for the non-mbuf case
+ * NOTE: uio will be null when top/mbuf is passed
+ */
+ if (top == NULL) {
+ struct sctp_stream_queue_pending *sp;
+ struct sctp_stream_out *strm;
+ uint32_t sndout;
+
+ if ((asoc->stream_locked) &&
+ (asoc->stream_locked_on != sinfo_stream)) {
+ error = EINVAL;
+ goto out;
+ }
+ strm = &asoc->strmout[sinfo_stream];
+ if (strm->last_msg_incomplete == 0) {
+ do_a_copy_in:
+ SCTP_TCB_UNLOCK(stcb);
+ sp = sctp_copy_it_in(stcb, asoc, sndrcvninfo, uio, net, max_len, user_marks_eor, &error);
+ SCTP_TCB_LOCK(stcb);
+ if ((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
+ (asoc->state & SCTP_STATE_WAS_ABORTED)) {
+ if (asoc->state & SCTP_STATE_WAS_ABORTED) {
+ /* XXX: Could also be ECONNABORTED, not enough info. */
+ error = ECONNRESET;
+ } else {
+ error = ENOTCONN;
+ }
+ goto out;
+ }
+ if (error != 0) {
+ goto out;
+ }
+ /*
+ * Reject the sending of a new user message, if the
+ * association is about to be shut down.
+ */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_SENT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_ACK_SENT) ||
+ (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) {
+ if (sp->data != 0) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ sp->tail_mbuf = NULL;
+ sp->length = 0;
+ }
+ if (sp->net != NULL) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ sctp_free_a_strmoq(stcb, sp, SCTP_SO_LOCKED);
+ error = EPIPE;
+ goto out_unlocked;
+ }
+ /* The out streams might be reallocated. */
+ strm = &asoc->strmout[sinfo_stream];
+ if (sp->msg_is_complete) {
+ strm->last_msg_incomplete = 0;
+ asoc->stream_locked = 0;
+ } else {
+ /* Just got locked to this guy in
+ * case of an interrupt.
+ */
+ strm->last_msg_incomplete = 1;
+ if (asoc->idata_supported == 0) {
+ asoc->stream_locked = 1;
+ asoc->stream_locked_on = sinfo_stream;
+ }
+ sp->sender_all_done = 0;
+ }
+ sctp_snd_sb_alloc(stcb, sp->length);
+ atomic_add_int(&asoc->stream_queue_cnt, 1);
+ if (sinfo_flags & SCTP_UNORDERED) {
+ SCTP_STAT_INCR(sctps_sends_with_unord);
+ }
+ sp->processing = 1;
+ TAILQ_INSERT_TAIL(&strm->outqueue, sp, next);
+ asoc->ss_functions.sctp_ss_add_to_stream(stcb, asoc, strm, sp);
+ } else {
+ sp = TAILQ_LAST(&strm->outqueue, sctp_streamhead);
+ if (sp == NULL) {
+ /* ???? Huh ??? last msg is gone */
+#ifdef INVARIANTS
+ panic("Warning: Last msg marked incomplete, yet nothing left?");
+#else
+ SCTP_PRINTF("Warning: Last msg marked incomplete, yet nothing left?\n");
+ strm->last_msg_incomplete = 0;
+#endif
+ goto do_a_copy_in;
+ }
+ if (sp->processing != 0) {
+ error = EINVAL;
+ goto out;
+ } else {
+ sp->processing = 1;
+ }
+ }
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ while (uio->uio_resid > 0) {
+#else
+ while (uio_resid(uio) > 0) {
+#endif
+#else
+ while (uio->uio_resid > 0) {
+#endif
+ /* How much room do we have? */
+ struct mbuf *new_tail, *mm;
+
+ inqueue_bytes = asoc->total_output_queue_size - (asoc->chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes) {
+ max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
+ } else {
+ max_len = 0;
+ }
+ if ((max_len > (ssize_t)SCTP_BASE_SYSCTL(sctp_add_more_threshold)) ||
+ ((max_len > 0 ) && (SCTP_SB_LIMIT_SND(so) < SCTP_BASE_SYSCTL(sctp_add_more_threshold))) ||
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ (uio->uio_resid <= max_len)) {
+#else
+ (uio_resid(uio) <= max_len)) {
+#endif
+#else
+ (uio->uio_resid <= max_len)) {
+#endif
+ SCTP_TCB_UNLOCK(stcb);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 0);
+#endif
+ sndout = 0;
+ new_tail = NULL;
+#if defined(__FreeBSD__) || defined(__Userspace__)
+ mm = sctp_copy_resume(uio, (int)max_len, user_marks_eor, &error, &sndout, &new_tail);
+#else
+ mm = sctp_copy_resume(uio, (int)max_len, &error, &sndout, &new_tail);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(so, 0);
+#endif
+ SCTP_TCB_LOCK(stcb);
+ if ((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
+ (asoc->state & SCTP_STATE_WAS_ABORTED)) {
+ /* We need to get out.
+ * Peer probably aborted.
+ */
+ sctp_m_freem(mm);
+ if (asoc->state & SCTP_STATE_WAS_ABORTED) {
+ /* XXX: Could also be ECONNABORTED, not enough info. */
+ error = ECONNRESET;
+ } else {
+ error = ENOTCONN;
+ }
+ goto out;
+ }
+ if ((mm == NULL) || (error != 0)) {
+ if (mm != NULL) {
+ sctp_m_freem(mm);
+ }
+ if (sp != NULL) {
+ sp->processing = 0;
+ }
+ goto out;
+ }
+ /* Update the mbuf and count */
+ if (sp->tail_mbuf != NULL) {
+ /* Tack it to the end. */
+ SCTP_BUF_NEXT(sp->tail_mbuf) = mm;
+ } else {
+ /* A stolen mbuf. */
+ sp->data = mm;
+ }
+ sp->tail_mbuf = new_tail;
+ sctp_snd_sb_alloc(stcb, sndout);
+ atomic_add_int(&sp->length, sndout);
+ if (sinfo_flags & SCTP_SACK_IMMEDIATELY) {
+ sp->sinfo_flags |= SCTP_SACK_IMMEDIATELY;
+ }
+
+ /* Did we reach EOR? */
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if ((uio->uio_resid == 0) &&
+#else
+ if ((uio_resid(uio) == 0) &&
+#endif
+#else
+ if ((uio->uio_resid == 0) &&
+#endif
+ ((user_marks_eor == 0) ||
+ (sinfo_flags & SCTP_EOF) ||
+ (user_marks_eor && (sinfo_flags & SCTP_EOR)))) {
+ sp->msg_is_complete = 1;
+ } else {
+ sp->msg_is_complete = 0;
+ }
+ }
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if (uio->uio_resid == 0) {
+#else
+ if (uio_resid(uio) == 0) {
+#endif
+#else
+ if (uio->uio_resid == 0) {
+#endif
+ /* got it all? */
+ continue;
+ }
+ /* PR-SCTP? */
+ if ((asoc->prsctp_supported) && (asoc->sent_queue_cnt_removeable > 0)) {
+ /* This is ugly but we must assure locking order */
+ sctp_prune_prsctp(stcb, asoc, sndrcvninfo, (int)sndlen);
+ inqueue_bytes = asoc->total_output_queue_size - (asoc->chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes)
+ max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
+ else
+ max_len = 0;
+ if (max_len > 0) {
+ continue;
+ }
+ }
+ /* wait for space now */
+ if (non_blocking) {
+ /* Non-blocking io in place out */
+ if (sp != NULL) {
+ sp->processing = 0;
+ }
+ goto skip_out_eof;
+ }
+ /* What about the INIT, send it maybe */
+ if (queue_only_for_init) {
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+ /* a collision took us forward? */
+ queue_only = 0;
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ queue_only = 1;
+ }
+ }
+ if ((net->flight_size > net->cwnd) &&
+ (asoc->sctp_cmt_on_off == 0)) {
+ SCTP_STAT_INCR(sctps_send_cwnd_avoid);
+ queue_only = 1;
+ } else if (asoc->ifp_had_enobuf) {
+ SCTP_STAT_INCR(sctps_ifnomemqueued);
+ if (net->flight_size > (2 * net->mtu)) {
+ queue_only = 1;
+ }
+ asoc->ifp_had_enobuf = 0;
+ }
+ un_sent = asoc->total_output_queue_size - asoc->total_flight;
+ if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) &&
+ (asoc->total_flight > 0) &&
+ (asoc->stream_queue_cnt < SCTP_MAX_DATA_BUNDLING) &&
+ (un_sent < (int)(asoc->smallest_mtu - SCTP_MIN_OVERHEAD))) {
+ /*-
+ * Ok, Nagle is set on and we have data outstanding.
+ * Don't send anything and let SACKs drive out the
+ * data unless we have a "full" segment to send.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
+ sctp_log_nagle_event(stcb, SCTP_NAGLE_APPLIED);
+ }
+ SCTP_STAT_INCR(sctps_naglequeued);
+ nagle_applies = 1;
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
+ if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY))
+ sctp_log_nagle_event(stcb, SCTP_NAGLE_SKIPPED);
+ }
+ SCTP_STAT_INCR(sctps_naglesent);
+ nagle_applies = 0;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_CWNDLOG_PRESEND, queue_only_for_init, queue_only,
+ nagle_applies, un_sent);
+ sctp_misc_ints(SCTP_CWNDLOG_PRESEND, asoc->total_output_queue_size,
+ asoc->total_flight,
+ asoc->chunks_on_out_queue, asoc->total_flight_count);
+ }
+ if (queue_only_for_init) {
+ queue_only_for_init = 0;
+ }
+ if ((queue_only == 0) && (nagle_applies == 0)) {
+ /*-
+ * need to start chunk output
+ * before blocking.. note that if
+ * a lock is already applied, then
+ * the input via the net is happening
+ * and I don't need to start output :-D
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_chunk_output(inp, stcb,
+ SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ }
+ /*-
+ * This is a bit strange, but I think it will
+ * work. The total_output_queue_size is locked and
+ * protected by the TCB_LOCK, which we just released.
+ * There is a race that can occur between releasing it
+ * above, and me getting the socket lock, where sacks
+ * come in but we have not put the SB_WAIT on the
+ * so_snd buffer to get the wakeup. After the LOCK
+ * is applied the sack_processing will also need to
+ * LOCK the so->so_snd to do the actual sowwakeup(). So
+ * once we have the socket buffer lock if we recheck the
+ * size we KNOW we will get to sleep safely with the
+ * wakeup flag in place.
+ */
+ inqueue_bytes = asoc->total_output_queue_size - (asoc->chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ SOCKBUF_LOCK(&so->so_snd);
+ if (SCTP_SB_LIMIT_SND(so) <= (inqueue_bytes +
+ min(SCTP_BASE_SYSCTL(sctp_add_more_threshold), SCTP_SB_LIMIT_SND(so)))) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK,
+ asoc, uio->uio_resid);
+#else
+ sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK,
+ asoc, uio_resid(uio));
+#endif
+#else
+ sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK,
+ asoc, uio->uio_resid);
+#endif
+ }
+ be.error = 0;
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ stcb->block_entry = &be;
+#endif
+ SCTP_TCB_UNLOCK(stcb);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&so->so_snd, 1);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ error = sbwait(so, SO_SND);
+#else
+ error = sbwait(&so->so_snd);
+#endif
+ if (error == 0) {
+ if (so->so_error != 0)
+ error = so->so_error;
+ if (be.error != 0) {
+ error = be.error;
+ }
+ }
+ SOCKBUF_UNLOCK(&so->so_snd);
+ SCTP_TCB_LOCK(stcb);
+ stcb->block_entry = NULL;
+ if ((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
+ (asoc->state & SCTP_STATE_WAS_ABORTED)) {
+ if (asoc->state & SCTP_STATE_WAS_ABORTED) {
+ /* XXX: Could also be ECONNABORTED, not enough info. */
+ error = ECONNRESET;
+ } else {
+ error = ENOTCONN;
+ }
+ goto out_unlocked;
+ }
+ if (error != 0) {
+ if (sp != NULL) {
+ sp->processing = 0;
+ }
+ goto out_unlocked;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ error = sblock(&so->so_snd, SBLOCKWAIT(flags));
+ if (error != 0) {
+ goto out_unlocked;
+ }
+#endif
+ } else {
+ SOCKBUF_UNLOCK(&so->so_snd);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+ sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK,
+ asoc, asoc->total_output_queue_size);
+ }
+ }
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+ /* The out streams might be reallocated. */
+ strm = &asoc->strmout[sinfo_stream];
+ if (sp != NULL) {
+ if (sp->msg_is_complete == 0) {
+ strm->last_msg_incomplete = 1;
+ if (asoc->idata_supported == 0) {
+ asoc->stream_locked = 1;
+ asoc->stream_locked_on = sinfo_stream;
+ }
+ } else {
+ sp->sender_all_done = 1;
+ strm->last_msg_incomplete = 0;
+ asoc->stream_locked = 0;
+ }
+ sp->processing = 0;
+ } else {
+ SCTP_PRINTF("Huh no sp TSNH?\n");
+ strm->last_msg_incomplete = 0;
+ asoc->stream_locked = 0;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if (uio->uio_resid == 0) {
+#else
+ if (uio_resid(uio) == 0) {
+#endif
+#else
+ if (uio->uio_resid == 0) {
+#endif
+ got_all_of_the_send = true;
+ }
+ } else {
+ error = sctp_msg_append(stcb, net, top, sndrcvninfo);
+ top = NULL;
+ if ((sinfo_flags & SCTP_EOF) != 0) {
+ got_all_of_the_send = true;
+ }
+ }
+ if (error != 0) {
+ goto out;
+ }
+
+dataless_eof:
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+ /* EOF thing ? */
+ if ((sinfo_flags & SCTP_EOF) && got_all_of_the_send) {
+ SCTP_STAT_INCR(sctps_sends_with_eof);
+ error = 0;
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ sctp_is_there_unsent_data(stcb, SCTP_SO_LOCKED) == 0) {
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ goto abort_anyway;
+ }
+ /* there is nothing queued to send, so I'm done... */
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ struct sctp_nets *netp;
+
+ /* only send SHUTDOWN the first time through */
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (asoc->alternate != NULL) {
+ netp = asoc->alternate;
+ } else {
+ netp = asoc->primary_destination;
+ }
+ sctp_send_shutdown(stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb,
+ netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
+ NULL);
+ }
+ } else {
+ /*-
+ * we still got (or just got) data to send, so set
+ * SHUTDOWN_PENDING
+ */
+ /*-
+ * XXX sockets draft says that SCTP_EOF should be
+ * sent with no data. currently, we will allow user
+ * data to be sent first and move to
+ * SHUTDOWN-PENDING
+ */
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING);
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ abort_anyway:
+ if (free_cnt_applied) {
+ atomic_subtract_int(&asoc->refcnt, 1);
+ free_cnt_applied = false;
+ }
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "%s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_abort_an_association(stcb->sctp_ep, stcb,
+ op_err, false, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ stcb = NULL;
+ error = ECONNABORTED;
+ goto out;
+ }
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_NODELAY);
+ }
+ }
+ }
+
+skip_out_eof:
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+ some_on_control = !TAILQ_EMPTY(&asoc->control_send_queue);
+ if (queue_only_for_init) {
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+ /* a collision took us forward? */
+ queue_only = 0;
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ queue_only = 1;
+ }
+ }
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+ if ((net->flight_size > net->cwnd) &&
+ (asoc->sctp_cmt_on_off == 0)) {
+ SCTP_STAT_INCR(sctps_send_cwnd_avoid);
+ queue_only = 1;
+ } else if (asoc->ifp_had_enobuf) {
+ SCTP_STAT_INCR(sctps_ifnomemqueued);
+ if (net->flight_size > (2 * net->mtu)) {
+ queue_only = 1;
+ }
+ asoc->ifp_had_enobuf = 0;
+ }
+ un_sent = asoc->total_output_queue_size - asoc->total_flight;
+ if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) &&
+ (asoc->total_flight > 0) &&
+ (asoc->stream_queue_cnt < SCTP_MAX_DATA_BUNDLING) &&
+ (un_sent < (int)(asoc->smallest_mtu - SCTP_MIN_OVERHEAD))) {
+ /*-
+ * Ok, Nagle is set on and we have data outstanding.
+ * Don't send anything and let SACKs drive out the
+ * data unless wen have a "full" segment to send.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
+ sctp_log_nagle_event(stcb, SCTP_NAGLE_APPLIED);
+ }
+ SCTP_STAT_INCR(sctps_naglequeued);
+ nagle_applies = 1;
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
+ if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY))
+ sctp_log_nagle_event(stcb, SCTP_NAGLE_SKIPPED);
+ }
+ SCTP_STAT_INCR(sctps_naglesent);
+ nagle_applies = 0;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_CWNDLOG_PRESEND, queue_only_for_init, queue_only,
+ nagle_applies, un_sent);
+ sctp_misc_ints(SCTP_CWNDLOG_PRESEND, asoc->total_output_queue_size,
+ asoc->total_flight,
+ asoc->chunks_on_out_queue, asoc->total_flight_count);
+ }
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ if ((queue_only == 0) && (nagle_applies == 0) && (asoc->peers_rwnd && un_sent)) {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+ } else if ((queue_only == 0) &&
+ (asoc->peers_rwnd == 0) &&
+ (asoc->total_flight == 0)) {
+ /* We get to have a probe outstanding */
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+ } else if (some_on_control) {
+ int num_out, reason;
+
+ /* Here we do control only */
+ (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out,
+ &reason, 1, 1, &now, &now_filled,
+ sctp_get_frag_point(stcb),
+ SCTP_SO_LOCKED);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "USR Send complete qo:%d prw:%d unsent:%d tf:%d cooq:%d toqs:%d err:%d\n",
+ queue_only, asoc->peers_rwnd, un_sent,
+ asoc->total_flight, asoc->chunks_on_out_queue,
+ asoc->total_output_queue_size, error);
+
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT((asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0,
+ ("Association about to be freed"));
+ KASSERT((asoc->state & SCTP_STATE_WAS_ABORTED) == 0,
+ ("Association was aborted"));
+
+out:
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&so->so_snd, 1);
+#endif
+out_unlocked:
+ if (create_lock_applied) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ }
+ if (stcb != NULL) {
+ if (local_soresv) {
+ atomic_subtract_int(&asoc->sb_send_resv, (int)sndlen);
+ }
+ if (free_cnt_applied) {
+ atomic_subtract_int(&asoc->refcnt, 1);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ if (top != NULL) {
+ sctp_m_freem(top);
+ }
+ if (control != NULL) {
+ sctp_m_freem(control);
+ }
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, error);
+ return (error);
+}
+
+/*
+ * generate an AUTHentication chunk, if required
+ */
+struct mbuf *
+sctp_add_auth_chunk(struct mbuf *m, struct mbuf **m_end,
+ struct sctp_auth_chunk **auth_ret, uint32_t * offset,
+ struct sctp_tcb *stcb, uint8_t chunk)
+{
+ struct mbuf *m_auth;
+ struct sctp_auth_chunk *auth;
+ int chunk_len;
+ struct mbuf *cn;
+
+ if ((m_end == NULL) || (auth_ret == NULL) || (offset == NULL) ||
+ (stcb == NULL))
+ return (m);
+
+ if (stcb->asoc.auth_supported == 0) {
+ return (m);
+ }
+ /* does the requested chunk require auth? */
+ if (!sctp_auth_is_required_chunk(chunk, stcb->asoc.peer_auth_chunks)) {
+ return (m);
+ }
+ m_auth = sctp_get_mbuf_for_msg(sizeof(*auth), 0, M_NOWAIT, 1, MT_HEADER);
+ if (m_auth == NULL) {
+ /* no mbuf's */
+ return (m);
+ }
+ /* reserve some space if this will be the first mbuf */
+ if (m == NULL)
+ SCTP_BUF_RESV_UF(m_auth, SCTP_MIN_OVERHEAD);
+ /* fill in the AUTH chunk details */
+ auth = mtod(m_auth, struct sctp_auth_chunk *);
+ memset(auth, 0, sizeof(*auth));
+ auth->ch.chunk_type = SCTP_AUTHENTICATION;
+ auth->ch.chunk_flags = 0;
+ chunk_len = sizeof(*auth) +
+ sctp_get_hmac_digest_len(stcb->asoc.peer_hmac_id);
+ auth->ch.chunk_length = htons(chunk_len);
+ auth->hmac_id = htons(stcb->asoc.peer_hmac_id);
+ /* key id and hmac digest will be computed and filled in upon send */
+
+ /* save the offset where the auth was inserted into the chain */
+ *offset = 0;
+ for (cn = m; cn; cn = SCTP_BUF_NEXT(cn)) {
+ *offset += SCTP_BUF_LEN(cn);
+ }
+
+ /* update length and return pointer to the auth chunk */
+ SCTP_BUF_LEN(m_auth) = chunk_len;
+ m = sctp_copy_mbufchain(m_auth, m, m_end, 1, chunk_len, 0);
+ if (auth_ret != NULL)
+ *auth_ret = auth;
+
+ return (m);
+}
+
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+#ifdef INET6
+int
+sctp_v6src_match_nexthop(struct sockaddr_in6 *src6, sctp_route_t *ro)
+{
+ struct nd_prefix *pfx = NULL;
+ struct nd_pfxrouter *pfxrtr = NULL;
+ struct sockaddr_in6 gw6;
+
+#if defined(__FreeBSD__)
+ if (ro == NULL || ro->ro_nh == NULL || src6->sin6_family != AF_INET6)
+#else
+ if (ro == NULL || ro->ro_rt == NULL || src6->sin6_family != AF_INET6)
+#endif
+ return (0);
+
+ /* get prefix entry of address */
+#if defined(__FreeBSD__)
+ ND6_RLOCK();
+#endif
+ LIST_FOREACH(pfx, &MODULE_GLOBAL(nd_prefix), ndpr_entry) {
+ if (pfx->ndpr_stateflags & NDPRF_DETACHED)
+ continue;
+ if (IN6_ARE_MASKED_ADDR_EQUAL(&pfx->ndpr_prefix.sin6_addr,
+ &src6->sin6_addr, &pfx->ndpr_mask))
+ break;
+ }
+ /* no prefix entry in the prefix list */
+ if (pfx == NULL) {
+#if defined(__FreeBSD__)
+ ND6_RUNLOCK();
+#endif
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "No prefix entry for ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, (struct sockaddr *)src6);
+ return (0);
+ }
+
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "v6src_match_nexthop(), Prefix entry is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, (struct sockaddr *)src6);
+
+ /* search installed gateway from prefix entry */
+ LIST_FOREACH(pfxrtr, &pfx->ndpr_advrtrs, pfr_entry) {
+ memset(&gw6, 0, sizeof(struct sockaddr_in6));
+ gw6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ gw6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ memcpy(&gw6.sin6_addr, &pfxrtr->router->rtaddr,
+ sizeof(struct in6_addr));
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "prefix router is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, (struct sockaddr *)&gw6);
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "installed router is ");
+#if defined(__FreeBSD__)
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, &ro->ro_nh->gw_sa);
+#else
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, ro->ro_rt->rt_gateway);
+#endif
+#if defined(__FreeBSD__)
+ if (sctp_cmpaddr((struct sockaddr *)&gw6, &ro->ro_nh->gw_sa)) {
+ ND6_RUNLOCK();
+#else
+ if (sctp_cmpaddr((struct sockaddr *)&gw6, ro->ro_rt->rt_gateway)) {
+#endif
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "pfxrouter is installed\n");
+ return (1);
+ }
+ }
+#if defined(__FreeBSD__)
+ ND6_RUNLOCK();
+#endif
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "pfxrouter is not installed\n");
+ return (0);
+}
+#endif
+
+int
+sctp_v4src_match_nexthop(struct sctp_ifa *sifa, sctp_route_t *ro)
+{
+#ifdef INET
+ struct sockaddr_in *sin, *mask;
+ struct ifaddr *ifa;
+ struct in_addr srcnetaddr, gwnetaddr;
+
+#if defined(__FreeBSD__)
+ if (ro == NULL || ro->ro_nh == NULL ||
+#else
+ if (ro == NULL || ro->ro_rt == NULL ||
+#endif
+ sifa->address.sa.sa_family != AF_INET) {
+ return (0);
+ }
+ ifa = (struct ifaddr *)sifa->ifa;
+ mask = (struct sockaddr_in *)(ifa->ifa_netmask);
+ sin = &sifa->address.sin;
+ srcnetaddr.s_addr = (sin->sin_addr.s_addr & mask->sin_addr.s_addr);
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "match_nexthop4: src address is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, &sifa->address.sa);
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "network address is %x\n", srcnetaddr.s_addr);
+
+#if defined(__FreeBSD__)
+ sin = &ro->ro_nh->gw4_sa;
+#else
+ sin = (struct sockaddr_in *)ro->ro_rt->rt_gateway;
+#endif
+ gwnetaddr.s_addr = (sin->sin_addr.s_addr & mask->sin_addr.s_addr);
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "match_nexthop4: nexthop is ");
+#if defined(__FreeBSD__)
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, &ro->ro_nh->gw_sa);
+#else
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, ro->ro_rt->rt_gateway);
+#endif
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "network address is %x\n", gwnetaddr.s_addr);
+ if (srcnetaddr.s_addr == gwnetaddr.s_addr) {
+ return (1);
+ }
+#endif
+ return (0);
+}
+#elif defined(__Userspace__)
+/* TODO __Userspace__ versions of sctp_vXsrc_match_nexthop(). */
+int
+sctp_v6src_match_nexthop(struct sockaddr_in6 *src6, sctp_route_t *ro)
+{
+ return (0);
+}
+int
+sctp_v4src_match_nexthop(struct sctp_ifa *sifa, sctp_route_t *ro)
+{
+ return (0);
+}
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_output.h b/netwerk/sctp/src/netinet/sctp_output.h
new file mode 100644
index 0000000000..150804aa61
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_output.h
@@ -0,0 +1,235 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_OUTPUT_H_
+#define _NETINET_SCTP_OUTPUT_H_
+
+#include <netinet/sctp_header.h>
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+struct mbuf *
+sctp_add_addresses_to_i_ia(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_scoping *scope,
+ struct mbuf *m_at,
+ int cnt_inits_to,
+ uint16_t *padding_len, uint16_t *chunk_len);
+
+int sctp_is_addr_restricted(struct sctp_tcb *, struct sctp_ifa *);
+
+int
+sctp_is_address_in_scope(struct sctp_ifa *ifa,
+ struct sctp_scoping *scope,
+ int do_update);
+
+int
+sctp_is_addr_in_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa);
+
+struct sctp_ifa *
+sctp_source_address_selection(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ sctp_route_t *ro, struct sctp_nets *net,
+ int non_asoc_addr_ok, uint32_t vrf_id);
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+int sctp_v6src_match_nexthop(struct sockaddr_in6 *src6, sctp_route_t *ro);
+
+int sctp_v4src_match_nexthop(struct sctp_ifa *sifa, sctp_route_t *ro);
+#endif
+
+void sctp_send_initiate(struct sctp_inpcb *, struct sctp_tcb *, int);
+
+void
+sctp_send_initiate_ack(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *, struct mbuf *,
+ int, int,
+ struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, struct sctp_init_chunk *,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t, uint32_t,
+#endif
+ uint32_t, uint16_t);
+
+struct mbuf *
+sctp_arethere_unrecognized_parameters(struct mbuf *, int, int *,
+ struct sctp_chunkhdr *, int *, int *);
+void sctp_queue_op_err(struct sctp_tcb *, struct mbuf *);
+
+int
+sctp_send_cookie_echo(struct mbuf *, int, int, struct sctp_tcb *,
+ struct sctp_nets *);
+
+void sctp_send_cookie_ack(struct sctp_tcb *);
+
+void
+sctp_send_heartbeat_ack(struct sctp_tcb *, struct mbuf *, int, int,
+ struct sctp_nets *);
+
+void
+sctp_remove_from_wheel(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_stream_out *strq, int holds_lock);
+
+void sctp_send_shutdown(struct sctp_tcb *, struct sctp_nets *);
+
+void sctp_send_shutdown_ack(struct sctp_tcb *, struct sctp_nets *);
+
+void sctp_send_shutdown_complete(struct sctp_tcb *, struct sctp_nets *, int);
+
+void sctp_send_shutdown_complete2(struct sockaddr *, struct sockaddr *,
+ struct sctphdr *,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t, uint32_t, uint16_t,
+#endif
+ uint32_t, uint16_t);
+
+void sctp_send_asconf(struct sctp_tcb *, struct sctp_nets *, int addr_locked);
+
+void sctp_send_asconf_ack(struct sctp_tcb *);
+
+uint32_t sctp_get_frag_point(struct sctp_tcb *);
+
+void sctp_toss_old_cookies(struct sctp_tcb *, struct sctp_association *);
+
+void sctp_toss_old_asconf(struct sctp_tcb *);
+
+void sctp_fix_ecn_echo(struct sctp_association *);
+
+void sctp_move_chunks_from_net(struct sctp_tcb *stcb, struct sctp_nets *net);
+
+#define SCTP_DATA_CHUNK_OVERHEAD(stcb) ((stcb)->asoc.idata_supported ? \
+ sizeof(struct sctp_idata_chunk) : \
+ sizeof(struct sctp_data_chunk))
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+int
+sctp_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *,
+ struct mbuf *, struct thread *, int);
+#elif defined(_WIN32) && !defined(__Userspace__)
+sctp_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *,
+ struct mbuf *, PKTHREAD, int);
+#else
+#if defined(__Userspace__)
+/* sctp_output is called bu sctp_sendm. Not using sctp_sendm for __Userspace__ */
+#endif
+int
+sctp_output(struct sctp_inpcb *,
+ struct mbuf *,
+ struct sockaddr *,
+ struct mbuf *,
+ struct proc *, int);
+#endif
+
+void sctp_chunk_output(struct sctp_inpcb *, struct sctp_tcb *, int, int);
+
+void sctp_send_abort_tcb(struct sctp_tcb *, struct mbuf *, int);
+
+void send_forward_tsn(struct sctp_tcb *, struct sctp_association *);
+
+void sctp_send_sack(struct sctp_tcb *, int);
+
+void sctp_send_hb(struct sctp_tcb *, struct sctp_nets *, int);
+
+void sctp_send_ecn_echo(struct sctp_tcb *, struct sctp_nets *, uint32_t);
+
+void
+sctp_send_packet_dropped(struct sctp_tcb *, struct sctp_nets *, struct mbuf *,
+ int, int, int);
+
+void sctp_send_cwr(struct sctp_tcb *, struct sctp_nets *, uint32_t, uint8_t);
+
+void
+sctp_add_stream_reset_result(struct sctp_tmit_chunk *, uint32_t, uint32_t);
+
+void
+sctp_send_deferred_reset_response(struct sctp_tcb *,
+ struct sctp_stream_reset_list *,
+ int);
+
+void
+sctp_add_stream_reset_result_tsn(struct sctp_tmit_chunk *,
+ uint32_t, uint32_t, uint32_t, uint32_t);
+int
+sctp_send_stream_reset_out_if_possible(struct sctp_tcb *, int);
+
+int
+sctp_send_str_reset_req(struct sctp_tcb *, uint16_t , uint16_t *,
+ uint8_t, uint8_t, uint8_t, uint16_t, uint16_t, uint8_t);
+
+void
+sctp_send_abort(struct mbuf *, int, struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, uint32_t, struct mbuf *,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t, uint32_t, uint16_t,
+#endif
+ uint32_t, uint16_t);
+
+void
+sctp_send_operr_to(struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, uint32_t, struct mbuf *,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t, uint32_t, uint16_t,
+#endif
+ uint32_t, uint16_t);
+
+#endif /* _KERNEL || __Userspace__ */
+
+#if defined(_KERNEL) || defined(__Userspace__)
+int
+sctp_sosend(struct socket *so,
+ struct sockaddr *addr,
+ struct uio *uio,
+ struct mbuf *top,
+ struct mbuf *control,
+#if defined(__APPLE__) && !defined(__Userspace__)
+ int flags
+#else
+ int flags,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *p
+#elif defined(_WIN32) && !defined(__Userspace__)
+ PKTHREAD p
+#else
+#if defined(__Userspace__)
+ /* proc is a dummy in __Userspace__ and will not be passed to sctp_lower_sosend */
+#endif
+ struct proc *p
+#endif
+#endif
+);
+
+#endif
+#endif
+
diff --git a/netwerk/sctp/src/netinet/sctp_pcb.c b/netwerk/sctp/src/netinet/sctp_pcb.c
new file mode 100644
index 0000000000..085b3103ac
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_pcb.c
@@ -0,0 +1,8125 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/proc.h>
+#endif
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_bsd_addr.h>
+#if defined(INET) || defined(INET6)
+#if !defined(_WIN32)
+#include <netinet/udp.h>
+#endif
+#endif
+#ifdef INET6
+#if defined(__Userspace__)
+#include "user_ip6_var.h"
+#else
+#include <netinet6/ip6_var.h>
+#endif
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/sched.h>
+#include <sys/smp.h>
+#include <sys/unistd.h>
+#endif
+#if defined(__Userspace__)
+#include <user_socketvar.h>
+#include <user_atomic.h>
+#if !defined(_WIN32)
+#include <netdb.h>
+#endif
+#endif
+
+#if !defined(__FreeBSD__) || defined(__Userspace__)
+struct sctp_base_info system_base_info;
+
+#endif
+/* FIX: we don't handle multiple link local scopes */
+/* "scopeless" replacement IN6_ARE_ADDR_EQUAL */
+#ifdef INET6
+int
+SCTP6_ARE_ADDR_EQUAL(struct sockaddr_in6 *a, struct sockaddr_in6 *b)
+{
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct in6_addr tmp_a, tmp_b;
+
+ tmp_a = a->sin6_addr;
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ if (in6_embedscope(&tmp_a, a, NULL, NULL) != 0) {
+#else
+ if (in6_embedscope(&tmp_a, a, NULL, NULL, NULL) != 0) {
+#endif
+ return (0);
+ }
+ tmp_b = b->sin6_addr;
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ if (in6_embedscope(&tmp_b, b, NULL, NULL) != 0) {
+#else
+ if (in6_embedscope(&tmp_b, b, NULL, NULL, NULL) != 0) {
+#endif
+ return (0);
+ }
+ return (IN6_ARE_ADDR_EQUAL(&tmp_a, &tmp_b));
+#elif defined(SCTP_KAME)
+ struct sockaddr_in6 tmp_a, tmp_b;
+
+ memcpy(&tmp_a, a, sizeof(struct sockaddr_in6));
+ if (sa6_embedscope(&tmp_a, MODULE_GLOBAL(ip6_use_defzone)) != 0) {
+ return (0);
+ }
+ memcpy(&tmp_b, b, sizeof(struct sockaddr_in6));
+ if (sa6_embedscope(&tmp_b, MODULE_GLOBAL(ip6_use_defzone)) != 0) {
+ return (0);
+ }
+ return (IN6_ARE_ADDR_EQUAL(&tmp_a.sin6_addr, &tmp_b.sin6_addr));
+#else
+ struct in6_addr tmp_a, tmp_b;
+
+ tmp_a = a->sin6_addr;
+ if (in6_embedscope(&tmp_a, a) != 0) {
+ return (0);
+ }
+ tmp_b = b->sin6_addr;
+ if (in6_embedscope(&tmp_b, b) != 0) {
+ return (0);
+ }
+ return (IN6_ARE_ADDR_EQUAL(&tmp_a, &tmp_b));
+#endif
+#else
+ return (IN6_ARE_ADDR_EQUAL(&(a->sin6_addr), &(b->sin6_addr)));
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+}
+#endif
+
+void
+sctp_fill_pcbinfo(struct sctp_pcbinfo *spcb)
+{
+ /*
+ * We really don't need to lock this, but I will just because it
+ * does not hurt.
+ */
+ SCTP_INP_INFO_RLOCK();
+ spcb->ep_count = SCTP_BASE_INFO(ipi_count_ep);
+ spcb->asoc_count = SCTP_BASE_INFO(ipi_count_asoc);
+ spcb->laddr_count = SCTP_BASE_INFO(ipi_count_laddr);
+ spcb->raddr_count = SCTP_BASE_INFO(ipi_count_raddr);
+ spcb->chk_count = SCTP_BASE_INFO(ipi_count_chunk);
+ spcb->readq_count = SCTP_BASE_INFO(ipi_count_readq);
+ spcb->stream_oque = SCTP_BASE_INFO(ipi_count_strmoq);
+ spcb->free_chunks = SCTP_BASE_INFO(ipi_free_chunks);
+ SCTP_INP_INFO_RUNLOCK();
+}
+
+/*-
+ * Addresses are added to VRF's (Virtual Router's). For BSD we
+ * have only the default VRF 0. We maintain a hash list of
+ * VRF's. Each VRF has its own list of sctp_ifn's. Each of
+ * these has a list of addresses. When we add a new address
+ * to a VRF we lookup the ifn/ifn_index, if the ifn does
+ * not exist we create it and add it to the list of IFN's
+ * within the VRF. Once we have the sctp_ifn, we add the
+ * address to the list. So we look something like:
+ *
+ * hash-vrf-table
+ * vrf-> ifn-> ifn -> ifn
+ * vrf |
+ * ... +--ifa-> ifa -> ifa
+ * vrf
+ *
+ * We keep these separate lists since the SCTP subsystem will
+ * point to these from its source address selection nets structure.
+ * When an address is deleted it does not happen right away on
+ * the SCTP side, it gets scheduled. What we do when a
+ * delete happens is immediately remove the address from
+ * the master list and decrement the refcount. As our
+ * addip iterator works through and frees the src address
+ * selection pointing to the sctp_ifa, eventually the refcount
+ * will reach 0 and we will delete it. Note that it is assumed
+ * that any locking on system level ifn/ifa is done at the
+ * caller of these functions and these routines will only
+ * lock the SCTP structures as they add or delete things.
+ *
+ * Other notes on VRF concepts.
+ * - An endpoint can be in multiple VRF's
+ * - An association lives within a VRF and only one VRF.
+ * - Any incoming packet we can deduce the VRF for by
+ * looking at the mbuf/pak inbound (for BSD its VRF=0 :D)
+ * - Any downward send call or connect call must supply the
+ * VRF via ancillary data or via some sort of set default
+ * VRF socket option call (again for BSD no brainer since
+ * the VRF is always 0).
+ * - An endpoint may add multiple VRF's to it.
+ * - Listening sockets can accept associations in any
+ * of the VRF's they are in but the assoc will end up
+ * in only one VRF (gotten from the packet or connect/send).
+ *
+ */
+
+struct sctp_vrf *
+sctp_allocate_vrf(int vrf_id)
+{
+ struct sctp_vrf *vrf = NULL;
+ struct sctp_vrflist *bucket;
+
+ /* First allocate the VRF structure */
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf) {
+ /* Already allocated */
+ return (vrf);
+ }
+ SCTP_MALLOC(vrf, struct sctp_vrf *, sizeof(struct sctp_vrf),
+ SCTP_M_VRF);
+ if (vrf == NULL) {
+ /* No memory */
+#ifdef INVARIANTS
+ panic("No memory for VRF:%d", vrf_id);
+#endif
+ return (NULL);
+ }
+ /* setup the VRF */
+ memset(vrf, 0, sizeof(struct sctp_vrf));
+ vrf->vrf_id = vrf_id;
+ LIST_INIT(&vrf->ifnlist);
+ vrf->total_ifa_count = 0;
+ vrf->refcount = 0;
+ /* now also setup table ids */
+ SCTP_INIT_VRF_TABLEID(vrf);
+ /* Init the HASH of addresses */
+ vrf->vrf_addr_hash = SCTP_HASH_INIT(SCTP_VRF_ADDR_HASH_SIZE,
+ &vrf->vrf_addr_hashmark);
+ if (vrf->vrf_addr_hash == NULL) {
+ /* No memory */
+#ifdef INVARIANTS
+ panic("No memory for VRF:%d", vrf_id);
+#endif
+ SCTP_FREE(vrf, SCTP_M_VRF);
+ return (NULL);
+ }
+
+ /* Add it to the hash table */
+ bucket = &SCTP_BASE_INFO(sctp_vrfhash)[(vrf_id & SCTP_BASE_INFO(hashvrfmark))];
+ LIST_INSERT_HEAD(bucket, vrf, next_vrf);
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_vrfs), 1);
+ return (vrf);
+}
+
+struct sctp_ifn *
+sctp_find_ifn(void *ifn, uint32_t ifn_index)
+{
+ struct sctp_ifn *sctp_ifnp;
+ struct sctp_ifnlist *hash_ifn_head;
+
+ /* We assume the lock is held for the addresses
+ * if that's wrong problems could occur :-)
+ */
+ SCTP_IPI_ADDR_LOCK_ASSERT();
+ hash_ifn_head = &SCTP_BASE_INFO(vrf_ifn_hash)[(ifn_index & SCTP_BASE_INFO(vrf_ifn_hashmark))];
+ LIST_FOREACH(sctp_ifnp, hash_ifn_head, next_bucket) {
+ if (sctp_ifnp->ifn_index == ifn_index) {
+ return (sctp_ifnp);
+ }
+ if (sctp_ifnp->ifn_p && ifn && (sctp_ifnp->ifn_p == ifn)) {
+ return (sctp_ifnp);
+ }
+ }
+ return (NULL);
+}
+
+struct sctp_vrf *
+sctp_find_vrf(uint32_t vrf_id)
+{
+ struct sctp_vrflist *bucket;
+ struct sctp_vrf *liste;
+
+ bucket = &SCTP_BASE_INFO(sctp_vrfhash)[(vrf_id & SCTP_BASE_INFO(hashvrfmark))];
+ LIST_FOREACH(liste, bucket, next_vrf) {
+ if (vrf_id == liste->vrf_id) {
+ return (liste);
+ }
+ }
+ return (NULL);
+}
+
+void
+sctp_free_vrf(struct sctp_vrf *vrf)
+{
+ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(&vrf->refcount)) {
+ if (vrf->vrf_addr_hash) {
+ SCTP_HASH_FREE(vrf->vrf_addr_hash, vrf->vrf_addr_hashmark);
+ vrf->vrf_addr_hash = NULL;
+ }
+ /* We zero'd the count */
+ LIST_REMOVE(vrf, next_vrf);
+ SCTP_FREE(vrf, SCTP_M_VRF);
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_vrfs), 1);
+ }
+}
+
+void
+sctp_free_ifn(struct sctp_ifn *sctp_ifnp)
+{
+ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(&sctp_ifnp->refcount)) {
+ /* We zero'd the count */
+ if (sctp_ifnp->vrf) {
+ sctp_free_vrf(sctp_ifnp->vrf);
+ }
+ SCTP_FREE(sctp_ifnp, SCTP_M_IFN);
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_ifns), 1);
+ }
+}
+
+void
+sctp_update_ifn_mtu(uint32_t ifn_index, uint32_t mtu)
+{
+ struct sctp_ifn *sctp_ifnp;
+
+ sctp_ifnp = sctp_find_ifn((void *)NULL, ifn_index);
+ if (sctp_ifnp != NULL) {
+ sctp_ifnp->ifn_mtu = mtu;
+ }
+}
+
+void
+sctp_free_ifa(struct sctp_ifa *sctp_ifap)
+{
+ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(&sctp_ifap->refcount)) {
+ /* We zero'd the count */
+ if (sctp_ifap->ifn_p) {
+ sctp_free_ifn(sctp_ifap->ifn_p);
+ }
+ SCTP_FREE(sctp_ifap, SCTP_M_IFA);
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_ifas), 1);
+ }
+}
+
+static void
+sctp_delete_ifn(struct sctp_ifn *sctp_ifnp, int hold_addr_lock)
+{
+ struct sctp_ifn *found;
+
+ found = sctp_find_ifn(sctp_ifnp->ifn_p, sctp_ifnp->ifn_index);
+ if (found == NULL) {
+ /* Not in the list.. sorry */
+ return;
+ }
+ if (hold_addr_lock == 0) {
+ SCTP_IPI_ADDR_WLOCK();
+ } else {
+ SCTP_IPI_ADDR_WLOCK_ASSERT();
+ }
+ LIST_REMOVE(sctp_ifnp, next_bucket);
+ LIST_REMOVE(sctp_ifnp, next_ifn);
+ if (hold_addr_lock == 0) {
+ SCTP_IPI_ADDR_WUNLOCK();
+ }
+ /* Take away the reference, and possibly free it */
+ sctp_free_ifn(sctp_ifnp);
+}
+
+void
+sctp_mark_ifa_addr_down(uint32_t vrf_id, struct sockaddr *addr,
+ const char *if_name, uint32_t ifn_index)
+{
+ struct sctp_vrf *vrf;
+ struct sctp_ifa *sctp_ifap;
+
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "Can't find vrf_id 0x%x\n", vrf_id);
+ goto out;
+ }
+ sctp_ifap = sctp_find_ifa_by_addr(addr, vrf->vrf_id, SCTP_ADDR_LOCKED);
+ if (sctp_ifap == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "Can't find sctp_ifap for address\n");
+ goto out;
+ }
+ if (sctp_ifap->ifn_p == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "IFA has no IFN - can't mark unusable\n");
+ goto out;
+ }
+ if (if_name) {
+ if (strncmp(if_name, sctp_ifap->ifn_p->ifn_name, SCTP_IFNAMSIZ) != 0) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "IFN %s of IFA not the same as %s\n",
+ sctp_ifap->ifn_p->ifn_name, if_name);
+ goto out;
+ }
+ } else {
+ if (sctp_ifap->ifn_p->ifn_index != ifn_index) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "IFA owned by ifn_index:%d down command for ifn_index:%d - ignored\n",
+ sctp_ifap->ifn_p->ifn_index, ifn_index);
+ goto out;
+ }
+ }
+
+ sctp_ifap->localifa_flags &= (~SCTP_ADDR_VALID);
+ sctp_ifap->localifa_flags |= SCTP_ADDR_IFA_UNUSEABLE;
+ out:
+ SCTP_IPI_ADDR_RUNLOCK();
+}
+
+void
+sctp_mark_ifa_addr_up(uint32_t vrf_id, struct sockaddr *addr,
+ const char *if_name, uint32_t ifn_index)
+{
+ struct sctp_vrf *vrf;
+ struct sctp_ifa *sctp_ifap;
+
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "Can't find vrf_id 0x%x\n", vrf_id);
+ goto out;
+ }
+ sctp_ifap = sctp_find_ifa_by_addr(addr, vrf->vrf_id, SCTP_ADDR_LOCKED);
+ if (sctp_ifap == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "Can't find sctp_ifap for address\n");
+ goto out;
+ }
+ if (sctp_ifap->ifn_p == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "IFA has no IFN - can't mark unusable\n");
+ goto out;
+ }
+ if (if_name) {
+ if (strncmp(if_name, sctp_ifap->ifn_p->ifn_name, SCTP_IFNAMSIZ) != 0) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "IFN %s of IFA not the same as %s\n",
+ sctp_ifap->ifn_p->ifn_name, if_name);
+ goto out;
+ }
+ } else {
+ if (sctp_ifap->ifn_p->ifn_index != ifn_index) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "IFA owned by ifn_index:%d down command for ifn_index:%d - ignored\n",
+ sctp_ifap->ifn_p->ifn_index, ifn_index);
+ goto out;
+ }
+ }
+
+ sctp_ifap->localifa_flags &= (~SCTP_ADDR_IFA_UNUSEABLE);
+ sctp_ifap->localifa_flags |= SCTP_ADDR_VALID;
+ out:
+ SCTP_IPI_ADDR_RUNLOCK();
+}
+
+/*-
+ * Add an ifa to an ifn.
+ * Register the interface as necessary.
+ * NOTE: ADDR write lock MUST be held.
+ */
+static void
+sctp_add_ifa_to_ifn(struct sctp_ifn *sctp_ifnp, struct sctp_ifa *sctp_ifap)
+{
+ int ifa_af;
+
+ LIST_INSERT_HEAD(&sctp_ifnp->ifalist, sctp_ifap, next_ifa);
+ sctp_ifap->ifn_p = sctp_ifnp;
+ atomic_add_int(&sctp_ifap->ifn_p->refcount, 1);
+ /* update address counts */
+ sctp_ifnp->ifa_count++;
+ ifa_af = sctp_ifap->address.sa.sa_family;
+ switch (ifa_af) {
+#ifdef INET
+ case AF_INET:
+ sctp_ifnp->num_v4++;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sctp_ifnp->num_v6++;
+ break;
+#endif
+ default:
+ break;
+ }
+ if (sctp_ifnp->ifa_count == 1) {
+ /* register the new interface */
+ sctp_ifnp->registered_af = ifa_af;
+ }
+}
+
+/*-
+ * Remove an ifa from its ifn.
+ * If no more addresses exist, remove the ifn too. Otherwise, re-register
+ * the interface based on the remaining address families left.
+ * NOTE: ADDR write lock MUST be held.
+ */
+static void
+sctp_remove_ifa_from_ifn(struct sctp_ifa *sctp_ifap)
+{
+ LIST_REMOVE(sctp_ifap, next_ifa);
+ if (sctp_ifap->ifn_p) {
+ /* update address counts */
+ sctp_ifap->ifn_p->ifa_count--;
+ switch (sctp_ifap->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ sctp_ifap->ifn_p->num_v4--;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sctp_ifap->ifn_p->num_v6--;
+ break;
+#endif
+ default:
+ break;
+ }
+
+ if (LIST_EMPTY(&sctp_ifap->ifn_p->ifalist)) {
+ /* remove the ifn, possibly freeing it */
+ sctp_delete_ifn(sctp_ifap->ifn_p, SCTP_ADDR_LOCKED);
+ } else {
+ /* re-register address family type, if needed */
+ if ((sctp_ifap->ifn_p->num_v6 == 0) &&
+ (sctp_ifap->ifn_p->registered_af == AF_INET6)) {
+ sctp_ifap->ifn_p->registered_af = AF_INET;
+ } else if ((sctp_ifap->ifn_p->num_v4 == 0) &&
+ (sctp_ifap->ifn_p->registered_af == AF_INET)) {
+ sctp_ifap->ifn_p->registered_af = AF_INET6;
+ }
+ /* free the ifn refcount */
+ sctp_free_ifn(sctp_ifap->ifn_p);
+ }
+ sctp_ifap->ifn_p = NULL;
+ }
+}
+
+struct sctp_ifa *
+sctp_add_addr_to_vrf(uint32_t vrf_id, void *ifn, uint32_t ifn_index,
+ uint32_t ifn_type, const char *if_name, void *ifa,
+ struct sockaddr *addr, uint32_t ifa_flags,
+ int dynamic_add)
+{
+ struct sctp_vrf *vrf;
+ struct sctp_ifn *sctp_ifnp, *new_sctp_ifnp;
+ struct sctp_ifa *sctp_ifap, *new_sctp_ifap;
+ struct sctp_ifalist *hash_addr_head;
+ struct sctp_ifnlist *hash_ifn_head;
+ uint32_t hash_of_addr;
+ int new_ifn_af = 0;
+
+#ifdef SCTP_DEBUG
+ SCTPDBG(SCTP_DEBUG_PCB4, "vrf_id 0x%x: adding address: ", vrf_id);
+ SCTPDBG_ADDR(SCTP_DEBUG_PCB4, addr);
+#endif
+ SCTP_MALLOC(new_sctp_ifnp, struct sctp_ifn *,
+ sizeof(struct sctp_ifn), SCTP_M_IFN);
+ if (new_sctp_ifnp == NULL) {
+#ifdef INVARIANTS
+ panic("No memory for IFN");
+#endif
+ return (NULL);
+ }
+ SCTP_MALLOC(new_sctp_ifap, struct sctp_ifa *, sizeof(struct sctp_ifa), SCTP_M_IFA);
+ if (new_sctp_ifap == NULL) {
+#ifdef INVARIANTS
+ panic("No memory for IFA");
+#endif
+ SCTP_FREE(new_sctp_ifnp, SCTP_M_IFN);
+ return (NULL);
+ }
+
+ SCTP_IPI_ADDR_WLOCK();
+ sctp_ifnp = sctp_find_ifn(ifn, ifn_index);
+ if (sctp_ifnp) {
+ vrf = sctp_ifnp->vrf;
+ } else {
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ vrf = sctp_allocate_vrf(vrf_id);
+ if (vrf == NULL) {
+ SCTP_IPI_ADDR_WUNLOCK();
+ SCTP_FREE(new_sctp_ifnp, SCTP_M_IFN);
+ SCTP_FREE(new_sctp_ifap, SCTP_M_IFA);
+ return (NULL);
+ }
+ }
+ }
+ if (sctp_ifnp == NULL) {
+ /* build one and add it, can't hold lock
+ * until after malloc done though.
+ */
+ sctp_ifnp = new_sctp_ifnp;
+ new_sctp_ifnp = NULL;
+ memset(sctp_ifnp, 0, sizeof(struct sctp_ifn));
+ sctp_ifnp->ifn_index = ifn_index;
+ sctp_ifnp->ifn_p = ifn;
+ sctp_ifnp->ifn_type = ifn_type;
+ sctp_ifnp->refcount = 0;
+ sctp_ifnp->vrf = vrf;
+ atomic_add_int(&vrf->refcount, 1);
+ sctp_ifnp->ifn_mtu = SCTP_GATHER_MTU_FROM_IFN_INFO(ifn, ifn_index);
+ if (if_name != NULL) {
+ SCTP_SNPRINTF(sctp_ifnp->ifn_name, SCTP_IFNAMSIZ, "%s", if_name);
+ } else {
+ SCTP_SNPRINTF(sctp_ifnp->ifn_name, SCTP_IFNAMSIZ, "%s", "unknown");
+ }
+ hash_ifn_head = &SCTP_BASE_INFO(vrf_ifn_hash)[(ifn_index & SCTP_BASE_INFO(vrf_ifn_hashmark))];
+ LIST_INIT(&sctp_ifnp->ifalist);
+ LIST_INSERT_HEAD(hash_ifn_head, sctp_ifnp, next_bucket);
+ LIST_INSERT_HEAD(&vrf->ifnlist, sctp_ifnp, next_ifn);
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_ifns), 1);
+ new_ifn_af = 1;
+ }
+ sctp_ifap = sctp_find_ifa_by_addr(addr, vrf->vrf_id, SCTP_ADDR_LOCKED);
+ if (sctp_ifap) {
+ /* Hmm, it already exists? */
+ if ((sctp_ifap->ifn_p) &&
+ (sctp_ifap->ifn_p->ifn_index == ifn_index)) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "Using existing ifn %s (0x%x) for ifa %p\n",
+ sctp_ifap->ifn_p->ifn_name, ifn_index,
+ (void *)sctp_ifap);
+ if (new_ifn_af) {
+ /* Remove the created one that we don't want */
+ sctp_delete_ifn(sctp_ifnp, SCTP_ADDR_LOCKED);
+ }
+ if (sctp_ifap->localifa_flags & SCTP_BEING_DELETED) {
+ /* easy to solve, just switch back to active */
+ SCTPDBG(SCTP_DEBUG_PCB4, "Clearing deleted ifa flag\n");
+ sctp_ifap->localifa_flags = SCTP_ADDR_VALID;
+ sctp_ifap->ifn_p = sctp_ifnp;
+ atomic_add_int(&sctp_ifap->ifn_p->refcount, 1);
+ }
+ exit_stage_left:
+ SCTP_IPI_ADDR_WUNLOCK();
+ if (new_sctp_ifnp != NULL) {
+ SCTP_FREE(new_sctp_ifnp, SCTP_M_IFN);
+ }
+ SCTP_FREE(new_sctp_ifap, SCTP_M_IFA);
+ return (sctp_ifap);
+ } else {
+ if (sctp_ifap->ifn_p) {
+ /*
+ * The last IFN gets the address, remove the
+ * old one
+ */
+ SCTPDBG(SCTP_DEBUG_PCB4, "Moving ifa %p from %s (0x%x) to %s (0x%x)\n",
+ (void *)sctp_ifap, sctp_ifap->ifn_p->ifn_name,
+ sctp_ifap->ifn_p->ifn_index, if_name,
+ ifn_index);
+ /* remove the address from the old ifn */
+ sctp_remove_ifa_from_ifn(sctp_ifap);
+ /* move the address over to the new ifn */
+ sctp_add_ifa_to_ifn(sctp_ifnp, sctp_ifap);
+ goto exit_stage_left;
+ } else {
+ /* repair ifnp which was NULL ? */
+ sctp_ifap->localifa_flags = SCTP_ADDR_VALID;
+ SCTPDBG(SCTP_DEBUG_PCB4, "Repairing ifn %p for ifa %p\n",
+ (void *)sctp_ifnp, (void *)sctp_ifap);
+ sctp_add_ifa_to_ifn(sctp_ifnp, sctp_ifap);
+ }
+ goto exit_stage_left;
+ }
+ }
+ sctp_ifap = new_sctp_ifap;
+ memset(sctp_ifap, 0, sizeof(struct sctp_ifa));
+ sctp_ifap->ifn_p = sctp_ifnp;
+ atomic_add_int(&sctp_ifnp->refcount, 1);
+ sctp_ifap->vrf_id = vrf_id;
+ sctp_ifap->ifa = ifa;
+#ifdef HAVE_SA_LEN
+ memcpy(&sctp_ifap->address, addr, addr->sa_len);
+#else
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ memcpy(&sctp_ifap->address, addr, sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memcpy(&sctp_ifap->address, addr, sizeof(struct sockaddr_in6));
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(&sctp_ifap->address, addr, sizeof(struct sockaddr_conn));
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+#endif
+ sctp_ifap->localifa_flags = SCTP_ADDR_VALID | SCTP_ADDR_DEFER_USE;
+ sctp_ifap->flags = ifa_flags;
+ /* Set scope */
+ switch (sctp_ifap->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = &sctp_ifap->address.sin;
+ if (SCTP_IFN_IS_IFT_LOOP(sctp_ifap->ifn_p) ||
+ (IN4_ISLOOPBACK_ADDRESS(&sin->sin_addr))) {
+ sctp_ifap->src_is_loop = 1;
+ }
+ if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) {
+ sctp_ifap->src_is_priv = 1;
+ }
+ sctp_ifnp->num_v4++;
+ if (new_ifn_af)
+ new_ifn_af = AF_INET;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* ok to use deprecated addresses? */
+ struct sockaddr_in6 *sin6;
+
+ sin6 = &sctp_ifap->address.sin6;
+ if (SCTP_IFN_IS_IFT_LOOP(sctp_ifap->ifn_p) ||
+ (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr))) {
+ sctp_ifap->src_is_loop = 1;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ sctp_ifap->src_is_priv = 1;
+ }
+ sctp_ifnp->num_v6++;
+ if (new_ifn_af)
+ new_ifn_af = AF_INET6;
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (new_ifn_af)
+ new_ifn_af = AF_CONN;
+ break;
+#endif
+ default:
+ new_ifn_af = 0;
+ break;
+ }
+ hash_of_addr = sctp_get_ifa_hash_val(&sctp_ifap->address.sa);
+
+ if ((sctp_ifap->src_is_priv == 0) &&
+ (sctp_ifap->src_is_loop == 0)) {
+ sctp_ifap->src_is_glob = 1;
+ }
+ hash_addr_head = &vrf->vrf_addr_hash[(hash_of_addr & vrf->vrf_addr_hashmark)];
+ LIST_INSERT_HEAD(hash_addr_head, sctp_ifap, next_bucket);
+ sctp_ifap->refcount = 1;
+ LIST_INSERT_HEAD(&sctp_ifnp->ifalist, sctp_ifap, next_ifa);
+ sctp_ifnp->ifa_count++;
+ vrf->total_ifa_count++;
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_ifas), 1);
+ if (new_ifn_af) {
+ sctp_ifnp->registered_af = new_ifn_af;
+ }
+ SCTP_IPI_ADDR_WUNLOCK();
+ if (new_sctp_ifnp != NULL) {
+ SCTP_FREE(new_sctp_ifnp, SCTP_M_IFN);
+ }
+
+ if (dynamic_add) {
+ /* Bump up the refcount so that when the timer
+ * completes it will drop back down.
+ */
+ struct sctp_laddr *wi;
+
+ atomic_add_int(&sctp_ifap->refcount, 1);
+ wi = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr);
+ if (wi == NULL) {
+ /*
+ * Gak, what can we do? We have lost an address
+ * change can you say HOSED?
+ */
+ SCTPDBG(SCTP_DEBUG_PCB4, "Lost an address change?\n");
+ /* Opps, must decrement the count */
+ sctp_del_addr_from_vrf(vrf_id, addr, ifn_index,
+ if_name);
+ return (NULL);
+ }
+ SCTP_INCR_LADDR_COUNT();
+ memset(wi, 0, sizeof(*wi));
+ (void)SCTP_GETTIME_TIMEVAL(&wi->start_time);
+ wi->ifa = sctp_ifap;
+ wi->action = SCTP_ADD_IP_ADDRESS;
+
+ SCTP_WQ_ADDR_LOCK();
+ LIST_INSERT_HEAD(&SCTP_BASE_INFO(addr_wq), wi, sctp_nxt_addr);
+ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
+ (struct sctp_inpcb *)NULL,
+ (struct sctp_tcb *)NULL,
+ (struct sctp_nets *)NULL);
+ SCTP_WQ_ADDR_UNLOCK();
+ } else {
+ /* it's ready for use */
+ sctp_ifap->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ return (sctp_ifap);
+}
+
+void
+sctp_del_addr_from_vrf(uint32_t vrf_id, struct sockaddr *addr,
+ uint32_t ifn_index, const char *if_name)
+{
+ struct sctp_vrf *vrf;
+ struct sctp_ifa *sctp_ifap = NULL;
+
+ SCTP_IPI_ADDR_WLOCK();
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "Can't find vrf_id 0x%x\n", vrf_id);
+ goto out_now;
+ }
+
+#ifdef SCTP_DEBUG
+ SCTPDBG(SCTP_DEBUG_PCB4, "vrf_id 0x%x: deleting address:", vrf_id);
+ SCTPDBG_ADDR(SCTP_DEBUG_PCB4, addr);
+#endif
+ sctp_ifap = sctp_find_ifa_by_addr(addr, vrf->vrf_id, SCTP_ADDR_LOCKED);
+ if (sctp_ifap) {
+ /* Validate the delete */
+ if (sctp_ifap->ifn_p) {
+ int valid = 0;
+ /*-
+ * The name has priority over the ifn_index
+ * if its given.
+ */
+ if (if_name) {
+ if (strncmp(if_name, sctp_ifap->ifn_p->ifn_name, SCTP_IFNAMSIZ) == 0) {
+ /* They match its a correct delete */
+ valid = 1;
+ }
+ }
+ if (!valid) {
+ /* last ditch check ifn_index */
+ if (ifn_index == sctp_ifap->ifn_p->ifn_index) {
+ valid = 1;
+ }
+ }
+ if (!valid) {
+ SCTPDBG(SCTP_DEBUG_PCB4, "ifn:%d ifname:%s does not match addresses\n",
+ ifn_index, ((if_name == NULL) ? "NULL" : if_name));
+ SCTPDBG(SCTP_DEBUG_PCB4, "ifn:%d ifname:%s - ignoring delete\n",
+ sctp_ifap->ifn_p->ifn_index, sctp_ifap->ifn_p->ifn_name);
+ SCTP_IPI_ADDR_WUNLOCK();
+ return;
+ }
+ }
+ SCTPDBG(SCTP_DEBUG_PCB4, "Deleting ifa %p\n", (void *)sctp_ifap);
+ sctp_ifap->localifa_flags &= SCTP_ADDR_VALID;
+ /*
+ * We don't set the flag. This means that the structure will
+ * hang around in EP's that have bound specific to it until
+ * they close. This gives us TCP like behavior if someone
+ * removes an address (or for that matter adds it right back).
+ */
+ /* sctp_ifap->localifa_flags |= SCTP_BEING_DELETED; */
+ vrf->total_ifa_count--;
+ LIST_REMOVE(sctp_ifap, next_bucket);
+ sctp_remove_ifa_from_ifn(sctp_ifap);
+ }
+#ifdef SCTP_DEBUG
+ else {
+ SCTPDBG(SCTP_DEBUG_PCB4, "Del Addr-ifn:%d Could not find address:",
+ ifn_index);
+ SCTPDBG_ADDR(SCTP_DEBUG_PCB1, addr);
+ }
+#endif
+
+ out_now:
+ SCTP_IPI_ADDR_WUNLOCK();
+ if (sctp_ifap) {
+ struct sctp_laddr *wi;
+
+ wi = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr);
+ if (wi == NULL) {
+ /*
+ * Gak, what can we do? We have lost an address
+ * change can you say HOSED?
+ */
+ SCTPDBG(SCTP_DEBUG_PCB4, "Lost an address change?\n");
+
+ /* Oops, must decrement the count */
+ sctp_free_ifa(sctp_ifap);
+ return;
+ }
+ SCTP_INCR_LADDR_COUNT();
+ memset(wi, 0, sizeof(*wi));
+ (void)SCTP_GETTIME_TIMEVAL(&wi->start_time);
+ wi->ifa = sctp_ifap;
+ wi->action = SCTP_DEL_IP_ADDRESS;
+ SCTP_WQ_ADDR_LOCK();
+ /*
+ * Should this really be a tailq? As it is we will process the
+ * newest first :-0
+ */
+ LIST_INSERT_HEAD(&SCTP_BASE_INFO(addr_wq), wi, sctp_nxt_addr);
+ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
+ (struct sctp_inpcb *)NULL,
+ (struct sctp_tcb *)NULL,
+ (struct sctp_nets *)NULL);
+ SCTP_WQ_ADDR_UNLOCK();
+ }
+ return;
+}
+
+static int
+sctp_does_stcb_own_this_addr(struct sctp_tcb *stcb, struct sockaddr *to)
+{
+ int loopback_scope;
+#if defined(INET)
+ int ipv4_local_scope, ipv4_addr_legal;
+#endif
+#if defined(INET6)
+ int local_scope, site_scope, ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ int conn_addr_legal;
+#endif
+ struct sctp_vrf *vrf;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+
+ loopback_scope = stcb->asoc.scope.loopback_scope;
+#if defined(INET)
+ ipv4_local_scope = stcb->asoc.scope.ipv4_local_scope;
+ ipv4_addr_legal = stcb->asoc.scope.ipv4_addr_legal;
+#endif
+#if defined(INET6)
+ local_scope = stcb->asoc.scope.local_scope;
+ site_scope = stcb->asoc.scope.site_scope;
+ ipv6_addr_legal = stcb->asoc.scope.ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ conn_addr_legal = stcb->asoc.scope.conn_addr_legal;
+#endif
+
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(stcb->asoc.vrf_id);
+ if (vrf == NULL) {
+ /* no vrf, no addresses */
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (0);
+ }
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if ((loopback_scope == 0) &&
+ SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ if (sctp_is_addr_restricted(stcb, sctp_ifa) &&
+ (!sctp_is_addr_pending(stcb, sctp_ifa))) {
+ /* We allow pending addresses, where we
+ * have sent an asconf-add to be considered
+ * valid.
+ */
+ continue;
+ }
+ if (sctp_ifa->address.sa.sa_family != to->sa_family) {
+ continue;
+ }
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (ipv4_addr_legal) {
+ struct sockaddr_in *sin, *rsin;
+
+ sin = &sctp_ifa->address.sin;
+ rsin = (struct sockaddr_in *)to;
+ if ((ipv4_local_scope == 0) &&
+ IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if (sin->sin_addr.s_addr == rsin->sin_addr.s_addr) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (1);
+ }
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (ipv6_addr_legal) {
+ struct sockaddr_in6 *sin6, *rsin6;
+#if defined(SCTP_EMBEDDED_V6_SCOPE) && !defined(SCTP_KAME)
+ struct sockaddr_in6 lsa6;
+#endif
+ sin6 = &sctp_ifa->address.sin6;
+ rsin6 = (struct sockaddr_in6 *)to;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (local_scope == 0)
+ continue;
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+ if (sin6->sin6_scope_id == 0) {
+#ifdef SCTP_KAME
+ if (sa6_recoverscope(sin6) != 0)
+ continue;
+#else
+ lsa6 = *sin6;
+ if (in6_recoverscope(&lsa6,
+ &lsa6.sin6_addr,
+ NULL))
+ continue;
+ sin6 = &lsa6;
+#endif /* SCTP_KAME */
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ }
+ if ((site_scope == 0) &&
+ (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ if (SCTP6_ARE_ADDR_EQUAL(sin6, rsin6)) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (1);
+ }
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (conn_addr_legal) {
+ struct sockaddr_conn *sconn, *rsconn;
+
+ sconn = &sctp_ifa->address.sconn;
+ rsconn = (struct sockaddr_conn *)to;
+ if (sconn->sconn_addr == rsconn->sconn_addr) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (1);
+ }
+ }
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ }
+ } else {
+ struct sctp_laddr *laddr;
+
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
+ SCTPDBG(SCTP_DEBUG_PCB1, "ifa being deleted\n");
+ continue;
+ }
+ if (sctp_is_addr_restricted(stcb, laddr->ifa) &&
+ (!sctp_is_addr_pending(stcb, laddr->ifa))) {
+ /* We allow pending addresses, where we
+ * have sent an asconf-add to be considered
+ * valid.
+ */
+ continue;
+ }
+ if (laddr->ifa->address.sa.sa_family != to->sa_family) {
+ continue;
+ }
+ switch (to->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin, *rsin;
+
+ sin = &laddr->ifa->address.sin;
+ rsin = (struct sockaddr_in *)to;
+ if (sin->sin_addr.s_addr == rsin->sin_addr.s_addr) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (1);
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6, *rsin6;
+
+ sin6 = &laddr->ifa->address.sin6;
+ rsin6 = (struct sockaddr_in6 *)to;
+ if (SCTP6_ARE_ADDR_EQUAL(sin6, rsin6)) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (1);
+ }
+ break;
+ }
+
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn, *rsconn;
+
+ sconn = &laddr->ifa->address.sconn;
+ rsconn = (struct sockaddr_conn *)to;
+ if (sconn->sconn_addr == rsconn->sconn_addr) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (1);
+ }
+ break;
+ }
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ }
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (0);
+}
+
+static struct sctp_tcb *
+sctp_tcb_special_locate(struct sctp_inpcb **inp_p, struct sockaddr *from,
+ struct sockaddr *to, struct sctp_nets **netp, uint32_t vrf_id)
+{
+ /**** ASSUMES THE CALLER holds the INP_INFO_RLOCK */
+ /*
+ * If we support the TCP model, then we must now dig through to see
+ * if we can find our endpoint in the list of tcp ep's.
+ */
+ uint16_t lport, rport;
+ struct sctppcbhead *ephead;
+ struct sctp_inpcb *inp;
+ struct sctp_laddr *laddr;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+#ifdef SCTP_MVRF
+ int fnd, i;
+#endif
+
+ if ((to == NULL) || (from == NULL)) {
+ return (NULL);
+ }
+
+ switch (to->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (from->sa_family == AF_INET) {
+ lport = ((struct sockaddr_in *)to)->sin_port;
+ rport = ((struct sockaddr_in *)from)->sin_port;
+ } else {
+ return (NULL);
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (from->sa_family == AF_INET6) {
+ lport = ((struct sockaddr_in6 *)to)->sin6_port;
+ rport = ((struct sockaddr_in6 *)from)->sin6_port;
+ } else {
+ return (NULL);
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (from->sa_family == AF_CONN) {
+ lport = ((struct sockaddr_conn *)to)->sconn_port;
+ rport = ((struct sockaddr_conn *)from)->sconn_port;
+ } else {
+ return (NULL);
+ }
+ break;
+#endif
+ default:
+ return (NULL);
+ }
+ ephead = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR((lport | rport), SCTP_BASE_INFO(hashtcpmark))];
+ /*
+ * Ok now for each of the guys in this bucket we must look and see:
+ * - Does the remote port match. - Does there single association's
+ * addresses match this address (to). If so we update p_ep to point
+ * to this ep and return the tcb from it.
+ */
+ LIST_FOREACH(inp, ephead, sctp_hash) {
+ SCTP_INP_RLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ if (lport != inp->sctp_lport) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ switch (to->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)to;
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)to;
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ break;
+ }
+#endif
+ default:
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#endif
+#ifdef SCTP_MVRF
+ fnd = 0;
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (inp->m_vrf_ids[i] == vrf_id) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (fnd == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#else
+ if (inp->def_vrf_id != vrf_id) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#endif
+ /* check to see if the ep has one of the addresses */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) {
+ /* We are NOT bound all, so look further */
+ int match = 0;
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB1, "%s: NULL ifa\n", __func__);
+ continue;
+ }
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
+ SCTPDBG(SCTP_DEBUG_PCB1, "ifa being deleted\n");
+ continue;
+ }
+ if (laddr->ifa->address.sa.sa_family ==
+ to->sa_family) {
+ /* see if it matches */
+#ifdef INET
+ if (from->sa_family == AF_INET) {
+ struct sockaddr_in *intf_addr, *sin;
+
+ intf_addr = &laddr->ifa->address.sin;
+ sin = (struct sockaddr_in *)to;
+ if (sin->sin_addr.s_addr ==
+ intf_addr->sin_addr.s_addr) {
+ match = 1;
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ if (from->sa_family == AF_INET6) {
+ struct sockaddr_in6 *intf_addr6;
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)
+ to;
+ intf_addr6 = &laddr->ifa->address.sin6;
+
+ if (SCTP6_ARE_ADDR_EQUAL(sin6,
+ intf_addr6)) {
+ match = 1;
+ break;
+ }
+ }
+#endif
+#if defined(__Userspace__)
+ if (from->sa_family == AF_CONN) {
+ struct sockaddr_conn *intf_addr, *sconn;
+
+ intf_addr = &laddr->ifa->address.sconn;
+ sconn = (struct sockaddr_conn *)to;
+ if (sconn->sconn_addr ==
+ intf_addr->sconn_addr) {
+ match = 1;
+ break;
+ }
+ }
+#endif
+ }
+ }
+ if (match == 0) {
+ /* This endpoint does not have this address */
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ }
+ /*
+ * Ok if we hit here the ep has the address, does it hold
+ * the tcb?
+ */
+ /* XXX: Why don't we TAILQ_FOREACH through sctp_asoc_list? */
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb == NULL) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ SCTP_TCB_LOCK(stcb);
+ if (!sctp_does_stcb_own_this_addr(stcb, to)) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ if (stcb->rport != rport) {
+ /* remote port does not match. */
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ if (!sctp_does_stcb_own_this_addr(stcb, to)) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ /* Does this TCB have a matching address? */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (net->ro._l_addr.sa.sa_family != from->sa_family) {
+ /* not the same family, can't be a match */
+ continue;
+ }
+ switch (from->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin, *rsin;
+
+ sin = (struct sockaddr_in *)&net->ro._l_addr;
+ rsin = (struct sockaddr_in *)from;
+ if (sin->sin_addr.s_addr ==
+ rsin->sin_addr.s_addr) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ /* Update the endpoint pointer */
+ *inp_p = inp;
+ SCTP_INP_RUNLOCK(inp);
+ return (stcb);
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6, *rsin6;
+
+ sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ rsin6 = (struct sockaddr_in6 *)from;
+ if (SCTP6_ARE_ADDR_EQUAL(sin6,
+ rsin6)) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ /* Update the endpoint pointer */
+ *inp_p = inp;
+ SCTP_INP_RUNLOCK(inp);
+ return (stcb);
+ }
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn, *rsconn;
+
+ sconn = (struct sockaddr_conn *)&net->ro._l_addr;
+ rsconn = (struct sockaddr_conn *)from;
+ if (sconn->sconn_addr == rsconn->sconn_addr) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ /* Update the endpoint pointer */
+ *inp_p = inp;
+ SCTP_INP_RUNLOCK(inp);
+ return (stcb);
+ }
+ break;
+ }
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ }
+ return (NULL);
+}
+
+/*
+ * rules for use
+ *
+ * 1) If I return a NULL you must decrement any INP ref cnt. 2) If I find an
+ * stcb, both will be locked (locked_tcb and stcb) but decrement will be done
+ * (if locked == NULL). 3) Decrement happens on return ONLY if locked ==
+ * NULL.
+ */
+
+struct sctp_tcb *
+sctp_findassociation_ep_addr(struct sctp_inpcb **inp_p, struct sockaddr *remote,
+ struct sctp_nets **netp, struct sockaddr *local, struct sctp_tcb *locked_tcb)
+{
+ struct sctpasochead *head;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb = NULL;
+ struct sctp_nets *net;
+ uint16_t rport;
+
+ inp = *inp_p;
+ switch (remote->sa_family) {
+#ifdef INET
+ case AF_INET:
+ rport = (((struct sockaddr_in *)remote)->sin_port);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ rport = (((struct sockaddr_in6 *)remote)->sin6_port);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ rport = (((struct sockaddr_conn *)remote)->sconn_port);
+ break;
+#endif
+ default:
+ return (NULL);
+ }
+ if (locked_tcb) {
+ /*
+ * UN-lock so we can do proper locking here this occurs when
+ * called from load_addresses_from_init.
+ */
+ atomic_add_int(&locked_tcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ SCTP_INP_INFO_RLOCK();
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ /*-
+ * Now either this guy is our listener or it's the
+ * connector. If it is the one that issued the connect, then
+ * it's only chance is to be the first TCB in the list. If
+ * it is the acceptor, then do the special_lookup to hash
+ * and find the real inp.
+ */
+ if ((inp->sctp_socket) && SCTP_IS_LISTENING(inp)) {
+ /* to is peer addr, from is my addr */
+#ifndef SCTP_MVRF
+ stcb = sctp_tcb_special_locate(inp_p, remote, local,
+ netp, inp->def_vrf_id);
+ if ((stcb != NULL) && (locked_tcb == NULL)) {
+ /* we have a locked tcb, lower refcount */
+ SCTP_INP_DECR_REF(inp);
+ }
+ if ((locked_tcb != NULL) && (locked_tcb != stcb)) {
+ SCTP_INP_RLOCK(locked_tcb->sctp_ep);
+ SCTP_TCB_LOCK(locked_tcb);
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ SCTP_INP_RUNLOCK(locked_tcb->sctp_ep);
+ }
+#else
+ /*-
+ * MVRF is tricky, we must look in every VRF
+ * the endpoint has.
+ */
+ int i;
+
+ for (i = 0; i < inp->num_vrfs; i++) {
+ stcb = sctp_tcb_special_locate(inp_p, remote, local,
+ netp, inp->m_vrf_ids[i]);
+ if ((stcb != NULL) && (locked_tcb == NULL)) {
+ /* we have a locked tcb, lower refcount */
+ SCTP_INP_DECR_REF(inp);
+ break;
+ }
+ if ((locked_tcb != NULL) && (locked_tcb != stcb)) {
+ SCTP_INP_RLOCK(locked_tcb->sctp_ep);
+ SCTP_TCB_LOCK(locked_tcb);
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ SCTP_INP_RUNLOCK(locked_tcb->sctp_ep);
+ break;
+ }
+ }
+#endif
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ } else {
+ SCTP_INP_WLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ goto null_return;
+ }
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb == NULL) {
+ goto null_return;
+ }
+ SCTP_TCB_LOCK(stcb);
+
+ if (stcb->rport != rport) {
+ /* remote port does not match. */
+ SCTP_TCB_UNLOCK(stcb);
+ goto null_return;
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_TCB_UNLOCK(stcb);
+ goto null_return;
+ }
+ if (local && !sctp_does_stcb_own_this_addr(stcb, local)) {
+ SCTP_TCB_UNLOCK(stcb);
+ goto null_return;
+ }
+ /* now look at the list of remote addresses */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+#ifdef INVARIANTS
+ if (net == (TAILQ_NEXT(net, sctp_next))) {
+ panic("Corrupt net list");
+ }
+#endif
+ if (net->ro._l_addr.sa.sa_family !=
+ remote->sa_family) {
+ /* not the same family */
+ continue;
+ }
+ switch (remote->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin, *rsin;
+
+ sin = (struct sockaddr_in *)
+ &net->ro._l_addr;
+ rsin = (struct sockaddr_in *)remote;
+ if (sin->sin_addr.s_addr ==
+ rsin->sin_addr.s_addr) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ if (locked_tcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else if (locked_tcb != stcb) {
+ SCTP_TCB_LOCK(locked_tcb);
+ }
+ if (locked_tcb) {
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ }
+
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6, *rsin6;
+
+ sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ rsin6 = (struct sockaddr_in6 *)remote;
+ if (SCTP6_ARE_ADDR_EQUAL(sin6,
+ rsin6)) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ if (locked_tcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else if (locked_tcb != stcb) {
+ SCTP_TCB_LOCK(locked_tcb);
+ }
+ if (locked_tcb) {
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn, *rsconn;
+
+ sconn = (struct sockaddr_conn *)&net->ro._l_addr;
+ rsconn = (struct sockaddr_conn *)remote;
+ if (sconn->sconn_addr == rsconn->sconn_addr) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ if (locked_tcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else if (locked_tcb != stcb) {
+ SCTP_TCB_LOCK(locked_tcb);
+ }
+ if (locked_tcb) {
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ break;
+ }
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ } else {
+ SCTP_INP_WLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ goto null_return;
+ }
+ head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(rport,
+ inp->sctp_hashmark)];
+ LIST_FOREACH(stcb, head, sctp_tcbhash) {
+ if (stcb->rport != rport) {
+ /* remote port does not match */
+ continue;
+ }
+ SCTP_TCB_LOCK(stcb);
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ if (local && !sctp_does_stcb_own_this_addr(stcb, local)) {
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ /* now look at the list of remote addresses */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+#ifdef INVARIANTS
+ if (net == (TAILQ_NEXT(net, sctp_next))) {
+ panic("Corrupt net list");
+ }
+#endif
+ if (net->ro._l_addr.sa.sa_family !=
+ remote->sa_family) {
+ /* not the same family */
+ continue;
+ }
+ switch (remote->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin, *rsin;
+
+ sin = (struct sockaddr_in *)
+ &net->ro._l_addr;
+ rsin = (struct sockaddr_in *)remote;
+ if (sin->sin_addr.s_addr ==
+ rsin->sin_addr.s_addr) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ if (locked_tcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else if (locked_tcb != stcb) {
+ SCTP_TCB_LOCK(locked_tcb);
+ }
+ if (locked_tcb) {
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6, *rsin6;
+
+ sin6 = (struct sockaddr_in6 *)
+ &net->ro._l_addr;
+ rsin6 = (struct sockaddr_in6 *)remote;
+ if (SCTP6_ARE_ADDR_EQUAL(sin6,
+ rsin6)) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ if (locked_tcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else if (locked_tcb != stcb) {
+ SCTP_TCB_LOCK(locked_tcb);
+ }
+ if (locked_tcb) {
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn, *rsconn;
+
+ sconn = (struct sockaddr_conn *)&net->ro._l_addr;
+ rsconn = (struct sockaddr_conn *)remote;
+ if (sconn->sconn_addr == rsconn->sconn_addr) {
+ /* found it */
+ if (netp != NULL) {
+ *netp = net;
+ }
+ if (locked_tcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else if (locked_tcb != stcb) {
+ SCTP_TCB_LOCK(locked_tcb);
+ }
+ if (locked_tcb) {
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ break;
+ }
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+null_return:
+ /* clean up for returning null */
+ if (locked_tcb) {
+ SCTP_TCB_LOCK(locked_tcb);
+ atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ /* not found */
+ return (NULL);
+}
+
+/*
+ * Find an association for a specific endpoint using the association id given
+ * out in the COMM_UP notification
+ */
+struct sctp_tcb *
+sctp_findasoc_ep_asocid_locked(struct sctp_inpcb *inp, sctp_assoc_t asoc_id, int want_lock)
+{
+ /*
+ * Use my the assoc_id to find a endpoint
+ */
+ struct sctpasochead *head;
+ struct sctp_tcb *stcb;
+ uint32_t id;
+
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ SCTP_PRINTF("TSNH ep_associd0\n");
+ return (NULL);
+ }
+ id = (uint32_t)asoc_id;
+ head = &inp->sctp_asocidhash[SCTP_PCBHASH_ASOC(id, inp->hashasocidmark)];
+ if (head == NULL) {
+ /* invalid id TSNH */
+ SCTP_PRINTF("TSNH ep_associd1\n");
+ return (NULL);
+ }
+ LIST_FOREACH(stcb, head, sctp_tcbasocidhash) {
+ if (stcb->asoc.assoc_id == id) {
+ if (inp != stcb->sctp_ep) {
+ /*
+ * some other guy has the same id active (id
+ * collision ??).
+ */
+ SCTP_PRINTF("TSNH ep_associd2\n");
+ continue;
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ continue;
+ }
+ if (want_lock) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ return (stcb);
+ }
+ }
+ return (NULL);
+}
+
+struct sctp_tcb *
+sctp_findassociation_ep_asocid(struct sctp_inpcb *inp, sctp_assoc_t asoc_id, int want_lock)
+{
+ struct sctp_tcb *stcb;
+
+ SCTP_INP_RLOCK(inp);
+ stcb = sctp_findasoc_ep_asocid_locked(inp, asoc_id, want_lock);
+ SCTP_INP_RUNLOCK(inp);
+ return (stcb);
+}
+
+/*
+ * Endpoint probe expects that the INP_INFO is locked.
+ */
+static struct sctp_inpcb *
+sctp_endpoint_probe(struct sockaddr *nam, struct sctppcbhead *head,
+ uint16_t lport, uint32_t vrf_id)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_laddr *laddr;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sockaddr_in6 *intf_addr6;
+#endif
+#if defined(__Userspace__)
+ struct sockaddr_conn *sconn;
+#endif
+#ifdef SCTP_MVRF
+ int i;
+#endif
+ int fnd;
+
+#ifdef INET
+ sin = NULL;
+#endif
+#ifdef INET6
+ sin6 = NULL;
+#endif
+#if defined(__Userspace__)
+ sconn = NULL;
+#endif
+ switch (nam->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sin = (struct sockaddr_in *)nam;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)nam;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ sconn = (struct sockaddr_conn *)nam;
+ break;
+#endif
+ default:
+ /* unsupported family */
+ return (NULL);
+ }
+
+ if (head == NULL)
+ return (NULL);
+
+ LIST_FOREACH(inp, head, sctp_hash) {
+ SCTP_INP_RLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) &&
+ (inp->sctp_lport == lport)) {
+ /* got it */
+ switch (nam->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ /* IPv4 on a IPv6 socket with ONLY IPv6 set */
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ /* A V6 address and the endpoint is NOT bound V6 */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#endif
+ break;
+#endif
+ default:
+ break;
+ }
+ /* does a VRF id match? */
+ fnd = 0;
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (inp->m_vrf_ids[i] == vrf_id) {
+ fnd = 1;
+ break;
+ }
+ }
+#else
+ if (inp->def_vrf_id == vrf_id)
+ fnd = 1;
+#endif
+
+ SCTP_INP_RUNLOCK(inp);
+ if (!fnd)
+ continue;
+ return (inp);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ switch (nam->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (sin->sin_addr.s_addr == INADDR_ANY) {
+ /* Can't hunt for one that has no address specified */
+ return (NULL);
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* Can't hunt for one that has no address specified */
+ return (NULL);
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (sconn->sconn_addr == NULL) {
+ return (NULL);
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ /*
+ * ok, not bound to all so see if we can find a EP bound to this
+ * address.
+ */
+ LIST_FOREACH(inp, head, sctp_hash) {
+ SCTP_INP_RLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL)) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ /*
+ * Ok this could be a likely candidate, look at all of its
+ * addresses
+ */
+ if (inp->sctp_lport != lport) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ /* does a VRF id match? */
+ fnd = 0;
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (inp->m_vrf_ids[i] == vrf_id) {
+ fnd = 1;
+ break;
+ }
+ }
+#else
+ if (inp->def_vrf_id == vrf_id)
+ fnd = 1;
+
+#endif
+ if (!fnd) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB1, "%s: NULL ifa\n",
+ __func__);
+ continue;
+ }
+ SCTPDBG(SCTP_DEBUG_PCB1, "Ok laddr->ifa:%p is possible, ",
+ (void *)laddr->ifa);
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
+ SCTPDBG(SCTP_DEBUG_PCB1, "Huh IFA being deleted\n");
+ continue;
+ }
+ if (laddr->ifa->address.sa.sa_family == nam->sa_family) {
+ /* possible, see if it matches */
+ switch (nam->sa_family) {
+#ifdef INET
+ case AF_INET:
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (sin == NULL) {
+ /* TSNH */
+ break;
+ }
+#endif
+ if (sin->sin_addr.s_addr ==
+ laddr->ifa->address.sin.sin_addr.s_addr) {
+ SCTP_INP_RUNLOCK(inp);
+ return (inp);
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ intf_addr6 = &laddr->ifa->address.sin6;
+ if (SCTP6_ARE_ADDR_EQUAL(sin6,
+ intf_addr6)) {
+ SCTP_INP_RUNLOCK(inp);
+ return (inp);
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (sconn->sconn_addr == laddr->ifa->address.sconn.sconn_addr) {
+ SCTP_INP_RUNLOCK(inp);
+ return (inp);
+ }
+ break;
+#endif
+ }
+ }
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ return (NULL);
+}
+
+static struct sctp_inpcb *
+sctp_isport_inuse(struct sctp_inpcb *inp, uint16_t lport, uint32_t vrf_id)
+{
+ struct sctppcbhead *head;
+ struct sctp_inpcb *t_inp;
+#ifdef SCTP_MVRF
+ int i;
+#endif
+ int fnd;
+
+ head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(lport,
+ SCTP_BASE_INFO(hashmark))];
+ LIST_FOREACH(t_inp, head, sctp_hash) {
+ if (t_inp->sctp_lport != lport) {
+ continue;
+ }
+ /* is it in the VRF in question */
+ fnd = 0;
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (t_inp->m_vrf_ids[i] == vrf_id) {
+ fnd = 1;
+ break;
+ }
+ }
+#else
+ if (t_inp->def_vrf_id == vrf_id)
+ fnd = 1;
+#endif
+ if (!fnd)
+ continue;
+
+ /* This one is in use. */
+ /* check the v6/v4 binding issue */
+ if ((t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(t_inp)) {
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ /* collision in V6 space */
+ return (t_inp);
+ } else {
+ /* inp is BOUND_V4 no conflict */
+ continue;
+ }
+ } else if (t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ /* t_inp is bound v4 and v6, conflict always */
+ return (t_inp);
+ } else {
+ /* t_inp is bound only V4 */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ /* no conflict */
+ continue;
+ }
+ /* else fall through to conflict */
+ }
+ return (t_inp);
+ }
+ return (NULL);
+}
+
+int
+sctp_swap_inpcb_for_listen(struct sctp_inpcb *inp)
+{
+ /* For 1-2-1 with port reuse */
+ struct sctppcbhead *head;
+ struct sctp_inpcb *tinp, *ninp;
+
+ SCTP_INP_INFO_WLOCK_ASSERT();
+ SCTP_INP_WLOCK_ASSERT(inp);
+
+ if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_PORTREUSE)) {
+ /* only works with port reuse on */
+ return (-1);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) {
+ return (0);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(inp->sctp_lport,
+ SCTP_BASE_INFO(hashmark))];
+ /* Kick out all non-listeners to the TCP hash */
+ LIST_FOREACH_SAFE(tinp, head, sctp_hash, ninp) {
+ if (tinp->sctp_lport != inp->sctp_lport) {
+ continue;
+ }
+ if (tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ continue;
+ }
+ if (tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ continue;
+ }
+ if (SCTP_IS_LISTENING(tinp)) {
+ continue;
+ }
+ SCTP_INP_WLOCK(tinp);
+ LIST_REMOVE(tinp, sctp_hash);
+ head = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR(tinp->sctp_lport, SCTP_BASE_INFO(hashtcpmark))];
+ tinp->sctp_flags |= SCTP_PCB_FLAGS_IN_TCPPOOL;
+ LIST_INSERT_HEAD(head, tinp, sctp_hash);
+ SCTP_INP_WUNLOCK(tinp);
+ }
+ SCTP_INP_WLOCK(inp);
+ /* Pull from where he was */
+ LIST_REMOVE(inp, sctp_hash);
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_IN_TCPPOOL;
+ head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(inp->sctp_lport, SCTP_BASE_INFO(hashmark))];
+ LIST_INSERT_HEAD(head, inp, sctp_hash);
+ return (0);
+}
+
+struct sctp_inpcb *
+sctp_pcb_findep(struct sockaddr *nam, int find_tcp_pool, int have_lock,
+ uint32_t vrf_id)
+{
+ /*
+ * First we check the hash table to see if someone has this port
+ * bound with just the port.
+ */
+ struct sctp_inpcb *inp;
+ struct sctppcbhead *head;
+ int lport;
+ unsigned int i;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+#if defined(__Userspace__)
+ struct sockaddr_conn *sconn;
+#endif
+
+ switch (nam->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sin = (struct sockaddr_in *)nam;
+ lport = sin->sin_port;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)nam;
+ lport = sin6->sin6_port;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ sconn = (struct sockaddr_conn *)nam;
+ lport = sconn->sconn_port;
+ break;
+#endif
+ default:
+ return (NULL);
+ }
+ /*
+ * I could cheat here and just cast to one of the types but we will
+ * do it right. It also provides the check against an Unsupported
+ * type too.
+ */
+ /* Find the head of the ALLADDR chain */
+ if (have_lock == 0) {
+ SCTP_INP_INFO_RLOCK();
+ }
+ head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(lport,
+ SCTP_BASE_INFO(hashmark))];
+ inp = sctp_endpoint_probe(nam, head, lport, vrf_id);
+
+ /*
+ * If the TCP model exists it could be that the main listening
+ * endpoint is gone but there still exists a connected socket for this
+ * guy. If so we can return the first one that we find. This may NOT
+ * be the correct one so the caller should be wary on the returned INP.
+ * Currently the only caller that sets find_tcp_pool is in bindx where
+ * we are verifying that a user CAN bind the address. He either
+ * has bound it already, or someone else has, or its open to bind,
+ * so this is good enough.
+ */
+ if (inp == NULL && find_tcp_pool) {
+ for (i = 0; i < SCTP_BASE_INFO(hashtcpmark) + 1; i++) {
+ head = &SCTP_BASE_INFO(sctp_tcpephash)[i];
+ inp = sctp_endpoint_probe(nam, head, lport, vrf_id);
+ if (inp) {
+ break;
+ }
+ }
+ }
+ if (inp) {
+ SCTP_INP_INCR_REF(inp);
+ }
+ if (have_lock == 0) {
+ SCTP_INP_INFO_RUNLOCK();
+ }
+ return (inp);
+}
+
+/*
+ * Find an association for an endpoint with the pointer to whom you want to
+ * send to and the endpoint pointer. The address can be IPv4 or IPv6. We may
+ * need to change the *to to some other struct like a mbuf...
+ */
+struct sctp_tcb *
+sctp_findassociation_addr_sa(struct sockaddr *from, struct sockaddr *to,
+ struct sctp_inpcb **inp_p, struct sctp_nets **netp, int find_tcp_pool,
+ uint32_t vrf_id)
+{
+ struct sctp_inpcb *inp = NULL;
+ struct sctp_tcb *stcb;
+
+ SCTP_INP_INFO_RLOCK();
+ if (find_tcp_pool) {
+ if (inp_p != NULL) {
+ stcb = sctp_tcb_special_locate(inp_p, from, to, netp,
+ vrf_id);
+ } else {
+ stcb = sctp_tcb_special_locate(&inp, from, to, netp,
+ vrf_id);
+ }
+ if (stcb != NULL) {
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ }
+ inp = sctp_pcb_findep(to, 0, 1, vrf_id);
+ if (inp_p != NULL) {
+ *inp_p = inp;
+ }
+ SCTP_INP_INFO_RUNLOCK();
+ if (inp == NULL) {
+ return (NULL);
+ }
+ /*
+ * ok, we have an endpoint, now lets find the assoc for it (if any)
+ * we now place the source address or from in the to of the find
+ * endpoint call. Since in reality this chain is used from the
+ * inbound packet side.
+ */
+ if (inp_p != NULL) {
+ stcb = sctp_findassociation_ep_addr(inp_p, from, netp, to,
+ NULL);
+ } else {
+ stcb = sctp_findassociation_ep_addr(&inp, from, netp, to,
+ NULL);
+ }
+ return (stcb);
+}
+
+/*
+ * This routine will grub through the mbuf that is a INIT or INIT-ACK and
+ * find all addresses that the sender has specified in any address list. Each
+ * address will be used to lookup the TCB and see if one exits.
+ */
+static struct sctp_tcb *
+sctp_findassociation_special_addr(struct mbuf *m, int offset,
+ struct sctphdr *sh, struct sctp_inpcb **inp_p, struct sctp_nets **netp,
+ struct sockaddr *dst)
+{
+ struct sctp_paramhdr *phdr, param_buf;
+#if defined(INET) || defined(INET6)
+ struct sctp_tcb *stcb;
+ uint16_t ptype;
+#endif
+ uint16_t plen;
+#ifdef INET
+ struct sockaddr_in sin4;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+
+#ifdef INET
+ memset(&sin4, 0, sizeof(sin4));
+#ifdef HAVE_SIN_LEN
+ sin4.sin_len = sizeof(sin4);
+#endif
+ sin4.sin_family = AF_INET;
+ sin4.sin_port = sh->src_port;
+#endif
+#ifdef INET6
+ memset(&sin6, 0, sizeof(sin6));
+#ifdef HAVE_SIN6_LEN
+ sin6.sin6_len = sizeof(sin6);
+#endif
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = sh->src_port;
+#endif
+
+ offset += sizeof(struct sctp_init_chunk);
+
+ phdr = sctp_get_next_param(m, offset, &param_buf, sizeof(param_buf));
+ while (phdr != NULL) {
+ /* now we must see if we want the parameter */
+#if defined(INET) || defined(INET6)
+ ptype = ntohs(phdr->param_type);
+#endif
+ plen = ntohs(phdr->param_length);
+ if (plen == 0) {
+ break;
+ }
+#ifdef INET
+ if (ptype == SCTP_IPV4_ADDRESS &&
+ plen == sizeof(struct sctp_ipv4addr_param)) {
+ /* Get the rest of the address */
+ struct sctp_ipv4addr_param ip4_param, *p4;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&ip4_param, sizeof(ip4_param));
+ if (phdr == NULL) {
+ return (NULL);
+ }
+ p4 = (struct sctp_ipv4addr_param *)phdr;
+ memcpy(&sin4.sin_addr, &p4->addr, sizeof(p4->addr));
+ /* look it up */
+ stcb = sctp_findassociation_ep_addr(inp_p,
+ (struct sockaddr *)&sin4, netp, dst, NULL);
+ if (stcb != NULL) {
+ return (stcb);
+ }
+ }
+#endif
+#ifdef INET6
+ if (ptype == SCTP_IPV6_ADDRESS &&
+ plen == sizeof(struct sctp_ipv6addr_param)) {
+ /* Get the rest of the address */
+ struct sctp_ipv6addr_param ip6_param, *p6;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&ip6_param, sizeof(ip6_param));
+ if (phdr == NULL) {
+ return (NULL);
+ }
+ p6 = (struct sctp_ipv6addr_param *)phdr;
+ memcpy(&sin6.sin6_addr, &p6->addr, sizeof(p6->addr));
+ /* look it up */
+ stcb = sctp_findassociation_ep_addr(inp_p,
+ (struct sockaddr *)&sin6, netp, dst, NULL);
+ if (stcb != NULL) {
+ return (stcb);
+ }
+ }
+#endif
+ offset += SCTP_SIZE32(plen);
+ phdr = sctp_get_next_param(m, offset, &param_buf,
+ sizeof(param_buf));
+ }
+ return (NULL);
+}
+
+static struct sctp_tcb *
+sctp_findassoc_by_vtag(struct sockaddr *from, struct sockaddr *to, uint32_t vtag,
+ struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint16_t rport,
+ uint16_t lport, int skip_src_check, uint32_t vrf_id, uint32_t remote_tag)
+{
+ /*
+ * Use my vtag to hash. If we find it we then verify the source addr
+ * is in the assoc. If all goes well we save a bit on rec of a
+ * packet.
+ */
+ struct sctpasochead *head;
+ struct sctp_nets *net;
+ struct sctp_tcb *stcb;
+#ifdef SCTP_MVRF
+ unsigned int i;
+#endif
+
+ SCTP_INP_INFO_RLOCK();
+ head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(vtag,
+ SCTP_BASE_INFO(hashasocmark))];
+ LIST_FOREACH(stcb, head, sctp_asocs) {
+ SCTP_INP_RLOCK(stcb->sctp_ep);
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ SCTP_INP_RUNLOCK(stcb->sctp_ep);
+ continue;
+ }
+#ifdef SCTP_MVRF
+ for (i = 0; i < stcb->sctp_ep->num_vrfs; i++) {
+ if (stcb->sctp_ep->m_vrf_ids[i] == vrf_id) {
+ break;
+ }
+ }
+ if (i == stcb->sctp_ep->num_vrfs) {
+ SCTP_INP_RUNLOCK(inp);
+ continue;
+ }
+#else
+ if (stcb->sctp_ep->def_vrf_id != vrf_id) {
+ SCTP_INP_RUNLOCK(stcb->sctp_ep);
+ continue;
+ }
+#endif
+ SCTP_TCB_LOCK(stcb);
+ SCTP_INP_RUNLOCK(stcb->sctp_ep);
+ if (stcb->asoc.my_vtag == vtag) {
+ /* candidate */
+ if (stcb->rport != rport) {
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ if (stcb->sctp_ep->sctp_lport != lport) {
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ /* RRS:Need toaddr check here */
+ if (sctp_does_stcb_own_this_addr(stcb, to) == 0) {
+ /* Endpoint does not own this address */
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ if (remote_tag) {
+ /* If we have both vtags that's all we match on */
+ if (stcb->asoc.peer_vtag == remote_tag) {
+ /* If both tags match we consider it conclusive
+ * and check NO source/destination addresses
+ */
+ goto conclusive;
+ }
+ }
+ if (skip_src_check) {
+ conclusive:
+ if (from) {
+ *netp = sctp_findnet(stcb, from);
+ } else {
+ *netp = NULL; /* unknown */
+ }
+ if (inp_p)
+ *inp_p = stcb->sctp_ep;
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ }
+ net = sctp_findnet(stcb, from);
+ if (net) {
+ /* yep its him. */
+ *netp = net;
+ SCTP_STAT_INCR(sctps_vtagexpress);
+ *inp_p = stcb->sctp_ep;
+ SCTP_INP_INFO_RUNLOCK();
+ return (stcb);
+ } else {
+ /*
+ * not him, this should only happen in rare
+ * cases so I peg it.
+ */
+ SCTP_STAT_INCR(sctps_vtagbogus);
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_INFO_RUNLOCK();
+ return (NULL);
+}
+
+/*
+ * Find an association with the pointer to the inbound IP packet. This can be
+ * a IPv4 or IPv6 packet.
+ */
+struct sctp_tcb *
+sctp_findassociation_addr(struct mbuf *m, int offset,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_chunkhdr *ch,
+ struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint32_t vrf_id)
+{
+ struct sctp_tcb *stcb;
+ struct sctp_inpcb *inp;
+
+ if (sh->v_tag) {
+ /* we only go down this path if vtag is non-zero */
+ stcb = sctp_findassoc_by_vtag(src, dst, ntohl(sh->v_tag),
+ inp_p, netp, sh->src_port, sh->dest_port, 0, vrf_id, 0);
+ if (stcb) {
+ return (stcb);
+ }
+ }
+
+ if (inp_p) {
+ stcb = sctp_findassociation_addr_sa(src, dst, inp_p, netp,
+ 1, vrf_id);
+ inp = *inp_p;
+ } else {
+ stcb = sctp_findassociation_addr_sa(src, dst, &inp, netp,
+ 1, vrf_id);
+ }
+ SCTPDBG(SCTP_DEBUG_PCB1, "stcb:%p inp:%p\n", (void *)stcb, (void *)inp);
+ if (stcb == NULL && inp) {
+ /* Found a EP but not this address */
+ if ((ch->chunk_type == SCTP_INITIATION) ||
+ (ch->chunk_type == SCTP_INITIATION_ACK)) {
+ /*-
+ * special hook, we do NOT return linp or an
+ * association that is linked to an existing
+ * association that is under the TCP pool (i.e. no
+ * listener exists). The endpoint finding routine
+ * will always find a listener before examining the
+ * TCP pool.
+ */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) {
+ if (inp_p) {
+ *inp_p = NULL;
+ }
+ return (NULL);
+ }
+ stcb = sctp_findassociation_special_addr(m,
+ offset, sh, &inp, netp, dst);
+ if (inp_p != NULL) {
+ *inp_p = inp;
+ }
+ }
+ }
+ SCTPDBG(SCTP_DEBUG_PCB1, "stcb is %p\n", (void *)stcb);
+ return (stcb);
+}
+
+/*
+ * lookup an association by an ASCONF lookup address.
+ * if the lookup address is 0.0.0.0 or ::0, use the vtag to do the lookup
+ */
+struct sctp_tcb *
+sctp_findassociation_ep_asconf(struct mbuf *m, int offset,
+ struct sockaddr *dst, struct sctphdr *sh,
+ struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint32_t vrf_id)
+{
+ struct sctp_tcb *stcb;
+ union sctp_sockstore remote_store;
+ struct sctp_paramhdr param_buf, *phdr;
+ int ptype;
+ int zero_address = 0;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+
+ memset(&remote_store, 0, sizeof(remote_store));
+ phdr = sctp_get_next_param(m, offset + sizeof(struct sctp_asconf_chunk),
+ &param_buf, sizeof(struct sctp_paramhdr));
+ if (phdr == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s: failed to get asconf lookup addr\n",
+ __func__);
+ return NULL;
+ }
+ ptype = (int)((uint32_t) ntohs(phdr->param_type));
+ /* get the correlation address */
+ switch (ptype) {
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ {
+ /* ipv6 address param */
+ struct sctp_ipv6addr_param *p6, p6_buf;
+
+ if (ntohs(phdr->param_length) != sizeof(struct sctp_ipv6addr_param)) {
+ return NULL;
+ }
+ p6 = (struct sctp_ipv6addr_param *)sctp_get_next_param(m,
+ offset + sizeof(struct sctp_asconf_chunk),
+ &p6_buf.ph, sizeof(p6_buf));
+ if (p6 == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s: failed to get asconf v6 lookup addr\n",
+ __func__);
+ return (NULL);
+ }
+ sin6 = &remote_store.sin6;
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(*sin6);
+#endif
+ sin6->sin6_port = sh->src_port;
+ memcpy(&sin6->sin6_addr, &p6->addr, sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+ zero_address = 1;
+ break;
+ }
+#endif
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ {
+ /* ipv4 address param */
+ struct sctp_ipv4addr_param *p4, p4_buf;
+
+ if (ntohs(phdr->param_length) != sizeof(struct sctp_ipv4addr_param)) {
+ return NULL;
+ }
+ p4 = (struct sctp_ipv4addr_param *)sctp_get_next_param(m,
+ offset + sizeof(struct sctp_asconf_chunk),
+ &p4_buf.ph, sizeof(p4_buf));
+ if (p4 == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s: failed to get asconf v4 lookup addr\n",
+ __func__);
+ return (NULL);
+ }
+ sin = &remote_store.sin;
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(*sin);
+#endif
+ sin->sin_port = sh->src_port;
+ memcpy(&sin->sin_addr, &p4->addr, sizeof(struct in_addr));
+ if (sin->sin_addr.s_addr == INADDR_ANY)
+ zero_address = 1;
+ break;
+ }
+#endif
+ default:
+ /* invalid address param type */
+ return NULL;
+ }
+
+ if (zero_address) {
+ stcb = sctp_findassoc_by_vtag(NULL, dst, ntohl(sh->v_tag), inp_p,
+ netp, sh->src_port, sh->dest_port, 1, vrf_id, 0);
+ if (stcb != NULL) {
+ SCTP_INP_DECR_REF(*inp_p);
+ }
+ } else {
+ stcb = sctp_findassociation_ep_addr(inp_p,
+ &remote_store.sa, netp,
+ dst, NULL);
+ }
+ return (stcb);
+}
+
+/*
+ * allocate a sctp_inpcb and setup a temporary binding to a port/all
+ * addresses. This way if we don't get a bind we by default pick a ephemeral
+ * port with all addresses bound.
+ */
+int
+sctp_inpcb_alloc(struct socket *so, uint32_t vrf_id)
+{
+ /*
+ * we get called when a new endpoint starts up. We need to allocate
+ * the sctp_inpcb structure from the zone and init it. Mark it as
+ * unbound and find a port that we can use as an ephemeral with
+ * INADDR_ANY. If the user binds later no problem we can then add in
+ * the specific addresses. And setup the default parameters for the
+ * EP.
+ */
+ int i, error;
+ struct sctp_inpcb *inp;
+ struct sctp_pcb *m;
+ struct timeval time;
+ sctp_sharedkey_t *null_key;
+
+ error = 0;
+
+ SCTP_INP_INFO_WLOCK();
+ inp = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_ep), struct sctp_inpcb);
+ if (inp == NULL) {
+ SCTP_PRINTF("Out of SCTP-INPCB structures - no resources\n");
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOBUFS);
+ return (ENOBUFS);
+ }
+ /* zap it */
+ memset(inp, 0, sizeof(*inp));
+
+ /* bump generations */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ inp->ip_inp.inp.inp_state = INPCB_STATE_INUSE;
+#endif
+ /* setup socket pointers */
+ inp->sctp_socket = so;
+ inp->ip_inp.inp.inp_socket = so;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ inp->ip_inp.inp.inp_cred = crhold(so->so_cred);
+#endif
+#ifdef INET6
+#if !defined(__Userspace__) && !defined(_WIN32)
+ if (INP_SOCKAF(so) == AF_INET6) {
+ if (MODULE_GLOBAL(ip6_auto_flowlabel)) {
+ inp->ip_inp.inp.inp_flags |= IN6P_AUTOFLOWLABEL;
+ }
+ if (MODULE_GLOBAL(ip6_v6only)) {
+ inp->ip_inp.inp.inp_flags |= IN6P_IPV6_V6ONLY;
+ }
+ }
+#endif
+#endif
+ inp->sctp_associd_counter = 1;
+ inp->partial_delivery_point = SCTP_SB_LIMIT_RCV(so) >> SCTP_PARTIAL_DELIVERY_SHIFT;
+ inp->sctp_frag_point = 0;
+ inp->max_cwnd = 0;
+ inp->sctp_cmt_on_off = SCTP_BASE_SYSCTL(sctp_cmt_on_off);
+ inp->ecn_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_ecn_enable);
+ inp->prsctp_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_pr_enable);
+ inp->auth_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_auth_enable);
+ inp->asconf_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_asconf_enable);
+ inp->reconfig_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_reconfig_enable);
+ inp->nrsack_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_nrsack_enable);
+ inp->pktdrop_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_pktdrop_enable);
+ inp->idata_supported = 0;
+ inp->rcv_edmid = SCTP_EDMID_NONE;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ inp->fibnum = so->so_fibnum;
+#else
+ inp->fibnum = 0;
+#endif
+#if defined(__Userspace__)
+ inp->ulp_info = NULL;
+ inp->recv_callback = NULL;
+ inp->send_callback = NULL;
+ inp->send_sb_threshold = 0;
+#endif
+ /* init the small hash table we use to track asocid <-> tcb */
+ inp->sctp_asocidhash = SCTP_HASH_INIT(SCTP_STACK_VTAG_HASH_SIZE, &inp->hashasocidmark);
+ if (inp->sctp_asocidhash == NULL) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ crfree(inp->ip_inp.inp.inp_cred);
+#endif
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (ENOBUFS);
+ }
+ SCTP_INCR_EP_COUNT();
+ inp->ip_inp.inp.inp_ip_ttl = MODULE_GLOBAL(ip_defttl);
+ SCTP_INP_INFO_WUNLOCK();
+
+ so->so_pcb = (caddr_t)inp;
+
+ if (SCTP_SO_TYPE(so) == SOCK_SEQPACKET) {
+ /* UDP style socket */
+ inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE |
+ SCTP_PCB_FLAGS_UNBOUND);
+ /* Be sure it is NON-BLOCKING IO for UDP */
+ /* SCTP_SET_SO_NBIO(so); */
+ } else if (SCTP_SO_TYPE(so) == SOCK_STREAM) {
+ /* TCP style socket */
+ inp->sctp_flags = (SCTP_PCB_FLAGS_TCPTYPE |
+ SCTP_PCB_FLAGS_UNBOUND);
+ /* Be sure we have blocking IO by default */
+ SOCK_LOCK(so);
+ SCTP_CLEAR_SO_NBIO(so);
+ SOCK_UNLOCK(so);
+ } else {
+ /*
+ * unsupported socket type (RAW, etc)- in case we missed it
+ * in protosw
+ */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EOPNOTSUPP);
+ so->so_pcb = NULL;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ crfree(inp->ip_inp.inp.inp_cred);
+#endif
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ return (EOPNOTSUPP);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_default_frag_interleave) == SCTP_FRAG_LEVEL_1) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE);
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS);
+ } else if (SCTP_BASE_SYSCTL(sctp_default_frag_interleave) == SCTP_FRAG_LEVEL_2) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE);
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS);
+ } else if (SCTP_BASE_SYSCTL(sctp_default_frag_interleave) == SCTP_FRAG_LEVEL_0) {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE);
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS);
+ }
+ inp->sctp_tcbhash = SCTP_HASH_INIT(SCTP_BASE_SYSCTL(sctp_pcbtblsize),
+ &inp->sctp_hashmark);
+ if (inp->sctp_tcbhash == NULL) {
+ SCTP_PRINTF("Out of SCTP-INPCB->hashinit - no resources\n");
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOBUFS);
+ so->so_pcb = NULL;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ crfree(inp->ip_inp.inp.inp_cred);
+#endif
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ return (ENOBUFS);
+ }
+#ifdef SCTP_MVRF
+ inp->vrf_size = SCTP_DEFAULT_VRF_SIZE;
+ SCTP_MALLOC(inp->m_vrf_ids, uint32_t *,
+ (sizeof(uint32_t) * inp->vrf_size), SCTP_M_MVRF);
+ if (inp->m_vrf_ids == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOBUFS);
+ so->so_pcb = NULL;
+ SCTP_HASH_FREE(inp->sctp_tcbhash, inp->sctp_hashmark);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ crfree(inp->ip_inp.inp.inp_cred);
+#endif
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ return (ENOBUFS);
+ }
+ inp->m_vrf_ids[0] = vrf_id;
+ inp->num_vrfs = 1;
+#endif
+ inp->def_vrf_id = vrf_id;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ inp->ip_inp.inp.inpcb_mtx = lck_mtx_alloc_init(SCTP_BASE_INFO(sctbinfo).mtx_grp, SCTP_BASE_INFO(sctbinfo).mtx_attr);
+ if (inp->ip_inp.inp.inpcb_mtx == NULL) {
+ SCTP_PRINTF("in_pcballoc: can't alloc mutex! so=%p\n", (void *)so);
+#ifdef SCTP_MVRF
+ SCTP_FREE(inp->m_vrf_ids, SCTP_M_MVRF);
+#endif
+ SCTP_HASH_FREE(inp->sctp_tcbhash, inp->sctp_hashmark);
+ so->so_pcb = NULL;
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ SCTP_UNLOCK_EXC(SCTP_BASE_INFO(sctbinfo).ipi_lock);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOMEM);
+ return (ENOMEM);
+ }
+#elif defined(APPLE_LION) || defined(APPLE_MOUNTAINLION)
+ lck_mtx_init(&inp->ip_inp.inp.inpcb_mtx, SCTP_BASE_INFO(sctbinfo).mtx_grp, SCTP_BASE_INFO(sctbinfo).mtx_attr);
+#else
+ lck_mtx_init(&inp->ip_inp.inp.inpcb_mtx, SCTP_BASE_INFO(sctbinfo).ipi_lock_grp, SCTP_BASE_INFO(sctbinfo).ipi_lock_attr);
+#endif
+#endif
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_LOCK_INIT(inp);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ rw_init_flags(&inp->ip_inp.inp.inp_lock, "sctpinp",
+ RW_RECURSE | RW_DUPOK);
+#endif
+ SCTP_INP_READ_LOCK_INIT(inp);
+ SCTP_ASOC_CREATE_LOCK_INIT(inp);
+ /* lock the new ep */
+ SCTP_INP_WLOCK(inp);
+
+ /* add it to the info area */
+ LIST_INSERT_HEAD(&SCTP_BASE_INFO(listhead), inp, sctp_list);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ inp->ip_inp.inp.inp_pcbinfo = &SCTP_BASE_INFO(sctbinfo);
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD) || defined(APPLE_LION) || defined(APPLE_MOUNTAINLION)
+ LIST_INSERT_HEAD(SCTP_BASE_INFO(sctbinfo).listhead, &inp->ip_inp.inp, inp_list);
+#else
+ LIST_INSERT_HEAD(SCTP_BASE_INFO(sctbinfo).ipi_listhead, &inp->ip_inp.inp, inp_list);
+#endif
+#endif
+ SCTP_INP_INFO_WUNLOCK();
+
+ TAILQ_INIT(&inp->read_queue);
+ LIST_INIT(&inp->sctp_addr_list);
+
+ LIST_INIT(&inp->sctp_asoc_list);
+
+#ifdef SCTP_TRACK_FREED_ASOCS
+ /* TEMP CODE */
+ LIST_INIT(&inp->sctp_asoc_free_list);
+#endif
+ /* Init the timer structure for signature change */
+ SCTP_OS_TIMER_INIT(&inp->sctp_ep.signature_change.timer);
+ inp->sctp_ep.signature_change.type = SCTP_TIMER_TYPE_NEWCOOKIE;
+
+ /* now init the actual endpoint default data */
+ m = &inp->sctp_ep;
+
+ /* setup the base timeout information */
+ m->sctp_timeoutticks[SCTP_TIMER_SEND] = sctp_secs_to_ticks(SCTP_SEND_SEC); /* needed ? */
+ m->sctp_timeoutticks[SCTP_TIMER_INIT] = sctp_secs_to_ticks(SCTP_INIT_SEC); /* needed ? */
+ m->sctp_timeoutticks[SCTP_TIMER_RECV] = sctp_msecs_to_ticks(SCTP_BASE_SYSCTL(sctp_delayed_sack_time_default));
+ m->sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = sctp_msecs_to_ticks(SCTP_BASE_SYSCTL(sctp_heartbeat_interval_default));
+ m->sctp_timeoutticks[SCTP_TIMER_PMTU] = sctp_secs_to_ticks(SCTP_BASE_SYSCTL(sctp_pmtu_raise_time_default));
+ m->sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN] = sctp_secs_to_ticks(SCTP_BASE_SYSCTL(sctp_shutdown_guard_time_default));
+ m->sctp_timeoutticks[SCTP_TIMER_SIGNATURE] = sctp_secs_to_ticks(SCTP_BASE_SYSCTL(sctp_secret_lifetime_default));
+ /* all max/min max are in ms */
+ m->sctp_maxrto = SCTP_BASE_SYSCTL(sctp_rto_max_default);
+ m->sctp_minrto = SCTP_BASE_SYSCTL(sctp_rto_min_default);
+ m->initial_rto = SCTP_BASE_SYSCTL(sctp_rto_initial_default);
+ m->initial_init_rto_max = SCTP_BASE_SYSCTL(sctp_init_rto_max_default);
+ m->sctp_sack_freq = SCTP_BASE_SYSCTL(sctp_sack_freq_default);
+ m->max_init_times = SCTP_BASE_SYSCTL(sctp_init_rtx_max_default);
+ m->max_send_times = SCTP_BASE_SYSCTL(sctp_assoc_rtx_max_default);
+ m->def_net_failure = SCTP_BASE_SYSCTL(sctp_path_rtx_max_default);
+ m->def_net_pf_threshold = SCTP_BASE_SYSCTL(sctp_path_pf_threshold);
+ m->sctp_sws_sender = SCTP_SWS_SENDER_DEF;
+ m->sctp_sws_receiver = SCTP_SWS_RECEIVER_DEF;
+ m->max_burst = SCTP_BASE_SYSCTL(sctp_max_burst_default);
+ m->fr_max_burst = SCTP_BASE_SYSCTL(sctp_fr_max_burst_default);
+
+ m->sctp_default_cc_module = SCTP_BASE_SYSCTL(sctp_default_cc_module);
+ m->sctp_default_ss_module = SCTP_BASE_SYSCTL(sctp_default_ss_module);
+ m->max_open_streams_intome = SCTP_BASE_SYSCTL(sctp_nr_incoming_streams_default);
+ /* number of streams to pre-open on a association */
+ m->pre_open_stream_count = SCTP_BASE_SYSCTL(sctp_nr_outgoing_streams_default);
+
+ m->default_mtu = 0;
+ /* Add adaptation cookie */
+ m->adaptation_layer_indicator = 0;
+ m->adaptation_layer_indicator_provided = 0;
+
+ /* seed random number generator */
+ m->random_counter = 1;
+ m->store_at = SCTP_SIGNATURE_SIZE;
+ SCTP_READ_RANDOM(m->random_numbers, sizeof(m->random_numbers));
+ sctp_fill_random_store(m);
+
+ /* Minimum cookie size */
+ m->size_of_a_cookie = (sizeof(struct sctp_init_msg) * 2) +
+ sizeof(struct sctp_state_cookie);
+ m->size_of_a_cookie += SCTP_SIGNATURE_SIZE;
+
+ /* Setup the initial secret */
+ (void)SCTP_GETTIME_TIMEVAL(&time);
+ m->time_of_secret_change = (unsigned int)time.tv_sec;
+
+ for (i = 0; i < SCTP_NUMBER_OF_SECRETS; i++) {
+ m->secret_key[0][i] = sctp_select_initial_TSN(m);
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL);
+
+ /* How long is a cookie good for ? */
+ m->def_cookie_life = sctp_msecs_to_ticks(SCTP_BASE_SYSCTL(sctp_valid_cookie_life_default));
+ /*
+ * Initialize authentication parameters
+ */
+ m->local_hmacs = sctp_default_supported_hmaclist();
+ m->local_auth_chunks = sctp_alloc_chunklist();
+ if (inp->asconf_supported) {
+ sctp_auth_add_chunk(SCTP_ASCONF, m->local_auth_chunks);
+ sctp_auth_add_chunk(SCTP_ASCONF_ACK, m->local_auth_chunks);
+ }
+ m->default_dscp = 0;
+#ifdef INET6
+ m->default_flowlabel = 0;
+#endif
+ m->port = 0; /* encapsulation disabled by default */
+ LIST_INIT(&m->shared_keys);
+ /* add default NULL key as key id 0 */
+ null_key = sctp_alloc_sharedkey();
+ sctp_insert_sharedkey(&m->shared_keys, null_key);
+ SCTP_INP_WUNLOCK(inp);
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 12);
+#endif
+ return (error);
+}
+
+void
+sctp_move_pcb_and_assoc(struct sctp_inpcb *old_inp, struct sctp_inpcb *new_inp,
+ struct sctp_tcb *stcb)
+{
+ struct sctp_nets *net;
+ uint16_t lport, rport;
+ struct sctppcbhead *head;
+ struct sctp_laddr *laddr, *oladdr;
+
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(old_inp);
+ SCTP_INP_WLOCK(new_inp);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET6
+ if (old_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ new_inp->ip_inp.inp.inp_flags |= old_inp->ip_inp.inp.inp_flags & INP_CONTROLOPTS;
+ if (old_inp->ip_inp.inp.in6p_outputopts) {
+ new_inp->ip_inp.inp.in6p_outputopts = ip6_copypktopts(old_inp->ip_inp.inp.in6p_outputopts, M_NOWAIT);
+ }
+ }
+#endif
+#if defined(INET) && defined(INET6)
+ else
+#endif
+#ifdef INET
+ {
+ new_inp->ip_inp.inp.inp_ip_tos = old_inp->ip_inp.inp.inp_ip_tos;
+ new_inp->ip_inp.inp.inp_ip_ttl = old_inp->ip_inp.inp.inp_ip_ttl;
+ }
+#endif
+#endif
+ new_inp->sctp_ep.time_of_secret_change =
+ old_inp->sctp_ep.time_of_secret_change;
+ memcpy(new_inp->sctp_ep.secret_key, old_inp->sctp_ep.secret_key,
+ sizeof(old_inp->sctp_ep.secret_key));
+ new_inp->sctp_ep.current_secret_number =
+ old_inp->sctp_ep.current_secret_number;
+ new_inp->sctp_ep.last_secret_number =
+ old_inp->sctp_ep.last_secret_number;
+ new_inp->sctp_ep.size_of_a_cookie = old_inp->sctp_ep.size_of_a_cookie;
+
+ /* make it so new data pours into the new socket */
+ stcb->sctp_socket = new_inp->sctp_socket;
+ stcb->sctp_ep = new_inp;
+
+ /* Copy the port across */
+ lport = new_inp->sctp_lport = old_inp->sctp_lport;
+ rport = stcb->rport;
+ /* Pull the tcb from the old association */
+ LIST_REMOVE(stcb, sctp_tcbhash);
+ LIST_REMOVE(stcb, sctp_tcblist);
+ if (stcb->asoc.in_asocid_hash) {
+ LIST_REMOVE(stcb, sctp_tcbasocidhash);
+ }
+ /* Now insert the new_inp into the TCP connected hash */
+ head = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR((lport | rport), SCTP_BASE_INFO(hashtcpmark))];
+
+ LIST_INSERT_HEAD(head, new_inp, sctp_hash);
+ /* Its safe to access */
+ new_inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND;
+
+ /* Now move the tcb into the endpoint list */
+ LIST_INSERT_HEAD(&new_inp->sctp_asoc_list, stcb, sctp_tcblist);
+ /*
+ * Question, do we even need to worry about the ep-hash since we
+ * only have one connection? Probably not :> so lets get rid of it
+ * and not suck up any kernel memory in that.
+ */
+ if (stcb->asoc.in_asocid_hash) {
+ struct sctpasochead *lhd;
+ lhd = &new_inp->sctp_asocidhash[SCTP_PCBHASH_ASOC(stcb->asoc.assoc_id,
+ new_inp->hashasocidmark)];
+ LIST_INSERT_HEAD(lhd, stcb, sctp_tcbasocidhash);
+ }
+ /* Ok. Let's restart timer. */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, new_inp,
+ stcb, net);
+ }
+
+ SCTP_INP_INFO_WUNLOCK();
+ if (new_inp->sctp_tcbhash != NULL) {
+ SCTP_HASH_FREE(new_inp->sctp_tcbhash, new_inp->sctp_hashmark);
+ new_inp->sctp_tcbhash = NULL;
+ }
+ if ((new_inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) {
+ /* Subset bound, so copy in the laddr list from the old_inp */
+ LIST_FOREACH(oladdr, &old_inp->sctp_addr_list, sctp_nxt_addr) {
+ laddr = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr);
+ if (laddr == NULL) {
+ /*
+ * Gak, what can we do? This assoc is really
+ * HOSED. We probably should send an abort
+ * here.
+ */
+ SCTPDBG(SCTP_DEBUG_PCB1, "Association hosed in TCP model, out of laddr memory\n");
+ continue;
+ }
+ SCTP_INCR_LADDR_COUNT();
+ memset(laddr, 0, sizeof(*laddr));
+ (void)SCTP_GETTIME_TIMEVAL(&laddr->start_time);
+ laddr->ifa = oladdr->ifa;
+ atomic_add_int(&laddr->ifa->refcount, 1);
+ LIST_INSERT_HEAD(&new_inp->sctp_addr_list, laddr,
+ sctp_nxt_addr);
+ new_inp->laddr_count++;
+ if (oladdr == stcb->asoc.last_used_address) {
+ stcb->asoc.last_used_address = laddr;
+ }
+ }
+ }
+ /* Now any running timers need to be adjusted. */
+ if (stcb->asoc.dack_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ stcb->asoc.dack_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ if (stcb->asoc.asconf_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ stcb->asoc.asconf_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ if (stcb->asoc.strreset_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ stcb->asoc.strreset_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ if (stcb->asoc.shut_guard_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ stcb->asoc.shut_guard_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ if (stcb->asoc.autoclose_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ stcb->asoc.autoclose_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ if (stcb->asoc.delete_prim_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ stcb->asoc.delete_prim_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ /* now what about the nets? */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (net->pmtu_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ net->pmtu_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ if (net->hb_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ net->hb_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ if (net->rxt_timer.ep == old_inp) {
+ SCTP_INP_DECR_REF(old_inp);
+ net->rxt_timer.ep = new_inp;
+ SCTP_INP_INCR_REF(new_inp);
+ }
+ }
+ SCTP_INP_WUNLOCK(new_inp);
+ SCTP_INP_WUNLOCK(old_inp);
+}
+
+/*
+ * insert an laddr entry with the given ifa for the desired list
+ */
+static int
+sctp_insert_laddr(struct sctpladdr *list, struct sctp_ifa *ifa, uint32_t act)
+{
+ struct sctp_laddr *laddr;
+
+ laddr = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr);
+ if (laddr == NULL) {
+ /* out of memory? */
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+ SCTP_INCR_LADDR_COUNT();
+ memset(laddr, 0, sizeof(*laddr));
+ (void)SCTP_GETTIME_TIMEVAL(&laddr->start_time);
+ laddr->ifa = ifa;
+ laddr->action = act;
+ atomic_add_int(&ifa->refcount, 1);
+ /* insert it */
+ LIST_INSERT_HEAD(list, laddr, sctp_nxt_addr);
+
+ return (0);
+}
+
+/*
+ * Remove an laddr entry from the local address list (on an assoc)
+ */
+static void
+sctp_remove_laddr(struct sctp_laddr *laddr)
+{
+
+ /* remove from the list */
+ LIST_REMOVE(laddr, sctp_nxt_addr);
+ sctp_free_ifa(laddr->ifa);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_laddr), laddr);
+ SCTP_DECR_LADDR_COUNT();
+}
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__))
+
+/*
+ * Don't know why, but without this there is an unknown reference when
+ * compiling NetBSD... hmm
+ */
+extern void in6_sin6_2_sin(struct sockaddr_in *, struct sockaddr_in6 *sin6);
+#endif
+
+/*
+ * Bind the socket, with the PCB and global info locks held. Note, if a
+ * socket address is specified, the PCB lock may be dropped and re-acquired.
+ *
+ * sctp_ifap is used to bypass normal local address validation checks.
+ */
+int
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+sctp_inpcb_bind_locked(struct sctp_inpcb *inp, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, struct thread *td)
+#elif defined(_WIN32) && !defined(__Userspace__)
+sctp_inpcb_bind_locked(struct sctp_inpcb *inp, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, PKTHREAD p)
+#else
+sctp_inpcb_bind_locked(struct sctp_inpcb *inp, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, struct proc *p)
+#endif
+{
+ /* bind a ep to a socket address */
+ struct sctppcbhead *head;
+ struct sctp_inpcb *inp_tmp;
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ struct inpcb *ip_inp;
+#endif
+ int port_reuse_active = 0;
+ int bindall;
+#ifdef SCTP_MVRF
+ int i;
+#endif
+ uint16_t lport;
+ int error;
+ uint32_t vrf_id;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ KASSERT(td != NULL, ("%s: null thread", __func__));
+
+#endif
+ error = 0;
+ lport = 0;
+ bindall = 1;
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ ip_inp = &inp->ip_inp.inp;
+#endif
+
+ SCTP_INP_INFO_WLOCK_ASSERT();
+ SCTP_INP_WLOCK_ASSERT(inp);
+
+#ifdef SCTP_DEBUG
+ if (addr) {
+ SCTPDBG(SCTP_DEBUG_PCB1, "Bind called port: %d\n",
+ ntohs(((struct sockaddr_in *)addr)->sin_port));
+ SCTPDBG(SCTP_DEBUG_PCB1, "Addr: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_PCB1, addr);
+ }
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) {
+ error = EINVAL;
+ /* already did a bind, subsequent binds NOT allowed ! */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ if (addr != NULL) {
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ /* IPV6_V6ONLY socket? */
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(*sin)) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#endif
+
+ sin = (struct sockaddr_in *)addr;
+ lport = sin->sin_port;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /*
+ * For LOOPBACK the prison_local_ip4() call will transmute the ip address
+ * to the proper value.
+ */
+ if ((error = prison_local_ip4(td->td_ucred, &sin->sin_addr)) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#endif
+ if (sin->sin_addr.s_addr != INADDR_ANY) {
+ bindall = 0;
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* Only for pure IPv6 Address. (No IPv4 Mapped!) */
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(*sin6)) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#endif
+ lport = sin6->sin6_port;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /*
+ * For LOOPBACK the prison_local_ip6() call will transmute the ipv6 address
+ * to the proper value.
+ */
+ if ((error = prison_local_ip6(td->td_ucred, &sin6->sin6_addr,
+ (SCTP_IPV6_V6ONLY(inp) != 0))) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#endif
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ bindall = 0;
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ /* KAME hack: embed scopeid */
+#if defined(SCTP_KAME)
+ if (sa6_embedscope(sin6, MODULE_GLOBAL(ip6_use_defzone)) != 0) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#elif defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ if (in6_embedscope(&sin6->sin6_addr, sin6, ip_inp, NULL) != 0) {
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6, ip_inp, NULL, NULL) != 0) {
+#endif
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#elif defined(__FreeBSD__) && !defined(__Userspace__)
+ error = scope6_check_id(sin6, MODULE_GLOBAL(ip6_use_defzone));
+ if (error != 0) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6) != 0) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#endif
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ }
+#ifndef SCOPEDROUTING
+ /* this must be cleared for ifa_ifwithaddr() */
+ sin6->sin6_scope_id = 0;
+#endif /* SCOPEDROUTING */
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn;
+
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_conn)) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#endif
+ sconn = (struct sockaddr_conn *)addr;
+ lport = sconn->sconn_port;
+ if (sconn->sconn_addr != NULL) {
+ bindall = 0;
+ }
+ break;
+ }
+#endif
+ default:
+ error = EAFNOSUPPORT;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ }
+ /* Setup a vrf_id to be the default for the non-bind-all case. */
+ vrf_id = inp->def_vrf_id;
+
+ if (lport) {
+ /*
+ * Did the caller specify a port? if so we must see if an ep
+ * already has this one bound.
+ */
+ /* got to be root to get at low ports */
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ if (ntohs(lport) < IPPORT_RESERVED &&
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ (error = priv_check(td, PRIV_NETINET_RESERVEDPORT)) != 0) {
+#elif defined(__APPLE__) && !defined(__Userspace__)
+ (error = suser(p->p_ucred, &p->p_acflag)) != 0) {
+#elif defined(__Userspace__)
+ /* TODO ensure uid is 0, etc... */
+ 0) {
+#else
+ (error = suser(p, 0)) != 0) {
+#endif
+ goto out;
+ }
+#endif
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ if (bindall) {
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ vrf_id = inp->m_vrf_ids[i];
+#else
+ vrf_id = inp->def_vrf_id;
+#endif
+ inp_tmp = sctp_pcb_findep(addr, 0, 1, vrf_id);
+ if (inp_tmp != NULL) {
+ /*
+ * lock guy returned and lower count
+ * note that we are not bound so
+ * inp_tmp should NEVER be inp. And
+ * it is this inp (inp_tmp) that gets
+ * the reference bump, so we must
+ * lower it.
+ */
+ SCTP_INP_DECR_REF(inp_tmp);
+ /* unlock info */
+ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) &&
+ (sctp_is_feature_on(inp_tmp, SCTP_PCB_FLAGS_PORTREUSE))) {
+ /* Ok, must be one-2-one and allowing port re-use */
+ port_reuse_active = 1;
+ goto continue_anyway;
+ }
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ error = EADDRINUSE;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#ifdef SCTP_MVRF
+ }
+#endif
+ } else {
+ inp_tmp = sctp_pcb_findep(addr, 0, 1, vrf_id);
+ if (inp_tmp != NULL) {
+ /*
+ * lock guy returned and lower count note
+ * that we are not bound so inp_tmp should
+ * NEVER be inp. And it is this inp (inp_tmp)
+ * that gets the reference bump, so we must
+ * lower it.
+ */
+ SCTP_INP_DECR_REF(inp_tmp);
+ /* unlock info */
+ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) &&
+ (sctp_is_feature_on(inp_tmp, SCTP_PCB_FLAGS_PORTREUSE))) {
+ /* Ok, must be one-2-one and allowing port re-use */
+ port_reuse_active = 1;
+ goto continue_anyway;
+ }
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ error = EADDRINUSE;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ }
+ continue_anyway:
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ if (bindall) {
+ /* verify that no lport is not used by a singleton */
+ if ((port_reuse_active == 0) &&
+ (inp_tmp = sctp_isport_inuse(inp, lport, vrf_id))) {
+ /* Sorry someone already has this one bound */
+ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) &&
+ (sctp_is_feature_on(inp_tmp, SCTP_PCB_FLAGS_PORTREUSE))) {
+ port_reuse_active = 1;
+ } else {
+ error = EADDRINUSE;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ }
+ }
+ } else {
+ uint16_t first, last, candidate;
+ uint16_t count;
+
+#if defined(__Userspace__)
+ first = MODULE_GLOBAL(ipport_firstauto);
+ last = MODULE_GLOBAL(ipport_lastauto);
+#elif defined(_WIN32)
+ first = 1;
+ last = 0xffff;
+#elif defined(__FreeBSD__) || defined(__APPLE__)
+ if (ip_inp->inp_flags & INP_HIGHPORT) {
+ first = MODULE_GLOBAL(ipport_hifirstauto);
+ last = MODULE_GLOBAL(ipport_hilastauto);
+ } else if (ip_inp->inp_flags & INP_LOWPORT) {
+#if defined(__FreeBSD__)
+ if ((error = priv_check(td, PRIV_NETINET_RESERVEDPORT)) != 0) {
+#else
+ if ((error = suser(p->p_ucred, &p->p_acflag)) != 0) {
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ first = MODULE_GLOBAL(ipport_lowfirstauto);
+ last = MODULE_GLOBAL(ipport_lowlastauto);
+ } else {
+ first = MODULE_GLOBAL(ipport_firstauto);
+ last = MODULE_GLOBAL(ipport_lastauto);
+ }
+#endif
+ if (first > last) {
+ uint16_t temp;
+
+ temp = first;
+ first = last;
+ last = temp;
+ }
+ count = last - first + 1; /* number of candidates */
+ candidate = first + sctp_select_initial_TSN(&inp->sctp_ep) % (count);
+
+ for (;;) {
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (sctp_isport_inuse(inp, htons(candidate), inp->m_vrf_ids[i]) != NULL) {
+ break;
+ }
+ }
+ if (i == inp->num_vrfs) {
+ lport = htons(candidate);
+ break;
+ }
+#else
+ if (sctp_isport_inuse(inp, htons(candidate), inp->def_vrf_id) == NULL) {
+ lport = htons(candidate);
+ break;
+ }
+#endif
+ if (--count == 0) {
+ error = EADDRINUSE;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ if (candidate == last)
+ candidate = first;
+ else
+ candidate = candidate + 1;
+ }
+ }
+ if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE |
+ SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ /*
+ * this really should not happen. The guy did a non-blocking
+ * bind and then did a close at the same time.
+ */
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ /* ok we look clear to give out this port, so lets setup the binding */
+ if (bindall) {
+ /* binding to all addresses, so just set in the proper flags */
+ inp->sctp_flags |= SCTP_PCB_FLAGS_BOUNDALL;
+ /* set the automatic addr changes from kernel flag */
+ if (SCTP_BASE_SYSCTL(sctp_auto_asconf) == 0) {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_DO_ASCONF);
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTO_ASCONF);
+ } else {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF);
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_AUTO_ASCONF);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_multiple_asconfs) == 0) {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_MULTIPLE_ASCONFS);
+ } else {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_MULTIPLE_ASCONFS);
+ }
+ /* set the automatic mobility_base from kernel
+ flag (by micchie)
+ */
+ if (SCTP_BASE_SYSCTL(sctp_mobility_base) == 0) {
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_BASE);
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
+ } else {
+ sctp_mobility_feature_on(inp, SCTP_MOBILITY_BASE);
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
+ }
+ /* set the automatic mobility_fasthandoff from kernel
+ flag (by micchie)
+ */
+ if (SCTP_BASE_SYSCTL(sctp_mobility_fasthandoff) == 0) {
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_FASTHANDOFF);
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
+ } else {
+ sctp_mobility_feature_on(inp, SCTP_MOBILITY_FASTHANDOFF);
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
+ }
+ } else {
+ /*
+ * bind specific, make sure flags is off and add a new
+ * address structure to the sctp_addr_list inside the ep
+ * structure.
+ *
+ * We will need to allocate one and insert it at the head. The
+ * socketopt call can just insert new addresses in there as
+ * well. It will also have to do the embed scope kame hack
+ * too (before adding).
+ */
+ struct sctp_ifa *ifa;
+ union sctp_sockstore store;
+
+ memset(&store, 0, sizeof(store));
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ memcpy(&store.sin, addr, sizeof(struct sockaddr_in));
+ store.sin.sin_port = 0;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memcpy(&store.sin6, addr, sizeof(struct sockaddr_in6));
+ store.sin6.sin6_port = 0;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(&store.sconn, addr, sizeof(struct sockaddr_conn));
+ store.sconn.sconn_port = 0;
+ break;
+#endif
+ default:
+ break;
+ }
+ /*
+ * first find the interface with the bound address need to
+ * zero out the port to find the address! yuck! can't do
+ * this earlier since need port for sctp_pcb_findep()
+ */
+ if (sctp_ifap != NULL) {
+ ifa = sctp_ifap;
+ } else {
+ /* Note for BSD we hit here always other
+ * O/S's will pass things in via the
+ * sctp_ifap argument.
+ */
+ ifa = sctp_find_ifa_by_addr(&store.sa,
+ vrf_id, SCTP_ADDR_NOT_LOCKED);
+ }
+ if (ifa == NULL) {
+ error = EADDRNOTAVAIL;
+ /* Can't find an interface with that address */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ /* GAK, more FIXME IFA lock? */
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ /* Can't bind a non-existent addr. */
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ goto out;
+ }
+ }
+#endif
+ /* we're not bound all */
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUNDALL;
+ /* allow bindx() to send ASCONF's for binding changes */
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF);
+ /* clear automatic addr changes from kernel flag */
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTO_ASCONF);
+
+ /* add this address to the endpoint list */
+ error = sctp_insert_laddr(&inp->sctp_addr_list, ifa, 0);
+ if (error != 0)
+ goto out;
+ inp->laddr_count++;
+ }
+ /* find the bucket */
+ if (port_reuse_active) {
+ /* Put it into tcp 1-2-1 hash */
+ head = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR(lport, SCTP_BASE_INFO(hashtcpmark))];
+ inp->sctp_flags |= SCTP_PCB_FLAGS_IN_TCPPOOL;
+ } else {
+ head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(lport, SCTP_BASE_INFO(hashmark))];
+ }
+ /* put it in the bucket */
+ LIST_INSERT_HEAD(head, inp, sctp_hash);
+ SCTPDBG(SCTP_DEBUG_PCB1, "Main hash to bind at head:%p, bound port:%d - in tcp_pool=%d\n",
+ (void *)head, ntohs(lport), port_reuse_active);
+ /* set in the port */
+ inp->sctp_lport = lport;
+
+ /* turn off just the unbound flag */
+ KASSERT((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) != 0,
+ ("%s: inp %p is already bound", __func__, inp));
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND;
+out:
+ return (error);
+}
+
+int
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+sctp_inpcb_bind(struct socket *so, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, struct thread *td)
+#elif defined(_WIN32) && !defined(__Userspace__)
+sctp_inpcb_bind(struct socket *so, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, PKTHREAD p)
+#else
+sctp_inpcb_bind(struct socket *so, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, struct proc *p)
+#endif
+{
+ struct sctp_inpcb *inp;
+ int error;
+
+ inp = so->so_pcb;
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ error = sctp_inpcb_bind_locked(inp, addr, sctp_ifap, td);
+#else
+ error = sctp_inpcb_bind_locked(inp, addr, sctp_ifap, p);
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (error);
+}
+
+static void
+sctp_iterator_inp_being_freed(struct sctp_inpcb *inp)
+{
+ struct sctp_iterator *it, *nit;
+
+ /*
+ * We enter with the only the ITERATOR_LOCK in place and a write
+ * lock on the inp_info stuff.
+ */
+ it = sctp_it_ctl.cur_it;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (it && (it->vn != curvnet)) {
+ /* Its not looking at our VNET */
+ return;
+ }
+#endif
+ if (it && (it->inp == inp)) {
+ /*
+ * This is tricky and we hold the iterator lock,
+ * but when it returns and gets the lock (when we
+ * release it) the iterator will try to operate on
+ * inp. We need to stop that from happening. But
+ * of course the iterator has a reference on the
+ * stcb and inp. We can mark it and it will stop.
+ *
+ * If its a single iterator situation, we
+ * set the end iterator flag. Otherwise
+ * we set the iterator to go to the next inp.
+ *
+ */
+ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) {
+ sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_STOP_CUR_IT;
+ } else {
+ sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_STOP_CUR_INP;
+ }
+ }
+ /* Now go through and remove any single reference to
+ * our inp that may be still pending on the list
+ */
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+ TAILQ_FOREACH_SAFE(it, &sctp_it_ctl.iteratorhead, sctp_nxt_itr, nit) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (it->vn != curvnet) {
+ continue;
+ }
+#endif
+ if (it->inp == inp) {
+ /* This one points to me is it inp specific? */
+ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) {
+ /* Remove and free this one */
+ TAILQ_REMOVE(&sctp_it_ctl.iteratorhead,
+ it, sctp_nxt_itr);
+ if (it->function_atend != NULL) {
+ (*it->function_atend) (it->pointer, it->val);
+ }
+ SCTP_FREE(it, SCTP_M_ITER);
+ } else {
+ it->inp = LIST_NEXT(it->inp, sctp_list);
+ if (it->inp) {
+ SCTP_INP_INCR_REF(it->inp);
+ }
+ }
+ /* When its put in the refcnt is incremented so decr it */
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+}
+
+/* release sctp_inpcb unbind the port */
+void
+sctp_inpcb_free(struct sctp_inpcb *inp, int immediate, int from)
+{
+ /*
+ * Here we free a endpoint. We must find it (if it is in the Hash
+ * table) and remove it from there. Then we must also find it in the
+ * overall list and remove it from there. After all removals are
+ * complete then any timer has to be stopped. Then start the actual
+ * freeing. a) Any local lists. b) Any associations. c) The hash of
+ * all associations. d) finally the ep itself.
+ */
+ struct sctp_tcb *stcb, *nstcb;
+ struct sctp_laddr *laddr, *nladdr;
+ struct inpcb *ip_pcb;
+ struct socket *so;
+ int being_refed = 0;
+ struct sctp_queued_to_read *sq, *nsq;
+#if !defined(__Userspace__)
+#if !defined(__FreeBSD__)
+ sctp_rtentry_t *rt;
+#endif
+#endif
+ int cnt;
+ sctp_sharedkey_t *shared_key, *nshared_key;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sctp_lock_assert(SCTP_INP_SO(inp));
+#endif
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 0);
+#endif
+ SCTP_ITERATOR_LOCK();
+ /* mark any iterators on the list or being processed */
+ sctp_iterator_inp_being_freed(inp);
+ SCTP_ITERATOR_UNLOCK();
+
+ SCTP_ASOC_CREATE_LOCK(inp);
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+ so = inp->sctp_socket;
+ KASSERT((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) != 0,
+ ("%s: inp %p still has socket", __func__, inp));
+ KASSERT((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) == 0,
+ ("%s: double free of inp %p", __func__, inp));
+ if (from == SCTP_CALLED_AFTER_CMPSET_OFCLOSE) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_CLOSE_IP;
+ /* socket is gone, so no more wakeups allowed */
+ inp->sctp_flags |= SCTP_PCB_FLAGS_DONT_WAKE;
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEINPUT;
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEOUTPUT;
+ }
+ /* First time through we have the socket lock, after that no more. */
+ sctp_timer_stop(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_1);
+
+ if (inp->control) {
+ sctp_m_freem(inp->control);
+ inp->control = NULL;
+ }
+ if (inp->pkt) {
+ sctp_m_freem(inp->pkt);
+ inp->pkt = NULL;
+ }
+ ip_pcb = &inp->ip_inp.inp; /* we could just cast the main pointer
+ * here but I will be nice :> (i.e.
+ * ip_pcb = ep;) */
+ if (immediate == SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE) {
+ int cnt_in_sd;
+
+ cnt_in_sd = 0;
+ LIST_FOREACH_SAFE(stcb, &inp->sctp_asoc_list, sctp_tcblist, nstcb) {
+ SCTP_TCB_LOCK(stcb);
+ /* Disconnect the socket please. */
+ stcb->sctp_socket = NULL;
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_CLOSED_SOCKET);
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ /* Skip guys being freed */
+ cnt_in_sd++;
+ if (stcb->asoc.state & SCTP_STATE_IN_ACCEPT_QUEUE) {
+ /*
+ * Special case - we did not start a kill
+ * timer on the asoc due to it was not
+ * closed. So go ahead and start it now.
+ */
+ SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
+ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ if (((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) &&
+ (stcb->asoc.total_output_queue_size == 0)) {
+ /* If we have data in queue, we don't want to just
+ * free since the app may have done, send()/close
+ * or connect/send/close. And it wants the data
+ * to get across first.
+ */
+ /* Just abandon things in the front states */
+ if (sctp_free_assoc(inp, stcb, SCTP_PCBFREE_NOFORCE,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_2) == 0) {
+ cnt_in_sd++;
+ }
+ continue;
+ }
+ if ((stcb->asoc.size_on_reasm_queue > 0) ||
+ (stcb->asoc.size_on_all_streams > 0) ||
+ ((so != NULL) && (SCTP_SBAVAIL(&so->so_rcv) > 0))) {
+ /* Left with Data unread */
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_3;
+ sctp_send_abort_tcb(stcb, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ if (sctp_free_assoc(inp, stcb,
+ SCTP_PCBFREE_NOFORCE, SCTP_FROM_SCTP_PCB + SCTP_LOC_4) == 0) {
+ cnt_in_sd++;
+ }
+ continue;
+ } else if (TAILQ_EMPTY(&stcb->asoc.send_queue) &&
+ TAILQ_EMPTY(&stcb->asoc.sent_queue) &&
+ (stcb->asoc.stream_queue_cnt == 0)) {
+ if ((*stcb->asoc.ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, &stcb->asoc)) {
+ goto abort_anyway;
+ }
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ struct sctp_nets *netp;
+
+ /*
+ * there is nothing queued to send,
+ * so I send shutdown
+ */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (stcb->asoc.alternate) {
+ netp = stcb->asoc.alternate;
+ } else {
+ netp = stcb->asoc.primary_destination;
+ }
+ sctp_send_shutdown(stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb,
+ netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, NULL);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_TMR, SCTP_SO_LOCKED);
+ }
+ } else {
+ /* mark into shutdown pending */
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING);
+ if ((*stcb->asoc.ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, &stcb->asoc)) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ if (TAILQ_EMPTY(&stcb->asoc.send_queue) &&
+ TAILQ_EMPTY(&stcb->asoc.sent_queue) &&
+ (stcb->asoc.state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+ abort_anyway:
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_5;
+ sctp_send_abort_tcb(stcb, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ if (sctp_free_assoc(inp, stcb,
+ SCTP_PCBFREE_NOFORCE,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_6) == 0) {
+ cnt_in_sd++;
+ }
+ continue;
+ } else {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
+ }
+ }
+ cnt_in_sd++;
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ /* now is there some left in our SHUTDOWN state? */
+ if (cnt_in_sd) {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 2);
+#endif
+ inp->sctp_socket = NULL;
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return;
+ }
+ }
+ inp->sctp_socket = NULL;
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) {
+ /*
+ * ok, this guy has been bound. It's port is
+ * somewhere in the SCTP_BASE_INFO(hash table). Remove
+ * it!
+ */
+ LIST_REMOVE(inp, sctp_hash);
+ inp->sctp_flags |= SCTP_PCB_FLAGS_UNBOUND;
+ }
+
+ /* If there is a timer running to kill us,
+ * forget it, since it may have a contest
+ * on the INP lock.. which would cause us
+ * to die ...
+ */
+ cnt = 0;
+ LIST_FOREACH_SAFE(stcb, &inp->sctp_asoc_list, sctp_tcblist, nstcb) {
+ SCTP_TCB_LOCK(stcb);
+ if (immediate != SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE) {
+ /* Disconnect the socket please */
+ stcb->sctp_socket = NULL;
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_CLOSED_SOCKET);
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ if (stcb->asoc.state & SCTP_STATE_IN_ACCEPT_QUEUE) {
+ SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
+ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
+ }
+ cnt++;
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ /* Free associations that are NOT killing us */
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT) &&
+ ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0)) {
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_7;
+ sctp_send_abort_tcb(stcb, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ } else if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ cnt++;
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ if (sctp_free_assoc(inp, stcb, SCTP_PCBFREE_FORCE,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_8) == 0) {
+ cnt++;
+ }
+ }
+ if (cnt) {
+ /* Ok we have someone out there that will kill us */
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 3);
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return;
+ }
+ if (SCTP_INP_LOCK_CONTENDED(inp))
+ being_refed++;
+ if (SCTP_INP_READ_CONTENDED(inp))
+ being_refed++;
+ if (SCTP_ASOC_CREATE_LOCK_CONTENDED(inp))
+ being_refed++;
+ /* NOTE: 0 refcount also means no timers are referencing us. */
+ if ((inp->refcount) ||
+ (being_refed) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_CLOSE_IP)) {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 4);
+#endif
+ sctp_timer_start(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return;
+ }
+ inp->sctp_ep.signature_change.type = 0;
+ inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_ALLGONE;
+ /* Remove it from the list .. last thing we need a
+ * lock for.
+ */
+ LIST_REMOVE(inp, sctp_list);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 5);
+#endif
+#if !(defined(_WIN32) || defined(__Userspace__))
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ rt = ip_pcb->inp_route.ro_rt;
+#endif
+#endif
+ if ((inp->sctp_asocidhash) != NULL) {
+ SCTP_HASH_FREE(inp->sctp_asocidhash, inp->hashasocidmark);
+ inp->sctp_asocidhash = NULL;
+ }
+ /*sa_ignore FREED_MEMORY*/
+ TAILQ_FOREACH_SAFE(sq, &inp->read_queue, next, nsq) {
+ /* Its only abandoned if it had data left */
+ if (sq->length)
+ SCTP_STAT_INCR(sctps_left_abandon);
+
+ TAILQ_REMOVE(&inp->read_queue, sq, next);
+ sctp_free_remote_addr(sq->whoFrom);
+ if (so)
+ SCTP_SB_DECR(&so->so_rcv, sq->length);
+ if (sq->data) {
+ sctp_m_freem(sq->data);
+ sq->data = NULL;
+ }
+ /*
+ * no need to free the net count, since at this point all
+ * assoc's are gone.
+ */
+ sctp_free_a_readq(NULL, sq);
+ }
+ /* Now the sctp_pcb things */
+ /*
+ * free each asoc if it is not already closed/free. we can't use the
+ * macro here since le_next will get freed as part of the
+ * sctp_free_assoc() call.
+ */
+ if (ip_pcb->inp_options) {
+ (void)sctp_m_free(ip_pcb->inp_options);
+ ip_pcb->inp_options = 0;
+ }
+#if !(defined(_WIN32) || defined(__Userspace__))
+#if !defined(__FreeBSD__)
+ if (rt) {
+ RTFREE(rt);
+ ip_pcb->inp_route.ro_rt = 0;
+ }
+#endif
+#endif
+#ifdef INET6
+#if !(defined(_WIN32) || defined(__Userspace__))
+#if (defined(__FreeBSD__) || defined(__APPLE__) && !defined(__Userspace__))
+ if (ip_pcb->inp_vflag & INP_IPV6) {
+#else
+ if (inp->inp_vflag & INP_IPV6) {
+#endif
+ ip6_freepcbopts(ip_pcb->in6p_outputopts);
+ }
+#endif
+#endif /* INET6 */
+ ip_pcb->inp_vflag = 0;
+ /* free up authentication fields */
+ if (inp->sctp_ep.local_auth_chunks != NULL)
+ sctp_free_chunklist(inp->sctp_ep.local_auth_chunks);
+ if (inp->sctp_ep.local_hmacs != NULL)
+ sctp_free_hmaclist(inp->sctp_ep.local_hmacs);
+
+ LIST_FOREACH_SAFE(shared_key, &inp->sctp_ep.shared_keys, next, nshared_key) {
+ LIST_REMOVE(shared_key, next);
+ sctp_free_sharedkey(shared_key);
+ /*sa_ignore FREED_MEMORY*/
+ }
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ inp->ip_inp.inp.inp_state = INPCB_STATE_DEAD;
+ if (in_pcb_checkstate(&inp->ip_inp.inp, WNT_STOPUSING, 1) != WNT_STOPUSING) {
+#ifdef INVARIANTS
+ panic("sctp_inpcb_free inp = %p couldn't set to STOPUSING", (void *)inp);
+#else
+ SCTP_PRINTF("sctp_inpcb_free inp = %p couldn't set to STOPUSING\n", (void *)inp);
+#endif
+ }
+ inp->ip_inp.inp.inp_socket->so_flags |= SOF_PCBCLEARING;
+#endif
+ /*
+ * if we have an address list the following will free the list of
+ * ifaddr's that are set into this ep. Again macro limitations here,
+ * since the LIST_FOREACH could be a bad idea.
+ */
+ LIST_FOREACH_SAFE(laddr, &inp->sctp_addr_list, sctp_nxt_addr, nladdr) {
+ sctp_remove_laddr(laddr);
+ }
+
+#ifdef SCTP_TRACK_FREED_ASOCS
+ /* TEMP CODE */
+ LIST_FOREACH_SAFE(stcb, &inp->sctp_asoc_free_list, sctp_tcblist, nstcb) {
+ LIST_REMOVE(stcb, sctp_tcblist);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
+ SCTP_DECR_ASOC_COUNT();
+ }
+ /* *** END TEMP CODE ****/
+#endif
+#ifdef SCTP_MVRF
+ SCTP_FREE(inp->m_vrf_ids, SCTP_M_MVRF);
+#endif
+ /* Now lets see about freeing the EP hash table. */
+ if (inp->sctp_tcbhash != NULL) {
+ SCTP_HASH_FREE(inp->sctp_tcbhash, inp->sctp_hashmark);
+ inp->sctp_tcbhash = NULL;
+ }
+ /* Now we must put the ep memory back into the zone pool */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ crfree(inp->ip_inp.inp.inp_cred);
+ INP_LOCK_DESTROY(&inp->ip_inp.inp);
+#endif
+ SCTP_INP_LOCK_DESTROY(inp);
+ SCTP_INP_READ_LOCK_DESTROY(inp);
+ SCTP_ASOC_CREATE_LOCK_DESTROY(inp);
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ SCTP_DECR_EP_COUNT();
+#else
+ /* For Tiger, we will do this later... */
+#endif
+}
+
+struct sctp_nets *
+sctp_findnet(struct sctp_tcb *stcb, struct sockaddr *addr)
+{
+ struct sctp_nets *net;
+ /* locate the address */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (sctp_cmpaddr(addr, (struct sockaddr *)&net->ro._l_addr))
+ return (net);
+ }
+ return (NULL);
+}
+
+int
+sctp_is_address_on_local_host(struct sockaddr *addr, uint32_t vrf_id)
+{
+ struct sctp_ifa *sctp_ifa;
+ sctp_ifa = sctp_find_ifa_by_addr(addr, vrf_id, SCTP_ADDR_NOT_LOCKED);
+ if (sctp_ifa) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+/*
+ * add's a remote endpoint address, done with the INIT/INIT-ACK as well as
+ * when a ASCONF arrives that adds it. It will also initialize all the cwnd
+ * stats of stuff.
+ */
+int
+sctp_add_remote_addr(struct sctp_tcb *stcb, struct sockaddr *newaddr,
+ struct sctp_nets **netp, uint16_t port, int set_scope, int from)
+{
+ /*
+ * The following is redundant to the same lines in the
+ * sctp_aloc_assoc() but is needed since others call the add
+ * address function
+ */
+ struct sctp_nets *net, *netfirst;
+ int addr_inscope;
+
+ SCTPDBG(SCTP_DEBUG_PCB1, "Adding an address (from:%d) to the peer: ",
+ from);
+ SCTPDBG_ADDR(SCTP_DEBUG_PCB1, newaddr);
+
+ netfirst = sctp_findnet(stcb, newaddr);
+ if (netfirst) {
+ /*
+ * Lie and return ok, we don't want to make the association
+ * go away for this behavior. It will happen in the TCP
+ * model in a connected socket. It does not reach the hash
+ * table until after the association is built so it can't be
+ * found. Mark as reachable, since the initial creation will
+ * have been cleared and the NOT_IN_ASSOC flag will have
+ * been added... and we don't want to end up removing it
+ * back out.
+ */
+ if (netfirst->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ netfirst->dest_state = (SCTP_ADDR_REACHABLE |
+ SCTP_ADDR_UNCONFIRMED);
+ } else {
+ netfirst->dest_state = SCTP_ADDR_REACHABLE;
+ }
+
+ return (0);
+ }
+ addr_inscope = 1;
+ switch (newaddr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)newaddr;
+ if (sin->sin_addr.s_addr == 0) {
+ /* Invalid address */
+ return (-1);
+ }
+ /* zero out the zero area */
+ memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
+
+ /* assure len is set */
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ if (set_scope) {
+ if (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
+ stcb->asoc.scope.ipv4_local_scope = 1;
+ }
+ } else {
+ /* Validate the address is in scope */
+ if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) &&
+ (stcb->asoc.scope.ipv4_local_scope == 0)) {
+ addr_inscope = 0;
+ }
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)newaddr;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* Invalid address */
+ return (-1);
+ }
+ /* assure len is set */
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ if (set_scope) {
+ if (sctp_is_address_on_local_host(newaddr, stcb->asoc.vrf_id)) {
+ stcb->asoc.scope.loopback_scope = 1;
+ stcb->asoc.scope.local_scope = 0;
+ stcb->asoc.scope.ipv4_local_scope = 1;
+ stcb->asoc.scope.site_scope = 1;
+ } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ /*
+ * If the new destination is a LINK_LOCAL we
+ * must have common site scope. Don't set
+ * the local scope since we may not share
+ * all links, only loopback can do this.
+ * Links on the local network would also be
+ * on our private network for v4 too.
+ */
+ stcb->asoc.scope.ipv4_local_scope = 1;
+ stcb->asoc.scope.site_scope = 1;
+ } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) {
+ /*
+ * If the new destination is SITE_LOCAL then
+ * we must have site scope in common.
+ */
+ stcb->asoc.scope.site_scope = 1;
+ }
+ } else {
+ /* Validate the address is in scope */
+ if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) &&
+ (stcb->asoc.scope.loopback_scope == 0)) {
+ addr_inscope = 0;
+ } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) &&
+ (stcb->asoc.scope.local_scope == 0)) {
+ addr_inscope = 0;
+ } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) &&
+ (stcb->asoc.scope.site_scope == 0)) {
+ addr_inscope = 0;
+ }
+ }
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)newaddr;
+ if (sconn->sconn_addr == NULL) {
+ /* Invalid address */
+ return (-1);
+ }
+#ifdef HAVE_SCONN_LEN
+ sconn->sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ break;
+ }
+#endif
+ default:
+ /* not supported family type */
+ return (-1);
+ }
+ net = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_net), struct sctp_nets);
+ if (net == NULL) {
+ return (-1);
+ }
+ SCTP_INCR_RADDR_COUNT();
+ memset(net, 0, sizeof(struct sctp_nets));
+ (void)SCTP_GETTIME_TIMEVAL(&net->start_time);
+#ifdef HAVE_SA_LEN
+ memcpy(&net->ro._l_addr, newaddr, newaddr->sa_len);
+#endif
+ switch (newaddr->sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifndef HAVE_SA_LEN
+ memcpy(&net->ro._l_addr, newaddr, sizeof(struct sockaddr_in));
+#endif
+ ((struct sockaddr_in *)&net->ro._l_addr)->sin_port = stcb->rport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#ifndef HAVE_SA_LEN
+ memcpy(&net->ro._l_addr, newaddr, sizeof(struct sockaddr_in6));
+#endif
+ ((struct sockaddr_in6 *)&net->ro._l_addr)->sin6_port = stcb->rport;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+#ifndef HAVE_SA_LEN
+ memcpy(&net->ro._l_addr, newaddr, sizeof(struct sockaddr_conn));
+#endif
+ ((struct sockaddr_conn *)&net->ro._l_addr)->sconn_port = stcb->rport;
+ break;
+#endif
+ default:
+ break;
+ }
+ net->addr_is_local = sctp_is_address_on_local_host(newaddr, stcb->asoc.vrf_id);
+ if (net->addr_is_local && ((set_scope || (from == SCTP_ADDR_IS_CONFIRMED)))) {
+ stcb->asoc.scope.loopback_scope = 1;
+ stcb->asoc.scope.ipv4_local_scope = 1;
+ stcb->asoc.scope.local_scope = 0;
+ stcb->asoc.scope.site_scope = 1;
+ addr_inscope = 1;
+ }
+ net->failure_threshold = stcb->asoc.def_net_failure;
+ net->pf_threshold = stcb->asoc.def_net_pf_threshold;
+ if (addr_inscope == 0) {
+ net->dest_state = (SCTP_ADDR_REACHABLE |
+ SCTP_ADDR_OUT_OF_SCOPE);
+ } else {
+ if (from == SCTP_ADDR_IS_CONFIRMED)
+ /* SCTP_ADDR_IS_CONFIRMED is passed by connect_x */
+ net->dest_state = SCTP_ADDR_REACHABLE;
+ else
+ net->dest_state = SCTP_ADDR_REACHABLE |
+ SCTP_ADDR_UNCONFIRMED;
+ }
+ /* We set this to 0, the timer code knows that
+ * this means its an initial value
+ */
+ net->rto_needed = 1;
+ net->RTO = 0;
+ net->RTO_measured = 0;
+ stcb->asoc.numnets++;
+ net->ref_count = 1;
+ net->cwr_window_tsn = net->last_cwr_tsn = stcb->asoc.sending_seq - 1;
+ net->port = port;
+ net->dscp = stcb->asoc.default_dscp;
+#ifdef INET6
+ net->flowlabel = stcb->asoc.default_flowlabel;
+#endif
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_DONOT_HEARTBEAT)) {
+ net->dest_state |= SCTP_ADDR_NOHB;
+ } else {
+ net->dest_state &= ~SCTP_ADDR_NOHB;
+ }
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_DO_NOT_PMTUD)) {
+ net->dest_state |= SCTP_ADDR_NO_PMTUD;
+ } else {
+ net->dest_state &= ~SCTP_ADDR_NO_PMTUD;
+ }
+ net->heart_beat_delay = stcb->asoc.heart_beat_delay;
+ /* Init the timer structure */
+ SCTP_OS_TIMER_INIT(&net->rxt_timer.timer);
+ SCTP_OS_TIMER_INIT(&net->pmtu_timer.timer);
+ SCTP_OS_TIMER_INIT(&net->hb_timer.timer);
+
+ /* Now generate a route for this guy */
+#ifdef INET6
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ /* KAME hack: embed scopeid */
+ if (newaddr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ (void)in6_embedscope(&sin6->sin6_addr, sin6, &stcb->sctp_ep->ip_inp.inp, NULL);
+#else
+ (void)in6_embedscope(&sin6->sin6_addr, sin6, &stcb->sctp_ep->ip_inp.inp, NULL, NULL);
+#endif
+#elif defined(SCTP_KAME)
+ (void)sa6_embedscope(sin6, MODULE_GLOBAL(ip6_use_defzone));
+#else
+ (void)in6_embedscope(&sin6->sin6_addr, sin6);
+#endif
+#ifndef SCOPEDROUTING
+ sin6->sin6_scope_id = 0;
+#endif
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#endif
+ SCTP_RTALLOC((sctp_route_t *)&net->ro,
+ stcb->asoc.vrf_id,
+ stcb->sctp_ep->fibnum);
+
+ net->src_addr_selected = 0;
+#if !defined(__Userspace__)
+ if (SCTP_ROUTE_HAS_VALID_IFN(&net->ro)) {
+ /* Get source address */
+ net->ro._s_addr = sctp_source_address_selection(stcb->sctp_ep,
+ stcb,
+ (sctp_route_t *)&net->ro,
+ net,
+ 0,
+ stcb->asoc.vrf_id);
+ if (stcb->asoc.default_mtu > 0) {
+ net->mtu = stcb->asoc.default_mtu;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ net->mtu += SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ net->mtu += SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ net->mtu += sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ break;
+ }
+#if defined(INET) || defined(INET6)
+ if (net->port) {
+ net->mtu += (uint32_t)sizeof(struct udphdr);
+ }
+#endif
+ } else if (net->ro._s_addr != NULL) {
+ uint32_t imtu, rmtu, hcmtu;
+
+ net->src_addr_selected = 1;
+ /* Now get the interface MTU */
+ if (net->ro._s_addr->ifn_p != NULL) {
+ /*
+ * XXX: Should we here just use
+ * net->ro._s_addr->ifn_p->ifn_mtu
+ */
+ imtu = SCTP_GATHER_MTU_FROM_IFN_INFO(net->ro._s_addr->ifn_p->ifn_p,
+ net->ro._s_addr->ifn_p->ifn_index);
+ } else {
+ imtu = 0;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ rmtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, net->ro.ro_nh);
+ hcmtu = sctp_hc_get_mtu(&net->ro._l_addr, stcb->sctp_ep->fibnum);
+#else
+ rmtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, net->ro.ro_rt);
+ hcmtu = 0;
+#endif
+ net->mtu = sctp_min_mtu(hcmtu, rmtu, imtu);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#else
+ if (rmtu == 0) {
+ /* Start things off to match mtu of interface please. */
+ SCTP_SET_MTU_OF_ROUTE(&net->ro._l_addr.sa,
+ net->ro.ro_rt, net->mtu);
+ }
+#endif
+ }
+ }
+#endif
+ if (net->mtu == 0) {
+ if (stcb->asoc.default_mtu > 0) {
+ net->mtu = stcb->asoc.default_mtu;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ net->mtu += SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ net->mtu += SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ net->mtu += sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ break;
+ }
+#if defined(INET) || defined(INET6)
+ if (net->port) {
+ net->mtu += (uint32_t)sizeof(struct udphdr);
+ }
+#endif
+ } else {
+ switch (newaddr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ net->mtu = SCTP_DEFAULT_MTU;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ net->mtu = 1280;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ net->mtu = 1280;
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+#if defined(INET) || defined(INET6)
+ if (net->port) {
+ net->mtu -= (uint32_t)sizeof(struct udphdr);
+ }
+#endif
+ if (from == SCTP_ALLOC_ASOC) {
+ stcb->asoc.smallest_mtu = net->mtu;
+ }
+ if (stcb->asoc.smallest_mtu > net->mtu) {
+ sctp_pathmtu_adjustment(stcb, net->mtu, true);
+ }
+#ifdef INET6
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ if (newaddr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+#ifdef SCTP_KAME
+ (void)sa6_recoverscope(sin6);
+#else
+ (void)in6_recoverscope(sin6, &sin6->sin6_addr, NULL);
+#endif /* SCTP_KAME */
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#endif
+
+ /* JRS - Use the congestion control given in the CC module */
+ if (stcb->asoc.cc_functions.sctp_set_initial_cc_param != NULL)
+ (*stcb->asoc.cc_functions.sctp_set_initial_cc_param)(stcb, net);
+
+ /*
+ * CMT: CUC algo - set find_pseudo_cumack to TRUE (1) at beginning
+ * of assoc (2005/06/27, iyengar@cis.udel.edu)
+ */
+ net->find_pseudo_cumack = 1;
+ net->find_rtx_pseudo_cumack = 1;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /* Choose an initial flowid. */
+ net->flowid = stcb->asoc.my_vtag ^
+ ntohs(stcb->rport) ^
+ ntohs(stcb->sctp_ep->sctp_lport);
+ net->flowtype = M_HASHTYPE_OPAQUE_HASH;
+#endif
+ if (netp) {
+ *netp = net;
+ }
+ netfirst = TAILQ_FIRST(&stcb->asoc.nets);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net->ro.ro_nh == NULL) {
+#else
+ if (net->ro.ro_rt == NULL) {
+#endif
+ /* Since we have no route put it at the back */
+ TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, sctp_next);
+ } else if (netfirst == NULL) {
+ /* We are the first one in the pool. */
+ TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ } else if (netfirst->ro.ro_nh == NULL) {
+#else
+ } else if (netfirst->ro.ro_rt == NULL) {
+#endif
+ /*
+ * First one has NO route. Place this one ahead of the first
+ * one.
+ */
+ TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ } else if (net->ro.ro_nh->nh_ifp != netfirst->ro.ro_nh->nh_ifp) {
+#else
+ } else if (net->ro.ro_rt->rt_ifp != netfirst->ro.ro_rt->rt_ifp) {
+#endif
+ /*
+ * This one has a different interface than the one at the
+ * top of the list. Place it ahead.
+ */
+ TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next);
+ } else {
+ /*
+ * Ok we have the same interface as the first one. Move
+ * forward until we find either a) one with a NULL route...
+ * insert ahead of that b) one with a different ifp.. insert
+ * after that. c) end of the list.. insert at the tail.
+ */
+ struct sctp_nets *netlook;
+
+ do {
+ netlook = TAILQ_NEXT(netfirst, sctp_next);
+ if (netlook == NULL) {
+ /* End of the list */
+ TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, sctp_next);
+ break;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ } else if (netlook->ro.ro_nh == NULL) {
+#else
+ } else if (netlook->ro.ro_rt == NULL) {
+#endif
+ /* next one has NO route */
+ TAILQ_INSERT_BEFORE(netfirst, net, sctp_next);
+ break;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ } else if (netlook->ro.ro_nh->nh_ifp != net->ro.ro_nh->nh_ifp) {
+#else
+ } else if (netlook->ro.ro_rt->rt_ifp != net->ro.ro_rt->rt_ifp) {
+#endif
+ TAILQ_INSERT_AFTER(&stcb->asoc.nets, netlook,
+ net, sctp_next);
+ break;
+ }
+ /* Shift forward */
+ netfirst = netlook;
+ } while (netlook != NULL);
+ }
+
+ /* got to have a primary set */
+ if (stcb->asoc.primary_destination == 0) {
+ stcb->asoc.primary_destination = net;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ } else if ((stcb->asoc.primary_destination->ro.ro_nh == NULL) &&
+ (net->ro.ro_nh) &&
+#else
+ } else if ((stcb->asoc.primary_destination->ro.ro_rt == NULL) &&
+ (net->ro.ro_rt) &&
+#endif
+ ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0)) {
+ /* No route to current primary adopt new primary */
+ stcb->asoc.primary_destination = net;
+ }
+ /* Validate primary is first */
+ net = TAILQ_FIRST(&stcb->asoc.nets);
+ if ((net != stcb->asoc.primary_destination) &&
+ (stcb->asoc.primary_destination)) {
+ /* first one on the list is NOT the primary
+ * sctp_cmpaddr() is much more efficient if
+ * the primary is the first on the list, make it
+ * so.
+ */
+ TAILQ_REMOVE(&stcb->asoc.nets,
+ stcb->asoc.primary_destination, sctp_next);
+ TAILQ_INSERT_HEAD(&stcb->asoc.nets,
+ stcb->asoc.primary_destination, sctp_next);
+ }
+ return (0);
+}
+
+static uint32_t
+sctp_aloc_a_assoc_id(struct sctp_inpcb *inp, struct sctp_tcb *stcb)
+{
+ uint32_t id;
+ struct sctpasochead *head;
+ struct sctp_tcb *lstcb;
+
+ try_again:
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ /* TSNH */
+ return (0);
+ }
+ /*
+ * We don't allow assoc id to be one of SCTP_FUTURE_ASSOC,
+ * SCTP_CURRENT_ASSOC and SCTP_ALL_ASSOC.
+ */
+ if (inp->sctp_associd_counter <= SCTP_ALL_ASSOC) {
+ inp->sctp_associd_counter = SCTP_ALL_ASSOC + 1;
+ }
+ id = inp->sctp_associd_counter;
+ inp->sctp_associd_counter++;
+ lstcb = sctp_findasoc_ep_asocid_locked(inp, (sctp_assoc_t)id, 0);
+ if (lstcb) {
+ goto try_again;
+ }
+ head = &inp->sctp_asocidhash[SCTP_PCBHASH_ASOC(id, inp->hashasocidmark)];
+ LIST_INSERT_HEAD(head, stcb, sctp_tcbasocidhash);
+ stcb->asoc.in_asocid_hash = 1;
+ return (id);
+}
+
+/*
+ * allocate an association and add it to the endpoint. The caller must be
+ * careful to add all additional addresses once they are know right away or
+ * else the assoc will be may experience a blackout scenario.
+ */
+static struct sctp_tcb *
+sctp_aloc_assoc_locked(struct sctp_inpcb *inp, struct sockaddr *firstaddr,
+ int *error, uint32_t override_tag, uint32_t initial_tsn,
+ uint32_t vrf_id, uint16_t o_streams, uint16_t port,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *p,
+#elif defined(_WIN32) && !defined(__Userspace__)
+ PKTHREAD p,
+#else
+#if defined(__Userspace__)
+ /* __Userspace__ NULL proc is going to be passed here. See sctp_lower_sosend */
+#endif
+ struct proc *p,
+#endif
+ int initialize_auth_params)
+{
+ /* note the p argument is only valid in unbound sockets */
+
+ struct sctp_tcb *stcb;
+ struct sctp_association *asoc;
+ struct sctpasochead *head;
+ uint16_t rport;
+ int err;
+
+ SCTP_INP_INFO_WLOCK_ASSERT();
+ SCTP_INP_WLOCK_ASSERT(inp);
+
+ /*
+ * Assumption made here: Caller has done a
+ * sctp_findassociation_ep_addr(ep, addr's); to make sure the
+ * address does not exist already.
+ */
+ if (SCTP_BASE_INFO(ipi_count_asoc) >= SCTP_MAX_NUM_OF_ASOC) {
+ /* Hit max assoc, sorry no more */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOBUFS);
+ *error = ENOBUFS;
+ return (NULL);
+ }
+ if (firstaddr == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) &&
+ ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_PORTREUSE)) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED))) {
+ /*
+ * If its in the TCP pool, its NOT allowed to create an
+ * association. The parent listener needs to call
+ * sctp_aloc_assoc.. or the one-2-many socket. If a peeled
+ * off, or connected one does this.. its an error.
+ */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)) {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_WAS_CONNECTED) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_WAS_ABORTED)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ }
+ SCTPDBG(SCTP_DEBUG_PCB3, "Allocate an association for peer:");
+#ifdef SCTP_DEBUG
+ if (firstaddr) {
+ SCTPDBG_ADDR(SCTP_DEBUG_PCB3, firstaddr);
+ switch (firstaddr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ SCTPDBG(SCTP_DEBUG_PCB3, "Port:%d\n",
+ ntohs(((struct sockaddr_in *)firstaddr)->sin_port));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ SCTPDBG(SCTP_DEBUG_PCB3, "Port:%d\n",
+ ntohs(((struct sockaddr_in6 *)firstaddr)->sin6_port));
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ SCTPDBG(SCTP_DEBUG_PCB3, "Port:%d\n",
+ ntohs(((struct sockaddr_conn *)firstaddr)->sconn_port));
+ break;
+#endif
+ default:
+ break;
+ }
+ } else {
+ SCTPDBG(SCTP_DEBUG_PCB3,"None\n");
+ }
+#endif /* SCTP_DEBUG */
+ switch (firstaddr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)firstaddr;
+ if ((ntohs(sin->sin_port) == 0) ||
+ (sin->sin_addr.s_addr == INADDR_ANY) ||
+ (sin->sin_addr.s_addr == INADDR_BROADCAST) ||
+ IN_MULTICAST(ntohl(sin->sin_addr.s_addr)) ||
+#if defined(__Userspace__)
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ (SCTP_IPV6_V6ONLY(inp) != 0)))) {
+#else
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ (SCTP_IPV6_V6ONLY(inp) != 0))) {
+#endif
+ /* Invalid address */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ rport = sin->sin_port;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)firstaddr;
+ if ((ntohs(sin6->sin6_port) == 0) ||
+ IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) ||
+ IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0)) {
+ /* Invalid address */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ rport = sin6->sin6_port;
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)firstaddr;
+ if ((ntohs(sconn->sconn_port) == 0) ||
+ (sconn->sconn_addr == NULL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) == 0)) {
+ /* Invalid address */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ rport = sconn->sconn_port;
+ break;
+ }
+#endif
+ default:
+ /* not supported family type */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+ /*
+ * If you have not performed a bind, then we need to do the
+ * ephemeral bind for you.
+ */
+ if ((err = sctp_inpcb_bind_locked(inp, NULL, NULL, p))) {
+ /* bind error, probably perm */
+ *error = err;
+ return (NULL);
+ }
+ }
+ stcb = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_asoc), struct sctp_tcb);
+ if (stcb == NULL) {
+ /* out of memory? */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOMEM);
+ *error = ENOMEM;
+ return (NULL);
+ }
+ SCTP_INCR_ASOC_COUNT();
+
+ memset(stcb, 0, sizeof(*stcb));
+ asoc = &stcb->asoc;
+
+ SCTP_TCB_LOCK_INIT(stcb);
+ stcb->rport = rport;
+ /* setup back pointer's */
+ stcb->sctp_ep = inp;
+ stcb->sctp_socket = inp->sctp_socket;
+ if ((err = sctp_init_asoc(inp, stcb, override_tag, initial_tsn, vrf_id, o_streams))) {
+ /* failed */
+ SCTP_TCB_LOCK_DESTROY(stcb);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
+ SCTP_DECR_ASOC_COUNT();
+ *error = err;
+ return (NULL);
+ }
+ SCTP_TCB_LOCK(stcb);
+
+ asoc->assoc_id = sctp_aloc_a_assoc_id(inp, stcb);
+ /* now that my_vtag is set, add it to the hash */
+ head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag, SCTP_BASE_INFO(hashasocmark))];
+ /* put it in the bucket in the vtag hash of assoc's for the system */
+ LIST_INSERT_HEAD(head, stcb, sctp_asocs);
+
+ if (sctp_add_remote_addr(stcb, firstaddr, NULL, port, SCTP_DO_SETSCOPE, SCTP_ALLOC_ASOC)) {
+ /* failure.. memory error? */
+ if (asoc->strmout) {
+ SCTP_FREE(asoc->strmout, SCTP_M_STRMO);
+ asoc->strmout = NULL;
+ }
+ if (asoc->mapping_array) {
+ SCTP_FREE(asoc->mapping_array, SCTP_M_MAP);
+ asoc->mapping_array = NULL;
+ }
+ if (asoc->nr_mapping_array) {
+ SCTP_FREE(asoc->nr_mapping_array, SCTP_M_MAP);
+ asoc->nr_mapping_array = NULL;
+ }
+ SCTP_DECR_ASOC_COUNT();
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_TCB_LOCK_DESTROY(stcb);
+ LIST_REMOVE(stcb, sctp_asocs);
+ LIST_REMOVE(stcb, sctp_tcbasocidhash);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOBUFS);
+ *error = ENOBUFS;
+ return (NULL);
+ }
+ /* Init all the timers */
+ SCTP_OS_TIMER_INIT(&asoc->dack_timer.timer);
+ SCTP_OS_TIMER_INIT(&asoc->strreset_timer.timer);
+ SCTP_OS_TIMER_INIT(&asoc->asconf_timer.timer);
+ SCTP_OS_TIMER_INIT(&asoc->shut_guard_timer.timer);
+ SCTP_OS_TIMER_INIT(&asoc->autoclose_timer.timer);
+ SCTP_OS_TIMER_INIT(&asoc->delete_prim_timer.timer);
+
+ LIST_INSERT_HEAD(&inp->sctp_asoc_list, stcb, sctp_tcblist);
+ /* now file the port under the hash as well */
+ if (inp->sctp_tcbhash != NULL) {
+ head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(stcb->rport,
+ inp->sctp_hashmark)];
+ LIST_INSERT_HEAD(head, stcb, sctp_tcbhash);
+ }
+ if (initialize_auth_params == SCTP_INITIALIZE_AUTH_PARAMS) {
+ sctp_initialize_auth_params(inp, stcb);
+ }
+ SCTPDBG(SCTP_DEBUG_PCB1, "Association %p now allocated\n", (void *)stcb);
+ return (stcb);
+}
+
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *inp, struct sockaddr *firstaddr,
+ int *error, uint32_t override_tag, uint32_t initial_tsn,
+ uint32_t vrf_id, uint16_t o_streams, uint16_t port,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *p,
+#elif defined(_WIN32) && !defined(__Userspace__)
+ PKTHREAD p,
+#else
+ struct proc *p,
+#endif
+ int initialize_auth_params)
+{
+ struct sctp_tcb *stcb;
+
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+ stcb = sctp_aloc_assoc_locked(inp, firstaddr, error, override_tag,
+ initial_tsn, vrf_id, o_streams, port, p, initialize_auth_params);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_INP_WUNLOCK(inp);
+ return (stcb);
+}
+
+struct sctp_tcb *
+sctp_aloc_assoc_connected(struct sctp_inpcb *inp, struct sockaddr *firstaddr,
+ int *error, uint32_t override_tag, uint32_t initial_tsn,
+ uint32_t vrf_id, uint16_t o_streams, uint16_t port,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *p,
+#elif defined(_WIN32) && !defined(__Userspace__)
+ PKTHREAD p,
+#else
+ struct proc *p,
+#endif
+ int initialize_auth_params)
+{
+ struct sctp_tcb *stcb;
+
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ SCTP_IS_LISTENING(inp)) {
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_INP_WUNLOCK(inp);
+ *error = EINVAL;
+ return (NULL);
+ }
+ stcb = sctp_aloc_assoc_locked(inp, firstaddr, error, override_tag,
+ initial_tsn, vrf_id, o_streams, port, p, initialize_auth_params);
+ SCTP_INP_INFO_WUNLOCK();
+ if (stcb != NULL && (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)) {
+ inp->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+ soisconnecting(inp->sctp_socket);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ return (stcb);
+}
+
+void
+sctp_remove_net(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_association *asoc;
+
+ inp = stcb->sctp_ep;
+ asoc = &stcb->asoc;
+ asoc->numnets--;
+ TAILQ_REMOVE(&asoc->nets, net, sctp_next);
+ if (net == asoc->primary_destination) {
+ /* Reset primary */
+ struct sctp_nets *lnet;
+
+ lnet = TAILQ_FIRST(&asoc->nets);
+ /* Mobility adaptation
+ Ideally, if deleted destination is the primary, it becomes
+ a fast retransmission trigger by the subsequent SET PRIMARY.
+ (by micchie)
+ */
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE) ||
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "remove_net: primary dst is deleting\n");
+ if (asoc->deleted_primary != NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "remove_net: deleted primary may be already stored\n");
+ goto out;
+ }
+ asoc->deleted_primary = net;
+ atomic_add_int(&net->ref_count, 1);
+ memset(&net->lastsa, 0, sizeof(net->lastsa));
+ memset(&net->lastsv, 0, sizeof(net->lastsv));
+ sctp_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_PRIM_DELETED);
+ sctp_timer_start(SCTP_TIMER_TYPE_PRIM_DELETED,
+ stcb->sctp_ep, stcb, NULL);
+ }
+out:
+ /* Try to find a confirmed primary */
+ asoc->primary_destination = sctp_find_alternate_net(stcb, lnet, 0);
+ }
+ if (net == asoc->last_data_chunk_from) {
+ /* Reset primary */
+ asoc->last_data_chunk_from = TAILQ_FIRST(&asoc->nets);
+ }
+ if (net == asoc->last_control_chunk_from) {
+ /* Clear net */
+ asoc->last_control_chunk_from = NULL;
+ }
+ if (net == asoc->last_net_cmt_send_started) {
+ /* Clear net */
+ asoc->last_net_cmt_send_started = NULL;
+ }
+ if (net == stcb->asoc.alternate) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_9);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_10);
+ net->dest_state |= SCTP_ADDR_BEING_DELETED;
+ sctp_free_remote_addr(net);
+}
+
+/*
+ * remove a remote endpoint address from an association, it will fail if the
+ * address does not exist.
+ */
+int
+sctp_del_remote_addr(struct sctp_tcb *stcb, struct sockaddr *remaddr)
+{
+ /*
+ * Here we need to remove a remote address. This is quite simple, we
+ * first find it in the list of address for the association
+ * (tasoc->asoc.nets) and then if it is there, we do a LIST_REMOVE
+ * on that item. Note we do not allow it to be removed if there are
+ * no other addresses.
+ */
+ struct sctp_association *asoc;
+ struct sctp_nets *net, *nnet;
+
+ asoc = &stcb->asoc;
+
+ /* locate the address */
+ TAILQ_FOREACH_SAFE(net, &asoc->nets, sctp_next, nnet) {
+ if (net->ro._l_addr.sa.sa_family != remaddr->sa_family) {
+ continue;
+ }
+ if (sctp_cmpaddr((struct sockaddr *)&net->ro._l_addr,
+ remaddr)) {
+ /* we found the guy */
+ if (asoc->numnets < 2) {
+ /* Must have at LEAST two remote addresses */
+ return (-1);
+ } else {
+ sctp_remove_net(stcb, net);
+ return (0);
+ }
+ }
+ }
+ /* not found. */
+ return (-2);
+}
+
+static bool
+sctp_is_in_timewait(uint32_t tag, uint16_t lport, uint16_t rport, uint32_t now)
+{
+ struct sctpvtaghead *chain;
+ struct sctp_tagblock *twait_block;
+ int i;
+
+ SCTP_INP_INFO_LOCK_ASSERT();
+ chain = &SCTP_BASE_INFO(vtag_timewait)[(tag % SCTP_STACK_VTAG_HASH_SIZE)];
+ LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) {
+ for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) {
+ if ((twait_block->vtag_block[i].tv_sec_at_expire >= now) &&
+ (twait_block->vtag_block[i].v_tag == tag) &&
+ (twait_block->vtag_block[i].lport == lport) &&
+ (twait_block->vtag_block[i].rport == rport)) {
+ return (true);
+ }
+ }
+ }
+ return (false);
+}
+
+static void
+sctp_set_vtag_block(struct sctp_timewait *vtag_block, uint32_t time,
+ uint32_t tag, uint16_t lport, uint16_t rport)
+{
+ vtag_block->tv_sec_at_expire = time;
+ vtag_block->v_tag = tag;
+ vtag_block->lport = lport;
+ vtag_block->rport = rport;
+}
+
+static void
+sctp_add_vtag_to_timewait(uint32_t tag, uint16_t lport, uint16_t rport)
+{
+ struct sctpvtaghead *chain;
+ struct sctp_tagblock *twait_block;
+ struct timeval now;
+ uint32_t time;
+ int i;
+ bool set;
+
+ SCTP_INP_INFO_WLOCK_ASSERT();
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ time = (uint32_t)now.tv_sec + SCTP_BASE_SYSCTL(sctp_vtag_time_wait);
+ chain = &SCTP_BASE_INFO(vtag_timewait)[(tag % SCTP_STACK_VTAG_HASH_SIZE)];
+ set = false;
+ LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) {
+ /* Block(s) present, lets find space, and expire on the fly */
+ for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) {
+ if ((twait_block->vtag_block[i].v_tag == 0) && !set) {
+ sctp_set_vtag_block(twait_block->vtag_block + i, time, tag, lport, rport);
+ set = true;
+ continue;
+ }
+ if ((twait_block->vtag_block[i].v_tag != 0) &&
+ (twait_block->vtag_block[i].tv_sec_at_expire < (uint32_t)now.tv_sec)) {
+ if (set) {
+ /* Audit expires this guy */
+ sctp_set_vtag_block(twait_block->vtag_block + i, 0, 0, 0, 0);
+ } else {
+ /* Reuse it for the new tag */
+ sctp_set_vtag_block(twait_block->vtag_block + i, time, tag, lport, rport);
+ set = true;
+ }
+ }
+ }
+ if (set) {
+ /*
+ * We only do up to the block where we can
+ * place our tag for audits
+ */
+ break;
+ }
+ }
+ /* Need to add a new block to chain */
+ if (!set) {
+ SCTP_MALLOC(twait_block, struct sctp_tagblock *,
+ sizeof(struct sctp_tagblock), SCTP_M_TIMW);
+ if (twait_block == NULL) {
+ return;
+ }
+ memset(twait_block, 0, sizeof(struct sctp_tagblock));
+ LIST_INSERT_HEAD(chain, twait_block, sctp_nxt_tagblock);
+ sctp_set_vtag_block(twait_block->vtag_block, time, tag, lport, rport);
+ }
+}
+
+void
+sctp_clean_up_stream(struct sctp_tcb *stcb, struct sctp_readhead *rh)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_queued_to_read *control, *ncontrol;
+
+ TAILQ_FOREACH_SAFE(control, rh, next_instrm, ncontrol) {
+ TAILQ_REMOVE(rh, control, next_instrm);
+ control->on_strm_q = 0;
+ if (control->on_read_q == 0) {
+ sctp_free_remote_addr(control->whoFrom);
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ }
+ /* Reassembly free? */
+ TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
+ TAILQ_REMOVE(&control->reasm, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ sctp_free_remote_addr(chk->whoTo);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ /*sa_ignore FREED_MEMORY*/
+ }
+ /*
+ * We don't free the address here
+ * since all the net's were freed
+ * above.
+ */
+ if (control->on_read_q == 0) {
+ sctp_free_a_readq(stcb, control);
+ }
+ }
+}
+
+/*-
+ * Free the association after un-hashing the remote port. This
+ * function ALWAYS returns holding NO LOCK on the stcb. It DOES
+ * expect that the input to this function IS a locked TCB.
+ * It will return 0, if it did NOT destroy the association (instead
+ * it unlocks it. It will return NON-zero if it either destroyed the
+ * association OR the association is already destroyed.
+ */
+int
+sctp_free_assoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int from_inpcbfree, int from_location)
+{
+ int i;
+ struct sctp_association *asoc;
+ struct sctp_nets *net, *nnet;
+ struct sctp_laddr *laddr, *naddr;
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_asconf_addr *aparam, *naparam;
+ struct sctp_asconf_ack *aack, *naack;
+ struct sctp_stream_reset_list *strrst, *nstrrst;
+ struct sctp_queued_to_read *sq, *nsq;
+ struct sctp_stream_queue_pending *sp, *nsp;
+ sctp_sharedkey_t *shared_key, *nshared_key;
+ struct socket *so;
+
+ /* first, lets purge the entry from the hash table. */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sctp_lock_assert(SCTP_INP_SO(inp));
+#endif
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, stcb, 6);
+#endif
+ if (stcb->asoc.state == 0) {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 7);
+#endif
+ /* there is no asoc, really TSNH :-0 */
+ return (1);
+ }
+ if (stcb->asoc.alternate) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+ /* TEMP CODE */
+ if (stcb->freed_from_where == 0) {
+ /* Only record the first place free happened from */
+ stcb->freed_from_where = from_location;
+ }
+ /* TEMP CODE */
+#endif
+
+ asoc = &stcb->asoc;
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE))
+ /* nothing around */
+ so = NULL;
+ else
+ so = inp->sctp_socket;
+
+ /*
+ * We used timer based freeing if a reader or writer is in the way.
+ * So we first check if we are actually being called from a timer,
+ * if so we abort early if a reader or writer is still in the way.
+ */
+ if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) &&
+ (from_inpcbfree == SCTP_NORMAL_PROC)) {
+ /*
+ * is it the timer driving us? if so are the reader/writers
+ * gone?
+ */
+ if (stcb->asoc.refcnt) {
+ /* nope, reader or writer in the way */
+ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
+ /* no asoc destroyed */
+ SCTP_TCB_UNLOCK(stcb);
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, stcb, 8);
+#endif
+ return (0);
+ }
+ }
+ /* Now clean up any other timers */
+ sctp_stop_association_timers(stcb, false);
+ /* Now the read queue needs to be cleaned up (only once) */
+ if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_ABOUT_TO_BE_FREED);
+ SCTP_INP_READ_LOCK(inp);
+ TAILQ_FOREACH(sq, &inp->read_queue, next) {
+ if (sq->stcb == stcb) {
+ sq->do_not_ref_stcb = 1;
+ sq->sinfo_cumtsn = stcb->asoc.cumulative_tsn;
+ /* If there is no end, there never
+ * will be now.
+ */
+ if (sq->end_added == 0) {
+ /* Held for PD-API, clear that. */
+ sq->pdapi_aborted = 1;
+ sq->held_length = 0;
+ if (sctp_stcb_is_feature_on(inp, stcb, SCTP_PCB_FLAGS_PDAPIEVNT) && (so != NULL)) {
+ sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION,
+ stcb,
+ SCTP_PARTIAL_DELIVERY_ABORTED,
+ (void *)sq,
+ SCTP_SO_LOCKED);
+ }
+ /* Add an end to wake them */
+ sq->end_added = 1;
+ }
+ }
+ }
+ SCTP_INP_READ_UNLOCK(inp);
+ if (stcb->block_entry) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_PCB, ECONNRESET);
+ stcb->block_entry->error = ECONNRESET;
+ stcb->block_entry = NULL;
+ }
+ }
+ if ((stcb->asoc.refcnt) || (stcb->asoc.state & SCTP_STATE_IN_ACCEPT_QUEUE)) {
+ /* Someone holds a reference OR the socket is unaccepted yet.
+ */
+ if ((stcb->asoc.refcnt) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
+ SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
+ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE))
+ /* nothing around */
+ so = NULL;
+ if (so) {
+ /* Wake any reader/writers */
+ sctp_sorwakeup(inp, so);
+ sctp_sowwakeup(inp, so);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, stcb, 9);
+#endif
+ /* no asoc destroyed */
+ return (0);
+ }
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, stcb, 10);
+#endif
+ /* When I reach here, no others want
+ * to kill the assoc yet.. and I own
+ * the lock. Now its possible an abort
+ * comes in when I do the lock exchange
+ * below to grab all the locks to do
+ * the final take out. to prevent this
+ * we increment the count, which will
+ * start a timer and blow out above thus
+ * assuring us that we hold exclusive
+ * killing of the asoc. Note that
+ * after getting back the TCB lock
+ * we will go ahead and increment the
+ * counter back up and stop any timer
+ * a passing stranger may have started :-S
+ */
+ if (from_inpcbfree == SCTP_NORMAL_PROC) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+ SCTP_TCB_LOCK(stcb);
+ }
+ /* Double check the GONE flag */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE))
+ /* nothing around */
+ so = NULL;
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ /*
+ * For TCP type we need special handling when we are
+ * connected. We also include the peel'ed off ones to.
+ */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_CONNECTED;
+ inp->sctp_flags |= SCTP_PCB_FLAGS_WAS_CONNECTED;
+ if (so) {
+ SOCKBUF_LOCK(&so->so_rcv);
+ so->so_state &= ~(SS_ISCONNECTING |
+ SS_ISDISCONNECTING |
+ SS_ISCONFIRMING |
+ SS_ISCONNECTED);
+ so->so_state |= SS_ISDISCONNECTED;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ socantrcvmore(so);
+#else
+ socantrcvmore_locked(so);
+#endif
+ socantsendmore(so);
+ sctp_sowwakeup(inp, so);
+ sctp_sorwakeup(inp, so);
+ SCTP_SOWAKEUP(so);
+ }
+ }
+ }
+
+ /* Make it invalid too, that way if its
+ * about to run it will abort and return.
+ */
+ /* re-increment the lock */
+ if (from_inpcbfree == SCTP_NORMAL_PROC) {
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ if (stcb->asoc.refcnt) {
+ SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
+ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
+ if (from_inpcbfree == SCTP_NORMAL_PROC) {
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_INP_WUNLOCK(inp);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ return (0);
+ }
+ asoc->state = 0;
+ if (inp->sctp_tcbhash) {
+ LIST_REMOVE(stcb, sctp_tcbhash);
+ }
+ if (stcb->asoc.in_asocid_hash) {
+ LIST_REMOVE(stcb, sctp_tcbasocidhash);
+ }
+ if (inp->sctp_socket == NULL) {
+ stcb->sctp_socket = NULL;
+ }
+ /* Now lets remove it from the list of ALL associations in the EP */
+ LIST_REMOVE(stcb, sctp_tcblist);
+ if (from_inpcbfree == SCTP_NORMAL_PROC) {
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ /* pull from vtag hash */
+ LIST_REMOVE(stcb, sctp_asocs);
+ sctp_add_vtag_to_timewait(asoc->my_vtag, inp->sctp_lport, stcb->rport);
+
+ /* Now restop the timers to be sure
+ * this is paranoia at is finest!
+ */
+ sctp_stop_association_timers(stcb, true);
+
+ /*
+ * The chunk lists and such SHOULD be empty but we check them just
+ * in case.
+ */
+ /* anything on the wheel needs to be removed */
+ for (i = 0; i < asoc->streamoutcnt; i++) {
+ struct sctp_stream_out *outs;
+
+ outs = &asoc->strmout[i];
+ /* now clean up any chunks here */
+ TAILQ_FOREACH_SAFE(sp, &outs->outqueue, next, nsp) {
+ atomic_subtract_int(&asoc->stream_queue_cnt, 1);
+ TAILQ_REMOVE(&outs->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, outs, sp);
+ sctp_free_spbufspace(stcb, asoc, sp);
+ if (sp->data) {
+ if (so) {
+ /* Still an open socket - report */
+ sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL, stcb,
+ 0, (void *)sp, SCTP_SO_LOCKED);
+ }
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ sp->tail_mbuf = NULL;
+ sp->length = 0;
+ }
+ }
+ if (sp->net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ sctp_free_a_strmoq(stcb, sp, SCTP_SO_LOCKED);
+ }
+ }
+ /*sa_ignore FREED_MEMORY*/
+ TAILQ_FOREACH_SAFE(strrst, &asoc->resetHead, next_resp, nstrrst) {
+ TAILQ_REMOVE(&asoc->resetHead, strrst, next_resp);
+ SCTP_FREE(strrst, SCTP_M_STRESET);
+ }
+ TAILQ_FOREACH_SAFE(sq, &asoc->pending_reply_queue, next, nsq) {
+ TAILQ_REMOVE(&asoc->pending_reply_queue, sq, next);
+ if (sq->data) {
+ sctp_m_freem(sq->data);
+ sq->data = NULL;
+ }
+ sctp_free_remote_addr(sq->whoFrom);
+ sq->whoFrom = NULL;
+ sq->stcb = NULL;
+ /* Free the ctl entry */
+ sctp_free_a_readq(stcb, sq);
+ /*sa_ignore FREED_MEMORY*/
+ }
+ TAILQ_FOREACH_SAFE(chk, &asoc->free_chunks, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->free_chunks, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_free_chunks), 1);
+ asoc->free_chunk_cnt--;
+ /*sa_ignore FREED_MEMORY*/
+ }
+ /* pending send queue SHOULD be empty */
+ TAILQ_FOREACH_SAFE(chk, &asoc->send_queue, sctp_next, nchk) {
+ if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.sid);
+#endif
+ }
+ TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next);
+ if (chk->data) {
+ if (so) {
+ /* Still a socket? */
+ sctp_ulp_notify(SCTP_NOTIFY_UNSENT_DG_FAIL, stcb,
+ 0, chk, SCTP_SO_LOCKED);
+ }
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ if (chk->whoTo) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->whoTo = NULL;
+ }
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ /*sa_ignore FREED_MEMORY*/
+ }
+ /* sent queue SHOULD be empty */
+ TAILQ_FOREACH_SAFE(chk, &asoc->sent_queue, sctp_next, nchk) {
+ if (chk->sent != SCTP_DATAGRAM_NR_ACKED) {
+ if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.sid);
+#endif
+ }
+ }
+ TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next);
+ if (chk->data) {
+ if (so) {
+ /* Still a socket? */
+ sctp_ulp_notify(SCTP_NOTIFY_SENT_DG_FAIL, stcb,
+ 0, chk, SCTP_SO_LOCKED);
+ }
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ sctp_free_remote_addr(chk->whoTo);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ /*sa_ignore FREED_MEMORY*/
+ }
+#ifdef INVARIANTS
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ if (stcb->asoc.strmout[i].chunks_on_queues > 0) {
+ panic("%u chunks left for stream %u.", stcb->asoc.strmout[i].chunks_on_queues, i);
+ }
+ }
+#endif
+ /* control queue MAY not be empty */
+ TAILQ_FOREACH_SAFE(chk, &asoc->control_send_queue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ sctp_free_remote_addr(chk->whoTo);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ /*sa_ignore FREED_MEMORY*/
+ }
+ /* ASCONF queue MAY not be empty */
+ TAILQ_FOREACH_SAFE(chk, &asoc->asconf_send_queue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->asconf_send_queue, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ if (chk->holds_key_ref)
+ sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
+ sctp_free_remote_addr(chk->whoTo);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
+ SCTP_DECR_CHK_COUNT();
+ /*sa_ignore FREED_MEMORY*/
+ }
+ if (asoc->mapping_array) {
+ SCTP_FREE(asoc->mapping_array, SCTP_M_MAP);
+ asoc->mapping_array = NULL;
+ }
+ if (asoc->nr_mapping_array) {
+ SCTP_FREE(asoc->nr_mapping_array, SCTP_M_MAP);
+ asoc->nr_mapping_array = NULL;
+ }
+ /* the stream outs */
+ if (asoc->strmout) {
+ SCTP_FREE(asoc->strmout, SCTP_M_STRMO);
+ asoc->strmout = NULL;
+ }
+ asoc->strm_realoutsize = asoc->streamoutcnt = 0;
+ if (asoc->strmin) {
+ for (i = 0; i < asoc->streamincnt; i++) {
+ sctp_clean_up_stream(stcb, &asoc->strmin[i].inqueue);
+ sctp_clean_up_stream(stcb, &asoc->strmin[i].uno_inqueue);
+ }
+ SCTP_FREE(asoc->strmin, SCTP_M_STRMI);
+ asoc->strmin = NULL;
+ }
+ asoc->streamincnt = 0;
+ TAILQ_FOREACH_SAFE(net, &asoc->nets, sctp_next, nnet) {
+#ifdef INVARIANTS
+ if (SCTP_BASE_INFO(ipi_count_raddr) == 0) {
+ panic("no net's left alloc'ed, or list points to itself");
+ }
+#endif
+ TAILQ_REMOVE(&asoc->nets, net, sctp_next);
+ sctp_free_remote_addr(net);
+ }
+ LIST_FOREACH_SAFE(laddr, &asoc->sctp_restricted_addrs, sctp_nxt_addr, naddr) {
+ /*sa_ignore FREED_MEMORY*/
+ sctp_remove_laddr(laddr);
+ }
+
+ /* pending asconf (address) parameters */
+ TAILQ_FOREACH_SAFE(aparam, &asoc->asconf_queue, next, naparam) {
+ /*sa_ignore FREED_MEMORY*/
+ TAILQ_REMOVE(&asoc->asconf_queue, aparam, next);
+ SCTP_FREE(aparam,SCTP_M_ASC_ADDR);
+ }
+ TAILQ_FOREACH_SAFE(aack, &asoc->asconf_ack_sent, next, naack) {
+ /*sa_ignore FREED_MEMORY*/
+ TAILQ_REMOVE(&asoc->asconf_ack_sent, aack, next);
+ if (aack->data != NULL) {
+ sctp_m_freem(aack->data);
+ }
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asconf_ack), aack);
+ }
+ /* clean up auth stuff */
+ if (asoc->local_hmacs)
+ sctp_free_hmaclist(asoc->local_hmacs);
+ if (asoc->peer_hmacs)
+ sctp_free_hmaclist(asoc->peer_hmacs);
+
+ if (asoc->local_auth_chunks)
+ sctp_free_chunklist(asoc->local_auth_chunks);
+ if (asoc->peer_auth_chunks)
+ sctp_free_chunklist(asoc->peer_auth_chunks);
+
+ sctp_free_authinfo(&asoc->authinfo);
+
+ LIST_FOREACH_SAFE(shared_key, &asoc->shared_keys, next, nshared_key) {
+ LIST_REMOVE(shared_key, next);
+ sctp_free_sharedkey(shared_key);
+ /*sa_ignore FREED_MEMORY*/
+ }
+
+ /* Insert new items here :> */
+
+ /* Get rid of LOCK */
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_TCB_LOCK_DESTROY(stcb);
+ if (from_inpcbfree == SCTP_NORMAL_PROC) {
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_INP_RLOCK(inp);
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ /* TEMP CODE */
+ stcb->freed_from_where = from_location;
+#endif
+#ifdef SCTP_TRACK_FREED_ASOCS
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* now clean up the tasoc itself */
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
+ SCTP_DECR_ASOC_COUNT();
+ } else {
+ LIST_INSERT_HEAD(&inp->sctp_asoc_free_list, stcb, sctp_tcblist);
+ }
+#else
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
+ SCTP_DECR_ASOC_COUNT();
+#endif
+ if (from_inpcbfree == SCTP_NORMAL_PROC) {
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* If its NOT the inp_free calling us AND
+ * sctp_close as been called, we
+ * call back...
+ */
+ SCTP_INP_RUNLOCK(inp);
+ /* This will start the kill timer (if we are
+ * the last one) since we hold an increment yet. But
+ * this is the only safe way to do this
+ * since otherwise if the socket closes
+ * at the same time we are here we might
+ * collide in the cleanup.
+ */
+ sctp_inpcb_free(inp,
+ SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE,
+ SCTP_CALLED_DIRECTLY_NOCMPSET);
+ SCTP_INP_DECR_REF(inp);
+ } else {
+ /* The socket is still open. */
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ /* destroyed the asoc */
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 11);
+#endif
+ return (1);
+}
+
+/*
+ * determine if a destination is "reachable" based upon the addresses bound
+ * to the current endpoint (e.g. only v4 or v6 currently bound)
+ */
+/*
+ * FIX: if we allow assoc-level bindx(), then this needs to be fixed to use
+ * assoc level v4/v6 flags, as the assoc *may* not have the same address
+ * types bound as its endpoint
+ */
+int
+sctp_destination_is_reachable(struct sctp_tcb *stcb, struct sockaddr *destaddr)
+{
+ struct sctp_inpcb *inp;
+ int answer;
+
+ /*
+ * No locks here, the TCB, in all cases is already locked and an
+ * assoc is up. There is either a INP lock by the caller applied (in
+ * asconf case when deleting an address) or NOT in the HB case,
+ * however if HB then the INP increment is up and the INP will not
+ * be removed (on top of the fact that we have a TCB lock). So we
+ * only want to read the sctp_flags, which is either bound-all or
+ * not.. no protection needed since once an assoc is up you can't be
+ * changing your binding.
+ */
+ inp = stcb->sctp_ep;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* if bound all, destination is not restricted */
+ /*
+ * RRS: Question during lock work: Is this correct? If you
+ * are bound-all you still might need to obey the V4--V6
+ * flags??? IMO this bound-all stuff needs to be removed!
+ */
+ return (1);
+ }
+ /* NOTE: all "scope" checks are done when local addresses are added */
+ switch (destaddr->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ answer = inp->ip_inp.inp.inp_vflag & INP_IPV6;
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+ answer = inp->ip_inp.inp.inp_vflag & INP_IPV4;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ answer = inp->ip_inp.inp.inp_vflag & INP_CONN;
+ break;
+#endif
+ default:
+ /* invalid family, so it's unreachable */
+ answer = 0;
+ break;
+ }
+ return (answer);
+}
+
+/*
+ * update the inp_vflags on an endpoint
+ */
+static void
+sctp_update_ep_vflag(struct sctp_inpcb *inp)
+{
+ struct sctp_laddr *laddr;
+
+ /* first clear the flag */
+ inp->ip_inp.inp.inp_vflag = 0;
+ /* set the flag based on addresses on the ep list */
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_PCB1, "%s: NULL ifa\n",
+ __func__);
+ continue;
+ }
+
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
+ continue;
+ }
+ switch (laddr->ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ inp->ip_inp.inp.inp_vflag |= INP_IPV6;
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+ inp->ip_inp.inp.inp_vflag |= INP_IPV4;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ inp->ip_inp.inp.inp_vflag |= INP_CONN;
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+}
+
+/*
+ * Add the address to the endpoint local address list There is nothing to be
+ * done if we are bound to all addresses
+ */
+void
+sctp_add_local_addr_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa, uint32_t action)
+{
+ struct sctp_laddr *laddr;
+ struct sctp_tcb *stcb;
+ int fnd, error = 0;
+
+ fnd = 0;
+
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* You are already bound to all. You have it already */
+ return;
+ }
+#ifdef INET6
+ if (ifa->address.sa.sa_family == AF_INET6) {
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ /* Can't bind a non-useable addr. */
+ return;
+ }
+ }
+#endif
+ /* first, is it already present? */
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == ifa) {
+ fnd = 1;
+ break;
+ }
+ }
+
+ if (fnd == 0) {
+ /* Not in the ep list */
+ error = sctp_insert_laddr(&inp->sctp_addr_list, ifa, action);
+ if (error != 0)
+ return;
+ inp->laddr_count++;
+ /* update inp_vflag flags */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ inp->ip_inp.inp.inp_vflag |= INP_IPV6;
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+ inp->ip_inp.inp.inp_vflag |= INP_IPV4;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ inp->ip_inp.inp.inp_vflag |= INP_CONN;
+ break;
+#endif
+ default:
+ break;
+ }
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ sctp_add_local_addr_restricted(stcb, ifa);
+ }
+ }
+ return;
+}
+
+/*
+ * select a new (hopefully reachable) destination net (should only be used
+ * when we deleted an ep addr that is the only usable source address to reach
+ * the destination net)
+ */
+static void
+sctp_select_primary_destination(struct sctp_tcb *stcb)
+{
+ struct sctp_nets *net;
+
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* for now, we'll just pick the first reachable one we find */
+ if (net->dest_state & SCTP_ADDR_UNCONFIRMED)
+ continue;
+ if (sctp_destination_is_reachable(stcb,
+ (struct sockaddr *)&net->ro._l_addr)) {
+ /* found a reachable destination */
+ stcb->asoc.primary_destination = net;
+ }
+ }
+ /* I can't there from here! ...we're gonna die shortly... */
+}
+
+/*
+ * Delete the address from the endpoint local address list. There is nothing
+ * to be done if we are bound to all addresses
+ */
+void
+sctp_del_local_addr_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa)
+{
+ struct sctp_laddr *laddr;
+ int fnd;
+
+ fnd = 0;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* You are already bound to all. You have it already */
+ return;
+ }
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == ifa) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (fnd && (inp->laddr_count < 2)) {
+ /* can't delete unless there are at LEAST 2 addresses */
+ return;
+ }
+ if (fnd) {
+ /*
+ * clean up any use of this address go through our
+ * associations and clear any last_used_address that match
+ * this one for each assoc, see if a new primary_destination
+ * is needed
+ */
+ struct sctp_tcb *stcb;
+
+ /* clean up "next_addr_touse" */
+ if (inp->next_addr_touse == laddr)
+ /* delete this address */
+ inp->next_addr_touse = NULL;
+
+ /* clean up "last_used_address" */
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ struct sctp_nets *net;
+
+ SCTP_TCB_LOCK(stcb);
+ if (stcb->asoc.last_used_address == laddr)
+ /* delete this address */
+ stcb->asoc.last_used_address = NULL;
+ /* Now spin through all the nets and purge any ref to laddr */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (net->ro._s_addr == laddr->ifa) {
+ /* Yep, purge src address selected */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ sctp_rtentry_t *rt;
+
+ /* delete this address if cached */
+ rt = net->ro.ro_rt;
+ if (rt != NULL) {
+ RTFREE(rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } /* for each tcb */
+ /* remove it from the ep list */
+ sctp_remove_laddr(laddr);
+ inp->laddr_count--;
+ /* update inp_vflag flags */
+ sctp_update_ep_vflag(inp);
+ }
+ return;
+}
+
+/*
+ * Add the address to the TCB local address restricted list.
+ * This is a "pending" address list (eg. addresses waiting for an
+ * ASCONF-ACK response) and cannot be used as a valid source address.
+ */
+void
+sctp_add_local_addr_restricted(struct sctp_tcb *stcb, struct sctp_ifa *ifa)
+{
+ struct sctp_laddr *laddr;
+ struct sctpladdr *list;
+
+ /*
+ * Assumes TCB is locked.. and possibly the INP. May need to
+ * confirm/fix that if we need it and is not the case.
+ */
+ list = &stcb->asoc.sctp_restricted_addrs;
+
+#ifdef INET6
+ if (ifa->address.sa.sa_family == AF_INET6) {
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ /* Can't bind a non-existent addr. */
+ return;
+ }
+ }
+#endif
+ /* does the address already exist? */
+ LIST_FOREACH(laddr, list, sctp_nxt_addr) {
+ if (laddr->ifa == ifa) {
+ return;
+ }
+ }
+
+ /* add to the list */
+ (void)sctp_insert_laddr(list, ifa, 0);
+ return;
+}
+
+/*
+ * Remove a local address from the TCB local address restricted list
+ */
+void
+sctp_del_local_addr_restricted(struct sctp_tcb *stcb, struct sctp_ifa *ifa)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_laddr *laddr;
+
+ /*
+ * This is called by asconf work. It is assumed that a) The TCB is
+ * locked and b) The INP is locked. This is true in as much as I can
+ * trace through the entry asconf code where I did these locks.
+ * Again, the ASCONF code is a bit different in that it does lock
+ * the INP during its work often times. This must be since we don't
+ * want other proc's looking up things while what they are looking
+ * up is changing :-D
+ */
+
+ inp = stcb->sctp_ep;
+ /* if subset bound and don't allow ASCONF's, can't delete last */
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) &&
+ sctp_is_feature_off(inp, SCTP_PCB_FLAGS_DO_ASCONF)) {
+ if (stcb->sctp_ep->laddr_count < 2) {
+ /* can't delete last address */
+ return;
+ }
+ }
+ LIST_FOREACH(laddr, &stcb->asoc.sctp_restricted_addrs, sctp_nxt_addr) {
+ /* remove the address if it exists */
+ if (laddr->ifa == NULL)
+ continue;
+ if (laddr->ifa == ifa) {
+ sctp_remove_laddr(laddr);
+ return;
+ }
+ }
+
+ /* address not found! */
+ return;
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+/* sysctl */
+static int sctp_max_number_of_assoc = SCTP_MAX_NUM_OF_ASOC;
+static int sctp_scale_up_for_address = SCTP_SCALE_FOR_ADDR;
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP_MCORE_INPUT) && defined(SMP)
+struct sctp_mcore_ctrl *sctp_mcore_workers = NULL;
+int *sctp_cpuarry = NULL;
+
+void
+sctp_queue_to_mcore(struct mbuf *m, int off, int cpu_to_use)
+{
+ /* Queue a packet to a processor for the specified core */
+ struct sctp_mcore_queue *qent;
+ struct sctp_mcore_ctrl *wkq;
+ int need_wake = 0;
+
+ if (sctp_mcore_workers == NULL) {
+ /* Something went way bad during setup */
+ sctp_input_with_port(m, off, 0);
+ return;
+ }
+ SCTP_MALLOC(qent, struct sctp_mcore_queue *,
+ (sizeof(struct sctp_mcore_queue)),
+ SCTP_M_MCORE);
+ if (qent == NULL) {
+ /* This is trouble */
+ sctp_input_with_port(m, off, 0);
+ return;
+ }
+ qent->vn = curvnet;
+ qent->m = m;
+ qent->off = off;
+ qent->v6 = 0;
+ wkq = &sctp_mcore_workers[cpu_to_use];
+ SCTP_MCORE_QLOCK(wkq);
+
+ TAILQ_INSERT_TAIL(&wkq->que, qent, next);
+ if (wkq->running == 0) {
+ need_wake = 1;
+ }
+ SCTP_MCORE_QUNLOCK(wkq);
+ if (need_wake) {
+ wakeup(&wkq->running);
+ }
+}
+
+static void
+sctp_mcore_thread(void *arg)
+{
+
+ struct sctp_mcore_ctrl *wkq;
+ struct sctp_mcore_queue *qent;
+
+ wkq = (struct sctp_mcore_ctrl *)arg;
+ struct mbuf *m;
+ int off, v6;
+
+ /* Wait for first tickle */
+ SCTP_MCORE_LOCK(wkq);
+ wkq->running = 0;
+ msleep(&wkq->running,
+ &wkq->core_mtx,
+ 0, "wait for pkt", 0);
+ SCTP_MCORE_UNLOCK(wkq);
+
+ /* Bind to our cpu */
+ thread_lock(curthread);
+ sched_bind(curthread, wkq->cpuid);
+ thread_unlock(curthread);
+
+ /* Now lets start working */
+ SCTP_MCORE_LOCK(wkq);
+ /* Now grab lock and go */
+ for (;;) {
+ SCTP_MCORE_QLOCK(wkq);
+ skip_sleep:
+ wkq->running = 1;
+ qent = TAILQ_FIRST(&wkq->que);
+ if (qent) {
+ TAILQ_REMOVE(&wkq->que, qent, next);
+ SCTP_MCORE_QUNLOCK(wkq);
+ CURVNET_SET(qent->vn);
+ m = qent->m;
+ off = qent->off;
+ v6 = qent->v6;
+ SCTP_FREE(qent, SCTP_M_MCORE);
+ if (v6 == 0) {
+ sctp_input_with_port(m, off, 0);
+ } else {
+ SCTP_PRINTF("V6 not yet supported\n");
+ sctp_m_freem(m);
+ }
+ CURVNET_RESTORE();
+ SCTP_MCORE_QLOCK(wkq);
+ }
+ wkq->running = 0;
+ if (!TAILQ_EMPTY(&wkq->que)) {
+ goto skip_sleep;
+ }
+ SCTP_MCORE_QUNLOCK(wkq);
+ msleep(&wkq->running,
+ &wkq->core_mtx,
+ 0, "wait for pkt", 0);
+ }
+}
+
+static void
+sctp_startup_mcore_threads(void)
+{
+ int i, cpu;
+
+ if (mp_ncpus == 1)
+ return;
+
+ if (sctp_mcore_workers != NULL) {
+ /* Already been here in some previous
+ * vnet?
+ */
+ return;
+ }
+ SCTP_MALLOC(sctp_mcore_workers, struct sctp_mcore_ctrl *,
+ ((mp_maxid+1) * sizeof(struct sctp_mcore_ctrl)),
+ SCTP_M_MCORE);
+ if (sctp_mcore_workers == NULL) {
+ /* TSNH I hope */
+ return;
+ }
+ memset(sctp_mcore_workers, 0 , ((mp_maxid+1) *
+ sizeof(struct sctp_mcore_ctrl)));
+ /* Init the structures */
+ for (i = 0; i<=mp_maxid; i++) {
+ TAILQ_INIT(&sctp_mcore_workers[i].que);
+ SCTP_MCORE_LOCK_INIT(&sctp_mcore_workers[i]);
+ SCTP_MCORE_QLOCK_INIT(&sctp_mcore_workers[i]);
+ sctp_mcore_workers[i].cpuid = i;
+ }
+ if (sctp_cpuarry == NULL) {
+ SCTP_MALLOC(sctp_cpuarry, int *,
+ (mp_ncpus * sizeof(int)),
+ SCTP_M_MCORE);
+ i = 0;
+ CPU_FOREACH(cpu) {
+ sctp_cpuarry[i] = cpu;
+ i++;
+ }
+ }
+ /* Now start them all */
+ CPU_FOREACH(cpu) {
+ (void)kproc_create(sctp_mcore_thread,
+ (void *)&sctp_mcore_workers[cpu],
+ &sctp_mcore_workers[cpu].thread_proc,
+ 0,
+ SCTP_KTHREAD_PAGES,
+ SCTP_MCORE_NAME);
+ }
+}
+#endif
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP_NOT_YET)
+static struct mbuf *
+sctp_netisr_hdlr(struct mbuf *m, uintptr_t source)
+{
+ struct ip *ip;
+ struct sctphdr *sh;
+ int offset;
+ uint32_t flowid, tag;
+
+ /*
+ * No flow id built by lower layers fix it so we
+ * create one.
+ */
+ ip = mtod(m, struct ip *);
+ offset = (ip->ip_hl << 2) + sizeof(struct sctphdr);
+ if (SCTP_BUF_LEN(m) < offset) {
+ if ((m = m_pullup(m, offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ return (NULL);
+ }
+ ip = mtod(m, struct ip *);
+ }
+ sh = (struct sctphdr *)((caddr_t)ip + (ip->ip_hl << 2));
+ tag = htonl(sh->v_tag);
+ flowid = tag ^ ntohs(sh->dest_port) ^ ntohs(sh->src_port);
+ m->m_pkthdr.flowid = flowid;
+ /* FIX ME */
+ m->m_flags |= M_FLOWID;
+ return (m);
+}
+
+#endif
+#endif
+void
+#if defined(__Userspace__)
+sctp_pcb_init(int start_threads)
+#else
+sctp_pcb_init(void)
+#endif
+{
+ /*
+ * SCTP initialization for the PCB structures should be called by
+ * the sctp_init() function.
+ */
+ int i;
+ struct timeval tv;
+
+ if (SCTP_BASE_VAR(sctp_pcb_initialized) != 0) {
+ /* error I was called twice */
+ return;
+ }
+ SCTP_BASE_VAR(sctp_pcb_initialized) = 1;
+
+#if defined(SCTP_PROCESS_LEVEL_LOCKS)
+#if !defined(_WIN32)
+ pthread_mutexattr_init(&SCTP_BASE_VAR(mtx_attr));
+ pthread_rwlockattr_init(&SCTP_BASE_VAR(rwlock_attr));
+#ifdef INVARIANTS
+ pthread_mutexattr_settype(&SCTP_BASE_VAR(mtx_attr), PTHREAD_MUTEX_ERRORCHECK);
+#endif
+#endif
+#endif
+#if defined(SCTP_LOCAL_TRACE_BUF)
+#if defined(_WIN32) && !defined(__Userspace__)
+ if (SCTP_BASE_SYSCTL(sctp_log) != NULL) {
+ memset(SCTP_BASE_SYSCTL(sctp_log), 0, sizeof(struct sctp_log));
+ }
+#else
+ memset(&SCTP_BASE_SYSCTL(sctp_log), 0, sizeof(struct sctp_log));
+#endif
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ SCTP_MALLOC(SCTP_BASE_STATS, struct sctpstat *,
+ ((mp_maxid+1) * sizeof(struct sctpstat)),
+ SCTP_M_MCORE);
+#endif
+#endif
+ (void)SCTP_GETTIME_TIMEVAL(&tv);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ memset(SCTP_BASE_STATS, 0, sizeof(struct sctpstat) * (mp_maxid+1));
+ SCTP_BASE_STATS[PCPU_GET(cpuid)].sctps_discontinuitytime.tv_sec = (uint32_t)tv.tv_sec;
+ SCTP_BASE_STATS[PCPU_GET(cpuid)].sctps_discontinuitytime.tv_usec = (uint32_t)tv.tv_usec;
+#else
+ memset(&SCTP_BASE_STATS, 0, sizeof(struct sctpstat));
+ SCTP_BASE_STAT(sctps_discontinuitytime).tv_sec = (uint32_t)tv.tv_sec;
+ SCTP_BASE_STAT(sctps_discontinuitytime).tv_usec = (uint32_t)tv.tv_usec;
+#endif
+#else
+ memset(&SCTP_BASE_STATS, 0, sizeof(struct sctpstat));
+ SCTP_BASE_STAT(sctps_discontinuitytime).tv_sec = (uint32_t)tv.tv_sec;
+ SCTP_BASE_STAT(sctps_discontinuitytime).tv_usec = (uint32_t)tv.tv_usec;
+#endif
+ /* init the empty list of (All) Endpoints */
+ LIST_INIT(&SCTP_BASE_INFO(listhead));
+#if defined(__APPLE__) && !defined(__Userspace__)
+ LIST_INIT(&SCTP_BASE_INFO(inplisthead));
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD) || defined(APPLE_LION) || defined(APPLE_MOUNTAINLION)
+ SCTP_BASE_INFO(sctbinfo).listhead = &SCTP_BASE_INFO(inplisthead);
+ SCTP_BASE_INFO(sctbinfo).mtx_grp_attr = lck_grp_attr_alloc_init();
+ lck_grp_attr_setdefault(SCTP_BASE_INFO(sctbinfo).mtx_grp_attr);
+ SCTP_BASE_INFO(sctbinfo).mtx_grp = lck_grp_alloc_init("sctppcb", SCTP_BASE_INFO(sctbinfo).mtx_grp_attr);
+ SCTP_BASE_INFO(sctbinfo).mtx_attr = lck_attr_alloc_init();
+ lck_attr_setdefault(SCTP_BASE_INFO(sctbinfo).mtx_attr);
+#else
+ SCTP_BASE_INFO(sctbinfo).ipi_listhead = &SCTP_BASE_INFO(inplisthead);
+ SCTP_BASE_INFO(sctbinfo).ipi_lock_grp_attr = lck_grp_attr_alloc_init();
+ lck_grp_attr_setdefault(SCTP_BASE_INFO(sctbinfo).ipi_lock_grp_attr);
+ SCTP_BASE_INFO(sctbinfo).ipi_lock_grp = lck_grp_alloc_init("sctppcb", SCTP_BASE_INFO(sctbinfo).ipi_lock_grp_attr);
+ SCTP_BASE_INFO(sctbinfo).ipi_lock_attr = lck_attr_alloc_init();
+ lck_attr_setdefault(SCTP_BASE_INFO(sctbinfo).ipi_lock_attr);
+#endif
+#if !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION)
+ SCTP_BASE_INFO(sctbinfo).ipi_gc = sctp_gc;
+ in_pcbinfo_attach(&SCTP_BASE_INFO(sctbinfo));
+#endif
+#endif
+
+ /* init the hash table of endpoints */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ TUNABLE_INT_FETCH("net.inet.sctp.tcbhashsize", &SCTP_BASE_SYSCTL(sctp_hashtblsize));
+ TUNABLE_INT_FETCH("net.inet.sctp.pcbhashsize", &SCTP_BASE_SYSCTL(sctp_pcbtblsize));
+ TUNABLE_INT_FETCH("net.inet.sctp.chunkscale", &SCTP_BASE_SYSCTL(sctp_chunkscale));
+#endif
+ SCTP_BASE_INFO(sctp_asochash) = SCTP_HASH_INIT((SCTP_BASE_SYSCTL(sctp_hashtblsize) * 31),
+ &SCTP_BASE_INFO(hashasocmark));
+ SCTP_BASE_INFO(sctp_ephash) = SCTP_HASH_INIT(SCTP_BASE_SYSCTL(sctp_hashtblsize),
+ &SCTP_BASE_INFO(hashmark));
+ SCTP_BASE_INFO(sctp_tcpephash) = SCTP_HASH_INIT(SCTP_BASE_SYSCTL(sctp_hashtblsize),
+ &SCTP_BASE_INFO(hashtcpmark));
+ SCTP_BASE_INFO(hashtblsize) = SCTP_BASE_SYSCTL(sctp_hashtblsize);
+ SCTP_BASE_INFO(sctp_vrfhash) = SCTP_HASH_INIT(SCTP_SIZE_OF_VRF_HASH,
+ &SCTP_BASE_INFO(hashvrfmark));
+
+ SCTP_BASE_INFO(vrf_ifn_hash) = SCTP_HASH_INIT(SCTP_VRF_IFN_HASH_SIZE,
+ &SCTP_BASE_INFO(vrf_ifn_hashmark));
+ /* init the zones */
+ /*
+ * FIX ME: Should check for NULL returns, but if it does fail we are
+ * doomed to panic anyways... add later maybe.
+ */
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_ep), "sctp_ep",
+ sizeof(struct sctp_inpcb), maxsockets);
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_asoc), "sctp_asoc",
+ sizeof(struct sctp_tcb), sctp_max_number_of_assoc);
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_laddr), "sctp_laddr",
+ sizeof(struct sctp_laddr),
+ (sctp_max_number_of_assoc * sctp_scale_up_for_address));
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_net), "sctp_raddr",
+ sizeof(struct sctp_nets),
+ (sctp_max_number_of_assoc * sctp_scale_up_for_address));
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_chunk), "sctp_chunk",
+ sizeof(struct sctp_tmit_chunk),
+ (sctp_max_number_of_assoc * SCTP_BASE_SYSCTL(sctp_chunkscale)));
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_readq), "sctp_readq",
+ sizeof(struct sctp_queued_to_read),
+ (sctp_max_number_of_assoc * SCTP_BASE_SYSCTL(sctp_chunkscale)));
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_strmoq), "sctp_stream_msg_out",
+ sizeof(struct sctp_stream_queue_pending),
+ (sctp_max_number_of_assoc * SCTP_BASE_SYSCTL(sctp_chunkscale)));
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_asconf), "sctp_asconf",
+ sizeof(struct sctp_asconf),
+ (sctp_max_number_of_assoc * SCTP_BASE_SYSCTL(sctp_chunkscale)));
+
+ SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_asconf_ack), "sctp_asconf_ack",
+ sizeof(struct sctp_asconf_ack),
+ (sctp_max_number_of_assoc * SCTP_BASE_SYSCTL(sctp_chunkscale)));
+
+ /* Master Lock INIT for info structure */
+ SCTP_INP_INFO_LOCK_INIT();
+ SCTP_STATLOG_INIT_LOCK();
+
+ SCTP_IPI_COUNT_INIT();
+ SCTP_IPI_ADDR_INIT();
+#ifdef SCTP_PACKET_LOGGING
+ SCTP_IP_PKTLOG_INIT();
+#endif
+ LIST_INIT(&SCTP_BASE_INFO(addr_wq));
+
+ SCTP_WQ_ADDR_INIT();
+ /* not sure if we need all the counts */
+ SCTP_BASE_INFO(ipi_count_ep) = 0;
+ /* assoc/tcb zone info */
+ SCTP_BASE_INFO(ipi_count_asoc) = 0;
+ /* local addrlist zone info */
+ SCTP_BASE_INFO(ipi_count_laddr) = 0;
+ /* remote addrlist zone info */
+ SCTP_BASE_INFO(ipi_count_raddr) = 0;
+ /* chunk info */
+ SCTP_BASE_INFO(ipi_count_chunk) = 0;
+
+ /* socket queue zone info */
+ SCTP_BASE_INFO(ipi_count_readq) = 0;
+
+ /* stream out queue cont */
+ SCTP_BASE_INFO(ipi_count_strmoq) = 0;
+
+ SCTP_BASE_INFO(ipi_free_strmoq) = 0;
+ SCTP_BASE_INFO(ipi_free_chunks) = 0;
+
+ SCTP_OS_TIMER_INIT(&SCTP_BASE_INFO(addr_wq_timer.timer));
+
+ /* Init the TIMEWAIT list */
+ for (i = 0; i < SCTP_STACK_VTAG_HASH_SIZE; i++) {
+ LIST_INIT(&SCTP_BASE_INFO(vtag_timewait)[i]);
+ }
+#if defined(SCTP_PROCESS_LEVEL_LOCKS)
+#if defined(_WIN32)
+ InitializeConditionVariable(&sctp_it_ctl.iterator_wakeup);
+#else
+ (void)pthread_cond_init(&sctp_it_ctl.iterator_wakeup, NULL);
+#endif
+#endif
+ sctp_startup_iterator();
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP_MCORE_INPUT) && defined(SMP)
+ sctp_startup_mcore_threads();
+#endif
+#endif
+
+ /*
+ * INIT the default VRF which for BSD is the only one, other O/S's
+ * may have more. But initially they must start with one and then
+ * add the VRF's as addresses are added.
+ */
+ sctp_init_vrf_list(SCTP_DEFAULT_VRF);
+#if defined(__FreeBSD__) && !defined(__Userspace__) && defined(SCTP_NOT_YET)
+ if (ip_register_flow_handler(sctp_netisr_hdlr, IPPROTO_SCTP)) {
+ SCTP_PRINTF("***SCTP- Error can't register netisr handler***\n");
+ }
+#endif
+#if defined(_SCTP_NEEDS_CALLOUT_) || defined(_USER_SCTP_NEEDS_CALLOUT_)
+ /* allocate the lock for the callout/timer queue */
+ SCTP_TIMERQ_LOCK_INIT();
+ TAILQ_INIT(&SCTP_BASE_INFO(callqueue));
+#endif
+#if defined(__Userspace__)
+ mbuf_initialize(NULL);
+ atomic_init();
+#if defined(INET) || defined(INET6)
+ if (start_threads)
+ recv_thread_init();
+#endif
+#endif
+}
+
+/*
+ * Assumes that the SCTP_BASE_INFO() lock is NOT held.
+ */
+void
+sctp_pcb_finish(void)
+{
+ struct sctp_vrflist *vrf_bucket;
+ struct sctp_vrf *vrf, *nvrf;
+ struct sctp_ifn *ifn, *nifn;
+ struct sctp_ifa *ifa, *nifa;
+ struct sctpvtaghead *chain;
+ struct sctp_tagblock *twait_block, *prev_twait_block;
+ struct sctp_laddr *wi, *nwi;
+ int i;
+ struct sctp_iterator *it, *nit;
+
+ if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
+ SCTP_PRINTF("%s: race condition on teardown.\n", __func__);
+ return;
+ }
+ SCTP_BASE_VAR(sctp_pcb_initialized) = 0;
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ /* Notify the iterator to exit. */
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+ sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_MUST_EXIT;
+ sctp_wakeup_iterator();
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION)
+ in_pcbinfo_detach(&SCTP_BASE_INFO(sctbinfo));
+#endif
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+ do {
+ msleep(&sctp_it_ctl.iterator_flags,
+ sctp_it_ctl.ipi_iterator_wq_mtx,
+ 0, "waiting_for_work", 0);
+ } while ((sctp_it_ctl.iterator_flags & SCTP_ITERATOR_EXITED) == 0);
+ thread_deallocate(sctp_it_ctl.thread_proc);
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+ if (sctp_it_ctl.iterator_thread_obj != NULL) {
+ NTSTATUS status = STATUS_SUCCESS;
+
+ KeSetEvent(&sctp_it_ctl.iterator_wakeup[1], IO_NO_INCREMENT, FALSE);
+ status = KeWaitForSingleObject(sctp_it_ctl.iterator_thread_obj,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+ ObDereferenceObject(sctp_it_ctl.iterator_thread_obj);
+ }
+#endif
+#if defined(__Userspace__)
+ if (SCTP_BASE_VAR(iterator_thread_started)) {
+#if defined(_WIN32)
+ WaitForSingleObject(sctp_it_ctl.thread_proc, INFINITE);
+ CloseHandle(sctp_it_ctl.thread_proc);
+ sctp_it_ctl.thread_proc = NULL;
+#else
+ pthread_join(sctp_it_ctl.thread_proc, NULL);
+ sctp_it_ctl.thread_proc = 0;
+#endif
+ }
+#endif
+#if defined(SCTP_PROCESS_LEVEL_LOCKS)
+#if defined(_WIN32)
+ DeleteConditionVariable(&sctp_it_ctl.iterator_wakeup);
+#else
+ pthread_cond_destroy(&sctp_it_ctl.iterator_wakeup);
+ pthread_mutexattr_destroy(&SCTP_BASE_VAR(mtx_attr));
+ pthread_rwlockattr_destroy(&SCTP_BASE_VAR(rwlock_attr));
+#endif
+#endif
+ /* In FreeBSD the iterator thread never exits
+ * but we do clean up.
+ * The only way FreeBSD reaches here is if we have VRF's
+ * but we still add the ifdef to make it compile on old versions.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+retry:
+#endif
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ /*
+ * sctp_iterator_worker() might be working on an it entry without
+ * holding the lock. We won't find it on the list either and
+ * continue and free/destroy it. While holding the lock, spin, to
+ * avoid the race condition as sctp_iterator_worker() will have to
+ * wait to re-acquire the lock.
+ */
+ if (sctp_it_ctl.iterator_running != 0 || sctp_it_ctl.cur_it != NULL) {
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+ SCTP_PRINTF("%s: Iterator running while we held the lock. Retry. "
+ "cur_it=%p\n", __func__, sctp_it_ctl.cur_it);
+ DELAY(10);
+ goto retry;
+ }
+#endif
+ TAILQ_FOREACH_SAFE(it, &sctp_it_ctl.iteratorhead, sctp_nxt_itr, nit) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (it->vn != curvnet) {
+ continue;
+ }
+#endif
+ TAILQ_REMOVE(&sctp_it_ctl.iteratorhead, it, sctp_nxt_itr);
+ if (it->function_atend != NULL) {
+ (*it->function_atend) (it->pointer, it->val);
+ }
+ SCTP_FREE(it,SCTP_M_ITER);
+ }
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_ITERATOR_LOCK();
+ if ((sctp_it_ctl.cur_it) &&
+ (sctp_it_ctl.cur_it->vn == curvnet)) {
+ sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_STOP_CUR_IT;
+ }
+ SCTP_ITERATOR_UNLOCK();
+#endif
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ SCTP_IPI_ITERATOR_WQ_DESTROY();
+ SCTP_ITERATOR_LOCK_DESTROY();
+#endif
+ SCTP_OS_TIMER_STOP_DRAIN(&SCTP_BASE_INFO(addr_wq_timer.timer));
+ SCTP_WQ_ADDR_LOCK();
+ LIST_FOREACH_SAFE(wi, &SCTP_BASE_INFO(addr_wq), sctp_nxt_addr, nwi) {
+ LIST_REMOVE(wi, sctp_nxt_addr);
+ SCTP_DECR_LADDR_COUNT();
+ if (wi->action == SCTP_DEL_IP_ADDRESS) {
+ SCTP_FREE(wi->ifa, SCTP_M_IFA);
+ }
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_laddr), wi);
+ }
+ SCTP_WQ_ADDR_UNLOCK();
+
+ /*
+ * free the vrf/ifn/ifa lists and hashes (be sure address monitor
+ * is destroyed first).
+ */
+ SCTP_IPI_ADDR_WLOCK();
+ vrf_bucket = &SCTP_BASE_INFO(sctp_vrfhash)[(SCTP_DEFAULT_VRFID & SCTP_BASE_INFO(hashvrfmark))];
+ LIST_FOREACH_SAFE(vrf, vrf_bucket, next_vrf, nvrf) {
+ LIST_FOREACH_SAFE(ifn, &vrf->ifnlist, next_ifn, nifn) {
+ LIST_FOREACH_SAFE(ifa, &ifn->ifalist, next_ifa, nifa) {
+ /* free the ifa */
+ LIST_REMOVE(ifa, next_bucket);
+ LIST_REMOVE(ifa, next_ifa);
+ SCTP_FREE(ifa, SCTP_M_IFA);
+ }
+ /* free the ifn */
+ LIST_REMOVE(ifn, next_bucket);
+ LIST_REMOVE(ifn, next_ifn);
+ SCTP_FREE(ifn, SCTP_M_IFN);
+ }
+ SCTP_HASH_FREE(vrf->vrf_addr_hash, vrf->vrf_addr_hashmark);
+ /* free the vrf */
+ LIST_REMOVE(vrf, next_vrf);
+ SCTP_FREE(vrf, SCTP_M_VRF);
+ }
+ SCTP_IPI_ADDR_WUNLOCK();
+ /* free the vrf hashes */
+ SCTP_HASH_FREE(SCTP_BASE_INFO(sctp_vrfhash), SCTP_BASE_INFO(hashvrfmark));
+ SCTP_HASH_FREE(SCTP_BASE_INFO(vrf_ifn_hash), SCTP_BASE_INFO(vrf_ifn_hashmark));
+
+ /* free the TIMEWAIT list elements malloc'd in the function
+ * sctp_add_vtag_to_timewait()...
+ */
+ for (i = 0; i < SCTP_STACK_VTAG_HASH_SIZE; i++) {
+ chain = &SCTP_BASE_INFO(vtag_timewait)[i];
+ if (!LIST_EMPTY(chain)) {
+ prev_twait_block = NULL;
+ LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) {
+ if (prev_twait_block) {
+ SCTP_FREE(prev_twait_block, SCTP_M_TIMW);
+ }
+ prev_twait_block = twait_block;
+ }
+ SCTP_FREE(prev_twait_block, SCTP_M_TIMW);
+ }
+ }
+
+ /* free the locks and mutexes */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_TIMERQ_LOCK_DESTROY();
+#endif
+#ifdef SCTP_PACKET_LOGGING
+ SCTP_IP_PKTLOG_DESTROY();
+#endif
+ SCTP_IPI_ADDR_DESTROY();
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_IPI_COUNT_DESTROY();
+#endif
+ SCTP_STATLOG_DESTROY();
+ SCTP_INP_INFO_LOCK_DESTROY();
+
+ SCTP_WQ_ADDR_DESTROY();
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD) || defined(APPLE_LION) || defined(APPLE_MOUNTAINLION)
+ lck_grp_attr_free(SCTP_BASE_INFO(sctbinfo).mtx_grp_attr);
+ lck_grp_free(SCTP_BASE_INFO(sctbinfo).mtx_grp);
+ lck_attr_free(SCTP_BASE_INFO(sctbinfo).mtx_attr);
+#else
+ lck_grp_attr_free(SCTP_BASE_INFO(sctbinfo).ipi_lock_grp_attr);
+ lck_grp_free(SCTP_BASE_INFO(sctbinfo).ipi_lock_grp);
+ lck_attr_free(SCTP_BASE_INFO(sctbinfo).ipi_lock_attr);
+#endif
+#endif
+#if defined(__Userspace__)
+ SCTP_TIMERQ_LOCK_DESTROY();
+ SCTP_ZONE_DESTROY(zone_mbuf);
+ SCTP_ZONE_DESTROY(zone_clust);
+ SCTP_ZONE_DESTROY(zone_ext_refcnt);
+#endif
+ /* Get rid of other stuff too. */
+ if (SCTP_BASE_INFO(sctp_asochash) != NULL)
+ SCTP_HASH_FREE(SCTP_BASE_INFO(sctp_asochash), SCTP_BASE_INFO(hashasocmark));
+ if (SCTP_BASE_INFO(sctp_ephash) != NULL)
+ SCTP_HASH_FREE(SCTP_BASE_INFO(sctp_ephash), SCTP_BASE_INFO(hashmark));
+ if (SCTP_BASE_INFO(sctp_tcpephash) != NULL)
+ SCTP_HASH_FREE(SCTP_BASE_INFO(sctp_tcpephash), SCTP_BASE_INFO(hashtcpmark));
+
+#if defined(_WIN32) || defined(__FreeBSD__) || defined(__Userspace__)
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_ep));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_asoc));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_laddr));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_net));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_chunk));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_readq));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_strmoq));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_asconf));
+ SCTP_ZONE_DESTROY(SCTP_BASE_INFO(ipi_zone_asconf_ack));
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ SCTP_FREE(SCTP_BASE_STATS, SCTP_M_MCORE);
+#endif
+#endif
+}
+
+int
+sctp_load_addresses_from_init(struct sctp_tcb *stcb, struct mbuf *m,
+ int offset, int limit,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sockaddr *altsa, uint16_t port)
+{
+ /*
+ * grub through the INIT pulling addresses and loading them to the
+ * nets structure in the asoc. The from address in the mbuf should
+ * also be loaded (if it is not already). This routine can be called
+ * with either INIT or INIT-ACK's as long as the m points to the IP
+ * packet and the offset points to the beginning of the parameters.
+ */
+ struct sctp_inpcb *inp;
+ struct sctp_nets *net, *nnet, *net_tmp;
+ struct sctp_paramhdr *phdr, param_buf;
+ struct sctp_tcb *stcb_tmp;
+ uint16_t ptype, plen;
+ struct sockaddr *sa;
+ uint8_t random_store[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_auth_random *p_random = NULL;
+ uint16_t random_len = 0;
+ uint8_t hmacs_store[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_auth_hmac_algo *hmacs = NULL;
+ uint16_t hmacs_len = 0;
+ uint8_t saw_asconf = 0;
+ uint8_t saw_asconf_ack = 0;
+ uint8_t chunks_store[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_auth_chunk_list *chunks = NULL;
+ uint16_t num_chunks = 0;
+ sctp_key_t *new_key;
+ uint32_t keylen;
+ int got_random = 0, got_hmacs = 0, got_chklist = 0;
+ uint8_t peer_supports_ecn;
+ uint8_t peer_supports_prsctp;
+ uint8_t peer_supports_auth;
+ uint8_t peer_supports_asconf;
+ uint8_t peer_supports_asconf_ack;
+ uint8_t peer_supports_reconfig;
+ uint8_t peer_supports_nrsack;
+ uint8_t peer_supports_pktdrop;
+ uint8_t peer_supports_idata;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+
+ /* First get the destination address setup too. */
+#ifdef INET
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin.sin_len = sizeof(sin);
+#endif
+ sin.sin_port = stcb->rport;
+#endif
+#ifdef INET6
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ sin6.sin6_port = stcb->rport;
+#endif
+ if (altsa) {
+ sa = altsa;
+ } else {
+ sa = src;
+ }
+ peer_supports_idata = 0;
+ peer_supports_ecn = 0;
+ peer_supports_prsctp = 0;
+ peer_supports_auth = 0;
+ peer_supports_asconf = 0;
+ peer_supports_asconf_ack = 0;
+ peer_supports_reconfig = 0;
+ peer_supports_nrsack = 0;
+ peer_supports_pktdrop = 0;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* mark all addresses that we have currently on the list */
+ net->dest_state |= SCTP_ADDR_NOT_IN_ASSOC;
+ }
+ /* does the source address already exist? if so skip it */
+ inp = stcb->sctp_ep;
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net_tmp, dst, stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+
+ if ((stcb_tmp == NULL && inp == stcb->sctp_ep) || inp == NULL) {
+ /* we must add the source address */
+ /* no scope set here since we have a tcb already. */
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (stcb->asoc.scope.ipv4_addr_legal) {
+ if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_2)) {
+ return (-1);
+ }
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (stcb->asoc.scope.ipv6_addr_legal) {
+ if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_3)) {
+ return (-2);
+ }
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (stcb->asoc.scope.conn_addr_legal) {
+ if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_3)) {
+ return (-2);
+ }
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ } else {
+ if (net_tmp != NULL && stcb_tmp == stcb) {
+ net_tmp->dest_state &= ~SCTP_ADDR_NOT_IN_ASSOC;
+ } else if (stcb_tmp != stcb) {
+ /* It belongs to another association? */
+ if (stcb_tmp)
+ SCTP_TCB_UNLOCK(stcb_tmp);
+ return (-3);
+ }
+ }
+ if (stcb->asoc.state == 0) {
+ /* the assoc was freed? */
+ return (-4);
+ }
+ /* now we must go through each of the params. */
+ phdr = sctp_get_next_param(m, offset, &param_buf, sizeof(param_buf));
+ while (phdr) {
+ ptype = ntohs(phdr->param_type);
+ plen = ntohs(phdr->param_length);
+ /*
+ * SCTP_PRINTF("ptype => %0x, plen => %d\n", (uint32_t)ptype,
+ * (int)plen);
+ */
+ if (offset + plen > limit) {
+ break;
+ }
+ if (plen < sizeof(struct sctp_paramhdr)) {
+ break;
+ }
+#ifdef INET
+ if (ptype == SCTP_IPV4_ADDRESS) {
+ if (stcb->asoc.scope.ipv4_addr_legal) {
+ struct sctp_ipv4addr_param *p4, p4_buf;
+
+ /* ok get the v4 address and check/add */
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&p4_buf,
+ sizeof(p4_buf));
+ if (plen != sizeof(struct sctp_ipv4addr_param) ||
+ phdr == NULL) {
+ return (-5);
+ }
+ p4 = (struct sctp_ipv4addr_param *)phdr;
+ sin.sin_addr.s_addr = p4->addr;
+ if (IN_MULTICAST(ntohl(sin.sin_addr.s_addr))) {
+ /* Skip multi-cast addresses */
+ goto next_param;
+ }
+ if ((sin.sin_addr.s_addr == INADDR_BROADCAST) ||
+ (sin.sin_addr.s_addr == INADDR_ANY)) {
+ goto next_param;
+ }
+ sa = (struct sockaddr *)&sin;
+ inp = stcb->sctp_ep;
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net,
+ dst, stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+
+ if ((stcb_tmp == NULL && inp == stcb->sctp_ep) ||
+ inp == NULL) {
+ /* we must add the source address */
+ /*
+ * no scope set since we have a tcb
+ * already
+ */
+
+ /*
+ * we must validate the state again
+ * here
+ */
+ add_it_now:
+ if (stcb->asoc.state == 0) {
+ /* the assoc was freed? */
+ return (-7);
+ }
+ if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_4)) {
+ return (-8);
+ }
+ } else if (stcb_tmp == stcb) {
+ if (stcb->asoc.state == 0) {
+ /* the assoc was freed? */
+ return (-10);
+ }
+ if (net != NULL) {
+ /* clear flag */
+ net->dest_state &=
+ ~SCTP_ADDR_NOT_IN_ASSOC;
+ }
+ } else {
+ /*
+ * strange, address is in another
+ * assoc? straighten out locks.
+ */
+ if (stcb_tmp) {
+ if (SCTP_GET_STATE(stcb_tmp) == SCTP_STATE_COOKIE_WAIT) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ /* in setup state we abort this guy */
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "%s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ sctp_abort_an_association(stcb_tmp->sctp_ep,
+ stcb_tmp, op_err, false,
+ SCTP_SO_NOT_LOCKED);
+ goto add_it_now;
+ }
+ SCTP_TCB_UNLOCK(stcb_tmp);
+ }
+
+ if (stcb->asoc.state == 0) {
+ /* the assoc was freed? */
+ return (-12);
+ }
+ return (-13);
+ }
+ }
+ } else
+#endif
+#ifdef INET6
+ if (ptype == SCTP_IPV6_ADDRESS) {
+ if (stcb->asoc.scope.ipv6_addr_legal) {
+ /* ok get the v6 address and check/add */
+ struct sctp_ipv6addr_param *p6, p6_buf;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&p6_buf,
+ sizeof(p6_buf));
+ if (plen != sizeof(struct sctp_ipv6addr_param) ||
+ phdr == NULL) {
+ return (-14);
+ }
+ p6 = (struct sctp_ipv6addr_param *)phdr;
+ memcpy((caddr_t)&sin6.sin6_addr, p6->addr,
+ sizeof(p6->addr));
+ if (IN6_IS_ADDR_MULTICAST(&sin6.sin6_addr)) {
+ /* Skip multi-cast addresses */
+ goto next_param;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) {
+ /* Link local make no sense without scope */
+ goto next_param;
+ }
+ sa = (struct sockaddr *)&sin6;
+ inp = stcb->sctp_ep;
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net,
+ dst, stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb_tmp == NULL &&
+ (inp == stcb->sctp_ep || inp == NULL)) {
+ /*
+ * we must validate the state again
+ * here
+ */
+ add_it_now6:
+ if (stcb->asoc.state == 0) {
+ /* the assoc was freed? */
+ return (-16);
+ }
+ /*
+ * we must add the address, no scope
+ * set
+ */
+ if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_5)) {
+ return (-17);
+ }
+ } else if (stcb_tmp == stcb) {
+ /*
+ * we must validate the state again
+ * here
+ */
+ if (stcb->asoc.state == 0) {
+ /* the assoc was freed? */
+ return (-19);
+ }
+ if (net != NULL) {
+ /* clear flag */
+ net->dest_state &=
+ ~SCTP_ADDR_NOT_IN_ASSOC;
+ }
+ } else {
+ /*
+ * strange, address is in another
+ * assoc? straighten out locks.
+ */
+ if (stcb_tmp) {
+ if (SCTP_GET_STATE(stcb_tmp) == SCTP_STATE_COOKIE_WAIT) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ /* in setup state we abort this guy */
+ SCTP_SNPRINTF(msg, sizeof(msg),
+ "%s:%d at %s", __FILE__, __LINE__, __func__);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ msg);
+ sctp_abort_an_association(stcb_tmp->sctp_ep,
+ stcb_tmp, op_err, false,
+ SCTP_SO_NOT_LOCKED);
+ goto add_it_now6;
+ }
+ SCTP_TCB_UNLOCK(stcb_tmp);
+ }
+ if (stcb->asoc.state == 0) {
+ /* the assoc was freed? */
+ return (-21);
+ }
+ return (-22);
+ }
+ }
+ } else
+#endif
+ if (ptype == SCTP_ECN_CAPABLE) {
+ peer_supports_ecn = 1;
+ } else if (ptype == SCTP_ULP_ADAPTATION) {
+ if (stcb->asoc.state != SCTP_STATE_OPEN) {
+ struct sctp_adaptation_layer_indication ai, *aip;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&ai, sizeof(ai));
+ aip = (struct sctp_adaptation_layer_indication *)phdr;
+ if (aip) {
+ stcb->asoc.peers_adaptation = ntohl(aip->indication);
+ stcb->asoc.adaptation_needed = 1;
+ }
+ }
+ } else if (ptype == SCTP_SET_PRIM_ADDR) {
+ struct sctp_asconf_addr_param lstore, *fee;
+ int lptype;
+ struct sockaddr *lsa = NULL;
+#ifdef INET
+ struct sctp_asconf_addrv4_param *fii;
+#endif
+
+ if (stcb->asoc.asconf_supported == 0) {
+ return (-100);
+ }
+ if (plen > sizeof(lstore)) {
+ return (-23);
+ }
+ if (plen < sizeof(struct sctp_asconf_addrv4_param)) {
+ return (-101);
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&lstore,
+ plen);
+ if (phdr == NULL) {
+ return (-24);
+ }
+ fee = (struct sctp_asconf_addr_param *)phdr;
+ lptype = ntohs(fee->addrp.ph.param_type);
+ switch (lptype) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (plen !=
+ sizeof(struct sctp_asconf_addrv4_param)) {
+ SCTP_PRINTF("Sizeof setprim in init/init ack not %d but %d - ignored\n",
+ (int)sizeof(struct sctp_asconf_addrv4_param),
+ plen);
+ } else {
+ fii = (struct sctp_asconf_addrv4_param *)fee;
+ sin.sin_addr.s_addr = fii->addrp.addr;
+ lsa = (struct sockaddr *)&sin;
+ }
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (plen !=
+ sizeof(struct sctp_asconf_addr_param)) {
+ SCTP_PRINTF("Sizeof setprim (v6) in init/init ack not %d but %d - ignored\n",
+ (int)sizeof(struct sctp_asconf_addr_param),
+ plen);
+ } else {
+ memcpy(sin6.sin6_addr.s6_addr,
+ fee->addrp.addr,
+ sizeof(fee->addrp.addr));
+ lsa = (struct sockaddr *)&sin6;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ if (lsa) {
+ (void)sctp_set_primary_addr(stcb, sa, NULL);
+ }
+ } else if (ptype == SCTP_HAS_NAT_SUPPORT) {
+ stcb->asoc.peer_supports_nat = 1;
+ } else if (ptype == SCTP_PRSCTP_SUPPORTED) {
+ /* Peer supports pr-sctp */
+ peer_supports_prsctp = 1;
+ } else if (ptype == SCTP_ZERO_CHECKSUM_ACCEPTABLE) {
+ struct sctp_zero_checksum_acceptable zero_chksum, *zero_chksum_p;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&zero_chksum,
+ sizeof(struct sctp_zero_checksum_acceptable));
+ if (phdr != NULL) {
+ /*
+ * Only send zero checksums if the upper layer
+ * has enabled the support for the same method
+ * as allowed by the peer.
+ */
+ zero_chksum_p = (struct sctp_zero_checksum_acceptable *)phdr;
+ if ((ntohl(zero_chksum_p->edmid) != SCTP_EDMID_NONE) &&
+ (ntohl(zero_chksum_p->edmid) == stcb->asoc.rcv_edmid)) {
+ stcb->asoc.snd_edmid = stcb->asoc.rcv_edmid;
+ }
+ }
+ } else if (ptype == SCTP_SUPPORTED_CHUNK_EXT) {
+ /* A supported extension chunk */
+ struct sctp_supported_chunk_types_param *pr_supported;
+ uint8_t local_store[SCTP_PARAM_BUFFER_SIZE];
+ int num_ent, i;
+
+ if (plen > sizeof(local_store)) {
+ return (-35);
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&local_store, plen);
+ if (phdr == NULL) {
+ return (-25);
+ }
+ pr_supported = (struct sctp_supported_chunk_types_param *)phdr;
+ num_ent = plen - sizeof(struct sctp_paramhdr);
+ for (i = 0; i < num_ent; i++) {
+ switch (pr_supported->chunk_types[i]) {
+ case SCTP_ASCONF:
+ peer_supports_asconf = 1;
+ break;
+ case SCTP_ASCONF_ACK:
+ peer_supports_asconf_ack = 1;
+ break;
+ case SCTP_FORWARD_CUM_TSN:
+ peer_supports_prsctp = 1;
+ break;
+ case SCTP_PACKET_DROPPED:
+ peer_supports_pktdrop = 1;
+ break;
+ case SCTP_NR_SELECTIVE_ACK:
+ peer_supports_nrsack = 1;
+ break;
+ case SCTP_STREAM_RESET:
+ peer_supports_reconfig = 1;
+ break;
+ case SCTP_AUTHENTICATION:
+ peer_supports_auth = 1;
+ break;
+ case SCTP_IDATA:
+ peer_supports_idata = 1;
+ break;
+ default:
+ /* one I have not learned yet */
+ break;
+ }
+ }
+ } else if (ptype == SCTP_RANDOM) {
+ if (plen > sizeof(random_store))
+ break;
+ if (got_random) {
+ /* already processed a RANDOM */
+ goto next_param;
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)random_store,
+ plen);
+ if (phdr == NULL)
+ return (-26);
+ p_random = (struct sctp_auth_random *)phdr;
+ random_len = plen - sizeof(*p_random);
+ /* enforce the random length */
+ if (random_len != SCTP_AUTH_RANDOM_SIZE_REQUIRED) {
+ SCTPDBG(SCTP_DEBUG_AUTH1, "SCTP: invalid RANDOM len\n");
+ return (-27);
+ }
+ got_random = 1;
+ } else if (ptype == SCTP_HMAC_LIST) {
+ uint16_t num_hmacs;
+ uint16_t i;
+
+ if (plen > sizeof(hmacs_store))
+ break;
+ if (got_hmacs) {
+ /* already processed a HMAC list */
+ goto next_param;
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)hmacs_store,
+ plen);
+ if (phdr == NULL)
+ return (-28);
+ hmacs = (struct sctp_auth_hmac_algo *)phdr;
+ hmacs_len = plen - sizeof(*hmacs);
+ num_hmacs = hmacs_len / sizeof(hmacs->hmac_ids[0]);
+ /* validate the hmac list */
+ if (sctp_verify_hmac_param(hmacs, num_hmacs)) {
+ return (-29);
+ }
+ if (stcb->asoc.peer_hmacs != NULL)
+ sctp_free_hmaclist(stcb->asoc.peer_hmacs);
+ stcb->asoc.peer_hmacs = sctp_alloc_hmaclist(num_hmacs);
+ if (stcb->asoc.peer_hmacs != NULL) {
+ for (i = 0; i < num_hmacs; i++) {
+ (void)sctp_auth_add_hmacid(stcb->asoc.peer_hmacs,
+ ntohs(hmacs->hmac_ids[i]));
+ }
+ }
+ got_hmacs = 1;
+ } else if (ptype == SCTP_CHUNK_LIST) {
+ int i;
+
+ if (plen > sizeof(chunks_store))
+ break;
+ if (got_chklist) {
+ /* already processed a Chunks list */
+ goto next_param;
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)chunks_store,
+ plen);
+ if (phdr == NULL)
+ return (-30);
+ chunks = (struct sctp_auth_chunk_list *)phdr;
+ num_chunks = plen - sizeof(*chunks);
+ if (stcb->asoc.peer_auth_chunks != NULL)
+ sctp_clear_chunklist(stcb->asoc.peer_auth_chunks);
+ else
+ stcb->asoc.peer_auth_chunks = sctp_alloc_chunklist();
+ for (i = 0; i < num_chunks; i++) {
+ (void)sctp_auth_add_chunk(chunks->chunk_types[i],
+ stcb->asoc.peer_auth_chunks);
+ /* record asconf/asconf-ack if listed */
+ if (chunks->chunk_types[i] == SCTP_ASCONF)
+ saw_asconf = 1;
+ if (chunks->chunk_types[i] == SCTP_ASCONF_ACK)
+ saw_asconf_ack = 1;
+ }
+ got_chklist = 1;
+ } else if ((ptype == SCTP_HEARTBEAT_INFO) ||
+ (ptype == SCTP_STATE_COOKIE) ||
+ (ptype == SCTP_UNRECOG_PARAM) ||
+ (ptype == SCTP_COOKIE_PRESERVE) ||
+ (ptype == SCTP_SUPPORTED_ADDRTYPE) ||
+ (ptype == SCTP_ADD_IP_ADDRESS) ||
+ (ptype == SCTP_DEL_IP_ADDRESS) ||
+ (ptype == SCTP_ERROR_CAUSE_IND) ||
+ (ptype == SCTP_SUCCESS_REPORT)) {
+ /* don't care */
+ } else {
+ if ((ptype & 0x8000) == 0x0000) {
+ /*
+ * must stop processing the rest of the
+ * param's. Any report bits were handled
+ * with the call to
+ * sctp_arethere_unrecognized_parameters()
+ * when the INIT or INIT-ACK was first seen.
+ */
+ break;
+ }
+ }
+
+ next_param:
+ offset += SCTP_SIZE32(plen);
+ if (offset >= limit) {
+ break;
+ }
+ phdr = sctp_get_next_param(m, offset, &param_buf,
+ sizeof(param_buf));
+ }
+ /* Now check to see if we need to purge any addresses */
+ TAILQ_FOREACH_SAFE(net, &stcb->asoc.nets, sctp_next, nnet) {
+ if ((net->dest_state & SCTP_ADDR_NOT_IN_ASSOC) ==
+ SCTP_ADDR_NOT_IN_ASSOC) {
+ /* This address has been removed from the asoc */
+ /* remove and free it */
+ stcb->asoc.numnets--;
+ TAILQ_REMOVE(&stcb->asoc.nets, net, sctp_next);
+ if (net == stcb->asoc.alternate) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ if (net == stcb->asoc.primary_destination) {
+ stcb->asoc.primary_destination = NULL;
+ sctp_select_primary_destination(stcb);
+ }
+ sctp_free_remote_addr(net);
+ }
+ }
+ if ((stcb->asoc.ecn_supported == 1) &&
+ (peer_supports_ecn == 0)) {
+ stcb->asoc.ecn_supported = 0;
+ }
+ if ((stcb->asoc.prsctp_supported == 1) &&
+ (peer_supports_prsctp == 0)) {
+ stcb->asoc.prsctp_supported = 0;
+ }
+ if ((stcb->asoc.auth_supported == 1) &&
+ ((peer_supports_auth == 0) ||
+ (got_random == 0) || (got_hmacs == 0))) {
+ stcb->asoc.auth_supported = 0;
+ }
+ if ((stcb->asoc.asconf_supported == 1) &&
+ ((peer_supports_asconf == 0) || (peer_supports_asconf_ack == 0) ||
+ (stcb->asoc.auth_supported == 0) ||
+ (saw_asconf == 0) || (saw_asconf_ack == 0))) {
+ stcb->asoc.asconf_supported = 0;
+ }
+ if ((stcb->asoc.reconfig_supported == 1) &&
+ (peer_supports_reconfig == 0)) {
+ stcb->asoc.reconfig_supported = 0;
+ }
+ if ((stcb->asoc.idata_supported == 1) &&
+ (peer_supports_idata == 0)) {
+ stcb->asoc.idata_supported = 0;
+ }
+ if ((stcb->asoc.nrsack_supported == 1) &&
+ (peer_supports_nrsack == 0)) {
+ stcb->asoc.nrsack_supported = 0;
+ }
+ if ((stcb->asoc.pktdrop_supported == 1) &&
+ (peer_supports_pktdrop == 0)) {
+ stcb->asoc.pktdrop_supported = 0;
+ }
+ /* validate authentication required parameters */
+ if ((peer_supports_auth == 0) && (got_chklist == 1)) {
+ /* peer does not support auth but sent a chunks list? */
+ return (-31);
+ }
+ if ((peer_supports_asconf == 1) && (peer_supports_auth == 0)) {
+ /* peer supports asconf but not auth? */
+ return (-32);
+ } else if ((peer_supports_asconf == 1) &&
+ (peer_supports_auth == 1) &&
+ ((saw_asconf == 0) || (saw_asconf_ack == 0))) {
+ return (-33);
+ }
+ /* concatenate the full random key */
+ keylen = sizeof(*p_random) + random_len + sizeof(*hmacs) + hmacs_len;
+ if (chunks != NULL) {
+ keylen += sizeof(*chunks) + num_chunks;
+ }
+ new_key = sctp_alloc_key(keylen);
+ if (new_key != NULL) {
+ /* copy in the RANDOM */
+ if (p_random != NULL) {
+ keylen = sizeof(*p_random) + random_len;
+ memcpy(new_key->key, p_random, keylen);
+ } else {
+ keylen = 0;
+ }
+ /* append in the AUTH chunks */
+ if (chunks != NULL) {
+ memcpy(new_key->key + keylen, chunks,
+ sizeof(*chunks) + num_chunks);
+ keylen += sizeof(*chunks) + num_chunks;
+ }
+ /* append in the HMACs */
+ if (hmacs != NULL) {
+ memcpy(new_key->key + keylen, hmacs,
+ sizeof(*hmacs) + hmacs_len);
+ }
+ } else {
+ /* failed to get memory for the key */
+ return (-34);
+ }
+ if (stcb->asoc.authinfo.peer_random != NULL)
+ sctp_free_key(stcb->asoc.authinfo.peer_random);
+ stcb->asoc.authinfo.peer_random = new_key;
+ sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.assoc_keyid);
+ sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.recv_keyid);
+
+ return (0);
+}
+
+int
+sctp_set_primary_addr(struct sctp_tcb *stcb, struct sockaddr *sa,
+ struct sctp_nets *net)
+{
+ /* make sure the requested primary address exists in the assoc */
+ if (net == NULL && sa)
+ net = sctp_findnet(stcb, sa);
+
+ if (net == NULL) {
+ /* didn't find the requested primary address! */
+ return (-1);
+ } else {
+ /* set the primary address */
+ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ /* Must be confirmed, so queue to set */
+ net->dest_state |= SCTP_ADDR_REQ_PRIMARY;
+ return (0);
+ }
+ stcb->asoc.primary_destination = net;
+ if (((net->dest_state & SCTP_ADDR_PF) == 0) &&
+ (stcb->asoc.alternate != NULL)) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ net = TAILQ_FIRST(&stcb->asoc.nets);
+ if (net != stcb->asoc.primary_destination) {
+ /* first one on the list is NOT the primary
+ * sctp_cmpaddr() is much more efficient if
+ * the primary is the first on the list, make it
+ * so.
+ */
+ TAILQ_REMOVE(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next);
+ TAILQ_INSERT_HEAD(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next);
+ }
+ return (0);
+ }
+}
+
+bool
+sctp_is_vtag_good(uint32_t tag, uint16_t lport, uint16_t rport, struct timeval *now)
+{
+ struct sctpasochead *head;
+ struct sctp_tcb *stcb;
+
+ SCTP_INP_INFO_LOCK_ASSERT();
+
+ head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(tag, SCTP_BASE_INFO(hashasocmark))];
+ LIST_FOREACH(stcb, head, sctp_asocs) {
+ /* We choose not to lock anything here. TCB's can't be
+ * removed since we have the read lock, so they can't
+ * be freed on us, same thing for the INP. I may
+ * be wrong with this assumption, but we will go
+ * with it for now :-)
+ */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ continue;
+ }
+ if (stcb->asoc.my_vtag == tag) {
+ /* candidate */
+ if (stcb->rport != rport) {
+ continue;
+ }
+ if (stcb->sctp_ep->sctp_lport != lport) {
+ continue;
+ }
+ /* The tag is currently used, so don't use it. */
+ return (false);
+ }
+ }
+ return (!sctp_is_in_timewait(tag, lport, rport, (uint32_t)now->tv_sec));
+}
+
+static void
+sctp_drain_mbufs(struct sctp_tcb *stcb)
+{
+ /*
+ * We must hunt this association for MBUF's past the cumack (i.e.
+ * out of order data that we can renege on).
+ */
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk, *nchk;
+ uint32_t cumulative_tsn_p1;
+ struct sctp_queued_to_read *control, *ncontrol;
+ int cnt, strmat;
+ uint32_t gap, i;
+ int fnd = 0;
+
+ /* We look for anything larger than the cum-ack + 1 */
+
+ asoc = &stcb->asoc;
+ if (asoc->cumulative_tsn == asoc->highest_tsn_inside_map) {
+ /* none we can reneg on. */
+ return;
+ }
+ SCTP_STAT_INCR(sctps_protocol_drains_done);
+ cumulative_tsn_p1 = asoc->cumulative_tsn + 1;
+ cnt = 0;
+ /* Ok that was fun, now we will drain all the inbound streams? */
+ for (strmat = 0; strmat < asoc->streamincnt; strmat++) {
+ TAILQ_FOREACH_SAFE(control, &asoc->strmin[strmat].inqueue, next_instrm, ncontrol) {
+#ifdef INVARIANTS
+ if (control->on_strm_q != SCTP_ON_ORDERED) {
+ panic("Huh control: %p on_q: %d -- not ordered?",
+ control, control->on_strm_q);
+ }
+#endif
+ if (SCTP_TSN_GT(control->sinfo_tsn, cumulative_tsn_p1)) {
+ /* Yep it is above cum-ack */
+ cnt++;
+ SCTP_CALC_TSN_TO_GAP(gap, control->sinfo_tsn, asoc->mapping_array_base_tsn);
+ KASSERT(control->length > 0, ("control has zero length"));
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ if (control->on_read_q) {
+ TAILQ_REMOVE(&stcb->sctp_ep->read_queue, control, next);
+ control->on_read_q = 0;
+ }
+ TAILQ_REMOVE(&asoc->strmin[strmat].inqueue, control, next_instrm);
+ control->on_strm_q = 0;
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ sctp_free_remote_addr(control->whoFrom);
+ /* Now its reasm? */
+ TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
+ cnt++;
+ SCTP_CALC_TSN_TO_GAP(gap, chk->rec.data.tsn, asoc->mapping_array_base_tsn);
+ KASSERT(chk->send_size > 0, ("chunk has zero length"));
+ if (asoc->size_on_reasm_queue >= chk->send_size) {
+ asoc->size_on_reasm_queue -= chk->send_size;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_reasm_queue = %u smaller than chunk length %u", asoc->size_on_reasm_queue, chk->send_size);
+#else
+ asoc->size_on_reasm_queue = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ TAILQ_REMOVE(&control->reasm, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ sctp_free_a_readq(stcb, control);
+ }
+ }
+ TAILQ_FOREACH_SAFE(control, &asoc->strmin[strmat].uno_inqueue, next_instrm, ncontrol) {
+#ifdef INVARIANTS
+ if (control->on_strm_q != SCTP_ON_UNORDERED) {
+ panic("Huh control: %p on_q: %d -- not unordered?",
+ control, control->on_strm_q);
+ }
+#endif
+ if (SCTP_TSN_GT(control->sinfo_tsn, cumulative_tsn_p1)) {
+ /* Yep it is above cum-ack */
+ cnt++;
+ SCTP_CALC_TSN_TO_GAP(gap, control->sinfo_tsn, asoc->mapping_array_base_tsn);
+ KASSERT(control->length > 0, ("control has zero length"));
+ if (asoc->size_on_all_streams >= control->length) {
+ asoc->size_on_all_streams -= control->length;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length);
+#else
+ asoc->size_on_all_streams = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ if (control->on_read_q) {
+ TAILQ_REMOVE(&stcb->sctp_ep->read_queue, control, next);
+ control->on_read_q = 0;
+ }
+ TAILQ_REMOVE(&asoc->strmin[strmat].uno_inqueue, control, next_instrm);
+ control->on_strm_q = 0;
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ sctp_free_remote_addr(control->whoFrom);
+ /* Now its reasm? */
+ TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
+ cnt++;
+ SCTP_CALC_TSN_TO_GAP(gap, chk->rec.data.tsn, asoc->mapping_array_base_tsn);
+ KASSERT(chk->send_size > 0, ("chunk has zero length"));
+ if (asoc->size_on_reasm_queue >= chk->send_size) {
+ asoc->size_on_reasm_queue -= chk->send_size;
+ } else {
+#ifdef INVARIANTS
+ panic("size_on_reasm_queue = %u smaller than chunk length %u", asoc->size_on_reasm_queue, chk->send_size);
+#else
+ asoc->size_on_reasm_queue = 0;
+#endif
+ }
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ TAILQ_REMOVE(&control->reasm, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ sctp_free_a_readq(stcb, control);
+ }
+ }
+ }
+ if (cnt) {
+ /* We must back down to see what the new highest is */
+ for (i = asoc->highest_tsn_inside_map; SCTP_TSN_GE(i, asoc->mapping_array_base_tsn); i--) {
+ SCTP_CALC_TSN_TO_GAP(gap, i, asoc->mapping_array_base_tsn);
+ if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap)) {
+ asoc->highest_tsn_inside_map = i;
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ asoc->highest_tsn_inside_map = asoc->mapping_array_base_tsn - 1;
+ }
+
+ /*
+ * Question, should we go through the delivery queue? The only
+ * reason things are on here is the app not reading OR a p-d-api up.
+ * An attacker COULD send enough in to initiate the PD-API and then
+ * send a bunch of stuff to other streams... these would wind up on
+ * the delivery queue.. and then we would not get to them. But in
+ * order to do this I then have to back-track and un-deliver
+ * sequence numbers in streams.. el-yucko. I think for now we will
+ * NOT look at the delivery queue and leave it to be something to
+ * consider later. An alternative would be to abort the P-D-API with
+ * a notification and then deliver the data.... Or another method
+ * might be to keep track of how many times the situation occurs and
+ * if we see a possible attack underway just abort the association.
+ */
+#ifdef SCTP_DEBUG
+ SCTPDBG(SCTP_DEBUG_PCB1, "Freed %d chunks from reneg harvest\n", cnt);
+#endif
+ /*
+ * Now do we need to find a new
+ * asoc->highest_tsn_inside_map?
+ */
+ asoc->last_revoke_count = cnt;
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_11);
+ /*sa_ignore NO_NULL_CHK*/
+ sctp_send_sack(stcb, SCTP_SO_NOT_LOCKED);
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_DRAIN, SCTP_SO_NOT_LOCKED);
+ }
+ /*
+ * Another issue, in un-setting the TSN's in the mapping array we
+ * DID NOT adjust the highest_tsn marker. This will cause one of two
+ * things to occur. It may cause us to do extra work in checking for
+ * our mapping array movement. More importantly it may cause us to
+ * SACK every datagram. This may not be a bad thing though since we
+ * will recover once we get our cum-ack above and all this stuff we
+ * dumped recovered.
+ */
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static void
+#else
+void
+#endif
+sctp_drain(void)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+ VNET_ITERATOR_DECL(vnet_iter);
+
+ NET_EPOCH_ENTER(et);
+#else
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+
+ SCTP_STAT_INCR(sctps_protocol_drain_calls);
+ if (SCTP_BASE_SYSCTL(sctp_do_drain) == 0) {
+ return;
+ }
+#endif
+ /*
+ * We must walk the PCB lists for ALL associations here. The system
+ * is LOW on MBUF's and needs help. This is where reneging will
+ * occur. We really hope this does NOT happen!
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ VNET_LIST_RLOCK_NOSLEEP();
+ VNET_FOREACH(vnet_iter) {
+ CURVNET_SET(vnet_iter);
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_STAT_INCR(sctps_protocol_drain_calls);
+ if (SCTP_BASE_SYSCTL(sctp_do_drain) == 0) {
+#ifdef VIMAGE
+ continue;
+#else
+ NET_EPOCH_EXIT(et);
+ return;
+#endif
+ }
+#endif
+ SCTP_INP_INFO_RLOCK();
+ LIST_FOREACH(inp, &SCTP_BASE_INFO(listhead), sctp_list) {
+ /* For each endpoint */
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ /* For each association */
+ SCTP_TCB_LOCK(stcb);
+ sctp_drain_mbufs(stcb);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ SCTP_INP_INFO_RUNLOCK();
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+ }
+ VNET_LIST_RUNLOCK_NOSLEEP();
+ NET_EPOCH_EXIT(et);
+#endif
+}
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+EVENTHANDLER_DEFINE(vm_lowmem, sctp_drain, NULL, LOWMEM_PRI_DEFAULT);
+EVENTHANDLER_DEFINE(mbuf_lowmem, sctp_drain, NULL, LOWMEM_PRI_DEFAULT);
+#endif
+
+/*
+ * start a new iterator
+ * iterates through all endpoints and associations based on the pcb_state
+ * flags and asoc_state. "af" (mandatory) is executed for all matching
+ * assocs and "ef" (optional) is executed when the iterator completes.
+ * "inpf" (optional) is executed for each new endpoint as it is being
+ * iterated through. inpe (optional) is called when the inp completes
+ * its way through all the stcbs.
+ */
+int
+sctp_initiate_iterator(inp_func inpf,
+ asoc_func af,
+ inp_func inpe,
+ uint32_t pcb_state,
+ uint32_t pcb_features,
+ uint32_t asoc_state,
+ void *argp,
+ uint32_t argi,
+ end_func ef,
+ struct sctp_inpcb *s_inp,
+ uint8_t chunk_output_off)
+{
+ struct sctp_iterator *it = NULL;
+
+ if (af == NULL) {
+ return (-1);
+ }
+ if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
+ SCTP_PRINTF("%s: abort on initialize being %d\n", __func__,
+ SCTP_BASE_VAR(sctp_pcb_initialized));
+ return (-1);
+ }
+ SCTP_MALLOC(it, struct sctp_iterator *, sizeof(struct sctp_iterator),
+ SCTP_M_ITER);
+ if (it == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOMEM);
+ return (-1);
+ }
+ memset(it, 0, sizeof(*it));
+ it->function_assoc = af;
+ it->function_inp = inpf;
+ if (inpf)
+ it->done_current_ep = 0;
+ else
+ it->done_current_ep = 1;
+ it->function_atend = ef;
+ it->pointer = argp;
+ it->val = argi;
+ it->pcb_flags = pcb_state;
+ it->pcb_features = pcb_features;
+ it->asoc_state = asoc_state;
+ it->function_inp_end = inpe;
+ it->no_chunk_output = chunk_output_off;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ it->vn = curvnet;
+#endif
+ if (s_inp) {
+ /* Assume lock is held here */
+ it->inp = s_inp;
+ SCTP_INP_INCR_REF(it->inp);
+ it->iterator_flags = SCTP_ITERATOR_DO_SINGLE_INP;
+ } else {
+ SCTP_INP_INFO_RLOCK();
+ it->inp = LIST_FIRST(&SCTP_BASE_INFO(listhead));
+ if (it->inp) {
+ SCTP_INP_INCR_REF(it->inp);
+ }
+ SCTP_INP_INFO_RUNLOCK();
+ it->iterator_flags = SCTP_ITERATOR_DO_ALL_INP;
+ }
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+ if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+ SCTP_PRINTF("%s: rollback on initialize being %d it=%p\n", __func__,
+ SCTP_BASE_VAR(sctp_pcb_initialized), it);
+ SCTP_FREE(it, SCTP_M_ITER);
+ return (-1);
+ }
+ TAILQ_INSERT_TAIL(&sctp_it_ctl.iteratorhead, it, sctp_nxt_itr);
+ if (sctp_it_ctl.iterator_running == 0) {
+ sctp_wakeup_iterator();
+ }
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+ /* sa_ignore MEMLEAK {memory is put on the tailq for the iterator} */
+ return (0);
+}
+
+/*
+ * Atomically add flags to the sctp_flags of an inp.
+ * To be used when the write lock of the inp is not held.
+ */
+void
+sctp_pcb_add_flags(struct sctp_inpcb *inp, uint32_t flags)
+{
+ uint32_t old_flags, new_flags;
+
+ do {
+ old_flags = inp->sctp_flags;
+ new_flags = old_flags | flags;
+ } while (atomic_cmpset_int(&inp->sctp_flags, old_flags, new_flags) == 0);
+}
diff --git a/netwerk/sctp/src/netinet/sctp_pcb.h b/netwerk/sctp/src/netinet/sctp_pcb.h
new file mode 100644
index 0000000000..5298afc2ad
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_pcb.h
@@ -0,0 +1,876 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_PCB_H_
+#define _NETINET_SCTP_PCB_H_
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_constants.h>
+#include <netinet/sctp_sysctl.h>
+
+LIST_HEAD(sctppcbhead, sctp_inpcb);
+LIST_HEAD(sctpasochead, sctp_tcb);
+LIST_HEAD(sctpladdr, sctp_laddr);
+LIST_HEAD(sctpvtaghead, sctp_tagblock);
+LIST_HEAD(sctp_vrflist, sctp_vrf);
+LIST_HEAD(sctp_ifnlist, sctp_ifn);
+LIST_HEAD(sctp_ifalist, sctp_ifa);
+TAILQ_HEAD(sctp_readhead, sctp_queued_to_read);
+TAILQ_HEAD(sctp_streamhead, sctp_stream_queue_pending);
+
+#include <netinet/sctp_structs.h>
+#include <netinet/sctp_auth.h>
+
+#define SCTP_PCBHASH_ALLADDR(port, mask) (port & mask)
+#define SCTP_PCBHASH_ASOC(tag, mask) (tag & mask)
+
+struct sctp_vrf {
+ LIST_ENTRY (sctp_vrf) next_vrf;
+ struct sctp_ifalist *vrf_addr_hash;
+ struct sctp_ifnlist ifnlist;
+ uint32_t vrf_id;
+ uint32_t tbl_id_v4; /* default v4 table id */
+ uint32_t tbl_id_v6; /* default v6 table id */
+ uint32_t total_ifa_count;
+ u_long vrf_addr_hashmark;
+ uint32_t refcount;
+};
+
+struct sctp_ifn {
+ struct sctp_ifalist ifalist;
+ struct sctp_vrf *vrf;
+ LIST_ENTRY(sctp_ifn) next_ifn;
+ LIST_ENTRY(sctp_ifn) next_bucket;
+ void *ifn_p; /* never access without appropriate lock */
+ uint32_t ifn_mtu;
+ uint32_t ifn_type;
+ uint32_t ifn_index; /* shorthand way to look at ifn for reference */
+ uint32_t refcount; /* number of reference held should be >= ifa_count */
+ uint32_t ifa_count; /* IFA's we hold (in our list - ifalist)*/
+ uint32_t num_v6; /* number of v6 addresses */
+ uint32_t num_v4; /* number of v4 addresses */
+ uint32_t registered_af; /* registered address family for i/f events */
+ char ifn_name[SCTP_IFNAMSIZ];
+};
+
+/* SCTP local IFA flags */
+#define SCTP_ADDR_VALID 0x00000001 /* its up and active */
+#define SCTP_BEING_DELETED 0x00000002 /* being deleted,
+ * when refcount = 0. Note
+ * that it is pulled from the ifn list
+ * and ifa_p is nulled right away but
+ * it cannot be freed until the last *net
+ * pointing to it is deleted.
+ */
+#define SCTP_ADDR_DEFER_USE 0x00000004 /* Hold off using this one */
+#define SCTP_ADDR_IFA_UNUSEABLE 0x00000008
+
+struct sctp_ifa {
+ LIST_ENTRY(sctp_ifa) next_ifa;
+ LIST_ENTRY(sctp_ifa) next_bucket;
+ struct sctp_ifn *ifn_p; /* back pointer to parent ifn */
+ void *ifa; /* pointer to ifa, needed for flag
+ * update for that we MUST lock
+ * appropriate locks. This is for V6.
+ */
+ union sctp_sockstore address;
+ uint32_t refcount; /* number of folks referring to this */
+ uint32_t flags;
+ uint32_t localifa_flags;
+ uint32_t vrf_id; /* vrf_id of this addr (for deleting) */
+ uint8_t src_is_loop;
+ uint8_t src_is_priv;
+ uint8_t src_is_glob;
+ uint8_t resv;
+};
+
+struct sctp_laddr {
+ LIST_ENTRY(sctp_laddr) sctp_nxt_addr; /* next in list */
+ struct sctp_ifa *ifa;
+ uint32_t action; /* Used during asconf and adding
+ * if no-zero src-addr selection will
+ * not consider this address.
+ */
+ struct timeval start_time; /* time when this address was created */
+};
+
+struct sctp_block_entry {
+ int error;
+};
+
+struct sctp_timewait {
+ uint32_t tv_sec_at_expire; /* the seconds from boot to expire */
+ uint32_t v_tag; /* the vtag that can not be reused */
+ uint16_t lport; /* the local port used in vtag */
+ uint16_t rport; /* the remote port used in vtag */
+};
+
+struct sctp_tagblock {
+ LIST_ENTRY(sctp_tagblock) sctp_nxt_tagblock;
+ struct sctp_timewait vtag_block[SCTP_NUMBER_IN_VTAG_BLOCK];
+};
+
+struct sctp_epinfo {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INET
+ struct socket *udp4_tun_socket;
+#endif
+#ifdef INET6
+ struct socket *udp6_tun_socket;
+#endif
+#endif
+ struct sctpasochead *sctp_asochash;
+ u_long hashasocmark;
+
+ struct sctppcbhead *sctp_ephash;
+ u_long hashmark;
+
+ /*-
+ * The TCP model represents a substantial overhead in that we get an
+ * additional hash table to keep explicit connections in. The
+ * listening TCP endpoint will exist in the usual ephash above and
+ * accept only INIT's. It will be incapable of sending off an INIT.
+ * When a dg arrives we must look in the normal ephash. If we find a
+ * TCP endpoint that will tell us to go to the specific endpoint
+ * hash and re-hash to find the right assoc/socket. If we find a UDP
+ * model socket we then must complete the lookup. If this fails,
+ * i.e. no association can be found then we must continue to see if
+ * a sctp_peeloff()'d socket is in the tcpephash (a spun off socket
+ * acts like a TCP model connected socket).
+ */
+ struct sctppcbhead *sctp_tcpephash;
+ u_long hashtcpmark;
+ uint32_t hashtblsize;
+
+ struct sctp_vrflist *sctp_vrfhash;
+ u_long hashvrfmark;
+
+ struct sctp_ifnlist *vrf_ifn_hash;
+ u_long vrf_ifn_hashmark;
+
+ struct sctppcbhead listhead;
+ struct sctpladdr addr_wq;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct inpcbhead inplisthead;
+ struct inpcbinfo sctbinfo;
+#endif
+ /* ep zone info */
+ sctp_zone_t ipi_zone_ep;
+ sctp_zone_t ipi_zone_asoc;
+ sctp_zone_t ipi_zone_laddr;
+ sctp_zone_t ipi_zone_net;
+ sctp_zone_t ipi_zone_chunk;
+ sctp_zone_t ipi_zone_readq;
+ sctp_zone_t ipi_zone_strmoq;
+ sctp_zone_t ipi_zone_asconf;
+ sctp_zone_t ipi_zone_asconf_ack;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct rwlock ipi_ep_mtx;
+ struct mtx ipi_iterator_wq_mtx;
+ struct rwlock ipi_addr_mtx;
+ struct mtx ipi_pktlog_mtx;
+ struct mtx wq_addr_mtx;
+#elif defined(SCTP_PROCESS_LEVEL_LOCKS)
+ userland_rwlock_t ipi_ep_mtx;
+ userland_rwlock_t ipi_addr_mtx;
+ userland_mutex_t ipi_count_mtx;
+ userland_mutex_t ipi_pktlog_mtx;
+ userland_mutex_t wq_addr_mtx;
+#elif defined(__APPLE__) && !defined(__Userspace__)
+#ifdef _KERN_LOCKS_H_
+ lck_mtx_t *ipi_addr_mtx;
+ lck_mtx_t *ipi_count_mtx;
+ lck_mtx_t *ipi_pktlog_mtx;
+ lck_mtx_t *logging_mtx;
+ lck_mtx_t *wq_addr_mtx;
+#else
+ void *ipi_count_mtx;
+ void *logging_mtx;
+#endif /* _KERN_LOCKS_H_ */
+#elif defined(_WIN32) && !defined(__Userspace__)
+ struct rwlock ipi_ep_lock;
+ struct rwlock ipi_addr_lock;
+ struct spinlock ipi_pktlog_mtx;
+ struct rwlock wq_addr_mtx;
+#elif defined(__Userspace__)
+#endif
+ uint32_t ipi_count_ep;
+
+ /* assoc/tcb zone info */
+ uint32_t ipi_count_asoc;
+
+ /* local addrlist zone info */
+ uint32_t ipi_count_laddr;
+
+ /* remote addrlist zone info */
+ uint32_t ipi_count_raddr;
+
+ /* chunk structure list for output */
+ uint32_t ipi_count_chunk;
+
+ /* socket queue zone info */
+ uint32_t ipi_count_readq;
+
+ /* socket queue zone info */
+ uint32_t ipi_count_strmoq;
+
+ /* Number of vrfs */
+ uint32_t ipi_count_vrfs;
+
+ /* Number of ifns */
+ uint32_t ipi_count_ifns;
+
+ /* Number of ifas */
+ uint32_t ipi_count_ifas;
+
+ /* system wide number of free chunks hanging around */
+ uint32_t ipi_free_chunks;
+ uint32_t ipi_free_strmoq;
+
+ struct sctpvtaghead vtag_timewait[SCTP_STACK_VTAG_HASH_SIZE];
+
+ /* address work queue handling */
+ struct sctp_timer addr_wq_timer;
+
+#if defined(_SCTP_NEEDS_CALLOUT_) || defined(_USER_SCTP_NEEDS_CALLOUT_)
+ struct calloutlist callqueue;
+#endif
+};
+
+struct sctp_base_info {
+ /* All static structures that
+ * anchor the system must be here.
+ */
+ struct sctp_epinfo sctppcbinfo;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ struct sctpstat *sctpstat;
+#else
+ struct sctpstat sctpstat;
+#endif
+#else
+ struct sctpstat sctpstat;
+#endif
+ struct sctp_sysctl sctpsysctl;
+ uint8_t first_time;
+ char sctp_pcb_initialized;
+#if defined(SCTP_PACKET_LOGGING)
+ int packet_log_writers;
+ int packet_log_end;
+ uint8_t packet_log_buffer[SCTP_PACKET_LOG_SIZE];
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ eventhandler_tag eh_tag;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ int sctp_main_timer_ticks;
+#endif
+#if defined(__Userspace__)
+ userland_mutex_t timer_mtx;
+ userland_thread_t timer_thread;
+ int timer_thread_should_exit;
+ int iterator_thread_started;
+ int timer_thread_started;
+#if !defined(_WIN32)
+ pthread_mutexattr_t mtx_attr;
+ pthread_rwlockattr_t rwlock_attr;
+#if defined(INET) || defined(INET6)
+ int userspace_route;
+ userland_thread_t recvthreadroute;
+#endif
+#endif
+#ifdef INET
+#if defined(_WIN32) && !defined(__MINGW32__)
+ SOCKET userspace_rawsctp;
+ SOCKET userspace_udpsctp;
+#else
+ int userspace_rawsctp;
+ int userspace_udpsctp;
+#endif
+ userland_thread_t recvthreadraw;
+ userland_thread_t recvthreadudp;
+#endif
+#ifdef INET6
+#if defined(_WIN32) && !defined(__MINGW32__)
+ SOCKET userspace_rawsctp6;
+ SOCKET userspace_udpsctp6;
+#else
+ int userspace_rawsctp6;
+ int userspace_udpsctp6;
+#endif
+ userland_thread_t recvthreadraw6;
+ userland_thread_t recvthreadudp6;
+#endif
+ int (*conn_output)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df);
+ void (*debug_printf)(const char *format, ...);
+ int crc32c_offloaded;
+#endif
+};
+
+/*-
+ * Here we have all the relevant information for each SCTP entity created. We
+ * will need to modify this as approprate. We also need to figure out how to
+ * access /dev/random.
+ */
+struct sctp_pcb {
+ unsigned int time_of_secret_change; /* number of seconds from
+ * timeval.tv_sec */
+ uint32_t secret_key[SCTP_HOW_MANY_SECRETS][SCTP_NUMBER_OF_SECRETS];
+ unsigned int size_of_a_cookie;
+
+ uint32_t sctp_timeoutticks[SCTP_NUM_TMRS];
+ uint32_t sctp_minrto;
+ uint32_t sctp_maxrto;
+ uint32_t initial_rto;
+ uint32_t initial_init_rto_max;
+
+ unsigned int sctp_sack_freq;
+ uint32_t sctp_sws_sender;
+ uint32_t sctp_sws_receiver;
+
+ uint32_t sctp_default_cc_module;
+ uint32_t sctp_default_ss_module;
+ /* authentication related fields */
+ struct sctp_keyhead shared_keys;
+ sctp_auth_chklist_t *local_auth_chunks;
+ sctp_hmaclist_t *local_hmacs;
+ uint16_t default_keyid;
+ uint32_t default_mtu;
+
+ /* various thresholds */
+ /* Max times I will init at a guy */
+ uint16_t max_init_times;
+
+ /* Max times I will send before we consider someone dead */
+ uint16_t max_send_times;
+
+ uint16_t def_net_failure;
+
+ uint16_t def_net_pf_threshold;
+
+ /* number of streams to pre-open on a association */
+ uint16_t pre_open_stream_count;
+ uint16_t max_open_streams_intome;
+
+ /* random number generator */
+ uint32_t random_counter;
+ uint8_t random_numbers[SCTP_SIGNATURE_ALOC_SIZE];
+ uint8_t random_store[SCTP_SIGNATURE_ALOC_SIZE];
+
+ /*
+ * This timer is kept running per endpoint. When it fires it will
+ * change the secret key. The default is once a hour
+ */
+ struct sctp_timer signature_change;
+
+ uint32_t def_cookie_life;
+ /* defaults to 0 */
+ uint32_t auto_close_time;
+ uint32_t initial_sequence_debug;
+ uint32_t adaptation_layer_indicator;
+ uint8_t adaptation_layer_indicator_provided;
+ uint32_t store_at;
+ uint32_t max_burst;
+ uint32_t fr_max_burst;
+#ifdef INET6
+ uint32_t default_flowlabel;
+#endif
+ uint8_t default_dscp;
+ char current_secret_number;
+ char last_secret_number;
+ uint16_t port; /* remote UDP encapsulation port */
+};
+
+#ifndef SCTP_ALIGNMENT
+#define SCTP_ALIGNMENT 32
+#endif
+
+#ifndef SCTP_ALIGNM1
+#define SCTP_ALIGNM1 (SCTP_ALIGNMENT-1)
+#endif
+
+#define sctp_lport ip_inp.inp.inp_lport
+
+struct sctp_pcbtsn_rlog {
+ uint32_t vtag;
+ uint16_t strm;
+ uint16_t seq;
+ uint16_t sz;
+ uint16_t flgs;
+};
+#define SCTP_READ_LOG_SIZE 135 /* we choose the number to make a pcb a page */
+
+struct sctp_inpcb {
+ /*-
+ * put an inpcb in front of it all, kind of a waste but we need to
+ * for compatibility with all the other stuff.
+ */
+ union {
+ struct inpcb inp;
+ char align[(sizeof(struct inpcb) + SCTP_ALIGNM1) &
+ ~SCTP_ALIGNM1];
+ } ip_inp;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ /* leave some space in case i386 inpcb is bigger than ppc */
+ uint8_t padding[128];
+#endif
+
+ /* Socket buffer lock protects read_queue and of course sb_cc */
+ struct sctp_readhead read_queue;
+
+ LIST_ENTRY(sctp_inpcb) sctp_list; /* lists all endpoints */
+ /* hash of all endpoints for model */
+ LIST_ENTRY(sctp_inpcb) sctp_hash;
+ /* count of local addresses bound, 0 if bound all */
+ int laddr_count;
+
+ /* list of addrs in use by the EP, NULL if bound-all */
+ struct sctpladdr sctp_addr_list;
+ /* used for source address selection rotation when we are subset bound */
+ struct sctp_laddr *next_addr_touse;
+
+ /* back pointer to our socket */
+ struct socket *sctp_socket;
+ uint64_t sctp_features; /* Feature flags */
+ uint32_t sctp_flags; /* INP state flag set */
+ uint32_t sctp_mobility_features; /* Mobility Feature flags */
+ struct sctp_pcb sctp_ep;/* SCTP ep data */
+ /* head of the hash of all associations */
+ struct sctpasochead *sctp_tcbhash;
+ u_long sctp_hashmark;
+ /* head of the list of all associations */
+ struct sctpasochead sctp_asoc_list;
+#ifdef SCTP_TRACK_FREED_ASOCS
+ struct sctpasochead sctp_asoc_free_list;
+#endif
+ uint32_t sctp_frag_point;
+ uint32_t partial_delivery_point;
+ uint32_t sctp_context;
+ uint32_t max_cwnd;
+ uint8_t local_strreset_support;
+ uint32_t sctp_cmt_on_off;
+ uint8_t ecn_supported;
+ uint8_t prsctp_supported;
+ uint8_t auth_supported;
+ uint8_t idata_supported;
+ uint8_t asconf_supported;
+ uint8_t reconfig_supported;
+ uint8_t nrsack_supported;
+ uint8_t pktdrop_supported;
+ uint8_t rcv_edmid;
+ struct sctp_nonpad_sndrcvinfo def_send;
+ /*-
+ * These three are here for the sosend_dgram
+ * (pkt, pkt_last and control).
+ * routine. However, I don't think anyone in
+ * the current FreeBSD kernel calls this. So
+ * they are candidates with sctp_sendm for
+ * de-supporting.
+ */
+ struct mbuf *pkt, *pkt_last;
+ struct mbuf *control;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct mtx inp_mtx;
+ struct mtx inp_create_mtx;
+ struct mtx inp_rdata_mtx;
+ int32_t refcount;
+#elif defined(SCTP_PROCESS_LEVEL_LOCKS)
+ userland_mutex_t inp_mtx;
+ userland_mutex_t inp_create_mtx;
+ userland_mutex_t inp_rdata_mtx;
+ int32_t refcount;
+#elif defined(__APPLE__) && !defined(__Userspace__)
+#if defined(SCTP_APPLE_RWLOCK)
+ lck_rw_t *inp_mtx;
+#else
+ lck_mtx_t *inp_mtx;
+#endif
+ lck_mtx_t *inp_create_mtx;
+ lck_mtx_t *inp_rdata_mtx;
+#elif defined(_WIN32) && !defined(__Userspace__)
+ struct rwlock inp_lock;
+ struct spinlock inp_create_lock;
+ struct spinlock inp_rdata_lock;
+ int32_t refcount;
+#elif defined(__Userspace__)
+ int32_t refcount;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ int32_t refcount;
+
+ uint32_t lock_caller1;
+ uint32_t lock_caller2;
+ uint32_t lock_caller3;
+ uint32_t unlock_caller1;
+ uint32_t unlock_caller2;
+ uint32_t unlock_caller3;
+ uint32_t getlock_caller1;
+ uint32_t getlock_caller2;
+ uint32_t getlock_caller3;
+ uint32_t gen_count;
+ uint32_t lock_gen_count;
+ uint32_t unlock_gen_count;
+ uint32_t getlock_gen_count;
+
+ uint32_t i_am_here_file;
+ uint32_t i_am_here_line;
+#endif
+ uint32_t def_vrf_id;
+ uint16_t fibnum;
+#ifdef SCTP_MVRF
+ uint32_t *m_vrf_ids;
+ uint32_t num_vrfs;
+ uint32_t vrf_size;
+#endif
+ uint32_t total_sends;
+ uint32_t total_recvs;
+ uint32_t last_abort_code;
+ uint32_t total_nospaces;
+ struct sctpasochead *sctp_asocidhash;
+ u_long hashasocidmark;
+ uint32_t sctp_associd_counter;
+
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ struct sctp_pcbtsn_rlog readlog[SCTP_READ_LOG_SIZE];
+ uint32_t readlog_index;
+#endif
+#if defined(__Userspace__)
+ void *ulp_info;
+ int (*recv_callback)(struct socket *, union sctp_sockstore, void *, size_t,
+ struct sctp_rcvinfo, int, void *);
+ uint32_t send_sb_threshold;
+ int (*send_callback)(struct socket *, uint32_t, void *);
+#endif
+};
+
+#if defined(__Userspace__)
+int register_recv_cb (struct socket *,
+ int (*)(struct socket *, union sctp_sockstore, void *, size_t,
+ struct sctp_rcvinfo, int, void *));
+int register_send_cb (struct socket *, uint32_t, int (*)(struct socket *, uint32_t, void *));
+int register_ulp_info (struct socket *, void *);
+int retrieve_ulp_info (struct socket *, void **);
+
+#endif
+struct sctp_tcb {
+ struct socket *sctp_socket; /* back pointer to socket */
+ struct sctp_inpcb *sctp_ep; /* back pointer to ep */
+ LIST_ENTRY(sctp_tcb) sctp_tcbhash; /* next link in hash
+ * table */
+ LIST_ENTRY(sctp_tcb) sctp_tcblist; /* list of all of the
+ * TCB's */
+ LIST_ENTRY(sctp_tcb) sctp_tcbasocidhash; /* next link in asocid
+ * hash table
+ */
+ LIST_ENTRY(sctp_tcb) sctp_asocs; /* vtag hash list */
+ struct sctp_block_entry *block_entry; /* pointer locked by socket
+ * send buffer */
+ struct sctp_association asoc;
+ /*-
+ * freed_by_sorcv_sincelast is protected by the sockbuf_lock NOT the
+ * tcb_lock. Its special in this way to help avoid extra mutex calls
+ * in the reading of data.
+ */
+ uint32_t freed_by_sorcv_sincelast;
+ uint32_t total_sends;
+ uint32_t total_recvs;
+ int freed_from_where;
+ uint16_t rport; /* remote port in network format */
+ uint16_t resv;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct mtx tcb_mtx;
+#elif defined(SCTP_PROCESS_LEVEL_LOCKS)
+ userland_mutex_t tcb_mtx;
+#elif defined(__APPLE__) && !defined(__Userspace__)
+ lck_mtx_t* tcb_mtx;
+#elif defined(_WIN32) && !defined(__Userspace__)
+ struct spinlock tcb_lock;
+#elif defined(__Userspace__)
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ uint32_t caller1;
+ uint32_t caller2;
+ uint32_t caller3;
+#endif
+};
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/sctp_lock_bsd.h>
+#elif defined(__APPLE__) && !defined(__Userspace__)
+/*
+ * Apple MacOS X 10.4 "Tiger"
+ */
+
+#include <netinet/sctp_lock_apple_fg.h>
+
+#elif defined(SCTP_PROCESS_LEVEL_LOCKS)
+
+#include <netinet/sctp_process_lock.h>
+
+#elif defined(_WIN32) && !defined(__Userspace__)
+
+#include <netinet/sctp_lock_windows.h>
+
+#elif defined(__Userspace__)
+
+#include <netinet/sctp_lock_userspace.h>
+
+#else
+/*
+ * Pre-5.x FreeBSD, and others.
+ */
+#include <netinet/sctp_lock_empty.h>
+#endif
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+/* Attention Julian, this is the extern that
+ * goes with the base info. sctp_pcb.c has
+ * the real definition.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+VNET_DECLARE(struct sctp_base_info, system_base_info);
+#else
+extern struct sctp_base_info system_base_info;
+#endif
+
+#ifdef INET6
+int SCTP6_ARE_ADDR_EQUAL(struct sockaddr_in6 *a, struct sockaddr_in6 *b);
+#endif
+
+void sctp_fill_pcbinfo(struct sctp_pcbinfo *);
+
+struct sctp_ifn *
+sctp_find_ifn(void *ifn, uint32_t ifn_index);
+
+struct sctp_vrf *sctp_allocate_vrf(int vrfid);
+struct sctp_vrf *sctp_find_vrf(uint32_t vrfid);
+void sctp_free_vrf(struct sctp_vrf *vrf);
+
+/*-
+ * Change address state, can be used if
+ * O/S supports telling transports about
+ * changes to IFA/IFN's (link layer triggers).
+ * If a ifn goes down, we will do src-addr-selection
+ * and NOT use that, as a source address. This does
+ * not stop the routing system from routing out
+ * that interface, but we won't put it as a source.
+ */
+void sctp_mark_ifa_addr_down(uint32_t vrf_id, struct sockaddr *addr, const char *if_name, uint32_t ifn_index);
+void sctp_mark_ifa_addr_up(uint32_t vrf_id, struct sockaddr *addr, const char *if_name, uint32_t ifn_index);
+
+struct sctp_ifa *
+sctp_add_addr_to_vrf(uint32_t vrfid,
+ void *ifn, uint32_t ifn_index, uint32_t ifn_type,
+ const char *if_name,
+ void *ifa, struct sockaddr *addr, uint32_t ifa_flags,
+ int dynamic_add);
+
+void sctp_update_ifn_mtu(uint32_t ifn_index, uint32_t mtu);
+
+void sctp_free_ifn(struct sctp_ifn *sctp_ifnp);
+void sctp_free_ifa(struct sctp_ifa *sctp_ifap);
+
+void sctp_del_addr_from_vrf(uint32_t vrfid, struct sockaddr *addr,
+ uint32_t ifn_index, const char *if_name);
+
+struct sctp_nets *sctp_findnet(struct sctp_tcb *, struct sockaddr *);
+
+struct sctp_inpcb *sctp_pcb_findep(struct sockaddr *, int, int, uint32_t);
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+int
+sctp_inpcb_bind(struct socket *, struct sockaddr *,
+ struct sctp_ifa *, struct thread *);
+int
+sctp_inpcb_bind_locked(struct sctp_inpcb *, struct sockaddr *,
+ struct sctp_ifa *, struct thread *);
+#elif defined(_WIN32) && !defined(__Userspace__)
+int
+sctp_inpcb_bind(struct socket *, struct sockaddr *,
+ struct sctp_ifa *, PKTHREAD);
+int
+sctp_inpcb_bind_locked(struct sctp_inpcb *, struct sockaddr *,
+ struct sctp_ifa *, PKTHREAD);
+#else
+/* struct proc is a dummy for __Userspace__ */
+int
+sctp_inpcb_bind(struct socket *, struct sockaddr *,
+ struct sctp_ifa *, struct proc *);
+int
+sctp_inpcb_bind_locked(struct sctp_inpcb *, struct sockaddr *,
+ struct sctp_ifa *, struct proc *);
+#endif
+
+struct sctp_tcb *
+sctp_findassociation_addr(struct mbuf *, int,
+ struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, struct sctp_chunkhdr *, struct sctp_inpcb **,
+ struct sctp_nets **, uint32_t vrf_id);
+
+struct sctp_tcb *
+sctp_findassociation_addr_sa(struct sockaddr *,
+ struct sockaddr *, struct sctp_inpcb **, struct sctp_nets **, int, uint32_t);
+
+void
+sctp_move_pcb_and_assoc(struct sctp_inpcb *, struct sctp_inpcb *,
+ struct sctp_tcb *);
+
+/*-
+ * For this call ep_addr, the to is the destination endpoint address of the
+ * peer (relative to outbound). The from field is only used if the TCP model
+ * is enabled and helps distingush amongst the subset bound (non-boundall).
+ * The TCP model MAY change the actual ep field, this is why it is passed.
+ */
+struct sctp_tcb *
+sctp_findassociation_ep_addr(struct sctp_inpcb **,
+ struct sockaddr *, struct sctp_nets **, struct sockaddr *,
+ struct sctp_tcb *);
+
+struct sctp_tcb *
+sctp_findasoc_ep_asocid_locked(struct sctp_inpcb *inp, sctp_assoc_t asoc_id, int want_lock);
+
+struct sctp_tcb *
+sctp_findassociation_ep_asocid(struct sctp_inpcb *,
+ sctp_assoc_t, int);
+
+struct sctp_tcb *
+sctp_findassociation_ep_asconf(struct mbuf *, int, struct sockaddr *,
+ struct sctphdr *, struct sctp_inpcb **, struct sctp_nets **, uint32_t vrf_id);
+
+int sctp_inpcb_alloc(struct socket *so, uint32_t vrf_id);
+
+int sctp_is_address_on_local_host(struct sockaddr *addr, uint32_t vrf_id);
+
+void sctp_inpcb_free(struct sctp_inpcb *, int, int);
+
+#define SCTP_DONT_INITIALIZE_AUTH_PARAMS 0
+#define SCTP_INITIALIZE_AUTH_PARAMS 1
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *, struct sockaddr *,
+ int *, uint32_t, uint32_t, uint32_t, uint16_t, uint16_t,
+ struct thread *, int);
+struct sctp_tcb *
+sctp_aloc_assoc_connected(struct sctp_inpcb *, struct sockaddr *,
+ int *, uint32_t, uint32_t, uint32_t, uint16_t, uint16_t,
+ struct thread *, int);
+#elif defined(_WIN32) && !defined(__Userspace__)
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *, struct sockaddr *, int *, uint32_t,
+ uint32_t, uint32_t, uint16_t, uint16_t, PKTHREAD, int);
+struct sctp_tcb *
+sctp_aloc_assoc_connected(struct sctp_inpcb *, struct sockaddr *, int *, uint32_t,
+ uint32_t, uint32_t, uint16_t, uint16_t, PKTHREAD, int);
+#else
+/* proc will be NULL for __Userspace__ */
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *, struct sockaddr *, int *, uint32_t,
+ uint32_t, uint32_t, uint16_t, uint16_t, struct proc *, int);
+struct sctp_tcb *
+sctp_aloc_assoc_connected(struct sctp_inpcb *, struct sockaddr *, int *, uint32_t,
+ uint32_t, uint32_t, uint16_t, uint16_t, struct proc *, int);
+#endif
+
+int sctp_free_assoc(struct sctp_inpcb *, struct sctp_tcb *, int, int);
+
+void sctp_add_local_addr_ep(struct sctp_inpcb *, struct sctp_ifa *, uint32_t);
+
+void sctp_del_local_addr_ep(struct sctp_inpcb *, struct sctp_ifa *);
+
+int sctp_add_remote_addr(struct sctp_tcb *, struct sockaddr *, struct sctp_nets **, uint16_t, int, int);
+
+void sctp_remove_net(struct sctp_tcb *, struct sctp_nets *);
+
+int sctp_del_remote_addr(struct sctp_tcb *, struct sockaddr *);
+
+#if defined(__Userspace__)
+void sctp_pcb_init(int);
+#else
+void sctp_pcb_init(void);
+#endif
+
+void sctp_pcb_finish(void);
+
+void sctp_add_local_addr_restricted(struct sctp_tcb *, struct sctp_ifa *);
+void sctp_del_local_addr_restricted(struct sctp_tcb *, struct sctp_ifa *);
+
+int
+sctp_load_addresses_from_init(struct sctp_tcb *, struct mbuf *, int, int,
+ struct sockaddr *, struct sockaddr *, struct sockaddr *, uint16_t);
+
+int
+sctp_set_primary_addr(struct sctp_tcb *, struct sockaddr *,
+ struct sctp_nets *);
+
+bool
+sctp_is_vtag_good(uint32_t, uint16_t lport, uint16_t rport, struct timeval *);
+
+int sctp_destination_is_reachable(struct sctp_tcb *, struct sockaddr *);
+
+int sctp_swap_inpcb_for_listen(struct sctp_inpcb *inp);
+
+void sctp_clean_up_stream(struct sctp_tcb *stcb, struct sctp_readhead *rh);
+
+void
+sctp_pcb_add_flags(struct sctp_inpcb *, uint32_t);
+
+/*-
+ * Null in last arg inpcb indicate run on ALL ep's. Specific inp in last arg
+ * indicates run on ONLY assoc's of the specified endpoint.
+ */
+int
+sctp_initiate_iterator(inp_func inpf,
+ asoc_func af,
+ inp_func inpe,
+ uint32_t, uint32_t,
+ uint32_t, void *,
+ uint32_t,
+ end_func ef,
+ struct sctp_inpcb *,
+ uint8_t co_off);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP_MCORE_INPUT) && defined(SMP)
+void
+sctp_queue_to_mcore(struct mbuf *m, int off, int cpu_to_use);
+
+#endif
+#endif
+
+#endif /* _KERNEL */
+#endif /* !__sctp_pcb_h__ */
diff --git a/netwerk/sctp/src/netinet/sctp_peeloff.c b/netwerk/sctp/src/netinet/sctp_peeloff.c
new file mode 100644
index 0000000000..2e74db048b
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_peeloff.c
@@ -0,0 +1,297 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctp_peeloff.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_auth.h>
+
+int
+sctp_can_peel_off(struct socket *head, sctp_assoc_t assoc_id)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ uint32_t state;
+
+ if (head == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, EBADF);
+ return (EBADF);
+ }
+ inp = (struct sctp_inpcb *)head->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, EFAULT);
+ return (EFAULT);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, EOPNOTSUPP);
+ return (EOPNOTSUPP);
+ }
+ stcb = sctp_findassociation_ep_asocid(inp, assoc_id, 1);
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_PEELOFF, ENOENT);
+ return (ENOENT);
+ }
+ state = SCTP_GET_STATE(stcb);
+ if ((state == SCTP_STATE_EMPTY) ||
+ (state == SCTP_STATE_INUSE)) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_PEELOFF, ENOTCONN);
+ return (ENOTCONN);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ /* We are clear to peel this one off */
+ return (0);
+}
+
+int
+sctp_do_peeloff(struct socket *head, struct socket *so, sctp_assoc_t assoc_id)
+{
+ struct sctp_inpcb *inp, *n_inp;
+ struct sctp_tcb *stcb;
+ uint32_t state;
+
+ inp = (struct sctp_inpcb *)head->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, EFAULT);
+ return (EFAULT);
+ }
+ stcb = sctp_findassociation_ep_asocid(inp, assoc_id, 1);
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, ENOTCONN);
+ return (ENOTCONN);
+ }
+
+ state = SCTP_GET_STATE(stcb);
+ if ((state == SCTP_STATE_EMPTY) ||
+ (state == SCTP_STATE_INUSE)) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, ENOTCONN);
+ return (ENOTCONN);
+ }
+
+ n_inp = (struct sctp_inpcb *)so->so_pcb;
+ n_inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE |
+ SCTP_PCB_FLAGS_CONNECTED |
+ SCTP_PCB_FLAGS_IN_TCPPOOL | /* Turn on Blocking IO */
+ (SCTP_PCB_COPY_FLAGS & inp->sctp_flags));
+ n_inp->sctp_socket = so;
+ n_inp->sctp_features = inp->sctp_features;
+ n_inp->sctp_mobility_features = inp->sctp_mobility_features;
+ n_inp->sctp_frag_point = inp->sctp_frag_point;
+ n_inp->sctp_cmt_on_off = inp->sctp_cmt_on_off;
+ n_inp->ecn_supported = inp->ecn_supported;
+ n_inp->prsctp_supported = inp->prsctp_supported;
+ n_inp->auth_supported = inp->auth_supported;
+ n_inp->asconf_supported = inp->asconf_supported;
+ n_inp->reconfig_supported = inp->reconfig_supported;
+ n_inp->nrsack_supported = inp->nrsack_supported;
+ n_inp->pktdrop_supported = inp->pktdrop_supported;
+ n_inp->partial_delivery_point = inp->partial_delivery_point;
+ n_inp->sctp_context = inp->sctp_context;
+ n_inp->max_cwnd = inp->max_cwnd;
+ n_inp->local_strreset_support = inp->local_strreset_support;
+ /* copy in the authentication parameters from the original endpoint */
+ if (n_inp->sctp_ep.local_hmacs)
+ sctp_free_hmaclist(n_inp->sctp_ep.local_hmacs);
+ n_inp->sctp_ep.local_hmacs =
+ sctp_copy_hmaclist(inp->sctp_ep.local_hmacs);
+ if (n_inp->sctp_ep.local_auth_chunks)
+ sctp_free_chunklist(n_inp->sctp_ep.local_auth_chunks);
+ n_inp->sctp_ep.local_auth_chunks =
+ sctp_copy_chunklist(inp->sctp_ep.local_auth_chunks);
+ (void)sctp_copy_skeylist(&inp->sctp_ep.shared_keys,
+ &n_inp->sctp_ep.shared_keys);
+#if defined(__Userspace__)
+ n_inp->ulp_info = inp->ulp_info;
+ n_inp->recv_callback = inp->recv_callback;
+ n_inp->send_callback = inp->send_callback;
+ n_inp->send_sb_threshold = inp->send_sb_threshold;
+#endif
+ /*
+ * Now we must move it from one hash table to another and get the
+ * stcb in the right place.
+ */
+ sctp_move_pcb_and_assoc(inp, n_inp, stcb);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ sctp_pull_off_control_to_new_inp(inp, n_inp, stcb, SBL_WAIT);
+#else
+ sctp_pull_off_control_to_new_inp(inp, n_inp, stcb, M_WAITOK);
+#endif
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+
+ return (0);
+}
+
+#if defined(HAVE_SCTP_PEELOFF_SOCKOPT)
+struct socket *
+sctp_get_peeloff(struct socket *head, sctp_assoc_t assoc_id, int *error)
+{
+ struct socket *newso;
+ struct sctp_inpcb *inp, *n_inp;
+ struct sctp_tcb *stcb;
+
+ SCTPDBG(SCTP_DEBUG_PEEL1, "SCTP peel-off called\n");
+ inp = (struct sctp_inpcb *)head->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, EFAULT);
+ *error = EFAULT;
+ return (NULL);
+ }
+ stcb = sctp_findassociation_ep_asocid(inp, assoc_id, 1);
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, ENOTCONN);
+ *error = ENOTCONN;
+ return (NULL);
+ }
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_SET(head->so_vnet);
+#endif
+ newso = sonewconn(head, SS_ISCONNECTED
+#if defined(__APPLE__) && !defined(__Userspace__)
+ , NULL
+#endif
+ );
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+#endif
+ if (newso == NULL) {
+ SCTPDBG(SCTP_DEBUG_PEEL1, "sctp_peeloff:sonewconn failed\n");
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_PEELOFF, ENOMEM);
+ *error = ENOMEM;
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (NULL);
+
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ else {
+ SCTP_SOCKET_LOCK(newso, 1);
+ }
+#endif
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ n_inp = (struct sctp_inpcb *)newso->so_pcb;
+ SOCK_LOCK(head);
+ n_inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE |
+ SCTP_PCB_FLAGS_CONNECTED |
+ SCTP_PCB_FLAGS_IN_TCPPOOL | /* Turn on Blocking IO */
+ (SCTP_PCB_COPY_FLAGS & inp->sctp_flags));
+ n_inp->sctp_features = inp->sctp_features;
+ n_inp->sctp_frag_point = inp->sctp_frag_point;
+ n_inp->sctp_cmt_on_off = inp->sctp_cmt_on_off;
+ n_inp->ecn_supported = inp->ecn_supported;
+ n_inp->prsctp_supported = inp->prsctp_supported;
+ n_inp->auth_supported = inp->auth_supported;
+ n_inp->asconf_supported = inp->asconf_supported;
+ n_inp->reconfig_supported = inp->reconfig_supported;
+ n_inp->nrsack_supported = inp->nrsack_supported;
+ n_inp->pktdrop_supported = inp->pktdrop_supported;
+ n_inp->partial_delivery_point = inp->partial_delivery_point;
+ n_inp->sctp_context = inp->sctp_context;
+ n_inp->max_cwnd = inp->max_cwnd;
+ n_inp->local_strreset_support = inp->local_strreset_support;
+ n_inp->inp_starting_point_for_iterator = NULL;
+#if defined(__Userspace__)
+ n_inp->ulp_info = inp->ulp_info;
+ n_inp->recv_callback = inp->recv_callback;
+ n_inp->send_callback = inp->send_callback;
+ n_inp->send_sb_threshold = inp->send_sb_threshold;
+#endif
+
+ /* copy in the authentication parameters from the original endpoint */
+ if (n_inp->sctp_ep.local_hmacs)
+ sctp_free_hmaclist(n_inp->sctp_ep.local_hmacs);
+ n_inp->sctp_ep.local_hmacs =
+ sctp_copy_hmaclist(inp->sctp_ep.local_hmacs);
+ if (n_inp->sctp_ep.local_auth_chunks)
+ sctp_free_chunklist(n_inp->sctp_ep.local_auth_chunks);
+ n_inp->sctp_ep.local_auth_chunks =
+ sctp_copy_chunklist(inp->sctp_ep.local_auth_chunks);
+ (void)sctp_copy_skeylist(&inp->sctp_ep.shared_keys,
+ &n_inp->sctp_ep.shared_keys);
+
+ n_inp->sctp_socket = newso;
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) {
+ sctp_feature_off(n_inp, SCTP_PCB_FLAGS_AUTOCLOSE);
+ n_inp->sctp_ep.auto_close_time = 0;
+ sctp_timer_stop(SCTP_TIMER_TYPE_AUTOCLOSE, n_inp, stcb, NULL,
+ SCTP_FROM_SCTP_PEELOFF + SCTP_LOC_1);
+ }
+ /* Turn off any non-blocking semantic. */
+ SOCK_LOCK(newso);
+ SCTP_CLEAR_SO_NBIO(newso);
+ newso->so_state |= SS_ISCONNECTED;
+ SOCK_UNLOCK(newso);
+ /* We remove it right away */
+
+#ifdef SCTP_LOCK_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) {
+ sctp_log_lock(inp, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_SOCK);
+ }
+#endif
+ TAILQ_REMOVE(&head->so_comp, newso, so_list);
+ head->so_qlen--;
+ SOCK_UNLOCK(head);
+ /*
+ * Now we must move it from one hash table to another and get the
+ * stcb in the right place.
+ */
+ sctp_move_pcb_and_assoc(inp, n_inp, stcb);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ /*
+ * And now the final hack. We move data in the pending side i.e.
+ * head to the new socket buffer. Let the GRUBBING begin :-0
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ sctp_pull_off_control_to_new_inp(inp, n_inp, stcb, SBL_WAIT);
+#else
+ sctp_pull_off_control_to_new_inp(inp, n_inp, stcb, M_WAITOK);
+#endif
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (newso);
+}
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_peeloff.h b/netwerk/sctp/src/netinet/sctp_peeloff.h
new file mode 100644
index 0000000000..3011fa3517
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_peeloff.h
@@ -0,0 +1,65 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_PEELOFF_H_
+#define _NETINET_SCTP_PEELOFF_H_
+#if defined(HAVE_SCTP_PEELOFF_SOCKOPT)
+/* socket option peeloff */
+struct sctp_peeloff_opt {
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ int s;
+#else
+ HANDLE s;
+#endif
+ sctp_assoc_t assoc_id;
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ int new_sd;
+#else
+ HANDLE new_sd;
+#endif
+};
+#endif /* HAVE_SCTP_PEELOFF_SOCKOPT */
+#if defined(_KERNEL)
+int sctp_can_peel_off(struct socket *, sctp_assoc_t);
+int sctp_do_peeloff(struct socket *, struct socket *, sctp_assoc_t);
+#if defined(HAVE_SCTP_PEELOFF_SOCKOPT)
+struct socket *sctp_get_peeloff(struct socket *, sctp_assoc_t, int *);
+int sctp_peeloff_option(struct proc *p, struct sctp_peeloff_opt *peeloff);
+#endif /* HAVE_SCTP_PEELOFF_SOCKOPT */
+#endif /* _KERNEL */
+#if defined(__Userspace__)
+int sctp_can_peel_off(struct socket *, sctp_assoc_t);
+int sctp_do_peeloff(struct socket *, struct socket *, sctp_assoc_t);
+#endif /* __Userspace__ */
+#endif /* _NETINET_SCTP_PEELOFF_H_ */
diff --git a/netwerk/sctp/src/netinet/sctp_process_lock.h b/netwerk/sctp/src/netinet/sctp_process_lock.h
new file mode 100644
index 0000000000..feaa2c6acd
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_process_lock.h
@@ -0,0 +1,693 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2011, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2011, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef __sctp_process_lock_h__
+#define __sctp_process_lock_h__
+
+/*
+ * Need to yet define five atomic fuctions or
+ * their equivalant.
+ * - atomic_add_int(&foo, val) - add atomically the value
+ * - atomic_fetchadd_int(&foo, val) - does same as atomic_add_int
+ * but value it was is returned.
+ * - atomic_subtract_int(&foo, val) - can be made from atomic_add_int()
+ *
+ * - atomic_cmpset_int(&foo, value, newvalue) - Does a set of newvalue
+ * in foo if and only if
+ * foo is value. Returns 0
+ * on success.
+ */
+
+#ifdef SCTP_PER_SOCKET_LOCKING
+/*
+ * per socket level locking
+ */
+
+#if defined(_WIN32)
+/* Lock for INFO stuff */
+#define SCTP_INP_INFO_LOCK_INIT()
+#define SCTP_INP_INFO_RLOCK()
+#define SCTP_INP_INFO_RUNLOCK()
+#define SCTP_INP_INFO_WLOCK()
+#define SCTP_INP_INFO_WUNLOCK()
+#define SCTP_INP_INFO_LOCK_ASSERT()
+#define SCTP_INP_INFO_RLOCK_ASSERT()
+#define SCTP_INP_INFO_WLOCK_ASSERT()
+#define SCTP_INP_INFO_LOCK_DESTROY()
+#define SCTP_IPI_COUNT_INIT()
+#define SCTP_IPI_COUNT_DESTROY()
+#else
+#define SCTP_INP_INFO_LOCK_INIT()
+#define SCTP_INP_INFO_RLOCK()
+#define SCTP_INP_INFO_RUNLOCK()
+#define SCTP_INP_INFO_WLOCK()
+#define SCTP_INP_INFO_WUNLOCK()
+#define SCTP_INP_INFO_LOCK_ASSERT()
+#define SCTP_INP_INFO_RLOCK_ASSERT()
+#define SCTP_INP_INFO_WLOCK_ASSERT()
+#define SCTP_INP_INFO_LOCK_DESTROY()
+#define SCTP_IPI_COUNT_INIT()
+#define SCTP_IPI_COUNT_DESTROY()
+#endif
+
+/* Lock for INP */
+#define SCTP_INP_LOCK_INIT(_inp)
+#define SCTP_INP_LOCK_DESTROY(_inp)
+
+#define SCTP_INP_RLOCK(_inp)
+#define SCTP_INP_RUNLOCK(_inp)
+#define SCTP_INP_WLOCK(_inp)
+#define SCTP_INP_WUNLOCK(_inp)
+#define SCTP_INP_RLOCK_ASSERT(_inp)
+#define SCTP_INP_WLOCK_ASSERT(_inp)
+#define SCTP_INP_INCR_REF(_inp)
+#define SCTP_INP_DECR_REF(_inp)
+
+#define SCTP_ASOC_CREATE_LOCK_INIT(_inp)
+#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp)
+#define SCTP_ASOC_CREATE_LOCK(_inp)
+#define SCTP_ASOC_CREATE_UNLOCK(_inp)
+
+#define SCTP_INP_READ_LOCK_INIT(_inp)
+#define SCTP_INP_READ_LOCK_DESTROY(_inp)
+#define SCTP_INP_READ_LOCK(_inp)
+#define SCTP_INP_READ_UNLOCK(_inp)
+#define SCTP_INP_READ_LOCK_ASSERT(_inp)
+
+/* Lock for TCB */
+#define SCTP_TCB_LOCK_INIT(_tcb)
+#define SCTP_TCB_LOCK_DESTROY(_tcb)
+#define SCTP_TCB_LOCK(_tcb)
+#define SCTP_TCB_TRYLOCK(_tcb) 1
+#define SCTP_TCB_UNLOCK(_tcb)
+#define SCTP_TCB_UNLOCK_IFOWNED(_tcb)
+#define SCTP_TCB_LOCK_ASSERT(_tcb)
+
+#else
+/*
+ * per tcb level locking
+ */
+#define SCTP_IPI_COUNT_INIT()
+
+#if defined(_WIN32)
+#define SCTP_WQ_ADDR_INIT() \
+ InitializeCriticalSection(&SCTP_BASE_INFO(wq_addr_mtx))
+#define SCTP_WQ_ADDR_DESTROY() \
+ DeleteCriticalSection(&SCTP_BASE_INFO(wq_addr_mtx))
+#define SCTP_WQ_ADDR_LOCK() \
+ EnterCriticalSection(&SCTP_BASE_INFO(wq_addr_mtx))
+#define SCTP_WQ_ADDR_UNLOCK() \
+ LeaveCriticalSection(&SCTP_BASE_INFO(wq_addr_mtx))
+#define SCTP_WQ_ADDR_LOCK_ASSERT()
+
+#if WINVER < 0x0600
+#define SCTP_INP_INFO_LOCK_INIT() \
+ InitializeCriticalSection(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_LOCK_DESTROY() \
+ DeleteCriticalSection(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_RLOCK() \
+ EnterCriticalSection(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_TRYLOCK() \
+ TryEnterCriticalSection(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WLOCK() \
+ EnterCriticalSection(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_RUNLOCK() \
+ LeaveCriticalSection(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WUNLOCK() \
+ LeaveCriticalSection(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_LOCK_ASSERT()
+#define SCTP_INP_INFO_RLOCK_ASSERT()
+#define SCTP_INP_INFO_WLOCK_ASSERT()
+#else
+#define SCTP_INP_INFO_LOCK_INIT() \
+ InitializeSRWLock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_LOCK_DESTROY()
+#define SCTP_INP_INFO_RLOCK() \
+ AcquireSRWLockShared(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_TRYLOCK() \
+ TryAcquireSRWLockShared(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WLOCK() \
+ AcquireSRWLockExclusive(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_RUNLOCK() \
+ ReleaseSRWLockShared(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WUNLOCK() \
+ ReleaseSRWLockExclusive(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_LOCK_ASSERT()
+#define SCTP_INP_INFO_RLOCK_ASSERT()
+#define SCTP_INP_INFO_WLOCK_ASSERT()
+#endif
+
+#define SCTP_IP_PKTLOG_INIT() \
+ InitializeCriticalSection(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+#define SCTP_IP_PKTLOG_DESTROY () \
+ DeleteCriticalSection(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+#define SCTP_IP_PKTLOG_LOCK() \
+ EnterCriticalSection(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+#define SCTP_IP_PKTLOG_UNLOCK() \
+ LeaveCriticalSection(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+
+/*
+ * The INP locks we will use for locking an SCTP endpoint, so for example if
+ * we want to change something at the endpoint level for example random_store
+ * or cookie secrets we lock the INP level.
+ */
+#define SCTP_INP_READ_LOCK_INIT(_inp) \
+ InitializeCriticalSection(&(_inp)->inp_rdata_mtx)
+#define SCTP_INP_READ_LOCK_DESTROY(_inp) \
+ DeleteCriticalSection(&(_inp)->inp_rdata_mtx)
+#define SCTP_INP_READ_LOCK(_inp) \
+ EnterCriticalSection(&(_inp)->inp_rdata_mtx)
+#define SCTP_INP_READ_UNLOCK(_inp) \
+ LeaveCriticalSection(&(_inp)->inp_rdata_mtx)
+#define SCTP_INP_READ_LOCK_ASSERT(_inp)
+
+#define SCTP_INP_LOCK_INIT(_inp) \
+ InitializeCriticalSection(&(_inp)->inp_mtx)
+#define SCTP_INP_LOCK_DESTROY(_inp) \
+ DeleteCriticalSection(&(_inp)->inp_mtx)
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_INP_RLOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_INP); \
+ EnterCriticalSection(&(_inp)->inp_mtx); \
+} while (0)
+#define SCTP_INP_WLOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_INP); \
+ EnterCriticalSection(&(_inp)->inp_mtx); \
+} while (0)
+#else
+#define SCTP_INP_RLOCK(_inp) \
+ EnterCriticalSection(&(_inp)->inp_mtx)
+#define SCTP_INP_WLOCK(_inp) \
+ EnterCriticalSection(&(_inp)->inp_mtx)
+#endif
+#define SCTP_INP_RLOCK_ASSERT(_tcb)
+#define SCTP_INP_WLOCK_ASSERT(_tcb)
+
+#define SCTP_INP_INCR_REF(_inp) atomic_add_int(&((_inp)->refcount), 1)
+#define SCTP_INP_DECR_REF(_inp) atomic_subtract_int(&((_inp)->refcount), 1)
+
+#define SCTP_ASOC_CREATE_LOCK_INIT(_inp) \
+ InitializeCriticalSection(&(_inp)->inp_create_mtx)
+#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp) \
+ DeleteCriticalSection(&(_inp)->inp_create_mtx)
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_ASOC_CREATE_LOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_CREATE); \
+ EnterCriticalSection(&(_inp)->inp_create_mtx); \
+} while (0)
+#else
+#define SCTP_ASOC_CREATE_LOCK(_inp) \
+ EnterCriticalSection(&(_inp)->inp_create_mtx)
+#endif
+
+#define SCTP_INP_RUNLOCK(_inp) \
+ LeaveCriticalSection(&(_inp)->inp_mtx)
+#define SCTP_INP_WUNLOCK(_inp) \
+ LeaveCriticalSection(&(_inp)->inp_mtx)
+#define SCTP_ASOC_CREATE_UNLOCK(_inp) \
+ LeaveCriticalSection(&(_inp)->inp_create_mtx)
+
+/*
+ * For the majority of things (once we have found the association) we will
+ * lock the actual association mutex. This will protect all the assoiciation
+ * level queues and streams and such. We will need to lock the socket layer
+ * when we stuff data up into the receiving sb_mb. I.e. we will need to do an
+ * extra SOCKBUF_LOCK(&so->so_rcv) even though the association is locked.
+ */
+
+#define SCTP_TCB_LOCK_INIT(_tcb) \
+ InitializeCriticalSection(&(_tcb)->tcb_mtx)
+#define SCTP_TCB_LOCK_DESTROY(_tcb) \
+ DeleteCriticalSection(&(_tcb)->tcb_mtx)
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_TCB_LOCK(_tcb) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_tcb->sctp_ep, _tcb, SCTP_LOG_LOCK_TCB); \
+ EnterCriticalSection(&(_tcb)->tcb_mtx); \
+} while (0)
+#else
+#define SCTP_TCB_LOCK(_tcb) \
+ EnterCriticalSection(&(_tcb)->tcb_mtx)
+#endif
+#define SCTP_TCB_TRYLOCK(_tcb) ((TryEnterCriticalSection(&(_tcb)->tcb_mtx)))
+#define SCTP_TCB_UNLOCK(_tcb) \
+ LeaveCriticalSection(&(_tcb)->tcb_mtx)
+#define SCTP_TCB_LOCK_ASSERT(_tcb)
+
+#else /* all Userspaces except Windows */
+#define SCTP_WQ_ADDR_INIT() \
+ (void)pthread_mutex_init(&SCTP_BASE_INFO(wq_addr_mtx), &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_WQ_ADDR_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(wq_addr_mtx))
+#ifdef INVARIANTS
+#define SCTP_WQ_ADDR_LOCK() \
+ KASSERT(pthread_mutex_lock(&SCTP_BASE_INFO(wq_addr_mtx)) == 0, ("%s:%d: wq_addr_mtx already locked", __FILE__, __LINE__))
+#define SCTP_WQ_ADDR_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(wq_addr_mtx)) == 0, ("%s:%d: wq_addr_mtx not locked", __FILE__, __LINE__))
+#else
+#define SCTP_WQ_ADDR_LOCK() \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(wq_addr_mtx))
+#define SCTP_WQ_ADDR_UNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(wq_addr_mtx))
+#endif
+#define SCTP_WQ_ADDR_LOCK_ASSERT() \
+ KASSERT(pthread_mutex_trylock(&SCTP_BASE_INFO(wq_addr_mtx)) == EBUSY, ("%s:%d: wq_addr_mtx not locked", __FILE__, __LINE__))
+
+#define SCTP_INP_INFO_LOCK_INIT() \
+ (void)pthread_rwlock_init(&SCTP_BASE_INFO(ipi_ep_mtx), &SCTP_BASE_VAR(rwlock_attr))
+#define SCTP_INP_INFO_LOCK_DESTROY() \
+ (void)pthread_rwlock_destroy(&SCTP_BASE_INFO(ipi_ep_mtx))
+#ifdef INVARIANTS
+#define SCTP_INP_INFO_RLOCK() \
+ KASSERT(pthread_rwlock_rdlock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s%d: ipi_ep_mtx already locked", __FILE__, __LINE__))
+#define SCTP_INP_INFO_WLOCK() \
+ KASSERT(pthread_rwlock_wrlock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s:%d: ipi_ep_mtx already locked", __FILE__, __LINE__))
+#define SCTP_INP_INFO_RUNLOCK() \
+ KASSERT(pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s:%d: ipi_ep_mtx not locked", __FILE__, __LINE__))
+#define SCTP_INP_INFO_WUNLOCK() \
+ KASSERT(pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s:%d: ipi_ep_mtx not locked", __FILE__, __LINE__))
+#else
+#define SCTP_INP_INFO_RLOCK() \
+ (void)pthread_rwlock_rdlock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WLOCK() \
+ (void)pthread_rwlock_wrlock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_RUNLOCK() \
+ (void)pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WUNLOCK() \
+ (void)pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#endif
+#define SCTP_INP_INFO_LOCK_ASSERT()
+#define SCTP_INP_INFO_RLOCK_ASSERT()
+#define SCTP_INP_INFO_WLOCK_ASSERT()
+#define SCTP_INP_INFO_TRYLOCK() \
+ (!(pthread_rwlock_tryrdlock(&SCTP_BASE_INFO(ipi_ep_mtx))))
+
+#define SCTP_IP_PKTLOG_INIT() \
+ (void)pthread_mutex_init(&SCTP_BASE_INFO(ipi_pktlog_mtx), &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_IP_PKTLOG_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+#ifdef INVARIANTS
+#define SCTP_IP_PKTLOG_LOCK() \
+ KASSERT(pthread_mutex_lock(&SCTP_BASE_INFO(ipi_pktlog_mtx)) == 0, ("%s:%d: ipi_pktlog_mtx already locked", __FILE__, __LINE__))
+#define SCTP_IP_PKTLOG_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_pktlog_mtx)) == 0, ("%s:%d: ipi_pktlog_mtx not locked", __FILE__, __LINE__))
+#else
+#define SCTP_IP_PKTLOG_LOCK() \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+#define SCTP_IP_PKTLOG_UNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+#endif
+
+
+/*
+ * The INP locks we will use for locking an SCTP endpoint, so for example if
+ * we want to change something at the endpoint level for example random_store
+ * or cookie secrets we lock the INP level.
+ */
+#define SCTP_INP_READ_LOCK_INIT(_inp) \
+ (void)pthread_mutex_init(&(_inp)->inp_rdata_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_INP_READ_LOCK_DESTROY(_inp) \
+ (void)pthread_mutex_destroy(&(_inp)->inp_rdata_mtx)
+#ifdef INVARIANTS
+#define SCTP_INP_READ_LOCK(_inp) \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_rdata_mtx) == 0, ("%s:%d: inp_rdata_mtx already locked", __FILE__, __LINE__))
+#define SCTP_INP_READ_UNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_rdata_mtx) == 0, ("%s:%d: inp_rdata_mtx not locked", __FILE__, __LINE__))
+#else
+#define SCTP_INP_READ_LOCK(_inp) \
+ (void)pthread_mutex_lock(&(_inp)->inp_rdata_mtx)
+#define SCTP_INP_READ_UNLOCK(_inp) \
+ (void)pthread_mutex_unlock(&(_inp)->inp_rdata_mtx)
+#endif
+#define SCTP_INP_READ_LOCK_ASSERT(_inp) \
+ KASSERT(pthread_mutex_trylock(&(_inp)->inp_rdata_mtx) == EBUSY, ("%s:%d: inp_rdata_mtx not locked", __FILE__, __LINE__))
+
+#define SCTP_INP_LOCK_INIT(_inp) \
+ (void)pthread_mutex_init(&(_inp)->inp_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_INP_LOCK_DESTROY(_inp) \
+ (void)pthread_mutex_destroy(&(_inp)->inp_mtx)
+#ifdef INVARIANTS
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_INP_RLOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_INP); \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_mtx) == 0, ("%s:%d: inp_mtx already locked", __FILE__, __LINE__)); \
+} while (0)
+#define SCTP_INP_WLOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_INP); \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_mtx) == 0, ("%s:%d: inp_mtx already locked", __FILE__, __LINE__)); \
+} while (0)
+#else
+#define SCTP_INP_RLOCK(_inp) \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_mtx) == 0, ("%s:%d: inp_mtx already locked", __FILE__, __LINE__))
+#define SCTP_INP_WLOCK(_inp) \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_mtx) == 0, ("%s:%d: inp_mtx already locked", __FILE__, __LINE__))
+#endif
+#define SCTP_INP_RUNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_mtx) == 0, ("%s:%d: inp_mtx not locked", __FILE__, __LINE__))
+#define SCTP_INP_WUNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_mtx) == 0, ("%s:%d: inp_mtx not locked", __FILE__, __LINE__))
+#else
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_INP_RLOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_INP); \
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx); \
+} while (0)
+#define SCTP_INP_WLOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_INP); \
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx); \
+} while (0)
+#else
+#define SCTP_INP_RLOCK(_inp) \
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx)
+#define SCTP_INP_WLOCK(_inp) \
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx)
+#endif
+#define SCTP_INP_RUNLOCK(_inp) \
+ (void)pthread_mutex_unlock(&(_inp)->inp_mtx)
+#define SCTP_INP_WUNLOCK(_inp) \
+ (void)pthread_mutex_unlock(&(_inp)->inp_mtx)
+#endif
+#define SCTP_INP_RLOCK_ASSERT(_inp) \
+ KASSERT(pthread_mutex_trylock(&(_inp)->inp_mtx) == EBUSY, ("%s:%d: inp_mtx not locked", __FILE__, __LINE__))
+#define SCTP_INP_WLOCK_ASSERT(_inp) \
+ KASSERT(pthread_mutex_trylock(&(_inp)->inp_mtx) == EBUSY, ("%s:%d: inp_mtx not locked", __FILE__, __LINE__))
+#define SCTP_INP_INCR_REF(_inp) atomic_add_int(&((_inp)->refcount), 1)
+#define SCTP_INP_DECR_REF(_inp) atomic_subtract_int(&((_inp)->refcount), 1)
+
+#define SCTP_ASOC_CREATE_LOCK_INIT(_inp) \
+ (void)pthread_mutex_init(&(_inp)->inp_create_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp) \
+ (void)pthread_mutex_destroy(&(_inp)->inp_create_mtx)
+#ifdef INVARIANTS
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_ASOC_CREATE_LOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_CREATE); \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_create_mtx) == 0, ("%s:%d: inp_create_mtx already locked", __FILE__, __LINE__)); \
+} while (0)
+#else
+#define SCTP_ASOC_CREATE_LOCK(_inp) \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_create_mtx) == 0, ("%s:%d: inp_create_mtx already locked", __FILE__, __LINE__))
+#endif
+#define SCTP_ASOC_CREATE_UNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_create_mtx) == 0, ("%s:%d: inp_create_mtx not locked", __FILE__, __LINE__))
+#else
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_ASOC_CREATE_LOCK(_inp) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_inp, NULL, SCTP_LOG_LOCK_CREATE); \
+ (void)pthread_mutex_lock(&(_inp)->inp_create_mtx); \
+} while (0)
+#else
+#define SCTP_ASOC_CREATE_LOCK(_inp) \
+ (void)pthread_mutex_lock(&(_inp)->inp_create_mtx)
+#endif
+#define SCTP_ASOC_CREATE_UNLOCK(_inp) \
+ (void)pthread_mutex_unlock(&(_inp)->inp_create_mtx)
+#endif
+/*
+ * For the majority of things (once we have found the association) we will
+ * lock the actual association mutex. This will protect all the assoiciation
+ * level queues and streams and such. We will need to lock the socket layer
+ * when we stuff data up into the receiving sb_mb. I.e. we will need to do an
+ * extra SOCKBUF_LOCK(&so->so_rcv) even though the association is locked.
+ */
+
+#define SCTP_TCB_LOCK_INIT(_tcb) \
+ (void)pthread_mutex_init(&(_tcb)->tcb_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_TCB_LOCK_DESTROY(_tcb) \
+ (void)pthread_mutex_destroy(&(_tcb)->tcb_mtx)
+#ifdef INVARIANTS
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_TCB_LOCK(_tcb) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_tcb->sctp_ep, _tcb, SCTP_LOG_LOCK_TCB); \
+ KASSERT(pthread_mutex_lock(&(_tcb)->tcb_mtx) == 0, ("%s:%d: tcb_mtx already locked", __FILE__, __LINE__)) \
+} while (0)
+#else
+#define SCTP_TCB_LOCK(_tcb) \
+ KASSERT(pthread_mutex_lock(&(_tcb)->tcb_mtx) == 0, ("%s:%d: tcb_mtx already locked", __FILE__, __LINE__))
+#endif
+#define SCTP_TCB_UNLOCK(_tcb) \
+ KASSERT(pthread_mutex_unlock(&(_tcb)->tcb_mtx) == 0, ("%s:%d: tcb_mtx not locked", __FILE__, __LINE__))
+#else
+#ifdef SCTP_LOCK_LOGGING
+#define SCTP_TCB_LOCK(_tcb) do { \
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) \
+ sctp_log_lock(_tcb->sctp_ep, _tcb, SCTP_LOG_LOCK_TCB); \
+ (void)pthread_mutex_lock(&(_tcb)->tcb_mtx); \
+} while (0)
+#else
+#define SCTP_TCB_LOCK(_tcb) \
+ (void)pthread_mutex_lock(&(_tcb)->tcb_mtx)
+#endif
+#define SCTP_TCB_UNLOCK(_tcb) (void)pthread_mutex_unlock(&(_tcb)->tcb_mtx)
+#endif
+#define SCTP_TCB_LOCK_ASSERT(_tcb) \
+ KASSERT(pthread_mutex_trylock(&(_tcb)->tcb_mtx) == EBUSY, ("%s:%d: tcb_mtx not locked", __FILE__, __LINE__))
+#define SCTP_TCB_TRYLOCK(_tcb) (!(pthread_mutex_trylock(&(_tcb)->tcb_mtx)))
+#endif
+
+#endif /* SCTP_PER_SOCKET_LOCKING */
+
+
+/*
+ * common locks
+ */
+
+/* copied over to compile */
+#define SCTP_INP_LOCK_CONTENDED(_inp) (0) /* Don't know if this is possible */
+#define SCTP_INP_READ_CONTENDED(_inp) (0) /* Don't know if this is possible */
+#define SCTP_ASOC_CREATE_LOCK_CONTENDED(_inp) (0) /* Don't know if this is possible */
+
+/* socket locks */
+
+#if defined(_WIN32)
+#define SOCKBUF_LOCK_ASSERT(_so_buf)
+#define SOCKBUF_LOCK(_so_buf) \
+ EnterCriticalSection(&(_so_buf)->sb_mtx)
+#define SOCKBUF_UNLOCK(_so_buf) \
+ LeaveCriticalSection(&(_so_buf)->sb_mtx)
+#define SOCK_LOCK(_so) \
+ SOCKBUF_LOCK(&(_so)->so_rcv)
+#define SOCK_UNLOCK(_so) \
+ SOCKBUF_UNLOCK(&(_so)->so_rcv)
+#else
+#define SOCKBUF_LOCK_ASSERT(_so_buf) \
+ KASSERT(pthread_mutex_trylock(SOCKBUF_MTX(_so_buf)) == EBUSY, ("%s:%d: socket buffer not locked", __FILE__, __LINE__))
+#ifdef INVARIANTS
+#define SOCKBUF_LOCK(_so_buf) \
+ KASSERT(pthread_mutex_lock(SOCKBUF_MTX(_so_buf)) == 0, ("%s:%d: sockbuf_mtx already locked", __FILE__, __LINE__))
+#define SOCKBUF_UNLOCK(_so_buf) \
+ KASSERT(pthread_mutex_unlock(SOCKBUF_MTX(_so_buf)) == 0, ("%s:%d: sockbuf_mtx not locked", __FILE__, __LINE__))
+#else
+#define SOCKBUF_LOCK(_so_buf) \
+ pthread_mutex_lock(SOCKBUF_MTX(_so_buf))
+#define SOCKBUF_UNLOCK(_so_buf) \
+ pthread_mutex_unlock(SOCKBUF_MTX(_so_buf))
+#endif
+#define SOCK_LOCK(_so) \
+ SOCKBUF_LOCK(&(_so)->so_rcv)
+#define SOCK_UNLOCK(_so) \
+ SOCKBUF_UNLOCK(&(_so)->so_rcv)
+#endif
+
+#define SCTP_STATLOG_INIT_LOCK()
+#define SCTP_STATLOG_LOCK()
+#define SCTP_STATLOG_UNLOCK()
+#define SCTP_STATLOG_DESTROY()
+
+#if defined(_WIN32)
+/* address list locks */
+#if WINVER < 0x0600
+#define SCTP_IPI_ADDR_INIT() \
+ InitializeCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_DESTROY() \
+ DeleteCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_RLOCK() \
+ EnterCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ LeaveCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WLOCK() \
+ EnterCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WUNLOCK() \
+ LeaveCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_LOCK_ASSERT()
+#define SCTP_IPI_ADDR_WLOCK_ASSERT()
+#else
+#define SCTP_IPI_ADDR_INIT() \
+ InitializeSRWLock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_DESTROY()
+#define SCTP_IPI_ADDR_RLOCK() \
+ AcquireSRWLockShared(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ ReleaseSRWLockShared(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WLOCK() \
+ AcquireSRWLockExclusive(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WUNLOCK() \
+ ReleaseSRWLockExclusive(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_LOCK_ASSERT()
+#define SCTP_IPI_ADDR_WLOCK_ASSERT()
+#endif
+
+/* iterator locks */
+#define SCTP_ITERATOR_LOCK_INIT() \
+ InitializeCriticalSection(&sctp_it_ctl.it_mtx)
+#define SCTP_ITERATOR_LOCK_DESTROY() \
+ DeleteCriticalSection(&sctp_it_ctl.it_mtx)
+#define SCTP_ITERATOR_LOCK() \
+ EnterCriticalSection(&sctp_it_ctl.it_mtx)
+#define SCTP_ITERATOR_UNLOCK() \
+ LeaveCriticalSection(&sctp_it_ctl.it_mtx)
+
+#define SCTP_IPI_ITERATOR_WQ_INIT() \
+ InitializeCriticalSection(&sctp_it_ctl.ipi_iterator_wq_mtx)
+#define SCTP_IPI_ITERATOR_WQ_DESTROY() \
+ DeleteCriticalSection(&sctp_it_ctl.ipi_iterator_wq_mtx)
+#define SCTP_IPI_ITERATOR_WQ_LOCK() \
+ EnterCriticalSection(&sctp_it_ctl.ipi_iterator_wq_mtx)
+#define SCTP_IPI_ITERATOR_WQ_UNLOCK() \
+ LeaveCriticalSection(&sctp_it_ctl.ipi_iterator_wq_mtx)
+
+#else
+/* address list locks */
+#define SCTP_IPI_ADDR_INIT() \
+ (void)pthread_rwlock_init(&SCTP_BASE_INFO(ipi_addr_mtx), &SCTP_BASE_VAR(rwlock_attr))
+#define SCTP_IPI_ADDR_DESTROY() \
+ (void)pthread_rwlock_destroy(&SCTP_BASE_INFO(ipi_addr_mtx))
+#ifdef INVARIANTS
+#define SCTP_IPI_ADDR_RLOCK() \
+ KASSERT(pthread_rwlock_rdlock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s:%d: ipi_addr_mtx already locked", __FILE__, __LINE__))
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ KASSERT(pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s:%d: ipi_addr_mtx not locked", __FILE__, __LINE__))
+#define SCTP_IPI_ADDR_WLOCK() \
+ KASSERT(pthread_rwlock_wrlock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s:%d: ipi_addr_mtx already locked", __FILE__, __LINE__))
+#define SCTP_IPI_ADDR_WUNLOCK() \
+ KASSERT(pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s:%d: ipi_addr_mtx not locked", __FILE__, __LINE__))
+#else
+#define SCTP_IPI_ADDR_RLOCK() \
+ (void)pthread_rwlock_rdlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ (void)pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WLOCK() \
+ (void)pthread_rwlock_wrlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WUNLOCK() \
+ (void)pthread_rwlock_unlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#endif
+#define SCTP_IPI_ADDR_LOCK_ASSERT()
+#define SCTP_IPI_ADDR_WLOCK_ASSERT()
+
+/* iterator locks */
+#define SCTP_ITERATOR_LOCK_INIT() \
+ (void)pthread_mutex_init(&sctp_it_ctl.it_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_ITERATOR_LOCK_DESTROY() \
+ (void)pthread_mutex_destroy(&sctp_it_ctl.it_mtx)
+#ifdef INVARIANTS
+#define SCTP_ITERATOR_LOCK() \
+ KASSERT(pthread_mutex_lock(&sctp_it_ctl.it_mtx) == 0, ("%s:%d: it_mtx already locked", __FILE__, __LINE__))
+#define SCTP_ITERATOR_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&sctp_it_ctl.it_mtx) == 0, ("%s:%d: it_mtx not locked", __FILE__, __LINE__))
+#else
+#define SCTP_ITERATOR_LOCK() \
+ (void)pthread_mutex_lock(&sctp_it_ctl.it_mtx)
+#define SCTP_ITERATOR_UNLOCK() \
+ (void)pthread_mutex_unlock(&sctp_it_ctl.it_mtx)
+#endif
+
+#define SCTP_IPI_ITERATOR_WQ_INIT() \
+ (void)pthread_mutex_init(&sctp_it_ctl.ipi_iterator_wq_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_IPI_ITERATOR_WQ_DESTROY() \
+ (void)pthread_mutex_destroy(&sctp_it_ctl.ipi_iterator_wq_mtx)
+#ifdef INVARIANTS
+#define SCTP_IPI_ITERATOR_WQ_LOCK() \
+ KASSERT(pthread_mutex_lock(&sctp_it_ctl.ipi_iterator_wq_mtx) == 0, ("%s:%d: ipi_iterator_wq_mtx already locked", __FILE__, __LINE__))
+#define SCTP_IPI_ITERATOR_WQ_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&sctp_it_ctl.ipi_iterator_wq_mtx) == 0, ("%s:%d: ipi_iterator_wq_mtx not locked", __FILE__, __LINE__))
+#else
+#define SCTP_IPI_ITERATOR_WQ_LOCK() \
+ (void)pthread_mutex_lock(&sctp_it_ctl.ipi_iterator_wq_mtx)
+#define SCTP_IPI_ITERATOR_WQ_UNLOCK() \
+ (void)pthread_mutex_unlock(&sctp_it_ctl.ipi_iterator_wq_mtx)
+#endif
+#endif
+
+#define SCTP_INCR_EP_COUNT() \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_ep), 1)
+
+#define SCTP_DECR_EP_COUNT() \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_ep), 1)
+
+#define SCTP_INCR_ASOC_COUNT() \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_asoc), 1)
+
+#define SCTP_DECR_ASOC_COUNT() \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_asoc), 1)
+
+#define SCTP_INCR_LADDR_COUNT() \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_laddr), 1)
+
+#define SCTP_DECR_LADDR_COUNT() \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_laddr), 1)
+
+#define SCTP_INCR_RADDR_COUNT() \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_raddr), 1)
+
+#define SCTP_DECR_RADDR_COUNT() \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_raddr), 1)
+
+#define SCTP_INCR_CHK_COUNT() \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_chunk), 1)
+
+#define SCTP_DECR_CHK_COUNT() \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_chunk), 1)
+
+#define SCTP_INCR_READQ_COUNT() \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_readq), 1)
+
+#define SCTP_DECR_READQ_COUNT() \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_readq), 1)
+
+#define SCTP_INCR_STRMOQ_COUNT() \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_strmoq), 1)
+
+#define SCTP_DECR_STRMOQ_COUNT() \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_strmoq), 1)
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_sha1.c b/netwerk/sctp/src/netinet/sctp_sha1.c
new file mode 100644
index 0000000000..db0e7533ff
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sha1.c
@@ -0,0 +1,329 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2013, by Michael Tuexen. All rights reserved.
+ * Copyright (c) 2013, by Lally Singh. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_sha1.h>
+
+#if defined(SCTP_USE_NSS_SHA1)
+/* A SHA-1 Digest is 160 bits, or 20 bytes */
+#define SHA_DIGEST_LENGTH (20)
+
+void
+sctp_sha1_init(struct sctp_sha1_context *ctx)
+{
+ ctx->pk11_ctx = PK11_CreateDigestContext(SEC_OID_SHA1);
+ PK11_DigestBegin(ctx->pk11_ctx);
+}
+
+void
+sctp_sha1_update(struct sctp_sha1_context *ctx, const unsigned char *ptr, unsigned int siz)
+{
+ PK11_DigestOp(ctx->pk11_ctx, ptr, siz);
+}
+
+void
+sctp_sha1_final(unsigned char *digest, struct sctp_sha1_context *ctx)
+{
+ unsigned int output_len = 0;
+
+ PK11_DigestFinal(ctx->pk11_ctx, digest, &output_len, SHA_DIGEST_LENGTH);
+ PK11_DestroyContext(ctx->pk11_ctx, PR_TRUE);
+}
+
+#elif defined(SCTP_USE_OPENSSL_SHA1)
+
+void
+sctp_sha1_init(struct sctp_sha1_context *ctx)
+{
+ SHA1_Init(&ctx->sha_ctx);
+}
+
+void
+sctp_sha1_update(struct sctp_sha1_context *ctx, const unsigned char *ptr, unsigned int siz)
+{
+ SHA1_Update(&ctx->sha_ctx, ptr, (unsigned long)siz);
+}
+
+void
+sctp_sha1_final(unsigned char *digest, struct sctp_sha1_context *ctx)
+{
+ SHA1_Final(digest, &ctx->sha_ctx);
+}
+
+#else
+
+#include <string.h>
+#if defined(_WIN32) && defined(__Userspace__)
+#include <winsock2.h>
+#elif !(defined(_WIN32) && !defined(__Userspace__))
+#include <arpa/inet.h>
+#endif
+
+#define F1(B,C,D) (((B & C) | ((~B) & D))) /* 0 <= t <= 19 */
+#define F2(B,C,D) (B ^ C ^ D) /* 20 <= t <= 39 */
+#define F3(B,C,D) ((B & C) | (B & D) | (C & D)) /* 40 <= t <= 59 */
+#define F4(B,C,D) (B ^ C ^ D) /* 600 <= t <= 79 */
+
+/* circular shift */
+#define CSHIFT(A,B) ((B << A) | (B >> (32-A)))
+
+#define K1 0x5a827999 /* 0 <= t <= 19 */
+#define K2 0x6ed9eba1 /* 20 <= t <= 39 */
+#define K3 0x8f1bbcdc /* 40 <= t <= 59 */
+#define K4 0xca62c1d6 /* 60 <= t <= 79 */
+
+#define H0INIT 0x67452301
+#define H1INIT 0xefcdab89
+#define H2INIT 0x98badcfe
+#define H3INIT 0x10325476
+#define H4INIT 0xc3d2e1f0
+
+void
+sctp_sha1_init(struct sctp_sha1_context *ctx)
+{
+ /* Init the SHA-1 context structure */
+ ctx->A = 0;
+ ctx->B = 0;
+ ctx->C = 0;
+ ctx->D = 0;
+ ctx->E = 0;
+ ctx->H0 = H0INIT;
+ ctx->H1 = H1INIT;
+ ctx->H2 = H2INIT;
+ ctx->H3 = H3INIT;
+ ctx->H4 = H4INIT;
+ ctx->TEMP = 0;
+ memset(ctx->words, 0, sizeof(ctx->words));
+ ctx->how_many_in_block = 0;
+ ctx->running_total = 0;
+}
+
+static void
+sctp_sha1_process_a_block(struct sctp_sha1_context *ctx, unsigned int *block)
+{
+ int i;
+
+ /* init the W0-W15 to the block of words being hashed. */
+ /* step a) */
+ for (i = 0; i < 16; i++) {
+ ctx->words[i] = ntohl(block[i]);
+ }
+ /* now init the rest based on the SHA-1 formula, step b) */
+ for (i = 16; i < 80; i++) {
+ ctx->words[i] = CSHIFT(1, ((ctx->words[(i - 3)]) ^
+ (ctx->words[(i - 8)]) ^
+ (ctx->words[(i - 14)]) ^
+ (ctx->words[(i - 16)])));
+ }
+ /* step c) */
+ ctx->A = ctx->H0;
+ ctx->B = ctx->H1;
+ ctx->C = ctx->H2;
+ ctx->D = ctx->H3;
+ ctx->E = ctx->H4;
+
+ /* step d) */
+ for (i = 0; i < 80; i++) {
+ if (i < 20) {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F1(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ ctx->words[i] +
+ K1);
+ } else if (i < 40) {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F2(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ (ctx->words[i]) +
+ K2);
+ } else if (i < 60) {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F3(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ (ctx->words[i]) +
+ K3);
+ } else {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F4(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ (ctx->words[i]) +
+ K4);
+ }
+ ctx->E = ctx->D;
+ ctx->D = ctx->C;
+ ctx->C = CSHIFT(30, ctx->B);
+ ctx->B = ctx->A;
+ ctx->A = ctx->TEMP;
+ }
+ /* step e) */
+ ctx->H0 = (ctx->H0) + (ctx->A);
+ ctx->H1 = (ctx->H1) + (ctx->B);
+ ctx->H2 = (ctx->H2) + (ctx->C);
+ ctx->H3 = (ctx->H3) + (ctx->D);
+ ctx->H4 = (ctx->H4) + (ctx->E);
+}
+
+void
+sctp_sha1_update(struct sctp_sha1_context *ctx, const unsigned char *ptr, unsigned int siz)
+{
+ unsigned int number_left, left_to_fill;
+
+ number_left = siz;
+ while (number_left > 0) {
+ left_to_fill = sizeof(ctx->sha_block) - ctx->how_many_in_block;
+ if (left_to_fill > number_left) {
+ /* can only partially fill up this one */
+ memcpy(&ctx->sha_block[ctx->how_many_in_block],
+ ptr, number_left);
+ ctx->how_many_in_block += number_left;
+ ctx->running_total += number_left;
+ break;
+ } else {
+ /* block is now full, process it */
+ memcpy(&ctx->sha_block[ctx->how_many_in_block],
+ ptr, left_to_fill);
+ sctp_sha1_process_a_block(ctx,
+ (unsigned int *)ctx->sha_block);
+ number_left -= left_to_fill;
+ ctx->running_total += left_to_fill;
+ ctx->how_many_in_block = 0;
+ ptr = (const unsigned char *)(ptr + left_to_fill);
+ }
+ }
+}
+
+void
+sctp_sha1_final(unsigned char *digest, struct sctp_sha1_context *ctx)
+{
+ /*
+ * if any left in block fill with padding and process. Then transfer
+ * the digest to the pointer. At the last block some special rules
+ * need to apply. We must add a 1 bit following the message, then we
+ * pad with 0's. The total size is encoded as a 64 bit number at the
+ * end. Now if the last buffer has more than 55 octets in it we
+ * cannot fit the 64 bit number + 10000000 pad on the end and must
+ * add the 10000000 pad, pad the rest of the message with 0's and
+ * then create an all 0 message with just the 64 bit size at the end
+ * and run this block through by itself. Also the 64 bit int must
+ * be in network byte order.
+ */
+ int left_to_fill;
+ unsigned int i, *ptr;
+
+ if (ctx->how_many_in_block > 55) {
+ /*
+ * special case, we need to process two blocks here. One for
+ * the current stuff plus possibly the pad. The other for
+ * the size.
+ */
+ left_to_fill = sizeof(ctx->sha_block) - ctx->how_many_in_block;
+ if (left_to_fill == 0) {
+ /* Should not really happen but I am paranoid */
+ sctp_sha1_process_a_block(ctx,
+ (unsigned int *)ctx->sha_block);
+ /* init last block, a bit different than the rest */
+ ctx->sha_block[0] = '\x80';
+ for (i = 1; i < sizeof(ctx->sha_block); i++) {
+ ctx->sha_block[i] = 0x0;
+ }
+ } else if (left_to_fill == 1) {
+ ctx->sha_block[ctx->how_many_in_block] = '\x80';
+ sctp_sha1_process_a_block(ctx,
+ (unsigned int *)ctx->sha_block);
+ /* init last block */
+ memset(ctx->sha_block, 0, sizeof(ctx->sha_block));
+ } else {
+ ctx->sha_block[ctx->how_many_in_block] = '\x80';
+ for (i = (ctx->how_many_in_block + 1);
+ i < sizeof(ctx->sha_block);
+ i++) {
+ ctx->sha_block[i] = 0x0;
+ }
+ sctp_sha1_process_a_block(ctx,
+ (unsigned int *)ctx->sha_block);
+ /* init last block */
+ memset(ctx->sha_block, 0, sizeof(ctx->sha_block));
+ }
+ /* This is in bits so multiply by 8 */
+ ctx->running_total *= 8;
+ ptr = (unsigned int *)&ctx->sha_block[60];
+ *ptr = htonl(ctx->running_total);
+ sctp_sha1_process_a_block(ctx, (unsigned int *)ctx->sha_block);
+ } else {
+ /*
+ * easy case, we just pad this message to size - end with 0
+ * add the magic 0x80 to the next word and then put the
+ * network byte order size in the last spot and process the
+ * block.
+ */
+ ctx->sha_block[ctx->how_many_in_block] = '\x80';
+ for (i = (ctx->how_many_in_block + 1);
+ i < sizeof(ctx->sha_block);
+ i++) {
+ ctx->sha_block[i] = 0x0;
+ }
+ /* get last int spot */
+ ctx->running_total *= 8;
+ ptr = (unsigned int *)&ctx->sha_block[60];
+ *ptr = htonl(ctx->running_total);
+ sctp_sha1_process_a_block(ctx, (unsigned int *)ctx->sha_block);
+ }
+ /* transfer the digest back to the user */
+ digest[3] = (ctx->H0 & 0xff);
+ digest[2] = ((ctx->H0 >> 8) & 0xff);
+ digest[1] = ((ctx->H0 >> 16) & 0xff);
+ digest[0] = ((ctx->H0 >> 24) & 0xff);
+
+ digest[7] = (ctx->H1 & 0xff);
+ digest[6] = ((ctx->H1 >> 8) & 0xff);
+ digest[5] = ((ctx->H1 >> 16) & 0xff);
+ digest[4] = ((ctx->H1 >> 24) & 0xff);
+
+ digest[11] = (ctx->H2 & 0xff);
+ digest[10] = ((ctx->H2 >> 8) & 0xff);
+ digest[9] = ((ctx->H2 >> 16) & 0xff);
+ digest[8] = ((ctx->H2 >> 24) & 0xff);
+
+ digest[15] = (ctx->H3 & 0xff);
+ digest[14] = ((ctx->H3 >> 8) & 0xff);
+ digest[13] = ((ctx->H3 >> 16) & 0xff);
+ digest[12] = ((ctx->H3 >> 24) & 0xff);
+
+ digest[19] = (ctx->H4 & 0xff);
+ digest[18] = ((ctx->H4 >> 8) & 0xff);
+ digest[17] = ((ctx->H4 >> 16) & 0xff);
+ digest[16] = ((ctx->H4 >> 24) & 0xff);
+}
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_sha1.h b/netwerk/sctp/src/netinet/sctp_sha1.h
new file mode 100644
index 0000000000..8e136045f6
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sha1.h
@@ -0,0 +1,84 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __NETINET_SCTP_SHA1_H__
+#define __NETINET_SCTP_SHA1_H__
+
+#include <sys/types.h>
+#if defined(SCTP_USE_NSS_SHA1)
+#include <pk11pub.h>
+#elif defined(SCTP_USE_OPENSSL_SHA1)
+#include <openssl/sha.h>
+#endif
+
+struct sctp_sha1_context {
+#if defined(SCTP_USE_NSS_SHA1)
+ struct PK11Context *pk11_ctx;
+#elif defined(SCTP_USE_OPENSSL_SHA1)
+ SHA_CTX sha_ctx;
+#else
+ unsigned int A;
+ unsigned int B;
+ unsigned int C;
+ unsigned int D;
+ unsigned int E;
+ unsigned int H0;
+ unsigned int H1;
+ unsigned int H2;
+ unsigned int H3;
+ unsigned int H4;
+ unsigned int words[80];
+ unsigned int TEMP;
+ /* block I am collecting to process */
+ char sha_block[64];
+ /* collected so far */
+ int how_many_in_block;
+ unsigned int running_total;
+#endif
+};
+
+#if (defined(__APPLE__) && !defined(__Userspace__) && defined(KERNEL))
+#ifndef _KERNEL
+#define _KERNEL
+#endif
+#endif
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+void sctp_sha1_init(struct sctp_sha1_context *);
+void sctp_sha1_update(struct sctp_sha1_context *, const unsigned char *, unsigned int);
+void sctp_sha1_final(unsigned char *, struct sctp_sha1_context *);
+
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_ss_functions.c b/netwerk/sctp/src/netinet/sctp_ss_functions.c
new file mode 100644
index 0000000000..07060c3806
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_ss_functions.c
@@ -0,0 +1,1121 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010-2012, by Michael Tuexen. All rights reserved.
+ * Copyright (c) 2010-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2010-2012, by Robin Seggelmann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_pcb.h>
+
+/*
+ * Default simple round-robin algorithm.
+ * Just iterates the streams in the order they appear.
+ */
+
+static void
+sctp_ss_default_add(struct sctp_tcb *, struct sctp_association *,
+ struct sctp_stream_out *,
+ struct sctp_stream_queue_pending *);
+
+static void
+sctp_ss_default_remove(struct sctp_tcb *, struct sctp_association *,
+ struct sctp_stream_out *,
+ struct sctp_stream_queue_pending *);
+
+static void
+sctp_ss_default_init(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ uint16_t i;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ asoc->ss_data.locked_on_sending = NULL;
+ asoc->ss_data.last_out_stream = NULL;
+ TAILQ_INIT(&asoc->ss_data.out.wheel);
+ /*
+ * If there is data in the stream queues already,
+ * the scheduler of an existing association has
+ * been changed. We need to add all stream queues
+ * to the wheel.
+ */
+ for (i = 0; i < asoc->streamoutcnt; i++) {
+ stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, asoc,
+ &asoc->strmout[i],
+ NULL);
+ }
+ return;
+}
+
+static void
+sctp_ss_default_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ bool clear_values SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ struct sctp_stream_out *strq;
+
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ KASSERT(strq->ss_params.scheduled, ("strq %p not scheduled", (void *)strq));
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke);
+ strq->ss_params.scheduled = false;
+ }
+ asoc->ss_data.last_out_stream = NULL;
+ return;
+}
+
+static void
+sctp_ss_default_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (with_strq != NULL) {
+ if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
+ stcb->asoc.ss_data.locked_on_sending = strq;
+ }
+ if (stcb->asoc.ss_data.last_out_stream == with_strq) {
+ stcb->asoc.ss_data.last_out_stream = strq;
+ }
+ }
+ strq->ss_params.scheduled = false;
+ return;
+}
+
+static void
+sctp_ss_default_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq,
+ struct sctp_stream_queue_pending *sp SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Add to wheel if not already on it and stream queue not empty */
+ if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel,
+ strq, ss_params.ss.rr.next_spoke);
+ strq->ss_params.scheduled = true;
+ }
+ return;
+}
+
+static bool
+sctp_ss_default_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ return (TAILQ_EMPTY(&asoc->ss_data.out.wheel));
+}
+
+static void
+sctp_ss_default_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq,
+ struct sctp_stream_queue_pending *sp SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Remove from wheel if stream queue is empty and actually is on the wheel */
+ if (TAILQ_EMPTY(&strq->outqueue) && strq->ss_params.scheduled) {
+ if (asoc->ss_data.last_out_stream == strq) {
+ asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream,
+ sctpwheel_listhead,
+ ss_params.ss.rr.next_spoke);
+ if (asoc->ss_data.last_out_stream == NULL) {
+ asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel,
+ sctpwheel_listhead);
+ }
+ if (asoc->ss_data.last_out_stream == strq) {
+ asoc->ss_data.last_out_stream = NULL;
+ }
+ }
+ if (asoc->ss_data.locked_on_sending == strq) {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke);
+ strq->ss_params.scheduled = false;
+ }
+ return;
+}
+
+static struct sctp_stream_out *
+sctp_ss_default_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
+ struct sctp_association *asoc)
+{
+ struct sctp_stream_out *strq, *strqt;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (asoc->ss_data.locked_on_sending != NULL) {
+ KASSERT(asoc->ss_data.locked_on_sending->ss_params.scheduled,
+ ("locked_on_sending %p not scheduled",
+ (void *)asoc->ss_data.locked_on_sending));
+ return (asoc->ss_data.locked_on_sending);
+ }
+ strqt = asoc->ss_data.last_out_stream;
+ KASSERT(strqt == NULL || strqt->ss_params.scheduled,
+ ("last_out_stream %p not scheduled", (void *)strqt));
+default_again:
+ /* Find the next stream to use */
+ if (strqt == NULL) {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ } else {
+ strq = TAILQ_NEXT(strqt, ss_params.ss.rr.next_spoke);
+ if (strq == NULL) {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ }
+ }
+ KASSERT(strq == NULL || strq->ss_params.scheduled,
+ ("strq %p not scheduled", (void *)strq));
+
+ /* If CMT is off, we must validate that
+ * the stream in question has the first
+ * item pointed towards are network destination
+ * requested by the caller. Note that if we
+ * turn out to be locked to a stream (assigning
+ * TSN's then we must stop, since we cannot
+ * look for another stream with data to send
+ * to that destination). In CMT's case, by
+ * skipping this check, we will send one
+ * data packet towards the requested net.
+ */
+ if (net != NULL && strq != NULL &&
+ SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
+ if (TAILQ_FIRST(&strq->outqueue) &&
+ TAILQ_FIRST(&strq->outqueue)->net != NULL &&
+ TAILQ_FIRST(&strq->outqueue)->net != net) {
+ if (strq == asoc->ss_data.last_out_stream) {
+ return (NULL);
+ } else {
+ strqt = strq;
+ goto default_again;
+ }
+ }
+ }
+ return (strq);
+}
+
+static void
+sctp_ss_default_scheduled(struct sctp_tcb *stcb,
+ struct sctp_nets *net SCTP_UNUSED,
+ struct sctp_association *asoc,
+ struct sctp_stream_out *strq,
+ int moved_how_much SCTP_UNUSED)
+{
+ struct sctp_stream_queue_pending *sp;
+
+ KASSERT(strq != NULL, ("strq is NULL"));
+ KASSERT(strq->ss_params.scheduled, ("strq %p is not scheduled", (void *)strq));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ asoc->ss_data.last_out_stream = strq;
+ if (asoc->idata_supported == 0) {
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if ((sp != NULL) && (sp->some_taken == 1)) {
+ asoc->ss_data.locked_on_sending = strq;
+ } else {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ } else {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ return;
+}
+
+static void
+sctp_ss_default_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED,
+ struct sctp_association *asoc SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Nothing to be done here */
+ return;
+}
+
+static int
+sctp_ss_default_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED,
+ struct sctp_stream_out *strq SCTP_UNUSED, uint16_t *value SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Nothing to be done here */
+ return (-1);
+}
+
+static int
+sctp_ss_default_set_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED,
+ struct sctp_stream_out *strq SCTP_UNUSED, uint16_t value SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Nothing to be done here */
+ return (-1);
+}
+
+static bool
+sctp_ss_default_is_user_msgs_incomplete(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
+{
+ struct sctp_stream_out *strq;
+ struct sctp_stream_queue_pending *sp;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (asoc->stream_queue_cnt != 1) {
+ return (false);
+ }
+ strq = asoc->ss_data.locked_on_sending;
+ if (strq == NULL) {
+ return (false);
+ }
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if (sp == NULL) {
+ return (false);
+ }
+ return (sp->msg_is_complete == 0);
+}
+
+/*
+ * Real round-robin algorithm.
+ * Always iterates the streams in ascending order.
+ */
+static void
+sctp_ss_rr_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq,
+ struct sctp_stream_queue_pending *sp SCTP_UNUSED)
+{
+ struct sctp_stream_out *strqt;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) {
+ if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke);
+ } else {
+ strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ while (strqt != NULL && (strqt->sid < strq->sid)) {
+ strqt = TAILQ_NEXT(strqt, ss_params.ss.rr.next_spoke);
+ }
+ if (strqt != NULL) {
+ TAILQ_INSERT_BEFORE(strqt, strq, ss_params.ss.rr.next_spoke);
+ } else {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke);
+ }
+ }
+ strq->ss_params.scheduled = true;
+ }
+ return;
+}
+
+/*
+ * Real round-robin per packet algorithm.
+ * Always iterates the streams in ascending order and
+ * only fills messages of the same stream in a packet.
+ */
+static struct sctp_stream_out *
+sctp_ss_rrp_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED,
+ struct sctp_association *asoc)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ return (asoc->ss_data.last_out_stream);
+}
+
+static void
+sctp_ss_rrp_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
+ struct sctp_association *asoc)
+{
+ struct sctp_stream_out *strq, *strqt;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ strqt = asoc->ss_data.last_out_stream;
+ KASSERT(strqt == NULL || strqt->ss_params.scheduled,
+ ("last_out_stream %p not scheduled", (void *)strqt));
+rrp_again:
+ /* Find the next stream to use */
+ if (strqt == NULL) {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ } else {
+ strq = TAILQ_NEXT(strqt, ss_params.ss.rr.next_spoke);
+ if (strq == NULL) {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ }
+ }
+ KASSERT(strq == NULL || strq->ss_params.scheduled,
+ ("strq %p not scheduled", (void *)strq));
+
+ /* If CMT is off, we must validate that
+ * the stream in question has the first
+ * item pointed towards are network destination
+ * requested by the caller. Note that if we
+ * turn out to be locked to a stream (assigning
+ * TSN's then we must stop, since we cannot
+ * look for another stream with data to send
+ * to that destination). In CMT's case, by
+ * skipping this check, we will send one
+ * data packet towards the requested net.
+ */
+ if (net != NULL && strq != NULL &&
+ SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
+ if (TAILQ_FIRST(&strq->outqueue) &&
+ TAILQ_FIRST(&strq->outqueue)->net != NULL &&
+ TAILQ_FIRST(&strq->outqueue)->net != net) {
+ if (strq == asoc->ss_data.last_out_stream) {
+ strq = NULL;
+ } else {
+ strqt = strq;
+ goto rrp_again;
+ }
+ }
+ }
+ asoc->ss_data.last_out_stream = strq;
+ return;
+}
+
+/*
+ * Priority algorithm.
+ * Always prefers streams based on their priority id.
+ */
+static void
+sctp_ss_prio_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ bool clear_values)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ struct sctp_stream_out *strq;
+
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ KASSERT(strq->ss_params.scheduled, ("strq %p not scheduled", (void *)strq));
+ if (clear_values) {
+ strq->ss_params.ss.prio.priority = 0;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke);
+ strq->ss_params.scheduled = false;
+ }
+ asoc->ss_data.last_out_stream = NULL;
+ return;
+}
+
+static void
+sctp_ss_prio_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (with_strq != NULL) {
+ if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
+ stcb->asoc.ss_data.locked_on_sending = strq;
+ }
+ if (stcb->asoc.ss_data.last_out_stream == with_strq) {
+ stcb->asoc.ss_data.last_out_stream = strq;
+ }
+ }
+ strq->ss_params.scheduled = false;
+ if (with_strq != NULL) {
+ strq->ss_params.ss.prio.priority = with_strq->ss_params.ss.prio.priority;
+ } else {
+ strq->ss_params.ss.prio.priority = 0;
+ }
+ return;
+}
+
+static void
+sctp_ss_prio_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED)
+{
+ struct sctp_stream_out *strqt;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Add to wheel if not already on it and stream queue not empty */
+ if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) {
+ if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke);
+ } else {
+ strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ while (strqt != NULL && strqt->ss_params.ss.prio.priority < strq->ss_params.ss.prio.priority) {
+ strqt = TAILQ_NEXT(strqt, ss_params.ss.prio.next_spoke);
+ }
+ if (strqt != NULL) {
+ TAILQ_INSERT_BEFORE(strqt, strq, ss_params.ss.prio.next_spoke);
+ } else {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke);
+ }
+ }
+ strq->ss_params.scheduled = true;
+ }
+ return;
+}
+
+static void
+sctp_ss_prio_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Remove from wheel if stream queue is empty and actually is on the wheel */
+ if (TAILQ_EMPTY(&strq->outqueue) && strq->ss_params.scheduled) {
+ if (asoc->ss_data.last_out_stream == strq) {
+ asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream,
+ sctpwheel_listhead,
+ ss_params.ss.prio.next_spoke);
+ if (asoc->ss_data.last_out_stream == NULL) {
+ asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel,
+ sctpwheel_listhead);
+ }
+ if (asoc->ss_data.last_out_stream == strq) {
+ asoc->ss_data.last_out_stream = NULL;
+ }
+ }
+ if (asoc->ss_data.locked_on_sending == strq) {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke);
+ strq->ss_params.scheduled = false;
+ }
+ return;
+}
+
+static struct sctp_stream_out*
+sctp_ss_prio_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
+ struct sctp_association *asoc)
+{
+ struct sctp_stream_out *strq, *strqt, *strqn;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (asoc->ss_data.locked_on_sending != NULL) {
+ KASSERT(asoc->ss_data.locked_on_sending->ss_params.scheduled,
+ ("locked_on_sending %p not scheduled",
+ (void *)asoc->ss_data.locked_on_sending));
+ return (asoc->ss_data.locked_on_sending);
+ }
+ strqt = asoc->ss_data.last_out_stream;
+ KASSERT(strqt == NULL || strqt->ss_params.scheduled,
+ ("last_out_stream %p not scheduled", (void *)strqt));
+prio_again:
+ /* Find the next stream to use */
+ if (strqt == NULL) {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ } else {
+ strqn = TAILQ_NEXT(strqt, ss_params.ss.prio.next_spoke);
+ if (strqn != NULL &&
+ strqn->ss_params.ss.prio.priority == strqt->ss_params.ss.prio.priority) {
+ strq = strqn;
+ } else {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ }
+ }
+ KASSERT(strq == NULL || strq->ss_params.scheduled,
+ ("strq %p not scheduled", (void *)strq));
+
+ /* If CMT is off, we must validate that
+ * the stream in question has the first
+ * item pointed towards are network destination
+ * requested by the caller. Note that if we
+ * turn out to be locked to a stream (assigning
+ * TSN's then we must stop, since we cannot
+ * look for another stream with data to send
+ * to that destination). In CMT's case, by
+ * skipping this check, we will send one
+ * data packet towards the requested net.
+ */
+ if (net != NULL && strq != NULL &&
+ SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
+ if (TAILQ_FIRST(&strq->outqueue) &&
+ TAILQ_FIRST(&strq->outqueue)->net != NULL &&
+ TAILQ_FIRST(&strq->outqueue)->net != net) {
+ if (strq == asoc->ss_data.last_out_stream) {
+ return (NULL);
+ } else {
+ strqt = strq;
+ goto prio_again;
+ }
+ }
+ }
+ return (strq);
+}
+
+static int
+sctp_ss_prio_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED,
+ struct sctp_stream_out *strq, uint16_t *value)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (strq == NULL) {
+ return (-1);
+ }
+ *value = strq->ss_params.ss.prio.priority;
+ return (1);
+}
+
+static int
+sctp_ss_prio_set_value(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, uint16_t value)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (strq == NULL) {
+ return (-1);
+ }
+ strq->ss_params.ss.prio.priority = value;
+ sctp_ss_prio_remove(stcb, asoc, strq, NULL);
+ sctp_ss_prio_add(stcb, asoc, strq, NULL);
+ return (1);
+}
+
+/*
+ * Fair bandwidth algorithm.
+ * Maintains an equal throughput per stream.
+ */
+static void
+sctp_ss_fb_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ bool clear_values)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ struct sctp_stream_out *strq;
+
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ KASSERT(strq->ss_params.scheduled, ("strq %p not scheduled", (void *)strq));
+ if (clear_values) {
+ strq->ss_params.ss.fb.rounds = -1;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.fb.next_spoke);
+ strq->ss_params.scheduled = false;
+ }
+ asoc->ss_data.last_out_stream = NULL;
+ return;
+}
+
+static void
+sctp_ss_fb_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (with_strq != NULL) {
+ if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
+ stcb->asoc.ss_data.locked_on_sending = strq;
+ }
+ if (stcb->asoc.ss_data.last_out_stream == with_strq) {
+ stcb->asoc.ss_data.last_out_stream = strq;
+ }
+ }
+ strq->ss_params.scheduled = false;
+ if (with_strq != NULL) {
+ strq->ss_params.ss.fb.rounds = with_strq->ss_params.ss.fb.rounds;
+ } else {
+ strq->ss_params.ss.fb.rounds = -1;
+ }
+ return;
+}
+
+static void
+sctp_ss_fb_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) {
+ if (strq->ss_params.ss.fb.rounds < 0)
+ strq->ss_params.ss.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length;
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.ss.fb.next_spoke);
+ strq->ss_params.scheduled = true;
+ }
+ return;
+}
+
+static void
+sctp_ss_fb_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ /* Remove from wheel if stream queue is empty and actually is on the wheel */
+ if (TAILQ_EMPTY(&strq->outqueue) && strq->ss_params.scheduled) {
+ if (asoc->ss_data.last_out_stream == strq) {
+ asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream,
+ sctpwheel_listhead,
+ ss_params.ss.fb.next_spoke);
+ if (asoc->ss_data.last_out_stream == NULL) {
+ asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel,
+ sctpwheel_listhead);
+ }
+ if (asoc->ss_data.last_out_stream == strq) {
+ asoc->ss_data.last_out_stream = NULL;
+ }
+ }
+ if (asoc->ss_data.locked_on_sending == strq) {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.fb.next_spoke);
+ strq->ss_params.scheduled = false;
+ }
+ return;
+}
+
+static struct sctp_stream_out*
+sctp_ss_fb_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
+ struct sctp_association *asoc)
+{
+ struct sctp_stream_out *strq = NULL, *strqt;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (asoc->ss_data.locked_on_sending != NULL) {
+ KASSERT(asoc->ss_data.locked_on_sending->ss_params.scheduled,
+ ("locked_on_sending %p not scheduled",
+ (void *)asoc->ss_data.locked_on_sending));
+ return (asoc->ss_data.locked_on_sending);
+ }
+ if (asoc->ss_data.last_out_stream == NULL ||
+ TAILQ_FIRST(&asoc->ss_data.out.wheel) == TAILQ_LAST(&asoc->ss_data.out.wheel, sctpwheel_listhead)) {
+ strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ } else {
+ strqt = TAILQ_NEXT(asoc->ss_data.last_out_stream, ss_params.ss.fb.next_spoke);
+ }
+ do {
+ if ((strqt != NULL) &&
+ ((SCTP_BASE_SYSCTL(sctp_cmt_on_off) > 0) ||
+ (SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0 &&
+ (net == NULL || (TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net == NULL) ||
+ (net != NULL && TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net != NULL &&
+ TAILQ_FIRST(&strqt->outqueue)->net == net))))) {
+ if ((strqt->ss_params.ss.fb.rounds >= 0) &&
+ ((strq == NULL) ||
+ (strqt->ss_params.ss.fb.rounds < strq->ss_params.ss.fb.rounds))) {
+ strq = strqt;
+ }
+ }
+ if (strqt != NULL) {
+ strqt = TAILQ_NEXT(strqt, ss_params.ss.fb.next_spoke);
+ } else {
+ strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ }
+ } while (strqt != strq);
+ return (strq);
+}
+
+static void
+sctp_ss_fb_scheduled(struct sctp_tcb *stcb, struct sctp_nets *net SCTP_UNUSED,
+ struct sctp_association *asoc, struct sctp_stream_out *strq,
+ int moved_how_much SCTP_UNUSED)
+{
+ struct sctp_stream_queue_pending *sp;
+ struct sctp_stream_out *strqt;
+ int subtract;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (asoc->idata_supported == 0) {
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if ((sp != NULL) && (sp->some_taken == 1)) {
+ asoc->ss_data.locked_on_sending = strq;
+ } else {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ } else {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ subtract = strq->ss_params.ss.fb.rounds;
+ TAILQ_FOREACH(strqt, &asoc->ss_data.out.wheel, ss_params.ss.fb.next_spoke) {
+ strqt->ss_params.ss.fb.rounds -= subtract;
+ if (strqt->ss_params.ss.fb.rounds < 0)
+ strqt->ss_params.ss.fb.rounds = 0;
+ }
+ if (TAILQ_FIRST(&strq->outqueue)) {
+ strq->ss_params.ss.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length;
+ } else {
+ strq->ss_params.ss.fb.rounds = -1;
+ }
+ asoc->ss_data.last_out_stream = strq;
+ return;
+}
+
+/*
+ * First-come, first-serve algorithm.
+ * Maintains the order provided by the application.
+ */
+static void
+sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq SCTP_UNUSED,
+ struct sctp_stream_queue_pending *sp);
+
+static void
+sctp_ss_fcfs_init(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ uint32_t x, n = 0, add_more = 1;
+ struct sctp_stream_queue_pending *sp;
+ uint16_t i;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ TAILQ_INIT(&asoc->ss_data.out.list);
+ /*
+ * If there is data in the stream queues already,
+ * the scheduler of an existing association has
+ * been changed. We can only cycle through the
+ * stream queues and add everything to the FCFS
+ * queue.
+ */
+ while (add_more) {
+ add_more = 0;
+ for (i = 0; i < asoc->streamoutcnt; i++) {
+ sp = TAILQ_FIRST(&asoc->strmout[i].outqueue);
+ x = 0;
+ /* Find n. message in current stream queue */
+ while (sp != NULL && x < n) {
+ sp = TAILQ_NEXT(sp, next);
+ x++;
+ }
+ if (sp != NULL) {
+ sctp_ss_fcfs_add(stcb, asoc, &asoc->strmout[i], sp);
+ add_more = 1;
+ }
+ }
+ n++;
+ }
+ return;
+}
+
+static void
+sctp_ss_fcfs_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ bool clear_values SCTP_UNUSED)
+{
+ struct sctp_stream_queue_pending *sp;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.list)) {
+ sp = TAILQ_FIRST(&asoc->ss_data.out.list);
+ KASSERT(sp->scheduled, ("sp %p not scheduled", (void *)sp));
+ TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next);
+ sp->scheduled = false;
+ }
+ asoc->ss_data.last_out_stream = NULL;
+ return;
+}
+
+static void
+sctp_ss_fcfs_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (with_strq != NULL) {
+ if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
+ stcb->asoc.ss_data.locked_on_sending = strq;
+ }
+ if (stcb->asoc.ss_data.last_out_stream == with_strq) {
+ stcb->asoc.ss_data.last_out_stream = strq;
+ }
+ }
+ strq->ss_params.scheduled = false;
+ return;
+}
+
+static void
+sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (!sp->scheduled) {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.list, sp, ss_next);
+ sp->scheduled = true;
+ }
+ return;
+}
+
+static bool
+sctp_ss_fcfs_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ return (TAILQ_EMPTY(&asoc->ss_data.out.list));
+}
+
+static void
+sctp_ss_fcfs_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp)
+{
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (sp->scheduled) {
+ TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next);
+ sp->scheduled = false;
+ }
+ return;
+}
+
+static struct sctp_stream_out *
+sctp_ss_fcfs_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
+ struct sctp_association *asoc)
+{
+ struct sctp_stream_out *strq;
+ struct sctp_stream_queue_pending *sp;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (asoc->ss_data.locked_on_sending) {
+ return (asoc->ss_data.locked_on_sending);
+ }
+ sp = TAILQ_FIRST(&asoc->ss_data.out.list);
+default_again:
+ if (sp != NULL) {
+ strq = &asoc->strmout[sp->sid];
+ } else {
+ strq = NULL;
+ }
+
+ /*
+ * If CMT is off, we must validate that
+ * the stream in question has the first
+ * item pointed towards are network destination
+ * requested by the caller. Note that if we
+ * turn out to be locked to a stream (assigning
+ * TSN's then we must stop, since we cannot
+ * look for another stream with data to send
+ * to that destination). In CMT's case, by
+ * skipping this check, we will send one
+ * data packet towards the requested net.
+ */
+ if (net != NULL && strq != NULL &&
+ SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
+ if (TAILQ_FIRST(&strq->outqueue) &&
+ TAILQ_FIRST(&strq->outqueue)->net != NULL &&
+ TAILQ_FIRST(&strq->outqueue)->net != net) {
+ sp = TAILQ_NEXT(sp, ss_next);
+ goto default_again;
+ }
+ }
+ return (strq);
+}
+
+static void
+sctp_ss_fcfs_scheduled(struct sctp_tcb *stcb,
+ struct sctp_nets *net SCTP_UNUSED,
+ struct sctp_association *asoc,
+ struct sctp_stream_out *strq,
+ int moved_how_much SCTP_UNUSED)
+{
+ struct sctp_stream_queue_pending *sp;
+
+ KASSERT(strq != NULL, ("strq is NULL"));
+ asoc->ss_data.last_out_stream = strq;
+ if (asoc->idata_supported == 0) {
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if ((sp != NULL) && (sp->some_taken == 1)) {
+ asoc->ss_data.locked_on_sending = strq;
+ } else {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ } else {
+ asoc->ss_data.locked_on_sending = NULL;
+ }
+ return;
+}
+
+const struct sctp_ss_functions sctp_ss_functions[] = {
+/* SCTP_SS_DEFAULT */
+{
+#if defined(_WIN32)
+ sctp_ss_default_init,
+ sctp_ss_default_clear,
+ sctp_ss_default_init_stream,
+ sctp_ss_default_add,
+ sctp_ss_default_is_empty,
+ sctp_ss_default_remove,
+ sctp_ss_default_select,
+ sctp_ss_default_scheduled,
+ sctp_ss_default_packet_done,
+ sctp_ss_default_get_value,
+ sctp_ss_default_set_value,
+ sctp_ss_default_is_user_msgs_incomplete
+#else
+ .sctp_ss_init = sctp_ss_default_init,
+ .sctp_ss_clear = sctp_ss_default_clear,
+ .sctp_ss_init_stream = sctp_ss_default_init_stream,
+ .sctp_ss_add_to_stream = sctp_ss_default_add,
+ .sctp_ss_is_empty = sctp_ss_default_is_empty,
+ .sctp_ss_remove_from_stream = sctp_ss_default_remove,
+ .sctp_ss_select_stream = sctp_ss_default_select,
+ .sctp_ss_scheduled = sctp_ss_default_scheduled,
+ .sctp_ss_packet_done = sctp_ss_default_packet_done,
+ .sctp_ss_get_value = sctp_ss_default_get_value,
+ .sctp_ss_set_value = sctp_ss_default_set_value,
+ .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
+#endif
+},
+/* SCTP_SS_RR */
+{
+#if defined(_WIN32)
+ sctp_ss_default_init,
+ sctp_ss_default_clear,
+ sctp_ss_default_init_stream,
+ sctp_ss_rr_add,
+ sctp_ss_default_is_empty,
+ sctp_ss_default_remove,
+ sctp_ss_default_select,
+ sctp_ss_default_scheduled,
+ sctp_ss_default_packet_done,
+ sctp_ss_default_get_value,
+ sctp_ss_default_set_value,
+ sctp_ss_default_is_user_msgs_incomplete
+#else
+ .sctp_ss_init = sctp_ss_default_init,
+ .sctp_ss_clear = sctp_ss_default_clear,
+ .sctp_ss_init_stream = sctp_ss_default_init_stream,
+ .sctp_ss_add_to_stream = sctp_ss_rr_add,
+ .sctp_ss_is_empty = sctp_ss_default_is_empty,
+ .sctp_ss_remove_from_stream = sctp_ss_default_remove,
+ .sctp_ss_select_stream = sctp_ss_default_select,
+ .sctp_ss_scheduled = sctp_ss_default_scheduled,
+ .sctp_ss_packet_done = sctp_ss_default_packet_done,
+ .sctp_ss_get_value = sctp_ss_default_get_value,
+ .sctp_ss_set_value = sctp_ss_default_set_value,
+ .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
+#endif
+},
+/* SCTP_SS_RR_PKT */
+{
+#if defined(_WIN32)
+ sctp_ss_default_init,
+ sctp_ss_default_clear,
+ sctp_ss_default_init_stream,
+ sctp_ss_rr_add,
+ sctp_ss_default_is_empty,
+ sctp_ss_default_remove,
+ sctp_ss_rrp_select,
+ sctp_ss_default_scheduled,
+ sctp_ss_rrp_packet_done,
+ sctp_ss_default_get_value,
+ sctp_ss_default_set_value,
+ sctp_ss_default_is_user_msgs_incomplete
+#else
+ .sctp_ss_init = sctp_ss_default_init,
+ .sctp_ss_clear = sctp_ss_default_clear,
+ .sctp_ss_init_stream = sctp_ss_default_init_stream,
+ .sctp_ss_add_to_stream = sctp_ss_rr_add,
+ .sctp_ss_is_empty = sctp_ss_default_is_empty,
+ .sctp_ss_remove_from_stream = sctp_ss_default_remove,
+ .sctp_ss_select_stream = sctp_ss_rrp_select,
+ .sctp_ss_scheduled = sctp_ss_default_scheduled,
+ .sctp_ss_packet_done = sctp_ss_rrp_packet_done,
+ .sctp_ss_get_value = sctp_ss_default_get_value,
+ .sctp_ss_set_value = sctp_ss_default_set_value,
+ .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
+#endif
+},
+/* SCTP_SS_PRIO */
+{
+#if defined(_WIN32)
+ sctp_ss_default_init,
+ sctp_ss_prio_clear,
+ sctp_ss_prio_init_stream,
+ sctp_ss_prio_add,
+ sctp_ss_default_is_empty,
+ sctp_ss_prio_remove,
+ sctp_ss_prio_select,
+ sctp_ss_default_scheduled,
+ sctp_ss_default_packet_done,
+ sctp_ss_prio_get_value,
+ sctp_ss_prio_set_value,
+ sctp_ss_default_is_user_msgs_incomplete
+#else
+ .sctp_ss_init = sctp_ss_default_init,
+ .sctp_ss_clear = sctp_ss_prio_clear,
+ .sctp_ss_init_stream = sctp_ss_prio_init_stream,
+ .sctp_ss_add_to_stream = sctp_ss_prio_add,
+ .sctp_ss_is_empty = sctp_ss_default_is_empty,
+ .sctp_ss_remove_from_stream = sctp_ss_prio_remove,
+ .sctp_ss_select_stream = sctp_ss_prio_select,
+ .sctp_ss_scheduled = sctp_ss_default_scheduled,
+ .sctp_ss_packet_done = sctp_ss_default_packet_done,
+ .sctp_ss_get_value = sctp_ss_prio_get_value,
+ .sctp_ss_set_value = sctp_ss_prio_set_value,
+ .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
+#endif
+},
+/* SCTP_SS_FB */
+{
+#if defined(_WIN32)
+ sctp_ss_default_init,
+ sctp_ss_fb_clear,
+ sctp_ss_fb_init_stream,
+ sctp_ss_fb_add,
+ sctp_ss_default_is_empty,
+ sctp_ss_fb_remove,
+ sctp_ss_fb_select,
+ sctp_ss_fb_scheduled,
+ sctp_ss_default_packet_done,
+ sctp_ss_default_get_value,
+ sctp_ss_default_set_value,
+ sctp_ss_default_is_user_msgs_incomplete
+#else
+ .sctp_ss_init = sctp_ss_default_init,
+ .sctp_ss_clear = sctp_ss_fb_clear,
+ .sctp_ss_init_stream = sctp_ss_fb_init_stream,
+ .sctp_ss_add_to_stream = sctp_ss_fb_add,
+ .sctp_ss_is_empty = sctp_ss_default_is_empty,
+ .sctp_ss_remove_from_stream = sctp_ss_fb_remove,
+ .sctp_ss_select_stream = sctp_ss_fb_select,
+ .sctp_ss_scheduled = sctp_ss_fb_scheduled,
+ .sctp_ss_packet_done = sctp_ss_default_packet_done,
+ .sctp_ss_get_value = sctp_ss_default_get_value,
+ .sctp_ss_set_value = sctp_ss_default_set_value,
+ .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
+#endif
+},
+/* SCTP_SS_FCFS */
+{
+#if defined(_WIN32)
+ sctp_ss_fcfs_init,
+ sctp_ss_fcfs_clear,
+ sctp_ss_fcfs_init_stream,
+ sctp_ss_fcfs_add,
+ sctp_ss_fcfs_is_empty,
+ sctp_ss_fcfs_remove,
+ sctp_ss_fcfs_select,
+ sctp_ss_fcfs_scheduled,
+ sctp_ss_default_packet_done,
+ sctp_ss_default_get_value,
+ sctp_ss_default_set_value,
+ sctp_ss_default_is_user_msgs_incomplete
+#else
+ .sctp_ss_init = sctp_ss_fcfs_init,
+ .sctp_ss_clear = sctp_ss_fcfs_clear,
+ .sctp_ss_init_stream = sctp_ss_fcfs_init_stream,
+ .sctp_ss_add_to_stream = sctp_ss_fcfs_add,
+ .sctp_ss_is_empty = sctp_ss_fcfs_is_empty,
+ .sctp_ss_remove_from_stream = sctp_ss_fcfs_remove,
+ .sctp_ss_select_stream = sctp_ss_fcfs_select,
+ .sctp_ss_scheduled = sctp_ss_fcfs_scheduled,
+ .sctp_ss_packet_done = sctp_ss_default_packet_done,
+ .sctp_ss_get_value = sctp_ss_default_get_value,
+ .sctp_ss_set_value = sctp_ss_default_set_value,
+ .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
+#endif
+}
+};
diff --git a/netwerk/sctp/src/netinet/sctp_structs.h b/netwerk/sctp/src/netinet/sctp_structs.h
new file mode 100644
index 0000000000..894137ae5d
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_structs.h
@@ -0,0 +1,1282 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_STRUCTS_H_
+#define _NETINET_SCTP_STRUCTS_H_
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_auth.h>
+
+struct sctp_timer {
+ sctp_os_timer_t timer;
+
+ int type;
+ /*
+ * Depending on the timer type these will be setup and cast with the
+ * appropriate entity.
+ */
+ void *ep;
+ void *tcb;
+ void *net;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ void *vnet;
+#endif
+
+ /* for sanity checking */
+ void *self;
+ uint32_t ticks;
+ uint32_t stopped_from;
+};
+
+struct sctp_foo_stuff {
+ struct sctp_inpcb *inp;
+ uint32_t lineno;
+ uint32_t ticks;
+ int updown;
+};
+
+/*
+ * This is the information we track on each interface that we know about from
+ * the distant end.
+ */
+TAILQ_HEAD(sctpnetlisthead, sctp_nets);
+
+struct sctp_stream_reset_list {
+ TAILQ_ENTRY(sctp_stream_reset_list) next_resp;
+ uint32_t seq;
+ uint32_t tsn;
+ uint32_t number_entries;
+ uint16_t list_of_streams[];
+};
+
+TAILQ_HEAD(sctp_resethead, sctp_stream_reset_list);
+
+/*
+ * Users of the iterator need to malloc a iterator with a call to
+ * sctp_initiate_iterator(inp_func, assoc_func, inp_func, pcb_flags, pcb_features,
+ * asoc_state, void-ptr-arg, uint32-arg, end_func, inp);
+ *
+ * Use the following two defines if you don't care what pcb flags are on the EP
+ * and/or you don't care what state the association is in.
+ *
+ * Note that if you specify an INP as the last argument then ONLY each
+ * association of that single INP will be executed upon. Note that the pcb
+ * flags STILL apply so if the inp you specify has different pcb_flags then
+ * what you put in pcb_flags nothing will happen. use SCTP_PCB_ANY_FLAGS to
+ * assure the inp you specify gets treated.
+ */
+#define SCTP_PCB_ANY_FLAGS 0x00000000
+#define SCTP_PCB_ANY_FEATURES 0x00000000
+#define SCTP_ASOC_ANY_STATE 0x00000000
+
+typedef void (*asoc_func) (struct sctp_inpcb *, struct sctp_tcb *, void *ptr,
+ uint32_t val);
+typedef int (*inp_func) (struct sctp_inpcb *, void *ptr, uint32_t val);
+typedef void (*end_func) (void *ptr, uint32_t val);
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SCTP_MCORE_INPUT) && defined(SMP)
+/* whats on the mcore control struct */
+struct sctp_mcore_queue {
+ TAILQ_ENTRY(sctp_mcore_queue) next;
+ struct vnet *vn;
+ struct mbuf *m;
+ int off;
+ int v6;
+};
+
+TAILQ_HEAD(sctp_mcore_qhead, sctp_mcore_queue);
+
+struct sctp_mcore_ctrl {
+ SCTP_PROCESS_STRUCT thread_proc;
+ struct sctp_mcore_qhead que;
+ struct mtx core_mtx;
+ struct mtx que_mtx;
+ int running;
+ int cpuid;
+};
+#endif
+#endif
+
+/* This struct is here to cut out the compatiabilty
+ * pad that bulks up both the inp and stcb. The non
+ * pad portion MUST stay in complete sync with
+ * sctp_sndrcvinfo... i.e. if sinfo_xxxx is added
+ * this must be done here too.
+ */
+struct sctp_nonpad_sndrcvinfo {
+ uint16_t sinfo_stream;
+ uint16_t sinfo_ssn;
+ uint16_t sinfo_flags;
+ uint32_t sinfo_ppid;
+ uint32_t sinfo_context;
+ uint32_t sinfo_timetolive;
+ uint32_t sinfo_tsn;
+ uint32_t sinfo_cumtsn;
+ sctp_assoc_t sinfo_assoc_id;
+ uint16_t sinfo_keynumber;
+ uint16_t sinfo_keynumber_valid;
+};
+
+struct sctp_iterator {
+ TAILQ_ENTRY(sctp_iterator) sctp_nxt_itr;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct vnet *vn;
+#endif
+ struct sctp_timer tmr;
+ struct sctp_inpcb *inp; /* current endpoint */
+ struct sctp_tcb *stcb; /* current* assoc */
+ struct sctp_inpcb *next_inp; /* special hook to skip to */
+ asoc_func function_assoc; /* per assoc function */
+ inp_func function_inp; /* per endpoint function */
+ inp_func function_inp_end; /* end INP function */
+ end_func function_atend; /* iterator completion function */
+ void *pointer; /* pointer for apply func to use */
+ uint32_t val; /* value for apply func to use */
+ uint32_t pcb_flags; /* endpoint flags being checked */
+ uint32_t pcb_features; /* endpoint features being checked */
+ uint32_t asoc_state; /* assoc state being checked */
+ uint32_t iterator_flags;
+ uint8_t no_chunk_output;
+ uint8_t done_current_ep;
+};
+/* iterator_flags values */
+#define SCTP_ITERATOR_DO_ALL_INP 0x00000001
+#define SCTP_ITERATOR_DO_SINGLE_INP 0x00000002
+
+TAILQ_HEAD(sctpiterators, sctp_iterator);
+
+struct sctp_copy_all {
+ struct sctp_inpcb *inp; /* ep */
+ struct mbuf *m;
+ struct sctp_nonpad_sndrcvinfo sndrcv;
+ ssize_t sndlen;
+ int cnt_sent;
+ int cnt_failed;
+};
+
+struct sctp_asconf_iterator {
+ struct sctpladdr list_of_work;
+ int cnt;
+};
+
+struct iterator_control {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct mtx ipi_iterator_wq_mtx;
+ struct mtx it_mtx;
+#elif defined(__APPLE__) && !defined(__Userspace__)
+ lck_mtx_t *ipi_iterator_wq_mtx;
+ lck_mtx_t *it_mtx;
+#elif defined(SCTP_PROCESS_LEVEL_LOCKS)
+#if defined(__Userspace__)
+ userland_mutex_t ipi_iterator_wq_mtx;
+ userland_mutex_t it_mtx;
+ userland_cond_t iterator_wakeup;
+#else
+ pthread_mutex_t ipi_iterator_wq_mtx;
+ pthread_mutex_t it_mtx;
+ pthread_cond_t iterator_wakeup;
+#endif
+#elif defined(_WIN32) && !defined(__Userspace__)
+ struct spinlock it_lock;
+ struct spinlock ipi_iterator_wq_lock;
+ KEVENT iterator_wakeup[2];
+ PFILE_OBJECT iterator_thread_obj;
+#else
+ void *it_mtx;
+#endif
+#if !(defined(_WIN32) && !defined(__Userspace__))
+#if !defined(__Userspace__)
+ SCTP_PROCESS_STRUCT thread_proc;
+#else
+ userland_thread_t thread_proc;
+#endif
+#endif
+ struct sctpiterators iteratorhead;
+ struct sctp_iterator *cur_it;
+ uint32_t iterator_running;
+ uint32_t iterator_flags;
+};
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+#define SCTP_ITERATOR_MUST_EXIT 0x00000001
+#define SCTP_ITERATOR_EXITED 0x00000002
+#endif
+#define SCTP_ITERATOR_STOP_CUR_IT 0x00000004
+#define SCTP_ITERATOR_STOP_CUR_INP 0x00000008
+
+struct sctp_net_route {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct nhop_object *ro_nh;
+ struct llentry *ro_lle;
+ char *ro_prepend;
+ uint16_t ro_plen;
+ uint16_t ro_flags;
+ uint16_t ro_mtu;
+ uint16_t spare;
+#else
+ sctp_rtentry_t *ro_rt;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION) && !defined(APPLE_ELCAPITAN)
+ struct llentry *ro_lle;
+#endif
+#if !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION)
+ struct ifaddr *ro_srcia;
+#endif
+#if !defined(APPLE_LEOPARD)
+ uint32_t ro_flags;
+#endif
+#endif
+ union sctp_sockstore _l_addr; /* remote peer addr */
+ struct sctp_ifa *_s_addr; /* our selected src addr */
+};
+
+struct htcp {
+ uint16_t alpha; /* Fixed point arith, << 7 */
+ uint8_t beta; /* Fixed point arith, << 7 */
+ uint8_t modeswitch; /* Delay modeswitch until we had at least one congestion event */
+ uint32_t last_cong; /* Time since last congestion event end */
+ uint32_t undo_last_cong;
+ uint16_t bytes_acked;
+ uint32_t bytecount;
+ uint32_t minRTT;
+ uint32_t maxRTT;
+
+ uint32_t undo_maxRTT;
+ uint32_t undo_old_maxB;
+
+ /* Bandwidth estimation */
+ uint32_t minB;
+ uint32_t maxB;
+ uint32_t old_maxB;
+ uint32_t Bi;
+ uint32_t lasttime;
+};
+
+struct rtcc_cc {
+ struct timeval tls; /* The time we started the sending */
+ uint64_t lbw; /* Our last estimated bw */
+ uint64_t lbw_rtt; /* RTT at bw estimate */
+ uint64_t bw_bytes; /* The total bytes since this sending began */
+ uint64_t bw_tot_time; /* The total time since sending began */
+ uint64_t new_tot_time; /* temp holding the new value */
+ uint64_t bw_bytes_at_last_rttc; /* What bw_bytes was at last rtt calc */
+ uint32_t cwnd_at_bw_set; /* Cwnd at last bw saved - lbw */
+ uint32_t vol_reduce; /* cnt of voluntary reductions */
+ uint16_t steady_step; /* The number required to be in steady state*/
+ uint16_t step_cnt; /* The current number */
+ uint8_t ret_from_eq; /* When all things are equal what do I return 0/1 - 1 no cc advance */
+ uint8_t use_dccc_ecn; /* Flag to enable DCCC ECN */
+ uint8_t tls_needs_set; /* Flag to indicate we need to set tls 0 or 1 means set at send 2 not */
+ uint8_t last_step_state; /* Last state if steady state stepdown is on */
+ uint8_t rtt_set_this_sack; /* Flag saying this sack had RTT calc on it */
+ uint8_t last_inst_ind; /* Last saved inst indication */
+};
+
+struct sctp_nets {
+ TAILQ_ENTRY(sctp_nets) sctp_next; /* next link */
+
+ /*
+ * Things on the top half may be able to be split into a common
+ * structure shared by all.
+ */
+ struct sctp_timer pmtu_timer;
+ struct sctp_timer hb_timer;
+
+ /*
+ * The following two in combination equate to a route entry for v6
+ * or v4.
+ */
+ struct sctp_net_route ro;
+
+ /* mtu discovered so far */
+ uint32_t mtu;
+ uint32_t ssthresh; /* not sure about this one for split */
+ uint32_t last_cwr_tsn;
+ uint32_t cwr_window_tsn;
+ uint32_t ecn_ce_pkt_cnt;
+ uint32_t lost_cnt;
+ /* smoothed average things for RTT and RTO itself */
+ int lastsa;
+ int lastsv;
+ uint64_t rtt; /* last measured rtt value in us */
+ uint32_t RTO;
+
+ /* This is used for SHUTDOWN/SHUTDOWN-ACK/SEND or INIT timers */
+ struct sctp_timer rxt_timer;
+
+ /* last time in seconds I sent to it */
+ struct timeval last_sent_time;
+ union cc_control_data {
+ struct htcp htcp_ca; /* JRS - struct used in HTCP algorithm */
+ struct rtcc_cc rtcc; /* rtcc module cc stuff */
+ } cc_mod;
+ int ref_count;
+
+ /* Congestion stats per destination */
+ /*
+ * flight size variables and such, sorry Vern, I could not avoid
+ * this if I wanted performance :>
+ */
+ uint32_t flight_size;
+ uint32_t cwnd; /* actual cwnd */
+ uint32_t prev_cwnd; /* cwnd before any processing */
+ uint32_t ecn_prev_cwnd; /* ECN prev cwnd at first ecn_echo seen in new window */
+ uint32_t partial_bytes_acked; /* in CA tracks when to incr a MTU */
+ /* tracking variables to avoid the aloc/free in sack processing */
+ unsigned int net_ack;
+ unsigned int net_ack2;
+
+ /*
+ * JRS - 5/8/07 - Variable to track last time
+ * a destination was active for CMT PF
+ */
+ uint32_t last_active;
+
+ /*
+ * CMT variables (iyengar@cis.udel.edu)
+ */
+ uint32_t this_sack_highest_newack; /* tracks highest TSN newly
+ * acked for a given dest in
+ * the current SACK. Used in
+ * SFR and HTNA algos */
+ uint32_t pseudo_cumack; /* CMT CUC algorithm. Maintains next expected
+ * pseudo-cumack for this destination */
+ uint32_t rtx_pseudo_cumack; /* CMT CUC algorithm. Maintains next
+ * expected pseudo-cumack for this
+ * destination */
+
+ /* CMT fast recovery variables */
+ uint32_t fast_recovery_tsn;
+ uint32_t heartbeat_random1;
+ uint32_t heartbeat_random2;
+#ifdef INET6
+ uint32_t flowlabel;
+#endif
+ uint8_t dscp;
+
+ struct timeval start_time; /* time when this net was created */
+ uint32_t marked_retrans; /* number or DATA chunks marked for
+ timer based retransmissions */
+ uint32_t marked_fastretrans;
+ uint32_t heart_beat_delay; /* Heart Beat delay in ms */
+
+ /* if this guy is ok or not ... status */
+ uint16_t dest_state;
+ /* number of timeouts to consider the destination unreachable */
+ uint16_t failure_threshold;
+ /* number of timeouts to consider the destination potentially failed */
+ uint16_t pf_threshold;
+ /* error stats on the destination */
+ uint16_t error_count;
+ /* UDP port number in case of UDP tunneling */
+ uint16_t port;
+
+ uint8_t fast_retran_loss_recovery;
+ uint8_t will_exit_fast_recovery;
+ /* Flags that probably can be combined into dest_state */
+ uint8_t fast_retran_ip; /* fast retransmit in progress */
+ uint8_t hb_responded;
+ uint8_t saw_newack; /* CMT's SFR algorithm flag */
+ uint8_t src_addr_selected; /* if we split we move */
+ uint8_t indx_of_eligible_next_to_use;
+ uint8_t addr_is_local; /* its a local address (if known) could move
+ * in split */
+
+ /*
+ * CMT variables (iyengar@cis.udel.edu)
+ */
+ uint8_t find_pseudo_cumack; /* CMT CUC algorithm. Flag used to
+ * find a new pseudocumack. This flag
+ * is set after a new pseudo-cumack
+ * has been received and indicates
+ * that the sender should find the
+ * next pseudo-cumack expected for
+ * this destination */
+ uint8_t find_rtx_pseudo_cumack; /* CMT CUCv2 algorithm. Flag used to
+ * find a new rtx-pseudocumack. This
+ * flag is set after a new
+ * rtx-pseudo-cumack has been received
+ * and indicates that the sender
+ * should find the next
+ * rtx-pseudo-cumack expected for this
+ * destination */
+ uint8_t new_pseudo_cumack; /* CMT CUC algorithm. Flag used to
+ * indicate if a new pseudo-cumack or
+ * rtx-pseudo-cumack has been received */
+ uint8_t window_probe; /* Doing a window probe? */
+ uint8_t RTO_measured; /* Have we done the first measure */
+ uint8_t last_hs_used; /* index into the last HS table entry we used */
+ uint8_t lan_type;
+ uint8_t rto_needed;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint32_t flowid;
+ uint8_t flowtype;
+#endif
+};
+
+struct sctp_data_chunkrec {
+ uint32_t tsn; /* the TSN of this transmit */
+ uint32_t mid; /* the message identifier of this transmit */
+ uint16_t sid; /* the stream number of this guy */
+ uint32_t ppid;
+ uint32_t context; /* from send */
+ uint32_t cwnd_at_send;
+ /*
+ * part of the Highest sacked algorithm to be able to stroke counts
+ * on ones that are FR'd.
+ */
+ uint32_t fast_retran_tsn; /* sending_seq at the time of FR */
+ struct timeval timetodrop; /* time we drop it from queue */
+ uint32_t fsn; /* Fragment Sequence Number */
+ uint8_t doing_fast_retransmit;
+ uint8_t rcv_flags; /* flags pulled from data chunk on inbound for
+ * outbound holds sending flags for PR-SCTP.
+ */
+ uint8_t state_flags;
+ uint8_t chunk_was_revoked;
+ uint8_t fwd_tsn_cnt;
+};
+
+TAILQ_HEAD(sctpchunk_listhead, sctp_tmit_chunk);
+
+/* The lower byte is used to enumerate PR_SCTP policies */
+#define CHUNK_FLAGS_PR_SCTP_TTL SCTP_PR_SCTP_TTL
+#define CHUNK_FLAGS_PR_SCTP_BUF SCTP_PR_SCTP_BUF
+#define CHUNK_FLAGS_PR_SCTP_RTX SCTP_PR_SCTP_RTX
+
+/* The upper byte is used as a bit mask */
+#define CHUNK_FLAGS_FRAGMENT_OK 0x0100
+
+struct chk_id {
+ uint8_t id;
+ uint8_t can_take_data;
+};
+
+struct sctp_tmit_chunk {
+ union {
+ struct sctp_data_chunkrec data;
+ struct chk_id chunk_id;
+ } rec;
+ struct sctp_association *asoc; /* bp to asoc this belongs to */
+ struct timeval sent_rcv_time; /* filled in if RTT being calculated */
+ struct mbuf *data; /* pointer to mbuf chain of data */
+ struct mbuf *last_mbuf; /* pointer to last mbuf in chain */
+ struct sctp_nets *whoTo;
+ TAILQ_ENTRY(sctp_tmit_chunk) sctp_next; /* next link */
+ int32_t sent; /* the send status */
+ uint16_t snd_count; /* number of times I sent */
+ uint16_t flags; /* flags, such as FRAGMENT_OK */
+ uint16_t send_size;
+ uint16_t book_size;
+ uint16_t mbcnt;
+ uint16_t auth_keyid;
+ uint8_t holds_key_ref; /* flag if auth keyid refcount is held */
+ uint8_t pad_inplace;
+ uint8_t do_rtt;
+ uint8_t book_size_scale;
+ uint8_t no_fr_allowed;
+ uint8_t copy_by_ref;
+ uint8_t window_probe;
+};
+
+struct sctp_queued_to_read { /* sinfo structure Pluse more */
+ uint16_t sinfo_stream; /* off the wire */
+ uint16_t sinfo_flags; /* SCTP_UNORDERED from wire use SCTP_EOF for
+ * EOR */
+ uint32_t sinfo_ppid; /* off the wire */
+ uint32_t sinfo_context; /* pick this up from assoc def context? */
+ uint32_t sinfo_timetolive; /* not used by kernel */
+ uint32_t sinfo_tsn; /* Use this in reassembly as first TSN */
+ uint32_t sinfo_cumtsn; /* Use this in reassembly as last TSN */
+ sctp_assoc_t sinfo_assoc_id; /* our assoc id */
+ /* Non sinfo stuff */
+ uint32_t mid; /* Fragment Index */
+ uint32_t length; /* length of data */
+ uint32_t held_length; /* length held in sb */
+ uint32_t top_fsn; /* Highest FSN in queue */
+ uint32_t fsn_included; /* Highest FSN in *data portion */
+ struct sctp_nets *whoFrom; /* where it came from */
+ struct mbuf *data; /* front of the mbuf chain of data with
+ * PKT_HDR */
+ struct mbuf *tail_mbuf; /* used for multi-part data */
+ struct mbuf *aux_data; /* used to hold/cache control if o/s does not take it from us */
+ struct sctp_tcb *stcb; /* assoc, used for window update */
+ TAILQ_ENTRY(sctp_queued_to_read) next;
+ TAILQ_ENTRY(sctp_queued_to_read) next_instrm;
+ struct sctpchunk_listhead reasm;
+ uint16_t port_from;
+ uint16_t spec_flags; /* Flags to hold the notification field */
+ uint8_t do_not_ref_stcb;
+ uint8_t end_added;
+ uint8_t pdapi_aborted;
+ uint8_t pdapi_started;
+ uint8_t some_taken;
+ uint8_t last_frag_seen;
+ uint8_t first_frag_seen;
+ uint8_t on_read_q;
+ uint8_t on_strm_q;
+};
+
+#define SCTP_ON_ORDERED 1
+#define SCTP_ON_UNORDERED 2
+
+/* This data structure will be on the outbound
+ * stream queues. Data will be pulled off from
+ * the front of the mbuf data and chunk-ified
+ * by the output routines. We will custom
+ * fit every chunk we pull to the send/sent
+ * queue to make up the next full packet
+ * if we can. An entry cannot be removed
+ * from the stream_out queue until
+ * the msg_is_complete flag is set. This
+ * means at times data/tail_mbuf MIGHT
+ * be NULL.. If that occurs it happens
+ * for one of two reasons. Either the user
+ * is blocked on a send() call and has not
+ * awoken to copy more data down... OR
+ * the user is in the explict MSG_EOR mode
+ * and wrote some data, but has not completed
+ * sending.
+ * ss_next and scheduled are only used by the FCFS stream scheduler.
+ */
+struct sctp_stream_queue_pending {
+ struct mbuf *data;
+ struct mbuf *tail_mbuf;
+ struct timeval ts;
+ struct sctp_nets *net;
+ TAILQ_ENTRY (sctp_stream_queue_pending) next;
+ TAILQ_ENTRY (sctp_stream_queue_pending) ss_next;
+ uint32_t fsn;
+ uint32_t length;
+ uint32_t timetolive;
+ uint32_t ppid;
+ uint32_t context;
+ uint16_t sinfo_flags;
+ uint16_t sid;
+ uint16_t act_flags;
+ uint16_t auth_keyid;
+ uint8_t holds_key_ref;
+ uint8_t msg_is_complete;
+ uint8_t some_taken;
+ uint8_t sender_all_done;
+ uint8_t put_last_out;
+ uint8_t discard_rest;
+ uint8_t processing;
+ bool scheduled;
+};
+
+/*
+ * this struct contains info that is used to track inbound stream data and
+ * help with ordering.
+ */
+TAILQ_HEAD(sctpwheelunrel_listhead, sctp_stream_in);
+struct sctp_stream_in {
+ struct sctp_readhead inqueue;
+ struct sctp_readhead uno_inqueue;
+ uint32_t last_mid_delivered; /* used for re-order */
+ uint16_t sid;
+ uint8_t delivery_started;
+ uint8_t pd_api_started;
+};
+
+TAILQ_HEAD(sctpwheel_listhead, sctp_stream_out);
+TAILQ_HEAD(sctplist_listhead, sctp_stream_queue_pending);
+
+/*
+ * This union holds all data necessary for
+ * different stream schedulers.
+ */
+struct scheduling_data {
+ struct sctp_stream_out *locked_on_sending;
+ /* circular looking for output selection */
+ struct sctp_stream_out *last_out_stream;
+ union {
+ struct sctpwheel_listhead wheel;
+ struct sctplist_listhead list;
+ } out;
+};
+
+/* Round-robin schedulers */
+struct ss_rr {
+ /* next link in wheel */
+ TAILQ_ENTRY(sctp_stream_out) next_spoke;
+};
+
+/* Priority scheduler */
+struct ss_prio {
+ /* next link in wheel */
+ TAILQ_ENTRY(sctp_stream_out) next_spoke;
+ /* priority id */
+ uint16_t priority;
+};
+
+/* Fair Bandwidth scheduler */
+struct ss_fb {
+ /* next link in wheel */
+ TAILQ_ENTRY(sctp_stream_out) next_spoke;
+ /* stores message size */
+ int32_t rounds;
+};
+
+/*
+ * This union holds all parameters per stream
+ * necessary for different stream schedulers.
+ */
+struct scheduling_parameters {
+ union {
+ struct ss_rr rr;
+ struct ss_prio prio;
+ struct ss_fb fb;
+ } ss;
+ bool scheduled;
+};
+
+/* States for outgoing streams */
+#define SCTP_STREAM_CLOSED 0x00
+#define SCTP_STREAM_OPENING 0x01
+#define SCTP_STREAM_OPEN 0x02
+#define SCTP_STREAM_RESET_PENDING 0x03
+#define SCTP_STREAM_RESET_IN_FLIGHT 0x04
+
+/* This struct is used to track the traffic on outbound streams */
+struct sctp_stream_out {
+ struct sctp_streamhead outqueue;
+ struct scheduling_parameters ss_params;
+ uint32_t chunks_on_queues; /* send queue and sent queue */
+#if defined(SCTP_DETAILED_STR_STATS)
+ uint32_t abandoned_unsent[SCTP_PR_SCTP_MAX + 1];
+ uint32_t abandoned_sent[SCTP_PR_SCTP_MAX + 1];
+#else
+ /* Only the aggregation */
+ uint32_t abandoned_unsent[1];
+ uint32_t abandoned_sent[1];
+#endif
+ /* For associations using DATA chunks, the lower 16-bit of
+ * next_mid_ordered are used as the next SSN.
+ */
+ uint32_t next_mid_ordered;
+ uint32_t next_mid_unordered;
+ uint16_t sid;
+ uint8_t last_msg_incomplete;
+ uint8_t state;
+};
+
+#define SCTP_MAX_STREAMS_AT_ONCE_RESET 200
+
+/* used to keep track of the addresses yet to try to add/delete */
+TAILQ_HEAD(sctp_asconf_addrhead, sctp_asconf_addr);
+struct sctp_asconf_addr {
+ TAILQ_ENTRY(sctp_asconf_addr) next;
+ struct sctp_asconf_addr_param ap;
+ struct sctp_ifa *ifa; /* save the ifa for add/del ip */
+ uint8_t sent; /* has this been sent yet? */
+ uint8_t special_del; /* not to be used in lookup */
+};
+
+struct sctp_scoping {
+ uint8_t ipv4_addr_legal;
+ uint8_t ipv6_addr_legal;
+#if defined(__Userspace__)
+ uint8_t conn_addr_legal;
+#endif
+ uint8_t loopback_scope;
+ uint8_t ipv4_local_scope;
+ uint8_t local_scope;
+ uint8_t site_scope;
+};
+
+#define SCTP_TSN_LOG_SIZE 40
+
+struct sctp_tsn_log {
+ void *stcb;
+ uint32_t tsn;
+ uint32_t seq;
+ uint16_t strm;
+ uint16_t sz;
+ uint16_t flgs;
+ uint16_t in_pos;
+ uint16_t in_out;
+ uint16_t resv;
+};
+
+#define SCTP_FS_SPEC_LOG_SIZE 200
+struct sctp_fs_spec_log {
+ uint32_t sent;
+ uint32_t total_flight;
+ uint32_t tsn;
+ uint16_t book;
+ uint8_t incr;
+ uint8_t decr;
+};
+
+/*
+ * JRS - Structure to hold function pointers to the functions responsible
+ * for congestion control.
+ */
+
+struct sctp_cc_functions {
+ void (*sctp_set_initial_cc_param)(struct sctp_tcb *stcb, struct sctp_nets *net);
+ void (*sctp_cwnd_update_after_sack)(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ int accum_moved ,int reneged_all, int will_exit);
+ void (*sctp_cwnd_update_exit_pf)(struct sctp_tcb *stcb, struct sctp_nets *net);
+ void (*sctp_cwnd_update_after_fr)(struct sctp_tcb *stcb,
+ struct sctp_association *asoc);
+ void (*sctp_cwnd_update_after_timeout)(struct sctp_tcb *stcb,
+ struct sctp_nets *net);
+ void (*sctp_cwnd_update_after_ecn_echo)(struct sctp_tcb *stcb,
+ struct sctp_nets *net, int in_window, int num_pkt_lost);
+ void (*sctp_cwnd_update_after_packet_dropped)(struct sctp_tcb *stcb,
+ struct sctp_nets *net, struct sctp_pktdrop_chunk *cp,
+ uint32_t *bottle_bw, uint32_t *on_queue);
+ void (*sctp_cwnd_update_after_output)(struct sctp_tcb *stcb,
+ struct sctp_nets *net, int burst_limit);
+ void (*sctp_cwnd_update_packet_transmitted)(struct sctp_tcb *stcb,
+ struct sctp_nets *net);
+ void (*sctp_cwnd_update_tsn_acknowledged)(struct sctp_nets *net,
+ struct sctp_tmit_chunk *);
+ void (*sctp_cwnd_new_transmission_begins)(struct sctp_tcb *stcb,
+ struct sctp_nets *net);
+ void (*sctp_cwnd_prepare_net_for_sack)(struct sctp_tcb *stcb,
+ struct sctp_nets *net);
+ int (*sctp_cwnd_socket_option)(struct sctp_tcb *stcb, int set, struct sctp_cc_option *);
+ void (*sctp_rtt_calculated)(struct sctp_tcb *, struct sctp_nets *, struct timeval *);
+};
+
+/*
+ * RS - Structure to hold function pointers to the functions responsible
+ * for stream scheduling.
+ */
+struct sctp_ss_functions {
+ void (*sctp_ss_init)(struct sctp_tcb *stcb, struct sctp_association *asoc);
+ void (*sctp_ss_clear)(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ bool clear_values);
+ void (*sctp_ss_init_stream)(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq);
+ void (*sctp_ss_add_to_stream)(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp);
+ bool (*sctp_ss_is_empty)(struct sctp_tcb *stcb, struct sctp_association *asoc);
+ void (*sctp_ss_remove_from_stream)(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp);
+ struct sctp_stream_out* (*sctp_ss_select_stream)(struct sctp_tcb *stcb,
+ struct sctp_nets *net, struct sctp_association *asoc);
+ void (*sctp_ss_scheduled)(struct sctp_tcb *stcb, struct sctp_nets *net,
+ struct sctp_association *asoc, struct sctp_stream_out *strq, int moved_how_much);
+ void (*sctp_ss_packet_done)(struct sctp_tcb *stcb, struct sctp_nets *net,
+ struct sctp_association *asoc);
+ int (*sctp_ss_get_value)(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, uint16_t *value);
+ int (*sctp_ss_set_value)(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_stream_out *strq, uint16_t value);
+ bool (*sctp_ss_is_user_msgs_incomplete)(struct sctp_tcb *stcb, struct sctp_association *asoc);
+};
+
+/* used to save ASCONF chunks for retransmission */
+TAILQ_HEAD(sctp_asconf_head, sctp_asconf);
+struct sctp_asconf {
+ TAILQ_ENTRY(sctp_asconf) next;
+ uint32_t serial_number;
+ uint16_t snd_count;
+ struct mbuf *data;
+ uint16_t len;
+};
+
+/* used to save ASCONF-ACK chunks for retransmission */
+TAILQ_HEAD(sctp_asconf_ackhead, sctp_asconf_ack);
+struct sctp_asconf_ack {
+ TAILQ_ENTRY(sctp_asconf_ack) next;
+ uint32_t serial_number;
+ struct sctp_nets *last_sent_to;
+ struct mbuf *data;
+ uint16_t len;
+};
+
+/*
+ * Here we have information about each individual association that we track.
+ * We probably in production would be more dynamic. But for ease of
+ * implementation we will have a fixed array that we hunt for in a linear
+ * fashion.
+ */
+struct sctp_association {
+ /* association state */
+ int state;
+
+ /* queue of pending addrs to add/delete */
+ struct sctp_asconf_addrhead asconf_queue;
+
+ struct timeval time_entered; /* time we entered state */
+ struct timeval time_last_rcvd;
+ struct timeval time_last_sent;
+ struct timeval time_last_sat_advance;
+ struct sctp_nonpad_sndrcvinfo def_send;
+
+ /* timers and such */
+ struct sctp_timer dack_timer; /* Delayed ack timer */
+ struct sctp_timer asconf_timer; /* asconf */
+ struct sctp_timer strreset_timer; /* stream reset */
+ struct sctp_timer shut_guard_timer; /* shutdown guard */
+ struct sctp_timer autoclose_timer; /* automatic close timer */
+ struct sctp_timer delete_prim_timer; /* deleting primary dst */
+
+ /* list of restricted local addresses */
+ struct sctpladdr sctp_restricted_addrs;
+
+ /* last local address pending deletion (waiting for an address add) */
+ struct sctp_ifa *asconf_addr_del_pending;
+ /* Deleted primary destination (used to stop timer) */
+ struct sctp_nets *deleted_primary;
+
+ struct sctpnetlisthead nets; /* remote address list */
+
+ /* Free chunk list */
+ struct sctpchunk_listhead free_chunks;
+
+ /* Control chunk queue */
+ struct sctpchunk_listhead control_send_queue;
+
+ /* ASCONF chunk queue */
+ struct sctpchunk_listhead asconf_send_queue;
+
+ /*
+ * Once a TSN hits the wire it is moved to the sent_queue. We
+ * maintain two counts here (don't know if any but retran_cnt is
+ * needed). The idea is that the sent_queue_retran_cnt reflects how
+ * many chunks have been marked for retranmission by either T3-rxt
+ * or FR.
+ */
+ struct sctpchunk_listhead sent_queue;
+ struct sctpchunk_listhead send_queue;
+
+ /* Scheduling queues */
+ struct scheduling_data ss_data;
+
+ /* If an iterator is looking at me, this is it */
+ struct sctp_iterator *stcb_starting_point_for_iterator;
+
+ /* ASCONF save the last ASCONF-ACK so we can resend it if necessary */
+ struct sctp_asconf_ackhead asconf_ack_sent;
+
+ /*
+ * pointer to last stream reset queued to control queue by us with
+ * requests.
+ */
+ struct sctp_tmit_chunk *str_reset;
+ /*
+ * if Source Address Selection happening, this will rotate through
+ * the link list.
+ */
+ struct sctp_laddr *last_used_address;
+
+ /* stream arrays */
+ struct sctp_stream_in *strmin;
+ struct sctp_stream_out *strmout;
+ uint8_t *mapping_array;
+ /* primary destination to use */
+ struct sctp_nets *primary_destination;
+ struct sctp_nets *alternate; /* If primary is down or PF */
+ /* For CMT */
+ struct sctp_nets *last_net_cmt_send_started;
+ /* last place I got a data chunk from */
+ struct sctp_nets *last_data_chunk_from;
+ /* last place I got a control from */
+ struct sctp_nets *last_control_chunk_from;
+
+ /*
+ * wait to the point the cum-ack passes req->send_reset_at_tsn for
+ * any req on the list.
+ */
+ struct sctp_resethead resetHead;
+
+ /* queue of chunks waiting to be sent into the local stack */
+ struct sctp_readhead pending_reply_queue;
+
+ /* JRS - the congestion control functions are in this struct */
+ struct sctp_cc_functions cc_functions;
+ /* JRS - value to store the currently loaded congestion control module */
+ uint32_t congestion_control_module;
+ /* RS - the stream scheduling functions are in this struct */
+ struct sctp_ss_functions ss_functions;
+ /* RS - value to store the currently loaded stream scheduling module */
+ uint32_t stream_scheduling_module;
+
+ uint32_t vrf_id;
+ uint32_t cookie_preserve_req;
+ /* ASCONF next seq I am sending out, inits at init-tsn */
+ uint32_t asconf_seq_out;
+ uint32_t asconf_seq_out_acked;
+ /* ASCONF last received ASCONF from peer, starts at peer's TSN-1 */
+ uint32_t asconf_seq_in;
+
+ /* next seq I am sending in str reset messages */
+ uint32_t str_reset_seq_out;
+ /* next seq I am expecting in str reset messages */
+ uint32_t str_reset_seq_in;
+
+ /* various verification tag information */
+ uint32_t my_vtag; /* The tag to be used. if assoc is re-initited
+ * by remote end, and I have unlocked this
+ * will be regenerated to a new random value. */
+ uint32_t peer_vtag; /* The peers last tag */
+
+ uint32_t my_vtag_nonce;
+ uint32_t peer_vtag_nonce;
+
+ uint32_t assoc_id;
+
+ /* This is the SCTP fragmentation threshold */
+ uint32_t smallest_mtu;
+
+ /*
+ * Special hook for Fast retransmit, allows us to track the highest
+ * TSN that is NEW in this SACK if gap ack blocks are present.
+ */
+ uint32_t this_sack_highest_gap;
+
+ /*
+ * The highest consecutive TSN that has been acked by peer on my
+ * sends
+ */
+ uint32_t last_acked_seq;
+
+ /* The next TSN that I will use in sending. */
+ uint32_t sending_seq;
+
+ /* Original seq number I used ??questionable to keep?? */
+ uint32_t init_seq_number;
+
+ /* The Advanced Peer Ack Point, as required by the PR-SCTP */
+ /* (A1 in Section 4.2) */
+ uint32_t advanced_peer_ack_point;
+
+ /*
+ * The highest consequetive TSN at the bottom of the mapping array
+ * (for his sends).
+ */
+ uint32_t cumulative_tsn;
+ /*
+ * Used to track the mapping array and its offset bits. This MAY be
+ * lower then cumulative_tsn.
+ */
+ uint32_t mapping_array_base_tsn;
+ /*
+ * used to track highest TSN we have received and is listed in the
+ * mapping array.
+ */
+ uint32_t highest_tsn_inside_map;
+
+ /* EY - new NR variables used for nr_sack based on mapping_array*/
+ uint8_t *nr_mapping_array;
+ uint32_t highest_tsn_inside_nr_map;
+
+ uint32_t fast_recovery_tsn;
+ uint32_t sat_t3_recovery_tsn;
+ uint32_t tsn_last_delivered;
+ uint32_t tsn_of_pdapi_last_delivered;
+ uint32_t pdapi_ppid;
+ uint32_t context;
+ uint32_t last_reset_action[SCTP_MAX_RESET_PARAMS];
+ uint32_t last_sending_seq[SCTP_MAX_RESET_PARAMS];
+ uint32_t last_base_tsnsent[SCTP_MAX_RESET_PARAMS];
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ /*
+ * special log - This adds considerable size
+ * to the asoc, but provides a log that you
+ * can use to detect problems via kgdb.
+ */
+ struct sctp_tsn_log in_tsnlog[SCTP_TSN_LOG_SIZE];
+ struct sctp_tsn_log out_tsnlog[SCTP_TSN_LOG_SIZE];
+ uint32_t cumack_log[SCTP_TSN_LOG_SIZE];
+ uint32_t cumack_logsnt[SCTP_TSN_LOG_SIZE];
+ uint16_t tsn_in_at;
+ uint16_t tsn_out_at;
+ uint16_t tsn_in_wrapped;
+ uint16_t tsn_out_wrapped;
+ uint16_t cumack_log_at;
+ uint16_t cumack_log_atsnt;
+#endif /* SCTP_ASOCLOG_OF_TSNS */
+#ifdef SCTP_FS_SPEC_LOG
+ struct sctp_fs_spec_log fslog[SCTP_FS_SPEC_LOG_SIZE];
+ uint16_t fs_index;
+#endif
+
+ /*
+ * window state information and smallest MTU that I use to bound
+ * segmentation
+ */
+ uint32_t peers_rwnd;
+ uint32_t my_rwnd;
+ uint32_t my_last_reported_rwnd;
+ uint32_t sctp_frag_point;
+
+ uint32_t total_output_queue_size;
+
+ uint32_t sb_cc; /* shadow of sb_cc */
+ uint32_t sb_send_resv; /* amount reserved on a send */
+ uint32_t my_rwnd_control_len; /* shadow of sb_mbcnt used for rwnd control */
+#ifdef INET6
+ uint32_t default_flowlabel;
+#endif
+ uint32_t pr_sctp_cnt;
+ int ctrl_queue_cnt; /* could be removed REM - NO IT CAN'T!! RRS */
+ /*
+ * All outbound datagrams queue into this list from the individual
+ * stream queue. Here they get assigned a TSN and then await
+ * sending. The stream seq comes when it is first put in the
+ * individual str queue
+ */
+ unsigned int stream_queue_cnt;
+ unsigned int send_queue_cnt;
+ unsigned int sent_queue_cnt;
+ unsigned int sent_queue_cnt_removeable;
+ /*
+ * Number on sent queue that are marked for retran until this value
+ * is 0 we only send one packet of retran'ed data.
+ */
+ unsigned int sent_queue_retran_cnt;
+
+ unsigned int size_on_reasm_queue;
+ unsigned int cnt_on_reasm_queue;
+ unsigned int fwd_tsn_cnt;
+ /* amount of data (bytes) currently in flight (on all destinations) */
+ unsigned int total_flight;
+ /* Total book size in flight */
+ unsigned int total_flight_count; /* count of chunks used with
+ * book total */
+ /* count of destinaton nets and list of destination nets */
+ unsigned int numnets;
+
+ /* Total error count on this association */
+ unsigned int overall_error_count;
+
+ unsigned int cnt_msg_on_sb;
+
+ /* All stream count of chunks for delivery */
+ unsigned int size_on_all_streams;
+ unsigned int cnt_on_all_streams;
+
+ /* Heart Beat delay in ms */
+ uint32_t heart_beat_delay;
+
+ /* autoclose */
+ uint32_t sctp_autoclose_ticks;
+
+ /* how many preopen streams we have */
+ unsigned int pre_open_streams;
+
+ /* How many streams I support coming into me */
+ unsigned int max_inbound_streams;
+
+ /* the cookie life I award for any cookie, in seconds */
+ uint32_t cookie_life;
+ /* time to delay acks for */
+ unsigned int delayed_ack;
+ unsigned int old_delayed_ack;
+ unsigned int sack_freq;
+ unsigned int data_pkts_seen;
+
+ unsigned int numduptsns;
+ int dup_tsns[SCTP_MAX_DUP_TSNS];
+ uint32_t initial_init_rto_max; /* initial RTO for INIT's */
+ uint32_t initial_rto; /* initial send RTO */
+ uint32_t minrto; /* per assoc RTO-MIN */
+ uint32_t maxrto; /* per assoc RTO-MAX */
+
+ /* authentication fields */
+ sctp_auth_chklist_t *local_auth_chunks;
+ sctp_auth_chklist_t *peer_auth_chunks;
+ sctp_hmaclist_t *local_hmacs; /* local HMACs supported */
+ sctp_hmaclist_t *peer_hmacs; /* peer HMACs supported */
+ struct sctp_keyhead shared_keys; /* assoc's shared keys */
+ sctp_authinfo_t authinfo; /* randoms, cached keys */
+ /*
+ * refcnt to block freeing when a sender or receiver is off coping
+ * user data in.
+ */
+ uint32_t refcnt;
+ uint32_t chunks_on_out_queue; /* total chunks floating around,
+ * locked by send socket buffer */
+ uint32_t peers_adaptation;
+ uint32_t default_mtu;
+ uint16_t peer_hmac_id; /* peer HMAC id to send */
+
+ /*
+ * Being that we have no bag to collect stale cookies, and that we
+ * really would not want to anyway.. we will count them in this
+ * counter. We of course feed them to the pigeons right away (I have
+ * always thought of pigeons as flying rats).
+ */
+ uint16_t stale_cookie_count;
+
+ /*
+ * For the partial delivery API, if up, invoked this is what last
+ * TSN I delivered
+ */
+ uint16_t str_of_pdapi;
+ uint16_t ssn_of_pdapi;
+
+ /* counts of actual built streams. Allocation may be more however */
+ /* could re-arrange to optimize space here. */
+ uint16_t streamincnt;
+ uint16_t streamoutcnt;
+ uint16_t strm_realoutsize;
+ uint16_t strm_pending_add_size;
+ /* my maximum number of retrans of INIT and SEND */
+ /* copied from SCTP but should be individually setable */
+ uint16_t max_init_times;
+ uint16_t max_send_times;
+
+ uint16_t def_net_failure;
+
+ uint16_t def_net_pf_threshold;
+
+ /*
+ * lock flag: 0 is ok to send, 1+ (duals as a retran count) is
+ * awaiting ACK
+ */
+ uint16_t mapping_array_size;
+
+ uint16_t last_strm_seq_delivered;
+ uint16_t last_strm_no_delivered;
+
+ uint16_t last_revoke_count;
+ int16_t num_send_timers_up;
+
+ uint16_t stream_locked_on;
+ uint16_t ecn_echo_cnt_onq;
+
+ uint16_t free_chunk_cnt;
+ uint8_t stream_locked;
+ uint8_t authenticated; /* packet authenticated ok */
+ /*
+ * This flag indicates that a SACK need to be sent.
+ * Initially this is 1 to send the first sACK immediately.
+ */
+ uint8_t send_sack;
+
+ /* max burst of new packets into the network */
+ uint32_t max_burst;
+ /* max burst of fast retransmit packets */
+ uint32_t fr_max_burst;
+
+ uint8_t sat_network; /* RTT is in range of sat net or greater */
+ uint8_t sat_network_lockout; /* lockout code */
+ uint8_t burst_limit_applied; /* Burst limit in effect at last send? */
+ /* flag goes on when we are doing a partial delivery api */
+ uint8_t hb_random_values[4];
+ uint8_t fragmented_delivery_inprogress;
+ uint8_t fragment_flags;
+ uint8_t last_flags_delivered;
+ uint8_t hb_ect_randombit;
+ uint8_t hb_random_idx;
+ uint8_t default_dscp;
+ uint8_t asconf_del_pending; /* asconf delete last addr pending */
+ uint8_t trigger_reset;
+ /*
+ * This value, plus all other ack'd but above cum-ack is added
+ * together to cross check against the bit that we have yet to
+ * define (probably in the SACK). When the cum-ack is updated, this
+ * sum is updated as well.
+ */
+
+ /* Flags whether an extension is supported or not */
+ uint8_t ecn_supported;
+ uint8_t prsctp_supported;
+ uint8_t auth_supported;
+ uint8_t asconf_supported;
+ uint8_t reconfig_supported;
+ uint8_t nrsack_supported;
+ uint8_t pktdrop_supported;
+ uint8_t idata_supported;
+
+ /* Zero checksum supported information */
+ uint8_t rcv_edmid;
+ uint8_t snd_edmid;
+
+ /* Did the peer make the stream config (add out) request */
+ uint8_t peer_req_out;
+
+ uint8_t local_strreset_support;
+ uint8_t peer_supports_nat;
+
+ struct sctp_scoping scope;
+ /* flags to handle send alternate net tracking */
+ uint8_t used_alt_asconfack;
+ uint8_t fast_retran_loss_recovery;
+ uint8_t sat_t3_loss_recovery;
+ uint8_t dropped_special_cnt;
+ uint8_t seen_a_sack_this_pkt;
+ uint8_t stream_reset_outstanding;
+ uint8_t stream_reset_out_is_outstanding;
+ uint8_t delayed_connection;
+ uint8_t ifp_had_enobuf;
+ uint8_t saw_sack_with_frags;
+ uint8_t saw_sack_with_nr_frags;
+ uint8_t in_asocid_hash;
+ uint8_t assoc_up_sent;
+ uint8_t adaptation_needed;
+ uint8_t adaptation_sent;
+ /* CMT variables */
+ uint8_t cmt_dac_pkts_rcvd;
+ uint8_t sctp_cmt_on_off;
+ uint8_t iam_blocking;
+ uint8_t cookie_how[8];
+ /* JRS 5/21/07 - CMT PF variable */
+ uint8_t sctp_cmt_pf;
+ uint8_t use_precise_time;
+ uint64_t sctp_features;
+ uint32_t max_cwnd;
+ uint16_t port; /* remote UDP encapsulation port */
+ /*
+ * The mapping array is used to track out of order sequences above
+ * last_acked_seq. 0 indicates packet missing 1 indicates packet
+ * rec'd. We slide it up every time we raise last_acked_seq and 0
+ * trailing locactions out. If I get a TSN above the array
+ * mappingArraySz, I discard the datagram and let retransmit happen.
+ */
+ uint32_t marked_retrans;
+ uint32_t timoinit;
+ uint32_t timodata;
+ uint32_t timosack;
+ uint32_t timoshutdown;
+ uint32_t timoheartbeat;
+ uint32_t timocookie;
+ uint32_t timoshutdownack;
+ struct timeval start_time;
+ struct timeval discontinuity_time;
+ uint64_t abandoned_unsent[SCTP_PR_SCTP_MAX + 1];
+ uint64_t abandoned_sent[SCTP_PR_SCTP_MAX + 1];
+};
+
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_sysctl.c b/netwerk/sctp/src/netinet/sctp_sysctl.c
new file mode 100644
index 0000000000..925aaf0313
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sysctl.c
@@ -0,0 +1,1664 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_constants.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/smp.h>
+#include <sys/sysctl.h>
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+#include <netinet/sctp_bsd_addr.h>
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+FEATURE(sctp, "Stream Control Transmission Protocol");
+#endif
+
+/*
+ * sysctl tunable variables
+ */
+
+void
+sctp_init_sysctls(void)
+{
+ SCTP_BASE_SYSCTL(sctp_sendspace) = SCTPCTL_MAXDGRAM_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_recvspace) = SCTPCTL_RECVSPACE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_auto_asconf) = SCTPCTL_AUTOASCONF_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_multiple_asconfs) = SCTPCTL_MULTIPLEASCONFS_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_ecn_enable) = SCTPCTL_ECN_ENABLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_pr_enable) = SCTPCTL_PR_ENABLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_auth_enable) = SCTPCTL_AUTH_ENABLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_asconf_enable) = SCTPCTL_ASCONF_ENABLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_reconfig_enable) = SCTPCTL_RECONFIG_ENABLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_nrsack_enable) = SCTPCTL_NRSACK_ENABLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_pktdrop_enable) = SCTPCTL_PKTDROP_ENABLE_DEFAULT;
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) = SCTPCTL_LOOPBACK_NOCSUM_DEFAULT;
+#endif
+ SCTP_BASE_SYSCTL(sctp_peer_chunk_oh) = SCTPCTL_PEER_CHKOH_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_max_burst_default) = SCTPCTL_MAXBURST_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_fr_max_burst_default) = SCTPCTL_FRMAXBURST_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue) = SCTPCTL_MAXCHUNKS_DEFAULT;
+#if defined(__Userspace__)
+ if (SCTP_BASE_SYSCTL(sctp_hashtblsize) == 0) {
+ SCTP_BASE_SYSCTL(sctp_hashtblsize) = SCTPCTL_TCBHASHSIZE_DEFAULT;
+ }
+#else
+ SCTP_BASE_SYSCTL(sctp_hashtblsize) = SCTPCTL_TCBHASHSIZE_DEFAULT;
+#endif
+#if defined(__Userspace__)
+ if (SCTP_BASE_SYSCTL(sctp_pcbtblsize) == 0) {
+ SCTP_BASE_SYSCTL(sctp_pcbtblsize) = SCTPCTL_PCBHASHSIZE_DEFAULT;
+ }
+#else
+ SCTP_BASE_SYSCTL(sctp_pcbtblsize) = SCTPCTL_PCBHASHSIZE_DEFAULT;
+#endif
+ SCTP_BASE_SYSCTL(sctp_min_split_point) = SCTPCTL_MIN_SPLIT_POINT_DEFAULT;
+#if defined(__Userspace__)
+ if (SCTP_BASE_SYSCTL(sctp_chunkscale) == 0) {
+ SCTP_BASE_SYSCTL(sctp_chunkscale) = SCTPCTL_CHUNKSCALE_DEFAULT;
+ }
+#else
+ SCTP_BASE_SYSCTL(sctp_chunkscale) = SCTPCTL_CHUNKSCALE_DEFAULT;
+#endif
+ SCTP_BASE_SYSCTL(sctp_delayed_sack_time_default) = SCTPCTL_DELAYED_SACK_TIME_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_sack_freq_default) = SCTPCTL_SACK_FREQ_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_system_free_resc_limit) = SCTPCTL_SYS_RESOURCE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_asoc_free_resc_limit) = SCTPCTL_ASOC_RESOURCE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_heartbeat_interval_default) = SCTPCTL_HEARTBEAT_INTERVAL_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_pmtu_raise_time_default) = SCTPCTL_PMTU_RAISE_TIME_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_shutdown_guard_time_default) = SCTPCTL_SHUTDOWN_GUARD_TIME_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_secret_lifetime_default) = SCTPCTL_SECRET_LIFETIME_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_rto_max_default) = SCTPCTL_RTO_MAX_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_rto_min_default) = SCTPCTL_RTO_MIN_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_rto_initial_default) = SCTPCTL_RTO_INITIAL_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_init_rto_max_default) = SCTPCTL_INIT_RTO_MAX_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_valid_cookie_life_default) = SCTPCTL_VALID_COOKIE_LIFE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_init_rtx_max_default) = SCTPCTL_INIT_RTX_MAX_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_assoc_rtx_max_default) = SCTPCTL_ASSOC_RTX_MAX_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_path_rtx_max_default) = SCTPCTL_PATH_RTX_MAX_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_path_pf_threshold) = SCTPCTL_PATH_PF_THRESHOLD_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_add_more_threshold) = SCTPCTL_ADD_MORE_ON_OUTPUT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_nr_incoming_streams_default) = SCTPCTL_INCOMING_STREAMS_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_nr_outgoing_streams_default) = SCTPCTL_OUTGOING_STREAMS_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_cmt_on_off) = SCTPCTL_CMT_ON_OFF_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_cmt_use_dac) = SCTPCTL_CMT_USE_DAC_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_use_cwnd_based_maxburst) = SCTPCTL_CWND_MAXBURST_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_nat_friendly) = SCTPCTL_NAT_FRIENDLY_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_L2_abc_variable) = SCTPCTL_ABC_L_VAR_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count) = SCTPCTL_MAX_CHAINED_MBUFS_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_do_drain) = SCTPCTL_DO_SCTP_DRAIN_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_hb_maxburst) = SCTPCTL_HB_MAX_BURST_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_abort_if_one_2_one_hits_limit) = SCTPCTL_ABORT_AT_LIMIT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_min_residual) = SCTPCTL_MIN_RESIDUAL_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_max_retran_chunk) = SCTPCTL_MAX_RETRAN_CHUNK_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_logging_level) = SCTPCTL_LOGGING_LEVEL_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_default_cc_module) = SCTPCTL_DEFAULT_CC_MODULE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_default_ss_module) = SCTPCTL_DEFAULT_SS_MODULE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_default_frag_interleave) = SCTPCTL_DEFAULT_FRAG_INTERLEAVE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_mobility_base) = SCTPCTL_MOBILITY_BASE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_mobility_fasthandoff) = SCTPCTL_MOBILITY_FASTHANDOFF_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_vtag_time_wait) = SCTPCTL_TIME_WAIT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_buffer_splitting) = SCTPCTL_BUFFER_SPLITTING_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_initial_cwnd) = SCTPCTL_INITIAL_CWND_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_rttvar_bw) = SCTPCTL_RTTVAR_BW_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_rttvar_rtt) = SCTPCTL_RTTVAR_RTT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_rttvar_eqret) = SCTPCTL_RTTVAR_EQRET_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_steady_step) = SCTPCTL_RTTVAR_STEADYS_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_use_dccc_ecn) = SCTPCTL_RTTVAR_DCCCECN_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_blackhole) = SCTPCTL_BLACKHOLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_sendall_limit) = SCTPCTL_SENDALL_LIMIT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_diag_info_code) = SCTPCTL_DIAG_INFO_CODE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_ootb_with_zero_cksum) = SCTPCTL_OOTB_WITH_ZERO_CKSUM_DEFAULT;
+#if defined(SCTP_LOCAL_TRACE_BUF)
+#if defined(_WIN32) && !defined(__Userspace__)
+ /* On Windows, the resource for global variables is limited. */
+ MALLOC(SCTP_BASE_SYSCTL(sctp_log), struct sctp_log *, sizeof(struct sctp_log), M_SYSCTL, M_ZERO);
+#else
+ memset(&SCTP_BASE_SYSCTL(sctp_log), 0, sizeof(struct sctp_log));
+#endif
+#endif
+ SCTP_BASE_SYSCTL(sctp_udp_tunneling_port) = SCTPCTL_UDP_TUNNELING_PORT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_enable_sack_immediately) = SCTPCTL_SACK_IMMEDIATELY_ENABLE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_inits_include_nat_friendly) = SCTPCTL_NAT_FRIENDLY_INITS_DEFAULT;
+#if defined(SCTP_DEBUG)
+ SCTP_BASE_SYSCTL(sctp_debug_on) = SCTPCTL_DEBUG_DEFAULT;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_BASE_SYSCTL(sctp_ignore_vmware_interfaces) = SCTPCTL_IGNORE_VMWARE_INTERFACES_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_main_timer) = SCTPCTL_MAIN_TIMER_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_addr_watchdog_limit) = SCTPCTL_ADDR_WATCHDOG_LIMIT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_vtag_watchdog_limit) = SCTPCTL_VTAG_WATCHDOG_LIMIT_DEFAULT;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_BASE_SYSCTL(sctp_output_unlocked) = SCTPCTL_OUTPUT_UNLOCKED_DEFAULT;
+#endif
+}
+#if defined(_WIN32) && !defined(__Userspace__)
+
+void
+sctp_finish_sysctls()
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ if (SCTP_BASE_SYSCTL(sctp_log) != NULL) {
+ FREE(SCTP_BASE_SYSCTL(sctp_log), M_SYSCTL);
+ SCTP_BASE_SYSCTL(sctp_log) = NULL;
+ }
+#endif
+}
+#endif
+
+#if !defined(__Userspace__)
+/* It returns an upper limit. No filtering is done here */
+static unsigned int
+sctp_sysctl_number_of_addresses(struct sctp_inpcb *inp)
+{
+ unsigned int cnt;
+ struct sctp_vrf *vrf;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+ struct sctp_laddr *laddr;
+
+ cnt = 0;
+ /* neither Mac OS X nor FreeBSD support multiple routing functions */
+ if ((vrf = sctp_find_vrf(inp->def_vrf_id)) == NULL) {
+ return (0);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#endif
+#ifdef INET6
+ case AF_INET6:
+#endif
+ cnt++;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ } else {
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ switch (laddr->ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#endif
+#ifdef INET6
+ case AF_INET6:
+#endif
+ cnt++;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return (cnt);
+}
+
+static int
+sctp_sysctl_copy_out_local_addresses(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sysctl_req *req)
+{
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+ int loopback_scope;
+#ifdef INET
+ int ipv4_local_scope;
+ int ipv4_addr_legal;
+#endif
+#ifdef INET6
+ int local_scope, site_scope;
+ int ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ int conn_addr_legal;
+#endif
+ struct sctp_vrf *vrf;
+ struct xsctp_laddr xladdr;
+ struct sctp_laddr *laddr;
+ int error;
+
+ /* Turn on all the appropriate scope */
+ if (stcb != NULL) {
+ /* use association specific values */
+ loopback_scope = stcb->asoc.scope.loopback_scope;
+#ifdef INET
+ ipv4_local_scope = stcb->asoc.scope.ipv4_local_scope;
+ ipv4_addr_legal = stcb->asoc.scope.ipv4_addr_legal;
+#endif
+#ifdef INET6
+ local_scope = stcb->asoc.scope.local_scope;
+ site_scope = stcb->asoc.scope.site_scope;
+ ipv6_addr_legal = stcb->asoc.scope.ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ conn_addr_legal = stcb->asoc.scope.conn_addr_legal;
+#endif
+ } else {
+ /* Use generic values for endpoints. */
+ loopback_scope = 1;
+#ifdef INET
+ ipv4_local_scope = 1;
+#endif
+#ifdef INET6
+ local_scope = 1;
+ site_scope = 1;
+#endif
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+#ifdef INET6
+ ipv6_addr_legal = 1;
+#endif
+#ifdef INET
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ ipv4_addr_legal = 0;
+ } else {
+ ipv4_addr_legal = 1;
+ }
+#endif
+#if defined(__Userspace__)
+ conn_addr_legal = 0;
+#endif
+ } else {
+#ifdef INET6
+ ipv6_addr_legal = 0;
+#endif
+#if defined(__Userspace__)
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ conn_addr_legal = 1;
+#ifdef INET
+ ipv4_addr_legal = 0;
+#endif
+ } else {
+ conn_addr_legal = 0;
+#ifdef INET
+ ipv4_addr_legal = 1;
+#endif
+ }
+#else
+#ifdef INET
+ ipv4_addr_legal = 1;
+#endif
+#endif
+ }
+ }
+
+ /* Neither Mac OS X nor FreeBSD support multiple routing functions. */
+ if ((vrf = sctp_find_vrf(inp->def_vrf_id)) == NULL) {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (ENOENT);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if ((loopback_scope == 0) && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* Skip loopback if loopback_scope not set. */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ if (stcb != NULL) {
+ /*
+ * Ignore if blacklisted at
+ * association level.
+ */
+ if (sctp_is_addr_restricted(stcb, sctp_ifa)) {
+ continue;
+ }
+ }
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (ipv4_addr_legal) {
+ struct sockaddr_in *sin;
+
+ sin = &sctp_ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if ((ipv4_local_scope == 0) && (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) {
+ continue;
+ }
+ } else {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (ipv6_addr_legal) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = &sctp_ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (local_scope == 0) {
+ continue;
+ }
+ }
+ if ((site_scope == 0) && (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ } else {
+ continue;
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (!conn_addr_legal) {
+ continue;
+ }
+ break;
+#endif
+ default:
+ continue;
+ }
+ memset((void *)&xladdr, 0, sizeof(struct xsctp_laddr));
+ memcpy((void *)&xladdr.address, (const void *)&sctp_ifa->address, sizeof(union sctp_sockstore));
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ error = SYSCTL_OUT(req, &xladdr, sizeof(struct xsctp_laddr));
+ if (error != 0) {
+ return (error);
+ } else {
+ SCTP_INP_INFO_RLOCK();
+ SCTP_INP_RLOCK(inp);
+ }
+ }
+ }
+ } else {
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ /* ignore if blacklisted at association level */
+ if (stcb != NULL && sctp_is_addr_restricted(stcb, laddr->ifa))
+ continue;
+ memset((void *)&xladdr, 0, sizeof(struct xsctp_laddr));
+ memcpy((void *)&xladdr.address, (const void *)&laddr->ifa->address, sizeof(union sctp_sockstore));
+ xladdr.start_time.tv_sec = (uint32_t)laddr->start_time.tv_sec;
+ xladdr.start_time.tv_usec = (uint32_t)laddr->start_time.tv_usec;
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ error = SYSCTL_OUT(req, &xladdr, sizeof(struct xsctp_laddr));
+ if (error != 0) {
+ return (error);
+ } else {
+ SCTP_INP_INFO_RLOCK();
+ SCTP_INP_RLOCK(inp);
+ }
+ }
+ }
+ memset((void *)&xladdr, 0, sizeof(struct xsctp_laddr));
+ xladdr.last = 1;
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ error = SYSCTL_OUT(req, &xladdr, sizeof(struct xsctp_laddr));
+
+ if (error != 0) {
+ return (error);
+ } else {
+ SCTP_INP_INFO_RLOCK();
+ SCTP_INP_RLOCK(inp);
+ return (0);
+ }
+}
+
+/*
+ * sysctl functions
+ */
+#if defined(__APPLE__) && !defined(__Userspace__)
+static int
+sctp_sysctl_handle_assoclist SYSCTL_HANDLER_ARGS
+{
+#pragma unused(oidp, arg1, arg2)
+#else
+static int
+sctp_sysctl_handle_assoclist(SYSCTL_HANDLER_ARGS)
+{
+#endif
+ unsigned int number_of_endpoints;
+ unsigned int number_of_local_addresses;
+ unsigned int number_of_associations;
+ unsigned int number_of_remote_addresses;
+ unsigned int n;
+ int error;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct xsctp_inpcb xinpcb;
+ struct xsctp_tcb xstcb;
+ struct xsctp_raddr xraddr;
+ struct socket *so;
+
+ number_of_endpoints = 0;
+ number_of_local_addresses = 0;
+ number_of_associations = 0;
+ number_of_remote_addresses = 0;
+
+ SCTP_INP_INFO_RLOCK();
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (req->oldptr == USER_ADDR_NULL) {
+#else
+ if (req->oldptr == NULL) {
+#endif
+ LIST_FOREACH(inp, &SCTP_BASE_INFO(listhead), sctp_list) {
+ SCTP_INP_RLOCK(inp);
+ number_of_endpoints++;
+ number_of_local_addresses += sctp_sysctl_number_of_addresses(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ number_of_associations++;
+ number_of_local_addresses += sctp_sysctl_number_of_addresses(inp);
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ number_of_remote_addresses++;
+ }
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ SCTP_INP_INFO_RUNLOCK();
+ n = (number_of_endpoints + 1) * sizeof(struct xsctp_inpcb) +
+ (number_of_local_addresses + number_of_endpoints + number_of_associations) * sizeof(struct xsctp_laddr) +
+ (number_of_associations + number_of_endpoints) * sizeof(struct xsctp_tcb) +
+ (number_of_remote_addresses + number_of_associations) * sizeof(struct xsctp_raddr);
+
+ /* request some more memory than needed */
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ req->oldidx = (n + n / 8);
+#else
+ req->dataidx = (n + n / 8);
+#endif
+ return (0);
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (req->newptr != USER_ADDR_NULL) {
+#else
+ if (req->newptr != NULL) {
+#endif
+ SCTP_INP_INFO_RUNLOCK();
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_SYSCTL, EPERM);
+ return (EPERM);
+ }
+ memset(&xinpcb, 0, sizeof(xinpcb));
+ memset(&xstcb, 0, sizeof(xstcb));
+ memset(&xraddr, 0, sizeof(xraddr));
+ LIST_FOREACH(inp, &SCTP_BASE_INFO(listhead), sctp_list) {
+ SCTP_INP_RLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ /* if its allgone it is being freed - skip it */
+ goto skip;
+ }
+ xinpcb.last = 0;
+ xinpcb.local_port = ntohs(inp->sctp_lport);
+ xinpcb.flags = inp->sctp_flags;
+ xinpcb.features = inp->sctp_features;
+ xinpcb.total_sends = inp->total_sends;
+ xinpcb.total_recvs = inp->total_recvs;
+ xinpcb.total_nospaces = inp->total_nospaces;
+ xinpcb.fragmentation_point = inp->sctp_frag_point;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ xinpcb.socket = (uintptr_t)inp->sctp_socket;
+#else
+ xinpcb.socket = inp->sctp_socket;
+#endif
+ so = inp->sctp_socket;
+ if ((so == NULL) ||
+ (!SCTP_IS_LISTENING(inp)) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
+ xinpcb.qlen = 0;
+ xinpcb.maxqlen = 0;
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ xinpcb.qlen = so->sol_qlen;
+ xinpcb.qlen_old = so->sol_qlen > USHRT_MAX ?
+ USHRT_MAX : (uint16_t) so->sol_qlen;
+ xinpcb.maxqlen = so->sol_qlimit;
+ xinpcb.maxqlen_old = so->sol_qlimit > USHRT_MAX ?
+ USHRT_MAX : (uint16_t) so->sol_qlimit;
+#else
+ xinpcb.qlen = so->so_qlen;
+ xinpcb.maxqlen = so->so_qlimit;
+#endif
+ }
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ error = SYSCTL_OUT(req, &xinpcb, sizeof(struct xsctp_inpcb));
+ if (error) {
+ SCTP_INP_DECR_REF(inp);
+ return (error);
+ }
+ SCTP_INP_INFO_RLOCK();
+ SCTP_INP_RLOCK(inp);
+ error = sctp_sysctl_copy_out_local_addresses(inp, NULL, req);
+ if (error) {
+ SCTP_INP_DECR_REF(inp);
+ return (error);
+ }
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ xstcb.last = 0;
+ xstcb.local_port = ntohs(inp->sctp_lport);
+ xstcb.remote_port = ntohs(stcb->rport);
+ if (stcb->asoc.primary_destination != NULL)
+ xstcb.primary_addr = stcb->asoc.primary_destination->ro._l_addr;
+ xstcb.heartbeat_interval = stcb->asoc.heart_beat_delay;
+ xstcb.state = (uint32_t)sctp_map_assoc_state(stcb->asoc.state);
+ xstcb.assoc_id = sctp_get_associd(stcb);
+ xstcb.peers_rwnd = stcb->asoc.peers_rwnd;
+ xstcb.in_streams = stcb->asoc.streamincnt;
+ xstcb.out_streams = stcb->asoc.streamoutcnt;
+ xstcb.max_nr_retrans = stcb->asoc.overall_error_count;
+ xstcb.primary_process = 0; /* not really supported yet */
+ xstcb.T1_expireries = stcb->asoc.timoinit + stcb->asoc.timocookie;
+ xstcb.T2_expireries = stcb->asoc.timoshutdown + stcb->asoc.timoshutdownack;
+ xstcb.retransmitted_tsns = stcb->asoc.marked_retrans;
+ xstcb.start_time.tv_sec = (uint32_t)stcb->asoc.start_time.tv_sec;
+ xstcb.start_time.tv_usec = (uint32_t)stcb->asoc.start_time.tv_usec;
+ xstcb.discontinuity_time.tv_sec = (uint32_t)stcb->asoc.discontinuity_time.tv_sec;
+ xstcb.discontinuity_time.tv_usec = (uint32_t)stcb->asoc.discontinuity_time.tv_usec;
+ xstcb.total_sends = stcb->total_sends;
+ xstcb.total_recvs = stcb->total_recvs;
+ xstcb.local_tag = stcb->asoc.my_vtag;
+ xstcb.remote_tag = stcb->asoc.peer_vtag;
+ xstcb.initial_tsn = stcb->asoc.init_seq_number;
+ xstcb.highest_tsn = stcb->asoc.sending_seq - 1;
+ xstcb.cumulative_tsn = stcb->asoc.last_acked_seq;
+ xstcb.cumulative_tsn_ack = stcb->asoc.cumulative_tsn;
+ xstcb.mtu = stcb->asoc.smallest_mtu;
+ xstcb.refcnt = stcb->asoc.refcnt;
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ error = SYSCTL_OUT(req, &xstcb, sizeof(struct xsctp_tcb));
+ if (error) {
+ SCTP_INP_DECR_REF(inp);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (error);
+ }
+ SCTP_INP_INFO_RLOCK();
+ SCTP_INP_RLOCK(inp);
+ error = sctp_sysctl_copy_out_local_addresses(inp, stcb, req);
+ if (error) {
+ SCTP_INP_DECR_REF(inp);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (error);
+ }
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ xraddr.last = 0;
+ xraddr.address = net->ro._l_addr;
+ xraddr.active = ((net->dest_state & SCTP_ADDR_REACHABLE) == SCTP_ADDR_REACHABLE);
+ xraddr.confirmed = ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0);
+ xraddr.heartbeat_enabled = ((net->dest_state & SCTP_ADDR_NOHB) == 0);
+ xraddr.potentially_failed = ((net->dest_state & SCTP_ADDR_PF) == SCTP_ADDR_PF);
+ xraddr.rto = net->RTO;
+ xraddr.max_path_rtx = net->failure_threshold;
+ xraddr.rtx = net->marked_retrans;
+ xraddr.error_counter = net->error_count;
+ xraddr.cwnd = net->cwnd;
+ xraddr.flight_size = net->flight_size;
+ xraddr.mtu = net->mtu;
+ xraddr.rtt = net->rtt / 1000;
+ xraddr.heartbeat_interval = net->heart_beat_delay;
+ xraddr.ssthresh = net->ssthresh;
+ xraddr.encaps_port = net->port;
+ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ xraddr.state = SCTP_UNCONFIRMED;
+ } else if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ xraddr.state = SCTP_ACTIVE;
+ } else {
+ xraddr.state = SCTP_INACTIVE;
+ }
+ xraddr.start_time.tv_sec = (uint32_t)net->start_time.tv_sec;
+ xraddr.start_time.tv_usec = (uint32_t)net->start_time.tv_usec;
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ error = SYSCTL_OUT(req, &xraddr, sizeof(struct xsctp_raddr));
+ if (error) {
+ SCTP_INP_DECR_REF(inp);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (error);
+ }
+ SCTP_INP_INFO_RLOCK();
+ SCTP_INP_RLOCK(inp);
+ }
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ memset((void *)&xraddr, 0, sizeof(struct xsctp_raddr));
+ xraddr.last = 1;
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ error = SYSCTL_OUT(req, &xraddr, sizeof(struct xsctp_raddr));
+ if (error) {
+ SCTP_INP_DECR_REF(inp);
+ return (error);
+ }
+ SCTP_INP_INFO_RLOCK();
+ SCTP_INP_RLOCK(inp);
+ }
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ memset((void *)&xstcb, 0, sizeof(struct xsctp_tcb));
+ xstcb.last = 1;
+ error = SYSCTL_OUT(req, &xstcb, sizeof(struct xsctp_tcb));
+ if (error) {
+ return (error);
+ }
+skip:
+ SCTP_INP_INFO_RLOCK();
+ }
+ SCTP_INP_INFO_RUNLOCK();
+
+ memset((void *)&xinpcb, 0, sizeof(struct xsctp_inpcb));
+ xinpcb.last = 1;
+ error = SYSCTL_OUT(req, &xinpcb, sizeof(struct xsctp_inpcb));
+ return (error);
+}
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+static int
+sctp_sysctl_handle_udp_tunneling SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+#else
+static int
+sctp_sysctl_handle_udp_tunneling(SYSCTL_HANDLER_ARGS)
+{
+#endif
+ int error;
+ uint32_t old, new;
+
+ SCTP_INP_INFO_RLOCK();
+ old = SCTP_BASE_SYSCTL(sctp_udp_tunneling_port);
+ SCTP_INP_INFO_RUNLOCK();
+ new = old;
+ error = sysctl_handle_int(oidp, &new, 0, req);
+ if ((error == 0) &&
+#if defined(__APPLE__) && !defined(__Userspace__)
+ (req->newptr != USER_ADDR_NULL)) {
+#else
+ (req->newptr != NULL)) {
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+ SCTP_INP_INFO_WLOCK();
+ sctp_over_udp_restart();
+ SCTP_INP_INFO_WUNLOCK();
+#else
+#if (SCTPCTL_UDP_TUNNELING_PORT_MIN == 0)
+ if (new > SCTPCTL_UDP_TUNNELING_PORT_MAX) {
+#else
+ if ((new < SCTPCTL_UDP_TUNNELING_PORT_MIN) ||
+ (new > SCTPCTL_UDP_TUNNELING_PORT_MAX)) {
+#endif
+ error = EINVAL;
+ } else {
+ SCTP_INP_INFO_WLOCK();
+ SCTP_BASE_SYSCTL(sctp_udp_tunneling_port) = new;
+ if (old != 0) {
+ sctp_over_udp_stop();
+ }
+ if (new != 0) {
+ error = sctp_over_udp_start();
+ }
+ SCTP_INP_INFO_WUNLOCK();
+ }
+#endif
+ }
+ return (error);
+}
+#if defined(__APPLE__) && !defined(__Userspace__)
+
+int sctp_is_vmware_interface(struct ifnet *);
+
+static int
+sctp_sysctl_handle_vmware_interfaces SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+ int error;
+ uint32_t old, new;
+
+ old = SCTP_BASE_SYSCTL(sctp_ignore_vmware_interfaces);
+ new = old;
+ error = sysctl_handle_int(oidp, &new, 0, req);
+ if ((error == 0) && (req->newptr != USER_ADDR_NULL)) {
+ if ((new < SCTPCTL_IGNORE_VMWARE_INTERFACES_MIN) ||
+ (new > SCTPCTL_IGNORE_VMWARE_INTERFACES_MAX)) {
+ error = EINVAL;
+ } else {
+ if ((old == 1) && (new == 0)) {
+ sctp_add_or_del_interfaces(sctp_is_vmware_interface, 1);
+ }
+ if ((old == 0) && (new == 1)) {
+ sctp_add_or_del_interfaces(sctp_is_vmware_interface, 0);
+ }
+ if (old != new) {
+ SCTP_BASE_SYSCTL(sctp_ignore_vmware_interfaces) = new;
+ }
+ }
+ }
+ return (error);
+}
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+static int
+sctp_sysctl_handle_auth SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+#else
+static int
+sctp_sysctl_handle_auth(SYSCTL_HANDLER_ARGS)
+{
+#endif
+ int error;
+ uint32_t new;
+
+ new = SCTP_BASE_SYSCTL(sctp_auth_enable);
+ error = sysctl_handle_int(oidp, &new, 0, req);
+ if ((error == 0) &&
+#if defined(__APPLE__) && !defined(__Userspace__)
+ (req->newptr != USER_ADDR_NULL)) {
+#else
+ (req->newptr != NULL)) {
+#endif
+#if (SCTPCTL_AUTH_ENABLE_MIN == 0)
+ if ((new > SCTPCTL_AUTH_ENABLE_MAX) ||
+ ((new == 0) && (SCTP_BASE_SYSCTL(sctp_asconf_enable) == 1))) {
+#else
+ if ((new < SCTPCTL_AUTH_ENABLE_MIN) ||
+ (new > SCTPCTL_AUTH_ENABLE_MAX) ||
+ ((new == 0) && (SCTP_BASE_SYSCTL(sctp_asconf_enable) == 1))) {
+#endif
+ error = EINVAL;
+ } else {
+ SCTP_BASE_SYSCTL(sctp_auth_enable) = new;
+ }
+ }
+ return (error);
+}
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+static int
+sctp_sysctl_handle_asconf SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+#else
+static int
+sctp_sysctl_handle_asconf(SYSCTL_HANDLER_ARGS)
+{
+#endif
+ int error;
+ uint32_t new;
+
+ new = SCTP_BASE_SYSCTL(sctp_asconf_enable);
+ error = sysctl_handle_int(oidp, &new, 0, req);
+ if ((error == 0) &&
+#if defined(__APPLE__) && !defined(__Userspace__)
+ (req->newptr != USER_ADDR_NULL)) {
+#else
+ (req->newptr != NULL)) {
+#endif
+#if (SCTPCTL_ASCONF_ENABLE_MIN == 0)
+ if ((new > SCTPCTL_ASCONF_ENABLE_MAX) ||
+ ((new == 1) && (SCTP_BASE_SYSCTL(sctp_auth_enable) == 0))) {
+#else
+ if ((new < SCTPCTL_ASCONF_ENABLE_MIN) ||
+ (new > SCTPCTL_ASCONF_ENABLE_MAX) ||
+ ((new == 1) && (SCTP_BASE_SYSCTL(sctp_auth_enable) == 0))) {
+#endif
+ error = EINVAL;
+ } else {
+ SCTP_BASE_SYSCTL(sctp_asconf_enable) = new;
+ }
+ }
+ return (error);
+}
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+static int
+sctp_sysctl_handle_stats SYSCTL_HANDLER_ARGS
+{
+#pragma unused(oidp, arg1, arg2)
+#else
+static int
+sctp_sysctl_handle_stats(SYSCTL_HANDLER_ARGS)
+{
+#endif
+ int error;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ struct sctpstat *sarry;
+ struct sctpstat sb;
+ int cpu;
+#endif
+ struct sctpstat sb_temp;
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if ((req->newptr != USER_ADDR_NULL) &&
+#else
+ if ((req->newptr != NULL) &&
+#endif
+ (req->newlen != sizeof(struct sctpstat))) {
+ return (EINVAL);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ memset(&sb_temp, 0, sizeof(struct sctpstat));
+
+ if (req->newptr != NULL) {
+ error = SYSCTL_IN(req, &sb_temp, sizeof(struct sctpstat));
+ if (error != 0) {
+ return (error);
+ }
+ }
+#if defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ memset(&sb, 0, sizeof(sb));
+ for (cpu = 0; cpu < mp_maxid; cpu++) {
+ sarry = &SCTP_BASE_STATS[cpu];
+ if (sarry->sctps_discontinuitytime.tv_sec > sb.sctps_discontinuitytime.tv_sec) {
+ sb.sctps_discontinuitytime.tv_sec = sarry->sctps_discontinuitytime.tv_sec;
+ sb.sctps_discontinuitytime.tv_usec = sarry->sctps_discontinuitytime.tv_usec;
+ }
+ sb.sctps_currestab += sarry->sctps_currestab;
+ sb.sctps_activeestab += sarry->sctps_activeestab;
+ sb.sctps_restartestab += sarry->sctps_restartestab;
+ sb.sctps_collisionestab += sarry->sctps_collisionestab;
+ sb.sctps_passiveestab += sarry->sctps_passiveestab;
+ sb.sctps_aborted += sarry->sctps_aborted;
+ sb.sctps_shutdown += sarry->sctps_shutdown;
+ sb.sctps_outoftheblue += sarry->sctps_outoftheblue;
+ sb.sctps_checksumerrors += sarry->sctps_checksumerrors;
+ sb.sctps_outcontrolchunks += sarry->sctps_outcontrolchunks;
+ sb.sctps_outorderchunks += sarry->sctps_outorderchunks;
+ sb.sctps_outunorderchunks += sarry->sctps_outunorderchunks;
+ sb.sctps_incontrolchunks += sarry->sctps_incontrolchunks;
+ sb.sctps_inorderchunks += sarry->sctps_inorderchunks;
+ sb.sctps_inunorderchunks += sarry->sctps_inunorderchunks;
+ sb.sctps_fragusrmsgs += sarry->sctps_fragusrmsgs;
+ sb.sctps_reasmusrmsgs += sarry->sctps_reasmusrmsgs;
+ sb.sctps_outpackets += sarry->sctps_outpackets;
+ sb.sctps_inpackets += sarry->sctps_inpackets;
+ sb.sctps_recvpackets += sarry->sctps_recvpackets;
+ sb.sctps_recvdatagrams += sarry->sctps_recvdatagrams;
+ sb.sctps_recvpktwithdata += sarry->sctps_recvpktwithdata;
+ sb.sctps_recvsacks += sarry->sctps_recvsacks;
+ sb.sctps_recvdata += sarry->sctps_recvdata;
+ sb.sctps_recvdupdata += sarry->sctps_recvdupdata;
+ sb.sctps_recvheartbeat += sarry->sctps_recvheartbeat;
+ sb.sctps_recvheartbeatack += sarry->sctps_recvheartbeatack;
+ sb.sctps_recvecne += sarry->sctps_recvecne;
+ sb.sctps_recvauth += sarry->sctps_recvauth;
+ sb.sctps_recvauthmissing += sarry->sctps_recvauthmissing;
+ sb.sctps_recvivalhmacid += sarry->sctps_recvivalhmacid;
+ sb.sctps_recvivalkeyid += sarry->sctps_recvivalkeyid;
+ sb.sctps_recvauthfailed += sarry->sctps_recvauthfailed;
+ sb.sctps_recvexpress += sarry->sctps_recvexpress;
+ sb.sctps_recvexpressm += sarry->sctps_recvexpressm;
+ sb.sctps_recvswcrc += sarry->sctps_recvswcrc;
+ sb.sctps_recvhwcrc += sarry->sctps_recvhwcrc;
+ sb.sctps_sendpackets += sarry->sctps_sendpackets;
+ sb.sctps_sendsacks += sarry->sctps_sendsacks;
+ sb.sctps_senddata += sarry->sctps_senddata;
+ sb.sctps_sendretransdata += sarry->sctps_sendretransdata;
+ sb.sctps_sendfastretrans += sarry->sctps_sendfastretrans;
+ sb.sctps_sendmultfastretrans += sarry->sctps_sendmultfastretrans;
+ sb.sctps_sendheartbeat += sarry->sctps_sendheartbeat;
+ sb.sctps_sendecne += sarry->sctps_sendecne;
+ sb.sctps_sendauth += sarry->sctps_sendauth;
+ sb.sctps_senderrors += sarry->sctps_senderrors;
+ sb.sctps_sendswcrc += sarry->sctps_sendswcrc;
+ sb.sctps_sendhwcrc += sarry->sctps_sendhwcrc;
+ sb.sctps_pdrpfmbox += sarry->sctps_pdrpfmbox;
+ sb.sctps_pdrpfehos += sarry->sctps_pdrpfehos;
+ sb.sctps_pdrpmbda += sarry->sctps_pdrpmbda;
+ sb.sctps_pdrpmbct += sarry->sctps_pdrpmbct;
+ sb.sctps_pdrpbwrpt += sarry->sctps_pdrpbwrpt;
+ sb.sctps_pdrpcrupt += sarry->sctps_pdrpcrupt;
+ sb.sctps_pdrpnedat += sarry->sctps_pdrpnedat;
+ sb.sctps_pdrppdbrk += sarry->sctps_pdrppdbrk;
+ sb.sctps_pdrptsnnf += sarry->sctps_pdrptsnnf;
+ sb.sctps_pdrpdnfnd += sarry->sctps_pdrpdnfnd;
+ sb.sctps_pdrpdiwnp += sarry->sctps_pdrpdiwnp;
+ sb.sctps_pdrpdizrw += sarry->sctps_pdrpdizrw;
+ sb.sctps_pdrpbadd += sarry->sctps_pdrpbadd;
+ sb.sctps_pdrpmark += sarry->sctps_pdrpmark;
+ sb.sctps_timoiterator += sarry->sctps_timoiterator;
+ sb.sctps_timodata += sarry->sctps_timodata;
+ sb.sctps_timowindowprobe += sarry->sctps_timowindowprobe;
+ sb.sctps_timoinit += sarry->sctps_timoinit;
+ sb.sctps_timosack += sarry->sctps_timosack;
+ sb.sctps_timoshutdown += sarry->sctps_timoshutdown;
+ sb.sctps_timoheartbeat += sarry->sctps_timoheartbeat;
+ sb.sctps_timocookie += sarry->sctps_timocookie;
+ sb.sctps_timosecret += sarry->sctps_timosecret;
+ sb.sctps_timopathmtu += sarry->sctps_timopathmtu;
+ sb.sctps_timoshutdownack += sarry->sctps_timoshutdownack;
+ sb.sctps_timoshutdownguard += sarry->sctps_timoshutdownguard;
+ sb.sctps_timostrmrst += sarry->sctps_timostrmrst;
+ sb.sctps_timoearlyfr += sarry->sctps_timoearlyfr;
+ sb.sctps_timoasconf += sarry->sctps_timoasconf;
+ sb.sctps_timodelprim += sarry->sctps_timodelprim;
+ sb.sctps_timoautoclose += sarry->sctps_timoautoclose;
+ sb.sctps_timoassockill += sarry->sctps_timoassockill;
+ sb.sctps_timoinpkill += sarry->sctps_timoinpkill;
+ sb.sctps_hdrops += sarry->sctps_hdrops;
+ sb.sctps_badsum += sarry->sctps_badsum;
+ sb.sctps_noport += sarry->sctps_noport;
+ sb.sctps_badvtag += sarry->sctps_badvtag;
+ sb.sctps_badsid += sarry->sctps_badsid;
+ sb.sctps_nomem += sarry->sctps_nomem;
+ sb.sctps_fastretransinrtt += sarry->sctps_fastretransinrtt;
+ sb.sctps_markedretrans += sarry->sctps_markedretrans;
+ sb.sctps_naglesent += sarry->sctps_naglesent;
+ sb.sctps_naglequeued += sarry->sctps_naglequeued;
+ sb.sctps_maxburstqueued += sarry->sctps_maxburstqueued;
+ sb.sctps_ifnomemqueued += sarry->sctps_ifnomemqueued;
+ sb.sctps_windowprobed += sarry->sctps_windowprobed;
+ sb.sctps_lowlevelerr += sarry->sctps_lowlevelerr;
+ sb.sctps_lowlevelerrusr += sarry->sctps_lowlevelerrusr;
+ sb.sctps_datadropchklmt += sarry->sctps_datadropchklmt;
+ sb.sctps_datadroprwnd += sarry->sctps_datadroprwnd;
+ sb.sctps_ecnereducedcwnd += sarry->sctps_ecnereducedcwnd;
+ sb.sctps_vtagexpress += sarry->sctps_vtagexpress;
+ sb.sctps_vtagbogus += sarry->sctps_vtagbogus;
+ sb.sctps_primary_randry += sarry->sctps_primary_randry;
+ sb.sctps_cmt_randry += sarry->sctps_cmt_randry;
+ sb.sctps_slowpath_sack += sarry->sctps_slowpath_sack;
+ sb.sctps_wu_sacks_sent += sarry->sctps_wu_sacks_sent;
+ sb.sctps_sends_with_flags += sarry->sctps_sends_with_flags;
+ sb.sctps_sends_with_unord += sarry->sctps_sends_with_unord;
+ sb.sctps_sends_with_eof += sarry->sctps_sends_with_eof;
+ sb.sctps_sends_with_abort += sarry->sctps_sends_with_abort;
+ sb.sctps_protocol_drain_calls += sarry->sctps_protocol_drain_calls;
+ sb.sctps_protocol_drains_done += sarry->sctps_protocol_drains_done;
+ sb.sctps_read_peeks += sarry->sctps_read_peeks;
+ sb.sctps_cached_chk += sarry->sctps_cached_chk;
+ sb.sctps_cached_strmoq += sarry->sctps_cached_strmoq;
+ sb.sctps_left_abandon += sarry->sctps_left_abandon;
+ sb.sctps_send_burst_avoid += sarry->sctps_send_burst_avoid;
+ sb.sctps_send_cwnd_avoid += sarry->sctps_send_cwnd_avoid;
+ sb.sctps_fwdtsn_map_over += sarry->sctps_fwdtsn_map_over;
+ sb.sctps_queue_upd_ecne += sarry->sctps_queue_upd_ecne;
+ sb.sctps_recvzerocrc += sarry->sctps_recvzerocrc;
+ sb.sctps_sendzerocrc += sarry->sctps_sendzerocrc;
+ if (req->newptr != NULL) {
+ memcpy(sarry, &sb_temp, sizeof(struct sctpstat));
+ }
+ }
+ error = SYSCTL_OUT(req, &sb, sizeof(struct sctpstat));
+#else
+ error = SYSCTL_OUT(req, &SCTP_BASE_STATS, sizeof(struct sctpstat));
+ if (error != 0) {
+ return (error);
+ }
+ if (req->newptr != NULL) {
+ memcpy(&SCTP_BASE_STATS, &sb_temp, sizeof(struct sctpstat));
+ }
+#endif
+#else
+ error = SYSCTL_OUT(req, &SCTP_BASE_STATS, sizeof(struct sctpstat));
+#endif
+ return (error);
+}
+
+#if defined(SCTP_LOCAL_TRACE_BUF)
+#if defined(__APPLE__) && !defined(__Userspace__)
+static int
+sctp_sysctl_handle_trace_log SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2, oidp)
+#else
+static int
+sctp_sysctl_handle_trace_log(SYSCTL_HANDLER_ARGS)
+{
+#endif
+ int error;
+
+#if defined(_WIN32) && !defined(__Userspace__)
+ error = SYSCTL_OUT(req, SCTP_BASE_SYSCTL(sctp_log), sizeof(struct sctp_log));
+#else
+ error = SYSCTL_OUT(req, &SCTP_BASE_SYSCTL(sctp_log), sizeof(struct sctp_log));
+#endif
+ return (error);
+}
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+static int
+sctp_sysctl_handle_trace_log_clear SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2, req, oidp)
+#else
+static int
+sctp_sysctl_handle_trace_log_clear(SYSCTL_HANDLER_ARGS)
+{
+#endif
+ int error = 0;
+#if defined(_WIN32) && !defined(__Userspace__)
+ int value = 0;
+
+ if (req->new_data == NULL) {
+ return (error);
+ }
+ error = SYSCTL_IN(req, &value, sizeof(int));
+ if (error == 0 && value != 0 && SCTP_BASE_SYSCTL(sctp_log) != NULL) {
+ memset(SCTP_BASE_SYSCTL(sctp_log), 0, sizeof(struct sctp_log));
+ }
+#else
+
+ memset(&SCTP_BASE_SYSCTL(sctp_log), 0, sizeof(struct sctp_log));
+#endif
+ return (error);
+}
+#endif
+
+#if (defined(__APPLE__) || defined(__FreeBSD__)) && !defined(__Userspace__)
+#if defined(__FreeBSD__)
+#define SCTP_UINT_SYSCTL(mib_name, var_name, prefix) \
+ static int \
+ sctp_sysctl_handle_##mib_name(SYSCTL_HANDLER_ARGS) \
+ { \
+ int error; \
+ uint32_t new; \
+ \
+ new = SCTP_BASE_SYSCTL(var_name); \
+ error = sysctl_handle_int(oidp, &new, 0, req); \
+ if ((error == 0) && (req->newptr != NULL)) { \
+ if ((new < prefix##_MIN) || \
+ (new > prefix##_MAX)) { \
+ error = EINVAL; \
+ } else { \
+ SCTP_BASE_SYSCTL(var_name) = new; \
+ } \
+ } \
+ return (error); \
+ } \
+ SYSCTL_PROC(_net_inet_sctp, OID_AUTO, mib_name, \
+ CTLFLAG_VNET|CTLTYPE_UINT|CTLFLAG_RW, NULL, 0, \
+ sctp_sysctl_handle_##mib_name, "UI", prefix##_DESC);
+#else
+#define SCTP_UINT_SYSCTL(mib_name, var_name, prefix) \
+ static int \
+ sctp_sysctl_handle_##mib_name(struct sysctl_oid *oidp, \
+ void *arg1 __attribute__((unused)), \
+ int arg2 __attribute__((unused)), \
+ struct sysctl_req *req) \
+ { \
+ int error; \
+ uint32_t new; \
+ \
+ new = SCTP_BASE_SYSCTL(var_name); \
+ error = sysctl_handle_int(oidp, &new, 0, req); \
+ if ((error == 0) && (req->newptr != USER_ADDR_NULL)) { \
+ if ((new < prefix##_MIN) || \
+ (new > prefix##_MAX)) { \
+ error = EINVAL; \
+ } else { \
+ SCTP_BASE_SYSCTL(var_name) = new; \
+ } \
+ } \
+ return (error); \
+ } \
+ SYSCTL_PROC(_net_inet_sctp, OID_AUTO, mib_name, \
+ CTLTYPE_INT | CTLFLAG_RW, NULL, 0, \
+ sctp_sysctl_handle_##mib_name, "I", prefix##_DESC);
+#define CTLTYPE_UINT CTLTYPE_INT
+#define CTLFLAG_VNET 0
+#endif
+
+/*
+ * sysctl definitions
+ */
+
+SCTP_UINT_SYSCTL(sendspace, sctp_sendspace, SCTPCTL_MAXDGRAM)
+SCTP_UINT_SYSCTL(recvspace, sctp_recvspace, SCTPCTL_RECVSPACE)
+SCTP_UINT_SYSCTL(auto_asconf, sctp_auto_asconf, SCTPCTL_AUTOASCONF)
+SCTP_UINT_SYSCTL(ecn_enable, sctp_ecn_enable, SCTPCTL_ECN_ENABLE)
+SCTP_UINT_SYSCTL(pr_enable, sctp_pr_enable, SCTPCTL_PR_ENABLE)
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, auth_enable, CTLFLAG_VNET|CTLTYPE_UINT|CTLFLAG_RW,
+ NULL, 0, sctp_sysctl_handle_auth, "IU", SCTPCTL_AUTH_ENABLE_DESC);
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, asconf_enable, CTLFLAG_VNET|CTLTYPE_UINT|CTLFLAG_RW,
+ NULL, 0, sctp_sysctl_handle_asconf, "IU", SCTPCTL_ASCONF_ENABLE_DESC);
+SCTP_UINT_SYSCTL(reconfig_enable, sctp_reconfig_enable, SCTPCTL_RECONFIG_ENABLE)
+SCTP_UINT_SYSCTL(nrsack_enable, sctp_nrsack_enable, SCTPCTL_NRSACK_ENABLE)
+SCTP_UINT_SYSCTL(pktdrop_enable, sctp_pktdrop_enable, SCTPCTL_PKTDROP_ENABLE)
+#if defined(__APPLE__) && !defined(__Userspace__)
+SCTP_UINT_SYSCTL(loopback_nocsum, sctp_no_csum_on_loopback, SCTPCTL_LOOPBACK_NOCSUM)
+#endif
+SCTP_UINT_SYSCTL(peer_chkoh, sctp_peer_chunk_oh, SCTPCTL_PEER_CHKOH)
+SCTP_UINT_SYSCTL(maxburst, sctp_max_burst_default, SCTPCTL_MAXBURST)
+SCTP_UINT_SYSCTL(fr_maxburst, sctp_fr_max_burst_default, SCTPCTL_FRMAXBURST)
+SCTP_UINT_SYSCTL(maxchunks, sctp_max_chunks_on_queue, SCTPCTL_MAXCHUNKS)
+SCTP_UINT_SYSCTL(tcbhashsize, sctp_hashtblsize, SCTPCTL_TCBHASHSIZE)
+SCTP_UINT_SYSCTL(pcbhashsize, sctp_pcbtblsize, SCTPCTL_PCBHASHSIZE)
+SCTP_UINT_SYSCTL(min_split_point, sctp_min_split_point, SCTPCTL_MIN_SPLIT_POINT)
+SCTP_UINT_SYSCTL(chunkscale, sctp_chunkscale, SCTPCTL_CHUNKSCALE)
+SCTP_UINT_SYSCTL(delayed_sack_time, sctp_delayed_sack_time_default, SCTPCTL_DELAYED_SACK_TIME)
+SCTP_UINT_SYSCTL(sack_freq, sctp_sack_freq_default, SCTPCTL_SACK_FREQ)
+SCTP_UINT_SYSCTL(sys_resource, sctp_system_free_resc_limit, SCTPCTL_SYS_RESOURCE)
+SCTP_UINT_SYSCTL(asoc_resource, sctp_asoc_free_resc_limit, SCTPCTL_ASOC_RESOURCE)
+SCTP_UINT_SYSCTL(heartbeat_interval, sctp_heartbeat_interval_default, SCTPCTL_HEARTBEAT_INTERVAL)
+SCTP_UINT_SYSCTL(pmtu_raise_time, sctp_pmtu_raise_time_default, SCTPCTL_PMTU_RAISE_TIME)
+SCTP_UINT_SYSCTL(shutdown_guard_time, sctp_shutdown_guard_time_default, SCTPCTL_SHUTDOWN_GUARD_TIME)
+SCTP_UINT_SYSCTL(secret_lifetime, sctp_secret_lifetime_default, SCTPCTL_SECRET_LIFETIME)
+SCTP_UINT_SYSCTL(rto_max, sctp_rto_max_default, SCTPCTL_RTO_MAX)
+SCTP_UINT_SYSCTL(rto_min, sctp_rto_min_default, SCTPCTL_RTO_MIN)
+SCTP_UINT_SYSCTL(rto_initial, sctp_rto_initial_default, SCTPCTL_RTO_INITIAL)
+SCTP_UINT_SYSCTL(init_rto_max, sctp_init_rto_max_default, SCTPCTL_INIT_RTO_MAX)
+SCTP_UINT_SYSCTL(valid_cookie_life, sctp_valid_cookie_life_default, SCTPCTL_VALID_COOKIE_LIFE)
+SCTP_UINT_SYSCTL(init_rtx_max, sctp_init_rtx_max_default, SCTPCTL_INIT_RTX_MAX)
+SCTP_UINT_SYSCTL(assoc_rtx_max, sctp_assoc_rtx_max_default, SCTPCTL_ASSOC_RTX_MAX)
+SCTP_UINT_SYSCTL(path_rtx_max, sctp_path_rtx_max_default, SCTPCTL_PATH_RTX_MAX)
+SCTP_UINT_SYSCTL(path_pf_threshold, sctp_path_pf_threshold, SCTPCTL_PATH_PF_THRESHOLD)
+SCTP_UINT_SYSCTL(add_more_on_output, sctp_add_more_threshold, SCTPCTL_ADD_MORE_ON_OUTPUT)
+SCTP_UINT_SYSCTL(incoming_streams, sctp_nr_incoming_streams_default, SCTPCTL_INCOMING_STREAMS)
+SCTP_UINT_SYSCTL(outgoing_streams, sctp_nr_outgoing_streams_default, SCTPCTL_OUTGOING_STREAMS)
+SCTP_UINT_SYSCTL(cmt_on_off, sctp_cmt_on_off, SCTPCTL_CMT_ON_OFF)
+SCTP_UINT_SYSCTL(cmt_use_dac, sctp_cmt_use_dac, SCTPCTL_CMT_USE_DAC)
+SCTP_UINT_SYSCTL(cwnd_maxburst, sctp_use_cwnd_based_maxburst, SCTPCTL_CWND_MAXBURST)
+SCTP_UINT_SYSCTL(nat_friendly, sctp_nat_friendly, SCTPCTL_NAT_FRIENDLY)
+SCTP_UINT_SYSCTL(abc_l_var, sctp_L2_abc_variable, SCTPCTL_ABC_L_VAR)
+SCTP_UINT_SYSCTL(max_chained_mbufs, sctp_mbuf_threshold_count, SCTPCTL_MAX_CHAINED_MBUFS)
+SCTP_UINT_SYSCTL(do_sctp_drain, sctp_do_drain, SCTPCTL_DO_SCTP_DRAIN)
+SCTP_UINT_SYSCTL(hb_max_burst, sctp_hb_maxburst, SCTPCTL_HB_MAX_BURST)
+SCTP_UINT_SYSCTL(abort_at_limit, sctp_abort_if_one_2_one_hits_limit, SCTPCTL_ABORT_AT_LIMIT)
+SCTP_UINT_SYSCTL(min_residual, sctp_min_residual, SCTPCTL_MIN_RESIDUAL)
+SCTP_UINT_SYSCTL(max_retran_chunk, sctp_max_retran_chunk, SCTPCTL_MAX_RETRAN_CHUNK)
+SCTP_UINT_SYSCTL(log_level, sctp_logging_level, SCTPCTL_LOGGING_LEVEL)
+SCTP_UINT_SYSCTL(default_cc_module, sctp_default_cc_module, SCTPCTL_DEFAULT_CC_MODULE)
+SCTP_UINT_SYSCTL(default_ss_module, sctp_default_ss_module, SCTPCTL_DEFAULT_SS_MODULE)
+SCTP_UINT_SYSCTL(default_frag_interleave, sctp_default_frag_interleave, SCTPCTL_DEFAULT_FRAG_INTERLEAVE)
+SCTP_UINT_SYSCTL(mobility_base, sctp_mobility_base, SCTPCTL_MOBILITY_BASE)
+SCTP_UINT_SYSCTL(mobility_fasthandoff, sctp_mobility_fasthandoff, SCTPCTL_MOBILITY_FASTHANDOFF)
+#if defined(SCTP_LOCAL_TRACE_BUF)
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, log, CTLFLAG_VNET|CTLTYPE_STRUCT|CTLFLAG_RD,
+ NULL, 0, sctp_sysctl_handle_trace_log, "S,sctplog", "SCTP logging (struct sctp_log)");
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, clear_trace, CTLFLAG_VNET|CTLTYPE_UINT | CTLFLAG_RW,
+ NULL, 0, sctp_sysctl_handle_trace_log_clear, "IU", "Clear SCTP Logging buffer");
+#endif
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, udp_tunneling_port, CTLFLAG_VNET|CTLTYPE_UINT|CTLFLAG_RW,
+ NULL, 0, sctp_sysctl_handle_udp_tunneling, "IU", SCTPCTL_UDP_TUNNELING_PORT_DESC);
+SCTP_UINT_SYSCTL(enable_sack_immediately, sctp_enable_sack_immediately, SCTPCTL_SACK_IMMEDIATELY_ENABLE)
+SCTP_UINT_SYSCTL(nat_friendly_init, sctp_inits_include_nat_friendly, SCTPCTL_NAT_FRIENDLY_INITS)
+SCTP_UINT_SYSCTL(vtag_time_wait, sctp_vtag_time_wait, SCTPCTL_TIME_WAIT)
+SCTP_UINT_SYSCTL(buffer_splitting, sctp_buffer_splitting, SCTPCTL_BUFFER_SPLITTING)
+SCTP_UINT_SYSCTL(initial_cwnd, sctp_initial_cwnd, SCTPCTL_INITIAL_CWND)
+SCTP_UINT_SYSCTL(rttvar_bw, sctp_rttvar_bw, SCTPCTL_RTTVAR_BW)
+SCTP_UINT_SYSCTL(rttvar_rtt, sctp_rttvar_rtt, SCTPCTL_RTTVAR_RTT)
+SCTP_UINT_SYSCTL(rttvar_eqret, sctp_rttvar_eqret, SCTPCTL_RTTVAR_EQRET)
+SCTP_UINT_SYSCTL(rttvar_steady_step, sctp_steady_step, SCTPCTL_RTTVAR_STEADYS)
+SCTP_UINT_SYSCTL(use_dcccecn, sctp_use_dccc_ecn, SCTPCTL_RTTVAR_DCCCECN)
+SCTP_UINT_SYSCTL(blackhole, sctp_blackhole, SCTPCTL_BLACKHOLE)
+SCTP_UINT_SYSCTL(sendall_limit, sctp_sendall_limit, SCTPCTL_SENDALL_LIMIT)
+SCTP_UINT_SYSCTL(diag_info_code, sctp_diag_info_code, SCTPCTL_DIAG_INFO_CODE)
+SCTP_UINT_SYSCTL(ootb_with_zero_cksum, sctp_ootb_with_zero_cksum, SCTPCTL_OOTB_WITH_ZERO_CKSUM)
+#ifdef SCTP_DEBUG
+SCTP_UINT_SYSCTL(debug, sctp_debug_on, SCTPCTL_DEBUG)
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+SCTP_UINT_SYSCTL(main_timer, sctp_main_timer, SCTPCTL_MAIN_TIMER)
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, ignore_vmware_interfaces, CTLTYPE_UINT|CTLFLAG_RW,
+ NULL, 0, sctp_sysctl_handle_vmware_interfaces, "IU", SCTPCTL_IGNORE_VMWARE_INTERFACES_DESC);
+SCTP_UINT_SYSCTL(addr_watchdog_limit, sctp_addr_watchdog_limit, SCTPCTL_ADDR_WATCHDOG_LIMIT)
+SCTP_UINT_SYSCTL(vtag_watchdog_limit, sctp_vtag_watchdog_limit, SCTPCTL_VTAG_WATCHDOG_LIMIT)
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+SCTP_UINT_SYSCTL(output_unlocked, sctp_output_unlocked, SCTPCTL_OUTPUT_UNLOCKED)
+#endif
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, stats, CTLFLAG_VNET|CTLTYPE_STRUCT|CTLFLAG_RW,
+ NULL, 0, sctp_sysctl_handle_stats, "S,sctpstat", "SCTP statistics (struct sctp_stat)");
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, assoclist, CTLFLAG_VNET|CTLTYPE_OPAQUE|CTLFLAG_RD,
+ NULL, 0, sctp_sysctl_handle_assoclist, "S,xassoc", "List of active SCTP associations");
+
+#elif defined(_WIN32) && !defined(__Userspace__)
+
+#define RANGECHK(var, min, max) \
+ if ((var) < (min)) { (var) = (min); } \
+ else if ((var) > (max)) { (var) = (max); }
+
+static int
+sctp_sysctl_handle_int(SYSCTL_HANDLER_ARGS)
+{
+ int error;
+
+ error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req);
+ if (error == 0) {
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_sendspace), SCTPCTL_MAXDGRAM_MIN, SCTPCTL_MAXDGRAM_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_recvspace), SCTPCTL_RECVSPACE_MIN, SCTPCTL_RECVSPACE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_auto_asconf), SCTPCTL_AUTOASCONF_MIN, SCTPCTL_AUTOASCONF_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_auto_asconf), SCTPCTL_AUTOASCONF_MIN, SCTPCTL_AUTOASCONF_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_ecn_enable), SCTPCTL_ECN_ENABLE_MIN, SCTPCTL_ECN_ENABLE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_pr_enable), SCTPCTL_PR_ENABLE_MIN, SCTPCTL_PR_ENABLE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_reconfig_enable), SCTPCTL_RECONFIG_ENABLE_MIN, SCTPCTL_RECONFIG_ENABLE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_nrsack_enable), SCTPCTL_NRSACK_ENABLE_MIN, SCTPCTL_NRSACK_ENABLE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_pktdrop_enable), SCTPCTL_PKTDROP_ENABLE_MIN, SCTPCTL_PKTDROP_ENABLE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback), SCTPCTL_LOOPBACK_NOCSUM_MIN, SCTPCTL_LOOPBACK_NOCSUM_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_peer_chunk_oh), SCTPCTL_PEER_CHKOH_MIN, SCTPCTL_PEER_CHKOH_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_max_burst_default), SCTPCTL_MAXBURST_MIN, SCTPCTL_MAXBURST_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_fr_max_burst_default), SCTPCTL_FRMAXBURST_MIN, SCTPCTL_FRMAXBURST_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue), SCTPCTL_MAXCHUNKS_MIN, SCTPCTL_MAXCHUNKS_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_hashtblsize), SCTPCTL_TCBHASHSIZE_MIN, SCTPCTL_TCBHASHSIZE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_pcbtblsize), SCTPCTL_PCBHASHSIZE_MIN, SCTPCTL_PCBHASHSIZE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_min_split_point), SCTPCTL_MIN_SPLIT_POINT_MIN, SCTPCTL_MIN_SPLIT_POINT_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_chunkscale), SCTPCTL_CHUNKSCALE_MIN, SCTPCTL_CHUNKSCALE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_delayed_sack_time_default), SCTPCTL_DELAYED_SACK_TIME_MIN, SCTPCTL_DELAYED_SACK_TIME_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_sack_freq_default), SCTPCTL_SACK_FREQ_MIN, SCTPCTL_SACK_FREQ_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_system_free_resc_limit), SCTPCTL_SYS_RESOURCE_MIN, SCTPCTL_SYS_RESOURCE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_asoc_free_resc_limit), SCTPCTL_ASOC_RESOURCE_MIN, SCTPCTL_ASOC_RESOURCE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_heartbeat_interval_default), SCTPCTL_HEARTBEAT_INTERVAL_MIN, SCTPCTL_HEARTBEAT_INTERVAL_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_pmtu_raise_time_default), SCTPCTL_PMTU_RAISE_TIME_MIN, SCTPCTL_PMTU_RAISE_TIME_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_shutdown_guard_time_default), SCTPCTL_SHUTDOWN_GUARD_TIME_MIN, SCTPCTL_SHUTDOWN_GUARD_TIME_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_secret_lifetime_default), SCTPCTL_SECRET_LIFETIME_MIN, SCTPCTL_SECRET_LIFETIME_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_rto_max_default), SCTPCTL_RTO_MAX_MIN, SCTPCTL_RTO_MAX_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_rto_min_default), SCTPCTL_RTO_MIN_MIN, SCTPCTL_RTO_MIN_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_rto_initial_default), SCTPCTL_RTO_INITIAL_MIN, SCTPCTL_RTO_INITIAL_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_init_rto_max_default), SCTPCTL_INIT_RTO_MAX_MIN, SCTPCTL_INIT_RTO_MAX_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_valid_cookie_life_default), SCTPCTL_VALID_COOKIE_LIFE_MIN, SCTPCTL_VALID_COOKIE_LIFE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_init_rtx_max_default), SCTPCTL_INIT_RTX_MAX_MIN, SCTPCTL_INIT_RTX_MAX_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_assoc_rtx_max_default), SCTPCTL_ASSOC_RTX_MAX_MIN, SCTPCTL_ASSOC_RTX_MAX_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_path_rtx_max_default), SCTPCTL_PATH_RTX_MAX_MIN, SCTPCTL_PATH_RTX_MAX_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_path_pf_threshold), SCTPCTL_PATH_PF_THRESHOLD_MIN, SCTPCTL_PATH_PF_THRESHOLD_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_add_more_threshold), SCTPCTL_ADD_MORE_ON_OUTPUT_MIN, SCTPCTL_ADD_MORE_ON_OUTPUT_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_nr_incoming_streams_default), SCTPCTL_INCOMING_STREAMS_MIN, SCTPCTL_INCOMING_STREAMS_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_nr_outgoing_streams_default), SCTPCTL_OUTGOING_STREAMS_MIN, SCTPCTL_OUTGOING_STREAMS_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_cmt_on_off), SCTPCTL_CMT_ON_OFF_MIN, SCTPCTL_CMT_ON_OFF_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_cmt_use_dac), SCTPCTL_CMT_USE_DAC_MIN, SCTPCTL_CMT_USE_DAC_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_use_cwnd_based_maxburst), SCTPCTL_CWND_MAXBURST_MIN, SCTPCTL_CWND_MAXBURST_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_nat_friendly), SCTPCTL_NAT_FRIENDLY_MIN, SCTPCTL_NAT_FRIENDLY_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_L2_abc_variable), SCTPCTL_ABC_L_VAR_MIN, SCTPCTL_ABC_L_VAR_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count), SCTPCTL_MAX_CHAINED_MBUFS_MIN, SCTPCTL_MAX_CHAINED_MBUFS_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_do_drain), SCTPCTL_DO_SCTP_DRAIN_MIN, SCTPCTL_DO_SCTP_DRAIN_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_hb_maxburst), SCTPCTL_HB_MAX_BURST_MIN, SCTPCTL_HB_MAX_BURST_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_abort_if_one_2_one_hits_limit), SCTPCTL_ABORT_AT_LIMIT_MIN, SCTPCTL_ABORT_AT_LIMIT_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_min_residual), SCTPCTL_MIN_RESIDUAL_MIN, SCTPCTL_MIN_RESIDUAL_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_max_retran_chunk), SCTPCTL_MAX_RETRAN_CHUNK_MIN, SCTPCTL_MAX_RETRAN_CHUNK_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_logging_level), SCTPCTL_LOGGING_LEVEL_MIN, SCTPCTL_LOGGING_LEVEL_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_default_cc_module), SCTPCTL_DEFAULT_CC_MODULE_MIN, SCTPCTL_DEFAULT_CC_MODULE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_default_ss_module), SCTPCTL_DEFAULT_SS_MODULE_MIN, SCTPCTL_DEFAULT_SS_MODULE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_default_frag_interleave), SCTPCTL_DEFAULT_FRAG_INTERLEAVE_MIN, SCTPCTL_DEFAULT_FRAG_INTERLEAVE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_vtag_time_wait), SCTPCTL_TIME_WAIT_MIN, SCTPCTL_TIME_WAIT_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_buffer_splitting), SCTPCTL_BUFFER_SPLITTING_MIN, SCTPCTL_BUFFER_SPLITTING_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_initial_cwnd), SCTPCTL_INITIAL_CWND_MIN, SCTPCTL_INITIAL_CWND_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_rttvar_bw), SCTPCTL_RTTVAR_BW_MIN, SCTPCTL_RTTVAR_BW_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_rttvar_rtt), SCTPCTL_RTTVAR_RTT_MIN, SCTPCTL_RTTVAR_RTT_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_rttvar_eqret), SCTPCTL_RTTVAR_EQRET_MIN, SCTPCTL_RTTVAR_EQRET_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_steady_step), SCTPCTL_RTTVAR_STEADYS_MIN, SCTPCTL_RTTVAR_STEADYS_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_use_dccc_ecn), SCTPCTL_RTTVAR_DCCCECN_MIN, SCTPCTL_RTTVAR_DCCCECN_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_mobility_base), SCTPCTL_MOBILITY_BASE_MIN, SCTPCTL_MOBILITY_BASE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_mobility_fasthandoff), SCTPCTL_MOBILITY_FASTHANDOFF_MIN, SCTPCTL_MOBILITY_FASTHANDOFF_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_enable_sack_immediately), SCTPCTL_SACK_IMMEDIATELY_ENABLE_MIN, SCTPCTL_SACK_IMMEDIATELY_ENABLE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_inits_include_nat_friendly), SCTPCTL_NAT_FRIENDLY_INITS_MIN, SCTPCTL_NAT_FRIENDLY_INITS_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_blackhole), SCTPCTL_BLACKHOLE_MIN, SCTPCTL_BLACKHOLE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_sendall_limit), SCTPCTL_SENDALL_LIMIT_MIN, SCTPCTL_SENDALL_LIMIT_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_diag_info_code), SCTPCTL_DIAG_INFO_CODE_MIN, SCTPCTL_DIAG_INFO_CODE_MAX);
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_ootb_with_zero_cksum), SCTPCTL_OOTB_WITH_ZERO_CKSUM_MIN, SCTPCTL_OOTB_WITH_ZERO_CKSUM_MAX);
+#ifdef SCTP_DEBUG
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_debug_on), SCTPCTL_DEBUG_MIN, SCTPCTL_DEBUG_MAX);
+#endif
+ }
+ return (error);
+}
+
+void
+sysctl_setup_sctp(void)
+{
+ sysctl_add_oid(&sysctl_oid_top, "sendspace", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_sendspace), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MAXDGRAM_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "recvspace", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_recvspace), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RECVSPACE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "auto_asconf", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_auto_asconf), 0, sctp_sysctl_handle_int,
+ SCTPCTL_AUTOASCONF_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "ecn_enable", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_ecn_enable), 0, sctp_sysctl_handle_int,
+ SCTPCTL_ECN_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "pr_enable", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_pr_enable), 0, sctp_sysctl_handle_int,
+ SCTPCTL_PR_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "auth_enable", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_auth_enable), 0, sctp_sysctl_handle_auth,
+ SCTPCTL_AUTH_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "asconf_enable", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_asconf_enable), 0, sctp_sysctl_handle_asconf,
+ SCTPCTL_ASCONF_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "reconfig_enable", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_reconfig_enable), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RECONFIG_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "nrsack_enable", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_nrsack_enable), 0, sctp_sysctl_handle_int,
+ SCTPCTL_NRSACK_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "pktdrop_enable", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_pktdrop_enable), 0, sctp_sysctl_handle_int,
+ SCTPCTL_PKTDROP_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "loopback_nocsum", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback), 0, sctp_sysctl_handle_int,
+ SCTPCTL_LOOPBACK_NOCSUM_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "peer_chkoh", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_peer_chunk_oh), 0, sctp_sysctl_handle_int,
+ SCTPCTL_PEER_CHKOH_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "maxburst", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_max_burst_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MAXBURST_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "fr_maxburst", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_fr_max_burst_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_FRMAXBURST_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "maxchunks", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MAXCHUNKS_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "tcbhashsize", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_hashtblsize), 0, sctp_sysctl_handle_int,
+ SCTPCTL_TCBHASHSIZE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "pcbhashsize", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_pcbtblsize), 0, sctp_sysctl_handle_int,
+ SCTPCTL_PCBHASHSIZE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "min_split_point", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_min_split_point), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MIN_SPLIT_POINT_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "chunkscale", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_chunkscale), 0, sctp_sysctl_handle_int,
+ SCTPCTL_CHUNKSCALE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "delayed_sack_time", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_delayed_sack_time_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_DELAYED_SACK_TIME_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "sack_freq", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_sack_freq_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_SACK_FREQ_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "sys_resource", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_system_free_resc_limit), 0, sctp_sysctl_handle_int,
+ SCTPCTL_SYS_RESOURCE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "asoc_resource", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_asoc_free_resc_limit), 0, sctp_sysctl_handle_int,
+ SCTPCTL_ASOC_RESOURCE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "heartbeat_interval", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_heartbeat_interval_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_HEARTBEAT_INTERVAL_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "pmtu_raise_time", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_pmtu_raise_time_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_PMTU_RAISE_TIME_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "shutdown_guard_time", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_shutdown_guard_time_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_SHUTDOWN_GUARD_TIME_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "secret_lifetime", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_secret_lifetime_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_SECRET_LIFETIME_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "rto_max", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_rto_max_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTO_MAX_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "rto_min", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_rto_min_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTO_MIN_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "rto_initial", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_rto_initial_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTO_INITIAL_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "init_rto_max", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_init_rto_max_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_INIT_RTO_MAX_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "valid_cookie_life", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_valid_cookie_life_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_VALID_COOKIE_LIFE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "init_rtx_max", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_init_rtx_max_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_INIT_RTX_MAX_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "assoc_rtx_max", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_assoc_rtx_max_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_ASSOC_RTX_MAX_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "path_rtx_max", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_path_rtx_max_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_PATH_RTX_MAX_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "path_pf_threshold", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_path_pf_threshold), 0, sctp_sysctl_handle_int,
+ SCTPCTL_PATH_PF_THRESHOLD_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "add_more_on_output", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_add_more_threshold), 0, sctp_sysctl_handle_int,
+ SCTPCTL_ADD_MORE_ON_OUTPUT_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "incoming_streams", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_nr_incoming_streams_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_INCOMING_STREAMS_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "outgoing_streams", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_nr_outgoing_streams_default), 0, sctp_sysctl_handle_int,
+ SCTPCTL_OUTGOING_STREAMS_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "cmt_on_off", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_cmt_on_off), 0, sctp_sysctl_handle_int,
+ SCTPCTL_CMT_ON_OFF_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "cmt_use_dac", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_cmt_use_dac), 0, sctp_sysctl_handle_int,
+ SCTPCTL_CMT_USE_DAC_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "cwnd_maxburst", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_use_cwnd_based_maxburst), 0, sctp_sysctl_handle_int,
+ SCTPCTL_CWND_MAXBURST_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "nat_friendly", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_nat_friendly), 0, sctp_sysctl_handle_int,
+ SCTPCTL_NAT_FRIENDLY_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "abc_l_var", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_L2_abc_variable), 0, sctp_sysctl_handle_int,
+ SCTPCTL_ABC_L_VAR_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "max_chained_mbufs", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MAX_CHAINED_MBUFS_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "do_sctp_drain", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_do_drain), 0, sctp_sysctl_handle_int,
+ SCTPCTL_DO_SCTP_DRAIN_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "hb_max_burst", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_hb_maxburst), 0, sctp_sysctl_handle_int,
+ SCTPCTL_HB_MAX_BURST_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "abort_at_limit", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_abort_if_one_2_one_hits_limit), 0, sctp_sysctl_handle_int,
+ SCTPCTL_ABORT_AT_LIMIT_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "min_residual", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_min_residual), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MIN_RESIDUAL_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "max_retran_chunk", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_max_retran_chunk), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MAX_RETRAN_CHUNK_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "log_level", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_logging_level), 0, sctp_sysctl_handle_int,
+ SCTPCTL_LOGGING_LEVEL_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "default_cc_module", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_default_cc_module), 0, sctp_sysctl_handle_int,
+ SCTPCTL_DEFAULT_CC_MODULE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "default_ss_module", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_default_ss_module), 0, sctp_sysctl_handle_int,
+ SCTPCTL_DEFAULT_SS_MODULE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "default_frag_interleave", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_default_frag_interleave), 0, sctp_sysctl_handle_int,
+ SCTPCTL_DEFAULT_FRAG_INTERLEAVE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "mobility_base", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_mobility_base), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MOBILITY_BASE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "mobility_fasthandoff", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_mobility_fasthandoff), 0, sctp_sysctl_handle_int,
+ SCTPCTL_MOBILITY_FASTHANDOFF_DESC);
+
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ sysctl_add_oid(&sysctl_oid_top, "sctp_log", CTLTYPE_STRUCT|CTLFLAG_RD,
+ SCTP_BASE_SYSCTL(sctp_log), sizeof(struct sctp_log), NULL,
+ "SCTP logging (struct sctp_log)");
+
+ sysctl_add_oid(&sysctl_oid_top, "clear_trace", CTLTYPE_INT|CTLFLAG_WR,
+ NULL, 0, sctp_sysctl_handle_trace_log_clear,
+ "Clear SCTP Logging buffer");
+#endif
+
+ sysctl_add_oid(&sysctl_oid_top, "udp_tunneling_port", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_udp_tunneling_port), 0, sctp_sysctl_handle_udp_tunneling,
+ SCTPCTL_UDP_TUNNELING_PORT_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "enable_sack_immediately", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_enable_sack_immediately), 0, sctp_sysctl_handle_int,
+ SCTPCTL_SACK_IMMEDIATELY_ENABLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "nat_friendly_init", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_inits_include_nat_friendly), 0, sctp_sysctl_handle_int,
+ SCTPCTL_NAT_FRIENDLY_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "vtag_time_wait", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_vtag_time_wait), 0, sctp_sysctl_handle_int,
+ SCTPCTL_TIME_WAIT_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "buffer_splitting", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_buffer_splitting), 0, sctp_sysctl_handle_int,
+ SCTPCTL_BUFFER_SPLITTING_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "initial_cwnd", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_initial_cwnd), 0, sctp_sysctl_handle_int,
+ SCTPCTL_INITIAL_CWND_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "rttvar_bw", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_rttvar_bw), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTTVAR_BW_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "rttvar_rtt", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_rttvar_rtt), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTTVAR_RTT_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "rttvar_eqret", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_rttvar_eqret), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTTVAR_EQRET_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "rttvar_steady_step", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_steady_step), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTTVAR_STEADYS_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "use_dcccecn", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_use_dccc_ecn), 0, sctp_sysctl_handle_int,
+ SCTPCTL_RTTVAR_DCCCECN_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "blackhole", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_blackhole), 0, sctp_sysctl_handle_int,
+ SCTPCTL_BLACKHOLE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "sendall_limit", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_sendall_limit), 0, sctp_sysctl_handle_int,
+ SCTPCTL_SENDALL_LIMIT_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "diag_info_code", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_diag_info_code), 0, sctp_sysctl_handle_int,
+ SCTPCTL_DIAG_INFO_CODE_DESC);
+
+ sysctl_add_oid(&sysctl_oid_top, "ootb_with_zero_cksum", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_ootb_with_zero_cksum), 0, sctp_sysctl_handle_int,
+ SCTPCTL_OOTB_WITH_ZERO_CKSUM_DESC);
+
+#ifdef SCTP_DEBUG
+ sysctl_add_oid(&sysctl_oid_top, "debug", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_debug_on), 0, sctp_sysctl_handle_int,
+ SCTPCTL_DEBUG_DESC);
+#endif
+
+ sysctl_add_oid(&sysctl_oid_top, "stats", CTLTYPE_STRUCT|CTLFLAG_RW,
+ &SCTP_BASE_STATS, sizeof(SCTP_BASE_STATS), NULL,
+ "SCTP statistics (struct sctp_stat)");
+
+ sysctl_add_oid(&sysctl_oid_top, "assoclist", CTLTYPE_STRUCT|CTLFLAG_RD,
+ NULL, 0, sctp_assoclist,
+ "List of active SCTP associations");
+}
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_sysctl.h b/netwerk/sctp/src/netinet/sctp_sysctl.h
new file mode 100644
index 0000000000..fb77a3aeb5
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sysctl.h
@@ -0,0 +1,628 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_SYSCTL_H_
+#define _NETINET_SCTP_SYSCTL_H_
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_constants.h>
+
+struct sctp_sysctl {
+ uint32_t sctp_sendspace;
+ uint32_t sctp_recvspace;
+ uint32_t sctp_auto_asconf;
+ uint32_t sctp_multiple_asconfs;
+ uint32_t sctp_ecn_enable;
+ uint32_t sctp_pr_enable;
+ uint32_t sctp_auth_enable;
+ uint32_t sctp_asconf_enable;
+ uint32_t sctp_reconfig_enable;
+ uint32_t sctp_nrsack_enable;
+ uint32_t sctp_pktdrop_enable;
+ uint32_t sctp_fr_max_burst_default;
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ uint32_t sctp_no_csum_on_loopback;
+#endif
+ uint32_t sctp_peer_chunk_oh;
+ uint32_t sctp_max_burst_default;
+ uint32_t sctp_max_chunks_on_queue;
+ uint32_t sctp_hashtblsize;
+ uint32_t sctp_pcbtblsize;
+ uint32_t sctp_min_split_point;
+ uint32_t sctp_chunkscale;
+ uint32_t sctp_delayed_sack_time_default;
+ uint32_t sctp_sack_freq_default;
+ uint32_t sctp_system_free_resc_limit;
+ uint32_t sctp_asoc_free_resc_limit;
+ uint32_t sctp_heartbeat_interval_default;
+ uint32_t sctp_pmtu_raise_time_default;
+ uint32_t sctp_shutdown_guard_time_default;
+ uint32_t sctp_secret_lifetime_default;
+ uint32_t sctp_rto_max_default;
+ uint32_t sctp_rto_min_default;
+ uint32_t sctp_rto_initial_default;
+ uint32_t sctp_init_rto_max_default;
+ uint32_t sctp_valid_cookie_life_default;
+ uint32_t sctp_init_rtx_max_default;
+ uint32_t sctp_assoc_rtx_max_default;
+ uint32_t sctp_path_rtx_max_default;
+ uint32_t sctp_path_pf_threshold;
+ uint32_t sctp_add_more_threshold;
+ uint32_t sctp_nr_incoming_streams_default;
+ uint32_t sctp_nr_outgoing_streams_default;
+ uint32_t sctp_cmt_on_off;
+ uint32_t sctp_cmt_use_dac;
+ uint32_t sctp_use_cwnd_based_maxburst;
+ uint32_t sctp_nat_friendly;
+ uint32_t sctp_L2_abc_variable;
+ uint32_t sctp_mbuf_threshold_count;
+ uint32_t sctp_do_drain;
+ uint32_t sctp_hb_maxburst;
+ uint32_t sctp_abort_if_one_2_one_hits_limit;
+ uint32_t sctp_min_residual;
+ uint32_t sctp_max_retran_chunk;
+ uint32_t sctp_logging_level;
+ /* JRS - Variable for default congestion control module */
+ uint32_t sctp_default_cc_module;
+ /* RS - Variable for default stream scheduling module */
+ uint32_t sctp_default_ss_module;
+ uint32_t sctp_default_frag_interleave;
+ uint32_t sctp_mobility_base;
+ uint32_t sctp_mobility_fasthandoff;
+ uint32_t sctp_inits_include_nat_friendly;
+ uint32_t sctp_rttvar_bw;
+ uint32_t sctp_rttvar_rtt;
+ uint32_t sctp_rttvar_eqret;
+ uint32_t sctp_steady_step;
+ uint32_t sctp_use_dccc_ecn;
+#if defined(SCTP_LOCAL_TRACE_BUF)
+#if defined(_WIN32) && !defined(__Userspace__)
+ struct sctp_log *sctp_log;
+#else
+ struct sctp_log sctp_log;
+#endif
+#endif
+ uint32_t sctp_udp_tunneling_port;
+ uint32_t sctp_enable_sack_immediately;
+ uint32_t sctp_vtag_time_wait;
+ uint32_t sctp_buffer_splitting;
+ uint32_t sctp_initial_cwnd;
+ uint32_t sctp_blackhole;
+ uint32_t sctp_sendall_limit;
+ uint32_t sctp_diag_info_code;
+ uint32_t sctp_ootb_with_zero_cksum;
+#if defined(SCTP_DEBUG)
+ uint32_t sctp_debug_on;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ uint32_t sctp_ignore_vmware_interfaces;
+ uint32_t sctp_main_timer;
+ uint32_t sctp_addr_watchdog_limit;
+ uint32_t sctp_vtag_watchdog_limit;
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ uint32_t sctp_output_unlocked;
+#endif
+};
+
+/*
+ * limits for the sysctl variables
+ */
+/* maxdgram: Maximum outgoing SCTP buffer size */
+#define SCTPCTL_MAXDGRAM_DESC "Maximum outgoing SCTP buffer size"
+#define SCTPCTL_MAXDGRAM_MIN 0
+#define SCTPCTL_MAXDGRAM_MAX 0xFFFFFFFF
+#if defined(__Userspace__)
+#define SCTPCTL_MAXDGRAM_DEFAULT SB_MAX
+#else
+#define SCTPCTL_MAXDGRAM_DEFAULT 262144 /* 256k */
+#endif
+
+/* recvspace: Maximum incoming SCTP buffer size */
+#define SCTPCTL_RECVSPACE_DESC "Maximum incoming SCTP buffer size"
+#define SCTPCTL_RECVSPACE_MIN 0
+#define SCTPCTL_RECVSPACE_MAX 0xFFFFFFFF
+#if defined(__Userspace__)
+#define SCTPCTL_RECVSPACE_DEFAULT SB_RAW
+#else
+#define SCTPCTL_RECVSPACE_DEFAULT 262144 /* 256k */
+#endif
+
+/* autoasconf: Enable SCTP Auto-ASCONF */
+#define SCTPCTL_AUTOASCONF_DESC "Enable SCTP Auto-ASCONF"
+#define SCTPCTL_AUTOASCONF_MIN 0
+#define SCTPCTL_AUTOASCONF_MAX 1
+#define SCTPCTL_AUTOASCONF_DEFAULT 1
+
+/* autoasconf: Enable SCTP Auto-ASCONF */
+#define SCTPCTL_MULTIPLEASCONFS_DESC "Enable SCTP Multiple-ASCONFs"
+#define SCTPCTL_MULTIPLEASCONFS_MIN 0
+#define SCTPCTL_MULTIPLEASCONFS_MAX 1
+#define SCTPCTL_MULTIPLEASCONFS_DEFAULT SCTP_DEFAULT_MULTIPLE_ASCONFS
+
+/* ecn_enable: Enable SCTP ECN */
+#define SCTPCTL_ECN_ENABLE_DESC "Enable SCTP ECN"
+#define SCTPCTL_ECN_ENABLE_MIN 0
+#define SCTPCTL_ECN_ENABLE_MAX 1
+#define SCTPCTL_ECN_ENABLE_DEFAULT 1
+
+/* pr_enable: Enable PR-SCTP */
+#define SCTPCTL_PR_ENABLE_DESC "Enable PR-SCTP"
+#define SCTPCTL_PR_ENABLE_MIN 0
+#define SCTPCTL_PR_ENABLE_MAX 1
+#define SCTPCTL_PR_ENABLE_DEFAULT 1
+
+/* auth_enable: Enable SCTP AUTH function */
+#define SCTPCTL_AUTH_ENABLE_DESC "Enable SCTP AUTH function"
+#define SCTPCTL_AUTH_ENABLE_MIN 0
+#define SCTPCTL_AUTH_ENABLE_MAX 1
+#define SCTPCTL_AUTH_ENABLE_DEFAULT 1
+
+/* asconf_enable: Enable SCTP ASCONF */
+#define SCTPCTL_ASCONF_ENABLE_DESC "Enable SCTP ASCONF"
+#define SCTPCTL_ASCONF_ENABLE_MIN 0
+#define SCTPCTL_ASCONF_ENABLE_MAX 1
+#define SCTPCTL_ASCONF_ENABLE_DEFAULT 1
+
+/* reconfig_enable: Enable SCTP RE-CONFIG */
+#define SCTPCTL_RECONFIG_ENABLE_DESC "Enable SCTP RE-CONFIG"
+#define SCTPCTL_RECONFIG_ENABLE_MIN 0
+#define SCTPCTL_RECONFIG_ENABLE_MAX 1
+#define SCTPCTL_RECONFIG_ENABLE_DEFAULT 1
+
+/* nrsack_enable: Enable NR_SACK */
+#define SCTPCTL_NRSACK_ENABLE_DESC "Enable SCTP NR-SACK"
+#define SCTPCTL_NRSACK_ENABLE_MIN 0
+#define SCTPCTL_NRSACK_ENABLE_MAX 1
+#define SCTPCTL_NRSACK_ENABLE_DEFAULT 0
+
+/* pktdrop_enable: Enable SCTP Packet Drop Reports */
+#define SCTPCTL_PKTDROP_ENABLE_DESC "Enable SCTP PKTDROP"
+#define SCTPCTL_PKTDROP_ENABLE_MIN 0
+#define SCTPCTL_PKTDROP_ENABLE_MAX 1
+#define SCTPCTL_PKTDROP_ENABLE_DEFAULT 0
+
+/* loopback_nocsum: Enable NO Csum on packets sent on loopback */
+#define SCTPCTL_LOOPBACK_NOCSUM_DESC "Enable NO Csum on packets sent on loopback"
+#define SCTPCTL_LOOPBACK_NOCSUM_MIN 0
+#define SCTPCTL_LOOPBACK_NOCSUM_MAX 1
+#define SCTPCTL_LOOPBACK_NOCSUM_DEFAULT 1
+
+/* peer_chkoh: Amount to debit peers rwnd per chunk sent */
+#define SCTPCTL_PEER_CHKOH_DESC "Amount to debit peers rwnd per chunk sent"
+#define SCTPCTL_PEER_CHKOH_MIN 0
+#define SCTPCTL_PEER_CHKOH_MAX 0xFFFFFFFF
+#define SCTPCTL_PEER_CHKOH_DEFAULT 256
+
+/* maxburst: Default max burst for sctp endpoints */
+#define SCTPCTL_MAXBURST_DESC "Default max burst for sctp endpoints"
+#define SCTPCTL_MAXBURST_MIN 0
+#define SCTPCTL_MAXBURST_MAX 0xFFFFFFFF
+#define SCTPCTL_MAXBURST_DEFAULT SCTP_DEF_MAX_BURST
+
+/* fr_maxburst: Default max burst for sctp endpoints when fast retransmitting */
+#define SCTPCTL_FRMAXBURST_DESC "Default max burst for SCTP endpoints when fast retransmitting"
+#define SCTPCTL_FRMAXBURST_MIN 0
+#define SCTPCTL_FRMAXBURST_MAX 0xFFFFFFFF
+#define SCTPCTL_FRMAXBURST_DEFAULT SCTP_DEF_FRMAX_BURST
+
+/* maxchunks: Default max chunks on queue per asoc */
+#define SCTPCTL_MAXCHUNKS_DESC "Default max chunks on queue per asoc"
+#define SCTPCTL_MAXCHUNKS_MIN 0
+#define SCTPCTL_MAXCHUNKS_MAX 0xFFFFFFFF
+#define SCTPCTL_MAXCHUNKS_DEFAULT SCTP_ASOC_MAX_CHUNKS_ON_QUEUE
+
+/* tcbhashsize: Tunable for Hash table sizes */
+#define SCTPCTL_TCBHASHSIZE_DESC "Tunable for TCB hash table sizes"
+#define SCTPCTL_TCBHASHSIZE_MIN 1
+#define SCTPCTL_TCBHASHSIZE_MAX 0xFFFFFFFF
+#define SCTPCTL_TCBHASHSIZE_DEFAULT SCTP_TCBHASHSIZE
+
+/* pcbhashsize: Tunable for PCB Hash table sizes */
+#define SCTPCTL_PCBHASHSIZE_DESC "Tunable for PCB hash table sizes"
+#define SCTPCTL_PCBHASHSIZE_MIN 1
+#define SCTPCTL_PCBHASHSIZE_MAX 0xFFFFFFFF
+#define SCTPCTL_PCBHASHSIZE_DEFAULT SCTP_PCBHASHSIZE
+
+/* min_split_point: Minimum size when splitting a chunk */
+#define SCTPCTL_MIN_SPLIT_POINT_DESC "Minimum size when splitting a chunk"
+#define SCTPCTL_MIN_SPLIT_POINT_MIN 0
+#define SCTPCTL_MIN_SPLIT_POINT_MAX 0xFFFFFFFF
+#define SCTPCTL_MIN_SPLIT_POINT_DEFAULT SCTP_DEFAULT_SPLIT_POINT_MIN
+
+/* chunkscale: Tunable for Scaling of number of chunks and messages */
+#define SCTPCTL_CHUNKSCALE_DESC "Tunable for scaling of number of chunks and messages"
+#define SCTPCTL_CHUNKSCALE_MIN 1
+#define SCTPCTL_CHUNKSCALE_MAX 0xFFFFFFFF
+#define SCTPCTL_CHUNKSCALE_DEFAULT SCTP_CHUNKQUEUE_SCALE
+
+/* delayed_sack_time: Default delayed SACK timer in ms */
+#define SCTPCTL_DELAYED_SACK_TIME_DESC "Default delayed SACK timer in ms"
+#define SCTPCTL_DELAYED_SACK_TIME_MIN 0
+#define SCTPCTL_DELAYED_SACK_TIME_MAX 0xFFFFFFFF
+#define SCTPCTL_DELAYED_SACK_TIME_DEFAULT SCTP_RECV_MSEC
+
+/* sack_freq: Default SACK frequency */
+#define SCTPCTL_SACK_FREQ_DESC "Default SACK frequency"
+#define SCTPCTL_SACK_FREQ_MIN 0
+#define SCTPCTL_SACK_FREQ_MAX 0xFFFFFFFF
+#define SCTPCTL_SACK_FREQ_DEFAULT SCTP_DEFAULT_SACK_FREQ
+
+/* sys_resource: Max number of cached resources in the system */
+#define SCTPCTL_SYS_RESOURCE_DESC "Max number of cached resources in the system"
+#define SCTPCTL_SYS_RESOURCE_MIN 0
+#define SCTPCTL_SYS_RESOURCE_MAX 0xFFFFFFFF
+#define SCTPCTL_SYS_RESOURCE_DEFAULT SCTP_DEF_SYSTEM_RESC_LIMIT
+
+/* asoc_resource: Max number of cached resources in an asoc */
+#define SCTPCTL_ASOC_RESOURCE_DESC "Max number of cached resources in an asoc"
+#define SCTPCTL_ASOC_RESOURCE_MIN 0
+#define SCTPCTL_ASOC_RESOURCE_MAX 0xFFFFFFFF
+#define SCTPCTL_ASOC_RESOURCE_DEFAULT SCTP_DEF_ASOC_RESC_LIMIT
+
+/* heartbeat_interval: Default heartbeat interval in ms */
+#define SCTPCTL_HEARTBEAT_INTERVAL_DESC "Default heartbeat interval in ms"
+#define SCTPCTL_HEARTBEAT_INTERVAL_MIN 0
+#define SCTPCTL_HEARTBEAT_INTERVAL_MAX 0xFFFFFFFF
+#define SCTPCTL_HEARTBEAT_INTERVAL_DEFAULT SCTP_HB_DEFAULT_MSEC
+
+/* pmtu_raise_time: Default PMTU raise timer in seconds */
+#define SCTPCTL_PMTU_RAISE_TIME_DESC "Default PMTU raise timer in seconds"
+#define SCTPCTL_PMTU_RAISE_TIME_MIN 0
+#define SCTPCTL_PMTU_RAISE_TIME_MAX 0xFFFFFFFF
+#define SCTPCTL_PMTU_RAISE_TIME_DEFAULT SCTP_DEF_PMTU_RAISE_SEC
+
+/* shutdown_guard_time: Default shutdown guard timer in seconds */
+#define SCTPCTL_SHUTDOWN_GUARD_TIME_DESC "Shutdown guard timer in seconds (0 means 5 times RTO.Max)"
+#define SCTPCTL_SHUTDOWN_GUARD_TIME_MIN 0
+#define SCTPCTL_SHUTDOWN_GUARD_TIME_MAX 0xFFFFFFFF
+#define SCTPCTL_SHUTDOWN_GUARD_TIME_DEFAULT 0
+
+/* secret_lifetime: Default secret lifetime in seconds */
+#define SCTPCTL_SECRET_LIFETIME_DESC "Default secret lifetime in seconds"
+#define SCTPCTL_SECRET_LIFETIME_MIN 0
+#define SCTPCTL_SECRET_LIFETIME_MAX 0xFFFFFFFF
+#define SCTPCTL_SECRET_LIFETIME_DEFAULT SCTP_DEFAULT_SECRET_LIFE_SEC
+
+/* rto_max: Default maximum retransmission timeout in ms */
+#define SCTPCTL_RTO_MAX_DESC "Default maximum retransmission timeout in ms"
+#define SCTPCTL_RTO_MAX_MIN 0
+#define SCTPCTL_RTO_MAX_MAX 0xFFFFFFFF
+#define SCTPCTL_RTO_MAX_DEFAULT SCTP_RTO_UPPER_BOUND
+
+/* rto_min: Default minimum retransmission timeout in ms */
+#define SCTPCTL_RTO_MIN_DESC "Default minimum retransmission timeout in ms"
+#define SCTPCTL_RTO_MIN_MIN 0
+#define SCTPCTL_RTO_MIN_MAX 0xFFFFFFFF
+#define SCTPCTL_RTO_MIN_DEFAULT SCTP_RTO_LOWER_BOUND
+
+/* rto_initial: Default initial retransmission timeout in ms */
+#define SCTPCTL_RTO_INITIAL_DESC "Default initial retransmission timeout in ms"
+#define SCTPCTL_RTO_INITIAL_MIN 0
+#define SCTPCTL_RTO_INITIAL_MAX 0xFFFFFFFF
+#define SCTPCTL_RTO_INITIAL_DEFAULT SCTP_RTO_INITIAL
+
+/* init_rto_max: Default maximum retransmission timeout during association setup in ms */
+#define SCTPCTL_INIT_RTO_MAX_DESC "Default maximum retransmission timeout during association setup in ms"
+#define SCTPCTL_INIT_RTO_MAX_MIN 0
+#define SCTPCTL_INIT_RTO_MAX_MAX 0xFFFFFFFF
+#define SCTPCTL_INIT_RTO_MAX_DEFAULT SCTP_RTO_UPPER_BOUND
+
+/* valid_cookie_life: Default cookie lifetime in ms */
+#define SCTPCTL_VALID_COOKIE_LIFE_DESC "Default cookie lifetime in ms"
+#define SCTPCTL_VALID_COOKIE_LIFE_MIN SCTP_MIN_COOKIE_LIFE
+#define SCTPCTL_VALID_COOKIE_LIFE_MAX SCTP_MAX_COOKIE_LIFE
+#define SCTPCTL_VALID_COOKIE_LIFE_DEFAULT SCTP_DEFAULT_COOKIE_LIFE
+
+/* init_rtx_max: Default maximum number of retransmission for INIT chunks */
+#define SCTPCTL_INIT_RTX_MAX_DESC "Default maximum number of retransmissions for INIT chunks"
+#define SCTPCTL_INIT_RTX_MAX_MIN 0
+#define SCTPCTL_INIT_RTX_MAX_MAX 0xFFFFFFFF
+#define SCTPCTL_INIT_RTX_MAX_DEFAULT SCTP_DEF_MAX_INIT
+
+/* assoc_rtx_max: Default maximum number of retransmissions per association */
+#define SCTPCTL_ASSOC_RTX_MAX_DESC "Default maximum number of retransmissions per association"
+#define SCTPCTL_ASSOC_RTX_MAX_MIN 0
+#define SCTPCTL_ASSOC_RTX_MAX_MAX 0xFFFFFFFF
+#define SCTPCTL_ASSOC_RTX_MAX_DEFAULT SCTP_DEF_MAX_SEND
+
+/* path_rtx_max: Default maximum of retransmissions per path */
+#define SCTPCTL_PATH_RTX_MAX_DESC "Default maximum of retransmissions per path"
+#define SCTPCTL_PATH_RTX_MAX_MIN 0
+#define SCTPCTL_PATH_RTX_MAX_MAX 0xFFFFFFFF
+#define SCTPCTL_PATH_RTX_MAX_DEFAULT SCTP_DEF_MAX_PATH_RTX
+
+/* path_pf_threshold: threshold for considering the path potentially failed */
+#define SCTPCTL_PATH_PF_THRESHOLD_DESC "Default potentially failed threshold"
+#define SCTPCTL_PATH_PF_THRESHOLD_MIN 0
+#define SCTPCTL_PATH_PF_THRESHOLD_MAX 0xFFFF
+#define SCTPCTL_PATH_PF_THRESHOLD_DEFAULT SCTPCTL_PATH_PF_THRESHOLD_MAX
+
+/* add_more_on_output: When space-wise is it worthwhile to try to add more to a socket send buffer */
+#define SCTPCTL_ADD_MORE_ON_OUTPUT_DESC "When space-wise is it worthwhile to try to add more to a socket send buffer"
+#define SCTPCTL_ADD_MORE_ON_OUTPUT_MIN 0
+#define SCTPCTL_ADD_MORE_ON_OUTPUT_MAX 0xFFFFFFFF
+#define SCTPCTL_ADD_MORE_ON_OUTPUT_DEFAULT SCTP_DEFAULT_ADD_MORE
+
+/* incoming_streams: Default number of incoming streams */
+#define SCTPCTL_INCOMING_STREAMS_DESC "Default number of incoming streams"
+#define SCTPCTL_INCOMING_STREAMS_MIN 1
+#define SCTPCTL_INCOMING_STREAMS_MAX 65535
+#define SCTPCTL_INCOMING_STREAMS_DEFAULT SCTP_ISTREAM_INITIAL
+
+/* outgoing_streams: Default number of outgoing streams */
+#define SCTPCTL_OUTGOING_STREAMS_DESC "Default number of outgoing streams"
+#define SCTPCTL_OUTGOING_STREAMS_MIN 1
+#define SCTPCTL_OUTGOING_STREAMS_MAX 65535
+#define SCTPCTL_OUTGOING_STREAMS_DEFAULT SCTP_OSTREAM_INITIAL
+
+/* cmt_on_off: CMT on/off flag */
+#define SCTPCTL_CMT_ON_OFF_DESC "CMT settings"
+#define SCTPCTL_CMT_ON_OFF_MIN SCTP_CMT_OFF
+#define SCTPCTL_CMT_ON_OFF_MAX SCTP_CMT_MAX
+#define SCTPCTL_CMT_ON_OFF_DEFAULT SCTP_CMT_OFF
+
+/* cmt_use_dac: CMT DAC on/off flag */
+#define SCTPCTL_CMT_USE_DAC_DESC "CMT DAC on/off flag"
+#define SCTPCTL_CMT_USE_DAC_MIN 0
+#define SCTPCTL_CMT_USE_DAC_MAX 1
+#define SCTPCTL_CMT_USE_DAC_DEFAULT 0
+
+/* cwnd_maxburst: Use a CWND adjusting to implement maxburst */
+#define SCTPCTL_CWND_MAXBURST_DESC "Adjust congestion control window to limit maximum burst when sending"
+#define SCTPCTL_CWND_MAXBURST_MIN 0
+#define SCTPCTL_CWND_MAXBURST_MAX 1
+#define SCTPCTL_CWND_MAXBURST_DEFAULT 1
+
+/* nat_friendly: SCTP NAT friendly operation */
+#define SCTPCTL_NAT_FRIENDLY_DESC "SCTP NAT friendly operation"
+#define SCTPCTL_NAT_FRIENDLY_MIN 0
+#define SCTPCTL_NAT_FRIENDLY_MAX 1
+#define SCTPCTL_NAT_FRIENDLY_DEFAULT 1
+
+/* abc_l_var: SCTP ABC max increase per SACK (L) */
+#define SCTPCTL_ABC_L_VAR_DESC "SCTP ABC max increase per SACK (L)"
+#define SCTPCTL_ABC_L_VAR_MIN 0
+#define SCTPCTL_ABC_L_VAR_MAX 0xFFFFFFFF
+#define SCTPCTL_ABC_L_VAR_DEFAULT 2
+
+/* max_chained_mbufs: Default max number of small mbufs on a chain */
+#define SCTPCTL_MAX_CHAINED_MBUFS_DESC "Default max number of small mbufs on a chain"
+#define SCTPCTL_MAX_CHAINED_MBUFS_MIN 0
+#define SCTPCTL_MAX_CHAINED_MBUFS_MAX 0xFFFFFFFF
+#define SCTPCTL_MAX_CHAINED_MBUFS_DEFAULT SCTP_DEFAULT_MBUFS_IN_CHAIN
+
+/* do_sctp_drain: Should SCTP respond to the drain calls */
+#define SCTPCTL_DO_SCTP_DRAIN_DESC "Should SCTP respond to the drain calls"
+#define SCTPCTL_DO_SCTP_DRAIN_MIN 0
+#define SCTPCTL_DO_SCTP_DRAIN_MAX 1
+#define SCTPCTL_DO_SCTP_DRAIN_DEFAULT 1
+
+/* hb_max_burst: Confirmation Heartbeat max burst? */
+#define SCTPCTL_HB_MAX_BURST_DESC "Confirmation Heartbeat max burst"
+#define SCTPCTL_HB_MAX_BURST_MIN 1
+#define SCTPCTL_HB_MAX_BURST_MAX 0xFFFFFFFF
+#define SCTPCTL_HB_MAX_BURST_DEFAULT SCTP_DEF_HBMAX_BURST
+
+/* abort_at_limit: When one-2-one hits qlimit abort */
+#define SCTPCTL_ABORT_AT_LIMIT_DESC "Abort when one-to-one hits qlimit"
+#define SCTPCTL_ABORT_AT_LIMIT_MIN 0
+#define SCTPCTL_ABORT_AT_LIMIT_MAX 1
+#define SCTPCTL_ABORT_AT_LIMIT_DEFAULT 0
+
+/* min_residual: min residual in a data fragment leftover */
+#define SCTPCTL_MIN_RESIDUAL_DESC "Minimum residual data chunk in second part of split"
+#define SCTPCTL_MIN_RESIDUAL_MIN 20
+#define SCTPCTL_MIN_RESIDUAL_MAX 65535
+#define SCTPCTL_MIN_RESIDUAL_DEFAULT 1452
+
+/* max_retran_chunk: max chunk retransmissions */
+#define SCTPCTL_MAX_RETRAN_CHUNK_DESC "Maximum times an unlucky chunk can be retransmitted before assoc abort"
+#define SCTPCTL_MAX_RETRAN_CHUNK_MIN 0
+#define SCTPCTL_MAX_RETRAN_CHUNK_MAX 65535
+#define SCTPCTL_MAX_RETRAN_CHUNK_DEFAULT 30
+
+/* sctp_logging: This gives us logging when the options are enabled */
+#define SCTPCTL_LOGGING_LEVEL_DESC "Ltrace/KTR trace logging level"
+#define SCTPCTL_LOGGING_LEVEL_MIN 0
+#define SCTPCTL_LOGGING_LEVEL_MAX 0xffffffff
+#define SCTPCTL_LOGGING_LEVEL_DEFAULT 0
+
+/* JRS - default congestion control module sysctl */
+#define SCTPCTL_DEFAULT_CC_MODULE_DESC "Default congestion control module"
+#define SCTPCTL_DEFAULT_CC_MODULE_MIN 0
+#define SCTPCTL_DEFAULT_CC_MODULE_MAX 2
+#define SCTPCTL_DEFAULT_CC_MODULE_DEFAULT 0
+
+/* RS - default stream scheduling module sysctl */
+#define SCTPCTL_DEFAULT_SS_MODULE_DESC "Default stream scheduling module"
+#define SCTPCTL_DEFAULT_SS_MODULE_MIN 0
+#define SCTPCTL_DEFAULT_SS_MODULE_MAX 5
+#define SCTPCTL_DEFAULT_SS_MODULE_DEFAULT 0
+
+/* RRS - default fragment interleave */
+#define SCTPCTL_DEFAULT_FRAG_INTERLEAVE_DESC "Default fragment interleave level"
+#define SCTPCTL_DEFAULT_FRAG_INTERLEAVE_MIN 0
+#define SCTPCTL_DEFAULT_FRAG_INTERLEAVE_MAX 2
+#define SCTPCTL_DEFAULT_FRAG_INTERLEAVE_DEFAULT 1
+
+/* mobility_base: Enable SCTP mobility support */
+#define SCTPCTL_MOBILITY_BASE_DESC "Enable SCTP base mobility"
+#define SCTPCTL_MOBILITY_BASE_MIN 0
+#define SCTPCTL_MOBILITY_BASE_MAX 1
+#define SCTPCTL_MOBILITY_BASE_DEFAULT 0
+
+/* mobility_fasthandoff: Enable SCTP fast handoff support */
+#define SCTPCTL_MOBILITY_FASTHANDOFF_DESC "Enable SCTP fast handoff"
+#define SCTPCTL_MOBILITY_FASTHANDOFF_MIN 0
+#define SCTPCTL_MOBILITY_FASTHANDOFF_MAX 1
+#define SCTPCTL_MOBILITY_FASTHANDOFF_DEFAULT 0
+
+/* Enable SCTP/UDP tunneling port */
+#define SCTPCTL_UDP_TUNNELING_PORT_DESC "Set the SCTP/UDP tunneling port"
+#define SCTPCTL_UDP_TUNNELING_PORT_MIN 0
+#define SCTPCTL_UDP_TUNNELING_PORT_MAX 65535
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#define SCTPCTL_UDP_TUNNELING_PORT_DEFAULT 0
+#else
+#define SCTPCTL_UDP_TUNNELING_PORT_DEFAULT SCTP_OVER_UDP_TUNNELING_PORT
+#endif
+
+/* Enable sending of the SACK-IMMEDIATELY bit */
+#define SCTPCTL_SACK_IMMEDIATELY_ENABLE_DESC "Enable sending of the SACK-IMMEDIATELY-bit"
+#define SCTPCTL_SACK_IMMEDIATELY_ENABLE_MIN 0
+#define SCTPCTL_SACK_IMMEDIATELY_ENABLE_MAX 1
+#define SCTPCTL_SACK_IMMEDIATELY_ENABLE_DEFAULT SCTPCTL_SACK_IMMEDIATELY_ENABLE_MAX
+
+/* Enable sending of the NAT-FRIENDLY message */
+#define SCTPCTL_NAT_FRIENDLY_INITS_DESC "Enable sending of the nat-friendly SCTP option on INITs"
+#define SCTPCTL_NAT_FRIENDLY_INITS_MIN 0
+#define SCTPCTL_NAT_FRIENDLY_INITS_MAX 1
+#define SCTPCTL_NAT_FRIENDLY_INITS_DEFAULT SCTPCTL_NAT_FRIENDLY_INITS_MIN
+
+/* Vtag time wait in seconds */
+#define SCTPCTL_TIME_WAIT_DESC "Vtag time wait time in seconds, 0 disables it"
+#define SCTPCTL_TIME_WAIT_MIN 0
+#define SCTPCTL_TIME_WAIT_MAX 0xffffffff
+#define SCTPCTL_TIME_WAIT_DEFAULT SCTP_TIME_WAIT
+
+/* Enable Send/Receive buffer splitting */
+#define SCTPCTL_BUFFER_SPLITTING_DESC "Enable send/receive buffer splitting"
+#define SCTPCTL_BUFFER_SPLITTING_MIN 0
+#define SCTPCTL_BUFFER_SPLITTING_MAX 0x3
+#define SCTPCTL_BUFFER_SPLITTING_DEFAULT SCTPCTL_BUFFER_SPLITTING_MIN
+
+/* Initial congestion window in MTUs */
+#define SCTPCTL_INITIAL_CWND_DESC "Defines the initial congestion window size in MTUs"
+#define SCTPCTL_INITIAL_CWND_MIN 0
+#define SCTPCTL_INITIAL_CWND_MAX 0xffffffff
+#define SCTPCTL_INITIAL_CWND_DEFAULT 3
+
+/* rttvar smooth avg for bw calc */
+#define SCTPCTL_RTTVAR_BW_DESC "Shift amount DCCC uses for bw smoothing on rtt calc"
+#define SCTPCTL_RTTVAR_BW_MIN 0
+#define SCTPCTL_RTTVAR_BW_MAX 32
+#define SCTPCTL_RTTVAR_BW_DEFAULT 4
+
+/* rttvar smooth avg for bw calc */
+#define SCTPCTL_RTTVAR_RTT_DESC "Shift amount DCCC uses for rtt smoothing on rtt calc"
+#define SCTPCTL_RTTVAR_RTT_MIN 0
+#define SCTPCTL_RTTVAR_RTT_MAX 32
+#define SCTPCTL_RTTVAR_RTT_DEFAULT 5
+
+#define SCTPCTL_RTTVAR_EQRET_DESC "Whether DCCC increases cwnd when the rtt and bw are unchanged"
+#define SCTPCTL_RTTVAR_EQRET_MIN 0
+#define SCTPCTL_RTTVAR_EQRET_MAX 1
+#define SCTPCTL_RTTVAR_EQRET_DEFAULT 0
+
+#define SCTPCTL_RTTVAR_STEADYS_DESC "Number of identical bw measurements DCCC takes to try step down of cwnd"
+#define SCTPCTL_RTTVAR_STEADYS_MIN 0
+#define SCTPCTL_RTTVAR_STEADYS_MAX 0xFFFF
+#define SCTPCTL_RTTVAR_STEADYS_DEFAULT 20 /* 0 means disable feature */
+
+#define SCTPCTL_RTTVAR_DCCCECN_DESC "Enable ECN for DCCC."
+#define SCTPCTL_RTTVAR_DCCCECN_MIN 0
+#define SCTPCTL_RTTVAR_DCCCECN_MAX 1
+#define SCTPCTL_RTTVAR_DCCCECN_DEFAULT 1 /* 0 means disable feature */
+
+#define SCTPCTL_BLACKHOLE_DESC "Enable SCTP blackholing, see blackhole(4) for more details"
+#define SCTPCTL_BLACKHOLE_MIN 0
+#define SCTPCTL_BLACKHOLE_MAX 2
+#define SCTPCTL_BLACKHOLE_DEFAULT SCTPCTL_BLACKHOLE_MIN
+
+/* sendall_limit: Maximum message with SCTP_SENDALL */
+#define SCTPCTL_SENDALL_LIMIT_DESC "Maximum size of a message send with SCTP_SENDALL"
+#define SCTPCTL_SENDALL_LIMIT_MIN 0
+#define SCTPCTL_SENDALL_LIMIT_MAX 0xFFFFFFFF
+#define SCTPCTL_SENDALL_LIMIT_DEFAULT 1432
+
+#define SCTPCTL_DIAG_INFO_CODE_DESC "Diagnostic information error cause code"
+#define SCTPCTL_DIAG_INFO_CODE_MIN 0
+#define SCTPCTL_DIAG_INFO_CODE_MAX 65535
+#define SCTPCTL_DIAG_INFO_CODE_DEFAULT 0
+
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_DESC "Accept OOTB packets with zero checksum"
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_MIN 0
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_MAX 1
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_DEFAULT 0
+
+#if defined(SCTP_DEBUG)
+/* debug: Configure debug output */
+#define SCTPCTL_DEBUG_DESC "Configure debug output"
+#define SCTPCTL_DEBUG_MIN 0
+#define SCTPCTL_DEBUG_MAX 0xFFFFFFFF
+#define SCTPCTL_DEBUG_DEFAULT 0
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#define SCTPCTL_MAIN_TIMER_DESC "Main timer interval in ms"
+#define SCTPCTL_MAIN_TIMER_MIN 1
+#define SCTPCTL_MAIN_TIMER_MAX 0xFFFFFFFF
+#define SCTPCTL_MAIN_TIMER_DEFAULT 10
+
+#define SCTPCTL_IGNORE_VMWARE_INTERFACES_DESC "Ignore VMware Interfaces"
+#define SCTPCTL_IGNORE_VMWARE_INTERFACES_MIN 0
+#define SCTPCTL_IGNORE_VMWARE_INTERFACES_MAX 1
+#define SCTPCTL_IGNORE_VMWARE_INTERFACES_DEFAULT SCTPCTL_IGNORE_VMWARE_INTERFACES_MAX
+
+#define SCTPCTL_OUTPUT_UNLOCKED_DESC "Unlock socket when sending packets down to IP"
+#define SCTPCTL_OUTPUT_UNLOCKED_MIN 0
+#define SCTPCTL_OUTPUT_UNLOCKED_MAX 1
+#define SCTPCTL_OUTPUT_UNLOCKED_DEFAULT SCTPCTL_OUTPUT_UNLOCKED_MIN
+
+#define SCTPCTL_ADDR_WATCHDOG_LIMIT_DESC "Address watchdog limit"
+#define SCTPCTL_ADDR_WATCHDOG_LIMIT_MIN 0
+#define SCTPCTL_ADDR_WATCHDOG_LIMIT_MAX 0xFFFFFFFF
+#define SCTPCTL_ADDR_WATCHDOG_LIMIT_DEFAULT SCTPCTL_ADDR_WATCHDOG_LIMIT_MIN
+
+#define SCTPCTL_VTAG_WATCHDOG_LIMIT_DESC "VTag watchdog limit"
+#define SCTPCTL_VTAG_WATCHDOG_LIMIT_MIN 0
+#define SCTPCTL_VTAG_WATCHDOG_LIMIT_MAX 0xFFFFFFFF
+#define SCTPCTL_VTAG_WATCHDOG_LIMIT_DEFAULT SCTPCTL_VTAG_WATCHDOG_LIMIT_MIN
+
+#endif
+#if defined(_KERNEL) || defined(__Userspace__)
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+#if defined(SYSCTL_DECL)
+SYSCTL_DECL(_net_inet_sctp);
+#endif
+#endif
+
+void sctp_init_sysctls(void);
+#if defined(_WIN32) && !defined(__Userspace__)
+void sctp_finish_sysctls(void);
+#endif
+
+#endif /* _KERNEL */
+#endif /* __sctp_sysctl_h__ */
diff --git a/netwerk/sctp/src/netinet/sctp_timer.c b/netwerk/sctp/src/netinet/sctp_timer.c
new file mode 100644
index 0000000000..497f2f0bdb
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_timer.c
@@ -0,0 +1,1634 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _IP_VHL
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_pcb.h>
+#ifdef INET6
+#if defined(__FreeBSD__) && defined(__Userspace__)
+#include <netinet6/sctp6_var.h>
+#endif
+#endif
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_uio.h>
+#if defined(INET) || defined(INET6)
+#if !(defined(_WIN32) && defined(__Userspace__))
+#include <netinet/udp.h>
+#endif
+#endif
+
+void
+sctp_audit_retranmission_queue(struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk;
+
+ SCTPDBG(SCTP_DEBUG_TIMER4, "Audit invoked on send queue cnt:%d onqueue:%d\n",
+ asoc->sent_queue_retran_cnt,
+ asoc->sent_queue_cnt);
+ asoc->sent_queue_retran_cnt = 0;
+ asoc->sent_queue_cnt = 0;
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ if (chk->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_incr(asoc->sent_queue_retran_cnt);
+ }
+ asoc->sent_queue_cnt++;
+ }
+ TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) {
+ if (chk->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_incr(asoc->sent_queue_retran_cnt);
+ }
+ }
+ TAILQ_FOREACH(chk, &asoc->asconf_send_queue, sctp_next) {
+ if (chk->sent == SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_incr(asoc->sent_queue_retran_cnt);
+ }
+ }
+ SCTPDBG(SCTP_DEBUG_TIMER4, "Audit completes retran:%d onqueue:%d\n",
+ asoc->sent_queue_retran_cnt,
+ asoc->sent_queue_cnt);
+}
+
+static int
+sctp_threshold_management(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net, uint16_t threshold)
+{
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if (net != NULL) {
+ net->error_count++;
+ SCTPDBG(SCTP_DEBUG_TIMER4, "Error count for %p now %d thresh:%d\n",
+ (void *)net, net->error_count,
+ net->failure_threshold);
+ if (net->error_count > net->failure_threshold) {
+ /* We had a threshold failure */
+ if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ net->dest_state &= ~SCTP_ADDR_REQ_PRIMARY;
+ net->dest_state &= ~SCTP_ADDR_PF;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN,
+ stcb, 0,
+ (void *)net, SCTP_SO_NOT_LOCKED);
+ }
+ } else if ((net->pf_threshold < net->failure_threshold) &&
+ (net->error_count > net->pf_threshold)) {
+ if ((net->dest_state & SCTP_ADDR_PF) == 0) {
+ net->dest_state |= SCTP_ADDR_PF;
+ net->last_active = sctp_get_tick_count();
+ sctp_send_hb(stcb, net, SCTP_SO_NOT_LOCKED);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ inp, stcb, net,
+ SCTP_FROM_SCTP_TIMER + SCTP_LOC_1);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net);
+ }
+ }
+ if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_INCR,
+ stcb->asoc.overall_error_count,
+ (stcb->asoc.overall_error_count+1),
+ SCTP_FROM_SCTP_TIMER,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count++;
+ }
+ } else {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_INCR,
+ stcb->asoc.overall_error_count,
+ (stcb->asoc.overall_error_count+1),
+ SCTP_FROM_SCTP_TIMER,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count++;
+ }
+ SCTPDBG(SCTP_DEBUG_TIMER4, "Overall error count for %p now %d thresh:%u state:%x\n",
+ (void *)&stcb->asoc, stcb->asoc.overall_error_count,
+ (uint32_t)threshold,
+ ((net == NULL) ? (uint32_t) 0 : (uint32_t) net->dest_state));
+ /*
+ * We specifically do not do >= to give the assoc one more change
+ * before we fail it.
+ */
+ if (stcb->asoc.overall_error_count > threshold) {
+ /* Abort notification sends a ULP notify */
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Association error counter exceeded");
+ inp->last_abort_code = SCTP_FROM_SCTP_TIMER + SCTP_LOC_2;
+ sctp_abort_an_association(inp, stcb, op_err, true, SCTP_SO_NOT_LOCKED);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * sctp_find_alternate_net() returns a non-NULL pointer as long as there
+ * exists nets, which are not being deleted.
+ */
+struct sctp_nets *
+sctp_find_alternate_net(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ int mode)
+{
+ /* Find and return an alternate network if possible */
+ struct sctp_nets *alt, *mnet, *min_errors_net = NULL , *max_cwnd_net = NULL;
+ bool looped;
+ /* JRS 5/14/07 - Initialize min_errors to an impossible value. */
+ int min_errors = -1;
+ uint32_t max_cwnd = 0;
+
+ if (stcb->asoc.numnets == 1) {
+ /* No selection can be made. */
+ return (TAILQ_FIRST(&stcb->asoc.nets));
+ }
+ /*
+ * JRS 5/14/07 - If mode is set to 2, use the CMT PF find alternate net algorithm.
+ * This algorithm chooses the active destination (not in PF state) with the largest
+ * cwnd value. If all destinations are in PF state, unreachable, or unconfirmed, choose
+ * the destination that is in PF state with the lowest error count. In case of a tie,
+ * choose the destination that was most recently active.
+ */
+ if (mode == 2) {
+ TAILQ_FOREACH(mnet, &stcb->asoc.nets, sctp_next) {
+ /* JRS 5/14/07 - If the destination is unreachable or unconfirmed, skip it. */
+ if (((mnet->dest_state & SCTP_ADDR_REACHABLE) != SCTP_ADDR_REACHABLE) ||
+ (mnet->dest_state & SCTP_ADDR_UNCONFIRMED)) {
+ continue;
+ }
+ /*
+ * JRS 5/14/07 - If the destination is reachable but in PF state, compare
+ * the error count of the destination to the minimum error count seen thus far.
+ * Store the destination with the lower error count. If the error counts are
+ * equal, store the destination that was most recently active.
+ */
+ if (mnet->dest_state & SCTP_ADDR_PF) {
+ /*
+ * JRS 5/14/07 - If the destination under consideration is the current
+ * destination, work as if the error count is one higher. The
+ * actual error count will not be incremented until later in the
+ * t3 handler.
+ */
+ if (mnet == net) {
+ if (min_errors == -1) {
+ min_errors = mnet->error_count + 1;
+ min_errors_net = mnet;
+ } else if (mnet->error_count + 1 < min_errors) {
+ min_errors = mnet->error_count + 1;
+ min_errors_net = mnet;
+ } else if (mnet->error_count + 1 == min_errors
+ && mnet->last_active > min_errors_net->last_active) {
+ min_errors_net = mnet;
+ min_errors = mnet->error_count + 1;
+ }
+ continue;
+ } else {
+ if (min_errors == -1) {
+ min_errors = mnet->error_count;
+ min_errors_net = mnet;
+ } else if (mnet->error_count < min_errors) {
+ min_errors = mnet->error_count;
+ min_errors_net = mnet;
+ } else if (mnet->error_count == min_errors
+ && mnet->last_active > min_errors_net->last_active) {
+ min_errors_net = mnet;
+ min_errors = mnet->error_count;
+ }
+ continue;
+ }
+ }
+ /*
+ * JRS 5/14/07 - If the destination is reachable and not in PF state, compare the
+ * cwnd of the destination to the highest cwnd seen thus far. Store the
+ * destination with the higher cwnd value. If the cwnd values are equal,
+ * randomly choose one of the two destinations.
+ */
+ if (max_cwnd < mnet->cwnd) {
+ max_cwnd_net = mnet;
+ max_cwnd = mnet->cwnd;
+ } else if (max_cwnd == mnet->cwnd) {
+ uint32_t rndval;
+ uint8_t this_random;
+
+ if (stcb->asoc.hb_random_idx > 3) {
+ rndval = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep);
+ memcpy(stcb->asoc.hb_random_values, &rndval, sizeof(stcb->asoc.hb_random_values));
+ this_random = stcb->asoc.hb_random_values[0];
+ stcb->asoc.hb_random_idx++;
+ stcb->asoc.hb_ect_randombit = 0;
+ } else {
+ this_random = stcb->asoc.hb_random_values[stcb->asoc.hb_random_idx];
+ stcb->asoc.hb_random_idx++;
+ stcb->asoc.hb_ect_randombit = 0;
+ }
+ if (this_random % 2 == 1) {
+ max_cwnd_net = mnet;
+ max_cwnd = mnet->cwnd; /* Useless? */
+ }
+ }
+ }
+ if (max_cwnd_net == NULL) {
+ if (min_errors_net == NULL) {
+ return (net);
+ }
+ return (min_errors_net);
+ } else {
+ return (max_cwnd_net);
+ }
+ } /* JRS 5/14/07 - If mode is set to 1, use the CMT policy for choosing an alternate net. */
+ else if (mode == 1) {
+ TAILQ_FOREACH(mnet, &stcb->asoc.nets, sctp_next) {
+ if (((mnet->dest_state & SCTP_ADDR_REACHABLE) != SCTP_ADDR_REACHABLE) ||
+ (mnet->dest_state & SCTP_ADDR_UNCONFIRMED)) {
+ /*
+ * will skip ones that are not-reachable or
+ * unconfirmed
+ */
+ continue;
+ }
+ if (max_cwnd < mnet->cwnd) {
+ max_cwnd_net = mnet;
+ max_cwnd = mnet->cwnd;
+ } else if (max_cwnd == mnet->cwnd) {
+ uint32_t rndval;
+ uint8_t this_random;
+
+ if (stcb->asoc.hb_random_idx > 3) {
+ rndval = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep);
+ memcpy(stcb->asoc.hb_random_values, &rndval,
+ sizeof(stcb->asoc.hb_random_values));
+ this_random = stcb->asoc.hb_random_values[0];
+ stcb->asoc.hb_random_idx = 0;
+ stcb->asoc.hb_ect_randombit = 0;
+ } else {
+ this_random = stcb->asoc.hb_random_values[stcb->asoc.hb_random_idx];
+ stcb->asoc.hb_random_idx++;
+ stcb->asoc.hb_ect_randombit = 0;
+ }
+ if (this_random % 2) {
+ max_cwnd_net = mnet;
+ max_cwnd = mnet->cwnd;
+ }
+ }
+ }
+ if (max_cwnd_net) {
+ return (max_cwnd_net);
+ }
+ }
+ /* Look for an alternate net, which is active. */
+ if ((net != NULL) && ((net->dest_state & SCTP_ADDR_BEING_DELETED) == 0)) {
+ alt = TAILQ_NEXT(net, sctp_next);
+ } else {
+ alt = TAILQ_FIRST(&stcb->asoc.nets);
+ }
+ looped = false;
+ for (;;) {
+ if (alt == NULL) {
+ if (!looped) {
+ alt = TAILQ_FIRST(&stcb->asoc.nets);
+ looped = true;
+ }
+ /* Definitely out of candidates. */
+ if (alt == NULL) {
+ break;
+ }
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (alt->ro.ro_nh == NULL) {
+#else
+ if (alt->ro.ro_rt == NULL) {
+#endif
+ if (alt->ro._s_addr) {
+ sctp_free_ifa(alt->ro._s_addr);
+ alt->ro._s_addr = NULL;
+ }
+ alt->src_addr_selected = 0;
+ }
+ if (((alt->dest_state & SCTP_ADDR_REACHABLE) == SCTP_ADDR_REACHABLE) &&
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ (alt->ro.ro_nh != NULL) &&
+#else
+ (alt->ro.ro_rt != NULL) &&
+#endif
+ ((alt->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) &&
+ (alt != net)) {
+ /* Found an alternate net, which is reachable. */
+ break;
+ }
+ alt = TAILQ_NEXT(alt, sctp_next);
+ }
+
+ if (alt == NULL) {
+ /*
+ * In case no active alternate net has been found, look for
+ * an alternate net, which is confirmed.
+ */
+ if ((net != NULL) && ((net->dest_state & SCTP_ADDR_BEING_DELETED) == 0)) {
+ alt = TAILQ_NEXT(net, sctp_next);
+ } else {
+ alt = TAILQ_FIRST(&stcb->asoc.nets);
+ }
+ looped = false;
+ for (;;) {
+ if (alt == NULL) {
+ if (!looped) {
+ alt = TAILQ_FIRST(&stcb->asoc.nets);
+ looped = true;
+ }
+ /* Definitely out of candidates. */
+ if (alt == NULL) {
+ break;
+ }
+ }
+ if (((alt->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) &&
+ (alt != net)) {
+ /* Found an alternate net, which is confirmed. */
+ break;
+ }
+ alt = TAILQ_NEXT(alt, sctp_next);
+ }
+ }
+ if (alt == NULL) {
+ /*
+ * In case no confirmed alternate net has been found, just
+ * return net, if it is not being deleted. In the other case
+ * just return the first net.
+ */
+ if ((net != NULL) && ((net->dest_state & SCTP_ADDR_BEING_DELETED) == 0)) {
+ alt = net;
+ }
+ if (alt == NULL) {
+ alt = TAILQ_FIRST(&stcb->asoc.nets);
+ }
+ }
+ return (alt);
+}
+
+static void
+sctp_backoff_on_timeout(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ int win_probe,
+ int num_marked, int num_abandoned)
+{
+ if (net->RTO == 0) {
+ if (net->RTO_measured) {
+ net->RTO = stcb->asoc.minrto;
+ } else {
+ net->RTO = stcb->asoc.initial_rto;
+ }
+ }
+ net->RTO <<= 1;
+ if (net->RTO > stcb->asoc.maxrto) {
+ net->RTO = stcb->asoc.maxrto;
+ }
+ if ((win_probe == 0) && (num_marked || num_abandoned)) {
+ /* We don't apply penalty to window probe scenarios */
+ /* JRS - Use the congestion control given in the CC module */
+ stcb->asoc.cc_functions.sctp_cwnd_update_after_timeout(stcb, net);
+ }
+}
+
+#ifndef INVARIANTS
+static void
+sctp_recover_sent_list(struct sctp_tcb *stcb)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_association *asoc;
+
+ asoc = &stcb->asoc;
+ TAILQ_FOREACH_SAFE(chk, &asoc->sent_queue, sctp_next, nchk) {
+ if (SCTP_TSN_GE(asoc->last_acked_seq, chk->rec.data.tsn)) {
+ SCTP_PRINTF("Found chk:%p tsn:%x <= last_acked_seq:%x\n",
+ (void *)chk, chk->rec.data.tsn, asoc->last_acked_seq);
+ if (chk->sent != SCTP_DATAGRAM_NR_ACKED) {
+ if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.sid].chunks_on_queues--;
+ }
+ }
+ if ((asoc->strmout[chk->rec.data.sid].chunks_on_queues == 0) &&
+ (asoc->strmout[chk->rec.data.sid].state == SCTP_STREAM_RESET_PENDING) &&
+ TAILQ_EMPTY(&asoc->strmout[chk->rec.data.sid].outqueue)) {
+ asoc->trigger_reset = 1;
+ }
+ TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next);
+ if (PR_SCTP_ENABLED(chk->flags)) {
+ if (asoc->pr_sctp_cnt != 0)
+ asoc->pr_sctp_cnt--;
+ }
+ if (chk->data) {
+ /*sa_ignore NO_NULL_CHK*/
+ sctp_free_bufspace(stcb, asoc, chk, 1);
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ if (asoc->prsctp_supported && PR_SCTP_BUF_ENABLED(chk->flags)) {
+ asoc->sent_queue_cnt_removeable--;
+ }
+ }
+ asoc->sent_queue_cnt--;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ SCTP_PRINTF("after recover order is as follows\n");
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ SCTP_PRINTF("chk:%p TSN:%x\n", (void *)chk, chk->rec.data.tsn);
+ }
+}
+#endif
+
+static int
+sctp_mark_all_for_resend(struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ struct sctp_nets *alt,
+ int window_probe,
+ int *num_marked,
+ int *num_abandoned)
+{
+
+ /*
+ * Mark all chunks (well not all) that were sent to *net for
+ * retransmission. Move them to alt for there destination as well...
+ * We only mark chunks that have been outstanding long enough to
+ * have received feed-back.
+ */
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_nets *lnets;
+ struct timeval now, min_wait, tv;
+ int cur_rto;
+ int cnt_abandoned;
+ int audit_tf, num_mk, fir;
+ unsigned int cnt_mk;
+ uint32_t orig_flight, orig_tf;
+ uint32_t tsnlast, tsnfirst;
+#ifndef INVARIANTS
+ int recovery_cnt = 0;
+#endif
+
+ /* none in flight now */
+ audit_tf = 0;
+ fir = 0;
+ /*
+ * figure out how long a data chunk must be pending before we can
+ * mark it ..
+ */
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ /* get cur rto in micro-seconds */
+ cur_rto = (net->lastsa >> SCTP_RTT_SHIFT) + net->lastsv;
+ cur_rto *= 1000;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(cur_rto,
+ stcb->asoc.peers_rwnd,
+ window_probe,
+ SCTP_FR_T3_MARK_TIME);
+ sctp_log_fr(net->flight_size, 0, 0, SCTP_FR_CWND_REPORT);
+ sctp_log_fr(net->flight_size, net->cwnd, stcb->asoc.total_flight, SCTP_FR_CWND_REPORT);
+ }
+ tv.tv_sec = cur_rto / 1000000;
+ tv.tv_usec = cur_rto % 1000000;
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ timersub(&now, &tv, &min_wait);
+#else
+ min_wait = now;
+ timevalsub(&min_wait, &tv);
+#endif
+ if (min_wait.tv_sec < 0 || min_wait.tv_usec < 0) {
+ /*
+ * if we hit here, we don't have enough seconds on the clock
+ * to account for the RTO. We just let the lower seconds be
+ * the bounds and don't worry about it. This may mean we
+ * will mark a lot more than we should.
+ */
+ min_wait.tv_sec = min_wait.tv_usec = 0;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(cur_rto, (uint32_t)now.tv_sec, now.tv_usec, SCTP_FR_T3_MARK_TIME);
+ sctp_log_fr(0, (uint32_t)min_wait.tv_sec, min_wait.tv_usec, SCTP_FR_T3_MARK_TIME);
+ }
+ /*
+ * Our rwnd will be incorrect here since we are not adding back the
+ * cnt * mbuf but we will fix that down below.
+ */
+ orig_flight = net->flight_size;
+ orig_tf = stcb->asoc.total_flight;
+
+ net->fast_retran_ip = 0;
+ /* Now on to each chunk */
+ cnt_abandoned = 0;
+ num_mk = cnt_mk = 0;
+ tsnfirst = tsnlast = 0;
+#ifndef INVARIANTS
+ start_again:
+#endif
+ TAILQ_FOREACH_SAFE(chk, &stcb->asoc.sent_queue, sctp_next, nchk) {
+ if (SCTP_TSN_GE(stcb->asoc.last_acked_seq, chk->rec.data.tsn)) {
+ /* Strange case our list got out of order? */
+ SCTP_PRINTF("Our list is out of order? last_acked:%x chk:%x\n",
+ (unsigned int)stcb->asoc.last_acked_seq, (unsigned int)chk->rec.data.tsn);
+#ifdef INVARIANTS
+ panic("last acked >= chk on sent-Q");
+#else
+ recovery_cnt++;
+ SCTP_PRINTF("Recover attempts a restart cnt:%d\n", recovery_cnt);
+ sctp_recover_sent_list(stcb);
+ if (recovery_cnt < 10) {
+ goto start_again;
+ } else {
+ SCTP_PRINTF("Recovery fails %d times??\n", recovery_cnt);
+ }
+#endif
+ }
+ if ((chk->whoTo == net) && (chk->sent < SCTP_DATAGRAM_ACKED)) {
+ /*
+ * found one to mark: If it is less than
+ * DATAGRAM_ACKED it MUST not be a skipped or marked
+ * TSN but instead one that is either already set
+ * for retransmission OR one that needs
+ * retransmission.
+ */
+
+ /* validate its been outstanding long enough */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(chk->rec.data.tsn,
+ (uint32_t)chk->sent_rcv_time.tv_sec,
+ chk->sent_rcv_time.tv_usec,
+ SCTP_FR_T3_MARK_TIME);
+ }
+ if ((chk->sent_rcv_time.tv_sec > min_wait.tv_sec) && (window_probe == 0)) {
+ /*
+ * we have reached a chunk that was sent
+ * some seconds past our min.. forget it we
+ * will find no more to send.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(0,
+ (uint32_t)chk->sent_rcv_time.tv_sec,
+ chk->sent_rcv_time.tv_usec,
+ SCTP_FR_T3_STOPPED);
+ }
+ continue;
+ } else if ((chk->sent_rcv_time.tv_sec == min_wait.tv_sec) &&
+ (window_probe == 0)) {
+ /*
+ * we must look at the micro seconds to
+ * know.
+ */
+ if (chk->sent_rcv_time.tv_usec >= min_wait.tv_usec) {
+ /*
+ * ok it was sent after our boundary
+ * time.
+ */
+ continue;
+ }
+ }
+ if (stcb->asoc.prsctp_supported && PR_SCTP_TTL_ENABLED(chk->flags)) {
+ /* Is it expired? */
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (timercmp(&now, &chk->rec.data.timetodrop, >)) {
+#else
+ if (timevalcmp(&now, &chk->rec.data.timetodrop, >)) {
+#endif
+ /* Yes so drop it */
+ if (chk->data) {
+ (void)sctp_release_pr_sctp_chunk(stcb,
+ chk,
+ 1,
+ SCTP_SO_NOT_LOCKED);
+ cnt_abandoned++;
+ }
+ continue;
+ }
+ }
+ if (stcb->asoc.prsctp_supported && PR_SCTP_RTX_ENABLED(chk->flags)) {
+ /* Has it been retransmitted tv_sec times? */
+ if (chk->snd_count > chk->rec.data.timetodrop.tv_sec) {
+ if (chk->data) {
+ (void)sctp_release_pr_sctp_chunk(stcb,
+ chk,
+ 1,
+ SCTP_SO_NOT_LOCKED);
+ cnt_abandoned++;
+ }
+ continue;
+ }
+ }
+ if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ num_mk++;
+ if (fir == 0) {
+ fir = 1;
+ tsnfirst = chk->rec.data.tsn;
+ }
+ tsnlast = chk->rec.data.tsn;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(chk->rec.data.tsn, chk->snd_count,
+ 0, SCTP_FR_T3_MARKED);
+ }
+
+ if (chk->rec.data.chunk_was_revoked) {
+ /* deflate the cwnd */
+ chk->whoTo->cwnd -= chk->book_size;
+ chk->rec.data.chunk_was_revoked = 0;
+ }
+ net->marked_retrans++;
+ stcb->asoc.marked_retrans++;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_RSND_TO,
+ chk->whoTo->flight_size,
+ chk->book_size,
+ (uint32_t)(uintptr_t)chk->whoTo,
+ chk->rec.data.tsn);
+ }
+ sctp_flight_size_decrease(chk);
+ sctp_total_flight_decrease(stcb, chk);
+ stcb->asoc.peers_rwnd += chk->send_size;
+ stcb->asoc.peers_rwnd += SCTP_BASE_SYSCTL(sctp_peer_chunk_oh);
+ }
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ SCTP_STAT_INCR(sctps_markedretrans);
+
+ /* reset the TSN for striking and other FR stuff */
+ chk->rec.data.doing_fast_retransmit = 0;
+ /* Clear any time so NO RTT is being done */
+
+ if (chk->do_rtt) {
+ if (chk->whoTo->rto_needed == 0) {
+ chk->whoTo->rto_needed = 1;
+ }
+ }
+ chk->do_rtt = 0;
+ if (alt != net) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->no_fr_allowed = 1;
+ chk->whoTo = alt;
+ atomic_add_int(&alt->ref_count, 1);
+ } else {
+ chk->no_fr_allowed = 0;
+ if (TAILQ_EMPTY(&stcb->asoc.send_queue)) {
+ chk->rec.data.fast_retran_tsn = stcb->asoc.sending_seq;
+ } else {
+ chk->rec.data.fast_retran_tsn = (TAILQ_FIRST(&stcb->asoc.send_queue))->rec.data.tsn;
+ }
+ }
+ /* CMT: Do not allow FRs on retransmitted TSNs.
+ */
+ if (stcb->asoc.sctp_cmt_on_off > 0) {
+ chk->no_fr_allowed = 1;
+ }
+#ifdef THIS_SHOULD_NOT_BE_DONE
+ } else if (chk->sent == SCTP_DATAGRAM_ACKED) {
+ /* remember highest acked one */
+ could_be_sent = chk;
+#endif
+ }
+ if (chk->sent == SCTP_DATAGRAM_RESEND) {
+ cnt_mk++;
+ }
+ }
+ if ((orig_flight - net->flight_size) != (orig_tf - stcb->asoc.total_flight)) {
+ /* we did not subtract the same things? */
+ audit_tf = 1;
+ }
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(tsnfirst, tsnlast, num_mk, SCTP_FR_T3_TIMEOUT);
+ }
+#ifdef SCTP_DEBUG
+ if (num_mk) {
+ SCTPDBG(SCTP_DEBUG_TIMER1, "LAST TSN marked was %x\n",
+ tsnlast);
+ SCTPDBG(SCTP_DEBUG_TIMER1, "Num marked for retransmission was %d peer-rwd:%u\n",
+ num_mk,
+ stcb->asoc.peers_rwnd);
+ }
+#endif
+ *num_marked = num_mk;
+ *num_abandoned = cnt_abandoned;
+ /* Now check for a ECN Echo that may be stranded And
+ * include the cnt_mk'd to have all resends in the
+ * control queue.
+ */
+ TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) {
+ if (chk->sent == SCTP_DATAGRAM_RESEND) {
+ cnt_mk++;
+ }
+ if ((chk->whoTo == net) &&
+ (chk->rec.chunk_id.id == SCTP_ECN_ECHO)) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->whoTo = alt;
+ if (chk->sent != SCTP_DATAGRAM_RESEND) {
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ cnt_mk++;
+ }
+ atomic_add_int(&alt->ref_count, 1);
+ }
+ }
+#ifdef THIS_SHOULD_NOT_BE_DONE
+ if ((stcb->asoc.sent_queue_retran_cnt == 0) && (could_be_sent)) {
+ /* fix it so we retransmit the highest acked anyway */
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ cnt_mk++;
+ could_be_sent->sent = SCTP_DATAGRAM_RESEND;
+ }
+#endif
+ if (stcb->asoc.sent_queue_retran_cnt != cnt_mk) {
+#ifdef INVARIANTS
+ SCTP_PRINTF("Local Audit says there are %d for retran asoc cnt:%d we marked:%d this time\n",
+ cnt_mk, stcb->asoc.sent_queue_retran_cnt, num_mk);
+#endif
+#ifndef SCTP_AUDITING_ENABLED
+ stcb->asoc.sent_queue_retran_cnt = cnt_mk;
+#endif
+ }
+ if (audit_tf) {
+ SCTPDBG(SCTP_DEBUG_TIMER4,
+ "Audit total flight due to negative value net:%p\n",
+ (void *)net);
+ stcb->asoc.total_flight = 0;
+ stcb->asoc.total_flight_count = 0;
+ /* Clear all networks flight size */
+ TAILQ_FOREACH(lnets, &stcb->asoc.nets, sctp_next) {
+ lnets->flight_size = 0;
+ SCTPDBG(SCTP_DEBUG_TIMER4,
+ "Net:%p c-f cwnd:%d ssthresh:%d\n",
+ (void *)lnets, lnets->cwnd, lnets->ssthresh);
+ }
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_UP,
+ chk->whoTo->flight_size,
+ chk->book_size,
+ (uint32_t)(uintptr_t)chk->whoTo,
+ chk->rec.data.tsn);
+ }
+
+ sctp_flight_size_increase(chk);
+ sctp_total_flight_increase(stcb, chk);
+ }
+ }
+ }
+ /* We return 1 if we only have a window probe outstanding */
+ return (0);
+}
+
+int
+sctp_t3rxt_timer(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ struct sctp_nets *alt;
+ int win_probe, num_mk, num_abandoned;
+
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(0, 0, 0, SCTP_FR_T3_TIMEOUT);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_LOGGING_ENABLE) {
+ struct sctp_nets *lnet;
+
+ TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) {
+ if (net == lnet) {
+ sctp_log_cwnd(stcb, lnet, 1, SCTP_CWND_LOG_FROM_T3);
+ } else {
+ sctp_log_cwnd(stcb, lnet, 0, SCTP_CWND_LOG_FROM_T3);
+ }
+ }
+ }
+ /* Find an alternate and mark those for retransmission */
+ if ((stcb->asoc.peers_rwnd == 0) &&
+ (stcb->asoc.total_flight < net->mtu)) {
+ SCTP_STAT_INCR(sctps_timowindowprobe);
+ win_probe = 1;
+ } else {
+ win_probe = 0;
+ }
+
+ if (win_probe == 0) {
+ /* We don't do normal threshold management on window probes */
+ if (sctp_threshold_management(inp, stcb, net,
+ stcb->asoc.max_send_times)) {
+ /* Association was destroyed */
+ return (1);
+ } else {
+ if (net != stcb->asoc.primary_destination) {
+ /* send a immediate HB if our RTO is stale */
+ struct timeval now;
+ uint32_t ms_goneby;
+
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ if (net->last_sent_time.tv_sec) {
+ ms_goneby = (uint32_t)(now.tv_sec - net->last_sent_time.tv_sec) * 1000;
+ } else {
+ ms_goneby = 0;
+ }
+ if ((net->dest_state & SCTP_ADDR_PF) == 0) {
+ if ((ms_goneby > net->RTO) || (net->RTO == 0)) {
+ /*
+ * no recent feed back in an RTO or
+ * more, request a RTT update
+ */
+ sctp_send_hb(stcb, net, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ }
+ }
+ } else {
+ /*
+ * For a window probe we don't penalize the net's but only
+ * the association. This may fail it if SACKs are not coming
+ * back. If sack's are coming with rwnd locked at 0, we will
+ * continue to hold things waiting for rwnd to raise
+ */
+ if (sctp_threshold_management(inp, stcb, NULL,
+ stcb->asoc.max_send_times)) {
+ /* Association was destroyed */
+ return (1);
+ }
+ }
+ if (stcb->asoc.sctp_cmt_on_off > 0) {
+ if (net->pf_threshold < net->failure_threshold) {
+ alt = sctp_find_alternate_net(stcb, net, 2);
+ } else {
+ /*
+ * CMT: Using RTX_SSTHRESH policy for CMT.
+ * If CMT is being used, then pick dest with
+ * largest ssthresh for any retransmission.
+ */
+ alt = sctp_find_alternate_net(stcb, net, 1);
+ /*
+ * CUCv2: If a different dest is picked for
+ * the retransmission, then new
+ * (rtx-)pseudo_cumack needs to be tracked
+ * for orig dest. Let CUCv2 track new (rtx-)
+ * pseudo-cumack always.
+ */
+ net->find_pseudo_cumack = 1;
+ net->find_rtx_pseudo_cumack = 1;
+ }
+ } else {
+ alt = sctp_find_alternate_net(stcb, net, 0);
+ }
+
+ num_mk = 0;
+ num_abandoned = 0;
+ (void)sctp_mark_all_for_resend(stcb, net, alt, win_probe,
+ &num_mk, &num_abandoned);
+ /* FR Loss recovery just ended with the T3. */
+ stcb->asoc.fast_retran_loss_recovery = 0;
+
+ /* CMT FR loss recovery ended with the T3 */
+ net->fast_retran_loss_recovery = 0;
+ if ((stcb->asoc.cc_functions.sctp_cwnd_new_transmission_begins) &&
+ (net->flight_size == 0)) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_new_transmission_begins)(stcb, net);
+ }
+
+ /*
+ * setup the sat loss recovery that prevents satellite cwnd advance.
+ */
+ stcb->asoc.sat_t3_loss_recovery = 1;
+ stcb->asoc.sat_t3_recovery_tsn = stcb->asoc.sending_seq;
+
+ /* Backoff the timer and cwnd */
+ sctp_backoff_on_timeout(stcb, net, win_probe, num_mk, num_abandoned);
+ if (((net->dest_state & SCTP_ADDR_REACHABLE) == 0) ||
+ (net->dest_state & SCTP_ADDR_PF)) {
+ /* Move all pending over too */
+ sctp_move_chunks_from_net(stcb, net);
+
+ /* Get the address that failed, to
+ * force a new src address selection and
+ * a route allocation.
+ */
+ if (net->ro._s_addr != NULL) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ }
+ net->src_addr_selected = 0;
+
+ /* Force a route allocation too */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ if (net->ro.ro_rt != NULL) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+
+ /* Was it our primary? */
+ if ((stcb->asoc.primary_destination == net) && (alt != net)) {
+ /*
+ * Yes, note it as such and find an alternate note:
+ * this means HB code must use this to resent the
+ * primary if it goes active AND if someone does a
+ * change-primary then this flag must be cleared
+ * from any net structures.
+ */
+ if (stcb->asoc.alternate != NULL) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ }
+ stcb->asoc.alternate = alt;
+ atomic_add_int(&stcb->asoc.alternate->ref_count, 1);
+ }
+ }
+ /*
+ * Special case for cookie-echo'ed case, we don't do output but must
+ * await the COOKIE-ACK before retransmission
+ */
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED) {
+ /*
+ * Here we just reset the timer and start again since we
+ * have not established the asoc
+ */
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net);
+ return (0);
+ }
+ if (stcb->asoc.prsctp_supported) {
+ struct sctp_tmit_chunk *lchk;
+
+ lchk = sctp_try_advance_peer_ack_point(stcb, &stcb->asoc);
+ /* C3. See if we need to send a Fwd-TSN */
+ if (SCTP_TSN_GT(stcb->asoc.advanced_peer_ack_point, stcb->asoc.last_acked_seq)) {
+ send_forward_tsn(stcb, &stcb->asoc);
+ for (; lchk != NULL; lchk = TAILQ_NEXT(lchk, sctp_next)) {
+ if (lchk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (lchk != NULL) {
+ /* Assure a timer is up */
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, lchk->whoTo);
+ }
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_CWND_MONITOR_ENABLE) {
+ sctp_log_cwnd(stcb, net, net->cwnd, SCTP_CWND_LOG_FROM_RTX);
+ }
+ return (0);
+}
+
+int
+sctp_t1init_timer(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ /* bump the thresholds */
+ if (stcb->asoc.delayed_connection) {
+ /*
+ * special hook for delayed connection. The library did NOT
+ * complete the rest of its sends.
+ */
+ stcb->asoc.delayed_connection = 0;
+ sctp_send_initiate(inp, stcb, SCTP_SO_NOT_LOCKED);
+ return (0);
+ }
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT) {
+ return (0);
+ }
+ if (sctp_threshold_management(inp, stcb, net,
+ stcb->asoc.max_init_times)) {
+ /* Association was destroyed */
+ return (1);
+ }
+ stcb->asoc.dropped_special_cnt = 0;
+ sctp_backoff_on_timeout(stcb, stcb->asoc.primary_destination, 1, 0, 0);
+ if (stcb->asoc.initial_init_rto_max < net->RTO) {
+ net->RTO = stcb->asoc.initial_init_rto_max;
+ }
+ if (stcb->asoc.numnets > 1) {
+ /* If we have more than one addr use it */
+ struct sctp_nets *alt;
+
+ alt = sctp_find_alternate_net(stcb, stcb->asoc.primary_destination, 0);
+ if (alt != stcb->asoc.primary_destination) {
+ sctp_move_chunks_from_net(stcb, stcb->asoc.primary_destination);
+ stcb->asoc.primary_destination = alt;
+ }
+ }
+ /* Send out a new init */
+ sctp_send_initiate(inp, stcb, SCTP_SO_NOT_LOCKED);
+ return (0);
+}
+
+/*
+ * For cookie and asconf we actually need to find and mark for resend, then
+ * increment the resend counter (after all the threshold management stuff of
+ * course).
+ */
+int
+sctp_cookie_timer(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net SCTP_UNUSED)
+{
+ struct sctp_nets *alt;
+ struct sctp_tmit_chunk *cookie;
+
+ /* first before all else we must find the cookie */
+ TAILQ_FOREACH(cookie, &stcb->asoc.control_send_queue, sctp_next) {
+ if (cookie->rec.chunk_id.id == SCTP_COOKIE_ECHO) {
+ break;
+ }
+ }
+ if (cookie == NULL) {
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED) {
+ /* FOOBAR! */
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Cookie timer expired, but no cookie");
+ inp->last_abort_code = SCTP_FROM_SCTP_TIMER + SCTP_LOC_3;
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ } else {
+#ifdef INVARIANTS
+ panic("Cookie timer expires in wrong state?");
+#else
+ SCTP_PRINTF("Strange in state %d not cookie-echoed yet c-e timer expires?\n", SCTP_GET_STATE(stcb));
+ return (0);
+#endif
+ }
+ return (0);
+ }
+ /* Ok we found the cookie, threshold management next */
+ if (sctp_threshold_management(inp, stcb, cookie->whoTo,
+ stcb->asoc.max_init_times)) {
+ /* Assoc is over */
+ return (1);
+ }
+ /*
+ * Cleared threshold management, now lets backoff the address
+ * and select an alternate
+ */
+ stcb->asoc.dropped_special_cnt = 0;
+ sctp_backoff_on_timeout(stcb, cookie->whoTo, 1, 0, 0);
+ alt = sctp_find_alternate_net(stcb, cookie->whoTo, 0);
+ if (alt != cookie->whoTo) {
+ sctp_free_remote_addr(cookie->whoTo);
+ cookie->whoTo = alt;
+ atomic_add_int(&alt->ref_count, 1);
+ }
+ /* Now mark the retran info */
+ if (cookie->sent != SCTP_DATAGRAM_RESEND) {
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ }
+ cookie->sent = SCTP_DATAGRAM_RESEND;
+ cookie->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ /*
+ * Now call the output routine to kick out the cookie again, Note we
+ * don't mark any chunks for retran so that FR will need to kick in
+ * to move these (or a send timer).
+ */
+ return (0);
+}
+
+int
+sctp_strreset_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb)
+{
+ struct sctp_nets *alt, *net;
+ struct sctp_tmit_chunk *strrst = NULL, *chk = NULL;
+
+ if (stcb->asoc.stream_reset_outstanding == 0) {
+ return (0);
+ }
+ /* find the existing STRRESET, we use the seq number we sent out on */
+ (void)sctp_find_stream_reset(stcb, stcb->asoc.str_reset_seq_out, &strrst);
+ if (strrst == NULL) {
+ return (0);
+ }
+ net = strrst->whoTo;
+ /* do threshold management */
+ if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) {
+ /* Assoc is over */
+ return (1);
+ }
+ /*
+ * Cleared threshold management, now lets backoff the address
+ * and select an alternate
+ */
+ sctp_backoff_on_timeout(stcb, net, 1, 0, 0);
+ alt = sctp_find_alternate_net(stcb, net, 0);
+ strrst->whoTo = alt;
+ atomic_add_int(&alt->ref_count, 1);
+
+ /* See if a ECN Echo is also stranded */
+ TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) {
+ if ((chk->whoTo == net) &&
+ (chk->rec.chunk_id.id == SCTP_ECN_ECHO)) {
+ sctp_free_remote_addr(chk->whoTo);
+ if (chk->sent != SCTP_DATAGRAM_RESEND) {
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ }
+ chk->whoTo = alt;
+ atomic_add_int(&alt->ref_count, 1);
+ }
+ }
+ if ((net->dest_state & SCTP_ADDR_REACHABLE) == 0) {
+ /*
+ * If the address went un-reachable, we need to move to
+ * alternates for ALL chk's in queue
+ */
+ sctp_move_chunks_from_net(stcb, net);
+ }
+ sctp_free_remote_addr(net);
+
+ /* mark the retran info */
+ if (strrst->sent != SCTP_DATAGRAM_RESEND)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ strrst->sent = SCTP_DATAGRAM_RESEND;
+ strrst->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+
+ /* restart the timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, inp, stcb, alt);
+ return (0);
+}
+
+int
+sctp_asconf_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ struct sctp_nets *alt;
+ struct sctp_tmit_chunk *asconf, *chk;
+
+ /* is this a first send, or a retransmission? */
+ if (TAILQ_EMPTY(&stcb->asoc.asconf_send_queue)) {
+ /* compose a new ASCONF chunk and send it */
+ sctp_send_asconf(stcb, net, SCTP_ADDR_NOT_LOCKED);
+ } else {
+ /*
+ * Retransmission of the existing ASCONF is needed
+ */
+
+ /* find the existing ASCONF */
+ asconf = TAILQ_FIRST(&stcb->asoc.asconf_send_queue);
+ if (asconf == NULL) {
+ return (0);
+ }
+ net = asconf->whoTo;
+ /* do threshold management */
+ if (sctp_threshold_management(inp, stcb, net,
+ stcb->asoc.max_send_times)) {
+ /* Assoc is over */
+ return (1);
+ }
+ if (asconf->snd_count > stcb->asoc.max_send_times) {
+ /*
+ * Something is rotten: our peer is not responding to
+ * ASCONFs but apparently is to other chunks. i.e. it
+ * is not properly handling the chunk type upper bits.
+ * Mark this peer as ASCONF incapable and cleanup.
+ */
+ SCTPDBG(SCTP_DEBUG_TIMER1, "asconf_timer: Peer has not responded to our repeated ASCONFs\n");
+ sctp_asconf_cleanup(stcb);
+ return (0);
+ }
+ /*
+ * cleared threshold management, so now backoff the net and
+ * select an alternate
+ */
+ sctp_backoff_on_timeout(stcb, net, 1, 0, 0);
+ alt = sctp_find_alternate_net(stcb, net, 0);
+ if (asconf->whoTo != alt) {
+ asconf->whoTo = alt;
+ atomic_add_int(&alt->ref_count, 1);
+ }
+
+ /* See if an ECN Echo is also stranded */
+ TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) {
+ if ((chk->whoTo == net) &&
+ (chk->rec.chunk_id.id == SCTP_ECN_ECHO)) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->whoTo = alt;
+ if (chk->sent != SCTP_DATAGRAM_RESEND) {
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ }
+ atomic_add_int(&alt->ref_count, 1);
+ }
+ }
+ TAILQ_FOREACH(chk, &stcb->asoc.asconf_send_queue, sctp_next) {
+ if (chk->whoTo != alt) {
+ sctp_free_remote_addr(chk->whoTo);
+ chk->whoTo = alt;
+ atomic_add_int(&alt->ref_count, 1);
+ }
+ if (asconf->sent != SCTP_DATAGRAM_RESEND && chk->sent != SCTP_DATAGRAM_UNSENT)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ }
+ if ((net->dest_state & SCTP_ADDR_REACHABLE) == 0) {
+ /*
+ * If the address went un-reachable, we need to move
+ * to the alternate for ALL chunks in queue
+ */
+ sctp_move_chunks_from_net(stcb, net);
+ }
+ sctp_free_remote_addr(net);
+
+ /* mark the retran info */
+ if (asconf->sent != SCTP_DATAGRAM_RESEND)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ asconf->sent = SCTP_DATAGRAM_RESEND;
+ asconf->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+
+ /* send another ASCONF if any and we can do */
+ sctp_send_asconf(stcb, alt, SCTP_ADDR_NOT_LOCKED);
+ }
+ return (0);
+}
+
+/* Mobility adaptation */
+void
+sctp_delete_prim_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb)
+{
+ if (stcb->asoc.deleted_primary == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "delete_prim_timer: deleted_primary is not stored...\n");
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
+ return;
+ }
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "delete_prim_timer: finished to keep deleted primary ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, &stcb->asoc.deleted_primary->ro._l_addr.sa);
+ sctp_free_remote_addr(stcb->asoc.deleted_primary);
+ stcb->asoc.deleted_primary = NULL;
+ sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
+ return;
+}
+
+/*
+ * For the shutdown and shutdown-ack, we do not keep one around on the
+ * control queue. This means we must generate a new one and call the general
+ * chunk output routine, AFTER having done threshold management.
+ * It is assumed that net is non-NULL.
+ */
+int
+sctp_shutdown_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ struct sctp_nets *alt;
+
+ /* first threshold management */
+ if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) {
+ /* Assoc is over */
+ return (1);
+ }
+ sctp_backoff_on_timeout(stcb, net, 1, 0, 0);
+ /* second select an alternative */
+ alt = sctp_find_alternate_net(stcb, net, 0);
+
+ /* third generate a shutdown into the queue for out net */
+ sctp_send_shutdown(stcb, alt);
+
+ /* fourth restart timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, inp, stcb, alt);
+ return (0);
+}
+
+int
+sctp_shutdownack_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ struct sctp_nets *alt;
+
+ /* first threshold management */
+ if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) {
+ /* Assoc is over */
+ return (1);
+ }
+ sctp_backoff_on_timeout(stcb, net, 1, 0, 0);
+ /* second select an alternative */
+ alt = sctp_find_alternate_net(stcb, net, 0);
+
+ /* third generate a shutdown into the queue for out net */
+ sctp_send_shutdown_ack(stcb, alt);
+
+ /* fourth restart timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, inp, stcb, alt);
+ return (0);
+}
+
+static void
+sctp_audit_stream_queues_for_size(struct sctp_inpcb *inp, struct sctp_tcb *stcb)
+{
+ struct sctp_stream_queue_pending *sp;
+ unsigned int i, chks_in_queue = 0;
+ int being_filled = 0;
+
+ KASSERT(inp != NULL, ("inp is NULL"));
+ KASSERT(stcb != NULL, ("stcb is NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ KASSERT(TAILQ_EMPTY(&stcb->asoc.send_queue), ("send_queue not empty"));
+ KASSERT(TAILQ_EMPTY(&stcb->asoc.sent_queue), ("sent_queue not empty"));
+
+ if (stcb->asoc.sent_queue_retran_cnt) {
+ SCTP_PRINTF("Hmm, sent_queue_retran_cnt is non-zero %d\n",
+ stcb->asoc.sent_queue_retran_cnt);
+ stcb->asoc.sent_queue_retran_cnt = 0;
+ }
+ if (stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, &stcb->asoc)) {
+ /* No stream scheduler information, initialize scheduler */
+ stcb->asoc.ss_functions.sctp_ss_init(stcb, &stcb->asoc);
+ if (!stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, &stcb->asoc)) {
+ /* yep, we lost a stream or two */
+ SCTP_PRINTF("Found additional streams NOT managed by scheduler, corrected\n");
+ } else {
+ /* no streams lost */
+ stcb->asoc.total_output_queue_size = 0;
+ }
+ }
+ /* Check to see if some data queued, if so report it */
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ if (!TAILQ_EMPTY(&stcb->asoc.strmout[i].outqueue)) {
+ TAILQ_FOREACH(sp, &stcb->asoc.strmout[i].outqueue, next) {
+ if (sp->msg_is_complete)
+ being_filled++;
+ chks_in_queue++;
+ }
+ }
+ }
+ if (chks_in_queue != stcb->asoc.stream_queue_cnt) {
+ SCTP_PRINTF("Hmm, stream queue cnt at %d I counted %d in stream out wheel\n",
+ stcb->asoc.stream_queue_cnt, chks_in_queue);
+ }
+ if (chks_in_queue) {
+ /* call the output queue function */
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ if ((TAILQ_EMPTY(&stcb->asoc.send_queue)) &&
+ (TAILQ_EMPTY(&stcb->asoc.sent_queue))) {
+ /*
+ * Probably should go in and make it go back through
+ * and add fragments allowed
+ */
+ if (being_filled == 0) {
+ SCTP_PRINTF("Still nothing moved %d chunks are stuck\n",
+ chks_in_queue);
+ }
+ }
+ } else {
+ SCTP_PRINTF("Found no chunks on any queue tot:%lu\n",
+ (u_long)stcb->asoc.total_output_queue_size);
+ stcb->asoc.total_output_queue_size = 0;
+ }
+}
+
+int
+sctp_heartbeat_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ bool net_was_pf;
+
+ net_was_pf = (net->dest_state & SCTP_ADDR_PF) != 0;
+ if (net->hb_responded == 0) {
+ if (net->ro._s_addr != NULL) {
+ /* Invalidate the src address if we did not get
+ * a response last time.
+ */
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ sctp_backoff_on_timeout(stcb, net, 1, 0, 0);
+ if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) {
+ /* Assoc is over */
+ return (1);
+ }
+ }
+ /* Zero PBA, if it needs it */
+ if (net->partial_bytes_acked > 0) {
+ net->partial_bytes_acked = 0;
+ }
+ if ((stcb->asoc.total_output_queue_size > 0) &&
+ (TAILQ_EMPTY(&stcb->asoc.send_queue)) &&
+ (TAILQ_EMPTY(&stcb->asoc.sent_queue))) {
+ sctp_audit_stream_queues_for_size(inp, stcb);
+ }
+ if ((((net->dest_state & SCTP_ADDR_NOHB) == 0) ||
+ (net->dest_state & SCTP_ADDR_UNCONFIRMED)) &&
+ (net_was_pf || ((net->dest_state & SCTP_ADDR_PF) == 0))) {
+ /* When moving to PF during threshold management, a HB has been
+ queued in that routine. */
+ uint32_t ms_gone_by;
+
+ if ((net->last_sent_time.tv_sec > 0) ||
+ (net->last_sent_time.tv_usec > 0)) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct timeval diff;
+
+ SCTP_GETTIME_TIMEVAL(&diff);
+ timevalsub(&diff, &net->last_sent_time);
+#else
+ struct timeval diff, now;
+
+ SCTP_GETTIME_TIMEVAL(&now);
+ timersub(&now, &net->last_sent_time, &diff);
+#endif
+ ms_gone_by = (uint32_t)(diff.tv_sec * 1000) +
+ (uint32_t)(diff.tv_usec / 1000);
+ } else {
+ ms_gone_by = 0xffffffff;
+ }
+ if ((ms_gone_by >= net->heart_beat_delay) ||
+ (net->dest_state & SCTP_ADDR_UNCONFIRMED) ||
+ (net->dest_state & SCTP_ADDR_PF)) {
+ sctp_send_hb(stcb, net, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ return (0);
+}
+
+void
+sctp_pathmtu_timer(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ uint32_t next_mtu, mtu;
+
+ next_mtu = sctp_get_next_mtu(net->mtu);
+
+ if ((next_mtu > net->mtu) && (net->port == 0)) {
+ if ((net->src_addr_selected == 0) ||
+ (net->ro._s_addr == NULL) ||
+ (net->ro._s_addr->localifa_flags & SCTP_BEING_DELETED)) {
+ if ((net->ro._s_addr != NULL) && (net->ro._s_addr->localifa_flags & SCTP_BEING_DELETED)) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ } else if (net->ro._s_addr == NULL) {
+#if defined(INET6) && defined(SCTP_EMBEDDED_V6_SCOPE)
+ if (net->ro._l_addr.sa.sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ /* KAME hack: embed scopeid */
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD)
+ (void)in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL);
+#else
+ (void)in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL, NULL);
+#endif
+#elif defined(SCTP_KAME)
+ (void)sa6_embedscope(sin6, MODULE_GLOBAL(ip6_use_defzone));
+#else
+ (void)in6_embedscope(&sin6->sin6_addr, sin6);
+#endif
+ }
+#endif
+
+ net->ro._s_addr = sctp_source_address_selection(inp,
+ stcb,
+ (sctp_route_t *)&net->ro,
+ net, 0, stcb->asoc.vrf_id);
+#if defined(INET6) && defined(SCTP_EMBEDDED_V6_SCOPE)
+ if (net->ro._l_addr.sa.sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+#ifdef SCTP_KAME
+ (void)sa6_recoverscope(sin6);
+#else
+ (void)in6_recoverscope(sin6, &sin6->sin6_addr, NULL);
+#endif /* SCTP_KAME */
+ }
+#endif /* INET6 */
+ }
+ if (net->ro._s_addr)
+ net->src_addr_selected = 1;
+ }
+ if (net->ro._s_addr) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._s_addr.sa, net->ro.ro_nh);
+#else
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._s_addr.sa, net->ro.ro_rt);
+#endif
+#if defined(INET) || defined(INET6)
+ if (net->port) {
+ mtu -= sizeof(struct udphdr);
+ }
+#endif
+ if (mtu > next_mtu) {
+ net->mtu = next_mtu;
+ } else {
+ net->mtu = mtu;
+ }
+ }
+ }
+ /* restart the timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net);
+}
+
+void
+sctp_autoclose_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb)
+{
+ struct timeval tn, *tim_touse;
+ struct sctp_association *asoc;
+ uint32_t ticks_gone_by;
+
+ (void)SCTP_GETTIME_TIMEVAL(&tn);
+ if (stcb->asoc.sctp_autoclose_ticks > 0 &&
+ sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) {
+ /* Auto close is on */
+ asoc = &stcb->asoc;
+ /* pick the time to use */
+ if (asoc->time_last_rcvd.tv_sec >
+ asoc->time_last_sent.tv_sec) {
+ tim_touse = &asoc->time_last_rcvd;
+ } else {
+ tim_touse = &asoc->time_last_sent;
+ }
+ /* Now has long enough transpired to autoclose? */
+ ticks_gone_by = sctp_secs_to_ticks((uint32_t)(tn.tv_sec - tim_touse->tv_sec));
+ if (ticks_gone_by >= asoc->sctp_autoclose_ticks) {
+ /*
+ * autoclose time has hit, call the output routine,
+ * which should do nothing just to be SURE we don't
+ * have hanging data. We can then safely check the
+ * queues and know that we are clear to send
+ * shutdown
+ */
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_AUTOCLOSE_TMR, SCTP_SO_NOT_LOCKED);
+ /* Are we clean? */
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue)) {
+ /*
+ * there is nothing queued to send, so I'm
+ * done...
+ */
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) {
+ /* only send SHUTDOWN 1st time thru */
+ struct sctp_nets *net;
+
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (stcb->asoc.alternate) {
+ net = stcb->asoc.alternate;
+ } else {
+ net = stcb->asoc.primary_destination;
+ }
+ sctp_send_shutdown(stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN,
+ stcb->sctp_ep, stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ }
+ }
+ } else {
+ /*
+ * No auto close at this time, reset t-o to check
+ * later
+ */
+ uint32_t tmp;
+
+ /* fool the timer startup to use the time left */
+ tmp = asoc->sctp_autoclose_ticks;
+ asoc->sctp_autoclose_ticks -= ticks_gone_by;
+ sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL);
+ /* restore the real tick value */
+ asoc->sctp_autoclose_ticks = tmp;
+ }
+ }
+}
+
diff --git a/netwerk/sctp/src/netinet/sctp_timer.h b/netwerk/sctp/src/netinet/sctp_timer.h
new file mode 100644
index 0000000000..166e86620f
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_timer.h
@@ -0,0 +1,98 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_TIMER_H_
+#define _NETINET_SCTP_TIMER_H_
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+#define SCTP_RTT_SHIFT 3
+#define SCTP_RTT_VAR_SHIFT 2
+
+struct sctp_nets *
+sctp_find_alternate_net(struct sctp_tcb *, struct sctp_nets *, int);
+
+int
+sctp_t3rxt_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+int
+sctp_t1init_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+int
+sctp_shutdown_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+int
+sctp_heartbeat_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+int
+sctp_cookie_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+void
+sctp_pathmtu_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+int
+sctp_shutdownack_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+int
+sctp_strreset_timer(struct sctp_inpcb *, struct sctp_tcb *);
+
+int
+sctp_asconf_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+void
+sctp_delete_prim_timer(struct sctp_inpcb *, struct sctp_tcb *);
+
+void
+sctp_autoclose_timer(struct sctp_inpcb *, struct sctp_tcb *);
+
+void sctp_audit_retranmission_queue(struct sctp_association *);
+
+void sctp_iterator_timer(struct sctp_iterator *it);
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD) || defined(APPLE_SNOWLEOPARD) || defined(APPLE_LION) || defined(APPLE_MOUNTAINLION)
+void sctp_slowtimo(void);
+#else
+void sctp_gc(struct inpcbinfo *);
+#endif
+#endif
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_uio.h b/netwerk/sctp/src/netinet/sctp_uio.h
new file mode 100644
index 0000000000..053d1cb16b
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_uio.h
@@ -0,0 +1,1353 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_UIO_H_
+#define _NETINET_SCTP_UIO_H_
+
+#if (defined(__APPLE__) && !defined(__Userspace__) && defined(KERNEL))
+#ifndef _KERNEL
+#define _KERNEL
+#endif
+#endif
+#if !defined(_WIN32)
+#if !defined(_KERNEL)
+#include <stdint.h>
+#endif
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+#pragma warning(push)
+#pragma warning(disable: 4200)
+#if defined(_KERNEL)
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+#endif
+
+typedef uint32_t sctp_assoc_t;
+
+#define SCTP_FUTURE_ASSOC 0
+#define SCTP_CURRENT_ASSOC 1
+#define SCTP_ALL_ASSOC 2
+
+struct sctp_event {
+ sctp_assoc_t se_assoc_id;
+ uint16_t se_type;
+ uint8_t se_on;
+};
+
+/* Compatibility to previous define's */
+#define sctp_stream_reset_events sctp_stream_reset_event
+
+/* On/Off setup for subscription to events */
+struct sctp_event_subscribe {
+ uint8_t sctp_data_io_event;
+ uint8_t sctp_association_event;
+ uint8_t sctp_address_event;
+ uint8_t sctp_send_failure_event;
+ uint8_t sctp_peer_error_event;
+ uint8_t sctp_shutdown_event;
+ uint8_t sctp_partial_delivery_event;
+ uint8_t sctp_adaptation_layer_event;
+ uint8_t sctp_authentication_event;
+ uint8_t sctp_sender_dry_event;
+ uint8_t sctp_stream_reset_event;
+};
+
+/* ancillary data types */
+#define SCTP_INIT 0x0001
+#define SCTP_SNDRCV 0x0002
+#define SCTP_EXTRCV 0x0003
+#define SCTP_SNDINFO 0x0004
+#define SCTP_RCVINFO 0x0005
+#define SCTP_NXTINFO 0x0006
+#define SCTP_PRINFO 0x0007
+#define SCTP_AUTHINFO 0x0008
+#define SCTP_DSTADDRV4 0x0009
+#define SCTP_DSTADDRV6 0x000a
+
+/*
+ * ancillary data structures
+ */
+struct sctp_initmsg {
+ uint16_t sinit_num_ostreams;
+ uint16_t sinit_max_instreams;
+ uint16_t sinit_max_attempts;
+ uint16_t sinit_max_init_timeo;
+};
+
+/* We add 96 bytes to the size of sctp_sndrcvinfo.
+ * This makes the current structure 128 bytes long
+ * which is nicely 64 bit aligned but also has room
+ * for us to add more and keep ABI compatibility.
+ * For example, already we have the sctp_extrcvinfo
+ * when enabled which is 48 bytes.
+ */
+
+/*
+ * The assoc up needs a verfid
+ * all sendrcvinfo's need a verfid for SENDING only.
+ */
+
+#define SCTP_ALIGN_RESV_PAD 92
+#define SCTP_ALIGN_RESV_PAD_SHORT 76
+
+struct sctp_sndrcvinfo {
+ uint16_t sinfo_stream;
+ uint16_t sinfo_ssn;
+ uint16_t sinfo_flags;
+ uint32_t sinfo_ppid;
+ uint32_t sinfo_context;
+ uint32_t sinfo_timetolive;
+ uint32_t sinfo_tsn;
+ uint32_t sinfo_cumtsn;
+ sctp_assoc_t sinfo_assoc_id;
+ uint16_t sinfo_keynumber;
+ uint16_t sinfo_keynumber_valid;
+ uint8_t __reserve_pad[SCTP_ALIGN_RESV_PAD];
+};
+
+struct sctp_extrcvinfo {
+ uint16_t sinfo_stream;
+ uint16_t sinfo_ssn;
+ uint16_t sinfo_flags;
+ uint32_t sinfo_ppid;
+ uint32_t sinfo_context;
+ uint32_t sinfo_timetolive; /* should have been sinfo_pr_value */
+ uint32_t sinfo_tsn;
+ uint32_t sinfo_cumtsn;
+ sctp_assoc_t sinfo_assoc_id;
+ uint16_t serinfo_next_flags;
+ uint16_t serinfo_next_stream;
+ uint32_t serinfo_next_aid;
+ uint32_t serinfo_next_length;
+ uint32_t serinfo_next_ppid;
+ uint16_t sinfo_keynumber;
+ uint16_t sinfo_keynumber_valid;
+ uint8_t __reserve_pad[SCTP_ALIGN_RESV_PAD_SHORT];
+};
+#define sinfo_pr_value sinfo_timetolive
+#define sreinfo_next_flags serinfo_next_flags
+#define sreinfo_next_stream serinfo_next_stream
+#define sreinfo_next_aid serinfo_next_aid
+#define sreinfo_next_length serinfo_next_length
+#define sreinfo_next_ppid serinfo_next_ppid
+
+struct sctp_sndinfo {
+ uint16_t snd_sid;
+ uint16_t snd_flags;
+ uint32_t snd_ppid;
+ uint32_t snd_context;
+ sctp_assoc_t snd_assoc_id;
+};
+
+struct sctp_prinfo {
+ uint16_t pr_policy;
+ uint32_t pr_value;
+};
+
+struct sctp_default_prinfo {
+ uint16_t pr_policy;
+ uint32_t pr_value;
+ sctp_assoc_t pr_assoc_id;
+};
+
+struct sctp_authinfo {
+ uint16_t auth_keynumber;
+};
+
+struct sctp_rcvinfo {
+ uint16_t rcv_sid;
+ uint16_t rcv_ssn;
+ uint16_t rcv_flags;
+ uint32_t rcv_ppid;
+ uint32_t rcv_tsn;
+ uint32_t rcv_cumtsn;
+ uint32_t rcv_context;
+ sctp_assoc_t rcv_assoc_id;
+};
+
+struct sctp_nxtinfo {
+ uint16_t nxt_sid;
+ uint16_t nxt_flags;
+ uint32_t nxt_ppid;
+ uint32_t nxt_length;
+ sctp_assoc_t nxt_assoc_id;
+};
+
+#define SCTP_NO_NEXT_MSG 0x0000
+#define SCTP_NEXT_MSG_AVAIL 0x0001
+#define SCTP_NEXT_MSG_ISCOMPLETE 0x0002
+#define SCTP_NEXT_MSG_IS_UNORDERED 0x0004
+#define SCTP_NEXT_MSG_IS_NOTIFICATION 0x0008
+
+struct sctp_recvv_rn {
+ struct sctp_rcvinfo recvv_rcvinfo;
+ struct sctp_nxtinfo recvv_nxtinfo;
+};
+
+#define SCTP_RECVV_NOINFO 0
+#define SCTP_RECVV_RCVINFO 1
+#define SCTP_RECVV_NXTINFO 2
+#define SCTP_RECVV_RN 3
+
+#define SCTP_SENDV_NOINFO 0
+#define SCTP_SENDV_SNDINFO 1
+#define SCTP_SENDV_PRINFO 2
+#define SCTP_SENDV_AUTHINFO 3
+#define SCTP_SENDV_SPA 4
+
+struct sctp_sendv_spa {
+ uint32_t sendv_flags;
+ struct sctp_sndinfo sendv_sndinfo;
+ struct sctp_prinfo sendv_prinfo;
+ struct sctp_authinfo sendv_authinfo;
+};
+
+#define SCTP_SEND_SNDINFO_VALID 0x00000001
+#define SCTP_SEND_PRINFO_VALID 0x00000002
+#define SCTP_SEND_AUTHINFO_VALID 0x00000004
+
+struct sctp_snd_all_completes {
+ uint16_t sall_stream;
+ uint16_t sall_flags;
+ uint32_t sall_ppid;
+ uint32_t sall_context;
+ uint32_t sall_num_sent;
+ uint32_t sall_num_failed;
+};
+
+/* Flags that go into the sinfo->sinfo_flags field */
+#define SCTP_NOTIFICATION 0x0010 /* next message is a notification */
+#define SCTP_COMPLETE 0x0020 /* next message is complete */
+#define SCTP_EOF 0x0100 /* Start shutdown procedures */
+#define SCTP_ABORT 0x0200 /* Send an ABORT to peer */
+#define SCTP_UNORDERED 0x0400 /* Message is un-ordered */
+#define SCTP_ADDR_OVER 0x0800 /* Override the primary-address */
+#define SCTP_SENDALL 0x1000 /* Send this on all associations */
+#define SCTP_EOR 0x2000 /* end of message signal */
+#define SCTP_SACK_IMMEDIATELY 0x4000 /* Set I-Bit */
+
+#define INVALID_SINFO_FLAG(x) (((x) & 0xfffffff0 \
+ & ~(SCTP_EOF | SCTP_ABORT | SCTP_UNORDERED |\
+ SCTP_ADDR_OVER | SCTP_SENDALL | SCTP_EOR |\
+ SCTP_SACK_IMMEDIATELY)) != 0)
+/* for the endpoint */
+
+/* The lower four bits is an enumeration of PR-SCTP policies */
+#define SCTP_PR_SCTP_NONE 0x0000 /* Reliable transfer */
+#define SCTP_PR_SCTP_TTL 0x0001 /* Time based PR-SCTP */
+#define SCTP_PR_SCTP_PRIO 0x0002 /* Buffer based PR-SCTP */
+#define SCTP_PR_SCTP_BUF SCTP_PR_SCTP_PRIO /* For backwards compatibility */
+#define SCTP_PR_SCTP_RTX 0x0003 /* Number of retransmissions based PR-SCTP */
+#define SCTP_PR_SCTP_MAX SCTP_PR_SCTP_RTX
+#define SCTP_PR_SCTP_ALL 0x000f /* Used for aggregated stats */
+
+#define PR_SCTP_POLICY(x) ((x) & 0x0f)
+#define PR_SCTP_ENABLED(x) ((PR_SCTP_POLICY(x) != SCTP_PR_SCTP_NONE) && \
+ (PR_SCTP_POLICY(x) != SCTP_PR_SCTP_ALL))
+#define PR_SCTP_TTL_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_TTL)
+#define PR_SCTP_BUF_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_BUF)
+#define PR_SCTP_RTX_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_RTX)
+#define PR_SCTP_INVALID_POLICY(x) (PR_SCTP_POLICY(x) > SCTP_PR_SCTP_MAX)
+#define PR_SCTP_VALID_POLICY(x) (PR_SCTP_POLICY(x) <= SCTP_PR_SCTP_MAX)
+
+/* Stat's */
+struct sctp_pcbinfo {
+ uint32_t ep_count;
+ uint32_t asoc_count;
+ uint32_t laddr_count;
+ uint32_t raddr_count;
+ uint32_t chk_count;
+ uint32_t readq_count;
+ uint32_t free_chunks;
+ uint32_t stream_oque;
+};
+
+struct sctp_sockstat {
+ sctp_assoc_t ss_assoc_id;
+ uint32_t ss_total_sndbuf;
+ uint32_t ss_total_recv_buf;
+};
+
+/*
+ * notification event structures
+ */
+
+/*
+ * association change event
+ */
+struct sctp_assoc_change {
+ uint16_t sac_type;
+ uint16_t sac_flags;
+ uint32_t sac_length;
+ uint16_t sac_state;
+ uint16_t sac_error;
+ uint16_t sac_outbound_streams;
+ uint16_t sac_inbound_streams;
+ sctp_assoc_t sac_assoc_id;
+ uint8_t sac_info[];
+};
+
+/* sac_state values */
+#define SCTP_COMM_UP 0x0001
+#define SCTP_COMM_LOST 0x0002
+#define SCTP_RESTART 0x0003
+#define SCTP_SHUTDOWN_COMP 0x0004
+#define SCTP_CANT_STR_ASSOC 0x0005
+
+/* sac_info values */
+#define SCTP_ASSOC_SUPPORTS_PR 0x01
+#define SCTP_ASSOC_SUPPORTS_AUTH 0x02
+#define SCTP_ASSOC_SUPPORTS_ASCONF 0x03
+#define SCTP_ASSOC_SUPPORTS_MULTIBUF 0x04
+#define SCTP_ASSOC_SUPPORTS_RE_CONFIG 0x05
+#define SCTP_ASSOC_SUPPORTS_INTERLEAVING 0x06
+#define SCTP_ASSOC_SUPPORTS_MAX 0x06
+/*
+ * Address event
+ */
+struct sctp_paddr_change {
+ uint16_t spc_type;
+ uint16_t spc_flags;
+ uint32_t spc_length;
+ struct sockaddr_storage spc_aaddr;
+ uint32_t spc_state;
+ uint32_t spc_error;
+ sctp_assoc_t spc_assoc_id;
+};
+
+/* paddr state values */
+#define SCTP_ADDR_AVAILABLE 0x0001
+#define SCTP_ADDR_UNREACHABLE 0x0002
+#define SCTP_ADDR_REMOVED 0x0003
+#define SCTP_ADDR_ADDED 0x0004
+#define SCTP_ADDR_MADE_PRIM 0x0005
+#define SCTP_ADDR_CONFIRMED 0x0006
+
+#define SCTP_ACTIVE 0x0001 /* SCTP_ADDR_REACHABLE */
+#define SCTP_INACTIVE 0x0002 /* neither SCTP_ADDR_REACHABLE
+ nor SCTP_ADDR_UNCONFIRMED */
+#define SCTP_UNCONFIRMED 0x0200 /* SCTP_ADDR_UNCONFIRMED */
+
+/* remote error events */
+struct sctp_remote_error {
+ uint16_t sre_type;
+ uint16_t sre_flags;
+ uint32_t sre_length;
+ uint16_t sre_error;
+ sctp_assoc_t sre_assoc_id;
+ uint8_t sre_data[];
+};
+
+/* data send failure event (deprecated) */
+struct sctp_send_failed {
+ uint16_t ssf_type;
+ uint16_t ssf_flags;
+ uint32_t ssf_length;
+ uint32_t ssf_error;
+ struct sctp_sndrcvinfo ssf_info;
+ sctp_assoc_t ssf_assoc_id;
+ uint8_t ssf_data[];
+};
+
+/* data send failure event (not deprecated) */
+struct sctp_send_failed_event {
+ uint16_t ssfe_type;
+ uint16_t ssfe_flags;
+ uint32_t ssfe_length;
+ uint32_t ssfe_error;
+ struct sctp_sndinfo ssfe_info;
+ sctp_assoc_t ssfe_assoc_id;
+ uint8_t ssfe_data[];
+};
+
+/* flag that indicates state of data */
+#define SCTP_DATA_UNSENT 0x0001 /* inqueue never on wire */
+#define SCTP_DATA_SENT 0x0002 /* on wire at failure */
+
+/* shutdown event */
+struct sctp_shutdown_event {
+ uint16_t sse_type;
+ uint16_t sse_flags;
+ uint32_t sse_length;
+ sctp_assoc_t sse_assoc_id;
+};
+
+/* Adaptation layer indication stuff */
+struct sctp_adaptation_event {
+ uint16_t sai_type;
+ uint16_t sai_flags;
+ uint32_t sai_length;
+ uint32_t sai_adaptation_ind;
+ sctp_assoc_t sai_assoc_id;
+};
+
+struct sctp_setadaptation {
+ uint32_t ssb_adaptation_ind;
+};
+
+/* compatible old spelling */
+struct sctp_adaption_event {
+ uint16_t sai_type;
+ uint16_t sai_flags;
+ uint32_t sai_length;
+ uint32_t sai_adaption_ind;
+ sctp_assoc_t sai_assoc_id;
+};
+
+struct sctp_setadaption {
+ uint32_t ssb_adaption_ind;
+};
+
+/*
+ * Partial Delivery API event
+ */
+struct sctp_pdapi_event {
+ uint16_t pdapi_type;
+ uint16_t pdapi_flags;
+ uint32_t pdapi_length;
+ uint32_t pdapi_indication;
+ uint16_t pdapi_stream;
+ uint16_t pdapi_seq;
+ sctp_assoc_t pdapi_assoc_id;
+};
+
+/* indication values */
+#define SCTP_PARTIAL_DELIVERY_ABORTED 0x0001
+
+/*
+ * authentication key event
+ */
+struct sctp_authkey_event {
+ uint16_t auth_type;
+ uint16_t auth_flags;
+ uint32_t auth_length;
+ uint16_t auth_keynumber;
+ uint16_t auth_altkeynumber;
+ uint32_t auth_indication;
+ sctp_assoc_t auth_assoc_id;
+};
+
+/* indication values */
+#define SCTP_AUTH_NEW_KEY 0x0001
+#define SCTP_AUTH_NEWKEY SCTP_AUTH_NEW_KEY
+#define SCTP_AUTH_NO_AUTH 0x0002
+#define SCTP_AUTH_FREE_KEY 0x0003
+
+struct sctp_sender_dry_event {
+ uint16_t sender_dry_type;
+ uint16_t sender_dry_flags;
+ uint32_t sender_dry_length;
+ sctp_assoc_t sender_dry_assoc_id;
+};
+
+/*
+ * Stream reset event - subscribe to SCTP_STREAM_RESET_EVENT
+ */
+struct sctp_stream_reset_event {
+ uint16_t strreset_type;
+ uint16_t strreset_flags;
+ uint32_t strreset_length;
+ sctp_assoc_t strreset_assoc_id;
+ uint16_t strreset_stream_list[];
+};
+
+/* flags in stream_reset_event (strreset_flags) */
+#define SCTP_STREAM_RESET_INCOMING_SSN 0x0001
+#define SCTP_STREAM_RESET_OUTGOING_SSN 0x0002
+#define SCTP_STREAM_RESET_DENIED 0x0004
+#define SCTP_STREAM_RESET_FAILED 0x0008
+
+/*
+ * Assoc reset event - subscribe to SCTP_ASSOC_RESET_EVENT
+ */
+struct sctp_assoc_reset_event {
+ uint16_t assocreset_type;
+ uint16_t assocreset_flags;
+ uint32_t assocreset_length;
+ sctp_assoc_t assocreset_assoc_id;
+ uint32_t assocreset_local_tsn;
+ uint32_t assocreset_remote_tsn;
+};
+
+#define SCTP_ASSOC_RESET_DENIED 0x0004
+#define SCTP_ASSOC_RESET_FAILED 0x0008
+
+/*
+ * Stream change event - subscribe to SCTP_STREAM_CHANGE_EVENT
+ */
+struct sctp_stream_change_event {
+ uint16_t strchange_type;
+ uint16_t strchange_flags;
+ uint32_t strchange_length;
+ sctp_assoc_t strchange_assoc_id;
+ uint16_t strchange_instrms;
+ uint16_t strchange_outstrms;
+};
+
+#define SCTP_STREAM_CHANGE_DENIED 0x0004
+#define SCTP_STREAM_CHANGE_FAILED 0x0008
+
+/* SCTP notification event */
+struct sctp_tlv {
+ uint16_t sn_type;
+ uint16_t sn_flags;
+ uint32_t sn_length;
+};
+
+union sctp_notification {
+ struct sctp_tlv sn_header;
+ struct sctp_assoc_change sn_assoc_change;
+ struct sctp_paddr_change sn_paddr_change;
+ struct sctp_remote_error sn_remote_error;
+ struct sctp_send_failed sn_send_failed;
+ struct sctp_shutdown_event sn_shutdown_event;
+ struct sctp_adaptation_event sn_adaptation_event;
+ /* compatibility same as above */
+ struct sctp_adaption_event sn_adaption_event;
+ struct sctp_pdapi_event sn_pdapi_event;
+ struct sctp_authkey_event sn_auth_event;
+ struct sctp_sender_dry_event sn_sender_dry_event;
+ struct sctp_send_failed_event sn_send_failed_event;
+ struct sctp_stream_reset_event sn_strreset_event;
+ struct sctp_assoc_reset_event sn_assocreset_event;
+ struct sctp_stream_change_event sn_strchange_event;
+};
+
+/* notification types */
+#define SCTP_ASSOC_CHANGE 0x0001
+#define SCTP_PEER_ADDR_CHANGE 0x0002
+#define SCTP_REMOTE_ERROR 0x0003
+#define SCTP_SEND_FAILED 0x0004
+#define SCTP_SHUTDOWN_EVENT 0x0005
+#define SCTP_ADAPTATION_INDICATION 0x0006
+/* same as above */
+#define SCTP_ADAPTION_INDICATION 0x0006
+#define SCTP_PARTIAL_DELIVERY_EVENT 0x0007
+#define SCTP_AUTHENTICATION_EVENT 0x0008
+#define SCTP_STREAM_RESET_EVENT 0x0009
+#define SCTP_SENDER_DRY_EVENT 0x000a
+#define SCTP_NOTIFICATIONS_STOPPED_EVENT 0x000b /* we don't send this*/
+#define SCTP_ASSOC_RESET_EVENT 0x000c
+#define SCTP_STREAM_CHANGE_EVENT 0x000d
+#define SCTP_SEND_FAILED_EVENT 0x000e
+/*
+ * socket option structs
+ */
+
+struct sctp_paddrparams {
+ struct sockaddr_storage spp_address;
+ sctp_assoc_t spp_assoc_id;
+ uint32_t spp_hbinterval;
+ uint32_t spp_pathmtu;
+ uint32_t spp_flags;
+ uint32_t spp_ipv6_flowlabel;
+ uint16_t spp_pathmaxrxt;
+ uint8_t spp_dscp;
+};
+#define spp_ipv4_tos spp_dscp
+
+#define SPP_HB_ENABLE 0x00000001
+#define SPP_HB_DISABLE 0x00000002
+#define SPP_HB_DEMAND 0x00000004
+#define SPP_PMTUD_ENABLE 0x00000008
+#define SPP_PMTUD_DISABLE 0x00000010
+#define SPP_HB_TIME_IS_ZERO 0x00000080
+#define SPP_IPV6_FLOWLABEL 0x00000100
+#define SPP_DSCP 0x00000200
+#define SPP_IPV4_TOS SPP_DSCP
+
+struct sctp_paddrthlds {
+ struct sockaddr_storage spt_address;
+ sctp_assoc_t spt_assoc_id;
+ uint16_t spt_pathmaxrxt;
+ uint16_t spt_pathpfthld;
+ uint16_t spt_pathcpthld;
+};
+
+struct sctp_paddrinfo {
+ struct sockaddr_storage spinfo_address;
+ sctp_assoc_t spinfo_assoc_id;
+ int32_t spinfo_state;
+ uint32_t spinfo_cwnd;
+ uint32_t spinfo_srtt;
+ uint32_t spinfo_rto;
+ uint32_t spinfo_mtu;
+};
+
+struct sctp_rtoinfo {
+ sctp_assoc_t srto_assoc_id;
+ uint32_t srto_initial;
+ uint32_t srto_max;
+ uint32_t srto_min;
+};
+
+struct sctp_assocparams {
+ sctp_assoc_t sasoc_assoc_id;
+ uint32_t sasoc_peer_rwnd;
+ uint32_t sasoc_local_rwnd;
+ uint32_t sasoc_cookie_life;
+ uint16_t sasoc_asocmaxrxt;
+ uint16_t sasoc_number_peer_destinations;
+};
+
+struct sctp_setprim {
+ struct sockaddr_storage ssp_addr;
+ sctp_assoc_t ssp_assoc_id;
+ uint8_t ssp_padding[4];
+};
+
+struct sctp_setpeerprim {
+ struct sockaddr_storage sspp_addr;
+ sctp_assoc_t sspp_assoc_id;
+ uint8_t sspp_padding[4];
+};
+
+union sctp_sockstore {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+#if defined(__Userspace__)
+ struct sockaddr_conn sconn;
+#endif
+ struct sockaddr sa;
+};
+
+struct sctp_getaddresses {
+ sctp_assoc_t sget_assoc_id;
+ union sctp_sockstore addr[];
+};
+
+struct sctp_status {
+ sctp_assoc_t sstat_assoc_id;
+ int32_t sstat_state;
+ uint32_t sstat_rwnd;
+ uint16_t sstat_unackdata;
+ uint16_t sstat_penddata;
+ uint16_t sstat_instrms;
+ uint16_t sstat_outstrms;
+ uint32_t sstat_fragmentation_point;
+ struct sctp_paddrinfo sstat_primary;
+};
+
+/*
+ * AUTHENTICATION support
+ */
+/* SCTP_AUTH_CHUNK */
+struct sctp_authchunk {
+ uint8_t sauth_chunk;
+};
+
+/* SCTP_AUTH_KEY */
+struct sctp_authkey {
+ sctp_assoc_t sca_assoc_id;
+ uint16_t sca_keynumber;
+ uint16_t sca_keylength;
+ uint8_t sca_key[];
+};
+
+/* SCTP_HMAC_IDENT */
+struct sctp_hmacalgo {
+ uint32_t shmac_number_of_idents;
+ uint16_t shmac_idents[];
+};
+
+/* AUTH hmac_id */
+#define SCTP_AUTH_HMAC_ID_RSVD 0x0000
+#define SCTP_AUTH_HMAC_ID_SHA1 0x0001 /* default, mandatory */
+#define SCTP_AUTH_HMAC_ID_SHA256 0x0003
+
+/* SCTP_AUTH_ACTIVE_KEY / SCTP_AUTH_DELETE_KEY */
+struct sctp_authkeyid {
+ sctp_assoc_t scact_assoc_id;
+ uint16_t scact_keynumber;
+};
+
+/* SCTP_PEER_AUTH_CHUNKS / SCTP_LOCAL_AUTH_CHUNKS */
+struct sctp_authchunks {
+ sctp_assoc_t gauth_assoc_id;
+ uint32_t gauth_number_of_chunks;
+ uint8_t gauth_chunks[];
+};
+
+struct sctp_assoc_value {
+ sctp_assoc_t assoc_id;
+ uint32_t assoc_value;
+};
+
+struct sctp_cc_option {
+ int option;
+ struct sctp_assoc_value aid_value;
+};
+
+struct sctp_stream_value {
+ sctp_assoc_t assoc_id;
+ uint16_t stream_id;
+ uint16_t stream_value;
+};
+
+struct sctp_assoc_ids {
+ uint32_t gaids_number_of_ids;
+ sctp_assoc_t gaids_assoc_id[];
+};
+
+struct sctp_sack_info {
+ sctp_assoc_t sack_assoc_id;
+ uint32_t sack_delay;
+ uint32_t sack_freq;
+};
+
+struct sctp_timeouts {
+ sctp_assoc_t stimo_assoc_id;
+ uint32_t stimo_init;
+ uint32_t stimo_data;
+ uint32_t stimo_sack;
+ uint32_t stimo_shutdown;
+ uint32_t stimo_heartbeat;
+ uint32_t stimo_cookie;
+ uint32_t stimo_shutdownack;
+};
+
+struct sctp_udpencaps {
+ struct sockaddr_storage sue_address;
+ sctp_assoc_t sue_assoc_id;
+ uint16_t sue_port;
+};
+
+struct sctp_prstatus {
+ sctp_assoc_t sprstat_assoc_id;
+ uint16_t sprstat_sid;
+ uint16_t sprstat_policy;
+ uint64_t sprstat_abandoned_unsent;
+ uint64_t sprstat_abandoned_sent;
+};
+
+struct sctp_cwnd_args {
+ struct sctp_nets *net; /* network to */ /* FIXME: LP64 issue */
+ uint32_t cwnd_new_value;/* cwnd in k */
+ uint32_t pseudo_cumack;
+ uint16_t inflight; /* flightsize in k */
+ uint16_t cwnd_augment; /* increment to it */
+ uint8_t meets_pseudo_cumack;
+ uint8_t need_new_pseudo_cumack;
+ uint8_t cnt_in_send;
+ uint8_t cnt_in_str;
+};
+
+struct sctp_blk_args {
+ uint32_t onsb; /* in 1k bytes */
+ uint32_t sndlen; /* len of send being attempted */
+ uint32_t peer_rwnd; /* rwnd of peer */
+ uint16_t send_sent_qcnt;/* chnk cnt */
+ uint16_t stream_qcnt; /* chnk cnt */
+ uint16_t chunks_on_oque;/* chunks out */
+ uint16_t flight_size; /* flight size in k */
+};
+
+/*
+ * Max we can reset in one setting, note this is dictated not by the define
+ * but the size of a mbuf cluster so don't change this define and think you
+ * can specify more. You must do multiple resets if you want to reset more
+ * than SCTP_MAX_EXPLICIT_STR_RESET.
+ */
+#define SCTP_MAX_EXPLICT_STR_RESET 1000
+
+struct sctp_reset_streams {
+ sctp_assoc_t srs_assoc_id;
+ uint16_t srs_flags;
+ uint16_t srs_number_streams; /* 0 == ALL */
+ uint16_t srs_stream_list[];/* list if strrst_num_streams is not 0 */
+};
+
+struct sctp_add_streams {
+ sctp_assoc_t sas_assoc_id;
+ uint16_t sas_instrms;
+ uint16_t sas_outstrms;
+};
+
+struct sctp_get_nonce_values {
+ sctp_assoc_t gn_assoc_id;
+ uint32_t gn_peers_tag;
+ uint32_t gn_local_tag;
+};
+
+/* Values for SCTP_ACCEPT_ZERO_CHECKSUM */
+#define SCTP_EDMID_NONE 0
+#define SCTP_EDMID_LOWER_LAYER_DTLS 1
+
+/* Debugging logs */
+struct sctp_str_log {
+ void *stcb; /* FIXME: LP64 issue */
+ uint32_t n_tsn;
+ uint32_t e_tsn;
+ uint16_t n_sseq;
+ uint16_t e_sseq;
+ uint16_t strm;
+};
+
+struct sctp_sb_log {
+ void *stcb; /* FIXME: LP64 issue */
+ uint32_t so_sbcc;
+ uint32_t stcb_sbcc;
+ uint32_t incr;
+};
+
+struct sctp_fr_log {
+ uint32_t largest_tsn;
+ uint32_t largest_new_tsn;
+ uint32_t tsn;
+};
+
+struct sctp_fr_map {
+ uint32_t base;
+ uint32_t cum;
+ uint32_t high;
+};
+
+struct sctp_rwnd_log {
+ uint32_t rwnd;
+ uint32_t send_size;
+ uint32_t overhead;
+ uint32_t new_rwnd;
+};
+
+struct sctp_mbcnt_log {
+ uint32_t total_queue_size;
+ uint32_t size_change;
+ uint32_t total_queue_mb_size;
+ uint32_t mbcnt_change;
+};
+
+struct sctp_sack_log {
+ uint32_t cumack;
+ uint32_t oldcumack;
+ uint32_t tsn;
+ uint16_t numGaps;
+ uint16_t numDups;
+};
+
+struct sctp_lock_log {
+ void *sock; /* FIXME: LP64 issue */
+ void *inp; /* FIXME: LP64 issue */
+ uint8_t tcb_lock;
+ uint8_t inp_lock;
+ uint8_t info_lock;
+ uint8_t sock_lock;
+ uint8_t sockrcvbuf_lock;
+ uint8_t socksndbuf_lock;
+ uint8_t create_lock;
+ uint8_t resv;
+};
+
+struct sctp_rto_log {
+ void * net; /* FIXME: LP64 issue */
+ uint32_t rtt;
+};
+
+struct sctp_nagle_log {
+ void *stcb; /* FIXME: LP64 issue */
+ uint32_t total_flight;
+ uint32_t total_in_queue;
+ uint16_t count_in_queue;
+ uint16_t count_in_flight;
+};
+
+struct sctp_sbwake_log {
+ void *stcb; /* FIXME: LP64 issue */
+ uint16_t send_q;
+ uint16_t sent_q;
+ uint16_t flight;
+ uint16_t wake_cnt;
+ uint8_t stream_qcnt; /* chnk cnt */
+ uint8_t chunks_on_oque;/* chunks out */
+ uint8_t sbflags;
+ uint8_t sctpflags;
+};
+
+struct sctp_misc_info {
+ uint32_t log1;
+ uint32_t log2;
+ uint32_t log3;
+ uint32_t log4;
+};
+
+struct sctp_log_closing {
+ void *inp; /* FIXME: LP64 issue */
+ void *stcb; /* FIXME: LP64 issue */
+ uint32_t sctp_flags;
+ uint16_t state;
+ int16_t loc;
+};
+
+struct sctp_mbuf_log {
+ struct mbuf *mp; /* FIXME: LP64 issue */
+ caddr_t ext;
+ caddr_t data;
+ uint16_t size;
+ uint8_t refcnt;
+ uint8_t mbuf_flags;
+};
+
+struct sctp_cwnd_log {
+ uint64_t time_event;
+ uint8_t from;
+ uint8_t event_type;
+ uint8_t resv[2];
+ union {
+ struct sctp_log_closing close;
+ struct sctp_blk_args blk;
+ struct sctp_cwnd_args cwnd;
+ struct sctp_str_log strlog;
+ struct sctp_fr_log fr;
+ struct sctp_fr_map map;
+ struct sctp_rwnd_log rwnd;
+ struct sctp_mbcnt_log mbcnt;
+ struct sctp_sack_log sack;
+ struct sctp_lock_log lock;
+ struct sctp_rto_log rto;
+ struct sctp_sb_log sb;
+ struct sctp_nagle_log nagle;
+ struct sctp_sbwake_log wake;
+ struct sctp_mbuf_log mb;
+ struct sctp_misc_info misc;
+ } x;
+};
+
+struct sctp_cwnd_log_req {
+ int32_t num_in_log; /* Number in log */
+ int32_t num_ret; /* Number returned */
+ int32_t start_at; /* start at this one */
+ int32_t end_at; /* end at this one */
+ struct sctp_cwnd_log log[];
+};
+
+struct sctp_timeval {
+ uint32_t tv_sec;
+ uint32_t tv_usec;
+};
+
+struct sctpstat {
+ struct sctp_timeval sctps_discontinuitytime; /* sctpStats 18 (TimeStamp) */
+ /* MIB according to RFC 3873 */
+ uint32_t sctps_currestab; /* sctpStats 1 (Gauge32) */
+ uint32_t sctps_activeestab; /* sctpStats 2 (Counter32) */
+ uint32_t sctps_restartestab;
+ uint32_t sctps_collisionestab;
+ uint32_t sctps_passiveestab; /* sctpStats 3 (Counter32) */
+ uint32_t sctps_aborted; /* sctpStats 4 (Counter32) */
+ uint32_t sctps_shutdown; /* sctpStats 5 (Counter32) */
+ uint32_t sctps_outoftheblue; /* sctpStats 6 (Counter32) */
+ uint32_t sctps_checksumerrors; /* sctpStats 7 (Counter32) */
+ uint32_t sctps_outcontrolchunks; /* sctpStats 8 (Counter64) */
+ uint32_t sctps_outorderchunks; /* sctpStats 9 (Counter64) */
+ uint32_t sctps_outunorderchunks; /* sctpStats 10 (Counter64) */
+ uint32_t sctps_incontrolchunks; /* sctpStats 11 (Counter64) */
+ uint32_t sctps_inorderchunks; /* sctpStats 12 (Counter64) */
+ uint32_t sctps_inunorderchunks; /* sctpStats 13 (Counter64) */
+ uint32_t sctps_fragusrmsgs; /* sctpStats 14 (Counter64) */
+ uint32_t sctps_reasmusrmsgs; /* sctpStats 15 (Counter64) */
+ uint32_t sctps_outpackets; /* sctpStats 16 (Counter64) */
+ uint32_t sctps_inpackets; /* sctpStats 17 (Counter64) */
+
+ /* input statistics: */
+ uint32_t sctps_recvpackets; /* total input packets */
+ uint32_t sctps_recvdatagrams; /* total input datagrams */
+ uint32_t sctps_recvpktwithdata; /* total packets that had data */
+ uint32_t sctps_recvsacks; /* total input SACK chunks */
+ uint32_t sctps_recvdata; /* total input DATA chunks */
+ uint32_t sctps_recvdupdata; /* total input duplicate DATA chunks */
+ uint32_t sctps_recvheartbeat; /* total input HB chunks */
+ uint32_t sctps_recvheartbeatack; /* total input HB-ACK chunks */
+ uint32_t sctps_recvecne; /* total input ECNE chunks */
+ uint32_t sctps_recvauth; /* total input AUTH chunks */
+ uint32_t sctps_recvauthmissing; /* total input chunks missing AUTH */
+ uint32_t sctps_recvivalhmacid; /* total number of invalid HMAC ids received */
+ uint32_t sctps_recvivalkeyid; /* total number of invalid secret ids received */
+ uint32_t sctps_recvauthfailed; /* total number of auth failed */
+ uint32_t sctps_recvexpress; /* total fast path receives all one chunk */
+ uint32_t sctps_recvexpressm; /* total fast path multi-part data */
+ uint32_t sctps_recv_spare; /* formerly sctps_recvnocrc */
+ uint32_t sctps_recvswcrc;
+ uint32_t sctps_recvhwcrc;
+
+ /* output statistics: */
+ uint32_t sctps_sendpackets; /* total output packets */
+ uint32_t sctps_sendsacks; /* total output SACKs */
+ uint32_t sctps_senddata; /* total output DATA chunks */
+ uint32_t sctps_sendretransdata; /* total output retransmitted DATA chunks */
+ uint32_t sctps_sendfastretrans; /* total output fast retransmitted DATA chunks */
+ uint32_t sctps_sendmultfastretrans; /* total FR's that happened more than once
+ * to same chunk (u-del multi-fr algo).
+ */
+ uint32_t sctps_sendheartbeat; /* total output HB chunks */
+ uint32_t sctps_sendecne; /* total output ECNE chunks */
+ uint32_t sctps_sendauth; /* total output AUTH chunks FIXME */
+ uint32_t sctps_senderrors; /* ip_output error counter */
+ uint32_t sctps_send_spare; /* formerly sctps_sendnocrc */
+ uint32_t sctps_sendswcrc;
+ uint32_t sctps_sendhwcrc;
+ /* PCKDROPREP statistics: */
+ uint32_t sctps_pdrpfmbox; /* Packet drop from middle box */
+ uint32_t sctps_pdrpfehos; /* P-drop from end host */
+ uint32_t sctps_pdrpmbda; /* P-drops with data */
+ uint32_t sctps_pdrpmbct; /* P-drops, non-data, non-endhost */
+ uint32_t sctps_pdrpbwrpt; /* P-drop, non-endhost, bandwidth rep only */
+ uint32_t sctps_pdrpcrupt; /* P-drop, not enough for chunk header */
+ uint32_t sctps_pdrpnedat; /* P-drop, not enough data to confirm */
+ uint32_t sctps_pdrppdbrk; /* P-drop, where process_chunk_drop said break */
+ uint32_t sctps_pdrptsnnf; /* P-drop, could not find TSN */
+ uint32_t sctps_pdrpdnfnd; /* P-drop, attempt reverse TSN lookup */
+ uint32_t sctps_pdrpdiwnp; /* P-drop, e-host confirms zero-rwnd */
+ uint32_t sctps_pdrpdizrw; /* P-drop, midbox confirms no space */
+ uint32_t sctps_pdrpbadd; /* P-drop, data did not match TSN */
+ uint32_t sctps_pdrpmark; /* P-drop, TSN's marked for Fast Retran */
+ /* timeouts */
+ uint32_t sctps_timoiterator; /* Number of iterator timers that fired */
+ uint32_t sctps_timodata; /* Number of T3 data time outs */
+ uint32_t sctps_timowindowprobe; /* Number of window probe (T3) timers that fired */
+ uint32_t sctps_timoinit; /* Number of INIT timers that fired */
+ uint32_t sctps_timosack; /* Number of sack timers that fired */
+ uint32_t sctps_timoshutdown; /* Number of shutdown timers that fired */
+ uint32_t sctps_timoheartbeat; /* Number of heartbeat timers that fired */
+ uint32_t sctps_timocookie; /* Number of times a cookie timeout fired */
+ uint32_t sctps_timosecret; /* Number of times an endpoint changed its cookie secret*/
+ uint32_t sctps_timopathmtu; /* Number of PMTU timers that fired */
+ uint32_t sctps_timoshutdownack; /* Number of shutdown ack timers that fired */
+ uint32_t sctps_timoshutdownguard; /* Number of shutdown guard timers that fired */
+ uint32_t sctps_timostrmrst; /* Number of stream reset timers that fired */
+ uint32_t sctps_timoearlyfr; /* Number of early FR timers that fired */
+ uint32_t sctps_timoasconf; /* Number of times an asconf timer fired */
+ uint32_t sctps_timodelprim; /* Number of times a prim_deleted timer fired */
+ uint32_t sctps_timoautoclose; /* Number of times auto close timer fired */
+ uint32_t sctps_timoassockill; /* Number of asoc free timers expired */
+ uint32_t sctps_timoinpkill; /* Number of inp free timers expired */
+ /* former early FR counters */
+ uint32_t sctps_spare[11];
+ /* others */
+ uint32_t sctps_hdrops; /* packet shorter than header */
+ uint32_t sctps_badsum; /* checksum error */
+ uint32_t sctps_noport; /* no endpoint for port */
+ uint32_t sctps_badvtag; /* bad v-tag */
+ uint32_t sctps_badsid; /* bad SID */
+ uint32_t sctps_nomem; /* no memory */
+ uint32_t sctps_fastretransinrtt; /* number of multiple FR in a RTT window */
+ uint32_t sctps_markedretrans;
+ uint32_t sctps_naglesent; /* nagle allowed sending */
+ uint32_t sctps_naglequeued; /* nagle doesn't allow sending */
+ uint32_t sctps_maxburstqueued; /* max burst doesn't allow sending */
+ uint32_t sctps_ifnomemqueued; /* look ahead tells us no memory in
+ * interface ring buffer OR we had a
+ * send error and are queuing one send.
+ */
+ uint32_t sctps_windowprobed; /* total number of window probes sent */
+ uint32_t sctps_lowlevelerr; /* total times an output error causes us
+ * to clamp down on next user send.
+ */
+ uint32_t sctps_lowlevelerrusr; /* total times sctp_senderrors were caused from
+ * a user send from a user invoked send not
+ * a sack response
+ */
+ uint32_t sctps_datadropchklmt; /* Number of in data drops due to chunk limit reached */
+ uint32_t sctps_datadroprwnd; /* Number of in data drops due to rwnd limit reached */
+ uint32_t sctps_ecnereducedcwnd; /* Number of times a ECN reduced the cwnd */
+ uint32_t sctps_vtagexpress; /* Used express lookup via vtag */
+ uint32_t sctps_vtagbogus; /* Collision in express lookup. */
+ uint32_t sctps_primary_randry; /* Number of times the sender ran dry of user data on primary */
+ uint32_t sctps_cmt_randry; /* Same for above */
+ uint32_t sctps_slowpath_sack; /* Sacks the slow way */
+ uint32_t sctps_wu_sacks_sent; /* Window Update only sacks sent */
+ uint32_t sctps_sends_with_flags; /* number of sends with sinfo_flags !=0 */
+ uint32_t sctps_sends_with_unord; /* number of unordered sends */
+ uint32_t sctps_sends_with_eof; /* number of sends with EOF flag set */
+ uint32_t sctps_sends_with_abort; /* number of sends with ABORT flag set */
+ uint32_t sctps_protocol_drain_calls; /* number of times protocol drain called */
+ uint32_t sctps_protocol_drains_done; /* number of times we did a protocol drain */
+ uint32_t sctps_read_peeks; /* Number of times recv was called with peek */
+ uint32_t sctps_cached_chk; /* Number of cached chunks used */
+ uint32_t sctps_cached_strmoq; /* Number of cached stream oq's used */
+ uint32_t sctps_left_abandon; /* Number of unread messages abandoned by close */
+ uint32_t sctps_send_burst_avoid; /* Unused */
+ uint32_t sctps_send_cwnd_avoid; /* Send cwnd full avoidance, already max burst inflight to net */
+ uint32_t sctps_fwdtsn_map_over; /* number of map array over-runs via fwd-tsn's */
+ uint32_t sctps_queue_upd_ecne; /* Number of times we queued or updated an ECN chunk on send queue */
+ uint32_t sctps_recvzerocrc; /* Number of accepted packets with zero CRC */
+ uint32_t sctps_sendzerocrc; /* Number of packets sent with zero CRC */
+ uint32_t sctps_reserved[29]; /* Future ABI compat - remove int's from here when adding new */
+};
+
+#define SCTP_STAT_INCR(_x) SCTP_STAT_INCR_BY(_x,1)
+#define SCTP_STAT_DECR(_x) SCTP_STAT_DECR_BY(_x,1)
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#if defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+#define SCTP_STAT_INCR_BY(_x,_d) (SCTP_BASE_STATS[PCPU_GET(cpuid)]._x += _d)
+#define SCTP_STAT_DECR_BY(_x,_d) (SCTP_BASE_STATS[PCPU_GET(cpuid)]._x -= _d)
+#else
+#define SCTP_STAT_INCR_BY(_x,_d) atomic_add_int(&SCTP_BASE_STAT(_x), _d)
+#define SCTP_STAT_DECR_BY(_x,_d) atomic_subtract_int(&SCTP_BASE_STAT(_x), _d)
+#endif
+#else
+#define SCTP_STAT_INCR_BY(_x,_d) atomic_add_int(&SCTP_BASE_STAT(_x), _d)
+#define SCTP_STAT_DECR_BY(_x,_d) atomic_subtract_int(&SCTP_BASE_STAT(_x), _d)
+#endif
+/* The following macros are for handling MIB values, */
+#define SCTP_STAT_INCR_COUNTER32(_x) SCTP_STAT_INCR(_x)
+#define SCTP_STAT_INCR_COUNTER64(_x) SCTP_STAT_INCR(_x)
+#define SCTP_STAT_INCR_GAUGE32(_x) SCTP_STAT_INCR(_x)
+#define SCTP_STAT_DECR_COUNTER32(_x) SCTP_STAT_DECR(_x)
+#define SCTP_STAT_DECR_COUNTER64(_x) SCTP_STAT_DECR(_x)
+#define SCTP_STAT_DECR_GAUGE32(_x) SCTP_STAT_DECR(_x)
+
+/***********************************/
+/* And something for us old timers */
+/***********************************/
+
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+#if !defined(__Userspace__)
+#ifndef ntohll
+#if defined(__linux__)
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+#include <endian.h>
+#else
+#include <sys/endian.h>
+#endif
+#define ntohll(x) be64toh(x)
+#endif
+
+#ifndef htonll
+#if defined(__linux__)
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+#include <endian.h>
+#else
+#include <sys/endian.h>
+#endif
+#define htonll(x) htobe64(x)
+#endif
+#endif
+#endif
+/***********************************/
+
+struct xsctp_inpcb {
+ uint32_t last;
+ uint32_t flags;
+ uint64_t features;
+ uint32_t total_sends;
+ uint32_t total_recvs;
+ uint32_t total_nospaces;
+ uint32_t fragmentation_point;
+ uint16_t local_port;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint16_t qlen_old;
+ uint16_t maxqlen_old;
+#else
+ uint16_t qlen;
+ uint16_t maxqlen;
+#endif
+ uint16_t __spare16;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ kvaddr_t socket;
+#else
+ void *socket;
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint32_t qlen;
+ uint32_t maxqlen;
+#endif
+ uint32_t extra_padding[26]; /* future */
+};
+
+struct xsctp_tcb {
+ union sctp_sockstore primary_addr; /* sctpAssocEntry 5/6 */
+ uint32_t last;
+ uint32_t heartbeat_interval; /* sctpAssocEntry 7 */
+ uint32_t state; /* sctpAssocEntry 8 */
+ uint32_t in_streams; /* sctpAssocEntry 9 */
+ uint32_t out_streams; /* sctpAssocEntry 10 */
+ uint32_t max_nr_retrans; /* sctpAssocEntry 11 */
+ uint32_t primary_process; /* sctpAssocEntry 12 */
+ uint32_t T1_expireries; /* sctpAssocEntry 13 */
+ uint32_t T2_expireries; /* sctpAssocEntry 14 */
+ uint32_t retransmitted_tsns; /* sctpAssocEntry 15 */
+ uint32_t total_sends;
+ uint32_t total_recvs;
+ uint32_t local_tag;
+ uint32_t remote_tag;
+ uint32_t initial_tsn;
+ uint32_t highest_tsn;
+ uint32_t cumulative_tsn;
+ uint32_t cumulative_tsn_ack;
+ uint32_t mtu;
+ uint32_t refcnt;
+ uint16_t local_port; /* sctpAssocEntry 3 */
+ uint16_t remote_port; /* sctpAssocEntry 4 */
+ struct sctp_timeval start_time; /* sctpAssocEntry 16 */
+ struct sctp_timeval discontinuity_time; /* sctpAssocEntry 17 */
+ uint32_t peers_rwnd;
+ sctp_assoc_t assoc_id; /* sctpAssocEntry 1 */
+ uint32_t extra_padding[32]; /* future */
+};
+
+struct xsctp_laddr {
+ union sctp_sockstore address; /* sctpAssocLocalAddrEntry 1/2 */
+ uint32_t last;
+ struct sctp_timeval start_time; /* sctpAssocLocalAddrEntry 3 */
+ uint32_t extra_padding[32]; /* future */
+};
+
+struct xsctp_raddr {
+ union sctp_sockstore address; /* sctpAssocLocalRemEntry 1/2 */
+ uint32_t last;
+ uint32_t rto; /* sctpAssocLocalRemEntry 5 */
+ uint32_t max_path_rtx; /* sctpAssocLocalRemEntry 6 */
+ uint32_t rtx; /* sctpAssocLocalRemEntry 7 */
+ uint32_t error_counter; /* */
+ uint32_t cwnd; /* */
+ uint32_t flight_size; /* */
+ uint32_t mtu; /* */
+ uint8_t active; /* sctpAssocLocalRemEntry 3 */
+ uint8_t confirmed; /* */
+ uint8_t heartbeat_enabled; /* sctpAssocLocalRemEntry 4 */
+ uint8_t potentially_failed;
+ struct sctp_timeval start_time; /* sctpAssocLocalRemEntry 8 */
+ uint32_t rtt;
+ uint32_t heartbeat_interval;
+ uint32_t ssthresh;
+ uint16_t encaps_port;
+ uint16_t state;
+ uint32_t extra_padding[29]; /* future */
+};
+
+#define SCTP_MAX_LOGGING_SIZE 30000
+#define SCTP_TRACE_PARAMS 6 /* This number MUST be even */
+
+struct sctp_log_entry {
+ uint64_t timestamp;
+ uint32_t subsys;
+ uint32_t padding;
+ uint32_t params[SCTP_TRACE_PARAMS];
+};
+
+struct sctp_log {
+ struct sctp_log_entry entry[SCTP_MAX_LOGGING_SIZE];
+ uint32_t index;
+ uint32_t padding;
+};
+
+/*
+ * Kernel defined for sctp_send
+ */
+#if defined(_KERNEL) || defined(__Userspace__)
+int
+sctp_lower_sosend(struct socket *so,
+ struct sockaddr *addr,
+ struct uio *uio,
+ struct mbuf *top,
+ struct mbuf *control,
+ int flags,
+ struct sctp_sndrcvinfo *srcv
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+ ,struct thread *p
+#elif defined(_WIN32)
+ , PKTHREAD p
+#else
+ ,struct proc *p
+#endif
+#endif
+);
+
+int
+sctp_sorecvmsg(struct socket *so,
+ struct uio *uio,
+ struct mbuf **mp,
+ struct sockaddr *from,
+ int fromlen,
+ int *msg_flags,
+ struct sctp_sndrcvinfo *sinfo,
+ int filling_sinfo);
+#endif
+
+/*
+ * API system calls
+ */
+#if !(defined(_KERNEL)) && !(defined(__Userspace__))
+
+__BEGIN_DECLS
+int sctp_peeloff(int, sctp_assoc_t);
+int sctp_bindx(int, struct sockaddr *, int, int);
+int sctp_connectx(int, const struct sockaddr *, int, sctp_assoc_t *);
+int sctp_getaddrlen(sa_family_t);
+int sctp_getpaddrs(int, sctp_assoc_t, struct sockaddr **);
+void sctp_freepaddrs(struct sockaddr *);
+int sctp_getladdrs(int, sctp_assoc_t, struct sockaddr **);
+void sctp_freeladdrs(struct sockaddr *);
+int sctp_opt_info(int, sctp_assoc_t, int, void *, socklen_t *);
+
+/* deprecated */
+ssize_t sctp_sendmsg(int, const void *, size_t, const struct sockaddr *,
+ socklen_t, uint32_t, uint32_t, uint16_t, uint32_t, uint32_t);
+
+/* deprecated */
+ssize_t sctp_send(int, const void *, size_t,
+ const struct sctp_sndrcvinfo *, int);
+
+/* deprecated */
+ssize_t sctp_sendx(int, const void *, size_t, struct sockaddr *,
+ int, struct sctp_sndrcvinfo *, int);
+
+/* deprecated */
+ssize_t sctp_sendmsgx(int sd, const void *, size_t, struct sockaddr *,
+ int, uint32_t, uint32_t, uint16_t, uint32_t, uint32_t);
+
+sctp_assoc_t sctp_getassocid(int, struct sockaddr *);
+
+/* deprecated */
+ssize_t sctp_recvmsg(int, void *, size_t, struct sockaddr *, socklen_t *,
+ struct sctp_sndrcvinfo *, int *);
+
+ssize_t sctp_sendv(int, const struct iovec *, int, struct sockaddr *,
+ int, void *, socklen_t, unsigned int, int);
+
+ssize_t sctp_recvv(int, const struct iovec *, int, struct sockaddr *,
+ socklen_t *, void *, socklen_t *, unsigned int *, int *);
+__END_DECLS
+
+#endif /* !_KERNEL */
+#endif /* !__sctp_uio_h__ */
diff --git a/netwerk/sctp/src/netinet/sctp_userspace.c b/netwerk/sctp/src/netinet/sctp_userspace.c
new file mode 100644
index 0000000000..71888901fe
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_userspace.c
@@ -0,0 +1,425 @@
+/*-
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+
+#ifdef _WIN32
+#include <netinet/sctp_pcb.h>
+#include <sys/timeb.h>
+#include <iphlpapi.h>
+#if !defined(__MINGW32__)
+#pragma comment(lib, "iphlpapi.lib")
+#endif
+#endif
+#include <netinet/sctp_os_userspace.h>
+#if defined(__FreeBSD__)
+#include <pthread_np.h>
+#endif
+
+#if defined(__linux__)
+#include <sys/prctl.h>
+#endif
+
+#if defined(_WIN32)
+/* Adapter to translate Unix thread start routines to Windows thread start
+ * routines.
+ */
+#if defined(__MINGW32__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+static DWORD WINAPI
+sctp_create_thread_adapter(void *arg) {
+ start_routine_t start_routine = (start_routine_t)arg;
+ return start_routine(NULL) == NULL;
+}
+
+int
+sctp_userspace_thread_create(userland_thread_t *thread, start_routine_t start_routine)
+{
+ *thread = CreateThread(NULL, 0, sctp_create_thread_adapter,
+ (void *)start_routine, 0, NULL);
+ if (*thread == NULL)
+ return GetLastError();
+ return 0;
+}
+
+#if defined(__MINGW32__)
+#pragma GCC diagnostic pop
+#endif
+
+#else
+int
+sctp_userspace_thread_create(userland_thread_t *thread, start_routine_t start_routine)
+{
+ return pthread_create(thread, NULL, start_routine, NULL);
+}
+#endif
+
+void
+sctp_userspace_set_threadname(const char *name)
+{
+#if defined(__APPLE__)
+ pthread_setname_np(name);
+#endif
+#if defined(__linux__)
+ prctl(PR_SET_NAME, name);
+#endif
+#if defined(__FreeBSD__)
+ pthread_set_name_np(pthread_self(), name);
+#endif
+}
+
+#if !defined(_WIN32) && !defined(__native_client__)
+int
+sctp_userspace_get_mtu_from_ifn(uint32_t if_index)
+{
+#if defined(INET) || defined(INET6)
+ struct ifreq ifr;
+ int fd;
+#endif
+ int mtu;
+
+ if (if_index == 0xffffffff) {
+ mtu = 1280;
+ } else {
+ mtu = 0;
+#if defined(INET) || defined(INET6)
+ memset(&ifr, 0, sizeof(struct ifreq));
+ if (if_indextoname(if_index, ifr.ifr_name) != NULL) {
+ /* TODO can I use the raw socket here and not have to open a new one with each query? */
+#if defined(INET)
+ if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0) {
+#else
+ if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) >= 0) {
+#endif
+ if (ioctl(fd, SIOCGIFMTU, &ifr) >= 0) {
+ mtu = ifr.ifr_mtu;
+ }
+ close(fd);
+ }
+ }
+#endif
+ }
+ return (mtu);
+}
+#endif
+
+#if defined(__native_client__)
+int
+sctp_userspace_get_mtu_from_ifn(uint32_t if_index)
+{
+ return 1280;
+}
+#endif
+
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__linux__) || defined(__native_client__) || defined(__NetBSD__) || defined(__QNX__) || defined(_WIN32) || defined(__Fuchsia__) || defined(__EMSCRIPTEN__)
+int
+timingsafe_bcmp(const void *b1, const void *b2, size_t n)
+{
+ const unsigned char *p1 = b1, *p2 = b2;
+ int ret = 0;
+
+ for (; n > 0; n--)
+ ret |= *p1++ ^ *p2++;
+ return (ret != 0);
+}
+#endif
+
+#ifdef _WIN32
+int
+sctp_userspace_get_mtu_from_ifn(uint32_t if_index)
+{
+#if defined(INET) || defined(INET6)
+ PIP_ADAPTER_ADDRESSES pAdapterAddrs, pAdapt;
+ DWORD AdapterAddrsSize, Err;
+#endif
+ int mtu;
+
+ if (if_index == 0xffffffff) {
+ mtu = 1280;
+ } else {
+ mtu = 0;
+#if defined(INET) || defined(INET6)
+ AdapterAddrsSize = 0;
+ pAdapterAddrs = NULL;
+ if ((Err = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &AdapterAddrsSize)) != 0) {
+ if ((Err != ERROR_BUFFER_OVERFLOW) && (Err != ERROR_INSUFFICIENT_BUFFER)) {
+ SCTPDBG(SCTP_DEBUG_USR, "GetAdaptersAddresses() sizing failed with error code %d, AdapterAddrsSize = %d\n", Err, AdapterAddrsSize);
+ mtu = -1;
+ goto cleanup;
+ }
+ }
+ if ((pAdapterAddrs = (PIP_ADAPTER_ADDRESSES) GlobalAlloc(GPTR, AdapterAddrsSize)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Memory allocation error!\n");
+ mtu = -1;
+ goto cleanup;
+ }
+ if ((Err = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTPDBG(SCTP_DEBUG_USR, "GetAdaptersAddresses() failed with error code %d\n", Err);
+ mtu = -1;
+ goto cleanup;
+ }
+ for (pAdapt = pAdapterAddrs; pAdapt; pAdapt = pAdapt->Next) {
+ if (pAdapt->IfIndex == if_index) {
+ mtu = pAdapt->Mtu;
+ break;
+ }
+ }
+ cleanup:
+ if (pAdapterAddrs != NULL) {
+ GlobalFree(pAdapterAddrs);
+ }
+#endif
+ }
+ return (mtu);
+}
+
+void
+getwintimeofday(struct timeval *tv)
+{
+ FILETIME filetime;
+ ULARGE_INTEGER ularge;
+
+ GetSystemTimeAsFileTime(&filetime);
+ ularge.LowPart = filetime.dwLowDateTime;
+ ularge.HighPart = filetime.dwHighDateTime;
+ /* Change base from Jan 1 1601 00:00:00 to Jan 1 1970 00:00:00 */
+#if defined(__MINGW32__)
+ ularge.QuadPart -= 116444736000000000ULL;
+#else
+ ularge.QuadPart -= 116444736000000000UI64;
+#endif
+ /*
+ * ularge.QuadPart is now the number of 100-nanosecond intervals
+ * since Jan 1 1970 00:00:00.
+ */
+#if defined(__MINGW32__)
+ tv->tv_sec = (long)(ularge.QuadPart / 10000000ULL);
+ tv->tv_usec = (long)((ularge.QuadPart % 10000000ULL) / 10ULL);
+#else
+ tv->tv_sec = (long)(ularge.QuadPart / 10000000UI64);
+ tv->tv_usec = (long)((ularge.QuadPart % 10000000UI64) / 10UI64);
+#endif
+}
+
+int
+win_if_nametoindex(const char *ifname)
+{
+ IP_ADAPTER_ADDRESSES *addresses, *addr;
+ ULONG status, size;
+ int index = 0;
+
+ if (!ifname) {
+ return 0;
+ }
+
+ size = 0;
+ status = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &size);
+ if (status != ERROR_BUFFER_OVERFLOW) {
+ return 0;
+ }
+ addresses = malloc(size);
+ status = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, addresses, &size);
+ if (status == ERROR_SUCCESS) {
+ for (addr = addresses; addr; addr = addr->Next) {
+ if (addr->AdapterName && !strcmp(ifname, addr->AdapterName)) {
+ index = addr->IfIndex;
+ break;
+ }
+ }
+ }
+
+ free(addresses);
+ return index;
+}
+
+#if WINVER < 0x0600
+/* These functions are written based on the code at
+ * http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
+ * Therefore, for the rest of the file the following applies:
+ *
+ *
+ * Copyright and Licensing Information for ACE(TM), TAO(TM), CIAO(TM),
+ * DAnCE(TM), and CoSMIC(TM)
+ *
+ * [1]ACE(TM), [2]TAO(TM), [3]CIAO(TM), DAnCE(TM), and [4]CoSMIC(TM)
+ * (henceforth referred to as "DOC software") are copyrighted by
+ * [5]Douglas C. Schmidt and his [6]research group at [7]Washington
+ * University, [8]University of California, Irvine, and [9]Vanderbilt
+ * University, Copyright (c) 1993-2012, all rights reserved. Since DOC
+ * software is open-source, freely available software, you are free to
+ * use, modify, copy, and distribute--perpetually and irrevocably--the
+ * DOC software source code and object code produced from the source, as
+ * well as copy and distribute modified versions of this software. You
+ * must, however, include this copyright statement along with any code
+ * built using DOC software that you release. No copyright statement
+ * needs to be provided if you just ship binary executables of your
+ * software products.
+ *
+ * You can use DOC software in commercial and/or binary software releases
+ * and are under no obligation to redistribute any of your source code
+ * that is built using DOC software. Note, however, that you may not
+ * misappropriate the DOC software code, such as copyrighting it yourself
+ * or claiming authorship of the DOC software code, in a way that will
+ * prevent DOC software from being distributed freely using an
+ * open-source development model. You needn't inform anyone that you're
+ * using DOC software in your software, though we encourage you to let
+ * [10]us know so we can promote your project in the [11]DOC software
+ * success stories.
+ *
+ * The [12]ACE, [13]TAO, [14]CIAO, [15]DAnCE, and [16]CoSMIC web sites
+ * are maintained by the [17]DOC Group at the [18]Institute for Software
+ * Integrated Systems (ISIS) and the [19]Center for Distributed Object
+ * Computing of Washington University, St. Louis for the development of
+ * open-source software as part of the open-source software community.
+ * Submissions are provided by the submitter ``as is'' with no warranties
+ * whatsoever, including any warranty of merchantability, noninfringement
+ * of third party intellectual property, or fitness for any particular
+ * purpose. In no event shall the submitter be liable for any direct,
+ * indirect, special, exemplary, punitive, or consequential damages,
+ * including without limitation, lost profits, even if advised of the
+ * possibility of such damages. Likewise, DOC software is provided as is
+ * with no warranties of any kind, including the warranties of design,
+ * merchantability, and fitness for a particular purpose,
+ * noninfringement, or arising from a course of dealing, usage or trade
+ * practice. Washington University, UC Irvine, Vanderbilt University,
+ * their employees, and students shall have no liability with respect to
+ * the infringement of copyrights, trade secrets or any patents by DOC
+ * software or any part thereof. Moreover, in no event will Washington
+ * University, UC Irvine, or Vanderbilt University, their employees, or
+ * students be liable for any lost revenue or profits or other special,
+ * indirect and consequential damages.
+ *
+ * DOC software is provided with no support and without any obligation on
+ * the part of Washington University, UC Irvine, Vanderbilt University,
+ * their employees, or students to assist in its use, correction,
+ * modification, or enhancement. A [20]number of companies around the
+ * world provide commercial support for DOC software, however. DOC
+ * software is Y2K-compliant, as long as the underlying OS platform is
+ * Y2K-compliant. Likewise, DOC software is compliant with the new US
+ * daylight savings rule passed by Congress as "The Energy Policy Act of
+ * 2005," which established new daylight savings times (DST) rules for
+ * the United States that expand DST as of March 2007. Since DOC software
+ * obtains time/date and calendaring information from operating systems
+ * users will not be affected by the new DST rules as long as they
+ * upgrade their operating systems accordingly.
+ *
+ * The names ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM), CoSMIC(TM),
+ * Washington University, UC Irvine, and Vanderbilt University, may not
+ * be used to endorse or promote products or services derived from this
+ * source without express written permission from Washington University,
+ * UC Irvine, or Vanderbilt University. This license grants no permission
+ * to call products or services derived from this source ACE(TM),
+ * TAO(TM), CIAO(TM), DAnCE(TM), or CoSMIC(TM), nor does it grant
+ * permission for the name Washington University, UC Irvine, or
+ * Vanderbilt University to appear in their names.
+ *
+ * If you have any suggestions, additions, comments, or questions, please
+ * let [21]me know.
+ *
+ * [22]Douglas C. Schmidt
+ *
+ * References
+ *
+ * 1. http://www.cs.wustl.edu/~schmidt/ACE.html
+ * 2. http://www.cs.wustl.edu/~schmidt/TAO.html
+ * 3. http://www.dre.vanderbilt.edu/CIAO/
+ * 4. http://www.dre.vanderbilt.edu/cosmic/
+ * 5. http://www.dre.vanderbilt.edu/~schmidt/
+ * 6. http://www.cs.wustl.edu/~schmidt/ACE-members.html
+ * 7. http://www.wustl.edu/
+ * 8. http://www.uci.edu/
+ * 9. http://www.vanderbilt.edu/
+ * 10. mailto:doc_group@cs.wustl.edu
+ * 11. http://www.cs.wustl.edu/~schmidt/ACE-users.html
+ * 12. http://www.cs.wustl.edu/~schmidt/ACE.html
+ * 13. http://www.cs.wustl.edu/~schmidt/TAO.html
+ * 14. http://www.dre.vanderbilt.edu/CIAO/
+ * 15. http://www.dre.vanderbilt.edu/~schmidt/DOC_ROOT/DAnCE/
+ * 16. http://www.dre.vanderbilt.edu/cosmic/
+ * 17. http://www.dre.vanderbilt.edu/
+ * 18. http://www.isis.vanderbilt.edu/
+ * 19. http://www.cs.wustl.edu/~schmidt/doc-center.html
+ * 20. http://www.cs.wustl.edu/~schmidt/commercial-support.html
+ * 21. mailto:d.schmidt@vanderbilt.edu
+ * 22. http://www.dre.vanderbilt.edu/~schmidt/
+ * 23. http://www.cs.wustl.edu/ACE.html
+ */
+
+void
+InitializeXPConditionVariable(userland_cond_t *cv)
+{
+ cv->waiters_count = 0;
+ InitializeCriticalSection(&(cv->waiters_count_lock));
+ cv->events_[C_SIGNAL] = CreateEvent (NULL, FALSE, FALSE, NULL);
+ cv->events_[C_BROADCAST] = CreateEvent (NULL, TRUE, FALSE, NULL);
+}
+
+void
+DeleteXPConditionVariable(userland_cond_t *cv)
+{
+ CloseHandle(cv->events_[C_BROADCAST]);
+ CloseHandle(cv->events_[C_SIGNAL]);
+ DeleteCriticalSection(&(cv->waiters_count_lock));
+}
+
+int
+SleepXPConditionVariable(userland_cond_t *cv, userland_mutex_t *mtx)
+{
+ int result, last_waiter;
+
+ EnterCriticalSection(&cv->waiters_count_lock);
+ cv->waiters_count++;
+ LeaveCriticalSection(&cv->waiters_count_lock);
+ LeaveCriticalSection (mtx);
+ result = WaitForMultipleObjects(2, cv->events_, FALSE, INFINITE);
+ if (result==-1) {
+ result = GetLastError();
+ }
+ EnterCriticalSection(&cv->waiters_count_lock);
+ cv->waiters_count--;
+ last_waiter = result == (C_SIGNAL + C_BROADCAST && (cv->waiters_count == 0));
+ LeaveCriticalSection(&cv->waiters_count_lock);
+ if (last_waiter)
+ ResetEvent(cv->events_[C_BROADCAST]);
+ EnterCriticalSection (mtx);
+ return result;
+}
+
+void
+WakeAllXPConditionVariable(userland_cond_t *cv)
+{
+ int have_waiters;
+ EnterCriticalSection(&cv->waiters_count_lock);
+ have_waiters = cv->waiters_count > 0;
+ LeaveCriticalSection(&cv->waiters_count_lock);
+ if (have_waiters)
+ SetEvent (cv->events_[C_BROADCAST]);
+}
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_usrreq.c b/netwerk/sctp/src/netinet/sctp_usrreq.c
new file mode 100644
index 0000000000..df4424e2fb
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_usrreq.c
@@ -0,0 +1,9039 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/proc.h>
+#endif
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_var.h>
+#ifdef INET6
+#include <netinet6/sctp6_var.h>
+#endif
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_bsd_addr.h>
+#if defined(__Userspace__)
+#include <netinet/sctp_callout.h>
+#else
+#include <netinet/udp.h>
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/eventhandler.h>
+#endif
+#if defined(HAVE_SCTP_PEELOFF_SOCKOPT)
+#include <netinet/sctp_peeloff.h>
+#endif /* HAVE_SCTP_PEELOFF_SOCKOPT */
+
+extern const struct sctp_cc_functions sctp_cc_functions[];
+extern const struct sctp_ss_functions sctp_ss_functions[];
+
+#if defined(__Userspace__)
+void
+sctp_init(uint16_t port,
+ int (*conn_output)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df),
+ void (*debug_printf)(const char *format, ...), int start_threads)
+#elif defined(__APPLE__) && (!defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) &&!defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION))
+void
+sctp_init(struct protosw *pp SCTP_UNUSED, struct domain *dp SCTP_UNUSED)
+#elif defined(__FreeBSD__)
+static void
+sctp_init(void *arg SCTP_UNUSED)
+#else
+void
+sctp_init(void)
+#endif
+{
+#if !defined(__Userspace__)
+ u_long sb_max_adj;
+
+#else
+ init_random();
+#endif
+ /* Initialize and modify the sysctled variables */
+ sctp_init_sysctls();
+#if defined(__Userspace__)
+ SCTP_BASE_SYSCTL(sctp_udp_tunneling_port) = port;
+#else
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sb_max_adj = (u_long)((u_quad_t) (sb_max) * MCLBYTES / (MSIZE + MCLBYTES));
+ SCTP_BASE_SYSCTL(sctp_sendspace) = sb_max_adj;
+#else
+ if ((nmbclusters / 8) > SCTP_ASOC_MAX_CHUNKS_ON_QUEUE)
+ SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue) = (nmbclusters / 8);
+ /*
+ * Allow a user to take no more than 1/2 the number of clusters or
+ * the SB_MAX, whichever is smaller, for the send window.
+ */
+ sb_max_adj = (u_long)((u_quad_t) (SB_MAX) * MCLBYTES / (MSIZE + MCLBYTES));
+ SCTP_BASE_SYSCTL(sctp_sendspace) = min(sb_max_adj,
+ (((uint32_t)nmbclusters / 2) * MCLBYTES));
+#endif
+ /*
+ * Now for the recv window, should we take the same amount? or
+ * should I do 1/2 the SB_MAX instead in the SB_MAX min above. For
+ * now I will just copy.
+ */
+ SCTP_BASE_SYSCTL(sctp_recvspace) = SCTP_BASE_SYSCTL(sctp_sendspace);
+#endif
+ SCTP_BASE_VAR(first_time) = 0;
+ SCTP_BASE_VAR(sctp_pcb_initialized) = 0;
+#if defined(__Userspace__)
+#if !defined(_WIN32)
+#if defined(INET) || defined(INET6)
+ SCTP_BASE_VAR(userspace_route) = -1;
+#endif
+#endif
+#ifdef INET
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+#endif
+#ifdef INET6
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+#endif
+ SCTP_BASE_VAR(timer_thread_should_exit) = 0;
+ SCTP_BASE_VAR(conn_output) = conn_output;
+ SCTP_BASE_VAR(debug_printf) = debug_printf;
+ SCTP_BASE_VAR(crc32c_offloaded) = 0;
+ SCTP_BASE_VAR(iterator_thread_started) = 0;
+ SCTP_BASE_VAR(timer_thread_started) = 0;
+#endif
+#if defined(__Userspace__)
+ sctp_pcb_init(start_threads);
+ if (start_threads) {
+ sctp_start_timer_thread();
+ }
+#else
+ sctp_pcb_init();
+#endif
+#if defined(SCTP_PACKET_LOGGING)
+ SCTP_BASE_VAR(packet_log_writers) = 0;
+ SCTP_BASE_VAR(packet_log_end) = 0;
+ memset(&SCTP_BASE_VAR(packet_log_buffer), 0, SCTP_PACKET_LOG_SIZE);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_BASE_VAR(sctp_main_timer_ticks) = 0;
+ sctp_start_main_timer();
+ timeout(sctp_delayed_startup, NULL, 1);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_BASE_VAR(eh_tag) = EVENTHANDLER_REGISTER(rt_addrmsg,
+ sctp_addr_change_event_handler, NULL, EVENTHANDLER_PRI_FIRST);
+#endif
+}
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+VNET_SYSINIT(sctp_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD, sctp_init, NULL);
+
+#ifdef VIMAGE
+static void
+sctp_finish(void *unused __unused)
+{
+ EVENTHANDLER_DEREGISTER(rt_addrmsg, SCTP_BASE_VAR(eh_tag));
+ sctp_pcb_finish();
+}
+VNET_SYSUNINIT(sctp, SI_SUB_PROTO_DOMAIN, SI_ORDER_FOURTH, sctp_finish, NULL);
+#endif
+#else
+void
+sctp_finish(void)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ untimeout(sctp_delayed_startup, NULL);
+ sctp_over_udp_stop();
+ sctp_address_monitor_stop();
+ sctp_stop_main_timer();
+#endif
+#if defined(__Userspace__)
+#if defined(INET) || defined(INET6)
+ recv_thread_destroy();
+#endif
+ sctp_stop_timer_thread();
+#endif
+ sctp_pcb_finish();
+#if defined(_WIN32) && !defined(__Userspace__)
+ sctp_finish_sysctls();
+#endif
+#if defined(__Userspace__)
+ finish_random();
+#endif
+}
+#endif
+
+void
+sctp_pathmtu_adjustment(struct sctp_tcb *stcb, uint32_t mtu, bool resend)
+{
+ struct sctp_association *asoc;
+ struct sctp_tmit_chunk *chk;
+ uint32_t overhead;
+
+ asoc = &stcb->asoc;
+ KASSERT(mtu < asoc->smallest_mtu,
+ ("Currently only reducing association MTU %u supported (MTU %u)",
+ asoc->smallest_mtu, mtu));
+ asoc->smallest_mtu = mtu;
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ overhead = SCTP_MIN_OVERHEAD;
+ } else {
+#if defined(__Userspace__)
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ overhead = sizeof(struct sctphdr);
+ } else {
+ overhead = SCTP_MIN_V4_OVERHEAD;
+ }
+#else
+ overhead = SCTP_MIN_V4_OVERHEAD;
+#endif
+ }
+ if (asoc->idata_supported) {
+ if (sctp_auth_is_required_chunk(SCTP_IDATA, asoc->peer_auth_chunks)) {
+ overhead += sctp_get_auth_chunk_len(asoc->peer_hmac_id);
+ }
+ } else {
+ if (sctp_auth_is_required_chunk(SCTP_DATA, asoc->peer_auth_chunks)) {
+ overhead += sctp_get_auth_chunk_len(asoc->peer_hmac_id);
+ }
+ }
+ KASSERT(overhead % 4 == 0,
+ ("overhead (%u) not a multiple of 4", overhead));
+ TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) {
+ if (((uint32_t)chk->send_size + overhead) > mtu) {
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ }
+ }
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ if (((uint32_t)chk->send_size + overhead) > mtu) {
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ if (resend && chk->sent < SCTP_DATAGRAM_RESEND) {
+ /*
+ * If requested, mark the chunk for immediate
+ * resend, since we sent it being too big.
+ */
+ sctp_flight_size_decrease(chk);
+ sctp_total_flight_decrease(stcb, chk);
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ sctp_ucount_incr(asoc->sent_queue_retran_cnt);
+ chk->rec.data.doing_fast_retransmit = 0;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FLIGHT_LOGGING_ENABLE) {
+ sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_PMTU,
+ chk->whoTo->flight_size,
+ chk->book_size,
+ (uint32_t)(uintptr_t)chk->whoTo,
+ chk->rec.data.tsn);
+ }
+ /* Clear any time, so NO RTT is being done. */
+ if (chk->do_rtt == 1) {
+ chk->do_rtt = 0;
+ chk->whoTo->rto_needed = 1;
+ }
+ }
+ }
+ }
+}
+
+#ifdef INET
+#if !defined(__Userspace__)
+void
+sctp_notify(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ uint8_t icmp_type,
+ uint8_t icmp_code,
+ uint16_t ip_len,
+ uint32_t next_mtu)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+ int timer_stopped;
+
+ if (icmp_type != ICMP_UNREACH) {
+ /* We only care about unreachable */
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ if ((icmp_code == ICMP_UNREACH_NET) ||
+ (icmp_code == ICMP_UNREACH_HOST) ||
+ (icmp_code == ICMP_UNREACH_NET_UNKNOWN) ||
+ (icmp_code == ICMP_UNREACH_HOST_UNKNOWN) ||
+ (icmp_code == ICMP_UNREACH_ISOLATED) ||
+ (icmp_code == ICMP_UNREACH_NET_PROHIB) ||
+ (icmp_code == ICMP_UNREACH_HOST_PROHIB) ||
+#if defined(__NetBSD__)
+ (icmp_code == ICMP_UNREACH_ADMIN_PROHIBIT)) {
+#else
+ (icmp_code == ICMP_UNREACH_FILTER_PROHIB)) {
+#endif
+ /* Mark the net unreachable. */
+ if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ /* OK, that destination is NOT reachable. */
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ net->dest_state &= ~SCTP_ADDR_PF;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN,
+ stcb, 0,
+ (void *)net, SCTP_SO_NOT_LOCKED);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else if ((icmp_code == ICMP_UNREACH_PROTOCOL) ||
+ (icmp_code == ICMP_UNREACH_PORT)) {
+ /* Treat it like an ABORT. */
+ sctp_abort_notification(stcb, true, false, 0, NULL, SCTP_SO_NOT_LOCKED);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_2);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+ /* SCTP_TCB_UNLOCK(stcb); MT: I think this is not needed.*/
+#endif
+ /* no need to unlock here, since the TCB is gone */
+ } else if (icmp_code == ICMP_UNREACH_NEEDFRAG) {
+ if (net->dest_state & SCTP_ADDR_NO_PMTUD) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ /* Find the next (smaller) MTU */
+ if (next_mtu == 0) {
+ /*
+ * Old type router that does not tell us what the next
+ * MTU is.
+ * Rats we will have to guess (in a educated fashion
+ * of course).
+ */
+ next_mtu = sctp_get_prev_mtu(ip_len);
+ }
+ /* Stop the PMTU timer. */
+ if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) {
+ timer_stopped = 1;
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_1);
+ } else {
+ timer_stopped = 0;
+ }
+ /* Update the path MTU. */
+ if (net->port) {
+ next_mtu -= sizeof(struct udphdr);
+ }
+ if (net->mtu > next_mtu) {
+ net->mtu = next_mtu;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net->port) {
+ sctp_hc_set_mtu(&net->ro._l_addr, inp->fibnum, next_mtu + sizeof(struct udphdr));
+ } else {
+ sctp_hc_set_mtu(&net->ro._l_addr, inp->fibnum, next_mtu);
+ }
+#endif
+ }
+ /* Update the association MTU */
+ if (stcb->asoc.smallest_mtu > next_mtu) {
+ sctp_pathmtu_adjustment(stcb, next_mtu, true);
+ }
+ /* Finally, start the PMTU timer if it was running before. */
+ if (timer_stopped) {
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+}
+#endif
+
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+void sctp_ctlinput(struct icmp *icmp)
+{
+ struct ip *inner_ip, *outer_ip;
+ struct sctphdr *sh;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sctp_init_chunk *ch;
+ struct sockaddr_in src, dst;
+
+ if (icmp_errmap(icmp) == 0)
+ return;
+
+ outer_ip = (struct ip *)((caddr_t)icmp - sizeof(struct ip));
+ inner_ip = &icmp->icmp_ip;
+ sh = (struct sctphdr *)((caddr_t)inner_ip + (inner_ip->ip_hl << 2));
+ memset(&src, 0, sizeof(struct sockaddr_in));
+ src.sin_family = AF_INET;
+ src.sin_len = sizeof(struct sockaddr_in);
+ src.sin_port = sh->src_port;
+ src.sin_addr = inner_ip->ip_src;
+ memset(&dst, 0, sizeof(struct sockaddr_in));
+ dst.sin_family = AF_INET;
+ dst.sin_len = sizeof(struct sockaddr_in);
+ dst.sin_port = sh->dest_port;
+ dst.sin_addr = inner_ip->ip_dst;
+ /*
+ * 'dst' holds the dest of the packet that failed to be sent.
+ * 'src' holds our local endpoint address. Thus we reverse
+ * the dst and the src in the lookup.
+ */
+ inp = NULL;
+ net = NULL;
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
+ (struct sockaddr *)&src,
+ &inp, &net, 1,
+ SCTP_DEFAULT_VRFID);
+ if ((stcb != NULL) &&
+ (net != NULL) &&
+ (inp != NULL)) {
+ /* Check the verification tag */
+ if (ntohl(sh->v_tag) != 0) {
+ /*
+ * This must be the verification tag used for
+ * sending out packets. We don't consider
+ * packets reflecting the verification tag.
+ */
+ if (ntohl(sh->v_tag) != stcb->asoc.peer_vtag) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ if (ntohs(outer_ip->ip_len) >=
+ sizeof(struct ip) +
+ 8 + (inner_ip->ip_hl << 2) + 20) {
+ /*
+ * In this case we can check if we
+ * got an INIT chunk and if the
+ * initiate tag matches.
+ */
+ ch = (struct sctp_init_chunk *)(sh + 1);
+ if ((ch->ch.chunk_type != SCTP_INITIATION) ||
+ (ntohl(ch->init.initiate_tag) != stcb->asoc.my_vtag)) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ }
+ sctp_notify(inp, stcb, net,
+ icmp->icmp_type,
+ icmp->icmp_code,
+ ntohs(inner_ip->ip_len),
+ (uint32_t)ntohs(icmp->icmp_nextmtu));
+ } else {
+ if ((stcb == NULL) && (inp != NULL)) {
+ /* reduce ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+}
+#else
+void
+#if defined(__APPLE__) && !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION) && !defined(APPLE_ELCAPITAN)
+sctp_ctlinput(int cmd, struct sockaddr *sa, void *vip, struct ifnet *ifp SCTP_UNUSED)
+#else
+sctp_ctlinput(int cmd, struct sockaddr *sa, void *vip)
+#endif
+{
+ struct ip *inner_ip;
+ struct sctphdr *sh;
+ struct icmp *icmp;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sockaddr_in src, dst;
+
+#if !defined(__FreeBSD__) && !defined(__Userspace__)
+ if (sa->sa_family != AF_INET ||
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr == INADDR_ANY) {
+ return;
+ }
+#endif
+ if (PRC_IS_REDIRECT(cmd)) {
+ vip = NULL;
+ } else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0) {
+ return;
+ }
+ if (vip != NULL) {
+ inner_ip = (struct ip *)vip;
+ icmp = (struct icmp *)((caddr_t)inner_ip -
+ (sizeof(struct icmp) - sizeof(struct ip)));
+ sh = (struct sctphdr *)((caddr_t)inner_ip + (inner_ip->ip_hl << 2));
+ memset(&src, 0, sizeof(struct sockaddr_in));
+ src.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ src.sin_len = sizeof(struct sockaddr_in);
+#endif
+ src.sin_port = sh->src_port;
+ src.sin_addr = inner_ip->ip_src;
+ memset(&dst, 0, sizeof(struct sockaddr_in));
+ dst.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ dst.sin_len = sizeof(struct sockaddr_in);
+#endif
+ dst.sin_port = sh->dest_port;
+ dst.sin_addr = inner_ip->ip_dst;
+ /*
+ * 'dst' holds the dest of the packet that failed to be sent.
+ * 'src' holds our local endpoint address. Thus we reverse
+ * the dst and the src in the lookup.
+ */
+ inp = NULL;
+ net = NULL;
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
+ (struct sockaddr *)&src,
+ &inp, &net, 1,
+ SCTP_DEFAULT_VRFID);
+ if ((stcb != NULL) &&
+ (net != NULL) &&
+ (inp != NULL)) {
+ /* Check the verification tag */
+ if (ntohl(sh->v_tag) != 0) {
+ /*
+ * This must be the verification tag used for
+ * sending out packets. We don't consider
+ * packets reflecting the verification tag.
+ */
+ if (ntohl(sh->v_tag) != stcb->asoc.peer_vtag) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ sctp_notify(inp, stcb, net,
+ icmp->icmp_type,
+ icmp->icmp_code,
+ inner_ip->ip_len,
+ (uint32_t)ntohs(icmp->icmp_nextmtu));
+#if defined(__Userspace__)
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (stcb->sctp_socket != NULL)) {
+ struct socket *upcall_socket;
+
+ upcall_socket = stcb->sctp_socket;
+ SOCK_LOCK(upcall_socket);
+ soref(upcall_socket);
+ SOCK_UNLOCK(upcall_socket);
+ if ((upcall_socket->so_upcall != NULL) &&
+ (upcall_socket->so_error != 0)) {
+ (*upcall_socket->so_upcall)(upcall_socket, upcall_socket->so_upcallarg, M_NOWAIT);
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(upcall_socket);
+ sorele(upcall_socket);
+ }
+#endif
+ } else {
+ if ((stcb == NULL) && (inp != NULL)) {
+ /* reduce ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+ }
+ return;
+}
+#endif
+#endif
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static int
+sctp_getcred(SYSCTL_HANDLER_ARGS)
+{
+ struct xucred xuc;
+ struct sockaddr_in addrs[2];
+ struct sctp_inpcb *inp;
+ struct sctp_nets *net;
+ struct sctp_tcb *stcb;
+ int error;
+ uint32_t vrf_id;
+
+ /* FIX, for non-bsd is this right? */
+ vrf_id = SCTP_DEFAULT_VRFID;
+
+ error = priv_check(req->td, PRIV_NETINET_GETCRED);
+
+ if (error)
+ return (error);
+
+ error = SYSCTL_IN(req, addrs, sizeof(addrs));
+ if (error)
+ return (error);
+
+ stcb = sctp_findassociation_addr_sa(sintosa(&addrs[1]),
+ sintosa(&addrs[0]),
+ &inp, &net, 1, vrf_id);
+ if (stcb == NULL || inp == NULL || inp->sctp_socket == NULL) {
+ if ((inp != NULL) && (stcb == NULL)) {
+ /* reduce ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ goto cred_can_cont;
+ }
+
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ goto out;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ /* We use the write lock here, only
+ * since in the error leg we need it.
+ * If we used RLOCK, then we would have
+ * to wlock/decr/unlock/rlock. Which
+ * in theory could create a hole. Better
+ * to use higher wlock.
+ */
+ SCTP_INP_WLOCK(inp);
+ cred_can_cont:
+ error = cr_canseesocket(req->td->td_ucred, inp->sctp_socket);
+ if (error) {
+ SCTP_INP_WUNLOCK(inp);
+ goto out;
+ }
+ cru2x(inp->sctp_socket->so_cred, &xuc);
+ SCTP_INP_WUNLOCK(inp);
+ error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred));
+out:
+ return (error);
+}
+
+SYSCTL_PROC(_net_inet_sctp, OID_AUTO, getcred,
+ CTLTYPE_OPAQUE | CTLFLAG_RW | CTLFLAG_NEEDGIANT,
+ 0, 0, sctp_getcred, "S,ucred",
+ "Get the ucred of a SCTP connection");
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+void
+#else
+int
+#endif
+sctp_abort(struct socket *so)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ return;
+#else
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+#endif
+ }
+
+ SCTP_INP_WLOCK(inp);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 17);
+#endif
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0)) {
+ inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP;
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 16);
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ SOCK_LOCK(so);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ KASSERT(!SOLISTENING(so),
+ ("sctp_abort: called on listening socket %p", so));
+#endif
+ SCTP_SB_CLEAR(so->so_snd);
+ SCTP_SB_CLEAR(so->so_rcv);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so->so_usecount--;
+#else
+ /* Now null out the reference, we are completely detached. */
+ so->so_pcb = NULL;
+#endif
+ SOCK_UNLOCK(so);
+ } else {
+ SCTP_INP_WUNLOCK(inp);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#else
+ return (0);
+#endif
+}
+
+#ifdef INET
+#if defined(__Userspace__)
+int
+#else
+static int
+#endif
+#if defined(__Userspace__)
+sctp_attach(struct socket *so, int proto SCTP_UNUSED, uint32_t vrf_id)
+#elif defined(__FreeBSD__)
+sctp_attach(struct socket *so, int proto SCTP_UNUSED, struct thread *p SCTP_UNUSED)
+#elif defined(_WIN32)
+sctp_attach(struct socket *so, int proto SCTP_UNUSED, PKTHREAD p SCTP_UNUSED)
+#else
+sctp_attach(struct socket *so, int proto SCTP_UNUSED, struct proc *p SCTP_UNUSED)
+#endif
+{
+ struct sctp_inpcb *inp;
+ struct inpcb *ip_inp;
+ int error;
+#if !defined(__Userspace__)
+ uint32_t vrf_id = SCTP_DEFAULT_VRFID;
+#endif
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp != NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) {
+ error = SCTP_SORESERVE(so, SCTP_BASE_SYSCTL(sctp_sendspace), SCTP_BASE_SYSCTL(sctp_recvspace));
+ if (error) {
+ return (error);
+ }
+ }
+ error = sctp_inpcb_alloc(so, vrf_id);
+ if (error) {
+ return (error);
+ }
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUND_V6; /* I'm not v6! */
+ ip_inp = &inp->ip_inp.inp;
+ ip_inp->inp_vflag |= INP_IPV4;
+ ip_inp->inp_ip_ttl = MODULE_GLOBAL(ip_defttl);
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+}
+
+#if defined(__Userspace__)
+int
+sctp_bind(struct socket *so, struct sockaddr *addr) {
+ void *p = NULL;
+#elif defined(__FreeBSD__)
+static int
+sctp_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#elif defined(__APPLE__)
+static int
+sctp_bind(struct socket *so, struct sockaddr *addr, struct proc *p) {
+#elif defined(_WIN32)
+static int
+sctp_bind(struct socket *so, struct sockaddr *addr, PKTHREAD p) {
+#else
+static int
+sctp_bind(struct socket *so, struct mbuf *nam, struct proc *p)
+{
+ struct sockaddr *addr = nam ? mtod(nam, struct sockaddr *): NULL;
+
+#endif
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (addr != NULL) {
+#ifdef HAVE_SA_LEN
+ if ((addr->sa_family != AF_INET) ||
+ (addr->sa_len != sizeof(struct sockaddr_in))) {
+#else
+ if (addr->sa_family != AF_INET) {
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ }
+ return (sctp_inpcb_bind(so, addr, NULL, p));
+}
+
+#endif
+#if defined(__Userspace__)
+
+int
+sctpconn_attach(struct socket *so, int proto SCTP_UNUSED, uint32_t vrf_id)
+{
+ struct sctp_inpcb *inp;
+ struct inpcb *ip_inp;
+ int error;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp != NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) {
+ error = SCTP_SORESERVE(so, SCTP_BASE_SYSCTL(sctp_sendspace), SCTP_BASE_SYSCTL(sctp_recvspace));
+ if (error) {
+ return (error);
+ }
+ }
+ error = sctp_inpcb_alloc(so, vrf_id);
+ if (error) {
+ return (error);
+ }
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUND_V6;
+ inp->sctp_flags |= SCTP_PCB_FLAGS_BOUND_CONN;
+ ip_inp = &inp->ip_inp.inp;
+ ip_inp->inp_vflag |= INP_CONN;
+ ip_inp->inp_ip_ttl = MODULE_GLOBAL(ip_defttl);
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+}
+
+int
+sctpconn_bind(struct socket *so, struct sockaddr *addr)
+{
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (addr != NULL) {
+#ifdef HAVE_SA_LEN
+ if ((addr->sa_family != AF_CONN) ||
+ (addr->sa_len != sizeof(struct sockaddr_conn))) {
+#else
+ if (addr->sa_family != AF_CONN) {
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ }
+ return (sctp_inpcb_bind(so, addr, NULL, NULL));
+}
+
+#endif
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+void
+sctp_close(struct socket *so)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL)
+ return;
+
+ /* Inform all the lower layer assoc that we
+ * are done.
+ */
+ SCTP_INP_WLOCK(inp);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 17);
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+ inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP;
+#if defined(__Userspace__)
+ if (((so->so_options & SCTP_SO_LINGER) && (so->so_linger == 0)) ||
+#else
+ if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) ||
+#endif
+ (SCTP_SBAVAIL(&so->so_rcv) > 0)) {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 13);
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ } else {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 14);
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ }
+ /* The socket is now detached, no matter what
+ * the state of the SCTP association.
+ */
+ SOCK_LOCK(so);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (!SOLISTENING(so)) {
+ SCTP_SB_CLEAR(so->so_snd);
+ SCTP_SB_CLEAR(so->so_rcv);
+ }
+#else
+ SCTP_SB_CLEAR(so->so_snd);
+ SCTP_SB_CLEAR(so->so_rcv);
+#endif
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+ /* Now null out the reference, we are completely detached. */
+ so->so_pcb = NULL;
+#endif
+ SOCK_UNLOCK(so);
+ } else {
+ SCTP_INP_WUNLOCK(inp);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+}
+#else
+int
+sctp_detach(struct socket *so)
+{
+ struct sctp_inpcb *inp;
+ uint32_t flags;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ sctp_must_try_again:
+ flags = inp->sctp_flags;
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 17);
+#endif
+ if (((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (atomic_cmpset_int(&inp->sctp_flags, flags, (flags | SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP)))) {
+ if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) ||
+ (SCTP_SBAVAIL(&so->so_rcv) > 0)) {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 13);
+#endif
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ } else {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 13);
+#endif
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ }
+ /* The socket is now detached, no matter what
+ * the state of the SCTP association.
+ */
+ SCTP_SB_CLEAR(so->so_snd);
+ /* same for the rcv ones, they are only
+ * here for the accounting/select.
+ */
+ SCTP_SB_CLEAR(so->so_rcv);
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+ /* Now disconnect */
+ so->so_pcb = NULL;
+#endif
+ } else {
+ flags = inp->sctp_flags;
+ if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+ goto sctp_must_try_again;
+ }
+ }
+ return (0);
+}
+#endif
+
+#if defined(__Userspace__)
+/* __Userspace__ is not calling sctp_sendm */
+#endif
+#if !(defined(_WIN32) && !defined(__Userspace__))
+int
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct thread *p);
+
+#else
+sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct proc *p);
+
+#endif
+int
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct thread *p)
+{
+#else
+sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct proc *p)
+{
+#endif
+ struct sctp_inpcb *inp;
+ int error;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ if (control) {
+ sctp_m_freem(control);
+ control = NULL;
+ }
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ sctp_m_freem(m);
+ return (EINVAL);
+ }
+ /* Got to have an to address if we are NOT a connected socket */
+ if ((addr == NULL) &&
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE))) {
+ goto connected_type;
+ }
+
+ error = 0;
+ if (addr == NULL) {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EDESTADDRREQ);
+ error = EDESTADDRREQ;
+ } else if (addr->sa_family != AF_INET) {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+#if defined(HAVE_SA_LEN)
+ } else if (addr->sa_len != sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+#endif
+ }
+ if (error != 0) {
+ sctp_m_freem(m);
+ if (control) {
+ sctp_m_freem(control);
+ control = NULL;
+ }
+ return (error);
+ }
+connected_type:
+ /* now what about control */
+ if (control) {
+ if (inp->control) {
+ sctp_m_freem(inp->control);
+ inp->control = NULL;
+ }
+ inp->control = control;
+ }
+ /* Place the data */
+ if (inp->pkt) {
+ SCTP_BUF_NEXT(inp->pkt_last) = m;
+ inp->pkt_last = m;
+ } else {
+ inp->pkt_last = inp->pkt = m;
+ }
+ if (
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ /* FreeBSD uses a flag passed */
+ ((flags & PRUS_MORETOCOME) == 0)
+#else
+ 1 /* Open BSD does not have any "more to come"
+ * indication */
+#endif
+ ) {
+ /*
+ * note with the current version this code will only be used
+ * by OpenBSD-- NetBSD, FreeBSD, and MacOS have methods for
+ * re-defining sosend to use the sctp_sosend. One can
+ * optionally switch back to this code (by changing back the
+ * definitions) but this is not advisable. This code is used
+ * by FreeBSD when sending a file with sendfile() though.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ int ret;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ inp->pkt = NULL;
+ inp->control = NULL;
+ return (ret);
+ } else {
+ return (0);
+ }
+}
+#endif
+
+int
+sctp_disconnect(struct socket *so)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_inpcb *inp;
+ struct sctp_association *asoc;
+ struct sctp_tcb *stcb;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTCONN);
+ return (ENOTCONN);
+ }
+ SCTP_INP_RLOCK(inp);
+ KASSERT(inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE ||
+ inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL,
+ ("Not a one-to-one style socket"));
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb == NULL) {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTCONN);
+ return (ENOTCONN);
+ }
+ SCTP_TCB_LOCK(stcb);
+ asoc = &stcb->asoc;
+ if (asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ /* We are about to be freed, out of here */
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ return (0);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+#if defined(__Userspace__)
+ if (((so->so_options & SCTP_SO_LINGER) && (so->so_linger == 0)) ||
+ (SCTP_SBAVAIL(&so->so_rcv) > 0)) {
+#else
+ if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) ||
+ (SCTP_SBAVAIL(&so->so_rcv) > 0)) {
+#endif
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT) {
+ /* Left with Data unread */
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ sctp_send_abort_tcb(stcb, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_3);
+ /* No unlock tcb assoc is gone */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return (0);
+ }
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->stream_queue_cnt == 0)) {
+ /* there is nothing queued to send, so done */
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ goto abort_anyway;
+ }
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ /* only send SHUTDOWN 1st time thru */
+ struct sctp_nets *netp;
+
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ if (stcb->asoc.alternate) {
+ netp = stcb->asoc.alternate;
+ } else {
+ netp = stcb->asoc.primary_destination;
+ }
+ sctp_send_shutdown(stcb,netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN,
+ stcb->sctp_ep, stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
+ }
+ } else {
+ /*
+ * we still got (or just got) data to send,
+ * so set SHUTDOWN_PENDING
+ */
+ /*
+ * XXX sockets draft says that SCTP_EOF
+ * should be sent with no data. currently,
+ * we will allow user data to be sent first
+ * and move to SHUTDOWN-PENDING
+ */
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING);
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+ abort_anyway:
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_USRREQ + SCTP_LOC_4;
+ sctp_send_abort_tcb(stcb, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_5);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return (0);
+ } else {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
+ }
+ }
+ soisdisconnecting(so);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ return (0);
+}
+
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+int
+sctp_flush(struct socket *so, int how)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_tcb *stcb;
+ struct sctp_queued_to_read *control, *ncontrol;
+ struct sctp_inpcb *inp;
+ struct mbuf *m, *op_err;
+ bool need_to_abort = false;
+
+ /*
+ * For 1-to-1 style sockets, flush the read queue and trigger an
+ * ungraceful shutdown of the association, if and only if user messages
+ * are lost. Loosing notifications does not need to be signalled to the
+ * peer.
+ */
+ if (how == PRU_FLUSH_WR) {
+ /* This function is only relevant for the read directions. */
+ return (0);
+ }
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ SCTP_INP_WLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) {
+ /* For 1-to-many style sockets this function does nothing. */
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+ }
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ SCTP_INP_READ_LOCK(inp);
+ inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_CANT_READ;
+ SOCK_LOCK(so);
+ TAILQ_FOREACH_SAFE(control, &inp->read_queue, next, ncontrol) {
+ if ((control->spec_flags & M_NOTIFICATION) == 0) {
+ need_to_abort = true;
+ }
+ TAILQ_REMOVE(&inp->read_queue, control, next);
+ control->on_read_q = 0;
+ for (m = control->data; m; m = SCTP_BUF_NEXT(m)) {
+ sctp_sbfree(control, control->stcb, &so->so_rcv, m);
+ }
+ if (control->on_strm_q == 0) {
+ sctp_free_remote_addr(control->whoFrom);
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+ } else {
+ stcb->asoc.size_on_all_streams += control->length;
+ }
+ }
+ SOCK_UNLOCK(so);
+ SCTP_INP_READ_UNLOCK(inp);
+ if (need_to_abort && (stcb != NULL)) {
+ inp->last_abort_code = SCTP_FROM_SCTP_USRREQ + SCTP_LOC_6;
+ SCTP_INP_WUNLOCK(inp);
+ op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return (ECONNABORTED);
+ }
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+}
+#endif
+
+int
+sctp_shutdown(struct socket *so)
+{
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ SCTP_INP_RLOCK(inp);
+ /* For UDP model this is a invalid call */
+ if (!((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) {
+ /* Restore the flags that the soshutdown took away. */
+#if (defined(__FreeBSD__) || defined(_WIN32)) && !defined(__Userspace__)
+ SOCKBUF_LOCK(&so->so_rcv);
+ so->so_rcv.sb_state &= ~SBS_CANTRCVMORE;
+ SOCKBUF_UNLOCK(&so->so_rcv);
+#else
+ SOCK_LOCK(so);
+ so->so_state &= ~SS_CANTRCVMORE;
+ SOCK_UNLOCK(so);
+#endif
+ /* This proc will wakeup for read and do nothing (I hope) */
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ return (EOPNOTSUPP);
+ } else {
+ /*
+ * Ok, if we reach here its the TCP model and it is either
+ * a SHUT_WR or SHUT_RDWR.
+ * This means we put the shutdown flag against it.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_tcb *stcb;
+ struct sctp_association *asoc;
+ struct sctp_nets *netp;
+
+ if ((so->so_state &
+ (SS_ISCONNECTED|SS_ISCONNECTING|SS_ISDISCONNECTING)) == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ return (ENOTCONN);
+ }
+ socantsendmore(so);
+
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb == NULL) {
+ /*
+ * Ok, we hit the case that the shutdown call was
+ * made after an abort or something. Nothing to do
+ * now.
+ */
+ SCTP_INP_RUNLOCK(inp);
+ return (0);
+ }
+ SCTP_TCB_LOCK(stcb);
+ asoc = &stcb->asoc;
+ if (asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ return (0);
+ }
+ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_ECHOED) &&
+ (SCTP_GET_STATE(stcb) != SCTP_STATE_OPEN)) {
+ /* If we are not in or before ESTABLISHED, there is
+ * no protocol action required.
+ */
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ return (0);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ if (stcb->asoc.alternate) {
+ netp = stcb->asoc.alternate;
+ } else {
+ netp = stcb->asoc.primary_destination;
+ }
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) &&
+ TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->stream_queue_cnt == 0)) {
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ goto abort_anyway;
+ }
+ /* there is nothing queued to send, so I'm done... */
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(stcb);
+ sctp_send_shutdown(stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN,
+ stcb->sctp_ep, stcb, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ } else {
+ /*
+ * We still got (or just got) data to send, so set
+ * SHUTDOWN_PENDING.
+ */
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING);
+ if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+ abort_anyway:
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_USRREQ + SCTP_LOC_6;
+ SCTP_INP_RUNLOCK(inp);
+ sctp_abort_an_association(stcb->sctp_ep, stcb,
+ op_err, false, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return (0);
+ }
+ }
+ /* XXX: Why do this in the case where we have still data queued? */
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return (0);
+ }
+}
+
+/*
+ * copies a "user" presentable address and removes embedded scope, etc.
+ * returns 0 on success, 1 on error
+ */
+static uint32_t
+sctp_fill_user_address(struct sockaddr *dst, struct sockaddr *src)
+{
+#ifdef INET6
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+ struct sockaddr_in6 lsa6;
+
+ src = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)src,
+ &lsa6);
+#endif
+#endif
+#ifdef HAVE_SA_LEN
+ memcpy(dst, src, src->sa_len);
+#else
+ switch (src->sa_family) {
+#ifdef INET
+ case AF_INET:
+ memcpy(dst, src, sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memcpy(dst, src, sizeof(struct sockaddr_in6));
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(dst, src, sizeof(struct sockaddr_conn));
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+#endif
+ return (0);
+}
+
+static size_t
+sctp_fill_up_addresses_vrf(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ size_t limit,
+ struct sockaddr *addr,
+ uint32_t vrf_id)
+{
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+ size_t actual;
+ int loopback_scope;
+#if defined(INET)
+ int ipv4_local_scope, ipv4_addr_legal;
+#endif
+#if defined(INET6)
+ int local_scope, site_scope, ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ int conn_addr_legal;
+#endif
+ struct sctp_vrf *vrf;
+
+ SCTP_IPI_ADDR_LOCK_ASSERT();
+ actual = 0;
+ if (limit == 0)
+ return (actual);
+
+ if (stcb) {
+ /* Turn on all the appropriate scope */
+ loopback_scope = stcb->asoc.scope.loopback_scope;
+#if defined(INET)
+ ipv4_local_scope = stcb->asoc.scope.ipv4_local_scope;
+ ipv4_addr_legal = stcb->asoc.scope.ipv4_addr_legal;
+#endif
+#if defined(INET6)
+ local_scope = stcb->asoc.scope.local_scope;
+ site_scope = stcb->asoc.scope.site_scope;
+ ipv6_addr_legal = stcb->asoc.scope.ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ conn_addr_legal = stcb->asoc.scope.conn_addr_legal;
+#endif
+ } else {
+ /* Use generic values for endpoints. */
+ loopback_scope = 1;
+#if defined(INET)
+ ipv4_local_scope = 1;
+#endif
+#if defined(INET6)
+ local_scope = 1;
+ site_scope = 1;
+#endif
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+#if defined(INET6)
+ ipv6_addr_legal = 1;
+#endif
+#if defined(INET)
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ ipv4_addr_legal = 0;
+ } else {
+ ipv4_addr_legal = 1;
+ }
+#endif
+#if defined(__Userspace__)
+ conn_addr_legal = 0;
+#endif
+ } else {
+#if defined(INET6)
+ ipv6_addr_legal = 0;
+#endif
+#if defined(__Userspace__)
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ conn_addr_legal = 1;
+#if defined(INET)
+ ipv4_addr_legal = 0;
+#endif
+ } else {
+ conn_addr_legal = 0;
+#if defined(INET)
+ ipv4_addr_legal = 1;
+#endif
+ }
+#else
+#if defined(INET)
+ ipv4_addr_legal = 1;
+#endif
+#endif
+ }
+ }
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ return (0);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if ((loopback_scope == 0) &&
+ SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* Skip loopback if loopback_scope not set */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ if (stcb) {
+ /*
+ * For the BOUND-ALL case, the list
+ * associated with a TCB is Always
+ * considered a reverse list.. i.e.
+ * it lists addresses that are NOT
+ * part of the association. If this
+ * is one of those we must skip it.
+ */
+ if (sctp_is_addr_restricted(stcb,
+ sctp_ifa)) {
+ continue;
+ }
+ }
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (ipv4_addr_legal) {
+ struct sockaddr_in *sin;
+
+ sin = &sctp_ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /*
+ * we skip unspecified
+ * addresses
+ */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if ((ipv4_local_scope == 0) &&
+ (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) {
+ continue;
+ }
+#ifdef INET6
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4)) {
+ if (actual + sizeof(struct sockaddr_in6) > limit) {
+ return (actual);
+ }
+ in6_sin_2_v4mapsin6(sin, (struct sockaddr_in6 *)addr);
+ ((struct sockaddr_in6 *)addr)->sin6_port = inp->sctp_lport;
+ addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_in6));
+ actual += sizeof(struct sockaddr_in6);
+ } else {
+#endif
+ if (actual + sizeof(struct sockaddr_in) > limit) {
+ return (actual);
+ }
+ memcpy(addr, sin, sizeof(struct sockaddr_in));
+ ((struct sockaddr_in *)addr)->sin_port = inp->sctp_lport;
+ addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_in));
+ actual += sizeof(struct sockaddr_in);
+#ifdef INET6
+ }
+#endif
+ } else {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (ipv6_addr_legal) {
+ struct sockaddr_in6 *sin6;
+
+#if defined(SCTP_EMBEDDED_V6_SCOPE) && !defined(SCTP_KAME)
+ struct sockaddr_in6 lsa6;
+#endif
+ sin6 = &sctp_ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /*
+ * we skip unspecified
+ * addresses
+ */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (local_scope == 0)
+ continue;
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+ if (sin6->sin6_scope_id == 0) {
+#ifdef SCTP_KAME
+ if (sa6_recoverscope(sin6) != 0)
+ /*
+ * bad link
+ * local
+ * address
+ */
+ continue;
+#else
+ lsa6 = *sin6;
+ if (in6_recoverscope(&lsa6,
+ &lsa6.sin6_addr,
+ NULL))
+ /*
+ * bad link
+ * local
+ * address
+ */
+ continue;
+ sin6 = &lsa6;
+#endif /* SCTP_KAME */
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ }
+ if ((site_scope == 0) &&
+ (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ if (actual + sizeof(struct sockaddr_in6) > limit) {
+ return (actual);
+ }
+ memcpy(addr, sin6, sizeof(struct sockaddr_in6));
+ ((struct sockaddr_in6 *)addr)->sin6_port = inp->sctp_lport;
+ addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_in6));
+ actual += sizeof(struct sockaddr_in6);
+ } else {
+ continue;
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (conn_addr_legal) {
+ if (actual + sizeof(struct sockaddr_conn) > limit) {
+ return (actual);
+ }
+ memcpy(addr, &sctp_ifa->address.sconn, sizeof(struct sockaddr_conn));
+ ((struct sockaddr_conn *)addr)->sconn_port = inp->sctp_lport;
+ addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_conn));
+ actual += sizeof(struct sockaddr_conn);
+ } else {
+ continue;
+ }
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ }
+ } else {
+ struct sctp_laddr *laddr;
+ size_t sa_len;
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (stcb) {
+ if (sctp_is_addr_restricted(stcb, laddr->ifa)) {
+ continue;
+ }
+ }
+#ifdef HAVE_SA_LEN
+ sa_len = laddr->ifa->address.sa.sa_len;
+#else
+ switch (laddr->ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ sa_len = sizeof(struct sockaddr_in);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa_len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ sa_len = sizeof(struct sockaddr_conn);
+ break;
+#endif
+ default:
+ /* TSNH */
+ sa_len = 0;
+ break;
+ }
+#endif
+ if (actual + sa_len > limit) {
+ return (actual);
+ }
+ if (sctp_fill_user_address(addr, &laddr->ifa->address.sa))
+ continue;
+ switch (laddr->ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ ((struct sockaddr_in *)addr)->sin_port = inp->sctp_lport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ((struct sockaddr_in6 *)addr)->sin6_port = inp->sctp_lport;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ ((struct sockaddr_conn *)addr)->sconn_port = inp->sctp_lport;
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ addr = (struct sockaddr *)((caddr_t)addr + sa_len);
+ actual += sa_len;
+ }
+ }
+ return (actual);
+}
+
+static size_t
+sctp_fill_up_addresses(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ size_t limit,
+ struct sockaddr *addr)
+{
+ size_t size;
+#ifdef SCTP_MVRF
+ uint32_t id;
+#endif
+
+ SCTP_IPI_ADDR_RLOCK();
+#ifdef SCTP_MVRF
+/*
+ * FIX ME: ?? this WILL report duplicate addresses if they appear
+ * in more than one VRF.
+ */
+ /* fill up addresses for all VRFs on the endpoint */
+ size = 0;
+ for (id = 0; (id < inp->num_vrfs) && (size < limit); id++) {
+ size += sctp_fill_up_addresses_vrf(inp, stcb, limit, addr,
+ inp->m_vrf_ids[id]);
+ addr = (struct sockaddr *)((caddr_t)addr + size);
+ }
+#else
+ /* fill up addresses for the endpoint's default vrf */
+ size = sctp_fill_up_addresses_vrf(inp, stcb, limit, addr,
+ inp->def_vrf_id);
+#endif
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (size);
+}
+
+static size_t
+sctp_max_size_addresses_vrf(struct sctp_inpcb *inp, uint32_t vrf_id)
+{
+ struct sctp_vrf *vrf;
+ size_t size;
+
+ /*
+ * In both sub-set bound an bound_all cases we return the size of
+ * the maximum number of addresses that you could get. In reality
+ * the sub-set bound may have an exclusion list for a given TCB or
+ * in the bound-all case a TCB may NOT include the loopback or other
+ * addresses as well.
+ */
+ SCTP_IPI_ADDR_LOCK_ASSERT();
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ return (0);
+ }
+ size = 0;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ /* Count them if they are the right type */
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef INET6
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4))
+ size += sizeof(struct sockaddr_in6);
+ else
+ size += sizeof(struct sockaddr_in);
+#else
+ size += sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ size += sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ size += sizeof(struct sockaddr_conn);
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+ } else {
+ struct sctp_laddr *laddr;
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ switch (laddr->ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef INET6
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4))
+ size += sizeof(struct sockaddr_in6);
+ else
+ size += sizeof(struct sockaddr_in);
+#else
+ size += sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ size += sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ size += sizeof(struct sockaddr_conn);
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+ return (size);
+}
+
+static size_t
+sctp_max_size_addresses(struct sctp_inpcb *inp)
+{
+ size_t size;
+#ifdef SCTP_MVRF
+ int id;
+#endif
+
+ SCTP_IPI_ADDR_RLOCK();
+#ifdef SCTP_MVRF
+/*
+ * FIX ME: ?? this WILL count duplicate addresses if they appear
+ * in more than one VRF.
+ */
+ /* Maximum size of all addresses for all VRFs on the endpoint */
+ size = 0;
+ for (id = 0; id < inp->num_vrfs; id++) {
+ size += sctp_max_size_addresses_vrf(inp, inp->m_vrf_ids[id]);
+ }
+#else
+ /* Maximum size of all addresses for the endpoint's default VRF */
+ size = sctp_max_size_addresses_vrf(inp, inp->def_vrf_id);
+#endif
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (size);
+}
+
+static int
+sctp_do_connect_x(struct socket *so, struct sctp_inpcb *inp, void *optval,
+ size_t optsize, void *p, int delay)
+{
+ int error;
+ int creat_lock_on = 0;
+ struct sctp_tcb *stcb = NULL;
+ struct sockaddr *sa;
+ unsigned int num_v6 = 0, num_v4 = 0, *totaddrp, totaddr;
+ uint32_t vrf_id;
+ sctp_assoc_t *a_id;
+
+ SCTPDBG(SCTP_DEBUG_PCB1, "Connectx called\n");
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) {
+ /* We are already connected AND the TCP model */
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) &&
+ (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_PORTREUSE))) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ SCTP_INP_RLOCK(inp);
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ SCTP_INP_RUNLOCK(inp);
+ }
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ return (EALREADY);
+ }
+ SCTP_INP_INCR_REF(inp);
+ SCTP_ASOC_CREATE_LOCK(inp);
+ creat_lock_on = 1;
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, EFAULT);
+ error = EFAULT;
+ goto out_now;
+ }
+ totaddrp = (unsigned int *)optval;
+ totaddr = *totaddrp;
+ sa = (struct sockaddr *)(totaddrp + 1);
+ error = sctp_connectx_helper_find(inp, sa, totaddr, &num_v4, &num_v6, (unsigned int)(optsize - sizeof(int)));
+ if (error != 0) {
+ /* Already have or am bring up an association */
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ creat_lock_on = 0;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ goto out_now;
+ }
+#ifdef INET6
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) &&
+ (num_v6 > 0)) {
+ error = EINVAL;
+ goto out_now;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ (num_v4 > 0)) {
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ /*
+ * if IPV6_V6ONLY flag, ignore connections destined
+ * to a v4 addr or v4-mapped addr
+ */
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+ }
+#endif /* INET6 */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+ /* Bind a ephemeral port */
+ error = sctp_inpcb_bind(so, NULL, NULL, p);
+ if (error) {
+ goto out_now;
+ }
+ }
+
+ /* FIX ME: do we want to pass in a vrf on the connect call? */
+ vrf_id = inp->def_vrf_id;
+
+ /* We are GOOD to go */
+ stcb = sctp_aloc_assoc_connected(inp, sa, &error, 0, 0, vrf_id,
+ inp->sctp_ep.pre_open_stream_count,
+ inp->sctp_ep.port,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ (struct thread *)p,
+#elif defined(_WIN32) && !defined(__Userspace__)
+ (PKTHREAD)p,
+#else
+ (struct proc *)p,
+#endif
+ SCTP_INITIALIZE_AUTH_PARAMS);
+ if (stcb == NULL) {
+ /* Gak! no memory */
+ goto out_now;
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ /* move to second address */
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in6));
+ break;
+#endif
+ default:
+ break;
+ }
+
+ error = 0;
+ sctp_connectx_helper_add(stcb, sa, (totaddr-1), &error);
+ /* Fill in the return id */
+ if (error) {
+ goto out_now;
+ }
+ a_id = (sctp_assoc_t *)optval;
+ *a_id = sctp_get_associd(stcb);
+
+ if (delay) {
+ /* doing delayed connection */
+ stcb->asoc.delayed_connection = 1;
+ sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, stcb->asoc.primary_destination);
+ } else {
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ out_now:
+ if (creat_lock_on) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ }
+ SCTP_INP_DECR_REF(inp);
+ return (error);
+}
+
+#define SCTP_FIND_STCB(inp, stcb, assoc_id) { \
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||\
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { \
+ SCTP_INP_RLOCK(inp); \
+ stcb = LIST_FIRST(&inp->sctp_asoc_list); \
+ if (stcb) { \
+ SCTP_TCB_LOCK(stcb); \
+ } \
+ SCTP_INP_RUNLOCK(inp); \
+ } else if (assoc_id > SCTP_ALL_ASSOC) { \
+ stcb = sctp_findassociation_ep_asocid(inp, assoc_id, 1); \
+ if (stcb == NULL) { \
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT); \
+ error = ENOENT; \
+ break; \
+ } \
+ } else { \
+ stcb = NULL; \
+ } \
+}
+
+#define SCTP_CHECK_AND_CAST(destp, srcp, type, size) {\
+ if (size < sizeof(type)) { \
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL); \
+ error = EINVAL; \
+ break; \
+ } else { \
+ destp = (type *)srcp; \
+ } \
+}
+
+#if defined(__Userspace__)
+int
+#else
+static int
+#endif
+sctp_getopt(struct socket *so, int optname, void *optval, size_t *optsize,
+ void *p) {
+ struct sctp_inpcb *inp = NULL;
+ int error, val = 0;
+ struct sctp_tcb *stcb = NULL;
+
+ if (optval == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return EINVAL;
+ }
+ error = 0;
+
+ switch (optname) {
+ case SCTP_NODELAY:
+ case SCTP_AUTOCLOSE:
+ case SCTP_EXPLICIT_EOR:
+ case SCTP_AUTO_ASCONF:
+ case SCTP_DISABLE_FRAGMENTS:
+ case SCTP_I_WANT_MAPPED_V4_ADDR:
+ case SCTP_USE_EXT_RCVINFO:
+ SCTP_INP_RLOCK(inp);
+ switch (optname) {
+ case SCTP_DISABLE_FRAGMENTS:
+ val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NO_FRAGMENT);
+ break;
+ case SCTP_I_WANT_MAPPED_V4_ADDR:
+ val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4);
+ break;
+ case SCTP_AUTO_ASCONF:
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* only valid for bound all sockets */
+ val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTO_ASCONF);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto flags_out;
+ }
+ break;
+ case SCTP_EXPLICIT_EOR:
+ val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR);
+ break;
+ case SCTP_NODELAY:
+ val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NODELAY);
+ break;
+ case SCTP_USE_EXT_RCVINFO:
+ val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO);
+ break;
+ case SCTP_AUTOCLOSE:
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE))
+ val = sctp_ticks_to_secs(inp->sctp_ep.auto_close_time);
+ else
+ val = 0;
+ break;
+
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOPROTOOPT);
+ error = ENOPROTOOPT;
+ } /* end switch (sopt->sopt_name) */
+ if (*optsize < sizeof(val)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ flags_out:
+ SCTP_INP_RUNLOCK(inp);
+ if (error == 0) {
+ /* return the option value */
+ *(int *)optval = val;
+ *optsize = sizeof(val);
+ }
+ break;
+ case SCTP_GET_PACKET_LOG:
+ {
+#ifdef SCTP_PACKET_LOGGING
+ uint8_t *target;
+ int ret;
+
+ SCTP_CHECK_AND_CAST(target, optval, uint8_t, *optsize);
+ ret = sctp_copy_out_packet_log(target , (int)*optsize);
+ *optsize = ret;
+#else
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+#endif
+ break;
+ }
+ case SCTP_REUSE_PORT:
+ {
+ uint32_t *value;
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE)) {
+ /* Can't do this for a 1-m socket */
+ error = EINVAL;
+ break;
+ }
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+ *value = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE);
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_PARTIAL_DELIVERY_POINT:
+ {
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+ *value = inp->partial_delivery_point;
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_FRAGMENT_INTERLEAVE:
+ {
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE)) {
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS)) {
+ *value = SCTP_FRAG_LEVEL_2;
+ } else {
+ *value = SCTP_FRAG_LEVEL_1;
+ }
+ } else {
+ *value = SCTP_FRAG_LEVEL_0;
+ }
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_INTERLEAVING_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.idata_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ if (inp->idata_supported) {
+ av->assoc_value = 1;
+ } else {
+ av->assoc_value = 0;
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_CMT_ON_OFF:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ av->assoc_value = stcb->asoc.sctp_cmt_on_off;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->sctp_cmt_on_off;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_PLUGGABLE_CC:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ av->assoc_value = stcb->asoc.congestion_control_module;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->sctp_ep.sctp_default_cc_module;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_CC_OPTION:
+ {
+ struct sctp_cc_option *cc_opt;
+
+ SCTP_CHECK_AND_CAST(cc_opt, optval, struct sctp_cc_option, *optsize);
+ SCTP_FIND_STCB(inp, stcb, cc_opt->aid_value.assoc_id);
+ if (stcb == NULL) {
+ error = EINVAL;
+ } else {
+ if (stcb->asoc.cc_functions.sctp_cwnd_socket_option == NULL) {
+ error = ENOTSUP;
+ } else {
+ error = (*stcb->asoc.cc_functions.sctp_cwnd_socket_option)(stcb, 0, cc_opt);
+ *optsize = sizeof(struct sctp_cc_option);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ break;
+ }
+ case SCTP_STREAM_SCHEDULER:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ av->assoc_value = stcb->asoc.stream_scheduling_module;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->sctp_ep.sctp_default_ss_module;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_STREAM_SCHEDULER_VALUE:
+ {
+ struct sctp_stream_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_stream_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ if ((av->stream_id >= stcb->asoc.streamoutcnt) ||
+ (stcb->asoc.ss_functions.sctp_ss_get_value(stcb, &stcb->asoc, &stcb->asoc.strmout[av->stream_id],
+ &av->stream_value) < 0)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ *optsize = sizeof(struct sctp_stream_value);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ /* Can't get stream value without association */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ break;
+ }
+ case SCTP_GET_ADDR_LEN:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ error = EINVAL;
+#ifdef INET
+ if (av->assoc_value == AF_INET) {
+ av->assoc_value = sizeof(struct sockaddr_in);
+ error = 0;
+ }
+#endif
+#ifdef INET6
+ if (av->assoc_value == AF_INET6) {
+ av->assoc_value = sizeof(struct sockaddr_in6);
+ error = 0;
+ }
+#endif
+#if defined(__Userspace__)
+ if (av->assoc_value == AF_CONN) {
+ av->assoc_value = sizeof(struct sockaddr_conn);
+ error = 0;
+ }
+#endif
+ if (error) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ } else {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_GET_ASSOC_NUMBER:
+ {
+ uint32_t *value, cnt;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+ SCTP_INP_RLOCK(inp);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ /* Can't do this for a 1-1 socket */
+ error = EINVAL;
+ SCTP_INP_RUNLOCK(inp);
+ break;
+ }
+ cnt = 0;
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ cnt++;
+ }
+ SCTP_INP_RUNLOCK(inp);
+ *value = cnt;
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_GET_ASSOC_ID_LIST:
+ {
+ struct sctp_assoc_ids *ids;
+ uint32_t at;
+ size_t limit;
+
+ SCTP_CHECK_AND_CAST(ids, optval, struct sctp_assoc_ids, *optsize);
+ SCTP_INP_RLOCK(inp);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ /* Can't do this for a 1-1 socket */
+ error = EINVAL;
+ SCTP_INP_RUNLOCK(inp);
+ break;
+ }
+ at = 0;
+ limit = (*optsize - sizeof(uint32_t)) / sizeof(sctp_assoc_t);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ if (at < limit) {
+ ids->gaids_assoc_id[at++] = sctp_get_associd(stcb);
+ if (at == 0) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ }
+ SCTP_INP_RUNLOCK(inp);
+ if (error == 0) {
+ ids->gaids_number_of_ids = at;
+ *optsize = ((at * sizeof(sctp_assoc_t)) + sizeof(uint32_t));
+ }
+ break;
+ }
+ case SCTP_CONTEXT:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.context;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->sctp_context;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_VRF_ID:
+ {
+ uint32_t *default_vrfid;
+
+ SCTP_CHECK_AND_CAST(default_vrfid, optval, uint32_t, *optsize);
+ *default_vrfid = inp->def_vrf_id;
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_GET_ASOC_VRF:
+ {
+ struct sctp_assoc_value *id;
+
+ SCTP_CHECK_AND_CAST(id, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, id->assoc_id);
+ if (stcb == NULL) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ } else {
+ id->assoc_value = stcb->asoc.vrf_id;
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_GET_VRF_IDS:
+ {
+#ifdef SCTP_MVRF
+ int siz_needed;
+ uint32_t *vrf_ids;
+
+ SCTP_CHECK_AND_CAST(vrf_ids, optval, uint32_t, *optsize);
+ siz_needed = inp->num_vrfs * sizeof(uint32_t);
+ if (*optsize < siz_needed) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ } else {
+ memcpy(vrf_ids, inp->m_vrf_ids, siz_needed);
+ *optsize = siz_needed;
+ }
+#else
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+#endif
+ break;
+ }
+ case SCTP_GET_NONCE_VALUES:
+ {
+ struct sctp_get_nonce_values *gnv;
+
+ SCTP_CHECK_AND_CAST(gnv, optval, struct sctp_get_nonce_values, *optsize);
+ SCTP_FIND_STCB(inp, stcb, gnv->gn_assoc_id);
+
+ if (stcb) {
+ gnv->gn_peers_tag = stcb->asoc.peer_vtag;
+ gnv->gn_local_tag = stcb->asoc.my_vtag;
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_get_nonce_values);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTCONN);
+ error = ENOTCONN;
+ }
+ break;
+ }
+ case SCTP_DELAYED_SACK:
+ {
+ struct sctp_sack_info *sack;
+
+ SCTP_CHECK_AND_CAST(sack, optval, struct sctp_sack_info, *optsize);
+ SCTP_FIND_STCB(inp, stcb, sack->sack_assoc_id);
+ if (stcb) {
+ sack->sack_delay = stcb->asoc.delayed_ack;
+ sack->sack_freq = stcb->asoc.sack_freq;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (sack->sack_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ sack->sack_delay = sctp_ticks_to_msecs(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]);
+ sack->sack_freq = inp->sctp_ep.sctp_sack_freq;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_sack_info);
+ }
+ break;
+ }
+ case SCTP_GET_SNDBUF_USE:
+ {
+ struct sctp_sockstat *ss;
+
+ SCTP_CHECK_AND_CAST(ss, optval, struct sctp_sockstat, *optsize);
+ SCTP_FIND_STCB(inp, stcb, ss->ss_assoc_id);
+
+ if (stcb) {
+ ss->ss_total_sndbuf = stcb->asoc.total_output_queue_size;
+ ss->ss_total_recv_buf = (stcb->asoc.size_on_reasm_queue +
+ stcb->asoc.size_on_all_streams);
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_sockstat);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTCONN);
+ error = ENOTCONN;
+ }
+ break;
+ }
+ case SCTP_MAX_BURST:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.max_burst;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->sctp_ep.max_burst;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_MAXSEG:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.sctp_frag_point;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->sctp_frag_point;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_GET_STAT_LOG:
+ error = sctp_fill_stat_log(optval, optsize);
+ break;
+ case SCTP_EVENTS:
+ {
+ struct sctp_event_subscribe *events;
+
+ SCTP_CHECK_AND_CAST(events, optval, struct sctp_event_subscribe, *optsize);
+ memset(events, 0, sizeof(struct sctp_event_subscribe));
+ SCTP_INP_RLOCK(inp);
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT))
+ events->sctp_data_io_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVASSOCEVNT))
+ events->sctp_association_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVPADDREVNT))
+ events->sctp_address_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVSENDFAILEVNT))
+ events->sctp_send_failure_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVPEERERR))
+ events->sctp_peer_error_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT))
+ events->sctp_shutdown_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PDAPIEVNT))
+ events->sctp_partial_delivery_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ADAPTATIONEVNT))
+ events->sctp_adaptation_layer_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTHEVNT))
+ events->sctp_authentication_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_DRYEVNT))
+ events->sctp_sender_dry_event = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_STREAM_RESETEVNT))
+ events->sctp_stream_reset_event = 1;
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(struct sctp_event_subscribe);
+ break;
+ }
+ case SCTP_ADAPTATION_LAYER:
+ {
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+
+ SCTP_INP_RLOCK(inp);
+ *value = inp->sctp_ep.adaptation_layer_indicator;
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_SET_INITIAL_DBG_SEQ:
+ {
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+ SCTP_INP_RLOCK(inp);
+ *value = inp->sctp_ep.initial_sequence_debug;
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_GET_LOCAL_ADDR_SIZE:
+ {
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+ SCTP_INP_RLOCK(inp);
+ *value = (uint32_t)sctp_max_size_addresses(inp);
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_GET_REMOTE_ADDR_SIZE:
+ {
+ uint32_t *value;
+ struct sctp_nets *net;
+ size_t size;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize);
+ /* FIXME MT: change to sctp_assoc_value? */
+ SCTP_FIND_STCB(inp, stcb, (sctp_assoc_t)*value);
+
+ if (stcb != NULL) {
+ size = 0;
+ /* Count the sizes */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef INET6
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4)) {
+ size += sizeof(struct sockaddr_in6);
+ } else {
+ size += sizeof(struct sockaddr_in);
+ }
+#else
+ size += sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ size += sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ size += sizeof(struct sockaddr_conn);
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ *value = (uint32_t)size;
+ *optsize = sizeof(uint32_t);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((sctp_assoc_t)*value <= SCTP_ALL_ASSOC)) {
+ error = EINVAL;
+ } else {
+ error = ENOENT;
+ }
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ }
+ break;
+ }
+ case SCTP_GET_PEER_ADDRESSES:
+ /*
+ * Get the address information, an array is passed in to
+ * fill up we pack it.
+ */
+ {
+ size_t cpsz, left;
+ struct sockaddr *addr;
+ struct sctp_nets *net;
+ struct sctp_getaddresses *saddr;
+
+ SCTP_CHECK_AND_CAST(saddr, optval, struct sctp_getaddresses, *optsize);
+ SCTP_FIND_STCB(inp, stcb, saddr->sget_assoc_id);
+
+ if (stcb != NULL) {
+ left = *optsize - offsetof(struct sctp_getaddresses, addr);
+ *optsize = offsetof(struct sctp_getaddresses, addr);
+ addr = &saddr->addr[0].sa;
+
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef INET6
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4)) {
+ cpsz = sizeof(struct sockaddr_in6);
+ } else {
+ cpsz = sizeof(struct sockaddr_in);
+ }
+#else
+ cpsz = sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ cpsz = sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ cpsz = sizeof(struct sockaddr_conn);
+ break;
+#endif
+ default:
+ cpsz = 0;
+ break;
+ }
+ if (cpsz == 0) {
+ break;
+ }
+ if (left < cpsz) {
+ /* not enough room. */
+ break;
+ }
+#if defined(INET) && defined(INET6)
+ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4)) &&
+ (net->ro._l_addr.sa.sa_family == AF_INET)) {
+ /* Must map the address */
+ in6_sin_2_v4mapsin6(&net->ro._l_addr.sin,
+ (struct sockaddr_in6 *)addr);
+ } else {
+ memcpy(addr, &net->ro._l_addr, cpsz);
+ }
+#else
+ memcpy(addr, &net->ro._l_addr, cpsz);
+#endif
+ ((struct sockaddr_in *)addr)->sin_port = stcb->rport;
+
+ addr = (struct sockaddr *)((caddr_t)addr + cpsz);
+ left -= cpsz;
+ *optsize += cpsz;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (saddr->sget_assoc_id <= SCTP_ALL_ASSOC)) {
+ error = EINVAL;
+ } else {
+ error = ENOENT;
+ }
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ }
+ break;
+ }
+ case SCTP_GET_LOCAL_ADDRESSES:
+ {
+ size_t limit, actual;
+ struct sctp_getaddresses *saddr;
+
+ SCTP_CHECK_AND_CAST(saddr, optval, struct sctp_getaddresses, *optsize);
+ SCTP_FIND_STCB(inp, stcb, saddr->sget_assoc_id);
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((saddr->sget_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (saddr->sget_assoc_id == SCTP_ALL_ASSOC))) {
+ error = EINVAL;
+ } else {
+ limit = *optsize - offsetof(struct sctp_getaddresses, addr);
+ actual = sctp_fill_up_addresses(inp, stcb, limit, &saddr->addr[0].sa);
+ *optsize = offsetof(struct sctp_getaddresses, addr) + actual;
+ }
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ break;
+ }
+ case SCTP_PEER_ADDR_PARAMS:
+ {
+ struct sctp_paddrparams *paddrp;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(paddrp, optval, struct sctp_paddrparams, *optsize);
+ SCTP_FIND_STCB(inp, stcb, paddrp->spp_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (paddrp->spp_address.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&paddrp->spp_address;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&paddrp->spp_address;
+ }
+ } else {
+ addr = (struct sockaddr *)&paddrp->spp_address;
+ }
+#else
+ addr = (struct sockaddr *)&paddrp->spp_address;
+#endif
+ if (stcb != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ if ((stcb != NULL) && (net == NULL)) {
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ if (sin->sin_addr.s_addr != INADDR_ANY) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)addr;
+ if (sconn->sconn_addr != NULL) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ }
+
+ if (stcb != NULL) {
+ /* Applies to the specific association */
+ paddrp->spp_flags = 0;
+ if (net != NULL) {
+ paddrp->spp_hbinterval = net->heart_beat_delay;
+ paddrp->spp_pathmaxrxt = net->failure_threshold;
+ paddrp->spp_pathmtu = net->mtu;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ paddrp->spp_pathmtu -= SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ paddrp->spp_pathmtu -= SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ paddrp->spp_pathmtu -= sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ break;
+ }
+ /* get flags for HB */
+ if (net->dest_state & SCTP_ADDR_NOHB) {
+ paddrp->spp_flags |= SPP_HB_DISABLE;
+ } else {
+ paddrp->spp_flags |= SPP_HB_ENABLE;
+ }
+ /* get flags for PMTU */
+ if (net->dest_state & SCTP_ADDR_NO_PMTUD) {
+ paddrp->spp_flags |= SPP_PMTUD_DISABLE;
+ } else {
+ paddrp->spp_flags |= SPP_PMTUD_ENABLE;
+ }
+ if (net->dscp & 0x01) {
+ paddrp->spp_dscp = net->dscp & 0xfc;
+ paddrp->spp_flags |= SPP_DSCP;
+ }
+#ifdef INET6
+ if ((net->ro._l_addr.sa.sa_family == AF_INET6) &&
+ (net->flowlabel & 0x80000000)) {
+ paddrp->spp_ipv6_flowlabel = net->flowlabel & 0x000fffff;
+ paddrp->spp_flags |= SPP_IPV6_FLOWLABEL;
+ }
+#endif
+ } else {
+ /*
+ * No destination so return default
+ * value
+ */
+ paddrp->spp_pathmaxrxt = stcb->asoc.def_net_failure;
+ paddrp->spp_pathmtu = stcb->asoc.default_mtu;
+ if (stcb->asoc.default_dscp & 0x01) {
+ paddrp->spp_dscp = stcb->asoc.default_dscp & 0xfc;
+ paddrp->spp_flags |= SPP_DSCP;
+ }
+#ifdef INET6
+ if (stcb->asoc.default_flowlabel & 0x80000000) {
+ paddrp->spp_ipv6_flowlabel = stcb->asoc.default_flowlabel & 0x000fffff;
+ paddrp->spp_flags |= SPP_IPV6_FLOWLABEL;
+ }
+#endif
+ /* default settings should be these */
+ if (sctp_stcb_is_feature_on(inp, stcb, SCTP_PCB_FLAGS_DONOT_HEARTBEAT)) {
+ paddrp->spp_flags |= SPP_HB_DISABLE;
+ } else {
+ paddrp->spp_flags |= SPP_HB_ENABLE;
+ }
+ if (sctp_stcb_is_feature_on(inp, stcb, SCTP_PCB_FLAGS_DO_NOT_PMTUD)) {
+ paddrp->spp_flags |= SPP_PMTUD_DISABLE;
+ } else {
+ paddrp->spp_flags |= SPP_PMTUD_ENABLE;
+ }
+ paddrp->spp_hbinterval = stcb->asoc.heart_beat_delay;
+ }
+ paddrp->spp_assoc_id = sctp_get_associd(stcb);
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (paddrp->spp_assoc_id == SCTP_FUTURE_ASSOC))) {
+ /* Use endpoint defaults */
+ SCTP_INP_RLOCK(inp);
+ paddrp->spp_pathmaxrxt = inp->sctp_ep.def_net_failure;
+ paddrp->spp_hbinterval = sctp_ticks_to_msecs(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT]);
+ paddrp->spp_assoc_id = SCTP_FUTURE_ASSOC;
+ /* get inp's default */
+ if (inp->sctp_ep.default_dscp & 0x01) {
+ paddrp->spp_dscp = inp->sctp_ep.default_dscp & 0xfc;
+ paddrp->spp_flags |= SPP_DSCP;
+ }
+#ifdef INET6
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ (inp->sctp_ep.default_flowlabel & 0x80000000)) {
+ paddrp->spp_ipv6_flowlabel = inp->sctp_ep.default_flowlabel & 0x000fffff;
+ paddrp->spp_flags |= SPP_IPV6_FLOWLABEL;
+ }
+#endif
+ paddrp->spp_pathmtu = inp->sctp_ep.default_mtu;
+
+ if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_DONOT_HEARTBEAT)) {
+ paddrp->spp_flags |= SPP_HB_ENABLE;
+ } else {
+ paddrp->spp_flags |= SPP_HB_DISABLE;
+ }
+ if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_DO_NOT_PMTUD)) {
+ paddrp->spp_flags |= SPP_PMTUD_ENABLE;
+ } else {
+ paddrp->spp_flags |= SPP_PMTUD_DISABLE;
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_paddrparams);
+ }
+ break;
+ }
+ case SCTP_GET_PEER_ADDR_INFO:
+ {
+ struct sctp_paddrinfo *paddri;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(paddri, optval, struct sctp_paddrinfo, *optsize);
+ SCTP_FIND_STCB(inp, stcb, paddri->spinfo_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (paddri->spinfo_address.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&paddri->spinfo_address;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&paddri->spinfo_address;
+ }
+ } else {
+ addr = (struct sockaddr *)&paddri->spinfo_address;
+ }
+#else
+ addr = (struct sockaddr *)&paddri->spinfo_address;
+#endif
+ if (stcb != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+
+ if ((stcb != NULL) && (net != NULL)) {
+ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ /* It's unconfirmed */
+ paddri->spinfo_state = SCTP_UNCONFIRMED;
+ } else if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ /* It's active */
+ paddri->spinfo_state = SCTP_ACTIVE;
+ } else {
+ /* It's inactive */
+ paddri->spinfo_state = SCTP_INACTIVE;
+ }
+ paddri->spinfo_cwnd = net->cwnd;
+ paddri->spinfo_srtt = net->lastsa >> SCTP_RTT_SHIFT;
+ paddri->spinfo_rto = net->RTO;
+ paddri->spinfo_assoc_id = sctp_get_associd(stcb);
+ paddri->spinfo_mtu = net->mtu;
+ switch (addr->sa_family) {
+#if defined(INET)
+ case AF_INET:
+ paddri->spinfo_mtu -= SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ paddri->spinfo_mtu -= SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ paddri->spinfo_mtu -= sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ break;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_paddrinfo);
+ } else {
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ }
+ break;
+ }
+ case SCTP_PCB_STATUS:
+ {
+ struct sctp_pcbinfo *spcb;
+
+ SCTP_CHECK_AND_CAST(spcb, optval, struct sctp_pcbinfo, *optsize);
+ sctp_fill_pcbinfo(spcb);
+ *optsize = sizeof(struct sctp_pcbinfo);
+ break;
+ }
+ case SCTP_STATUS:
+ {
+ struct sctp_nets *net;
+ struct sctp_status *sstat;
+
+ SCTP_CHECK_AND_CAST(sstat, optval, struct sctp_status, *optsize);
+ SCTP_FIND_STCB(inp, stcb, sstat->sstat_assoc_id);
+
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ sstat->sstat_state = sctp_map_assoc_state(stcb->asoc.state);
+ sstat->sstat_assoc_id = sctp_get_associd(stcb);
+ sstat->sstat_rwnd = stcb->asoc.peers_rwnd;
+ sstat->sstat_unackdata = stcb->asoc.sent_queue_cnt;
+ /*
+ * We can't include chunks that have been passed to
+ * the socket layer. Only things in queue.
+ */
+ sstat->sstat_penddata = (stcb->asoc.cnt_on_reasm_queue +
+ stcb->asoc.cnt_on_all_streams);
+ sstat->sstat_instrms = stcb->asoc.streamincnt;
+ sstat->sstat_outstrms = stcb->asoc.streamoutcnt;
+ sstat->sstat_fragmentation_point = sctp_get_frag_point(stcb);
+ net = stcb->asoc.primary_destination;
+ if (net != NULL) {
+#ifdef HAVE_SA_LEN
+ memcpy(&sstat->sstat_primary.spinfo_address,
+ &net->ro._l_addr,
+ ((struct sockaddr *)(&net->ro._l_addr))->sa_len);
+#else
+ switch (stcb->asoc.primary_destination->ro._l_addr.sa.sa_family) {
+#if defined(INET)
+ case AF_INET:
+ memcpy(&sstat->sstat_primary.spinfo_address,
+ &net->ro._l_addr,
+ sizeof(struct sockaddr_in));
+ break;
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ memcpy(&sstat->sstat_primary.spinfo_address,
+ &net->ro._l_addr,
+ sizeof(struct sockaddr_in6));
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(&sstat->sstat_primary.spinfo_address,
+ &net->ro._l_addr,
+ sizeof(struct sockaddr_conn));
+ break;
+#endif
+ default:
+ break;
+ }
+#endif
+ ((struct sockaddr_in *)&sstat->sstat_primary.spinfo_address)->sin_port = stcb->rport;
+ /*
+ * Again the user can get info from sctp_constants.h
+ * for what the state of the network is.
+ */
+ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ /* It's unconfirmed */
+ sstat->sstat_primary.spinfo_state = SCTP_UNCONFIRMED;
+ } else if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ /* It's active */
+ sstat->sstat_primary.spinfo_state = SCTP_ACTIVE;
+ } else {
+ /* It's inactive */
+ sstat->sstat_primary.spinfo_state = SCTP_INACTIVE;
+ }
+ sstat->sstat_primary.spinfo_cwnd = net->cwnd;
+ sstat->sstat_primary.spinfo_srtt = net->lastsa >> SCTP_RTT_SHIFT;
+ sstat->sstat_primary.spinfo_rto = net->RTO;
+ sstat->sstat_primary.spinfo_mtu = net->mtu;
+ switch (stcb->asoc.primary_destination->ro._l_addr.sa.sa_family) {
+#if defined(INET)
+ case AF_INET:
+ sstat->sstat_primary.spinfo_mtu -= SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ sstat->sstat_primary.spinfo_mtu -= SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ sstat->sstat_primary.spinfo_mtu -= sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ break;
+ }
+ } else {
+ memset(&sstat->sstat_primary, 0, sizeof(struct sctp_paddrinfo));
+ }
+ sstat->sstat_primary.spinfo_assoc_id = sctp_get_associd(stcb);
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_status);
+ break;
+ }
+ case SCTP_RTOINFO:
+ {
+ struct sctp_rtoinfo *srto;
+
+ SCTP_CHECK_AND_CAST(srto, optval, struct sctp_rtoinfo, *optsize);
+ SCTP_FIND_STCB(inp, stcb, srto->srto_assoc_id);
+
+ if (stcb) {
+ srto->srto_initial = stcb->asoc.initial_rto;
+ srto->srto_max = stcb->asoc.maxrto;
+ srto->srto_min = stcb->asoc.minrto;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (srto->srto_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ srto->srto_initial = inp->sctp_ep.initial_rto;
+ srto->srto_max = inp->sctp_ep.sctp_maxrto;
+ srto->srto_min = inp->sctp_ep.sctp_minrto;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_rtoinfo);
+ }
+ break;
+ }
+ case SCTP_TIMEOUTS:
+ {
+ struct sctp_timeouts *stimo;
+
+ SCTP_CHECK_AND_CAST(stimo, optval, struct sctp_timeouts, *optsize);
+ SCTP_FIND_STCB(inp, stcb, stimo->stimo_assoc_id);
+
+ if (stcb) {
+ stimo->stimo_init= stcb->asoc.timoinit;
+ stimo->stimo_data= stcb->asoc.timodata;
+ stimo->stimo_sack= stcb->asoc.timosack;
+ stimo->stimo_shutdown= stcb->asoc.timoshutdown;
+ stimo->stimo_heartbeat= stcb->asoc.timoheartbeat;
+ stimo->stimo_cookie= stcb->asoc.timocookie;
+ stimo->stimo_shutdownack= stcb->asoc.timoshutdownack;
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_timeouts);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ break;
+ }
+ case SCTP_ASSOCINFO:
+ {
+ struct sctp_assocparams *sasoc;
+
+ SCTP_CHECK_AND_CAST(sasoc, optval, struct sctp_assocparams, *optsize);
+ SCTP_FIND_STCB(inp, stcb, sasoc->sasoc_assoc_id);
+
+ if (stcb) {
+ sasoc->sasoc_cookie_life = sctp_ticks_to_msecs(stcb->asoc.cookie_life);
+ sasoc->sasoc_asocmaxrxt = stcb->asoc.max_send_times;
+ sasoc->sasoc_number_peer_destinations = stcb->asoc.numnets;
+ sasoc->sasoc_peer_rwnd = stcb->asoc.peers_rwnd;
+ sasoc->sasoc_local_rwnd = stcb->asoc.my_rwnd;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (sasoc->sasoc_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ sasoc->sasoc_cookie_life = sctp_ticks_to_msecs(inp->sctp_ep.def_cookie_life);
+ sasoc->sasoc_asocmaxrxt = inp->sctp_ep.max_send_times;
+ sasoc->sasoc_number_peer_destinations = 0;
+ sasoc->sasoc_peer_rwnd = 0;
+ sasoc->sasoc_local_rwnd = (uint32_t)sbspace(&inp->sctp_socket->so_rcv);
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assocparams);
+ }
+ break;
+ }
+ case SCTP_DEFAULT_SEND_PARAM:
+ {
+ struct sctp_sndrcvinfo *s_info;
+
+ SCTP_CHECK_AND_CAST(s_info, optval, struct sctp_sndrcvinfo, *optsize);
+ SCTP_FIND_STCB(inp, stcb, s_info->sinfo_assoc_id);
+
+ if (stcb) {
+ memcpy(s_info, &stcb->asoc.def_send, sizeof(stcb->asoc.def_send));
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (s_info->sinfo_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ memcpy(s_info, &inp->def_send, sizeof(inp->def_send));
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_sndrcvinfo);
+ }
+ break;
+ }
+ case SCTP_INITMSG:
+ {
+ struct sctp_initmsg *sinit;
+
+ SCTP_CHECK_AND_CAST(sinit, optval, struct sctp_initmsg, *optsize);
+ SCTP_INP_RLOCK(inp);
+ sinit->sinit_num_ostreams = inp->sctp_ep.pre_open_stream_count;
+ sinit->sinit_max_instreams = inp->sctp_ep.max_open_streams_intome;
+ sinit->sinit_max_attempts = inp->sctp_ep.max_init_times;
+ sinit->sinit_max_init_timeo = inp->sctp_ep.initial_init_rto_max;
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(struct sctp_initmsg);
+ break;
+ }
+ case SCTP_PRIMARY_ADDR:
+ /* we allow a "get" operation on this */
+ {
+ struct sctp_setprim *ssp;
+
+ SCTP_CHECK_AND_CAST(ssp, optval, struct sctp_setprim, *optsize);
+ SCTP_FIND_STCB(inp, stcb, ssp->ssp_assoc_id);
+
+ if (stcb) {
+ union sctp_sockstore *addr;
+
+ addr = &stcb->asoc.primary_destination->ro._l_addr;
+ switch (addr->sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef INET6
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4)) {
+ in6_sin_2_v4mapsin6(&addr->sin,
+ (struct sockaddr_in6 *)&ssp->ssp_addr);
+ } else {
+ memcpy(&ssp->ssp_addr, &addr->sin, sizeof(struct sockaddr_in));
+ }
+#else
+ memcpy(&ssp->ssp_addr, &addr->sin, sizeof(struct sockaddr_in));
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memcpy(&ssp->ssp_addr, &addr->sin6, sizeof(struct sockaddr_in6));
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(&ssp->ssp_addr, &addr->sconn, sizeof(struct sockaddr_conn));
+ break;
+#endif
+ default:
+ break;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_setprim);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ break;
+ }
+ case SCTP_HMAC_IDENT:
+ {
+ struct sctp_hmacalgo *shmac;
+ sctp_hmaclist_t *hmaclist;
+ size_t size;
+ int i;
+
+ SCTP_CHECK_AND_CAST(shmac, optval, struct sctp_hmacalgo, *optsize);
+
+ SCTP_INP_RLOCK(inp);
+ hmaclist = inp->sctp_ep.local_hmacs;
+ if (hmaclist == NULL) {
+ /* no HMACs to return */
+ *optsize = sizeof(*shmac);
+ SCTP_INP_RUNLOCK(inp);
+ break;
+ }
+ /* is there room for all of the hmac ids? */
+ size = sizeof(*shmac) + (hmaclist->num_algo *
+ sizeof(shmac->shmac_idents[0]));
+ if (*optsize < size) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_INP_RUNLOCK(inp);
+ break;
+ }
+ /* copy in the list */
+ shmac->shmac_number_of_idents = hmaclist->num_algo;
+ for (i = 0; i < hmaclist->num_algo; i++) {
+ shmac->shmac_idents[i] = hmaclist->hmac[i];
+ }
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = size;
+ break;
+ }
+ case SCTP_AUTH_ACTIVE_KEY:
+ {
+ struct sctp_authkeyid *scact;
+
+ SCTP_CHECK_AND_CAST(scact, optval, struct sctp_authkeyid, *optsize);
+ SCTP_FIND_STCB(inp, stcb, scact->scact_assoc_id);
+
+ if (stcb) {
+ /* get the active key on the assoc */
+ scact->scact_keynumber = stcb->asoc.authinfo.active_keyid;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (scact->scact_assoc_id == SCTP_FUTURE_ASSOC))) {
+ /* get the endpoint active key */
+ SCTP_INP_RLOCK(inp);
+ scact->scact_keynumber = inp->sctp_ep.default_keyid;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_authkeyid);
+ }
+ break;
+ }
+ case SCTP_LOCAL_AUTH_CHUNKS:
+ {
+ struct sctp_authchunks *sac;
+ sctp_auth_chklist_t *chklist = NULL;
+ size_t size = 0;
+
+ SCTP_CHECK_AND_CAST(sac, optval, struct sctp_authchunks, *optsize);
+ SCTP_FIND_STCB(inp, stcb, sac->gauth_assoc_id);
+
+ if (stcb) {
+ /* get off the assoc */
+ chklist = stcb->asoc.local_auth_chunks;
+ /* is there enough space? */
+ size = sctp_auth_get_chklist_size(chklist);
+ if (*optsize < (sizeof(struct sctp_authchunks) + size)) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ } else {
+ /* copy in the chunks */
+ (void)sctp_serialize_auth_chunks(chklist, sac->gauth_chunks);
+ sac->gauth_number_of_chunks = (uint32_t)size;
+ *optsize = sizeof(struct sctp_authchunks) + size;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (sac->gauth_assoc_id == SCTP_FUTURE_ASSOC))) {
+ /* get off the endpoint */
+ SCTP_INP_RLOCK(inp);
+ chklist = inp->sctp_ep.local_auth_chunks;
+ /* is there enough space? */
+ size = sctp_auth_get_chklist_size(chklist);
+ if (*optsize < (sizeof(struct sctp_authchunks) + size)) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ } else {
+ /* copy in the chunks */
+ (void)sctp_serialize_auth_chunks(chklist, sac->gauth_chunks);
+ sac->gauth_number_of_chunks = (uint32_t)size;
+ *optsize = sizeof(struct sctp_authchunks) + size;
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_PEER_AUTH_CHUNKS:
+ {
+ struct sctp_authchunks *sac;
+ sctp_auth_chklist_t *chklist = NULL;
+ size_t size = 0;
+
+ SCTP_CHECK_AND_CAST(sac, optval, struct sctp_authchunks, *optsize);
+ SCTP_FIND_STCB(inp, stcb, sac->gauth_assoc_id);
+
+ if (stcb) {
+ /* get off the assoc */
+ chklist = stcb->asoc.peer_auth_chunks;
+ /* is there enough space? */
+ size = sctp_auth_get_chklist_size(chklist);
+ if (*optsize < (sizeof(struct sctp_authchunks) + size)) {
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ } else {
+ /* copy in the chunks */
+ (void)sctp_serialize_auth_chunks(chklist, sac->gauth_chunks);
+ sac->gauth_number_of_chunks = (uint32_t)size;
+ *optsize = sizeof(struct sctp_authchunks) + size;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ }
+ break;
+ }
+#if defined(HAVE_SCTP_PEELOFF_SOCKOPT)
+ case SCTP_PEELOFF:
+ {
+ struct sctp_peeloff_opt *peeloff;
+
+ SCTP_CHECK_AND_CAST(peeloff, optval, struct sctp_peeloff_opt, *optsize);
+ /* do the peeloff */
+ error = sctp_peeloff_option(p, peeloff);
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_peeloff_opt);
+ }
+ }
+ break;
+#endif /* HAVE_SCTP_PEELOFF_SOCKOPT */
+ case SCTP_EVENT:
+ {
+ struct sctp_event *event;
+ uint32_t event_type;
+
+ SCTP_CHECK_AND_CAST(event, optval, struct sctp_event, *optsize);
+ SCTP_FIND_STCB(inp, stcb, event->se_assoc_id);
+
+ switch (event->se_type) {
+ case SCTP_ASSOC_CHANGE:
+ event_type = SCTP_PCB_FLAGS_RECVASSOCEVNT;
+ break;
+ case SCTP_PEER_ADDR_CHANGE:
+ event_type = SCTP_PCB_FLAGS_RECVPADDREVNT;
+ break;
+ case SCTP_REMOTE_ERROR:
+ event_type = SCTP_PCB_FLAGS_RECVPEERERR;
+ break;
+ case SCTP_SEND_FAILED:
+ event_type = SCTP_PCB_FLAGS_RECVSENDFAILEVNT;
+ break;
+ case SCTP_SHUTDOWN_EVENT:
+ event_type = SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT;
+ break;
+ case SCTP_ADAPTATION_INDICATION:
+ event_type = SCTP_PCB_FLAGS_ADAPTATIONEVNT;
+ break;
+ case SCTP_PARTIAL_DELIVERY_EVENT:
+ event_type = SCTP_PCB_FLAGS_PDAPIEVNT;
+ break;
+ case SCTP_AUTHENTICATION_EVENT:
+ event_type = SCTP_PCB_FLAGS_AUTHEVNT;
+ break;
+ case SCTP_STREAM_RESET_EVENT:
+ event_type = SCTP_PCB_FLAGS_STREAM_RESETEVNT;
+ break;
+ case SCTP_SENDER_DRY_EVENT:
+ event_type = SCTP_PCB_FLAGS_DRYEVNT;
+ break;
+ case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+ event_type = 0;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTSUP);
+ error = ENOTSUP;
+ break;
+ case SCTP_ASSOC_RESET_EVENT:
+ event_type = SCTP_PCB_FLAGS_ASSOC_RESETEVNT;
+ break;
+ case SCTP_STREAM_CHANGE_EVENT:
+ event_type = SCTP_PCB_FLAGS_STREAM_CHANGEEVNT;
+ break;
+ case SCTP_SEND_FAILED_EVENT:
+ event_type = SCTP_PCB_FLAGS_RECVNSENDFAILEVNT;
+ break;
+ default:
+ event_type = 0;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if (event_type > 0) {
+ if (stcb) {
+ event->se_on = sctp_stcb_is_feature_on(inp, stcb, event_type);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (event->se_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ event->se_on = sctp_is_feature_on(inp, event_type);
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ }
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_event);
+ }
+ break;
+ }
+ case SCTP_RECVRCVINFO:
+ if (*optsize < sizeof(int)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ SCTP_INP_RLOCK(inp);
+ *(int *)optval = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVRCVINFO);
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(int);
+ }
+ break;
+ case SCTP_RECVNXTINFO:
+ if (*optsize < sizeof(int)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ SCTP_INP_RLOCK(inp);
+ *(int *)optval = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVNXTINFO);
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(int);
+ }
+ break;
+ case SCTP_DEFAULT_SNDINFO:
+ {
+ struct sctp_sndinfo *info;
+
+ SCTP_CHECK_AND_CAST(info, optval, struct sctp_sndinfo, *optsize);
+ SCTP_FIND_STCB(inp, stcb, info->snd_assoc_id);
+
+ if (stcb) {
+ info->snd_sid = stcb->asoc.def_send.sinfo_stream;
+ info->snd_flags = stcb->asoc.def_send.sinfo_flags;
+ info->snd_flags &= 0xfff0;
+ info->snd_ppid = stcb->asoc.def_send.sinfo_ppid;
+ info->snd_context = stcb->asoc.def_send.sinfo_context;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (info->snd_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ info->snd_sid = inp->def_send.sinfo_stream;
+ info->snd_flags = inp->def_send.sinfo_flags;
+ info->snd_flags &= 0xfff0;
+ info->snd_ppid = inp->def_send.sinfo_ppid;
+ info->snd_context = inp->def_send.sinfo_context;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_sndinfo);
+ }
+ break;
+ }
+ case SCTP_DEFAULT_PRINFO:
+ {
+ struct sctp_default_prinfo *info;
+
+ SCTP_CHECK_AND_CAST(info, optval, struct sctp_default_prinfo, *optsize);
+ SCTP_FIND_STCB(inp, stcb, info->pr_assoc_id);
+
+ if (stcb) {
+ info->pr_policy = PR_SCTP_POLICY(stcb->asoc.def_send.sinfo_flags);
+ info->pr_value = stcb->asoc.def_send.sinfo_timetolive;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (info->pr_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ info->pr_policy = PR_SCTP_POLICY(inp->def_send.sinfo_flags);
+ info->pr_value = inp->def_send.sinfo_timetolive;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_default_prinfo);
+ }
+ break;
+ }
+ case SCTP_PEER_ADDR_THLDS:
+ {
+ struct sctp_paddrthlds *thlds;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(thlds, optval, struct sctp_paddrthlds, *optsize);
+ SCTP_FIND_STCB(inp, stcb, thlds->spt_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (thlds->spt_address.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&thlds->spt_address;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&thlds->spt_address;
+ }
+ } else {
+ addr = (struct sockaddr *)&thlds->spt_address;
+ }
+#else
+ addr = (struct sockaddr *)&thlds->spt_address;
+#endif
+ if (stcb != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ if ((stcb != NULL) && (net == NULL)) {
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ if (sin->sin_addr.s_addr != INADDR_ANY) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)addr;
+ if (sconn->sconn_addr != NULL) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ }
+
+ if (stcb != NULL) {
+ if (net != NULL) {
+ thlds->spt_pathmaxrxt = net->failure_threshold;
+ thlds->spt_pathpfthld = net->pf_threshold;
+ thlds->spt_pathcpthld = 0xffff;
+ } else {
+ thlds->spt_pathmaxrxt = stcb->asoc.def_net_failure;
+ thlds->spt_pathpfthld = stcb->asoc.def_net_pf_threshold;
+ thlds->spt_pathcpthld = 0xffff;
+ }
+ thlds->spt_assoc_id = sctp_get_associd(stcb);
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (thlds->spt_assoc_id == SCTP_FUTURE_ASSOC))) {
+ /* Use endpoint defaults */
+ SCTP_INP_RLOCK(inp);
+ thlds->spt_pathmaxrxt = inp->sctp_ep.def_net_failure;
+ thlds->spt_pathpfthld = inp->sctp_ep.def_net_pf_threshold;
+ thlds->spt_pathcpthld = 0xffff;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_paddrthlds);
+ }
+ break;
+ }
+ case SCTP_REMOTE_UDP_ENCAPS_PORT:
+ {
+ struct sctp_udpencaps *encaps;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(encaps, optval, struct sctp_udpencaps, *optsize);
+ SCTP_FIND_STCB(inp, stcb, encaps->sue_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (encaps->sue_address.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&encaps->sue_address;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&encaps->sue_address;
+ }
+ } else {
+ addr = (struct sockaddr *)&encaps->sue_address;
+ }
+#else
+ addr = (struct sockaddr *)&encaps->sue_address;
+#endif
+ if (stcb) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ if ((stcb != NULL) && (net == NULL)) {
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ if (sin->sin_addr.s_addr != INADDR_ANY) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)addr;
+ if (sconn->sconn_addr != NULL) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ }
+
+ if (stcb != NULL) {
+ if (net) {
+ encaps->sue_port = net->port;
+ } else {
+ encaps->sue_port = stcb->asoc.port;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (encaps->sue_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ encaps->sue_port = inp->sctp_ep.port;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_udpencaps);
+ }
+ break;
+ }
+ case SCTP_ECN_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.ecn_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->ecn_supported;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_PR_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.prsctp_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->prsctp_supported;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_AUTH_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.auth_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->auth_supported;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_ASCONF_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.asconf_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->asconf_supported;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_RECONFIG_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.reconfig_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->reconfig_supported;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_NRSACK_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.nrsack_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->nrsack_supported;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_PKTDROP_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.pktdrop_supported;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->pktdrop_supported;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_ENABLE_STREAM_RESET:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = (uint32_t)stcb->asoc.local_strreset_support;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = (uint32_t)inp->local_strreset_support;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ case SCTP_PR_STREAM_STATUS:
+ {
+ struct sctp_prstatus *sprstat;
+ uint16_t sid;
+ uint16_t policy;
+
+ SCTP_CHECK_AND_CAST(sprstat, optval, struct sctp_prstatus, *optsize);
+ SCTP_FIND_STCB(inp, stcb, sprstat->sprstat_assoc_id);
+
+ sid = sprstat->sprstat_sid;
+ policy = sprstat->sprstat_policy;
+#if defined(SCTP_DETAILED_STR_STATS)
+ if ((stcb != NULL) &&
+ (sid < stcb->asoc.streamoutcnt) &&
+ (policy != SCTP_PR_SCTP_NONE) &&
+ ((policy <= SCTP_PR_SCTP_MAX) ||
+ (policy == SCTP_PR_SCTP_ALL))) {
+ if (policy == SCTP_PR_SCTP_ALL) {
+ sprstat->sprstat_abandoned_unsent = stcb->asoc.strmout[sid].abandoned_unsent[0];
+ sprstat->sprstat_abandoned_sent = stcb->asoc.strmout[sid].abandoned_sent[0];
+ } else {
+ sprstat->sprstat_abandoned_unsent = stcb->asoc.strmout[sid].abandoned_unsent[policy];
+ sprstat->sprstat_abandoned_sent = stcb->asoc.strmout[sid].abandoned_sent[policy];
+ }
+#else
+ if ((stcb != NULL) &&
+ (sid < stcb->asoc.streamoutcnt) &&
+ (policy == SCTP_PR_SCTP_ALL)) {
+ sprstat->sprstat_abandoned_unsent = stcb->asoc.strmout[sid].abandoned_unsent[0];
+ sprstat->sprstat_abandoned_sent = stcb->asoc.strmout[sid].abandoned_sent[0];
+#endif
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_prstatus);
+ }
+ break;
+ }
+ case SCTP_PR_ASSOC_STATUS:
+ {
+ struct sctp_prstatus *sprstat;
+ uint16_t policy;
+
+ SCTP_CHECK_AND_CAST(sprstat, optval, struct sctp_prstatus, *optsize);
+ SCTP_FIND_STCB(inp, stcb, sprstat->sprstat_assoc_id);
+
+ policy = sprstat->sprstat_policy;
+ if ((stcb != NULL) &&
+ (policy != SCTP_PR_SCTP_NONE) &&
+ ((policy <= SCTP_PR_SCTP_MAX) ||
+ (policy == SCTP_PR_SCTP_ALL))) {
+ if (policy == SCTP_PR_SCTP_ALL) {
+ sprstat->sprstat_abandoned_unsent = stcb->asoc.abandoned_unsent[0];
+ sprstat->sprstat_abandoned_sent = stcb->asoc.abandoned_sent[0];
+ } else {
+ sprstat->sprstat_abandoned_unsent = stcb->asoc.abandoned_unsent[policy];
+ sprstat->sprstat_abandoned_sent = stcb->asoc.abandoned_sent[policy];
+ }
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_prstatus);
+ }
+ break;
+ }
+ case SCTP_MAX_CWND:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = stcb->asoc.max_cwnd;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ av->assoc_value = inp->max_cwnd;
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ if (error == 0) {
+ *optsize = sizeof(struct sctp_assoc_value);
+ }
+ break;
+ }
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOPROTOOPT);
+ error = ENOPROTOOPT;
+ break;
+ } /* end switch (sopt->sopt_name) */
+ if (error) {
+ *optsize = 0;
+ }
+ return (error);
+}
+
+#if defined(__Userspace__)
+int
+#else
+static int
+#endif
+sctp_setopt(struct socket *so, int optname, void *optval, size_t optsize,
+ void *p)
+{
+ int error, set_opt;
+ uint32_t *mopt;
+ struct sctp_tcb *stcb = NULL;
+ struct sctp_inpcb *inp = NULL;
+ uint32_t vrf_id;
+
+ if (optval == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ vrf_id = inp->def_vrf_id;
+
+ error = 0;
+ switch (optname) {
+ case SCTP_NODELAY:
+ case SCTP_AUTOCLOSE:
+ case SCTP_AUTO_ASCONF:
+ case SCTP_EXPLICIT_EOR:
+ case SCTP_DISABLE_FRAGMENTS:
+ case SCTP_USE_EXT_RCVINFO:
+ case SCTP_I_WANT_MAPPED_V4_ADDR:
+ /* copy in the option value */
+ SCTP_CHECK_AND_CAST(mopt, optval, uint32_t, optsize);
+ set_opt = 0;
+ if (error)
+ break;
+ switch (optname) {
+ case SCTP_DISABLE_FRAGMENTS:
+ set_opt = SCTP_PCB_FLAGS_NO_FRAGMENT;
+ break;
+ case SCTP_AUTO_ASCONF:
+ /*
+ * NOTE: we don't really support this flag
+ */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* only valid for bound all sockets */
+ if ((SCTP_BASE_SYSCTL(sctp_auto_asconf) == 0) &&
+ (*mopt != 0)) {
+ /* forbidden by admin */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EPERM);
+ return (EPERM);
+ }
+ set_opt = SCTP_PCB_FLAGS_AUTO_ASCONF;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ break;
+ case SCTP_EXPLICIT_EOR:
+ set_opt = SCTP_PCB_FLAGS_EXPLICIT_EOR;
+ break;
+ case SCTP_USE_EXT_RCVINFO:
+ set_opt = SCTP_PCB_FLAGS_EXT_RCVINFO;
+ break;
+ case SCTP_I_WANT_MAPPED_V4_ADDR:
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ set_opt = SCTP_PCB_FLAGS_NEEDS_MAPPED_V4;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ break;
+ case SCTP_NODELAY:
+ set_opt = SCTP_PCB_FLAGS_NODELAY;
+ break;
+ case SCTP_AUTOCLOSE:
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ set_opt = SCTP_PCB_FLAGS_AUTOCLOSE;
+ /*
+ * The value is in ticks. Note this does not effect
+ * old associations, only new ones.
+ */
+ inp->sctp_ep.auto_close_time = sctp_secs_to_ticks(*mopt);
+ break;
+ }
+ SCTP_INP_WLOCK(inp);
+ if (*mopt != 0) {
+ sctp_feature_on(inp, set_opt);
+ } else {
+ sctp_feature_off(inp, set_opt);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ case SCTP_REUSE_PORT:
+ {
+ SCTP_CHECK_AND_CAST(mopt, optval, uint32_t, optsize);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) {
+ /* Can't set it after we are bound */
+ error = EINVAL;
+ break;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE)) {
+ /* Can't do this for a 1-m socket */
+ error = EINVAL;
+ break;
+ }
+ if (optval)
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE);
+ else
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_PORTREUSE);
+ break;
+ }
+ case SCTP_PARTIAL_DELIVERY_POINT:
+ {
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, optsize);
+ if (*value > SCTP_SB_LIMIT_RCV(so)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ inp->partial_delivery_point = *value;
+ break;
+ }
+ case SCTP_FRAGMENT_INTERLEAVE:
+ /* not yet until we re-write sctp_recvmsg() */
+ {
+ uint32_t *level;
+
+ SCTP_CHECK_AND_CAST(level, optval, uint32_t, optsize);
+ if (*level == SCTP_FRAG_LEVEL_2) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE);
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS);
+ } else if (*level == SCTP_FRAG_LEVEL_1) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE);
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS);
+ } else if (*level == SCTP_FRAG_LEVEL_0) {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE);
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS);
+
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ break;
+ }
+ case SCTP_INTERLEAVING_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->idata_supported = 0;
+ } else {
+ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE)) &&
+ (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS))) {
+ inp->idata_supported = 1;
+ } else {
+ /* Must have Frag interleave and stream interleave on */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_CMT_ON_OFF:
+ if (SCTP_BASE_SYSCTL(sctp_cmt_on_off)) {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ if (av->assoc_value > SCTP_CMT_MAX) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ stcb->asoc.sctp_cmt_on_off = av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_FUTURE_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_cmt_on_off = av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_CURRENT_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.sctp_cmt_on_off = av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOPROTOOPT);
+ error = ENOPROTOOPT;
+ }
+ break;
+ case SCTP_PLUGGABLE_CC:
+ {
+ struct sctp_assoc_value *av;
+ struct sctp_nets *net;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ if ((av->assoc_value != SCTP_CC_RFC2581) &&
+ (av->assoc_value != SCTP_CC_HSTCP) &&
+ (av->assoc_value != SCTP_CC_HTCP) &&
+ (av->assoc_value != SCTP_CC_RTCC)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ stcb->asoc.cc_functions = sctp_cc_functions[av->assoc_value];
+ stcb->asoc.congestion_control_module = av->assoc_value;
+ if (stcb->asoc.cc_functions.sctp_set_initial_cc_param != NULL) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb, net);
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_FUTURE_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.sctp_default_cc_module = av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_CURRENT_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.cc_functions = sctp_cc_functions[av->assoc_value];
+ stcb->asoc.congestion_control_module = av->assoc_value;
+ if (stcb->asoc.cc_functions.sctp_set_initial_cc_param != NULL) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb, net);
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_CC_OPTION:
+ {
+ struct sctp_cc_option *cc_opt;
+
+ SCTP_CHECK_AND_CAST(cc_opt, optval, struct sctp_cc_option, optsize);
+ SCTP_FIND_STCB(inp, stcb, cc_opt->aid_value.assoc_id);
+ if (stcb == NULL) {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (cc_opt->aid_value.assoc_id == SCTP_CURRENT_ASSOC)) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ if (stcb->asoc.cc_functions.sctp_cwnd_socket_option) {
+ (*stcb->asoc.cc_functions.sctp_cwnd_socket_option)(stcb, 1, cc_opt);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ error = EINVAL;
+ }
+ } else {
+ if (stcb->asoc.cc_functions.sctp_cwnd_socket_option == NULL) {
+ error = ENOTSUP;
+ } else {
+ error = (*stcb->asoc.cc_functions.sctp_cwnd_socket_option)(stcb, 1,
+ cc_opt);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ break;
+ }
+ case SCTP_STREAM_SCHEDULER:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ if ((av->assoc_value != SCTP_SS_DEFAULT) &&
+ (av->assoc_value != SCTP_SS_RR) &&
+ (av->assoc_value != SCTP_SS_RR_PKT) &&
+ (av->assoc_value != SCTP_SS_PRIO) &&
+ (av->assoc_value != SCTP_SS_FB) &&
+ (av->assoc_value != SCTP_SS_FCFS)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ stcb->asoc.ss_functions.sctp_ss_clear(stcb, &stcb->asoc, true);
+ stcb->asoc.ss_functions = sctp_ss_functions[av->assoc_value];
+ stcb->asoc.stream_scheduling_module = av->assoc_value;
+ stcb->asoc.ss_functions.sctp_ss_init(stcb, &stcb->asoc);
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_FUTURE_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.sctp_default_ss_module = av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_CURRENT_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.ss_functions.sctp_ss_clear(stcb, &stcb->asoc, true);
+ stcb->asoc.ss_functions = sctp_ss_functions[av->assoc_value];
+ stcb->asoc.stream_scheduling_module = av->assoc_value;
+ stcb->asoc.ss_functions.sctp_ss_init(stcb, &stcb->asoc);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_STREAM_SCHEDULER_VALUE:
+ {
+ struct sctp_stream_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_stream_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ if ((av->stream_id >= stcb->asoc.streamoutcnt) ||
+ (stcb->asoc.ss_functions.sctp_ss_set_value(stcb, &stcb->asoc, &stcb->asoc.strmout[av->stream_id],
+ av->stream_value) < 0)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_CURRENT_ASSOC)) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ if (av->stream_id < stcb->asoc.streamoutcnt) {
+ stcb->asoc.ss_functions.sctp_ss_set_value(stcb,
+ &stcb->asoc,
+ &stcb->asoc.strmout[av->stream_id],
+ av->stream_value);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ /* Can't set stream value without association */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_CLR_STAT_LOG:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+ break;
+ case SCTP_CONTEXT:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ stcb->asoc.context = av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_FUTURE_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_context = av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_CURRENT_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.context = av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_VRF_ID:
+ {
+ uint32_t *default_vrfid;
+#ifdef SCTP_MVRF
+ int i;
+#endif
+ SCTP_CHECK_AND_CAST(default_vrfid, optval, uint32_t, optsize);
+ if (*default_vrfid > SCTP_MAX_VRF_ID) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ /* The VRF must be in the VRF list */
+ if (*default_vrfid == inp->m_vrf_ids[i]) {
+ SCTP_INP_WLOCK(inp);
+ inp->def_vrf_id = *default_vrfid;
+ SCTP_INP_WUNLOCK(inp);
+ goto sctp_done;
+ }
+ }
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+#else
+ inp->def_vrf_id = *default_vrfid;
+#endif
+#ifdef SCTP_MVRF
+ sctp_done:
+#endif
+ break;
+ }
+ case SCTP_DEL_VRF_ID:
+ {
+#ifdef SCTP_MVRF
+ uint32_t *del_vrfid;
+ int i, fnd = 0;
+
+ SCTP_CHECK_AND_CAST(del_vrfid, optval, uint32_t, optsize);
+ if (*del_vrfid > SCTP_MAX_VRF_ID) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if (inp->num_vrfs == 1) {
+ /* Can't delete last one */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) {
+ /* Can't add more once you are bound */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ SCTP_INP_WLOCK(inp);
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (*del_vrfid == inp->m_vrf_ids[i]) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if (i != (inp->num_vrfs - 1)) {
+ /* Take bottom one and move to this slot */
+ inp->m_vrf_ids[i] = inp->m_vrf_ids[(inp->num_vrfs-1)];
+ }
+ if (*del_vrfid == inp->def_vrf_id) {
+ /* Take the first one as the new default */
+ inp->def_vrf_id = inp->m_vrf_ids[0];
+ }
+ /* Drop the number by one killing last one */
+ inp->num_vrfs--;
+#else
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+#endif
+ break;
+ }
+ case SCTP_ADD_VRF_ID:
+ {
+#ifdef SCTP_MVRF
+ uint32_t *add_vrfid;
+ int i;
+
+ SCTP_CHECK_AND_CAST(add_vrfid, optval, uint32_t, optsize);
+ if (*add_vrfid > SCTP_MAX_VRF_ID) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) {
+ /* Can't add more once you are bound */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ SCTP_INP_WLOCK(inp);
+ /* Verify its not already here */
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (*add_vrfid == inp->m_vrf_ids[i]) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ }
+ if ((inp->num_vrfs + 1) > inp->vrf_size) {
+ /* need to grow array */
+ uint32_t *tarray;
+ SCTP_MALLOC(tarray, uint32_t *,
+ (sizeof(uint32_t) * (inp->vrf_size + SCTP_DEFAULT_VRF_SIZE)),
+ SCTP_M_MVRF);
+ if (tarray == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOMEM);
+ error = ENOMEM;
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ memcpy(tarray, inp->m_vrf_ids, (sizeof(uint32_t) * inp->vrf_size));
+ SCTP_FREE(inp->m_vrf_ids, SCTP_M_MVRF);
+ inp->m_vrf_ids = tarray;
+ inp->vrf_size += SCTP_DEFAULT_VRF_SIZE;
+ }
+ inp->m_vrf_ids[inp->num_vrfs] = *add_vrfid;
+ inp->num_vrfs++;
+ SCTP_INP_WUNLOCK(inp);
+#else
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+#endif
+ break;
+ }
+ case SCTP_DELAYED_SACK:
+ {
+ struct sctp_sack_info *sack;
+
+ SCTP_CHECK_AND_CAST(sack, optval, struct sctp_sack_info, optsize);
+ SCTP_FIND_STCB(inp, stcb, sack->sack_assoc_id);
+ if (sack->sack_delay) {
+ if (sack->sack_delay > SCTP_MAX_SACK_DELAY) {
+ error = EINVAL;
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ break;
+ }
+ }
+ if (stcb) {
+ if (sack->sack_delay) {
+ stcb->asoc.delayed_ack = sack->sack_delay;
+ }
+ if (sack->sack_freq) {
+ stcb->asoc.sack_freq = sack->sack_freq;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((sack->sack_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (sack->sack_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ if (sack->sack_delay) {
+ inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV] = sctp_msecs_to_ticks(sack->sack_delay);
+ }
+ if (sack->sack_freq) {
+ inp->sctp_ep.sctp_sack_freq = sack->sack_freq;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((sack->sack_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (sack->sack_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ if (sack->sack_delay) {
+ stcb->asoc.delayed_ack = sack->sack_delay;
+ }
+ if (sack->sack_freq) {
+ stcb->asoc.sack_freq = sack->sack_freq;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_AUTH_CHUNK:
+ {
+ struct sctp_authchunk *sauth;
+
+ SCTP_CHECK_AND_CAST(sauth, optval, struct sctp_authchunk, optsize);
+
+ SCTP_INP_WLOCK(inp);
+ if (sctp_auth_add_chunk(sauth->sauth_chunk, inp->sctp_ep.local_auth_chunks)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ inp->auth_supported = 1;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ case SCTP_AUTH_KEY:
+ {
+ struct sctp_authkey *sca;
+ struct sctp_keyhead *shared_keys;
+ sctp_sharedkey_t *shared_key;
+ sctp_key_t *key = NULL;
+ size_t size;
+
+ SCTP_CHECK_AND_CAST(sca, optval, struct sctp_authkey, optsize);
+ if (sca->sca_keylength == 0) {
+ size = optsize - sizeof(struct sctp_authkey);
+ } else {
+ if (sca->sca_keylength + sizeof(struct sctp_authkey) <= optsize) {
+ size = sca->sca_keylength;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ }
+ SCTP_FIND_STCB(inp, stcb, sca->sca_assoc_id);
+
+ if (stcb) {
+ shared_keys = &stcb->asoc.shared_keys;
+ /* clear the cached keys for this key id */
+ sctp_clear_cachedkeys(stcb, sca->sca_keynumber);
+ /*
+ * create the new shared key and
+ * insert/replace it
+ */
+ if (size > 0) {
+ key = sctp_set_key(sca->sca_key, (uint32_t) size);
+ if (key == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOMEM);
+ error = ENOMEM;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ }
+ shared_key = sctp_alloc_sharedkey();
+ if (shared_key == NULL) {
+ sctp_free_key(key);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOMEM);
+ error = ENOMEM;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ shared_key->key = key;
+ shared_key->keyid = sca->sca_keynumber;
+ error = sctp_insert_sharedkey(shared_keys, shared_key);
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((sca->sca_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (sca->sca_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ shared_keys = &inp->sctp_ep.shared_keys;
+ /*
+ * clear the cached keys on all assocs for
+ * this key id
+ */
+ sctp_clear_cachedkeys_ep(inp, sca->sca_keynumber);
+ /*
+ * create the new shared key and
+ * insert/replace it
+ */
+ if (size > 0) {
+ key = sctp_set_key(sca->sca_key, (uint32_t) size);
+ if (key == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOMEM);
+ error = ENOMEM;
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ }
+ shared_key = sctp_alloc_sharedkey();
+ if (shared_key == NULL) {
+ sctp_free_key(key);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOMEM);
+ error = ENOMEM;
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ shared_key->key = key;
+ shared_key->keyid = sca->sca_keynumber;
+ error = sctp_insert_sharedkey(shared_keys, shared_key);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((sca->sca_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (sca->sca_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ shared_keys = &stcb->asoc.shared_keys;
+ /* clear the cached keys for this key id */
+ sctp_clear_cachedkeys(stcb, sca->sca_keynumber);
+ /*
+ * create the new shared key and
+ * insert/replace it
+ */
+ if (size > 0) {
+ key = sctp_set_key(sca->sca_key, (uint32_t) size);
+ if (key == NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ }
+ shared_key = sctp_alloc_sharedkey();
+ if (shared_key == NULL) {
+ sctp_free_key(key);
+ SCTP_TCB_UNLOCK(stcb);
+ continue;
+ }
+ shared_key->key = key;
+ shared_key->keyid = sca->sca_keynumber;
+ error = sctp_insert_sharedkey(shared_keys, shared_key);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_HMAC_IDENT:
+ {
+ struct sctp_hmacalgo *shmac;
+ sctp_hmaclist_t *hmaclist;
+ uint16_t hmacid;
+ uint32_t i;
+
+ SCTP_CHECK_AND_CAST(shmac, optval, struct sctp_hmacalgo, optsize);
+ if ((optsize < sizeof(struct sctp_hmacalgo) + shmac->shmac_number_of_idents * sizeof(uint16_t)) ||
+ (shmac->shmac_number_of_idents > 0xffff)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+
+ hmaclist = sctp_alloc_hmaclist((uint16_t)shmac->shmac_number_of_idents);
+ if (hmaclist == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOMEM);
+ error = ENOMEM;
+ break;
+ }
+ for (i = 0; i < shmac->shmac_number_of_idents; i++) {
+ hmacid = shmac->shmac_idents[i];
+ if (sctp_auth_add_hmacid(hmaclist, hmacid)) {
+ /* invalid HMACs were found */;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ sctp_free_hmaclist(hmaclist);
+ goto sctp_set_hmac_done;
+ }
+ }
+ for (i = 0; i < hmaclist->num_algo; i++) {
+ if (hmaclist->hmac[i] == SCTP_AUTH_HMAC_ID_SHA1) {
+ /* already in list */
+ break;
+ }
+ }
+ if (i == hmaclist->num_algo) {
+ /* not found in list */
+ sctp_free_hmaclist(hmaclist);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ /* set it on the endpoint */
+ SCTP_INP_WLOCK(inp);
+ if (inp->sctp_ep.local_hmacs)
+ sctp_free_hmaclist(inp->sctp_ep.local_hmacs);
+ inp->sctp_ep.local_hmacs = hmaclist;
+ SCTP_INP_WUNLOCK(inp);
+ sctp_set_hmac_done:
+ break;
+ }
+ case SCTP_AUTH_ACTIVE_KEY:
+ {
+ struct sctp_authkeyid *scact;
+
+ SCTP_CHECK_AND_CAST(scact, optval, struct sctp_authkeyid, optsize);
+ SCTP_FIND_STCB(inp, stcb, scact->scact_assoc_id);
+
+ /* set the active key on the right place */
+ if (stcb) {
+ /* set the active key on the assoc */
+ if (sctp_auth_setactivekey(stcb,
+ scact->scact_keynumber)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL,
+ SCTP_FROM_SCTP_USRREQ,
+ EINVAL);
+ error = EINVAL;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((scact->scact_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (scact->scact_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ if (sctp_auth_setactivekey_ep(inp, scact->scact_keynumber)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((scact->scact_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (scact->scact_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ sctp_auth_setactivekey(stcb, scact->scact_keynumber);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_AUTH_DELETE_KEY:
+ {
+ struct sctp_authkeyid *scdel;
+
+ SCTP_CHECK_AND_CAST(scdel, optval, struct sctp_authkeyid, optsize);
+ SCTP_FIND_STCB(inp, stcb, scdel->scact_assoc_id);
+
+ /* delete the key from the right place */
+ if (stcb) {
+ if (sctp_delete_sharedkey(stcb, scdel->scact_keynumber)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((scdel->scact_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (scdel->scact_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ if (sctp_delete_sharedkey_ep(inp, scdel->scact_keynumber)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((scdel->scact_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (scdel->scact_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ sctp_delete_sharedkey(stcb, scdel->scact_keynumber);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_AUTH_DEACTIVATE_KEY:
+ {
+ struct sctp_authkeyid *keyid;
+
+ SCTP_CHECK_AND_CAST(keyid, optval, struct sctp_authkeyid, optsize);
+ SCTP_FIND_STCB(inp, stcb, keyid->scact_assoc_id);
+
+ /* deactivate the key from the right place */
+ if (stcb) {
+ if (sctp_deact_sharedkey(stcb, keyid->scact_keynumber)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((keyid->scact_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (keyid->scact_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ if (sctp_deact_sharedkey_ep(inp, keyid->scact_keynumber)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((keyid->scact_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (keyid->scact_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ sctp_deact_sharedkey(stcb, keyid->scact_keynumber);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_ENABLE_STREAM_RESET:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ if (av->assoc_value & (~SCTP_ENABLE_VALUE_MASK)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+ if (stcb) {
+ stcb->asoc.local_strreset_support = (uint8_t)av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_FUTURE_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->local_strreset_support = (uint8_t)av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_CURRENT_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.local_strreset_support = (uint8_t)av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_RESET_STREAMS:
+ {
+ struct sctp_reset_streams *strrst;
+ int i, send_out = 0;
+ int send_in = 0;
+
+ SCTP_CHECK_AND_CAST(strrst, optval, struct sctp_reset_streams, optsize);
+ SCTP_FIND_STCB(inp, stcb, strrst->srs_assoc_id);
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ break;
+ }
+ if (stcb->asoc.reconfig_supported == 0) {
+ /*
+ * Peer does not support the chunk type.
+ */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_OPEN) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (sizeof(struct sctp_reset_streams) +
+ strrst->srs_number_streams * sizeof(uint16_t) > optsize) {
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (strrst->srs_flags & SCTP_STREAM_RESET_INCOMING) {
+ send_in = 1;
+ if (stcb->asoc.stream_reset_outstanding) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ }
+ if (strrst->srs_flags & SCTP_STREAM_RESET_OUTGOING) {
+ send_out = 1;
+ }
+ if ((strrst->srs_number_streams > SCTP_MAX_STREAMS_AT_ONCE_RESET) && send_in) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOMEM);
+ error = ENOMEM;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if ((send_in == 0) && (send_out == 0)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ for (i = 0; i < strrst->srs_number_streams; i++) {
+ if ((send_in) &&
+ (strrst->srs_stream_list[i] >= stcb->asoc.streamincnt)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if ((send_out) &&
+ (strrst->srs_stream_list[i] >= stcb->asoc.streamoutcnt)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ }
+ if (error) {
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (send_out) {
+ int cnt;
+ uint16_t strm;
+ if (strrst->srs_number_streams) {
+ for (i = 0, cnt = 0; i < strrst->srs_number_streams; i++) {
+ strm = strrst->srs_stream_list[i];
+ if (stcb->asoc.strmout[strm].state == SCTP_STREAM_OPEN) {
+ stcb->asoc.strmout[strm].state = SCTP_STREAM_RESET_PENDING;
+ cnt++;
+ }
+ }
+ } else {
+ /* Its all */
+ for (i = 0, cnt = 0; i < stcb->asoc.streamoutcnt; i++) {
+ if (stcb->asoc.strmout[i].state == SCTP_STREAM_OPEN) {
+ stcb->asoc.strmout[i].state = SCTP_STREAM_RESET_PENDING;
+ cnt++;
+ }
+ }
+ }
+ }
+ if (send_in) {
+ error = sctp_send_str_reset_req(stcb, strrst->srs_number_streams,
+ strrst->srs_stream_list,
+ send_in, 0, 0, 0, 0, 0);
+ } else {
+ error = sctp_send_stream_reset_out_if_possible(stcb, SCTP_SO_LOCKED);
+ }
+ if (error == 0) {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_REQ, SCTP_SO_LOCKED);
+ } else {
+ /*
+ * For outgoing streams don't report any problems in
+ * sending the request to the application.
+ * XXX: Double check resetting incoming streams.
+ */
+ error = 0;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ case SCTP_ADD_STREAMS:
+ {
+ struct sctp_add_streams *stradd;
+ uint8_t addstream = 0;
+ uint16_t add_o_strmcnt = 0;
+ uint16_t add_i_strmcnt = 0;
+
+ SCTP_CHECK_AND_CAST(stradd, optval, struct sctp_add_streams, optsize);
+ SCTP_FIND_STCB(inp, stcb, stradd->sas_assoc_id);
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ break;
+ }
+ if (stcb->asoc.reconfig_supported == 0) {
+ /*
+ * Peer does not support the chunk type.
+ */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_OPEN) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (stcb->asoc.stream_reset_outstanding) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if ((stradd->sas_outstrms == 0) &&
+ (stradd->sas_instrms == 0)) {
+ error = EINVAL;
+ goto skip_stuff;
+ }
+ if (stradd->sas_outstrms) {
+ addstream = 1;
+ /* We allocate here */
+ add_o_strmcnt = stradd->sas_outstrms;
+ if ((((int)add_o_strmcnt) + ((int)stcb->asoc.streamoutcnt)) > 0x0000ffff) {
+ /* You can't have more than 64k */
+ error = EINVAL;
+ goto skip_stuff;
+ }
+ }
+ if (stradd->sas_instrms) {
+ int cnt;
+
+ addstream |= 2;
+ /* We allocate inside sctp_send_str_reset_req() */
+ add_i_strmcnt = stradd->sas_instrms;
+ cnt = add_i_strmcnt;
+ cnt += stcb->asoc.streamincnt;
+ if (cnt > 0x0000ffff) {
+ /* You can't have more than 64k */
+ error = EINVAL;
+ goto skip_stuff;
+ }
+ if (cnt > (int)stcb->asoc.max_inbound_streams) {
+ /* More than you are allowed */
+ error = EINVAL;
+ goto skip_stuff;
+ }
+ }
+ error = sctp_send_str_reset_req(stcb, 0, NULL, 0, 0, addstream, add_o_strmcnt, add_i_strmcnt, 0);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_REQ, SCTP_SO_LOCKED);
+ skip_stuff:
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ case SCTP_RESET_ASSOC:
+ {
+ int i;
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, optsize);
+ SCTP_FIND_STCB(inp, stcb, (sctp_assoc_t) *value);
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ break;
+ }
+ if (stcb->asoc.reconfig_supported == 0) {
+ /*
+ * Peer does not support the chunk type.
+ */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ error = EOPNOTSUPP;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (SCTP_GET_STATE(stcb) != SCTP_STATE_OPEN) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (stcb->asoc.stream_reset_outstanding) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ /* Is there any data pending in the send or sent queues? */
+ if (!TAILQ_EMPTY(&stcb->asoc.send_queue) ||
+ !TAILQ_EMPTY(&stcb->asoc.sent_queue)) {
+ busy_out:
+ error = EBUSY;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ /* Do any streams have data queued? */
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ if (!TAILQ_EMPTY(&stcb->asoc.strmout[i].outqueue)) {
+ goto busy_out;
+ }
+ }
+ error = sctp_send_str_reset_req(stcb, 0, NULL, 0, 1, 0, 0, 0, 0);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_REQ, SCTP_SO_LOCKED);
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ case SCTP_CONNECT_X:
+ if (optsize < (sizeof(int) + sizeof(struct sockaddr_in))) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ error = sctp_do_connect_x(so, inp, optval, optsize, p, 0);
+ break;
+ case SCTP_CONNECT_X_DELAYED:
+ if (optsize < (sizeof(int) + sizeof(struct sockaddr_in))) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ error = sctp_do_connect_x(so, inp, optval, optsize, p, 1);
+ break;
+ case SCTP_CONNECT_X_COMPLETE:
+ {
+ struct sockaddr *sa;
+
+ /* FIXME MT: check correct? */
+ SCTP_CHECK_AND_CAST(sa, optval, struct sockaddr, optsize);
+
+ /* find tcb */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ SCTP_INP_RLOCK(inp);
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, sa, NULL, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+
+ if (stcb == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ break;
+ }
+ if (stcb->asoc.delayed_connection == 1) {
+ stcb->asoc.delayed_connection = 0;
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb,
+ stcb->asoc.primary_destination,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_8);
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ } else {
+ /*
+ * already expired or did not use delayed
+ * connectx
+ */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ case SCTP_MAX_BURST:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ stcb->asoc.max_burst = av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_FUTURE_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.max_burst = av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((av->assoc_id == SCTP_CURRENT_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.max_burst = av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_MAXSEG:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ stcb->asoc.sctp_frag_point = av->assoc_value;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_frag_point = av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_EVENTS:
+ {
+ struct sctp_event_subscribe *events;
+
+ SCTP_CHECK_AND_CAST(events, optval, struct sctp_event_subscribe, optsize);
+
+ SCTP_INP_WLOCK(inp);
+ if (events->sctp_data_io_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT);
+ }
+
+ if (events->sctp_association_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVASSOCEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVASSOCEVNT);
+ }
+
+ if (events->sctp_address_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVPADDREVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVPADDREVNT);
+ }
+
+ if (events->sctp_send_failure_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVSENDFAILEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVSENDFAILEVNT);
+ }
+
+ if (events->sctp_peer_error_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVPEERERR);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVPEERERR);
+ }
+
+ if (events->sctp_shutdown_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT);
+ }
+
+ if (events->sctp_partial_delivery_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_PDAPIEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_PDAPIEVNT);
+ }
+
+ if (events->sctp_adaptation_layer_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_ADAPTATIONEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_ADAPTATIONEVNT);
+ }
+
+ if (events->sctp_authentication_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_AUTHEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTHEVNT);
+ }
+
+ if (events->sctp_sender_dry_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_DRYEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_DRYEVNT);
+ }
+
+ if (events->sctp_stream_reset_event) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_STREAM_RESETEVNT);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_STREAM_RESETEVNT);
+ }
+
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ if (events->sctp_association_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_RECVASSOCEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_RECVASSOCEVNT);
+ }
+ if (events->sctp_address_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_RECVPADDREVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_RECVPADDREVNT);
+ }
+ if (events->sctp_send_failure_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_RECVSENDFAILEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_RECVSENDFAILEVNT);
+ }
+ if (events->sctp_peer_error_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_RECVPEERERR);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_RECVPEERERR);
+ }
+ if (events->sctp_shutdown_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT);
+ }
+ if (events->sctp_partial_delivery_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_PDAPIEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_PDAPIEVNT);
+ }
+ if (events->sctp_adaptation_layer_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_ADAPTATIONEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_ADAPTATIONEVNT);
+ }
+ if (events->sctp_authentication_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_AUTHEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_AUTHEVNT);
+ }
+ if (events->sctp_sender_dry_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_DRYEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_DRYEVNT);
+ }
+ if (events->sctp_stream_reset_event) {
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_STREAM_RESETEVNT);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_STREAM_RESETEVNT);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ /* Send up the sender dry event only for 1-to-1 style sockets. */
+ if (events->sctp_sender_dry_event) {
+ if (((inp->sctp_flags & (SCTP_PCB_FLAGS_TCPTYPE | SCTP_PCB_FLAGS_IN_TCPPOOL)) != 0) &&
+ !SCTP_IS_LISTENING(inp)) {
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK(stcb);
+ if (TAILQ_EMPTY(&stcb->asoc.send_queue) &&
+ TAILQ_EMPTY(&stcb->asoc.sent_queue) &&
+ (stcb->asoc.stream_queue_cnt == 0)) {
+ sctp_ulp_notify(SCTP_NOTIFY_SENDER_DRY, stcb, 0, NULL, SCTP_SO_LOCKED);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+ }
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ case SCTP_ADAPTATION_LAYER:
+ {
+ struct sctp_setadaptation *adap_bits;
+
+ SCTP_CHECK_AND_CAST(adap_bits, optval, struct sctp_setadaptation, optsize);
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.adaptation_layer_indicator = adap_bits->ssb_adaptation_ind;
+ inp->sctp_ep.adaptation_layer_indicator_provided = 1;
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+#ifdef SCTP_DEBUG
+ case SCTP_SET_INITIAL_DBG_SEQ:
+ {
+ uint32_t *vvv;
+
+ SCTP_CHECK_AND_CAST(vvv, optval, uint32_t, optsize);
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.initial_sequence_debug = *vvv;
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+#endif
+ case SCTP_DEFAULT_SEND_PARAM:
+ {
+ struct sctp_sndrcvinfo *s_info;
+
+ SCTP_CHECK_AND_CAST(s_info, optval, struct sctp_sndrcvinfo, optsize);
+ SCTP_FIND_STCB(inp, stcb, s_info->sinfo_assoc_id);
+
+ if (stcb) {
+ if (s_info->sinfo_stream < stcb->asoc.streamoutcnt) {
+ memcpy(&stcb->asoc.def_send, s_info, min(optsize, sizeof(stcb->asoc.def_send)));
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((s_info->sinfo_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (s_info->sinfo_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ memcpy(&inp->def_send, s_info, min(optsize, sizeof(inp->def_send)));
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((s_info->sinfo_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (s_info->sinfo_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ if (s_info->sinfo_stream < stcb->asoc.streamoutcnt) {
+ memcpy(&stcb->asoc.def_send, s_info, min(optsize, sizeof(stcb->asoc.def_send)));
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_PEER_ADDR_PARAMS:
+ {
+ struct sctp_paddrparams *paddrp;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(paddrp, optval, struct sctp_paddrparams, optsize);
+ SCTP_FIND_STCB(inp, stcb, paddrp->spp_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (paddrp->spp_address.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&paddrp->spp_address;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&paddrp->spp_address;
+ }
+ } else {
+ addr = (struct sockaddr *)&paddrp->spp_address;
+ }
+#else
+ addr = (struct sockaddr *)&paddrp->spp_address;
+#endif
+ if (stcb != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr,
+ &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ if ((stcb != NULL) && (net == NULL)) {
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ if (sin->sin_addr.s_addr != INADDR_ANY) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)addr;
+ if (sconn->sconn_addr != NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ }
+ /* sanity checks */
+ if ((paddrp->spp_flags & SPP_HB_ENABLE) && (paddrp->spp_flags & SPP_HB_DISABLE)) {
+ if (stcb)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+ if ((paddrp->spp_flags & SPP_PMTUD_ENABLE) && (paddrp->spp_flags & SPP_PMTUD_DISABLE)) {
+ if (stcb)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if ((paddrp->spp_flags & SPP_PMTUD_DISABLE) &&
+ (paddrp->spp_pathmtu > 0) &&
+ ((paddrp->spp_pathmtu < SCTP_SMALLEST_PMTU) ||
+ (paddrp->spp_pathmtu > SCTP_LARGEST_PMTU))) {
+ if (stcb)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+ if (stcb != NULL) {
+ /************************TCB SPECIFIC SET ******************/
+ if (net != NULL) {
+ /************************NET SPECIFIC SET ******************/
+ if (paddrp->spp_flags & SPP_HB_DISABLE) {
+ if (((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) &&
+ ((net->dest_state & SCTP_ADDR_NOHB) == 0)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_9);
+ }
+ net->dest_state |= SCTP_ADDR_NOHB;
+ }
+ if (paddrp->spp_flags & SPP_HB_ENABLE) {
+ if (paddrp->spp_hbinterval) {
+ net->heart_beat_delay = paddrp->spp_hbinterval;
+ } else if (paddrp->spp_flags & SPP_HB_TIME_IS_ZERO) {
+ net->heart_beat_delay = 0;
+ }
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_10);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net);
+ net->dest_state &= ~SCTP_ADDR_NOHB;
+ }
+ if (paddrp->spp_flags & SPP_HB_DEMAND) {
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+ sctp_send_hb(stcb, net, SCTP_SO_LOCKED);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SOCKOPT, SCTP_SO_LOCKED);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net);
+ }
+ }
+ if (paddrp->spp_flags & SPP_PMTUD_DISABLE) {
+ if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_11);
+ }
+ net->dest_state |= SCTP_ADDR_NO_PMTUD;
+ if (paddrp->spp_pathmtu > 0) {
+ net->mtu = paddrp->spp_pathmtu;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ net->mtu += SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ net->mtu += SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ net->mtu += sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ break;
+ }
+ if (net->mtu < stcb->asoc.smallest_mtu) {
+ sctp_pathmtu_adjustment(stcb, net->mtu, true);
+ }
+ }
+ }
+ if (paddrp->spp_flags & SPP_PMTUD_ENABLE) {
+ if (!SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net);
+ }
+ net->dest_state &= ~SCTP_ADDR_NO_PMTUD;
+ }
+ if (paddrp->spp_pathmaxrxt > 0) {
+ if (net->dest_state & SCTP_ADDR_PF) {
+ if (net->error_count > paddrp->spp_pathmaxrxt) {
+ net->dest_state &= ~SCTP_ADDR_PF;
+ }
+ } else {
+ if ((net->error_count <= paddrp->spp_pathmaxrxt) &&
+ (net->error_count > net->pf_threshold)) {
+ net->dest_state |= SCTP_ADDR_PF;
+ sctp_send_hb(stcb, net, SCTP_SO_LOCKED);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_12);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net);
+ }
+ }
+ if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ if (net->error_count > paddrp->spp_pathmaxrxt) {
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ } else {
+ if (net->error_count <= paddrp->spp_pathmaxrxt) {
+ net->dest_state |= SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ }
+ net->failure_threshold = paddrp->spp_pathmaxrxt;
+ }
+ if (paddrp->spp_flags & SPP_DSCP) {
+ net->dscp = paddrp->spp_dscp & 0xfc;
+ net->dscp |= 0x01;
+ }
+#ifdef INET6
+ if (paddrp->spp_flags & SPP_IPV6_FLOWLABEL) {
+ if (net->ro._l_addr.sa.sa_family == AF_INET6) {
+ net->flowlabel = paddrp->spp_ipv6_flowlabel & 0x000fffff;
+ net->flowlabel |= 0x80000000;
+ }
+ }
+#endif
+ } else {
+ /************************ASSOC ONLY -- NO NET SPECIFIC SET ******************/
+ if (paddrp->spp_pathmaxrxt > 0) {
+ stcb->asoc.def_net_failure = paddrp->spp_pathmaxrxt;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (net->dest_state & SCTP_ADDR_PF) {
+ if (net->error_count > paddrp->spp_pathmaxrxt) {
+ net->dest_state &= ~SCTP_ADDR_PF;
+ }
+ } else {
+ if ((net->error_count <= paddrp->spp_pathmaxrxt) &&
+ (net->error_count > net->pf_threshold)) {
+ net->dest_state |= SCTP_ADDR_PF;
+ sctp_send_hb(stcb, net, SCTP_SO_LOCKED);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_13);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net);
+ }
+ }
+ if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ if (net->error_count > paddrp->spp_pathmaxrxt) {
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ } else {
+ if (net->error_count <= paddrp->spp_pathmaxrxt) {
+ net->dest_state |= SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ }
+ net->failure_threshold = paddrp->spp_pathmaxrxt;
+ }
+ }
+ if (paddrp->spp_flags & SPP_HB_ENABLE) {
+ if (paddrp->spp_hbinterval != 0) {
+ stcb->asoc.heart_beat_delay = paddrp->spp_hbinterval;
+ } else if (paddrp->spp_flags & SPP_HB_TIME_IS_ZERO) {
+ stcb->asoc.heart_beat_delay = 0;
+ }
+ /* Turn back on the timer */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (paddrp->spp_hbinterval != 0) {
+ net->heart_beat_delay = paddrp->spp_hbinterval;
+ } else if (paddrp->spp_flags & SPP_HB_TIME_IS_ZERO) {
+ net->heart_beat_delay = 0;
+ }
+ if (net->dest_state & SCTP_ADDR_NOHB) {
+ net->dest_state &= ~SCTP_ADDR_NOHB;
+ }
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_14);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net);
+ }
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_DONOT_HEARTBEAT);
+ }
+ if (paddrp->spp_flags & SPP_HB_DISABLE) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if ((net->dest_state & SCTP_ADDR_NOHB) == 0) {
+ net->dest_state |= SCTP_ADDR_NOHB;
+ if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_15);
+ }
+ }
+ }
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_DONOT_HEARTBEAT);
+ }
+ if (paddrp->spp_flags & SPP_PMTUD_DISABLE) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_16);
+ }
+ net->dest_state |= SCTP_ADDR_NO_PMTUD;
+ if (paddrp->spp_pathmtu > 0) {
+ net->mtu = paddrp->spp_pathmtu;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ net->mtu += SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ net->mtu += SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ net->mtu += sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ break;
+ }
+ if (net->mtu < stcb->asoc.smallest_mtu) {
+ sctp_pathmtu_adjustment(stcb, net->mtu, true);
+ }
+ }
+ }
+ if (paddrp->spp_pathmtu > 0) {
+ stcb->asoc.default_mtu = paddrp->spp_pathmtu;
+ }
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_DO_NOT_PMTUD);
+ }
+ if (paddrp->spp_flags & SPP_PMTUD_ENABLE) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (!SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net);
+ }
+ net->dest_state &= ~SCTP_ADDR_NO_PMTUD;
+ }
+ stcb->asoc.default_mtu = 0;
+ sctp_stcb_feature_off(inp, stcb, SCTP_PCB_FLAGS_DO_NOT_PMTUD);
+ }
+ if (paddrp->spp_flags & SPP_DSCP) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ net->dscp = paddrp->spp_dscp & 0xfc;
+ net->dscp |= 0x01;
+ }
+ stcb->asoc.default_dscp = paddrp->spp_dscp & 0xfc;
+ stcb->asoc.default_dscp |= 0x01;
+ }
+#ifdef INET6
+ if (paddrp->spp_flags & SPP_IPV6_FLOWLABEL) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (net->ro._l_addr.sa.sa_family == AF_INET6) {
+ net->flowlabel = paddrp->spp_ipv6_flowlabel & 0x000fffff;
+ net->flowlabel |= 0x80000000;
+ }
+ }
+ stcb->asoc.default_flowlabel = paddrp->spp_ipv6_flowlabel & 0x000fffff;
+ stcb->asoc.default_flowlabel |= 0x80000000;
+ }
+#endif
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ /************************NO TCB, SET TO default stuff ******************/
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (paddrp->spp_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ /*
+ * For the TOS/FLOWLABEL stuff you set it
+ * with the options on the socket
+ */
+ if (paddrp->spp_pathmaxrxt > 0) {
+ inp->sctp_ep.def_net_failure = paddrp->spp_pathmaxrxt;
+ }
+
+ if (paddrp->spp_flags & SPP_HB_TIME_IS_ZERO)
+ inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = 0;
+ else if (paddrp->spp_hbinterval != 0) {
+ if (paddrp->spp_hbinterval > SCTP_MAX_HB_INTERVAL)
+ paddrp->spp_hbinterval= SCTP_MAX_HB_INTERVAL;
+ inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = sctp_msecs_to_ticks(paddrp->spp_hbinterval);
+ }
+
+ if (paddrp->spp_flags & SPP_HB_ENABLE) {
+ if (paddrp->spp_flags & SPP_HB_TIME_IS_ZERO) {
+ inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = 0;
+ } else if (paddrp->spp_hbinterval) {
+ inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = sctp_msecs_to_ticks(paddrp->spp_hbinterval);
+ }
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_DONOT_HEARTBEAT);
+ } else if (paddrp->spp_flags & SPP_HB_DISABLE) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_DONOT_HEARTBEAT);
+ }
+ if (paddrp->spp_flags & SPP_PMTUD_ENABLE) {
+ inp->sctp_ep.default_mtu = 0;
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_DO_NOT_PMTUD);
+ } else if (paddrp->spp_flags & SPP_PMTUD_DISABLE) {
+ if (paddrp->spp_pathmtu > 0) {
+ inp->sctp_ep.default_mtu = paddrp->spp_pathmtu;
+ }
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_DO_NOT_PMTUD);
+ }
+ if (paddrp->spp_flags & SPP_DSCP) {
+ inp->sctp_ep.default_dscp = paddrp->spp_dscp & 0xfc;
+ inp->sctp_ep.default_dscp |= 0x01;
+ }
+#ifdef INET6
+ if (paddrp->spp_flags & SPP_IPV6_FLOWLABEL) {
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ inp->sctp_ep.default_flowlabel = paddrp->spp_ipv6_flowlabel & 0x000fffff;
+ inp->sctp_ep.default_flowlabel |= 0x80000000;
+ }
+ }
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_RTOINFO:
+ {
+ struct sctp_rtoinfo *srto;
+ uint32_t new_init, new_min, new_max;
+
+ SCTP_CHECK_AND_CAST(srto, optval, struct sctp_rtoinfo, optsize);
+ SCTP_FIND_STCB(inp, stcb, srto->srto_assoc_id);
+
+ if (stcb) {
+ if (srto->srto_initial)
+ new_init = srto->srto_initial;
+ else
+ new_init = stcb->asoc.initial_rto;
+ if (srto->srto_max)
+ new_max = srto->srto_max;
+ else
+ new_max = stcb->asoc.maxrto;
+ if (srto->srto_min)
+ new_min = srto->srto_min;
+ else
+ new_min = stcb->asoc.minrto;
+ if ((new_min <= new_init) && (new_init <= new_max)) {
+ stcb->asoc.initial_rto = new_init;
+ stcb->asoc.maxrto = new_max;
+ stcb->asoc.minrto = new_min;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (srto->srto_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (srto->srto_initial)
+ new_init = srto->srto_initial;
+ else
+ new_init = inp->sctp_ep.initial_rto;
+ if (srto->srto_max)
+ new_max = srto->srto_max;
+ else
+ new_max = inp->sctp_ep.sctp_maxrto;
+ if (srto->srto_min)
+ new_min = srto->srto_min;
+ else
+ new_min = inp->sctp_ep.sctp_minrto;
+ if ((new_min <= new_init) && (new_init <= new_max)) {
+ inp->sctp_ep.initial_rto = new_init;
+ inp->sctp_ep.sctp_maxrto = new_max;
+ inp->sctp_ep.sctp_minrto = new_min;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_ASSOCINFO:
+ {
+ struct sctp_assocparams *sasoc;
+
+ SCTP_CHECK_AND_CAST(sasoc, optval, struct sctp_assocparams, optsize);
+ SCTP_FIND_STCB(inp, stcb, sasoc->sasoc_assoc_id);
+ if (sasoc->sasoc_cookie_life > 0) {
+ /* boundary check the cookie life */
+ if (sasoc->sasoc_cookie_life < SCTP_MIN_COOKIE_LIFE) {
+ sasoc->sasoc_cookie_life = SCTP_MIN_COOKIE_LIFE;
+ }
+ if (sasoc->sasoc_cookie_life > SCTP_MAX_COOKIE_LIFE) {
+ sasoc->sasoc_cookie_life = SCTP_MAX_COOKIE_LIFE;
+ }
+ }
+ if (stcb) {
+ if (sasoc->sasoc_asocmaxrxt > 0) {
+ stcb->asoc.max_send_times = sasoc->sasoc_asocmaxrxt;
+ }
+ if (sasoc->sasoc_cookie_life > 0) {
+ stcb->asoc.cookie_life = sctp_msecs_to_ticks(sasoc->sasoc_cookie_life);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (sasoc->sasoc_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (sasoc->sasoc_asocmaxrxt > 0) {
+ inp->sctp_ep.max_send_times = sasoc->sasoc_asocmaxrxt;
+ }
+ if (sasoc->sasoc_cookie_life > 0) {
+ inp->sctp_ep.def_cookie_life = sctp_msecs_to_ticks(sasoc->sasoc_cookie_life);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_INITMSG:
+ {
+ struct sctp_initmsg *sinit;
+
+ SCTP_CHECK_AND_CAST(sinit, optval, struct sctp_initmsg, optsize);
+ SCTP_INP_WLOCK(inp);
+ if (sinit->sinit_num_ostreams)
+ inp->sctp_ep.pre_open_stream_count = sinit->sinit_num_ostreams;
+
+ if (sinit->sinit_max_instreams)
+ inp->sctp_ep.max_open_streams_intome = sinit->sinit_max_instreams;
+
+ if (sinit->sinit_max_attempts)
+ inp->sctp_ep.max_init_times = sinit->sinit_max_attempts;
+
+ if (sinit->sinit_max_init_timeo)
+ inp->sctp_ep.initial_init_rto_max = sinit->sinit_max_init_timeo;
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ case SCTP_PRIMARY_ADDR:
+ {
+ struct sctp_setprim *spa;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(spa, optval, struct sctp_setprim, optsize);
+ SCTP_FIND_STCB(inp, stcb, spa->ssp_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (spa->ssp_addr.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&spa->ssp_addr;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&spa->ssp_addr;
+ }
+ } else {
+ addr = (struct sockaddr *)&spa->ssp_addr;
+ }
+#else
+ addr = (struct sockaddr *)&spa->ssp_addr;
+#endif
+ if (stcb != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr,
+ &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+
+ if ((stcb != NULL) && (net != NULL)) {
+ if (net != stcb->asoc.primary_destination) {
+ if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) {
+ /* Ok we need to set it */
+ if (sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, net) == 0) {
+ if ((stcb->asoc.alternate) &&
+ ((net->dest_state & SCTP_ADDR_PF) == 0) &&
+ (net->dest_state & SCTP_ADDR_REACHABLE)) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ break;
+ }
+ case SCTP_SET_DYNAMIC_PRIMARY:
+ {
+ union sctp_sockstore *ss;
+#ifdef SCTP_MVRF
+ int i, fnd = 0;
+#endif
+#if !defined(_WIN32) && !defined(__Userspace__)
+#if defined(__APPLE__)
+ struct proc *proc;
+#endif
+#if defined(__FreeBSD__)
+ error = priv_check(curthread,
+ PRIV_NETINET_RESERVEDPORT);
+#elif defined(__APPLE__)
+ proc = (struct proc *)p;
+ if (p) {
+ error = suser(proc->p_ucred, &proc->p_acflag);
+ } else {
+ break;
+ }
+#else
+ error = suser(p, 0);
+#endif
+ if (error)
+ break;
+#endif
+
+ SCTP_CHECK_AND_CAST(ss, optval, union sctp_sockstore, optsize);
+ /* SUPER USER CHECK? */
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (vrf_id == inp->m_vrf_ids[i]) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#endif
+ error = sctp_dynamic_set_primary(&ss->sa, vrf_id);
+ break;
+ }
+ case SCTP_SET_PEER_PRIMARY_ADDR:
+ {
+ struct sctp_setpeerprim *sspp;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(sspp, optval, struct sctp_setpeerprim, optsize);
+ SCTP_FIND_STCB(inp, stcb, sspp->sspp_assoc_id);
+ if (stcb != NULL) {
+ struct sctp_ifa *ifa;
+
+#if defined(INET) && defined(INET6)
+ if (sspp->sspp_addr.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&sspp->sspp_addr;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&sspp->sspp_addr;
+ }
+ } else {
+ addr = (struct sockaddr *)&sspp->sspp_addr;
+ }
+#else
+ addr = (struct sockaddr *)&sspp->sspp_addr;
+#endif
+ ifa = sctp_find_ifa_by_addr(addr, stcb->asoc.vrf_id, SCTP_ADDR_NOT_LOCKED);
+ if (ifa == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_of_it;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) {
+ /* Must validate the ifa found is in our ep */
+ struct sctp_laddr *laddr;
+ int found = 0;
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "%s: NULL ifa\n",
+ __func__);
+ continue;
+ }
+ if ((sctp_is_addr_restricted(stcb, laddr->ifa)) &&
+ (!sctp_is_addr_pending(stcb, laddr->ifa))) {
+ continue;
+ }
+ if (laddr->ifa == ifa) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_of_it;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ } else {
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_of_it;
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_of_it;
+ }
+ break;
+ }
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_of_it;
+ }
+#endif
+ }
+ if (sctp_set_primary_ip_address_sa(stcb, addr) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SOCKOPT, SCTP_SO_LOCKED);
+ out_of_it:
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ break;
+ }
+ case SCTP_BINDX_ADD_ADDR:
+ {
+ struct sockaddr *sa;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *td;
+
+ td = (struct thread *)p;
+#endif
+ SCTP_CHECK_AND_CAST(sa, optval, struct sockaddr, optsize);
+#ifdef INET
+ if (sa->sa_family == AF_INET) {
+ if (optsize < sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (td != NULL &&
+ (error = prison_local_ip4(td->td_ucred, &(((struct sockaddr_in *)sa)->sin_addr)))) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+#endif
+ } else
+#endif
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+ if (optsize < sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (td != NULL &&
+ (error = prison_local_ip6(td->td_ucred,
+ &(((struct sockaddr_in6 *)sa)->sin6_addr),
+ (SCTP_IPV6_V6ONLY(inp) != 0))) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+#endif
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ break;
+ }
+ sctp_bindx_add_address(so, inp, sa, vrf_id, &error, p);
+ break;
+ }
+ case SCTP_BINDX_REM_ADDR:
+ {
+ struct sockaddr *sa;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct thread *td;
+ td = (struct thread *)p;
+
+#endif
+ SCTP_CHECK_AND_CAST(sa, optval, struct sockaddr, optsize);
+#ifdef INET
+ if (sa->sa_family == AF_INET) {
+ if (optsize < sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (td != NULL &&
+ (error = prison_local_ip4(td->td_ucred, &(((struct sockaddr_in *)sa)->sin_addr)))) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+#endif
+ } else
+#endif
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+ if (optsize < sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (td != NULL &&
+ (error = prison_local_ip6(td->td_ucred,
+ &(((struct sockaddr_in6 *)sa)->sin6_addr),
+ (SCTP_IPV6_V6ONLY(inp) != 0))) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+#endif
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ break;
+ }
+ sctp_bindx_delete_address(inp, sa, vrf_id, &error);
+ break;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ case SCTP_LISTEN_FIX:
+ /* only applies to one-to-many sockets */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) {
+ /* make sure the ACCEPTCONN flag is OFF */
+ so->so_options &= ~SO_ACCEPTCONN;
+ } else {
+ /* otherwise, not allowed */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ break;
+#endif
+ case SCTP_EVENT:
+ {
+ struct sctp_event *event;
+ uint32_t event_type;
+
+ SCTP_CHECK_AND_CAST(event, optval, struct sctp_event, optsize);
+ SCTP_FIND_STCB(inp, stcb, event->se_assoc_id);
+ switch (event->se_type) {
+ case SCTP_ASSOC_CHANGE:
+ event_type = SCTP_PCB_FLAGS_RECVASSOCEVNT;
+ break;
+ case SCTP_PEER_ADDR_CHANGE:
+ event_type = SCTP_PCB_FLAGS_RECVPADDREVNT;
+ break;
+ case SCTP_REMOTE_ERROR:
+ event_type = SCTP_PCB_FLAGS_RECVPEERERR;
+ break;
+ case SCTP_SEND_FAILED:
+ event_type = SCTP_PCB_FLAGS_RECVSENDFAILEVNT;
+ break;
+ case SCTP_SHUTDOWN_EVENT:
+ event_type = SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT;
+ break;
+ case SCTP_ADAPTATION_INDICATION:
+ event_type = SCTP_PCB_FLAGS_ADAPTATIONEVNT;
+ break;
+ case SCTP_PARTIAL_DELIVERY_EVENT:
+ event_type = SCTP_PCB_FLAGS_PDAPIEVNT;
+ break;
+ case SCTP_AUTHENTICATION_EVENT:
+ event_type = SCTP_PCB_FLAGS_AUTHEVNT;
+ break;
+ case SCTP_STREAM_RESET_EVENT:
+ event_type = SCTP_PCB_FLAGS_STREAM_RESETEVNT;
+ break;
+ case SCTP_SENDER_DRY_EVENT:
+ event_type = SCTP_PCB_FLAGS_DRYEVNT;
+ break;
+ case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+ event_type = 0;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTSUP);
+ error = ENOTSUP;
+ break;
+ case SCTP_ASSOC_RESET_EVENT:
+ event_type = SCTP_PCB_FLAGS_ASSOC_RESETEVNT;
+ break;
+ case SCTP_STREAM_CHANGE_EVENT:
+ event_type = SCTP_PCB_FLAGS_STREAM_CHANGEEVNT;
+ break;
+ case SCTP_SEND_FAILED_EVENT:
+ event_type = SCTP_PCB_FLAGS_RECVNSENDFAILEVNT;
+ break;
+ default:
+ event_type = 0;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if (event_type > 0) {
+ if (stcb) {
+ if (event->se_on) {
+ sctp_stcb_feature_on(inp, stcb, event_type);
+ if (event_type == SCTP_PCB_FLAGS_DRYEVNT) {
+ if (TAILQ_EMPTY(&stcb->asoc.send_queue) &&
+ TAILQ_EMPTY(&stcb->asoc.sent_queue) &&
+ (stcb->asoc.stream_queue_cnt == 0)) {
+ sctp_ulp_notify(SCTP_NOTIFY_SENDER_DRY, stcb, 0, NULL, SCTP_SO_LOCKED);
+ }
+ }
+ } else {
+ sctp_stcb_feature_off(inp, stcb, event_type);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ /*
+ * We don't want to send up a storm of events,
+ * so return an error for sender dry events
+ */
+ if ((event_type == SCTP_PCB_FLAGS_DRYEVNT) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((event->se_assoc_id == SCTP_ALL_ASSOC) ||
+ (event->se_assoc_id == SCTP_CURRENT_ASSOC))) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTSUP);
+ error = ENOTSUP;
+ break;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((event->se_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (event->se_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ if (event->se_on) {
+ sctp_feature_on(inp, event_type);
+ } else {
+ sctp_feature_off(inp, event_type);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((event->se_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (event->se_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ if (event->se_on) {
+ sctp_stcb_feature_on(inp, stcb, event_type);
+ } else {
+ sctp_stcb_feature_off(inp, stcb, event_type);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ } else {
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+ break;
+ }
+ case SCTP_RECVRCVINFO:
+ {
+ int *onoff;
+
+ SCTP_CHECK_AND_CAST(onoff, optval, int, optsize);
+ SCTP_INP_WLOCK(inp);
+ if (*onoff != 0) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVRCVINFO);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVRCVINFO);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ case SCTP_RECVNXTINFO:
+ {
+ int *onoff;
+
+ SCTP_CHECK_AND_CAST(onoff, optval, int, optsize);
+ SCTP_INP_WLOCK(inp);
+ if (*onoff != 0) {
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVNXTINFO);
+ } else {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVNXTINFO);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ break;
+ }
+ case SCTP_DEFAULT_SNDINFO:
+ {
+ struct sctp_sndinfo *info;
+ uint16_t policy;
+
+ SCTP_CHECK_AND_CAST(info, optval, struct sctp_sndinfo, optsize);
+ SCTP_FIND_STCB(inp, stcb, info->snd_assoc_id);
+
+ if (stcb) {
+ if (info->snd_sid < stcb->asoc.streamoutcnt) {
+ stcb->asoc.def_send.sinfo_stream = info->snd_sid;
+ policy = PR_SCTP_POLICY(stcb->asoc.def_send.sinfo_flags);
+ stcb->asoc.def_send.sinfo_flags = info->snd_flags;
+ stcb->asoc.def_send.sinfo_flags |= policy;
+ stcb->asoc.def_send.sinfo_ppid = info->snd_ppid;
+ stcb->asoc.def_send.sinfo_context = info->snd_context;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((info->snd_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (info->snd_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->def_send.sinfo_stream = info->snd_sid;
+ policy = PR_SCTP_POLICY(inp->def_send.sinfo_flags);
+ inp->def_send.sinfo_flags = info->snd_flags;
+ inp->def_send.sinfo_flags |= policy;
+ inp->def_send.sinfo_ppid = info->snd_ppid;
+ inp->def_send.sinfo_context = info->snd_context;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((info->snd_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (info->snd_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ if (info->snd_sid < stcb->asoc.streamoutcnt) {
+ stcb->asoc.def_send.sinfo_stream = info->snd_sid;
+ policy = PR_SCTP_POLICY(stcb->asoc.def_send.sinfo_flags);
+ stcb->asoc.def_send.sinfo_flags = info->snd_flags;
+ stcb->asoc.def_send.sinfo_flags |= policy;
+ stcb->asoc.def_send.sinfo_ppid = info->snd_ppid;
+ stcb->asoc.def_send.sinfo_context = info->snd_context;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_DEFAULT_PRINFO:
+ {
+ struct sctp_default_prinfo *info;
+
+ SCTP_CHECK_AND_CAST(info, optval, struct sctp_default_prinfo, optsize);
+ SCTP_FIND_STCB(inp, stcb, info->pr_assoc_id);
+
+ if (info->pr_policy > SCTP_PR_SCTP_MAX) {
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+ if (stcb) {
+ stcb->asoc.def_send.sinfo_flags &= 0xfff0;
+ stcb->asoc.def_send.sinfo_flags |= info->pr_policy;
+ stcb->asoc.def_send.sinfo_timetolive = info->pr_value;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((info->pr_assoc_id == SCTP_FUTURE_ASSOC) ||
+ (info->pr_assoc_id == SCTP_ALL_ASSOC)))) {
+ SCTP_INP_WLOCK(inp);
+ inp->def_send.sinfo_flags &= 0xfff0;
+ inp->def_send.sinfo_flags |= info->pr_policy;
+ inp->def_send.sinfo_timetolive = info->pr_value;
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ ((info->pr_assoc_id == SCTP_CURRENT_ASSOC) ||
+ (info->pr_assoc_id == SCTP_ALL_ASSOC))) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.def_send.sinfo_flags &= 0xfff0;
+ stcb->asoc.def_send.sinfo_flags |= info->pr_policy;
+ stcb->asoc.def_send.sinfo_timetolive = info->pr_value;
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_PEER_ADDR_THLDS:
+ /* Applies to the specific association */
+ {
+ struct sctp_paddrthlds *thlds;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(thlds, optval, struct sctp_paddrthlds, optsize);
+ SCTP_FIND_STCB(inp, stcb, thlds->spt_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (thlds->spt_address.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&thlds->spt_address;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&thlds->spt_address;
+ }
+ } else {
+ addr = (struct sockaddr *)&thlds->spt_address;
+ }
+#else
+ addr = (struct sockaddr *)&thlds->spt_address;
+#endif
+ if (stcb != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr,
+ &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ if ((stcb != NULL) && (net == NULL)) {
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ if (sin->sin_addr.s_addr != INADDR_ANY) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)addr;
+ if (sconn->sconn_addr != NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ }
+ if (thlds->spt_pathcpthld != 0xffff) {
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ if (stcb != NULL) {
+ if (net != NULL) {
+ net->failure_threshold = thlds->spt_pathmaxrxt;
+ net->pf_threshold = thlds->spt_pathpfthld;
+ if (net->dest_state & SCTP_ADDR_PF) {
+ if ((net->error_count > net->failure_threshold) ||
+ (net->error_count <= net->pf_threshold)) {
+ net->dest_state &= ~SCTP_ADDR_PF;
+ }
+ } else {
+ if ((net->error_count > net->pf_threshold) &&
+ (net->error_count <= net->failure_threshold)) {
+ net->dest_state |= SCTP_ADDR_PF;
+ sctp_send_hb(stcb, net, SCTP_SO_LOCKED);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_17);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net);
+ }
+ }
+ if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ if (net->error_count > net->failure_threshold) {
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ } else {
+ if (net->error_count <= net->failure_threshold) {
+ net->dest_state |= SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ }
+ } else {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ net->failure_threshold = thlds->spt_pathmaxrxt;
+ net->pf_threshold = thlds->spt_pathpfthld;
+ if (net->dest_state & SCTP_ADDR_PF) {
+ if ((net->error_count > net->failure_threshold) ||
+ (net->error_count <= net->pf_threshold)) {
+ net->dest_state &= ~SCTP_ADDR_PF;
+ }
+ } else {
+ if ((net->error_count > net->pf_threshold) &&
+ (net->error_count <= net->failure_threshold)) {
+ net->dest_state |= SCTP_ADDR_PF;
+ sctp_send_hb(stcb, net, SCTP_SO_LOCKED);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT,
+ stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_18);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net);
+ }
+ }
+ if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ if (net->error_count > net->failure_threshold) {
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ } else {
+ if (net->error_count <= net->failure_threshold) {
+ net->dest_state |= SCTP_ADDR_REACHABLE;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, 0, net, SCTP_SO_LOCKED);
+ }
+ }
+ }
+ stcb->asoc.def_net_failure = thlds->spt_pathmaxrxt;
+ stcb->asoc.def_net_pf_threshold = thlds->spt_pathpfthld;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (thlds->spt_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.def_net_failure = thlds->spt_pathmaxrxt;
+ inp->sctp_ep.def_net_pf_threshold = thlds->spt_pathpfthld;
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_REMOTE_UDP_ENCAPS_PORT:
+ {
+ struct sctp_udpencaps *encaps;
+ struct sctp_nets *net;
+ struct sockaddr *addr;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin_store;
+#endif
+
+ SCTP_CHECK_AND_CAST(encaps, optval, struct sctp_udpencaps, optsize);
+ SCTP_FIND_STCB(inp, stcb, encaps->sue_assoc_id);
+
+#if defined(INET) && defined(INET6)
+ if (encaps->sue_address.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&encaps->sue_address;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ in6_sin6_2_sin(&sin_store, sin6);
+ addr = (struct sockaddr *)&sin_store;
+ } else {
+ addr = (struct sockaddr *)&encaps->sue_address;
+ }
+ } else {
+ addr = (struct sockaddr *)&encaps->sue_address;
+ }
+#else
+ addr = (struct sockaddr *)&encaps->sue_address;
+#endif
+ if (stcb != NULL) {
+ net = sctp_findnet(stcb, addr);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() wil
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ net = NULL;
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, &net, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ if ((stcb != NULL) && (net == NULL)) {
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ if (sin->sin_addr.s_addr != INADDR_ANY) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)addr;
+ if (sconn->sconn_addr != NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ SCTP_TCB_UNLOCK(stcb);
+ error = EINVAL;
+ break;
+ }
+ } else
+#endif
+ {
+ error = EAFNOSUPPORT;
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+ }
+
+ if (stcb != NULL) {
+ if (net != NULL) {
+ net->port = encaps->sue_port;
+ } else {
+ stcb->asoc.port = encaps->sue_port;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (encaps->sue_assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.port = encaps->sue_port;
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_ECN_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->ecn_supported = 0;
+ } else {
+ inp->ecn_supported = 1;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_PR_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->prsctp_supported = 0;
+ } else {
+ inp->prsctp_supported = 1;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_AUTH_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ if ((av->assoc_value == 0) &&
+ (inp->asconf_supported == 1)) {
+ /* AUTH is required for ASCONF */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->auth_supported = 0;
+ } else {
+ inp->auth_supported = 1;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_ASCONF_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ if ((av->assoc_value != 0) &&
+ (inp->auth_supported == 0)) {
+ /* AUTH is required for ASCONF */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->asconf_supported = 0;
+ sctp_auth_delete_chunk(SCTP_ASCONF,
+ inp->sctp_ep.local_auth_chunks);
+ sctp_auth_delete_chunk(SCTP_ASCONF_ACK,
+ inp->sctp_ep.local_auth_chunks);
+ } else {
+ inp->asconf_supported = 1;
+ sctp_auth_add_chunk(SCTP_ASCONF,
+ inp->sctp_ep.local_auth_chunks);
+ sctp_auth_add_chunk(SCTP_ASCONF_ACK,
+ inp->sctp_ep.local_auth_chunks);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_RECONFIG_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->reconfig_supported = 0;
+ } else {
+ inp->reconfig_supported = 1;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_NRSACK_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->nrsack_supported = 0;
+ } else {
+ inp->nrsack_supported = 1;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_PKTDROP_SUPPORTED:
+ {
+ struct sctp_assoc_value *av;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ if (av->assoc_value == 0) {
+ inp->pktdrop_supported = 0;
+ } else {
+ inp->pktdrop_supported = 1;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_MAX_CWND:
+ {
+ struct sctp_assoc_value *av;
+ struct sctp_nets *net;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ stcb->asoc.max_cwnd = av->assoc_value;
+ if (stcb->asoc.max_cwnd > 0) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if ((net->cwnd > stcb->asoc.max_cwnd) &&
+ (net->cwnd > (net->mtu - sizeof(struct sctphdr)))) {
+ net->cwnd = stcb->asoc.max_cwnd;
+ if (net->cwnd < (net->mtu - sizeof(struct sctphdr))) {
+ net->cwnd = net->mtu - sizeof(struct sctphdr);
+ }
+ }
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
+ (av->assoc_id == SCTP_FUTURE_ASSOC))) {
+ SCTP_INP_WLOCK(inp);
+ inp->max_cwnd = av->assoc_value;
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+ case SCTP_ACCEPT_ZERO_CHECKSUM:
+ {
+ uint32_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint32_t, optsize);
+ if ((*value == SCTP_EDMID_NONE) ||
+ (*value == SCTP_EDMID_LOWER_LAYER_DTLS)) {
+ SCTP_INP_WLOCK(inp);
+ inp->rcv_edmid = *value;
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ break;
+ }
+
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOPROTOOPT);
+ error = ENOPROTOOPT;
+ break;
+ } /* end switch (opt) */
+ return (error);
+}
+
+#if !defined(__Userspace__)
+int
+sctp_ctloutput(struct socket *so, struct sockopt *sopt)
+{
+#if defined(__FreeBSD__)
+ struct epoch_tracker et;
+ struct sctp_inpcb *inp;
+#endif
+ void *optval = NULL;
+ void *p;
+ size_t optsize = 0;
+ int error = 0;
+
+#if defined(__FreeBSD__)
+ if ((sopt->sopt_level == SOL_SOCKET) &&
+ (sopt->sopt_name == SO_SETFIB)) {
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(so->so_pcb, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOBUFS);
+ return (EINVAL);
+ }
+ SCTP_INP_WLOCK(inp);
+ inp->fibnum = so->so_fibnum;
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+ }
+#endif
+ if (sopt->sopt_level != IPPROTO_SCTP) {
+ /* wrong proto level... send back up to IP */
+#ifdef INET6
+ if (INP_CHECK_SOCKAF(so, AF_INET6))
+ error = ip6_ctloutput(so, sopt);
+#endif /* INET6 */
+#if defined(INET) && defined(INET6)
+ else
+#endif
+#ifdef INET
+ error = ip_ctloutput(so, sopt);
+#endif
+ return (error);
+ }
+ optsize = sopt->sopt_valsize;
+ if (optsize > SCTP_SOCKET_OPTION_LIMIT) {
+ SCTP_LTRACE_ERR_RET(so->so_pcb, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOBUFS);
+ return (ENOBUFS);
+ }
+ if (optsize) {
+ SCTP_MALLOC(optval, void *, optsize, SCTP_M_SOCKOPT);
+ if (optval == NULL) {
+ SCTP_LTRACE_ERR_RET(so->so_pcb, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOBUFS);
+ return (ENOBUFS);
+ }
+ error = sooptcopyin(sopt, optval, optsize, optsize);
+ if (error) {
+ SCTP_FREE(optval, SCTP_M_SOCKOPT);
+ goto out;
+ }
+ }
+#if defined(__FreeBSD__) || defined(_WIN32)
+ p = (void *)sopt->sopt_td;
+#else
+ p = (void *)sopt->sopt_p;
+#endif
+ if (sopt->sopt_dir == SOPT_SET) {
+#if defined(__FreeBSD__)
+ NET_EPOCH_ENTER(et);
+#endif
+ error = sctp_setopt(so, sopt->sopt_name, optval, optsize, p);
+#if defined(__FreeBSD__)
+ NET_EPOCH_EXIT(et);
+#endif
+ } else if (sopt->sopt_dir == SOPT_GET) {
+ error = sctp_getopt(so, sopt->sopt_name, optval, &optsize, p);
+ } else {
+ SCTP_LTRACE_ERR_RET(so->so_pcb, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ if ((error == 0) && (optval != NULL)) {
+ error = sooptcopyout(sopt, optval, optsize);
+ SCTP_FREE(optval, SCTP_M_SOCKOPT);
+ } else if (optval != NULL) {
+ SCTP_FREE(optval, SCTP_M_SOCKOPT);
+ }
+out:
+ return (error);
+}
+#endif
+
+#ifdef INET
+#if defined(__Userspace__)
+int
+sctp_connect(struct socket *so, struct sockaddr *addr)
+{
+ void *p = NULL;
+#elif defined(__FreeBSD__)
+static int
+sctp_connect(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#elif defined(__APPLE__)
+static int
+sctp_connect(struct socket *so, struct sockaddr *addr, struct proc *p)
+{
+#elif defined(_WIN32)
+static int
+sctp_connect(struct socket *so, struct sockaddr *addr, PKTHREAD p)
+{
+#else
+static int
+sctp_connect(struct socket *so, struct mbuf *nam, struct proc *p)
+{
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+#ifdef SCTP_MVRF
+ int i, fnd = 0;
+#endif
+ int error = 0;
+ int create_lock_on = 0;
+ uint32_t vrf_id;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb = NULL;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ /* I made the same as TCP since we are not setup? */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ if (addr == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return EINVAL;
+ }
+
+#if defined(__Userspace__)
+ /* TODO __Userspace__ falls into this code for IPv6 stuff at the moment... */
+#endif
+#if !defined(_WIN32) && !defined(__linux__) && !defined(__EMSCRIPTEN__)
+ switch (addr->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct sockaddr_in6 *sin6;
+
+#endif
+ if (addr->sa_len != sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (p != NULL && (error = prison_remote_ip6(p->td_ucred, &sin6->sin6_addr)) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ return (error);
+ }
+#endif
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct sockaddr_in *sin;
+
+#endif
+#if !defined(_WIN32)
+ if (addr->sa_len != sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ sin = (struct sockaddr_in *)addr;
+ if (p != NULL && (error = prison_remote_ip4(p->td_ucred, &sin->sin_addr)) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ return (error);
+ }
+#endif
+ break;
+ }
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EAFNOSUPPORT);
+ return (EAFNOSUPPORT);
+ }
+#endif
+ SCTP_INP_INCR_REF(inp);
+ SCTP_ASOC_CREATE_LOCK(inp);
+ create_lock_on = 1;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
+ /* Should I really unlock ? */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EFAULT);
+ error = EFAULT;
+ goto out_now;
+ }
+#ifdef INET6
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) &&
+ (addr->sa_family == AF_INET6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+#endif
+#if defined(__Userspace__)
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) &&
+ (addr->sa_family != AF_CONN)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+#endif
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+ /* Bind a ephemeral port */
+ error = sctp_inpcb_bind(so, NULL, NULL, p);
+ if (error) {
+ goto out_now;
+ }
+ }
+ /* Now do we connect? */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) &&
+ (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_PORTREUSE))) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) {
+ /* We are already connected AND the TCP model */
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, EADDRINUSE);
+ error = EADDRINUSE;
+ goto out_now;
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ SCTP_INP_RLOCK(inp);
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() will
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+ if (stcb != NULL) {
+ /* Already have or am bring up an association */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ goto out_now;
+ }
+
+ vrf_id = inp->def_vrf_id;
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (vrf_id == inp->m_vrf_ids[i]) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+#endif
+ /* We are GOOD to go */
+ stcb = sctp_aloc_assoc_connected(inp, addr, &error, 0, 0, vrf_id,
+ inp->sctp_ep.pre_open_stream_count,
+ inp->sctp_ep.port, p,
+ SCTP_INITIALIZE_AUTH_PARAMS);
+ if (stcb == NULL) {
+ /* Gak! no memory */
+ goto out_now;
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ SCTP_TCB_UNLOCK(stcb);
+ out_now:
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ if (create_lock_on) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ }
+ SCTP_INP_DECR_REF(inp);
+ return (error);
+}
+#endif
+
+#if defined(__Userspace__)
+int
+sctpconn_connect(struct socket *so, struct sockaddr *addr)
+{
+#ifdef SCTP_MVRF
+ int i, fnd = 0;
+#endif
+ void *p = NULL;
+ int error = 0;
+ int create_lock_on = 0;
+ uint32_t vrf_id;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb = NULL;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ /* I made the same as TCP since we are not setup? */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ if (addr == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return EINVAL;
+ }
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+ case AF_CONN:
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_conn)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EAFNOSUPPORT);
+ return (EAFNOSUPPORT);
+ }
+ SCTP_INP_INCR_REF(inp);
+ SCTP_ASOC_CREATE_LOCK(inp);
+ create_lock_on = 1;
+
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
+ /* Should I really unlock ? */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EFAULT);
+ error = EFAULT;
+ goto out_now;
+ }
+#ifdef INET6
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) &&
+ (addr->sa_family == AF_INET6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+#endif
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+ /* Bind a ephemeral port */
+ error = sctp_inpcb_bind(so, NULL, NULL, p);
+ if (error) {
+ goto out_now;
+ }
+ }
+ /* Now do we connect? */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) &&
+ (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_PORTREUSE))) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) {
+ /* We are already connected AND the TCP model */
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, EADDRINUSE);
+ error = EADDRINUSE;
+ goto out_now;
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ SCTP_INP_RLOCK(inp);
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ /* We increment here since sctp_findassociation_ep_addr() will
+ * do a decrement if it finds the stcb as long as the locked
+ * tcb (last argument) is NOT a TCB.. aka NULL.
+ */
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_DECR_REF(inp);
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+ if (stcb != NULL) {
+ /* Already have or am bring up an association */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ goto out_now;
+ }
+
+ vrf_id = inp->def_vrf_id;
+#ifdef SCTP_MVRF
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (vrf_id == inp->m_vrf_ids[i]) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out_now;
+ }
+#endif
+ /* We are GOOD to go */
+ stcb = sctp_aloc_assoc_connected(inp, addr, &error, 0, 0, vrf_id,
+ inp->sctp_ep.pre_open_stream_count,
+ inp->sctp_ep.port, p,
+ SCTP_INITIALIZE_AUTH_PARAMS);
+ if (stcb == NULL) {
+ /* Gak! no memory */
+ goto out_now;
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ SCTP_TCB_UNLOCK(stcb);
+ out_now:
+ if (create_lock_on) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ }
+
+ SCTP_INP_DECR_REF(inp);
+ return (error);
+}
+#endif
+int
+#if defined(__Userspace__)
+sctp_listen(struct socket *so, int backlog, struct proc *p)
+#elif defined(__FreeBSD__)
+sctp_listen(struct socket *so, int backlog, struct thread *p)
+#elif defined(_WIN32)
+sctp_listen(struct socket *so, int backlog, PKTHREAD p)
+#else
+sctp_listen(struct socket *so, struct proc *p)
+#endif
+{
+ /*
+ * Note this module depends on the protocol processing being called
+ * AFTER any socket level flags and backlog are applied to the
+ * socket. The traditional way that the socket flags are applied is
+ * AFTER protocol processing. We have made a change to the
+ * sys/kern/uipc_socket.c module to reverse this but this MUST be in
+ * place if the socket API for SCTP is to work properly.
+ */
+
+ int error = 0;
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ /* I made the same as TCP since we are not setup? */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) {
+ /* See if we have a listener */
+ struct sctp_inpcb *tinp;
+ union sctp_sockstore store;
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) {
+ /* not bound all */
+ struct sctp_laddr *laddr;
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ memcpy(&store, &laddr->ifa->address, sizeof(store));
+ switch (store.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ store.sin.sin_port = inp->sctp_lport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ store.sin6.sin6_port = inp->sctp_lport;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ store.sconn.sconn_port = inp->sctp_lport;
+ break;
+#endif
+ default:
+ break;
+ }
+ tinp = sctp_pcb_findep(&store.sa, 0, 0, inp->def_vrf_id);
+ if (tinp && (tinp != inp) &&
+ ((tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) == 0) &&
+ ((tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (SCTP_IS_LISTENING(tinp))) {
+ /* we have a listener already and its not this inp. */
+ SCTP_INP_DECR_REF(tinp);
+ return (EADDRINUSE);
+ } else if (tinp) {
+ SCTP_INP_DECR_REF(tinp);
+ }
+ }
+ } else {
+ /* Setup a local addr bound all */
+ memset(&store, 0, sizeof(store));
+#ifdef INET6
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ store.sa.sa_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ store.sa.sa_len = sizeof(struct sockaddr_in6);
+#endif
+ }
+#endif
+#if defined(__Userspace__)
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ store.sa.sa_family = AF_CONN;
+#ifdef HAVE_SA_LEN
+ store.sa.sa_len = sizeof(struct sockaddr_conn);
+#endif
+ }
+#endif
+#ifdef INET
+#if defined(__Userspace__)
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) &&
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) == 0)) {
+#else
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+#endif
+ store.sa.sa_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ store.sa.sa_len = sizeof(struct sockaddr_in);
+#endif
+ }
+#endif
+ switch (store.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ store.sin.sin_port = inp->sctp_lport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ store.sin6.sin6_port = inp->sctp_lport;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ store.sconn.sconn_port = inp->sctp_lport;
+ break;
+#endif
+ default:
+ break;
+ }
+ tinp = sctp_pcb_findep(&store.sa, 0, 0, inp->def_vrf_id);
+ if (tinp && (tinp != inp) &&
+ ((tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) == 0) &&
+ ((tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (SCTP_IS_LISTENING(tinp))) {
+ /* we have a listener already and its not this inp. */
+ SCTP_INP_DECR_REF(tinp);
+ return (EADDRINUSE);
+ } else if (tinp) {
+ SCTP_INP_DECR_REF(tinp);
+ }
+ }
+ }
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+#ifdef SCTP_LOCK_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOCK_LOGGING_ENABLE) {
+ sctp_log_lock(inp, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_SOCK);
+ }
+#endif
+ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ /* The unlucky case
+ * - We are in the tcp pool with this guy.
+ * - Someone else is in the main inp slot.
+ * - We must move this guy (the listener) to the main slot
+ * - We must then move the guy that was listener to the TCP Pool.
+ */
+ if (sctp_swap_inpcb_for_listen(inp)) {
+ error = EADDRINUSE;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ goto out;
+ }
+ }
+#if defined(__FreeBSD__) || defined(__Userspace__)
+ SOCK_LOCK(so);
+ error = solisten_proto_check(so);
+ if (error) {
+ SOCK_UNLOCK(so);
+ goto out;
+ }
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) {
+ SOCK_UNLOCK(so);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ solisten_proto_abort(so);
+#endif
+ error = EADDRINUSE;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ goto out;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_WAS_CONNECTED) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_WAS_ABORTED))) {
+ SOCK_UNLOCK(so);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ solisten_proto_abort(so);
+#endif
+ error = EINVAL;
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ goto out;
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+ if ((error = sctp_inpcb_bind_locked(inp, NULL, NULL, p))) {
+ SOCK_UNLOCK(so);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ solisten_proto_abort(so);
+#endif
+ /* bind error, probably perm */
+ goto out;
+ }
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) == 0) {
+ solisten_proto(so, backlog);
+ SOCK_UNLOCK(so);
+ inp->sctp_flags |= SCTP_PCB_FLAGS_ACCEPTING;
+ } else {
+ solisten_proto_abort(so);
+ SOCK_UNLOCK(so);
+ if (backlog > 0) {
+ inp->sctp_flags |= SCTP_PCB_FLAGS_ACCEPTING;
+ } else {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_ACCEPTING;
+ }
+ }
+#elif defined(_WIN32) || defined(__Userspace__)
+ solisten_proto(so, backlog);
+#endif
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) {
+ /* remove the ACCEPTCONN flag for one-to-many sockets */
+#if defined(__Userspace__)
+ so->so_options &= ~SCTP_SO_ACCEPTCONN;
+#else
+ so->so_options &= ~SO_ACCEPTCONN;
+#endif
+ }
+ SOCK_UNLOCK(so);
+ if (backlog > 0) {
+ inp->sctp_flags |= SCTP_PCB_FLAGS_ACCEPTING;
+ } else {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_ACCEPTING;
+ }
+#endif
+out:
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (error);
+}
+
+static int sctp_defered_wakeup_cnt = 0;
+
+int
+sctp_accept(struct socket *so, struct sockaddr **addr)
+{
+ struct sctp_tcb *stcb;
+ struct sctp_inpcb *inp;
+ union sctp_sockstore store;
+#ifdef INET6
+#if defined(SCTP_KAME) && defined(SCTP_EMBEDDED_V6_SCOPE)
+ int error;
+#endif
+#endif
+ inp = (struct sctp_inpcb *)so->so_pcb;
+
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ SCTP_INP_WLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) {
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ return (EOPNOTSUPP);
+ }
+ if (so->so_state & SS_ISDISCONNECTED) {
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ECONNABORTED);
+ return (ECONNABORTED);
+ }
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb == NULL) {
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ SCTP_TCB_LOCK(stcb);
+ store = stcb->asoc.primary_destination->ro._l_addr;
+ SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
+ /* Wake any delayed sleep action */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_DONT_WAKE;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEOUTPUT) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEOUTPUT;
+ SOCKBUF_LOCK(&inp->sctp_socket->so_snd);
+ if (sowriteable(inp->sctp_socket)) {
+#if defined(__Userspace__)
+ /*__Userspace__ calling sowwakup_locked because of SOCKBUF_LOCK above. */
+#endif
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+ sowwakeup_locked(inp->sctp_socket);
+#else
+#if defined(__APPLE__)
+ /* socket is locked */
+#endif
+ sowwakeup(inp->sctp_socket);
+#endif
+ } else {
+ SOCKBUF_UNLOCK(&inp->sctp_socket->so_snd);
+ }
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEINPUT) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEINPUT;
+ SOCKBUF_LOCK(&inp->sctp_socket->so_rcv);
+ if (soreadable(inp->sctp_socket)) {
+ sctp_defered_wakeup_cnt++;
+#if defined(__Userspace__)
+ /*__Userspace__ calling sorwakup_locked because of SOCKBUF_LOCK above */
+#endif
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+ sorwakeup_locked(inp->sctp_socket);
+#else
+#if defined(__APPLE__)
+ /* socket is locked */
+#endif
+ sorwakeup(inp->sctp_socket);
+#endif
+ } else {
+ SOCKBUF_UNLOCK(&inp->sctp_socket->so_rcv);
+ }
+ }
+ }
+ SCTP_INP_WUNLOCK(inp);
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_19);
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ switch (store.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin);
+ if (sin == NULL)
+ return (ENOMEM);
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(*sin);
+#endif
+ sin->sin_port = store.sin.sin_port;
+ sin->sin_addr = store.sin.sin_addr;
+ *addr = (struct sockaddr *)sin;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+ if (sin6 == NULL)
+ return (ENOMEM);
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(*sin6);
+#endif
+ sin6->sin6_port = store.sin6.sin6_port;
+ sin6->sin6_addr = store.sin6.sin6_addr;
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+#ifdef SCTP_KAME
+ if ((error = sa6_recoverscope(sin6)) != 0) {
+ SCTP_FREE_SONAME(sin6);
+ return (error);
+ }
+#else
+ if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr))
+ /*
+ * sin6->sin6_scope_id =
+ * ntohs(sin6->sin6_addr.s6_addr16[1]);
+ */
+ in6_recoverscope(sin6, &sin6->sin6_addr, NULL); /* skip ifp check */
+ else
+ sin6->sin6_scope_id = 0; /* XXX */
+#endif /* SCTP_KAME */
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ *addr = (struct sockaddr *)sin6;
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn;
+
+ SCTP_MALLOC_SONAME(sconn, struct sockaddr_conn *, sizeof(struct sockaddr_conn));
+ if (sconn == NULL) {
+ return (ENOMEM);
+ }
+ sconn->sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+ sconn->sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ sconn->sconn_port = store.sconn.sconn_port;
+ sconn->sconn_addr = store.sconn.sconn_addr;
+ *addr = (struct sockaddr *)sconn;
+ break;
+ }
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ return (0);
+}
+
+#ifdef INET
+int
+#if !defined(__Userspace__)
+sctp_ingetaddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in *sin;
+#else
+sctp_ingetaddr(struct socket *so, struct mbuf *nam)
+{
+ struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *);
+#endif
+ uint32_t vrf_id;
+ struct sctp_inpcb *inp;
+ struct sctp_ifa *sctp_ifa;
+
+ /*
+ * Do the malloc first in case it blocks.
+ */
+#if !defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin);
+ if (sin == NULL)
+ return (ENOMEM);
+#else
+ SCTP_BUF_LEN(nam) = sizeof(*sin);
+ memset(sin, 0, sizeof(*sin));
+#endif
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(*sin);
+#endif
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (!inp) {
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ SCTP_INP_RLOCK(inp);
+ sin->sin_port = inp->sctp_lport;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ struct sctp_tcb *stcb;
+ struct sockaddr_in *sin_a;
+ struct sctp_nets *net;
+ int fnd;
+
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb == NULL) {
+ goto notConn;
+ }
+ fnd = 0;
+ sin_a = NULL;
+ SCTP_TCB_LOCK(stcb);
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ sin_a = (struct sockaddr_in *)&net->ro._l_addr;
+ if (sin_a == NULL)
+ /* this will make coverity happy */
+ continue;
+
+ if (sin_a->sin_family == AF_INET) {
+ fnd = 1;
+ break;
+ }
+ }
+ if ((!fnd) || (sin_a == NULL)) {
+ /* punt */
+ SCTP_TCB_UNLOCK(stcb);
+ goto notConn;
+ }
+
+ vrf_id = inp->def_vrf_id;
+ sctp_ifa = sctp_source_address_selection(inp,
+ stcb,
+ (sctp_route_t *)&net->ro,
+ net, 0, vrf_id);
+ if (sctp_ifa) {
+ sin->sin_addr = sctp_ifa->address.sin.sin_addr;
+ sctp_free_ifa(sctp_ifa);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ /* For the bound all case you get back 0 */
+ notConn:
+ sin->sin_addr.s_addr = 0;
+ }
+
+ } else {
+ /* Take the first IPv4 address in the list */
+ struct sctp_laddr *laddr;
+ int fnd = 0;
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa->address.sa.sa_family == AF_INET) {
+ struct sockaddr_in *sin_a;
+
+ sin_a = &laddr->ifa->address.sin;
+ sin->sin_addr = sin_a->sin_addr;
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin);
+#endif
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ return (ENOENT);
+ }
+ }
+ SCTP_INP_RUNLOCK(inp);
+#if !defined(__Userspace__)
+ (*addr) = (struct sockaddr *)sin;
+#endif
+ return (0);
+}
+
+int
+#if !defined(__Userspace__)
+sctp_peeraddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in *sin;
+#else
+sctp_peeraddr(struct socket *so, struct mbuf *nam)
+{
+ struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *);
+
+#endif
+ int fnd;
+ struct sockaddr_in *sin_a;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+
+ /* Do the malloc first in case it blocks. */
+#if !defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin);
+ if (sin == NULL)
+ return (ENOMEM);
+#else
+ SCTP_BUF_LEN(nam) = sizeof(*sin);
+ memset(sin, 0, sizeof(*sin));
+#endif
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(*sin);
+#endif
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if ((inp == NULL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) {
+ /* UDP type and listeners will drop out here */
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTCONN);
+ return (ENOTCONN);
+ }
+ SCTP_INP_RLOCK(inp);
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ if (stcb == NULL) {
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ fnd = 0;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ sin_a = (struct sockaddr_in *)&net->ro._l_addr;
+ if (sin_a->sin_family == AF_INET) {
+ fnd = 1;
+ sin->sin_port = stcb->rport;
+ sin->sin_addr = sin_a->sin_addr;
+ break;
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ if (!fnd) {
+ /* No IPv4 address */
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ return (ENOENT);
+ }
+#if !defined(__Userspace__)
+ (*addr) = (struct sockaddr *)sin;
+#endif
+ return (0);
+}
+
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+#define SCTP_PROTOSW \
+ .pr_protocol = IPPROTO_SCTP, \
+ .pr_ctloutput = sctp_ctloutput, \
+ .pr_abort = sctp_abort, \
+ .pr_accept = sctp_accept, \
+ .pr_attach = sctp_attach, \
+ .pr_bind = sctp_bind, \
+ .pr_connect = sctp_connect, \
+ .pr_control = in_control, \
+ .pr_close = sctp_close, \
+ .pr_detach = sctp_close, \
+ .pr_sopoll = sopoll_generic, \
+ .pr_flush = sctp_flush, \
+ .pr_disconnect = sctp_disconnect, \
+ .pr_listen = sctp_listen, \
+ .pr_peeraddr = sctp_peeraddr, \
+ .pr_send = sctp_sendm, \
+ .pr_shutdown = sctp_shutdown, \
+ .pr_sockaddr = sctp_ingetaddr, \
+ .pr_sosend = sctp_sosend, \
+ .pr_soreceive = sctp_soreceive \
+
+struct protosw sctp_seqpacket_protosw = {
+ .pr_type = SOCK_SEQPACKET,
+ .pr_flags = PR_WANTRCVD,
+ SCTP_PROTOSW
+};
+
+struct protosw sctp_stream_protosw = {
+ .pr_type = SOCK_STREAM,
+ .pr_flags = PR_CONNREQUIRED | PR_WANTRCVD,
+ SCTP_PROTOSW
+};
+#else
+struct pr_usrreqs sctp_usrreqs = {
+#if defined(__APPLE__)
+ .pru_abort = sctp_abort,
+ .pru_accept = sctp_accept,
+ .pru_attach = sctp_attach,
+ .pru_bind = sctp_bind,
+ .pru_connect = sctp_connect,
+ .pru_connect2 = pru_connect2_notsupp,
+ .pru_control = in_control,
+ .pru_detach = sctp_detach,
+ .pru_disconnect = sctp_disconnect,
+ .pru_listen = sctp_listen,
+ .pru_peeraddr = sctp_peeraddr,
+ .pru_rcvd = NULL,
+ .pru_rcvoob = pru_rcvoob_notsupp,
+ .pru_send = sctp_sendm,
+ .pru_sense = pru_sense_null,
+ .pru_shutdown = sctp_shutdown,
+ .pru_sockaddr = sctp_ingetaddr,
+ .pru_sosend = sctp_sosend,
+ .pru_soreceive = sctp_soreceive,
+ .pru_sopoll = sopoll
+#elif defined(_WIN32) && !defined(__Userspace__)
+ sctp_abort,
+ sctp_accept,
+ sctp_attach,
+ sctp_bind,
+ sctp_connect,
+ pru_connect2_notsupp,
+ NULL,
+ NULL,
+ sctp_disconnect,
+ sctp_listen,
+ sctp_peeraddr,
+ NULL,
+ pru_rcvoob_notsupp,
+ NULL,
+ pru_sense_null,
+ sctp_shutdown,
+ sctp_flush,
+ sctp_ingetaddr,
+ sctp_sosend,
+ sctp_soreceive,
+ sopoll_generic,
+ NULL,
+ sctp_close
+#endif
+};
+#endif
+#endif
+#endif
+
+#if defined(__Userspace__)
+int
+register_recv_cb(struct socket *so,
+ int (*receive_cb)(struct socket *sock, union sctp_sockstore addr, void *data,
+ size_t datalen, struct sctp_rcvinfo, int flags, void *ulp_info))
+{
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *) so->so_pcb;
+ if (inp == NULL) {
+ return (0);
+ }
+ SCTP_INP_WLOCK(inp);
+ inp->recv_callback = receive_cb;
+ SCTP_INP_WUNLOCK(inp);
+ return (1);
+}
+
+int
+register_send_cb(struct socket *so, uint32_t sb_threshold, int (*send_cb)(struct socket *sock, uint32_t sb_free, void *ulp_info))
+{
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *) so->so_pcb;
+ if (inp == NULL) {
+ return (0);
+ }
+ SCTP_INP_WLOCK(inp);
+ inp->send_callback = send_cb;
+ inp->send_sb_threshold = sb_threshold;
+ SCTP_INP_WUNLOCK(inp);
+ /* FIXME change to current amount free. This will be the full buffer
+ * the first time this is registered but it could be only a portion
+ * of the send buffer if this is called a second time e.g. if the
+ * threshold changes.
+ */
+ return (1);
+}
+
+int
+register_ulp_info (struct socket *so, void *ulp_info)
+{
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *) so->so_pcb;
+ if (inp == NULL) {
+ return (0);
+ }
+ SCTP_INP_WLOCK(inp);
+ inp->ulp_info = ulp_info;
+ SCTP_INP_WUNLOCK(inp);
+ return (1);
+}
+
+int
+retrieve_ulp_info (struct socket *so, void **pulp_info)
+{
+ struct sctp_inpcb *inp;
+
+ if (pulp_info == NULL) {
+ return (0);
+ }
+
+ inp = (struct sctp_inpcb *) so->so_pcb;
+ if (inp == NULL) {
+ return (0);
+ }
+ SCTP_INP_RLOCK(inp);
+ *pulp_info = inp->ulp_info;
+ SCTP_INP_RUNLOCK(inp);
+ return (1);
+}
+#endif
diff --git a/netwerk/sctp/src/netinet/sctp_var.h b/netwerk/sctp/src/netinet/sctp_var.h
new file mode 100644
index 0000000000..ab67402219
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_var.h
@@ -0,0 +1,453 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_VAR_H_
+#define _NETINET_SCTP_VAR_H_
+
+#include <netinet/sctp_uio.h>
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+extern struct protosw sctp_seqpacket_protosw, sctp_stream_protosw;
+#else
+extern struct pr_usrreqs sctp_usrreqs;
+#endif
+#endif
+
+#define sctp_feature_on(inp, feature) (inp->sctp_features |= feature)
+#define sctp_feature_off(inp, feature) (inp->sctp_features &= ~feature)
+#define sctp_is_feature_on(inp, feature) ((inp->sctp_features & feature) == feature)
+#define sctp_is_feature_off(inp, feature) ((inp->sctp_features & feature) == 0)
+
+#define sctp_stcb_feature_on(inp, stcb, feature) {\
+ if (stcb) { \
+ stcb->asoc.sctp_features |= feature; \
+ } else if (inp) { \
+ inp->sctp_features |= feature; \
+ } \
+}
+#define sctp_stcb_feature_off(inp, stcb, feature) {\
+ if (stcb) { \
+ stcb->asoc.sctp_features &= ~feature; \
+ } else if (inp) { \
+ inp->sctp_features &= ~feature; \
+ } \
+}
+#define sctp_stcb_is_feature_on(inp, stcb, feature) \
+ (((stcb != NULL) && \
+ ((stcb->asoc.sctp_features & feature) == feature)) || \
+ ((stcb == NULL) && (inp != NULL) && \
+ ((inp->sctp_features & feature) == feature)))
+#define sctp_stcb_is_feature_off(inp, stcb, feature) \
+ (((stcb != NULL) && \
+ ((stcb->asoc.sctp_features & feature) == 0)) || \
+ ((stcb == NULL) && (inp != NULL) && \
+ ((inp->sctp_features & feature) == 0)) || \
+ ((stcb == NULL) && (inp == NULL)))
+
+/* managing mobility_feature in inpcb (by micchie) */
+#define sctp_mobility_feature_on(inp, feature) (inp->sctp_mobility_features |= feature)
+#define sctp_mobility_feature_off(inp, feature) (inp->sctp_mobility_features &= ~feature)
+#define sctp_is_mobility_feature_on(inp, feature) (inp->sctp_mobility_features & feature)
+#define sctp_is_mobility_feature_off(inp, feature) ((inp->sctp_mobility_features & feature) == 0)
+
+#define sctp_maxspace(sb) (max((sb)->sb_hiwat,SCTP_MINIMAL_RWND))
+
+#define sctp_sbspace(asoc, sb) ((long) ((sctp_maxspace(sb) > (asoc)->sb_cc) ? (sctp_maxspace(sb) - (asoc)->sb_cc) : 0))
+
+#define sctp_sbspace_failedmsgs(sb) ((long) ((sctp_maxspace(sb) > SCTP_SBAVAIL(sb)) ? (sctp_maxspace(sb) - SCTP_SBAVAIL(sb)) : 0))
+
+#define sctp_sbspace_sub(a,b) (((a) > (b)) ? ((a) - (b)) : 0)
+
+/*
+ * I tried to cache the readq entries at one point. But the reality
+ * is that it did not add any performance since this meant we had to
+ * lock the STCB on read. And at that point once you have to do an
+ * extra lock, it really does not matter if the lock is in the ZONE
+ * stuff or in our code. Note that this same problem would occur with
+ * an mbuf cache as well so it is not really worth doing, at least
+ * right now :-D
+ */
+#ifdef INVARIANTS
+#define sctp_free_a_readq(_stcb, _readq) { \
+ if ((_readq)->on_strm_q) \
+ panic("On strm q stcb:%p readq:%p", (_stcb), (_readq)); \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), (_readq)); \
+ SCTP_DECR_READQ_COUNT(); \
+}
+#else
+#define sctp_free_a_readq(_stcb, _readq) { \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), (_readq)); \
+ SCTP_DECR_READQ_COUNT(); \
+}
+#endif
+
+#define sctp_alloc_a_readq(_stcb, _readq) { \
+ (_readq) = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_readq), struct sctp_queued_to_read); \
+ if ((_readq)) { \
+ SCTP_INCR_READQ_COUNT(); \
+ } \
+}
+
+#define sctp_free_a_strmoq(_stcb, _strmoq, _so_locked) { \
+ if ((_strmoq)->holds_key_ref) { \
+ sctp_auth_key_release(stcb, sp->auth_keyid, _so_locked); \
+ (_strmoq)->holds_key_ref = 0; \
+ } \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_strmoq), (_strmoq)); \
+ SCTP_DECR_STRMOQ_COUNT(); \
+}
+
+#define sctp_alloc_a_strmoq(_stcb, _strmoq) { \
+ (_strmoq) = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_strmoq), struct sctp_stream_queue_pending); \
+ if ((_strmoq)) { \
+ memset(_strmoq, 0, sizeof(struct sctp_stream_queue_pending)); \
+ SCTP_INCR_STRMOQ_COUNT(); \
+ (_strmoq)->holds_key_ref = 0; \
+ } \
+}
+
+#define sctp_free_a_chunk(_stcb, _chk, _so_locked) { \
+ if ((_chk)->holds_key_ref) {\
+ sctp_auth_key_release((_stcb), (_chk)->auth_keyid, _so_locked); \
+ (_chk)->holds_key_ref = 0; \
+ } \
+ if (_stcb) { \
+ SCTP_TCB_LOCK_ASSERT((_stcb)); \
+ if ((_chk)->whoTo) { \
+ sctp_free_remote_addr((_chk)->whoTo); \
+ (_chk)->whoTo = NULL; \
+ } \
+ if (((_stcb)->asoc.free_chunk_cnt > SCTP_BASE_SYSCTL(sctp_asoc_free_resc_limit)) || \
+ (SCTP_BASE_INFO(ipi_free_chunks) > SCTP_BASE_SYSCTL(sctp_system_free_resc_limit))) { \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), (_chk)); \
+ SCTP_DECR_CHK_COUNT(); \
+ } else { \
+ TAILQ_INSERT_TAIL(&(_stcb)->asoc.free_chunks, (_chk), sctp_next); \
+ (_stcb)->asoc.free_chunk_cnt++; \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_free_chunks), 1); \
+ } \
+ } else { \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), (_chk)); \
+ SCTP_DECR_CHK_COUNT(); \
+ } \
+}
+
+#define sctp_alloc_a_chunk(_stcb, _chk) { \
+ if (TAILQ_EMPTY(&(_stcb)->asoc.free_chunks)) { \
+ (_chk) = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_chunk), struct sctp_tmit_chunk); \
+ if ((_chk)) { \
+ SCTP_INCR_CHK_COUNT(); \
+ (_chk)->whoTo = NULL; \
+ (_chk)->holds_key_ref = 0; \
+ } \
+ } else { \
+ (_chk) = TAILQ_FIRST(&(_stcb)->asoc.free_chunks); \
+ TAILQ_REMOVE(&(_stcb)->asoc.free_chunks, (_chk), sctp_next); \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_free_chunks), 1); \
+ (_chk)->holds_key_ref = 0; \
+ SCTP_STAT_INCR(sctps_cached_chk); \
+ (_stcb)->asoc.free_chunk_cnt--; \
+ } \
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#define sctp_free_remote_addr(__net) { \
+ if ((__net)) { \
+ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(&(__net)->ref_count)) { \
+ RO_NHFREE(&(__net)->ro); \
+ if ((__net)->src_addr_selected) { \
+ sctp_free_ifa((__net)->ro._s_addr); \
+ (__net)->ro._s_addr = NULL; \
+ } \
+ (__net)->src_addr_selected = 0; \
+ (__net)->dest_state &= ~SCTP_ADDR_REACHABLE; \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_net), (__net)); \
+ SCTP_DECR_RADDR_COUNT(); \
+ } \
+ } \
+}
+
+#define sctp_sbfree(ctl, stcb, sb, m) { \
+ SCTP_SB_DECR(sb, SCTP_BUF_LEN((m))); \
+ SCTP_SAVE_ATOMIC_DECREMENT(&(sb)->sb_mbcnt, MSIZE); \
+ if (((ctl)->do_not_ref_stcb == 0) && stcb) {\
+ SCTP_SAVE_ATOMIC_DECREMENT(&(stcb)->asoc.sb_cc, SCTP_BUF_LEN((m))); \
+ SCTP_SAVE_ATOMIC_DECREMENT(&(stcb)->asoc.my_rwnd_control_len, MSIZE); \
+ } \
+ if (SCTP_BUF_TYPE(m) != MT_DATA && SCTP_BUF_TYPE(m) != MT_HEADER && \
+ SCTP_BUF_TYPE(m) != MT_OOBDATA) \
+ atomic_subtract_int(&(sb)->sb_ctl,SCTP_BUF_LEN((m))); \
+}
+
+#define sctp_sballoc(stcb, sb, m) { \
+ SCTP_SB_INCR(sb, SCTP_BUF_LEN((m))); \
+ atomic_add_int(&(sb)->sb_mbcnt, MSIZE); \
+ if (stcb) { \
+ atomic_add_int(&(stcb)->asoc.sb_cc, SCTP_BUF_LEN((m))); \
+ atomic_add_int(&(stcb)->asoc.my_rwnd_control_len, MSIZE); \
+ } \
+ if (SCTP_BUF_TYPE(m) != MT_DATA && SCTP_BUF_TYPE(m) != MT_HEADER && \
+ SCTP_BUF_TYPE(m) != MT_OOBDATA) \
+ atomic_add_int(&(sb)->sb_ctl,SCTP_BUF_LEN((m))); \
+}
+#else /* FreeBSD Version <= 500000 or non-FreeBSD */
+#define sctp_free_remote_addr(__net) { \
+ if ((__net)) { \
+ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(&(__net)->ref_count)) { \
+ if ((__net)->ro.ro_rt) { \
+ RTFREE((__net)->ro.ro_rt); \
+ (__net)->ro.ro_rt = NULL; \
+ } \
+ if ((__net)->src_addr_selected) { \
+ sctp_free_ifa((__net)->ro._s_addr); \
+ (__net)->ro._s_addr = NULL; \
+ } \
+ (__net)->src_addr_selected = 0; \
+ (__net)->dest_state &=~SCTP_ADDR_REACHABLE; \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_net), (__net)); \
+ SCTP_DECR_RADDR_COUNT(); \
+ } \
+ } \
+}
+
+#define sctp_sbfree(ctl, stcb, sb, m) { \
+ SCTP_SB_DECR(sb, SCTP_BUF_LEN((m))); \
+ SCTP_SAVE_ATOMIC_DECREMENT(&(sb)->sb_mbcnt, MSIZE); \
+ if (((ctl)->do_not_ref_stcb == 0) && stcb) { \
+ SCTP_SAVE_ATOMIC_DECREMENT(&(stcb)->asoc.sb_cc, SCTP_BUF_LEN((m))); \
+ SCTP_SAVE_ATOMIC_DECREMENT(&(stcb)->asoc.my_rwnd_control_len, MSIZE); \
+ } \
+}
+
+#define sctp_sballoc(stcb, sb, m) { \
+ SCTP_SB_INCR(sb, SCTP_BUF_LEN((m))); \
+ atomic_add_int(&(sb)->sb_mbcnt, MSIZE); \
+ if (stcb) { \
+ atomic_add_int(&(stcb)->asoc.sb_cc, SCTP_BUF_LEN((m))); \
+ atomic_add_int(&(stcb)->asoc.my_rwnd_control_len, MSIZE); \
+ } \
+}
+#endif
+
+#define sctp_ucount_incr(val) { \
+ val++; \
+}
+
+#define sctp_ucount_decr(val) { \
+ if (val > 0) { \
+ val--; \
+ } else { \
+ val = 0; \
+ } \
+}
+
+#define sctp_mbuf_crush(data) do { \
+ struct mbuf *_m; \
+ _m = (data); \
+ while (_m && (SCTP_BUF_LEN(_m) == 0)) { \
+ (data) = SCTP_BUF_NEXT(_m); \
+ SCTP_BUF_NEXT(_m) = NULL; \
+ sctp_m_free(_m); \
+ _m = (data); \
+ } \
+} while (0)
+
+#define sctp_flight_size_decrease(tp1) do { \
+ if (tp1->whoTo->flight_size >= tp1->book_size) \
+ tp1->whoTo->flight_size -= tp1->book_size; \
+ else \
+ tp1->whoTo->flight_size = 0; \
+} while (0)
+
+#define sctp_flight_size_increase(tp1) do { \
+ (tp1)->whoTo->flight_size += (tp1)->book_size; \
+} while (0)
+
+#ifdef SCTP_FS_SPEC_LOG
+#define sctp_total_flight_decrease(stcb, tp1) do { \
+ if (stcb->asoc.fs_index > SCTP_FS_SPEC_LOG_SIZE) \
+ stcb->asoc.fs_index = 0;\
+ stcb->asoc.fslog[stcb->asoc.fs_index].total_flight = stcb->asoc.total_flight; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].tsn = tp1->rec.data.tsn; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].book = tp1->book_size; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].sent = tp1->sent; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].incr = 0; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].decr = 1; \
+ stcb->asoc.fs_index++; \
+ tp1->window_probe = 0; \
+ if (stcb->asoc.total_flight >= tp1->book_size) { \
+ stcb->asoc.total_flight -= tp1->book_size; \
+ if (stcb->asoc.total_flight_count > 0) \
+ stcb->asoc.total_flight_count--; \
+ } else { \
+ stcb->asoc.total_flight = 0; \
+ stcb->asoc.total_flight_count = 0; \
+ } \
+} while (0)
+
+#define sctp_total_flight_increase(stcb, tp1) do { \
+ if (stcb->asoc.fs_index > SCTP_FS_SPEC_LOG_SIZE) \
+ stcb->asoc.fs_index = 0;\
+ stcb->asoc.fslog[stcb->asoc.fs_index].total_flight = stcb->asoc.total_flight; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].tsn = tp1->rec.data.tsn; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].book = tp1->book_size; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].sent = tp1->sent; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].incr = 1; \
+ stcb->asoc.fslog[stcb->asoc.fs_index].decr = 0; \
+ stcb->asoc.fs_index++; \
+ (stcb)->asoc.total_flight_count++; \
+ (stcb)->asoc.total_flight += (tp1)->book_size; \
+} while (0)
+
+#else
+
+#define sctp_total_flight_decrease(stcb, tp1) do { \
+ tp1->window_probe = 0; \
+ if (stcb->asoc.total_flight >= tp1->book_size) { \
+ stcb->asoc.total_flight -= tp1->book_size; \
+ if (stcb->asoc.total_flight_count > 0) \
+ stcb->asoc.total_flight_count--; \
+ } else { \
+ stcb->asoc.total_flight = 0; \
+ stcb->asoc.total_flight_count = 0; \
+ } \
+} while (0)
+
+#define sctp_total_flight_increase(stcb, tp1) do { \
+ (stcb)->asoc.total_flight_count++; \
+ (stcb)->asoc.total_flight += (tp1)->book_size; \
+} while (0)
+
+#endif
+
+#define SCTP_PF_ENABLED(_net) (_net->pf_threshold < _net->failure_threshold)
+#define SCTP_NET_IS_PF(_net) (_net->pf_threshold < _net->error_count)
+
+struct sctp_nets;
+struct sctp_inpcb;
+struct sctp_tcb;
+struct sctphdr;
+
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+void sctp_close(struct socket *so);
+#else
+int sctp_detach(struct socket *so);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+void sctp_abort(struct socket *so);
+#else
+int sctp_abort(struct socket *so);
+#endif
+int sctp_disconnect(struct socket *so);
+#if !defined(__Userspace__)
+#if defined(__APPLE__) && !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION) && !defined(APPLE_ELCAPITAN)
+void sctp_ctlinput(int, struct sockaddr *, void *, struct ifnet * SCTP_UNUSED);
+#elif defined(__FreeBSD__)
+ipproto_ctlinput_t sctp_ctlinput;
+#else
+void sctp_ctlinput(int, struct sockaddr *, void *);
+#endif
+int sctp_ctloutput(struct socket *, struct sockopt *);
+#ifdef INET
+void sctp_input_with_port(struct mbuf *, int, uint16_t);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+int sctp_input(struct mbuf **, int *, int);
+#else
+void sctp_input(struct mbuf *, int);
+#endif
+#endif
+void sctp_pathmtu_adjustment(struct sctp_tcb *, uint32_t, bool);
+#else
+#if defined(__Userspace__)
+void sctp_pathmtu_adjustment(struct sctp_tcb *, uint32_t, bool);
+#else
+void sctp_input(struct mbuf *,...);
+#endif
+void *sctp_ctlinput(int, struct sockaddr *, void *);
+int sctp_ctloutput(int, struct socket *, int, int, struct mbuf **);
+#endif
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+void sctp_drain(void);
+#endif
+#if defined(__Userspace__)
+void sctp_init(uint16_t,
+ int (*)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df),
+ void (*)(const char *, ...), int start_threads);
+#elif defined(__APPLE__) && (!defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) &&!defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION))
+void sctp_init(struct protosw *pp, struct domain *dp);
+#else
+#if !defined(__FreeBSD__)
+void sctp_init(void);
+#endif
+void sctp_notify(struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *,
+ uint8_t, uint8_t, uint16_t, uint32_t);
+#endif
+#if !defined(__FreeBSD__) && !defined(__Userspace__)
+void sctp_finish(void);
+#endif
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+int sctp_flush(struct socket *, int);
+#endif
+int sctp_shutdown(struct socket *);
+int sctp_bindx(struct socket *, int, struct sockaddr_storage *,
+ int, int, struct proc *);
+/* can't use sctp_assoc_t here */
+int sctp_peeloff(struct socket *, struct socket *, int, caddr_t, int *);
+#if !defined(__Userspace__)
+int sctp_ingetaddr(struct socket *, struct sockaddr **);
+#else
+int sctp_ingetaddr(struct socket *, struct mbuf *);
+#endif
+#if !defined(__Userspace__)
+int sctp_peeraddr(struct socket *, struct sockaddr **);
+#else
+int sctp_peeraddr(struct socket *, struct mbuf *);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+int sctp_listen(struct socket *, int, struct thread *);
+#elif defined(_WIN32) && !defined(__Userspace__)
+int sctp_listen(struct socket *, int, PKTHREAD);
+#elif defined(__Userspace__)
+int sctp_listen(struct socket *, int, struct proc *);
+#else
+int sctp_listen(struct socket *, struct proc *);
+#endif
+int sctp_accept(struct socket *, struct sockaddr **);
+
+#endif /* _KERNEL */
+
+#endif /* !_NETINET_SCTP_VAR_H_ */
diff --git a/netwerk/sctp/src/netinet/sctputil.c b/netwerk/sctp/src/netinet/sctputil.c
new file mode 100644
index 0000000000..af062001da
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctputil.c
@@ -0,0 +1,8724 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#ifdef INET6
+#if defined(__Userspace__) || defined(__FreeBSD__)
+#include <netinet6/sctp6_var.h>
+#endif
+#endif
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_bsd_addr.h>
+#if defined(__Userspace__)
+#include <netinet/sctp_constants.h>
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <netinet/sctp_kdtrace.h>
+#if defined(INET6) || defined(INET)
+#include <netinet/tcp_var.h>
+#endif
+#include <netinet/udp.h>
+#include <netinet/udp_var.h>
+#include <sys/proc.h>
+#ifdef INET6
+#include <netinet/icmp6.h>
+#endif
+#endif
+
+#if defined(_WIN32) && !defined(__Userspace__)
+#if !defined(SCTP_LOCAL_TRACE_BUF)
+#include "eventrace_netinet.h"
+#include "sctputil.tmh" /* this is the file that will be auto generated */
+#endif
+#else
+#ifndef KTR_SCTP
+#define KTR_SCTP KTR_SUBSYS
+#endif
+#endif
+
+extern const struct sctp_cc_functions sctp_cc_functions[];
+extern const struct sctp_ss_functions sctp_ss_functions[];
+
+void
+sctp_sblog(struct sockbuf *sb, struct sctp_tcb *stcb, int from, int incr)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.sb.stcb = stcb;
+ sctp_clog.x.sb.so_sbcc = SCTP_SBAVAIL(sb);
+ if (stcb)
+ sctp_clog.x.sb.stcb_sbcc = stcb->asoc.sb_cc;
+ else
+ sctp_clog.x.sb.stcb_sbcc = 0;
+ sctp_clog.x.sb.incr = incr;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_SB,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_closing(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int16_t loc)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.close.inp = (void *)inp;
+ sctp_clog.x.close.sctp_flags = inp->sctp_flags;
+ if (stcb) {
+ sctp_clog.x.close.stcb = (void *)stcb;
+ sctp_clog.x.close.state = (uint16_t)stcb->asoc.state;
+ } else {
+ sctp_clog.x.close.stcb = 0;
+ sctp_clog.x.close.state = 0;
+ }
+ sctp_clog.x.close.loc = loc;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_CLOSE,
+ 0,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+rto_logging(struct sctp_nets *net, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ memset(&sctp_clog, 0, sizeof(sctp_clog));
+ sctp_clog.x.rto.net = (void *) net;
+ sctp_clog.x.rto.rtt = net->rtt / 1000;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_RTT,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_strm_del_alt(struct sctp_tcb *stcb, uint32_t tsn, uint16_t sseq, uint16_t stream, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.strlog.stcb = stcb;
+ sctp_clog.x.strlog.n_tsn = tsn;
+ sctp_clog.x.strlog.n_sseq = sseq;
+ sctp_clog.x.strlog.e_tsn = 0;
+ sctp_clog.x.strlog.e_sseq = 0;
+ sctp_clog.x.strlog.strm = stream;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_STRM,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_nagle_event(struct sctp_tcb *stcb, int action)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.nagle.stcb = (void *)stcb;
+ sctp_clog.x.nagle.total_flight = stcb->asoc.total_flight;
+ sctp_clog.x.nagle.total_in_queue = stcb->asoc.total_output_queue_size;
+ sctp_clog.x.nagle.count_in_queue = stcb->asoc.chunks_on_out_queue;
+ sctp_clog.x.nagle.count_in_flight = stcb->asoc.total_flight_count;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_NAGLE,
+ action,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_sack(uint32_t old_cumack, uint32_t cumack, uint32_t tsn, uint16_t gaps, uint16_t dups, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.sack.cumack = cumack;
+ sctp_clog.x.sack.oldcumack = old_cumack;
+ sctp_clog.x.sack.tsn = tsn;
+ sctp_clog.x.sack.numGaps = gaps;
+ sctp_clog.x.sack.numDups = dups;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_SACK,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_map(uint32_t map, uint32_t cum, uint32_t high, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ memset(&sctp_clog, 0, sizeof(sctp_clog));
+ sctp_clog.x.map.base = map;
+ sctp_clog.x.map.cum = cum;
+ sctp_clog.x.map.high = high;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_MAP,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_fr(uint32_t biggest_tsn, uint32_t biggest_new_tsn, uint32_t tsn, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ memset(&sctp_clog, 0, sizeof(sctp_clog));
+ sctp_clog.x.fr.largest_tsn = biggest_tsn;
+ sctp_clog.x.fr.largest_new_tsn = biggest_new_tsn;
+ sctp_clog.x.fr.tsn = tsn;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_FR,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+#ifdef SCTP_MBUF_LOGGING
+void
+sctp_log_mb(struct mbuf *m, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.mb.mp = m;
+ sctp_clog.x.mb.mbuf_flags = (uint8_t)(SCTP_BUF_GET_FLAGS(m));
+ sctp_clog.x.mb.size = (uint16_t)(SCTP_BUF_LEN(m));
+ sctp_clog.x.mb.data = SCTP_BUF_AT(m, 0);
+ if (SCTP_BUF_IS_EXTENDED(m)) {
+ sctp_clog.x.mb.ext = SCTP_BUF_EXTEND_BASE(m);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ /* APPLE does not use a ref_cnt, but a forward/backward ref queue */
+#else
+ sctp_clog.x.mb.refcnt = (uint8_t)(SCTP_BUF_EXTEND_REFCNT(m));
+#endif
+ } else {
+ sctp_clog.x.mb.ext = 0;
+ sctp_clog.x.mb.refcnt = 0;
+ }
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_MBUF,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_mbc(struct mbuf *m, int from)
+{
+ struct mbuf *mat;
+
+ for (mat = m; mat; mat = SCTP_BUF_NEXT(mat)) {
+ sctp_log_mb(mat, from);
+ }
+}
+#endif
+
+void
+sctp_log_strm_del(struct sctp_queued_to_read *control, struct sctp_queued_to_read *poschk, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ if (control == NULL) {
+ SCTP_PRINTF("Gak log of NULL?\n");
+ return;
+ }
+ sctp_clog.x.strlog.stcb = control->stcb;
+ sctp_clog.x.strlog.n_tsn = control->sinfo_tsn;
+ sctp_clog.x.strlog.n_sseq = (uint16_t)control->mid;
+ sctp_clog.x.strlog.strm = control->sinfo_stream;
+ if (poschk != NULL) {
+ sctp_clog.x.strlog.e_tsn = poschk->sinfo_tsn;
+ sctp_clog.x.strlog.e_sseq = (uint16_t)poschk->mid;
+ } else {
+ sctp_clog.x.strlog.e_tsn = 0;
+ sctp_clog.x.strlog.e_sseq = 0;
+ }
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_STRM,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_cwnd(struct sctp_tcb *stcb, struct sctp_nets *net, int augment, uint8_t from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.cwnd.net = net;
+ if (stcb->asoc.send_queue_cnt > 255)
+ sctp_clog.x.cwnd.cnt_in_send = 255;
+ else
+ sctp_clog.x.cwnd.cnt_in_send = stcb->asoc.send_queue_cnt;
+ if (stcb->asoc.stream_queue_cnt > 255)
+ sctp_clog.x.cwnd.cnt_in_str = 255;
+ else
+ sctp_clog.x.cwnd.cnt_in_str = stcb->asoc.stream_queue_cnt;
+
+ if (net) {
+ sctp_clog.x.cwnd.cwnd_new_value = net->cwnd;
+ sctp_clog.x.cwnd.inflight = net->flight_size;
+ sctp_clog.x.cwnd.pseudo_cumack = net->pseudo_cumack;
+ sctp_clog.x.cwnd.meets_pseudo_cumack = net->new_pseudo_cumack;
+ sctp_clog.x.cwnd.need_new_pseudo_cumack = net->find_pseudo_cumack;
+ }
+ if (SCTP_CWNDLOG_PRESEND == from) {
+ sctp_clog.x.cwnd.meets_pseudo_cumack = stcb->asoc.peers_rwnd;
+ }
+ sctp_clog.x.cwnd.cwnd_augment = augment;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_CWND,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+#if !defined(__APPLE__) && !defined(__Userspace__)
+void
+sctp_log_lock(struct sctp_inpcb *inp, struct sctp_tcb *stcb, uint8_t from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ memset(&sctp_clog, 0, sizeof(sctp_clog));
+ if (inp) {
+ sctp_clog.x.lock.sock = (void *) inp->sctp_socket;
+
+ } else {
+ sctp_clog.x.lock.sock = (void *) NULL;
+ }
+ sctp_clog.x.lock.inp = (void *) inp;
+#if defined(__FreeBSD__)
+ if (stcb) {
+ sctp_clog.x.lock.tcb_lock = mtx_owned(&stcb->tcb_mtx);
+ } else {
+ sctp_clog.x.lock.tcb_lock = SCTP_LOCK_UNKNOWN;
+ }
+ if (inp) {
+ sctp_clog.x.lock.inp_lock = mtx_owned(&inp->inp_mtx);
+ sctp_clog.x.lock.create_lock = mtx_owned(&inp->inp_create_mtx);
+ } else {
+ sctp_clog.x.lock.inp_lock = SCTP_LOCK_UNKNOWN;
+ sctp_clog.x.lock.create_lock = SCTP_LOCK_UNKNOWN;
+ }
+ sctp_clog.x.lock.info_lock = rw_wowned(&SCTP_BASE_INFO(ipi_ep_mtx));
+ if (inp && (inp->sctp_socket)) {
+ sctp_clog.x.lock.sock_lock = mtx_owned(SOCK_MTX(inp->sctp_socket));
+ sctp_clog.x.lock.sockrcvbuf_lock = mtx_owned(SOCKBUF_MTX(&inp->sctp_socket->so_rcv));
+ sctp_clog.x.lock.socksndbuf_lock = mtx_owned(SOCKBUF_MTX(&inp->sctp_socket->so_snd));
+ } else {
+ sctp_clog.x.lock.sock_lock = SCTP_LOCK_UNKNOWN;
+ sctp_clog.x.lock.sockrcvbuf_lock = SCTP_LOCK_UNKNOWN;
+ sctp_clog.x.lock.socksndbuf_lock = SCTP_LOCK_UNKNOWN;
+ }
+#endif
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_LOCK_EVENT,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+#endif
+
+void
+sctp_log_maxburst(struct sctp_tcb *stcb, struct sctp_nets *net, int error, int burst, uint8_t from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ memset(&sctp_clog, 0, sizeof(sctp_clog));
+ sctp_clog.x.cwnd.net = net;
+ sctp_clog.x.cwnd.cwnd_new_value = error;
+ sctp_clog.x.cwnd.inflight = net->flight_size;
+ sctp_clog.x.cwnd.cwnd_augment = burst;
+ if (stcb->asoc.send_queue_cnt > 255)
+ sctp_clog.x.cwnd.cnt_in_send = 255;
+ else
+ sctp_clog.x.cwnd.cnt_in_send = stcb->asoc.send_queue_cnt;
+ if (stcb->asoc.stream_queue_cnt > 255)
+ sctp_clog.x.cwnd.cnt_in_str = 255;
+ else
+ sctp_clog.x.cwnd.cnt_in_str = stcb->asoc.stream_queue_cnt;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_MAXBURST,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_rwnd(uint8_t from, uint32_t peers_rwnd, uint32_t snd_size, uint32_t overhead)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.rwnd.rwnd = peers_rwnd;
+ sctp_clog.x.rwnd.send_size = snd_size;
+ sctp_clog.x.rwnd.overhead = overhead;
+ sctp_clog.x.rwnd.new_rwnd = 0;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_RWND,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_rwnd_set(uint8_t from, uint32_t peers_rwnd, uint32_t flight_size, uint32_t overhead, uint32_t a_rwndval)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.rwnd.rwnd = peers_rwnd;
+ sctp_clog.x.rwnd.send_size = flight_size;
+ sctp_clog.x.rwnd.overhead = overhead;
+ sctp_clog.x.rwnd.new_rwnd = a_rwndval;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_RWND,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+#ifdef SCTP_MBCNT_LOGGING
+static void
+sctp_log_mbcnt(uint8_t from, uint32_t total_oq, uint32_t book, uint32_t total_mbcnt_q, uint32_t mbcnt)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.mbcnt.total_queue_size = total_oq;
+ sctp_clog.x.mbcnt.size_change = book;
+ sctp_clog.x.mbcnt.total_queue_mb_size = total_mbcnt_q;
+ sctp_clog.x.mbcnt.mbcnt_change = mbcnt;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_MBCNT,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+#endif
+
+void
+sctp_misc_ints(uint8_t from, uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_MISC_EVENT,
+ from,
+ a, b, c, d);
+#endif
+}
+
+void
+sctp_wakeup_log(struct sctp_tcb *stcb, uint32_t wake_cnt, int from)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.wake.stcb = (void *)stcb;
+ sctp_clog.x.wake.wake_cnt = wake_cnt;
+ sctp_clog.x.wake.flight = stcb->asoc.total_flight_count;
+ sctp_clog.x.wake.send_q = stcb->asoc.send_queue_cnt;
+ sctp_clog.x.wake.sent_q = stcb->asoc.sent_queue_cnt;
+
+ if (stcb->asoc.stream_queue_cnt < 0xff)
+ sctp_clog.x.wake.stream_qcnt = (uint8_t) stcb->asoc.stream_queue_cnt;
+ else
+ sctp_clog.x.wake.stream_qcnt = 0xff;
+
+ if (stcb->asoc.chunks_on_out_queue < 0xff)
+ sctp_clog.x.wake.chunks_on_oque = (uint8_t) stcb->asoc.chunks_on_out_queue;
+ else
+ sctp_clog.x.wake.chunks_on_oque = 0xff;
+
+ sctp_clog.x.wake.sctpflags = 0;
+ /* set in the defered mode stuff */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE)
+ sctp_clog.x.wake.sctpflags |= 1;
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_WAKEOUTPUT)
+ sctp_clog.x.wake.sctpflags |= 2;
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_WAKEINPUT)
+ sctp_clog.x.wake.sctpflags |= 4;
+ /* what about the sb */
+ if (stcb->sctp_socket) {
+ struct socket *so = stcb->sctp_socket;
+
+ sctp_clog.x.wake.sbflags = (uint8_t)((so->so_snd.sb_flags & 0x00ff));
+ } else {
+ sctp_clog.x.wake.sbflags = 0xff;
+ }
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_WAKE,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+void
+sctp_log_block(uint8_t from, struct sctp_association *asoc, ssize_t sendlen)
+{
+#if defined(SCTP_LOCAL_TRACE_BUF)
+ struct sctp_cwnd_log sctp_clog;
+
+ sctp_clog.x.blk.onsb = asoc->total_output_queue_size;
+ sctp_clog.x.blk.send_sent_qcnt = (uint16_t) (asoc->send_queue_cnt + asoc->sent_queue_cnt);
+ sctp_clog.x.blk.peer_rwnd = asoc->peers_rwnd;
+ sctp_clog.x.blk.stream_qcnt = (uint16_t) asoc->stream_queue_cnt;
+ sctp_clog.x.blk.chunks_on_oque = (uint16_t) asoc->chunks_on_out_queue;
+ sctp_clog.x.blk.flight_size = (uint16_t) (asoc->total_flight/1024);
+ sctp_clog.x.blk.sndlen = (uint32_t)sendlen;
+ SCTP_CTR6(KTR_SCTP, "SCTP:%d[%d]:%x-%x-%x-%x",
+ SCTP_LOG_EVENT_BLOCK,
+ from,
+ sctp_clog.x.misc.log1,
+ sctp_clog.x.misc.log2,
+ sctp_clog.x.misc.log3,
+ sctp_clog.x.misc.log4);
+#endif
+}
+
+int
+sctp_fill_stat_log(void *optval SCTP_UNUSED, size_t *optsize SCTP_UNUSED)
+{
+ /* May need to fix this if ktrdump does not work */
+ return (0);
+}
+
+#ifdef SCTP_AUDITING_ENABLED
+uint8_t sctp_audit_data[SCTP_AUDIT_SIZE][2];
+static int sctp_audit_indx = 0;
+
+static
+void
+sctp_print_audit_report(void)
+{
+ int i;
+ int cnt;
+
+ cnt = 0;
+ for (i = sctp_audit_indx; i < SCTP_AUDIT_SIZE; i++) {
+ if ((sctp_audit_data[i][0] == 0xe0) &&
+ (sctp_audit_data[i][1] == 0x01)) {
+ cnt = 0;
+ SCTP_PRINTF("\n");
+ } else if (sctp_audit_data[i][0] == 0xf0) {
+ cnt = 0;
+ SCTP_PRINTF("\n");
+ } else if ((sctp_audit_data[i][0] == 0xc0) &&
+ (sctp_audit_data[i][1] == 0x01)) {
+ SCTP_PRINTF("\n");
+ cnt = 0;
+ }
+ SCTP_PRINTF("%2.2x%2.2x ", (uint32_t) sctp_audit_data[i][0],
+ (uint32_t) sctp_audit_data[i][1]);
+ cnt++;
+ if ((cnt % 14) == 0)
+ SCTP_PRINTF("\n");
+ }
+ for (i = 0; i < sctp_audit_indx; i++) {
+ if ((sctp_audit_data[i][0] == 0xe0) &&
+ (sctp_audit_data[i][1] == 0x01)) {
+ cnt = 0;
+ SCTP_PRINTF("\n");
+ } else if (sctp_audit_data[i][0] == 0xf0) {
+ cnt = 0;
+ SCTP_PRINTF("\n");
+ } else if ((sctp_audit_data[i][0] == 0xc0) &&
+ (sctp_audit_data[i][1] == 0x01)) {
+ SCTP_PRINTF("\n");
+ cnt = 0;
+ }
+ SCTP_PRINTF("%2.2x%2.2x ", (uint32_t) sctp_audit_data[i][0],
+ (uint32_t) sctp_audit_data[i][1]);
+ cnt++;
+ if ((cnt % 14) == 0)
+ SCTP_PRINTF("\n");
+ }
+ SCTP_PRINTF("\n");
+}
+
+void
+sctp_auditing(int from, struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ int resend_cnt, tot_out, rep, tot_book_cnt;
+ struct sctp_nets *lnet;
+ struct sctp_tmit_chunk *chk;
+
+ sctp_audit_data[sctp_audit_indx][0] = 0xAA;
+ sctp_audit_data[sctp_audit_indx][1] = 0x000000ff & from;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ if (inp == NULL) {
+ sctp_audit_data[sctp_audit_indx][0] = 0xAF;
+ sctp_audit_data[sctp_audit_indx][1] = 0x01;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ return;
+ }
+ if (stcb == NULL) {
+ sctp_audit_data[sctp_audit_indx][0] = 0xAF;
+ sctp_audit_data[sctp_audit_indx][1] = 0x02;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ return;
+ }
+ sctp_audit_data[sctp_audit_indx][0] = 0xA1;
+ sctp_audit_data[sctp_audit_indx][1] =
+ (0x000000ff & stcb->asoc.sent_queue_retran_cnt);
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ rep = 0;
+ tot_book_cnt = 0;
+ resend_cnt = tot_out = 0;
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->sent == SCTP_DATAGRAM_RESEND) {
+ resend_cnt++;
+ } else if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ tot_out += chk->book_size;
+ tot_book_cnt++;
+ }
+ }
+ if (resend_cnt != stcb->asoc.sent_queue_retran_cnt) {
+ sctp_audit_data[sctp_audit_indx][0] = 0xAF;
+ sctp_audit_data[sctp_audit_indx][1] = 0xA1;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ SCTP_PRINTF("resend_cnt:%d asoc-tot:%d\n",
+ resend_cnt, stcb->asoc.sent_queue_retran_cnt);
+ rep = 1;
+ stcb->asoc.sent_queue_retran_cnt = resend_cnt;
+ sctp_audit_data[sctp_audit_indx][0] = 0xA2;
+ sctp_audit_data[sctp_audit_indx][1] =
+ (0x000000ff & stcb->asoc.sent_queue_retran_cnt);
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ }
+ if (tot_out != stcb->asoc.total_flight) {
+ sctp_audit_data[sctp_audit_indx][0] = 0xAF;
+ sctp_audit_data[sctp_audit_indx][1] = 0xA2;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ rep = 1;
+ SCTP_PRINTF("tot_flt:%d asoc_tot:%d\n", tot_out,
+ (int)stcb->asoc.total_flight);
+ stcb->asoc.total_flight = tot_out;
+ }
+ if (tot_book_cnt != stcb->asoc.total_flight_count) {
+ sctp_audit_data[sctp_audit_indx][0] = 0xAF;
+ sctp_audit_data[sctp_audit_indx][1] = 0xA5;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ rep = 1;
+ SCTP_PRINTF("tot_flt_book:%d\n", tot_book_cnt);
+
+ stcb->asoc.total_flight_count = tot_book_cnt;
+ }
+ tot_out = 0;
+ TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) {
+ tot_out += lnet->flight_size;
+ }
+ if (tot_out != stcb->asoc.total_flight) {
+ sctp_audit_data[sctp_audit_indx][0] = 0xAF;
+ sctp_audit_data[sctp_audit_indx][1] = 0xA3;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+ rep = 1;
+ SCTP_PRINTF("real flight:%d net total was %d\n",
+ stcb->asoc.total_flight, tot_out);
+ /* now corrective action */
+ TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) {
+ tot_out = 0;
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if ((chk->whoTo == lnet) &&
+ (chk->sent < SCTP_DATAGRAM_RESEND)) {
+ tot_out += chk->book_size;
+ }
+ }
+ if (lnet->flight_size != tot_out) {
+ SCTP_PRINTF("net:%p flight was %d corrected to %d\n",
+ (void *)lnet, lnet->flight_size,
+ tot_out);
+ lnet->flight_size = tot_out;
+ }
+ }
+ }
+ if (rep) {
+ sctp_print_audit_report();
+ }
+}
+
+void
+sctp_audit_log(uint8_t ev, uint8_t fd)
+{
+
+ sctp_audit_data[sctp_audit_indx][0] = ev;
+ sctp_audit_data[sctp_audit_indx][1] = fd;
+ sctp_audit_indx++;
+ if (sctp_audit_indx >= SCTP_AUDIT_SIZE) {
+ sctp_audit_indx = 0;
+ }
+}
+
+#endif
+
+/*
+ * The conversion from time to ticks and vice versa is done by rounding
+ * upwards. This way we can test in the code the time to be positive and
+ * know that this corresponds to a positive number of ticks.
+ */
+
+uint32_t
+sctp_msecs_to_ticks(uint32_t msecs)
+{
+ uint64_t temp;
+ uint32_t ticks;
+
+ if (hz == 1000) {
+ ticks = msecs;
+ } else {
+ temp = (((uint64_t)msecs * hz) + 999) / 1000;
+ if (temp > UINT32_MAX) {
+ ticks = UINT32_MAX;
+ } else {
+ ticks = (uint32_t)temp;
+ }
+ }
+ return (ticks);
+}
+
+uint32_t
+sctp_ticks_to_msecs(uint32_t ticks)
+{
+ uint64_t temp;
+ uint32_t msecs;
+
+ if (hz == 1000) {
+ msecs = ticks;
+ } else {
+ temp = (((uint64_t)ticks * 1000) + (hz - 1)) / hz;
+ if (temp > UINT32_MAX) {
+ msecs = UINT32_MAX;
+ } else {
+ msecs = (uint32_t)temp;
+ }
+ }
+ return (msecs);
+}
+
+uint32_t
+sctp_secs_to_ticks(uint32_t secs)
+{
+ uint64_t temp;
+ uint32_t ticks;
+
+ temp = (uint64_t)secs * hz;
+ if (temp > UINT32_MAX) {
+ ticks = UINT32_MAX;
+ } else {
+ ticks = (uint32_t)temp;
+ }
+ return (ticks);
+}
+
+uint32_t
+sctp_ticks_to_secs(uint32_t ticks)
+{
+ uint64_t temp;
+ uint32_t secs;
+
+ temp = ((uint64_t)ticks + (hz - 1)) / hz;
+ if (temp > UINT32_MAX) {
+ secs = UINT32_MAX;
+ } else {
+ secs = (uint32_t)temp;
+ }
+ return (secs);
+}
+
+/*
+ * sctp_stop_timers_for_shutdown() should be called
+ * when entering the SHUTDOWN_SENT or SHUTDOWN_ACK_SENT
+ * state to make sure that all timers are stopped.
+ */
+void
+sctp_stop_timers_for_shutdown(struct sctp_tcb *stcb)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_nets *net;
+
+ inp = stcb->sctp_ep;
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_12);
+ sctp_timer_stop(SCTP_TIMER_TYPE_STRRESET, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_13);
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_14);
+ sctp_timer_stop(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_15);
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_16);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_17);
+ }
+}
+
+void
+sctp_stop_association_timers(struct sctp_tcb *stcb, bool stop_assoc_kill_timer)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_nets *net;
+
+ inp = stcb->sctp_ep;
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_18);
+ sctp_timer_stop(SCTP_TIMER_TYPE_STRRESET, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_19);
+ if (stop_assoc_kill_timer) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_20);
+ }
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_21);
+ sctp_timer_stop(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_22);
+ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWNGUARD, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_23);
+ /* Mobility adaptation */
+ sctp_timer_stop(SCTP_TIMER_TYPE_PRIM_DELETED, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_24);
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_25);
+ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_26);
+ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_27);
+ sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_28);
+ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWNACK, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_29);
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_30);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_31);
+ }
+}
+
+/*
+ * A list of sizes based on typical mtu's, used only if next hop size not
+ * returned. These values MUST be multiples of 4 and MUST be ordered.
+ */
+static uint32_t sctp_mtu_sizes[] = {
+ 68,
+ 296,
+ 508,
+ 512,
+ 544,
+ 576,
+ 1004,
+ 1492,
+ 1500,
+ 1536,
+ 2000,
+ 2048,
+ 4352,
+ 4464,
+ 8168,
+ 17912,
+ 32000,
+ 65532
+};
+
+/*
+ * Return the largest MTU in sctp_mtu_sizes smaller than val.
+ * If val is smaller than the minimum, just return the largest
+ * multiple of 4 smaller or equal to val.
+ * Ensure that the result is a multiple of 4.
+ */
+uint32_t
+sctp_get_prev_mtu(uint32_t val)
+{
+ uint32_t i;
+
+ val &= 0xfffffffc;
+ if (val <= sctp_mtu_sizes[0]) {
+ return (val);
+ }
+ for (i = 1; i < (sizeof(sctp_mtu_sizes) / sizeof(uint32_t)); i++) {
+ if (val <= sctp_mtu_sizes[i]) {
+ break;
+ }
+ }
+ KASSERT((sctp_mtu_sizes[i - 1] & 0x00000003) == 0,
+ ("sctp_mtu_sizes[%u] not a multiple of 4", i - 1));
+ return (sctp_mtu_sizes[i - 1]);
+}
+
+/*
+ * Return the smallest MTU in sctp_mtu_sizes larger than val.
+ * If val is larger than the maximum, just return the largest multiple of 4 smaller
+ * or equal to val.
+ * Ensure that the result is a multiple of 4.
+ */
+uint32_t
+sctp_get_next_mtu(uint32_t val)
+{
+ /* select another MTU that is just bigger than this one */
+ uint32_t i;
+
+ val &= 0xfffffffc;
+ for (i = 0; i < (sizeof(sctp_mtu_sizes) / sizeof(uint32_t)); i++) {
+ if (val < sctp_mtu_sizes[i]) {
+ KASSERT((sctp_mtu_sizes[i] & 0x00000003) == 0,
+ ("sctp_mtu_sizes[%u] not a multiple of 4", i));
+ return (sctp_mtu_sizes[i]);
+ }
+ }
+ return (val);
+}
+
+void
+sctp_fill_random_store(struct sctp_pcb *m)
+{
+ /*
+ * Here we use the MD5/SHA-1 to hash with our good randomNumbers and
+ * our counter. The result becomes our good random numbers and we
+ * then setup to give these out. Note that we do no locking to
+ * protect this. This is ok, since if competing folks call this we
+ * will get more gobbled gook in the random store which is what we
+ * want. There is a danger that two guys will use the same random
+ * numbers, but thats ok too since that is random as well :->
+ */
+ m->store_at = 0;
+#if defined(__Userspace__) && defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ for (int i = 0; i < (int) (sizeof(m->random_store) / sizeof(m->random_store[0])); i++) {
+ m->random_store[i] = (uint8_t) rand();
+ }
+#else
+ (void)sctp_hmac(SCTP_HMAC, (uint8_t *)m->random_numbers,
+ sizeof(m->random_numbers), (uint8_t *)&m->random_counter,
+ sizeof(m->random_counter), (uint8_t *)m->random_store);
+#endif
+ m->random_counter++;
+}
+
+uint32_t
+sctp_select_initial_TSN(struct sctp_pcb *inp)
+{
+ /*
+ * A true implementation should use random selection process to get
+ * the initial stream sequence number, using RFC1750 as a good
+ * guideline
+ */
+ uint32_t x, *xp;
+ uint8_t *p;
+ int store_at, new_store;
+
+ if (inp->initial_sequence_debug != 0) {
+ uint32_t ret;
+
+ ret = inp->initial_sequence_debug;
+ inp->initial_sequence_debug++;
+ return (ret);
+ }
+ retry:
+ store_at = inp->store_at;
+ new_store = store_at + sizeof(uint32_t);
+ if (new_store >= (SCTP_SIGNATURE_SIZE-3)) {
+ new_store = 0;
+ }
+ if (!atomic_cmpset_int(&inp->store_at, store_at, new_store)) {
+ goto retry;
+ }
+ if (new_store == 0) {
+ /* Refill the random store */
+ sctp_fill_random_store(inp);
+ }
+ p = &inp->random_store[store_at];
+ xp = (uint32_t *)p;
+ x = *xp;
+ return (x);
+}
+
+uint32_t
+sctp_select_a_tag(struct sctp_inpcb *inp, uint16_t lport, uint16_t rport, int check)
+{
+ uint32_t x;
+ struct timeval now;
+
+ if (check) {
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ }
+ for (;;) {
+ x = sctp_select_initial_TSN(&inp->sctp_ep);
+ if (x == 0) {
+ /* we never use 0 */
+ continue;
+ }
+ if (!check || sctp_is_vtag_good(x, lport, rport, &now)) {
+ break;
+ }
+ }
+ return (x);
+}
+
+int32_t
+sctp_map_assoc_state(int kernel_state)
+{
+ int32_t user_state;
+
+ if (kernel_state & SCTP_STATE_WAS_ABORTED) {
+ user_state = SCTP_CLOSED;
+ } else if (kernel_state & SCTP_STATE_SHUTDOWN_PENDING) {
+ user_state = SCTP_SHUTDOWN_PENDING;
+ } else {
+ switch (kernel_state & SCTP_STATE_MASK) {
+ case SCTP_STATE_EMPTY:
+ user_state = SCTP_CLOSED;
+ break;
+ case SCTP_STATE_INUSE:
+ user_state = SCTP_CLOSED;
+ break;
+ case SCTP_STATE_COOKIE_WAIT:
+ user_state = SCTP_COOKIE_WAIT;
+ break;
+ case SCTP_STATE_COOKIE_ECHOED:
+ user_state = SCTP_COOKIE_ECHOED;
+ break;
+ case SCTP_STATE_OPEN:
+ user_state = SCTP_ESTABLISHED;
+ break;
+ case SCTP_STATE_SHUTDOWN_SENT:
+ user_state = SCTP_SHUTDOWN_SENT;
+ break;
+ case SCTP_STATE_SHUTDOWN_RECEIVED:
+ user_state = SCTP_SHUTDOWN_RECEIVED;
+ break;
+ case SCTP_STATE_SHUTDOWN_ACK_SENT:
+ user_state = SCTP_SHUTDOWN_ACK_SENT;
+ break;
+ default:
+ user_state = SCTP_CLOSED;
+ break;
+ }
+ }
+ return (user_state);
+}
+
+int
+sctp_init_asoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ uint32_t override_tag, uint32_t initial_tsn, uint32_t vrf_id,
+ uint16_t o_strms)
+{
+ struct sctp_association *asoc;
+ /*
+ * Anything set to zero is taken care of by the allocation routine's
+ * bzero
+ */
+
+ /*
+ * Up front select what scoping to apply on addresses I tell my peer
+ * Not sure what to do with these right now, we will need to come up
+ * with a way to set them. We may need to pass them through from the
+ * caller in the sctp_aloc_assoc() function.
+ */
+ int i;
+#if defined(SCTP_DETAILED_STR_STATS)
+ int j;
+#endif
+
+ asoc = &stcb->asoc;
+ /* init all variables to a known value. */
+ SCTP_SET_STATE(stcb, SCTP_STATE_INUSE);
+ asoc->max_burst = inp->sctp_ep.max_burst;
+ asoc->fr_max_burst = inp->sctp_ep.fr_max_burst;
+ asoc->heart_beat_delay = sctp_ticks_to_msecs(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT]);
+ asoc->cookie_life = inp->sctp_ep.def_cookie_life;
+ asoc->sctp_cmt_on_off = inp->sctp_cmt_on_off;
+ asoc->ecn_supported = inp->ecn_supported;
+ asoc->prsctp_supported = inp->prsctp_supported;
+ asoc->auth_supported = inp->auth_supported;
+ asoc->asconf_supported = inp->asconf_supported;
+ asoc->reconfig_supported = inp->reconfig_supported;
+ asoc->nrsack_supported = inp->nrsack_supported;
+ asoc->pktdrop_supported = inp->pktdrop_supported;
+ asoc->idata_supported = inp->idata_supported;
+ asoc->rcv_edmid = inp->rcv_edmid;
+ asoc->snd_edmid = SCTP_EDMID_NONE;
+ asoc->sctp_cmt_pf = (uint8_t)0;
+ asoc->sctp_frag_point = inp->sctp_frag_point;
+ asoc->sctp_features = inp->sctp_features;
+ asoc->default_dscp = inp->sctp_ep.default_dscp;
+ asoc->max_cwnd = inp->max_cwnd;
+#ifdef INET6
+ if (inp->sctp_ep.default_flowlabel) {
+ asoc->default_flowlabel = inp->sctp_ep.default_flowlabel;
+ } else {
+ if (inp->ip_inp.inp.inp_flags & IN6P_AUTOFLOWLABEL) {
+ asoc->default_flowlabel = sctp_select_initial_TSN(&inp->sctp_ep);
+ asoc->default_flowlabel &= 0x000fffff;
+ asoc->default_flowlabel |= 0x80000000;
+ } else {
+ asoc->default_flowlabel = 0;
+ }
+ }
+#endif
+ asoc->sb_send_resv = 0;
+ if (override_tag) {
+ asoc->my_vtag = override_tag;
+ } else {
+ asoc->my_vtag = sctp_select_a_tag(inp, stcb->sctp_ep->sctp_lport, stcb->rport, 1);
+ }
+ /* Get the nonce tags */
+ asoc->my_vtag_nonce = sctp_select_a_tag(inp, stcb->sctp_ep->sctp_lport, stcb->rport, 0);
+ asoc->peer_vtag_nonce = sctp_select_a_tag(inp, stcb->sctp_ep->sctp_lport, stcb->rport, 0);
+ asoc->vrf_id = vrf_id;
+
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ asoc->tsn_in_at = 0;
+ asoc->tsn_out_at = 0;
+ asoc->tsn_in_wrapped = 0;
+ asoc->tsn_out_wrapped = 0;
+ asoc->cumack_log_at = 0;
+ asoc->cumack_log_atsnt = 0;
+#endif
+#ifdef SCTP_FS_SPEC_LOG
+ asoc->fs_index = 0;
+#endif
+ asoc->refcnt = 0;
+ asoc->assoc_up_sent = 0;
+ if (override_tag) {
+ asoc->init_seq_number = initial_tsn;
+ } else {
+ asoc->init_seq_number = sctp_select_initial_TSN(&inp->sctp_ep);
+ }
+ asoc->asconf_seq_out = asoc->init_seq_number;
+ asoc->str_reset_seq_out = asoc->init_seq_number;
+ asoc->sending_seq = asoc->init_seq_number;
+ asoc->asconf_seq_out_acked = asoc->init_seq_number - 1;
+ /* we are optimistic here */
+ asoc->peer_supports_nat = 0;
+ asoc->sent_queue_retran_cnt = 0;
+
+ /* for CMT */
+ asoc->last_net_cmt_send_started = NULL;
+
+ asoc->last_acked_seq = asoc->init_seq_number - 1;
+ asoc->advanced_peer_ack_point = asoc->init_seq_number - 1;
+ asoc->asconf_seq_in = asoc->init_seq_number - 1;
+
+ /* here we are different, we hold the next one we expect */
+ asoc->str_reset_seq_in = asoc->init_seq_number;
+
+ asoc->initial_init_rto_max = inp->sctp_ep.initial_init_rto_max;
+ asoc->initial_rto = inp->sctp_ep.initial_rto;
+
+ asoc->default_mtu = inp->sctp_ep.default_mtu;
+ asoc->max_init_times = inp->sctp_ep.max_init_times;
+ asoc->max_send_times = inp->sctp_ep.max_send_times;
+ asoc->def_net_failure = inp->sctp_ep.def_net_failure;
+ asoc->def_net_pf_threshold = inp->sctp_ep.def_net_pf_threshold;
+ asoc->free_chunk_cnt = 0;
+
+ asoc->iam_blocking = 0;
+ asoc->context = inp->sctp_context;
+ asoc->local_strreset_support = inp->local_strreset_support;
+ asoc->def_send = inp->def_send;
+ asoc->delayed_ack = sctp_ticks_to_msecs(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]);
+ asoc->sack_freq = inp->sctp_ep.sctp_sack_freq;
+ asoc->pr_sctp_cnt = 0;
+ asoc->total_output_queue_size = 0;
+
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ asoc->scope.ipv6_addr_legal = 1;
+ if (SCTP_IPV6_V6ONLY(inp) == 0) {
+ asoc->scope.ipv4_addr_legal = 1;
+ } else {
+ asoc->scope.ipv4_addr_legal = 0;
+ }
+#if defined(__Userspace__)
+ asoc->scope.conn_addr_legal = 0;
+#endif
+ } else {
+ asoc->scope.ipv6_addr_legal = 0;
+#if defined(__Userspace__)
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ asoc->scope.conn_addr_legal = 1;
+ asoc->scope.ipv4_addr_legal = 0;
+ } else {
+ asoc->scope.conn_addr_legal = 0;
+ asoc->scope.ipv4_addr_legal = 1;
+ }
+#else
+ asoc->scope.ipv4_addr_legal = 1;
+#endif
+ }
+
+ asoc->my_rwnd = max(SCTP_SB_LIMIT_RCV(inp->sctp_socket), SCTP_MINIMAL_RWND);
+ asoc->peers_rwnd = SCTP_SB_LIMIT_RCV(inp->sctp_socket);
+
+ asoc->smallest_mtu = 0;
+ asoc->minrto = inp->sctp_ep.sctp_minrto;
+ asoc->maxrto = inp->sctp_ep.sctp_maxrto;
+
+ asoc->stream_locked_on = 0;
+ asoc->ecn_echo_cnt_onq = 0;
+ asoc->stream_locked = 0;
+
+ asoc->send_sack = 1;
+
+ LIST_INIT(&asoc->sctp_restricted_addrs);
+
+ TAILQ_INIT(&asoc->nets);
+ TAILQ_INIT(&asoc->pending_reply_queue);
+ TAILQ_INIT(&asoc->asconf_ack_sent);
+ /* Setup to fill the hb random cache at first HB */
+ asoc->hb_random_idx = 4;
+
+ asoc->sctp_autoclose_ticks = inp->sctp_ep.auto_close_time;
+
+ stcb->asoc.congestion_control_module = inp->sctp_ep.sctp_default_cc_module;
+ stcb->asoc.cc_functions = sctp_cc_functions[inp->sctp_ep.sctp_default_cc_module];
+
+ stcb->asoc.stream_scheduling_module = inp->sctp_ep.sctp_default_ss_module;
+ stcb->asoc.ss_functions = sctp_ss_functions[inp->sctp_ep.sctp_default_ss_module];
+
+ /*
+ * Now the stream parameters, here we allocate space for all streams
+ * that we request by default.
+ */
+ asoc->strm_realoutsize = asoc->streamoutcnt = asoc->pre_open_streams =
+ o_strms;
+ SCTP_MALLOC(asoc->strmout, struct sctp_stream_out *,
+ asoc->streamoutcnt * sizeof(struct sctp_stream_out),
+ SCTP_M_STRMO);
+ if (asoc->strmout == NULL) {
+ /* big trouble no memory */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ENOMEM);
+ return (ENOMEM);
+ }
+ SCTP_TCB_LOCK(stcb);
+ for (i = 0; i < asoc->streamoutcnt; i++) {
+ /*
+ * inbound side must be set to 0xffff, also NOTE when we get
+ * the INIT-ACK back (for INIT sender) we MUST reduce the
+ * count (streamoutcnt) but first check if we sent to any of
+ * the upper streams that were dropped (if some were). Those
+ * that were dropped must be notified to the upper layer as
+ * failed to send.
+ */
+ TAILQ_INIT(&asoc->strmout[i].outqueue);
+ asoc->ss_functions.sctp_ss_init_stream(stcb, &asoc->strmout[i], NULL);
+ asoc->strmout[i].chunks_on_queues = 0;
+#if defined(SCTP_DETAILED_STR_STATS)
+ for (j = 0; j < SCTP_PR_SCTP_MAX + 1; j++) {
+ asoc->strmout[i].abandoned_sent[j] = 0;
+ asoc->strmout[i].abandoned_unsent[j] = 0;
+ }
+#else
+ asoc->strmout[i].abandoned_sent[0] = 0;
+ asoc->strmout[i].abandoned_unsent[0] = 0;
+#endif
+ asoc->strmout[i].next_mid_ordered = 0;
+ asoc->strmout[i].next_mid_unordered = 0;
+ asoc->strmout[i].sid = i;
+ asoc->strmout[i].last_msg_incomplete = 0;
+ asoc->strmout[i].state = SCTP_STREAM_OPENING;
+ }
+ asoc->ss_functions.sctp_ss_init(stcb, asoc);
+ SCTP_TCB_UNLOCK(stcb);
+
+ /* Now the mapping array */
+ asoc->mapping_array_size = SCTP_INITIAL_MAPPING_ARRAY;
+ SCTP_MALLOC(asoc->mapping_array, uint8_t *, asoc->mapping_array_size,
+ SCTP_M_MAP);
+ if (asoc->mapping_array == NULL) {
+ SCTP_FREE(asoc->strmout, SCTP_M_STRMO);
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ENOMEM);
+ return (ENOMEM);
+ }
+ memset(asoc->mapping_array, 0, asoc->mapping_array_size);
+ SCTP_MALLOC(asoc->nr_mapping_array, uint8_t *, asoc->mapping_array_size,
+ SCTP_M_MAP);
+ if (asoc->nr_mapping_array == NULL) {
+ SCTP_FREE(asoc->strmout, SCTP_M_STRMO);
+ SCTP_FREE(asoc->mapping_array, SCTP_M_MAP);
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ENOMEM);
+ return (ENOMEM);
+ }
+ memset(asoc->nr_mapping_array, 0, asoc->mapping_array_size);
+
+ /* Now the init of the other outqueues */
+ TAILQ_INIT(&asoc->free_chunks);
+ TAILQ_INIT(&asoc->control_send_queue);
+ TAILQ_INIT(&asoc->asconf_send_queue);
+ TAILQ_INIT(&asoc->send_queue);
+ TAILQ_INIT(&asoc->sent_queue);
+ TAILQ_INIT(&asoc->resetHead);
+ asoc->max_inbound_streams = inp->sctp_ep.max_open_streams_intome;
+ TAILQ_INIT(&asoc->asconf_queue);
+ /* authentication fields */
+ asoc->authinfo.random = NULL;
+ asoc->authinfo.active_keyid = 0;
+ asoc->authinfo.assoc_key = NULL;
+ asoc->authinfo.assoc_keyid = 0;
+ asoc->authinfo.recv_key = NULL;
+ asoc->authinfo.recv_keyid = 0;
+ LIST_INIT(&asoc->shared_keys);
+ asoc->marked_retrans = 0;
+ asoc->port = inp->sctp_ep.port;
+ asoc->timoinit = 0;
+ asoc->timodata = 0;
+ asoc->timosack = 0;
+ asoc->timoshutdown = 0;
+ asoc->timoheartbeat = 0;
+ asoc->timocookie = 0;
+ asoc->timoshutdownack = 0;
+ (void)SCTP_GETTIME_TIMEVAL(&asoc->start_time);
+ asoc->discontinuity_time = asoc->start_time;
+ for (i = 0; i < SCTP_PR_SCTP_MAX + 1; i++) {
+ asoc->abandoned_unsent[i] = 0;
+ asoc->abandoned_sent[i] = 0;
+ }
+ /* sa_ignore MEMLEAK {memory is put in the assoc mapping array and freed later when
+ * the association is freed.
+ */
+ return (0);
+}
+
+void
+sctp_print_mapping_array(struct sctp_association *asoc)
+{
+ unsigned int i, limit;
+
+ SCTP_PRINTF("Mapping array size: %d, baseTSN: %8.8x, cumAck: %8.8x, highestTSN: (%8.8x, %8.8x).\n",
+ asoc->mapping_array_size,
+ asoc->mapping_array_base_tsn,
+ asoc->cumulative_tsn,
+ asoc->highest_tsn_inside_map,
+ asoc->highest_tsn_inside_nr_map);
+ for (limit = asoc->mapping_array_size; limit > 1; limit--) {
+ if (asoc->mapping_array[limit - 1] != 0) {
+ break;
+ }
+ }
+ SCTP_PRINTF("Renegable mapping array (last %d entries are zero):\n", asoc->mapping_array_size - limit);
+ for (i = 0; i < limit; i++) {
+ SCTP_PRINTF("%2.2x%c", asoc->mapping_array[i], ((i + 1) % 16) ? ' ' : '\n');
+ }
+ if (limit % 16)
+ SCTP_PRINTF("\n");
+ for (limit = asoc->mapping_array_size; limit > 1; limit--) {
+ if (asoc->nr_mapping_array[limit - 1]) {
+ break;
+ }
+ }
+ SCTP_PRINTF("Non renegable mapping array (last %d entries are zero):\n", asoc->mapping_array_size - limit);
+ for (i = 0; i < limit; i++) {
+ SCTP_PRINTF("%2.2x%c", asoc->nr_mapping_array[i], ((i + 1) % 16) ? ' ': '\n');
+ }
+ if (limit % 16)
+ SCTP_PRINTF("\n");
+}
+
+int
+sctp_expand_mapping_array(struct sctp_association *asoc, uint32_t needed)
+{
+ /* mapping array needs to grow */
+ uint8_t *new_array1, *new_array2;
+ uint32_t new_size;
+
+ new_size = asoc->mapping_array_size + ((needed+7)/8 + SCTP_MAPPING_ARRAY_INCR);
+ SCTP_MALLOC(new_array1, uint8_t *, new_size, SCTP_M_MAP);
+ SCTP_MALLOC(new_array2, uint8_t *, new_size, SCTP_M_MAP);
+ if ((new_array1 == NULL) || (new_array2 == NULL)) {
+ /* can't get more, forget it */
+ SCTP_PRINTF("No memory for expansion of SCTP mapping array %d\n", new_size);
+ if (new_array1) {
+ SCTP_FREE(new_array1, SCTP_M_MAP);
+ }
+ if (new_array2) {
+ SCTP_FREE(new_array2, SCTP_M_MAP);
+ }
+ return (-1);
+ }
+ memset(new_array1, 0, new_size);
+ memset(new_array2, 0, new_size);
+ memcpy(new_array1, asoc->mapping_array, asoc->mapping_array_size);
+ memcpy(new_array2, asoc->nr_mapping_array, asoc->mapping_array_size);
+ SCTP_FREE(asoc->mapping_array, SCTP_M_MAP);
+ SCTP_FREE(asoc->nr_mapping_array, SCTP_M_MAP);
+ asoc->mapping_array = new_array1;
+ asoc->nr_mapping_array = new_array2;
+ asoc->mapping_array_size = new_size;
+ return (0);
+}
+
+static void
+sctp_iterator_work(struct sctp_iterator *it)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_inpcb *tinp;
+ int iteration_count = 0;
+ int inp_skip = 0;
+ int first_in = 1;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ SCTP_INP_INFO_RLOCK();
+ SCTP_ITERATOR_LOCK();
+ sctp_it_ctl.cur_it = it;
+ if (it->inp) {
+ SCTP_INP_RLOCK(it->inp);
+ SCTP_INP_DECR_REF(it->inp);
+ }
+ if (it->inp == NULL) {
+ /* iterator is complete */
+done_with_iterator:
+ sctp_it_ctl.cur_it = NULL;
+ SCTP_ITERATOR_UNLOCK();
+ SCTP_INP_INFO_RUNLOCK();
+ if (it->function_atend != NULL) {
+ (*it->function_atend) (it->pointer, it->val);
+ }
+ SCTP_FREE(it, SCTP_M_ITER);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return;
+ }
+select_a_new_ep:
+ if (first_in) {
+ first_in = 0;
+ } else {
+ SCTP_INP_RLOCK(it->inp);
+ }
+ while (((it->pcb_flags) &&
+ ((it->inp->sctp_flags & it->pcb_flags) != it->pcb_flags)) ||
+ ((it->pcb_features) &&
+ ((it->inp->sctp_features & it->pcb_features) != it->pcb_features))) {
+ /* endpoint flags or features don't match, so keep looking */
+ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) {
+ SCTP_INP_RUNLOCK(it->inp);
+ goto done_with_iterator;
+ }
+ tinp = it->inp;
+ it->inp = LIST_NEXT(it->inp, sctp_list);
+ it->stcb = NULL;
+ SCTP_INP_RUNLOCK(tinp);
+ if (it->inp == NULL) {
+ goto done_with_iterator;
+ }
+ SCTP_INP_RLOCK(it->inp);
+ }
+ /* now go through each assoc which is in the desired state */
+ if (it->done_current_ep == 0) {
+ if (it->function_inp != NULL)
+ inp_skip = (*it->function_inp)(it->inp, it->pointer, it->val);
+ it->done_current_ep = 1;
+ }
+ if (it->stcb == NULL) {
+ /* run the per instance function */
+ it->stcb = LIST_FIRST(&it->inp->sctp_asoc_list);
+ }
+ if ((inp_skip) || it->stcb == NULL) {
+ if (it->function_inp_end != NULL) {
+ inp_skip = (*it->function_inp_end)(it->inp,
+ it->pointer,
+ it->val);
+ }
+ SCTP_INP_RUNLOCK(it->inp);
+ goto no_stcb;
+ }
+ while (it->stcb != NULL) {
+ SCTP_TCB_LOCK(it->stcb);
+ if (it->asoc_state && ((it->stcb->asoc.state & it->asoc_state) != it->asoc_state)) {
+ /* not in the right state... keep looking */
+ SCTP_TCB_UNLOCK(it->stcb);
+ goto next_assoc;
+ }
+ /* see if we have limited out the iterator loop */
+ iteration_count++;
+ if (iteration_count > SCTP_ITERATOR_MAX_AT_ONCE) {
+ /* Pause to let others grab the lock */
+ atomic_add_int(&it->stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(it->stcb);
+ SCTP_INP_INCR_REF(it->inp);
+ SCTP_INP_RUNLOCK(it->inp);
+ SCTP_ITERATOR_UNLOCK();
+ SCTP_INP_INFO_RUNLOCK();
+ SCTP_INP_INFO_RLOCK();
+ SCTP_ITERATOR_LOCK();
+ if (sctp_it_ctl.iterator_flags) {
+ /* We won't be staying here */
+ SCTP_INP_DECR_REF(it->inp);
+ atomic_subtract_int(&it->stcb->asoc.refcnt, 1);
+#if !(defined(__FreeBSD__) && !defined(__Userspace__))
+ if (sctp_it_ctl.iterator_flags &
+ SCTP_ITERATOR_MUST_EXIT) {
+ goto done_with_iterator;
+ }
+#endif
+ if (sctp_it_ctl.iterator_flags &
+ SCTP_ITERATOR_STOP_CUR_IT) {
+ sctp_it_ctl.iterator_flags &= ~SCTP_ITERATOR_STOP_CUR_IT;
+ goto done_with_iterator;
+ }
+ if (sctp_it_ctl.iterator_flags &
+ SCTP_ITERATOR_STOP_CUR_INP) {
+ sctp_it_ctl.iterator_flags &= ~SCTP_ITERATOR_STOP_CUR_INP;
+ goto no_stcb;
+ }
+ /* If we reach here huh? */
+ SCTP_PRINTF("Unknown it ctl flag %x\n",
+ sctp_it_ctl.iterator_flags);
+ sctp_it_ctl.iterator_flags = 0;
+ }
+ SCTP_INP_RLOCK(it->inp);
+ SCTP_INP_DECR_REF(it->inp);
+ SCTP_TCB_LOCK(it->stcb);
+ atomic_subtract_int(&it->stcb->asoc.refcnt, 1);
+ iteration_count = 0;
+ }
+ KASSERT(it->inp == it->stcb->sctp_ep,
+ ("%s: stcb %p does not belong to inp %p, but inp %p",
+ __func__, it->stcb, it->inp, it->stcb->sctp_ep));
+ SCTP_INP_RLOCK_ASSERT(it->inp);
+ SCTP_TCB_LOCK_ASSERT(it->stcb);
+
+ /* run function on this one */
+ (*it->function_assoc)(it->inp, it->stcb, it->pointer, it->val);
+ SCTP_INP_RLOCK_ASSERT(it->inp);
+ SCTP_TCB_LOCK_ASSERT(it->stcb);
+
+ /*
+ * we lie here, it really needs to have its own type but
+ * first I must verify that this won't effect things :-0
+ */
+ if (it->no_chunk_output == 0) {
+ sctp_chunk_output(it->inp, it->stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ SCTP_INP_RLOCK_ASSERT(it->inp);
+ SCTP_TCB_LOCK_ASSERT(it->stcb);
+ }
+
+ SCTP_TCB_UNLOCK(it->stcb);
+ next_assoc:
+ it->stcb = LIST_NEXT(it->stcb, sctp_tcblist);
+ if (it->stcb == NULL) {
+ /* Run last function */
+ if (it->function_inp_end != NULL) {
+ inp_skip = (*it->function_inp_end)(it->inp,
+ it->pointer,
+ it->val);
+ }
+ }
+ }
+ SCTP_INP_RUNLOCK(it->inp);
+ no_stcb:
+ /* done with all assocs on this endpoint, move on to next endpoint */
+ it->done_current_ep = 0;
+ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) {
+ it->inp = NULL;
+ } else {
+ it->inp = LIST_NEXT(it->inp, sctp_list);
+ }
+ it->stcb = NULL;
+ if (it->inp == NULL) {
+ goto done_with_iterator;
+ }
+ goto select_a_new_ep;
+}
+
+void
+sctp_iterator_worker(void)
+{
+ struct sctp_iterator *it;
+
+ /* This function is called with the WQ lock in place */
+ sctp_it_ctl.iterator_running = 1;
+ while ((it = TAILQ_FIRST(&sctp_it_ctl.iteratorhead)) != NULL) {
+ /* now lets work on this one */
+ TAILQ_REMOVE(&sctp_it_ctl.iteratorhead, it, sctp_nxt_itr);
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_SET(it->vn);
+#endif
+ sctp_iterator_work(it);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+#endif
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+#if !defined(__FreeBSD__) && !defined(__Userspace__)
+ if (sctp_it_ctl.iterator_flags & SCTP_ITERATOR_MUST_EXIT) {
+ break;
+ }
+#endif
+ /*sa_ignore FREED_MEMORY*/
+ }
+ sctp_it_ctl.iterator_running = 0;
+ return;
+}
+
+static void
+sctp_handle_addr_wq(void)
+{
+ /* deal with the ADDR wq from the rtsock calls */
+ struct sctp_laddr *wi, *nwi;
+ struct sctp_asconf_iterator *asc;
+
+ SCTP_MALLOC(asc, struct sctp_asconf_iterator *,
+ sizeof(struct sctp_asconf_iterator), SCTP_M_ASC_IT);
+ if (asc == NULL) {
+ /* Try later, no memory */
+ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
+ (struct sctp_inpcb *)NULL,
+ (struct sctp_tcb *)NULL,
+ (struct sctp_nets *)NULL);
+ return;
+ }
+ LIST_INIT(&asc->list_of_work);
+ asc->cnt = 0;
+
+ LIST_FOREACH_SAFE(wi, &SCTP_BASE_INFO(addr_wq), sctp_nxt_addr, nwi) {
+ LIST_REMOVE(wi, sctp_nxt_addr);
+ LIST_INSERT_HEAD(&asc->list_of_work, wi, sctp_nxt_addr);
+ asc->cnt++;
+ }
+
+ if (asc->cnt == 0) {
+ SCTP_FREE(asc, SCTP_M_ASC_IT);
+ } else {
+ int ret;
+
+ ret = sctp_initiate_iterator(sctp_asconf_iterator_ep,
+ sctp_asconf_iterator_stcb,
+ NULL, /* No ep end for boundall */
+ SCTP_PCB_FLAGS_BOUNDALL,
+ SCTP_PCB_ANY_FEATURES,
+ SCTP_ASOC_ANY_STATE,
+ (void *)asc, 0,
+ sctp_asconf_iterator_end, NULL, 0);
+ if (ret) {
+ SCTP_PRINTF("Failed to initiate iterator for handle_addr_wq\n");
+ /* Freeing if we are stopping or put back on the addr_wq. */
+ if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
+ sctp_asconf_iterator_end(asc, 0);
+ } else {
+ LIST_FOREACH(wi, &asc->list_of_work, sctp_nxt_addr) {
+ LIST_INSERT_HEAD(&SCTP_BASE_INFO(addr_wq), wi, sctp_nxt_addr);
+ }
+ SCTP_FREE(asc, SCTP_M_ASC_IT);
+ }
+ }
+ }
+}
+
+/*-
+ * The following table shows which pointers for the inp, stcb, or net are
+ * stored for each timer after it was started.
+ *
+ *|Name |Timer |inp |stcb|net |
+ *|-----------------------------|-----------------------------|----|----|----|
+ *|SCTP_TIMER_TYPE_SEND |net->rxt_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_INIT |net->rxt_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_RECV |stcb->asoc.dack_timer |Yes |Yes |No |
+ *|SCTP_TIMER_TYPE_SHUTDOWN |net->rxt_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_HEARTBEAT |net->hb_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_COOKIE |net->rxt_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_NEWCOOKIE |inp->sctp_ep.signature_change|Yes |No |No |
+ *|SCTP_TIMER_TYPE_PATHMTURAISE |net->pmtu_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_SHUTDOWNACK |net->rxt_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_ASCONF |stcb->asoc.asconf_timer |Yes |Yes |Yes |
+ *|SCTP_TIMER_TYPE_SHUTDOWNGUARD|stcb->asoc.shut_guard_timer |Yes |Yes |No |
+ *|SCTP_TIMER_TYPE_AUTOCLOSE |stcb->asoc.autoclose_timer |Yes |Yes |No |
+ *|SCTP_TIMER_TYPE_STRRESET |stcb->asoc.strreset_timer |Yes |Yes |No |
+ *|SCTP_TIMER_TYPE_INPKILL |inp->sctp_ep.signature_change|Yes |No |No |
+ *|SCTP_TIMER_TYPE_ASOCKILL |stcb->asoc.strreset_timer |Yes |Yes |No |
+ *|SCTP_TIMER_TYPE_ADDR_WQ |SCTP_BASE_INFO(addr_wq_timer)|No |No |No |
+ *|SCTP_TIMER_TYPE_PRIM_DELETED |stcb->asoc.delete_prim_timer |Yes |Yes |No |
+ */
+
+void
+sctp_timeout_handler(void *t)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct timeval tv;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sctp_timer *tmr;
+ struct mbuf *op_err;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+#if defined(__Userspace__)
+ struct socket *upcall_socket = NULL;
+#endif
+ int type;
+ int i, secret;
+ bool did_output, released_asoc_reference;
+
+ /*
+ * If inp, stcb or net are not NULL, then references to these were
+ * added when the timer was started, and must be released before this
+ * function returns.
+ */
+ tmr = (struct sctp_timer *)t;
+ inp = (struct sctp_inpcb *)tmr->ep;
+ stcb = (struct sctp_tcb *)tmr->tcb;
+ net = (struct sctp_nets *)tmr->net;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_SET((struct vnet *)tmr->vnet);
+ NET_EPOCH_ENTER(et);
+#endif
+ released_asoc_reference = false;
+
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xF0, (uint8_t) tmr->type);
+ sctp_auditing(3, inp, stcb, net);
+#endif
+
+ /* sanity checks... */
+ KASSERT(tmr->self == NULL || tmr->self == tmr,
+ ("sctp_timeout_handler: tmr->self corrupted"));
+ KASSERT(SCTP_IS_TIMER_TYPE_VALID(tmr->type),
+ ("sctp_timeout_handler: invalid timer type %d", tmr->type));
+ type = tmr->type;
+ KASSERT(stcb == NULL || stcb->sctp_ep == inp,
+ ("sctp_timeout_handler of type %d: inp = %p, stcb->sctp_ep %p",
+ type, stcb, stcb->sctp_ep));
+ tmr->stopped_from = 0xa001;
+ if ((stcb != NULL) && (stcb->asoc.state == SCTP_STATE_EMPTY)) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d handler exiting due to CLOSED association.\n",
+ type);
+ goto out_decr;
+ }
+ tmr->stopped_from = 0xa002;
+ SCTPDBG(SCTP_DEBUG_TIMER2, "Timer type %d goes off.\n", type);
+ if (!SCTP_OS_TIMER_ACTIVE(&tmr->timer)) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d handler exiting due to not being active.\n",
+ type);
+ goto out_decr;
+ }
+
+ tmr->stopped_from = 0xa003;
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ /*
+ * Release reference so that association can be freed if
+ * necessary below.
+ * This is safe now that we have acquired the lock.
+ */
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ released_asoc_reference = true;
+ if ((type != SCTP_TIMER_TYPE_ASOCKILL) &&
+ ((stcb->asoc.state == SCTP_STATE_EMPTY) ||
+ (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED))) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d handler exiting due to CLOSED association.\n",
+ type);
+ goto out;
+ }
+ } else if (inp != NULL) {
+ SCTP_INP_WLOCK(inp);
+ } else {
+ SCTP_WQ_ADDR_LOCK();
+ }
+
+ /* Record in stopped_from which timeout occurred. */
+ tmr->stopped_from = type;
+ /* mark as being serviced now */
+ if (SCTP_OS_TIMER_PENDING(&tmr->timer)) {
+ /*
+ * Callout has been rescheduled.
+ */
+ goto out;
+ }
+ if (!SCTP_OS_TIMER_ACTIVE(&tmr->timer)) {
+ /*
+ * Not active, so no action.
+ */
+ goto out;
+ }
+ SCTP_OS_TIMER_DEACTIVATE(&tmr->timer);
+
+#if defined(__Userspace__)
+ if ((stcb != NULL) &&
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (stcb->sctp_socket != NULL)) {
+ upcall_socket = stcb->sctp_socket;
+ SOCK_LOCK(upcall_socket);
+ soref(upcall_socket);
+ SOCK_UNLOCK(upcall_socket);
+ }
+#endif
+ /* call the handler for the appropriate timer type */
+ switch (type) {
+ case SCTP_TIMER_TYPE_SEND:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timodata);
+ stcb->asoc.timodata++;
+ stcb->asoc.num_send_timers_up--;
+ if (stcb->asoc.num_send_timers_up < 0) {
+ stcb->asoc.num_send_timers_up = 0;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ if (sctp_t3rxt_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+
+ goto out_decr;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, inp, stcb, net);
+#endif
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ if ((stcb->asoc.num_send_timers_up == 0) &&
+ (stcb->asoc.sent_queue_cnt > 0)) {
+ struct sctp_tmit_chunk *chk;
+
+ /*
+ * Safeguard. If there on some on the sent queue
+ * somewhere but no timers running something is
+ * wrong... so we start a timer on the first chunk
+ * on the send queue on whatever net it is sent to.
+ */
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (chk != NULL) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, chk->whoTo);
+ }
+ }
+ break;
+ case SCTP_TIMER_TYPE_INIT:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoinit);
+ stcb->asoc.timoinit++;
+ if (sctp_t1init_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+ did_output = false;
+ break;
+ case SCTP_TIMER_TYPE_RECV:
+ KASSERT(inp != NULL && stcb != NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timosack);
+ stcb->asoc.timosack++;
+ sctp_send_sack(stcb, SCTP_SO_NOT_LOCKED);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, inp, stcb, NULL);
+#endif
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SACK_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWN:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoshutdown);
+ stcb->asoc.timoshutdown++;
+ if (sctp_shutdown_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, inp, stcb, net);
+#endif
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_HEARTBEAT:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoheartbeat);
+ stcb->asoc.timoheartbeat++;
+ if (sctp_heartbeat_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, inp, stcb, net);
+#endif
+ if ((net->dest_state & SCTP_ADDR_NOHB) == 0) {
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_HB_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ } else {
+ did_output = false;
+ }
+ break;
+ case SCTP_TIMER_TYPE_COOKIE:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timocookie);
+ stcb->asoc.timocookie++;
+ if (sctp_cookie_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, inp, stcb, net);
+#endif
+ /*
+ * We consider T3 and Cookie timer pretty much the same with
+ * respect to where from in chunk_output.
+ */
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_NEWCOOKIE:
+ KASSERT(inp != NULL && stcb == NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timosecret);
+ (void)SCTP_GETTIME_TIMEVAL(&tv);
+ inp->sctp_ep.time_of_secret_change = (unsigned int)tv.tv_sec;
+ inp->sctp_ep.last_secret_number =
+ inp->sctp_ep.current_secret_number;
+ inp->sctp_ep.current_secret_number++;
+ if (inp->sctp_ep.current_secret_number >=
+ SCTP_HOW_MANY_SECRETS) {
+ inp->sctp_ep.current_secret_number = 0;
+ }
+ secret = (int)inp->sctp_ep.current_secret_number;
+ for (i = 0; i < SCTP_NUMBER_OF_SECRETS; i++) {
+ inp->sctp_ep.secret_key[secret][i] =
+ sctp_select_initial_TSN(&inp->sctp_ep);
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL);
+ did_output = false;
+ break;
+ case SCTP_TIMER_TYPE_PATHMTURAISE:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timopathmtu);
+ sctp_pathmtu_timer(inp, stcb, net);
+ did_output = false;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNACK:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ if (sctp_shutdownack_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+ SCTP_STAT_INCR(sctps_timoshutdownack);
+ stcb->asoc.timoshutdownack++;
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, inp, stcb, net);
+#endif
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_ACK_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_ASCONF:
+ KASSERT(inp != NULL && stcb != NULL && net != NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoasconf);
+ if (sctp_asconf_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, inp, stcb, net);
+#endif
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_ASCONF_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNGUARD:
+ KASSERT(inp != NULL && stcb != NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoshutdownguard);
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "Shutdown guard timer expired");
+ sctp_abort_an_association(inp, stcb, op_err, true, SCTP_SO_NOT_LOCKED);
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ case SCTP_TIMER_TYPE_AUTOCLOSE:
+ KASSERT(inp != NULL && stcb != NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoautoclose);
+ sctp_autoclose_timer(inp, stcb);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_AUTOCLOSE_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_STRRESET:
+ KASSERT(inp != NULL && stcb != NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timostrmrst);
+ if (sctp_strreset_timer(inp, stcb)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_INPKILL:
+ KASSERT(inp != NULL && stcb == NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoinpkill);
+ /*
+ * special case, take away our increment since WE are the
+ * killer
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_3);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(SCTP_INP_SO(inp), 1);
+#endif
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_FROM_INPKILL_TIMER);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(SCTP_INP_SO(inp), 1);
+#endif
+ inp = NULL;
+ goto out_decr;
+ case SCTP_TIMER_TYPE_ASOCKILL:
+ KASSERT(inp != NULL && stcb != NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timoassockill);
+ /* Can we free it yet? */
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_1);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_2);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ /*
+ * free asoc, always unlocks (or destroy's) so prevent
+ * duplicate unlock or unlock of a free mtx :-0
+ */
+ stcb = NULL;
+ goto out_decr;
+ case SCTP_TIMER_TYPE_ADDR_WQ:
+ KASSERT(inp == NULL && stcb == NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ sctp_handle_addr_wq();
+ did_output = true;
+ break;
+ case SCTP_TIMER_TYPE_PRIM_DELETED:
+ KASSERT(inp != NULL && stcb != NULL && net == NULL,
+ ("timeout of type %d: inp = %p, stcb = %p, net = %p",
+ type, inp, stcb, net));
+ SCTP_STAT_INCR(sctps_timodelprim);
+ sctp_delete_prim_timer(inp, stcb);
+ did_output = false;
+ break;
+ default:
+#ifdef INVARIANTS
+ panic("Unknown timer type %d", type);
+#else
+ goto out;
+#endif
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xF1, (uint8_t) type);
+ if (inp != NULL)
+ sctp_auditing(5, inp, stcb, net);
+#endif
+ if (did_output && (stcb != NULL)) {
+ /*
+ * Now we need to clean up the control chunk chain if an
+ * ECNE is on it. It must be marked as UNSENT again so next
+ * call will continue to send it until such time that we get
+ * a CWR, to remove it. It is, however, less likely that we
+ * will find a ecn echo on the chain though.
+ */
+ sctp_fix_ecn_echo(&stcb->asoc);
+ }
+out:
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ } else if (inp != NULL) {
+ SCTP_INP_WUNLOCK(inp);
+ } else {
+ SCTP_WQ_ADDR_UNLOCK();
+ }
+
+out_decr:
+#if defined(__Userspace__)
+ if (upcall_socket != NULL) {
+ if ((upcall_socket->so_upcall != NULL) &&
+ (upcall_socket->so_error != 0)) {
+ (*upcall_socket->so_upcall)(upcall_socket, upcall_socket->so_upcallarg, M_NOWAIT);
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(upcall_socket);
+ sorele(upcall_socket);
+ }
+#endif
+ /* These reference counts were incremented in sctp_timer_start(). */
+ if (inp != NULL) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ if ((stcb != NULL) && !released_asoc_reference) {
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ if (net != NULL) {
+ sctp_free_remote_addr(net);
+ }
+ SCTPDBG(SCTP_DEBUG_TIMER2, "Timer type %d handler finished.\n", type);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+ NET_EPOCH_EXIT(et);
+#endif
+}
+
+/*-
+ * The following table shows which parameters must be provided
+ * when calling sctp_timer_start(). For parameters not being
+ * provided, NULL must be used.
+ *
+ * |Name |inp |stcb|net |
+ * |-----------------------------|----|----|----|
+ * |SCTP_TIMER_TYPE_SEND |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_INIT |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_RECV |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_SHUTDOWN |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_HEARTBEAT |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_COOKIE |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_NEWCOOKIE |Yes |No |No |
+ * |SCTP_TIMER_TYPE_PATHMTURAISE |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_SHUTDOWNACK |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_ASCONF |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_SHUTDOWNGUARD|Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_AUTOCLOSE |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_STRRESET |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_INPKILL |Yes |No |No |
+ * |SCTP_TIMER_TYPE_ASOCKILL |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_ADDR_WQ |No |No |No |
+ * |SCTP_TIMER_TYPE_PRIM_DELETED |Yes |Yes |No |
+ *
+ */
+
+void
+sctp_timer_start(int t_type, struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ struct sctp_timer *tmr;
+ uint32_t to_ticks;
+ uint32_t rndval, jitter;
+
+ KASSERT(stcb == NULL || stcb->sctp_ep == inp,
+ ("sctp_timer_start of type %d: inp = %p, stcb->sctp_ep %p",
+ t_type, stcb, stcb->sctp_ep));
+ tmr = NULL;
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ } else if (inp != NULL) {
+ SCTP_INP_WLOCK_ASSERT(inp);
+ } else {
+ SCTP_WQ_ADDR_LOCK_ASSERT();
+ }
+ if (stcb != NULL) {
+ /* Don't restart timer on association that's about to be killed. */
+ if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) &&
+ (t_type != SCTP_TIMER_TYPE_ASOCKILL)) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d not started: inp=%p, stcb=%p, net=%p (stcb deleted).\n",
+ t_type, inp, stcb, net);
+ return;
+ }
+ /* Don't restart timer on net that's been removed. */
+ if (net != NULL && (net->dest_state & SCTP_ADDR_BEING_DELETED)) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d not started: inp=%p, stcb=%p, net=%p (net deleted).\n",
+ t_type, inp, stcb, net);
+ return;
+ }
+ }
+ switch (t_type) {
+ case SCTP_TIMER_TYPE_SEND:
+ /* Here we use the RTO timer. */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ if (net->RTO == 0) {
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(net->RTO);
+ }
+ break;
+ case SCTP_TIMER_TYPE_INIT:
+ /*
+ * Here we use the INIT timer default usually about 1
+ * second.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ if (net->RTO == 0) {
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(net->RTO);
+ }
+ break;
+ case SCTP_TIMER_TYPE_RECV:
+ /*
+ * Here we use the Delayed-Ack timer value from the inp,
+ * usually about 200ms.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.dack_timer;
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.delayed_ack);
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWN:
+ /* Here we use the RTO of the destination. */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ if (net->RTO == 0) {
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(net->RTO);
+ }
+ break;
+ case SCTP_TIMER_TYPE_HEARTBEAT:
+ /*
+ * The net is used here so that we can add in the RTO. Even
+ * though we use a different timer. We also add the HB timer
+ * PLUS a random jitter.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ if ((net->dest_state & SCTP_ADDR_NOHB) &&
+ ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0)) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d not started: inp=%p, stcb=%p, net=%p.\n",
+ t_type, inp, stcb, net);
+ return;
+ }
+ tmr = &net->hb_timer;
+ if (net->RTO == 0) {
+ to_ticks = stcb->asoc.initial_rto;
+ } else {
+ to_ticks = net->RTO;
+ }
+ rndval = sctp_select_initial_TSN(&inp->sctp_ep);
+ jitter = rndval % to_ticks;
+ if (to_ticks > 1) {
+ to_ticks >>= 1;
+ }
+ if (jitter < (UINT32_MAX - to_ticks)) {
+ to_ticks += jitter;
+ } else {
+ to_ticks = UINT32_MAX;
+ }
+ if (!((net->dest_state & SCTP_ADDR_UNCONFIRMED) &&
+ (net->dest_state & SCTP_ADDR_REACHABLE)) &&
+ ((net->dest_state & SCTP_ADDR_PF) == 0)) {
+ if (net->heart_beat_delay < (UINT32_MAX - to_ticks)) {
+ to_ticks += net->heart_beat_delay;
+ } else {
+ to_ticks = UINT32_MAX;
+ }
+ }
+ /*
+ * Now we must convert the to_ticks that are now in
+ * ms to ticks.
+ */
+ to_ticks = sctp_msecs_to_ticks(to_ticks);
+ break;
+ case SCTP_TIMER_TYPE_COOKIE:
+ /*
+ * Here we can use the RTO timer from the network since one
+ * RTT was complete. If a retransmission happened then we will
+ * be using the RTO initial value.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ if (net->RTO == 0) {
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(net->RTO);
+ }
+ break;
+ case SCTP_TIMER_TYPE_NEWCOOKIE:
+ /*
+ * Nothing needed but the endpoint here usually about 60
+ * minutes.
+ */
+ if ((inp == NULL) || (stcb != NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &inp->sctp_ep.signature_change;
+ to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_SIGNATURE];
+ break;
+ case SCTP_TIMER_TYPE_PATHMTURAISE:
+ /*
+ * Here we use the value found in the EP for PMTUD, usually
+ * about 10 minutes.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ if (net->dest_state & SCTP_ADDR_NO_PMTUD) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d not started: inp=%p, stcb=%p, net=%p.\n",
+ t_type, inp, stcb, net);
+ return;
+ }
+ tmr = &net->pmtu_timer;
+ to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_PMTU];
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNACK:
+ /* Here we use the RTO of the destination. */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ if (net->RTO == 0) {
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(net->RTO);
+ }
+ break;
+ case SCTP_TIMER_TYPE_ASCONF:
+ /*
+ * Here the timer comes from the stcb but its value is from
+ * the net's RTO.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.asconf_timer;
+ if (net->RTO == 0) {
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(net->RTO);
+ }
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNGUARD:
+ /*
+ * Here we use the endpoints shutdown guard timer usually
+ * about 3 minutes.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.shut_guard_timer;
+ if (inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN] == 0) {
+ if (stcb->asoc.maxrto < UINT32_MAX / 5) {
+ to_ticks = sctp_msecs_to_ticks(5 * stcb->asoc.maxrto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(UINT32_MAX);
+ }
+ } else {
+ to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN];
+ }
+ break;
+ case SCTP_TIMER_TYPE_AUTOCLOSE:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.autoclose_timer;
+ to_ticks = stcb->asoc.sctp_autoclose_ticks;
+ break;
+ case SCTP_TIMER_TYPE_STRRESET:
+ /*
+ * Here the timer comes from the stcb but its value is from
+ * the net's RTO.
+ */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.strreset_timer;
+ if (net->RTO == 0) {
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = sctp_msecs_to_ticks(net->RTO);
+ }
+ break;
+ case SCTP_TIMER_TYPE_INPKILL:
+ /*
+ * The inp is setup to die. We re-use the signature_change
+ * timer since that has stopped and we are in the GONE
+ * state.
+ */
+ if ((inp == NULL) || (stcb != NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &inp->sctp_ep.signature_change;
+ to_ticks = sctp_msecs_to_ticks(SCTP_INP_KILL_TIMEOUT);
+ break;
+ case SCTP_TIMER_TYPE_ASOCKILL:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.strreset_timer;
+ to_ticks = sctp_msecs_to_ticks(SCTP_ASOC_KILL_TIMEOUT);
+ break;
+ case SCTP_TIMER_TYPE_ADDR_WQ:
+ if ((inp != NULL) || (stcb != NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ /* Only 1 tick away :-) */
+ tmr = &SCTP_BASE_INFO(addr_wq_timer);
+ to_ticks = SCTP_ADDRESS_TICK_DELAY;
+ break;
+ case SCTP_TIMER_TYPE_PRIM_DELETED:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_start of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.delete_prim_timer;
+ to_ticks = sctp_msecs_to_ticks(stcb->asoc.initial_rto);
+ break;
+ default:
+#ifdef INVARIANTS
+ panic("Unknown timer type %d", t_type);
+#else
+ return;
+#endif
+ }
+ KASSERT(tmr != NULL, ("tmr is NULL for timer type %d", t_type));
+ KASSERT(to_ticks > 0, ("to_ticks == 0 for timer type %d", t_type));
+ if (SCTP_OS_TIMER_PENDING(&tmr->timer)) {
+ /*
+ * We do NOT allow you to have it already running. If it is,
+ * we leave the current one up unchanged.
+ */
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d already running: inp=%p, stcb=%p, net=%p.\n",
+ t_type, inp, stcb, net);
+ return;
+ }
+ /* At this point we can proceed. */
+ if (t_type == SCTP_TIMER_TYPE_SEND) {
+ stcb->asoc.num_send_timers_up++;
+ }
+ tmr->stopped_from = 0;
+ tmr->type = t_type;
+ tmr->ep = (void *)inp;
+ tmr->tcb = (void *)stcb;
+ if (t_type == SCTP_TIMER_TYPE_STRRESET) {
+ tmr->net = NULL;
+ } else {
+ tmr->net = (void *)net;
+ }
+ tmr->self = (void *)tmr;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ tmr->vnet = (void *)curvnet;
+#endif
+ tmr->ticks = sctp_get_tick_count();
+ if (SCTP_OS_TIMER_START(&tmr->timer, to_ticks, sctp_timeout_handler, tmr) == 0) {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d started: ticks=%u, inp=%p, stcb=%p, net=%p.\n",
+ t_type, to_ticks, inp, stcb, net);
+ /*
+ * If this is a newly scheduled callout, as opposed to a
+ * rescheduled one, increment relevant reference counts.
+ */
+ if (tmr->ep != NULL) {
+ SCTP_INP_INCR_REF(inp);
+ }
+ if (tmr->tcb != NULL) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ }
+ if (tmr->net != NULL) {
+ atomic_add_int(&net->ref_count, 1);
+ }
+ } else {
+ /*
+ * This should not happen, since we checked for pending
+ * above.
+ */
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d restarted: ticks=%u, inp=%p, stcb=%p, net=%p.\n",
+ t_type, to_ticks, inp, stcb, net);
+ }
+ return;
+}
+
+/*-
+ * The following table shows which parameters must be provided
+ * when calling sctp_timer_stop(). For parameters not being
+ * provided, NULL must be used.
+ *
+ * |Name |inp |stcb|net |
+ * |-----------------------------|----|----|----|
+ * |SCTP_TIMER_TYPE_SEND |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_INIT |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_RECV |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_SHUTDOWN |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_HEARTBEAT |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_COOKIE |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_NEWCOOKIE |Yes |No |No |
+ * |SCTP_TIMER_TYPE_PATHMTURAISE |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_SHUTDOWNACK |Yes |Yes |Yes |
+ * |SCTP_TIMER_TYPE_ASCONF |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_SHUTDOWNGUARD|Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_AUTOCLOSE |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_STRRESET |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_INPKILL |Yes |No |No |
+ * |SCTP_TIMER_TYPE_ASOCKILL |Yes |Yes |No |
+ * |SCTP_TIMER_TYPE_ADDR_WQ |No |No |No |
+ * |SCTP_TIMER_TYPE_PRIM_DELETED |Yes |Yes |No |
+ *
+ */
+
+void
+sctp_timer_stop(int t_type, struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net, uint32_t from)
+{
+ struct sctp_timer *tmr;
+
+ KASSERT(stcb == NULL || stcb->sctp_ep == inp,
+ ("sctp_timer_stop of type %d: inp = %p, stcb->sctp_ep %p",
+ t_type, stcb, stcb->sctp_ep));
+ if (stcb != NULL) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ } else if (inp != NULL) {
+ SCTP_INP_WLOCK_ASSERT(inp);
+ } else {
+ SCTP_WQ_ADDR_LOCK_ASSERT();
+ }
+ tmr = NULL;
+ switch (t_type) {
+ case SCTP_TIMER_TYPE_SEND:
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_INIT:
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_RECV:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.dack_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWN:
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_HEARTBEAT:
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->hb_timer;
+ break;
+ case SCTP_TIMER_TYPE_COOKIE:
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_NEWCOOKIE:
+ if ((inp == NULL) || (stcb != NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &inp->sctp_ep.signature_change;
+ break;
+ case SCTP_TIMER_TYPE_PATHMTURAISE:
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->pmtu_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNACK:
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_ASCONF:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.asconf_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNGUARD:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.shut_guard_timer;
+ break;
+ case SCTP_TIMER_TYPE_AUTOCLOSE:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.autoclose_timer;
+ break;
+ case SCTP_TIMER_TYPE_STRRESET:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.strreset_timer;
+ break;
+ case SCTP_TIMER_TYPE_INPKILL:
+ /*
+ * The inp is setup to die. We re-use the signature_change
+ * timer since that has stopped and we are in the GONE
+ * state.
+ */
+ if ((inp == NULL) || (stcb != NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &inp->sctp_ep.signature_change;
+ break;
+ case SCTP_TIMER_TYPE_ASOCKILL:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.strreset_timer;
+ break;
+ case SCTP_TIMER_TYPE_ADDR_WQ:
+ if ((inp != NULL) || (stcb != NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &SCTP_BASE_INFO(addr_wq_timer);
+ break;
+ case SCTP_TIMER_TYPE_PRIM_DELETED:
+ if ((inp == NULL) || (stcb == NULL) || (net != NULL)) {
+#ifdef INVARIANTS
+ panic("sctp_timer_stop of type %d: inp = %p, stcb = %p, net = %p",
+ t_type, inp, stcb, net);
+#else
+ return;
+#endif
+ }
+ tmr = &stcb->asoc.delete_prim_timer;
+ break;
+ default:
+#ifdef INVARIANTS
+ panic("Unknown timer type %d", t_type);
+#else
+ return;
+#endif
+ }
+ KASSERT(tmr != NULL, ("tmr is NULL for timer type %d", t_type));
+ if ((tmr->type != SCTP_TIMER_TYPE_NONE) &&
+ (tmr->type != t_type)) {
+ /*
+ * Ok we have a timer that is under joint use. Cookie timer
+ * per chance with the SEND timer. We therefore are NOT
+ * running the timer that the caller wants stopped. So just
+ * return.
+ */
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Shared timer type %d not running: inp=%p, stcb=%p, net=%p.\n",
+ t_type, inp, stcb, net);
+ return;
+ }
+ if ((t_type == SCTP_TIMER_TYPE_SEND) && (stcb != NULL)) {
+ stcb->asoc.num_send_timers_up--;
+ if (stcb->asoc.num_send_timers_up < 0) {
+ stcb->asoc.num_send_timers_up = 0;
+ }
+ }
+ tmr->self = NULL;
+ tmr->stopped_from = from;
+ if (SCTP_OS_TIMER_STOP(&tmr->timer) == 1) {
+ KASSERT(tmr->ep == inp,
+ ("sctp_timer_stop of type %d: inp = %p, tmr->inp = %p",
+ t_type, inp, tmr->ep));
+ KASSERT(tmr->tcb == stcb,
+ ("sctp_timer_stop of type %d: stcb = %p, tmr->stcb = %p",
+ t_type, stcb, tmr->tcb));
+ KASSERT(((t_type == SCTP_TIMER_TYPE_ASCONF) && (tmr->net != NULL)) ||
+ ((t_type != SCTP_TIMER_TYPE_ASCONF) && (tmr->net == net)),
+ ("sctp_timer_stop of type %d: net = %p, tmr->net = %p",
+ t_type, net, tmr->net));
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d stopped: inp=%p, stcb=%p, net=%p.\n",
+ t_type, inp, stcb, net);
+ /*
+ * If the timer was actually stopped, decrement reference counts
+ * that were incremented in sctp_timer_start().
+ */
+ if (tmr->ep != NULL) {
+ tmr->ep = NULL;
+ SCTP_INP_DECR_REF(inp);
+ }
+ if (tmr->tcb != NULL) {
+ tmr->tcb = NULL;
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ if (tmr->net != NULL) {
+ struct sctp_nets *tmr_net;
+
+ /*
+ * Can't use net, since it doesn't work for
+ * SCTP_TIMER_TYPE_ASCONF.
+ */
+ tmr_net = tmr->net;
+ tmr->net = NULL;
+ sctp_free_remote_addr(tmr_net);
+ }
+ } else {
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d not stopped: inp=%p, stcb=%p, net=%p.\n",
+ t_type, inp, stcb, net);
+ }
+ return;
+}
+
+uint32_t
+sctp_calculate_len(struct mbuf *m)
+{
+ struct mbuf *at;
+ uint32_t tlen;
+
+ tlen = 0;
+ for (at = m; at != NULL; at = SCTP_BUF_NEXT(at)) {
+ tlen += SCTP_BUF_LEN(at);
+ }
+ return (tlen);
+}
+
+/*
+ * Given an association and starting time of the current RTT period, update
+ * RTO in number of msecs. net should point to the current network.
+ * Return 1, if an RTO update was performed, return 0 if no update was
+ * performed due to invalid starting point.
+ */
+
+int
+sctp_calculate_rto(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_nets *net,
+ struct timeval *old,
+ int rtt_from_sack)
+{
+ struct timeval now;
+ uint64_t rtt_us; /* RTT in us */
+ int32_t rtt; /* RTT in ms */
+ uint32_t new_rto;
+ int first_measure = 0;
+
+ /************************/
+ /* 1. calculate new RTT */
+ /************************/
+ /* get the current time */
+ if (stcb->asoc.use_precise_time) {
+ (void)SCTP_GETPTIME_TIMEVAL(&now);
+ } else {
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ }
+ if ((old->tv_sec > now.tv_sec) ||
+ ((old->tv_sec == now.tv_sec) && (old->tv_usec > now.tv_usec))) {
+ /* The starting point is in the future. */
+ return (0);
+ }
+ timevalsub(&now, old);
+ rtt_us = (uint64_t)1000000 * (uint64_t)now.tv_sec + (uint64_t)now.tv_usec;
+ if (rtt_us > SCTP_RTO_UPPER_BOUND * 1000) {
+ /* The RTT is larger than a sane value. */
+ return (0);
+ }
+ /* store the current RTT in us */
+ net->rtt = rtt_us;
+ /* compute rtt in ms */
+ rtt = (int32_t)(net->rtt / 1000);
+ if ((asoc->cc_functions.sctp_rtt_calculated) && (rtt_from_sack == SCTP_RTT_FROM_DATA)) {
+ /* Tell the CC module that a new update has just occurred from a sack */
+ (*asoc->cc_functions.sctp_rtt_calculated)(stcb, net, &now);
+ }
+ /* Do we need to determine the lan? We do this only
+ * on sacks i.e. RTT being determined from data not
+ * non-data (HB/INIT->INITACK).
+ */
+ if ((rtt_from_sack == SCTP_RTT_FROM_DATA) &&
+ (net->lan_type == SCTP_LAN_UNKNOWN)) {
+ if (net->rtt > SCTP_LOCAL_LAN_RTT) {
+ net->lan_type = SCTP_LAN_INTERNET;
+ } else {
+ net->lan_type = SCTP_LAN_LOCAL;
+ }
+ }
+
+ /***************************/
+ /* 2. update RTTVAR & SRTT */
+ /***************************/
+ /*-
+ * Compute the scaled average lastsa and the
+ * scaled variance lastsv as described in van Jacobson
+ * Paper "Congestion Avoidance and Control", Annex A.
+ *
+ * (net->lastsa >> SCTP_RTT_SHIFT) is the srtt
+ * (net->lastsv >> SCTP_RTT_VAR_SHIFT) is the rttvar
+ */
+ if (net->RTO_measured) {
+ rtt -= (net->lastsa >> SCTP_RTT_SHIFT);
+ net->lastsa += rtt;
+ if (rtt < 0) {
+ rtt = -rtt;
+ }
+ rtt -= (net->lastsv >> SCTP_RTT_VAR_SHIFT);
+ net->lastsv += rtt;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_RTTVAR_LOGGING_ENABLE) {
+ rto_logging(net, SCTP_LOG_RTTVAR);
+ }
+ } else {
+ /* First RTO measurement */
+ net->RTO_measured = 1;
+ first_measure = 1;
+ net->lastsa = rtt << SCTP_RTT_SHIFT;
+ net->lastsv = (rtt / 2) << SCTP_RTT_VAR_SHIFT;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_RTTVAR_LOGGING_ENABLE) {
+ rto_logging(net, SCTP_LOG_INITIAL_RTT);
+ }
+ }
+ if (net->lastsv == 0) {
+ net->lastsv = SCTP_CLOCK_GRANULARITY;
+ }
+ new_rto = (net->lastsa >> SCTP_RTT_SHIFT) + net->lastsv;
+ if ((new_rto > SCTP_SAT_NETWORK_MIN) &&
+ (stcb->asoc.sat_network_lockout == 0)) {
+ stcb->asoc.sat_network = 1;
+ } else if ((!first_measure) && stcb->asoc.sat_network) {
+ stcb->asoc.sat_network = 0;
+ stcb->asoc.sat_network_lockout = 1;
+ }
+ /* bound it, per C6/C7 in Section 5.3.1 */
+ if (new_rto < stcb->asoc.minrto) {
+ new_rto = stcb->asoc.minrto;
+ }
+ if (new_rto > stcb->asoc.maxrto) {
+ new_rto = stcb->asoc.maxrto;
+ }
+ net->RTO = new_rto;
+ return (1);
+}
+
+/*
+ * return a pointer to a contiguous piece of data from the given mbuf chain
+ * starting at 'off' for 'len' bytes. If the desired piece spans more than
+ * one mbuf, a copy is made at 'ptr'. caller must ensure that the buffer size
+ * is >= 'len' returns NULL if there there isn't 'len' bytes in the chain.
+ */
+caddr_t
+sctp_m_getptr(struct mbuf *m, int off, int len, uint8_t * in_ptr)
+{
+ uint32_t count;
+ uint8_t *ptr;
+
+ ptr = in_ptr;
+ if ((off < 0) || (len <= 0))
+ return (NULL);
+
+ /* find the desired start location */
+ while ((m != NULL) && (off > 0)) {
+ if (off < SCTP_BUF_LEN(m))
+ break;
+ off -= SCTP_BUF_LEN(m);
+ m = SCTP_BUF_NEXT(m);
+ }
+ if (m == NULL)
+ return (NULL);
+
+ /* is the current mbuf large enough (eg. contiguous)? */
+ if ((SCTP_BUF_LEN(m) - off) >= len) {
+ return (mtod(m, caddr_t) + off);
+ } else {
+ /* else, it spans more than one mbuf, so save a temp copy... */
+ while ((m != NULL) && (len > 0)) {
+ count = min(SCTP_BUF_LEN(m) - off, len);
+ memcpy(ptr, mtod(m, caddr_t) + off, count);
+ len -= count;
+ ptr += count;
+ off = 0;
+ m = SCTP_BUF_NEXT(m);
+ }
+ if ((m == NULL) && (len > 0))
+ return (NULL);
+ else
+ return ((caddr_t)in_ptr);
+ }
+}
+
+struct sctp_paramhdr *
+sctp_get_next_param(struct mbuf *m,
+ int offset,
+ struct sctp_paramhdr *pull,
+ int pull_limit)
+{
+ /* This just provides a typed signature to Peter's Pull routine */
+ return ((struct sctp_paramhdr *)sctp_m_getptr(m, offset, pull_limit,
+ (uint8_t *) pull));
+}
+
+struct mbuf *
+sctp_add_pad_tombuf(struct mbuf *m, int padlen)
+{
+ struct mbuf *m_last;
+ caddr_t dp;
+
+ if (padlen > 3) {
+ return (NULL);
+ }
+ if (padlen <= M_TRAILINGSPACE(m)) {
+ /*
+ * The easy way. We hope the majority of the time we hit
+ * here :)
+ */
+ m_last = m;
+ } else {
+ /* Hard way we must grow the mbuf chain */
+ m_last = sctp_get_mbuf_for_msg(padlen, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_last == NULL) {
+ return (NULL);
+ }
+ SCTP_BUF_LEN(m_last) = 0;
+ SCTP_BUF_NEXT(m_last) = NULL;
+ SCTP_BUF_NEXT(m) = m_last;
+ }
+ dp = mtod(m_last, caddr_t) + SCTP_BUF_LEN(m_last);
+ SCTP_BUF_LEN(m_last) += padlen;
+ memset(dp, 0, padlen);
+ return (m_last);
+}
+
+struct mbuf *
+sctp_pad_lastmbuf(struct mbuf *m, int padval, struct mbuf *last_mbuf)
+{
+ /* find the last mbuf in chain and pad it */
+ struct mbuf *m_at;
+
+ if (last_mbuf != NULL) {
+ return (sctp_add_pad_tombuf(last_mbuf, padval));
+ } else {
+ for (m_at = m; m_at; m_at = SCTP_BUF_NEXT(m_at)) {
+ if (SCTP_BUF_NEXT(m_at) == NULL) {
+ return (sctp_add_pad_tombuf(m_at, padval));
+ }
+ }
+ }
+ return (NULL);
+}
+
+static void
+sctp_notify_assoc_change(uint16_t state, struct sctp_tcb *stcb,
+ uint16_t error, struct sctp_abort_chunk *abort,
+ bool from_peer, bool timedout, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_assoc_change *sac;
+ struct sctp_queued_to_read *control;
+ struct sctp_inpcb *inp;
+ unsigned int notif_len;
+ unsigned int i;
+ uint16_t abort_len;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ KASSERT(abort == NULL || from_peer,
+ ("sctp_notify_assoc_change: ABORT chunk provided for local termination"));
+ KASSERT(!from_peer || !timedout,
+ ("sctp_notify_assoc_change: timeouts can only be local"));
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ inp = stcb->sctp_ep;
+ SCTP_INP_READ_LOCK_ASSERT(inp);
+
+ if (sctp_stcb_is_feature_on(inp, stcb, SCTP_PCB_FLAGS_RECVASSOCEVNT)) {
+ notif_len = (unsigned int)sizeof(struct sctp_assoc_change);
+ if (abort != NULL) {
+ abort_len = ntohs(abort->ch.chunk_length);
+ /*
+ * Only SCTP_CHUNK_BUFFER_SIZE are guaranteed to be
+ * contiguous.
+ */
+ if (abort_len > SCTP_CHUNK_BUFFER_SIZE) {
+ abort_len = SCTP_CHUNK_BUFFER_SIZE;
+ }
+ } else {
+ abort_len = 0;
+ }
+ if ((state == SCTP_COMM_UP) || (state == SCTP_RESTART)) {
+ notif_len += SCTP_ASSOC_SUPPORTS_MAX;
+ } else if ((state == SCTP_COMM_LOST) || (state == SCTP_CANT_STR_ASSOC)) {
+ notif_len += abort_len;
+ }
+ m_notify = sctp_get_mbuf_for_msg(notif_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL) {
+ /* Retry with smaller value. */
+ notif_len = (unsigned int)sizeof(struct sctp_assoc_change);
+ m_notify = sctp_get_mbuf_for_msg(notif_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL) {
+ goto set_error;
+ }
+ }
+ SCTP_BUF_NEXT(m_notify) = NULL;
+ sac = mtod(m_notify, struct sctp_assoc_change *);
+ memset(sac, 0, notif_len);
+ sac->sac_type = SCTP_ASSOC_CHANGE;
+ sac->sac_flags = 0;
+ sac->sac_length = sizeof(struct sctp_assoc_change);
+ sac->sac_state = state;
+ sac->sac_error = error;
+ if (state == SCTP_CANT_STR_ASSOC) {
+ sac->sac_outbound_streams = 0;
+ sac->sac_inbound_streams = 0;
+ } else {
+ sac->sac_outbound_streams = stcb->asoc.streamoutcnt;
+ sac->sac_inbound_streams = stcb->asoc.streamincnt;
+ }
+ sac->sac_assoc_id = sctp_get_associd(stcb);
+ if (notif_len > sizeof(struct sctp_assoc_change)) {
+ if ((state == SCTP_COMM_UP) || (state == SCTP_RESTART)) {
+ i = 0;
+ if (stcb->asoc.prsctp_supported == 1) {
+ sac->sac_info[i++] = SCTP_ASSOC_SUPPORTS_PR;
+ }
+ if (stcb->asoc.auth_supported == 1) {
+ sac->sac_info[i++] = SCTP_ASSOC_SUPPORTS_AUTH;
+ }
+ if (stcb->asoc.asconf_supported == 1) {
+ sac->sac_info[i++] = SCTP_ASSOC_SUPPORTS_ASCONF;
+ }
+ if (stcb->asoc.idata_supported == 1) {
+ sac->sac_info[i++] = SCTP_ASSOC_SUPPORTS_INTERLEAVING;
+ }
+ sac->sac_info[i++] = SCTP_ASSOC_SUPPORTS_MULTIBUF;
+ if (stcb->asoc.reconfig_supported == 1) {
+ sac->sac_info[i++] = SCTP_ASSOC_SUPPORTS_RE_CONFIG;
+ }
+ sac->sac_length += i;
+ } else if ((state == SCTP_COMM_LOST) || (state == SCTP_CANT_STR_ASSOC)) {
+ memcpy(sac->sac_info, abort, abort_len);
+ sac->sac_length += abort_len;
+ }
+ }
+ SCTP_BUF_LEN(m_notify) = sac->sac_length;
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control != NULL) {
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(inp, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+ } else {
+ sctp_m_freem(m_notify);
+ }
+ }
+ /*
+ * For 1-to-1 style sockets, we send up and error when an ABORT
+ * comes in.
+ */
+set_error:
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) &&
+ ((state == SCTP_COMM_LOST) || (state == SCTP_CANT_STR_ASSOC))) {
+ SOCK_LOCK(stcb->sctp_socket);
+ if (from_peer) {
+ if (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ECONNREFUSED);
+ stcb->sctp_socket->so_error = ECONNREFUSED;
+ } else {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ECONNRESET);
+ stcb->sctp_socket->so_error = ECONNRESET;
+ }
+ } else {
+ if (timedout) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ETIMEDOUT);
+ stcb->sctp_socket->so_error = ETIMEDOUT;
+ } else {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ECONNABORTED);
+ stcb->sctp_socket->so_error = ECONNABORTED;
+ }
+ }
+ SOCK_UNLOCK(stcb->sctp_socket);
+ }
+ /* Wake ANY sleepers */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+ if (!so_locked) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+ }
+#endif
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) &&
+ ((state == SCTP_COMM_LOST) || (state == SCTP_CANT_STR_ASSOC))) {
+ socantrcvmore(stcb->sctp_socket);
+ }
+ sorwakeup(stcb->sctp_socket);
+ sowwakeup(stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+}
+
+static void
+sctp_notify_peer_addr_change(struct sctp_tcb *stcb, uint32_t state,
+ struct sockaddr *sa, uint32_t error, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_paddr_change *spc;
+ struct sctp_queued_to_read *control;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVPADDREVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_paddr_change), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ return;
+ SCTP_BUF_LEN(m_notify) = 0;
+ spc = mtod(m_notify, struct sctp_paddr_change *);
+ memset(spc, 0, sizeof(struct sctp_paddr_change));
+ spc->spc_type = SCTP_PEER_ADDR_CHANGE;
+ spc->spc_flags = 0;
+ spc->spc_length = sizeof(struct sctp_paddr_change);
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef INET6
+ if (sctp_is_feature_on(stcb->sctp_ep, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4)) {
+ in6_sin_2_v4mapsin6((struct sockaddr_in *)sa,
+ (struct sockaddr_in6 *)&spc->spc_aaddr);
+ } else {
+ memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_in));
+ }
+#else
+ memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_in));
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ struct sockaddr_in6 *sin6;
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_in6));
+
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr;
+ if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) {
+ if (sin6->sin6_scope_id == 0) {
+ /* recover scope_id for user */
+#ifdef SCTP_KAME
+ (void)sa6_recoverscope(sin6);
+#else
+ (void)in6_recoverscope(sin6, &sin6->sin6_addr,
+ NULL);
+#endif
+ } else {
+ /* clear embedded scope_id for user */
+ in6_clearscope(&sin6->sin6_addr);
+ }
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_conn));
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ spc->spc_state = state;
+ spc->spc_error = error;
+ spc->spc_assoc_id = sctp_get_associd(stcb);
+
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_paddr_change);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_send_failed(struct sctp_tcb *stcb, uint8_t sent, uint32_t error,
+ struct sctp_tmit_chunk *chk, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_send_failed *ssf;
+ struct sctp_send_failed_event *ssfe;
+ struct sctp_queued_to_read *control;
+ struct sctp_chunkhdr *chkhdr;
+ int notifhdr_len, chk_len, chkhdr_len, padding_len, payload_len;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVSENDFAILEVNT) &&
+ sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVNSENDFAILEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVNSENDFAILEVNT)) {
+ notifhdr_len = sizeof(struct sctp_send_failed_event);
+ } else {
+ notifhdr_len = sizeof(struct sctp_send_failed);
+ }
+ m_notify = sctp_get_mbuf_for_msg(notifhdr_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ SCTP_BUF_LEN(m_notify) = notifhdr_len;
+ if (stcb->asoc.idata_supported) {
+ chkhdr_len = sizeof(struct sctp_idata_chunk);
+ } else {
+ chkhdr_len = sizeof(struct sctp_data_chunk);
+ }
+ /* Use some defaults in case we can't access the chunk header */
+ if (chk->send_size >= chkhdr_len) {
+ payload_len = chk->send_size - chkhdr_len;
+ } else {
+ payload_len = 0;
+ }
+ padding_len = 0;
+ if (chk->data != NULL) {
+ chkhdr = mtod(chk->data, struct sctp_chunkhdr *);
+ if (chkhdr != NULL) {
+ chk_len = ntohs(chkhdr->chunk_length);
+ if ((chk_len >= chkhdr_len) &&
+ (chk->send_size >= chk_len) &&
+ (chk->send_size - chk_len < 4)) {
+ padding_len = chk->send_size - chk_len;
+ payload_len = chk->send_size - chkhdr_len - padding_len;
+ }
+ }
+ }
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVNSENDFAILEVNT)) {
+ ssfe = mtod(m_notify, struct sctp_send_failed_event *);
+ memset(ssfe, 0, notifhdr_len);
+ ssfe->ssfe_type = SCTP_SEND_FAILED_EVENT;
+ if (sent) {
+ ssfe->ssfe_flags = SCTP_DATA_SENT;
+ } else {
+ ssfe->ssfe_flags = SCTP_DATA_UNSENT;
+ }
+ ssfe->ssfe_length = (uint32_t)(notifhdr_len + payload_len);
+ ssfe->ssfe_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ ssfe->ssfe_info.snd_sid = chk->rec.data.sid;
+ ssfe->ssfe_info.snd_flags = chk->rec.data.rcv_flags;
+ ssfe->ssfe_info.snd_ppid = chk->rec.data.ppid;
+ ssfe->ssfe_info.snd_context = chk->rec.data.context;
+ ssfe->ssfe_info.snd_assoc_id = sctp_get_associd(stcb);
+ ssfe->ssfe_assoc_id = sctp_get_associd(stcb);
+ } else {
+ ssf = mtod(m_notify, struct sctp_send_failed *);
+ memset(ssf, 0, notifhdr_len);
+ ssf->ssf_type = SCTP_SEND_FAILED;
+ if (sent) {
+ ssf->ssf_flags = SCTP_DATA_SENT;
+ } else {
+ ssf->ssf_flags = SCTP_DATA_UNSENT;
+ }
+ ssf->ssf_length = (uint32_t)(notifhdr_len + payload_len);
+ ssf->ssf_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ ssf->ssf_info.sinfo_stream = chk->rec.data.sid;
+ ssf->ssf_info.sinfo_ssn = (uint16_t)chk->rec.data.mid;
+ ssf->ssf_info.sinfo_flags = chk->rec.data.rcv_flags;
+ ssf->ssf_info.sinfo_ppid = chk->rec.data.ppid;
+ ssf->ssf_info.sinfo_context = chk->rec.data.context;
+ ssf->ssf_info.sinfo_assoc_id = sctp_get_associd(stcb);
+ ssf->ssf_assoc_id = sctp_get_associd(stcb);
+ }
+ if (chk->data != NULL) {
+ /* Trim off the sctp chunk header (it should be there) */
+ if (chk->send_size == chkhdr_len + payload_len + padding_len) {
+ m_adj(chk->data, chkhdr_len);
+ m_adj(chk->data, -padding_len);
+ sctp_mbuf_crush(chk->data);
+ chk->send_size -= (chkhdr_len + padding_len);
+ }
+ }
+ SCTP_BUF_NEXT(m_notify) = chk->data;
+ /* Steal off the mbuf */
+ chk->data = NULL;
+ /*
+ * For this case, we check the actual socket buffer, since the assoc
+ * is going away we don't want to overfill the socket buffer for a
+ * non-reader
+ */
+ if (sctp_sbspace_failedmsgs(&stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) {
+ sctp_m_freem(m_notify);
+ return;
+ }
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_send_failed2(struct sctp_tcb *stcb, uint32_t error,
+ struct sctp_stream_queue_pending *sp, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_send_failed *ssf;
+ struct sctp_send_failed_event *ssfe;
+ struct sctp_queued_to_read *control;
+ int notifhdr_len;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVSENDFAILEVNT) &&
+ sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVNSENDFAILEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVNSENDFAILEVNT)) {
+ notifhdr_len = sizeof(struct sctp_send_failed_event);
+ } else {
+ notifhdr_len = sizeof(struct sctp_send_failed);
+ }
+ m_notify = sctp_get_mbuf_for_msg(notifhdr_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL) {
+ /* no space left */
+ return;
+ }
+ SCTP_BUF_LEN(m_notify) = notifhdr_len;
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVNSENDFAILEVNT)) {
+ ssfe = mtod(m_notify, struct sctp_send_failed_event *);
+ memset(ssfe, 0, notifhdr_len);
+ ssfe->ssfe_type = SCTP_SEND_FAILED_EVENT;
+ ssfe->ssfe_flags = SCTP_DATA_UNSENT;
+ ssfe->ssfe_length = (uint32_t)(notifhdr_len + sp->length);
+ ssfe->ssfe_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ ssfe->ssfe_info.snd_sid = sp->sid;
+ if (sp->some_taken) {
+ ssfe->ssfe_info.snd_flags = SCTP_DATA_LAST_FRAG;
+ } else {
+ ssfe->ssfe_info.snd_flags = SCTP_DATA_NOT_FRAG;
+ }
+ ssfe->ssfe_info.snd_ppid = sp->ppid;
+ ssfe->ssfe_info.snd_context = sp->context;
+ ssfe->ssfe_info.snd_assoc_id = sctp_get_associd(stcb);
+ ssfe->ssfe_assoc_id = sctp_get_associd(stcb);
+ } else {
+ ssf = mtod(m_notify, struct sctp_send_failed *);
+ memset(ssf, 0, notifhdr_len);
+ ssf->ssf_type = SCTP_SEND_FAILED;
+ ssf->ssf_flags = SCTP_DATA_UNSENT;
+ ssf->ssf_length = (uint32_t)(notifhdr_len + sp->length);
+ ssf->ssf_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ ssf->ssf_info.sinfo_stream = sp->sid;
+ ssf->ssf_info.sinfo_ssn = 0;
+ if (sp->some_taken) {
+ ssf->ssf_info.sinfo_flags = SCTP_DATA_LAST_FRAG;
+ } else {
+ ssf->ssf_info.sinfo_flags = SCTP_DATA_NOT_FRAG;
+ }
+ ssf->ssf_info.sinfo_ppid = sp->ppid;
+ ssf->ssf_info.sinfo_context = sp->context;
+ ssf->ssf_info.sinfo_assoc_id = sctp_get_associd(stcb);
+ ssf->ssf_assoc_id = sctp_get_associd(stcb);
+ }
+ SCTP_BUF_NEXT(m_notify) = sp->data;
+
+ /* Steal off the mbuf */
+ sp->data = NULL;
+ /*
+ * For this case, we check the actual socket buffer, since the assoc
+ * is going away we don't want to overfill the socket buffer for a
+ * non-reader
+ */
+ if (sctp_sbspace_failedmsgs(&stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) {
+ sctp_m_freem(m_notify);
+ return;
+ }
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_adaptation_layer(struct sctp_tcb *stcb, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_adaptation_event *sai;
+ struct sctp_queued_to_read *control;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_ADAPTATIONEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_adaption_event), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ SCTP_BUF_LEN(m_notify) = 0;
+ sai = mtod(m_notify, struct sctp_adaptation_event *);
+ memset(sai, 0, sizeof(struct sctp_adaptation_event));
+ sai->sai_type = SCTP_ADAPTATION_INDICATION;
+ sai->sai_flags = 0;
+ sai->sai_length = sizeof(struct sctp_adaptation_event);
+ sai->sai_adaptation_ind = stcb->asoc.peers_adaptation;
+ sai->sai_assoc_id = sctp_get_associd(stcb);
+
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_adaptation_event);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_partial_delivery_indication(struct sctp_tcb *stcb, uint32_t error,
+ struct sctp_queued_to_read *aborted_control,
+ int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_pdapi_event *pdapi;
+ struct sctp_queued_to_read *control;
+ struct sockbuf *sb;
+
+ KASSERT(aborted_control != NULL, ("aborted_control is NULL"));
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_PDAPIEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_pdapi_event), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ SCTP_BUF_LEN(m_notify) = 0;
+ pdapi = mtod(m_notify, struct sctp_pdapi_event *);
+ memset(pdapi, 0, sizeof(struct sctp_pdapi_event));
+ pdapi->pdapi_type = SCTP_PARTIAL_DELIVERY_EVENT;
+ pdapi->pdapi_flags = 0;
+ pdapi->pdapi_length = sizeof(struct sctp_pdapi_event);
+ pdapi->pdapi_indication = error;
+ pdapi->pdapi_stream = aborted_control->sinfo_stream;
+ pdapi->pdapi_seq = (uint16_t)aborted_control->mid;
+ pdapi->pdapi_assoc_id = sctp_get_associd(stcb);
+
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_pdapi_event);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sb = &stcb->sctp_socket->so_rcv;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(sb, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBALLOC, SCTP_BUF_LEN(m_notify));
+ }
+ sctp_sballoc(stcb, sb, m_notify);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(sb, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBRESULT, 0);
+ }
+ control->end_added = 1;
+ TAILQ_INSERT_AFTER(&stcb->sctp_ep->read_queue, aborted_control, control, next);
+ if (stcb->sctp_ep && stcb->sctp_socket) {
+ /* This should always be the case */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ if (!so_locked) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+ }
+#endif
+ sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+ }
+}
+
+static void
+sctp_notify_shutdown_event(struct sctp_tcb *stcb, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_shutdown_event *sse;
+ struct sctp_queued_to_read *control;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ /*
+ * For TCP model AND UDP connected sockets we will send an error up
+ * when an SHUTDOWN completes
+ */
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ /* mark socket closed for read/write and wakeup! */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+#endif
+ socantsendmore(stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_event), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ sse = mtod(m_notify, struct sctp_shutdown_event *);
+ memset(sse, 0, sizeof(struct sctp_shutdown_event));
+ sse->sse_type = SCTP_SHUTDOWN_EVENT;
+ sse->sse_flags = 0;
+ sse->sse_length = sizeof(struct sctp_shutdown_event);
+ sse->sse_assoc_id = sctp_get_associd(stcb);
+
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_shutdown_event);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_sender_dry_event(struct sctp_tcb *stcb, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_sender_dry_event *event;
+ struct sctp_queued_to_read *control;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_DRYEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_sender_dry_event), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL) {
+ /* no space left */
+ return;
+ }
+ SCTP_BUF_LEN(m_notify) = 0;
+ event = mtod(m_notify, struct sctp_sender_dry_event *);
+ memset(event, 0, sizeof(struct sctp_sender_dry_event));
+ event->sender_dry_type = SCTP_SENDER_DRY_EVENT;
+ event->sender_dry_flags = 0;
+ event->sender_dry_length = sizeof(struct sctp_sender_dry_event);
+ event->sender_dry_assoc_id = sctp_get_associd(stcb);
+
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_sender_dry_event);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_stream_reset_add(struct sctp_tcb *stcb, int flag, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_queued_to_read *control;
+ struct sctp_stream_change_event *stradd;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_STREAM_CHANGEEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ if ((stcb->asoc.peer_req_out) && flag) {
+ /* Peer made the request, don't tell the local user */
+ stcb->asoc.peer_req_out = 0;
+ return;
+ }
+ stcb->asoc.peer_req_out = 0;
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_stream_change_event), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ SCTP_BUF_LEN(m_notify) = 0;
+ stradd = mtod(m_notify, struct sctp_stream_change_event *);
+ memset(stradd, 0, sizeof(struct sctp_stream_change_event));
+ stradd->strchange_type = SCTP_STREAM_CHANGE_EVENT;
+ stradd->strchange_flags = flag;
+ stradd->strchange_length = sizeof(struct sctp_stream_change_event);
+ stradd->strchange_assoc_id = sctp_get_associd(stcb);
+ stradd->strchange_instrms = stcb->asoc.streamincnt;
+ stradd->strchange_outstrms = stcb->asoc.streamoutcnt;
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_stream_change_event);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+ if (sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) {
+ /* no space */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_stream_reset_tsn(struct sctp_tcb *stcb, int flag, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_queued_to_read *control;
+ struct sctp_assoc_reset_event *strasoc;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_ASSOC_RESETEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_assoc_reset_event), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ SCTP_BUF_LEN(m_notify) = 0;
+ strasoc = mtod(m_notify, struct sctp_assoc_reset_event *);
+ memset(strasoc, 0, sizeof(struct sctp_assoc_reset_event));
+ strasoc->assocreset_type = SCTP_ASSOC_RESET_EVENT;
+ strasoc->assocreset_flags = flag;
+ strasoc->assocreset_length = sizeof(struct sctp_assoc_reset_event);
+ strasoc->assocreset_assoc_id= sctp_get_associd(stcb);
+ strasoc->assocreset_local_tsn = stcb->asoc.sending_seq;
+ strasoc->assocreset_remote_tsn = stcb->asoc.mapping_array_base_tsn + 1;
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_assoc_reset_event);
+ SCTP_BUF_NEXT(m_notify) = NULL;
+ if (sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) {
+ /* no space */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_stream_reset(struct sctp_tcb *stcb,
+ int number_entries, uint16_t *list, int flag, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_queued_to_read *control;
+ struct sctp_stream_reset_event *strreset;
+ int len;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_STREAM_RESETEVNT)) {
+ /* event not enabled */
+ return;
+ }
+
+ m_notify = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ SCTP_BUF_LEN(m_notify) = 0;
+ len = sizeof(struct sctp_stream_reset_event) + (number_entries * sizeof(uint16_t));
+ if (len > M_TRAILINGSPACE(m_notify)) {
+ /* never enough room */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ strreset = mtod(m_notify, struct sctp_stream_reset_event *);
+ memset(strreset, 0, len);
+ strreset->strreset_type = SCTP_STREAM_RESET_EVENT;
+ strreset->strreset_flags = flag;
+ strreset->strreset_length = len;
+ strreset->strreset_assoc_id = sctp_get_associd(stcb);
+ if (number_entries) {
+ int i;
+
+ for (i = 0; i < number_entries; i++) {
+ strreset->strreset_stream_list[i] = ntohs(list[i]);
+ }
+ }
+ SCTP_BUF_LEN(m_notify) = len;
+ SCTP_BUF_NEXT(m_notify) = NULL;
+ if (sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) {
+ /* no space */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ /* append to socket */
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control == NULL) {
+ /* no memory */
+ sctp_m_freem(m_notify);
+ return;
+ }
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+}
+
+static void
+sctp_notify_remote_error(struct sctp_tcb *stcb, uint16_t error,
+ struct sctp_error_chunk *chunk, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_remote_error *sre;
+ struct sctp_queued_to_read *control;
+ unsigned int notif_len;
+ uint16_t chunk_len;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ SCTP_INP_READ_LOCK_ASSERT(stcb->sctp_ep);
+
+ if (sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVPEERERR)) {
+ return;
+ }
+
+ if (chunk != NULL) {
+ chunk_len = ntohs(chunk->ch.chunk_length);
+ /*
+ * Only SCTP_CHUNK_BUFFER_SIZE are guaranteed to be
+ * contiguous.
+ */
+ if (chunk_len > SCTP_CHUNK_BUFFER_SIZE) {
+ chunk_len = SCTP_CHUNK_BUFFER_SIZE;
+ }
+ } else {
+ chunk_len = 0;
+ }
+ notif_len = (unsigned int)(sizeof(struct sctp_remote_error) + chunk_len);
+ m_notify = sctp_get_mbuf_for_msg(notif_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL) {
+ /* Retry with smaller value. */
+ notif_len = (unsigned int)sizeof(struct sctp_remote_error);
+ m_notify = sctp_get_mbuf_for_msg(notif_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL) {
+ return;
+ }
+ }
+ SCTP_BUF_NEXT(m_notify) = NULL;
+ sre = mtod(m_notify, struct sctp_remote_error *);
+ memset(sre, 0, notif_len);
+ sre->sre_type = SCTP_REMOTE_ERROR;
+ sre->sre_flags = 0;
+ sre->sre_length = sizeof(struct sctp_remote_error);
+ sre->sre_error = error;
+ sre->sre_assoc_id = sctp_get_associd(stcb);
+ if (notif_len > sizeof(struct sctp_remote_error)) {
+ memcpy(sre->sre_data, chunk, chunk_len);
+ sre->sre_length += chunk_len;
+ }
+ SCTP_BUF_LEN(m_notify) = sre->sre_length;
+ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
+ 0, 0, stcb->asoc.context, 0, 0, 0,
+ m_notify);
+ if (control != NULL) {
+ control->length = SCTP_BUF_LEN(m_notify);
+ control->spec_flags = M_NOTIFICATION;
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ sctp_add_to_readq(stcb->sctp_ep, stcb, control,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_HELD, so_locked);
+ } else {
+ sctp_m_freem(m_notify);
+ }
+}
+
+void
+sctp_ulp_notify(uint32_t notification, struct sctp_tcb *stcb,
+ uint32_t error, void *data, int so_locked)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_nets *net;
+
+ KASSERT(stcb != NULL, ("stcb == NULL"));
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ inp = stcb->sctp_ep;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ return;
+ }
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ if ((notification == SCTP_NOTIFY_INTERFACE_DOWN) ||
+ (notification == SCTP_NOTIFY_INTERFACE_UP) ||
+ (notification == SCTP_NOTIFY_INTERFACE_CONFIRMED)) {
+ /* Don't report these in front states */
+ return;
+ }
+ }
+ if (notification != SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION) {
+ SCTP_INP_READ_LOCK(inp);
+ }
+ SCTP_INP_READ_LOCK_ASSERT(inp);
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_CANT_READ)) {
+ SCTP_INP_READ_UNLOCK(inp);
+ return;
+ }
+
+ switch (notification) {
+ case SCTP_NOTIFY_ASSOC_UP:
+ if (stcb->asoc.assoc_up_sent == 0) {
+ sctp_notify_assoc_change(SCTP_COMM_UP, stcb, error, NULL, false, false, so_locked);
+ stcb->asoc.assoc_up_sent = 1;
+ }
+ if (stcb->asoc.adaptation_needed && (stcb->asoc.adaptation_sent == 0)) {
+ sctp_notify_adaptation_layer(stcb, so_locked);
+ }
+ if (stcb->asoc.auth_supported == 0) {
+ sctp_notify_authentication(stcb, SCTP_AUTH_NO_AUTH, 0, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_ASSOC_DOWN:
+ sctp_notify_assoc_change(SCTP_SHUTDOWN_COMP, stcb, error, NULL, false, false, so_locked);
+#if defined(__Userspace__)
+ if (inp->recv_callback) {
+ if (stcb->sctp_socket) {
+ union sctp_sockstore addr;
+ struct sctp_rcvinfo rcv;
+
+ memset(&addr, 0, sizeof(union sctp_sockstore));
+ memset(&rcv, 0, sizeof(struct sctp_rcvinfo));
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ inp->recv_callback(stcb->sctp_socket, addr, NULL, 0, rcv, 0, inp->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ }
+#endif
+ break;
+ case SCTP_NOTIFY_INTERFACE_DOWN:
+ net = (struct sctp_nets *)data;
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_UNREACHABLE,
+ &net->ro._l_addr.sa, error, so_locked);
+ break;
+ case SCTP_NOTIFY_INTERFACE_UP:
+ net = (struct sctp_nets *)data;
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_AVAILABLE,
+ &net->ro._l_addr.sa, error, so_locked);
+ break;
+ case SCTP_NOTIFY_INTERFACE_CONFIRMED:
+ net = (struct sctp_nets *)data;
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_CONFIRMED,
+ &net->ro._l_addr.sa, error, so_locked);
+ break;
+ case SCTP_NOTIFY_SPECIAL_SP_FAIL:
+ sctp_notify_send_failed2(stcb, error,
+ (struct sctp_stream_queue_pending *)data, so_locked);
+ break;
+ case SCTP_NOTIFY_SENT_DG_FAIL:
+ sctp_notify_send_failed(stcb, 1, error,
+ (struct sctp_tmit_chunk *)data, so_locked);
+ break;
+ case SCTP_NOTIFY_UNSENT_DG_FAIL:
+ sctp_notify_send_failed(stcb, 0, error,
+ (struct sctp_tmit_chunk *)data, so_locked);
+ break;
+ case SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION:
+ sctp_notify_partial_delivery_indication(stcb, error,
+ (struct sctp_queued_to_read *)data,
+ so_locked);
+ break;
+ case SCTP_NOTIFY_ASSOC_LOC_ABORTED:
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ sctp_notify_assoc_change(SCTP_CANT_STR_ASSOC, stcb, error, data, false, false, so_locked);
+ } else {
+ sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error, data, false, false, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_ASSOC_REM_ABORTED:
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ sctp_notify_assoc_change(SCTP_CANT_STR_ASSOC, stcb, error, data, true, false, so_locked);
+ } else {
+ sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error, data, true, false, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_ASSOC_TIMEDOUT:
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ sctp_notify_assoc_change(SCTP_CANT_STR_ASSOC, stcb, error, data, false, true, so_locked);
+ } else {
+ sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error, data, false, true, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_ASSOC_RESTART:
+ sctp_notify_assoc_change(SCTP_RESTART, stcb, error, NULL, false, false, so_locked);
+ if (stcb->asoc.auth_supported == 0) {
+ sctp_notify_authentication(stcb, SCTP_AUTH_NO_AUTH, 0, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_STR_RESET_SEND:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), SCTP_STREAM_RESET_OUTGOING_SSN, so_locked);
+ break;
+ case SCTP_NOTIFY_STR_RESET_RECV:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), SCTP_STREAM_RESET_INCOMING, so_locked);
+ break;
+ case SCTP_NOTIFY_STR_RESET_FAILED_OUT:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data),
+ (SCTP_STREAM_RESET_OUTGOING_SSN|SCTP_STREAM_RESET_FAILED), so_locked);
+ break;
+ case SCTP_NOTIFY_STR_RESET_DENIED_OUT:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data),
+ (SCTP_STREAM_RESET_OUTGOING_SSN|SCTP_STREAM_RESET_DENIED), so_locked);
+ break;
+ case SCTP_NOTIFY_STR_RESET_FAILED_IN:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data),
+ (SCTP_STREAM_RESET_INCOMING|SCTP_STREAM_RESET_FAILED), so_locked);
+ break;
+ case SCTP_NOTIFY_STR_RESET_DENIED_IN:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data),
+ (SCTP_STREAM_RESET_INCOMING|SCTP_STREAM_RESET_DENIED), so_locked);
+ break;
+ case SCTP_NOTIFY_STR_RESET_ADD:
+ sctp_notify_stream_reset_add(stcb, error, so_locked);
+ break;
+ case SCTP_NOTIFY_STR_RESET_TSN:
+ sctp_notify_stream_reset_tsn(stcb, error, so_locked);
+ break;
+ case SCTP_NOTIFY_ASCONF_ADD_IP:
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_ADDED, data,
+ error, so_locked);
+ break;
+ case SCTP_NOTIFY_ASCONF_DELETE_IP:
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_REMOVED, data,
+ error, so_locked);
+ break;
+ case SCTP_NOTIFY_ASCONF_SET_PRIMARY:
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_MADE_PRIM, data,
+ error, so_locked);
+ break;
+ case SCTP_NOTIFY_PEER_SHUTDOWN:
+ sctp_notify_shutdown_event(stcb, so_locked);
+ break;
+ case SCTP_NOTIFY_AUTH_NEW_KEY:
+ sctp_notify_authentication(stcb, SCTP_AUTH_NEW_KEY,
+ *(uint16_t *)data, so_locked);
+ break;
+ case SCTP_NOTIFY_AUTH_FREE_KEY:
+ sctp_notify_authentication(stcb, SCTP_AUTH_FREE_KEY,
+ *(uint16_t *)data, so_locked);
+ break;
+ case SCTP_NOTIFY_NO_PEER_AUTH:
+ sctp_notify_authentication(stcb, SCTP_AUTH_NO_AUTH,
+ 0, so_locked);
+ break;
+ case SCTP_NOTIFY_SENDER_DRY:
+ sctp_notify_sender_dry_event(stcb, so_locked);
+ break;
+ case SCTP_NOTIFY_REMOTE_ERROR:
+ sctp_notify_remote_error(stcb, error, data, so_locked);
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_UTIL1, "%s: unknown notification %xh (%u)\n",
+ __func__, notification, notification);
+ break;
+ }
+ if (notification != SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION) {
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+}
+
+void
+sctp_report_all_outbound(struct sctp_tcb *stcb, uint16_t error, int so_locked)
+{
+ struct sctp_association *asoc;
+ struct sctp_stream_out *outs;
+ struct sctp_tmit_chunk *chk, *nchk;
+ struct sctp_stream_queue_pending *sp, *nsp;
+ int i;
+
+ if (stcb == NULL) {
+ return;
+ }
+ asoc = &stcb->asoc;
+ if (asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ /* already being freed */
+ return;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ }
+#endif
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (asoc->state & SCTP_STATE_CLOSED_SOCKET)) {
+ return;
+ }
+ /* now through all the gunk freeing chunks */
+ /* sent queue SHOULD be empty */
+ TAILQ_FOREACH_SAFE(chk, &asoc->sent_queue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next);
+ asoc->sent_queue_cnt--;
+ if (chk->sent != SCTP_DATAGRAM_NR_ACKED) {
+ if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.sid);
+#endif
+ }
+ }
+ if (chk->data != NULL) {
+ sctp_free_bufspace(stcb, asoc, chk, 1);
+ sctp_ulp_notify(SCTP_NOTIFY_SENT_DG_FAIL, stcb,
+ error, chk, so_locked);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ }
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ /*sa_ignore FREED_MEMORY*/
+ }
+ /* pending send queue SHOULD be empty */
+ TAILQ_FOREACH_SAFE(chk, &asoc->send_queue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next);
+ asoc->send_queue_cnt--;
+ if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.sid].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.sid);
+#endif
+ }
+ if (chk->data != NULL) {
+ sctp_free_bufspace(stcb, asoc, chk, 1);
+ sctp_ulp_notify(SCTP_NOTIFY_UNSENT_DG_FAIL, stcb,
+ error, chk, so_locked);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ }
+ sctp_free_a_chunk(stcb, chk, so_locked);
+ /*sa_ignore FREED_MEMORY*/
+ }
+ for (i = 0; i < asoc->streamoutcnt; i++) {
+ /* For each stream */
+ outs = &asoc->strmout[i];
+ /* clean up any sends there */
+ TAILQ_FOREACH_SAFE(sp, &outs->outqueue, next, nsp) {
+ atomic_subtract_int(&asoc->stream_queue_cnt, 1);
+ TAILQ_REMOVE(&outs->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, outs, sp);
+ sctp_free_spbufspace(stcb, asoc, sp);
+ if (sp->data) {
+ sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL, stcb,
+ error, (void *)sp, so_locked);
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ sp->tail_mbuf = NULL;
+ sp->length = 0;
+ }
+ }
+ if (sp->net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ /* Free the chunk */
+ sctp_free_a_strmoq(stcb, sp, so_locked);
+ /*sa_ignore FREED_MEMORY*/
+ }
+ }
+}
+
+void
+sctp_abort_notification(struct sctp_tcb *stcb, bool from_peer, bool timeout,
+ uint16_t error, struct sctp_abort_chunk *abort,
+ int so_locked)
+{
+ if (stcb == NULL) {
+ return;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ }
+#endif
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED))) {
+ sctp_pcb_add_flags(stcb->sctp_ep, SCTP_PCB_FLAGS_WAS_ABORTED);
+ }
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) {
+ return;
+ }
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_WAS_ABORTED);
+ /* Tell them we lost the asoc */
+ sctp_report_all_outbound(stcb, error, so_locked);
+ if (from_peer) {
+ sctp_ulp_notify(SCTP_NOTIFY_ASSOC_REM_ABORTED, stcb, error, abort, so_locked);
+ } else {
+ if (timeout) {
+ sctp_ulp_notify(SCTP_NOTIFY_ASSOC_TIMEDOUT, stcb, error, abort, so_locked);
+ } else {
+ sctp_ulp_notify(SCTP_NOTIFY_ASSOC_LOC_ABORTED, stcb, error, abort, so_locked);
+ }
+ }
+}
+
+void
+sctp_abort_association(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct mbuf *m, int iphlen,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct mbuf *op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+ struct sctp_gen_error_cause* cause;
+ uint32_t vtag;
+ uint16_t cause_code;
+
+ if (stcb != NULL) {
+ vtag = stcb->asoc.peer_vtag;
+ vrf_id = stcb->asoc.vrf_id;
+ if (op_err != NULL) {
+ /* Read the cause code from the error cause. */
+ cause = mtod(op_err, struct sctp_gen_error_cause *);
+ cause_code = ntohs(cause->code);
+ } else {
+ cause_code = 0;
+ }
+ } else {
+ vtag = 0;
+ }
+ sctp_send_abort(m, iphlen, src, dst, sh, vtag, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, inp->fibnum,
+#endif
+ vrf_id, port);
+ if (stcb != NULL) {
+ /* We have a TCB to abort, send notification too */
+ sctp_abort_notification(stcb, false, false, cause_code, NULL, SCTP_SO_NOT_LOCKED);
+ /* Ok, now lets free it */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_4);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+}
+#ifdef SCTP_ASOCLOG_OF_TSNS
+void
+sctp_print_out_track_log(struct sctp_tcb *stcb)
+{
+#ifdef NOSIY_PRINTS
+ int i;
+ SCTP_PRINTF("Last ep reason:%x\n", stcb->sctp_ep->last_abort_code);
+ SCTP_PRINTF("IN bound TSN log-aaa\n");
+ if ((stcb->asoc.tsn_in_at == 0) && (stcb->asoc.tsn_in_wrapped == 0)) {
+ SCTP_PRINTF("None rcvd\n");
+ goto none_in;
+ }
+ if (stcb->asoc.tsn_in_wrapped) {
+ for (i = stcb->asoc.tsn_in_at; i < SCTP_TSN_LOG_SIZE; i++) {
+ SCTP_PRINTF("TSN:%x strm:%d seq:%d flags:%x sz:%d\n",
+ stcb->asoc.in_tsnlog[i].tsn,
+ stcb->asoc.in_tsnlog[i].strm,
+ stcb->asoc.in_tsnlog[i].seq,
+ stcb->asoc.in_tsnlog[i].flgs,
+ stcb->asoc.in_tsnlog[i].sz);
+ }
+ }
+ if (stcb->asoc.tsn_in_at) {
+ for (i = 0; i < stcb->asoc.tsn_in_at; i++) {
+ SCTP_PRINTF("TSN:%x strm:%d seq:%d flags:%x sz:%d\n",
+ stcb->asoc.in_tsnlog[i].tsn,
+ stcb->asoc.in_tsnlog[i].strm,
+ stcb->asoc.in_tsnlog[i].seq,
+ stcb->asoc.in_tsnlog[i].flgs,
+ stcb->asoc.in_tsnlog[i].sz);
+ }
+ }
+ none_in:
+ SCTP_PRINTF("OUT bound TSN log-aaa\n");
+ if ((stcb->asoc.tsn_out_at == 0) &&
+ (stcb->asoc.tsn_out_wrapped == 0)) {
+ SCTP_PRINTF("None sent\n");
+ }
+ if (stcb->asoc.tsn_out_wrapped) {
+ for (i = stcb->asoc.tsn_out_at; i < SCTP_TSN_LOG_SIZE; i++) {
+ SCTP_PRINTF("TSN:%x strm:%d seq:%d flags:%x sz:%d\n",
+ stcb->asoc.out_tsnlog[i].tsn,
+ stcb->asoc.out_tsnlog[i].strm,
+ stcb->asoc.out_tsnlog[i].seq,
+ stcb->asoc.out_tsnlog[i].flgs,
+ stcb->asoc.out_tsnlog[i].sz);
+ }
+ }
+ if (stcb->asoc.tsn_out_at) {
+ for (i = 0; i < stcb->asoc.tsn_out_at; i++) {
+ SCTP_PRINTF("TSN:%x strm:%d seq:%d flags:%x sz:%d\n",
+ stcb->asoc.out_tsnlog[i].tsn,
+ stcb->asoc.out_tsnlog[i].strm,
+ stcb->asoc.out_tsnlog[i].seq,
+ stcb->asoc.out_tsnlog[i].flgs,
+ stcb->asoc.out_tsnlog[i].sz);
+ }
+ }
+#endif
+}
+#endif
+
+void
+sctp_abort_an_association(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct mbuf *op_err, bool timedout, int so_locked)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+ struct sctp_gen_error_cause* cause;
+ uint16_t cause_code;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ so = SCTP_INP_SO(inp);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ if (stcb == NULL) {
+ /* Got to have a TCB */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ if (LIST_EMPTY(&inp->sctp_asoc_list)) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ SCTP_SOCKET_LOCK(so, 1);
+ }
+#endif
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_DIRECTLY_NOCMPSET);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+ }
+ }
+ return;
+ }
+ if (op_err != NULL) {
+ /* Read the cause code from the error cause. */
+ cause = mtod(op_err, struct sctp_gen_error_cause *);
+ cause_code = ntohs(cause->code);
+ } else {
+ cause_code = 0;
+ }
+ /* notify the peer */
+ sctp_send_abort_tcb(stcb, op_err, so_locked);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ /* notify the ulp */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+ sctp_abort_notification(stcb, false, timedout, cause_code, NULL, so_locked);
+ }
+ /* now free the asoc */
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ sctp_print_out_track_log(stcb);
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_5);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+}
+
+void
+sctp_handle_ootb(struct mbuf *m, int iphlen, int offset,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_inpcb *inp,
+ struct mbuf *cause,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_chunkhdr *ch, chunk_buf;
+ unsigned int chk_length;
+ int contains_init_chunk;
+
+ SCTP_STAT_INCR_COUNTER32(sctps_outoftheblue);
+ /* Generate a TO address for future reference */
+ if (inp && (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
+ if (LIST_EMPTY(&inp->sctp_asoc_list)) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(SCTP_INP_SO(inp), 1);
+#endif
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_DIRECTLY_NOCMPSET);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(SCTP_INP_SO(inp), 1);
+#endif
+ }
+ }
+ contains_init_chunk = 0;
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset,
+ sizeof(*ch), (uint8_t *) & chunk_buf);
+ while (ch != NULL) {
+ chk_length = ntohs(ch->chunk_length);
+ if (chk_length < sizeof(*ch)) {
+ /* break to abort land */
+ break;
+ }
+ switch (ch->chunk_type) {
+ case SCTP_INIT:
+ contains_init_chunk = 1;
+ break;
+ case SCTP_PACKET_DROPPED:
+ /* we don't respond to pkt-dropped */
+ return;
+ case SCTP_ABORT_ASSOCIATION:
+ /* we don't respond with an ABORT to an ABORT */
+ return;
+ case SCTP_SHUTDOWN_COMPLETE:
+ /*
+ * we ignore it since we are not waiting for it and
+ * peer is gone
+ */
+ return;
+ case SCTP_SHUTDOWN_ACK:
+ sctp_send_shutdown_complete2(src, dst, sh,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ return;
+ default:
+ break;
+ }
+ offset += SCTP_SIZE32(chk_length);
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset,
+ sizeof(*ch), (uint8_t *) & chunk_buf);
+ }
+ if ((SCTP_BASE_SYSCTL(sctp_blackhole) == 0) ||
+ ((SCTP_BASE_SYSCTL(sctp_blackhole) == 1) &&
+ (contains_init_chunk == 0))) {
+ sctp_send_abort(m, iphlen, src, dst, sh, 0, cause,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ }
+}
+
+/*
+ * check the inbound datagram to make sure there is not an abort inside it,
+ * if there is return 1, else return 0.
+ */
+int
+sctp_is_there_an_abort_here(struct mbuf *m, int iphlen, uint32_t *vtag)
+{
+ struct sctp_chunkhdr *ch;
+ struct sctp_init_chunk *init_chk, chunk_buf;
+ int offset;
+ unsigned int chk_length;
+
+ offset = iphlen + sizeof(struct sctphdr);
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, sizeof(*ch),
+ (uint8_t *) & chunk_buf);
+ while (ch != NULL) {
+ chk_length = ntohs(ch->chunk_length);
+ if (chk_length < sizeof(*ch)) {
+ /* packet is probably corrupt */
+ break;
+ }
+ /* we seem to be ok, is it an abort? */
+ if (ch->chunk_type == SCTP_ABORT_ASSOCIATION) {
+ /* yep, tell them */
+ return (1);
+ }
+ if ((ch->chunk_type == SCTP_INITIATION) ||
+ (ch->chunk_type == SCTP_INITIATION_ACK)) {
+ /* need to update the Vtag */
+ init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m,
+ offset, sizeof(struct sctp_init_chunk), (uint8_t *) & chunk_buf);
+ if (init_chk != NULL) {
+ *vtag = ntohl(init_chk->init.initiate_tag);
+ }
+ }
+ /* Nope, move to the next chunk */
+ offset += SCTP_SIZE32(chk_length);
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset,
+ sizeof(*ch), (uint8_t *) & chunk_buf);
+ }
+ return (0);
+}
+
+/*
+ * currently (2/02), ifa_addr embeds scope_id's and don't have sin6_scope_id
+ * set (i.e. it's 0) so, create this function to compare link local scopes
+ */
+#ifdef INET6
+uint32_t
+sctp_is_same_scope(struct sockaddr_in6 *addr1, struct sockaddr_in6 *addr2)
+{
+#if defined(__Userspace__)
+ /*__Userspace__ Returning 1 here always */
+#endif
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+ struct sockaddr_in6 a, b;
+
+ /* save copies */
+ a = *addr1;
+ b = *addr2;
+
+ if (a.sin6_scope_id == 0)
+#ifdef SCTP_KAME
+ if (sa6_recoverscope(&a)) {
+#else
+ if (in6_recoverscope(&a, &a.sin6_addr, NULL)) {
+#endif /* SCTP_KAME */
+ /* can't get scope, so can't match */
+ return (0);
+ }
+ if (b.sin6_scope_id == 0)
+#ifdef SCTP_KAME
+ if (sa6_recoverscope(&b)) {
+#else
+ if (in6_recoverscope(&b, &b.sin6_addr, NULL)) {
+#endif /* SCTP_KAME */
+ /* can't get scope, so can't match */
+ return (0);
+ }
+ if (a.sin6_scope_id != b.sin6_scope_id)
+ return (0);
+#else
+ if (addr1->sin6_scope_id != addr2->sin6_scope_id)
+ return (0);
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+
+ return (1);
+}
+
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+/*
+ * returns a sockaddr_in6 with embedded scope recovered and removed
+ */
+struct sockaddr_in6 *
+sctp_recover_scope(struct sockaddr_in6 *addr, struct sockaddr_in6 *store)
+{
+ /* check and strip embedded scope junk */
+ if (addr->sin6_family == AF_INET6) {
+ if (IN6_IS_SCOPE_LINKLOCAL(&addr->sin6_addr)) {
+ if (addr->sin6_scope_id == 0) {
+ *store = *addr;
+#ifdef SCTP_KAME
+ if (!sa6_recoverscope(store)) {
+#else
+ if (!in6_recoverscope(store, &store->sin6_addr,
+ NULL)) {
+#endif /* SCTP_KAME */
+ /* use the recovered scope */
+ addr = store;
+ }
+ } else {
+ /* else, return the original "to" addr */
+ in6_clearscope(&addr->sin6_addr);
+ }
+ }
+ }
+ return (addr);
+}
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#endif
+
+/*
+ * are the two addresses the same? currently a "scopeless" check returns: 1
+ * if same, 0 if not
+ */
+int
+sctp_cmpaddr(struct sockaddr *sa1, struct sockaddr *sa2)
+{
+
+ /* must be valid */
+ if (sa1 == NULL || sa2 == NULL)
+ return (0);
+
+ /* must be the same family */
+ if (sa1->sa_family != sa2->sa_family)
+ return (0);
+
+ switch (sa1->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* IPv6 addresses */
+ struct sockaddr_in6 *sin6_1, *sin6_2;
+
+ sin6_1 = (struct sockaddr_in6 *)sa1;
+ sin6_2 = (struct sockaddr_in6 *)sa2;
+ return (SCTP6_ARE_ADDR_EQUAL(sin6_1,
+ sin6_2));
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ /* IPv4 addresses */
+ struct sockaddr_in *sin_1, *sin_2;
+
+ sin_1 = (struct sockaddr_in *)sa1;
+ sin_2 = (struct sockaddr_in *)sa2;
+ return (sin_1->sin_addr.s_addr == sin_2->sin_addr.s_addr);
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn_1, *sconn_2;
+
+ sconn_1 = (struct sockaddr_conn *)sa1;
+ sconn_2 = (struct sockaddr_conn *)sa2;
+ return (sconn_1->sconn_addr == sconn_2->sconn_addr);
+ }
+#endif
+ default:
+ /* we don't do these... */
+ return (0);
+ }
+}
+
+void
+sctp_print_address(struct sockaddr *sa)
+{
+#ifdef INET6
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ char ip6buf[INET6_ADDRSTRLEN];
+#endif
+#endif
+
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)sa;
+#if defined(__Userspace__)
+ SCTP_PRINTF("IPv6 address: %x:%x:%x:%x:%x:%x:%x:%x:port:%d scope:%u\n",
+ ntohs(sin6->sin6_addr.s6_addr16[0]),
+ ntohs(sin6->sin6_addr.s6_addr16[1]),
+ ntohs(sin6->sin6_addr.s6_addr16[2]),
+ ntohs(sin6->sin6_addr.s6_addr16[3]),
+ ntohs(sin6->sin6_addr.s6_addr16[4]),
+ ntohs(sin6->sin6_addr.s6_addr16[5]),
+ ntohs(sin6->sin6_addr.s6_addr16[6]),
+ ntohs(sin6->sin6_addr.s6_addr16[7]),
+ ntohs(sin6->sin6_port),
+ sin6->sin6_scope_id);
+#else
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PRINTF("IPv6 address: %s:port:%d scope:%u\n",
+ ip6_sprintf(ip6buf, &sin6->sin6_addr),
+ ntohs(sin6->sin6_port),
+ sin6->sin6_scope_id);
+#else
+ SCTP_PRINTF("IPv6 address: %s:port:%d scope:%u\n",
+ ip6_sprintf(&sin6->sin6_addr),
+ ntohs(sin6->sin6_port),
+ sin6->sin6_scope_id);
+#endif
+#endif
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+ unsigned char *p;
+
+ sin = (struct sockaddr_in *)sa;
+ p = (unsigned char *)&sin->sin_addr;
+ SCTP_PRINTF("IPv4 address: %u.%u.%u.%u:%d\n",
+ p[0], p[1], p[2], p[3], ntohs(sin->sin_port));
+ break;
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn;
+
+ sconn = (struct sockaddr_conn *)sa;
+ SCTP_PRINTF("AF_CONN address: %p\n", sconn->sconn_addr);
+ break;
+ }
+#endif
+ default:
+ SCTP_PRINTF("?\n");
+ break;
+ }
+}
+
+void
+sctp_pull_off_control_to_new_inp(struct sctp_inpcb *old_inp,
+ struct sctp_inpcb *new_inp,
+ struct sctp_tcb *stcb,
+ int waitflags)
+{
+ /*
+ * go through our old INP and pull off any control structures that
+ * belong to stcb and move then to the new inp.
+ */
+ struct socket *old_so, *new_so;
+ struct sctp_queued_to_read *control, *nctl;
+ struct sctp_readhead tmp_queue;
+ struct mbuf *m;
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ int error = 0;
+#endif
+
+ old_so = old_inp->sctp_socket;
+ new_so = new_inp->sctp_socket;
+ TAILQ_INIT(&tmp_queue);
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+#if defined(__FreeBSD__)
+ error = SOCK_IO_RECV_LOCK(old_so, waitflags);
+#else
+ error = sblock(&old_so->so_rcv, waitflags);
+#endif
+ if (error) {
+ /* Gak, can't get I/O lock, we have a problem.
+ * data will be left stranded.. and we
+ * don't dare look at it since the
+ * other thread may be reading something.
+ * Oh well, its a screwed up app that does
+ * a peeloff OR a accept while reading
+ * from the main socket... actually its
+ * only the peeloff() case, since I think
+ * read will fail on a listening socket..
+ */
+ return;
+ }
+#endif
+ /* lock the socket buffers */
+ SCTP_INP_READ_LOCK(old_inp);
+ TAILQ_FOREACH_SAFE(control, &old_inp->read_queue, next, nctl) {
+ /* Pull off all for out target stcb */
+ if (control->stcb == stcb) {
+ /* remove it we want it */
+ TAILQ_REMOVE(&old_inp->read_queue, control, next);
+ TAILQ_INSERT_TAIL(&tmp_queue, control, next);
+ m = control->data;
+ while (m) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&old_so->so_rcv, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBFREE,SCTP_BUF_LEN(m));
+ }
+ sctp_sbfree(control, stcb, &old_so->so_rcv, m);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&old_so->so_rcv, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBRESULT, 0);
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ }
+ }
+ SCTP_INP_READ_UNLOCK(old_inp);
+ /* Remove the recv-lock on the old socket */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&old_so->so_rcv, 1);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SOCK_IO_RECV_UNLOCK(old_so);
+#endif
+ /* Now we move them over to the new socket buffer */
+ SCTP_INP_READ_LOCK(new_inp);
+ TAILQ_FOREACH_SAFE(control, &tmp_queue, next, nctl) {
+ TAILQ_INSERT_TAIL(&new_inp->read_queue, control, next);
+ m = control->data;
+ while (m) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&new_so->so_rcv, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBALLOC, SCTP_BUF_LEN(m));
+ }
+ sctp_sballoc(stcb, &new_so->so_rcv, m);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&new_so->so_rcv, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBRESULT, 0);
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ }
+ SCTP_INP_READ_UNLOCK(new_inp);
+}
+
+void
+sctp_wakeup_the_read_socket(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ int so_locked
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+ SCTP_UNUSED
+#endif
+)
+{
+ if ((inp != NULL) &&
+ (inp->sctp_socket != NULL) &&
+ (((inp->sctp_flags & (SCTP_PCB_FLAGS_TCPTYPE | SCTP_PCB_FLAGS_IN_TCPPOOL)) == 0) ||
+ !SCTP_IS_LISTENING(inp))) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(inp);
+ if (!so_locked) {
+ if (stcb) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_SOCKET_LOCK(so, 1);
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+ }
+#endif
+ sctp_sorwakeup(inp, inp->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+ }
+}
+#if defined(__Userspace__)
+
+void
+sctp_invoke_recv_callback(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_queued_to_read *control,
+ int inp_read_lock_held)
+{
+ uint32_t pd_point, length;
+
+ if ((inp->recv_callback == NULL) ||
+ (stcb == NULL) ||
+ (stcb->sctp_socket == NULL)) {
+ return;
+ }
+
+ length = control->length;
+ if (stcb != NULL && stcb->sctp_socket != NULL) {
+ pd_point = min(SCTP_SB_LIMIT_RCV(stcb->sctp_socket) >> SCTP_PARTIAL_DELIVERY_SHIFT,
+ stcb->sctp_ep->partial_delivery_point);
+ } else {
+ pd_point = inp->partial_delivery_point;
+ }
+ if ((control->end_added == 1) || (length >= pd_point)) {
+ struct socket *so;
+ struct mbuf *m;
+ char *buffer;
+ struct sctp_rcvinfo rcv;
+ union sctp_sockstore addr;
+ int flags;
+
+ if ((buffer = malloc(length)) == NULL) {
+ return;
+ }
+ if (inp_read_lock_held == 0) {
+ SCTP_INP_READ_LOCK(inp);
+ }
+ so = stcb->sctp_socket;
+ for (m = control->data; m; m = SCTP_BUF_NEXT(m)) {
+ sctp_sbfree(control, control->stcb, &so->so_rcv, m);
+ }
+ m_copydata(control->data, 0, length, buffer);
+ memset(&rcv, 0, sizeof(struct sctp_rcvinfo));
+ rcv.rcv_sid = control->sinfo_stream;
+ rcv.rcv_ssn = (uint16_t)control->mid;
+ rcv.rcv_flags = control->sinfo_flags;
+ rcv.rcv_ppid = control->sinfo_ppid;
+ rcv.rcv_tsn = control->sinfo_tsn;
+ rcv.rcv_cumtsn = control->sinfo_cumtsn;
+ rcv.rcv_context = control->sinfo_context;
+ rcv.rcv_assoc_id = control->sinfo_assoc_id;
+ memset(&addr, 0, sizeof(union sctp_sockstore));
+ switch (control->whoFrom->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ addr.sin = control->whoFrom->ro._l_addr.sin;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ addr.sin6 = control->whoFrom->ro._l_addr.sin6;
+ break;
+#endif
+ case AF_CONN:
+ addr.sconn = control->whoFrom->ro._l_addr.sconn;
+ break;
+ default:
+ addr.sa = control->whoFrom->ro._l_addr.sa;
+ break;
+ }
+ flags = 0;
+ if (control->end_added == 1) {
+ flags |= MSG_EOR;
+ }
+ if (control->spec_flags & M_NOTIFICATION) {
+ flags |= MSG_NOTIFICATION;
+ }
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ control->tail_mbuf = NULL;
+ control->length = 0;
+ if (control->end_added) {
+ TAILQ_REMOVE(&stcb->sctp_ep->read_queue, control, next);
+ control->on_read_q = 0;
+ sctp_free_remote_addr(control->whoFrom);
+ control->whoFrom = NULL;
+ sctp_free_a_readq(stcb, control);
+ }
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ if (inp_read_lock_held == 0) {
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+ inp->recv_callback(so, addr, buffer, length, rcv, flags, inp->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+}
+#endif
+
+void
+sctp_add_to_readq(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_queued_to_read *control,
+ struct sockbuf *sb,
+ int end,
+ int inp_read_lock_held,
+ int so_locked)
+{
+ /*
+ * Here we must place the control on the end of the socket read
+ * queue AND increment sb_cc so that select will work properly on
+ * read.
+ */
+ struct mbuf *m, *prev = NULL;
+
+ if (inp == NULL) {
+ /* Gak, TSNH!! */
+#ifdef INVARIANTS
+ panic("Gak, inp NULL on add_to_readq");
+#endif
+ return;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ if (inp_read_lock_held == SCTP_READ_LOCK_NOT_HELD) {
+ SCTP_INP_READ_LOCK(inp);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_CANT_READ) {
+ if (!control->on_strm_q) {
+ sctp_free_remote_addr(control->whoFrom);
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+ }
+ if (inp_read_lock_held == SCTP_READ_LOCK_NOT_HELD) {
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+ return;
+ }
+ if ((control->spec_flags & M_NOTIFICATION) == 0) {
+ atomic_add_int(&inp->total_recvs, 1);
+ if (!control->do_not_ref_stcb) {
+ atomic_add_int(&stcb->total_recvs, 1);
+ }
+ }
+ m = control->data;
+ control->held_length = 0;
+ control->length = 0;
+ while (m != NULL) {
+ if (SCTP_BUF_LEN(m) == 0) {
+ /* Skip mbufs with NO length */
+ if (prev == NULL) {
+ /* First one */
+ control->data = sctp_m_free(m);
+ m = control->data;
+ } else {
+ SCTP_BUF_NEXT(prev) = sctp_m_free(m);
+ m = SCTP_BUF_NEXT(prev);
+ }
+ if (m == NULL) {
+ control->tail_mbuf = prev;
+ }
+ continue;
+ }
+ prev = m;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(sb, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBALLOC, SCTP_BUF_LEN(m));
+ }
+ sctp_sballoc(stcb, sb, m);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(sb, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBRESULT, 0);
+ }
+ atomic_add_int(&control->length, SCTP_BUF_LEN(m));
+ m = SCTP_BUF_NEXT(m);
+ }
+ if (prev != NULL) {
+ control->tail_mbuf = prev;
+ } else {
+ /* Everything got collapsed out?? */
+ if (!control->on_strm_q) {
+ sctp_free_remote_addr(control->whoFrom);
+ sctp_free_a_readq(stcb, control);
+ }
+ if (inp_read_lock_held == 0)
+ SCTP_INP_READ_UNLOCK(inp);
+ return;
+ }
+ if (end) {
+ control->end_added = 1;
+ }
+ TAILQ_INSERT_TAIL(&inp->read_queue, control, next);
+ control->on_read_q = 1;
+#if defined(__Userspace__)
+ sctp_invoke_recv_callback(inp, stcb, control, SCTP_READ_LOCK_HELD);
+#endif
+ if ((inp != NULL) && (inp->sctp_socket != NULL)) {
+ sctp_wakeup_the_read_socket(inp, stcb, so_locked);
+ }
+ if (inp_read_lock_held == SCTP_READ_LOCK_NOT_HELD) {
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+}
+
+/*************HOLD THIS COMMENT FOR PATCH FILE OF
+ *************ALTERNATE ROUTING CODE
+ */
+
+/*************HOLD THIS COMMENT FOR END OF PATCH FILE OF
+ *************ALTERNATE ROUTING CODE
+ */
+
+struct mbuf *
+sctp_generate_cause(uint16_t code, char *info)
+{
+ struct mbuf *m;
+ struct sctp_gen_error_cause *cause;
+ size_t info_len;
+ uint16_t len;
+
+ if ((code == 0) || (info == NULL)) {
+ return (NULL);
+ }
+ info_len = strlen(info);
+ if (info_len > (SCTP_MAX_CAUSE_LENGTH - sizeof(struct sctp_paramhdr))) {
+ return (NULL);
+ }
+ len = (uint16_t)(sizeof(struct sctp_paramhdr) + info_len);
+ m = sctp_get_mbuf_for_msg(len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m != NULL) {
+ SCTP_BUF_LEN(m) = len;
+ cause = mtod(m, struct sctp_gen_error_cause *);
+ cause->code = htons(code);
+ cause->length = htons(len);
+ memcpy(cause->info, info, info_len);
+ }
+ return (m);
+}
+
+struct mbuf *
+sctp_generate_no_user_data_cause(uint32_t tsn)
+{
+ struct mbuf *m;
+ struct sctp_error_no_user_data *no_user_data_cause;
+ uint16_t len;
+
+ len = (uint16_t)sizeof(struct sctp_error_no_user_data);
+ m = sctp_get_mbuf_for_msg(len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m != NULL) {
+ SCTP_BUF_LEN(m) = len;
+ no_user_data_cause = mtod(m, struct sctp_error_no_user_data *);
+ no_user_data_cause->cause.code = htons(SCTP_CAUSE_NO_USER_DATA);
+ no_user_data_cause->cause.length = htons(len);
+ no_user_data_cause->tsn = htonl(tsn);
+ }
+ return (m);
+}
+
+void
+sctp_free_bufspace(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_tmit_chunk *tp1, int chk_cnt)
+{
+ if (tp1->data == NULL) {
+ return;
+ }
+ atomic_subtract_int(&asoc->chunks_on_out_queue, chk_cnt);
+#ifdef SCTP_MBCNT_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBCNT_LOGGING_ENABLE) {
+ sctp_log_mbcnt(SCTP_LOG_MBCNT_DECREASE,
+ asoc->total_output_queue_size,
+ tp1->book_size,
+ 0,
+ tp1->mbcnt);
+ }
+#endif
+ if (asoc->total_output_queue_size >= tp1->book_size) {
+ atomic_subtract_int(&asoc->total_output_queue_size, tp1->book_size);
+ } else {
+ asoc->total_output_queue_size = 0;
+ }
+ if ((stcb->sctp_socket != NULL) &&
+ (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) ||
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)))) {
+ SCTP_SB_DECR(&stcb->sctp_socket->so_snd, tp1->book_size);
+ }
+}
+
+int
+sctp_release_pr_sctp_chunk(struct sctp_tcb *stcb, struct sctp_tmit_chunk *tp1,
+ uint8_t sent, int so_locked)
+{
+ struct sctp_stream_out *strq;
+ struct sctp_tmit_chunk *chk = NULL, *tp2;
+ struct sctp_stream_queue_pending *sp;
+ uint32_t mid;
+ uint16_t sid;
+ uint8_t foundeom = 0;
+ int ret_sz = 0;
+ int notdone;
+ int do_wakeup_routine = 0;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ }
+#endif
+ SCTP_TCB_LOCK_ASSERT(stcb);
+
+ sid = tp1->rec.data.sid;
+ mid = tp1->rec.data.mid;
+ if (sent || ((tp1->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0)) {
+ stcb->asoc.abandoned_sent[0]++;
+ stcb->asoc.abandoned_sent[PR_SCTP_POLICY(tp1->flags)]++;
+ stcb->asoc.strmout[sid].abandoned_sent[0]++;
+#if defined(SCTP_DETAILED_STR_STATS)
+ stcb->asoc.strmout[sid].abandoned_sent[PR_SCTP_POLICY(tp1->flags)]++;
+#endif
+ } else {
+ stcb->asoc.abandoned_unsent[0]++;
+ stcb->asoc.abandoned_unsent[PR_SCTP_POLICY(tp1->flags)]++;
+ stcb->asoc.strmout[sid].abandoned_unsent[0]++;
+#if defined(SCTP_DETAILED_STR_STATS)
+ stcb->asoc.strmout[sid].abandoned_unsent[PR_SCTP_POLICY(tp1->flags)]++;
+#endif
+ }
+ do {
+ ret_sz += tp1->book_size;
+ if (tp1->data != NULL) {
+ if (tp1->sent < SCTP_DATAGRAM_RESEND) {
+ sctp_flight_size_decrease(tp1);
+ sctp_total_flight_decrease(stcb, tp1);
+ }
+ sctp_free_bufspace(stcb, &stcb->asoc, tp1, 1);
+ stcb->asoc.peers_rwnd += tp1->send_size;
+ stcb->asoc.peers_rwnd += SCTP_BASE_SYSCTL(sctp_peer_chunk_oh);
+ if (sent) {
+ sctp_ulp_notify(SCTP_NOTIFY_SENT_DG_FAIL, stcb, 0, tp1, so_locked);
+ } else {
+ sctp_ulp_notify(SCTP_NOTIFY_UNSENT_DG_FAIL, stcb, 0, tp1, so_locked);
+ }
+ if (tp1->data) {
+ sctp_m_freem(tp1->data);
+ tp1->data = NULL;
+ }
+ do_wakeup_routine = 1;
+ if (PR_SCTP_BUF_ENABLED(tp1->flags)) {
+ stcb->asoc.sent_queue_cnt_removeable--;
+ }
+ }
+ tp1->sent = SCTP_FORWARD_TSN_SKIP;
+ if ((tp1->rec.data.rcv_flags & SCTP_DATA_NOT_FRAG) ==
+ SCTP_DATA_NOT_FRAG) {
+ /* not frag'ed we ae done */
+ notdone = 0;
+ foundeom = 1;
+ } else if (tp1->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ /* end of frag, we are done */
+ notdone = 0;
+ foundeom = 1;
+ } else {
+ /*
+ * Its a begin or middle piece, we must mark all of
+ * it
+ */
+ notdone = 1;
+ tp1 = TAILQ_NEXT(tp1, sctp_next);
+ }
+ } while (tp1 && notdone);
+ if (foundeom == 0) {
+ /*
+ * The multi-part message was scattered across the send and
+ * sent queue.
+ */
+ TAILQ_FOREACH_SAFE(tp1, &stcb->asoc.send_queue, sctp_next, tp2) {
+ if ((tp1->rec.data.sid != sid) ||
+ (!SCTP_MID_EQ(stcb->asoc.idata_supported, tp1->rec.data.mid, mid))) {
+ break;
+ }
+ /* save to chk in case we have some on stream out
+ * queue. If so and we have an un-transmitted one
+ * we don't have to fudge the TSN.
+ */
+ chk = tp1;
+ ret_sz += tp1->book_size;
+ sctp_free_bufspace(stcb, &stcb->asoc, tp1, 1);
+ if (sent) {
+ sctp_ulp_notify(SCTP_NOTIFY_SENT_DG_FAIL, stcb, 0, tp1, so_locked);
+ } else {
+ sctp_ulp_notify(SCTP_NOTIFY_UNSENT_DG_FAIL, stcb, 0, tp1, so_locked);
+ }
+ if (tp1->data) {
+ sctp_m_freem(tp1->data);
+ tp1->data = NULL;
+ }
+ /* No flight involved here book the size to 0 */
+ tp1->book_size = 0;
+ if (tp1->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ foundeom = 1;
+ }
+ do_wakeup_routine = 1;
+ tp1->sent = SCTP_FORWARD_TSN_SKIP;
+ TAILQ_REMOVE(&stcb->asoc.send_queue, tp1, sctp_next);
+ /* on to the sent queue so we can wait for it to be passed by. */
+ TAILQ_INSERT_TAIL(&stcb->asoc.sent_queue, tp1,
+ sctp_next);
+ stcb->asoc.send_queue_cnt--;
+ stcb->asoc.sent_queue_cnt++;
+ }
+ }
+ if (foundeom == 0) {
+ /*
+ * Still no eom found. That means there
+ * is stuff left on the stream out queue.. yuck.
+ */
+ strq = &stcb->asoc.strmout[sid];
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if (sp != NULL) {
+ sp->discard_rest = 1;
+ /*
+ * We may need to put a chunk on the
+ * queue that holds the TSN that
+ * would have been sent with the LAST
+ * bit.
+ */
+ if (chk == NULL) {
+ /* Yep, we have to */
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* we are hosed. All we can
+ * do is nothing.. which will
+ * cause an abort if the peer is
+ * paying attention.
+ */
+ goto oh_well;
+ }
+ memset(chk, 0, sizeof(*chk));
+ chk->rec.data.rcv_flags = 0;
+ chk->sent = SCTP_FORWARD_TSN_SKIP;
+ chk->asoc = &stcb->asoc;
+ if (stcb->asoc.idata_supported == 0) {
+ if (sp->sinfo_flags & SCTP_UNORDERED) {
+ chk->rec.data.mid = 0;
+ } else {
+ chk->rec.data.mid = strq->next_mid_ordered;
+ }
+ } else {
+ if (sp->sinfo_flags & SCTP_UNORDERED) {
+ chk->rec.data.mid = strq->next_mid_unordered;
+ } else {
+ chk->rec.data.mid = strq->next_mid_ordered;
+ }
+ }
+ chk->rec.data.sid = sp->sid;
+ chk->rec.data.ppid = sp->ppid;
+ chk->rec.data.context = sp->context;
+ chk->flags = sp->act_flags;
+ chk->whoTo = NULL;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ chk->rec.data.tsn = atomic_fetchadd_int(&stcb->asoc.sending_seq, 1);
+#else
+ chk->rec.data.tsn = stcb->asoc.sending_seq++;
+#endif
+ strq->chunks_on_queues++;
+ TAILQ_INSERT_TAIL(&stcb->asoc.sent_queue, chk, sctp_next);
+ stcb->asoc.sent_queue_cnt++;
+ stcb->asoc.pr_sctp_cnt++;
+ }
+ chk->rec.data.rcv_flags |= SCTP_DATA_LAST_FRAG;
+ if (sp->sinfo_flags & SCTP_UNORDERED) {
+ chk->rec.data.rcv_flags |= SCTP_DATA_UNORDERED;
+ }
+ if (stcb->asoc.idata_supported == 0) {
+ if ((sp->sinfo_flags & SCTP_UNORDERED) == 0) {
+ strq->next_mid_ordered++;
+ }
+ } else {
+ if (sp->sinfo_flags & SCTP_UNORDERED) {
+ strq->next_mid_unordered++;
+ } else {
+ strq->next_mid_ordered++;
+ }
+ }
+ oh_well:
+ if (sp->data) {
+ /* Pull any data to free up the SB and
+ * allow sender to "add more" while we
+ * will throw away :-)
+ */
+ sctp_free_spbufspace(stcb, &stcb->asoc, sp);
+ ret_sz += sp->length;
+ do_wakeup_routine = 1;
+ sp->some_taken = 1;
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ sp->tail_mbuf = NULL;
+ sp->length = 0;
+ }
+ }
+ }
+ if (do_wakeup_routine) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+
+ so = SCTP_INP_SO(stcb->sctp_ep);
+ if (!so_locked) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ /* assoc was freed while we were unlocked */
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return (ret_sz);
+ }
+ }
+#endif
+ sctp_sowwakeup(stcb->sctp_ep, stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+ }
+ return (ret_sz);
+}
+
+/*
+ * checks to see if the given address, sa, is one that is currently known by
+ * the kernel note: can't distinguish the same address on multiple interfaces
+ * and doesn't handle multiple addresses with different zone/scope id's note:
+ * ifa_ifwithaddr() compares the entire sockaddr struct
+ */
+struct sctp_ifa *
+sctp_find_ifa_in_ep(struct sctp_inpcb *inp, struct sockaddr *addr,
+ int holds_lock)
+{
+ struct sctp_laddr *laddr;
+
+ if (holds_lock == 0) {
+ SCTP_INP_RLOCK(inp);
+ }
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL)
+ continue;
+ if (addr->sa_family != laddr->ifa->address.sa.sa_family)
+ continue;
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ if (((struct sockaddr_in *)addr)->sin_addr.s_addr ==
+ laddr->ifa->address.sin.sin_addr.s_addr) {
+ /* found him. */
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ if (SCTP6_ARE_ADDR_EQUAL((struct sockaddr_in6 *)addr,
+ &laddr->ifa->address.sin6)) {
+ /* found him. */
+ break;
+ }
+ }
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ if (((struct sockaddr_conn *)addr)->sconn_addr == laddr->ifa->address.sconn.sconn_addr) {
+ /* found him. */
+ break;
+ }
+ }
+#endif
+ }
+ if (holds_lock == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ }
+ if (laddr != NULL) {
+ return (laddr->ifa);
+ } else {
+ return (NULL);
+ }
+}
+
+uint32_t
+sctp_get_ifa_hash_val(struct sockaddr *addr)
+{
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ return (sin->sin_addr.s_addr ^ (sin->sin_addr.s_addr >> 16));
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+ uint32_t hash_of_addr;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+#if !defined(_WIN32) && !(defined(__FreeBSD__) && defined(__Userspace__)) && !defined(__APPLE__)
+ hash_of_addr = (sin6->sin6_addr.s6_addr32[0] +
+ sin6->sin6_addr.s6_addr32[1] +
+ sin6->sin6_addr.s6_addr32[2] +
+ sin6->sin6_addr.s6_addr32[3]);
+#else
+ hash_of_addr = (((uint32_t *)&sin6->sin6_addr)[0] +
+ ((uint32_t *)&sin6->sin6_addr)[1] +
+ ((uint32_t *)&sin6->sin6_addr)[2] +
+ ((uint32_t *)&sin6->sin6_addr)[3]);
+#endif
+ hash_of_addr = (hash_of_addr ^ (hash_of_addr >> 16));
+ return (hash_of_addr);
+ }
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ {
+ struct sockaddr_conn *sconn;
+ uintptr_t temp;
+
+ sconn = (struct sockaddr_conn *)addr;
+ temp = (uintptr_t)sconn->sconn_addr;
+ return ((uint32_t)(temp ^ (temp >> 16)));
+ }
+#endif
+ default:
+ break;
+ }
+ return (0);
+}
+
+struct sctp_ifa *
+sctp_find_ifa_by_addr(struct sockaddr *addr, uint32_t vrf_id, int holds_lock)
+{
+ struct sctp_ifa *sctp_ifap;
+ struct sctp_vrf *vrf;
+ struct sctp_ifalist *hash_head;
+ uint32_t hash_of_addr;
+
+ if (holds_lock == 0) {
+ SCTP_IPI_ADDR_RLOCK();
+ } else {
+ SCTP_IPI_ADDR_LOCK_ASSERT();
+ }
+
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ if (holds_lock == 0)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (NULL);
+ }
+
+ hash_of_addr = sctp_get_ifa_hash_val(addr);
+
+ hash_head = &vrf->vrf_addr_hash[(hash_of_addr & vrf->vrf_addr_hashmark)];
+ if (hash_head == NULL) {
+ SCTP_PRINTF("hash_of_addr:%x mask:%x table:%x - ",
+ hash_of_addr, (uint32_t)vrf->vrf_addr_hashmark,
+ (uint32_t)(hash_of_addr & vrf->vrf_addr_hashmark));
+ sctp_print_address(addr);
+ SCTP_PRINTF("No such bucket for address\n");
+ if (holds_lock == 0)
+ SCTP_IPI_ADDR_RUNLOCK();
+
+ return (NULL);
+ }
+ LIST_FOREACH(sctp_ifap, hash_head, next_bucket) {
+ if (addr->sa_family != sctp_ifap->address.sa.sa_family)
+ continue;
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ if (((struct sockaddr_in *)addr)->sin_addr.s_addr ==
+ sctp_ifap->address.sin.sin_addr.s_addr) {
+ /* found him. */
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ if (addr->sa_family == AF_INET6) {
+ if (SCTP6_ARE_ADDR_EQUAL((struct sockaddr_in6 *)addr,
+ &sctp_ifap->address.sin6)) {
+ /* found him. */
+ break;
+ }
+ }
+#endif
+#if defined(__Userspace__)
+ if (addr->sa_family == AF_CONN) {
+ if (((struct sockaddr_conn *)addr)->sconn_addr == sctp_ifap->address.sconn.sconn_addr) {
+ /* found him. */
+ break;
+ }
+ }
+#endif
+ }
+ if (holds_lock == 0)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (sctp_ifap);
+}
+
+static void
+sctp_user_rcvd(struct sctp_tcb *stcb, uint32_t *freed_so_far, int hold_rlock,
+ uint32_t rwnd_req)
+{
+ /* User pulled some data, do we need a rwnd update? */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ int r_unlocked = 0;
+ uint32_t dif, rwnd;
+ struct socket *so = NULL;
+
+ if (stcb == NULL)
+ return;
+
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_ACK_SENT) ||
+ (stcb->asoc.state & (SCTP_STATE_ABOUT_TO_BE_FREED | SCTP_STATE_SHUTDOWN_RECEIVED))) {
+ /* Pre-check If we are freeing no update */
+ goto no_lock;
+ }
+ SCTP_INP_INCR_REF(stcb->sctp_ep);
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ goto out;
+ }
+ so = stcb->sctp_socket;
+ if (so == NULL) {
+ goto out;
+ }
+ atomic_add_int(&stcb->freed_by_sorcv_sincelast, *freed_so_far);
+ /* Have you have freed enough to look */
+ *freed_so_far = 0;
+ /* Yep, its worth a look and the lock overhead */
+
+ /* Figure out what the rwnd would be */
+ rwnd = sctp_calc_rwnd(stcb, &stcb->asoc);
+ if (rwnd >= stcb->asoc.my_last_reported_rwnd) {
+ dif = rwnd - stcb->asoc.my_last_reported_rwnd;
+ } else {
+ dif = 0;
+ }
+ if (dif >= rwnd_req) {
+ if (hold_rlock) {
+ SCTP_INP_READ_UNLOCK(stcb->sctp_ep);
+ r_unlocked = 1;
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ /*
+ * One last check before we allow the guy possibly
+ * to get in. There is a race, where the guy has not
+ * reached the gate. In that case
+ */
+ goto out;
+ }
+ SCTP_TCB_LOCK(stcb);
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ /* No reports here */
+ SCTP_TCB_UNLOCK(stcb);
+ goto out;
+ }
+ SCTP_STAT_INCR(sctps_wu_sacks_sent);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_send_sack(stcb, SCTP_SO_LOCKED);
+
+ sctp_chunk_output(stcb->sctp_ep, stcb,
+ SCTP_OUTPUT_FROM_USR_RCVD, SCTP_SO_LOCKED);
+ /* make sure no timer is running */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_6);
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ /* Update how much we have pending */
+ stcb->freed_by_sorcv_sincelast = dif;
+ }
+ out:
+ if (so && r_unlocked && hold_rlock) {
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ }
+
+ SCTP_INP_DECR_REF(stcb->sctp_ep);
+ no_lock:
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return;
+}
+
+int
+sctp_sorecvmsg(struct socket *so,
+ struct uio *uio,
+ struct mbuf **mp,
+ struct sockaddr *from,
+ int fromlen,
+ int *msg_flags,
+ struct sctp_sndrcvinfo *sinfo,
+ int filling_sinfo)
+{
+ /*
+ * MSG flags we will look at MSG_DONTWAIT - non-blocking IO.
+ * MSG_PEEK - Look don't touch :-D (only valid with OUT mbuf copy
+ * mp=NULL thus uio is the copy method to userland) MSG_WAITALL - ??
+ * On the way out we may send out any combination of:
+ * MSG_NOTIFICATION MSG_EOR
+ *
+ */
+ struct sctp_inpcb *inp = NULL;
+ ssize_t my_len = 0;
+ ssize_t cp_len = 0;
+ int error = 0;
+ struct sctp_queued_to_read *control = NULL, *ctl = NULL, *nxt = NULL;
+ struct mbuf *m = NULL;
+ struct sctp_tcb *stcb = NULL;
+ int wakeup_read_socket = 0;
+ int freecnt_applied = 0;
+ int out_flags = 0, in_flags = 0;
+ int block_allowed = 1;
+ uint32_t freed_so_far = 0;
+ ssize_t copied_so_far = 0;
+ int in_eeor_mode = 0;
+ int no_rcv_needed = 0;
+ uint32_t rwnd_req = 0;
+ int hold_sblock = 0;
+ int hold_rlock = 0;
+ ssize_t slen = 0;
+ uint32_t held_length = 0;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ int sockbuf_lock = 0;
+#endif
+
+ if (uio == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ return (EINVAL);
+ }
+
+ if (msg_flags) {
+ in_flags = *msg_flags;
+ if (in_flags & MSG_PEEK)
+ SCTP_STAT_INCR(sctps_read_peeks);
+ } else {
+ in_flags = 0;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ slen = uio->uio_resid;
+#else
+ slen = uio_resid(uio);
+#endif
+#else
+ slen = uio->uio_resid;
+#endif
+
+ /* Pull in and set up our int flags */
+ if (in_flags & MSG_OOB) {
+ /* Out of band's NOT supported */
+ return (EOPNOTSUPP);
+ }
+ if ((in_flags & MSG_PEEK) && (mp != NULL)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ return (EINVAL);
+ }
+ if ((in_flags & (MSG_DONTWAIT
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ | MSG_NBIO
+#endif
+ )) ||
+ SCTP_SO_IS_NBIO(so)) {
+ block_allowed = 0;
+ }
+ /* setup the endpoint */
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTPUTIL, EFAULT);
+ return (EFAULT);
+ }
+ rwnd_req = (SCTP_SB_LIMIT_RCV(so) >> SCTP_RWND_HIWAT_SHIFT);
+ /* Must be at least a MTU's worth */
+ if (rwnd_req < SCTP_MIN_RWND)
+ rwnd_req = SCTP_MIN_RWND;
+ in_eeor_mode = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_RECV_RWND_LOGGING_ENABLE) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ sctp_misc_ints(SCTP_SORECV_ENTER,
+ rwnd_req, in_eeor_mode, SCTP_SBAVAIL(&so->so_rcv), uio->uio_resid);
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTER,
+ rwnd_req, in_eeor_mode, SCTP_SBAVAIL(&so->so_rcv), uio_resid(uio));
+#endif
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTER,
+ rwnd_req, in_eeor_mode, SCTP_SBAVAIL(&so->so_rcv), (uint32_t)uio->uio_resid);
+#endif
+ }
+#if defined(__Userspace__)
+ SOCKBUF_LOCK(&so->so_rcv);
+ hold_sblock = 1;
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) &SCTP_RECV_RWND_LOGGING_ENABLE) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ sctp_misc_ints(SCTP_SORECV_ENTERPL,
+ rwnd_req, block_allowed, SCTP_SBAVAIL(&so->so_rcv), uio->uio_resid);
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTERPL,
+ rwnd_req, block_allowed, SCTP_SBAVAIL(&so->so_rcv), uio_resid(uio));
+#endif
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTERPL,
+ rwnd_req, block_allowed, SCTP_SBAVAIL(&so->so_rcv), (uint32_t)uio->uio_resid);
+#endif
+ }
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ error = sblock(&so->so_rcv, SBLOCKWAIT(in_flags));
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ error = SOCK_IO_RECV_LOCK(so, SBLOCKWAIT(in_flags));
+#endif
+ if (error) {
+ goto release_unlocked;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ sockbuf_lock = 1;
+#endif
+ restart:
+#if defined(__Userspace__)
+ if (hold_sblock == 0) {
+ SOCKBUF_LOCK(&so->so_rcv);
+ hold_sblock = 1;
+ }
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&so->so_rcv, 1);
+#endif
+
+ restart_nosblocks:
+ if (hold_sblock == 0) {
+ SOCKBUF_LOCK(&so->so_rcv);
+ hold_sblock = 1;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ goto out;
+ }
+#if (defined(__FreeBSD__) || defined(_WIN32)) && !defined(__Userspace__)
+ if ((so->so_rcv.sb_state & SBS_CANTRCVMORE) && SCTP_SBAVAIL(&so->so_rcv) == 0) {
+#else
+ if ((so->so_state & SS_CANTRCVMORE) && SCTP_SBAVAIL(&so->so_rcv) == 0) {
+#endif
+ if (so->so_error) {
+ error = so->so_error;
+ if ((in_flags & MSG_PEEK) == 0)
+ so->so_error = 0;
+ goto out;
+ } else {
+ if (SCTP_SBAVAIL(&so->so_rcv) == 0) {
+ /* indicate EOF */
+ error = 0;
+ goto out;
+ }
+ }
+ }
+ if (SCTP_SBAVAIL(&so->so_rcv) <= held_length) {
+ if (so->so_error) {
+ error = so->so_error;
+ if ((in_flags & MSG_PEEK) == 0) {
+ so->so_error = 0;
+ }
+ goto out;
+ }
+ if ((SCTP_SBAVAIL(&so->so_rcv) == 0) &&
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0) {
+ /* For active open side clear flags for re-use
+ * passive open is blocked by connect.
+ */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_WAS_ABORTED) {
+ /* You were aborted, passive side always hits here */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, ECONNRESET);
+ error = ECONNRESET;
+ }
+ so->so_state &= ~(SS_ISCONNECTING |
+ SS_ISDISCONNECTING |
+ SS_ISCONFIRMING |
+ SS_ISCONNECTED);
+ if (error == 0) {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_WAS_CONNECTED) == 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, ENOTCONN);
+ error = ENOTCONN;
+ }
+ }
+ goto out;
+ }
+ }
+ if (block_allowed) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ error = sbwait(so, SO_RCV);
+#else
+ error = sbwait(&so->so_rcv);
+#endif
+ if (error) {
+ goto out;
+ }
+ held_length = 0;
+ goto restart_nosblocks;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EWOULDBLOCK);
+ error = EWOULDBLOCK;
+ goto out;
+ }
+ }
+ if (hold_sblock == 1) {
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ hold_sblock = 0;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ error = sblock(&so->so_rcv, SBLOCKWAIT(in_flags));
+#endif
+ /* we possibly have data we can read */
+ /*sa_ignore FREED_MEMORY*/
+ control = TAILQ_FIRST(&inp->read_queue);
+ if (control == NULL) {
+ /* This could be happening since
+ * the appender did the increment but as not
+ * yet did the tailq insert onto the read_queue
+ */
+ if (hold_rlock == 0) {
+ SCTP_INP_READ_LOCK(inp);
+ }
+ control = TAILQ_FIRST(&inp->read_queue);
+ if ((control == NULL) && (SCTP_SBAVAIL(&so->so_rcv) > 0)) {
+#ifdef INVARIANTS
+ panic("Huh, its non zero and nothing on control?");
+#endif
+ SCTP_SB_CLEAR(so->so_rcv);
+ }
+ SCTP_INP_READ_UNLOCK(inp);
+ hold_rlock = 0;
+ goto restart;
+ }
+
+ if ((control->length == 0) &&
+ (control->do_not_ref_stcb)) {
+ /* Clean up code for freeing assoc that left behind a pdapi..
+ * maybe a peer in EEOR that just closed after sending and
+ * never indicated a EOR.
+ */
+ if (hold_rlock == 0) {
+ hold_rlock = 1;
+ SCTP_INP_READ_LOCK(inp);
+ }
+ control->held_length = 0;
+ if (control->data) {
+ /* Hmm there is data here .. fix */
+ struct mbuf *m_tmp;
+ int cnt = 0;
+ m_tmp = control->data;
+ while (m_tmp) {
+ cnt += SCTP_BUF_LEN(m_tmp);
+ if (SCTP_BUF_NEXT(m_tmp) == NULL) {
+ control->tail_mbuf = m_tmp;
+ control->end_added = 1;
+ }
+ m_tmp = SCTP_BUF_NEXT(m_tmp);
+ }
+ control->length = cnt;
+ } else {
+ /* remove it */
+ TAILQ_REMOVE(&inp->read_queue, control, next);
+ /* Add back any hidden data */
+ sctp_free_remote_addr(control->whoFrom);
+ sctp_free_a_readq(stcb, control);
+ }
+ if (hold_rlock) {
+ hold_rlock = 0;
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+ goto restart;
+ }
+ if ((control->length == 0) &&
+ (control->end_added == 1)) {
+ /* Do we also need to check for (control->pdapi_aborted == 1)? */
+ if (hold_rlock == 0) {
+ hold_rlock = 1;
+ SCTP_INP_READ_LOCK(inp);
+ }
+ TAILQ_REMOVE(&inp->read_queue, control, next);
+ if (control->data) {
+#ifdef INVARIANTS
+ panic("control->data not null but control->length == 0");
+#else
+ SCTP_PRINTF("Strange, data left in the control buffer. Cleaning up.\n");
+ sctp_m_freem(control->data);
+ control->data = NULL;
+#endif
+ }
+ if (control->aux_data) {
+ sctp_m_free (control->aux_data);
+ control->aux_data = NULL;
+ }
+#ifdef INVARIANTS
+ if (control->on_strm_q) {
+ panic("About to free ctl:%p so:%p and its in %d",
+ control, so, control->on_strm_q);
+ }
+#endif
+ sctp_free_remote_addr(control->whoFrom);
+ sctp_free_a_readq(stcb, control);
+ if (hold_rlock) {
+ hold_rlock = 0;
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+ goto restart;
+ }
+ if (control->length == 0) {
+ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE)) &&
+ (filling_sinfo)) {
+ /* find a more suitable one then this */
+ ctl = TAILQ_NEXT(control, next);
+ while (ctl) {
+ if ((ctl->stcb != control->stcb) && (ctl->length) &&
+ (ctl->some_taken ||
+ (ctl->spec_flags & M_NOTIFICATION) ||
+ ((ctl->do_not_ref_stcb == 0) &&
+ (ctl->stcb->asoc.strmin[ctl->sinfo_stream].delivery_started == 0)))
+ ) {
+ /*-
+ * If we have a different TCB next, and there is data
+ * present. If we have already taken some (pdapi), OR we can
+ * ref the tcb and no delivery as started on this stream, we
+ * take it. Note we allow a notification on a different
+ * assoc to be delivered..
+ */
+ control = ctl;
+ goto found_one;
+ } else if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS)) &&
+ (ctl->length) &&
+ ((ctl->some_taken) ||
+ ((ctl->do_not_ref_stcb == 0) &&
+ ((ctl->spec_flags & M_NOTIFICATION) == 0) &&
+ (ctl->stcb->asoc.strmin[ctl->sinfo_stream].delivery_started == 0)))) {
+ /*-
+ * If we have the same tcb, and there is data present, and we
+ * have the strm interleave feature present. Then if we have
+ * taken some (pdapi) or we can refer to tht tcb AND we have
+ * not started a delivery for this stream, we can take it.
+ * Note we do NOT allow a notification on the same assoc to
+ * be delivered.
+ */
+ control = ctl;
+ goto found_one;
+ }
+ ctl = TAILQ_NEXT(ctl, next);
+ }
+ }
+ /*
+ * if we reach here, not suitable replacement is available
+ * <or> fragment interleave is NOT on. So stuff the sb_cc
+ * into the our held count, and its time to sleep again.
+ */
+ held_length = SCTP_SBAVAIL(&so->so_rcv);
+ control->held_length = SCTP_SBAVAIL(&so->so_rcv);
+ goto restart;
+ }
+ /* Clear the held length since there is something to read */
+ control->held_length = 0;
+ found_one:
+ /*
+ * If we reach here, control has a some data for us to read off.
+ * Note that stcb COULD be NULL.
+ */
+ if (hold_rlock == 0) {
+ hold_rlock = 1;
+ SCTP_INP_READ_LOCK(inp);
+ }
+ control->some_taken++;
+ stcb = control->stcb;
+ if (stcb) {
+ if ((control->do_not_ref_stcb == 0) &&
+ (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED)) {
+ if (freecnt_applied == 0)
+ stcb = NULL;
+ } else if (control->do_not_ref_stcb == 0) {
+ /* you can't free it on me please */
+ /*
+ * The lock on the socket buffer protects us so the
+ * free code will stop. But since we used the socketbuf
+ * lock and the sender uses the tcb_lock to increment,
+ * we need to use the atomic add to the refcnt
+ */
+ if (freecnt_applied) {
+#ifdef INVARIANTS
+ panic("refcnt already incremented");
+#else
+ SCTP_PRINTF("refcnt already incremented?\n");
+#endif
+ } else {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ freecnt_applied = 1;
+ }
+ /*
+ * Setup to remember how much we have not yet told
+ * the peer our rwnd has opened up. Note we grab
+ * the value from the tcb from last time.
+ * Note too that sack sending clears this when a sack
+ * is sent, which is fine. Once we hit the rwnd_req,
+ * we then will go to the sctp_user_rcvd() that will
+ * not lock until it KNOWs it MUST send a WUP-SACK.
+ */
+ freed_so_far = (uint32_t)stcb->freed_by_sorcv_sincelast;
+ stcb->freed_by_sorcv_sincelast = 0;
+ }
+ }
+ if (stcb &&
+ ((control->spec_flags & M_NOTIFICATION) == 0) &&
+ control->do_not_ref_stcb == 0) {
+ stcb->asoc.strmin[control->sinfo_stream].delivery_started = 1;
+ }
+
+ /* First lets get off the sinfo and sockaddr info */
+ if ((sinfo != NULL) && (filling_sinfo != 0)) {
+ sinfo->sinfo_stream = control->sinfo_stream;
+ sinfo->sinfo_ssn = (uint16_t)control->mid;
+ sinfo->sinfo_flags = control->sinfo_flags;
+ sinfo->sinfo_ppid = control->sinfo_ppid;
+ sinfo->sinfo_context =control->sinfo_context;
+ sinfo->sinfo_timetolive = control->sinfo_timetolive;
+ sinfo->sinfo_tsn = control->sinfo_tsn;
+ sinfo->sinfo_cumtsn = control->sinfo_cumtsn;
+ sinfo->sinfo_assoc_id = control->sinfo_assoc_id;
+ nxt = TAILQ_NEXT(control, next);
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO) ||
+ sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVNXTINFO)) {
+ struct sctp_extrcvinfo *s_extra;
+ s_extra = (struct sctp_extrcvinfo *)sinfo;
+ if ((nxt) &&
+ (nxt->length)) {
+ s_extra->serinfo_next_flags = SCTP_NEXT_MSG_AVAIL;
+ if (nxt->sinfo_flags & SCTP_UNORDERED) {
+ s_extra->serinfo_next_flags |= SCTP_NEXT_MSG_IS_UNORDERED;
+ }
+ if (nxt->spec_flags & M_NOTIFICATION) {
+ s_extra->serinfo_next_flags |= SCTP_NEXT_MSG_IS_NOTIFICATION;
+ }
+ s_extra->serinfo_next_aid = nxt->sinfo_assoc_id;
+ s_extra->serinfo_next_length = nxt->length;
+ s_extra->serinfo_next_ppid = nxt->sinfo_ppid;
+ s_extra->serinfo_next_stream = nxt->sinfo_stream;
+ if (nxt->tail_mbuf != NULL) {
+ if (nxt->end_added) {
+ s_extra->serinfo_next_flags |= SCTP_NEXT_MSG_ISCOMPLETE;
+ }
+ }
+ } else {
+ /* we explicitly 0 this, since the memcpy got
+ * some other things beyond the older sinfo_
+ * that is on the control's structure :-D
+ */
+ nxt = NULL;
+ s_extra->serinfo_next_flags = SCTP_NO_NEXT_MSG;
+ s_extra->serinfo_next_aid = 0;
+ s_extra->serinfo_next_length = 0;
+ s_extra->serinfo_next_ppid = 0;
+ s_extra->serinfo_next_stream = 0;
+ }
+ }
+ /*
+ * update off the real current cum-ack, if we have an stcb.
+ */
+ if ((control->do_not_ref_stcb == 0) && stcb)
+ sinfo->sinfo_cumtsn = stcb->asoc.cumulative_tsn;
+ /*
+ * mask off the high bits, we keep the actual chunk bits in
+ * there.
+ */
+ sinfo->sinfo_flags &= 0x00ff;
+ if ((control->sinfo_flags >> 8) & SCTP_DATA_UNORDERED) {
+ sinfo->sinfo_flags |= SCTP_UNORDERED;
+ }
+ }
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ {
+ int index, newindex;
+ struct sctp_pcbtsn_rlog *entry;
+ do {
+ index = inp->readlog_index;
+ newindex = index + 1;
+ if (newindex >= SCTP_READ_LOG_SIZE) {
+ newindex = 0;
+ }
+ } while (atomic_cmpset_int(&inp->readlog_index, index, newindex) == 0);
+ entry = &inp->readlog[index];
+ entry->vtag = control->sinfo_assoc_id;
+ entry->strm = control->sinfo_stream;
+ entry->seq = (uint16_t)control->mid;
+ entry->sz = control->length;
+ entry->flgs = control->sinfo_flags;
+ }
+#endif
+ if ((fromlen > 0) && (from != NULL)) {
+ union sctp_sockstore store;
+ size_t len;
+
+ switch (control->whoFrom->ro._l_addr.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ len = sizeof(struct sockaddr_in6);
+ store.sin6 = control->whoFrom->ro._l_addr.sin6;
+ store.sin6.sin6_port = control->port_from;
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#ifdef INET6
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4)) {
+ len = sizeof(struct sockaddr_in6);
+ in6_sin_2_v4mapsin6(&control->whoFrom->ro._l_addr.sin,
+ &store.sin6);
+ store.sin6.sin6_port = control->port_from;
+ } else {
+ len = sizeof(struct sockaddr_in);
+ store.sin = control->whoFrom->ro._l_addr.sin;
+ store.sin.sin_port = control->port_from;
+ }
+#else
+ len = sizeof(struct sockaddr_in);
+ store.sin = control->whoFrom->ro._l_addr.sin;
+ store.sin.sin_port = control->port_from;
+#endif
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ len = sizeof(struct sockaddr_conn);
+ store.sconn = control->whoFrom->ro._l_addr.sconn;
+ store.sconn.sconn_port = control->port_from;
+ break;
+#endif
+ default:
+ len = 0;
+ break;
+ }
+ memcpy(from, &store, min((size_t)fromlen, len));
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+#ifdef INET6
+ {
+ struct sockaddr_in6 lsa6, *from6;
+
+ from6 = (struct sockaddr_in6 *)from;
+ sctp_recover_scope_mac(from6, (&lsa6));
+ }
+#endif
+#endif
+ }
+ if (hold_rlock) {
+ SCTP_INP_READ_UNLOCK(inp);
+ hold_rlock = 0;
+ }
+ if (hold_sblock) {
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ hold_sblock = 0;
+ }
+ /* now copy out what data we can */
+ if (mp == NULL) {
+ /* copy out each mbuf in the chain up to length */
+ get_more_data:
+ m = control->data;
+ while (m) {
+ /* Move out all we can */
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ cp_len = uio->uio_resid;
+#else
+ cp_len = uio_resid(uio);
+#endif
+#else
+ cp_len = uio->uio_resid;
+#endif
+ my_len = SCTP_BUF_LEN(m);
+ if (cp_len > my_len) {
+ /* not enough in this buf */
+ cp_len = my_len;
+ }
+ if (hold_rlock) {
+ SCTP_INP_READ_UNLOCK(inp);
+ hold_rlock = 0;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 0);
+#endif
+ if (cp_len > 0)
+ error = uiomove(mtod(m, char *), (int)cp_len, uio);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(so, 0);
+#endif
+ /* re-read */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ goto release;
+ }
+
+ if ((control->do_not_ref_stcb == 0) && stcb &&
+ stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ no_rcv_needed = 1;
+ }
+ if (error) {
+ /* error we are out of here */
+ goto release;
+ }
+ SCTP_INP_READ_LOCK(inp);
+ hold_rlock = 1;
+ if (cp_len == SCTP_BUF_LEN(m)) {
+ if ((SCTP_BUF_NEXT(m)== NULL) &&
+ (control->end_added)) {
+ out_flags |= MSG_EOR;
+ if ((control->do_not_ref_stcb == 0) &&
+ (control->stcb != NULL) &&
+ ((control->spec_flags & M_NOTIFICATION) == 0))
+ control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0;
+ }
+ if (control->spec_flags & M_NOTIFICATION) {
+ out_flags |= MSG_NOTIFICATION;
+ }
+ /* we ate up the mbuf */
+ if (in_flags & MSG_PEEK) {
+ /* just looking */
+ m = SCTP_BUF_NEXT(m);
+ copied_so_far += cp_len;
+ } else {
+ /* dispose of the mbuf */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&so->so_rcv,
+ control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBFREE, SCTP_BUF_LEN(m));
+ }
+ sctp_sbfree(control, stcb, &so->so_rcv, m);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&so->so_rcv,
+ control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBRESULT, 0);
+ }
+ copied_so_far += cp_len;
+ freed_so_far += (uint32_t)cp_len;
+ freed_so_far += MSIZE;
+ atomic_subtract_int(&control->length, (int)cp_len);
+ control->data = sctp_m_free(m);
+ m = control->data;
+ /* been through it all, must hold sb lock ok to null tail */
+ if (control->data == NULL) {
+#ifdef INVARIANTS
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if ((control->end_added == 0) ||
+ (TAILQ_NEXT(control, next) == NULL)) {
+ /* If the end is not added, OR the
+ * next is NOT null we MUST have the lock.
+ */
+ if (mtx_owned(&inp->inp_rdata_mtx) == 0) {
+ panic("Hmm we don't own the lock?");
+ }
+ }
+#endif
+#endif
+ control->tail_mbuf = NULL;
+#ifdef INVARIANTS
+ if ((control->end_added) && ((out_flags & MSG_EOR) == 0)) {
+ panic("end_added, nothing left and no MSG_EOR");
+ }
+#endif
+ }
+ }
+ } else {
+ /* Do we need to trim the mbuf? */
+ if (control->spec_flags & M_NOTIFICATION) {
+ out_flags |= MSG_NOTIFICATION;
+ }
+ if ((in_flags & MSG_PEEK) == 0) {
+ SCTP_BUF_RESV_UF(m, cp_len);
+ SCTP_BUF_LEN(m) -= (int)cp_len;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&so->so_rcv, control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBFREE, (int)cp_len);
+ }
+ SCTP_SB_DECR(&so->so_rcv, cp_len);
+ if ((control->do_not_ref_stcb == 0) &&
+ stcb) {
+ atomic_subtract_int(&stcb->asoc.sb_cc, (int)cp_len);
+ }
+ copied_so_far += cp_len;
+ freed_so_far += (uint32_t)cp_len;
+ freed_so_far += MSIZE;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&so->so_rcv, control->do_not_ref_stcb?NULL:stcb,
+ SCTP_LOG_SBRESULT, 0);
+ }
+ atomic_subtract_int(&control->length, (int)cp_len);
+ } else {
+ copied_so_far += cp_len;
+ }
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if ((out_flags & MSG_EOR) || (uio->uio_resid == 0)) {
+#else
+ if ((out_flags & MSG_EOR) || (uio_resid(uio) == 0)) {
+#endif
+#else
+ if ((out_flags & MSG_EOR) || (uio->uio_resid == 0)) {
+#endif
+ break;
+ }
+ if (((stcb) && (in_flags & MSG_PEEK) == 0) &&
+ (control->do_not_ref_stcb == 0) &&
+ (freed_so_far >= rwnd_req)) {
+ sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
+ }
+ } /* end while(m) */
+ /*
+ * At this point we have looked at it all and we either have
+ * a MSG_EOR/or read all the user wants... <OR>
+ * control->length == 0.
+ */
+ if ((out_flags & MSG_EOR) && ((in_flags & MSG_PEEK) == 0)) {
+ /* we are done with this control */
+ if (control->length == 0) {
+ if (control->data) {
+#ifdef INVARIANTS
+ panic("control->data not null at read eor?");
+#else
+ SCTP_PRINTF("Strange, data left in the control buffer .. invariants would panic?\n");
+ sctp_m_freem(control->data);
+ control->data = NULL;
+#endif
+ }
+ done_with_control:
+ if (hold_rlock == 0) {
+ SCTP_INP_READ_LOCK(inp);
+ hold_rlock = 1;
+ }
+ TAILQ_REMOVE(&inp->read_queue, control, next);
+ /* Add back any hidden data */
+ if (control->held_length) {
+ held_length = 0;
+ control->held_length = 0;
+ wakeup_read_socket = 1;
+ }
+ if (control->aux_data) {
+ sctp_m_free (control->aux_data);
+ control->aux_data = NULL;
+ }
+ no_rcv_needed = control->do_not_ref_stcb;
+ sctp_free_remote_addr(control->whoFrom);
+ control->data = NULL;
+#ifdef INVARIANTS
+ if (control->on_strm_q) {
+ panic("About to free ctl:%p so:%p and its in %d",
+ control, so, control->on_strm_q);
+ }
+#endif
+ sctp_free_a_readq(stcb, control);
+ control = NULL;
+ if ((freed_so_far >= rwnd_req) &&
+ (no_rcv_needed == 0))
+ sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
+
+ } else {
+ /*
+ * The user did not read all of this
+ * message, turn off the returned MSG_EOR
+ * since we are leaving more behind on the
+ * control to read.
+ */
+#ifdef INVARIANTS
+ if (control->end_added &&
+ (control->data == NULL) &&
+ (control->tail_mbuf == NULL)) {
+ panic("Gak, control->length is corrupt?");
+ }
+#endif
+ no_rcv_needed = control->do_not_ref_stcb;
+ out_flags &= ~MSG_EOR;
+ }
+ }
+ if (out_flags & MSG_EOR) {
+ goto release;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ if ((uio->uio_resid == 0) ||
+#else
+ if ((uio_resid(uio) == 0) ||
+#endif
+#else
+ if ((uio->uio_resid == 0) ||
+#endif
+ ((in_eeor_mode) &&
+ (copied_so_far >= max(so->so_rcv.sb_lowat, 1)))) {
+ goto release;
+ }
+ /*
+ * If I hit here the receiver wants more and this message is
+ * NOT done (pd-api). So two questions. Can we block? if not
+ * we are done. Did the user NOT set MSG_WAITALL?
+ */
+ if (block_allowed == 0) {
+ goto release;
+ }
+ /*
+ * We need to wait for more data a few things:
+ * - We don't release the I/O lock so we don't get someone else
+ * reading.
+ * - We must be sure to account for the case where what is added
+ * is NOT to our control when we wakeup.
+ */
+
+ /* Do we need to tell the transport a rwnd update might be
+ * needed before we go to sleep?
+ */
+ if (((stcb) && (in_flags & MSG_PEEK) == 0) &&
+ ((freed_so_far >= rwnd_req) &&
+ (control->do_not_ref_stcb == 0) &&
+ (no_rcv_needed == 0))) {
+ sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
+ }
+ wait_some_more:
+#if (defined(__FreeBSD__) || defined(_WIN32)) && !defined(__Userspace__)
+ if (so->so_rcv.sb_state & SBS_CANTRCVMORE) {
+ goto release;
+ }
+#else
+ if (so->so_state & SS_CANTRCVMORE) {
+ goto release;
+ }
+#endif
+
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)
+ goto release;
+
+ if (hold_rlock == 1) {
+ SCTP_INP_READ_UNLOCK(inp);
+ hold_rlock = 0;
+ }
+ if (hold_sblock == 0) {
+ SOCKBUF_LOCK(&so->so_rcv);
+ hold_sblock = 1;
+ }
+ if ((copied_so_far) && (control->length == 0) &&
+ (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE))) {
+ goto release;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&so->so_rcv, 1);
+#endif
+ if (SCTP_SBAVAIL(&so->so_rcv) <= control->held_length) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ error = sbwait(so, SO_RCV);
+#else
+ error = sbwait(&so->so_rcv);
+#endif
+ if (error) {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ goto release_unlocked;
+#else
+ goto release;
+#endif
+ }
+ control->held_length = 0;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ error = sblock(&so->so_rcv, SBLOCKWAIT(in_flags));
+#endif
+ if (hold_sblock) {
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ hold_sblock = 0;
+ }
+ if (control->length == 0) {
+ /* still nothing here */
+ if (control->end_added == 1) {
+ /* he aborted, or is done i.e.did a shutdown */
+ out_flags |= MSG_EOR;
+ if (control->pdapi_aborted) {
+ if ((control->do_not_ref_stcb == 0) && ((control->spec_flags & M_NOTIFICATION) == 0))
+ control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0;
+
+ out_flags |= MSG_TRUNC;
+ } else {
+ if ((control->do_not_ref_stcb == 0) && ((control->spec_flags & M_NOTIFICATION) == 0))
+ control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0;
+ }
+ goto done_with_control;
+ }
+ if (SCTP_SBAVAIL(&so->so_rcv) > held_length) {
+ control->held_length = SCTP_SBAVAIL(&so->so_rcv);
+ held_length = 0;
+ }
+ goto wait_some_more;
+ } else if (control->data == NULL) {
+ /* we must re-sync since data
+ * is probably being added
+ */
+ SCTP_INP_READ_LOCK(inp);
+ if ((control->length > 0) && (control->data == NULL)) {
+ /* big trouble.. we have the lock and its corrupt? */
+#ifdef INVARIANTS
+ panic ("Impossible data==NULL length !=0");
+#endif
+ out_flags |= MSG_EOR;
+ out_flags |= MSG_TRUNC;
+ control->length = 0;
+ SCTP_INP_READ_UNLOCK(inp);
+ goto done_with_control;
+ }
+ SCTP_INP_READ_UNLOCK(inp);
+ /* We will fall around to get more data */
+ }
+ goto get_more_data;
+ } else {
+ /*-
+ * Give caller back the mbuf chain,
+ * store in uio_resid the length
+ */
+ wakeup_read_socket = 0;
+ if ((control->end_added == 0) ||
+ (TAILQ_NEXT(control, next) == NULL)) {
+ /* Need to get rlock */
+ if (hold_rlock == 0) {
+ SCTP_INP_READ_LOCK(inp);
+ hold_rlock = 1;
+ }
+ }
+ if (control->end_added) {
+ out_flags |= MSG_EOR;
+ if ((control->do_not_ref_stcb == 0) &&
+ (control->stcb != NULL) &&
+ ((control->spec_flags & M_NOTIFICATION) == 0))
+ control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0;
+ }
+ if (control->spec_flags & M_NOTIFICATION) {
+ out_flags |= MSG_NOTIFICATION;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ uio->uio_resid = control->length;
+#else
+ uio_setresid(uio, control->length);
+#endif
+#else
+ uio->uio_resid = control->length;
+#endif
+ *mp = control->data;
+ m = control->data;
+ while (m) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&so->so_rcv,
+ control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBFREE, SCTP_BUF_LEN(m));
+ }
+ sctp_sbfree(control, stcb, &so->so_rcv, m);
+ freed_so_far += (uint32_t)SCTP_BUF_LEN(m);
+ freed_so_far += MSIZE;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_SB_LOGGING_ENABLE) {
+ sctp_sblog(&so->so_rcv,
+ control->do_not_ref_stcb?NULL:stcb, SCTP_LOG_SBRESULT, 0);
+ }
+ m = SCTP_BUF_NEXT(m);
+ }
+ control->data = control->tail_mbuf = NULL;
+ control->length = 0;
+ if (out_flags & MSG_EOR) {
+ /* Done with this control */
+ goto done_with_control;
+ }
+ }
+ release:
+ if (hold_rlock == 1) {
+ SCTP_INP_READ_UNLOCK(inp);
+ hold_rlock = 0;
+ }
+#if defined(__Userspace__)
+ if (hold_sblock == 0) {
+ SOCKBUF_LOCK(&so->so_rcv);
+ hold_sblock = 1;
+ }
+#else
+ if (hold_sblock == 1) {
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ hold_sblock = 0;
+ }
+#endif
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&so->so_rcv, 1);
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SOCK_IO_RECV_UNLOCK(so);
+ sockbuf_lock = 0;
+#endif
+
+ release_unlocked:
+ if (hold_sblock) {
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ hold_sblock = 0;
+ }
+ if ((stcb) && (in_flags & MSG_PEEK) == 0) {
+ if ((freed_so_far >= rwnd_req) &&
+ (control && (control->do_not_ref_stcb == 0)) &&
+ (no_rcv_needed == 0))
+ sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
+ }
+ out:
+ if (msg_flags) {
+ *msg_flags = out_flags;
+ }
+ if (((out_flags & MSG_EOR) == 0) &&
+ ((in_flags & MSG_PEEK) == 0) &&
+ (sinfo) &&
+ (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO) ||
+ sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVNXTINFO))) {
+ struct sctp_extrcvinfo *s_extra;
+ s_extra = (struct sctp_extrcvinfo *)sinfo;
+ s_extra->serinfo_next_flags = SCTP_NO_NEXT_MSG;
+ }
+ if (hold_rlock == 1) {
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+ if (hold_sblock) {
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (sockbuf_lock) {
+ SOCK_IO_RECV_UNLOCK(so);
+ }
+#endif
+
+ if (freecnt_applied) {
+ /*
+ * The lock on the socket buffer protects us so the free
+ * code will stop. But since we used the socketbuf lock and
+ * the sender uses the tcb_lock to increment, we need to use
+ * the atomic add to the refcnt.
+ */
+ if (stcb == NULL) {
+#ifdef INVARIANTS
+ panic("stcb for refcnt has gone NULL?");
+ goto stage_left;
+#else
+ goto stage_left;
+#endif
+ }
+ /* Save the value back for next time */
+ stcb->freed_by_sorcv_sincelast = freed_so_far;
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) &SCTP_RECV_RWND_LOGGING_ENABLE) {
+ if (stcb) {
+ sctp_misc_ints(SCTP_SORECV_DONE,
+ freed_so_far,
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ ((uio) ? (slen - uio->uio_resid) : slen),
+#else
+ ((uio) ? (slen - uio_resid(uio)) : slen),
+#endif
+#else
+ (uint32_t)((uio) ? (slen - uio->uio_resid) : slen),
+#endif
+ stcb->asoc.my_rwnd,
+ SCTP_SBAVAIL(&so->so_rcv));
+ } else {
+ sctp_misc_ints(SCTP_SORECV_DONE,
+ freed_so_far,
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ ((uio) ? (slen - uio->uio_resid) : slen),
+#else
+ ((uio) ? (slen - uio_resid(uio)) : slen),
+#endif
+#else
+ (uint32_t)((uio) ? (slen - uio->uio_resid) : slen),
+#endif
+ 0,
+ SCTP_SBAVAIL(&so->so_rcv));
+ }
+ }
+ stage_left:
+ if (wakeup_read_socket) {
+ sctp_sorwakeup(inp, so);
+ }
+ return (error);
+}
+
+#ifdef SCTP_MBUF_LOGGING
+struct mbuf *
+sctp_m_free(struct mbuf *m)
+{
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mb(m, SCTP_MBUF_IFREE);
+ }
+ return (m_free(m));
+}
+
+void
+sctp_m_freem(struct mbuf *mb)
+{
+ while (mb != NULL)
+ mb = sctp_m_free(mb);
+}
+
+#endif
+
+int
+sctp_dynamic_set_primary(struct sockaddr *sa, uint32_t vrf_id)
+{
+ /* Given a local address. For all associations
+ * that holds the address, request a peer-set-primary.
+ */
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *wi;
+
+ ifa = sctp_find_ifa_by_addr(sa, vrf_id, SCTP_ADDR_NOT_LOCKED);
+ if (ifa == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTPUTIL, EADDRNOTAVAIL);
+ return (EADDRNOTAVAIL);
+ }
+ /* Now that we have the ifa we must awaken the
+ * iterator with this message.
+ */
+ wi = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr);
+ if (wi == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTPUTIL, ENOMEM);
+ return (ENOMEM);
+ }
+ /* Now incr the count and int wi structure */
+ SCTP_INCR_LADDR_COUNT();
+ memset(wi, 0, sizeof(*wi));
+ (void)SCTP_GETTIME_TIMEVAL(&wi->start_time);
+ wi->ifa = ifa;
+ wi->action = SCTP_SET_PRIM_ADDR;
+ atomic_add_int(&ifa->refcount, 1);
+
+ /* Now add it to the work queue */
+ SCTP_WQ_ADDR_LOCK();
+ /*
+ * Should this really be a tailq? As it is we will process the
+ * newest first :-0
+ */
+ LIST_INSERT_HEAD(&SCTP_BASE_INFO(addr_wq), wi, sctp_nxt_addr);
+ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
+ (struct sctp_inpcb *)NULL,
+ (struct sctp_tcb *)NULL,
+ (struct sctp_nets *)NULL);
+ SCTP_WQ_ADDR_UNLOCK();
+ return (0);
+}
+
+#if defined(__Userspace__)
+/* no sctp_soreceive for __Userspace__ now */
+#endif
+#if !defined(__Userspace__)
+int
+sctp_soreceive( struct socket *so,
+ struct sockaddr **psa,
+ struct uio *uio,
+ struct mbuf **mp0,
+ struct mbuf **controlp,
+ int *flagsp)
+{
+ int error, fromlen;
+ uint8_t sockbuf[256];
+ struct sockaddr *from;
+ struct sctp_extrcvinfo sinfo;
+ int filling_sinfo = 1;
+ int flags;
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ /* pickup the assoc we are reading from */
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ return (EINVAL);
+ }
+ if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT) &&
+ sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVRCVINFO) &&
+ sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVNXTINFO)) ||
+ (controlp == NULL)) {
+ /* user does not want the sndrcv ctl */
+ filling_sinfo = 0;
+ }
+ if (psa) {
+ from = (struct sockaddr *)sockbuf;
+ fromlen = sizeof(sockbuf);
+#ifdef HAVE_SA_LEN
+ from->sa_len = 0;
+#endif
+ } else {
+ from = NULL;
+ fromlen = 0;
+ }
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+ if (filling_sinfo) {
+ memset(&sinfo, 0, sizeof(struct sctp_extrcvinfo));
+ }
+ if (flagsp != NULL) {
+ flags = *flagsp;
+ } else {
+ flags = 0;
+ }
+ error = sctp_sorecvmsg(so, uio, mp0, from, fromlen, &flags,
+ (struct sctp_sndrcvinfo *)&sinfo, filling_sinfo);
+ if (flagsp != NULL) {
+ *flagsp = flags;
+ }
+ if (controlp != NULL) {
+ /* copy back the sinfo in a CMSG format */
+ if (filling_sinfo && ((flags & MSG_NOTIFICATION) == 0)) {
+ *controlp = sctp_build_ctl_nchunk(inp,
+ (struct sctp_sndrcvinfo *)&sinfo);
+ } else {
+ *controlp = NULL;
+ }
+ }
+ if (psa) {
+ /* copy back the address info */
+#ifdef HAVE_SA_LEN
+ if (from && from->sa_len) {
+#else
+ if (from) {
+#endif
+#if (defined(__FreeBSD__) || defined(_WIN32)) && !defined(__Userspace__)
+ *psa = sodupsockaddr(from, M_NOWAIT);
+#else
+ *psa = dup_sockaddr(from, mp0 == 0);
+#endif
+ } else {
+ *psa = NULL;
+ }
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (error);
+}
+
+#if defined(_WIN32) && !defined(__Userspace__)
+/*
+ * General routine to allocate a hash table with control of memory flags.
+ * is in 7.0 and beyond for sure :-)
+ */
+void *
+sctp_hashinit_flags(int elements, struct malloc_type *type,
+ u_long *hashmask, int flags)
+{
+ long hashsize;
+ LIST_HEAD(generic, generic) *hashtbl;
+ int i;
+
+
+ if (elements <= 0) {
+#ifdef INVARIANTS
+ panic("hashinit: bad elements");
+#else
+ SCTP_PRINTF("hashinit: bad elements?");
+ elements = 1;
+#endif
+ }
+ for (hashsize = 1; hashsize <= elements; hashsize <<= 1)
+ continue;
+ hashsize >>= 1;
+ if (flags & HASH_WAITOK)
+ hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl), type, M_WAITOK);
+ else if (flags & HASH_NOWAIT)
+ hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl), type, M_NOWAIT);
+ else {
+#ifdef INVARIANTS
+ panic("flag incorrect in hashinit_flags");
+#else
+ return (NULL);
+#endif
+ }
+
+ /* no memory? */
+ if (hashtbl == NULL)
+ return (NULL);
+
+ for (i = 0; i < hashsize; i++)
+ LIST_INIT(&hashtbl[i]);
+ *hashmask = hashsize - 1;
+ return (hashtbl);
+}
+#endif
+#else /* __Userspace__ ifdef above sctp_soreceive */
+/*
+ * __Userspace__ Defining sctp_hashinit_flags() and sctp_hashdestroy() for userland.
+ * NOTE: We don't want multiple definitions here. So sctp_hashinit_flags() above for
+ *__FreeBSD__ must be excluded.
+ *
+ */
+
+void *
+sctp_hashinit_flags(int elements, struct malloc_type *type,
+ u_long *hashmask, int flags)
+{
+ long hashsize;
+ LIST_HEAD(generic, generic) *hashtbl;
+ int i;
+
+ if (elements <= 0) {
+ SCTP_PRINTF("hashinit: bad elements?");
+#ifdef INVARIANTS
+ return (NULL);
+#else
+ elements = 1;
+#endif
+ }
+ for (hashsize = 1; hashsize <= elements; hashsize <<= 1)
+ continue;
+ hashsize >>= 1;
+ /*cannot use MALLOC here because it has to be declared or defined
+ using MALLOC_DECLARE or MALLOC_DEFINE first. */
+ if (flags & HASH_WAITOK)
+ hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl));
+ else if (flags & HASH_NOWAIT)
+ hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl));
+ else {
+#ifdef INVARIANTS
+ SCTP_PRINTF("flag incorrect in hashinit_flags.\n");
+#endif
+ return (NULL);
+ }
+
+ /* no memory? */
+ if (hashtbl == NULL)
+ return (NULL);
+
+ for (i = 0; i < hashsize; i++)
+ LIST_INIT(&hashtbl[i]);
+ *hashmask = hashsize - 1;
+ return (hashtbl);
+}
+
+void
+sctp_hashdestroy(void *vhashtbl, struct malloc_type *type, u_long hashmask)
+{
+ LIST_HEAD(generic, generic) *hashtbl, *hp;
+
+ hashtbl = vhashtbl;
+ for (hp = hashtbl; hp <= &hashtbl[hashmask]; hp++)
+ if (!LIST_EMPTY(hp)) {
+ SCTP_PRINTF("hashdestroy: hash not empty.\n");
+ return;
+ }
+ FREE(hashtbl, type);
+}
+
+void
+sctp_hashfreedestroy(void *vhashtbl, struct malloc_type *type, u_long hashmask)
+{
+ LIST_HEAD(generic, generic) *hashtbl/*, *hp*/;
+ /*
+ LIST_ENTRY(type) *start, *temp;
+ */
+ hashtbl = vhashtbl;
+ /* Apparently temp is not dynamically allocated, so attempts to
+ free it results in error.
+ for (hp = hashtbl; hp <= &hashtbl[hashmask]; hp++)
+ if (!LIST_EMPTY(hp)) {
+ start = LIST_FIRST(hp);
+ while (start != NULL) {
+ temp = start;
+ start = start->le_next;
+ SCTP_PRINTF("%s: %p \n", __func__, (void *)temp);
+ FREE(temp, type);
+ }
+ }
+ */
+ FREE(hashtbl, type);
+}
+
+#endif
+int
+sctp_connectx_helper_add(struct sctp_tcb *stcb, struct sockaddr *addr,
+ int totaddr, int *error)
+{
+ int added = 0;
+ int i;
+ struct sctp_inpcb *inp;
+ struct sockaddr *sa;
+ size_t incr = 0;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+
+ sa = addr;
+ inp = stcb->sctp_ep;
+ *error = 0;
+ for (i = 0; i < totaddr; i++) {
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ incr = sizeof(struct sockaddr_in);
+ sin = (struct sockaddr_in *)sa;
+ if ((sin->sin_addr.s_addr == INADDR_ANY) ||
+ (sin->sin_addr.s_addr == INADDR_BROADCAST) ||
+ IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_7);
+ *error = EINVAL;
+ goto out_now;
+ }
+ if (sctp_add_remote_addr(stcb, sa, NULL, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE,
+ SCTP_ADDR_IS_CONFIRMED)) {
+ /* assoc gone no un-lock */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ENOBUFS);
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_8);
+ *error = ENOBUFS;
+ goto out_now;
+ }
+ added++;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ incr = sizeof(struct sockaddr_in6);
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) ||
+ IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_9);
+ *error = EINVAL;
+ goto out_now;
+ }
+ if (sctp_add_remote_addr(stcb, sa, NULL, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE,
+ SCTP_ADDR_IS_CONFIRMED)) {
+ /* assoc gone no un-lock */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ENOBUFS);
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_10);
+ *error = ENOBUFS;
+ goto out_now;
+ }
+ added++;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ incr = sizeof(struct sockaddr_conn);
+ if (sctp_add_remote_addr(stcb, sa, NULL, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE,
+ SCTP_ADDR_IS_CONFIRMED)) {
+ /* assoc gone no un-lock */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTPUTIL, ENOBUFS);
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTPUTIL + SCTP_LOC_11);
+ *error = ENOBUFS;
+ goto out_now;
+ }
+ added++;
+ break;
+#endif
+ default:
+ break;
+ }
+ sa = (struct sockaddr *)((caddr_t)sa + incr);
+ }
+ out_now:
+ return (added);
+}
+
+int
+sctp_connectx_helper_find(struct sctp_inpcb *inp, struct sockaddr *addr,
+ unsigned int totaddr,
+ unsigned int *num_v4, unsigned int *num_v6,
+ unsigned int limit)
+{
+ struct sockaddr *sa;
+ struct sctp_tcb *stcb;
+ unsigned int incr, at, i;
+
+ at = 0;
+ sa = addr;
+ *num_v6 = *num_v4 = 0;
+ /* account and validate addresses */
+ if (totaddr == 0) {
+ return (EINVAL);
+ }
+ for (i = 0; i < totaddr; i++) {
+ if (at + sizeof(struct sockaddr) > limit) {
+ return (EINVAL);
+ }
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ incr = (unsigned int)sizeof(struct sockaddr_in);
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != incr) {
+ return (EINVAL);
+ }
+#endif
+ (*num_v4) += 1;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ incr = (unsigned int)sizeof(struct sockaddr_in6);
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != incr) {
+ return (EINVAL);
+ }
+#endif
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ /* Must be non-mapped for connectx */
+ return (EINVAL);
+ }
+ (*num_v6) += 1;
+ break;
+ }
+#endif
+ default:
+ return (EINVAL);
+ }
+ if ((at + incr) > limit) {
+ return (EINVAL);
+ }
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, sa, NULL, NULL, NULL);
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ return (EALREADY);
+ } else {
+ SCTP_INP_DECR_REF(inp);
+ }
+ at += incr;
+ sa = (struct sockaddr *)((caddr_t)sa + incr);
+ }
+ return (0);
+}
+
+/*
+ * sctp_bindx(ADD) for one address.
+ * assumes all arguments are valid/checked by caller.
+ */
+void
+sctp_bindx_add_address(struct socket *so, struct sctp_inpcb *inp,
+ struct sockaddr *sa, uint32_t vrf_id, int *error,
+ void *p)
+{
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+#ifdef INET
+ struct sockaddr_in *sinp;
+#endif
+ struct sockaddr *addr_to_use;
+ struct sctp_inpcb *lep;
+#ifdef SCTP_MVRF
+ int i;
+#endif
+ uint16_t port;
+
+ /* see if we're bound all already! */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#ifdef SCTP_MVRF
+ /* Is the VRF one we have */
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (vrf_id == inp->m_vrf_ids[i]) {
+ break;
+ }
+ }
+ if (i == inp->num_vrfs) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+ /* can only bind v6 on PF_INET6 sockets */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ sin6 = (struct sockaddr_in6 *)sa;
+ port = sin6->sin6_port;
+#ifdef INET
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ /* can't bind v4-mapped on PF_INET sockets */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ in6_sin6_2_sin(&sin, sin6);
+ addr_to_use = (struct sockaddr *)&sin;
+ } else {
+ addr_to_use = sa;
+ }
+#else
+ addr_to_use = sa;
+#endif
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ /* can't bind v4 on PF_INET sockets */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ sinp = (struct sockaddr_in *)sa;
+ port = sinp->sin_port;
+ addr_to_use = sa;
+ break;
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+#if !(defined(_WIN32) || defined(__Userspace__))
+ if (p == NULL) {
+ /* Can't get proc for Net/Open BSD */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ *error = sctp_inpcb_bind(so, addr_to_use, NULL, p);
+ return;
+ }
+ /* Validate the incoming port. */
+ if ((port != 0) && (port != inp->sctp_lport)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ lep = sctp_pcb_findep(addr_to_use, 1, 0, vrf_id);
+ if (lep == NULL) {
+ /* add the address */
+ *error = sctp_addr_mgmt_ep_sa(inp, addr_to_use,
+ SCTP_ADD_IP_ADDRESS, vrf_id);
+ } else {
+ if (lep != inp) {
+ *error = EADDRINUSE;
+ }
+ SCTP_INP_DECR_REF(lep);
+ }
+}
+
+/*
+ * sctp_bindx(DELETE) for one address.
+ * assumes all arguments are valid/checked by caller.
+ */
+void
+sctp_bindx_delete_address(struct sctp_inpcb *inp,
+ struct sockaddr *sa, uint32_t vrf_id, int *error)
+{
+ struct sockaddr *addr_to_use;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in6 *sin6;
+ struct sockaddr_in sin;
+#endif
+#ifdef SCTP_MVRF
+ int i;
+#endif
+
+ /* see if we're bound all already! */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#ifdef SCTP_MVRF
+ /* Is the VRF one we have */
+ for (i = 0; i < inp->num_vrfs; i++) {
+ if (vrf_id == inp->m_vrf_ids[i]) {
+ break;
+ }
+ }
+ if (i == inp->num_vrfs) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+ /* can only bind v6 on PF_INET6 sockets */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#ifdef INET
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ /* can't bind mapped-v4 on PF_INET sockets */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ in6_sin6_2_sin(&sin, sin6);
+ addr_to_use = (struct sockaddr *)&sin;
+ } else {
+ addr_to_use = sa;
+ }
+#else
+ addr_to_use = sa;
+#endif
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ /* can't bind v4 on PF_INET sockets */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ addr_to_use = sa;
+ break;
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+ /* No lock required mgmt_ep_sa does its own locking. */
+ *error = sctp_addr_mgmt_ep_sa(inp, addr_to_use, SCTP_DEL_IP_ADDRESS,
+ vrf_id);
+}
+
+/*
+ * returns the valid local address count for an assoc, taking into account
+ * all scoping rules
+ */
+int
+sctp_local_addr_count(struct sctp_tcb *stcb)
+{
+ int loopback_scope;
+#if defined(INET)
+ int ipv4_local_scope, ipv4_addr_legal;
+#endif
+#if defined(INET6)
+ int local_scope, site_scope, ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ int conn_addr_legal;
+#endif
+ struct sctp_vrf *vrf;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+ int count = 0;
+
+ /* Turn on all the appropriate scopes */
+ loopback_scope = stcb->asoc.scope.loopback_scope;
+#if defined(INET)
+ ipv4_local_scope = stcb->asoc.scope.ipv4_local_scope;
+ ipv4_addr_legal = stcb->asoc.scope.ipv4_addr_legal;
+#endif
+#if defined(INET6)
+ local_scope = stcb->asoc.scope.local_scope;
+ site_scope = stcb->asoc.scope.site_scope;
+ ipv6_addr_legal = stcb->asoc.scope.ipv6_addr_legal;
+#endif
+#if defined(__Userspace__)
+ conn_addr_legal = stcb->asoc.scope.conn_addr_legal;
+#endif
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(stcb->asoc.vrf_id);
+ if (vrf == NULL) {
+ /* no vrf, no addresses */
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (0);
+ }
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /*
+ * bound all case: go through all ifns on the vrf
+ */
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if ((loopback_scope == 0) &&
+ SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ if (sctp_is_addr_restricted(stcb, sctp_ifa))
+ continue;
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (ipv4_addr_legal) {
+ struct sockaddr_in *sin;
+
+ sin = &sctp_ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* skip unspecified addrs */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if ((ipv4_local_scope == 0) &&
+ (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) {
+ continue;
+ }
+ /* count this one */
+ count++;
+ } else {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (ipv6_addr_legal) {
+ struct sockaddr_in6 *sin6;
+
+#if defined(SCTP_EMBEDDED_V6_SCOPE) && !defined(SCTP_KAME)
+ struct sockaddr_in6 lsa6;
+#endif
+ sin6 = &sctp_ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (local_scope == 0)
+ continue;
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+ if (sin6->sin6_scope_id == 0) {
+#ifdef SCTP_KAME
+ if (sa6_recoverscope(sin6) != 0)
+ /*
+ * bad link
+ * local
+ * address
+ */
+ continue;
+#else
+ lsa6 = *sin6;
+ if (in6_recoverscope(&lsa6,
+ &lsa6.sin6_addr,
+ NULL))
+ /*
+ * bad link
+ * local
+ * address
+ */
+ continue;
+ sin6 = &lsa6;
+#endif /* SCTP_KAME */
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ }
+ if ((site_scope == 0) &&
+ (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ /* count this one */
+ count++;
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (conn_addr_legal) {
+ count++;
+ }
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ }
+ } else {
+ /*
+ * subset bound case
+ */
+ struct sctp_laddr *laddr;
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list,
+ sctp_nxt_addr) {
+ if (sctp_is_addr_restricted(stcb, laddr->ifa)) {
+ continue;
+ }
+ /* count this one */
+ count++;
+ }
+ }
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (count);
+}
+
+#if defined(SCTP_LOCAL_TRACE_BUF)
+
+void
+sctp_log_trace(uint32_t subsys, const char *str SCTP_UNUSED, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f)
+{
+ uint32_t saveindex, newindex;
+
+#if defined(_WIN32) && !defined(__Userspace__)
+ if (SCTP_BASE_SYSCTL(sctp_log) == NULL) {
+ return;
+ }
+ do {
+ saveindex = SCTP_BASE_SYSCTL(sctp_log)->index;
+ if (saveindex >= SCTP_MAX_LOGGING_SIZE) {
+ newindex = 1;
+ } else {
+ newindex = saveindex + 1;
+ }
+ } while (atomic_cmpset_int(&SCTP_BASE_SYSCTL(sctp_log)->index, saveindex, newindex) == 0);
+ if (saveindex >= SCTP_MAX_LOGGING_SIZE) {
+ saveindex = 0;
+ }
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].timestamp = SCTP_GET_CYCLECOUNT;
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].subsys = subsys;
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].params[0] = a;
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].params[1] = b;
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].params[2] = c;
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].params[3] = d;
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].params[4] = e;
+ SCTP_BASE_SYSCTL(sctp_log)->entry[saveindex].params[5] = f;
+#else
+ do {
+ saveindex = SCTP_BASE_SYSCTL(sctp_log).index;
+ if (saveindex >= SCTP_MAX_LOGGING_SIZE) {
+ newindex = 1;
+ } else {
+ newindex = saveindex + 1;
+ }
+ } while (atomic_cmpset_int(&SCTP_BASE_SYSCTL(sctp_log).index, saveindex, newindex) == 0);
+ if (saveindex >= SCTP_MAX_LOGGING_SIZE) {
+ saveindex = 0;
+ }
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].timestamp = SCTP_GET_CYCLECOUNT;
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].subsys = subsys;
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].params[0] = a;
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].params[1] = b;
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].params[2] = c;
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].params[3] = d;
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].params[4] = e;
+ SCTP_BASE_SYSCTL(sctp_log).entry[saveindex].params[5] = f;
+#endif
+}
+
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static bool
+sctp_recv_udp_tunneled_packet(struct mbuf *m, int off, struct inpcb *inp,
+ const struct sockaddr *sa SCTP_UNUSED, void *ctx SCTP_UNUSED)
+{
+ struct ip *iph;
+#ifdef INET6
+ struct ip6_hdr *ip6;
+#endif
+ struct mbuf *sp, *last;
+ struct udphdr *uhdr;
+ uint16_t port;
+
+ if ((m->m_flags & M_PKTHDR) == 0) {
+ /* Can't handle one that is not a pkt hdr */
+ goto out;
+ }
+ /* Pull the src port */
+ iph = mtod(m, struct ip *);
+ uhdr = (struct udphdr *)((caddr_t)iph + off);
+ port = uhdr->uh_sport;
+ /* Split out the mbuf chain. Leave the
+ * IP header in m, place the
+ * rest in the sp.
+ */
+ sp = m_split(m, off, M_NOWAIT);
+ if (sp == NULL) {
+ /* Gak, drop packet, we can't do a split */
+ goto out;
+ }
+ if (sp->m_pkthdr.len < sizeof(struct udphdr) + sizeof(struct sctphdr)) {
+ /* Gak, packet can't have an SCTP header in it - too small */
+ m_freem(sp);
+ goto out;
+ }
+ /* Now pull up the UDP header and SCTP header together */
+ sp = m_pullup(sp, sizeof(struct udphdr) + sizeof(struct sctphdr));
+ if (sp == NULL) {
+ /* Gak pullup failed */
+ goto out;
+ }
+ /* Trim out the UDP header */
+ m_adj(sp, sizeof(struct udphdr));
+
+ /* Now reconstruct the mbuf chain */
+ for (last = m; last->m_next; last = last->m_next);
+ last->m_next = sp;
+ m->m_pkthdr.len += sp->m_pkthdr.len;
+ /*
+ * The CSUM_DATA_VALID flags indicates that the HW checked the
+ * UDP checksum and it was valid.
+ * Since CSUM_DATA_VALID == CSUM_SCTP_VALID this would imply that
+ * the HW also verified the SCTP checksum. Therefore, clear the bit.
+ */
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp_recv_udp_tunneled_packet(): Packet of length %d received on %s with csum_flags 0x%b.\n",
+ m->m_pkthdr.len,
+ if_name(m->m_pkthdr.rcvif),
+ (int)m->m_pkthdr.csum_flags, CSUM_BITS);
+ m->m_pkthdr.csum_flags &= ~CSUM_DATA_VALID;
+ iph = mtod(m, struct ip *);
+ switch (iph->ip_v) {
+#ifdef INET
+ case IPVERSION:
+ iph->ip_len = htons(ntohs(iph->ip_len) - sizeof(struct udphdr));
+ sctp_input_with_port(m, off, port);
+ break;
+#endif
+#ifdef INET6
+ case IPV6_VERSION >> 4:
+ ip6 = mtod(m, struct ip6_hdr *);
+ ip6->ip6_plen = htons(ntohs(ip6->ip6_plen) - sizeof(struct udphdr));
+ sctp6_input_with_port(&m, &off, port);
+ break;
+#endif
+ default:
+ goto out;
+ break;
+ }
+ return (true);
+ out:
+ m_freem(m);
+
+ return (true);
+}
+
+#ifdef INET
+static void
+sctp_recv_icmp_tunneled_packet(udp_tun_icmp_param_t param)
+{
+ struct icmp *icmp = param.icmp;
+ struct ip *outer_ip, *inner_ip;
+ struct sctphdr *sh;
+ struct udphdr *udp;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sctp_init_chunk *ch;
+ struct sockaddr_in src, dst;
+ uint8_t type, code;
+
+ inner_ip = &icmp->icmp_ip;
+ outer_ip = (struct ip *)((caddr_t)icmp - sizeof(struct ip));
+ if (ntohs(outer_ip->ip_len) <
+ sizeof(struct ip) + 8 + (inner_ip->ip_hl << 2) + sizeof(struct udphdr) + 8) {
+ return;
+ }
+ udp = (struct udphdr *)((caddr_t)inner_ip + (inner_ip->ip_hl << 2));
+ sh = (struct sctphdr *)(udp + 1);
+ memset(&src, 0, sizeof(struct sockaddr_in));
+ src.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ src.sin_len = sizeof(struct sockaddr_in);
+#endif
+ src.sin_port = sh->src_port;
+ src.sin_addr = inner_ip->ip_src;
+ memset(&dst, 0, sizeof(struct sockaddr_in));
+ dst.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ dst.sin_len = sizeof(struct sockaddr_in);
+#endif
+ dst.sin_port = sh->dest_port;
+ dst.sin_addr = inner_ip->ip_dst;
+ /*
+ * 'dst' holds the dest of the packet that failed to be sent.
+ * 'src' holds our local endpoint address. Thus we reverse
+ * the dst and the src in the lookup.
+ */
+ inp = NULL;
+ net = NULL;
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
+ (struct sockaddr *)&src,
+ &inp, &net, 1,
+ SCTP_DEFAULT_VRFID);
+ if ((stcb != NULL) &&
+ (net != NULL) &&
+ (inp != NULL)) {
+ /* Check the UDP port numbers */
+ if ((udp->uh_dport != net->port) ||
+ (udp->uh_sport != htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port)))) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ /* Check the verification tag */
+ if (ntohl(sh->v_tag) != 0) {
+ /*
+ * This must be the verification tag used
+ * for sending out packets. We don't
+ * consider packets reflecting the
+ * verification tag.
+ */
+ if (ntohl(sh->v_tag) != stcb->asoc.peer_vtag) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ if (ntohs(outer_ip->ip_len) >=
+ sizeof(struct ip) +
+ 8 + (inner_ip->ip_hl << 2) + 8 + 20) {
+ /*
+ * In this case we can check if we
+ * got an INIT chunk and if the
+ * initiate tag matches.
+ */
+ ch = (struct sctp_init_chunk *)(sh + 1);
+ if ((ch->ch.chunk_type != SCTP_INITIATION) ||
+ (ntohl(ch->init.initiate_tag) != stcb->asoc.my_vtag)) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ }
+ type = icmp->icmp_type;
+ code = icmp->icmp_code;
+ if ((type == ICMP_UNREACH) &&
+ (code == ICMP_UNREACH_PORT)) {
+ code = ICMP_UNREACH_PROTOCOL;
+ }
+ sctp_notify(inp, stcb, net, type, code,
+ ntohs(inner_ip->ip_len),
+ (uint32_t)ntohs(icmp->icmp_nextmtu));
+#if defined(__Userspace__)
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (stcb->sctp_socket != NULL)) {
+ struct socket *upcall_socket;
+
+ upcall_socket = stcb->sctp_socket;
+ SOCK_LOCK(upcall_socket);
+ soref(upcall_socket);
+ SOCK_UNLOCK(upcall_socket);
+ if ((upcall_socket->so_upcall != NULL) &&
+ (upcall_socket->so_error != 0)) {
+ (*upcall_socket->so_upcall)(upcall_socket, upcall_socket->so_upcallarg, M_NOWAIT);
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(upcall_socket);
+ sorele(upcall_socket);
+ }
+#endif
+ } else {
+ if ((stcb == NULL) && (inp != NULL)) {
+ /* reduce ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+ return;
+}
+#endif
+
+#ifdef INET6
+static void
+sctp_recv_icmp6_tunneled_packet(udp_tun_icmp_param_t param)
+{
+ struct ip6ctlparam *ip6cp = param.ip6cp;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sctphdr sh;
+ struct udphdr udp;
+ struct sockaddr_in6 src, dst;
+ uint8_t type, code;
+
+ /*
+ * XXX: We assume that when IPV6 is non NULL, M and OFF are
+ * valid.
+ */
+ if (ip6cp->ip6c_m == NULL) {
+ return;
+ }
+ /* Check if we can safely examine the ports and the
+ * verification tag of the SCTP common header.
+ */
+ if (ip6cp->ip6c_m->m_pkthdr.len <
+ ip6cp->ip6c_off + sizeof(struct udphdr)+ offsetof(struct sctphdr, checksum)) {
+ return;
+ }
+ /* Copy out the UDP header. */
+ memset(&udp, 0, sizeof(struct udphdr));
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off,
+ sizeof(struct udphdr),
+ (caddr_t)&udp);
+ /* Copy out the port numbers and the verification tag. */
+ memset(&sh, 0, sizeof(struct sctphdr));
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off + sizeof(struct udphdr),
+ sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t),
+ (caddr_t)&sh);
+ memset(&src, 0, sizeof(struct sockaddr_in6));
+ src.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ src.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ src.sin6_port = sh.src_port;
+ src.sin6_addr = ip6cp->ip6c_ip6->ip6_src;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (in6_setscope(&src.sin6_addr, ip6cp->ip6c_m->m_pkthdr.rcvif, NULL) != 0) {
+ return;
+ }
+#endif
+ memset(&dst, 0, sizeof(struct sockaddr_in6));
+ dst.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ dst.sin6_port = sh.dest_port;
+ dst.sin6_addr = ip6cp->ip6c_ip6->ip6_dst;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (in6_setscope(&dst.sin6_addr, ip6cp->ip6c_m->m_pkthdr.rcvif, NULL) != 0) {
+ return;
+ }
+#endif
+ inp = NULL;
+ net = NULL;
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
+ (struct sockaddr *)&src,
+ &inp, &net, 1, SCTP_DEFAULT_VRFID);
+ if ((stcb != NULL) &&
+ (net != NULL) &&
+ (inp != NULL)) {
+ /* Check the UDP port numbers */
+ if ((udp.uh_dport != net->port) ||
+ (udp.uh_sport != htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port)))) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ /* Check the verification tag */
+ if (ntohl(sh.v_tag) != 0) {
+ /*
+ * This must be the verification tag used for
+ * sending out packets. We don't consider
+ * packets reflecting the verification tag.
+ */
+ if (ntohl(sh.v_tag) != stcb->asoc.peer_vtag) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (ip6cp->ip6c_m->m_pkthdr.len >=
+ ip6cp->ip6c_off + sizeof(struct udphdr) +
+ sizeof(struct sctphdr) +
+ sizeof(struct sctp_chunkhdr) +
+ offsetof(struct sctp_init, a_rwnd)) {
+ /*
+ * In this case we can check if we
+ * got an INIT chunk and if the
+ * initiate tag matches.
+ */
+ uint32_t initiate_tag;
+ uint8_t chunk_type;
+
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off +
+ sizeof(struct udphdr) +
+ sizeof(struct sctphdr),
+ sizeof(uint8_t),
+ (caddr_t)&chunk_type);
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off +
+ sizeof(struct udphdr) +
+ sizeof(struct sctphdr) +
+ sizeof(struct sctp_chunkhdr),
+ sizeof(uint32_t),
+ (caddr_t)&initiate_tag);
+ if ((chunk_type != SCTP_INITIATION) ||
+ (ntohl(initiate_tag) != stcb->asoc.my_vtag)) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+#else
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+#endif
+ }
+ type = ip6cp->ip6c_icmp6->icmp6_type;
+ code = ip6cp->ip6c_icmp6->icmp6_code;
+ if ((type == ICMP6_DST_UNREACH) &&
+ (code == ICMP6_DST_UNREACH_NOPORT)) {
+ type = ICMP6_PARAM_PROB;
+ code = ICMP6_PARAMPROB_NEXTHEADER;
+ }
+ sctp6_notify(inp, stcb, net, type, code,
+ ntohl(ip6cp->ip6c_icmp6->icmp6_mtu));
+#if defined(__Userspace__)
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (stcb->sctp_socket != NULL)) {
+ struct socket *upcall_socket;
+
+ upcall_socket = stcb->sctp_socket;
+ SOCK_LOCK(upcall_socket);
+ soref(upcall_socket);
+ SOCK_UNLOCK(upcall_socket);
+ if ((upcall_socket->so_upcall != NULL) &&
+ (upcall_socket->so_error != 0)) {
+ (*upcall_socket->so_upcall)(upcall_socket, upcall_socket->so_upcallarg, M_NOWAIT);
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(upcall_socket);
+ sorele(upcall_socket);
+ }
+#endif
+ } else {
+ if ((stcb == NULL) && (inp != NULL)) {
+ /* reduce inp's ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+}
+#endif
+
+void
+sctp_over_udp_stop(void)
+{
+ /*
+ * This function assumes sysctl caller holds sctp_sysctl_info_lock() for writing!
+ */
+#ifdef INET
+ if (SCTP_BASE_INFO(udp4_tun_socket) != NULL) {
+ soclose(SCTP_BASE_INFO(udp4_tun_socket));
+ SCTP_BASE_INFO(udp4_tun_socket) = NULL;
+ }
+#endif
+#ifdef INET6
+ if (SCTP_BASE_INFO(udp6_tun_socket) != NULL) {
+ soclose(SCTP_BASE_INFO(udp6_tun_socket));
+ SCTP_BASE_INFO(udp6_tun_socket) = NULL;
+ }
+#endif
+}
+
+int
+sctp_over_udp_start(void)
+{
+ uint16_t port;
+ int ret;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+ /*
+ * This function assumes sysctl caller holds sctp_sysctl_info_lock() for writing!
+ */
+ port = SCTP_BASE_SYSCTL(sctp_udp_tunneling_port);
+ if (ntohs(port) == 0) {
+ /* Must have a port set */
+ return (EINVAL);
+ }
+#ifdef INET
+ if (SCTP_BASE_INFO(udp4_tun_socket) != NULL) {
+ /* Already running -- must stop first */
+ return (EALREADY);
+ }
+#endif
+#ifdef INET6
+ if (SCTP_BASE_INFO(udp6_tun_socket) != NULL) {
+ /* Already running -- must stop first */
+ return (EALREADY);
+ }
+#endif
+#ifdef INET
+ if ((ret = socreate(PF_INET, &SCTP_BASE_INFO(udp4_tun_socket),
+ SOCK_DGRAM, IPPROTO_UDP,
+ curthread->td_ucred, curthread))) {
+ sctp_over_udp_stop();
+ return (ret);
+ }
+ /* Call the special UDP hook. */
+ if ((ret = udp_set_kernel_tunneling(SCTP_BASE_INFO(udp4_tun_socket),
+ sctp_recv_udp_tunneled_packet,
+ sctp_recv_icmp_tunneled_packet,
+ NULL))) {
+ sctp_over_udp_stop();
+ return (ret);
+ }
+ /* Ok, we have a socket, bind it to the port. */
+ memset(&sin, 0, sizeof(struct sockaddr_in));
+ sin.sin_len = sizeof(struct sockaddr_in);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ if ((ret = sobind(SCTP_BASE_INFO(udp4_tun_socket),
+ (struct sockaddr *)&sin, curthread))) {
+ sctp_over_udp_stop();
+ return (ret);
+ }
+#endif
+#ifdef INET6
+ if ((ret = socreate(PF_INET6, &SCTP_BASE_INFO(udp6_tun_socket),
+ SOCK_DGRAM, IPPROTO_UDP,
+ curthread->td_ucred, curthread))) {
+ sctp_over_udp_stop();
+ return (ret);
+ }
+ /* Call the special UDP hook. */
+ if ((ret = udp_set_kernel_tunneling(SCTP_BASE_INFO(udp6_tun_socket),
+ sctp_recv_udp_tunneled_packet,
+ sctp_recv_icmp6_tunneled_packet,
+ NULL))) {
+ sctp_over_udp_stop();
+ return (ret);
+ }
+ /* Ok, we have a socket, bind it to the port. */
+ memset(&sin6, 0, sizeof(struct sockaddr_in6));
+ sin6.sin6_len = sizeof(struct sockaddr_in6);
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(port);
+ if ((ret = sobind(SCTP_BASE_INFO(udp6_tun_socket),
+ (struct sockaddr *)&sin6, curthread))) {
+ sctp_over_udp_stop();
+ return (ret);
+ }
+#endif
+ return (0);
+}
+#endif
+
+/*
+ * sctp_min_mtu ()returns the minimum of all non-zero arguments.
+ * If all arguments are zero, zero is returned.
+ */
+uint32_t
+sctp_min_mtu(uint32_t mtu1, uint32_t mtu2, uint32_t mtu3)
+{
+ if (mtu1 > 0) {
+ if (mtu2 > 0) {
+ if (mtu3 > 0) {
+ return (min(mtu1, min(mtu2, mtu3)));
+ } else {
+ return (min(mtu1, mtu2));
+ }
+ } else {
+ if (mtu3 > 0) {
+ return (min(mtu1, mtu3));
+ } else {
+ return (mtu1);
+ }
+ }
+ } else {
+ if (mtu2 > 0) {
+ if (mtu3 > 0) {
+ return (min(mtu2, mtu3));
+ } else {
+ return (mtu2);
+ }
+ } else {
+ return (mtu3);
+ }
+ }
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+void
+sctp_hc_set_mtu(union sctp_sockstore *addr, uint16_t fibnum, uint32_t mtu)
+{
+ struct in_conninfo inc;
+
+ memset(&inc, 0, sizeof(struct in_conninfo));
+ inc.inc_fibnum = fibnum;
+ switch (addr->sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ inc.inc_faddr = addr->sin.sin_addr;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ inc.inc_flags |= INC_ISIPV6;
+ inc.inc6_faddr = addr->sin6.sin6_addr;
+ break;
+#endif
+ default:
+ return;
+ }
+ tcp_hc_updatemtu(&inc, (u_long)mtu);
+}
+
+uint32_t
+sctp_hc_get_mtu(union sctp_sockstore *addr, uint16_t fibnum)
+{
+ struct in_conninfo inc;
+
+ memset(&inc, 0, sizeof(struct in_conninfo));
+ inc.inc_fibnum = fibnum;
+ switch (addr->sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ inc.inc_faddr = addr->sin.sin_addr;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ inc.inc_flags |= INC_ISIPV6;
+ inc.inc6_faddr = addr->sin6.sin6_addr;
+ break;
+#endif
+ default:
+ return (0);
+ }
+ return ((uint32_t)tcp_hc_getmtu(&inc));
+}
+#endif
+
+void
+sctp_set_state(struct sctp_tcb *stcb, int new_state)
+{
+#if defined(KDTRACE_HOOKS)
+ int old_state = stcb->asoc.state;
+#endif
+
+ KASSERT((new_state & ~SCTP_STATE_MASK) == 0,
+ ("sctp_set_state: Can't set substate (new_state = %x)",
+ new_state));
+ stcb->asoc.state = (stcb->asoc.state & ~SCTP_STATE_MASK) | new_state;
+ if ((new_state == SCTP_STATE_SHUTDOWN_RECEIVED) ||
+ (new_state == SCTP_STATE_SHUTDOWN_SENT) ||
+ (new_state == SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING);
+ }
+#if defined(KDTRACE_HOOKS)
+ if (((old_state & SCTP_STATE_MASK) != new_state) &&
+ !(((old_state & SCTP_STATE_MASK) == SCTP_STATE_EMPTY) &&
+ (new_state == SCTP_STATE_INUSE))) {
+ SCTP_PROBE6(state__change, NULL, stcb, NULL, stcb, NULL, old_state);
+ }
+#endif
+}
+
+void
+sctp_add_substate(struct sctp_tcb *stcb, int substate)
+{
+#if defined(KDTRACE_HOOKS)
+ int old_state = stcb->asoc.state;
+#endif
+
+ KASSERT((substate & SCTP_STATE_MASK) == 0,
+ ("sctp_add_substate: Can't set state (substate = %x)",
+ substate));
+ stcb->asoc.state |= substate;
+#if defined(KDTRACE_HOOKS)
+ if (((substate & SCTP_STATE_ABOUT_TO_BE_FREED) &&
+ ((old_state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0)) ||
+ ((substate & SCTP_STATE_SHUTDOWN_PENDING) &&
+ ((old_state & SCTP_STATE_SHUTDOWN_PENDING) == 0))) {
+ SCTP_PROBE6(state__change, NULL, stcb, NULL, stcb, NULL, old_state);
+ }
+#endif
+}
+
diff --git a/netwerk/sctp/src/netinet/sctputil.h b/netwerk/sctp/src/netinet/sctputil.h
new file mode 100644
index 0000000000..5e0fc81f9a
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctputil.h
@@ -0,0 +1,375 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET_SCTP_UTIL_H_
+#define _NETINET_SCTP_UTIL_H_
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+#define SCTP_READ_LOCK_HELD 1
+#define SCTP_READ_LOCK_NOT_HELD 0
+
+#ifdef SCTP_ASOCLOG_OF_TSNS
+void sctp_print_out_track_log(struct sctp_tcb *stcb);
+#endif
+
+#ifdef SCTP_MBUF_LOGGING
+struct mbuf *sctp_m_free(struct mbuf *m);
+void sctp_m_freem(struct mbuf *m);
+#else
+#define sctp_m_free m_free
+#define sctp_m_freem m_freem
+#endif
+
+#if defined(SCTP_LOCAL_TRACE_BUF)
+void
+sctp_log_trace(uint32_t fr, const char *str SCTP_UNUSED, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f);
+#endif
+
+#define sctp_get_associd(stcb) ((sctp_assoc_t)stcb->asoc.assoc_id)
+
+/*
+ * Function prototypes
+ */
+int32_t
+sctp_map_assoc_state(int);
+
+uint32_t
+sctp_get_ifa_hash_val(struct sockaddr *addr);
+
+struct sctp_ifa *
+sctp_find_ifa_in_ep(struct sctp_inpcb *inp, struct sockaddr *addr, int hold_lock);
+
+struct sctp_ifa *
+sctp_find_ifa_by_addr(struct sockaddr *addr, uint32_t vrf_id, int holds_lock);
+
+uint32_t sctp_select_initial_TSN(struct sctp_pcb *);
+
+uint32_t sctp_select_a_tag(struct sctp_inpcb *, uint16_t lport, uint16_t rport, int);
+
+int sctp_init_asoc(struct sctp_inpcb *, struct sctp_tcb *, uint32_t, uint32_t, uint32_t, uint16_t);
+
+void sctp_fill_random_store(struct sctp_pcb *);
+
+/*
+ * NOTE: sctp_timer_start() will increment the reference count of any relevant
+ * structure the timer is referencing, in order to prevent a race condition
+ * between the timer executing and the structure being freed.
+ *
+ * When the timer fires or sctp_timer_stop() is called, these references are
+ * removed.
+ */
+void
+sctp_timer_start(int, struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+void
+sctp_timer_stop(int, struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *, uint32_t);
+
+int
+sctp_dynamic_set_primary(struct sockaddr *sa, uint32_t vrf_id);
+
+void
+sctp_wakeup_the_read_socket(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ int so_locked
+#if !(defined(__APPLE__) && !defined(__Userspace__))
+ SCTP_UNUSED
+#endif
+);
+
+#if defined(__Userspace__)
+void sctp_invoke_recv_callback(struct sctp_inpcb *,
+ struct sctp_tcb *,
+ struct sctp_queued_to_read *,
+ int);
+
+#endif
+void
+sctp_add_to_readq(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_queued_to_read *control,
+ struct sockbuf *sb,
+ int end,
+ int inpread_locked,
+ int so_locked);
+
+void sctp_iterator_worker(void);
+
+uint32_t sctp_get_prev_mtu(uint32_t);
+uint32_t sctp_get_next_mtu(uint32_t);
+
+void
+sctp_timeout_handler(void *);
+
+int
+sctp_calculate_rto(struct sctp_tcb *, struct sctp_association *,
+ struct sctp_nets *, struct timeval *, int);
+
+uint32_t sctp_calculate_len(struct mbuf *);
+
+caddr_t sctp_m_getptr(struct mbuf *, int, int, uint8_t *);
+
+struct sctp_paramhdr *
+sctp_get_next_param(struct mbuf *, int,
+ struct sctp_paramhdr *, int);
+
+struct mbuf *
+sctp_add_pad_tombuf(struct mbuf *, int);
+
+struct mbuf *
+sctp_pad_lastmbuf(struct mbuf *, int, struct mbuf *);
+
+void sctp_ulp_notify(uint32_t, struct sctp_tcb *, uint32_t, void *, int);
+
+void
+sctp_pull_off_control_to_new_inp(struct sctp_inpcb *old_inp,
+ struct sctp_inpcb *new_inp,
+ struct sctp_tcb *stcb, int waitflags);
+
+void sctp_stop_timers_for_shutdown(struct sctp_tcb *);
+
+/* Stop all timers for association and remote addresses. */
+void sctp_stop_association_timers(struct sctp_tcb *, bool);
+
+void sctp_report_all_outbound(struct sctp_tcb *, uint16_t, int);
+
+int sctp_expand_mapping_array(struct sctp_association *, uint32_t);
+
+void sctp_abort_notification(struct sctp_tcb *, bool, bool, uint16_t,
+ struct sctp_abort_chunk *, int);
+
+/* We abort responding to an IP packet for some reason */
+void
+sctp_abort_association(struct sctp_inpcb *, struct sctp_tcb *, struct mbuf *,
+ int, struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, struct mbuf *,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t, uint32_t,
+#endif
+ uint32_t, uint16_t);
+
+/* We choose to abort via user input */
+void
+sctp_abort_an_association(struct sctp_inpcb *, struct sctp_tcb *,
+ struct mbuf *, bool, int);
+
+void sctp_handle_ootb(struct mbuf *, int, int,
+ struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, struct sctp_inpcb *,
+ struct mbuf *,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ uint8_t, uint32_t, uint16_t,
+#endif
+ uint32_t, uint16_t);
+
+int sctp_connectx_helper_add(struct sctp_tcb *stcb, struct sockaddr *addr,
+ int totaddr, int *error);
+
+int
+sctp_connectx_helper_find(struct sctp_inpcb *, struct sockaddr *,
+ unsigned int, unsigned int *, unsigned int *, unsigned int);
+
+int sctp_is_there_an_abort_here(struct mbuf *, int, uint32_t *);
+#ifdef INET6
+uint32_t sctp_is_same_scope(struct sockaddr_in6 *, struct sockaddr_in6 *);
+
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+struct sockaddr_in6 *
+sctp_recover_scope(struct sockaddr_in6 *, struct sockaddr_in6 *);
+
+#ifdef SCTP_KAME
+#define sctp_recover_scope_mac(addr, store) do { \
+ if ((addr->sin6_family == AF_INET6) && \
+ (IN6_IS_SCOPE_LINKLOCAL(&addr->sin6_addr))) { \
+ *store = *addr; \
+ if (addr->sin6_scope_id == 0) { \
+ if (!sa6_recoverscope(store)) { \
+ addr = store; \
+ } \
+ } else { \
+ in6_clearscope(&addr->sin6_addr); \
+ addr = store; \
+ } \
+ } \
+} while (0)
+#else
+#define sctp_recover_scope_mac(addr, store) do { \
+ if ((addr->sin6_family == AF_INET6) && \
+ (IN6_IS_SCOPE_LINKLOCAL(&addr->sin6_addr))) { \
+ *store = *addr; \
+ if (addr->sin6_scope_id == 0) { \
+ if (!in6_recoverscope(store, &store->sin6_addr, \
+ NULL)) { \
+ addr = store; \
+ } \
+ } else { \
+ in6_clearscope(&addr->sin6_addr); \
+ addr = store; \
+ } \
+ } \
+} while (0)
+#endif
+#endif
+#endif
+
+int sctp_cmpaddr(struct sockaddr *, struct sockaddr *);
+
+void sctp_print_address(struct sockaddr *);
+
+int
+sctp_release_pr_sctp_chunk(struct sctp_tcb *, struct sctp_tmit_chunk *,
+ uint8_t, int);
+
+struct mbuf *sctp_generate_cause(uint16_t, char *);
+struct mbuf *sctp_generate_no_user_data_cause(uint32_t);
+
+void sctp_bindx_add_address(struct socket *so, struct sctp_inpcb *inp,
+ struct sockaddr *sa, uint32_t vrf_id, int *error,
+ void *p);
+void sctp_bindx_delete_address(struct sctp_inpcb *inp, struct sockaddr *sa,
+ uint32_t vrf_id, int *error);
+
+int sctp_local_addr_count(struct sctp_tcb *stcb);
+
+void
+sctp_free_bufspace(struct sctp_tcb *, struct sctp_association *,
+ struct sctp_tmit_chunk *, int);
+
+#define sctp_free_spbufspace(stcb, asoc, sp) \
+do { \
+ if (sp->data != NULL) { \
+ if ((asoc)->total_output_queue_size >= sp->length) { \
+ atomic_subtract_int(&(asoc)->total_output_queue_size, sp->length); \
+ } else { \
+ (asoc)->total_output_queue_size = 0; \
+ } \
+ if (stcb->sctp_socket && ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || \
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { \
+ SCTP_SB_DECR(&stcb->sctp_socket->so_snd, sp->length); \
+ } \
+ } \
+} while (0)
+
+#define sctp_snd_sb_alloc(stcb, sz) \
+do { \
+ atomic_add_int(&stcb->asoc.total_output_queue_size,sz); \
+ if ((stcb->sctp_socket != NULL) && \
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || \
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { \
+ SCTP_SB_INCR(&stcb->sctp_socket->so_snd, sz); \
+ } \
+} while (0)
+
+/* functions to start/stop udp tunneling */
+#if (defined(__APPLE__) || defined(__FreeBSD__)) && !defined(__Userspace__)
+void sctp_over_udp_stop(void);
+int sctp_over_udp_start(void);
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+void sctp_over_udp_restart(void);
+#endif
+
+int
+sctp_soreceive(struct socket *so, struct sockaddr **psa,
+ struct uio *uio,
+ struct mbuf **mp0,
+ struct mbuf **controlp,
+ int *flagsp);
+
+void
+sctp_misc_ints(uint8_t from, uint32_t a, uint32_t b, uint32_t c, uint32_t d);
+
+void
+sctp_wakeup_log(struct sctp_tcb *stcb,
+ uint32_t wake_cnt, int from);
+
+void sctp_log_strm_del_alt(struct sctp_tcb *stcb, uint32_t, uint16_t, uint16_t, int);
+
+void sctp_log_nagle_event(struct sctp_tcb *stcb, int action);
+
+#ifdef SCTP_MBUF_LOGGING
+void
+sctp_log_mb(struct mbuf *m, int from);
+
+void
+sctp_log_mbc(struct mbuf *m, int from);
+#endif
+
+void
+sctp_sblog(struct sockbuf *sb,
+ struct sctp_tcb *stcb, int from, int incr);
+
+void
+sctp_log_strm_del(struct sctp_queued_to_read *control,
+ struct sctp_queued_to_read *poschk,
+ int from);
+void sctp_log_cwnd(struct sctp_tcb *stcb, struct sctp_nets *, int, uint8_t);
+void rto_logging(struct sctp_nets *net, int from);
+
+void sctp_log_closing(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int16_t loc);
+
+void sctp_log_lock(struct sctp_inpcb *inp, struct sctp_tcb *stcb, uint8_t from);
+void sctp_log_maxburst(struct sctp_tcb *stcb, struct sctp_nets *, int, int, uint8_t);
+void sctp_log_block(uint8_t, struct sctp_association *, ssize_t);
+void sctp_log_rwnd(uint8_t, uint32_t, uint32_t, uint32_t);
+void sctp_log_rwnd_set(uint8_t, uint32_t, uint32_t, uint32_t, uint32_t);
+int sctp_fill_stat_log(void *, size_t *);
+void sctp_log_fr(uint32_t, uint32_t, uint32_t, int);
+void sctp_log_sack(uint32_t, uint32_t, uint32_t, uint16_t, uint16_t, int);
+void sctp_log_map(uint32_t, uint32_t, uint32_t, int);
+void sctp_print_mapping_array(struct sctp_association *asoc);
+void sctp_clr_stat_log(void);
+
+#ifdef SCTP_AUDITING_ENABLED
+void
+sctp_auditing(int, struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+void sctp_audit_log(uint8_t, uint8_t);
+
+#endif
+uint32_t sctp_min_mtu(uint32_t, uint32_t, uint32_t);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+void sctp_hc_set_mtu(union sctp_sockstore *, uint16_t, uint32_t);
+uint32_t sctp_hc_get_mtu(union sctp_sockstore *, uint16_t);
+#endif
+void sctp_set_state(struct sctp_tcb *, int);
+void sctp_add_substate(struct sctp_tcb *, int);
+uint32_t sctp_ticks_to_msecs(uint32_t);
+uint32_t sctp_msecs_to_ticks(uint32_t);
+uint32_t sctp_ticks_to_secs(uint32_t);
+uint32_t sctp_secs_to_ticks(uint32_t);
+
+#endif /* _KERNEL */
+#endif
diff --git a/netwerk/sctp/src/netinet6/sctp6_usrreq.c b/netwerk/sctp/src/netinet6/sctp6_usrreq.c
new file mode 100644
index 0000000000..e130b41aca
--- /dev/null
+++ b/netwerk/sctp/src/netinet6/sctp6_usrreq.c
@@ -0,0 +1,1745 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <netinet/sctp_os.h>
+#ifdef INET6
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/proc.h>
+#endif
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_var.h>
+#include <netinet6/sctp6_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_bsd_addr.h>
+#include <netinet/sctp_crc32.h>
+#if !defined(_WIN32)
+#include <netinet/icmp6.h>
+#include <netinet/udp.h>
+#endif
+#if defined(__Userspace__)
+int ip6_v6only=0;
+#endif
+#if defined(__Userspace__)
+#ifdef INET
+void
+in6_sin6_2_sin(struct sockaddr_in *sin, struct sockaddr_in6 *sin6)
+{
+#if defined(_WIN32)
+ uint32_t temp;
+#endif
+ memset(sin, 0, sizeof(*sin));
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_family = AF_INET;
+ sin->sin_port = sin6->sin6_port;
+#if defined(_WIN32)
+ temp = sin6->sin6_addr.s6_addr16[7];
+ temp = temp << 16;
+ temp = temp | sin6->sin6_addr.s6_addr16[6];
+ sin->sin_addr.s_addr = temp;
+#else
+ sin->sin_addr.s_addr = sin6->sin6_addr.s6_addr32[3];
+#endif
+}
+
+void
+in6_sin6_2_sin_in_sock(struct sockaddr *nam)
+{
+ struct sockaddr_in *sin_p;
+ struct sockaddr_in6 sin6;
+
+ /* save original sockaddr_in6 addr and convert it to sockaddr_in */
+ sin6 = *(struct sockaddr_in6 *)nam;
+ sin_p = (struct sockaddr_in *)nam;
+ in6_sin6_2_sin(sin_p, &sin6);
+}
+
+void
+in6_sin_2_v4mapsin6(const struct sockaddr_in *sin, struct sockaddr_in6 *sin6)
+{
+ memset(sin6, 0, sizeof(struct sockaddr_in6));
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ sin6->sin6_port = sin->sin_port;
+#if defined(_WIN32)
+ ((uint32_t *)&sin6->sin6_addr)[0] = 0;
+ ((uint32_t *)&sin6->sin6_addr)[1] = 0;
+ ((uint32_t *)&sin6->sin6_addr)[2] = htonl(0xffff);
+ ((uint32_t *)&sin6->sin6_addr)[3] = sin->sin_addr.s_addr;
+#else
+ sin6->sin6_addr.s6_addr32[0] = 0;
+ sin6->sin6_addr.s6_addr32[1] = 0;
+ sin6->sin6_addr.s6_addr32[2] = htonl(0xffff);
+ sin6->sin6_addr.s6_addr32[3] = sin->sin_addr.s_addr;
+#endif
+}
+#endif
+#endif
+
+#if !defined(__Userspace__)
+int
+#if defined(__APPLE__) || defined(__FreeBSD__)
+sctp6_input_with_port(struct mbuf **i_pak, int *offp, uint16_t port)
+#else
+sctp6_input(struct mbuf **i_pak, int *offp, int proto)
+#endif
+{
+ struct mbuf *m;
+ int iphlen;
+ uint32_t vrf_id;
+ uint8_t ecn_bits;
+ struct sockaddr_in6 src, dst;
+ struct ip6_hdr *ip6;
+ struct sctphdr *sh;
+ struct sctp_chunkhdr *ch;
+ int length, offset;
+ uint8_t compute_crc;
+#if defined(__FreeBSD__)
+ uint32_t mflowid;
+ uint8_t mflowtype;
+ uint16_t fibnum;
+#endif
+#if !(defined(__APPLE__) || defined(__FreeBSD__))
+ uint16_t port = 0;
+#endif
+
+ iphlen = *offp;
+ if (SCTP_GET_PKT_VRFID(*i_pak, vrf_id)) {
+ SCTP_RELEASE_PKT(*i_pak);
+ return (IPPROTO_DONE);
+ }
+ m = SCTP_HEADER_TO_CHAIN(*i_pak);
+#ifdef SCTP_MBUF_LOGGING
+ /* Log in any input mbufs */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(m, SCTP_MBUF_INPUT);
+ }
+#endif
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING) {
+ sctp_packet_log(m);
+ }
+#endif
+#if defined(__FreeBSD__)
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp6_input(): Packet of length %d received on %s with csum_flags 0x%b.\n",
+ m->m_pkthdr.len,
+ if_name(m->m_pkthdr.rcvif),
+ (int)m->m_pkthdr.csum_flags, CSUM_BITS);
+#endif
+#if defined(__APPLE__)
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp6_input(): Packet of length %d received on %s%d with csum_flags 0x%x.\n",
+ m->m_pkthdr.len,
+ m->m_pkthdr.rcvif->if_name,
+ m->m_pkthdr.rcvif->if_unit,
+ m->m_pkthdr.csum_flags);
+#endif
+#if defined(_WIN32) && !defined(__Userspace__)
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp6_input(): Packet of length %d received on %s with csum_flags 0x%x.\n",
+ m->m_pkthdr.len,
+ m->m_pkthdr.rcvif->if_xname,
+ m->m_pkthdr.csum_flags);
+#endif
+#if defined(__FreeBSD__)
+ mflowid = m->m_pkthdr.flowid;
+ mflowtype = M_HASHTYPE_GET(m);
+ fibnum = M_GETFIB(m);
+#endif
+ SCTP_STAT_INCR(sctps_recvpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+ /* Get IP, SCTP, and first chunk header together in the first mbuf. */
+ offset = iphlen + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ if (m->m_len < offset) {
+ m = m_pullup(m, offset);
+ if (m == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ return (IPPROTO_DONE);
+ }
+ }
+ ip6 = mtod(m, struct ip6_hdr *);
+ sh = (struct sctphdr *)(mtod(m, caddr_t) + iphlen);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset -= sizeof(struct sctp_chunkhdr);
+ memset(&src, 0, sizeof(struct sockaddr_in6));
+ src.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ src.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ src.sin6_port = sh->src_port;
+ src.sin6_addr = ip6->ip6_src;
+#if defined(__FreeBSD__)
+#if defined(__APPLE__)
+ /* XXX: This code should also be used on Apple */
+#endif
+ if (in6_setscope(&src.sin6_addr, m->m_pkthdr.rcvif, NULL) != 0) {
+ goto out;
+ }
+#endif
+ memset(&dst, 0, sizeof(struct sockaddr_in6));
+ dst.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ dst.sin6_port = sh->dest_port;
+ dst.sin6_addr = ip6->ip6_dst;
+#if defined(__FreeBSD__)
+#if defined(__APPLE__)
+ /* XXX: This code should also be used on Apple */
+#endif
+ if (in6_setscope(&dst.sin6_addr, m->m_pkthdr.rcvif, NULL) != 0) {
+ goto out;
+ }
+#endif
+#if defined(__APPLE__)
+#if defined(NFAITH) && 0 < NFAITH
+ if (faithprefix(&dst.sin6_addr)) {
+ goto out;
+ }
+#endif
+#endif
+ length = ntohs(ip6->ip6_plen) + iphlen;
+ /* Validate mbuf chain length with IP payload length. */
+ if (SCTP_HEADER_LEN(m) != length) {
+ SCTPDBG(SCTP_DEBUG_INPUT1,
+ "sctp6_input() length:%d reported length:%d\n", length, SCTP_HEADER_LEN(m));
+ SCTP_STAT_INCR(sctps_hdrops);
+ goto out;
+ }
+ if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+ goto out;
+ }
+#if defined(__FreeBSD__)
+ ecn_bits = IPV6_TRAFFIC_CLASS(ip6);
+ if (m->m_pkthdr.csum_flags & CSUM_SCTP_VALID) {
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ compute_crc = 0;
+ } else {
+#else
+ ecn_bits = ((ntohl(ip6->ip6_flow) >> 20) & 0x000000ff);
+ if (SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ (IN6_ARE_ADDR_EQUAL(&src.sin6_addr, &dst.sin6_addr))) {
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ compute_crc = 0;
+ } else {
+#endif
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ compute_crc = 1;
+ }
+ sctp_common_input_processing(&m, iphlen, offset, length,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+ compute_crc,
+ ecn_bits,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid, fibnum,
+#endif
+ vrf_id, port);
+ out:
+ if (m) {
+ sctp_m_freem(m);
+ }
+ return (IPPROTO_DONE);
+}
+
+#if defined(__APPLE__)
+int
+sctp6_input(struct mbuf **i_pak, int *offp)
+{
+ return (sctp6_input_with_port(i_pak, offp, 0));
+}
+#endif
+#if defined(__FreeBSD__)
+int
+sctp6_input(struct mbuf **i_pak, int *offp, int proto SCTP_UNUSED)
+{
+ return (sctp6_input_with_port(i_pak, offp, 0));
+}
+#endif
+
+void
+sctp6_notify(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ uint8_t icmp6_type,
+ uint8_t icmp6_code,
+ uint32_t next_mtu)
+{
+#if defined(__APPLE__)
+ struct socket *so;
+#endif
+ int timer_stopped;
+
+ switch (icmp6_type) {
+ case ICMP6_DST_UNREACH:
+ if ((icmp6_code == ICMP6_DST_UNREACH_NOROUTE) ||
+ (icmp6_code == ICMP6_DST_UNREACH_ADMIN) ||
+ (icmp6_code == ICMP6_DST_UNREACH_BEYONDSCOPE) ||
+ (icmp6_code == ICMP6_DST_UNREACH_ADDR)) {
+ /* Mark the net unreachable. */
+ if (net->dest_state & SCTP_ADDR_REACHABLE) {
+ /* Ok that destination is not reachable */
+ net->dest_state &= ~SCTP_ADDR_REACHABLE;
+ net->dest_state &= ~SCTP_ADDR_PF;
+ sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN,
+ stcb, 0, (void *)net, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ case ICMP6_PARAM_PROB:
+ /* Treat it like an ABORT. */
+ if (icmp6_code == ICMP6_PARAMPROB_NEXTHEADER) {
+ sctp_abort_notification(stcb, true, false, 0, NULL, SCTP_SO_NOT_LOCKED);
+#if defined(__APPLE__)
+ so = SCTP_INP_SO(inp);
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_2);
+#if defined(__APPLE__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ break;
+ case ICMP6_PACKET_TOO_BIG:
+ if (net->dest_state & SCTP_ADDR_NO_PMTUD) {
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) {
+ timer_stopped = 1;
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ + SCTP_LOC_1);
+ } else {
+ timer_stopped = 0;
+ }
+ /* Update the path MTU. */
+ if (net->port) {
+ next_mtu -= sizeof(struct udphdr);
+ }
+ if (net->mtu > next_mtu) {
+ net->mtu = next_mtu;
+#if defined(__FreeBSD__)
+ if (net->port) {
+ sctp_hc_set_mtu(&net->ro._l_addr, inp->fibnum, next_mtu + sizeof(struct udphdr));
+ } else {
+ sctp_hc_set_mtu(&net->ro._l_addr, inp->fibnum, next_mtu);
+ }
+#endif
+ }
+ /* Update the association MTU */
+ if (stcb->asoc.smallest_mtu > next_mtu) {
+ sctp_pathmtu_adjustment(stcb, next_mtu, true);
+ }
+ /* Finally, start the PMTU timer if it was running before. */
+ if (timer_stopped) {
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ default:
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+}
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+void
+sctp6_ctlinput(struct ip6ctlparam *ip6cp)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sctphdr sh;
+ struct sockaddr_in6 src, dst;
+
+ if (icmp6_errmap(ip6cp->ip6c_icmp6) == 0) {
+ return;
+ }
+
+ /*
+ * Check if we can safely examine the ports and the
+ * verification tag of the SCTP common header.
+ */
+ if (ip6cp->ip6c_m->m_pkthdr.len <
+ (int32_t)(ip6cp->ip6c_off + offsetof(struct sctphdr, checksum))) {
+ return;
+ }
+
+ /* Copy out the port numbers and the verification tag. */
+ memset(&sh, 0, sizeof(sh));
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off,
+ sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t),
+ (caddr_t)&sh);
+ memset(&src, 0, sizeof(struct sockaddr_in6));
+ src.sin6_family = AF_INET6;
+ src.sin6_len = sizeof(struct sockaddr_in6);
+ src.sin6_port = sh.src_port;
+ src.sin6_addr = ip6cp->ip6c_ip6->ip6_src;
+ if (in6_setscope(&src.sin6_addr, ip6cp->ip6c_m->m_pkthdr.rcvif, NULL) != 0) {
+ return;
+ }
+ memset(&dst, 0, sizeof(struct sockaddr_in6));
+ dst.sin6_family = AF_INET6;
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+ dst.sin6_port = sh.dest_port;
+ dst.sin6_addr = ip6cp->ip6c_ip6->ip6_dst;
+ if (in6_setscope(&dst.sin6_addr, ip6cp->ip6c_m->m_pkthdr.rcvif, NULL) != 0) {
+ return;
+ }
+ inp = NULL;
+ net = NULL;
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
+ (struct sockaddr *)&src,
+ &inp, &net, 1, SCTP_DEFAULT_VRFID);
+ if ((stcb != NULL) &&
+ (net != NULL) &&
+ (inp != NULL)) {
+ /* Check the verification tag */
+ if (ntohl(sh.v_tag) != 0) {
+ /*
+ * This must be the verification tag used for
+ * sending out packets. We don't consider
+ * packets reflecting the verification tag.
+ */
+ if (ntohl(sh.v_tag) != stcb->asoc.peer_vtag) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ if (ip6cp->ip6c_m->m_pkthdr.len >=
+ ip6cp->ip6c_off + sizeof(struct sctphdr) +
+ sizeof(struct sctp_chunkhdr) +
+ offsetof(struct sctp_init, a_rwnd)) {
+ /*
+ * In this case we can check if we
+ * got an INIT chunk and if the
+ * initiate tag matches.
+ */
+ uint32_t initiate_tag;
+ uint8_t chunk_type;
+
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off +
+ sizeof(struct sctphdr),
+ sizeof(uint8_t),
+ (caddr_t)&chunk_type);
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off +
+ sizeof(struct sctphdr) +
+ sizeof(struct sctp_chunkhdr),
+ sizeof(uint32_t),
+ (caddr_t)&initiate_tag);
+ if ((chunk_type != SCTP_INITIATION) ||
+ (ntohl(initiate_tag) != stcb->asoc.my_vtag)) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ }
+ sctp6_notify(inp, stcb, net,
+ ip6cp->ip6c_icmp6->icmp6_type,
+ ip6cp->ip6c_icmp6->icmp6_code,
+ ntohl(ip6cp->ip6c_icmp6->icmp6_mtu));
+ } else {
+ if ((stcb == NULL) && (inp != NULL)) {
+ /* reduce inp's ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+}
+#else
+void
+#if defined(__APPLE__) && !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION) && !defined(APPLE_ELCAPITAN)
+sctp6_ctlinput(int cmd, struct sockaddr *pktdst, void *d, struct ifnet *ifp SCTP_UNUSED)
+#else
+sctp6_ctlinput(int cmd, struct sockaddr *pktdst, void *d)
+#endif
+{
+ struct ip6ctlparam *ip6cp;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sctphdr sh;
+ struct sockaddr_in6 src, dst;
+
+#ifdef HAVE_SA_LEN
+ if (pktdst->sa_family != AF_INET6 ||
+ pktdst->sa_len != sizeof(struct sockaddr_in6)) {
+#else
+ if (pktdst->sa_family != AF_INET6) {
+#endif
+ return;
+ }
+
+ if ((unsigned)cmd >= PRC_NCMDS) {
+ return;
+ }
+ if (PRC_IS_REDIRECT(cmd)) {
+ d = NULL;
+ } else if (inet6ctlerrmap[cmd] == 0) {
+ return;
+ }
+ /* If the parameter is from icmp6, decode it. */
+ if (d != NULL) {
+ ip6cp = (struct ip6ctlparam *)d;
+ } else {
+ ip6cp = (struct ip6ctlparam *)NULL;
+ }
+
+ if (ip6cp != NULL) {
+ /*
+ * XXX: We assume that when IPV6 is non NULL, M and OFF are
+ * valid.
+ */
+ if (ip6cp->ip6c_m == NULL) {
+ return;
+ }
+
+ /* Check if we can safely examine the ports and the
+ * verification tag of the SCTP common header.
+ */
+ if (ip6cp->ip6c_m->m_pkthdr.len <
+ (int32_t)(ip6cp->ip6c_off + offsetof(struct sctphdr, checksum))) {
+ return;
+ }
+
+ /* Copy out the port numbers and the verification tag. */
+ memset(&sh, 0, sizeof(sh));
+ m_copydata(ip6cp->ip6c_m,
+ ip6cp->ip6c_off,
+ sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t),
+ (caddr_t)&sh);
+ memset(&src, 0, sizeof(struct sockaddr_in6));
+ src.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ src.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ src.sin6_port = sh.src_port;
+ src.sin6_addr = ip6cp->ip6c_ip6->ip6_src;
+ memset(&dst, 0, sizeof(struct sockaddr_in6));
+ dst.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ dst.sin6_port = sh.dest_port;
+ dst.sin6_addr = ip6cp->ip6c_ip6->ip6_dst;
+ inp = NULL;
+ net = NULL;
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
+ (struct sockaddr *)&src,
+ &inp, &net, 1, SCTP_DEFAULT_VRFID);
+ if ((stcb != NULL) &&
+ (net != NULL) &&
+ (inp != NULL)) {
+ /* Check the verification tag */
+ if (ntohl(sh.v_tag) != 0) {
+ /*
+ * This must be the verification tag used for
+ * sending out packets. We don't consider
+ * packets reflecting the verification tag.
+ */
+ if (ntohl(sh.v_tag) != stcb->asoc.peer_vtag) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ } else {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ sctp6_notify(inp, stcb, net,
+ ip6cp->ip6c_icmp6->icmp6_type,
+ ip6cp->ip6c_icmp6->icmp6_code,
+ ntohl(ip6cp->ip6c_icmp6->icmp6_mtu));
+#if defined(__Userspace__)
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+ (stcb->sctp_socket != NULL)) {
+ struct socket *upcall_socket;
+
+ upcall_socket = stcb->sctp_socket;
+ SOCK_LOCK(upcall_socket);
+ soref(upcall_socket);
+ SOCK_UNLOCK(upcall_socket);
+ if ((upcall_socket->so_upcall != NULL) &&
+ (upcall_socket->so_error != 0)) {
+ (*upcall_socket->so_upcall)(upcall_socket, upcall_socket->so_upcallarg, M_NOWAIT);
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(upcall_socket);
+ sorele(upcall_socket);
+ }
+#endif
+ } else {
+ if ((stcb == NULL) && (inp != NULL)) {
+ /* reduce inp's ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ }
+ }
+}
+#endif
+#endif
+
+/*
+ * this routine can probably be collasped into the one in sctp_userreq.c
+ * since they do the same thing and now we lookup with a sockaddr
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+static int
+sctp6_getcred(SYSCTL_HANDLER_ARGS)
+{
+ struct xucred xuc;
+ struct sockaddr_in6 addrs[2];
+ struct sctp_inpcb *inp;
+ struct sctp_nets *net;
+ struct sctp_tcb *stcb;
+ int error;
+ uint32_t vrf_id;
+
+ vrf_id = SCTP_DEFAULT_VRFID;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ error = priv_check(req->td, PRIV_NETINET_GETCRED);
+#else
+ error = suser(req->p);
+#endif
+ if (error)
+ return (error);
+
+ if (req->newlen != sizeof(addrs)) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (req->oldlen != sizeof(struct ucred)) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ error = SYSCTL_IN(req, addrs, sizeof(addrs));
+ if (error)
+ return (error);
+
+ stcb = sctp_findassociation_addr_sa(sin6tosa(&addrs[1]),
+ sin6tosa(&addrs[0]),
+ &inp, &net, 1, vrf_id);
+ if (stcb == NULL || inp == NULL || inp->sctp_socket == NULL) {
+ if ((inp != NULL) && (stcb == NULL)) {
+ /* reduce ref-count */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ goto cred_can_cont;
+ }
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+ error = ENOENT;
+ goto out;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ /* We use the write lock here, only
+ * since in the error leg we need it.
+ * If we used RLOCK, then we would have
+ * to wlock/decr/unlock/rlock. Which
+ * in theory could create a hole. Better
+ * to use higher wlock.
+ */
+ SCTP_INP_WLOCK(inp);
+ cred_can_cont:
+ error = cr_canseesocket(req->td->td_ucred, inp->sctp_socket);
+ if (error) {
+ SCTP_INP_WUNLOCK(inp);
+ goto out;
+ }
+ cru2x(inp->sctp_socket->so_cred, &xuc);
+ SCTP_INP_WUNLOCK(inp);
+ error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred));
+out:
+ return (error);
+}
+
+SYSCTL_PROC(_net_inet6_sctp6, OID_AUTO, getcred,
+ CTLTYPE_OPAQUE | CTLFLAG_RW | CTLFLAG_NEEDGIANT,
+ 0, 0, sctp6_getcred, "S,ucred",
+ "Get the ucred of a SCTP6 connection");
+#endif
+
+#if defined(__Userspace__)
+int
+sctp6_attach(struct socket *so, int proto SCTP_UNUSED, uint32_t vrf_id)
+#elif defined(__FreeBSD__)
+static int
+sctp6_attach(struct socket *so, int proto SCTP_UNUSED, struct thread *p SCTP_UNUSED)
+#elif defined(_WIN32)
+static int
+sctp6_attach(struct socket *so, int proto SCTP_UNUSED, PKTHREAD p SCTP_UNUSED)
+#else
+static int
+sctp6_attach(struct socket *so, int proto SCTP_UNUSED, struct proc *p SCTP_UNUSED)
+#endif
+{
+ int error;
+ struct sctp_inpcb *inp;
+#if !defined(__Userspace__)
+ uint32_t vrf_id = SCTP_DEFAULT_VRFID;
+#endif
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp != NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+ if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) {
+ error = SCTP_SORESERVE(so, SCTP_BASE_SYSCTL(sctp_sendspace), SCTP_BASE_SYSCTL(sctp_recvspace));
+ if (error)
+ return (error);
+ }
+ error = sctp_inpcb_alloc(so, vrf_id);
+ if (error)
+ return (error);
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_flags |= SCTP_PCB_FLAGS_BOUND_V6; /* I'm v6! */
+
+ inp->ip_inp.inp.inp_vflag |= INP_IPV6;
+ inp->ip_inp.inp.in6p_hops = -1; /* use kernel default */
+ inp->ip_inp.inp.in6p_cksum = -1; /* just to be sure */
+#ifdef INET
+ /*
+ * XXX: ugly!! IPv4 TTL initialization is necessary for an IPv6
+ * socket as well, because the socket may be bound to an IPv6
+ * wildcard address, which may match an IPv4-mapped IPv6 address.
+ */
+ inp->ip_inp.inp.inp_ip_ttl = MODULE_GLOBAL(ip_defttl);
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+}
+
+#if defined(__Userspace__)
+int
+sctp6_bind(struct socket *so, struct sockaddr *addr, void * p)
+{
+#elif defined(__FreeBSD__)
+static int
+sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#elif defined(__APPLE__)
+static int
+sctp6_bind(struct socket *so, struct sockaddr *addr, struct proc *p)
+{
+#elif defined(_WIN32)
+static int
+sctp6_bind(struct socket *so, struct sockaddr *addr, PKTHREAD p)
+{
+#else
+static int
+sctp6_bind(struct socket *so, struct mbuf *nam, struct proc *p)
+{
+ struct sockaddr *addr = nam ? mtod(nam, struct sockaddr *): NULL;
+
+#endif
+ struct sctp_inpcb *inp;
+ int error;
+ u_char vflagsav;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ if (addr) {
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ }
+#endif
+ vflagsav = inp->ip_inp.inp.inp_vflag;
+ inp->ip_inp.inp.inp_vflag &= ~INP_IPV4;
+ inp->ip_inp.inp.inp_vflag |= INP_IPV6;
+ if ((addr != NULL) && (SCTP_IPV6_V6ONLY(inp) == 0)) {
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ /* binding v4 addr to v6 socket, so reset flags */
+ inp->ip_inp.inp.inp_vflag |= INP_IPV4;
+ inp->ip_inp.inp.inp_vflag &= ~INP_IPV6;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6_p;
+
+ sin6_p = (struct sockaddr_in6 *)addr;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) {
+ inp->ip_inp.inp.inp_vflag |= INP_IPV4;
+ }
+#ifdef INET
+ if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) {
+ struct sockaddr_in sin;
+
+ in6_sin6_2_sin(&sin, sin6_p);
+ inp->ip_inp.inp.inp_vflag |= INP_IPV4;
+ inp->ip_inp.inp.inp_vflag &= ~INP_IPV6;
+ error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, NULL, p);
+ goto out;
+ }
+#endif
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+ } else if (addr != NULL) {
+ struct sockaddr_in6 *sin6_p;
+
+ /* IPV6_V6ONLY socket */
+#ifdef INET
+ if (addr->sa_family == AF_INET) {
+ /* can't bind v4 addr to v6 only socket! */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out;
+ }
+#endif
+ sin6_p = (struct sockaddr_in6 *)addr;
+
+ if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) {
+ /* can't bind v4-mapped addrs either! */
+ /* NOTE: we don't support SIIT */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ error = EINVAL;
+ goto out;
+ }
+ }
+ error = sctp_inpcb_bind(so, addr, NULL, p);
+out:
+ if (error != 0)
+ inp->ip_inp.inp.inp_vflag = vflagsav;
+ return (error);
+}
+
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+#if !defined(__Userspace__)
+static void
+#else
+void
+#endif
+sctp6_close(struct socket *so)
+{
+ sctp_close(so);
+}
+
+/* This could be made common with sctp_detach() since they are identical */
+#else
+
+static
+int
+sctp6_detach(struct socket *so)
+{
+#if defined(__Userspace__)
+ sctp_close(so);
+ return (0);
+#else
+ return (sctp_detach(so));
+#endif
+}
+
+#endif
+
+int
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct thread *p);
+#else
+sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct proc *p);
+#endif
+
+#if !defined(_WIN32) && !defined(__Userspace__)
+#if defined(__FreeBSD__)
+static int
+sctp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct thread *p)
+{
+#elif defined(__APPLE__)
+static int
+sctp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct proc *p)
+{
+#else
+static int
+sctp6_send(struct socket *so, int flags, struct mbuf *m, struct mbuf *nam,
+ struct mbuf *control, struct proc *p)
+{
+ struct sockaddr *addr = nam ? mtod(nam, struct sockaddr *): NULL;
+#endif
+ struct sctp_inpcb *inp;
+
+#ifdef INET
+ struct sockaddr_in6 *sin6;
+#endif /* INET */
+ /* No SPL needed since sctp_output does this */
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ if (control) {
+ SCTP_RELEASE_PKT(control);
+ control = NULL;
+ }
+ SCTP_RELEASE_PKT(m);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ /*
+ * For the TCP model we may get a NULL addr, if we are a connected
+ * socket thats ok.
+ */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) &&
+ (addr == NULL)) {
+ goto connected_type;
+ }
+ if (addr == NULL) {
+ SCTP_RELEASE_PKT(m);
+ if (control) {
+ SCTP_RELEASE_PKT(control);
+ control = NULL;
+ }
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EDESTADDRREQ);
+ return (EDESTADDRREQ);
+ }
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+#if defined(HAVE_SA_LEN)
+ if (addr->sa_len != sizeof(struct sockaddr_in)) {
+ if (control) {
+ SCTP_RELEASE_PKT(control);
+ control = NULL;
+ }
+ SCTP_RELEASE_PKT(m);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#if defined(HAVE_SA_LEN)
+ if (addr->sa_len != sizeof(struct sockaddr_in6)) {
+ if (control) {
+ SCTP_RELEASE_PKT(control);
+ control = NULL;
+ }
+ SCTP_RELEASE_PKT(m);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+ default:
+ if (control) {
+ SCTP_RELEASE_PKT(control);
+ control = NULL;
+ }
+ SCTP_RELEASE_PKT(m);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#ifdef INET
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ /*
+ * if IPV6_V6ONLY flag, we discard datagrams destined to a
+ * v4 addr or v4-mapped addr
+ */
+ if (addr->sa_family == AF_INET) {
+ if (control) {
+ SCTP_RELEASE_PKT(control);
+ control = NULL;
+ }
+ SCTP_RELEASE_PKT(m);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ if (control) {
+ SCTP_RELEASE_PKT(control);
+ control = NULL;
+ }
+ SCTP_RELEASE_PKT(m);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ }
+ if ((addr->sa_family == AF_INET6) &&
+ IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ struct sockaddr_in sin;
+
+ /* convert v4-mapped into v4 addr and send */
+ in6_sin6_2_sin(&sin, sin6);
+ return (sctp_sendm(so, flags, m, (struct sockaddr *)&sin, control, p));
+ }
+#endif /* INET */
+connected_type:
+ /* now what about control */
+ if (control) {
+ if (inp->control) {
+ SCTP_PRINTF("huh? control set?\n");
+ SCTP_RELEASE_PKT(inp->control);
+ inp->control = NULL;
+ }
+ inp->control = control;
+ }
+ /* Place the data */
+ if (inp->pkt) {
+ SCTP_BUF_NEXT(inp->pkt_last) = m;
+ inp->pkt_last = m;
+ } else {
+ inp->pkt_last = inp->pkt = m;
+ }
+ if (
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ /* FreeBSD and MacOSX uses a flag passed */
+ ((flags & PRUS_MORETOCOME) == 0)
+#else
+ 1 /* Open BSD does not have any "more to come"
+ * indication */
+#endif
+ ) {
+ /*
+ * note with the current version this code will only be used
+ * by OpenBSD, NetBSD and FreeBSD have methods for
+ * re-defining sosend() to use sctp_sosend(). One can
+ * optionaly switch back to this code (by changing back the
+ * defininitions but this is not advisable.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ int ret;
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ inp->pkt = NULL;
+ inp->control = NULL;
+ return (ret);
+ } else {
+ return (0);
+ }
+}
+#endif
+
+#if defined(__Userspace__)
+int
+sctp6_connect(struct socket *so, struct sockaddr *addr)
+{
+ void *p = NULL;
+#elif defined(__FreeBSD__)
+static int
+sctp6_connect(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#elif defined(__APPLE__)
+static int
+sctp6_connect(struct socket *so, struct sockaddr *addr, struct proc *p)
+{
+#elif defined(_WIN32)
+static int
+sctp6_connect(struct socket *so, struct sockaddr *addr, PKTHREAD p)
+{
+#else
+static int
+sctp6_connect(struct socket *so, struct mbuf *nam, struct proc *p)
+{
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+#endif
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ uint32_t vrf_id;
+ int error = 0;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+#ifdef INET
+ struct sockaddr_in6 *sin6;
+ union sctp_sockstore store;
+#endif
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ECONNRESET);
+ return (ECONNRESET); /* I made the same as TCP since we are
+ * not setup? */
+ }
+ if (addr == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#if !(defined(_WIN32) && !defined(__Userspace__))
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ break;
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+#endif
+
+ vrf_id = inp->def_vrf_id;
+ SCTP_ASOC_CREATE_LOCK(inp);
+ SCTP_INP_RLOCK(inp);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) ==
+ SCTP_PCB_FLAGS_UNBOUND) {
+ /* Bind a ephemeral port */
+ SCTP_INP_RUNLOCK(inp);
+ error = sctp6_bind(so, NULL, p);
+ if (error) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+
+ return (error);
+ }
+ SCTP_INP_RLOCK(inp);
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) {
+ /* We are already connected AND the TCP model */
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+#ifdef INET
+ sin6 = (struct sockaddr_in6 *)addr;
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ /*
+ * if IPV6_V6ONLY flag, ignore connections destined to a v4
+ * addr or v4-mapped addr
+ */
+ if (addr->sa_family == AF_INET) {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ }
+ if ((addr->sa_family == AF_INET6) &&
+ IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ /* convert v4-mapped into v4 addr */
+ in6_sin6_2_sin(&store.sin, sin6);
+ addr = &store.sa;
+ }
+#endif /* INET */
+ /* Now do we connect? */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL);
+ if (stcb == NULL) {
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ }
+ }
+
+ if (stcb != NULL) {
+ /* Already have or am bring up an association */
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EALREADY);
+ return (EALREADY);
+ }
+ /* We are GOOD to go */
+ stcb = sctp_aloc_assoc_connected(inp, addr, &error, 0, 0, vrf_id,
+ inp->sctp_ep.pre_open_stream_count,
+ inp->sctp_ep.port, p,
+ SCTP_INITIALIZE_AUTH_PARAMS);
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ if (stcb == NULL) {
+ /* Gak! no memory */
+ return (error);
+ }
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ SCTP_TCB_UNLOCK(stcb);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return (error);
+}
+
+static int
+#if !defined(__Userspace__)
+sctp6_getaddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in6 *sin6;
+#else
+sctp6_getaddr(struct socket *so, struct mbuf *nam)
+{
+ struct sockaddr_in6 *sin6 = mtod(nam, struct sockaddr_in6 *);
+#endif
+ struct sctp_inpcb *inp;
+ uint32_t vrf_id;
+ struct sctp_ifa *sctp_ifa;
+
+#if defined(SCTP_KAME) && defined(SCTP_EMBEDDED_V6_SCOPE)
+ int error;
+#endif
+
+ /*
+ * Do the malloc first in case it blocks.
+ */
+#if !defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof(*sin6));
+ if (sin6 == NULL)
+ return (ENOMEM);
+#else
+ SCTP_BUF_LEN(nam) = sizeof(*sin6);
+ memset(sin6, 0, sizeof(*sin6));
+#endif
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(*sin6);
+#endif
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ECONNRESET);
+ return (ECONNRESET);
+ }
+ SCTP_INP_RLOCK(inp);
+ sin6->sin6_port = inp->sctp_lport;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* For the bound all case you get back 0 */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+ struct sctp_tcb *stcb;
+ struct sockaddr_in6 *sin_a6;
+ struct sctp_nets *net;
+ int fnd;
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb == NULL) {
+ SCTP_INP_RUNLOCK(inp);
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+ return (ENOENT);
+ }
+ fnd = 0;
+ sin_a6 = NULL;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ if (sin_a6 == NULL)
+ /* this will make coverity happy */
+ continue;
+
+ if (sin_a6->sin6_family == AF_INET6) {
+ fnd = 1;
+ break;
+ }
+ }
+ if ((!fnd) || (sin_a6 == NULL)) {
+ /* punt */
+ SCTP_INP_RUNLOCK(inp);
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+ return (ENOENT);
+ }
+ vrf_id = inp->def_vrf_id;
+ sctp_ifa = sctp_source_address_selection(inp, stcb, (sctp_route_t *)&net->ro, net, 0, vrf_id);
+ if (sctp_ifa) {
+ sin6->sin6_addr = sctp_ifa->address.sin6.sin6_addr;
+ }
+ } else {
+ /* For the bound all case you get back 0 */
+ memset(&sin6->sin6_addr, 0, sizeof(sin6->sin6_addr));
+ }
+ } else {
+ /* Take the first IPv6 address in the list */
+ struct sctp_laddr *laddr;
+ int fnd = 0;
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa->address.sa.sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin_a;
+
+ sin_a = &laddr->ifa->address.sin6;
+ sin6->sin6_addr = sin_a->sin6_addr;
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+ return (ENOENT);
+ }
+ }
+ SCTP_INP_RUNLOCK(inp);
+ /* Scoping things for v6 */
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+#ifdef SCTP_KAME
+ if ((error = sa6_recoverscope(sin6)) != 0) {
+ SCTP_FREE_SONAME(sin6);
+ return (error);
+ }
+#else
+ if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr))
+ /* skip ifp check below */
+ in6_recoverscope(sin6, &sin6->sin6_addr, NULL);
+ else
+ sin6->sin6_scope_id = 0; /* XXX */
+#endif /* SCTP_KAME */
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#if !defined(__Userspace__)
+ (*addr) = (struct sockaddr *)sin6;
+#endif
+ return (0);
+}
+
+static int
+#if !defined(__Userspace__)
+sctp6_peeraddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in6 *sin6;
+#else
+sctp6_peeraddr(struct socket *so, struct mbuf *nam)
+{
+ struct sockaddr_in6 *sin6 = mtod(nam, struct sockaddr_in6 *);
+#endif
+ int fnd;
+ struct sockaddr_in6 *sin_a6;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+#ifdef SCTP_KAME
+ int error;
+#endif
+
+ /* Do the malloc first in case it blocks. */
+#if !defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+ if (sin6 == NULL)
+ return (ENOMEM);
+#else
+ SCTP_BUF_LEN(nam) = sizeof(*sin6);
+ memset(sin6, 0, sizeof(*sin6));
+#endif
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(*sin6);
+#endif
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if ((inp == NULL) ||
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) {
+ /* UDP type and listeners will drop out here */
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOTCONN);
+ return (ENOTCONN);
+ }
+ SCTP_INP_RLOCK(inp);
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ if (stcb == NULL) {
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ECONNRESET);
+ return (ECONNRESET);
+ }
+ fnd = 0;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ if (sin_a6->sin6_family == AF_INET6) {
+ fnd = 1;
+ sin6->sin6_port = stcb->rport;
+ sin6->sin6_addr = sin_a6->sin6_addr;
+ break;
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ if (!fnd) {
+ /* No IPv4 address */
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+ return (ENOENT);
+ }
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+#ifdef SCTP_KAME
+ if ((error = sa6_recoverscope(sin6)) != 0) {
+#if !defined(__Userspace__)
+ SCTP_FREE_SONAME(sin6);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, error);
+ return (error);
+ }
+#else
+ in6_recoverscope(sin6, &sin6->sin6_addr, NULL);
+#endif /* SCTP_KAME */
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+#if !defined(__Userspace__)
+ *addr = (struct sockaddr *)sin6;
+#endif
+ return (0);
+}
+
+#if !defined(__Userspace__)
+static int
+sctp6_in6getaddr(struct socket *so, struct sockaddr **nam)
+{
+#elif defined(__Userspace__)
+int
+sctp6_in6getaddr(struct socket *so, struct mbuf *nam)
+{
+#ifdef INET
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+#endif
+#else
+static int
+sctp6_in6getaddr(struct socket *so, struct mbuf *nam)
+{
+#ifdef INET
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+#endif
+#endif
+ struct inpcb *inp = sotoinpcb(so);
+ int error;
+
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+ /* allow v6 addresses precedence */
+ error = sctp6_getaddr(so, nam);
+#ifdef INET
+ if (error) {
+#if !defined(__Userspace__)
+ struct sockaddr_in6 *sin6;
+#else
+ struct sockaddr_in6 sin6;
+#endif
+
+ /* try v4 next if v6 failed */
+ error = sctp_ingetaddr(so, nam);
+ if (error) {
+ return (error);
+ }
+#if !defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+ if (sin6 == NULL) {
+ SCTP_FREE_SONAME(*nam);
+ return (ENOMEM);
+ }
+ in6_sin_2_v4mapsin6((struct sockaddr_in *)*nam, sin6);
+ SCTP_FREE_SONAME(*nam);
+ *nam = (struct sockaddr *)sin6;
+#else
+ in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6);
+ SCTP_BUF_LEN(nam) = sizeof(struct sockaddr_in6);
+ memcpy(addr, &sin6, sizeof(struct sockaddr_in6));
+#endif
+ }
+#endif
+ return (error);
+}
+
+#if !defined(__Userspace__)
+static int
+sctp6_getpeeraddr(struct socket *so, struct sockaddr **nam)
+{
+#elif defined(__Userspace__)
+int
+sctp6_getpeeraddr(struct socket *so, struct mbuf *nam)
+{
+#ifdef INET
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+#endif
+#else
+static
+int
+sctp6_getpeeraddr(struct socket *so, struct mbuf *nam)
+{
+#ifdef INET
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+#endif
+
+#endif
+ struct inpcb *inp = sotoinpcb(so);
+ int error;
+
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+
+ /* allow v6 addresses precedence */
+ error = sctp6_peeraddr(so, nam);
+#ifdef INET
+ if (error) {
+#if !defined(__Userspace__)
+ struct sockaddr_in6 *sin6;
+#else
+ struct sockaddr_in6 sin6;
+#endif
+
+ /* try v4 next if v6 failed */
+ error = sctp_peeraddr(so, nam);
+ if (error) {
+ return (error);
+ }
+#if !defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+ if (sin6 == NULL) {
+ SCTP_FREE_SONAME(*nam);
+ return (ENOMEM);
+ }
+ in6_sin_2_v4mapsin6((struct sockaddr_in *)*nam, sin6);
+ SCTP_FREE_SONAME(*nam);
+ *nam = (struct sockaddr *)sin6;
+#else
+ in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6);
+ SCTP_BUF_LEN(nam) = sizeof(struct sockaddr_in6);
+ memcpy(addr, &sin6, sizeof(struct sockaddr_in6));
+#endif
+ }
+#endif
+ return (error);
+}
+
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__)
+#define SCTP6_PROTOSW \
+ .pr_protocol = IPPROTO_SCTP, \
+ .pr_ctloutput = sctp_ctloutput, \
+ .pr_abort = sctp_abort, \
+ .pr_accept = sctp_accept, \
+ .pr_attach = sctp6_attach, \
+ .pr_bind = sctp6_bind, \
+ .pr_connect = sctp6_connect, \
+ .pr_control = in6_control, \
+ .pr_close = sctp6_close, \
+ .pr_detach = sctp6_close, \
+ .pr_sopoll = sopoll_generic, \
+ .pr_flush = sctp_flush, \
+ .pr_disconnect = sctp_disconnect, \
+ .pr_listen = sctp_listen, \
+ .pr_peeraddr = sctp6_getpeeraddr, \
+ .pr_send = sctp6_send, \
+ .pr_shutdown = sctp_shutdown, \
+ .pr_sockaddr = sctp6_in6getaddr, \
+ .pr_sosend = sctp_sosend, \
+ .pr_soreceive = sctp_soreceive
+
+struct protosw sctp6_seqpacket_protosw = {
+ .pr_type = SOCK_SEQPACKET,
+ .pr_flags = PR_WANTRCVD,
+ SCTP6_PROTOSW
+};
+
+struct protosw sctp6_stream_protosw = {
+ .pr_type = SOCK_STREAM,
+ .pr_flags = PR_CONNREQUIRED | PR_WANTRCVD,
+ SCTP6_PROTOSW
+};
+#else
+struct pr_usrreqs sctp6_usrreqs = {
+#if defined(__APPLE__) && !defined(__Userspace__)
+ .pru_abort = sctp_abort,
+ .pru_accept = sctp_accept,
+ .pru_attach = sctp6_attach,
+ .pru_bind = sctp6_bind,
+ .pru_connect = sctp6_connect,
+ .pru_connect2 = pru_connect2_notsupp,
+ .pru_control = in6_control,
+ .pru_detach = sctp6_detach,
+ .pru_disconnect = sctp_disconnect,
+ .pru_listen = sctp_listen,
+ .pru_peeraddr = sctp6_getpeeraddr,
+ .pru_rcvd = NULL,
+ .pru_rcvoob = pru_rcvoob_notsupp,
+ .pru_send = sctp6_send,
+ .pru_sense = pru_sense_null,
+ .pru_shutdown = sctp_shutdown,
+ .pru_sockaddr = sctp6_in6getaddr,
+ .pru_sosend = sctp_sosend,
+ .pru_soreceive = sctp_soreceive,
+ .pru_sopoll = sopoll
+#elif defined(_WIN32) && !defined(__Userspace__)
+ sctp_abort,
+ sctp_accept,
+ sctp6_attach,
+ sctp6_bind,
+ sctp6_connect,
+ pru_connect2_notsupp,
+ NULL,
+ NULL,
+ sctp_disconnect,
+ sctp_listen,
+ sctp6_getpeeraddr,
+ NULL,
+ pru_rcvoob_notsupp,
+ NULL,
+ pru_sense_null,
+ sctp_shutdown,
+ sctp_flush,
+ sctp6_in6getaddr,
+ sctp_sosend,
+ sctp_soreceive,
+ sopoll_generic,
+ NULL,
+ sctp6_close
+};
+#endif
+#endif
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet6/sctp6_var.h b/netwerk/sctp/src/netinet6/sctp6_var.h
new file mode 100644
index 0000000000..8bfef64ca3
--- /dev/null
+++ b/netwerk/sctp/src/netinet6/sctp6_var.h
@@ -0,0 +1,82 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+ * Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET6_SCTP6_VAR_H_
+#define _NETINET6_SCTP6_VAR_H_
+
+#if defined(__Userspace__)
+#ifdef INET
+extern void in6_sin6_2_sin(struct sockaddr_in *, struct sockaddr_in6 *);
+extern void in6_sin6_2_sin_in_sock(struct sockaddr *);
+extern void in6_sin_2_v4mapsin6(const struct sockaddr_in *, struct sockaddr_in6 *);
+#endif
+#endif
+#if defined(_KERNEL)
+
+#if !defined(__Userspace__)
+SYSCTL_DECL(_net_inet6_sctp6);
+#if defined(__FreeBSD__)
+extern struct protosw sctp6_seqpacket_protosw, sctp6_stream_protosw;
+#else
+extern struct pr_usrreqs sctp6_usrreqs;
+#endif
+#else
+int sctp6_usrreq(struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *);
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+int sctp6_input(struct mbuf **, int *);
+int sctp6_input_with_port(struct mbuf **, int *, uint16_t);
+#else
+int sctp6_input(struct mbuf **, int *, int);
+int sctp6_input_with_port(struct mbuf **, int *, uint16_t);
+#endif
+int sctp6_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *,
+ struct mbuf *, struct proc *);
+#if defined(__APPLE__) && !defined(__Userspace__) && !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION) && !defined(APPLE_ELCAPITAN)
+void sctp6_ctlinput(int, struct sockaddr *, void *, struct ifnet * SCTP_UNUSED);
+#elif defined(__FreeBSD__) && !defined(__Userspace__)
+ip6proto_ctlinput_t sctp6_ctlinput;
+#else
+void sctp6_ctlinput(int, struct sockaddr_in6 *, ip6ctlparam *);
+#endif
+#if !((defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__))
+extern void in6_sin_2_v4mapsin6(struct sockaddr_in *, struct sockaddr_in6 *);
+extern void in6_sin6_2_sin(struct sockaddr_in *, struct sockaddr_in6 *);
+extern void in6_sin6_2_sin_in_sock(struct sockaddr *);
+#endif
+void sctp6_notify(struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *,
+ uint8_t, uint8_t, uint32_t);
+#endif
+#endif
diff --git a/netwerk/sctp/src/restore_mod.sh b/netwerk/sctp/src/restore_mod.sh
new file mode 100755
index 0000000000..238603cc40
--- /dev/null
+++ b/netwerk/sctp/src/restore_mod.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+find . -name '*.[ch]' -exec chmod 644 {} \;
diff --git a/netwerk/sctp/src/user_atomic.h b/netwerk/sctp/src/user_atomic.h
new file mode 100644
index 0000000000..6a59587efc
--- /dev/null
+++ b/netwerk/sctp/src/user_atomic.h
@@ -0,0 +1,315 @@
+/*-
+ * Copyright (c) 2009-2010 Brad Penoff
+ * Copyright (c) 2009-2010 Humaira Kamal
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USER_ATOMIC_H_
+#define _USER_ATOMIC_H_
+
+/* __Userspace__ version of sys/i386/include/atomic.h goes here */
+
+/* TODO In the future, might want to not use i386 specific assembly.
+ * The options include:
+ * - implement them generically (but maybe not truly atomic?) in userspace
+ * - have ifdef's for __Userspace_arch_ perhaps (OS isn't enough...)
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#if defined(__APPLE__) || defined(_WIN32)
+#if defined(_WIN32)
+#define atomic_add_int(addr, val) InterlockedExchangeAdd((LPLONG)addr, (LONG)val)
+#define atomic_fetchadd_int(addr, val) InterlockedExchangeAdd((LPLONG)addr, (LONG)val)
+#define atomic_subtract_int(addr, val) InterlockedExchangeAdd((LPLONG)addr,-((LONG)val))
+#define atomic_cmpset_int(dst, exp, src) InterlockedCompareExchange((LPLONG)dst, src, exp)
+#define SCTP_DECREMENT_AND_CHECK_REFCOUNT(addr) (InterlockedExchangeAdd((LPLONG)addr, (-1L)) == 1)
+#else
+#include <libkern/OSAtomic.h>
+#define atomic_add_int(addr, val) OSAtomicAdd32Barrier(val, (int32_t *)addr)
+#define atomic_fetchadd_int(addr, val) OSAtomicAdd32Barrier(val, (int32_t *)addr)
+#define atomic_subtract_int(addr, val) OSAtomicAdd32Barrier(-val, (int32_t *)addr)
+#define atomic_cmpset_int(dst, exp, src) OSAtomicCompareAndSwapIntBarrier(exp, src, (int *)dst)
+#define SCTP_DECREMENT_AND_CHECK_REFCOUNT(addr) (atomic_fetchadd_int(addr, -1) == 0)
+#endif
+
+#if defined(INVARIANTS)
+#define SCTP_SAVE_ATOMIC_DECREMENT(addr, val) \
+{ \
+ int32_t newval; \
+ newval = atomic_fetchadd_int(addr, -val); \
+ if (newval < 0) { \
+ panic("Counter goes negative"); \
+ } \
+}
+#else
+#define SCTP_SAVE_ATOMIC_DECREMENT(addr, val) \
+{ \
+ int32_t newval; \
+ newval = atomic_fetchadd_int(addr, -val); \
+ if (newval < 0) { \
+ *addr = 0; \
+ } \
+}
+#endif
+#if defined(_WIN32)
+static void atomic_init(void) {} /* empty when we are not using atomic_mtx */
+#else
+static inline void atomic_init(void) {} /* empty when we are not using atomic_mtx */
+#endif
+
+#else
+/* Using gcc built-in functions for atomic memory operations
+ Reference: http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html
+ Requires gcc version 4.1.0
+ compile with -march=i486
+ */
+
+/*Atomically add V to *P.*/
+#define atomic_add_int(P, V) (void) __sync_fetch_and_add(P, V)
+
+/*Atomically subtrace V from *P.*/
+#define atomic_subtract_int(P, V) (void) __sync_fetch_and_sub(P, V)
+
+/*
+ * Atomically add the value of v to the integer pointed to by p and return
+ * the previous value of *p.
+ */
+#define atomic_fetchadd_int(p, v) __sync_fetch_and_add(p, v)
+
+/* Following explanation from src/sys/i386/include/atomic.h,
+ * for atomic compare and set
+ *
+ * if (*dst == exp) *dst = src (all 32 bit words)
+ *
+ * Returns 0 on failure, non-zero on success
+ */
+
+#define atomic_cmpset_int(dst, exp, src) __sync_bool_compare_and_swap(dst, exp, src)
+
+#define SCTP_DECREMENT_AND_CHECK_REFCOUNT(addr) (atomic_fetchadd_int(addr, -1) == 1)
+#if defined(INVARIANTS)
+#define SCTP_SAVE_ATOMIC_DECREMENT(addr, val) \
+{ \
+ int32_t oldval; \
+ oldval = atomic_fetchadd_int(addr, -val); \
+ if (oldval < val) { \
+ panic("Counter goes negative"); \
+ } \
+}
+#else
+#define SCTP_SAVE_ATOMIC_DECREMENT(addr, val) \
+{ \
+ int32_t oldval; \
+ oldval = atomic_fetchadd_int(addr, -val); \
+ if (oldval < val) { \
+ *addr = 0; \
+ } \
+}
+#endif
+static inline void atomic_init(void) {} /* empty when we are not using atomic_mtx */
+#endif
+
+#if 0 /* using libatomic_ops */
+#include "user_include/atomic_ops.h"
+
+/*Atomically add incr to *P, and return the original value of *P.*/
+#define atomic_add_int(P, V) AO_fetch_and_add((AO_t*)P, V)
+
+#define atomic_subtract_int(P, V) AO_fetch_and_add((AO_t*)P, -(V))
+
+/*
+ * Atomically add the value of v to the integer pointed to by p and return
+ * the previous value of *p.
+ */
+#define atomic_fetchadd_int(p, v) AO_fetch_and_add((AO_t*)p, v)
+
+/* Atomically compare *addr to old_val, and replace *addr by new_val
+ if the first comparison succeeds. Returns nonzero if the comparison
+ succeeded and *addr was updated.
+*/
+/* Following Explanation from src/sys/i386/include/atomic.h, which
+ matches that of AO_compare_and_swap above.
+ * Atomic compare and set, used by the mutex functions
+ *
+ * if (*dst == exp) *dst = src (all 32 bit words)
+ *
+ * Returns 0 on failure, non-zero on success
+ */
+
+#define atomic_cmpset_int(dst, exp, src) AO_compare_and_swap((AO_t*)dst, exp, src)
+
+static inline void atomic_init() {} /* empty when we are not using atomic_mtx */
+#endif /* closing #if for libatomic */
+
+#if 0 /* using atomic_mtx */
+
+#include <pthread.h>
+
+extern userland_mutex_t atomic_mtx;
+
+#if defined(_WIN32)
+static inline void atomic_init() {
+ InitializeCriticalSection(&atomic_mtx);
+}
+static inline void atomic_destroy() {
+ DeleteCriticalSection(&atomic_mtx);
+}
+static inline void atomic_lock() {
+ EnterCriticalSection(&atomic_mtx);
+}
+static inline void atomic_unlock() {
+ LeaveCriticalSection(&atomic_mtx);
+}
+#else
+static inline void atomic_init() {
+ pthread_mutexattr_t mutex_attr;
+
+ pthread_mutexattr_init(&mutex_attr);
+#ifdef INVARIANTS
+ pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_ERRORCHECK);
+#endif
+ pthread_mutex_init(&accept_mtx, &mutex_attr);
+ pthread_mutexattr_destroy(&mutex_attr);
+}
+static inline void atomic_destroy() {
+ (void)pthread_mutex_destroy(&atomic_mtx);
+}
+static inline void atomic_lock() {
+#ifdef INVARIANTS
+ KASSERT(pthread_mutex_lock(&atomic_mtx) == 0, ("atomic_lock: atomic_mtx already locked"))
+#else
+ (void)pthread_mutex_lock(&atomic_mtx);
+#endif
+}
+static inline void atomic_unlock() {
+#ifdef INVARIANTS
+ KASSERT(pthread_mutex_unlock(&atomic_mtx) == 0, ("atomic_unlock: atomic_mtx not locked"))
+#else
+ (void)pthread_mutex_unlock(&atomic_mtx);
+#endif
+}
+#endif
+/*
+ * For userland, always use lock prefixes so that the binaries will run
+ * on both SMP and !SMP systems.
+ */
+
+#define MPLOCKED "lock ; "
+
+/*
+ * Atomically add the value of v to the integer pointed to by p and return
+ * the previous value of *p.
+ */
+static __inline u_int
+atomic_fetchadd_int(volatile void *n, u_int v)
+{
+ int *p = (int *) n;
+ atomic_lock();
+ __asm __volatile(
+ " " MPLOCKED " "
+ " xaddl %0, %1 ; "
+ "# atomic_fetchadd_int"
+ : "+r" (v), /* 0 (result) */
+ "=m" (*p) /* 1 */
+ : "m" (*p)); /* 2 */
+ atomic_unlock();
+
+ return (v);
+}
+
+
+#ifdef CPU_DISABLE_CMPXCHG
+
+static __inline int
+atomic_cmpset_int(volatile u_int *dst, u_int exp, u_int src)
+{
+ u_char res;
+
+ atomic_lock();
+ __asm __volatile(
+ " pushfl ; "
+ " cli ; "
+ " cmpl %3,%4 ; "
+ " jne 1f ; "
+ " movl %2,%1 ; "
+ "1: "
+ " sete %0 ; "
+ " popfl ; "
+ "# atomic_cmpset_int"
+ : "=q" (res), /* 0 */
+ "=m" (*dst) /* 1 */
+ : "r" (src), /* 2 */
+ "r" (exp), /* 3 */
+ "m" (*dst) /* 4 */
+ : "memory");
+ atomic_unlock();
+
+ return (res);
+}
+
+#else /* !CPU_DISABLE_CMPXCHG */
+
+static __inline int
+atomic_cmpset_int(volatile u_int *dst, u_int exp, u_int src)
+{
+ atomic_lock();
+ u_char res;
+
+ __asm __volatile(
+ " " MPLOCKED " "
+ " cmpxchgl %2,%1 ; "
+ " sete %0 ; "
+ "1: "
+ "# atomic_cmpset_int"
+ : "=a" (res), /* 0 */
+ "=m" (*dst) /* 1 */
+ : "r" (src), /* 2 */
+ "a" (exp), /* 3 */
+ "m" (*dst) /* 4 */
+ : "memory");
+ atomic_unlock();
+
+ return (res);
+}
+
+#endif /* CPU_DISABLE_CMPXCHG */
+
+#define atomic_add_int(P, V) do { \
+ atomic_lock(); \
+ (*(u_int *)(P) += (V)); \
+ atomic_unlock(); \
+} while(0)
+#define atomic_subtract_int(P, V) do { \
+ atomic_lock(); \
+ (*(u_int *)(P) -= (V)); \
+ atomic_unlock(); \
+} while(0)
+
+#endif
+#endif
diff --git a/netwerk/sctp/src/user_environment.c b/netwerk/sctp/src/user_environment.c
new file mode 100644
index 0000000000..3deb3ef0d4
--- /dev/null
+++ b/netwerk/sctp/src/user_environment.c
@@ -0,0 +1,384 @@
+/*-
+ * Copyright (c) 2009-2010 Brad Penoff
+ * Copyright (c) 2009-2010 Humaira Kamal
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* __Userspace__ */
+
+#if defined(_WIN32)
+#if !defined(_CRT_RAND_S) && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+#define _CRT_RAND_S
+#endif
+#else
+#include <stdint.h>
+#include <netinet/sctp_os_userspace.h>
+#endif
+#ifdef INVARIANTS
+#include <netinet/sctp_pcb.h>
+#endif
+#include <user_environment.h>
+#include <sys/types.h>
+/* #include <sys/param.h> defines MIN */
+#if !defined(MIN)
+#define MIN(arg1,arg2) ((arg1) < (arg2) ? (arg1) : (arg2))
+#endif
+
+#define uHZ 1000
+
+/* See user_include/user_environment.h for comments about these variables */
+int maxsockets = 25600;
+int hz = uHZ;
+int ip_defttl = 64;
+int ipport_firstauto = 49152, ipport_lastauto = 65535;
+int nmbclusters = 65536;
+
+/* Source ip_output.c. extern'd in ip_var.h */
+u_short ip_id = 0; /*__Userspace__ TODO Should it be initialized to zero? */
+
+/* used in user_include/user_atomic.h in order to make the operations
+ * defined there truly atomic
+ */
+userland_mutex_t atomic_mtx;
+
+/* If the entropy device is not loaded, make a token effort to
+ * provide _some_ kind of randomness. This should only be used
+ * inside other RNG's, like arc4random(9).
+ */
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+#include <string.h>
+
+void
+init_random(void)
+{
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ memset(buf, 'A', size);
+ return;
+}
+
+void
+finish_random(void)
+{
+ return;
+}
+/* This define can be used to optionally use OpenSSL's random number utility,
+ * which is capable of bypassing the chromium sandbox which normally would
+ * prevent opening files, including /dev/urandom.
+ */
+#elif defined(SCTP_USE_OPENSSL_RAND)
+#include <openssl/rand.h>
+
+/* Requiring BoringSSL because it guarantees that RAND_bytes will succeed. */
+#ifndef OPENSSL_IS_BORINGSSL
+#error Only BoringSSL is supported with SCTP_USE_OPENSSL_RAND.
+#endif
+
+void
+init_random(void)
+{
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ RAND_bytes((uint8_t *)buf, size);
+ return;
+}
+
+void
+finish_random(void)
+{
+ return;
+}
+#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(__Bitrig__)
+#include <stdlib.h>
+
+void
+init_random(void)
+{
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ arc4random_buf(buf, size);
+ return;
+}
+
+void
+finish_random(void)
+{
+ return;
+}
+#elif defined(_WIN32)
+#include <stdlib.h>
+
+void
+init_random(void)
+{
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ unsigned int randval;
+ size_t position, remaining;
+
+ position = 0;
+ while (position < size) {
+ if (rand_s(&randval) == 0) {
+ remaining = MIN(size - position, sizeof(unsigned int));
+ memcpy((char *)buf + position, &randval, remaining);
+ position += sizeof(unsigned int);
+ }
+ }
+ return;
+}
+
+void
+finish_random(void)
+{
+ return;
+}
+#elif (defined(__ANDROID__) && (__ANDROID_API__ < 28)) || defined(__QNX__) || defined(__EMSCRIPTEN__)
+#include <fcntl.h>
+
+static int fd = -1;
+
+void
+init_random(void)
+{
+ fd = open("/dev/urandom", O_RDONLY);
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ size_t position;
+ ssize_t n;
+
+ position = 0;
+ while (position < size) {
+ n = read(fd, (char *)buf + position, size - position);
+ if (n > 0) {
+ position += n;
+ }
+ }
+ return;
+}
+
+void
+finish_random(void)
+{
+ close(fd);
+ return;
+}
+#elif defined(__ANDROID__) && (__ANDROID_API__ >= 28)
+#include <sys/random.h>
+
+void
+init_random(void)
+{
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ size_t position;
+ ssize_t n;
+
+ position = 0;
+ while (position < size) {
+ n = getrandom((char *)buf + position, size - position, 0);
+ if (n > 0) {
+ position += n;
+ }
+ }
+ return;
+}
+
+void
+finish_random(void)
+{
+ return;
+}
+#elif defined(__linux__)
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#if defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+void __msan_unpoison(void *, size_t);
+#endif
+#endif
+
+#ifdef __NR_getrandom
+#if !defined(GRND_NONBLOCK)
+#define GRND_NONBLOCK 1
+#endif
+static int getrandom_available = 0;
+#endif
+static int fd = -1;
+
+void
+init_random(void)
+{
+#ifdef __NR_getrandom
+ char dummy;
+ ssize_t n = syscall(__NR_getrandom, &dummy, sizeof(dummy), GRND_NONBLOCK);
+ if (n > 0 || errno == EINTR || errno == EAGAIN) {
+ /* Either getrandom succeeded, was interrupted or is waiting for entropy;
+ * all of which mean the syscall is available.
+ */
+ getrandom_available = 1;
+ } else {
+#ifdef INVARIANTS
+ if (errno != ENOSYS) {
+ panic("getrandom syscall returned unexpected error: %d", errno);
+ }
+#endif
+ /* If the syscall isn't available, fall back to /dev/urandom. */
+#endif
+ fd = open("/dev/urandom", O_RDONLY);
+#ifdef __NR_getrandom
+ }
+#endif
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ size_t position;
+ ssize_t n;
+
+ position = 0;
+ while (position < size) {
+#ifdef __NR_getrandom
+ if (getrandom_available) {
+ /* Using syscall directly because getrandom isn't present in glibc < 2.25.
+ */
+ n = syscall(__NR_getrandom, (char *)buf + position, size - position, 0);
+ if (n > 0) {
+#if defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+ /* Need to do this because MSan doesn't realize that syscall has
+ * initialized the output buffer.
+ */
+ __msan_unpoison(buf + position, n);
+#endif
+#endif
+ position += n;
+ } else if (errno != EINTR && errno != EAGAIN) {
+#ifdef INVARIANTS
+ panic("getrandom syscall returned unexpected error: %d", errno);
+#endif
+ }
+ } else
+#endif /* __NR_getrandom */
+ {
+ n = read(fd, (char *)buf + position, size - position);
+ if (n > 0) {
+ position += n;
+ }
+ }
+ }
+ return;
+}
+
+void
+finish_random(void)
+{
+ if (fd != -1) {
+ close(fd);
+ }
+ return;
+}
+#elif defined(__Fuchsia__)
+#include <zircon/syscalls.h>
+
+void
+init_random(void)
+{
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ zx_cprng_draw(buf, size);
+ return;
+}
+
+void
+finish_random(void)
+{
+ return;
+}
+#elif defined(__native_client__)
+#include <nacl/nacl_random.h>
+
+void
+init_random(void)
+{
+ return;
+}
+
+void
+read_random(void *buf, size_t size)
+{
+ size_t position;
+ size_t n;
+
+ position = 0;
+ while (position < size) {
+ if (nacl_secure_random((char *)buf + position, size - position, &n) == 0)
+ position += n;
+ }
+ }
+ return;
+}
+
+void
+finish_random(void)
+{
+ return;
+}
+#else
+#error "Unknown platform. Please provide platform specific RNG."
+#endif
diff --git a/netwerk/sctp/src/user_environment.h b/netwerk/sctp/src/user_environment.h
new file mode 100644
index 0000000000..930d085637
--- /dev/null
+++ b/netwerk/sctp/src/user_environment.h
@@ -0,0 +1,132 @@
+/*-
+ * Copyright (c) 2009-2010 Brad Penoff
+ * Copyright (c) 2009-2010 Humaira Kamal
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USER_ENVIRONMENT_H_
+#define _USER_ENVIRONMENT_H_
+
+#if defined(_WIN32)
+// Needed for unified build so that rand_s is available to all unified
+// sources.
+#if !defined(_CRT_RAND_S) && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+#define _CRT_RAND_S
+#endif
+#endif
+
+/* __Userspace__ */
+#include <sys/types.h>
+
+#ifdef __FreeBSD__
+#ifndef _SYS_MUTEX_H_
+#include <sys/mutex.h>
+#endif
+#endif
+#if defined(_WIN32)
+#include "netinet/sctp_os_userspace.h"
+#endif
+
+/* maxsockets is used in SCTP_ZONE_INIT call. It refers to
+ * kern.ipc.maxsockets kernel environment variable.
+ */
+extern int maxsockets;
+
+/* int hz; is declared in sys/kern/subr_param.c and refers to kernel timer frequency.
+ * See http://ivoras.sharanet.org/freebsd/vmware.html for additional info about kern.hz
+ * hz is initialized in void init_param1(void) in that file.
+ */
+extern int hz;
+
+
+/* The following two ints define a range of available ephemeral ports. */
+extern int ipport_firstauto, ipport_lastauto;
+
+/* nmbclusters is used in sctp_usrreq.c (e.g., sctp_init). In the FreeBSD kernel,
+ * this is 1024 + maxusers * 64.
+ */
+extern int nmbclusters;
+
+#if !defined(_MSC_VER) && !defined(__MINGW32__)
+#define min(a,b) (((a)>(b))?(b):(a))
+#define max(a,b) (((a)>(b))?(a):(b))
+#endif
+
+void init_random(void);
+void read_random(void *, size_t);
+void finish_random(void);
+
+/* errno's may differ per OS. errno.h now included in sctp_os_userspace.h */
+/* Source: /usr/src/sys/sys/errno.h */
+/* #define ENOSPC 28 */ /* No space left on device */
+/* #define ENOBUFS 55 */ /* No buffer space available */
+/* #define ENOMEM 12 */ /* Cannot allocate memory */
+/* #define EACCES 13 */ /* Permission denied */
+/* #define EFAULT 14 */ /* Bad address */
+/* #define EHOSTDOWN 64 */ /* Host is down */
+/* #define EHOSTUNREACH 65 */ /* No route to host */
+
+/* Source ip_output.c. extern'd in ip_var.h */
+extern u_short ip_id;
+
+#if defined(__linux__)
+#define IPV6_VERSION 0x60
+#endif
+
+#if defined(INVARIANTS)
+#include <stdlib.h>
+
+#if defined(_WIN32)
+static inline void __declspec(noreturn)
+#else
+static inline void __attribute__((__noreturn__))
+#endif
+terminate_non_graceful(void) {
+ abort();
+}
+
+#define panic(...) \
+ do { \
+ SCTP_PRINTF("%s(): ", __func__); \
+ SCTP_PRINTF(__VA_ARGS__); \
+ SCTP_PRINTF("\n"); \
+ terminate_non_graceful(); \
+} while (0)
+
+#define KASSERT(cond, args) \
+ do { \
+ if (!(cond)) { \
+ panic args ; \
+ } \
+ } while (0)
+#else
+#define KASSERT(cond, args)
+#endif
+
+/* necessary for sctp_pcb.c */
+extern int ip_defttl;
+#endif
diff --git a/netwerk/sctp/src/user_inpcb.h b/netwerk/sctp/src/user_inpcb.h
new file mode 100644
index 0000000000..2e6e9334ea
--- /dev/null
+++ b/netwerk/sctp/src/user_inpcb.h
@@ -0,0 +1,373 @@
+/*-
+ * Copyright (c) 1982, 1986, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)in_pcb.h 8.1 (Berkeley) 6/10/93
+ * $FreeBSD: src/sys/netinet/in_pcb.h,v 1.100.2.1 2007/12/07 05:46:08 kmacy Exp $
+ */
+
+#ifndef _USER_INPCB_H_
+#define _USER_INPCB_H_
+
+#include <user_route.h> /* was <net/route.h> */
+
+struct inpcbpolicy;
+
+/*
+ * Struct inpcb is the ommon structure pcb for the Internet Protocol
+ * implementation.
+ *
+ * Pointers to local and foreign host table entries, local and foreign socket
+ * numbers, and pointers up (to a socket structure) and down (to a
+ * protocol-specific control block) are stored here.
+ */
+LIST_HEAD(inpcbhead, inpcb);
+LIST_HEAD(inpcbporthead, inpcbport);
+
+/*
+ * PCB with AF_INET6 null bind'ed laddr can receive AF_INET input packet.
+ * So, AF_INET6 null laddr is also used as AF_INET null laddr, by utilizing
+ * the following structure.
+ */
+struct in_addr_4in6 {
+ uint32_t ia46_pad32[3];
+ struct in_addr ia46_addr4;
+};
+
+/*
+ * NOTE: ipv6 addrs should be 64-bit aligned, per RFC 2553. in_conninfo has
+ * some extra padding to accomplish this.
+ */
+struct in_endpoints {
+ uint16_t ie_fport; /* foreign port */
+ uint16_t ie_lport; /* local port */
+ /* protocol dependent part, local and foreign addr */
+ union {
+ /* foreign host table entry */
+ struct in_addr_4in6 ie46_foreign;
+ struct in6_addr ie6_foreign;
+ } ie_dependfaddr;
+ union {
+ /* local host table entry */
+ struct in_addr_4in6 ie46_local;
+ struct in6_addr ie6_local;
+ } ie_dependladdr;
+#define ie_faddr ie_dependfaddr.ie46_foreign.ia46_addr4
+#define ie_laddr ie_dependladdr.ie46_local.ia46_addr4
+#define ie6_faddr ie_dependfaddr.ie6_foreign
+#define ie6_laddr ie_dependladdr.ie6_local
+};
+
+/*
+ * XXX The defines for inc_* are hacks and should be changed to direct
+ * references.
+ */
+struct in_conninfo {
+ uint8_t inc_flags;
+ uint8_t inc_len;
+ uint16_t inc_pad; /* XXX alignment for in_endpoints */
+ /* protocol dependent part */
+ struct in_endpoints inc_ie;
+};
+#define inc_isipv6 inc_flags /* temp compatibility */
+#define inc_fport inc_ie.ie_fport
+#define inc_lport inc_ie.ie_lport
+#define inc_faddr inc_ie.ie_faddr
+#define inc_laddr inc_ie.ie_laddr
+#define inc6_faddr inc_ie.ie6_faddr
+#define inc6_laddr inc_ie.ie6_laddr
+
+struct icmp6_filter;
+
+struct inpcb {
+ LIST_ENTRY(inpcb) inp_hash; /* hash list */
+ LIST_ENTRY(inpcb) inp_list; /* list for all PCBs of this proto */
+ void *inp_ppcb; /* pointer to per-protocol pcb */
+ struct inpcbinfo *inp_pcbinfo; /* PCB list info */
+ struct socket *inp_socket; /* back pointer to socket */
+
+ uint32_t inp_flow;
+ int inp_flags; /* generic IP/datagram flags */
+
+ u_char inp_vflag; /* IP version flag (v4/v6) */
+#define INP_IPV4 0x1
+#define INP_IPV6 0x2
+#define INP_IPV6PROTO 0x4 /* opened under IPv6 protocol */
+#define INP_TIMEWAIT 0x8 /* .. probably doesn't go here */
+#define INP_ONESBCAST 0x10 /* send all-ones broadcast */
+#define INP_DROPPED 0x20 /* protocol drop flag */
+#define INP_SOCKREF 0x40 /* strong socket reference */
+#define INP_CONN 0x80
+ u_char inp_ip_ttl; /* time to live proto */
+ u_char inp_ip_p; /* protocol proto */
+ u_char inp_ip_minttl; /* minimum TTL or drop */
+ uint32_t inp_ispare1; /* connection id / queue id */
+ void *inp_pspare[2]; /* rtentry / general use */
+
+ /* Local and foreign ports, local and foreign addr. */
+ struct in_conninfo inp_inc;
+
+ /* list for this PCB's local port */
+ struct label *inp_label; /* MAC label */
+ struct inpcbpolicy *inp_sp; /* for IPSEC */
+
+ /* Protocol-dependent part; options. */
+ struct {
+ u_char inp4_ip_tos; /* type of service proto */
+ struct mbuf *inp4_options; /* IP options */
+ struct ip_moptions *inp4_moptions; /* IP multicast options */
+ } inp_depend4;
+#define inp_fport inp_inc.inc_fport
+#define inp_lport inp_inc.inc_lport
+#define inp_faddr inp_inc.inc_faddr
+#define inp_laddr inp_inc.inc_laddr
+#define inp_ip_tos inp_depend4.inp4_ip_tos
+#define inp_options inp_depend4.inp4_options
+#define inp_moptions inp_depend4.inp4_moptions
+ struct {
+ /* IP options */
+ struct mbuf *inp6_options;
+ /* IP6 options for outgoing packets */
+ struct ip6_pktopts *inp6_outputopts;
+ /* IP multicast options */
+#if 0
+ struct ip6_moptions *inp6_moptions;
+#endif
+ /* ICMPv6 code type filter */
+ struct icmp6_filter *inp6_icmp6filt;
+ /* IPV6_CHECKSUM setsockopt */
+ int inp6_cksum;
+ short inp6_hops;
+ } inp_depend6;
+ LIST_ENTRY(inpcb) inp_portlist;
+ struct inpcbport *inp_phd; /* head of this list */
+#define inp_zero_size offsetof(struct inpcb, inp_gencnt)
+ struct mtx inp_mtx;
+
+#define in6p_faddr inp_inc.inc6_faddr
+#define in6p_laddr inp_inc.inc6_laddr
+#define in6p_hops inp_depend6.inp6_hops /* default hop limit */
+#define in6p_ip6_nxt inp_ip_p
+#define in6p_flowinfo inp_flow
+#define in6p_vflag inp_vflag
+#define in6p_options inp_depend6.inp6_options
+#define in6p_outputopts inp_depend6.inp6_outputopts
+#if 0
+#define in6p_moptions inp_depend6.inp6_moptions
+#endif
+#define in6p_icmp6filt inp_depend6.inp6_icmp6filt
+#define in6p_cksum inp_depend6.inp6_cksum
+#define in6p_flags inp_flags /* for KAME src sync over BSD*'s */
+#define in6p_socket inp_socket /* for KAME src sync over BSD*'s */
+#define in6p_lport inp_lport /* for KAME src sync over BSD*'s */
+#define in6p_fport inp_fport /* for KAME src sync over BSD*'s */
+#define in6p_ppcb inp_ppcb /* for KAME src sync over BSD*'s */
+};
+/*
+ * The range of the generation count, as used in this implementation, is 9e19.
+ * We would have to create 300 billion connections per second for this number
+ * to roll over in a year. This seems sufficiently unlikely that we simply
+ * don't concern ourselves with that possibility.
+ */
+
+struct inpcbport {
+ LIST_ENTRY(inpcbport) phd_hash;
+ struct inpcbhead phd_pcblist;
+ u_short phd_port;
+};
+
+/*
+ * Global data structure for each high-level protocol (UDP, TCP, ...) in both
+ * IPv4 and IPv6. Holds inpcb lists and information for managing them.
+ */
+struct inpcbinfo {
+ /*
+ * Global list of inpcbs on the protocol.
+ */
+ struct inpcbhead *ipi_listhead;
+ u_int ipi_count;
+
+ /*
+ * Global hash of inpcbs, hashed by local and foreign addresses and
+ * port numbers.
+ */
+ struct inpcbhead *ipi_hashbase;
+ u_long ipi_hashmask;
+
+ /*
+ * Global hash of inpcbs, hashed by only local port number.
+ */
+ struct inpcbporthead *ipi_porthashbase;
+ u_long ipi_porthashmask;
+
+ /*
+ * Fields associated with port lookup and allocation.
+ */
+ u_short ipi_lastport;
+ u_short ipi_lastlow;
+ u_short ipi_lasthi;
+
+ /*
+ * UMA zone from which inpcbs are allocated for this protocol.
+ */
+ struct uma_zone *ipi_zone;
+
+ /*
+ * Generation count--incremented each time a connection is allocated
+ * or freed.
+ */
+ struct mtx ipi_mtx;
+
+ /*
+ * vimage 1
+ * general use 1
+ */
+ void *ipi_pspare[2];
+};
+
+#define INP_LOCK_INIT(inp, d, t) \
+ mtx_init(&(inp)->inp_mtx, (d), (t), MTX_DEF | MTX_RECURSE | MTX_DUPOK)
+#define INP_LOCK_DESTROY(inp) mtx_destroy(&(inp)->inp_mtx)
+#define INP_LOCK(inp) mtx_lock(&(inp)->inp_mtx)
+#define INP_UNLOCK(inp) mtx_unlock(&(inp)->inp_mtx)
+#define INP_LOCK_ASSERT(inp) mtx_assert(&(inp)->inp_mtx, MA_OWNED)
+#define INP_UNLOCK_ASSERT(inp) mtx_assert(&(inp)->inp_mtx, MA_NOTOWNED)
+
+#define INP_INFO_LOCK_INIT(ipi, d) \
+ mtx_init(&(ipi)->ipi_mtx, (d), NULL, MTX_DEF | MTX_RECURSE)
+#define INP_INFO_LOCK_DESTROY(ipi) mtx_destroy(&(ipi)->ipi_mtx)
+#define INP_INFO_RLOCK(ipi) mtx_lock(&(ipi)->ipi_mtx)
+#define INP_INFO_WLOCK(ipi) mtx_lock(&(ipi)->ipi_mtx)
+#define INP_INFO_RUNLOCK(ipi) mtx_unlock(&(ipi)->ipi_mtx)
+#define INP_INFO_WUNLOCK(ipi) mtx_unlock(&(ipi)->ipi_mtx)
+#define INP_INFO_RLOCK_ASSERT(ipi) mtx_assert(&(ipi)->ipi_mtx, MA_OWNED)
+#define INP_INFO_WLOCK_ASSERT(ipi) mtx_assert(&(ipi)->ipi_mtx, MA_OWNED)
+#define INP_INFO_UNLOCK_ASSERT(ipi) mtx_assert(&(ipi)->ipi_mtx, MA_NOTOWNED)
+
+#define INP_PCBHASH(faddr, lport, fport, mask) \
+ (((faddr) ^ ((faddr) >> 16) ^ ntohs((lport) ^ (fport))) & (mask))
+#define INP_PCBPORTHASH(lport, mask) \
+ (ntohs((lport)) & (mask))
+
+/* flags in inp_flags: */
+#define INP_RECVOPTS 0x01 /* receive incoming IP options */
+#define INP_RECVRETOPTS 0x02 /* receive IP options for reply */
+#define INP_RECVDSTADDR 0x04 /* receive IP dst address */
+#define INP_HDRINCL 0x08 /* user supplies entire IP header */
+#define INP_HIGHPORT 0x10 /* user wants "high" port binding */
+#define INP_LOWPORT 0x20 /* user wants "low" port binding */
+#define INP_ANONPORT 0x40 /* port chosen for user */
+#define INP_RECVIF 0x80 /* receive incoming interface */
+#define INP_MTUDISC 0x100 /* user can do MTU discovery */
+#define INP_FAITH 0x200 /* accept FAITH'ed connections */
+#define INP_RECVTTL 0x400 /* receive incoming IP TTL */
+#define INP_DONTFRAG 0x800 /* don't fragment packet */
+
+#define IN6P_IPV6_V6ONLY 0x008000 /* restrict AF_INET6 socket for v6 */
+
+#define IN6P_PKTINFO 0x010000 /* receive IP6 dst and I/F */
+#define IN6P_HOPLIMIT 0x020000 /* receive hoplimit */
+#define IN6P_HOPOPTS 0x040000 /* receive hop-by-hop options */
+#define IN6P_DSTOPTS 0x080000 /* receive dst options after rthdr */
+#define IN6P_RTHDR 0x100000 /* receive routing header */
+#define IN6P_RTHDRDSTOPTS 0x200000 /* receive dstoptions before rthdr */
+#define IN6P_TCLASS 0x400000 /* receive traffic class value */
+#define IN6P_AUTOFLOWLABEL 0x800000 /* attach flowlabel automatically */
+#define IN6P_RFC2292 0x40000000 /* used RFC2292 API on the socket */
+#define IN6P_MTU 0x80000000 /* receive path MTU */
+
+#define INP_CONTROLOPTS (INP_RECVOPTS|INP_RECVRETOPTS|INP_RECVDSTADDR|\
+ INP_RECVIF|INP_RECVTTL|\
+ IN6P_PKTINFO|IN6P_HOPLIMIT|IN6P_HOPOPTS|\
+ IN6P_DSTOPTS|IN6P_RTHDR|IN6P_RTHDRDSTOPTS|\
+ IN6P_TCLASS|IN6P_AUTOFLOWLABEL|IN6P_RFC2292|\
+ IN6P_MTU)
+#define INP_UNMAPPABLEOPTS (IN6P_HOPOPTS|IN6P_DSTOPTS|IN6P_RTHDR|\
+ IN6P_TCLASS|IN6P_AUTOFLOWLABEL)
+
+ /* for KAME src sync over BSD*'s */
+#define IN6P_HIGHPORT INP_HIGHPORT
+#define IN6P_LOWPORT INP_LOWPORT
+#define IN6P_ANONPORT INP_ANONPORT
+#define IN6P_RECVIF INP_RECVIF
+#define IN6P_MTUDISC INP_MTUDISC
+#define IN6P_FAITH INP_FAITH
+#define IN6P_CONTROLOPTS INP_CONTROLOPTS
+ /*
+ * socket AF version is {newer than,or include}
+ * actual datagram AF version
+ */
+
+#define INPLOOKUP_WILDCARD 1
+#define sotoinpcb(so) ((struct inpcb *)(so)->so_pcb)
+
+#define INP_SOCKAF(so) so->so_proto->pr_domain->dom_family
+
+#define INP_CHECK_SOCKAF(so, af) (INP_SOCKAF(so) == af)
+
+extern int ipport_reservedhigh;
+extern int ipport_reservedlow;
+extern int ipport_lowfirstauto;
+extern int ipport_lowlastauto;
+extern int ipport_firstauto;
+extern int ipport_lastauto;
+extern int ipport_hifirstauto;
+extern int ipport_hilastauto;
+extern struct callout ipport_tick_callout;
+
+void in_pcbpurgeif0(struct inpcbinfo *, struct ifnet *);
+int in_pcballoc(struct socket *, struct inpcbinfo *);
+int in_pcbbind(struct inpcb *, struct sockaddr *, struct ucred *);
+int in_pcbconnect(struct inpcb *, struct sockaddr *, struct ucred *);
+void in_pcbdetach(struct inpcb *);
+void in_pcbdisconnect(struct inpcb *);
+void in_pcbdrop(struct inpcb *);
+void in_pcbfree(struct inpcb *);
+int in_pcbinshash(struct inpcb *);
+struct inpcb *
+ in_pcblookup_local(struct inpcbinfo *,
+ struct in_addr, u_int, int);
+struct inpcb *
+ in_pcblookup_hash(struct inpcbinfo *, struct in_addr, u_int,
+ struct in_addr, u_int, int, struct ifnet *);
+void in_pcbnotifyall(struct inpcbinfo *pcbinfo, struct in_addr,
+ int, struct inpcb *(*)(struct inpcb *, int));
+void in_pcbrehash(struct inpcb *);
+void in_pcbsetsolabel(struct socket *so);
+int in_getpeeraddr(struct socket *so, struct sockaddr **nam);
+int in_getsockaddr(struct socket *so, struct sockaddr **nam);
+void in_pcbsosetlabel(struct socket *so);
+void in_pcbremlists(struct inpcb *inp);
+void ipport_tick(void *xtp);
+
+/*
+ * Debugging routines compiled in when DDB is present.
+ */
+void db_print_inpcb(struct inpcb *inp, const char *name, int indent);
+
+
+#endif /* !_NETINET_IN_PCB_H_ */
diff --git a/netwerk/sctp/src/user_ip6_var.h b/netwerk/sctp/src/user_ip6_var.h
new file mode 100644
index 0000000000..b970fb8fa5
--- /dev/null
+++ b/netwerk/sctp/src/user_ip6_var.h
@@ -0,0 +1,124 @@
+/*-
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+/*-
+ * Copyright (c) 1982, 1986, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _USER_IP6_VAR_H_
+#define _USER_IP6_VAR_H_
+
+#if defined(_WIN32)
+struct ip6_hdr {
+ union {
+ struct ip6_hdrctl {
+ uint32_t ip6_un1_flow; /* 20 bits of flow-ID */
+ uint16_t ip6_un1_plen; /* payload length */
+ uint8_t ip6_un1_nxt; /* next header */
+ uint8_t ip6_un1_hlim; /* hop limit */
+ } ip6_un1;
+ uint8_t ip6_un2_vfc; /* 4 bits version, top 4 bits class */
+ } ip6_ctlun;
+ struct in6_addr ip6_src; /* source address */
+ struct in6_addr ip6_dst; /* destination address */
+};
+#define ip6_vfc ip6_ctlun.ip6_un2_vfc
+#define ip6_flow ip6_ctlun.ip6_un1.ip6_un1_flow
+#define ip6_plen ip6_ctlun.ip6_un1.ip6_un1_plen
+#define ip6_nxt ip6_ctlun.ip6_un1.ip6_un1_nxt
+#define ip6_hlim ip6_ctlun.ip6_un1.ip6_un1_hlim
+#define ip6_hops ip6_ctlun.ip6_un1.ip6_un1_hlim
+
+#define IPV6_VERSION 0x60
+#endif
+
+#if defined(_WIN32)
+#define s6_addr16 u.Word
+#endif
+#if !defined(_WIN32) && !defined(__linux__) && !defined(__EMSCRIPTEN__)
+#define s6_addr8 __u6_addr.__u6_addr8
+#define s6_addr16 __u6_addr.__u6_addr16
+#define s6_addr32 __u6_addr.__u6_addr32
+#endif
+
+#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__)
+struct route_in6 {
+ struct rtentry *ro_rt;
+ struct llentry *ro_lle;
+ struct in6_addr *ro_ia6;
+ int ro_flags;
+ struct sockaddr_in6 ro_dst;
+};
+#endif
+#define IP6_EXTHDR_GET(val, typ, m, off, len) \
+do { \
+ struct mbuf *t; \
+ int tmp; \
+ if ((m)->m_len >= (off) + (len)) \
+ (val) = (typ)(mtod((m), caddr_t) + (off)); \
+ else { \
+ t = m_pulldown((m), (off), (len), &tmp); \
+ if (t) { \
+ KASSERT(t->m_len >= tmp + (len), \
+ ("m_pulldown malfunction")); \
+ (val) = (typ)(mtod(t, caddr_t) + tmp); \
+ } else { \
+ (val) = (typ)NULL; \
+ (m) = NULL; \
+ } \
+ } \
+} while (0)
+
+#endif /* !_USER_IP6_VAR_H_ */
diff --git a/netwerk/sctp/src/user_ip_icmp.h b/netwerk/sctp/src/user_ip_icmp.h
new file mode 100644
index 0000000000..a993411a17
--- /dev/null
+++ b/netwerk/sctp/src/user_ip_icmp.h
@@ -0,0 +1,225 @@
+/*-
+ * Copyright (c) 1982, 1986, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _USER_IP_ICMP_H_
+#define _USER_IP_ICMP_H_
+
+/*
+ * Interface Control Message Protocol Definitions.
+ * Per RFC 792, September 1981.
+ */
+
+/*
+ * Internal of an ICMP Router Advertisement
+ */
+struct icmp_ra_addr {
+ uint32_t ira_addr;
+ uint32_t ira_preference;
+};
+
+/*
+ * Structure of an icmp header.
+ */
+struct icmphdr {
+ u_char icmp_type; /* type of message, see below */
+ u_char icmp_code; /* type sub code */
+ u_short icmp_cksum; /* ones complement cksum of struct */
+};
+
+#if defined(_WIN32)
+#pragma pack (push, 1)
+struct icmp6_hdr {
+ uint8_t icmp6_type;
+ uint8_t icmp6_code;
+ uint16_t icmp6_cksum;
+ union {
+ uint32_t icmp6_un_data32[1];
+ uint16_t icmp6_un_data16[2];
+ uint8_t icmp6_un_data8[4];
+ } icmp6_dataun;
+};
+#pragma pack(pop)
+
+#define icmp6_data32 icmp6_dataun.icmp6_un_data32
+#define icmp6_mtu icmp6_data32[0]
+#endif
+
+/*
+ * Structure of an icmp packet.
+ *
+ * XXX: should start with a struct icmphdr.
+ */
+struct icmp {
+ u_char icmp_type; /* type of message, see below */
+ u_char icmp_code; /* type sub code */
+ u_short icmp_cksum; /* ones complement cksum of struct */
+ union {
+ u_char ih_pptr; /* ICMP_PARAMPROB */
+ struct in_addr ih_gwaddr; /* ICMP_REDIRECT */
+ struct ih_idseq {
+ uint16_t icd_id; /* network format */
+ uint16_t icd_seq; /* network format */
+ } ih_idseq;
+ int ih_void;
+
+ /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
+ struct ih_pmtu {
+ uint16_t ipm_void; /* network format */
+ uint16_t ipm_nextmtu; /* network format */
+ } ih_pmtu;
+
+ struct ih_rtradv {
+ u_char irt_num_addrs;
+ u_char irt_wpa;
+ uint16_t irt_lifetime;
+ } ih_rtradv;
+ } icmp_hun;
+#define icmp_pptr icmp_hun.ih_pptr
+#define icmp_gwaddr icmp_hun.ih_gwaddr
+#define icmp_id icmp_hun.ih_idseq.icd_id
+#define icmp_seq icmp_hun.ih_idseq.icd_seq
+#define icmp_void icmp_hun.ih_void
+#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void
+#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu
+#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs
+#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa
+#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime
+ union {
+ struct id_ts { /* ICMP Timestamp */
+ /*
+ * The next 3 fields are in network format,
+ * milliseconds since 00:00 GMT
+ */
+ uint32_t its_otime; /* Originate */
+ uint32_t its_rtime; /* Receive */
+ uint32_t its_ttime; /* Transmit */
+ } id_ts;
+ struct id_ip {
+ struct ip idi_ip;
+ /* options and then 64 bits of data */
+ } id_ip;
+ struct icmp_ra_addr id_radv;
+ uint32_t id_mask;
+ char id_data[1];
+ } icmp_dun;
+#define icmp_otime icmp_dun.id_ts.its_otime
+#define icmp_rtime icmp_dun.id_ts.its_rtime
+#define icmp_ttime icmp_dun.id_ts.its_ttime
+#define icmp_ip icmp_dun.id_ip.idi_ip
+#define icmp_radv icmp_dun.id_radv
+#define icmp_mask icmp_dun.id_mask
+#define icmp_data icmp_dun.id_data
+};
+
+/*
+ * Lower bounds on packet lengths for various types.
+ * For the error advice packets must first insure that the
+ * packet is large enough to contain the returned ip header.
+ * Only then can we do the check to see if 64 bits of packet
+ * data have been returned, since we need to check the returned
+ * ip header length.
+ */
+#define ICMP_MINLEN 8 /* abs minimum */
+#define ICMP_TSLEN (8 + 3 * sizeof (uint32_t)) /* timestamp */
+#define ICMP_MASKLEN 12 /* address mask */
+#define ICMP_ADVLENMIN (8 + sizeof (struct ip) + 8) /* min */
+#define ICMP_ADVLEN(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8)
+ /* N.B.: must separately check that ip_hl >= 5 */
+
+/*
+ * Definition of type and code field values.
+ */
+#define ICMP_ECHOREPLY 0 /* echo reply */
+#define ICMP_UNREACH 3 /* dest unreachable, codes: */
+#define ICMP_UNREACH_NET 0 /* bad net */
+#define ICMP_UNREACH_HOST 1 /* bad host */
+#define ICMP_UNREACH_PROTOCOL 2 /* bad protocol */
+#define ICMP_UNREACH_PORT 3 /* bad port */
+#define ICMP_UNREACH_NEEDFRAG 4 /* IP_DF caused drop */
+#define ICMP_UNREACH_SRCFAIL 5 /* src route failed */
+#define ICMP_UNREACH_NET_UNKNOWN 6 /* unknown net */
+#define ICMP_UNREACH_HOST_UNKNOWN 7 /* unknown host */
+#define ICMP_UNREACH_ISOLATED 8 /* src host isolated */
+#define ICMP_UNREACH_NET_PROHIB 9 /* prohibited access */
+#define ICMP_UNREACH_HOST_PROHIB 10 /* ditto */
+#define ICMP_UNREACH_TOSNET 11 /* bad tos for net */
+#define ICMP_UNREACH_TOSHOST 12 /* bad tos for host */
+#define ICMP_UNREACH_FILTER_PROHIB 13 /* admin prohib */
+#define ICMP_UNREACH_HOST_PRECEDENCE 14 /* host prec vio. */
+#define ICMP_UNREACH_PRECEDENCE_CUTOFF 15 /* prec cutoff */
+#define ICMP_SOURCEQUENCH 4 /* packet lost, slow down */
+#define ICMP_REDIRECT 5 /* shorter route, codes: */
+#define ICMP_REDIRECT_NET 0 /* for network */
+#define ICMP_REDIRECT_HOST 1 /* for host */
+#define ICMP_REDIRECT_TOSNET 2 /* for tos and net */
+#define ICMP_REDIRECT_TOSHOST 3 /* for tos and host */
+#define ICMP_ALTHOSTADDR 6 /* alternate host address */
+#define ICMP_ECHO 8 /* echo service */
+#define ICMP_ROUTERADVERT 9 /* router advertisement */
+#define ICMP_ROUTERADVERT_NORMAL 0 /* normal advertisement */
+#define ICMP_ROUTERADVERT_NOROUTE_COMMON 16 /* selective routing */
+#define ICMP_ROUTERSOLICIT 10 /* router solicitation */
+#define ICMP_TIMXCEED 11 /* time exceeded, code: */
+#define ICMP_TIMXCEED_INTRANS 0 /* ttl==0 in transit */
+#define ICMP_TIMXCEED_REASS 1 /* ttl==0 in reass */
+#define ICMP_PARAMPROB 12 /* ip header bad */
+#define ICMP_PARAMPROB_ERRATPTR 0 /* error at param ptr */
+#define ICMP_PARAMPROB_OPTABSENT 1 /* req. opt. absent */
+#define ICMP_PARAMPROB_LENGTH 2 /* bad length */
+#define ICMP_TSTAMP 13 /* timestamp request */
+#define ICMP_TSTAMPREPLY 14 /* timestamp reply */
+#define ICMP_IREQ 15 /* information request */
+#define ICMP_IREQREPLY 16 /* information reply */
+#define ICMP_MASKREQ 17 /* address mask request */
+#define ICMP_MASKREPLY 18 /* address mask reply */
+#define ICMP_TRACEROUTE 30 /* traceroute */
+#define ICMP_DATACONVERR 31 /* data conversion error */
+#define ICMP_MOBILE_REDIRECT 32 /* mobile host redirect */
+#define ICMP_IPV6_WHEREAREYOU 33 /* IPv6 where-are-you */
+#define ICMP_IPV6_IAMHERE 34 /* IPv6 i-am-here */
+#define ICMP_MOBILE_REGREQUEST 35 /* mobile registration req */
+#define ICMP_MOBILE_REGREPLY 36 /* mobile registration reply */
+#define ICMP_SKIP 39 /* SKIP */
+#define ICMP_PHOTURIS 40 /* Photuris */
+#define ICMP_PHOTURIS_UNKNOWN_INDEX 1 /* unknown sec index */
+#define ICMP_PHOTURIS_AUTH_FAILED 2 /* auth failed */
+#define ICMP_PHOTURIS_DECRYPT_FAILED 3 /* decrypt failed */
+
+#define ICMP_MAXTYPE 40
+
+#define ICMP_INFOTYPE(type) \
+ ((type) == ICMP_ECHOREPLY || (type) == ICMP_ECHO || \
+ (type) == ICMP_ROUTERADVERT || (type) == ICMP_ROUTERSOLICIT || \
+ (type) == ICMP_TSTAMP || (type) == ICMP_TSTAMPREPLY || \
+ (type) == ICMP_IREQ || (type) == ICMP_IREQREPLY || \
+ (type) == ICMP_MASKREQ || (type) == ICMP_MASKREPLY)
+
+
+#endif
diff --git a/netwerk/sctp/src/user_malloc.h b/netwerk/sctp/src/user_malloc.h
new file mode 100644
index 0000000000..c588e094af
--- /dev/null
+++ b/netwerk/sctp/src/user_malloc.h
@@ -0,0 +1,203 @@
+/*-
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California.
+ * Copyright (c) 2005 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/* This file has been renamed user_malloc.h for Userspace */
+#ifndef _USER_MALLOC_H_
+#define _USER_MALLOC_H_
+
+/*__Userspace__*/
+#include <stdlib.h>
+#include <sys/types.h>
+#if !defined(_WIN32)
+#include <strings.h>
+#include <stdint.h>
+#else
+#if (defined(_MSC_VER) && _MSC_VER >= 1600) || (defined(__MSVCRT_VERSION__) && __MSVCRT_VERSION__ >= 1400)
+#include <stdint.h>
+#elif defined(SCTP_STDINT_INCLUDE)
+#include SCTP_STDINT_INCLUDE
+#else
+#define uint32_t unsigned __int32
+#define uint64_t unsigned __int64
+#endif
+#include <winsock2.h>
+#endif
+
+#define MINALLOCSIZE UMA_SMALLEST_UNIT
+
+/*
+ * flags to malloc.
+ */
+#define M_NOWAIT 0x0001 /* do not block */
+#define M_WAITOK 0x0002 /* ok to block */
+#define M_ZERO 0x0100 /* bzero the allocation */
+#define M_NOVM 0x0200 /* don't ask VM for pages */
+#define M_USE_RESERVE 0x0400 /* can alloc out of reserve memory */
+
+#define M_MAGIC 877983977 /* time when first defined :-) */
+
+/*
+ * Two malloc type structures are present: malloc_type, which is used by a
+ * type owner to declare the type, and malloc_type_internal, which holds
+ * malloc-owned statistics and other ABI-sensitive fields, such as the set of
+ * malloc statistics indexed by the compile-time MAXCPU constant.
+ * Applications should avoid introducing dependence on the allocator private
+ * data layout and size.
+ *
+ * The malloc_type ks_next field is protected by malloc_mtx. Other fields in
+ * malloc_type are static after initialization so unsynchronized.
+ *
+ * Statistics in malloc_type_stats are written only when holding a critical
+ * section and running on the CPU associated with the index into the stat
+ * array, but read lock-free resulting in possible (minor) races, which the
+ * monitoring app should take into account.
+ */
+struct malloc_type_stats {
+ uint64_t mts_memalloced; /* Bytes allocated on CPU. */
+ uint64_t mts_memfreed; /* Bytes freed on CPU. */
+ uint64_t mts_numallocs; /* Number of allocates on CPU. */
+ uint64_t mts_numfrees; /* number of frees on CPU. */
+ uint64_t mts_size; /* Bitmask of sizes allocated on CPU. */
+ uint64_t _mts_reserved1; /* Reserved field. */
+ uint64_t _mts_reserved2; /* Reserved field. */
+ uint64_t _mts_reserved3; /* Reserved field. */
+};
+
+#ifndef MAXCPU /* necessary on Linux */
+#define MAXCPU 4 /* arbitrary? */
+#endif
+
+struct malloc_type_internal {
+ struct malloc_type_stats mti_stats[MAXCPU];
+};
+
+/*
+ * ABI-compatible version of the old 'struct malloc_type', only all stats are
+ * now malloc-managed in malloc-owned memory rather than in caller memory, so
+ * as to avoid ABI issues. The ks_next pointer is reused as a pointer to the
+ * internal data handle.
+ */
+struct malloc_type {
+ struct malloc_type *ks_next; /* Next in global chain. */
+ u_long _ks_memuse; /* No longer used. */
+ u_long _ks_size; /* No longer used. */
+ u_long _ks_inuse; /* No longer used. */
+ uint64_t _ks_calls; /* No longer used. */
+ u_long _ks_maxused; /* No longer used. */
+ u_long ks_magic; /* Detect programmer error. */
+ const char *ks_shortdesc; /* Printable type name. */
+
+ /*
+ * struct malloc_type was terminated with a struct mtx, which is no
+ * longer required. For ABI reasons, continue to flesh out the full
+ * size of the old structure, but reuse the _lo_class field for our
+ * internal data handle.
+ */
+ void *ks_handle; /* Priv. data, was lo_class. */
+ const char *_lo_name;
+ const char *_lo_type;
+ u_int _lo_flags;
+ void *_lo_list_next;
+ struct witness *_lo_witness;
+ uintptr_t _mtx_lock;
+ u_int _mtx_recurse;
+};
+
+/*
+ * Statistics structure headers for user space. The kern.malloc sysctl
+ * exposes a structure stream consisting of a stream header, then a series of
+ * malloc type headers and statistics structures (quantity maxcpus). For
+ * convenience, the kernel will provide the current value of maxcpus at the
+ * head of the stream.
+ */
+#define MALLOC_TYPE_STREAM_VERSION 0x00000001
+struct malloc_type_stream_header {
+ uint32_t mtsh_version; /* Stream format version. */
+ uint32_t mtsh_maxcpus; /* Value of MAXCPU for stream. */
+ uint32_t mtsh_count; /* Number of records. */
+ uint32_t _mtsh_pad; /* Pad/reserved field. */
+};
+
+#define MALLOC_MAX_NAME 32
+struct malloc_type_header {
+ char mth_name[MALLOC_MAX_NAME];
+};
+
+/* __Userspace__
+Notice that at places it uses ifdef _KERNEL. That line cannot be
+removed because it causes conflicts with malloc definition in
+/usr/include/malloc.h, which essentially says that malloc.h has
+been overridden by stdlib.h. We will need to use names like
+user_malloc.h for isolating kernel interface headers. using
+original names like malloc.h in a user_include header can be
+confusing, All userspace header files are being placed in ./user_include
+Better still to remove from user_include.h all irrelevant code such
+as that in the block starting with #ifdef _KERNEL. I am only leaving
+it in for the time being to see what functionality is in this file
+that kernel uses.
+
+Start copy: Copied code for __Userspace__ */
+#define MALLOC_DEFINE(type, shortdesc, longdesc) \
+ struct malloc_type type[1] = { \
+ { NULL, 0, 0, 0, 0, 0, M_MAGIC, shortdesc, NULL, NULL, \
+ NULL, 0, NULL, NULL, 0, 0 } \
+ }
+
+/* Removed "extern" in __Userspace__ code */
+/* If we need to use MALLOC_DECLARE before using MALLOC then
+ we have to remove extern.
+ In /usr/include/sys/malloc.h there is this definition:
+ #define MALLOC_DECLARE(type) \
+ extern struct malloc_type type[1]
+ and loader is unable to find the extern malloc_type because
+ it may be defined in one of kernel object files.
+ It seems that MALLOC_DECLARE and MALLOC_DEFINE cannot be used at
+ the same time for same "type" variable. Also, in Randall's architecture
+ document, where it specifies O/S specific macros and functions, it says
+ that the name in SCTP_MALLOC does not have to be used.
+*/
+#define MALLOC_DECLARE(type) \
+ extern struct malloc_type type[1]
+
+#define FREE(addr, type) free((addr))
+
+/* changed definitions of MALLOC and FREE */
+/* Using memset if flag M_ZERO is specified. Todo: M_WAITOK and M_NOWAIT */
+#define MALLOC(space, cast, size, type, flags) \
+ ((space) = (cast)malloc((u_long)(size))); \
+ do { \
+ if (flags & M_ZERO) { \
+ memset(space,0,size); \
+ } \
+ } while (0);
+
+#endif /* !_SYS_MALLOC_H_ */
diff --git a/netwerk/sctp/src/user_mbuf.c b/netwerk/sctp/src/user_mbuf.c
new file mode 100644
index 0000000000..85badc0fa5
--- /dev/null
+++ b/netwerk/sctp/src/user_mbuf.c
@@ -0,0 +1,1589 @@
+/*-
+ * Copyright (c) 1982, 1986, 1988, 1993
+ * The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ * __Userspace__ version of /usr/src/sys/kern/kern_mbuf.c
+ * We are initializing two zones for Mbufs and Clusters.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+/* #include <sys/param.h> This defines MSIZE 256 */
+#if !defined(SCTP_SIMPLE_ALLOCATOR)
+#include "umem.h"
+#endif
+#include "user_mbuf.h"
+#include "user_environment.h"
+#include "user_atomic.h"
+#include "netinet/sctp_pcb.h"
+
+#define KIPC_MAX_LINKHDR 4 /* int: max length of link header (see sys/sysclt.h) */
+#define KIPC_MAX_PROTOHDR 5 /* int: max length of network header (see sys/sysclt.h)*/
+int max_linkhdr = KIPC_MAX_LINKHDR;
+int max_protohdr = KIPC_MAX_PROTOHDR; /* Size of largest protocol layer header. */
+
+/*
+ * Zones from which we allocate.
+ */
+sctp_zone_t zone_mbuf;
+sctp_zone_t zone_clust;
+sctp_zone_t zone_ext_refcnt;
+
+/* __Userspace__ clust_mb_args will be passed as callback data to mb_ctor_clust
+ * and mb_dtor_clust.
+ * Note: I had to use struct clust_args as an encapsulation for an mbuf pointer.
+ * struct mbuf * clust_mb_args; does not work.
+ */
+struct clust_args clust_mb_args;
+
+
+/* __Userspace__
+ * Local prototypes.
+ */
+static int mb_ctor_mbuf(void *, void *, int);
+static int mb_ctor_clust(void *, void *, int);
+static void mb_dtor_mbuf(void *, void *);
+static void mb_dtor_clust(void *, void *);
+
+
+/***************** Functions taken from user_mbuf.h *************/
+
+static int mbuf_constructor_dup(struct mbuf *m, int pkthdr, short type)
+{
+ int flags = pkthdr;
+
+ m->m_next = NULL;
+ m->m_nextpkt = NULL;
+ m->m_len = 0;
+ m->m_flags = flags;
+ m->m_type = type;
+ if (flags & M_PKTHDR) {
+ m->m_data = m->m_pktdat;
+ m->m_pkthdr.rcvif = NULL;
+ m->m_pkthdr.len = 0;
+ m->m_pkthdr.header = NULL;
+ m->m_pkthdr.csum_flags = 0;
+ m->m_pkthdr.csum_data = 0;
+ m->m_pkthdr.tso_segsz = 0;
+ m->m_pkthdr.ether_vtag = 0;
+ SLIST_INIT(&m->m_pkthdr.tags);
+ } else
+ m->m_data = m->m_dat;
+
+ return (0);
+}
+
+/* __Userspace__ */
+struct mbuf *
+m_get(int how, short type)
+{
+ struct mbuf *mret;
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ struct mb_args mbuf_mb_args;
+
+ /* The following setter function is not yet being enclosed within
+ * #if USING_MBUF_CONSTRUCTOR - #endif, until I have thoroughly tested
+ * mb_dtor_mbuf. See comment there
+ */
+ mbuf_mb_args.flags = 0;
+ mbuf_mb_args.type = type;
+#endif
+ /* Mbuf master zone, zone_mbuf, has already been
+ * created in mbuf_initialize() */
+ mret = SCTP_ZONE_GET(zone_mbuf, struct mbuf);
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ mb_ctor_mbuf(mret, &mbuf_mb_args, 0);
+#endif
+ /*mret = ((struct mbuf *)umem_cache_alloc(zone_mbuf, UMEM_DEFAULT));*/
+
+ /* There are cases when an object available in the current CPU's
+ * loaded magazine and in those cases the object's constructor is not applied.
+ * If that is the case, then we are duplicating constructor initialization here,
+ * so that the mbuf is properly constructed before returning it.
+ */
+ if (mret) {
+#if USING_MBUF_CONSTRUCTOR
+ if (! (mret->m_type == type) ) {
+ mbuf_constructor_dup(mret, 0, type);
+ }
+#else
+ mbuf_constructor_dup(mret, 0, type);
+#endif
+
+ }
+ return mret;
+}
+
+
+/* __Userspace__ */
+struct mbuf *
+m_gethdr(int how, short type)
+{
+ struct mbuf *mret;
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ struct mb_args mbuf_mb_args;
+
+ /* The following setter function is not yet being enclosed within
+ * #if USING_MBUF_CONSTRUCTOR - #endif, until I have thoroughly tested
+ * mb_dtor_mbuf. See comment there
+ */
+ mbuf_mb_args.flags = M_PKTHDR;
+ mbuf_mb_args.type = type;
+#endif
+ mret = SCTP_ZONE_GET(zone_mbuf, struct mbuf);
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ mb_ctor_mbuf(mret, &mbuf_mb_args, 0);
+#endif
+ /*mret = ((struct mbuf *)umem_cache_alloc(zone_mbuf, UMEM_DEFAULT));*/
+ /* There are cases when an object available in the current CPU's
+ * loaded magazine and in those cases the object's constructor is not applied.
+ * If that is the case, then we are duplicating constructor initialization here,
+ * so that the mbuf is properly constructed before returning it.
+ */
+ if (mret) {
+#if USING_MBUF_CONSTRUCTOR
+ if (! ((mret->m_flags & M_PKTHDR) && (mret->m_type == type)) ) {
+ mbuf_constructor_dup(mret, M_PKTHDR, type);
+ }
+#else
+ mbuf_constructor_dup(mret, M_PKTHDR, type);
+#endif
+ }
+ return mret;
+}
+
+/* __Userspace__ */
+struct mbuf *
+m_free(struct mbuf *m)
+{
+
+ struct mbuf *n = m->m_next;
+
+ if (m->m_flags & M_EXT)
+ mb_free_ext(m);
+ else if ((m->m_flags & M_NOFREE) == 0) {
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ mb_dtor_mbuf(m, NULL);
+#endif
+ SCTP_ZONE_FREE(zone_mbuf, m);
+ }
+ /*umem_cache_free(zone_mbuf, m);*/
+ return (n);
+}
+
+
+static void
+clust_constructor_dup(caddr_t m_clust, struct mbuf* m)
+{
+ u_int *refcnt;
+ int type, size;
+
+ if (m == NULL) {
+ return;
+ }
+ /* Assigning cluster of MCLBYTES. TODO: Add jumbo frame functionality */
+ type = EXT_CLUSTER;
+ size = MCLBYTES;
+
+ refcnt = SCTP_ZONE_GET(zone_ext_refcnt, u_int);
+ /*refcnt = (u_int *)umem_cache_alloc(zone_ext_refcnt, UMEM_DEFAULT);*/
+#if !defined(SCTP_SIMPLE_ALLOCATOR)
+ if (refcnt == NULL) {
+ umem_reap();
+ refcnt = SCTP_ZONE_GET(zone_ext_refcnt, u_int);
+ /*refcnt = (u_int *)umem_cache_alloc(zone_ext_refcnt, UMEM_DEFAULT);*/
+ }
+#endif
+ *refcnt = 1;
+ m->m_ext.ext_buf = (caddr_t)m_clust;
+ m->m_data = m->m_ext.ext_buf;
+ m->m_flags |= M_EXT;
+ m->m_ext.ext_free = NULL;
+ m->m_ext.ext_args = NULL;
+ m->m_ext.ext_size = size;
+ m->m_ext.ext_type = type;
+ m->m_ext.ref_cnt = refcnt;
+ return;
+}
+
+
+/* __Userspace__ */
+void
+m_clget(struct mbuf *m, int how)
+{
+ caddr_t mclust_ret;
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ struct clust_args clust_mb_args_l;
+#endif
+ if (m->m_flags & M_EXT) {
+ SCTPDBG(SCTP_DEBUG_USR, "%s: %p mbuf already has cluster\n", __func__, (void *)m);
+ }
+ m->m_ext.ext_buf = (char *)NULL;
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ clust_mb_args_l.parent_mbuf = m;
+#endif
+ mclust_ret = SCTP_ZONE_GET(zone_clust, char);
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ mb_ctor_clust(mclust_ret, &clust_mb_args_l, 0);
+#endif
+ /*mclust_ret = umem_cache_alloc(zone_clust, UMEM_DEFAULT);*/
+ /*
+ On a cluster allocation failure, call umem_reap() and retry.
+ */
+
+ if (mclust_ret == NULL) {
+#if !defined(SCTP_SIMPLE_ALLOCATOR)
+ /* mclust_ret = SCTP_ZONE_GET(zone_clust, char);
+ mb_ctor_clust(mclust_ret, &clust_mb_args, 0);
+#else*/
+ umem_reap();
+ mclust_ret = SCTP_ZONE_GET(zone_clust, char);
+#endif
+ /*mclust_ret = umem_cache_alloc(zone_clust, UMEM_DEFAULT);*/
+ /* if (NULL == mclust_ret) { */
+ SCTPDBG(SCTP_DEBUG_USR, "Memory allocation failure in %s\n", __func__);
+ /* } */
+ }
+
+#if USING_MBUF_CONSTRUCTOR
+ if ((m->m_ext.ext_buf == NULL)) {
+ clust_constructor_dup(mclust_ret, m);
+ }
+#else
+ clust_constructor_dup(mclust_ret, m);
+#endif
+}
+
+struct mbuf *
+m_getm2(struct mbuf *m, int len, int how, short type, int flags, int allonebuf)
+{
+ struct mbuf *mb, *nm = NULL, *mtail = NULL;
+ int size, mbuf_threshold, space_needed = len;
+
+ KASSERT(len >= 0, ("%s: len is < 0", __func__));
+
+ /* Validate flags. */
+ flags &= (M_PKTHDR | M_EOR);
+
+ /* Packet header mbuf must be first in chain. */
+ if ((flags & M_PKTHDR) && m != NULL) {
+ flags &= ~M_PKTHDR;
+ }
+
+ if (allonebuf == 0)
+ mbuf_threshold = SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count);
+ else
+ mbuf_threshold = 1;
+
+ /* Loop and append maximum sized mbufs to the chain tail. */
+ while (len > 0) {
+ if ((!allonebuf && len >= MCLBYTES) || (len > (int)(((mbuf_threshold - 1) * MLEN) + MHLEN))) {
+ mb = m_gethdr(how, type);
+ MCLGET(mb, how);
+ size = MCLBYTES;
+ /* SCTP_BUF_LEN(mb) = MCLBYTES; */
+ } else if (flags & M_PKTHDR) {
+ mb = m_gethdr(how, type);
+ if (len < MHLEN) {
+ size = len;
+ } else {
+ size = MHLEN;
+ }
+ } else {
+ mb = m_get(how, type);
+ if (len < MLEN) {
+ size = len;
+ } else {
+ size = MLEN;
+ }
+ }
+
+ /* Fail the whole operation if one mbuf can't be allocated. */
+ if (mb == NULL) {
+ if (nm != NULL)
+ m_freem(nm);
+ return (NULL);
+ }
+
+ if (allonebuf != 0 && size < space_needed) {
+ m_freem(mb);
+ return (NULL);
+ }
+
+ /* Book keeping. */
+ len -= size;
+ if (mtail != NULL)
+ mtail->m_next = mb;
+ else
+ nm = mb;
+ mtail = mb;
+ flags &= ~M_PKTHDR; /* Only valid on the first mbuf. */
+ }
+ if (flags & M_EOR) {
+ mtail->m_flags |= M_EOR; /* Only valid on the last mbuf. */
+ }
+
+ /* If mbuf was supplied, append new chain to the end of it. */
+ if (m != NULL) {
+ for (mtail = m; mtail->m_next != NULL; mtail = mtail->m_next);
+ mtail->m_next = nm;
+ mtail->m_flags &= ~M_EOR;
+ } else {
+ m = nm;
+ }
+
+ return (m);
+}
+
+/*
+ * Copy the contents of uio into a properly sized mbuf chain.
+ */
+struct mbuf *
+m_uiotombuf(struct uio *uio, int how, int len, int align, int flags)
+{
+ struct mbuf *m, *mb;
+ int error, length;
+ ssize_t total;
+ int progress = 0;
+
+ /*
+ * len can be zero or an arbitrary large value bound by
+ * the total data supplied by the uio.
+ */
+ if (len > 0)
+ total = min(uio->uio_resid, len);
+ else
+ total = uio->uio_resid;
+ /*
+ * The smallest unit returned by m_getm2() is a single mbuf
+ * with pkthdr. We can't align past it.
+ */
+ if (align >= MHLEN)
+ return (NULL);
+ /*
+ * Give us the full allocation or nothing.
+ * If len is zero return the smallest empty mbuf.
+ */
+ m = m_getm2(NULL, (int)max(total + align, 1), how, MT_DATA, flags, 0);
+ if (m == NULL)
+ return (NULL);
+ m->m_data += align;
+
+ /* Fill all mbufs with uio data and update header information. */
+ for (mb = m; mb != NULL; mb = mb->m_next) {
+ length = (int)min(M_TRAILINGSPACE(mb), total - progress);
+ error = uiomove(mtod(mb, void *), length, uio);
+ if (error) {
+ m_freem(m);
+ return (NULL);
+ }
+
+ mb->m_len = length;
+ progress += length;
+ if (flags & M_PKTHDR)
+ m->m_pkthdr.len += length;
+ }
+ KASSERT(progress == total, ("%s: progress != total", __func__));
+
+ return (m);
+}
+
+u_int
+m_length(struct mbuf *m0, struct mbuf **last)
+{
+ struct mbuf *m;
+ u_int len;
+
+ len = 0;
+ for (m = m0; m != NULL; m = m->m_next) {
+ len += m->m_len;
+ if (m->m_next == NULL)
+ break;
+ }
+ if (last != NULL)
+ *last = m;
+ return (len);
+}
+
+struct mbuf *
+m_last(struct mbuf *m)
+{
+ while (m->m_next) {
+ m = m->m_next;
+ }
+ return (m);
+}
+
+/*
+ * Unlink a tag from the list of tags associated with an mbuf.
+ */
+static __inline void
+m_tag_unlink(struct mbuf *m, struct m_tag *t)
+{
+
+ SLIST_REMOVE(&m->m_pkthdr.tags, t, m_tag, m_tag_link);
+}
+
+/*
+ * Reclaim resources associated with a tag.
+ */
+static __inline void
+m_tag_free(struct m_tag *t)
+{
+
+ (*t->m_tag_free)(t);
+}
+
+/*
+ * Set up the contents of a tag. Note that this does not fill in the free
+ * method; the caller is expected to do that.
+ *
+ * XXX probably should be called m_tag_init, but that was already taken.
+ */
+static __inline void
+m_tag_setup(struct m_tag *t, uint32_t cookie, int type, int len)
+{
+
+ t->m_tag_id = type;
+ t->m_tag_len = len;
+ t->m_tag_cookie = cookie;
+}
+
+/************ End functions from user_mbuf.h ******************/
+
+
+
+/************ End functions to substitute umem_cache_alloc and umem_cache_free **************/
+
+void
+mbuf_initialize(void *dummy)
+{
+
+ /*
+ * __Userspace__Configure UMA zones for Mbufs and Clusters.
+ * (TODO: m_getcl() - using packet secondary zone).
+ * There is no provision for trash_init and trash_fini in umem.
+ *
+ */
+ /* zone_mbuf = umem_cache_create(MBUF_MEM_NAME, MSIZE, 0,
+ mb_ctor_mbuf, mb_dtor_mbuf, NULL,
+ &mbuf_mb_args,
+ NULL, 0);
+ zone_mbuf = umem_cache_create(MBUF_MEM_NAME, MSIZE, 0, NULL, NULL, NULL, NULL, NULL, 0);*/
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ SCTP_ZONE_INIT(zone_mbuf, MBUF_MEM_NAME, MSIZE, 0);
+#else
+ zone_mbuf = umem_cache_create(MBUF_MEM_NAME, MSIZE, 0,
+ mb_ctor_mbuf, mb_dtor_mbuf, NULL,
+ NULL,
+ NULL, 0);
+#endif
+ /*zone_ext_refcnt = umem_cache_create(MBUF_EXTREFCNT_MEM_NAME, sizeof(u_int), 0,
+ NULL, NULL, NULL,
+ NULL,
+ NULL, 0);*/
+ SCTP_ZONE_INIT(zone_ext_refcnt, MBUF_EXTREFCNT_MEM_NAME, sizeof(u_int), 0);
+
+ /*zone_clust = umem_cache_create(MBUF_CLUSTER_MEM_NAME, MCLBYTES, 0,
+ mb_ctor_clust, mb_dtor_clust, NULL,
+ &clust_mb_args,
+ NULL, 0);
+ zone_clust = umem_cache_create(MBUF_CLUSTER_MEM_NAME, MCLBYTES, 0, NULL, NULL, NULL, NULL, NULL,0);*/
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ SCTP_ZONE_INIT(zone_clust, MBUF_CLUSTER_MEM_NAME, MCLBYTES, 0);
+#else
+ zone_clust = umem_cache_create(MBUF_CLUSTER_MEM_NAME, MCLBYTES, 0,
+ mb_ctor_clust, mb_dtor_clust, NULL,
+ &clust_mb_args,
+ NULL, 0);
+#endif
+
+ /* uma_prealloc() goes here... */
+
+ /* __Userspace__ Add umem_reap here for low memory situation?
+ *
+ */
+
+}
+
+
+
+/*
+ * __Userspace__
+ *
+ * Constructor for Mbuf master zone. We have a different constructor
+ * for allocating the cluster.
+ *
+ * The 'arg' pointer points to a mb_args structure which
+ * contains call-specific information required to support the
+ * mbuf allocation API. See user_mbuf.h.
+ *
+ * The flgs parameter below can be UMEM_DEFAULT or UMEM_NOFAIL depending on what
+ * was passed when umem_cache_alloc was called.
+ * TODO: Use UMEM_NOFAIL in umem_cache_alloc and also define a failure handler
+ * and call umem_nofail_callback(my_failure_handler) in the stack initialization routines
+ * The advantage of using UMEM_NOFAIL is that we don't have to check if umem_cache_alloc
+ * was successful or not. The failure handler would take care of it, if we use the UMEM_NOFAIL
+ * flag.
+ *
+ * NOTE Ref: http://docs.sun.com/app/docs/doc/819-2243/6n4i099p2?l=en&a=view&q=umem_zalloc)
+ * The umem_nofail_callback() function sets the **process-wide** UMEM_NOFAIL callback.
+ * It also mentions that umem_nofail_callback is Evolving.
+ *
+ */
+static int
+mb_ctor_mbuf(void *mem, void *arg, int flgs)
+{
+#if USING_MBUF_CONSTRUCTOR
+ struct mbuf *m;
+ struct mb_args *args;
+
+ int flags;
+ short type;
+
+ m = (struct mbuf *)mem;
+ args = (struct mb_args *)arg;
+ flags = args->flags;
+ type = args->type;
+
+ m->m_next = NULL;
+ m->m_nextpkt = NULL;
+ m->m_len = 0;
+ m->m_flags = flags;
+ m->m_type = type;
+ if (flags & M_PKTHDR) {
+ m->m_data = m->m_pktdat;
+ m->m_pkthdr.rcvif = NULL;
+ m->m_pkthdr.len = 0;
+ m->m_pkthdr.header = NULL;
+ m->m_pkthdr.csum_flags = 0;
+ m->m_pkthdr.csum_data = 0;
+ m->m_pkthdr.tso_segsz = 0;
+ m->m_pkthdr.ether_vtag = 0;
+ SLIST_INIT(&m->m_pkthdr.tags);
+ } else
+ m->m_data = m->m_dat;
+#endif
+ return (0);
+}
+
+
+/*
+ * __Userspace__
+ * The Mbuf master zone destructor.
+ * This would be called in response to umem_cache_destroy
+ * TODO: Recheck if this is what we want to do in this destructor.
+ * (Note: the number of times mb_dtor_mbuf is called is equal to the
+ * number of individual mbufs allocated from zone_mbuf.
+ */
+static void
+mb_dtor_mbuf(void *mem, void *arg)
+{
+ struct mbuf *m;
+
+ m = (struct mbuf *)mem;
+ if ((m->m_flags & M_PKTHDR) != 0) {
+ m_tag_delete_chain(m, NULL);
+ }
+}
+
+
+/* __Userspace__
+ * The Cluster zone constructor.
+ *
+ * Here the 'arg' pointer points to the Mbuf which we
+ * are configuring cluster storage for. If 'arg' is
+ * empty we allocate just the cluster without setting
+ * the mbuf to it. See mbuf.h.
+ */
+static int
+mb_ctor_clust(void *mem, void *arg, int flgs)
+{
+
+#if USING_MBUF_CONSTRUCTOR
+ struct mbuf *m;
+ struct clust_args * cla;
+ u_int *refcnt;
+ int type, size;
+ sctp_zone_t zone;
+
+ /* Assigning cluster of MCLBYTES. TODO: Add jumbo frame functionality */
+ type = EXT_CLUSTER;
+ zone = zone_clust;
+ size = MCLBYTES;
+
+ cla = (struct clust_args *)arg;
+ m = cla->parent_mbuf;
+
+ refcnt = SCTP_ZONE_GET(zone_ext_refcnt, u_int);
+ /*refcnt = (u_int *)umem_cache_alloc(zone_ext_refcnt, UMEM_DEFAULT);*/
+ *refcnt = 1;
+
+ if (m != NULL) {
+ m->m_ext.ext_buf = (caddr_t)mem;
+ m->m_data = m->m_ext.ext_buf;
+ m->m_flags |= M_EXT;
+ m->m_ext.ext_free = NULL;
+ m->m_ext.ext_args = NULL;
+ m->m_ext.ext_size = size;
+ m->m_ext.ext_type = type;
+ m->m_ext.ref_cnt = refcnt;
+ }
+#endif
+ return (0);
+}
+
+/* __Userspace__ */
+static void
+mb_dtor_clust(void *mem, void *arg)
+{
+
+ /* mem is of type caddr_t. In sys/types.h we have typedef char * caddr_t; */
+ /* mb_dtor_clust is called at time of umem_cache_destroy() (the number of times
+ * mb_dtor_clust is called is equal to the number of individual mbufs allocated
+ * from zone_clust. Similarly for mb_dtor_mbuf).
+ * At this point the following:
+ * struct mbuf *m;
+ * m = (struct mbuf *)arg;
+ * assert (*(m->m_ext.ref_cnt) == 0); is not meaningful since m->m_ext.ref_cnt = NULL;
+ * has been done in mb_free_ext().
+ */
+
+}
+
+
+
+
+/* Unlink and free a packet tag. */
+void
+m_tag_delete(struct mbuf *m, struct m_tag *t)
+{
+ KASSERT(m && t, ("m_tag_delete: null argument, m %p t %p", (void *)m, (void *)t));
+ m_tag_unlink(m, t);
+ m_tag_free(t);
+}
+
+
+/* Unlink and free a packet tag chain, starting from given tag. */
+void
+m_tag_delete_chain(struct mbuf *m, struct m_tag *t)
+{
+
+ struct m_tag *p, *q;
+
+ KASSERT(m, ("m_tag_delete_chain: null mbuf"));
+ if (t != NULL)
+ p = t;
+ else
+ p = SLIST_FIRST(&m->m_pkthdr.tags);
+ if (p == NULL)
+ return;
+ while ((q = SLIST_NEXT(p, m_tag_link)) != NULL)
+ m_tag_delete(m, q);
+ m_tag_delete(m, p);
+}
+
+#if 0
+static void
+sctp_print_mbuf_chain(struct mbuf *m)
+{
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, "Printing mbuf chain %p.\n", (void *)m);
+ for(; m; m=m->m_next) {
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, "%p: m_len = %ld, m_type = %x, m_next = %p.\n", (void *)m, m->m_len, m->m_type, (void *)m->m_next);
+ if (m->m_flags & M_EXT)
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, "%p: extend_size = %d, extend_buffer = %p, ref_cnt = %d.\n", (void *)m, m->m_ext.ext_size, (void *)m->m_ext.ext_buf, *(m->m_ext.ref_cnt));
+ }
+}
+#endif
+
+/*
+ * Free an entire chain of mbufs and associated external buffers, if
+ * applicable.
+ */
+void
+m_freem(struct mbuf *mb)
+{
+ while (mb != NULL)
+ mb = m_free(mb);
+}
+
+/*
+ * __Userspace__
+ * clean mbufs with M_EXT storage attached to them
+ * if the reference count hits 1.
+ */
+void
+mb_free_ext(struct mbuf *m)
+{
+
+ int skipmbuf;
+
+ KASSERT((m->m_flags & M_EXT) == M_EXT, ("%s: M_EXT not set", __func__));
+ KASSERT(m->m_ext.ref_cnt != NULL, ("%s: ref_cnt not set", __func__));
+
+ /*
+ * check if the header is embedded in the cluster
+ */
+ skipmbuf = (m->m_flags & M_NOFREE);
+
+ /* Free the external attached storage if this
+ * mbuf is the only reference to it.
+ *__Userspace__ TODO: jumbo frames
+ *
+ */
+ /* NOTE: We had the same code that SCTP_DECREMENT_AND_CHECK_REFCOUNT
+ reduces to here before but the IPHONE malloc commit had changed
+ this to compare to 0 instead of 1 (see next line). Why?
+ . .. this caused a huge memory leak in Linux.
+ */
+#ifdef IPHONE
+ if (atomic_fetchadd_int(m->m_ext.ref_cnt, -1) == 0)
+#else
+ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(m->m_ext.ref_cnt))
+#endif
+ {
+ if (m->m_ext.ext_type == EXT_CLUSTER){
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ mb_dtor_clust(m->m_ext.ext_buf, &clust_mb_args);
+#endif
+ SCTP_ZONE_FREE(zone_clust, m->m_ext.ext_buf);
+ SCTP_ZONE_FREE(zone_ext_refcnt, (u_int*)m->m_ext.ref_cnt);
+ m->m_ext.ref_cnt = NULL;
+ }
+ }
+
+ if (skipmbuf)
+ return;
+
+
+ /* __Userspace__ Also freeing the storage for ref_cnt
+ * Free this mbuf back to the mbuf zone with all m_ext
+ * information purged.
+ */
+ m->m_ext.ext_buf = NULL;
+ m->m_ext.ext_free = NULL;
+ m->m_ext.ext_args = NULL;
+ m->m_ext.ref_cnt = NULL;
+ m->m_ext.ext_size = 0;
+ m->m_ext.ext_type = 0;
+ m->m_flags &= ~M_EXT;
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ mb_dtor_mbuf(m, NULL);
+#endif
+ SCTP_ZONE_FREE(zone_mbuf, m);
+
+ /*umem_cache_free(zone_mbuf, m);*/
+}
+
+/*
+ * "Move" mbuf pkthdr from "from" to "to".
+ * "from" must have M_PKTHDR set, and "to" must be empty.
+ */
+void
+m_move_pkthdr(struct mbuf *to, struct mbuf *from)
+{
+
+ to->m_flags = (from->m_flags & M_COPYFLAGS) | (to->m_flags & M_EXT);
+ if ((to->m_flags & M_EXT) == 0)
+ to->m_data = to->m_pktdat;
+ to->m_pkthdr = from->m_pkthdr; /* especially tags */
+ SLIST_INIT(&from->m_pkthdr.tags); /* purge tags from src */
+ from->m_flags &= ~M_PKTHDR;
+}
+
+
+/*
+ * Rearange an mbuf chain so that len bytes are contiguous
+ * and in the data area of an mbuf (so that mtod and dtom
+ * will work for a structure of size len). Returns the resulting
+ * mbuf chain on success, frees it and returns null on failure.
+ * If there is room, it will add up to max_protohdr-len extra bytes to the
+ * contiguous region in an attempt to avoid being called next time.
+ */
+struct mbuf *
+m_pullup(struct mbuf *n, int len)
+{
+ struct mbuf *m;
+ int count;
+ int space;
+
+ /*
+ * If first mbuf has no cluster, and has room for len bytes
+ * without shifting current data, pullup into it,
+ * otherwise allocate a new mbuf to prepend to the chain.
+ */
+ if ((n->m_flags & M_EXT) == 0 &&
+ n->m_data + len < &n->m_dat[MLEN] && n->m_next) {
+ if (n->m_len >= len)
+ return (n);
+ m = n;
+ n = n->m_next;
+ len -= m->m_len;
+ } else {
+ if (len > MHLEN)
+ goto bad;
+ MGET(m, M_NOWAIT, n->m_type);
+ if (m == NULL)
+ goto bad;
+ m->m_len = 0;
+ if (n->m_flags & M_PKTHDR)
+ M_MOVE_PKTHDR(m, n);
+ }
+ space = (int)(&m->m_dat[MLEN] - (m->m_data + m->m_len));
+ do {
+ count = min(min(max(len, max_protohdr), space), n->m_len);
+ memcpy(mtod(m, caddr_t) + m->m_len,mtod(n, caddr_t), (u_int)count);
+ len -= count;
+ m->m_len += count;
+ n->m_len -= count;
+ space -= count;
+ if (n->m_len)
+ n->m_data += count;
+ else
+ n = m_free(n);
+ } while (len > 0 && n);
+ if (len > 0) {
+ (void) m_free(m);
+ goto bad;
+ }
+ m->m_next = n;
+ return (m);
+bad:
+ m_freem(n);
+ return (NULL);
+}
+
+
+static struct mbuf *
+m_dup1(struct mbuf *m, int off, int len, int wait)
+{
+ struct mbuf *n = NULL;
+ int copyhdr;
+
+ if (len > MCLBYTES)
+ return NULL;
+ if (off == 0 && (m->m_flags & M_PKTHDR) != 0)
+ copyhdr = 1;
+ else
+ copyhdr = 0;
+ if (len >= MINCLSIZE) {
+ if (copyhdr == 1) {
+ m_clget(n, wait); /* TODO: include code for copying the header */
+ m_dup_pkthdr(n, m, wait);
+ } else
+ m_clget(n, wait);
+ } else {
+ if (copyhdr == 1)
+ n = m_gethdr(wait, m->m_type);
+ else
+ n = m_get(wait, m->m_type);
+ }
+ if (!n)
+ return NULL; /* ENOBUFS */
+
+ if (copyhdr && !m_dup_pkthdr(n, m, wait)) {
+ m_free(n);
+ return NULL;
+ }
+ m_copydata(m, off, len, mtod(n, caddr_t));
+ n->m_len = len;
+ return n;
+}
+
+
+/* Taken from sys/kern/uipc_mbuf2.c */
+struct mbuf *
+m_pulldown(struct mbuf *m, int off, int len, int *offp)
+{
+ struct mbuf *n, *o;
+ int hlen, tlen, olen;
+ int writable;
+
+ /* check invalid arguments. */
+ KASSERT(m, ("m == NULL in m_pulldown()"));
+ if (len > MCLBYTES) {
+ m_freem(m);
+ return NULL; /* impossible */
+ }
+
+#ifdef PULLDOWN_DEBUG
+ {
+ struct mbuf *t;
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, "before:");
+ for (t = m; t; t = t->m_next)
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, " %d", t->m_len);
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, "\n");
+ }
+#endif
+ n = m;
+ while (n != NULL && off > 0) {
+ if (n->m_len > off)
+ break;
+ off -= n->m_len;
+ n = n->m_next;
+ }
+ /* be sure to point non-empty mbuf */
+ while (n != NULL && n->m_len == 0)
+ n = n->m_next;
+ if (!n) {
+ m_freem(m);
+ return NULL; /* mbuf chain too short */
+ }
+
+ writable = 0;
+ if ((n->m_flags & M_EXT) == 0 ||
+ (n->m_ext.ext_type == EXT_CLUSTER && M_WRITABLE(n)))
+ writable = 1;
+
+ /*
+ * the target data is on <n, off>.
+ * if we got enough data on the mbuf "n", we're done.
+ */
+ if ((off == 0 || offp) && len <= n->m_len - off && writable)
+ goto ok;
+
+ /*
+ * when len <= n->m_len - off and off != 0, it is a special case.
+ * len bytes from <n, off> sits in single mbuf, but the caller does
+ * not like the starting position (off).
+ * chop the current mbuf into two pieces, set off to 0.
+ */
+ if (len <= n->m_len - off) {
+ o = m_dup1(n, off, n->m_len - off, M_NOWAIT);
+ if (o == NULL) {
+ m_freem(m);
+ return NULL; /* ENOBUFS */
+ }
+ n->m_len = off;
+ o->m_next = n->m_next;
+ n->m_next = o;
+ n = n->m_next;
+ off = 0;
+ goto ok;
+ }
+ /*
+ * we need to take hlen from <n, off> and tlen from <n->m_next, 0>,
+ * and construct contiguous mbuf with m_len == len.
+ * note that hlen + tlen == len, and tlen > 0.
+ */
+ hlen = n->m_len - off;
+ tlen = len - hlen;
+
+ /*
+ * ensure that we have enough trailing data on mbuf chain.
+ * if not, we can do nothing about the chain.
+ */
+ olen = 0;
+ for (o = n->m_next; o != NULL; o = o->m_next)
+ olen += o->m_len;
+ if (hlen + olen < len) {
+ m_freem(m);
+ return NULL; /* mbuf chain too short */
+ }
+
+ /*
+ * easy cases first.
+ * we need to use m_copydata() to get data from <n->m_next, 0>.
+ */
+ if ((off == 0 || offp) && (M_TRAILINGSPACE(n) >= tlen) && writable) {
+ m_copydata(n->m_next, 0, tlen, mtod(n, caddr_t) + n->m_len);
+ n->m_len += tlen;
+ m_adj(n->m_next, tlen);
+ goto ok;
+ }
+
+ if ((off == 0 || offp) && (M_LEADINGSPACE(n->m_next) >= hlen) && writable) {
+ n->m_next->m_data -= hlen;
+ n->m_next->m_len += hlen;
+ memcpy( mtod(n->m_next, caddr_t), mtod(n, caddr_t) + off,hlen);
+ n->m_len -= hlen;
+ n = n->m_next;
+ off = 0;
+ goto ok;
+ }
+
+ /*
+ * now, we need to do the hard way. don't m_copy as there's no room
+ * on both end.
+ */
+ if (len > MLEN)
+ m_clget(o, M_NOWAIT);
+ /* o = m_getcl(M_NOWAIT, m->m_type, 0);*/
+ else
+ o = m_get(M_NOWAIT, m->m_type);
+ if (!o) {
+ m_freem(m);
+ return NULL; /* ENOBUFS */
+ }
+ /* get hlen from <n, off> into <o, 0> */
+ o->m_len = hlen;
+ memcpy(mtod(o, caddr_t), mtod(n, caddr_t) + off, hlen);
+ n->m_len -= hlen;
+ /* get tlen from <n->m_next, 0> into <o, hlen> */
+ m_copydata(n->m_next, 0, tlen, mtod(o, caddr_t) + o->m_len);
+ o->m_len += tlen;
+ m_adj(n->m_next, tlen);
+ o->m_next = n->m_next;
+ n->m_next = o;
+ n = o;
+ off = 0;
+ok:
+#ifdef PULLDOWN_DEBUG
+ {
+ struct mbuf *t;
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, "after:");
+ for (t = m; t; t = t->m_next)
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, "%c%d", t == n ? '*' : ' ', t->m_len);
+ SCTP_DEBUG_USR(SCTP_DEBUG_USR, " (off=%d)\n", off);
+ }
+#endif
+ if (offp)
+ *offp = off;
+ return n;
+}
+
+/*
+ * Attach the the cluster from *m to *n, set up m_ext in *n
+ * and bump the refcount of the cluster.
+ */
+static void
+mb_dupcl(struct mbuf *n, struct mbuf *m)
+{
+ KASSERT((m->m_flags & M_EXT) == M_EXT, ("%s: M_EXT not set", __func__));
+ KASSERT(m->m_ext.ref_cnt != NULL, ("%s: ref_cnt not set", __func__));
+ KASSERT((n->m_flags & M_EXT) == 0, ("%s: M_EXT set", __func__));
+
+ if (*(m->m_ext.ref_cnt) == 1)
+ *(m->m_ext.ref_cnt) += 1;
+ else
+ atomic_add_int(m->m_ext.ref_cnt, 1);
+ n->m_ext.ext_buf = m->m_ext.ext_buf;
+ n->m_ext.ext_free = m->m_ext.ext_free;
+ n->m_ext.ext_args = m->m_ext.ext_args;
+ n->m_ext.ext_size = m->m_ext.ext_size;
+ n->m_ext.ref_cnt = m->m_ext.ref_cnt;
+ n->m_ext.ext_type = m->m_ext.ext_type;
+ n->m_flags |= M_EXT;
+}
+
+
+/*
+ * Make a copy of an mbuf chain starting "off0" bytes from the beginning,
+ * continuing for "len" bytes. If len is M_COPYALL, copy to end of mbuf.
+ * The wait parameter is a choice of M_TRYWAIT/M_NOWAIT from caller.
+ * Note that the copy is read-only, because clusters are not copied,
+ * only their reference counts are incremented.
+ */
+
+struct mbuf *
+m_copym(struct mbuf *m, int off0, int len, int wait)
+{
+ struct mbuf *n, **np;
+ int off = off0;
+ struct mbuf *top;
+ int copyhdr = 0;
+
+ KASSERT(off >= 0, ("m_copym, negative off %d", off));
+ KASSERT(len >= 0, ("m_copym, negative len %d", len));
+ KASSERT(m != NULL, ("m_copym, m is NULL"));
+
+#if !defined(INVARIANTS)
+ if (m == NULL) {
+ return (NULL);
+ }
+#endif
+ if (off == 0 && m->m_flags & M_PKTHDR)
+ copyhdr = 1;
+ while (off > 0) {
+ KASSERT(m != NULL, ("m_copym, offset > size of mbuf chain"));
+ if (off < m->m_len)
+ break;
+ off -= m->m_len;
+ m = m->m_next;
+ }
+ np = &top;
+ top = 0;
+ while (len > 0) {
+ if (m == NULL) {
+ KASSERT(len == M_COPYALL, ("m_copym, length > size of mbuf chain"));
+ break;
+ }
+ if (copyhdr)
+ MGETHDR(n, wait, m->m_type);
+ else
+ MGET(n, wait, m->m_type);
+ *np = n;
+ if (n == NULL)
+ goto nospace;
+ if (copyhdr) {
+ if (!m_dup_pkthdr(n, m, wait))
+ goto nospace;
+ if (len == M_COPYALL)
+ n->m_pkthdr.len -= off0;
+ else
+ n->m_pkthdr.len = len;
+ copyhdr = 0;
+ }
+ n->m_len = min(len, m->m_len - off);
+ if (m->m_flags & M_EXT) {
+ n->m_data = m->m_data + off;
+ mb_dupcl(n, m);
+ } else
+ memcpy(mtod(n, caddr_t), mtod(m, caddr_t) + off, (u_int)n->m_len);
+ if (len != M_COPYALL)
+ len -= n->m_len;
+ off = 0;
+ m = m->m_next;
+ np = &n->m_next;
+ }
+
+ return (top);
+nospace:
+ m_freem(top);
+ return (NULL);
+}
+
+
+int
+m_tag_copy_chain(struct mbuf *to, struct mbuf *from, int how)
+{
+ struct m_tag *p, *t, *tprev = NULL;
+
+ KASSERT(to && from, ("m_tag_copy_chain: null argument, to %p from %p", (void *)to, (void *)from));
+ m_tag_delete_chain(to, NULL);
+ SLIST_FOREACH(p, &from->m_pkthdr.tags, m_tag_link) {
+ t = m_tag_copy(p, how);
+ if (t == NULL) {
+ m_tag_delete_chain(to, NULL);
+ return 0;
+ }
+ if (tprev == NULL)
+ SLIST_INSERT_HEAD(&to->m_pkthdr.tags, t, m_tag_link);
+ else
+ SLIST_INSERT_AFTER(tprev, t, m_tag_link);
+ tprev = t;
+ }
+ return 1;
+}
+
+/*
+ * Duplicate "from"'s mbuf pkthdr in "to".
+ * "from" must have M_PKTHDR set, and "to" must be empty.
+ * In particular, this does a deep copy of the packet tags.
+ */
+int
+m_dup_pkthdr(struct mbuf *to, struct mbuf *from, int how)
+{
+
+ KASSERT(to, ("m_dup_pkthdr: to is NULL"));
+ KASSERT(from, ("m_dup_pkthdr: from is NULL"));
+ to->m_flags = (from->m_flags & M_COPYFLAGS) | (to->m_flags & M_EXT);
+ if ((to->m_flags & M_EXT) == 0)
+ to->m_data = to->m_pktdat;
+ to->m_pkthdr = from->m_pkthdr;
+ SLIST_INIT(&to->m_pkthdr.tags);
+ return (m_tag_copy_chain(to, from, MBTOM(how)));
+}
+
+/* Copy a single tag. */
+struct m_tag *
+m_tag_copy(struct m_tag *t, int how)
+{
+ struct m_tag *p;
+
+ KASSERT(t, ("m_tag_copy: null tag"));
+ p = m_tag_alloc(t->m_tag_cookie, t->m_tag_id, t->m_tag_len, how);
+ if (p == NULL)
+ return (NULL);
+ memcpy(p + 1, t + 1, t->m_tag_len); /* Copy the data */
+ return p;
+}
+
+/* Get a packet tag structure along with specified data following. */
+struct m_tag *
+m_tag_alloc(uint32_t cookie, int type, int len, int wait)
+{
+ struct m_tag *t;
+
+ if (len < 0)
+ return NULL;
+ t = malloc(len + sizeof(struct m_tag));
+ if (t == NULL)
+ return NULL;
+ m_tag_setup(t, cookie, type, len);
+ t->m_tag_free = m_tag_free_default;
+ return t;
+}
+
+/* Free a packet tag. */
+void
+m_tag_free_default(struct m_tag *t)
+{
+ free(t);
+}
+
+/*
+ * Copy data from a buffer back into the indicated mbuf chain,
+ * starting "off" bytes from the beginning, extending the mbuf
+ * chain if necessary.
+ */
+void
+m_copyback(struct mbuf *m0, int off, int len, caddr_t cp)
+{
+ int mlen;
+ struct mbuf *m = m0, *n;
+ int totlen = 0;
+
+ if (m0 == NULL)
+ return;
+ while (off > (mlen = m->m_len)) {
+ off -= mlen;
+ totlen += mlen;
+ if (m->m_next == NULL) {
+ n = m_get(M_NOWAIT, m->m_type);
+ if (n == NULL)
+ goto out;
+ memset(mtod(n, caddr_t), 0, MLEN);
+ n->m_len = min(MLEN, len + off);
+ m->m_next = n;
+ }
+ m = m->m_next;
+ }
+ while (len > 0) {
+ mlen = min (m->m_len - off, len);
+ memcpy(off + mtod(m, caddr_t), cp, (u_int)mlen);
+ cp += mlen;
+ len -= mlen;
+ mlen += off;
+ off = 0;
+ totlen += mlen;
+ if (len == 0)
+ break;
+ if (m->m_next == NULL) {
+ n = m_get(M_NOWAIT, m->m_type);
+ if (n == NULL)
+ break;
+ n->m_len = min(MLEN, len);
+ m->m_next = n;
+ }
+ m = m->m_next;
+ }
+out: if (((m = m0)->m_flags & M_PKTHDR) && (m->m_pkthdr.len < totlen))
+ m->m_pkthdr.len = totlen;
+}
+
+/*
+ * Apply function f to the data in an mbuf chain starting "off" bytes from
+ * the beginning, continuing for "len" bytes.
+ */
+int
+m_apply(struct mbuf *m, int off, int len,
+ int (*f)(void *, void *, u_int), void *arg)
+{
+ u_int count;
+ int rval;
+
+ KASSERT(off >= 0, ("m_apply, negative off %d", off));
+ KASSERT(len >= 0, ("m_apply, negative len %d", len));
+ while (off > 0) {
+ KASSERT(m != NULL, ("m_apply, offset > size of mbuf chain"));
+ if (off < m->m_len)
+ break;
+ off -= m->m_len;
+ m = m->m_next;
+ }
+ while (len > 0) {
+ KASSERT(m != NULL, ("m_apply, offset > size of mbuf chain"));
+ count = min(m->m_len - off, len);
+ rval = (*f)(arg, mtod(m, caddr_t) + off, count);
+ if (rval)
+ return (rval);
+ len -= count;
+ off = 0;
+ m = m->m_next;
+ }
+ return (0);
+}
+
+/*
+ * Lesser-used path for M_PREPEND:
+ * allocate new mbuf to prepend to chain,
+ * copy junk along.
+ */
+struct mbuf *
+m_prepend(struct mbuf *m, int len, int how)
+{
+ struct mbuf *mn;
+
+ if (m->m_flags & M_PKTHDR)
+ MGETHDR(mn, how, m->m_type);
+ else
+ MGET(mn, how, m->m_type);
+ if (mn == NULL) {
+ m_freem(m);
+ return (NULL);
+ }
+ if (m->m_flags & M_PKTHDR)
+ M_MOVE_PKTHDR(mn, m);
+ mn->m_next = m;
+ m = mn;
+ if (m->m_flags & M_PKTHDR) {
+ if (len < MHLEN)
+ MH_ALIGN(m, len);
+ } else {
+ if (len < MLEN)
+ M_ALIGN(m, len);
+ }
+ m->m_len = len;
+ return (m);
+}
+
+/*
+ * Copy data from an mbuf chain starting "off" bytes from the beginning,
+ * continuing for "len" bytes, into the indicated buffer.
+ */
+void
+m_copydata(const struct mbuf *m, int off, int len, caddr_t cp)
+{
+ u_int count;
+
+ KASSERT(off >= 0, ("m_copydata, negative off %d", off));
+ KASSERT(len >= 0, ("m_copydata, negative len %d", len));
+ while (off > 0) {
+ KASSERT(m != NULL, ("m_copydata, offset > size of mbuf chain"));
+ if (off < m->m_len)
+ break;
+ off -= m->m_len;
+ m = m->m_next;
+ }
+ while (len > 0) {
+ KASSERT(m != NULL, ("m_copydata, length > size of mbuf chain"));
+ count = min(m->m_len - off, len);
+ memcpy(cp, mtod(m, caddr_t) + off, count);
+ len -= count;
+ cp += count;
+ off = 0;
+ m = m->m_next;
+ }
+}
+
+
+/*
+ * Concatenate mbuf chain n to m.
+ * Both chains must be of the same type (e.g. MT_DATA).
+ * Any m_pkthdr is not updated.
+ */
+void
+m_cat(struct mbuf *m, struct mbuf *n)
+{
+ while (m->m_next)
+ m = m->m_next;
+ while (n) {
+ if (m->m_flags & M_EXT ||
+ m->m_data + m->m_len + n->m_len >= &m->m_dat[MLEN]) {
+ /* just join the two chains */
+ m->m_next = n;
+ return;
+ }
+ /* splat the data from one into the other */
+ memcpy(mtod(m, caddr_t) + m->m_len, mtod(n, caddr_t), (u_int)n->m_len);
+ m->m_len += n->m_len;
+ n = m_free(n);
+ }
+}
+
+
+void
+m_adj(struct mbuf *mp, int req_len)
+{
+ int len = req_len;
+ struct mbuf *m;
+ int count;
+
+ if ((m = mp) == NULL)
+ return;
+ if (len >= 0) {
+ /*
+ * Trim from head.
+ */
+ while (m != NULL && len > 0) {
+ if (m->m_len <= len) {
+ len -= m->m_len;
+ m->m_len = 0;
+ m = m->m_next;
+ } else {
+ m->m_len -= len;
+ m->m_data += len;
+ len = 0;
+ }
+ }
+ m = mp;
+ if (mp->m_flags & M_PKTHDR)
+ m->m_pkthdr.len -= (req_len - len);
+ } else {
+ /*
+ * Trim from tail. Scan the mbuf chain,
+ * calculating its length and finding the last mbuf.
+ * If the adjustment only affects this mbuf, then just
+ * adjust and return. Otherwise, rescan and truncate
+ * after the remaining size.
+ */
+ len = -len;
+ count = 0;
+ for (;;) {
+ count += m->m_len;
+ if (m->m_next == (struct mbuf *)0)
+ break;
+ m = m->m_next;
+ }
+ if (m->m_len >= len) {
+ m->m_len -= len;
+ if (mp->m_flags & M_PKTHDR)
+ mp->m_pkthdr.len -= len;
+ return;
+ }
+ count -= len;
+ if (count < 0)
+ count = 0;
+ /*
+ * Correct length for chain is "count".
+ * Find the mbuf with last data, adjust its length,
+ * and toss data from remaining mbufs on chain.
+ */
+ m = mp;
+ if (m->m_flags & M_PKTHDR)
+ m->m_pkthdr.len = count;
+ for (; m; m = m->m_next) {
+ if (m->m_len >= count) {
+ m->m_len = count;
+ if (m->m_next != NULL) {
+ m_freem(m->m_next);
+ m->m_next = NULL;
+ }
+ break;
+ }
+ count -= m->m_len;
+ }
+ }
+}
+
+
+/* m_split is used within sctp_handle_cookie_echo. */
+
+/*
+ * Partition an mbuf chain in two pieces, returning the tail --
+ * all but the first len0 bytes. In case of failure, it returns NULL and
+ * attempts to restore the chain to its original state.
+ *
+ * Note that the resulting mbufs might be read-only, because the new
+ * mbuf can end up sharing an mbuf cluster with the original mbuf if
+ * the "breaking point" happens to lie within a cluster mbuf. Use the
+ * M_WRITABLE() macro to check for this case.
+ */
+struct mbuf *
+m_split(struct mbuf *m0, int len0, int wait)
+{
+ struct mbuf *m, *n;
+ u_int len = len0, remain;
+
+ /* MBUF_CHECKSLEEP(wait); */
+ for (m = m0; m && (int)len > m->m_len; m = m->m_next)
+ len -= m->m_len;
+ if (m == NULL)
+ return (NULL);
+ remain = m->m_len - len;
+ if (m0->m_flags & M_PKTHDR) {
+ MGETHDR(n, wait, m0->m_type);
+ if (n == NULL)
+ return (NULL);
+ n->m_pkthdr.rcvif = m0->m_pkthdr.rcvif;
+ n->m_pkthdr.len = m0->m_pkthdr.len - len0;
+ m0->m_pkthdr.len = len0;
+ if (m->m_flags & M_EXT)
+ goto extpacket;
+ if (remain > MHLEN) {
+ /* m can't be the lead packet */
+ MH_ALIGN(n, 0);
+ n->m_next = m_split(m, len, wait);
+ if (n->m_next == NULL) {
+ (void) m_free(n);
+ return (NULL);
+ } else {
+ n->m_len = 0;
+ return (n);
+ }
+ } else
+ MH_ALIGN(n, remain);
+ } else if (remain == 0) {
+ n = m->m_next;
+ m->m_next = NULL;
+ return (n);
+ } else {
+ MGET(n, wait, m->m_type);
+ if (n == NULL)
+ return (NULL);
+ M_ALIGN(n, remain);
+ }
+extpacket:
+ if (m->m_flags & M_EXT) {
+ n->m_data = m->m_data + len;
+ mb_dupcl(n, m);
+ } else {
+ memcpy(mtod(n, caddr_t), mtod(m, caddr_t) + len, remain);
+ }
+ n->m_len = remain;
+ m->m_len = len;
+ n->m_next = m->m_next;
+ m->m_next = NULL;
+ return (n);
+}
+
+
+
+
+int
+pack_send_buffer(caddr_t buffer, struct mbuf* mb){
+
+ int count_to_copy;
+ int total_count_copied = 0;
+ int offset = 0;
+
+ do {
+ count_to_copy = mb->m_len;
+ memcpy(buffer+offset, mtod(mb, caddr_t), count_to_copy);
+ offset += count_to_copy;
+ total_count_copied += count_to_copy;
+ mb = mb->m_next;
+ } while(mb);
+
+ return (total_count_copied);
+}
diff --git a/netwerk/sctp/src/user_mbuf.h b/netwerk/sctp/src/user_mbuf.h
new file mode 100644
index 0000000000..268988f912
--- /dev/null
+++ b/netwerk/sctp/src/user_mbuf.h
@@ -0,0 +1,412 @@
+/*-
+ * Copyright (c) 1982, 1986, 1988, 1993
+ * The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _USER_MBUF_H_
+#define _USER_MBUF_H_
+
+/* __Userspace__ header file for mbufs */
+#include <stdio.h>
+#if !defined(SCTP_SIMPLE_ALLOCATOR)
+#include "umem.h"
+#endif
+#include "user_malloc.h"
+#include "netinet/sctp_os_userspace.h"
+
+#define USING_MBUF_CONSTRUCTOR 0
+
+/* For Linux */
+#ifndef MSIZE
+#define MSIZE 256
+/* #define MSIZE 1024 */
+#endif
+#ifndef MCLBYTES
+#define MCLBYTES 2048
+#endif
+
+struct mbuf * m_gethdr(int how, short type);
+struct mbuf * m_get(int how, short type);
+struct mbuf * m_free(struct mbuf *m);
+void m_clget(struct mbuf *m, int how);
+struct mbuf * m_getm2(struct mbuf *m, int len, int how, short type, int flags, int allonebuf);
+struct mbuf *m_uiotombuf(struct uio *uio, int how, int len, int align, int flags);
+u_int m_length(struct mbuf *m0, struct mbuf **last);
+struct mbuf *m_last(struct mbuf *m);
+
+/* mbuf initialization function */
+void mbuf_initialize(void *);
+
+#define M_MOVE_PKTHDR(to, from) m_move_pkthdr((to), (from))
+#define MGET(m, how, type) ((m) = m_get((how), (type)))
+#define MGETHDR(m, how, type) ((m) = m_gethdr((how), (type)))
+#define MCLGET(m, how) m_clget((m), (how))
+
+
+#define M_HDR_PAD ((sizeof(intptr_t)==4) ? 2 : 6) /* modified for __Userspace__ */
+
+/* Length to m_copy to copy all. */
+#define M_COPYALL 1000000000
+
+/* umem_cache_t is defined in user_include/umem.h as
+ * typedef struct umem_cache umem_cache_t;
+ * Note:umem_zone_t is a pointer.
+ */
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+typedef size_t sctp_zone_t;
+#else
+typedef umem_cache_t *sctp_zone_t;
+#endif
+
+extern sctp_zone_t zone_mbuf;
+extern sctp_zone_t zone_clust;
+extern sctp_zone_t zone_ext_refcnt;
+
+/*-
+ * Macros for type conversion:
+ * mtod(m, t) -- Convert mbuf pointer to data pointer of correct type.
+ * dtom(x) -- Convert data pointer within mbuf to mbuf pointer (XXX).
+ */
+#define mtod(m, t) ((t)((m)->m_data))
+#define dtom(x) ((struct mbuf *)((intptr_t)(x) & ~(MSIZE-1)))
+
+struct mb_args {
+ int flags; /* Flags for mbuf being allocated */
+ short type; /* Type of mbuf being allocated */
+};
+
+struct clust_args {
+ struct mbuf * parent_mbuf;
+};
+
+struct mbuf * m_split(struct mbuf *, int, int);
+void m_cat(struct mbuf *m, struct mbuf *n);
+void m_adj(struct mbuf *, int);
+void mb_free_ext(struct mbuf *);
+void m_freem(struct mbuf *);
+struct m_tag *m_tag_alloc(uint32_t, int, int, int);
+struct mbuf *m_copym(struct mbuf *, int, int, int);
+void m_copyback(struct mbuf *, int, int, caddr_t);
+int m_apply(struct mbuf *, int, int, int (*)(void *, void *, u_int), void *arg);
+struct mbuf *m_pullup(struct mbuf *, int);
+struct mbuf *m_pulldown(struct mbuf *, int off, int len, int *offp);
+int m_dup_pkthdr(struct mbuf *, struct mbuf *, int);
+struct m_tag *m_tag_copy(struct m_tag *, int);
+int m_tag_copy_chain(struct mbuf *, struct mbuf *, int);
+struct mbuf *m_prepend(struct mbuf *, int, int);
+void m_copydata(const struct mbuf *, int, int, caddr_t);
+
+#define MBUF_MEM_NAME "mbuf"
+#define MBUF_CLUSTER_MEM_NAME "mbuf_cluster"
+#define MBUF_EXTREFCNT_MEM_NAME "mbuf_ext_refcnt"
+
+/*
+ * Mbufs are of a single size, MSIZE (sys/param.h), which includes overhead.
+ * An mbuf may add a single "mbuf cluster" of size MCLBYTES (also in
+ * sys/param.h), which has no additional overhead and is used instead of the
+ * internal data area; this is done when at least MINCLSIZE of data must be
+ * stored. Additionally, it is possible to allocate a separate buffer
+ * externally and attach it to the mbuf in a way similar to that of mbuf
+ * clusters.
+ */
+#define MLEN ((int)(MSIZE - sizeof(struct m_hdr))) /* normal data len */
+#define MHLEN ((int)(MLEN - sizeof(struct pkthdr))) /* data len w/pkthdr */
+#define MINCLSIZE ((int)(MHLEN + 1)) /* smallest amount to put in cluster */
+#define M_MAXCOMPRESS (MHLEN / 2) /* max amount to copy for compression */
+
+
+/*
+ * Header present at the beginning of every mbuf.
+ */
+struct m_hdr {
+ struct mbuf *mh_next; /* next buffer in chain */
+ struct mbuf *mh_nextpkt; /* next chain in queue/record */
+ caddr_t mh_data; /* location of data */
+ int mh_len; /* amount of data in this mbuf */
+ int mh_flags; /* flags; see below */
+ short mh_type; /* type of data in this mbuf */
+ uint8_t pad[M_HDR_PAD];/* word align */
+};
+
+/*
+ * Packet tag structure (see below for details).
+ */
+struct m_tag {
+ SLIST_ENTRY(m_tag) m_tag_link; /* List of packet tags */
+ uint16_t m_tag_id; /* Tag ID */
+ uint16_t m_tag_len; /* Length of data */
+ uint32_t m_tag_cookie; /* ABI/Module ID */
+ void (*m_tag_free)(struct m_tag *);
+};
+
+/*
+ * Record/packet header in first mbuf of chain; valid only if M_PKTHDR is set.
+ */
+struct pkthdr {
+ struct ifnet *rcvif; /* rcv interface */
+ /* variables for ip and tcp reassembly */
+ void *header; /* pointer to packet header */
+ int len; /* total packet length */
+ /* variables for hardware checksum */
+ int csum_flags; /* flags regarding checksum */
+ int csum_data; /* data field used by csum routines */
+ uint16_t tso_segsz; /* TSO segment size */
+ uint16_t ether_vtag; /* Ethernet 802.1p+q vlan tag */
+ SLIST_HEAD(packet_tags, m_tag) tags; /* list of packet tags */
+};
+
+/*
+ * Description of external storage mapped into mbuf; valid only if M_EXT is
+ * set.
+ */
+struct m_ext {
+ caddr_t ext_buf; /* start of buffer */
+ void (*ext_free) /* free routine if not the usual */
+ (void *, void *);
+ void *ext_args; /* optional argument pointer */
+ u_int ext_size; /* size of buffer, for ext_free */
+ volatile u_int *ref_cnt; /* pointer to ref count info */
+ int ext_type; /* type of external storage */
+};
+
+
+/*
+ * The core of the mbuf object along with some shortcut defined for practical
+ * purposes.
+ */
+struct mbuf {
+ struct m_hdr m_hdr;
+ union {
+ struct {
+ struct pkthdr MH_pkthdr; /* M_PKTHDR set */
+ union {
+ struct m_ext MH_ext; /* M_EXT set */
+ char MH_databuf[MHLEN];
+ } MH_dat;
+ } MH;
+ char M_databuf[MLEN]; /* !M_PKTHDR, !M_EXT */
+ } M_dat;
+};
+
+#define m_next m_hdr.mh_next
+#define m_len m_hdr.mh_len
+#define m_data m_hdr.mh_data
+#define m_type m_hdr.mh_type
+#define m_flags m_hdr.mh_flags
+#define m_nextpkt m_hdr.mh_nextpkt
+#define m_act m_nextpkt
+#define m_pkthdr M_dat.MH.MH_pkthdr
+#define m_ext M_dat.MH.MH_dat.MH_ext
+#define m_pktdat M_dat.MH.MH_dat.MH_databuf
+#define m_dat M_dat.M_databuf
+
+
+/*
+ * mbuf flags.
+ */
+#define M_EXT 0x0001 /* has associated external storage */
+#define M_PKTHDR 0x0002 /* start of record */
+#define M_EOR 0x0004 /* end of record */
+#define M_RDONLY 0x0008 /* associated data is marked read-only */
+#define M_PROTO1 0x0010 /* protocol-specific */
+#define M_PROTO2 0x0020 /* protocol-specific */
+#define M_PROTO3 0x0040 /* protocol-specific */
+#define M_PROTO4 0x0080 /* protocol-specific */
+#define M_PROTO5 0x0100 /* protocol-specific */
+#define M_FREELIST 0x8000 /* mbuf is on the free list */
+
+
+/*
+ * Flags copied when copying m_pkthdr.
+ */
+#define M_COPYFLAGS (M_PKTHDR|M_EOR|M_RDONLY|M_PROTO1|M_PROTO1|M_PROTO2|\
+ M_PROTO3|M_PROTO4|M_PROTO5|\
+ M_BCAST|M_MCAST|M_FRAG|M_FIRSTFRAG|M_LASTFRAG|\
+ M_VLANTAG|M_PROMISC)
+
+
+/*
+ * mbuf pkthdr flags (also stored in m_flags).
+ */
+#define M_BCAST 0x0200 /* send/received as link-level broadcast */
+#define M_MCAST 0x0400 /* send/received as link-level multicast */
+#define M_FRAG 0x0800 /* packet is a fragment of a larger packet */
+#define M_FIRSTFRAG 0x1000 /* packet is first fragment */
+#define M_LASTFRAG 0x2000 /* packet is last fragment */
+#define M_VLANTAG 0x10000 /* ether_vtag is valid */
+#define M_PROMISC 0x20000 /* packet was not for us */
+#define M_NOFREE 0x40000 /* do not free mbuf - it is embedded in the cluster */
+
+
+/*
+ * External buffer types: identify ext_buf type.
+ */
+#define EXT_CLUSTER 1 /* mbuf cluster */
+#define EXT_SFBUF 2 /* sendfile(2)'s sf_bufs */
+#define EXT_JUMBOP 3 /* jumbo cluster 4096 bytes */
+#define EXT_JUMBO9 4 /* jumbo cluster 9216 bytes */
+#define EXT_JUMBO16 5 /* jumbo cluster 16184 bytes */
+#define EXT_PACKET 6 /* mbuf+cluster from packet zone */
+#define EXT_MBUF 7 /* external mbuf reference (M_IOVEC) */
+#define EXT_NET_DRV 100 /* custom ext_buf provided by net driver(s) */
+#define EXT_MOD_TYPE 200 /* custom module's ext_buf type */
+#define EXT_DISPOSABLE 300 /* can throw this buffer away w/page flipping */
+#define EXT_EXTREF 400 /* has externally maintained ref_cnt ptr */
+
+
+/*
+ * mbuf types.
+ */
+#define MT_NOTMBUF 0 /* USED INTERNALLY ONLY! Object is not mbuf */
+#define MT_DATA 1 /* dynamic (data) allocation */
+#define MT_HEADER MT_DATA /* packet header, use M_PKTHDR instead */
+#define MT_SONAME 8 /* socket name */
+#define MT_CONTROL 14 /* extra-data protocol message */
+#define MT_OOBDATA 15 /* expedited data */
+#define MT_NTYPES 16 /* number of mbuf types for mbtypes[] */
+
+/*
+ * __Userspace__ flags like M_NOWAIT are defined in malloc.h
+ * Flags like these are used in functions like uma_zalloc()
+ * but don't have an equivalent in userland umem
+ * Flags specifying how an allocation should be made.
+ *
+ * The flag to use is as follows:
+ * - M_DONTWAIT or M_NOWAIT from an interrupt handler to not block allocation.
+ * - M_WAIT or M_WAITOK or M_TRYWAIT from wherever it is safe to block.
+ *
+ * M_DONTWAIT/M_NOWAIT means that we will not block the thread explicitly and
+ * if we cannot allocate immediately we may return NULL, whereas
+ * M_WAIT/M_WAITOK/M_TRYWAIT means that if we cannot allocate resources we
+ * will block until they are available, and thus never return NULL.
+ *
+ * XXX Eventually just phase this out to use M_WAITOK/M_NOWAIT.
+ */
+#define MBTOM(how) (how)
+
+void m_tag_delete(struct mbuf *, struct m_tag *);
+void m_tag_delete_chain(struct mbuf *, struct m_tag *);
+void m_move_pkthdr(struct mbuf *, struct mbuf *);
+void m_tag_free_default(struct m_tag *);
+
+extern int max_linkhdr; /* Largest link-level header */
+extern int max_protohdr; /* Size of largest protocol layer header. See user_mbuf.c */
+
+/*
+ * Evaluate TRUE if it's safe to write to the mbuf m's data region (this can
+ * be both the local data payload, or an external buffer area, depending on
+ * whether M_EXT is set).
+ */
+#define M_WRITABLE(m) (!((m)->m_flags & M_RDONLY) && \
+ (!(((m)->m_flags & M_EXT)) || \
+ (*((m)->m_ext.ref_cnt) == 1)) ) \
+
+/* Check if the supplied mbuf has a packet header, or else panic. */
+#define M_ASSERTPKTHDR(m) \
+ KASSERT((m) != NULL && (m)->m_flags & M_PKTHDR, \
+ ("%s: no mbuf packet header!", __func__))
+
+/*
+ * Compute the amount of space available before the current start of data in
+ * an mbuf.
+ *
+ * The M_WRITABLE() is a temporary, conservative safety measure: the burden
+ * of checking writability of the mbuf data area rests solely with the caller.
+ */
+#define M_LEADINGSPACE(m) \
+ (((m)->m_flags & M_EXT) ? \
+ (M_WRITABLE(m) ? (m)->m_data - (m)->m_ext.ext_buf : 0): \
+ ((m)->m_flags & M_PKTHDR)? (m)->m_data - (m)->m_pktdat : \
+ (m)->m_data - (m)->m_dat)
+
+/*
+ * Compute the amount of space available after the end of data in an mbuf.
+ *
+ * The M_WRITABLE() is a temporary, conservative safety measure: the burden
+ * of checking writability of the mbuf data area rests solely with the caller.
+ */
+#define M_TRAILINGSPACE(m) \
+ (((m)->m_flags & M_EXT) ? \
+ (M_WRITABLE(m) ? (m)->m_ext.ext_buf + (m)->m_ext.ext_size \
+ - ((m)->m_data + (m)->m_len) : 0) : \
+ &(m)->m_dat[MLEN] - ((m)->m_data + (m)->m_len))
+
+
+
+/*
+ * Arrange to prepend space of size plen to mbuf m. If a new mbuf must be
+ * allocated, how specifies whether to wait. If the allocation fails, the
+ * original mbuf chain is freed and m is set to NULL.
+ */
+#define M_PREPEND(m, plen, how) do { \
+ struct mbuf **_mmp = &(m); \
+ struct mbuf *_mm = *_mmp; \
+ int _mplen = (plen); \
+ int __mhow = (how); \
+ \
+ if (M_LEADINGSPACE(_mm) >= _mplen) { \
+ _mm->m_data -= _mplen; \
+ _mm->m_len += _mplen; \
+ } else \
+ _mm = m_prepend(_mm, _mplen, __mhow); \
+ if (_mm != NULL && _mm->m_flags & M_PKTHDR) \
+ _mm->m_pkthdr.len += _mplen; \
+ *_mmp = _mm; \
+} while (0)
+
+/*
+ * Set the m_data pointer of a newly-allocated mbuf (m_get/MGET) to place an
+ * object of the specified size at the end of the mbuf, longword aligned.
+ */
+#define M_ALIGN(m, len) do { \
+ KASSERT(!((m)->m_flags & (M_PKTHDR|M_EXT)), \
+ ("%s: M_ALIGN not normal mbuf", __func__)); \
+ KASSERT((m)->m_data == (m)->m_dat, \
+ ("%s: M_ALIGN not a virgin mbuf", __func__)); \
+ (m)->m_data += (MLEN - (len)) & ~(sizeof(long) - 1); \
+} while (0)
+
+/*
+ * As above, for mbufs allocated with m_gethdr/MGETHDR or initialized by
+ * M_DUP/MOVE_PKTHDR.
+ */
+#define MH_ALIGN(m, len) do { \
+ KASSERT((m)->m_flags & M_PKTHDR && !((m)->m_flags & M_EXT), \
+ ("%s: MH_ALIGN not PKTHDR mbuf", __func__)); \
+ KASSERT((m)->m_data == (m)->m_pktdat, \
+ ("%s: MH_ALIGN not a virgin mbuf", __func__)); \
+ (m)->m_data += (MHLEN - (len)) & ~(sizeof(long) - 1); \
+} while (0)
+
+#define M_SIZE(m) \
+ (((m)->m_flags & M_EXT) ? (m)->m_ext.ext_size : \
+ ((m)->m_flags & M_PKTHDR) ? MHLEN : \
+ MLEN)
+
+#endif
diff --git a/netwerk/sctp/src/user_queue.h b/netwerk/sctp/src/user_queue.h
new file mode 100644
index 0000000000..fcd368bdd2
--- /dev/null
+++ b/netwerk/sctp/src/user_queue.h
@@ -0,0 +1,639 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _USER_QUEUE_H_
+#define _USER_QUEUE_H_
+
+/*
+ * This file defines four types of data structures: singly-linked lists,
+ * singly-linked tail queues, lists and tail queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A singly-linked tail queue is headed by a pair of pointers, one to the
+ * head of the list and the other to the tail of the list. The elements are
+ * singly linked for minimum space and pointer manipulation overhead at the
+ * expense of O(n) removal for arbitrary elements. New elements can be added
+ * to the list after an existing element, at the head of the list, or at the
+ * end of the list. Elements being removed from the head of the tail queue
+ * should use the explicit macro for this purpose for optimum efficiency.
+ * A singly-linked tail queue may only be traversed in the forward direction.
+ * Singly-linked tail queues are ideal for applications with large datasets
+ * and few or no removals or for implementing a FIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ *
+ *
+ * SLIST LIST STAILQ TAILQ
+ * _HEAD + + + +
+ * _HEAD_INITIALIZER + + + +
+ * _ENTRY + + + +
+ * _INIT + + + +
+ * _EMPTY + + + +
+ * _FIRST + + + +
+ * _NEXT + + + +
+ * _PREV - - - +
+ * _LAST - - + +
+ * _FOREACH + + + +
+ * _FOREACH_SAFE + + + +
+ * _FOREACH_REVERSE - - - +
+ * _FOREACH_REVERSE_SAFE - - - +
+ * _INSERT_HEAD + + + +
+ * _INSERT_BEFORE - + - +
+ * _INSERT_AFTER + + + +
+ * _INSERT_TAIL - - + +
+ * _CONCAT - - + +
+ * _REMOVE_AFTER + - + -
+ * _REMOVE_HEAD + - + -
+ * _REMOVE + + + +
+ * _SWAP + + + +
+ *
+ */
+#ifdef QUEUE_MACRO_DEBUG
+/* Store the last 2 places the queue element or head was altered */
+struct qm_trace {
+ char * lastfile;
+ int lastline;
+ char * prevfile;
+ int prevline;
+};
+
+#define TRACEBUF struct qm_trace trace;
+#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
+#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
+
+#define QMD_TRACE_HEAD(head) do { \
+ (head)->trace.prevline = (head)->trace.lastline; \
+ (head)->trace.prevfile = (head)->trace.lastfile; \
+ (head)->trace.lastline = __LINE__; \
+ (head)->trace.lastfile = __FILE__; \
+} while (0)
+
+#define QMD_TRACE_ELEM(elem) do { \
+ (elem)->trace.prevline = (elem)->trace.lastline; \
+ (elem)->trace.prevfile = (elem)->trace.lastfile; \
+ (elem)->trace.lastline = __LINE__; \
+ (elem)->trace.lastfile = __FILE__; \
+} while (0)
+
+#else
+#define QMD_TRACE_ELEM(elem)
+#define QMD_TRACE_HEAD(head)
+#define QMD_SAVELINK(name, link)
+#define TRACEBUF
+#define TRASHIT(x)
+#endif /* QUEUE_MACRO_DEBUG */
+
+/*
+ * Singly-linked List declarations.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#if defined(_WIN32)
+#if defined(SLIST_ENTRY)
+#undef SLIST_ENTRY
+#endif
+#endif
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+
+#define SLIST_FIRST(head) ((head)->slh_first)
+
+#define SLIST_FOREACH(var, head, field) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var); \
+ (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
+ for ((varp) = &SLIST_FIRST((head)); \
+ ((var) = *(varp)) != NULL; \
+ (varp) = &SLIST_NEXT((var), field))
+
+#define SLIST_INIT(head) do { \
+ SLIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
+ SLIST_NEXT((slistelm), field) = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
+ SLIST_FIRST((head)) = (elm); \
+} while (0)
+
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
+ if (SLIST_FIRST((head)) == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = SLIST_FIRST((head)); \
+ while (SLIST_NEXT(curelm, field) != (elm)) \
+ curelm = SLIST_NEXT(curelm, field); \
+ SLIST_REMOVE_AFTER(curelm, field); \
+ } \
+ TRASHIT(*oldnext); \
+} while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) do { \
+ SLIST_NEXT(elm, field) = \
+ SLIST_NEXT(SLIST_NEXT(elm, field), field); \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
+} while (0)
+
+#define SLIST_SWAP(head1, head2, type) do { \
+ struct type *swap_first = SLIST_FIRST(head1); \
+ SLIST_FIRST(head1) = SLIST_FIRST(head2); \
+ SLIST_FIRST(head2) = swap_first; \
+} while (0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type) \
+struct name { \
+ struct type *stqh_first;/* first element */ \
+ struct type **stqh_last;/* addr of last next element */ \
+}
+
+#define STAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).stqh_first }
+
+#define STAILQ_ENTRY(type) \
+struct { \
+ struct type *stqe_next; /* next element */ \
+}
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_CONCAT(head1, head2) do { \
+ if (!STAILQ_EMPTY((head2))) { \
+ *(head1)->stqh_last = (head2)->stqh_first; \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_INIT((head2)); \
+ } \
+} while (0)
+
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+
+#define STAILQ_FOREACH(var, head, field) \
+ for((var) = STAILQ_FIRST((head)); \
+ (var); \
+ (var) = STAILQ_NEXT((var), field))
+
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = STAILQ_FIRST((head)); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define STAILQ_INIT(head) do { \
+ STAILQ_FIRST((head)) = NULL; \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_NEXT((tqelm), field) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_HEAD(head, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_FIRST((head)) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_TAIL(head, elm, field) do { \
+ STAILQ_NEXT((elm), field) = NULL; \
+ *(head)->stqh_last = (elm); \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+} while (0)
+
+#define STAILQ_LAST(head, type, field) \
+ (STAILQ_EMPTY((head)) ? \
+ NULL : \
+ ((struct type *)(void *) \
+ ((char *)((head)->stqh_last) - __offsetof(struct type, field))))
+
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_REMOVE(head, elm, type, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
+ if (STAILQ_FIRST((head)) == (elm)) { \
+ STAILQ_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = STAILQ_FIRST((head)); \
+ while (STAILQ_NEXT(curelm, field) != (elm)) \
+ curelm = STAILQ_NEXT(curelm, field); \
+ STAILQ_REMOVE_AFTER(head, curelm, field); \
+ } \
+ TRASHIT(*oldnext); \
+} while (0)
+
+#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
+ if ((STAILQ_NEXT(elm, field) = \
+ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+} while (0)
+
+#define STAILQ_REMOVE_HEAD(head, field) do { \
+ if ((STAILQ_FIRST((head)) = \
+ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+#define STAILQ_SWAP(head1, head2, type) do { \
+ struct type *swap_first = STAILQ_FIRST(head1); \
+ struct type **swap_last = (head1)->stqh_last; \
+ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_FIRST(head2) = swap_first; \
+ (head2)->stqh_last = swap_last; \
+ if (STAILQ_EMPTY(head1)) \
+ (head1)->stqh_last = &STAILQ_FIRST(head1); \
+ if (STAILQ_EMPTY(head2)) \
+ (head2)->stqh_last = &STAILQ_FIRST(head2); \
+} while (0)
+
+
+/*
+ * List declarations.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List functions.
+ */
+
+#if defined(INVARIANTS)
+#define QMD_LIST_CHECK_HEAD(head, field) do { \
+ if (LIST_FIRST((head)) != NULL && \
+ LIST_FIRST((head))->field.le_prev != \
+ &LIST_FIRST((head))) \
+ panic("Bad list head %p first->prev != head", (void *)(head)); \
+} while (0)
+
+#define QMD_LIST_CHECK_NEXT(elm, field) do { \
+ if (LIST_NEXT((elm), field) != NULL && \
+ LIST_NEXT((elm), field)->field.le_prev != \
+ &((elm)->field.le_next)) \
+ panic("Bad link elm %p next->prev != elm", (void *)(elm)); \
+} while (0)
+
+#define QMD_LIST_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.le_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (void *)(elm)); \
+} while (0)
+#else
+#define QMD_LIST_CHECK_HEAD(head, field)
+#define QMD_LIST_CHECK_NEXT(elm, field)
+#define QMD_LIST_CHECK_PREV(elm, field)
+#endif /* (INVARIANTS) */
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+
+#define LIST_FIRST(head) ((head)->lh_first)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST((head)); \
+ (var); \
+ (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST((head)); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define LIST_INIT(head) do { \
+ LIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ QMD_LIST_CHECK_NEXT(listelm, field); \
+ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
+ LIST_NEXT((listelm), field)->field.le_prev = \
+ &LIST_NEXT((elm), field); \
+ LIST_NEXT((listelm), field) = (elm); \
+ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_LIST_CHECK_PREV(listelm, field); \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ LIST_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ QMD_LIST_CHECK_HEAD((head), field); \
+ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
+ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
+ LIST_FIRST((head)) = (elm); \
+ (elm)->field.le_prev = &LIST_FIRST((head)); \
+} while (0)
+
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_REMOVE(elm, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.le_next); \
+ QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
+ QMD_LIST_CHECK_NEXT(elm, field); \
+ QMD_LIST_CHECK_PREV(elm, field); \
+ if (LIST_NEXT((elm), field) != NULL) \
+ LIST_NEXT((elm), field)->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = LIST_NEXT((elm), field); \
+ TRASHIT(*oldnext); \
+ TRASHIT(*oldprev); \
+} while (0)
+
+#define LIST_SWAP(head1, head2, type, field) do { \
+ struct type *swap_tmp = LIST_FIRST((head1)); \
+ LIST_FIRST((head1)) = LIST_FIRST((head2)); \
+ LIST_FIRST((head2)) = swap_tmp; \
+ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
+ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
+ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
+ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
+} while (0)
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+ TRACEBUF \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+ TRACEBUF \
+}
+
+/*
+ * Tail queue functions.
+ */
+#if defined(INVARIANTS)
+#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
+ if (!TAILQ_EMPTY(head) && \
+ TAILQ_FIRST((head))->field.tqe_prev != \
+ &TAILQ_FIRST((head))) \
+ panic("Bad tailq head %p first->prev != head", (void *)(head)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
+ if (*(head)->tqh_last != NULL) \
+ panic("Bad tailq NEXT(%p->tqh_last) != NULL", (void *)(head)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
+ if (TAILQ_NEXT((elm), field) != NULL && \
+ TAILQ_NEXT((elm), field)->field.tqe_prev != \
+ &((elm)->field.tqe_next)) \
+ panic("Bad link elm %p next->prev != elm", (void *)(elm)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.tqe_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (void *)(elm)); \
+} while (0)
+#else
+#define QMD_TAILQ_CHECK_HEAD(head, field)
+#define QMD_TAILQ_CHECK_TAIL(head, headname)
+#define QMD_TAILQ_CHECK_NEXT(elm, field)
+#define QMD_TAILQ_CHECK_PREV(elm, field)
+#endif /* (INVARIANTS) */
+
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ QMD_TRACE_HEAD(head1); \
+ QMD_TRACE_HEAD(head2); \
+ } \
+} while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var); \
+ (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var); \
+ (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_INIT(head) do { \
+ TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_NEXT(listelm, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else { \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ } \
+ TAILQ_NEXT((listelm), field) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_PREV(listelm, field); \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ TAILQ_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ QMD_TAILQ_CHECK_HEAD(head, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
+ TAILQ_FIRST((head))->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ QMD_TAILQ_CHECK_TAIL(head, field); \
+ TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
+ QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
+ QMD_TAILQ_CHECK_NEXT(elm, field); \
+ QMD_TAILQ_CHECK_PREV(elm, field); \
+ if ((TAILQ_NEXT((elm), field)) != NULL) \
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else { \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ QMD_TRACE_HEAD(head); \
+ } \
+ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
+ TRASHIT(*oldnext); \
+ TRASHIT(*oldprev); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_SWAP(head1, head2, type, field) do { \
+ struct type *swap_first = (head1)->tqh_first; \
+ struct type **swap_last = (head1)->tqh_last; \
+ (head1)->tqh_first = (head2)->tqh_first; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ (head2)->tqh_first = swap_first; \
+ (head2)->tqh_last = swap_last; \
+ if ((swap_first = (head1)->tqh_first) != NULL) \
+ swap_first->field.tqe_prev = &(head1)->tqh_first; \
+ else \
+ (head1)->tqh_last = &(head1)->tqh_first; \
+ if ((swap_first = (head2)->tqh_first) != NULL) \
+ swap_first->field.tqe_prev = &(head2)->tqh_first; \
+ else \
+ (head2)->tqh_last = &(head2)->tqh_first; \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/netwerk/sctp/src/user_recv_thread.c b/netwerk/sctp/src/user_recv_thread.c
new file mode 100644
index 0000000000..66be13d749
--- /dev/null
+++ b/netwerk/sctp/src/user_recv_thread.c
@@ -0,0 +1,1566 @@
+/*-
+ * Copyright (c) 2009-2010 Brad Penoff
+ * Copyright (c) 2009-2010 Humaira Kamal
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#if defined(INET) || defined(INET6)
+#include <sys/types.h>
+#if !defined(_WIN32)
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <pthread.h>
+#if !defined(__DragonFly__) && !defined(__FreeBSD__) && !defined(__NetBSD__)
+#include <sys/uio.h>
+#else
+#include <user_ip6_var.h>
+#endif
+#endif
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_input.h>
+#if 0
+#if defined(__linux__)
+#include <linux/netlink.h>
+#ifdef HAVE_LINUX_IF_ADDR_H
+#include <linux/if_addr.h>
+#endif
+#ifdef HAVE_LINUX_RTNETLINK_H
+#include <linux/rtnetlink.h>
+#endif
+#endif
+#endif
+#if defined(HAVE_NET_ROUTE_H)
+# include <net/route.h>
+#elif defined(__APPLE__)
+/* Apple SDKs for iOS, tvOS, watchOS, etc. don't ship this header */
+# define RTM_NEWADDR 0xc
+# define RTM_DELADDR 0xd
+# define RTAX_IFA 5
+# define RTAX_MAX 8
+#endif
+/* local macros and datatypes used to get IP addresses system independently */
+#if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR)
+# error "Can't determine socket option to use to get UDP IP"
+#endif
+
+void recv_thread_destroy(void);
+
+#define MAXLEN_MBUF_CHAIN 128
+
+#define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
+
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__)
+#define NEXT_SA(ap) ap = (struct sockaddr *) \
+ ((caddr_t) ap + (ap->sa_len ? ROUNDUP(ap->sa_len, sizeof (uint32_t)) : sizeof(uint32_t)))
+#endif
+
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__)
+static void
+sctp_get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
+{
+ int i;
+
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (addrs & (1 << i)) {
+ rti_info[i] = sa;
+ NEXT_SA(sa);
+ } else {
+ rti_info[i] = NULL;
+ }
+ }
+}
+
+static void
+sctp_handle_ifamsg(unsigned char type, unsigned short index, struct sockaddr *sa)
+{
+ int rc;
+ struct ifaddrs *ifa, *ifas;
+
+ /* handle only the types we want */
+ if ((type != RTM_NEWADDR) && (type != RTM_DELADDR)) {
+ return;
+ }
+
+ rc = getifaddrs(&ifas);
+ if (rc != 0) {
+ return;
+ }
+ for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
+ if (index == if_nametoindex(ifa->ifa_name)) {
+ break;
+ }
+ }
+ if (ifa == NULL) {
+ freeifaddrs(ifas);
+ return;
+ }
+
+ /* relay the appropriate address change to the base code */
+ if (type == RTM_NEWADDR) {
+ (void)sctp_add_addr_to_vrf(SCTP_DEFAULT_VRFID,
+ NULL,
+ if_nametoindex(ifa->ifa_name),
+ 0,
+ ifa->ifa_name,
+ NULL,
+ sa,
+ 0,
+ 1);
+ } else {
+ sctp_del_addr_from_vrf(SCTP_DEFAULT_VRFID, ifa->ifa_addr,
+ if_nametoindex(ifa->ifa_name),
+ ifa->ifa_name);
+ }
+ freeifaddrs(ifas);
+}
+
+static void *
+recv_function_route(void *arg)
+{
+ ssize_t ret;
+ struct ifa_msghdr *ifa;
+ char rt_buffer[1024];
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+
+ sctp_userspace_set_threadname("SCTP addr mon");
+
+ while (1) {
+ memset(rt_buffer, 0, sizeof(rt_buffer));
+ ret = recv(SCTP_BASE_VAR(userspace_route), rt_buffer, sizeof(rt_buffer), 0);
+
+ if (ret > 0) {
+ ifa = (struct ifa_msghdr *) rt_buffer;
+ if (ifa->ifam_type != RTM_DELADDR && ifa->ifam_type != RTM_NEWADDR) {
+ continue;
+ }
+ sa = (struct sockaddr *) (ifa + 1);
+ sctp_get_rtaddrs(ifa->ifam_addrs, sa, rti_info);
+ switch (ifa->ifam_type) {
+ case RTM_DELADDR:
+ case RTM_NEWADDR:
+ sctp_handle_ifamsg(ifa->ifam_type, ifa->ifam_index, rti_info[RTAX_IFA]);
+ break;
+ default:
+ /* ignore this routing event */
+ break;
+ }
+ }
+ if (ret < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ }
+ return (NULL);
+}
+#endif
+
+#if 0
+/* This does not yet work on Linux */
+static void *
+recv_function_route(void *arg)
+{
+ int len;
+ char buf[4096];
+ struct iovec iov = { buf, sizeof(buf) };
+ struct msghdr msg;
+ struct nlmsghdr *nh;
+ struct ifaddrmsg *rtmsg;
+ struct rtattr *rtatp;
+ struct in_addr *inp;
+ struct sockaddr_nl sanl;
+#ifdef INET
+ struct sockaddr_in *sa;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sa6;
+#endif
+
+ for (;;) {
+ memset(&sanl, 0, sizeof(sanl));
+ sanl.nl_family = AF_NETLINK;
+ sanl.nl_groups = RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_IFADDR;
+ memset(&msg, 0, sizeof(struct msghdr));
+ msg.msg_name = (void *)&sanl;
+ msg.msg_namelen = sizeof(sanl);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+
+ len = recvmsg(SCTP_BASE_VAR(userspace_route), &msg, 0);
+
+ if (len < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ for (nh = (struct nlmsghdr *) buf; NLMSG_OK (nh, len);
+ nh = NLMSG_NEXT (nh, len)) {
+ if (nh->nlmsg_type == NLMSG_DONE)
+ break;
+
+ if (nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) {
+ rtmsg = (struct ifaddrmsg *)NLMSG_DATA(nh);
+ rtatp = (struct rtattr *)IFA_RTA(rtmsg);
+ if (rtatp->rta_type == IFA_ADDRESS) {
+ inp = (struct in_addr *)RTA_DATA(rtatp);
+ switch (rtmsg->ifa_family) {
+#ifdef INET
+ case AF_INET:
+ sa = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
+ sa->sin_family = rtmsg->ifa_family;
+ sa->sin_port = 0;
+ memcpy(&sa->sin_addr, inp, sizeof(struct in_addr));
+ sctp_handle_ifamsg(nh->nlmsg_type, rtmsg->ifa_index, (struct sockaddr *)sa);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa6 = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
+ sa6->sin6_family = rtmsg->ifa_family;
+ sa6->sin6_port = 0;
+ memcpy(&sa6->sin6_addr, inp, sizeof(struct in6_addr));
+ sctp_handle_ifamsg(nh->nlmsg_type, rtmsg->ifa_index, (struct sockaddr *)sa6);
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_USR, "Address family %d not supported.\n", rtmsg->ifa_family);
+ break;
+ }
+ }
+ }
+ }
+ }
+ return (NULL);
+}
+#endif
+
+#ifdef INET
+static void *
+recv_function_raw(void *arg)
+{
+ struct mbuf **recvmbuf;
+ struct ip *iphdr;
+ struct sctphdr *sh;
+ uint16_t port;
+ int offset, ecn = 0;
+ int compute_crc = 1;
+ struct sctp_chunkhdr *ch;
+ struct sockaddr_in src, dst;
+#if !defined(_WIN32)
+ ssize_t res;
+ unsigned int ncounter;
+ struct msghdr msg;
+ struct iovec recv_iovec[MAXLEN_MBUF_CHAIN];
+#else
+ WSABUF recv_iovec[MAXLEN_MBUF_CHAIN];
+ int nResult, m_ErrorCode;
+ DWORD flags;
+ DWORD ncounter;
+ struct sockaddr_in from;
+ int fromlen;
+#endif
+ /*Initially the entire set of mbufs is to be allocated.
+ to_fill indicates this amount. */
+ int to_fill = MAXLEN_MBUF_CHAIN;
+ /* iovlen is the size of each mbuf in the chain */
+ int i, n;
+ unsigned int iovlen = MCLBYTES;
+ int want_ext = (iovlen > MLEN)? 1 : 0;
+ int want_header = 0;
+
+ sctp_userspace_set_threadname("SCTP/IP4 rcv");
+
+ memset(&src, 0, sizeof(struct sockaddr_in));
+ memset(&dst, 0, sizeof(struct sockaddr_in));
+
+ recvmbuf = malloc(sizeof(struct mbuf *) * MAXLEN_MBUF_CHAIN);
+
+ while (1) {
+ for (i = 0; i < to_fill; i++) {
+ /* Not getting the packet header. Tests with chain of one run
+ as usual without having the packet header.
+ Have tried both sending and receiving
+ */
+ recvmbuf[i] = sctp_get_mbuf_for_msg(iovlen, want_header, M_NOWAIT, want_ext, MT_DATA);
+#if !defined(_WIN32)
+ recv_iovec[i].iov_base = (caddr_t)recvmbuf[i]->m_data;
+ recv_iovec[i].iov_len = iovlen;
+#else
+ recv_iovec[i].buf = (caddr_t)recvmbuf[i]->m_data;
+ recv_iovec[i].len = iovlen;
+#endif
+ }
+ to_fill = 0;
+#if defined(_WIN32)
+ flags = 0;
+ ncounter = 0;
+ fromlen = sizeof(struct sockaddr_in);
+ memset(&from, 0, sizeof(struct sockaddr_in));
+
+ nResult = WSARecvFrom(SCTP_BASE_VAR(userspace_rawsctp), recv_iovec, MAXLEN_MBUF_CHAIN, &ncounter, &flags, (struct sockaddr *)&from, &fromlen, NULL, NULL);
+ if (nResult != 0) {
+ m_ErrorCode = WSAGetLastError();
+ if ((m_ErrorCode == WSAENOTSOCK) || (m_ErrorCode == WSAEINTR)) {
+ break;
+ }
+ continue;
+ }
+ n = ncounter;
+#else
+ memset(&msg, 0, sizeof(struct msghdr));
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = recv_iovec;
+ msg.msg_iovlen = MAXLEN_MBUF_CHAIN;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ res = recvmsg(SCTP_BASE_VAR(userspace_rawsctp), &msg, 0);
+ if (res < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ ncounter = (unsigned int)res;
+ n = (int)res;
+#endif
+ SCTP_HEADER_LEN(recvmbuf[0]) = n; /* length of total packet */
+ SCTP_STAT_INCR(sctps_recvpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+
+ if ((unsigned int)n <= iovlen) {
+ SCTP_BUF_LEN(recvmbuf[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(recvmbuf[0]) = iovlen;
+
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ do {
+ recvmbuf[i]->m_next = recvmbuf[i+1];
+ SCTP_BUF_LEN(recvmbuf[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ } while (ncounter > 0);
+ }
+
+ offset = sizeof(struct ip) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ if (SCTP_BUF_LEN(recvmbuf[0]) < offset) {
+ if ((recvmbuf[0] = m_pullup(recvmbuf[0], offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ continue;
+ }
+ }
+ iphdr = mtod(recvmbuf[0], struct ip *);
+ sh = (struct sctphdr *)((caddr_t)iphdr + sizeof(struct ip));
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset -= sizeof(struct sctp_chunkhdr);
+
+ if (iphdr->ip_tos != 0) {
+ ecn = iphdr->ip_tos & 0x03;
+ }
+
+ dst.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ dst.sin_len = sizeof(struct sockaddr_in);
+#endif
+ dst.sin_addr = iphdr->ip_dst;
+ dst.sin_port = sh->dest_port;
+
+ src.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ src.sin_len = sizeof(struct sockaddr_in);
+#endif
+ src.sin_addr = iphdr->ip_src;
+ src.sin_port = sh->src_port;
+
+ /* SCTP does not allow broadcasts or multicasts */
+ if (IN_MULTICAST(ntohl(dst.sin_addr.s_addr))) {
+ m_freem(recvmbuf[0]);
+ continue;
+ }
+ if (SCTP_IS_IT_BROADCAST(dst.sin_addr, recvmbuf[0])) {
+ m_freem(recvmbuf[0]);
+ continue;
+ }
+
+ port = 0;
+
+ if (SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ ((IN4_ISLOOPBACK_ADDRESS(&src.sin_addr) &&
+ IN4_ISLOOPBACK_ADDRESS(&dst.sin_addr)) ||
+ (src.sin_addr.s_addr == dst.sin_addr.s_addr))) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Received %d bytes.", __func__, n);
+ SCTPDBG(SCTP_DEBUG_USR, " - calling sctp_common_input_processing with off=%d\n", offset);
+ sctp_common_input_processing(&recvmbuf[0], sizeof(struct ip), offset, n,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+ compute_crc,
+ ecn,
+ SCTP_DEFAULT_VRFID, port);
+ if (recvmbuf[0]) {
+ m_freem(recvmbuf[0]);
+ }
+ }
+ for (i = 0; i < MAXLEN_MBUF_CHAIN; i++) {
+ m_free(recvmbuf[i]);
+ }
+ /* free the array itself */
+ free(recvmbuf);
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Exiting SCTP/IP4 rcv\n", __func__);
+ return (NULL);
+}
+#endif
+
+#if defined(INET6)
+static void *
+recv_function_raw6(void *arg)
+{
+ struct mbuf **recvmbuf6;
+#if !defined(_WIN32)
+ ssize_t res;
+ unsigned int ncounter;
+ struct iovec recv_iovec[MAXLEN_MBUF_CHAIN];
+ struct msghdr msg;
+ struct cmsghdr *cmsgptr;
+ char cmsgbuf[CMSG_SPACE(sizeof (struct in6_pktinfo))];
+#else
+ WSABUF recv_iovec[MAXLEN_MBUF_CHAIN];
+ int nResult, m_ErrorCode;
+ DWORD ncounter = 0;
+ struct sockaddr_in6 from;
+ GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
+ LPFN_WSARECVMSG WSARecvMsg;
+ WSACMSGHDR *cmsgptr;
+ WSAMSG msg;
+ char ControlBuffer[1024];
+#endif
+ struct sockaddr_in6 src, dst;
+ struct sctphdr *sh;
+ int offset;
+ struct sctp_chunkhdr *ch;
+ /*Initially the entire set of mbufs is to be allocated.
+ to_fill indicates this amount. */
+ int to_fill = MAXLEN_MBUF_CHAIN;
+ /* iovlen is the size of each mbuf in the chain */
+ int i, n;
+ int compute_crc = 1;
+ unsigned int iovlen = MCLBYTES;
+ int want_ext = (iovlen > MLEN)? 1 : 0;
+ int want_header = 0;
+
+ sctp_userspace_set_threadname("SCTP/IP6 rcv");
+
+ recvmbuf6 = malloc(sizeof(struct mbuf *) * MAXLEN_MBUF_CHAIN);
+
+ for (;;) {
+ for (i = 0; i < to_fill; i++) {
+ /* Not getting the packet header. Tests with chain of one run
+ as usual without having the packet header.
+ Have tried both sending and receiving
+ */
+ recvmbuf6[i] = sctp_get_mbuf_for_msg(iovlen, want_header, M_NOWAIT, want_ext, MT_DATA);
+#if !defined(_WIN32)
+ recv_iovec[i].iov_base = (caddr_t)recvmbuf6[i]->m_data;
+ recv_iovec[i].iov_len = iovlen;
+#else
+ recv_iovec[i].buf = (caddr_t)recvmbuf6[i]->m_data;
+ recv_iovec[i].len = iovlen;
+#endif
+ }
+ to_fill = 0;
+#if defined(_WIN32)
+ ncounter = 0;
+ memset(&from, 0, sizeof(struct sockaddr_in6));
+ nResult = WSAIoctl(SCTP_BASE_VAR(userspace_rawsctp6), SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &WSARecvMsg_GUID, sizeof WSARecvMsg_GUID,
+ &WSARecvMsg, sizeof WSARecvMsg,
+ &ncounter, NULL, NULL);
+ if (nResult == 0) {
+ msg.name = (void *)&src;
+ msg.namelen = sizeof(struct sockaddr_in6);
+ msg.lpBuffers = recv_iovec;
+ msg.dwBufferCount = MAXLEN_MBUF_CHAIN;
+ msg.Control.len = sizeof ControlBuffer;
+ msg.Control.buf = ControlBuffer;
+ msg.dwFlags = 0;
+ nResult = WSARecvMsg(SCTP_BASE_VAR(userspace_rawsctp6), &msg, &ncounter, NULL, NULL);
+ }
+ if (nResult != 0) {
+ m_ErrorCode = WSAGetLastError();
+ if ((m_ErrorCode == WSAENOTSOCK) || (m_ErrorCode == WSAEINTR)) {
+ break;
+ }
+ continue;
+ }
+ n = ncounter;
+#else
+ memset(&msg, 0, sizeof(struct msghdr));
+ memset(&src, 0, sizeof(struct sockaddr_in6));
+ memset(&dst, 0, sizeof(struct sockaddr_in6));
+ memset(cmsgbuf, 0, CMSG_SPACE(sizeof (struct in6_pktinfo)));
+ msg.msg_name = (void *)&src;
+ msg.msg_namelen = sizeof(struct sockaddr_in6);
+ msg.msg_iov = recv_iovec;
+ msg.msg_iovlen = MAXLEN_MBUF_CHAIN;
+ msg.msg_control = (void *)cmsgbuf;
+ msg.msg_controllen = (socklen_t)CMSG_SPACE(sizeof (struct in6_pktinfo));
+ msg.msg_flags = 0;
+ res = recvmsg(SCTP_BASE_VAR(userspace_rawsctp6), &msg, 0);
+ if (res < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ ncounter = (unsigned int)res;
+ n = (int)res;
+#endif
+ SCTP_HEADER_LEN(recvmbuf6[0]) = n; /* length of total packet */
+ SCTP_STAT_INCR(sctps_recvpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+
+ if ((unsigned int)n <= iovlen) {
+ SCTP_BUF_LEN(recvmbuf6[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(recvmbuf6[0]) = iovlen;
+
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ do {
+ recvmbuf6[i]->m_next = recvmbuf6[i+1];
+ SCTP_BUF_LEN(recvmbuf6[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ } while (ncounter > 0);
+ }
+
+ for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
+ if ((cmsgptr->cmsg_level == IPPROTO_IPV6) && (cmsgptr->cmsg_type == IPV6_PKTINFO)) {
+ struct in6_pktinfo * info;
+
+ info = (struct in6_pktinfo *)CMSG_DATA(cmsgptr);
+ memcpy((void *)&dst.sin6_addr, (const void *) &(info->ipi6_addr), sizeof(struct in6_addr));
+ break;
+ }
+ }
+
+ /* SCTP does not allow broadcasts or multicasts */
+ if (IN6_IS_ADDR_MULTICAST(&dst.sin6_addr)) {
+ m_freem(recvmbuf6[0]);
+ continue;
+ }
+
+ offset = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ if (SCTP_BUF_LEN(recvmbuf6[0]) < offset) {
+ if ((recvmbuf6[0] = m_pullup(recvmbuf6[0], offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ continue;
+ }
+ }
+ sh = mtod(recvmbuf6[0], struct sctphdr *);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset -= sizeof(struct sctp_chunkhdr);
+
+ dst.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ dst.sin6_port = sh->dest_port;
+
+ src.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ src.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ src.sin6_port = sh->src_port;
+ if (SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ (memcmp(&src.sin6_addr, &dst.sin6_addr, sizeof(struct in6_addr)) == 0)) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Received %d bytes.", __func__, n);
+ SCTPDBG(SCTP_DEBUG_USR, " - calling sctp_common_input_processing with off=%d\n", offset);
+ sctp_common_input_processing(&recvmbuf6[0], 0, offset, n,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+ compute_crc,
+ 0,
+ SCTP_DEFAULT_VRFID, 0);
+ if (recvmbuf6[0]) {
+ m_freem(recvmbuf6[0]);
+ }
+ }
+ for (i = 0; i < MAXLEN_MBUF_CHAIN; i++) {
+ m_free(recvmbuf6[i]);
+ }
+ /* free the array itself */
+ free(recvmbuf6);
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Exiting SCTP/IP6 rcv\n", __func__);
+ return (NULL);
+}
+#endif
+
+#ifdef INET
+static void *
+recv_function_udp(void *arg)
+{
+ struct mbuf **udprecvmbuf;
+ /*Initially the entire set of mbufs is to be allocated.
+ to_fill indicates this amount. */
+ int to_fill = MAXLEN_MBUF_CHAIN;
+ /* iovlen is the size of each mbuf in the chain */
+ int i, n, offset;
+ unsigned int iovlen = MCLBYTES;
+ int want_ext = (iovlen > MLEN)? 1 : 0;
+ int want_header = 0;
+ struct sctphdr *sh;
+ uint16_t port;
+ struct sctp_chunkhdr *ch;
+ struct sockaddr_in src, dst;
+#if defined(IP_PKTINFO)
+ char cmsgbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#else
+ char cmsgbuf[CMSG_SPACE(sizeof(struct in_addr))];
+#endif
+ int compute_crc = 1;
+#if !defined(_WIN32)
+ ssize_t res;
+ unsigned int ncounter;
+ struct iovec iov[MAXLEN_MBUF_CHAIN];
+ struct msghdr msg;
+ struct cmsghdr *cmsgptr;
+#else
+ GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
+ LPFN_WSARECVMSG WSARecvMsg;
+ char ControlBuffer[1024];
+ WSABUF iov[MAXLEN_MBUF_CHAIN];
+ WSAMSG msg;
+ int nResult, m_ErrorCode;
+ WSACMSGHDR *cmsgptr;
+ DWORD ncounter;
+#endif
+
+ sctp_userspace_set_threadname("SCTP/UDP/IP4 rcv");
+
+ udprecvmbuf = malloc(sizeof(struct mbuf *) * MAXLEN_MBUF_CHAIN);
+
+ while (1) {
+ for (i = 0; i < to_fill; i++) {
+ /* Not getting the packet header. Tests with chain of one run
+ as usual without having the packet header.
+ Have tried both sending and receiving
+ */
+ udprecvmbuf[i] = sctp_get_mbuf_for_msg(iovlen, want_header, M_NOWAIT, want_ext, MT_DATA);
+#if !defined(_WIN32)
+ iov[i].iov_base = (caddr_t)udprecvmbuf[i]->m_data;
+ iov[i].iov_len = iovlen;
+#else
+ iov[i].buf = (caddr_t)udprecvmbuf[i]->m_data;
+ iov[i].len = iovlen;
+#endif
+ }
+ to_fill = 0;
+#if !defined(_WIN32)
+ memset(&msg, 0, sizeof(struct msghdr));
+#else
+ memset(&msg, 0, sizeof(WSAMSG));
+#endif
+ memset(&src, 0, sizeof(struct sockaddr_in));
+ memset(&dst, 0, sizeof(struct sockaddr_in));
+ memset(cmsgbuf, 0, sizeof(cmsgbuf));
+
+#if !defined(_WIN32)
+ msg.msg_name = (void *)&src;
+ msg.msg_namelen = sizeof(struct sockaddr_in);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = MAXLEN_MBUF_CHAIN;
+ msg.msg_control = (void *)cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ msg.msg_flags = 0;
+
+ res = recvmsg(SCTP_BASE_VAR(userspace_udpsctp), &msg, 0);
+ if (res < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ ncounter = (unsigned int)res;
+ n = (int)res;
+#else
+ nResult = WSAIoctl(SCTP_BASE_VAR(userspace_udpsctp), SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &WSARecvMsg_GUID, sizeof WSARecvMsg_GUID,
+ &WSARecvMsg, sizeof WSARecvMsg,
+ &ncounter, NULL, NULL);
+ if (nResult == 0) {
+ msg.name = (void *)&src;
+ msg.namelen = sizeof(struct sockaddr_in);
+ msg.lpBuffers = iov;
+ msg.dwBufferCount = MAXLEN_MBUF_CHAIN;
+ msg.Control.len = sizeof ControlBuffer;
+ msg.Control.buf = ControlBuffer;
+ msg.dwFlags = 0;
+ nResult = WSARecvMsg(SCTP_BASE_VAR(userspace_udpsctp), &msg, &ncounter, NULL, NULL);
+ }
+ if (nResult != 0) {
+ m_ErrorCode = WSAGetLastError();
+ if ((m_ErrorCode == WSAENOTSOCK) || (m_ErrorCode == WSAEINTR)) {
+ break;
+ }
+ continue;
+ }
+ n = ncounter;
+#endif
+ SCTP_HEADER_LEN(udprecvmbuf[0]) = n; /* length of total packet */
+ SCTP_STAT_INCR(sctps_recvpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+
+ if ((unsigned int)n <= iovlen) {
+ SCTP_BUF_LEN(udprecvmbuf[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(udprecvmbuf[0]) = iovlen;
+
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ do {
+ udprecvmbuf[i]->m_next = udprecvmbuf[i+1];
+ SCTP_BUF_LEN(udprecvmbuf[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ } while (ncounter > 0);
+ }
+
+ for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
+#if defined(IP_PKTINFO)
+ if ((cmsgptr->cmsg_level == IPPROTO_IP) && (cmsgptr->cmsg_type == IP_PKTINFO)) {
+ struct in_pktinfo *info;
+
+ dst.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ dst.sin_len = sizeof(struct sockaddr_in);
+#endif
+ info = (struct in_pktinfo *)CMSG_DATA(cmsgptr);
+ memcpy((void *)&dst.sin_addr, (const void *)&(info->ipi_addr), sizeof(struct in_addr));
+ break;
+ }
+#else
+ if ((cmsgptr->cmsg_level == IPPROTO_IP) && (cmsgptr->cmsg_type == IP_RECVDSTADDR)) {
+ struct in_addr *addr;
+
+ dst.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ dst.sin_len = sizeof(struct sockaddr_in);
+#endif
+ addr = (struct in_addr *)CMSG_DATA(cmsgptr);
+ memcpy((void *)&dst.sin_addr, (const void *)addr, sizeof(struct in_addr));
+ break;
+ }
+#endif
+ }
+
+ /* SCTP does not allow broadcasts or multicasts */
+ if (IN_MULTICAST(ntohl(dst.sin_addr.s_addr))) {
+ m_freem(udprecvmbuf[0]);
+ continue;
+ }
+ if (SCTP_IS_IT_BROADCAST(dst.sin_addr, udprecvmbuf[0])) {
+ m_freem(udprecvmbuf[0]);
+ continue;
+ }
+
+ offset = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ if (SCTP_BUF_LEN(udprecvmbuf[0]) < offset) {
+ if ((udprecvmbuf[0] = m_pullup(udprecvmbuf[0], offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ continue;
+ }
+ }
+ sh = mtod(udprecvmbuf[0], struct sctphdr *);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset -= sizeof(struct sctp_chunkhdr);
+
+ port = src.sin_port;
+ src.sin_port = sh->src_port;
+ dst.sin_port = sh->dest_port;
+ if (SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ (src.sin_addr.s_addr == dst.sin_addr.s_addr)) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Received %d bytes.", __func__, n);
+ SCTPDBG(SCTP_DEBUG_USR, " - calling sctp_common_input_processing with off=%d\n", offset);
+ sctp_common_input_processing(&udprecvmbuf[0], 0, offset, n,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+ compute_crc,
+ 0,
+ SCTP_DEFAULT_VRFID, port);
+ if (udprecvmbuf[0]) {
+ m_freem(udprecvmbuf[0]);
+ }
+ }
+ for (i = 0; i < MAXLEN_MBUF_CHAIN; i++) {
+ m_free(udprecvmbuf[i]);
+ }
+ /* free the array itself */
+ free(udprecvmbuf);
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Exiting SCTP/UDP/IP4 rcv\n", __func__);
+ return (NULL);
+}
+#endif
+
+#if defined(INET6)
+static void *
+recv_function_udp6(void *arg)
+{
+ struct mbuf **udprecvmbuf6;
+ /*Initially the entire set of mbufs is to be allocated.
+ to_fill indicates this amount. */
+ int to_fill = MAXLEN_MBUF_CHAIN;
+ /* iovlen is the size of each mbuf in the chain */
+ int i, n, offset;
+ unsigned int iovlen = MCLBYTES;
+ int want_ext = (iovlen > MLEN)? 1 : 0;
+ int want_header = 0;
+ struct sockaddr_in6 src, dst;
+ struct sctphdr *sh;
+ uint16_t port;
+ struct sctp_chunkhdr *ch;
+ char cmsgbuf[CMSG_SPACE(sizeof (struct in6_pktinfo))];
+ int compute_crc = 1;
+#if !defined(_WIN32)
+ struct iovec iov[MAXLEN_MBUF_CHAIN];
+ struct msghdr msg;
+ struct cmsghdr *cmsgptr;
+ ssize_t res;
+ unsigned int ncounter;
+#else
+ GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
+ LPFN_WSARECVMSG WSARecvMsg;
+ char ControlBuffer[1024];
+ WSABUF iov[MAXLEN_MBUF_CHAIN];
+ WSAMSG msg;
+ int nResult, m_ErrorCode;
+ WSACMSGHDR *cmsgptr;
+ DWORD ncounter;
+#endif
+
+ sctp_userspace_set_threadname("SCTP/UDP/IP6 rcv");
+
+ udprecvmbuf6 = malloc(sizeof(struct mbuf *) * MAXLEN_MBUF_CHAIN);
+ while (1) {
+ for (i = 0; i < to_fill; i++) {
+ /* Not getting the packet header. Tests with chain of one run
+ as usual without having the packet header.
+ Have tried both sending and receiving
+ */
+ udprecvmbuf6[i] = sctp_get_mbuf_for_msg(iovlen, want_header, M_NOWAIT, want_ext, MT_DATA);
+#if !defined(_WIN32)
+ iov[i].iov_base = (caddr_t)udprecvmbuf6[i]->m_data;
+ iov[i].iov_len = iovlen;
+#else
+ iov[i].buf = (caddr_t)udprecvmbuf6[i]->m_data;
+ iov[i].len = iovlen;
+#endif
+ }
+ to_fill = 0;
+
+#if !defined(_WIN32)
+ memset(&msg, 0, sizeof(struct msghdr));
+#else
+ memset(&msg, 0, sizeof(WSAMSG));
+#endif
+ memset(&src, 0, sizeof(struct sockaddr_in6));
+ memset(&dst, 0, sizeof(struct sockaddr_in6));
+ memset(cmsgbuf, 0, CMSG_SPACE(sizeof (struct in6_pktinfo)));
+
+#if !defined(_WIN32)
+ msg.msg_name = (void *)&src;
+ msg.msg_namelen = sizeof(struct sockaddr_in6);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = MAXLEN_MBUF_CHAIN;
+ msg.msg_control = (void *)cmsgbuf;
+ msg.msg_controllen = (socklen_t)CMSG_SPACE(sizeof (struct in6_pktinfo));
+ msg.msg_flags = 0;
+
+ res = recvmsg(SCTP_BASE_VAR(userspace_udpsctp6), &msg, 0);
+ if (res < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ ncounter = (unsigned int)res;
+ n = (int)res;
+#else
+ nResult = WSAIoctl(SCTP_BASE_VAR(userspace_udpsctp6), SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &WSARecvMsg_GUID, sizeof WSARecvMsg_GUID,
+ &WSARecvMsg, sizeof WSARecvMsg,
+ &ncounter, NULL, NULL);
+ if (nResult == SOCKET_ERROR) {
+ m_ErrorCode = WSAGetLastError();
+ WSARecvMsg = NULL;
+ }
+ if (nResult == 0) {
+ msg.name = (void *)&src;
+ msg.namelen = sizeof(struct sockaddr_in6);
+ msg.lpBuffers = iov;
+ msg.dwBufferCount = MAXLEN_MBUF_CHAIN;
+ msg.Control.len = sizeof ControlBuffer;
+ msg.Control.buf = ControlBuffer;
+ msg.dwFlags = 0;
+ nResult = WSARecvMsg(SCTP_BASE_VAR(userspace_udpsctp6), &msg, &ncounter, NULL, NULL);
+ }
+ if (nResult != 0) {
+ m_ErrorCode = WSAGetLastError();
+ if ((m_ErrorCode == WSAENOTSOCK) || (m_ErrorCode == WSAEINTR)) {
+ break;
+ }
+ continue;
+ }
+ n = ncounter;
+#endif
+ SCTP_HEADER_LEN(udprecvmbuf6[0]) = n; /* length of total packet */
+ SCTP_STAT_INCR(sctps_recvpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+
+ if ((unsigned int)n <= iovlen) {
+ SCTP_BUF_LEN(udprecvmbuf6[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(udprecvmbuf6[0]) = iovlen;
+
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ do {
+ udprecvmbuf6[i]->m_next = udprecvmbuf6[i+1];
+ SCTP_BUF_LEN(udprecvmbuf6[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ ncounter -= min(ncounter, iovlen);
+ (to_fill)++;
+ } while (ncounter > 0);
+ }
+
+ for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
+ if ((cmsgptr->cmsg_level == IPPROTO_IPV6) && (cmsgptr->cmsg_type == IPV6_PKTINFO)) {
+ struct in6_pktinfo *info;
+
+ dst.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ info = (struct in6_pktinfo *)CMSG_DATA(cmsgptr);
+ /*dst.sin6_port = htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port));*/
+ memcpy((void *)&dst.sin6_addr, (const void *)&(info->ipi6_addr), sizeof(struct in6_addr));
+ }
+ }
+
+ /* SCTP does not allow broadcasts or multicasts */
+ if (IN6_IS_ADDR_MULTICAST(&dst.sin6_addr)) {
+ m_freem(udprecvmbuf6[0]);
+ continue;
+ }
+
+ offset = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ if (SCTP_BUF_LEN(udprecvmbuf6[0]) < offset) {
+ if ((udprecvmbuf6[0] = m_pullup(udprecvmbuf6[0], offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ continue;
+ }
+ }
+ sh = mtod(udprecvmbuf6[0], struct sctphdr *);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset -= sizeof(struct sctp_chunkhdr);
+
+ port = src.sin6_port;
+ src.sin6_port = sh->src_port;
+ dst.sin6_port = sh->dest_port;
+ if (SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) &&
+ (memcmp(&src.sin6_addr, &dst.sin6_addr, sizeof(struct in6_addr)) == 0)) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvhwcrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Received %d bytes.", __func__, n);
+ SCTPDBG(SCTP_DEBUG_USR, " - calling sctp_common_input_processing with off=%d\n", (int)sizeof(struct sctphdr));
+ sctp_common_input_processing(&udprecvmbuf6[0], 0, offset, n,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+ compute_crc,
+ 0,
+ SCTP_DEFAULT_VRFID, port);
+ if (udprecvmbuf6[0]) {
+ m_freem(udprecvmbuf6[0]);
+ }
+ }
+ for (i = 0; i < MAXLEN_MBUF_CHAIN; i++) {
+ m_free(udprecvmbuf6[i]);
+ }
+ /* free the array itself */
+ free(udprecvmbuf6);
+ SCTPDBG(SCTP_DEBUG_USR, "%s: Exiting SCTP/UDP/IP6 rcv\n", __func__);
+ return (NULL);
+}
+#endif
+
+#if defined(_WIN32)
+static void
+setReceiveBufferSize(SOCKET sfd, int new_size)
+#else
+static void
+setReceiveBufferSize(int sfd, int new_size)
+#endif
+{
+ int ch = new_size;
+
+ if (setsockopt (sfd, SOL_SOCKET, SO_RCVBUF, (void*)&ch, sizeof(ch)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set recv-buffers size (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set recv-buffers size (errno = %d).\n", errno);
+#endif
+ }
+ return;
+}
+
+#if defined(_WIN32)
+static void
+setSendBufferSize(SOCKET sfd, int new_size)
+#else
+static void
+setSendBufferSize(int sfd, int new_size)
+#endif
+{
+ int ch = new_size;
+
+ if (setsockopt (sfd, SOL_SOCKET, SO_SNDBUF, (void*)&ch, sizeof(ch)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set send-buffers size (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set send-buffers size (errno = %d).\n", errno);
+#endif
+ }
+ return;
+}
+
+#define SOCKET_TIMEOUT 100 /* in ms */
+void
+recv_thread_init(void)
+{
+#if defined(INET)
+ struct sockaddr_in addr_ipv4;
+ const int hdrincl = 1;
+#endif
+#if defined(INET6)
+ struct sockaddr_in6 addr_ipv6;
+#endif
+#if defined(INET) || defined(INET6)
+ const int on = 1;
+#endif
+#if !defined(_WIN32)
+ struct timeval timeout;
+
+ memset(&timeout, 0, sizeof(struct timeval));
+ timeout.tv_sec = (SOCKET_TIMEOUT / 1000);
+ timeout.tv_usec = (SOCKET_TIMEOUT % 1000) * 1000;
+#else
+ unsigned int timeout = SOCKET_TIMEOUT; /* Timeout in milliseconds */
+#endif
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__)
+ if (SCTP_BASE_VAR(userspace_route) == -1) {
+ if ((SCTP_BASE_VAR(userspace_route) = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create routing socket (errno = %d).\n", errno);
+ }
+#if 0
+ struct sockaddr_nl sanl;
+
+ if ((SCTP_BASE_VAR(userspace_route) = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create routing socket (errno = %d.\n", errno);
+ }
+ memset(&sanl, 0, sizeof(sanl));
+ sanl.nl_family = AF_NETLINK;
+ sanl.nl_groups = 0;
+#ifdef INET
+ sanl.nl_groups |= RTMGRP_IPV4_IFADDR;
+#endif
+#ifdef INET6
+ sanl.nl_groups |= RTMGRP_IPV6_IFADDR;
+#endif
+ if (bind(SCTP_BASE_VAR(userspace_route), (struct sockaddr *) &sanl, sizeof(sanl)) < 0) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind routing socket (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_route));
+ SCTP_BASE_VAR(userspace_route) = -1;
+ }
+#endif
+ if (SCTP_BASE_VAR(userspace_route) != -1) {
+ if (setsockopt(SCTP_BASE_VAR(userspace_route), SOL_SOCKET, SO_RCVTIMEO,(const void*)&timeout, sizeof(struct timeval)) < 0) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on routing socket (errno = %d).\n", errno);
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_route));
+#else
+ close(SCTP_BASE_VAR(userspace_route));
+#endif
+ SCTP_BASE_VAR(userspace_route) = -1;
+ }
+ }
+ }
+#endif
+#if defined(INET)
+ if (SCTP_BASE_VAR(userspace_rawsctp) == -1) {
+ if ((SCTP_BASE_VAR(userspace_rawsctp) = socket(AF_INET, SOCK_RAW, IPPROTO_SCTP)) == -1) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create raw socket for IPv4 (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create raw socket for IPv4 (errno = %d).\n", errno);
+#endif
+ } else {
+ /* complete setting up the raw SCTP socket */
+ if (setsockopt(SCTP_BASE_VAR(userspace_rawsctp), IPPROTO_IP, IP_HDRINCL,(const void*)&hdrincl, sizeof(int)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IP_HDRINCL (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IP_HDRINCL (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_rawsctp));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ } else if (setsockopt(SCTP_BASE_VAR(userspace_rawsctp), SOL_SOCKET, SO_RCVTIMEO, (const void *)&timeout, sizeof(timeout)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/IPv4 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/IPv4 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_rawsctp));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ } else {
+ memset((void *)&addr_ipv4, 0, sizeof(struct sockaddr_in));
+#ifdef HAVE_SIN_LEN
+ addr_ipv4.sin_len = sizeof(struct sockaddr_in);
+#endif
+ addr_ipv4.sin_family = AF_INET;
+ addr_ipv4.sin_port = htons(0);
+ addr_ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bind(SCTP_BASE_VAR(userspace_rawsctp), (const struct sockaddr *)&addr_ipv4, sizeof(struct sockaddr_in)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/IPv4 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/IPv4 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_rawsctp));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ } else {
+ setReceiveBufferSize(SCTP_BASE_VAR(userspace_rawsctp), SB_RAW); /* 128K */
+ setSendBufferSize(SCTP_BASE_VAR(userspace_rawsctp), SB_RAW); /* 128K Is this setting net.inet.raw.maxdgram value? Should it be set to 64K? */
+ }
+ }
+ }
+ }
+ if ((SCTP_BASE_VAR(userspace_udpsctp) == -1) && (SCTP_BASE_SYSCTL(sctp_udp_tunneling_port) != 0)) {
+ if ((SCTP_BASE_VAR(userspace_udpsctp) = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create socket for SCTP/UDP/IPv4 (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create socket for SCTP/UDP/IPv4 (errno = %d).\n", errno);
+#endif
+ } else {
+#if defined(IP_PKTINFO)
+ if (setsockopt(SCTP_BASE_VAR(userspace_udpsctp), IPPROTO_IP, IP_PKTINFO, (const void *)&on, (int)sizeof(int)) < 0) {
+#else
+ if (setsockopt(SCTP_BASE_VAR(userspace_udpsctp), IPPROTO_IP, IP_RECVDSTADDR, (const void *)&on, (int)sizeof(int)) < 0) {
+#endif
+#if defined(_WIN32)
+#if defined(IP_PKTINFO)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IP_PKTINFO on socket for SCTP/UDP/IPv4 (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IP_RECVDSTADDR on socket for SCTP/UDP/IPv4 (errno = %d).\n", WSAGetLastError());
+#endif
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp));
+#else
+#if defined(IP_PKTINFO)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IP_PKTINFO on socket for SCTP/UDP/IPv4 (errno = %d).\n", errno);
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IP_RECVDSTADDR on socket for SCTP/UDP/IPv4 (errno = %d).\n", errno);
+#endif
+ close(SCTP_BASE_VAR(userspace_udpsctp));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ } else if (setsockopt(SCTP_BASE_VAR(userspace_udpsctp), SOL_SOCKET, SO_RCVTIMEO, (const void *)&timeout, sizeof(timeout)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/UDP/IPv4 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/UDP/IPv4 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_udpsctp));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ } else {
+ memset((void *)&addr_ipv4, 0, sizeof(struct sockaddr_in));
+#ifdef HAVE_SIN_LEN
+ addr_ipv4.sin_len = sizeof(struct sockaddr_in);
+#endif
+ addr_ipv4.sin_family = AF_INET;
+ addr_ipv4.sin_port = htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port));
+ addr_ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bind(SCTP_BASE_VAR(userspace_udpsctp), (const struct sockaddr *)&addr_ipv4, sizeof(struct sockaddr_in)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/UDP/IPv4 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/UDP/IPv4 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_udpsctp));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ } else {
+ setReceiveBufferSize(SCTP_BASE_VAR(userspace_udpsctp), SB_RAW); /* 128K */
+ setSendBufferSize(SCTP_BASE_VAR(userspace_udpsctp), SB_RAW); /* 128K Is this setting net.inet.raw.maxdgram value? Should it be set to 64K? */
+ }
+ }
+ }
+ }
+#endif
+#if defined(INET6)
+ if (SCTP_BASE_VAR(userspace_rawsctp6) == -1) {
+ if ((SCTP_BASE_VAR(userspace_rawsctp6) = socket(AF_INET6, SOCK_RAW, IPPROTO_SCTP)) == -1) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create socket for SCTP/IPv6 (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create socket for SCTP/IPv6 (errno = %d).\n", errno);
+#endif
+ } else {
+ /* complete setting up the raw SCTP socket */
+#if defined(IPV6_RECVPKTINFO)
+ if (setsockopt(SCTP_BASE_VAR(userspace_rawsctp6), IPPROTO_IPV6, IPV6_RECVPKTINFO, (const void *)&on, sizeof(on)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_RECVPKTINFO on socket for SCTP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_RECVPKTINFO on socket for SCTP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ } else {
+#else
+ if (setsockopt(SCTP_BASE_VAR(userspace_rawsctp6), IPPROTO_IPV6, IPV6_PKTINFO,(const void*)&on, sizeof(on)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_PKTINFO on socket for SCTP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_PKTINFO on socket for SCTP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ } else {
+#endif
+ if (setsockopt(SCTP_BASE_VAR(userspace_rawsctp6), IPPROTO_IPV6, IPV6_V6ONLY, (const void*)&on, (socklen_t)sizeof(on)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_V6ONLY on socket for SCTP/IPv6 (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_V6ONLY on socket for SCTP/IPv6 (errno = %d).\n", errno);
+#endif
+ }
+ if (setsockopt(SCTP_BASE_VAR(userspace_rawsctp6), SOL_SOCKET, SO_RCVTIMEO, (const void *)&timeout, sizeof(timeout)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ } else {
+ memset((void *)&addr_ipv6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN6_LEN
+ addr_ipv6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ addr_ipv6.sin6_family = AF_INET6;
+ addr_ipv6.sin6_port = htons(0);
+ addr_ipv6.sin6_addr = in6addr_any;
+ if (bind(SCTP_BASE_VAR(userspace_rawsctp6), (const struct sockaddr *)&addr_ipv6, sizeof(struct sockaddr_in6)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ } else {
+ setReceiveBufferSize(SCTP_BASE_VAR(userspace_rawsctp6), SB_RAW); /* 128K */
+ setSendBufferSize(SCTP_BASE_VAR(userspace_rawsctp6), SB_RAW); /* 128K Is this setting net.inet.raw.maxdgram value? Should it be set to 64K? */
+ }
+ }
+ }
+ }
+ }
+ if ((SCTP_BASE_VAR(userspace_udpsctp6) == -1) && (SCTP_BASE_SYSCTL(sctp_udp_tunneling_port) != 0)) {
+ if ((SCTP_BASE_VAR(userspace_udpsctp6) = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create socket for SCTP/UDP/IPv6 (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't create socket for SCTP/UDP/IPv6 (errno = %d).\n", errno);
+#endif
+ }
+#if defined(IPV6_RECVPKTINFO)
+ if (setsockopt(SCTP_BASE_VAR(userspace_udpsctp6), IPPROTO_IPV6, IPV6_RECVPKTINFO, (const void *)&on, (int)sizeof(int)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_RECVPKTINFO on socket for SCTP/UDP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_RECVPKTINFO on socket for SCTP/UDP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ } else {
+#else
+ if (setsockopt(SCTP_BASE_VAR(userspace_udpsctp6), IPPROTO_IPV6, IPV6_PKTINFO, (const void *)&on, (int)sizeof(int)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_PKTINFO on socket for SCTP/UDP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_PKTINFO on socket for SCTP/UDP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ } else {
+#endif
+ if (setsockopt(SCTP_BASE_VAR(userspace_udpsctp6), IPPROTO_IPV6, IPV6_V6ONLY, (const void *)&on, (socklen_t)sizeof(on)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_V6ONLY on socket for SCTP/UDP/IPv6 (errno = %d).\n", WSAGetLastError());
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set IPV6_V6ONLY on socket for SCTP/UDP/IPv6 (errno = %d).\n", errno);
+#endif
+ }
+ if (setsockopt(SCTP_BASE_VAR(userspace_udpsctp6), SOL_SOCKET, SO_RCVTIMEO, (const void *)&timeout, sizeof(timeout)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/UDP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't set timeout on socket for SCTP/UDP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ } else {
+ memset((void *)&addr_ipv6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN6_LEN
+ addr_ipv6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ addr_ipv6.sin6_family = AF_INET6;
+ addr_ipv6.sin6_port = htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port));
+ addr_ipv6.sin6_addr = in6addr_any;
+ if (bind(SCTP_BASE_VAR(userspace_udpsctp6), (const struct sockaddr *)&addr_ipv6, sizeof(struct sockaddr_in6)) < 0) {
+#if defined(_WIN32)
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/UDP/IPv6 (errno = %d).\n", WSAGetLastError());
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+#else
+ SCTPDBG(SCTP_DEBUG_USR, "Can't bind socket for SCTP/UDP/IPv6 (errno = %d).\n", errno);
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ } else {
+ setReceiveBufferSize(SCTP_BASE_VAR(userspace_udpsctp6), SB_RAW); /* 128K */
+ setSendBufferSize(SCTP_BASE_VAR(userspace_udpsctp6), SB_RAW); /* 128K Is this setting net.inet.raw.maxdgram value? Should it be set to 64K? */
+ }
+ }
+ }
+ }
+#endif
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__)
+#if defined(INET) || defined(INET6)
+ if (SCTP_BASE_VAR(userspace_route) != -1) {
+ int rc;
+
+ if ((rc = sctp_userspace_thread_create(&SCTP_BASE_VAR(recvthreadroute), &recv_function_route))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start routing thread (%d).\n", rc);
+ close(SCTP_BASE_VAR(userspace_route));
+ SCTP_BASE_VAR(userspace_route) = -1;
+ }
+ }
+#endif
+#endif
+#if defined(INET)
+ if (SCTP_BASE_VAR(userspace_rawsctp) != -1) {
+ int rc;
+
+ if ((rc = sctp_userspace_thread_create(&SCTP_BASE_VAR(recvthreadraw), &recv_function_raw))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/IPv4 recv thread (%d).\n", rc);
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp));
+#else
+ close(SCTP_BASE_VAR(userspace_rawsctp));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ }
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp) != -1) {
+ int rc;
+
+ if ((rc = sctp_userspace_thread_create(&SCTP_BASE_VAR(recvthreadudp), &recv_function_udp))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/UDP/IPv4 recv thread (%d).\n", rc);
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp));
+#else
+ close(SCTP_BASE_VAR(userspace_udpsctp));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ }
+ }
+#endif
+#if defined(INET6)
+ if (SCTP_BASE_VAR(userspace_rawsctp6) != -1) {
+ int rc;
+
+ if ((rc = sctp_userspace_thread_create(&SCTP_BASE_VAR(recvthreadraw6), &recv_function_raw6))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/IPv6 recv thread (%d).\n", rc);
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+#else
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ }
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp6) != -1) {
+ int rc;
+
+ if ((rc = sctp_userspace_thread_create(&SCTP_BASE_VAR(recvthreadudp6), &recv_function_udp6))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/UDP/IPv6 recv thread (%d).\n", rc);
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+#else
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+#endif
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ }
+ }
+#endif
+}
+
+void
+recv_thread_destroy(void)
+{
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__)
+#if defined(INET) || defined(INET6)
+ if (SCTP_BASE_VAR(userspace_route) != -1) {
+ close(SCTP_BASE_VAR(userspace_route));
+ pthread_join(SCTP_BASE_VAR(recvthreadroute), NULL);
+ }
+#endif
+#endif
+#if defined(INET)
+ if (SCTP_BASE_VAR(userspace_rawsctp) != -1) {
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp));
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadraw), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadraw));
+#else
+ close(SCTP_BASE_VAR(userspace_rawsctp));
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ pthread_join(SCTP_BASE_VAR(recvthreadraw), NULL);
+#endif
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp) != -1) {
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp));
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadudp), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadudp));
+#else
+ close(SCTP_BASE_VAR(userspace_udpsctp));
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ pthread_join(SCTP_BASE_VAR(recvthreadudp), NULL);
+#endif
+ }
+#endif
+#if defined(INET6)
+ if (SCTP_BASE_VAR(userspace_rawsctp6) != -1) {
+#if defined(_WIN32)
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadraw6), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadraw6));
+#else
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ pthread_join(SCTP_BASE_VAR(recvthreadraw6), NULL);
+#endif
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp6) != -1) {
+#if defined(_WIN32)
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadudp6), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadudp6));
+#else
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ pthread_join(SCTP_BASE_VAR(recvthreadudp6), NULL);
+#endif
+ }
+#endif
+}
+#else
+int foo;
+#endif
diff --git a/netwerk/sctp/src/user_recv_thread.h b/netwerk/sctp/src/user_recv_thread.h
new file mode 100644
index 0000000000..61cdf17211
--- /dev/null
+++ b/netwerk/sctp/src/user_recv_thread.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 2012 Michael Tuexen
+ * Copyright (c) 2012 Irene Ruengeler
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USER_RECV_THREAD_H_
+#define _USER_RECV_THREAD_H_
+
+void recv_thread_init(void);
+void recv_thread_destroy(void);
+
+#endif
diff --git a/netwerk/sctp/src/user_route.h b/netwerk/sctp/src/user_route.h
new file mode 100644
index 0000000000..4abf2eac98
--- /dev/null
+++ b/netwerk/sctp/src/user_route.h
@@ -0,0 +1,130 @@
+/*-
+ * Copyright (c) 1980, 1986, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _USER_ROUTE_H_
+#define _USER_ROUTE_H_
+
+/*
+ * Kernel resident routing tables.
+ *
+ * The routing tables are initialized when interface addresses
+ * are set by making entries for all directly connected interfaces.
+ */
+
+/*
+ * A route consists of a destination address and a reference
+ * to a routing entry. These are often held by protocols
+ * in their control blocks, e.g. inpcb.
+ */
+
+struct sctp_route {
+ struct sctp_rtentry *ro_rt;
+ struct sockaddr ro_dst;
+};
+
+/*
+ * These numbers are used by reliable protocols for determining
+ * retransmission behavior and are included in the routing structure.
+ */
+struct sctp_rt_metrics_lite {
+ uint32_t rmx_mtu; /* MTU for this path */
+#if 0
+ u_long rmx_expire; /* lifetime for route, e.g. redirect */
+ u_long rmx_pksent; /* packets sent using this route */
+#endif
+};
+
+/*
+ * We distinguish between routes to hosts and routes to networks,
+ * preferring the former if available. For each route we infer
+ * the interface to use from the gateway address supplied when
+ * the route was entered. Routes that forward packets through
+ * gateways are marked so that the output routines know to address the
+ * gateway rather than the ultimate destination.
+ */
+struct sctp_rtentry {
+#if 0
+ struct radix_node rt_nodes[2]; /* tree glue, and other values */
+ /*
+ * XXX struct rtentry must begin with a struct radix_node (or two!)
+ * because the code does some casts of a 'struct radix_node *'
+ * to a 'struct rtentry *'
+ */
+#define rt_key(r) (*((struct sockaddr **)(&(r)->rt_nodes->rn_key)))
+#define rt_mask(r) (*((struct sockaddr **)(&(r)->rt_nodes->rn_mask)))
+ struct sockaddr *rt_gateway; /* value */
+ u_long rt_flags; /* up/down?, host/net */
+#endif
+ struct ifnet *rt_ifp; /* the answer: interface to use */
+ struct ifaddr *rt_ifa; /* the answer: interface address to use */
+ struct sctp_rt_metrics_lite rt_rmx; /* metrics used by rx'ing protocols */
+ long rt_refcnt; /* # held references */
+#if 0
+ struct sockaddr *rt_genmask; /* for generation of cloned routes */
+ caddr_t rt_llinfo; /* pointer to link level info cache */
+ struct rtentry *rt_gwroute; /* implied entry for gatewayed routes */
+ struct rtentry *rt_parent; /* cloning parent of this route */
+#endif
+ struct mtx rt_mtx; /* mutex for routing entry */
+};
+
+#define RT_LOCK_INIT(_rt) mtx_init(&(_rt)->rt_mtx, "rtentry", NULL, MTX_DEF | MTX_DUPOK)
+#define RT_LOCK(_rt) mtx_lock(&(_rt)->rt_mtx)
+#define RT_UNLOCK(_rt) mtx_unlock(&(_rt)->rt_mtx)
+#define RT_LOCK_DESTROY(_rt) mtx_destroy(&(_rt)->rt_mtx)
+#define RT_LOCK_ASSERT(_rt) mtx_assert(&(_rt)->rt_mtx, MA_OWNED)
+
+#define RT_ADDREF(_rt) do { \
+ RT_LOCK_ASSERT(_rt); \
+ KASSERT((_rt)->rt_refcnt >= 0, \
+ ("negative refcnt %ld", (_rt)->rt_refcnt)); \
+ (_rt)->rt_refcnt++; \
+} while (0)
+#define RT_REMREF(_rt) do { \
+ RT_LOCK_ASSERT(_rt); \
+ KASSERT((_rt)->rt_refcnt > 0, \
+ ("bogus refcnt %ld", (_rt)->rt_refcnt)); \
+ (_rt)->rt_refcnt--; \
+} while (0)
+#define RTFREE_LOCKED(_rt) do { \
+ if ((_rt)->rt_refcnt <= 1) { \
+ rtfree(_rt); \
+ } else { \
+ RT_REMREF(_rt); \
+ RT_UNLOCK(_rt); \
+ } \
+ /* guard against invalid refs */ \
+ _rt = NULL; \
+ } while (0)
+#define RTFREE(_rt) do { \
+ RT_LOCK(_rt); \
+ RTFREE_LOCKED(_rt); \
+} while (0)
+#endif
diff --git a/netwerk/sctp/src/user_socket.c b/netwerk/sctp/src/user_socket.c
new file mode 100644
index 0000000000..6658390420
--- /dev/null
+++ b/netwerk/sctp/src/user_socket.c
@@ -0,0 +1,3571 @@
+/*-
+ * Copyright (c) 1982, 1986, 1988, 1990, 1993
+ * The Regents of the University of California.
+ * Copyright (c) 2004 The FreeBSD Foundation
+ * Copyright (c) 2004-2008 Robert N. M. Watson
+ * Copyright (c) 2009-2010 Brad Penoff
+ * Copyright (c) 2009-2010 Humaira Kamal
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp_peeloff.h>
+#include <netinet/sctp_callout.h>
+#include <netinet/sctp_crc32.h>
+#ifdef INET6
+#include <netinet6/sctp6_var.h>
+#endif
+#if defined(__FreeBSD__)
+#include <sys/param.h>
+#endif
+#if defined(__linux__)
+#define __FAVOR_BSD /* (on Ubuntu at least) enables UDP header field names like BSD in RFC 768 */
+#endif
+#if !defined(_WIN32)
+#if defined INET || defined INET6
+#include <netinet/udp.h>
+#endif
+#include <arpa/inet.h>
+#else
+#include <user_socketvar.h>
+#endif
+userland_mutex_t accept_mtx;
+userland_cond_t accept_cond;
+#ifdef _WIN32
+#include <time.h>
+#include <sys/timeb.h>
+#endif
+
+MALLOC_DEFINE(M_PCB, "sctp_pcb", "sctp pcb");
+MALLOC_DEFINE(M_SONAME, "sctp_soname", "sctp soname");
+#define MAXLEN_MBUF_CHAIN 32
+
+/* Prototypes */
+extern int sctp_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio,
+ struct mbuf *top, struct mbuf *control, int flags,
+ /* proc is a dummy in __Userspace__ and will not be passed to sctp_lower_sosend */
+ struct proc *p);
+
+extern int sctp_attach(struct socket *so, int proto, uint32_t vrf_id);
+extern int sctpconn_attach(struct socket *so, int proto, uint32_t vrf_id);
+
+static void init_sync(void) {
+#if defined(_WIN32)
+#if defined(INET) || defined(INET6)
+ WSADATA wsaData;
+
+ if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
+ SCTP_PRINTF("WSAStartup failed\n");
+ exit (-1);
+ }
+#endif
+ InitializeConditionVariable(&accept_cond);
+ InitializeCriticalSection(&accept_mtx);
+#else
+ pthread_mutexattr_t mutex_attr;
+
+ pthread_mutexattr_init(&mutex_attr);
+#ifdef INVARIANTS
+ pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_ERRORCHECK);
+#endif
+ pthread_mutex_init(&accept_mtx, &mutex_attr);
+ pthread_mutexattr_destroy(&mutex_attr);
+ pthread_cond_init(&accept_cond, NULL);
+#endif
+}
+
+void
+usrsctp_init(uint16_t port,
+ int (*conn_output)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df),
+ void (*debug_printf)(const char *format, ...))
+{
+ init_sync();
+ sctp_init(port, conn_output, debug_printf, 1);
+}
+
+
+void
+usrsctp_init_nothreads(uint16_t port,
+ int (*conn_output)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df),
+ void (*debug_printf)(const char *format, ...))
+{
+ init_sync();
+ sctp_init(port, conn_output, debug_printf, 0);
+}
+
+
+/* Taken from usr/src/sys/kern/uipc_sockbuf.c and modified for __Userspace__*/
+/*
+ * Socantsendmore indicates that no more data will be sent on the socket; it
+ * would normally be applied to a socket when the user informs the system
+ * that no more data is to be sent, by the protocol code (in case
+ * PRU_SHUTDOWN). Socantrcvmore indicates that no more data will be
+ * received, and will normally be applied to the socket by a protocol when it
+ * detects that the peer will send no more data. Data queued for reading in
+ * the socket may yet be read.
+ */
+
+void socantrcvmore_locked(struct socket *so)
+{
+ SOCKBUF_LOCK_ASSERT(&so->so_rcv);
+ so->so_rcv.sb_state |= SBS_CANTRCVMORE;
+ sorwakeup_locked(so);
+}
+
+void socantrcvmore(struct socket *so)
+{
+ SOCKBUF_LOCK(&so->so_rcv);
+ socantrcvmore_locked(so);
+}
+
+void
+socantsendmore_locked(struct socket *so)
+{
+ SOCKBUF_LOCK_ASSERT(&so->so_snd);
+ so->so_snd.sb_state |= SBS_CANTSENDMORE;
+ sowwakeup_locked(so);
+}
+
+void
+socantsendmore(struct socket *so)
+{
+ SOCKBUF_LOCK(&so->so_snd);
+ socantsendmore_locked(so);
+}
+
+
+
+/* Taken from usr/src/sys/kern/uipc_sockbuf.c and called within sctp_lower_sosend.
+ */
+int
+sbwait(struct sockbuf *sb)
+{
+ SOCKBUF_LOCK_ASSERT(sb);
+
+ sb->sb_flags |= SB_WAIT;
+#if defined(_WIN32)
+ if (SleepConditionVariableCS(&(sb->sb_cond), &(sb->sb_mtx), INFINITE))
+ return (0);
+ else
+ return (-1);
+#else
+ return (pthread_cond_wait(&(sb->sb_cond), &(sb->sb_mtx)));
+#endif
+}
+
+
+
+
+/* Taken from /src/sys/kern/uipc_socket.c
+ * and modified for __Userspace__
+ */
+static struct socket *
+soalloc(void)
+{
+ struct socket *so;
+
+ /*
+ * soalloc() sets of socket layer state for a socket,
+ * called only by socreate() and sonewconn().
+ *
+ * sodealloc() tears down socket layer state for a socket,
+ * called only by sofree() and sonewconn().
+ * __Userspace__ TODO : Make sure so is properly deallocated
+ * when tearing down the connection.
+ */
+
+ so = (struct socket *)malloc(sizeof(struct socket));
+
+ if (so == NULL) {
+ return (NULL);
+ }
+ memset(so, 0, sizeof(struct socket));
+
+ /* __Userspace__ Initializing the socket locks here */
+ SOCKBUF_LOCK_INIT(&so->so_snd, "so_snd");
+ SOCKBUF_LOCK_INIT(&so->so_rcv, "so_rcv");
+ SOCKBUF_COND_INIT(&so->so_snd);
+ SOCKBUF_COND_INIT(&so->so_rcv);
+ SOCK_COND_INIT(so); /* timeo_cond */
+
+ /* __Userspace__ Any ref counting required here? Will we have any use for aiojobq?
+ What about gencnt and numopensockets?*/
+ TAILQ_INIT(&so->so_aiojobq);
+ return (so);
+}
+
+static void
+sodealloc(struct socket *so)
+{
+
+ KASSERT(so->so_count == 0, ("sodealloc(): so_count %d", so->so_count));
+ KASSERT(so->so_pcb == NULL, ("sodealloc(): so_pcb != NULL"));
+
+ SOCKBUF_COND_DESTROY(&so->so_snd);
+ SOCKBUF_COND_DESTROY(&so->so_rcv);
+
+ SOCK_COND_DESTROY(so);
+
+ SOCKBUF_LOCK_DESTROY(&so->so_snd);
+ SOCKBUF_LOCK_DESTROY(&so->so_rcv);
+
+ free(so);
+}
+
+/* Taken from /src/sys/kern/uipc_socket.c
+ * and modified for __Userspace__
+ */
+void
+sofree(struct socket *so)
+{
+ struct socket *head;
+
+ ACCEPT_LOCK_ASSERT();
+ SOCK_LOCK_ASSERT(so);
+ /* SS_NOFDREF unset in accept call. this condition seems irrelevant
+ * for __Userspace__...
+ */
+ if (so->so_count != 0 ||
+ (so->so_state & SS_PROTOREF) || (so->so_qstate & SQ_COMP)) {
+ SOCK_UNLOCK(so);
+ ACCEPT_UNLOCK();
+ return;
+ }
+ head = so->so_head;
+ if (head != NULL) {
+ KASSERT((so->so_qstate & SQ_COMP) != 0 ||
+ (so->so_qstate & SQ_INCOMP) != 0,
+ ("sofree: so_head != NULL, but neither SQ_COMP nor "
+ "SQ_INCOMP"));
+ KASSERT((so->so_qstate & SQ_COMP) == 0 ||
+ (so->so_qstate & SQ_INCOMP) == 0,
+ ("sofree: so->so_qstate is SQ_COMP and also SQ_INCOMP"));
+ TAILQ_REMOVE(&head->so_incomp, so, so_list);
+ head->so_incqlen--;
+ so->so_qstate &= ~SQ_INCOMP;
+ so->so_head = NULL;
+ }
+ KASSERT((so->so_qstate & SQ_COMP) == 0 &&
+ (so->so_qstate & SQ_INCOMP) == 0,
+ ("sofree: so_head == NULL, but still SQ_COMP(%d) or SQ_INCOMP(%d)",
+ so->so_qstate & SQ_COMP, so->so_qstate & SQ_INCOMP));
+ if (so->so_options & SCTP_SO_ACCEPTCONN) {
+ KASSERT((TAILQ_EMPTY(&so->so_comp)), ("sofree: so_comp populated"));
+ KASSERT((TAILQ_EMPTY(&so->so_incomp)), ("sofree: so_comp populated"));
+ }
+ SOCK_UNLOCK(so);
+ ACCEPT_UNLOCK();
+ sctp_close(so); /* was... sctp_detach(so); */
+ /*
+ * From this point on, we assume that no other references to this
+ * socket exist anywhere else in the stack. Therefore, no locks need
+ * to be acquired or held.
+ *
+ * We used to do a lot of socket buffer and socket locking here, as
+ * well as invoke sorflush() and perform wakeups. The direct call to
+ * dom_dispose() and sbrelease_internal() are an inlining of what was
+ * necessary from sorflush().
+ *
+ * Notice that the socket buffer and kqueue state are torn down
+ * before calling pru_detach. This means that protocols should not
+ * assume they can perform socket wakeups, etc, in their detach code.
+ */
+ sodealloc(so);
+}
+
+
+
+/* Taken from /src/sys/kern/uipc_socket.c */
+void
+soabort(struct socket *so)
+{
+ sctp_abort(so);
+ ACCEPT_LOCK();
+ SOCK_LOCK(so);
+ sofree(so);
+}
+
+
+/* Taken from usr/src/sys/kern/uipc_socket.c and called within sctp_connect (sctp_usrreq.c).
+ * We use sctp_connect for send_one_init_real in ms1.
+ */
+void
+soisconnecting(struct socket *so)
+{
+
+ SOCK_LOCK(so);
+ so->so_state &= ~(SS_ISCONNECTED|SS_ISDISCONNECTING);
+ so->so_state |= SS_ISCONNECTING;
+ SOCK_UNLOCK(so);
+}
+
+/* Taken from usr/src/sys/kern/uipc_socket.c and called within sctp_disconnect (sctp_usrreq.c).
+ * TODO Do we use sctp_disconnect?
+ */
+void
+soisdisconnecting(struct socket *so)
+{
+
+ /*
+ * Note: This code assumes that SOCK_LOCK(so) and
+ * SOCKBUF_LOCK(&so->so_rcv) are the same.
+ */
+ SOCKBUF_LOCK(&so->so_rcv);
+ so->so_state &= ~SS_ISCONNECTING;
+ so->so_state |= SS_ISDISCONNECTING;
+ so->so_rcv.sb_state |= SBS_CANTRCVMORE;
+ sorwakeup_locked(so);
+ SOCKBUF_LOCK(&so->so_snd);
+ so->so_snd.sb_state |= SBS_CANTSENDMORE;
+ sowwakeup_locked(so);
+ wakeup("dummy",so);
+ /* requires 2 args but this was in orig */
+ /* wakeup(&so->so_timeo); */
+}
+
+
+/* Taken from sys/kern/kern_synch.c and
+ modified for __Userspace__
+*/
+
+/*
+ * Make all threads sleeping on the specified identifier runnable.
+ * Associating wakeup with so_timeo identifier and timeo_cond
+ * condition variable. TODO. If we use iterator thread then we need to
+ * modify wakeup so it can distinguish between iterator identifier and
+ * timeo identifier.
+ */
+void
+wakeup(void *ident, struct socket *so)
+{
+ SOCK_LOCK(so);
+#if defined(_WIN32)
+ WakeAllConditionVariable(&(so)->timeo_cond);
+#else
+ pthread_cond_broadcast(&(so)->timeo_cond);
+#endif
+ SOCK_UNLOCK(so);
+}
+
+
+/*
+ * Make a thread sleeping on the specified identifier runnable.
+ * May wake more than one thread if a target thread is currently
+ * swapped out.
+ */
+void
+wakeup_one(void *ident)
+{
+ /* __Userspace__ Check: We are using accept_cond for wakeup_one.
+ It seems that wakeup_one is only called within
+ soisconnected() and sonewconn() with ident &head->so_timeo
+ head is so->so_head, which is back pointer to listen socket
+ This seems to indicate that the use of accept_cond is correct
+ since socket where accepts occur is so_head in all
+ subsidiary sockets.
+ */
+ ACCEPT_LOCK();
+#if defined(_WIN32)
+ WakeAllConditionVariable(&accept_cond);
+#else
+ pthread_cond_broadcast(&accept_cond);
+#endif
+ ACCEPT_UNLOCK();
+}
+
+
+/* Called within sctp_process_cookie_[existing/new] */
+void
+soisconnected(struct socket *so)
+{
+ struct socket *head;
+
+ ACCEPT_LOCK();
+ SOCK_LOCK(so);
+ so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING);
+ so->so_state |= SS_ISCONNECTED;
+ head = so->so_head;
+ if (head != NULL && (so->so_qstate & SQ_INCOMP)) {
+ SOCK_UNLOCK(so);
+ TAILQ_REMOVE(&head->so_incomp, so, so_list);
+ head->so_incqlen--;
+ so->so_qstate &= ~SQ_INCOMP;
+ TAILQ_INSERT_TAIL(&head->so_comp, so, so_list);
+ head->so_qlen++;
+ so->so_qstate |= SQ_COMP;
+ ACCEPT_UNLOCK();
+ sorwakeup(head);
+ wakeup_one(&head->so_timeo);
+ return;
+ }
+ SOCK_UNLOCK(so);
+ ACCEPT_UNLOCK();
+ wakeup(&so->so_timeo, so);
+ sorwakeup(so);
+ sowwakeup(so);
+
+}
+
+/* called within sctp_handle_cookie_echo */
+
+struct socket *
+sonewconn(struct socket *head, int connstatus)
+{
+ struct socket *so;
+ int over;
+
+ ACCEPT_LOCK();
+ over = (head->so_qlen > 3 * head->so_qlimit / 2);
+ ACCEPT_UNLOCK();
+#ifdef REGRESSION
+ if (regression_sonewconn_earlytest && over)
+#else
+ if (over)
+#endif
+ return (NULL);
+ so = soalloc();
+ if (so == NULL)
+ return (NULL);
+ so->so_head = head;
+ so->so_type = head->so_type;
+ so->so_options = head->so_options &~ SCTP_SO_ACCEPTCONN;
+ so->so_linger = head->so_linger;
+ so->so_state = head->so_state | SS_NOFDREF;
+ so->so_dom = head->so_dom;
+#ifdef MAC
+ SOCK_LOCK(head);
+ mac_create_socket_from_socket(head, so);
+ SOCK_UNLOCK(head);
+#endif
+ if (soreserve(so, head->so_snd.sb_hiwat, head->so_rcv.sb_hiwat)) {
+ sodealloc(so);
+ return (NULL);
+ }
+ switch (head->so_dom) {
+#ifdef INET
+ case AF_INET:
+ if (sctp_attach(so, IPPROTO_SCTP, SCTP_DEFAULT_VRFID)) {
+ sodealloc(so);
+ return (NULL);
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (sctp6_attach(so, IPPROTO_SCTP, SCTP_DEFAULT_VRFID)) {
+ sodealloc(so);
+ return (NULL);
+ }
+ break;
+#endif
+ case AF_CONN:
+ if (sctpconn_attach(so, IPPROTO_SCTP, SCTP_DEFAULT_VRFID)) {
+ sodealloc(so);
+ return (NULL);
+ }
+ break;
+ default:
+ sodealloc(so);
+ return (NULL);
+ break;
+ }
+ so->so_rcv.sb_lowat = head->so_rcv.sb_lowat;
+ so->so_snd.sb_lowat = head->so_snd.sb_lowat;
+ so->so_rcv.sb_timeo = head->so_rcv.sb_timeo;
+ so->so_snd.sb_timeo = head->so_snd.sb_timeo;
+ so->so_rcv.sb_flags |= head->so_rcv.sb_flags & SB_AUTOSIZE;
+ so->so_snd.sb_flags |= head->so_snd.sb_flags & SB_AUTOSIZE;
+ so->so_state |= connstatus;
+ ACCEPT_LOCK();
+ if (connstatus) {
+ TAILQ_INSERT_TAIL(&head->so_comp, so, so_list);
+ so->so_qstate |= SQ_COMP;
+ head->so_qlen++;
+ } else {
+ /*
+ * Keep removing sockets from the head until there's room for
+ * us to insert on the tail. In pre-locking revisions, this
+ * was a simple if (), but as we could be racing with other
+ * threads and soabort() requires dropping locks, we must
+ * loop waiting for the condition to be true.
+ */
+ while (head->so_incqlen > head->so_qlimit) {
+ struct socket *sp;
+ sp = TAILQ_FIRST(&head->so_incomp);
+ TAILQ_REMOVE(&head->so_incomp, sp, so_list);
+ head->so_incqlen--;
+ sp->so_qstate &= ~SQ_INCOMP;
+ sp->so_head = NULL;
+ ACCEPT_UNLOCK();
+ soabort(sp);
+ ACCEPT_LOCK();
+ }
+ TAILQ_INSERT_TAIL(&head->so_incomp, so, so_list);
+ so->so_qstate |= SQ_INCOMP;
+ head->so_incqlen++;
+ }
+ ACCEPT_UNLOCK();
+ if (connstatus) {
+ sorwakeup(head);
+ wakeup_one(&head->so_timeo);
+ }
+ return (so);
+
+}
+
+ /*
+ Source: /src/sys/gnu/fs/xfs/FreeBSD/xfs_ioctl.c
+ */
+static __inline__ int
+copy_to_user(void *dst, void *src, size_t len) {
+ memcpy(dst, src, len);
+ return 0;
+}
+
+static __inline__ int
+copy_from_user(void *dst, void *src, size_t len) {
+ memcpy(dst, src, len);
+ return 0;
+}
+
+/*
+ References:
+ src/sys/dev/lmc/if_lmc.h:
+ src/sys/powerpc/powerpc/copyinout.c
+ src/sys/sys/systm.h
+*/
+# define copyin(u, k, len) copy_from_user(k, u, len)
+
+/* References:
+ src/sys/powerpc/powerpc/copyinout.c
+ src/sys/sys/systm.h
+*/
+# define copyout(k, u, len) copy_to_user(u, k, len)
+
+
+/* copyiniov definition copied/modified from src/sys/kern/kern_subr.c */
+int
+copyiniov(struct iovec *iovp, u_int iovcnt, struct iovec **iov, int error)
+{
+ u_int iovlen;
+
+ *iov = NULL;
+ if (iovcnt > UIO_MAXIOV)
+ return (error);
+ iovlen = iovcnt * sizeof (struct iovec);
+ *iov = malloc(iovlen); /*, M_IOV, M_WAITOK); */
+ error = copyin(iovp, *iov, iovlen);
+ if (error) {
+ free(*iov); /*, M_IOV); */
+ *iov = NULL;
+ }
+ return (error);
+}
+
+/* (__Userspace__) version of uiomove */
+int
+uiomove(void *cp, int n, struct uio *uio)
+{
+ struct iovec *iov;
+ size_t cnt;
+ int error = 0;
+
+ if ((uio->uio_rw != UIO_READ) &&
+ (uio->uio_rw != UIO_WRITE)) {
+ return (EINVAL);
+ }
+
+ while (n > 0 && uio->uio_resid) {
+ iov = uio->uio_iov;
+ cnt = iov->iov_len;
+ if (cnt == 0) {
+ uio->uio_iov++;
+ uio->uio_iovcnt--;
+ continue;
+ }
+ if (cnt > (size_t)n)
+ cnt = n;
+
+ switch (uio->uio_segflg) {
+
+ case UIO_USERSPACE:
+ if (uio->uio_rw == UIO_READ)
+ error = copyout(cp, iov->iov_base, cnt);
+ else
+ error = copyin(iov->iov_base, cp, cnt);
+ if (error)
+ goto out;
+ break;
+
+ case UIO_SYSSPACE:
+ if (uio->uio_rw == UIO_READ)
+ memcpy(iov->iov_base, cp, cnt);
+ else
+ memcpy(cp, iov->iov_base, cnt);
+ break;
+ }
+ iov->iov_base = (char *)iov->iov_base + cnt;
+ iov->iov_len -= cnt;
+ uio->uio_resid -= cnt;
+ uio->uio_offset += (off_t)cnt;
+ cp = (char *)cp + cnt;
+ n -= (int)cnt;
+ }
+out:
+ return (error);
+}
+
+
+/* Source: src/sys/kern/uipc_syscalls.c */
+int
+getsockaddr(struct sockaddr **namp, caddr_t uaddr, size_t len)
+{
+ struct sockaddr *sa;
+ int error;
+
+ if (len > SOCK_MAXADDRLEN)
+ return (ENAMETOOLONG);
+ if (len < offsetof(struct sockaddr, sa_data))
+ return (EINVAL);
+ MALLOC(sa, struct sockaddr *, len, M_SONAME, M_WAITOK);
+ error = copyin(uaddr, sa, len);
+ if (error) {
+ FREE(sa, M_SONAME);
+ } else {
+#ifdef HAVE_SA_LEN
+ sa->sa_len = len;
+#endif
+ *namp = sa;
+ }
+ return (error);
+}
+
+int
+usrsctp_getsockopt(struct socket *so, int level, int option_name,
+ void *option_value, socklen_t *option_len);
+
+sctp_assoc_t
+usrsctp_getassocid(struct socket *sock, struct sockaddr *sa)
+{
+ struct sctp_paddrinfo sp;
+ socklen_t siz;
+#ifndef HAVE_SA_LEN
+ size_t sa_len;
+#endif
+
+ /* First get the assoc id */
+ siz = sizeof(sp);
+ memset(&sp, 0, sizeof(sp));
+#ifdef HAVE_SA_LEN
+ memcpy((caddr_t)&sp.spinfo_address, sa, sa->sa_len);
+#else
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sa_len = sizeof(struct sockaddr_in);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa_len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ case AF_CONN:
+ sa_len = sizeof(struct sockaddr_conn);
+ break;
+ default:
+ sa_len = 0;
+ break;
+ }
+ memcpy((caddr_t)&sp.spinfo_address, sa, sa_len);
+#endif
+ if (usrsctp_getsockopt(sock, IPPROTO_SCTP, SCTP_GET_PEER_ADDR_INFO, &sp, &siz) != 0) {
+ /* We depend on the fact that 0 can never be returned */
+ return ((sctp_assoc_t) 0);
+ }
+ return (sp.spinfo_assoc_id);
+}
+
+
+/* Taken from /src/lib/libc/net/sctp_sys_calls.c
+ * and modified for __Userspace__
+ * calling sctp_generic_sendmsg from this function
+ */
+ssize_t
+userspace_sctp_sendmsg(struct socket *so,
+ const void *data,
+ size_t len,
+ struct sockaddr *to,
+ socklen_t tolen,
+ uint32_t ppid,
+ uint32_t flags,
+ uint16_t stream_no,
+ uint32_t timetolive,
+ uint32_t context)
+{
+ struct sctp_sndrcvinfo sndrcvinfo, *sinfo = &sndrcvinfo;
+ struct uio auio;
+ struct iovec iov[1];
+
+ memset(sinfo, 0, sizeof(struct sctp_sndrcvinfo));
+ sinfo->sinfo_ppid = ppid;
+ sinfo->sinfo_flags = flags;
+ sinfo->sinfo_stream = stream_no;
+ sinfo->sinfo_timetolive = timetolive;
+ sinfo->sinfo_context = context;
+ sinfo->sinfo_assoc_id = 0;
+
+
+ /* Perform error checks on destination (to) */
+ if (tolen > SOCK_MAXADDRLEN) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ if ((tolen > 0) &&
+ ((to == NULL) || (tolen < (socklen_t)sizeof(struct sockaddr)))) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (data == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+ /* Adding the following as part of defensive programming, in case the application
+ does not do it when preparing the destination address.*/
+#ifdef HAVE_SA_LEN
+ if (to != NULL) {
+ to->sa_len = tolen;
+ }
+#endif
+
+ iov[0].iov_base = (caddr_t)data;
+ iov[0].iov_len = len;
+
+ auio.uio_iov = iov;
+ auio.uio_iovcnt = 1;
+ auio.uio_segflg = UIO_USERSPACE;
+ auio.uio_rw = UIO_WRITE;
+ auio.uio_offset = 0; /* XXX */
+ auio.uio_resid = len;
+ errno = sctp_lower_sosend(so, to, &auio, NULL, NULL, 0, sinfo);
+ if (errno == 0) {
+ return (len - auio.uio_resid);
+ } else {
+ return (-1);
+ }
+}
+
+
+ssize_t
+usrsctp_sendv(struct socket *so,
+ const void *data,
+ size_t len,
+ struct sockaddr *to,
+ int addrcnt,
+ void *info,
+ socklen_t infolen,
+ unsigned int infotype,
+ int flags)
+{
+ struct sctp_sndrcvinfo sinfo;
+ struct uio auio;
+ struct iovec iov[1];
+ int use_sinfo;
+ sctp_assoc_t *assoc_id;
+
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ if (data == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+ memset(&sinfo, 0, sizeof(struct sctp_sndrcvinfo));
+ assoc_id = NULL;
+ use_sinfo = 0;
+ switch (infotype) {
+ case SCTP_SENDV_NOINFO:
+ if ((infolen != 0) || (info != NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ break;
+ case SCTP_SENDV_SNDINFO:
+ if ((info == NULL) || (infolen != sizeof(struct sctp_sndinfo))) {
+ errno = EINVAL;
+ return (-1);
+ }
+ sinfo.sinfo_stream = ((struct sctp_sndinfo *)info)->snd_sid;
+ sinfo.sinfo_flags = ((struct sctp_sndinfo *)info)->snd_flags;
+ sinfo.sinfo_ppid = ((struct sctp_sndinfo *)info)->snd_ppid;
+ sinfo.sinfo_context = ((struct sctp_sndinfo *)info)->snd_context;
+ sinfo.sinfo_assoc_id = ((struct sctp_sndinfo *)info)->snd_assoc_id;
+ assoc_id = &(((struct sctp_sndinfo *)info)->snd_assoc_id);
+ use_sinfo = 1;
+ break;
+ case SCTP_SENDV_PRINFO:
+ if ((info == NULL) || (infolen != sizeof(struct sctp_prinfo))) {
+ errno = EINVAL;
+ return (-1);
+ }
+ sinfo.sinfo_stream = 0;
+ sinfo.sinfo_flags = PR_SCTP_POLICY(((struct sctp_prinfo *)info)->pr_policy);
+ sinfo.sinfo_timetolive = ((struct sctp_prinfo *)info)->pr_value;
+ use_sinfo = 1;
+ break;
+ case SCTP_SENDV_AUTHINFO:
+ errno = EINVAL;
+ return (-1);
+ case SCTP_SENDV_SPA:
+ if ((info == NULL) || (infolen != sizeof(struct sctp_sendv_spa))) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (((struct sctp_sendv_spa *)info)->sendv_flags & SCTP_SEND_SNDINFO_VALID) {
+ sinfo.sinfo_stream = ((struct sctp_sendv_spa *)info)->sendv_sndinfo.snd_sid;
+ sinfo.sinfo_flags = ((struct sctp_sendv_spa *)info)->sendv_sndinfo.snd_flags;
+ sinfo.sinfo_ppid = ((struct sctp_sendv_spa *)info)->sendv_sndinfo.snd_ppid;
+ sinfo.sinfo_context = ((struct sctp_sendv_spa *)info)->sendv_sndinfo.snd_context;
+ sinfo.sinfo_assoc_id = ((struct sctp_sendv_spa *)info)->sendv_sndinfo.snd_assoc_id;
+ assoc_id = &(((struct sctp_sendv_spa *)info)->sendv_sndinfo.snd_assoc_id);
+ } else {
+ sinfo.sinfo_flags = 0;
+ sinfo.sinfo_stream = 0;
+ }
+ if (((struct sctp_sendv_spa *)info)->sendv_flags & SCTP_SEND_PRINFO_VALID) {
+ sinfo.sinfo_flags |= PR_SCTP_POLICY(((struct sctp_sendv_spa *)info)->sendv_prinfo.pr_policy);
+ sinfo.sinfo_timetolive = ((struct sctp_sendv_spa *)info)->sendv_prinfo.pr_value;
+ }
+ if (((struct sctp_sendv_spa *)info)->sendv_flags & SCTP_SEND_AUTHINFO_VALID) {
+ errno = EINVAL;
+ return (-1);
+ }
+ use_sinfo = 1;
+ break;
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* Perform error checks on destination (to) */
+ if (addrcnt > 1) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ iov[0].iov_base = (caddr_t)data;
+ iov[0].iov_len = len;
+
+ auio.uio_iov = iov;
+ auio.uio_iovcnt = 1;
+ auio.uio_segflg = UIO_USERSPACE;
+ auio.uio_rw = UIO_WRITE;
+ auio.uio_offset = 0; /* XXX */
+ auio.uio_resid = len;
+ errno = sctp_lower_sosend(so, to, &auio, NULL, NULL, flags, use_sinfo ? &sinfo : NULL);
+ if (errno == 0) {
+ if ((to != NULL) && (assoc_id != NULL)) {
+ *assoc_id = usrsctp_getassocid(so, to);
+ }
+ return (len - auio.uio_resid);
+ } else {
+ return (-1);
+ }
+}
+
+
+ssize_t
+userspace_sctp_sendmbuf(struct socket *so,
+ struct mbuf* mbufdata,
+ size_t len,
+ struct sockaddr *to,
+ socklen_t tolen,
+ uint32_t ppid,
+ uint32_t flags,
+ uint16_t stream_no,
+ uint32_t timetolive,
+ uint32_t context)
+{
+
+ struct sctp_sndrcvinfo sndrcvinfo, *sinfo = &sndrcvinfo;
+ /* struct uio auio;
+ struct iovec iov[1]; */
+ int error = 0;
+ int uflags = 0;
+ ssize_t retval;
+
+ sinfo->sinfo_ppid = ppid;
+ sinfo->sinfo_flags = flags;
+ sinfo->sinfo_stream = stream_no;
+ sinfo->sinfo_timetolive = timetolive;
+ sinfo->sinfo_context = context;
+ sinfo->sinfo_assoc_id = 0;
+
+ /* Perform error checks on destination (to) */
+ if (tolen > SOCK_MAXADDRLEN){
+ error = (ENAMETOOLONG);
+ goto sendmsg_return;
+ }
+ if (tolen < (socklen_t)offsetof(struct sockaddr, sa_data)){
+ error = (EINVAL);
+ goto sendmsg_return;
+ }
+ /* Adding the following as part of defensive programming, in case the application
+ does not do it when preparing the destination address.*/
+#ifdef HAVE_SA_LEN
+ to->sa_len = tolen;
+#endif
+
+ error = sctp_lower_sosend(so, to, NULL/*uio*/,
+ (struct mbuf *)mbufdata, (struct mbuf *)NULL,
+ uflags, sinfo);
+sendmsg_return:
+ /* TODO: Needs a condition for non-blocking when error is EWOULDBLOCK */
+ if (0 == error)
+ retval = len;
+ else if (error == EWOULDBLOCK) {
+ errno = EWOULDBLOCK;
+ retval = -1;
+ } else {
+ SCTP_PRINTF("%s: error = %d\n", __func__, error);
+ errno = error;
+ retval = -1;
+ }
+ return (retval);
+}
+
+
+/* taken from usr.lib/sctp_sys_calls.c and needed here */
+#define SCTP_SMALL_IOVEC_SIZE 2
+
+/* Taken from /src/lib/libc/net/sctp_sys_calls.c
+ * and modified for __Userspace__
+ * calling sctp_generic_recvmsg from this function
+ */
+ssize_t
+userspace_sctp_recvmsg(struct socket *so,
+ void *dbuf,
+ size_t len,
+ struct sockaddr *from,
+ socklen_t *fromlenp,
+ struct sctp_sndrcvinfo *sinfo,
+ int *msg_flags)
+{
+ struct uio auio;
+ struct iovec iov[SCTP_SMALL_IOVEC_SIZE];
+ struct iovec *tiov;
+ int iovlen = 1;
+ int error = 0;
+ ssize_t ulen;
+ int i;
+ socklen_t fromlen;
+
+ iov[0].iov_base = dbuf;
+ iov[0].iov_len = len;
+
+ auio.uio_iov = iov;
+ auio.uio_iovcnt = iovlen;
+ auio.uio_segflg = UIO_USERSPACE;
+ auio.uio_rw = UIO_READ;
+ auio.uio_offset = 0; /* XXX */
+ auio.uio_resid = 0;
+ tiov = iov;
+ for (i = 0; i <iovlen; i++, tiov++) {
+ if ((auio.uio_resid += tiov->iov_len) < 0) {
+ error = EINVAL;
+ SCTP_PRINTF("%s: error = %d\n", __func__, error);
+ return (-1);
+ }
+ }
+ ulen = auio.uio_resid;
+ if (fromlenp != NULL) {
+ fromlen = *fromlenp;
+ } else {
+ fromlen = 0;
+ }
+ error = sctp_sorecvmsg(so, &auio, (struct mbuf **)NULL,
+ from, fromlen, msg_flags,
+ (struct sctp_sndrcvinfo *)sinfo, 1);
+
+ if (error) {
+ if ((auio.uio_resid != ulen) &&
+ (error == EINTR ||
+#if !defined(__NetBSD__)
+ error == ERESTART ||
+#endif
+ error == EWOULDBLOCK)) {
+ error = 0;
+ }
+ }
+ if ((fromlenp != NULL) && (fromlen > 0) && (from != NULL)) {
+ switch (from->sa_family) {
+#if defined(INET)
+ case AF_INET:
+ *fromlenp = sizeof(struct sockaddr_in);
+ break;
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ *fromlenp = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ case AF_CONN:
+ *fromlenp = sizeof(struct sockaddr_conn);
+ break;
+ default:
+ *fromlenp = 0;
+ break;
+ }
+ if (*fromlenp > fromlen) {
+ *fromlenp = fromlen;
+ }
+ }
+ if (error == 0) {
+ /* ready return value */
+ return (ulen - auio.uio_resid);
+ } else {
+ SCTP_PRINTF("%s: error = %d\n", __func__, error);
+ return (-1);
+ }
+}
+
+ssize_t
+usrsctp_recvv(struct socket *so,
+ void *dbuf,
+ size_t len,
+ struct sockaddr *from,
+ socklen_t *fromlenp,
+ void *info,
+ socklen_t *infolen,
+ unsigned int *infotype,
+ int *msg_flags)
+{
+ struct uio auio;
+ struct iovec iov[SCTP_SMALL_IOVEC_SIZE];
+ struct iovec *tiov;
+ int iovlen = 1;
+ ssize_t ulen;
+ int i;
+ socklen_t fromlen;
+ struct sctp_rcvinfo *rcv;
+ struct sctp_recvv_rn *rn;
+ struct sctp_extrcvinfo seinfo;
+
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ iov[0].iov_base = dbuf;
+ iov[0].iov_len = len;
+
+ auio.uio_iov = iov;
+ auio.uio_iovcnt = iovlen;
+ auio.uio_segflg = UIO_USERSPACE;
+ auio.uio_rw = UIO_READ;
+ auio.uio_offset = 0; /* XXX */
+ auio.uio_resid = 0;
+ tiov = iov;
+ for (i = 0; i <iovlen; i++, tiov++) {
+ if ((auio.uio_resid += tiov->iov_len) < 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+ }
+ ulen = auio.uio_resid;
+ if (fromlenp != NULL) {
+ fromlen = *fromlenp;
+ } else {
+ fromlen = 0;
+ }
+ errno = sctp_sorecvmsg(so, &auio, (struct mbuf **)NULL,
+ from, fromlen, msg_flags,
+ (struct sctp_sndrcvinfo *)&seinfo, 1);
+ if (errno) {
+ if ((auio.uio_resid != ulen) &&
+ (errno == EINTR ||
+#if !defined(__NetBSD__)
+ errno == ERESTART ||
+#endif
+ errno == EWOULDBLOCK)) {
+ errno = 0;
+ }
+ }
+ if (errno != 0) {
+ goto out;
+ }
+ if ((*msg_flags & MSG_NOTIFICATION) == 0) {
+ struct sctp_inpcb *inp;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVNXTINFO) &&
+ sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVRCVINFO) &&
+ *infolen >= (socklen_t)sizeof(struct sctp_recvv_rn) &&
+ seinfo.sreinfo_next_flags & SCTP_NEXT_MSG_AVAIL) {
+ rn = (struct sctp_recvv_rn *)info;
+ rn->recvv_rcvinfo.rcv_sid = seinfo.sinfo_stream;
+ rn->recvv_rcvinfo.rcv_ssn = seinfo.sinfo_ssn;
+ rn->recvv_rcvinfo.rcv_flags = seinfo.sinfo_flags;
+ rn->recvv_rcvinfo.rcv_ppid = seinfo.sinfo_ppid;
+ rn->recvv_rcvinfo.rcv_context = seinfo.sinfo_context;
+ rn->recvv_rcvinfo.rcv_tsn = seinfo.sinfo_tsn;
+ rn->recvv_rcvinfo.rcv_cumtsn = seinfo.sinfo_cumtsn;
+ rn->recvv_rcvinfo.rcv_assoc_id = seinfo.sinfo_assoc_id;
+ rn->recvv_nxtinfo.nxt_sid = seinfo.sreinfo_next_stream;
+ rn->recvv_nxtinfo.nxt_flags = 0;
+ if (seinfo.sreinfo_next_flags & SCTP_NEXT_MSG_IS_UNORDERED) {
+ rn->recvv_nxtinfo.nxt_flags |= SCTP_UNORDERED;
+ }
+ if (seinfo.sreinfo_next_flags & SCTP_NEXT_MSG_IS_NOTIFICATION) {
+ rn->recvv_nxtinfo.nxt_flags |= SCTP_NOTIFICATION;
+ }
+ if (seinfo.sreinfo_next_flags & SCTP_NEXT_MSG_ISCOMPLETE) {
+ rn->recvv_nxtinfo.nxt_flags |= SCTP_COMPLETE;
+ }
+ rn->recvv_nxtinfo.nxt_ppid = seinfo.sreinfo_next_ppid;
+ rn->recvv_nxtinfo.nxt_length = seinfo.sreinfo_next_length;
+ rn->recvv_nxtinfo.nxt_assoc_id = seinfo.sreinfo_next_aid;
+ *infolen = (socklen_t)sizeof(struct sctp_recvv_rn);
+ *infotype = SCTP_RECVV_RN;
+ } else if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVRCVINFO) &&
+ *infolen >= (socklen_t)sizeof(struct sctp_rcvinfo)) {
+ rcv = (struct sctp_rcvinfo *)info;
+ rcv->rcv_sid = seinfo.sinfo_stream;
+ rcv->rcv_ssn = seinfo.sinfo_ssn;
+ rcv->rcv_flags = seinfo.sinfo_flags;
+ rcv->rcv_ppid = seinfo.sinfo_ppid;
+ rcv->rcv_context = seinfo.sinfo_context;
+ rcv->rcv_tsn = seinfo.sinfo_tsn;
+ rcv->rcv_cumtsn = seinfo.sinfo_cumtsn;
+ rcv->rcv_assoc_id = seinfo.sinfo_assoc_id;
+ *infolen = (socklen_t)sizeof(struct sctp_rcvinfo);
+ *infotype = SCTP_RECVV_RCVINFO;
+ } else {
+ *infotype = SCTP_RECVV_NOINFO;
+ *infolen = 0;
+ }
+ }
+ if ((fromlenp != NULL) &&
+ (fromlen > 0) &&
+ (from != NULL) &&
+ (ulen > auio.uio_resid)) {
+ switch (from->sa_family) {
+#if defined(INET)
+ case AF_INET:
+ *fromlenp = sizeof(struct sockaddr_in);
+ break;
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ *fromlenp = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ case AF_CONN:
+ *fromlenp = sizeof(struct sockaddr_conn);
+ break;
+ default:
+ *fromlenp = 0;
+ break;
+ }
+ if (*fromlenp > fromlen) {
+ *fromlenp = fromlen;
+ }
+ }
+out:
+ if (errno == 0) {
+ /* ready return value */
+ return (ulen - auio.uio_resid);
+ } else {
+ return (-1);
+ }
+}
+
+
+
+
+/* Taken from /src/sys/kern/uipc_socket.c
+ * and modified for __Userspace__
+ * socreate returns a socket. The socket should be
+ * closed with soclose().
+ */
+int
+socreate(int dom, struct socket **aso, int type, int proto)
+{
+ struct socket *so;
+ int error;
+
+ if ((dom != AF_CONN) && (dom != AF_INET) && (dom != AF_INET6)) {
+ return (EINVAL);
+ }
+ if ((type != SOCK_STREAM) && (type != SOCK_SEQPACKET)) {
+ return (EINVAL);
+ }
+ if (proto != IPPROTO_SCTP) {
+ return (EINVAL);
+ }
+
+ so = soalloc();
+ if (so == NULL) {
+ return (ENOBUFS);
+ }
+
+ /*
+ * so_incomp represents a queue of connections that
+ * must be completed at protocol level before being
+ * returned. so_comp field heads a list of sockets
+ * that are ready to be returned to the listening process
+ *__Userspace__ These queues are being used at a number of places like accept etc.
+ */
+ TAILQ_INIT(&so->so_incomp);
+ TAILQ_INIT(&so->so_comp);
+ so->so_type = type;
+ so->so_count = 1;
+ so->so_dom = dom;
+ /*
+ * Auto-sizing of socket buffers is managed by the protocols and
+ * the appropriate flags must be set in the pru_attach function.
+ * For __Userspace__ The pru_attach function in this case is sctp_attach.
+ */
+ switch (dom) {
+#if defined(INET)
+ case AF_INET:
+ error = sctp_attach(so, proto, SCTP_DEFAULT_VRFID);
+ break;
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ error = sctp6_attach(so, proto, SCTP_DEFAULT_VRFID);
+ break;
+#endif
+ case AF_CONN:
+ error = sctpconn_attach(so, proto, SCTP_DEFAULT_VRFID);
+ break;
+ default:
+ error = EAFNOSUPPORT;
+ break;
+ }
+ if (error) {
+ KASSERT(so->so_count == 1, ("socreate: so_count %d", so->so_count));
+ so->so_count = 0;
+ sodealloc(so);
+ return (error);
+ }
+ *aso = so;
+ return (0);
+}
+
+
+/* Taken from /src/sys/kern/uipc_syscalls.c
+ * and modified for __Userspace__
+ * Removing struct thread td.
+ */
+struct socket *
+userspace_socket(int domain, int type, int protocol)
+{
+ struct socket *so = NULL;
+
+ errno = socreate(domain, &so, type, protocol);
+ if (errno) {
+ return (NULL);
+ }
+ /*
+ * The original socket call returns the file descriptor fd.
+ * td->td_retval[0] = fd.
+ * We are returning struct socket *so.
+ */
+ return (so);
+}
+
+struct socket *
+usrsctp_socket(int domain, int type, int protocol,
+ int (*receive_cb)(struct socket *sock, union sctp_sockstore addr, void *data,
+ size_t datalen, struct sctp_rcvinfo, int flags, void *ulp_info),
+ int (*send_cb)(struct socket *sock, uint32_t sb_free, void *ulp_info),
+ uint32_t sb_threshold,
+ void *ulp_info)
+{
+ struct socket *so = NULL;
+
+ if ((protocol == IPPROTO_SCTP) && (SCTP_BASE_VAR(sctp_pcb_initialized) == 0)) {
+ errno = EPROTONOSUPPORT;
+ return (NULL);
+ }
+ if ((receive_cb == NULL) &&
+ ((send_cb != NULL) || (sb_threshold != 0) || (ulp_info != NULL))) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ if ((domain == AF_CONN) && (SCTP_BASE_VAR(conn_output) == NULL)) {
+ errno = EAFNOSUPPORT;
+ return (NULL);
+ }
+ errno = socreate(domain, &so, type, protocol);
+ if (errno) {
+ return (NULL);
+ }
+ /*
+ * The original socket call returns the file descriptor fd.
+ * td->td_retval[0] = fd.
+ * We are returning struct socket *so.
+ */
+ register_recv_cb(so, receive_cb);
+ register_send_cb(so, sb_threshold, send_cb);
+ register_ulp_info(so, ulp_info);
+ return (so);
+}
+
+
+u_long sb_max = SB_MAX;
+u_long sb_max_adj =
+ SB_MAX * MCLBYTES / (MSIZE + MCLBYTES); /* adjusted sb_max */
+
+static u_long sb_efficiency = 8; /* parameter for sbreserve() */
+
+/*
+ * Allot mbufs to a sockbuf. Attempt to scale mbmax so that mbcnt doesn't
+ * become limiting if buffering efficiency is near the normal case.
+ */
+int
+sbreserve_locked(struct sockbuf *sb, u_long cc, struct socket *so)
+{
+ SOCKBUF_LOCK_ASSERT(sb);
+ sb->sb_mbmax = (u_int)min(cc * sb_efficiency, sb_max);
+ sb->sb_hiwat = (u_int)cc;
+ if (sb->sb_lowat > (int)sb->sb_hiwat)
+ sb->sb_lowat = (int)sb->sb_hiwat;
+ return (1);
+}
+
+static int
+sbreserve(struct sockbuf *sb, u_long cc, struct socket *so)
+{
+ int error;
+
+ SOCKBUF_LOCK(sb);
+ error = sbreserve_locked(sb, cc, so);
+ SOCKBUF_UNLOCK(sb);
+ return (error);
+}
+
+int
+soreserve(struct socket *so, u_long sndcc, u_long rcvcc)
+{
+ SOCKBUF_LOCK(&so->so_snd);
+ SOCKBUF_LOCK(&so->so_rcv);
+ so->so_snd.sb_hiwat = (uint32_t)sndcc;
+ so->so_rcv.sb_hiwat = (uint32_t)rcvcc;
+
+ if (sbreserve_locked(&so->so_snd, sndcc, so) == 0) {
+ goto bad;
+ }
+ if (sbreserve_locked(&so->so_rcv, rcvcc, so) == 0) {
+ goto bad;
+ }
+ if (so->so_rcv.sb_lowat == 0)
+ so->so_rcv.sb_lowat = 1;
+ if (so->so_snd.sb_lowat == 0)
+ so->so_snd.sb_lowat = MCLBYTES;
+ if (so->so_snd.sb_lowat > (int)so->so_snd.sb_hiwat)
+ so->so_snd.sb_lowat = (int)so->so_snd.sb_hiwat;
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ SOCKBUF_UNLOCK(&so->so_snd);
+ return (0);
+
+ bad:
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ SOCKBUF_UNLOCK(&so->so_snd);
+ return (ENOBUFS);
+}
+
+
+/* Taken from /src/sys/kern/uipc_sockbuf.c
+ * and modified for __Userspace__
+ */
+
+void
+sowakeup(struct socket *so, struct sockbuf *sb)
+{
+
+ SOCKBUF_LOCK_ASSERT(sb);
+
+ sb->sb_flags &= ~SB_SEL;
+ if (sb->sb_flags & SB_WAIT) {
+ sb->sb_flags &= ~SB_WAIT;
+#if defined(_WIN32)
+ WakeAllConditionVariable(&(sb)->sb_cond);
+#else
+ pthread_cond_broadcast(&(sb)->sb_cond);
+#endif
+ }
+ SOCKBUF_UNLOCK(sb);
+}
+
+
+/* Taken from /src/sys/kern/uipc_socket.c
+ * and modified for __Userspace__
+ */
+
+int
+sobind(struct socket *so, struct sockaddr *nam)
+{
+ switch (nam->sa_family) {
+#if defined(INET)
+ case AF_INET:
+ return (sctp_bind(so, nam));
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ return (sctp6_bind(so, nam, NULL));
+#endif
+ case AF_CONN:
+ return (sctpconn_bind(so, nam));
+ default:
+ return EAFNOSUPPORT;
+ }
+}
+
+/* Taken from /src/sys/kern/uipc_syscalls.c
+ * and modified for __Userspace__
+ */
+
+int
+usrsctp_bind(struct socket *so, struct sockaddr *name, int namelen)
+{
+ struct sockaddr *sa;
+
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ if ((errno = getsockaddr(&sa, (caddr_t)name, namelen)) != 0)
+ return (-1);
+
+ errno = sobind(so, sa);
+ FREE(sa, M_SONAME);
+ if (errno) {
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+int
+userspace_bind(struct socket *so, struct sockaddr *name, int namelen)
+{
+ return (usrsctp_bind(so, name, namelen));
+}
+
+/* Taken from /src/sys/kern/uipc_socket.c
+ * and modified for __Userspace__
+ */
+
+int
+solisten(struct socket *so, int backlog)
+{
+ if (so == NULL) {
+ return (EBADF);
+ } else {
+ return (sctp_listen(so, backlog, NULL));
+ }
+}
+
+
+int
+solisten_proto_check(struct socket *so)
+{
+
+ SOCK_LOCK_ASSERT(so);
+
+ if (so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING |
+ SS_ISDISCONNECTING))
+ return (EINVAL);
+ return (0);
+}
+
+static int somaxconn = SOMAXCONN;
+
+void
+solisten_proto(struct socket *so, int backlog)
+{
+
+ SOCK_LOCK_ASSERT(so);
+
+ if (backlog < 0 || backlog > somaxconn)
+ backlog = somaxconn;
+ so->so_qlimit = backlog;
+ so->so_options |= SCTP_SO_ACCEPTCONN;
+}
+
+
+
+
+/* Taken from /src/sys/kern/uipc_syscalls.c
+ * and modified for __Userspace__
+ */
+
+int
+usrsctp_listen(struct socket *so, int backlog)
+{
+ errno = solisten(so, backlog);
+ if (errno) {
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+int
+userspace_listen(struct socket *so, int backlog)
+{
+ return (usrsctp_listen(so, backlog));
+}
+
+/* Taken from /src/sys/kern/uipc_socket.c
+ * and modified for __Userspace__
+ */
+
+int
+soaccept(struct socket *so, struct sockaddr **nam)
+{
+ int error;
+
+ SOCK_LOCK(so);
+ KASSERT((so->so_state & SS_NOFDREF) != 0, ("soaccept: !NOFDREF"));
+ so->so_state &= ~SS_NOFDREF;
+ SOCK_UNLOCK(so);
+ error = sctp_accept(so, nam);
+ return (error);
+}
+
+
+
+/* Taken from /src/sys/kern/uipc_syscalls.c
+ * kern_accept modified for __Userspace__
+ */
+int
+user_accept(struct socket *head, struct sockaddr **name, socklen_t *namelen, struct socket **ptr_accept_ret_sock)
+{
+ struct sockaddr *sa = NULL;
+ int error;
+ struct socket *so = NULL;
+
+
+ if (name) {
+ *name = NULL;
+ }
+
+ if ((head->so_options & SCTP_SO_ACCEPTCONN) == 0) {
+ error = EINVAL;
+ goto done;
+ }
+
+ ACCEPT_LOCK();
+ if ((head->so_state & SS_NBIO) && TAILQ_EMPTY(&head->so_comp)) {
+ ACCEPT_UNLOCK();
+ error = EWOULDBLOCK;
+ goto noconnection;
+ }
+ while (TAILQ_EMPTY(&head->so_comp) && head->so_error == 0) {
+ if (head->so_rcv.sb_state & SBS_CANTRCVMORE) {
+ head->so_error = ECONNABORTED;
+ break;
+ }
+#if defined(_WIN32)
+ if (SleepConditionVariableCS(&accept_cond, &accept_mtx, INFINITE))
+ error = 0;
+ else
+ error = GetLastError();
+#else
+ error = pthread_cond_wait(&accept_cond, &accept_mtx);
+#endif
+ if (error) {
+ ACCEPT_UNLOCK();
+ goto noconnection;
+ }
+ }
+ if (head->so_error) {
+ error = head->so_error;
+ head->so_error = 0;
+ ACCEPT_UNLOCK();
+ goto noconnection;
+ }
+ so = TAILQ_FIRST(&head->so_comp);
+ KASSERT(!(so->so_qstate & SQ_INCOMP), ("accept1: so SQ_INCOMP"));
+ KASSERT(so->so_qstate & SQ_COMP, ("accept1: so not SQ_COMP"));
+
+ /*
+ * Before changing the flags on the socket, we have to bump the
+ * reference count. Otherwise, if the protocol calls sofree(),
+ * the socket will be released due to a zero refcount.
+ */
+ SOCK_LOCK(so); /* soref() and so_state update */
+ soref(so); /* file descriptor reference */
+
+ TAILQ_REMOVE(&head->so_comp, so, so_list);
+ head->so_qlen--;
+ so->so_state |= (head->so_state & SS_NBIO);
+ so->so_qstate &= ~SQ_COMP;
+ so->so_head = NULL;
+ SOCK_UNLOCK(so);
+ ACCEPT_UNLOCK();
+
+
+ /*
+ * The original accept returns fd value via td->td_retval[0] = fd;
+ * we will return the socket for accepted connection.
+ */
+
+ error = soaccept(so, &sa);
+ if (error) {
+ /*
+ * return a namelen of zero for older code which might
+ * ignore the return value from accept.
+ */
+ if (name)
+ *namelen = 0;
+ goto noconnection;
+ }
+ if (sa == NULL) {
+ if (name)
+ *namelen = 0;
+ goto done;
+ }
+ if (name) {
+#ifdef HAVE_SA_LEN
+ /* check sa_len before it is destroyed */
+ if (*namelen > sa->sa_len) {
+ *namelen = sa->sa_len;
+ }
+#else
+ socklen_t sa_len;
+
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sa_len = sizeof(struct sockaddr_in);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa_len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ case AF_CONN:
+ sa_len = sizeof(struct sockaddr_conn);
+ break;
+ default:
+ sa_len = 0;
+ break;
+ }
+ if (*namelen > sa_len) {
+ *namelen = sa_len;
+ }
+#endif
+ *name = sa;
+ sa = NULL;
+ }
+noconnection:
+ if (sa) {
+ FREE(sa, M_SONAME);
+ }
+
+done:
+ *ptr_accept_ret_sock = so;
+ return (error);
+}
+
+
+
+/* Taken from /src/sys/kern/uipc_syscalls.c
+ * and modified for __Userspace__
+ */
+/*
+ * accept1()
+ */
+static int
+accept1(struct socket *so, struct sockaddr *aname, socklen_t *anamelen, struct socket **ptr_accept_ret_sock)
+{
+ struct sockaddr *name;
+ socklen_t namelen;
+ int error;
+
+ if (so == NULL) {
+ return (EBADF);
+ }
+ if (aname == NULL) {
+ return (user_accept(so, NULL, NULL, ptr_accept_ret_sock));
+ }
+
+ error = copyin(anamelen, &namelen, sizeof (namelen));
+ if (error)
+ return (error);
+
+ error = user_accept(so, &name, &namelen, ptr_accept_ret_sock);
+
+ /*
+ * return a namelen of zero for older code which might
+ * ignore the return value from accept.
+ */
+ if (error) {
+ (void) copyout(&namelen,
+ anamelen, sizeof(*anamelen));
+ return (error);
+ }
+
+ if (error == 0 && name != NULL) {
+ error = copyout(name, aname, namelen);
+ }
+ if (error == 0) {
+ error = copyout(&namelen, anamelen, sizeof(namelen));
+ }
+
+ if (name) {
+ FREE(name, M_SONAME);
+ }
+ return (error);
+}
+
+struct socket *
+usrsctp_accept(struct socket *so, struct sockaddr *aname, socklen_t *anamelen)
+{
+ struct socket *accept_return_sock = NULL;
+
+ errno = accept1(so, aname, anamelen, &accept_return_sock);
+ if (errno) {
+ return (NULL);
+ } else {
+ return (accept_return_sock);
+ }
+}
+
+struct socket *
+userspace_accept(struct socket *so, struct sockaddr *aname, socklen_t *anamelen)
+{
+ return (usrsctp_accept(so, aname, anamelen));
+}
+
+struct socket *
+usrsctp_peeloff(struct socket *head, sctp_assoc_t id)
+{
+ struct socket *so;
+
+ if ((errno = sctp_can_peel_off(head, id)) != 0) {
+ return (NULL);
+ }
+ if ((so = sonewconn(head, SS_ISCONNECTED)) == NULL) {
+ return (NULL);
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(so);
+ soref(so);
+ TAILQ_REMOVE(&head->so_comp, so, so_list);
+ head->so_qlen--;
+ so->so_state |= (head->so_state & SS_NBIO);
+ so->so_qstate &= ~SQ_COMP;
+ so->so_head = NULL;
+ SOCK_UNLOCK(so);
+ ACCEPT_UNLOCK();
+ if ((errno = sctp_do_peeloff(head, so, id)) != 0) {
+ so->so_count = 0;
+ sodealloc(so);
+ return (NULL);
+ }
+ return (so);
+}
+
+int
+sodisconnect(struct socket *so)
+{
+ int error;
+
+ if ((so->so_state & SS_ISCONNECTED) == 0)
+ return (ENOTCONN);
+ if (so->so_state & SS_ISDISCONNECTING)
+ return (EALREADY);
+ error = sctp_disconnect(so);
+ return (error);
+}
+
+int
+usrsctp_set_non_blocking(struct socket *so, int onoff)
+{
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ SOCK_LOCK(so);
+ if (onoff != 0) {
+ so->so_state |= SS_NBIO;
+ } else {
+ so->so_state &= ~SS_NBIO;
+ }
+ SOCK_UNLOCK(so);
+ return (0);
+}
+
+int
+usrsctp_get_non_blocking(struct socket *so)
+{
+ int result;
+
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ SOCK_LOCK(so);
+ if (so->so_state & SS_NBIO) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ SOCK_UNLOCK(so);
+ return (result);
+}
+
+int
+soconnect(struct socket *so, struct sockaddr *nam)
+{
+ int error;
+
+ if (so->so_options & SCTP_SO_ACCEPTCONN)
+ return (EOPNOTSUPP);
+ /*
+ * If protocol is connection-based, can only connect once.
+ * Otherwise, if connected, try to disconnect first. This allows
+ * user to disconnect by connecting to, e.g., a null address.
+ */
+ if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) && (sodisconnect(so) != 0)) {
+ error = EISCONN;
+ } else {
+ /*
+ * Prevent accumulated error from previous connection from
+ * biting us.
+ */
+ so->so_error = 0;
+ switch (nam->sa_family) {
+#if defined(INET)
+ case AF_INET:
+ error = sctp_connect(so, nam);
+ break;
+#endif
+#if defined(INET6)
+ case AF_INET6:
+ error = sctp6_connect(so, nam);
+ break;
+#endif
+ case AF_CONN:
+ error = sctpconn_connect(so, nam);
+ break;
+ default:
+ error = EAFNOSUPPORT;
+ }
+ }
+
+ return (error);
+}
+
+
+
+int user_connect(struct socket *so, struct sockaddr *sa)
+{
+ int error;
+ int interrupted = 0;
+
+ if (so == NULL) {
+ error = EBADF;
+ goto done1;
+ }
+ if (so->so_state & SS_ISCONNECTING) {
+ error = EALREADY;
+ goto done1;
+ }
+
+ error = soconnect(so, sa);
+ if (error) {
+ goto bad;
+ }
+ if ((so->so_state & SS_NBIO) && (so->so_state & SS_ISCONNECTING)) {
+ error = EINPROGRESS;
+ goto done1;
+ }
+
+ SOCK_LOCK(so);
+ while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) {
+#if defined(_WIN32)
+ if (SleepConditionVariableCS(SOCK_COND(so), SOCK_MTX(so), INFINITE))
+ error = 0;
+ else
+ error = -1;
+#else
+ error = pthread_cond_wait(SOCK_COND(so), SOCK_MTX(so));
+#endif
+ if (error) {
+#if defined(__NetBSD__)
+ if (error == EINTR) {
+#else
+ if (error == EINTR || error == ERESTART) {
+#endif
+ interrupted = 1;
+ }
+ break;
+ }
+ }
+ if (error == 0) {
+ error = so->so_error;
+ so->so_error = 0;
+ }
+ SOCK_UNLOCK(so);
+
+bad:
+ if (!interrupted) {
+ so->so_state &= ~SS_ISCONNECTING;
+ }
+#if !defined(__NetBSD__)
+ if (error == ERESTART) {
+ error = EINTR;
+ }
+#endif
+done1:
+ return (error);
+}
+
+int usrsctp_connect(struct socket *so, struct sockaddr *name, int namelen)
+{
+ struct sockaddr *sa = NULL;
+
+ errno = getsockaddr(&sa, (caddr_t)name, namelen);
+ if (errno)
+ return (-1);
+
+ errno = user_connect(so, sa);
+ FREE(sa, M_SONAME);
+ if (errno) {
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+int userspace_connect(struct socket *so, struct sockaddr *name, int namelen)
+{
+ return (usrsctp_connect(so, name, namelen));
+}
+
+#define SCTP_STACK_BUF_SIZE 2048
+
+void
+usrsctp_close(struct socket *so) {
+ if (so != NULL) {
+ if (so->so_options & SCTP_SO_ACCEPTCONN) {
+ struct socket *sp;
+
+ ACCEPT_LOCK();
+ while ((sp = TAILQ_FIRST(&so->so_comp)) != NULL) {
+ TAILQ_REMOVE(&so->so_comp, sp, so_list);
+ so->so_qlen--;
+ sp->so_qstate &= ~SQ_COMP;
+ sp->so_head = NULL;
+ ACCEPT_UNLOCK();
+ soabort(sp);
+ ACCEPT_LOCK();
+ }
+ ACCEPT_UNLOCK();
+ }
+ ACCEPT_LOCK();
+ SOCK_LOCK(so);
+ sorele(so);
+ }
+}
+
+void
+userspace_close(struct socket *so)
+{
+ usrsctp_close(so);
+}
+
+int
+usrsctp_shutdown(struct socket *so, int how)
+{
+ if (!(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ sctp_flush(so, how);
+ if (how != SHUT_WR)
+ socantrcvmore(so);
+ if (how != SHUT_RD) {
+ errno = sctp_shutdown(so);
+ if (errno) {
+ return (-1);
+ } else {
+ return (0);
+ }
+ }
+ return (0);
+}
+
+int
+userspace_shutdown(struct socket *so, int how)
+{
+ return (usrsctp_shutdown(so, how));
+}
+
+int
+usrsctp_finish(void)
+{
+ if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
+ return (0);
+ }
+ if (SCTP_INP_INFO_TRYLOCK()) {
+ if (!LIST_EMPTY(&SCTP_BASE_INFO(listhead))) {
+ SCTP_INP_INFO_RUNLOCK();
+ return (-1);
+ }
+ SCTP_INP_INFO_RUNLOCK();
+ } else {
+ return (-1);
+ }
+ sctp_finish();
+#if defined(_WIN32)
+ DeleteConditionVariable(&accept_cond);
+ DeleteCriticalSection(&accept_mtx);
+#if defined(INET) || defined(INET6)
+ WSACleanup();
+#endif
+#else
+ pthread_cond_destroy(&accept_cond);
+ pthread_mutex_destroy(&accept_mtx);
+#endif
+ return (0);
+}
+
+int
+userspace_finish(void)
+{
+ return (usrsctp_finish());
+}
+
+/* needed from sctp_usrreq.c */
+int
+sctp_setopt(struct socket *so, int optname, void *optval, size_t optsize, void *p);
+
+int
+usrsctp_setsockopt(struct socket *so, int level, int option_name,
+ const void *option_value, socklen_t option_len)
+{
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ switch (level) {
+ case SOL_SOCKET:
+ {
+ switch (option_name) {
+ case SO_RCVBUF:
+ if (option_len < (socklen_t)sizeof(int)) {
+ errno = EINVAL;
+ return (-1);
+ } else {
+ int *buf_size;
+
+ buf_size = (int *)option_value;
+ if (*buf_size < 1) {
+ errno = EINVAL;
+ return (-1);
+ }
+ sbreserve(&so->so_rcv, (u_long)*buf_size, so);
+ return (0);
+ }
+ break;
+ case SO_SNDBUF:
+ if (option_len < (socklen_t)sizeof(int)) {
+ errno = EINVAL;
+ return (-1);
+ } else {
+ int *buf_size;
+
+ buf_size = (int *)option_value;
+ if (*buf_size < 1) {
+ errno = EINVAL;
+ return (-1);
+ }
+ sbreserve(&so->so_snd, (u_long)*buf_size, so);
+ return (0);
+ }
+ break;
+ case SO_LINGER:
+ if (option_len < (socklen_t)sizeof(struct linger)) {
+ errno = EINVAL;
+ return (-1);
+ } else {
+ struct linger *l;
+
+ l = (struct linger *)option_value;
+ so->so_linger = l->l_linger;
+ if (l->l_onoff) {
+ so->so_options |= SCTP_SO_LINGER;
+ } else {
+ so->so_options &= ~SCTP_SO_LINGER;
+ }
+ return (0);
+ }
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+ }
+ case IPPROTO_SCTP:
+ errno = sctp_setopt(so, option_name, (void *) option_value, (size_t)option_len, NULL);
+ if (errno) {
+ return (-1);
+ } else {
+ return (0);
+ }
+ default:
+ errno = ENOPROTOOPT;
+ return (-1);
+ }
+}
+
+int
+userspace_setsockopt(struct socket *so, int level, int option_name,
+ const void *option_value, socklen_t option_len)
+{
+ return (usrsctp_setsockopt(so, level, option_name, option_value, option_len));
+}
+
+/* needed from sctp_usrreq.c */
+int
+sctp_getopt(struct socket *so, int optname, void *optval, size_t *optsize,
+ void *p);
+
+int
+usrsctp_getsockopt(struct socket *so, int level, int option_name,
+ void *option_value, socklen_t *option_len)
+{
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ if (option_len == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+ switch (level) {
+ case SOL_SOCKET:
+ switch (option_name) {
+ case SO_RCVBUF:
+ if (*option_len < (socklen_t)sizeof(int)) {
+ errno = EINVAL;
+ return (-1);
+ } else {
+ int *buf_size;
+
+ buf_size = (int *)option_value;
+ *buf_size = so->so_rcv.sb_hiwat;
+ *option_len = (socklen_t)sizeof(int);
+ return (0);
+ }
+ break;
+ case SO_SNDBUF:
+ if (*option_len < (socklen_t)sizeof(int)) {
+ errno = EINVAL;
+ return (-1);
+ } else {
+ int *buf_size;
+
+ buf_size = (int *)option_value;
+ *buf_size = so->so_snd.sb_hiwat;
+ *option_len = (socklen_t)sizeof(int);
+ return (0);
+ }
+ break;
+ case SO_LINGER:
+ if (*option_len < (socklen_t)sizeof(struct linger)) {
+ errno = EINVAL;
+ return (-1);
+ } else {
+ struct linger *l;
+
+ l = (struct linger *)option_value;
+ l->l_linger = so->so_linger;
+ if (so->so_options & SCTP_SO_LINGER) {
+ l->l_onoff = 1;
+ } else {
+ l->l_onoff = 0;
+ }
+ *option_len = (socklen_t)sizeof(struct linger);
+ return (0);
+ }
+ break;
+ case SO_ERROR:
+ if (*option_len < (socklen_t)sizeof(int)) {
+ errno = EINVAL;
+ return (-1);
+ } else {
+ int *intval;
+
+ intval = (int *)option_value;
+ *intval = so->so_error;
+ *option_len = (socklen_t)sizeof(int);
+ return (0);
+ }
+ break;
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+ case IPPROTO_SCTP:
+ {
+ size_t len;
+
+ len = (size_t)*option_len;
+ errno = sctp_getopt(so, option_name, option_value, &len, NULL);
+ *option_len = (socklen_t)len;
+ if (errno) {
+ return (-1);
+ } else {
+ return (0);
+ }
+ }
+ default:
+ errno = ENOPROTOOPT;
+ return (-1);
+ }
+}
+
+int
+userspace_getsockopt(struct socket *so, int level, int option_name,
+ void *option_value, socklen_t *option_len)
+{
+ return (usrsctp_getsockopt(so, level, option_name, option_value, option_len));
+}
+
+int
+usrsctp_opt_info(struct socket *so, sctp_assoc_t id, int opt, void *arg, socklen_t *size)
+{
+ if (arg == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if ((id == SCTP_CURRENT_ASSOC) ||
+ (id == SCTP_ALL_ASSOC)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ switch (opt) {
+ case SCTP_RTOINFO:
+ ((struct sctp_rtoinfo *)arg)->srto_assoc_id = id;
+ break;
+ case SCTP_ASSOCINFO:
+ ((struct sctp_assocparams *)arg)->sasoc_assoc_id = id;
+ break;
+ case SCTP_DEFAULT_SEND_PARAM:
+ ((struct sctp_assocparams *)arg)->sasoc_assoc_id = id;
+ break;
+ case SCTP_PRIMARY_ADDR:
+ ((struct sctp_setprim *)arg)->ssp_assoc_id = id;
+ break;
+ case SCTP_PEER_ADDR_PARAMS:
+ ((struct sctp_paddrparams *)arg)->spp_assoc_id = id;
+ break;
+ case SCTP_MAXSEG:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_AUTH_KEY:
+ ((struct sctp_authkey *)arg)->sca_assoc_id = id;
+ break;
+ case SCTP_AUTH_ACTIVE_KEY:
+ ((struct sctp_authkeyid *)arg)->scact_assoc_id = id;
+ break;
+ case SCTP_DELAYED_SACK:
+ ((struct sctp_sack_info *)arg)->sack_assoc_id = id;
+ break;
+ case SCTP_CONTEXT:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_STATUS:
+ ((struct sctp_status *)arg)->sstat_assoc_id = id;
+ break;
+ case SCTP_GET_PEER_ADDR_INFO:
+ ((struct sctp_paddrinfo *)arg)->spinfo_assoc_id = id;
+ break;
+ case SCTP_PEER_AUTH_CHUNKS:
+ ((struct sctp_authchunks *)arg)->gauth_assoc_id = id;
+ break;
+ case SCTP_LOCAL_AUTH_CHUNKS:
+ ((struct sctp_authchunks *)arg)->gauth_assoc_id = id;
+ break;
+ case SCTP_TIMEOUTS:
+ ((struct sctp_timeouts *)arg)->stimo_assoc_id = id;
+ break;
+ case SCTP_EVENT:
+ ((struct sctp_event *)arg)->se_assoc_id = id;
+ break;
+ case SCTP_DEFAULT_SNDINFO:
+ ((struct sctp_sndinfo *)arg)->snd_assoc_id = id;
+ break;
+ case SCTP_DEFAULT_PRINFO:
+ ((struct sctp_default_prinfo *)arg)->pr_assoc_id = id;
+ break;
+ case SCTP_PEER_ADDR_THLDS:
+ ((struct sctp_paddrthlds *)arg)->spt_assoc_id = id;
+ break;
+ case SCTP_REMOTE_UDP_ENCAPS_PORT:
+ ((struct sctp_udpencaps *)arg)->sue_assoc_id = id;
+ break;
+ case SCTP_ECN_SUPPORTED:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_PR_SUPPORTED:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_AUTH_SUPPORTED:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_ASCONF_SUPPORTED:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_RECONFIG_SUPPORTED:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_NRSACK_SUPPORTED:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_PKTDROP_SUPPORTED:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_MAX_BURST:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_ENABLE_STREAM_RESET:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ case SCTP_PR_STREAM_STATUS:
+ ((struct sctp_prstatus *)arg)->sprstat_assoc_id = id;
+ break;
+ case SCTP_PR_ASSOC_STATUS:
+ ((struct sctp_prstatus *)arg)->sprstat_assoc_id = id;
+ break;
+ case SCTP_MAX_CWND:
+ ((struct sctp_assoc_value *)arg)->assoc_id = id;
+ break;
+ default:
+ break;
+ }
+ return (usrsctp_getsockopt(so, IPPROTO_SCTP, opt, arg, size));
+}
+
+int
+usrsctp_set_ulpinfo(struct socket *so, void *ulp_info)
+{
+ return (register_ulp_info(so, ulp_info));
+}
+
+
+int
+usrsctp_get_ulpinfo(struct socket *so, void **pulp_info)
+{
+ return (retrieve_ulp_info(so, pulp_info));
+}
+
+int
+usrsctp_bindx(struct socket *so, struct sockaddr *addrs, int addrcnt, int flags)
+{
+ struct sockaddr *sa;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+ int i;
+#if defined(INET) || defined(INET6)
+ uint16_t sport;
+ bool fix_port;
+#endif
+
+ /* validate the flags */
+ if ((flags != SCTP_BINDX_ADD_ADDR) &&
+ (flags != SCTP_BINDX_REM_ADDR)) {
+ errno = EFAULT;
+ return (-1);
+ }
+ /* validate the address count and list */
+ if ((addrcnt <= 0) || (addrs == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+#if defined(INET) || defined(INET6)
+ sport = 0;
+ fix_port = false;
+#endif
+ /* First pre-screen the addresses */
+ sa = addrs;
+ for (i = 0; i < addrcnt; i++) {
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != sizeof(struct sockaddr_in)) {
+ errno = EINVAL;
+ return (-1);
+ }
+#endif
+ sin = (struct sockaddr_in *)sa;
+ if (sin->sin_port) {
+ /* non-zero port, check or save */
+ if (sport) {
+ /* Check against our port */
+ if (sport != sin->sin_port) {
+ errno = EINVAL;
+ return (-1);
+ }
+ } else {
+ /* save off the port */
+ sport = sin->sin_port;
+ fix_port = (i > 0);
+ }
+ }
+#ifndef HAVE_SA_LEN
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in));
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != sizeof(struct sockaddr_in6)) {
+ errno = EINVAL;
+ return (-1);
+ }
+#endif
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (sin6->sin6_port) {
+ /* non-zero port, check or save */
+ if (sport) {
+ /* Check against our port */
+ if (sport != sin6->sin6_port) {
+ errno = EINVAL;
+ return (-1);
+ }
+ } else {
+ /* save off the port */
+ sport = sin6->sin6_port;
+ fix_port = (i > 0);
+ }
+ }
+#ifndef HAVE_SA_LEN
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in6));
+#endif
+ break;
+#endif
+ default:
+ /* Invalid address family specified. */
+ errno = EAFNOSUPPORT;
+ return (-1);
+ }
+#ifdef HAVE_SA_LEN
+ sa = (struct sockaddr *)((caddr_t)sa + sa->sa_len);
+#endif
+ }
+ sa = addrs;
+ for (i = 0; i < addrcnt; i++) {
+#ifndef HAVE_SA_LEN
+ size_t sa_len;
+
+#endif
+#ifdef HAVE_SA_LEN
+#if defined(INET) || defined(INET6)
+ if (fix_port) {
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ ((struct sockaddr_in *)sa)->sin_port = sport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ((struct sockaddr_in6 *)sa)->sin6_port = sport;
+ break;
+#endif
+ }
+ }
+#endif
+ if (usrsctp_setsockopt(so, IPPROTO_SCTP, flags, sa, sa->sa_len) != 0) {
+ return (-1);
+ }
+ sa = (struct sockaddr *)((caddr_t)sa + sa->sa_len);
+#else
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sa_len = sizeof(struct sockaddr_in);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa_len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ default:
+ sa_len = 0;
+ break;
+ }
+ /*
+ * Now, if there was a port mentioned, assure that the
+ * first address has that port to make sure it fails or
+ * succeeds correctly.
+ */
+#if defined(INET) || defined(INET6)
+ if (fix_port) {
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ ((struct sockaddr_in *)sa)->sin_port = sport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ((struct sockaddr_in6 *)sa)->sin6_port = sport;
+ break;
+#endif
+ }
+ }
+#endif
+ if (usrsctp_setsockopt(so, IPPROTO_SCTP, flags, sa, (socklen_t)sa_len) != 0) {
+ return (-1);
+ }
+ sa = (struct sockaddr *)((caddr_t)sa + sa_len);
+#endif
+ }
+ return (0);
+}
+
+int
+usrsctp_connectx(struct socket *so,
+ const struct sockaddr *addrs, int addrcnt,
+ sctp_assoc_t *id)
+{
+#if defined(INET) || defined(INET6)
+ char buf[SCTP_STACK_BUF_SIZE];
+ int i, ret, cnt, *aa;
+ char *cpto;
+ const struct sockaddr *at;
+ sctp_assoc_t *p_id;
+ size_t len = sizeof(int);
+
+ /* validate the address count and list */
+ if ((addrs == NULL) || (addrcnt <= 0)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ at = addrs;
+ cnt = 0;
+ cpto = ((caddr_t)buf + sizeof(int));
+ /* validate all the addresses and get the size */
+ for (i = 0; i < addrcnt; i++) {
+ switch (at->sa_family) {
+#ifdef INET
+ case AF_INET:
+#ifdef HAVE_SA_LEN
+ if (at->sa_len != sizeof(struct sockaddr_in)) {
+ errno = EINVAL;
+ return (-1);
+ }
+#endif
+ len += sizeof(struct sockaddr_in);
+ if (len > SCTP_STACK_BUF_SIZE) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ memcpy(cpto, at, sizeof(struct sockaddr_in));
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in));
+ at = (struct sockaddr *)((caddr_t)at + sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+#ifdef HAVE_SA_LEN
+ if (at->sa_len != sizeof(struct sockaddr_in6)) {
+ errno = EINVAL;
+ return (-1);
+ }
+#endif
+#ifdef INET
+ if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)at)->sin6_addr)) {
+ len += sizeof(struct sockaddr_in);
+ if (len > SCTP_STACK_BUF_SIZE) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ in6_sin6_2_sin((struct sockaddr_in *)cpto, (struct sockaddr_in6 *)at);
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in));
+ } else {
+ len += sizeof(struct sockaddr_in6);
+ if (len > SCTP_STACK_BUF_SIZE) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ memcpy(cpto, at, sizeof(struct sockaddr_in6));
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in6));
+ }
+#else
+ len += sizeof(struct sockaddr_in6);
+ if (len > SCTP_STACK_BUF_SIZE) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ memcpy(cpto, at, sizeof(struct sockaddr_in6));
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in6));
+#endif
+ at = (struct sockaddr *)((caddr_t)at + sizeof(struct sockaddr_in6));
+ break;
+#endif
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+ cnt++;
+ }
+ aa = (int *)buf;
+ *aa = cnt;
+ ret = usrsctp_setsockopt(so, IPPROTO_SCTP, SCTP_CONNECT_X, (void *)buf, (socklen_t)len);
+ if ((ret == 0) && id) {
+ p_id = (sctp_assoc_t *)buf;
+ *id = *p_id;
+ }
+ return (ret);
+#else
+ errno = EINVAL;
+ return (-1);
+#endif
+}
+
+int
+usrsctp_getpaddrs(struct socket *so, sctp_assoc_t id, struct sockaddr **raddrs)
+{
+ struct sctp_getaddresses *addrs;
+ struct sockaddr *sa;
+ caddr_t lim;
+ socklen_t opt_len;
+ uint32_t size_of_addresses;
+ int cnt;
+
+ if (raddrs == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+ /* When calling getsockopt(), the value contains the assoc_id. */
+ size_of_addresses = (uint32_t)id;
+ opt_len = (socklen_t)sizeof(uint32_t);
+ if (usrsctp_getsockopt(so, IPPROTO_SCTP, SCTP_GET_REMOTE_ADDR_SIZE, &size_of_addresses, &opt_len) != 0) {
+ if (errno == ENOENT) {
+ return (0);
+ } else {
+ return (-1);
+ }
+ }
+ opt_len = (socklen_t)((size_t)size_of_addresses + sizeof(struct sctp_getaddresses));
+ addrs = calloc(1, (size_t)opt_len);
+ if (addrs == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ addrs->sget_assoc_id = id;
+ /* Now lets get the array of addresses */
+ if (usrsctp_getsockopt(so, IPPROTO_SCTP, SCTP_GET_PEER_ADDRESSES, addrs, &opt_len) != 0) {
+ free(addrs);
+ return (-1);
+ }
+ *raddrs = &addrs->addr[0].sa;
+ cnt = 0;
+ sa = &addrs->addr[0].sa;
+ lim = (caddr_t)addrs + opt_len;
+#ifdef HAVE_SA_LEN
+ while (((caddr_t)sa < lim) && (sa->sa_len > 0)) {
+ sa = (struct sockaddr *)((caddr_t)sa + sa->sa_len);
+#else
+ while ((caddr_t)sa < lim) {
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in6));
+ break;
+#endif
+ case AF_CONN:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_conn));
+ break;
+ default:
+ return (cnt);
+ break;
+ }
+#endif
+ cnt++;
+ }
+ return (cnt);
+}
+
+void
+usrsctp_freepaddrs(struct sockaddr *addrs)
+{
+ /* Take away the hidden association id */
+ void *fr_addr;
+
+ fr_addr = (void *)((caddr_t)addrs - offsetof(struct sctp_getaddresses, addr));
+ /* Now free it */
+ free(fr_addr);
+}
+
+int
+usrsctp_getladdrs(struct socket *so, sctp_assoc_t id, struct sockaddr **raddrs)
+{
+ struct sctp_getaddresses *addrs;
+ struct sockaddr *sa;
+ caddr_t lim;
+ socklen_t opt_len;
+ uint32_t size_of_addresses;
+ int cnt;
+
+ if (raddrs == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+ size_of_addresses = 0;
+ opt_len = (socklen_t)sizeof(uint32_t);
+ if (usrsctp_getsockopt(so, IPPROTO_SCTP, SCTP_GET_LOCAL_ADDR_SIZE, &size_of_addresses, &opt_len) != 0) {
+ return (-1);
+ }
+ opt_len = (socklen_t)(size_of_addresses + sizeof(struct sctp_getaddresses));
+ addrs = calloc(1, (size_t)opt_len);
+ if (addrs == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ addrs->sget_assoc_id = id;
+ /* Now lets get the array of addresses */
+ if (usrsctp_getsockopt(so, IPPROTO_SCTP, SCTP_GET_LOCAL_ADDRESSES, addrs, &opt_len) != 0) {
+ free(addrs);
+ return (-1);
+ }
+ if (size_of_addresses == 0) {
+ free(addrs);
+ return (0);
+ }
+ *raddrs = &addrs->addr[0].sa;
+ cnt = 0;
+ sa = &addrs->addr[0].sa;
+ lim = (caddr_t)addrs + opt_len;
+#ifdef HAVE_SA_LEN
+ while (((caddr_t)sa < lim) && (sa->sa_len > 0)) {
+ sa = (struct sockaddr *)((caddr_t)sa + sa->sa_len);
+#else
+ while ((caddr_t)sa < lim) {
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in6));
+ break;
+#endif
+ case AF_CONN:
+ sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_conn));
+ break;
+ default:
+ return (cnt);
+ break;
+ }
+#endif
+ cnt++;
+ }
+ return (cnt);
+}
+
+void
+usrsctp_freeladdrs(struct sockaddr *addrs)
+{
+ /* Take away the hidden association id */
+ void *fr_addr;
+
+ fr_addr = (void *)((caddr_t)addrs - offsetof(struct sctp_getaddresses, addr));
+ /* Now free it */
+ free(fr_addr);
+}
+
+#ifdef INET
+void
+sctp_userspace_ip_output(int *result, struct mbuf *o_pak,
+ sctp_route_t *ro, void *inp,
+ uint32_t vrf_id)
+{
+ struct mbuf *m;
+ struct mbuf *m_orig;
+ int iovcnt;
+ int len;
+ struct ip *ip;
+ struct udphdr *udp;
+ struct sockaddr_in dst;
+#if defined(_WIN32)
+ WSAMSG win_msg_hdr;
+ DWORD win_sent_len;
+ WSABUF send_iovec[MAXLEN_MBUF_CHAIN];
+ WSABUF winbuf;
+#else
+ struct iovec send_iovec[MAXLEN_MBUF_CHAIN];
+ struct msghdr msg_hdr;
+#endif
+ int use_udp_tunneling;
+
+ *result = 0;
+
+ m = SCTP_HEADER_TO_CHAIN(o_pak);
+ m_orig = m;
+
+ len = sizeof(struct ip);
+ if (SCTP_BUF_LEN(m) < len) {
+ if ((m = m_pullup(m, len)) == 0) {
+ SCTP_PRINTF("Can not get the IP header in the first mbuf.\n");
+ return;
+ }
+ }
+ ip = mtod(m, struct ip *);
+ use_udp_tunneling = (ip->ip_p == IPPROTO_UDP);
+
+ if (use_udp_tunneling) {
+ len = sizeof(struct ip) + sizeof(struct udphdr);
+ if (SCTP_BUF_LEN(m) < len) {
+ if ((m = m_pullup(m, len)) == 0) {
+ SCTP_PRINTF("Can not get the UDP/IP header in the first mbuf.\n");
+ return;
+ }
+ ip = mtod(m, struct ip *);
+ }
+ udp = (struct udphdr *)(ip + 1);
+ } else {
+ udp = NULL;
+ }
+
+ if (!use_udp_tunneling) {
+ if (ip->ip_src.s_addr == INADDR_ANY) {
+ /* TODO get addr of outgoing interface */
+ SCTP_PRINTF("Why did the SCTP implementation did not choose a source address?\n");
+ }
+ /* TODO need to worry about ro->ro_dst as in ip_output? */
+#if defined(__linux__) || defined(_WIN32) || (defined(__FreeBSD__) && (__FreeBSD_version >= 1100030))
+ /* need to put certain fields into network order for Linux */
+ ip->ip_len = htons(ip->ip_len);
+#endif
+ }
+
+ memset((void *)&dst, 0, sizeof(struct sockaddr_in));
+ dst.sin_family = AF_INET;
+ dst.sin_addr.s_addr = ip->ip_dst.s_addr;
+#ifdef HAVE_SIN_LEN
+ dst.sin_len = sizeof(struct sockaddr_in);
+#endif
+ if (use_udp_tunneling) {
+ dst.sin_port = udp->uh_dport;
+ } else {
+ dst.sin_port = 0;
+ }
+
+ /* tweak the mbuf chain */
+ if (use_udp_tunneling) {
+ m_adj(m, sizeof(struct ip) + sizeof(struct udphdr));
+ }
+
+ for (iovcnt = 0; m != NULL && iovcnt < MAXLEN_MBUF_CHAIN; m = m->m_next, iovcnt++) {
+#if !defined(_WIN32)
+ send_iovec[iovcnt].iov_base = (caddr_t)m->m_data;
+ send_iovec[iovcnt].iov_len = SCTP_BUF_LEN(m);
+#else
+ send_iovec[iovcnt].buf = (caddr_t)m->m_data;
+ send_iovec[iovcnt].len = SCTP_BUF_LEN(m);
+#endif
+ }
+
+ if (m != NULL) {
+ SCTP_PRINTF("mbuf chain couldn't be copied completely\n");
+ goto free_mbuf;
+ }
+
+#if !defined(_WIN32)
+ msg_hdr.msg_name = (struct sockaddr *) &dst;
+ msg_hdr.msg_namelen = sizeof(struct sockaddr_in);
+ msg_hdr.msg_iov = send_iovec;
+ msg_hdr.msg_iovlen = iovcnt;
+ msg_hdr.msg_control = NULL;
+ msg_hdr.msg_controllen = 0;
+ msg_hdr.msg_flags = 0;
+
+ if ((!use_udp_tunneling) && (SCTP_BASE_VAR(userspace_rawsctp) != -1)) {
+ if (sendmsg(SCTP_BASE_VAR(userspace_rawsctp), &msg_hdr, MSG_DONTWAIT) < 0) {
+ *result = errno;
+ }
+ }
+ if ((use_udp_tunneling) && (SCTP_BASE_VAR(userspace_udpsctp) != -1)) {
+ if (sendmsg(SCTP_BASE_VAR(userspace_udpsctp), &msg_hdr, MSG_DONTWAIT) < 0) {
+ *result = errno;
+ }
+ }
+#else
+ win_msg_hdr.name = (struct sockaddr *) &dst;
+ win_msg_hdr.namelen = sizeof(struct sockaddr_in);
+ win_msg_hdr.lpBuffers = (LPWSABUF)send_iovec;
+ win_msg_hdr.dwBufferCount = iovcnt;
+ winbuf.len = 0;
+ winbuf.buf = NULL;
+ win_msg_hdr.Control = winbuf;
+ win_msg_hdr.dwFlags = 0;
+
+ if ((!use_udp_tunneling) && (SCTP_BASE_VAR(userspace_rawsctp) != -1)) {
+ if (WSASendTo(SCTP_BASE_VAR(userspace_rawsctp), (LPWSABUF) send_iovec, iovcnt, &win_sent_len, win_msg_hdr.dwFlags, win_msg_hdr.name, (int) win_msg_hdr.namelen, NULL, NULL) != 0) {
+ *result = WSAGetLastError();
+ }
+ }
+ if ((use_udp_tunneling) && (SCTP_BASE_VAR(userspace_udpsctp) != -1)) {
+ if (WSASendTo(SCTP_BASE_VAR(userspace_udpsctp), (LPWSABUF) send_iovec, iovcnt, &win_sent_len, win_msg_hdr.dwFlags, win_msg_hdr.name, (int) win_msg_hdr.namelen, NULL, NULL) != 0) {
+ *result = WSAGetLastError();
+ }
+ }
+#endif
+free_mbuf:
+ sctp_m_freem(m_orig);
+}
+#endif
+
+#if defined(INET6)
+void sctp_userspace_ip6_output(int *result, struct mbuf *o_pak,
+ struct route_in6 *ro, void *inp,
+ uint32_t vrf_id)
+{
+ struct mbuf *m;
+ struct mbuf *m_orig;
+ int iovcnt;
+ int len;
+ struct ip6_hdr *ip6;
+ struct udphdr *udp;
+ struct sockaddr_in6 dst;
+#if defined(_WIN32)
+ WSAMSG win_msg_hdr;
+ DWORD win_sent_len;
+ WSABUF send_iovec[MAXLEN_MBUF_CHAIN];
+ WSABUF winbuf;
+#else
+ struct iovec send_iovec[MAXLEN_MBUF_CHAIN];
+ struct msghdr msg_hdr;
+#endif
+ int use_udp_tunneling;
+
+ *result = 0;
+
+ m = SCTP_HEADER_TO_CHAIN(o_pak);
+ m_orig = m;
+
+ len = sizeof(struct ip6_hdr);
+
+ if (SCTP_BUF_LEN(m) < len) {
+ if ((m = m_pullup(m, len)) == 0) {
+ SCTP_PRINTF("Can not get the IP header in the first mbuf.\n");
+ return;
+ }
+ }
+
+ ip6 = mtod(m, struct ip6_hdr *);
+ use_udp_tunneling = (ip6->ip6_nxt == IPPROTO_UDP);
+
+ if (use_udp_tunneling) {
+ len = sizeof(struct ip6_hdr) + sizeof(struct udphdr);
+ if (SCTP_BUF_LEN(m) < len) {
+ if ((m = m_pullup(m, len)) == 0) {
+ SCTP_PRINTF("Can not get the UDP/IP header in the first mbuf.\n");
+ return;
+ }
+ ip6 = mtod(m, struct ip6_hdr *);
+ }
+ udp = (struct udphdr *)(ip6 + 1);
+ } else {
+ udp = NULL;
+ }
+
+ if (!use_udp_tunneling) {
+ if (ip6->ip6_src.s6_addr == in6addr_any.s6_addr) {
+ /* TODO get addr of outgoing interface */
+ SCTP_PRINTF("Why did the SCTP implementation did not choose a source address?\n");
+ }
+ /* TODO need to worry about ro->ro_dst as in ip_output? */
+ }
+
+ memset((void *)&dst, 0, sizeof(struct sockaddr_in6));
+ dst.sin6_family = AF_INET6;
+ dst.sin6_addr = ip6->ip6_dst;
+#ifdef HAVE_SIN6_LEN
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+
+ if (use_udp_tunneling) {
+ dst.sin6_port = udp->uh_dport;
+ } else {
+ dst.sin6_port = 0;
+ }
+
+ /* tweak the mbuf chain */
+ if (use_udp_tunneling) {
+ m_adj(m, sizeof(struct ip6_hdr) + sizeof(struct udphdr));
+ } else {
+ m_adj(m, sizeof(struct ip6_hdr));
+ }
+
+ for (iovcnt = 0; m != NULL && iovcnt < MAXLEN_MBUF_CHAIN; m = m->m_next, iovcnt++) {
+#if !defined(_WIN32)
+ send_iovec[iovcnt].iov_base = (caddr_t)m->m_data;
+ send_iovec[iovcnt].iov_len = SCTP_BUF_LEN(m);
+#else
+ send_iovec[iovcnt].buf = (caddr_t)m->m_data;
+ send_iovec[iovcnt].len = SCTP_BUF_LEN(m);
+#endif
+ }
+ if (m != NULL) {
+ SCTP_PRINTF("mbuf chain couldn't be copied completely\n");
+ goto free_mbuf;
+ }
+
+#if !defined(_WIN32)
+ msg_hdr.msg_name = (struct sockaddr *) &dst;
+ msg_hdr.msg_namelen = sizeof(struct sockaddr_in6);
+ msg_hdr.msg_iov = send_iovec;
+ msg_hdr.msg_iovlen = iovcnt;
+ msg_hdr.msg_control = NULL;
+ msg_hdr.msg_controllen = 0;
+ msg_hdr.msg_flags = 0;
+
+ if ((!use_udp_tunneling) && (SCTP_BASE_VAR(userspace_rawsctp6) != -1)) {
+ if (sendmsg(SCTP_BASE_VAR(userspace_rawsctp6), &msg_hdr, MSG_DONTWAIT)< 0) {
+ *result = errno;
+ }
+ }
+ if ((use_udp_tunneling) && (SCTP_BASE_VAR(userspace_udpsctp6) != -1)) {
+ if (sendmsg(SCTP_BASE_VAR(userspace_udpsctp6), &msg_hdr, MSG_DONTWAIT) < 0) {
+ *result = errno;
+ }
+ }
+#else
+ win_msg_hdr.name = (struct sockaddr *) &dst;
+ win_msg_hdr.namelen = sizeof(struct sockaddr_in6);
+ win_msg_hdr.lpBuffers = (LPWSABUF)send_iovec;
+ win_msg_hdr.dwBufferCount = iovcnt;
+ winbuf.len = 0;
+ winbuf.buf = NULL;
+ win_msg_hdr.Control = winbuf;
+ win_msg_hdr.dwFlags = 0;
+
+ if ((!use_udp_tunneling) && (SCTP_BASE_VAR(userspace_rawsctp6) != -1)) {
+ if (WSASendTo(SCTP_BASE_VAR(userspace_rawsctp6), (LPWSABUF) send_iovec, iovcnt, &win_sent_len, win_msg_hdr.dwFlags, win_msg_hdr.name, (int) win_msg_hdr.namelen, NULL, NULL) != 0) {
+ *result = WSAGetLastError();
+ }
+ }
+ if ((use_udp_tunneling) && (SCTP_BASE_VAR(userspace_udpsctp6) != -1)) {
+ if (WSASendTo(SCTP_BASE_VAR(userspace_udpsctp6), (LPWSABUF) send_iovec, iovcnt, &win_sent_len, win_msg_hdr.dwFlags, win_msg_hdr.name, (int) win_msg_hdr.namelen, NULL, NULL) != 0) {
+ *result = WSAGetLastError();
+ }
+ }
+#endif
+free_mbuf:
+ sctp_m_freem(m_orig);
+}
+#endif
+
+void
+usrsctp_register_address(void *addr)
+{
+ struct sockaddr_conn sconn;
+
+ memset(&sconn, 0, sizeof(struct sockaddr_conn));
+ sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+ sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ sconn.sconn_port = 0;
+ sconn.sconn_addr = addr;
+ sctp_add_addr_to_vrf(SCTP_DEFAULT_VRFID,
+ NULL,
+ 0xffffffff,
+ 0,
+ "conn",
+ NULL,
+ (struct sockaddr *)&sconn,
+ 0,
+ 0);
+}
+
+void
+usrsctp_deregister_address(void *addr)
+{
+ struct sockaddr_conn sconn;
+
+ memset(&sconn, 0, sizeof(struct sockaddr_conn));
+ sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+ sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ sconn.sconn_port = 0;
+ sconn.sconn_addr = addr;
+ sctp_del_addr_from_vrf(SCTP_DEFAULT_VRFID,
+ (struct sockaddr *)&sconn,
+ 0xffffffff,
+ "conn");
+}
+
+#define PREAMBLE_FORMAT "\n%c %02d:%02d:%02d.%06ld "
+#define PREAMBLE_LENGTH 19
+#define HEADER "0000 "
+#define TRAILER "# SCTP_PACKET\n"
+
+char *
+usrsctp_dumppacket(const void *buf, size_t len, int outbound)
+{
+ size_t i, pos;
+ char *dump_buf, *packet;
+ struct tm t;
+#ifdef _WIN32
+ struct timeb tb;
+#else
+ struct timeval tv;
+ time_t sec;
+#endif
+
+ if ((len == 0) || (buf == NULL)) {
+ return (NULL);
+ }
+ if ((dump_buf = malloc(PREAMBLE_LENGTH + strlen(HEADER) + 3 * len + strlen(TRAILER) + 1)) == NULL) {
+ return (NULL);
+ }
+ pos = 0;
+#ifdef _WIN32
+ ftime(&tb);
+ localtime_s(&t, &tb.time);
+#if defined(__MINGW32__)
+ if (snprintf(dump_buf, PREAMBLE_LENGTH + 1, PREAMBLE_FORMAT,
+ outbound ? 'O' : 'I',
+ t.tm_hour, t.tm_min, t.tm_sec, (long)(1000 * tb.millitm)) < 0) {
+ free(dump_buf);
+ return (NULL);
+ }
+#else
+ if (_snprintf_s(dump_buf, PREAMBLE_LENGTH + 1, PREAMBLE_LENGTH, PREAMBLE_FORMAT,
+ outbound ? 'O' : 'I',
+ t.tm_hour, t.tm_min, t.tm_sec, (long)(1000 * tb.millitm)) < 0) {
+ free(dump_buf);
+ return (NULL);
+ }
+#endif
+#else
+ gettimeofday(&tv, NULL);
+ sec = (time_t)tv.tv_sec;
+ localtime_r((const time_t *)&sec, &t);
+ if (snprintf(dump_buf, PREAMBLE_LENGTH + 1, PREAMBLE_FORMAT,
+ outbound ? 'O' : 'I',
+ t.tm_hour, t.tm_min, t.tm_sec, (long)tv.tv_usec) < 0) {
+ free(dump_buf);
+ return (NULL);
+ }
+#endif
+ pos += PREAMBLE_LENGTH;
+#if defined(_WIN32) && !defined(__MINGW32__)
+ strncpy_s(dump_buf + pos, strlen(HEADER) + 1, HEADER, strlen(HEADER));
+#else
+ strcpy(dump_buf + pos, HEADER);
+#endif
+ pos += strlen(HEADER);
+ packet = (char *)buf;
+ for (i = 0; i < len; i++) {
+ uint8_t byte, low, high;
+
+ byte = (uint8_t)packet[i];
+ high = byte / 16;
+ low = byte % 16;
+ dump_buf[pos++] = high < 10 ? '0' + high : 'a' + (high - 10);
+ dump_buf[pos++] = low < 10 ? '0' + low : 'a' + (low - 10);
+ dump_buf[pos++] = ' ';
+ }
+#if defined(_WIN32) && !defined(__MINGW32__)
+ strncpy_s(dump_buf + pos, strlen(TRAILER) + 1, TRAILER, strlen(TRAILER));
+#else
+ strcpy(dump_buf + pos, TRAILER);
+#endif
+ pos += strlen(TRAILER);
+ dump_buf[pos++] = '\0';
+ return (dump_buf);
+}
+
+void
+usrsctp_freedumpbuffer(char *buf)
+{
+ free(buf);
+}
+
+void
+usrsctp_enable_crc32c_offload(void)
+{
+ SCTP_BASE_VAR(crc32c_offloaded) = 1;
+}
+
+void
+usrsctp_disable_crc32c_offload(void)
+{
+ SCTP_BASE_VAR(crc32c_offloaded) = 0;
+}
+
+/* Compute the CRC32C in network byte order */
+uint32_t
+usrsctp_crc32c(void *buffer, size_t length)
+{
+ uint32_t base = 0xffffffff;
+
+ base = calculate_crc32c(0xffffffff, (unsigned char *)buffer, (unsigned int) length);
+ base = sctp_finalize_crc32c(base);
+ return (base);
+}
+
+void
+usrsctp_conninput(void *addr, const void *buffer, size_t length, uint8_t ecn_bits)
+{
+ struct sockaddr_conn src, dst;
+ struct mbuf *m, *mm;
+ struct sctphdr *sh;
+ struct sctp_chunkhdr *ch;
+ int remaining, offset;
+
+ SCTP_STAT_INCR(sctps_recvpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+ memset(&src, 0, sizeof(struct sockaddr_conn));
+ src.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+ src.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ src.sconn_addr = addr;
+ memset(&dst, 0, sizeof(struct sockaddr_conn));
+ dst.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+ dst.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ dst.sconn_addr = addr;
+ if ((m = sctp_get_mbuf_for_msg((unsigned int)length, 1, M_NOWAIT, 0, MT_DATA)) == NULL) {
+ return;
+ }
+ /* Set the lengths fields of the mbuf chain.
+ * This is expected by m_copyback().
+ */
+ remaining = (int)length;
+ for (mm = m; mm != NULL; mm = mm->m_next) {
+ mm->m_len = min((int)M_SIZE(mm), remaining);
+ m->m_pkthdr.len += mm->m_len;
+ remaining -= mm->m_len;
+ }
+ KASSERT(remaining == 0, ("usrsctp_conninput: %zu bytes left", remaining));
+ m_copyback(m, 0, (int)length, (caddr_t)buffer);
+ offset = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+ if (SCTP_BUF_LEN(m) < offset) {
+ if ((m = m_pullup(m, offset)) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ return;
+ }
+ }
+ sh = mtod(m, struct sctphdr *);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset -= sizeof(struct sctp_chunkhdr);
+ src.sconn_port = sh->src_port;
+ dst.sconn_port = sh->dest_port;
+ sctp_common_input_processing(&m, 0, offset, (int)length,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+ SCTP_BASE_VAR(crc32c_offloaded) == 1 ? 0 : 1,
+ ecn_bits,
+ SCTP_DEFAULT_VRFID, 0);
+ if (m) {
+ sctp_m_freem(m);
+ }
+ return;
+}
+
+void usrsctp_handle_timers(uint32_t elapsed_milliseconds)
+{
+ sctp_handle_tick(sctp_msecs_to_ticks(elapsed_milliseconds));
+}
+
+int
+usrsctp_get_events(struct socket *so)
+{
+ int events = 0;
+
+ if (so == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+
+ SOCK_LOCK(so);
+ if (soreadable(so)) {
+ events |= SCTP_EVENT_READ;
+ }
+ if (sowriteable(so)) {
+ events |= SCTP_EVENT_WRITE;
+ }
+ if (so->so_error) {
+ events |= SCTP_EVENT_ERROR;
+ }
+ SOCK_UNLOCK(so);
+
+ return events;
+}
+
+int
+usrsctp_set_upcall(struct socket *so, void (*upcall)(struct socket *, void *, int), void *arg)
+{
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+
+ SOCK_LOCK(so);
+ so->so_upcall = upcall;
+ so->so_upcallarg = arg;
+ so->so_snd.sb_flags |= SB_UPCALL;
+ so->so_rcv.sb_flags |= SB_UPCALL;
+ SOCK_UNLOCK(so);
+
+ return (0);
+}
+
+#define USRSCTP_TUNABLE_SET_DEF(__field, __prefix) \
+int usrsctp_tunable_set_ ## __field(uint32_t value) \
+{ \
+ if ((value < __prefix##_MIN) || \
+ (value > __prefix##_MAX)) { \
+ errno = EINVAL; \
+ return (-1); \
+ } else { \
+ SCTP_BASE_SYSCTL(__field) = value; \
+ return (0); \
+ } \
+}
+
+USRSCTP_TUNABLE_SET_DEF(sctp_hashtblsize, SCTPCTL_TCBHASHSIZE)
+USRSCTP_TUNABLE_SET_DEF(sctp_pcbtblsize, SCTPCTL_PCBHASHSIZE)
+USRSCTP_TUNABLE_SET_DEF(sctp_chunkscale, SCTPCTL_CHUNKSCALE)
+
+#define USRSCTP_SYSCTL_SET_DEF(__field, __prefix) \
+int usrsctp_sysctl_set_ ## __field(uint32_t value) \
+{ \
+ if ((value < __prefix##_MIN) || \
+ (value > __prefix##_MAX)) { \
+ errno = EINVAL; \
+ return (-1); \
+ } else { \
+ SCTP_BASE_SYSCTL(__field) = value; \
+ return (0); \
+ } \
+}
+
+#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+USRSCTP_SYSCTL_SET_DEF(sctp_sendspace, SCTPCTL_MAXDGRAM)
+USRSCTP_SYSCTL_SET_DEF(sctp_recvspace, SCTPCTL_RECVSPACE)
+USRSCTP_SYSCTL_SET_DEF(sctp_auto_asconf, SCTPCTL_AUTOASCONF)
+USRSCTP_SYSCTL_SET_DEF(sctp_ecn_enable, SCTPCTL_ECN_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_pr_enable, SCTPCTL_PR_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_auth_enable, SCTPCTL_AUTH_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_asconf_enable, SCTPCTL_ASCONF_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_reconfig_enable, SCTPCTL_RECONFIG_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_nrsack_enable, SCTPCTL_NRSACK_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_pktdrop_enable, SCTPCTL_PKTDROP_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_no_csum_on_loopback, SCTPCTL_LOOPBACK_NOCSUM)
+USRSCTP_SYSCTL_SET_DEF(sctp_peer_chunk_oh, SCTPCTL_PEER_CHKOH)
+USRSCTP_SYSCTL_SET_DEF(sctp_max_burst_default, SCTPCTL_MAXBURST)
+USRSCTP_SYSCTL_SET_DEF(sctp_max_chunks_on_queue, SCTPCTL_MAXCHUNKS)
+USRSCTP_SYSCTL_SET_DEF(sctp_min_split_point, SCTPCTL_MIN_SPLIT_POINT)
+USRSCTP_SYSCTL_SET_DEF(sctp_delayed_sack_time_default, SCTPCTL_DELAYED_SACK_TIME)
+USRSCTP_SYSCTL_SET_DEF(sctp_sack_freq_default, SCTPCTL_SACK_FREQ)
+USRSCTP_SYSCTL_SET_DEF(sctp_system_free_resc_limit, SCTPCTL_SYS_RESOURCE)
+USRSCTP_SYSCTL_SET_DEF(sctp_asoc_free_resc_limit, SCTPCTL_ASOC_RESOURCE)
+USRSCTP_SYSCTL_SET_DEF(sctp_heartbeat_interval_default, SCTPCTL_HEARTBEAT_INTERVAL)
+USRSCTP_SYSCTL_SET_DEF(sctp_pmtu_raise_time_default, SCTPCTL_PMTU_RAISE_TIME)
+USRSCTP_SYSCTL_SET_DEF(sctp_shutdown_guard_time_default, SCTPCTL_SHUTDOWN_GUARD_TIME)
+USRSCTP_SYSCTL_SET_DEF(sctp_secret_lifetime_default, SCTPCTL_SECRET_LIFETIME)
+USRSCTP_SYSCTL_SET_DEF(sctp_rto_max_default, SCTPCTL_RTO_MAX)
+USRSCTP_SYSCTL_SET_DEF(sctp_rto_min_default, SCTPCTL_RTO_MIN)
+USRSCTP_SYSCTL_SET_DEF(sctp_rto_initial_default, SCTPCTL_RTO_INITIAL)
+USRSCTP_SYSCTL_SET_DEF(sctp_init_rto_max_default, SCTPCTL_INIT_RTO_MAX)
+USRSCTP_SYSCTL_SET_DEF(sctp_valid_cookie_life_default, SCTPCTL_VALID_COOKIE_LIFE)
+USRSCTP_SYSCTL_SET_DEF(sctp_init_rtx_max_default, SCTPCTL_INIT_RTX_MAX)
+USRSCTP_SYSCTL_SET_DEF(sctp_assoc_rtx_max_default, SCTPCTL_ASSOC_RTX_MAX)
+USRSCTP_SYSCTL_SET_DEF(sctp_path_rtx_max_default, SCTPCTL_PATH_RTX_MAX)
+USRSCTP_SYSCTL_SET_DEF(sctp_add_more_threshold, SCTPCTL_ADD_MORE_ON_OUTPUT)
+USRSCTP_SYSCTL_SET_DEF(sctp_nr_incoming_streams_default, SCTPCTL_INCOMING_STREAMS)
+USRSCTP_SYSCTL_SET_DEF(sctp_nr_outgoing_streams_default, SCTPCTL_OUTGOING_STREAMS)
+USRSCTP_SYSCTL_SET_DEF(sctp_cmt_on_off, SCTPCTL_CMT_ON_OFF)
+USRSCTP_SYSCTL_SET_DEF(sctp_cmt_use_dac, SCTPCTL_CMT_USE_DAC)
+USRSCTP_SYSCTL_SET_DEF(sctp_use_cwnd_based_maxburst, SCTPCTL_CWND_MAXBURST)
+USRSCTP_SYSCTL_SET_DEF(sctp_nat_friendly, SCTPCTL_NAT_FRIENDLY)
+USRSCTP_SYSCTL_SET_DEF(sctp_L2_abc_variable, SCTPCTL_ABC_L_VAR)
+USRSCTP_SYSCTL_SET_DEF(sctp_mbuf_threshold_count, SCTPCTL_MAX_CHAINED_MBUFS)
+USRSCTP_SYSCTL_SET_DEF(sctp_do_drain, SCTPCTL_DO_SCTP_DRAIN)
+USRSCTP_SYSCTL_SET_DEF(sctp_hb_maxburst, SCTPCTL_HB_MAX_BURST)
+USRSCTP_SYSCTL_SET_DEF(sctp_abort_if_one_2_one_hits_limit, SCTPCTL_ABORT_AT_LIMIT)
+USRSCTP_SYSCTL_SET_DEF(sctp_min_residual, SCTPCTL_MIN_RESIDUAL)
+USRSCTP_SYSCTL_SET_DEF(sctp_max_retran_chunk, SCTPCTL_MAX_RETRAN_CHUNK)
+USRSCTP_SYSCTL_SET_DEF(sctp_logging_level, SCTPCTL_LOGGING_LEVEL)
+USRSCTP_SYSCTL_SET_DEF(sctp_default_cc_module, SCTPCTL_DEFAULT_CC_MODULE)
+USRSCTP_SYSCTL_SET_DEF(sctp_default_frag_interleave, SCTPCTL_DEFAULT_FRAG_INTERLEAVE)
+USRSCTP_SYSCTL_SET_DEF(sctp_mobility_base, SCTPCTL_MOBILITY_BASE)
+USRSCTP_SYSCTL_SET_DEF(sctp_mobility_fasthandoff, SCTPCTL_MOBILITY_FASTHANDOFF)
+USRSCTP_SYSCTL_SET_DEF(sctp_inits_include_nat_friendly, SCTPCTL_NAT_FRIENDLY_INITS)
+USRSCTP_SYSCTL_SET_DEF(sctp_udp_tunneling_port, SCTPCTL_UDP_TUNNELING_PORT)
+USRSCTP_SYSCTL_SET_DEF(sctp_enable_sack_immediately, SCTPCTL_SACK_IMMEDIATELY_ENABLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_vtag_time_wait, SCTPCTL_TIME_WAIT)
+USRSCTP_SYSCTL_SET_DEF(sctp_blackhole, SCTPCTL_BLACKHOLE)
+USRSCTP_SYSCTL_SET_DEF(sctp_diag_info_code, SCTPCTL_DIAG_INFO_CODE)
+USRSCTP_SYSCTL_SET_DEF(sctp_fr_max_burst_default, SCTPCTL_FRMAXBURST)
+USRSCTP_SYSCTL_SET_DEF(sctp_path_pf_threshold, SCTPCTL_PATH_PF_THRESHOLD)
+USRSCTP_SYSCTL_SET_DEF(sctp_default_ss_module, SCTPCTL_DEFAULT_SS_MODULE)
+USRSCTP_SYSCTL_SET_DEF(sctp_rttvar_bw, SCTPCTL_RTTVAR_BW)
+USRSCTP_SYSCTL_SET_DEF(sctp_rttvar_rtt, SCTPCTL_RTTVAR_RTT)
+USRSCTP_SYSCTL_SET_DEF(sctp_rttvar_eqret, SCTPCTL_RTTVAR_EQRET)
+USRSCTP_SYSCTL_SET_DEF(sctp_steady_step, SCTPCTL_RTTVAR_STEADYS)
+USRSCTP_SYSCTL_SET_DEF(sctp_use_dccc_ecn, SCTPCTL_RTTVAR_DCCCECN)
+USRSCTP_SYSCTL_SET_DEF(sctp_buffer_splitting, SCTPCTL_BUFFER_SPLITTING)
+USRSCTP_SYSCTL_SET_DEF(sctp_initial_cwnd, SCTPCTL_INITIAL_CWND)
+#ifdef SCTP_DEBUG
+USRSCTP_SYSCTL_SET_DEF(sctp_debug_on, SCTPCTL_DEBUG)
+#endif
+#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#define USRSCTP_SYSCTL_GET_DEF(__field) \
+uint32_t usrsctp_sysctl_get_ ## __field(void) { \
+ return SCTP_BASE_SYSCTL(__field); \
+}
+
+USRSCTP_SYSCTL_GET_DEF(sctp_sendspace)
+USRSCTP_SYSCTL_GET_DEF(sctp_recvspace)
+USRSCTP_SYSCTL_GET_DEF(sctp_auto_asconf)
+USRSCTP_SYSCTL_GET_DEF(sctp_multiple_asconfs)
+USRSCTP_SYSCTL_GET_DEF(sctp_ecn_enable)
+USRSCTP_SYSCTL_GET_DEF(sctp_pr_enable)
+USRSCTP_SYSCTL_GET_DEF(sctp_auth_enable)
+USRSCTP_SYSCTL_GET_DEF(sctp_asconf_enable)
+USRSCTP_SYSCTL_GET_DEF(sctp_reconfig_enable)
+USRSCTP_SYSCTL_GET_DEF(sctp_nrsack_enable)
+USRSCTP_SYSCTL_GET_DEF(sctp_pktdrop_enable)
+USRSCTP_SYSCTL_GET_DEF(sctp_no_csum_on_loopback)
+USRSCTP_SYSCTL_GET_DEF(sctp_peer_chunk_oh)
+USRSCTP_SYSCTL_GET_DEF(sctp_max_burst_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_max_chunks_on_queue)
+USRSCTP_SYSCTL_GET_DEF(sctp_hashtblsize)
+USRSCTP_SYSCTL_GET_DEF(sctp_pcbtblsize)
+USRSCTP_SYSCTL_GET_DEF(sctp_min_split_point)
+USRSCTP_SYSCTL_GET_DEF(sctp_chunkscale)
+USRSCTP_SYSCTL_GET_DEF(sctp_delayed_sack_time_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_sack_freq_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_system_free_resc_limit)
+USRSCTP_SYSCTL_GET_DEF(sctp_asoc_free_resc_limit)
+USRSCTP_SYSCTL_GET_DEF(sctp_heartbeat_interval_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_pmtu_raise_time_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_shutdown_guard_time_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_secret_lifetime_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_rto_max_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_rto_min_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_rto_initial_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_init_rto_max_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_valid_cookie_life_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_init_rtx_max_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_assoc_rtx_max_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_path_rtx_max_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_add_more_threshold)
+USRSCTP_SYSCTL_GET_DEF(sctp_nr_incoming_streams_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_nr_outgoing_streams_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_cmt_on_off)
+USRSCTP_SYSCTL_GET_DEF(sctp_cmt_use_dac)
+USRSCTP_SYSCTL_GET_DEF(sctp_use_cwnd_based_maxburst)
+USRSCTP_SYSCTL_GET_DEF(sctp_nat_friendly)
+USRSCTP_SYSCTL_GET_DEF(sctp_L2_abc_variable)
+USRSCTP_SYSCTL_GET_DEF(sctp_mbuf_threshold_count)
+USRSCTP_SYSCTL_GET_DEF(sctp_do_drain)
+USRSCTP_SYSCTL_GET_DEF(sctp_hb_maxburst)
+USRSCTP_SYSCTL_GET_DEF(sctp_abort_if_one_2_one_hits_limit)
+USRSCTP_SYSCTL_GET_DEF(sctp_min_residual)
+USRSCTP_SYSCTL_GET_DEF(sctp_max_retran_chunk)
+USRSCTP_SYSCTL_GET_DEF(sctp_logging_level)
+USRSCTP_SYSCTL_GET_DEF(sctp_default_cc_module)
+USRSCTP_SYSCTL_GET_DEF(sctp_default_frag_interleave)
+USRSCTP_SYSCTL_GET_DEF(sctp_mobility_base)
+USRSCTP_SYSCTL_GET_DEF(sctp_mobility_fasthandoff)
+USRSCTP_SYSCTL_GET_DEF(sctp_inits_include_nat_friendly)
+USRSCTP_SYSCTL_GET_DEF(sctp_udp_tunneling_port)
+USRSCTP_SYSCTL_GET_DEF(sctp_enable_sack_immediately)
+USRSCTP_SYSCTL_GET_DEF(sctp_vtag_time_wait)
+USRSCTP_SYSCTL_GET_DEF(sctp_blackhole)
+USRSCTP_SYSCTL_GET_DEF(sctp_diag_info_code)
+USRSCTP_SYSCTL_GET_DEF(sctp_fr_max_burst_default)
+USRSCTP_SYSCTL_GET_DEF(sctp_path_pf_threshold)
+USRSCTP_SYSCTL_GET_DEF(sctp_default_ss_module)
+USRSCTP_SYSCTL_GET_DEF(sctp_rttvar_bw)
+USRSCTP_SYSCTL_GET_DEF(sctp_rttvar_rtt)
+USRSCTP_SYSCTL_GET_DEF(sctp_rttvar_eqret)
+USRSCTP_SYSCTL_GET_DEF(sctp_steady_step)
+USRSCTP_SYSCTL_GET_DEF(sctp_use_dccc_ecn)
+USRSCTP_SYSCTL_GET_DEF(sctp_buffer_splitting)
+USRSCTP_SYSCTL_GET_DEF(sctp_initial_cwnd)
+#ifdef SCTP_DEBUG
+USRSCTP_SYSCTL_GET_DEF(sctp_debug_on)
+#endif
+
+void usrsctp_get_stat(struct sctpstat *stat)
+{
+ *stat = SCTP_BASE_STATS;
+}
diff --git a/netwerk/sctp/src/user_socketvar.h b/netwerk/sctp/src/user_socketvar.h
new file mode 100644
index 0000000000..e8eccc7fb1
--- /dev/null
+++ b/netwerk/sctp/src/user_socketvar.h
@@ -0,0 +1,527 @@
+/*-
+ * Copyright (c) 1982, 1986, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/* __Userspace__ version of <sys/socketvar.h> goes here.*/
+
+#ifndef _USER_SOCKETVAR_H_
+#define _USER_SOCKETVAR_H_
+
+#if defined(__APPLE__)
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+/* #include <sys/selinfo.h> */ /*__Userspace__ alternative?*/ /* for struct selinfo */
+/* #include <sys/_lock.h> was 0 byte file */
+/* #include <sys/_mutex.h> was 0 byte file */
+/* #include <sys/_sx.h> */ /*__Userspace__ alternative?*/
+#if !defined(__DragonFly__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(_WIN32) && !defined(__native_client__)
+#include <sys/uio.h>
+#endif
+#define SOCK_MAXADDRLEN 255
+#if !defined(MSG_NOTIFICATION)
+#define MSG_NOTIFICATION 0x2000 /* SCTP notification */
+#endif
+#define SCTP_SO_LINGER 0x0001
+#define SCTP_SO_ACCEPTCONN 0x0002
+#define SS_CANTRCVMORE 0x020
+#define SS_CANTSENDMORE 0x010
+
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(_WIN32) || defined(__native_client__)
+#define UIO_MAXIOV 1024
+#define ERESTART (-1)
+#endif
+
+#if !defined(__APPLE__) && !defined(__NetBSD__) && !defined(__OpenBSD__)
+enum uio_rw { UIO_READ, UIO_WRITE };
+#endif
+
+#if !defined(__NetBSD__) && !defined(__OpenBSD__)
+/* Segment flag values. */
+enum uio_seg {
+ UIO_USERSPACE, /* from user data space */
+ UIO_SYSSPACE /* from system space */
+};
+#endif
+
+struct proc {
+ int stub; /* struct proc is a dummy for __Userspace__ */
+};
+
+MALLOC_DECLARE(M_ACCF);
+MALLOC_DECLARE(M_PCB);
+MALLOC_DECLARE(M_SONAME);
+
+/* __Userspace__ Are these all the fields we need?
+ * Removing struct thread *uio_td; owner field
+*/
+struct uio {
+ struct iovec *uio_iov; /* scatter/gather list */
+ int uio_iovcnt; /* length of scatter/gather list */
+ off_t uio_offset; /* offset in target object */
+ ssize_t uio_resid; /* remaining bytes to process */
+ enum uio_seg uio_segflg; /* address space */
+ enum uio_rw uio_rw; /* operation */
+};
+
+
+/* __Userspace__ */
+
+/*
+ * Kernel structure per socket.
+ * Contains send and receive buffer queues,
+ * handle on protocol and pointer to protocol
+ * private data and error information.
+ */
+#if defined(_WIN32)
+#define AF_ROUTE 17
+#if !defined(__MINGW32__)
+typedef __int32 pid_t;
+#endif
+typedef unsigned __int32 uid_t;
+enum sigType {
+ SIGNAL = 0,
+ BROADCAST = 1,
+ MAX_EVENTS = 2
+};
+#endif
+
+/*-
+ * Locking key to struct socket:
+ * (a) constant after allocation, no locking required.
+ * (b) locked by SOCK_LOCK(so).
+ * (c) locked by SOCKBUF_LOCK(&so->so_rcv).
+ * (d) locked by SOCKBUF_LOCK(&so->so_snd).
+ * (e) locked by ACCEPT_LOCK().
+ * (f) not locked since integer reads/writes are atomic.
+ * (g) used only as a sleep/wakeup address, no value.
+ * (h) locked by global mutex so_global_mtx.
+ */
+struct socket {
+ int so_count; /* (b) reference count */
+ short so_type; /* (a) generic type, see socket.h */
+ short so_options; /* from socket call, see socket.h */
+ short so_linger; /* time to linger while closing */
+ short so_state; /* (b) internal state flags SS_* */
+ int so_qstate; /* (e) internal state flags SQ_* */
+ void *so_pcb; /* protocol control block */
+ int so_dom;
+/*
+ * Variables for connection queuing.
+ * Socket where accepts occur is so_head in all subsidiary sockets.
+ * If so_head is 0, socket is not related to an accept.
+ * For head socket so_incomp queues partially completed connections,
+ * while so_comp is a queue of connections ready to be accepted.
+ * If a connection is aborted and it has so_head set, then
+ * it has to be pulled out of either so_incomp or so_comp.
+ * We allow connections to queue up based on current queue lengths
+ * and limit on number of queued connections for this socket.
+ */
+ struct socket *so_head; /* (e) back pointer to listen socket */
+ TAILQ_HEAD(, socket) so_incomp; /* (e) queue of partial unaccepted connections */
+ TAILQ_HEAD(, socket) so_comp; /* (e) queue of complete unaccepted connections */
+ TAILQ_ENTRY(socket) so_list; /* (e) list of unaccepted connections */
+ u_short so_qlen; /* (e) number of unaccepted connections */
+ u_short so_incqlen; /* (e) number of unaccepted incomplete
+ connections */
+ u_short so_qlimit; /* (e) max number queued connections */
+ short so_timeo; /* (g) connection timeout */
+ userland_cond_t timeo_cond; /* timeo_cond condition variable being used in wakeup */
+
+ u_short so_error; /* (f) error affecting connection */
+ struct sigio *so_sigio; /* [sg] information for async I/O or
+ out of band data (SIGURG) */
+ u_long so_oobmark; /* (c) chars to oob mark */
+ TAILQ_HEAD(, aiocblist) so_aiojobq; /* AIO ops waiting on socket */
+/*
+ * Variables for socket buffering.
+ */
+ struct sockbuf {
+ /* __Userspace__ Many of these fields may
+ * not be required for the sctp stack.
+ * Commenting out the following.
+ * Including pthread mutex and condition variable to be
+ * used by sbwait, sorwakeup and sowwakeup.
+ */
+ /* struct selinfo sb_sel;*/ /* process selecting read/write */
+ /* struct mtx sb_mtx;*/ /* sockbuf lock */
+ /* struct sx sb_sx;*/ /* prevent I/O interlacing */
+ userland_cond_t sb_cond; /* sockbuf condition variable */
+ userland_mutex_t sb_mtx; /* sockbuf lock associated with sb_cond */
+ short sb_state; /* (c/d) socket state on sockbuf */
+#define sb_startzero sb_mb
+ struct mbuf *sb_mb; /* (c/d) the mbuf chain */
+ struct mbuf *sb_mbtail; /* (c/d) the last mbuf in the chain */
+ struct mbuf *sb_lastrecord; /* (c/d) first mbuf of last
+ * record in socket buffer */
+ struct mbuf *sb_sndptr; /* (c/d) pointer into mbuf chain */
+ u_int sb_sndptroff; /* (c/d) byte offset of ptr into chain */
+ u_int sb_cc; /* (c/d) actual chars in buffer */
+ u_int sb_hiwat; /* (c/d) max actual char count */
+ u_int sb_mbcnt; /* (c/d) chars of mbufs used */
+ u_int sb_mbmax; /* (c/d) max chars of mbufs to use */
+ u_int sb_ctl; /* (c/d) non-data chars in buffer */
+ int sb_lowat; /* (c/d) low water mark */
+ int sb_timeo; /* (c/d) timeout for read/write */
+ short sb_flags; /* (c/d) flags, see below */
+ } so_rcv, so_snd;
+/*
+ * Constants for sb_flags field of struct sockbuf.
+ */
+#define SB_MAX (256*1024) /* default for max chars in sockbuf */
+#define SB_RAW (64*1024*2) /*Aligning so->so_rcv.sb_hiwat with the receive buffer size of raw socket*/
+/*
+ * Constants for sb_flags field of struct sockbuf.
+ */
+#define SB_WAIT 0x04 /* someone is waiting for data/space */
+#define SB_SEL 0x08 /* someone is selecting */
+#define SB_ASYNC 0x10 /* ASYNC I/O, need signals */
+#define SB_UPCALL 0x20 /* someone wants an upcall */
+#define SB_NOINTR 0x40 /* operations not interruptible */
+#define SB_AIO 0x80 /* AIO operations queued */
+#define SB_KNOTE 0x100 /* kernel note attached */
+#define SB_AUTOSIZE 0x800 /* automatically size socket buffer */
+
+ void (*so_upcall)(struct socket *, void *, int);
+ void *so_upcallarg;
+ struct ucred *so_cred; /* (a) user credentials */
+ struct label *so_label; /* (b) MAC label for socket */
+ struct label *so_peerlabel; /* (b) cached MAC label for peer */
+ /* NB: generation count must not be first. */
+ uint32_t so_gencnt; /* (h) generation count */
+ void *so_emuldata; /* (b) private data for emulators */
+ struct so_accf {
+ struct accept_filter *so_accept_filter;
+ void *so_accept_filter_arg; /* saved filter args */
+ char *so_accept_filter_str; /* saved user args */
+ } *so_accf;
+};
+
+#define SB_EMPTY_FIXUP(sb) do { \
+ if ((sb)->sb_mb == NULL) { \
+ (sb)->sb_mbtail = NULL; \
+ (sb)->sb_lastrecord = NULL; \
+ } \
+} while (/*CONSTCOND*/0)
+
+/*
+ * Global accept mutex to serialize access to accept queues and
+ * fields associated with multiple sockets. This allows us to
+ * avoid defining a lock order between listen and accept sockets
+ * until such time as it proves to be a good idea.
+ */
+#if defined(_WIN32)
+extern userland_mutex_t accept_mtx;
+extern userland_cond_t accept_cond;
+#define ACCEPT_LOCK_ASSERT()
+#define ACCEPT_LOCK() do { \
+ EnterCriticalSection(&accept_mtx); \
+} while (0)
+#define ACCEPT_UNLOCK() do { \
+ LeaveCriticalSection(&accept_mtx); \
+} while (0)
+#define ACCEPT_UNLOCK_ASSERT()
+#else
+extern userland_mutex_t accept_mtx;
+
+extern userland_cond_t accept_cond;
+#ifdef INVARIANTS
+#define ACCEPT_LOCK() KASSERT(pthread_mutex_lock(&accept_mtx) == 0, ("%s: accept_mtx already locked", __func__))
+#define ACCEPT_UNLOCK() KASSERT(pthread_mutex_unlock(&accept_mtx) == 0, ("%s: accept_mtx not locked", __func__))
+#else
+#define ACCEPT_LOCK() (void)pthread_mutex_lock(&accept_mtx)
+#define ACCEPT_UNLOCK() (void)pthread_mutex_unlock(&accept_mtx)
+#endif
+#define ACCEPT_LOCK_ASSERT() \
+ KASSERT(pthread_mutex_trylock(&accept_mtx) == EBUSY, ("%s: accept_mtx not locked", __func__))
+#define ACCEPT_UNLOCK_ASSERT() do { \
+ KASSERT(pthread_mutex_trylock(&accept_mtx) == 0, ("%s: accept_mtx locked", __func__)); \
+ (void)pthread_mutex_unlock(&accept_mtx); \
+ } while (0)
+#endif
+
+/*
+ * Per-socket buffer mutex used to protect most fields in the socket
+ * buffer.
+ */
+#define SOCKBUF_MTX(_sb) (&(_sb)->sb_mtx)
+#if defined(_WIN32)
+#define SOCKBUF_LOCK_INIT(_sb, _name) \
+ InitializeCriticalSection(SOCKBUF_MTX(_sb))
+#define SOCKBUF_LOCK_DESTROY(_sb) DeleteCriticalSection(SOCKBUF_MTX(_sb))
+#define SOCKBUF_COND_INIT(_sb) InitializeConditionVariable((&(_sb)->sb_cond))
+#define SOCKBUF_COND_DESTROY(_sb) DeleteConditionVariable((&(_sb)->sb_cond))
+#define SOCK_COND_INIT(_so) InitializeConditionVariable((&(_so)->timeo_cond))
+#define SOCK_COND_DESTROY(_so) DeleteConditionVariable((&(_so)->timeo_cond))
+#define SOCK_COND(_so) (&(_so)->timeo_cond)
+#else
+#ifdef INVARIANTS
+#define SOCKBUF_LOCK_INIT(_sb, _name) do { \
+ pthread_mutexattr_t mutex_attr; \
+ \
+ pthread_mutexattr_init(&mutex_attr); \
+ pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_ERRORCHECK); \
+ pthread_mutex_init(SOCKBUF_MTX(_sb), &mutex_attr); \
+ pthread_mutexattr_destroy(&mutex_attr); \
+} while (0)
+#else
+#define SOCKBUF_LOCK_INIT(_sb, _name) \
+ pthread_mutex_init(SOCKBUF_MTX(_sb), NULL)
+#endif
+#define SOCKBUF_LOCK_DESTROY(_sb) pthread_mutex_destroy(SOCKBUF_MTX(_sb))
+#define SOCKBUF_COND_INIT(_sb) pthread_cond_init((&(_sb)->sb_cond), NULL)
+#define SOCKBUF_COND_DESTROY(_sb) pthread_cond_destroy((&(_sb)->sb_cond))
+#define SOCK_COND_INIT(_so) pthread_cond_init((&(_so)->timeo_cond), NULL)
+#define SOCK_COND_DESTROY(_so) pthread_cond_destroy((&(_so)->timeo_cond))
+#define SOCK_COND(_so) (&(_so)->timeo_cond)
+#endif
+/*__Userspace__ SOCKBUF_LOCK(_sb) is now defined in netinet/sctp_process_lock.h */
+
+/* #define SOCKBUF_OWNED(_sb) mtx_owned(SOCKBUF_MTX(_sb)) unused */
+/*__Userspace__ SOCKBUF_UNLOCK(_sb) is now defined in netinet/sctp_process_lock.h */
+
+/*__Userspace__ SOCKBUF_LOCK_ASSERT(_sb) is now defined in netinet/sctp_process_lock.h */
+
+/* #define SOCKBUF_UNLOCK_ASSERT(_sb) mtx_assert(SOCKBUF_MTX(_sb), MA_NOTOWNED) unused */
+
+/*
+ * Per-socket mutex: we reuse the receive socket buffer mutex for space
+ * efficiency. This decision should probably be revisited as we optimize
+ * locking for the socket code.
+ */
+#define SOCK_MTX(_so) SOCKBUF_MTX(&(_so)->so_rcv)
+/*__Userspace__ SOCK_LOCK(_so) is now defined in netinet/sctp_process_lock.h */
+
+/* #define SOCK_OWNED(_so) SOCKBUF_OWNED(&(_so)->so_rcv) unused */
+/*__Userspace__ SOCK_UNLOCK(_so) is now defined in netinet/sctp_process_lock.h */
+
+#define SOCK_LOCK_ASSERT(_so) SOCKBUF_LOCK_ASSERT(&(_so)->so_rcv)
+
+/*
+ * Socket state bits.
+ *
+ * Historically, this bits were all kept in the so_state field. For
+ * locking reasons, they are now in multiple fields, as they are
+ * locked differently. so_state maintains basic socket state protected
+ * by the socket lock. so_qstate holds information about the socket
+ * accept queues. Each socket buffer also has a state field holding
+ * information relevant to that socket buffer (can't send, rcv). Many
+ * fields will be read without locks to improve performance and avoid
+ * lock order issues. However, this approach must be used with caution.
+ */
+#define SS_NOFDREF 0x0001 /* no file table ref any more */
+#define SS_ISCONNECTED 0x0002 /* socket connected to a peer */
+#define SS_ISCONNECTING 0x0004 /* in process of connecting to peer */
+#define SS_ISDISCONNECTING 0x0008 /* in process of disconnecting */
+#define SS_NBIO 0x0100 /* non-blocking ops */
+#define SS_ASYNC 0x0200 /* async i/o notify */
+#define SS_ISCONFIRMING 0x0400 /* deciding to accept connection req */
+#define SS_ISDISCONNECTED 0x2000 /* socket disconnected from peer */
+/*
+ * Protocols can mark a socket as SS_PROTOREF to indicate that, following
+ * pru_detach, they still want the socket to persist, and will free it
+ * themselves when they are done. Protocols should only ever call sofree()
+ * following setting this flag in pru_detach(), and never otherwise, as
+ * sofree() bypasses socket reference counting.
+ */
+#define SS_PROTOREF 0x4000 /* strong protocol reference */
+
+/*
+ * Socket state bits now stored in the socket buffer state field.
+ */
+#define SBS_CANTSENDMORE 0x0010 /* can't send more data to peer */
+#define SBS_CANTRCVMORE 0x0020 /* can't receive more data from peer */
+#define SBS_RCVATMARK 0x0040 /* at mark on input */
+
+/*
+ * Socket state bits stored in so_qstate.
+ */
+#define SQ_INCOMP 0x0800 /* unaccepted, incomplete connection */
+#define SQ_COMP 0x1000 /* unaccepted, complete connection */
+
+/*
+ * Socket event flags
+ */
+#define SCTP_EVENT_READ 0x0001 /* socket is readable */
+#define SCTP_EVENT_WRITE 0x0002 /* socket is writeable */
+#define SCTP_EVENT_ERROR 0x0004 /* socket has an error state */
+
+
+/*-------------------------------------------------------------*/
+/*-------------------------------------------------------------*/
+/* __Userspace__ */
+/*-------------------------------------------------------------*/
+/*-------------------------------------------------------------*/
+/* this new __Userspace__ section is to copy portions of the _KERNEL block
+ * above into, avoiding having to port the entire thing at once...
+ * For function prototypes, the full bodies are in user_socket.c .
+ */
+
+/* ---------------------------------------------------------- */
+/* --- function prototypes (implemented in user_socket.c) --- */
+/* ---------------------------------------------------------- */
+void soisconnecting(struct socket *so);
+void soisdisconnecting(struct socket *so);
+void soisconnected(struct socket *so);
+struct socket * sonewconn(struct socket *head, int connstatus);
+void socantrcvmore(struct socket *so);
+void socantsendmore(struct socket *so);
+void sofree(struct socket *so);
+
+
+
+/* -------------- */
+/* --- macros --- */
+/* -------------- */
+
+#define soref(so) do { \
+ SOCK_LOCK_ASSERT(so); \
+ ++(so)->so_count; \
+} while (0)
+
+#define sorele(so) do { \
+ ACCEPT_LOCK_ASSERT(); \
+ SOCK_LOCK_ASSERT(so); \
+ KASSERT((so)->so_count > 0, ("sorele")); \
+ if (--(so)->so_count == 0) \
+ sofree(so); \
+ else { \
+ SOCK_UNLOCK(so); \
+ ACCEPT_UNLOCK(); \
+ } \
+} while (0)
+
+
+/* replacing imin with min (user_environment.h) */
+#define sbspace(sb) \
+ ((long) min((int)((sb)->sb_hiwat - (sb)->sb_cc), \
+ (int)((sb)->sb_mbmax - (sb)->sb_mbcnt)))
+
+/* do we have to send all at once on a socket? */
+#define sosendallatonce(so) \
+ ((so)->so_proto->pr_flags & PR_ATOMIC)
+
+/* can we read something from so? */
+#define soreadable(so) \
+ ((int)((so)->so_rcv.sb_cc) >= (so)->so_rcv.sb_lowat || \
+ ((so)->so_rcv.sb_state & SBS_CANTRCVMORE) || \
+ !TAILQ_EMPTY(&(so)->so_comp) || (so)->so_error)
+
+#if 0 /* original */
+#define PR_CONNREQUIRED 0x04 /* from sys/protosw.h "needed" for sowriteable */
+#define sowriteable(so) \
+ ((sbspace(&(so)->so_snd) >= (so)->so_snd.sb_lowat && \
+ (((so)->so_state&SS_ISCONNECTED) || \
+ ((so)->so_proto->pr_flags&PR_CONNREQUIRED)==0)) || \
+ ((so)->so_snd.sb_state & SBS_CANTSENDMORE) || \
+ (so)->so_error)
+#else /* line with PR_CONNREQUIRED removed */
+/* can we write something to so? */
+#define sowriteable(so) \
+ ((sbspace(&(so)->so_snd) >= (so)->so_snd.sb_lowat && \
+ (((so)->so_state&SS_ISCONNECTED))) || \
+ ((so)->so_snd.sb_state & SBS_CANTSENDMORE) || \
+ (so)->so_error)
+#endif
+
+extern void solisten_proto(struct socket *so, int backlog);
+extern int solisten_proto_check(struct socket *so);
+extern int sctp_listen(struct socket *so, int backlog, struct proc *p);
+extern void socantrcvmore_locked(struct socket *so);
+extern int sctp_bind(struct socket *so, struct sockaddr *addr);
+extern int sctp6_bind(struct socket *so, struct sockaddr *addr, void *proc);
+extern int sctpconn_bind(struct socket *so, struct sockaddr *addr);
+extern int sctp_accept(struct socket *so, struct sockaddr **addr);
+extern int sctp_attach(struct socket *so, int proto, uint32_t vrf_id);
+extern int sctp6_attach(struct socket *so, int proto, uint32_t vrf_id);
+extern int sctp_abort(struct socket *so);
+extern int sctp6_abort(struct socket *so);
+extern void sctp_close(struct socket *so);
+extern int soaccept(struct socket *so, struct sockaddr **nam);
+extern int solisten(struct socket *so, int backlog);
+extern int soreserve(struct socket *so, u_long sndcc, u_long rcvcc);
+extern void sowakeup(struct socket *so, struct sockbuf *sb);
+extern void wakeup(void *ident, struct socket *so); /*__Userspace__ */
+extern int uiomove(void *cp, int n, struct uio *uio);
+extern int sbwait(struct sockbuf *sb);
+extern int sodisconnect(struct socket *so);
+extern int soconnect(struct socket *so, struct sockaddr *nam);
+extern int sctp_disconnect(struct socket *so);
+extern int sctp_connect(struct socket *so, struct sockaddr *addr);
+extern int sctp6_connect(struct socket *so, struct sockaddr *addr);
+extern int sctpconn_connect(struct socket *so, struct sockaddr *addr);
+extern void sctp_finish(void);
+
+/* ------------------------------------------------ */
+/* ----- macros copied from above ---- */
+/* ------------------------------------------------ */
+
+/*
+ * Do we need to notify the other side when I/O is possible?
+ */
+#define sb_notify(sb) (((sb)->sb_flags & (SB_WAIT | SB_SEL | SB_ASYNC | \
+ SB_UPCALL | SB_AIO | SB_KNOTE)) != 0)
+
+
+/*
+ * In sorwakeup() and sowwakeup(), acquire the socket buffer lock to
+ * avoid a non-atomic test-and-wakeup. However, sowakeup is
+ * responsible for releasing the lock if it is called. We unlock only
+ * if we don't call into sowakeup. If any code is introduced that
+ * directly invokes the underlying sowakeup() primitives, it must
+ * maintain the same semantics.
+ */
+#define sorwakeup_locked(so) do { \
+ SOCKBUF_LOCK_ASSERT(&(so)->so_rcv); \
+ if (sb_notify(&(so)->so_rcv)) \
+ sowakeup((so), &(so)->so_rcv); \
+ else \
+ SOCKBUF_UNLOCK(&(so)->so_rcv); \
+} while (0)
+
+#define sorwakeup(so) do { \
+ SOCKBUF_LOCK(&(so)->so_rcv); \
+ sorwakeup_locked(so); \
+} while (0)
+
+#define sowwakeup_locked(so) do { \
+ SOCKBUF_LOCK_ASSERT(&(so)->so_snd); \
+ if (sb_notify(&(so)->so_snd)) \
+ sowakeup((so), &(so)->so_snd); \
+ else \
+ SOCKBUF_UNLOCK(&(so)->so_snd); \
+} while (0)
+
+#define sowwakeup(so) do { \
+ SOCKBUF_LOCK(&(so)->so_snd); \
+ sowwakeup_locked(so); \
+} while (0)
+
+#endif /* !_SYS_SOCKETVAR_H_ */
diff --git a/netwerk/sctp/src/user_uma.h b/netwerk/sctp/src/user_uma.h
new file mode 100644
index 0000000000..59d71fff42
--- /dev/null
+++ b/netwerk/sctp/src/user_uma.h
@@ -0,0 +1,96 @@
+/*-
+ * Copyright (c) 2009-2010 Brad Penoff
+ * Copyright (c) 2009-2010 Humaira Kamal
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USER_UMA_H_
+#define _USER_UMA_H_
+
+#define UMA_ZFLAG_FULL 0x40000000 /* Reached uz_maxpages */
+#define UMA_ALIGN_PTR (sizeof(void *) - 1) /* Alignment fit for ptr */
+
+/* __Userspace__ All these definitions will change for
+userspace Universal Memory Allocator (UMA). These are included
+for reference purposes and to avoid compile errors for the time being.
+*/
+typedef int (*uma_ctor)(void *mem, int size, void *arg, int flags);
+typedef void (*uma_dtor)(void *mem, int size, void *arg);
+typedef int (*uma_init)(void *mem, int size, int flags);
+typedef void (*uma_fini)(void *mem, int size);
+typedef struct uma_zone * uma_zone_t;
+typedef struct uma_keg * uma_keg_t;
+
+struct uma_cache {
+ int stub; /* TODO __Userspace__ */
+};
+
+struct uma_keg {
+ int stub; /* TODO __Userspace__ */
+};
+
+struct uma_zone {
+ char *uz_name; /* Text name of the zone */
+ struct mtx *uz_lock; /* Lock for the zone (keg's lock) */
+ uma_keg_t uz_keg; /* Our underlying Keg */
+
+ LIST_ENTRY(uma_zone) uz_link; /* List of all zones in keg */
+ LIST_HEAD(,uma_bucket) uz_full_bucket; /* full buckets */
+ LIST_HEAD(,uma_bucket) uz_free_bucket; /* Buckets for frees */
+
+ uma_ctor uz_ctor; /* Constructor for each allocation */
+ uma_dtor uz_dtor; /* Destructor */
+ uma_init uz_init; /* Initializer for each item */
+ uma_fini uz_fini; /* Discards memory */
+
+ u_int64_t uz_allocs; /* Total number of allocations */
+ u_int64_t uz_frees; /* Total number of frees */
+ u_int64_t uz_fails; /* Total number of alloc failures */
+ uint16_t uz_fills; /* Outstanding bucket fills */
+ uint16_t uz_count; /* Highest value ub_ptr can have */
+
+ /*
+ * This HAS to be the last item because we adjust the zone size
+ * based on NCPU and then allocate the space for the zones.
+ */
+ struct uma_cache uz_cpu[1]; /* Per cpu caches */
+};
+
+/* Prototype */
+uma_zone_t
+uma_zcreate(char *name, size_t size, uma_ctor ctor, uma_dtor dtor,
+ uma_init uminit, uma_fini fini, int align, uint32_t flags);
+
+
+#define uma_zone_set_max(zone, number) /* stub TODO __Userspace__ */
+
+uma_zone_t
+uma_zcreate(char *name, size_t size, uma_ctor ctor, uma_dtor dtor,
+ uma_init uminit, uma_fini fini, int align, uint32_t flags) {
+ return NULL; /* stub TODO __Userspace__. Also place implementation in a separate .c file */
+}
+#endif
diff --git a/netwerk/sctp/src/usrsctp.h b/netwerk/sctp/src/usrsctp.h
new file mode 100644
index 0000000000..93e89b386c
--- /dev/null
+++ b/netwerk/sctp/src/usrsctp.h
@@ -0,0 +1,1329 @@
+/*-
+ * Copyright (c) 2009-2010 Brad Penoff
+ * Copyright (c) 2009-2010 Humaira Kamal
+ * Copyright (c) 2011-2012 Irene Ruengeler
+ * Copyright (c) 2011-2012 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __USRSCTP_H__
+#define __USRSCTP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <errno.h>
+#include <sys/types.h>
+#ifdef _WIN32
+#ifdef _MSC_VER
+#pragma warning(disable: 4200)
+#endif
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#ifndef MSG_NOTIFICATION
+/* This definition MUST be in sync with usrsctplib/user_socketvar.h */
+#define MSG_NOTIFICATION 0x2000
+#endif
+
+#ifndef IPPROTO_SCTP
+/* This is the IANA assigned protocol number of SCTP. */
+#define IPPROTO_SCTP 132
+#endif
+
+#ifdef _WIN32
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+#include <stdint.h>
+#elif defined(SCTP_STDINT_INCLUDE)
+#include SCTP_STDINT_INCLUDE
+#else
+typedef unsigned __int8 uint8_t;
+typedef unsigned __int16 uint16_t;
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+typedef __int16 int16_t;
+typedef __int32 int32_t;
+#endif
+
+#ifndef ssize_t
+#ifdef _WIN64
+typedef __int64 ssize_t;
+#elif defined _WIN32
+typedef int ssize_t;
+#else
+#error "Unknown platform!"
+#endif
+#endif
+
+#define MSG_EOR 0x8
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#endif
+#ifndef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#endif
+#define SHUT_RD 1
+#define SHUT_WR 2
+#define SHUT_RDWR 3
+#endif
+
+typedef uint32_t sctp_assoc_t;
+
+#if defined(_WIN32) && defined(_MSC_VER)
+#pragma pack (push, 1)
+#define SCTP_PACKED
+#else
+#define SCTP_PACKED __attribute__((packed))
+#endif
+
+struct sctp_common_header {
+ uint16_t source_port;
+ uint16_t destination_port;
+ uint32_t verification_tag;
+ uint32_t crc32c;
+} SCTP_PACKED;
+
+#if defined(_WIN32) && defined(_MSC_VER)
+#pragma pack(pop)
+#endif
+#undef SCTP_PACKED
+
+#define AF_CONN 123
+/* The definition of struct sockaddr_conn MUST be in
+ * tune with other sockaddr_* structures.
+ */
+#if defined(__APPLE__) || defined(__Bitrig__) || defined(__DragonFly__) || \
+ defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+struct sockaddr_conn {
+ uint8_t sconn_len;
+ uint8_t sconn_family;
+ uint16_t sconn_port;
+ void *sconn_addr;
+};
+#else
+struct sockaddr_conn {
+ uint16_t sconn_family;
+ uint16_t sconn_port;
+ void *sconn_addr;
+};
+#endif
+
+union sctp_sockstore {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_conn sconn;
+ struct sockaddr sa;
+};
+
+#define SCTP_FUTURE_ASSOC 0
+#define SCTP_CURRENT_ASSOC 1
+#define SCTP_ALL_ASSOC 2
+
+#define SCTP_EVENT_READ 0x0001
+#define SCTP_EVENT_WRITE 0x0002
+#define SCTP_EVENT_ERROR 0x0004
+
+/*** Structures and definitions to use the socket API ***/
+
+#define SCTP_ALIGN_RESV_PAD 92
+#define SCTP_ALIGN_RESV_PAD_SHORT 76
+
+struct sctp_rcvinfo {
+ uint16_t rcv_sid;
+ uint16_t rcv_ssn;
+ uint16_t rcv_flags;
+ uint32_t rcv_ppid;
+ uint32_t rcv_tsn;
+ uint32_t rcv_cumtsn;
+ uint32_t rcv_context;
+ sctp_assoc_t rcv_assoc_id;
+};
+
+struct sctp_nxtinfo {
+ uint16_t nxt_sid;
+ uint16_t nxt_flags;
+ uint32_t nxt_ppid;
+ uint32_t nxt_length;
+ sctp_assoc_t nxt_assoc_id;
+};
+
+#define SCTP_NO_NEXT_MSG 0x0000
+#define SCTP_NEXT_MSG_AVAIL 0x0001
+#define SCTP_NEXT_MSG_ISCOMPLETE 0x0002
+#define SCTP_NEXT_MSG_IS_UNORDERED 0x0004
+#define SCTP_NEXT_MSG_IS_NOTIFICATION 0x0008
+
+struct sctp_recvv_rn {
+ struct sctp_rcvinfo recvv_rcvinfo;
+ struct sctp_nxtinfo recvv_nxtinfo;
+};
+
+#define SCTP_RECVV_NOINFO 0
+#define SCTP_RECVV_RCVINFO 1
+#define SCTP_RECVV_NXTINFO 2
+#define SCTP_RECVV_RN 3
+
+#define SCTP_SENDV_NOINFO 0
+#define SCTP_SENDV_SNDINFO 1
+#define SCTP_SENDV_PRINFO 2
+#define SCTP_SENDV_AUTHINFO 3
+#define SCTP_SENDV_SPA 4
+
+#define SCTP_SEND_SNDINFO_VALID 0x00000001
+#define SCTP_SEND_PRINFO_VALID 0x00000002
+#define SCTP_SEND_AUTHINFO_VALID 0x00000004
+
+struct sctp_snd_all_completes {
+ uint16_t sall_stream;
+ uint16_t sall_flags;
+ uint32_t sall_ppid;
+ uint32_t sall_context;
+ uint32_t sall_num_sent;
+ uint32_t sall_num_failed;
+};
+
+struct sctp_sndinfo {
+ uint16_t snd_sid;
+ uint16_t snd_flags;
+ uint32_t snd_ppid;
+ uint32_t snd_context;
+ sctp_assoc_t snd_assoc_id;
+};
+
+struct sctp_prinfo {
+ uint16_t pr_policy;
+ uint32_t pr_value;
+};
+
+struct sctp_authinfo {
+ uint16_t auth_keynumber;
+};
+
+struct sctp_sendv_spa {
+ uint32_t sendv_flags;
+ struct sctp_sndinfo sendv_sndinfo;
+ struct sctp_prinfo sendv_prinfo;
+ struct sctp_authinfo sendv_authinfo;
+};
+
+struct sctp_udpencaps {
+ struct sockaddr_storage sue_address;
+ uint32_t sue_assoc_id;
+ uint16_t sue_port;
+};
+
+/******** Notifications **************/
+
+/* notification types */
+#define SCTP_ASSOC_CHANGE 0x0001
+#define SCTP_PEER_ADDR_CHANGE 0x0002
+#define SCTP_REMOTE_ERROR 0x0003
+#define SCTP_SEND_FAILED 0x0004
+#define SCTP_SHUTDOWN_EVENT 0x0005
+#define SCTP_ADAPTATION_INDICATION 0x0006
+#define SCTP_PARTIAL_DELIVERY_EVENT 0x0007
+#define SCTP_AUTHENTICATION_EVENT 0x0008
+#define SCTP_STREAM_RESET_EVENT 0x0009
+#define SCTP_SENDER_DRY_EVENT 0x000a
+#define SCTP_NOTIFICATIONS_STOPPED_EVENT 0x000b
+#define SCTP_ASSOC_RESET_EVENT 0x000c
+#define SCTP_STREAM_CHANGE_EVENT 0x000d
+#define SCTP_SEND_FAILED_EVENT 0x000e
+
+/* notification event structures */
+
+
+/* association change event */
+struct sctp_assoc_change {
+ uint16_t sac_type;
+ uint16_t sac_flags;
+ uint32_t sac_length;
+ uint16_t sac_state;
+ uint16_t sac_error;
+ uint16_t sac_outbound_streams;
+ uint16_t sac_inbound_streams;
+ sctp_assoc_t sac_assoc_id;
+ uint8_t sac_info[]; /* not available yet */
+};
+
+/* sac_state values */
+#define SCTP_COMM_UP 0x0001
+#define SCTP_COMM_LOST 0x0002
+#define SCTP_RESTART 0x0003
+#define SCTP_SHUTDOWN_COMP 0x0004
+#define SCTP_CANT_STR_ASSOC 0x0005
+
+/* sac_info values */
+#define SCTP_ASSOC_SUPPORTS_PR 0x01
+#define SCTP_ASSOC_SUPPORTS_AUTH 0x02
+#define SCTP_ASSOC_SUPPORTS_ASCONF 0x03
+#define SCTP_ASSOC_SUPPORTS_MULTIBUF 0x04
+#define SCTP_ASSOC_SUPPORTS_RE_CONFIG 0x05
+#define SCTP_ASSOC_SUPPORTS_INTERLEAVING 0x06
+#define SCTP_ASSOC_SUPPORTS_MAX 0x06
+
+/* Address event */
+struct sctp_paddr_change {
+ uint16_t spc_type;
+ uint16_t spc_flags;
+ uint32_t spc_length;
+ struct sockaddr_storage spc_aaddr;
+ uint32_t spc_state;
+ uint32_t spc_error;
+ sctp_assoc_t spc_assoc_id;
+ uint8_t spc_padding[4];
+};
+
+/* paddr state values */
+#define SCTP_ADDR_AVAILABLE 0x0001
+#define SCTP_ADDR_UNREACHABLE 0x0002
+#define SCTP_ADDR_REMOVED 0x0003
+#define SCTP_ADDR_ADDED 0x0004
+#define SCTP_ADDR_MADE_PRIM 0x0005
+#define SCTP_ADDR_CONFIRMED 0x0006
+
+/* remote error events */
+struct sctp_remote_error {
+ uint16_t sre_type;
+ uint16_t sre_flags;
+ uint32_t sre_length;
+ uint16_t sre_error;
+ sctp_assoc_t sre_assoc_id;
+ uint8_t sre_data[];
+};
+
+/* shutdown event */
+struct sctp_shutdown_event {
+ uint16_t sse_type;
+ uint16_t sse_flags;
+ uint32_t sse_length;
+ sctp_assoc_t sse_assoc_id;
+};
+
+/* Adaptation layer indication */
+struct sctp_adaptation_event {
+ uint16_t sai_type;
+ uint16_t sai_flags;
+ uint32_t sai_length;
+ uint32_t sai_adaptation_ind;
+ sctp_assoc_t sai_assoc_id;
+};
+
+/* Partial delivery event */
+struct sctp_pdapi_event {
+ uint16_t pdapi_type;
+ uint16_t pdapi_flags;
+ uint32_t pdapi_length;
+ uint32_t pdapi_indication;
+ uint32_t pdapi_stream;
+ uint32_t pdapi_seq;
+ sctp_assoc_t pdapi_assoc_id;
+};
+
+/* indication values */
+#define SCTP_PARTIAL_DELIVERY_ABORTED 0x0001
+
+/* SCTP authentication event */
+struct sctp_authkey_event {
+ uint16_t auth_type;
+ uint16_t auth_flags;
+ uint32_t auth_length;
+ uint16_t auth_keynumber;
+ uint32_t auth_indication;
+ sctp_assoc_t auth_assoc_id;
+};
+
+/* indication values */
+#define SCTP_AUTH_NEW_KEY 0x0001
+#define SCTP_AUTH_NO_AUTH 0x0002
+#define SCTP_AUTH_FREE_KEY 0x0003
+
+/* SCTP sender dry event */
+struct sctp_sender_dry_event {
+ uint16_t sender_dry_type;
+ uint16_t sender_dry_flags;
+ uint32_t sender_dry_length;
+ sctp_assoc_t sender_dry_assoc_id;
+};
+
+
+/* Stream reset event - subscribe to SCTP_STREAM_RESET_EVENT */
+struct sctp_stream_reset_event {
+ uint16_t strreset_type;
+ uint16_t strreset_flags;
+ uint32_t strreset_length;
+ sctp_assoc_t strreset_assoc_id;
+ uint16_t strreset_stream_list[];
+};
+
+/* flags in stream_reset_event (strreset_flags) */
+#define SCTP_STREAM_RESET_INCOMING_SSN 0x0001
+#define SCTP_STREAM_RESET_OUTGOING_SSN 0x0002
+#define SCTP_STREAM_RESET_DENIED 0x0004 /* SCTP_STRRESET_FAILED */
+#define SCTP_STREAM_RESET_FAILED 0x0008 /* SCTP_STRRESET_FAILED */
+#define SCTP_STREAM_CHANGED_DENIED 0x0010
+
+#define SCTP_STREAM_RESET_INCOMING 0x00000001
+#define SCTP_STREAM_RESET_OUTGOING 0x00000002
+
+
+/* Assoc reset event - subscribe to SCTP_ASSOC_RESET_EVENT */
+struct sctp_assoc_reset_event {
+ uint16_t assocreset_type;
+ uint16_t assocreset_flags;
+ uint32_t assocreset_length;
+ sctp_assoc_t assocreset_assoc_id;
+ uint32_t assocreset_local_tsn;
+ uint32_t assocreset_remote_tsn;
+};
+
+#define SCTP_ASSOC_RESET_DENIED 0x0004
+#define SCTP_ASSOC_RESET_FAILED 0x0008
+
+
+/* Stream change event - subscribe to SCTP_STREAM_CHANGE_EVENT */
+struct sctp_stream_change_event {
+ uint16_t strchange_type;
+ uint16_t strchange_flags;
+ uint32_t strchange_length;
+ sctp_assoc_t strchange_assoc_id;
+ uint16_t strchange_instrms;
+ uint16_t strchange_outstrms;
+};
+
+#define SCTP_STREAM_CHANGE_DENIED 0x0004
+#define SCTP_STREAM_CHANGE_FAILED 0x0008
+
+
+/* SCTP send failed event */
+struct sctp_send_failed_event {
+ uint16_t ssfe_type;
+ uint16_t ssfe_flags;
+ uint32_t ssfe_length;
+ uint32_t ssfe_error;
+ struct sctp_sndinfo ssfe_info;
+ sctp_assoc_t ssfe_assoc_id;
+ uint8_t ssfe_data[];
+};
+
+/* flag that indicates state of data */
+#define SCTP_DATA_UNSENT 0x0001 /* inqueue never on wire */
+#define SCTP_DATA_SENT 0x0002 /* on wire at failure */
+
+/* SCTP event option */
+struct sctp_event {
+ sctp_assoc_t se_assoc_id;
+ uint16_t se_type;
+ uint8_t se_on;
+};
+
+union sctp_notification {
+ struct sctp_tlv {
+ uint16_t sn_type;
+ uint16_t sn_flags;
+ uint32_t sn_length;
+ } sn_header;
+ struct sctp_assoc_change sn_assoc_change;
+ struct sctp_paddr_change sn_paddr_change;
+ struct sctp_remote_error sn_remote_error;
+ struct sctp_shutdown_event sn_shutdown_event;
+ struct sctp_adaptation_event sn_adaptation_event;
+ struct sctp_pdapi_event sn_pdapi_event;
+ struct sctp_authkey_event sn_auth_event;
+ struct sctp_sender_dry_event sn_sender_dry_event;
+ struct sctp_send_failed_event sn_send_failed_event;
+ struct sctp_stream_reset_event sn_strreset_event;
+ struct sctp_assoc_reset_event sn_assocreset_event;
+ struct sctp_stream_change_event sn_strchange_event;
+};
+
+struct sctp_event_subscribe {
+ uint8_t sctp_data_io_event;
+ uint8_t sctp_association_event;
+ uint8_t sctp_address_event;
+ uint8_t sctp_send_failure_event;
+ uint8_t sctp_peer_error_event;
+ uint8_t sctp_shutdown_event;
+ uint8_t sctp_partial_delivery_event;
+ uint8_t sctp_adaptation_layer_event;
+ uint8_t sctp_authentication_event;
+ uint8_t sctp_sender_dry_event;
+ uint8_t sctp_stream_reset_event;
+};
+
+
+
+/* Flags that go into the sinfo->sinfo_flags field */
+#define SCTP_DATA_LAST_FRAG 0x0001 /* tail part of the message could not be sent */
+#define SCTP_DATA_NOT_FRAG 0x0003 /* complete message could not be sent */
+#define SCTP_NOTIFICATION 0x0010 /* next message is a notification */
+#define SCTP_COMPLETE 0x0020 /* next message is complete */
+#define SCTP_EOF 0x0100 /* Start shutdown procedures */
+#define SCTP_ABORT 0x0200 /* Send an ABORT to peer */
+#define SCTP_UNORDERED 0x0400 /* Message is un-ordered */
+#define SCTP_ADDR_OVER 0x0800 /* Override the primary-address */
+#define SCTP_SENDALL 0x1000 /* Send this on all associations */
+#define SCTP_EOR 0x2000 /* end of message signal */
+#define SCTP_SACK_IMMEDIATELY 0x4000 /* Set I-Bit */
+
+#define INVALID_SINFO_FLAG(x) (((x) & 0xfffffff0 \
+ & ~(SCTP_EOF | SCTP_ABORT | SCTP_UNORDERED |\
+ SCTP_ADDR_OVER | SCTP_SENDALL | SCTP_EOR |\
+ SCTP_SACK_IMMEDIATELY)) != 0)
+/* for the endpoint */
+
+/* The lower byte is an enumeration of PR-SCTP policies */
+#define SCTP_PR_SCTP_NONE 0x0000 /* Reliable transfer */
+#define SCTP_PR_SCTP_TTL 0x0001 /* Time based PR-SCTP */
+#define SCTP_PR_SCTP_BUF 0x0002 /* Buffer based PR-SCTP */
+#define SCTP_PR_SCTP_RTX 0x0003 /* Number of retransmissions based PR-SCTP */
+
+#define PR_SCTP_POLICY(x) ((x) & 0x0f)
+#define PR_SCTP_ENABLED(x) (PR_SCTP_POLICY(x) != SCTP_PR_SCTP_NONE)
+#define PR_SCTP_TTL_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_TTL)
+#define PR_SCTP_BUF_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_BUF)
+#define PR_SCTP_RTX_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_RTX)
+#define PR_SCTP_INVALID_POLICY(x) (PR_SCTP_POLICY(x) > SCTP_PR_SCTP_RTX)
+
+
+/*
+ * user socket options: socket API defined
+ */
+/*
+ * read-write options
+ */
+#define SCTP_RTOINFO 0x00000001
+#define SCTP_ASSOCINFO 0x00000002
+#define SCTP_INITMSG 0x00000003
+#define SCTP_NODELAY 0x00000004
+#define SCTP_AUTOCLOSE 0x00000005
+#define SCTP_PRIMARY_ADDR 0x00000007
+#define SCTP_ADAPTATION_LAYER 0x00000008
+#define SCTP_DISABLE_FRAGMENTS 0x00000009
+#define SCTP_PEER_ADDR_PARAMS 0x0000000a
+/* ancillary data/notification interest options */
+/* Without this applied we will give V4 and V6 addresses on a V6 socket */
+#define SCTP_I_WANT_MAPPED_V4_ADDR 0x0000000d
+#define SCTP_MAXSEG 0x0000000e
+#define SCTP_DELAYED_SACK 0x0000000f
+#define SCTP_FRAGMENT_INTERLEAVE 0x00000010
+#define SCTP_PARTIAL_DELIVERY_POINT 0x00000011
+/* authentication support */
+#define SCTP_HMAC_IDENT 0x00000014
+#define SCTP_AUTH_ACTIVE_KEY 0x00000015
+#define SCTP_AUTO_ASCONF 0x00000018
+#define SCTP_MAX_BURST 0x00000019
+/* assoc level context */
+#define SCTP_CONTEXT 0x0000001a
+/* explicit EOR signalling */
+#define SCTP_EXPLICIT_EOR 0x0000001b
+#define SCTP_REUSE_PORT 0x0000001c
+
+#define SCTP_EVENT 0x0000001e
+#define SCTP_RECVRCVINFO 0x0000001f
+#define SCTP_RECVNXTINFO 0x00000020
+#define SCTP_DEFAULT_SNDINFO 0x00000021
+#define SCTP_DEFAULT_PRINFO 0x00000022
+#define SCTP_REMOTE_UDP_ENCAPS_PORT 0x00000024
+#define SCTP_ECN_SUPPORTED 0x00000025
+#define SCTP_PR_SUPPORTED 0x00000026
+#define SCTP_AUTH_SUPPORTED 0x00000027
+#define SCTP_ASCONF_SUPPORTED 0x00000028
+#define SCTP_RECONFIG_SUPPORTED 0x00000029
+#define SCTP_NRSACK_SUPPORTED 0x00000030
+#define SCTP_PKTDROP_SUPPORTED 0x00000031
+#define SCTP_MAX_CWND 0x00000032
+#define SCTP_ACCEPT_ZERO_CHECKSUM 0x00000033
+
+#define SCTP_ENABLE_STREAM_RESET 0x00000900 /* struct sctp_assoc_value */
+
+/* Pluggable Stream Scheduling Socket option */
+#define SCTP_PLUGGABLE_SS 0x00001203
+#define SCTP_SS_VALUE 0x00001204
+
+/*
+ * read-only options
+ */
+#define SCTP_STATUS 0x00000100
+#define SCTP_GET_PEER_ADDR_INFO 0x00000101
+/* authentication support */
+#define SCTP_PEER_AUTH_CHUNKS 0x00000102
+#define SCTP_LOCAL_AUTH_CHUNKS 0x00000103
+#define SCTP_GET_ASSOC_NUMBER 0x00000104
+#define SCTP_GET_ASSOC_ID_LIST 0x00000105
+#define SCTP_TIMEOUTS 0x00000106
+#define SCTP_PR_STREAM_STATUS 0x00000107
+#define SCTP_PR_ASSOC_STATUS 0x00000108
+
+/*
+ * write-only options
+ */
+#define SCTP_SET_PEER_PRIMARY_ADDR 0x00000006
+#define SCTP_AUTH_CHUNK 0x00000012
+#define SCTP_AUTH_KEY 0x00000013
+#define SCTP_AUTH_DEACTIVATE_KEY 0x0000001d
+#define SCTP_AUTH_DELETE_KEY 0x00000016
+#define SCTP_RESET_STREAMS 0x00000901 /* struct sctp_reset_streams */
+#define SCTP_RESET_ASSOC 0x00000902 /* sctp_assoc_t */
+#define SCTP_ADD_STREAMS 0x00000903 /* struct sctp_add_streams */
+
+struct sctp_initmsg {
+ uint16_t sinit_num_ostreams;
+ uint16_t sinit_max_instreams;
+ uint16_t sinit_max_attempts;
+ uint16_t sinit_max_init_timeo;
+};
+
+struct sctp_rtoinfo {
+ sctp_assoc_t srto_assoc_id;
+ uint32_t srto_initial;
+ uint32_t srto_max;
+ uint32_t srto_min;
+};
+
+struct sctp_assocparams {
+ sctp_assoc_t sasoc_assoc_id;
+ uint32_t sasoc_peer_rwnd;
+ uint32_t sasoc_local_rwnd;
+ uint32_t sasoc_cookie_life;
+ uint16_t sasoc_asocmaxrxt;
+ uint16_t sasoc_number_peer_destinations;
+};
+
+struct sctp_setprim {
+ struct sockaddr_storage ssp_addr;
+ sctp_assoc_t ssp_assoc_id;
+ uint8_t ssp_padding[4];
+};
+
+struct sctp_setadaptation {
+ uint32_t ssb_adaptation_ind;
+};
+
+struct sctp_paddrparams {
+ struct sockaddr_storage spp_address;
+ sctp_assoc_t spp_assoc_id;
+ uint32_t spp_hbinterval;
+ uint32_t spp_pathmtu;
+ uint32_t spp_flags;
+ uint32_t spp_ipv6_flowlabel;
+ uint16_t spp_pathmaxrxt;
+ uint8_t spp_dscp;
+};
+
+#define SPP_HB_ENABLE 0x00000001
+#define SPP_HB_DISABLE 0x00000002
+#define SPP_HB_DEMAND 0x00000004
+#define SPP_PMTUD_ENABLE 0x00000008
+#define SPP_PMTUD_DISABLE 0x00000010
+#define SPP_HB_TIME_IS_ZERO 0x00000080
+#define SPP_IPV6_FLOWLABEL 0x00000100
+#define SPP_DSCP 0x00000200
+
+/* Used for SCTP_MAXSEG, SCTP_MAX_BURST, SCTP_ENABLE_STREAM_RESET, and SCTP_CONTEXT */
+struct sctp_assoc_value {
+ sctp_assoc_t assoc_id;
+ uint32_t assoc_value;
+};
+
+/* To enable stream reset */
+#define SCTP_ENABLE_RESET_STREAM_REQ 0x00000001
+#define SCTP_ENABLE_RESET_ASSOC_REQ 0x00000002
+#define SCTP_ENABLE_CHANGE_ASSOC_REQ 0x00000004
+#define SCTP_ENABLE_VALUE_MASK 0x00000007
+
+struct sctp_reset_streams {
+ sctp_assoc_t srs_assoc_id;
+ uint16_t srs_flags;
+ uint16_t srs_number_streams; /* 0 == ALL */
+ uint16_t srs_stream_list[]; /* list if strrst_num_streams is not 0 */
+};
+
+struct sctp_add_streams {
+ sctp_assoc_t sas_assoc_id;
+ uint16_t sas_instrms;
+ uint16_t sas_outstrms;
+};
+
+struct sctp_hmacalgo {
+ uint32_t shmac_number_of_idents;
+ uint16_t shmac_idents[];
+};
+
+/* AUTH hmac_id */
+#define SCTP_AUTH_HMAC_ID_RSVD 0x0000
+#define SCTP_AUTH_HMAC_ID_SHA1 0x0001 /* default, mandatory */
+#define SCTP_AUTH_HMAC_ID_SHA256 0x0003
+#define SCTP_AUTH_HMAC_ID_SHA224 0x0004
+#define SCTP_AUTH_HMAC_ID_SHA384 0x0005
+#define SCTP_AUTH_HMAC_ID_SHA512 0x0006
+
+
+struct sctp_sack_info {
+ sctp_assoc_t sack_assoc_id;
+ uint32_t sack_delay;
+ uint32_t sack_freq;
+};
+
+struct sctp_default_prinfo {
+ uint16_t pr_policy;
+ uint32_t pr_value;
+ sctp_assoc_t pr_assoc_id;
+};
+
+struct sctp_paddrinfo {
+ struct sockaddr_storage spinfo_address;
+ sctp_assoc_t spinfo_assoc_id;
+ int32_t spinfo_state;
+ uint32_t spinfo_cwnd;
+ uint32_t spinfo_srtt;
+ uint32_t spinfo_rto;
+ uint32_t spinfo_mtu;
+};
+
+struct sctp_status {
+ sctp_assoc_t sstat_assoc_id;
+ int32_t sstat_state;
+ uint32_t sstat_rwnd;
+ uint16_t sstat_unackdata;
+ uint16_t sstat_penddata;
+ uint16_t sstat_instrms;
+ uint16_t sstat_outstrms;
+ uint32_t sstat_fragmentation_point;
+ struct sctp_paddrinfo sstat_primary;
+};
+
+/*
+ * user state values
+ */
+#define SCTP_CLOSED 0x0000
+#define SCTP_BOUND 0x1000
+#define SCTP_LISTEN 0x2000
+#define SCTP_COOKIE_WAIT 0x0002
+#define SCTP_COOKIE_ECHOED 0x0004
+#define SCTP_ESTABLISHED 0x0008
+#define SCTP_SHUTDOWN_SENT 0x0010
+#define SCTP_SHUTDOWN_RECEIVED 0x0020
+#define SCTP_SHUTDOWN_ACK_SENT 0x0040
+#define SCTP_SHUTDOWN_PENDING 0x0080
+
+
+#define SCTP_ACTIVE 0x0001 /* SCTP_ADDR_REACHABLE */
+#define SCTP_INACTIVE 0x0002 /* neither SCTP_ADDR_REACHABLE
+ nor SCTP_ADDR_UNCONFIRMED */
+#define SCTP_UNCONFIRMED 0x0200 /* SCTP_ADDR_UNCONFIRMED */
+
+struct sctp_authchunks {
+ sctp_assoc_t gauth_assoc_id;
+/* uint32_t gauth_number_of_chunks; not available */
+ uint8_t gauth_chunks[];
+};
+
+struct sctp_assoc_ids {
+ uint32_t gaids_number_of_ids;
+ sctp_assoc_t gaids_assoc_id[];
+};
+
+struct sctp_setpeerprim {
+ struct sockaddr_storage sspp_addr;
+ sctp_assoc_t sspp_assoc_id;
+ uint8_t sspp_padding[4];
+};
+
+struct sctp_authchunk {
+ uint8_t sauth_chunk;
+};
+
+
+struct sctp_get_nonce_values {
+ sctp_assoc_t gn_assoc_id;
+ uint32_t gn_peers_tag;
+ uint32_t gn_local_tag;
+};
+
+/* Values for SCTP_ACCEPT_ZERO_CHECKSUM */
+#define SCTP_EDMID_NONE 0
+#define SCTP_EDMID_LOWER_LAYER_DTLS 1
+
+
+/*
+ * Main SCTP chunk types
+ */
+/************0x00 series ***********/
+#define SCTP_DATA 0x00
+#define SCTP_INITIATION 0x01
+#define SCTP_INITIATION_ACK 0x02
+#define SCTP_SELECTIVE_ACK 0x03
+#define SCTP_HEARTBEAT_REQUEST 0x04
+#define SCTP_HEARTBEAT_ACK 0x05
+#define SCTP_ABORT_ASSOCIATION 0x06
+#define SCTP_SHUTDOWN 0x07
+#define SCTP_SHUTDOWN_ACK 0x08
+#define SCTP_OPERATION_ERROR 0x09
+#define SCTP_COOKIE_ECHO 0x0a
+#define SCTP_COOKIE_ACK 0x0b
+#define SCTP_ECN_ECHO 0x0c
+#define SCTP_ECN_CWR 0x0d
+#define SCTP_SHUTDOWN_COMPLETE 0x0e
+/* RFC4895 */
+#define SCTP_AUTHENTICATION 0x0f
+/* EY nr_sack chunk id*/
+#define SCTP_NR_SELECTIVE_ACK 0x10
+/************0x40 series ***********/
+/************0x80 series ***********/
+/* RFC5061 */
+#define SCTP_ASCONF_ACK 0x80
+/* draft-ietf-stewart-pktdrpsctp */
+#define SCTP_PACKET_DROPPED 0x81
+/* draft-ietf-stewart-strreset-xxx */
+#define SCTP_STREAM_RESET 0x82
+
+/* RFC4820 */
+#define SCTP_PAD_CHUNK 0x84
+/************0xc0 series ***********/
+/* RFC3758 */
+#define SCTP_FORWARD_CUM_TSN 0xc0
+/* RFC5061 */
+#define SCTP_ASCONF 0xc1
+
+struct sctp_authkey {
+ sctp_assoc_t sca_assoc_id;
+ uint16_t sca_keynumber;
+ uint16_t sca_keylength;
+ uint8_t sca_key[];
+};
+
+struct sctp_authkeyid {
+ sctp_assoc_t scact_assoc_id;
+ uint16_t scact_keynumber;
+};
+
+struct sctp_cc_option {
+ int option;
+ struct sctp_assoc_value aid_value;
+};
+
+struct sctp_stream_value {
+ sctp_assoc_t assoc_id;
+ uint16_t stream_id;
+ uint16_t stream_value;
+};
+
+struct sctp_timeouts {
+ sctp_assoc_t stimo_assoc_id;
+ uint32_t stimo_init;
+ uint32_t stimo_data;
+ uint32_t stimo_sack;
+ uint32_t stimo_shutdown;
+ uint32_t stimo_heartbeat;
+ uint32_t stimo_cookie;
+ uint32_t stimo_shutdownack;
+};
+
+struct sctp_prstatus {
+ sctp_assoc_t sprstat_assoc_id;
+ uint16_t sprstat_sid;
+ uint16_t sprstat_policy;
+ uint64_t sprstat_abandoned_unsent;
+ uint64_t sprstat_abandoned_sent;
+};
+
+/* Standard TCP Congestion Control */
+#define SCTP_CC_RFC2581 0x00000000
+/* High Speed TCP Congestion Control (Floyd) */
+#define SCTP_CC_HSTCP 0x00000001
+/* HTCP Congestion Control */
+#define SCTP_CC_HTCP 0x00000002
+/* RTCC Congestion Control - RFC2581 plus */
+#define SCTP_CC_RTCC 0x00000003
+
+#define SCTP_CC_OPT_RTCC_SETMODE 0x00002000
+#define SCTP_CC_OPT_USE_DCCC_EC 0x00002001
+#define SCTP_CC_OPT_STEADY_STEP 0x00002002
+
+#define SCTP_CMT_OFF 0
+#define SCTP_CMT_BASE 1
+#define SCTP_CMT_RPV1 2
+#define SCTP_CMT_RPV2 3
+#define SCTP_CMT_MPTCP 4
+#define SCTP_CMT_MAX SCTP_CMT_MPTCP
+
+/* RS - Supported stream scheduling modules for pluggable
+ * stream scheduling
+ */
+/* Default simple round-robin */
+#define SCTP_SS_DEFAULT 0x00000000
+/* Real round-robin */
+#define SCTP_SS_ROUND_ROBIN 0x00000001
+/* Real round-robin per packet */
+#define SCTP_SS_ROUND_ROBIN_PACKET 0x00000002
+/* Priority */
+#define SCTP_SS_PRIORITY 0x00000003
+/* Fair Bandwidth */
+#define SCTP_SS_FAIR_BANDWITH 0x00000004
+/* First-come, first-serve */
+#define SCTP_SS_FIRST_COME 0x00000005
+
+/******************** System calls *************/
+
+struct socket;
+
+void
+usrsctp_init(uint16_t,
+ int (*)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df),
+ void (*)(const char *format, ...));
+
+void
+usrsctp_init_nothreads(uint16_t,
+ int (*)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df),
+ void (*)(const char *format, ...));
+
+struct socket *
+usrsctp_socket(int domain, int type, int protocol,
+ int (*receive_cb)(struct socket *sock, union sctp_sockstore addr, void *data,
+ size_t datalen, struct sctp_rcvinfo, int flags, void *ulp_info),
+ int (*send_cb)(struct socket *sock, uint32_t sb_free, void *ulp_info),
+ uint32_t sb_threshold,
+ void *ulp_info);
+
+int
+usrsctp_setsockopt(struct socket *so,
+ int level,
+ int option_name,
+ const void *option_value,
+ socklen_t option_len);
+
+int
+usrsctp_getsockopt(struct socket *so,
+ int level,
+ int option_name,
+ void *option_value,
+ socklen_t *option_len);
+
+int
+usrsctp_opt_info(struct socket *so,
+ sctp_assoc_t id,
+ int opt,
+ void *arg,
+ socklen_t *size);
+
+int
+usrsctp_getpaddrs(struct socket *so,
+ sctp_assoc_t id,
+ struct sockaddr **raddrs);
+
+void
+usrsctp_freepaddrs(struct sockaddr *addrs);
+
+int
+usrsctp_getladdrs(struct socket *so,
+ sctp_assoc_t id,
+ struct sockaddr **raddrs);
+
+void
+usrsctp_freeladdrs(struct sockaddr *addrs);
+
+ssize_t
+usrsctp_sendv(struct socket *so,
+ const void *data,
+ size_t len,
+ struct sockaddr *to,
+ int addrcnt,
+ void *info,
+ socklen_t infolen,
+ unsigned int infotype,
+ int flags);
+
+ssize_t
+usrsctp_recvv(struct socket *so,
+ void *dbuf,
+ size_t len,
+ struct sockaddr *from,
+ socklen_t * fromlen,
+ void *info,
+ socklen_t *infolen,
+ unsigned int *infotype,
+ int *msg_flags);
+
+int
+usrsctp_bind(struct socket *so,
+ struct sockaddr *name,
+ socklen_t namelen);
+
+#define SCTP_BINDX_ADD_ADDR 0x00008001
+#define SCTP_BINDX_REM_ADDR 0x00008002
+
+int
+usrsctp_bindx(struct socket *so,
+ struct sockaddr *addrs,
+ int addrcnt,
+ int flags);
+
+int
+usrsctp_listen(struct socket *so,
+ int backlog);
+
+struct socket *
+usrsctp_accept(struct socket *so,
+ struct sockaddr * aname,
+ socklen_t * anamelen);
+
+struct socket *
+usrsctp_peeloff(struct socket *, sctp_assoc_t);
+
+int
+usrsctp_connect(struct socket *so,
+ struct sockaddr *name,
+ socklen_t namelen);
+
+int
+usrsctp_connectx(struct socket *so,
+ const struct sockaddr *addrs, int addrcnt,
+ sctp_assoc_t *id);
+
+void
+usrsctp_close(struct socket *so);
+
+sctp_assoc_t
+usrsctp_getassocid(struct socket *, struct sockaddr *);
+
+int
+usrsctp_finish(void);
+
+int
+usrsctp_shutdown(struct socket *so, int how);
+
+void
+usrsctp_conninput(void *, const void *, size_t, uint8_t);
+
+int
+usrsctp_set_non_blocking(struct socket *, int);
+
+int
+usrsctp_get_non_blocking(struct socket *);
+
+void
+usrsctp_register_address(void *);
+
+void
+usrsctp_deregister_address(void *);
+
+int
+usrsctp_set_ulpinfo(struct socket *, void *);
+
+int
+usrsctp_get_ulpinfo(struct socket *, void **);
+
+int
+usrsctp_set_upcall(struct socket *so,
+ void (*upcall)(struct socket *, void *, int),
+ void *arg);
+
+int
+usrsctp_get_events(struct socket *so);
+
+
+void
+usrsctp_handle_timers(uint32_t elapsed_milliseconds);
+
+#define SCTP_DUMP_OUTBOUND 1
+#define SCTP_DUMP_INBOUND 0
+
+char *
+usrsctp_dumppacket(const void *, size_t, int);
+
+void
+usrsctp_freedumpbuffer(char *);
+
+void
+usrsctp_enable_crc32c_offload(void);
+
+void
+usrsctp_disable_crc32c_offload(void);
+
+uint32_t
+usrsctp_crc32c(void *, size_t);
+
+#define USRSCTP_TUNABLE_DECL(__field) \
+int usrsctp_tunable_set_ ## __field(uint32_t value);\
+uint32_t usrsctp_sysctl_get_ ## __field(void);
+
+USRSCTP_TUNABLE_DECL(sctp_hashtblsize)
+USRSCTP_TUNABLE_DECL(sctp_pcbtblsize)
+USRSCTP_TUNABLE_DECL(sctp_chunkscale)
+
+#define USRSCTP_SYSCTL_DECL(__field) \
+int usrsctp_sysctl_set_ ## __field(uint32_t value);\
+uint32_t usrsctp_sysctl_get_ ## __field(void);
+
+USRSCTP_SYSCTL_DECL(sctp_sendspace)
+USRSCTP_SYSCTL_DECL(sctp_recvspace)
+USRSCTP_SYSCTL_DECL(sctp_auto_asconf)
+USRSCTP_SYSCTL_DECL(sctp_multiple_asconfs)
+USRSCTP_SYSCTL_DECL(sctp_ecn_enable)
+USRSCTP_SYSCTL_DECL(sctp_pr_enable)
+USRSCTP_SYSCTL_DECL(sctp_auth_enable)
+USRSCTP_SYSCTL_DECL(sctp_asconf_enable)
+USRSCTP_SYSCTL_DECL(sctp_reconfig_enable)
+USRSCTP_SYSCTL_DECL(sctp_nrsack_enable)
+USRSCTP_SYSCTL_DECL(sctp_pktdrop_enable)
+USRSCTP_SYSCTL_DECL(sctp_no_csum_on_loopback)
+USRSCTP_SYSCTL_DECL(sctp_peer_chunk_oh)
+USRSCTP_SYSCTL_DECL(sctp_max_burst_default)
+USRSCTP_SYSCTL_DECL(sctp_max_chunks_on_queue)
+USRSCTP_SYSCTL_DECL(sctp_min_split_point)
+USRSCTP_SYSCTL_DECL(sctp_delayed_sack_time_default)
+USRSCTP_SYSCTL_DECL(sctp_sack_freq_default)
+USRSCTP_SYSCTL_DECL(sctp_system_free_resc_limit)
+USRSCTP_SYSCTL_DECL(sctp_asoc_free_resc_limit)
+USRSCTP_SYSCTL_DECL(sctp_heartbeat_interval_default)
+USRSCTP_SYSCTL_DECL(sctp_pmtu_raise_time_default)
+USRSCTP_SYSCTL_DECL(sctp_shutdown_guard_time_default)
+USRSCTP_SYSCTL_DECL(sctp_secret_lifetime_default)
+USRSCTP_SYSCTL_DECL(sctp_rto_max_default)
+USRSCTP_SYSCTL_DECL(sctp_rto_min_default)
+USRSCTP_SYSCTL_DECL(sctp_rto_initial_default)
+USRSCTP_SYSCTL_DECL(sctp_init_rto_max_default)
+USRSCTP_SYSCTL_DECL(sctp_valid_cookie_life_default)
+USRSCTP_SYSCTL_DECL(sctp_init_rtx_max_default)
+USRSCTP_SYSCTL_DECL(sctp_assoc_rtx_max_default)
+USRSCTP_SYSCTL_DECL(sctp_path_rtx_max_default)
+USRSCTP_SYSCTL_DECL(sctp_add_more_threshold)
+USRSCTP_SYSCTL_DECL(sctp_nr_incoming_streams_default)
+USRSCTP_SYSCTL_DECL(sctp_nr_outgoing_streams_default)
+USRSCTP_SYSCTL_DECL(sctp_cmt_on_off)
+USRSCTP_SYSCTL_DECL(sctp_cmt_use_dac)
+USRSCTP_SYSCTL_DECL(sctp_use_cwnd_based_maxburst)
+USRSCTP_SYSCTL_DECL(sctp_nat_friendly)
+USRSCTP_SYSCTL_DECL(sctp_L2_abc_variable)
+USRSCTP_SYSCTL_DECL(sctp_mbuf_threshold_count)
+USRSCTP_SYSCTL_DECL(sctp_do_drain)
+USRSCTP_SYSCTL_DECL(sctp_hb_maxburst)
+USRSCTP_SYSCTL_DECL(sctp_abort_if_one_2_one_hits_limit)
+USRSCTP_SYSCTL_DECL(sctp_min_residual)
+USRSCTP_SYSCTL_DECL(sctp_max_retran_chunk)
+USRSCTP_SYSCTL_DECL(sctp_logging_level)
+USRSCTP_SYSCTL_DECL(sctp_default_cc_module)
+USRSCTP_SYSCTL_DECL(sctp_default_frag_interleave)
+USRSCTP_SYSCTL_DECL(sctp_mobility_base)
+USRSCTP_SYSCTL_DECL(sctp_mobility_fasthandoff)
+USRSCTP_SYSCTL_DECL(sctp_inits_include_nat_friendly)
+USRSCTP_SYSCTL_DECL(sctp_udp_tunneling_port)
+USRSCTP_SYSCTL_DECL(sctp_enable_sack_immediately)
+USRSCTP_SYSCTL_DECL(sctp_vtag_time_wait)
+USRSCTP_SYSCTL_DECL(sctp_blackhole)
+USRSCTP_SYSCTL_DECL(sctp_sendall_limit)
+USRSCTP_SYSCTL_DECL(sctp_diag_info_code)
+USRSCTP_SYSCTL_DECL(sctp_fr_max_burst_default)
+USRSCTP_SYSCTL_DECL(sctp_path_pf_threshold)
+USRSCTP_SYSCTL_DECL(sctp_default_ss_module)
+USRSCTP_SYSCTL_DECL(sctp_rttvar_bw)
+USRSCTP_SYSCTL_DECL(sctp_rttvar_rtt)
+USRSCTP_SYSCTL_DECL(sctp_rttvar_eqret)
+USRSCTP_SYSCTL_DECL(sctp_steady_step)
+USRSCTP_SYSCTL_DECL(sctp_use_dccc_ecn)
+USRSCTP_SYSCTL_DECL(sctp_buffer_splitting)
+USRSCTP_SYSCTL_DECL(sctp_initial_cwnd)
+USRSCTP_SYSCTL_DECL(sctp_ootb_with_zero_cksum)
+#ifdef SCTP_DEBUG
+USRSCTP_SYSCTL_DECL(sctp_debug_on)
+/* More specific values can be found in sctp_constants, but
+ * are not considered to be part of the API.
+ */
+#define SCTP_DEBUG_NONE 0x00000000
+#define SCTP_DEBUG_ALL 0xffffffff
+#endif
+#undef USRSCTP_SYSCTL_DECL
+struct sctp_timeval {
+ uint32_t tv_sec;
+ uint32_t tv_usec;
+};
+
+struct sctpstat {
+ struct sctp_timeval sctps_discontinuitytime; /* sctpStats 18 (TimeStamp) */
+ /* MIB according to RFC 3873 */
+ uint32_t sctps_currestab; /* sctpStats 1 (Gauge32) */
+ uint32_t sctps_activeestab; /* sctpStats 2 (Counter32) */
+ uint32_t sctps_restartestab;
+ uint32_t sctps_collisionestab;
+ uint32_t sctps_passiveestab; /* sctpStats 3 (Counter32) */
+ uint32_t sctps_aborted; /* sctpStats 4 (Counter32) */
+ uint32_t sctps_shutdown; /* sctpStats 5 (Counter32) */
+ uint32_t sctps_outoftheblue; /* sctpStats 6 (Counter32) */
+ uint32_t sctps_checksumerrors; /* sctpStats 7 (Counter32) */
+ uint32_t sctps_outcontrolchunks; /* sctpStats 8 (Counter64) */
+ uint32_t sctps_outorderchunks; /* sctpStats 9 (Counter64) */
+ uint32_t sctps_outunorderchunks; /* sctpStats 10 (Counter64) */
+ uint32_t sctps_incontrolchunks; /* sctpStats 11 (Counter64) */
+ uint32_t sctps_inorderchunks; /* sctpStats 12 (Counter64) */
+ uint32_t sctps_inunorderchunks; /* sctpStats 13 (Counter64) */
+ uint32_t sctps_fragusrmsgs; /* sctpStats 14 (Counter64) */
+ uint32_t sctps_reasmusrmsgs; /* sctpStats 15 (Counter64) */
+ uint32_t sctps_outpackets; /* sctpStats 16 (Counter64) */
+ uint32_t sctps_inpackets; /* sctpStats 17 (Counter64) */
+
+ /* input statistics: */
+ uint32_t sctps_recvpackets; /* total input packets */
+ uint32_t sctps_recvdatagrams; /* total input datagrams */
+ uint32_t sctps_recvpktwithdata; /* total packets that had data */
+ uint32_t sctps_recvsacks; /* total input SACK chunks */
+ uint32_t sctps_recvdata; /* total input DATA chunks */
+ uint32_t sctps_recvdupdata; /* total input duplicate DATA chunks */
+ uint32_t sctps_recvheartbeat; /* total input HB chunks */
+ uint32_t sctps_recvheartbeatack; /* total input HB-ACK chunks */
+ uint32_t sctps_recvecne; /* total input ECNE chunks */
+ uint32_t sctps_recvauth; /* total input AUTH chunks */
+ uint32_t sctps_recvauthmissing; /* total input chunks missing AUTH */
+ uint32_t sctps_recvivalhmacid; /* total number of invalid HMAC ids received */
+ uint32_t sctps_recvivalkeyid; /* total number of invalid secret ids received */
+ uint32_t sctps_recvauthfailed; /* total number of auth failed */
+ uint32_t sctps_recvexpress; /* total fast path receives all one chunk */
+ uint32_t sctps_recvexpressm; /* total fast path multi-part data */
+ uint32_t sctps_recv_spare; /* formerly sctps_recvnocrc */
+ uint32_t sctps_recvswcrc;
+ uint32_t sctps_recvhwcrc;
+
+ /* output statistics: */
+ uint32_t sctps_sendpackets; /* total output packets */
+ uint32_t sctps_sendsacks; /* total output SACKs */
+ uint32_t sctps_senddata; /* total output DATA chunks */
+ uint32_t sctps_sendretransdata; /* total output retransmitted DATA chunks */
+ uint32_t sctps_sendfastretrans; /* total output fast retransmitted DATA chunks */
+ uint32_t sctps_sendmultfastretrans; /* total FR's that happened more than once
+ * to same chunk (u-del multi-fr algo).
+ */
+ uint32_t sctps_sendheartbeat; /* total output HB chunks */
+ uint32_t sctps_sendecne; /* total output ECNE chunks */
+ uint32_t sctps_sendauth; /* total output AUTH chunks FIXME */
+ uint32_t sctps_senderrors; /* ip_output error counter */
+ uint32_t sctps_send_spare; /* formerly sctps_sendnocrc */
+ uint32_t sctps_sendswcrc;
+ uint32_t sctps_sendhwcrc;
+ /* PCKDROPREP statistics: */
+ uint32_t sctps_pdrpfmbox; /* Packet drop from middle box */
+ uint32_t sctps_pdrpfehos; /* P-drop from end host */
+ uint32_t sctps_pdrpmbda; /* P-drops with data */
+ uint32_t sctps_pdrpmbct; /* P-drops, non-data, non-endhost */
+ uint32_t sctps_pdrpbwrpt; /* P-drop, non-endhost, bandwidth rep only */
+ uint32_t sctps_pdrpcrupt; /* P-drop, not enough for chunk header */
+ uint32_t sctps_pdrpnedat; /* P-drop, not enough data to confirm */
+ uint32_t sctps_pdrppdbrk; /* P-drop, where process_chunk_drop said break */
+ uint32_t sctps_pdrptsnnf; /* P-drop, could not find TSN */
+ uint32_t sctps_pdrpdnfnd; /* P-drop, attempt reverse TSN lookup */
+ uint32_t sctps_pdrpdiwnp; /* P-drop, e-host confirms zero-rwnd */
+ uint32_t sctps_pdrpdizrw; /* P-drop, midbox confirms no space */
+ uint32_t sctps_pdrpbadd; /* P-drop, data did not match TSN */
+ uint32_t sctps_pdrpmark; /* P-drop, TSN's marked for Fast Retran */
+ /* timeouts */
+ uint32_t sctps_timoiterator; /* Number of iterator timers that fired */
+ uint32_t sctps_timodata; /* Number of T3 data time outs */
+ uint32_t sctps_timowindowprobe; /* Number of window probe (T3) timers that fired */
+ uint32_t sctps_timoinit; /* Number of INIT timers that fired */
+ uint32_t sctps_timosack; /* Number of sack timers that fired */
+ uint32_t sctps_timoshutdown; /* Number of shutdown timers that fired */
+ uint32_t sctps_timoheartbeat; /* Number of heartbeat timers that fired */
+ uint32_t sctps_timocookie; /* Number of times a cookie timeout fired */
+ uint32_t sctps_timosecret; /* Number of times an endpoint changed its cookie secret*/
+ uint32_t sctps_timopathmtu; /* Number of PMTU timers that fired */
+ uint32_t sctps_timoshutdownack; /* Number of shutdown ack timers that fired */
+ uint32_t sctps_timoshutdownguard; /* Number of shutdown guard timers that fired */
+ uint32_t sctps_timostrmrst; /* Number of stream reset timers that fired */
+ uint32_t sctps_timoearlyfr; /* Number of early FR timers that fired */
+ uint32_t sctps_timoasconf; /* Number of times an asconf timer fired */
+ uint32_t sctps_timodelprim; /* Number of times a prim_deleted timer fired */
+ uint32_t sctps_timoautoclose; /* Number of times auto close timer fired */
+ uint32_t sctps_timoassockill; /* Number of asoc free timers expired */
+ uint32_t sctps_timoinpkill; /* Number of inp free timers expired */
+ /* former early FR counters */
+ uint32_t sctps_spare[11];
+ /* others */
+ uint32_t sctps_hdrops; /* packet shorter than header */
+ uint32_t sctps_badsum; /* checksum error */
+ uint32_t sctps_noport; /* no endpoint for port */
+ uint32_t sctps_badvtag; /* bad v-tag */
+ uint32_t sctps_badsid; /* bad SID */
+ uint32_t sctps_nomem; /* no memory */
+ uint32_t sctps_fastretransinrtt; /* number of multiple FR in a RTT window */
+ uint32_t sctps_markedretrans;
+ uint32_t sctps_naglesent; /* nagle allowed sending */
+ uint32_t sctps_naglequeued; /* nagle doesn't allow sending */
+ uint32_t sctps_maxburstqueued; /* max burst doesn't allow sending */
+ uint32_t sctps_ifnomemqueued; /* look ahead tells us no memory in
+ * interface ring buffer OR we had a
+ * send error and are queuing one send.
+ */
+ uint32_t sctps_windowprobed; /* total number of window probes sent */
+ uint32_t sctps_lowlevelerr; /* total times an output error causes us
+ * to clamp down on next user send.
+ */
+ uint32_t sctps_lowlevelerrusr; /* total times sctp_senderrors were caused from
+ * a user send from a user invoked send not
+ * a sack response
+ */
+ uint32_t sctps_datadropchklmt; /* Number of in data drops due to chunk limit reached */
+ uint32_t sctps_datadroprwnd; /* Number of in data drops due to rwnd limit reached */
+ uint32_t sctps_ecnereducedcwnd; /* Number of times a ECN reduced the cwnd */
+ uint32_t sctps_vtagexpress; /* Used express lookup via vtag */
+ uint32_t sctps_vtagbogus; /* Collision in express lookup. */
+ uint32_t sctps_primary_randry; /* Number of times the sender ran dry of user data on primary */
+ uint32_t sctps_cmt_randry; /* Same for above */
+ uint32_t sctps_slowpath_sack; /* Sacks the slow way */
+ uint32_t sctps_wu_sacks_sent; /* Window Update only sacks sent */
+ uint32_t sctps_sends_with_flags; /* number of sends with sinfo_flags !=0 */
+ uint32_t sctps_sends_with_unord; /* number of unordered sends */
+ uint32_t sctps_sends_with_eof; /* number of sends with EOF flag set */
+ uint32_t sctps_sends_with_abort; /* number of sends with ABORT flag set */
+ uint32_t sctps_protocol_drain_calls;/* number of times protocol drain called */
+ uint32_t sctps_protocol_drains_done;/* number of times we did a protocol drain */
+ uint32_t sctps_read_peeks; /* Number of times recv was called with peek */
+ uint32_t sctps_cached_chk; /* Number of cached chunks used */
+ uint32_t sctps_cached_strmoq; /* Number of cached stream oq's used */
+ uint32_t sctps_left_abandon; /* Number of unread messages abandoned by close */
+ uint32_t sctps_send_burst_avoid; /* Unused */
+ uint32_t sctps_send_cwnd_avoid; /* Send cwnd full avoidance, already max burst inflight to net */
+ uint32_t sctps_fwdtsn_map_over; /* number of map array over-runs via fwd-tsn's */
+ uint32_t sctps_queue_upd_ecne; /* Number of times we queued or updated an ECN chunk on send queue */
+ uint32_t sctps_recvzerocrc; /* Number of accepted packets with zero CRC */
+ uint32_t sctps_sendzerocrc; /* Number of packets sent with zero CRC */
+ uint32_t sctps_reserved[29]; /* Future ABI compat - remove int's from here when adding new */
+};
+
+void
+usrsctp_get_stat(struct sctpstat *);
+
+#ifdef _WIN32
+#ifdef _MSC_VER
+#pragma warning(default: 4200)
+#endif
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/netwerk/sctp/src/win32-free.patch b/netwerk/sctp/src/win32-free.patch
new file mode 100644
index 0000000000..566b774b46
--- /dev/null
+++ b/netwerk/sctp/src/win32-free.patch
@@ -0,0 +1,55 @@
+diff --git a/netinet/sctp_bsd_addr.c b/netinet/sctp_bsd_addr.c
+--- a/netinet/sctp_bsd_addr.c
++++ b/netinet/sctp_bsd_addr.c
+@@ -304,14 +304,7 @@ sctp_is_vmware_interface(struct ifnet *ifn)
+ #endif
+
+ #if defined(_WIN32) && defined(__Userspace__)
+-#ifdef MALLOC
+-#undef MALLOC
+-#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
+-#endif
+-#ifdef FREE
+-#undef FREE
+-#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
+-#endif
++#define SCTP_BSD_FREE(x) HeapFree(GetProcessHeap(), 0, (x))
+ static void
+ sctp_init_ifns_for_vrf(int vrfid)
+ {
+@@ -341,7 +334,7 @@ sctp_init_ifns_for_vrf(int vrfid)
+ /* Get actual adapter information */
+ if ((Err = GetAdaptersAddresses(AF_INET, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTP_PRINTF("GetAdaptersV4Addresses() failed with error code %d\n", Err);
+- FREE(pAdapterAddrs);
++ SCTP_BSD_FREE(pAdapterAddrs);
+ return;
+ }
+ /* Enumerate through each returned adapter and save its information */
+@@ -366,7 +359,7 @@ sctp_init_ifns_for_vrf(int vrfid)
+ }
+ }
+ }
+- FREE(pAdapterAddrs);
++ SCTP_BSD_FREE(pAdapterAddrs);
+ #endif
+ #ifdef INET6
+ AdapterAddrsSize = 0;
+@@ -386,7 +379,7 @@ sctp_init_ifns_for_vrf(int vrfid)
+ /* Get actual adapter information */
+ if ((Err = GetAdaptersAddresses(AF_INET6, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTP_PRINTF("GetAdaptersV6Addresses() failed with error code %d\n", Err);
+- FREE(pAdapterAddrs);
++ SCTP_BSD_FREE(pAdapterAddrs);
+ return;
+ }
+ /* Enumerate through each returned adapter and save its information */
+@@ -408,7 +401,7 @@ sctp_init_ifns_for_vrf(int vrfid)
+ }
+ }
+ }
+- FREE(pAdapterAddrs);
++ SCTP_BSD_FREE(pAdapterAddrs);
+ #endif
+ }
+ #elif defined(__Userspace__)
diff --git a/netwerk/sctp/src/win32-rands.patch b/netwerk/sctp/src/win32-rands.patch
new file mode 100644
index 0000000000..65bd2a08dc
--- /dev/null
+++ b/netwerk/sctp/src/win32-rands.patch
@@ -0,0 +1,36 @@
+diff --git a/netinet/sctp_asconf.c b/netinet/sctp_asconf.c
+--- a/netinet/sctp_asconf.c
++++ b/netinet/sctp_asconf.c
+@@ -32,6 +32,14 @@
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
++#if defined(_WIN32)
++// Needed for unified build so that rand_s is available to all unified
++// sources.
++#if !defined(_CRT_RAND_S) && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
++#define _CRT_RAND_S
++#endif
++#endif
++
+ #include <netinet/sctp_os.h>
+ #include <netinet/sctp_var.h>
+ #include <netinet/sctp_sysctl.h>
+diff --git a/user_environment.h b/user_environment.h
+--- a/user_environment.h
++++ b/user_environment.h
+@@ -30,6 +30,15 @@
+
+ #ifndef _USER_ENVIRONMENT_H_
+ #define _USER_ENVIRONMENT_H_
++
++#if defined(_WIN32)
++// Needed for unified build so that rand_s is available to all unified
++// sources.
++#if !defined(_CRT_RAND_S) && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
++#define _CRT_RAND_S
++#endif
++#endif
++
+ /* __Userspace__ */
+ #include <sys/types.h>
diff --git a/netwerk/socket/moz.build b/netwerk/socket/moz.build
new file mode 100644
index 0000000000..d8ce8d0b2b
--- /dev/null
+++ b/netwerk/socket/moz.build
@@ -0,0 +1,46 @@
+# -*- 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/.
+
+DIRS += [
+ "neqo_glue",
+]
+
+XPIDL_SOURCES += [
+ "nsISocketProvider.idl",
+ "nsISocketProviderService.idl",
+]
+
+XPIDL_MODULE = "necko_socket"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+EXPORTS += [
+ "nsSocketProviderService.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsSocketProviderService.cpp",
+ "nsSOCKSIOLayer.cpp",
+ "nsSOCKSSocketProvider.cpp",
+ "nsUDPSocketProvider.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ XPIDL_SOURCES += [
+ "nsINamedPipeService.idl",
+ ]
+ EXPORTS += [
+ "nsNamedPipeService.h",
+ ]
+ UNIFIED_SOURCES += ["nsNamedPipeIOLayer.cpp", "nsNamedPipeService.cpp"]
+
+FINAL_LIBRARY = "xul"
+
+CONFIGURE_SUBST_FILES += ["neqo/extra-bindgen-flags"]
diff --git a/netwerk/socket/neqo/extra-bindgen-flags.in b/netwerk/socket/neqo/extra-bindgen-flags.in
new file mode 100644
index 0000000000..69f7dabf26
--- /dev/null
+++ b/netwerk/socket/neqo/extra-bindgen-flags.in
@@ -0,0 +1 @@
+@BINDGEN_SYSTEM_FLAGS@ @NSPR_CFLAGS@ @NSS_CFLAGS@
diff --git a/netwerk/socket/neqo_glue/Cargo.toml b/netwerk/socket/neqo_glue/Cargo.toml
new file mode 100644
index 0000000000..0e506c8c2d
--- /dev/null
+++ b/netwerk/socket/neqo_glue/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "neqo_glue"
+version = "0.1.0"
+authors = ["Dragana Damjanovic <dd.mozilla@gmail.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[lib]
+name = "neqo_glue"
+
+[dependencies]
+neqo-http3 = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-transport = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-common = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-qpack = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+log = "0.4.0"
+qlog = { git = "https://github.com/cloudflare/quiche", rev = "09ea4b244096a013071cfe2175bbf2945fb7f8d1" }
+libc = "0.2.0"
+static_prefs = { path = "../../../modules/libpref/init/static_prefs"}
+uuid = { version = "1.0", features = ["v4"] }
+
+[target.'cfg(target_os = "windows")'.dependencies]
+winapi = {version = "0.3", features = ["ws2def"] }
+
+[dependencies.neqo-crypto]
+tag = "v0.7.0"
+git = "https://github.com/mozilla/neqo"
+default-features = false
+features = ["gecko"]
+
+[features]
+fuzzing = ["neqo-http3/fuzzing"]
diff --git a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
new file mode 100644
index 0000000000..63fbe389f3
--- /dev/null
+++ b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
@@ -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/. */
+
+#ifndef NeqoHttp3Conn_h__
+#define NeqoHttp3Conn_h__
+
+#include <cstdint>
+#include "mozilla/net/neqo_glue_ffi_generated.h"
+
+namespace mozilla {
+namespace net {
+
+class NeqoHttp3Conn final {
+ public:
+ static nsresult Init(const nsACString& aOrigin, const nsACString& aAlpn,
+ const NetAddr& aLocalAddr, const NetAddr& aRemoteAddr,
+ uint32_t aMaxTableSize, uint16_t aMaxBlockedStreams,
+ uint64_t aMaxData, uint64_t aMaxStreamData,
+ bool aVersionNegotiation, bool aWebTransport,
+ const nsACString& aQlogDir, uint32_t aDatagramSize,
+ uint32_t aMaxAccumulatedTime, NeqoHttp3Conn** aConn) {
+ return neqo_http3conn_new(
+ &aOrigin, &aAlpn, &aLocalAddr, &aRemoteAddr, aMaxTableSize,
+ aMaxBlockedStreams, aMaxData, aMaxStreamData, aVersionNegotiation,
+ aWebTransport, &aQlogDir, aDatagramSize, aMaxAccumulatedTime,
+ (const mozilla::net::NeqoHttp3Conn**)aConn);
+ }
+
+ void Close(uint64_t aError) { neqo_http3conn_close(this, aError); }
+
+ nsresult GetSecInfo(NeqoSecretInfo* aSecInfo) {
+ return neqo_http3conn_tls_info(this, aSecInfo);
+ }
+
+ nsresult PeerCertificateInfo(NeqoCertificateInfo* aCertInfo) {
+ return neqo_http3conn_peer_certificate_info(this, aCertInfo);
+ }
+
+ void PeerAuthenticated(PRErrorCode aError) {
+ neqo_http3conn_authenticated(this, aError);
+ }
+
+ nsresult ProcessInput(const NetAddr& aRemoteAddr,
+ const nsTArray<uint8_t>& aPacket) {
+ return neqo_http3conn_process_input(this, &aRemoteAddr, &aPacket);
+ }
+
+ nsresult ProcessOutputAndSend(void* aContext, SendFunc aSendFunc,
+ SetTimerFunc aSetTimerFunc) {
+ return neqo_http3conn_process_output_and_send(this, aContext, aSendFunc,
+ aSetTimerFunc);
+ }
+
+ nsresult GetEvent(Http3Event* aEvent, nsTArray<uint8_t>& aData) {
+ return neqo_http3conn_event(this, aEvent, &aData);
+ }
+
+ nsresult Fetch(const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aHeaders, uint64_t* aStreamId,
+ uint8_t aUrgency, bool aIncremental) {
+ return neqo_http3conn_fetch(this, &aMethod, &aScheme, &aHost, &aPath,
+ &aHeaders, aStreamId, aUrgency, aIncremental);
+ }
+
+ nsresult PriorityUpdate(uint64_t aStreamId, uint8_t aUrgency,
+ bool aIncremental) {
+ return neqo_http3conn_priority_update(this, aStreamId, aUrgency,
+ aIncremental);
+ }
+
+ nsresult SendRequestBody(uint64_t aStreamId, const uint8_t* aBuf,
+ uint32_t aCount, uint32_t* aCountRead) {
+ return neqo_htttp3conn_send_request_body(this, aStreamId, aBuf, aCount,
+ aCountRead);
+ }
+
+ // This closes only the sending side of a stream.
+ nsresult CloseStream(uint64_t aStreamId) {
+ return neqo_http3conn_close_stream(this, aStreamId);
+ }
+
+ nsresult ReadResponseData(uint64_t aStreamId, uint8_t* aBuf, uint32_t aLen,
+ uint32_t* aRead, bool* aFin) {
+ return neqo_http3conn_read_response_data(this, aStreamId, aBuf, aLen, aRead,
+ aFin);
+ }
+
+ void CancelFetch(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_cancel_fetch(this, aStreamId, aError);
+ }
+
+ void ResetStream(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_reset_stream(this, aStreamId, aError);
+ }
+
+ void StreamStopSending(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_stream_stop_sending(this, aStreamId, aError);
+ }
+
+ void SetResumptionToken(nsTArray<uint8_t>& aToken) {
+ neqo_http3conn_set_resumption_token(this, &aToken);
+ }
+
+ void SetEchConfig(nsTArray<uint8_t>& aEchConfig) {
+ neqo_http3conn_set_ech_config(this, &aEchConfig);
+ }
+
+ bool IsZeroRtt() { return neqo_http3conn_is_zero_rtt(this); }
+
+ void AddRef() { neqo_http3conn_addref(this); }
+ void Release() { neqo_http3conn_release(this); }
+
+ void GetStats(Http3Stats* aStats) {
+ return neqo_http3conn_get_stats(this, aStats);
+ }
+
+ nsresult CreateWebTransport(const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aHeaders,
+ uint64_t* aSessionId) {
+ return neqo_http3conn_webtransport_create_session(this, &aHost, &aPath,
+ &aHeaders, aSessionId);
+ }
+
+ nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
+ const nsACString& aMessage) {
+ return neqo_http3conn_webtransport_close_session(this, aSessionId, aError,
+ &aMessage);
+ }
+
+ nsresult CreateWebTransportStream(uint64_t aSessionId,
+ WebTransportStreamType aStreamType,
+ uint64_t* aStreamId) {
+ return neqo_http3conn_webtransport_create_stream(this, aSessionId,
+ aStreamType, aStreamId);
+ }
+
+ nsresult WebTransportSendDatagram(uint64_t aSessionId,
+ nsTArray<uint8_t>& aData,
+ uint64_t aTrackingId) {
+ return neqo_http3conn_webtransport_send_datagram(this, aSessionId, &aData,
+ aTrackingId);
+ }
+
+ nsresult WebTransportMaxDatagramSize(uint64_t aSessionId, uint64_t* aResult) {
+ return neqo_http3conn_webtransport_max_datagram_size(this, aSessionId,
+ aResult);
+ }
+
+ nsresult WebTransportSetSendOrder(uint64_t aSessionId,
+ Maybe<int64_t> aSendOrder) {
+ return neqo_http3conn_webtransport_set_sendorder(this, aSessionId,
+ aSendOrder.ptrOr(nullptr));
+ }
+
+ private:
+ NeqoHttp3Conn() = delete;
+ ~NeqoHttp3Conn() = delete;
+ NeqoHttp3Conn(const NeqoHttp3Conn&) = delete;
+ NeqoHttp3Conn& operator=(const NeqoHttp3Conn&) = delete;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/socket/neqo_glue/cbindgen.toml b/netwerk/socket/neqo_glue/cbindgen.toml
new file mode 100644
index 0000000000..312e1731e5
--- /dev/null
+++ b/netwerk/socket/neqo_glue/cbindgen.toml
@@ -0,0 +1,27 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
+ */
+
+namespace mozilla {
+namespace net {
+class NeqoHttp3Conn;
+union NetAddr;
+} // namespace net
+} // namespace mozilla
+ """
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "net"]
+includes = ["certt.h", "prerror.h"]
+
+[export]
+exclude = ["NeqoHttp3Conn", "NetAddr"]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[export.rename]
+"ThinVec" = "nsTArray"
diff --git a/netwerk/socket/neqo_glue/moz.build b/netwerk/socket/neqo_glue/moz.build
new file mode 100644
index 0000000000..b3123dde4f
--- /dev/null
+++ b/netwerk/socket/neqo_glue/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "NeqoHttp3Conn.h",
+]
+
+LOCAL_INCLUDES += [
+ "/security/manager/ssl",
+ "/security/nss/lib/ssl",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ CbindgenHeader("neqo_glue_ffi_generated.h", inputs=["/netwerk/socket/neqo_glue"])
+
+ EXPORTS.mozilla.net += [
+ "!neqo_glue_ffi_generated.h",
+ ]
diff --git a/netwerk/socket/neqo_glue/src/lib.rs b/netwerk/socket/neqo_glue/src/lib.rs
new file mode 100644
index 0000000000..6c86211ed5
--- /dev/null
+++ b/netwerk/socket/neqo_glue/src/lib.rs
@@ -0,0 +1,1439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#[cfg(not(windows))]
+use libc::{AF_INET, AF_INET6};
+use neqo_common::event::Provider;
+use neqo_common::{self as common, qlog::NeqoQlog, qwarn, Datagram, Header, IpTos, Role};
+use neqo_crypto::{init, PRErrorCode};
+use neqo_http3::{
+ features::extended_connect::SessionCloseReason, Error as Http3Error, Http3Client,
+ Http3ClientEvent, Http3Parameters, Http3State, Priority, WebTransportEvent,
+};
+use neqo_transport::{
+ stream_id::StreamType, CongestionControlAlgorithm, ConnectionParameters,
+ Error as TransportError, Output, RandomConnectionIdGenerator, StreamId, Version,
+};
+use nserror::*;
+use nsstring::*;
+use qlog::streamer::QlogStreamer;
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::convert::TryFrom;
+use std::convert::TryInto;
+use std::ffi::c_void;
+use std::fs::OpenOptions;
+use std::net::SocketAddr;
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+use std::path::PathBuf;
+use std::ptr;
+use std::rc::Rc;
+use std::slice;
+use std::str;
+#[cfg(feature = "fuzzing")]
+use std::time::Duration;
+use std::time::{Duration, Instant};
+use thin_vec::ThinVec;
+use uuid::Uuid;
+#[cfg(windows)]
+use winapi::shared::ws2def::{AF_INET, AF_INET6};
+use xpcom::{AtomicRefcnt, RefCounted, RefPtr};
+
+#[repr(C)]
+pub struct NeqoHttp3Conn {
+ conn: Http3Client,
+ local_addr: SocketAddr,
+ refcnt: AtomicRefcnt,
+ last_output_time: Instant,
+ max_accumlated_time: Duration,
+}
+
+// Opaque interface to mozilla::net::NetAddr defined in DNS.h
+#[repr(C)]
+pub union NetAddr {
+ private: [u8; 0],
+}
+
+extern "C" {
+ pub fn moz_netaddr_get_family(arg: *const NetAddr) -> u16;
+ pub fn moz_netaddr_get_network_order_ip(arg: *const NetAddr) -> u32;
+ pub fn moz_netaddr_get_ipv6(arg: *const NetAddr) -> *const u8;
+ pub fn moz_netaddr_get_network_order_port(arg: *const NetAddr) -> u16;
+}
+
+fn netaddr_to_socket_addr(arg: *const NetAddr) -> Result<SocketAddr, nsresult> {
+ if arg == ptr::null() {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+
+ unsafe {
+ let family = moz_netaddr_get_family(arg) as i32;
+ if family == AF_INET {
+ let port = u16::from_be(moz_netaddr_get_network_order_port(arg));
+ let ipv4 = Ipv4Addr::from(u32::from_be(moz_netaddr_get_network_order_ip(arg)));
+ return Ok(SocketAddr::new(IpAddr::V4(ipv4), port));
+ }
+
+ if family == AF_INET6 {
+ let port = u16::from_be(moz_netaddr_get_network_order_port(arg));
+ let ipv6_slice: [u8; 16] = slice::from_raw_parts(moz_netaddr_get_ipv6(arg), 16)
+ .try_into()
+ .expect("slice with incorrect length");
+ let ipv6 = Ipv6Addr::from(ipv6_slice);
+ return Ok(SocketAddr::new(IpAddr::V6(ipv6), port));
+ }
+ }
+
+ Err(NS_ERROR_UNEXPECTED)
+}
+
+fn get_current_or_last_output_time(last_output_time: &Instant) -> Instant {
+ max(*last_output_time, Instant::now())
+}
+
+type SendFunc = extern "C" fn(
+ context: *mut c_void,
+ addr_family: u16,
+ addr: *const u8,
+ port: u16,
+ data: *const u8,
+ size: u32,
+) -> nsresult;
+
+type SetTimerFunc = extern "C" fn(context: *mut c_void, timeout: u64);
+
+impl NeqoHttp3Conn {
+ fn new(
+ origin: &nsACString,
+ alpn: &nsACString,
+ local_addr: *const NetAddr,
+ remote_addr: *const NetAddr,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ max_data: u64,
+ max_stream_data: u64,
+ version_negotiation: bool,
+ webtransport: bool,
+ qlog_dir: &nsACString,
+ webtransport_datagram_size: u32,
+ max_accumlated_time_ms: u32,
+ ) -> Result<RefPtr<NeqoHttp3Conn>, nsresult> {
+ // Nss init.
+ init();
+
+ let origin_conv = str::from_utf8(origin).map_err(|_| NS_ERROR_INVALID_ARG)?;
+
+ let alpn_conv = str::from_utf8(alpn).map_err(|_| NS_ERROR_INVALID_ARG)?;
+
+ let local: SocketAddr = netaddr_to_socket_addr(local_addr)?;
+
+ let remote: SocketAddr = netaddr_to_socket_addr(remote_addr)?;
+
+ let quic_version = match alpn_conv {
+ "h3-32" => Version::Draft32,
+ "h3-31" => Version::Draft31,
+ "h3-30" => Version::Draft30,
+ "h3-29" => Version::Draft29,
+ "h3" => Version::Version1,
+ _ => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let version_list = if version_negotiation {
+ Version::all()
+ } else {
+ vec![quic_version]
+ };
+
+ let cc_algorithm = match static_prefs::pref!("network.http.http3.cc_algorithm") {
+ 0 => CongestionControlAlgorithm::NewReno,
+ 1 => CongestionControlAlgorithm::Cubic,
+ _ => {
+ // Unknown preferences; default to Cubic
+ CongestionControlAlgorithm::Cubic
+ }
+ };
+
+ #[allow(unused_mut)]
+ let mut params = ConnectionParameters::default()
+ .versions(quic_version, version_list)
+ .cc_algorithm(cc_algorithm)
+ .max_data(max_data)
+ .max_stream_data(StreamType::BiDi, false, max_stream_data)
+ .grease(static_prefs::pref!("security.tls.grease_http3_enable"));
+
+ // Set a short timeout when fuzzing.
+ #[cfg(feature = "fuzzing")]
+ if static_prefs::pref!("fuzzing.necko.http3") {
+ params = params.idle_timeout(Duration::from_millis(10));
+ }
+
+ if webtransport_datagram_size > 0 {
+ params = params.datagram_size(webtransport_datagram_size.into());
+ }
+
+ let http3_settings = Http3Parameters::default()
+ .max_table_size_encoder(max_table_size)
+ .max_table_size_decoder(max_table_size)
+ .max_blocked_streams(max_blocked_streams)
+ .max_concurrent_push_streams(0)
+ .connection_parameters(params)
+ .webtransport(webtransport)
+ .http3_datagram(webtransport);
+
+ let mut conn = match Http3Client::new(
+ origin_conv,
+ Rc::new(RefCell::new(RandomConnectionIdGenerator::new(3))),
+ local,
+ remote,
+ http3_settings,
+ Instant::now(),
+ ) {
+ Ok(c) => c,
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ if !qlog_dir.is_empty() {
+ let qlog_dir_conv = str::from_utf8(qlog_dir).map_err(|_| NS_ERROR_INVALID_ARG)?;
+ let mut qlog_path = PathBuf::from(qlog_dir_conv);
+ qlog_path.push(format!("{}_{}.qlog", origin, Uuid::new_v4()));
+
+ // Emit warnings but to not return an error if qlog initialization
+ // fails.
+ match OpenOptions::new()
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(&qlog_path)
+ {
+ Err(_) => qwarn!("Could not open qlog path: {}", qlog_path.display()),
+ Ok(f) => {
+ let streamer = QlogStreamer::new(
+ qlog::QLOG_VERSION.to_string(),
+ Some("Firefox Client qlog".to_string()),
+ Some("Firefox Client qlog".to_string()),
+ None,
+ std::time::Instant::now(),
+ common::qlog::new_trace(Role::Client),
+ qlog::events::EventImportance::Base,
+ Box::new(f),
+ );
+
+ match NeqoQlog::enabled(streamer, &qlog_path) {
+ Err(_) => qwarn!("Could not write to qlog path: {}", qlog_path.display()),
+ Ok(nq) => conn.set_qlog(nq),
+ }
+ }
+ }
+ }
+
+ let conn = Box::into_raw(Box::new(NeqoHttp3Conn {
+ conn,
+ local_addr: local,
+ refcnt: unsafe { AtomicRefcnt::new() },
+ last_output_time: Instant::now(),
+ max_accumlated_time: Duration::from_millis(max_accumlated_time_ms.into()),
+ }));
+ unsafe { Ok(RefPtr::from_raw(conn).unwrap()) }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_addref(conn: &NeqoHttp3Conn) {
+ conn.refcnt.inc();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_release(conn: &NeqoHttp3Conn) {
+ let rc = conn.refcnt.dec();
+ if rc == 0 {
+ std::mem::drop(Box::from_raw(conn as *const _ as *mut NeqoHttp3Conn));
+ }
+}
+
+// xpcom::RefPtr support
+unsafe impl RefCounted for NeqoHttp3Conn {
+ unsafe fn addref(&self) {
+ neqo_http3conn_addref(self);
+ }
+ unsafe fn release(&self) {
+ neqo_http3conn_release(self);
+ }
+}
+
+// Allocate a new NeqoHttp3Conn object.
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_new(
+ origin: &nsACString,
+ alpn: &nsACString,
+ local_addr: *const NetAddr,
+ remote_addr: *const NetAddr,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ max_data: u64,
+ max_stream_data: u64,
+ version_negotiation: bool,
+ webtransport: bool,
+ qlog_dir: &nsACString,
+ webtransport_datagram_size: u32,
+ max_accumlated_time_ms: u32,
+ result: &mut *const NeqoHttp3Conn,
+) -> nsresult {
+ *result = ptr::null_mut();
+
+ match NeqoHttp3Conn::new(
+ origin,
+ alpn,
+ local_addr,
+ remote_addr,
+ max_table_size,
+ max_blocked_streams,
+ max_data,
+ max_stream_data,
+ version_negotiation,
+ webtransport,
+ qlog_dir,
+ webtransport_datagram_size,
+ max_accumlated_time_ms,
+ ) {
+ Ok(http3_conn) => {
+ http3_conn.forget(result);
+ NS_OK
+ }
+ Err(e) => e,
+ }
+}
+
+/* Process a packet.
+ * packet holds packet data.
+ */
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_process_input(
+ conn: &mut NeqoHttp3Conn,
+ remote_addr: *const NetAddr,
+ packet: *const ThinVec<u8>,
+) -> nsresult {
+ let remote = match netaddr_to_socket_addr(remote_addr) {
+ Ok(addr) => addr,
+ Err(result) => return result,
+ };
+ let d = Datagram::new(
+ remote,
+ conn.local_addr,
+ IpTos::default(),
+ None,
+ (*packet).to_vec(),
+ );
+ conn.conn
+ .process_input(&d, get_current_or_last_output_time(&conn.last_output_time));
+ return NS_OK;
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_process_output_and_send(
+ conn: &mut NeqoHttp3Conn,
+ context: *mut c_void,
+ send_func: SendFunc,
+ set_timer_func: SetTimerFunc,
+) -> nsresult {
+ let now = Instant::now();
+ if conn.last_output_time > now {
+ // The timer fired too early, so reschedule it.
+ // The 1ms of extra delay is not ideal, but this is a fail
+ set_timer_func(
+ context,
+ u64::try_from((conn.last_output_time - now + conn.max_accumlated_time).as_millis())
+ .unwrap(),
+ );
+ return NS_OK;
+ }
+
+ let mut accumulated_time = Duration::from_nanos(0);
+ loop {
+ conn.last_output_time = if accumulated_time.is_zero() {
+ Instant::now()
+ } else {
+ now + accumulated_time
+ };
+ match conn.conn.process_output(conn.last_output_time) {
+ Output::Datagram(dg) => {
+ let rv = match dg.destination().ip() {
+ IpAddr::V4(v4) => send_func(
+ context,
+ u16::try_from(AF_INET).unwrap(),
+ v4.octets().as_ptr(),
+ dg.destination().port(),
+ dg.as_ptr(),
+ u32::try_from(dg.len()).unwrap(),
+ ),
+ IpAddr::V6(v6) => send_func(
+ context,
+ u16::try_from(AF_INET6).unwrap(),
+ v6.octets().as_ptr(),
+ dg.destination().port(),
+ dg.as_ptr(),
+ u32::try_from(dg.len()).unwrap(),
+ ),
+ };
+ if rv != NS_OK {
+ return rv;
+ }
+ }
+ Output::Callback(to) => {
+ if to.is_zero() {
+ set_timer_func(context, 1);
+ break;
+ }
+
+ let timeout = min(to, Duration::from_nanos(u64::MAX - 1));
+ accumulated_time += timeout;
+ if accumulated_time >= conn.max_accumlated_time {
+ let mut timeout = accumulated_time.as_millis() as u64;
+ if timeout == 0 {
+ timeout = 1;
+ }
+ set_timer_func(context, timeout);
+ break;
+ }
+ }
+ Output::None => {
+ set_timer_func(context, std::u64::MAX);
+ break;
+ }
+ }
+ }
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_close(conn: &mut NeqoHttp3Conn, error: u64) {
+ conn.conn.close(
+ get_current_or_last_output_time(&conn.last_output_time),
+ error,
+ "",
+ );
+}
+
+fn is_excluded_header(name: &str) -> bool {
+ if (name == "connection")
+ || (name == "host")
+ || (name == "keep-alive")
+ || (name == "proxy-connection")
+ || (name == "te")
+ || (name == "transfer-encoding")
+ || (name == "upgrade")
+ || (name == "sec-websocket-key")
+ {
+ true
+ } else {
+ false
+ }
+}
+
+fn parse_headers(headers: &nsACString) -> Result<Vec<Header>, nsresult> {
+ let mut hdrs = Vec::new();
+ // this is only used for headers built by Firefox.
+ // Firefox supplies all headers already prepared for sending over http1.
+ // They need to be split into (String, String) pairs.
+ match str::from_utf8(headers) {
+ Err(_) => {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+ Ok(h) => {
+ for elem in h.split("\r\n").skip(1) {
+ if elem.starts_with(':') {
+ // colon headers are for http/2 and 3 and this is http/1
+ // input, so that is probably a smuggling attack of some
+ // kind.
+ continue;
+ }
+ if elem.len() == 0 {
+ continue;
+ }
+ let hdr_str: Vec<_> = elem.splitn(2, ":").collect();
+ let name = hdr_str[0].trim().to_lowercase();
+ if is_excluded_header(&name) {
+ continue;
+ }
+ let value = if hdr_str.len() > 1 {
+ String::from(hdr_str[1].trim())
+ } else {
+ String::new()
+ };
+
+ hdrs.push(Header::new(name, value));
+ }
+ }
+ }
+ Ok(hdrs)
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_fetch(
+ conn: &mut NeqoHttp3Conn,
+ method: &nsACString,
+ scheme: &nsACString,
+ host: &nsACString,
+ path: &nsACString,
+ headers: &nsACString,
+ stream_id: &mut u64,
+ urgency: u8,
+ incremental: bool,
+) -> nsresult {
+ let hdrs = match parse_headers(headers) {
+ Err(e) => {
+ return e;
+ }
+ Ok(h) => h,
+ };
+ let method_tmp = match str::from_utf8(method) {
+ Ok(m) => m,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let scheme_tmp = match str::from_utf8(scheme) {
+ Ok(s) => s,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let host_tmp = match str::from_utf8(host) {
+ Ok(h) => h,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let path_tmp = match str::from_utf8(path) {
+ Ok(p) => p,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ if urgency >= 8 {
+ return NS_ERROR_INVALID_ARG;
+ }
+ let priority = Priority::new(urgency, incremental);
+ match conn.conn.fetch(
+ get_current_or_last_output_time(&conn.last_output_time),
+ method_tmp,
+ &(scheme_tmp, host_tmp, path_tmp),
+ &hdrs,
+ priority,
+ ) {
+ Ok(id) => {
+ *stream_id = id.as_u64();
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_priority_update(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ urgency: u8,
+ incremental: bool,
+) -> nsresult {
+ if urgency >= 8 {
+ return NS_ERROR_INVALID_ARG;
+ }
+ let priority = Priority::new(urgency, incremental);
+ match conn
+ .conn
+ .priority_update(StreamId::from(stream_id), priority)
+ {
+ Ok(_) => NS_OK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_htttp3conn_send_request_body(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ buf: *const u8,
+ len: u32,
+ read: &mut u32,
+) -> nsresult {
+ let array = slice::from_raw_parts(buf, len as usize);
+ match conn.conn.send_data(StreamId::from(stream_id), array) {
+ Ok(amount) => {
+ *read = u32::try_from(amount).unwrap();
+ if amount == 0 {
+ NS_BASE_STREAM_WOULD_BLOCK
+ } else {
+ NS_OK
+ }
+ }
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+fn crypto_error_code(err: neqo_crypto::Error) -> u64 {
+ match err {
+ neqo_crypto::Error::AeadError => 1,
+ neqo_crypto::Error::CertificateLoading => 2,
+ neqo_crypto::Error::CreateSslSocket => 3,
+ neqo_crypto::Error::HkdfError => 4,
+ neqo_crypto::Error::InternalError => 5,
+ neqo_crypto::Error::IntegerOverflow => 6,
+ neqo_crypto::Error::InvalidEpoch => 7,
+ neqo_crypto::Error::MixedHandshakeMethod => 8,
+ neqo_crypto::Error::NoDataAvailable => 9,
+ neqo_crypto::Error::NssError { .. } => 10,
+ neqo_crypto::Error::OverrunError => 11,
+ neqo_crypto::Error::SelfEncryptFailure => 12,
+ neqo_crypto::Error::TimeTravelError => 13,
+ neqo_crypto::Error::UnsupportedCipher => 14,
+ neqo_crypto::Error::UnsupportedVersion => 15,
+ neqo_crypto::Error::StringError => 16,
+ neqo_crypto::Error::EchRetry(_) => 17,
+ neqo_crypto::Error::CipherInitFailure => 18,
+ }
+}
+
+// This is only used for telemetry. Therefore we only return error code
+// numbers and do not label them. Recording telemetry is easier with a
+// number.
+#[repr(C)]
+pub enum CloseError {
+ TransportInternalError,
+ TransportInternalErrorOther(u16),
+ TransportError(u64),
+ CryptoError(u64),
+ CryptoAlert(u8),
+ PeerAppError(u64),
+ PeerError(u64),
+ AppError(u64),
+ EchRetry,
+}
+
+impl From<TransportError> for CloseError {
+ fn from(error: TransportError) -> CloseError {
+ match error {
+ TransportError::InternalError => CloseError::TransportInternalError,
+ TransportError::CryptoError(neqo_crypto::Error::EchRetry(_)) => CloseError::EchRetry,
+ TransportError::CryptoError(c) => CloseError::CryptoError(crypto_error_code(c)),
+ TransportError::CryptoAlert(c) => CloseError::CryptoAlert(c),
+ TransportError::PeerApplicationError(c) => CloseError::PeerAppError(c),
+ TransportError::PeerError(c) => CloseError::PeerError(c),
+ TransportError::NoError
+ | TransportError::IdleTimeout
+ | TransportError::ConnectionRefused
+ | TransportError::FlowControlError
+ | TransportError::StreamLimitError
+ | TransportError::StreamStateError
+ | TransportError::FinalSizeError
+ | TransportError::FrameEncodingError
+ | TransportError::TransportParameterError
+ | TransportError::ProtocolViolation
+ | TransportError::InvalidToken
+ | TransportError::KeysExhausted
+ | TransportError::ApplicationError
+ | TransportError::NoAvailablePath
+ | TransportError::CryptoBufferExceeded => CloseError::TransportError(error.code()),
+ TransportError::EchRetry(_) => CloseError::EchRetry,
+ TransportError::AckedUnsentPacket => CloseError::TransportInternalErrorOther(0),
+ TransportError::ConnectionIdLimitExceeded => CloseError::TransportInternalErrorOther(1),
+ TransportError::ConnectionIdsExhausted => CloseError::TransportInternalErrorOther(2),
+ TransportError::ConnectionState => CloseError::TransportInternalErrorOther(3),
+ TransportError::DecodingFrame => CloseError::TransportInternalErrorOther(4),
+ TransportError::DecryptError => CloseError::TransportInternalErrorOther(5),
+ TransportError::HandshakeFailed => CloseError::TransportInternalErrorOther(6),
+ TransportError::IntegerOverflow => CloseError::TransportInternalErrorOther(7),
+ TransportError::InvalidInput => CloseError::TransportInternalErrorOther(8),
+ TransportError::InvalidMigration => CloseError::TransportInternalErrorOther(9),
+ TransportError::InvalidPacket => CloseError::TransportInternalErrorOther(10),
+ TransportError::InvalidResumptionToken => CloseError::TransportInternalErrorOther(11),
+ TransportError::InvalidRetry => CloseError::TransportInternalErrorOther(12),
+ TransportError::InvalidStreamId => CloseError::TransportInternalErrorOther(13),
+ TransportError::KeysDiscarded(_) => CloseError::TransportInternalErrorOther(14),
+ TransportError::KeysPending(_) => CloseError::TransportInternalErrorOther(15),
+ TransportError::KeyUpdateBlocked => CloseError::TransportInternalErrorOther(16),
+ TransportError::NoMoreData => CloseError::TransportInternalErrorOther(17),
+ TransportError::NotConnected => CloseError::TransportInternalErrorOther(18),
+ TransportError::PacketNumberOverlap => CloseError::TransportInternalErrorOther(19),
+ TransportError::StatelessReset => CloseError::TransportInternalErrorOther(20),
+ TransportError::TooMuchData => CloseError::TransportInternalErrorOther(21),
+ TransportError::UnexpectedMessage => CloseError::TransportInternalErrorOther(22),
+ TransportError::UnknownConnectionId => CloseError::TransportInternalErrorOther(23),
+ TransportError::UnknownFrameType => CloseError::TransportInternalErrorOther(24),
+ TransportError::VersionNegotiation => CloseError::TransportInternalErrorOther(25),
+ TransportError::WrongRole => CloseError::TransportInternalErrorOther(26),
+ TransportError::QlogError => CloseError::TransportInternalErrorOther(27),
+ TransportError::NotAvailable => CloseError::TransportInternalErrorOther(28),
+ TransportError::DisabledVersion => CloseError::TransportInternalErrorOther(29),
+ }
+ }
+}
+
+impl From<neqo_transport::ConnectionError> for CloseError {
+ fn from(error: neqo_transport::ConnectionError) -> CloseError {
+ match error {
+ neqo_transport::ConnectionError::Transport(c) => c.into(),
+ neqo_transport::ConnectionError::Application(c) => CloseError::AppError(c),
+ }
+ }
+}
+
+// Reset a stream with streamId.
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_cancel_fetch(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ error: u64,
+) -> nsresult {
+ match conn.conn.cancel_fetch(StreamId::from(stream_id), error) {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+// Reset a stream with streamId.
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_reset_stream(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ error: u64,
+) -> nsresult {
+ match conn
+ .conn
+ .stream_reset_send(StreamId::from(stream_id), error)
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_stream_stop_sending(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ error: u64,
+) -> nsresult {
+ match conn
+ .conn
+ .stream_stop_sending(StreamId::from(stream_id), error)
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+// Close sending side of a stream with stream_id
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_close_stream(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+) -> nsresult {
+ match conn.conn.stream_close_send(StreamId::from(stream_id)) {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+// WebTransport streams can be unidirectional and bidirectional.
+// It is mapped to and from neqo's StreamType enum.
+#[repr(C)]
+pub enum WebTransportStreamType {
+ BiDi,
+ UniDi,
+}
+
+impl From<StreamType> for WebTransportStreamType {
+ fn from(t: StreamType) -> WebTransportStreamType {
+ match t {
+ StreamType::BiDi => WebTransportStreamType::BiDi,
+ StreamType::UniDi => WebTransportStreamType::UniDi,
+ }
+ }
+}
+
+impl From<WebTransportStreamType> for StreamType {
+ fn from(t: WebTransportStreamType) -> StreamType {
+ match t {
+ WebTransportStreamType::BiDi => StreamType::BiDi,
+ WebTransportStreamType::UniDi => StreamType::UniDi,
+ }
+ }
+}
+
+#[repr(C)]
+pub enum SessionCloseReasonExternal {
+ Error(u64),
+ Status(u16),
+ Clean(u32),
+}
+
+impl SessionCloseReasonExternal {
+ fn new(reason: SessionCloseReason, data: &mut ThinVec<u8>) -> SessionCloseReasonExternal {
+ match reason {
+ SessionCloseReason::Error(e) => SessionCloseReasonExternal::Error(e),
+ SessionCloseReason::Status(s) => SessionCloseReasonExternal::Status(s),
+ SessionCloseReason::Clean { error, message } => {
+ data.extend_from_slice(message.as_ref());
+ SessionCloseReasonExternal::Clean(error)
+ }
+ }
+ }
+}
+
+#[repr(C)]
+pub enum WebTransportEventExternal {
+ Negotiated(bool),
+ Session(u64),
+ SessionClosed {
+ stream_id: u64,
+ reason: SessionCloseReasonExternal,
+ },
+ NewStream {
+ stream_id: u64,
+ stream_type: WebTransportStreamType,
+ session_id: u64,
+ },
+ Datagram {
+ session_id: u64,
+ },
+}
+
+impl WebTransportEventExternal {
+ fn new(event: WebTransportEvent, data: &mut ThinVec<u8>) -> WebTransportEventExternal {
+ match event {
+ WebTransportEvent::Negotiated(n) => WebTransportEventExternal::Negotiated(n),
+ WebTransportEvent::Session {
+ stream_id,
+ status,
+ headers: _,
+ } => {
+ data.extend_from_slice(b"HTTP/3 ");
+ data.extend_from_slice(&status.to_string().as_bytes());
+ data.extend_from_slice(b"\r\n\r\n");
+ WebTransportEventExternal::Session(stream_id.as_u64())
+ }
+ WebTransportEvent::SessionClosed {
+ stream_id,
+ reason,
+ headers: _,
+ } => match reason {
+ SessionCloseReason::Status(status) => {
+ data.extend_from_slice(b"HTTP/3 ");
+ data.extend_from_slice(&status.to_string().as_bytes());
+ data.extend_from_slice(b"\r\n\r\n");
+ WebTransportEventExternal::Session(stream_id.as_u64())
+ }
+ _ => WebTransportEventExternal::SessionClosed {
+ stream_id: stream_id.as_u64(),
+ reason: SessionCloseReasonExternal::new(reason, data),
+ },
+ },
+ WebTransportEvent::NewStream {
+ stream_id,
+ session_id,
+ } => WebTransportEventExternal::NewStream {
+ stream_id: stream_id.as_u64(),
+ stream_type: stream_id.stream_type().into(),
+ session_id: session_id.as_u64(),
+ },
+ WebTransportEvent::Datagram {
+ session_id,
+ datagram,
+ } => {
+ data.extend_from_slice(datagram.as_ref());
+ WebTransportEventExternal::Datagram {
+ session_id: session_id.as_u64(),
+ }
+ }
+ }
+ }
+}
+
+#[repr(C)]
+pub enum Http3Event {
+ /// A request stream has space for more data to be sent.
+ DataWritable {
+ stream_id: u64,
+ },
+ /// A server has send STOP_SENDING frame.
+ StopSending {
+ stream_id: u64,
+ error: u64,
+ },
+ HeaderReady {
+ stream_id: u64,
+ fin: bool,
+ interim: bool,
+ },
+ /// New bytes available for reading.
+ DataReadable {
+ stream_id: u64,
+ },
+ /// Peer reset the stream.
+ Reset {
+ stream_id: u64,
+ error: u64,
+ local: bool,
+ },
+ /// A PushPromise
+ PushPromise {
+ push_id: u64,
+ request_stream_id: u64,
+ },
+ /// A push response headers are ready.
+ PushHeaderReady {
+ push_id: u64,
+ fin: bool,
+ },
+ /// New bytes are available on a push stream for reading.
+ PushDataReadable {
+ push_id: u64,
+ },
+ /// A push has been canceled.
+ PushCanceled {
+ push_id: u64,
+ },
+ PushReset {
+ push_id: u64,
+ error: u64,
+ },
+ RequestsCreatable,
+ AuthenticationNeeded,
+ ZeroRttRejected,
+ ConnectionConnected,
+ GoawayReceived,
+ ConnectionClosing {
+ error: CloseError,
+ },
+ ConnectionClosed {
+ error: CloseError,
+ },
+ ResumptionToken {
+ expire_in: u64, // microseconds
+ },
+ EchFallbackAuthenticationNeeded,
+ WebTransport(WebTransportEventExternal),
+ NoEvent,
+}
+
+fn sanitize_header(mut y: Cow<[u8]>) -> Cow<[u8]> {
+ for i in 0..y.len() {
+ if matches!(y[i], b'\n' | b'\r' | b'\0') {
+ y.to_mut()[i] = b' ';
+ }
+ }
+ y
+}
+
+fn convert_h3_to_h1_headers(headers: Vec<Header>, ret_headers: &mut ThinVec<u8>) -> nsresult {
+ if headers.iter().filter(|&h| h.name() == ":status").count() != 1 {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ let status_val = headers
+ .iter()
+ .find(|&h| h.name() == ":status")
+ .expect("must be one")
+ .value();
+
+ ret_headers.extend_from_slice(b"HTTP/3 ");
+ ret_headers.extend_from_slice(status_val.as_bytes());
+ ret_headers.extend_from_slice(b"\r\n");
+
+ for hdr in headers.iter().filter(|&h| h.name() != ":status") {
+ ret_headers.extend_from_slice(&sanitize_header(Cow::from(hdr.name().as_bytes())));
+ ret_headers.extend_from_slice(b": ");
+ ret_headers.extend_from_slice(&sanitize_header(Cow::from(hdr.value().as_bytes())));
+ ret_headers.extend_from_slice(b"\r\n");
+ }
+ ret_headers.extend_from_slice(b"\r\n");
+ return NS_OK;
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_event(
+ conn: &mut NeqoHttp3Conn,
+ ret_event: &mut Http3Event,
+ data: &mut ThinVec<u8>,
+) -> nsresult {
+ while let Some(evt) = conn.conn.next_event() {
+ let fe = match evt {
+ Http3ClientEvent::DataWritable { stream_id } => Http3Event::DataWritable {
+ stream_id: stream_id.as_u64(),
+ },
+ Http3ClientEvent::StopSending { stream_id, error } => Http3Event::StopSending {
+ stream_id: stream_id.as_u64(),
+ error,
+ },
+ Http3ClientEvent::HeaderReady {
+ stream_id,
+ headers,
+ fin,
+ interim,
+ } => {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::HeaderReady {
+ stream_id: stream_id.as_u64(),
+ fin,
+ interim,
+ }
+ }
+ Http3ClientEvent::DataReadable { stream_id } => Http3Event::DataReadable {
+ stream_id: stream_id.as_u64(),
+ },
+ Http3ClientEvent::Reset {
+ stream_id,
+ error,
+ local,
+ } => Http3Event::Reset {
+ stream_id: stream_id.as_u64(),
+ error,
+ local,
+ },
+ Http3ClientEvent::PushPromise {
+ push_id,
+ request_stream_id,
+ headers,
+ } => {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::PushPromise {
+ push_id,
+ request_stream_id: request_stream_id.as_u64(),
+ }
+ }
+ Http3ClientEvent::PushHeaderReady {
+ push_id,
+ headers,
+ fin,
+ interim,
+ } => {
+ if interim {
+ Http3Event::NoEvent
+ } else {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::PushHeaderReady { push_id, fin }
+ }
+ }
+ Http3ClientEvent::PushDataReadable { push_id } => {
+ Http3Event::PushDataReadable { push_id }
+ }
+ Http3ClientEvent::PushCanceled { push_id } => Http3Event::PushCanceled { push_id },
+ Http3ClientEvent::PushReset { push_id, error } => {
+ Http3Event::PushReset { push_id, error }
+ }
+ Http3ClientEvent::RequestsCreatable => Http3Event::RequestsCreatable,
+ Http3ClientEvent::AuthenticationNeeded => Http3Event::AuthenticationNeeded,
+ Http3ClientEvent::ZeroRttRejected => Http3Event::ZeroRttRejected,
+ Http3ClientEvent::ResumptionToken(token) => {
+ // expiration_time time is Instant, transform it into microseconds it will
+ // be valid for. Necko code will add the value to PR_Now() to get the expiration
+ // time in PRTime.
+ if token.expiration_time() > Instant::now() {
+ let e = (token.expiration_time() - Instant::now()).as_micros();
+ if let Ok(expire_in) = u64::try_from(e) {
+ data.extend_from_slice(token.as_ref());
+ Http3Event::ResumptionToken { expire_in }
+ } else {
+ Http3Event::NoEvent
+ }
+ } else {
+ Http3Event::NoEvent
+ }
+ }
+ Http3ClientEvent::GoawayReceived => Http3Event::GoawayReceived,
+ Http3ClientEvent::StateChange(state) => match state {
+ Http3State::Connected => Http3Event::ConnectionConnected,
+ Http3State::Closing(error_code) => {
+ match error_code {
+ neqo_transport::ConnectionError::Transport(
+ TransportError::CryptoError(neqo_crypto::Error::EchRetry(ref c)),
+ )
+ | neqo_transport::ConnectionError::Transport(TransportError::EchRetry(
+ ref c,
+ )) => {
+ data.extend_from_slice(c.as_ref());
+ }
+ _ => {}
+ }
+ Http3Event::ConnectionClosing {
+ error: error_code.into(),
+ }
+ }
+ Http3State::Closed(error_code) => {
+ match error_code {
+ neqo_transport::ConnectionError::Transport(
+ TransportError::CryptoError(neqo_crypto::Error::EchRetry(ref c)),
+ )
+ | neqo_transport::ConnectionError::Transport(TransportError::EchRetry(
+ ref c,
+ )) => {
+ data.extend_from_slice(c.as_ref());
+ }
+ _ => {}
+ }
+ Http3Event::ConnectionClosed {
+ error: error_code.into(),
+ }
+ }
+ _ => Http3Event::NoEvent,
+ },
+ Http3ClientEvent::EchFallbackAuthenticationNeeded { public_name } => {
+ data.extend_from_slice(public_name.as_ref());
+ Http3Event::EchFallbackAuthenticationNeeded
+ }
+ Http3ClientEvent::WebTransport(e) => {
+ Http3Event::WebTransport(WebTransportEventExternal::new(e, data))
+ }
+ };
+
+ if !matches!(fe, Http3Event::NoEvent) {
+ *ret_event = fe;
+ return NS_OK;
+ }
+ }
+
+ *ret_event = Http3Event::NoEvent;
+ NS_OK
+}
+
+// Read response data into buf.
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_read_response_data(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ buf: *mut u8,
+ len: u32,
+ read: &mut u32,
+ fin: &mut bool,
+) -> nsresult {
+ let array = slice::from_raw_parts_mut(buf, len as usize);
+ match conn.conn.read_data(
+ get_current_or_last_output_time(&conn.last_output_time),
+ StreamId::from(stream_id),
+ &mut array[..],
+ ) {
+ Ok((amount, fin_recvd)) => {
+ *read = u32::try_from(amount).unwrap();
+ *fin = fin_recvd;
+ if (amount == 0) && !fin_recvd {
+ NS_BASE_STREAM_WOULD_BLOCK
+ } else {
+ NS_OK
+ }
+ }
+ Err(Http3Error::InvalidStreamId)
+ | Err(Http3Error::TransportError(TransportError::NoMoreData)) => NS_ERROR_INVALID_ARG,
+ Err(_) => NS_ERROR_NET_HTTP3_PROTOCOL_ERROR,
+ }
+}
+
+#[repr(C)]
+pub struct NeqoSecretInfo {
+ set: bool,
+ version: u16,
+ cipher: u16,
+ group: u16,
+ resumed: bool,
+ early_data: bool,
+ alpn: nsCString,
+ signature_scheme: u16,
+ ech_accepted: bool,
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_tls_info(
+ conn: &mut NeqoHttp3Conn,
+ sec_info: &mut NeqoSecretInfo,
+) -> nsresult {
+ match conn.conn.tls_info() {
+ Some(info) => {
+ sec_info.set = true;
+ sec_info.version = info.version();
+ sec_info.cipher = info.cipher_suite();
+ sec_info.group = info.key_exchange();
+ sec_info.resumed = info.resumed();
+ sec_info.early_data = info.early_data_accepted();
+ sec_info.alpn = match info.alpn() {
+ Some(a) => nsCString::from(a),
+ None => nsCString::new(),
+ };
+ sec_info.signature_scheme = info.signature_scheme();
+ sec_info.ech_accepted = info.ech_accepted();
+ NS_OK
+ }
+ None => NS_ERROR_NOT_AVAILABLE,
+ }
+}
+
+#[repr(C)]
+pub struct NeqoCertificateInfo {
+ certs: ThinVec<ThinVec<u8>>,
+ stapled_ocsp_responses_present: bool,
+ stapled_ocsp_responses: ThinVec<ThinVec<u8>>,
+ signed_cert_timestamp_present: bool,
+ signed_cert_timestamp: ThinVec<u8>,
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_peer_certificate_info(
+ conn: &mut NeqoHttp3Conn,
+ neqo_certs_info: &mut NeqoCertificateInfo,
+) -> nsresult {
+ let mut certs_info = match conn.conn.peer_certificate() {
+ Some(certs) => certs,
+ None => return NS_ERROR_NOT_AVAILABLE,
+ };
+
+ neqo_certs_info.certs = certs_info
+ .map(|cert| cert.iter().cloned().collect())
+ .collect();
+
+ match &mut certs_info.stapled_ocsp_responses() {
+ Some(ocsp_val) => {
+ neqo_certs_info.stapled_ocsp_responses_present = true;
+ neqo_certs_info.stapled_ocsp_responses = ocsp_val
+ .iter()
+ .map(|ocsp| ocsp.iter().cloned().collect())
+ .collect();
+ }
+ None => {
+ neqo_certs_info.stapled_ocsp_responses_present = false;
+ }
+ };
+
+ match certs_info.signed_cert_timestamp() {
+ Some(sct_val) => {
+ neqo_certs_info.signed_cert_timestamp_present = true;
+ neqo_certs_info
+ .signed_cert_timestamp
+ .extend_from_slice(sct_val);
+ }
+ None => {
+ neqo_certs_info.signed_cert_timestamp_present = false;
+ }
+ };
+
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_authenticated(conn: &mut NeqoHttp3Conn, error: PRErrorCode) {
+ conn.conn.authenticated(
+ error.into(),
+ get_current_or_last_output_time(&conn.last_output_time),
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_set_resumption_token(
+ conn: &mut NeqoHttp3Conn,
+ token: &mut ThinVec<u8>,
+) {
+ let _ = conn.conn.enable_resumption(
+ get_current_or_last_output_time(&conn.last_output_time),
+ token,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_set_ech_config(
+ conn: &mut NeqoHttp3Conn,
+ ech_config: &mut ThinVec<u8>,
+) {
+ let _ = conn.conn.enable_ech(ech_config);
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_is_zero_rtt(conn: &mut NeqoHttp3Conn) -> bool {
+ conn.conn.state() == Http3State::ZeroRtt
+}
+
+#[repr(C)]
+#[derive(Default)]
+pub struct Http3Stats {
+ /// Total packets received, including all the bad ones.
+ pub packets_rx: usize,
+ /// Duplicate packets received.
+ pub dups_rx: usize,
+ /// Dropped packets or dropped garbage.
+ pub dropped_rx: usize,
+ /// The number of packet that were saved for later processing.
+ pub saved_datagrams: usize,
+ /// Total packets sent.
+ pub packets_tx: usize,
+ /// Total number of packets that are declared lost.
+ pub lost: usize,
+ /// Late acknowledgments, for packets that were declared lost already.
+ pub late_ack: usize,
+ /// Acknowledgments for packets that contained data that was marked
+ /// for retransmission when the PTO timer popped.
+ pub pto_ack: usize,
+ /// Count PTOs. Single PTOs, 2 PTOs in a row, 3 PTOs in row, etc. are counted
+ /// separately.
+ pub pto_counts: [usize; 16],
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_get_stats(conn: &mut NeqoHttp3Conn, stats: &mut Http3Stats) {
+ let t_stats = conn.conn.transport_stats();
+ stats.packets_rx = t_stats.packets_rx;
+ stats.dups_rx = t_stats.dups_rx;
+ stats.dropped_rx = t_stats.dropped_rx;
+ stats.saved_datagrams = t_stats.saved_datagrams;
+ stats.packets_tx = t_stats.packets_tx;
+ stats.lost = t_stats.lost;
+ stats.late_ack = t_stats.late_ack;
+ stats.pto_ack = t_stats.pto_ack;
+ stats.pto_counts = t_stats.pto_counts;
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_create_session(
+ conn: &mut NeqoHttp3Conn,
+ host: &nsACString,
+ path: &nsACString,
+ headers: &nsACString,
+ stream_id: &mut u64,
+) -> nsresult {
+ let hdrs = match parse_headers(headers) {
+ Err(e) => {
+ return e;
+ }
+ Ok(h) => h,
+ };
+ let host_tmp = match str::from_utf8(host) {
+ Ok(h) => h,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let path_tmp = match str::from_utf8(path) {
+ Ok(p) => p,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+
+ match conn.conn.webtransport_create_session(
+ get_current_or_last_output_time(&conn.last_output_time),
+ &("https", host_tmp, path_tmp),
+ &hdrs,
+ ) {
+ Ok(id) => {
+ *stream_id = id.as_u64();
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_close_session(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ error: u32,
+ message: &nsACString,
+) -> nsresult {
+ let message_tmp = match str::from_utf8(message) {
+ Ok(p) => p,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ match conn
+ .conn
+ .webtransport_close_session(StreamId::from(session_id), error, message_tmp)
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_create_stream(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ stream_type: WebTransportStreamType,
+ stream_id: &mut u64,
+) -> nsresult {
+ match conn
+ .conn
+ .webtransport_create_stream(StreamId::from(session_id), stream_type.into())
+ {
+ Ok(id) => {
+ *stream_id = id.as_u64();
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_send_datagram(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ data: &mut ThinVec<u8>,
+ tracking_id: u64,
+) -> nsresult {
+ let id = if tracking_id == 0 {
+ None
+ } else {
+ Some(tracking_id)
+ };
+ match conn
+ .conn
+ .webtransport_send_datagram(StreamId::from(session_id), data, id)
+ {
+ Ok(()) => NS_OK,
+ Err(Http3Error::TransportError(TransportError::TooMuchData)) => NS_ERROR_NOT_AVAILABLE,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_max_datagram_size(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ result: &mut u64,
+) -> nsresult {
+ match conn
+ .conn
+ .webtransport_max_datagram_size(StreamId::from(session_id))
+ {
+ Ok(size) => {
+ *result = size;
+ NS_OK
+ }
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_set_sendorder(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ sendorder: *const i64,
+) -> nsresult {
+ unsafe {
+ match conn
+ .conn
+ .webtransport_set_sendorder(StreamId::from(stream_id), sendorder.as_ref().copied())
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+ }
+}
diff --git a/netwerk/socket/nsINamedPipeService.idl b/netwerk/socket/nsINamedPipeService.idl
new file mode 100644
index 0000000000..9db557577d
--- /dev/null
+++ b/netwerk/socket/nsINamedPipeService.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsrootidl.idl"
+
+/**
+ * nsINamedPipeDataObserver
+ *
+ * This is the callback interface for nsINamedPipeService.
+ * The functions are called by the internal thread in the nsINamedPipeService.
+ */
+[scriptable, uuid(de4f460b-94fd-442c-9002-1637beb2185a)]
+interface nsINamedPipeDataObserver : nsISupports
+{
+ /**
+ * onDataAvailable
+ *
+ * @param aBytesTransferred
+ * Transfered bytes during last transmission.
+ * @param aOverlapped
+ * Corresponding overlapped structure used by the async I/O
+ */
+ void onDataAvailable(in unsigned long aBytesTransferred,
+ in voidPtr aOverlapped);
+
+ /**
+ * onError
+ *
+ * @param aError
+ * Error code of the error.
+ * @param aOverlapped
+ * Corresponding overlapped structure used by the async I/O
+ */
+ void onError(in unsigned long aError,
+ in voidPtr aOverlapped);
+};
+
+/**
+ * nsINamedPipeService
+ */
+[scriptable, uuid(1bf19133-5625-4ac8-836a-80b1c215f72b)]
+interface nsINamedPipeService : nsISupports
+{
+ /**
+ * addDataObserver
+ *
+ * @param aHandle
+ * The handle that is going to be monitored for read/write operations.
+ * Only handles that are opened with overlapped IO are supported.
+ * @param aObserver
+ * The observer of the handle, the service strong-refs of the observer.
+ */
+ void addDataObserver(in voidPtr aHandle,
+ in nsINamedPipeDataObserver aObserver);
+
+ /**
+ * removeDataObserver
+ *
+ * @param aHandle
+ The handle associated to the observer, and will be closed by the
+ service.
+ * @param aObserver
+ * The observer to be removed.
+ */
+ void removeDataObserver(in voidPtr aHandle,
+ in nsINamedPipeDataObserver aObserver);
+
+ /**
+ * isOnCurrentThread
+ *
+ * @return the caller runs within the internal thread.
+ */
+ boolean isOnCurrentThread();
+};
diff --git a/netwerk/socket/nsISocketProvider.idl b/netwerk/socket/nsISocketProvider.idl
new file mode 100644
index 0000000000..1f19b932f9
--- /dev/null
+++ b/netwerk/socket/nsISocketProvider.idl
@@ -0,0 +1,145 @@
+/* -*- 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 nsIProxyInfo;
+interface nsITLSSocketControl;
+[ptr] native PRFileDescStar(struct PRFileDesc);
+native OriginAttributes(mozilla::OriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+%}
+
+/**
+ * nsISocketProvider
+ */
+[scriptable, uuid(508d5469-9e1e-4a08-b5b0-7cfebba1e51a)]
+interface nsISocketProvider : nsISupports
+{
+ /**
+ * newSocket
+ *
+ * @param aFamily
+ * The address family for this socket (PR_AF_INET or PR_AF_INET6).
+ * @param aHost
+ * The origin hostname for this connection.
+ * @param aPort
+ * The origin port for this connection.
+ * @param aProxyHost
+ * If non-null, the proxy hostname for this connection.
+ * @param aProxyPort
+ * The proxy port for this connection.
+ * @param aFlags
+ * Control flags that govern this connection (see below.)
+ * @param aTlsFlags
+ * An opaque flags for non-standard behavior of the TLS system.
+ * It is unlikely this will need to be set outside of telemetry
+ * studies relating to the TLS implementation.
+ * @param aFileDesc
+ * The resulting PRFileDesc.
+ * @param aTLSSocketControl
+ * TLS socket control object that should be associated with
+ * aFileDesc, if applicable.
+ */
+ [noscript]
+ void newSocket(in long aFamily,
+ in string aHost,
+ in long aPort,
+ in nsIProxyInfo aProxy,
+ in const_OriginAttributesRef aOriginAttributes,
+ in unsigned long aFlags,
+ in unsigned long aTlsFlags,
+ out PRFileDescStar aFileDesc,
+ out nsITLSSocketControl aTLSSocketControl);
+
+ /**
+ * addToSocket
+ *
+ * This function is called to allow the socket provider to layer a
+ * PRFileDesc on top of another PRFileDesc. For example, SSL via a SOCKS
+ * proxy.
+ *
+ * Parameters are the same as newSocket with the exception of aFileDesc,
+ * which is an in-param instead.
+ */
+ [noscript]
+ void addToSocket(in long aFamily,
+ in string aHost,
+ in long aPort,
+ in nsIProxyInfo aProxy,
+ in const_OriginAttributesRef aOriginAttributes,
+ in unsigned long aFlags,
+ in unsigned long aTlsFlags,
+ in PRFileDescStar aFileDesc,
+ out nsITLSSocketControl aTLSSocketControl);
+
+ /**
+ * PROXY_RESOLVES_HOST
+ *
+ * This flag is set if the proxy is to perform hostname resolution instead
+ * of the client. When set, the hostname parameter passed when in this
+ * interface will be used instead of the address structure passed for a
+ * later connect et al. request.
+ */
+ const long PROXY_RESOLVES_HOST = 1 << 0;
+
+ /**
+ * When setting this flag, the socket will not apply any
+ * credentials when establishing a connection. For example,
+ * an SSL connection would not send any client-certificates
+ * if this flag is set.
+ */
+ const long ANONYMOUS_CONNECT = 1 << 1;
+
+ /**
+ * If set, indicates that the connection was initiated from a source
+ * defined as being private in the sense of Private Browsing. Generally,
+ * there should be no state shared between connections that are private
+ * and those that are not; it is OK for multiple private connections
+ * to share state with each other, and it is OK for multiple non-private
+ * connections to share state with each other.
+ */
+ const unsigned long NO_PERMANENT_STORAGE = 1 << 2;
+
+ /**
+ * If set, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ const unsigned long BE_CONSERVATIVE = 1 << 3;
+
+ /**
+ * This is used for a temporary workaround for a web-compat issue. The flag is
+ * only set on CORS preflight request to allowed sending client certificates
+ * on a connection for an anonymous request.
+ */
+ const long ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT = 1 << 4;
+
+ /**
+ * If set, indicates that this is a speculative connection.
+ */
+ const unsigned long IS_SPECULATIVE_CONNECTION = 1 << 5;
+
+ /**
+ * If set, do not send an ECH extension (whether GREASE or 'real').
+ * Currently false by default and is set when retrying failed connections.
+ */
+ const unsigned long DONT_TRY_ECH = (1 << 10);
+
+ /**
+ * If set, indicates that the connection is a retry.
+ */
+ const unsigned long IS_RETRY = (1 << 11);
+
+ /**
+ * If set, indicates that the connection used a privacy-preserving DNS
+ * transport such as DoH, DoQ or similar. Currently this field is
+ * set only when DoH is used via the TRR.
+ */
+ const unsigned long USED_PRIVATE_DNS = (1 << 12);
+};
diff --git a/netwerk/socket/nsISocketProviderService.idl b/netwerk/socket/nsISocketProviderService.idl
new file mode 100644
index 0000000000..db38d299a5
--- /dev/null
+++ b/netwerk/socket/nsISocketProviderService.idl
@@ -0,0 +1,20 @@
+/* -*- 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 nsISocketProvider;
+
+/**
+ * nsISocketProviderService
+ *
+ * Provides a mapping between a socket type and its associated socket provider
+ * instance. One could also use the service manager directly.
+ */
+[scriptable, uuid(8f8a23d0-5472-11d3-bbc8-0000861d1237)]
+interface nsISocketProviderService : nsISupports
+{
+ nsISocketProvider getSocketProvider(in string socketType);
+};
diff --git a/netwerk/socket/nsNamedPipeIOLayer.cpp b/netwerk/socket/nsNamedPipeIOLayer.cpp
new file mode 100644
index 0000000000..d229f0e23e
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeIOLayer.cpp
@@ -0,0 +1,864 @@
+/* -*- 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 "nsNamedPipeIOLayer.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/DNS.h"
+#include "nsISupportsImpl.h"
+#include "nsNamedPipeService.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nspr.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+static mozilla::LazyLogModule gNamedPipeLog("NamedPipeWin");
+#define LOG_NPIO_DEBUG(...) \
+ MOZ_LOG(gNamedPipeLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#define LOG_NPIO_ERROR(...) \
+ MOZ_LOG(gNamedPipeLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+PRDescIdentity nsNamedPipeLayerIdentity;
+static PRIOMethods nsNamedPipeLayerMethods;
+
+class NamedPipeInfo final : public nsINamedPipeDataObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINAMEDPIPEDATAOBSERVER
+
+ explicit NamedPipeInfo();
+
+ nsresult Connect(const nsAString& aPath);
+ nsresult Disconnect();
+
+ /**
+ * Both blocking/non-blocking mode are supported in this class.
+ * The default mode is non-blocking mode, however, the client may change its
+ * mode to blocking mode during hand-shaking (e.g. nsSOCKSSocketInfo).
+ *
+ * In non-blocking mode, |Read| and |Write| should be called by clients only
+ * when |GetPollFlags| reports data availability. That is, the client calls
+ * |GetPollFlags| with |PR_POLL_READ| and/or |PR_POLL_WRITE| set, and
+ * according to the flags that set, |GetPollFlags| will check buffers status
+ * and decide corresponding actions:
+ *
+ * -------------------------------------------------------------------
+ * | | data in buffer | empty buffer |
+ * |---------------+-------------------------+-----------------------|
+ * | PR_POLL_READ | out: PR_POLL_READ | DoRead/DoReadContinue |
+ * |---------------+-------------------------+-----------------------|
+ * | PR_POLL_WRITE | DoWrite/DoWriteContinue | out: PR_POLL_WRITE |
+ * ------------------------------------------+------------------------
+ *
+ * |DoRead| and |DoWrite| initiate read/write operations asynchronously, and
+ * the |DoReadContinue| and |DoWriteContinue| are used to check the amount
+ * of the data are read/written to/from buffers.
+ *
+ * The output parameter and the return value of |GetPollFlags| are identical
+ * because we don't rely on the low-level select function to wait for data
+ * availability, we instead use nsNamedPipeService to poll I/O completeness.
+ *
+ * When client get |PR_POLL_READ| or |PR_POLL_WRITE| from |GetPollFlags|,
+ * they are able to use |Read| or |Write| to access the data in the buffer,
+ * and this is supposed to be very fast because no network traffic is
+ * involved.
+ *
+ * In blocking mode, the flow is quite similar to non-blocking mode, but
+ * |DoReadContinue| and |DoWriteContinue| are never been used since the
+ * operations are done synchronously, which could lead to slow responses.
+ */
+ int32_t Read(void* aBuffer, int32_t aSize);
+ int32_t Write(const void* aBuffer, int32_t aSize);
+
+ // Like Read, but doesn't remove data in internal buffer.
+ uint32_t Peek(void* aBuffer, int32_t aSize);
+
+ // Number of bytes available to read in internal buffer.
+ int32_t Available() const;
+
+ // Flush write buffer
+ //
+ // @return whether the buffer has been flushed
+ bool Sync(uint32_t aTimeout);
+ void SetNonblocking(bool nonblocking);
+
+ bool IsConnected() const;
+ bool IsNonblocking() const;
+ HANDLE GetHandle() const;
+
+ // Initiate and check current status for read/write operations.
+ int16_t GetPollFlags(int16_t aInFlags, int16_t* aOutFlags);
+
+ private:
+ virtual ~NamedPipeInfo();
+
+ /**
+ * DoRead/DoWrite starts a read/write call synchronously or asynchronously
+ * depending on |mNonblocking|. In blocking mode, they return when the action
+ * has been done and in non-blocking mode it returns the number of bytes that
+ * were read/written if the operation is done immediately. If it takes some
+ * time to finish the operation, zero is returned and
+ * DoReadContinue/DoWriteContinue must be called to get async I/O result.
+ */
+ int32_t DoRead();
+ int32_t DoReadContinue();
+ int32_t DoWrite();
+ int32_t DoWriteContinue();
+
+ /**
+ * There was a write size limitation of named pipe,
+ * see https://support.microsoft.com/en-us/kb/119218 for more information.
+ * The limitation no longer exists, so feel free to change the value.
+ */
+ static const uint32_t kBufferSize = 65536;
+
+ nsCOMPtr<nsINamedPipeService> mNamedPipeService;
+
+ HANDLE mPipe; // the handle to the named pipe.
+ OVERLAPPED mReadOverlapped; // used for asynchronous read operations.
+ OVERLAPPED mWriteOverlapped; // used for asynchronous write operations.
+
+ uint8_t mReadBuffer[kBufferSize]; // octets read from pipe.
+
+ /**
+ * These indicates the [begin, end) position of the data in the buffer.
+ */
+ DWORD mReadBegin;
+ DWORD mReadEnd;
+
+ bool mHasPendingRead; // previous asynchronous read is not finished yet.
+
+ uint8_t mWriteBuffer[kBufferSize]; // octets to be written to pipe.
+
+ /**
+ * These indicates the [begin, end) position of the data in the buffer.
+ */
+ DWORD mWriteBegin; // how many bytes are already written.
+ DWORD mWriteEnd; // valid amount of data in the buffer.
+
+ bool mHasPendingWrite; // previous asynchronous write is not finished yet.
+
+ /**
+ * current blocking mode is non-blocking or not, accessed only in socket
+ * thread.
+ */
+ bool mNonblocking;
+
+ Atomic<DWORD> mErrorCode; // error code from Named Pipe Service.
+};
+
+NS_IMPL_ISUPPORTS(NamedPipeInfo, nsINamedPipeDataObserver)
+
+NamedPipeInfo::NamedPipeInfo()
+ : mNamedPipeService(NamedPipeService::GetOrCreate()),
+ mPipe(INVALID_HANDLE_VALUE),
+ mReadBegin(0),
+ mReadEnd(0),
+ mHasPendingRead(false),
+ mWriteBegin(0),
+ mWriteEnd(0),
+ mHasPendingWrite(false),
+ mNonblocking(true),
+ mErrorCode(0) {
+ MOZ_ASSERT(mNamedPipeService);
+
+ ZeroMemory(&mReadOverlapped, sizeof(OVERLAPPED));
+ ZeroMemory(&mWriteOverlapped, sizeof(OVERLAPPED));
+}
+
+NamedPipeInfo::~NamedPipeInfo() { MOZ_ASSERT(!mPipe); }
+
+// nsINamedPipeDataObserver
+
+NS_IMETHODIMP
+NamedPipeInfo::OnDataAvailable(uint32_t aBytesTransferred, void* aOverlapped) {
+ DebugOnly<bool> isOnPipeServiceThread;
+ MOZ_ASSERT(NS_SUCCEEDED(mNamedPipeService->IsOnCurrentThread(
+ &isOnPipeServiceThread)) &&
+ isOnPipeServiceThread);
+
+ if (aOverlapped == &mReadOverlapped) {
+ LOG_NPIO_DEBUG("[%s] %p read %d bytes", __func__, this, aBytesTransferred);
+ } else if (aOverlapped == &mWriteOverlapped) {
+ LOG_NPIO_DEBUG("[%s] %p write %d bytes", __func__, this, aBytesTransferred);
+ } else {
+ MOZ_ASSERT(false, "invalid callback");
+ mErrorCode = ERROR_INVALID_DATA;
+ return NS_ERROR_FAILURE;
+ }
+
+ mErrorCode = ERROR_SUCCESS;
+
+ // dispatch an empty event to trigger STS thread
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("NamedPipeInfo::OnDataAvailable", [] {}),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NamedPipeInfo::OnError(uint32_t aError, void* aOverlapped) {
+ DebugOnly<bool> isOnPipeServiceThread;
+ MOZ_ASSERT(NS_SUCCEEDED(mNamedPipeService->IsOnCurrentThread(
+ &isOnPipeServiceThread)) &&
+ isOnPipeServiceThread);
+
+ LOG_NPIO_ERROR("[%s] error code=%d", __func__, aError);
+ mErrorCode = aError;
+
+ // dispatch an empty event to trigger STS thread
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("NamedPipeInfo::OnError", [] {}),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+// Named pipe operations
+
+nsresult NamedPipeInfo::Connect(const nsAString& aPath) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ HANDLE pipe =
+ CreateFileW(PromiseFlatString(aPath).get(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, nullptr);
+
+ if (pipe == INVALID_HANDLE_VALUE) {
+ LOG_NPIO_ERROR("[%p] CreateFile error (%lu)", this, GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD pipeMode = PIPE_READMODE_MESSAGE;
+ if (!SetNamedPipeHandleState(pipe, &pipeMode, nullptr, nullptr)) {
+ LOG_NPIO_ERROR("[%p] SetNamedPipeHandleState error (%lu)", this,
+ GetLastError());
+ CloseHandle(pipe);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = mNamedPipeService->AddDataObserver(pipe, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseHandle(pipe);
+ return rv;
+ }
+
+ HANDLE readEvent = CreateEventA(nullptr, TRUE, TRUE, "NamedPipeRead");
+ if (NS_WARN_IF(!readEvent || readEvent == INVALID_HANDLE_VALUE)) {
+ CloseHandle(pipe);
+ return NS_ERROR_FAILURE;
+ }
+
+ HANDLE writeEvent = CreateEventA(nullptr, TRUE, TRUE, "NamedPipeWrite");
+ if (NS_WARN_IF(!writeEvent || writeEvent == INVALID_HANDLE_VALUE)) {
+ CloseHandle(pipe);
+ CloseHandle(readEvent);
+ return NS_ERROR_FAILURE;
+ }
+
+ mPipe = pipe;
+ mReadOverlapped.hEvent = readEvent;
+ mWriteOverlapped.hEvent = writeEvent;
+ return NS_OK;
+}
+
+nsresult NamedPipeInfo::Disconnect() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv = mNamedPipeService->RemoveDataObserver(mPipe, this);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ mPipe = nullptr;
+
+ if (mReadOverlapped.hEvent &&
+ mReadOverlapped.hEvent != INVALID_HANDLE_VALUE) {
+ CloseHandle(mReadOverlapped.hEvent);
+ mReadOverlapped.hEvent = nullptr;
+ }
+
+ if (mWriteOverlapped.hEvent &&
+ mWriteOverlapped.hEvent != INVALID_HANDLE_VALUE) {
+ CloseHandle(mWriteOverlapped.hEvent);
+ mWriteOverlapped.hEvent = nullptr;
+ }
+
+ return rv;
+}
+
+int32_t NamedPipeInfo::Read(void* aBuffer, int32_t aSize) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ int32_t bytesRead = Peek(aBuffer, aSize);
+
+ if (bytesRead > 0) {
+ mReadBegin += bytesRead;
+ }
+
+ return bytesRead;
+}
+
+int32_t NamedPipeInfo::Write(const void* aBuffer, int32_t aSize) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mWriteBegin <= mWriteEnd);
+
+ if (!IsConnected()) {
+ // pipe unconnected
+ PR_SetError(PR_NOT_CONNECTED_ERROR, 0);
+ return -1;
+ }
+
+ if (mWriteBegin == mWriteEnd) {
+ mWriteBegin = mWriteEnd = 0;
+ }
+
+ int32_t bytesToWrite =
+ std::min<int32_t>(aSize, sizeof(mWriteBuffer) - mWriteEnd);
+ MOZ_ASSERT(bytesToWrite >= 0);
+
+ if (bytesToWrite == 0) {
+ PR_SetError(IsNonblocking() ? PR_WOULD_BLOCK_ERROR : PR_IO_PENDING_ERROR,
+ 0);
+ return -1;
+ }
+
+ memcpy(&mWriteBuffer[mWriteEnd], aBuffer, bytesToWrite);
+ mWriteEnd += bytesToWrite;
+
+ /**
+ * Triggers internal write operation by calling |GetPollFlags|.
+ * This is required for callers that use blocking I/O because they don't call
+ * |GetPollFlags| to write data, but this also works for non-blocking I/O.
+ */
+ int16_t outFlag;
+ GetPollFlags(PR_POLL_WRITE, &outFlag);
+
+ return bytesToWrite;
+}
+
+uint32_t NamedPipeInfo::Peek(void* aBuffer, int32_t aSize) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mReadBegin <= mReadEnd);
+
+ if (!IsConnected()) {
+ // pipe unconnected
+ PR_SetError(PR_NOT_CONNECTED_ERROR, 0);
+ return -1;
+ }
+
+ /**
+ * If there's nothing in the read buffer, try to trigger internal read
+ * operation by calling |GetPollFlags|. This is required for callers that
+ * use blocking I/O because they don't call |GetPollFlags| to read data,
+ * but this also works for non-blocking I/O.
+ */
+ if (!Available()) {
+ int16_t outFlag;
+ GetPollFlags(PR_POLL_READ, &outFlag);
+
+ if (!(outFlag & PR_POLL_READ)) {
+ PR_SetError(IsNonblocking() ? PR_WOULD_BLOCK_ERROR : PR_IO_PENDING_ERROR,
+ 0);
+ return -1;
+ }
+ }
+
+ // Available() can't return more than what fits to the buffer at the read
+ // offset.
+ int32_t bytesRead = std::min<int32_t>(aSize, Available());
+ MOZ_ASSERT(bytesRead >= 0);
+ MOZ_ASSERT(mReadBegin + bytesRead <= mReadEnd);
+ memcpy(aBuffer, &mReadBuffer[mReadBegin], bytesRead);
+ return bytesRead;
+}
+
+int32_t NamedPipeInfo::Available() const {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mReadBegin <= mReadEnd);
+ MOZ_ASSERT(mReadEnd - mReadBegin <= 0x7FFFFFFF); // no more than int32_max
+ return mReadEnd - mReadBegin;
+}
+
+bool NamedPipeInfo::Sync(uint32_t aTimeout) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mHasPendingWrite) {
+ return true;
+ }
+ return WaitForSingleObject(mWriteOverlapped.hEvent, aTimeout) ==
+ WAIT_OBJECT_0;
+}
+
+void NamedPipeInfo::SetNonblocking(bool nonblocking) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mNonblocking = nonblocking;
+}
+
+bool NamedPipeInfo::IsConnected() const {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mPipe && mPipe != INVALID_HANDLE_VALUE;
+}
+
+bool NamedPipeInfo::IsNonblocking() const {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mNonblocking;
+}
+
+HANDLE
+NamedPipeInfo::GetHandle() const {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mPipe;
+}
+
+int16_t NamedPipeInfo::GetPollFlags(int16_t aInFlags, int16_t* aOutFlags) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ *aOutFlags = 0;
+
+ if (aInFlags & PR_POLL_READ) {
+ int32_t bytesToRead = 0;
+ if (mReadBegin < mReadEnd) { // data in buffer and is ready to be read
+ bytesToRead = Available();
+ } else if (mHasPendingRead) { // nonblocking I/O and has pending task
+ bytesToRead = DoReadContinue();
+ } else { // read bufer is empty.
+ bytesToRead = DoRead();
+ }
+
+ if (bytesToRead > 0) {
+ *aOutFlags |= PR_POLL_READ;
+ } else if (bytesToRead < 0) {
+ *aOutFlags |= PR_POLL_ERR;
+ }
+ }
+
+ if (aInFlags & PR_POLL_WRITE) {
+ int32_t bytesWritten = 0;
+ if (mHasPendingWrite) { // nonblocking I/O and has pending task.
+ bytesWritten = DoWriteContinue();
+ } else if (mWriteBegin < mWriteEnd) { // data in buffer, ready to write
+ bytesWritten = DoWrite();
+ } else { // write buffer is empty.
+ *aOutFlags |= PR_POLL_WRITE;
+ }
+
+ if (bytesWritten < 0) {
+ *aOutFlags |= PR_POLL_ERR;
+ } else if (bytesWritten && !mHasPendingWrite && mWriteBegin == mWriteEnd) {
+ *aOutFlags |= PR_POLL_WRITE;
+ }
+ }
+
+ return *aOutFlags;
+}
+
+// @return: data has been read and is available
+int32_t NamedPipeInfo::DoRead() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mHasPendingRead);
+ MOZ_ASSERT(mReadBegin == mReadEnd); // the buffer should be empty
+
+ mReadBegin = 0;
+ mReadEnd = 0;
+
+ BOOL success = ReadFile(mPipe, mReadBuffer, sizeof(mReadBuffer), &mReadEnd,
+ IsNonblocking() ? &mReadOverlapped : nullptr);
+
+ if (success) {
+ LOG_NPIO_DEBUG("[%s][%p] %lu bytes read", __func__, this, mReadEnd);
+ return mReadEnd;
+ }
+
+ switch (GetLastError()) {
+ case ERROR_MORE_DATA: // has more data to read
+ mHasPendingRead = true;
+ return DoReadContinue();
+
+ case ERROR_IO_PENDING: // read is pending
+ mHasPendingRead = true;
+ break;
+
+ default:
+ LOG_NPIO_ERROR("[%s] ReadFile failed (%lu)", __func__, GetLastError());
+ Disconnect();
+ PR_SetError(PR_IO_ERROR, 0);
+ return -1;
+ }
+
+ return 0;
+}
+
+int32_t NamedPipeInfo::DoReadContinue() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mHasPendingRead);
+ MOZ_ASSERT(mReadBegin == 0 && mReadEnd == 0);
+
+ BOOL success;
+ success = GetOverlappedResult(mPipe, &mReadOverlapped, &mReadEnd, FALSE);
+ if (success) {
+ mHasPendingRead = false;
+ if (mReadEnd == 0) {
+ Disconnect();
+ PR_SetError(PR_NOT_CONNECTED_ERROR, 0);
+ return -1;
+ }
+
+ LOG_NPIO_DEBUG("[%s][%p] %lu bytes read", __func__, this, mReadEnd);
+ return mReadEnd;
+ }
+
+ switch (GetLastError()) {
+ case ERROR_MORE_DATA:
+ mHasPendingRead = false;
+ LOG_NPIO_DEBUG("[%s][%p] %lu bytes read", __func__, this, mReadEnd);
+ return mReadEnd;
+ case ERROR_IO_INCOMPLETE: // still in progress
+ break;
+ default:
+ LOG_NPIO_ERROR("[%s]: GetOverlappedResult failed (%lu)", __func__,
+ GetLastError());
+ Disconnect();
+ PR_SetError(PR_IO_ERROR, 0);
+ return -1;
+ }
+
+ return 0;
+}
+
+int32_t NamedPipeInfo::DoWrite() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mHasPendingWrite);
+ MOZ_ASSERT(mWriteBegin < mWriteEnd);
+
+ DWORD bytesWritten = 0;
+ BOOL success =
+ WriteFile(mPipe, &mWriteBuffer[mWriteBegin], mWriteEnd - mWriteBegin,
+ &bytesWritten, IsNonblocking() ? &mWriteOverlapped : nullptr);
+
+ if (success) {
+ mWriteBegin += bytesWritten;
+ LOG_NPIO_DEBUG("[%s][%p] %lu bytes written", __func__, this, bytesWritten);
+ return bytesWritten;
+ }
+
+ if (GetLastError() != ERROR_IO_PENDING) {
+ LOG_NPIO_ERROR("[%s] WriteFile failed (%lu)", __func__, GetLastError());
+ Disconnect();
+ PR_SetError(PR_IO_ERROR, 0);
+ return -1;
+ }
+
+ mHasPendingWrite = true;
+
+ return 0;
+}
+
+int32_t NamedPipeInfo::DoWriteContinue() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mHasPendingWrite);
+
+ DWORD bytesWritten = 0;
+ BOOL success =
+ GetOverlappedResult(mPipe, &mWriteOverlapped, &bytesWritten, FALSE);
+
+ if (!success) {
+ if (GetLastError() == ERROR_IO_INCOMPLETE) {
+ // still in progress
+ return 0;
+ }
+
+ LOG_NPIO_ERROR("[%s] GetOverlappedResult failed (%lu)", __func__,
+ GetLastError());
+ Disconnect();
+ PR_SetError(PR_IO_ERROR, 0);
+ return -1;
+ }
+
+ mHasPendingWrite = false;
+ mWriteBegin += bytesWritten;
+ LOG_NPIO_DEBUG("[%s][%p] %lu bytes written", __func__, this, bytesWritten);
+ return bytesWritten;
+}
+
+static inline NamedPipeInfo* GetNamedPipeInfo(PRFileDesc* aFd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_DIAGNOSTIC_ASSERT(aFd);
+ MOZ_DIAGNOSTIC_ASSERT(aFd->secret);
+ MOZ_DIAGNOSTIC_ASSERT(PR_GetLayersIdentity(aFd) == nsNamedPipeLayerIdentity);
+
+ if (!aFd || !aFd->secret ||
+ PR_GetLayersIdentity(aFd) != nsNamedPipeLayerIdentity) {
+ LOG_NPIO_ERROR("cannot get named pipe info");
+ return nullptr;
+ }
+
+ return reinterpret_cast<NamedPipeInfo*>(aFd->secret);
+}
+
+static PRStatus nsNamedPipeConnect(PRFileDesc* aFd, const PRNetAddr* aAddr,
+ PRIntervalTime aTimeout) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return PR_FAILURE;
+ }
+
+ nsAutoString path;
+ if (NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(aAddr->local.path),
+ path))) {
+ PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+ return PR_FAILURE;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->Connect(path)))) {
+ return PR_FAILURE;
+ }
+
+ return PR_SUCCESS;
+}
+
+static PRStatus nsNamedPipeConnectContinue(PRFileDesc* aFd, PRInt16 aOutFlags) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return PR_SUCCESS;
+}
+
+static PRStatus nsNamedPipeClose(PRFileDesc* aFd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (aFd->secret && PR_GetLayersIdentity(aFd) == nsNamedPipeLayerIdentity) {
+ RefPtr<NamedPipeInfo> info = dont_AddRef(GetNamedPipeInfo(aFd));
+ info->Disconnect();
+ aFd->secret = nullptr;
+ aFd->identity = PR_INVALID_IO_LAYER;
+ }
+
+ MOZ_ASSERT(!aFd->lower);
+ PR_Free(aFd); // PRFileDescs are allocated with PR_Malloc().
+
+ return PR_SUCCESS;
+}
+
+static PRInt32 nsNamedPipeSend(PRFileDesc* aFd, const void* aBuffer,
+ PRInt32 aAmount, PRIntn aFlags,
+ PRIntervalTime aTimeout) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ Unused << aFlags;
+ Unused << aTimeout;
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return -1;
+ }
+ return info->Write(aBuffer, aAmount);
+}
+
+static PRInt32 nsNamedPipeRecv(PRFileDesc* aFd, void* aBuffer, PRInt32 aAmount,
+ PRIntn aFlags, PRIntervalTime aTimeout) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ Unused << aTimeout;
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return -1;
+ }
+
+ if (aFlags) {
+ if (aFlags != PR_MSG_PEEK) {
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ return -1;
+ }
+ return info->Peek(aBuffer, aAmount);
+ }
+
+ return info->Read(aBuffer, aAmount);
+}
+
+static inline PRInt32 nsNamedPipeRead(PRFileDesc* aFd, void* aBuffer,
+ PRInt32 aAmount) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return -1;
+ }
+ return info->Read(aBuffer, aAmount);
+}
+
+static inline PRInt32 nsNamedPipeWrite(PRFileDesc* aFd, const void* aBuffer,
+ PRInt32 aAmount) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return -1;
+ }
+ return info->Write(aBuffer, aAmount);
+}
+
+static PRInt32 nsNamedPipeAvailable(PRFileDesc* aFd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return -1;
+ }
+ return static_cast<PRInt32>(info->Available());
+}
+
+static PRInt64 nsNamedPipeAvailable64(PRFileDesc* aFd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return -1;
+ }
+ return static_cast<PRInt64>(info->Available());
+}
+
+static PRStatus nsNamedPipeSync(PRFileDesc* aFd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return PR_FAILURE;
+ }
+ return info->Sync(0) ? PR_SUCCESS : PR_FAILURE;
+}
+
+static PRInt16 nsNamedPipePoll(PRFileDesc* aFd, PRInt16 aInFlags,
+ PRInt16* aOutFlags) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return 0;
+ }
+ return info->GetPollFlags(aInFlags, aOutFlags);
+}
+
+// FIXME: remove socket option functions?
+static PRStatus nsNamedPipeGetSocketOption(PRFileDesc* aFd,
+ PRSocketOptionData* aData) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_ASSERT(aFd);
+ MOZ_ASSERT(aData);
+
+ switch (aData->option) {
+ case PR_SockOpt_Nonblocking:
+ aData->value.non_blocking =
+ GetNamedPipeInfo(aFd)->IsNonblocking() ? PR_TRUE : PR_FALSE;
+ break;
+ case PR_SockOpt_Keepalive:
+ aData->value.keep_alive = PR_TRUE;
+ break;
+ case PR_SockOpt_NoDelay:
+ aData->value.no_delay = PR_TRUE;
+ break;
+ default:
+ PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+ return PR_FAILURE;
+ }
+
+ return PR_SUCCESS;
+}
+
+static PRStatus nsNamedPipeSetSocketOption(PRFileDesc* aFd,
+ const PRSocketOptionData* aData) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_ASSERT(aFd);
+ MOZ_ASSERT(aData);
+
+ switch (aData->option) {
+ case PR_SockOpt_Nonblocking:
+ GetNamedPipeInfo(aFd)->SetNonblocking(aData->value.non_blocking);
+ break;
+ case PR_SockOpt_Keepalive:
+ case PR_SockOpt_NoDelay:
+ break;
+ default:
+ PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+ return PR_FAILURE;
+ }
+
+ return PR_SUCCESS;
+}
+
+static void Initialize() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ static bool initialized = false;
+ if (initialized) {
+ return;
+ }
+
+ nsNamedPipeLayerIdentity = PR_GetUniqueIdentity("Named Pipe layer");
+ nsNamedPipeLayerMethods = *PR_GetDefaultIOMethods();
+ nsNamedPipeLayerMethods.close = nsNamedPipeClose;
+ nsNamedPipeLayerMethods.read = nsNamedPipeRead;
+ nsNamedPipeLayerMethods.write = nsNamedPipeWrite;
+ nsNamedPipeLayerMethods.available = nsNamedPipeAvailable;
+ nsNamedPipeLayerMethods.available64 = nsNamedPipeAvailable64;
+ nsNamedPipeLayerMethods.fsync = nsNamedPipeSync;
+ nsNamedPipeLayerMethods.connect = nsNamedPipeConnect;
+ nsNamedPipeLayerMethods.recv = nsNamedPipeRecv;
+ nsNamedPipeLayerMethods.send = nsNamedPipeSend;
+ nsNamedPipeLayerMethods.poll = nsNamedPipePoll;
+ nsNamedPipeLayerMethods.getsocketoption = nsNamedPipeGetSocketOption;
+ nsNamedPipeLayerMethods.setsocketoption = nsNamedPipeSetSocketOption;
+ nsNamedPipeLayerMethods.connectcontinue = nsNamedPipeConnectContinue;
+
+ initialized = true;
+}
+
+bool IsNamedPipePath(const nsACString& aPath) {
+ return StringBeginsWith(aPath, "\\\\.\\pipe\\"_ns);
+}
+
+PRFileDesc* CreateNamedPipeLayer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ Initialize();
+
+ PRFileDesc* layer =
+ PR_CreateIOLayerStub(nsNamedPipeLayerIdentity, &nsNamedPipeLayerMethods);
+ if (NS_WARN_IF(!layer)) {
+ LOG_NPIO_ERROR("CreateNamedPipeLayer() failed.");
+ return nullptr;
+ }
+
+ RefPtr<NamedPipeInfo> info = new NamedPipeInfo();
+ layer->secret = reinterpret_cast<PRFilePrivate*>(info.forget().take());
+
+ return layer;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/socket/nsNamedPipeIOLayer.h b/netwerk/socket/nsNamedPipeIOLayer.h
new file mode 100644
index 0000000000..87598324b6
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeIOLayer.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_netwerk_socket_nsNamedPipeIOLayer_h
+#define mozilla_netwerk_socket_nsNamedPipeIOLayer_h
+
+#include "nscore.h"
+#include "nsStringFwd.h"
+#include "prio.h"
+
+namespace mozilla {
+namespace net {
+
+bool IsNamedPipePath(const nsACString& aPath);
+PRFileDesc* CreateNamedPipeLayer();
+
+extern PRDescIdentity nsNamedPipeLayerIdentity;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_socket_nsNamedPipeIOLayer_h
diff --git a/netwerk/socket/nsNamedPipeService.cpp b/netwerk/socket/nsNamedPipeService.cpp
new file mode 100644
index 0000000000..a713a34c1d
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeService.cpp
@@ -0,0 +1,319 @@
+/* -*- 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/Services.h"
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsNamedPipeService.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace net {
+
+static mozilla::LazyLogModule gNamedPipeServiceLog("NamedPipeWin");
+#define LOG_NPS_DEBUG(...) \
+ MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#define LOG_NPS_ERROR(...) \
+ MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+StaticRefPtr<NamedPipeService> NamedPipeService::gSingleton;
+
+NS_IMPL_ISUPPORTS(NamedPipeService, nsINamedPipeService, nsIObserver,
+ nsIRunnable)
+
+NamedPipeService::NamedPipeService()
+ : mIocp(nullptr), mIsShutdown(false), mLock("NamedPipeServiceLock") {}
+
+nsresult NamedPipeService::Init() {
+ MOZ_ASSERT(!mIsShutdown);
+
+ nsresult rv;
+
+ // nsIObserverService must be accessed in main thread.
+ // register shutdown event to stop NamedPipeSrv thread.
+ nsCOMPtr<nsIObserver> self(this);
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "NamedPipeService::Init", [self = std::move(self)]() -> void {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> svc =
+ mozilla::services::GetObserverService();
+
+ if (NS_WARN_IF(!svc)) {
+ return;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(svc->AddObserver(
+ self, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) {
+ return;
+ }
+ });
+
+ if (NS_IsMainThread()) {
+ rv = r->Run();
+ } else {
+ rv = NS_DispatchToMainThread(r);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
+ if (NS_WARN_IF(!mIocp || mIocp == INVALID_HANDLE_VALUE)) {
+ Shutdown();
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_NewNamedThread("NamedPipeSrv", getter_AddRefs(mThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Shutdown();
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+already_AddRefed<nsINamedPipeService> NamedPipeService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<NamedPipeService> inst;
+ if (gSingleton) {
+ inst = gSingleton;
+ } else {
+ inst = new NamedPipeService();
+ nsresult rv = inst->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ gSingleton = inst;
+ ClearOnShutdown(&gSingleton);
+ }
+
+ return inst.forget();
+}
+
+void NamedPipeService::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // remove observer
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ // stop thread
+ if (mThread && !mIsShutdown) {
+ mIsShutdown = true;
+
+ // invoke ERROR_ABANDONED_WAIT_0 to |GetQueuedCompletionStatus|
+ CloseHandle(mIocp);
+ mIocp = nullptr;
+
+ mThread->Shutdown();
+ }
+
+ // close I/O Completion Port
+ if (mIocp && mIocp != INVALID_HANDLE_VALUE) {
+ CloseHandle(mIocp);
+ mIocp = nullptr;
+ }
+}
+
+void NamedPipeService::RemoveRetiredObjects() {
+ MOZ_ASSERT(NS_GetCurrentThread() == mThread);
+ mLock.AssertCurrentThreadOwns();
+
+ if (!mRetiredHandles.IsEmpty()) {
+ for (auto& handle : mRetiredHandles) {
+ CloseHandle(handle);
+ }
+ mRetiredHandles.Clear();
+ }
+
+ mRetiredObservers.Clear();
+}
+
+/**
+ * Implement nsINamedPipeService
+ */
+
+NS_IMETHODIMP
+NamedPipeService::AddDataObserver(void* aHandle,
+ nsINamedPipeDataObserver* aObserver) {
+ if (!aHandle || aHandle == INVALID_HANDLE_VALUE || !aObserver) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv;
+
+ HANDLE h = CreateIoCompletionPort(aHandle, mIocp,
+ reinterpret_cast<ULONG_PTR>(aObserver), 1);
+ if (NS_WARN_IF(!h)) {
+ LOG_NPS_ERROR("CreateIoCompletionPort error (%lu)", GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(h != mIocp)) {
+ LOG_NPS_ERROR(
+ "CreateIoCompletionPort got unexpected value %p (should be %p)", h,
+ mIocp);
+ CloseHandle(h);
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(!mObservers.Contains(aObserver));
+
+ mObservers.AppendElement(aObserver);
+
+ // start event loop
+ if (mObservers.Length() == 1) {
+ rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG_NPS_ERROR("Dispatch to thread failed (%08x)", uint32_t(rv));
+ mObservers.Clear();
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NamedPipeService::RemoveDataObserver(void* aHandle,
+ nsINamedPipeDataObserver* aObserver) {
+ MutexAutoLock lock(mLock);
+ mObservers.RemoveElement(aObserver);
+
+ mRetiredHandles.AppendElement(aHandle);
+ mRetiredObservers.AppendElement(aObserver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NamedPipeService::IsOnCurrentThread(bool* aRetVal) {
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(aRetVal);
+
+ if (!mThread) {
+ *aRetVal = false;
+ return NS_OK;
+ }
+
+ return mThread->IsOnCurrentThread(aRetVal);
+}
+
+/**
+ * Implement nsIObserver
+ */
+
+NS_IMETHODIMP
+NamedPipeService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Implement nsIRunnable
+ */
+
+NS_IMETHODIMP
+NamedPipeService::Run() {
+ MOZ_ASSERT(NS_GetCurrentThread() == mThread);
+ MOZ_ASSERT(mIocp && mIocp != INVALID_HANDLE_VALUE);
+
+ while (!mIsShutdown) {
+ {
+ MutexAutoLock lock(mLock);
+ if (mObservers.IsEmpty()) {
+ LOG_NPS_DEBUG("no observer, stop loop");
+ break;
+ }
+
+ RemoveRetiredObjects();
+ }
+
+ DWORD bytesTransferred = 0;
+ ULONG_PTR key = 0;
+ LPOVERLAPPED overlapped = nullptr;
+ BOOL success =
+ GetQueuedCompletionStatus(mIocp, &bytesTransferred, &key, &overlapped,
+ 1000); // timeout, 1s
+ auto err = GetLastError();
+ if (!success) {
+ if (err == WAIT_TIMEOUT) {
+ continue;
+ } else if (err == ERROR_ABANDONED_WAIT_0) { // mIocp was closed
+ break;
+ } else if (!overlapped) {
+ /**
+ * Did not dequeue a completion packet from the completion port, and
+ * bytesTransferred/key are meaningless.
+ * See remarks of |GetQueuedCompletionStatus| API.
+ */
+
+ LOG_NPS_ERROR("invalid overlapped (%lu)", err);
+ continue;
+ }
+
+ MOZ_ASSERT(key);
+ }
+
+ /**
+ * Windows doesn't provide a method to remove created I/O Completion Port,
+ * all we can do is just close the handle we monitored before.
+ * In some cases, there's race condition that the monitored handle has an
+ * I/O status after the observer is being removed and destroyed.
+ * To avoid changing the ref-count of a dangling pointer, don't use nsCOMPtr
+ * here.
+ */
+ nsINamedPipeDataObserver* target =
+ reinterpret_cast<nsINamedPipeDataObserver*>(key);
+
+ nsCOMPtr<nsINamedPipeDataObserver> obs;
+ {
+ MutexAutoLock lock(mLock);
+
+ auto idx = mObservers.IndexOf(target);
+ if (idx == decltype(mObservers)::NoIndex) {
+ LOG_NPS_ERROR("observer %p not found", target);
+ continue;
+ }
+ obs = target;
+ }
+
+ MOZ_ASSERT(obs.get());
+
+ if (success) {
+ LOG_NPS_DEBUG("OnDataAvailable: obs=%p, bytes=%lu", obs.get(),
+ bytesTransferred);
+ obs->OnDataAvailable(bytesTransferred, overlapped);
+ } else {
+ LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%lu", obs.get(),
+ err);
+ obs->OnError(err, overlapped);
+ }
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ RemoveRetiredObjects();
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/socket/nsNamedPipeService.h b/netwerk/socket/nsNamedPipeService.h
new file mode 100644
index 0000000000..cbe6ff9631
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeService.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_netwerk_socket_nsNamedPipeService_h
+#define mozilla_netwerk_socket_nsNamedPipeService_h
+
+#include <windows.h>
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "nsINamedPipeService.h"
+#include "nsIObserver.h"
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+#include "nsTArray.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class NamedPipeService final : public nsINamedPipeService,
+ public nsIObserver,
+ public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINAMEDPIPESERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIRUNNABLE
+
+ static already_AddRefed<nsINamedPipeService> GetOrCreate();
+
+ private:
+ explicit NamedPipeService();
+ virtual ~NamedPipeService() = default;
+
+ nsresult Init();
+
+ void Shutdown();
+ void RemoveRetiredObjects();
+
+ HANDLE mIocp; // native handle to the I/O completion port.
+ Atomic<bool>
+ mIsShutdown; // set to true to stop the event loop running by mThread.
+ nsCOMPtr<nsIThread> mThread; // worker thread to get I/O events.
+
+ /**
+ * The observers is maintained in |mObservers| to ensure valid life-cycle.
+ * We don't remove the handle and corresponding observer directly, instead
+ * the handle and observer into a "retired" list and close/remove them in
+ * the worker thread to avoid a race condition that might happen between
+ * |CloseHandle()| and |GetQueuedCompletionStatus()|.
+ */
+ Mutex mLock MOZ_UNANNOTATED;
+ nsTArray<nsCOMPtr<nsINamedPipeDataObserver>>
+ mObservers; // protected by mLock
+ nsTArray<nsCOMPtr<nsINamedPipeDataObserver>>
+ mRetiredObservers; // protected by mLock
+ nsTArray<HANDLE> mRetiredHandles; // protected by mLock
+
+ static StaticRefPtr<NamedPipeService> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_socket_nsNamedPipeService_h
diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp
new file mode 100644
index 0000000000..b92de4e87a
--- /dev/null
+++ b/netwerk/socket/nsSOCKSIOLayer.cpp
@@ -0,0 +1,1471 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=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 "nspr.h"
+#include "private/pprio.h"
+#include "nsString.h"
+#include "nsCRT.h"
+
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsISocketProvider.h"
+#include "nsNamedPipeIOLayer.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsNetCID.h"
+#include "nsIDNSListener.h"
+#include "nsICancelable.h"
+#include "nsThreadUtils.h"
+#include "nsIFile.h"
+#include "nsIFileProtocolHandler.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Unused.h"
+
+using mozilla::LogLevel;
+using namespace mozilla::net;
+
+static PRDescIdentity nsSOCKSIOLayerIdentity;
+static PRIOMethods nsSOCKSIOLayerMethods;
+static bool firstTime = true;
+static bool ipv6Supported = true;
+
+static mozilla::LazyLogModule gSOCKSLog("SOCKS");
+#define LOGDEBUG(args) MOZ_LOG(gSOCKSLog, mozilla::LogLevel::Debug, args)
+#define LOGERROR(args) MOZ_LOG(gSOCKSLog, mozilla::LogLevel::Error, args)
+
+class nsSOCKSSocketInfo : public nsIDNSListener {
+ enum State {
+ SOCKS_INITIAL,
+ SOCKS_DNS_IN_PROGRESS,
+ SOCKS_DNS_COMPLETE,
+ SOCKS_CONNECTING_TO_PROXY,
+ SOCKS4_WRITE_CONNECT_REQUEST,
+ SOCKS4_READ_CONNECT_RESPONSE,
+ SOCKS5_WRITE_AUTH_REQUEST,
+ SOCKS5_READ_AUTH_RESPONSE,
+ SOCKS5_WRITE_USERNAME_REQUEST,
+ SOCKS5_READ_USERNAME_RESPONSE,
+ SOCKS5_WRITE_CONNECT_REQUEST,
+ SOCKS5_READ_CONNECT_RESPONSE_TOP,
+ SOCKS5_READ_CONNECT_RESPONSE_BOTTOM,
+ SOCKS_CONNECTED,
+ SOCKS_FAILED
+ };
+
+ // A buffer of 520 bytes should be enough for any request and response
+ // in case of SOCKS4 as well as SOCKS5
+ static const uint32_t BUFFER_SIZE = 520;
+ static const uint32_t MAX_HOSTNAME_LEN = 255;
+ static const uint32_t MAX_USERNAME_LEN = 255;
+ static const uint32_t MAX_PASSWORD_LEN = 255;
+
+ public:
+ nsSOCKSSocketInfo();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ void Init(int32_t version, int32_t family, nsIProxyInfo* proxy,
+ const char* destinationHost, uint32_t flags, uint32_t tlsFlags);
+
+ void SetConnectTimeout(PRIntervalTime to);
+ PRStatus DoHandshake(PRFileDesc* fd, int16_t oflags = -1);
+ int16_t GetPollFlags() const;
+ bool IsConnected() const { return mState == SOCKS_CONNECTED; }
+ void ForgetFD() { mFD = nullptr; }
+ void SetNamedPipeFD(PRFileDesc* fd) { mFD = fd; }
+
+ void GetExternalProxyAddr(NetAddr& aExternalProxyAddr);
+ void GetDestinationAddr(NetAddr& aDestinationAddr);
+ void SetDestinationAddr(const NetAddr& aDestinationAddr);
+
+ private:
+ virtual ~nsSOCKSSocketInfo() {
+ ForgetFD();
+ HandshakeFinished();
+ }
+
+ void HandshakeFinished(PRErrorCode err = 0);
+ PRStatus StartDNS(PRFileDesc* fd);
+ PRStatus ConnectToProxy(PRFileDesc* fd);
+ void FixupAddressFamily(PRFileDesc* fd, NetAddr* proxy);
+ PRStatus ContinueConnectingToProxy(PRFileDesc* fd, int16_t oflags);
+ PRStatus WriteV4ConnectRequest();
+ PRStatus ReadV4ConnectResponse();
+ PRStatus WriteV5AuthRequest();
+ PRStatus ReadV5AuthResponse();
+ PRStatus WriteV5UsernameRequest();
+ PRStatus ReadV5UsernameResponse();
+ PRStatus WriteV5ConnectRequest();
+ PRStatus ReadV5AddrTypeAndLength(uint8_t* type, uint32_t* len);
+ PRStatus ReadV5ConnectResponseTop();
+ PRStatus ReadV5ConnectResponseBottom();
+
+ uint8_t ReadUint8();
+ uint16_t ReadUint16();
+ uint32_t ReadUint32();
+ void ReadNetAddr(NetAddr* addr, uint16_t fam);
+ void ReadNetPort(NetAddr* addr);
+
+ void WantRead(uint32_t sz);
+ PRStatus ReadFromSocket(PRFileDesc* fd);
+ PRStatus WriteToSocket(PRFileDesc* fd);
+
+ bool IsLocalProxy() {
+ nsAutoCString proxyHost;
+ mProxy->GetHost(proxyHost);
+ return IsHostLocalTarget(proxyHost);
+ }
+
+ nsresult SetLocalProxyPath(const nsACString& aLocalProxyPath,
+ NetAddr* aProxyAddr) {
+#ifdef XP_UNIX
+ nsresult rv;
+ MOZ_ASSERT(aProxyAddr);
+
+ nsCOMPtr<nsIProtocolHandler> protocolHandler(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler(
+ do_QueryInterface(protocolHandler, &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> socketFile;
+ rv = fileHandler->GetFileFromURLSpec(aLocalProxyPath,
+ getter_AddRefs(socketFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString path;
+ if (NS_WARN_IF(NS_FAILED(rv = socketFile->GetNativePath(path)))) {
+ return rv;
+ }
+
+ if (sizeof(aProxyAddr->local.path) <= path.Length()) {
+ NS_WARNING("domain socket path too long.");
+ return NS_ERROR_FAILURE;
+ }
+
+ aProxyAddr->raw.family = AF_UNIX;
+ strcpy(aProxyAddr->local.path, path.get());
+
+ return NS_OK;
+#elif defined(XP_WIN)
+ MOZ_ASSERT(aProxyAddr);
+
+ if (sizeof(aProxyAddr->local.path) <= aLocalProxyPath.Length()) {
+ NS_WARNING("pipe path too long.");
+ return NS_ERROR_FAILURE;
+ }
+
+ aProxyAddr->raw.family = AF_LOCAL;
+ strcpy(aProxyAddr->local.path, PromiseFlatCString(aLocalProxyPath).get());
+ return NS_OK;
+#else
+ mozilla::Unused << aLocalProxyPath;
+ mozilla::Unused << aProxyAddr;
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+ }
+
+ bool SetupNamedPipeLayer(PRFileDesc* fd) {
+#if defined(XP_WIN)
+ if (IsLocalProxy()) {
+ // nsSOCKSIOLayer handshaking only works under blocking mode
+ // unfortunately. Remember named pipe's FD to switch between modes.
+ SetNamedPipeFD(fd->lower);
+ return true;
+ }
+#endif
+ return false;
+ }
+
+ private:
+ State mState{SOCKS_INITIAL};
+ uint8_t* mData{nullptr};
+ uint8_t* mDataIoPtr{nullptr};
+ uint32_t mDataLength{0};
+ uint32_t mReadOffset{0};
+ uint32_t mAmountToRead{0};
+ nsCOMPtr<nsIDNSRecord> mDnsRec;
+ nsCOMPtr<nsICancelable> mLookup;
+ nsresult mLookupStatus{NS_ERROR_NOT_INITIALIZED};
+ PRFileDesc* mFD{nullptr};
+
+ nsCString mDestinationHost;
+ nsCOMPtr<nsIProxyInfo> mProxy;
+ int32_t mVersion{-1}; // SOCKS version 4 or 5
+ int32_t mDestinationFamily{AF_INET};
+ uint32_t mFlags{0};
+ uint32_t mTlsFlags{0};
+ NetAddr mInternalProxyAddr;
+ NetAddr mExternalProxyAddr;
+ NetAddr mDestinationAddr;
+ PRIntervalTime mTimeout{PR_INTERVAL_NO_TIMEOUT};
+ nsCString mProxyUsername; // Cache, from mProxy
+};
+
+nsSOCKSSocketInfo::nsSOCKSSocketInfo() {
+ mData = new uint8_t[BUFFER_SIZE];
+
+ mInternalProxyAddr.raw.family = AF_INET;
+ mInternalProxyAddr.inet.ip = htonl(INADDR_ANY);
+ mInternalProxyAddr.inet.port = htons(0);
+
+ mExternalProxyAddr.raw.family = AF_INET;
+ mExternalProxyAddr.inet.ip = htonl(INADDR_ANY);
+ mExternalProxyAddr.inet.port = htons(0);
+
+ mDestinationAddr.raw.family = AF_INET;
+ mDestinationAddr.inet.ip = htonl(INADDR_ANY);
+ mDestinationAddr.inet.port = htons(0);
+}
+
+/* Helper template class to statically check that writes to a fixed-size
+ * buffer are not going to overflow.
+ *
+ * Example usage:
+ * uint8_t real_buf[TOTAL_SIZE];
+ * Buffer<TOTAL_SIZE> buf(&real_buf);
+ * auto buf2 = buf.WriteUint16(1);
+ * auto buf3 = buf2.WriteUint8(2);
+ *
+ * It is possible to chain them, to limit the number of (error-prone)
+ * intermediate variables:
+ * auto buf = Buffer<TOTAL_SIZE>(&real_buf)
+ * .WriteUint16(1)
+ * .WriteUint8(2);
+ *
+ * Debug builds assert when intermediate variables are reused:
+ * Buffer<TOTAL_SIZE> buf(&real_buf);
+ * auto buf2 = buf.WriteUint16(1);
+ * auto buf3 = buf.WriteUint8(2); // Asserts
+ *
+ * Strings can be written, given an explicit maximum length.
+ * buf.WriteString<MAX_STRING_LENGTH>(str);
+ *
+ * The Written() method returns how many bytes have been written so far:
+ * Buffer<TOTAL_SIZE> buf(&real_buf);
+ * auto buf2 = buf.WriteUint16(1);
+ * auto buf3 = buf2.WriteUint8(2);
+ * buf3.Written(); // returns 3.
+ */
+template <size_t Size>
+class Buffer {
+ public:
+ Buffer() = default;
+
+ explicit Buffer(uint8_t* aBuf, size_t aLength = 0)
+ : mBuf(aBuf), mLength(aLength) {}
+
+ template <size_t Size2>
+ MOZ_IMPLICIT Buffer(const Buffer<Size2>& aBuf)
+ : mBuf(aBuf.mBuf), mLength(aBuf.mLength) {
+ static_assert(Size2 > Size, "Cannot cast buffer");
+ }
+
+ Buffer<Size - sizeof(uint8_t)> WriteUint8(uint8_t aValue) {
+ return Write(aValue);
+ }
+
+ Buffer<Size - sizeof(uint16_t)> WriteUint16(uint16_t aValue) {
+ return Write(aValue);
+ }
+
+ Buffer<Size - sizeof(uint32_t)> WriteUint32(uint32_t aValue) {
+ return Write(aValue);
+ }
+
+ Buffer<Size - sizeof(uint16_t)> WriteNetPort(const NetAddr* aAddr) {
+ return WriteUint16(aAddr->inet.port);
+ }
+
+ Buffer<Size - sizeof(IPv6Addr)> WriteNetAddr(const NetAddr* aAddr) {
+ if (aAddr->raw.family == AF_INET) {
+ return Write(aAddr->inet.ip);
+ }
+ if (aAddr->raw.family == AF_INET6) {
+ return Write(aAddr->inet6.ip.u8);
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown address family");
+ return *this;
+ }
+
+ template <size_t MaxLength>
+ Buffer<Size - MaxLength> WriteString(const nsACString& aStr) {
+ if (aStr.Length() > MaxLength) {
+ return Buffer<Size - MaxLength>(nullptr);
+ }
+ return WritePtr<char, MaxLength>(aStr.Data(), aStr.Length());
+ }
+
+ size_t Written() {
+ MOZ_ASSERT(mBuf);
+ return mLength;
+ }
+
+ explicit operator bool() { return !!mBuf; }
+
+ private:
+ template <size_t Size2>
+ friend class Buffer;
+
+ template <typename T>
+ Buffer<Size - sizeof(T)> Write(T& aValue) {
+ return WritePtr<T, sizeof(T)>(&aValue, sizeof(T));
+ }
+
+ template <typename T, size_t Length>
+ Buffer<Size - Length> WritePtr(const T* aValue, size_t aCopyLength) {
+ static_assert(Size >= Length, "Cannot write that much");
+ MOZ_ASSERT(aCopyLength <= Length);
+ MOZ_ASSERT(mBuf);
+ memcpy(mBuf, aValue, aCopyLength);
+ Buffer<Size - Length> result(mBuf + aCopyLength, mLength + aCopyLength);
+ mBuf = nullptr;
+ mLength = 0;
+ return result;
+ }
+
+ uint8_t* mBuf{nullptr};
+ size_t mLength{0};
+};
+
+void nsSOCKSSocketInfo::Init(int32_t version, int32_t family,
+ nsIProxyInfo* proxy, const char* host,
+ uint32_t flags, uint32_t tlsFlags) {
+ mVersion = version;
+ mDestinationFamily = family;
+ mProxy = proxy;
+ mDestinationHost = host;
+ mFlags = flags;
+ mTlsFlags = tlsFlags;
+ mProxy->GetUsername(mProxyUsername); // cache
+}
+
+NS_IMPL_ISUPPORTS(nsSOCKSSocketInfo, nsIDNSListener)
+
+void nsSOCKSSocketInfo::GetExternalProxyAddr(NetAddr& aExternalProxyAddr) {
+ aExternalProxyAddr = mExternalProxyAddr;
+}
+
+void nsSOCKSSocketInfo::GetDestinationAddr(NetAddr& aDestinationAddr) {
+ aDestinationAddr = mDestinationAddr;
+}
+
+void nsSOCKSSocketInfo::SetDestinationAddr(const NetAddr& aDestinationAddr) {
+ mDestinationAddr = aDestinationAddr;
+}
+
+// There needs to be a means of distinguishing between connection errors
+// that the SOCKS server reports when it rejects a connection request, and
+// connection errors that happen while attempting to connect to the SOCKS
+// server. Otherwise, Firefox will report incorrectly that the proxy server
+// is refusing connections when a SOCKS request is rejected by the proxy.
+// When a SOCKS handshake failure occurs, the PR error is set to
+// PR_UNKNOWN_ERROR, and the real error code is returned via the OS error.
+void nsSOCKSSocketInfo::HandshakeFinished(PRErrorCode err) {
+ if (err == 0) {
+ mState = SOCKS_CONNECTED;
+#if defined(XP_WIN)
+ // Switch back to nonblocking mode after finishing handshaking.
+ if (IsLocalProxy() && mFD) {
+ PRSocketOptionData opt_nonblock;
+ opt_nonblock.option = PR_SockOpt_Nonblocking;
+ opt_nonblock.value.non_blocking = PR_TRUE;
+ PR_SetSocketOption(mFD, &opt_nonblock);
+ mFD = nullptr;
+ }
+#endif
+ } else {
+ mState = SOCKS_FAILED;
+ PR_SetError(PR_UNKNOWN_ERROR, err);
+ }
+
+ // We don't need the buffer any longer, so free it.
+ delete[] mData;
+ mData = nullptr;
+ mDataIoPtr = nullptr;
+ mDataLength = 0;
+ mReadOffset = 0;
+ mAmountToRead = 0;
+ if (mLookup) {
+ mLookup->Cancel(NS_ERROR_FAILURE);
+ mLookup = nullptr;
+ }
+}
+
+PRStatus nsSOCKSSocketInfo::StartDNS(PRFileDesc* fd) {
+ MOZ_ASSERT(!mDnsRec && mState == SOCKS_INITIAL,
+ "Must be in initial state to make DNS Lookup");
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) return PR_FAILURE;
+
+ nsCString proxyHost;
+ mProxy->GetHost(proxyHost);
+
+ mozilla::OriginAttributes attrs;
+
+ mFD = fd;
+ nsresult rv = dns->AsyncResolveNative(
+ proxyHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS, nullptr, this,
+ mozilla::GetCurrentSerialEventTarget(), attrs, getter_AddRefs(mLookup));
+
+ if (NS_FAILED(rv)) {
+ LOGERROR(("socks: DNS lookup for SOCKS proxy %s failed", proxyHost.get()));
+ return PR_FAILURE;
+ }
+ mState = SOCKS_DNS_IN_PROGRESS;
+ PR_SetError(PR_IN_PROGRESS_ERROR, 0);
+ return PR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketInfo::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord, nsresult aStatus) {
+ MOZ_ASSERT(aRequest == mLookup, "wrong DNS query");
+ mLookup = nullptr;
+ mLookupStatus = aStatus;
+ mDnsRec = aRecord;
+ mState = SOCKS_DNS_COMPLETE;
+ if (mFD) {
+ ConnectToProxy(mFD);
+ ForgetFD();
+ }
+ return NS_OK;
+}
+
+PRStatus nsSOCKSSocketInfo::ConnectToProxy(PRFileDesc* fd) {
+ PRStatus status;
+ nsresult rv;
+
+ MOZ_ASSERT(mState == SOCKS_DNS_COMPLETE, "Must have DNS to make connection!");
+
+ if (NS_FAILED(mLookupStatus)) {
+ PR_SetError(PR_BAD_ADDRESS_ERROR, 0);
+ return PR_FAILURE;
+ }
+
+ // Try socks5 if the destination addrress is IPv6
+ if (mVersion == 4 && mDestinationAddr.raw.family == AF_INET6) {
+ mVersion = 5;
+ }
+
+ nsAutoCString proxyHost;
+ mProxy->GetHost(proxyHost);
+
+ int32_t proxyPort;
+ mProxy->GetPort(&proxyPort);
+
+ int32_t addresses = 0;
+ do {
+ if (IsLocalProxy()) {
+ rv = SetLocalProxyPath(proxyHost, &mInternalProxyAddr);
+ if (NS_FAILED(rv)) {
+ LOGERROR(
+ ("socks: unable to connect to SOCKS proxy, %s", proxyHost.get()));
+ return PR_FAILURE;
+ }
+ } else {
+ nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(mDnsRec);
+ MOZ_ASSERT(record);
+ if (addresses++) {
+ record->ReportUnusable(proxyPort);
+ }
+
+ rv = record->GetNextAddr(proxyPort, &mInternalProxyAddr);
+ // No more addresses to try? If so, we'll need to bail
+ if (NS_FAILED(rv)) {
+ LOGERROR(
+ ("socks: unable to connect to SOCKS proxy, %s", proxyHost.get()));
+ return PR_FAILURE;
+ }
+
+ if (MOZ_LOG_TEST(gSOCKSLog, LogLevel::Debug)) {
+ char buf[kIPv6CStrBufSize];
+ mInternalProxyAddr.ToStringBuffer(buf, sizeof(buf));
+ LOGDEBUG(("socks: trying proxy server, %s:%hu", buf,
+ ntohs(mInternalProxyAddr.inet.port)));
+ }
+ }
+
+ NetAddr proxy = mInternalProxyAddr;
+ FixupAddressFamily(fd, &proxy);
+ PRNetAddr prProxy;
+ NetAddrToPRNetAddr(&proxy, &prProxy);
+ status = fd->lower->methods->connect(fd->lower, &prProxy, mTimeout);
+ if (status != PR_SUCCESS) {
+ PRErrorCode c = PR_GetError();
+
+ // If EINPROGRESS, return now and check back later after polling
+ if (c == PR_WOULD_BLOCK_ERROR || c == PR_IN_PROGRESS_ERROR) {
+ mState = SOCKS_CONNECTING_TO_PROXY;
+ return status;
+ }
+ if (IsLocalProxy()) {
+ LOGERROR(("socks: connect to domain socket failed (%d)", c));
+ PR_SetError(PR_CONNECT_REFUSED_ERROR, 0);
+ mState = SOCKS_FAILED;
+ return status;
+ }
+ }
+ } while (status != PR_SUCCESS);
+
+#if defined(XP_WIN)
+ // Switch to blocking mode during handshaking
+ if (IsLocalProxy() && mFD) {
+ PRSocketOptionData opt_nonblock;
+ opt_nonblock.option = PR_SockOpt_Nonblocking;
+ opt_nonblock.value.non_blocking = PR_FALSE;
+ PR_SetSocketOption(mFD, &opt_nonblock);
+ }
+#endif
+
+ // Connected now, start SOCKS
+ if (mVersion == 4) return WriteV4ConnectRequest();
+ return WriteV5AuthRequest();
+}
+
+void nsSOCKSSocketInfo::FixupAddressFamily(PRFileDesc* fd, NetAddr* proxy) {
+ int32_t proxyFamily = mInternalProxyAddr.raw.family;
+ // Do nothing if the address family is already matched
+ if (proxyFamily == mDestinationFamily) {
+ return;
+ }
+ // If the system does not support IPv6 and the proxy address is IPv6,
+ // We can do nothing here.
+ if (proxyFamily == AF_INET6 && !ipv6Supported) {
+ return;
+ }
+ // If the system does not support IPv6 and the destination address is
+ // IPv6, convert IPv4 address to IPv4-mapped IPv6 address to satisfy
+ // the emulation layer
+ if (mDestinationFamily == AF_INET6 && !ipv6Supported) {
+ proxy->inet6.family = AF_INET6;
+ proxy->inet6.port = mInternalProxyAddr.inet.port;
+ uint8_t* proxyp = proxy->inet6.ip.u8;
+ memset(proxyp, 0, 10);
+ memset(proxyp + 10, 0xff, 2);
+ memcpy(proxyp + 12, (char*)&mInternalProxyAddr.inet.ip, 4);
+ // mDestinationFamily should not be updated
+ return;
+ }
+ // There's no PR_NSPR_IO_LAYER required when using named pipe,
+ // we simply ignore the TCP family here.
+ if (SetupNamedPipeLayer(fd)) {
+ return;
+ }
+
+ // Get an OS native handle from a specified FileDesc
+ PROsfd osfd = PR_FileDesc2NativeHandle(fd);
+ if (osfd == -1) {
+ return;
+ }
+
+ // Create a new FileDesc with a specified family
+ PRFileDesc* tmpfd = PR_OpenTCPSocket(proxyFamily);
+ if (!tmpfd) {
+ return;
+ }
+ PROsfd newsd = PR_FileDesc2NativeHandle(tmpfd);
+ if (newsd == -1) {
+ PR_Close(tmpfd);
+ return;
+ }
+ // Must succeed because PR_FileDesc2NativeHandle succeeded
+ fd = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
+ MOZ_ASSERT(fd);
+ // Swap OS native handles
+ PR_ChangeFileDescNativeHandle(fd, newsd);
+ PR_ChangeFileDescNativeHandle(tmpfd, osfd);
+ // Close temporary FileDesc which is now associated with
+ // old OS native handle
+ PR_Close(tmpfd);
+ mDestinationFamily = proxyFamily;
+}
+
+PRStatus nsSOCKSSocketInfo::ContinueConnectingToProxy(PRFileDesc* fd,
+ int16_t oflags) {
+ PRStatus status;
+
+ MOZ_ASSERT(mState == SOCKS_CONNECTING_TO_PROXY,
+ "Continuing connection in wrong state!");
+
+ LOGDEBUG(("socks: continuing connection to proxy"));
+
+ status = fd->lower->methods->connectcontinue(fd->lower, oflags);
+ if (status != PR_SUCCESS) {
+ PRErrorCode c = PR_GetError();
+ if (c != PR_WOULD_BLOCK_ERROR && c != PR_IN_PROGRESS_ERROR) {
+ // A connection failure occured, try another address
+ mState = SOCKS_DNS_COMPLETE;
+ return ConnectToProxy(fd);
+ }
+
+ // We're still connecting
+ return PR_FAILURE;
+ }
+
+ // Connected now, start SOCKS
+ if (mVersion == 4) return WriteV4ConnectRequest();
+ return WriteV5AuthRequest();
+}
+
+PRStatus nsSOCKSSocketInfo::WriteV4ConnectRequest() {
+ if (mProxyUsername.Length() > MAX_USERNAME_LEN) {
+ LOGERROR(("socks username is too long"));
+ HandshakeFinished(PR_UNKNOWN_ERROR);
+ return PR_FAILURE;
+ }
+
+ NetAddr* addr = &mDestinationAddr;
+ int32_t proxy_resolve;
+
+ MOZ_ASSERT(mState == SOCKS_CONNECTING_TO_PROXY, "Invalid state!");
+
+ proxy_resolve = mFlags & nsISocketProvider::PROXY_RESOLVES_HOST;
+
+ mDataLength = 0;
+ mState = SOCKS4_WRITE_CONNECT_REQUEST;
+
+ LOGDEBUG(("socks4: sending connection request (socks4a resolve? %s)",
+ proxy_resolve ? "yes" : "no"));
+
+ // Send a SOCKS 4 connect request.
+ auto buf = Buffer<BUFFER_SIZE>(mData)
+ .WriteUint8(0x04) // version -- 4
+ .WriteUint8(0x01) // command -- connect
+ .WriteNetPort(addr);
+
+ // We don't have anything more to write after the if, so we can
+ // use a buffer with no further writes allowed.
+ Buffer<0> buf3;
+ if (proxy_resolve) {
+ // Add the full name, null-terminated, to the request
+ // according to SOCKS 4a. A fake IP address, with the first
+ // four bytes set to 0 and the last byte set to something other
+ // than 0, is used to notify the proxy that this is a SOCKS 4a
+ // request. This request type works for Tor and perhaps others.
+ // Passwords not supported by V4.
+ auto buf2 =
+ buf.WriteUint32(htonl(0x00000001)) // Fake IP
+ .WriteString<MAX_USERNAME_LEN>(mProxyUsername)
+ .WriteUint8(0x00) // Null-terminate username
+ .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname
+ if (!buf2) {
+ LOGERROR(("socks4: destination host name is too long!"));
+ HandshakeFinished(PR_BAD_ADDRESS_ERROR);
+ return PR_FAILURE;
+ }
+ buf3 = buf2.WriteUint8(0x00);
+ } else if (addr->raw.family == AF_INET) {
+ // Passwords not supported by V4.
+ buf3 = buf.WriteNetAddr(addr) // Add the IPv4 address
+ .WriteString<MAX_USERNAME_LEN>(mProxyUsername)
+ .WriteUint8(0x00); // Null-terminate username
+ } else {
+ LOGERROR(("socks: SOCKS 4 can only handle IPv4 addresses!"));
+ HandshakeFinished(PR_BAD_ADDRESS_ERROR);
+ return PR_FAILURE;
+ }
+
+ mDataLength = buf3.Written();
+ return PR_SUCCESS;
+}
+
+PRStatus nsSOCKSSocketInfo::ReadV4ConnectResponse() {
+ MOZ_ASSERT(mState == SOCKS4_READ_CONNECT_RESPONSE,
+ "Handling SOCKS 4 connection reply in wrong state!");
+ MOZ_ASSERT(mDataLength == 8, "SOCKS 4 connection reply must be 8 bytes!");
+
+ LOGDEBUG(("socks4: checking connection reply"));
+
+ if (ReadUint8() != 0x00) {
+ LOGERROR(("socks4: wrong connection reply"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+ }
+
+ // See if our connection request was granted
+ if (ReadUint8() == 90) {
+ LOGDEBUG(("socks4: connection successful!"));
+ HandshakeFinished();
+ return PR_SUCCESS;
+ }
+
+ LOGERROR(("socks4: unable to connect"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+}
+
+PRStatus nsSOCKSSocketInfo::WriteV5AuthRequest() {
+ MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!");
+
+ mDataLength = 0;
+ mState = SOCKS5_WRITE_AUTH_REQUEST;
+
+ // Send an initial SOCKS 5 greeting
+ LOGDEBUG(("socks5: sending auth methods"));
+ mDataLength = Buffer<BUFFER_SIZE>(mData)
+ .WriteUint8(0x05) // version -- 5
+ .WriteUint8(0x01) // # of auth methods -- 1
+ // Use authenticate iff we have a proxy username.
+ .WriteUint8(mProxyUsername.IsEmpty() ? 0x00 : 0x02)
+ .Written();
+
+ return PR_SUCCESS;
+}
+
+PRStatus nsSOCKSSocketInfo::ReadV5AuthResponse() {
+ MOZ_ASSERT(mState == SOCKS5_READ_AUTH_RESPONSE,
+ "Handling SOCKS 5 auth method reply in wrong state!");
+ MOZ_ASSERT(mDataLength == 2, "SOCKS 5 auth method reply must be 2 bytes!");
+
+ LOGDEBUG(("socks5: checking auth method reply"));
+
+ // Check version number
+ if (ReadUint8() != 0x05) {
+ LOGERROR(("socks5: unexpected version in the reply"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+ }
+
+ // Make sure our authentication choice was accepted,
+ // and continue accordingly
+ uint8_t authMethod = ReadUint8();
+ if (mProxyUsername.IsEmpty() && authMethod == 0x00) { // no auth
+ LOGDEBUG(("socks5: server allows connection without authentication"));
+ return WriteV5ConnectRequest();
+ }
+ if (!mProxyUsername.IsEmpty() && authMethod == 0x02) { // username/pw
+ LOGDEBUG(("socks5: auth method accepted by server"));
+ return WriteV5UsernameRequest();
+ } // 0xFF signals error
+ LOGERROR(("socks5: server did not accept our authentication method"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+}
+
+PRStatus nsSOCKSSocketInfo::WriteV5UsernameRequest() {
+ MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!");
+
+ if (mProxyUsername.Length() > MAX_USERNAME_LEN) {
+ LOGERROR(("socks username is too long"));
+ HandshakeFinished(PR_UNKNOWN_ERROR);
+ return PR_FAILURE;
+ }
+
+ nsCString password;
+ mProxy->GetPassword(password);
+ if (password.Length() > MAX_PASSWORD_LEN) {
+ LOGERROR(("socks password is too long"));
+ HandshakeFinished(PR_UNKNOWN_ERROR);
+ return PR_FAILURE;
+ }
+
+ mDataLength = 0;
+ mState = SOCKS5_WRITE_USERNAME_REQUEST;
+
+ // RFC 1929 Username/password auth for SOCKS 5
+ LOGDEBUG(("socks5: sending username and password"));
+ mDataLength = Buffer<BUFFER_SIZE>(mData)
+ .WriteUint8(0x01) // version 1 (not 5)
+ .WriteUint8(mProxyUsername.Length()) // username length
+ .WriteString<MAX_USERNAME_LEN>(mProxyUsername) // username
+ .WriteUint8(password.Length()) // password length
+ .WriteString<MAX_PASSWORD_LEN>(
+ password) // password. WARNING: Sent unencrypted!
+ .Written();
+
+ return PR_SUCCESS;
+}
+
+PRStatus nsSOCKSSocketInfo::ReadV5UsernameResponse() {
+ MOZ_ASSERT(mState == SOCKS5_READ_USERNAME_RESPONSE,
+ "Handling SOCKS 5 username/password reply in wrong state!");
+
+ MOZ_ASSERT(mDataLength == 2, "SOCKS 5 username reply must be 2 bytes");
+
+ // Check version number, must be 1 (not 5)
+ if (ReadUint8() != 0x01) {
+ LOGERROR(("socks5: unexpected version in the reply"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+ }
+
+ // Check whether username/password were accepted
+ if (ReadUint8() != 0x00) { // 0 = success
+ LOGERROR(("socks5: username/password not accepted"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+ }
+
+ LOGDEBUG(("socks5: username/password accepted by server"));
+
+ return WriteV5ConnectRequest();
+}
+
+PRStatus nsSOCKSSocketInfo::WriteV5ConnectRequest() {
+ // Send SOCKS 5 connect request
+ NetAddr* addr = &mDestinationAddr;
+ int32_t proxy_resolve;
+ proxy_resolve = mFlags & nsISocketProvider::PROXY_RESOLVES_HOST;
+
+ LOGDEBUG(("socks5: sending connection request (socks5 resolve? %s)",
+ proxy_resolve ? "yes" : "no"));
+
+ mDataLength = 0;
+ mState = SOCKS5_WRITE_CONNECT_REQUEST;
+
+ auto buf = Buffer<BUFFER_SIZE>(mData)
+ .WriteUint8(0x05) // version -- 5
+ .WriteUint8(0x01) // command -- connect
+ .WriteUint8(0x00); // reserved
+
+ // We're writing a net port after the if, so we need a buffer allowing
+ // to write that much.
+ Buffer<sizeof(uint16_t)> buf2;
+ // Add the address to the SOCKS 5 request. SOCKS 5 supports several
+ // address types, so we pick the one that works best for us.
+ if (proxy_resolve) {
+ // Add the host name. Only a single byte is used to store the length,
+ // so we must prevent long names from being used.
+ buf2 = buf.WriteUint8(0x03) // addr type -- domainname
+ .WriteUint8(mDestinationHost.Length()) // name length
+ .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname
+ if (!buf2) {
+ LOGERROR(("socks5: destination host name is too long!"));
+ HandshakeFinished(PR_BAD_ADDRESS_ERROR);
+ return PR_FAILURE;
+ }
+ } else if (addr->raw.family == AF_INET) {
+ buf2 = buf.WriteUint8(0x01) // addr type -- IPv4
+ .WriteNetAddr(addr);
+ } else if (addr->raw.family == AF_INET6) {
+ buf2 = buf.WriteUint8(0x04) // addr type -- IPv6
+ .WriteNetAddr(addr);
+ } else {
+ LOGERROR(("socks5: destination address of unknown type!"));
+ HandshakeFinished(PR_BAD_ADDRESS_ERROR);
+ return PR_FAILURE;
+ }
+
+ auto buf3 = buf2.WriteNetPort(addr); // port
+ mDataLength = buf3.Written();
+
+ return PR_SUCCESS;
+}
+
+PRStatus nsSOCKSSocketInfo::ReadV5AddrTypeAndLength(uint8_t* type,
+ uint32_t* len) {
+ MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP ||
+ mState == SOCKS5_READ_CONNECT_RESPONSE_BOTTOM,
+ "Invalid state!");
+ MOZ_ASSERT(mDataLength >= 5,
+ "SOCKS 5 connection reply must be at least 5 bytes!");
+
+ // Seek to the address location
+ mReadOffset = 3;
+
+ *type = ReadUint8();
+
+ switch (*type) {
+ case 0x01: // ipv4
+ *len = 4 - 1;
+ break;
+ case 0x04: // ipv6
+ *len = 16 - 1;
+ break;
+ case 0x03: // fqdn
+ *len = ReadUint8();
+ break;
+ default: // wrong address type
+ LOGERROR(("socks5: wrong address type in connection reply!"));
+ return PR_FAILURE;
+ }
+
+ return PR_SUCCESS;
+}
+
+PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseTop() {
+ uint8_t res;
+ uint32_t len;
+
+ MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP, "Invalid state!");
+ MOZ_ASSERT(mDataLength == 5,
+ "SOCKS 5 connection reply must be exactly 5 bytes!");
+
+ LOGDEBUG(("socks5: checking connection reply"));
+
+ // Check version number
+ if (ReadUint8() != 0x05) {
+ LOGERROR(("socks5: unexpected version in the reply"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+ }
+
+ // Check response
+ res = ReadUint8();
+ if (res != 0x00) {
+ PRErrorCode c = PR_CONNECT_REFUSED_ERROR;
+
+ switch (res) {
+ case 0x01:
+ LOGERROR(
+ ("socks5: connect failed: "
+ "01, General SOCKS server failure."));
+ break;
+ case 0x02:
+ LOGERROR(
+ ("socks5: connect failed: "
+ "02, Connection not allowed by ruleset."));
+ break;
+ case 0x03:
+ LOGERROR(("socks5: connect failed: 03, Network unreachable."));
+ c = PR_NETWORK_UNREACHABLE_ERROR;
+ break;
+ case 0x04:
+ LOGERROR(("socks5: connect failed: 04, Host unreachable."));
+ c = PR_BAD_ADDRESS_ERROR;
+ break;
+ case 0x05:
+ LOGERROR(("socks5: connect failed: 05, Connection refused."));
+ break;
+ case 0x06:
+ LOGERROR(("socks5: connect failed: 06, TTL expired."));
+ c = PR_CONNECT_TIMEOUT_ERROR;
+ break;
+ case 0x07:
+ LOGERROR(
+ ("socks5: connect failed: "
+ "07, Command not supported."));
+ break;
+ case 0x08:
+ LOGERROR(
+ ("socks5: connect failed: "
+ "08, Address type not supported."));
+ c = PR_BAD_ADDRESS_ERROR;
+ break;
+ default:
+ LOGERROR(("socks5: connect failed."));
+ break;
+ }
+
+ HandshakeFinished(c);
+ return PR_FAILURE;
+ }
+
+ if (ReadV5AddrTypeAndLength(&res, &len) != PR_SUCCESS) {
+ HandshakeFinished(PR_BAD_ADDRESS_ERROR);
+ return PR_FAILURE;
+ }
+
+ mState = SOCKS5_READ_CONNECT_RESPONSE_BOTTOM;
+ WantRead(len + 2);
+
+ return PR_SUCCESS;
+}
+
+PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseBottom() {
+ uint8_t type;
+ uint32_t len;
+
+ MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, "Invalid state!");
+
+ if (ReadV5AddrTypeAndLength(&type, &len) != PR_SUCCESS) {
+ HandshakeFinished(PR_BAD_ADDRESS_ERROR);
+ return PR_FAILURE;
+ }
+
+ MOZ_ASSERT(mDataLength == 7 + len,
+ "SOCKS 5 unexpected length of connection reply!");
+
+ LOGDEBUG(("socks5: loading source addr and port"));
+ // Read what the proxy says is our source address
+ switch (type) {
+ case 0x01: // ipv4
+ ReadNetAddr(&mExternalProxyAddr, AF_INET);
+ break;
+ case 0x04: // ipv6
+ ReadNetAddr(&mExternalProxyAddr, AF_INET6);
+ break;
+ case 0x03: // fqdn (skip)
+ mReadOffset += len;
+ mExternalProxyAddr.raw.family = AF_INET;
+ break;
+ }
+
+ ReadNetPort(&mExternalProxyAddr);
+
+ LOGDEBUG(("socks5: connected!"));
+ HandshakeFinished();
+
+ return PR_SUCCESS;
+}
+
+void nsSOCKSSocketInfo::SetConnectTimeout(PRIntervalTime to) { mTimeout = to; }
+
+PRStatus nsSOCKSSocketInfo::DoHandshake(PRFileDesc* fd, int16_t oflags) {
+ LOGDEBUG(("socks: DoHandshake(), state = %d", mState));
+
+ switch (mState) {
+ case SOCKS_INITIAL:
+ if (IsLocalProxy()) {
+ mState = SOCKS_DNS_COMPLETE;
+ mLookupStatus = NS_OK;
+ return ConnectToProxy(fd);
+ }
+
+ return StartDNS(fd);
+ case SOCKS_DNS_IN_PROGRESS:
+ PR_SetError(PR_IN_PROGRESS_ERROR, 0);
+ return PR_FAILURE;
+ case SOCKS_DNS_COMPLETE:
+ return ConnectToProxy(fd);
+ case SOCKS_CONNECTING_TO_PROXY:
+ return ContinueConnectingToProxy(fd, oflags);
+ case SOCKS4_WRITE_CONNECT_REQUEST:
+ if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ WantRead(8);
+ mState = SOCKS4_READ_CONNECT_RESPONSE;
+ return PR_SUCCESS;
+ case SOCKS4_READ_CONNECT_RESPONSE:
+ if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ return ReadV4ConnectResponse();
+
+ case SOCKS5_WRITE_AUTH_REQUEST:
+ if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ WantRead(2);
+ mState = SOCKS5_READ_AUTH_RESPONSE;
+ return PR_SUCCESS;
+ case SOCKS5_READ_AUTH_RESPONSE:
+ if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ return ReadV5AuthResponse();
+ case SOCKS5_WRITE_USERNAME_REQUEST:
+ if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ WantRead(2);
+ mState = SOCKS5_READ_USERNAME_RESPONSE;
+ return PR_SUCCESS;
+ case SOCKS5_READ_USERNAME_RESPONSE:
+ if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ return ReadV5UsernameResponse();
+ case SOCKS5_WRITE_CONNECT_REQUEST:
+ if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+
+ // The SOCKS 5 response to the connection request is variable
+ // length. First, we'll read enough to tell how long the response
+ // is, and will read the rest later.
+ WantRead(5);
+ mState = SOCKS5_READ_CONNECT_RESPONSE_TOP;
+ return PR_SUCCESS;
+ case SOCKS5_READ_CONNECT_RESPONSE_TOP:
+ if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ return ReadV5ConnectResponseTop();
+ case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM:
+ if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE;
+ return ReadV5ConnectResponseBottom();
+
+ case SOCKS_CONNECTED:
+ LOGERROR(("socks: already connected"));
+ HandshakeFinished(PR_IS_CONNECTED_ERROR);
+ return PR_FAILURE;
+ case SOCKS_FAILED:
+ LOGERROR(("socks: already failed"));
+ return PR_FAILURE;
+ }
+
+ LOGERROR(("socks: executing handshake in invalid state, %d", mState));
+ HandshakeFinished(PR_INVALID_STATE_ERROR);
+
+ return PR_FAILURE;
+}
+
+int16_t nsSOCKSSocketInfo::GetPollFlags() const {
+ switch (mState) {
+ case SOCKS_DNS_IN_PROGRESS:
+ case SOCKS_DNS_COMPLETE:
+ case SOCKS_CONNECTING_TO_PROXY:
+ return PR_POLL_EXCEPT | PR_POLL_WRITE;
+ case SOCKS4_WRITE_CONNECT_REQUEST:
+ case SOCKS5_WRITE_AUTH_REQUEST:
+ case SOCKS5_WRITE_USERNAME_REQUEST:
+ case SOCKS5_WRITE_CONNECT_REQUEST:
+ return PR_POLL_WRITE;
+ case SOCKS4_READ_CONNECT_RESPONSE:
+ case SOCKS5_READ_AUTH_RESPONSE:
+ case SOCKS5_READ_USERNAME_RESPONSE:
+ case SOCKS5_READ_CONNECT_RESPONSE_TOP:
+ case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM:
+ return PR_POLL_READ;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+inline uint8_t nsSOCKSSocketInfo::ReadUint8() {
+ uint8_t rv;
+ MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength,
+ "Not enough space to pop a uint8_t!");
+ rv = mData[mReadOffset];
+ mReadOffset += sizeof(rv);
+ return rv;
+}
+
+inline uint16_t nsSOCKSSocketInfo::ReadUint16() {
+ uint16_t rv;
+ MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength,
+ "Not enough space to pop a uint16_t!");
+ memcpy(&rv, mData + mReadOffset, sizeof(rv));
+ mReadOffset += sizeof(rv);
+ return rv;
+}
+
+inline uint32_t nsSOCKSSocketInfo::ReadUint32() {
+ uint32_t rv;
+ MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength,
+ "Not enough space to pop a uint32_t!");
+ memcpy(&rv, mData + mReadOffset, sizeof(rv));
+ mReadOffset += sizeof(rv);
+ return rv;
+}
+
+void nsSOCKSSocketInfo::ReadNetAddr(NetAddr* addr, uint16_t fam) {
+ uint32_t amt = 0;
+ const uint8_t* ip = mData + mReadOffset;
+
+ addr->raw.family = fam;
+ if (fam == AF_INET) {
+ amt = sizeof(addr->inet.ip);
+ MOZ_ASSERT(mReadOffset + amt <= mDataLength,
+ "Not enough space to pop an ipv4 addr!");
+ memcpy(&addr->inet.ip, ip, amt);
+ } else if (fam == AF_INET6) {
+ amt = sizeof(addr->inet6.ip.u8);
+ MOZ_ASSERT(mReadOffset + amt <= mDataLength,
+ "Not enough space to pop an ipv6 addr!");
+ memcpy(addr->inet6.ip.u8, ip, amt);
+ }
+
+ mReadOffset += amt;
+}
+
+void nsSOCKSSocketInfo::ReadNetPort(NetAddr* addr) {
+ addr->inet.port = ReadUint16();
+}
+
+void nsSOCKSSocketInfo::WantRead(uint32_t sz) {
+ MOZ_ASSERT(mDataIoPtr == nullptr,
+ "WantRead() called while I/O already in progress!");
+ MOZ_ASSERT(mDataLength + sz <= BUFFER_SIZE, "Can't read that much data!");
+ mAmountToRead = sz;
+}
+
+PRStatus nsSOCKSSocketInfo::ReadFromSocket(PRFileDesc* fd) {
+ int32_t rc;
+ const uint8_t* end;
+
+ if (!mAmountToRead) {
+ LOGDEBUG(("socks: ReadFromSocket(), nothing to do"));
+ return PR_SUCCESS;
+ }
+
+ if (!mDataIoPtr) {
+ mDataIoPtr = mData + mDataLength;
+ mDataLength += mAmountToRead;
+ }
+
+ end = mData + mDataLength;
+
+ while (mDataIoPtr < end) {
+ rc = PR_Read(fd, mDataIoPtr, end - mDataIoPtr);
+ if (rc <= 0) {
+ if (rc == 0) {
+ LOGERROR(("socks: proxy server closed connection"));
+ HandshakeFinished(PR_CONNECT_REFUSED_ERROR);
+ return PR_FAILURE;
+ }
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+ LOGDEBUG(("socks: ReadFromSocket(), want read"));
+ }
+ break;
+ }
+
+ mDataIoPtr += rc;
+ }
+
+ LOGDEBUG(("socks: ReadFromSocket(), have %u bytes total",
+ unsigned(mDataIoPtr - mData)));
+ if (mDataIoPtr == end) {
+ mDataIoPtr = nullptr;
+ mAmountToRead = 0;
+ mReadOffset = 0;
+ return PR_SUCCESS;
+ }
+
+ return PR_FAILURE;
+}
+
+PRStatus nsSOCKSSocketInfo::WriteToSocket(PRFileDesc* fd) {
+ int32_t rc;
+ const uint8_t* end;
+
+ if (!mDataLength) {
+ LOGDEBUG(("socks: WriteToSocket(), nothing to do"));
+ return PR_SUCCESS;
+ }
+
+ if (!mDataIoPtr) mDataIoPtr = mData;
+
+ end = mData + mDataLength;
+
+ while (mDataIoPtr < end) {
+ rc = PR_Write(fd, mDataIoPtr, end - mDataIoPtr);
+ if (rc < 0) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+ LOGDEBUG(("socks: WriteToSocket(), want write"));
+ }
+ break;
+ }
+
+ mDataIoPtr += rc;
+ }
+
+ if (mDataIoPtr == end) {
+ mDataIoPtr = nullptr;
+ mDataLength = 0;
+ mReadOffset = 0;
+ return PR_SUCCESS;
+ }
+
+ return PR_FAILURE;
+}
+
+static PRStatus nsSOCKSIOLayerConnect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime to) {
+ PRStatus status;
+ NetAddr dst;
+
+ nsSOCKSSocketInfo* info = (nsSOCKSSocketInfo*)fd->secret;
+ if (info == nullptr) return PR_FAILURE;
+
+ if (addr->raw.family == PR_AF_INET6 &&
+ PR_IsNetAddrType(addr, PR_IpAddrV4Mapped)) {
+ const uint8_t* srcp;
+
+ LOGDEBUG(("socks: converting ipv4-mapped ipv6 address to ipv4"));
+
+ // copied from _PR_ConvertToIpv4NetAddr()
+ dst.raw.family = AF_INET;
+ dst.inet.ip = htonl(INADDR_ANY);
+ dst.inet.port = htons(0);
+ srcp = addr->ipv6.ip.pr_s6_addr;
+ memcpy(&dst.inet.ip, srcp + 12, 4);
+ dst.inet.family = AF_INET;
+ dst.inet.port = addr->ipv6.port;
+ } else {
+ memcpy(&dst, addr, sizeof(dst));
+ }
+
+ info->SetDestinationAddr(dst);
+ info->SetConnectTimeout(to);
+
+ do {
+ status = info->DoHandshake(fd, -1);
+ } while (status == PR_SUCCESS && !info->IsConnected());
+
+ return status;
+}
+
+static PRStatus nsSOCKSIOLayerConnectContinue(PRFileDesc* fd, int16_t oflags) {
+ PRStatus status;
+
+ nsSOCKSSocketInfo* info = (nsSOCKSSocketInfo*)fd->secret;
+ if (info == nullptr) return PR_FAILURE;
+
+ do {
+ status = info->DoHandshake(fd, oflags);
+ } while (status == PR_SUCCESS && !info->IsConnected());
+
+ return status;
+}
+
+static int16_t nsSOCKSIOLayerPoll(PRFileDesc* fd, int16_t in_flags,
+ int16_t* out_flags) {
+ nsSOCKSSocketInfo* info = (nsSOCKSSocketInfo*)fd->secret;
+ if (info == nullptr) return PR_FAILURE;
+
+ if (!info->IsConnected()) {
+ *out_flags = 0;
+ return info->GetPollFlags();
+ }
+
+ return fd->lower->methods->poll(fd->lower, in_flags, out_flags);
+}
+
+static PRStatus nsSOCKSIOLayerClose(PRFileDesc* fd) {
+ nsSOCKSSocketInfo* info = (nsSOCKSSocketInfo*)fd->secret;
+ PRDescIdentity id = PR_GetLayersIdentity(fd);
+
+ if (info && id == nsSOCKSIOLayerIdentity) {
+ info->ForgetFD();
+ NS_RELEASE(info);
+ fd->identity = PR_INVALID_IO_LAYER;
+ }
+
+ return fd->lower->methods->close(fd->lower);
+}
+
+static PRFileDesc* nsSOCKSIOLayerAccept(PRFileDesc* fd, PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ // TODO: implement SOCKS support for accept
+ return fd->lower->methods->accept(fd->lower, addr, timeout);
+}
+
+static int32_t nsSOCKSIOLayerAcceptRead(PRFileDesc* sd, PRFileDesc** nd,
+ PRNetAddr** raddr, void* buf,
+ int32_t amount,
+ PRIntervalTime timeout) {
+ // TODO: implement SOCKS support for accept, then read from it
+ return sd->lower->methods->acceptread(sd->lower, nd, raddr, buf, amount,
+ timeout);
+}
+
+static PRStatus nsSOCKSIOLayerBind(PRFileDesc* fd, const PRNetAddr* addr) {
+ // TODO: implement SOCKS support for bind (very similar to connect)
+ return fd->lower->methods->bind(fd->lower, addr);
+}
+
+static PRStatus nsSOCKSIOLayerGetName(PRFileDesc* fd, PRNetAddr* addr) {
+ nsSOCKSSocketInfo* info = (nsSOCKSSocketInfo*)fd->secret;
+
+ if (info != nullptr && addr != nullptr) {
+ NetAddr temp;
+ info->GetExternalProxyAddr(temp);
+ NetAddrToPRNetAddr(&temp, addr);
+ return PR_SUCCESS;
+ }
+
+ return PR_FAILURE;
+}
+
+static PRStatus nsSOCKSIOLayerGetPeerName(PRFileDesc* fd, PRNetAddr* addr) {
+ nsSOCKSSocketInfo* info = (nsSOCKSSocketInfo*)fd->secret;
+
+ if (info != nullptr && addr != nullptr) {
+ NetAddr temp;
+ info->GetDestinationAddr(temp);
+ NetAddrToPRNetAddr(&temp, addr);
+ return PR_SUCCESS;
+ }
+
+ return PR_FAILURE;
+}
+
+static PRStatus nsSOCKSIOLayerListen(PRFileDesc* fd, int backlog) {
+ // TODO: implement SOCKS support for listen
+ return fd->lower->methods->listen(fd->lower, backlog);
+}
+
+// add SOCKS IO layer to an existing socket
+nsresult nsSOCKSIOLayerAddToSocket(int32_t family, const char* host,
+ int32_t port, nsIProxyInfo* proxy,
+ int32_t socksVersion, uint32_t flags,
+ uint32_t tlsFlags, PRFileDesc* fd) {
+ NS_ENSURE_TRUE((socksVersion == 4) || (socksVersion == 5),
+ NS_ERROR_NOT_INITIALIZED);
+
+ if (firstTime) {
+ // XXX hack until NSPR provides an official way to detect system IPv6
+ // support (bug 388519)
+ PRFileDesc* tmpfd = PR_OpenTCPSocket(PR_AF_INET6);
+ if (!tmpfd) {
+ ipv6Supported = false;
+ } else {
+ // If the system does not support IPv6, NSPR will push
+ // IPv6-to-IPv4 emulation layer onto the native layer
+ ipv6Supported = PR_GetIdentitiesLayer(tmpfd, PR_NSPR_IO_LAYER) == tmpfd;
+ PR_Close(tmpfd);
+ }
+
+ nsSOCKSIOLayerIdentity = PR_GetUniqueIdentity("SOCKS layer");
+ nsSOCKSIOLayerMethods = *PR_GetDefaultIOMethods();
+
+ nsSOCKSIOLayerMethods.connect = nsSOCKSIOLayerConnect;
+ nsSOCKSIOLayerMethods.connectcontinue = nsSOCKSIOLayerConnectContinue;
+ nsSOCKSIOLayerMethods.poll = nsSOCKSIOLayerPoll;
+ nsSOCKSIOLayerMethods.bind = nsSOCKSIOLayerBind;
+ nsSOCKSIOLayerMethods.acceptread = nsSOCKSIOLayerAcceptRead;
+ nsSOCKSIOLayerMethods.getsockname = nsSOCKSIOLayerGetName;
+ nsSOCKSIOLayerMethods.getpeername = nsSOCKSIOLayerGetPeerName;
+ nsSOCKSIOLayerMethods.accept = nsSOCKSIOLayerAccept;
+ nsSOCKSIOLayerMethods.listen = nsSOCKSIOLayerListen;
+ nsSOCKSIOLayerMethods.close = nsSOCKSIOLayerClose;
+
+ firstTime = false;
+ }
+
+ LOGDEBUG(("Entering nsSOCKSIOLayerAddToSocket()."));
+
+ PRFileDesc* layer;
+ PRStatus rv;
+
+ layer = PR_CreateIOLayerStub(nsSOCKSIOLayerIdentity, &nsSOCKSIOLayerMethods);
+ if (!layer) {
+ LOGERROR(("PR_CreateIOLayerStub() failed."));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsSOCKSSocketInfo* infoObject = new nsSOCKSSocketInfo();
+ if (!infoObject) {
+ // clean up IOLayerStub
+ LOGERROR(("Failed to create nsSOCKSSocketInfo()."));
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(infoObject);
+ infoObject->Init(socksVersion, family, proxy, host, flags, tlsFlags);
+ layer->secret = (PRFilePrivate*)infoObject;
+
+ PRDescIdentity fdIdentity = PR_GetLayersIdentity(fd);
+#if defined(XP_WIN)
+ if (fdIdentity == mozilla::net::nsNamedPipeLayerIdentity) {
+ // remember named pipe fd on the info object so that we can switch
+ // blocking and non-blocking mode on the pipe later.
+ infoObject->SetNamedPipeFD(fd);
+ }
+#endif
+ rv = PR_PushIOLayer(fd, fdIdentity, layer);
+
+ if (rv == PR_FAILURE) {
+ LOGERROR(("PR_PushIOLayer() failed. rv = %x.", rv));
+ NS_RELEASE(infoObject);
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool IsHostLocalTarget(const nsACString& aHost) {
+#if defined(XP_UNIX)
+ return StringBeginsWith(aHost, "file:"_ns);
+#elif defined(XP_WIN)
+ return IsNamedPipePath(aHost);
+#else
+ return false;
+#endif // XP_UNIX
+}
diff --git a/netwerk/socket/nsSOCKSIOLayer.h b/netwerk/socket/nsSOCKSIOLayer.h
new file mode 100644
index 0000000000..576c96ea7f
--- /dev/null
+++ b/netwerk/socket/nsSOCKSIOLayer.h
@@ -0,0 +1,21 @@
+/* -*- 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 nsSOCKSIOLayer_h__
+#define nsSOCKSIOLayer_h__
+
+#include "prio.h"
+#include "nscore.h"
+#include "nsIProxyInfo.h"
+
+nsresult nsSOCKSIOLayerAddToSocket(int32_t family, const char* host,
+ int32_t port, nsIProxyInfo* proxyInfo,
+ int32_t socksVersion, uint32_t flags,
+ uint32_t tlsFlags, PRFileDesc* fd);
+
+bool IsHostLocalTarget(const nsACString& aHost);
+
+#endif /* nsSOCKSIOLayer_h__ */
diff --git a/netwerk/socket/nsSOCKSSocketProvider.cpp b/netwerk/socket/nsSOCKSSocketProvider.cpp
new file mode 100644
index 0000000000..fc18e8e788
--- /dev/null
+++ b/netwerk/socket/nsSOCKSSocketProvider.cpp
@@ -0,0 +1,98 @@
+/* -*- 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 "nsNamedPipeIOLayer.h"
+#include "nsSOCKSSocketProvider.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+
+using mozilla::OriginAttributes;
+using namespace mozilla::net;
+
+//////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsSOCKSSocketProvider, nsISocketProvider)
+
+// Per-platform implemenation of OpenTCPSocket helper function
+// Different platforms have special cases to handle
+
+#if defined(XP_WIN)
+// The proxy host on Windows may be a named pipe uri, in which
+// case a named-pipe (rather than a socket) should be returned
+static PRFileDesc* OpenTCPSocket(int32_t family, nsIProxyInfo* proxy) {
+ PRFileDesc* sock = nullptr;
+
+ nsAutoCString proxyHost;
+ proxy->GetHost(proxyHost);
+ if (IsNamedPipePath(proxyHost)) {
+ sock = CreateNamedPipeLayer();
+ } else {
+ sock = PR_OpenTCPSocket(family);
+ }
+
+ return sock;
+}
+#elif defined(XP_UNIX)
+// The proxy host on UNIX systems may point to a local file uri
+// in which case we should create an AF_LOCAL (UNIX Domain) socket
+// instead of the requested AF_INET or AF_INET6 socket.
+
+// Normally,this socket would get thrown out and recreated later on
+// with the proper family, but we want to do it early here so that
+// we can enforce seccomp policy to blacklist socket(AF_INET) calls
+// to prevent the content sandbox from creating network requests
+static PRFileDesc* OpenTCPSocket(int32_t family, nsIProxyInfo* proxy) {
+ nsAutoCString proxyHost;
+ proxy->GetHost(proxyHost);
+ if (StringBeginsWith(proxyHost, "file://"_ns)) {
+ family = AF_LOCAL;
+ }
+
+ return PR_OpenTCPSocket(family);
+}
+#else
+// Default, pass-through to PR_OpenTCPSocket
+static PRFileDesc* OpenTCPSocket(int32_t family, nsIProxyInfo*) {
+ return PR_OpenTCPSocket(family);
+}
+#endif
+
+NS_IMETHODIMP
+nsSOCKSSocketProvider::NewSocket(int32_t family, const char* host, int32_t port,
+ nsIProxyInfo* proxy,
+ const OriginAttributes& originAttributes,
+ uint32_t flags, uint32_t tlsFlags,
+ PRFileDesc** result,
+ nsITLSSocketControl** tlsSocketControl) {
+ PRFileDesc* sock = OpenTCPSocket(family, proxy);
+ if (!sock) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = nsSOCKSIOLayerAddToSocket(family, host, port, proxy, mVersion,
+ flags, tlsFlags, sock);
+ if (NS_SUCCEEDED(rv)) {
+ *result = sock;
+ return NS_OK;
+ }
+
+ return NS_ERROR_SOCKET_CREATE_FAILED;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketProvider::AddToSocket(int32_t family, const char* host,
+ int32_t port, nsIProxyInfo* proxy,
+ const OriginAttributes& originAttributes,
+ uint32_t flags, uint32_t tlsFlags,
+ PRFileDesc* sock,
+ nsITLSSocketControl** tlsSocketControl) {
+ nsresult rv = nsSOCKSIOLayerAddToSocket(family, host, port, proxy, mVersion,
+ flags, tlsFlags, sock);
+
+ if (NS_FAILED(rv)) rv = NS_ERROR_SOCKET_CREATE_FAILED;
+ return rv;
+}
diff --git a/netwerk/socket/nsSOCKSSocketProvider.h b/netwerk/socket/nsSOCKSSocketProvider.h
new file mode 100644
index 0000000000..2733856733
--- /dev/null
+++ b/netwerk/socket/nsSOCKSSocketProvider.h
@@ -0,0 +1,28 @@
+/* -*- 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 nsSOCKSSocketProvider_h__
+#define nsSOCKSSocketProvider_h__
+
+#include "nsISocketProvider.h"
+
+// values for ctor's |version| argument
+enum { NS_SOCKS_VERSION_4 = 4, NS_SOCKS_VERSION_5 = 5 };
+
+class nsSOCKSSocketProvider : public nsISocketProvider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISOCKETPROVIDER
+
+ explicit nsSOCKSSocketProvider(uint32_t version) : mVersion(version) {}
+
+ private:
+ virtual ~nsSOCKSSocketProvider() = default;
+
+ uint32_t mVersion; // NS_SOCKS_VERSION_4 or 5
+};
+
+#endif /* nsSOCKSSocketProvider_h__ */
diff --git a/netwerk/socket/nsSocketProviderService.cpp b/netwerk/socket/nsSocketProviderService.cpp
new file mode 100644
index 0000000000..737820890e
--- /dev/null
+++ b/netwerk/socket/nsSocketProviderService.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "nsString.h"
+#include "nsISocketProvider.h"
+#include "nsError.h"
+#include "nsNSSComponent.h"
+#include "nsSOCKSSocketProvider.h"
+#include "nsSocketProviderService.h"
+#include "nsSSLSocketProvider.h"
+#include "nsTLSSocketProvider.h"
+#include "nsUDPSocketProvider.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsThreadUtils.h"
+#include "nsCRT.h"
+
+mozilla::StaticRefPtr<nsSocketProviderService>
+ nsSocketProviderService::gSingleton;
+
+////////////////////////////////////////////////////////////////////////////////
+
+already_AddRefed<nsISocketProviderService>
+nsSocketProviderService::GetOrCreate() {
+ RefPtr<nsSocketProviderService> inst;
+ if (gSingleton) {
+ inst = gSingleton;
+ } else {
+ inst = new nsSocketProviderService();
+ gSingleton = inst;
+ if (NS_IsMainThread()) {
+ mozilla::ClearOnShutdown(&gSingleton);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::nsSocketProviderService::GetOrCreate",
+ []() -> void { mozilla::ClearOnShutdown(&gSingleton); }));
+ }
+ }
+ return inst.forget();
+}
+
+NS_IMPL_ISUPPORTS(nsSocketProviderService, nsISocketProviderService)
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsSocketProviderService::GetSocketProvider(const char* type,
+ nsISocketProvider** result) {
+ nsCOMPtr<nsISocketProvider> inst;
+ if (!nsCRT::strcmp(type, "ssl") &&
+ (XRE_IsParentProcess() || XRE_IsSocketProcess()) &&
+ EnsureNSSInitializedChromeOrContent()) {
+ inst = new nsSSLSocketProvider();
+ } else if (!nsCRT::strcmp(type, "starttls") &&
+ (XRE_IsParentProcess() || XRE_IsSocketProcess()) &&
+ EnsureNSSInitializedChromeOrContent()) {
+ inst = new nsTLSSocketProvider();
+ } else if (!nsCRT::strcmp(type, "socks")) {
+ inst = new nsSOCKSSocketProvider(NS_SOCKS_VERSION_5);
+ } else if (!nsCRT::strcmp(type, "socks4")) {
+ inst = new nsSOCKSSocketProvider(NS_SOCKS_VERSION_4);
+ } else if (!nsCRT::strcmp(type, "udp")) {
+ inst = new nsUDPSocketProvider();
+ } else {
+ return NS_ERROR_UNKNOWN_SOCKET_TYPE;
+ }
+ inst.forget(result);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/socket/nsSocketProviderService.h b/netwerk/socket/nsSocketProviderService.h
new file mode 100644
index 0000000000..85b2ba04a4
--- /dev/null
+++ b/netwerk/socket/nsSocketProviderService.h
@@ -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/. */
+
+#ifndef nsSocketProviderService_h__
+#define nsSocketProviderService_h__
+
+#include "nsISocketProviderService.h"
+#include "mozilla/StaticPtr.h"
+
+class nsSocketProviderService : public nsISocketProviderService {
+ nsSocketProviderService() = default;
+ virtual ~nsSocketProviderService() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISOCKETPROVIDERSERVICE
+
+ static already_AddRefed<nsISocketProviderService> GetOrCreate();
+
+ private:
+ static mozilla::StaticRefPtr<nsSocketProviderService> gSingleton;
+};
+
+#endif /* nsSocketProviderService_h__ */
diff --git a/netwerk/socket/nsUDPSocketProvider.cpp b/netwerk/socket/nsUDPSocketProvider.cpp
new file mode 100644
index 0000000000..e99ef36a6d
--- /dev/null
+++ b/netwerk/socket/nsUDPSocketProvider.cpp
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUDPSocketProvider.h"
+
+#include "nspr.h"
+
+using mozilla::OriginAttributes;
+
+NS_IMPL_ISUPPORTS(nsUDPSocketProvider, nsISocketProvider)
+
+NS_IMETHODIMP
+nsUDPSocketProvider::NewSocket(int32_t aFamily, const char* aHost,
+ int32_t aPort, nsIProxyInfo* aProxy,
+ const OriginAttributes& originAttributes,
+ uint32_t aFlags, uint32_t aTlsFlags,
+ PRFileDesc** aFileDesc,
+ nsITLSSocketControl** aTLSSocketControl) {
+ NS_ENSURE_ARG_POINTER(aFileDesc);
+
+ PRFileDesc* udpFD = PR_OpenUDPSocket(aFamily);
+ if (!udpFD) return NS_ERROR_FAILURE;
+
+ *aFileDesc = udpFD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocketProvider::AddToSocket(int32_t aFamily, const char* aHost,
+ int32_t aPort, nsIProxyInfo* aProxy,
+ const OriginAttributes& originAttributes,
+ uint32_t aFlags, uint32_t aTlsFlags,
+ struct PRFileDesc* aFileDesc,
+ nsITLSSocketControl** aTLSSocketControl) {
+ // does not make sense to strap a UDP socket onto an existing socket
+ MOZ_ASSERT_UNREACHABLE("Cannot layer UDP socket on an existing socket");
+ return NS_ERROR_UNEXPECTED;
+}
diff --git a/netwerk/socket/nsUDPSocketProvider.h b/netwerk/socket/nsUDPSocketProvider.h
new file mode 100644
index 0000000000..fb1fe749d9
--- /dev/null
+++ b/netwerk/socket/nsUDPSocketProvider.h
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUDPSocketProvider_h__
+#define nsUDPSocketProvider_h__
+
+#include "nsISocketProvider.h"
+#include "mozilla/Attributes.h"
+
+class nsUDPSocketProvider final : public nsISocketProvider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISOCKETPROVIDER
+
+ private:
+ ~nsUDPSocketProvider() = default;
+};
+
+#endif /* nsUDPSocketProvider_h__ */
diff --git a/netwerk/streamconv/converters/moz.build b/netwerk/streamconv/converters/moz.build
new file mode 100644
index 0000000000..d985eb363a
--- /dev/null
+++ b/netwerk/streamconv/converters/moz.build
@@ -0,0 +1,31 @@
+# -*- 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 += ["nsICompressConvStats.idl"]
+
+EXPORTS += [
+ "nsHTTPCompressConv.h",
+ "nsUnknownDecoder.h",
+]
+
+XPIDL_MODULE = "necko_http"
+
+UNIFIED_SOURCES += [
+ "mozTXTToHTMLConv.cpp",
+ "nsDirIndex.cpp",
+ "nsDirIndexParser.cpp",
+ "nsHTTPCompressConv.cpp",
+ "nsIndexedToHTML.cpp",
+ "nsMultiMixedConv.cpp",
+ "nsUnknownDecoder.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/modules/brotli/dec",
+ "/netwerk/base",
+]
diff --git a/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp b/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
new file mode 100644
index 0000000000..a9f1d5183f
--- /dev/null
+++ b/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
@@ -0,0 +1,1291 @@
+/* -*- 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/TextUtils.h"
+#include "mozTXTToHTMLConv.h"
+#include "mozilla/intl/Segmenter.h"
+#include "mozilla/Maybe.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsNetUtil.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "nsCRT.h"
+#include "nsIExternalProtocolHandler.h"
+#include "nsIURI.h"
+
+#include <algorithm>
+
+#ifdef DEBUG_BenB_Perf
+# include "prtime.h"
+# include "prinrval.h"
+#endif
+
+using mozilla::IsAscii;
+using mozilla::IsAsciiAlpha;
+using mozilla::IsAsciiDigit;
+using mozilla::Maybe;
+using mozilla::Some;
+using mozilla::Span;
+using mozilla::intl::GraphemeClusterBreakIteratorUtf16;
+using mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16;
+
+const double growthRate = 1.2;
+
+// Bug 183111, editor now replaces multiple spaces with leading
+// 0xA0's and a single ending space, so need to treat 0xA0's as spaces.
+// 0xA0 is the Latin1/Unicode character for "non-breaking space (nbsp)"
+// Also recognize the Japanese ideographic space 0x3000 as a space.
+static inline bool IsSpace(const char16_t aChar) {
+ return (nsCRT::IsAsciiSpace(aChar) || aChar == 0xA0 || aChar == 0x3000);
+}
+
+// Escape Char will take ch, escape it and append the result to
+// aStringToAppendTo
+void mozTXTToHTMLConv::EscapeChar(const char16_t ch,
+ nsAString& aStringToAppendTo,
+ bool inAttribute) {
+ switch (ch) {
+ case '<':
+ aStringToAppendTo.AppendLiteral("&lt;");
+ break;
+ case '>':
+ aStringToAppendTo.AppendLiteral("&gt;");
+ break;
+ case '&':
+ aStringToAppendTo.AppendLiteral("&amp;");
+ break;
+ case '"':
+ if (inAttribute) {
+ aStringToAppendTo.AppendLiteral("&quot;");
+ break;
+ }
+ // else fall through
+ [[fallthrough]];
+ default:
+ aStringToAppendTo += ch;
+ }
+}
+
+// EscapeStr takes the passed in string and
+// escapes it IN PLACE.
+void mozTXTToHTMLConv::EscapeStr(nsString& aInString, bool inAttribute) {
+ // the replace substring routines
+ // don't seem to work if you have a character
+ // in the in string that is also in the replacement
+ // string! =(
+ // aInString.ReplaceSubstring("&", "&amp;");
+ // aInString.ReplaceSubstring("<", "&lt;");
+ // aInString.ReplaceSubstring(">", "&gt;");
+ for (uint32_t i = 0; i < aInString.Length();) {
+ switch (aInString[i]) {
+ case '<':
+ aInString.Cut(i, 1);
+ aInString.InsertLiteral(u"&lt;", i);
+ i += 4; // skip past the integers we just added
+ break;
+ case '>':
+ aInString.Cut(i, 1);
+ aInString.InsertLiteral(u"&gt;", i);
+ i += 4; // skip past the integers we just added
+ break;
+ case '&':
+ aInString.Cut(i, 1);
+ aInString.InsertLiteral(u"&amp;", i);
+ i += 5; // skip past the integers we just added
+ break;
+ case '"':
+ if (inAttribute) {
+ aInString.Cut(i, 1);
+ aInString.InsertLiteral(u"&quot;", i);
+ i += 6;
+ break;
+ }
+ // else fall through
+ [[fallthrough]];
+ default:
+ i++;
+ }
+ }
+}
+
+void mozTXTToHTMLConv::UnescapeStr(const char16_t* aInString, int32_t aStartPos,
+ int32_t aLength, nsString& aOutString) {
+ const char16_t* subString = nullptr;
+ for (uint32_t i = aStartPos; int32_t(i) - aStartPos < aLength;) {
+ int32_t remainingChars = i - aStartPos;
+ if (aInString[i] == '&') {
+ subString = &aInString[i];
+ if (!NS_strncmp(subString, u"&lt;",
+ std::min(4, aLength - remainingChars))) {
+ aOutString.Append(char16_t('<'));
+ i += 4;
+ } else if (!NS_strncmp(subString, u"&gt;",
+ std::min(4, aLength - remainingChars))) {
+ aOutString.Append(char16_t('>'));
+ i += 4;
+ } else if (!NS_strncmp(subString, u"&amp;",
+ std::min(5, aLength - remainingChars))) {
+ aOutString.Append(char16_t('&'));
+ i += 5;
+ } else if (!NS_strncmp(subString, u"&quot;",
+ std::min(6, aLength - remainingChars))) {
+ aOutString.Append(char16_t('"'));
+ i += 6;
+ } else {
+ aOutString += aInString[i];
+ i++;
+ }
+ } else {
+ aOutString += aInString[i];
+ i++;
+ }
+ }
+}
+
+void mozTXTToHTMLConv::CompleteAbbreviatedURL(const char16_t* aInString,
+ int32_t aInLength,
+ const uint32_t pos,
+ nsString& aOutString) {
+ NS_ASSERTION(int32_t(pos) < aInLength,
+ "bad args to CompleteAbbreviatedURL, see bug #190851");
+ if (int32_t(pos) >= aInLength) return;
+
+ if (aInString[pos] == '@') {
+ // only pre-pend a mailto url if the string contains a .domain in it..
+ // i.e. we want to linkify johndoe@foo.com but not "let's meet @8pm"
+ nsDependentString inString(aInString, aInLength);
+ if (inString.FindChar('.', pos) !=
+ kNotFound) // if we have a '.' after the @ sign....
+ {
+ aOutString.AssignLiteral("mailto:");
+ aOutString += aInString;
+ }
+ } else if (aInString[pos] == '.') {
+ if (ItMatchesDelimited(aInString, aInLength, u"www.", 4, LT_IGNORE,
+ LT_IGNORE)) {
+ aOutString.AssignLiteral("http://");
+ aOutString += aInString;
+ }
+ }
+}
+
+bool mozTXTToHTMLConv::FindURLStart(const char16_t* aInString,
+ int32_t aInLength, const uint32_t pos,
+ const modetype check, uint32_t& start) {
+ switch (check) { // no breaks, because end of blocks is never reached
+ case RFC1738: {
+ if (!NS_strncmp(&aInString[std::max(int32_t(pos - 4), 0)], u"<URL:", 5)) {
+ start = pos + 1;
+ return true;
+ }
+ return false;
+ }
+ case RFC2396E: {
+ nsDependentSubstring temp(aInString, aInLength);
+ int32_t i = pos <= 0 ? kNotFound : temp.RFindCharInSet(u"<>\"", pos - 1);
+ if (i != kNotFound &&
+ (temp[uint32_t(i)] == '<' || temp[uint32_t(i)] == '"')) {
+ start = uint32_t(++i);
+ return start < pos;
+ }
+ return false;
+ }
+ case freetext: {
+ int32_t i = pos - 1;
+ for (; i >= 0 &&
+ (IsAsciiAlpha(aInString[uint32_t(i)]) ||
+ IsAsciiDigit(aInString[uint32_t(i)]) ||
+ aInString[uint32_t(i)] == '+' || aInString[uint32_t(i)] == '-' ||
+ aInString[uint32_t(i)] == '.');
+ i--) {
+ ;
+ }
+ if (++i >= 0 && uint32_t(i) < pos &&
+ IsAsciiAlpha(aInString[uint32_t(i)])) {
+ start = uint32_t(i);
+ return true;
+ }
+ return false;
+ }
+ case abbreviated: {
+ int32_t i = pos - 1;
+ // This disallows non-ascii-characters for email.
+ // Currently correct, but revisit later after standards changed.
+ bool isEmail = aInString[pos] == (char16_t)'@';
+ // These chars mark the start of the URL
+ for (; i >= 0 && aInString[uint32_t(i)] != '>' &&
+ aInString[uint32_t(i)] != '<' && aInString[uint32_t(i)] != '"' &&
+ aInString[uint32_t(i)] != '\'' && aInString[uint32_t(i)] != '`' &&
+ aInString[uint32_t(i)] != ',' && aInString[uint32_t(i)] != '{' &&
+ aInString[uint32_t(i)] != '[' && aInString[uint32_t(i)] != '(' &&
+ aInString[uint32_t(i)] != '|' && aInString[uint32_t(i)] != '\\' &&
+ !IsSpace(aInString[uint32_t(i)]) &&
+ (!isEmail || IsAscii(aInString[uint32_t(i)])) &&
+ (!isEmail || aInString[uint32_t(i)] != ')');
+ i--) {
+ ;
+ }
+ if (++i >= 0 && uint32_t(i) < pos &&
+ (IsAsciiAlpha(aInString[uint32_t(i)]) ||
+ IsAsciiDigit(aInString[uint32_t(i)]))) {
+ start = uint32_t(i);
+ return true;
+ }
+ return false;
+ }
+ default:
+ return false;
+ } // switch
+}
+
+bool mozTXTToHTMLConv::FindURLEnd(const char16_t* aInString,
+ int32_t aInStringLength, const uint32_t pos,
+ const modetype check, const uint32_t start,
+ uint32_t& end) {
+ switch (check) { // no breaks, because end of blocks is never reached
+ case RFC1738:
+ case RFC2396E: {
+ nsDependentSubstring temp(aInString, aInStringLength);
+
+ int32_t i = temp.FindCharInSet(u"<>\"", pos + 1);
+ if (i != kNotFound &&
+ temp[uint32_t(i--)] ==
+ (check == RFC1738 || temp[start - 1] == '<' ? '>' : '"')) {
+ end = uint32_t(i);
+ return end > pos;
+ }
+ return false;
+ }
+ case freetext:
+ case abbreviated: {
+ uint32_t i = pos + 1;
+ bool isEmail = aInString[pos] == (char16_t)'@';
+ bool seenOpeningParenthesis = false; // there is a '(' earlier in the URL
+ bool seenOpeningSquareBracket =
+ false; // there is a '[' earlier in the URL
+ for (; int32_t(i) < aInStringLength; i++) {
+ // These chars mark the end of the URL
+ if (aInString[i] == '>' || aInString[i] == '<' || aInString[i] == '"' ||
+ aInString[i] == '`' || aInString[i] == '}' || aInString[i] == '{' ||
+ (aInString[i] == ')' && !seenOpeningParenthesis) ||
+ (aInString[i] == ']' && !seenOpeningSquareBracket) ||
+ // Allow IPv6 adresses like http://[1080::8:800:200C:417A]/foo.
+ (aInString[i] == '[' && i > 2 &&
+ (aInString[i - 1] != '/' || aInString[i - 2] != '/')) ||
+ IsSpace(aInString[i])) {
+ break;
+ }
+ // Disallow non-ascii-characters for email.
+ // Currently correct, but revisit later after standards changed.
+ if (isEmail && (aInString[i] == '(' || aInString[i] == '\'' ||
+ !IsAscii(aInString[i]))) {
+ break;
+ }
+ if (aInString[i] == '(') seenOpeningParenthesis = true;
+ if (aInString[i] == '[') seenOpeningSquareBracket = true;
+ }
+ // These chars are allowed in the middle of the URL, but not at end.
+ // Technically they are, but are used in normal text after the URL.
+ while (--i > pos && (aInString[i] == '.' || aInString[i] == ',' ||
+ aInString[i] == ';' || aInString[i] == '!' ||
+ aInString[i] == '?' || aInString[i] == '-' ||
+ aInString[i] == ':' || aInString[i] == '\'')) {
+ ;
+ }
+ if (i > pos) {
+ end = i;
+ return true;
+ }
+ return false;
+ }
+ default:
+ return false;
+ } // switch
+}
+
+void mozTXTToHTMLConv::CalculateURLBoundaries(
+ const char16_t* aInString, int32_t aInStringLength, const uint32_t pos,
+ const uint32_t whathasbeendone, const modetype check, const uint32_t start,
+ const uint32_t end, nsString& txtURL, nsString& desc,
+ int32_t& replaceBefore, int32_t& replaceAfter) {
+ uint32_t descstart = start;
+ switch (check) {
+ case RFC1738: {
+ descstart = start - 5;
+ desc.Append(&aInString[descstart],
+ end - descstart + 2); // include "<URL:" and ">"
+ replaceAfter = end - pos + 1;
+ } break;
+ case RFC2396E: {
+ descstart = start - 1;
+ desc.Append(&aInString[descstart],
+ end - descstart + 2); // include brackets
+ replaceAfter = end - pos + 1;
+ } break;
+ case freetext:
+ case abbreviated: {
+ descstart = start;
+ desc.Append(&aInString[descstart],
+ end - start + 1); // don't include brackets
+ replaceAfter = end - pos;
+ } break;
+ default:
+ break;
+ } // switch
+
+ EscapeStr(desc, false);
+
+ txtURL.Append(&aInString[start], end - start + 1);
+ txtURL.StripWhitespace();
+
+ // FIX ME
+ nsAutoString temp2;
+ ScanTXT(nsDependentSubstring(&aInString[descstart], pos - descstart),
+ ~kURLs /*prevents loop*/ & whathasbeendone, temp2);
+ replaceBefore = temp2.Length();
+}
+
+bool mozTXTToHTMLConv::ShouldLinkify(const nsCString& aURL) {
+ if (!mIOService) return false;
+
+ nsAutoCString scheme;
+ nsresult rv = mIOService->ExtractScheme(aURL, scheme);
+ if (NS_FAILED(rv)) return false;
+
+ if (scheme == "http" || scheme == "https" || scheme == "mailto") {
+ return true;
+ }
+
+ // Get the handler for this scheme.
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = mIOService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return false;
+
+ // Is it an external protocol handler? If not, linkify it.
+ nsCOMPtr<nsIExternalProtocolHandler> externalHandler =
+ do_QueryInterface(handler);
+ if (!externalHandler) return true; // handler is built-in, linkify it!
+
+ // If external app exists for the scheme then linkify it.
+ bool exists;
+ rv = externalHandler->ExternalAppExistsForScheme(scheme, &exists);
+ return (NS_SUCCEEDED(rv) && exists);
+}
+
+bool mozTXTToHTMLConv::CheckURLAndCreateHTML(const nsString& txtURL,
+ const nsString& desc,
+ const modetype mode,
+ nsString& outputHTML) {
+ // Create *uri from txtURL
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv;
+ // Lazily initialize mIOService
+ if (!mIOService) {
+ mIOService = do_GetIOService();
+
+ if (!mIOService) return false;
+ }
+
+ // See if the url should be linkified.
+ NS_ConvertUTF16toUTF8 utf8URL(txtURL);
+ if (!ShouldLinkify(utf8URL)) return false;
+
+ // it would be faster if we could just check to see if there is a protocol
+ // handler for the url and return instead of actually trying to create a
+ // url...
+ rv = mIOService->NewURI(utf8URL, nullptr, nullptr, getter_AddRefs(uri));
+
+ // Real work
+ if (NS_SUCCEEDED(rv) && uri) {
+ outputHTML.AssignLiteral("<a class=\"moz-txt-link-");
+ switch (mode) {
+ case RFC1738:
+ outputHTML.AppendLiteral("rfc1738");
+ break;
+ case RFC2396E:
+ outputHTML.AppendLiteral("rfc2396E");
+ break;
+ case freetext:
+ outputHTML.AppendLiteral("freetext");
+ break;
+ case abbreviated:
+ outputHTML.AppendLiteral("abbreviated");
+ break;
+ default:
+ break;
+ }
+ nsAutoString escapedURL(txtURL);
+ EscapeStr(escapedURL, true);
+
+ outputHTML.AppendLiteral("\" href=\"");
+ outputHTML += escapedURL;
+ outputHTML.AppendLiteral("\">");
+ outputHTML += desc;
+ outputHTML.AppendLiteral("</a>");
+ return true;
+ }
+ return false;
+}
+
+NS_IMETHODIMP mozTXTToHTMLConv::FindURLInPlaintext(const char16_t* aInString,
+ int32_t aInLength,
+ int32_t aPos,
+ int32_t* aStartPos,
+ int32_t* aEndPos) {
+ // call FindURL on the passed in string
+ nsAutoString outputHTML; // we'll ignore the generated output HTML
+
+ *aStartPos = -1;
+ *aEndPos = -1;
+
+ FindURL(aInString, aInLength, aPos, kURLs, outputHTML, *aStartPos, *aEndPos);
+
+ return NS_OK;
+}
+
+bool mozTXTToHTMLConv::FindURL(const char16_t* aInString, int32_t aInLength,
+ const uint32_t pos,
+ const uint32_t whathasbeendone,
+ nsString& outputHTML, int32_t& replaceBefore,
+ int32_t& replaceAfter) {
+ enum statetype { unchecked, invalid, startok, endok, success };
+ static const modetype ranking[] = {RFC1738, RFC2396E, freetext, abbreviated};
+
+ statetype state[mozTXTToHTMLConv_lastMode + 1]; // 0(=unknown)..lastMode
+ /* I don't like this abuse of enums as index for the array,
+ but I don't know a better method */
+
+ // Define, which modes to check
+ /* all modes but abbreviated are checked for text[pos] == ':',
+ only abbreviated for '.', RFC2396E and abbreviated for '@' */
+ for (modetype iState = unknown; iState <= mozTXTToHTMLConv_lastMode;
+ iState = modetype(iState + 1)) {
+ state[iState] = aInString[pos] == ':' ? unchecked : invalid;
+ }
+ switch (aInString[pos]) {
+ case '@':
+ state[RFC2396E] = unchecked;
+ [[fallthrough]];
+ case '.':
+ state[abbreviated] = unchecked;
+ break;
+ case ':':
+ state[abbreviated] = invalid;
+ break;
+ default:
+ break;
+ }
+
+ // Test, first successful mode wins, sequence defined by |ranking|
+ int32_t iCheck = 0; // the currently tested modetype
+ modetype check = ranking[iCheck];
+ for (; iCheck < mozTXTToHTMLConv_numberOfModes && state[check] != success;
+ iCheck++)
+ /* check state from last run.
+ If this is the first, check this one, which isn't = success yet */
+ {
+ check = ranking[iCheck];
+
+ uint32_t start, end;
+
+ if (state[check] == unchecked) {
+ if (FindURLStart(aInString, aInLength, pos, check, start)) {
+ state[check] = startok;
+ }
+ }
+
+ if (state[check] == startok) {
+ if (FindURLEnd(aInString, aInLength, pos, check, start, end)) {
+ state[check] = endok;
+ }
+ }
+
+ if (state[check] == endok) {
+ nsAutoString txtURL, desc;
+ int32_t resultReplaceBefore, resultReplaceAfter;
+
+ CalculateURLBoundaries(aInString, aInLength, pos, whathasbeendone, check,
+ start, end, txtURL, desc, resultReplaceBefore,
+ resultReplaceAfter);
+
+ if (aInString[pos] != ':') {
+ nsAutoString temp = txtURL;
+ txtURL.SetLength(0);
+ CompleteAbbreviatedURL(temp.get(), temp.Length(), pos - start, txtURL);
+ }
+
+ if (!txtURL.IsEmpty() &&
+ CheckURLAndCreateHTML(txtURL, desc, check, outputHTML)) {
+ replaceBefore = resultReplaceBefore;
+ replaceAfter = resultReplaceAfter;
+ state[check] = success;
+ }
+ } // if
+ } // for
+ return state[check] == success;
+}
+
+static inline bool IsAlpha(const uint32_t aChar) {
+ return mozilla::unicode::GetGenCategory(aChar) == nsUGenCategory::kLetter;
+}
+
+static inline bool IsDigit(const uint32_t aChar) {
+ return mozilla::unicode::GetGenCategory(aChar) == nsUGenCategory::kNumber;
+}
+
+bool mozTXTToHTMLConv::ItMatchesDelimited(const char16_t* aInString,
+ int32_t aInLength,
+ const char16_t* rep, int32_t aRepLen,
+ LIMTYPE before, LIMTYPE after) {
+ // this little method gets called a LOT. I found we were spending a
+ // lot of time just calculating the length of the variable "rep"
+ // over and over again every time we called it. So we're now passing
+ // an integer in here.
+ int32_t textLen = aInLength;
+
+ if (((before == LT_IGNORE && (after == LT_IGNORE || after == LT_DELIMITER)) &&
+ textLen < aRepLen) ||
+ ((before != LT_IGNORE || (after != LT_IGNORE && after != LT_DELIMITER)) &&
+ textLen < aRepLen + 1) ||
+ (before != LT_IGNORE && after != LT_IGNORE && after != LT_DELIMITER &&
+ textLen < aRepLen + 2)) {
+ return false;
+ }
+
+ uint32_t text0 = aInString[0];
+ if (aInLength > 1 && NS_IS_SURROGATE_PAIR(text0, aInString[1])) {
+ text0 = SURROGATE_TO_UCS4(text0, aInString[1]);
+ }
+ // find length of the char/cluster to be ignored
+ int32_t ignoreLen = before == LT_IGNORE ? 0 : 1;
+ if (ignoreLen) {
+ GraphemeClusterBreakIteratorUtf16 ci(
+ Span<const char16_t>(aInString, aInLength));
+ ignoreLen = *ci.Next();
+ }
+
+ int32_t afterIndex = aRepLen + ignoreLen;
+ uint32_t textAfterPos = aInString[afterIndex];
+ if (aInLength > afterIndex + 1 &&
+ NS_IS_SURROGATE_PAIR(textAfterPos, aInString[afterIndex + 1])) {
+ textAfterPos = SURROGATE_TO_UCS4(textAfterPos, aInString[afterIndex + 1]);
+ }
+
+ return !((before == LT_ALPHA && !IsAlpha(text0)) ||
+ (before == LT_DIGIT && !IsDigit(text0)) ||
+ (before == LT_DELIMITER &&
+ (IsAlpha(text0) || IsDigit(text0) || text0 == *rep)) ||
+ (after == LT_ALPHA && !IsAlpha(textAfterPos)) ||
+ (after == LT_DIGIT && !IsDigit(textAfterPos)) ||
+ (after == LT_DELIMITER &&
+ (IsAlpha(textAfterPos) || IsDigit(textAfterPos) ||
+ textAfterPos == *rep)) ||
+ !Substring(Substring(aInString, aInString + aInLength), ignoreLen,
+ aRepLen)
+ .Equals(Substring(rep, rep + aRepLen),
+ nsCaseInsensitiveStringComparator));
+}
+
+uint32_t mozTXTToHTMLConv::NumberOfMatches(const char16_t* aInString,
+ int32_t aInStringLength,
+ const char16_t* rep, int32_t aRepLen,
+ LIMTYPE before, LIMTYPE after) {
+ uint32_t result = 0;
+
+ // Limit lookahead length to avoid pathological O(n^2) behavior; looking so
+ // far ahead is unlikely to be important for cases where styling marked-up
+ // fragments is actually useful anyhow.
+ const uint32_t len =
+ std::min(2000u, mozilla::AssertedCast<uint32_t>(aInStringLength));
+ GraphemeClusterBreakIteratorUtf16 ci(Span<const char16_t>(aInString, len));
+ for (uint32_t pos = 0; pos < len; pos = *ci.Next()) {
+ if (ItMatchesDelimited(aInString + pos, aInStringLength - pos, rep, aRepLen,
+ before, after)) {
+ result++;
+ }
+ }
+ return result;
+}
+
+// NOTE: the converted html for the phrase is appended to aOutString
+// tagHTML and attributeHTML are plain ASCII (literal strings, in fact)
+bool mozTXTToHTMLConv::StructPhraseHit(
+ const char16_t* aInString, int32_t aInStringLength, bool col0,
+ const char16_t* tagTXT, int32_t aTagTXTLen, const char* tagHTML,
+ const char* attributeHTML, nsAString& aOutString, uint32_t& openTags) {
+ /* We're searching for the following pattern:
+ LT_DELIMITER - "*" - ALPHA -
+ [ some text (maybe more "*"-pairs) - ALPHA ] "*" - LT_DELIMITER.
+ <strong> is only inserted, if existence of a pair could be verified
+ We use the first opening/closing tag, if we can choose */
+
+ const char16_t* newOffset = aInString;
+ int32_t newLength = aInStringLength;
+ if (!col0) // skip the first element?
+ {
+ newOffset = &aInString[1];
+ newLength = aInStringLength - 1;
+ }
+
+ // opening tag
+ if (ItMatchesDelimited(aInString, aInStringLength, tagTXT, aTagTXTLen,
+ (col0 ? LT_IGNORE : LT_DELIMITER),
+ LT_ALPHA) // is opening tag
+ && NumberOfMatches(newOffset, newLength, tagTXT, aTagTXTLen, LT_ALPHA,
+ LT_DELIMITER) // remaining closing tags
+ > openTags) {
+ openTags++;
+ aOutString.Append('<');
+ aOutString.AppendASCII(tagHTML);
+ aOutString.Append(char16_t(' '));
+ aOutString.AppendASCII(attributeHTML);
+ aOutString.AppendLiteral("><span class=\"moz-txt-tag\">");
+ aOutString.Append(tagTXT);
+ aOutString.AppendLiteral("</span>");
+ return true;
+ }
+
+ // closing tag
+ if (openTags > 0 && ItMatchesDelimited(aInString, aInStringLength, tagTXT,
+ aTagTXTLen, LT_ALPHA, LT_DELIMITER)) {
+ openTags--;
+ aOutString.AppendLiteral("<span class=\"moz-txt-tag\">");
+ aOutString.Append(tagTXT);
+ aOutString.AppendLiteral("</span></");
+ aOutString.AppendASCII(tagHTML);
+ aOutString.Append(char16_t('>'));
+ return true;
+ }
+
+ return false;
+}
+
+bool mozTXTToHTMLConv::SmilyHit(const char16_t* aInString, int32_t aLength,
+ bool col0, const char* tagTXT,
+ const nsString& imageName, nsString& outputHTML,
+ int32_t& glyphTextLen) {
+ if (!aInString || !tagTXT || imageName.IsEmpty()) return false;
+
+ int32_t tagLen = strlen(tagTXT);
+
+ uint32_t delim = (col0 ? 0 : 1) + tagLen;
+
+ if ((col0 || IsSpace(aInString[0])) &&
+ (aLength <= int32_t(delim) || IsSpace(aInString[delim]) ||
+ (aLength > int32_t(delim + 1) &&
+ (aInString[delim] == '.' || aInString[delim] == ',' ||
+ aInString[delim] == ';' || aInString[delim] == '8' ||
+ aInString[delim] == '>' || aInString[delim] == '!' ||
+ aInString[delim] == '?') &&
+ IsSpace(aInString[delim + 1]))) &&
+ ItMatchesDelimited(aInString, aLength,
+ NS_ConvertASCIItoUTF16(tagTXT).get(), tagLen,
+ col0 ? LT_IGNORE : LT_DELIMITER, LT_IGNORE)
+ // Note: tests at different pos for LT_IGNORE and LT_DELIMITER
+ ) {
+ if (!col0) {
+ outputHTML.Truncate();
+ outputHTML.Append(char16_t(' '));
+ }
+
+ outputHTML.Append(imageName); // emoji unicode
+ glyphTextLen = (col0 ? 0 : 1) + tagLen;
+ return true;
+ }
+
+ return false;
+}
+
+// the glyph is appended to aOutputString instead of the original string...
+bool mozTXTToHTMLConv::GlyphHit(const char16_t* aInString, int32_t aInLength,
+ bool col0, nsAString& aOutputString,
+ int32_t& glyphTextLen) {
+ char16_t text0 = aInString[0];
+ char16_t text1 = aInString[1];
+ char16_t firstChar = (col0 ? text0 : text1);
+
+ // temporary variable used to store the glyph html text
+ nsAutoString outputHTML;
+ bool bTestSmilie;
+ bool bArg = false;
+ int i;
+
+ // refactor some of this mess to avoid code duplication and speed execution a
+ // bit there are two cases that need to be tried one after another. To avoid a
+ // lot of duplicate code, rolling into a loop
+
+ i = 0;
+ while (i < 2) {
+ bTestSmilie = false;
+ if (!i && (firstChar == ':' || firstChar == ';' || firstChar == '=' ||
+ firstChar == '>' || firstChar == '8' || firstChar == 'O')) {
+ // first test passed
+
+ bTestSmilie = true;
+ bArg = col0;
+ }
+ if (i && col0 &&
+ (text1 == ':' || text1 == ';' || text1 == '=' || text1 == '>' ||
+ text1 == '8' || text1 == 'O')) {
+ // second test passed
+
+ bTestSmilie = true;
+ bArg = false;
+ }
+ if (bTestSmilie && (SmilyHit(aInString, aInLength, bArg, ":-)",
+ u"🙂"_ns, // smile, U+1F642
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":)",
+ u"🙂"_ns, // smile, U+1F642
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-D",
+ u"😂"_ns, // laughing, U+1F602
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-(",
+ u"🙁"_ns, // frown, U+1F641
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":(",
+ u"🙁"_ns, // frown, U+1F641
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":$",
+ u"😳"_ns, // embarassed, U+1F633
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ";-)",
+ u"😉"_ns, // wink, U+1F609
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, col0, ";)",
+ u"😉"_ns, // wink, U+1F609
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-\\",
+ u"😕"_ns, // undecided, U+1F615
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-P",
+ u"😛"_ns, // tongue, U+1F61B
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ";-P",
+ u"😜"_ns, // winking face with tongue, U+1F61C
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, "=-O",
+ u"😮"_ns, // surprise, U+1F62E
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-*",
+ u"😘"_ns, // kiss, U+1F618
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ">:o",
+ u"🤬"_ns, // swearing, U+1F92C
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ">:-o",
+ u"🤬"_ns, // swearing, U+1F92C
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ">:(",
+ u"😠"_ns, // angry, U+1F620
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ">:-(",
+ u"😠"_ns, // angry, U+1F620
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, "8-)",
+ u"😎"_ns, // cool, U+1F60E
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-$",
+ u"🤑"_ns, // money, U+1F911
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-!",
+ u"😬"_ns, // foot, U+1F62C
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, "O:-)",
+ u"😇"_ns, // innocent, U+1F607
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":'(",
+ u"😭"_ns, // cry, U+1F62D
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-X",
+ u"🤐"_ns, // sealed, U+1F910
+ outputHTML, glyphTextLen))) {
+ aOutputString.Append(outputHTML);
+ return true;
+ }
+ i++;
+ }
+ if (text0 == '\f') {
+ aOutputString.AppendLiteral("<span class='moz-txt-formfeed'></span>");
+ glyphTextLen = 1;
+ return true;
+ }
+ if (text0 == '+' || text1 == '+') {
+ if (ItMatchesDelimited(aInString, aInLength, u" +/-", 4, LT_IGNORE,
+ LT_IGNORE)) {
+ aOutputString.AppendLiteral(" &plusmn;");
+ glyphTextLen = 4;
+ return true;
+ }
+ if (col0 && ItMatchesDelimited(aInString, aInLength, u"+/-", 3, LT_IGNORE,
+ LT_IGNORE)) {
+ aOutputString.AppendLiteral("&plusmn;");
+ glyphTextLen = 3;
+ return true;
+ }
+ }
+
+ // x^2 => x<sup>2</sup>, also handle powers x^-2, x^0.5
+ // implement regular expression /[\dA-Za-z\)\]}]\^-?\d+(\.\d+)*[^\dA-Za-z]/
+ if (text1 == '^' &&
+ (IsAsciiDigit(text0) || IsAsciiAlpha(text0) || text0 == ')' ||
+ text0 == ']' || text0 == '}') &&
+ ((2 < aInLength && IsAsciiDigit(aInString[2])) ||
+ (3 < aInLength && aInString[2] == '-' && IsAsciiDigit(aInString[3])))) {
+ // Find first non-digit
+ int32_t delimPos = 3; // skip "^" and first digit (or '-')
+ for (; delimPos < aInLength &&
+ (IsAsciiDigit(aInString[delimPos]) ||
+ (aInString[delimPos] == '.' && delimPos + 1 < aInLength &&
+ IsAsciiDigit(aInString[delimPos + 1])));
+ delimPos++) {
+ ;
+ }
+
+ if (delimPos < aInLength && IsAsciiAlpha(aInString[delimPos])) {
+ return false;
+ }
+
+ outputHTML.Truncate();
+ outputHTML += text0;
+ outputHTML.AppendLiteral(
+ "<sup class=\"moz-txt-sup\">"
+ "<span style=\"display:inline-block;width:0;height:0;overflow:hidden\">"
+ "^</span>");
+
+ aOutputString.Append(outputHTML);
+ aOutputString.Append(&aInString[2], delimPos - 2);
+ aOutputString.AppendLiteral("</sup>");
+
+ glyphTextLen = delimPos /* - 1 + 1 */;
+ return true;
+ }
+ /*
+ The following strings are not substituted:
+ |TXT |HTML |Reason
+ +------+---------+----------
+ -> &larr; Bug #454
+ => &lArr; dito
+ <- &rarr; dito
+ <= &rArr; dito
+ (tm) &trade; dito
+ 1/4 &frac14; is triggered by 1/4 Part 1, 2/4 Part 2, ...
+ 3/4 &frac34; dito
+ 1/2 &frac12; similar
+ */
+ return false;
+}
+
+/***************************************************************************
+ Library-internal Interface
+****************************************************************************/
+
+NS_IMPL_ISUPPORTS(mozTXTToHTMLConv, mozITXTToHTMLConv, nsIStreamConverter,
+ nsIThreadRetargetableStreamListener, nsIStreamListener,
+ nsIRequestObserver)
+
+int32_t mozTXTToHTMLConv::CiteLevelTXT(const char16_t* line,
+ uint32_t& logLineStart) {
+ int32_t result = 0;
+ int32_t lineLength = NS_strlen(line);
+
+ bool moreCites = true;
+ while (moreCites) {
+ /* E.g. the following lines count as quote:
+
+ > text
+ //#ifdef QUOTE_RECOGNITION_AGGRESSIVE
+ >text
+ //#ifdef QUOTE_RECOGNITION_AGGRESSIVE
+ > text
+ ] text
+ USER> text
+ USER] text
+ //#endif
+
+ logLineStart is the position of "t" in this example
+ */
+ uint32_t i = logLineStart;
+
+#ifdef QUOTE_RECOGNITION_AGGRESSIVE
+ for (; int32_t(i) < lineLength && IsSpace(line[i]); i++)
+ ;
+ for (; int32_t(i) < lineLength && IsAsciiAlpha(line[i]) &&
+ nsCRT::IsUpper(line[i]);
+ i++)
+ ;
+ if (int32_t(i) < lineLength && (line[i] == '>' || line[i] == ']'))
+#else
+ if (int32_t(i) < lineLength && line[i] == '>')
+#endif
+ {
+ i++;
+ if (int32_t(i) < lineLength && line[i] == ' ') i++;
+ // sendmail/mbox
+ // Placed here for performance increase
+ const char16_t* indexString = &line[logLineStart];
+ // here, |logLineStart < lineLength| is always true
+ uint32_t minlength = std::min(uint32_t(6), NS_strlen(indexString));
+ if (Substring(indexString, indexString + minlength)
+ .Equals(Substring(u">From "_ns, 0, minlength),
+ nsCaseInsensitiveStringComparator)) {
+ // XXX RFC2646
+ moreCites = false;
+ } else {
+ result++;
+ logLineStart = i;
+ }
+ } else {
+ moreCites = false;
+ }
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::ScanTXT(const nsAString& aInString, uint32_t whattodo,
+ nsAString& aOutString) {
+ if (aInString.Length() == 0) {
+ aOutString.Truncate();
+ return NS_OK;
+ }
+
+ if (!aOutString.SetCapacity(uint32_t(aInString.Length() * growthRate),
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ bool doURLs = 0 != (whattodo & kURLs);
+ bool doGlyphSubstitution = 0 != (whattodo & kGlyphSubstitution);
+ bool doStructPhrase = 0 != (whattodo & kStructPhrase);
+
+ uint32_t structPhrase_strong = 0; // Number of currently open tags
+ uint32_t structPhrase_underline = 0;
+ uint32_t structPhrase_italic = 0;
+ uint32_t structPhrase_code = 0;
+
+ uint32_t endOfLastURLOutput = 0;
+
+ nsAutoString outputHTML; // moved here for performance increase
+
+ const char16_t* rawInputString = aInString.BeginReading();
+ uint32_t inLength = aInString.Length();
+
+ const Span<const char16_t> inString(aInString);
+ GraphemeClusterBreakIteratorUtf16 ci(inString);
+ uint32_t i = 0;
+ while (i < inLength) {
+ if (doGlyphSubstitution) {
+ int32_t glyphTextLen;
+ if (GlyphHit(&rawInputString[i], inLength - i, i == 0, aOutString,
+ glyphTextLen)) {
+ i = *ci.Seek(i + glyphTextLen - 1);
+ continue;
+ }
+ }
+
+ if (doStructPhrase) {
+ const char16_t* newOffset = rawInputString;
+ int32_t newLength = aInString.Length();
+ if (i > 0) // skip the first element?
+ {
+ GraphemeClusterBreakReverseIteratorUtf16 ri(
+ Span<const char16_t>(rawInputString, i));
+ Maybe<uint32_t> nextPos = ri.Next();
+ newOffset += *nextPos;
+ newLength -= *nextPos;
+ }
+
+ switch (aInString[i]) // Performance increase
+ {
+ case '*':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"*", 1, "b",
+ "class=\"moz-txt-star\"", aOutString,
+ structPhrase_strong)) {
+ i = *ci.Next();
+ continue;
+ }
+ break;
+ case '/':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"/", 1, "i",
+ "class=\"moz-txt-slash\"", aOutString,
+ structPhrase_italic)) {
+ i = *ci.Next();
+ continue;
+ }
+ break;
+ case '_':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"_", 1,
+ "span" /* <u> is deprecated */,
+ "class=\"moz-txt-underscore\"", aOutString,
+ structPhrase_underline)) {
+ i = *ci.Next();
+ continue;
+ }
+ break;
+ case '|':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"|", 1, "code",
+ "class=\"moz-txt-verticalline\"", aOutString,
+ structPhrase_code)) {
+ i = *ci.Next();
+ continue;
+ }
+ break;
+ }
+ }
+
+ if (doURLs) {
+ switch (aInString[i]) {
+ case ':':
+ case '@':
+ case '.':
+ if ((i == 0 || ((i > 0) && aInString[i - 1] != ' ')) &&
+ ((i == aInString.Length() - 1) ||
+ (aInString[i + 1] != ' '))) // Performance increase
+ {
+ int32_t replaceBefore;
+ int32_t replaceAfter;
+ if (FindURL(rawInputString, aInString.Length(), i, whattodo,
+ outputHTML, replaceBefore, replaceAfter) &&
+ structPhrase_strong + structPhrase_italic +
+ structPhrase_underline + structPhrase_code ==
+ 0
+ /* workaround for bug #19445 */) {
+ // Don't cut into previously inserted HTML (bug 1509493)
+ if (aOutString.Length() - replaceBefore < endOfLastURLOutput) {
+ break;
+ }
+ aOutString.Cut(aOutString.Length() - replaceBefore,
+ replaceBefore);
+ aOutString += outputHTML;
+ endOfLastURLOutput = aOutString.Length();
+ i = *ci.Seek(i + replaceAfter);
+ continue;
+ }
+ }
+ break;
+ } // switch
+ }
+
+ switch (aInString[i]) {
+ // Special symbols
+ case '<':
+ case '>':
+ case '&':
+ EscapeChar(aInString[i], aOutString, false);
+ i = *ci.Next();
+ break;
+ // Normal characters
+ default: {
+ const uint32_t oldIdx = i;
+ i = *ci.Next();
+ aOutString.Append(inString.FromTo(oldIdx, i));
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::ScanHTML(const nsAString& input, uint32_t whattodo,
+ nsAString& aOutString) {
+ const nsPromiseFlatString& aInString = PromiseFlatString(input);
+ if (!aOutString.SetCapacity(uint32_t(aInString.Length() * growthRate),
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // some common variables we were recalculating
+ // every time inside the for loop...
+ int32_t lengthOfInString = aInString.Length();
+ const char16_t* uniBuffer = aInString.get();
+
+#ifdef DEBUG_BenB_Perf
+ PRTime parsing_start = PR_IntervalNow();
+#endif
+
+ // Look for simple entities not included in a tags and scan them.
+ // Skip all tags ("<[...]>") and content in an a link tag ("<a [...]</a>"),
+ // comment tag ("<!--[...]-->"), style tag, script tag or head tag.
+ // Unescape the rest (text between tags) and pass it to ScanTXT.
+ nsAutoCString canFollow(" \f\n\r\t>");
+ for (int32_t i = 0; i < lengthOfInString;) {
+ if (aInString[i] == '<') // html tag
+ {
+ int32_t start = i;
+ if (i + 2 < lengthOfInString && nsCRT::ToLower(aInString[i + 1]) == 'a' &&
+ canFollow.FindChar(aInString[i + 2]) != kNotFound)
+ // if a tag, skip until </a>.
+ // Make sure there's a white-space character after, not to match "abbr".
+ {
+ i = aInString.LowerCaseFindASCII("</a>", i);
+ if (i == kNotFound) {
+ i = lengthOfInString;
+ } else {
+ i += 4;
+ }
+ } else if (Substring(aInString, i + 1, 3).LowerCaseEqualsASCII("!--"))
+ // if out-commended code, skip until -->
+ {
+ i = aInString.Find(u"-->", i);
+ if (i == kNotFound) {
+ i = lengthOfInString;
+ } else {
+ i += 3;
+ }
+ } else if (i + 6 < lengthOfInString &&
+ Substring(aInString, i + 1, 5).LowerCaseEqualsASCII("style") &&
+ canFollow.FindChar(aInString[i + 6]) != kNotFound)
+ // if style tag, skip until </style>
+ {
+ i = aInString.LowerCaseFindASCII("</style>", i);
+ if (i == kNotFound) {
+ i = lengthOfInString;
+ } else {
+ i += 8;
+ }
+ } else if (i + 7 < lengthOfInString &&
+ Substring(aInString, i + 1, 6)
+ .LowerCaseEqualsASCII("script") &&
+ canFollow.FindChar(aInString[i + 7]) != kNotFound)
+ // if script tag, skip until </script>
+ {
+ i = aInString.LowerCaseFindASCII("</script>", i);
+ if (i == kNotFound) {
+ i = lengthOfInString;
+ } else {
+ i += 9;
+ }
+ } else if (i + 5 < lengthOfInString &&
+ Substring(aInString, i + 1, 4).LowerCaseEqualsASCII("head") &&
+ canFollow.FindChar(aInString[i + 5]) != kNotFound)
+ // if head tag, skip until </head>
+ // Make sure not to match <header>.
+ {
+ i = aInString.LowerCaseFindASCII("</head>", i);
+ if (i == kNotFound) {
+ i = lengthOfInString;
+ } else {
+ i += 7;
+ }
+ } else // just skip tag (attributes etc.)
+ {
+ i = aInString.FindChar('>', i);
+ if (i == kNotFound) {
+ i = lengthOfInString;
+ } else {
+ i++;
+ }
+ }
+ aOutString.Append(&uniBuffer[start], i - start);
+ } else {
+ uint32_t start = uint32_t(i);
+ i = aInString.FindChar('<', i);
+ if (i == kNotFound) i = lengthOfInString;
+
+ nsAutoStringN<256> tempString;
+ tempString.SetCapacity(uint32_t((uint32_t(i) - start) * growthRate));
+ UnescapeStr(uniBuffer, start, uint32_t(i) - start, tempString);
+ ScanTXT(tempString, whattodo, aOutString);
+ }
+ }
+
+#ifdef DEBUG_BenB_Perf
+ printf("ScanHTML time: %d ms\n",
+ PR_IntervalToMilliseconds(PR_IntervalNow() - parsing_start));
+#endif
+ return NS_OK;
+}
+
+/****************************************************************************
+ XPCOM Interface
+*****************************************************************************/
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::Convert(nsIInputStream* aFromStream, const char* aFromType,
+ const char* aToType, nsISupports* aCtxt,
+ nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::AsyncConvertData(const char* aFromType, const char* aToType,
+ nsIStreamListener* aListener,
+ nsISupports* aCtxt) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::GetConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel, nsACString& aToType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::OnDataFinished(nsresult aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::CheckListenerChain() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::OnStartRequest(nsIRequest* request) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::CiteLevelTXT(const char16_t* line, uint32_t* logLineStart,
+ uint32_t* _retval) {
+ if (!logLineStart || !_retval || !line) return NS_ERROR_NULL_POINTER;
+ *_retval = CiteLevelTXT(line, *logLineStart);
+ return NS_OK;
+}
+
+nsresult MOZ_NewTXTToHTMLConv(mozTXTToHTMLConv** aConv) {
+ MOZ_ASSERT(aConv != nullptr, "null ptr");
+ if (!aConv) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<mozTXTToHTMLConv> conv = new mozTXTToHTMLConv();
+ conv.forget(aConv);
+ // return (*aConv)->Init();
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/mozTXTToHTMLConv.h b/netwerk/streamconv/converters/mozTXTToHTMLConv.h
new file mode 100644
index 0000000000..dbae8edb0f
--- /dev/null
+++ b/netwerk/streamconv/converters/mozTXTToHTMLConv.h
@@ -0,0 +1,286 @@
+/* -*- 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/. */
+
+/**
+ Description: Currently only functions to enhance plain text with HTML tags.
+ See mozITXTToHTMLConv. Stream conversion is defunct.
+*/
+
+#ifndef _mozTXTToHTMLConv_h__
+#define _mozTXTToHTMLConv_h__
+
+#include "mozITXTToHTMLConv.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsIIOService;
+
+class mozTXTToHTMLConv : public mozITXTToHTMLConv {
+ virtual ~mozTXTToHTMLConv() = default;
+
+ //////////////////////////////////////////////////////////
+ public:
+ //////////////////////////////////////////////////////////
+
+ mozTXTToHTMLConv() = default;
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_MOZITXTTOHTMLCONV
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSISTREAMCONVERTER
+
+ /**
+ see mozITXTToHTMLConv::CiteLevelTXT
+ */
+ int32_t CiteLevelTXT(const char16_t* line, uint32_t& logLineStart);
+
+ //////////////////////////////////////////////////////////
+ protected:
+ //////////////////////////////////////////////////////////
+ nsCOMPtr<nsIIOService>
+ mIOService; // for performance reasons, cache the netwerk service...
+ /**
+ Completes<ul>
+ <li>Case 1: mailto: "mozilla@bucksch.org" -> "mailto:mozilla@bucksch.org"
+ <li>Case 2: http: "www.mozilla.org" -> "http://www.mozilla.org"
+ <li>Case 3: ftp: "ftp.mozilla.org" -> "ftp://www.mozilla.org"
+ </ul>
+ It does no check, if the resulting URL is valid.
+ @param text (in): abbreviated URL
+ @param pos (in): position of "@" (case 1) or first "." (case 2 and 3)
+ @return Completed URL at success and empty string at failure
+ */
+ void CompleteAbbreviatedURL(const char16_t* aInString, int32_t aInLength,
+ const uint32_t pos, nsString& aOutString);
+
+ //////////////////////////////////////////////////////////
+ private:
+ //////////////////////////////////////////////////////////
+
+ enum LIMTYPE {
+ LT_IGNORE, // limitation not checked
+ LT_DELIMITER, // not alphanumeric and not rep[0]. End of text is also ok.
+ LT_ALPHA, // alpha char
+ LT_DIGIT
+ };
+
+ /**
+ @param text (in): the string to search through.<p>
+ If before = IGNORE,<br>
+ rep is compared starting at 1. char of text (text[0]),<br>
+ else starting at 2. char of text (text[1]).
+ Chars after "after"-delimiter are ignored.
+ @param rep (in): the string to look for
+ @param aRepLen (in): the number of bytes in the string to look for
+ @param before (in): limitation before rep
+ @param after (in): limitation after rep
+ @return true, if rep is found and limitation spec is met or rep is empty
+ */
+ bool ItMatchesDelimited(const char16_t* aInString, int32_t aInLength,
+ const char16_t* rep, int32_t aRepLen, LIMTYPE before,
+ LIMTYPE after);
+
+ /**
+ @param see ItMatchesDelimited
+ @return Number of ItMatchesDelimited in text
+ */
+ uint32_t NumberOfMatches(const char16_t* aInString, int32_t aInStringLength,
+ const char16_t* rep, int32_t aRepLen, LIMTYPE before,
+ LIMTYPE after);
+
+ /**
+ Currently only changes "<", ">" and "&". All others stay as they are.<p>
+ "Char" in function name to avoid side effects with nsString(ch)
+ constructors.
+ @param ch (in)
+ @param aStringToAppendto (out) - the string to append the escaped
+ string to.
+ @param inAttribute (in) - will escape quotes, too (which is
+ only needed for attribute values)
+ */
+ void EscapeChar(const char16_t ch, nsAString& aStringToAppendto,
+ bool inAttribute);
+
+ /**
+ See EscapeChar. Escapes the string in place.
+ */
+ void EscapeStr(nsString& aInString, bool inAttribute);
+
+ /**
+ Currently only reverts "<", ">" and "&". All others stay as they are.<p>
+ @param aInString (in) HTML string
+ @param aStartPos (in) start index into the buffer
+ @param aLength (in) length of the buffer
+ @param aOutString (out) unescaped buffer
+ */
+ void UnescapeStr(const char16_t* aInString, int32_t aStartPos,
+ int32_t aLength, nsString& aOutString);
+
+ /**
+ <em>Note</em>: I use different strategies to pass context between the
+ functions (full text and pos vs. cutted text and col0, glphyTextLen vs.
+ replaceBefore/-After). It makes some sense, but is hard to understand
+ (maintain) :-(.
+ */
+
+ /**
+ <p><em>Note:</em> replaceBefore + replaceAfter + 1 (for char at pos) chars
+ in text should be replaced by outputHTML.</p>
+ <p><em>Note:</em> This function should be able to process a URL on multiple
+ lines, but currently, ScanForURLs is called for every line, so it can't.</p>
+ @param text (in): includes possibly a URL
+ @param pos (in): position in text, where either ":", "." or "@" are found
+ @param whathasbeendone (in): What the calling ScanTXT did/has to do with the
+ (not-linkified) text, i.e. usually the "whattodo" parameter.
+ (Needed to calculate replaceBefore.) NOT what will be done with
+ the content of the link.
+ @param outputHTML (out): URL with HTML-a tag
+ @param replaceBefore (out): Number of chars of URL before pos
+ @param replaceAfter (out): Number of chars of URL after pos
+ @return URL found
+ */
+ bool FindURL(const char16_t* aInString, int32_t aInLength, const uint32_t pos,
+ const uint32_t whathasbeendone, nsString& outputHTML,
+ int32_t& replaceBefore, int32_t& replaceAfter);
+
+ enum modetype {
+ unknown,
+ RFC1738, /* Check, if RFC1738, APPENDIX compliant,
+ like "<URL:http://www.mozilla.org>". */
+ RFC2396E, /* RFC2396, APPENDIX E allows anglebrackets (like
+ "<http://www.mozilla.org>") (without "URL:") or
+ quotation marks(like ""http://www.mozilla.org"").
+ Also allow email addresses without scheme,
+ e.g. "<mozilla@bucksch.org>" */
+ freetext, /* assume heading scheme
+ with "[a-zA-Z][a-zA-Z0-9+\-\.]*:" like "news:"
+ (see RFC2396, Section 3.1).
+ Certain characters (see code) or any whitespace
+ (including linebreaks) end the URL.
+ Other certain (punctation) characters (see code)
+ at the end are stripped off. */
+ abbreviated /* Similar to freetext, but without scheme, e.g.
+ "www.mozilla.org", "ftp.mozilla.org" and
+ "mozilla@bucksch.org". */
+ /* RFC1738 and RFC2396E type URLs may use multiple lines,
+ whitespace is stripped. Special characters like ")" stay intact.*/
+ };
+
+ /**
+ * @param text (in), pos (in): see FindURL
+ * @param check (in): Start must be conform with this mode
+ * @param start (out): Position in text, where URL (including brackets or
+ * similar) starts
+ * @return |check|-conform start has been found
+ */
+ bool FindURLStart(const char16_t* aInString, int32_t aInLength,
+ const uint32_t pos, const modetype check, uint32_t& start);
+
+ /**
+ * @param text (in), pos (in): see FindURL
+ * @param check (in): End must be conform with this mode
+ * @param start (in): see FindURLStart
+ * @param end (out): Similar to |start| param of FindURLStart
+ * @return |check|-conform end has been found
+ */
+ bool FindURLEnd(const char16_t* aInString, int32_t aInStringLength,
+ const uint32_t pos, const modetype check,
+ const uint32_t start, uint32_t& end);
+
+ /**
+ * @param text (in), pos (in), whathasbeendone (in): see FindURL
+ * @param check (in): Current mode
+ * @param start (in), end (in): see FindURLEnd
+ * @param txtURL (out): Guessed (raw) URL.
+ * Without whitespace, but not completed.
+ * @param desc (out): Link as shown to the user, but already escaped.
+ * Should be placed between the <a> and </a> tags.
+ * @param replaceBefore(out), replaceAfter (out): see FindURL
+ */
+ void CalculateURLBoundaries(const char16_t* aInString,
+ int32_t aInStringLength, const uint32_t pos,
+ const uint32_t whathasbeendone,
+ const modetype check, const uint32_t start,
+ const uint32_t end, nsString& txtURL,
+ nsString& desc, int32_t& replaceBefore,
+ int32_t& replaceAfter);
+
+ /**
+ * @param txtURL (in), desc (in): see CalculateURLBoundaries
+ * @param outputHTML (out): see FindURL
+ * @return A valid URL could be found (and creation of HTML successful)
+ */
+ bool CheckURLAndCreateHTML(const nsString& txtURL, const nsString& desc,
+ const modetype mode, nsString& outputHTML);
+
+ /**
+ @param text (in): line of text possibly with tagTXT.<p>
+ if col0 is true,
+ starting with tagTXT<br>
+ else
+ starting one char before tagTXT
+ @param col0 (in): tagTXT is on the beginning of the line (or paragraph).
+ open must be 0 then.
+ @param tagTXT (in): Tag in plaintext to search for, e.g. "*"
+ @param aTagTxtLen (in): length of tagTXT.
+ @param tagHTML (in): HTML-Tag to replace tagTXT with,
+ without "<" and ">", e.g. "strong"
+ @param attributeHTML (in): HTML-attribute to add to opening tagHTML,
+ e.g. "class=txt_star"
+ @param aOutString: string to APPEND the converted html into
+ @param open (in/out): Number of currently open tags of type tagHTML
+ @return Conversion succeeded
+ */
+ bool StructPhraseHit(const char16_t* aInString, int32_t aInStringLength,
+ bool col0, const char16_t* tagTXT, int32_t aTagTxtLen,
+ const char* tagHTML, const char* attributeHTML,
+ nsAString& aOutString, uint32_t& openTags);
+
+ /**
+ @param text (in), col0 (in): see GlyphHit
+ @param tagTXT (in): Smily, see also StructPhraseHit
+ @param imageName (in): the basename of the file that contains the image for
+ this smilie
+ @param outputHTML (out): new string containing the html for the smily
+ @param glyphTextLen (out): see GlyphHit
+ */
+ bool SmilyHit(const char16_t* aInString, int32_t aLength, bool col0,
+ const char* tagTXT, const nsString& imageName,
+ nsString& outputHTML, int32_t& glyphTextLen);
+
+ /**
+ Checks, if we can replace some chars at the start of line with prettier HTML
+ code.<p>
+ If success is reported, replace the first glyphTextLen chars with outputHTML
+
+ @param text (in): line of text possibly with Glyph.<p>
+ If col0 is true,
+ starting with Glyph <br><!-- (br not part of text) -->
+ else
+ starting one char before Glyph
+ @param col0 (in): text starts at the beginning of the line (or paragraph)
+ @param aOutString (out): APPENDS html for the glyph to this string
+ @param glyphTextLen (out): Length of original text to replace
+ @return see StructPhraseHit
+ */
+ bool GlyphHit(const char16_t* aInString, int32_t aInLength, bool col0,
+ nsAString& aOutputString, int32_t& glyphTextLen);
+
+ /**
+ Check if a given url should be linkified.
+ @param aURL (in): url to be checked on.
+ */
+ bool ShouldLinkify(const nsCString& aURL);
+};
+
+// It's said, that Win32 and Mac don't like static const members
+const int32_t mozTXTToHTMLConv_lastMode = 4;
+// Needed (only) by mozTXTToHTMLConv::FindURL
+const int32_t mozTXTToHTMLConv_numberOfModes = 4; // dito; unknown not counted
+
+#endif
diff --git a/netwerk/streamconv/converters/nsDirIndex.cpp b/netwerk/streamconv/converters/nsDirIndex.cpp
new file mode 100644
index 0000000000..fefe5e1863
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndex.cpp
@@ -0,0 +1,62 @@
+/* -*- 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 "nsDirIndex.h"
+
+NS_IMPL_ISUPPORTS(nsDirIndex, nsIDirIndex)
+
+NS_IMETHODIMP
+nsDirIndex::GetType(uint32_t* aType) {
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetType(uint32_t aType) {
+ mType = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::GetLocation(nsACString& aLocation) {
+ aLocation = mLocation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetLocation(const nsACString& aLocation) {
+ mLocation = aLocation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::GetSize(int64_t* aSize) {
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ *aSize = mSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetSize(int64_t aSize) {
+ mSize = aSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::GetLastModified(PRTime* aLastModified) {
+ NS_ENSURE_ARG_POINTER(aLastModified);
+
+ *aLastModified = mLastModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetLastModified(PRTime aLastModified) {
+ mLastModified = aLastModified;
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsDirIndex.h b/netwerk/streamconv/converters/nsDirIndex.h
new file mode 100644
index 0000000000..eb6009295c
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndex.h
@@ -0,0 +1,32 @@
+/* -*- 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 nsDirIndex_h__
+#define nsDirIndex_h__
+
+#include "nsIDirIndex.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+class nsDirIndex final : public nsIDirIndex {
+ private:
+ ~nsDirIndex() = default;
+
+ public:
+ nsDirIndex() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDIRINDEX
+
+ protected:
+ uint32_t mType{TYPE_UNKNOWN};
+ nsCString mContentType;
+ nsCString mLocation;
+ nsString mDescription;
+ int64_t mSize{INT64_MAX};
+ PRTime mLastModified{-1LL};
+};
+
+#endif
diff --git a/netwerk/streamconv/converters/nsDirIndexParser.cpp b/netwerk/streamconv/converters/nsDirIndexParser.cpp
new file mode 100644
index 0000000000..600f43a1aa
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndexParser.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This parsing code originally lived in xpfe/components/directory/ - bbaetz */
+
+#include "nsDirIndexParser.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/StaticPtr.h"
+#include "prprf.h"
+#include "nsCRT.h"
+#include "nsDirIndex.h"
+#include "nsEscape.h"
+#include "nsIDirIndex.h"
+#include "nsIInputStream.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsDirIndexParser, nsIRequestObserver, nsIStreamListener,
+ nsIDirIndexParser)
+
+void nsDirIndexParser::Init() {
+ mLineStart = 0;
+ mFormat[0] = -1;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::SetListener(nsIDirIndexListener* aListener) {
+ mListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::GetListener(nsIDirIndexListener** aListener) {
+ *aListener = do_AddRef(mListener).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
+
+NS_IMETHODIMP
+nsDirIndexParser::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // Finish up
+ if (mBuf.Length() > (uint32_t)mLineStart) {
+ ProcessData(aRequest);
+ }
+
+ return NS_OK;
+}
+
+nsDirIndexParser::Field nsDirIndexParser::gFieldTable[] = {
+ {"Filename", FIELD_FILENAME},
+ {"Content-Length", FIELD_CONTENTLENGTH},
+ {"Last-Modified", FIELD_LASTMODIFIED},
+ {"File-Type", FIELD_FILETYPE},
+ {nullptr, FIELD_UNKNOWN}};
+
+void nsDirIndexParser::ParseFormat(const char* aFormatStr) {
+ // Parse a "200" format line, and remember the fields and their
+ // ordering in mFormat. Multiple 200 lines stomp on each other.
+ unsigned int formatNum = 0;
+ mFormat[0] = -1;
+
+ do {
+ while (*aFormatStr && nsCRT::IsAsciiSpace(char16_t(*aFormatStr))) {
+ ++aFormatStr;
+ }
+
+ if (!*aFormatStr) break;
+
+ nsAutoCString name;
+ int32_t len = 0;
+ while (aFormatStr[len] && !nsCRT::IsAsciiSpace(char16_t(aFormatStr[len]))) {
+ ++len;
+ }
+ name.Append(aFormatStr, len);
+ aFormatStr += len;
+
+ // Okay, we're gonna monkey with the nsStr. Bold!
+ name.SetLength(nsUnescapeCount(name.BeginWriting()));
+
+ for (Field* i = gFieldTable; i->mName; ++i) {
+ if (name.EqualsIgnoreCase(i->mName)) {
+ mFormat[formatNum] = i->mType;
+ mFormat[++formatNum] = -1;
+ break;
+ }
+ }
+
+ } while (*aFormatStr && (formatNum < (ArrayLength(mFormat) - 1)));
+}
+
+void nsDirIndexParser::ParseData(nsIDirIndex* aIdx, char* aDataStr,
+ int32_t aLineLen) {
+ // Parse a "201" data line, using the field ordering specified in
+ // mFormat.
+
+ if (mFormat[0] == -1) {
+ // Ignore if we haven't seen a format yet.
+ return;
+ }
+
+ nsAutoCString filename;
+ int32_t lineLen = aLineLen;
+
+ for (int32_t i = 0; mFormat[i] != -1; ++i) {
+ // If we've exhausted the data before we run out of fields, just bail.
+ if (!*aDataStr || (lineLen < 1)) {
+ return;
+ }
+
+ while ((lineLen > 0) && nsCRT::IsAsciiSpace(*aDataStr)) {
+ ++aDataStr;
+ --lineLen;
+ }
+
+ if (lineLen < 1) {
+ // invalid format, bail
+ return;
+ }
+
+ char* value = aDataStr;
+ if (*aDataStr == '"' || *aDataStr == '\'') {
+ // it's a quoted string. snarf everything up to the next quote character
+ const char quotechar = *(aDataStr++);
+ lineLen--;
+ ++value;
+ while ((lineLen > 0) && *aDataStr != quotechar) {
+ ++aDataStr;
+ --lineLen;
+ }
+ if (lineLen > 0) {
+ *aDataStr++ = '\0';
+ --lineLen;
+ }
+
+ if (!lineLen) {
+ // invalid format, bail
+ return;
+ }
+ } else {
+ // it's unquoted. snarf until we see whitespace.
+ value = aDataStr;
+ while ((lineLen > 0) && (!nsCRT::IsAsciiSpace(*aDataStr))) {
+ ++aDataStr;
+ --lineLen;
+ }
+ if (lineLen > 0) {
+ *aDataStr++ = '\0';
+ --lineLen;
+ }
+ // even if we ran out of line length here, there's still a trailing zero
+ // byte afterwards
+ }
+
+ fieldType t = fieldType(mFormat[i]);
+ switch (t) {
+ case FIELD_FILENAME: {
+ // don't unescape at this point, so that UnEscapeAndConvert() can
+ filename = value;
+ aIdx->SetLocation(filename);
+ } break;
+ case FIELD_CONTENTLENGTH: {
+ int64_t len;
+ int32_t status = PR_sscanf(value, "%lld", &len);
+ if (status == 1) {
+ aIdx->SetSize(len);
+ } else {
+ aIdx->SetSize(UINT64_MAX); // UINT64_MAX means unknown
+ }
+ } break;
+ case FIELD_LASTMODIFIED: {
+ PRTime tm;
+ nsUnescape(value);
+ if (PR_ParseTimeString(value, false, &tm) == PR_SUCCESS) {
+ aIdx->SetLastModified(tm);
+ }
+ } break;
+ case FIELD_FILETYPE:
+ // unescape in-place
+ nsUnescape(value);
+ if (!nsCRT::strcasecmp(value, "directory")) {
+ aIdx->SetType(nsIDirIndex::TYPE_DIRECTORY);
+ } else if (!nsCRT::strcasecmp(value, "file")) {
+ aIdx->SetType(nsIDirIndex::TYPE_FILE);
+ } else if (!nsCRT::strcasecmp(value, "symbolic-link")) {
+ aIdx->SetType(nsIDirIndex::TYPE_SYMLINK);
+ } else {
+ aIdx->SetType(nsIDirIndex::TYPE_UNKNOWN);
+ }
+ break;
+ case FIELD_UNKNOWN:
+ // ignore
+ break;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t aSourceOffset, uint32_t aCount) {
+ if (aCount < 1) return NS_OK;
+
+ uint32_t len = mBuf.Length();
+
+ // Ensure that our mBuf has capacity to hold the data we're about to
+ // read.
+ // Before adjusting the capacity, guard against any potential overflow
+ // resulting from the addition of aCount with len. See Bug 1823551.
+ NS_ENSURE_TRUE((UINT32_MAX - aCount) >= len, NS_ERROR_FAILURE);
+ if (!mBuf.SetLength(len + aCount, fallible)) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Now read the data into our buffer.
+ nsresult rv;
+ uint32_t count;
+ rv = aStream->Read(mBuf.BeginWriting() + len, aCount, &count);
+ if (NS_FAILED(rv)) return rv;
+
+ // Set the string's length according to the amount of data we've read.
+ // Note: we know this to work on nsCString. This isn't guaranteed to
+ // work on other strings.
+ mBuf.SetLength(len + count);
+
+ return ProcessData(aRequest);
+}
+
+nsresult nsDirIndexParser::ProcessData(nsIRequest* aRequest) {
+ if (!mListener) return NS_ERROR_FAILURE;
+
+ while (true) {
+ int32_t eol = mBuf.FindCharInSet("\n\r", mLineStart);
+ if (eol < 0) break;
+ mBuf.SetCharAt(char16_t('\0'), eol);
+
+ const char* line = mBuf.get() + mLineStart;
+
+ int32_t lineLen = eol - mLineStart;
+ mLineStart = eol + 1;
+
+ if (lineLen >= 4) {
+ const char* buf = line;
+
+ if (buf[0] == '2') {
+ if (buf[1] == '0') {
+ if (buf[2] == '0' && buf[3] == ':') {
+ // 200. Define field names
+ ParseFormat(buf + 4);
+ } else if (buf[2] == '1' && buf[3] == ':') {
+ // 201. Field data
+ nsCOMPtr<nsIDirIndex> idx = new nsDirIndex();
+
+ ParseData(idx, ((char*)buf) + 4, lineLen - 4);
+ mListener->OnIndexAvailable(aRequest, idx);
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsDirIndexParser.h b/netwerk/streamconv/converters/nsDirIndexParser.h
new file mode 100644
index 0000000000..291a86337e
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndexParser.h
@@ -0,0 +1,65 @@
+/* -*- 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 __NSDIRINDEX_H_
+#define __NSDIRINDEX_H_
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIDirIndexListener.h"
+#include "mozilla/RefPtr.h"
+
+class nsIDirIndex;
+class nsITextToSubURI;
+
+/* CID: {a0d6ad32-1dd1-11b2-aa55-a40187b54036} */
+
+class nsDirIndexParser : public nsIDirIndexParser {
+ private:
+ virtual ~nsDirIndexParser() = default;
+
+ nsDirIndexParser() = default;
+ void Init();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIDIRINDEXPARSER
+
+ static already_AddRefed<nsIDirIndexParser> CreateInstance() {
+ RefPtr<nsDirIndexParser> parser = new nsDirIndexParser();
+ parser->Init();
+ return parser.forget();
+ }
+
+ enum fieldType {
+ FIELD_UNKNOWN = 0, // MUST be 0
+ FIELD_FILENAME,
+ FIELD_CONTENTLENGTH,
+ FIELD_LASTMODIFIED,
+ FIELD_FILETYPE
+ };
+
+ protected:
+ nsCOMPtr<nsIDirIndexListener> mListener;
+
+ nsCString mBuf;
+ int32_t mLineStart{0};
+ int mFormat[8]{-1};
+
+ nsresult ProcessData(nsIRequest* aRequest);
+ void ParseFormat(const char* aFormatStr);
+ void ParseData(nsIDirIndex* aIdx, char* aDataStr, int32_t lineLen);
+
+ struct Field {
+ const char* mName;
+ fieldType mType;
+ };
+
+ static Field gFieldTable[];
+};
+
+#endif
diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
new file mode 100644
index 0000000000..da430a0611
--- /dev/null
+++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
@@ -0,0 +1,767 @@
+/* -*- 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 "nsHTTPCompressConv.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsError.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Logging.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIRequest.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+// brotli headers
+#undef assert
+#include "assert.h"
+#include "state.h"
+#include "brotli/decode.h"
+
+namespace mozilla {
+namespace net {
+
+extern LazyLogModule gHttpLog;
+#define LOG(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args)
+
+class BrotliWrapper {
+ public:
+ BrotliWrapper() {
+ BrotliDecoderStateInit(&mState, nullptr, nullptr, nullptr);
+ }
+ ~BrotliWrapper() { BrotliDecoderStateCleanup(&mState); }
+
+ BrotliDecoderState mState{};
+ Atomic<size_t, Relaxed> mTotalOut{0};
+ nsresult mStatus = NS_OK;
+ Atomic<bool, Relaxed> mBrotliStateIsStreamEnd{false};
+
+ nsIRequest* mRequest{nullptr};
+ nsISupports* mContext{nullptr};
+ uint64_t mSourceOffset{0};
+};
+
+// nsISupports implementation
+NS_IMPL_ISUPPORTS(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener,
+ nsIRequestObserver, nsICompressConvStats,
+ nsIThreadRetargetableStreamListener)
+
+// nsFTPDirListingConv methods
+nsHTTPCompressConv::nsHTTPCompressConv() {
+ LOG(("nsHttpCompresssConv %p ctor\n", this));
+ if (NS_IsMainThread()) {
+ mFailUncleanStops =
+ Preferences::GetBool("network.http.enforce-framing.http", false);
+ } else {
+ mFailUncleanStops = false;
+ }
+}
+
+nsHTTPCompressConv::~nsHTTPCompressConv() {
+ LOG(("nsHttpCompresssConv %p dtor\n", this));
+ if (mInpBuffer) {
+ free(mInpBuffer);
+ }
+
+ if (mOutBuffer) {
+ free(mOutBuffer);
+ }
+
+ // For some reason we are not getting Z_STREAM_END. But this was also seen
+ // for mozilla bug 198133. Need to handle this case.
+ if (mStreamInitialized && !mStreamEnded) {
+ inflateEnd(&d_stream);
+ }
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::GetDecodedDataLength(uint64_t* aDecodedDataLength) {
+ *aDecodedDataLength = mDecodedDataLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::AsyncConvertData(const char* aFromType, const char* aToType,
+ nsIStreamListener* aListener,
+ nsISupports* aCtxt) {
+ if (!nsCRT::strncasecmp(aFromType, HTTP_COMPRESS_TYPE,
+ sizeof(HTTP_COMPRESS_TYPE) - 1) ||
+ !nsCRT::strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE,
+ sizeof(HTTP_X_COMPRESS_TYPE) - 1)) {
+ mMode = HTTP_COMPRESS_COMPRESS;
+ } else if (!nsCRT::strncasecmp(aFromType, HTTP_GZIP_TYPE,
+ sizeof(HTTP_GZIP_TYPE) - 1) ||
+ !nsCRT::strncasecmp(aFromType, HTTP_X_GZIP_TYPE,
+ sizeof(HTTP_X_GZIP_TYPE) - 1)) {
+ mMode = HTTP_COMPRESS_GZIP;
+ } else if (!nsCRT::strncasecmp(aFromType, HTTP_DEFLATE_TYPE,
+ sizeof(HTTP_DEFLATE_TYPE) - 1)) {
+ mMode = HTTP_COMPRESS_DEFLATE;
+ } else if (!nsCRT::strncasecmp(aFromType, HTTP_BROTLI_TYPE,
+ sizeof(HTTP_BROTLI_TYPE) - 1)) {
+ mMode = HTTP_COMPRESS_BROTLI;
+ }
+ LOG(("nsHttpCompresssConv %p AsyncConvertData %s %s mode %d\n", this,
+ aFromType, aToType, (CompressMode)mMode));
+
+ MutexAutoLock lock(mMutex);
+ // hook ourself up with the receiving listener.
+ mListener = aListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::GetConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel,
+ nsACString& aToType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::OnStartRequest(nsIRequest* request) {
+ LOG(("nsHttpCompresssConv %p onstart\n", this));
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+ return listener->OnStartRequest(request);
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ nsresult status = aStatus;
+ LOG(("nsHttpCompresssConv %p onstop %" PRIx32 "\n", this,
+ static_cast<uint32_t>(aStatus)));
+
+ // Framing integrity is enforced for content-encoding: gzip, but not for
+ // content-encoding: deflate. Note that gzip vs deflate is NOT determined
+ // by content sniffing but only via header.
+ if (!mStreamEnded && NS_SUCCEEDED(status) &&
+ (mFailUncleanStops && (mMode == HTTP_COMPRESS_GZIP))) {
+ // This is not a clean end of gzip stream: the transfer is incomplete.
+ status = NS_ERROR_NET_PARTIAL_TRANSFER;
+ LOG(("nsHttpCompresssConv %p onstop partial gzip\n", this));
+ }
+ if (NS_SUCCEEDED(status) && mMode == HTTP_COMPRESS_BROTLI) {
+ nsCOMPtr<nsIForcePendingChannel> fpChannel = do_QueryInterface(request);
+ bool isPending = false;
+ if (request) {
+ request->IsPending(&isPending);
+ }
+ if (fpChannel && !isPending) {
+ fpChannel->ForcePending(true);
+ }
+ bool allowTruncatedEmpty =
+ StaticPrefs::network_compress_allow_truncated_empty_brotli();
+ if (mBrotli && ((allowTruncatedEmpty && NS_FAILED(mBrotli->mStatus)) ||
+ (!allowTruncatedEmpty && mBrotli->mTotalOut == 0 &&
+ !mBrotli->mBrotliStateIsStreamEnd))) {
+ status = NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ LOG(("nsHttpCompresssConv %p onstop brotlihandler rv %" PRIx32 "\n", this,
+ static_cast<uint32_t>(status)));
+ if (fpChannel && !isPending) {
+ fpChannel->ForcePending(false);
+ }
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+ return listener->OnStopRequest(request, status);
+}
+
+/* static */
+nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream,
+ void* closure, const char* dataIn,
+ uint32_t, uint32_t aAvail,
+ uint32_t* countRead) {
+ MOZ_ASSERT(stream);
+ nsHTTPCompressConv* self = static_cast<nsHTTPCompressConv*>(closure);
+ *countRead = 0;
+
+ const size_t kOutSize = 128 * 1024; // just a chunk size, we call in a loop
+ uint8_t* outPtr;
+ size_t outSize;
+ size_t avail = aAvail;
+ BrotliDecoderResult res;
+
+ if (!self->mBrotli) {
+ *countRead = aAvail;
+ return NS_OK;
+ }
+
+ auto outBuffer = MakeUniqueFallible<uint8_t[]>(kOutSize);
+ if (outBuffer == nullptr) {
+ self->mBrotli->mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return self->mBrotli->mStatus;
+ }
+ do {
+ outSize = kOutSize;
+ outPtr = outBuffer.get();
+
+ // brotli api is documented in brotli/dec/decode.h and brotli/dec/decode.c
+ LOG(("nsHttpCompresssConv %p brotlihandler decompress %zu\n", self, avail));
+ size_t totalOut = self->mBrotli->mTotalOut;
+ res = ::BrotliDecoderDecompressStream(
+ &self->mBrotli->mState, &avail,
+ reinterpret_cast<const unsigned char**>(&dataIn), &outSize, &outPtr,
+ &totalOut);
+ outSize = kOutSize - outSize;
+ self->mBrotli->mTotalOut = totalOut;
+ self->mBrotli->mBrotliStateIsStreamEnd =
+ BrotliDecoderIsFinished(&self->mBrotli->mState);
+ LOG(("nsHttpCompresssConv %p brotlihandler decompress rv=%" PRIx32
+ " out=%zu\n",
+ self, static_cast<uint32_t>(res), outSize));
+
+ if (res == BROTLI_DECODER_RESULT_ERROR) {
+ LOG(("nsHttpCompressConv %p marking invalid encoding", self));
+ self->mBrotli->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING;
+ return self->mBrotli->mStatus;
+ }
+
+ // in 'the current implementation' brotli must consume everything before
+ // asking for more input
+ if (res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
+ MOZ_ASSERT(!avail);
+ if (avail) {
+ LOG(("nsHttpCompressConv %p did not consume all input", self));
+ self->mBrotli->mStatus = NS_ERROR_UNEXPECTED;
+ return self->mBrotli->mStatus;
+ }
+ }
+
+ auto callOnDataAvailable = [&](uint64_t aSourceOffset, const char* aBuffer,
+ uint32_t aCount) {
+ nsresult rv = self->do_OnDataAvailable(self->mBrotli->mRequest,
+ aSourceOffset, aBuffer, aCount);
+ LOG(("nsHttpCompressConv %p BrotliHandler ODA rv=%" PRIx32, self,
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ self->mBrotli->mStatus = rv;
+ }
+
+ return rv;
+ };
+
+ if (outSize > 0) {
+ if (NS_FAILED(callOnDataAvailable(
+ self->mBrotli->mSourceOffset,
+ reinterpret_cast<const char*>(outBuffer.get()), outSize))) {
+ return self->mBrotli->mStatus;
+ }
+ }
+
+ // See bug 1759745. If the decoder has more output data, take it.
+ while (::BrotliDecoderHasMoreOutput(&self->mBrotli->mState)) {
+ outSize = kOutSize;
+ const uint8_t* buffer =
+ ::BrotliDecoderTakeOutput(&self->mBrotli->mState, &outSize);
+ if (NS_FAILED(callOnDataAvailable(self->mBrotli->mSourceOffset,
+ reinterpret_cast<const char*>(buffer),
+ outSize))) {
+ return self->mBrotli->mStatus;
+ }
+ }
+
+ if (res == BROTLI_DECODER_RESULT_SUCCESS ||
+ res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
+ *countRead = aAvail;
+ return NS_OK;
+ }
+ MOZ_ASSERT(res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
+ } while (res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
+
+ self->mBrotli->mStatus = NS_ERROR_UNEXPECTED;
+ return self->mBrotli->mStatus;
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr,
+ uint64_t aSourceOffset, uint32_t aCount) {
+ nsresult rv = NS_ERROR_INVALID_CONTENT_ENCODING;
+ uint32_t streamLen = aCount;
+ LOG(("nsHttpCompressConv %p OnDataAvailable %d", this, aCount));
+
+ if (streamLen == 0) {
+ NS_ERROR("count of zero passed to OnDataAvailable");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mStreamEnded) {
+ // Hmm... this may just indicate that the data stream is done and that
+ // what's left is either metadata or padding of some sort.... throwing
+ // it out is probably the safe thing to do.
+ uint32_t n;
+ return iStr->ReadSegments(NS_DiscardSegment, nullptr, streamLen, &n);
+ }
+
+ switch (mMode) {
+ case HTTP_COMPRESS_GZIP:
+ streamLen = check_header(iStr, streamLen, &rv);
+
+ if (rv != NS_OK) {
+ return rv;
+ }
+
+ if (streamLen == 0) {
+ return NS_OK;
+ }
+
+ [[fallthrough]];
+
+ case HTTP_COMPRESS_DEFLATE:
+
+ if (mInpBuffer != nullptr && streamLen > mInpBufferLen) {
+ unsigned char* originalInpBuffer = mInpBuffer;
+ if (!(mInpBuffer = (unsigned char*)realloc(
+ originalInpBuffer, mInpBufferLen = streamLen))) {
+ free(originalInpBuffer);
+ }
+
+ if (mOutBufferLen < streamLen * 2) {
+ unsigned char* originalOutBuffer = mOutBuffer;
+ if (!(mOutBuffer = (unsigned char*)realloc(
+ mOutBuffer, mOutBufferLen = streamLen * 3))) {
+ free(originalOutBuffer);
+ }
+ }
+
+ if (mInpBuffer == nullptr || mOutBuffer == nullptr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (mInpBuffer == nullptr) {
+ mInpBuffer = (unsigned char*)malloc(mInpBufferLen = streamLen);
+ }
+
+ if (mOutBuffer == nullptr) {
+ mOutBuffer = (unsigned char*)malloc(mOutBufferLen = streamLen * 3);
+ }
+
+ if (mInpBuffer == nullptr || mOutBuffer == nullptr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t unused;
+ iStr->Read((char*)mInpBuffer, streamLen, &unused);
+
+ if (mMode == HTTP_COMPRESS_DEFLATE) {
+ if (!mStreamInitialized) {
+ memset(&d_stream, 0, sizeof(d_stream));
+
+ if (inflateInit(&d_stream) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mStreamInitialized = true;
+ }
+ d_stream.next_in = mInpBuffer;
+ d_stream.avail_in = (uInt)streamLen;
+
+ mDummyStreamInitialised = false;
+ for (;;) {
+ d_stream.next_out = mOutBuffer;
+ d_stream.avail_out = (uInt)mOutBufferLen;
+
+ int code = inflate(&d_stream, Z_NO_FLUSH);
+ unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
+
+ if (code == Z_STREAM_END) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
+ bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ inflateEnd(&d_stream);
+ mStreamEnded = true;
+ break;
+ }
+ if (code == Z_OK) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
+ bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ } else if (code == Z_BUF_ERROR) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
+ bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ break;
+ } else if (code == Z_DATA_ERROR) {
+ // some servers (notably Apache with mod_deflate) don't generate
+ // zlib headers insert a dummy header and try again
+ static char dummy_head[2] = {
+ 0x8 + 0x7 * 0x10,
+ (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
+ };
+ inflateReset(&d_stream);
+ d_stream.next_in = (Bytef*)dummy_head;
+ d_stream.avail_in = sizeof(dummy_head);
+
+ code = inflate(&d_stream, Z_NO_FLUSH);
+ if (code != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // stop an endless loop caused by non-deflate data being labelled as
+ // deflate
+ if (mDummyStreamInitialised) {
+ NS_WARNING(
+ "endless loop detected"
+ " - invalid deflate");
+ return NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ mDummyStreamInitialised = true;
+ // reset stream pointers to our original data
+ d_stream.next_in = mInpBuffer;
+ d_stream.avail_in = (uInt)streamLen;
+ } else {
+ return NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ } /* for */
+ } else {
+ if (!mStreamInitialized) {
+ memset(&d_stream, 0, sizeof(d_stream));
+
+ if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mStreamInitialized = true;
+ }
+
+ d_stream.next_in = mInpBuffer;
+ d_stream.avail_in = (uInt)streamLen;
+
+ for (;;) {
+ d_stream.next_out = mOutBuffer;
+ d_stream.avail_out = (uInt)mOutBufferLen;
+
+ int code = inflate(&d_stream, Z_NO_FLUSH);
+ unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
+
+ if (code == Z_STREAM_END) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
+ bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ inflateEnd(&d_stream);
+ mStreamEnded = true;
+ break;
+ }
+ if (code == Z_OK) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
+ bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ } else if (code == Z_BUF_ERROR) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
+ bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ break;
+ } else {
+ return NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ } /* for */
+ } /* gzip */
+ break;
+
+ case HTTP_COMPRESS_BROTLI: {
+ if (!mBrotli) {
+ mBrotli = MakeUnique<BrotliWrapper>();
+ }
+
+ mBrotli->mRequest = request;
+ mBrotli->mContext = nullptr;
+ mBrotli->mSourceOffset = aSourceOffset;
+
+ uint32_t countRead;
+ rv = iStr->ReadSegments(BrotliHandler, this, streamLen, &countRead);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mBrotli->mStatus;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } break;
+
+ default:
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+ rv = listener->OnDataAvailable(request, iStr, aSourceOffset, aCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } /* switch */
+
+ return NS_OK;
+} /* OnDataAvailable */
+
+// XXX/ruslan: need to implement this too
+
+NS_IMETHODIMP
+nsHTTPCompressConv::Convert(nsIInputStream* aFromStream, const char* aFromType,
+ const char* aToType, nsISupports* aCtxt,
+ nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request,
+ uint64_t offset,
+ const char* buffer,
+ uint32_t count) {
+ if (!mStream) {
+ mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
+ NS_ENSURE_STATE(mStream);
+ }
+
+ mStream->ShareData(buffer, count);
+
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+ nsresult rv = listener->OnDataAvailable(request, mStream, offset, count);
+
+ // Make sure the stream no longer references |buffer| in case our listener
+ // is crazy enough to try to read from |mStream| after ODA.
+ mStream->ShareData("", 0);
+ mDecodedDataLength += count;
+
+ return rv;
+}
+
+#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
+#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
+#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
+#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
+#define COMMENT 0x10 /* bit 4 set: file comment present */
+#define RESERVED 0xE0 /* bits 5..7: reserved */
+
+static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
+
+uint32_t nsHTTPCompressConv::check_header(nsIInputStream* iStr,
+ uint32_t streamLen, nsresult* rs) {
+ enum {
+ GZIP_INIT = 0,
+ GZIP_OS,
+ GZIP_EXTRA0,
+ GZIP_EXTRA1,
+ GZIP_EXTRA2,
+ GZIP_ORIG,
+ GZIP_COMMENT,
+ GZIP_CRC
+ };
+ char c;
+
+ *rs = NS_OK;
+
+ if (mCheckHeaderDone) {
+ return streamLen;
+ }
+
+ while (streamLen) {
+ switch (hMode) {
+ case GZIP_INIT:
+ uint32_t unused;
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+
+ if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0]) {
+ *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
+ return 0;
+ }
+
+ if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1]) {
+ *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
+ return 0;
+ }
+
+ if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED) {
+ *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
+ return 0;
+ }
+
+ mSkipCount++;
+ if (mSkipCount == 4) {
+ mFlags = (unsigned)c & 0377;
+ if (mFlags & RESERVED) {
+ *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
+ return 0;
+ }
+ hMode = GZIP_OS;
+ mSkipCount = 0;
+ }
+ break;
+
+ case GZIP_OS:
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+ mSkipCount++;
+
+ if (mSkipCount == 6) {
+ hMode = GZIP_EXTRA0;
+ }
+ break;
+
+ case GZIP_EXTRA0:
+ if (mFlags & EXTRA_FIELD) {
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+ mLen = (uInt)c & 0377;
+ hMode = GZIP_EXTRA1;
+ } else {
+ hMode = GZIP_ORIG;
+ }
+ break;
+
+ case GZIP_EXTRA1:
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+ mLen |= ((uInt)c & 0377) << 8;
+ mSkipCount = 0;
+ hMode = GZIP_EXTRA2;
+ break;
+
+ case GZIP_EXTRA2:
+ if (mSkipCount == mLen) {
+ hMode = GZIP_ORIG;
+ } else {
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+ mSkipCount++;
+ }
+ break;
+
+ case GZIP_ORIG:
+ if (mFlags & ORIG_NAME) {
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+ if (c == 0) hMode = GZIP_COMMENT;
+ } else {
+ hMode = GZIP_COMMENT;
+ }
+ break;
+
+ case GZIP_COMMENT:
+ if (mFlags & COMMENT) {
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+ if (c == 0) {
+ hMode = GZIP_CRC;
+ mSkipCount = 0;
+ }
+ } else {
+ hMode = GZIP_CRC;
+ mSkipCount = 0;
+ }
+ break;
+
+ case GZIP_CRC:
+ if (mFlags & HEAD_CRC) {
+ iStr->Read(&c, 1, &unused);
+ streamLen--;
+ mSkipCount++;
+ if (mSkipCount == 2) {
+ mCheckHeaderDone = true;
+ return streamLen;
+ }
+ } else {
+ mCheckHeaderDone = true;
+ return streamLen;
+ }
+ break;
+ }
+ }
+ return streamLen;
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::CheckListenerChain() {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = do_QueryInterface(mListener);
+ }
+
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
+
+ {
+ MutexAutoLock lock(mMutex);
+ listener = do_QueryInterface(mListener);
+ }
+
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+nsresult NS_NewHTTPCompressConv(
+ mozilla::net::nsHTTPCompressConv** aHTTPCompressConv) {
+ MOZ_ASSERT(aHTTPCompressConv != nullptr, "null ptr");
+ if (!aHTTPCompressConv) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ RefPtr<mozilla::net::nsHTTPCompressConv> outVal =
+ new mozilla::net::nsHTTPCompressConv();
+ if (!outVal) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ outVal.forget(aHTTPCompressConv);
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.h b/netwerk/streamconv/converters/nsHTTPCompressConv.h
new file mode 100644
index 0000000000..4ba2d9819c
--- /dev/null
+++ b/netwerk/streamconv/converters/nsHTTPCompressConv.h
@@ -0,0 +1,109 @@
+/* -*- 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/. */
+
+#if !defined(__nsHTTPCompressConv__h__)
+# define __nsHTTPCompressConv__h__ 1
+
+# include "nsIStreamConverter.h"
+# include "nsICompressConvStats.h"
+# include "nsIThreadRetargetableStreamListener.h"
+# include "nsCOMPtr.h"
+# include "mozilla/Atomics.h"
+# include "mozilla/Mutex.h"
+
+# include "zlib.h"
+
+class nsIStringInputStream;
+
+# define NS_HTTPCOMPRESSCONVERTER_CID \
+ { \
+ /* 66230b2b-17fa-4bd3-abf4-07986151022d */ \
+ 0x66230b2b, 0x17fa, 0x4bd3, { \
+ 0xab, 0xf4, 0x07, 0x98, 0x61, 0x51, 0x02, 0x2d \
+ } \
+ }
+
+# define HTTP_DEFLATE_TYPE "deflate"
+# define HTTP_GZIP_TYPE "gzip"
+# define HTTP_X_GZIP_TYPE "x-gzip"
+# define HTTP_COMPRESS_TYPE "compress"
+# define HTTP_X_COMPRESS_TYPE "x-compress"
+# define HTTP_BROTLI_TYPE "br"
+# define HTTP_IDENTITY_TYPE "identity"
+# define HTTP_UNCOMPRESSED_TYPE "uncompressed"
+
+namespace mozilla {
+namespace net {
+
+class BrotliWrapper;
+
+class nsHTTPCompressConv : public nsIStreamConverter,
+ public nsICompressConvStats {
+ public:
+ // nsISupports methods
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICOMPRESSCONVSTATS
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ // nsIStreamConverter methods
+ NS_DECL_NSISTREAMCONVERTER
+
+ nsHTTPCompressConv();
+
+ using CompressMode = enum {
+ HTTP_COMPRESS_GZIP,
+ HTTP_COMPRESS_DEFLATE,
+ HTTP_COMPRESS_COMPRESS,
+ HTTP_COMPRESS_BROTLI,
+ HTTP_COMPRESS_IDENTITY
+ };
+
+ private:
+ virtual ~nsHTTPCompressConv();
+
+ nsCOMPtr<nsIStreamListener>
+ mListener; // this guy gets the converted data via his OnDataAvailable ()
+ Atomic<CompressMode, Relaxed> mMode{HTTP_COMPRESS_IDENTITY};
+
+ unsigned char* mOutBuffer{nullptr};
+ unsigned char* mInpBuffer{nullptr};
+
+ uint32_t mOutBufferLen{0};
+ uint32_t mInpBufferLen{0};
+
+ UniquePtr<BrotliWrapper> mBrotli;
+
+ nsCOMPtr<nsIStringInputStream> mStream;
+
+ static nsresult BrotliHandler(nsIInputStream* stream, void* closure,
+ const char* dataIn, uint32_t, uint32_t avail,
+ uint32_t* countRead);
+
+ nsresult do_OnDataAvailable(nsIRequest* request, uint64_t aSourceOffset,
+ const char* buffer, uint32_t aCount);
+
+ bool mCheckHeaderDone{false};
+ Atomic<bool> mStreamEnded{false};
+ bool mStreamInitialized{false};
+ bool mDummyStreamInitialised{false};
+ bool mFailUncleanStops;
+
+ z_stream d_stream{};
+ unsigned mLen{0}, hMode{0}, mSkipCount{0}, mFlags{0};
+
+ uint32_t check_header(nsIInputStream* iStr, uint32_t streamLen, nsresult* rs);
+
+ Atomic<uint32_t, Relaxed> mDecodedDataLength{0};
+
+ mutable mozilla::Mutex mMutex MOZ_UNANNOTATED{"nsHTTPCompressConv"};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/streamconv/converters/nsICompressConvStats.idl b/netwerk/streamconv/converters/nsICompressConvStats.idl
new file mode 100644
index 0000000000..a8837563ed
--- /dev/null
+++ b/netwerk/streamconv/converters/nsICompressConvStats.idl
@@ -0,0 +1,17 @@
+/* -*- 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"
+
+/**
+ * nsICompressConvStats
+ *
+ * This interface allows for the observation of decoded resource sizes
+ */
+[builtinclass, scriptable, uuid(58172ad0-46a9-4893-8fde-cd909c10792a)]
+interface nsICompressConvStats : nsISupports
+{
+ readonly attribute uint64_t decodedDataLength;
+};
diff --git a/netwerk/streamconv/converters/nsIndexedToHTML.cpp b/netwerk/streamconv/converters/nsIndexedToHTML.cpp
new file mode 100644
index 0000000000..efc8962459
--- /dev/null
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp
@@ -0,0 +1,825 @@
+/* -*- 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 "nsIndexedToHTML.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/intl/AppDateTimeFormat.h"
+#include "mozilla/intl/LocaleService.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsNetUtil.h"
+#include "netCore.h"
+#include "nsStringStream.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsEscape.h"
+#include "nsIDirIndex.h"
+#include "nsURLHelper.h"
+#include "nsIStringBundle.h"
+#include "nsDirIndexParser.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsString.h"
+#include "nsContentUtils.h"
+#include <algorithm>
+#include "nsIChannel.h"
+#include "mozilla/Unused.h"
+#include "nsIURIMutator.h"
+#include "nsITextToSubURI.h"
+
+using mozilla::intl::LocaleService;
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsIndexedToHTML, nsIDirIndexListener, nsIStreamConverter,
+ nsIThreadRetargetableStreamListener, nsIRequestObserver,
+ nsIStreamListener)
+
+static void AppendNonAsciiToNCR(const nsAString& in, nsCString& out) {
+ nsAString::const_iterator start, end;
+
+ in.BeginReading(start);
+ in.EndReading(end);
+
+ while (start != end) {
+ if (*start < 128) {
+ out.Append(*start++);
+ } else {
+ out.AppendLiteral("&#x");
+ out.AppendInt(*start++, 16);
+ out.Append(';');
+ }
+ }
+}
+
+nsresult nsIndexedToHTML::Create(REFNSIID aIID, void** aResult) {
+ nsresult rv;
+
+ nsIndexedToHTML* _s = new nsIndexedToHTML();
+ if (_s == nullptr) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = _s->QueryInterface(aIID, aResult);
+ return rv;
+}
+
+nsresult nsIndexedToHTML::Init(nsIStreamListener* aListener) {
+ nsresult rv = NS_OK;
+
+ mListener = aListener;
+
+ nsCOMPtr<nsIStringBundleService> sbs =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ rv = sbs->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(mBundle));
+
+ mExpectAbsLoc = false;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::Convert(nsIInputStream* aFromStream, const char* aFromType,
+ const char* aToType, nsISupports* aCtxt,
+ nsIInputStream** res) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::AsyncConvertData(const char* aFromType, const char* aToType,
+ nsIStreamListener* aListener,
+ nsISupports* aCtxt) {
+ return Init(aListener);
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::GetConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel, nsACString& aToType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnStartRequest(nsIRequest* request) {
+ nsCString buffer;
+ nsresult rv = DoOnStartRequest(request, buffer);
+ if (NS_FAILED(rv)) {
+ request->Cancel(rv);
+ }
+
+ rv = mListener->OnStartRequest(request);
+ if (NS_FAILED(rv)) return rv;
+
+ // The request may have been canceled, and if that happens, we want to
+ // suppress calls to OnDataAvailable.
+ request->GetStatus(&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Push our buffer to the listener.
+
+ rv = SendToListener(request, buffer);
+ return rv;
+}
+
+nsresult nsIndexedToHTML::DoOnStartRequest(nsIRequest* request,
+ nsCString& aBuffer) {
+ nsresult rv;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetOriginalURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ // We use the original URI for the title and parent link when it's a
+ // resource:// url, instead of the jar:file:// url it resolves to.
+ if (!uri->SchemeIs("resource")) {
+ rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ channel->SetContentType("text/html"_ns);
+
+ mParser = nsDirIndexParser::CreateInstance();
+ if (!mParser) return NS_ERROR_FAILURE;
+
+ rv = mParser->SetListener(this);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->OnStartRequest(request);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString baseUri, titleUri;
+ rv = uri->GetAsciiSpec(baseUri);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> titleURL;
+ rv = NS_MutateURI(uri).SetQuery(""_ns).SetRef(""_ns).Finalize(titleURL);
+ if (NS_FAILED(rv)) {
+ titleURL = uri;
+ }
+
+ nsCString parentStr;
+
+ nsCString buffer;
+ buffer.AppendLiteral("<!DOCTYPE html>\n<html>\n<head>\n");
+
+ // XXX - should be using the 300: line from the parser.
+ // We can't guarantee that that comes before any entry, so we'd have to
+ // buffer, and do other painful stuff.
+ // I'll deal with this when I make the changes to handle welcome messages
+ // The .. stuff should also come from the lower level protocols, but that
+ // would muck up the XUL display
+ // - bbaetz
+
+ if (uri->SchemeIs("file")) {
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString url;
+ rv = net_GetURLSpecFromFile(file, url);
+ if (NS_FAILED(rv)) return rv;
+ baseUri.Assign(url);
+
+ nsCOMPtr<nsIFile> parent;
+ rv = file->GetParent(getter_AddRefs(parent));
+
+ if (parent && NS_SUCCEEDED(rv)) {
+ net_GetURLSpecFromDir(parent, url);
+ if (NS_FAILED(rv)) return rv;
+ parentStr.Assign(url);
+ }
+
+ // Directory index will be always encoded in UTF-8 if this is file url
+ buffer.AppendLiteral("<meta charset=\"UTF-8\">\n");
+
+ } else if (uri->SchemeIs("jar")) {
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // a top-level jar directory URL is of the form jar:foo.zip!/
+ // path will be of the form foo.zip!/, and its last two characters
+ // will be "!/"
+ // XXX this won't work correctly when the name of the directory being
+ // XXX displayed ends with "!", but then again, jar: URIs don't deal
+ // XXX particularly well with such directories anyway
+ if (!StringEndsWith(path, "!/"_ns)) {
+ rv = uri->Resolve(".."_ns, parentStr);
+ if (NS_FAILED(rv)) return rv;
+ }
+ } else {
+ // default behavior for other protocols is to assume the channel's
+ // URL references a directory ending in '/' -- fixup if necessary.
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+ if (baseUri.Last() != '/') {
+ baseUri.Append('/');
+ path.Append('/');
+ mozilla::Unused << NS_MutateURI(uri).SetPathQueryRef(path).Finalize(uri);
+ }
+ if (!path.EqualsLiteral("/")) {
+ rv = uri->Resolve(".."_ns, parentStr);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ rv = titleURL->GetAsciiSpec(titleUri);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ buffer.AppendLiteral(
+ "<style type=\"text/css\">\n"
+ ":root {\n"
+ " font-family: sans-serif;\n"
+ "}\n"
+ "img {\n"
+ " border: 0;\n"
+ "}\n"
+ "th {\n"
+ " text-align: start;\n"
+ " white-space: nowrap;\n"
+ "}\n"
+ "th > a {\n"
+ " color: inherit;\n"
+ "}\n"
+ "table[order] > thead > tr > th {\n"
+ " cursor: pointer;\n"
+ "}\n"
+ "table[order] > thead > tr > th::after {\n"
+ " display: none;\n"
+ " width: .8em;\n"
+ " margin-inline-end: -.8em;\n"
+ " text-align: end;\n"
+ "}\n"
+ "table[order=\"asc\"] > thead > tr > th::after {\n"
+ " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n"
+ "}\n"
+ "table[order=\"desc\"] > thead > tr > th::after {\n"
+ " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n"
+ "}\n"
+ "table[order][order-by=\"0\"] > thead > tr > th:first-child > a ,\n"
+ "table[order][order-by=\"1\"] > thead > tr > th:first-child + th > a ,\n"
+ "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th > "
+ "a {\n"
+ " text-decoration: underline;\n"
+ "}\n"
+ "table[order][order-by=\"0\"] > thead > tr > th:first-child::after ,\n"
+ "table[order][order-by=\"1\"] > thead > tr > th:first-child + th::after "
+ ",\n"
+ "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + "
+ "th::after {\n"
+ " display: inline-block;\n"
+ "}\n"
+ "table.remove-hidden > tbody > tr.hidden-object {\n"
+ " display: none;\n"
+ "}\n"
+ "td {\n"
+ " white-space: nowrap;\n"
+ "}\n"
+ "table.ellipsis {\n"
+ " width: 100%;\n"
+ " table-layout: fixed;\n"
+ " border-spacing: 0;\n"
+ "}\n"
+ "table.ellipsis > tbody > tr > td {\n"
+ " overflow: hidden;\n"
+ " text-overflow: ellipsis;\n"
+ "}\n"
+ "/* name */\n"
+ "/* name */\n"
+ "th:first-child {\n"
+ " padding-inline-end: 2em;\n"
+ "}\n"
+ "/* size */\n"
+ "th:first-child + th {\n"
+ " padding-inline-end: 1em;\n"
+ "}\n"
+ "td:first-child + td {\n"
+ " text-align: end;\n"
+ " padding-inline-end: 1em;\n"
+ "}\n"
+ "/* date */\n"
+ "td:first-child + td + td {\n"
+ " padding-inline-start: 1em;\n"
+ " padding-inline-end: .5em;\n"
+ "}\n"
+ "/* time */\n"
+ "td:first-child + td + td + td {\n"
+ " padding-inline-start: .5em;\n"
+ "}\n"
+ ".symlink {\n"
+ " font-style: italic;\n"
+ "}\n"
+ ".dir ,\n"
+ ".symlink ,\n"
+ ".file {\n"
+ " margin-inline-start: 20px;\n"
+ "}\n"
+ ".dir::before ,\n"
+ ".file > img {\n"
+ " margin-inline-end: 4px;\n"
+ " margin-inline-start: -20px;\n"
+ " max-width: 16px;\n"
+ " max-height: 16px;\n"
+ " vertical-align: middle;\n"
+ "}\n"
+ ".dir::before {\n"
+ " content: url(resource://content-accessible/html/folder.png);\n"
+ "}\n"
+ "</style>\n"
+ "<link rel=\"stylesheet\" media=\"screen, projection\" type=\"text/css\""
+ " href=\"chrome://global/skin/dirListing/dirListing.css\">\n"
+ "<script type=\"application/javascript\">\n"
+ "'use strict';\n"
+ "var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;\n"
+ "document.addEventListener(\"DOMContentLoaded\", function() {\n"
+ " gTable = document.getElementsByTagName(\"table\")[0];\n"
+ " gTBody = gTable.tBodies[0];\n"
+ " if (gTBody.rows.length < 2)\n"
+ " return;\n"
+ " gUI_showHidden = document.getElementById(\"UI_showHidden\");\n"
+ " var headCells = gTable.tHead.rows[0].cells,\n"
+ " hiddenObjects = false;\n"
+ " function rowAction(i) {\n"
+ " return function(event) {\n"
+ " event.preventDefault();\n"
+ " orderBy(i);\n"
+ " }\n"
+ " }\n"
+ " for (var i = headCells.length - 1; i >= 0; i--) {\n"
+ " var anchor = document.createElement(\"a\");\n"
+ " anchor.href = \"\";\n"
+ " anchor.appendChild(headCells[i].firstChild);\n"
+ " headCells[i].appendChild(anchor);\n"
+ " headCells[i].addEventListener(\"click\", rowAction(i), true);\n"
+ " }\n"
+ " if (gUI_showHidden) {\n"
+ " gRows = Array.from(gTBody.rows);\n"
+ " hiddenObjects = gRows.some(row => row.className == "
+ "\"hidden-object\");\n"
+ " }\n"
+ " gTable.setAttribute(\"order\", \"\");\n"
+ " if (hiddenObjects) {\n"
+ " gUI_showHidden.style.display = \"block\";\n"
+ " updateHidden();\n"
+ " }\n"
+ "}, \"false\");\n"
+ "function compareRows(rowA, rowB) {\n"
+ " var a = rowA.cells[gOrderBy].getAttribute(\"sortable-data\") || "
+ "\"\";\n"
+ " var b = rowB.cells[gOrderBy].getAttribute(\"sortable-data\") || "
+ "\"\";\n"
+ " var intA = +a;\n"
+ " var intB = +b;\n"
+ " if (a == intA && b == intB) {\n"
+ " a = intA;\n"
+ " b = intB;\n"
+ " } else {\n"
+ " a = a.toLowerCase();\n"
+ " b = b.toLowerCase();\n"
+ " }\n"
+ " if (a < b)\n"
+ " return -1;\n"
+ " if (a > b)\n"
+ " return 1;\n"
+ " return 0;\n"
+ "}\n"
+ "function orderBy(column) {\n"
+ " if (!gRows)\n"
+ " gRows = Array.from(gTBody.rows);\n"
+ " var order;\n"
+ " if (gOrderBy == column) {\n"
+ " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : "
+ "\"asc\";\n"
+ " } else {\n"
+ " order = \"asc\";\n"
+ " gOrderBy = column;\n"
+ " gTable.setAttribute(\"order-by\", column);\n"
+ " gRows.sort(compareRows);\n"
+ " }\n"
+ " gTable.removeChild(gTBody);\n"
+ " gTable.setAttribute(\"order\", order);\n"
+ " if (order == \"asc\")\n"
+ " for (var i = 0; i < gRows.length; i++)\n"
+ " gTBody.appendChild(gRows[i]);\n"
+ " else\n"
+ " for (var i = gRows.length - 1; i >= 0; i--)\n"
+ " gTBody.appendChild(gRows[i]);\n"
+ " gTable.appendChild(gTBody);\n"
+ "}\n"
+ "function updateHidden() {\n"
+ " gTable.className = "
+ "gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n"
+ " \"\" :\n"
+ " \"remove-hidden\";\n"
+ "}\n"
+ "</script>\n");
+
+ buffer.AppendLiteral(R"(<link rel="icon" type="image/png" href=")");
+ nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
+ if (!innerUri) return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(innerUri));
+ // XXX bug 388553: can't use skinnable icons here due to security restrictions
+ if (fileURL) {
+ buffer.AppendLiteral(
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
+ "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
+ "ZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNqsU8uOElEQPffR"
+ "3XQ3ONASdBJCSBxHos5%2B3Bg3rvkCv8PElS78gPkO%2FATj"
+ "QoUdO2ftrJiRh6aneTb9sOpC4weMN6lcuFV16pxDIfI8x12O"
+ "YIDhcPiu2Wx%2B%2FHF5CW1Z6Jyegt%2FTNEWSJIjjGFEUIQ"
+ "xDrFYrWFSzXC4%2FdLvd95pRKpXKy%2BpRFZ7nwaWo1%2BsG"
+ "nQG2260BKJfLKJVKGI1GEEJw7ateryd0v993W63WEwjgxfn5"
+ "obGYzgCbzcaEbdsIggDj8Riu6z6iUk9SYZMSx8W0LMsM%2FS"
+ "KK75xnJlIq80anQXdbEp0OhcPJ0eiaJnGRMEyyPDsAKKUM9c"
+ "lkYoDo3SZJzzSdp0VSKYmfV1co%2Bz580kw5KDIM8RbRfEnU"
+ "f1HzxtQyMAGcaGruTKczMzEIaqhKifV6jd%2BzGQQB5llunF"
+ "%2FM52BizC2K5sYPYvZcu653tjOM9O93wnYc08gmkgg4VAxi"
+ "xfqFUJT36AYBZGd6PJkFCZnnlBxMp38gqIgLpZB0y4Nph18l"
+ "yWh5FFbrOSxbl3V4G%2BVB7T4ajYYxTyuLtO%2BCvWGgJE1M"
+ "c7JNsJEhvgw%2FQV4fo%2F24nbEsX2u1d5sVyn8sJO0ZAQiI"
+ "YnFh%2BxrfLz%2Fj29cBS%2FO14zg3i8XigW3ZkErDtmKoeM"
+ "%2BAJGRMnXeEPGKf0nCD1ydvkDzU9Jbc6OpR7WIw6L8lQ%2B"
+ "4pQ1%2FlPF0RGM9Ns91Wmptk0GfB4EJkt77vXYj%2F8m%2B8"
+ "y%2FkrwABHbz2H9V68DQAAAABJRU5ErkJggg%3D%3D");
+ } else {
+ buffer.AppendLiteral(
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
+ "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
+ "ZSBJbWFnZVJlYWR5ccllPAAAAeBJREFUeNqcU81O20AQ%2Ft"
+ "Z2AgQSYQRqL1UPVG2hAUQkxLEStz4DrXpLpD5Drz31Cajax%"
+ "2Bghhx6qHIJURBTxIwQRwopCBbZjHMcOTrzermPipsSt1Iw0"
+ "3p3ZmW%2B%2B2R0TxhgOD34wjCHZlQ0iDYz9yvEfhxMTCYhE"
+ "QDIZhkxKd2sqzX2TOD2vBQCQhpPefng1ZP2dVPlLLdpL8SEM"
+ "cxng%2Fbs0RIHhtgs4twxOh%2BHjZxvzDx%2F3GQQiDFISiR"
+ "BLFMPKTRMollzcWECrDVhtxtdRVsL9youPxGj%2FbdfFlUZh"
+ "tDyYbYqWRUdai1oQRZ5oHeHl2gNM%2B01Uqio8RlH%2Bnsaz"
+ "JzNwXcq1B%2BiXPHprlEEymeBfXs1w8XxxihfyuXqoHqpoGj"
+ "ZM04bddgG%2F9%2B8WGj87qDdsrK9m%2BoA%2BpbhQTDh2l1"
+ "%2Bi2weNbSHMZyjvNXmVbqh9Fj5Oz27uEoP%2BSTxANruJs9"
+ "L%2FT6P0ewqPx5nmiAG5f6AoCtN1PbJzuRyJAyDBzzSQYvEr"
+ "f06yYxhGXlEa8H2KVGoasjwLx3Ewk858opQWXm%2B%2Fib9E"
+ "QrBzclLLLy89xYvlpchvtixcX6uo1y%2FzsiwHrkIsgKbp%2"
+ "BYWFOWicuqppoNTnStHzPFCPQhBEBOyGAX4JMADFetubi4BS"
+ "YAAAAABJRU5ErkJggg%3D%3D");
+ }
+ buffer.AppendLiteral("\">\n<title>");
+
+ // Everything needs to end in a /,
+ // otherwise we end up linking to file:///foo/dirfile
+
+ if (!mTextToSubURI) {
+ mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsAutoString unEscapeSpec;
+ rv = mTextToSubURI->UnEscapeAndConvert("UTF-8"_ns, titleUri, unEscapeSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString htmlEscSpecUtf8;
+ nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(unEscapeSpec), htmlEscSpecUtf8);
+ AutoTArray<nsString, 1> formatTitle;
+ CopyUTF8toUTF16(htmlEscSpecUtf8, *formatTitle.AppendElement());
+
+ nsAutoString title;
+ rv = mBundle->FormatStringFromName("DirTitle", formatTitle, title);
+ if (NS_FAILED(rv)) return rv;
+
+ // we want to convert string bundle to NCR
+ // to ensure they're shown in any charsets
+ AppendNonAsciiToNCR(title, buffer);
+
+ buffer.AppendLiteral("</title>\n");
+
+ // If there is a quote character in the baseUri, then
+ // lets not add a base URL. The reason for this is that
+ // if we stick baseUri containing a quote into a quoted
+ // string, the quote character will prematurely close
+ // the base href string. This is a fall-back check;
+ // that's why it is OK to not use a base rather than
+ // trying to play nice and escaping the quotes. See bug
+ // 358128.
+
+ if (!baseUri.Contains('"')) {
+ // Great, the baseUri does not contain a char that
+ // will prematurely close the string. Go ahead an
+ // add a base href, but only do so if we're not
+ // dealing with a resource URI.
+ if (!uri->SchemeIs("resource")) {
+ buffer.AppendLiteral("<base href=\"");
+ nsAppendEscapedHTML(baseUri, buffer);
+ buffer.AppendLiteral("\" />\n");
+ }
+ } else {
+ NS_ERROR("broken protocol handler didn't escape double-quote.");
+ }
+
+ nsCString direction("ltr"_ns);
+ if (LocaleService::GetInstance()->IsAppLocaleRTL()) {
+ direction.AssignLiteral("rtl");
+ }
+
+ buffer.AppendLiteral("</head>\n<body dir=\"");
+ buffer.Append(direction);
+ buffer.AppendLiteral("\">\n<h1>");
+ AppendNonAsciiToNCR(title, buffer);
+ buffer.AppendLiteral("</h1>\n");
+
+ if (!parentStr.IsEmpty()) {
+ nsAutoString parentText;
+ rv = mBundle->GetStringFromName("DirGoUp", parentText);
+ if (NS_FAILED(rv)) return rv;
+
+ buffer.AppendLiteral(R"(<p id="UI_goUp"><a class="up" href=")");
+ nsAppendEscapedHTML(parentStr, buffer);
+ buffer.AppendLiteral("\">");
+ AppendNonAsciiToNCR(parentText, buffer);
+ buffer.AppendLiteral("</a></p>\n");
+ }
+
+ if (uri->SchemeIs("file")) {
+ nsAutoString showHiddenText;
+ rv = mBundle->GetStringFromName("ShowHidden", showHiddenText);
+ if (NS_FAILED(rv)) return rv;
+
+ buffer.AppendLiteral(
+ "<p id=\"UI_showHidden\" style=\"display:none\"><label><input "
+ "type=\"checkbox\" checked onchange=\"updateHidden()\">");
+ AppendNonAsciiToNCR(showHiddenText, buffer);
+ buffer.AppendLiteral("</label></p>\n");
+ }
+
+ buffer.AppendLiteral(
+ "<table>\n"
+ " <thead>\n"
+ " <tr>\n"
+ " <th>");
+
+ nsAutoString columnText;
+ rv = mBundle->GetStringFromName("DirColName", columnText);
+ if (NS_FAILED(rv)) return rv;
+ AppendNonAsciiToNCR(columnText, buffer);
+ buffer.AppendLiteral(
+ "</th>\n"
+ " <th>");
+
+ rv = mBundle->GetStringFromName("DirColSize", columnText);
+ if (NS_FAILED(rv)) return rv;
+ AppendNonAsciiToNCR(columnText, buffer);
+ buffer.AppendLiteral(
+ "</th>\n"
+ " <th colspan=\"2\">");
+
+ rv = mBundle->GetStringFromName("DirColMTime", columnText);
+ if (NS_FAILED(rv)) return rv;
+ AppendNonAsciiToNCR(columnText, buffer);
+ buffer.AppendLiteral(
+ "</th>\n"
+ " </tr>\n"
+ " </thead>\n");
+ buffer.AppendLiteral(" <tbody>\n");
+
+ aBuffer = buffer;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ nsCString buffer;
+ buffer.AssignLiteral("</tbody></table></body></html>\n");
+
+ aStatus = SendToListener(request, buffer);
+ }
+
+ mParser->OnStopRequest(request, aStatus);
+ mParser = nullptr;
+
+ return mListener->OnStopRequest(request, aStatus);
+}
+
+nsresult nsIndexedToHTML::SendToListener(nsIRequest* aRequest,
+ const nsACString& aBuffer) {
+ nsCOMPtr<nsIInputStream> inputData;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputData), aBuffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mListener->OnDataAvailable(aRequest, inputData, 0, aBuffer.Length());
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInput,
+ uint64_t aOffset, uint32_t aCount) {
+ return mParser->OnDataAvailable(aRequest, aInput, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnDataFinished(nsresult aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::CheckListenerChain() {
+ // nsIndexedToHTML does not support OnDataAvailable to run OMT. This class
+ // should only pass-through OnDataFinished notification.
+ return NS_ERROR_NO_INTERFACE;
+}
+
+static nsresult FormatTime(
+ const mozilla::intl::DateTimeFormat::StyleBag& aStyleBag,
+ const PRTime aPrTime, nsAString& aStringOut) {
+ // FormatPRExplodedTime will use GMT based formatted string (e.g. GMT+1)
+ // instead of local time zone name (e.g. CEST).
+ // To avoid this case when ResistFingerprinting is disabled, use
+ // |FormatPRTime| to show exact time zone name.
+ if (!nsContentUtils::ShouldResistFingerprinting(true,
+ RFPTarget::JSDateTimeUTC)) {
+ return mozilla::intl::AppDateTimeFormat::Format(aStyleBag, aPrTime,
+ aStringOut);
+ }
+
+ PRExplodedTime prExplodedTime;
+ PR_ExplodeTime(aPrTime, PR_GMTParameters, &prExplodedTime);
+ return mozilla::intl::AppDateTimeFormat::Format(aStyleBag, &prExplodedTime,
+ aStringOut);
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnIndexAvailable(nsIRequest* aRequest, nsIDirIndex* aIndex) {
+ nsresult rv;
+ if (!aIndex) return NS_ERROR_NULL_POINTER;
+
+ nsCString pushBuffer;
+ pushBuffer.AppendLiteral("<tr");
+
+ // We don't know the file's character set yet, so retrieve the raw bytes
+ // which will be decoded by the HTML parser.
+ nsCString loc;
+ aIndex->GetLocation(loc);
+
+ // Adjust the length in case unescaping shortened the string.
+ loc.Truncate(nsUnescapeCount(loc.BeginWriting()));
+
+ if (loc.IsEmpty()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (loc.First() == char16_t('.')) {
+ pushBuffer.AppendLiteral(" class=\"hidden-object\"");
+ }
+
+ pushBuffer.AppendLiteral(">\n <td sortable-data=\"");
+
+ // The sort key is the name of the item, prepended by either 0, 1 or 2
+ // in order to group items.
+ uint32_t type;
+ aIndex->GetType(&type);
+ switch (type) {
+ case nsIDirIndex::TYPE_SYMLINK:
+ pushBuffer.Append('0');
+ break;
+ case nsIDirIndex::TYPE_DIRECTORY:
+ pushBuffer.Append('1');
+ break;
+ default:
+ pushBuffer.Append('2');
+ break;
+ }
+ nsCString escaped;
+ nsAppendEscapedHTML(loc, escaped);
+ pushBuffer.Append(escaped);
+
+ pushBuffer.AppendLiteral(
+ R"("><table class="ellipsis"><tbody><tr><td><a class=")");
+ switch (type) {
+ case nsIDirIndex::TYPE_DIRECTORY:
+ pushBuffer.AppendLiteral("dir");
+ break;
+ case nsIDirIndex::TYPE_SYMLINK:
+ pushBuffer.AppendLiteral("symlink");
+ break;
+ default:
+ pushBuffer.AppendLiteral("file");
+ break;
+ }
+
+ pushBuffer.AppendLiteral("\" href=\"");
+
+ // need to escape links
+ nsAutoCString locEscaped;
+
+ // Adding trailing slash helps to recognize whether the URL points to a file
+ // or a directory (bug #214405).
+ if ((type == nsIDirIndex::TYPE_DIRECTORY) && (loc.Last() != '/')) {
+ loc.Append('/');
+ }
+
+ // now minimally re-escape the location...
+ uint32_t escFlags;
+ // for some protocols, we expect the location to be absolute.
+ // if so, and if the location indeed appears to be a valid URI, then go
+ // ahead and treat it like one.
+
+ nsAutoCString scheme;
+ if (mExpectAbsLoc && NS_SUCCEEDED(net_ExtractURLScheme(loc, scheme))) {
+ // escape as absolute
+ escFlags = esc_Forced | esc_AlwaysCopy | esc_Minimal;
+ } else {
+ // escape as relative
+ // esc_Directory is needed because directories have a trailing slash.
+ // Without it, the trailing '/' will be escaped, and links from within
+ // that directory will be incorrect
+ escFlags = esc_Forced | esc_AlwaysCopy | esc_FileBaseName | esc_Colon |
+ esc_Directory;
+ }
+ NS_EscapeURL(loc.get(), loc.Length(), escFlags, locEscaped);
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ locEscaped.ReplaceSubstring(";", "%3b");
+ nsAppendEscapedHTML(locEscaped, pushBuffer);
+ pushBuffer.AppendLiteral("\">");
+
+ if (type == nsIDirIndex::TYPE_FILE || type == nsIDirIndex::TYPE_UNKNOWN) {
+ pushBuffer.AppendLiteral("<img src=\"moz-icon://");
+ int32_t lastDot = locEscaped.RFindChar('.');
+ if (lastDot != kNotFound) {
+ locEscaped.Cut(0, lastDot);
+ nsAppendEscapedHTML(locEscaped, pushBuffer);
+ } else {
+ pushBuffer.AppendLiteral("unknown");
+ }
+ pushBuffer.AppendLiteral("?size=16\" alt=\"");
+
+ nsAutoString altText;
+ rv = mBundle->GetStringFromName("DirFileLabel", altText);
+ if (NS_FAILED(rv)) return rv;
+ AppendNonAsciiToNCR(altText, pushBuffer);
+ pushBuffer.AppendLiteral("\">");
+ }
+
+ pushBuffer.Append(escaped);
+ pushBuffer.AppendLiteral("</a></td></tr></tbody></table></td>\n <td");
+
+ if (type == nsIDirIndex::TYPE_DIRECTORY ||
+ type == nsIDirIndex::TYPE_SYMLINK) {
+ pushBuffer.Append('>');
+ } else {
+ int64_t size;
+ aIndex->GetSize(&size);
+
+ if (uint64_t(size) != UINT64_MAX) {
+ pushBuffer.AppendLiteral(" sortable-data=\"");
+ pushBuffer.AppendInt(size);
+ pushBuffer.AppendLiteral("\">");
+ nsAutoCString sizeString;
+ FormatSizeString(size, sizeString);
+ pushBuffer.Append(sizeString);
+ } else {
+ pushBuffer.Append('>');
+ }
+ }
+ pushBuffer.AppendLiteral("</td>\n <td");
+
+ PRTime t;
+ aIndex->GetLastModified(&t);
+
+ if (t == -1LL) {
+ pushBuffer.AppendLiteral("></td>\n <td>");
+ } else {
+ pushBuffer.AppendLiteral(" sortable-data=\"");
+ pushBuffer.AppendInt(static_cast<int64_t>(t));
+ pushBuffer.AppendLiteral("\">");
+ // Add date string
+ nsAutoString formatted;
+ mozilla::intl::DateTimeFormat::StyleBag dateBag;
+ dateBag.date = Some(mozilla::intl::DateTimeFormat::Style::Short);
+ FormatTime(dateBag, t, formatted);
+ AppendNonAsciiToNCR(formatted, pushBuffer);
+ pushBuffer.AppendLiteral("</td>\n <td>");
+ // Add time string
+ mozilla::intl::DateTimeFormat::StyleBag timeBag;
+ timeBag.time = Some(mozilla::intl::DateTimeFormat::Style::Long);
+ FormatTime(timeBag, t, formatted);
+ // use NCR to show date in any doc charset
+ AppendNonAsciiToNCR(formatted, pushBuffer);
+ }
+
+ pushBuffer.AppendLiteral("</td>\n</tr>");
+
+ return SendToListener(aRequest, pushBuffer);
+}
+
+void nsIndexedToHTML::FormatSizeString(int64_t inSize,
+ nsCString& outSizeString) {
+ outSizeString.Truncate();
+ if (inSize > int64_t(0)) {
+ // round up to the nearest Kilobyte
+ int64_t upperSize = (inSize + int64_t(1023)) / int64_t(1024);
+ outSizeString.AppendInt(upperSize);
+ outSizeString.AppendLiteral(" KB");
+ }
+}
diff --git a/netwerk/streamconv/converters/nsIndexedToHTML.h b/netwerk/streamconv/converters/nsIndexedToHTML.h
new file mode 100644
index 0000000000..736df3ebf7
--- /dev/null
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.h
@@ -0,0 +1,61 @@
+/* -*- 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 ____nsindexedtohtml___h___
+#define ____nsindexedtohtml___h___
+
+#include "nsCOMPtr.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsString.h"
+#include "nsIStreamConverter.h"
+#include "nsIDirIndexListener.h"
+
+#define NS_NSINDEXEDTOHTMLCONVERTER_CID \
+ { \
+ 0xcf0f71fd, 0xfafd, 0x4e2b, { \
+ 0x9f, 0xdc, 0x13, 0x4d, 0x97, 0x2e, 0x16, 0xe2 \
+ } \
+ }
+
+class nsIStringBundle;
+class nsITextToSubURI;
+
+class nsIndexedToHTML : public nsIStreamConverter, public nsIDirIndexListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMCONVERTER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSIDIRINDEXLISTENER
+
+ nsIndexedToHTML() = default;
+
+ nsresult Init(nsIStreamListener* aListener);
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ void FormatSizeString(int64_t inSize, nsCString& outSizeString);
+ nsresult SendToListener(nsIRequest* aRequest, const nsACString& aBuffer);
+ // Helper to properly implement OnStartRequest
+ nsresult DoOnStartRequest(nsIRequest* request, nsCString& aBuffer);
+
+ protected:
+ nsCOMPtr<nsIDirIndexParser> mParser;
+ nsCOMPtr<nsIStreamListener> mListener; // final listener (consumer)
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+
+ nsCOMPtr<nsITextToSubURI> mTextToSubURI;
+
+ private:
+ // Expecting absolute locations, given by 201 lines.
+ bool mExpectAbsLoc{false};
+
+ virtual ~nsIndexedToHTML() = default;
+};
+
+#endif
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.cpp b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
new file mode 100644
index 0000000000..fb21d6673f
--- /dev/null
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -0,0 +1,1051 @@
+/* -*- 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 "nsMultiMixedConv.h"
+#include "nsIHttpChannel.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsNetCID.h"
+#include "nsMimeTypes.h"
+#include "nsIStringStream.h"
+#include "nsCRT.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsURLHelper.h"
+#include "nsIStreamConverterService.h"
+#include <algorithm>
+#include "nsContentSecurityManager.h"
+#include "nsHttp.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsHttpHeaderArray.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Tokenizer.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/StaticPrefs_network.h"
+
+using namespace mozilla;
+
+nsPartChannel::nsPartChannel(nsIChannel* aMultipartChannel, uint32_t aPartID,
+ bool aIsFirstPart, nsIStreamListener* aListener)
+ : mMultipartChannel(aMultipartChannel),
+ mListener(aListener),
+ mPartID(aPartID),
+ mIsFirstPart(aIsFirstPart) {
+ // Inherit the load flags from the original channel...
+ mMultipartChannel->GetLoadFlags(&mLoadFlags);
+
+ mMultipartChannel->GetLoadGroup(getter_AddRefs(mLoadGroup));
+}
+
+void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd) {
+ mIsByteRangeRequest = true;
+
+ mByteRangeStart = aStart;
+ mByteRangeEnd = aEnd;
+}
+
+nsresult nsPartChannel::SendOnStartRequest(nsISupports* aContext) {
+ return mListener->OnStartRequest(this);
+}
+
+nsresult nsPartChannel::SendOnDataAvailable(nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aLen) {
+ return mListener->OnDataAvailable(this, aStream, aOffset, aLen);
+}
+
+nsresult nsPartChannel::SendOnStopRequest(nsISupports* aContext,
+ nsresult aStatus) {
+ // Drop the listener
+ nsCOMPtr<nsIStreamListener> listener;
+ listener.swap(mListener);
+ return listener->OnStopRequest(this, aStatus);
+}
+
+void nsPartChannel::SetContentDisposition(
+ const nsACString& aContentDispositionHeader) {
+ mContentDispositionHeader = aContentDispositionHeader;
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ NS_GetFilenameFromDisposition(mContentDispositionFilename,
+ mContentDispositionHeader);
+ mContentDisposition =
+ NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
+}
+
+//
+// nsISupports implementation...
+//
+
+NS_IMPL_ADDREF(nsPartChannel)
+NS_IMPL_RELEASE(nsPartChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsPartChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIByteRangeRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannel)
+NS_INTERFACE_MAP_END
+
+//
+// nsIRequest implementation...
+//
+
+NS_IMETHODIMP
+nsPartChannel::GetName(nsACString& aResult) {
+ return mMultipartChannel->GetName(aResult);
+}
+
+NS_IMETHODIMP
+nsPartChannel::IsPending(bool* aResult) {
+ // For now, consider the active lifetime of each part the same as
+ // the underlying multipart channel... This is not exactly right,
+ // but it is good enough :-)
+ return mMultipartChannel->IsPending(aResult);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetStatus(nsresult* aResult) {
+ nsresult rv = NS_OK;
+
+ if (NS_FAILED(mStatus)) {
+ *aResult = mStatus;
+ } else {
+ rv = mMultipartChannel->GetStatus(aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPartChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsPartChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsPartChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsPartChannel::Cancel(nsresult aStatus) {
+ // Cancelling an individual part must not cancel the underlying
+ // multipart channel...
+ // XXX but we should stop sending data for _this_ part channel!
+ mStatus = aStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetCanceled(bool* aCanceled) {
+ *aCanceled = NS_FAILED(mStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::Suspend(void) {
+ // Suspending an individual part must not suspend the underlying
+ // multipart channel...
+ // XXX why not?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::Resume(void) {
+ // Resuming an individual part must not resume the underlying
+ // multipart channel...
+ // XXX why not?
+ return NS_OK;
+}
+
+//
+// nsIChannel implementation
+//
+
+NS_IMETHODIMP
+nsPartChannel::GetOriginalURI(nsIURI** aURI) {
+ return mMultipartChannel->GetOriginalURI(aURI);
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetOriginalURI(nsIURI* aURI) {
+ return mMultipartChannel->SetOriginalURI(aURI);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetURI(nsIURI** aURI) { return mMultipartChannel->GetURI(aURI); }
+
+NS_IMETHODIMP
+nsPartChannel::Open(nsIInputStream** aStream) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This channel cannot be opened!
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPartChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This channel cannot be opened!
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ mLoadGroup = aLoadGroup;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetOwner(nsISupports** aOwner) {
+ return mMultipartChannel->GetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetOwner(nsISupports* aOwner) {
+ return mMultipartChannel->SetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ return mMultipartChannel->GetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ return mMultipartChannel->SetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ return mMultipartChannel->GetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ return mMultipartChannel->SetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ return mMultipartChannel->GetSecurityInfo(aSecurityInfo);
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetContentType(nsACString& aContentType) {
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetContentType(const nsACString& aContentType) {
+ bool dummy;
+ net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetContentCharset(nsACString& aContentCharset) {
+ aContentCharset = mContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetContentCharset(const nsACString& aContentCharset) {
+ mContentCharset = aContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetContentLength(int64_t* aContentLength) {
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetContentLength(int64_t aContentLength) {
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ *aContentDisposition = mContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ if (mContentDispositionFilename.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionFilename = mContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionHeader = mContentDispositionHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetPartID(uint32_t* aPartID) {
+ *aPartID = mPartID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetIsFirstPart(bool* aIsFirstPart) {
+ *aIsFirstPart = mIsFirstPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetIsLastPart(bool* aIsLastPart) {
+ *aIsLastPart = mIsLastPart;
+ return NS_OK;
+}
+
+//
+// nsIByteRangeRequest implementation...
+//
+
+NS_IMETHODIMP
+nsPartChannel::GetIsByteRangeRequest(bool* aIsByteRangeRequest) {
+ *aIsByteRangeRequest = mIsByteRangeRequest;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetStartRange(int64_t* aStartRange) {
+ *aStartRange = mByteRangeStart;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetEndRange(int64_t* aEndRange) {
+ *aEndRange = mByteRangeEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetBaseChannel(nsIChannel** aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ *aReturn = do_AddRef(mMultipartChannel).take();
+ return NS_OK;
+}
+
+// nsISupports implementation
+NS_IMPL_ISUPPORTS(nsMultiMixedConv, nsIStreamConverter, nsIStreamListener,
+ nsIThreadRetargetableStreamListener, nsIRequestObserver)
+
+// nsIStreamConverter implementation
+
+// No syncronous conversion at this time.
+NS_IMETHODIMP
+nsMultiMixedConv::Convert(nsIInputStream* aFromStream, const char* aFromType,
+ const char* aToType, nsISupports* aCtxt,
+ nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// Stream converter service calls this to initialize the actual stream converter
+// (us).
+NS_IMETHODIMP
+nsMultiMixedConv::AsyncConvertData(const char* aFromType, const char* aToType,
+ nsIStreamListener* aListener,
+ nsISupports* aCtxt) {
+ NS_ASSERTION(aListener && aFromType && aToType,
+ "null pointer passed into multi mixed converter");
+
+ // hook up our final listener. this guy gets the various On*() calls we want
+ // to throw at him.
+ //
+ // WARNING: this listener must be able to handle multiple OnStartRequest,
+ // OnDataAvail() and OnStopRequest() call combinations. We call of series
+ // of these for each sub-part in the raw stream.
+ mFinalListener = aListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiMixedConv::GetConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel, nsACString& aToType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIRequestObserver implementation
+NS_IMETHODIMP
+nsMultiMixedConv::OnStartRequest(nsIRequest* request) {
+ // we're assuming the content-type is available at this stage
+ NS_ASSERTION(mBoundary.IsEmpty(), "a second on start???");
+
+ nsresult rv;
+
+ mTotalSent = 0;
+ mChannel = do_QueryInterface(request, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString contentType;
+
+ // ask the HTTP channel for the content-type and extract the boundary from it.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = httpChannel->GetResponseHeader("content-type"_ns, contentType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCString csp;
+ rv = httpChannel->GetResponseHeader("content-security-policy"_ns, csp);
+ if (NS_SUCCEEDED(rv)) {
+ mRootContentSecurityPolicy = csp;
+ }
+ } else {
+ // try asking the channel directly
+ rv = mChannel->GetContentType(contentType);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ Tokenizer p(contentType);
+ p.SkipUntil(Token::Char(';'));
+ if (!p.CheckChar(';')) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ p.SkipWhites();
+ if (!p.CheckWord("boundary")) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ p.SkipWhites();
+ if (!p.CheckChar('=')) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ p.SkipWhites();
+ Unused << p.ReadUntil(Token::Char(';'), mBoundary);
+ mBoundary.Trim(
+ " \""); // ignoring potential quoted string formatting violations
+ if (mBoundary.IsEmpty()) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ mHeaderTokens[HEADER_CONTENT_TYPE] = mTokenizer.AddCustomToken(
+ "content-type", mTokenizer.CASE_INSENSITIVE, false);
+ mHeaderTokens[HEADER_CONTENT_LENGTH] = mTokenizer.AddCustomToken(
+ "content-length", mTokenizer.CASE_INSENSITIVE, false);
+ mHeaderTokens[HEADER_CONTENT_DISPOSITION] = mTokenizer.AddCustomToken(
+ "content-disposition", mTokenizer.CASE_INSENSITIVE, false);
+ mHeaderTokens[HEADER_SET_COOKIE] = mTokenizer.AddCustomToken(
+ "set-cookie", mTokenizer.CASE_INSENSITIVE, false);
+ mHeaderTokens[HEADER_CONTENT_RANGE] = mTokenizer.AddCustomToken(
+ "content-range", mTokenizer.CASE_INSENSITIVE, false);
+ mHeaderTokens[HEADER_RANGE] =
+ mTokenizer.AddCustomToken("range", mTokenizer.CASE_INSENSITIVE, false);
+ mHeaderTokens[HEADER_CONTENT_SECURITY_POLICY] = mTokenizer.AddCustomToken(
+ "content-security-policy", mTokenizer.CASE_INSENSITIVE, false);
+
+ mLFToken = mTokenizer.AddCustomToken("\n", mTokenizer.CASE_SENSITIVE, false);
+ mCRLFToken =
+ mTokenizer.AddCustomToken("\r\n", mTokenizer.CASE_SENSITIVE, false);
+
+ SwitchToControlParsing();
+
+ mBoundaryToken =
+ mTokenizer.AddCustomToken(mBoundary, mTokenizer.CASE_SENSITIVE);
+ mBoundaryTokenWithDashes =
+ mTokenizer.AddCustomToken("--"_ns + mBoundary, mTokenizer.CASE_SENSITIVE);
+
+ return NS_OK;
+}
+
+// nsIStreamListener implementation
+NS_IMETHODIMP
+nsMultiMixedConv::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ // Failing these assertions may indicate that some of the target listeners of
+ // this converter is looping the thead queue, which is harmful to how we
+ // collect the raw (content) data.
+ MOZ_DIAGNOSTIC_ASSERT(!mInOnDataAvailable,
+ "nsMultiMixedConv::OnDataAvailable reentered!");
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mRawData, "There are unsent data from the previous tokenizer feed!");
+
+ if (mInOnDataAvailable) {
+ // The multipart logic is incapable of being reentered.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mozilla::AutoRestore<bool> restore(mInOnDataAvailable);
+ mInOnDataAvailable = true;
+
+ nsresult rv_feed = mTokenizer.FeedInput(inStr, count);
+ // We must do this every time. Regardless if something has failed during the
+ // parsing process. Otherwise the raw data reference would not be thrown
+ // away.
+ nsresult rv_send = SendData();
+
+ return NS_FAILED(rv_send) ? rv_send : rv_feed;
+}
+
+NS_IMETHODIMP
+nsMultiMixedConv::OnDataFinished(nsresult aStatus) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMultiMixedConv::CheckListenerChain() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsMultiMixedConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ nsresult rv;
+
+ if (mPartChannel) {
+ mPartChannel->SetIsLastPart();
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mRawData, "There are unsent data from the previous tokenizer feed!");
+
+ rv = mTokenizer.FinishInput();
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = rv;
+ }
+ rv = SendData();
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = rv;
+ }
+
+ (void)SendStop(aStatus);
+ } else if (NS_FAILED(aStatus) && !mRequestListenerNotified) {
+ // underlying data production problem. we should not be in
+ // the middle of sending data. if we were, mPartChannel,
+ // above, would have been non-null.
+
+ (void)mFinalListener->OnStartRequest(request);
+ (void)mFinalListener->OnStopRequest(request, aStatus);
+ }
+
+ nsCOMPtr<nsIMultiPartChannelListener> multiListener =
+ do_QueryInterface(mFinalListener);
+ if (multiListener) {
+ multiListener->OnAfterLastPart(aStatus);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMultiMixedConv::ConsumeToken(Token const& token) {
+ nsresult rv;
+
+ switch (mParserState) {
+ case PREAMBLE:
+ if (token.Equals(mBoundaryTokenWithDashes)) {
+ // The server first used boundary '--boundary'. Hence, we no longer
+ // accept plain 'boundary' token as a delimiter.
+ mTokenizer.RemoveCustomToken(mBoundaryToken);
+ mParserState = BOUNDARY_CRLF;
+ break;
+ }
+ if (token.Equals(mBoundaryToken)) {
+ // And here the opposite from the just above block...
+ mTokenizer.RemoveCustomToken(mBoundaryTokenWithDashes);
+ mParserState = BOUNDARY_CRLF;
+ break;
+ }
+
+ // This is a preamble, just ignore it and wait for the boundary.
+ break;
+
+ case BOUNDARY_CRLF:
+ if (token.Equals(Token::NewLine())) {
+ mParserState = HEADER_NAME;
+ mResponseHeader = HEADER_UNKNOWN;
+ HeadersToDefault();
+ SetHeaderTokensEnabled(true);
+ break;
+ }
+ return NS_ERROR_CORRUPTED_CONTENT;
+
+ case HEADER_NAME:
+ SetHeaderTokensEnabled(false);
+ if (token.Equals(Token::NewLine())) {
+ mParserState = BODY_INIT;
+ SwitchToBodyParsing();
+ break;
+ }
+ for (uint32_t h = HEADER_CONTENT_TYPE; h < HEADER_UNKNOWN; ++h) {
+ if (token.Equals(mHeaderTokens[h])) {
+ mResponseHeader = static_cast<EHeader>(h);
+ break;
+ }
+ }
+ mParserState = HEADER_SEP;
+ break;
+
+ case HEADER_SEP:
+ if (token.Equals(Token::Char(':'))) {
+ mParserState = HEADER_VALUE;
+ mResponseHeaderValue.Truncate();
+ break;
+ }
+ if (mResponseHeader == HEADER_UNKNOWN) {
+ // If the header is not of any we understand, just pass everything till
+ // ':'
+ break;
+ }
+ if (token.Equals(Token::Whitespace())) {
+ // Accept only header-name traling whitespaces after known headers
+ break;
+ }
+ return NS_ERROR_CORRUPTED_CONTENT;
+
+ case HEADER_VALUE:
+ if (token.Equals(Token::Whitespace()) && mResponseHeaderValue.IsEmpty()) {
+ // Eat leading whitespaces
+ break;
+ }
+ if (token.Equals(Token::NewLine())) {
+ nsresult rv = ProcessHeader();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mParserState = HEADER_NAME;
+ mResponseHeader = HEADER_UNKNOWN;
+ SetHeaderTokensEnabled(true);
+ } else {
+ mResponseHeaderValue.Append(token.Fragment());
+ }
+ break;
+
+ case BODY_INIT:
+ rv = SendStart();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mParserState = BODY;
+ [[fallthrough]];
+
+ case BODY: {
+ if (!token.Equals(mLFToken) && !token.Equals(mCRLFToken)) {
+ if (token.Equals(mBoundaryTokenWithDashes) ||
+ token.Equals(mBoundaryToken)) {
+ // Allow CRLF to NOT be part of the boundary as well
+ SwitchToControlParsing();
+ mParserState = TRAIL_DASH1;
+ break;
+ }
+ AccumulateData(token);
+ break;
+ }
+
+ // After CRLF we must explicitly check for boundary. If found,
+ // that CRLF is part of the boundary and must not be send to the
+ // data listener.
+ Token token2;
+ if (!mTokenizer.Next(token2)) {
+ // Note: this will give us the CRLF token again when more data
+ // or OnStopRequest arrive. I.e. we will enter BODY case in
+ // the very same state as we are now and start this block over.
+ mTokenizer.NeedMoreInput();
+ break;
+ }
+ if (token2.Equals(mBoundaryTokenWithDashes) ||
+ token2.Equals(mBoundaryToken)) {
+ SwitchToControlParsing();
+ mParserState = TRAIL_DASH1;
+ break;
+ }
+
+ AccumulateData(token);
+ AccumulateData(token2);
+ break;
+ }
+
+ case TRAIL_DASH1:
+ if (token.Equals(Token::NewLine())) {
+ rv = SendStop(NS_OK);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mParserState = BOUNDARY_CRLF;
+ mTokenizer.Rollback();
+ break;
+ }
+ if (token.Equals(Token::Char('-'))) {
+ mParserState = TRAIL_DASH2;
+ break;
+ }
+ return NS_ERROR_CORRUPTED_CONTENT;
+
+ case TRAIL_DASH2:
+ if (token.Equals(Token::Char('-'))) {
+ mPartChannel->SetIsLastPart();
+ // SendStop calls SendData first.
+ rv = SendStop(NS_OK);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mParserState = EPILOGUE;
+ break;
+ }
+ return NS_ERROR_CORRUPTED_CONTENT;
+
+ case EPILOGUE:
+ // Just ignore
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Missing parser state handling branch");
+ break;
+ } // switch
+
+ return NS_OK;
+}
+
+void nsMultiMixedConv::SetHeaderTokensEnabled(bool aEnable) {
+ for (uint32_t h = HEADER_FIRST; h < HEADER_UNKNOWN; ++h) {
+ mTokenizer.EnableCustomToken(mHeaderTokens[h], aEnable);
+ }
+}
+
+void nsMultiMixedConv::SwitchToBodyParsing() {
+ mTokenizer.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+ mTokenizer.EnableCustomToken(mLFToken, true);
+ mTokenizer.EnableCustomToken(mCRLFToken, true);
+ mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, true);
+ mTokenizer.EnableCustomToken(mBoundaryToken, true);
+}
+
+void nsMultiMixedConv::SwitchToControlParsing() {
+ mTokenizer.SetTokenizingMode(Tokenizer::Mode::FULL);
+ mTokenizer.EnableCustomToken(mLFToken, false);
+ mTokenizer.EnableCustomToken(mCRLFToken, false);
+ mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, false);
+ mTokenizer.EnableCustomToken(mBoundaryToken, false);
+}
+
+// nsMultiMixedConv methods
+nsMultiMixedConv::nsMultiMixedConv()
+ // XXX: This is a hack to bypass the raw pointer to refcounted object in
+ // lambda analysis. It should be removed and replaced when the
+ // IncrementalTokenizer API is improved to avoid the need for such
+ // workarounds.
+ //
+ // This is safe because `mTokenizer` will not outlive `this`, meaning
+ // that this std::bind object will be destroyed before `this` dies.
+ : mTokenizer(std::bind(&nsMultiMixedConv::ConsumeToken, this,
+ std::placeholders::_1)) {}
+
+nsresult nsMultiMixedConv::SendStart() {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIStreamListener> partListener(mFinalListener);
+ if (mContentType.IsEmpty()) {
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ nsCOMPtr<nsIStreamConverterService> serv =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mFinalListener,
+ mContext, getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ partListener = converter;
+ }
+ }
+ }
+
+ // if we already have an mPartChannel, that means we never sent a Stop()
+ // before starting up another "part." that would be bad.
+ MOZ_ASSERT(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel");
+
+ nsPartChannel* newChannel;
+ newChannel = new nsPartChannel(mChannel, mCurrentPartID, mCurrentPartID == 0,
+ partListener);
+
+ ++mCurrentPartID;
+
+ if (mIsByteRangeRequest) {
+ newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd);
+ }
+
+ mTotalSent = 0;
+
+ // Set up the new part channel...
+ mPartChannel = newChannel;
+
+ rv = mPartChannel->SetContentType(mContentType);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mPartChannel->SetContentLength(mContentLength);
+ if (NS_FAILED(rv)) return rv;
+
+ mPartChannel->SetContentDisposition(mContentDisposition);
+
+ // Each part of a multipart/replace response can be used
+ // for the top level document. We must inform upper layers
+ // about this by setting the LOAD_REPLACE flag so that certain
+ // state assertions are evaluated as positive.
+ nsLoadFlags loadFlags = 0;
+ mPartChannel->GetLoadFlags(&loadFlags);
+ loadFlags |= nsIChannel::LOAD_REPLACE;
+ mPartChannel->SetLoadFlags(loadFlags);
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // Add the new channel to the load group (if any)
+ if (loadGroup) {
+ rv = loadGroup->AddRequest(mPartChannel, nullptr);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // This prevents artificial call to OnStart/StopRequest when the root
+ // channel fails. Since now it's ensured to keep with the nsIStreamListener
+ // contract every time.
+ mRequestListenerNotified = true;
+
+ // Let's start off the load. NOTE: we don't forward on the channel passed
+ // into our OnDataAvailable() as it's the root channel for the raw stream.
+ return mPartChannel->SendOnStartRequest(mContext);
+}
+
+nsresult nsMultiMixedConv::SendStop(nsresult aStatus) {
+ // Make sure we send out all accumulcated data prior call to OnStopRequest.
+ // If there is no data, this is a no-op.
+ nsresult rv = SendData();
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = rv;
+ }
+ if (mPartChannel) {
+ rv = mPartChannel->SendOnStopRequest(mContext, aStatus);
+ // don't check for failure here, we need to remove the channel from
+ // the loadgroup.
+
+ // Remove the channel from its load group (if any)
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ (void)loadGroup->RemoveRequest(mPartChannel, mContext, aStatus);
+ }
+ }
+
+ mPartChannel = nullptr;
+ return rv;
+}
+
+void nsMultiMixedConv::AccumulateData(Token const& aToken) {
+ if (!mRawData) {
+ // This is the first read of raw data during this FeedInput loop
+ // of the incremental tokenizer. All 'raw' tokens are coming from
+ // the same linear buffer, hence begining of this loop raw data
+ // is begining of the first raw token. Length of this loop raw
+ // data is just sum of all 'raw' tokens we collect during this loop.
+ //
+ // It's ensured we flush (send to to the listener via OnDataAvailable)
+ // and nullify the collected raw data right after FeedInput call.
+ // Hence, the reference can't outlive the actual buffer.
+ mRawData = aToken.Fragment().BeginReading();
+ mRawDataLength = 0;
+ }
+
+ mRawDataLength += aToken.Fragment().Length();
+}
+
+nsresult nsMultiMixedConv::SendData() {
+ nsresult rv;
+
+ if (!mRawData) {
+ return NS_OK;
+ }
+
+ nsACString::const_char_iterator rawData = mRawData;
+ mRawData = nullptr;
+
+ if (!mPartChannel) {
+ return NS_ERROR_FAILURE; // something went wrong w/ processing
+ }
+
+ if (mContentLength != UINT64_MAX) {
+ // make sure that we don't send more than the mContentLength
+ // XXX why? perhaps the Content-Length header was actually wrong!!
+ if ((uint64_t(mRawDataLength) + mTotalSent) > mContentLength) {
+ mRawDataLength = static_cast<uint32_t>(mContentLength - mTotalSent);
+ }
+
+ if (mRawDataLength == 0) return NS_OK;
+ }
+
+ uint64_t offset = mTotalSent;
+ mTotalSent += mRawDataLength;
+
+ nsCOMPtr<nsIStringInputStream> ss(
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ss->ShareData(rawData, mRawDataLength);
+ mRawData = nullptr;
+ if (NS_FAILED(rv)) return rv;
+
+ return mPartChannel->SendOnDataAvailable(mContext, ss, offset,
+ mRawDataLength);
+}
+
+void nsMultiMixedConv::HeadersToDefault() {
+ mContentLength = UINT64_MAX;
+ mContentType.Truncate();
+ mContentDisposition.Truncate();
+ mContentSecurityPolicy.Truncate();
+ mIsByteRangeRequest = false;
+}
+
+nsresult nsMultiMixedConv::ProcessHeader() {
+ mozilla::Tokenizer p(mResponseHeaderValue);
+
+ switch (mResponseHeader) {
+ case HEADER_CONTENT_TYPE:
+ mContentType = mResponseHeaderValue;
+ mContentType.CompressWhitespace();
+ break;
+ case HEADER_CONTENT_LENGTH:
+ p.SkipWhites();
+ if (!p.ReadInteger(&mContentLength)) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ break;
+ case HEADER_CONTENT_DISPOSITION:
+ mContentDisposition = mResponseHeaderValue;
+ mContentDisposition.CompressWhitespace();
+ break;
+ case HEADER_SET_COOKIE: {
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal =
+ do_QueryInterface(mChannel);
+ mResponseHeaderValue.CompressWhitespace();
+ if (!StaticPrefs::network_cookie_prevent_set_cookie_from_multipart() &&
+ httpInternal) {
+ DebugOnly<nsresult> rv = httpInternal->SetCookie(mResponseHeaderValue);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ break;
+ }
+ case HEADER_RANGE:
+ case HEADER_CONTENT_RANGE: {
+ if (!p.CheckWord("bytes") || !p.CheckWhite()) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ p.SkipWhites();
+ if (p.CheckChar('*')) {
+ mByteRangeStart = mByteRangeEnd = 0;
+ } else if (!p.ReadInteger(&mByteRangeStart) || !p.CheckChar('-') ||
+ !p.ReadInteger(&mByteRangeEnd)) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ mIsByteRangeRequest = true;
+ if (mContentLength == UINT64_MAX) {
+ mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1);
+ }
+ break;
+ }
+ case HEADER_CONTENT_SECURITY_POLICY: {
+ mContentSecurityPolicy = mResponseHeaderValue;
+ mContentSecurityPolicy.CompressWhitespace();
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ nsCString resultCSP = mRootContentSecurityPolicy;
+ if (!mContentSecurityPolicy.IsEmpty()) {
+ // We are updating the root channel CSP header respectively for
+ // each part as: CSP-root + CSP-partN, where N is the part number.
+ // Here we append current part's CSP to root CSP and reset CSP
+ // header for each part.
+ if (!resultCSP.IsEmpty()) {
+ resultCSP.Append(";");
+ }
+ resultCSP.Append(mContentSecurityPolicy);
+ }
+ nsresult rv = httpChannel->SetResponseHeader(
+ "Content-Security-Policy"_ns, resultCSP, false);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ }
+ break;
+ }
+ case HEADER_UNKNOWN:
+ // We ignore anything else...
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv) {
+ MOZ_ASSERT(aMultiMixedConv != nullptr, "null ptr");
+
+ RefPtr<nsMultiMixedConv> conv = new nsMultiMixedConv();
+ conv.forget(aMultiMixedConv);
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.h b/netwerk/streamconv/converters/nsMultiMixedConv.h
new file mode 100644
index 0000000000..4f773a69eb
--- /dev/null
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.h
@@ -0,0 +1,259 @@
+/* -*- 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 __nsmultimixedconv__h__
+#define __nsmultimixedconv__h__
+
+#include "nsIStreamConverter.h"
+#include "nsIChannel.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIByteRangeRequest.h"
+#include "nsIMultiPartChannel.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/IncrementalTokenizer.h"
+#include "nsHttpResponseHead.h"
+#include "mozilla/UniquePtr.h"
+
+#define NS_MULTIMIXEDCONVERTER_CID \
+ { /* 7584CE90-5B25-11d3-A175-0050041CAF44 */ \
+ 0x7584ce90, 0x5b25, 0x11d3, { \
+ 0xa1, 0x75, 0x0, 0x50, 0x4, 0x1c, 0xaf, 0x44 \
+ } \
+ }
+
+//
+// nsPartChannel is a "dummy" channel which represents an individual part of
+// a multipart/mixed stream...
+//
+// Instances on this channel are passed out to the consumer through the
+// nsIStreamListener interface.
+//
+class nsPartChannel final : public nsIChannel,
+ public nsIByteRangeRequest,
+ public nsIMultiPartChannel {
+ public:
+ nsPartChannel(nsIChannel* aMultipartChannel, uint32_t aPartID,
+ bool aIsFirstPart, nsIStreamListener* aListener);
+
+ void InitializeByteRange(int64_t aStart, int64_t aEnd);
+ void SetIsLastPart() { mIsLastPart = true; }
+ nsresult SendOnStartRequest(nsISupports* aContext);
+ nsresult SendOnDataAvailable(nsISupports* aContext, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aLen);
+ nsresult SendOnStopRequest(nsISupports* aContext, nsresult aStatus);
+ /* SetContentDisposition expects the full value of the Content-Disposition
+ * header */
+ void SetContentDisposition(const nsACString& aContentDispositionHeader);
+ // TODO(ER): This appears to be dead code
+ void SetResponseHead(mozilla::net::nsHttpResponseHead* head) {
+ mResponseHead.reset(head);
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIBYTERANGEREQUEST
+ NS_DECL_NSIMULTIPARTCHANNEL
+
+ protected:
+ ~nsPartChannel() = default;
+
+ protected:
+ nsCOMPtr<nsIChannel> mMultipartChannel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ mozilla::UniquePtr<mozilla::net::nsHttpResponseHead> mResponseHead;
+
+ nsresult mStatus{NS_OK};
+ nsLoadFlags mLoadFlags{0};
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ nsCString mContentType;
+ nsCString mContentCharset;
+ uint32_t mContentDisposition{0};
+ nsString mContentDispositionFilename;
+ nsCString mContentDispositionHeader;
+ uint64_t mContentLength{UINT64_MAX};
+
+ bool mIsByteRangeRequest{false};
+ int64_t mByteRangeStart{0};
+ int64_t mByteRangeEnd{0};
+
+ uint32_t mPartID; // unique ID that can be used to identify
+ // this part of the multipart document
+ bool mIsFirstPart;
+ bool mIsLastPart{false};
+};
+
+// The nsMultiMixedConv stream converter converts a stream of type
+// "multipart/x-mixed-replace" to it's subparts. There was some debate as to
+// whether or not the functionality desired when HTTP confronted this type
+// required a stream converter. After all, this type really prompts various
+// viewer related actions rather than stream conversion. There simply needs to
+// be a piece in place that can strip out the multiple parts of a stream of this
+// type, and "display" them accordingly.
+//
+// With that said, this "stream converter" spends more time packaging up the sub
+// parts of the main stream and sending them off the destination stream
+// listener, than doing any real stream parsing/converting.
+//
+// WARNING: This converter requires that it's destination stream listener be
+// able to handle multiple OnStartRequest(), OnDataAvailable(), and
+// OnStopRequest() call combinations. Each series represents the beginning,
+// data production, and ending phase of each sub- part of the original
+// stream.
+//
+// NOTE: this MIME-type is used by HTTP, *not* SMTP, or IMAP.
+//
+// NOTE: For reference, a general description of how this MIME type should be
+// handled via HTTP, see
+// http://home.netscape.com/assist/net_sites/pushpull.html . Note that real
+// world server content deviates considerably from this overview.
+//
+// Implementation assumptions:
+// Assumed structue:
+// --BoundaryToken[\r]\n
+// content-type: foo/bar[\r]\n
+// ... (other headers if any)
+// [\r]\n (second line feed to delimit end of headers)
+// data
+// --BoundaryToken-- (end delimited by final "--")
+//
+// linebreaks can be either CRLF or LFLF. linebreaks preceding
+// boundary tokens are NOT considered part of the data. BoundaryToken
+// is any opaque string.
+//
+//
+
+class nsMultiMixedConv : public nsIStreamConverter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMCONVERTER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ explicit nsMultiMixedConv();
+
+ protected:
+ using Token = mozilla::IncrementalTokenizer::Token;
+
+ virtual ~nsMultiMixedConv() = default;
+
+ nsresult SendStart();
+ void AccumulateData(Token const& aToken);
+ nsresult SendData();
+ nsresult SendStop(nsresult aStatus);
+
+ // member data
+ nsCOMPtr<nsIStreamListener> mFinalListener; // this guy gets the converted
+ // data via his OnDataAvailable()
+
+ nsCOMPtr<nsIChannel>
+ mChannel; // The channel as we get in in OnStartRequest call
+ RefPtr<nsPartChannel> mPartChannel; // the channel for the given part we're
+ // processing. one channel per part.
+ nsCOMPtr<nsISupports> mContext;
+ nsCString mContentType;
+ nsCString mContentDisposition;
+ nsCString mContentSecurityPolicy;
+ nsCString mRootContentSecurityPolicy;
+ uint64_t mContentLength{UINT64_MAX};
+ uint64_t mTotalSent{0};
+
+ // The following members are for tracking the byte ranges in
+ // multipart/mixed content which specified the 'Content-Range:'
+ // header...
+ int64_t mByteRangeStart{0};
+ int64_t mByteRangeEnd{0};
+ bool mIsByteRangeRequest{false};
+ // This flag is set first time we create a part channel.
+ // We use it to prevent duplicated OnStopRequest call on the listener
+ // when we fail from some reason to ever create a part channel that
+ // ensures correct notifications.
+ bool mRequestListenerNotified{false};
+
+ uint32_t mCurrentPartID{0};
+
+ // Flag preventing reenter of OnDataAvailable in case the target listener
+ // ends up spinning the event loop.
+ bool mInOnDataAvailable{false};
+
+ // Current state of the incremental parser
+ enum EParserState {
+ PREAMBLE,
+ BOUNDARY_CRLF,
+ HEADER_NAME,
+ HEADER_SEP,
+ HEADER_VALUE,
+ BODY_INIT,
+ BODY,
+ TRAIL_DASH1,
+ TRAIL_DASH2,
+ EPILOGUE,
+
+ INIT = PREAMBLE
+ } mParserState{INIT};
+
+ // Response part header value, valid when we find a header name
+ // we recognize.
+ enum EHeader : uint32_t {
+ HEADER_FIRST,
+ HEADER_CONTENT_TYPE = HEADER_FIRST,
+ HEADER_CONTENT_LENGTH,
+ HEADER_CONTENT_DISPOSITION,
+ HEADER_SET_COOKIE,
+ HEADER_CONTENT_RANGE,
+ HEADER_RANGE,
+ HEADER_CONTENT_SECURITY_POLICY,
+ HEADER_UNKNOWN
+ } mResponseHeader{HEADER_UNKNOWN};
+ // Cumulated value of a response header.
+ nsCString mResponseHeaderValue;
+
+ nsCString mBoundary;
+ mozilla::IncrementalTokenizer mTokenizer;
+
+ // When in the "body parsing" mode, see below, we cumulate raw data
+ // incrementally to mainly avoid any unnecessary granularity.
+ // mRawData points to the first byte in the tokenizer buffer where part
+ // body data begins or continues. mRawDataLength is a cumulated length
+ // of that data during a single tokenizer input feed. This is always
+ // flushed right after we fed the tokenizer.
+ nsACString::const_char_iterator mRawData{nullptr};
+ nsACString::size_type mRawDataLength{0};
+
+ // At the start we don't know if the server will be sending boundary with
+ // or without the leading dashes.
+ Token mBoundaryToken;
+ Token mBoundaryTokenWithDashes;
+ // We need these custom tokens to allow finding CRLF when in the binary mode.
+ // CRLF before boundary is considered part of the boundary and not part of
+ // the data.
+ Token mLFToken;
+ Token mCRLFToken;
+ // Custom tokens for each of the response headers we recognize.
+ Token mHeaderTokens[HEADER_UNKNOWN];
+
+ // Resets values driven by part headers, like content type, to their defaults,
+ // called at the start of every part processing.
+ void HeadersToDefault();
+ // Processes captured value of mResponseHeader header.
+ nsresult ProcessHeader();
+ // Switches the parser and tokenizer state to "binary mode" which only
+ // searches for the 'CRLF boundary' delimiter.
+ void SwitchToBodyParsing();
+ // Switches to the default mode, we are in this mode when parsing headers and
+ // control data around the boundary delimiters.
+ void SwitchToControlParsing();
+ // Turns on or off recognition of the headers we recognize in part heads.
+ void SetHeaderTokensEnabled(bool aEnable);
+
+ // The main parser callback called by the IncrementalTokenizer
+ // instance from OnDataAvailable or OnStopRequest.
+ nsresult ConsumeToken(Token const& token);
+};
+
+#endif /* __nsmultimixedconv__h__ */
diff --git a/netwerk/streamconv/converters/nsUnknownDecoder.cpp b/netwerk/streamconv/converters/nsUnknownDecoder.cpp
new file mode 100644
index 0000000000..b572b1c276
--- /dev/null
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.cpp
@@ -0,0 +1,867 @@
+/* -*- 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 "nsUnknownDecoder.h"
+#include "nsIPipe.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsMimeTypes.h"
+
+#include "nsCRT.h"
+
+#include "nsIMIMEService.h"
+
+#include "nsIViewSourceChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIEncodedChannel.h"
+#include "nsIURI.h"
+#include "nsStringStream.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include <algorithm>
+
+#define MAX_BUFFER_SIZE 512u
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsUnknownDecoder::ConvertedStreamListener, nsIStreamListener,
+ nsIRequestObserver)
+
+nsUnknownDecoder::ConvertedStreamListener::ConvertedStreamListener(
+ nsUnknownDecoder* aDecoder) {
+ mDecoder = aDecoder;
+}
+
+nsresult nsUnknownDecoder::ConvertedStreamListener::AppendDataToString(
+ nsIInputStream* inputStream, void* closure, const char* rawSegment,
+ uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
+ nsCString* decodedData = static_cast<nsCString*>(closure);
+ decodedData->Append(rawSegment, count);
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::ConvertedStreamListener::OnStartRequest(nsIRequest* request) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::ConvertedStreamListener::OnDataAvailable(
+ nsIRequest* request, nsIInputStream* stream, uint64_t offset,
+ uint32_t count) {
+ uint32_t read;
+ nsAutoCString decodedData;
+ {
+ MutexAutoLock lock(mDecoder->mMutex);
+ decodedData = mDecoder->mDecodedData;
+ }
+ nsresult rv =
+ stream->ReadSegments(AppendDataToString, &decodedData, count, &read);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MutexAutoLock lock(mDecoder->mMutex);
+ mDecoder->mDecodedData = decodedData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::ConvertedStreamListener::OnStopRequest(nsIRequest* request,
+ nsresult status) {
+ return NS_OK;
+}
+
+nsUnknownDecoder::nsUnknownDecoder(nsIStreamListener* aListener)
+ : mNextListener(aListener),
+ mBuffer(nullptr),
+ mBufferLen(0),
+ mMutex("nsUnknownDecoder"),
+ mDecodedData("") {}
+
+nsUnknownDecoder::~nsUnknownDecoder() {
+ if (mBuffer) {
+ delete[] mBuffer;
+ mBuffer = nullptr;
+ }
+}
+
+// ----
+//
+// nsISupports implementation...
+//
+// ----
+
+NS_IMPL_ADDREF(nsUnknownDecoder)
+NS_IMPL_RELEASE(nsUnknownDecoder)
+
+NS_INTERFACE_MAP_BEGIN(nsUnknownDecoder)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamConverter)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIContentSniffer)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,
+ nsIThreadRetargetableStreamListener)
+NS_INTERFACE_MAP_END
+
+// ----
+//
+// nsIStreamConverter methods...
+//
+// ----
+
+NS_IMETHODIMP
+nsUnknownDecoder::Convert(nsIInputStream* aFromStream, const char* aFromType,
+ const char* aToType, nsISupports* aCtxt,
+ nsIInputStream** aResultStream) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::AsyncConvertData(const char* aFromType, const char* aToType,
+ nsIStreamListener* aListener,
+ nsISupports* aCtxt) {
+ NS_ASSERTION(aListener && aFromType && aToType,
+ "null pointer passed into multi mixed converter");
+ // hook up our final listener. this guy gets the various On*() calls we want
+ // to throw at him.
+ //
+
+ MutexAutoLock lock(mMutex);
+ mNextListener = aListener;
+ return (aListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::GetConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel, nsACString& aToType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// ----
+//
+// nsIStreamListener methods...
+//
+// ----
+
+NS_IMETHODIMP
+nsUnknownDecoder::OnDataAvailable(nsIRequest* request, nsIInputStream* aStream,
+ uint64_t aSourceOffset, uint32_t aCount) {
+ nsresult rv = NS_OK;
+
+ bool contentTypeEmpty;
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mNextListener) return NS_ERROR_FAILURE;
+
+ contentTypeEmpty = mContentType.IsEmpty();
+ }
+
+ if (contentTypeEmpty) {
+ uint32_t count, len;
+
+ // If the buffer has not been allocated by now, just fail...
+ if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY;
+
+ //
+ // Determine how much of the stream should be read to fill up the
+ // sniffer buffer...
+ //
+ if (mBufferLen + aCount >= MAX_BUFFER_SIZE) {
+ count = MAX_BUFFER_SIZE - mBufferLen;
+ } else {
+ count = aCount;
+ }
+
+ // Read the data into the buffer...
+ rv = aStream->Read((mBuffer + mBufferLen), count, &len);
+ if (NS_FAILED(rv)) return rv;
+
+ mBufferLen += len;
+ aCount -= len;
+
+ if (aCount) {
+ //
+ // Adjust the source offset... The call to FireListenerNotifications(...)
+ // will make the first OnDataAvailable(...) call with an offset of 0.
+ // So, this offset needs to be adjusted to reflect that...
+ //
+ aSourceOffset += mBufferLen;
+
+ DetermineContentType(request);
+
+ rv = FireListenerNotifications(request, nullptr);
+ }
+ }
+
+ // Must not fire ODA again if it failed once
+ if (aCount && NS_SUCCEEDED(rv)) {
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+ }
+#endif
+
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mNextListener;
+ }
+ rv = listener->OnDataAvailable(request, aStream, aSourceOffset, aCount);
+ }
+
+ return rv;
+}
+
+// ----
+//
+// nsIRequestObserver methods...
+//
+// ----
+
+NS_IMETHODIMP
+nsUnknownDecoder::OnStartRequest(nsIRequest* request) {
+ nsresult rv = NS_OK;
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mNextListener) return NS_ERROR_FAILURE;
+ }
+
+ // Allocate the sniffer buffer...
+ if (NS_SUCCEEDED(rv) && !mBuffer) {
+ mBuffer = new char[MAX_BUFFER_SIZE];
+
+ if (!mBuffer) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // Do not pass the OnStartRequest on to the next listener (yet)...
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ nsresult rv = NS_OK;
+
+ bool contentTypeEmpty;
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mNextListener) return NS_ERROR_FAILURE;
+
+ contentTypeEmpty = mContentType.IsEmpty();
+ }
+
+ //
+ // The total amount of data is less than the size of the sniffer buffer.
+ // Analyze the buffer now...
+ //
+ if (contentTypeEmpty) {
+ DetermineContentType(request);
+
+ // Make sure channel listeners see channel as pending while we call
+ // OnStartRequest/OnDataAvailable, even though the underlying channel
+ // has already hit OnStopRequest.
+ nsCOMPtr<nsIForcePendingChannel> forcePendingChannel =
+ do_QueryInterface(request);
+ if (forcePendingChannel) {
+ forcePendingChannel->ForcePending(true);
+ }
+
+ rv = FireListenerNotifications(request, nullptr);
+
+ if (NS_FAILED(rv)) {
+ aStatus = rv;
+ }
+
+ // now we need to set pending state to false before calling OnStopRequest
+ if (forcePendingChannel) {
+ forcePendingChannel->ForcePending(false);
+ }
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mNextListener;
+ mNextListener = nullptr;
+ }
+ rv = listener->OnStopRequest(request, aStatus);
+
+ return rv;
+}
+
+// ----
+//
+// nsIContentSniffer methods...
+//
+// ----
+NS_IMETHODIMP
+nsUnknownDecoder::GetMIMETypeFromContent(nsIRequest* aRequest,
+ const uint8_t* aData, uint32_t aLength,
+ nsACString& type) {
+ // This is only used by sniffer, therefore we do not need to lock anything
+ // here.
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (loadInfo->GetSkipContentSniffing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ mBuffer = const_cast<char*>(reinterpret_cast<const char*>(aData));
+ mBufferLen = aLength;
+ DetermineContentType(aRequest);
+ mBuffer = nullptr;
+ mBufferLen = 0;
+ type.Assign(mContentType);
+ mContentType.Truncate();
+ return type.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+}
+
+// Actual sniffing code
+
+/**
+ * This is the array of sniffer entries that depend on "magic numbers"
+ * in the file. Each entry has either a type associated with it (set
+ * these with the SNIFFER_ENTRY macro) or a function to be executed
+ * (set these with the SNIFFER_ENTRY_WITH_FUNC macro). The function
+ * should take a single nsIRequest* and returns bool -- true if
+ * it sets mContentType, false otherwise
+ */
+nsUnknownDecoder::nsSnifferEntry nsUnknownDecoder::sSnifferEntries[] = {
+ SNIFFER_ENTRY("%PDF-", APPLICATION_PDF),
+
+ SNIFFER_ENTRY("%!PS-Adobe-", APPLICATION_POSTSCRIPT),
+
+ // Files that start with mailbox delimiters let's provisionally call
+ // text/plain
+ SNIFFER_ENTRY("From", TEXT_PLAIN), SNIFFER_ENTRY(">From", TEXT_PLAIN),
+
+ // If the buffer begins with "#!" or "%!" then it is a script of
+ // some sort... "Scripts" can include arbitrary data to be passed
+ // to an interpreter, so we need to decide whether we can call this
+ // text or whether it's data.
+ SNIFFER_ENTRY_WITH_FUNC("#!", &nsUnknownDecoder::LastDitchSniff),
+
+ // XXXbz should (and can) we also include the various ways that <?xml can
+ // appear as UTF-16 and such? See http://www.w3.org/TR/REC-xml#sec-guessing
+ SNIFFER_ENTRY_WITH_FUNC("<?xml", &nsUnknownDecoder::SniffForXML)};
+
+uint32_t nsUnknownDecoder::sSnifferEntryNum =
+ sizeof(nsUnknownDecoder::sSnifferEntries) /
+ sizeof(nsUnknownDecoder::nsSnifferEntry);
+
+void nsUnknownDecoder::DetermineContentType(nsIRequest* aRequest) {
+ {
+ MutexAutoLock lock(mMutex);
+ NS_ASSERTION(mContentType.IsEmpty(), "Content type is already known.");
+ if (!mContentType.IsEmpty()) return;
+ }
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (loadInfo->GetSkipContentSniffing()) {
+ /*
+ * If we did not get a useful Content-Type from the server
+ * but also have sniffing disabled, just determine whether
+ * to use text/plain or octetstream and log an error to the Console
+ */
+ LastDitchSniff(aRequest);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
+ if (httpChannel) {
+ nsAutoCString type;
+ httpChannel->GetContentType(type);
+ nsCOMPtr<nsIURI> requestUri;
+ httpChannel->GetURI(getter_AddRefs(requestUri));
+ nsAutoCString spec;
+ requestUri->GetSpec(spec);
+ if (spec.Length() > 50) {
+ spec.Truncate(50);
+ spec.AppendLiteral("...");
+ }
+ httpChannel->LogMimeTypeMismatch(
+ "XTCOWithMIMEValueMissing"_ns, false, NS_ConvertUTF8toUTF16(spec),
+ // Type is not used in the Error Message but required
+ NS_ConvertUTF8toUTF16(type));
+ }
+ return;
+ }
+ }
+
+ const char* testData = mBuffer;
+ uint32_t testDataLen = mBufferLen;
+ // Check if data are compressed.
+ nsAutoCString decodedData;
+
+ if (channel) {
+ // ConvertEncodedData is always called only on a single thread for each
+ // instance of an object.
+ nsresult rv = ConvertEncodedData(aRequest, mBuffer, mBufferLen);
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock lock(mMutex);
+ decodedData = mDecodedData;
+ }
+ if (!decodedData.IsEmpty()) {
+ testData = decodedData.get();
+ testDataLen = std::min<uint32_t>(decodedData.Length(), MAX_BUFFER_SIZE);
+ }
+ }
+
+ // First, run through all the types we can detect reliably based on
+ // magic numbers
+ uint32_t i;
+ for (i = 0; i < sSnifferEntryNum; ++i) {
+ if (testDataLen >= sSnifferEntries[i].mByteLen && // enough data
+ memcmp(testData, sSnifferEntries[i].mBytes,
+ sSnifferEntries[i].mByteLen) == 0) { // and type matches
+ NS_ASSERTION(
+ sSnifferEntries[i].mMimeType ||
+ sSnifferEntries[i].mContentTypeSniffer,
+ "Must have either a type string or a function to set the type");
+ NS_ASSERTION(!sSnifferEntries[i].mMimeType ||
+ !sSnifferEntries[i].mContentTypeSniffer,
+ "Both a type string and a type sniffing function set;"
+ " using type string");
+ if (sSnifferEntries[i].mMimeType) {
+ MutexAutoLock lock(mMutex);
+ mContentType = sSnifferEntries[i].mMimeType;
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+ return;
+ }
+ if ((this->*(sSnifferEntries[i].mContentTypeSniffer))(aRequest)) {
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+#endif
+ return;
+ }
+ }
+ }
+
+ nsAutoCString sniffedType;
+ NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest, (const uint8_t*)testData,
+ testDataLen, sniffedType);
+ {
+ MutexAutoLock lock(mMutex);
+ mContentType = sniffedType;
+ if (!mContentType.IsEmpty()) {
+ return;
+ }
+ }
+
+ if (SniffForHTML(aRequest)) {
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+#endif
+ return;
+ }
+
+ // We don't know what this is yet. Before we just give up, try
+ // the URI from the request.
+ if (SniffURI(aRequest)) {
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+#endif
+ return;
+ }
+
+ LastDitchSniff(aRequest);
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+ NS_ASSERTION(!mContentType.IsEmpty(), "Content type should be known by now.");
+#endif
+}
+
+bool nsUnknownDecoder::SniffForHTML(nsIRequest* aRequest) {
+ MutexAutoLock lock(mMutex);
+
+ // Now look for HTML.
+ const char* str;
+ const char* end;
+ if (mDecodedData.IsEmpty()) {
+ str = mBuffer;
+ end = mBuffer + mBufferLen;
+ } else {
+ str = mDecodedData.get();
+ end = mDecodedData.get() +
+ std::min<uint32_t>(mDecodedData.Length(), MAX_BUFFER_SIZE);
+ }
+
+ // skip leading whitespace
+ while (str != end && nsCRT::IsAsciiSpace(*str)) {
+ ++str;
+ }
+
+ // did we find something like a start tag?
+ if (str == end || *str != '<' || ++str == end) {
+ return false;
+ }
+
+ // If we seem to be SGML or XML and we got down here, just pretend we're HTML
+ if (*str == '!' || *str == '?') {
+ mContentType = TEXT_HTML;
+ return true;
+ }
+
+ uint32_t bufSize = end - str;
+ // We use sizeof(_tagstr) below because that's the length of _tagstr
+ // with the one char " " or ">" appended.
+#define MATCHES_TAG(_tagstr) \
+ (bufSize >= sizeof(_tagstr) && \
+ (nsCRT::strncasecmp(str, _tagstr " ", sizeof(_tagstr)) == 0 || \
+ nsCRT::strncasecmp(str, _tagstr ">", sizeof(_tagstr)) == 0))
+
+ if (MATCHES_TAG("html") || MATCHES_TAG("frameset") || MATCHES_TAG("body") ||
+ MATCHES_TAG("head") || MATCHES_TAG("script") || MATCHES_TAG("iframe") ||
+ MATCHES_TAG("a") || MATCHES_TAG("img") || MATCHES_TAG("table") ||
+ MATCHES_TAG("title") || MATCHES_TAG("link") || MATCHES_TAG("base") ||
+ MATCHES_TAG("style") || MATCHES_TAG("div") || MATCHES_TAG("p") ||
+ MATCHES_TAG("font") || MATCHES_TAG("applet") || MATCHES_TAG("meta") ||
+ MATCHES_TAG("center") || MATCHES_TAG("form") || MATCHES_TAG("isindex") ||
+ MATCHES_TAG("h1") || MATCHES_TAG("h2") || MATCHES_TAG("h3") ||
+ MATCHES_TAG("h4") || MATCHES_TAG("h5") || MATCHES_TAG("h6") ||
+ MATCHES_TAG("b") || MATCHES_TAG("pre")) {
+ mContentType = TEXT_HTML;
+ return true;
+ }
+
+#undef MATCHES_TAG
+
+ return false;
+}
+
+bool nsUnknownDecoder::SniffForXML(nsIRequest* aRequest) {
+ // First see whether we can glean anything from the uri...
+ if (!SniffURI(aRequest)) {
+ // Oh well; just generic XML will have to do
+ MutexAutoLock lock(mMutex);
+ mContentType = TEXT_XML;
+ }
+
+ return true;
+}
+
+bool nsUnknownDecoder::SniffURI(nsIRequest* aRequest) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (loadInfo->GetSkipContentSniffing()) {
+ return false;
+ }
+ nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1"));
+ if (mimeService) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult result = channel->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(result) && uri) {
+ nsAutoCString type;
+ result = mimeService->GetTypeFromURI(uri, type);
+ if (NS_SUCCEEDED(result)) {
+ MutexAutoLock lock(mMutex);
+ mContentType = type;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+// This macro is based on RFC 2046 Section 4.1.2. Treat any char 0-31
+// except the 9-13 range (\t, \n, \v, \f, \r) and char 27 (used by
+// encodings like Shift_JIS) as non-text
+#define IS_TEXT_CHAR(ch) \
+ (((unsigned char)(ch)) > 31 || (9 <= (ch) && (ch) <= 13) || (ch) == 27)
+
+bool nsUnknownDecoder::LastDitchSniff(nsIRequest* aRequest) {
+ // All we can do now is try to guess whether this is text/plain or
+ // application/octet-stream
+
+ MutexAutoLock lock(mMutex);
+
+ const char* testData;
+ uint32_t testDataLen;
+ if (mDecodedData.IsEmpty()) {
+ testData = mBuffer;
+ // Since some legacy text files end with 0x1A, reading the entire buffer
+ // will lead misdetection.
+ testDataLen = std::min<uint32_t>(mBufferLen, MAX_BUFFER_SIZE);
+ } else {
+ testData = mDecodedData.get();
+ testDataLen = std::min<uint32_t>(mDecodedData.Length(), MAX_BUFFER_SIZE);
+ }
+
+ // First, check for a BOM. If we see one, assume this is text/plain
+ // in whatever encoding. If there is a BOM _and_ text we will
+ // always have at least 4 bytes in the buffer (since the 2-byte BOMs
+ // are for 2-byte encodings and the UTF-8 BOM is 3 bytes).
+ if (testDataLen >= 4) {
+ const unsigned char* buf = (const unsigned char*)testData;
+ if ((buf[0] == 0xFE && buf[1] == 0xFF) || // UTF-16, Big Endian
+ (buf[0] == 0xFF && buf[1] == 0xFE) || // UTF-16 or UCS-4, Little Endian
+ (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) || // UTF-8
+ (buf[0] == 0 && buf[1] == 0 && buf[2] == 0xFE &&
+ buf[3] == 0xFF)) { // UCS-4, Big Endian
+
+ mContentType = TEXT_PLAIN;
+ return true;
+ }
+ }
+
+ // Now see whether the buffer has any non-text chars. If not, then let's
+ // just call it text/plain...
+ //
+ uint32_t i;
+ for (i = 0; i < testDataLen && IS_TEXT_CHAR(testData[i]); i++) {
+ }
+
+ if (i == testDataLen) {
+ mContentType = TEXT_PLAIN;
+ } else {
+ mContentType = APPLICATION_OCTET_STREAM;
+ }
+
+ return true;
+}
+
+nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request,
+ nsISupports* aCtxt) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsAutoCString contentType;
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mNextListener) return NS_ERROR_FAILURE;
+
+ listener = mNextListener;
+ contentType = mContentType;
+ }
+
+ if (!contentType.IsEmpty()) {
+ nsCOMPtr<nsIViewSourceChannel> viewSourceChannel =
+ do_QueryInterface(request);
+ if (viewSourceChannel) {
+ rv = viewSourceChannel->SetOriginalContentType(contentType);
+ } else {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ // Set the new content type on the channel...
+ rv = channel->SetContentType(contentType);
+ }
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to set content type on channel!");
+
+ if (NS_FAILED(rv)) {
+ // Cancel the request to make sure it has the correct status if
+ // mNextListener looks at it.
+ request->Cancel(rv);
+ listener->OnStartRequest(request);
+ return rv;
+ }
+ }
+
+ // Fire the OnStartRequest(...)
+ rv = listener->OnStartRequest(request);
+
+ if (NS_SUCCEEDED(rv)) {
+ // install stream converter if required
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(request);
+ if (encodedChannel) {
+ nsCOMPtr<nsIStreamListener> listenerNew;
+ rv = encodedChannel->DoApplyContentConversions(
+ listener, getter_AddRefs(listenerNew), aCtxt);
+ if (NS_SUCCEEDED(rv) && listenerNew) {
+ MutexAutoLock lock(mMutex);
+ mNextListener = listenerNew;
+ listener = listenerNew;
+ }
+ }
+ }
+
+ if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY;
+
+ // If the request was canceled, then we need to treat that equivalently
+ // to an error returned by OnStartRequest.
+ if (NS_SUCCEEDED(rv)) request->GetStatus(&rv);
+
+ // Fire the first OnDataAvailable for the data that was read from the
+ // stream into the sniffer buffer...
+ if (NS_SUCCEEDED(rv) && (mBufferLen > 0)) {
+ uint32_t len = 0;
+ nsCOMPtr<nsIInputStream> in;
+ nsCOMPtr<nsIOutputStream> out;
+
+ // Create a pipe and fill it with the data from the sniffer buffer.
+ NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), MAX_BUFFER_SIZE,
+ MAX_BUFFER_SIZE);
+
+ rv = out->Write(mBuffer, mBufferLen, &len);
+ if (NS_SUCCEEDED(rv)) {
+ if (len == mBufferLen) {
+ rv = listener->OnDataAvailable(request, in, 0, len);
+ } else {
+ NS_ERROR("Unable to write all the data into the pipe.");
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ delete[] mBuffer;
+ mBuffer = nullptr;
+ mBufferLen = 0;
+
+ return rv;
+}
+
+nsresult nsUnknownDecoder::ConvertEncodedData(nsIRequest* request,
+ const char* data,
+ uint32_t length) {
+ nsresult rv = NS_OK;
+
+ {
+ MutexAutoLock lock(mMutex);
+ mDecodedData = "";
+ }
+ nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(request));
+ if (encodedChannel) {
+ RefPtr<ConvertedStreamListener> strListener =
+ new ConvertedStreamListener(this);
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = encodedChannel->DoApplyContentConversions(
+ strListener, getter_AddRefs(listener), nullptr);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (listener) {
+ listener->OnStartRequest(request);
+
+ if (length) {
+ nsCOMPtr<nsIStringInputStream> rawStream =
+ do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
+ if (!rawStream) return NS_ERROR_FAILURE;
+
+ rv = rawStream->SetData((const char*)data, length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = listener->OnDataAvailable(request, rawStream, 0, length);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ listener->OnStopRequest(request, NS_OK);
+ }
+ }
+ return rv;
+}
+
+//
+// nsIThreadRetargetableStreamListener methods
+//
+NS_IMETHODIMP
+nsUnknownDecoder::CheckListenerChain() {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = do_QueryInterface(mNextListener);
+ }
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = do_QueryInterface(mNextListener);
+ }
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+void nsBinaryDetector::DetermineContentType(nsIRequest* aRequest) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
+ if (loadInfo->GetSkipContentSniffing()) {
+ LastDitchSniff(aRequest);
+ return;
+ }
+ // It's an HTTP channel. Check for the text/plain mess
+ nsAutoCString contentTypeHdr;
+ Unused << httpChannel->GetResponseHeader("Content-Type"_ns, contentTypeHdr);
+ nsAutoCString contentType;
+ httpChannel->GetContentType(contentType);
+
+ // Make sure to do a case-sensitive exact match comparison here. Apache
+ // 1.x just sends text/plain for "unknown", while Apache 2.x sends
+ // text/plain with a ISO-8859-1 charset. Debian's Apache version, just to
+ // be different, sends text/plain with iso-8859-1 charset. For extra fun,
+ // FC7, RHEL4, and Ubuntu Feisty send charset=UTF-8. Don't do general
+ // case-insensitive comparison, since we really want to apply this crap as
+ // rarely as we can.
+ if (!contentType.EqualsLiteral("text/plain") ||
+ (!contentTypeHdr.EqualsLiteral("text/plain") &&
+ !contentTypeHdr.EqualsLiteral("text/plain; charset=ISO-8859-1") &&
+ !contentTypeHdr.EqualsLiteral("text/plain; charset=iso-8859-1") &&
+ !contentTypeHdr.EqualsLiteral("text/plain; charset=UTF-8"))) {
+ return;
+ }
+
+ // Check whether we have content-encoding. If we do, don't try to
+ // detect the type.
+ // XXXbz we could improve this by doing a local decompress if we
+ // wanted, I'm sure.
+ nsAutoCString contentEncoding;
+ Unused << httpChannel->GetResponseHeader("Content-Encoding"_ns,
+ contentEncoding);
+ if (!contentEncoding.IsEmpty()) {
+ return;
+ }
+
+ LastDitchSniff(aRequest);
+ MutexAutoLock lock(mMutex);
+ if (mContentType.EqualsLiteral(APPLICATION_OCTET_STREAM)) {
+ // We want to guess at it instead
+ mContentType = APPLICATION_GUESS_FROM_EXT;
+ } else {
+ // Let the text/plain type we already have be, so that other content
+ // sniffers can also get a shot at this data.
+ mContentType.Truncate();
+ }
+}
diff --git a/netwerk/streamconv/converters/nsUnknownDecoder.h b/netwerk/streamconv/converters/nsUnknownDecoder.h
new file mode 100644
index 0000000000..3aed80639d
--- /dev/null
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.h
@@ -0,0 +1,150 @@
+/* -*- 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 nsUnknownDecoder_h__
+#define nsUnknownDecoder_h__
+
+#include "nsIStreamConverter.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIContentSniffer.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Atomics.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#define NS_UNKNOWNDECODER_CID \
+ { /* 7d7008a0-c49a-11d3-9b22-0080c7cb1080 */ \
+ 0x7d7008a0, 0xc49a, 0x11d3, { \
+ 0x9b, 0x22, 0x00, 0x80, 0xc7, 0xcb, 0x10, 0x80 \
+ } \
+ }
+
+class nsUnknownDecoder : public nsIStreamConverter, public nsIContentSniffer {
+ public:
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ // nsIStreamConverter methods
+ NS_DECL_NSISTREAMCONVERTER
+
+ // nsIStreamListener methods
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIRequestObserver methods
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsIContentSniffer methods
+ NS_DECL_NSICONTENTSNIFFER
+
+ // nsIThreadRetargetableStreamListener methods
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ explicit nsUnknownDecoder(nsIStreamListener* aListener = nullptr);
+
+ protected:
+ virtual ~nsUnknownDecoder();
+
+ virtual void DetermineContentType(nsIRequest* aRequest);
+ nsresult FireListenerNotifications(nsIRequest* request, nsISupports* aCtxt);
+
+ class ConvertedStreamListener : public nsIStreamListener {
+ public:
+ explicit ConvertedStreamListener(nsUnknownDecoder* aDecoder);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ private:
+ virtual ~ConvertedStreamListener() = default;
+ static nsresult AppendDataToString(nsIInputStream* inputStream,
+ void* closure, const char* rawSegment,
+ uint32_t toOffset, uint32_t count,
+ uint32_t* writeCount);
+ nsUnknownDecoder* mDecoder;
+ };
+
+ protected:
+ nsCOMPtr<nsIStreamListener> mNextListener;
+
+ // Various sniffer functions. Returning true means that a type
+ // was determined; false means no luck.
+ bool SniffForHTML(nsIRequest* aRequest);
+ bool SniffForXML(nsIRequest* aRequest);
+
+ // SniffURI guesses at the content type based on the URI (typically
+ // using the extentsion)
+ bool SniffURI(nsIRequest* aRequest);
+
+ // LastDitchSniff guesses at text/plain vs. application/octet-stream
+ // by just looking at whether the data contains null bytes, and
+ // maybe at the fraction of chars with high bit set. Use this only
+ // as a last-ditch attempt to decide a content type!
+ bool LastDitchSniff(nsIRequest* aRequest);
+
+ /**
+ * An entry struct for our array of sniffers. Each entry has either
+ * a type associated with it (set these with the SNIFFER_ENTRY macro)
+ * or a function to be executed (set these with the
+ * SNIFFER_ENTRY_WITH_FUNC macro). The function should take a single
+ * nsIRequest* and returns bool -- true if it sets mContentType,
+ * false otherwise
+ */
+ struct nsSnifferEntry {
+ using TypeSniffFunc = bool (nsUnknownDecoder::*)(nsIRequest*);
+
+ const char* mBytes;
+ uint32_t mByteLen;
+
+ // Exactly one of mMimeType and mContentTypeSniffer should be set non-null
+ const char* mMimeType;
+ TypeSniffFunc mContentTypeSniffer;
+ };
+
+#define SNIFFER_ENTRY(_bytes, _type) \
+ { _bytes, sizeof(_bytes) - 1, _type, nullptr }
+
+#define SNIFFER_ENTRY_WITH_FUNC(_bytes, _func) \
+ { _bytes, sizeof(_bytes) - 1, nullptr, _func }
+
+ static nsSnifferEntry sSnifferEntries[];
+ static uint32_t sSnifferEntryNum;
+
+ // We guarantee in order delivery of OnStart, OnStop and OnData, therefore
+ // we do not need proper locking for mBuffer.
+ mozilla::Atomic<char*> mBuffer;
+ mozilla::Atomic<uint32_t> mBufferLen;
+
+ nsCString mContentType;
+
+ // This mutex syncs: mContentType, mDecodedData and mNextListener.
+ mutable mozilla::Mutex mMutex MOZ_UNANNOTATED;
+
+ protected:
+ nsresult ConvertEncodedData(nsIRequest* request, const char* data,
+ uint32_t length);
+ nsCString mDecodedData; // If data are encoded this will be uncompress data.
+};
+
+#define NS_BINARYDETECTOR_CID \
+ { /* a2027ec6-ba0d-4c72-805d-148233f5f33c */ \
+ 0xa2027ec6, 0xba0d, 0x4c72, { \
+ 0x80, 0x5d, 0x14, 0x82, 0x33, 0xf5, 0xf3, 0x3c \
+ } \
+ }
+
+/**
+ * Class that detects whether a data stream is text or binary. This reuses
+ * most of nsUnknownDecoder except the actual content-type determination logic
+ * -- our overridden DetermineContentType simply calls LastDitchSniff and sets
+ * the type to APPLICATION_GUESS_FROM_EXT if the data is detected as binary.
+ */
+class nsBinaryDetector : public nsUnknownDecoder {
+ protected:
+ virtual void DetermineContentType(nsIRequest* aRequest) override;
+};
+
+#endif /* nsUnknownDecoder_h__ */
diff --git a/netwerk/streamconv/moz.build b/netwerk/streamconv/moz.build
new file mode 100644
index 0000000000..e7ef5b95b7
--- /dev/null
+++ b/netwerk/streamconv/moz.build
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+DIRS += ["converters"]
+
+XPIDL_SOURCES += [
+ "mozITXTToHTMLConv.idl",
+ "nsIDirIndex.idl",
+ "nsIDirIndexListener.idl",
+ "nsIStreamConverter.idl",
+ "nsIStreamConverterService.idl",
+ "nsITXTToHTMLConv.idl",
+]
+
+SOURCES += [
+ "nsStreamConverterService.cpp",
+]
+
+XPIDL_MODULE = "necko_strconv"
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/streamconv/mozITXTToHTMLConv.idl b/netwerk/streamconv/mozITXTToHTMLConv.idl
new file mode 100644
index 0000000000..5a2a1510c9
--- /dev/null
+++ b/netwerk/streamconv/mozITXTToHTMLConv.idl
@@ -0,0 +1,88 @@
+/* -*- 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/. */
+
+/**
+ Description: Currently only functions to enhance plain text with HTML tags.
+ <p>
+ Wrapper class for various parsing routines, that convert plain text to HTML.
+ They try to recognize cites, URLs, plain text formattting like *bold* etc.
+ See <http://www.bucksch.org/1/projects/mozilla/16507/> for a description.
+ */
+
+#include "nsIStreamConverter.idl"
+
+%{C++
+// {77c0e42a-1dd2-11b2-8ebf-edc6606f2f4b}
+#define MOZITXTTOHTMLCONV_CID \
+ { 0x77c0e42a, 0x1dd2, 0x11b2, \
+ { 0x8e, 0xbf, 0xed, 0xc6, 0x60, 0x6f, 0x2f, 0x4b } }
+
+#define MOZ_TXTTOHTMLCONV_CONTRACTID \
+ "@mozilla.org/txttohtmlconv;1"
+
+%}
+
+[scriptable, uuid(77c0e42a-1dd2-11b2-8ebf-edc6606f2f4b)]
+interface mozITXTToHTMLConv : nsIStreamConverter {
+ const unsigned long kEntities = 0; // just convert < & > to &lt; &amp; and &gt;
+ const unsigned long kURLs = 1 << 1;
+ const unsigned long kGlyphSubstitution = 1 << 2; // Smilies, &reg; etc.
+ const unsigned long kStructPhrase = 1 << 3; // E.g. *bold* -> <strong>
+
+/**
+ @param text: plain text to scan. May be a line, paragraph (recommended)
+ or just a substring.<p>
+ Must be non-escaped, pure unicode.<p>
+ <em>Note:</em> ScanTXT(a, o) + ScanTXT(b, o) may be !=
+ Scan(a + b, o)
+ @param whattodo: Bitfield describing the modes of operation
+ @result "<", ">" and "&" are escaped and HTML tags are inserted where
+ appropriate.
+ */
+ AString scanTXT(in AString text, in unsigned long whattodo);
+
+/**
+ Adds additional formatting to user edited text, that the user was too lazy
+ or "unknowledged" (DELETEME: is that a word?) to make.
+ <p>
+ <em>Note:</em> Don't use kGlyphSubstitution with this function. This option
+ generates tags, that are unuseable for UAs other than Mozilla. This would
+ be a data loss bug.
+
+ @param text: HTML source to scan. May be a line, paragraph (recommended)
+ or just a substring.<p>
+ Must be correct HTML. "<", ">" and "&" must be escaped,
+ other chars must be pure unicode.<p>
+ <em>Note:</em> ScanTXT(a, o) + ScanTXT(b, o) may be !=
+ Scan(a + b, o)
+ @param whattodo: Bitfield describing the modes of operation
+ @result Additional HTML tags are inserted where appropriate.
+ */
+ AString scanHTML(in AString text, in unsigned long whattodo);
+
+/**
+ @param line: line in original msg, possibly starting starting with
+ txt quote tags like ">"
+ @param logLineStart: pos in line, where the real content (logical line)
+ begins, i.e. pos after all txt quote tags.
+ E.g. position of "t" in "> > text".
+ Initial value must be 0, unless line is not real line.
+ @return Cite Level, i.e. number of txt quote tags found, i.e. number of
+ nested quotes.
+ */
+ unsigned long citeLevelTXT(in wstring line,
+ out unsigned long logLineStart);
+
+/**
+ @param a wide string to scan for the presence of a URL.
+ @param aLength --> the length of the buffer to be scanned
+ @param aPos --> the position in the buffer to start scanning for a url
+
+ aStartPos --> index into the start of a url (-1 if no url found)
+ aEndPos --> index of the last character in the url (-1 if no url found)
+ */
+
+ void findURLInPlaintext(in wstring text, in long aLength, in long aPos, out long aStartPos, out long aEndPos);
+};
diff --git a/netwerk/streamconv/nsIDirIndex.idl b/netwerk/streamconv/nsIDirIndex.idl
new file mode 100644
index 0000000000..6f1df669a0
--- /dev/null
+++ b/netwerk/streamconv/nsIDirIndex.idl
@@ -0,0 +1,59 @@
+/* -*- 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"
+
+/** A class holding information about a directory index.
+ * These have no reference back to their original source -
+ * changing these attributes won't affect the directory
+ */
+[scriptable, uuid(23bbabd0-1dd2-11b2-86b7-aad68ae7d7e0)]
+interface nsIDirIndex : nsISupports
+{
+ /**
+ * Entry's type is unknown
+ */
+ const unsigned long TYPE_UNKNOWN = 0;
+
+ /**
+ * Entry is a directory
+ */
+ const unsigned long TYPE_DIRECTORY = 1;
+
+ /**
+ * Entry is a file
+ */
+ const unsigned long TYPE_FILE = 2;
+
+ /**
+ * Entry is a symlink
+ */
+ const unsigned long TYPE_SYMLINK = 3;
+
+ /**
+ * The type of the entry - one of the constants above
+ */
+ attribute unsigned long type;
+
+ /**
+ * The fully qualified filename, expressed as a uri
+ *
+ * This is encoded with the encoding specified in
+ * the nsIDirIndexParser, and is also escaped.
+ */
+ attribute ACString location;
+
+ /**
+ * File size, with -1 meaning "unknown"
+ */
+ attribute long long size;
+
+ /**
+ * Last-modified time in seconds-since-epoch.
+ * -1 means unknown - this is valid, because there were no
+ * ftp servers in 1969
+ */
+ attribute PRTime lastModified;
+};
diff --git a/netwerk/streamconv/nsIDirIndexListener.idl b/netwerk/streamconv/nsIDirIndexListener.idl
new file mode 100644
index 0000000000..ba248b5e75
--- /dev/null
+++ b/netwerk/streamconv/nsIDirIndexListener.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIDirIndex;
+
+/**
+ * This interface is used to receive contents of directory index listings
+ * from a protocol. They can then be transformed into an output format
+ * (such as rdf, html, etc)
+ */
+[scriptable, uuid(fae4e9a8-1dd1-11b2-b53c-8f3aa1bbf8f5)]
+interface nsIDirIndexListener : nsISupports {
+ /**
+ * Called for each directory entry
+ *
+ * @param request - the request
+ * @param index - new index to add
+ */
+ void onIndexAvailable(in nsIRequest aRequest,
+ in nsIDirIndex aIndex);
+};
+
+/**
+ * A parser for application/http-index-format
+ */
+[scriptable, uuid(38e3066c-1dd2-11b2-9b59-8be515c1ee3f)]
+interface nsIDirIndexParser : nsIStreamListener {
+ /**
+ * The interface to use as a callback for new entries
+ */
+ attribute nsIDirIndexListener listener;
+};
diff --git a/netwerk/streamconv/nsIStreamConverter.idl b/netwerk/streamconv/nsIStreamConverter.idl
new file mode 100644
index 0000000000..68f341b328
--- /dev/null
+++ b/netwerk/streamconv/nsIStreamConverter.idl
@@ -0,0 +1,113 @@
+/* -*- 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 "nsIThreadRetargetableStreamListener.idl"
+
+interface nsIChannel;
+interface nsIInputStream;
+
+/**
+ * nsIStreamConverter provides an interface to implement when you have code
+ * that converts data from one type to another.
+ *
+ * Suppose you had code that converted plain text into HTML. You could implement
+ * this interface to allow everyone else to use your conversion logic using a
+ * standard api.
+ * <p>
+ * <b>STREAM CONVERTER USERS</b>
+ *
+ * There are currently two ways to use a stream converter:
+ * <ol>
+ * <li> <b>SYNCHRONOUS</b> Stream to Stream
+ * You can supply the service with a stream of type X
+ * and it will convert it to your desired output type and return
+ * a converted (blocking) stream to you.</li>
+ *
+ * <li> <b>ASYNCHRONOUS</b> nsIStreamListener to nsIStreamListener
+ * You can supply data directly to the converter by calling it's
+ * nsIStreamListener::OnDataAvailable() method. It will then
+ * convert that data from type X to your desired output type and
+ * return converted data to you via the nsIStreamListener you passed
+ * in by calling its OnDataAvailable() method.</li>
+ * </ol>
+ * <p>
+ *
+ * <b>STREAM CONVERTER SUPPLIERS</b>
+ *
+ * Registering a stream converter:
+ * Stream converter registration is a two step process. First of all the stream
+ * converter implementation must register itself with the component manager using
+ * a contractid in the format below. Second, the stream converter must add the contractid
+ * to the registry.
+ *
+ * Stream converter contractid format (the stream converter root key is defined in this
+ * file):
+ *
+ * <pre>@mozilla.org/streamconv;1?from=FROM_MIME_TYPE&to=TO_MIME_TYPE</pre>
+ *
+ * @author Jud Valeski
+ * @see nsIStreamConverterService
+ */
+
+[scriptable, uuid(0b6e2c69-5cf5-48b0-9dfd-c95950e2cc7b)]
+interface nsIStreamConverter : nsIThreadRetargetableStreamListener {
+
+ /**
+ * <b>SYNCRONOUS VERSION</b>
+ * Converts a stream of one type, to a stream of another type.
+ *
+ * Use this method when you have a stream you want to convert.
+ *
+ * @param aFromStream The stream representing the original/raw data.
+ * @param aFromType The MIME type of aFromStream.
+ * @param aToType The MIME type of the returned stream.
+ * @param aCtxt Either an opaque context, or a converter specific context
+ * (implementation specific).
+ * @return The converted stream. NOTE: The returned stream may not
+ * already be converted. An efficient stream converter
+ * implementation will converter data on demand rather than
+ * buffering the converted data until it is used.
+ */
+ nsIInputStream convert(in nsIInputStream aFromStream,
+ in string aFromType,
+ in string aToType,
+ in nsISupports aCtxt);
+
+ /**
+ * <b>ASYNCRONOUS VERSION</b>
+ * Converts data arriving via the converter's nsIStreamListener::OnDataAvailable()
+ * method from one type to another, pushing the converted data out to the caller
+ * via aListener::OnDataAvailable().
+ *
+ * Use this method when you want to proxy (and convert) nsIStreamListener callbacks
+ * asynchronously.
+ *
+ * @param aFromType The MIME type of the original/raw data.
+ * @param aToType The MIME type of the converted data.
+ * @param aListener The listener who receives the converted data.
+ * @param aCtxt Either an opaque context, or a converter specific context
+ * (implementation specific).
+ */
+ void asyncConvertData(in string aFromType,
+ in string aToType,
+ in nsIStreamListener aListener,
+ in nsISupports aCtxt);
+
+ /**
+ * Returns the content type that the stream listener passed to asyncConvertData will
+ * see on the channel if the conversion is being done from aFromType to * /*.
+ *
+ * @param aFromType The type of the content prior to conversion.
+ * @param aChannel The channel that we'd like to convert. May be null.
+ *
+ * @throws if the converter does not support conversion to * /* or if it doesn't know
+ * the type in advance.
+ */
+ ACString getConvertedType(in ACString aFromType, in nsIChannel aChannel);
+};
+
+%{C++
+#define NS_ISTREAMCONVERTER_KEY "@mozilla.org/streamconv;1"
+%}
diff --git a/netwerk/streamconv/nsIStreamConverterService.idl b/netwerk/streamconv/nsIStreamConverterService.idl
new file mode 100644
index 0000000000..f3bc0931ac
--- /dev/null
+++ b/netwerk/streamconv/nsIStreamConverterService.idl
@@ -0,0 +1,89 @@
+/* -*- 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 nsIChannel;
+interface nsIInputStream;
+interface nsIStreamListener;
+
+%{C++
+#define NS_ISTREAMCONVERTER_KEY "@mozilla.org/streamconv;1"
+%}
+
+/**
+ * The nsIStreamConverterService is a higher level stream converter factory
+ * responsible for locating and creating stream converters
+ * (nsIStreamConverter).
+ *
+ * This service retrieves an interface that can convert data from a particular
+ * MIME type, to a particular MIME type. It is responsible for any intermediary
+ * conversion required in order to get from X to Z, assuming direct conversion
+ * is not possible.
+ *
+ * @author Jud Valeski
+ * @see nsIStreamConverter
+ */
+[scriptable, uuid(f2b1ab53-f0bd-4adb-9365-e59b1701a258)]
+interface nsIStreamConverterService : nsISupports {
+ /**
+ * Tests whether conversion between the two specified types is possible.
+ * This is cheaper than calling convert()/asyncConvertData(); it is not
+ * necessary to call this function before calling one of those, though.
+ */
+ boolean canConvert(in string aFromType, in string aToType);
+
+ /**
+ * Returns the content type that will be returned from a converter
+ * created with aFromType and * /*.
+ * Can fail if no converters support this conversion, or if the
+ * output type isn't known in advance.
+ */
+ ACString convertedType(in ACString aFromType, in nsIChannel aChannel);
+
+ /**
+ * <b>SYNCHRONOUS VERSION</b>
+ * Converts a stream of one type, to a stream of another type.
+ *
+ * Use this method when you have a stream you want to convert.
+ *
+ * @param aFromStream The stream representing the original/raw data.
+ * @param aFromType The MIME type of aFromStream.
+ * @param aToType The MIME type of the returned stream.
+ * @param aContext Either an opaque context, or a converter specific
+ * context (implementation specific).
+ * @return The converted stream. NOTE: The returned stream
+ * may not already be converted. An efficient stream
+ * converter implementation will convert data on
+ * demand rather than buffering the converted data
+ * until it is used.
+ */
+ nsIInputStream convert(in nsIInputStream aFromStream,
+ in string aFromType,
+ in string aToType,
+ in nsISupports aContext);
+
+ /**
+ * <b>ASYNCHRONOUS VERSION</b>
+ * Retrieves a nsIStreamListener that receives the original/raw data via its
+ * nsIStreamListener::OnDataAvailable() callback, then converts and pushes
+ * the data to aListener.
+ *
+ * Use this method when you want to proxy (and convert) nsIStreamListener
+ * callbacks asynchronously.
+ *
+ * @param aFromType The MIME type of the original/raw data.
+ * @param aToType The MIME type of the converted data.
+ * @param aListener The listener that receives the converted data.
+ * @param aCtxt Either an opaque context, or a converter specific
+ * context (implementation specific).
+ * @return A nsIStreamListener that receives data via its
+ * OnDataAvailable() method.
+ */
+ nsIStreamListener asyncConvertData(in string aFromType,
+ in string aToType,
+ in nsIStreamListener aListener,
+ in nsISupports aContext);
+};
diff --git a/netwerk/streamconv/nsITXTToHTMLConv.idl b/netwerk/streamconv/nsITXTToHTMLConv.idl
new file mode 100644
index 0000000000..bfe841834d
--- /dev/null
+++ b/netwerk/streamconv/nsITXTToHTMLConv.idl
@@ -0,0 +1,25 @@
+/* -*- 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 "nsIStreamConverter.idl"
+
+[scriptable, uuid(933355f6-1dd2-11b2-a9b0-d335b9e35983)]
+interface nsITXTToHTMLConv : nsIStreamConverter {
+ /**
+ * @param text: Title to set for the HTML document. Only applicable if
+ * preFormatHTML(true) is called.
+ * @result The given title will be used to form an HTML document
+ * from the plain text document.
+ */
+ void setTitle(in wstring text);
+
+ /**
+ * @param value: true to use an HTML header and footer on the document,
+ * false to omit it.
+ * @result The document will use a header and footer if value is
+ * true.
+ */
+ void preFormatHTML(in boolean value);
+};
diff --git a/netwerk/streamconv/nsStreamConverterService.cpp b/netwerk/streamconv/nsStreamConverterService.cpp
new file mode 100644
index 0000000000..ce95bb0404
--- /dev/null
+++ b/netwerk/streamconv/nsStreamConverterService.cpp
@@ -0,0 +1,543 @@
+/* -*- 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 "nsComponentManagerUtils.h"
+#include "nsStreamConverterService.h"
+#include "nsIComponentRegistrar.h"
+#include "nsString.h"
+#include "nsAtom.h"
+#include "nsDeque.h"
+#include "nsIInputStream.h"
+#include "nsIStreamConverter.h"
+#include "nsICategoryManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsTArray.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/UniquePtr.h"
+
+///////////////////////////////////////////////////////////////////
+// Breadth-First-Search (BFS) algorithm state classes and types.
+
+// Used to establish discovered verticies.
+enum BFScolors { white, gray, black };
+
+// BFS hashtable data class.
+struct BFSTableData {
+ nsCString key;
+ BFScolors color;
+ int32_t distance;
+ mozilla::UniquePtr<nsCString> predecessor;
+
+ explicit BFSTableData(const nsACString& aKey)
+ : key(aKey), color(white), distance(-1) {}
+};
+
+////////////////////////////////////////////////////////////
+// nsISupports methods
+NS_IMPL_ISUPPORTS(nsStreamConverterService, nsIStreamConverterService)
+
+////////////////////////////////////////////////////////////
+// nsIStreamConverterService methods
+
+////////////////////////////////////////////////////////////
+// nsStreamConverterService methods
+
+// Builds the graph represented as an adjacency list (and built up in
+// memory using an nsObjectHashtable and nsCOMArray combination).
+//
+// :BuildGraph() consults the category manager for all stream converter
+// CONTRACTIDS then fills the adjacency list with edges.
+// An edge in this case is comprised of a FROM and TO MIME type combination.
+//
+// CONTRACTID format:
+// @mozilla.org/streamconv;1?from=text/html&to=text/plain
+// XXX curently we only handle a single from and to combo, we should repeat the
+// XXX registration process for any series of from-to combos.
+// XXX can use nsTokenizer for this.
+//
+
+nsresult nsStreamConverterService::BuildGraph() {
+ nsresult rv;
+
+ nsCOMPtr<nsICategoryManager> catmgr(
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY,
+ getter_AddRefs(entries));
+ if (NS_FAILED(rv)) return rv;
+
+ // go through each entry to build the graph
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsISupportsCString> entry;
+ rv = entries->GetNext(getter_AddRefs(supports));
+ while (NS_SUCCEEDED(rv)) {
+ entry = do_QueryInterface(supports);
+
+ // get the entry string
+ nsAutoCString entryString;
+ rv = entry->GetData(entryString);
+ if (NS_FAILED(rv)) return rv;
+
+ // cobble the entry string w/ the converter key to produce a full
+ // contractID.
+ nsAutoCString contractID(NS_ISTREAMCONVERTER_KEY);
+ contractID.Append(entryString);
+
+ // now we've got the CONTRACTID, let's parse it up.
+ rv = AddAdjacency(contractID.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = entries->GetNext(getter_AddRefs(supports));
+ }
+
+ return NS_OK;
+}
+
+// XXX currently you can not add the same adjacency (i.e. you can't have
+// multiple
+// XXX stream converters registering to handle the same from-to combination.
+// It's
+// XXX not programatically prohibited, it's just that results are un-predictable
+// XXX right now.
+nsresult nsStreamConverterService::AddAdjacency(const char* aContractID) {
+ nsresult rv;
+ // first parse out the FROM and TO MIME-types.
+
+ nsAutoCString fromStr, toStr;
+ rv = ParseFromTo(aContractID, fromStr, toStr);
+ if (NS_FAILED(rv)) return rv;
+
+ // Each MIME-type is a vertex in the graph, so first lets make sure
+ // each MIME-type is represented as a key in our hashtable.
+
+ nsTArray<RefPtr<nsAtom>>* const fromEdges =
+ mAdjacencyList.GetOrInsertNew(fromStr);
+
+ mAdjacencyList.GetOrInsertNew(toStr);
+
+ // Now we know the FROM and TO types are represented as keys in the hashtable.
+ // Let's "connect" the verticies, making an edge.
+
+ RefPtr<nsAtom> vertex = NS_Atomize(toStr);
+ if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ASSERTION(fromEdges, "something wrong in adjacency list construction");
+ if (!fromEdges) return NS_ERROR_FAILURE;
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ fromEdges->AppendElement(vertex);
+ return NS_OK;
+}
+
+nsresult nsStreamConverterService::ParseFromTo(const char* aContractID,
+ nsCString& aFromRes,
+ nsCString& aToRes) {
+ nsAutoCString ContractIDStr(aContractID);
+
+ int32_t fromLoc = ContractIDStr.Find("from=");
+ int32_t toLoc = ContractIDStr.Find("to=");
+ if (-1 == fromLoc || -1 == toLoc) return NS_ERROR_FAILURE;
+
+ fromLoc = fromLoc + 5;
+ toLoc = toLoc + 3;
+
+ nsAutoCString fromStr, toStr;
+
+ ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
+ ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc);
+
+ aFromRes.Assign(fromStr);
+ aToRes.Assign(toStr);
+
+ return NS_OK;
+}
+
+using BFSHashTable = nsClassHashtable<nsCStringHashKey, BFSTableData>;
+
+// nsObjectHashtable enumerator functions.
+
+class CStreamConvDeallocator : public nsDequeFunctor<nsCString> {
+ public:
+ void operator()(nsCString* anObject) override { delete anObject; }
+};
+
+// walks the graph using a breadth-first-search algorithm which generates a
+// discovered verticies tree. This tree is then walked up (from destination
+// vertex, to origin vertex) and each link in the chain is added to an
+// nsStringArray. A direct lookup for the given CONTRACTID should be made prior
+// to calling this method in an attempt to find a direct converter rather than
+// walking the graph.
+nsresult nsStreamConverterService::FindConverter(
+ const char* aContractID, nsTArray<nsCString>** aEdgeList) {
+ nsresult rv;
+ if (!aEdgeList) return NS_ERROR_NULL_POINTER;
+ *aEdgeList = nullptr;
+
+ // walk the graph in search of the appropriate converter.
+
+ uint32_t vertexCount = mAdjacencyList.Count();
+ if (0 >= vertexCount) return NS_ERROR_FAILURE;
+
+ // Create a corresponding color table for each vertex in the graph.
+ BFSHashTable lBFSTable;
+ for (const auto& entry : mAdjacencyList) {
+ const nsACString& key = entry.GetKey();
+ MOZ_ASSERT(entry.GetWeak(), "no data in the table iteration");
+ lBFSTable.InsertOrUpdate(key, mozilla::MakeUnique<BFSTableData>(key));
+ }
+
+ NS_ASSERTION(lBFSTable.Count() == vertexCount,
+ "strmconv BFS table init problem");
+
+ // This is our source vertex; our starting point.
+ nsAutoCString fromC, toC;
+ rv = ParseFromTo(aContractID, fromC, toC);
+ if (NS_FAILED(rv)) return rv;
+
+ BFSTableData* data = lBFSTable.Get(fromC);
+ if (!data) {
+ return NS_ERROR_FAILURE;
+ }
+
+ data->color = gray;
+ data->distance = 0;
+ auto* dtorFunc = new CStreamConvDeallocator();
+
+ nsDeque grayQ(dtorFunc);
+
+ // Now generate the shortest path tree.
+ grayQ.Push(new nsCString(fromC));
+ while (0 < grayQ.GetSize()) {
+ nsCString* currentHead = (nsCString*)grayQ.PeekFront();
+ nsTArray<RefPtr<nsAtom>>* data2 = mAdjacencyList.Get(*currentHead);
+ if (!data2) return NS_ERROR_FAILURE;
+
+ // Get the state of the current head to calculate the distance of each
+ // reachable vertex in the loop.
+ BFSTableData* headVertexState = lBFSTable.Get(*currentHead);
+ if (!headVertexState) return NS_ERROR_FAILURE;
+
+ int32_t edgeCount = data2->Length();
+
+ for (int32_t i = 0; i < edgeCount; i++) {
+ nsAtom* curVertexAtom = data2->ElementAt(i);
+ auto* curVertex = new nsCString();
+ curVertexAtom->ToUTF8String(*curVertex);
+
+ BFSTableData* curVertexState = lBFSTable.Get(*curVertex);
+ if (!curVertexState) {
+ delete curVertex;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (white == curVertexState->color) {
+ curVertexState->color = gray;
+ curVertexState->distance = headVertexState->distance + 1;
+ curVertexState->predecessor =
+ mozilla::MakeUnique<nsCString>(*currentHead);
+ grayQ.Push(curVertex);
+ } else {
+ delete curVertex; // if this vertex has already been discovered, we
+ // don't want to leak it. (non-discovered vertex's
+ // get cleaned up when they're popped).
+ }
+ }
+ headVertexState->color = black;
+ nsCString* cur = (nsCString*)grayQ.PopFront();
+ delete cur;
+ cur = nullptr;
+ }
+ // The shortest path (if any) has been generated and is represented by the
+ // chain of BFSTableData->predecessor keys. Start at the bottom and work our
+ // way up.
+
+ // first parse out the FROM and TO MIME-types being registered.
+
+ nsAutoCString fromStr, toMIMEType;
+ rv = ParseFromTo(aContractID, fromStr, toMIMEType);
+ if (NS_FAILED(rv)) return rv;
+
+ // get the root CONTRACTID
+ nsAutoCString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY);
+ auto* shortestPath = new nsTArray<nsCString>();
+
+ data = lBFSTable.Get(toMIMEType);
+ if (!data) {
+ // If this vertex isn't in the BFSTable, then no-one has registered for it,
+ // therefore we can't do the conversion.
+ delete shortestPath;
+ return NS_ERROR_FAILURE;
+ }
+
+ while (data) {
+ if (fromStr.Equals(data->key)) {
+ // found it. We're done here.
+ *aEdgeList = shortestPath;
+ return NS_OK;
+ }
+
+ // reconstruct the CONTRACTID.
+ // Get the predecessor.
+ if (!data->predecessor) break; // no predecessor
+ BFSTableData* predecessorData = lBFSTable.Get(*data->predecessor);
+
+ if (!predecessorData) break; // no predecessor, chain doesn't exist.
+
+ // build out the CONTRACTID.
+ nsAutoCString newContractID(ContractIDPrefix);
+ newContractID.AppendLiteral("?from=");
+
+ newContractID.Append(predecessorData->key);
+
+ newContractID.AppendLiteral("&to=");
+ newContractID.Append(data->key);
+
+ // Add this CONTRACTID to the chain.
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ shortestPath->AppendElement(newContractID);
+
+ // move up the tree.
+ data = predecessorData;
+ }
+ delete shortestPath;
+ return NS_ERROR_FAILURE; // couldn't find a stream converter or chain.
+}
+
+/////////////////////////////////////////////////////
+// nsIStreamConverterService methods
+NS_IMETHODIMP
+nsStreamConverterService::CanConvert(const char* aFromType, const char* aToType,
+ bool* _retval) {
+ nsCOMPtr<nsIComponentRegistrar> reg;
+ nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
+ contractID.Append(aFromType);
+ contractID.AppendLiteral("&to=");
+ contractID.Append(aToType);
+
+ // See if we have a direct match
+ rv = reg->IsContractIDRegistered(contractID.get(), _retval);
+ if (NS_FAILED(rv)) return rv;
+ if (*_retval) return NS_OK;
+
+ // Otherwise try the graph.
+ rv = BuildGraph();
+ if (NS_FAILED(rv)) return rv;
+
+ nsTArray<nsCString>* converterChain = nullptr;
+ rv = FindConverter(contractID.get(), &converterChain);
+ *_retval = NS_SUCCEEDED(rv);
+
+ delete converterChain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverterService::ConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel,
+ nsACString& aOutToType) {
+ // first determine whether we can even handle this conversion
+ // build a CONTRACTID
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
+ contractID.Append(aFromType);
+ contractID.AppendLiteral("&to=*/*");
+ const char* cContractID = contractID.get();
+
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ return converter->GetConvertedType(aFromType, aChannel, aOutToType);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamConverterService::Convert(nsIInputStream* aFromStream,
+ const char* aFromType, const char* aToType,
+ nsISupports* aContext,
+ nsIInputStream** _retval) {
+ if (!aFromStream || !aFromType || !aToType || !_retval) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsresult rv;
+
+ // first determine whether we can even handle this conversion
+ // build a CONTRACTID
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
+ contractID.Append(aFromType);
+ contractID.AppendLiteral("&to=");
+ contractID.Append(aToType);
+ const char* cContractID = contractID.get();
+
+ nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
+ if (NS_FAILED(rv)) {
+ // couldn't go direct, let's try walking the graph of converters.
+ rv = BuildGraph();
+ if (NS_FAILED(rv)) return rv;
+
+ nsTArray<nsCString>* converterChain = nullptr;
+
+ rv = FindConverter(cContractID, &converterChain);
+ if (NS_FAILED(rv)) {
+ // can't make this conversion.
+ // XXX should have a more descriptive error code.
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t edgeCount = int32_t(converterChain->Length());
+ NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
+
+ // convert the stream using each edge of the graph as a step.
+ // this is our stream conversion traversal.
+ nsCOMPtr<nsIInputStream> dataToConvert = aFromStream;
+ nsCOMPtr<nsIInputStream> convertedData;
+
+ for (int32_t i = edgeCount - 1; i >= 0; i--) {
+ const char* lContractID = converterChain->ElementAt(i).get();
+
+ converter = do_CreateInstance(lContractID, &rv);
+
+ if (NS_FAILED(rv)) {
+ delete converterChain;
+ return rv;
+ }
+
+ nsAutoCString fromStr, toStr;
+ rv = ParseFromTo(lContractID, fromStr, toStr);
+ if (NS_FAILED(rv)) {
+ delete converterChain;
+ return rv;
+ }
+
+ rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(),
+ aContext, getter_AddRefs(convertedData));
+ dataToConvert = convertedData;
+ if (NS_FAILED(rv)) {
+ delete converterChain;
+ return rv;
+ }
+ }
+
+ delete converterChain;
+ convertedData.forget(_retval);
+ } else {
+ // we're going direct.
+ rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamConverterService::AsyncConvertData(const char* aFromType,
+ const char* aToType,
+ nsIStreamListener* aListener,
+ nsISupports* aContext,
+ nsIStreamListener** _retval) {
+ if (!aFromType || !aToType || !aListener || !_retval) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult rv;
+
+ // first determine whether we can even handle this conversion
+ // build a CONTRACTID
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
+ contractID.Append(aFromType);
+ contractID.AppendLiteral("&to=");
+ contractID.Append(aToType);
+ const char* cContractID = contractID.get();
+
+ nsCOMPtr<nsIStreamConverter> listener(do_CreateInstance(cContractID, &rv));
+ if (NS_FAILED(rv)) {
+ // couldn't go direct, let's try walking the graph of converters.
+ rv = BuildGraph();
+ if (NS_FAILED(rv)) return rv;
+
+ nsTArray<nsCString>* converterChain = nullptr;
+
+ rv = FindConverter(cContractID, &converterChain);
+ if (NS_FAILED(rv)) {
+ // can't make this conversion.
+ // XXX should have a more descriptive error code.
+ return NS_ERROR_FAILURE;
+ }
+
+ // aListener is the listener that wants the final, converted, data.
+ // we initialize finalListener w/ aListener so it gets put at the
+ // tail end of the chain, which in the loop below, means the *first*
+ // converter created.
+ nsCOMPtr<nsIStreamListener> finalListener = aListener;
+
+ // convert the stream using each edge of the graph as a step.
+ // this is our stream conversion traversal.
+ int32_t edgeCount = int32_t(converterChain->Length());
+ NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
+ for (int i = 0; i < edgeCount; i++) {
+ const char* lContractID = converterChain->ElementAt(i).get();
+
+ // create the converter for this from/to pair
+ nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(lContractID));
+ NS_ASSERTION(converter,
+ "graph construction problem, built a contractid that wasn't "
+ "registered");
+
+ nsAutoCString fromStr, toStr;
+ rv = ParseFromTo(lContractID, fromStr, toStr);
+ if (NS_FAILED(rv)) {
+ delete converterChain;
+ return rv;
+ }
+
+ // connect the converter w/ the listener that should get the converted
+ // data.
+ rv = converter->AsyncConvertData(fromStr.get(), toStr.get(),
+ finalListener, aContext);
+ if (NS_FAILED(rv)) {
+ delete converterChain;
+ return rv;
+ }
+
+ // the last iteration of this loop will result in finalListener
+ // pointing to the converter that "starts" the conversion chain.
+ // this converter's "from" type is the original "from" type. Prior
+ // to the last iteration, finalListener will continuously be wedged
+ // into the next listener in the chain, then be updated.
+ finalListener = converter;
+ }
+ delete converterChain;
+ // return the first listener in the chain.
+ finalListener.forget(_retval);
+ } else {
+ // we're going direct.
+ rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext);
+ listener.forget(_retval);
+ }
+
+ return rv;
+}
+
+nsresult NS_NewStreamConv(nsStreamConverterService** aStreamConv) {
+ MOZ_ASSERT(aStreamConv != nullptr, "null ptr");
+ if (!aStreamConv) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsStreamConverterService> conv = new nsStreamConverterService();
+ conv.forget(aStreamConv);
+
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/nsStreamConverterService.h b/netwerk/streamconv/nsStreamConverterService.h
new file mode 100644
index 0000000000..0d402c5657
--- /dev/null
+++ b/netwerk/streamconv/nsStreamConverterService.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsstreamconverterservice__h___
+#define __nsstreamconverterservice__h___
+
+#include "nsIStreamConverterService.h"
+
+#include "nsClassHashtable.h"
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsAtom;
+
+class nsStreamConverterService : public nsIStreamConverterService {
+ public:
+ /////////////////////////////////////////////////////
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ /////////////////////////////////////////////////////
+ // nsIStreamConverterService methods
+ NS_DECL_NSISTREAMCONVERTERSERVICE
+
+ /////////////////////////////////////////////////////
+ // nsStreamConverterService methods
+ nsStreamConverterService() = default;
+
+ private:
+ virtual ~nsStreamConverterService() = default;
+
+ // Responsible for finding a converter for the given MIME-type.
+ nsresult FindConverter(const char* aContractID,
+ nsTArray<nsCString>** aEdgeList);
+ nsresult BuildGraph(void);
+ nsresult AddAdjacency(const char* aContractID);
+ nsresult ParseFromTo(const char* aContractID, nsCString& aFromRes,
+ nsCString& aToRes);
+
+ // member variables
+ nsClassHashtable<nsCStringHashKey, nsTArray<RefPtr<nsAtom>>> mAdjacencyList;
+};
+
+#endif // __nsstreamconverterservice__h___
diff --git a/netwerk/system/LinkServiceCommon.cpp b/netwerk/system/LinkServiceCommon.cpp
new file mode 100644
index 0000000000..9c9ef986d7
--- /dev/null
+++ b/netwerk/system/LinkServiceCommon.cpp
@@ -0,0 +1,30 @@
+/* -*- 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 "LinkServiceCommon.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/TimeStamp.h"
+#include "nsID.h"
+
+using namespace mozilla;
+
+void SeedNetworkId(SHA1Sum& aSha1) {
+ static Maybe<nsID> seed = ([]() {
+ Maybe<nsID> uuid(std::in_place);
+ if (NS_FAILED(nsID::GenerateUUIDInPlace(*uuid))) {
+ uuid.reset();
+ }
+ return uuid;
+ })();
+ if (seed) {
+ aSha1.update(seed.ptr(), sizeof(*seed));
+ } else {
+ TimeStamp timestamp = TimeStamp::ProcessCreation();
+ aSha1.update(&timestamp, sizeof(timestamp));
+ }
+}
diff --git a/netwerk/system/LinkServiceCommon.h b/netwerk/system/LinkServiceCommon.h
new file mode 100644
index 0000000000..fc96e7ad95
--- /dev/null
+++ b/netwerk/system/LinkServiceCommon.h
@@ -0,0 +1,17 @@
+/* -*- 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 LINK_SERVICE_COMMON_H_
+#define LINK_SERVICE_COMMON_H_
+
+namespace mozilla {
+class SHA1Sum;
+}
+
+// Add a seed to the computed network ID to prevent user linkability.
+void SeedNetworkId(mozilla::SHA1Sum& aSha1);
+
+#endif // LINK_SERVICE_COMMON_H_
diff --git a/netwerk/system/NetworkLinkServiceDefines.h b/netwerk/system/NetworkLinkServiceDefines.h
new file mode 100644
index 0000000000..b0b1892f72
--- /dev/null
+++ b/netwerk/system/NetworkLinkServiceDefines.h
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NETWORK_LINK_SERVICE_DEFINES_H_
+#define NETWORK_LINK_SERVICE_DEFINES_H_
+
+namespace mozilla {
+namespace net {
+
+// IP addresses that are used to check the route for public traffic. They are
+// used just to check routing rules, no packets are sent to those hosts.
+// Initially, addresses of host detectportal.firefox.com were used but they
+// don't necessarily need to be updated when addresses of this host change.
+#define ROUTE_CHECK_IPV4 "23.219.91.27"
+#define ROUTE_CHECK_IPV6 "2a02:26f0:40::17db:5b1b"
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NETWORK_LINK_SERVICE_DEFINES_H_
diff --git a/netwerk/system/android/moz.build b/netwerk/system/android/moz.build
new file mode 100644
index 0000000000..c60b3e5871
--- /dev/null
+++ b/netwerk/system/android/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+SOURCES += [
+ "nsAndroidNetworkLinkService.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/netwerk/system/android/nsAndroidNetworkLinkService.cpp b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
new file mode 100644
index 0000000000..850ac5b565
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsAndroidNetworkLinkService.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIObserverService.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Services.h"
+#include "mozilla/Logging.h"
+
+#include "AndroidBridge.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/jni/Utils.h"
+
+namespace java = mozilla::java;
+namespace jni = mozilla::jni;
+
+static mozilla::LazyLogModule gNotifyAddrLog("nsAndroidNetworkLinkService");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsAndroidNetworkLinkService, nsINetworkLinkService,
+ nsIObserver)
+
+nsAndroidNetworkLinkService::nsAndroidNetworkLinkService()
+ : mStatusIsKnown(false) {}
+
+nsresult nsAndroidNetworkLinkService::Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ rv = observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mNetlinkSvc = new mozilla::net::NetlinkService();
+ rv = mNetlinkSvc->Init(this);
+ if (NS_FAILED(rv)) {
+ mNetlinkSvc = nullptr;
+ LOG(("Cannot initialize NetlinkService [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsAndroidNetworkLinkService::Shutdown() {
+ // remove xpcom shutdown observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+
+ if (mNetlinkSvc) {
+ mNetlinkSvc->Shutdown();
+ mNetlinkSvc = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp("xpcom-shutdown-threads", topic)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+ if (mNetlinkSvc && mStatusIsKnown) {
+ mNetlinkSvc->GetIsLinkUp(aIsUp);
+ return NS_OK;
+ }
+
+ if (!mozilla::AndroidBridge::Bridge()) {
+ // Fail soft here and assume a connection exists
+ NS_WARNING("GetIsLinkUp is not supported without a bridge connection");
+ *aIsUp = true;
+ return NS_OK;
+ }
+
+ *aIsUp = java::GeckoAppShell::IsNetworkLinkUp();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
+ if (mStatusIsKnown) {
+ *aIsKnown = true;
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(mozilla::AndroidBridge::Bridge(), NS_ERROR_NOT_IMPLEMENTED);
+
+ *aIsKnown = java::GeckoAppShell::IsNetworkLinkKnown();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ if (!mozilla::AndroidBridge::Bridge()) {
+ // Fail soft here and assume a connection exists
+ NS_WARNING("GetLinkType is not supported without a bridge connection");
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+ }
+
+ *aLinkType = java::GeckoAppShell::GetNetworkLinkType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetlinkSvc->GetNetworkID(aNetworkID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetDnsSuffixList(
+ nsTArray<nsCString>& aDnsSuffixList) {
+ aDnsSuffixList.Clear();
+ if (!jni::IsAvailable()) {
+ NS_WARNING("GetDnsSuffixList is not supported without JNI");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ jni::String::LocalRef suffixList;
+ nsresult rv = java::GeckoAppShell::GetDNSDomains(&suffixList);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!suffixList || !suffixList->Length()) {
+ return NS_OK;
+ }
+
+ nsAutoCString list(suffixList->ToCString());
+ for (const nsACString& suffix : list.Split(',')) {
+ aDnsSuffixList.AppendElement(suffix);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetResolvers(
+ nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetNativeResolvers(
+ nsTArray<mozilla::net::NetAddr>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetPlatformDNSIndications(
+ uint32_t* aPlatformDNSIndications) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsAndroidNetworkLinkService::OnNetworkChanged() {
+ if (mozilla::StaticPrefs::network_notify_changed()) {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnNetworkChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC,
+ NS_NETWORK_LINK_DATA_CHANGED);
+ }));
+ }
+}
+
+void nsAndroidNetworkLinkService::OnNetworkIDChanged() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnNetworkIDChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+ }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkUp() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsAndroidNetworkLinkService::OnLinkUp", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UP);
+ }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkDown() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnLinkDown", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_DOWN);
+ }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; }
+
+void nsAndroidNetworkLinkService::OnDnsSuffixListUpdated() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnDnsSuffixListUpdated", [self]() {
+ self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
+ }));
+}
+
+/* Sends the given event. Assumes aTopic/aData never goes out of scope (static
+ * strings are ideal).
+ */
+void nsAndroidNetworkLinkService::NotifyObservers(const char* aTopic,
+ const char* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsAndroidNetworkLinkService::NotifyObservers: topic:%s data:%s\n",
+ aTopic, aData ? aData : ""));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(
+ static_cast<nsINetworkLinkService*>(this), aTopic,
+ aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
+ }
+}
diff --git a/netwerk/system/android/nsAndroidNetworkLinkService.h b/netwerk/system/android/nsAndroidNetworkLinkService.h
new file mode 100644
index 0000000000..eaa3e2de4a
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; 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 NSANDROIDNETWORKLINKSERVICE_H_
+#define NSANDROIDNETWORKLINKSERVICE_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "../netlink/NetlinkService.h"
+
+class nsAndroidNetworkLinkService
+ : public nsINetworkLinkService,
+ public nsIObserver,
+ public mozilla::net::NetlinkServiceListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsAndroidNetworkLinkService();
+
+ nsresult Init();
+
+ void OnNetworkChanged() override;
+ void OnNetworkIDChanged() override;
+ void OnLinkUp() override;
+ void OnLinkDown() override;
+ void OnLinkStatusKnown() override;
+ void OnDnsSuffixListUpdated() override;
+
+ private:
+ virtual ~nsAndroidNetworkLinkService() = default;
+
+ // Called when xpcom-shutdown-threads is received.
+ nsresult Shutdown();
+
+ // Sends the network event.
+ void NotifyObservers(const char* aTopic, const char* aData);
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
+
+ RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
+};
+
+#endif /* NSANDROIDNETWORKLINKSERVICE_H_ */
diff --git a/netwerk/system/linux/moz.build b/netwerk/system/linux/moz.build
new file mode 100644
index 0000000000..8ddc517c3e
--- /dev/null
+++ b/netwerk/system/linux/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["OS_ARCH"] == "Linux":
+ SOURCES += [
+ "nsNetworkLinkService.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/linux/nsNetworkLinkService.cpp b/netwerk/system/linux/nsNetworkLinkService.cpp
new file mode 100644
index 0000000000..363b058eed
--- /dev/null
+++ b/netwerk/system/linux/nsNetworkLinkService.cpp
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIObserverService.h"
+#include "nsNetworkLinkService.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+#include "nsNetAddr.h"
+
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNetworkLinkService");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver)
+
+nsNetworkLinkService::nsNetworkLinkService() : mStatusIsKnown(false) {}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetlinkSvc->GetIsLinkUp(aIsUp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
+ *aIsKnown = mStatusIsKnown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ // XXX This function has not yet been implemented for this platform
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetlinkSvc->GetNetworkID(aNetworkID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mNetlinkSvc->GetDnsSuffixList(aDnsSuffixList);
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetResolvers(nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
+ nsTArray<mozilla::net::NetAddr> addresses;
+ nsresult rv = GetNativeResolvers(addresses);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ for (const auto& addr : addresses) {
+ aResolvers.AppendElement(MakeRefPtr<nsNetAddr>(&addr));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetNativeResolvers(
+ nsTArray<mozilla::net::NetAddr>& aResolvers) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mNetlinkSvc->GetResolvers(aResolvers);
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetPlatformDNSIndications(
+ uint32_t* aPlatformDNSIndications) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp("xpcom-shutdown-threads", topic)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNetworkLinkService::Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ rv = observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mNetlinkSvc = new mozilla::net::NetlinkService();
+ rv = mNetlinkSvc->Init(this);
+ if (NS_FAILED(rv)) {
+ mNetlinkSvc = nullptr;
+ LOG(("Cannot initialize NetlinkService [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsNetworkLinkService::Shutdown() {
+ // remove xpcom shutdown observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+ }
+
+ if (mNetlinkSvc) {
+ mNetlinkSvc->Shutdown();
+ mNetlinkSvc = nullptr;
+ }
+
+ return NS_OK;
+}
+
+void nsNetworkLinkService::OnNetworkChanged() {
+ if (StaticPrefs::network_notify_changed()) {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::OnNetworkChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC,
+ NS_NETWORK_LINK_DATA_CHANGED);
+ }));
+ }
+}
+
+void nsNetworkLinkService::OnNetworkIDChanged() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::OnNetworkIDChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+ }));
+}
+
+void nsNetworkLinkService::OnLinkUp() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsNetworkLinkService::OnLinkUp", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UP);
+ }));
+}
+
+void nsNetworkLinkService::OnLinkDown() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsNetworkLinkService::OnLinkDown", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_DOWN);
+ }));
+}
+
+void nsNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; }
+
+void nsNetworkLinkService::OnDnsSuffixListUpdated() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::OnDnsSuffixListUpdated", [self]() {
+ self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
+ }));
+}
+
+/* Sends the given event. Assumes aTopic/aData never goes out of scope (static
+ * strings are ideal).
+ */
+void nsNetworkLinkService::NotifyObservers(const char* aTopic,
+ const char* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsNetworkLinkService::NotifyObservers: topic:%s data:%s\n", aTopic,
+ aData ? aData : ""));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(
+ static_cast<nsINetworkLinkService*>(this), aTopic,
+ aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
+ }
+}
diff --git a/netwerk/system/linux/nsNetworkLinkService.h b/netwerk/system/linux/nsNetworkLinkService.h
new file mode 100644
index 0000000000..d2c4521122
--- /dev/null
+++ b/netwerk/system/linux/nsNetworkLinkService.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NSNETWORKLINKSERVICE_LINUX_H_
+#define NSNETWORKLINKSERVICE_LINUX_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "../netlink/NetlinkService.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Atomics.h"
+
+class nsNetworkLinkService : public nsINetworkLinkService,
+ public nsIObserver,
+ public mozilla::net::NetlinkServiceListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsNetworkLinkService();
+ nsresult Init();
+
+ void OnNetworkChanged() override;
+ void OnNetworkIDChanged() override;
+ void OnLinkUp() override;
+ void OnLinkDown() override;
+ void OnLinkStatusKnown() override;
+ void OnDnsSuffixListUpdated() override;
+
+ private:
+ virtual ~nsNetworkLinkService() = default;
+
+ // Called when xpcom-shutdown-threads is received.
+ nsresult Shutdown();
+
+ // Sends the network event.
+ void NotifyObservers(const char* aTopic, const char* aData);
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
+
+ RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
+};
+
+#endif /* NSNETWORKLINKSERVICE_LINUX_H_ */
diff --git a/netwerk/system/mac/moz.build b/netwerk/system/mac/moz.build
new file mode 100644
index 0000000000..f6436fab56
--- /dev/null
+++ b/netwerk/system/mac/moz.build
@@ -0,0 +1,11 @@
+# -*- 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/.
+
+SOURCES += [
+ "nsNetworkLinkService.mm",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h
new file mode 100644
index 0000000000..d3de10e6a5
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSNETWORKLINKSERVICEMAC_H_
+#define NSNETWORKLINKSERVICEMAC_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsString.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SHA1.h"
+
+#include <netinet/in.h>
+#include <SystemConfiguration/SCNetworkReachability.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+using prefix_and_netmask = std::pair<in6_addr, in6_addr>;
+
+class nsNetworkLinkService : public nsINetworkLinkService,
+ public nsIObserver,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsNetworkLinkService();
+
+ nsresult Init();
+ nsresult Shutdown();
+
+ static void HashSortedPrefixesAndNetmasks(
+ std::vector<prefix_and_netmask> prefixAndNetmaskStore,
+ mozilla::SHA1Sum* sha1);
+
+ protected:
+ virtual ~nsNetworkLinkService();
+
+ private:
+ bool mLinkUp;
+ bool mStatusKnown;
+
+ SCNetworkReachabilityRef mReachability;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mRunLoopSource;
+ SCDynamicStoreRef mStoreRef;
+
+ bool IPv4NetworkId(mozilla::SHA1Sum* sha1);
+ bool IPv6NetworkId(mozilla::SHA1Sum* sha1);
+
+ void UpdateReachability();
+ void OnIPConfigChanged();
+ void OnNetworkIdChanged();
+ void OnReachabilityChanged();
+ void NotifyObservers(const char* aTopic, const char* aData);
+ static void ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags, void* info);
+ static void NetworkConfigChanged(SCDynamicStoreRef store,
+ CFArrayRef changedKeys, void* info);
+ void calculateNetworkIdWithDelay(uint32_t aDelay);
+ void calculateNetworkIdInternal(void);
+ void DNSConfigChanged(uint32_t aDelayMs);
+ void GetDnsSuffixListInternal();
+ bool RoutingFromKernel(nsTArray<nsCString>& aHash);
+ bool RoutingTable(nsTArray<nsCString>& aHash);
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+ nsCString mNetworkId;
+ nsTArray<nsCString> mDNSSuffixList;
+
+ // The timer used to delay the calculation of network id since it takes some
+ // time to discover the gateway's MAC address.
+ nsCOMPtr<nsITimer> mNetworkIdTimer;
+
+ // Scheduled timers used to delay querying of the DNS suffix list when
+ // triggered by a network change. Guarded by mMutex.
+ nsTArray<nsCOMPtr<nsITimer>> mDNSConfigChangedTimers;
+
+ // IP address used to check the route for public traffic.
+ struct in_addr mRouteCheckIPv4;
+};
+
+#endif /* NSNETWORKLINKSERVICEMAC_H_ */
diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm
new file mode 100644
index 0000000000..83271c0712
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -0,0 +1,993 @@
+/* -*- 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 <numeric>
+#include <vector>
+#include <algorithm>
+
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <resolv.h>
+
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "nsNetworkLinkService.h"
+#include "../../base/IPv6Utils.h"
+#include "../LinkServiceCommon.h"
+#include "../NetworkLinkServiceDefines.h"
+
+#import <Cocoa/Cocoa.h>
+#import <netinet/in.h>
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+// See bug 1584165. Sometimes the ARP table is empty or doesn't have
+// the entry of gateway after the network change, so we'd like to delay
+// the calaulation of network id.
+static const uint32_t kNetworkIdDelayAfterChange = 3000;
+
+// When you remove search domains from the settings page and hit Apply a
+// network change event is generated, but res.dnsrch is not updated to the
+// correct values. Thus, after a network change, we add a small delay to
+// the runnable so the OS has the chance to update the values.
+static const uint32_t kDNSSuffixDelayAfterChange = 50;
+
+// If non-successful, extract the error code and return it. This
+// error code dance is inspired by
+// http://developer.apple.com/technotes/tn/tn1145.html
+static OSStatus getErrorCodeBool(Boolean success) {
+ OSStatus err = noErr;
+ if (!success) {
+ int scErr = ::SCError();
+ if (scErr == kSCStatusOK) {
+ scErr = kSCStatusFailed;
+ }
+ err = scErr;
+ }
+ return err;
+}
+
+// If given a NULL pointer, return the error code.
+static OSStatus getErrorCodePtr(const void* value) {
+ return getErrorCodeBool(value != nullptr);
+}
+
+// Convenience function to allow NULL input.
+static void CFReleaseSafe(CFTypeRef cf) {
+ if (cf) {
+ // "If cf is NULL, this will cause a runtime error and your
+ // application will crash." / Apple docs
+ ::CFRelease(cf);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver,
+ nsITimerCallback, nsINamed)
+
+nsNetworkLinkService::nsNetworkLinkService()
+ : mLinkUp(true),
+ mStatusKnown(false),
+ mReachability(nullptr),
+ mCFRunLoop(nullptr),
+ mRunLoopSource(nullptr),
+ mStoreRef(nullptr),
+ mMutex("nsNetworkLinkService::mMutex") {}
+
+nsNetworkLinkService::~nsNetworkLinkService() = default;
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkStatusKnown(bool* aIsUp) {
+ *aIsUp = mStatusKnown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ // XXX This function has not yet been implemented for this platform
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+ MutexAutoLock lock(mMutex);
+ aNetworkID = mNetworkId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetPlatformDNSIndications(
+ uint32_t* aPlatformDNSIndications) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsNetworkLinkService::GetDnsSuffixListInternal() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ LOG(("GetDnsSuffixListInternal"));
+
+ auto sendNotification = mozilla::MakeScopeExit([self = RefPtr{this}] {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::GetDnsSuffixListInternal", [self]() {
+ self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
+ }));
+ });
+
+ nsTArray<nsCString> result;
+
+ struct __res_state res;
+ if (res_ninit(&res) == 0) {
+ for (int i = 0; i < MAXDNSRCH; i++) {
+ if (!res.dnsrch[i]) {
+ break;
+ }
+ LOG(("DNS search domain from [%s]\n", res.dnsrch[i]));
+ result.AppendElement(nsCString(res.dnsrch[i]));
+ }
+ res_nclose(&res);
+ }
+
+ MutexAutoLock lock(mMutex);
+ mDNSSuffixList = std::move(result);
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
+ aDnsSuffixList.Clear();
+
+ MutexAutoLock lock(mMutex);
+ aDnsSuffixList.AppendElements(mDNSSuffixList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetResolvers(nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetNativeResolvers(
+ nsTArray<mozilla::net::NetAddr>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#ifndef SA_SIZE
+# define SA_SIZE(sa) \
+ ((!(sa) || ((struct sockaddr*)(sa))->sa_len == 0) \
+ ? sizeof(uint32_t) \
+ : 1 + ((((struct sockaddr*)(sa))->sa_len - 1) | \
+ (sizeof(uint32_t) - 1)))
+#endif
+
+static bool getMac(struct sockaddr_dl* sdl, char* buf, size_t bufsize) {
+ unsigned char* mac;
+ mac = (unsigned char*)LLADDR(sdl);
+
+ if (sdl->sdl_alen != 6) {
+ LOG(("networkid: unexpected MAC size %u", sdl->sdl_alen));
+ return false;
+ }
+
+ snprintf(buf, bufsize, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1],
+ mac[2], mac[3], mac[4], mac[5]);
+ return true;
+}
+
+/* If the IP matches, get the MAC and return true */
+static bool matchIp(struct sockaddr_dl* sdl, struct sockaddr_inarp* addr,
+ char* ip, char* buf, size_t bufsize) {
+ if (sdl->sdl_alen) {
+ if (!strcmp(inet_ntoa(addr->sin_addr), ip)) {
+ if (getMac(sdl, buf, bufsize)) {
+ return true; /* done! */
+ }
+ }
+ }
+ return false; /* continue */
+}
+
+/*
+ * Scan for the 'IP' address in the ARP table and store the corresponding MAC
+ * address in 'mac'. The output buffer is 'maclen' bytes big.
+ *
+ * Returns 'true' if it found the IP and returns a MAC.
+ */
+static bool scanArp(char* ip, char* mac, size_t maclen) {
+ int mib[6];
+ char *lim, *next;
+ int st;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_FLAGS;
+ mib[5] = RTF_LLINFO;
+
+ size_t needed;
+ auto allocateBuf = [&]() -> UniquePtr<char[]> {
+ // calling sysctl with a null buffer to get the minimum buffer size
+ if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
+ return nullptr;
+ }
+
+ if (needed == 0) {
+ LOG(("scanArp: empty table"));
+ return nullptr;
+ }
+
+ return MakeUnique<char[]>(needed);
+ };
+
+ UniquePtr<char[]> buf = allocateBuf();
+ if (!buf) {
+ return false;
+ }
+
+ st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
+ // If errno is ENOMEM, try to allocate a new buffer and try again.
+ if (st != 0) {
+ if (errno != ENOMEM) {
+ return false;
+ }
+
+ buf = allocateBuf();
+ if (!buf) {
+ return false;
+ }
+
+ st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
+ if (st == -1) {
+ return false;
+ }
+ }
+
+ lim = &buf[needed];
+
+ struct rt_msghdr* rtm;
+ for (next = &buf[0]; next < lim; next += rtm->rtm_msglen) {
+ rtm = reinterpret_cast<struct rt_msghdr*>(next);
+ struct sockaddr_inarp* sin2 =
+ reinterpret_cast<struct sockaddr_inarp*>(rtm + 1);
+ struct sockaddr_dl* sdl =
+ reinterpret_cast<struct sockaddr_dl*>((char*)sin2 + SA_SIZE(sin2));
+ if (matchIp(sdl, sin2, ip, mac, maclen)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Append the mac address of rtm to `stringsToHash`. If it's not in arp table,
+// append ifname and IP address.
+static bool parseHashKey(struct rt_msghdr* rtm,
+ nsTArray<nsCString>& stringsToHash,
+ bool skipDstCheck) {
+ struct sockaddr* sa;
+ struct sockaddr_in* sockin;
+ char ip[INET_ADDRSTRLEN];
+
+ // Ignore the routing table message without destination/gateway sockaddr.
+ // Destination address is needed to check if the gateway is default or
+ // overwritten by VPN. If yes, append the mac address or IP/interface name to
+ // `stringsToHash`.
+ if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) != (RTA_DST | RTA_GATEWAY)) {
+ return false;
+ }
+
+ sa = reinterpret_cast<struct sockaddr*>(rtm + 1);
+
+ struct sockaddr* destination =
+ reinterpret_cast<struct sockaddr*>((char*)sa + RTAX_DST * SA_SIZE(sa));
+ if (!destination || destination->sa_family != AF_INET) {
+ return false;
+ }
+
+ sockin = reinterpret_cast<struct sockaddr_in*>(destination);
+
+ inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
+
+ if (!skipDstCheck && strcmp("0.0.0.0", ip)) {
+ return false;
+ }
+
+ struct sockaddr* gateway = reinterpret_cast<struct sockaddr*>(
+ (char*)sa + RTAX_GATEWAY * SA_SIZE(sa));
+
+ if (!gateway) {
+ return false;
+ }
+ if (gateway->sa_family == AF_INET) {
+ sockin = reinterpret_cast<struct sockaddr_in*>(gateway);
+ inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
+ char mac[18];
+
+ // TODO: cache the arp table instead of multiple system call.
+ if (scanArp(ip, mac, sizeof(mac))) {
+ stringsToHash.AppendElement(nsCString(mac));
+ } else {
+ // Can't find a real MAC address. This might be a VPN gateway.
+ char buf[IFNAMSIZ] = {0};
+ char* ifName = if_indextoname(rtm->rtm_index, buf);
+ if (!ifName) {
+ LOG(("parseHashKey: AF_INET if_indextoname failed"));
+ return false;
+ }
+
+ stringsToHash.AppendElement(nsCString(ifName));
+ stringsToHash.AppendElement(nsCString(ip));
+ }
+ } else if (gateway->sa_family == AF_LINK) {
+ char buf[64];
+ struct sockaddr_dl* sockdl = reinterpret_cast<struct sockaddr_dl*>(gateway);
+ if (getMac(sockdl, buf, sizeof(buf))) {
+ stringsToHash.AppendElement(nsCString(buf));
+ } else {
+ char buf[IFNAMSIZ] = {0};
+ char* ifName = if_indextoname(rtm->rtm_index, buf);
+ if (!ifName) {
+ LOG(("parseHashKey: AF_LINK if_indextoname failed"));
+ return false;
+ }
+
+ stringsToHash.AppendElement(nsCString(ifName));
+ }
+ }
+ return true;
+}
+
+// It detects the IP of the default gateways in the routing table, then the MAC
+// address of that IP in the ARP table before it hashes that string (to avoid
+// information leakage).
+bool nsNetworkLinkService::RoutingTable(nsTArray<nsCString>& aHash) {
+ size_t needed;
+ int mib[6];
+ struct rt_msghdr* rtm;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+
+ if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
+ return false;
+ }
+
+ UniquePtr<char[]> buf(new char[needed]);
+
+ if (sysctl(mib, 6, &buf[0], &needed, nullptr, 0) < 0) {
+ return false;
+ }
+
+ char* lim = &buf[0] + needed;
+ bool rv = false;
+
+ // `next + 1 < lim` ensures we have valid `rtm->rtm_msglen` which is an
+ // unsigned short at the beginning of `rt_msghdr`.
+ for (char* next = &buf[0]; next + 1 < lim; next += rtm->rtm_msglen) {
+ rtm = reinterpret_cast<struct rt_msghdr*>(next);
+
+ if (next + rtm->rtm_msglen > lim) {
+ LOG(("Rt msg is truncated..."));
+ break;
+ }
+
+ if (parseHashKey(rtm, aHash, false)) {
+ rv = true;
+ }
+ }
+ return rv;
+}
+
+// Detect the routing of network.netlink.route.check.IPv4
+bool nsNetworkLinkService::RoutingFromKernel(nsTArray<nsCString>& aHash) {
+ int sockfd;
+ if ((sockfd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) {
+ LOG(("RoutingFromKernel: Can create a socket for network id"));
+ return false;
+ }
+
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ size_t needed = 1024;
+ struct rt_msghdr* rtm;
+ struct sockaddr_in* sin;
+ UniquePtr<char[]> buf(new char[needed]);
+ pid_t pid;
+ int seq;
+
+ rtm = reinterpret_cast<struct rt_msghdr*>(&buf[0]);
+ memset(rtm, 0, sizeof(struct rt_msghdr));
+ rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_type = RTM_GET;
+ rtm->rtm_addrs = RTA_DST;
+ rtm->rtm_pid = (pid = getpid());
+ rtm->rtm_seq = (seq = random());
+
+ sin = reinterpret_cast<struct sockaddr_in*>(rtm + 1);
+ memset(sin, 0, sizeof(struct sockaddr_in));
+ sin->sin_len = sizeof(struct sockaddr_in);
+ sin->sin_family = AF_INET;
+ sin->sin_addr = mRouteCheckIPv4;
+
+ if (write(sockfd, rtm, rtm->rtm_msglen) == -1) {
+ LOG(("RoutingFromKernel: write() failed. No route to the predefine "
+ "destincation"));
+ return false;
+ }
+
+ do {
+ ssize_t r;
+ if ((r = read(sockfd, rtm, needed)) < 0) {
+ LOG(("RoutingFromKernel: read() failed."));
+ return false;
+ }
+
+ LOG(("RoutingFromKernel: read() rtm_type: %d (%d), rtm_pid: %d (%d), "
+ "rtm_seq: %d (%d)\n",
+ rtm->rtm_type, RTM_GET, rtm->rtm_pid, pid, rtm->rtm_seq, seq));
+ } while (rtm->rtm_type != RTM_GET || rtm->rtm_pid != pid ||
+ rtm->rtm_seq != seq);
+
+ return parseHashKey(rtm, aHash, true);
+}
+
+// Figure out the current IPv4 "network identification" string.
+bool nsNetworkLinkService::IPv4NetworkId(SHA1Sum* aSHA1) {
+ nsTArray<nsCString> hash;
+ if (!RoutingTable(hash)) {
+ NS_WARNING("IPv4NetworkId: No default gateways");
+ }
+
+ if (!RoutingFromKernel(hash)) {
+ NS_WARNING("IPv4NetworkId: No route to the predefined destination");
+ }
+
+ // We didn't get any valid hash key to generate network ID.
+ if (hash.IsEmpty()) {
+ LOG(("IPv4NetworkId: No valid hash key"));
+ return false;
+ }
+
+ hash.Sort();
+ for (uint32_t i = 0; i < hash.Length(); ++i) {
+ LOG(("IPv4NetworkId: Hashing string for network id: %s", hash[i].get()));
+ aSHA1->update(hash[i].get(), hash[i].Length());
+ }
+
+ return true;
+}
+
+//
+// Sort and hash the prefixes and netmasks
+//
+void nsNetworkLinkService::HashSortedPrefixesAndNetmasks(
+ std::vector<prefix_and_netmask> prefixAndNetmaskStore, SHA1Sum* sha1) {
+ // getifaddrs does not guarantee the interfaces will always be in the same
+ // order. We want to make sure the hash remains consistent Regardless of the
+ // interface order.
+ std::sort(prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
+ [](prefix_and_netmask a, prefix_and_netmask b) {
+ // compare prefixStore
+ int comparedPrefix = memcmp(&a.first, &b.first, sizeof(in6_addr));
+ if (comparedPrefix == 0) {
+ // compare netmaskStore
+ return memcmp(&a.second, &b.second, sizeof(in6_addr)) < 0;
+ }
+ return comparedPrefix < 0;
+ });
+
+ for (const auto& prefixAndNetmask : prefixAndNetmaskStore) {
+ sha1->update(&prefixAndNetmask.first, sizeof(in6_addr));
+ sha1->update(&prefixAndNetmask.second, sizeof(in6_addr));
+ }
+}
+
+bool nsNetworkLinkService::IPv6NetworkId(SHA1Sum* sha1) {
+ struct ifaddrs* ifap;
+ std::vector<prefix_and_netmask> prefixAndNetmaskStore;
+
+ if (!getifaddrs(&ifap)) {
+ struct ifaddrs* ifa;
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL) {
+ continue;
+ }
+ if ((AF_INET6 == ifa->ifa_addr->sa_family) &&
+ !(ifa->ifa_flags & (IFF_POINTOPOINT | IFF_LOOPBACK))) {
+ // only IPv6 interfaces that aren't pointtopoint or loopback
+ struct sockaddr_in6* sin_netmask =
+ (struct sockaddr_in6*)ifa->ifa_netmask;
+ if (sin_netmask) {
+ struct sockaddr_in6* sin_addr = (struct sockaddr_in6*)ifa->ifa_addr;
+ int scope = net::utils::ipv6_scope(sin_addr->sin6_addr.s6_addr);
+ if (scope == IPV6_SCOPE_GLOBAL) {
+ struct in6_addr prefix;
+ memset(&prefix, 0, sizeof(prefix));
+ // Get the prefix by combining the address and netmask.
+ for (size_t i = 0; i < sizeof(prefix); ++i) {
+ prefix.s6_addr[i] = sin_addr->sin6_addr.s6_addr[i] &
+ sin_netmask->sin6_addr.s6_addr[i];
+ }
+
+ // check if prefix and netmask was already found
+ auto prefixAndNetmask =
+ std::make_pair(prefix, sin_netmask->sin6_addr);
+ auto foundPosition = std::find_if(
+ prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
+ [&prefixAndNetmask](prefix_and_netmask current) {
+ return memcmp(&prefixAndNetmask.first, &current.first,
+ sizeof(in6_addr)) == 0 &&
+ memcmp(&prefixAndNetmask.second, &current.second,
+ sizeof(in6_addr)) == 0;
+ });
+ if (foundPosition != prefixAndNetmaskStore.end()) {
+ continue;
+ }
+ prefixAndNetmaskStore.push_back(prefixAndNetmask);
+ }
+ }
+ }
+ }
+ freeifaddrs(ifap);
+ }
+ if (prefixAndNetmaskStore.empty()) {
+ LOG(("IPv6NetworkId failed"));
+ return false;
+ }
+
+ nsNetworkLinkService::HashSortedPrefixesAndNetmasks(prefixAndNetmaskStore,
+ sha1);
+
+ return true;
+}
+
+void nsNetworkLinkService::calculateNetworkIdWithDelay(uint32_t aDelay) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aDelay) {
+ if (mNetworkIdTimer) {
+ LOG(("Restart the network id timer."));
+ mNetworkIdTimer->Cancel();
+ } else {
+ LOG(("Create the network id timer."));
+ mNetworkIdTimer = NS_NewTimer();
+ }
+ mNetworkIdTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!target) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(target->Dispatch(
+ NewRunnableMethod("nsNetworkLinkService::calculateNetworkIdInternal",
+ this,
+ &nsNetworkLinkService::calculateNetworkIdInternal),
+ NS_DISPATCH_NORMAL));
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(aTimer == mNetworkIdTimer);
+
+ mNetworkIdTimer = nullptr;
+ calculateNetworkIdWithDelay(0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsNetworkLinkService");
+ return NS_OK;
+}
+
+void nsNetworkLinkService::calculateNetworkIdInternal(void) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread");
+ SHA1Sum sha1;
+ bool idChanged = false;
+ bool found4 = IPv4NetworkId(&sha1);
+ bool found6 = IPv6NetworkId(&sha1);
+
+ if (found4 || found6) {
+ nsAutoCString output;
+ SeedNetworkId(sha1);
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsAutoCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ nsresult rv = Base64Encode(newString, output);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ LOG(("networkid: id %s\n", output.get()));
+ MutexAutoLock lock(mMutex);
+ if (mNetworkId != output) {
+ // new id
+ if (found4 && !found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1); // IPv4 only
+ } else if (!found4 && found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3); // IPv6 only
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4); // Both!
+ }
+ mNetworkId = output;
+ idChanged = true;
+ } else {
+ // same id
+ LOG(("Same network id"));
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
+ }
+ } else {
+ // no id
+ LOG(("No network id"));
+ MutexAutoLock lock(mMutex);
+ if (!mNetworkId.IsEmpty()) {
+ mNetworkId.Truncate();
+ idChanged = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
+ }
+ }
+
+ // Don't report network change if this is the first time we calculate the id.
+ static bool initialIDCalculation = true;
+ if (idChanged && !initialIDCalculation) {
+ RefPtr<nsNetworkLinkService> self = this;
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::calculateNetworkIdInternal",
+ [self]() { self->OnNetworkIdChanged(); }));
+ }
+
+ initialIDCalculation = false;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsNetworkLinkService::NetworkConfigChanged(SCDynamicStoreRef aStoreREf,
+ CFArrayRef aChangedKeys,
+ void* aInfo) {
+ LOG(("nsNetworkLinkService::NetworkConfigChanged"));
+
+ bool ipConfigChanged = false;
+ bool dnsConfigChanged = false;
+ for (CFIndex i = 0; i < CFArrayGetCount(aChangedKeys); ++i) {
+ CFStringRef key =
+ static_cast<CFStringRef>(CFArrayGetValueAtIndex(aChangedKeys, i));
+ if (CFStringHasSuffix(key, kSCEntNetIPv4) ||
+ CFStringHasSuffix(key, kSCEntNetIPv6)) {
+ ipConfigChanged = true;
+ }
+ if (CFStringHasSuffix(key, kSCEntNetDNS)) {
+ dnsConfigChanged = true;
+ }
+ }
+
+ nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(aInfo);
+ if (ipConfigChanged) {
+ service->OnIPConfigChanged();
+ }
+
+ if (dnsConfigChanged) {
+ service->DNSConfigChanged(kDNSSuffixDelayAfterChange);
+ }
+}
+
+void nsNetworkLinkService::DNSConfigChanged(uint32_t aDelayMs) {
+ LOG(("nsNetworkLinkService::DNSConfigChanged"));
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!target) {
+ return;
+ }
+ if (aDelayMs) {
+ MutexAutoLock lock(mMutex);
+ nsCOMPtr<nsITimer> timer;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewTimerWithCallback(
+ getter_AddRefs(timer),
+ [self = RefPtr{this}](nsITimer* aTimer) {
+ self->GetDnsSuffixListInternal();
+
+ MutexAutoLock lock(self->mMutex);
+ self->mDNSConfigChangedTimers.RemoveElement(aTimer);
+ },
+ TimeDuration::FromMilliseconds(aDelayMs), nsITimer::TYPE_ONE_SHOT,
+ "nsNetworkLinkService::GetDnsSuffixListInternal", target));
+ mDNSConfigChangedTimers.AppendElement(timer);
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(target->Dispatch(NS_NewRunnableFunction(
+ "nsNetworkLinkService::GetDnsSuffixListInternal",
+ [self = RefPtr{this}]() { self->GetDnsSuffixListInternal(); })));
+ }
+}
+
+nsresult nsNetworkLinkService::Init(void) {
+ nsresult rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "xpcom-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (inet_pton(AF_INET, ROUTE_CHECK_IPV4, &mRouteCheckIPv4) != 1) {
+ LOG(("Cannot parse address " ROUTE_CHECK_IPV4));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV4);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If the network reachability API can reach 0.0.0.0 without
+ // requiring a connection, there is a network interface available.
+ struct sockaddr_in addr;
+ bzero(&addr, sizeof(addr));
+ addr.sin_len = sizeof(addr);
+ addr.sin_family = AF_INET;
+ mReachability = ::SCNetworkReachabilityCreateWithAddress(
+ nullptr, (struct sockaddr*)&addr);
+ if (!mReachability) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCNetworkReachabilityContext context = {0, this, nullptr, nullptr, nullptr};
+ if (!::SCNetworkReachabilitySetCallback(mReachability, ReachabilityChanged,
+ &context)) {
+ NS_WARNING("SCNetworkReachabilitySetCallback failed.");
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCDynamicStoreContext storeContext = {0, this, nullptr, nullptr, nullptr};
+ mStoreRef =
+ ::SCDynamicStoreCreate(nullptr, CFSTR("IPAndDNSChangeCallbackSCF"),
+ NetworkConfigChanged, &storeContext);
+
+ CFStringRef patterns[4] = {nullptr, nullptr, nullptr, nullptr};
+ OSStatus err = getErrorCodePtr(mStoreRef);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv4".
+ patterns[0] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
+ // This pattern is "State:/Network/Service/[^/]+/IPv6".
+ patterns[1] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6);
+ // This pattern is "State:/Network/Service/[^/]+/DNS".
+ patterns[2] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetDNS);
+ // This pattern is "Setup:/Network/Service/[^/]+/DNS".
+ patterns[3] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetDNS);
+ if (!patterns[0] || !patterns[1] || !patterns[2] || !patterns[3]) {
+ err = -1;
+ }
+ }
+
+ CFArrayRef patternList = nullptr;
+ // Create a pattern list containing just one pattern,
+ // then tell SCF that we want to watch changes in keys
+ // that match that pattern list, then create our run loop
+ // source.
+ if (err == noErr) {
+ patternList = ::CFArrayCreate(nullptr, (const void**)patterns, 4,
+ &kCFTypeArrayCallBacks);
+ if (!patternList) {
+ err = -1;
+ }
+ }
+ if (err == noErr) {
+ err = getErrorCodeBool(
+ ::SCDynamicStoreSetNotificationKeys(mStoreRef, nullptr, patternList));
+ }
+
+ if (err == noErr) {
+ mRunLoopSource = ::SCDynamicStoreCreateRunLoopSource(nullptr, mStoreRef, 0);
+ err = getErrorCodePtr(mRunLoopSource);
+ }
+
+ CFReleaseSafe(patterns[0]);
+ CFReleaseSafe(patterns[1]);
+ CFReleaseSafe(patterns[2]);
+ CFReleaseSafe(patterns[3]);
+ CFReleaseSafe(patternList);
+
+ if (err != noErr) {
+ CFReleaseSafe(mStoreRef);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Get the current run loop. This service is initialized at startup,
+ // so we shouldn't run in to any problems with modal dialog run loops.
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ if (!mCFRunLoop) {
+ NS_WARNING("Could not get current run loop.");
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ ::CFRetain(mCFRunLoop);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ UpdateReachability();
+
+ calculateNetworkIdWithDelay(0);
+
+ DNSConfigChanged(0);
+
+ return NS_OK;
+}
+
+nsresult nsNetworkLinkService::Shutdown() {
+ if (!::SCNetworkReachabilityUnscheduleFromRunLoop(mReachability, mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityUnscheduleFromRunLoop failed.");
+ }
+
+ CFRunLoopRemoveSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = nullptr;
+
+ ::CFRelease(mStoreRef);
+ mStoreRef = nullptr;
+
+ ::CFRelease(mRunLoopSource);
+ mRunLoopSource = nullptr;
+
+ if (mNetworkIdTimer) {
+ mNetworkIdTimer->Cancel();
+ mNetworkIdTimer = nullptr;
+ }
+
+ nsTArray<nsCOMPtr<nsITimer>> dnsConfigChangedTimers;
+ {
+ MutexAutoLock lock(mMutex);
+ dnsConfigChangedTimers = std::move(mDNSConfigChangedTimers);
+ mDNSConfigChangedTimers.Clear();
+ }
+ for (const auto& timer : dnsConfigChangedTimers) {
+ timer->Cancel();
+ }
+
+ return NS_OK;
+}
+
+void nsNetworkLinkService::UpdateReachability() {
+ if (!mReachability) {
+ return;
+ }
+
+ SCNetworkConnectionFlags flags;
+ if (!::SCNetworkReachabilityGetFlags(mReachability, &flags)) {
+ mStatusKnown = false;
+ return;
+ }
+
+ bool reachable = (flags & kSCNetworkFlagsReachable) != 0;
+ bool needsConnection = (flags & kSCNetworkFlagsConnectionRequired) != 0;
+
+ mLinkUp = (reachable && !needsConnection);
+ mStatusKnown = true;
+}
+
+void nsNetworkLinkService::OnIPConfigChanged() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
+ if (!StaticPrefs::network_notify_changed()) {
+ return;
+ }
+
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_CHANGED);
+}
+
+void nsNetworkLinkService::OnNetworkIdChanged() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+}
+
+void nsNetworkLinkService::OnReachabilityChanged() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mStatusKnown) {
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UNKNOWN);
+ return;
+ }
+
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, mLinkUp ? NS_NETWORK_LINK_DATA_UP
+ : NS_NETWORK_LINK_DATA_DOWN);
+}
+
+void nsNetworkLinkService::NotifyObservers(const char* aTopic,
+ const char* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsNetworkLinkService::NotifyObservers: topic:%s data:%s\n", aTopic,
+ aData ? aData : ""));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(
+ static_cast<nsINetworkLinkService*>(this), aTopic,
+ aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
+ }
+}
+
+/* static */
+void nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void* info) {
+ LOG(("nsNetworkLinkService::ReachabilityChanged"));
+ nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(info);
+
+ service->UpdateReachability();
+ service->OnReachabilityChanged();
+ service->calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
+ // If a new interface is up or the order of interfaces is changed, we should
+ // update the DNS suffix list.
+ service->DNSConfigChanged(0);
+}
diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build
new file mode 100644
index 0000000000..b70eb40aaa
--- /dev/null
+++ b/netwerk/system/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ DIRS += ["win32"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ DIRS += ["mac"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ DIRS += ["android", "netlink"]
+
+elif CONFIG["OS_ARCH"] == "Linux":
+ DIRS += ["linux", "netlink"]
+
+SOURCES += [
+ "LinkServiceCommon.cpp",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/netlink/NetlinkService.cpp b/netwerk/system/netlink/NetlinkService.cpp
new file mode 100644
index 0000000000..f50284af0c
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.cpp
@@ -0,0 +1,1903 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <arpa/inet.h>
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <poll.h>
+#include <unistd.h>
+#include <linux/rtnetlink.h>
+
+#include "nsThreadUtils.h"
+#include "NetlinkService.h"
+#include "nsIThread.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Logging.h"
+#include "../../base/IPv6Utils.h"
+#include "../LinkServiceCommon.h"
+#include "../NetworkLinkServiceDefines.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/FunctionTypeTraits.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+
+#if defined(HAVE_RES_NINIT)
+# include <netinet/in.h>
+# include <resolv.h>
+#endif
+
+namespace mozilla::net {
+
+template <typename F>
+static auto eintr_retry(F&& func) ->
+ typename FunctionTypeTraits<decltype(func)>::ReturnType {
+ typename FunctionTypeTraits<decltype(func)>::ReturnType _rc;
+ do {
+ _rc = func();
+ } while (_rc == -1 && errno == EINTR);
+ return _rc;
+}
+
+#define EINTR_RETRY(expr) eintr_retry([&]() { return expr; })
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+static LazyLogModule gNlSvcLog("NetlinkService");
+#define LOG(args) MOZ_LOG(gNlSvcLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gNlSvcLog, mozilla::LogLevel::Debug)
+
+using in_common_addr = union {
+ struct in_addr addr4;
+ struct in6_addr addr6;
+};
+
+static void GetAddrStr(const in_common_addr* aAddr, uint8_t aFamily,
+ nsACString& _retval) {
+ char addr[INET6_ADDRSTRLEN];
+ addr[0] = 0;
+
+ if (aFamily == AF_INET) {
+ inet_ntop(AF_INET, &(aAddr->addr4), addr, INET_ADDRSTRLEN);
+ } else {
+ inet_ntop(AF_INET6, &(aAddr->addr6), addr, INET6_ADDRSTRLEN);
+ }
+ _retval.Assign(addr);
+}
+
+class NetlinkAddress {
+ public:
+ NetlinkAddress() = default;
+
+ uint8_t Family() const { return mIfam.ifa_family; }
+ uint32_t GetIndex() const { return mIfam.ifa_index; }
+ uint8_t GetPrefixLen() const { return mIfam.ifa_prefixlen; }
+ bool ScopeIsUniverse() const { return mIfam.ifa_scope == RT_SCOPE_UNIVERSE; }
+ const in_common_addr* GetAddrPtr() const { return &mAddr; }
+
+ bool MsgEquals(const NetlinkAddress& aOther) const {
+ return !memcmp(&mIfam, &(aOther.mIfam), sizeof(mIfam));
+ }
+
+ bool Equals(const NetlinkAddress& aOther) const {
+ if (mIfam.ifa_family != aOther.mIfam.ifa_family) {
+ return false;
+ }
+ if (mIfam.ifa_index != aOther.mIfam.ifa_index) {
+ // addresses are different when they are on a different interface
+ return false;
+ }
+ if (mIfam.ifa_prefixlen != aOther.mIfam.ifa_prefixlen) {
+ // It's possible to have two equal addresses with a different netmask on
+ // the same interface, so we need to check prefixlen too.
+ return false;
+ }
+ size_t addrSize = (mIfam.ifa_family == AF_INET) ? sizeof(mAddr.addr4)
+ : sizeof(mAddr.addr6);
+ return memcmp(&mAddr, aOther.GetAddrPtr(), addrSize) == 0;
+ }
+
+ bool ContainsAddr(const in_common_addr* aAddr) {
+ int32_t addrSize = (mIfam.ifa_family == AF_INET)
+ ? (int32_t)sizeof(mAddr.addr4)
+ : (int32_t)sizeof(mAddr.addr6);
+ uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
+ int32_t bits = mIfam.ifa_prefixlen;
+ if (bits > addrSize * 8) {
+ MOZ_ASSERT(false, "Unexpected prefix length!");
+ LOG(("Unexpected prefix length %d, maximum for this family is %d", bits,
+ addrSize * 8));
+ return false;
+ }
+ for (int32_t i = 0; i < addrSize; i++) {
+ uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
+ if ((((unsigned char*)aAddr)[i] & mask) !=
+ (((unsigned char*)(&mAddr))[i] & mask)) {
+ return false;
+ }
+ bits -= 8;
+ if (bits <= 0) {
+ return true;
+ }
+ }
+ return true;
+ }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct ifaddrmsg* ifam;
+ struct rtattr* attr;
+ int len;
+
+ ifam = (ifaddrmsg*)NLMSG_DATA(aNlh);
+ len = IFA_PAYLOAD(aNlh);
+
+ if (ifam->ifa_family != AF_INET && ifam->ifa_family != AF_INET6) {
+ return false;
+ }
+
+ bool hasAddr = false;
+ for (attr = IFA_RTA(ifam); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == IFA_ADDRESS || attr->rta_type == IFA_LOCAL) {
+ memcpy(&mAddr, RTA_DATA(attr),
+ ifam->ifa_family == AF_INET ? sizeof(mAddr.addr4)
+ : sizeof(mAddr.addr6));
+ hasAddr = true;
+ if (attr->rta_type == IFA_LOCAL) {
+ // local address is preferred, so don't continue parsing other
+ // attributes
+ break;
+ }
+ }
+ }
+
+ if (!hasAddr) {
+ return false;
+ }
+
+ memcpy(&mIfam, (ifaddrmsg*)NLMSG_DATA(aNlh), sizeof(mIfam));
+ return true;
+ }
+
+ private:
+ in_common_addr mAddr;
+ struct ifaddrmsg mIfam;
+};
+
+class NetlinkNeighbor {
+ public:
+ NetlinkNeighbor() = default;
+
+ uint8_t Family() const { return mNeigh.ndm_family; }
+ uint32_t GetIndex() const { return mNeigh.ndm_ifindex; }
+ const in_common_addr* GetAddrPtr() const { return &mAddr; }
+ const uint8_t* GetMACPtr() const { return mMAC; }
+ bool HasMAC() const { return mHasMAC; }
+
+ void GetAsString(nsACString& _retval) const {
+ nsAutoCString addrStr;
+ _retval.Assign("addr=");
+ GetAddrStr(&mAddr, mNeigh.ndm_family, addrStr);
+ _retval.Append(addrStr);
+ if (mNeigh.ndm_family == AF_INET) {
+ _retval.Append(" family=AF_INET if=");
+ } else {
+ _retval.Append(" family=AF_INET6 if=");
+ }
+ _retval.AppendInt(mNeigh.ndm_ifindex);
+ if (mHasMAC) {
+ _retval.Append(" mac=");
+ _retval.Append(nsPrintfCString("%02x:%02x:%02x:%02x:%02x:%02x", mMAC[0],
+ mMAC[1], mMAC[2], mMAC[3], mMAC[4],
+ mMAC[5]));
+ }
+ }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct ndmsg* neigh;
+ struct rtattr* attr;
+ int len;
+
+ neigh = (ndmsg*)NLMSG_DATA(aNlh);
+ len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*neigh));
+
+ if (neigh->ndm_family != AF_INET && neigh->ndm_family != AF_INET6) {
+ return false;
+ }
+
+ bool hasDST = false;
+ for (attr = RTM_RTA(neigh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == NDA_LLADDR) {
+ memcpy(mMAC, RTA_DATA(attr), ETH_ALEN);
+ mHasMAC = true;
+ }
+
+ if (attr->rta_type == NDA_DST) {
+ memcpy(&mAddr, RTA_DATA(attr),
+ neigh->ndm_family == AF_INET ? sizeof(mAddr.addr4)
+ : sizeof(mAddr.addr6));
+ hasDST = true;
+ }
+ }
+
+ if (!hasDST) {
+ return false;
+ }
+
+ memcpy(&mNeigh, (ndmsg*)NLMSG_DATA(aNlh), sizeof(mNeigh));
+ return true;
+ }
+
+ private:
+ bool mHasMAC{false};
+ uint8_t mMAC[ETH_ALEN]{};
+ in_common_addr mAddr{};
+ struct ndmsg mNeigh {};
+};
+
+class NetlinkLink {
+ public:
+ NetlinkLink() = default;
+
+ bool IsUp() const {
+ return (mIface.ifi_flags & IFF_RUNNING) &&
+ !(mIface.ifi_flags & IFF_LOOPBACK);
+ }
+
+ void GetName(nsACString& _retval) const { _retval = mName; }
+ bool IsTypeEther() const { return mIface.ifi_type == ARPHRD_ETHER; }
+ uint32_t GetIndex() const { return mIface.ifi_index; }
+ uint32_t GetFlags() const { return mIface.ifi_flags; }
+ uint16_t GetType() const { return mIface.ifi_type; }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct ifinfomsg* iface;
+ struct rtattr* attr;
+ int len;
+
+ iface = (ifinfomsg*)NLMSG_DATA(aNlh);
+ len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*iface));
+
+ bool hasName = false;
+ for (attr = IFLA_RTA(iface); RTA_OK(attr, len);
+ attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == IFLA_IFNAME) {
+ mName.Assign((char*)RTA_DATA(attr));
+ hasName = true;
+ break;
+ }
+ }
+
+ if (!hasName) {
+ return false;
+ }
+
+ memcpy(&mIface, (ifinfomsg*)NLMSG_DATA(aNlh), sizeof(mIface));
+ return true;
+ }
+
+ private:
+ nsCString mName;
+ struct ifinfomsg mIface {};
+};
+
+class NetlinkRoute {
+ public:
+ NetlinkRoute()
+ : mHasGWAddr(false),
+ mHasPrefSrcAddr(false),
+ mHasDstAddr(false),
+ mHasOif(false),
+ mHasPrio(false) {}
+
+ bool IsUnicast() const { return mRtm.rtm_type == RTN_UNICAST; }
+ bool ScopeIsUniverse() const { return mRtm.rtm_scope == RT_SCOPE_UNIVERSE; }
+ bool IsDefault() const { return mRtm.rtm_dst_len == 0; }
+ bool HasOif() const { return mHasOif; }
+ uint8_t Oif() const { return mOif; }
+ uint8_t Family() const { return mRtm.rtm_family; }
+ bool HasPrefSrcAddr() const { return mHasPrefSrcAddr; }
+ const in_common_addr* GetGWAddrPtr() const {
+ return mHasGWAddr ? &mGWAddr : nullptr;
+ }
+ const in_common_addr* GetPrefSrcAddrPtr() const {
+ return mHasPrefSrcAddr ? &mPrefSrcAddr : nullptr;
+ }
+
+ bool Equals(const NetlinkRoute& aOther) const {
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
+ : sizeof(mDstAddr.addr6);
+ if (memcmp(&mRtm, &(aOther.mRtm), sizeof(mRtm)) != 0) {
+ return false;
+ }
+ if (mHasOif != aOther.mHasOif || mOif != aOther.mOif) {
+ return false;
+ }
+ if (mHasPrio != aOther.mHasPrio || mPrio != aOther.mPrio) {
+ return false;
+ }
+ if ((mHasGWAddr != aOther.mHasGWAddr) ||
+ (mHasGWAddr && memcmp(&mGWAddr, &(aOther.mGWAddr), addrSize) != 0)) {
+ return false;
+ }
+ if ((mHasDstAddr != aOther.mHasDstAddr) ||
+ (mHasDstAddr && memcmp(&mDstAddr, &(aOther.mDstAddr), addrSize) != 0)) {
+ return false;
+ }
+ if ((mHasPrefSrcAddr != aOther.mHasPrefSrcAddr) ||
+ (mHasPrefSrcAddr &&
+ memcmp(&mPrefSrcAddr, &(aOther.mPrefSrcAddr), addrSize) != 0)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool GatewayEquals(const NetlinkNeighbor& aNeigh) const {
+ if (!mHasGWAddr) {
+ return false;
+ }
+ if (aNeigh.Family() != mRtm.rtm_family) {
+ return false;
+ }
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+ : sizeof(mGWAddr.addr6);
+ return memcmp(&mGWAddr, aNeigh.GetAddrPtr(), addrSize) == 0;
+ }
+
+ bool GatewayEquals(const NetlinkRoute* aRoute) const {
+ if (!mHasGWAddr || !aRoute->mHasGWAddr) {
+ return false;
+ }
+ if (mRtm.rtm_family != aRoute->mRtm.rtm_family) {
+ return false;
+ }
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+ : sizeof(mGWAddr.addr6);
+ return memcmp(&mGWAddr, &(aRoute->mGWAddr), addrSize) == 0;
+ }
+
+ bool PrefSrcAddrEquals(const NetlinkAddress& aAddress) const {
+ if (!mHasPrefSrcAddr) {
+ return false;
+ }
+ if (mRtm.rtm_family != aAddress.Family()) {
+ return false;
+ }
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
+ : sizeof(mPrefSrcAddr.addr6);
+ return memcmp(&mPrefSrcAddr, aAddress.GetAddrPtr(), addrSize) == 0;
+ }
+
+ void GetAsString(nsACString& _retval) const {
+ nsAutoCString addrStr;
+ _retval.Assign("table=");
+ _retval.AppendInt(mRtm.rtm_table);
+ _retval.Append(" type=");
+ _retval.AppendInt(mRtm.rtm_type);
+ _retval.Append(" scope=");
+ _retval.AppendInt(mRtm.rtm_scope);
+ if (mRtm.rtm_family == AF_INET) {
+ _retval.Append(" family=AF_INET dst=");
+ addrStr.Assign("0.0.0.0/");
+ } else {
+ _retval.Append(" family=AF_INET6 dst=");
+ addrStr.Assign("::/");
+ }
+ if (mHasDstAddr) {
+ GetAddrStr(&mDstAddr, mRtm.rtm_family, addrStr);
+ addrStr.Append("/");
+ }
+ _retval.Append(addrStr);
+ _retval.AppendInt(mRtm.rtm_dst_len);
+ if (mHasPrefSrcAddr) {
+ _retval.Append(" src=");
+ GetAddrStr(&mPrefSrcAddr, mRtm.rtm_family, addrStr);
+ _retval.Append(addrStr);
+ }
+ if (mHasGWAddr) {
+ _retval.Append(" via=");
+ GetAddrStr(&mGWAddr, mRtm.rtm_family, addrStr);
+ _retval.Append(addrStr);
+ }
+ if (mHasOif) {
+ _retval.Append(" oif=");
+ _retval.AppendInt(mOif);
+ }
+ if (mHasPrio) {
+ _retval.Append(" prio=");
+ _retval.AppendInt(mPrio);
+ }
+ }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct rtmsg* rtm;
+ struct rtattr* attr;
+ int len;
+
+ rtm = (rtmsg*)NLMSG_DATA(aNlh);
+ len = RTM_PAYLOAD(aNlh);
+
+ if (rtm->rtm_family != AF_INET && rtm->rtm_family != AF_INET6) {
+ return false;
+ }
+
+ for (attr = RTM_RTA(rtm); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == RTA_DST) {
+ memcpy(&mDstAddr, RTA_DATA(attr),
+ (rtm->rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
+ : sizeof(mDstAddr.addr6));
+ mHasDstAddr = true;
+ } else if (attr->rta_type == RTA_GATEWAY) {
+ memcpy(&mGWAddr, RTA_DATA(attr),
+ (rtm->rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+ : sizeof(mGWAddr.addr6));
+ mHasGWAddr = true;
+ } else if (attr->rta_type == RTA_PREFSRC) {
+ memcpy(&mPrefSrcAddr, RTA_DATA(attr),
+ (rtm->rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
+ : sizeof(mPrefSrcAddr.addr6));
+ mHasPrefSrcAddr = true;
+ } else if (attr->rta_type == RTA_OIF) {
+ mOif = *(uint32_t*)RTA_DATA(attr);
+ mHasOif = true;
+ } else if (attr->rta_type == RTA_PRIORITY) {
+ mPrio = *(uint32_t*)RTA_DATA(attr);
+ mHasPrio = true;
+ }
+ }
+
+ memcpy(&mRtm, (rtmsg*)NLMSG_DATA(aNlh), sizeof(mRtm));
+ return true;
+ }
+
+ private:
+ bool mHasGWAddr : 1;
+ bool mHasPrefSrcAddr : 1;
+ bool mHasDstAddr : 1;
+ bool mHasOif : 1;
+ bool mHasPrio : 1;
+
+ in_common_addr mGWAddr{};
+ in_common_addr mDstAddr{};
+ in_common_addr mPrefSrcAddr{};
+
+ uint32_t mOif{};
+ uint32_t mPrio{};
+ struct rtmsg mRtm {};
+};
+
+class NetlinkMsg {
+ public:
+ static uint8_t const kGenMsg = 1;
+ static uint8_t const kRtMsg = 2;
+
+ NetlinkMsg() = default;
+ virtual ~NetlinkMsg() = default;
+
+ virtual bool Send(int aFD) = 0;
+ virtual bool IsPending() { return mIsPending; }
+ virtual uint32_t SeqId() = 0;
+ virtual uint8_t Family() = 0;
+ virtual uint8_t MsgType() = 0;
+
+ protected:
+ bool SendRequest(int aFD, void* aRequest, uint32_t aRequestLength) {
+ MOZ_ASSERT(!mIsPending, "Request has been already sent!");
+
+ struct sockaddr_nl kernel {};
+ memset(&kernel, 0, sizeof(kernel));
+ kernel.nl_family = AF_NETLINK;
+ kernel.nl_groups = 0;
+
+ struct iovec io {};
+ memset(&io, 0, sizeof(io));
+ io.iov_base = aRequest;
+ io.iov_len = aRequestLength;
+
+ struct msghdr rtnl_msg {};
+ memset(&rtnl_msg, 0, sizeof(rtnl_msg));
+ rtnl_msg.msg_iov = &io;
+ rtnl_msg.msg_iovlen = 1;
+ rtnl_msg.msg_name = &kernel;
+ rtnl_msg.msg_namelen = sizeof(kernel);
+
+ ssize_t rc = EINTR_RETRY(sendmsg(aFD, (struct msghdr*)&rtnl_msg, 0));
+ if (rc > 0 && (uint32_t)rc == aRequestLength) {
+ mIsPending = true;
+ }
+
+ return mIsPending;
+ }
+
+ bool mIsPending{false};
+};
+
+class NetlinkGenMsg : public NetlinkMsg {
+ public:
+ NetlinkGenMsg(uint16_t aMsgType, uint8_t aFamily, uint32_t aSeqId) {
+ memset(&mReq, 0, sizeof(mReq));
+
+ mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
+ mReq.hdr.nlmsg_type = aMsgType;
+ mReq.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ mReq.hdr.nlmsg_seq = aSeqId;
+ mReq.hdr.nlmsg_pid = 0;
+
+ mReq.gen.rtgen_family = aFamily;
+ }
+
+ virtual bool Send(int aFD) {
+ return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
+ }
+
+ virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
+ virtual uint8_t Family() { return mReq.gen.rtgen_family; }
+ virtual uint8_t MsgType() { return kGenMsg; }
+
+ private:
+ struct {
+ struct nlmsghdr hdr;
+ struct rtgenmsg gen;
+ } mReq{};
+};
+
+class NetlinkRtMsg : public NetlinkMsg {
+ public:
+ NetlinkRtMsg(uint8_t aFamily, void* aAddress, uint32_t aSeqId) {
+ MOZ_ASSERT(aFamily == AF_INET || aFamily == AF_INET6);
+
+ memset(&mReq, 0, sizeof(mReq));
+
+ mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ mReq.hdr.nlmsg_type = RTM_GETROUTE;
+ mReq.hdr.nlmsg_flags = NLM_F_REQUEST;
+ mReq.hdr.nlmsg_seq = aSeqId;
+ mReq.hdr.nlmsg_pid = 0;
+
+ mReq.rtm.rtm_family = aFamily;
+ mReq.rtm.rtm_flags = 0;
+ mReq.rtm.rtm_dst_len = aFamily == AF_INET ? 32 : 128;
+
+ struct rtattr* rta;
+ rta = (struct rtattr*)(((char*)&mReq) + NLMSG_ALIGN(mReq.hdr.nlmsg_len));
+ rta->rta_type = RTA_DST;
+ size_t addrSize =
+ aFamily == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr);
+ rta->rta_len = RTA_LENGTH(addrSize);
+ memcpy(RTA_DATA(rta), aAddress, addrSize);
+ mReq.hdr.nlmsg_len = NLMSG_ALIGN(mReq.hdr.nlmsg_len) + RTA_LENGTH(addrSize);
+ }
+
+ virtual bool Send(int aFD) {
+ return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
+ }
+
+ virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
+ virtual uint8_t Family() { return mReq.rtm.rtm_family; }
+ virtual uint8_t MsgType() { return kRtMsg; }
+
+ private:
+ struct {
+ struct nlmsghdr hdr;
+ struct rtmsg rtm;
+ unsigned char data[1024];
+ } mReq{};
+};
+
+NetlinkService::LinkInfo::LinkInfo(UniquePtr<NetlinkLink>&& aLink)
+ : mLink(std::move(aLink)), mIsUp(false) {}
+
+NetlinkService::LinkInfo::~LinkInfo() = default;
+
+bool NetlinkService::LinkInfo::UpdateStatus() {
+ LOG(("NetlinkService::LinkInfo::UpdateStatus"));
+
+ bool oldIsUp = mIsUp;
+ mIsUp = false;
+
+ if (!mLink->IsUp()) {
+ // The link is not up or is a loopback
+ LOG(("The link is down or is a loopback"));
+ } else {
+ // Link is up when there is non-local address associated with it.
+ for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
+ if (LOG_ENABLED()) {
+ nsAutoCString dbgStr;
+ GetAddrStr(mAddresses[i]->GetAddrPtr(), mAddresses[i]->Family(),
+ dbgStr);
+ LOG(("checking address %s", dbgStr.get()));
+ }
+ if (mAddresses[i]->ScopeIsUniverse()) {
+ mIsUp = true;
+ LOG(("global address found"));
+ break;
+ }
+ }
+ }
+
+ return mIsUp == oldIsUp;
+}
+
+NS_IMPL_ISUPPORTS(NetlinkService, nsIRunnable)
+
+NetlinkService::NetlinkService() : mPid(getpid()) {}
+
+NetlinkService::~NetlinkService() {
+ MOZ_ASSERT(!mThread, "NetlinkService thread shutdown failed");
+
+ if (mShutdownPipe[0] != -1) {
+ EINTR_RETRY(close(mShutdownPipe[0]));
+ }
+ if (mShutdownPipe[1] != -1) {
+ EINTR_RETRY(close(mShutdownPipe[1]));
+ }
+}
+
+void NetlinkService::OnNetlinkMessage(int aNetlinkSocket) {
+ // The buffer size 4096 is a common page size, which is a recommended limit
+ // for netlink messages.
+ char buffer[4096];
+
+ struct sockaddr_nl kernel {};
+ memset(&kernel, 0, sizeof(kernel));
+ kernel.nl_family = AF_NETLINK;
+ kernel.nl_groups = 0;
+
+ struct iovec io {};
+ memset(&io, 0, sizeof(io));
+ io.iov_base = buffer;
+ io.iov_len = sizeof(buffer);
+
+ struct msghdr rtnl_reply {};
+ memset(&rtnl_reply, 0, sizeof(rtnl_reply));
+ rtnl_reply.msg_iov = &io;
+ rtnl_reply.msg_iovlen = 1;
+ rtnl_reply.msg_name = &kernel;
+ rtnl_reply.msg_namelen = sizeof(kernel);
+
+ ssize_t rc = EINTR_RETRY(recvmsg(aNetlinkSocket, &rtnl_reply, MSG_DONTWAIT));
+ if (rc < 0) {
+ return;
+ }
+ size_t netlink_bytes = rc;
+
+ struct nlmsghdr* nlh = reinterpret_cast<struct nlmsghdr*>(buffer);
+
+ for (; NLMSG_OK(nlh, netlink_bytes); nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
+ // If PID in the message is our PID, then it's a response to our request.
+ // Otherwise it's a multicast message.
+ bool isResponse = (pid_t)nlh->nlmsg_pid == mPid;
+ if (isResponse) {
+ if (!mOutgoingMessages.Length() || !mOutgoingMessages[0]->IsPending()) {
+ // There is no enqueued message pending?
+ LOG((
+ "Ignoring message seq_id %u, because there is no associated message"
+ " pending",
+ nlh->nlmsg_seq));
+ continue;
+ }
+
+ if (mOutgoingMessages[0]->SeqId() != nlh->nlmsg_seq) {
+ LOG(("Received unexpected seq_id [received=%u, expected=%u]",
+ nlh->nlmsg_seq, mOutgoingMessages[0]->SeqId()));
+ RemovePendingMsg();
+ continue;
+ }
+ }
+
+ switch (nlh->nlmsg_type) {
+ case NLMSG_DONE: /* Message signalling end of dump for responses to
+ request containing NLM_F_DUMP flag */
+ LOG(("received NLMSG_DONE"));
+ if (isResponse) {
+ RemovePendingMsg();
+ }
+ break;
+ case NLMSG_ERROR:
+ LOG(("received NLMSG_ERROR"));
+ if (isResponse) {
+ if (mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg) {
+ OnRouteCheckResult(nullptr);
+ }
+ RemovePendingMsg();
+ }
+ break;
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ MOZ_ASSERT(!isResponse ||
+ (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+ OnLinkMessage(nlh);
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ MOZ_ASSERT(!isResponse ||
+ (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+ OnAddrMessage(nlh);
+ break;
+ case RTM_NEWROUTE:
+ case RTM_DELROUTE:
+ if (isResponse && ((nlh->nlmsg_flags & NLM_F_MULTI) != NLM_F_MULTI)) {
+ // If it's not multipart message, then it must be response to a route
+ // check.
+ MOZ_ASSERT(mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
+ OnRouteCheckResult(nlh);
+ RemovePendingMsg();
+ } else {
+ OnRouteMessage(nlh);
+ }
+ break;
+ case RTM_NEWNEIGH:
+ case RTM_DELNEIGH:
+ MOZ_ASSERT(!isResponse ||
+ (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+ OnNeighborMessage(nlh);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void NetlinkService::OnLinkMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnLinkMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWLINK ? "new" : "del"));
+
+ UniquePtr<NetlinkLink> link(new NetlinkLink());
+ if (!link->Init(aNlh)) {
+ return;
+ }
+
+ const uint32_t linkIndex = link->GetIndex();
+ mLinks.WithEntryHandle(linkIndex, [&](auto&& entry) {
+ nsAutoCString linkName;
+ link->GetName(linkName);
+
+ if (aNlh->nlmsg_type == RTM_NEWLINK) {
+ if (!entry) {
+ LOG(("Creating new link [index=%u, name=%s, flags=%u, type=%u]",
+ linkIndex, linkName.get(), link->GetFlags(), link->GetType()));
+ entry.Insert(MakeUnique<LinkInfo>(std::move(link)));
+ } else {
+ LOG(("Updating link [index=%u, name=%s, flags=%u, type=%u]", linkIndex,
+ linkName.get(), link->GetFlags(), link->GetType()));
+
+ auto* linkInfo = entry->get();
+
+ // Check whether administrative state has changed.
+ if (linkInfo->mLink->GetFlags() & IFF_UP &&
+ !(link->GetFlags() & IFF_UP)) {
+ LOG((" link went down"));
+ // If the link went down, remove all routes and neighbors, but keep
+ // addresses.
+ linkInfo->mDefaultRoutes.Clear();
+ linkInfo->mNeighbors.Clear();
+ }
+
+ linkInfo->mLink = std::move(link);
+ linkInfo->UpdateStatus();
+ }
+ } else {
+ if (!entry) {
+ // This can happen during startup
+ LOG(("Link info doesn't exist [index=%u, name=%s]", linkIndex,
+ linkName.get()));
+ } else {
+ LOG(("Removing link [index=%u, name=%s]", linkIndex, linkName.get()));
+ entry.Remove();
+ }
+ }
+ });
+}
+
+void NetlinkService::OnAddrMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnAddrMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWADDR ? "new" : "del"));
+
+ UniquePtr<NetlinkAddress> address(new NetlinkAddress());
+ if (!address->Init(aNlh)) {
+ return;
+ }
+
+ uint32_t ifIdx = address->GetIndex();
+
+ nsAutoCString addrStr;
+ GetAddrStr(address->GetAddrPtr(), address->Family(), addrStr);
+
+ LinkInfo* linkInfo = nullptr;
+ mLinks.Get(ifIdx, &linkInfo);
+ if (!linkInfo) {
+ // This can happen during startup
+ LOG(("Cannot find link info [ifIdx=%u, addr=%s/%u", ifIdx, addrStr.get(),
+ address->GetPrefixLen()));
+ return;
+ }
+
+ // There might be already an equal address in the array even in case of
+ // RTM_NEWADDR message, e.g. when lifetime of IPv6 address is renewed. Equal
+ // in this case means that IP and prefix is the same but some attributes
+ // might be different. Remove existing equal address in case of RTM_DELADDR
+ // as well as RTM_NEWADDR message and add a new one in the latter case.
+ for (uint32_t i = 0; i < linkInfo->mAddresses.Length(); ++i) {
+ if (aNlh->nlmsg_type == RTM_NEWADDR &&
+ linkInfo->mAddresses[i]->MsgEquals(*address)) {
+ // If the new address is exactly the same, there is nothing to do.
+ LOG(("Exactly the same address already exists [ifIdx=%u, addr=%s/%u",
+ ifIdx, addrStr.get(), address->GetPrefixLen()));
+ return;
+ }
+
+ if (linkInfo->mAddresses[i]->Equals(*address)) {
+ LOG(("Removing address [ifIdx=%u, addr=%s/%u]", ifIdx, addrStr.get(),
+ address->GetPrefixLen()));
+ linkInfo->mAddresses.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (aNlh->nlmsg_type == RTM_NEWADDR) {
+ LOG(("Adding address [ifIdx=%u, addr=%s/%u]", ifIdx, addrStr.get(),
+ address->GetPrefixLen()));
+ linkInfo->mAddresses.AppendElement(std::move(address));
+ } else {
+ // Remove all routes associated with this address
+ for (uint32_t i = linkInfo->mDefaultRoutes.Length(); i-- > 0;) {
+ MOZ_ASSERT(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(),
+ "Stored routes must have gateway!");
+ if (linkInfo->mDefaultRoutes[i]->Family() == address->Family() &&
+ address->ContainsAddr(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr())) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ linkInfo->mDefaultRoutes[i]->GetAsString(routeDbgStr);
+ LOG(("Removing default route: %s", routeDbgStr.get()));
+ }
+ linkInfo->mDefaultRoutes.RemoveElementAt(i);
+ }
+ }
+
+ // Remove all neighbors associated with this address
+ for (auto iter = linkInfo->mNeighbors.Iter(); !iter.Done(); iter.Next()) {
+ NetlinkNeighbor* neigh = iter.UserData();
+ if (neigh->Family() == address->Family() &&
+ address->ContainsAddr(neigh->GetAddrPtr())) {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Removing neighbor %s", neighDbgStr.get()));
+ }
+ iter.Remove();
+ }
+ }
+ }
+
+ // Address change on the interface can change its status
+ linkInfo->UpdateStatus();
+
+ // Don't treat address changes during initial scan as a network change
+ if (mInitialScanFinished) {
+ // Send network event change regardless of whether the ID has changed or
+ // not
+ mSendNetworkChangeEvent = true;
+ TriggerNetworkIDCalculation();
+ }
+}
+
+void NetlinkService::OnRouteMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnRouteMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWROUTE ? "new" : "del"));
+
+ UniquePtr<NetlinkRoute> route(new NetlinkRoute());
+ if (!route->Init(aNlh)) {
+ return;
+ }
+
+ if (!route->IsUnicast() || !route->ScopeIsUniverse()) {
+ // Use only unicast routes
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Not an unicast global route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ // Adding/removing any unicast route might change network ID
+ TriggerNetworkIDCalculation();
+
+ if (!route->IsDefault()) {
+ // Store only default routes
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Not a default route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ if (!route->HasOif()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("There is no output interface in route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ if (!route->GetGWAddrPtr()) {
+ // We won't use the route if there is no gateway, so don't store it
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("There is no gateway in route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ if (route->Family() == AF_INET6 &&
+ net::utils::ipv6_scope((const unsigned char*)route->GetGWAddrPtr()) !=
+ IPV6_SCOPE_GLOBAL) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Scope of GW isn't global: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ LinkInfo* linkInfo = nullptr;
+ mLinks.Get(route->Oif(), &linkInfo);
+ if (!linkInfo) {
+ // This can happen during startup
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Cannot find link info for route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
+ if (linkInfo->mDefaultRoutes[i]->Equals(*route)) {
+ // We shouldn't find equal route when adding a new one, but just in case
+ // it can happen remove the old one to avoid duplicities.
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Removing default route: %s", routeDbgStr.get()));
+ }
+ linkInfo->mDefaultRoutes.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (aNlh->nlmsg_type == RTM_NEWROUTE) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Adding default route: %s", routeDbgStr.get()));
+ }
+ linkInfo->mDefaultRoutes.AppendElement(std::move(route));
+ }
+}
+
+void NetlinkService::OnNeighborMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnNeighborMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWNEIGH ? "new" : "del"));
+
+ UniquePtr<NetlinkNeighbor> neigh(new NetlinkNeighbor());
+ if (!neigh->Init(aNlh)) {
+ return;
+ }
+
+ LinkInfo* linkInfo = nullptr;
+ mLinks.Get(neigh->GetIndex(), &linkInfo);
+ if (!linkInfo) {
+ // This can happen during startup
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Cannot find link info for neighbor: %s", neighDbgStr.get()));
+ }
+ return;
+ }
+
+ if (!linkInfo->mLink->IsTypeEther()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Ignoring message on non-ethernet link: %s", neighDbgStr.get()));
+ }
+ return;
+ }
+
+ nsAutoCString key;
+ GetAddrStr(neigh->GetAddrPtr(), neigh->Family(), key);
+
+ if (aNlh->nlmsg_type == RTM_NEWNEIGH) {
+ if (!mRecalculateNetworkId && neigh->HasMAC()) {
+ NetlinkNeighbor* oldNeigh = nullptr;
+ linkInfo->mNeighbors.Get(key, &oldNeigh);
+
+ if (!oldNeigh || !oldNeigh->HasMAC()) {
+ // The MAC address was added, if it's a host from some of the saved
+ // routing tables we should recalculate network ID
+ for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
+ if (linkInfo->mDefaultRoutes[i]->GatewayEquals(*neigh)) {
+ TriggerNetworkIDCalculation();
+ break;
+ }
+ }
+ if ((mIPv4RouteCheckResult &&
+ mIPv4RouteCheckResult->GatewayEquals(*neigh)) ||
+ (mIPv6RouteCheckResult &&
+ mIPv6RouteCheckResult->GatewayEquals(*neigh))) {
+ TriggerNetworkIDCalculation();
+ }
+ }
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Adding neighbor: %s", neighDbgStr.get()));
+ }
+ linkInfo->mNeighbors.InsertOrUpdate(key, std::move(neigh));
+ } else {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Removing neighbor %s", neighDbgStr.get()));
+ }
+ linkInfo->mNeighbors.Remove(key);
+ }
+}
+
+void NetlinkService::OnRouteCheckResult(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnRouteCheckResult"));
+ UniquePtr<NetlinkRoute> route;
+
+ if (aNlh) {
+ route = MakeUnique<NetlinkRoute>();
+ if (!route->Init(aNlh)) {
+ route = nullptr;
+ } else {
+ if (!route->IsUnicast() || !route->ScopeIsUniverse()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Not an unicast global route: %s", routeDbgStr.get()));
+ }
+ route = nullptr;
+ } else if (!route->HasOif()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("There is no output interface in route: %s", routeDbgStr.get()));
+ }
+ route = nullptr;
+ }
+ }
+ }
+
+ if (LOG_ENABLED()) {
+ if (route) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Storing route: %s", routeDbgStr.get()));
+ } else {
+ LOG(("Clearing result for the check"));
+ }
+ }
+
+ if (mOutgoingMessages[0]->Family() == AF_INET) {
+ mIPv4RouteCheckResult = std::move(route);
+ } else {
+ mIPv6RouteCheckResult = std::move(route);
+ }
+}
+
+void NetlinkService::EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily) {
+ NetlinkGenMsg* msg = new NetlinkGenMsg(aMsgType, aFamily, ++mMsgId);
+ mOutgoingMessages.AppendElement(msg);
+}
+
+void NetlinkService::EnqueueRtMsg(uint8_t aFamily, void* aAddress) {
+ NetlinkRtMsg* msg = new NetlinkRtMsg(aFamily, aAddress, ++mMsgId);
+ mOutgoingMessages.AppendElement(msg);
+}
+
+void NetlinkService::RemovePendingMsg() {
+ LOG(("NetlinkService::RemovePendingMsg [seqId=%u]",
+ mOutgoingMessages[0]->SeqId()));
+
+ MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
+
+ DebugOnly<bool> isRtMessage =
+ (mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
+
+ mOutgoingMessages.RemoveElementAt(0);
+ if (!mOutgoingMessages.Length()) {
+ if (!mInitialScanFinished) {
+ // Now we've received all initial data from the kernel. Perform a link
+ // check and trigger network ID calculation even if it wasn't triggered
+ // by the incoming messages.
+ mInitialScanFinished = true;
+
+ TriggerNetworkIDCalculation();
+
+ // Link status should be known by now.
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+ if (listener) {
+ listener->OnLinkStatusKnown();
+ }
+ } else {
+ // We've received last response for route check, calculate ID now
+ MOZ_ASSERT(isRtMessage);
+ CalculateNetworkID();
+ }
+ }
+}
+
+NS_IMETHODIMP
+NetlinkService::Run() {
+ int netlinkSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (netlinkSocket < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct sockaddr_nl addr {};
+ memset(&addr, 0, sizeof(addr));
+
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_LINK |
+ RTMGRP_NEIGH | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
+
+ if (bind(netlinkSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ // failure!
+ EINTR_RETRY(close(netlinkSocket));
+ return NS_ERROR_FAILURE;
+ }
+
+ struct pollfd fds[2];
+ fds[0].fd = mShutdownPipe[0];
+ fds[0].events = POLLIN;
+ fds[0].revents = 0;
+
+ fds[1].fd = netlinkSocket;
+ fds[1].events = POLLIN;
+ fds[1].revents = 0;
+
+ // send all requests to get initial network information
+ EnqueueGenMsg(RTM_GETLINK, AF_PACKET);
+ EnqueueGenMsg(RTM_GETNEIGH, AF_INET);
+ EnqueueGenMsg(RTM_GETNEIGH, AF_INET6);
+ EnqueueGenMsg(RTM_GETADDR, AF_PACKET);
+ EnqueueGenMsg(RTM_GETROUTE, AF_PACKET);
+
+ nsresult rv = NS_OK;
+ bool shutdown = false;
+ while (!shutdown) {
+ if (mOutgoingMessages.Length() && !mOutgoingMessages[0]->IsPending()) {
+ if (!mOutgoingMessages[0]->Send(netlinkSocket)) {
+ LOG(("Failed to send netlink message"));
+ mOutgoingMessages.RemoveElementAt(0);
+ // try to send another message if available before polling
+ continue;
+ }
+ }
+
+ int rc = eintr_retry([&]() {
+ AUTO_PROFILER_THREAD_SLEEP;
+ return poll(fds, 2, GetPollWait());
+ });
+
+ if (rc > 0) {
+ if (fds[0].revents & POLLIN) {
+ // shutdown, abort the loop!
+ LOG(("thread shutdown received, dying...\n"));
+ shutdown = true;
+ } else if (fds[1].revents & POLLIN) {
+ LOG(("netlink message received, handling it...\n"));
+ OnNetlinkMessage(netlinkSocket);
+ }
+ } else if (rc < 0) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+
+ EINTR_RETRY(close(netlinkSocket));
+
+ return rv;
+}
+
+nsresult NetlinkService::Init(NetlinkServiceListener* aListener) {
+ nsresult rv;
+
+ mListener = aListener;
+
+ if (inet_pton(AF_INET, ROUTE_CHECK_IPV4, &mRouteCheckIPv4) != 1) {
+ LOG(("Cannot parse address " ROUTE_CHECK_IPV4));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV4);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (inet_pton(AF_INET6, ROUTE_CHECK_IPV6, &mRouteCheckIPv6) != 1) {
+ LOG(("Cannot parse address " ROUTE_CHECK_IPV6));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV6);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (pipe(mShutdownPipe) == -1) {
+ LOG(("Cannot create pipe"));
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_NewNamedThread("Netlink Monitor", getter_AddRefs(mThread), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult NetlinkService::Shutdown() {
+ LOG(("write() to signal thread shutdown\n"));
+
+ {
+ MutexAutoLock lock(mMutex);
+ mListener = nullptr;
+ }
+
+ // awake the thread to make it terminate
+ ssize_t rc = EINTR_RETRY(write(mShutdownPipe[1], "1", 1));
+ LOG(("write() returned %d, errno == %d\n", (int)rc, errno));
+
+ nsresult rv = mThread->Shutdown();
+
+ // Have to break the cycle here, otherwise NetlinkService holds
+ // onto the thread and the thread holds onto the NetlinkService
+ // via its mRunnable
+ mThread = nullptr;
+
+ return rv;
+}
+
+/*
+ * A network event that might change network ID has been registered. Delay
+ * network ID calculation and sending of the event in case it changed for
+ * a while. Absorbing potential subsequent events increases chance of successful
+ * network ID calculation (e.g. MAC address of the router might be discovered in
+ * the meantime)
+ */
+void NetlinkService::TriggerNetworkIDCalculation() {
+ LOG(("NetlinkService::TriggerNetworkIDCalculation"));
+
+ if (mRecalculateNetworkId) {
+ return;
+ }
+
+ mRecalculateNetworkId = true;
+ mTriggerTime = TimeStamp::Now();
+}
+
+int NetlinkService::GetPollWait() {
+ if (!mRecalculateNetworkId) {
+ return -1;
+ }
+
+ if (mOutgoingMessages.Length()) {
+ MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
+ // Message is pending, we don't have to set timeout because we'll receive
+ // reply from kernel ASAP
+ return -1;
+ }
+
+ MOZ_ASSERT(mInitialScanFinished);
+
+ double period = (TimeStamp::Now() - mTriggerTime).ToMilliseconds();
+ if (period >= kNetworkChangeCoalescingPeriod) {
+ // Coalescing time has elapsed, send route check messages to find out
+ // where IPv4 and IPv6 traffic is routed and calculate network ID after
+ // the response is received.
+ EnqueueRtMsg(AF_INET, &mRouteCheckIPv4);
+ EnqueueRtMsg(AF_INET6, &mRouteCheckIPv6);
+
+ // Return 0 to make sure we start sending enqueued messages immediately
+ return 0;
+ }
+
+ return static_cast<int>(kNetworkChangeCoalescingPeriod - period);
+}
+
+class NeighborComparator {
+ public:
+ bool Equals(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
+ return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) == 0);
+ }
+ bool LessThan(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
+ return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) < 0);
+ }
+};
+
+class LinknameComparator {
+ public:
+ bool LessThan(const nsCString& aA, const nsCString& aB) const {
+ return aA < aB;
+ }
+ bool Equals(const nsCString& aA, const nsCString& aB) const {
+ return aA == aB;
+ }
+};
+
+// Get Gateway Neighbours for a particular Address Family, for which we know MAC
+// address
+void NetlinkService::GetGWNeighboursForFamily(
+ uint8_t aFamily, nsTArray<NetlinkNeighbor*>& aGwNeighbors) {
+ LOG(("NetlinkService::GetGWNeighboursForFamily"));
+ // Check only routes on links that are up
+ for (const auto& linkInfo : mLinks.Values()) {
+ nsAutoCString linkName;
+ linkInfo->mLink->GetName(linkName);
+
+ if (!linkInfo->mIsUp) {
+ LOG((" %s is down", linkName.get()));
+ continue;
+ }
+
+ if (!linkInfo->mLink->IsTypeEther()) {
+ LOG((" %s is not ethernet link", linkName.get()));
+ continue;
+ }
+
+ LOG((" checking link %s", linkName.get()));
+
+ // Check all default routes and try to get MAC of the gateway
+ for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ linkInfo->mDefaultRoutes[i]->GetAsString(routeDbgStr);
+ LOG(("Checking default route: %s", routeDbgStr.get()));
+ }
+
+ if (linkInfo->mDefaultRoutes[i]->Family() != aFamily) {
+ LOG((" skipping due to different family"));
+ continue;
+ }
+
+ MOZ_ASSERT(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(),
+ "Stored routes must have gateway!");
+
+ nsAutoCString neighKey;
+ GetAddrStr(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(), aFamily,
+ neighKey);
+
+ NetlinkNeighbor* neigh = nullptr;
+ if (!linkInfo->mNeighbors.Get(neighKey, &neigh)) {
+ LOG(("Neighbor %s not found in hashtable.", neighKey.get()));
+ continue;
+ }
+
+ if (!neigh->HasMAC()) {
+ // We don't know MAC address
+ LOG(("We have no MAC for neighbor %s.", neighKey.get()));
+ continue;
+ }
+
+ if (aGwNeighbors.IndexOf(neigh, 0, NeighborComparator()) !=
+ nsTArray<NetlinkNeighbor*>::NoIndex) {
+ // avoid host duplicities
+ LOG(("MAC of neighbor %s is already selected for hashing.",
+ neighKey.get()));
+ continue;
+ }
+
+ LOG(("MAC of neighbor %s will be used for network ID.", neighKey.get()));
+ aGwNeighbors.AppendElement(neigh);
+ }
+ }
+}
+
+bool NetlinkService::CalculateIDForEthernetLink(uint8_t aFamily,
+ NetlinkRoute* aRouteCheckResult,
+ uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo,
+ SHA1Sum* aSHA1) {
+ LOG(("NetlinkService::CalculateIDForEthernetLink"));
+ bool retval = false;
+ const in_common_addr* addrPtr = aRouteCheckResult->GetGWAddrPtr();
+
+ if (!addrPtr) {
+ // This shouldn't normally happen, missing next hop in case of ethernet
+ // device would mean that the checked host is on the same network.
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ aRouteCheckResult->GetAsString(routeDbgStr);
+ LOG(("There is no next hop in route: %s", routeDbgStr.get()));
+ }
+ return retval;
+ }
+
+ // If we know MAC address of the next hop for mRouteCheckIPv4/6 host, hash
+ // it even if it's MAC of some of the default routes we've checked above.
+ // This ensures that if we have 2 different default routes and next hop for
+ // mRouteCheckIPv4/6 changes from one default route to the other, we'll
+ // detect it as a network change.
+ nsAutoCString neighKey;
+ GetAddrStr(addrPtr, aFamily, neighKey);
+ LOG(("Next hop for the checked host is %s on ifIdx %u.", neighKey.get(),
+ aRouteCheckIfIdx));
+
+ NetlinkNeighbor* neigh = nullptr;
+ if (!aRouteCheckLinkInfo->mNeighbors.Get(neighKey, &neigh)) {
+ LOG(("Neighbor %s not found in hashtable.", neighKey.get()));
+ return retval;
+ }
+
+ if (!neigh->HasMAC()) {
+ LOG(("We have no MAC for neighbor %s.", neighKey.get()));
+ return retval;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
+ }
+ aSHA1->update(neigh->GetMACPtr(), ETH_ALEN);
+ retval = true;
+
+ return retval;
+}
+
+bool NetlinkService::CalculateIDForNonEthernetLink(
+ uint8_t aFamily, NetlinkRoute* aRouteCheckResult,
+ nsTArray<nsCString>& aLinkNamesToHash, uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo, SHA1Sum* aSHA1) {
+ LOG(("NetlinkService::CalculateIDForNonEthernetLink"));
+ bool retval = false;
+ const in_common_addr* addrPtr = aRouteCheckResult->GetGWAddrPtr();
+ nsAutoCString routeCheckLinkName;
+ aRouteCheckLinkInfo->mLink->GetName(routeCheckLinkName);
+
+ if (addrPtr) {
+ // The route contains next hop. Hash the name of the interface (e.g.
+ // "tun1") and the IP address of the next hop.
+
+ nsAutoCString addrStr;
+ GetAddrStr(addrPtr, aFamily, addrStr);
+ size_t addrSize =
+ (aFamily == AF_INET) ? sizeof(addrPtr->addr4) : sizeof(addrPtr->addr6);
+
+ LOG(("Hashing link name %s", routeCheckLinkName.get()));
+ aSHA1->update(routeCheckLinkName.get(), routeCheckLinkName.Length());
+
+ // Don't hash GW address if it's rmnet_data device.
+ if (!aLinkNamesToHash.Contains(routeCheckLinkName)) {
+ LOG(("Hashing GW address %s", addrStr.get()));
+ aSHA1->update(addrPtr, addrSize);
+ }
+
+ retval = true;
+ } else {
+ // The traffic is routed directly via an interface. Hash the name of the
+ // interface and the network address. Using host address would cause that
+ // network ID would be different every time we get a different IP address
+ // in this network/VPN.
+
+ bool hasSrcAddr = aRouteCheckResult->HasPrefSrcAddr();
+ if (!hasSrcAddr) {
+ LOG(("There is no preferred source address."));
+ }
+
+ NetlinkAddress* linkAddress = nullptr;
+ // Find network address of the interface matching the source address. In
+ // theory there could be multiple addresses with different prefix length.
+ // Get the one with smallest prefix length.
+ for (uint32_t i = 0; i < aRouteCheckLinkInfo->mAddresses.Length(); ++i) {
+ if (!hasSrcAddr) {
+ // there is no preferred src, match just the family
+ if (aRouteCheckLinkInfo->mAddresses[i]->Family() != aFamily) {
+ continue;
+ }
+ } else if (!aRouteCheckResult->PrefSrcAddrEquals(
+ *aRouteCheckLinkInfo->mAddresses[i])) {
+ continue;
+ }
+
+ if (!linkAddress ||
+ linkAddress->GetPrefixLen() >
+ aRouteCheckLinkInfo->mAddresses[i]->GetPrefixLen()) {
+ // We have no address yet or this one has smaller prefix length,
+ // use it.
+ linkAddress = aRouteCheckLinkInfo->mAddresses[i].get();
+ }
+ }
+
+ if (!linkAddress) {
+ // There is no address in our array?
+ if (LOG_ENABLED()) {
+ nsAutoCString dbgStr;
+ aRouteCheckResult->GetAsString(dbgStr);
+ LOG(("No address found for preferred source address in route: %s",
+ dbgStr.get()));
+ }
+ return retval;
+ }
+
+ in_common_addr prefix;
+ int32_t prefixSize = (aFamily == AF_INET) ? (int32_t)sizeof(prefix.addr4)
+ : (int32_t)sizeof(prefix.addr6);
+ memcpy(&prefix, linkAddress->GetAddrPtr(), prefixSize);
+ uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
+ int32_t bits = linkAddress->GetPrefixLen();
+ if (bits > prefixSize * 8) {
+ MOZ_ASSERT(false, "Unexpected prefix length!");
+ LOG(("Unexpected prefix length %d, maximum for this family is %d", bits,
+ prefixSize * 8));
+ return retval;
+ }
+ for (int32_t i = 0; i < prefixSize; i++) {
+ uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
+ ((unsigned char*)&prefix)[i] &= mask;
+ bits -= 8;
+ if (bits <= 0) {
+ bits = 0;
+ }
+ }
+
+ nsAutoCString addrStr;
+ GetAddrStr(&prefix, aFamily, addrStr);
+ LOG(("Hashing link name %s and network address %s/%u",
+ routeCheckLinkName.get(), addrStr.get(), linkAddress->GetPrefixLen()));
+ aSHA1->update(routeCheckLinkName.get(), routeCheckLinkName.Length());
+ aSHA1->update(&prefix, prefixSize);
+ bits = linkAddress->GetPrefixLen();
+ aSHA1->update(&bits, sizeof(bits));
+ retval = true;
+ }
+ return retval;
+}
+
+bool NetlinkService::CalculateIDForFamily(uint8_t aFamily, SHA1Sum* aSHA1) {
+ LOG(("NetlinkService::CalculateIDForFamily [family=%s]",
+ aFamily == AF_INET ? "AF_INET" : "AF_INET6"));
+
+ bool retval = false;
+
+ if (!mLinkUp) {
+ // Skip ID calculation if the link is down, we have no ID...
+ LOG(("Link is down, skipping ID calculation."));
+ return retval;
+ }
+
+ NetlinkRoute* routeCheckResult;
+ if (aFamily == AF_INET) {
+ routeCheckResult = mIPv4RouteCheckResult.get();
+ } else {
+ routeCheckResult = mIPv6RouteCheckResult.get();
+ }
+
+ // All GW neighbors for which we know MAC address. We'll probably have at
+ // most only one, but in case we have more default routes, we hash them all
+ // even though the routing rules sends the traffic only via one of them.
+ // If the system switches between them, we'll detect the change with
+ // mIPv4/6RouteCheckResult.
+ nsTArray<NetlinkNeighbor*> gwNeighbors;
+
+ GetGWNeighboursForFamily(aFamily, gwNeighbors);
+
+ // Sort them so we always have the same network ID on the same network
+ gwNeighbors.Sort(NeighborComparator());
+
+ for (uint32_t i = 0; i < gwNeighbors.Length(); ++i) {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ gwNeighbors[i]->GetAsString(neighDbgStr);
+ LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
+ }
+ aSHA1->update(gwNeighbors[i]->GetMACPtr(), ETH_ALEN);
+ retval = true;
+ }
+
+ nsTArray<nsCString> linkNamesToHash;
+ if (!gwNeighbors.Length()) {
+ // If we don't know MAC of the gateway and link is up, it's probably not
+ // an ethernet link. If the name of the link begins with "rmnet" then
+ // the mobile data is used. We cannot easily differentiate when user
+ // switches sim cards so let's treat mobile data as a single network. We'll
+ // simply hash link name. If the traffic is redirected via some VPN, it'll
+ // still be detected below.
+
+ // TODO: maybe we could get operator name via AndroidBridge
+ for (const auto& linkInfo : mLinks.Values()) {
+ if (linkInfo->mIsUp) {
+ nsAutoCString linkName;
+ linkInfo->mLink->GetName(linkName);
+ if (StringBeginsWith(linkName, "rmnet"_ns)) {
+ // Check whether there is some non-local address associated with this
+ // link.
+ for (uint32_t i = 0; i < linkInfo->mAddresses.Length(); ++i) {
+ if (linkInfo->mAddresses[i]->Family() == aFamily &&
+ linkInfo->mAddresses[i]->ScopeIsUniverse()) {
+ linkNamesToHash.AppendElement(linkName);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Sort link names to ensure consistent results
+ linkNamesToHash.Sort(LinknameComparator());
+
+ for (uint32_t i = 0; i < linkNamesToHash.Length(); ++i) {
+ LOG(("Hashing name of adapter: %s", linkNamesToHash[i].get()));
+ aSHA1->update(linkNamesToHash[i].get(), linkNamesToHash[i].Length());
+ retval = true;
+ }
+ }
+
+ if (!routeCheckResult) {
+ // If we don't have result for route check to mRouteCheckIPv4/6 host, the
+ // network is unreachable and there is no more to do.
+ LOG(("There is no route check result."));
+ return retval;
+ }
+
+ LinkInfo* routeCheckLinkInfo = nullptr;
+ uint32_t routeCheckIfIdx = routeCheckResult->Oif();
+ if (!mLinks.Get(routeCheckIfIdx, &routeCheckLinkInfo)) {
+ LOG(("Cannot find link with index %u ??", routeCheckIfIdx));
+ return retval;
+ }
+
+ if (routeCheckLinkInfo->mLink->IsTypeEther()) {
+ // The traffic is routed through an ethernet device.
+ retval |= CalculateIDForEthernetLink(
+ aFamily, routeCheckResult, routeCheckIfIdx, routeCheckLinkInfo, aSHA1);
+ } else {
+ // The traffic is routed through a non-ethernet device.
+ retval |= CalculateIDForNonEthernetLink(aFamily, routeCheckResult,
+ linkNamesToHash, routeCheckIfIdx,
+ routeCheckLinkInfo, aSHA1);
+ }
+
+ return retval;
+}
+
+void NetlinkService::ExtractDNSProperties() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
+ nsTArray<nsCString> suffixList;
+ nsTArray<NetAddr> resolvers;
+#if defined(HAVE_RES_NINIT)
+ [&]() {
+ struct __res_state res {};
+ int ret = res_ninit(&res);
+ if (ret != 0) {
+ LOG(("Call to res_ninit failed: %d", ret));
+ return;
+ }
+
+ // Get DNS suffixes
+ for (int i = 0; i < MAXDNSRCH; i++) {
+ if (!res.dnsrch[i]) {
+ break;
+ }
+ suffixList.AppendElement(nsCString(res.dnsrch[i]));
+ }
+
+ // Get DNS resolvers
+ // Chromium's dns_config_service_posix.cc is the origin of this code
+ // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
+ // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
+ // but we have to combine the two arrays ourselves.
+ for (int i = 0; i < res.nscount; ++i) {
+ const struct sockaddr* addr = nullptr;
+ size_t addr_len = 0;
+ if (res.nsaddr_list[i].sin_family) { // The indicator used by res_nsend.
+ addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
+ addr_len = sizeof res.nsaddr_list[i];
+ } else if (res._u._ext.nsaddrs[i]) {
+ addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
+ addr_len = sizeof *res._u._ext.nsaddrs[i];
+ } else {
+ LOG(("Bad ext struct"));
+ return;
+ }
+ const socklen_t kSockaddrInSize = sizeof(struct sockaddr_in);
+ const socklen_t kSockaddrIn6Size = sizeof(struct sockaddr_in6);
+
+ if ((addr->sa_family == AF_INET && addr_len < kSockaddrInSize) ||
+ (addr->sa_family == AF_INET6 && addr_len < kSockaddrIn6Size)) {
+ LOG(("Bad address size"));
+ return;
+ }
+
+ NetAddr ip;
+ if (addr->sa_family == AF_INET) {
+ const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
+ ip.inet.family = AF_INET;
+ ip.inet.ip = sin->sin_addr.s_addr;
+ ip.inet.port = sin->sin_port;
+ } else if (addr->sa_family == AF_INET6) {
+ const struct sockaddr_in6* sin6 = (const struct sockaddr_in6*)addr;
+ ip.inet6.family = AF_INET6;
+ memcpy(&ip.inet6.ip.u8, &sin6->sin6_addr, sizeof(ip.inet6.ip.u8));
+ ip.inet6.port = sin6->sin6_port;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected sa_family");
+ return;
+ }
+
+ resolvers.AppendElement(ip);
+ }
+
+ res_nclose(&res);
+ }();
+
+#endif
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ mDNSSuffixList = std::move(suffixList);
+ mDNSResolvers = std::move(resolvers);
+ }
+
+ if (listener) {
+ listener->OnDnsSuffixListUpdated();
+ }
+}
+
+void NetlinkService::UpdateLinkStatus() {
+ LOG(("NetlinkService::UpdateLinkStatus"));
+
+ MOZ_ASSERT(!mRecalculateNetworkId);
+ MOZ_ASSERT(mInitialScanFinished);
+
+ // Link is up when we have a route for ROUTE_CHECK_IPV4 or ROUTE_CHECK_IPV6
+ bool newLinkUp = mIPv4RouteCheckResult || mIPv6RouteCheckResult;
+
+ if (mLinkUp == newLinkUp) {
+ LOG(("Link status hasn't changed [linkUp=%d]", mLinkUp));
+ } else {
+ LOG(("Link status has changed [linkUp=%d]", newLinkUp));
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ mLinkUp = newLinkUp;
+ }
+ if (mLinkUp) {
+ if (listener) {
+ listener->OnLinkUp();
+ }
+ } else {
+ if (listener) {
+ listener->OnLinkDown();
+ }
+ }
+ }
+}
+
+// Figure out the "network identification".
+void NetlinkService::CalculateNetworkID() {
+ LOG(("NetlinkService::CalculateNetworkID"));
+
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
+ MOZ_ASSERT(mRecalculateNetworkId);
+
+ mRecalculateNetworkId = false;
+
+ SHA1Sum sha1;
+
+ UpdateLinkStatus();
+ ExtractDNSProperties();
+
+ bool idChanged = false;
+ bool found4 = CalculateIDForFamily(AF_INET, &sha1);
+ bool found6 = CalculateIDForFamily(AF_INET6, &sha1);
+
+ if (found4 || found6) {
+ nsAutoCString output;
+ SeedNetworkId(sha1);
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsAutoCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ nsresult rv = Base64Encode(newString, output);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ LOG(("networkid: id %s\n", output.get()));
+ MutexAutoLock lock(mMutex);
+ if (mNetworkId != output) {
+ // new id
+ if (found4 && !found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1); // IPv4 only
+ } else if (!found4 && found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3); // IPv6 only
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4); // Both!
+ }
+ mNetworkId = output;
+ idChanged = true;
+ } else {
+ // same id
+ LOG(("Same network id"));
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
+ }
+ } else {
+ // no id
+ LOG(("No network id"));
+ MutexAutoLock lock(mMutex);
+ if (!mNetworkId.IsEmpty()) {
+ mNetworkId.Truncate();
+ idChanged = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
+ }
+ }
+
+ // If this is first time we calculate network ID, don't report it as a network
+ // change. We've started with an empty ID and we've just calculated the
+ // correct ID. The network hasn't really changed.
+ static bool initialIDCalculation = true;
+
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+
+ if (!initialIDCalculation && idChanged && listener) {
+ listener->OnNetworkIDChanged();
+ mSendNetworkChangeEvent = true;
+ }
+
+ if (mSendNetworkChangeEvent && listener) {
+ listener->OnNetworkChanged();
+ }
+
+ initialIDCalculation = false;
+ mSendNetworkChangeEvent = false;
+}
+
+void NetlinkService::GetNetworkID(nsACString& aNetworkID) {
+ MutexAutoLock lock(mMutex);
+ aNetworkID = mNetworkId;
+}
+
+nsresult NetlinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
+#if defined(HAVE_RES_NINIT)
+ MutexAutoLock lock(mMutex);
+ aDnsSuffixList = mDNSSuffixList.Clone();
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+nsresult NetlinkService::GetResolvers(nsTArray<NetAddr>& aResolvers) {
+#if defined(HAVE_RES_NINIT)
+ MutexAutoLock lock(mMutex);
+ aResolvers = mDNSResolvers.Clone();
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+void NetlinkService::GetIsLinkUp(bool* aIsUp) {
+ MutexAutoLock lock(mMutex);
+ *aIsUp = mLinkUp;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/system/netlink/NetlinkService.h b/netwerk/system/netlink/NetlinkService.h
new file mode 100644
index 0000000000..1de3992f47
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NETLINKSERVICE_H_
+#define NETLINKSERVICE_H_
+
+#include <netinet/in.h>
+
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsClassHashtable.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "mozilla/net/DNS.h"
+
+namespace mozilla {
+namespace net {
+
+class NetlinkAddress;
+class NetlinkNeighbor;
+class NetlinkLink;
+class NetlinkRoute;
+class NetlinkMsg;
+
+class NetlinkServiceListener : public nsISupports {
+ public:
+ virtual void OnNetworkChanged() = 0;
+ virtual void OnNetworkIDChanged() = 0;
+ virtual void OnLinkUp() = 0;
+ virtual void OnLinkDown() = 0;
+ virtual void OnLinkStatusKnown() = 0;
+ virtual void OnDnsSuffixListUpdated() = 0;
+
+ protected:
+ virtual ~NetlinkServiceListener() = default;
+};
+
+class NetlinkService : public nsIRunnable {
+ virtual ~NetlinkService();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ NetlinkService();
+ nsresult Init(NetlinkServiceListener* aListener);
+ nsresult Shutdown();
+ void GetNetworkID(nsACString& aNetworkID);
+ void GetIsLinkUp(bool* aIsUp);
+ nsresult GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList);
+ nsresult GetResolvers(nsTArray<NetAddr>& aResolvers);
+
+ private:
+ void EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily);
+ void EnqueueRtMsg(uint8_t aFamily, void* aAddress);
+ void RemovePendingMsg();
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED{"NetlinkService::mMutex"};
+
+ void OnNetlinkMessage(int aNetlinkSocket);
+ void OnLinkMessage(struct nlmsghdr* aNlh);
+ void OnAddrMessage(struct nlmsghdr* aNlh);
+ void OnRouteMessage(struct nlmsghdr* aNlh);
+ void OnNeighborMessage(struct nlmsghdr* aNlh);
+ void OnRouteCheckResult(struct nlmsghdr* aNlh);
+
+ void UpdateLinkStatus();
+
+ void TriggerNetworkIDCalculation();
+ int GetPollWait();
+ void GetGWNeighboursForFamily(uint8_t aFamily,
+ nsTArray<NetlinkNeighbor*>& aGwNeighbors);
+ bool CalculateIDForFamily(uint8_t aFamily, mozilla::SHA1Sum* aSHA1);
+ void CalculateNetworkID();
+ void ExtractDNSProperties();
+
+ nsCOMPtr<nsIThread> mThread;
+
+ bool mInitialScanFinished{false};
+
+ // A pipe to signal shutdown with.
+ int mShutdownPipe[2]{-1, -1};
+
+ // IP addresses that are used to check the route for public traffic.
+ struct in_addr mRouteCheckIPv4 {};
+ struct in6_addr mRouteCheckIPv6 {};
+
+ pid_t mPid;
+ uint32_t mMsgId{0};
+
+ bool mLinkUp{true};
+
+ // Flag indicating that network ID could change and should be recalculated.
+ // Calculation is postponed until we receive responses to all enqueued
+ // messages.
+ bool mRecalculateNetworkId{false};
+
+ // Flag indicating that network change event needs to be sent even if
+ // network ID hasn't changed.
+ bool mSendNetworkChangeEvent{false};
+
+ // Time stamp of setting mRecalculateNetworkId to true
+ mozilla::TimeStamp mTriggerTime;
+
+ nsCString mNetworkId;
+ nsTArray<nsCString> mDNSSuffixList;
+ nsTArray<NetAddr> mDNSResolvers;
+
+ class LinkInfo {
+ public:
+ explicit LinkInfo(UniquePtr<NetlinkLink>&& aLink);
+ virtual ~LinkInfo();
+
+ // Updates mIsUp according to current mLink and mAddresses. Returns true if
+ // the value has changed.
+ bool UpdateStatus();
+
+ // NetlinkLink structure for this link
+ UniquePtr<NetlinkLink> mLink;
+
+ // All IPv4/IPv6 addresses on this link
+ nsTArray<UniquePtr<NetlinkAddress>> mAddresses;
+
+ // All neighbors on this link, key is an address
+ nsClassHashtable<nsCStringHashKey, NetlinkNeighbor> mNeighbors;
+
+ // Default IPv4/IPv6 routes
+ nsTArray<UniquePtr<NetlinkRoute>> mDefaultRoutes;
+
+ // Link is up when it's running, it's not a loopback and there is
+ // a non-local address associated with it.
+ bool mIsUp;
+ };
+
+ bool CalculateIDForEthernetLink(uint8_t aFamily,
+ NetlinkRoute* aRouteCheckResult,
+ uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo,
+ mozilla::SHA1Sum* aSHA1);
+ bool CalculateIDForNonEthernetLink(uint8_t aFamily,
+ NetlinkRoute* aRouteCheckResult,
+ nsTArray<nsCString>& aLinkNamesToHash,
+ uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo,
+ mozilla::SHA1Sum* aSHA1);
+
+ nsClassHashtable<nsUint32HashKey, LinkInfo> mLinks;
+
+ // Route for mRouteCheckIPv4 address
+ UniquePtr<NetlinkRoute> mIPv4RouteCheckResult;
+ // Route for mRouteCheckIPv6 address
+ UniquePtr<NetlinkRoute> mIPv6RouteCheckResult;
+
+ nsTArray<UniquePtr<NetlinkMsg>> mOutgoingMessages;
+
+ RefPtr<NetlinkServiceListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NETLINKSERVICE_H_ */
diff --git a/netwerk/system/netlink/moz.build b/netwerk/system/netlink/moz.build
new file mode 100644
index 0000000000..6882a21126
--- /dev/null
+++ b/netwerk/system/netlink/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["OS_ARCH"] == "Linux":
+ SOURCES += [
+ "NetlinkService.cpp",
+ ]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/win32/moz.build b/netwerk/system/win32/moz.build
new file mode 100644
index 0000000000..2d7e668c3c
--- /dev/null
+++ b/netwerk/system/win32/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "nsNotifyAddrListener.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/win32/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp
new file mode 100644
index 0000000000..a64206fbc6
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.cpp
@@ -0,0 +1,736 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// We define this to make our use of inet_ntoa() pass. The "proper" function
+// inet_ntop() doesn't exist on Windows XP.
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+
+#include <algorithm>
+#include <vector>
+
+#include <stdarg.h>
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winuser.h>
+#include <ole2.h>
+#include <netcon.h>
+#include <objbase.h>
+#include <winsock2.h>
+#include <ws2ipdef.h>
+#include <tcpmib.h>
+#include <iphlpapi.h>
+#include <netioapi.h>
+#include <netlistmgr.h>
+#include <iprtrmib.h>
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsIWindowsRegKey.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetAddr.h"
+#include "nsNotifyAddrListener.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Services.h"
+#include "nsCRT.h"
+#include "nsThreadPool.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.h"
+#include "../LinkServiceCommon.h"
+#include <iptypes.h>
+#include <iphlpapi.h>
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(gNotifyAddrLog, mozilla::LogLevel::Debug)
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+NS_IMPL_ISUPPORTS(nsNotifyAddrListener, nsINetworkLinkService, nsIRunnable,
+ nsIObserver)
+
+nsNotifyAddrListener::nsNotifyAddrListener()
+ : mLinkUp(true), // assume true by default
+ mStatusKnown(false),
+ mCheckAttempted(false),
+ mMutex("nsNotifyAddrListener::mMutex"),
+ mCheckEvent(nullptr),
+ mShutdown(false),
+ mPlatformDNSIndications(NONE_DETECTED),
+ mIPInterfaceChecksum(0),
+ mCoalescingActive(false) {}
+
+nsNotifyAddrListener::~nsNotifyAddrListener() {
+ NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed");
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetIsLinkUp(bool* aIsUp) {
+ if (!mCheckAttempted && !mStatusKnown) {
+ mCheckAttempted = true;
+ CheckLinkStatus();
+ }
+
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetLinkStatusKnown(bool* aIsUp) {
+ *aIsUp = mStatusKnown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetLinkType(uint32_t* aLinkType) {
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ // XXX This function has not yet been implemented for this platform
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetNetworkID(nsACString& aNetworkID) {
+ MutexAutoLock lock(mMutex);
+ aNetworkID = mNetworkId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
+ aDnsSuffixList.Clear();
+ MutexAutoLock lock(mMutex);
+ aDnsSuffixList.AppendElements(mDnsSuffixList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetResolvers(nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
+ nsTArray<mozilla::net::NetAddr> addresses;
+ nsresult rv = GetNativeResolvers(addresses);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ for (const auto& addr : addresses) {
+ aResolvers.AppendElement(MakeRefPtr<nsNetAddr>(&addr));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetNativeResolvers(
+ nsTArray<mozilla::net::NetAddr>& aResolvers) {
+ aResolvers.Clear();
+ MutexAutoLock lock(mMutex);
+ aResolvers.AppendElements(mDNSResolvers);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetPlatformDNSIndications(
+ uint32_t* aPlatformDNSIndications) {
+ *aPlatformDNSIndications = mPlatformDNSIndications;
+ return NS_OK;
+}
+
+//
+// Hash the sorted network ids
+//
+void nsNotifyAddrListener::HashSortedNetworkIds(std::vector<GUID> nwGUIDS,
+ SHA1Sum& sha1) {
+ std::sort(nwGUIDS.begin(), nwGUIDS.end(), [](const GUID& a, const GUID& b) {
+ return memcmp(&a, &b, sizeof(GUID)) < 0;
+ });
+
+ for (auto const& nwGUID : nwGUIDS) {
+ sha1.update(&nwGUID, sizeof(GUID));
+
+ if (LOG_ENABLED()) {
+ nsPrintfCString guid("%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X",
+ nwGUID.Data1, nwGUID.Data2, nwGUID.Data3,
+ nwGUID.Data4[0], nwGUID.Data4[1], nwGUID.Data4[2],
+ nwGUID.Data4[3], nwGUID.Data4[4], nwGUID.Data4[5],
+ nwGUID.Data4[6], nwGUID.Data4[7]);
+ LOG(("calculateNetworkId: interface networkID: %s\n", guid.get()));
+ }
+ }
+}
+
+//
+// Figure out the current "network identification" string.
+//
+void nsNotifyAddrListener::calculateNetworkId(void) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
+
+ // No need to recompute the networkId if we're shutting down.
+ if (mShutdown) {
+ return;
+ }
+
+ if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
+ return;
+ }
+
+ auto unitialize = MakeScopeExit([]() { CoUninitialize(); });
+
+ RefPtr<INetworkListManager> nlm;
+ HRESULT hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_ALL,
+ IID_INetworkListManager, getter_AddRefs(nlm));
+ if (NS_WARN_IF(FAILED(hr))) {
+ LOG(("CoCreateInstance error: %lX", hr));
+ return;
+ }
+ RefPtr<IEnumNetworks> enumNetworks;
+ hr = nlm->GetNetworks(NLM_ENUM_NETWORK_CONNECTED,
+ getter_AddRefs(enumNetworks));
+ if (NS_WARN_IF(FAILED(hr))) {
+ LOG(("GetNetworks error: %lX", hr));
+ return;
+ }
+
+ // We will hash the found network ids
+ // for privacy reasons
+ SHA1Sum sha1;
+
+ // The networks stored in enumNetworks
+ // are not ordered. We will sort them
+ // To keep a consistent hash
+ // regardless of the found networks order.
+ std::vector<GUID> nwGUIDS;
+
+ // Consume the found networks iterator
+ while (true) {
+ RefPtr<INetwork> network;
+ hr = enumNetworks->Next(1, getter_AddRefs(network), nullptr);
+ if (hr != S_OK) {
+ break;
+ }
+
+ GUID nwGUID;
+ hr = network->GetNetworkId(&nwGUID);
+ if (hr != S_OK) {
+ continue;
+ }
+ nwGUIDS.push_back(nwGUID);
+ }
+
+ if (nwGUIDS.empty()) {
+ bool idChanged = false;
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mNetworkId.IsEmpty()) {
+ idChanged = true;
+ }
+ mNetworkId.Truncate();
+ }
+ LOG(("calculateNetworkId: no network ID - no active networks"));
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
+ if (idChanged) {
+ NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+ }
+ return;
+ }
+
+ nsAutoCString output;
+ SHA1Sum::Hash digest;
+ HashSortedNetworkIds(nwGUIDS, sha1);
+ SeedNetworkId(sha1);
+ sha1.finish(digest);
+ nsCString newString(reinterpret_cast<char*>(digest), SHA1Sum::kHashSize);
+ nsresult rv = Base64Encode(newString, output);
+ if (NS_FAILED(rv)) {
+ {
+ MutexAutoLock lock(mMutex);
+ mNetworkId.Truncate();
+ }
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
+ LOG(("calculateNetworkId: no network ID Base64Encode error %X",
+ uint32_t(rv)));
+ return;
+ }
+
+ MutexAutoLock lock(mMutex);
+ if (output != mNetworkId) {
+ mNetworkId = output;
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1);
+ LOG(("calculateNetworkId: new NetworkID: %s", output.get()));
+ NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
+ LOG(("calculateNetworkId: same NetworkID: %s", output.get()));
+ }
+}
+
+// Static Callback function for NotifyIpInterfaceChange API.
+static void WINAPI OnInterfaceChange(PVOID callerContext,
+ PMIB_IPINTERFACE_ROW row,
+ MIB_NOTIFICATION_TYPE notificationType) {
+ nsNotifyAddrListener* notify =
+ static_cast<nsNotifyAddrListener*>(callerContext);
+ notify->CheckLinkStatus();
+}
+
+DWORD
+nsNotifyAddrListener::nextCoalesceWaitTime() {
+ // check if coalescing period should continue
+ double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
+ if (period >= kNetworkChangeCoalescingPeriod) {
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_CHANGED);
+ mCoalescingActive = false;
+ return INFINITE; // return default
+ } else {
+ // wait no longer than to the end of the period
+ return static_cast<DWORD>(kNetworkChangeCoalescingPeriod - period);
+ }
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::Run() {
+ mStartTime = TimeStamp::Now();
+
+ calculateNetworkId();
+
+ DWORD waitTime = INFINITE;
+
+ // Windows Vista and newer versions.
+ HANDLE interfacechange;
+ // The callback will simply invoke CheckLinkStatus()
+ DWORD ret = NotifyIpInterfaceChange(
+ StaticPrefs::network_notify_IPv6() ? AF_UNSPEC
+ : AF_INET, // IPv4 and IPv6
+ (PIPINTERFACE_CHANGE_CALLBACK)OnInterfaceChange,
+ this, // pass to callback
+ StaticPrefs::network_notify_initial_call(), // initial notification
+ &interfacechange);
+
+ if (ret == NO_ERROR) {
+ do {
+ ret = WaitForSingleObject(mCheckEvent, waitTime);
+ if (!mShutdown) {
+ waitTime = nextCoalesceWaitTime();
+ } else {
+ break;
+ }
+ } while (ret != WAIT_FAILED);
+ CancelMibChangeNotify2(interfacechange);
+ } else {
+ LOG(("Link Monitor: NotifyIpInterfaceChange returned %d\n", (int)ret));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp("xpcom-shutdown-threads", topic)) Shutdown();
+
+ return NS_OK;
+}
+
+nsresult nsNotifyAddrListener::Init(void) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return NS_ERROR_FAILURE;
+
+ nsresult rv =
+ observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCheckEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+ NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsIThreadPool> threadPool = new nsThreadPool();
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(1));
+ MOZ_ALWAYS_SUCCEEDS(
+ threadPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize));
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("Link Monitor"_ns));
+ mThread = threadPool.forget();
+
+ return mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+nsresult nsNotifyAddrListener::Shutdown(void) {
+ // remove xpcom shutdown observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+
+ if (!mCheckEvent) return NS_OK;
+
+ mShutdown = true;
+ SetEvent(mCheckEvent);
+
+ nsresult rv = mThread ? mThread->ShutdownWithTimeout(2000) : NS_OK;
+
+ // Have to break the cycle here, otherwise nsNotifyAddrListener holds
+ // onto the thread and the thread holds onto the nsNotifyAddrListener
+ // via its mRunnable
+ mThread = nullptr;
+
+ CloseHandle(mCheckEvent);
+ mCheckEvent = nullptr;
+
+ return rv;
+}
+
+/*
+ * A network event has been registered. Delay the actual sending of the event
+ * for a while and absorb subsequent events in the mean time in an effort to
+ * squash potentially many triggers into a single event.
+ * Only ever called from the same thread.
+ */
+nsresult nsNotifyAddrListener::NetworkChanged() {
+ if (mCoalescingActive) {
+ LOG(("NetworkChanged: absorbed an event (coalescing active)\n"));
+ } else {
+ // A fresh trigger!
+ mChangeTime = TimeStamp::Now();
+ mCoalescingActive = true;
+ SetEvent(mCheckEvent);
+ LOG(("NetworkChanged: coalescing period started\n"));
+ }
+ return NS_OK;
+}
+
+/* Sends the given event. Assumes aTopic/aData never goes out of scope (static
+ * strings are ideal).
+ */
+nsresult nsNotifyAddrListener::NotifyObservers(const char* aTopic,
+ const char* aData) {
+ LOG(("NotifyObservers: %s=%s\n", aTopic, aData));
+
+ if (mShutdown) {
+ LOG(("NotifyObservers call failed when called during shutdown"));
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ auto runnable = [self = RefPtr<nsNotifyAddrListener>(this), aTopic, aData] {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(
+ static_cast<nsINetworkLinkService*>(self.get()), aTopic,
+ !aData ? nullptr : NS_ConvertASCIItoUTF16(aData).get());
+ };
+ nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNotifyAddrListener::NotifyObservers", runnable));
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "nsNotifyAddrListener::NotifyObservers Failed to dispatch observer "
+ "notification");
+ }
+ return rv;
+}
+
+DWORD
+nsNotifyAddrListener::CheckAdaptersAddresses(void) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Don't call this on the main thread");
+ ULONG len = 16384;
+
+ PIP_ADAPTER_ADDRESSES adapterList = (PIP_ADAPTER_ADDRESSES)moz_xmalloc(len);
+
+ ULONG flags = GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST;
+ if (!StaticPrefs::network_notify_resolvers()) {
+ flags |= GAA_FLAG_SKIP_DNS_SERVER;
+ }
+
+ DWORD ret =
+ GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len);
+ if (ret == ERROR_BUFFER_OVERFLOW) {
+ free(adapterList);
+ adapterList = static_cast<PIP_ADAPTER_ADDRESSES>(moz_xmalloc(len));
+
+ ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len);
+ }
+
+ if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
+ free(adapterList);
+ return ERROR_NOT_SUPPORTED;
+ }
+
+ //
+ // Since NotifyIpInterfaceChange() signals a change more often than we
+ // think is a worthy change, we checksum the entire state of all interfaces
+ // that are UP. If the checksum is the same as previous check, nothing
+ // of interest changed!
+ //
+ ULONG sumAll = 0;
+
+ nsTArray<nsCString> dnsSuffixList;
+ nsTArray<mozilla::net::NetAddr> resolvers;
+ uint32_t platformDNSIndications = NONE_DETECTED;
+ if (ret == ERROR_SUCCESS) {
+ bool linkUp = false;
+ ULONG sum = 0;
+
+ for (PIP_ADAPTER_ADDRESSES adapter = adapterList; adapter;
+ adapter = adapter->Next) {
+ if (adapter->OperStatus != IfOperStatusUp ||
+ !adapter->FirstUnicastAddress ||
+ adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) {
+ continue;
+ }
+
+ LOG(("Adapter %s type: %lu",
+ NS_ConvertUTF16toUTF8(adapter->FriendlyName).get(),
+ adapter->IfType));
+
+ if (adapter->IfType == IF_TYPE_PPP ||
+ adapter->IfType == IF_TYPE_PROP_VIRTUAL ||
+ nsDependentString(adapter->FriendlyName).Find(u"VPN") != kNotFound ||
+ nsDependentString(adapter->Description).Find(u"VPN") != kNotFound) {
+ LOG(("VPN connection found"));
+ platformDNSIndications |= VPN_DETECTED;
+ }
+
+ sum <<= 2;
+ // Add chars from AdapterName to the checksum.
+ for (int i = 0; adapter->AdapterName[i]; ++i) {
+ sum += adapter->AdapterName[i];
+ }
+
+ // Add bytes from each socket address to the checksum.
+ for (PIP_ADAPTER_UNICAST_ADDRESS pip = adapter->FirstUnicastAddress; pip;
+ pip = pip->Next) {
+ SOCKET_ADDRESS* sockAddr = &pip->Address;
+ for (int i = 0; i < sockAddr->iSockaddrLength; ++i) {
+ sum += (reinterpret_cast<unsigned char*>(sockAddr->lpSockaddr))[i];
+ }
+ }
+
+ for (IP_ADAPTER_DNS_SERVER_ADDRESS* pDnServer =
+ adapter->FirstDnsServerAddress;
+ pDnServer; pDnServer = pDnServer->Next) {
+ mozilla::net::NetAddr addr;
+ if (pDnServer->Address.lpSockaddr->sa_family == AF_INET) {
+ const struct sockaddr_in* sin =
+ (const struct sockaddr_in*)pDnServer->Address.lpSockaddr;
+ addr.inet.family = AF_INET;
+ addr.inet.ip = sin->sin_addr.s_addr;
+ addr.inet.port = sin->sin_port;
+ } else if (pDnServer->Address.lpSockaddr->sa_family == AF_INET6) {
+ const struct sockaddr_in6* sin6 =
+ (const struct sockaddr_in6*)pDnServer->Address.lpSockaddr;
+ addr.inet6.family = AF_INET6;
+ memcpy(&addr.inet6.ip.u8, &sin6->sin6_addr, sizeof(addr.inet6.ip.u8));
+ addr.inet6.port = sin6->sin6_port;
+ } else {
+ NS_WARNING("Unexpected addr type");
+ continue;
+ }
+
+ if (LOG_ENABLED()) {
+ char buf[100];
+ addr.ToStringBuffer(buf, 100);
+ LOG(("Found DNS resolver=%s", buf));
+ }
+ resolvers.AppendElement(addr);
+ }
+
+ if (StaticPrefs::network_notify_dnsSuffixList()) {
+ nsCString suffix = NS_ConvertUTF16toUTF8(adapter->DnsSuffix);
+ if (!suffix.IsEmpty()) {
+ LOG((" found DNS suffix=%s\n", suffix.get()));
+ dnsSuffixList.AppendElement(suffix);
+ }
+ }
+
+ linkUp = true;
+ sumAll ^= sum;
+ }
+ mLinkUp = linkUp;
+ mStatusKnown = true;
+ }
+ free(adapterList);
+
+ if (mLinkUp) {
+ /* Store the checksum only if one or more interfaces are up */
+ mIPInterfaceChecksum = sumAll;
+ }
+
+ CoUninitialize();
+
+ if (StaticPrefs::network_notify_dnsSuffixList()) {
+ // It seems that the only way to retrieve non-connection specific DNS
+ // suffixes is via the Windows registry.
+
+ // This function takes a registry path. If aRegPath\\SearchList is
+ // found and successfully parsed, then it returns true. Otherwise it
+ // returns false.
+ auto checkRegistry = [&dnsSuffixList](const nsAString& aRegPath) -> bool {
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG((" creating nsIWindowsRegKey failed\n"));
+ return false;
+ }
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, aRegPath,
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ LOG((" opening registry key failed\n"));
+ return false;
+ }
+ nsAutoString wideSuffixString;
+ rv = regKey->ReadStringValue(u"SearchList"_ns, wideSuffixString);
+ if (NS_FAILED(rv)) {
+ LOG((" reading registry string value failed\n"));
+ return false;
+ }
+
+ // Normally the key should not contain whitespace, but editing the
+ // registry manually or through gpedit doesn't alway enforce this.
+ nsAutoCString list = NS_ConvertUTF16toUTF8(wideSuffixString);
+ list.StripWhitespace();
+ for (const nsACString& suffix : list.Split(',')) {
+ LOG((" appending DNS suffix from registry: %s\n",
+ suffix.BeginReading()));
+ if (!suffix.IsEmpty()) {
+ dnsSuffixList.AppendElement(suffix);
+ }
+ }
+
+ return true;
+ };
+
+ // The Local group policy overrides the user set suffix list, so we must
+ // first check the registry key that is sets by gpedit, and if that fails we
+ // fall back to the one that is set by the user.
+ if (!checkRegistry(nsLiteralString(
+ u"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient"))) {
+ checkRegistry(nsLiteralString(
+ u"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"));
+ }
+ }
+
+ auto registryChildCount = [](const nsAString& aRegPath) -> uint32_t {
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG((" creating nsIWindowsRegKey failed\n"));
+ return 0;
+ }
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, aRegPath,
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ LOG((" opening registry key failed\n"));
+ return 0;
+ }
+
+ uint32_t count = 0;
+ rv = regKey->GetChildCount(&count);
+ if (NS_FAILED(rv)) {
+ return 0;
+ }
+
+ return count;
+ };
+
+ if (StaticPrefs::network_notify_checkForProxies()) {
+ if (registryChildCount(u"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\"
+ "Parameters\\DnsConnections"_ns) > 0 ||
+ registryChildCount(u"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\"
+ "Parameters\\DnsConnectionsProxies"_ns) > 0) {
+ platformDNSIndications |= PROXY_DETECTED;
+ }
+ }
+
+ if (StaticPrefs::network_notify_checkForNRPT()) {
+ if (registryChildCount(u"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\"
+ "Parameters\\DnsPolicyConfig"_ns) > 0 ||
+ registryChildCount(u"SOFTWARE\\Policies\\Microsoft\\Windows NT\\"
+ "DNSClient\\DnsPolicyConfig"_ns) > 0) {
+ platformDNSIndications |= NRPT_DETECTED;
+ }
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ mDnsSuffixList = std::move(dnsSuffixList);
+ mDNSResolvers = std::move(resolvers);
+ mPlatformDNSIndications = platformDNSIndications;
+ }
+
+ NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
+
+ calculateNetworkId();
+
+ return ret;
+}
+
+/**
+ * Checks the status of all network adapters. If one is up and has a valid IP
+ * address, sets mLinkUp to true. Sets mStatusKnown to true if the link status
+ * is definitive.
+ */
+void nsNotifyAddrListener::CheckLinkStatus(void) {
+ DWORD ret;
+ const char* event;
+ bool prevLinkUp = mLinkUp;
+ ULONG prevCsum = mIPInterfaceChecksum;
+
+ LOG(("check status of all network adapters\n"));
+
+ // The CheckAdaptersAddresses call is very expensive (~650 milliseconds),
+ // so we don't want to call it synchronously. Instead, we just start up
+ // assuming we have a network link, but we'll report that the status is
+ // unknown.
+ if (NS_IsMainThread()) {
+ NS_WARNING(
+ "CheckLinkStatus called on main thread! No check "
+ "performed. Assuming link is up, status is unknown.");
+ mLinkUp = true;
+
+ if (!mStatusKnown) {
+ event = NS_NETWORK_LINK_DATA_UNKNOWN;
+ } else if (!prevLinkUp) {
+ event = NS_NETWORK_LINK_DATA_UP;
+ } else {
+ // Known status and it was already UP
+ event = nullptr;
+ }
+
+ if (event) {
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, event);
+ }
+ } else {
+ ret = CheckAdaptersAddresses();
+ if (ret != ERROR_SUCCESS) {
+ mLinkUp = true;
+ }
+
+ if (mLinkUp && (prevCsum != mIPInterfaceChecksum)) {
+ TimeDuration since = TimeStamp::Now() - mStartTime;
+
+ // Network is online. Topology has changed. Always send CHANGED
+ // before UP - if allowed to and having cooled down.
+ if (StaticPrefs::network_notify_changed() &&
+ (since.ToMilliseconds() > 2000)) {
+ NetworkChanged();
+ }
+ }
+ if (prevLinkUp != mLinkUp) {
+ // UP/DOWN status changed, send appropriate UP/DOWN event
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, mLinkUp
+ ? NS_NETWORK_LINK_DATA_UP
+ : NS_NETWORK_LINK_DATA_DOWN);
+ }
+ }
+}
diff --git a/netwerk/system/win32/nsNotifyAddrListener.h b/netwerk/system/win32/nsNotifyAddrListener.h
new file mode 100644
index 0000000000..30244685f2
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NSNOTIFYADDRLISTENER_H_
+#define NSNOTIFYADDRLISTENER_H_
+
+#include <windows.h>
+#include <winsock2.h>
+#include <iptypes.h>
+#include "nsINetworkLinkService.h"
+#include "nsIRunnable.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsThreadPool.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/net/DNS.h"
+
+class nsIThreadPool;
+
+class nsNotifyAddrListener : public nsINetworkLinkService,
+ public nsIRunnable,
+ public nsIObserver {
+ virtual ~nsNotifyAddrListener();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+
+ nsNotifyAddrListener();
+
+ nsresult Init(void);
+ void CheckLinkStatus(void);
+ static void HashSortedNetworkIds(const std::vector<GUID> nwGUIDS,
+ mozilla::SHA1Sum& sha1);
+
+ protected:
+ bool mLinkUp;
+ bool mStatusKnown;
+ bool mCheckAttempted;
+
+ nsresult Shutdown(void);
+ nsresult NotifyObservers(const char* aTopic, const char* aData);
+
+ DWORD CheckAdaptersAddresses(void);
+
+ // This threadpool only ever holds 1 thread. It is a threadpool and not a
+ // regular thread so that we may call shutdownWithTimeout on it.
+ nsCOMPtr<nsIThreadPool> mThread;
+
+ private:
+ // Returns the new timeout period for coalescing (or INFINITE)
+ DWORD nextCoalesceWaitTime();
+
+ // Called for every detected network change
+ nsresult NetworkChanged();
+
+ // Figure out the current network identification
+ void calculateNetworkId(void);
+ bool findMac(char* gateway);
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+ nsCString mNetworkId;
+ nsTArray<nsCString> mDnsSuffixList;
+ nsTArray<mozilla::net::NetAddr> mDNSResolvers;
+
+ HANDLE mCheckEvent;
+
+ // set true when mCheckEvent means shutdown
+ mozilla::Atomic<bool> mShutdown;
+
+ // Contains a set of flags that codify the reasons for which
+ // the platform indicates DNS should be used instead of TRR.
+ mozilla::Atomic<uint32_t, mozilla::Relaxed> mPlatformDNSIndications;
+
+ // This is a checksum of various meta data for all network interfaces
+ // considered UP at last check.
+ ULONG mIPInterfaceChecksum;
+
+ // start time of the checking
+ mozilla::TimeStamp mStartTime;
+
+ // Flag set while coalescing change events
+ bool mCoalescingActive;
+
+ // Time stamp for first event during coalescing
+ mozilla::TimeStamp mChangeTime;
+};
+
+#endif /* NSNOTIFYADDRLISTENER_H_ */
diff --git a/netwerk/test/browser/103_preload.html b/netwerk/test/browser/103_preload.html
new file mode 100644
index 0000000000..9583815cfb
--- /dev/null
+++ b/netwerk/test/browser/103_preload.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/square.png" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload.html^headers^ b/netwerk/test/browser/103_preload.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/103_preload.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/103_preload.html^informationalResponse^ b/netwerk/test/browser/103_preload.html^informationalResponse^
new file mode 100644
index 0000000000..b95a96e74b
--- /dev/null
+++ b/netwerk/test/browser/103_preload.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Too Early
+Link: <https://example.com/browser/netwerk/test/browser/square.png>; rel=preload; as=image
diff --git a/netwerk/test/browser/103_preload_anchor.html b/netwerk/test/browser/103_preload_anchor.html
new file mode 100644
index 0000000000..c12fe92072
--- /dev/null
+++ b/netwerk/test/browser/103_preload_anchor.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs?f5a05cb8-43e6-4868-bc0f-ca453ef87826" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload_anchor.html^headers^ b/netwerk/test/browser/103_preload_anchor.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/103_preload_anchor.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/103_preload_anchor.html^informationalResponse^ b/netwerk/test/browser/103_preload_anchor.html^informationalResponse^
new file mode 100644
index 0000000000..1099062e15
--- /dev/null
+++ b/netwerk/test/browser/103_preload_anchor.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Early Hints
+Link: <netwerk/test/browser/early_hint_pixel.sjs?f5a05cb8-43e6-4868-bc0f-ca453ef87826>; rel=preload; as=image; anchor="/browser/"
diff --git a/netwerk/test/browser/103_preload_and_404.html b/netwerk/test/browser/103_preload_and_404.html
new file mode 100644
index 0000000000..f09f5cb085
--- /dev/null
+++ b/netwerk/test/browser/103_preload_and_404.html
@@ -0,0 +1,6 @@
+<html>
+ <head><title>404 Not Found</title></head>
+ <body>
+ <h1>404 Not Found</h1>
+ </body>
+</html>
diff --git a/netwerk/test/browser/103_preload_and_404.html^headers^ b/netwerk/test/browser/103_preload_and_404.html^headers^
new file mode 100644
index 0000000000..937e38c6c4
--- /dev/null
+++ b/netwerk/test/browser/103_preload_and_404.html^headers^
@@ -0,0 +1 @@
+HTTP 404 Not Found
diff --git a/netwerk/test/browser/103_preload_and_404.html^informationalResponse^ b/netwerk/test/browser/103_preload_and_404.html^informationalResponse^
new file mode 100644
index 0000000000..78cb7efea4
--- /dev/null
+++ b/netwerk/test/browser/103_preload_and_404.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Early Hints
+Link: <https://example.com/browser/netwerk/test/browser/square.png>; rel=preload; as=image
diff --git a/netwerk/test/browser/103_preload_csp_imgsrc_none.html b/netwerk/test/browser/103_preload_csp_imgsrc_none.html
new file mode 100644
index 0000000000..367e80a6b3
--- /dev/null
+++ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs?1ac2a5e1-90c7-4171-b0f0-676f7d899af3" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^
new file mode 100644
index 0000000000..b4dedd0812
--- /dev/null
+++ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Content-Security-Policy: img-src 'none'
diff --git a/netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^
new file mode 100644
index 0000000000..d82224fd07
--- /dev/null
+++ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Too Early
+Link: <https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs?1ac2a5e1-90c7-4171-b0f0-676f7d899af3>; rel=preload; as=image
diff --git a/netwerk/test/browser/103_preload_iframe.html b/netwerk/test/browser/103_preload_iframe.html
new file mode 100644
index 0000000000..815a14220f
--- /dev/null
+++ b/netwerk/test/browser/103_preload_iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe src="/browser/netwerk/test/browser/early_hint_main_html.sjs?early_hint_pixel.sjs=5ecccd01-dd3f-4bbd-bd3e-0491d7dd78a1" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload_iframe.html^headers^ b/netwerk/test/browser/103_preload_iframe.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/103_preload_iframe.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/auth_post.sjs b/netwerk/test/browser/auth_post.sjs
new file mode 100644
index 0000000000..8c3e723558
--- /dev/null
+++ b/netwerk/test/browser/auth_post.sjs
@@ -0,0 +1,37 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+
+ return result.join("");
+}
+
+function handleRequest(request, response) {
+ if (request.method != "POST") {
+ response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
+ return;
+ }
+
+ if (request.hasHeader("Authorization")) {
+ let data = "";
+ try {
+ data = readStream(new BinaryInputStream(request.bodyInputStream));
+ } catch (e) {
+ data = `${e}`;
+ }
+ response.bodyOutputStream.write(data, data.length);
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", `basic realm="test"`, true);
+}
diff --git a/netwerk/test/browser/browser.toml b/netwerk/test/browser/browser.toml
new file mode 100644
index 0000000000..9a470d8c59
--- /dev/null
+++ b/netwerk/test/browser/browser.toml
@@ -0,0 +1,188 @@
+[DEFAULT]
+support-files = [
+ "dummy.html",
+ "ioactivity.html",
+ "redirect.sjs",
+ "auth_post.sjs",
+ "early_hint_main_html.sjs",
+ "early_hint_pixel_count.sjs",
+ "early_hint_redirect.sjs",
+ "early_hint_redirect_html.sjs",
+ "early_hint_pixel.sjs",
+ "early_hint_error.sjs",
+ "early_hint_asset.sjs",
+ "early_hint_asset_html.sjs",
+ "early_hint_csp_options_html.sjs",
+ "early_hint_preconnect_html.sjs",
+ "post.html",
+ "res.css",
+ "res.css^headers^",
+ "res.csv",
+ "res.csv^headers^",
+ "res_206.html",
+ "res_206.html^headers^",
+ "res_nosniff.html",
+ "res_nosniff.html^headers^",
+ "res_img.png",
+ "res_nosniff2.html",
+ "res_nosniff2.html^headers^",
+ "res_not_ok.html",
+ "res_not_ok.html^headers^",
+ "res.unknown",
+ "res_img_unknown.png",
+ "res.mp3",
+ "res_invalid_partial.mp3",
+ "res_invalid_partial.mp3^headers^",
+ "res_206.mp3",
+ "res_206.mp3^headers^",
+ "res_not_200or206.mp3",
+ "res_not_200or206.mp3^headers^",
+ "res_img_for_unknown_decoder",
+ "res_img_for_unknown_decoder^headers^",
+ "res_object.html",
+ "res_sub_document.html",
+ "res_empty.zip",
+ "res_http_index_format",
+ "res_http_index_format^headers^",
+ "square.png",
+ "103_preload.html",
+ "103_preload.html^informationalResponse^",
+ "103_preload.html^headers^",
+ "no_103_preload.html",
+ "no_103_preload.html^headers^",
+ "103_preload_anchor.html^informationalResponse^",
+ "103_preload_anchor.html^headers^",
+ "103_preload_anchor.html",
+ "103_preload_and_404.html^informationalResponse^",
+ "103_preload_and_404.html^headers^",
+ "103_preload_and_404.html",
+ "103_preload_iframe.html",
+ "103_preload_iframe.html^headers^",
+ "103_preload_csp_imgsrc_none.html",
+ "103_preload_csp_imgsrc_none.html^headers^",
+ "103_preload_csp_imgsrc_none.html^informationalResponse^",
+ "cookie_filtering_resource.sjs",
+ "cookie_filtering_secure_resource_com.html",
+ "cookie_filtering_secure_resource_com.html^headers^",
+ "cookie_filtering_secure_resource_org.html",
+ "cookie_filtering_secure_resource_org.html^headers^",
+ "cookie_filtering_square.png",
+ "cookie_filtering_square.png^headers^",
+ "x_frame_options.html",
+ "x_frame_options.html^headers^",
+ "test_1629307.html",
+ "file_link_header.sjs",
+]
+
+["browser_103_assets.js"]
+
+["browser_103_assets_extension.js"]
+
+["browser_103_cleanup.js"]
+
+["browser_103_csp.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_csp_images.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_csp_styles.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_error.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_no_cancel_on_error.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_preconnect.js"]
+
+["browser_103_preload.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_preload_2.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_private_window.js"]
+
+["browser_103_redirect.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_redirect_from_server.js"]
+
+["browser_103_referrer_policy.js"]
+support-files = [
+ "early_hint_referrer_policy_html.sjs",
+ "early_hint_preload_test_helper.sys.mjs",
+]
+
+["browser_103_telemetry.js"]
+
+["browser_103_user_load.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_NetUtil.js"]
+
+["browser_about_cache.js"]
+
+["browser_backgroundtask_purgeHTTPCache.js"]
+skip-if = [
+ "os == 'android'", # MultiInstanceLock doesn't work on Android
+ "os == 'mac'", # intermittent TV timeouts on Mac
+]
+
+["browser_bug1535877.js"]
+
+["browser_bug1629307.js"]
+fail-if = ["a11y_checks"] # Bug 1854523 clicked button may not be focusable
+
+["browser_child_resource.js"]
+run-if = ["crashreporter"]
+skip-if = ["os == 'win'"] # Bug 1775761
+
+["browser_cookie_filtering_basic.js"]
+
+["browser_cookie_filtering_cross_origin.js"]
+
+["browser_cookie_filtering_insecure.js"]
+
+["browser_cookie_filtering_oa.js"]
+
+["browser_cookie_filtering_subdomain.js"]
+
+["browser_cookie_sync_across_tabs.js"]
+
+["browser_fetch_lnk.js"]
+run-if = ["os == 'win'"]
+support-files = ["file_lnk.lnk",]
+
+["browser_http_index_format.js"]
+
+["browser_nsIFormPOSTActionChannel.js"]
+skip-if = ["true"] # protocol handler and channel does not work in content process
+
+["browser_post_auth.js"]
+skip-if = ["socketprocess_networking"] # Bug 1772209
+
+["browser_post_file.js"]
+
+["browser_purgeCache_idle_daily.js"]
+
+["browser_resource_navigation.js"]
+
+["browser_speculative_connection_link_header.js"]
+
+["browser_test_data_channel_observer.js"]
+
+["browser_test_favicon.js"]
+skip-if = ["verify && (os == 'linux' || os == 'mac')"]
+support-files = [
+ "damonbowling.jpg",
+ "damonbowling.jpg^headers^",
+ "file_favicon.html",
+]
+
+["browser_test_io_activity.js"]
+skip-if = ["socketprocess_networking"]
+
+["browser_test_offline_tab.js"]
diff --git a/netwerk/test/browser/browser_103_assets.js b/netwerk/test/browser/browser_103_assets.js
new file mode 100644
index 0000000000..4e32c8bf7c
--- /dev/null
+++ b/netwerk/test/browser/browser_103_assets.js
@@ -0,0 +1,169 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// On debug osx test machine, verify chaos mode takes slightly too long
+requestLongerTimeout(2);
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { request_count_checking } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// - testName is just there to be printed during Asserts when failing
+// - asset is the asset type, see early_hint_asset_html.sjs for possible values
+// for the asset type fetch see test_hint_fetch due to timing issues
+// - variant:
+// - "normal": no early hints, expects one normal request expected
+// - "hinted": early hints sent, expects one hinted request
+// - "reload": early hints sent, resources non-cacheable, two early-hint requests expected
+// - "cached": same as reload, but resources are cacheable, so only one hinted network request expected
+async function test_hint_asset(testName, asset, variant) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=${asset}&hinted=${
+ variant !== "normal" ? "1" : "0"
+ }&cached=${variant === "cached" ? "1" : "0"}`;
+
+ let numConnectBackRemaining = 0;
+ if (variant === "hinted") {
+ numConnectBackRemaining = 1;
+ } else if (variant === "reload" || variant === "cached") {
+ numConnectBackRemaining = 2;
+ }
+
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "earlyhints-connectback") {
+ numConnectBackRemaining -= 1;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "earlyhints-connectback");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function (browser) {
+ if (asset === "fetch") {
+ // wait until the fetch is complete
+ await TestUtils.waitForCondition(_ => {
+ return SpecialPowers.spawn(browser, [], _ => {
+ return (
+ content.document.getElementsByTagName("h2")[0] != undefined &&
+ content.document.getElementsByTagName("h2")[0].textContent !==
+ "Fetching..." // default text set by early_hint_asset_html.sjs
+ );
+ });
+ });
+ }
+
+ // reload
+ if (variant === "reload" || variant === "cached") {
+ await BrowserTestUtils.reloadTab(gBrowser.selectedTab);
+ }
+
+ if (asset === "fetch") {
+ // wait until the fetch is complete
+ await TestUtils.waitForCondition(_ => {
+ return SpecialPowers.spawn(browser, [], _ => {
+ return (
+ content.document.getElementsByTagName("h2")[0] != undefined &&
+ content.document.getElementsByTagName("h2")[0].textContent !==
+ "Fetching..." // default text set by early_hint_asset_html.sjs
+ );
+ });
+ });
+ }
+ }
+ );
+ Services.obs.removeObserver(observer, "earlyhints-connectback");
+
+ let gotRequestCount = await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+ Assert.equal(
+ numConnectBackRemaining,
+ 0,
+ `${testName} (${asset}) no remaining connect back expected`
+ );
+
+ let expectedRequestCount;
+ if (variant === "normal") {
+ expectedRequestCount = { hinted: 0, normal: 1 };
+ } else if (variant === "hinted") {
+ expectedRequestCount = { hinted: 1, normal: 0 };
+ } else if (variant === "reload") {
+ expectedRequestCount = { hinted: 2, normal: 0 };
+ } else if (variant === "cached") {
+ expectedRequestCount = { hinted: 1, normal: 0 };
+ }
+
+ await request_count_checking(
+ `${testName} (${asset})`,
+ gotRequestCount,
+ expectedRequestCount
+ );
+ if (variant === "cached") {
+ Services.cache2.clear();
+ }
+}
+
+// preload image
+add_task(async function test_103_asset_image() {
+ await test_hint_asset("test_103_asset_normal", "image", "normal");
+ await test_hint_asset("test_103_asset_hinted", "image", "hinted");
+ await test_hint_asset("test_103_asset_reload", "image", "reload");
+ // TODO(Bug 1815884): await test_hint_asset("test_103_asset_cached", "image", "cached");
+});
+
+// preload css
+add_task(async function test_103_asset_style() {
+ await test_hint_asset("test_103_asset_normal", "style", "normal");
+ await test_hint_asset("test_103_asset_hinted", "style", "hinted");
+ await test_hint_asset("test_103_asset_reload", "style", "reload");
+ // TODO(Bug 1815884): await test_hint_asset("test_103_asset_cached", "style", "cached");
+});
+
+// preload javascript
+add_task(async function test_103_asset_javascript() {
+ await test_hint_asset("test_103_asset_normal", "script", "normal");
+ await test_hint_asset("test_103_asset_hinted", "script", "hinted");
+ await test_hint_asset("test_103_asset_reload", "script", "reload");
+ await test_hint_asset("test_103_asset_cached", "script", "cached");
+});
+
+// preload javascript module
+add_task(async function test_103_asset_module() {
+ await test_hint_asset("test_103_asset_normal", "module", "normal");
+ await test_hint_asset("test_103_asset_hinted", "module", "hinted");
+ await test_hint_asset("test_103_asset_reload", "module", "reload");
+ await test_hint_asset("test_103_asset_cached", "module", "cached");
+});
+
+// preload font
+add_task(async function test_103_asset_font() {
+ await test_hint_asset("test_103_asset_normal", "font", "normal");
+ await test_hint_asset("test_103_asset_hinted", "font", "hinted");
+ await test_hint_asset("test_103_asset_reload", "font", "reload");
+ await test_hint_asset("test_103_asset_cached", "font", "cached");
+});
+
+// preload fetch
+add_task(async function test_103_asset_fetch() {
+ await test_hint_asset("test_103_asset_normal", "fetch", "normal");
+ await test_hint_asset("test_103_asset_hinted", "fetch", "hinted");
+ await test_hint_asset("test_103_asset_reload", "fetch", "reload");
+ await test_hint_asset("test_103_asset_cached", "fetch", "cached");
+});
diff --git a/netwerk/test/browser/browser_103_assets_extension.js b/netwerk/test/browser/browser_103_assets_extension.js
new file mode 100644
index 0000000000..dbd1061380
--- /dev/null
+++ b/netwerk/test/browser/browser_103_assets_extension.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+});
+
+const { request_count_checking } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// Test steps:
+// 1. Install an extension to observe the tabId.
+// 2. Load early_hint_asset_html.sjs?as=image, so we should see a hinted
+// request to early_hint_asset.sjs?as=image.
+// 3. Check if the hinted request has the same tabId as
+// early_hint_asset_html.sys.
+add_task(async function test_103_asset_with_extension() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "webRequest",
+ "webRequestBlocking",
+ "https://example.com/*",
+ ],
+ },
+ background() {
+ let { browser } = this;
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("request", {
+ url: details.url,
+ tabId: details.tabId,
+ });
+ return undefined;
+ },
+ { urls: ["https://example.com/*"] },
+ ["blocking"]
+ );
+ },
+ });
+
+ await extension.startup();
+
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let baseUrl = "https://example.com/browser/netwerk/test/browser/";
+ let requestUrl = `${baseUrl}early_hint_asset_html.sjs?as=image&hinted=1`;
+ let assetUrl = `${baseUrl}early_hint_asset.sjs?as=image`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ const result1 = await extension.awaitMessage("request");
+ Assert.equal(result1.url, requestUrl);
+ let tabId = result1.tabId;
+
+ const result2 = await extension.awaitMessage("request");
+ Assert.equal(result2.url, assetUrl);
+ Assert.equal(result2.tabId, tabId);
+
+ await extension.unload();
+});
diff --git a/netwerk/test/browser/browser_103_cleanup.js b/netwerk/test/browser/browser_103_cleanup.js
new file mode 100644
index 0000000000..c823f5f01a
--- /dev/null
+++ b/netwerk/test/browser/browser_103_cleanup.js
@@ -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/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+add_task(async function test_103_cancel_parent_connect() {
+ Services.prefs.setIntPref("network.early-hints.parent-connect-timeout", 1);
+
+ let callback;
+ let promise = new Promise(resolve => {
+ callback = resolve;
+ });
+ let observed_cancel_reason = "";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ aSubject = aSubject.QueryInterface(Ci.nsIRequest);
+ if (
+ aTopic == "http-on-stop-request" &&
+ aSubject.name ==
+ "https://example.com/browser/netwerk/test/browser/square.png"
+ ) {
+ observed_cancel_reason = aSubject.canceledReason;
+ Services.obs.removeObserver(observer, "http-on-stop-request");
+ callback();
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "http-on-stop-request");
+
+ // test that no crash or memory leak happens when cancelling before
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "https://example.com/browser/netwerk/test/browser/103_preload.html",
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+ await promise;
+ Assert.equal(observed_cancel_reason, "parent-connect-timeout");
+
+ Services.prefs.clearUserPref("network.early-hints.parent-connect-timeout");
+});
diff --git a/netwerk/test/browser/browser_103_csp.js b/netwerk/test/browser/browser_103_csp.js
new file mode 100644
index 0000000000..1786bac454
--- /dev/null
+++ b/netwerk/test/browser/browser_103_csp.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_preload_hint_and_request } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+add_task(async function test_preload_images_csp_in_early_hints_response() {
+ let tests = [
+ {
+ input: {
+ test_name: "image - no csp",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'self';",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'self'; same host provided",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "https://example.com/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'self'; other host provided",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "https://example.org/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'none';",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'none'; same host provided",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'none';",
+ host: "https://example.com/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ ];
+
+ for (let test of tests) {
+ await test_preload_hint_and_request(test.input, test.expected);
+ }
+});
diff --git a/netwerk/test/browser/browser_103_csp_images.js b/netwerk/test/browser/browser_103_csp_images.js
new file mode 100644
index 0000000000..c089f29898
--- /dev/null
+++ b/netwerk/test/browser/browser_103_csp_images.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+// This verifies hints, requests server-side and client-side that the image actually loaded
+async function test_image_preload_hint_request_loaded(
+ input,
+ expected_results,
+ image_should_load
+) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_csp_options_html.sjs?as=${
+ input.resource_type
+ }&hinted=${input.hinted ? "1" : "0"}${input.csp ? "&csp=" + input.csp : ""}${
+ input.csp_in_early_hint
+ ? "&csp_in_early_hint=" + input.csp_in_early_hint
+ : ""
+ }${input.host ? "&host=" + input.host : ""}`;
+
+ console.log("requestUrl: " + requestUrl);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function (browser) {
+ let imageLoaded = await ContentTask.spawn(browser, [], function () {
+ let image = content.document.getElementById("test_image");
+ return image && image.complete && image.naturalHeight !== 0;
+ });
+ await Assert.ok(
+ image_should_load == imageLoaded,
+ "test_image_preload_hint_request_loaded: the image can be loaded as expected " +
+ requestUrl
+ );
+ }
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await Assert.deepEqual(gotRequestCount, expected_results, input.test_name);
+
+ Services.cache2.clear();
+}
+
+// These tests verify whether or not the image actually loaded in the document
+add_task(async function test_images_loaded_with_csp() {
+ let tests = [
+ {
+ input: {
+ test_name: "image loaded - no csp",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src none",
+ resource_type: "image",
+ csp: "img-src 'none';",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: false,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src none in EH response",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src none in both headers",
+ resource_type: "image",
+ csp: "img-src 'none';",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 0 },
+ image_should_load: false,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src self",
+ resource_type: "image",
+ csp: "img-src 'self';",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src self in EH response",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - conflicting csp, early hint skipped",
+ resource_type: "image",
+ csp: "img-src 'self';",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name:
+ "image loaded - conflicting csp, resource not loaded in document",
+ resource_type: "image",
+ csp: "img-src 'none';",
+ csp_in_early_hint: "img-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: false,
+ },
+ ];
+
+ for (let test of tests) {
+ await test_image_preload_hint_request_loaded(
+ test.input,
+ test.expected,
+ test.image_should_load
+ );
+ }
+});
diff --git a/netwerk/test/browser/browser_103_csp_styles.js b/netwerk/test/browser/browser_103_csp_styles.js
new file mode 100644
index 0000000000..59c2fc14be
--- /dev/null
+++ b/netwerk/test/browser/browser_103_csp_styles.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_preload_hint_and_request } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+add_task(async function test_preload_styles_csp_in_response() {
+ let tests = [
+ {
+ input: {
+ test_name: "style - no csp",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'self';",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "style style-src self; same host provided",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'self';",
+ host: "https://example.com/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'self'; other host provided",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'self';",
+ host: "https://example.org/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'none';",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'none'; other host provided",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'none';",
+ host: "https://example.org/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ ];
+
+ for (let test of tests) {
+ await test_preload_hint_and_request(test.input, test.expected);
+ }
+});
diff --git a/netwerk/test/browser/browser_103_error.js b/netwerk/test/browser/browser_103_error.js
new file mode 100644
index 0000000000..a7a447aa7e
--- /dev/null
+++ b/netwerk/test/browser/browser_103_error.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_hint_preload } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// 400 Bad Request
+add_task(async function test_103_error_400() {
+ await test_hint_preload(
+ "test_103_error_400",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?400",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 401 Unauthorized
+add_task(async function test_103_error_401() {
+ await test_hint_preload(
+ "test_103_error_401",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?401",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 403 Forbidden
+add_task(async function test_103_error_403() {
+ await test_hint_preload(
+ "test_103_error_403",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?403",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 404 Not Found
+add_task(async function test_103_error_404() {
+ await test_hint_preload(
+ "test_103_error_404",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?404",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 408 Request Timeout
+add_task(async function test_103_error_408() {
+ await test_hint_preload(
+ "test_103_error_408",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?408",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 410 Gone
+add_task(async function test_103_error_410() {
+ await test_hint_preload(
+ "test_103_error_410",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?410",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 429 Too Many Requests
+add_task(async function test_103_error_429() {
+ await test_hint_preload(
+ "test_103_error_429",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?429",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 500 Internal Server Error
+add_task(async function test_103_error_500() {
+ await test_hint_preload(
+ "test_103_error_500",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?500",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 502 Bad Gateway
+add_task(async function test_103_error_502() {
+ await test_hint_preload(
+ "test_103_error_502",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?502",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 503 Service Unavailable
+add_task(async function test_103_error_503() {
+ await test_hint_preload(
+ "test_103_error_503",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?503",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 504 Gateway Timeout
+add_task(async function test_103_error_504() {
+ await test_hint_preload(
+ "test_103_error_504",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?504",
+ { hinted: 1, normal: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_no_cancel_on_error.js b/netwerk/test/browser/browser_103_no_cancel_on_error.js
new file mode 100644
index 0000000000..2420441585
--- /dev/null
+++ b/netwerk/test/browser/browser_103_no_cancel_on_error.js
@@ -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/. */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { request_count_checking } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// - httpCode is the response code we're testing for. This file mostly covers 400 and 500 responses
+async function test_hint_completion_on_error(httpCode) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?hinted=1&as=image&code=${httpCode}`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking(`test_103_error_${httpCode}`, gotRequestCount, {
+ hinted: 1,
+ normal: 0,
+ });
+}
+
+// 400 Bad Request
+add_task(async function test_complete_103_on_400() {
+ await test_hint_completion_on_error(400);
+});
+add_task(async function test_complete_103_on_401() {
+ await test_hint_completion_on_error(401);
+});
+add_task(async function test_complete_103_on_402() {
+ await test_hint_completion_on_error(402);
+});
+add_task(async function test_complete_103_on_403() {
+ await test_hint_completion_on_error(403);
+});
+add_task(async function test_complete_103_on_500() {
+ await test_hint_completion_on_error(500);
+});
+add_task(async function test_complete_103_on_501() {
+ await test_hint_completion_on_error(501);
+});
+add_task(async function test_complete_103_on_502() {
+ await test_hint_completion_on_error(502);
+});
diff --git a/netwerk/test/browser/browser_103_preconnect.js b/netwerk/test/browser/browser_103_preconnect.js
new file mode 100644
index 0000000000..dcdcc1b138
--- /dev/null
+++ b/netwerk/test/browser/browser_103_preconnect.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+Services.prefs.setBoolPref("network.early-hints.preconnect.enabled", true);
+Services.prefs.setBoolPref("network.http.debug-observations", true);
+Services.prefs.setIntPref("network.early-hints.preconnect.max_connections", 10);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+ Services.prefs.clearUserPref("network.early-hints.preconnect.enabled");
+ Services.prefs.clearUserPref("network.http.debug-observations");
+ Services.prefs.clearUserPref(
+ "network.early-hints.preconnect.max_connections"
+ );
+});
+
+// Test steps:
+// 1. Load early_hint_preconnect_html.sjs
+// 2. In early_hint_preconnect_html.sjs, a 103 response with
+// "rel=preconnect" is returned.
+// 3. We use "speculative-connect-request" topic to observe whether the
+// speculative connection is attempted.
+// 4. Finally, we check if the observed URL is the same as the expected.
+async function test_hint_preconnect(href, crossOrigin) {
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_preconnect_html.sjs?href=${href}&crossOrigin=${crossOrigin}`;
+
+ let observed = "";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "speculative-connect-request") {
+ Services.obs.removeObserver(observer, "speculative-connect-request");
+ observed = aData;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "speculative-connect-request");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ // Extracting "localhost:443"
+ let hostPortRegex = /\[.*\](.*?)\^/;
+ let hostPortMatch = hostPortRegex.exec(observed);
+ let hostPort = hostPortMatch ? hostPortMatch[1] : "";
+ // Extracting "%28https%2Cexample.com%29"
+ let partitionKeyRegex = /\^partitionKey=(.*)$/;
+ let partitionKeyMatch = partitionKeyRegex.exec(observed);
+ let partitionKey = partitionKeyMatch ? partitionKeyMatch[1] : "";
+ // See nsHttpConnectionInfo::BuildHashKey, the second character is A if this
+ // is an anonymous connection.
+ let anonymousFlag = observed[2];
+
+ Assert.equal(anonymousFlag, crossOrigin === "use-credentials" ? "." : "A");
+ Assert.equal(hostPort, "localhost:443");
+ Assert.equal(partitionKey, "%28https%2Cexample.com%29");
+}
+
+add_task(async function test_103_preconnect() {
+ await test_hint_preconnect("https://localhost", "use-credentials");
+ await test_hint_preconnect("https://localhost", "");
+ await test_hint_preconnect("https://localhost", "anonymous");
+});
diff --git a/netwerk/test/browser/browser_103_preload.js b/netwerk/test/browser/browser_103_preload.js
new file mode 100644
index 0000000000..977ae83d68
--- /dev/null
+++ b/netwerk/test/browser/browser_103_preload.js
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+// Disable mixed-content upgrading as this test is expecting HTTP image loads
+Services.prefs.setBoolPref(
+ "security.mixed_content.upgrade_display_content",
+ false
+);
+
+const { request_count_checking, test_preload_url, test_hint_preload } =
+ ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+ );
+
+// TODO testing:
+// * Abort main document load while early hint is still loading -> early hint should be aborted
+
+// Test that with early hint config option disabled, no early hint requests are made
+add_task(async function test_103_preload_disabled() {
+ Services.prefs.setBoolPref("network.early-hints.enabled", false);
+ await test_hint_preload(
+ "test_103_preload_disabled",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+ Services.prefs.setBoolPref("network.early-hints.enabled", true);
+});
+
+add_task(async function test_103_font_disabled() {
+ let url =
+ "https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?hinted=1&as=font";
+ Services.prefs.setBoolPref("gfx.downloadable_fonts.enabled", false);
+ await test_preload_url("font_loading_disabled", url, {
+ hinted: 0,
+ normal: 0,
+ });
+ Services.prefs.setBoolPref("gfx.downloadable_fonts.enabled", true);
+ await test_preload_url("font_loading_enabled", url, {
+ hinted: 1,
+ normal: 0,
+ });
+ Services.prefs.clearUserPref("gfx.downloadable_fonts.enabled");
+});
+
+// Preload with same origin in secure context with mochitest http proxy
+add_task(async function test_103_preload_https() {
+ await test_hint_preload(
+ "test_103_preload_https",
+ "https://example.org",
+ "/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Preload with same origin in secure context
+add_task(async function test_103_preload() {
+ await test_hint_preload(
+ "test_103_preload",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Cross origin preload in secure context
+add_task(async function test_103_preload_cor() {
+ await test_hint_preload(
+ "test_103_preload_cor",
+ "https://example.com",
+ "https://example.net/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Cross origin preload in insecure context
+add_task(async function test_103_preload_insecure_cor() {
+ await test_hint_preload(
+ "test_103_preload_insecure_cor",
+ "https://example.com",
+ "http://mochi.test:8888/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+});
+
+// Same origin request with relative url
+add_task(async function test_103_relative_preload() {
+ await test_hint_preload(
+ "test_103_relative_preload",
+ "https://example.com",
+ "/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Early hint from insecure context
+add_task(async function test_103_insecure_preload() {
+ await test_hint_preload(
+ "test_103_insecure_preload",
+ "http://mochi.test:8888",
+ "/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+});
+
+// Cross origin preload from secure context to insecure context on same domain
+add_task(async function test_103_preload_mixed_content() {
+ await test_hint_preload(
+ "test_103_preload_mixed_content",
+ "https://example.org",
+ "http://example.org/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+});
+
+// Same preload from localhost to localhost should preload
+add_task(async function test_103_preload_localhost_to_localhost() {
+ await test_hint_preload(
+ "test_103_preload_localhost_to_localhost",
+ "http://127.0.0.1:8888",
+ "http://127.0.0.1:8888/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Relative url, correct file for requested uri
+add_task(async function test_103_preload_only_file() {
+ await test_hint_preload(
+ "test_103_preload_only_file",
+ "https://example.com",
+ "early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_preload_2.js b/netwerk/test/browser/browser_103_preload_2.js
new file mode 100644
index 0000000000..c9f92fdef5
--- /dev/null
+++ b/netwerk/test/browser/browser_103_preload_2.js
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const {
+ test_hint_preload,
+ test_hint_preload_internal,
+ request_count_checking,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// two early hint responses
+add_task(async function test_103_two_preload_responses() {
+ await test_hint_preload_internal(
+ "103_two_preload_responses",
+ "https://example.com",
+ [
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ["", "new_response"], // indicate new early hint response
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 1, normal: 1 }
+ );
+});
+
+// two link header in one early hint response
+add_task(async function test_103_two_link_header() {
+ await test_hint_preload_internal(
+ "103_two_link_header",
+ "https://example.com",
+ [
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ["", ""], // indicate new link header in same reponse
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// two links in one early hint link header
+add_task(async function test_103_two_links() {
+ await test_hint_preload_internal(
+ "103_two_links",
+ "https://example.com",
+ [
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// two early hint responses, only second one has a link header
+add_task(async function test_103_two_links() {
+ await test_hint_preload_internal(
+ "103_two_links",
+ "https://example.com",
+ [
+ ["", "non_link_header"], // indicate non-link related header
+ ["", "new_response"], // indicate new early hint response
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Preload twice same origin in secure context
+add_task(async function test_103_preload_twice() {
+ // pass two times the same uuid so that on the second request, the response is
+ // already in the cache
+ let uuid = Services.uuid.generateUUID();
+ await test_hint_preload(
+ "test_103_preload_twice_1",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 },
+ uuid
+ );
+ await test_hint_preload(
+ "test_103_preload_twice_2",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 0 },
+ uuid
+ );
+});
+
+// Test that preloads in iframes don't get triggered
+add_task(async function test_103_iframe() {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let iframeUri =
+ "https://example.com/browser/netwerk/test/browser/103_preload_iframe.html";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: iframeUri,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+ let expectedRequestCount = { hinted: 0, normal: 1 };
+
+ await request_count_checking(
+ "test_103_iframe",
+ gotRequestCount,
+ expectedRequestCount
+ );
+
+ Services.cache2.clear();
+});
+
+// Test that anchors are parsed
+add_task(async function test_103_anchor() {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let anchorUri =
+ "https://example.com/browser/netwerk/test/browser/103_preload_anchor.html";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: anchorUri,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking("test_103_anchor", gotRequestCount, {
+ hinted: 0,
+ normal: 1,
+ });
+});
diff --git a/netwerk/test/browser/browser_103_private_window.js b/netwerk/test/browser/browser_103_private_window.js
new file mode 100644
index 0000000000..7d23ea4b28
--- /dev/null
+++ b/netwerk/test/browser/browser_103_private_window.js
@@ -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/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+});
+
+// Test steps:
+// 1. Load early_hint_asset_html.sjs with a provided uuid.
+// 2. In early_hint_asset_html.sjs, a 103 response with
+// a Link header<early_hint_asset.sjs> and the provided uuid is returned.
+// 3. We use "http-on-opening-request" topic to observe whether the
+// early hinted request is created.
+// 4. Finally, we check if the request has the correct `isPrivate` value.
+async function test_early_hints_load_url(usePrivateWin) {
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ // Open a browsing window.
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ private: usePrivateWin,
+ });
+
+ let id = Services.uuid.generateUUID().toString();
+ let expectedUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset.sjs?as=fetch&uuid=${id}`;
+ let observed = {};
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "http-on-opening-request") {
+ let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ if (channel.URI.spec === expectedUrl) {
+ observed.actrualUrl = channel.URI.spec;
+ let isPrivate = channel.QueryInterface(
+ Ci.nsIPrivateBrowsingChannel
+ ).isChannelPrivate;
+ observed.isPrivate = isPrivate;
+ Services.obs.removeObserver(observer, "http-on-opening-request");
+ }
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "http-on-opening-request");
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=fetch&hinted=1&uuid=${id}`;
+
+ const browser = win.gBrowser.selectedTab.linkedBrowser;
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, requestUrl);
+ BrowserTestUtils.startLoadingURIString(browser, requestUrl);
+ await loaded;
+
+ Assert.equal(observed.actrualUrl, expectedUrl);
+ Assert.equal(observed.isPrivate, usePrivateWin);
+
+ await BrowserTestUtils.closeWindow(win);
+}
+
+add_task(async function test_103_private_window() {
+ await test_early_hints_load_url(true);
+ await test_early_hints_load_url(false);
+});
diff --git a/netwerk/test/browser/browser_103_redirect.js b/netwerk/test/browser/browser_103_redirect.js
new file mode 100644
index 0000000000..d396813d50
--- /dev/null
+++ b/netwerk/test/browser/browser_103_redirect.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_hint_preload, request_count_checking } =
+ ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+ );
+
+// Early hint to redirect to same origin in secure context
+add_task(async function test_103_redirect_same_origin() {
+ await test_hint_preload(
+ "test_103_redirect_same_origin",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_redirect.sjs?https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 } // successful preload of redirect and resulting image
+ );
+});
+
+// Early hint to redirect to cross origin in secure context
+add_task(async function test_103_redirect_cross_origin() {
+ await test_hint_preload(
+ "test_103_redirect_cross_origin",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_redirect.sjs?https://example.net/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// Early hint to redirect to cross origin in insecure context
+add_task(async function test_103_redirect_insecure_cross_origin() {
+ await test_hint_preload(
+ "test_103_redirect_insecure_cross_origin",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_redirect.sjs?http://mochi.test:8888/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// Cross origin preload from secure context to redirected insecure context on same domain
+add_task(async function test_103_preload_redirect_mixed_content() {
+ await test_hint_preload(
+ "test_103_preload_redirect_mixed_content",
+ "https://example.org",
+ "https://example.org/browser/netwerk/test/browser/early_hint_redirect.sjs?http://example.org/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_redirect_from_server.js b/netwerk/test/browser/browser_103_redirect_from_server.js
new file mode 100644
index 0000000000..0357d11516
--- /dev/null
+++ b/netwerk/test/browser/browser_103_redirect_from_server.js
@@ -0,0 +1,321 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+});
+
+// This function tests Early Hint responses before and in between HTTP redirects.
+//
+// Arguments:
+// - name: String identifying the test case for easier parsing in the log
+// - chain and destination: defines the redirect chain, see example below
+// note: ALL preloaded urls must be image urls
+// - expected: number of normal, cancelled and completed hinted responses.
+//
+// # Example
+// The parameter values of
+// ```
+// chain = [
+// {link:"https://link1", host:"https://host1.com"},
+// {link:"https://link2", host:"https://host2.com"},
+// ]
+// ```
+// and `destination = "https://host3.com/page.html" would result in the
+// following HTTP exchange (simplified):
+//
+// ```
+// > GET https://host1.com/redirect?something1
+//
+// < 103 Early Hints
+// < Link: <https://link1>;rel=preload;as=image
+// <
+// < 307 Temporary Redirect
+// < Location: https://host2.com/redirect?something2
+// <
+//
+// > GET https://host2.com/redirect?something2
+//
+// < 103 Early Hints
+// < Link: <https://link2>;rel=preload;as=image
+// <
+// < 307 Temporary Redirect
+// < Location: https://host3.com/page.html
+// <
+//
+// > GET https://host3.com/page.html
+//
+// < [...] Result depends on the final page
+// ```
+//
+// Legend:
+// * `>` indicates a request going from client to server
+// * `<` indicates a response going from server to client
+// * all lines are terminated with a `\r\n`
+//
+async function test_hint_redirect(
+ name,
+ chain,
+ destination,
+ hint_destination,
+ expected
+) {
+ // pass the full redirect chain as a url parameter. Each redirect is handled
+ // by `early_hint_redirect_html.sjs` which url-decodes the query string and
+ // redirects to the result
+ let links = [];
+ let url = destination;
+ for (let i = chain.length - 1; i >= 0; i--) {
+ let qp = new URLSearchParams();
+ if (chain[i].link != "") {
+ qp.append("link", "<" + chain[i].link + ">;rel=preload;as=image");
+ links.push(chain[i].link);
+ }
+ qp.append("location", url);
+
+ url = `${
+ chain[i].host
+ }/browser/netwerk/test/browser/early_hint_redirect_html.sjs?${qp.toString()}`;
+ }
+ if (hint_destination != "") {
+ links.push(hint_destination);
+ }
+
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ // main request and all other must get their respective OnStopRequest
+ let numRequestRemaining =
+ expected.normal + expected.hinted + expected.cancelled;
+ let observed = {
+ hinted: 0,
+ normal: 0,
+ cancelled: 0,
+ };
+ // store channelIds
+ let observedChannelIds = [];
+ let callback;
+ let promise = new Promise(resolve => {
+ callback = resolve;
+ });
+ if (numRequestRemaining > 0) {
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ aSubject.QueryInterface(Ci.nsIIdentChannel);
+ let id = aSubject.channelId;
+ if (observedChannelIds.includes(id)) {
+ return;
+ }
+ aSubject.QueryInterface(Ci.nsIRequest);
+ dump("Observer aSubject.name " + aSubject.name + "\n");
+ if (aTopic == "http-on-stop-request" && links.includes(aSubject.name)) {
+ if (aSubject.status == Cr.NS_ERROR_ABORT) {
+ observed.cancelled += 1;
+ } else {
+ aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let initiator = "";
+ try {
+ initiator = aSubject.getRequestHeader("X-Moz");
+ } catch {}
+ if (initiator == "early hint") {
+ observed.hinted += 1;
+ } else {
+ observed.normal += 1;
+ }
+ }
+ observedChannelIds.push(id);
+ numRequestRemaining -= 1;
+ dump("Observer numRequestRemaining " + numRequestRemaining + "\n");
+ }
+ if (numRequestRemaining == 0) {
+ Services.obs.removeObserver(observer, "http-on-stop-request");
+ callback();
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "http-on-stop-request");
+ } else {
+ callback();
+ }
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ // wait until all requests are stopped, especially the cancelled ones
+ await promise;
+
+ let got = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ // stringify to pretty print assert output
+ let g = JSON.stringify(observed);
+ let e = JSON.stringify(expected);
+ Assert.equal(
+ expected.normal,
+ observed.normal,
+ `${name} normal observed from client expected ${expected.normal} (${e}) got ${observed.normal} (${g})`
+ );
+ Assert.equal(
+ expected.hinted,
+ observed.hinted,
+ `${name} hinted observed from client expected ${expected.hinted} (${e}) got ${observed.hinted} (${g})`
+ );
+ Assert.equal(
+ expected.cancelled,
+ observed.cancelled,
+ `${name} cancelled observed from client expected ${expected.cancelled} (${e}) got ${observed.cancelled} (${g})`
+ );
+
+ // each cancelled request might be cancelled after the request was already
+ // made. Allow cancelled responses to count towards the hinted to avoid
+ // intermittent test failures.
+ Assert.ok(
+ expected.hinted <= got.hinted &&
+ got.hinted <= expected.hinted + expected.cancelled,
+ `${name}: unexpected amount of hinted request made got ${
+ got.hinted
+ }, expected between ${expected.hinted} and ${
+ expected.hinted + expected.cancelled
+ }`
+ );
+ Assert.ok(
+ got.normal == expected.normal,
+ `${name}: unexpected amount of normal request made expected ${expected.normal}, got ${got.normal}`
+ );
+ Assert.equal(numRequestRemaining, 0, "Requests remaining");
+}
+
+add_task(async function double_redirect_cross_origin() {
+ await test_hint_redirect(
+ "double_redirect_cross_origin_both_hints",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.com/",
+ },
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 2 }
+ );
+ await test_hint_redirect(
+ "double_redirect_second_hint",
+ [
+ {
+ link: "",
+ host: "https://example.com/",
+ },
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 1 }
+ );
+ await test_hint_redirect(
+ "double_redirect_first_hint",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.com/",
+ },
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=0",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 0, normal: 1, cancelled: 2 }
+ );
+});
+
+add_task(async function redirect_cross_origin() {
+ await test_hint_redirect(
+ "redirect_cross_origin_start_second_preload",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 1 }
+ );
+ await test_hint_redirect(
+ "redirect_cross_origin_dont_use_first_preload",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image&a",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=0",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 0, normal: 1, cancelled: 1 }
+ );
+});
+
+add_task(async function redirect_same_origin() {
+ await test_hint_redirect(
+ "hint_before_redirect_same_origin",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.org",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 0 }
+ );
+ await test_hint_redirect(
+ "hint_after_redirect_same_origin",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.org",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=0",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 0 }
+ );
+ await test_hint_redirect(
+ "hint_after_redirect_same_origin",
+ [
+ {
+ link: "",
+ host: "https://example.org",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_referrer_policy.js b/netwerk/test/browser/browser_103_referrer_policy.js
new file mode 100644
index 0000000000..646b7255fd
--- /dev/null
+++ b/netwerk/test/browser/browser_103_referrer_policy.js
@@ -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/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+async function test_referrer_policy(input, expected_results) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?action=reset_referrer_results"
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?as=${
+ input.resource_type
+ }&hinted=${input.hinted ? "1" : "0"}${
+ input.header_referrer_policy
+ ? "&header_referrer_policy=" + input.header_referrer_policy
+ : ""
+ }
+ ${
+ input.link_referrer_policy
+ ? "&link_referrer_policy=" + input.link_referrer_policy
+ : ""
+ }`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ // Retrieve the request referrer from the server
+ let referrer_response = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?action=get_request_referrer_results"
+ ).then(response => response.text());
+
+ Assert.ok(
+ referrer_response === expected_results.referrer,
+ "Request referrer matches expected - " + input.test_name
+ );
+
+ await Assert.deepEqual(
+ gotRequestCount,
+ { hinted: expected_results.hinted, normal: expected_results.normal },
+ `${input.testName} (${input.resource_type}): Unexpected amount of requests made`
+ );
+}
+
+add_task(async function test_103_referrer_policies() {
+ let tests = [
+ {
+ input: {
+ test_name: "image - no policies",
+ resource_type: "image",
+ header_referrer_policy: "",
+ link_referrer_policy: "",
+ hinted: true,
+ },
+ expected: {
+ hinted: 1,
+ normal: 0,
+ referrer:
+ "https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?as=image&hinted=1",
+ },
+ },
+ {
+ input: {
+ test_name: "image - origin on header",
+ resource_type: "image",
+ header_referrer_policy: "origin",
+ link_referrer_policy: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ {
+ input: {
+ test_name: "image - origin on link",
+ resource_type: "image",
+ header_referrer_policy: "",
+ link_referrer_policy: "origin",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ {
+ input: {
+ test_name: "image - origin on both",
+ resource_type: "image",
+ header_referrer_policy: "origin",
+ link_referrer_policy: "origin",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ {
+ input: {
+ test_name: "image - no-referrer on header",
+ resource_type: "image",
+ header_referrer_policy: "no-referrer",
+ link_referrer_policy: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ input: {
+ test_name: "image - no-referrer on link",
+ resource_type: "image",
+ header_referrer_policy: "",
+ link_referrer_policy: "no-referrer",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ input: {
+ test_name: "image - no-referrer on both",
+ resource_type: "image",
+ header_referrer_policy: "no-referrer",
+ link_referrer_policy: "no-referrer",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ // link referrer policy takes precedence
+ input: {
+ test_name: "image - origin on header, no-referrer on link",
+ resource_type: "image",
+ header_referrer_policy: "origin",
+ link_referrer_policy: "no-referrer",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ // link referrer policy takes precedence
+ input: {
+ test_name: "image - no-referrer on header, origin on link",
+ resource_type: "image",
+ header_referrer_policy: "no-referrer",
+ link_referrer_policy: "origin",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ ];
+
+ for (let test of tests) {
+ await test_referrer_policy(test.input, test.expected);
+ }
+});
diff --git a/netwerk/test/browser/browser_103_telemetry.js b/netwerk/test/browser/browser_103_telemetry.js
new file mode 100644
index 0000000000..c6cc7dd070
--- /dev/null
+++ b/netwerk/test/browser/browser_103_telemetry.js
@@ -0,0 +1,107 @@
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+Services.prefs.setCharPref("dom.securecontext.allowlist", "example.com");
+
+var kTest103 =
+ "https://example.com/browser/netwerk/test/browser/103_preload.html";
+var kTestNo103 =
+ "https://example.com/browser/netwerk/test/browser/no_103_preload.html";
+var kTest404 =
+ "https://example.com/browser/netwerk/test/browser/103_preload_and_404.html";
+
+add_task(async function () {
+ let hist_hints = TelemetryTestUtils.getAndClearHistogram(
+ "EH_NUM_OF_HINTS_PER_PAGE"
+ );
+ let hist_final = TelemetryTestUtils.getAndClearHistogram("EH_FINAL_RESPONSE");
+ let hist_time = TelemetryTestUtils.getAndClearHistogram(
+ "EH_TIME_TO_FINAL_RESPONSE"
+ );
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, kTest103, true);
+
+ // This is a 200 response with 103:
+ // EH_NUM_OF_HINTS_PER_PAGE should record 1.
+ // EH_FINAL_RESPONSE should record 1 on position 0 (R2xx).
+ // EH_TIME_TO_FINAL_RESPONSE should have a new value
+ // (we cannot determine what the timing will be therefore we only check that
+ // the histogram sum is > 0).
+ TelemetryTestUtils.assertHistogram(hist_hints, 1, 1);
+ TelemetryTestUtils.assertHistogram(hist_final, 0, 1);
+ const snapshot = hist_time.snapshot();
+ let found = false;
+ for (let [val] of Object.entries(snapshot.values)) {
+ if (val > 0) {
+ found = true;
+ }
+ }
+ Assert.ok(found);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ let hist_hints = TelemetryTestUtils.getAndClearHistogram(
+ "EH_NUM_OF_HINTS_PER_PAGE"
+ );
+ let hist_final = TelemetryTestUtils.getAndClearHistogram("EH_FINAL_RESPONSE");
+ let hist_time = TelemetryTestUtils.getAndClearHistogram(
+ "EH_TIME_TO_FINAL_RESPONSE"
+ );
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, kTestNo103, true);
+
+ // This is a 200 response without 103:
+ // EH_NUM_OF_HINTS_PER_PAGE should record 0.
+ // EH_FINAL_RESPONSE andd EH_TIME_TO_FINAL_RESPONSE should not be recorded.
+ TelemetryTestUtils.assertHistogram(hist_hints, 0, 1);
+ const snapshot_final = hist_final.snapshot();
+ Assert.equal(snapshot_final.sum, 0);
+ const snapshot_time = hist_time.snapshot();
+ let found = false;
+ for (let [val] of Object.entries(snapshot_time.values)) {
+ if (val > 0) {
+ found = true;
+ }
+ }
+ Assert.ok(!found);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ let hist_hints = TelemetryTestUtils.getAndClearHistogram(
+ "EH_NUM_OF_HINTS_PER_PAGE"
+ );
+ let hist_final = TelemetryTestUtils.getAndClearHistogram("EH_FINAL_RESPONSE");
+ let hist_time = TelemetryTestUtils.getAndClearHistogram(
+ "EH_TIME_TO_FINAL_RESPONSE"
+ );
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, kTest404, true);
+
+ // This is a 404 response with 103:
+ // EH_NUM_OF_HINTS_PER_PAGE and EH_TIME_TO_FINAL_RESPONSE should not be recorded.
+ // EH_FINAL_RESPONSE should record 1 on index 2 (R4xx).
+ const snapshot_hints = hist_hints.snapshot();
+ Assert.equal(snapshot_hints.sum, 0);
+ TelemetryTestUtils.assertHistogram(hist_final, 2, 1);
+ const snapshot_time = hist_time.snapshot();
+ let found = false;
+ for (let [val] of Object.entries(snapshot_time.values)) {
+ if (val > 0) {
+ found = true;
+ }
+ }
+ Assert.ok(!found);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function cleanup() {
+ Services.prefs.clearUserPref("dom.securecontext.allowlist");
+});
diff --git a/netwerk/test/browser/browser_103_user_load.js b/netwerk/test/browser/browser_103_user_load.js
new file mode 100644
index 0000000000..94a30e1c8c
--- /dev/null
+++ b/netwerk/test/browser/browser_103_user_load.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// simulate user initiated loads by entering the URL in the URL-bar code based on
+// https://searchfox.org/mozilla-central/rev/5644fae86d5122519a0e34ee03117c88c6ed9b47/browser/components/urlbar/tests/browser/browser_enter.js
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+const {
+ request_count_checking,
+ test_hint_preload_internal,
+ test_hint_preload,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+const START_VALUE =
+ "https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=style&hinted=1";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+});
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+// bug 1780822
+add_task(async function user_initiated_load() {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ info("Simple user initiated load");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // under normal test conditions using the systemPrincipal as loadingPrincipal
+ // doesn't elicit a crash, changing the behavior for this test:
+ // https://searchfox.org/mozilla-central/rev/5644fae86d5122519a0e34ee03117c88c6ed9b47/dom/security/nsContentSecurityManager.cpp#1149-1150
+ Services.prefs.setBoolPref(
+ "security.disallow_non_local_systemprincipal_in_tests",
+ true
+ );
+
+ gURLBar.value = START_VALUE;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ // reset the config option
+ Services.prefs.clearUserPref(
+ "security.disallow_non_local_systemprincipal_in_tests"
+ );
+
+ // Check url bar and selected tab.
+ is(
+ gURLBar.value,
+ UrlbarTestUtils.trimURL(START_VALUE),
+ "Urlbar should preserve the value on return keypress"
+ );
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+ let expectedRequestCount = { hinted: 1, normal: 0 };
+
+ await request_count_checking(
+ "test_preload_user_initiated",
+ gotRequestCount,
+ expectedRequestCount
+ );
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/netwerk/test/browser/browser_NetUtil.js b/netwerk/test/browser/browser_NetUtil.js
new file mode 100644
index 0000000000..09875a5280
--- /dev/null
+++ b/netwerk/test/browser/browser_NetUtil.js
@@ -0,0 +1,111 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+"use strict";
+
+function test() {
+ waitForExplicitFinish();
+
+ // We overload this test to include verifying that httpd.js is
+ // importable as a testing-only JS module.
+ ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs");
+
+ nextTest();
+}
+
+function nextTest() {
+ if (tests.length) {
+ executeSoon(tests.shift());
+ } else {
+ executeSoon(finish);
+ }
+}
+
+var tests = [test_asyncFetchBadCert];
+
+function test_asyncFetchBadCert() {
+ // Try a load from an untrusted cert, with errors supressed
+ NetUtil.asyncFetch(
+ {
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+
+ // Now try again with a channel whose notificationCallbacks doesn't suprress errors
+ let channel = NetUtil.newChannel({
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true,
+ });
+ channel.notificationCallbacks = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIProgressEventSink",
+ "nsIInterfaceRequestor",
+ ]),
+ getInterface(aIID) {
+ return this.QueryInterface(aIID);
+ },
+ onProgress() {},
+ onStatus() {},
+ };
+ NetUtil.asyncFetch(
+ channel,
+ function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(
+ aRequest instanceof Ci.nsIHttpChannel,
+ "request is an nsIHttpChannel"
+ );
+
+ // Now try a valid request
+ NetUtil.asyncFetch(
+ {
+ uri: "https://example.com",
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aStatusCode, aRequest) {
+ info("aStatusCode for valid request: " + aStatusCode);
+ ok(Components.isSuccessCode(aStatusCode), "request succeeded");
+ ok(
+ aRequest instanceof Ci.nsIHttpChannel,
+ "request is an nsIHttpChannel"
+ );
+ ok(aRequest.requestSucceeded, "HTTP request succeeded");
+
+ nextTest();
+ }
+ );
+ }
+ );
+ }
+ );
+}
+
+function WindowListener(aURL, aCallback) {
+ this.callback = aCallback;
+ this.url = aURL;
+}
+WindowListener.prototype = {
+ onOpenWindow(aXULWindow) {
+ var domwindow = aXULWindow.docShell.domWindow;
+ var self = this;
+ domwindow.addEventListener(
+ "load",
+ function () {
+ if (domwindow.document.location.href != self.url) {
+ return;
+ }
+
+ // Allow other window load listeners to execute before passing to callback
+ executeSoon(function () {
+ self.callback(domwindow);
+ });
+ },
+ { once: true }
+ );
+ },
+ onCloseWindow(aXULWindow) {},
+};
diff --git a/netwerk/test/browser/browser_about_cache.js b/netwerk/test/browser/browser_about_cache.js
new file mode 100644
index 0000000000..9e9b2467d5
--- /dev/null
+++ b/netwerk/test/browser/browser_about_cache.js
@@ -0,0 +1,136 @@
+"use strict";
+
+/**
+ * Open a dummy page, then open about:cache and verify the opened page shows up in the cache.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.network_state", false]],
+ });
+
+ const kRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+ );
+ const kTestPage = kRoot + "dummy.html";
+ // Open the dummy page to get it cached.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ kTestPage,
+ true
+ );
+ BrowserTestUtils.removeTab(tab);
+
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:cache",
+ true
+ );
+ let expectedPageCheck = function (uri) {
+ info("Saw load for " + uri);
+ // Can't easily use searchParms and new URL() because it's an about: URI...
+ return uri.startsWith("about:cache?") && uri.includes("storage=disk");
+ };
+ let diskPageLoaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedPageCheck
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ ok(
+ !content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache should not have system principal"
+ );
+ let principal = content.document.nodePrincipal;
+ let channel = content.docShell.currentDocumentChannel;
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ is(
+ principal.spec,
+ content.document.location.href,
+ "Principal matches location"
+ );
+ let links = [...content.document.querySelectorAll("a[href*=disk]")];
+ is(links.length, 1, "Should have 1 link to the disk entries");
+ links[0].click();
+ });
+ await diskPageLoaded;
+ info("about:cache disk subpage loaded");
+
+ expectedPageCheck = function (uri) {
+ info("Saw load for " + uri);
+ return uri.startsWith("about:cache-entry") && uri.includes("dummy.html");
+ };
+ let triggeringURISpec = tab.linkedBrowser.currentURI.spec;
+ let entryLoaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedPageCheck
+ );
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [kTestPage],
+ function (kTestPage) {
+ ok(
+ !content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache with query params should still not have system principal"
+ );
+ let principal = content.document.nodePrincipal;
+ is(
+ principal.spec,
+ content.document.location.href,
+ "Principal matches location"
+ );
+ let channel = content.docShell.currentDocumentChannel;
+ principal = channel.loadInfo.triggeringPrincipal;
+ is(
+ principal.spec,
+ "about:cache",
+ "Triggering principal matches previous location"
+ );
+ ok(
+ !channel.loadInfo.loadingPrincipal,
+ "Loading principal should be null."
+ );
+ let links = [
+ ...content.document.querySelectorAll("a[href*='" + kTestPage + "']"),
+ ];
+ is(links.length, 1, "Should have 1 link to the entry for " + kTestPage);
+ links[0].click();
+ }
+ );
+ await entryLoaded;
+ info("about:cache entry loaded");
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [triggeringURISpec],
+ function (triggeringURISpec) {
+ ok(
+ !content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache-entry should also not have system principal"
+ );
+ let principal = content.document.nodePrincipal;
+ is(
+ principal.spec,
+ content.document.location.href,
+ "Principal matches location"
+ );
+ let channel = content.docShell.currentDocumentChannel;
+ principal = channel.loadInfo.triggeringPrincipal;
+ is(
+ principal.spec,
+ triggeringURISpec,
+ "Triggering principal matches previous location"
+ );
+ ok(
+ !channel.loadInfo.loadingPrincipal,
+ "Loading principal should be null."
+ );
+ ok(
+ content.document.querySelectorAll("th").length,
+ "Should have several table headers with data."
+ );
+ }
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js b/netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js
new file mode 100644
index 0000000000..76af1451f5
--- /dev/null
+++ b/netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js
@@ -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/. */
+
+"use strict";
+
+add_task(async function test_startupCleanup() {
+ Services.prefs.setBoolPref(
+ "network.cache.shutdown_purge_in_background_task",
+ true
+ );
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
+ Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
+ let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dir.append("cache2.2021-11-25-08-47-04.purge.bg_rm");
+ Assert.equal(dir.exists(), false, `Folder ${dir.path} should not exist`);
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+ Assert.equal(
+ dir.exists(),
+ true,
+ `Folder ${dir.path} should have been created`
+ );
+
+ Services.obs.notifyObservers(null, "browser-delayed-startup-finished");
+
+ await TestUtils.waitForCondition(() => {
+ return !dir.exists();
+ });
+
+ Assert.equal(
+ dir.exists(),
+ false,
+ `Folder ${dir.path} should have been purged by background task`
+ );
+ Services.prefs.clearUserPref(
+ "network.cache.shutdown_purge_in_background_task"
+ );
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.cache");
+ Services.prefs.clearUserPref("privacy.sanitize.sanitizeOnShutdown");
+});
diff --git a/netwerk/test/browser/browser_bug1535877.js b/netwerk/test/browser/browser_bug1535877.js
new file mode 100644
index 0000000000..0bd0a98d11
--- /dev/null
+++ b/netwerk/test/browser/browser_bug1535877.js
@@ -0,0 +1,15 @@
+"use strict";
+
+add_task(_ => {
+ try {
+ Cc["@mozilla.org/network/effective-tld-service;1"].createInstance(
+ Ci.nsISupports
+ );
+ } catch (e) {
+ is(
+ e.result,
+ Cr.NS_ERROR_XPC_CI_RETURNED_FAILURE,
+ "Component creation as an instance fails with expected code"
+ );
+ }
+});
diff --git a/netwerk/test/browser/browser_bug1629307.js b/netwerk/test/browser/browser_bug1629307.js
new file mode 100644
index 0000000000..03ea2476e2
--- /dev/null
+++ b/netwerk/test/browser/browser_bug1629307.js
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Load a web page containing an iframe that requires authentication but includes the X-Frame-Options: SAMEORIGIN header.
+// Make sure that we don't needlessly show an authentication prompt for it.
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+add_task(async function () {
+ SpecialPowers.pushPrefEnv({
+ set: [["network.auth.supress_auth_prompt_for_XFO_failures", true]],
+ });
+
+ let URL =
+ "https://example.com/browser/netwerk/test/browser/test_1629307.html";
+
+ let hasPrompt = false;
+
+ PromptTestUtils.handleNextPrompt(
+ window,
+ {
+ modalType: Services.prefs.getIntPref("prompts.modalType.httpAuth"),
+ promptType: "promptUserAndPass",
+ },
+ { buttonNumClick: 1 }
+ )
+ .then(function () {
+ hasPrompt = true;
+ })
+ .catch(function () {});
+
+ BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, URL);
+
+ // wait until the page and its iframe page is loaded
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true, URL);
+
+ Assert.equal(
+ hasPrompt,
+ false,
+ "no prompt when loading page via iframe with x-auth options"
+ );
+});
+
+add_task(async function () {
+ SpecialPowers.pushPrefEnv({
+ set: [["network.auth.supress_auth_prompt_for_XFO_failures", false]],
+ });
+
+ let URL =
+ "https://example.com/browser/netwerk/test/browser/test_1629307.html";
+
+ let hasPrompt = false;
+
+ PromptTestUtils.handleNextPrompt(
+ window,
+ {
+ modalType: Services.prefs.getIntPref("prompts.modalType.httpAuth"),
+ promptType: "promptUserAndPass",
+ },
+ { buttonNumClick: 1 }
+ )
+ .then(function () {
+ hasPrompt = true;
+ })
+ .catch(function () {});
+
+ BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, URL);
+
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true, URL);
+
+ Assert.equal(
+ hasPrompt,
+ true,
+ "prompt when loading page via iframe with x-auth options with pref network.auth.supress_auth_prompt_for_XFO_failures disabled"
+ );
+});
diff --git a/netwerk/test/browser/browser_child_resource.js b/netwerk/test/browser/browser_child_resource.js
new file mode 100644
index 0000000000..341a8fc8e3
--- /dev/null
+++ b/netwerk/test/browser/browser_child_resource.js
@@ -0,0 +1,246 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+"use strict";
+
+// This must be loaded in the remote process for this test to be useful
+const TEST_URL = "https://example.com/browser/netwerk/test/browser/dummy.html";
+
+const expectedRemote = gMultiProcessBrowser ? "true" : "";
+
+const resProtocol = Cc[
+ "@mozilla.org/network/protocol;1?name=resource"
+].getService(Ci.nsIResProtocolHandler);
+
+function waitForEvent(obj, name, capturing, chromeEvent) {
+ info("Waiting for " + name);
+ return new Promise(resolve => {
+ function listener(event) {
+ info("Saw " + name);
+ obj.removeEventListener(name, listener, capturing, chromeEvent);
+ resolve(event);
+ }
+
+ obj.addEventListener(name, listener, capturing, chromeEvent);
+ });
+}
+
+function resolveURI(uri) {
+ uri = Services.io.newURI(uri);
+ try {
+ return resProtocol.resolveURI(uri);
+ } catch (e) {
+ return null;
+ }
+}
+
+function remoteResolveURI(uri) {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [uri], uriToResolve => {
+ let resProtocol = Cc[
+ "@mozilla.org/network/protocol;1?name=resource"
+ ].getService(Ci.nsIResProtocolHandler);
+
+ uriToResolve = Services.io.newURI(uriToResolve);
+ try {
+ return resProtocol.resolveURI(uriToResolve);
+ } catch (e) {}
+ return null;
+ });
+}
+
+// Restarts the child process by crashing it then reloading the tab
+var restart = async function () {
+ let browser = gBrowser.selectedBrowser;
+ // If the tab isn't remote this would crash the main process so skip it
+ if (browser.getAttribute("remote") != "true") {
+ return browser;
+ }
+
+ await BrowserTestUtils.crashFrame(browser);
+
+ browser.reload();
+
+ await BrowserTestUtils.browserLoaded(browser);
+ is(
+ browser.getAttribute("remote"),
+ expectedRemote,
+ "Browser should be in the right process"
+ );
+ return browser;
+};
+
+// Sanity check that this test is going to be useful
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // This must be loaded in the remote process for this test to be useful
+ is(
+ gBrowser.selectedBrowser.getAttribute("remote"),
+ expectedRemote,
+ "Browser should be in the right process"
+ );
+
+ let local = resolveURI("resource://gre/modules/AppConstants.jsm");
+ let remote = await remoteResolveURI(
+ "resource://gre/modules/AppConstants.jsm"
+ );
+ is(local, remote, "AppConstants.jsm should resolve in both processes");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, update it then remove it
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Set");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/content")
+ );
+ let local = resolveURI("resource://testing/test.js");
+ let remote = await remoteResolveURI("resource://testing/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ info("Change");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/skin")
+ );
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, restart the child process then check it is still there
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Set");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/content")
+ );
+ let local = resolveURI("resource://testing/test.js");
+ let remote = await remoteResolveURI("resource://testing/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ await restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ info("Change");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/skin")
+ );
+
+ await restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+
+ await restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Adding a mapping to a resource URI should work
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Set");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/content")
+ );
+ resProtocol.setSubstitution(
+ "testing2",
+ Services.io.newURI("resource://testing")
+ );
+ let local = resolveURI("resource://testing2/test.js");
+ let remote = await remoteResolveURI("resource://testing2/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = await remoteResolveURI("resource://testing2/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ resProtocol.setSubstitution("testing2", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = await remoteResolveURI("resource://testing2/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_cookie_filtering_basic.js b/netwerk/test/browser/browser_cookie_filtering_basic.js
new file mode 100644
index 0000000000..e51bed5bc5
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_basic.js
@@ -0,0 +1,184 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ fetchHelper,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+// run suite with content listener
+// 1. initializes the content process and observer
+// 2. runs the test gamut
+// 3. cleans up the content process
+async function runSuiteWithContentListener(name, triggerSuiteFunc, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await triggerSuiteFunc();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: Different domains (org)
+// * example.org cookies go to example.org process
+// * exmaple.com cookies do not go to example.org process
+async function test_basic_suite_org() {
+ // example.org - start content process when loading page
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_ORG),
+ },
+ await runSuiteWithContentListener(
+ "basic suite org",
+ triggerBasicSuite,
+ basicSuiteMatchingDomain(HTTPS_EXAMPLE_ORG)
+ )
+ );
+}
+
+// TEST: Different domains (com)
+// * example.com cookies go to example.com process
+// * example.org cookies do not go to example.com process
+// * insecure example.com cookies go to secure com process
+async function test_basic_suite_com() {
+ // example.com - start content process when loading page
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "basic suite com",
+ triggerBasicSuite,
+ basicSuiteMatchingDomain(HTTPS_EXAMPLE_COM).concat(
+ basicSuiteMatchingDomain(HTTP_EXAMPLE_COM)
+ )
+ )
+ );
+}
+
+// TEST: Duplicate domain (org)
+// * example.org cookies go to multiple example.org processes
+async function test_basic_suite_org_duplicate() {
+ let expected = basicSuiteMatchingDomain(HTTPS_EXAMPLE_ORG);
+ let t1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_ORG)
+ );
+ let testStruct1 = {
+ name: "example.org primary",
+ browser: gBrowser.getBrowserForTab(t1),
+ tab: t1,
+ expected,
+ };
+
+ // example.org dup
+ let t3 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_ORG)
+ );
+ let testStruct3 = {
+ name: "example.org dup",
+ browser: gBrowser.getBrowserForTab(t3),
+ tab: t3,
+ expected,
+ };
+
+ let parentpid = Services.appinfo.processID;
+ let pid1 = testStruct1.browser.frameLoader.remoteTab.osPid;
+ let pid3 = testStruct3.browser.frameLoader.remoteTab.osPid;
+ ok(
+ parentpid != pid1,
+ "Parent pid should differ from content process for 1st example.org"
+ );
+ ok(
+ parentpid != pid3,
+ "Parent pid should differ from content process for 2nd example.org"
+ );
+ ok(pid1 != pid3, "Content pids should differ from each other");
+
+ await SpecialPowers.spawn(
+ testStruct1.browser,
+ [testStruct1.expected, testStruct1.name],
+ checkExpectedCookies
+ );
+
+ await SpecialPowers.spawn(
+ testStruct3.browser,
+ [testStruct3.expected, testStruct3.name],
+ checkExpectedCookies
+ );
+
+ await triggerBasicSuite();
+
+ // wait for all tests and cleanup
+ await SpecialPowers.spawn(testStruct1.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct3.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct1.browser, [], cleanupObservers);
+ await SpecialPowers.spawn(testStruct3.browser, [], cleanupObservers);
+ BrowserTestUtils.removeTab(testStruct1.tab);
+ BrowserTestUtils.removeTab(testStruct3.tab);
+}
+
+function basicSuite() {
+ var suite = [];
+ suite.push(["test-cookie=aaa", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=bbb", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=dad", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=rad", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=orgwontsee", HTTPS_EXAMPLE_COM]);
+ suite.push(["test-cookie=sentinelorg", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=sentinelcom", HTTPS_EXAMPLE_COM]);
+ return suite;
+}
+
+function basicSuiteMatchingDomain(domain) {
+ var suite = basicSuite();
+ var result = [];
+ for (var [cookie, dom] of suite) {
+ if (dom == domain) {
+ result.push(cookie);
+ }
+ }
+ return result;
+}
+
+// triggers set-cookie, which will trigger cookie-changed messages
+// messages will be filtered against the cookie list created from above
+// only unfiltered messages should make it to the content process
+async function triggerBasicSuite() {
+ let triggerCookies = basicSuite();
+ for (var [cookie, domain] of triggerCookies) {
+ let secure = false;
+ if (domain.includes("https")) {
+ secure = true;
+ }
+
+ //trigger
+ var url = browserTestPath(domain) + "cookie_filtering_resource.sjs";
+ await fetchHelper(url, cookie, secure);
+ }
+}
+
+add_task(preclean_test);
+add_task(test_basic_suite_org); // 5
+add_task(test_basic_suite_com); // 2
+add_task(test_basic_suite_org_duplicate); // 13
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_cross_origin.js b/netwerk/test/browser/browser_cookie_filtering_cross_origin.js
new file mode 100644
index 0000000000..5a722846ef
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_cross_origin.js
@@ -0,0 +1,146 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+async function runSuiteWithContentListener(name, trigger_suite_func, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await trigger_suite_func();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: Cross Origin Resource (com)
+// * process receives only COR cookies pertaining to same page
+async function test_cross_origin_resource_com() {
+ let comExpected = [];
+ comExpected.push("test-cookie=comhtml"); // 1
+ comExpected.push("test-cookie=png"); // 2
+ comExpected.push("test-cookie=orghtml"); // 3
+ // nothing for 4, 5, 6, 7 -> goes to .org process
+ comExpected.push("test-cookie=png"); // 8
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "COR example.com",
+ triggerCrossOriginSuite,
+ comExpected
+ )
+ );
+ Services.cookies.removeAll();
+}
+
+// TEST: Cross Origin Resource (org)
+// * received COR cookies only pertaining to the process's page
+async function test_cross_origin_resource_org() {
+ let orgExpected = [];
+ // nothing for 1, 2 and 3 -> goes to .com
+ orgExpected.push("test-cookie=png"); // 4
+ orgExpected.push("test-cookie=orghtml"); // 5
+ orgExpected.push("test-cookie=png"); // 6
+ orgExpected.push("test-cookie=comhtml"); // 7
+ // 8 nothing for 8 -> goes to .com process
+ orgExpected.push("test-cookie=png"); // 9. Sentinel to verify no more came in
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_ORG),
+ },
+ await runSuiteWithContentListener(
+ "COR example.org",
+ triggerCrossOriginSuite,
+ orgExpected
+ )
+ );
+}
+
+// seems to block better than fetch for secondary resource
+// using for more predicatable recving
+async function requestBrowserPageWithFilename(
+ testName,
+ requestFrom,
+ filename,
+ param = ""
+) {
+ let url = requestFrom + "/browser/netwerk/test/browser/" + filename;
+
+ // add param if necessary
+ if (param != "") {
+ url += "?" + param;
+ }
+
+ info("requesting " + url + " (" + testName + ")");
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async () => {}
+ );
+}
+
+async function triggerCrossOriginSuite() {
+ // SameSite - 1 com page, 2 com png
+ await requestBrowserPageWithFilename(
+ "SameSite resource (com)",
+ HTTPS_EXAMPLE_COM,
+ "cookie_filtering_secure_resource_com.html"
+ );
+
+ // COR - 3 com page, 4 org png
+ await requestBrowserPageWithFilename(
+ "COR (com-org)",
+ HTTPS_EXAMPLE_COM,
+ "cookie_filtering_secure_resource_org.html"
+ );
+
+ // SameSite - 5 org page, 6 org png
+ await requestBrowserPageWithFilename(
+ "SameSite resource (org)",
+ HTTPS_EXAMPLE_ORG,
+ "cookie_filtering_secure_resource_org.html"
+ );
+
+ // COR - 7 org page, 8 com png
+ await requestBrowserPageWithFilename(
+ "SameSite resource (org-com)",
+ HTTPS_EXAMPLE_ORG,
+ "cookie_filtering_secure_resource_com.html"
+ );
+
+ // Sentinel to verify no more cookies come in after last true test
+ await requestBrowserPageWithFilename(
+ "COR sentinel",
+ HTTPS_EXAMPLE_ORG,
+ "cookie_filtering_square.png"
+ );
+}
+
+add_task(preclean_test);
+add_task(test_cross_origin_resource_com); // 4
+add_task(test_cross_origin_resource_org); // 5
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_insecure.js b/netwerk/test/browser/browser_cookie_filtering_insecure.js
new file mode 100644
index 0000000000..679bfc5a56
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_insecure.js
@@ -0,0 +1,106 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ fetchHelper,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+async function runSuiteWithContentListener(name, trigger_suite_func, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await trigger_suite_func();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: In/Secure (insecure com)
+// * secure example.com cookie do not go to insecure example.com process
+// * insecure cookies go to insecure process
+// * secure request with insecure cookie will go to insecure process
+async function test_insecure_suite_insecure_com() {
+ var expected = [];
+ expected.push("test-cookie=png1");
+ expected.push("test-cookie=png2");
+ // insecure com will not recieve the secure com request with secure cookie
+ expected.push(""); // insecure com will lose visibility of secure com cookie
+ expected.push("test-cookie=png3");
+ info(expected);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTP_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "insecure suite insecure com",
+ triggerInsecureSuite,
+ expected
+ )
+ );
+}
+
+// TEST: In/Secure (secure com)
+// * secure page will recieve all secure/insecure cookies
+async function test_insecure_suite_secure_com() {
+ var expected = [];
+ expected.push("test-cookie=png1");
+ expected.push("test-cookie=png2");
+ expected.push("test-cookie=secure-png");
+ expected.push("test-cookie=png3");
+ info(expected);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "insecure suite secure com",
+ triggerInsecureSuite,
+ expected
+ )
+ );
+}
+
+async function triggerInsecureSuite() {
+ const cookieSjsFilename = "cookie_filtering_resource.sjs";
+
+ // insecure page, insecure cookie
+ var url = browserTestPath(HTTP_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=png1", false);
+
+ // secure page req, insecure cookie
+ url = browserTestPath(HTTPS_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=png2", false);
+
+ // secure page, secure cookie
+ url = browserTestPath(HTTPS_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=secure-png", true);
+
+ // not testing insecure server returning secure cookie --
+
+ // sentinel
+ url = browserTestPath(HTTPS_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=png3", false);
+}
+
+add_task(preclean_test);
+add_task(test_insecure_suite_insecure_com); // 3
+add_task(test_insecure_suite_secure_com); // 4
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_oa.js b/netwerk/test/browser/browser_cookie_filtering_oa.js
new file mode 100644
index 0000000000..f69ad09e81
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_oa.js
@@ -0,0 +1,190 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ triggerSetCookieFromHttp,
+ triggerSetCookieFromHttpPrivate,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+// TEST: OriginAttributes
+// * example.com OA-changed cookies don't go to example.com & vice-versa
+async function test_origin_attributes() {
+ var suite = oaSuite();
+
+ // example.com - start content process when loading page
+ let t2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_COM)
+ );
+ let testStruct2 = {
+ name: "OA example.com",
+ browser: gBrowser.getBrowserForTab(t2),
+ tab: t2,
+ expected: [suite[0], suite[4]],
+ };
+
+ // open a tab with altered OA: userContextId
+ let t4 = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: (function () {
+ return function () {
+ // info("calling addTab from lambda");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ HTTPS_EXAMPLE_COM,
+ { userContextId: 1 }
+ );
+ };
+ })(),
+ });
+ let testStruct4 = {
+ name: "OA example.com (contextId)",
+ browser: gBrowser.getBrowserForTab(t4),
+ tab: t4,
+ expected: [suite[2], suite[5]],
+ };
+
+ // example.com
+ await SpecialPowers.spawn(
+ testStruct2.browser,
+ [testStruct2.expected, testStruct2.name],
+ checkExpectedCookies
+ );
+ // example.com with different OA: userContextId
+ await SpecialPowers.spawn(
+ testStruct4.browser,
+ [testStruct4.expected, testStruct4.name],
+ checkExpectedCookies
+ );
+
+ await triggerOriginAttributesEmulatedSuite();
+
+ await SpecialPowers.spawn(testStruct2.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct4.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct2.browser, [], cleanupObservers);
+ await SpecialPowers.spawn(testStruct4.browser, [], cleanupObservers);
+ BrowserTestUtils.removeTab(testStruct2.tab);
+ BrowserTestUtils.removeTab(testStruct4.tab);
+}
+
+// TEST: Private
+// * example.com private cookies don't go to example.com process & vice-v
+async function test_private() {
+ var suite = oaSuite();
+
+ // example.com
+ let t2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_COM)
+ );
+ let testStruct2 = {
+ name: "non-priv example.com",
+ browser: gBrowser.getBrowserForTab(t2),
+ tab: t2,
+ expected: [suite[0], suite[4]],
+ };
+
+ // private window example.com
+ let privateBrowserWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let privateTab = (privateBrowserWindow.gBrowser.selectedTab =
+ BrowserTestUtils.addTab(
+ privateBrowserWindow.gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_COM)
+ ));
+ let testStruct5 = {
+ name: "private example.com",
+ browser: privateBrowserWindow.gBrowser.getBrowserForTab(privateTab),
+ tab: privateTab,
+ expected: [suite[3], suite[6]],
+ };
+ await BrowserTestUtils.browserLoaded(testStruct5.tab.linkedBrowser);
+
+ let parentpid = Services.appinfo.processID;
+ let privatePid = testStruct5.browser.frameLoader.remoteTab.osPid;
+ let pid = testStruct2.browser.frameLoader.remoteTab.osPid;
+ ok(parentpid != privatePid, "Parent and private processes are unique");
+ ok(parentpid != pid, "Parent and non-private processes are unique");
+ ok(privatePid != pid, "Private and non-private processes are unique");
+
+ // example.com
+ await SpecialPowers.spawn(
+ testStruct2.browser,
+ [testStruct2.expected, testStruct2.name],
+ checkExpectedCookies
+ );
+
+ // example.com private
+ await SpecialPowers.spawn(
+ testStruct5.browser,
+ [testStruct5.expected, testStruct5.name],
+ checkExpectedCookies
+ );
+
+ await triggerOriginAttributesEmulatedSuite();
+
+ // wait for all tests and cleanup
+ await SpecialPowers.spawn(testStruct2.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct5.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct2.browser, [], cleanupObservers);
+ await SpecialPowers.spawn(testStruct5.browser, [], cleanupObservers);
+ BrowserTestUtils.removeTab(testStruct2.tab);
+ BrowserTestUtils.removeTab(testStruct5.tab);
+ await BrowserTestUtils.closeWindow(privateBrowserWindow);
+}
+
+function oaSuite() {
+ var suite = [];
+ suite.push("test-cookie=orgwontsee"); // 0
+ suite.push("test-cookie=firstparty"); // 1
+ suite.push("test-cookie=usercontext"); // 2
+ suite.push("test-cookie=privateonly"); // 3
+ suite.push("test-cookie=sentinelcom"); // 4
+ suite.push("test-cookie=sentineloa"); // 5
+ suite.push("test-cookie=sentinelprivate"); // 6
+ return suite;
+}
+
+// emulated because we are not making actual page requests
+// we are just directly invoking the api
+async function triggerOriginAttributesEmulatedSuite() {
+ var suite = oaSuite();
+
+ let uriCom = NetUtil.newURI(HTTPS_EXAMPLE_COM);
+ triggerSetCookieFromHttp(uriCom, suite[0]);
+
+ // FPD (OA) changed
+ triggerSetCookieFromHttp(uriCom, suite[1], "foo.com");
+
+ // context id (OA) changed
+ triggerSetCookieFromHttp(uriCom, suite[2], "", 1);
+
+ // private
+ triggerSetCookieFromHttpPrivate(uriCom, suite[3]);
+
+ // sentinels
+ triggerSetCookieFromHttp(uriCom, suite[4]);
+ triggerSetCookieFromHttp(uriCom, suite[5], "", 1);
+ triggerSetCookieFromHttpPrivate(uriCom, suite[6]);
+}
+
+add_task(preclean_test);
+add_task(test_origin_attributes); // 4
+add_task(test_private); // 7
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_subdomain.js b/netwerk/test/browser/browser_cookie_filtering_subdomain.js
new file mode 100644
index 0000000000..78fcdb07dd
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_subdomain.js
@@ -0,0 +1,175 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ fetchHelper,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+const HTTPS_SUBDOMAIN_1_EXAMPLE_COM = "https://test1.example.com";
+const HTTP_SUBDOMAIN_1_EXAMPLE_COM = "http://test1.example.com";
+const HTTPS_SUBDOMAIN_2_EXAMPLE_COM = "https://test2.example.com";
+const HTTP_SUBDOMAIN_2_EXAMPLE_COM = "http://test2.example.com";
+
+// run suite with content listener
+// 1. initializes the content process and observer
+// 2. runs the test gamut
+// 3. cleans up the content process
+async function runSuiteWithContentListener(name, triggerSuiteFunc, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await triggerSuiteFunc();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: domain receives subdomain cookies
+async function test_domain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "test_domain",
+ triggerSuite,
+ cookiesFromSuite()
+ )
+ );
+}
+
+// TEST: insecure domain receives base and sub-domain insecure cookies
+async function test_insecure_domain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTP_EXAMPLE_COM),
+ },
+
+ await runSuiteWithContentListener("test_insecure_domain", triggerSuite, [
+ "",
+ "", // HTTPS fetch cookies show as empty strings
+ "test-cookie-insecure=insecure_domain",
+ "test-cookie-insecure=insecure_subdomain",
+ "",
+ ])
+ );
+}
+
+// TEST: subdomain receives base domain and other sub-domain cookies
+async function test_subdomain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_SUBDOMAIN_2_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "test_subdomain",
+ triggerSuite,
+ cookiesFromSuite()
+ )
+ );
+}
+
+// TEST: insecure subdomain receives base and sub-domain insecure cookies
+async function test_insecure_subdomain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTP_SUBDOMAIN_2_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "test_insecure_subdomain",
+ triggerSuite,
+
+ [
+ "",
+ "", // HTTPS fetch cookies show as empty strings
+ "test-cookie-insecure=insecure_domain",
+ "test-cookie-insecure=insecure_subdomain",
+ "",
+ ]
+ )
+ );
+}
+
+function suite() {
+ var suite = [];
+ suite.push(["test-cookie=domain", HTTPS_EXAMPLE_COM]);
+ suite.push(["test-cookie=subdomain", HTTPS_SUBDOMAIN_1_EXAMPLE_COM]);
+ suite.push(["test-cookie-insecure=insecure_domain", HTTP_EXAMPLE_COM]);
+ suite.push([
+ "test-cookie-insecure=insecure_subdomain",
+ HTTP_SUBDOMAIN_1_EXAMPLE_COM,
+ ]);
+ suite.push(["test-cookie=sentinel", HTTPS_EXAMPLE_COM]);
+ return suite;
+}
+
+function cookiesFromSuite() {
+ var cookies = [];
+ for (var [cookie] of suite()) {
+ cookies.push(cookie);
+ }
+ return cookies;
+}
+
+function cookiesMatchingDomain(domain) {
+ var s = suite();
+ var result = [];
+ for (var [cookie, dom] of s) {
+ if (dom == domain) {
+ result.push(cookie);
+ }
+ }
+ return result;
+}
+
+function justSitename(maybeSchemefulMaybeSubdomainSite) {
+ let mssArray = maybeSchemefulMaybeSubdomainSite.split("://");
+ let maybesubdomain = mssArray[mssArray.length - 1];
+ let msdArray = maybesubdomain.split(".");
+ return msdArray.slice(msdArray.length - 2, msdArray.length).join(".");
+}
+
+// triggers set-cookie, which will trigger cookie-changed messages
+// messages will be filtered against the cookie list created from above
+// only unfiltered messages should make it to the content process
+async function triggerSuite() {
+ let triggerCookies = suite();
+ for (var [cookie, schemefulDomain] of triggerCookies) {
+ let secure = false;
+ if (schemefulDomain.includes("https")) {
+ secure = true;
+ }
+
+ var url =
+ browserTestPath(schemefulDomain) + "cookie_filtering_resource.sjs";
+ await fetchHelper(url, cookie, secure, justSitename(schemefulDomain));
+ Services.cookies.removeAll(); // clean cookies across secure/insecure runs
+ }
+}
+
+add_task(preclean_test);
+add_task(test_domain); // 5
+add_task(test_insecure_domain); // 2
+add_task(test_subdomain); // 5
+add_task(test_insecure_subdomain); // 2
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_sync_across_tabs.js b/netwerk/test/browser/browser_cookie_sync_across_tabs.js
new file mode 100644
index 0000000000..127bb2555b
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_sync_across_tabs.js
@@ -0,0 +1,79 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(async function () {
+ info("Make sure cookie changes in one process are visible in the other");
+
+ const kRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+ );
+ const kTestPage = kRoot + "dummy.html";
+
+ Services.cookies.removeAll();
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: kTestPage,
+ forceNewProcess: true,
+ });
+ let tab2 = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: kTestPage,
+ forceNewProcess: true,
+ });
+
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+
+ let pid1 = browser1.frameLoader.remoteTab.osPid;
+ let pid2 = browser2.frameLoader.remoteTab.osPid;
+
+ // Note, this might not be true once fission is implemented (Bug 1451850)
+ ok(pid1 != pid2, "We should have different processes here.");
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ is(content.document.cookie, "", "Expecting no cookies");
+ });
+
+ await SpecialPowers.spawn(browser2, [], async function () {
+ is(content.document.cookie, "", "Expecting no cookies");
+ });
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ content.document.cookie = "a1=test";
+ });
+
+ await SpecialPowers.spawn(browser2, [], async function () {
+ is(content.document.cookie, "a1=test", "Cookie should be set");
+ content.document.cookie = "a1=other_test";
+ });
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ is(content.document.cookie, "a1=other_test", "Cookie should be set");
+ content.document.cookie = "a2=again";
+ });
+
+ await SpecialPowers.spawn(browser2, [], async function () {
+ is(
+ content.document.cookie,
+ "a1=other_test; a2=again",
+ "Cookie should be set"
+ );
+ content.document.cookie = "a1=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
+ content.document.cookie = "a2=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
+ });
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ is(content.document.cookie, "", "Cookies should be cleared");
+ });
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+
+ ok(true, "Got to the end of the test!");
+});
diff --git a/netwerk/test/browser/browser_fetch_lnk.js b/netwerk/test/browser/browser_fetch_lnk.js
new file mode 100644
index 0000000000..ea8ef57984
--- /dev/null
+++ b/netwerk/test/browser/browser_fetch_lnk.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ const FILE_PAGE = Services.io.newFileURI(
+ new FileUtils.File(getTestFilePath("dummy.html"))
+ ).spec;
+ await BrowserTestUtils.withNewTab(FILE_PAGE, async browser => {
+ try {
+ await SpecialPowers.spawn(browser, [], () =>
+ content.fetch("./file_lnk.lnk")
+ );
+ ok(
+ false,
+ "Loading lnk must fail if it links to a file from other directory"
+ );
+ } catch (err) {
+ is(err.constructor.name, "TypeError", "Should fail on Windows");
+ }
+ });
+});
diff --git a/netwerk/test/browser/browser_http_index_format.js b/netwerk/test/browser/browser_http_index_format.js
new file mode 100644
index 0000000000..7aa2f84a86
--- /dev/null
+++ b/netwerk/test/browser/browser_http_index_format.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FILE_URI = Services.appinfo.OS == "WINNT" ? "file://C:/" : "file:///";
+
+const RESOURCE_URI = "resource:///";
+
+const ZIP_FILE = "res_empty.zip";
+const ZIP_DIR = getChromeDir(getResolvedURI(gTestPath));
+ZIP_DIR.append(ZIP_FILE);
+ZIP_DIR.normalize();
+const ZIP_URI = Services.io.newFileURI(ZIP_DIR).spec;
+const JAR_URI = "jar:" + ZIP_URI + "!/";
+
+const BASE_URI = "http://mochi.test:8888/browser/netwerk/test/browser/";
+const INDEX_URI = BASE_URI + "res_http_index_format";
+
+async function expectIndexToDisplay(aUrl, aExpectToDisplay) {
+ info(`Opening ${aUrl} to check if index will be displayed`);
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, aUrl, true);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [aExpectToDisplay],
+ aExpectToDisplay => {
+ is(
+ !!content.document.getElementsByTagName("table").length,
+ aExpectToDisplay,
+ "The index should be displayed"
+ );
+ }
+ );
+ await BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_http_index_format() {
+ await expectIndexToDisplay(FILE_URI, true);
+ await expectIndexToDisplay(RESOURCE_URI, true);
+ await expectIndexToDisplay(JAR_URI, true);
+ await expectIndexToDisplay(INDEX_URI, false);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http_index_format.allowed_schemes", "*"]],
+ });
+
+ await expectIndexToDisplay(INDEX_URI, true);
+});
diff --git a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
new file mode 100644
index 0000000000..e791794579
--- /dev/null
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -0,0 +1,273 @@
+/*
+ * Tests for bug 1241377: A channel with nsIFormPOSTActionChannel interface
+ * should be able to accept form POST.
+ */
+
+"use strict";
+
+const SCHEME = "x-bug1241377";
+
+const FORM_BASE = SCHEME + "://dummy/form/";
+const NORMAL_FORM_URI = FORM_BASE + "normal.html";
+const UPLOAD_FORM_URI = FORM_BASE + "upload.html";
+const POST_FORM_URI = FORM_BASE + "post.html";
+
+const ACTION_BASE = SCHEME + "://dummy/action/";
+const NORMAL_ACTION_URI = ACTION_BASE + "normal.html";
+const UPLOAD_ACTION_URI = ACTION_BASE + "upload.html";
+const POST_ACTION_URI = ACTION_BASE + "post.html";
+
+function CustomProtocolHandler() {}
+CustomProtocolHandler.prototype = {
+ /** nsIProtocolHandler */
+ get scheme() {
+ return SCHEME;
+ },
+ newChannel(aURI, aLoadInfo) {
+ return new CustomChannel(aURI, aLoadInfo);
+ },
+ allowPort(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsISupports */
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler"]),
+};
+
+function CustomChannel(aURI, aLoadInfo) {
+ this.uri = aURI;
+ this.loadInfo = aLoadInfo;
+
+ this._uploadStream = null;
+
+ var interfaces = [Ci.nsIRequest, Ci.nsIChannel];
+ if (this.uri.spec == POST_ACTION_URI) {
+ interfaces.push(Ci.nsIFormPOSTActionChannel);
+ } else if (this.uri.spec == UPLOAD_ACTION_URI) {
+ interfaces.push(Ci.nsIUploadChannel);
+ }
+ this.QueryInterface = ChromeUtils.generateQI(interfaces);
+}
+CustomChannel.prototype = {
+ /** nsIUploadChannel */
+ get uploadStream() {
+ return this._uploadStream;
+ },
+ set uploadStream(val) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ setUploadStream(aStream, aContentType, aContentLength) {
+ this._uploadStream = aStream;
+ },
+
+ /** nsIChannel */
+ get originalURI() {
+ return this.uri;
+ },
+ get URI() {
+ return this.uri;
+ },
+ owner: null,
+ notificationCallbacks: null,
+ get securityInfo() {
+ return null;
+ },
+ get contentType() {
+ return "text/html";
+ },
+ set contentType(val) {},
+ contentCharset: "UTF-8",
+ get contentLength() {
+ return -1;
+ },
+ set contentLength(val) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ open() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ asyncOpen(aListener) {
+ var data = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>test bug 1241377</title>
+</head>
+<body>
+`;
+
+ if (this.uri.spec.startsWith(FORM_BASE)) {
+ data += `
+<form id="form" action="${this.uri.spec.replace(FORM_BASE, ACTION_BASE)}"
+ method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+
+<iframe id="frame" name="frame" width="200" height="200"></iframe>
+
+<script type="text/javascript">
+<!--
+document.getElementById('form').submit();
+//-->
+</script>
+`;
+ } else if (this.uri.spec.startsWith(ACTION_BASE)) {
+ var postData = "";
+ var headers = {};
+ if (this._uploadStream) {
+ var bstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ bstream.setInputStream(this._uploadStream);
+ postData = bstream.readBytes(bstream.available());
+
+ if (this._uploadStream instanceof Ci.nsIMIMEInputStream) {
+ this._uploadStream.visitHeaders((name, value) => {
+ headers[name] = value;
+ });
+ }
+ }
+ data += `
+<input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
+<input id="post_data" value="${btoa(postData)}">
+<input id="upload_headers" value='${JSON.stringify(headers)}'>
+`;
+ }
+
+ data += `
+</body>
+</html>
+`;
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.setData(data, data.length);
+
+ var runnable = {
+ run: () => {
+ try {
+ aListener.onStartRequest(this, null);
+ } catch (e) {}
+ try {
+ aListener.onDataAvailable(this, stream, 0, stream.available());
+ } catch (e) {}
+ try {
+ aListener.onStopRequest(this, null, Cr.NS_OK);
+ } catch (e) {}
+ },
+ };
+ Services.tm.dispatchToMainThread(runnable);
+ },
+
+ /** nsIRequest */
+ get name() {
+ return this.uri.spec;
+ },
+ isPending() {
+ return false;
+ },
+ get status() {
+ return Cr.NS_OK;
+ },
+ cancel(status) {},
+ loadGroup: null,
+ loadFlags:
+ Ci.nsIRequest.LOAD_NORMAL |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+};
+
+function frameScript() {
+ /* eslint-env mozilla/frame-script */
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ addMessageListener("Test:WaitForIFrame", function () {
+ var check = function () {
+ if (content) {
+ var frame = content.document.getElementById("frame");
+ if (frame) {
+ var upload_stream =
+ frame.contentDocument.getElementById("upload_stream");
+ var post_data = frame.contentDocument.getElementById("post_data");
+ var headers = frame.contentDocument.getElementById("upload_headers");
+ if (upload_stream && post_data && headers) {
+ sendAsyncMessage("Test:IFrameLoaded", [
+ upload_stream.value,
+ post_data.value,
+ headers.value,
+ ]);
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+ /* eslint-enable mozilla/no-arbitrary-setTimeout */
+}
+
+function loadTestTab(uri) {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, uri);
+ var browser = gBrowser.selectedBrowser;
+
+ let manager = browser.messageManager;
+ browser.messageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")();",
+ true
+ );
+
+ return new Promise(resolve => {
+ function listener({ data: [hasUploadStream, postData, headers] }) {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve([hasUploadStream, atob(postData), JSON.parse(headers)]);
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+}
+
+add_task(async function () {
+ var handler = new CustomProtocolHandler();
+ Services.io.registerProtocolHandler(
+ SCHEME,
+ handler,
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
+ -1
+ );
+ registerCleanupFunction(function () {
+ Services.io.unregisterProtocolHandler(SCHEME);
+ });
+});
+
+add_task(async function () {
+ var [hasUploadStream] = await loadTestTab(NORMAL_FORM_URI);
+ is(hasUploadStream, "no", "normal action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ var [hasUploadStream] = await loadTestTab(UPLOAD_FORM_URI);
+ is(hasUploadStream, "no", "upload action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ var [hasUploadStream, postData, headers] = await loadTestTab(POST_FORM_URI);
+
+ is(hasUploadStream, "yes", "post action should have uploadStream");
+ is(postData, "foo=bar\r\n", "POST data is received correctly");
+
+ is(headers["Content-Type"], "text/plain", "Content-Type header is correct");
+ is(headers["Content-Length"], undefined, "Content-Length header is correct");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_post_auth.js b/netwerk/test/browser/browser_post_auth.js
new file mode 100644
index 0000000000..24104f96d6
--- /dev/null
+++ b/netwerk/test/browser/browser_post_auth.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+const FOLDER = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+);
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ `${FOLDER}post.html`
+ );
+ BrowserTestUtils.startLoadingURIString(
+ tab.linkedBrowser,
+ `${FOLDER}post.html`
+ );
+ await browserLoadedPromise;
+
+ let finalLoadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ `${FOLDER}auth_post.sjs`
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let file = new content.File(
+ [new content.Blob(["1234".repeat(1024 * 500)], { type: "text/plain" })],
+ "test-name"
+ );
+ content.document.getElementById("input_file").mozSetFileArray([file]);
+ content.document.getElementById("form").submit();
+ });
+
+ let promptPromise = PromptTestUtils.handleNextPrompt(
+ tab.linkedBrowser,
+ {
+ modalType: Services.prefs.getIntPref("prompts.modalType.httpAuth"),
+ promptType: "promptUserAndPass",
+ },
+ { buttonNumClick: 0, loginInput: "user", passwordInput: "pass" }
+ );
+
+ await promptPromise;
+
+ await finalLoadPromise;
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ Assert.ok(content.location.href.includes("auth_post.sjs"));
+ Assert.ok(content.document.body.innerHTML.includes("1234"));
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Clean up any active logins we added during the test.
+ Services.obs.notifyObservers(null, "net:clear-active-logins");
+});
diff --git a/netwerk/test/browser/browser_post_file.js b/netwerk/test/browser/browser_post_file.js
new file mode 100644
index 0000000000..8b156dff9e
--- /dev/null
+++ b/netwerk/test/browser/browser_post_file.js
@@ -0,0 +1,71 @@
+/*
+ * Tests for bug 1241100: Post to local file should not overwrite the file.
+ */
+"use strict";
+
+async function createTestFile(filename, content) {
+ let path = PathUtils.join(PathUtils.tempDir, filename);
+ await IOUtils.writeUTF8(path, content);
+ return path;
+}
+
+add_task(async function () {
+ var postFilename = "post_file.html";
+ var actionFilename = "action_file.html";
+
+ var postFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>post file</title>
+</head>
+<body onload="document.getElementById('form').submit();">
+<form id="form" action="${actionFilename}" method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+<iframe id="frame" name="frame"></iframe>
+</body>
+</html>
+`;
+
+ var actionFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>action file</title>
+</head>
+<body>
+<div id="action_file_ok">ok</div>
+</body>
+</html>
+`;
+
+ var postPath = await createTestFile(postFilename, postFileContent);
+ var actionPath = await createTestFile(actionFilename, actionFileContent);
+
+ var postURI = PathUtils.toFileURI(postPath);
+ var actionURI = PathUtils.toFileURI(actionPath);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ actionURI
+ );
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, postURI);
+ await browserLoadedPromise;
+
+ var actionFileContentAfter = await IOUtils.readUTF8(actionPath);
+ is(actionFileContentAfter, actionFileContent, "action file is not modified");
+
+ await IOUtils.remove(postPath);
+ await IOUtils.remove(actionPath);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_purgeCache_idle_daily.js b/netwerk/test/browser/browser_purgeCache_idle_daily.js
new file mode 100644
index 0000000000..21ba46af1d
--- /dev/null
+++ b/netwerk/test/browser/browser_purgeCache_idle_daily.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function test_idle_cleanup() {
+ Services.fog.testResetFOG();
+ Services.prefs.setBoolPref(
+ "network.cache.shutdown_purge_in_background_task",
+ true
+ );
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
+ Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
+ let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dir.append("cache2.2021-11-25-08-47-04.purge.bg_rm");
+ Assert.equal(dir.exists(), false, `Folder ${dir.path} should not exist`);
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+ Assert.equal(
+ dir.exists(),
+ true,
+ `Folder ${dir.path} should have been created`
+ );
+
+ Services.obs.notifyObservers(null, "idle-daily");
+
+ await TestUtils.waitForCondition(() => {
+ return !dir.exists();
+ });
+
+ Assert.equal(
+ dir.exists(),
+ false,
+ `Folder ${dir.path} should have been purged by background task`
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderCount.testGetValue(),
+ 1
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.success.testGetValue(),
+ 1
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.failure.testGetValue(),
+ null
+ );
+
+ // Check that telemetry properly detects folders failing to be deleted when readonly
+ // Making folders readonly only works on windows
+ if (AppConstants.platform == "win") {
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+ dir.QueryInterface(Ci.nsILocalFileWin).readOnly = true;
+
+ Services.obs.notifyObservers(null, "idle-daily");
+
+ await BrowserTestUtils.waitForCondition(async () => {
+ return (
+ (await Glean.networking.residualCacheFolderRemoval.failure.testGetValue()) ==
+ 1
+ );
+ });
+
+ Assert.equal(
+ await Glean.networking.residualCacheFolderCount.testGetValue(),
+ 2
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.success.testGetValue(),
+ 1
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.failure.testGetValue(),
+ 1
+ );
+
+ dir.QueryInterface(Ci.nsILocalFileWin).readOnly = false;
+ dir.remove(true);
+ }
+
+ Services.prefs.clearUserPref(
+ "network.cache.shutdown_purge_in_background_task"
+ );
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.cache");
+ Services.prefs.clearUserPref("privacy.sanitize.sanitizeOnShutdown");
+});
diff --git a/netwerk/test/browser/browser_resource_navigation.js b/netwerk/test/browser/browser_resource_navigation.js
new file mode 100644
index 0000000000..56ec280b83
--- /dev/null
+++ b/netwerk/test/browser/browser_resource_navigation.js
@@ -0,0 +1,76 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(async function () {
+ info("Make sure navigation through links in resource:// pages work");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "resource://gre/" },
+ async function (browser) {
+ // Following a directory link shall properly open the directory (bug 1224046)
+ await SpecialPowers.spawn(browser, [], function () {
+ let link = Array.prototype.filter.call(
+ content.document.getElementsByClassName("dir"),
+ function (element) {
+ let name = element.textContent;
+ // Depending whether resource:// is backed by jar: or file://,
+ // directories either have a trailing slash or they don't.
+ if (name.endsWith("/")) {
+ name = name.slice(0, -1);
+ }
+ return name == "components";
+ }
+ )[0];
+ // First ensure the link is in the viewport
+ link.scrollIntoView();
+ // Then click on it.
+ link.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ undefined,
+ "resource://gre/components/"
+ );
+
+ // Following the parent link shall properly open the parent (bug 1366180)
+ await SpecialPowers.spawn(browser, [], function () {
+ let link = content.document
+ .getElementById("UI_goUp")
+ .getElementsByTagName("a")[0];
+ // The link should always be high enough in the page to be in the viewport.
+ link.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ undefined,
+ "resource://gre/"
+ );
+
+ // Following a link to a given file shall properly open the file.
+ await SpecialPowers.spawn(browser, [], function () {
+ let link = Array.prototype.filter.call(
+ content.document.getElementsByClassName("file"),
+ function (element) {
+ return element.textContent == "greprefs.js";
+ }
+ )[0];
+ link.scrollIntoView();
+ link.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ undefined,
+ "resource://gre/greprefs.js"
+ );
+
+ ok(true, "Got to the end of the test!");
+ }
+ );
+});
diff --git a/netwerk/test/browser/browser_speculative_connection_link_header.js b/netwerk/test/browser/browser_speculative_connection_link_header.js
new file mode 100644
index 0000000000..24549d30b0
--- /dev/null
+++ b/netwerk/test/browser/browser_speculative_connection_link_header.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Services.prefs.setBoolPref("network.http.debug-observations", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.http.debug-observations");
+});
+
+// Test steps:
+// 1. Load file_link_header.sjs
+// 2.`<link rel="preconnect" href="https://localhost">` is in
+// file_link_header.sjs, so we will create a speculative connection.
+// 3. We use "speculative-connect-request" topic to observe whether the
+// speculative connection is attempted.
+// 4. Finally, we check if the observed host and partition key are the same and
+// as the expected.
+add_task(async function test_link_preconnect() {
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/file_link_header.sjs`;
+
+ let observed = "";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "speculative-connect-request") {
+ Services.obs.removeObserver(observer, "speculative-connect-request");
+ observed = aData;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "speculative-connect-request");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ // The hash key should be like:
+ // ".S........[tlsflags0x00000000]localhost:443^partitionKey=%28https%2Cexample.com%29"
+
+ // Extracting "localhost:443"
+ let hostPortRegex = /\[.*\](.*?)\^/;
+ let hostPortMatch = hostPortRegex.exec(observed);
+ let hostPort = hostPortMatch ? hostPortMatch[1] : "";
+ // Extracting "%28https%2Cexample.com%29"
+ let partitionKeyRegex = /\^partitionKey=(.*)$/;
+ let partitionKeyMatch = partitionKeyRegex.exec(observed);
+ let partitionKey = partitionKeyMatch ? partitionKeyMatch[1] : "";
+
+ Assert.equal(hostPort, "localhost:443");
+ Assert.equal(partitionKey, "%28https%2Cexample.com%29");
+});
diff --git a/netwerk/test/browser/browser_test_data_channel_observer.js b/netwerk/test/browser/browser_test_data_channel_observer.js
new file mode 100644
index 0000000000..e03bbc72e6
--- /dev/null
+++ b/netwerk/test/browser/browser_test_data_channel_observer.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,<h1>Test";
+
+let created = false;
+
+add_task(async function test_data_channel_observer() {
+ setupObserver();
+ let tab = await BrowserTestUtils.addTab(gBrowser, TEST_URI);
+ await BrowserTestUtils.waitForCondition(() => created);
+ ok(created, "We received observer notification");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+function setupObserver() {
+ const observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe: function observe(subject, topic) {
+ switch (topic) {
+ case "data-channel-opened":
+ let channelURI = subject.QueryInterface(Ci.nsIChannel).URI.spec;
+ if (channelURI === TEST_URI) {
+ Services.obs.removeObserver(observer, "data-channel-opened");
+ created = true;
+ }
+ break;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "data-channel-opened");
+}
diff --git a/netwerk/test/browser/browser_test_favicon.js b/netwerk/test/browser/browser_test_favicon.js
new file mode 100644
index 0000000000..99cc6b0922
--- /dev/null
+++ b/netwerk/test/browser/browser_test_favicon.js
@@ -0,0 +1,26 @@
+// Tests third party cookie blocking using a favicon loaded from a different
+// domain. The cookie should be considered third party.
+"use strict";
+add_task(async function () {
+ const iconUrl =
+ "http://example.org/browser/netwerk/test/browser/damonbowling.jpg";
+ const pageUrl =
+ "http://example.com/browser/netwerk/test/browser/file_favicon.html";
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior", 1]],
+ });
+
+ let promise = TestUtils.topicObserved("cookie-rejected", subject => {
+ let uri = subject.QueryInterface(Ci.nsIURI);
+ return uri.spec == iconUrl;
+ });
+
+ // Kick off a page load that will load the favicon.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ await promise;
+ ok(true, "foreign favicon cookie was blocked");
+});
diff --git a/netwerk/test/browser/browser_test_io_activity.js b/netwerk/test/browser/browser_test_io_activity.js
new file mode 100644
index 0000000000..1e9cb29b6d
--- /dev/null
+++ b/netwerk/test/browser/browser_test_io_activity.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+"use strict";
+const ROOT_URL = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+);
+const TEST_URL = "about:license";
+const TEST_URL2 = ROOT_URL + "ioactivity.html";
+
+var gotSocket = false;
+var gotFile = false;
+var gotSqlite = false;
+var gotEmptyData = false;
+
+function processResults(results) {
+ for (let data of results) {
+ console.log(data.location);
+ gotEmptyData = data.rx == 0 && data.tx == 0 && !gotEmptyData;
+ gotSocket = data.location.startsWith("socket://127.0.0.1:") || gotSocket;
+ gotFile = data.location.endsWith("aboutLicense.css") || gotFile;
+ gotSqlite = data.location.endsWith("places.sqlite") || gotSqlite;
+ // check for the write-ahead file as well
+ gotSqlite = data.location.endsWith("places.sqlite-wal") || gotSqlite;
+ }
+}
+
+add_task(async function testRequestIOActivity() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["io.activity.enabled", true]],
+ });
+ waitForExplicitFinish();
+ Services.obs.notifyObservers(null, "profile-initial-state");
+
+ await BrowserTestUtils.withNewTab(TEST_URL, async function (browser) {
+ await BrowserTestUtils.withNewTab(TEST_URL2, async function (browser) {
+ let results = await ChromeUtils.requestIOActivity();
+ processResults(results);
+
+ ok(gotSocket, "A socket was used");
+ // test deactivated for now
+ // ok(gotFile, "A file was used");
+ ok(gotSqlite, "A sqlite DB was used");
+ ok(!gotEmptyData, "Every I/O event had data");
+ });
+ });
+});
diff --git a/netwerk/test/browser/browser_test_offline_tab.js b/netwerk/test/browser/browser_test_offline_tab.js
new file mode 100644
index 0000000000..bf60b4f462
--- /dev/null
+++ b/netwerk/test/browser/browser_test_offline_tab.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_set_tab_offline() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ // Set the tab to offline
+ gBrowser.selectedBrowser.browsingContext.forceOffline = true;
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ try {
+ await content.fetch("https://example.com/empty.html");
+ ok(false, "Should not load since tab is offline");
+ } catch (err) {
+ is(err.name, "TypeError", "Should fail since tab is offline");
+ }
+ });
+ });
+});
+
+add_task(async function test_set_tab_online() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ // Set the tab to online
+ gBrowser.selectedBrowser.browsingContext.forceOffline = false;
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ try {
+ await content.fetch("https://example.com/empty.html");
+ ok(true, "Should load since tab is online");
+ } catch (err) {
+ ok(false, "Should not fail since tab is online");
+ }
+ });
+ });
+});
diff --git a/netwerk/test/browser/cookie_filtering_helper.sys.mjs b/netwerk/test/browser/cookie_filtering_helper.sys.mjs
new file mode 100644
index 0000000000..ab9d721359
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_helper.sys.mjs
@@ -0,0 +1,166 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The functions in this file will run in the content process in a test
+// scope.
+/* eslint-env mozilla/simpletest */
+/* global ContentTaskUtils, content */
+
+import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";
+
+const info = console.log;
+
+export var HTTPS_EXAMPLE_ORG = "https://example.org";
+export var HTTPS_EXAMPLE_COM = "https://example.com";
+export var HTTP_EXAMPLE_COM = "http://example.com";
+
+export function browserTestPath(uri) {
+ return uri + "/browser/netwerk/test/browser/";
+}
+
+export function waitForAllExpectedTests() {
+ return ContentTaskUtils.waitForCondition(() => {
+ return content.testDone === true;
+ });
+}
+
+export function cleanupObservers() {
+ Services.obs.notifyObservers(null, "cookie-content-filter-cleanup");
+}
+
+export async function preclean_test() {
+ // enable all cookies for the set-cookie trigger via setCookieStringFromHttp
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+ Services.prefs.setBoolPref(
+ "network.cookie.sameSite.noneRequiresSecure",
+ false
+ );
+ Services.prefs.setBoolPref("network.cookie.sameSite.schemeful", false);
+
+ Services.cookies.removeAll();
+}
+
+export async function cleanup_test() {
+ Services.prefs.clearUserPref("network.cookie.cookieBehavior");
+ Services.prefs.clearUserPref(
+ "network.cookieJarSettings.unblocked_for_testing"
+ );
+
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+ Services.prefs.clearUserPref("network.cookie.sameSite.noneRequiresSecure");
+ Services.prefs.clearUserPref("network.cookie.sameSite.schemeful");
+
+ Services.cookies.removeAll();
+}
+
+export async function fetchHelper(url, cookie, secure, domain = "") {
+ let headers = new Headers();
+
+ headers.append("return-set-cookie", cookie);
+
+ if (!secure) {
+ headers.append("return-insecure-cookie", cookie);
+ }
+
+ if (domain != "") {
+ headers.append("return-cookie-domain", domain);
+ }
+
+ info("fetching " + url);
+ await fetch(url, { headers });
+}
+
+// cookie header strings with multiple name=value pairs delimited by \n
+// will trigger multiple "cookie-changed" signals
+export function triggerSetCookieFromHttp(uri, cookie, fpd = "", ucd = 0) {
+ info("about to trigger set-cookie: " + uri + " " + cookie);
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ if (fpd != "") {
+ channel.loadInfo.originAttributes = { firstPartyDomain: fpd };
+ }
+
+ if (ucd != 0) {
+ channel.loadInfo.originAttributes = { userContextId: ucd };
+ }
+ Services.cookies.setCookieStringFromHttp(uri, cookie, channel);
+}
+
+export async function triggerSetCookieFromHttpPrivate(uri, cookie) {
+ info("about to trigger set-cookie: " + uri + " " + cookie);
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ channel.loadInfo.originAttributes = { privateBrowsingId: 1 };
+ channel.setPrivate(true);
+ Services.cookies.setCookieStringFromHttp(uri, cookie, channel);
+}
+
+// observer/listener function that will be run on the content processes
+// listens and checks for the expected cookies
+export function checkExpectedCookies(expected, browserName) {
+ const COOKIE_FILTER_TEST_MESSAGE = "content-added-cookie";
+ const COOKIE_FILTER_TEST_CLEANUP = "cookie-content-filter-cleanup";
+
+ // Counting the expected number of tests is vital to the integrity of these
+ // tests due to the fact that this test suite relies on triggering tests
+ // to occur on multiple content processes.
+ // As such, test modifications/bugs often lead to silent failures.
+ // Hence, we count to ensure we didn't break anything
+ // To reduce risk here, we modularize each test as much as possible to
+ // increase liklihood that a silent failure will trigger a no-test
+ // error/warning
+ content.testDone = false;
+ let testNumber = 0;
+
+ // setup observer that continues listening/testing
+ function obs(subject, topic) {
+ // cleanup trigger recieved -> tear down the observer
+ if (topic == COOKIE_FILTER_TEST_CLEANUP) {
+ info("cleaning up: " + browserName);
+ Services.obs.removeObserver(obs, COOKIE_FILTER_TEST_MESSAGE);
+ Services.obs.removeObserver(obs, COOKIE_FILTER_TEST_CLEANUP);
+ return;
+ }
+
+ // test trigger recv'd -> perform test on cookie contents
+ if (topic == COOKIE_FILTER_TEST_MESSAGE) {
+ info("Checking if cookie visible: " + browserName);
+ let result = content.document.cookie;
+ let resultStr =
+ "Result " +
+ result +
+ " == expected: " +
+ expected[testNumber] +
+ " in " +
+ browserName;
+ ok(result == expected[testNumber], resultStr);
+ testNumber++;
+ if (testNumber >= expected.length) {
+ info("finishing browser tests: " + browserName);
+ content.testDone = true;
+ }
+ return;
+ }
+
+ ok(false, "Didn't handle cookie message properly"); //
+ }
+
+ info("setting up observers: " + browserName);
+ Services.obs.addObserver(obs, COOKIE_FILTER_TEST_MESSAGE);
+ Services.obs.addObserver(obs, COOKIE_FILTER_TEST_CLEANUP);
+}
diff --git a/netwerk/test/browser/cookie_filtering_resource.sjs b/netwerk/test/browser/cookie_filtering_resource.sjs
new file mode 100644
index 0000000000..979d56dc9c
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_resource.sjs
@@ -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/. */
+
+"use strict";
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ // configure set-cookie domain
+ let domain = "";
+ if (request.hasHeader("return-cookie-domain")) {
+ domain = "; Domain=" + request.getHeader("return-cookie-domain");
+ }
+
+ // configure set-cookie sameSite
+ let authStr = "; Secure";
+ if (request.hasHeader("return-insecure-cookie")) {
+ authStr = "";
+ }
+
+ // use headers to decide if we have them
+ if (request.hasHeader("return-set-cookie")) {
+ response.setHeader(
+ "Set-Cookie",
+ request.getHeader("return-set-cookie") + authStr + domain,
+ false
+ );
+ }
+
+ let body = "<!DOCTYPE html> <html> <body> true </body> </html>";
+ response.write(body);
+}
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_com.html b/netwerk/test/browser/cookie_filtering_secure_resource_com.html
new file mode 100644
index 0000000000..e25a719644
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_com.html
@@ -0,0 +1,6 @@
+ <!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/cookie_filtering_square.png" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^ b/netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^
new file mode 100644
index 0000000000..2bdf118064
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store
+Set-Cookie: test-cookie=comhtml
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_org.html b/netwerk/test/browser/cookie_filtering_secure_resource_org.html
new file mode 100644
index 0000000000..7221dc370d
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_org.html
@@ -0,0 +1,6 @@
+ <!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.org/browser/netwerk/test/browser/cookie_filtering_square.png" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^ b/netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^
new file mode 100644
index 0000000000..924c150ccc
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store
+Set-Cookie: test-cookie=orghtml
diff --git a/netwerk/test/browser/cookie_filtering_square.png b/netwerk/test/browser/cookie_filtering_square.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_square.png
diff --git a/netwerk/test/browser/cookie_filtering_square.png^headers^ b/netwerk/test/browser/cookie_filtering_square.png^headers^
new file mode 100644
index 0000000000..912856ae4a
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_square.png^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Set-Cookie: test-cookie=png
diff --git a/netwerk/test/browser/damonbowling.jpg b/netwerk/test/browser/damonbowling.jpg
new file mode 100644
index 0000000000..8bdb2b6042
--- /dev/null
+++ b/netwerk/test/browser/damonbowling.jpg
Binary files differ
diff --git a/netwerk/test/browser/damonbowling.jpg^headers^ b/netwerk/test/browser/damonbowling.jpg^headers^
new file mode 100644
index 0000000000..77f4f49089
--- /dev/null
+++ b/netwerk/test/browser/damonbowling.jpg^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store
+Set-Cookie: damon=bowling
diff --git a/netwerk/test/browser/dummy.html b/netwerk/test/browser/dummy.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/dummy.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/early_hint_asset.sjs b/netwerk/test/browser/early_hint_asset.sjs
new file mode 100644
index 0000000000..ba52e757ff
--- /dev/null
+++ b/netwerk/test/browser/early_hint_asset.sjs
@@ -0,0 +1,50 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let hinted =
+ request.hasHeader("X-Moz") && request.getHeader("X-Moz") === "early hint";
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (hinted) {
+ count.hinted += 1;
+ } else {
+ count.normal += 1;
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+
+ let content = "";
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+
+ if (qs.get("cached") === "1") {
+ response.setHeader("Cache-Control", "max-age=604800", false);
+ } else {
+ response.setHeader("Cache-Control", "no-cache", false);
+ }
+
+ if (asset === "image") {
+ response.setHeader("Content-Type", "image/png", false);
+ // set to green/black horizontal stripes (71 bytes)
+ content = atob(
+ hinted
+ ? "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8XGBMAAAAASUVORK5CYII="
+ : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ } else if (asset === "style") {
+ response.setHeader("Content-Type", "text/css", false);
+ // green background on hint response, purple response otherwise
+ content = `#square { background: ${hinted ? "#1aff1a" : "#4b0092"}`;
+ } else if (asset === "script") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ // green background on hint response, purple response otherwise
+ content = `window.onload = function() {
+ document.getElementById('square').style.background = "${
+ hinted ? "#1aff1a" : "#4b0092"
+ }";
+ }`;
+ } else if (asset === "fetch") {
+ response.setHeader("Content-Type", "text/plain", false);
+ content = hinted ? "hinted" : "normal";
+ }
+
+ response.write(content);
+}
diff --git a/netwerk/test/browser/early_hint_asset_html.sjs b/netwerk/test/browser/early_hint_asset_html.sjs
new file mode 100644
index 0000000000..eb5156d4f8
--- /dev/null
+++ b/netwerk/test/browser/early_hint_asset_html.sjs
@@ -0,0 +1,135 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+ let hinted = qs.get("hinted") === "1";
+ let httpCode = qs.get("code");
+ let uuid = qs.get("uuid");
+ let cached = qs.get("cached") === "1";
+
+ let url = `early_hint_asset.sjs?as=${asset}${uuid ? `&uuid=${uuid}` : ""}${
+ cached ? "&cached=1" : ""
+ }`;
+
+ // write to raw socket
+ response.seizePower();
+ let link = "";
+ if (hinted) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ if (asset === "fetch" || asset === "font") {
+ // fetch and font has to specify the crossorigin attribute
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as
+ link = `Link: <${url}>; rel=preload; as=${asset}; crossorigin=anonymous\r\n`;
+ response.write(link);
+ } else if (asset === "module") {
+ // module preloads are handled differently
+ link = `Link: <${url}>; rel=modulepreload\r\n`;
+ response.write(link);
+ } else {
+ link = `Link: <${url}>; rel=preload; as=${asset}\r\n`;
+ response.write(link);
+ }
+ response.write("\r\n");
+ }
+
+ let body = "";
+ if (asset === "image") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <img src="${url}" width="100px">
+ </body>
+ </html>`;
+ } else if (asset === "style") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="${url}">
+ </head>
+ <body>
+ <h1>Test preload css<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "script") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <script src="${url}"></script>
+ </head>
+ <body>
+ <h1>Test preload javascript<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "module") {
+ // this code assumes that the .sjs for the module is in the same directory
+ var file_name = url.split("/");
+ file_name = file_name[file_name.length - 1];
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ </head>
+ <body>
+ <h1>Test preload module<h1>
+ <div id="square" style="width:100px;height:100px;">
+ <script type="module">
+ import { draw } from "./${file_name}";
+ draw();
+ </script>
+ </body>
+ </html>
+ `;
+ } else if (asset === "fetch") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body onload="onLoad()">
+ <script>
+ function onLoad() {
+ fetch("${url}")
+ .then(r => r.text())
+ .then(r => document.getElementsByTagName("h2")[0].textContent = r);
+ }
+ </script>
+ <h1>Test preload fetch</h1>
+ <h2>Fetching...</h2>
+ </body>
+ </html>
+ `;
+ } else if (asset === "font") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <style>
+ @font-face {
+ font-family: "preloadFont";
+ src: url("${url}");
+ }
+ body {
+ font-family: "preloadFont";
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Test preload font<h1>
+ </body>
+ </html>
+ `;
+ }
+
+ if (!httpCode) {
+ response.write(`HTTP/1.1 200 OK\r\n`);
+ } else {
+ response.write(`HTTP/1.1 ${httpCode} Error\r\n`);
+ }
+ response.write(link);
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_csp_options_html.sjs b/netwerk/test/browser/early_hint_csp_options_html.sjs
new file mode 100644
index 0000000000..73a6a539d8
--- /dev/null
+++ b/netwerk/test/browser/early_hint_csp_options_html.sjs
@@ -0,0 +1,120 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+ let hinted = qs.get("hinted") !== "0";
+ let httpCode = qs.get("code");
+ let csp = qs.get("csp");
+ let csp_in_early_hint = qs.get("csp_in_early_hint");
+ let host = qs.get("host");
+
+ // eslint-disable-next-line mozilla/use-services
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
+ Ci.nsIUUIDGenerator
+ );
+ let uuid = uuidGenerator.generateUUID().toString();
+ let url = `early_hint_pixel.sjs?as=${asset}&uuid=${uuid}`;
+ if (host) {
+ url = host + url;
+ }
+
+ // write to raw socket
+ response.seizePower();
+
+ if (hinted) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ if (csp_in_early_hint) {
+ response.write(
+ `Content-Security-Policy: ${csp_in_early_hint.replaceAll('"', "")}\r\n`
+ );
+ }
+ response.write(`Link: <${url}>; rel=preload; as=${asset}\r\n`);
+ response.write("\r\n");
+ }
+
+ let body = "";
+ if (asset === "image") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <img id="test_image" src="${url}" width="100px">
+ </body>
+ </html>`;
+ } else if (asset === "style") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="${url}">
+ </head>
+ <body>
+ <h1>Test preload css<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "script") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <script src="${url}"></script>
+ </head>
+ <body>
+ <h1>Test preload javascript<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "fetch") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body onload="onLoad()">
+ <script>
+ function onLoad() {
+ fetch("${url}")
+ .then(r => r.text())
+ .then(r => document.getElementsByTagName("h2")[0].textContent = r);
+ }
+ </script>
+ <h1>Test preload fetch</h1>
+ <h2>Fetching...</h2>
+ </body>
+ </html>
+ `;
+ } else if (asset === "font") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <style>
+ @font-face {
+ font-family: "preloadFont";
+ src: url("${url}") format("woff");
+ }
+ body {
+ font-family: "preloadFont";
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Test preload font<h1>
+ </body>
+ </html>
+ `;
+ }
+
+ if (!httpCode) {
+ response.write(`HTTP/1.1 200 OK\r\n`);
+ } else {
+ response.write(`HTTP/1.1 ${httpCode} Error\r\n`);
+ }
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ if (csp) {
+ response.write(`Content-Security-Policy: ${csp.replaceAll('"', "")}\r\n`);
+ }
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_error.sjs b/netwerk/test/browser/early_hint_error.sjs
new file mode 100644
index 0000000000..d3d0da4bd5
--- /dev/null
+++ b/netwerk/test/browser/early_hint_error.sjs
@@ -0,0 +1,35 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setStatusLine(
+ request.httpVersion,
+ parseInt(request.queryString),
+ "Dynamic error"
+ );
+ response.setHeader("Content-Type", "image/png", false);
+ response.setHeader("Cache-Control", "max-age=604800", false);
+
+ // count requests
+ let image;
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (
+ request.hasHeader("X-Moz") &&
+ request.getHeader("X-Moz") === "early hint"
+ ) {
+ count.hinted += 1;
+ // set to green/black horizontal stripes (71 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8X" +
+ "GBMAAAAASUVORK5CYII="
+ );
+ } else {
+ count.normal += 1;
+ // set to purple/white checkered pattern (76 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1" +
+ "bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+ response.write(image);
+}
diff --git a/netwerk/test/browser/early_hint_main_html.sjs b/netwerk/test/browser/early_hint_main_html.sjs
new file mode 100644
index 0000000000..dc95273c9d
--- /dev/null
+++ b/netwerk/test/browser/early_hint_main_html.sjs
@@ -0,0 +1,62 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // write to raw socket
+ response.seizePower();
+
+ let qs = new URLSearchParams(request.queryString);
+ let imgs = [];
+ let new_hint = true;
+ let new_header = true;
+ for (const [imgUrl, uuid] of qs.entries()) {
+ if (new_hint) {
+ // we need to write a new header
+ new_hint = false;
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ }
+ if (!imgUrl.length) {
+ // next hint in new early hint response when empty string is passed
+ new_header = true;
+ if (uuid === "new_response") {
+ new_hint = true;
+ response.write("\r\n");
+ } else if (uuid === "non_link_header") {
+ response.write("Content-Length: 25\r\n");
+ }
+ response.write("\r\n");
+ } else {
+ // either append link in new header or in same header
+ if (new_header) {
+ new_header = false;
+ response.write("Link: ");
+ } else {
+ response.write(", ");
+ }
+ // add query string to make request unique this has the drawback that
+ // the preloaded image can't accept query strings on it's own / or has
+ // to strip the appended "?uuid" from the query string before parsing
+ imgs.push(`<img src="${imgUrl}?${uuid}" width="100px">`);
+ response.write(`<${imgUrl}?${uuid}>; rel=preload; as=image`);
+ }
+ }
+ if (!new_hint) {
+ // add separator to main document
+ response.write("\r\n\r\n");
+ }
+
+ let body = `<!DOCTYPE html>
+<html>
+<body>
+${imgs.join("\n")}
+</body>
+</html>`;
+
+ // main document response
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_main_redirect.sjs b/netwerk/test/browser/early_hint_main_redirect.sjs
new file mode 100644
index 0000000000..0da69d5cee
--- /dev/null
+++ b/netwerk/test/browser/early_hint_main_redirect.sjs
@@ -0,0 +1,67 @@
+"use strict";
+
+// In an SJS file we need to get the setTimeout bits ourselves, despite
+// what eslint might think applies for browser tests.
+// eslint-disable-next-line mozilla/no-redeclare-with-import-autofix
+let { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+async function handleRequest(request, response) {
+ let hinted =
+ request.hasHeader("X-Moz") && request.getHeader("X-Moz") === "early hint";
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (hinted) {
+ count.hinted += 1;
+ } else {
+ count.normal += 1;
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+ response.setHeader("Cache-Control", "max-age=604800", false);
+
+ let content = "";
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+
+ if (asset === "image") {
+ response.setHeader("Content-Type", "image/png", false);
+ // set to green/black horizontal stripes (71 bytes)
+ content = atob(
+ hinted
+ ? "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8XGBMAAAAASUVORK5CYII="
+ : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ } else if (asset === "style") {
+ response.setHeader("Content-Type", "text/css", false);
+ // green background on hint response, purple response otherwise
+ content = `#square { background: ${hinted ? "#1aff1a" : "#4b0092"}`;
+ } else if (asset === "script") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ // green background on hint response, purple response otherwise
+ content = `window.onload = function() {
+ document.getElementById('square').style.background = "${
+ hinted ? "#1aff1a" : "#4b0092"
+ }";
+ }`;
+ } else if (asset === "module") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ // green background on hint response, purple response otherwise
+ content = `export function draw() {
+ document.getElementById('square').style.background = "${
+ hinted ? "#1aff1a" : "#4b0092"
+ }";
+ }`;
+ } else if (asset === "fetch") {
+ response.setHeader("Content-Type", "text/plain", false);
+ content = hinted ? "hinted" : "normal";
+ } else if (asset === "font") {
+ response.setHeader("Content-Type", "font/svg+xml", false);
+ content = '<font><font-face font-family="preloadFont" /></font>';
+ }
+ response.processAsync();
+ setTimeout(() => {
+ response.write(content);
+ response.finish();
+ }, 0);
+ //response.write(content);
+}
diff --git a/netwerk/test/browser/early_hint_pixel.sjs b/netwerk/test/browser/early_hint_pixel.sjs
new file mode 100644
index 0000000000..56a64e9af2
--- /dev/null
+++ b/netwerk/test/browser/early_hint_pixel.sjs
@@ -0,0 +1,37 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "image/png", false);
+ response.setHeader("Cache-Control", "max-age=604800", false);
+
+ // the typo in "Referer" is part of the http spec
+ if (request.hasHeader("Referer")) {
+ setSharedState("requestReferrer", request.getHeader("Referer"));
+ } else {
+ setSharedState("requestReferrer", "");
+ }
+
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ let image;
+ // send different sized images depending whether this is an early hint request
+ if (
+ request.hasHeader("X-Moz") &&
+ request.getHeader("X-Moz") === "early hint"
+ ) {
+ count.hinted += 1;
+ // set to green/black horizontal stripes (71 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8X" +
+ "GBMAAAAASUVORK5CYII="
+ );
+ } else {
+ count.normal += 1;
+ // set to purple/white checkered pattern (76 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1" +
+ "bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+ response.write(image);
+}
diff --git a/netwerk/test/browser/early_hint_pixel_count.sjs b/netwerk/test/browser/early_hint_pixel_count.sjs
new file mode 100644
index 0000000000..b59dd035de
--- /dev/null
+++ b/netwerk/test/browser/early_hint_pixel_count.sjs
@@ -0,0 +1,9 @@
+"use strict";
+
+function handleRequest(request, response) {
+ if (request.hasHeader("X-Early-Hint-Count-Start")) {
+ setSharedState("earlyHintCount", JSON.stringify({ hinted: 0, normal: 0 }));
+ }
+ response.setHeader("Content-Type", "application/json", false);
+ response.write(getSharedState("earlyHintCount"));
+}
diff --git a/netwerk/test/browser/early_hint_preconnect_html.sjs b/netwerk/test/browser/early_hint_preconnect_html.sjs
new file mode 100644
index 0000000000..044c842142
--- /dev/null
+++ b/netwerk/test/browser/early_hint_preconnect_html.sjs
@@ -0,0 +1,32 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let href = qs.get("href");
+ let crossOrigin = qs.get("crossOrigin");
+
+ // write to raw socket
+ response.seizePower();
+
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ response.write(
+ `Link: <${href}>; rel=preconnect; crossOrigin=${crossOrigin}\r\n`
+ );
+ response.write("\r\n");
+
+ let body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <h1>Test rel=preconnect<h1>
+ </body>
+ </html>`;
+
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_preload_test_helper.sys.mjs b/netwerk/test/browser/early_hint_preload_test_helper.sys.mjs
new file mode 100644
index 0000000000..afe6a6bb70
--- /dev/null
+++ b/netwerk/test/browser/early_hint_preload_test_helper.sys.mjs
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Assert } from "resource://testing-common/Assert.sys.mjs";
+import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs";
+
+const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
+
+export async function request_count_checking(testName, got, expected) {
+ // stringify to pretty print assert output
+ let g = JSON.stringify(got);
+ let e = JSON.stringify(expected);
+ // each early hint request can starts one hinted request, but doesn't yet
+ // complete the early hint request during the test case
+ Assert.ok(
+ got.hinted == expected.hinted,
+ `${testName}: unexpected amount of hinted request made expected ${expected.hinted} (${e}), got ${got.hinted} (${g})`
+ );
+ // when the early hint request doesn't complete fast enough, another request
+ // is currently sent from the main document
+ let expected_normal = expected.normal;
+ Assert.ok(
+ got.normal == expected_normal,
+ `${testName}: unexpected amount of normal request made expected ${expected_normal} (${e}), got ${got.normal} (${g})`
+ );
+}
+
+export async function test_hint_preload(
+ testName,
+ requestFrom,
+ imgUrl,
+ expectedRequestCount,
+ uuid = undefined
+) {
+ // generate a uuid if none were passed
+ if (uuid == undefined) {
+ uuid = Services.uuid.generateUUID();
+ }
+ await test_hint_preload_internal(
+ testName,
+ requestFrom,
+ [[imgUrl, uuid.toString()]],
+ expectedRequestCount
+ );
+}
+
+// - testName is just there to be printed during Asserts when failing
+// - the baseUrl can't have query strings, because they are currently used to pass
+// the early hint the server responds with
+// - urls are in the form [[url1, uuid1], ...]. The uuids are there to make each preload
+// unique and not available in the cache from other test cases
+// - expectedRequestCount is the sum of all requested objects { normal: count, hinted: count }
+export async function test_hint_preload_internal(
+ testName,
+ requestFrom,
+ imgUrls,
+ expectedRequestCount
+) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl =
+ requestFrom +
+ "/browser/netwerk/test/browser/early_hint_main_html.sjs?" +
+ new URLSearchParams(imgUrls).toString(); // encode the hinted images as query string
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking(testName, gotRequestCount, expectedRequestCount);
+}
+
+// Verify that CSP policies in both the 103 response as well as the main response are respected.
+// e.g.
+// 103 Early Hint
+// Content-Security-Policy: style-src: self;
+// Link: </style.css>; rel=preload; as=style
+// 200 OK
+// Content-Security-Policy: style-src: none;
+// Link: </font.ttf>; rel=preload; as=font
+
+// Server-side we verify that:
+// - the hinted preload request was made as expected
+// - the load request request was made as expected
+// Client-side, we verify that the image was loaded or not loaded, depending on the scenario
+
+// This verifies preload hints and requests
+export async function test_preload_hint_and_request(input, expected_results) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_csp_options_html.sjs?as=${
+ input.resource_type
+ }&hinted=${input.hinted ? "1" : "0"}${input.csp ? "&csp=" + input.csp : ""}${
+ input.csp_in_early_hint
+ ? "&csp_in_early_hint=" + input.csp_in_early_hint
+ : ""
+ }${input.host ? "&host=" + input.host : ""}`;
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, requestUrl, true);
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await Assert.deepEqual(gotRequestCount, expected_results, input.test_name);
+
+ gBrowser.removeCurrentTab();
+ Services.cache2.clear();
+}
+
+// simple loading of one url and then checking the request count against the
+// passed expected count
+export async function test_preload_url(testName, url, expectedRequestCount) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking(testName, gotRequestCount, expectedRequestCount);
+ Services.cache2.clear();
+}
diff --git a/netwerk/test/browser/early_hint_redirect.sjs b/netwerk/test/browser/early_hint_redirect.sjs
new file mode 100644
index 0000000000..6bcb6bdc86
--- /dev/null
+++ b/netwerk/test/browser/early_hint_redirect.sjs
@@ -0,0 +1,21 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // increase count
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (
+ request.hasHeader("X-Moz") &&
+ request.getHeader("X-Moz") === "early hint"
+ ) {
+ count.hinted += 1;
+ } else {
+ count.normal += 1;
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+
+ // respond with redirect
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ let location = request.queryString;
+ response.setHeader("Location", location, false);
+ response.write("Hello world!");
+}
diff --git a/netwerk/test/browser/early_hint_redirect_html.sjs b/netwerk/test/browser/early_hint_redirect_html.sjs
new file mode 100644
index 0000000000..c111af0978
--- /dev/null
+++ b/netwerk/test/browser/early_hint_redirect_html.sjs
@@ -0,0 +1,24 @@
+"use strict";
+
+// usage via url parameters:
+// - link: if set sends a link header with the given link value as an early hint repsonse
+// - location: sets destination of 301 response
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let link = qs.get("link");
+ let location = qs.get("location");
+
+ // write to raw socket
+ response.seizePower();
+ if (link != undefined) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ response.write(`Link: ${link}\r\n`);
+ response.write("\r\n");
+ }
+
+ response.write("HTTP/1.1 307 Temporary Redirect\r\n");
+ response.write(`Location: ${location}\r\n`);
+ response.write("\r\n");
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_referrer_policy_html.sjs b/netwerk/test/browser/early_hint_referrer_policy_html.sjs
new file mode 100644
index 0000000000..3c8a626de1
--- /dev/null
+++ b/netwerk/test/browser/early_hint_referrer_policy_html.sjs
@@ -0,0 +1,132 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+ var action = qs.get("action");
+ let hinted = qs.get("hinted") !== "0";
+ let httpCode = qs.get("code");
+ let header_referrer_policy = qs.get("header_referrer_policy");
+ let link_referrer_policy = qs.get("link_referrer_policy");
+
+ // eslint-disable-next-line mozilla/use-services
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
+ Ci.nsIUUIDGenerator
+ );
+ let uuid = uuidGenerator.generateUUID().toString();
+ let url = `early_hint_pixel.sjs?as=${asset}&uuid=${uuid}`;
+
+ if (action === "get_request_referrer_results") {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(getSharedState("requestReferrer"));
+ return;
+ } else if (action === "reset_referrer_results") {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(setSharedState("requestReferrer", "not set"));
+ return;
+ }
+
+ // write to raw socket
+ response.seizePower();
+
+ if (hinted) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+
+ if (header_referrer_policy) {
+ response.write(
+ `Referrer-Policy: ${header_referrer_policy.replaceAll('"', "")}\r\n`
+ );
+ }
+
+ response.write(
+ `Link: <${url}>; rel=preload; as=${asset}; ${
+ link_referrer_policy ? "referrerpolicy=" + link_referrer_policy : ""
+ } \r\n`
+ );
+ response.write("\r\n");
+ }
+
+ let body = "";
+ if (asset === "image") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <img src="${url}" width="100px">
+ </body>
+ </html>`;
+ } else if (asset === "style") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="${url}">
+ </head>
+ <body>
+ <h1>Test preload css<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "script") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <script src="${url}"></script>
+ </head>
+ <body>
+ <h1>Test preload javascript<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "fetch") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body onload="onLoad()">
+ <script>
+ function onLoad() {
+ fetch("${url}")
+ .then(r => r.text())
+ .then(r => document.getElementsByTagName("h2")[0].textContent = r);
+ }
+ </script>
+ <h1>Test preload fetch</h1>
+ <h2>Fetching...</h2>
+ </body>
+ </html>
+ `;
+ } else if (asset === "font") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <style>
+ @font-face {
+ font-family: "preloadFont";
+ src: url("${url}") format("woff");
+ }
+ body {
+ font-family: "preloadFont";
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Test preload font<h1>
+ </body>
+ </html>
+ `;
+ }
+
+ if (!httpCode) {
+ response.write(`HTTP/1.1 200 OK\r\n`);
+ } else {
+ response.write(`HTTP/1.1 ${httpCode} Error\r\n`);
+ }
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/file_favicon.html b/netwerk/test/browser/file_favicon.html
new file mode 100644
index 0000000000..77532a3a53
--- /dev/null
+++ b/netwerk/test/browser/file_favicon.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <link rel="shortcut icon" href="http://example.org/browser/netwerk/test/browser/damonbowling.jpg">
+ </head>
+</html>
diff --git a/netwerk/test/browser/file_link_header.sjs b/netwerk/test/browser/file_link_header.sjs
new file mode 100644
index 0000000000..6bab515d19
--- /dev/null
+++ b/netwerk/test/browser/file_link_header.sjs
@@ -0,0 +1,24 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // write to raw socket
+ response.seizePower();
+ let body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="preconnect" href="https://localhost">
+ </head>
+ <body>
+ <h1>Test rel=preconnect<h1>
+ </body>
+ </html>`;
+
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/file_lnk.lnk b/netwerk/test/browser/file_lnk.lnk
new file mode 100644
index 0000000000..abce7587d2
--- /dev/null
+++ b/netwerk/test/browser/file_lnk.lnk
Binary files differ
diff --git a/netwerk/test/browser/ioactivity.html b/netwerk/test/browser/ioactivity.html
new file mode 100644
index 0000000000..5e23f6f117
--- /dev/null
+++ b/netwerk/test/browser/ioactivity.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>IOActivity Test Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/no_103_preload.html b/netwerk/test/browser/no_103_preload.html
new file mode 100644
index 0000000000..64f5e79259
--- /dev/null
+++ b/netwerk/test/browser/no_103_preload.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="http://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/no_103_preload.html^headers^ b/netwerk/test/browser/no_103_preload.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/no_103_preload.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/post.html b/netwerk/test/browser/post.html
new file mode 100644
index 0000000000..9d238c2b97
--- /dev/null
+++ b/netwerk/test/browser/post.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>post file</title>
+</head>
+<body">
+<form id="form" action="auth_post.sjs" method="post" enctype="multipart/form-data">
+<input type="hidden" id="input_hidden" name="foo" value="bar">
+<input id="input_file" name="test_file" type="file">
+<input type="submit">
+</form>
+</body>
+</html>
diff --git a/netwerk/test/browser/redirect.sjs b/netwerk/test/browser/redirect.sjs
new file mode 100644
index 0000000000..09e7d9b1e4
--- /dev/null
+++ b/netwerk/test/browser/redirect.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ let location = request.queryString;
+ response.setHeader("Location", location, false);
+ response.write("Hello world!");
+}
diff --git a/netwerk/test/browser/res.css b/netwerk/test/browser/res.css
new file mode 100644
index 0000000000..eab83656ed
--- /dev/null
+++ b/netwerk/test/browser/res.css
@@ -0,0 +1,4 @@
+/* François was here. */
+#purple-text {
+ color: purple;
+}
diff --git a/netwerk/test/browser/res.css^headers^ b/netwerk/test/browser/res.css^headers^
new file mode 100644
index 0000000000..e13897f157
--- /dev/null
+++ b/netwerk/test/browser/res.css^headers^
@@ -0,0 +1 @@
+Content-Type: text/css; charset=utf-8
diff --git a/netwerk/test/browser/res.csv b/netwerk/test/browser/res.csv
new file mode 100644
index 0000000000..b0246d5964
--- /dev/null
+++ b/netwerk/test/browser/res.csv
@@ -0,0 +1 @@
+1,2,3
diff --git a/netwerk/test/browser/res.csv^headers^ b/netwerk/test/browser/res.csv^headers^
new file mode 100644
index 0000000000..8d30131059
--- /dev/null
+++ b/netwerk/test/browser/res.csv^headers^
@@ -0,0 +1 @@
+Content-Type: text/csv;
diff --git a/netwerk/test/browser/res.mp3 b/netwerk/test/browser/res.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res.mp3
Binary files differ
diff --git a/netwerk/test/browser/res.unknown b/netwerk/test/browser/res.unknown
new file mode 100644
index 0000000000..3546645658
--- /dev/null
+++ b/netwerk/test/browser/res.unknown
@@ -0,0 +1 @@
+unknown
diff --git a/netwerk/test/browser/res_206.html b/netwerk/test/browser/res_206.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_206.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_206.html^headers^ b/netwerk/test/browser/res_206.html^headers^
new file mode 100644
index 0000000000..5a3e3a24c8
--- /dev/null
+++ b/netwerk/test/browser/res_206.html^headers^
@@ -0,0 +1,2 @@
+HTTP 206
+Content-Type: text/html;
diff --git a/netwerk/test/browser/res_206.mp3 b/netwerk/test/browser/res_206.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res_206.mp3
Binary files differ
diff --git a/netwerk/test/browser/res_206.mp3^headers^ b/netwerk/test/browser/res_206.mp3^headers^
new file mode 100644
index 0000000000..6e7e4d23ba
--- /dev/null
+++ b/netwerk/test/browser/res_206.mp3^headers^
@@ -0,0 +1 @@
+HTTP 206
diff --git a/netwerk/test/browser/res_empty.zip b/netwerk/test/browser/res_empty.zip
new file mode 100644
index 0000000000..b613b60c02
--- /dev/null
+++ b/netwerk/test/browser/res_empty.zip
Binary files differ
diff --git a/netwerk/test/browser/res_http_index_format b/netwerk/test/browser/res_http_index_format
new file mode 100644
index 0000000000..e42645a762
--- /dev/null
+++ b/netwerk/test/browser/res_http_index_format
@@ -0,0 +1 @@
+100: This is a sample application/http-index-format directory index listing.
diff --git a/netwerk/test/browser/res_http_index_format^headers^ b/netwerk/test/browser/res_http_index_format^headers^
new file mode 100644
index 0000000000..f076e27a72
--- /dev/null
+++ b/netwerk/test/browser/res_http_index_format^headers^
@@ -0,0 +1 @@
+Content-Type: application/http-index-format
diff --git a/netwerk/test/browser/res_img.png b/netwerk/test/browser/res_img.png
new file mode 100644
index 0000000000..94e7eb6db2
--- /dev/null
+++ b/netwerk/test/browser/res_img.png
Binary files differ
diff --git a/netwerk/test/browser/res_img_for_unknown_decoder b/netwerk/test/browser/res_img_for_unknown_decoder
new file mode 100644
index 0000000000..74d74fde5a
--- /dev/null
+++ b/netwerk/test/browser/res_img_for_unknown_decoder
Binary files differ
diff --git a/netwerk/test/browser/res_img_for_unknown_decoder^headers^ b/netwerk/test/browser/res_img_for_unknown_decoder^headers^
new file mode 100644
index 0000000000..defde38020
--- /dev/null
+++ b/netwerk/test/browser/res_img_for_unknown_decoder^headers^
@@ -0,0 +1,2 @@
+Content-Type:
+Content-Encoding: gzip
diff --git a/netwerk/test/browser/res_img_unknown.png b/netwerk/test/browser/res_img_unknown.png
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_img_unknown.png
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_invalid_partial.mp3 b/netwerk/test/browser/res_invalid_partial.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res_invalid_partial.mp3
Binary files differ
diff --git a/netwerk/test/browser/res_invalid_partial.mp3^headers^ b/netwerk/test/browser/res_invalid_partial.mp3^headers^
new file mode 100644
index 0000000000..0213f38e4e
--- /dev/null
+++ b/netwerk/test/browser/res_invalid_partial.mp3^headers^
@@ -0,0 +1,2 @@
+HTTP 206
+Content-Range: bytes 100-1024/*
diff --git a/netwerk/test/browser/res_nosniff.html b/netwerk/test/browser/res_nosniff.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_nosniff.html^headers^ b/netwerk/test/browser/res_nosniff.html^headers^
new file mode 100644
index 0000000000..024cdcf5ab
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff.html^headers^
@@ -0,0 +1,2 @@
+X-Content-Type-Options: nosniff
+Content-Type: text/html;
diff --git a/netwerk/test/browser/res_nosniff2.html b/netwerk/test/browser/res_nosniff2.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_nosniff2.html^headers^ b/netwerk/test/browser/res_nosniff2.html^headers^
new file mode 100644
index 0000000000..e46db01e23
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff2.html^headers^
@@ -0,0 +1,2 @@
+X-Content-Type-Options: nosniff
+Content-Type: text/test
diff --git a/netwerk/test/browser/res_not_200or206.mp3 b/netwerk/test/browser/res_not_200or206.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res_not_200or206.mp3
Binary files differ
diff --git a/netwerk/test/browser/res_not_200or206.mp3^headers^ b/netwerk/test/browser/res_not_200or206.mp3^headers^
new file mode 100644
index 0000000000..dd0b48aaa0
--- /dev/null
+++ b/netwerk/test/browser/res_not_200or206.mp3^headers^
@@ -0,0 +1 @@
+HTTP 226
diff --git a/netwerk/test/browser/res_not_ok.html b/netwerk/test/browser/res_not_ok.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_not_ok.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_not_ok.html^headers^ b/netwerk/test/browser/res_not_ok.html^headers^
new file mode 100644
index 0000000000..5d15d79e46
--- /dev/null
+++ b/netwerk/test/browser/res_not_ok.html^headers^
@@ -0,0 +1 @@
+HTTP 302 Found
diff --git a/netwerk/test/browser/res_object.html b/netwerk/test/browser/res_object.html
new file mode 100644
index 0000000000..8097415d17
--- /dev/null
+++ b/netwerk/test/browser/res_object.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+ <script>
+ let foo = async () => {
+ let url = "https://example.com/browser/netwerk/test/browser/res_img.png";
+ await fetch(url, { mode: "no-cors" });
+ }
+ foo();
+ </script>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_sub_document.html b/netwerk/test/browser/res_sub_document.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_sub_document.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/square.png b/netwerk/test/browser/square.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/browser/square.png
diff --git a/netwerk/test/browser/test_1629307.html b/netwerk/test/browser/test_1629307.html
new file mode 100644
index 0000000000..01f2a0439e
--- /dev/null
+++ b/netwerk/test/browser/test_1629307.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+ <iframe
+ src="https://example.org/browser/netwerk/test/browser/x_frame_options.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/browser/x_frame_options.html b/netwerk/test/browser/x_frame_options.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/browser/x_frame_options.html
diff --git a/netwerk/test/browser/x_frame_options.html^headers^ b/netwerk/test/browser/x_frame_options.html^headers^
new file mode 100644
index 0000000000..dc4bb949f5
--- /dev/null
+++ b/netwerk/test/browser/x_frame_options.html^headers^
@@ -0,0 +1,3 @@
+HTTP 401 UNAUTHORIZED
+X-Frame-Options: SAMEORIGIN
+WWW-Authenticate: basic realm="login required"
diff --git a/netwerk/test/crashtests/1274044-1.html b/netwerk/test/crashtests/1274044-1.html
new file mode 100644
index 0000000000..cb88e50bcd
--- /dev/null
+++ b/netwerk/test/crashtests/1274044-1.html
@@ -0,0 +1,7 @@
+<script>
+
+var u = new URL("http://127.0.0.1:9607/");
+u.protocol = "resource:";
+u.port = "";
+
+</script>
diff --git a/netwerk/test/crashtests/1334468-1.html b/netwerk/test/crashtests/1334468-1.html
new file mode 100644
index 0000000000..3d94d69949
--- /dev/null
+++ b/netwerk/test/crashtests/1334468-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<!--
+user_pref("privacy.firstparty.isolate", true);
+-->
+<script>
+
+let RESTRICTED_CHARS = "\001\002\003\004\005\006\007" +
+ "\010\011\012\013\014\015\016\017" +
+ "\020\021\022\023\024\025\026\027" +
+ "\030\031\032\033\034\035\036\037" +
+ "/:*?\"<>|\\";
+
+function boom() {
+ for (let c of RESTRICTED_CHARS) {
+ window.location = 'http://s.s' + c;
+ }
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/netwerk/test/crashtests/1399467-1.html b/netwerk/test/crashtests/1399467-1.html
new file mode 100644
index 0000000000..7d16e119d0
--- /dev/null
+++ b/netwerk/test/crashtests/1399467-1.html
@@ -0,0 +1 @@
+<script src='ftp:%'>
diff --git a/netwerk/test/crashtests/1787122.html b/netwerk/test/crashtests/1787122.html
new file mode 100644
index 0000000000..faeb41ef9c
--- /dev/null
+++ b/netwerk/test/crashtests/1787122.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom() {
+ // malformed uri
+ window.location = 'http://a...example.com';
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/netwerk/test/crashtests/1793521.html b/netwerk/test/crashtests/1793521.html
new file mode 100644
index 0000000000..5bd19df01d
--- /dev/null
+++ b/netwerk/test/crashtests/1793521.html
@@ -0,0 +1 @@
+<iframe src='http://&#xD49&#x44&#x2D'></iframe>
diff --git a/netwerk/test/crashtests/675518.html b/netwerk/test/crashtests/675518.html
new file mode 100644
index 0000000000..44a43570e4
--- /dev/null
+++ b/netwerk/test/crashtests/675518.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script>
+function boom()
+{
+ var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ frame.src = "javascript:'<html><body>1</body></html>';";
+ document.body.appendChild(frame);
+ var frameWin = frame.contentWindow;
+
+ var resizeListener = function() {
+ frameWin.removeEventListener("resize", resizeListener, false);
+ frameWin.document.write("3...");
+ };
+ frameWin.addEventListener("resize", resizeListener, false);
+
+ frameWin.document.write("2...");
+}
+
+</script>
+<body onload="boom();"></body>
+</html>
diff --git a/netwerk/test/crashtests/785753-1.html b/netwerk/test/crashtests/785753-1.html
new file mode 100644
index 0000000000..9c8e06bba8
--- /dev/null
+++ b/netwerk/test/crashtests/785753-1.html
@@ -0,0 +1,253 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 0.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<title>Test for importing styles via incorrect link element</title>
+<link type="text/css" href="data:text/css;charset=utf-8,p#one%-32519279132875%7Bbackground-color%32769A%340282366920938463463374607431768211436red%1B%7D%0D%0A"/>
+<link rel="stylesheet" href="data:text/css;charset=utf-16,p#two%1%7Bbackground-color%65535A%4294967297lime%3B%7D%0D%0A"/>
+</head>
+<link href="data:text/css;charset=utf-8,p#three%1%7Bbackground-color%3A%20red%3B%7D%0D%0A"/>
+<link type="text/css" rel="stylesheet" href="data:text/css;charset=utf-8,p#four%32767%7Bbackground-color%2147483649A%20lime%257B%7D%0D%0A"/>
+</head>
+<body>
+<p id="one">This line should not have red background</p>
+<p id="two">This line should have lime background</p>
+<p id="three">This line should not have red background</p>
+<p id="four">This line should have lime background</p>
+</body>
+<script type="text/javascript">
+ function alert(msg){}; function confirm(msg){}; function prompt(msg){};
+ try{ document.head.appendChild(document.createElement("style"));}catch(e){}
+ var styleSheet = document.styleSheets[document.styleSheets.length-1];
+try{if(styleSheet.length===undefined){styleSheet.insertRule(":root{}",0); styleSheet.disabled=false}
+
+styleSheet.insertRule("body {counter-reset:c}",0)}catch(e){}
+var styleSheet0 = document.styleSheets[0];
+var styleSheet1 = document.styleSheets[1];
+var styleSheet2 = document.styleSheets[2];
+var test0=document.getElementById("four")
+var test1=document.getElementById("one")
+var test2=document.getElementById("two")
+var test3=document.getElementById("three")
+setTimeout(function(){
+try{test0.style['padding-top']='32px';}catch(e){}
+try{test2.insertBefore(test3);}catch(e){}
+try{test0.style.setProperty('background-image','url(data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7)','important');}catch(e){}
+try{test3.style['line-height']='-324px';}catch(e){}
+try{test0.style.setProperty('border-image-repeat','repeat','important');}catch(e){}
+},3);
+
+setTimeout(function(){
+try{test1.style['line-height']='43px';}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined{clear:right; }",styleSheet0.cssRules.length);}catch(e){}
+try{test2.style.setProperty('bottom','inherit','important');}catch(e){}
+try{test3.remove()}catch(e){};
+try{test0.style.setProperty('overflow-x','no-content','important');}catch(e){}
+},0);
+
+setTimeout(function(){
+try{styleSheet0.insertRule(".undefined,.undefined{font-size:40px; overflow-x:no-content; transition-duration:-5.408991568721831s; column-span:483; }",styleSheet0.cssRules.length);}catch(e){}
+try{test0.remove()}catch(e){};
+try{test0.insertBefore(test2);}catch(e){}
+try{test3.style.setProperty('bottom','inherit','important');}catch(e){}
+try{test2.style.setProperty('background-origin','border-box','important');}catch(e){}
+},2);
+
+setTimeout(function(){
+window.resizeTo(1018,353)
+try{test0.insertBefore(test1);}catch(e){}
+try{test1.style.setProperty('bottom','auto','important');}catch(e){}
+try{test1.style.setProperty('z-index','inherit','important');}catch(e){}
+window.resizeTo(1018,353)
+},1);
+
+setTimeout(function(){
+try{test0.innerHtml=test3.innerHtml;}catch(e){}
+document.body.style.setProperty('-webkit-filter','blur(18px)','null')
+try{test3.remove()}catch(e){};
+try{test0.style.setProperty('padding-right','18px','important');}catch(e){}
+try{test3.style.setProperty('border-bottom-color','rgb(96%,328%,106)','important');}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test2.innerHtml=test0.innerHtml;}catch(e){}
+try{styleSheet1.insertRule(".undefined,.undefined{list-style-type:sidama; background-clip:border-box; overflow-x:scroll; border-bottom-left-radius:70px; text-transform:uppercase; empty-cells:inherit; }",styleSheet1.cssRules.length);}catch(e){}
+try{styleSheet1.insertRule(".undefined:active {min-width:759; }",styleSheet1.cssRules.length);}catch(e){}
+try{test1.style.setProperty('top','343','important');}catch(e){}
+try{test2.replaceChild(test0,test2.firstChild)}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined,.undefined{background-attachment:inherit; flood-color:rgba(93%,364%,104,4.471563883125782); }",0);}catch(e){}
+try{test1.style.setProperty('font-style','oblique','important');}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{border-right-style:double; }",styleSheet0.cssRules.length);}catch(e){}
+document.execCommand("SelectAll", true);
+try{test3.remove()}catch(e){};
+},1);
+
+setTimeout(function(){
+try{test1.remove()}catch(e){};
+try{test0.innerHtml=test2.innerHtml;}catch(e){}
+try{test1.remove()}catch(e){};
+try{test0.remove()}catch(e){};
+try{test2.innerHtml=test2.innerHtml;}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test0.appendChild(test1);}catch(e){}
+try{test1.style['position']='inherit';}catch(e){}
+try{test2.replaceChild(test3,test2.lastChild)}catch(e){}
+try{test1.style['border-left-color']='#6D8997';}catch(e){}
+try{test1.innerHtml=test3.innerHtml;}catch(e){}
+},6);
+
+setTimeout(function(){
+try{test2.insertBefore(test1);}catch(e){}
+try{test2.innerHtml=test3.innerHtml;}catch(e){}
+try{test0.appendChild(test2);}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined{resize:both; background-color:#A22225; position:relative; column-width:auto; letter-spacing:361px; border-top-width:151%; }",styleSheet0.cssRules.length);}catch(e){}
+document.body.style.zoom=1.8764980849809945
+},6);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined{outline-color:rgba(126,179,46,0.8964905887842178); width:183; }",styleSheet1.cssRules.length);}catch(e){}
+try{test2.insertBefore(test3);}catch(e){}
+try{test1.innerHtml=test3.innerHtml;}catch(e){}
+try{styleSheet0.insertRule("article,footer,article,article{border-bottom-right-radius:7px; }",0);}catch(e){}
+try{test1.insertBefore(test0);}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test0.remove()}catch(e){};
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined,.undefined{display: table-header-group; content: counter(c, ethiopic); counter-increment:c;}",0);}catch(e){}
+try{test0.style.setProperty('background-color','#8897D3','important');}catch(e){}
+try{test3.appendChild(test0);}catch(e){}
+try{styleSheet0.insertRule("hgroup,hgroup,hgroup{outline-color:rgba(167,242,90%,-0.10827295063063502); }",styleSheet0.cssRules.length);}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test3.style.setProperty('border-bottom-color','#55D7F6','important');}catch(e){}
+try{test1.remove()}catch(e){};
+try{test2.insertBefore(test1);}catch(e){}
+try{test2.innerHtml=test0.innerHtml;}catch(e){}
+try{styleSheet1.insertRule("#one,#one,#three,#three{background-clip:border-box; border-top-width:85em; }",0);}catch(e){}
+},5);
+
+setTimeout(function(){
+try{test3.appendChild(test0);}catch(e){}
+try{test2.innerHtml=test1.innerHtml;}catch(e){}
+try{test2.style['background-attachment']='inherit';}catch(e){}
+try{test3.style['clip']='inherit';}catch(e){}
+try{test3.remove()}catch(e){};
+},3);
+
+setTimeout(function(){
+try{test1.remove()}catch(e){};
+try{styleSheet0.insertRule(".undefined,.undefined{height:424; }",styleSheet0.cssRules.length);}catch(e){}
+try{test2.style.setProperty('border-top-style','solid','important');}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined,.undefined{page-break-inside:left; border-image-slice:fill; border-left-width:184pc; }",styleSheet0.cssRules.length);}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined{margin-left:-105px; text-transform:inherit; box-sizing:border-box; }",styleSheet0.cssRules.length);}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet0.insertRule("param,img,img,param{left:auto; background-clip:padding-box; }",styleSheet0.cssRules.length);}catch(e){}
+try{test2.style.setProperty('min-height','605','important');}catch(e){}
+try{test2.remove()}catch(e){};
+try{styleSheet1.insertRule(".undefined::first-line, #two::first-line {border-bottom-width:69pt; lighting-color:rgba(75%,166,81,-0.8728196211159229); text-shadow:85px 459px #2F1; }",styleSheet1.cssRules.length);}catch(e){}
+try{test3.appendChild(document.createTextNode(unescape("!F幓[")))}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet0.insertRule("#two,#four{font-style:italic; list-style-position:inside; border-collapse:inherit; word-wrap:break-word; text-transform:uppercase; }",styleSheet0.cssRules.length);}catch(e){}
+try{test1.style.setProperty('border-bottom-right-radius','2px','important');}catch(e){}
+try{test3.style['text-shadow']='58px 64px rgba(61%,60,199,0.03203143551945686)';}catch(e){}
+try{styleSheet1.insertRule("dir,dir,nav{display: inline-table; content: counter(c, upper-greek); counter-increment:c;}",styleSheet1.cssRules.length);}catch(e){}
+try{styleSheet0.insertRule("#one:target, #three:after {color:#D9B; outline-style:hidden; flood-color:rgba(22,59%,99%,-0.008097740123048425); }",0);}catch(e){}
+},6);
+
+setTimeout(function(){
+try{test2.remove()}catch(e){};
+document.execCommand("Copy", true);
+try{test3.style['letter-spacing']='102px';}catch(e){}
+try{test2.remove()}catch(e){};
+try{test3.appendChild(test0);}catch(e){}
+},5);
+
+setTimeout(function(){
+try{test0.style['text-transform']='inherit';}catch(e){}
+try{test0.style['word-break']='hyphenate';}catch(e){}
+try{test3.insertBefore(test2);}catch(e){}
+try{test2.style.setProperty('bottom','410','important');}catch(e){}
+try{test1.style['background']='url(data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7)';}catch(e){}
+},5);
+
+setTimeout(function(){
+try{test2.appendChild(test2);}catch(e){}
+try{test1.appendChild(test2);}catch(e){}
+try{test2.appendChild(document.createTextNode(unescape("zn!쎔gw눢fb¤£kꄍ£3wa02fnpå0!äwC䰴頥!!a")))}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{display: inline; content: counter(c, khmer); counter-increment:c;}",0);}catch(e){}
+try{test3.style.setProperty('line-height','42in','important');}catch(e){}
+},7);
+
+setTimeout(function(){
+window.moveBy(133,126)
+try{test0.style['padding-right']='20px';}catch(e){}
+try{test1.replaceChild(test2,test1.firstChild)}catch(e){}
+try{test1.style.setProperty('letter-spacing','120px','important');}catch(e){}
+try{test1.style.setProperty('height','57','important');}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined:nth-child(even), #one:nth-last-child(even) {margin-top:-241cm; font-size:23px; }",0);}catch(e){}
+try{styleSheet0.insertRule(".undefined:nth-child(even), #three:default {stop-color:rgba(92%,-201%,31,0.8133529485203326); lighting-color:#E31; }",styleSheet0.cssRules.length);}catch(e){}
+try{test1.style.setProperty('border-top-left-radius','63px','important');}catch(e){}
+try{test0.style['letter-spacing']='36px';}catch(e){}
+try{test1.appendChild(document.createTextNode(unescape("1u£Fⶵ隗(籬fsä⍉㯗cሮ銐k䆴n#蹹圭篺(1w馁")))}catch(e){}
+},7);
+
+setTimeout(function(){
+try{test1.appendChild(test3);}catch(e){}
+try{test2.style.setProperty('lighting-color','rgb(4,36%,95%)','important');}catch(e){}
+try{test0.style.setProperty('border-right-width','71pt','important');}catch(e){}
+try{test2.style['box-shadow']='-228px , 86px , 9px , #F0A134';}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{left:inherit; }",styleSheet0.cssRules.length);}catch(e){}
+},7);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined,.undefined{min-height:277; transition-property:none; }",0);}catch(e){}
+try{test0.style.setProperty('word-wrap','break-word','important');}catch(e){}
+try{test1.style.setProperty('top','inherit','important');}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{overflow-x:visible; border-bottom-left-radius:844488.660965659px; }",0);}catch(e){}
+try{test2.style.setProperty('margin-bottom','auto','important');}catch(e){}
+},0);
+
+setTimeout(function(){
+try{styleSheet1.insertRule("hgroup,hgroup,dl,dl{display: list-item; content: counter(c, hebrew); counter-increment:c;}",styleSheet1.cssRules.length);}catch(e){}
+try{test0.style.setProperty('border-image-repeat','repeat','important');}catch(e){}
+try{styleSheet0.insertRule("figure,footer,figure,table{-moz-border-image-repeat:stretch; word-wrap:normal; border-right-color:rgb(4%,19%,6%); caption-side:top; stop-color:rgba(450%,226,14%,1.5385327017866075); }",styleSheet0.cssRules.length);}catch(e){}
+try{test3.innerHtml=test2.innerHtml;}catch(e){}
+try{test2.appendChild(test0);}catch(e){}
+},0);
+
+setTimeout(function(){
+try{test1.replaceChild(test0,test1.lastChild)}catch(e){}
+try{test2.style.setProperty('border-collapse','inherit','important');}catch(e){}
+try{test1.style['overflow-x']='visible';}catch(e){}
+try{test1.style['text-indent']='-30.283706605434418cm';}catch(e){}
+try{styleSheet0.insertRule("tt,hgroup{stroke-width:-439px; box-sizing:border-box; }",styleSheet0.cssRules.length);}catch(e){}
+},1);
+
+setTimeout(function(){
+styleSheet0.disabled=true
+styleSheet1.disabled=false
+styleSheet1.disabled=true
+document.body.style.setProperty('-webkit-filter','invert(338%)','null')
+window.moveBy(302,115)
+},0);
+
+setTimeout(function(){
+window.blur()
+},4);
+
+</script>
+
+</html>
diff --git a/netwerk/test/crashtests/785753-2.html b/netwerk/test/crashtests/785753-2.html
new file mode 100644
index 0000000000..15a9865388
--- /dev/null
+++ b/netwerk/test/crashtests/785753-2.html
@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="data:text/css;charset=utf-16,a"/>
+
+<link rel="stylesheet" href="data:text/css;charset=utf-16,p#two%1%7Bbackground-color%65535A%4294967297lime%3B%7D%0D%0A"/> \ No newline at end of file
diff --git a/netwerk/test/crashtests/crashtests.list b/netwerk/test/crashtests/crashtests.list
new file mode 100644
index 0000000000..a48c1e3c5a
--- /dev/null
+++ b/netwerk/test/crashtests/crashtests.list
@@ -0,0 +1,8 @@
+skip load 675518.html
+load 785753-1.html
+load 785753-2.html
+load 1274044-1.html
+skip-if(ThreadSanitizer) skip-if(Android) pref(privacy.firstparty.isolate,true) load 1334468-1.html # Bug 1639080
+load 1399467-1.html
+load 1793521.html
+load 1787122.html
diff --git a/netwerk/test/fuzz/FuzzingStreamListener.cpp b/netwerk/test/fuzz/FuzzingStreamListener.cpp
new file mode 100644
index 0000000000..878b116c5b
--- /dev/null
+++ b/netwerk/test/fuzz/FuzzingStreamListener.cpp
@@ -0,0 +1,44 @@
+#include "FuzzingInterface.h"
+#include "FuzzingStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(FuzzingStreamListener, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+FuzzingStreamListener::OnStartRequest(nsIRequest* aRequest) {
+ FUZZING_LOG(("FuzzingStreamListener::OnStartRequest"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingStreamListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ FUZZING_LOG(("FuzzingStreamListener::OnDataAvailable"));
+ static uint32_t const kCopyChunkSize = 128 * 1024;
+ uint32_t toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+ nsCString data;
+
+ while (aCount) {
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, toRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aCount -= toRead;
+ toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingStreamListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ FUZZING_LOG(("FuzzingStreamListener::OnStopRequest"));
+ mChannelDone = true;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/fuzz/FuzzingStreamListener.h b/netwerk/test/fuzz/FuzzingStreamListener.h
new file mode 100644
index 0000000000..86f60ed102
--- /dev/null
+++ b/netwerk/test/fuzz/FuzzingStreamListener.h
@@ -0,0 +1,37 @@
+#ifndef FuzzingStreamListener_h__
+#define FuzzingStreamListener_h__
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+#include "nsIStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+class FuzzingStreamListener final : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ FuzzingStreamListener() = default;
+
+ void waitUntilDone() {
+ SpinEventLoopUntil("net::FuzzingStreamListener::waitUntilDone"_ns,
+ [&]() { return mChannelDone; });
+ }
+
+ bool isDone() { return mChannelDone; }
+
+ private:
+ ~FuzzingStreamListener() = default;
+ bool mChannelDone = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/test/fuzz/TestHttpFuzzing.cpp b/netwerk/test/fuzz/TestHttpFuzzing.cpp
new file mode 100644
index 0000000000..e717b608e7
--- /dev/null
+++ b/netwerk/test/fuzz/TestHttpFuzzing.cpp
@@ -0,0 +1,297 @@
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SpinEventLoopUntil.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsILoadInfo.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsIOService.h"
+#include "nsProtocolProxyService.h"
+#include "nsScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetUtil.h"
+#include "NullPrincipal.h"
+#include "nsCycleCollector.h"
+#include "RequestContextService.h"
+#include "nsSandboxFlags.h"
+
+#include "FuzzingInterface.h"
+#include "FuzzingStreamListener.h"
+#include "FuzzyLayer.h"
+
+namespace mozilla {
+namespace net {
+
+// Target spec and optional proxy type to use, set by the respective
+// initialization function so we can cover all combinations.
+static nsAutoCString httpSpec;
+static nsAutoCString proxyType;
+static size_t minSize;
+
+static int FuzzingInitNetworkHttp(int* argc, char*** argv) {
+ Preferences::SetBool("network.dns.native-is-localhost", true);
+ Preferences::SetBool("fuzzing.necko.enabled", true);
+ Preferences::SetInt("network.http.speculative-parallel-limit", 0);
+ Preferences::SetInt("network.http.http2.default-concurrent", 1);
+
+ if (httpSpec.IsEmpty()) {
+ httpSpec = "http://127.0.0.1/";
+ }
+
+ net_EnsurePSMInit();
+
+ return 0;
+}
+
+static int FuzzingInitNetworkHttp2(int* argc, char*** argv) {
+ httpSpec = "https://127.0.0.1/";
+ return FuzzingInitNetworkHttp(argc, argv);
+}
+
+static int FuzzingInitNetworkHttp3(int* argc, char*** argv) {
+ Preferences::SetBool("fuzzing.necko.http3", true);
+ Preferences::SetBool("network.http.http3.enable", true);
+ Preferences::SetCString("network.http.http3.alt-svc-mapping-for-testing",
+ "fuzz.bad.tld;h3=:443");
+ httpSpec = "https://fuzz.bad.tld/";
+ minSize = 1200;
+ return FuzzingInitNetworkHttp(argc, argv);
+}
+
+static int FuzzingInitNetworkHttpProxyHttp2(int* argc, char*** argv) {
+ // This is http over an https proxy
+ proxyType = "https";
+
+ return FuzzingInitNetworkHttp(argc, argv);
+}
+
+static int FuzzingInitNetworkHttp2ProxyHttp2(int* argc, char*** argv) {
+ // This is https over an https proxy
+ proxyType = "https";
+
+ return FuzzingInitNetworkHttp2(argc, argv);
+}
+
+static int FuzzingInitNetworkHttpProxyPlain(int* argc, char*** argv) {
+ // This is http over an http proxy
+ proxyType = "http";
+
+ return FuzzingInitNetworkHttp(argc, argv);
+}
+
+static int FuzzingInitNetworkHttp2ProxyPlain(int* argc, char*** argv) {
+ // This is https over an http proxy
+ proxyType = "http";
+
+ return FuzzingInitNetworkHttp2(argc, argv);
+}
+
+static int FuzzingRunNetworkHttp(const uint8_t* data, size_t size) {
+ if (size < minSize) {
+ return 0;
+ }
+
+ // Set the data to be processed
+ addNetworkFuzzingBuffer(data, size);
+
+ nsWeakPtr channelRef;
+
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ mozilla::net::RequestContextService::GetOrCreate();
+ uint64_t rcID;
+
+ {
+ nsCOMPtr<nsIURI> url;
+ nsresult rv;
+
+ if (NS_NewURI(getter_AddRefs(url), httpSpec) != NS_OK) {
+ MOZ_CRASH("Call to NS_NewURI failed.");
+ }
+
+ nsLoadFlags loadFlags;
+ loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_FRESH_CONNECTION |
+ nsIChannel::LOAD_INITIAL_DOCUMENT_URI;
+ nsSecurityFlags secFlags;
+ secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+ uint32_t sandboxFlags = SANDBOXED_ORIGIN;
+
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsILoadInfo> loadInfo;
+
+ if (!proxyType.IsEmpty()) {
+ nsAutoCString proxyHost("127.0.0.2");
+
+ nsCOMPtr<nsIProtocolProxyService2> ps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CID);
+ if (!ps) {
+ MOZ_CRASH("Failed to create nsIProtocolProxyService2");
+ }
+
+ mozilla::net::nsProtocolProxyService* pps =
+ static_cast<mozilla::net::nsProtocolProxyService*>(ps.get());
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ rv = pps->NewProxyInfo(proxyType, proxyHost, 443,
+ ""_ns, // aProxyAuthorizationHeader
+ ""_ns, // aConnectionIsolationKey
+ 0, // aFlags
+ UINT32_MAX, // aFailoverTimeout
+ nullptr, // aFailoverProxy
+ getter_AddRefs(proxyInfo));
+
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Call to NewProxyInfo failed.");
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("do_GetIOService failed.");
+ }
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("GetProtocolHandler failed.");
+ }
+
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("do_QueryInterface failed.");
+ }
+
+ loadInfo = new LoadInfo(
+ nsContentUtils::GetSystemPrincipal(), // loading principal
+ nsContentUtils::GetSystemPrincipal(), // triggering principal
+ nullptr, // Context
+ secFlags, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
+ Maybe<mozilla::dom::ClientInfo>(),
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(), sandboxFlags);
+
+ rv = pph->NewProxiedChannel(url, proxyInfo,
+ 0, // aProxyResolveFlags
+ nullptr, // aProxyURI
+ loadInfo, getter_AddRefs(channel));
+
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Call to newProxiedChannel failed.");
+ }
+ } else {
+ rv = NS_NewChannel(getter_AddRefs(channel), url,
+ nsContentUtils::GetSystemPrincipal(), secFlags,
+ nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
+ nullptr, // aCookieJarSettings
+ nullptr, // aPerformanceStorage
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ loadFlags, // aLoadFlags
+ nullptr, // aIoService
+ sandboxFlags);
+
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Call to NS_NewChannel failed.");
+ }
+
+ loadInfo = channel->LoadInfo();
+ }
+
+ if (NS_FAILED(loadInfo->SetSkipContentSniffing(true))) {
+ MOZ_CRASH("Failed to call SetSkipContentSniffing");
+ }
+
+ RefPtr<FuzzingStreamListener> gStreamListener;
+ nsCOMPtr<nsIHttpChannel> gHttpChannel;
+
+ gHttpChannel = do_QueryInterface(channel);
+ rv = gHttpChannel->SetRequestMethod("GET"_ns);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("SetRequestMethod on gHttpChannel failed.");
+ }
+
+ nsCOMPtr<nsIRequestContext> rc;
+ rv = rcsvc->NewRequestContext(getter_AddRefs(rc));
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("NewRequestContext failed.");
+ }
+ rcID = rc->GetID();
+
+ rv = gHttpChannel->SetRequestContextID(rcID);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("SetRequestContextID on gHttpChannel failed.");
+ }
+
+ if (!proxyType.IsEmpty()) {
+ // NewProxiedChannel doesn't allow us to pass loadFlags directly
+ rv = gHttpChannel->SetLoadFlags(loadFlags);
+ if (rv != NS_OK) {
+ MOZ_CRASH("SetRequestMethod on gHttpChannel failed.");
+ }
+ }
+
+ gStreamListener = new FuzzingStreamListener();
+ gHttpChannel->AsyncOpen(gStreamListener);
+
+ // Wait for StopRequest
+ gStreamListener->waitUntilDone();
+
+ bool mainPingBack = false;
+
+ NS_DispatchBackgroundTask(NS_NewRunnableFunction("Dummy", [&]() {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("Dummy", [&]() { mainPingBack = true; }));
+ }));
+
+ SpinEventLoopUntil("FuzzingRunNetworkHttp(mainPingBack)"_ns,
+ [&]() -> bool { return mainPingBack; });
+
+ channelRef = do_GetWeakReference(gHttpChannel);
+ }
+
+ // Wait for the channel to be destroyed
+ SpinEventLoopUntil(
+ "FuzzingRunNetworkHttp(channel == nullptr)"_ns, [&]() -> bool {
+ nsCycleCollector_collect(CCReason::API, nullptr);
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(channelRef);
+ return channel == nullptr;
+ });
+
+ if (!signalNetworkFuzzingDone()) {
+ // Wait for the connection to indicate closed
+ SpinEventLoopUntil("FuzzingRunNetworkHttp(gFuzzingConnClosed)"_ns,
+ [&]() -> bool { return gFuzzingConnClosed; });
+ }
+
+ rcsvc->RemoveRequestContext(rcID);
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkHttp, FuzzingRunNetworkHttp,
+ NetworkHttp);
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkHttp2, FuzzingRunNetworkHttp,
+ NetworkHttp2);
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkHttp3, FuzzingRunNetworkHttp,
+ NetworkHttp3);
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkHttp2ProxyHttp2,
+ FuzzingRunNetworkHttp, NetworkHttp2ProxyHttp2);
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkHttpProxyHttp2,
+ FuzzingRunNetworkHttp, NetworkHttpProxyHttp2);
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkHttpProxyPlain,
+ FuzzingRunNetworkHttp, NetworkHttpProxyPlain);
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkHttp2ProxyPlain,
+ FuzzingRunNetworkHttp, NetworkHttp2ProxyPlain);
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/fuzz/TestJARFuzzing.cpp b/netwerk/test/fuzz/TestJARFuzzing.cpp
new file mode 100644
index 0000000000..3d27a5bf7d
--- /dev/null
+++ b/netwerk/test/fuzz/TestJARFuzzing.cpp
@@ -0,0 +1,187 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include <iostream>
+
+#include "FuzzingInterface.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIZipReader.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "mozilla/Span.h"
+#include "mozilla/Unused.h"
+#include "nsIInputStream.h"
+#include "nsIStringEnumerator.h"
+
+enum FuzzMethodType {
+ eTest = 0,
+ eGetEntry,
+ eHasEntry,
+ eFindEntries,
+ eInputStream,
+ eOpenInner,
+ eLastMethod,
+};
+static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
+
+template <typename T>
+T jar_get_num(char** buf, size_t* size) {
+ if (sizeof(T) > *size) {
+ return 0;
+ }
+
+ T* iptr = reinterpret_cast<T*>(*buf);
+ *buf += sizeof(T);
+ *size -= sizeof(T);
+ return *iptr;
+}
+
+nsAutoCString jar_get_string(char** buf, size_t* size) {
+ uint8_t len = jar_get_num<uint8_t>(buf, size);
+ if (len > *size) {
+ len = static_cast<uint8_t>(*size);
+ }
+ nsAutoCString str(*buf, len);
+
+ *buf += len;
+ *size -= len;
+ return str;
+}
+
+nsresult FuzzEntries(char** buf, size_t* size, nsIZipReader* aReader,
+ const nsACString& aName) {
+ uint8_t iters = jar_get_num<uint8_t>(buf, size);
+ nsresult rv;
+ for (uint8_t i = 0; i < iters; ++i) {
+ nsAutoCString out;
+ uint64_t written;
+ nsCOMPtr<nsIZipEntry> entry;
+ nsCOMPtr<nsIInputStream> stream;
+
+ switch (jar_get_num<uint8_t>(buf, size) % eLastMethod) {
+ case eTest: {
+ rv = aReader->Test(aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case eGetEntry: {
+ rv = aReader->GetEntry(aName, getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case eHasEntry: {
+ bool has = false;
+ rv = aReader->HasEntry(aName, &has);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case eInputStream:
+ rv = aReader->GetInputStream(aName, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ rv = NS_ReadInputStreamToString(stream, out, -1, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ default:
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult FuzzReader(char** buf, size_t* size, nsIZipReader* aReader) {
+ nsresult rv;
+ nsAutoCString name;
+ nsCOMPtr<nsIZipEntry> entry;
+ bool has = false;
+ nsAutoCString pattern;
+ nsCOMPtr<nsIUTF8StringEnumerator> enumerator;
+ nsCOMPtr<nsIInputStream> stream;
+ bool hasMore;
+ nsAutoCString out;
+ uint64_t written;
+ nsCOMPtr<nsIZipReader> newReader = do_CreateInstance(kZipReaderCID, &rv);
+ switch (jar_get_num<uint8_t>(buf, size) % eLastMethod) {
+ case eTest:
+ rv = aReader->Test(""_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case eGetEntry:
+ name = jar_get_string(buf, size);
+ rv = aReader->GetEntry(name, getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case eHasEntry:
+ name = jar_get_string(buf, size);
+ rv = aReader->HasEntry(name, &has);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case eFindEntries:
+ pattern = jar_get_string(buf, size);
+ rv = aReader->FindEntries(pattern, getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (NS_SUCCEEDED(enumerator->HasMore(&hasMore)) && hasMore) {
+ if (NS_FAILED(enumerator->GetNext(name))) {
+ break;
+ }
+ rv = FuzzEntries(buf, size, aReader, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ break;
+ case eInputStream:
+ name = jar_get_string(buf, size);
+ rv = aReader->GetInputStream(name, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_ReadInputStreamToString(stream, out, -1, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case eOpenInner:
+ name = jar_get_string(buf, size);
+ rv = newReader->OpenInner(aReader, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = FuzzReader(buf, size, newReader);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ default:
+ break;
+ }
+ return rv;
+}
+
+static int FuzzingRunJARParser(const uint8_t* data, size_t size) {
+ char* buf = (char*)data;
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString jardata = jar_get_string(&buf, &size);
+
+ nsCOMPtr<nsIZipReader> reader = do_CreateInstance(kZipReaderCID, &rv);
+ rv = reader->OpenMemory((void*)jardata.get(), jardata.Length());
+ NS_ENSURE_SUCCESS(rv, 0);
+
+#if 0
+ // For easily exporting the last test case that triggered a crash.
+ FILE * f = fopen("/tmp/input.jar", "wb");
+ fwrite((void*)jardata.get(), 1, jardata.Length(), f);
+ fclose(f);
+#endif
+
+ uint8_t iters = jar_get_num<uint8_t>(&buf, &size);
+ for (uint8_t i = 0; i < iters; ++i) {
+ rv = FuzzReader(&buf, &size, reader);
+ NS_ENSURE_SUCCESS(rv, 0);
+ }
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(nullptr, FuzzingRunJARParser, JARParser);
diff --git a/netwerk/test/fuzz/TestURIFuzzing.cpp b/netwerk/test/fuzz/TestURIFuzzing.cpp
new file mode 100644
index 0000000000..312a0d2db5
--- /dev/null
+++ b/netwerk/test/fuzz/TestURIFuzzing.cpp
@@ -0,0 +1,238 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include <iostream>
+
+#include "FuzzingInterface.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIURL.h"
+#include "nsIStandardURL.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Span.h"
+#include "mozilla/Unused.h"
+
+template <typename T>
+T get_numeric(char** buf, size_t* size) {
+ if (sizeof(T) > *size) {
+ return 0;
+ }
+
+ T* iptr = reinterpret_cast<T*>(*buf);
+ *buf += sizeof(T);
+ *size -= sizeof(T);
+ return *iptr;
+}
+
+nsAutoCString get_string(char** buf, size_t* size) {
+ uint8_t len = get_numeric<uint8_t>(buf, size);
+ if (len > *size) {
+ len = static_cast<uint8_t>(*size);
+ }
+ nsAutoCString str(*buf, len);
+
+ *buf += len;
+ *size -= len;
+ return str;
+}
+
+const char* charsets[] = {
+ "Big5", "EUC-JP", "EUC-KR", "gb18030",
+ "gbk", "IBM866", "ISO-2022-JP", "ISO-8859-10",
+ "ISO-8859-13", "ISO-8859-14", "ISO-8859-15", "ISO-8859-16",
+ "ISO-8859-2", "ISO-8859-3", "ISO-8859-4", "ISO-8859-5",
+ "ISO-8859-6", "ISO-8859-7", "ISO-8859-8", "ISO-8859-8-I",
+ "KOI8-R", "KOI8-U", "macintosh", "replacement",
+ "Shift_JIS", "UTF-16BE", "UTF-16LE", "UTF-8",
+ "windows-1250", "windows-1251", "windows-1252", "windows-1253",
+ "windows-1254", "windows-1255", "windows-1256", "windows-1257",
+ "windows-1258", "windows-874", "x-mac-cyrillic", "x-user-defined"};
+
+static int FuzzingRunURIParser(const uint8_t* data, size_t size) {
+ char* buf = (char*)data;
+
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString spec = get_string(&buf, &size);
+
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), spec);
+
+ if (NS_FAILED(rv)) {
+ return 0;
+ }
+
+ uint8_t iters = get_numeric<uint8_t>(&buf, &size);
+ for (int i = 0; i < iters; i++) {
+ if (get_numeric<uint8_t>(&buf, &size) % 25 != 0) {
+ NS_MutateURI mutator(uri);
+ nsAutoCString acdata = get_string(&buf, &size);
+
+ switch (get_numeric<uint8_t>(&buf, &size) % 12) {
+ default:
+ mutator.SetSpec(acdata);
+ break;
+ case 1:
+ mutator.SetScheme(acdata);
+ break;
+ case 2:
+ mutator.SetUserPass(acdata);
+ break;
+ case 3:
+ mutator.SetUsername(acdata);
+ break;
+ case 4:
+ mutator.SetPassword(acdata);
+ break;
+ case 5:
+ mutator.SetHostPort(acdata);
+ break;
+ case 6:
+ // Called via SetHostPort
+ mutator.SetHost(acdata);
+ break;
+ case 7:
+ // Called via multiple paths
+ mutator.SetPathQueryRef(acdata);
+ break;
+ case 8:
+ mutator.SetRef(acdata);
+ break;
+ case 9:
+ mutator.SetFilePath(acdata);
+ break;
+ case 10:
+ mutator.SetQuery(acdata);
+ break;
+ case 11: {
+ const uint8_t index = get_numeric<uint8_t>(&buf, &size) %
+ (sizeof(charsets) / sizeof(char*));
+ const char* charset = charsets[index];
+ auto encoding = mozilla::Encoding::ForLabelNoReplacement(
+ mozilla::MakeStringSpan(charset));
+ mutator.SetQueryWithEncoding(acdata, encoding);
+ break;
+ }
+ }
+
+ nsresult rv = mutator.Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return 0;
+ }
+ } else {
+ nsAutoCString out;
+
+ if (uri) {
+ switch (get_numeric<uint8_t>(&buf, &size) % 26) {
+ default:
+ uri->GetSpec(out);
+ break;
+ case 1:
+ uri->GetPrePath(out);
+ break;
+ case 2:
+ uri->GetScheme(out);
+ break;
+ case 3:
+ uri->GetUserPass(out);
+ break;
+ case 4:
+ uri->GetUsername(out);
+ break;
+ case 5:
+ uri->GetPassword(out);
+ break;
+ case 6:
+ uri->GetHostPort(out);
+ break;
+ case 7:
+ uri->GetHost(out);
+ break;
+ case 8: {
+ int rv;
+ uri->GetPort(&rv);
+ break;
+ }
+ case 9:
+ uri->GetPathQueryRef(out);
+ break;
+ case 10: {
+ nsCOMPtr<nsIURI> other;
+ bool rv;
+ nsAutoCString spec = get_string(&buf, &size);
+ NS_NewURI(getter_AddRefs(other), spec);
+ uri->Equals(other, &rv);
+ break;
+ }
+ case 11: {
+ nsAutoCString scheme = get_string(&buf, &size);
+ bool rv;
+ uri->SchemeIs("https", &rv);
+ break;
+ }
+ case 12: {
+ nsAutoCString in = get_string(&buf, &size);
+ uri->Resolve(in, out);
+ break;
+ }
+ case 13:
+ uri->GetAsciiSpec(out);
+ break;
+ case 14:
+ uri->GetAsciiHostPort(out);
+ break;
+ case 15:
+ uri->GetAsciiHost(out);
+ break;
+ case 16:
+ uri->GetRef(out);
+ break;
+ case 17: {
+ nsCOMPtr<nsIURI> other;
+ bool rv;
+ nsAutoCString spec = get_string(&buf, &size);
+ NS_NewURI(getter_AddRefs(other), spec);
+ uri->EqualsExceptRef(other, &rv);
+ break;
+ }
+ case 18:
+ uri->GetSpecIgnoringRef(out);
+ break;
+ case 19: {
+ bool rv;
+ uri->GetHasRef(&rv);
+ break;
+ }
+ case 20:
+ uri->GetFilePath(out);
+ break;
+ case 21:
+ uri->GetQuery(out);
+ break;
+ case 22:
+ uri->GetDisplayHost(out);
+ break;
+ case 23:
+ uri->GetDisplayHostPort(out);
+ break;
+ case 24:
+ uri->GetDisplaySpec(out);
+ break;
+ case 25:
+ uri->GetDisplayPrePath(out);
+ break;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(nullptr, FuzzingRunURIParser, URIParser);
diff --git a/netwerk/test/fuzz/TestWebsocketFuzzing.cpp b/netwerk/test/fuzz/TestWebsocketFuzzing.cpp
new file mode 100644
index 0000000000..89fd08859f
--- /dev/null
+++ b/netwerk/test/fuzz/TestWebsocketFuzzing.cpp
@@ -0,0 +1,229 @@
+#include "mozilla/Preferences.h"
+
+#include "FuzzingInterface.h"
+#include "FuzzyLayer.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollector.h"
+#include "nsIPrincipal.h"
+#include "nsIWebSocketChannel.h"
+#include "nsIWebSocketListener.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "NullPrincipal.h"
+#include "nsSandboxFlags.h"
+
+namespace mozilla {
+namespace net {
+
+// Used to determine if the fuzzing target should use https:// in spec.
+static bool fuzzWSS = true;
+
+class FuzzingWebSocketListener final : public nsIWebSocketListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETLISTENER
+
+ FuzzingWebSocketListener() = default;
+
+ void waitUntilDoneOrStarted() {
+ SpinEventLoopUntil("FuzzingWebSocketListener::waitUntilDoneOrStarted"_ns,
+ [&]() { return mChannelDone || mChannelStarted; });
+ }
+
+ void waitUntilDone() {
+ SpinEventLoopUntil("FuzzingWebSocketListener::waitUntilDone"_ns,
+ [&]() { return mChannelDone; });
+ }
+
+ void waitUntilDoneOrAck() {
+ SpinEventLoopUntil("FuzzingWebSocketListener::waitUntilDoneOrAck"_ns,
+ [&]() { return mChannelDone || mChannelAck; });
+ }
+
+ bool isStarted() { return mChannelStarted; }
+
+ private:
+ ~FuzzingWebSocketListener() = default;
+ bool mChannelDone = false;
+ bool mChannelStarted = false;
+ bool mChannelAck = false;
+};
+
+NS_IMPL_ISUPPORTS(FuzzingWebSocketListener, nsIWebSocketListener)
+
+NS_IMETHODIMP
+FuzzingWebSocketListener::OnStart(nsISupports* aContext) {
+ FUZZING_LOG(("FuzzingWebSocketListener::OnStart"));
+ mChannelStarted = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingWebSocketListener::OnStop(nsISupports* aContext, nsresult aStatusCode) {
+ FUZZING_LOG(("FuzzingWebSocketListener::OnStop"));
+ mChannelDone = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingWebSocketListener::OnAcknowledge(nsISupports* aContext, uint32_t aSize) {
+ FUZZING_LOG(("FuzzingWebSocketListener::OnAcknowledge"));
+ mChannelAck = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingWebSocketListener::OnServerClose(nsISupports* aContext, uint16_t aCode,
+ const nsACString& aReason) {
+ FUZZING_LOG(("FuzzingWebSocketListener::OnServerClose"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingWebSocketListener::OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ FUZZING_LOG(("FuzzingWebSocketListener::OnMessageAvailable"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingWebSocketListener::OnBinaryMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ FUZZING_LOG(("FuzzingWebSocketListener::OnBinaryMessageAvailable"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzingWebSocketListener::OnError() {
+ FUZZING_LOG(("FuzzingWebSocketListener::OnError"));
+ return NS_OK;
+}
+
+static int FuzzingInitNetworkWebsocket(int* argc, char*** argv) {
+ Preferences::SetBool("network.dns.native-is-localhost", true);
+ Preferences::SetBool("fuzzing.necko.enabled", true);
+ Preferences::SetBool("network.websocket.delay-failed-reconnects", false);
+ Preferences::SetInt("network.http.speculative-parallel-limit", 0);
+ Preferences::SetInt("network.proxy.type", 0); // PROXYCONFIG_DIRECT
+ return 0;
+}
+
+static int FuzzingInitNetworkWebsocketPlain(int* argc, char*** argv) {
+ fuzzWSS = false;
+ return FuzzingInitNetworkWebsocket(argc, argv);
+}
+
+static int FuzzingRunNetworkWebsocket(const uint8_t* data, size_t size) {
+ // Set the data to be processed
+ addNetworkFuzzingBuffer(data, size);
+
+ nsWeakPtr channelRef;
+
+ {
+ nsresult rv;
+
+ nsSecurityFlags secFlags;
+ secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+ uint32_t sandboxFlags = SANDBOXED_ORIGIN;
+
+ nsCOMPtr<nsIURI> url;
+ nsAutoCString spec;
+ RefPtr<FuzzingWebSocketListener> gWebSocketListener;
+ nsCOMPtr<nsIWebSocketChannel> gWebSocketChannel;
+
+ if (fuzzWSS) {
+ spec = "https://127.0.0.1/";
+ gWebSocketChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
+ } else {
+ spec = "http://127.0.0.1/";
+ gWebSocketChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
+ }
+
+ if (rv != NS_OK) {
+ MOZ_CRASH("Failed to create WebSocketChannel");
+ }
+
+ if (NS_NewURI(getter_AddRefs(url), spec) != NS_OK) {
+ MOZ_CRASH("Call to NS_NewURI failed.");
+ }
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+
+ rv = gWebSocketChannel->InitLoadInfoNative(
+ nullptr, nullPrincipal, nsContentUtils::GetSystemPrincipal(), nullptr,
+ secFlags, nsIContentPolicy::TYPE_WEBSOCKET, sandboxFlags);
+
+ if (rv != NS_OK) {
+ MOZ_CRASH("Failed to call InitLoadInfo");
+ }
+
+ gWebSocketListener = new FuzzingWebSocketListener();
+
+ OriginAttributes attrs;
+ rv = gWebSocketChannel->AsyncOpenNative(url, spec, attrs, 0,
+ gWebSocketListener, nullptr);
+
+ if (rv == NS_OK) {
+ FUZZING_LOG(("Successful call to AsyncOpen"));
+
+ // Wait for StartRequest or StopRequest
+ gWebSocketListener->waitUntilDoneOrStarted();
+
+ if (gWebSocketListener->isStarted()) {
+ rv = gWebSocketChannel->SendBinaryMsg("Hello world"_ns);
+
+ if (rv != NS_OK) {
+ FUZZING_LOG(("Warning: Failed to call SendBinaryMsg"));
+ } else {
+ gWebSocketListener->waitUntilDoneOrAck();
+ }
+
+ rv = gWebSocketChannel->Close(1000, ""_ns);
+
+ if (rv != NS_OK) {
+ FUZZING_LOG(("Warning: Failed to call close"));
+ }
+ }
+
+ // Wait for StopRequest
+ gWebSocketListener->waitUntilDone();
+ } else {
+ FUZZING_LOG(("Warning: Failed to call AsyncOpen"));
+ }
+
+ channelRef = do_GetWeakReference(gWebSocketChannel);
+ }
+
+ // Wait for the channel to be destroyed
+ SpinEventLoopUntil(
+ "FuzzingRunNetworkWebsocket(channel == nullptr)"_ns, [&]() -> bool {
+ nsCycleCollector_collect(CCReason::API, nullptr);
+ nsCOMPtr<nsIWebSocketChannel> channel = do_QueryReferent(channelRef);
+ return channel == nullptr;
+ });
+
+ if (!signalNetworkFuzzingDone()) {
+ // Wait for the connection to indicate closed
+ SpinEventLoopUntil("FuzzingRunNetworkWebsocket(gFuzzingConnClosed)"_ns,
+ [&]() -> bool { return gFuzzingConnClosed; });
+ }
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkWebsocket,
+ FuzzingRunNetworkWebsocket, NetworkWebsocket);
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkWebsocketPlain,
+ FuzzingRunNetworkWebsocket, NetworkWebsocketPlain);
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/fuzz/moz.build b/netwerk/test/fuzz/moz.build
new file mode 100644
index 0000000000..969b9b0b82
--- /dev/null
+++ b/netwerk/test/fuzz/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+UNIFIED_SOURCES += [
+ "FuzzingStreamListener.cpp",
+ "TestHttpFuzzing.cpp",
+ "TestJARFuzzing.cpp",
+ "TestURIFuzzing.cpp",
+ "TestWebsocketFuzzing.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/caps",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+ "/xpcom/tests/gtest",
+]
+
+EXPORTS.mozilla.fuzzing += [
+ "FuzzingStreamListener.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += ["!/xpcom", "/xpcom/components"]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/netwerk/test/fuzz/url_tokens.dict b/netwerk/test/fuzz/url_tokens.dict
new file mode 100644
index 0000000000..f297ad5de7
--- /dev/null
+++ b/netwerk/test/fuzz/url_tokens.dict
@@ -0,0 +1,51 @@
+### netwerk/base/nsStandardURL.cpp
+# Control characters
+" "
+"#"
+"/"
+":"
+"?"
+"@"
+"["
+"\\"
+"]"
+"*"
+"<"
+">"
+"|"
+"\\"
+
+# URI schemes
+"about"
+"android"
+"blob"
+"chrome"
+"data"
+"file"
+"ftp"
+"http"
+"https"
+"indexeddb"
+"jar"
+"javascript"
+"moz"
+"moz-safe-about"
+"page"
+"resource"
+"sftp"
+"smb"
+"ssh"
+"view"
+"ws"
+"wss"
+
+# URI Hosts
+"selfuri.com"
+"127.0.0.1"
+"::1"
+
+# about protocol safe paths
+"blank"
+"license"
+"logo"
+"srcdoc"
diff --git a/netwerk/test/gtest/TestBase64Stream.cpp b/netwerk/test/gtest/TestBase64Stream.cpp
new file mode 100644
index 0000000000..47ef9e7bc6
--- /dev/null
+++ b/netwerk/test/gtest/TestBase64Stream.cpp
@@ -0,0 +1,123 @@
+/* -*- 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 "gtest/gtest.h"
+#include "mozilla/Base64.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsStringStream.h"
+
+namespace mozilla {
+namespace net {
+
+// An input stream whose ReadSegments method calls aWriter with writes of size
+// aStep from the provided aInput in order to test edge-cases related to small
+// buffers.
+class TestStream final : public nsIInputStream {
+ public:
+ NS_DECL_ISUPPORTS;
+
+ TestStream(const nsACString& aInput, uint32_t aStep)
+ : mInput(aInput), mStep(aStep) {}
+
+ NS_IMETHOD Close() override { MOZ_CRASH("This should not be called"); }
+
+ NS_IMETHOD Available(uint64_t* aLength) override {
+ *aLength = mInput.Length() - mPos;
+ return NS_OK;
+ }
+
+ NS_IMETHOD StreamStatus() override { return NS_OK; }
+
+ NS_IMETHOD Read(char* aBuffer, uint32_t aCount,
+ uint32_t* aReadCount) override {
+ MOZ_CRASH("This should not be called");
+ }
+
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) override {
+ *aResult = 0;
+
+ if (mPos == mInput.Length()) {
+ return NS_OK;
+ }
+
+ while (aCount > 0) {
+ uint32_t amt = std::min(mStep, (uint32_t)(mInput.Length() - mPos));
+
+ uint32_t read = 0;
+ nsresult rv =
+ aWriter(this, aClosure, mInput.get() + mPos, *aResult, amt, &read);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aResult += read;
+ aCount -= read;
+ mPos += read;
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD IsNonBlocking(bool* aNonBlocking) override {
+ *aNonBlocking = true;
+ return NS_OK;
+ }
+
+ private:
+ ~TestStream() = default;
+
+ nsCString mInput;
+ const uint32_t mStep;
+ uint32_t mPos = 0;
+};
+
+NS_IMPL_ISUPPORTS(TestStream, nsIInputStream)
+
+// Test the base64 encoder with writer buffer sizes between 1 byte and the
+// entire length of "Hello World!" in order to exercise various edge cases.
+TEST(TestBase64Stream, Run)
+{
+ nsCString input;
+ input.AssignLiteral("Hello World!");
+
+ for (uint32_t step = 1; step <= input.Length(); ++step) {
+ RefPtr<TestStream> ts = new TestStream(input, step);
+
+ nsAutoString encodedData;
+ nsresult rv = Base64EncodeInputStream(ts, encodedData, input.Length());
+ ASSERT_NS_SUCCEEDED(rv);
+
+ EXPECT_TRUE(encodedData.EqualsLiteral("SGVsbG8gV29ybGQh"));
+ }
+}
+
+TEST(TestBase64Stream, VaryingCount)
+{
+ nsCString input;
+ input.AssignLiteral("Hello World!");
+
+ std::pair<size_t, nsCString> tests[] = {
+ {0, "SGVsbG8gV29ybGQh"_ns}, {1, "SA=="_ns},
+ {5, "SGVsbG8="_ns}, {11, "SGVsbG8gV29ybGQ="_ns},
+ {12, "SGVsbG8gV29ybGQh"_ns}, {13, "SGVsbG8gV29ybGQh"_ns},
+ };
+
+ for (auto& [count, expected] : tests) {
+ nsCOMPtr<nsIInputStream> is;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(is), input);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsAutoCString encodedData;
+ rv = Base64EncodeInputStream(is, encodedData, count);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(encodedData, expected) << "count: " << count;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestBind.cpp b/netwerk/test/gtest/TestBind.cpp
new file mode 100644
index 0000000000..371a09fdab
--- /dev/null
+++ b/netwerk/test/gtest/TestBind.cpp
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIServerSocket.h"
+#include "nsIAsyncInputStream.h"
+#include "mozilla/net/DNS.h"
+#include "prerror.h"
+#include "../../base/nsSocketTransportService2.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::net;
+using namespace mozilla;
+
+class ServerListener : public nsIServerSocketListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ explicit ServerListener(WaitForCondition* waiter);
+
+ // Port that is got from server side will be store here.
+ uint32_t mClientPort;
+ bool mFailed;
+ RefPtr<WaitForCondition> mWaiter;
+
+ private:
+ virtual ~ServerListener();
+};
+
+NS_IMPL_ISUPPORTS(ServerListener, nsIServerSocketListener)
+
+ServerListener::ServerListener(WaitForCondition* waiter)
+ : mClientPort(-1), mFailed(false), mWaiter(waiter) {}
+
+ServerListener::~ServerListener() = default;
+
+NS_IMETHODIMP
+ServerListener::OnSocketAccepted(nsIServerSocket* aServ,
+ nsISocketTransport* aTransport) {
+ // Run on STS thread.
+ NetAddr peerAddr;
+ nsresult rv = aTransport->GetPeerAddr(&peerAddr);
+ if (NS_FAILED(rv)) {
+ mFailed = true;
+ mWaiter->Notify();
+ return NS_OK;
+ }
+ mClientPort = PR_ntohs(peerAddr.inet.port);
+ mWaiter->Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServerListener::OnStopListening(nsIServerSocket* aServ, nsresult aStatus) {
+ return NS_OK;
+}
+
+class ClientInputCallback : public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ explicit ClientInputCallback(WaitForCondition* waiter);
+
+ bool mFailed;
+ RefPtr<WaitForCondition> mWaiter;
+
+ private:
+ virtual ~ClientInputCallback();
+};
+
+NS_IMPL_ISUPPORTS(ClientInputCallback, nsIInputStreamCallback)
+
+ClientInputCallback::ClientInputCallback(WaitForCondition* waiter)
+ : mFailed(false), mWaiter(waiter) {}
+
+ClientInputCallback::~ClientInputCallback() = default;
+
+NS_IMETHODIMP
+ClientInputCallback::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ // Server doesn't send. That means if we are here, we probably have run into
+ // an error.
+ uint64_t avail;
+ nsresult rv = aStream->Available(&avail);
+ if (NS_FAILED(rv)) {
+ mFailed = true;
+ }
+ mWaiter->Notify();
+ return NS_OK;
+}
+
+TEST(TestBind, MainTest)
+{
+ //
+ // Server side.
+ //
+ nsCOMPtr<nsIServerSocket> server =
+ do_CreateInstance("@mozilla.org/network/server-socket;1");
+ ASSERT_TRUE(server);
+
+ nsresult rv = server->Init(-1, true, -1);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ int32_t serverPort;
+ rv = server->GetPort(&serverPort);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ RefPtr<WaitForCondition> waiter = new WaitForCondition();
+
+ // Listening.
+ RefPtr<ServerListener> serverListener = new ServerListener(waiter);
+ rv = server->AsyncListen(serverListener);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ //
+ // Client side
+ //
+ uint32_t bindingPort = 20000;
+ nsCOMPtr<nsISocketTransportService> service =
+ do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ RefPtr<ClientInputCallback> clientCallback;
+
+ auto* sts = gSocketTransportService;
+ ASSERT_TRUE(sts);
+ for (int32_t tried = 0; tried < 100; tried++) {
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "test"_ns, sts, NS_NewRunnableFunction("test", [&]() {
+ nsCOMPtr<nsISocketTransport> client;
+ rv = service->CreateTransport(nsTArray<nsCString>(), "127.0.0.1"_ns,
+ serverPort, nullptr, nullptr,
+ getter_AddRefs(client));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Bind to a port. It's possible that we are binding to a port
+ // that is currently in use. If we failed to bind, we try next
+ // port.
+ NetAddr bindingAddr;
+ bindingAddr.inet.family = AF_INET;
+ bindingAddr.inet.ip = 0;
+ bindingAddr.inet.port = PR_htons(bindingPort);
+ rv = client->Bind(&bindingAddr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Open IO streams, to make client SocketTransport connect to
+ // server.
+ clientCallback = new ClientInputCallback(waiter);
+ rv = client->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(inputStream));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(inputStream);
+ rv = asyncInputStream->AsyncWait(clientCallback, 0, 0, nullptr);
+ }));
+
+ // Wait for server's response or callback of input stream.
+ waiter->Wait(1);
+ if (clientCallback->mFailed) {
+ // if client received error, we likely have bound a port that is
+ // in use. we can try another port.
+ bindingPort++;
+ } else {
+ // We are unlocked by server side, leave the loop and check
+ // result.
+ break;
+ }
+ }
+
+ ASSERT_FALSE(serverListener->mFailed);
+ ASSERT_EQ(serverListener->mClientPort, bindingPort);
+
+ inputStream->Close();
+ waiter->Wait(1);
+ ASSERT_TRUE(clientCallback->mFailed);
+
+ server->Close();
+}
diff --git a/netwerk/test/gtest/TestBufferedInputStream.cpp b/netwerk/test/gtest/TestBufferedInputStream.cpp
new file mode 100644
index 0000000000..7230fe7e5b
--- /dev/null
+++ b/netwerk/test/gtest/TestBufferedInputStream.cpp
@@ -0,0 +1,252 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsBufferedStreams.h"
+#include "nsIThread.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "Helpers.h"
+
+// Helper function for creating a testing::AsyncStringStream
+already_AddRefed<nsBufferedInputStream> CreateStream(uint32_t aSize,
+ nsCString& aBuffer) {
+ aBuffer.SetLength(aSize);
+ for (uint32_t i = 0; i < aSize; ++i) {
+ aBuffer.BeginWriting()[i] = i % 10;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(aBuffer);
+
+ RefPtr<nsBufferedInputStream> bis = new nsBufferedInputStream();
+ bis->Init(stream, aSize);
+ return bis.forget();
+}
+
+// Simple reading.
+TEST(TestBufferedInputStream, SimpleRead)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, bis->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ char buf2[kBufSize];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, bis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, buf.Length());
+ ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+}
+
+// Simple segment reading.
+TEST(TestBufferedInputStream, SimpleReadSegments)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ char buf2[kBufSize];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, bis->ReadSegments(NS_CopySegmentToBuffer, buf2, sizeof(buf2),
+ &count));
+ ASSERT_EQ(count, buf.Length());
+ ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+}
+
+// AsyncWait - sync
+TEST(TestBufferedInputStream, AsyncWait_sync)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, nullptr));
+
+ // Immediatelly called
+ ASSERT_TRUE(cb->Called());
+}
+
+// AsyncWait - async
+TEST(TestBufferedInputStream, AsyncWait_async)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, thread));
+
+ ASSERT_FALSE(cb->Called());
+
+ // Eventually it is called.
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TEST(TestBufferedInputStream, AsyncWait_async)"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
+
+// AsyncWait - sync - closureOnly
+TEST(TestBufferedInputStream, AsyncWait_sync_closureOnly)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ ASSERT_EQ(NS_OK, bis->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
+ nullptr));
+ ASSERT_FALSE(cb->Called());
+
+ bis->CloseWithStatus(NS_ERROR_FAILURE);
+
+ // Immediatelly called
+ ASSERT_TRUE(cb->Called());
+}
+
+// AsyncWait - async
+TEST(TestBufferedInputStream, AsyncWait_async_closureOnly)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ ASSERT_EQ(NS_OK, bis->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
+ thread));
+
+ ASSERT_FALSE(cb->Called());
+ bis->CloseWithStatus(NS_ERROR_FAILURE);
+ ASSERT_FALSE(cb->Called());
+
+ // Eventually it is called.
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TEST(TestBufferedInputStream, AsyncWait_async_closureOnly)"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
+
+TEST(TestBufferedInputStream, AsyncWait_after_close)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ nsCOMPtr<nsIThread> eventTarget = do_GetCurrentThread();
+
+ auto cb = mozilla::MakeRefPtr<testing::InputStreamCallback>();
+ ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, eventTarget));
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TEST(TestBufferedInputStream, AsyncWait_after_close) 1"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+
+ ASSERT_EQ(NS_OK, bis->Close());
+
+ cb = mozilla::MakeRefPtr<testing::InputStreamCallback>();
+ ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, eventTarget));
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TEST(TestBufferedInputStream, AsyncWait_after_close) 2"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
+
+TEST(TestBufferedInputStream, AsyncLengthWait_after_close)
+{
+ nsCString buf{"The Quick Brown Fox Jumps over the Lazy Dog"};
+ const size_t kBufSize = 44;
+
+ RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf);
+
+ nsCOMPtr<nsIThread> eventTarget = do_GetCurrentThread();
+
+ auto cb = mozilla::MakeRefPtr<testing::LengthCallback>();
+ ASSERT_EQ(NS_OK, bis->AsyncLengthWait(cb, eventTarget));
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TEST(TestBufferedInputStream, AsyncLengthWait_after_close) 1"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, bis->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ cb = mozilla::MakeRefPtr<testing::LengthCallback>();
+ ASSERT_EQ(NS_OK, bis->AsyncLengthWait(cb, eventTarget));
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TEST(TestBufferedInputStream, AsyncLengthWait_after_close) 2"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
+
+// This stream returns a few bytes on the first read, and error on the second.
+class BrokenInputStream : public nsIInputStream {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ private:
+ virtual ~BrokenInputStream() = default;
+ bool mFirst = true;
+};
+
+NS_IMPL_ISUPPORTS(BrokenInputStream, nsIInputStream)
+
+NS_IMETHODIMP BrokenInputStream::Close(void) { return NS_OK; }
+
+NS_IMETHODIMP BrokenInputStream::Available(uint64_t* _retval) {
+ *_retval = 100;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrokenInputStream::StreamStatus(void) { return NS_OK; }
+
+NS_IMETHODIMP BrokenInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ if (mFirst) {
+ aBuf[0] = 'h';
+ aBuf[1] = 'e';
+ aBuf[2] = 'l';
+ aBuf[3] = 0;
+ *_retval = 4;
+ mFirst = false;
+ return NS_OK;
+ }
+ return NS_ERROR_CORRUPTED_CONTENT;
+}
+
+NS_IMETHODIMP BrokenInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP BrokenInputStream::IsNonBlocking(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+// Check that the error from BrokenInputStream::Read is propagated
+// through NS_ReadInputStreamToString
+TEST(TestBufferedInputStream, BrokenInputStreamToBuffer)
+{
+ nsAutoCString out;
+ RefPtr<BrokenInputStream> stream = new BrokenInputStream();
+
+ nsresult rv = NS_ReadInputStreamToString(stream, out, -1);
+ ASSERT_EQ(rv, NS_ERROR_CORRUPTED_CONTENT);
+}
diff --git a/netwerk/test/gtest/TestCommon.cpp b/netwerk/test/gtest/TestCommon.cpp
new file mode 100644
index 0000000000..37c08fbed8
--- /dev/null
+++ b/netwerk/test/gtest/TestCommon.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+
+NS_IMPL_ISUPPORTS(WaitForCondition, nsIRunnable)
diff --git a/netwerk/test/gtest/TestCommon.h b/netwerk/test/gtest/TestCommon.h
new file mode 100644
index 0000000000..0d2fd74e5b
--- /dev/null
+++ b/netwerk/test/gtest/TestCommon.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TestCommon_h__
+#define TestCommon_h__
+
+#include <stdlib.h>
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/SpinEventLoopUntil.h"
+
+//-----------------------------------------------------------------------------
+
+class WaitForCondition final : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ void Wait(int pending) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPending == 0);
+
+ mPending = pending;
+ mozilla::SpinEventLoopUntil("TestCommon.h:WaitForCondition::Wait"_ns,
+ [&]() { return !mPending; });
+ NS_ProcessPendingEvents(nullptr);
+ }
+
+ void Notify() { NS_DispatchToMainThread(this); }
+
+ private:
+ virtual ~WaitForCondition() = default;
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPending);
+
+ --mPending;
+ return NS_OK;
+ }
+
+ uint32_t mPending = 0;
+};
+
+#endif
diff --git a/netwerk/test/gtest/TestCookie.cpp b/netwerk/test/gtest/TestCookie.cpp
new file mode 100644
index 0000000000..4812ee47f1
--- /dev/null
+++ b/netwerk/test/gtest/TestCookie.cpp
@@ -0,0 +1,1126 @@
+/* -*- 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 "TestCommon.h"
+#include "gtest/gtest.h"
+#include "nsContentUtils.h"
+#include "nsICookieService.h"
+#include "nsICookieManager.h"
+#include "nsICookie.h"
+#include <stdio.h>
+#include "plstr.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "Cookie.h"
+#include "nsIURI.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+static NS_DEFINE_CID(kCookieServiceCID, NS_COOKIESERVICE_CID);
+static NS_DEFINE_CID(kPrefServiceCID, NS_PREFSERVICE_CID);
+
+// various pref strings
+static const char kCookiesPermissions[] = "network.cookie.cookieBehavior";
+static const char kPrefCookieQuotaPerHost[] = "network.cookie.quotaPerHost";
+static const char kCookiesMaxPerHost[] = "network.cookie.maxPerHost";
+
+#define OFFSET_ONE_WEEK int64_t(604800) * PR_USEC_PER_SEC
+#define OFFSET_ONE_DAY int64_t(86400) * PR_USEC_PER_SEC
+
+// Set server time or expiry time
+void SetTime(PRTime offsetTime, nsAutoCString& serverString,
+ nsAutoCString& cookieString, bool expiry) {
+ char timeStringPreset[40];
+ PRTime CurrentTime = PR_Now();
+ PRTime SetCookieTime = CurrentTime + offsetTime;
+ PRTime SetExpiryTime;
+ if (expiry) {
+ SetExpiryTime = SetCookieTime - OFFSET_ONE_DAY;
+ } else {
+ SetExpiryTime = SetCookieTime + OFFSET_ONE_DAY;
+ }
+
+ // Set server time string
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(SetCookieTime, PR_GMTParameters, &explodedTime);
+ PR_FormatTimeUSEnglish(timeStringPreset, 40, "%c GMT", &explodedTime);
+ serverString.Assign(timeStringPreset);
+
+ // Set cookie string
+ PR_ExplodeTime(SetExpiryTime, PR_GMTParameters, &explodedTime);
+ PR_FormatTimeUSEnglish(timeStringPreset, 40, "%c GMT", &explodedTime);
+ cookieString.ReplaceLiteral(
+ 0, strlen("test=expiry; expires=") + strlen(timeStringPreset) + 1,
+ "test=expiry; expires=");
+ cookieString.Append(timeStringPreset);
+}
+
+void SetACookieInternal(nsICookieService* aCookieService, const char* aSpec,
+ const char* aCookieString, bool aAllowed) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aSpec);
+
+ // We create a dummy channel using the aSpec to simulate same-siteness
+ nsresult rv0;
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv0);
+ ASSERT_NS_SUCCEEDED(rv0);
+ nsCOMPtr<nsIPrincipal> specPrincipal;
+ nsCString tmpString(aSpec);
+ ssm->CreateContentPrincipalFromOrigin(tmpString,
+ getter_AddRefs(specPrincipal));
+
+ nsCOMPtr<nsIChannel> dummyChannel;
+ NS_NewChannel(getter_AddRefs(dummyChannel), uri, specPrincipal,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_OTHER);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ aAllowed
+ ? CookieJarSettings::Create(CookieJarSettings::eRegular,
+ /* shouldResistFingerprinting */ false)
+ : CookieJarSettings::GetBlockingAll(
+ /* shouldResistFingerprinting */ false);
+ MOZ_ASSERT(cookieJarSettings);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = dummyChannel->LoadInfo();
+ loadInfo->SetCookieJarSettings(cookieJarSettings);
+
+ nsresult rv = aCookieService->SetCookieStringFromHttp(
+ uri, nsDependentCString(aCookieString), dummyChannel);
+ EXPECT_NS_SUCCEEDED(rv);
+}
+
+void SetACookieJarBlocked(nsICookieService* aCookieService, const char* aSpec,
+ const char* aCookieString) {
+ SetACookieInternal(aCookieService, aSpec, aCookieString, false);
+}
+
+void SetACookie(nsICookieService* aCookieService, const char* aSpec,
+ const char* aCookieString) {
+ SetACookieInternal(aCookieService, aSpec, aCookieString, true);
+}
+
+// The cookie string is returned via aCookie.
+void GetACookie(nsICookieService* aCookieService, const char* aSpec,
+ nsACString& aCookie) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aSpec);
+
+ nsCOMPtr<nsIIOService> service = do_GetIOService();
+
+ nsCOMPtr<nsIChannel> channel;
+ Unused << service->NewChannelFromURI(
+ uri, nullptr, nsContentUtils::GetSystemPrincipal(),
+ nsContentUtils::GetSystemPrincipal(), 0, nsIContentPolicy::TYPE_DOCUMENT,
+ getter_AddRefs(channel));
+
+ Unused << aCookieService->GetCookieStringFromHttp(uri, channel, aCookie);
+}
+
+// The cookie string is returned via aCookie.
+void GetACookieNoHttp(nsICookieService* aCookieService, const char* aSpec,
+ nsACString& aCookie) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aSpec);
+
+ RefPtr<BasePrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, OriginAttributes());
+ MOZ_ASSERT(principal);
+
+ nsCOMPtr<mozilla::dom::Document> document;
+ nsresult rv = NS_NewDOMDocument(getter_AddRefs(document),
+ u""_ns, // aNamespaceURI
+ u""_ns, // aQualifiedName
+ nullptr, // aDoctype
+ uri, uri, principal,
+ false, // aLoadedAsData
+ nullptr, // aEventObject
+ DocumentFlavorHTML);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ Unused << aCookieService->GetCookieStringFromDocument(document, aCookie);
+}
+
+// some #defines for comparison rules
+#define MUST_BE_NULL 0
+#define MUST_EQUAL 1
+#define MUST_CONTAIN 2
+#define MUST_NOT_CONTAIN 3
+#define MUST_NOT_EQUAL 4
+
+// a simple helper function to improve readability:
+// takes one of the #defined rules above, and performs the appropriate test.
+// true means the test passed; false means the test failed.
+static inline bool CheckResult(const char* aLhs, uint32_t aRule,
+ const char* aRhs = nullptr) {
+ switch (aRule) {
+ case MUST_BE_NULL:
+ return !aLhs || !*aLhs;
+
+ case MUST_EQUAL:
+ return !PL_strcmp(aLhs, aRhs);
+
+ case MUST_NOT_EQUAL:
+ return PL_strcmp(aLhs, aRhs);
+
+ case MUST_CONTAIN:
+ return strstr(aLhs, aRhs) != nullptr;
+
+ case MUST_NOT_CONTAIN:
+ return strstr(aLhs, aRhs) == nullptr;
+
+ default:
+ return false; // failure
+ }
+}
+
+void InitPrefs(nsIPrefBranch* aPrefBranch) {
+ // init some relevant prefs, so the tests don't go awry.
+ // we use the most restrictive set of prefs we can;
+ // however, we don't test third party blocking here.
+ aPrefBranch->SetIntPref(kCookiesPermissions, 0); // accept all
+ // Set quotaPerHost to maxPerHost - 1, so there is only one cookie
+ // will be evicted everytime.
+ aPrefBranch->SetIntPref(kPrefCookieQuotaPerHost, 49);
+ // Set the base domain limit to 50 so we have a known value.
+ aPrefBranch->SetIntPref(kCookiesMaxPerHost, 50);
+
+ // SameSite=None by default. We have other tests for lax-by-default.
+ // XXX: Bug 1617611 - Fix all the tests broken by "cookies SameSite=Lax by
+ // default"
+ Preferences::SetBool("network.cookie.sameSite.laxByDefault", false);
+ Preferences::SetBool("network.cookieJarSettings.unblocked_for_testing", true);
+ Preferences::SetBool("dom.securecontext.allowlist_onions", false);
+ Preferences::SetBool("network.cookie.sameSite.schemeful", false);
+}
+
+TEST(TestCookie, TestCookieMain)
+{
+ nsresult rv0;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv0);
+ ASSERT_NS_SUCCEEDED(rv0);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(kPrefServiceCID, &rv0);
+ ASSERT_NS_SUCCEEDED(rv0);
+
+ InitPrefs(prefBranch);
+
+ nsCString cookie;
+
+ /* The basic idea behind these tests is the following:
+ *
+ * we set() some cookie, then try to get() it in various ways. we have
+ * several possible tests we perform on the cookie string returned from
+ * get():
+ *
+ * a) check whether the returned string is null (i.e. we got no cookies
+ * back). this is used e.g. to ensure a given cookie was deleted
+ * correctly, or to ensure a certain cookie wasn't returned to a given
+ * host.
+ * b) check whether the returned string exactly matches a given string.
+ * this is used where we want to make sure our cookie service adheres to
+ * some strict spec (e.g. ordering of multiple cookies), or where we
+ * just know exactly what the returned string should be.
+ * c) check whether the returned string contains/does not contain a given
+ * string. this is used where we don't know/don't care about the
+ * ordering of multiple cookies - we just want to make sure the cookie
+ * string contains them all, in some order.
+ *
+ * NOTE: this testsuite is not yet comprehensive or complete, and is
+ * somewhat contrived - still under development, and needs improving!
+ */
+
+ // test some basic variations of the domain & path
+ SetACookie(cookieService, "http://www.basic.com", "test=basic");
+ GetACookie(cookieService, "http://www.basic.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic"));
+ GetACookie(cookieService, "http://www.basic.com/testPath/testfile.txt",
+ cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic"));
+ GetACookie(cookieService, "http://www.basic.com./", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ GetACookie(cookieService, "http://www.basic.com.", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ GetACookie(cookieService, "http://www.basic.com./testPath/testfile.txt",
+ cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ GetACookie(cookieService, "http://www.basic2.com/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://www.basic.com", "test=basic; max-age=-1");
+ GetACookie(cookieService, "http://www.basic.com/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // *** domain tests
+
+ // test some variations of the domain & path, for different domains of
+ // a domain cookie
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=domain.com");
+ GetACookie(cookieService, "http://domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain"));
+ GetACookie(cookieService, "http://domain.com.", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ GetACookie(cookieService, "http://www.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain"));
+ GetACookie(cookieService, "http://foo.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain"));
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=domain.com; max-age=-1");
+ GetACookie(cookieService, "http://domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=.domain.com");
+ GetACookie(cookieService, "http://domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain"));
+ GetACookie(cookieService, "http://www.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain"));
+ GetACookie(cookieService, "http://bah.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain"));
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=.domain.com; max-age=-1");
+ GetACookie(cookieService, "http://domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=.foo.domain.com");
+ GetACookie(cookieService, "http://foo.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=moose.com");
+ GetACookie(cookieService, "http://foo.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=domain.com.");
+ GetACookie(cookieService, "http://foo.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=..domain.com");
+ GetACookie(cookieService, "http://foo.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://www.domain.com",
+ "test=domain; domain=..domain.com.");
+ GetACookie(cookieService, "http://foo.domain.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://path.net/path/file",
+ R"(test=taco; path="/bogus")");
+ GetACookie(cookieService, "http://path.net/path/file", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=taco"));
+ SetACookie(cookieService, "http://path.net/path/file",
+ "test=taco; max-age=-1");
+ GetACookie(cookieService, "http://path.net/path/file", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // *** path tests
+
+ // test some variations of the domain & path, for different paths of
+ // a path cookie
+ SetACookie(cookieService, "http://path.net/path/file",
+ "test=path; path=/path");
+ GetACookie(cookieService, "http://path.net/path", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path"));
+ GetACookie(cookieService, "http://path.net/path/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path"));
+ GetACookie(cookieService, "http://path.net/path/hithere.foo", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path"));
+ GetACookie(cookieService, "http://path.net/path?hithere/foo", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path"));
+ GetACookie(cookieService, "http://path.net/path2", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ GetACookie(cookieService, "http://path.net/path2/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://path.net/path/file",
+ "test=path; path=/path; max-age=-1");
+ GetACookie(cookieService, "http://path.net/path/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://path.net/path/file",
+ "test=path; path=/path/");
+ GetACookie(cookieService, "http://path.net/path", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ GetACookie(cookieService, "http://path.net/path/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path"));
+ SetACookie(cookieService, "http://path.net/path/file",
+ "test=path; path=/path/; max-age=-1");
+ GetACookie(cookieService, "http://path.net/path/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // note that a site can set a cookie for a path it's not on.
+ // this is an intentional deviation from spec (see comments in
+ // CookieService::CheckPath()), so we test this functionality too
+ SetACookie(cookieService, "http://path.net/path/file",
+ "test=path; path=/foo/");
+ GetACookie(cookieService, "http://path.net/path", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ GetACookie(cookieService, "http://path.net/foo", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://path.net/path/file",
+ "test=path; path=/foo/; max-age=-1");
+ GetACookie(cookieService, "http://path.net/foo/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // bug 373228: make sure cookies with paths longer than 1024 bytes,
+ // and cookies with paths or names containing tabs, are rejected.
+ // the following cookie has a path > 1024 bytes explicitly specified in the
+ // cookie
+ SetACookie(
+ cookieService, "http://path.net/",
+ "test=path; "
+ "path=/"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "9012345678901234567890/");
+ GetACookie(
+ cookieService,
+ "http://path.net/"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "9012345678901234567890",
+ cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ // the following cookie has a path > 1024 bytes implicitly specified by the
+ // uri path
+ SetACookie(
+ cookieService,
+ "http://path.net/"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "9012345678901234567890/",
+ "test=path");
+ GetACookie(
+ cookieService,
+ "http://path.net/"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "901234567890123456789012345678901234567890123456789012345678901234567890"
+ "123456789012345678901234567890123456789012345678901234567890123456789012"
+ "345678901234567890123456789012345678901234567890123456789012345678901234"
+ "567890123456789012345678901234567890123456789012345678901234567890123456"
+ "789012345678901234567890123456789012345678901234567890123456789012345678"
+ "9012345678901234567890/",
+ cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ // the following cookie includes a tab in the path
+ SetACookie(cookieService, "http://path.net/", "test=path; path=/foo\tbar/");
+ GetACookie(cookieService, "http://path.net/foo\tbar/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ // the following cookie includes a tab in the name
+ SetACookie(cookieService, "http://path.net/", "test\ttabs=tab");
+ GetACookie(cookieService, "http://path.net/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ // the following cookie includes a tab in the value - allowed
+ SetACookie(cookieService, "http://path.net/", "test=tab\ttest");
+ GetACookie(cookieService, "http://path.net/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=tab\ttest"));
+ SetACookie(cookieService, "http://path.net/", "test=tab\ttest; max-age=-1");
+ GetACookie(cookieService, "http://path.net/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // *** expiry & deletion tests
+ // XXX add server time str parsing tests here
+
+ // test some variations of the expiry time,
+ // and test deletion of previously set cookies
+ SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=-1");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=0");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://expireme.org/", "test=expiry; expires=bad");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry"));
+ SetACookie(cookieService, "http://expireme.org/",
+ "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://expireme.org/",
+ R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT)");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://expireme.org/",
+ R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT")");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=60");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry"));
+ SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=-20");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=60");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry"));
+ SetACookie(cookieService, "http://expireme.org/",
+ "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=60");
+ SetACookie(cookieService, "http://expireme.org/",
+ "newtest=expiry; max-age=60");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=expiry"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "newtest=expiry"));
+ SetACookie(cookieService, "http://expireme.org/",
+ "test=differentvalue; max-age=0");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "newtest=expiry"));
+ SetACookie(cookieService, "http://expireme.org/",
+ "newtest=evendifferentvalue; max-age=0");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://foo.expireme.org/",
+ "test=expiry; domain=.expireme.org; max-age=60");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry"));
+ SetACookie(cookieService, "http://bar.expireme.org/",
+ "test=differentvalue; domain=.expireme.org; max-age=0");
+ GetACookie(cookieService, "http://expireme.org/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ nsAutoCString ServerTime;
+ nsAutoCString CookieString;
+
+ // *** multiple cookie tests
+
+ // test the setting of multiple cookies, and test the order of precedence
+ // (a later cookie overwriting an earlier one, in the same header string)
+ SetACookie(cookieService, "http://multiple.cookies/",
+ "test=multiple; domain=.multiple.cookies \n test=different \n "
+ "test=same; domain=.multiple.cookies \n newtest=ciao \n "
+ "newtest=foo; max-age=-6 \n newtest=reincarnated");
+ GetACookie(cookieService, "http://multiple.cookies/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=multiple"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=different"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=same"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=ciao"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=foo"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "newtest=reincarnated"));
+ SetACookie(cookieService, "http://multiple.cookies/",
+ "test=expiry; domain=.multiple.cookies; max-age=0");
+ GetACookie(cookieService, "http://multiple.cookies/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=same"));
+ SetACookie(cookieService, "http://multiple.cookies/",
+ "\n test=different; max-age=0 \n");
+ GetACookie(cookieService, "http://multiple.cookies/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=different"));
+ SetACookie(cookieService, "http://multiple.cookies/",
+ "newtest=dead; max-age=0");
+ GetACookie(cookieService, "http://multiple.cookies/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // *** parser tests
+
+ // test the cookie header parser, under various circumstances.
+ SetACookie(cookieService, "http://parser.test/",
+ "test=parser; domain=.parser.test; ;; ;=; ,,, ===,abc,=; "
+ "abracadabra! max-age=20;=;;");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=parser"));
+ SetACookie(cookieService, "http://parser.test/",
+ "test=parser; domain=.parser.test; max-age=0");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "http://parser.test/",
+ "test=\"fubar! = foo;bar\\\";\" parser; domain=.parser.test; "
+ "max-age=6\nfive; max-age=2.63,");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, R"(test="fubar! = foo)"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "five"));
+ SetACookie(cookieService, "http://parser.test/",
+ "test=kill; domain=.parser.test; max-age=0 \n five; max-age=0");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // test the handling of VALUE-only cookies (see bug 169091),
+ // i.e. "six" should assume an empty NAME, which allows other VALUE-only
+ // cookies to overwrite it
+ SetACookie(cookieService, "http://parser.test/", "six");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "six"));
+ SetACookie(cookieService, "http://parser.test/", "seven");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "seven"));
+ SetACookie(cookieService, "http://parser.test/", " =eight");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "eight"));
+ SetACookie(cookieService, "http://parser.test/", "test=six");
+ GetACookie(cookieService, "http://parser.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=six"));
+
+ // *** path ordering tests
+
+ // test that cookies are returned in path order - longest to shortest.
+ // if the header doesn't specify a path, it's taken from the host URI.
+ SetACookie(cookieService, "http://multi.path.tests/",
+ "test1=path; path=/one/two/three");
+ SetACookie(cookieService, "http://multi.path.tests/",
+ "test2=path; path=/one \n test3=path; path=/one/two/three/four \n "
+ "test4=path; path=/one/two \n test5=path; path=/one/two/");
+ SetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/",
+ "test6=path");
+ SetACookie(cookieService,
+ "http://multi.path.tests/one/two/three/four/five/six/",
+ "test7=path; path=");
+ SetACookie(cookieService, "http://multi.path.tests/", "test8=path; path=/");
+ GetACookie(cookieService,
+ "http://multi.path.tests/one/two/three/four/five/six/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL,
+ "test7=path; test6=path; test3=path; test1=path; "
+ "test5=path; test4=path; test2=path; test8=path"));
+
+ // *** Cookie prefix tests
+
+ // prefixed cookies can't be set from insecure HTTP
+ SetACookie(cookieService, "http://prefixed.test/", "__Secure-test1=test");
+ SetACookie(cookieService, "http://prefixed.test/",
+ "__Secure-test2=test; secure");
+ SetACookie(cookieService, "http://prefixed.test/", "__Host-test1=test");
+ SetACookie(cookieService, "http://prefixed.test/",
+ "__Host-test2=test; secure");
+ GetACookie(cookieService, "http://prefixed.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // prefixed cookies won't be set without the secure flag
+ SetACookie(cookieService, "https://prefixed.test/", "__Secure-test=test");
+ SetACookie(cookieService, "https://prefixed.test/", "__Host-test=test");
+ GetACookie(cookieService, "https://prefixed.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // prefixed cookies can be set when done correctly
+ SetACookie(cookieService, "https://prefixed.test/",
+ "__Secure-test=test; secure");
+ SetACookie(cookieService, "https://prefixed.test/",
+ "__Host-test=test; secure");
+ GetACookie(cookieService, "https://prefixed.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "__Secure-test=test"));
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "__Host-test=test"));
+
+ // but when set must not be returned to the host insecurely
+ GetACookie(cookieService, "http://prefixed.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // Host-prefixed cookies cannot specify a domain
+ SetACookie(cookieService, "https://host.prefixed.test/",
+ "__Host-a=test; secure; domain=prefixed.test");
+ SetACookie(cookieService, "https://host.prefixed.test/",
+ "__Host-b=test; secure; domain=.prefixed.test");
+ SetACookie(cookieService, "https://host.prefixed.test/",
+ "__Host-c=test; secure; domain=host.prefixed.test");
+ SetACookie(cookieService, "https://host.prefixed.test/",
+ "__Host-d=test; secure; domain=.host.prefixed.test");
+ GetACookie(cookieService, "https://host.prefixed.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // Host-prefixed cookies can only have a path of "/"
+ SetACookie(cookieService, "https://host.prefixed.test/some/path",
+ "__Host-e=test; secure");
+ SetACookie(cookieService, "https://host.prefixed.test/some/path",
+ "__Host-f=test; secure; path=/");
+ SetACookie(cookieService, "https://host.prefixed.test/some/path",
+ "__Host-g=test; secure; path=/some");
+ GetACookie(cookieService, "https://host.prefixed.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "__Host-f=test"));
+
+ // *** leave-secure-alone tests
+
+ // testing items 0 & 1 for 3.1 of spec Deprecate modification of ’secure’
+ // cookies from non-secure origins
+ SetACookie(cookieService, "http://www.security.test/",
+ "test=non-security; secure");
+ GetACookieNoHttp(cookieService, "https://www.security.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+ SetACookie(cookieService, "https://www.security.test/path/",
+ "test=security; secure; path=/path/");
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=security"));
+ // testing items 2 & 3 & 4 for 3.2 of spec Deprecate modification of ’secure’
+ // cookies from non-secure origins
+ // Secure site can modify cookie value
+ SetACookie(cookieService, "https://www.security.test/path/",
+ "test=security2; secure; path=/path/");
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=security2"));
+ // If new cookie contains same name, same host and partially matching path
+ // with an existing security cookie on non-security site, it can't modify an
+ // existing security cookie.
+ SetACookie(cookieService, "http://www.security.test/path/foo/",
+ "test=non-security; path=/path/foo");
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/foo/",
+ cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=security2"));
+ // Non-secure cookie can set by same name, same host and non-matching path.
+ SetACookie(cookieService, "http://www.security.test/bar/",
+ "test=non-security; path=/bar");
+ GetACookieNoHttp(cookieService, "http://www.security.test/bar/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=non-security"));
+ // Modify value and downgrade secure level.
+ SetACookie(
+ cookieService, "https://www.security.test/",
+ "test_modify_cookie=security-cookie; secure; domain=.security.test");
+ GetACookieNoHttp(cookieService, "https://www.security.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL,
+ "test_modify_cookie=security-cookie"));
+ SetACookie(cookieService, "https://www.security.test/",
+ "test_modify_cookie=non-security-cookie; domain=.security.test");
+ GetACookieNoHttp(cookieService, "https://www.security.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL,
+ "test_modify_cookie=non-security-cookie"));
+
+ // Test the non-security cookie can set when domain or path not same to secure
+ // cookie of same name.
+ SetACookie(cookieService, "https://www.security.test/", "test=security3");
+ GetACookieNoHttp(cookieService, "http://www.security.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=security3"));
+ SetACookie(cookieService, "http://www.security.test/",
+ "test=non-security2; domain=security.test");
+ GetACookieNoHttp(cookieService, "http://www.security.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=non-security2"));
+
+ // *** nsICookieManager interface tests
+ nsCOMPtr<nsICookieManager> cookieMgr =
+ do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv0);
+ ASSERT_NS_SUCCEEDED(rv0);
+
+ const nsCOMPtr<nsICookieManager>& cookieMgr2 = cookieMgr;
+ ASSERT_TRUE(cookieMgr2);
+
+ mozilla::OriginAttributes attrs;
+
+ // first, ensure a clean slate
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+ // add some cookies
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative("cookiemgr.test"_ns, // domain
+ "/foo"_ns, // path
+ "test1"_ns, // name
+ "yes"_ns, // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MAX, // expiry time
+ &attrs, // originAttributes
+ nsICookie::SAMESITE_NONE,
+ nsICookie::SCHEME_HTTPS)));
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(
+ "cookiemgr.test"_ns, // domain
+ "/foo"_ns, // path
+ "test2"_ns, // name
+ "yes"_ns, // value
+ false, // is secure
+ true, // is httponly
+ true, // is session
+ PR_Now() / PR_USEC_PER_SEC + 2, // expiry time
+ &attrs, // originAttributes
+ nsICookie::SAMESITE_NONE, nsICookie::SCHEME_HTTPS)));
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative("new.domain"_ns, // domain
+ "/rabbit"_ns, // path
+ "test3"_ns, // name
+ "yes"_ns, // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MAX, // expiry time
+ &attrs, // originAttributes
+ nsICookie::SAMESITE_NONE,
+ nsICookie::SCHEME_HTTPS)));
+ // confirm using enumerator
+ nsTArray<RefPtr<nsICookie>> cookies;
+ EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies));
+ nsCOMPtr<nsICookie> expiredCookie, newDomainCookie;
+ for (const auto& cookie : cookies) {
+ nsAutoCString name;
+ cookie->GetName(name);
+ if (name.EqualsLiteral("test2")) {
+ expiredCookie = cookie;
+ } else if (name.EqualsLiteral("test3")) {
+ newDomainCookie = cookie;
+ }
+ }
+ EXPECT_EQ(cookies.Length(), 3ul);
+ // check the httpOnly attribute of the second cookie is honored
+ GetACookie(cookieService, "http://cookiemgr.test/foo/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test2=yes"));
+ GetACookieNoHttp(cookieService, "http://cookiemgr.test/foo/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test2=yes"));
+ // check CountCookiesFromHost()
+ uint32_t hostCookies = 0;
+ EXPECT_TRUE(NS_SUCCEEDED(
+ cookieMgr2->CountCookiesFromHost("cookiemgr.test"_ns, &hostCookies)));
+ EXPECT_EQ(hostCookies, 2u);
+ // check CookieExistsNative() using the third cookie
+ bool found;
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(
+ "new.domain"_ns, "/rabbit"_ns, "test3"_ns, &attrs, &found)));
+ EXPECT_TRUE(found);
+
+ // sleep four seconds, to make sure the second cookie has expired
+ PR_Sleep(4 * PR_TicksPerSecond());
+ // check that both CountCookiesFromHost() and CookieExistsNative() count the
+ // expired cookie
+ EXPECT_TRUE(NS_SUCCEEDED(
+ cookieMgr2->CountCookiesFromHost("cookiemgr.test"_ns, &hostCookies)));
+ EXPECT_EQ(hostCookies, 2u);
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(
+ "cookiemgr.test"_ns, "/foo"_ns, "test2"_ns, &attrs, &found)));
+ EXPECT_TRUE(found);
+ // double-check RemoveAll() using the enumerator
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+ cookies.SetLength(0);
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetCookies(cookies)) &&
+ cookies.IsEmpty());
+
+ // *** eviction and creation ordering tests
+
+ // test that cookies are
+ // a) returned by order of creation time (oldest first, newest last)
+ // b) evicted by order of lastAccessed time, if the limit on cookies per host
+ // (50) is reached
+ nsAutoCString name;
+ nsAutoCString expected;
+ for (int32_t i = 0; i < 60; ++i) {
+ name = "test"_ns;
+ name.AppendInt(i);
+ name += "=creation"_ns;
+ SetACookie(cookieService, "http://creation.ordering.tests/", name.get());
+
+ if (i >= 10) {
+ expected += name;
+ if (i < 59) expected += "; "_ns;
+ }
+ }
+ GetACookie(cookieService, "http://creation.ordering.tests/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, expected.get()));
+
+ cookieMgr->RemoveAll();
+
+ for (int32_t i = 0; i < 60; ++i) {
+ name = "test"_ns;
+ name.AppendInt(i);
+ name += "=delete_non_security"_ns;
+
+ // Create 50 cookies that include the secure flag.
+ if (i < 50) {
+ name += "; secure"_ns;
+ SetACookie(cookieService, "https://creation.ordering.tests/", name.get());
+ } else {
+ // non-security cookies will be removed beside the latest cookie that be
+ // created.
+ SetACookie(cookieService, "http://creation.ordering.tests/", name.get());
+ }
+ }
+ GetACookie(cookieService, "http://creation.ordering.tests/", cookie);
+
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ // *** SameSite attribute - parsing and cookie storage tests
+ // Clear the cookies
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+
+ // None of these cookies will be set because using
+ // CookieJarSettings::GetBlockingAll().
+ SetACookieJarBlocked(cookieService, "http://samesite.test", "unset=yes");
+ SetACookieJarBlocked(cookieService, "http://samesite.test",
+ "unspecified=yes; samesite");
+ SetACookieJarBlocked(cookieService, "http://samesite.test",
+ "empty=yes; samesite=");
+ SetACookieJarBlocked(cookieService, "http://samesite.test",
+ "bogus=yes; samesite=bogus");
+ SetACookieJarBlocked(cookieService, "http://samesite.test",
+ "strict=yes; samesite=strict");
+ SetACookieJarBlocked(cookieService, "http://samesite.test",
+ "lax=yes; samesite=lax");
+
+ cookies.SetLength(0);
+ EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies));
+
+ EXPECT_TRUE(cookies.IsEmpty());
+
+ // Set cookies with various incantations of the samesite attribute:
+ // No same site attribute present
+ SetACookie(cookieService, "http://samesite.test", "unset=yes");
+ // samesite attribute present but with no value
+ SetACookie(cookieService, "http://samesite.test",
+ "unspecified=yes; samesite");
+ // samesite attribute present but with an empty value
+ SetACookie(cookieService, "http://samesite.test", "empty=yes; samesite=");
+ // samesite attribute present but with an invalid value
+ SetACookie(cookieService, "http://samesite.test",
+ "bogus=yes; samesite=bogus");
+ // samesite=strict
+ SetACookie(cookieService, "http://samesite.test",
+ "strict=yes; samesite=strict");
+ // samesite=lax
+ SetACookie(cookieService, "http://samesite.test", "lax=yes; samesite=lax");
+
+ cookies.SetLength(0);
+ EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies));
+
+ // check the cookies for the required samesite value
+ for (const auto& cookie : cookies) {
+ nsAutoCString name;
+ cookie->GetName(name);
+ int32_t sameSiteAttr;
+ cookie->GetSameSite(&sameSiteAttr);
+ if (name.EqualsLiteral("unset")) {
+ EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE);
+ } else if (name.EqualsLiteral("unspecified")) {
+ EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE);
+ } else if (name.EqualsLiteral("empty")) {
+ EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE);
+ } else if (name.EqualsLiteral("bogus")) {
+ EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE);
+ } else if (name.EqualsLiteral("strict")) {
+ EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_STRICT);
+ } else if (name.EqualsLiteral("lax")) {
+ EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_LAX);
+ }
+ }
+
+ EXPECT_TRUE(cookies.Length() == 6);
+
+ // *** SameSite attribute
+ // Clear the cookies
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+
+ // please note that the flag aForeign is always set to true using this test
+ // setup because no nsIChannel is passed to SetCookieString(). therefore we
+ // can only test that no cookies are sent for cross origin requests using
+ // same-site cookies.
+ SetACookie(cookieService, "http://www.samesite.com",
+ "test=sameSiteStrictVal; samesite=strict");
+ GetACookie(cookieService, "http://www.notsamesite.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://www.samesite.test",
+ "test=sameSiteLaxVal; samesite=lax");
+ GetACookie(cookieService, "http://www.notsamesite.com", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ static const char* secureURIs[] = {
+ "http://localhost", "http://localhost:1234", "http://127.0.0.1",
+ "http://127.0.0.2", "http://127.1.0.1", "http://[::1]",
+ // TODO bug 1220810 "http://xyzzy.localhost"
+ };
+
+ uint32_t numSecureURIs = sizeof(secureURIs) / sizeof(const char*);
+ for (uint32_t i = 0; i < numSecureURIs; ++i) {
+ SetACookie(cookieService, secureURIs[i], "test=basic; secure");
+ GetACookie(cookieService, secureURIs[i], cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic"));
+ SetACookie(cookieService, secureURIs[i], "test=basic1");
+ GetACookie(cookieService, secureURIs[i], cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic1"));
+ }
+
+ // XXX the following are placeholders: add these tests please!
+ // *** "noncompliant cookie" tests
+ // *** IP address tests
+ // *** speed tests
+}
+
+TEST(TestCookie, SameSiteLax)
+{
+ Preferences::SetBool("network.cookie.sameSite.laxByDefault", true);
+
+ nsresult rv;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsICookieManager> cookieMgr =
+ do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+
+ SetACookie(cookieService, "http://samesite.test", "unset=yes");
+
+ nsTArray<RefPtr<nsICookie>> cookies;
+ EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies));
+ EXPECT_EQ(cookies.Length(), (uint64_t)1);
+
+ Cookie* cookie = static_cast<Cookie*>(cookies[0].get());
+ EXPECT_EQ(cookie->RawSameSite(), nsICookie::SAMESITE_NONE);
+ EXPECT_EQ(cookie->SameSite(), nsICookie::SAMESITE_LAX);
+
+ Preferences::SetCString("network.cookie.sameSite.laxByDefault.disabledHosts",
+ "foo.com,samesite.test,bar.net");
+
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+
+ cookies.SetLength(0);
+ EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies));
+ EXPECT_EQ(cookies.Length(), (uint64_t)0);
+
+ SetACookie(cookieService, "http://samesite.test", "unset=yes");
+
+ cookies.SetLength(0);
+ EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies));
+ EXPECT_EQ(cookies.Length(), (uint64_t)1);
+
+ cookie = static_cast<Cookie*>(cookies[0].get());
+ EXPECT_EQ(cookie->RawSameSite(), nsICookie::SAMESITE_NONE);
+ EXPECT_EQ(cookie->SameSite(), nsICookie::SAMESITE_LAX);
+}
+
+TEST(TestCookie, OnionSite)
+{
+ Preferences::SetBool("dom.securecontext.allowlist_onions", true);
+ Preferences::SetBool("network.cookie.sameSite.laxByDefault", false);
+
+ nsresult rv;
+ nsCString cookie;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // .onion secure cookie tests
+ SetACookie(cookieService, "http://123456789abcdef.onion/",
+ "test=onion-security; secure");
+ GetACookieNoHttp(cookieService, "https://123456789abcdef.onion/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security"));
+ SetACookie(cookieService, "http://123456789abcdef.onion/",
+ "test=onion-security2; secure");
+ GetACookieNoHttp(cookieService, "http://123456789abcdef.onion/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security2"));
+ SetACookie(cookieService, "https://123456789abcdef.onion/",
+ "test=onion-security3; secure");
+ GetACookieNoHttp(cookieService, "http://123456789abcdef.onion/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security3"));
+ SetACookie(cookieService, "http://123456789abcdef.onion/",
+ "test=onion-security4");
+ GetACookieNoHttp(cookieService, "http://123456789abcdef.onion/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security4"));
+}
+
+TEST(TestCookie, HiddenPrefix)
+{
+ nsresult rv;
+ nsCString cookie;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ SetACookie(cookieService, "http://hiddenprefix.test/", "=__Host-test=a");
+ GetACookie(cookieService, "http://hiddenprefix.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://hiddenprefix.test/", "=__Secure-test=a");
+ GetACookie(cookieService, "http://hiddenprefix.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://hiddenprefix.test/", "=__Host-check");
+ GetACookie(cookieService, "http://hiddenprefix.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://hiddenprefix.test/", "=__Secure-check");
+ GetACookie(cookieService, "http://hiddenprefix.test/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+}
+
+TEST(TestCookie, BlockUnicode)
+{
+ Preferences::SetBool("network.cookie.blockUnicode", true);
+
+ nsresult rv;
+ nsCString cookie;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ SetACookie(cookieService, "http://unicode.com/", "name=🍪");
+ GetACookie(cookieService, "http://unicode.com/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ SetACookie(cookieService, "http://unicode.com/", "🍪=value");
+ GetACookie(cookieService, "http://unicode.com/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
+
+ Preferences::SetBool("network.cookie.blockUnicode", false);
+
+ SetACookie(cookieService, "http://unicode.com/", "name=🍪");
+ GetACookie(cookieService, "http://unicode.com/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "name=🍪"));
+
+ nsCOMPtr<nsICookieManager> cookieMgr =
+ do_GetService(NS_COOKIEMANAGER_CONTRACTID);
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+
+ SetACookie(cookieService, "http://unicode.com/", "🍪=value");
+ GetACookie(cookieService, "http://unicode.com/", cookie);
+ EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "🍪=value"));
+
+ EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll());
+ Preferences::ClearUser("network.cookie.blockUnicode");
+}
diff --git a/netwerk/test/gtest/TestDNSPacket.cpp b/netwerk/test/gtest/TestDNSPacket.cpp
new file mode 100644
index 0000000000..49530b80e0
--- /dev/null
+++ b/netwerk/test/gtest/TestDNSPacket.cpp
@@ -0,0 +1,69 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/net/DNSPacket.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+void AssertDnsPadding(uint32_t PaddingLength, unsigned int WithPadding,
+ unsigned int WithoutPadding, bool DisableEcn,
+ const nsCString& host) {
+ DNSPacket encoder;
+ nsCString buf;
+
+ ASSERT_EQ(Preferences::SetUint("network.trr.padding.length", PaddingLength),
+ NS_OK);
+
+ ASSERT_EQ(Preferences::SetBool("network.trr.padding", true), NS_OK);
+ ASSERT_EQ(encoder.EncodeRequest(buf, host, 1, DisableEcn), NS_OK);
+ ASSERT_EQ(buf.Length(), WithPadding);
+
+ ASSERT_EQ(Preferences::SetBool("network.trr.padding", false), NS_OK);
+ ASSERT_EQ(encoder.EncodeRequest(buf, host, 1, DisableEcn), NS_OK);
+ ASSERT_EQ(buf.Length(), WithoutPadding);
+}
+
+TEST(TestDNSPacket, PaddingLenEcn)
+{
+ AssertDnsPadding(16, 48, 41, true, "a.de"_ns);
+ AssertDnsPadding(16, 48, 42, true, "ab.de"_ns);
+ AssertDnsPadding(16, 48, 43, true, "abc.de"_ns);
+ AssertDnsPadding(16, 48, 44, true, "abcd.de"_ns);
+ AssertDnsPadding(16, 64, 45, true, "abcde.de"_ns);
+ AssertDnsPadding(16, 64, 46, true, "abcdef.de"_ns);
+ AssertDnsPadding(16, 64, 47, true, "abcdefg.de"_ns);
+ AssertDnsPadding(16, 64, 48, true, "abcdefgh.de"_ns);
+}
+
+TEST(TestDNSPacket, PaddingLenDisableEcn)
+{
+ AssertDnsPadding(16, 48, 22, false, "a.de"_ns);
+ AssertDnsPadding(16, 48, 23, false, "ab.de"_ns);
+ AssertDnsPadding(16, 48, 24, false, "abc.de"_ns);
+ AssertDnsPadding(16, 48, 25, false, "abcd.de"_ns);
+ AssertDnsPadding(16, 48, 26, false, "abcde.de"_ns);
+ AssertDnsPadding(16, 48, 27, false, "abcdef.de"_ns);
+ AssertDnsPadding(16, 48, 32, false, "abcdefghijk.de"_ns);
+ AssertDnsPadding(16, 48, 33, false, "abcdefghijkl.de"_ns);
+ AssertDnsPadding(16, 64, 34, false, "abcdefghijklm.de"_ns);
+ AssertDnsPadding(16, 64, 35, false, "abcdefghijklmn.de"_ns);
+}
+
+TEST(TestDNSPacket, PaddingLengths)
+{
+ AssertDnsPadding(0, 45, 41, true, "a.de"_ns);
+ AssertDnsPadding(1, 45, 41, true, "a.de"_ns);
+ AssertDnsPadding(2, 46, 41, true, "a.de"_ns);
+ AssertDnsPadding(3, 45, 41, true, "a.de"_ns);
+ AssertDnsPadding(4, 48, 41, true, "a.de"_ns);
+ AssertDnsPadding(16, 48, 41, true, "a.de"_ns);
+ AssertDnsPadding(32, 64, 41, true, "a.de"_ns);
+ AssertDnsPadding(42, 84, 41, true, "a.de"_ns);
+ AssertDnsPadding(52, 52, 41, true, "a.de"_ns);
+ AssertDnsPadding(80, 80, 41, true, "a.de"_ns);
+ AssertDnsPadding(128, 128, 41, true, "a.de"_ns);
+ AssertDnsPadding(256, 256, 41, true, "a.de"_ns);
+ AssertDnsPadding(1024, 1024, 41, true, "a.de"_ns);
+ AssertDnsPadding(1025, 1024, 41, true, "a.de"_ns);
+}
diff --git a/netwerk/test/gtest/TestHeaders.cpp b/netwerk/test/gtest/TestHeaders.cpp
new file mode 100644
index 0000000000..0da6b06c70
--- /dev/null
+++ b/netwerk/test/gtest/TestHeaders.cpp
@@ -0,0 +1,29 @@
+#include "gtest/gtest.h"
+
+#include "nsHttpHeaderArray.h"
+
+TEST(TestHeaders, DuplicateHSTS)
+{
+ // When the Strict-Transport-Security header is sent multiple times, its
+ // effective value is the value of the first item. It is not merged as other
+ // headers are.
+ mozilla::net::nsHttpHeaderArray headers;
+ nsresult rv = headers.SetHeaderFromNet(
+ mozilla::net::nsHttp::Strict_Transport_Security,
+ "Strict_Transport_Security"_ns, "max-age=360"_ns, true);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsAutoCString h;
+ rv = headers.GetHeader(mozilla::net::nsHttp::Strict_Transport_Security, h);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(h.get(), "max-age=360");
+
+ rv = headers.SetHeaderFromNet(mozilla::net::nsHttp::Strict_Transport_Security,
+ "Strict_Transport_Security"_ns,
+ "max-age=720"_ns, true);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = headers.GetHeader(mozilla::net::nsHttp::Strict_Transport_Security, h);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(h.get(), "max-age=360");
+}
diff --git a/netwerk/test/gtest/TestHttpAtom.cpp b/netwerk/test/gtest/TestHttpAtom.cpp
new file mode 100644
index 0000000000..917210a2c4
--- /dev/null
+++ b/netwerk/test/gtest/TestHttpAtom.cpp
@@ -0,0 +1,39 @@
+#include "gtest/gtest.h"
+
+#include "nsHttp.h"
+
+TEST(TestHttpAtom, AtomComparison)
+{
+ mozilla::net::nsHttpAtom atom(mozilla::net::nsHttp::Host);
+ mozilla::net::nsHttpAtom same_atom(mozilla::net::nsHttp::Host);
+ mozilla::net::nsHttpAtom different_atom(mozilla::net::nsHttp::Accept);
+
+ ASSERT_EQ(atom, atom);
+ ASSERT_EQ(atom, mozilla::net::nsHttp::Host);
+ ASSERT_EQ(mozilla::net::nsHttp::Host, atom);
+ ASSERT_EQ(atom, same_atom);
+ ASSERT_EQ(atom.get(), same_atom.get());
+ ASSERT_EQ(atom.get(), mozilla::net::nsHttp::Host.get());
+
+ ASSERT_NE(atom, different_atom);
+ ASSERT_NE(atom.get(), different_atom.get());
+}
+
+TEST(TestHttpAtom, LiteralComparison)
+{
+ ASSERT_EQ(mozilla::net::nsHttp::Host, mozilla::net::nsHttp::Host);
+ ASSERT_NE(mozilla::net::nsHttp::Host, mozilla::net::nsHttp::Accept);
+
+ ASSERT_EQ(mozilla::net::nsHttp::Host.get(), mozilla::net::nsHttp::Host.get());
+ ASSERT_NE(mozilla::net::nsHttp::Host.get(),
+ mozilla::net::nsHttp::Accept.get());
+}
+
+TEST(TestHttpAtom, Validity)
+{
+ mozilla::net::nsHttpAtom atom(mozilla::net::nsHttp::Host);
+ ASSERT_TRUE(atom);
+
+ mozilla::net::nsHttpAtom atom_empty;
+ ASSERT_FALSE(atom_empty);
+}
diff --git a/netwerk/test/gtest/TestHttpAuthUtils.cpp b/netwerk/test/gtest/TestHttpAuthUtils.cpp
new file mode 100644
index 0000000000..78fb40d4d0
--- /dev/null
+++ b/netwerk/test/gtest/TestHttpAuthUtils.cpp
@@ -0,0 +1,43 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/net/HttpAuthUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+#define TEST_PREF "network.http_test.auth_utils"
+
+TEST(TestHttpAuthUtils, Bug1351301)
+{
+ nsCOMPtr<nsIURI> url;
+ nsAutoCString spec;
+
+ ASSERT_EQ(Preferences::SetCString(TEST_PREF, "bar.com"), NS_OK);
+ spec = "http://bar.com";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), true);
+
+ spec = "http://foo.bar.com";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), true);
+
+ spec = "http://foobar.com";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), false);
+
+ ASSERT_EQ(Preferences::SetCString(TEST_PREF, ".bar.com"), NS_OK);
+ spec = "http://foo.bar.com";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), true);
+
+ spec = "http://bar.com";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), false);
+
+ ASSERT_EQ(Preferences::ClearUser(TEST_PREF), NS_OK);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestHttpChannel.cpp b/netwerk/test/gtest/TestHttpChannel.cpp
new file mode 100644
index 0000000000..10ef744bb4
--- /dev/null
+++ b/netwerk/test/gtest/TestHttpChannel.cpp
@@ -0,0 +1,135 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PreloadHashKey.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+#include "nsThreadUtils.h"
+#include "nsStringStream.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+class FakeListener : public nsIStreamListener, public nsIInterfaceRequestor {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ enum { Never, OnStart, OnData, OnStop } mCancelIn = Never;
+
+ nsresult mOnStartResult = NS_OK;
+ nsresult mOnDataResult = NS_OK;
+ nsresult mOnStopResult = NS_OK;
+
+ bool mOnStart = false;
+ nsCString mOnData;
+ Maybe<nsresult> mOnStop;
+
+ private:
+ virtual ~FakeListener() = default;
+};
+
+NS_IMPL_ISUPPORTS(FakeListener, nsIStreamListener, nsIRequestObserver,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+FakeListener::GetInterface(const nsIID& aIID, void** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP FakeListener::OnStartRequest(nsIRequest* request) {
+ EXPECT_FALSE(mOnStart);
+ mOnStart = true;
+
+ if (mCancelIn == OnStart) {
+ request->Cancel(NS_ERROR_ABORT);
+ }
+
+ return mOnStartResult;
+}
+
+NS_IMETHODIMP FakeListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* input,
+ uint64_t offset, uint32_t count) {
+ nsAutoCString data;
+ data.SetLength(count);
+
+ uint32_t read;
+ input->Read(data.BeginWriting(), count, &read);
+ mOnData += data;
+
+ if (mCancelIn == OnData) {
+ request->Cancel(NS_ERROR_ABORT);
+ }
+
+ return mOnDataResult;
+}
+
+NS_IMETHODIMP FakeListener::OnStopRequest(nsIRequest* request,
+ nsresult status) {
+ EXPECT_FALSE(mOnStop);
+ mOnStop.emplace(status);
+
+ if (mCancelIn == OnStop) {
+ request->Cancel(NS_ERROR_ABORT);
+ }
+
+ return mOnStopResult;
+}
+
+// Test that nsHttpChannel::AsyncOpen properly picks up changes to
+// loadInfo.mPrivateBrowsingId that occur after the channel was created.
+TEST(TestHttpChannel, PBAsyncOpen)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "http://localhost/"_ns);
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(
+ getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ ASSERT_EQ(rv, NS_OK);
+
+ RefPtr<FakeListener> listener = new FakeListener();
+ rv = channel->SetNotificationCallbacks(listener);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbchannel = do_QueryInterface(channel);
+ ASSERT_TRUE(pbchannel);
+
+ bool isPrivate = false;
+ rv = pbchannel->GetIsChannelPrivate(&isPrivate);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(isPrivate, false);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ OriginAttributes attrs;
+ attrs.mPrivateBrowsingId = 1;
+ rv = loadInfo->SetOriginAttributes(attrs);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = pbchannel->GetIsChannelPrivate(&isPrivate);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(isPrivate, false);
+
+ rv = channel->AsyncOpen(listener);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = pbchannel->GetIsChannelPrivate(&isPrivate);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(isPrivate, true);
+
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TEST(TestHttpChannel, PBAsyncOpen)"_ns,
+ [&]() -> bool { return listener->mOnStop.isSome(); }));
+}
diff --git a/netwerk/test/gtest/TestHttpResponseHead.cpp b/netwerk/test/gtest/TestHttpResponseHead.cpp
new file mode 100644
index 0000000000..643489d496
--- /dev/null
+++ b/netwerk/test/gtest/TestHttpResponseHead.cpp
@@ -0,0 +1,183 @@
+#include "gtest/gtest.h"
+
+#include "chrome/common/ipc_message.h"
+#include "mozilla/net/PHttpChannelParams.h"
+#include "mozilla/Unused.h"
+#include "nsHttp.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsURLHelper.h"
+
+namespace mozilla {
+namespace net {
+
+void AssertRoundTrips(const nsHttpResponseHead& aHead) {
+ {
+ // Assert it round-trips via IPC.
+ UniquePtr<IPC::Message> msg(new IPC::Message(MSG_ROUTING_NONE, 0));
+ IPC::MessageWriter writer(*msg);
+ IPC::ParamTraits<nsHttpResponseHead>::Write(&writer, aHead);
+
+ nsHttpResponseHead deserializedHead;
+ IPC::MessageReader reader(*msg);
+ bool res = IPC::ParamTraits<mozilla::net::nsHttpResponseHead>::Read(
+ &reader, &deserializedHead);
+ ASSERT_TRUE(res);
+ ASSERT_EQ(aHead, deserializedHead);
+ }
+
+ {
+ // Assert it round-trips through copy-ctor.
+ nsHttpResponseHead copied(aHead);
+ ASSERT_EQ(aHead, copied);
+ }
+
+ {
+ // Assert it round-trips through operator=
+ nsHttpResponseHead copied;
+ copied = aHead;
+ // It is important that the below statement cannot be
+ // ASSERT_EQ(aHead, copied) to avoid potential lock-order inversion problem.
+ // See Bug 1829445 for more details
+ ASSERT_EQ(copied, aHead);
+ }
+}
+
+TEST(TestHttpResponseHead, Bug1636930)
+{
+ nsHttpResponseHead head;
+
+ Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
+ Unused << head.ParseHeaderLine("content-type: text/plain"_ns);
+ Unused << head.ParseHeaderLine("etag: Just testing"_ns);
+ Unused << head.ParseHeaderLine("cache-control: max-age=99999"_ns);
+ Unused << head.ParseHeaderLine("accept-ranges: bytes"_ns);
+ Unused << head.ParseHeaderLine("content-length: 1408"_ns);
+ Unused << head.ParseHeaderLine("connection: close"_ns);
+ Unused << head.ParseHeaderLine("server: httpd.js"_ns);
+ Unused << head.ParseHeaderLine("date: Tue, 12 May 2020 09:24:23 GMT"_ns);
+
+ AssertRoundTrips(head);
+}
+
+TEST(TestHttpResponseHead, bug1649807)
+{
+ nsHttpResponseHead head;
+
+ Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
+ Unused << head.ParseHeaderLine("content-type: text/plain"_ns);
+ Unused << head.ParseHeaderLine("etag: Just testing"_ns);
+ Unused << head.ParseHeaderLine("cache-control: age=99999"_ns);
+ Unused << head.ParseHeaderLine("accept-ranges: bytes"_ns);
+ Unused << head.ParseHeaderLine("content-length: 1408"_ns);
+ Unused << head.ParseHeaderLine("connection: close"_ns);
+ Unused << head.ParseHeaderLine("server: httpd.js"_ns);
+ Unused << head.ParseHeaderLine("pragma: no-cache"_ns);
+ Unused << head.ParseHeaderLine("date: Tue, 12 May 2020 09:24:23 GMT"_ns);
+
+ ASSERT_FALSE(head.NoCache())
+ << "Cache-Control wins over Pragma: no-cache";
+ AssertRoundTrips(head);
+}
+
+TEST(TestHttpResponseHead, bug1660200)
+{
+ nsHttpResponseHead head;
+
+ Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
+ Unused << head.ParseHeaderLine("content-type: text/plain"_ns);
+ Unused << head.ParseHeaderLine("etag: Just testing"_ns);
+ Unused << head.ParseHeaderLine("cache-control: no-cache"_ns);
+ Unused << head.ParseHeaderLine("accept-ranges: bytes"_ns);
+ Unused << head.ParseHeaderLine("content-length: 1408"_ns);
+ Unused << head.ParseHeaderLine("connection: close"_ns);
+ Unused << head.ParseHeaderLine("server: httpd.js"_ns);
+ Unused << head.ParseHeaderLine("date: Tue, 12 May 2020 09:24:23 GMT"_ns);
+
+ AssertRoundTrips(head);
+}
+
+TEST(TestHttpResponseHead, bug1687903)
+{
+ nsHttpResponseHead head;
+
+ bool usingStrictParsing = false;
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->GetBoolPref("network.http.strict_response_status_line_parsing",
+ &usingStrictParsing);
+ }
+
+ nsresult expectation =
+ usingStrictParsing ? NS_ERROR_PARSING_HTTP_STATUS_LINE : NS_OK;
+
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 "_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 BLAH"_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 1000 BOO"_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 0200 BOO"_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 60200 200"_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 131072 HIOK"_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 -200 OK"_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 0x9 OK"_ns));
+ ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 C8 OK"_ns));
+}
+
+TEST(TestHttpResponseHead, atoms)
+{
+ // Test that the resolving the content-type atom returns the initial static
+ ASSERT_EQ(nsHttp::Content_Type, nsHttp::ResolveAtom("content-type"_ns));
+ // Check that they're case insensitive
+ ASSERT_EQ(nsHttp::ResolveAtom("Content-Type"_ns),
+ nsHttp::ResolveAtom("content-type"_ns));
+ // This string literal should be the backing of the atom when resolved first
+ auto header1 = "CustomHeaderXXX1"_ns;
+ auto atom1 = nsHttp::ResolveAtom(header1);
+ auto header2 = "customheaderxxx1"_ns;
+ auto atom2 = nsHttp::ResolveAtom(header2);
+ ASSERT_EQ(atom1, atom2);
+ ASSERT_EQ(atom1.get(), atom2.get());
+ // Check that we get the expected pointer back.
+ ASSERT_EQ(atom2.get(), header1.BeginReading());
+}
+
+TEST(ContentTypeParsing, CommentHandling1)
+{
+ bool dummy;
+ const nsAutoCString val("text/html;charset=gbk(");
+ nsCString contentType;
+ nsCString contentCharset;
+
+ net_ParseContentType(val, contentType, contentCharset, &dummy);
+
+ ASSERT_TRUE(contentType.EqualsLiteral("text/html"));
+ ASSERT_TRUE(contentCharset.EqualsLiteral("gbk("));
+}
+
+TEST(ContentTypeParsing, CommentHandling2)
+{
+ bool dummy;
+ const nsAutoCString val("text/html;x=(;charset=gbk");
+ nsCString contentType;
+ nsCString contentCharset;
+
+ net_ParseContentType(val, contentType, contentCharset, &dummy);
+
+ ASSERT_TRUE(contentType.EqualsLiteral("text/html"));
+ ASSERT_TRUE(contentCharset.EqualsLiteral("gbk"));
+}
+
+TEST(ContentTypeParsing, CommentHandling3)
+{
+ bool dummy;
+ const nsAutoCString val("text/html;test=test;(;charset=gbk");
+ nsCString contentType;
+ nsCString contentCharset;
+
+ net_ParseContentType(val, contentType, contentCharset, &dummy);
+
+ ASSERT_TRUE(contentType.EqualsLiteral("text/html"));
+ ASSERT_TRUE(contentCharset.EqualsLiteral("gbk"));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestInputStreamTransport.cpp b/netwerk/test/gtest/TestInputStreamTransport.cpp
new file mode 100644
index 0000000000..43df0e193a
--- /dev/null
+++ b/netwerk/test/gtest/TestInputStreamTransport.cpp
@@ -0,0 +1,204 @@
+#include "gtest/gtest.h"
+
+#include "nsIStreamTransportService.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "Helpers.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsITransport.h"
+#include "nsNetUtil.h"
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+void CreateStream(already_AddRefed<nsIInputStream> aSource,
+ nsIAsyncInputStream** aStream) {
+ nsCOMPtr<nsIInputStream> source = std::move(aSource);
+
+ nsresult rv;
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ ASSERT_EQ(NS_OK, rv);
+
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateInputTransport(source, true, getter_AddRefs(transport));
+ ASSERT_EQ(NS_OK, rv);
+
+ nsCOMPtr<nsIInputStream> wrapper;
+ rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
+ ASSERT_EQ(NS_OK, rv);
+
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(wrapper);
+ MOZ_ASSERT(asyncStream);
+
+ asyncStream.forget(aStream);
+}
+
+class BlockingSyncStream final : public nsIInputStream {
+ nsCOMPtr<nsIInputStream> mStream;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit BlockingSyncStream(const nsACString& aBuffer) {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return mStream->Available(aLength); }
+
+ NS_IMETHOD
+ StreamStatus() override { return mStream->StreamStatus(); }
+
+ NS_IMETHOD
+ Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override {
+ return mStream->Read(aBuffer, aCount, aReadCount);
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) override {
+ return mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+ }
+
+ NS_IMETHOD
+ Close() override { return mStream->Close(); }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override {
+ *aNonBlocking = false;
+ return NS_OK;
+ }
+
+ private:
+ ~BlockingSyncStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(BlockingSyncStream, nsIInputStream)
+
+// Testing a simple blocking stream.
+TEST(TestInputStreamTransport, BlockingNotAsync)
+{
+ RefPtr<BlockingSyncStream> stream = new BlockingSyncStream("Hello world"_ns);
+
+ nsCOMPtr<nsIAsyncInputStream> ais;
+ CreateStream(stream.forget(), getter_AddRefs(ais));
+ ASSERT_TRUE(!!ais);
+
+ nsAutoCString data;
+ nsresult rv = NS_ReadInputStreamToString(ais, data, -1);
+ ASSERT_EQ(NS_OK, rv);
+
+ ASSERT_TRUE(data.EqualsLiteral("Hello world"));
+}
+
+class BlockingAsyncStream final : public nsIAsyncInputStream {
+ nsCOMPtr<nsIInputStream> mStream;
+ bool mPending;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit BlockingAsyncStream(const nsACString& aBuffer) : mPending(false) {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override {
+ mStream->Available(aLength);
+
+ // 1 char at the time, just to test the asyncWait+Read loop a bit more.
+ if (*aLength > 0) {
+ *aLength = 1;
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ StreamStatus() override { return mStream->StreamStatus(); }
+
+ NS_IMETHOD
+ Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override {
+ mPending = !mPending;
+ if (mPending) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // 1 char at the time, just to test the asyncWait+Read loop a bit more.
+ aCount = 1;
+
+ return mStream->Read(aBuffer, aCount, aReadCount);
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) override {
+ mPending = !mPending;
+ if (mPending) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // 1 char at the time, just to test the asyncWait+Read loop a bit more.
+ aCount = 1;
+
+ return mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+ }
+
+ NS_IMETHOD
+ Close() override { return mStream->Close(); }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override {
+ *aNonBlocking = false;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ CloseWithStatus(nsresult aStatus) override { return Close(); }
+
+ NS_IMETHOD
+ AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override {
+ if (!aCallback) {
+ return NS_OK;
+ }
+
+ RefPtr<BlockingAsyncStream> self = this;
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "gtest-asyncwait",
+ [self, callback]() { callback->OnInputStreamReady(self); });
+
+ if (aEventTarget) {
+ aEventTarget->Dispatch(r.forget());
+ } else {
+ r->Run();
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~BlockingAsyncStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(BlockingAsyncStream, nsIInputStream, nsIAsyncInputStream)
+
+// Testing an async blocking stream.
+TEST(TestInputStreamTransport, BlockingAsync)
+{
+ RefPtr<BlockingAsyncStream> stream =
+ new BlockingAsyncStream("Hello world"_ns);
+
+ nsCOMPtr<nsIAsyncInputStream> ais;
+ CreateStream(stream.forget(), getter_AddRefs(ais));
+ ASSERT_TRUE(!!ais);
+
+ nsAutoCString data;
+ nsresult rv = NS_ReadInputStreamToString(ais, data, -1);
+ ASSERT_EQ(NS_OK, rv);
+
+ ASSERT_TRUE(data.EqualsLiteral("Hello world"));
+}
diff --git a/netwerk/test/gtest/TestIsValidIp.cpp b/netwerk/test/gtest/TestIsValidIp.cpp
new file mode 100644
index 0000000000..dfee8c5c0f
--- /dev/null
+++ b/netwerk/test/gtest/TestIsValidIp.cpp
@@ -0,0 +1,178 @@
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+#include "gtest/gtest.h"
+
+#include "nsURLHelper.h"
+
+TEST(TestIsValidIp, IPV4Localhost)
+{
+ constexpr auto ip = "127.0.0.1"_ns;
+ ASSERT_EQ(true, net_IsValidIPv4Addr(ip));
+}
+
+TEST(TestIsValidIp, IPV4Only0)
+{
+ constexpr auto ip = "0.0.0.0"_ns;
+ ASSERT_EQ(true, net_IsValidIPv4Addr(ip));
+}
+
+TEST(TestIsValidIp, IPV4Max)
+{
+ constexpr auto ip = "255.255.255.255"_ns;
+ ASSERT_EQ(true, net_IsValidIPv4Addr(ip));
+}
+
+TEST(TestIsValidIp, IPV4LeadingZero)
+{
+ constexpr auto ip = "055.225.255.255"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip));
+
+ constexpr auto ip2 = "255.055.255.255"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip2));
+
+ constexpr auto ip3 = "255.255.055.255"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip3));
+
+ constexpr auto ip4 = "255.255.255.055"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip4));
+}
+
+TEST(TestIsValidIp, IPV4StartWithADot)
+{
+ constexpr auto ip = ".192.168.120.197"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip));
+}
+
+TEST(TestIsValidIp, IPV4StartWith4Digits)
+{
+ constexpr auto ip = "1927.168.120.197"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip));
+}
+
+TEST(TestIsValidIp, IPV4OutOfRange)
+{
+ constexpr auto invalid1 = "421.168.120.124"_ns;
+ constexpr auto invalid2 = "192.997.120.124"_ns;
+ constexpr auto invalid3 = "192.168.300.124"_ns;
+ constexpr auto invalid4 = "192.168.120.256"_ns;
+
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid1));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid2));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid3));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid4));
+}
+
+TEST(TestIsValidIp, IPV4EmptyDigits)
+{
+ constexpr auto invalid1 = "..0.0.0"_ns;
+ constexpr auto invalid2 = "127..0.0"_ns;
+ constexpr auto invalid3 = "127.0..0"_ns;
+ constexpr auto invalid4 = "127.0.0."_ns;
+
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid1));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid2));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid3));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid4));
+}
+
+TEST(TestIsValidIp, IPV4NonNumeric)
+{
+ constexpr auto invalid1 = "127.0.0.f"_ns;
+ constexpr auto invalid2 = "127.0.0.!"_ns;
+ constexpr auto invalid3 = "127#0.0.1"_ns;
+
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid1));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid2));
+ ASSERT_EQ(false, net_IsValidIPv4Addr(invalid3));
+}
+
+TEST(TestIsValidIp, IPV4TooManyDigits)
+{
+ constexpr auto ip = "127.0.0.1.2"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip));
+}
+
+TEST(TestIsValidIp, IPV4TooFewDigits)
+{
+ constexpr auto ip = "127.0.1"_ns;
+ ASSERT_EQ(false, net_IsValidIPv4Addr(ip));
+}
+
+TEST(TestIsValidIp, IPV6WithIPV4Inside)
+{
+ constexpr auto ipv6 = "0123:4567:89ab:cdef:0123:4567:127.0.0.1"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPv6FullForm)
+{
+ constexpr auto ipv6 = "0123:4567:89ab:cdef:0123:4567:890a:bcde"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPv6TrimLeading0)
+{
+ constexpr auto ipv6 = "123:4567:0:0:123:4567:890a:bcde"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPv6Collapsed)
+{
+ constexpr auto ipv6 = "FF01::101"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPV6WithIPV4InsideCollapsed)
+{
+ constexpr auto ipv6 = "::FFFF:129.144.52.38"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPV6Localhost)
+{
+ constexpr auto ipv6 = "::1"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPV6LinkLocalPrefix)
+{
+ constexpr auto ipv6 = "fe80::"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPV6GlobalUnicastPrefix)
+{
+ constexpr auto ipv6 = "2001::"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPV6Unspecified)
+{
+ constexpr auto ipv6 = "::"_ns;
+ ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPV6InvalidIPV4Inside)
+{
+ constexpr auto ipv6 = "0123:4567:89ab:cdef:0123:4567:127.0."_ns;
+ ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6));
+}
+
+TEST(TestIsValidIp, IPV6InvalidCharacters)
+{
+ constexpr auto ipv6 = "012g:4567:89ab:cdef:0123:4567:127.0.0.1"_ns;
+ ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6));
+
+ constexpr auto ipv6pound = "0123:456#:89ab:cdef:0123:4567:127.0.0.1"_ns;
+ ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6pound));
+}
+
+TEST(TestIsValidIp, IPV6TooManyCharacters)
+{
+ constexpr auto ipv6 = "0123:45671:89ab:cdef:0123:4567:127.0.0.1"_ns;
+ ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6));
+}
+TEST(TestIsValidIp, IPV6DoubleDoubleDots)
+{
+ constexpr auto ipv6 = "0123::4567:890a::bcde:0123:4567"_ns;
+ ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6));
+}
diff --git a/netwerk/test/gtest/TestLinkHeader.cpp b/netwerk/test/gtest/TestLinkHeader.cpp
new file mode 100644
index 0000000000..4da7002f87
--- /dev/null
+++ b/netwerk/test/gtest/TestLinkHeader.cpp
@@ -0,0 +1,356 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <ostream>
+
+#include "gtest/gtest-param-test.h"
+#include "gtest/gtest.h"
+
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::net;
+
+LinkHeader LinkHeaderSetAll(nsAString const& v) {
+ LinkHeader l;
+ l.mHref = v;
+ l.mRel = v;
+ l.mTitle = v;
+ l.mIntegrity = v;
+ l.mSrcset = v;
+ l.mSizes = v;
+ l.mType = v;
+ l.mMedia = v;
+ l.mAnchor = v;
+ l.mCrossOrigin = v;
+ l.mReferrerPolicy = v;
+ l.mAs = v;
+ l.mFetchPriority = v;
+ return l;
+}
+
+LinkHeader LinkHeaderSetTitle(nsAString const& v) {
+ LinkHeader l;
+ l.mHref = v;
+ l.mRel = v;
+ l.mTitle = v;
+ return l;
+}
+
+LinkHeader LinkHeaderSetMinimum(nsAString const& v) {
+ LinkHeader l;
+ l.mHref = v;
+ l.mRel = v;
+ return l;
+}
+
+void PrintTo(const nsTArray<LinkHeader>& aLinkHeaders, std::ostream* aOs) {
+ bool first = true;
+ for (const auto& header : aLinkHeaders) {
+ if (!first) {
+ *aOs << ", ";
+ }
+ first = false;
+
+ *aOs << "(mHref=" << header.mHref << ", "
+ << "mRel=" << header.mRel << ", "
+ << "mTitle=" << header.mTitle << ", "
+ << "mNonce=" << header.mNonce << ", "
+ << "mIntegrity=" << header.mIntegrity << ", "
+ << "mSrcset=" << header.mSrcset << ", "
+ << "mSizes=" << header.mSizes << ", "
+ << "mType=" << header.mType << ", "
+ << "mMedia=" << header.mMedia << ", "
+ << "mAnchor=" << header.mAnchor << ", "
+ << "mCrossOrigin=" << header.mCrossOrigin << ", "
+ << "mReferrerPolicy=" << header.mReferrerPolicy << ", "
+ << "mAs=" << header.mAs << ", "
+ << "mFetchPriority=" << header.mFetchPriority << ")";
+ }
+}
+
+TEST(TestLinkHeader, MultipleLinkHeaders)
+{
+ nsString link =
+ u"<a>; rel=a; title=a; integrity=a; imagesrcset=a; imagesizes=a; type=a; media=a; anchor=a; crossorigin=a; referrerpolicy=a; as=a; fetchpriority=a,"_ns
+ u"<b>; rel=b; title=b; integrity=b; imagesrcset=b; imagesizes=b; type=b; media=b; anchor=b; crossorigin=b; referrerpolicy=b; as=b; fetchpriority=b,"_ns
+ u"<c>; rel=c"_ns;
+
+ nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(link);
+
+ nsTArray<LinkHeader> expected;
+ expected.AppendElement(LinkHeaderSetAll(u"a"_ns));
+ expected.AppendElement(LinkHeaderSetAll(u"b"_ns));
+ expected.AppendElement(LinkHeaderSetMinimum(u"c"_ns));
+
+ ASSERT_EQ(linkHeaders, expected);
+}
+
+// title* has to be tested separately
+TEST(TestLinkHeader, MultipleLinkHeadersTitleStar)
+{
+ nsString link =
+ u"<d>; rel=d; title*=UTF-8'de'd,"_ns
+ u"<e>; rel=e; title*=UTF-8'de'e; title=g,"_ns
+ u"<f>; rel=f"_ns;
+
+ nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(link);
+
+ nsTArray<LinkHeader> expected;
+ expected.AppendElement(LinkHeaderSetTitle(u"d"_ns));
+ expected.AppendElement(LinkHeaderSetTitle(u"e"_ns));
+ expected.AppendElement(LinkHeaderSetMinimum(u"f"_ns));
+
+ ASSERT_EQ(linkHeaders, expected);
+}
+
+struct SimpleParseTestData {
+ nsString link;
+ bool valid;
+ nsString url;
+ nsString rel;
+ nsString as;
+ nsString fetchpriority;
+
+ friend void PrintTo(const SimpleParseTestData& aData, std::ostream* aOs) {
+ *aOs << "link=" << aData.link << ", valid=" << aData.valid
+ << ", url=" << aData.url << ", rel=" << aData.rel
+ << ", as=" << aData.as << ", fetchpriority=" << aData.fetchpriority
+ << ")";
+ }
+};
+
+class SimpleParseTest : public ::testing::TestWithParam<SimpleParseTestData> {};
+
+TEST_P(SimpleParseTest, Simple) {
+ const SimpleParseTestData test = GetParam();
+
+ nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(test.link);
+
+ EXPECT_EQ(test.valid, !linkHeaders.IsEmpty());
+ if (test.valid) {
+ ASSERT_EQ(linkHeaders.Length(), (nsTArray<LinkHeader>::size_type)1);
+ EXPECT_EQ(test.url, linkHeaders[0].mHref);
+ EXPECT_EQ(test.rel, linkHeaders[0].mRel);
+ EXPECT_EQ(test.as, linkHeaders[0].mAs);
+ EXPECT_EQ(test.fetchpriority, linkHeaders[0].mFetchPriority);
+ }
+}
+
+// Some test data copied and adapted from
+// https://source.chromium.org/chromium/chromium/src/+/main:components/link_header_util/link_header_util_unittest.cc
+// the different behavior of the parser is commented above each test case.
+const SimpleParseTestData simple_parse_tests[] = {
+ {u"<s.css>; rel=stylesheet; fetchpriority=\"auto\""_ns, true, u"s.css"_ns,
+ u"stylesheet"_ns, u""_ns, u"auto"_ns},
+ {u"<s.css>; rel=stylesheet; fetchpriority=\"low\""_ns, true, u"s.css"_ns,
+ u"stylesheet"_ns, u""_ns, u"low"_ns},
+ {u"<s.css>; rel=stylesheet; fetchpriority=\"high\""_ns, true, u"s.css"_ns,
+ u"stylesheet"_ns, u""_ns, u"high"_ns},
+ {u"<s.css>; rel=stylesheet; fetchpriority=\"foo\""_ns, true, u"s.css"_ns,
+ u"stylesheet"_ns, u""_ns, u"foo"_ns},
+ {u"<s.css>; rel=stylesheet; fetchpriority=\"\""_ns, true, u"s.css"_ns,
+ u"stylesheet"_ns, u""_ns, u""_ns},
+ {u"<s.css>; rel=stylesheet; fetchpriority=fooWithoutDoubleQuotes"_ns, true,
+ u"s.css"_ns, u"stylesheet"_ns, u""_ns, u"fooWithoutDoubleQuotes"_ns},
+ {u"</images/cat.jpg>; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>;rel=prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg> ;rel=prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg> ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"< /images/cat.jpg> ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg > ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ // TODO(1744051): don't ignore spaces in href
+ // {u"</images/cat.jpg wutwut> ; rel=prefetch"_ns, true,
+ // u"/images/cat.jpg wutwut"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg wutwut> ; rel=prefetch"_ns, true,
+ u"/images/cat.jpgwutwut"_ns, u"prefetch"_ns, u""_ns},
+ // TODO(1744051): don't ignore spaces in href
+ // {u"</images/cat.jpg wutwut \t > ; rel=prefetch"_ns, true,
+ // u"/images/cat.jpg wutwut"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg wutwut \t > ; rel=prefetch"_ns, true,
+ u"/images/cat.jpgwutwut"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; rel=prefetch "_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; Rel=prefetch "_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; Rel=PReFetCh "_ns, true, u"/images/cat.jpg"_ns,
+ u"PReFetCh"_ns, u""_ns},
+ {u"</images/cat.jpg>; rel=prefetch; rel=somethingelse"_ns, true,
+ u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>\t\t ; \trel=prefetch \t "_ns, true,
+ u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; rel= prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"<../images/cat.jpg?dog>; rel= prefetch"_ns, true,
+ u"../images/cat.jpg?dog"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; rel =prefetch"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; rel pel=prefetch"_ns, false},
+ // different from chromium test case, because we already check for
+ // existence of "rel" parameter
+ {u"< /images/cat.jpg>"_ns, false},
+ {u"</images/cat.jpg>; wut=sup; rel =prefetch"_ns, true,
+ u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; wut=sup ; rel =prefetch"_ns, true,
+ u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; wut=sup ; rel =prefetch \t ;"_ns, true,
+ u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns},
+ // TODO(1744051): forbid non-whitespace characters between '>' and the first
+ // semicolon making it conform RFC 8288 Sec 3
+ // {u"</images/cat.jpg> wut=sup ; rel =prefetch \t ;"_ns, false},
+ {u"</images/cat.jpg> wut=sup ; rel =prefetch \t ;"_ns, true,
+ u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns},
+ {u"< /images/cat.jpg"_ns, false},
+ // TODO(1744051): don't ignore spaces in href
+ // {u"< http://wut.com/ sdfsdf ?sd>; rel=dns-prefetch"_ns, true,
+ // u"http://wut.com/ sdfsdf ?sd"_ns, u"dns-prefetch"_ns, u""_ns},
+ {u"< http://wut.com/ sdfsdf ?sd>; rel=dns-prefetch"_ns, true,
+ u"http://wut.com/sdfsdf?sd"_ns, u"dns-prefetch"_ns, u""_ns},
+ {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=dns-prefetch"_ns, true,
+ u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"dns-prefetch"_ns, u""_ns},
+ {u"< http://wut.com/dfsdf?sdf=ghj&wer=rty>; rel=prefetch"_ns, true,
+ u"http://wut.com/dfsdf?sdf=ghj&wer=rty"_ns, u"prefetch"_ns, u""_ns},
+ {u"< http://wut.com/dfsdf?sdf=ghj&wer=rty>;;;;; rel=prefetch"_ns, true,
+ u"http://wut.com/dfsdf?sdf=ghj&wer=rty"_ns, u"prefetch"_ns, u""_ns},
+ {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=image"_ns, true,
+ u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"preload"_ns, u"image"_ns},
+ {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=whatever"_ns,
+ true, u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"preload"_ns,
+ u"whatever"_ns},
+ {u"</images/cat.jpg>; rel=prefetch;"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/cat.jpg>; rel=prefetch ;"_ns, true, u"/images/cat.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"</images/ca,t.jpg>; rel=prefetch ;"_ns, true, u"/images/ca,t.jpg"_ns,
+ u"prefetch"_ns, u""_ns},
+ {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE and "
+ "backslash\""_ns,
+ true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns},
+ // TODO(1744051): forbid missing end quote
+ // {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and "
+ // "backslash: \\\""_ns, false},
+ {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and backslash: \\\""_ns,
+ true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns},
+ {u"<simple.css>; title=\"title with a DQUOTE \\\" and backslash: \"; "
+ "rel=stylesheet; "_ns,
+ true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns},
+ {u"<simple.css>; title=\'title with a DQUOTE \\\' and backslash: \'; "
+ "rel=stylesheet; "_ns,
+ true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns},
+ {u"<simple.css>; title=\"title with a DQUOTE \\\" and ;backslash,: \"; "
+ "rel=stylesheet; "_ns,
+ true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns},
+ {u"<simple.css>; title=\"title with a DQUOTE \' and ;backslash,: \"; "
+ "rel=stylesheet; "_ns,
+ true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns},
+ {u"<simple.css>; title=\"\"; rel=stylesheet; "_ns, true, u"simple.css"_ns,
+ u"stylesheet"_ns, u""_ns},
+ {u"<simple.css>; title=\"\"; rel=\"stylesheet\"; "_ns, true,
+ u"simple.css"_ns, u"stylesheet"_ns, u""_ns},
+ // TODO(1744051): forbid missing end quote
+ // {u"<simple.css>; rel=stylesheet; title=\""_ns, false},
+ {u"<simple.css>; rel=stylesheet; title=\""_ns, true, u"simple.css"_ns,
+ u"stylesheet"_ns, u""_ns},
+ {u"<simple.css>; rel=stylesheet; title=\"\""_ns, true, u"simple.css"_ns,
+ u"stylesheet"_ns, u""_ns},
+ // TODO(1744051): forbid missing end quote
+ // {u"<simple.css>; rel=\"stylesheet\"; title=\""_ns, false},
+ {u"<simple.css>; rel=\"stylesheet\"; title=\""_ns, true, u"simple.css"_ns,
+ u"stylesheet"_ns, u""_ns},
+ // TODO(1744051): forbid missing end quote
+ // {u"<simple.css>; rel=\";style,sheet\"; title=\""_ns, false},
+ {u"<simple.css>; rel=\";style,sheet\"; title=\""_ns, true, u"simple.css"_ns,
+ u";style,sheet"_ns, u""_ns},
+ // TODO(1744051): forbid missing end quote
+ // {u"<simple.css>; rel=\"bla'sdf\"; title=\""_ns, false}
+ {u"<simple.css>; rel=\"bla'sdf\"; title=\""_ns, true, u"simple.css"_ns,
+ u"bla'sdf"_ns, u""_ns},
+ // TODO(1744051): allow explicit empty rel
+ // {u"<simple.css>; rel=\"\"; title=\"\""_ns, true, u"simple.css"_ns,
+ // u""_ns, u""_ns}
+ {u"<simple.css>; rel=\"\"; title=\"\""_ns, false},
+ {u"<simple.css>; rel=''; title=\"\""_ns, true, u"simple.css"_ns, u"''"_ns,
+ u""_ns},
+ {u"<simple.css>; rel=''; bla"_ns, true, u"simple.css"_ns, u"''"_ns, u""_ns},
+ {u"<simple.css>; rel='prefetch"_ns, true, u"simple.css"_ns, u"'prefetch"_ns,
+ u""_ns},
+ // TODO(1744051): forbid missing end quote
+ // {u"<simple.css>; rel=\"prefetch"_ns, false},
+ {u"<simple.css>; rel=\"prefetch"_ns, true, u"simple.css"_ns,
+ u"\"prefetch"_ns, u""_ns},
+ {u"<simple.css>; rel=\""_ns, false},
+ {u"simple.css; rel=prefetch"_ns, false},
+ {u"<simple.css>; rel=prefetch; rel=foobar"_ns, true, u"simple.css"_ns,
+ u"prefetch"_ns, u""_ns},
+};
+
+INSTANTIATE_TEST_SUITE_P(TestLinkHeader, SimpleParseTest,
+ testing::ValuesIn(simple_parse_tests));
+
+// Test anchor
+
+struct AnchorTestData {
+ nsString baseURI;
+ // building the new anchor in combination with the baseURI
+ nsString anchor;
+ nsString href;
+ const char* resolved;
+};
+
+class AnchorTest : public ::testing::TestWithParam<AnchorTestData> {};
+
+const AnchorTestData anchor_tests[] = {
+ {u"http://example.com/path/to/index.html"_ns, u""_ns, u"page.html"_ns,
+ "http://example.com/path/to/page.html"},
+ {u"http://example.com/path/to/index.html"_ns,
+ u"http://example.com/path/"_ns, u"page.html"_ns,
+ "http://example.com/path/page.html"},
+ {u"http://example.com/path/to/index.html"_ns,
+ u"http://example.com/path/"_ns, u"/page.html"_ns,
+ "http://example.com/page.html"},
+ {u"http://example.com/path/to/index.html"_ns, u".."_ns, u"page.html"_ns,
+ "http://example.com/path/page.html"},
+ {u"http://example.com/path/to/index.html"_ns, u".."_ns,
+ u"from/page.html"_ns, "http://example.com/path/from/page.html"},
+ {u"http://example.com/path/to/index.html"_ns, u"/hello/"_ns,
+ u"page.html"_ns, "http://example.com/hello/page.html"},
+ {u"http://example.com/path/to/index.html"_ns, u"/hello"_ns, u"page.html"_ns,
+ "http://example.com/page.html"},
+ {u"http://example.com/path/to/index.html"_ns, u"#necko"_ns, u"page.html"_ns,
+ "http://example.com/path/to/page.html"},
+ {u"http://example.com/path/to/index.html"_ns, u"https://example.net/"_ns,
+ u"to/page.html"_ns, "https://example.net/to/page.html"},
+};
+
+LinkHeader LinkHeaderFromHrefAndAnchor(nsAString const& aHref,
+ nsAString const& aAnchor) {
+ LinkHeader l;
+ l.mHref = aHref;
+ l.mAnchor = aAnchor;
+ return l;
+}
+
+TEST_P(AnchorTest, Anchor) {
+ const AnchorTestData test = GetParam();
+
+ LinkHeader linkHeader = LinkHeaderFromHrefAndAnchor(test.href, test.anchor);
+
+ nsCOMPtr<nsIURI> baseURI;
+ ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(baseURI), test.baseURI));
+
+ nsCOMPtr<nsIURI> resolved;
+ ASSERT_TRUE(NS_SUCCEEDED(
+ linkHeader.NewResolveHref(getter_AddRefs(resolved), baseURI)));
+
+ ASSERT_STREQ(resolved->GetSpecOrDefault().get(), test.resolved);
+}
+
+INSTANTIATE_TEST_SUITE_P(TestLinkHeader, AnchorTest,
+ testing::ValuesIn(anchor_tests));
diff --git a/netwerk/test/gtest/TestMIMEInputStream.cpp b/netwerk/test/gtest/TestMIMEInputStream.cpp
new file mode 100644
index 0000000000..a2f3f7a43d
--- /dev/null
+++ b/netwerk/test/gtest/TestMIMEInputStream.cpp
@@ -0,0 +1,268 @@
+#include "gtest/gtest.h"
+
+#include "Helpers.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "nsIMIMEInputStream.h"
+#include "nsISeekableStream.h"
+
+using mozilla::GetCurrentSerialEventTarget;
+using mozilla::SpinEventLoopUntil;
+
+namespace {
+
+class SeekableLengthInputStream final : public testing::LengthInputStream,
+ public nsISeekableStream {
+ public:
+ SeekableLengthInputStream(const nsACString& aBuffer,
+ bool aIsInputStreamLength,
+ bool aIsAsyncInputStreamLength,
+ nsresult aLengthRv = NS_OK,
+ bool aNegativeValue = false)
+ : testing::LengthInputStream(aBuffer, aIsInputStreamLength,
+ aIsAsyncInputStreamLength, aLengthRv,
+ aNegativeValue) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD
+ Seek(int32_t aWhence, int64_t aOffset) override {
+ MOZ_CRASH("This method should not be called.");
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IMETHOD
+ Tell(int64_t* aResult) override {
+ MOZ_CRASH("This method should not be called.");
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IMETHOD
+ SetEOF() override {
+ MOZ_CRASH("This method should not be called.");
+ return NS_ERROR_FAILURE;
+ }
+
+ private:
+ ~SeekableLengthInputStream() = default;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(SeekableLengthInputStream,
+ testing::LengthInputStream, nsISeekableStream)
+
+} // namespace
+
+// nsIInputStreamLength && nsIAsyncInputStreamLength
+
+TEST(TestNsMIMEInputStream, QIInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ for (int i = 0; i < 4; i++) {
+ nsCOMPtr<nsIInputStream> mis;
+ {
+ RefPtr<SeekableLengthInputStream> stream =
+ new SeekableLengthInputStream(buf, i % 2, i > 1);
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEInputStream> m(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ ASSERT_EQ(NS_OK, rv);
+
+ rv = m->SetData(stream);
+ ASSERT_EQ(NS_OK, rv);
+
+ mis = m;
+ ASSERT_TRUE(!!mis);
+ }
+
+ {
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(mis);
+ ASSERT_EQ(!!(i % 2), !!qi);
+ }
+
+ {
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis);
+ ASSERT_EQ(i > 1, !!qi);
+ }
+ }
+}
+
+TEST(TestNsMIMEInputStream, InputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> mis;
+ {
+ RefPtr<SeekableLengthInputStream> stream =
+ new SeekableLengthInputStream(buf, true, false);
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEInputStream> m(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ ASSERT_EQ(NS_OK, rv);
+
+ rv = m->SetData(stream);
+ ASSERT_EQ(NS_OK, rv);
+
+ mis = m;
+ ASSERT_TRUE(!!mis);
+ }
+
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(mis);
+ ASSERT_TRUE(!!qi);
+
+ int64_t size;
+ nsresult rv = qi->Length(&size);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(int64_t(buf.Length()), size);
+}
+
+TEST(TestNsMIMEInputStream, NegativeInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> mis;
+ {
+ RefPtr<SeekableLengthInputStream> stream =
+ new SeekableLengthInputStream(buf, true, false, NS_OK, true);
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEInputStream> m(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ ASSERT_EQ(NS_OK, rv);
+
+ rv = m->SetData(stream);
+ ASSERT_EQ(NS_OK, rv);
+
+ mis = m;
+ ASSERT_TRUE(!!mis);
+ }
+
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(mis);
+ ASSERT_TRUE(!!qi);
+
+ int64_t size;
+ nsresult rv = qi->Length(&size);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(-1, size);
+}
+
+TEST(TestNsMIMEInputStream, AsyncInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> mis;
+ {
+ RefPtr<SeekableLengthInputStream> stream =
+ new SeekableLengthInputStream(buf, false, true);
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEInputStream> m(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ ASSERT_EQ(NS_OK, rv);
+
+ rv = m->SetData(stream);
+ ASSERT_EQ(NS_OK, rv);
+
+ mis = m;
+ ASSERT_TRUE(!!mis);
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis);
+ ASSERT_TRUE(!!qi);
+
+ RefPtr<testing::LengthCallback> callback = new testing::LengthCallback();
+
+ nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "TEST(TestNsMIMEInputStream, AsyncInputStreamLength)"_ns,
+ [&]() { return callback->Called(); }));
+ ASSERT_EQ(int64_t(buf.Length()), callback->Size());
+}
+
+TEST(TestNsMIMEInputStream, NegativeAsyncInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> mis;
+ {
+ RefPtr<SeekableLengthInputStream> stream =
+ new SeekableLengthInputStream(buf, false, true, NS_OK, true);
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEInputStream> m(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ ASSERT_EQ(NS_OK, rv);
+
+ rv = m->SetData(stream);
+ ASSERT_EQ(NS_OK, rv);
+
+ mis = m;
+ ASSERT_TRUE(!!mis);
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis);
+ ASSERT_TRUE(!!qi);
+
+ RefPtr<testing::LengthCallback> callback = new testing::LengthCallback();
+
+ nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "TEST(TestNsMIMEInputStream, NegativeAsyncInputStreamLength)"_ns,
+ [&]() { return callback->Called(); }));
+ ASSERT_EQ(-1, callback->Size());
+}
+
+TEST(TestNsMIMEInputStream, AbortLengthCallback)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> mis;
+ {
+ RefPtr<SeekableLengthInputStream> stream =
+ new SeekableLengthInputStream(buf, false, true, NS_OK, true);
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEInputStream> m(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ ASSERT_EQ(NS_OK, rv);
+
+ rv = m->SetData(stream);
+ ASSERT_EQ(NS_OK, rv);
+
+ mis = m;
+ ASSERT_TRUE(!!mis);
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis);
+ ASSERT_TRUE(!!qi);
+
+ RefPtr<testing::LengthCallback> callback1 = new testing::LengthCallback();
+ nsresult rv = qi->AsyncLengthWait(callback1, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ RefPtr<testing::LengthCallback> callback2 = new testing::LengthCallback();
+ rv = qi->AsyncLengthWait(callback2, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(
+ SpinEventLoopUntil("TEST(TestNsMIMEInputStream, AbortLengthCallback)"_ns,
+ [&]() { return callback2->Called(); }));
+ ASSERT_TRUE(!callback1->Called());
+ ASSERT_EQ(-1, callback2->Size());
+}
diff --git a/netwerk/test/gtest/TestMozURL.cpp b/netwerk/test/gtest/TestMozURL.cpp
new file mode 100644
index 0000000000..826adb241d
--- /dev/null
+++ b/netwerk/test/gtest/TestMozURL.cpp
@@ -0,0 +1,392 @@
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+
+#include <regex>
+#include "json/json.h"
+#include "json/reader.h"
+#include "mozilla/TextUtils.h"
+#include "nsString.h"
+#include "mozilla/net/MozURL.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsStreamUtils.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+TEST(TestMozURL, Getters)
+{
+ nsAutoCString href("http://user:pass@example.com/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+
+ ASSERT_TRUE(url->Scheme().EqualsLiteral("http"));
+
+ ASSERT_TRUE(url->Spec() == href);
+
+ ASSERT_TRUE(url->Username().EqualsLiteral("user"));
+
+ ASSERT_TRUE(url->Password().EqualsLiteral("pass"));
+
+ ASSERT_TRUE(url->Host().EqualsLiteral("example.com"));
+
+ ASSERT_TRUE(url->FilePath().EqualsLiteral("/path"));
+
+ ASSERT_TRUE(url->Query().EqualsLiteral("query"));
+
+ ASSERT_TRUE(url->Ref().EqualsLiteral("ref"));
+
+ url = nullptr;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), ""_ns), NS_ERROR_MALFORMED_URI);
+ ASSERT_EQ(url, nullptr);
+}
+
+TEST(TestMozURL, MutatorChain)
+{
+ nsAutoCString href("http://user:pass@example.com/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+ nsAutoCString out;
+
+ RefPtr<MozURL> url2;
+ ASSERT_EQ(url->Mutate()
+ .SetScheme("https"_ns)
+ .SetUsername("newuser"_ns)
+ .SetPassword("newpass"_ns)
+ .SetHostname("test"_ns)
+ .SetFilePath("new/file/path"_ns)
+ .SetQuery("bla"_ns)
+ .SetRef("huh"_ns)
+ .Finalize(getter_AddRefs(url2)),
+ NS_OK);
+
+ ASSERT_TRUE(url2->Spec().EqualsLiteral(
+ "https://newuser:newpass@test/new/file/path?bla#huh"));
+}
+
+TEST(TestMozURL, MutatorFinalizeTwice)
+{
+ nsAutoCString href("http://user:pass@example.com/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+ nsAutoCString out;
+
+ RefPtr<MozURL> url2;
+ MozURL::Mutator mut = url->Mutate();
+ mut.SetScheme("https"_ns); // Change the scheme to https
+ ASSERT_EQ(mut.Finalize(getter_AddRefs(url2)), NS_OK);
+ ASSERT_TRUE(url2->Spec().EqualsLiteral(
+ "https://user:pass@example.com/path?query#ref"));
+
+ // Test that a second call to Finalize will result in an error code
+ url2 = nullptr;
+ ASSERT_EQ(mut.Finalize(getter_AddRefs(url2)), NS_ERROR_NOT_AVAILABLE);
+ ASSERT_EQ(url2, nullptr);
+}
+
+TEST(TestMozURL, MutatorErrorStatus)
+{
+ nsAutoCString href("http://user:pass@example.com/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+ nsAutoCString out;
+
+ // Test that trying to set the scheme to a bad value will get you an error
+ MozURL::Mutator mut = url->Mutate();
+ mut.SetScheme("!@#$%^&*("_ns);
+ ASSERT_EQ(mut.GetStatus(), NS_ERROR_MALFORMED_URI);
+
+ // Test that the mutator will not work after one faulty operation
+ mut.SetScheme("test"_ns);
+ ASSERT_EQ(mut.GetStatus(), NS_ERROR_MALFORMED_URI);
+}
+
+TEST(TestMozURL, InitWithBase)
+{
+ nsAutoCString href("https://example.net/a/b.html");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+
+ ASSERT_TRUE(url->Spec().EqualsLiteral("https://example.net/a/b.html"));
+
+ RefPtr<MozURL> url2;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url2), "c.png"_ns, url), NS_OK);
+
+ ASSERT_TRUE(url2->Spec().EqualsLiteral("https://example.net/a/c.png"));
+}
+
+TEST(TestMozURL, Path)
+{
+ nsAutoCString href("about:blank");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+
+ ASSERT_TRUE(url->Spec().EqualsLiteral("about:blank"));
+
+ ASSERT_TRUE(url->Scheme().EqualsLiteral("about"));
+
+ ASSERT_TRUE(url->FilePath().EqualsLiteral("blank"));
+}
+
+TEST(TestMozURL, HostPort)
+{
+ nsAutoCString href("https://user:pass@example.net:1234/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+
+ ASSERT_TRUE(url->HostPort().EqualsLiteral("example.net:1234"));
+
+ RefPtr<MozURL> url2;
+ url->Mutate().SetHostPort("test:321"_ns).Finalize(getter_AddRefs(url2));
+
+ ASSERT_TRUE(url2->HostPort().EqualsLiteral("test:321"));
+ ASSERT_TRUE(
+ url2->Spec().EqualsLiteral("https://user:pass@test:321/path?query#ref"));
+
+ href.Assign("https://user:pass@example.net:443/path?query#ref");
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+ ASSERT_TRUE(url->HostPort().EqualsLiteral("example.net"));
+ ASSERT_EQ(url->Port(), -1);
+}
+
+TEST(TestMozURL, Origin)
+{
+ nsAutoCString href("https://user:pass@example.net:1234/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+
+ nsAutoCString out;
+ url->Origin(out);
+ ASSERT_TRUE(out.EqualsLiteral("https://example.net:1234"));
+
+ RefPtr<MozURL> url2;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url2), "file:///tmp/foo"_ns), NS_OK);
+ url2->Origin(out);
+ ASSERT_TRUE(out.EqualsLiteral("file:///tmp/foo"));
+
+ RefPtr<MozURL> url3;
+ ASSERT_EQ(
+ MozURL::Init(getter_AddRefs(url3),
+ nsLiteralCString(
+ "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc/"
+ "foo/bar.html")),
+ NS_OK);
+ url3->Origin(out);
+ ASSERT_TRUE(out.EqualsLiteral(
+ "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc"));
+
+ RefPtr<MozURL> url4;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url4), "resource://foo/bar.html"_ns),
+ NS_OK);
+ url4->Origin(out);
+ ASSERT_TRUE(out.EqualsLiteral("resource://foo"));
+
+ RefPtr<MozURL> url5;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url5), "about:home"_ns), NS_OK);
+ url5->Origin(out);
+ ASSERT_TRUE(out.EqualsLiteral("about:home"));
+}
+
+TEST(TestMozURL, BaseDomain)
+{
+ nsAutoCString href("https://user:pass@example.net:1234/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK);
+
+ nsAutoCString out;
+ ASSERT_EQ(url->BaseDomain(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("example.net"));
+
+ RefPtr<MozURL> url2;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url2), "file:///tmp/foo"_ns), NS_OK);
+ ASSERT_EQ(url2->BaseDomain(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("/tmp/foo"));
+
+ RefPtr<MozURL> url3;
+ ASSERT_EQ(
+ MozURL::Init(getter_AddRefs(url3),
+ nsLiteralCString(
+ "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc/"
+ "foo/bar.html")),
+ NS_OK);
+ ASSERT_EQ(url3->BaseDomain(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("53711a8f-65ed-e742-9671-1f02e267c0bc"));
+
+ RefPtr<MozURL> url4;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url4), "resource://foo/bar.html"_ns),
+ NS_OK);
+ ASSERT_EQ(url4->BaseDomain(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("foo"));
+
+ RefPtr<MozURL> url5;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url5), "about:home"_ns), NS_OK);
+ ASSERT_EQ(url5->BaseDomain(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("about:home"));
+}
+
+namespace {
+
+bool OriginMatchesExpectedOrigin(const nsACString& aOrigin,
+ const nsACString& aExpectedOrigin) {
+ if (aExpectedOrigin.Equals("null") &&
+ StringBeginsWith(aOrigin, "moz-nullprincipal"_ns)) {
+ return true;
+ }
+ return aOrigin == aExpectedOrigin;
+}
+
+bool IsUUID(const nsACString& aString) {
+ if (!IsAscii(aString)) {
+ return false;
+ }
+
+ std::regex pattern(
+ "^\\{[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab"
+ "][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\\}$");
+ return regex_match(nsCString(aString).get(), pattern);
+}
+
+bool BaseDomainsEqual(const nsACString& aBaseDomain1,
+ const nsACString& aBaseDomain2) {
+ if (IsUUID(aBaseDomain1) && IsUUID(aBaseDomain2)) {
+ return true;
+ }
+ return aBaseDomain1 == aBaseDomain2;
+}
+
+void CheckOrigin(const nsACString& aSpec, const nsACString& aBase,
+ const nsACString& aOrigin) {
+ nsCOMPtr<nsIURI> baseUri;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), aSpec, nullptr, baseUri);
+ ASSERT_EQ(rv, NS_OK);
+
+ OriginAttributes attrs;
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(principal);
+
+ nsCString origin;
+ rv = principal->GetOriginNoSuffix(origin);
+ ASSERT_EQ(rv, NS_OK);
+
+ EXPECT_TRUE(OriginMatchesExpectedOrigin(origin, aOrigin));
+
+ nsCString baseDomain;
+ rv = principal->GetBaseDomain(baseDomain);
+
+ bool baseDomainSucceeded = NS_SUCCEEDED(rv);
+
+ RefPtr<MozURL> baseUrl;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(baseUrl), aBase), NS_OK);
+
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(getter_AddRefs(url), aSpec, baseUrl), NS_OK);
+
+ url->Origin(origin);
+
+ EXPECT_TRUE(OriginMatchesExpectedOrigin(origin, aOrigin));
+
+ nsCString baseDomain2;
+ rv = url->BaseDomain(baseDomain2);
+
+ bool baseDomain2Succeeded = NS_SUCCEEDED(rv);
+
+ EXPECT_TRUE(baseDomainSucceeded == baseDomain2Succeeded);
+
+ if (baseDomainSucceeded) {
+ EXPECT_TRUE(BaseDomainsEqual(baseDomain, baseDomain2));
+ }
+}
+
+} // namespace
+
+TEST(TestMozURL, UrlTestData)
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(file));
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = file->Append(u"urltestdata.json"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ ASSERT_EQ(rv, NS_OK);
+
+ ASSERT_TRUE(exists);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ stream.forget(), 4096);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsCString data;
+ rv = NS_ConsumeStream(bufferedStream, UINT32_MAX, data);
+ ASSERT_EQ(rv, NS_OK);
+
+ Json::Value root;
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> const reader(builder.newCharReader());
+ ASSERT_TRUE(
+ reader->parse(data.BeginReading(), data.EndReading(), &root, nullptr));
+ ASSERT_TRUE(root.isArray());
+
+ for (auto& item : root) {
+ if (!item.isObject()) {
+ continue;
+ }
+
+ const Json::Value& skip = item["skip"];
+ ASSERT_TRUE(skip.isNull() || skip.isBool());
+ if (skip.isBool() && skip.asBool()) {
+ continue;
+ }
+
+ const Json::Value& failure = item["failure"];
+ ASSERT_TRUE(failure.isNull() || failure.isBool());
+ if (failure.isBool() && failure.asBool()) {
+ continue;
+ }
+
+ const Json::Value& origin = item["origin"];
+ ASSERT_TRUE(origin.isNull() || origin.isString());
+ if (origin.isNull()) {
+ continue;
+ }
+ const char* originBegin;
+ const char* originEnd;
+ origin.getString(&originBegin, &originEnd);
+
+ auto baseCString = nsDependentCString("about:blank");
+ const Json::Value& base = item["base"];
+ if (!base.isNull()) {
+ const char* baseBegin;
+ const char* baseEnd;
+ base.getString(&baseBegin, &baseEnd);
+ baseCString.Assign(nsDependentCSubstring(baseBegin, baseEnd));
+ }
+
+ const Json::Value& input = item["input"];
+ ASSERT_TRUE(input.isString());
+ const char* inputBegin;
+ const char* inputEnd;
+ input.getString(&inputBegin, &inputEnd);
+
+ CheckOrigin(nsDependentCString(inputBegin, inputEnd), baseCString,
+ nsDependentCString(originBegin, originEnd));
+ }
+}
diff --git a/netwerk/test/gtest/TestNamedPipeService.cpp b/netwerk/test/gtest/TestNamedPipeService.cpp
new file mode 100644
index 0000000000..b91a17a93e
--- /dev/null
+++ b/netwerk/test/gtest/TestNamedPipeService.cpp
@@ -0,0 +1,281 @@
+/* -*- 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 "TestCommon.h"
+#include "gtest/gtest.h"
+
+#include <windows.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Monitor.h"
+#include "nsNamedPipeService.h"
+#include "nsNetCID.h"
+
+#define PIPE_NAME L"\\\\.\\pipe\\TestNPS"
+#define TEST_STR "Hello World"
+
+using namespace mozilla;
+
+/**
+ * Unlike a monitor, an event allows a thread to wait on another thread
+ * completing an action without regard to ordering of the wait and the notify.
+ */
+class Event {
+ public:
+ explicit Event(const char* aName) : mMonitor(aName) {}
+
+ ~Event() = default;
+
+ void Set() {
+ MonitorAutoLock lock(mMonitor);
+ MOZ_ASSERT(!mSignaled);
+ mSignaled = true;
+ mMonitor.Notify();
+ }
+ void Wait() {
+ MonitorAutoLock lock(mMonitor);
+ while (!mSignaled) {
+ lock.Wait();
+ }
+ mSignaled = false;
+ }
+
+ private:
+ Monitor mMonitor MOZ_UNANNOTATED;
+ bool mSignaled = false;
+};
+
+class nsNamedPipeDataObserver final : public nsINamedPipeDataObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINAMEDPIPEDATAOBSERVER
+
+ explicit nsNamedPipeDataObserver(HANDLE aPipe);
+
+ int Read(void* aBuffer, uint32_t aSize);
+ int Write(const void* aBuffer, uint32_t aSize);
+
+ uint32_t Transferred() const { return mBytesTransferred; }
+
+ private:
+ ~nsNamedPipeDataObserver() = default;
+
+ HANDLE mPipe;
+ OVERLAPPED mOverlapped;
+ Atomic<uint32_t> mBytesTransferred;
+ Event mEvent;
+};
+
+NS_IMPL_ISUPPORTS(nsNamedPipeDataObserver, nsINamedPipeDataObserver)
+
+nsNamedPipeDataObserver::nsNamedPipeDataObserver(HANDLE aPipe)
+ : mPipe(aPipe), mOverlapped(), mBytesTransferred(0), mEvent("named-pipe") {
+ mOverlapped.hEvent = CreateEventA(nullptr, TRUE, TRUE, "named-pipe");
+}
+
+int nsNamedPipeDataObserver::Read(void* aBuffer, uint32_t aSize) {
+ DWORD bytesRead = 0;
+ if (!ReadFile(mPipe, aBuffer, aSize, &bytesRead, &mOverlapped)) {
+ switch (GetLastError()) {
+ case ERROR_IO_PENDING: {
+ mEvent.Wait();
+ }
+ if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesRead, FALSE)) {
+ ADD_FAILURE() << "GetOverlappedResult failed";
+ return -1;
+ }
+ if (mBytesTransferred != bytesRead) {
+ ADD_FAILURE() << "GetOverlappedResult mismatch";
+ return -1;
+ }
+
+ break;
+ default:
+ ADD_FAILURE() << "ReadFile error " << GetLastError();
+ return -1;
+ }
+ } else {
+ mEvent.Wait();
+
+ if (mBytesTransferred != bytesRead) {
+ ADD_FAILURE() << "GetOverlappedResult mismatch";
+ return -1;
+ }
+ }
+
+ mBytesTransferred = 0;
+ return bytesRead;
+}
+
+int nsNamedPipeDataObserver::Write(const void* aBuffer, uint32_t aSize) {
+ DWORD bytesWritten = 0;
+ if (!WriteFile(mPipe, aBuffer, aSize, &bytesWritten, &mOverlapped)) {
+ switch (GetLastError()) {
+ case ERROR_IO_PENDING: {
+ mEvent.Wait();
+ }
+ if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesWritten, FALSE)) {
+ ADD_FAILURE() << "GetOverlappedResult failed";
+ return -1;
+ }
+ if (mBytesTransferred != bytesWritten) {
+ ADD_FAILURE() << "GetOverlappedResult mismatch";
+ return -1;
+ }
+
+ break;
+ default:
+ ADD_FAILURE() << "WriteFile error " << GetLastError();
+ return -1;
+ }
+ } else {
+ mEvent.Wait();
+
+ if (mBytesTransferred != bytesWritten) {
+ ADD_FAILURE() << "GetOverlappedResult mismatch";
+ return -1;
+ }
+ }
+
+ mBytesTransferred = 0;
+ return bytesWritten;
+}
+
+NS_IMETHODIMP
+nsNamedPipeDataObserver::OnDataAvailable(uint32_t aBytesTransferred,
+ void* aOverlapped) {
+ if (aOverlapped != &mOverlapped) {
+ ADD_FAILURE() << "invalid overlapped object";
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD bytesTransferred = 0;
+ BOOL ret =
+ GetOverlappedResult(mPipe, reinterpret_cast<LPOVERLAPPED>(aOverlapped),
+ &bytesTransferred, FALSE);
+
+ if (!ret) {
+ ADD_FAILURE() << "GetOverlappedResult failed";
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bytesTransferred != aBytesTransferred) {
+ ADD_FAILURE() << "GetOverlappedResult mismatch";
+ return NS_ERROR_FAILURE;
+ }
+
+ mBytesTransferred += aBytesTransferred;
+ mEvent.Set();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNamedPipeDataObserver::OnError(uint32_t aError, void* aOverlapped) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+BOOL CreateAndConnectInstance(LPOVERLAPPED aOverlapped, LPHANDLE aPipe);
+BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped);
+
+BOOL CreateAndConnectInstance(LPOVERLAPPED aOverlapped, LPHANDLE aPipe) {
+ // FIXME: adjust parameters
+ *aPipe =
+ CreateNamedPipeW(PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1,
+ 65536, 65536, 3000, NULL);
+
+ if (*aPipe == INVALID_HANDLE_VALUE) {
+ ADD_FAILURE() << "CreateNamedPipe failed " << GetLastError();
+ return FALSE;
+ }
+
+ return ConnectToNewClient(*aPipe, aOverlapped);
+}
+
+BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped) {
+ if (ConnectNamedPipe(aPipe, aOverlapped)) {
+ ADD_FAILURE()
+ << "Unexpected, overlapped ConnectNamedPipe() always returns 0.";
+ return FALSE;
+ }
+
+ switch (GetLastError()) {
+ case ERROR_IO_PENDING:
+ return TRUE;
+
+ case ERROR_PIPE_CONNECTED:
+ if (SetEvent(aOverlapped->hEvent)) break;
+
+ [[fallthrough]];
+ default: // error
+ ADD_FAILURE() << "ConnectNamedPipe failed " << GetLastError();
+ break;
+ }
+
+ return FALSE;
+}
+
+static nsresult CreateNamedPipe(LPHANDLE aServer, LPHANDLE aClient) {
+ OVERLAPPED overlapped;
+ overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
+ BOOL ret;
+
+ ret = CreateAndConnectInstance(&overlapped, aServer);
+ if (!ret) {
+ ADD_FAILURE() << "pipe server should be pending";
+ return NS_ERROR_FAILURE;
+ }
+
+ *aClient = CreateFileW(PIPE_NAME, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
+
+ if (*aClient == INVALID_HANDLE_VALUE) {
+ ADD_FAILURE() << "Unable to create pipe client";
+ CloseHandle(*aServer);
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD pipeMode = PIPE_READMODE_MESSAGE;
+ if (!SetNamedPipeHandleState(*aClient, &pipeMode, nullptr, nullptr)) {
+ ADD_FAILURE() << "SetNamedPipeHandleState error " << GetLastError();
+ CloseHandle(*aServer);
+ CloseHandle(*aClient);
+ return NS_ERROR_FAILURE;
+ }
+
+ WaitForSingleObjectEx(overlapped.hEvent, INFINITE, TRUE);
+
+ return NS_OK;
+}
+
+TEST(TestNamedPipeService, Test)
+{
+ nsCOMPtr<nsINamedPipeService> svc = net::NamedPipeService::GetOrCreate();
+
+ HANDLE readPipe, writePipe;
+ nsresult rv = CreateNamedPipe(&readPipe, &writePipe);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ RefPtr<nsNamedPipeDataObserver> readObserver =
+ new nsNamedPipeDataObserver(readPipe);
+ RefPtr<nsNamedPipeDataObserver> writeObserver =
+ new nsNamedPipeDataObserver(writePipe);
+
+ ASSERT_NS_SUCCEEDED(svc->AddDataObserver(readPipe, readObserver));
+ ASSERT_NS_SUCCEEDED(svc->AddDataObserver(writePipe, writeObserver));
+ ASSERT_EQ(std::size_t(writeObserver->Write(TEST_STR, sizeof(TEST_STR))),
+ sizeof(TEST_STR));
+
+ char buffer[sizeof(TEST_STR)];
+ ASSERT_EQ(std::size_t(readObserver->Read(buffer, sizeof(buffer))),
+ sizeof(TEST_STR));
+ ASSERT_STREQ(buffer, TEST_STR) << "I/O mismatch";
+
+ ASSERT_NS_SUCCEEDED(svc->RemoveDataObserver(readPipe, readObserver));
+ ASSERT_NS_SUCCEEDED(svc->RemoveDataObserver(writePipe, writeObserver));
+}
diff --git a/netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp b/netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp
new file mode 100644
index 0000000000..a07c9438bd
--- /dev/null
+++ b/netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp
@@ -0,0 +1,93 @@
+#include <arpa/inet.h>
+
+#include "gtest/gtest.h"
+#include "mozilla/SHA1.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Logging.h"
+#include "nsNetworkLinkService.h"
+
+using namespace mozilla;
+
+in6_addr StringToSockAddr(const std::string& str) {
+ sockaddr_in6 ip;
+ inet_pton(AF_INET6, str.c_str(), &(ip.sin6_addr));
+ return ip.sin6_addr;
+}
+
+TEST(TestNetworkLinkIdHashingDarwin, Single)
+{
+ // Setup
+ SHA1Sum expected_sha1;
+ SHA1Sum::Hash expected_digest;
+
+ in6_addr a1 = StringToSockAddr("2001:db8:8714:3a91::1");
+
+ // Prefix
+ expected_sha1.update(&a1, sizeof(in6_addr));
+ // Netmask
+ expected_sha1.update(&a1, sizeof(in6_addr));
+ expected_sha1.finish(expected_digest);
+
+ std::vector<prefix_and_netmask> prefixNetmaskStore;
+ prefixNetmaskStore.push_back(std::make_pair(a1, a1));
+ SHA1Sum actual_sha1;
+ // Run
+ nsNetworkLinkService::HashSortedPrefixesAndNetmasks(prefixNetmaskStore,
+ &actual_sha1);
+ SHA1Sum::Hash actual_digest;
+ actual_sha1.finish(actual_digest);
+
+ // Assert
+ ASSERT_EQ(0, memcmp(&expected_digest, &actual_digest, sizeof(SHA1Sum::Hash)));
+}
+
+TEST(TestNetworkLinkIdHashingDarwin, Multiple)
+{
+ // Setup
+ SHA1Sum expected_sha1;
+ SHA1Sum::Hash expected_digest;
+
+ std::vector<in6_addr> addresses;
+ addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::1"));
+ addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::2"));
+ addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::3"));
+ addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::4"));
+
+ for (const auto& address : addresses) {
+ // Prefix
+ expected_sha1.update(&address, sizeof(in6_addr));
+ // Netmask
+ expected_sha1.update(&address, sizeof(in6_addr));
+ }
+ expected_sha1.finish(expected_digest);
+
+ // Ordered
+ std::vector<prefix_and_netmask> ordered;
+ for (const auto& address : addresses) {
+ ordered.push_back(std::make_pair(address, address));
+ }
+ SHA1Sum ordered_sha1;
+
+ // Unordered
+ std::vector<prefix_and_netmask> reversed;
+ for (auto it = addresses.rbegin(); it != addresses.rend(); ++it) {
+ reversed.push_back(std::make_pair(*it, *it));
+ }
+ SHA1Sum reversed_sha1;
+
+ // Run
+ nsNetworkLinkService::HashSortedPrefixesAndNetmasks(ordered, &ordered_sha1);
+ SHA1Sum::Hash ordered_digest;
+ ordered_sha1.finish(ordered_digest);
+
+ nsNetworkLinkService::HashSortedPrefixesAndNetmasks(reversed, &reversed_sha1);
+ SHA1Sum::Hash reversed_digest;
+ reversed_sha1.finish(reversed_digest);
+
+ // Assert
+ ASSERT_EQ(0,
+ memcmp(&expected_digest, &ordered_digest, sizeof(SHA1Sum::Hash)));
+ ASSERT_EQ(0,
+ memcmp(&expected_digest, &reversed_digest, sizeof(SHA1Sum::Hash)));
+}
diff --git a/netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp b/netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp
new file mode 100644
index 0000000000..eb2097d0a4
--- /dev/null
+++ b/netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp
@@ -0,0 +1,88 @@
+#include <combaseapi.h>
+
+#include "gtest/gtest.h"
+#include "mozilla/SHA1.h"
+#include "nsNotifyAddrListener.h"
+
+using namespace mozilla;
+
+GUID StringToGuid(const std::string& str) {
+ GUID guid;
+ sscanf(str.c_str(),
+ "%8lx-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
+ &guid.Data1, &guid.Data2, &guid.Data3, &guid.Data4[0], &guid.Data4[1],
+ &guid.Data4[2], &guid.Data4[3], &guid.Data4[4], &guid.Data4[5],
+ &guid.Data4[6], &guid.Data4[7]);
+
+ return guid;
+}
+
+TEST(TestGuidHashWindows, Single)
+{
+ // Setup
+ SHA1Sum expected_sha1;
+ SHA1Sum::Hash expected_digest;
+
+ GUID g1 = StringToGuid("264555b1-289c-4494-83d1-e158d1d95115");
+
+ expected_sha1.update(&g1, sizeof(GUID));
+ expected_sha1.finish(expected_digest);
+
+ std::vector<GUID> nwGUIDS;
+ nwGUIDS.push_back(g1);
+ SHA1Sum actual_sha1;
+ // Run
+ nsNotifyAddrListener::HashSortedNetworkIds(nwGUIDS, actual_sha1);
+ SHA1Sum::Hash actual_digest;
+ actual_sha1.finish(actual_digest);
+
+ // Assert
+ ASSERT_EQ(0, memcmp(&expected_digest, &actual_digest, sizeof(SHA1Sum::Hash)));
+}
+
+TEST(TestNetworkLinkIdHashingWindows, Multiple)
+{
+ // Setup
+ SHA1Sum expected_sha1;
+ SHA1Sum::Hash expected_digest;
+
+ std::vector<GUID> nwGUIDS;
+ nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000001"));
+ nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000002"));
+ nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000003"));
+ nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000004"));
+
+ for (const auto& guid : nwGUIDS) {
+ expected_sha1.update(&guid, sizeof(GUID));
+ }
+ expected_sha1.finish(expected_digest);
+
+ // Ordered
+ std::vector<GUID> ordered;
+ for (const auto& guid : nwGUIDS) {
+ ordered.push_back(guid);
+ }
+ SHA1Sum ordered_sha1;
+
+ // Unordered
+ std::vector<GUID> reversed;
+ for (auto it = nwGUIDS.rbegin(); it != nwGUIDS.rend(); ++it) {
+ reversed.push_back(*it);
+ }
+ SHA1Sum reversed_sha1;
+
+ // Run
+ nsNotifyAddrListener::HashSortedNetworkIds(ordered, ordered_sha1);
+ SHA1Sum::Hash ordered_digest;
+ ordered_sha1.finish(ordered_digest);
+
+ nsNotifyAddrListener::HashSortedNetworkIds(reversed, reversed_sha1);
+ SHA1Sum::Hash reversed_digest;
+ reversed_sha1.finish(reversed_digest);
+
+ // Assert
+ ASSERT_EQ(0,
+ memcmp(&expected_digest, &ordered_digest, sizeof(SHA1Sum::Hash)));
+ ASSERT_EQ(0,
+ memcmp(&expected_digest, &reversed_digest, sizeof(SHA1Sum::Hash)));
+}
diff --git a/netwerk/test/gtest/TestPACMan.cpp b/netwerk/test/gtest/TestPACMan.cpp
new file mode 100644
index 0000000000..46c57cfc79
--- /dev/null
+++ b/netwerk/test/gtest/TestPACMan.cpp
@@ -0,0 +1,246 @@
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "nsServiceManagerUtils.h"
+#include "../../../xpcom/threads/nsThreadManager.h"
+#include "nsIDHCPClient.h"
+#include "nsIPrefBranch.h"
+#include "nsComponentManager.h"
+#include "nsIPrefService.h"
+#include "nsNetCID.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/GenericFactory.h"
+#include "../../base/nsPACMan.h"
+
+#define TEST_WPAD_DHCP_OPTION "http://pac/pac.dat"
+#define TEST_ASSIGNED_PAC_URL "http://assignedpac/pac.dat"
+#define WPAD_PREF 4
+#define NETWORK_PROXY_TYPE_PREF_NAME "network.proxy.type"
+#define GETTING_NETWORK_PROXY_TYPE_FAILED (-1)
+
+nsCString WPADOptionResult;
+
+namespace mozilla {
+namespace net {
+
+nsresult SetNetworkProxyType(int32_t pref) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ if (!prefs) {
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+ return prefs->SetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref);
+}
+
+nsresult GetNetworkProxyType(int32_t* pref) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ if (!prefs) {
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+ return prefs->GetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref);
+}
+
+class nsTestDHCPClient final : public nsIDHCPClient {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDHCPCLIENT
+
+ nsTestDHCPClient() = default;
+
+ nsresult Init() { return NS_OK; };
+
+ private:
+ ~nsTestDHCPClient() = default;
+};
+
+NS_IMETHODIMP
+nsTestDHCPClient::GetOption(uint8_t option, nsACString& _retval) {
+ _retval.Assign(WPADOptionResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsTestDHCPClient, nsIDHCPClient)
+
+#define NS_TESTDHCPCLIENTSERVICE_CID /* {FEBF1D69-4D7D-4891-9524-045AD18B5593} \
+ */ \
+ { \
+ 0xFEBF1D69, 0x4D7D, 0x4891, { \
+ 0x95, 0x24, 0x04, 0x5a, 0xd1, 0x8b, 0x55, 0x93 \
+ } \
+ }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsTestDHCPClient, Init)
+NS_DEFINE_NAMED_CID(NS_TESTDHCPCLIENTSERVICE_CID);
+
+void SetOptionResult(const char* result) { WPADOptionResult.Assign(result); }
+
+class ProcessPendingEventsAction final : public Runnable {
+ public:
+ ProcessPendingEventsAction() : Runnable("net::ProcessPendingEventsAction") {}
+
+ NS_IMETHOD
+ Run() override {
+ if (NS_HasPendingEvents(nullptr)) {
+ NS_WARNING("Found pending requests on PAC thread");
+ nsresult rv;
+ rv = NS_ProcessPendingEvents(nullptr);
+ EXPECT_EQ(NS_OK, rv);
+ }
+ NS_WARNING("No pending requests on PAC thread");
+ return NS_OK;
+ }
+};
+
+class TestPACMan : public ::testing::Test {
+ protected:
+ RefPtr<nsPACMan> mPACMan;
+
+ void ProcessAllEvents() {
+ ProcessPendingEventsOnPACThread();
+ nsresult rv;
+ while (NS_HasPendingEvents(nullptr)) {
+ NS_WARNING("Pending events on main thread");
+ rv = NS_ProcessPendingEvents(nullptr);
+ ASSERT_EQ(NS_OK, rv);
+ ProcessPendingEventsOnPACThread();
+ }
+ NS_WARNING("End of pending events on main thread");
+ }
+
+ // This method is used to ensure that all pending events on the main thread
+ // and the Proxy thread are processsed.
+ // It iterates over ProcessAllEvents because simply calling ProcessAllEvents
+ // once did not reliably process the events on both threads on all platforms.
+ void ProcessAllEventsTenTimes() {
+ for (int i = 0; i < 10; i++) {
+ ProcessAllEvents();
+ }
+ }
+
+ virtual void SetUp() {
+ ASSERT_EQ(NS_OK, GetNetworkProxyType(&originalNetworkProxyTypePref));
+ nsCOMPtr<nsIFactory> factory;
+ nsresult rv = nsComponentManagerImpl::gComponentManager->GetClassObject(
+ kNS_TESTDHCPCLIENTSERVICE_CID, NS_GET_IID(nsIFactory),
+ getter_AddRefs(factory));
+ if (NS_SUCCEEDED(rv) && factory) {
+ rv = nsComponentManagerImpl::gComponentManager->UnregisterFactory(
+ kNS_TESTDHCPCLIENTSERVICE_CID, factory);
+ ASSERT_EQ(NS_OK, rv);
+ }
+ factory = new mozilla::GenericFactory(nsTestDHCPClientConstructor);
+ nsComponentManagerImpl::gComponentManager->RegisterFactory(
+ kNS_TESTDHCPCLIENTSERVICE_CID, "nsTestDHCPClient",
+ NS_DHCPCLIENT_CONTRACTID, factory);
+
+ mPACMan = new nsPACMan(nullptr);
+ mPACMan->SetWPADOverDHCPEnabled(true);
+ mPACMan->Init(nullptr);
+ ASSERT_EQ(NS_OK, SetNetworkProxyType(WPAD_PREF));
+ }
+
+ virtual void TearDown() {
+ mPACMan->Shutdown();
+ if (originalNetworkProxyTypePref != GETTING_NETWORK_PROXY_TYPE_FAILED) {
+ ASSERT_EQ(NS_OK, SetNetworkProxyType(originalNetworkProxyTypePref));
+ }
+ }
+
+ nsCOMPtr<nsIDHCPClient> GetPACManDHCPCient() { return mPACMan->mDHCPClient; }
+
+ void SetPACManDHCPCient(nsCOMPtr<nsIDHCPClient> aValue) {
+ mPACMan->mDHCPClient = std::move(aValue);
+ }
+
+ void AssertPACSpecEqualTo(const char* aExpected) {
+ ASSERT_STREQ(aExpected, mPACMan->mPACURISpec.Data());
+ }
+
+ private:
+ int32_t originalNetworkProxyTypePref = GETTING_NETWORK_PROXY_TYPE_FAILED;
+
+ void ProcessPendingEventsOnPACThread() {
+ RefPtr<ProcessPendingEventsAction> action =
+ new ProcessPendingEventsAction();
+
+ mPACMan->DispatchToPAC(action.forget(), /*aSync =*/true);
+ }
+};
+
+TEST_F(TestPACMan, TestCreateDHCPClientAndGetOption) {
+ SetOptionResult(TEST_WPAD_DHCP_OPTION);
+ nsCString spec;
+
+ GetPACManDHCPCient()->GetOption(252, spec);
+
+ ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, spec.Data());
+}
+
+TEST_F(TestPACMan, TestCreateDHCPClientAndGetEmptyOption) {
+ SetOptionResult("");
+ nsCString spec;
+ spec.AssignLiteral(TEST_ASSIGNED_PAC_URL);
+
+ GetPACManDHCPCient()->GetOption(252, spec);
+
+ ASSERT_TRUE(spec.IsEmpty());
+}
+
+TEST_F(TestPACMan,
+ WhenTheDHCPClientExistsAndDHCPIsNonEmptyDHCPOptionIsUsedAsPACUri) {
+ SetOptionResult(TEST_WPAD_DHCP_OPTION);
+
+ mPACMan->LoadPACFromURI(""_ns);
+ ProcessAllEventsTenTimes();
+
+ ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
+ AssertPACSpecEqualTo(TEST_WPAD_DHCP_OPTION);
+}
+
+TEST_F(TestPACMan, WhenTheDHCPResponseIsEmptyWPADDefaultsToStandardURL) {
+ SetOptionResult(""_ns.Data());
+
+ mPACMan->LoadPACFromURI(""_ns);
+ ASSERT_TRUE(NS_HasPendingEvents(nullptr));
+ ProcessAllEventsTenTimes();
+
+ ASSERT_STREQ("", WPADOptionResult.Data());
+ AssertPACSpecEqualTo("http://wpad/wpad.dat");
+}
+
+TEST_F(TestPACMan, WhenThereIsNoDHCPClientWPADDefaultsToStandardURL) {
+ SetOptionResult(TEST_WPAD_DHCP_OPTION);
+ SetPACManDHCPCient(nullptr);
+
+ mPACMan->LoadPACFromURI(""_ns);
+ ProcessAllEventsTenTimes();
+
+ ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
+ AssertPACSpecEqualTo("http://wpad/wpad.dat");
+}
+
+TEST_F(TestPACMan, WhenWPADOverDHCPIsPreffedOffWPADDefaultsToStandardURL) {
+ SetOptionResult(TEST_WPAD_DHCP_OPTION);
+ mPACMan->SetWPADOverDHCPEnabled(false);
+
+ mPACMan->LoadPACFromURI(""_ns);
+ ProcessAllEventsTenTimes();
+
+ ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
+ AssertPACSpecEqualTo("http://wpad/wpad.dat");
+}
+
+TEST_F(TestPACMan, WhenPACUriIsSetDirectlyItIsUsedRatherThanWPAD) {
+ SetOptionResult(TEST_WPAD_DHCP_OPTION);
+ nsCString spec;
+ spec.AssignLiteral(TEST_ASSIGNED_PAC_URL);
+
+ mPACMan->LoadPACFromURI(spec);
+ ProcessAllEventsTenTimes();
+
+ AssertPACSpecEqualTo(TEST_ASSIGNED_PAC_URL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestProtocolProxyService.cpp b/netwerk/test/gtest/TestProtocolProxyService.cpp
new file mode 100644
index 0000000000..a26f5f62a8
--- /dev/null
+++ b/netwerk/test/gtest/TestProtocolProxyService.cpp
@@ -0,0 +1,164 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "../../base/nsProtocolProxyService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+TEST(TestProtocolProxyService, LoadHostFilters)
+{
+ nsCOMPtr<nsIProtocolProxyService2> ps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CID);
+ ASSERT_TRUE(ps);
+ mozilla::net::nsProtocolProxyService* pps =
+ static_cast<mozilla::net::nsProtocolProxyService*>(ps.get());
+
+ nsCOMPtr<nsIURI> url;
+ nsAutoCString spec;
+
+ auto CheckLoopbackURLs = [&](bool expected) {
+ // loopback IPs are always filtered
+ spec = "http://127.0.0.1";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ spec = "http://[::1]";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ spec = "http://localhost";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckURLs = [&](bool expected) {
+ spec = "http://example.com";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "https://10.2.3.4";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 443), expected);
+
+ spec = "http://1.2.3.4";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://1.2.3.4:8080";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://[2001::1]";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://2.3.4.5:7777";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://[abcd::2]:123";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://bla.test.com";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckPortDomain = [&](bool expected) {
+ spec = "http://blabla.com:10";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckLocalDomain = [&](bool expected) {
+ spec = "http://test";
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ // --------------------------------------------------------------------------
+
+ nsAutoCString filter;
+
+ // Anything is allowed when there are no filters set
+ printf("Testing empty filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ CheckLoopbackURLs(false);
+ CheckLocalDomain(true);
+ CheckURLs(true);
+ CheckPortDomain(true);
+
+ // --------------------------------------------------------------------------
+
+ filter =
+ "example.com, 1.2.3.4/16, [2001::1], 10.0.0.0/8, 2.3.0.0/16:7777, "
+ "[abcd::1]/64:123, *.test.com";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ CheckLoopbackURLs(false);
+ // Check URLs can no longer use filtered proxy
+ CheckURLs(false);
+ CheckLocalDomain(true);
+ CheckPortDomain(true);
+
+ // --------------------------------------------------------------------------
+
+ // This is space separated. See bug 1346711 comment 4. We check this to keep
+ // backwards compatibility.
+ filter = "<local> blabla.com:10";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ CheckLoopbackURLs(false);
+ CheckURLs(true);
+ CheckLocalDomain(false);
+ CheckPortDomain(false);
+
+ // Check that we don't crash on weird input
+ filter = "a b c abc:1x2, ,, * ** *.* *:10 :20 :40/12 */12:90";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ // Check that filtering works properly when the filter is set to "<local>"
+ filter = "<local>";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ CheckLoopbackURLs(false);
+ CheckURLs(true);
+ CheckLocalDomain(false);
+ CheckPortDomain(true);
+
+ // Check that allow_hijacking_localhost works with empty filter
+ Preferences::SetBool("network.proxy.allow_hijacking_localhost", true);
+
+ filter = "";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ CheckLoopbackURLs(true);
+ CheckLocalDomain(true);
+ CheckURLs(true);
+ CheckPortDomain(true);
+
+ // Check that allow_hijacking_localhost works with non-trivial filter
+ filter = "127.0.0.1, [::1], localhost, blabla.com:10";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ CheckLoopbackURLs(false);
+ CheckLocalDomain(true);
+ CheckURLs(true);
+ CheckPortDomain(false);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestReadStreamToString.cpp b/netwerk/test/gtest/TestReadStreamToString.cpp
new file mode 100644
index 0000000000..f5fa0a4979
--- /dev/null
+++ b/netwerk/test/gtest/TestReadStreamToString.cpp
@@ -0,0 +1,190 @@
+#include "gtest/gtest.h"
+
+#include "Helpers.h"
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+
+// Here we test the reading a pre-allocated size
+TEST(TestReadStreamToString, SyncStreamPreAllocatedSize)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer));
+
+ uint64_t written;
+ nsAutoCString result;
+ result.SetLength(5);
+
+ void* ptr = result.BeginWriting();
+
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, 5, &written));
+ ASSERT_EQ((uint64_t)5, written);
+ ASSERT_TRUE(nsCString(buffer.get(), 5).Equals(result));
+
+ // The pointer should be equal: no relocation.
+ ASSERT_EQ(ptr, result.BeginWriting());
+}
+
+// Here we test the reading the full size of a sync stream
+TEST(TestReadStreamToString, SyncStreamFullSize)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer));
+
+ uint64_t written;
+ nsAutoCString result;
+
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, buffer.Length(),
+ &written));
+ ASSERT_EQ(buffer.Length(), written);
+ ASSERT_TRUE(buffer.Equals(result));
+}
+
+// Here we test the reading less than the full size of a sync stream
+TEST(TestReadStreamToString, SyncStreamLessThan)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer));
+
+ uint64_t written;
+ nsAutoCString result;
+
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, 5, &written));
+ ASSERT_EQ((uint64_t)5, written);
+ ASSERT_TRUE(nsCString(buffer.get(), 5).Equals(result));
+}
+
+// Here we test the reading more than the full size of a sync stream
+TEST(TestReadStreamToString, SyncStreamMoreThan)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer));
+
+ uint64_t written;
+ nsAutoCString result;
+
+ // Reading more than the buffer size.
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result,
+ buffer.Length() + 5, &written));
+ ASSERT_EQ(buffer.Length(), written);
+ ASSERT_TRUE(buffer.Equals(result));
+}
+
+// Here we test the reading a sync stream without passing the size
+TEST(TestReadStreamToString, SyncStreamUnknownSize)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer));
+
+ uint64_t written;
+ nsAutoCString result;
+
+ // Reading all without passing the size
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, -1, &written));
+ ASSERT_EQ(buffer.Length(), written);
+ ASSERT_TRUE(buffer.Equals(result));
+}
+
+// Here we test the reading the full size of an async stream
+TEST(TestReadStreamToString, AsyncStreamFullSize)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer);
+
+ uint64_t written;
+ nsAutoCString result;
+
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, buffer.Length(),
+ &written));
+ ASSERT_EQ(buffer.Length(), written);
+ ASSERT_TRUE(buffer.Equals(result));
+}
+
+// Here we test the reading less than the full size of an async stream
+TEST(TestReadStreamToString, AsyncStreamLessThan)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer);
+
+ uint64_t written;
+ nsAutoCString result;
+
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, 5, &written));
+ ASSERT_EQ((uint64_t)5, written);
+ ASSERT_TRUE(nsCString(buffer.get(), 5).Equals(result));
+}
+
+// Here we test the reading more than the full size of an async stream
+TEST(TestReadStreamToString, AsyncStreamMoreThan)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer);
+
+ uint64_t written;
+ nsAutoCString result;
+
+ // Reading more than the buffer size.
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result,
+ buffer.Length() + 5, &written));
+ ASSERT_EQ(buffer.Length(), written);
+ ASSERT_TRUE(buffer.Equals(result));
+}
+
+// Here we test the reading an async stream without passing the size
+TEST(TestReadStreamToString, AsyncStreamUnknownSize)
+{
+ nsCString buffer;
+ buffer.AssignLiteral("Hello world!");
+
+ nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer);
+
+ uint64_t written;
+ nsAutoCString result;
+
+ // Reading all without passing the size
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, -1, &written));
+ ASSERT_EQ(buffer.Length(), written);
+ ASSERT_TRUE(buffer.Equals(result));
+}
+
+// Here we test the reading an async big stream without passing the size
+TEST(TestReadStreamToString, AsyncStreamUnknownBigSize)
+{
+ nsCString buffer;
+
+ buffer.SetLength(4096 * 2);
+ for (uint32_t i = 0; i < 4096 * 2; ++i) {
+ buffer.BeginWriting()[i] = i % 10;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer);
+
+ uint64_t written;
+ nsAutoCString result;
+
+ // Reading all without passing the size
+ ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, -1, &written));
+ ASSERT_EQ(buffer.Length(), written);
+ ASSERT_TRUE(buffer.Equals(result));
+}
diff --git a/netwerk/test/gtest/TestSSLTokensCache.cpp b/netwerk/test/gtest/TestSSLTokensCache.cpp
new file mode 100644
index 0000000000..0f67a532eb
--- /dev/null
+++ b/netwerk/test/gtest/TestSSLTokensCache.cpp
@@ -0,0 +1,154 @@
+#include <numeric>
+
+#include "CertVerifier.h"
+#include "CommonSocketControl.h"
+#include "SSLTokensCache.h"
+#include "TransportSecurityInfo.h"
+#include "gtest/gtest.h"
+#include "mozilla/Preferences.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIWebProgressListener.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsServiceManagerUtils.h"
+#include "sslproto.h"
+
+static already_AddRefed<CommonSocketControl> createDummySocketControl() {
+ nsCOMPtr<nsIX509CertDB> certDB(do_GetService(NS_X509CERTDB_CONTRACTID));
+ EXPECT_TRUE(certDB);
+ nsLiteralCString base64(
+ "MIIBbjCCARWgAwIBAgIUOyCxVVqw03yUxKSfSojsMF8K/"
+ "ikwCgYIKoZIzj0EAwIwHTEbMBkGA1UEAwwScm9vdF9zZWNwMjU2azFfMjU2MCIYDzIwMjAxM"
+ "TI3MDAwMDAwWhgPMjAyMzAyMDUwMDAwMDBaMC8xLTArBgNVBAMMJGludF9zZWNwMjU2cjFfM"
+ "jU2LXJvb3Rfc2VjcDI1NmsxXzI1NjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE+/"
+ "u7th4Pj5saYKWayHBOLsBQtCPjz3LpI/"
+ "LE95S0VcKmnSM0VsNsQRnQcG4A7tyNGTkNeZG3stB6ME6qBKpsCjHTAbMAwGA1UdEwQFMAMB"
+ "Af8wCwYDVR0PBAQDAgEGMAoGCCqGSM49BAMCA0cAMEQCIFuwodUwyOUnIR4KN5ZCSrU7y4iz"
+ "4/1EWRdHm5kWKi8dAiB6Ixn9sw3uBVbyxnQKYqGnOwM+qLOkJK0W8XkIE3n5sg==");
+ nsCOMPtr<nsIX509Cert> cert;
+ EXPECT_TRUE(NS_SUCCEEDED(
+ certDB->ConstructX509FromBase64(base64, getter_AddRefs(cert))));
+ EXPECT_TRUE(cert);
+ nsTArray<nsTArray<uint8_t>> succeededCertChain;
+ for (size_t i = 0; i < 3; i++) {
+ nsTArray<uint8_t> certDER;
+ EXPECT_TRUE(NS_SUCCEEDED(cert->GetRawDER(certDER)));
+ succeededCertChain.AppendElement(std::move(certDER));
+ }
+ RefPtr<CommonSocketControl> socketControl(
+ new CommonSocketControl(nsLiteralCString("example.com"), 433, 0));
+ socketControl->SetServerCert(cert, mozilla::psm::EVStatus::NotEV);
+ socketControl->SetSucceededCertChain(std::move(succeededCertChain));
+ return socketControl.forget();
+}
+
+static auto MakeTestData(const size_t aDataSize) {
+ auto data = nsTArray<uint8_t>();
+ data.SetLength(aDataSize);
+ std::iota(data.begin(), data.end(), 0);
+ return data;
+}
+
+static void putToken(const nsACString& aKey, uint32_t aSize) {
+ RefPtr<CommonSocketControl> socketControl = createDummySocketControl();
+ nsTArray<uint8_t> token = MakeTestData(aSize);
+ nsresult rv = mozilla::net::SSLTokensCache::Put(aKey, token.Elements(), aSize,
+ socketControl, aSize);
+ ASSERT_EQ(rv, NS_OK);
+}
+
+static void getAndCheckResult(const nsACString& aKey, uint32_t aExpectedSize) {
+ nsTArray<uint8_t> result;
+ mozilla::net::SessionCacheInfo unused;
+ nsresult rv = mozilla::net::SSLTokensCache::Get(aKey, result, unused);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(result.Length(), (size_t)aExpectedSize);
+}
+
+TEST(TestTokensCache, SinglePut)
+{
+ mozilla::net::SSLTokensCache::Clear();
+ mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 1);
+ mozilla::Preferences::SetBool("network.ssl_tokens_cache_use_only_once", true);
+
+ putToken("anon:www.example.com:443"_ns, 100);
+ nsTArray<uint8_t> result;
+ mozilla::net::SessionCacheInfo unused;
+ nsresult rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns,
+ result, unused);
+ ASSERT_EQ(rv, NS_OK);
+ rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns, result,
+ unused);
+ ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE);
+}
+
+TEST(TestTokensCache, MultiplePut)
+{
+ mozilla::net::SSLTokensCache::Clear();
+ mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
+
+ putToken("anon:www.example1.com:443"_ns, 300);
+ // This record will be removed because
+ // "network.ssl_tokens_cache_records_per_entry" is 3.
+ putToken("anon:www.example1.com:443"_ns, 100);
+ putToken("anon:www.example1.com:443"_ns, 200);
+ putToken("anon:www.example1.com:443"_ns, 400);
+
+ // Test if records are ordered by the expiration time
+ getAndCheckResult("anon:www.example1.com:443"_ns, 200);
+ getAndCheckResult("anon:www.example1.com:443"_ns, 300);
+ getAndCheckResult("anon:www.example1.com:443"_ns, 400);
+}
+
+TEST(TestTokensCache, RemoveAll)
+{
+ mozilla::net::SSLTokensCache::Clear();
+ mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
+
+ putToken("anon:www.example1.com:443"_ns, 100);
+ putToken("anon:www.example1.com:443"_ns, 200);
+ putToken("anon:www.example1.com:443"_ns, 300);
+
+ putToken("anon:www.example2.com:443"_ns, 100);
+ putToken("anon:www.example2.com:443"_ns, 200);
+ putToken("anon:www.example2.com:443"_ns, 300);
+
+ nsTArray<uint8_t> result;
+ mozilla::net::SessionCacheInfo unused;
+ nsresult rv = mozilla::net::SSLTokensCache::Get(
+ "anon:www.example1.com:443"_ns, result, unused);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(result.Length(), (size_t)100);
+
+ rv = mozilla::net::SSLTokensCache::RemoveAll("anon:www.example1.com:443"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = mozilla::net::SSLTokensCache::Get("anon:www.example1.com:443"_ns, result,
+ unused);
+ ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE);
+
+ rv = mozilla::net::SSLTokensCache::Get("anon:www.example2.com:443"_ns, result,
+ unused);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(result.Length(), (size_t)100);
+}
+
+TEST(TestTokensCache, Eviction)
+{
+ mozilla::net::SSLTokensCache::Clear();
+
+ mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
+ mozilla::Preferences::SetInt("network.ssl_tokens_cache_capacity", 8);
+
+ putToken("anon:www.example2.com:443"_ns, 300);
+ putToken("anon:www.example2.com:443"_ns, 400);
+ putToken("anon:www.example2.com:443"_ns, 500);
+ // The one has expiration time "300" will be removed because we only allow 3
+ // records per entry.
+ putToken("anon:www.example2.com:443"_ns, 600);
+
+ putToken("anon:www.example3.com:443"_ns, 600);
+ putToken("anon:www.example3.com:443"_ns, 500);
+ // The one has expiration time "400" was evicted, so we get "500".
+ getAndCheckResult("anon:www.example2.com:443"_ns, 500);
+}
diff --git a/netwerk/test/gtest/TestServerTimingHeader.cpp b/netwerk/test/gtest/TestServerTimingHeader.cpp
new file mode 100644
index 0000000000..183726a440
--- /dev/null
+++ b/netwerk/test/gtest/TestServerTimingHeader.cpp
@@ -0,0 +1,238 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/net/nsServerTiming.h"
+#include <string>
+#include <vector>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+void testServerTimingHeader(
+ const char* headerValue,
+ std::vector<std::vector<std::string>> expectedResults) {
+ nsAutoCString header(headerValue);
+ ServerTimingParser parser(header);
+ parser.Parse();
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> results =
+ parser.TakeServerTimingHeaders();
+
+ ASSERT_EQ(results.Length(), expectedResults.size());
+
+ unsigned i = 0;
+ for (const auto& header : results) {
+ std::vector<std::string> expectedResult(expectedResults[i++]);
+ nsCString name;
+ mozilla::Unused << header->GetName(name);
+ ASSERT_TRUE(name.Equals(expectedResult[0].c_str()));
+
+ double duration;
+ mozilla::Unused << header->GetDuration(&duration);
+ ASSERT_EQ(duration, atof(expectedResult[1].c_str()));
+
+ nsCString description;
+ mozilla::Unused << header->GetDescription(description);
+ ASSERT_TRUE(description.Equals(expectedResult[2].c_str()));
+ }
+}
+
+TEST(TestServerTimingHeader, HeaderParsing)
+{
+ // Test cases below are copied from
+ // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/network/HTTPParsersTest.cpp
+
+ testServerTimingHeader("", {});
+ testServerTimingHeader("metric", {{"metric", "0", ""}});
+ testServerTimingHeader("metric;dur", {{"metric", "0", ""}});
+ testServerTimingHeader("metric;dur=123.4", {{"metric", "123.4", ""}});
+ testServerTimingHeader("metric;dur=\"123.4\"", {{"metric", "123.4", ""}});
+
+ testServerTimingHeader("metric;desc", {{"metric", "0", ""}});
+ testServerTimingHeader("metric;desc=description",
+ {{"metric", "0", "description"}});
+ testServerTimingHeader("metric;desc=\"description\"",
+ {{"metric", "0", "description"}});
+
+ testServerTimingHeader("metric;dur;desc", {{"metric", "0", ""}});
+ testServerTimingHeader("metric;dur=123.4;desc", {{"metric", "123.4", ""}});
+ testServerTimingHeader("metric;dur;desc=description",
+ {{"metric", "0", "description"}});
+ testServerTimingHeader("metric;dur=123.4;desc=description",
+ {{"metric", "123.4", "description"}});
+ testServerTimingHeader("metric;desc;dur", {{"metric", "0", ""}});
+ testServerTimingHeader("metric;desc;dur=123.4", {{"metric", "123.4", ""}});
+ testServerTimingHeader("metric;desc=description;dur",
+ {{"metric", "0", "description"}});
+ testServerTimingHeader("metric;desc=description;dur=123.4",
+ {{"metric", "123.4", "description"}});
+
+ // special chars in name
+ testServerTimingHeader("aB3!#$%&'*+-.^_`|~",
+ {{"aB3!#$%&'*+-.^_`|~", "0", ""}});
+
+ // delimiter chars in quoted description
+ testServerTimingHeader("metric;desc=\"descr;,=iption\";dur=123.4",
+ {{"metric", "123.4", "descr;,=iption"}});
+
+ // whitespace
+ testServerTimingHeader("metric ; ", {{"metric", "0", ""}});
+ testServerTimingHeader("metric , ", {{"metric", "0", ""}});
+ testServerTimingHeader("metric ; dur = 123.4 ; desc = description",
+ {{"metric", "123.4", "description"}});
+ testServerTimingHeader("metric ; desc = description ; dur = 123.4",
+ {{"metric", "123.4", "description"}});
+
+ // multiple entries
+ testServerTimingHeader(
+ "metric1;dur=12.3;desc=description1,metric2;dur=45.6;"
+ "desc=description2,metric3;dur=78.9;desc=description3",
+ {{"metric1", "12.3", "description1"},
+ {"metric2", "45.6", "description2"},
+ {"metric3", "78.9", "description3"}});
+ testServerTimingHeader("metric1,metric2 ,metric3, metric4 , metric5",
+ {{"metric1", "0", ""},
+ {"metric2", "0", ""},
+ {"metric3", "0", ""},
+ {"metric4", "0", ""},
+ {"metric5", "0", ""}});
+
+ // quoted-strings
+ // metric;desc=\ --> ''
+ testServerTimingHeader("metric;desc=\\", {{"metric", "0", ""}});
+ // metric;desc=" --> ''
+ testServerTimingHeader("metric;desc=\"", {{"metric", "0", ""}});
+ // metric;desc=\\ --> ''
+ testServerTimingHeader("metric;desc=\\\\", {{"metric", "0", ""}});
+ // metric;desc=\" --> ''
+ testServerTimingHeader("metric;desc=\\\"", {{"metric", "0", ""}});
+ // metric;desc="\ --> ''
+ testServerTimingHeader("metric;desc=\"\\", {{"metric", "0", ""}});
+ // metric;desc="" --> ''
+ testServerTimingHeader("metric;desc=\"\"", {{"metric", "0", ""}});
+ // metric;desc=\\\ --> ''
+ testServerTimingHeader(R"(metric;desc=\\\)", {{"metric", "0", ""}});
+ // metric;desc=\\" --> ''
+ testServerTimingHeader(R"(metric;desc=\\")", {{"metric", "0", ""}});
+ // metric;desc=\"\ --> ''
+ testServerTimingHeader(R"(metric;desc=\"\)", {{"metric", "0", ""}});
+ // metric;desc=\"" --> ''
+ testServerTimingHeader(R"(metric;desc=\"")", {{"metric", "0", ""}});
+ // metric;desc="\\ --> ''
+ testServerTimingHeader(R"(metric;desc="\\)", {{"metric", "0", ""}});
+ // metric;desc="\" --> ''
+ testServerTimingHeader(R"(metric;desc="\")", {{"metric", "0", ""}});
+ // metric;desc=""\ --> ''
+ testServerTimingHeader(R"(metric;desc=""\)", {{"metric", "0", ""}});
+ // metric;desc=""" --> ''
+ testServerTimingHeader(R"(metric;desc=""")", {{"metric", "0", ""}});
+ // metric;desc=\\\\ --> ''
+ testServerTimingHeader(R"(metric;desc=\\\\)", {{"metric", "0", ""}});
+ // metric;desc=\\\" --> ''
+ testServerTimingHeader(R"(metric;desc=\\\")", {{"metric", "0", ""}});
+ // metric;desc=\\"\ --> ''
+ testServerTimingHeader(R"(metric;desc=\\"\)", {{"metric", "0", ""}});
+ // metric;desc=\\"" --> ''
+ testServerTimingHeader(R"(metric;desc=\\"")", {{"metric", "0", ""}});
+ // metric;desc=\"\\ --> ''
+ testServerTimingHeader(R"(metric;desc=\"\\)", {{"metric", "0", ""}});
+ // metric;desc=\"\" --> ''
+ testServerTimingHeader(R"(metric;desc=\"\")", {{"metric", "0", ""}});
+ // metric;desc=\""\ --> ''
+ testServerTimingHeader(R"(metric;desc=\""\)", {{"metric", "0", ""}});
+ // metric;desc=\""" --> ''
+ testServerTimingHeader(R"(metric;desc=\""")", {{"metric", "0", ""}});
+ // metric;desc="\\\ --> ''
+ testServerTimingHeader(R"(metric;desc="\\\)", {{"metric", "0", ""}});
+ // metric;desc="\\" --> '\'
+ testServerTimingHeader(R"(metric;desc="\\")", {{"metric", "0", "\\"}});
+ // metric;desc="\"\ --> ''
+ testServerTimingHeader(R"(metric;desc="\"\)", {{"metric", "0", ""}});
+ // metric;desc="\"" --> '"'
+ testServerTimingHeader(R"(metric;desc="\"")", {{"metric", "0", "\""}});
+ // metric;desc=""\\ --> ''
+ testServerTimingHeader(R"(metric;desc=""\\)", {{"metric", "0", ""}});
+ // metric;desc=""\" --> ''
+ testServerTimingHeader(R"(metric;desc=""\")", {{"metric", "0", ""}});
+ // metric;desc="""\ --> ''
+ testServerTimingHeader(R"(metric;desc="""\)", {{"metric", "0", ""}});
+ // metric;desc="""" --> ''
+ testServerTimingHeader(R"(metric;desc="""")", {{"metric", "0", ""}});
+
+ // duplicate entry names
+ testServerTimingHeader(
+ "metric;dur=12.3;desc=description1,metric;dur=45.6;"
+ "desc=description2",
+ {{"metric", "12.3", "description1"}, {"metric", "45.6", "description2"}});
+
+ // non-numeric durations
+ testServerTimingHeader("metric;dur=foo", {{"metric", "0", ""}});
+ testServerTimingHeader("metric;dur=\"foo\"", {{"metric", "0", ""}});
+
+ // unrecognized param names
+ testServerTimingHeader(
+ "metric;foo=bar;desc=description;foo=bar;dur=123.4;foo=bar",
+ {{"metric", "123.4", "description"}});
+
+ // duplicate param names
+ testServerTimingHeader("metric;dur=123.4;dur=567.8",
+ {{"metric", "123.4", ""}});
+ testServerTimingHeader("metric;desc=description1;desc=description2",
+ {{"metric", "0", "description1"}});
+ testServerTimingHeader("metric;dur=foo;dur=567.8", {{"metric", "", ""}});
+
+ // unspecified param values
+ testServerTimingHeader("metric;dur;dur=123.4", {{"metric", "0", ""}});
+ testServerTimingHeader("metric;desc;desc=description", {{"metric", "0", ""}});
+
+ // param name case
+ testServerTimingHeader("metric;DuR=123.4;DeSc=description",
+ {{"metric", "123.4", "description"}});
+
+ // nonsense
+ testServerTimingHeader("metric=foo;dur;dur=123.4,metric2",
+ {{"metric", "0", ""}, {"metric2", "0", ""}});
+ testServerTimingHeader("metric\"foo;dur;dur=123.4,metric2",
+ {{"metric", "0", ""}});
+
+ // nonsense - return zero entries
+ testServerTimingHeader(" ", {});
+ testServerTimingHeader("=", {});
+ testServerTimingHeader("[", {});
+ testServerTimingHeader("]", {});
+ testServerTimingHeader(";", {});
+ testServerTimingHeader(",", {});
+ testServerTimingHeader("=;", {});
+ testServerTimingHeader(";=", {});
+ testServerTimingHeader("=,", {});
+ testServerTimingHeader(",=", {});
+ testServerTimingHeader(";,", {});
+ testServerTimingHeader(",;", {});
+ testServerTimingHeader("=;,", {});
+
+ // Invalid token
+ testServerTimingHeader("met=ric", {{"met", "0", ""}});
+ testServerTimingHeader("met ric", {{"met", "0", ""}});
+ testServerTimingHeader("met[ric", {{"met", "0", ""}});
+ testServerTimingHeader("met]ric", {{"met", "0", ""}});
+ testServerTimingHeader("metric;desc=desc=123, metric2",
+ {{"metric", "0", "desc"}, {"metric2", "0", ""}});
+ testServerTimingHeader("met ric;desc=de sc , metric2",
+ {{"met", "0", "de"}, {"metric2", "0", ""}});
+
+ // test cases from https://w3c.github.io/server-timing/#examples
+ testServerTimingHeader(
+ " miss, ,db;dur=53, app;dur=47.2 ",
+ {{"miss", "0", ""}, {"db", "53", ""}, {"app", "47.2", ""}});
+ testServerTimingHeader(" customView, dc;desc=atl ",
+ {{"customView", "0", ""}, {"dc", "0", "atl"}});
+ testServerTimingHeader(" total;dur=123.4 ", {{"total", "123.4", ""}});
+
+ // test cases for comma in quoted string
+ testServerTimingHeader(R"( metric ; desc="descr\"\";,=iption";dur=123.4)",
+ {{"metric", "123.4", "descr\"\";,=iption"}});
+ testServerTimingHeader(
+ " metric2;dur=\"123.4\";;desc=\",;\\\",;,\";;, metric ; desc = \" "
+ "\\\", ;\\\" \"; dur=123.4,",
+ {{"metric2", "123.4", ",;\",;,"}, {"metric", "123.4", " \", ;\" "}});
+}
diff --git a/netwerk/test/gtest/TestSocketTransportService.cpp b/netwerk/test/gtest/TestSocketTransportService.cpp
new file mode 100644
index 0000000000..89adad3740
--- /dev/null
+++ b/netwerk/test/gtest/TestSocketTransportService.cpp
@@ -0,0 +1,164 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsISocketTransport.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "../../base/nsSocketTransportService2.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+TEST(TestSocketTransportService, PortRemappingPreferenceReading)
+{
+ nsCOMPtr<nsISocketTransportService> service =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ ASSERT_TRUE(service);
+
+ auto* sts = gSocketTransportService;
+ ASSERT_TRUE(sts);
+
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "test"_ns, sts, NS_NewRunnableFunction("test", [&]() {
+ auto CheckPortRemap = [&](uint16_t input, uint16_t output) -> bool {
+ sts->ApplyPortRemap(&input);
+ return input == output;
+ };
+
+ // Ill-formed prefs
+ ASSERT_FALSE(sts->UpdatePortRemapPreference(";"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference(" ;"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("; "_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("foo"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference(" foo"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference(" foo "_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("10=20;"_ns));
+
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1="_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1,="_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-="_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-,="_ns));
+
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2,"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2-3"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,=3"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4,"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4="_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("10000000=10"_ns));
+
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2;3"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3="_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-foo=2;3=15"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=foo;3=15"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;foo=15"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3=foo"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2x3=15"_ns));
+ ASSERT_FALSE(sts->UpdatePortRemapPreference("1+4=2;3=15"_ns));
+
+ // Well-formed prefs
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("1=2"_ns));
+ ASSERT_TRUE(CheckPortRemap(1, 2));
+ ASSERT_TRUE(CheckPortRemap(2, 2));
+ ASSERT_TRUE(CheckPortRemap(3, 3));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("10=20"_ns));
+ ASSERT_TRUE(CheckPortRemap(1, 1));
+ ASSERT_TRUE(CheckPortRemap(2, 2));
+ ASSERT_TRUE(CheckPortRemap(3, 3));
+ ASSERT_TRUE(CheckPortRemap(10, 20));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("100-200=1000"_ns));
+ ASSERT_TRUE(CheckPortRemap(10, 10));
+ ASSERT_TRUE(CheckPortRemap(99, 99));
+ ASSERT_TRUE(CheckPortRemap(100, 1000));
+ ASSERT_TRUE(CheckPortRemap(101, 1000));
+ ASSERT_TRUE(CheckPortRemap(200, 1000));
+ ASSERT_TRUE(CheckPortRemap(201, 201));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("100-200,500=1000"_ns));
+ ASSERT_TRUE(CheckPortRemap(10, 10));
+ ASSERT_TRUE(CheckPortRemap(99, 99));
+ ASSERT_TRUE(CheckPortRemap(100, 1000));
+ ASSERT_TRUE(CheckPortRemap(101, 1000));
+ ASSERT_TRUE(CheckPortRemap(200, 1000));
+ ASSERT_TRUE(CheckPortRemap(201, 201));
+ ASSERT_TRUE(CheckPortRemap(499, 499));
+ ASSERT_TRUE(CheckPortRemap(500, 1000));
+ ASSERT_TRUE(CheckPortRemap(501, 501));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("1-3=10;5-8,12=20"_ns));
+ ASSERT_TRUE(CheckPortRemap(1, 10));
+ ASSERT_TRUE(CheckPortRemap(2, 10));
+ ASSERT_TRUE(CheckPortRemap(3, 10));
+ ASSERT_TRUE(CheckPortRemap(4, 4));
+ ASSERT_TRUE(CheckPortRemap(5, 20));
+ ASSERT_TRUE(CheckPortRemap(8, 20));
+ ASSERT_TRUE(CheckPortRemap(11, 11));
+ ASSERT_TRUE(CheckPortRemap(12, 20));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("80=8080;443=8080"_ns));
+ ASSERT_TRUE(CheckPortRemap(80, 8080));
+ ASSERT_TRUE(CheckPortRemap(443, 8080));
+
+ // Later rules rewrite earlier rules
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("10=100;10=200"_ns));
+ ASSERT_TRUE(CheckPortRemap(10, 200));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("10-20=100;10-20=200"_ns));
+ ASSERT_TRUE(CheckPortRemap(10, 200));
+ ASSERT_TRUE(CheckPortRemap(20, 200));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference("10-20=100;15=200"_ns));
+ ASSERT_TRUE(CheckPortRemap(10, 100));
+ ASSERT_TRUE(CheckPortRemap(15, 200));
+ ASSERT_TRUE(CheckPortRemap(20, 100));
+
+ ASSERT_TRUE(sts->UpdatePortRemapPreference(
+ " 100 - 200 = 1000 ; 150 = 2000 "_ns));
+ ASSERT_TRUE(CheckPortRemap(100, 1000));
+ ASSERT_TRUE(CheckPortRemap(150, 2000));
+ ASSERT_TRUE(CheckPortRemap(200, 1000));
+
+ // Turn off any mapping
+ ASSERT_TRUE(sts->UpdatePortRemapPreference(""_ns));
+ for (uint32_t port = 0; port < 65536; ++port) {
+ ASSERT_TRUE(CheckPortRemap((uint16_t)port, (uint16_t)port));
+ }
+ }));
+}
+
+TEST(TestSocketTransportService, StatusValues)
+{
+ static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_RESOLVING) ==
+ NS_NET_STATUS_RESOLVING_HOST);
+ static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_RESOLVED) ==
+ NS_NET_STATUS_RESOLVED_HOST);
+ static_assert(
+ static_cast<nsresult>(nsISocketTransport::STATUS_CONNECTING_TO) ==
+ NS_NET_STATUS_CONNECTING_TO);
+ static_assert(
+ static_cast<nsresult>(nsISocketTransport::STATUS_CONNECTED_TO) ==
+ NS_NET_STATUS_CONNECTED_TO);
+ static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_SENDING_TO) ==
+ NS_NET_STATUS_SENDING_TO);
+ static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_WAITING_FOR) ==
+ NS_NET_STATUS_WAITING_FOR);
+ static_assert(
+ static_cast<nsresult>(nsISocketTransport::STATUS_RECEIVING_FROM) ==
+ NS_NET_STATUS_RECEIVING_FROM);
+ static_assert(static_cast<nsresult>(
+ nsISocketTransport::STATUS_TLS_HANDSHAKE_STARTING) ==
+ NS_NET_STATUS_TLS_HANDSHAKE_STARTING);
+ static_assert(
+ static_cast<nsresult>(nsISocketTransport::STATUS_TLS_HANDSHAKE_ENDED) ==
+ NS_NET_STATUS_TLS_HANDSHAKE_ENDED);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestStandardURL.cpp b/netwerk/test/gtest/TestStandardURL.cpp
new file mode 100644
index 0000000000..035c92fcc2
--- /dev/null
+++ b/netwerk/test/gtest/TestStandardURL.cpp
@@ -0,0 +1,441 @@
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsIStandardURL.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIURIMutator.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/Base64.h"
+#include "nsEscape.h"
+
+using namespace mozilla;
+
+// In nsStandardURL.cpp
+extern nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result);
+extern nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber);
+extern int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length);
+TEST(TestStandardURL, Simple)
+{
+ nsCOMPtr<nsIURI> url;
+ ASSERT_EQ(NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec("http://example.com"_ns)
+ .Finalize(url),
+ NS_OK);
+ ASSERT_TRUE(url);
+
+ ASSERT_EQ(NS_MutateURI(url).SetSpec("http://example.com"_ns).Finalize(url),
+ NS_OK);
+
+ nsAutoCString out;
+
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "http://example.com/"_ns);
+
+ ASSERT_EQ(url->Resolve("foo.html?q=45"_ns, out), NS_OK);
+ ASSERT_TRUE(out == "http://example.com/foo.html?q=45"_ns);
+
+ ASSERT_EQ(NS_MutateURI(url).SetScheme("foo"_ns).Finalize(url), NS_OK);
+
+ ASSERT_EQ(url->GetScheme(out), NS_OK);
+ ASSERT_TRUE(out == "foo"_ns);
+
+ ASSERT_EQ(url->GetHost(out), NS_OK);
+ ASSERT_TRUE(out == "example.com"_ns);
+ ASSERT_EQ(NS_MutateURI(url).SetHost("www.yahoo.com"_ns).Finalize(url), NS_OK);
+ ASSERT_EQ(url->GetHost(out), NS_OK);
+ ASSERT_TRUE(out == "www.yahoo.com"_ns);
+
+ ASSERT_EQ(NS_MutateURI(url)
+ .SetPathQueryRef(nsLiteralCString(
+ "/some-path/one-the-net/about.html?with-a-query#for-you"))
+ .Finalize(url),
+ NS_OK);
+ ASSERT_EQ(url->GetPathQueryRef(out), NS_OK);
+ ASSERT_TRUE(out ==
+ nsLiteralCString(
+ "/some-path/one-the-net/about.html?with-a-query#for-you"));
+
+ ASSERT_EQ(NS_MutateURI(url)
+ .SetQuery(nsLiteralCString(
+ "a=b&d=c&what-ever-you-want-to-be-called=45"))
+ .Finalize(url),
+ NS_OK);
+ ASSERT_EQ(url->GetQuery(out), NS_OK);
+ ASSERT_TRUE(out == "a=b&d=c&what-ever-you-want-to-be-called=45"_ns);
+
+ ASSERT_EQ(NS_MutateURI(url).SetRef("#some-book-mark"_ns).Finalize(url),
+ NS_OK);
+ ASSERT_EQ(url->GetRef(out), NS_OK);
+ ASSERT_TRUE(out == "some-book-mark"_ns);
+}
+
+TEST(TestStandardURL, NormalizeGood)
+{
+ nsCString result;
+ const char* manual[] = {"0.0.0.0",
+ "0.0.0.0",
+ "0",
+ "0.0.0.0",
+ "000",
+ "0.0.0.0",
+ "0x00",
+ "0.0.0.0",
+ "10.20.100.200",
+ "10.20.100.200",
+ "255.255.255.255",
+ "255.255.255.255",
+ "0XFF.0xFF.0xff.0xFf",
+ "255.255.255.255",
+ "0x000ff.0X00FF.0x0ff.0xff",
+ "255.255.255.255",
+ "0x000fA.0X00FB.0x0fC.0xfD",
+ "250.251.252.253",
+ "0x000fE.0X00FF.0x0fC.0xfD",
+ "254.255.252.253",
+ "0x000fa.0x00fb.0x0fc.0xfd",
+ "250.251.252.253",
+ "0x000fe.0x00ff.0x0fc.0xfd",
+ "254.255.252.253",
+ "0377.0377.0377.0377",
+ "255.255.255.255",
+ "0000377.000377.00377.0377",
+ "255.255.255.255",
+ "65535",
+ "0.0.255.255",
+ "0xfFFf",
+ "0.0.255.255",
+ "0x00000ffff",
+ "0.0.255.255",
+ "0177777",
+ "0.0.255.255",
+ "000177777",
+ "0.0.255.255",
+ "0.13.65535",
+ "0.13.255.255",
+ "0.22.0xffff",
+ "0.22.255.255",
+ "0.123.0177777",
+ "0.123.255.255",
+ "65536",
+ "0.1.0.0",
+ "0200000",
+ "0.1.0.0",
+ "0x10000",
+ "0.1.0.0"};
+ for (uint32_t i = 0; i < sizeof(manual) / sizeof(manual[0]); i += 2) {
+ nsCString encHost(manual[i + 0]);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result));
+ ASSERT_TRUE(result.Equals(manual[i + 1]));
+ }
+
+ // Make sure we're getting the numbers correctly interpreted:
+ for (int i = 0; i < 256; i++) {
+ nsCString encHost = nsPrintfCString("0x%x", i);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result));
+ ASSERT_TRUE(result.Equals(nsPrintfCString("0.0.0.%d", i)));
+
+ encHost = nsPrintfCString("0%o", i);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result));
+ ASSERT_TRUE(result.Equals(nsPrintfCString("0.0.0.%d", i)));
+ }
+
+ // Some random numbers in the range, mixing hex, decimal, octal
+ for (int i = 0; i < 8; i++) {
+ int val[4] = {i * 11 + 13, i * 18 + 22, i * 4 + 28, i * 15 + 2};
+
+ nsCString encHost =
+ nsPrintfCString("%d.%d.%d.%d", val[0], val[1], val[2], val[3]);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result));
+ ASSERT_TRUE(result.Equals(encHost));
+
+ nsCString encHostM =
+ nsPrintfCString("0x%x.0x%x.0x%x.0x%x", val[0], val[1], val[2], val[3]);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result));
+ ASSERT_TRUE(result.Equals(encHost));
+
+ encHostM =
+ nsPrintfCString("0%o.0%o.0%o.0%o", val[0], val[1], val[2], val[3]);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result));
+ ASSERT_TRUE(result.Equals(encHost));
+
+ encHostM =
+ nsPrintfCString("0x%x.%d.0%o.%d", val[0], val[1], val[2], val[3]);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result));
+ ASSERT_TRUE(result.Equals(encHost));
+
+ encHostM =
+ nsPrintfCString("%d.0%o.0%o.0x%x", val[0], val[1], val[2], val[3]);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result));
+ ASSERT_TRUE(result.Equals(encHost));
+
+ encHostM =
+ nsPrintfCString("0%o.0%o.0x%x.0x%x", val[0], val[1], val[2], val[3]);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result));
+ ASSERT_TRUE(result.Equals(encHost));
+ }
+}
+
+TEST(TestStandardURL, NormalizeBad)
+{
+ nsAutoCString result;
+ const char* manual[] = {
+ "x22.232.12.32", "122..12.32", "122.12.32.12.32", "122.12.32..",
+ "122.12.xx.22", "122.12.0xx.22", "0xx.12.01.22", "12.12.02x.22",
+ "1q.12.2.22", "122.01f.02.22", "12a.01.02.22", "12.01.02.20x1",
+ "10x2.01.02.20", "0xx.01.02.20", "10.x.02.20", "10.00x2.02.20",
+ "10.13.02x2.20", "10.x13.02.20", "10.0x134def.02.20", "\0.2.2.2",
+ "256.2.2.2", "2.256.2.2", "2.2.256.2", "2.2.2.256",
+ "2.2.-2.3", "+2.2.2.3", "13.0x2x2.2.3", "0x2x2.13.2.3"};
+
+ for (auto& i : manual) {
+ nsCString encHost(i);
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result));
+ }
+}
+
+TEST(TestStandardURL, From_test_standardurldotjs)
+{
+ // These are test (success and failure) cases from test_standardurl.js
+ nsAutoCString result;
+
+ const char* localIPv4s[] = {
+ "127.0.0.1",
+ "127.0.1",
+ "127.1",
+ "2130706433",
+ "0177.00.00.01",
+ "0177.00.01",
+ "0177.01",
+ "00000000000000000000000000177.0000000.0000000.0001",
+ "000000177.0000001",
+ "017700000001",
+ "0x7f.0x00.0x00.0x01",
+ "0x7f.0x01",
+ "0x7f000001",
+ "0x007f.0x0000.0x0000.0x0001",
+ "000177.0.00000.0x0001",
+ "127.0.0.1.",
+
+ "0X7F.0X00.0X00.0X01",
+ "0X7F.0X01",
+ "0X7F000001",
+ "0X007F.0X0000.0X0000.0X0001",
+ "000177.0.00000.0X0001"};
+ for (auto& localIPv4 : localIPv4s) {
+ nsCString encHost(localIPv4);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result));
+ ASSERT_TRUE(result.EqualsLiteral("127.0.0.1"));
+ }
+
+ const char* nonIPv4s[] = {"0xfffffffff", "0x100000000",
+ "4294967296", "1.2.0x10000",
+ "1.0x1000000", "256.0.0.1",
+ "1.256.1", "-1.0.0.0",
+ "1.2.3.4.5", "010000000000000000",
+ "2+3", "0.0.0.-1",
+ "1.2.3.4..", "1..2",
+ ".1.2.3.4", ".127"};
+ for (auto& nonIPv4 : nonIPv4s) {
+ nsCString encHost(nonIPv4);
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result));
+ }
+
+ const char* oneOrNoDotsIPv4s[] = {"127", "127."};
+ for (auto& localIPv4 : oneOrNoDotsIPv4s) {
+ nsCString encHost(localIPv4);
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result));
+ ASSERT_TRUE(result.EqualsLiteral("0.0.0.127"));
+ }
+}
+
+#define TEST_COUNT 10000
+
+MOZ_GTEST_BENCH(TestStandardURL, DISABLED_Perf, [] {
+ nsCOMPtr<nsIURI> url;
+ ASSERT_EQ(NS_OK, NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec("http://example.com"_ns)
+ .Finalize(url));
+
+ nsAutoCString out;
+ for (int i = TEST_COUNT; i; --i) {
+ ASSERT_EQ(NS_MutateURI(url).SetSpec("http://example.com"_ns).Finalize(url),
+ NS_OK);
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ url->Resolve("foo.html?q=45"_ns, out);
+ mozilla::Unused << NS_MutateURI(url).SetScheme("foo"_ns).Finalize(url);
+ url->GetScheme(out);
+ mozilla::Unused
+ << NS_MutateURI(url).SetHost("www.yahoo.com"_ns).Finalize(url);
+ url->GetHost(out);
+ mozilla::Unused
+ << NS_MutateURI(url)
+ .SetPathQueryRef(nsLiteralCString(
+ "/some-path/one-the-net/about.html?with-a-query#for-you"))
+ .Finalize(url);
+ url->GetPathQueryRef(out);
+ mozilla::Unused << NS_MutateURI(url)
+ .SetQuery(nsLiteralCString(
+ "a=b&d=c&what-ever-you-want-to-be-called=45"))
+ .Finalize(url);
+ url->GetQuery(out);
+ mozilla::Unused
+ << NS_MutateURI(url).SetRef("#some-book-mark"_ns).Finalize(url);
+ url->GetRef(out);
+ }
+});
+
+// Note the five calls in the loop, so divide by 100k
+MOZ_GTEST_BENCH(TestStandardURL, DISABLED_NormalizePerf, [] {
+ nsAutoCString result;
+ for (int i = 0; i < 20000; i++) {
+ nsAutoCString encHost("123.232.12.32");
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result));
+ nsAutoCString encHost2("83.62.12.92");
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost2, result));
+ nsAutoCString encHost3("8.7.6.5");
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost3, result));
+ nsAutoCString encHost4("111.159.123.220");
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost4, result));
+ nsAutoCString encHost5("1.160.204.200");
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost5, result));
+ }
+});
+
+// Bug 1394785 - ignore unstable test on OSX
+#ifndef XP_MACOSX
+// Note the five calls in the loop, so divide by 100k
+MOZ_GTEST_BENCH(TestStandardURL, DISABLED_NormalizePerfFails, [] {
+ nsAutoCString result;
+ for (int i = 0; i < 20000; i++) {
+ nsAutoCString encHost("123.292.12.32");
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result));
+ nsAutoCString encHost2("83.62.12.0x13292");
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost2, result));
+ nsAutoCString encHost3("8.7.6.0xhello");
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost3, result));
+ nsAutoCString encHost4("111.159.notonmywatch.220");
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost4, result));
+ nsAutoCString encHost5("1.160.204.20f");
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost5, result));
+ }
+});
+#endif
+
+TEST(TestStandardURL, Mutator)
+{
+ nsAutoCString out;
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec("http://example.com"_ns)
+ .Finalize(uri);
+ ASSERT_EQ(rv, NS_OK);
+
+ ASSERT_EQ(uri->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "http://example.com/"_ns);
+
+ rv = NS_MutateURI(uri)
+ .SetScheme("ftp"_ns)
+ .SetHost("mozilla.org"_ns)
+ .SetPathQueryRef("/path?query#ref"_ns)
+ .Finalize(uri);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(uri->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "ftp://mozilla.org/path?query#ref"_ns);
+
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(uri).SetScheme("https"_ns).Finalize(url);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "https://mozilla.org/path?query#ref"_ns);
+}
+
+TEST(TestStandardURL, Deserialize_Bug1392739)
+{
+ mozilla::ipc::StandardURLParams standard_params;
+ standard_params.urlType() = nsIStandardURL::URLTYPE_STANDARD;
+ standard_params.spec().Truncate();
+ standard_params.host() = mozilla::ipc::StandardURLSegment(4294967295, 1);
+
+ mozilla::ipc::URIParams params(standard_params);
+
+ nsCOMPtr<nsIURIMutator> mutator =
+ do_CreateInstance(NS_STANDARDURLMUTATOR_CID);
+ ASSERT_EQ(mutator->Deserialize(params), NS_ERROR_FAILURE);
+}
+
+TEST(TestStandardURL, CorruptSerialization)
+{
+ auto spec = "http://user:pass@example.com/path/to/file.ext?query#hash"_ns;
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(spec)
+ .Finalize(uri);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsAutoCString serialization;
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(uri);
+ ASSERT_TRUE(serializable);
+
+ // Check that the URL is normally serializable.
+ ASSERT_EQ(NS_OK, NS_SerializeToString(serializable, serialization));
+ nsCOMPtr<nsISupports> deserializedObject;
+ ASSERT_EQ(NS_OK, NS_DeserializeObject(serialization,
+ getter_AddRefs(deserializedObject)));
+
+ nsAutoCString canonicalBin;
+ Unused << Base64Decode(serialization, canonicalBin);
+
+// The spec serialization begins at byte 49
+// If the implementation of nsStandardURL::Write changes, this test will need
+// to be adjusted.
+#define SPEC_OFFSET 49
+
+ ASSERT_EQ(Substring(canonicalBin, SPEC_OFFSET, 7), "http://"_ns);
+
+ nsAutoCString corruptedBin = canonicalBin;
+ // change mScheme.mPos
+ corruptedBin.BeginWriting()[SPEC_OFFSET + spec.Length()] = 1;
+ Unused << Base64Encode(corruptedBin, serialization);
+ ASSERT_EQ(
+ NS_ERROR_MALFORMED_URI,
+ NS_DeserializeObject(serialization, getter_AddRefs(deserializedObject)));
+
+ corruptedBin = canonicalBin;
+ // change mScheme.mLen
+ corruptedBin.BeginWriting()[SPEC_OFFSET + spec.Length() + 4] = 127;
+ Unused << Base64Encode(corruptedBin, serialization);
+ ASSERT_EQ(
+ NS_ERROR_MALFORMED_URI,
+ NS_DeserializeObject(serialization, getter_AddRefs(deserializedObject)));
+}
+
+TEST(TestStandardURL, ParseIPv4Num)
+{
+ auto host = "0x.0x.0"_ns;
+
+ int32_t bases[4] = {10, 10, 10, 10};
+ bool onlyBase10 = true; // Track this as a special case
+ int32_t dotIndex[3]; // The positions of the dots in the string
+ int32_t length = static_cast<int32_t>(host.Length());
+
+ ASSERT_EQ(2,
+ Test_ValidateIPv4Number(host, bases, dotIndex, onlyBase10, length));
+
+ nsCString result;
+ ASSERT_EQ(NS_OK, Test_NormalizeIPv4("0x.0x.0"_ns, result));
+
+ uint32_t number;
+ Test_ParseIPv4Number("0x10"_ns, 16, number, 255);
+ ASSERT_EQ(number, (uint32_t)16);
+}
diff --git a/netwerk/test/gtest/TestUDPSocket.cpp b/netwerk/test/gtest/TestUDPSocket.cpp
new file mode 100644
index 0000000000..fe6e9b223d
--- /dev/null
+++ b/netwerk/test/gtest/TestUDPSocket.cpp
@@ -0,0 +1,410 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "gtest/gtest.h"
+#include "nsIUDPSocket.h"
+#include "nsISocketTransport.h"
+#include "nsIOutputStream.h"
+#include "nsINetAddr.h"
+#include "nsITimer.h"
+#include "nsContentUtils.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/net/DNS.h"
+#include "prerror.h"
+#include "nsComponentManagerUtils.h"
+
+#define REQUEST 0x68656c6f
+#define RESPONSE 0x6f6c6568
+#define MULTICAST_TIMEOUT 2000
+
+enum TestPhase { TEST_OUTPUT_STREAM, TEST_SEND_API, TEST_MULTICAST, TEST_NONE };
+
+static TestPhase phase = TEST_NONE;
+
+static bool CheckMessageContent(nsIUDPMessage* aMessage,
+ uint32_t aExpectedContent) {
+ nsCString data;
+ aMessage->GetData(data);
+
+ const char* buffer = data.get();
+ uint32_t len = data.Length();
+
+ FallibleTArray<uint8_t>& rawData = aMessage->GetDataAsTArray();
+ uint32_t rawLen = rawData.Length();
+
+ if (len != rawLen) {
+ ADD_FAILURE() << "Raw data length " << rawLen
+ << " does not match String data length " << len;
+ return false;
+ }
+
+ for (uint32_t i = 0; i < len; i++) {
+ if (buffer[i] != rawData[i]) {
+ ADD_FAILURE();
+ return false;
+ }
+ }
+
+ uint32_t input = 0;
+ for (uint32_t i = 0; i < len; i++) {
+ input += buffer[i] << (8 * i);
+ }
+
+ if (len != sizeof(uint32_t)) {
+ ADD_FAILURE() << "Message length mismatch, expected " << sizeof(uint32_t)
+ << " got " << len;
+ return false;
+ }
+ if (input != aExpectedContent) {
+ ADD_FAILURE() << "Message content mismatch, expected 0x" << std::hex
+ << aExpectedContent << " got 0x" << input;
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * UDPClientListener: listens for incomming UDP packets
+ */
+class UDPClientListener : public nsIUDPSocketListener {
+ protected:
+ virtual ~UDPClientListener();
+
+ public:
+ explicit UDPClientListener(WaitForCondition* waiter) : mWaiter(waiter) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+ nsresult mResult = NS_ERROR_FAILURE;
+ RefPtr<WaitForCondition> mWaiter;
+};
+
+NS_IMPL_ISUPPORTS(UDPClientListener, nsIUDPSocketListener)
+
+UDPClientListener::~UDPClientListener() = default;
+
+NS_IMETHODIMP
+UDPClientListener::OnPacketReceived(nsIUDPSocket* socket,
+ nsIUDPMessage* message) {
+ mResult = NS_OK;
+
+ uint16_t port;
+ nsCString ip;
+ nsCOMPtr<nsINetAddr> fromAddr;
+ message->GetFromAddr(getter_AddRefs(fromAddr));
+ fromAddr->GetPort(&port);
+ fromAddr->GetAddress(ip);
+
+ if (TEST_SEND_API == phase && CheckMessageContent(message, REQUEST)) {
+ uint32_t count;
+ nsTArray<uint8_t> data;
+ const uint32_t dataBuffer = RESPONSE;
+ data.AppendElements((const uint8_t*)&dataBuffer, sizeof(uint32_t));
+ mResult = socket->SendWithAddr(fromAddr, data, &count);
+ if (mResult == NS_OK && count == sizeof(uint32_t)) {
+ SUCCEED();
+ } else {
+ ADD_FAILURE();
+ }
+ return NS_OK;
+ }
+ if (TEST_OUTPUT_STREAM != phase || !CheckMessageContent(message, RESPONSE)) {
+ mResult = NS_ERROR_FAILURE;
+ }
+
+ // Notify thread
+ mWaiter->Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPClientListener::OnStopListening(nsIUDPSocket*, nsresult) {
+ mWaiter->Notify();
+ return NS_OK;
+}
+
+/*
+ * UDPServerListener: listens for incomming UDP packets
+ */
+class UDPServerListener : public nsIUDPSocketListener {
+ protected:
+ virtual ~UDPServerListener();
+
+ public:
+ explicit UDPServerListener(WaitForCondition* waiter) : mWaiter(waiter) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ nsresult mResult = NS_ERROR_FAILURE;
+ RefPtr<WaitForCondition> mWaiter;
+};
+
+NS_IMPL_ISUPPORTS(UDPServerListener, nsIUDPSocketListener)
+
+UDPServerListener::~UDPServerListener() = default;
+
+NS_IMETHODIMP
+UDPServerListener::OnPacketReceived(nsIUDPSocket* socket,
+ nsIUDPMessage* message) {
+ mResult = NS_OK;
+
+ uint16_t port;
+ nsCString ip;
+ nsCOMPtr<nsINetAddr> fromAddr;
+ message->GetFromAddr(getter_AddRefs(fromAddr));
+ fromAddr->GetPort(&port);
+ fromAddr->GetAddress(ip);
+ SUCCEED();
+
+ if (TEST_OUTPUT_STREAM == phase && CheckMessageContent(message, REQUEST)) {
+ nsCOMPtr<nsIOutputStream> outstream;
+ message->GetOutputStream(getter_AddRefs(outstream));
+
+ uint32_t count;
+ const uint32_t data = RESPONSE;
+ mResult = outstream->Write((const char*)&data, sizeof(uint32_t), &count);
+
+ if (mResult == NS_OK && count == sizeof(uint32_t)) {
+ SUCCEED();
+ } else {
+ ADD_FAILURE();
+ }
+ return NS_OK;
+ }
+ if (TEST_MULTICAST == phase && CheckMessageContent(message, REQUEST)) {
+ mResult = NS_OK;
+ } else if (TEST_SEND_API != phase ||
+ !CheckMessageContent(message, RESPONSE)) {
+ mResult = NS_ERROR_FAILURE;
+ }
+
+ // Notify thread
+ mWaiter->Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPServerListener::OnStopListening(nsIUDPSocket*, nsresult) {
+ mWaiter->Notify();
+ return NS_OK;
+}
+
+/**
+ * Multicast timer callback: detects delivery failure
+ */
+class MulticastTimerCallback : public nsITimerCallback, public nsINamed {
+ protected:
+ virtual ~MulticastTimerCallback();
+
+ public:
+ explicit MulticastTimerCallback(WaitForCondition* waiter)
+ : mResult(NS_ERROR_NOT_INITIALIZED), mWaiter(waiter) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsresult mResult;
+ RefPtr<WaitForCondition> mWaiter;
+};
+
+NS_IMPL_ISUPPORTS(MulticastTimerCallback, nsITimerCallback, nsINamed)
+
+MulticastTimerCallback::~MulticastTimerCallback() = default;
+
+NS_IMETHODIMP
+MulticastTimerCallback::Notify(nsITimer* timer) {
+ if (TEST_MULTICAST != phase) {
+ return NS_OK;
+ }
+ // Multicast ping failed
+ printf("Multicast ping timeout expired\n");
+ mResult = NS_ERROR_FAILURE;
+ mWaiter->Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastTimerCallback::GetName(nsACString& aName) {
+ aName.AssignLiteral("MulticastTimerCallback");
+ return NS_OK;
+}
+
+/**** Main ****/
+
+TEST(TestUDPSocket, TestUDPSocketMain)
+{
+ nsresult rv;
+
+ // Create UDPSocket
+ nsCOMPtr<nsIUDPSocket> server, client;
+ server = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ client = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ RefPtr<WaitForCondition> waiter = new WaitForCondition();
+
+ // Create UDPServerListener to process UDP packets
+ RefPtr<UDPServerListener> serverListener = new UDPServerListener(waiter);
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal = nsContentUtils::GetSystemPrincipal();
+
+ // Bind server socket to 0.0.0.0
+ rv = server->Init(0, false, systemPrincipal, true, 0);
+ ASSERT_NS_SUCCEEDED(rv);
+ int32_t serverPort;
+ server->GetPort(&serverPort);
+ server->AsyncListen(serverListener);
+
+ // Bind clinet on arbitrary port
+ RefPtr<UDPClientListener> clientListener = new UDPClientListener(waiter);
+ client->Init(0, false, systemPrincipal, true, 0);
+ client->AsyncListen(clientListener);
+
+ // Write data to server
+ uint32_t count;
+ nsTArray<uint8_t> data;
+ const uint32_t dataBuffer = REQUEST;
+ data.AppendElements((const uint8_t*)&dataBuffer, sizeof(uint32_t));
+
+ phase = TEST_OUTPUT_STREAM;
+ rv = client->Send("127.0.0.1"_ns, serverPort, data, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server
+ waiter->Wait(1);
+ ASSERT_NS_SUCCEEDED(serverListener->mResult);
+
+ // Read response from server
+ ASSERT_NS_SUCCEEDED(clientListener->mResult);
+
+ mozilla::net::NetAddr clientAddr;
+ rv = client->GetAddress(&clientAddr);
+ ASSERT_NS_SUCCEEDED(rv);
+ // The client address is 0.0.0.0, but Windows won't receive packets there, so
+ // use 127.0.0.1 explicitly
+ clientAddr.inet.ip = PR_htonl(127 << 24 | 1);
+
+ phase = TEST_SEND_API;
+ rv = server->SendWithAddress(&clientAddr, data.Elements(), data.Length(),
+ &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server
+ waiter->Wait(1);
+ ASSERT_NS_SUCCEEDED(serverListener->mResult);
+
+ // Read response from server
+ ASSERT_NS_SUCCEEDED(clientListener->mResult);
+
+ // Setup timer to detect multicast failure
+ nsCOMPtr<nsITimer> timer = NS_NewTimer();
+ ASSERT_TRUE(timer);
+ RefPtr<MulticastTimerCallback> timerCb = new MulticastTimerCallback(waiter);
+
+ // Join multicast group
+ printf("Joining multicast group\n");
+ phase = TEST_MULTICAST;
+ mozilla::net::NetAddr multicastAddr;
+ multicastAddr.inet.family = AF_INET;
+ multicastAddr.inet.ip = PR_htonl(224 << 24 | 255);
+ multicastAddr.inet.port = PR_htons(serverPort);
+ rv = server->JoinMulticastAddr(multicastAddr, nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, data.Elements(), data.Length(),
+ &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server to receive successfully
+ waiter->Wait(1);
+ ASSERT_NS_SUCCEEDED(serverListener->mResult);
+ ASSERT_NS_SUCCEEDED(timerCb->mResult);
+ timer->Cancel();
+
+ // Disable multicast loopback
+ printf("Disable multicast loopback\n");
+ client->SetMulticastLoopback(false);
+ server->SetMulticastLoopback(false);
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, data.Elements(), data.Length(),
+ &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server to fail to receive
+ waiter->Wait(1);
+ ASSERT_FALSE(NS_SUCCEEDED(timerCb->mResult));
+ timer->Cancel();
+
+ // Reset state
+ client->SetMulticastLoopback(true);
+ server->SetMulticastLoopback(true);
+
+ // Change multicast interface
+ mozilla::net::NetAddr loopbackAddr;
+ loopbackAddr.inet.family = AF_INET;
+ loopbackAddr.inet.ip = PR_htonl(INADDR_LOOPBACK);
+ client->SetMulticastInterfaceAddr(loopbackAddr);
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, data.Elements(), data.Length(),
+ &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server to fail to receive
+ waiter->Wait(1);
+ ASSERT_FALSE(NS_SUCCEEDED(timerCb->mResult));
+ timer->Cancel();
+
+ // Reset state
+ mozilla::net::NetAddr anyAddr;
+ anyAddr.inet.family = AF_INET;
+ anyAddr.inet.ip = PR_htonl(INADDR_ANY);
+ client->SetMulticastInterfaceAddr(anyAddr);
+
+ // Leave multicast group
+ rv = server->LeaveMulticastAddr(multicastAddr, nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, data.Elements(), data.Length(),
+ &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server to fail to receive
+ waiter->Wait(1);
+ ASSERT_FALSE(NS_SUCCEEDED(timerCb->mResult));
+ timer->Cancel();
+
+ goto close; // suppress warning about unused label
+
+close:
+ // Close server
+ server->Close();
+ client->Close();
+
+ // Wait for client and server to see closing
+ waiter->Wait(2);
+}
diff --git a/netwerk/test/gtest/TestURIMutator.cpp b/netwerk/test/gtest/TestURIMutator.cpp
new file mode 100644
index 0000000000..255ed640eb
--- /dev/null
+++ b/netwerk/test/gtest/TestURIMutator.cpp
@@ -0,0 +1,163 @@
+#include "gtest/gtest.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsIURIMutator.h"
+#include "nsIURL.h"
+#include "nsThreadPool.h"
+#include "nsNetUtil.h"
+
+TEST(TestURIMutator, Mutator)
+{
+ nsAutoCString out;
+
+ // This test instantiates a new nsStandardURL::Mutator (via contractID)
+ // and uses it to create a new URI.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec("http://example.com"_ns)
+ .Finalize(uri);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(uri->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "http://example.com/"_ns);
+
+ // This test verifies that we can use NS_MutateURI to change a URI
+ rv = NS_MutateURI(uri)
+ .SetScheme("ftp"_ns)
+ .SetHost("mozilla.org"_ns)
+ .SetPathQueryRef("/path?query#ref"_ns)
+ .Finalize(uri);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(uri->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "ftp://mozilla.org/path?query#ref"_ns);
+
+ // This test verifies that we can pass nsIURL to Finalize, and
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(uri).SetScheme("https"_ns).Finalize(url);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "https://mozilla.org/path?query#ref"_ns);
+
+ // This test verifies that we can pass nsIURL** to Finalize.
+ // We need to use the explicit template because it's actually passing
+ // getter_AddRefs
+ nsCOMPtr<nsIURL> url2;
+ rv = NS_MutateURI(url)
+ .SetRef("newref"_ns)
+ .Finalize<nsIURL>(getter_AddRefs(url2));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(url2->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "https://mozilla.org/path?query#newref"_ns);
+
+ // This test verifies that we can pass nsIURI** to Finalize.
+ // No need to be explicit.
+ auto functionSetRef = [](nsIURI* aURI, nsIURI** aResult) -> nsresult {
+ return NS_MutateURI(aURI).SetRef("originalRef"_ns).Finalize(aResult);
+ };
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = functionSetRef(url2, getter_AddRefs(newURI));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(newURI->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "https://mozilla.org/path?query#originalRef"_ns);
+
+ // This test verifies that we can pass nsIURI** to Finalize.
+ nsCOMPtr<nsIURI> uri2;
+ rv =
+ NS_MutateURI(url2).SetQuery("newquery"_ns).Finalize(getter_AddRefs(uri2));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(uri2->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "https://mozilla.org/path?newquery#newref"_ns);
+
+ // This test verifies that we can pass nsIURI** to Finalize.
+ // No need to be explicit.
+ auto functionSetQuery = [](nsIURI* aURI, nsIURL** aResult) -> nsresult {
+ return NS_MutateURI(aURI).SetQuery("originalQuery"_ns).Finalize(aResult);
+ };
+
+ nsCOMPtr<nsIURL> newURL;
+ rv = functionSetQuery(uri2, getter_AddRefs(newURL));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(newURL->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "https://mozilla.org/path?originalQuery#newref"_ns);
+
+ // Check that calling Finalize twice will fail.
+ NS_MutateURI mutator(newURL);
+ rv = mutator.SetQuery(""_ns).Finalize(uri2);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(uri2->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "https://mozilla.org/path#newref"_ns);
+ nsCOMPtr<nsIURI> uri3;
+ rv = mutator.Finalize(uri3);
+ ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE);
+ ASSERT_TRUE(uri3 == nullptr);
+
+ // Make sure changing scheme updates the default port
+ rv = NS_NewURI(getter_AddRefs(uri),
+ "https://example.org:80/path?query#ref"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ rv = NS_MutateURI(uri).SetScheme("http"_ns).Finalize(uri);
+ ASSERT_EQ(rv, NS_OK);
+ rv = uri->GetSpec(out);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(out, "http://example.org/path?query#ref"_ns);
+ int32_t port;
+ rv = uri->GetPort(&port);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(port, -1);
+ rv = uri->GetFilePath(out);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(out, "/path"_ns);
+ rv = uri->GetQuery(out);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(out, "query"_ns);
+ rv = uri->GetRef(out);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(out, "ref"_ns);
+
+ // Make sure changing scheme does not change non-default port
+ rv = NS_NewURI(getter_AddRefs(uri), "https://example.org:123"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ rv = NS_MutateURI(uri).SetScheme("http"_ns).Finalize(uri);
+ ASSERT_EQ(rv, NS_OK);
+ rv = uri->GetSpec(out);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(out, "http://example.org:123/"_ns);
+ rv = uri->GetPort(&port);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(port, 123);
+}
+
+extern MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount;
+
+TEST(TestURIMutator, OnAnyThread)
+{
+ nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
+ pool->SetThreadLimit(60);
+
+ pool = new nsThreadPool();
+ for (int i = 0; i < 1000; ++i) {
+ nsCOMPtr<nsIRunnable> task =
+ NS_NewRunnableFunction("gtest-OnAnyThread", []() {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), "http://example.com"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ nsAutoCString out;
+ ASSERT_EQ(uri->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "http://example.com/"_ns);
+ });
+ EXPECT_TRUE(task);
+
+ pool->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), "http://example.com"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ nsAutoCString out;
+ ASSERT_EQ(uri->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == "http://example.com/"_ns);
+
+ pool->Shutdown();
+
+ ASSERT_EQ(gTlsURLRecursionCount.get(), 0u);
+}
diff --git a/netwerk/test/gtest/moz.build b/netwerk/test/gtest/moz.build
new file mode 100644
index 0000000000..b6d82b41db
--- /dev/null
+++ b/netwerk/test/gtest/moz.build
@@ -0,0 +1,80 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+UNIFIED_SOURCES += [
+ "TestBase64Stream.cpp",
+ "TestBind.cpp",
+ "TestBufferedInputStream.cpp",
+ "TestCommon.cpp",
+ "TestCookie.cpp",
+ "TestDNSPacket.cpp",
+ "TestHeaders.cpp",
+ "TestHttpAtom.cpp",
+ "TestHttpAuthUtils.cpp",
+ "TestHttpChannel.cpp",
+ "TestHttpResponseHead.cpp",
+ "TestInputStreamTransport.cpp",
+ "TestIsValidIp.cpp",
+ "TestLinkHeader.cpp",
+ "TestMIMEInputStream.cpp",
+ "TestMozURL.cpp",
+ "TestProtocolProxyService.cpp",
+ "TestReadStreamToString.cpp",
+ "TestServerTimingHeader.cpp",
+ "TestSocketTransportService.cpp",
+ "TestSSLTokensCache.cpp",
+ "TestStandardURL.cpp",
+ "TestUDPSocket.cpp",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "TestNamedPipeService.cpp",
+ ]
+
+# skip the test on windows10-aarch64
+if not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["TARGET_CPU"] == "aarch64"):
+ UNIFIED_SOURCES += [
+ "TestPACMan.cpp",
+ "TestURIMutator.cpp",
+ ]
+
+# run the test on windows only
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += ["TestNetworkLinkIdHashingWindows.cpp"]
+
+# run the test on mac only
+if CONFIG["OS_TARGET"] == "Darwin":
+ UNIFIED_SOURCES += ["TestNetworkLinkIdHashingDarwin.cpp"]
+
+TEST_HARNESS_FILES.gtest += [
+ "urltestdata.json",
+]
+
+USE_LIBS += [
+ "jsoncpp",
+]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/cookie",
+ "/toolkit/components/jsoncpp/include",
+ "/xpcom/tests/gtest",
+]
+
+# windows includes only
+if CONFIG["OS_TARGET"] == "WINNT":
+ LOCAL_INCLUDES += ["/netwerk/system/win32"]
+
+# mac includes only
+if CONFIG["OS_TARGET"] == "Darwin":
+ LOCAL_INCLUDES += ["/netwerk/system/mac"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += ["!/xpcom", "/xpcom/components"]
diff --git a/netwerk/test/gtest/urltestdata-orig.json b/netwerk/test/gtest/urltestdata-orig.json
new file mode 100644
index 0000000000..5565c938fd
--- /dev/null
+++ b/netwerk/test/gtest/urltestdata-orig.json
@@ -0,0 +1,6148 @@
+[
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/segments.js",
+ {
+ "input": "http://example\t.\norg",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://user:pass@foo:21/bar;par?b#c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://user:pass@foo:21/bar;par?b#c",
+ "origin": "http://foo:21",
+ "protocol": "http:",
+ "username": "user",
+ "password": "pass",
+ "host": "foo:21",
+ "hostname": "foo",
+ "port": "21",
+ "pathname": "/bar;par",
+ "search": "?b",
+ "hash": "#c"
+ },
+ {
+ "input": "https://test:@test",
+ "base": "about:blank",
+ "href": "https://test@test/",
+ "origin": "https://test",
+ "protocol": "https:",
+ "username": "test",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://:@test",
+ "base": "about:blank",
+ "href": "https://test/",
+ "origin": "https://test",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://test:@test/x",
+ "base": "about:blank",
+ "href": "non-special://test@test/x",
+ "origin": "null",
+ "protocol": "non-special:",
+ "username": "test",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/x",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://:@test/x",
+ "base": "about:blank",
+ "href": "non-special://test/x",
+ "origin": "null",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/x",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:foo.com",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/foo.com",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\t :foo.com \n",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:foo.com",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": " foo.com ",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/foo.com",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "a:\t foo.com",
+ "base": "http://example.org/foo/bar",
+ "href": "a: foo.com",
+ "origin": "null",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": " foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:21/ b ? d # e ",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f:21/%20b%20?%20d%20# e",
+ "origin": "http://f:21",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f:21",
+ "hostname": "f",
+ "port": "21",
+ "pathname": "/%20b%20",
+ "search": "?%20d%20",
+ "hash": "# e"
+ },
+ {
+ "input": "lolscheme:x x#x x",
+ "base": "about:blank",
+ "href": "lolscheme:x x#x x",
+ "protocol": "lolscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "x x",
+ "search": "",
+ "hash": "#x x"
+ },
+ {
+ "input": "http://f:/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f/c",
+ "origin": "http://f",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f",
+ "hostname": "f",
+ "port": "",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:0/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f:0/c",
+ "origin": "http://f:0",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f:0",
+ "hostname": "f",
+ "port": "0",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:00000000000000/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f:0/c",
+ "origin": "http://f:0",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f:0",
+ "hostname": "f",
+ "port": "0",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:00000000000000000000080/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f/c",
+ "origin": "http://f",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f",
+ "hostname": "f",
+ "port": "",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:b/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f: /c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f:\n/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f/c",
+ "origin": "http://f",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f",
+ "hostname": "f",
+ "port": "",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:fifty-two/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f:999999/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "non-special://f:999999/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f: 21 / b ? d # e ",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": " \t",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":foo.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:foo.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:foo.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":foo.com\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:foo.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:foo.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":a",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:a",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:a",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":#",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:#",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#/"
+ },
+ {
+ "input": "#\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#\\",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#\\"
+ },
+ {
+ "input": "#;?",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#;?",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#;?"
+ },
+ {
+ "input": "?",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar?",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":23",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:23",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:23",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/:23",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/:23",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/:23",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "::",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/::",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/::",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "::23",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/::23",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/::23",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo://",
+ "base": "http://example.org/foo/bar",
+ "href": "foo:///",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://a:b@c:29/d",
+ "base": "http://example.org/foo/bar",
+ "href": "http://a:b@c:29/d",
+ "origin": "http://c:29",
+ "protocol": "http:",
+ "username": "a",
+ "password": "b",
+ "host": "c:29",
+ "hostname": "c",
+ "port": "29",
+ "pathname": "/d",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http::@c:29",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:@c:29",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:@c:29",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://&a:foo(b]c@d:2/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://&a:foo(b%5Dc@d:2/",
+ "origin": "http://d:2",
+ "protocol": "http:",
+ "username": "&a",
+ "password": "foo(b%5Dc",
+ "host": "d:2",
+ "hostname": "d",
+ "port": "2",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://::@c@d:2",
+ "base": "http://example.org/foo/bar",
+ "href": "http://:%3A%40c@d:2/",
+ "origin": "http://d:2",
+ "protocol": "http:",
+ "username": "",
+ "password": "%3A%40c",
+ "host": "d:2",
+ "hostname": "d",
+ "port": "2",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo.com:b@d/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo.com:b@d/",
+ "origin": "http://d",
+ "protocol": "http:",
+ "username": "foo.com",
+ "password": "b",
+ "host": "d",
+ "hostname": "d",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo.com/\\@",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo.com//@",
+ "origin": "http://foo.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.com",
+ "hostname": "foo.com",
+ "port": "",
+ "pathname": "//@",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:\\\\foo.com\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo.com/",
+ "origin": "http://foo.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.com",
+ "hostname": "foo.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:\\\\a\\b:c\\d@foo.com\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://a/b:c/d@foo.com/",
+ "origin": "http://a",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "a",
+ "hostname": "a",
+ "port": "",
+ "pathname": "/b:c/d@foo.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo:/",
+ "base": "http://example.org/foo/bar",
+ "href": "foo:/",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo:/bar.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "foo:/bar.com/",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/bar.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo://///////",
+ "base": "http://example.org/foo/bar",
+ "href": "foo://///////",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "///////",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo://///////bar.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "foo://///////bar.com/",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "///////bar.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo:////://///",
+ "base": "http://example.org/foo/bar",
+ "href": "foo:////://///",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//://///",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "c:/foo",
+ "base": "http://example.org/foo/bar",
+ "href": "c:/foo",
+ "origin": "null",
+ "protocol": "c:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//foo/bar",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/bar",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo/path;a??e#f#g",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/path;a??e#f#g",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/path;a",
+ "search": "??e",
+ "hash": "#f#g"
+ },
+ {
+ "input": "http://foo/abcd?efgh?ijkl",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/abcd?efgh?ijkl",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/abcd",
+ "search": "?efgh?ijkl",
+ "hash": ""
+ },
+ {
+ "input": "http://foo/abcd#foo?bar",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/abcd#foo?bar",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/abcd",
+ "search": "",
+ "hash": "#foo?bar"
+ },
+ {
+ "input": "[61:24:74]:98",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/[61:24:74]:98",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/[61:24:74]:98",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:[61:27]/:foo",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/[61:27]/:foo",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/[61:27]/:foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[1::2]:3:4",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://2001::1",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://2001::1]",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://2001::1]:80",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://[2001::1]",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[2001::1]/",
+ "origin": "http://[2001::1]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[2001::1]",
+ "hostname": "[2001::1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[::127.0.0.1]",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[::7f00:1]/",
+ "origin": "http://[::7f00:1]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[::7f00:1]",
+ "hostname": "[::7f00:1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[0:0:0:0:0:0:13.1.68.3]",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[::d01:4403]/",
+ "origin": "http://[::d01:4403]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[::d01:4403]",
+ "hostname": "[::d01:4403]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[2001::1]:80",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[2001::1]/",
+ "origin": "http://[2001::1]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[2001::1]",
+ "hostname": "[2001::1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/example.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "madeupscheme:/example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "file:///example.com/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://example:1/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "file://example:test/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "file://example%/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "file://[example]/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "ftps:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftps:/example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "gopher://example.com/",
+ "origin": "gopher://example.com",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "data:/example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "javascript:/example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "mailto:/example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/example.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "madeupscheme:example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftps:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftps:example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "gopher://example.com/",
+ "origin": "gopher://example.com",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "data:example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "javascript:example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "mailto:example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a/b/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a/b/c",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a/b/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a/ /c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a/%20/c",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a/%20/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a%2fc",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a%2fc",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a%2fc",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a/%2f/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a/%2f/c",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a/%2f/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#β",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#%CE%B2",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#%CE%B2"
+ },
+ {
+ "input": "data:text/html,test#test",
+ "base": "http://example.org/foo/bar",
+ "href": "data:text/html,test#test",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "text/html,test",
+ "search": "",
+ "hash": "#test"
+ },
+ {
+ "input": "tel:1234567890",
+ "base": "http://example.org/foo/bar",
+ "href": "tel:1234567890",
+ "origin": "null",
+ "protocol": "tel:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "1234567890",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html",
+ {
+ "input": "file:c:\\foo\\bar.html",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///c:/foo/bar.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:/foo/bar.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": " File:c|////foo\\bar.html",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///c:////foo/bar.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:////foo/bar.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|/foo/bar",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///C:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/C|\\foo\\bar",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///C:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//C|/foo/bar",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///C:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//server/file",
+ "base": "file:///tmp/mock/path",
+ "href": "file://server/file",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "server",
+ "hostname": "server",
+ "port": "",
+ "pathname": "/file",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\\\server\\file",
+ "base": "file:///tmp/mock/path",
+ "href": "file://server/file",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "server",
+ "hostname": "server",
+ "port": "",
+ "pathname": "/file",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/\\server/file",
+ "base": "file:///tmp/mock/path",
+ "href": "file://server/file",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "server",
+ "hostname": "server",
+ "port": "",
+ "pathname": "/file",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///foo/bar.txt",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///foo/bar.txt",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/foo/bar.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///home/me",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///home/me",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/home/me",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "///",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "///test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://test",
+ "base": "file:///tmp/mock/path",
+ "href": "file://test/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost/",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost/test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///tmp/mock/test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/tmp/mock/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///tmp/mock/test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/tmp/mock/test",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js",
+ {
+ "input": "http://example.com/././foo",
+ "base": "about:blank",
+ "href": "http://example.com/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/./.foo",
+ "base": "about:blank",
+ "href": "http://example.com/.foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/.foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/.",
+ "base": "about:blank",
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/./",
+ "base": "about:blank",
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/..",
+ "base": "about:blank",
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/../",
+ "base": "about:blank",
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/..bar",
+ "base": "about:blank",
+ "href": "http://example.com/foo/..bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/..bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/../ton",
+ "base": "about:blank",
+ "href": "http://example.com/foo/ton",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/ton",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/../ton/../../a",
+ "base": "about:blank",
+ "href": "http://example.com/a",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/a",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/../../..",
+ "base": "about:blank",
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/../../../ton",
+ "base": "about:blank",
+ "href": "http://example.com/ton",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/ton",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/%2e",
+ "base": "about:blank",
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/%2e%2",
+ "base": "about:blank",
+ "href": "http://example.com/foo/%2e%2",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/%2e%2",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar",
+ "base": "about:blank",
+ "href": "http://example.com/%2e.bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%2e.bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com////../..",
+ "base": "about:blank",
+ "href": "http://example.com//",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar//../..",
+ "base": "about:blank",
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar//..",
+ "base": "about:blank",
+ "href": "http://example.com/foo/bar/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/bar/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo",
+ "base": "about:blank",
+ "href": "http://example.com/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/%20foo",
+ "base": "about:blank",
+ "href": "http://example.com/%20foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%20foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%",
+ "base": "about:blank",
+ "href": "http://example.com/foo%",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%2",
+ "base": "about:blank",
+ "href": "http://example.com/foo%2",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%2",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%2zbar",
+ "base": "about:blank",
+ "href": "http://example.com/foo%2zbar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%2zbar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%2©zbar",
+ "base": "about:blank",
+ "href": "http://example.com/foo%2%C3%82%C2%A9zbar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%2%C3%82%C2%A9zbar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%41%7a",
+ "base": "about:blank",
+ "href": "http://example.com/foo%41%7a",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%41%7a",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo\t\u0091%91",
+ "base": "about:blank",
+ "href": "http://example.com/foo%C2%91%91",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%C2%91%91",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%00%51",
+ "base": "about:blank",
+ "href": "http://example.com/foo%00%51",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%00%51",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/(%28:%3A%29)",
+ "base": "about:blank",
+ "href": "http://example.com/(%28:%3A%29)",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/(%28:%3A%29)",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/%3A%3a%3C%3c",
+ "base": "about:blank",
+ "href": "http://example.com/%3A%3a%3C%3c",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%3A%3a%3C%3c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo\tbar",
+ "base": "about:blank",
+ "href": "http://example.com/foobar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foobar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com\\\\foo\\\\bar",
+ "base": "about:blank",
+ "href": "http://example.com//foo//bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "//foo//bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd",
+ "base": "about:blank",
+ "href": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%7Ffp3%3Eju%3Dduvgw%3Dd",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/@asdf%40",
+ "base": "about:blank",
+ "href": "http://example.com/@asdf%40",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/@asdf%40",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/你好你好",
+ "base": "about:blank",
+ "href": "http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/‥/foo",
+ "base": "about:blank",
+ "href": "http://example.com/%E2%80%A5/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%E2%80%A5/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com//foo",
+ "base": "about:blank",
+ "href": "http://example.com/%EF%BB%BF/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%EF%BB%BF/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/‮/foo/‭/bar",
+ "base": "about:blank",
+ "href": "http://example.com/%E2%80%AE/foo/%E2%80%AD/bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%E2%80%AE/foo/%E2%80%AD/bar",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js",
+ {
+ "input": "http://www.google.com/foo?bar=baz#",
+ "base": "about:blank",
+ "href": "http://www.google.com/foo?bar=baz#",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "?bar=baz",
+ "hash": ""
+ },
+ {
+ "input": "http://www.google.com/foo?bar=baz# »",
+ "base": "about:blank",
+ "href": "http://www.google.com/foo?bar=baz# %C2%BB",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "?bar=baz",
+ "hash": "# %C2%BB"
+ },
+ {
+ "input": "data:test# »",
+ "base": "about:blank",
+ "href": "data:test# %C2%BB",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "test",
+ "search": "",
+ "hash": "# %C2%BB"
+ },
+ {
+ "input": "http://www.google.com",
+ "base": "about:blank",
+ "href": "http://www.google.com/",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.0x00A80001",
+ "base": "about:blank",
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www/foo%2Ehtml",
+ "base": "about:blank",
+ "href": "http://www/foo%2Ehtml",
+ "origin": "http://www",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www",
+ "hostname": "www",
+ "port": "",
+ "pathname": "/foo%2Ehtml",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www/foo/%2E/html",
+ "base": "about:blank",
+ "href": "http://www/foo/html",
+ "origin": "http://www",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www",
+ "hostname": "www",
+ "port": "",
+ "pathname": "/foo/html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://user:pass@/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://%25DOMAIN:foobar@foodomain.com/",
+ "base": "about:blank",
+ "href": "http://%25DOMAIN:foobar@foodomain.com/",
+ "origin": "http://foodomain.com",
+ "protocol": "http:",
+ "username": "%25DOMAIN",
+ "password": "foobar",
+ "host": "foodomain.com",
+ "hostname": "foodomain.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:\\\\www.google.com\\foo",
+ "base": "about:blank",
+ "href": "http://www.google.com/foo",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo:80/",
+ "base": "about:blank",
+ "href": "http://foo/",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo:81/",
+ "base": "about:blank",
+ "href": "http://foo:81/",
+ "origin": "http://foo:81",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo:81",
+ "hostname": "foo",
+ "port": "81",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "httpa://foo:80/",
+ "base": "about:blank",
+ "href": "httpa://foo:80/",
+ "origin": "null",
+ "protocol": "httpa:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo:-80/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://foo:443/",
+ "base": "about:blank",
+ "href": "https://foo/",
+ "origin": "https://foo",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://foo:80/",
+ "base": "about:blank",
+ "href": "https://foo:80/",
+ "origin": "https://foo:80",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp://foo:21/",
+ "base": "about:blank",
+ "href": "ftp://foo/",
+ "origin": "ftp://foo",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp://foo:80/",
+ "base": "about:blank",
+ "href": "ftp://foo:80/",
+ "origin": "ftp://foo:80",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher://foo:70/",
+ "base": "about:blank",
+ "href": "gopher://foo/",
+ "origin": "gopher://foo",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher://foo:443/",
+ "base": "about:blank",
+ "href": "gopher://foo:443/",
+ "origin": "gopher://foo:443",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "foo:443",
+ "hostname": "foo",
+ "port": "443",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:80/",
+ "base": "about:blank",
+ "href": "ws://foo/",
+ "origin": "ws://foo",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:81/",
+ "base": "about:blank",
+ "href": "ws://foo:81/",
+ "origin": "ws://foo:81",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo:81",
+ "hostname": "foo",
+ "port": "81",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:443/",
+ "base": "about:blank",
+ "href": "ws://foo:443/",
+ "origin": "ws://foo:443",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo:443",
+ "hostname": "foo",
+ "port": "443",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:815/",
+ "base": "about:blank",
+ "href": "ws://foo:815/",
+ "origin": "ws://foo:815",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo:815",
+ "hostname": "foo",
+ "port": "815",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:80/",
+ "base": "about:blank",
+ "href": "wss://foo:80/",
+ "origin": "wss://foo:80",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:81/",
+ "base": "about:blank",
+ "href": "wss://foo:81/",
+ "origin": "wss://foo:81",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo:81",
+ "hostname": "foo",
+ "port": "81",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:443/",
+ "base": "about:blank",
+ "href": "wss://foo/",
+ "origin": "wss://foo",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:815/",
+ "base": "about:blank",
+ "href": "wss://foo:815/",
+ "origin": "wss://foo:815",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo:815",
+ "hostname": "foo",
+ "port": "815",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/example.com/",
+ "base": "about:blank",
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:/example.com/",
+ "base": "about:blank",
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:/example.com/",
+ "base": "about:blank",
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:/example.com/",
+ "base": "about:blank",
+ "href": "madeupscheme:/example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:/example.com/",
+ "base": "about:blank",
+ "href": "file:///example.com/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftps:/example.com/",
+ "base": "about:blank",
+ "href": "ftps:/example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:/example.com/",
+ "base": "about:blank",
+ "href": "gopher://example.com/",
+ "origin": "gopher://example.com",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:/example.com/",
+ "base": "about:blank",
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:/example.com/",
+ "base": "about:blank",
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:/example.com/",
+ "base": "about:blank",
+ "href": "data:/example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:/example.com/",
+ "base": "about:blank",
+ "href": "javascript:/example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:/example.com/",
+ "base": "about:blank",
+ "href": "mailto:/example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:example.com/",
+ "base": "about:blank",
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:example.com/",
+ "base": "about:blank",
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:example.com/",
+ "base": "about:blank",
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:example.com/",
+ "base": "about:blank",
+ "href": "madeupscheme:example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftps:example.com/",
+ "base": "about:blank",
+ "href": "ftps:example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:example.com/",
+ "base": "about:blank",
+ "href": "gopher://example.com/",
+ "origin": "gopher://example.com",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:example.com/",
+ "base": "about:blank",
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:example.com/",
+ "base": "about:blank",
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:example.com/",
+ "base": "about:blank",
+ "href": "data:example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:example.com/",
+ "base": "about:blank",
+ "href": "javascript:example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:example.com/",
+ "base": "about:blank",
+ "href": "mailto:example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html",
+ {
+ "input": "http:@www.example.com",
+ "base": "about:blank",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/@www.example.com",
+ "base": "about:blank",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://@www.example.com",
+ "base": "about:blank",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:a:b@www.example.com",
+ "base": "about:blank",
+ "href": "http://a:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/a:b@www.example.com",
+ "base": "about:blank",
+ "href": "http://a:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://a:b@www.example.com",
+ "base": "about:blank",
+ "href": "http://a:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://@pple.com",
+ "base": "about:blank",
+ "href": "http://pple.com/",
+ "origin": "http://pple.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "pple.com",
+ "hostname": "pple.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http::b@www.example.com",
+ "base": "about:blank",
+ "href": "http://:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/:b@www.example.com",
+ "base": "about:blank",
+ "href": "http://:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://:b@www.example.com",
+ "base": "about:blank",
+ "href": "http://:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/:@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://user@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http:@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http:/@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https:@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http:a:b@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http:/a:b@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://a:b@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http::@/www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http:a:@www.example.com",
+ "base": "about:blank",
+ "href": "http://a@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/a:@www.example.com",
+ "base": "about:blank",
+ "href": "http://a@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://a:@www.example.com",
+ "base": "about:blank",
+ "href": "http://a@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www.@pple.com",
+ "base": "about:blank",
+ "href": "http://www.@pple.com/",
+ "origin": "http://pple.com",
+ "protocol": "http:",
+ "username": "www.",
+ "password": "",
+ "host": "pple.com",
+ "hostname": "pple.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:@:www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http:/@:www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://@:www.example.com",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://:@www.example.com",
+ "base": "about:blank",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# Others",
+ {
+ "input": "/",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ".",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "./test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../aaa/test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/aaa/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/aaa/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../../test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "中/test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/%E4%B8%AD/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/%E4%B8%AD/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www.example2.com",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example2.com/",
+ "origin": "http://www.example2.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example2.com",
+ "hostname": "www.example2.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//www.example2.com",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example2.com/",
+ "origin": "http://www.example2.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example2.com",
+ "hostname": "www.example2.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:...",
+ "base": "http://www.example.com/test",
+ "href": "file:///...",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/...",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:..",
+ "base": "http://www.example.com/test",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:a",
+ "base": "http://www.example.com/test",
+ "href": "file:///a",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/a",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html",
+ "Basic canonicalization, uppercase should be converted to lowercase",
+ {
+ "input": "http://ExAmPlE.CoM",
+ "base": "http://other.com/",
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example example.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://Goo%20 goo%7C|.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[:]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "U+3000 is mapped to U+0020 (space) which is disallowed",
+ {
+ "input": "http://GOO\u00a0\u3000goo.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored",
+ {
+ "input": "http://GOO\u200b\u2060\ufeffgoo.com",
+ "base": "http://other.com/",
+ "href": "http://googoo.com/",
+ "origin": "http://googoo.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "googoo.com",
+ "hostname": "googoo.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Leading and trailing C0 control or space",
+ {
+ "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ",
+ "base": "about:blank",
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)",
+ {
+ "input": "http://www.foo。bar.com",
+ "base": "http://other.com/",
+ "href": "http://www.foo.bar.com/",
+ "origin": "http://www.foo.bar.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.foo.bar.com",
+ "hostname": "www.foo.bar.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0",
+ {
+ "input": "http://\ufdd0zyx.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "This is the same as previous but escaped",
+ {
+ "input": "http://%ef%b7%90zyx.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "U+FFFD",
+ {
+ "input": "https://\ufffd",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://%EF%BF%BD",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://x/\ufffd?\ufffd#\ufffd",
+ "base": "about:blank",
+ "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD",
+ "origin": "https://x",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "x",
+ "hostname": "x",
+ "port": "",
+ "pathname": "/%EF%BF%BD",
+ "search": "?%EF%BF%BD",
+ "hash": "#%EF%BF%BD"
+ },
+ "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.",
+ {
+ "input": "http://Go.com",
+ "base": "http://other.com/",
+ "href": "http://go.com/",
+ "origin": "http://go.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "go.com",
+ "hostname": "go.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257",
+ {
+ "input": "http://%41.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "...%00 in fullwidth should fail (also as escaped UTF-8 input)",
+ {
+ "input": "http://%00.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN",
+ {
+ "input": "http://你好你好",
+ "base": "http://other.com/",
+ "href": "http://xn--6qqa088eba/",
+ "origin": "http://xn--6qqa088eba",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "xn--6qqa088eba",
+ "hostname": "xn--6qqa088eba",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://faß.ExAmPlE/",
+ "base": "about:blank",
+ "href": "https://xn--fa-hia.example/",
+ "origin": "https://xn--fa-hia.example",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "xn--fa-hia.example",
+ "hostname": "xn--fa-hia.example",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://faß.ExAmPlE/",
+ "base": "about:blank",
+ "href": "sc://fa%C3%9F.ExAmPlE/",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "fa%C3%9F.ExAmPlE",
+ "hostname": "fa%C3%9F.ExAmPlE",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191",
+ {
+ "input": "http://%zz%66%a.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "If we get an invalid character that has been escaped.",
+ {
+ "input": "http://%25",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://hello%00",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Escaped numbers should be treated like IP addresses if they are.",
+ {
+ "input": "http://%30%78%63%30%2e%30%32%35%30.01",
+ "base": "http://other.com/",
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e",
+ "base": "http://other.com/",
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.168.0.257",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Invalid escaping in hosts causes failure",
+ {
+ "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "A space in a host causes failure",
+ {
+ "input": "http://192.168.0.1 hello",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "https://x x:12",
+ "base": "about:blank",
+ "failure": true
+ },
+ "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP",
+ {
+ "input": "http://0Xc0.0250.01",
+ "base": "http://other.com/",
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Domains with empty labels",
+ {
+ "input": "http://./",
+ "base": "about:blank",
+ "href": "http://./",
+ "origin": "http://.",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": ".",
+ "hostname": ".",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://../",
+ "base": "about:blank",
+ "href": "http://../",
+ "origin": "http://..",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "..",
+ "hostname": "..",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://0..0x300/",
+ "base": "about:blank",
+ "href": "http://0..0x300/",
+ "origin": "http://0..0x300",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0..0x300",
+ "hostname": "0..0x300",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Broken IPv6",
+ {
+ "input": "http://[www.google.com]/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://[google.com]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.2.3.4x]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.2.3.]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.2.]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Misc Unicode",
+ {
+ "input": "http://foo:💩@example.com/bar",
+ "base": "http://other.com/",
+ "href": "http://foo:%F0%9F%92%A9@example.com/bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "foo",
+ "password": "%F0%9F%92%A9",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/bar",
+ "search": "",
+ "hash": ""
+ },
+ "# resolving a fragment against any scheme succeeds",
+ {
+ "input": "#",
+ "base": "test:test",
+ "href": "test:test#",
+ "origin": "null",
+ "protocol": "test:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#x",
+ "base": "mailto:x@x.com",
+ "href": "mailto:x@x.com#x",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "x@x.com",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "#x",
+ "base": "data:,",
+ "href": "data:,#x",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": ",",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "#x",
+ "base": "about:blank",
+ "href": "about:blank#x",
+ "origin": "null",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "#",
+ "base": "test:test?test",
+ "href": "test:test?test#",
+ "origin": "null",
+ "protocol": "test:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "test",
+ "search": "?test",
+ "hash": ""
+ },
+ "# multiple @ in authority state",
+ {
+ "input": "https://@test@test@example:800/",
+ "base": "http://doesnotmatter/",
+ "href": "https://%40test%40test@example:800/",
+ "origin": "https://example:800",
+ "protocol": "https:",
+ "username": "%40test%40test",
+ "password": "",
+ "host": "example:800",
+ "hostname": "example",
+ "port": "800",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://@@@example",
+ "base": "http://doesnotmatter/",
+ "href": "https://%40%40@example/",
+ "origin": "https://example",
+ "protocol": "https:",
+ "username": "%40%40",
+ "password": "",
+ "host": "example",
+ "hostname": "example",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "non-az-09 characters",
+ {
+ "input": "http://`{}:`{}@h/`{}?`{}",
+ "base": "http://doesnotmatter/",
+ "href": "http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}",
+ "origin": "http://h",
+ "protocol": "http:",
+ "username": "%60%7B%7D",
+ "password": "%60%7B%7D",
+ "host": "h",
+ "hostname": "h",
+ "port": "",
+ "pathname": "/%60%7B%7D",
+ "search": "?`{}",
+ "hash": ""
+ },
+ "# Credentials in base",
+ {
+ "input": "/some/path",
+ "base": "http://user@example.org/smth",
+ "href": "http://user@example.org/some/path",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "user",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/some/path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "",
+ "base": "http://user:pass@example.org:21/smth",
+ "href": "http://user:pass@example.org:21/smth",
+ "origin": "http://example.org:21",
+ "protocol": "http:",
+ "username": "user",
+ "password": "pass",
+ "host": "example.org:21",
+ "hostname": "example.org",
+ "port": "21",
+ "pathname": "/smth",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/some/path",
+ "base": "http://user:pass@example.org:21/smth",
+ "href": "http://user:pass@example.org:21/some/path",
+ "origin": "http://example.org:21",
+ "protocol": "http:",
+ "username": "user",
+ "password": "pass",
+ "host": "example.org:21",
+ "hostname": "example.org",
+ "port": "21",
+ "pathname": "/some/path",
+ "search": "",
+ "hash": ""
+ },
+ "# a set of tests designed by zcorpan for relative URLs with unknown schemes",
+ {
+ "input": "i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/pa/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///pa/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "../i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "../i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "/i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "/i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "?i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "?i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "?i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/pa/pa?i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "?i",
+ "hash": ""
+ },
+ {
+ "input": "?i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/pa?i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/pa",
+ "search": "?i",
+ "hash": ""
+ },
+ {
+ "input": "?i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///pa/pa?i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "?i",
+ "hash": ""
+ },
+ {
+ "input": "#i",
+ "base": "sc:sd",
+ "href": "sc:sd#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "sd",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc:sd/sd",
+ "href": "sc:sd/sd#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "sd/sd",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/pa/pa#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/pa#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/pa",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///pa/pa#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "",
+ "hash": "#i"
+ },
+ "# make sure that relative URL logic works on known typically non-relative schemes too",
+ {
+ "input": "about:/../",
+ "base": "about:blank",
+ "href": "about:/",
+ "origin": "null",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:/../",
+ "base": "about:blank",
+ "href": "data:/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:/../",
+ "base": "about:blank",
+ "href": "javascript:/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:/../",
+ "base": "about:blank",
+ "href": "mailto:/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown schemes and their hosts",
+ {
+ "input": "sc://ñ.test/",
+ "base": "about:blank",
+ "href": "sc://%C3%B1.test/",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1.test",
+ "hostname": "%C3%B1.test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://\u001F!\"$&'()*+,-.;<=>^_`{|}~/",
+ "base": "about:blank",
+ "href": "sc://%1F!\"$&'()*+,-.;<=>^_`{|}~/",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%1F!\"$&'()*+,-.;<=>^_`{|}~",
+ "hostname": "%1F!\"$&'()*+,-.;<=>^_`{|}~",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://\u0000/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "sc:// /",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "sc://%/",
+ "base": "about:blank",
+ "href": "sc://%/",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%",
+ "hostname": "%",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://[/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "sc://\\/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "sc://]/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "x",
+ "base": "sc://ñ",
+ "href": "sc://%C3%B1/x",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1",
+ "hostname": "%C3%B1",
+ "port": "",
+ "pathname": "/x",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown schemes and backslashes",
+ {
+ "input": "sc:\\../",
+ "base": "about:blank",
+ "href": "sc:\\../",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "\\../",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown scheme with path looking like a password",
+ {
+ "input": "sc::a@example.net",
+ "base": "about:blank",
+ "href": "sc::a@example.net",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": ":a@example.net",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown scheme with bogus percent-encoding",
+ {
+ "input": "wow:%NBD",
+ "base": "about:blank",
+ "href": "wow:%NBD",
+ "origin": "null",
+ "protocol": "wow:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "%NBD",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wow:%1G",
+ "base": "about:blank",
+ "href": "wow:%1G",
+ "origin": "null",
+ "protocol": "wow:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "%1G",
+ "search": "",
+ "hash": ""
+ },
+ "# Hosts and percent-encoding",
+ {
+ "input": "ftp://example.com%80/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "ftp://example.com%A0/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://example.com%80/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://example.com%A0/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "ftp://%e2%98%83",
+ "base": "about:blank",
+ "href": "ftp://xn--n3h/",
+ "origin": "ftp://xn--n3h",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "xn--n3h",
+ "hostname": "xn--n3h",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://%e2%98%83",
+ "base": "about:blank",
+ "href": "https://xn--n3h/",
+ "origin": "https://xn--n3h",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "xn--n3h",
+ "hostname": "xn--n3h",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# tests from jsdom/whatwg-url designed for code coverage",
+ {
+ "input": "http://127.0.0.1:10100/relative_import.html",
+ "base": "about:blank",
+ "href": "http://127.0.0.1:10100/relative_import.html",
+ "origin": "http://127.0.0.1:10100",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "127.0.0.1:10100",
+ "hostname": "127.0.0.1",
+ "port": "10100",
+ "pathname": "/relative_import.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://facebook.com/?foo=%7B%22abc%22",
+ "base": "about:blank",
+ "href": "http://facebook.com/?foo=%7B%22abc%22",
+ "origin": "http://facebook.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "facebook.com",
+ "hostname": "facebook.com",
+ "port": "",
+ "pathname": "/",
+ "search": "?foo=%7B%22abc%22",
+ "hash": ""
+ },
+ {
+ "input": "https://localhost:3000/jqueryui@1.2.3",
+ "base": "about:blank",
+ "href": "https://localhost:3000/jqueryui@1.2.3",
+ "origin": "https://localhost:3000",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "localhost:3000",
+ "hostname": "localhost",
+ "port": "3000",
+ "pathname": "/jqueryui@1.2.3",
+ "search": "",
+ "hash": ""
+ },
+ "# tab/LF/CR",
+ {
+ "input": "h\tt\nt\rp://h\to\ns\rt:9\t0\n0\r0/p\ta\nt\rh?q\tu\ne\rry#f\tr\na\rg",
+ "base": "about:blank",
+ "href": "http://host:9000/path?query#frag",
+ "origin": "http://host:9000",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "host:9000",
+ "hostname": "host",
+ "port": "9000",
+ "pathname": "/path",
+ "search": "?query",
+ "hash": "#frag"
+ },
+ "# Stringification of URL.searchParams",
+ {
+ "input": "?a=b&c=d",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar?a=b&c=d",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "?a=b&c=d",
+ "searchParams": "a=b&c=d",
+ "hash": ""
+ },
+ {
+ "input": "??a=b&c=d",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar??a=b&c=d",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "??a=b&c=d",
+ "searchParams": "%3Fa=b&c=d",
+ "hash": ""
+ },
+ "# Scheme only",
+ {
+ "input": "http:",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "searchParams": "",
+ "hash": ""
+ },
+ {
+ "input": "http:",
+ "base": "https://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "sc:",
+ "base": "https://example.org/foo/bar",
+ "href": "sc:",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "searchParams": "",
+ "hash": ""
+ },
+ "# Percent encoding of fragments",
+ {
+ "input": "http://foo.bar/baz?qux#foo\bbar",
+ "base": "about:blank",
+ "href": "http://foo.bar/baz?qux#foo%08bar",
+ "origin": "http://foo.bar",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.bar",
+ "hostname": "foo.bar",
+ "port": "",
+ "pathname": "/baz",
+ "search": "?qux",
+ "searchParams": "qux=",
+ "hash": "#foo%08bar"
+ },
+ "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)",
+ {
+ "input": "http://192.168.257",
+ "base": "http://other.com/",
+ "href": "http://192.168.1.1/",
+ "origin": "http://192.168.1.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.1.1",
+ "hostname": "192.168.1.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.168.257.com",
+ "base": "http://other.com/",
+ "href": "http://192.168.257.com/",
+ "origin": "http://192.168.257.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.257.com",
+ "hostname": "192.168.257.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://256",
+ "base": "http://other.com/",
+ "href": "http://0.0.1.0/",
+ "origin": "http://0.0.1.0",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0.0.1.0",
+ "hostname": "0.0.1.0",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://256.com",
+ "base": "http://other.com/",
+ "href": "http://256.com/",
+ "origin": "http://256.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "256.com",
+ "hostname": "256.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://999999999",
+ "base": "http://other.com/",
+ "href": "http://59.154.201.255/",
+ "origin": "http://59.154.201.255",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "59.154.201.255",
+ "hostname": "59.154.201.255",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://999999999.com",
+ "base": "http://other.com/",
+ "href": "http://999999999.com/",
+ "origin": "http://999999999.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "999999999.com",
+ "hostname": "999999999.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://10000000000",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://10000000000.com",
+ "base": "http://other.com/",
+ "href": "http://10000000000.com/",
+ "origin": "http://10000000000.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "10000000000.com",
+ "hostname": "10000000000.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://4294967295",
+ "base": "http://other.com/",
+ "href": "http://255.255.255.255/",
+ "origin": "http://255.255.255.255",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "255.255.255.255",
+ "hostname": "255.255.255.255",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://4294967296",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://0xffffffff",
+ "base": "http://other.com/",
+ "href": "http://255.255.255.255/",
+ "origin": "http://255.255.255.255",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "255.255.255.255",
+ "hostname": "255.255.255.255",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://0xffffffff1",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://256.256.256.256",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://256.256.256.256.256",
+ "base": "http://other.com/",
+ "href": "http://256.256.256.256.256/",
+ "origin": "http://256.256.256.256.256",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "256.256.256.256.256",
+ "hostname": "256.256.256.256.256",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://0x.0x.0",
+ "base": "about:blank",
+ "href": "https://0.0.0.0/",
+ "origin": "https://0.0.0.0",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "0.0.0.0",
+ "hostname": "0.0.0.0",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)",
+ {
+ "input": "https://256.0.0.1/test",
+ "base": "about:blank",
+ "failure": true
+ },
+ "# file URLs containing percent-encoded Windows drive letters (shouldn't work)",
+ {
+ "input": "file:///C%3A/",
+ "base": "about:blank",
+ "href": "file:///C%3A/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C%3A/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///C%7C/",
+ "base": "about:blank",
+ "href": "file:///C%7C/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C%7C/",
+ "search": "",
+ "hash": ""
+ },
+ "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)",
+ {
+ "input": "pix/submit.gif",
+ "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html",
+ "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///C:/",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# More file URL tests by zcorpan and annevk",
+ {
+ "input": "/",
+ "base": "file:///C:/a/b",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//d:",
+ "base": "file:///C:/a/b",
+ "href": "file:///d:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/d:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//d:/..",
+ "base": "file:///C:/a/b",
+ "href": "file:///d:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/d:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///ab:/",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///1:/",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": ""
+ },
+ {
+ "input": "file:",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": ""
+ },
+ {
+ "input": "?x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?x",
+ "hash": ""
+ },
+ {
+ "input": "file:?x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?x",
+ "hash": ""
+ },
+ {
+ "input": "#x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test#x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": "#x"
+ },
+ {
+ "input": "file:#x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test#x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": "#x"
+ },
+ "# File URLs and many (back)slashes",
+ {
+ "input": "file:///localhost//cat",
+ "base": "about:blank",
+ "href": "file:///localhost//cat",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/localhost//cat",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\//pig",
+ "base": "file://lion/",
+ "href": "file:///pig",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pig",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://",
+ "base": "file://ape/",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# Windows drive letter handling with the 'file:' base URL",
+ {
+ "input": "C|#",
+ "base": "file://host/dir/file",
+ "href": "file:///C:#",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|?",
+ "base": "file://host/dir/file",
+ "href": "file:///C:?",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|/",
+ "base": "file://host/dir/file",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|\n/",
+ "base": "file://host/dir/file",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|\\",
+ "base": "file://host/dir/file",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C",
+ "base": "file://host/dir/file",
+ "href": "file://host/dir/C",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/dir/C",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|a",
+ "base": "file://host/dir/file",
+ "href": "file://host/dir/C|a",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/dir/C|a",
+ "search": "",
+ "hash": ""
+ },
+ "# Windows drive letter quirk in the file slash state",
+ {
+ "input": "/c:/foo/bar",
+ "base": "file://host/path",
+ "href": "file:///c:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ "# Windows drive letter quirk (no host)",
+ {
+ "input": "file:/C|/",
+ "base": "about:blank",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://C|/",
+ "base": "about:blank",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ "# Windows drive letter quirk with not empty host",
+ {
+ "input": "file://example.net/C:/",
+ "base": "about:blank",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://1.2.3.4/C:/",
+ "base": "about:blank",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://[1::8]/C:/",
+ "base": "about:blank",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ "# file URLs without base URL by Rimas Misevičius",
+ {
+ "input": "file:",
+ "base": "about:blank",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:?q=v",
+ "base": "about:blank",
+ "href": "file:///?q=v",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "?q=v",
+ "hash": ""
+ },
+ {
+ "input": "file:#frag",
+ "base": "about:blank",
+ "href": "file:///#frag",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": "#frag"
+ },
+ "# IPv6 tests",
+ {
+ "input": "http://[1:0::]",
+ "base": "http://example.net/",
+ "href": "http://[1::]/",
+ "origin": "http://[1::]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[1::]",
+ "hostname": "[1::]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[0:1:2:3:4:5:6:7:8]",
+ "base": "http://example.net/",
+ "failure": true
+ },
+ {
+ "input": "https://[0::0::0]",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://[0:.0]",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://[0:0:]",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://[0:1.00.0.0.0]",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://[0:1.290.0.0.0]",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "https://[0:1.23.23]",
+ "base": "about:blank",
+ "failure": true
+ },
+ "# Empty host",
+ {
+ "input": "http://?",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "http://#",
+ "base": "about:blank",
+ "failure": true
+ },
+ "Port overflow (2^32 + 81)",
+ {
+ "input": "http://f:4294967377/c",
+ "base": "http://example.org/",
+ "failure": true
+ },
+ "Port overflow (2^64 + 81)",
+ {
+ "input": "http://f:18446744073709551697/c",
+ "base": "http://example.org/",
+ "failure": true
+ },
+ "Port overflow (2^128 + 81)",
+ {
+ "input": "http://f:340282366920938463463374607431768211537/c",
+ "base": "http://example.org/",
+ "failure": true
+ },
+ "# Non-special-URL path tests",
+ {
+ "input": "///",
+ "base": "sc://x/",
+ "href": "sc:///",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "tftp://foobar.com/someconfig;mode=netascii",
+ "base": "about:blank",
+ "href": "tftp://foobar.com/someconfig;mode=netascii",
+ "origin": "null",
+ "protocol": "tftp:",
+ "username": "",
+ "password": "",
+ "host": "foobar.com",
+ "hostname": "foobar.com",
+ "port": "",
+ "pathname": "/someconfig;mode=netascii",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "telnet://user:pass@foobar.com:23/",
+ "base": "about:blank",
+ "href": "telnet://user:pass@foobar.com:23/",
+ "origin": "null",
+ "protocol": "telnet:",
+ "username": "user",
+ "password": "pass",
+ "host": "foobar.com:23",
+ "hostname": "foobar.com",
+ "port": "23",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ut2004://10.10.10.10:7777/Index.ut2",
+ "base": "about:blank",
+ "href": "ut2004://10.10.10.10:7777/Index.ut2",
+ "origin": "null",
+ "protocol": "ut2004:",
+ "username": "",
+ "password": "",
+ "host": "10.10.10.10:7777",
+ "hostname": "10.10.10.10",
+ "port": "7777",
+ "pathname": "/Index.ut2",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+ "base": "about:blank",
+ "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+ "origin": "null",
+ "protocol": "redis:",
+ "username": "foo",
+ "password": "bar",
+ "host": "somehost:6379",
+ "hostname": "somehost",
+ "port": "6379",
+ "pathname": "/0",
+ "search": "?baz=bam&qux=baz",
+ "hash": ""
+ },
+ {
+ "input": "rsync://foo@host:911/sup",
+ "base": "about:blank",
+ "href": "rsync://foo@host:911/sup",
+ "origin": "null",
+ "protocol": "rsync:",
+ "username": "foo",
+ "password": "",
+ "host": "host:911",
+ "hostname": "host",
+ "port": "911",
+ "pathname": "/sup",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "git://github.com/foo/bar.git",
+ "base": "about:blank",
+ "href": "git://github.com/foo/bar.git",
+ "origin": "null",
+ "protocol": "git:",
+ "username": "",
+ "password": "",
+ "host": "github.com",
+ "hostname": "github.com",
+ "port": "",
+ "pathname": "/foo/bar.git",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "irc://myserver.com:6999/channel?passwd",
+ "base": "about:blank",
+ "href": "irc://myserver.com:6999/channel?passwd",
+ "origin": "null",
+ "protocol": "irc:",
+ "username": "",
+ "password": "",
+ "host": "myserver.com:6999",
+ "hostname": "myserver.com",
+ "port": "6999",
+ "pathname": "/channel",
+ "search": "?passwd",
+ "hash": ""
+ },
+ {
+ "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+ "base": "about:blank",
+ "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+ "origin": "null",
+ "protocol": "dns:",
+ "username": "",
+ "password": "",
+ "host": "fw.example.org:9999",
+ "hostname": "fw.example.org",
+ "port": "9999",
+ "pathname": "/foo.bar.org",
+ "search": "?type=TXT",
+ "hash": ""
+ },
+ {
+ "input": "ldap://localhost:389/ou=People,o=JNDITutorial",
+ "base": "about:blank",
+ "href": "ldap://localhost:389/ou=People,o=JNDITutorial",
+ "origin": "null",
+ "protocol": "ldap:",
+ "username": "",
+ "password": "",
+ "host": "localhost:389",
+ "hostname": "localhost",
+ "port": "389",
+ "pathname": "/ou=People,o=JNDITutorial",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "git+https://github.com/foo/bar",
+ "base": "about:blank",
+ "href": "git+https://github.com/foo/bar",
+ "origin": "null",
+ "protocol": "git+https:",
+ "username": "",
+ "password": "",
+ "host": "github.com",
+ "hostname": "github.com",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "urn:ietf:rfc:2648",
+ "base": "about:blank",
+ "href": "urn:ietf:rfc:2648",
+ "origin": "null",
+ "protocol": "urn:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "ietf:rfc:2648",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "tag:joe@example.org,2001:foo/bar",
+ "base": "about:blank",
+ "href": "tag:joe@example.org,2001:foo/bar",
+ "origin": "null",
+ "protocol": "tag:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "joe@example.org,2001:foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ "# percent encoded hosts in non-special-URLs",
+ {
+ "input": "non-special://%E2%80%A0/",
+ "base": "about:blank",
+ "href": "non-special://%E2%80%A0/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "%E2%80%A0",
+ "hostname": "%E2%80%A0",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://H%4fSt/path",
+ "base": "about:blank",
+ "href": "non-special://H%4fSt/path",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "H%4fSt",
+ "hostname": "H%4fSt",
+ "port": "",
+ "pathname": "/path",
+ "search": "",
+ "hash": ""
+ },
+ "# IPv6 in non-special-URLs",
+ {
+ "input": "non-special://[1:2:0:0:5:0:0:0]/",
+ "base": "about:blank",
+ "href": "non-special://[1:2:0:0:5::]/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "[1:2:0:0:5::]",
+ "hostname": "[1:2:0:0:5::]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://[1:2:0:0:0:0:0:3]/",
+ "base": "about:blank",
+ "href": "non-special://[1:2::3]/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "[1:2::3]",
+ "hostname": "[1:2::3]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://[1:2::3]:80/",
+ "base": "about:blank",
+ "href": "non-special://[1:2::3]:80/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "[1:2::3]:80",
+ "hostname": "[1:2::3]",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://[:80/",
+ "base": "about:blank",
+ "failure": true
+ },
+ {
+ "input": "blob:https://example.com:443/",
+ "base": "about:blank",
+ "href": "blob:https://example.com:443/",
+ "protocol": "blob:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "https://example.com:443/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+ "base": "about:blank",
+ "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+ "protocol": "blob:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid IPv4 radix digits",
+ {
+ "input": "http://0177.0.0.0189",
+ "base": "about:blank",
+ "href": "http://0177.0.0.0189/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0177.0.0.0189",
+ "hostname": "0177.0.0.0189",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://0x7f.0.0.0x7g",
+ "base": "about:blank",
+ "href": "http://0x7f.0.0.0x7g/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0x7f.0.0.0x7g",
+ "hostname": "0x7f.0.0.0x7g",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://0X7F.0.0.0X7G",
+ "base": "about:blank",
+ "href": "http://0x7f.0.0.0x7g/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0x7f.0.0.0x7g",
+ "hostname": "0x7f.0.0.0x7g",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid IPv4 portion of IPv6 address",
+ {
+ "input": "http://[::127.0.0.0.1]",
+ "base": "about:blank",
+ "failure": true
+ },
+ "Uncompressed IPv6 addresses with 0",
+ {
+ "input": "http://[0:1:0:1:0:1:0:1]",
+ "base": "about:blank",
+ "href": "http://[0:1:0:1:0:1:0:1]/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[0:1:0:1:0:1:0:1]",
+ "hostname": "[0:1:0:1:0:1:0:1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[1:0:1:0:1:0:1:0]",
+ "base": "about:blank",
+ "href": "http://[1:0:1:0:1:0:1:0]/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[1:0:1:0:1:0:1:0]",
+ "hostname": "[1:0:1:0:1:0:1:0]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Percent-encoded query and fragment",
+ {
+ "input": "http://example.org/test?\u0022",
+ "base": "about:blank",
+ "href": "http://example.org/test?%22",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%22",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u0023",
+ "base": "about:blank",
+ "href": "http://example.org/test?#",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u003C",
+ "base": "about:blank",
+ "href": "http://example.org/test?%3C",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%3C",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u003E",
+ "base": "about:blank",
+ "href": "http://example.org/test?%3E",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%3E",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u2323",
+ "base": "about:blank",
+ "href": "http://example.org/test?%E2%8C%A3",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%E2%8C%A3",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?%23%23",
+ "base": "about:blank",
+ "href": "http://example.org/test?%23%23",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%23%23",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?%GH",
+ "base": "about:blank",
+ "href": "http://example.org/test?%GH",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%GH",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?a#%EF",
+ "base": "about:blank",
+ "href": "http://example.org/test?a#%EF",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#%EF"
+ },
+ {
+ "input": "http://example.org/test?a#%GH",
+ "base": "about:blank",
+ "href": "http://example.org/test?a#%GH",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#%GH"
+ },
+ "Bad bases",
+ {
+ "input": "test-a.html",
+ "base": "a",
+ "failure": true
+ },
+ {
+ "input": "test-a-slash.html",
+ "base": "a/",
+ "failure": true
+ },
+ {
+ "input": "test-a-slash-slash.html",
+ "base": "a//",
+ "failure": true
+ },
+ {
+ "input": "test-a-colon.html",
+ "base": "a:",
+ "failure": true
+ },
+ {
+ "input": "test-a-colon-slash.html",
+ "base": "a:/",
+ "href": "a:/test-a-colon-slash.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test-a-colon-slash.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test-a-colon-slash-slash.html",
+ "base": "a://",
+ "href": "a:///test-a-colon-slash-slash.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test-a-colon-slash-slash.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test-a-colon-b.html",
+ "base": "a:b",
+ "failure": true
+ },
+ {
+ "input": "test-a-colon-slash-b.html",
+ "base": "a:/b",
+ "href": "a:/test-a-colon-slash-b.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test-a-colon-slash-b.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test-a-colon-slash-slash-b.html",
+ "base": "a://b",
+ "href": "a://b/test-a-colon-slash-slash-b.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "b",
+ "hostname": "b",
+ "port": "",
+ "pathname": "/test-a-colon-slash-slash-b.html",
+ "search": "",
+ "hash": ""
+ },
+ "Null code point in fragment",
+ {
+ "input": "http://example.org/test?a#b\u0000c",
+ "base": "about:blank",
+ "href": "http://example.org/test?a#bc",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#bc"
+ }
+]
diff --git a/netwerk/test/gtest/urltestdata.json b/netwerk/test/gtest/urltestdata.json
new file mode 100644
index 0000000000..41cf6725b3
--- /dev/null
+++ b/netwerk/test/gtest/urltestdata.json
@@ -0,0 +1,9050 @@
+[
+ "This file is based on testing/web-platform/tests/url/resources/urltestdata.json (with failing tests removed)",
+ {
+ "input": "http://example\t.\norg",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://user:pass@foo:21/bar;par?b#c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://user:pass@foo:21/bar;par?b#c",
+ "origin": "http://foo:21",
+ "protocol": "http:",
+ "username": "user",
+ "password": "pass",
+ "host": "foo:21",
+ "hostname": "foo",
+ "port": "21",
+ "pathname": "/bar;par",
+ "search": "?b",
+ "hash": "#c"
+ },
+ {
+ "input": "https://test:@test",
+ "base": null,
+ "href": "https://test@test/",
+ "origin": "https://test",
+ "protocol": "https:",
+ "username": "test",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://:@test",
+ "base": null,
+ "href": "https://test/",
+ "origin": "https://test",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://test:@test/x",
+ "base": null,
+ "href": "non-special://test@test/x",
+ "origin": "null",
+ "protocol": "non-special:",
+ "username": "test",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/x",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://:@test/x",
+ "base": null,
+ "href": "non-special://test/x",
+ "origin": "null",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/x",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:foo.com",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/foo.com",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\t :foo.com \n",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:foo.com",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": " foo.com ",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/foo.com",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "a:\t foo.com",
+ "base": "http://example.org/foo/bar",
+ "href": "a: foo.com",
+ "origin": "null",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": " foo.com",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:21/ b ? d # e ",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f:21/%20b%20?%20d%20#%20e",
+ "origin": "http://f:21",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f:21",
+ "hostname": "f",
+ "port": "21",
+ "pathname": "/%20b%20",
+ "search": "?%20d%20",
+ "hash": "#%20e"
+ },
+ {
+ "input": "lolscheme:x x#x x",
+ "base": null,
+ "href": "lolscheme:x x#x%20x",
+ "protocol": "lolscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "x x",
+ "search": "",
+ "hash": "#x%20x"
+ },
+ {
+ "input": "http://f:/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f/c",
+ "origin": "http://f",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f",
+ "hostname": "f",
+ "port": "",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:0/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f:0/c",
+ "origin": "http://f:0",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f:0",
+ "hostname": "f",
+ "port": "0",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:00000000000000/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f:0/c",
+ "origin": "http://f:0",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f:0",
+ "hostname": "f",
+ "port": "0",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:00000000000000000000080/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f/c",
+ "origin": "http://f",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f",
+ "hostname": "f",
+ "port": "",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:b/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f: /c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f:\n/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://f/c",
+ "origin": "http://f",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "f",
+ "hostname": "f",
+ "port": "",
+ "pathname": "/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://f:fifty-two/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f:999999/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "non-special://f:999999/c",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://f: 21 / b ? d # e ",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": " \t",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":foo.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:foo.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:foo.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":foo.com\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:foo.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:foo.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":a",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:a",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:a",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":#",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:#",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#/"
+ },
+ {
+ "input": "#\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#\\",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#\\"
+ },
+ {
+ "input": "#;?",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#;?",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#;?"
+ },
+ {
+ "input": "?",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar?",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ":23",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:23",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:23",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/:23",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/:23",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/:23",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\x",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/x",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/x",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\\\x\\hello",
+ "base": "http://example.org/foo/bar",
+ "href": "http://x/hello",
+ "origin": "http://x",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "x",
+ "hostname": "x",
+ "port": "",
+ "pathname": "/hello",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "::",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/::",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/::",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "::23",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/::23",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/::23",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo://",
+ "base": "http://example.org/foo/bar",
+ "href": "foo://",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://a:b@c:29/d",
+ "base": "http://example.org/foo/bar",
+ "href": "http://a:b@c:29/d",
+ "origin": "http://c:29",
+ "protocol": "http:",
+ "username": "a",
+ "password": "b",
+ "host": "c:29",
+ "hostname": "c",
+ "port": "29",
+ "pathname": "/d",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http::@c:29",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/:@c:29",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/:@c:29",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://&a:foo(b]c@d:2/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://&a:foo(b%5Dc@d:2/",
+ "origin": "http://d:2",
+ "protocol": "http:",
+ "username": "&a",
+ "password": "foo(b%5Dc",
+ "host": "d:2",
+ "hostname": "d",
+ "port": "2",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://::@c@d:2",
+ "base": "http://example.org/foo/bar",
+ "href": "http://:%3A%40c@d:2/",
+ "origin": "http://d:2",
+ "protocol": "http:",
+ "username": "",
+ "password": "%3A%40c",
+ "host": "d:2",
+ "hostname": "d",
+ "port": "2",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo.com:b@d/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo.com:b@d/",
+ "origin": "http://d",
+ "protocol": "http:",
+ "username": "foo.com",
+ "password": "b",
+ "host": "d",
+ "hostname": "d",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo.com/\\@",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo.com//@",
+ "origin": "http://foo.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.com",
+ "hostname": "foo.com",
+ "port": "",
+ "pathname": "//@",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:\\\\foo.com\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo.com/",
+ "origin": "http://foo.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.com",
+ "hostname": "foo.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:\\\\a\\b:c\\d@foo.com\\",
+ "base": "http://example.org/foo/bar",
+ "href": "http://a/b:c/d@foo.com/",
+ "origin": "http://a",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "a",
+ "hostname": "a",
+ "port": "",
+ "pathname": "/b:c/d@foo.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo:/",
+ "base": "http://example.org/foo/bar",
+ "href": "foo:/",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo:/bar.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "foo:/bar.com/",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/bar.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo://///////",
+ "base": "http://example.org/foo/bar",
+ "href": "foo://///////",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "///////",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo://///////bar.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "foo://///////bar.com/",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "///////bar.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "foo:////://///",
+ "base": "http://example.org/foo/bar",
+ "href": "foo:////://///",
+ "origin": "null",
+ "protocol": "foo:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//://///",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "c:/foo",
+ "base": "http://example.org/foo/bar",
+ "href": "c:/foo",
+ "origin": "null",
+ "protocol": "c:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//foo/bar",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/bar",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo/path;a??e#f#g",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/path;a??e#f#g",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/path;a",
+ "search": "??e",
+ "hash": "#f#g"
+ },
+ {
+ "input": "http://foo/abcd?efgh?ijkl",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/abcd?efgh?ijkl",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/abcd",
+ "search": "?efgh?ijkl",
+ "hash": ""
+ },
+ {
+ "input": "http://foo/abcd#foo?bar",
+ "base": "http://example.org/foo/bar",
+ "href": "http://foo/abcd#foo?bar",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/abcd",
+ "search": "",
+ "hash": "#foo?bar"
+ },
+ {
+ "input": "[61:24:74]:98",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/[61:24:74]:98",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/[61:24:74]:98",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:[61:27]/:foo",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/[61:27]/:foo",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/[61:27]/:foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[1::2]:3:4",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://2001::1",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://2001::1]",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://2001::1]:80",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://[2001::1]",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[2001::1]/",
+ "origin": "http://[2001::1]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[2001::1]",
+ "hostname": "[2001::1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[::127.0.0.1]",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[::7f00:1]/",
+ "origin": "http://[::7f00:1]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[::7f00:1]",
+ "hostname": "[::7f00:1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[::127.0.0.1.]",
+ "base": "http://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "http://[0:0:0:0:0:0:13.1.68.3]",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[::d01:4403]/",
+ "origin": "http://[::d01:4403]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[::d01:4403]",
+ "hostname": "[::d01:4403]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[2001::1]:80",
+ "base": "http://example.org/foo/bar",
+ "href": "http://[2001::1]/",
+ "origin": "http://[2001::1]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[2001::1]",
+ "hostname": "[2001::1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/example.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "madeupscheme:/example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "file:///example.com/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://example:1/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://example:test/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://example%/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://[example]/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "ftps:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftps:/example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "gopher:/example.com/",
+ "origin": "null",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "data:/example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "javascript:/example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:/example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "mailto:/example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/example.com/",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "madeupscheme:example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftps:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ftps:example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "gopher:example.com/",
+ "origin": "null",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "data:example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "javascript:example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:example.com/",
+ "base": "http://example.org/foo/bar",
+ "href": "mailto:example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a/b/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a/b/c",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a/b/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a/ /c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a/%20/c",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a/%20/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a%2fc",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a%2fc",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a%2fc",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/a/%2f/c",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/a/%2f/c",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/a/%2f/c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#β",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar#%CE%B2",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": "#%CE%B2"
+ },
+ {
+ "input": "data:text/html,test#test",
+ "base": "http://example.org/foo/bar",
+ "href": "data:text/html,test#test",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "text/html,test",
+ "search": "",
+ "hash": "#test"
+ },
+ {
+ "input": "tel:1234567890",
+ "base": "http://example.org/foo/bar",
+ "href": "tel:1234567890",
+ "origin": "null",
+ "protocol": "tel:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "1234567890",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html",
+ {
+ "input": "file:c:\\foo\\bar.html",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///c:/foo/bar.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:/foo/bar.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": " File:c|////foo\\bar.html",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///c:////foo/bar.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:////foo/bar.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|/foo/bar",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///C:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/C|\\foo\\bar",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///C:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//C|/foo/bar",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///C:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//server/file",
+ "base": "file:///tmp/mock/path",
+ "href": "file://server/file",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "server",
+ "hostname": "server",
+ "port": "",
+ "pathname": "/file",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\\\server\\file",
+ "base": "file:///tmp/mock/path",
+ "href": "file://server/file",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "server",
+ "hostname": "server",
+ "port": "",
+ "pathname": "/file",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/\\server/file",
+ "base": "file:///tmp/mock/path",
+ "href": "file://server/file",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "server",
+ "hostname": "server",
+ "port": "",
+ "pathname": "/file",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///foo/bar.txt",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///foo/bar.txt",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/foo/bar.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///home/me",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///home/me",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/home/me",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "///",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "///test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://test",
+ "base": "file:///tmp/mock/path",
+ "href": "file://test/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost/",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost/test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///tmp/mock/test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/tmp/mock/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:test",
+ "base": "file:///tmp/mock/path",
+ "href": "file:///tmp/mock/test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/tmp/mock/test",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js",
+ {
+ "input": "http://example.com/././foo",
+ "base": null,
+ "href": "http://example.com/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/./.foo",
+ "base": null,
+ "href": "http://example.com/.foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/.foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/.",
+ "base": null,
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/./",
+ "base": null,
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/..",
+ "base": null,
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/../",
+ "base": null,
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/..bar",
+ "base": null,
+ "href": "http://example.com/foo/..bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/..bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/../ton",
+ "base": null,
+ "href": "http://example.com/foo/ton",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/ton",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar/../ton/../../a",
+ "base": null,
+ "href": "http://example.com/a",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/a",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/../../..",
+ "base": null,
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/../../../ton",
+ "base": null,
+ "href": "http://example.com/ton",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/ton",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/%2e",
+ "base": null,
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/%2e%2",
+ "base": null,
+ "href": "http://example.com/foo/%2e%2",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/%2e%2",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar",
+ "base": null,
+ "href": "http://example.com/%2e.bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%2e.bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com////../..",
+ "base": null,
+ "href": "http://example.com//",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar//../..",
+ "base": null,
+ "href": "http://example.com/foo/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo/bar//..",
+ "base": null,
+ "href": "http://example.com/foo/bar/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo/bar/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo",
+ "base": null,
+ "href": "http://example.com/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/%20foo",
+ "base": null,
+ "href": "http://example.com/%20foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%20foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%",
+ "base": null,
+ "href": "http://example.com/foo%",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%2",
+ "base": null,
+ "href": "http://example.com/foo%2",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%2",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%2zbar",
+ "base": null,
+ "href": "http://example.com/foo%2zbar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%2zbar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%2©zbar",
+ "base": null,
+ "href": "http://example.com/foo%2%C3%82%C2%A9zbar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%2%C3%82%C2%A9zbar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%41%7a",
+ "base": null,
+ "href": "http://example.com/foo%41%7a",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%41%7a",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo\t\u0091%91",
+ "base": null,
+ "href": "http://example.com/foo%C2%91%91",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%C2%91%91",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo%00%51",
+ "base": null,
+ "href": "http://example.com/foo%00%51",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foo%00%51",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/(%28:%3A%29)",
+ "base": null,
+ "href": "http://example.com/(%28:%3A%29)",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/(%28:%3A%29)",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/%3A%3a%3C%3c",
+ "base": null,
+ "href": "http://example.com/%3A%3a%3C%3c",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%3A%3a%3C%3c",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/foo\tbar",
+ "base": null,
+ "href": "http://example.com/foobar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/foobar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com\\\\foo\\\\bar",
+ "base": null,
+ "href": "http://example.com//foo//bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "//foo//bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd",
+ "base": null,
+ "href": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%7Ffp3%3Eju%3Dduvgw%3Dd",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/@asdf%40",
+ "base": null,
+ "href": "http://example.com/@asdf%40",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/@asdf%40",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/你好你好",
+ "base": null,
+ "href": "http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/‥/foo",
+ "base": null,
+ "href": "http://example.com/%E2%80%A5/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%E2%80%A5/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com//foo",
+ "base": null,
+ "href": "http://example.com/%EF%BB%BF/foo",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%EF%BB%BF/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.com/‮/foo/‭/bar",
+ "base": null,
+ "href": "http://example.com/%E2%80%AE/foo/%E2%80%AD/bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/%E2%80%AE/foo/%E2%80%AD/bar",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js",
+ {
+ "input": "http://www.google.com/foo?bar=baz#",
+ "base": null,
+ "href": "http://www.google.com/foo?bar=baz#",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "?bar=baz",
+ "hash": ""
+ },
+ {
+ "input": "http://www.google.com/foo?bar=baz# »",
+ "base": null,
+ "href": "http://www.google.com/foo?bar=baz#%20%C2%BB",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "?bar=baz",
+ "hash": "#%20%C2%BB"
+ },
+ {
+ "input": "data:test# »",
+ "base": null,
+ "href": "data:test#%20%C2%BB",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "test",
+ "search": "",
+ "hash": "#%20%C2%BB"
+ },
+ {
+ "input": "http://www.google.com",
+ "base": null,
+ "href": "http://www.google.com/",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.0x00A80001",
+ "base": null,
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www/foo%2Ehtml",
+ "base": null,
+ "href": "http://www/foo%2Ehtml",
+ "origin": "http://www",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www",
+ "hostname": "www",
+ "port": "",
+ "pathname": "/foo%2Ehtml",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www/foo/%2E/html",
+ "base": null,
+ "href": "http://www/foo/html",
+ "origin": "http://www",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www",
+ "hostname": "www",
+ "port": "",
+ "pathname": "/foo/html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://user:pass@/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://%25DOMAIN:foobar@foodomain.com/",
+ "base": null,
+ "href": "http://%25DOMAIN:foobar@foodomain.com/",
+ "origin": "http://foodomain.com",
+ "protocol": "http:",
+ "username": "%25DOMAIN",
+ "password": "foobar",
+ "host": "foodomain.com",
+ "hostname": "foodomain.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:\\\\www.google.com\\foo",
+ "base": null,
+ "href": "http://www.google.com/foo",
+ "origin": "http://www.google.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.google.com",
+ "hostname": "www.google.com",
+ "port": "",
+ "pathname": "/foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo:80/",
+ "base": null,
+ "href": "http://foo/",
+ "origin": "http://foo",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo:81/",
+ "base": null,
+ "href": "http://foo:81/",
+ "origin": "http://foo:81",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo:81",
+ "hostname": "foo",
+ "port": "81",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "httpa://foo:80/",
+ "base": null,
+ "href": "httpa://foo:80/",
+ "origin": "null",
+ "protocol": "httpa:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://foo:-80/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://foo:443/",
+ "base": null,
+ "href": "https://foo/",
+ "origin": "https://foo",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://foo:80/",
+ "base": null,
+ "href": "https://foo:80/",
+ "origin": "https://foo:80",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp://foo:21/",
+ "base": null,
+ "href": "ftp://foo/",
+ "origin": "ftp://foo",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp://foo:80/",
+ "base": null,
+ "href": "ftp://foo:80/",
+ "origin": "ftp://foo:80",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher://foo:70/",
+ "base": null,
+ "href": "gopher://foo:70/",
+ "origin": "null",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "foo:70",
+ "hostname": "foo",
+ "port": "70",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher://foo:443/",
+ "base": null,
+ "href": "gopher://foo:443/",
+ "origin": "null",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "foo:443",
+ "hostname": "foo",
+ "port": "443",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:80/",
+ "base": null,
+ "href": "ws://foo/",
+ "origin": "ws://foo",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:81/",
+ "base": null,
+ "href": "ws://foo:81/",
+ "origin": "ws://foo:81",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo:81",
+ "hostname": "foo",
+ "port": "81",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:443/",
+ "base": null,
+ "href": "ws://foo:443/",
+ "origin": "ws://foo:443",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo:443",
+ "hostname": "foo",
+ "port": "443",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws://foo:815/",
+ "base": null,
+ "href": "ws://foo:815/",
+ "origin": "ws://foo:815",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "foo:815",
+ "hostname": "foo",
+ "port": "815",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:80/",
+ "base": null,
+ "href": "wss://foo:80/",
+ "origin": "wss://foo:80",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo:80",
+ "hostname": "foo",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:81/",
+ "base": null,
+ "href": "wss://foo:81/",
+ "origin": "wss://foo:81",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo:81",
+ "hostname": "foo",
+ "port": "81",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:443/",
+ "base": null,
+ "href": "wss://foo/",
+ "origin": "wss://foo",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss://foo:815/",
+ "base": null,
+ "href": "wss://foo:815/",
+ "origin": "wss://foo:815",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "foo:815",
+ "hostname": "foo",
+ "port": "815",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:/example.com/",
+ "base": null,
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:/example.com/",
+ "base": null,
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:/example.com/",
+ "base": null,
+ "href": "madeupscheme:/example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:/example.com/",
+ "base": null,
+ "href": "file:///example.com/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftps:/example.com/",
+ "base": null,
+ "href": "ftps:/example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:/example.com/",
+ "base": null,
+ "href": "gopher:/example.com/",
+ "origin": "null",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:/example.com/",
+ "base": null,
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:/example.com/",
+ "base": null,
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:/example.com/",
+ "base": null,
+ "href": "data:/example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:/example.com/",
+ "base": null,
+ "href": "javascript:/example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:/example.com/",
+ "base": null,
+ "href": "mailto:/example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftp:example.com/",
+ "base": null,
+ "href": "ftp://example.com/",
+ "origin": "ftp://example.com",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https:example.com/",
+ "base": null,
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "madeupscheme:example.com/",
+ "base": null,
+ "href": "madeupscheme:example.com/",
+ "origin": "null",
+ "protocol": "madeupscheme:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ftps:example.com/",
+ "base": null,
+ "href": "ftps:example.com/",
+ "origin": "null",
+ "protocol": "ftps:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher:example.com/",
+ "base": null,
+ "href": "gopher:example.com/",
+ "origin": "null",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ws:example.com/",
+ "base": null,
+ "href": "ws://example.com/",
+ "origin": "ws://example.com",
+ "protocol": "ws:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wss:example.com/",
+ "base": null,
+ "href": "wss://example.com/",
+ "origin": "wss://example.com",
+ "protocol": "wss:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data:example.com/",
+ "base": null,
+ "href": "data:example.com/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:example.com/",
+ "base": null,
+ "href": "javascript:example.com/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:example.com/",
+ "base": null,
+ "href": "mailto:example.com/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "example.com/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://@www.example.com",
+ "base": null,
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://a:b@www.example.com",
+ "base": null,
+ "href": "http://a:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://@pple.com",
+ "base": null,
+ "href": "http://pple.com/",
+ "origin": "http://pple.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "pple.com",
+ "hostname": "pple.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://:b@www.example.com",
+ "base": null,
+ "href": "http://:b@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "b",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:/:@/www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http://user@/www.example.com",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http:@/www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http:/@/www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http://@/www.example.com",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https:@/www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http:a:b@/www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http:/a:b@/www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http://a:b@/www.example.com",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http::@/www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http://a:@www.example.com",
+ "base": null,
+ "href": "http://a@www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "a",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www.@pple.com",
+ "base": null,
+ "href": "http://www.@pple.com/",
+ "origin": "http://pple.com",
+ "protocol": "http:",
+ "username": "www.",
+ "password": "",
+ "host": "pple.com",
+ "hostname": "pple.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http:@:www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http:/@:www.example.com",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "http://@:www.example.com",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://:@www.example.com",
+ "base": null,
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# Others",
+ {
+ "input": "/",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": ".",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "./test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../aaa/test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/aaa/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/aaa/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../../test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "中/test.txt",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example.com/%E4%B8%AD/test.txt",
+ "origin": "http://www.example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "port": "",
+ "pathname": "/%E4%B8%AD/test.txt",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://www.example2.com",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example2.com/",
+ "origin": "http://www.example2.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example2.com",
+ "hostname": "www.example2.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//www.example2.com",
+ "base": "http://www.example.com/test",
+ "href": "http://www.example2.com/",
+ "origin": "http://www.example2.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.example2.com",
+ "hostname": "www.example2.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:...",
+ "base": "http://www.example.com/test",
+ "href": "file:///...",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/...",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:..",
+ "base": "http://www.example.com/test",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:a",
+ "base": "http://www.example.com/test",
+ "href": "file:///a",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/a",
+ "search": "",
+ "hash": ""
+ },
+ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html",
+ "Basic canonicalization, uppercase should be converted to lowercase",
+ {
+ "input": "http://ExAmPlE.CoM",
+ "base": "http://other.com/",
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example example.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://Goo%20 goo%7C|.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[:]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "U+3000 is mapped to U+0020 (space) which is disallowed",
+ {
+ "input": "http://GOO\u00a0\u3000goo.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored",
+ {
+ "input": "http://GOO\u200b\u2060\ufeffgoo.com",
+ "base": "http://other.com/",
+ "href": "http://googoo.com/",
+ "origin": "http://googoo.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "googoo.com",
+ "hostname": "googoo.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Leading and trailing C0 control or space",
+ {
+ "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ",
+ "base": null,
+ "href": "http://example.com/",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)",
+ {
+ "input": "http://www.foo。bar.com",
+ "base": "http://other.com/",
+ "href": "http://www.foo.bar.com/",
+ "origin": "http://www.foo.bar.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "www.foo.bar.com",
+ "hostname": "www.foo.bar.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0",
+ {
+ "input": "http://\ufdd0zyx.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "This is the same as previous but escaped",
+ {
+ "input": "http://%ef%b7%90zyx.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "U+FFFD",
+ {
+ "input": "https://\ufffd",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://%EF%BF%BD",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://x/\ufffd?\ufffd#\ufffd",
+ "base": null,
+ "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD",
+ "origin": "https://x",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "x",
+ "hostname": "x",
+ "port": "",
+ "pathname": "/%EF%BF%BD",
+ "search": "?%EF%BF%BD",
+ "hash": "#%EF%BF%BD"
+ },
+ "Domain is ASCII, but a label is invalid IDNA",
+ {
+ "input": "http://a.b.c.xn--pokxncvks",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://10.0.0.xn--pokxncvks",
+ "base": null,
+ "failure": true
+ },
+ "IDNA labels should be matched case-insensitively",
+ {
+ "input": "http://a.b.c.XN--pokxncvks",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a.b.c.Xn--pokxncvks",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://10.0.0.XN--pokxncvks",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://10.0.0.xN--pokxncvks",
+ "base": null,
+ "failure": true
+ },
+ "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.",
+ {
+ "input": "http://Go.com",
+ "base": "http://other.com/",
+ "href": "http://go.com/",
+ "origin": "http://go.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "go.com",
+ "hostname": "go.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257",
+ {
+ "input": "http://%41.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "...%00 in fullwidth should fail (also as escaped UTF-8 input)",
+ {
+ "input": "http://%00.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN",
+ {
+ "input": "http://你好你好",
+ "base": "http://other.com/",
+ "href": "http://xn--6qqa088eba/",
+ "origin": "http://xn--6qqa088eba",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "xn--6qqa088eba",
+ "hostname": "xn--6qqa088eba",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://faß.ExAmPlE/",
+ "base": null,
+ "href": "https://xn--fa-hia.example/",
+ "origin": "https://xn--fa-hia.example",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "xn--fa-hia.example",
+ "hostname": "xn--fa-hia.example",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://faß.ExAmPlE/",
+ "base": null,
+ "href": "sc://fa%C3%9F.ExAmPlE/",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "fa%C3%9F.ExAmPlE",
+ "hostname": "fa%C3%9F.ExAmPlE",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191",
+ {
+ "input": "http://%zz%66%a.com",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "If we get an invalid character that has been escaped.",
+ {
+ "input": "http://%25",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://hello%00",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Escaped numbers should be treated like IP addresses if they are.",
+ {
+ "input": "http://%30%78%63%30%2e%30%32%35%30.01",
+ "base": "http://other.com/",
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e",
+ "base": "http://other.com/",
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.168.0.257",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Invalid escaping in hosts causes failure",
+ {
+ "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "A space in a host causes failure",
+ {
+ "input": "http://192.168.0.1 hello",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "https://x x:12",
+ "base": null,
+ "failure": true
+ },
+ "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP",
+ {
+ "input": "http://0Xc0.0250.01",
+ "base": "http://other.com/",
+ "href": "http://192.168.0.1/",
+ "origin": "http://192.168.0.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.0.1",
+ "hostname": "192.168.0.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Domains with empty labels",
+ {
+ "input": "http://./",
+ "base": null,
+ "href": "http://./",
+ "origin": "http://.",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": ".",
+ "hostname": ".",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://../",
+ "base": null,
+ "href": "http://../",
+ "origin": "http://..",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "..",
+ "hostname": "..",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Non-special domains with empty labels",
+ {
+ "input": "h://.",
+ "base": null,
+ "href": "h://.",
+ "origin": "null",
+ "protocol": "h:",
+ "username": "",
+ "password": "",
+ "host": ".",
+ "hostname": ".",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": ""
+ },
+ "Broken IPv6",
+ {
+ "input": "http://[www.google.com]/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://[google.com]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.2.3.4x]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.2.3.]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.2.]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::.1.2]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::1.]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::.1]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://[::%31]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://%5B::1]",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "Misc Unicode",
+ {
+ "input": "http://foo:💩@example.com/bar",
+ "base": "http://other.com/",
+ "href": "http://foo:%F0%9F%92%A9@example.com/bar",
+ "origin": "http://example.com",
+ "protocol": "http:",
+ "username": "foo",
+ "password": "%F0%9F%92%A9",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/bar",
+ "search": "",
+ "hash": ""
+ },
+ "# resolving a fragment against any scheme succeeds",
+ {
+ "input": "#",
+ "base": "test:test",
+ "href": "test:test#",
+ "origin": "null",
+ "protocol": "test:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "#x",
+ "base": "mailto:x@x.com",
+ "href": "mailto:x@x.com#x",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "x@x.com",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "#x",
+ "base": "data:,",
+ "href": "data:,#x",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": ",",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "#x",
+ "base": "about:blank",
+ "href": "about:blank#x",
+ "origin": "null",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "#x:y",
+ "base": "about:blank",
+ "href": "about:blank#x:y",
+ "origin": "null",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": "#x:y"
+ },
+ {
+ "input": "#",
+ "base": "test:test?test",
+ "href": "test:test?test#",
+ "origin": "null",
+ "protocol": "test:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "test",
+ "search": "?test",
+ "hash": ""
+ },
+ "# multiple @ in authority state",
+ {
+ "input": "https://@test@test@example:800/",
+ "base": "http://doesnotmatter/",
+ "href": "https://%40test%40test@example:800/",
+ "origin": "https://example:800",
+ "protocol": "https:",
+ "username": "%40test%40test",
+ "password": "",
+ "host": "example:800",
+ "hostname": "example",
+ "port": "800",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://@@@example",
+ "base": "http://doesnotmatter/",
+ "href": "https://%40%40@example/",
+ "origin": "https://example",
+ "protocol": "https:",
+ "username": "%40%40",
+ "password": "",
+ "host": "example",
+ "hostname": "example",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "non-az-09 characters",
+ {
+ "input": "http://`{}:`{}@h/`{}?`{}",
+ "base": "http://doesnotmatter/",
+ "href": "http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}",
+ "origin": "http://h",
+ "protocol": "http:",
+ "username": "%60%7B%7D",
+ "password": "%60%7B%7D",
+ "host": "h",
+ "hostname": "h",
+ "port": "",
+ "pathname": "/%60%7B%7D",
+ "search": "?`{}",
+ "hash": ""
+ },
+ "byte is ' and url is special",
+ {
+ "input": "http://host/?'",
+ "base": null,
+ "href": "http://host/?%27",
+ "origin": "http://host",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/",
+ "search": "?%27",
+ "hash": ""
+ },
+ {
+ "input": "notspecial://host/?'",
+ "base": null,
+ "href": "notspecial://host/?'",
+ "origin": "null",
+ "protocol": "notspecial:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/",
+ "search": "?'",
+ "hash": ""
+ },
+ "# Credentials in base",
+ {
+ "input": "/some/path",
+ "base": "http://user@example.org/smth",
+ "href": "http://user@example.org/some/path",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "user",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/some/path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "",
+ "base": "http://user:pass@example.org:21/smth",
+ "href": "http://user:pass@example.org:21/smth",
+ "origin": "http://example.org:21",
+ "protocol": "http:",
+ "username": "user",
+ "password": "pass",
+ "host": "example.org:21",
+ "hostname": "example.org",
+ "port": "21",
+ "pathname": "/smth",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/some/path",
+ "base": "http://user:pass@example.org:21/smth",
+ "href": "http://user:pass@example.org:21/some/path",
+ "origin": "http://example.org:21",
+ "protocol": "http:",
+ "username": "user",
+ "password": "pass",
+ "host": "example.org:21",
+ "hostname": "example.org",
+ "port": "21",
+ "pathname": "/some/path",
+ "search": "",
+ "hash": ""
+ },
+ "# a set of tests designed by zcorpan for relative URLs with unknown schemes",
+ {
+ "input": "i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/pa/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///pa/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "../i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "../i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "../i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "/i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "/i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "?i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "?i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ {
+ "input": "?i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/pa/pa?i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "?i",
+ "hash": ""
+ },
+ {
+ "input": "?i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/pa?i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/pa",
+ "search": "?i",
+ "hash": ""
+ },
+ {
+ "input": "?i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///pa/pa?i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "?i",
+ "hash": ""
+ },
+ {
+ "input": "#i",
+ "base": "sc:sd",
+ "href": "sc:sd#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "sd",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc:sd/sd",
+ "href": "sc:sd/sd#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "sd/sd",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/pa/pa#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc://ho/pa",
+ "href": "sc://ho/pa#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "ho",
+ "hostname": "ho",
+ "port": "",
+ "pathname": "/pa",
+ "search": "",
+ "hash": "#i"
+ },
+ {
+ "input": "#i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///pa/pa#i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/pa",
+ "search": "",
+ "hash": "#i"
+ },
+ "# make sure that relative URL logic works on known typically non-relative schemes too",
+ {
+ "input": "data:/../",
+ "base": null,
+ "href": "data:/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript:/../",
+ "base": null,
+ "href": "javascript:/",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto:/../",
+ "base": null,
+ "href": "mailto:/",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown schemes and their hosts",
+ {
+ "input": "sc://ñ.test/",
+ "base": null,
+ "href": "sc://%C3%B1.test/",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1.test",
+ "hostname": "%C3%B1.test",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://%/",
+ "base": null,
+ "href": "sc://%/",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%",
+ "hostname": "%",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://@/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://te@s:t@/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://:/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://:12/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "x",
+ "base": "sc://ñ",
+ "href": "sc://%C3%B1/x",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1",
+ "hostname": "%C3%B1",
+ "port": "",
+ "pathname": "/x",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown schemes and backslashes",
+ {
+ "input": "sc:\\../",
+ "base": null,
+ "href": "sc:\\../",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "\\../",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown scheme with path looking like a password",
+ {
+ "input": "sc::a@example.net",
+ "base": null,
+ "href": "sc::a@example.net",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": ":a@example.net",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown scheme with bogus percent-encoding",
+ {
+ "input": "wow:%NBD",
+ "base": null,
+ "href": "wow:%NBD",
+ "origin": "null",
+ "protocol": "wow:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "%NBD",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "wow:%1G",
+ "base": null,
+ "href": "wow:%1G",
+ "origin": "null",
+ "protocol": "wow:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "%1G",
+ "search": "",
+ "hash": ""
+ },
+ "# unknown scheme with non-URL characters",
+ {
+ "input": "wow:\uFFFF",
+ "base": null,
+ "href": "wow:%EF%BF%BF",
+ "origin": "null",
+ "protocol": "wow:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "%EF%BF%BF",
+ "search": "",
+ "hash": ""
+ },
+ "Forbidden host code points",
+ {
+ "input": "sc://a\u0000b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a<b",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a>b",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a[b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a\\b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a]b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a^b",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "sc://a|b/",
+ "base": null,
+ "failure": true
+ },
+ "Forbidden host codepoints: tabs and newlines are removed during preprocessing",
+ {
+ "input": "foo://ho\u0009st/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://host/",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "foo://ho\u000Ast/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://host/",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "foo://ho\u000Dst/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://host/",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": ""
+ },
+ "Forbidden domain code-points",
+ {
+ "input": "http://a\u0000b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0001b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0002b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0003b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0004b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0005b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0006b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0007b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0008b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u000Bb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u000Cb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u000Eb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u000Fb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0010b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0011b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0012b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0013b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0014b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0015b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0016b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0017b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0018b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u0019b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u001Ab/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u001Bb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u001Cb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u001Db/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u001Eb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u001Fb/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a%b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a<b",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a>b",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a[b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a]b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a^b",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a|b/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://a\u007Fb/",
+ "base": null,
+ "failure": true
+ },
+ "Forbidden domain codepoints: tabs and newlines are removed during preprocessing",
+ {
+ "input": "http://ho\u0009st/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "http://host/",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "http:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "http://ho\u000Ast/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "http://host/",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "http:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "http://ho\u000Dst/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "http://host/",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "http:",
+ "search": "",
+ "username": ""
+ },
+ "Encoded forbidden domain codepoints in special URLs",
+ {
+ "input": "http://ho%00st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%01st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%02st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%03st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%04st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%05st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%06st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%07st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%08st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%09st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%0Ast/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%0Bst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%0Cst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%0Dst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%0Est/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%0Fst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%10st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%11st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%12st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%13st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%14st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%15st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%16st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%17st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%18st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%19st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%1Ast/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%1Bst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%1Cst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%1Dst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%1Est/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%1Fst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%20st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%23st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%25st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%2Fst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%3Ast/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%3Cst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%3Est/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%3Fst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%40st/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%5Bst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%5Cst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%5Dst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%7Cst/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://ho%7Fst/",
+ "base": null,
+ "failure": true
+ },
+ "Allowed host/domain code points",
+ {
+ "input": "sc://\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000B\u000C\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u007F!\"$%&'()*+,-.;=_`{}~/",
+ "base": null,
+ "href": "sc://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~/",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~",
+ "hostname": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# Hosts and percent-encoding",
+ {
+ "input": "ftp://example.com%80/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "ftp://example.com%A0/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://example.com%80/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://example.com%A0/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "ftp://%e2%98%83",
+ "base": null,
+ "href": "ftp://xn--n3h/",
+ "origin": "ftp://xn--n3h",
+ "protocol": "ftp:",
+ "username": "",
+ "password": "",
+ "host": "xn--n3h",
+ "hostname": "xn--n3h",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "https://%e2%98%83",
+ "base": null,
+ "href": "https://xn--n3h/",
+ "origin": "https://xn--n3h",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "xn--n3h",
+ "hostname": "xn--n3h",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# tests from jsdom/whatwg-url designed for code coverage",
+ {
+ "input": "http://127.0.0.1:10100/relative_import.html",
+ "base": null,
+ "href": "http://127.0.0.1:10100/relative_import.html",
+ "origin": "http://127.0.0.1:10100",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "127.0.0.1:10100",
+ "hostname": "127.0.0.1",
+ "port": "10100",
+ "pathname": "/relative_import.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://facebook.com/?foo=%7B%22abc%22",
+ "base": null,
+ "href": "http://facebook.com/?foo=%7B%22abc%22",
+ "origin": "http://facebook.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "facebook.com",
+ "hostname": "facebook.com",
+ "port": "",
+ "pathname": "/",
+ "search": "?foo=%7B%22abc%22",
+ "hash": ""
+ },
+ {
+ "input": "https://localhost:3000/jqueryui@1.2.3",
+ "base": null,
+ "href": "https://localhost:3000/jqueryui@1.2.3",
+ "origin": "https://localhost:3000",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "localhost:3000",
+ "hostname": "localhost",
+ "port": "3000",
+ "pathname": "/jqueryui@1.2.3",
+ "search": "",
+ "hash": ""
+ },
+ "# tab/LF/CR",
+ {
+ "input": "h\tt\nt\rp://h\to\ns\rt:9\t0\n0\r0/p\ta\nt\rh?q\tu\ne\rry#f\tr\na\rg",
+ "base": null,
+ "href": "http://host:9000/path?query#frag",
+ "origin": "http://host:9000",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "host:9000",
+ "hostname": "host",
+ "port": "9000",
+ "pathname": "/path",
+ "search": "?query",
+ "hash": "#frag"
+ },
+ "# Stringification of URL.searchParams",
+ {
+ "input": "?a=b&c=d",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar?a=b&c=d",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "?a=b&c=d",
+ "searchParams": "a=b&c=d",
+ "hash": ""
+ },
+ {
+ "input": "??a=b&c=d",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar??a=b&c=d",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "??a=b&c=d",
+ "searchParams": "%3Fa=b&c=d",
+ "hash": ""
+ },
+ "# Scheme only",
+ {
+ "input": "http:",
+ "base": "http://example.org/foo/bar",
+ "href": "http://example.org/foo/bar",
+ "origin": "http://example.org",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "searchParams": "",
+ "hash": ""
+ },
+ {
+ "input": "http:",
+ "base": "https://example.org/foo/bar",
+ "failure": true
+ },
+ {
+ "input": "sc:",
+ "base": "https://example.org/foo/bar",
+ "href": "sc:",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "searchParams": "",
+ "hash": ""
+ },
+ "# Percent encoding of fragments",
+ {
+ "input": "http://foo.bar/baz?qux#foo\bbar",
+ "base": null,
+ "href": "http://foo.bar/baz?qux#foo%08bar",
+ "origin": "http://foo.bar",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.bar",
+ "hostname": "foo.bar",
+ "port": "",
+ "pathname": "/baz",
+ "search": "?qux",
+ "searchParams": "qux=",
+ "hash": "#foo%08bar"
+ },
+ {
+ "input": "http://foo.bar/baz?qux#foo\"bar",
+ "base": null,
+ "href": "http://foo.bar/baz?qux#foo%22bar",
+ "origin": "http://foo.bar",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.bar",
+ "hostname": "foo.bar",
+ "port": "",
+ "pathname": "/baz",
+ "search": "?qux",
+ "searchParams": "qux=",
+ "hash": "#foo%22bar"
+ },
+ {
+ "input": "http://foo.bar/baz?qux#foo<bar",
+ "base": null,
+ "href": "http://foo.bar/baz?qux#foo%3Cbar",
+ "origin": "http://foo.bar",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.bar",
+ "hostname": "foo.bar",
+ "port": "",
+ "pathname": "/baz",
+ "search": "?qux",
+ "searchParams": "qux=",
+ "hash": "#foo%3Cbar"
+ },
+ {
+ "input": "http://foo.bar/baz?qux#foo>bar",
+ "base": null,
+ "href": "http://foo.bar/baz?qux#foo%3Ebar",
+ "origin": "http://foo.bar",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.bar",
+ "hostname": "foo.bar",
+ "port": "",
+ "pathname": "/baz",
+ "search": "?qux",
+ "searchParams": "qux=",
+ "hash": "#foo%3Ebar"
+ },
+ {
+ "input": "http://foo.bar/baz?qux#foo`bar",
+ "base": null,
+ "href": "http://foo.bar/baz?qux#foo%60bar",
+ "origin": "http://foo.bar",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "foo.bar",
+ "hostname": "foo.bar",
+ "port": "",
+ "pathname": "/baz",
+ "search": "?qux",
+ "searchParams": "qux=",
+ "hash": "#foo%60bar"
+ },
+ "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)",
+ {
+ "input": "http://1.2.3.4/",
+ "base": "http://other.com/",
+ "href": "http://1.2.3.4/",
+ "origin": "http://1.2.3.4",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "1.2.3.4",
+ "hostname": "1.2.3.4",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://1.2.3.4./",
+ "base": "http://other.com/",
+ "href": "http://1.2.3.4/",
+ "origin": "http://1.2.3.4",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "1.2.3.4",
+ "hostname": "1.2.3.4",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.168.257",
+ "base": "http://other.com/",
+ "href": "http://192.168.1.1/",
+ "origin": "http://192.168.1.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.1.1",
+ "hostname": "192.168.1.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.168.257.",
+ "base": "http://other.com/",
+ "href": "http://192.168.1.1/",
+ "origin": "http://192.168.1.1",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.1.1",
+ "hostname": "192.168.1.1",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://192.168.257.com",
+ "base": "http://other.com/",
+ "href": "http://192.168.257.com/",
+ "origin": "http://192.168.257.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "192.168.257.com",
+ "hostname": "192.168.257.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://256",
+ "base": "http://other.com/",
+ "href": "http://0.0.1.0/",
+ "origin": "http://0.0.1.0",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0.0.1.0",
+ "hostname": "0.0.1.0",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://256.com",
+ "base": "http://other.com/",
+ "href": "http://256.com/",
+ "origin": "http://256.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "256.com",
+ "hostname": "256.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://999999999",
+ "base": "http://other.com/",
+ "href": "http://59.154.201.255/",
+ "origin": "http://59.154.201.255",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "59.154.201.255",
+ "hostname": "59.154.201.255",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://999999999.",
+ "base": "http://other.com/",
+ "href": "http://59.154.201.255/",
+ "origin": "http://59.154.201.255",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "59.154.201.255",
+ "hostname": "59.154.201.255",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": "",
+ "failure": true
+ },
+ {
+ "input": "http://999999999.com",
+ "base": "http://other.com/",
+ "href": "http://999999999.com/",
+ "origin": "http://999999999.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "999999999.com",
+ "hostname": "999999999.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://10000000000",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://10000000000.com",
+ "base": "http://other.com/",
+ "href": "http://10000000000.com/",
+ "origin": "http://10000000000.com",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "10000000000.com",
+ "hostname": "10000000000.com",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://4294967295",
+ "base": "http://other.com/",
+ "href": "http://255.255.255.255/",
+ "origin": "http://255.255.255.255",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "255.255.255.255",
+ "hostname": "255.255.255.255",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://4294967296",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://0xffffffff",
+ "base": "http://other.com/",
+ "href": "http://255.255.255.255/",
+ "origin": "http://255.255.255.255",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "255.255.255.255",
+ "hostname": "255.255.255.255",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://0xffffffff1",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://256.256.256.256",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)",
+ {
+ "input": "https://0x100000000/test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://256.0.0.1/test",
+ "base": null,
+ "failure": true
+ },
+ "# file URLs containing percent-encoded Windows drive letters (shouldn't work)",
+ {
+ "input": "file:///C%3A/",
+ "base": null,
+ "href": "file:///C%3A/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C%3A/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///C%7C/",
+ "base": null,
+ "href": "file:///C%7C/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C%7C/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://%43%3A",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://%43%7C",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://%43|",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://C%7C",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://%43%7C/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://%43%7C/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "asdf://%43|/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "asdf://%43%7C/",
+ "base": null,
+ "href": "asdf://%43%7C/",
+ "origin": "null",
+ "protocol": "asdf:",
+ "username": "",
+ "password": "",
+ "host": "%43%7C",
+ "hostname": "%43%7C",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)",
+ {
+ "input": "pix/submit.gif",
+ "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html",
+ "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///C:/",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# More file URL tests by zcorpan and annevk",
+ {
+ "input": "/",
+ "base": "file:///C:/a/b",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/",
+ "base": "file://h/C:/a/b",
+ "href": "file://h/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "h",
+ "hostname": "h",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/",
+ "base": "file://h/a/b",
+ "href": "file://h/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "h",
+ "hostname": "h",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//d:",
+ "base": "file:///C:/a/b",
+ "href": "file:///d:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/d:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//d:/..",
+ "base": "file:///C:/a/b",
+ "href": "file:///d:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/d:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///ab:/",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..",
+ "base": "file:///1:/",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": ""
+ },
+ {
+ "input": "file:",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": ""
+ },
+ {
+ "input": "?x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?x",
+ "hash": ""
+ },
+ {
+ "input": "file:?x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?x",
+ "hash": ""
+ },
+ {
+ "input": "#x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test#x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": "#x"
+ },
+ {
+ "input": "file:#x",
+ "base": "file:///test?test#test",
+ "href": "file:///test?test#x",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?test",
+ "hash": "#x"
+ },
+ "# File URLs and many (back)slashes",
+ {
+ "input": "file:\\\\//",
+ "base": null,
+ "href": "file:////",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:\\\\\\\\",
+ "base": null,
+ "href": "file:////",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:\\\\\\\\?fox",
+ "base": null,
+ "href": "file:////?fox",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "?fox",
+ "hash": ""
+ },
+ {
+ "input": "file:\\\\\\\\#guppy",
+ "base": null,
+ "href": "file:////#guppy",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": "#guppy"
+ },
+ {
+ "input": "file://spider///",
+ "base": null,
+ "href": "file://spider///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "spider",
+ "hostname": "spider",
+ "port": "",
+ "pathname": "///",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:\\\\localhost//",
+ "base": null,
+ "href": "file:////",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///localhost//cat",
+ "base": null,
+ "href": "file:///localhost//cat",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/localhost//cat",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://\\/localhost//cat",
+ "base": null,
+ "href": "file:////localhost//cat",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//localhost//cat",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost//a//../..//",
+ "base": null,
+ "href": "file://///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "///",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/////mouse",
+ "base": "file:///elephant",
+ "href": "file://///mouse",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "///mouse",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\//pig",
+ "base": "file://lion/",
+ "href": "file:///pig",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pig",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\/localhost//pig",
+ "base": "file://lion/",
+ "href": "file:////pig",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//pig",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//localhost//pig",
+ "base": "file://lion/",
+ "href": "file:////pig",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//pig",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/..//localhost//pig",
+ "base": "file://lion/",
+ "href": "file://lion//localhost//pig",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "lion",
+ "hostname": "lion",
+ "port": "",
+ "pathname": "//localhost//pig",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://",
+ "base": "file://ape/",
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "# File URLs with non-empty hosts",
+ {
+ "input": "/rooibos",
+ "base": "file://tea/",
+ "href": "file://tea/rooibos",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "tea",
+ "hostname": "tea",
+ "port": "",
+ "pathname": "/rooibos",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/?chai",
+ "base": "file://tea/",
+ "href": "file://tea/?chai",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "tea",
+ "hostname": "tea",
+ "port": "",
+ "pathname": "/",
+ "search": "?chai",
+ "hash": ""
+ },
+ "# Windows drive letter handling with the 'file:' base URL",
+ {
+ "input": "C|",
+ "base": "file://host/dir/file",
+ "href": "file://host/C:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|",
+ "base": "file://host/D:/dir1/dir2/file",
+ "href": "file://host/C:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|#",
+ "base": "file://host/dir/file",
+ "href": "file://host/C:#",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|?",
+ "base": "file://host/dir/file",
+ "href": "file://host/C:?",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|/",
+ "base": "file://host/dir/file",
+ "href": "file://host/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|\n/",
+ "base": "file://host/dir/file",
+ "href": "file://host/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|\\",
+ "base": "file://host/dir/file",
+ "href": "file://host/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C",
+ "base": "file://host/dir/file",
+ "href": "file://host/dir/C",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/dir/C",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "C|a",
+ "base": "file://host/dir/file",
+ "href": "file://host/dir/C|a",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/dir/C|a",
+ "search": "",
+ "hash": ""
+ },
+ "# Windows drive letter quirk in the file slash state",
+ {
+ "input": "/c:/foo/bar",
+ "base": "file:///c:/baz/qux",
+ "href": "file:///c:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/c|/foo/bar",
+ "base": "file:///c:/baz/qux",
+ "href": "file:///c:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:\\c:\\foo\\bar",
+ "base": "file:///c:/baz/qux",
+ "href": "file:///c:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/c:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/c:/foo/bar",
+ "base": "file://host/path",
+ "href": "file://host/c:/foo/bar",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/c:/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ "# Do not drop the host in the presence of a drive letter",
+ {
+ "input": "file://example.net/C:/",
+ "base": null,
+ "href": "file://example.net/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "example.net",
+ "hostname": "example.net",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://1.2.3.4/C:/",
+ "base": null,
+ "href": "file://1.2.3.4/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "1.2.3.4",
+ "hostname": "1.2.3.4",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://[1::8]/C:/",
+ "base": null,
+ "href": "file://[1::8]/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "[1::8]",
+ "hostname": "[1::8]",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ "# Copy the host from the base URL in the following cases",
+ {
+ "input": "C|/",
+ "base": "file://host/",
+ "href": "file://host/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/C:/",
+ "base": "file://host/",
+ "href": "file://host/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:C:/",
+ "base": "file://host/",
+ "href": "file://host/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:/C:/",
+ "base": "file://host/",
+ "href": "file://host/C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "host",
+ "hostname": "host",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ "# Copy the empty host from the input in the following cases",
+ {
+ "input": "//C:/",
+ "base": "file://host/",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://C:/",
+ "base": "file://host/",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "///C:/",
+ "base": "file://host/",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///C:/",
+ "base": "file://host/",
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ "# Windows drive letter quirk (no host)",
+ {
+ "input": "file:/C|/",
+ "base": null,
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://C|/",
+ "base": null,
+ "href": "file:///C:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/C:/",
+ "search": "",
+ "hash": ""
+ },
+ "# file URLs without base URL by Rimas Misevičius",
+ {
+ "input": "file:",
+ "base": null,
+ "href": "file:///",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:?q=v",
+ "base": null,
+ "href": "file:///?q=v",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "?q=v",
+ "hash": ""
+ },
+ {
+ "input": "file:#frag",
+ "base": null,
+ "href": "file:///#frag",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": "#frag"
+ },
+ "# file: drive letter cases from https://crbug.com/1078698",
+ {
+ "input": "file:///Y:",
+ "base": null,
+ "href": "file:///Y:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/Y:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///Y:/",
+ "base": null,
+ "href": "file:///Y:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/Y:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///./Y",
+ "base": null,
+ "href": "file:///Y",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/Y",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///./Y:",
+ "base": null,
+ "href": "file:///Y:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/Y:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\\\\\.\\Y:",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ "# file: drive letter cases from https://crbug.com/1078698 but lowercased",
+ {
+ "input": "file:///y:",
+ "base": null,
+ "href": "file:///y:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/y:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///y:/",
+ "base": null,
+ "href": "file:///y:/",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/y:/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///./y",
+ "base": null,
+ "href": "file:///y",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/y",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///./y:",
+ "base": null,
+ "href": "file:///y:",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/y:",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "\\\\\\.\\y:",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ "# Additional file URL tests for (https://github.com/whatwg/url/issues/405)",
+ {
+ "input": "file://localhost//a//../..//foo",
+ "base": null,
+ "href": "file://///foo",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "///foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://localhost////foo",
+ "base": null,
+ "href": "file://////foo",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "////foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:////foo",
+ "base": null,
+ "href": "file:////foo",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//foo",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///one/two",
+ "base": "file:///",
+ "href": "file:///one/two",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/one/two",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:////one/two",
+ "base": "file:///",
+ "href": "file:////one/two",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//one/two",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "//one/two",
+ "base": "file:///",
+ "href": "file://one/two",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "one",
+ "hostname": "one",
+ "port": "",
+ "pathname": "/two",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "///one/two",
+ "base": "file:///",
+ "href": "file:///one/two",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/one/two",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "////one/two",
+ "base": "file:///",
+ "href": "file:////one/two",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//one/two",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///.//",
+ "base": "file:////",
+ "href": "file:////",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ "File URL tests for https://github.com/whatwg/url/issues/549",
+ {
+ "input": "file:.//p",
+ "base": null,
+ "href": "file:////p",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//p",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:/.//p",
+ "base": null,
+ "href": "file:////p",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//p",
+ "search": "",
+ "hash": ""
+ },
+ "# IPv6 tests",
+ {
+ "input": "http://[1:0::]",
+ "base": "http://example.net/",
+ "href": "http://[1::]/",
+ "origin": "http://[1::]",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[1::]",
+ "hostname": "[1::]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[0:1:2:3:4:5:6:7:8]",
+ "base": "http://example.net/",
+ "failure": true
+ },
+ {
+ "input": "https://[0::0::0]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://[0:.0]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://[0:0:]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://[0:1.00.0.0.0]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://[0:1.290.0.0.0]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://[0:1.23.23]",
+ "base": null,
+ "failure": true
+ },
+ "# Empty host",
+ {
+ "input": "http://?",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://#",
+ "base": null,
+ "failure": true
+ },
+ "Port overflow (2^32 + 81)",
+ {
+ "input": "http://f:4294967377/c",
+ "base": "http://example.org/",
+ "failure": true
+ },
+ "Port overflow (2^64 + 81)",
+ {
+ "input": "http://f:18446744073709551697/c",
+ "base": "http://example.org/",
+ "failure": true
+ },
+ "Port overflow (2^128 + 81)",
+ {
+ "input": "http://f:340282366920938463463374607431768211537/c",
+ "base": "http://example.org/",
+ "failure": true
+ },
+ "# Non-special-URL path tests",
+ {
+ "input": "sc://ñ",
+ "base": null,
+ "href": "sc://%C3%B1",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1",
+ "hostname": "%C3%B1",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://ñ?x",
+ "base": null,
+ "href": "sc://%C3%B1?x",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1",
+ "hostname": "%C3%B1",
+ "port": "",
+ "pathname": "",
+ "search": "?x",
+ "hash": ""
+ },
+ {
+ "input": "sc://ñ#x",
+ "base": null,
+ "href": "sc://%C3%B1#x",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1",
+ "hostname": "%C3%B1",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "#x",
+ "base": "sc://ñ",
+ "href": "sc://%C3%B1#x",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1",
+ "hostname": "%C3%B1",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": "#x"
+ },
+ {
+ "input": "?x",
+ "base": "sc://ñ",
+ "href": "sc://%C3%B1?x",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "%C3%B1",
+ "hostname": "%C3%B1",
+ "port": "",
+ "pathname": "",
+ "search": "?x",
+ "hash": ""
+ },
+ {
+ "input": "sc://?",
+ "base": null,
+ "href": "sc://?",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "sc://#",
+ "base": null,
+ "href": "sc://#",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "///",
+ "base": "sc://x/",
+ "href": "sc:///",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "////",
+ "base": "sc://x/",
+ "href": "sc:////",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "////x/",
+ "base": "sc://x/",
+ "href": "sc:////x/",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//x/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "tftp://foobar.com/someconfig;mode=netascii",
+ "base": null,
+ "href": "tftp://foobar.com/someconfig;mode=netascii",
+ "origin": "null",
+ "protocol": "tftp:",
+ "username": "",
+ "password": "",
+ "host": "foobar.com",
+ "hostname": "foobar.com",
+ "port": "",
+ "pathname": "/someconfig;mode=netascii",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "telnet://user:pass@foobar.com:23/",
+ "base": null,
+ "href": "telnet://user:pass@foobar.com:23/",
+ "origin": "null",
+ "protocol": "telnet:",
+ "username": "user",
+ "password": "pass",
+ "host": "foobar.com:23",
+ "hostname": "foobar.com",
+ "port": "23",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "ut2004://10.10.10.10:7777/Index.ut2",
+ "base": null,
+ "href": "ut2004://10.10.10.10:7777/Index.ut2",
+ "origin": "null",
+ "protocol": "ut2004:",
+ "username": "",
+ "password": "",
+ "host": "10.10.10.10:7777",
+ "hostname": "10.10.10.10",
+ "port": "7777",
+ "pathname": "/Index.ut2",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+ "base": null,
+ "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+ "origin": "null",
+ "protocol": "redis:",
+ "username": "foo",
+ "password": "bar",
+ "host": "somehost:6379",
+ "hostname": "somehost",
+ "port": "6379",
+ "pathname": "/0",
+ "search": "?baz=bam&qux=baz",
+ "hash": ""
+ },
+ {
+ "input": "rsync://foo@host:911/sup",
+ "base": null,
+ "href": "rsync://foo@host:911/sup",
+ "origin": "null",
+ "protocol": "rsync:",
+ "username": "foo",
+ "password": "",
+ "host": "host:911",
+ "hostname": "host",
+ "port": "911",
+ "pathname": "/sup",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "git://github.com/foo/bar.git",
+ "base": null,
+ "href": "git://github.com/foo/bar.git",
+ "origin": "null",
+ "protocol": "git:",
+ "username": "",
+ "password": "",
+ "host": "github.com",
+ "hostname": "github.com",
+ "port": "",
+ "pathname": "/foo/bar.git",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "irc://myserver.com:6999/channel?passwd",
+ "base": null,
+ "href": "irc://myserver.com:6999/channel?passwd",
+ "origin": "null",
+ "protocol": "irc:",
+ "username": "",
+ "password": "",
+ "host": "myserver.com:6999",
+ "hostname": "myserver.com",
+ "port": "6999",
+ "pathname": "/channel",
+ "search": "?passwd",
+ "hash": ""
+ },
+ {
+ "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+ "base": null,
+ "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+ "origin": "null",
+ "protocol": "dns:",
+ "username": "",
+ "password": "",
+ "host": "fw.example.org:9999",
+ "hostname": "fw.example.org",
+ "port": "9999",
+ "pathname": "/foo.bar.org",
+ "search": "?type=TXT",
+ "hash": ""
+ },
+ {
+ "input": "ldap://localhost:389/ou=People,o=JNDITutorial",
+ "base": null,
+ "href": "ldap://localhost:389/ou=People,o=JNDITutorial",
+ "origin": "null",
+ "protocol": "ldap:",
+ "username": "",
+ "password": "",
+ "host": "localhost:389",
+ "hostname": "localhost",
+ "port": "389",
+ "pathname": "/ou=People,o=JNDITutorial",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "git+https://github.com/foo/bar",
+ "base": null,
+ "href": "git+https://github.com/foo/bar",
+ "origin": "null",
+ "protocol": "git+https:",
+ "username": "",
+ "password": "",
+ "host": "github.com",
+ "hostname": "github.com",
+ "port": "",
+ "pathname": "/foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "urn:ietf:rfc:2648",
+ "base": null,
+ "href": "urn:ietf:rfc:2648",
+ "origin": "null",
+ "protocol": "urn:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "ietf:rfc:2648",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "tag:joe@example.org,2001:foo/bar",
+ "base": null,
+ "href": "tag:joe@example.org,2001:foo/bar",
+ "origin": "null",
+ "protocol": "tag:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "joe@example.org,2001:foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ "Serialize /. in path",
+ {
+ "input": "non-spec:/.//",
+ "base": null,
+ "href": "non-spec:/.//",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-spec:/..//",
+ "base": null,
+ "href": "non-spec:/.//",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-spec:/a/..//",
+ "base": null,
+ "href": "non-spec:/.//",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-spec:/.//path",
+ "base": null,
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-spec:/..//path",
+ "base": null,
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-spec:/a/..//path",
+ "base": null,
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/.//path",
+ "base": "non-spec:/p",
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "/..//path",
+ "base": "non-spec:/p",
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "..//path",
+ "base": "non-spec:/p",
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "a/..//path",
+ "base": "non-spec:/p",
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "",
+ "base": "non-spec:/..//p",
+ "href": "non-spec:/.//p",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//p",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "path",
+ "base": "non-spec:/..//p",
+ "href": "non-spec:/.//path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "//path",
+ "search": "",
+ "hash": ""
+ },
+ "Do not serialize /. in path",
+ {
+ "input": "../path",
+ "base": "non-spec:/.//p",
+ "href": "non-spec:/path",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/path",
+ "search": "",
+ "hash": ""
+ },
+ "# percent encoded hosts in non-special-URLs",
+ {
+ "input": "non-special://%E2%80%A0/",
+ "base": null,
+ "href": "non-special://%E2%80%A0/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "%E2%80%A0",
+ "hostname": "%E2%80%A0",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://H%4fSt/path",
+ "base": null,
+ "href": "non-special://H%4fSt/path",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "H%4fSt",
+ "hostname": "H%4fSt",
+ "port": "",
+ "pathname": "/path",
+ "search": "",
+ "hash": ""
+ },
+ "# IPv6 in non-special-URLs",
+ {
+ "input": "non-special://[1:2:0:0:5:0:0:0]/",
+ "base": null,
+ "href": "non-special://[1:2:0:0:5::]/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "[1:2:0:0:5::]",
+ "hostname": "[1:2:0:0:5::]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://[1:2:0:0:0:0:0:3]/",
+ "base": null,
+ "href": "non-special://[1:2::3]/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "[1:2::3]",
+ "hostname": "[1:2::3]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://[1:2::3]:80/",
+ "base": null,
+ "href": "non-special://[1:2::3]:80/",
+ "protocol": "non-special:",
+ "username": "",
+ "password": "",
+ "host": "[1:2::3]:80",
+ "hostname": "[1:2::3]",
+ "port": "80",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "non-special://[:80/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+ "base": null,
+ "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+ "origin": "null",
+ "protocol": "blob:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "blob:",
+ "base": null,
+ "href": "blob:",
+ "origin": "null",
+ "protocol": "blob:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid IPv4 radix digits",
+ {
+ "input": "http://0x7f.0.0.0x7g",
+ "base": null,
+ "href": "http://0x7f.0.0.0x7g/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0x7f.0.0.0x7g",
+ "hostname": "0x7f.0.0.0x7g",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://0X7F.0.0.0X7G",
+ "base": null,
+ "href": "http://0x7f.0.0.0x7g/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "0x7f.0.0.0x7g",
+ "hostname": "0x7f.0.0.0x7g",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Invalid IPv4 portion of IPv6 address",
+ {
+ "input": "http://[::127.0.0.0.1]",
+ "base": null,
+ "failure": true
+ },
+ "Uncompressed IPv6 addresses with 0",
+ {
+ "input": "http://[0:1:0:1:0:1:0:1]",
+ "base": null,
+ "href": "http://[0:1:0:1:0:1:0:1]/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[0:1:0:1:0:1:0:1]",
+ "hostname": "[0:1:0:1:0:1:0:1]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://[1:0:1:0:1:0:1:0]",
+ "base": null,
+ "href": "http://[1:0:1:0:1:0:1:0]/",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "[1:0:1:0:1:0:1:0]",
+ "hostname": "[1:0:1:0:1:0:1:0]",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ "Percent-encoded query and fragment",
+ {
+ "input": "http://example.org/test?\u0022",
+ "base": null,
+ "href": "http://example.org/test?%22",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%22",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u0023",
+ "base": null,
+ "href": "http://example.org/test?#",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u003C",
+ "base": null,
+ "href": "http://example.org/test?%3C",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%3C",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u003E",
+ "base": null,
+ "href": "http://example.org/test?%3E",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%3E",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?\u2323",
+ "base": null,
+ "href": "http://example.org/test?%E2%8C%A3",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%E2%8C%A3",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?%23%23",
+ "base": null,
+ "href": "http://example.org/test?%23%23",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%23%23",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?%GH",
+ "base": null,
+ "href": "http://example.org/test?%GH",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?%GH",
+ "hash": ""
+ },
+ {
+ "input": "http://example.org/test?a#%EF",
+ "base": null,
+ "href": "http://example.org/test?a#%EF",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#%EF"
+ },
+ {
+ "input": "http://example.org/test?a#%GH",
+ "base": null,
+ "href": "http://example.org/test?a#%GH",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#%GH"
+ },
+ "URLs that require a non-about:blank base. (Also serve as invalid base tests.)",
+ {
+ "input": "a",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "a/",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "a//",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ "Bases that don't fail to parse but fail to be bases",
+ {
+ "input": "test-a-colon.html",
+ "base": "a:",
+ "failure": true
+ },
+ {
+ "input": "test-a-colon-b.html",
+ "base": "a:b",
+ "failure": true
+ },
+ "Other base URL tests, that must succeed",
+ {
+ "input": "test-a-colon-slash.html",
+ "base": "a:/",
+ "href": "a:/test-a-colon-slash.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test-a-colon-slash.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test-a-colon-slash-slash.html",
+ "base": "a://",
+ "href": "a:///test-a-colon-slash-slash.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test-a-colon-slash-slash.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test-a-colon-slash-b.html",
+ "base": "a:/b",
+ "href": "a:/test-a-colon-slash-b.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test-a-colon-slash-b.html",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "test-a-colon-slash-slash-b.html",
+ "base": "a://b",
+ "href": "a://b/test-a-colon-slash-slash-b.html",
+ "protocol": "a:",
+ "username": "",
+ "password": "",
+ "host": "b",
+ "hostname": "b",
+ "port": "",
+ "pathname": "/test-a-colon-slash-slash-b.html",
+ "search": "",
+ "hash": ""
+ },
+ "Null code point in fragment",
+ {
+ "input": "http://example.org/test?a#b\u0000c",
+ "base": null,
+ "href": "http://example.org/test?a#b%00c",
+ "protocol": "http:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#b%00c"
+ },
+ {
+ "input": "non-spec://example.org/test?a#b\u0000c",
+ "base": null,
+ "href": "non-spec://example.org/test?a#b%00c",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#b%00c"
+ },
+ {
+ "input": "non-spec:/test?a#b\u0000c",
+ "base": null,
+ "href": "non-spec:/test?a#b%00c",
+ "protocol": "non-spec:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "?a",
+ "hash": "#b%00c"
+ },
+ "First scheme char - not allowed: https://github.com/whatwg/url/issues/464",
+ {
+ "input": "10.0.0.7:8080/foo.html",
+ "base": "file:///some/dir/bar.html",
+ "href": "file:///some/dir/10.0.0.7:8080/foo.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/some/dir/10.0.0.7:8080/foo.html",
+ "search": "",
+ "hash": ""
+ },
+ "Subsequent scheme chars - not allowed",
+ {
+ "input": "a!@$*=/foo.html",
+ "base": "file:///some/dir/bar.html",
+ "href": "file:///some/dir/a!@$*=/foo.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/some/dir/a!@$*=/foo.html",
+ "search": "",
+ "hash": ""
+ },
+ "First and subsequent scheme chars - allowed",
+ {
+ "input": "a1234567890-+.:foo/bar",
+ "base": "http://example.com/dir/file",
+ "href": "a1234567890-+.:foo/bar",
+ "protocol": "a1234567890-+.:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "foo/bar",
+ "search": "",
+ "hash": ""
+ },
+ "IDNA ignored code points in file URLs hosts",
+ {
+ "input": "file://a\u00ADb/p",
+ "base": null,
+ "href": "file://ab/p",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "ab",
+ "hostname": "ab",
+ "port": "",
+ "pathname": "/p",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file://a%C2%ADb/p",
+ "base": null,
+ "href": "file://ab/p",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "ab",
+ "hostname": "ab",
+ "port": "",
+ "pathname": "/p",
+ "search": "",
+ "hash": ""
+ },
+ "IDNA hostnames which get mapped to 'localhost'",
+ {
+ "input": "file://loC𝐀𝐋𝐇𝐨𝐬𝐭/usr/bin",
+ "base": null,
+ "href": "file:///usr/bin",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/usr/bin",
+ "search": "",
+ "hash": ""
+ },
+ "Empty host after the domain to ASCII",
+ {
+ "input": "file://\u00ad/p",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://%C2%AD/p",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "file://xn--/p",
+ "base": null,
+ "failure": true
+ },
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1647058",
+ {
+ "input": "#link",
+ "base": "https://example.org/##link",
+ "href": "https://example.org/#link",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.org",
+ "hostname": "example.org",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": "#link"
+ },
+ "UTF-8 percent-encode of C0 control percent-encode set and supersets",
+ {
+ "input": "non-special:cannot-be-a-base-url-\u0000\u0001\u001F\u001E\u007E\u007F\u0080",
+ "base": null,
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80",
+ "origin": "null",
+ "password": "",
+ "pathname": "cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "https://www.example.com/path{\u007Fpath.html?query'\u007F=query#fragment<\u007Ffragment",
+ "base": null,
+ "hash": "#fragment%3C%7Ffragment",
+ "host": "www.example.com",
+ "hostname": "www.example.com",
+ "href": "https://www.example.com/path%7B%7Fpath.html?query%27%7F=query#fragment%3C%7Ffragment",
+ "origin": "https://www.example.com",
+ "password": "",
+ "pathname": "/path%7B%7Fpath.html",
+ "port": "",
+ "protocol": "https:",
+ "search": "?query%27%7F=query",
+ "username": ""
+ },
+ {
+ "input": "https://user:pass[\u007F@foo/bar",
+ "base": "http://example.org",
+ "hash": "",
+ "host": "foo",
+ "hostname": "foo",
+ "href": "https://user:pass%5B%7F@foo/bar",
+ "origin": "https://foo",
+ "password": "pass%5B%7F",
+ "pathname": "/bar",
+ "port": "",
+ "protocol": "https:",
+ "search": "",
+ "username": "user"
+ },
+ "Tests for the distinct percent-encode sets",
+ {
+ "input": "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/",
+ "origin": "null",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~"
+ },
+ {
+ "input": "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/",
+ "origin": "null",
+ "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~",
+ "pathname": "/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": "joe"
+ },
+ {
+ "input": "foo://!\"$%&'()*+,-.;=_`{}~/",
+ "base": null,
+ "hash": "",
+ "host": "!\"$%&'()*+,-.;=_`{}~",
+ "hostname": "!\"$%&'()*+,-.;=_`{}~",
+ "href": "foo://!\"$%&'()*+,-.;=_`{}~/",
+ "origin": "null",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~",
+ "origin": "null",
+ "password": "",
+ "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "foo://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+ "origin": "null",
+ "password": "",
+ "pathname": "/dir/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+ "username": ""
+ },
+ {
+ "input": "wss://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+ "base": null,
+ "hash": "",
+ "host": "host",
+ "hostname": "host",
+ "href": "wss://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+ "origin": "wss://host",
+ "password": "",
+ "pathname": "/dir/",
+ "port": "",
+ "protocol": "wss:",
+ "search": "?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+ "username": ""
+ },
+ {
+ "input": "foo://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+ "base": null,
+ "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+ "host": "host",
+ "hostname": "host",
+ "href": "foo://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+ "origin": "null",
+ "password": "",
+ "pathname": "/dir/",
+ "port": "",
+ "protocol": "foo:",
+ "search": "",
+ "username": ""
+ },
+ "Ensure that input schemes are not ignored when resolving non-special URLs",
+ {
+ "input": "abc:rootless",
+ "base": "abc://host/path",
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "abc:rootless",
+ "password": "",
+ "pathname": "rootless",
+ "port": "",
+ "protocol": "abc:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "abc:rootless",
+ "base": "abc:/path",
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "abc:rootless",
+ "password": "",
+ "pathname": "rootless",
+ "port": "",
+ "protocol": "abc:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "abc:rootless",
+ "base": "abc:path",
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "abc:rootless",
+ "password": "",
+ "pathname": "rootless",
+ "port": "",
+ "protocol": "abc:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "abc:/rooted",
+ "base": "abc://host/path",
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "abc:/rooted",
+ "password": "",
+ "pathname": "/rooted",
+ "port": "",
+ "protocol": "abc:",
+ "search": "",
+ "username": ""
+ },
+ "Empty query and fragment with blank should throw an error",
+ {
+ "input": "#",
+ "base": null,
+ "failure": true,
+ "relativeTo": "any-base"
+ },
+ {
+ "input": "?",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ "Last component looks like a number, but not valid IPv4",
+ {
+ "input": "http://1.2.3.4.5",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://1.2.3.4.5.",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://0..0x300/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://0..0x300./",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://256.256.256.256.256",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://256.256.256.256.256.",
+ "base": "http://other.com/",
+ "failure": true
+ },
+ {
+ "input": "http://1.2.3.08",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://1.2.3.08.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://1.2.3.09",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://09.2.3.4",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://09.2.3.4.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://01.2.3.4.5",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://01.2.3.4.5.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://0x100.2.3.4",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://0x100.2.3.4.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://0x1.2.3.4.5",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://0x1.2.3.4.5.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.1.2.3.4",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.1.2.3.4.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.2.3.4",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.2.3.4.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.09",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.09.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.0x4",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.0x4.",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.09..",
+ "base": null,
+ "hash": "",
+ "host": "foo.09..",
+ "hostname": "foo.09..",
+ "href": "http://foo.09../",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "http:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "http://0999999999999999999/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.0x",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "http://💩.123/",
+ "base": null,
+ "failure": true
+ },
+ "U+0000 and U+FFFF in various places",
+ {
+ "input": "https://\u0000y",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://x/\u0000y",
+ "base": null,
+ "hash": "",
+ "host": "x",
+ "hostname": "x",
+ "href": "https://x/%00y",
+ "password": "",
+ "pathname": "/%00y",
+ "port": "",
+ "protocol": "https:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "https://x/?\u0000y",
+ "base": null,
+ "hash": "",
+ "host": "x",
+ "hostname": "x",
+ "href": "https://x/?%00y",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "https:",
+ "search": "?%00y",
+ "username": ""
+ },
+ {
+ "input": "https://x/?#\u0000y",
+ "base": null,
+ "hash": "#%00y",
+ "host": "x",
+ "hostname": "x",
+ "href": "https://x/?#%00y",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "https:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "https://\uFFFFy",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://x/\uFFFFy",
+ "base": null,
+ "hash": "",
+ "host": "x",
+ "hostname": "x",
+ "href": "https://x/%EF%BF%BFy",
+ "password": "",
+ "pathname": "/%EF%BF%BFy",
+ "port": "",
+ "protocol": "https:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "https://x/?\uFFFFy",
+ "base": null,
+ "hash": "",
+ "host": "x",
+ "hostname": "x",
+ "href": "https://x/?%EF%BF%BFy",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "https:",
+ "search": "?%EF%BF%BFy",
+ "username": ""
+ },
+ {
+ "input": "https://x/?#\uFFFFy",
+ "base": null,
+ "hash": "#%EF%BF%BFy",
+ "host": "x",
+ "hostname": "x",
+ "href": "https://x/?#%EF%BF%BFy",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "https:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "non-special:\u0000y",
+ "base": null,
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:%00y",
+ "password": "",
+ "pathname": "%00y",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "non-special:x/\u0000y",
+ "base": null,
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:x/%00y",
+ "password": "",
+ "pathname": "x/%00y",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "non-special:x/?\u0000y",
+ "base": null,
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:x/?%00y",
+ "password": "",
+ "pathname": "x/",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "?%00y",
+ "username": ""
+ },
+ {
+ "input": "non-special:x/?#\u0000y",
+ "base": null,
+ "hash": "#%00y",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:x/?#%00y",
+ "password": "",
+ "pathname": "x/",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "non-special:\uFFFFy",
+ "base": null,
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:%EF%BF%BFy",
+ "password": "",
+ "pathname": "%EF%BF%BFy",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "non-special:x/\uFFFFy",
+ "base": null,
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:x/%EF%BF%BFy",
+ "password": "",
+ "pathname": "x/%EF%BF%BFy",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "non-special:x/?\uFFFFy",
+ "base": null,
+ "hash": "",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:x/?%EF%BF%BFy",
+ "password": "",
+ "pathname": "x/",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "?%EF%BF%BFy",
+ "username": ""
+ },
+ {
+ "input": "non-special:x/?#\uFFFFy",
+ "base": null,
+ "hash": "#%EF%BF%BFy",
+ "host": "",
+ "hostname": "",
+ "href": "non-special:x/?#%EF%BF%BFy",
+ "password": "",
+ "pathname": "x/",
+ "port": "",
+ "protocol": "non-special:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "",
+ "base": null,
+ "failure": true,
+ "relativeTo": "non-opaque-path-base"
+ },
+ {
+ "input": "https://example.com/\"quoted\"",
+ "base": null,
+ "hash": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "href": "https://example.com/%22quoted%22",
+ "origin": "https://example.com",
+ "password": "",
+ "pathname": "/%22quoted%22",
+ "port": "",
+ "protocol": "https:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "input": "https://a%C2%ADb/",
+ "base": null,
+ "hash": "",
+ "host": "ab",
+ "hostname": "ab",
+ "href": "https://ab/",
+ "origin": "https://ab",
+ "password": "",
+ "pathname": "/",
+ "port": "",
+ "protocol": "https:",
+ "search": "",
+ "username": ""
+ },
+ {
+ "comment": "Empty host after domain to ASCII",
+ "input": "https://\u00AD/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://%C2%AD/",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "https://xn--/",
+ "base": null,
+ "failure": true
+ },
+ "Non-special schemes that some implementations might incorrectly treat as special",
+ {
+ "input": "data://example.com:8080/pathname?search#hash",
+ "base": null,
+ "href": "data://example.com:8080/pathname?search#hash",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "example.com:8080",
+ "hostname": "example.com",
+ "port": "8080",
+ "pathname": "/pathname",
+ "search": "?search",
+ "hash": "#hash"
+ },
+ {
+ "input": "data:///test",
+ "base": null,
+ "href": "data:///test",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data://test/a/../b",
+ "base": null,
+ "href": "data://test/b",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/b",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "data://:443",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "data://test:test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "data://[:1]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "javascript://example.com:8080/pathname?search#hash",
+ "base": null,
+ "href": "javascript://example.com:8080/pathname?search#hash",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "example.com:8080",
+ "hostname": "example.com",
+ "port": "8080",
+ "pathname": "/pathname",
+ "search": "?search",
+ "hash": "#hash"
+ },
+ {
+ "input": "javascript:///test",
+ "base": null,
+ "href": "javascript:///test",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript://test/a/../b",
+ "base": null,
+ "href": "javascript://test/b",
+ "origin": "null",
+ "protocol": "javascript:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/b",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "javascript://:443",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "javascript://test:test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "javascript://[:1]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "mailto://example.com:8080/pathname?search#hash",
+ "base": null,
+ "href": "mailto://example.com:8080/pathname?search#hash",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "example.com:8080",
+ "hostname": "example.com",
+ "port": "8080",
+ "pathname": "/pathname",
+ "search": "?search",
+ "hash": "#hash"
+ },
+ {
+ "input": "mailto:///test",
+ "base": null,
+ "href": "mailto:///test",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto://test/a/../b",
+ "base": null,
+ "href": "mailto://test/b",
+ "origin": "null",
+ "protocol": "mailto:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/b",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "mailto://:443",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "mailto://test:test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "mailto://[:1]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "intent://example.com:8080/pathname?search#hash",
+ "base": null,
+ "href": "intent://example.com:8080/pathname?search#hash",
+ "origin": "null",
+ "protocol": "intent:",
+ "username": "",
+ "password": "",
+ "host": "example.com:8080",
+ "hostname": "example.com",
+ "port": "8080",
+ "pathname": "/pathname",
+ "search": "?search",
+ "hash": "#hash"
+ },
+ {
+ "input": "intent:///test",
+ "base": null,
+ "href": "intent:///test",
+ "origin": "null",
+ "protocol": "intent:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "intent://test/a/../b",
+ "base": null,
+ "href": "intent://test/b",
+ "origin": "null",
+ "protocol": "intent:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/b",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "intent://:443",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "intent://test:test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "intent://[:1]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "urn://example.com:8080/pathname?search#hash",
+ "base": null,
+ "href": "urn://example.com:8080/pathname?search#hash",
+ "origin": "null",
+ "protocol": "urn:",
+ "username": "",
+ "password": "",
+ "host": "example.com:8080",
+ "hostname": "example.com",
+ "port": "8080",
+ "pathname": "/pathname",
+ "search": "?search",
+ "hash": "#hash"
+ },
+ {
+ "input": "urn:///test",
+ "base": null,
+ "href": "urn:///test",
+ "origin": "null",
+ "protocol": "urn:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "urn://test/a/../b",
+ "base": null,
+ "href": "urn://test/b",
+ "origin": "null",
+ "protocol": "urn:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/b",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "urn://:443",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "urn://test:test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "urn://[:1]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "turn://example.com:8080/pathname?search#hash",
+ "base": null,
+ "href": "turn://example.com:8080/pathname?search#hash",
+ "origin": "null",
+ "protocol": "turn:",
+ "username": "",
+ "password": "",
+ "host": "example.com:8080",
+ "hostname": "example.com",
+ "port": "8080",
+ "pathname": "/pathname",
+ "search": "?search",
+ "hash": "#hash"
+ },
+ {
+ "input": "turn:///test",
+ "base": null,
+ "href": "turn:///test",
+ "origin": "null",
+ "protocol": "turn:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "turn://test/a/../b",
+ "base": null,
+ "href": "turn://test/b",
+ "origin": "null",
+ "protocol": "turn:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/b",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "turn://:443",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "turn://test:test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "turn://[:1]",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "stun://example.com:8080/pathname?search#hash",
+ "base": null,
+ "href": "stun://example.com:8080/pathname?search#hash",
+ "origin": "null",
+ "protocol": "stun:",
+ "username": "",
+ "password": "",
+ "host": "example.com:8080",
+ "hostname": "example.com",
+ "port": "8080",
+ "pathname": "/pathname",
+ "search": "?search",
+ "hash": "#hash"
+ },
+ {
+ "input": "stun:///test",
+ "base": null,
+ "href": "stun:///test",
+ "origin": "null",
+ "protocol": "stun:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/test",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "stun://test/a/../b",
+ "base": null,
+ "href": "stun://test/b",
+ "origin": "null",
+ "protocol": "stun:",
+ "username": "",
+ "password": "",
+ "host": "test",
+ "hostname": "test",
+ "port": "",
+ "pathname": "/b",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "stun://:443",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "stun://test:test",
+ "base": null,
+ "failure": true
+ },
+ {
+ "input": "stun://[:1]",
+ "base": null,
+ "failure": true
+ }
+]
diff --git a/netwerk/test/http3server/Cargo.toml b/netwerk/test/http3server/Cargo.toml
new file mode 100644
index 0000000000..24fdd28137
--- /dev/null
+++ b/netwerk/test/http3server/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "http3server"
+version = "0.1.1"
+authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+neqo-transport = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-common = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-http3 = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-qpack = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+mio = "0.6.17"
+mio-extras = "2.0.5"
+log = "0.4.0"
+base64 = "0.21"
+cfg-if = "1.0"
+http = "0.2.8"
+hyper = { version = "0.14", features = ["full"] }
+tokio = { version = "1", features = ["rt-multi-thread"] }
+mozilla-central-workspace-hack = { version = "0.1", features = ["http3server"], optional = true }
+
+[dependencies.neqo-crypto]
+tag = "v0.7.0"
+git = "https://github.com/mozilla/neqo"
+default-features = false
+features = ["gecko"]
+
+# Make sure to use bindgen's runtime-loading of libclang, as it allows for a wider range of clang versions to be used
+[build-dependencies]
+bindgen = {version = "0.69", default-features = false, features = ["runtime"] }
+
+[[bin]]
+name = "http3server"
+path = "src/main.rs"
diff --git a/netwerk/test/http3server/moz.build b/netwerk/test/http3server/moz.build
new file mode 100644
index 0000000000..8be2cdb31e
--- /dev/null
+++ b/netwerk/test/http3server/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ RUST_PROGRAMS += [
+ "http3server",
+ ]
+
+# Ideally, the build system would set @rpath to be @executable_path as
+# a default for this executable so that this addition to LDFLAGS would not be
+# needed here. Bug 1772575 is filed to implement that.
+if CONFIG["OS_ARCH"] == "Darwin":
+ LDFLAGS += ["-Wl,-rpath,@executable_path"]
+
+USE_LIBS += ["nss"]
diff --git a/netwerk/test/http3server/src/main.rs b/netwerk/test/http3server/src/main.rs
new file mode 100644
index 0000000000..a308f56442
--- /dev/null
+++ b/netwerk/test/http3server/src/main.rs
@@ -0,0 +1,1386 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![deny(warnings)]
+
+use base64::prelude::*;
+use neqo_common::{event::Provider, qdebug, qinfo, qtrace, Datagram, Header, IpTos};
+use neqo_crypto::{generate_ech_keys, init_db, AllowZeroRtt, AntiReplay};
+use neqo_http3::{
+ Error, Http3OrWebTransportStream, Http3Parameters, Http3Server, Http3ServerEvent,
+ WebTransportRequest, WebTransportServerEvent, WebTransportSessionAcceptAction,
+};
+use neqo_transport::server::{ActiveConnectionRef, Server};
+use neqo_transport::{
+ ConnectionEvent, ConnectionParameters, Output, RandomConnectionIdGenerator, StreamId,
+ StreamType,
+};
+use std::env;
+
+use std::cell::RefCell;
+use std::io;
+use std::path::PathBuf;
+use std::process::exit;
+use std::rc::Rc;
+use std::thread;
+use std::time::{Duration, Instant};
+
+use cfg_if::cfg_if;
+use core::fmt::Display;
+
+cfg_if! {
+ if #[cfg(not(target_os = "android"))] {
+ use std::sync::mpsc::{channel, Receiver, TryRecvError};
+ use hyper::body::HttpBody;
+ use hyper::header::{HeaderName, HeaderValue};
+ use hyper::{Body, Client, Method, Request};
+ }
+}
+
+use mio::net::UdpSocket;
+use mio::{Events, Poll, PollOpt, Ready, Token};
+use mio_extras::timer::{Builder, Timeout, Timer};
+use std::cmp::{max, min};
+use std::collections::hash_map::DefaultHasher;
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::hash::{Hash, Hasher};
+use std::mem;
+use std::net::SocketAddr;
+
+const MAX_TABLE_SIZE: u64 = 65536;
+const MAX_BLOCKED_STREAMS: u16 = 10;
+const PROTOCOLS: &[&str] = &["h3-29", "h3"];
+const TIMER_TOKEN: Token = Token(0xffff);
+const ECH_CONFIG_ID: u8 = 7;
+const ECH_PUBLIC_NAME: &str = "public.example";
+
+const HTTP_RESPONSE_WITH_WRONG_FRAME: &[u8] = &[
+ 0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x37, // headers
+ 0x0, 0x3, 0x61, 0x62, 0x63, // the first data frame
+ 0x3, 0x1, 0x5, // a cancel push frame that is not allowed
+];
+
+trait HttpServer: Display {
+ fn process(&mut self, dgram: Option<Datagram>) -> Output;
+ fn process_events(&mut self);
+ fn get_timeout(&self) -> Option<Duration> {
+ None
+ }
+}
+
+struct Http3TestServer {
+ server: Http3Server,
+ // This a map from a post request to amount of data ithas been received on the request.
+ // The respons will carry the amount of data received.
+ posts: HashMap<Http3OrWebTransportStream, usize>,
+ responses: HashMap<Http3OrWebTransportStream, Vec<u8>>,
+ current_connection_hash: u64,
+ sessions_to_close: HashMap<Instant, Vec<WebTransportRequest>>,
+ sessions_to_create_stream: Vec<(WebTransportRequest, StreamType, bool)>,
+ webtransport_bidi_stream: HashSet<Http3OrWebTransportStream>,
+ wt_unidi_conn_to_stream: HashMap<ActiveConnectionRef, Http3OrWebTransportStream>,
+ wt_unidi_echo_back: HashMap<Http3OrWebTransportStream, Http3OrWebTransportStream>,
+ received_datagram: Option<Vec<u8>>,
+}
+
+impl ::std::fmt::Display for Http3TestServer {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "{}", self.server)
+ }
+}
+
+impl Http3TestServer {
+ pub fn new(server: Http3Server) -> Self {
+ Self {
+ server,
+ posts: HashMap::new(),
+ responses: HashMap::new(),
+ current_connection_hash: 0,
+ sessions_to_close: HashMap::new(),
+ sessions_to_create_stream: Vec::new(),
+ webtransport_bidi_stream: HashSet::new(),
+ wt_unidi_conn_to_stream: HashMap::new(),
+ wt_unidi_echo_back: HashMap::new(),
+ received_datagram: None,
+ }
+ }
+
+ fn new_response(&mut self, mut stream: Http3OrWebTransportStream, mut data: Vec<u8>) {
+ if data.len() == 0 {
+ let _ = stream.stream_close_send();
+ return;
+ }
+ match stream.send_data(&data) {
+ Ok(sent) => {
+ if sent < data.len() {
+ self.responses.insert(stream, data.split_off(sent));
+ } else {
+ stream.stream_close_send().unwrap();
+ }
+ }
+ Err(e) => {
+ eprintln!("error is {:?}", e);
+ }
+ }
+ }
+
+ fn handle_stream_writable(&mut self, mut stream: Http3OrWebTransportStream) {
+ if let Some(data) = self.responses.get_mut(&stream) {
+ match stream.send_data(&data) {
+ Ok(sent) => {
+ if sent < data.len() {
+ let new_d = (*data).split_off(sent);
+ *data = new_d;
+ } else {
+ stream.stream_close_send().unwrap();
+ self.responses.remove(&stream);
+ }
+ }
+ Err(_) => {
+ eprintln!("Unexpected error");
+ }
+ }
+ }
+ }
+
+ fn maybe_close_session(&mut self) {
+ let now = Instant::now();
+ for (expires, sessions) in self.sessions_to_close.iter_mut() {
+ if *expires <= now {
+ for s in sessions.iter_mut() {
+ mem::drop(s.close_session(0, ""));
+ }
+ }
+ }
+ self.sessions_to_close.retain(|expires, _| *expires >= now);
+ }
+
+ fn maybe_create_wt_stream(&mut self) {
+ if self.sessions_to_create_stream.is_empty() {
+ return;
+ }
+ let tuple = self.sessions_to_create_stream.pop().unwrap();
+ let mut session = tuple.0;
+ let mut wt_server_stream = session.create_stream(tuple.1).unwrap();
+ if tuple.1 == StreamType::UniDi {
+ if tuple.2 {
+ wt_server_stream.send_data(b"qwerty").unwrap();
+ wt_server_stream.stream_close_send().unwrap();
+ } else {
+ // relaying Http3ServerEvent::Data to uni streams
+ // slows down netwerk/test/unit/test_webtransport_simple.js
+ // to the point of failure. Only do so when necessary.
+ self.wt_unidi_conn_to_stream
+ .insert(wt_server_stream.conn.clone(), wt_server_stream);
+ }
+ } else {
+ if tuple.2 {
+ wt_server_stream.send_data(b"asdfg").unwrap();
+ wt_server_stream.stream_close_send().unwrap();
+ wt_server_stream
+ .stream_stop_sending(Error::HttpNoError.code())
+ .unwrap();
+ } else {
+ self.webtransport_bidi_stream.insert(wt_server_stream);
+ }
+ }
+ }
+}
+
+impl HttpServer for Http3TestServer {
+ fn process(&mut self, dgram: Option<Datagram>) -> Output {
+ self.server.process(dgram.as_ref(), Instant::now())
+ }
+
+ fn process_events(&mut self) {
+ self.maybe_close_session();
+ self.maybe_create_wt_stream();
+
+ while let Some(event) = self.server.next_event() {
+ qtrace!("Event: {:?}", event);
+ match event {
+ Http3ServerEvent::Headers {
+ mut stream,
+ headers,
+ fin,
+ } => {
+ qtrace!("Headers (request={} fin={}): {:?}", stream, fin, headers);
+
+ // Some responses do not have content-type. This is on purpose to exercise
+ // UnknownDecoder code.
+ let default_ret = b"Hello World".to_vec();
+ let default_headers = vec![
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-length", default_ret.len().to_string()),
+ Header::new(
+ "x-http3-conn-hash",
+ self.current_connection_hash.to_string(),
+ ),
+ ];
+
+ let path_hdr = headers.iter().find(|&h| h.name() == ":path");
+ match path_hdr {
+ Some(ph) if !ph.value().is_empty() => {
+ let path = ph.value();
+ qtrace!("Serve request {}", path);
+ if path == "/Response421" {
+ let response_body = b"0123456789".to_vec();
+ stream
+ .send_headers(&[
+ Header::new(":status", "421"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-type", "text/plain"),
+ Header::new(
+ "content-length",
+ response_body.len().to_string(),
+ ),
+ ])
+ .unwrap();
+ self.new_response(stream, response_body);
+ } else if path == "/RequestCancelled" {
+ stream
+ .stream_stop_sending(Error::HttpRequestCancelled.code())
+ .unwrap();
+ stream
+ .stream_reset_send(Error::HttpRequestCancelled.code())
+ .unwrap();
+ } else if path == "/VersionFallback" {
+ stream
+ .stream_stop_sending(Error::HttpVersionFallback.code())
+ .unwrap();
+ stream
+ .stream_reset_send(Error::HttpVersionFallback.code())
+ .unwrap();
+ } else if path == "/EarlyResponse" {
+ stream
+ .stream_stop_sending(Error::HttpNoError.code())
+ .unwrap();
+ } else if path == "/RequestRejected" {
+ stream
+ .stream_stop_sending(Error::HttpRequestRejected.code())
+ .unwrap();
+ stream
+ .stream_reset_send(Error::HttpRequestRejected.code())
+ .unwrap();
+ } else if path == "/.well-known/http-opportunistic" {
+ let host_hdr = headers.iter().find(|&h| h.name() == ":authority");
+ match host_hdr {
+ Some(host) if !host.value().is_empty() => {
+ let mut content = b"[\"http://".to_vec();
+ content.extend(host.value().as_bytes());
+ content.extend(b"\"]".to_vec());
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-type", "application/json"),
+ Header::new(
+ "content-length",
+ content.len().to_string(),
+ ),
+ ])
+ .unwrap();
+ self.new_response(stream, content);
+ }
+ _ => {
+ stream.send_headers(&default_headers).unwrap();
+ self.new_response(stream, default_ret);
+ }
+ }
+ } else if path == "/no_body" {
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ ])
+ .unwrap();
+ stream.stream_close_send().unwrap();
+ } else if path == "/no_content_length" {
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ ])
+ .unwrap();
+ self.new_response(stream, vec![b'a'; 4000]);
+ } else if path == "/content_length_smaller" {
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-type", "text/plain"),
+ Header::new("content-length", 4000.to_string()),
+ ])
+ .unwrap();
+ self.new_response(stream, vec![b'a'; 8000]);
+ } else if path == "/post" {
+ // Read all data before responding.
+ self.posts.insert(stream, 0);
+ } else if path == "/priority_mirror" {
+ if let Some(priority) =
+ headers.iter().find(|h| h.name() == "priority")
+ {
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-type", "text/plain"),
+ Header::new("priority-mirror", priority.value()),
+ Header::new(
+ "content-length",
+ priority.value().len().to_string(),
+ ),
+ ])
+ .unwrap();
+ self.new_response(stream, priority.value().as_bytes().to_vec());
+ } else {
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ ])
+ .unwrap();
+ stream.stream_close_send().unwrap();
+ }
+ } else if path == "/103_response" {
+ if let Some(early_hint) =
+ headers.iter().find(|h| h.name() == "link-to-set")
+ {
+ for l in early_hint.value().split(',') {
+ stream
+ .send_headers(&[
+ Header::new(":status", "103"),
+ Header::new("link", l),
+ ])
+ .unwrap();
+ }
+ }
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-length", "0"),
+ ])
+ .unwrap();
+ stream.stream_close_send().unwrap();
+ } else if path == "/get_webtransport_datagram" {
+ if let Some(vec_ref) = self.received_datagram.as_ref() {
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new(
+ "content-length",
+ vec_ref.len().to_string(),
+ ),
+ ])
+ .unwrap();
+ self.new_response(stream, vec_ref.to_vec());
+ self.received_datagram = None;
+ } else {
+ stream
+ .send_headers(&[
+ Header::new(":status", "404"),
+ Header::new("cache-control", "no-cache"),
+ ])
+ .unwrap();
+ stream.stream_close_send().unwrap();
+ }
+ } else {
+ match path.trim_matches(|p| p == '/').parse::<usize>() {
+ Ok(v) => {
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-type", "text/plain"),
+ Header::new("content-length", v.to_string()),
+ ])
+ .unwrap();
+ self.new_response(stream, vec![b'a'; v]);
+ }
+ Err(_) => {
+ stream.send_headers(&default_headers).unwrap();
+ self.new_response(stream, default_ret);
+ }
+ }
+ }
+ }
+ _ => {
+ stream.send_headers(&default_headers).unwrap();
+ self.new_response(stream, default_ret);
+ }
+ }
+ }
+ Http3ServerEvent::Data {
+ mut stream,
+ data,
+ fin,
+ } => {
+ // echo bidirectional input back to client
+ if self.webtransport_bidi_stream.contains(&stream) {
+ if stream.handler.borrow().state().active() {
+ self.new_response(stream, data);
+ }
+ break;
+ }
+
+ // echo unidirectional input to back to client
+ // need to close or we hang
+ if self.wt_unidi_echo_back.contains_key(&stream) {
+ let mut echo_back = self.wt_unidi_echo_back.remove(&stream).unwrap();
+ echo_back.send_data(&data).unwrap();
+ echo_back.stream_close_send().unwrap();
+ break;
+ }
+
+ if let Some(r) = self.posts.get_mut(&stream) {
+ *r += data.len();
+ }
+ if fin {
+ if let Some(r) = self.posts.remove(&stream) {
+ let default_ret = b"Hello World".to_vec();
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("x-data-received-length", r.to_string()),
+ Header::new("content-length", default_ret.len().to_string()),
+ ])
+ .unwrap();
+ self.new_response(stream, default_ret);
+ }
+ }
+ }
+ Http3ServerEvent::DataWritable { stream } => self.handle_stream_writable(stream),
+ Http3ServerEvent::StateChange { conn, state } => {
+ if matches!(state, neqo_http3::Http3State::Connected) {
+ let mut h = DefaultHasher::new();
+ conn.hash(&mut h);
+ self.current_connection_hash = h.finish();
+ }
+ }
+ Http3ServerEvent::PriorityUpdate { .. } => {}
+ Http3ServerEvent::StreamReset { stream, error } => {
+ qtrace!("Http3ServerEvent::StreamReset {:?} {:?}", stream, error);
+ }
+ Http3ServerEvent::StreamStopSending { stream, error } => {
+ qtrace!(
+ "Http3ServerEvent::StreamStopSending {:?} {:?}",
+ stream,
+ error
+ );
+ }
+ Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession {
+ mut session,
+ headers,
+ }) => {
+ qdebug!(
+ "WebTransportServerEvent::NewSession {:?} {:?}",
+ session,
+ headers
+ );
+ let path_hdr = headers.iter().find(|&h| h.name() == ":path");
+ match path_hdr {
+ Some(ph) if !ph.value().is_empty() => {
+ let path = ph.value();
+ qtrace!("Serve request {}", path);
+ if path == "/success" {
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ } else if path == "/redirect" {
+ session
+ .response(&WebTransportSessionAcceptAction::Reject(
+ [
+ Header::new(":status", "302"),
+ Header::new("location", "/"),
+ ]
+ .to_vec(),
+ ))
+ .unwrap();
+ } else if path == "/reject" {
+ session
+ .response(&WebTransportSessionAcceptAction::Reject(
+ [Header::new(":status", "404")].to_vec(),
+ ))
+ .unwrap();
+ } else if path == "/closeafter0ms" {
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ let now = Instant::now();
+ if !self.sessions_to_close.contains_key(&now) {
+ self.sessions_to_close.insert(now, Vec::new());
+ }
+ self.sessions_to_close.get_mut(&now).unwrap().push(session);
+ } else if path == "/closeafter100ms" {
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ let expires = Instant::now() + Duration::from_millis(100);
+ if !self.sessions_to_close.contains_key(&expires) {
+ self.sessions_to_close.insert(expires, Vec::new());
+ }
+ self.sessions_to_close
+ .get_mut(&expires)
+ .unwrap()
+ .push(session);
+ } else if path == "/create_unidi_stream" {
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ self.sessions_to_create_stream.push((
+ session,
+ StreamType::UniDi,
+ false,
+ ));
+ } else if path == "/create_unidi_stream_and_hello" {
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ self.sessions_to_create_stream.push((
+ session,
+ StreamType::UniDi,
+ true,
+ ));
+ } else if path == "/create_bidi_stream" {
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ self.sessions_to_create_stream.push((
+ session,
+ StreamType::BiDi,
+ false,
+ ));
+ } else if path == "/create_bidi_stream_and_hello" {
+ self.webtransport_bidi_stream.clear();
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ self.sessions_to_create_stream.push((
+ session,
+ StreamType::BiDi,
+ true,
+ ));
+ } else {
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ }
+ }
+ _ => {
+ session
+ .response(&WebTransportSessionAcceptAction::Reject(
+ [Header::new(":status", "404")].to_vec(),
+ ))
+ .unwrap();
+ }
+ }
+ }
+ Http3ServerEvent::WebTransport(WebTransportServerEvent::SessionClosed {
+ session,
+ reason,
+ headers: _,
+ }) => {
+ qdebug!(
+ "WebTransportServerEvent::SessionClosed {:?} {:?}",
+ session,
+ reason
+ );
+ }
+ Http3ServerEvent::WebTransport(WebTransportServerEvent::NewStream(stream)) => {
+ // new stream could be from client-outgoing unidirectional
+ // or bidirectional
+ if !stream.stream_info.is_http() {
+ if stream.stream_id().is_bidi() {
+ self.webtransport_bidi_stream.insert(stream);
+ } else {
+ // Newly created stream happens on same connection
+ // as the stream creation for client's incoming stream.
+ // Link the streams with map for echo back
+ if self.wt_unidi_conn_to_stream.contains_key(&stream.conn) {
+ let s = self.wt_unidi_conn_to_stream.remove(&stream.conn).unwrap();
+ self.wt_unidi_echo_back.insert(stream, s);
+ }
+ }
+ }
+ }
+ Http3ServerEvent::WebTransport(WebTransportServerEvent::Datagram {
+ session,
+ datagram,
+ }) => {
+ qdebug!(
+ "WebTransportServerEvent::Datagram {:?} {:?}",
+ session,
+ datagram
+ );
+ self.received_datagram = Some(datagram);
+ }
+ }
+ }
+ }
+
+ fn get_timeout(&self) -> Option<Duration> {
+ if let Some(next) = self.sessions_to_close.keys().min() {
+ return Some(max(*next - Instant::now(), Duration::from_millis(0)));
+ }
+ None
+ }
+}
+
+impl HttpServer for Server {
+ fn process(&mut self, dgram: Option<Datagram>) -> Output {
+ self.process(dgram.as_ref(), Instant::now())
+ }
+
+ fn process_events(&mut self) {
+ let active_conns = self.active_connections();
+ for mut acr in active_conns {
+ loop {
+ let event = match acr.borrow_mut().next_event() {
+ None => break,
+ Some(e) => e,
+ };
+ match event {
+ ConnectionEvent::RecvStreamReadable { stream_id } => {
+ if stream_id.is_bidi() && stream_id.is_client_initiated() {
+ // We are only interesting in request streams
+ acr.borrow_mut()
+ .stream_send(stream_id, HTTP_RESPONSE_WITH_WRONG_FRAME)
+ .expect("Read should succeed");
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+}
+
+struct Http3ProxyServer {
+ server: Http3Server,
+ responses: HashMap<Http3OrWebTransportStream, Vec<u8>>,
+ server_port: i32,
+ request_header: HashMap<StreamId, Vec<Header>>,
+ request_body: HashMap<StreamId, Vec<u8>>,
+ #[cfg(not(target_os = "android"))]
+ stream_map: HashMap<StreamId, Http3OrWebTransportStream>,
+ #[cfg(not(target_os = "android"))]
+ response_to_send: HashMap<StreamId, Receiver<(Vec<Header>, Vec<u8>)>>,
+}
+
+impl ::std::fmt::Display for Http3ProxyServer {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "{}", self.server)
+ }
+}
+
+impl Http3ProxyServer {
+ pub fn new(server: Http3Server, server_port: i32) -> Self {
+ Self {
+ server,
+ responses: HashMap::new(),
+ server_port,
+ request_header: HashMap::new(),
+ request_body: HashMap::new(),
+ #[cfg(not(target_os = "android"))]
+ stream_map: HashMap::new(),
+ #[cfg(not(target_os = "android"))]
+ response_to_send: HashMap::new(),
+ }
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn new_response(&mut self, mut stream: Http3OrWebTransportStream, mut data: Vec<u8>) {
+ if data.len() == 0 {
+ let _ = stream.stream_close_send();
+ return;
+ }
+ match stream.send_data(&data) {
+ Ok(sent) => {
+ if sent < data.len() {
+ self.responses.insert(stream, data.split_off(sent));
+ } else {
+ stream.stream_close_send().unwrap();
+ }
+ }
+ Err(e) => {
+ eprintln!("error is {:?}, stream will be reset", e);
+ let _ = stream.stream_reset_send(Error::HttpRequestCancelled.code());
+ }
+ }
+ }
+
+ fn handle_stream_writable(&mut self, mut stream: Http3OrWebTransportStream) {
+ if let Some(data) = self.responses.get_mut(&stream) {
+ match stream.send_data(&data) {
+ Ok(sent) => {
+ if sent < data.len() {
+ let new_d = (*data).split_off(sent);
+ *data = new_d;
+ } else {
+ stream.stream_close_send().unwrap();
+ self.responses.remove(&stream);
+ }
+ }
+ Err(_) => {
+ eprintln!("Unexpected error");
+ }
+ }
+ }
+ }
+
+ #[cfg(not(target_os = "android"))]
+ async fn fetch_url(
+ request: hyper::Request<Body>,
+ out_header: &mut Vec<Header>,
+ out_body: &mut Vec<u8>,
+ ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+ let client = Client::new();
+ let mut resp = client.request(request).await?;
+ out_header.push(Header::new(":status", resp.status().as_str()));
+ for (key, value) in resp.headers() {
+ out_header.push(Header::new(
+ key.as_str().to_ascii_lowercase(),
+ match value.to_str() {
+ Ok(str) => str,
+ _ => "",
+ },
+ ));
+ }
+
+ while let Some(chunk) = resp.body_mut().data().await {
+ match chunk {
+ Ok(data) => {
+ out_body.append(&mut data.to_vec());
+ }
+ _ => {}
+ }
+ }
+
+ Ok(())
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn fetch(
+ &mut self,
+ mut stream: Http3OrWebTransportStream,
+ request_headers: &Vec<Header>,
+ request_body: Vec<u8>,
+ ) {
+ let mut request: hyper::Request<Body> = Request::default();
+ let mut path = String::new();
+ for hdr in request_headers.iter() {
+ match hdr.name() {
+ ":method" => {
+ *request.method_mut() = Method::from_bytes(hdr.value().as_bytes()).unwrap();
+ }
+ ":scheme" => {}
+ ":authority" => {
+ request.headers_mut().insert(
+ hyper::header::HOST,
+ HeaderValue::from_str(hdr.value()).unwrap(),
+ );
+ }
+ ":path" => {
+ path = String::from(hdr.value());
+ }
+ _ => {
+ if let Ok(hdr_name) = HeaderName::from_lowercase(hdr.name().as_bytes()) {
+ request
+ .headers_mut()
+ .insert(hdr_name, HeaderValue::from_str(hdr.value()).unwrap());
+ }
+ }
+ }
+ }
+ *request.body_mut() = Body::from(request_body);
+ *request.uri_mut() =
+ match format!("http://127.0.0.1:{}{}", self.server_port.to_string(), path).parse() {
+ Ok(uri) => uri,
+ _ => {
+ eprintln!("invalid uri: {}", path);
+ stream
+ .send_headers(&[
+ Header::new(":status", "400"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-length", "0"),
+ ])
+ .unwrap();
+ return;
+ }
+ };
+ qtrace!("request header: {:?}", request);
+
+ let (sender, receiver) = channel();
+ thread::spawn(move || {
+ let rt = tokio::runtime::Runtime::new().unwrap();
+ let mut h: Vec<Header> = Vec::new();
+ let mut data: Vec<u8> = Vec::new();
+ let _ = rt.block_on(Self::fetch_url(request, &mut h, &mut data));
+ qtrace!("response headers: {:?}", h);
+ qtrace!("res data: {:02X?}", data);
+
+ match sender.send((h, data)) {
+ Ok(()) => {}
+ _ => {
+ eprintln!("sender.send failed");
+ }
+ }
+ });
+
+ self.response_to_send.insert(stream.stream_id(), receiver);
+ self.stream_map.insert(stream.stream_id(), stream);
+ }
+
+ #[cfg(target_os = "android")]
+ fn fetch(
+ &mut self,
+ mut _stream: Http3OrWebTransportStream,
+ _request_headers: &Vec<Header>,
+ _request_body: Vec<u8>,
+ ) {
+ // do nothing
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn maybe_process_response(&mut self) {
+ let mut data_to_send = HashMap::new();
+ self.response_to_send
+ .retain(|id, receiver| match receiver.try_recv() {
+ Ok((headers, body)) => {
+ data_to_send.insert(*id, (headers.clone(), body.clone()));
+ false
+ }
+ Err(TryRecvError::Empty) => true,
+ Err(TryRecvError::Disconnected) => false,
+ });
+ while let Some(id) = data_to_send.keys().next().cloned() {
+ let mut stream = self.stream_map.remove(&id).unwrap();
+ let (header, data) = data_to_send.remove(&id).unwrap();
+ qtrace!("response headers: {:?}", header);
+ match stream.send_headers(&header) {
+ Ok(()) => {
+ self.new_response(stream, data);
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+impl HttpServer for Http3ProxyServer {
+ fn process(&mut self, dgram: Option<Datagram>) -> Output {
+ self.server.process(dgram.as_ref(), Instant::now())
+ }
+
+ fn process_events(&mut self) {
+ #[cfg(not(target_os = "android"))]
+ self.maybe_process_response();
+ while let Some(event) = self.server.next_event() {
+ qtrace!("Event: {:?}", event);
+ match event {
+ Http3ServerEvent::Headers {
+ mut stream,
+ headers,
+ fin: _,
+ } => {
+ qtrace!("Headers {:?}", headers);
+ if self.server_port != -1 {
+ let method_hdr = headers.iter().find(|&h| h.name() == ":method");
+ match method_hdr {
+ Some(method) => match method.value() {
+ "POST" => {
+ let content_length =
+ headers.iter().find(|&h| h.name() == "content-length");
+ if let Some(length_str) = content_length {
+ if let Ok(len) = length_str.value().parse::<u32>() {
+ if len > 0 {
+ self.request_header
+ .insert(stream.stream_id(), headers);
+ self.request_body
+ .insert(stream.stream_id(), Vec::new());
+ } else {
+ self.fetch(stream, &headers, b"".to_vec());
+ }
+ }
+ }
+ }
+ _ => {
+ self.fetch(stream, &headers, b"".to_vec());
+ }
+ },
+ _ => {}
+ }
+ } else {
+ let path_hdr = headers.iter().find(|&h| h.name() == ":path");
+ match path_hdr {
+ Some(ph) if !ph.value().is_empty() => {
+ let path = ph.value();
+ match &path[..6] {
+ "/port?" => {
+ let port = path[6..].parse::<i32>();
+ if let Ok(port) = port {
+ qtrace!("got port {}", port);
+ self.server_port = port;
+ }
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ stream
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("cache-control", "no-cache"),
+ Header::new("content-length", "0"),
+ ])
+ .unwrap();
+ }
+ }
+ Http3ServerEvent::Data {
+ stream,
+ mut data,
+ fin,
+ } => {
+ if let Some(d) = self.request_body.get_mut(&stream.stream_id()) {
+ d.append(&mut data);
+ }
+ if fin {
+ if let Some(d) = self.request_body.remove(&stream.stream_id()) {
+ let headers = self.request_header.remove(&stream.stream_id()).unwrap();
+ self.fetch(stream, &headers, d);
+ }
+ }
+ }
+ Http3ServerEvent::DataWritable { stream } => self.handle_stream_writable(stream),
+ Http3ServerEvent::StateChange { .. } | Http3ServerEvent::PriorityUpdate { .. } => {}
+ Http3ServerEvent::StreamReset { stream, error } => {
+ qtrace!("Http3ServerEvent::StreamReset {:?} {:?}", stream, error);
+ }
+ Http3ServerEvent::StreamStopSending { stream, error } => {
+ qtrace!(
+ "Http3ServerEvent::StreamStopSending {:?} {:?}",
+ stream,
+ error
+ );
+ }
+ Http3ServerEvent::WebTransport(_) => {}
+ }
+ }
+ }
+}
+
+#[derive(Default)]
+struct NonRespondingServer {}
+
+impl ::std::fmt::Display for NonRespondingServer {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "NonRespondingServer")
+ }
+}
+
+impl HttpServer for NonRespondingServer {
+ fn process(&mut self, _dgram: Option<Datagram>) -> Output {
+ Output::None
+ }
+
+ fn process_events(&mut self) {}
+}
+
+fn emit_packet(socket: &UdpSocket, out_dgram: Datagram) {
+ let res = match socket.send_to(&out_dgram, &out_dgram.destination()) {
+ Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => 0,
+ Err(err) => {
+ eprintln!("UDP send error: {:?}", err);
+ exit(1);
+ }
+ Ok(res) => res,
+ };
+ if res != out_dgram.len() {
+ qinfo!("Unable to send all {} bytes of datagram", out_dgram.len());
+ }
+}
+
+fn process(
+ server: &mut dyn HttpServer,
+ svr_timeout: &mut Option<Timeout>,
+ inx: usize,
+ dgram: Option<Datagram>,
+ timer: &mut Timer<usize>,
+ socket: &mut UdpSocket,
+) -> bool {
+ match server.process(dgram) {
+ Output::Datagram(dgram) => {
+ emit_packet(socket, dgram);
+ true
+ }
+ Output::Callback(mut new_timeout) => {
+ if let Some(t) = server.get_timeout() {
+ new_timeout = min(new_timeout, t);
+ }
+ if let Some(svr_timeout) = svr_timeout {
+ timer.cancel_timeout(svr_timeout);
+ }
+
+ qinfo!("Setting timeout of {:?} for {}", new_timeout, server);
+ if new_timeout > Duration::from_secs(1) {
+ new_timeout = Duration::from_millis(500);
+ }
+ *svr_timeout = Some(timer.set_timeout(new_timeout, inx));
+ false
+ }
+ Output::None => {
+ qdebug!("Output::None");
+ false
+ }
+ }
+}
+
+fn read_dgram(
+ socket: &mut UdpSocket,
+ local_address: &SocketAddr,
+) -> Result<Option<Datagram>, io::Error> {
+ let buf = &mut [0u8; 2048];
+ let res = socket.recv_from(&mut buf[..]);
+ if let Some(err) = res.as_ref().err() {
+ if err.kind() != io::ErrorKind::WouldBlock {
+ eprintln!("UDP recv error: {:?}", err);
+ }
+ return Ok(None);
+ };
+
+ let (sz, remote_addr) = res.unwrap();
+ if sz == buf.len() {
+ eprintln!("Might have received more than {} bytes", buf.len());
+ }
+
+ if sz == 0 {
+ eprintln!("zero length datagram received?");
+ Ok(None)
+ } else {
+ Ok(Some(Datagram::new(
+ remote_addr,
+ *local_address,
+ IpTos::default(),
+ None,
+ &buf[..sz],
+ )))
+ }
+}
+
+enum ServerType {
+ Http3,
+ Http3Fail,
+ Http3NoResponse,
+ Http3Ech,
+ Http3Proxy,
+}
+
+struct ServersRunner {
+ hosts: Vec<SocketAddr>,
+ poll: Poll,
+ sockets: Vec<UdpSocket>,
+ servers: HashMap<SocketAddr, (Box<dyn HttpServer>, Option<Timeout>)>,
+ timer: Timer<usize>,
+ active_servers: HashSet<usize>,
+ ech_config: Vec<u8>,
+}
+
+impl ServersRunner {
+ pub fn new() -> Result<Self, io::Error> {
+ Ok(Self {
+ hosts: Vec::new(),
+ poll: Poll::new()?,
+ sockets: Vec::new(),
+ servers: HashMap::new(),
+ timer: Builder::default()
+ .tick_duration(Duration::from_millis(1))
+ .build::<usize>(),
+ active_servers: HashSet::new(),
+ ech_config: Vec::new(),
+ })
+ }
+
+ pub fn init(&mut self) {
+ self.add_new_socket(0, ServerType::Http3, 0);
+ self.add_new_socket(1, ServerType::Http3Fail, 0);
+ self.add_new_socket(2, ServerType::Http3Ech, 0);
+
+ let proxy_port = match env::var("MOZ_HTTP3_PROXY_PORT") {
+ Ok(val) => val.parse::<u16>().unwrap(),
+ _ => 0,
+ };
+ self.add_new_socket(3, ServerType::Http3Proxy, proxy_port);
+ self.add_new_socket(5, ServerType::Http3NoResponse, 0);
+
+ println!(
+ "HTTP3 server listening on ports {}, {}, {}, {} and {}. EchConfig is @{}@",
+ self.hosts[0].port(),
+ self.hosts[1].port(),
+ self.hosts[2].port(),
+ self.hosts[3].port(),
+ self.hosts[4].port(),
+ BASE64_STANDARD.encode(&self.ech_config)
+ );
+ self.poll
+ .register(&self.timer, TIMER_TOKEN, Ready::readable(), PollOpt::edge())
+ .unwrap();
+ }
+
+ fn add_new_socket(&mut self, count: usize, server_type: ServerType, port: u16) -> u16 {
+ let addr = format!("127.0.0.1:{}", port).parse().unwrap();
+
+ let socket = match UdpSocket::bind(&addr) {
+ Err(err) => {
+ eprintln!("Unable to bind UDP socket: {}", err);
+ exit(1)
+ }
+ Ok(s) => s,
+ };
+
+ let local_addr = match socket.local_addr() {
+ Err(err) => {
+ eprintln!("Socket local address not bound: {}", err);
+ exit(1)
+ }
+ Ok(s) => s,
+ };
+
+ self.hosts.push(local_addr);
+
+ self.poll
+ .register(
+ &socket,
+ Token(count),
+ Ready::readable() | Ready::writable(),
+ PollOpt::edge(),
+ )
+ .unwrap();
+
+ self.sockets.push(socket);
+ let server = self.create_server(server_type);
+ self.servers.insert(local_addr, (server, None));
+ local_addr.port()
+ }
+
+ fn create_server(&mut self, server_type: ServerType) -> Box<dyn HttpServer> {
+ let anti_replay = AntiReplay::new(Instant::now(), Duration::from_secs(10), 7, 14)
+ .expect("unable to setup anti-replay");
+ let cid_mgr = Rc::new(RefCell::new(RandomConnectionIdGenerator::new(10)));
+
+ match server_type {
+ ServerType::Http3 => Box::new(Http3TestServer::new(
+ Http3Server::new(
+ Instant::now(),
+ &[" HTTP2 Test Cert"],
+ PROTOCOLS,
+ anti_replay,
+ cid_mgr,
+ Http3Parameters::default()
+ .max_table_size_encoder(MAX_TABLE_SIZE)
+ .max_table_size_decoder(MAX_TABLE_SIZE)
+ .max_blocked_streams(MAX_BLOCKED_STREAMS)
+ .webtransport(true)
+ .connection_parameters(ConnectionParameters::default().datagram_size(1200)),
+ None,
+ )
+ .expect("We cannot make a server!"),
+ )),
+ ServerType::Http3Fail => Box::new(
+ Server::new(
+ Instant::now(),
+ &[" HTTP2 Test Cert"],
+ PROTOCOLS,
+ anti_replay,
+ Box::new(AllowZeroRtt {}),
+ cid_mgr,
+ ConnectionParameters::default(),
+ )
+ .expect("We cannot make a server!"),
+ ),
+ ServerType::Http3NoResponse => Box::new(NonRespondingServer::default()),
+ ServerType::Http3Ech => {
+ let mut server = Box::new(Http3TestServer::new(
+ Http3Server::new(
+ Instant::now(),
+ &[" HTTP2 Test Cert"],
+ PROTOCOLS,
+ anti_replay,
+ cid_mgr,
+ Http3Parameters::default()
+ .max_table_size_encoder(MAX_TABLE_SIZE)
+ .max_table_size_decoder(MAX_TABLE_SIZE)
+ .max_blocked_streams(MAX_BLOCKED_STREAMS),
+ None,
+ )
+ .expect("We cannot make a server!"),
+ ));
+ let ref mut unboxed_server = (*server).server;
+ let (sk, pk) = generate_ech_keys().unwrap();
+ unboxed_server
+ .enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk)
+ .expect("unable to enable ech");
+ self.ech_config = Vec::from(unboxed_server.ech_config());
+ server
+ }
+ ServerType::Http3Proxy => {
+ let server_config = if env::var("MOZ_HTTP3_MOCHITEST").is_ok() {
+ ("mochitest-cert", 8888)
+ } else {
+ (" HTTP2 Test Cert", -1)
+ };
+ let server = Box::new(Http3ProxyServer::new(
+ Http3Server::new(
+ Instant::now(),
+ &[server_config.0],
+ PROTOCOLS,
+ anti_replay,
+ cid_mgr,
+ Http3Parameters::default()
+ .max_table_size_encoder(MAX_TABLE_SIZE)
+ .max_table_size_decoder(MAX_TABLE_SIZE)
+ .max_blocked_streams(MAX_BLOCKED_STREAMS)
+ .webtransport(true)
+ .connection_parameters(
+ ConnectionParameters::default().datagram_size(1200),
+ ),
+ None,
+ )
+ .expect("We cannot make a server!"),
+ server_config.1,
+ ));
+ server
+ }
+ }
+ }
+
+ fn process_datagrams_and_events(
+ &mut self,
+ inx: usize,
+ read_socket: bool,
+ ) -> Result<(), io::Error> {
+ if let Some(socket) = self.sockets.get_mut(inx) {
+ if let Some((ref mut server, svr_timeout)) =
+ self.servers.get_mut(&socket.local_addr().unwrap())
+ {
+ if read_socket {
+ loop {
+ let dgram = read_dgram(socket, &self.hosts[inx])?;
+ if dgram.is_none() {
+ break;
+ }
+ let _ = process(
+ &mut **server,
+ svr_timeout,
+ inx,
+ dgram,
+ &mut self.timer,
+ socket,
+ );
+ }
+ } else {
+ let _ = process(
+ &mut **server,
+ svr_timeout,
+ inx,
+ None,
+ &mut self.timer,
+ socket,
+ );
+ }
+ server.process_events();
+ if process(
+ &mut **server,
+ svr_timeout,
+ inx,
+ None,
+ &mut self.timer,
+ socket,
+ ) {
+ self.active_servers.insert(inx);
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn process_active_conns(&mut self) -> Result<(), io::Error> {
+ let curr_active = mem::take(&mut self.active_servers);
+ for inx in curr_active {
+ self.process_datagrams_and_events(inx, false)?;
+ }
+ Ok(())
+ }
+
+ fn process_timeout(&mut self) -> Result<(), io::Error> {
+ while let Some(inx) = self.timer.poll() {
+ qinfo!("Timer expired for {:?}", inx);
+ self.process_datagrams_and_events(inx, false)?;
+ }
+ Ok(())
+ }
+
+ pub fn run(&mut self) -> Result<(), io::Error> {
+ let mut events = Events::with_capacity(1024);
+ loop {
+ // If there are active servers do not block in poll.
+ self.poll.poll(
+ &mut events,
+ if self.active_servers.is_empty() {
+ None
+ } else {
+ Some(Duration::from_millis(0))
+ },
+ )?;
+
+ for event in &events {
+ if event.token() == TIMER_TOKEN {
+ self.process_timeout()?;
+ } else {
+ self.process_datagrams_and_events(
+ event.token().0,
+ event.readiness().is_readable(),
+ )?;
+ }
+ }
+ self.process_active_conns()?;
+ }
+ }
+}
+
+fn main() -> Result<(), io::Error> {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ eprintln!("Wrong arguments.");
+ exit(1)
+ }
+
+ // Read data from stdin and terminate the server if EOF is detected, which
+ // means that runxpcshelltests.py ended without shutting down the server.
+ thread::spawn(|| loop {
+ let mut buffer = String::new();
+ match io::stdin().read_line(&mut buffer) {
+ Ok(n) => {
+ if n == 0 {
+ exit(0);
+ }
+ }
+ Err(_) => {
+ exit(0);
+ }
+ }
+ });
+
+ init_db(PathBuf::from(args[1].clone()));
+
+ let mut servers_runner = ServersRunner::new()?;
+ servers_runner.init();
+ servers_runner.run()
+}
diff --git a/netwerk/test/http3serverDB/cert9.db b/netwerk/test/http3serverDB/cert9.db
new file mode 100644
index 0000000000..173c4fff61
--- /dev/null
+++ b/netwerk/test/http3serverDB/cert9.db
Binary files differ
diff --git a/netwerk/test/http3serverDB/key4.db b/netwerk/test/http3serverDB/key4.db
new file mode 100644
index 0000000000..a06bec3684
--- /dev/null
+++ b/netwerk/test/http3serverDB/key4.db
Binary files differ
diff --git a/netwerk/test/http3serverDB/pkcs11.txt b/netwerk/test/http3serverDB/pkcs11.txt
new file mode 100644
index 0000000000..2f1a4bfb5b
--- /dev/null
+++ b/netwerk/test/http3serverDB/pkcs11.txt
@@ -0,0 +1,4 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='.' certPrefix='' keyPrefix='' secmod='' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
diff --git a/netwerk/test/httpserver/README b/netwerk/test/httpserver/README
new file mode 100644
index 0000000000..97624789ca
--- /dev/null
+++ b/netwerk/test/httpserver/README
@@ -0,0 +1,101 @@
+httpd.js README
+===============
+
+httpd.js is a small cross-platform implementation of an HTTP/1.1 server in
+JavaScript for the Mozilla platform.
+
+httpd.js may be used as an XPCOM component, as an inline script in a document
+with XPCOM privileges, or from the XPCOM shell (xpcshell). Currently, its most-
+supported method of use is from the XPCOM shell, where you can get all the
+dynamicity of JS in adding request handlers and the like, but component-based
+equivalent functionality is planned.
+
+
+Using httpd.js as an XPCOM Component
+------------------------------------
+
+First, create an XPT file for nsIHttpServer.idl, using the xpidl tool included
+in the Mozilla SDK for the environment in which you wish to run httpd.js. See
+<http://developer.mozilla.org/en/docs/XPIDL:xpidl> for further details on how to
+do this.
+
+Next, register httpd.js and nsIHttpServer.xpt in your Mozilla application. In
+Firefox, these simply need to be added to the /components directory of your XPI.
+Other applications may require use of regxpcom or other techniques; consult the
+applicable documentation for further details.
+
+Finally, load httpd.js into the current file, and create an instance of the
+server using the following command:
+
+ var server = new nsHttpServer();
+
+At this point you'll want to initialize the server, since by default it doesn't
+serve many useful paths. For more information on this, see the IDL docs for the
+nsIHttpServer interface in nsIHttpServer.idl, particularly for
+registerDirectory (useful for mapping the contents of directories onto request
+paths), registerPathHandler (for setting a custom handler for a specific path on
+the server, such as CGI functionality), and registerFile (for mapping a file to
+a specific path).
+
+Finally, you'll want to start (and later stop) the server. Here's some example
+code which does this:
+
+ server.start(8080); // port on which server will operate
+
+ // ...server now runs and serves requests...
+
+ server.stop();
+
+This server will only respond to requests on 127.0.0.1:8080 or localhost:8080.
+If you want it to respond to requests at different hosts (say via a proxy
+mechanism), you must use server.identity.add() or server.identity.setPrimary()
+to add it.
+
+
+Using httpd.js as an Inline Script or from xpcshell
+---------------------------------------------------
+
+Using httpd.js as a script or from xpcshell isn't very different from using it
+as a component; the only real difference lies in how you create an instance of
+the server. To create an instance, do the following:
+
+ var server = new nsHttpServer();
+
+You now can use |server| exactly as you would when |server| was created as an
+XPCOM component. Note, however, that doing so will trample over the global
+namespace, and global values defined in httpd.js will leak into your script.
+This may typically be benign, but since some of the global values defined are
+constants (specifically, Cc/Ci/Cr as abbreviations for the classes, interfaces,
+and results properties of Components), it's possible this trampling could
+break your script. In general you should use httpd.js as an XPCOM component
+whenever possible.
+
+
+Known Issues
+------------
+
+httpd.js makes no effort to time out requests, beyond any the socket itself
+might or might not provide. I don't believe it provides any by default, but
+I haven't verified this.
+
+Every incoming request is processed by the corresponding request handler
+synchronously. In other words, once the first CRLFCRLF of a request is
+received, the entire response is created before any new incoming requests can be
+served. I anticipate adding asynchronous handler functionality in bug 396226,
+but it may be some time before that happens.
+
+There is no way to access the body of an incoming request. This problem is
+merely a symptom of the previous one, and they will probably both be addressed
+at the same time.
+
+
+Other Goodies
+-------------
+
+A special testing function, |server|, is provided for use in xpcshell for quick
+testing of the server; see the source code for details on its use. You don't
+want to use this in a script, however, because doing so will block until the
+server is shut down. It's also a good example of how to use the basic
+functionality of httpd.js, if you need one.
+
+Have fun!
diff --git a/netwerk/test/httpserver/TODO b/netwerk/test/httpserver/TODO
new file mode 100644
index 0000000000..3a95466117
--- /dev/null
+++ b/netwerk/test/httpserver/TODO
@@ -0,0 +1,17 @@
+Bugs to fix:
+- make content-length generation not rely on .available() returning the entire
+ size of the body stream's contents -- some sort of wrapper (but how does that
+ work for the unscriptable method WriteSegments, which is good to support from
+ a performance standpoint?)
+
+Ideas for future improvements:
+- add API to disable response buffering which, when called, causes errors when
+ you try to do anything other than write to the body stream (i.e., modify
+ headers or status line) once you've written anything to it -- useful when
+ storing the entire response in memory is unfeasible (e.g., you're testing
+ >4GB download characteristics)
+- add an API which performs asynchronous response processing (instead of
+ nsIHttpRequestHandler.handle, which must construct the response before control
+ returns; |void asyncHandle(request, response)|) -- useful, and can it be done
+ in JS?
+- other awesomeness?
diff --git a/netwerk/test/httpserver/httpd.sys.mjs b/netwerk/test/httpserver/httpd.sys.mjs
new file mode 100644
index 0000000000..569e4d1489
--- /dev/null
+++ b/netwerk/test/httpserver/httpd.sys.mjs
@@ -0,0 +1,5576 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * An implementation of an HTTP server both as a loadable script and as an XPCOM
+ * component. See the accompanying README file for user documentation on
+ * httpd.js.
+ */
+
+const CC = Components.Constructor;
+
+const PR_UINT32_MAX = Math.pow(2, 32) - 1;
+
+/** True if debugging output is enabled, false otherwise. */
+var DEBUG = false; // non-const *only* so tweakable in server tests
+
+/** True if debugging output should be timestamped. */
+var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
+
+/**
+ * Sets the debugging status, intended for tweaking in server tests.
+ *
+ * @param {boolean} debug
+ * Enables debugging output
+ * @param {boolean} debugTimestamp
+ * Enables timestamping of the debugging output.
+ */
+export function setDebuggingStatus(debug, debugTimestamp) {
+ DEBUG = debug;
+ DEBUG_TIMESTAMP = debugTimestamp;
+}
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+/**
+ * Asserts that the given condition holds. If it doesn't, the given message is
+ * dumped, a stack trace is printed, and an exception is thrown to attempt to
+ * stop execution (which unfortunately must rely upon the exception not being
+ * accidentally swallowed by the code that uses it).
+ */
+function NS_ASSERT(cond, msg) {
+ if (DEBUG && !cond) {
+ dumpn("###!!!");
+ dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
+ dumpn("###!!! Stack follows:");
+
+ var stack = new Error().stack.split(/\n/);
+ dumpn(
+ stack
+ .map(function (val) {
+ return "###!!! " + val;
+ })
+ .join("\n")
+ );
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ }
+}
+
+/** Constructs an HTTP error object. */
+export function HttpError(code, description) {
+ this.code = code;
+ this.description = description;
+}
+
+HttpError.prototype = {
+ toString() {
+ return this.code + " " + this.description;
+ },
+};
+
+/**
+ * Errors thrown to trigger specific HTTP server responses.
+ */
+export var HTTP_400 = new HttpError(400, "Bad Request");
+
+export var HTTP_401 = new HttpError(401, "Unauthorized");
+export var HTTP_402 = new HttpError(402, "Payment Required");
+export var HTTP_403 = new HttpError(403, "Forbidden");
+export var HTTP_404 = new HttpError(404, "Not Found");
+export var HTTP_405 = new HttpError(405, "Method Not Allowed");
+export var HTTP_406 = new HttpError(406, "Not Acceptable");
+export var HTTP_407 = new HttpError(407, "Proxy Authentication Required");
+export var HTTP_408 = new HttpError(408, "Request Timeout");
+export var HTTP_409 = new HttpError(409, "Conflict");
+export var HTTP_410 = new HttpError(410, "Gone");
+export var HTTP_411 = new HttpError(411, "Length Required");
+export var HTTP_412 = new HttpError(412, "Precondition Failed");
+export var HTTP_413 = new HttpError(413, "Request Entity Too Large");
+export var HTTP_414 = new HttpError(414, "Request-URI Too Long");
+export var HTTP_415 = new HttpError(415, "Unsupported Media Type");
+export var HTTP_417 = new HttpError(417, "Expectation Failed");
+export var HTTP_500 = new HttpError(500, "Internal Server Error");
+export var HTTP_501 = new HttpError(501, "Not Implemented");
+export var HTTP_502 = new HttpError(502, "Bad Gateway");
+export var HTTP_503 = new HttpError(503, "Service Unavailable");
+export var HTTP_504 = new HttpError(504, "Gateway Timeout");
+export var HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
+
+/** Creates a hash with fields corresponding to the values in arr. */
+function array2obj(arr) {
+ var obj = {};
+ for (var i = 0; i < arr.length; i++) {
+ obj[arr[i]] = arr[i];
+ }
+ return obj;
+}
+
+/** Returns an array of the integers x through y, inclusive. */
+function range(x, y) {
+ var arr = [];
+ for (var i = x; i <= y; i++) {
+ arr.push(i);
+ }
+ return arr;
+}
+
+/** An object (hash) whose fields are the numbers of all HTTP error codes. */
+const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));
+
+/**
+ * The character used to distinguish hidden files from non-hidden files, a la
+ * the leading dot in Apache. Since that mechanism also hides files from
+ * easy display in LXR, ls output, etc. however, we choose instead to use a
+ * suffix character. If a requested file ends with it, we append another
+ * when getting the file on the server. If it doesn't, we just look up that
+ * file. Therefore, any file whose name ends with exactly one of the character
+ * is "hidden" and available for use by the server.
+ */
+const HIDDEN_CHAR = "^";
+
+/**
+ * The file name suffix indicating the file containing overridden headers for
+ * a requested file.
+ */
+const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;
+const INFORMATIONAL_RESPONSE_SUFFIX =
+ HIDDEN_CHAR + "informationalResponse" + HIDDEN_CHAR;
+
+/** Type used to denote SJS scripts for CGI-like functionality. */
+const SJS_TYPE = "sjs";
+
+/** Base for relative timestamps produced by dumpn(). */
+var firstStamp = 0;
+
+/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */
+export function dumpn(str) {
+ if (DEBUG) {
+ var prefix = "HTTPD-INFO | ";
+ if (DEBUG_TIMESTAMP) {
+ if (firstStamp === 0) {
+ firstStamp = Date.now();
+ }
+
+ var elapsed = Date.now() - firstStamp; // milliseconds
+ var min = Math.floor(elapsed / 60000);
+ var sec = (elapsed % 60000) / 1000;
+
+ if (sec < 10) {
+ prefix += min + ":0" + sec.toFixed(3) + " | ";
+ } else {
+ prefix += min + ":" + sec.toFixed(3) + " | ";
+ }
+ }
+
+ dump(prefix + str + "\n");
+ }
+}
+
+/** Dumps the current JS stack if DEBUG. */
+function dumpStack() {
+ // peel off the frames for dumpStack() and Error()
+ var stack = new Error().stack.split(/\n/).slice(2);
+ stack.forEach(dumpn);
+}
+
+/**
+ * JavaScript constructors for commonly-used classes; precreating these is a
+ * speedup over doing the same from base principles. See the docs at
+ * http://developer.mozilla.org/en/docs/Components.Constructor for details.
+ */
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+const ServerSocketIPv6 = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initIPv6"
+);
+const ServerSocketDualStack = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initDualStack"
+);
+const ScriptableInputStream = CC(
+ "@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init"
+);
+const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
+const FileInputStream = CC(
+ "@mozilla.org/network/file-input-stream;1",
+ "nsIFileInputStream",
+ "init"
+);
+const ConverterInputStream = CC(
+ "@mozilla.org/intl/converter-input-stream;1",
+ "nsIConverterInputStream",
+ "init"
+);
+const WritablePropertyBag = CC(
+ "@mozilla.org/hash-property-bag;1",
+ "nsIWritablePropertyBag2"
+);
+const SupportsString = CC(
+ "@mozilla.org/supports-string;1",
+ "nsISupportsString"
+);
+
+/* These two are non-const only so a test can overwrite them. */
+var BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+var BinaryOutputStream = CC(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+export function overrideBinaryStreamsForTests(
+ inputStream,
+ outputStream,
+ responseSegmentSize
+) {
+ BinaryInputStream = inputStream;
+ BinaryOutputStream = outputStream;
+ Response.SEGMENT_SIZE = responseSegmentSize;
+}
+
+/**
+ * Returns the RFC 822/1123 representation of a date.
+ *
+ * @param date : Number
+ * the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT
+ * @returns string
+ * the representation of the given date
+ */
+function toDateString(date) {
+ //
+ // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
+ // date1 = 2DIGIT SP month SP 4DIGIT
+ // ; day month year (e.g., 02 Jun 1982)
+ // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+ // ; 00:00:00 - 23:59:59
+ // wkday = "Mon" | "Tue" | "Wed"
+ // | "Thu" | "Fri" | "Sat" | "Sun"
+ // month = "Jan" | "Feb" | "Mar" | "Apr"
+ // | "May" | "Jun" | "Jul" | "Aug"
+ // | "Sep" | "Oct" | "Nov" | "Dec"
+ //
+
+ const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ const monthStrings = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+
+ /**
+ * Processes a date and returns the encoded UTC time as a string according to
+ * the format specified in RFC 2616.
+ *
+ * @param date : Date
+ * the date to process
+ * @returns string
+ * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+ */
+ function toTime(date) {
+ var hrs = date.getUTCHours();
+ var rv = hrs < 10 ? "0" + hrs : hrs;
+
+ var mins = date.getUTCMinutes();
+ rv += ":";
+ rv += mins < 10 ? "0" + mins : mins;
+
+ var secs = date.getUTCSeconds();
+ rv += ":";
+ rv += secs < 10 ? "0" + secs : secs;
+
+ return rv;
+ }
+
+ /**
+ * Processes a date and returns the encoded UTC date as a string according to
+ * the date1 format specified in RFC 2616.
+ *
+ * @param date : Date
+ * the date to process
+ * @returns string
+ * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+ */
+ function toDate1(date) {
+ var day = date.getUTCDate();
+ var month = date.getUTCMonth();
+ var year = date.getUTCFullYear();
+
+ var rv = day < 10 ? "0" + day : day;
+ rv += " " + monthStrings[month];
+ rv += " " + year;
+
+ return rv;
+ }
+
+ date = new Date(date);
+
+ const fmtString = "%wkday%, %date1% %time% GMT";
+ var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);
+ rv = rv.replace("%time%", toTime(date));
+ return rv.replace("%date1%", toDate1(date));
+}
+
+/**
+ * Instantiates a new HTTP server.
+ */
+export function nsHttpServer() {
+ /** The port on which this server listens. */
+ this._port = undefined;
+
+ /** The socket associated with this. */
+ this._socket = null;
+
+ /** The handler used to process requests to this server. */
+ this._handler = new ServerHandler(this);
+
+ /** Naming information for this server. */
+ this._identity = new ServerIdentity();
+
+ /**
+ * Indicates when the server is to be shut down at the end of the request.
+ */
+ this._doQuit = false;
+
+ /**
+ * True if the socket in this is closed (and closure notifications have been
+ * sent and processed if the socket was ever opened), false otherwise.
+ */
+ this._socketClosed = true;
+
+ /**
+ * Used for tracking existing connections and ensuring that all connections
+ * are properly cleaned up before server shutdown; increases by 1 for every
+ * new incoming connection.
+ */
+ this._connectionGen = 0;
+
+ /**
+ * Hash of all open connections, indexed by connection number at time of
+ * creation.
+ */
+ this._connections = {};
+}
+
+nsHttpServer.prototype = {
+ // NSISERVERSOCKETLISTENER
+
+ /**
+ * Processes an incoming request coming in on the given socket and contained
+ * in the given transport.
+ *
+ * @param socket : nsIServerSocket
+ * the socket through which the request was served
+ * @param trans : nsISocketTransport
+ * the transport for the request/response
+ * @see nsIServerSocketListener.onSocketAccepted
+ */
+ onSocketAccepted(socket, trans) {
+ dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")");
+
+ dumpn(">>> new connection on " + trans.host + ":" + trans.port);
+
+ const SEGMENT_SIZE = 8192;
+ const SEGMENT_COUNT = 1024;
+ try {
+ var input = trans
+ .openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ var output = trans.openOutputStream(0, 0, 0);
+ } catch (e) {
+ dumpn("*** error opening transport streams: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ var connectionNumber = ++this._connectionGen;
+
+ try {
+ var conn = new Connection(
+ input,
+ output,
+ this,
+ socket.port,
+ trans.port,
+ connectionNumber,
+ trans
+ );
+ var reader = new RequestReader(conn);
+
+ // XXX add request timeout functionality here!
+
+ // Note: must use main thread here, or we might get a GC that will cause
+ // threadsafety assertions. We really need to fix XPConnect so that
+ // you can actually do things in multi-threaded JS. :-(
+ input.asyncWait(reader, 0, 0, Services.tm.mainThread);
+ } catch (e) {
+ // Assume this connection can't be salvaged and bail on it completely;
+ // don't attempt to close it so that we can assert that any connection
+ // being closed is in this._connections.
+ dumpn("*** error in initial request-processing stages: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ this._connections[connectionNumber] = conn;
+ dumpn("*** starting connection " + connectionNumber);
+ },
+
+ /**
+ * Called when the socket associated with this is closed.
+ *
+ * @param socket : nsIServerSocket
+ * the socket being closed
+ * @param status : nsresult
+ * the reason the socket stopped listening (NS_BINDING_ABORTED if the server
+ * was stopped using nsIHttpServer.stop)
+ * @see nsIServerSocketListener.onStopListening
+ */
+ onStopListening(socket, status) {
+ dumpn(">>> shutting down server on port " + socket.port);
+ for (var n in this._connections) {
+ if (!this._connections[n]._requestStarted) {
+ this._connections[n].close();
+ }
+ }
+ this._socketClosed = true;
+ if (this._hasOpenConnections()) {
+ dumpn("*** open connections!!!");
+ }
+ if (!this._hasOpenConnections()) {
+ dumpn("*** no open connections, notifying async from onStopListening");
+
+ // Notify asynchronously so that any pending teardown in stop() has a
+ // chance to run first.
+ var self = this;
+ var stopEvent = {
+ run() {
+ dumpn("*** _notifyStopped async callback");
+ self._notifyStopped();
+ },
+ };
+ Services.tm.currentThread.dispatch(
+ stopEvent,
+ Ci.nsIThread.DISPATCH_NORMAL
+ );
+ }
+ },
+
+ // NSIHTTPSERVER
+
+ //
+ // see nsIHttpServer.start
+ //
+ start(port) {
+ this._start(port, "localhost");
+ },
+
+ //
+ // see nsIHttpServer.start_ipv6
+ //
+ start_ipv6(port) {
+ this._start(port, "[::1]");
+ },
+
+ start_dualStack(port) {
+ this._start(port, "[::1]", true);
+ },
+
+ _start(port, host, dualStack) {
+ if (this._socket) {
+ throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED);
+ }
+
+ this._port = port;
+ this._doQuit = this._socketClosed = false;
+
+ this._host = host;
+
+ // The listen queue needs to be long enough to handle
+ // network.http.max-persistent-connections-per-server or
+ // network.http.max-persistent-connections-per-proxy concurrent
+ // connections, plus a safety margin in case some other process is
+ // talking to the server as well.
+ var maxConnections =
+ 5 +
+ Math.max(
+ Services.prefs.getIntPref(
+ "network.http.max-persistent-connections-per-server"
+ ),
+ Services.prefs.getIntPref(
+ "network.http.max-persistent-connections-per-proxy"
+ )
+ );
+
+ try {
+ var loopback = true;
+ if (
+ this._host != "127.0.0.1" &&
+ this._host != "localhost" &&
+ this._host != "[::1]"
+ ) {
+ loopback = false;
+ }
+
+ // When automatically selecting a port, sometimes the chosen port is
+ // "blocked" from clients. We don't want to use these ports because
+ // tests will intermittently fail. So, we simply keep trying to to
+ // get a server socket until a valid port is obtained. We limit
+ // ourselves to finite attempts just so we don't loop forever.
+ var socket;
+ for (var i = 100; i; i--) {
+ var temp = null;
+ if (dualStack) {
+ temp = new ServerSocketDualStack(this._port, maxConnections);
+ } else if (this._host.includes(":")) {
+ temp = new ServerSocketIPv6(
+ this._port,
+ loopback, // true = localhost, false = everybody
+ maxConnections
+ );
+ } else {
+ temp = new ServerSocket(
+ this._port,
+ loopback, // true = localhost, false = everybody
+ maxConnections
+ );
+ }
+
+ var allowed = Services.io.allowPort(temp.port, "http");
+ if (!allowed) {
+ dumpn(
+ ">>>Warning: obtained ServerSocket listens on a blocked " +
+ "port: " +
+ temp.port
+ );
+ }
+
+ if (!allowed && this._port == -1) {
+ dumpn(">>>Throwing away ServerSocket with bad port.");
+ temp.close();
+ continue;
+ }
+
+ socket = temp;
+ break;
+ }
+
+ if (!socket) {
+ throw new Error(
+ "No socket server available. Are there no available ports?"
+ );
+ }
+
+ socket.asyncListen(this);
+ this._port = socket.port;
+ this._identity._initialize(socket.port, host, true, dualStack);
+ this._socket = socket;
+ dumpn(
+ ">>> listening on port " +
+ socket.port +
+ ", " +
+ maxConnections +
+ " pending connections"
+ );
+ } catch (e) {
+ dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ },
+
+ //
+ // see nsIHttpServer.stop
+ //
+ stop(callback) {
+ if (!this._socket) {
+ throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+ }
+
+ // If no argument was provided to stop, return a promise.
+ let returnValue = undefined;
+ if (!callback) {
+ returnValue = new Promise(resolve => {
+ callback = resolve;
+ });
+ }
+
+ this._stopCallback =
+ typeof callback === "function"
+ ? callback
+ : function () {
+ callback.onStopped();
+ };
+
+ dumpn(">>> stopping listening on port " + this._socket.port);
+ this._socket.close();
+ this._socket = null;
+
+ // We can't have this identity any more, and the port on which we're running
+ // this server now could be meaningless the next time around.
+ this._identity._teardown();
+
+ this._doQuit = false;
+
+ // socket-close notification and pending request completion happen async
+
+ return returnValue;
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile(path, file, handler) {
+ if (file && (!file.exists() || file.isDirectory())) {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ this._handler.registerFile(path, file, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory(path, directory) {
+ // XXX true path validation!
+ if (
+ path.charAt(0) != "/" ||
+ path.charAt(path.length - 1) != "/" ||
+ (directory && (!directory.exists() || !directory.isDirectory()))
+ ) {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
+ // exists!
+
+ this._handler.registerDirectory(path, directory);
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler(path, handler) {
+ this._handler.registerPathHandler(path, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler(prefix, handler) {
+ this._handler.registerPrefixHandler(prefix, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler(code, handler) {
+ this._handler.registerErrorHandler(code, handler);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler(handler) {
+ this._handler.setIndexHandler(handler);
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType(ext, type) {
+ this._handler.registerContentType(ext, type);
+ },
+
+ get connectionNumber() {
+ return this._connectionGen;
+ },
+
+ //
+ // see nsIHttpServer.serverIdentity
+ //
+ get identity() {
+ return this._identity;
+ },
+
+ //
+ // see nsIHttpServer.getState
+ //
+ getState(path, k) {
+ return this._handler._getState(path, k);
+ },
+
+ //
+ // see nsIHttpServer.setState
+ //
+ setState(path, k, v) {
+ return this._handler._setState(path, k, v);
+ },
+
+ //
+ // see nsIHttpServer.getSharedState
+ //
+ getSharedState(k) {
+ return this._handler._getSharedState(k);
+ },
+
+ //
+ // see nsIHttpServer.setSharedState
+ //
+ setSharedState(k, v) {
+ return this._handler._setSharedState(k, v);
+ },
+
+ //
+ // see nsIHttpServer.getObjectState
+ //
+ getObjectState(k) {
+ return this._handler._getObjectState(k);
+ },
+
+ //
+ // see nsIHttpServer.setObjectState
+ //
+ setObjectState(k, v) {
+ return this._handler._setObjectState(k, v);
+ },
+
+ get wrappedJSObject() {
+ return this;
+ },
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIHttpServer",
+ "nsIServerSocketListener",
+ ]),
+
+ // NON-XPCOM PUBLIC API
+
+ /**
+ * Returns true iff this server is not running (and is not in the process of
+ * serving any requests still to be processed when the server was last
+ * stopped after being run).
+ */
+ isStopped() {
+ return this._socketClosed && !this._hasOpenConnections();
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /** True if this server has any open connections to it, false otherwise. */
+ _hasOpenConnections() {
+ //
+ // If we have any open connections, they're tracked as numeric properties on
+ // |this._connections|. The non-standard __count__ property could be used
+ // to check whether there are any properties, but standard-wise, even
+ // looking forward to ES5, there's no less ugly yet still O(1) way to do
+ // this.
+ //
+ for (var n in this._connections) {
+ return true;
+ }
+ return false;
+ },
+
+ /** Calls the server-stopped callback provided when stop() was called. */
+ _notifyStopped() {
+ NS_ASSERT(this._stopCallback !== null, "double-notifying?");
+ NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now");
+
+ //
+ // NB: We have to grab this now, null out the member, *then* call the
+ // callback here, or otherwise the callback could (indirectly) futz with
+ // this._stopCallback by starting and immediately stopping this, at
+ // which point we'd be nulling out a field we no longer have a right to
+ // modify.
+ //
+ var callback = this._stopCallback;
+ this._stopCallback = null;
+ try {
+ callback();
+ } catch (e) {
+ // not throwing because this is specified as being usually (but not
+ // always) asynchronous
+ dump("!!! error running onStopped callback: " + e + "\n");
+ }
+ },
+
+ /**
+ * Notifies this server that the given connection has been closed.
+ *
+ * @param connection : Connection
+ * the connection that was closed
+ */
+ _connectionClosed(connection) {
+ NS_ASSERT(
+ connection.number in this._connections,
+ "closing a connection " +
+ this +
+ " that we never added to the " +
+ "set of open connections?"
+ );
+ NS_ASSERT(
+ this._connections[connection.number] === connection,
+ "connection number mismatch? " + this._connections[connection.number]
+ );
+ delete this._connections[connection.number];
+
+ // Fire a pending server-stopped notification if it's our responsibility.
+ if (!this._hasOpenConnections() && this._socketClosed) {
+ this._notifyStopped();
+ }
+ },
+
+ /**
+ * Requests that the server be shut down when possible.
+ */
+ _requestQuit() {
+ dumpn(">>> requesting a quit");
+ dumpStack();
+ this._doQuit = true;
+ },
+};
+
+export var HttpServer = nsHttpServer;
+
+export class NodeServer {
+ // Executes command in the context of a node server.
+ // See handler in moz-http2.js
+ //
+ // Example use:
+ // let id = NodeServer.fork(); // id is a random string
+ // await NodeServer.execute(id, `"hello"`)
+ // > "hello"
+ // await NodeServer.execute(id, `(() => "hello")()`)
+ // > "hello"
+ // await NodeServer.execute(id, `(() => var_defined_on_server)()`)
+ // > "0"
+ // await NodeServer.execute(id, `var_defined_on_server`)
+ // > "0"
+ // function f(param) { if (param) return param; return "bla"; }
+ // await NodeServer.execute(id, f); // Defines the function on the server
+ // await NodeServer.execute(id, `f()`) // executes defined function
+ // > "bla"
+ // let result = await NodeServer.execute(id, `f("test")`);
+ // > "test"
+ // await NodeServer.kill(id); // shuts down the server
+
+ // Forks a new node server using moz-http2-child.js as a starting point
+ static fork() {
+ return this.sendCommand("", "/fork");
+ }
+ // Executes command in the context of the node server indicated by `id`
+ static execute(id, command) {
+ return this.sendCommand(command, `/execute/${id}`);
+ }
+ // Shuts down the server
+ static kill(id) {
+ return this.sendCommand("", `/kill/${id}`);
+ }
+
+ // Issues a request to the node server (handler defined in moz-http2.js)
+ // This method should not be called directly.
+ static sendCommand(command, path) {
+ let h2Port = Services.env.get("MOZNODE_EXEC_PORT");
+ if (!h2Port) {
+ throw new Error("Could not find MOZNODE_EXEC_PORT");
+ }
+
+ let req = new XMLHttpRequest();
+ const serverIP =
+ AppConstants.platform == "android" ? "10.0.2.2" : "127.0.0.1";
+ req.open("POST", `http://${serverIP}:${h2Port}${path}`);
+ req.channel.QueryInterface(Ci.nsIHttpChannelInternal).bypassProxy = true;
+
+ // Passing a function to NodeServer.execute will define that function
+ // in node. It can be called in a later execute command.
+ let isFunction = function (obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ };
+ let payload = command;
+ if (isFunction(command)) {
+ payload = `${command.name} = ${command.toString()};`;
+ }
+
+ return new Promise((resolve, reject) => {
+ req.onload = () => {
+ let x = null;
+
+ if (req.statusText != "OK") {
+ reject(`XHR request failed: ${req.statusText}`);
+ return;
+ }
+
+ try {
+ x = JSON.parse(req.responseText);
+ } catch (e) {
+ reject(`Failed to parse ${req.responseText} - ${e}`);
+ return;
+ }
+
+ if (x.error) {
+ let e = new Error(x.error, "", 0);
+ e.stack = x.errorStack;
+ reject(e);
+ return;
+ }
+ resolve(x.result);
+ };
+ req.onerror = e => {
+ reject(e);
+ };
+
+ req.send(payload.toString());
+ });
+ }
+}
+
+//
+// RFC 2396 section 3.2.2:
+//
+// host = hostname | IPv4address
+// hostname = *( domainlabel "." ) toplabel [ "." ]
+// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+//
+// IPv6 addresses are notably lacking in the above definition of 'host'.
+// RFC 2732 section 3 extends the host definition:
+// host = hostname | IPv4address | IPv6reference
+// ipv6reference = "[" IPv6address "]"
+//
+// RFC 3986 supersedes RFC 2732 and offers a more precise definition of a IPv6
+// address. For simplicity, the regexp below captures all canonical IPv6
+// addresses (e.g. [::1]), but may also match valid non-canonical IPv6 addresses
+// (e.g. [::127.0.0.1]) and even invalid bracketed addresses ([::], [99999::]).
+
+const HOST_REGEX = new RegExp(
+ "^(?:" +
+ // *( domainlabel "." )
+ "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
+ // toplabel [ "." ]
+ "[a-z](?:[a-z0-9-]*[a-z0-9])?\\.?" +
+ "|" +
+ // IPv4 address
+ "\\d+\\.\\d+\\.\\d+\\.\\d+" +
+ "|" +
+ // IPv6 addresses (e.g. [::1])
+ "\\[[:0-9a-f]+\\]" +
+ ")$",
+ "i"
+);
+
+/**
+ * Represents the identity of a server. An identity consists of a set of
+ * (scheme, host, port) tuples denoted as locations (allowing a single server to
+ * serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
+ * host/port). Any incoming request must be to one of these locations, or it
+ * will be rejected with an HTTP 400 error. One location, denoted as the
+ * primary location, is the location assigned in contexts where a location
+ * cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
+ *
+ * A single identity may contain at most one location per unique host/port pair;
+ * other than that, no restrictions are placed upon what locations may
+ * constitute an identity.
+ */
+function ServerIdentity() {
+ /** The scheme of the primary location. */
+ this._primaryScheme = "http";
+
+ /** The hostname of the primary location. */
+ this._primaryHost = "127.0.0.1";
+
+ /** The port number of the primary location. */
+ this._primaryPort = -1;
+
+ /**
+ * The current port number for the corresponding server, stored so that a new
+ * primary location can always be set if the current one is removed.
+ */
+ this._defaultPort = -1;
+
+ /**
+ * Maps hosts to maps of ports to schemes, e.g. the following would represent
+ * https://example.com:789/ and http://example.org/:
+ *
+ * {
+ * "xexample.com": { 789: "https" },
+ * "xexample.org": { 80: "http" }
+ * }
+ *
+ * Note the "x" prefix on hostnames, which prevents collisions with special
+ * JS names like "prototype".
+ */
+ this._locations = { xlocalhost: {} };
+}
+ServerIdentity.prototype = {
+ // NSIHTTPSERVERIDENTITY
+
+ //
+ // see nsIHttpServerIdentity.primaryScheme
+ //
+ get primaryScheme() {
+ if (this._primaryPort === -1) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+ return this._primaryScheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryHost
+ //
+ get primaryHost() {
+ if (this._primaryPort === -1) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+ return this._primaryHost;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryPort
+ //
+ get primaryPort() {
+ if (this._primaryPort === -1) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+ return this._primaryPort;
+ },
+
+ //
+ // see nsIHttpServerIdentity.add
+ //
+ add(scheme, host, port) {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry) {
+ this._locations["x" + host] = entry = {};
+ }
+
+ entry[port] = scheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.remove
+ //
+ remove(scheme, host, port) {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry) {
+ return false;
+ }
+
+ var present = port in entry;
+ delete entry[port];
+
+ if (
+ this._primaryScheme == scheme &&
+ this._primaryHost == host &&
+ this._primaryPort == port &&
+ this._defaultPort !== -1
+ ) {
+ // Always keep at least one identity in existence at any time, unless
+ // we're in the process of shutting down (the last condition above).
+ this._primaryPort = -1;
+ this._initialize(this._defaultPort, host, false);
+ }
+
+ return present;
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ has(scheme, host, port) {
+ this._validate(scheme, host, port);
+
+ return (
+ "x" + host in this._locations &&
+ scheme === this._locations["x" + host][port]
+ );
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ getScheme(host, port) {
+ this._validate("http", host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry) {
+ return "";
+ }
+
+ return entry[port] || "";
+ },
+
+ //
+ // see nsIHttpServerIdentity.setPrimary
+ //
+ setPrimary(scheme, host, port) {
+ this._validate(scheme, host, port);
+
+ this.add(scheme, host, port);
+
+ this._primaryScheme = scheme;
+ this._primaryHost = host;
+ this._primaryPort = port;
+ },
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: ChromeUtils.generateQI(["nsIHttpServerIdentity"]),
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+ * Initializes the primary name for the corresponding server, based on the
+ * provided port number.
+ */
+ _initialize(port, host, addSecondaryDefault, dualStack) {
+ this._host = host;
+ if (this._primaryPort !== -1) {
+ this.add("http", host, port);
+ } else {
+ this.setPrimary("http", "localhost", port);
+ }
+ this._defaultPort = port;
+
+ // Only add this if we're being called at server startup
+ if (addSecondaryDefault && host != "127.0.0.1") {
+ if (host.includes(":")) {
+ this.add("http", "[::1]", port);
+ if (dualStack) {
+ this.add("http", "127.0.0.1", port);
+ }
+ } else {
+ this.add("http", "127.0.0.1", port);
+ }
+ }
+ },
+
+ /**
+ * Called at server shutdown time, unsets the primary location only if it was
+ * the default-assigned location and removes the default location from the
+ * set of locations used.
+ */
+ _teardown() {
+ if (this._host != "127.0.0.1") {
+ // Not the default primary location, nothing special to do here
+ this.remove("http", "127.0.0.1", this._defaultPort);
+ }
+
+ // This is a *very* tricky bit of reasoning here; make absolutely sure the
+ // tests for this code pass before you commit changes to it.
+ if (
+ this._primaryScheme == "http" &&
+ this._primaryHost == this._host &&
+ this._primaryPort == this._defaultPort
+ ) {
+ // Make sure we don't trigger the readding logic in .remove(), then remove
+ // the default location.
+ var port = this._defaultPort;
+ this._defaultPort = -1;
+ this.remove("http", this._host, port);
+
+ // Ensure a server start triggers the setPrimary() path in ._initialize()
+ this._primaryPort = -1;
+ } else {
+ // No reason not to remove directly as it's not our primary location
+ this.remove("http", this._host, this._defaultPort);
+ }
+ },
+
+ /**
+ * Ensures scheme, host, and port are all valid with respect to RFC 2396.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if any argument doesn't match the corresponding production
+ */
+ _validate(scheme, host, port) {
+ if (scheme !== "http" && scheme !== "https") {
+ dumpn("*** server only supports http/https schemes: '" + scheme + "'");
+ dumpStack();
+ throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ if (!HOST_REGEX.test(host)) {
+ dumpn("*** unexpected host: '" + host + "'");
+ throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ if (port < 0 || port > 65535) {
+ dumpn("*** unexpected port: '" + port + "'");
+ throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ },
+};
+
+/**
+ * Represents a connection to the server (and possibly in the future the thread
+ * on which the connection is processed).
+ *
+ * @param input : nsIInputStream
+ * stream from which incoming data on the connection is read
+ * @param output : nsIOutputStream
+ * stream to write data out the connection
+ * @param server : nsHttpServer
+ * the server handling the connection
+ * @param port : int
+ * the port on which the server is running
+ * @param outgoingPort : int
+ * the outgoing port used by this connection
+ * @param number : uint
+ * a serial number used to uniquely identify this connection
+ */
+function Connection(
+ input,
+ output,
+ server,
+ port,
+ outgoingPort,
+ number,
+ transport
+) {
+ dumpn("*** opening new connection " + number + " on port " + outgoingPort);
+
+ /** Stream of incoming data. */
+ this.input = input;
+
+ /** Stream for outgoing data. */
+ this.output = output;
+
+ /** The server associated with this request. */
+ this.server = server;
+
+ /** The port on which the server is running. */
+ this.port = port;
+
+ /** The outgoing poort used by this connection. */
+ this._outgoingPort = outgoingPort;
+
+ /** The serial number of this connection. */
+ this.number = number;
+
+ /** Reference to the underlying transport. */
+ this.transport = transport;
+
+ /**
+ * The request for which a response is being generated, null if the
+ * incoming request has not been fully received or if it had errors.
+ */
+ this.request = null;
+
+ /** This allows a connection to disambiguate between a peer initiating a
+ * close and the socket being forced closed on shutdown.
+ */
+ this._closed = false;
+
+ /** State variable for debugging. */
+ this._processed = false;
+
+ /** whether or not 1st line of request has been received */
+ this._requestStarted = false;
+}
+Connection.prototype = {
+ /** Closes this connection's input/output streams. */
+ close() {
+ if (this._closed) {
+ return;
+ }
+
+ dumpn(
+ "*** closing connection " + this.number + " on port " + this._outgoingPort
+ );
+
+ this.input.close();
+ this.output.close();
+ this._closed = true;
+
+ var server = this.server;
+ server._connectionClosed(this);
+
+ // If an error triggered a server shutdown, act on it now
+ if (server._doQuit) {
+ server.stop(function () {
+ /* not like we can do anything better */
+ });
+ }
+ },
+
+ /**
+ * Initiates processing of this connection, using the data in the given
+ * request.
+ *
+ * @param request : Request
+ * the request which should be processed
+ */
+ process(request) {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+
+ this.request = request;
+ this.server._handler.handleResponse(this);
+ },
+
+ /**
+ * Initiates processing of this connection, generating a response with the
+ * given HTTP error code.
+ *
+ * @param code : uint
+ * an HTTP code, so in the range [0, 1000)
+ * @param request : Request
+ * incomplete data about the incoming request (since there were errors
+ * during its processing
+ */
+ processError(code, request) {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+ this.request = request;
+ this.server._handler.handleError(code, this);
+ },
+
+ /** Converts this to a string for debugging purposes. */
+ toString() {
+ return (
+ "<Connection(" +
+ this.number +
+ (this.request ? ", " + this.request.path : "") +
+ "): " +
+ (this._closed ? "closed" : "open") +
+ ">"
+ );
+ },
+
+ requestStarted() {
+ this._requestStarted = true;
+ },
+};
+
+/** Returns an array of count bytes from the given input stream. */
+function readBytes(inputStream, count) {
+ return new BinaryInputStream(inputStream).readByteArray(count);
+}
+
+/** Request reader processing states; see RequestReader for details. */
+const READER_IN_REQUEST_LINE = 0;
+const READER_IN_HEADERS = 1;
+const READER_IN_BODY = 2;
+const READER_FINISHED = 3;
+
+/**
+ * Reads incoming request data asynchronously, does any necessary preprocessing,
+ * and forwards it to the request handler. Processing occurs in three states:
+ *
+ * READER_IN_REQUEST_LINE Reading the request's status line
+ * READER_IN_HEADERS Reading headers in the request
+ * READER_IN_BODY Reading the body of the request
+ * READER_FINISHED Entire request has been read and processed
+ *
+ * During the first two stages, initial metadata about the request is gathered
+ * into a Request object. Once the status line and headers have been processed,
+ * we start processing the body of the request into the Request. Finally, when
+ * the entire body has been read, we create a Response and hand it off to the
+ * ServerHandler to be given to the appropriate request handler.
+ *
+ * @param connection : Connection
+ * the connection for the request being read
+ */
+function RequestReader(connection) {
+ /** Connection metadata for this request. */
+ this._connection = connection;
+
+ /**
+ * A container providing line-by-line access to the raw bytes that make up the
+ * data which has been read from the connection but has not yet been acted
+ * upon (by passing it to the request handler or by extracting request
+ * metadata from it).
+ */
+ this._data = new LineData();
+
+ /**
+ * The amount of data remaining to be read from the body of this request.
+ * After all headers in the request have been read this is the value in the
+ * Content-Length header, but as the body is read its value decreases to zero.
+ */
+ this._contentLength = 0;
+
+ /** The current state of parsing the incoming request. */
+ this._state = READER_IN_REQUEST_LINE;
+
+ /** Metadata constructed from the incoming request for the request handler. */
+ this._metadata = new Request(connection.port);
+
+ /**
+ * Used to preserve state if we run out of line data midway through a
+ * multi-line header. _lastHeaderName stores the name of the header, while
+ * _lastHeaderValue stores the value we've seen so far for the header.
+ *
+ * These fields are always either both undefined or both strings.
+ */
+ this._lastHeaderName = this._lastHeaderValue = undefined;
+}
+RequestReader.prototype = {
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+ * Called when more data from the incoming request is available. This method
+ * then reads the available data from input and deals with that data as
+ * necessary, depending upon the syntax of already-downloaded data.
+ *
+ * @param input : nsIAsyncInputStream
+ * the stream of incoming data from the connection
+ */
+ onInputStreamReady(input) {
+ dumpn(
+ "*** onInputStreamReady(input=" +
+ input +
+ ") on thread " +
+ Services.tm.currentThread +
+ " (main is " +
+ Services.tm.mainThread +
+ ")"
+ );
+ dumpn("*** this._state == " + this._state);
+
+ // Handle cases where we get more data after a request error has been
+ // discovered but *before* we can close the connection.
+ var data = this._data;
+ if (!data) {
+ return;
+ }
+
+ try {
+ data.appendBytes(readBytes(input, input.available()));
+ } catch (e) {
+ if (streamClosed(e)) {
+ dumpn(
+ "*** WARNING: unexpected error when reading from socket; will " +
+ "be treated as if the input stream had been closed"
+ );
+ dumpn("*** WARNING: actual error was: " + e);
+ }
+
+ // We've lost a race -- input has been closed, but we're still expecting
+ // to read more data. available() will throw in this case, and since
+ // we're dead in the water now, destroy the connection.
+ dumpn(
+ "*** onInputStreamReady called on a closed input, destroying " +
+ "connection"
+ );
+ this._connection.close();
+ return;
+ }
+
+ switch (this._state) {
+ default:
+ NS_ASSERT(false, "invalid state: " + this._state);
+ break;
+
+ case READER_IN_REQUEST_LINE:
+ if (!this._processRequestLine()) {
+ break;
+ }
+ /* fall through */
+
+ case READER_IN_HEADERS:
+ if (!this._processHeaders()) {
+ break;
+ }
+ /* fall through */
+
+ case READER_IN_BODY:
+ this._processBody();
+ }
+
+ if (this._state != READER_FINISHED) {
+ input.asyncWait(this, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: ChromeUtils.generateQI(["nsIInputStreamCallback"]),
+
+ // PRIVATE API
+
+ /**
+ * Processes unprocessed, downloaded data as a request line.
+ *
+ * @returns boolean
+ * true iff the request line has been fully processed
+ */
+ _processRequestLine() {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ // Servers SHOULD ignore any empty line(s) received where a Request-Line
+ // is expected (section 4.1).
+ var data = this._data;
+ var line = {};
+ var readSuccess;
+ while ((readSuccess = data.readLine(line)) && line.value == "") {
+ dumpn("*** ignoring beginning blank line...");
+ }
+
+ // if we don't have a full line, wait until we do
+ if (!readSuccess) {
+ return false;
+ }
+
+ // we have the first non-blank line
+ try {
+ this._parseRequestLine(line.value);
+ this._state = READER_IN_HEADERS;
+ this._connection.requestStarted();
+ return true;
+ } catch (e) {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+ * Processes stored data, assuming it is either at the beginning or in
+ * the middle of processing request headers.
+ *
+ * @returns boolean
+ * true iff header data in the request has been fully processed
+ */
+ _processHeaders() {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ // XXX things to fix here:
+ //
+ // - need to support RFC 2047-encoded non-US-ASCII characters
+
+ try {
+ var done = this._parseHeaders();
+ if (done) {
+ var request = this._metadata;
+
+ // XXX this is wrong for requests with transfer-encodings applied to
+ // them, particularly chunked (which by its nature can have no
+ // meaningful Content-Length header)!
+ this._contentLength = request.hasHeader("Content-Length")
+ ? parseInt(request.getHeader("Content-Length"), 10)
+ : 0;
+ dumpn("_processHeaders, Content-length=" + this._contentLength);
+
+ this._state = READER_IN_BODY;
+ }
+ return done;
+ } catch (e) {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+ * Processes stored data, assuming it is either at the beginning or in
+ * the middle of processing the request body.
+ *
+ * @returns boolean
+ * true iff the request body has been fully processed
+ */
+ _processBody() {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ // XXX handle chunked transfer-coding request bodies!
+
+ try {
+ if (this._contentLength > 0) {
+ var data = this._data.purge();
+ var count = Math.min(data.length, this._contentLength);
+ dumpn(
+ "*** loading data=" +
+ data +
+ " len=" +
+ data.length +
+ " excess=" +
+ (data.length - count)
+ );
+ data.length = count;
+
+ var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
+ bos.writeByteArray(data);
+ this._contentLength -= count;
+ }
+
+ dumpn("*** remaining body data len=" + this._contentLength);
+ if (this._contentLength == 0) {
+ this._validateRequest();
+ this._state = READER_FINISHED;
+ this._handleResponse();
+ return true;
+ }
+
+ return false;
+ } catch (e) {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+ * Does various post-header checks on the data in this request.
+ *
+ * @throws : HttpError
+ * if the request was malformed in some way
+ */
+ _validateRequest() {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ dumpn("*** _validateRequest");
+
+ var metadata = this._metadata;
+ var headers = metadata._headers;
+
+ // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
+ var identity = this._connection.server.identity;
+ if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) {
+ if (!headers.hasHeader("Host")) {
+ dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
+ throw HTTP_400;
+ }
+
+ // If the Request-URI wasn't absolute, then we need to determine our host.
+ // We have to determine what scheme was used to access us based on the
+ // server identity data at this point, because the request just doesn't
+ // contain enough data on its own to do this, sadly.
+ if (!metadata._host) {
+ var host, port;
+ var hostPort = headers.getHeader("Host");
+ var colon = hostPort.lastIndexOf(":");
+ if (hostPort.lastIndexOf("]") > colon) {
+ colon = -1;
+ }
+ if (colon < 0) {
+ host = hostPort;
+ port = "";
+ } else {
+ host = hostPort.substring(0, colon);
+ port = hostPort.substring(colon + 1);
+ }
+
+ // NB: We allow an empty port here because, oddly, a colon may be
+ // present even without a port number, e.g. "example.com:"; in this
+ // case the default port applies.
+ if (!HOST_REGEX.test(host) || !/^\d*$/.test(port)) {
+ dumpn(
+ "*** malformed hostname (" +
+ hostPort +
+ ") in Host " +
+ "header, 400 time"
+ );
+ throw HTTP_400;
+ }
+
+ // If we're not given a port, we're stuck, because we don't know what
+ // scheme to use to look up the correct port here, in general. Since
+ // the HTTPS case requires a tunnel/proxy and thus requires that the
+ // requested URI be absolute (and thus contain the necessary
+ // information), let's assume HTTP will prevail and use that.
+ port = +port || 80;
+
+ var scheme = identity.getScheme(host, port);
+ if (!scheme) {
+ dumpn(
+ "*** unrecognized hostname (" +
+ hostPort +
+ ") in Host " +
+ "header, 400 time"
+ );
+ throw HTTP_400;
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ }
+ } else {
+ NS_ASSERT(
+ metadata._host === undefined,
+ "HTTP/1.0 doesn't allow absolute paths in the request line!"
+ );
+
+ metadata._scheme = identity.primaryScheme;
+ metadata._host = identity.primaryHost;
+ metadata._port = identity.primaryPort;
+ }
+
+ NS_ASSERT(
+ identity.has(metadata._scheme, metadata._host, metadata._port),
+ "must have a location we recognize by now!"
+ );
+ },
+
+ /**
+ * Handles responses in case of error, either in the server or in the request.
+ *
+ * @param e
+ * the specific error encountered, which is an HttpError in the case where
+ * the request is in some way invalid or cannot be fulfilled; if this isn't
+ * an HttpError we're going to be paranoid and shut down, because that
+ * shouldn't happen, ever
+ */
+ _handleError(e) {
+ // Don't fall back into normal processing!
+ this._state = READER_FINISHED;
+
+ var server = this._connection.server;
+ if (e instanceof HttpError) {
+ var code = e.code;
+ } else {
+ dumpn(
+ "!!! UNEXPECTED ERROR: " +
+ e +
+ (e.lineNumber ? ", line " + e.lineNumber : "")
+ );
+
+ // no idea what happened -- be paranoid and shut down
+ code = 500;
+ server._requestQuit();
+ }
+
+ // make attempted reuse of data an error
+ this._data = null;
+
+ this._connection.processError(code, this._metadata);
+ },
+
+ /**
+ * Now that we've read the request line and headers, we can actually hand off
+ * the request to be handled.
+ *
+ * This method is called once per request, after the request line and all
+ * headers and the body, if any, have been received.
+ */
+ _handleResponse() {
+ NS_ASSERT(this._state == READER_FINISHED);
+
+ // We don't need the line-based data any more, so make attempted reuse an
+ // error.
+ this._data = null;
+
+ this._connection.process(this._metadata);
+ },
+
+ // PARSING
+
+ /**
+ * Parses the request line for the HTTP request associated with this.
+ *
+ * @param line : string
+ * the request line
+ */
+ _parseRequestLine(line) {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ dumpn("*** _parseRequestLine('" + line + "')");
+
+ var metadata = this._metadata;
+
+ // clients and servers SHOULD accept any amount of SP or HT characters
+ // between fields, even though only a single SP is required (section 19.3)
+ var request = line.split(/[ \t]+/);
+ if (!request || request.length != 3) {
+ dumpn("*** No request in line");
+ throw HTTP_400;
+ }
+
+ metadata._method = request[0];
+
+ // get the HTTP version
+ var ver = request[2];
+ var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
+ if (!match) {
+ dumpn("*** No HTTP version in line");
+ throw HTTP_400;
+ }
+
+ // determine HTTP version
+ try {
+ metadata._httpVersion = new nsHttpVersion(match[1]);
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0)) {
+ throw new Error("unsupported HTTP version");
+ }
+ } catch (e) {
+ // we support HTTP/1.0 and HTTP/1.1 only
+ throw HTTP_501;
+ }
+
+ var fullPath = request[1];
+
+ if (metadata._method == "CONNECT") {
+ metadata._path = "CONNECT";
+ metadata._scheme = "https";
+ [metadata._host, metadata._port] = fullPath.split(":");
+ return;
+ }
+
+ var serverIdentity = this._connection.server.identity;
+ var scheme, host, port;
+
+ if (fullPath.charAt(0) != "/") {
+ // No absolute paths in the request line in HTTP prior to 1.1
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) {
+ dumpn("*** Metadata version too low");
+ throw HTTP_400;
+ }
+
+ try {
+ var uri = Services.io.newURI(fullPath);
+ fullPath = uri.pathQueryRef;
+ scheme = uri.scheme;
+ host = uri.asciiHost;
+ if (host.includes(":")) {
+ // If the host still contains a ":", then it is an IPv6 address.
+ // IPv6 addresses-as-host are registered with brackets, so we need to
+ // wrap the host in brackets because nsIURI's host lacks them.
+ // This inconsistency in nsStandardURL is tracked at bug 1195459.
+ host = `[${host}]`;
+ }
+ metadata._host = host;
+ port = uri.port;
+ if (port === -1) {
+ if (scheme === "http") {
+ port = 80;
+ } else if (scheme === "https") {
+ port = 443;
+ } else {
+ dumpn("*** Unknown scheme: " + scheme);
+ throw HTTP_400;
+ }
+ }
+ } catch (e) {
+ // If the host is not a valid host on the server, the response MUST be a
+ // 400 (Bad Request) error message (section 5.2). Alternately, the URI
+ // is malformed.
+ dumpn("*** Threw when dealing with URI: " + e);
+ throw HTTP_400;
+ }
+
+ if (
+ !serverIdentity.has(scheme, host, port) ||
+ fullPath.charAt(0) != "/"
+ ) {
+ dumpn("*** serverIdentity unknown or path does not start with '/'");
+ throw HTTP_400;
+ }
+ }
+
+ var splitter = fullPath.indexOf("?");
+ if (splitter < 0) {
+ // _queryString already set in ctor
+ metadata._path = fullPath;
+ } else {
+ metadata._path = fullPath.substring(0, splitter);
+ metadata._queryString = fullPath.substring(splitter + 1);
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ },
+
+ /**
+ * Parses all available HTTP headers in this until the header-ending CRLFCRLF,
+ * adding them to the store of headers in the request.
+ *
+ * @throws
+ * HTTP_400 if the headers are malformed
+ * @returns boolean
+ * true if all headers have now been processed, false otherwise
+ */
+ _parseHeaders() {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ dumpn("*** _parseHeaders");
+
+ var data = this._data;
+
+ var headers = this._metadata._headers;
+ var lastName = this._lastHeaderName;
+ var lastVal = this._lastHeaderValue;
+
+ var line = {};
+ while (true) {
+ dumpn("*** Last name: '" + lastName + "'");
+ dumpn("*** Last val: '" + lastVal + "'");
+ NS_ASSERT(
+ !((lastVal === undefined) ^ (lastName === undefined)),
+ lastName === undefined
+ ? "lastVal without lastName? lastVal: '" + lastVal + "'"
+ : "lastName without lastVal? lastName: '" + lastName + "'"
+ );
+
+ if (!data.readLine(line)) {
+ // save any data we have from the header we might still be processing
+ this._lastHeaderName = lastName;
+ this._lastHeaderValue = lastVal;
+ return false;
+ }
+
+ var lineText = line.value;
+ dumpn("*** Line text: '" + lineText + "'");
+ var firstChar = lineText.charAt(0);
+
+ // blank line means end of headers
+ if (lineText == "") {
+ // we're finished with the previous header
+ if (lastName) {
+ try {
+ headers.setHeader(lastName, lastVal, true);
+ } catch (e) {
+ dumpn("*** setHeader threw on last header, e == " + e);
+ throw HTTP_400;
+ }
+ } else {
+ // no headers in request -- valid for HTTP/1.0 requests
+ }
+
+ // either way, we're done processing headers
+ this._state = READER_IN_BODY;
+ return true;
+ } else if (firstChar == " " || firstChar == "\t") {
+ // multi-line header if we've already seen a header line
+ if (!lastName) {
+ dumpn("We don't have a header to continue!");
+ throw HTTP_400;
+ }
+
+ // append this line's text to the value; starts with SP/HT, so no need
+ // for separating whitespace
+ lastVal += lineText;
+ } else {
+ // we have a new header, so set the old one (if one existed)
+ if (lastName) {
+ try {
+ headers.setHeader(lastName, lastVal, true);
+ } catch (e) {
+ dumpn("*** setHeader threw on a header, e == " + e);
+ throw HTTP_400;
+ }
+ }
+
+ var colon = lineText.indexOf(":"); // first colon must be splitter
+ if (colon < 1) {
+ dumpn("*** No colon or missing header field-name");
+ throw HTTP_400;
+ }
+
+ // set header name, value (to be set in the next loop, usually)
+ lastName = lineText.substring(0, colon);
+ lastVal = lineText.substring(colon + 1);
+ } // empty, continuation, start of header
+ } // while (true)
+ },
+};
+
+/** The character codes for CR and LF. */
+const CR = 0x0d,
+ LF = 0x0a;
+
+/**
+ * Calculates the number of characters before the first CRLF pair in array, or
+ * -1 if the array contains no CRLF pair.
+ *
+ * @param array : Array
+ * an array of numbers in the range [0, 256), each representing a single
+ * character; the first CRLF is the lowest index i where
+ * |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
+ * if such an |i| exists, and -1 otherwise
+ * @param start : uint
+ * start index from which to begin searching in array
+ * @returns int
+ * the index of the first CRLF if any were present, -1 otherwise
+ */
+function findCRLF(array, start) {
+ for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1)) {
+ if (array[i + 1] == LF) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * A container which provides line-by-line access to the arrays of bytes with
+ * which it is seeded.
+ */
+export function LineData() {
+ /** An array of queued bytes from which to get line-based characters. */
+ this._data = [];
+
+ /** Start index from which to search for CRLF. */
+ this._start = 0;
+}
+LineData.prototype = {
+ /**
+ * Appends the bytes in the given array to the internal data cache maintained
+ * by this.
+ */
+ appendBytes(bytes) {
+ var count = bytes.length;
+ var quantum = 262144; // just above half SpiderMonkey's argument-count limit
+ if (count < quantum) {
+ Array.prototype.push.apply(this._data, bytes);
+ return;
+ }
+
+ // Large numbers of bytes may cause Array.prototype.push to be called with
+ // more arguments than the JavaScript engine supports. In that case append
+ // bytes in fixed-size amounts until all bytes are appended.
+ for (var start = 0; start < count; start += quantum) {
+ var slice = bytes.slice(start, Math.min(start + quantum, count));
+ Array.prototype.push.apply(this._data, slice);
+ }
+ },
+
+ /**
+ * Removes and returns a line of data, delimited by CRLF, from this.
+ *
+ * @param out
+ * an object whose "value" property will be set to the first line of text
+ * present in this, sans CRLF, if this contains a full CRLF-delimited line
+ * of text; if this doesn't contain enough data, the value of the property
+ * is undefined
+ * @returns boolean
+ * true if a full line of data could be read from the data in this, false
+ * otherwise
+ */
+ readLine(out) {
+ var data = this._data;
+ var length = findCRLF(data, this._start);
+ if (length < 0) {
+ this._start = data.length;
+
+ // But if our data ends in a CR, we have to back up one, because
+ // the first byte in the next packet might be an LF and if we
+ // start looking at data.length we won't find it.
+ if (data.length && data[data.length - 1] === CR) {
+ --this._start;
+ }
+
+ return false;
+ }
+
+ // Reset for future lines.
+ this._start = 0;
+
+ //
+ // We have the index of the CR, so remove all the characters, including
+ // CRLF, from the array with splice, and convert the removed array
+ // (excluding the trailing CRLF characters) into the corresponding string.
+ //
+ var leading = data.splice(0, length + 2);
+ var quantum = 262144;
+ var line = "";
+ for (var start = 0; start < length; start += quantum) {
+ var slice = leading.slice(start, Math.min(start + quantum, length));
+ line += String.fromCharCode.apply(null, slice);
+ }
+
+ out.value = line;
+ return true;
+ },
+
+ /**
+ * Removes the bytes currently within this and returns them in an array.
+ *
+ * @returns Array
+ * the bytes within this when this method is called
+ */
+ purge() {
+ var data = this._data;
+ this._data = [];
+ return data;
+ },
+};
+
+/**
+ * Creates a request-handling function for an nsIHttpRequestHandler object.
+ */
+function createHandlerFunc(handler) {
+ return function (metadata, response) {
+ handler.handle(metadata, response);
+ };
+}
+
+/**
+ * The default handler for directories; writes an HTML response containing a
+ * slightly-formatted directory listing.
+ */
+function defaultIndexHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var path = htmlEscape(decodeURI(metadata.path));
+
+ //
+ // Just do a very basic bit of directory listings -- no need for too much
+ // fanciness, especially since we don't have a style sheet in which we can
+ // stick rules (don't want to pollute the default path-space).
+ //
+
+ var body =
+ "<html>\
+ <head>\
+ <title>" +
+ path +
+ "</title>\
+ </head>\
+ <body>\
+ <h1>" +
+ path +
+ '</h1>\
+ <ol style="list-style-type: none">';
+
+ var directory = metadata.getProperty("directory");
+ NS_ASSERT(directory && directory.isDirectory());
+
+ var fileList = [];
+ var files = directory.directoryEntries;
+ while (files.hasMoreElements()) {
+ var f = files.nextFile;
+ let name = f.leafName;
+ if (
+ !f.isHidden() &&
+ (name.charAt(name.length - 1) != HIDDEN_CHAR ||
+ name.charAt(name.length - 2) == HIDDEN_CHAR)
+ ) {
+ fileList.push(f);
+ }
+ }
+
+ fileList.sort(fileSort);
+
+ for (var i = 0; i < fileList.length; i++) {
+ var file = fileList[i];
+ try {
+ let name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR) {
+ name = name.substring(0, name.length - 1);
+ }
+ var sep = file.isDirectory() ? "/" : "";
+
+ // Note: using " to delimit the attribute here because encodeURIComponent
+ // passes through '.
+ var item =
+ '<li><a href="' +
+ encodeURIComponent(name) +
+ sep +
+ '">' +
+ htmlEscape(name) +
+ sep +
+ "</a></li>";
+
+ body += item;
+ } catch (e) {
+ /* some file system error, ignore the file */
+ }
+ }
+
+ body +=
+ " </ol>\
+ </body>\
+ </html>";
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+/**
+ * Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
+ */
+function fileSort(a, b) {
+ var dira = a.isDirectory(),
+ dirb = b.isDirectory();
+
+ if (dira && !dirb) {
+ return -1;
+ }
+ if (dirb && !dira) {
+ return 1;
+ }
+
+ var namea = a.leafName.toLowerCase(),
+ nameb = b.leafName.toLowerCase();
+ return nameb > namea ? -1 : 1;
+}
+
+/**
+ * Converts an externally-provided path into an internal path for use in
+ * determining file mappings.
+ *
+ * @param path
+ * the path to convert
+ * @param encoded
+ * true if the given path should be passed through decodeURI prior to
+ * conversion
+ * @throws URIError
+ * if path is incorrectly encoded
+ */
+function toInternalPath(path, encoded) {
+ if (encoded) {
+ path = decodeURI(path);
+ }
+
+ var comps = path.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++) {
+ var comp = comps[i];
+ if (comp.charAt(comp.length - 1) == HIDDEN_CHAR) {
+ comps[i] = comp + HIDDEN_CHAR;
+ }
+ }
+ return comps.join("/");
+}
+
+const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
+
+/**
+ * Adds custom-specified headers for the given file to the given response, if
+ * any such headers are specified.
+ *
+ * @param file
+ * the file on the disk which is to be written
+ * @param metadata
+ * metadata about the incoming request
+ * @param response
+ * the Response to which any specified headers/data should be written
+ * @throws HTTP_500
+ * if an error occurred while processing custom-specified headers
+ */
+function maybeAddHeadersInternal(
+ file,
+ metadata,
+ response,
+ informationalResponse
+) {
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR) {
+ name = name.substring(0, name.length - 1);
+ }
+
+ var headerFile = file.parent;
+ if (!informationalResponse) {
+ headerFile.append(name + HEADERS_SUFFIX);
+ } else {
+ headerFile.append(name + INFORMATIONAL_RESPONSE_SUFFIX);
+ }
+
+ if (!headerFile.exists()) {
+ return;
+ }
+
+ const PR_RDONLY = 0x01;
+ var fis = new FileInputStream(
+ headerFile,
+ PR_RDONLY,
+ PERMS_READONLY,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF
+ );
+
+ try {
+ var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
+ lis.QueryInterface(Ci.nsIUnicharLineInputStream);
+
+ var line = { value: "" };
+ var more = lis.readLine(line);
+
+ if (!more && line.value == "") {
+ return;
+ }
+
+ // request line
+
+ var status = line.value;
+ if (status.indexOf("HTTP ") == 0) {
+ status = status.substring(5);
+ var space = status.indexOf(" ");
+ var code, description;
+ if (space < 0) {
+ code = status;
+ description = "";
+ } else {
+ code = status.substring(0, space);
+ description = status.substring(space + 1, status.length);
+ }
+
+ if (!informationalResponse) {
+ response.setStatusLine(
+ metadata.httpVersion,
+ parseInt(code, 10),
+ description
+ );
+ } else {
+ response.setInformationalResponseStatusLine(
+ metadata.httpVersion,
+ parseInt(code, 10),
+ description
+ );
+ }
+
+ line.value = "";
+ more = lis.readLine(line);
+ } else if (informationalResponse) {
+ // An informational response must have a status line.
+ return;
+ }
+
+ // headers
+ while (more || line.value != "") {
+ var header = line.value;
+ var colon = header.indexOf(":");
+
+ if (!informationalResponse) {
+ response.setHeader(
+ header.substring(0, colon),
+ header.substring(colon + 1, header.length),
+ false
+ ); // allow overriding server-set headers
+ } else {
+ response.setInformationalResponseHeader(
+ header.substring(0, colon),
+ header.substring(colon + 1, header.length),
+ false
+ ); // allow overriding server-set headers
+ }
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+ } catch (e) {
+ dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
+ throw HTTP_500;
+ } finally {
+ fis.close();
+ }
+}
+
+function maybeAddHeaders(file, metadata, response) {
+ maybeAddHeadersInternal(file, metadata, response, false);
+}
+
+function maybeAddInformationalResponse(file, metadata, response) {
+ maybeAddHeadersInternal(file, metadata, response, true);
+}
+
+/**
+ * An object which handles requests for a server, executing default and
+ * overridden behaviors as instructed by the code which uses and manipulates it.
+ * Default behavior includes the paths / and /trace (diagnostics), with some
+ * support for HTTP error pages for various codes and fallback to HTTP 500 if
+ * those codes fail for any reason.
+ *
+ * @param server : nsHttpServer
+ * the server in which this handler is being used
+ */
+function ServerHandler(server) {
+ // FIELDS
+
+ /**
+ * The nsHttpServer instance associated with this handler.
+ */
+ this._server = server;
+
+ /**
+ * A FileMap object containing the set of path->nsIFile mappings for
+ * all directory mappings set in the server (e.g., "/" for /var/www/html/,
+ * "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).
+ *
+ * Note carefully: the leading and trailing "/" in each path (not file) are
+ * removed before insertion to simplify the code which uses this. You have
+ * been warned!
+ */
+ this._pathDirectoryMap = new FileMap();
+
+ /**
+ * Custom request handlers for the server in which this resides. Path-handler
+ * pairs are stored as property-value pairs in this property.
+ *
+ * @see ServerHandler.prototype._defaultPaths
+ */
+ this._overridePaths = {};
+
+ /**
+ * Custom request handlers for the path prefixes on the server in which this
+ * resides. Path-handler pairs are stored as property-value pairs in this
+ * property.
+ *
+ * @see ServerHandler.prototype._defaultPaths
+ */
+ this._overridePrefixes = {};
+
+ /**
+ * Custom request handlers for the error handlers in the server in which this
+ * resides. Path-handler pairs are stored as property-value pairs in this
+ * property.
+ *
+ * @see ServerHandler.prototype._defaultErrors
+ */
+ this._overrideErrors = {};
+
+ /**
+ * Maps file extensions to their MIME types in the server, overriding any
+ * mapping that might or might not exist in the MIME service.
+ */
+ this._mimeMappings = {};
+
+ /**
+ * The default handler for requests for directories, used to serve directories
+ * when no index file is present.
+ */
+ this._indexHandler = defaultIndexHandler;
+
+ /** Per-path state storage for the server. */
+ this._state = {};
+
+ /** Entire-server state storage. */
+ this._sharedState = {};
+
+ /** Entire-server state storage for nsISupports values. */
+ this._objectState = {};
+}
+ServerHandler.prototype = {
+ // PUBLIC API
+
+ /**
+ * Handles a request to this server, responding to the request appropriately
+ * and initiating server shutdown if necessary.
+ *
+ * This method never throws an exception.
+ *
+ * @param connection : Connection
+ * the connection for this request
+ */
+ handleResponse(connection) {
+ var request = connection.request;
+ var response = new Response(connection);
+
+ var path = request.path;
+ dumpn("*** path == " + path);
+
+ try {
+ try {
+ if (path in this._overridePaths) {
+ // explicit paths first, then files based on existing directory mappings,
+ // then (if the file doesn't exist) built-in server default paths
+ dumpn("calling override for " + path);
+ this._overridePaths[path](request, response);
+ } else {
+ var longestPrefix = "";
+ for (let prefix in this._overridePrefixes) {
+ if (
+ prefix.length > longestPrefix.length &&
+ path.substr(0, prefix.length) == prefix
+ ) {
+ longestPrefix = prefix;
+ }
+ }
+ if (longestPrefix.length) {
+ dumpn("calling prefix override for " + longestPrefix);
+ this._overridePrefixes[longestPrefix](request, response);
+ } else {
+ this._handleDefault(request, response);
+ }
+ }
+ } catch (e) {
+ if (response.partiallySent()) {
+ response.abort(e);
+ return;
+ }
+
+ if (!(e instanceof HttpError)) {
+ dumpn("*** unexpected error: e == " + e);
+ throw HTTP_500;
+ }
+ if (e.code !== 404) {
+ throw e;
+ }
+
+ dumpn("*** default: " + (path in this._defaultPaths));
+
+ response = new Response(connection);
+ if (path in this._defaultPaths) {
+ this._defaultPaths[path](request, response);
+ } else {
+ throw HTTP_404;
+ }
+ }
+ } catch (e) {
+ if (response.partiallySent()) {
+ response.abort(e);
+ return;
+ }
+
+ var errorCode = "internal";
+
+ try {
+ if (!(e instanceof HttpError)) {
+ throw e;
+ }
+
+ errorCode = e.code;
+ dumpn("*** errorCode == " + errorCode);
+
+ response = new Response(connection);
+ if (e.customErrorHandling) {
+ e.customErrorHandling(response);
+ }
+ this._handleError(errorCode, request, response);
+ return;
+ } catch (e2) {
+ dumpn(
+ "*** error handling " +
+ errorCode +
+ " error: " +
+ "e2 == " +
+ e2 +
+ ", shutting down server"
+ );
+
+ connection.server._requestQuit();
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile(path, file, handler) {
+ if (!file) {
+ dumpn("*** unregistering '" + path + "' mapping");
+ delete this._overridePaths[path];
+ return;
+ }
+
+ dumpn("*** registering '" + path + "' as mapping to " + file.path);
+ file = file.clone();
+
+ var self = this;
+ this._overridePaths[path] = function (request, response) {
+ if (!file.exists()) {
+ throw HTTP_404;
+ }
+
+ dumpn("*** responding '" + path + "' as mapping to " + file.path);
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ if (typeof handler === "function") {
+ handler(request, response);
+ }
+ self._writeFileResponse(request, file, response, 0, file.fileSize);
+ };
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler(path, handler) {
+ if (!path.length) {
+ throw Components.Exception(
+ "Handler path cannot be empty",
+ Cr.NS_ERROR_INVALID_ARG
+ );
+ }
+
+ // XXX true path validation!
+ if (path.charAt(0) != "/" && path != "CONNECT") {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ this._handlerToField(handler, this._overridePaths, path);
+ },
+
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler(path, handler) {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/") {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ this._handlerToField(handler, this._overridePrefixes, path);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory(path, directory) {
+ // strip off leading and trailing '/' so that we can use lastIndexOf when
+ // determining exactly how a path maps onto a mapped directory --
+ // conditional is required here to deal with "/".substring(1, 0) being
+ // converted to "/".substring(0, 1) per the JS specification
+ var key = path.length == 1 ? "" : path.substring(1, path.length - 1);
+
+ // the path-to-directory mapping code requires that the first character not
+ // be "/", or it will go into an infinite loop
+ if (key.charAt(0) == "/") {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ key = toInternalPath(key, false);
+
+ if (directory) {
+ dumpn("*** mapping '" + path + "' to the location " + directory.path);
+ this._pathDirectoryMap.put(key, directory);
+ } else {
+ dumpn("*** removing mapping for '" + path + "'");
+ this._pathDirectoryMap.put(key, null);
+ }
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler(err, handler) {
+ if (!(err in HTTP_ERROR_CODES)) {
+ dumpn(
+ "*** WARNING: registering non-HTTP/1.1 error code " +
+ "(" +
+ err +
+ ") handler -- was this intentional?"
+ );
+ }
+
+ this._handlerToField(handler, this._overrideErrors, err);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler(handler) {
+ if (!handler) {
+ handler = defaultIndexHandler;
+ } else if (typeof handler != "function") {
+ handler = createHandlerFunc(handler);
+ }
+
+ this._indexHandler = handler;
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType(ext, type) {
+ if (!type) {
+ delete this._mimeMappings[ext];
+ } else {
+ this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
+ }
+ },
+
+ // PRIVATE API
+
+ /**
+ * Sets or remove (if handler is null) a handler in an object with a key.
+ *
+ * @param handler
+ * a handler, either function or an nsIHttpRequestHandler
+ * @param dict
+ * The object to attach the handler to.
+ * @param key
+ * The field name of the handler.
+ */
+ _handlerToField(handler, dict, key) {
+ // for convenience, handler can be a function if this is run from xpcshell
+ if (typeof handler == "function") {
+ dict[key] = handler;
+ } else if (handler) {
+ dict[key] = createHandlerFunc(handler);
+ } else {
+ delete dict[key];
+ }
+ },
+
+ /**
+ * Handles a request which maps to a file in the local filesystem (if a base
+ * path has already been set; otherwise the 404 error is thrown).
+ *
+ * @param metadata : Request
+ * metadata for the incoming request
+ * @param response : Response
+ * an uninitialized Response to the given request, to be initialized by a
+ * request handler
+ * @throws HTTP_###
+ * if an HTTP error occurred (usually HTTP_404); note that in this case the
+ * calling code must handle post-processing of the response
+ */
+ _handleDefault(metadata, response) {
+ dumpn("*** _handleDefault()");
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+
+ var path = metadata.path;
+ NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");
+
+ // determine the actual on-disk file; this requires finding the deepest
+ // path-to-directory mapping in the requested URL
+ var file = this._getFileForPath(path);
+
+ // the "file" might be a directory, in which case we either serve the
+ // contained index.html or make the index handler write the response
+ if (file.exists() && file.isDirectory()) {
+ file.append("index.html"); // make configurable?
+ if (!file.exists() || file.isDirectory()) {
+ metadata._ensurePropertyBag();
+ metadata._bag.setPropertyAsInterface("directory", file.parent);
+ this._indexHandler(metadata, response);
+ return;
+ }
+ }
+
+ // alternately, the file might not exist
+ if (!file.exists()) {
+ throw HTTP_404;
+ }
+
+ var start, end;
+ if (
+ metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
+ metadata.hasHeader("Range") &&
+ this._getTypeFromFile(file) !== SJS_TYPE
+ ) {
+ var rangeMatch = metadata
+ .getHeader("Range")
+ .match(/^bytes=(\d+)?-(\d+)?$/);
+ if (!rangeMatch) {
+ dumpn(
+ "*** Range header bogosity: '" + metadata.getHeader("Range") + "'"
+ );
+ throw HTTP_400;
+ }
+
+ if (rangeMatch[1] !== undefined) {
+ start = parseInt(rangeMatch[1], 10);
+ }
+
+ if (rangeMatch[2] !== undefined) {
+ end = parseInt(rangeMatch[2], 10);
+ }
+
+ if (start === undefined && end === undefined) {
+ dumpn(
+ "*** More Range header bogosity: '" +
+ metadata.getHeader("Range") +
+ "'"
+ );
+ throw HTTP_400;
+ }
+
+ // No start given, so the end is really the count of bytes from the
+ // end of the file.
+ if (start === undefined) {
+ start = Math.max(0, file.fileSize - end);
+ end = file.fileSize - 1;
+ }
+
+ // start and end are inclusive
+ if (end === undefined || end >= file.fileSize) {
+ end = file.fileSize - 1;
+ }
+
+ if (start !== undefined && start >= file.fileSize) {
+ var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
+ HTTP_416.customErrorHandling = function (errorResponse) {
+ maybeAddHeaders(file, metadata, errorResponse);
+ };
+ throw HTTP_416;
+ }
+
+ if (end < start) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ start = 0;
+ end = file.fileSize - 1;
+ } else {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
+ response.setHeader("Content-Range", contentRange);
+ }
+ } else {
+ start = 0;
+ end = file.fileSize - 1;
+ }
+
+ // finally...
+ dumpn(
+ "*** handling '" +
+ path +
+ "' as mapping to " +
+ file.path +
+ " from " +
+ start +
+ " to " +
+ end +
+ " inclusive"
+ );
+ this._writeFileResponse(metadata, file, response, start, end - start + 1);
+ },
+
+ /**
+ * Writes an HTTP response for the given file, including setting headers for
+ * file metadata.
+ *
+ * @param metadata : Request
+ * the Request for which a response is being generated
+ * @param file : nsIFile
+ * the file which is to be sent in the response
+ * @param response : Response
+ * the response to which the file should be written
+ * @param offset: uint
+ * the byte offset to skip to when writing
+ * @param count: uint
+ * the number of bytes to write
+ */
+ _writeFileResponse(metadata, file, response, offset, count) {
+ const PR_RDONLY = 0x01;
+
+ var type = this._getTypeFromFile(file);
+ if (type === SJS_TYPE) {
+ let fis = new FileInputStream(
+ file,
+ PR_RDONLY,
+ PERMS_READONLY,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF
+ );
+
+ try {
+ // If you update the list of imports, please update the list in
+ // tools/lint/eslint/eslint-plugin-mozilla/lib/environments/sjs.js
+ // as well.
+ var s = Cu.Sandbox(Cu.getGlobalForObject({}), {
+ wantGlobalProperties: [
+ "atob",
+ "btoa",
+ "ChromeUtils",
+ "IOUtils",
+ "PathUtils",
+ "TextDecoder",
+ "TextEncoder",
+ "URLSearchParams",
+ "URL",
+ ],
+ });
+ s.importFunction(dump, "dump");
+ s.importFunction(Services, "Services");
+
+ // Define a basic key-value state-preservation API across requests, with
+ // keys initially corresponding to the empty string.
+ var self = this;
+ var path = metadata.path;
+ s.importFunction(function getState(k) {
+ return self._getState(path, k);
+ });
+ s.importFunction(function setState(k, v) {
+ self._setState(path, k, v);
+ });
+ s.importFunction(function getSharedState(k) {
+ return self._getSharedState(k);
+ });
+ s.importFunction(function setSharedState(k, v) {
+ self._setSharedState(k, v);
+ });
+ s.importFunction(function getObjectState(k, callback) {
+ callback(self._getObjectState(k));
+ });
+ s.importFunction(function setObjectState(k, v) {
+ self._setObjectState(k, v);
+ });
+ s.importFunction(function registerPathHandler(p, h) {
+ self.registerPathHandler(p, h);
+ });
+
+ // Make it possible for sjs files to access their location
+ this._setState(path, "__LOCATION__", file.path);
+
+ try {
+ // Alas, the line number in errors dumped to console when calling the
+ // request handler is simply an offset from where we load the SJS file.
+ // Work around this in a reasonably non-fragile way by dynamically
+ // getting the line number where we evaluate the SJS file. Don't
+ // separate these two lines!
+ var line = new Error().lineNumber;
+ let uri = Services.io.newFileURI(file);
+ Services.scriptloader.loadSubScript(uri.spec, s);
+ } catch (e) {
+ dumpn("*** syntax error in SJS at " + file.path + ": " + e);
+ throw HTTP_500;
+ }
+
+ try {
+ s.handleRequest(metadata, response);
+ } catch (e) {
+ dump(
+ "*** error running SJS at " +
+ file.path +
+ ": " +
+ e +
+ " on line " +
+ (e instanceof Error
+ ? e.lineNumber + " in httpd.js"
+ : e.lineNumber - line) +
+ "\n"
+ );
+ throw HTTP_500;
+ }
+ } finally {
+ fis.close();
+ }
+ } else {
+ try {
+ response.setHeader(
+ "Last-Modified",
+ toDateString(file.lastModifiedTime),
+ false
+ );
+ } catch (e) {
+ /* lastModifiedTime threw, ignore */
+ }
+
+ response.setHeader("Content-Type", type, false);
+ maybeAddInformationalResponse(file, metadata, response);
+ maybeAddHeaders(file, metadata, response);
+ // Allow overriding Content-Length
+ try {
+ response.getHeader("Content-Length");
+ } catch (e) {
+ response.setHeader("Content-Length", "" + count, false);
+ }
+
+ let fis = new FileInputStream(
+ file,
+ PR_RDONLY,
+ PERMS_READONLY,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF
+ );
+
+ offset = offset || 0;
+ count = count || file.fileSize;
+ NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
+ NS_ASSERT(count >= 0, "bad count");
+ NS_ASSERT(offset + count <= file.fileSize, "bad total data size");
+
+ try {
+ if (offset !== 0) {
+ // Seek (or read, if seeking isn't supported) to the correct offset so
+ // the data sent to the client matches the requested range.
+ if (fis instanceof Ci.nsISeekableStream) {
+ fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
+ } else {
+ new ScriptableInputStream(fis).read(offset);
+ }
+ }
+ } catch (e) {
+ fis.close();
+ throw e;
+ }
+
+ let writeMore = function () {
+ Services.tm.currentThread.dispatch(
+ writeData,
+ Ci.nsIThread.DISPATCH_NORMAL
+ );
+ };
+
+ var input = new BinaryInputStream(fis);
+ var output = new BinaryOutputStream(response.bodyOutputStream);
+ var writeData = {
+ run() {
+ var chunkSize = Math.min(65536, count);
+ count -= chunkSize;
+ NS_ASSERT(count >= 0, "underflow");
+
+ try {
+ var data = input.readByteArray(chunkSize);
+ NS_ASSERT(
+ data.length === chunkSize,
+ "incorrect data returned? got " +
+ data.length +
+ ", expected " +
+ chunkSize
+ );
+ output.writeByteArray(data);
+ if (count === 0) {
+ fis.close();
+ response.finish();
+ } else {
+ writeMore();
+ }
+ } catch (e) {
+ try {
+ fis.close();
+ } finally {
+ response.finish();
+ }
+ throw e;
+ }
+ },
+ };
+
+ writeMore();
+
+ // Now that we know copying will start, flag the response as async.
+ response.processAsync();
+ }
+ },
+
+ /**
+ * Get the value corresponding to a given key for the given path for SJS state
+ * preservation across requests.
+ *
+ * @param path : string
+ * the path from which the given state is to be retrieved
+ * @param k : string
+ * the key whose corresponding value is to be returned
+ * @returns string
+ * the corresponding value, which is initially the empty string
+ */
+ _getState(path, k) {
+ var state = this._state;
+ if (path in state && k in state[path]) {
+ return state[path][k];
+ }
+ return "";
+ },
+
+ /**
+ * Set the value corresponding to a given key for the given path for SJS state
+ * preservation across requests.
+ *
+ * @param path : string
+ * the path from which the given state is to be retrieved
+ * @param k : string
+ * the key whose corresponding value is to be set
+ * @param v : string
+ * the value to be set
+ */
+ _setState(path, k, v) {
+ if (typeof v !== "string") {
+ throw new Error("non-string value passed");
+ }
+ var state = this._state;
+ if (!(path in state)) {
+ state[path] = {};
+ }
+ state[path][k] = v;
+ },
+
+ /**
+ * Get the value corresponding to a given key for SJS state preservation
+ * across requests.
+ *
+ * @param k : string
+ * the key whose corresponding value is to be returned
+ * @returns string
+ * the corresponding value, which is initially the empty string
+ */
+ _getSharedState(k) {
+ var state = this._sharedState;
+ if (k in state) {
+ return state[k];
+ }
+ return "";
+ },
+
+ /**
+ * Set the value corresponding to a given key for SJS state preservation
+ * across requests.
+ *
+ * @param k : string
+ * the key whose corresponding value is to be set
+ * @param v : string
+ * the value to be set
+ */
+ _setSharedState(k, v) {
+ if (typeof v !== "string") {
+ throw new Error("non-string value passed");
+ }
+ this._sharedState[k] = v;
+ },
+
+ /**
+ * Returns the object associated with the given key in the server for SJS
+ * state preservation across requests.
+ *
+ * @param k : string
+ * the key whose corresponding object is to be returned
+ * @returns nsISupports
+ * the corresponding object, or null if none was present
+ */
+ _getObjectState(k) {
+ if (typeof k !== "string") {
+ throw new Error("non-string key passed");
+ }
+ return this._objectState[k] || null;
+ },
+
+ /**
+ * Sets the object associated with the given key in the server for SJS
+ * state preservation across requests.
+ *
+ * @param k : string
+ * the key whose corresponding object is to be set
+ * @param v : nsISupports
+ * the object to be associated with the given key; may be null
+ */
+ _setObjectState(k, v) {
+ if (typeof k !== "string") {
+ throw new Error("non-string key passed");
+ }
+ if (typeof v !== "object") {
+ throw new Error("non-object value passed");
+ }
+ if (v && !("QueryInterface" in v)) {
+ throw new Error(
+ "must pass an nsISupports; use wrappedJSObject to ease " +
+ "pain when using the server from JS"
+ );
+ }
+
+ this._objectState[k] = v;
+ },
+
+ /**
+ * Gets a content-type for the given file, first by checking for any custom
+ * MIME-types registered with this handler for the file's extension, second by
+ * asking the global MIME service for a content-type, and finally by failing
+ * over to application/octet-stream.
+ *
+ * @param file : nsIFile
+ * the nsIFile for which to get a file type
+ * @returns string
+ * the best content-type which can be determined for the file
+ */
+ _getTypeFromFile(file) {
+ try {
+ var name = file.leafName;
+ var dot = name.lastIndexOf(".");
+ if (dot > 0) {
+ var ext = name.slice(dot + 1);
+ if (ext in this._mimeMappings) {
+ return this._mimeMappings[ext];
+ }
+ }
+ return Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(file);
+ } catch (e) {
+ return "application/octet-stream";
+ }
+ },
+
+ /**
+ * Returns the nsIFile which corresponds to the path, as determined using
+ * all registered path->directory mappings and any paths which are explicitly
+ * overridden.
+ *
+ * @param path : string
+ * the server path for which a file should be retrieved, e.g. "/foo/bar"
+ * @throws HttpError
+ * when the correct action is the corresponding HTTP error (i.e., because no
+ * mapping was found for a directory in path, the referenced file doesn't
+ * exist, etc.)
+ * @returns nsIFile
+ * the file to be sent as the response to a request for the path
+ */
+ _getFileForPath(path) {
+ // decode and add underscores as necessary
+ try {
+ path = toInternalPath(path, true);
+ } catch (e) {
+ dumpn("*** toInternalPath threw " + e);
+ throw HTTP_400; // malformed path
+ }
+
+ // next, get the directory which contains this path
+ var pathMap = this._pathDirectoryMap;
+
+ // An example progression of tmp for a path "/foo/bar/baz/" might be:
+ // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
+ var tmp = path.substring(1);
+ while (true) {
+ // do we have a match for current head of the path?
+ var file = pathMap.get(tmp);
+ if (file) {
+ // XXX hack; basically disable showing mapping for /foo/bar/ when the
+ // requested path was /foo/bar, because relative links on the page
+ // will all be incorrect -- we really need the ability to easily
+ // redirect here instead
+ if (
+ tmp == path.substring(1) &&
+ !!tmp.length &&
+ tmp.charAt(tmp.length - 1) != "/"
+ ) {
+ file = null;
+ } else {
+ break;
+ }
+ }
+
+ // if we've finished trying all prefixes, exit
+ if (tmp == "") {
+ break;
+ }
+
+ tmp = tmp.substring(0, tmp.lastIndexOf("/"));
+ }
+
+ // no mapping applies, so 404
+ if (!file) {
+ throw HTTP_404;
+ }
+
+ // last, get the file for the path within the determined directory
+ var parentFolder = file.parent;
+ var dirIsRoot = parentFolder == null;
+
+ // Strategy here is to append components individually, making sure we
+ // never move above the given directory; this allows paths such as
+ // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";
+ // this component-wise approach also means the code works even on platforms
+ // which don't use "/" as the directory separator, such as Windows
+ var leafPath = path.substring(tmp.length + 1);
+ var comps = leafPath.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++) {
+ var comp = comps[i];
+
+ if (comp == "..") {
+ file = file.parent;
+ } else if (comp == "." || comp == "") {
+ continue;
+ } else {
+ file.append(comp);
+ }
+
+ if (!dirIsRoot && file.equals(parentFolder)) {
+ throw HTTP_403;
+ }
+ }
+
+ return file;
+ },
+
+ /**
+ * Writes the error page for the given HTTP error code over the given
+ * connection.
+ *
+ * @param errorCode : uint
+ * the HTTP error code to be used
+ * @param connection : Connection
+ * the connection on which the error occurred
+ */
+ handleError(errorCode, connection) {
+ var response = new Response(connection);
+
+ dumpn("*** error in request: " + errorCode);
+
+ this._handleError(errorCode, new Request(connection.port), response);
+ },
+
+ /**
+ * Handles a request which generates the given error code, using the
+ * user-defined error handler if one has been set, gracefully falling back to
+ * the x00 status code if the code has no handler, and failing to status code
+ * 500 if all else fails.
+ *
+ * @param errorCode : uint
+ * the HTTP error which is to be returned
+ * @param metadata : Request
+ * metadata for the request, which will often be incomplete since this is an
+ * error
+ * @param response : Response
+ * an uninitialized Response should be initialized when this method
+ * completes with information which represents the desired error code in the
+ * ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
+ * fallback for 505, per HTTP specs)
+ */
+ _handleError(errorCode, metadata, response) {
+ if (!metadata) {
+ throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ var errorX00 = errorCode - (errorCode % 100);
+
+ try {
+ if (!(errorCode in HTTP_ERROR_CODES)) {
+ dumpn("*** WARNING: requested invalid error: " + errorCode);
+ }
+
+ // RFC 2616 says that we should try to handle an error by its class if we
+ // can't otherwise handle it -- if that fails, we revert to handling it as
+ // a 500 internal server error, and if that fails we throw and shut down
+ // the server
+
+ // actually handle the error
+ try {
+ if (errorCode in this._overrideErrors) {
+ this._overrideErrors[errorCode](metadata, response);
+ } else {
+ this._defaultErrors[errorCode](metadata, response);
+ }
+ } catch (e) {
+ if (response.partiallySent()) {
+ response.abort(e);
+ return;
+ }
+
+ // don't retry the handler that threw
+ if (errorX00 == errorCode) {
+ throw HTTP_500;
+ }
+
+ dumpn(
+ "*** error in handling for error code " +
+ errorCode +
+ ", " +
+ "falling back to " +
+ errorX00 +
+ "..."
+ );
+ response = new Response(response._connection);
+ if (errorX00 in this._overrideErrors) {
+ this._overrideErrors[errorX00](metadata, response);
+ } else if (errorX00 in this._defaultErrors) {
+ this._defaultErrors[errorX00](metadata, response);
+ } else {
+ throw HTTP_500;
+ }
+ }
+ } catch (e) {
+ if (response.partiallySent()) {
+ response.abort();
+ return;
+ }
+
+ // we've tried everything possible for a meaningful error -- now try 500
+ dumpn(
+ "*** error in handling for error code " +
+ errorX00 +
+ ", falling " +
+ "back to 500..."
+ );
+
+ try {
+ response = new Response(response._connection);
+ if (500 in this._overrideErrors) {
+ this._overrideErrors[500](metadata, response);
+ } else {
+ this._defaultErrors[500](metadata, response);
+ }
+ } catch (e2) {
+ dumpn("*** multiple errors in default error handlers!");
+ dumpn("*** e == " + e + ", e2 == " + e2);
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ // FIELDS
+
+ /**
+ * This object contains the default handlers for the various HTTP error codes.
+ */
+ _defaultErrors: {
+ 400(metadata, response) {
+ // none of the data in metadata is reliable, so hard-code everything here
+ response.setStatusLine("1.1", 400, "Bad Request");
+ response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
+
+ var body = "Bad request\n";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 403(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body =
+ "<html>\
+ <head><title>403 Forbidden</title></head>\
+ <body>\
+ <h1>403 Forbidden</h1>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 404(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body =
+ "<html>\
+ <head><title>404 Not Found</title></head>\
+ <body>\
+ <h1>404 Not Found</h1>\
+ <p>\
+ <span style='font-family: monospace;'>" +
+ htmlEscape(metadata.path) +
+ "</span> was not found.\
+ </p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 416(metadata, response) {
+ response.setStatusLine(
+ metadata.httpVersion,
+ 416,
+ "Requested Range Not Satisfiable"
+ );
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body =
+ "<html>\
+ <head>\
+ <title>416 Requested Range Not Satisfiable</title></head>\
+ <body>\
+ <h1>416 Requested Range Not Satisfiable</h1>\
+ <p>The byte range was not valid for the\
+ requested resource.\
+ </p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 500(metadata, response) {
+ response.setStatusLine(
+ metadata.httpVersion,
+ 500,
+ "Internal Server Error"
+ );
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body =
+ "<html>\
+ <head><title>500 Internal Server Error</title></head>\
+ <body>\
+ <h1>500 Internal Server Error</h1>\
+ <p>Something's broken in this server and\
+ needs to be fixed.</p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 501(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body =
+ "<html>\
+ <head><title>501 Not Implemented</title></head>\
+ <body>\
+ <h1>501 Not Implemented</h1>\
+ <p>This server is not (yet) Apache.</p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 505(metadata, response) {
+ response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body =
+ "<html>\
+ <head><title>505 HTTP Version Not Supported</title></head>\
+ <body>\
+ <h1>505 HTTP Version Not Supported</h1>\
+ <p>This server only supports HTTP/1.0 and HTTP/1.1\
+ connections.</p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ },
+
+ /**
+ * Contains handlers for the default set of URIs contained in this server.
+ */
+ _defaultPaths: {
+ "/": function (metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body =
+ "<html>\
+ <head><title>httpd.js</title></head>\
+ <body>\
+ <h1>httpd.js</h1>\
+ <p>If you're seeing this page, httpd.js is up and\
+ serving requests! Now set a base path and serve some\
+ files!</p>\
+ </body>\
+ </html>";
+
+ response.bodyOutputStream.write(body, body.length);
+ },
+
+ "/trace": function (metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
+
+ var body =
+ "Request-URI: " +
+ metadata.scheme +
+ "://" +
+ metadata.host +
+ ":" +
+ metadata.port +
+ metadata.path +
+ "\n\n";
+ body += "Request (semantically equivalent, slightly reformatted):\n\n";
+ body += metadata.method + " " + metadata.path;
+
+ if (metadata.queryString) {
+ body += "?" + metadata.queryString;
+ }
+
+ body += " HTTP/" + metadata.httpVersion + "\r\n";
+
+ var headEnum = metadata.headers;
+ while (headEnum.hasMoreElements()) {
+ var fieldName = headEnum
+ .getNext()
+ .QueryInterface(Ci.nsISupportsString).data;
+ body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+ },
+ },
+};
+
+/**
+ * Maps absolute paths to files on the local file system (as nsILocalFiles).
+ */
+function FileMap() {
+ /** Hash which will map paths to nsILocalFiles. */
+ this._map = {};
+}
+FileMap.prototype = {
+ // PUBLIC API
+
+ /**
+ * Maps key to a clone of the nsIFile value if value is non-null;
+ * otherwise, removes any extant mapping for key.
+ *
+ * @param key : string
+ * string to which a clone of value is mapped
+ * @param value : nsIFile
+ * the file to map to key, or null to remove a mapping
+ */
+ put(key, value) {
+ if (value) {
+ this._map[key] = value.clone();
+ } else {
+ delete this._map[key];
+ }
+ },
+
+ /**
+ * Returns a clone of the nsIFile mapped to key, or null if no such
+ * mapping exists.
+ *
+ * @param key : string
+ * key to which the returned file maps
+ * @returns nsIFile
+ * a clone of the mapped file, or null if no mapping exists
+ */
+ get(key) {
+ var val = this._map[key];
+ return val ? val.clone() : null;
+ },
+};
+
+// Response CONSTANTS
+
+// token = *<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (0-127)>
+// CTL = <any US-ASCII control character (0-31) and DEL (127)>
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+const IS_TOKEN_ARRAY = [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0, // 0
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0, // 8
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0, // 16
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0, // 24
+
+ 0,
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 32
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0, // 40
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 48
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0, // 56
+
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 64
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 72
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 80
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1, // 88
+
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 96
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 104
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1, // 112
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 0,
+ 1,
+]; // 120
+
+/**
+ * Determines whether the given character code is a CTL.
+ *
+ * @param code : uint
+ * the character code
+ * @returns boolean
+ * true if code is a CTL, false otherwise
+ */
+function isCTL(code) {
+ return (code >= 0 && code <= 31) || code == 127;
+}
+
+/**
+ * Represents a response to an HTTP request, encapsulating all details of that
+ * response. This includes all headers, the HTTP version, status code and
+ * explanation, and the entity itself.
+ *
+ * @param connection : Connection
+ * the connection over which this response is to be written
+ */
+function Response(connection) {
+ /** The connection over which this response will be written. */
+ this._connection = connection;
+
+ /**
+ * The HTTP version of this response; defaults to 1.1 if not set by the
+ * handler.
+ */
+ this._httpVersion = nsHttpVersion.HTTP_1_1;
+
+ /**
+ * The HTTP code of this response; defaults to 200.
+ */
+ this._httpCode = 200;
+
+ /**
+ * The description of the HTTP code in this response; defaults to "OK".
+ */
+ this._httpDescription = "OK";
+
+ /**
+ * An nsIHttpHeaders object in which the headers in this response should be
+ * stored. This property is null after the status line and headers have been
+ * written to the network, and it may be modified up until it is cleared,
+ * except if this._finished is set first (in which case headers are written
+ * asynchronously in response to a finish() call not preceded by
+ * flushHeaders()).
+ */
+ this._headers = new nsHttpHeaders();
+
+ /**
+ * Informational response:
+ * For example 103 Early Hint
+ **/
+ this._informationalResponseHttpVersion = nsHttpVersion.HTTP_1_1;
+ this._informationalResponseHttpCode = 0;
+ this._informationalResponseHttpDescription = "";
+ this._informationalResponseHeaders = new nsHttpHeaders();
+ this._informationalResponseSet = false;
+
+ /**
+ * Set to true when this response is ended (completely constructed if possible
+ * and the connection closed); further actions on this will then fail.
+ */
+ this._ended = false;
+
+ /**
+ * A stream used to hold data written to the body of this response.
+ */
+ this._bodyOutputStream = null;
+
+ /**
+ * A stream containing all data that has been written to the body of this
+ * response so far. (Async handlers make the data contained in this
+ * unreliable as a way of determining content length in general, but auxiliary
+ * saved information can sometimes be used to guarantee reliability.)
+ */
+ this._bodyInputStream = null;
+
+ /**
+ * A stream copier which copies data to the network. It is initially null
+ * until replaced with a copier for response headers; when headers have been
+ * fully sent it is replaced with a copier for the response body, remaining
+ * so for the duration of response processing.
+ */
+ this._asyncCopier = null;
+
+ /**
+ * True if this response has been designated as being processed
+ * asynchronously rather than for the duration of a single call to
+ * nsIHttpRequestHandler.handle.
+ */
+ this._processAsync = false;
+
+ /**
+ * True iff finish() has been called on this, signaling that no more changes
+ * to this may be made.
+ */
+ this._finished = false;
+
+ /**
+ * True iff powerSeized() has been called on this, signaling that this
+ * response is to be handled manually by the response handler (which may then
+ * send arbitrary data in response, even non-HTTP responses).
+ */
+ this._powerSeized = false;
+}
+Response.prototype = {
+ // PUBLIC CONSTRUCTION API
+
+ //
+ // see nsIHttpResponse.bodyOutputStream
+ //
+ get bodyOutputStream() {
+ if (this._finished) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ if (!this._bodyOutputStream) {
+ var pipe = new Pipe(
+ true,
+ false,
+ Response.SEGMENT_SIZE,
+ PR_UINT32_MAX,
+ null
+ );
+ this._bodyOutputStream = pipe.outputStream;
+ this._bodyInputStream = pipe.inputStream;
+ if (this._processAsync || this._powerSeized) {
+ this._startAsyncProcessor();
+ }
+ }
+
+ return this._bodyOutputStream;
+ },
+
+ //
+ // see nsIHttpResponse.write
+ //
+ write(data) {
+ if (this._finished) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ var dataAsString = String(data);
+ this.bodyOutputStream.write(dataAsString, dataAsString.length);
+ },
+
+ //
+ // see nsIHttpResponse.setStatusLine
+ //
+ setStatusLineInternal(httpVersion, code, description, informationalResponse) {
+ if (this._finished || this._powerSeized) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ if (!informationalResponse) {
+ if (!this._headers) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ } else if (!this._informationalResponseHeaders) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ this._ensureAlive();
+
+ if (!(code >= 0 && code < 1000)) {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ try {
+ var httpVer;
+ // avoid version construction for the most common cases
+ if (!httpVersion || httpVersion == "1.1") {
+ httpVer = nsHttpVersion.HTTP_1_1;
+ } else if (httpVersion == "1.0") {
+ httpVer = nsHttpVersion.HTTP_1_0;
+ } else {
+ httpVer = new nsHttpVersion(httpVersion);
+ }
+ } catch (e) {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // Reason-Phrase = *<TEXT, excluding CR, LF>
+ // TEXT = <any OCTET except CTLs, but including LWS>
+ //
+ // XXX this ends up disallowing octets which aren't Unicode, I think -- not
+ // much to do if description is IDL'd as string
+ if (!description) {
+ description = "";
+ }
+ for (var i = 0; i < description.length; i++) {
+ if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t") {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+ }
+
+ // set the values only after validation to preserve atomicity
+ if (!informationalResponse) {
+ this._httpDescription = description;
+ this._httpCode = code;
+ this._httpVersion = httpVer;
+ } else {
+ this._informationalResponseSet = true;
+ this._informationalResponseHttpDescription = description;
+ this._informationalResponseHttpCode = code;
+ this._informationalResponseHttpVersion = httpVer;
+ }
+ },
+
+ //
+ // see nsIHttpResponse.setStatusLine
+ //
+ setStatusLine(httpVersion, code, description) {
+ this.setStatusLineInternal(httpVersion, code, description, false);
+ },
+
+ setInformationalResponseStatusLine(httpVersion, code, description) {
+ this.setStatusLineInternal(httpVersion, code, description, true);
+ },
+
+ //
+ // see nsIHttpResponse.setHeader
+ //
+ setHeader(name, value, merge) {
+ if (!this._headers || this._finished || this._powerSeized) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ this._ensureAlive();
+
+ this._headers.setHeader(name, value, merge);
+ },
+
+ setInformationalResponseHeader(name, value, merge) {
+ if (
+ !this._informationalResponseHeaders ||
+ this._finished ||
+ this._powerSeized
+ ) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ this._ensureAlive();
+
+ this._informationalResponseHeaders.setHeader(name, value, merge);
+ },
+
+ setHeaderNoCheck(name, value) {
+ if (!this._headers || this._finished || this._powerSeized) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ this._ensureAlive();
+
+ this._headers.setHeaderNoCheck(name, value);
+ },
+
+ setInformationalHeaderNoCheck(name, value) {
+ if (!this._headers || this._finished || this._powerSeized) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ this._ensureAlive();
+
+ this._informationalResponseHeaders.setHeaderNoCheck(name, value);
+ },
+
+ //
+ // see nsIHttpResponse.processAsync
+ //
+ processAsync() {
+ if (this._finished) {
+ throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+ }
+ if (this._powerSeized) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ if (this._processAsync) {
+ return;
+ }
+ this._ensureAlive();
+
+ dumpn("*** processing connection " + this._connection.number + " async");
+ this._processAsync = true;
+
+ /*
+ * Either the bodyOutputStream getter or this method is responsible for
+ * starting the asynchronous processor and catching writes of data to the
+ * response body of async responses as they happen, for the purpose of
+ * forwarding those writes to the actual connection's output stream.
+ * If bodyOutputStream is accessed first, calling this method will create
+ * the processor (when it first is clear that body data is to be written
+ * immediately, not buffered). If this method is called first, accessing
+ * bodyOutputStream will create the processor. If only this method is
+ * called, we'll write nothing, neither headers nor the nonexistent body,
+ * until finish() is called. Since that delay is easily avoided by simply
+ * getting bodyOutputStream or calling write(""), we don't worry about it.
+ */
+ if (this._bodyOutputStream && !this._asyncCopier) {
+ this._startAsyncProcessor();
+ }
+ },
+
+ //
+ // see nsIHttpResponse.seizePower
+ //
+ seizePower() {
+ if (this._processAsync) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ if (this._finished) {
+ throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+ }
+ if (this._powerSeized) {
+ return;
+ }
+ this._ensureAlive();
+
+ dumpn(
+ "*** forcefully seizing power over connection " +
+ this._connection.number +
+ "..."
+ );
+
+ // Purge any already-written data without sending it. We could as easily
+ // swap out the streams entirely, but that makes it possible to acquire and
+ // unknowingly use a stale reference, so we require there only be one of
+ // each stream ever for any response to avoid this complication.
+ if (this._asyncCopier) {
+ this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
+ }
+ this._asyncCopier = null;
+ if (this._bodyOutputStream) {
+ var input = new BinaryInputStream(this._bodyInputStream);
+ var avail;
+ while ((avail = input.available()) > 0) {
+ input.readByteArray(avail);
+ }
+ }
+
+ this._powerSeized = true;
+ if (this._bodyOutputStream) {
+ this._startAsyncProcessor();
+ }
+ },
+
+ //
+ // see nsIHttpResponse.finish
+ //
+ finish() {
+ if (!this._processAsync && !this._powerSeized) {
+ throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+ }
+ if (this._finished) {
+ return;
+ }
+
+ dumpn("*** finishing connection " + this._connection.number);
+ this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
+ if (this._bodyOutputStream) {
+ this._bodyOutputStream.close();
+ }
+ this._finished = true;
+ },
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: ChromeUtils.generateQI(["nsIHttpResponse"]),
+
+ // POST-CONSTRUCTION API (not exposed externally)
+
+ /**
+ * The HTTP version number of this, as a string (e.g. "1.1").
+ */
+ get httpVersion() {
+ this._ensureAlive();
+ return this._httpVersion.toString();
+ },
+
+ /**
+ * The HTTP status code of this response, as a string of three characters per
+ * RFC 2616.
+ */
+ get httpCode() {
+ this._ensureAlive();
+
+ var codeString =
+ (this._httpCode < 10 ? "0" : "") +
+ (this._httpCode < 100 ? "0" : "") +
+ this._httpCode;
+ return codeString;
+ },
+
+ /**
+ * The description of the HTTP status code of this response, or "" if none is
+ * set.
+ */
+ get httpDescription() {
+ this._ensureAlive();
+
+ return this._httpDescription;
+ },
+
+ /**
+ * The headers in this response, as an nsHttpHeaders object.
+ */
+ get headers() {
+ this._ensureAlive();
+
+ return this._headers;
+ },
+
+ //
+ // see nsHttpHeaders.getHeader
+ //
+ getHeader(name) {
+ this._ensureAlive();
+
+ return this._headers.getHeader(name);
+ },
+
+ /**
+ * Determines whether this response may be abandoned in favor of a newly
+ * constructed response. A response may be abandoned only if it is not being
+ * sent asynchronously and if raw control over it has not been taken from the
+ * server.
+ *
+ * @returns boolean
+ * true iff no data has been written to the network
+ */
+ partiallySent() {
+ dumpn("*** partiallySent()");
+ return this._processAsync || this._powerSeized;
+ },
+
+ /**
+ * If necessary, kicks off the remaining request processing needed to be done
+ * after a request handler performs its initial work upon this response.
+ */
+ complete() {
+ dumpn("*** complete()");
+ if (this._processAsync || this._powerSeized) {
+ NS_ASSERT(
+ this._processAsync ^ this._powerSeized,
+ "can't both send async and relinquish power"
+ );
+ return;
+ }
+
+ NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
+
+ this._startAsyncProcessor();
+
+ // Now make sure we finish processing this request!
+ if (this._bodyOutputStream) {
+ this._bodyOutputStream.close();
+ }
+ },
+
+ /**
+ * Abruptly ends processing of this response, usually due to an error in an
+ * incoming request but potentially due to a bad error handler. Since we
+ * cannot handle the error in the usual way (giving an HTTP error page in
+ * response) because data may already have been sent (or because the response
+ * might be expected to have been generated asynchronously or completely from
+ * scratch by the handler), we stop processing this response and abruptly
+ * close the connection.
+ *
+ * @param e : Error
+ * the exception which precipitated this abort, or null if no such exception
+ * was generated
+ * @param truncateConnection : Boolean
+ * ensures that we truncate the connection using an RST packet, so the
+ * client testing code is aware that an error occurred, otherwise it may
+ * consider the response as valid.
+ */
+ abort(e, truncateConnection = false) {
+ dumpn("*** abort(<" + e + ">)");
+
+ if (truncateConnection) {
+ dumpn("*** truncate connection");
+ this._connection.transport.setLinger(true, 0);
+ }
+
+ // This response will be ended by the processor if one was created.
+ var copier = this._asyncCopier;
+ if (copier) {
+ // We dispatch asynchronously here so that any pending writes of data to
+ // the connection will be deterministically written. This makes it easier
+ // to specify exact behavior, and it makes observable behavior more
+ // predictable for clients. Note that the correctness of this depends on
+ // callbacks in response to _waitToReadData in WriteThroughCopier
+ // happening asynchronously with respect to the actual writing of data to
+ // bodyOutputStream, as they currently do; if they happened synchronously,
+ // an event which ran before this one could write more data to the
+ // response body before we get around to canceling the copier. We have
+ // tests for this in test_seizepower.js, however, and I can't think of a
+ // way to handle both cases without removing bodyOutputStream access and
+ // moving its effective write(data, length) method onto Response, which
+ // would be slower and require more code than this anyway.
+ Services.tm.currentThread.dispatch(
+ {
+ run() {
+ dumpn("*** canceling copy asynchronously...");
+ copier.cancel(Cr.NS_ERROR_UNEXPECTED);
+ },
+ },
+ Ci.nsIThread.DISPATCH_NORMAL
+ );
+ } else {
+ this.end();
+ }
+ },
+
+ /**
+ * Closes this response's network connection, marks the response as finished,
+ * and notifies the server handler that the request is done being processed.
+ */
+ end() {
+ NS_ASSERT(!this._ended, "ending this response twice?!?!");
+
+ this._connection.close();
+ if (this._bodyOutputStream) {
+ this._bodyOutputStream.close();
+ }
+
+ this._finished = true;
+ this._ended = true;
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+ * Sends the status line and headers of this response if they haven't been
+ * sent and initiates the process of copying data written to this response's
+ * body to the network.
+ */
+ _startAsyncProcessor() {
+ dumpn("*** _startAsyncProcessor()");
+
+ // Handle cases where we're being called a second time. The former case
+ // happens when this is triggered both by complete() and by processAsync(),
+ // while the latter happens when processAsync() in conjunction with sent
+ // data causes abort() to be called.
+ if (this._asyncCopier || this._ended) {
+ dumpn("*** ignoring second call to _startAsyncProcessor");
+ return;
+ }
+
+ // Send headers if they haven't been sent already and should be sent, then
+ // asynchronously continue to send the body.
+ if (this._headers && !this._powerSeized) {
+ this._sendHeaders();
+ return;
+ }
+
+ this._headers = null;
+ this._sendBody();
+ },
+
+ /**
+ * Signals that all modifications to the response status line and headers are
+ * complete and then sends that data over the network to the client. Once
+ * this method completes, a different response to the request that resulted
+ * in this response cannot be sent -- the only possible action in case of
+ * error is to abort the response and close the connection.
+ */
+ _sendHeaders() {
+ dumpn("*** _sendHeaders()");
+
+ NS_ASSERT(this._headers);
+ NS_ASSERT(this._informationalResponseHeaders);
+ NS_ASSERT(!this._powerSeized);
+
+ var preambleData = [];
+
+ // Informational response, e.g. 103
+ if (this._informationalResponseSet) {
+ // request-line
+ let statusLine =
+ "HTTP/" +
+ this._informationalResponseHttpVersion +
+ " " +
+ this._informationalResponseHttpCode +
+ " " +
+ this._informationalResponseHttpDescription +
+ "\r\n";
+ preambleData.push(statusLine);
+
+ // headers
+ let headEnum = this._informationalResponseHeaders.enumerator;
+ while (headEnum.hasMoreElements()) {
+ let fieldName = headEnum
+ .getNext()
+ .QueryInterface(Ci.nsISupportsString).data;
+ let values =
+ this._informationalResponseHeaders.getHeaderValues(fieldName);
+ for (let i = 0, sz = values.length; i < sz; i++) {
+ preambleData.push(fieldName + ": " + values[i] + "\r\n");
+ }
+ }
+ // end request-line/headers
+ preambleData.push("\r\n");
+ }
+
+ // request-line
+ var statusLine =
+ "HTTP/" +
+ this.httpVersion +
+ " " +
+ this.httpCode +
+ " " +
+ this.httpDescription +
+ "\r\n";
+
+ // header post-processing
+
+ var headers = this._headers;
+ headers.setHeader("Connection", "close", false);
+ headers.setHeader("Server", "httpd.js", false);
+ if (!headers.hasHeader("Date")) {
+ headers.setHeader("Date", toDateString(Date.now()), false);
+ }
+
+ // Any response not being processed asynchronously must have an associated
+ // Content-Length header for reasons of backwards compatibility with the
+ // initial server, which fully buffered every response before sending it.
+ // Beyond that, however, it's good to do this anyway because otherwise it's
+ // impossible to test behaviors that depend on the presence or absence of a
+ // Content-Length header.
+ if (!this._processAsync) {
+ dumpn("*** non-async response, set Content-Length");
+
+ var bodyStream = this._bodyInputStream;
+ var avail = bodyStream ? bodyStream.available() : 0;
+
+ // XXX assumes stream will always report the full amount of data available
+ headers.setHeader("Content-Length", "" + avail, false);
+ }
+
+ // construct and send response
+ dumpn("*** header post-processing completed, sending response head...");
+
+ // request-line
+ preambleData.push(statusLine);
+
+ // headers
+ var headEnum = headers.enumerator;
+ while (headEnum.hasMoreElements()) {
+ var fieldName = headEnum
+ .getNext()
+ .QueryInterface(Ci.nsISupportsString).data;
+ var values = headers.getHeaderValues(fieldName);
+ for (var i = 0, sz = values.length; i < sz; i++) {
+ preambleData.push(fieldName + ": " + values[i] + "\r\n");
+ }
+ }
+
+ // end request-line/headers
+ preambleData.push("\r\n");
+
+ var preamble = preambleData.join("");
+
+ var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
+ responseHeadPipe.outputStream.write(preamble, preamble.length);
+
+ var response = this;
+ var copyObserver = {
+ onStartRequest(request) {
+ dumpn("*** preamble copying started");
+ },
+
+ onStopRequest(request, statusCode) {
+ dumpn(
+ "*** preamble copying complete " +
+ "[status=0x" +
+ statusCode.toString(16) +
+ "]"
+ );
+
+ if (!Components.isSuccessCode(statusCode)) {
+ dumpn(
+ "!!! header copying problems: non-success statusCode, " +
+ "ending response"
+ );
+
+ response.end();
+ } else {
+ response._sendBody();
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIRequestObserver"]),
+ };
+
+ this._asyncCopier = new WriteThroughCopier(
+ responseHeadPipe.inputStream,
+ this._connection.output,
+ copyObserver,
+ null
+ );
+
+ responseHeadPipe.outputStream.close();
+
+ // Forbid setting any more headers or modifying the request line.
+ this._headers = null;
+ },
+
+ /**
+ * Asynchronously writes the body of the response (or the entire response, if
+ * seizePower() has been called) to the network.
+ */
+ _sendBody() {
+ dumpn("*** _sendBody");
+
+ NS_ASSERT(!this._headers, "still have headers around but sending body?");
+
+ // If no body data was written, we're done
+ if (!this._bodyInputStream) {
+ dumpn("*** empty body, response finished");
+ this.end();
+ return;
+ }
+
+ var response = this;
+ var copyObserver = {
+ onStartRequest(request) {
+ dumpn("*** onStartRequest");
+ },
+
+ onStopRequest(request, statusCode) {
+ dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
+
+ if (statusCode === Cr.NS_BINDING_ABORTED) {
+ dumpn("*** terminating copy observer without ending the response");
+ } else {
+ if (!Components.isSuccessCode(statusCode)) {
+ dumpn("*** WARNING: non-success statusCode in onStopRequest");
+ }
+
+ response.end();
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIRequestObserver"]),
+ };
+
+ dumpn("*** starting async copier of body data...");
+ this._asyncCopier = new WriteThroughCopier(
+ this._bodyInputStream,
+ this._connection.output,
+ copyObserver,
+ null
+ );
+ },
+
+ /** Ensures that this hasn't been ended. */
+ _ensureAlive() {
+ NS_ASSERT(!this._ended, "not handling response lifetime correctly");
+ },
+};
+
+/**
+ * Size of the segments in the buffer used in storing response data and writing
+ * it to the socket.
+ */
+Response.SEGMENT_SIZE = 8192;
+
+/** Serves double duty in WriteThroughCopier implementation. */
+function notImplemented() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+}
+
+/** Returns true iff the given exception represents stream closure. */
+function streamClosed(e) {
+ return (
+ e === Cr.NS_BASE_STREAM_CLOSED ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED)
+ );
+}
+
+/** Returns true iff the given exception represents a blocked stream. */
+function wouldBlock(e) {
+ return (
+ e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK)
+ );
+}
+
+/**
+ * Copies data from source to sink as it becomes available, when that data can
+ * be written to sink without blocking.
+ *
+ * @param source : nsIAsyncInputStream
+ * the stream from which data is to be read
+ * @param sink : nsIAsyncOutputStream
+ * the stream to which data is to be copied
+ * @param observer : nsIRequestObserver
+ * an observer which will be notified when the copy starts and finishes
+ * @param context : nsISupports
+ * context passed to observer when notified of start/stop
+ * @throws NS_ERROR_NULL_POINTER
+ * if source, sink, or observer are null
+ */
+export function WriteThroughCopier(source, sink, observer, context) {
+ if (!source || !sink || !observer) {
+ throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ /** Stream from which data is being read. */
+ this._source = source;
+
+ /** Stream to which data is being written. */
+ this._sink = sink;
+
+ /** Observer watching this copy. */
+ this._observer = observer;
+
+ /** Context for the observer watching this. */
+ this._context = context;
+
+ /**
+ * True iff this is currently being canceled (cancel has been called, the
+ * callback may not yet have been made).
+ */
+ this._canceled = false;
+
+ /**
+ * False until all data has been read from input and written to output, at
+ * which point this copy is completed and cancel() is asynchronously called.
+ */
+ this._completed = false;
+
+ /** Required by nsIRequest, meaningless. */
+ this.loadFlags = 0;
+ /** Required by nsIRequest, meaningless. */
+ this.loadGroup = null;
+ /** Required by nsIRequest, meaningless. */
+ this.name = "response-body-copy";
+
+ /** Status of this request. */
+ this.status = Cr.NS_OK;
+
+ /** Arrays of byte strings waiting to be written to output. */
+ this._pendingData = [];
+
+ // start copying
+ try {
+ observer.onStartRequest(this);
+ this._waitToReadData();
+ this._waitForSinkClosure();
+ } catch (e) {
+ dumpn(
+ "!!! error starting copy: " +
+ e +
+ ("lineNumber" in e ? ", line " + e.lineNumber : "")
+ );
+ dumpn(e.stack);
+ this.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+}
+WriteThroughCopier.prototype = {
+ /* nsISupports implementation */
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInputStreamCallback",
+ "nsIOutputStreamCallback",
+ "nsIRequest",
+ ]),
+
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+ * Receives a more-data-in-input notification and writes the corresponding
+ * data to the output.
+ *
+ * @param input : nsIAsyncInputStream
+ * the input stream on whose data we have been waiting
+ */
+ onInputStreamReady(input) {
+ if (this._source === null) {
+ return;
+ }
+
+ dumpn("*** onInputStreamReady");
+
+ //
+ // Ordinarily we'll read a non-zero amount of data from input, queue it up
+ // to be written and then wait for further callbacks. The complications in
+ // this method are the cases where we deviate from that behavior when errors
+ // occur or when copying is drawing to a finish.
+ //
+ // The edge cases when reading data are:
+ //
+ // Zero data is read
+ // If zero data was read, we're at the end of available data, so we can
+ // should stop reading and move on to writing out what we have (or, if
+ // we've already done that, onto notifying of completion).
+ // A stream-closed exception is thrown
+ // This is effectively a less kind version of zero data being read; the
+ // only difference is that we notify of completion with that result
+ // rather than with NS_OK.
+ // Some other exception is thrown
+ // This is the least kind result. We don't know what happened, so we
+ // act as though the stream closed except that we notify of completion
+ // with the result NS_ERROR_UNEXPECTED.
+ //
+
+ var bytesWanted = 0,
+ bytesConsumed = -1;
+ try {
+ input = new BinaryInputStream(input);
+
+ bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
+ dumpn("*** input wanted: " + bytesWanted);
+
+ if (bytesWanted > 0) {
+ var data = input.readByteArray(bytesWanted);
+ bytesConsumed = data.length;
+ this._pendingData.push(String.fromCharCode.apply(String, data));
+ }
+
+ dumpn("*** " + bytesConsumed + " bytes read");
+
+ // Handle the zero-data edge case in the same place as all other edge
+ // cases are handled.
+ if (bytesWanted === 0) {
+ throw Components.Exception("", Cr.NS_BASE_STREAM_CLOSED);
+ }
+ } catch (e) {
+ let rv;
+ if (streamClosed(e)) {
+ dumpn("*** input stream closed");
+ rv = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
+ } else {
+ dumpn("!!! unexpected error reading from input, canceling: " + e);
+ rv = Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ this._doneReadingSource(rv);
+ return;
+ }
+
+ var pendingData = this._pendingData;
+
+ NS_ASSERT(bytesConsumed > 0);
+ NS_ASSERT(!!pendingData.length, "no pending data somehow?");
+ NS_ASSERT(
+ !!pendingData[pendingData.length - 1].length,
+ "buffered zero bytes of data?"
+ );
+
+ NS_ASSERT(this._source !== null);
+
+ // Reading has gone great, and we've gotten data to write now. What if we
+ // don't have a place to write that data, because output went away just
+ // before this read? Drop everything on the floor, including new data, and
+ // cancel at this point.
+ if (this._sink === null) {
+ pendingData.length = 0;
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we've read the data, and we know we have a place to write it. We
+ // need to queue up the data to be written, but *only* if none is queued
+ // already -- if data's already queued, the code that actually writes the
+ // data will make sure to wait on unconsumed pending data.
+ try {
+ if (pendingData.length === 1) {
+ this._waitToWriteData();
+ }
+ } catch (e) {
+ dumpn(
+ "!!! error waiting to write data just read, swallowing and " +
+ "writing only what we already have: " +
+ e
+ );
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Whee! We successfully read some data, and it's successfully queued up to
+ // be written. All that remains now is to wait for more data to read.
+ try {
+ this._waitToReadData();
+ } catch (e) {
+ dumpn("!!! error waiting to read more data: " + e);
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ }
+ },
+
+ // NSIOUTPUTSTREAMCALLBACK
+
+ /**
+ * Callback when data may be written to the output stream without blocking, or
+ * when the output stream has been closed.
+ *
+ * @param output : nsIAsyncOutputStream
+ * the output stream on whose writability we've been waiting, also known as
+ * this._sink
+ */
+ onOutputStreamReady(output) {
+ if (this._sink === null) {
+ return;
+ }
+
+ dumpn("*** onOutputStreamReady");
+
+ var pendingData = this._pendingData;
+ if (pendingData.length === 0) {
+ // There's no pending data to write. The only way this can happen is if
+ // we're waiting on the output stream's closure, so we can respond to a
+ // copying failure as quickly as possible (rather than waiting for data to
+ // be available to read and then fail to be copied). Therefore, we must
+ // be done now -- don't bother to attempt to write anything and wrap
+ // things up.
+ dumpn("!!! output stream closed prematurely, ending copy");
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ NS_ASSERT(!!pendingData[0].length, "queued up an empty quantum?");
+
+ //
+ // Write out the first pending quantum of data. The possible errors here
+ // are:
+ //
+ // The write might fail because we can't write that much data
+ // Okay, we've written what we can now, so re-queue what's left and
+ // finish writing it out later.
+ // The write failed because the stream was closed
+ // Discard pending data that we can no longer write, stop reading, and
+ // signal that copying finished.
+ // Some other error occurred.
+ // Same as if the stream were closed, but notify with the status
+ // NS_ERROR_UNEXPECTED so the observer knows something was wonky.
+ //
+
+ try {
+ var quantum = pendingData[0];
+
+ // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
+ // undefined behavior! We're only using this because writeByteArray
+ // is unusably broken for asynchronous output streams; see bug 532834
+ // for details.
+ var bytesWritten = output.write(quantum, quantum.length);
+ if (bytesWritten === quantum.length) {
+ pendingData.shift();
+ } else {
+ pendingData[0] = quantum.substring(bytesWritten);
+ }
+
+ dumpn("*** wrote " + bytesWritten + " bytes of data");
+ } catch (e) {
+ if (wouldBlock(e)) {
+ NS_ASSERT(
+ !!pendingData.length,
+ "stream-blocking exception with no data to write?"
+ );
+ NS_ASSERT(
+ !!pendingData[0].length,
+ "stream-blocking exception with empty quantum?"
+ );
+ this._waitToWriteData();
+ return;
+ }
+
+ if (streamClosed(e)) {
+ dumpn("!!! output stream prematurely closed, signaling error...");
+ } else {
+ dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
+ }
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // The day is ours! Quantum written, now let's see if we have more data
+ // still to write.
+ try {
+ if (pendingData.length) {
+ this._waitToWriteData();
+ return;
+ }
+ } catch (e) {
+ dumpn("!!! unexpected error waiting to write pending data: " + e);
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we have no more pending data to write -- but might we get more in
+ // the future?
+ if (this._source !== null) {
+ /*
+ * If we might, then wait for the output stream to be closed. (We wait
+ * only for closure because we have no data to write -- and if we waited
+ * for a specific amount of data, we would get repeatedly notified for no
+ * reason if over time the output stream permitted more and more data to
+ * be written to it without blocking.)
+ */
+ this._waitForSinkClosure();
+ } else {
+ /*
+ * On the other hand, if we can't have more data because the input
+ * stream's gone away, then it's time to notify of copy completion.
+ * Victory!
+ */
+ this._sink = null;
+ this._cancelOrDispatchCancelCallback(Cr.NS_OK);
+ }
+ },
+
+ // NSIREQUEST
+
+ /** Returns true if the cancel observer hasn't been notified yet. */
+ isPending() {
+ return !this._completed;
+ },
+
+ /** Not implemented, don't use! */
+ suspend: notImplemented,
+ /** Not implemented, don't use! */
+ resume: notImplemented,
+
+ /**
+ * Cancels data reading from input, asynchronously writes out any pending
+ * data, and causes the observer to be notified with the given error code when
+ * all writing has finished.
+ *
+ * @param status : nsresult
+ * the status to pass to the observer when data copying has been canceled
+ */
+ cancel(status) {
+ dumpn("*** cancel(" + status.toString(16) + ")");
+
+ if (this._canceled) {
+ dumpn("*** suppressing a late cancel");
+ return;
+ }
+
+ this._canceled = true;
+ this.status = status;
+
+ // We could be in the middle of absolutely anything at this point. Both
+ // input and output might still be around, we might have pending data to
+ // write, and in general we know nothing about the state of the world. We
+ // therefore must assume everything's in progress and take everything to its
+ // final steady state (or so far as it can go before we need to finish
+ // writing out remaining data).
+
+ this._doneReadingSource(status);
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+ * Stop reading input if we haven't already done so, passing e as the status
+ * when closing the stream, and kick off a copy-completion notice if no more
+ * data remains to be written.
+ *
+ * @param e : nsresult
+ * the status to be used when closing the input stream
+ */
+ _doneReadingSource(e) {
+ dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");
+
+ this._finishSource(e);
+ if (this._pendingData.length === 0) {
+ this._sink = null;
+ } else {
+ NS_ASSERT(this._sink !== null, "null output?");
+ }
+
+ // If we've written out all data read up to this point, then it's time to
+ // signal completion.
+ if (this._sink === null) {
+ NS_ASSERT(this._pendingData.length === 0, "pending data still?");
+ this._cancelOrDispatchCancelCallback(e);
+ }
+ },
+
+ /**
+ * Stop writing output if we haven't already done so, discard any data that
+ * remained to be sent, close off input if it wasn't already closed, and kick
+ * off a copy-completion notice.
+ *
+ * @param e : nsresult
+ * the status to be used when closing input if it wasn't already closed
+ */
+ _doneWritingToSink(e) {
+ dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");
+
+ this._pendingData.length = 0;
+ this._sink = null;
+ this._doneReadingSource(e);
+ },
+
+ /**
+ * Completes processing of this copy: either by canceling the copy if it
+ * hasn't already been canceled using the provided status, or by dispatching
+ * the cancel callback event (with the originally provided status, of course)
+ * if it already has been canceled.
+ *
+ * @param status : nsresult
+ * the status code to use to cancel this, if this hasn't already been
+ * canceled
+ */
+ _cancelOrDispatchCancelCallback(status) {
+ dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");
+
+ NS_ASSERT(this._source === null, "should have finished input");
+ NS_ASSERT(this._sink === null, "should have finished output");
+ NS_ASSERT(this._pendingData.length === 0, "should have no pending data");
+
+ if (!this._canceled) {
+ this.cancel(status);
+ return;
+ }
+
+ var self = this;
+ var event = {
+ run() {
+ dumpn("*** onStopRequest async callback");
+
+ self._completed = true;
+ try {
+ self._observer.onStopRequest(self, self.status);
+ } catch (e) {
+ NS_ASSERT(
+ false,
+ "how are we throwing an exception here? we control " +
+ "all the callers! " +
+ e
+ );
+ }
+ },
+ };
+
+ Services.tm.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ /**
+ * Kicks off another wait for more data to be available from the input stream.
+ */
+ _waitToReadData() {
+ dumpn("*** _waitToReadData");
+ this._source.asyncWait(
+ this,
+ 0,
+ Response.SEGMENT_SIZE,
+ Services.tm.mainThread
+ );
+ },
+
+ /**
+ * Kicks off another wait until data can be written to the output stream.
+ */
+ _waitToWriteData() {
+ dumpn("*** _waitToWriteData");
+
+ var pendingData = this._pendingData;
+ NS_ASSERT(!!pendingData.length, "no pending data to write?");
+ NS_ASSERT(!!pendingData[0].length, "buffered an empty write?");
+
+ this._sink.asyncWait(
+ this,
+ 0,
+ pendingData[0].length,
+ Services.tm.mainThread
+ );
+ },
+
+ /**
+ * Kicks off a wait for the sink to which data is being copied to be closed.
+ * We wait for stream closure when we don't have any data to be copied, rather
+ * than waiting to write a specific amount of data. We can't wait to write
+ * data because the sink might be infinitely writable, and if no data appears
+ * in the source for a long time we might have to spin quite a bit waiting to
+ * write, waiting to write again, &c. Waiting on stream closure instead means
+ * we'll get just one notification if the sink dies. Note that when data
+ * starts arriving from the sink we'll resume waiting for data to be written,
+ * dropping this closure-only callback entirely.
+ */
+ _waitForSinkClosure() {
+ dumpn("*** _waitForSinkClosure");
+
+ this._sink.asyncWait(
+ this,
+ Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY,
+ 0,
+ Services.tm.mainThread
+ );
+ },
+
+ /**
+ * Closes input with the given status, if it hasn't already been closed;
+ * otherwise a no-op.
+ *
+ * @param status : nsresult
+ * status code use to close the source stream if necessary
+ */
+ _finishSource(status) {
+ dumpn("*** _finishSource(" + status.toString(16) + ")");
+
+ if (this._source !== null) {
+ this._source.closeWithStatus(status);
+ this._source = null;
+ }
+ },
+};
+
+/**
+ * A container for utility functions used with HTTP headers.
+ */
+const headerUtils = {
+ /**
+ * Normalizes fieldName (by converting it to lowercase) and ensures it is a
+ * valid header field name (although not necessarily one specified in RFC
+ * 2616).
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not match the field-name production in RFC 2616
+ * @returns string
+ * fieldName converted to lowercase if it is a valid header, for characters
+ * where case conversion is possible
+ */
+ normalizeFieldName(fieldName) {
+ if (fieldName == "") {
+ dumpn("*** Empty fieldName");
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ for (var i = 0, sz = fieldName.length; i < sz; i++) {
+ if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)]) {
+ dumpn(fieldName + " is not a valid header field name!");
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+ }
+
+ return fieldName.toLowerCase();
+ },
+
+ /**
+ * Ensures that fieldValue is a valid header field value (although not
+ * necessarily as specified in RFC 2616 if the corresponding field name is
+ * part of the HTTP protocol), normalizes the value if it is, and
+ * returns the normalized value.
+ *
+ * @param fieldValue : string
+ * a value to be normalized as an HTTP header field value
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldValue does not match the field-value production in RFC 2616
+ * @returns string
+ * fieldValue as a normalized HTTP header field value
+ */
+ normalizeFieldValue(fieldValue) {
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ // TEXT = <any OCTET except CTLs,
+ // but including LWS>
+ // LWS = [CRLF] 1*( SP | HT )
+ //
+ // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ // qdtext = <any TEXT except <">>
+ // quoted-pair = "\" CHAR
+ // CHAR = <any US-ASCII character (octets 0 - 127)>
+
+ // Any LWS that occurs between field-content MAY be replaced with a single
+ // SP before interpreting the field value or forwarding the message
+ // downstream (section 4.2); we replace 1*LWS with a single SP
+ var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");
+
+ // remove leading/trailing LWS (which has been converted to SP)
+ val = val.replace(/^ +/, "").replace(/ +$/, "");
+
+ // that should have taken care of all CTLs, so val should contain no CTLs
+ dumpn("*** Normalized value: '" + val + "'");
+ for (var i = 0, len = val.length; i < len; i++) {
+ if (isCTL(val.charCodeAt(i))) {
+ dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+ }
+
+ // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
+ // normalize, however, so this can be construed as a tightening of the
+ // spec and not entirely as a bug
+ return val;
+ },
+};
+
+/**
+ * Converts the given string into a string which is safe for use in an HTML
+ * context.
+ *
+ * @param str : string
+ * the string to make HTML-safe
+ * @returns string
+ * an HTML-safe version of str
+ */
+function htmlEscape(str) {
+ // this is naive, but it'll work
+ var s = "";
+ for (var i = 0; i < str.length; i++) {
+ s += "&#" + str.charCodeAt(i) + ";";
+ }
+ return s;
+}
+
+/**
+ * Constructs an object representing an HTTP version (see section 3.1).
+ *
+ * @param versionString
+ * a string of the form "#.#", where # is an non-negative decimal integer with
+ * or without leading zeros
+ * @throws
+ * if versionString does not specify a valid HTTP version number
+ */
+function nsHttpVersion(versionString) {
+ var matches = /^(\d+)\.(\d+)$/.exec(versionString);
+ if (!matches) {
+ throw new Error("Not a valid HTTP version!");
+ }
+
+ /** The major version number of this, as a number. */
+ this.major = parseInt(matches[1], 10);
+
+ /** The minor version number of this, as a number. */
+ this.minor = parseInt(matches[2], 10);
+
+ if (
+ isNaN(this.major) ||
+ isNaN(this.minor) ||
+ this.major < 0 ||
+ this.minor < 0
+ ) {
+ throw new Error("Not a valid HTTP version!");
+ }
+}
+nsHttpVersion.prototype = {
+ /**
+ * Returns the standard string representation of the HTTP version represented
+ * by this (e.g., "1.1").
+ */
+ toString() {
+ return this.major + "." + this.minor;
+ },
+
+ /**
+ * Returns true if this represents the same HTTP version as otherVersion,
+ * false otherwise.
+ *
+ * @param otherVersion : nsHttpVersion
+ * the version to compare against this
+ */
+ equals(otherVersion) {
+ return this.major == otherVersion.major && this.minor == otherVersion.minor;
+ },
+
+ /** True if this >= otherVersion, false otherwise. */
+ atLeast(otherVersion) {
+ return (
+ this.major > otherVersion.major ||
+ (this.major == otherVersion.major && this.minor >= otherVersion.minor)
+ );
+ },
+};
+
+nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
+nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");
+
+/**
+ * An object which stores HTTP headers for a request or response.
+ *
+ * Note that since headers are case-insensitive, this object converts headers to
+ * lowercase before storing them. This allows the getHeader and hasHeader
+ * methods to work correctly for any case of a header, but it means that the
+ * values returned by .enumerator may not be equal case-sensitively to the
+ * values passed to setHeader when adding headers to this.
+ */
+export function nsHttpHeaders() {
+ /**
+ * A hash of headers, with header field names as the keys and header field
+ * values as the values. Header field names are case-insensitive, but upon
+ * insertion here they are converted to lowercase. Header field values are
+ * normalized upon insertion to contain no leading or trailing whitespace.
+ *
+ * Note also that per RFC 2616, section 4.2, two headers with the same name in
+ * a message may be treated as one header with the same field name and a field
+ * value consisting of the separate field values joined together with a "," in
+ * their original order. This hash stores multiple headers with the same name
+ * in this manner.
+ */
+ this._headers = {};
+}
+nsHttpHeaders.prototype = {
+ /**
+ * Sets the header represented by name and value in this.
+ *
+ * @param name : string
+ * the header name
+ * @param value : string
+ * the header value
+ * @throws NS_ERROR_INVALID_ARG
+ * if name or value is not a valid header component
+ */
+ setHeader(fieldName, fieldValue, merge) {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ var value = headerUtils.normalizeFieldValue(fieldValue);
+
+ // The following three headers are stored as arrays because their real-world
+ // syntax prevents joining individual headers into a single header using
+ // ",". See also <https://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
+ if (merge && name in this._headers) {
+ if (
+ name === "www-authenticate" ||
+ name === "proxy-authenticate" ||
+ name === "set-cookie"
+ ) {
+ this._headers[name].push(value);
+ } else {
+ this._headers[name][0] += "," + value;
+ NS_ASSERT(
+ this._headers[name].length === 1,
+ "how'd a non-special header have multiple values?"
+ );
+ }
+ } else {
+ this._headers[name] = [value];
+ }
+ },
+
+ setHeaderNoCheck(fieldName, fieldValue) {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ var value = headerUtils.normalizeFieldValue(fieldValue);
+ if (name in this._headers) {
+ this._headers[name].push(value);
+ } else {
+ this._headers[name] = [value];
+ }
+ },
+
+ /**
+ * Returns the value for the header specified by this.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the given header does not exist in this
+ * @returns string
+ * the field value for the given header, possibly with non-semantic changes
+ * (i.e., leading/trailing whitespace stripped, whitespace runs replaced
+ * with spaces, etc.) at the option of the implementation; multiple
+ * instances of the header will be combined with a comma, except for
+ * the three headers noted in the description of getHeaderValues
+ */
+ getHeader(fieldName) {
+ return this.getHeaderValues(fieldName).join("\n");
+ },
+
+ /**
+ * Returns the value for the header specified by fieldName as an array.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the given header does not exist in this
+ * @returns [string]
+ * an array of all the header values in this for the given
+ * header name. Header values will generally be collapsed
+ * into a single header by joining all header values together
+ * with commas, but certain headers (Proxy-Authenticate,
+ * WWW-Authenticate, and Set-Cookie) violate the HTTP spec
+ * and cannot be collapsed in this manner. For these headers
+ * only, the returned array may contain multiple elements if
+ * that header has been added more than once.
+ */
+ getHeaderValues(fieldName) {
+ var name = headerUtils.normalizeFieldName(fieldName);
+
+ if (name in this._headers) {
+ return this._headers[name];
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ },
+
+ /**
+ * Returns true if a header with the given field name exists in this, false
+ * otherwise.
+ *
+ * @param fieldName : string
+ * the field name whose existence is to be determined in this
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @returns boolean
+ * true if the header's present, false otherwise
+ */
+ hasHeader(fieldName) {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ return name in this._headers;
+ },
+
+ /**
+ * Returns a new enumerator over the field names of the headers in this, as
+ * nsISupportsStrings. The names returned will be in lowercase, regardless of
+ * how they were input using setHeader (header names are case-insensitive per
+ * RFC 2616).
+ */
+ get enumerator() {
+ var headers = [];
+ for (var i in this._headers) {
+ var supports = new SupportsString();
+ supports.data = i;
+ headers.push(supports);
+ }
+
+ return new nsSimpleEnumerator(headers);
+ },
+};
+
+/**
+ * Constructs an nsISimpleEnumerator for the given array of items.
+ *
+ * @param items : Array
+ * the items, which must all implement nsISupports
+ */
+function nsSimpleEnumerator(items) {
+ this._items = items;
+ this._nextIndex = 0;
+}
+nsSimpleEnumerator.prototype = {
+ hasMoreElements() {
+ return this._nextIndex < this._items.length;
+ },
+ getNext() {
+ if (!this.hasMoreElements()) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ return this._items[this._nextIndex++];
+ },
+ [Symbol.iterator]() {
+ return this._items.values();
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsISimpleEnumerator"]),
+};
+
+/**
+ * A representation of the data in an HTTP request.
+ *
+ * @param port : uint
+ * the port on which the server receiving this request runs
+ */
+function Request(port) {
+ /** Method of this request, e.g. GET or POST. */
+ this._method = "";
+
+ /** Path of the requested resource; empty paths are converted to '/'. */
+ this._path = "";
+
+ /** Query string, if any, associated with this request (not including '?'). */
+ this._queryString = "";
+
+ /** Scheme of requested resource, usually http, always lowercase. */
+ this._scheme = "http";
+
+ /** Hostname on which the requested resource resides. */
+ this._host = undefined;
+
+ /** Port number over which the request was received. */
+ this._port = port;
+
+ var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
+
+ /** Stream from which data in this request's body may be read. */
+ this._bodyInputStream = bodyPipe.inputStream;
+
+ /** Stream to which data in this request's body is written. */
+ this._bodyOutputStream = bodyPipe.outputStream;
+
+ /**
+ * The headers in this request.
+ */
+ this._headers = new nsHttpHeaders();
+
+ /**
+ * For the addition of ad-hoc properties and new functionality without having
+ * to change nsIHttpRequest every time; currently lazily created, as its only
+ * use is in directory listings.
+ */
+ this._bag = null;
+}
+Request.prototype = {
+ // SERVER METADATA
+
+ //
+ // see nsIHttpRequest.scheme
+ //
+ get scheme() {
+ return this._scheme;
+ },
+
+ //
+ // see nsIHttpRequest.host
+ //
+ get host() {
+ return this._host;
+ },
+
+ //
+ // see nsIHttpRequest.port
+ //
+ get port() {
+ return this._port;
+ },
+
+ // REQUEST LINE
+
+ //
+ // see nsIHttpRequest.method
+ //
+ get method() {
+ return this._method;
+ },
+
+ //
+ // see nsIHttpRequest.httpVersion
+ //
+ get httpVersion() {
+ return this._httpVersion.toString();
+ },
+
+ //
+ // see nsIHttpRequest.path
+ //
+ get path() {
+ return this._path;
+ },
+
+ //
+ // see nsIHttpRequest.queryString
+ //
+ get queryString() {
+ return this._queryString;
+ },
+
+ // HEADERS
+
+ //
+ // see nsIHttpRequest.getHeader
+ //
+ getHeader(name) {
+ return this._headers.getHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.hasHeader
+ //
+ hasHeader(name) {
+ return this._headers.hasHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get headers() {
+ return this._headers.enumerator;
+ },
+
+ //
+ // see nsIPropertyBag.enumerator
+ //
+ get enumerator() {
+ this._ensurePropertyBag();
+ return this._bag.enumerator;
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get bodyInputStream() {
+ return this._bodyInputStream;
+ },
+
+ //
+ // see nsIPropertyBag.getProperty
+ //
+ getProperty(name) {
+ this._ensurePropertyBag();
+ return this._bag.getProperty(name);
+ },
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: ChromeUtils.generateQI(["nsIHttpRequest"]),
+
+ // PRIVATE IMPLEMENTATION
+
+ /** Ensures a property bag has been created for ad-hoc behaviors. */
+ _ensurePropertyBag() {
+ if (!this._bag) {
+ this._bag = new WritablePropertyBag();
+ }
+ },
+};
diff --git a/netwerk/test/httpserver/moz.build b/netwerk/test/httpserver/moz.build
new file mode 100644
index 0000000000..f4c978b160
--- /dev/null
+++ b/netwerk/test/httpserver/moz.build
@@ -0,0 +1,21 @@
+# -*- 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 += [
+ "nsIHttpServer.idl",
+]
+
+XPIDL_MODULE = "test_necko"
+
+XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell.toml"]
+
+EXTRA_COMPONENTS += [
+ "httpd.sys.mjs",
+]
+
+TESTING_JS_MODULES += [
+ "httpd.sys.mjs",
+]
diff --git a/netwerk/test/httpserver/nsIHttpServer.idl b/netwerk/test/httpserver/nsIHttpServer.idl
new file mode 100644
index 0000000000..83614dbdb0
--- /dev/null
+++ b/netwerk/test/httpserver/nsIHttpServer.idl
@@ -0,0 +1,649 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsIFile;
+interface nsIOutputStream;
+interface nsISimpleEnumerator;
+
+interface nsIHttpServer;
+interface nsIHttpServerStoppedCallback;
+interface nsIHttpRequestHandler;
+interface nsIHttpRequest;
+interface nsIHttpResponse;
+interface nsIHttpServerIdentity;
+
+/**
+ * An interface which represents an HTTP server.
+ */
+[scriptable, uuid(cea8812e-faa6-4013-9396-f9936cbb74ec)]
+interface nsIHttpServer : nsISupports
+{
+ /**
+ * Starts up this server, listening upon the given port.
+ *
+ * @param port
+ * the port upon which listening should happen, or -1 if no specific port is
+ * desired
+ * @throws NS_ERROR_ALREADY_INITIALIZED
+ * if this server is already started
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the server is not started and cannot be started on the desired port
+ * (perhaps because the port is already in use or because the process does
+ * not have privileges to do so)
+ * @note
+ * Behavior is undefined if this method is called after stop() has been
+ * called on this but before the provided callback function has been
+ * called.
+ */
+ void start(in long port);
+
+ /**
+ * Starts up this server, listening upon the given port on a ipv6 adddress.
+ *
+ * @param port
+ * the port upon which listening should happen, or -1 if no specific port is
+ * desired
+ * @throws NS_ERROR_ALREADY_INITIALIZED
+ * if this server is already started
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the server is not started and cannot be started on the desired port
+ * (perhaps because the port is already in use or because the process does
+ * not have privileges to do so)
+ * @note
+ * Behavior is undefined if this method is called after stop() has been
+ * called on this but before the provided callback function has been
+ * called.
+ */
+ void start_ipv6(in long port);
+
+ /**
+ * Like the two functions above, but this server supports both IPv6 and
+ * IPv4 addresses.
+ */
+ void start_dualStack(in long port);
+
+ /**
+ * Shuts down this server if it is running (including the period of time after
+ * stop() has been called but before the provided callback has been called).
+ *
+ * @param callback
+ * an asynchronous callback used to notify the user when this server is
+ * stopped and all pending requests have been fully served
+ * @throws NS_ERROR_NULL_POINTER
+ * if callback is null
+ * @throws NS_ERROR_UNEXPECTED
+ * if this server is not running
+ */
+ void stop(in nsIHttpServerStoppedCallback callback);
+
+ /**
+ * Associates the local file represented by the string file with all requests
+ * which match request.
+ *
+ * @param path
+ * the path which is to be mapped to the given file; must begin with "/" and
+ * be a valid URI path (i.e., no query string, hash reference, etc.)
+ * @param file
+ * the file to serve for the given path, or null to remove any mapping that
+ * might exist; this file must exist for the lifetime of the server
+ * @param handler
+ * an optional object which can be used to handle any further changes.
+ */
+ void registerFile(in string path,
+ in nsIFile file,
+ [optional] in nsIHttpRequestHandler handler);
+
+ /**
+ * Registers a custom path handler.
+ *
+ * @param path
+ * the path on the server (beginning with a "/") which is to be handled by
+ * handler; this path must not include a query string or hash component; it
+ * also should usually be canonicalized, since most browsers will do so
+ * before sending otherwise-matching requests
+ * @param handler
+ * an object which will handle any requests for the given path, or null to
+ * remove any existing handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500 response
+ * will be returned
+ * @throws NS_ERROR_INVALID_ARG
+ * if path does not begin with a "/"
+ */
+ void registerPathHandler(in string path, in nsIHttpRequestHandler handler);
+
+ /**
+ * Registers a custom prefix handler.
+ *
+ * @param prefix
+ * the path on the server (beginning and ending with "/") which is to be
+ * handled by handler; this path must not include a query string or hash
+ * component. All requests that start with this prefix will be directed to
+ * the given handler.
+ * @param handler
+ * an object which will handle any requests for the given path, or null to
+ * remove any existing handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500 response
+ * will be returned
+ * @throws NS_ERROR_INVALID_ARG
+ * if path does not begin with a "/" or does not end with a "/"
+ */
+ void registerPrefixHandler(in string prefix, in nsIHttpRequestHandler handler);
+
+ /**
+ * Registers a custom error page handler.
+ *
+ * @param code
+ * the error code which is to be handled by handler
+ * @param handler
+ * an object which will handle any requests which generate the given status
+ * code, or null to remove any existing handler. If the handler throws an
+ * exception during server operation, fallback is to the genericized error
+ * handler (the x00 version), then to 500, using a user-defined error
+ * handler if one exists or the server default handler otherwise. Fallback
+ * will never occur from a user-provided handler that throws to the same
+ * handler as provided by the server, e.g. a throwing user 404 falls back to
+ * 400, not a server-provided 404 that might not throw.
+ * @note
+ * If the error handler handles HTTP 500 and throws, behavior is undefined.
+ */
+ void registerErrorHandler(in unsigned long code, in nsIHttpRequestHandler handler);
+
+ /**
+ * Maps all requests to paths beneath path to the corresponding file beneath
+ * dir.
+ *
+ * @param path
+ * the absolute path on the server against which requests will be served
+ * from dir (e.g., "/", "/foo/", etc.); must begin and end with a forward
+ * slash
+ * @param dir
+ * the directory to be used to serve all requests for paths underneath path
+ * (except those further overridden by another, deeper path registered with
+ * another directory); if null, any current mapping for the given path is
+ * removed
+ * @throws NS_ERROR_INVALID_ARG
+ * if dir is non-null and does not exist or is not a directory, or if path
+ * does not begin with and end with a forward slash
+ */
+ void registerDirectory(in string path, in nsIFile dir);
+
+ /**
+ * Associates files with the given extension with the given Content-Type when
+ * served by this server, in the absence of any file-specific information
+ * about the desired Content-Type. If type is empty, removes any extant
+ * mapping, if one is present.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if the given type is not a valid header field value, i.e. if it doesn't
+ * match the field-value production in RFC 2616
+ * @note
+ * No syntax checking is done of the given type, beyond ensuring that it is
+ * a valid header field value. Behavior when not given a string matching
+ * the media-type production in RFC 2616 section 3.7 is undefined.
+ * Implementations may choose to define specific behavior for types which do
+ * not match the production, such as for CGI functionality.
+ * @note
+ * Implementations MAY treat type as a trusted argument; users who fail to
+ * generate this string from trusted data risk security vulnerabilities.
+ */
+ void registerContentType(in string extension, in string type);
+
+ /**
+ * Sets the handler used to display the contents of a directory if
+ * the directory contains no index page.
+ *
+ * @param handler
+ * an object which will handle any requests for directories which
+ * do not contain index pages, or null to reset to the default
+ * index handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500
+ * response will be returned. An nsIFile corresponding to the
+ * directory is available from the metadata object passed to the
+ * handler, under the key "directory".
+ */
+ void setIndexHandler(in nsIHttpRequestHandler handler);
+
+ /** Represents the locations at which this server is reachable. */
+ readonly attribute nsIHttpServerIdentity identity;
+
+ /**
+ * Retrieves the string associated with the given key in this, for the given
+ * path's saved state. All keys are initially associated with the empty
+ * string.
+ */
+ AString getState(in AString path, in AString key);
+
+ /**
+ * Sets the string associated with the given key in this, for the given path's
+ * saved state.
+ */
+ void setState(in AString path, in AString key, in AString value);
+
+ /**
+ * Retrieves the string associated with the given key in this, in
+ * entire-server saved state. All keys are initially associated with the
+ * empty string.
+ */
+ AString getSharedState(in AString key);
+
+ /**
+ * Sets the string associated with the given key in this, in entire-server
+ * saved state.
+ */
+ void setSharedState(in AString key, in AString value);
+
+ /**
+ * Retrieves the object associated with the given key in this in
+ * object-valued saved state. All keys are initially associated with null.
+ */
+ nsISupports getObjectState(in AString key);
+
+ /**
+ * Sets the object associated with the given key in this in object-valued
+ * saved state. The value may be null.
+ */
+ void setObjectState(in AString key, in nsISupports value);
+};
+
+/**
+ * An interface through which a notification of the complete stopping (socket
+ * closure, in-flight requests all fully served and responded to) of an HTTP
+ * server may be received.
+ */
+[scriptable, function, uuid(925a6d33-9937-4c63-abe1-a1c56a986455)]
+interface nsIHttpServerStoppedCallback : nsISupports
+{
+ /** Called when the corresponding server has been fully stopped. */
+ void onStopped();
+};
+
+/**
+ * Represents a set of names for a server, one of which is the primary name for
+ * the server and the rest of which are secondary. By default every server will
+ * contain ("http", "localhost", port) and ("http", "127.0.0.1", port) as names,
+ * where port is what was provided to the corresponding server when started;
+ * however, except for their being removed when the corresponding server stops
+ * they have no special importance.
+ */
+[scriptable, uuid(a89de175-ae8e-4c46-91a5-0dba99bbd284)]
+interface nsIHttpServerIdentity : nsISupports
+{
+ /**
+ * The primary scheme at which the corresponding server is located, defaulting
+ * to 'http'. This name will be the value of nsIHttpRequest.scheme for
+ * HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute string primaryScheme;
+
+ /**
+ * The primary name by which the corresponding server is known, defaulting to
+ * 'localhost'. This name will be the value of nsIHttpRequest.host for
+ * HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute string primaryHost;
+
+ /**
+ * The primary port on which the corresponding server runs, defaulting to the
+ * associated server's port. This name will be the value of
+ * nsIHttpRequest.port for HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute long primaryPort;
+
+ /**
+ * Adds a location at which this server may be accessed.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ void add(in string scheme, in string host, in long port);
+
+ /**
+ * Removes this name from the list of names by which the corresponding server
+ * is known. If name is also the primary name for the server, the primary
+ * name reverts to 'http://127.0.0.1' with the associated server's port.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ * @returns
+ * true if the given name was a name for this server, false otherwise
+ */
+ boolean remove(in string scheme, in string host, in long port);
+
+ /**
+ * Returns true if the given name is in this, false otherwise.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ boolean has(in string scheme, in string host, in long port);
+
+ /**
+ * Returns the scheme for the name with the given host and port, if one is
+ * present; otherwise returns the empty string.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if host does not match the host production imported into RFC 2616 from
+ * RFC 2396, or if port is not a valid port number
+ */
+ string getScheme(in string host, in long port);
+
+ /**
+ * Designates the given name as the primary name in this and adds it to this
+ * if it is not already present.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ void setPrimary(in string scheme, in string host, in long port);
+};
+
+/**
+ * A representation of a handler for HTTP requests. The handler is used by
+ * calling its .handle method with data for an incoming request; it is the
+ * handler's job to use that data as it sees fit to make the desired response.
+ *
+ * @note
+ * This interface uses the [function] attribute, so you can pass a
+ * script-defined function with the functionality of handle() to any
+ * method which has a nsIHttpRequestHandler parameter, instead of wrapping
+ * it in an otherwise empty object.
+ */
+[scriptable, function, uuid(2bbb4db7-d285-42b3-a3ce-142b8cc7e139)]
+interface nsIHttpRequestHandler : nsISupports
+{
+ /**
+ * Processes an HTTP request and initializes the passed-in response to reflect
+ * the correct HTTP response.
+ *
+ * If this method throws an exception, externally observable behavior depends
+ * upon whether is being processed asynchronously. If such is the case, the
+ * output is some prefix (perhaps all, perhaps none, perhaps only some) of the
+ * data which would have been sent if, instead, the response had been finished
+ * at that point. If no data has been written, the response has not had
+ * seizePower() called on it, and it is not being asynchronously created, an
+ * error handler will be invoked (usually 500 unless otherwise specified).
+ *
+ * Some uses of nsIHttpRequestHandler may require this method to never throw
+ * an exception; in the general case, however, this method may throw an
+ * exception (causing an HTTP 500 response to occur, if the above conditions
+ * are met).
+ *
+ * @param request
+ * data representing an HTTP request
+ * @param response
+ * an initially-empty response which must be modified to reflect the data
+ * which should be sent as the response to the request described by metadata
+ */
+ void handle(in nsIHttpRequest request, in nsIHttpResponse response);
+};
+
+
+/**
+ * A representation of the data included in an HTTP request.
+ */
+[scriptable, uuid(978cf30e-ad73-42ee-8f22-fe0aaf1bf5d2)]
+interface nsIHttpRequest : nsISupports
+{
+ /**
+ * The request type for this request (see RFC 2616, section 5.1.1).
+ */
+ readonly attribute string method;
+
+ /**
+ * The scheme of the requested path, usually 'http' but might possibly be
+ * 'https' if some form of SSL tunneling is in use. Note that this value
+ * cannot be accurately determined unless the incoming request used the
+ * absolute-path form of the request line; it defaults to 'http', so only
+ * if it is something else can you be entirely certain it's correct.
+ */
+ readonly attribute string scheme;
+
+ /**
+ * The host of the data being requested (e.g. "localhost" for the
+ * http://localhost:8080/file resource). Note that the relevant port on the
+ * host is specified in this.port. This value is in the ASCII character
+ * encoding.
+ */
+ readonly attribute string host;
+
+ /**
+ * The port on the server on which the request was received.
+ */
+ readonly attribute unsigned long port;
+
+ /**
+ * The requested path, without any query string (e.g. "/dir/file.txt"). It is
+ * guaranteed to begin with a "/". The individual components in this string
+ * are URL-encoded.
+ */
+ readonly attribute string path;
+
+ /**
+ * The URL-encoded query string associated with this request, not including
+ * the initial "?", or "" if no query string was present.
+ */
+ readonly attribute string queryString;
+
+ /**
+ * A string containing the HTTP version of the request (i.e., "1.1"). Leading
+ * zeros for either component of the version will be omitted. (In other
+ * words, if the request contains the version "1.01", this attribute will be
+ * "1.1"; see RFC 2616, section 3.1.)
+ */
+ readonly attribute string httpVersion;
+
+ /**
+ * Returns the value for the header in this request specified by fieldName.
+ *
+ * @param fieldName
+ * the name of the field whose value is to be gotten; note that since HTTP
+ * header field names are case-insensitive, this method produces equivalent
+ * results for "HeAdER" and "hEADer" as fieldName
+ * @returns
+ * The result is a string containing the individual values of the header,
+ * usually separated with a comma. The headers WWW-Authenticate,
+ * Proxy-Authenticate, and Set-Cookie violate the HTTP specification,
+ * however, and for these headers only the separator string is '\n'.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the given header does not exist in this
+ */
+ string getHeader(in string fieldName);
+
+ /**
+ * Returns true if a header with the given field name exists in this, false
+ * otherwise.
+ *
+ * @param fieldName
+ * the field name whose existence is to be determined in this; note that
+ * since HTTP header field names are case-insensitive, this method produces
+ * equivalent results for "HeAdER" and "hEADer" as fieldName
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ */
+ boolean hasHeader(in string fieldName);
+
+ /**
+ * An nsISimpleEnumerator of nsISupportsStrings over the names of the headers
+ * in this request. The header field names in the enumerator may not
+ * necessarily have the same case as they do in the request itself.
+ */
+ readonly attribute nsISimpleEnumerator headers;
+
+ /**
+ * A stream from which data appearing in the body of this request can be read.
+ */
+ readonly attribute nsIInputStream bodyInputStream;
+};
+
+
+/**
+ * Represents an HTTP response, as described in RFC 2616, section 6.
+ */
+[scriptable, uuid(1acd16c2-dc59-42fa-9160-4f26c43c1c21)]
+interface nsIHttpResponse : nsISupports
+{
+ /**
+ * Sets the status line for this. If this method is never called on this, the
+ * status line defaults to "HTTP/", followed by the server's default HTTP
+ * version (e.g. "1.1"), followed by " 200 OK".
+ *
+ * @param httpVersion
+ * the HTTP version of this, as a string (e.g. "1.1"); if null, the server
+ * default is used
+ * @param code
+ * the numeric HTTP status code for this
+ * @param description
+ * a human-readable description of code; may be null if no description is
+ * desired
+ * @throws NS_ERROR_INVALID_ARG
+ * if httpVersion is not a valid HTTP version string, statusCode is greater
+ * than 999, or description contains invalid characters
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if this response is being processed asynchronously and data has been
+ * written to this response's body, or if seizePower() has been called on
+ * this
+ */
+ void setStatusLine(in string httpVersion,
+ in unsigned short statusCode,
+ in string description);
+
+ /**
+ * Sets the specified header in this.
+ *
+ * @param name
+ * the name of the header; must match the field-name production per RFC 2616
+ * @param value
+ * the value of the header; must match the field-value production per RFC
+ * 2616
+ * @param merge
+ * when true, if the given header already exists in this, the values passed
+ * to this function will be merged into the existing header, per RFC 2616
+ * header semantics (except for the Set-Cookie, WWW-Authenticate, and
+ * Proxy-Authenticate headers, which will treat each such merged header as
+ * an additional instance of the header, for real-world compatibility
+ * reasons); when false, replaces any existing header of the given name (if
+ * any exists) with a new header with the specified value
+ * @throws NS_ERROR_INVALID_ARG
+ * if name or value is not a valid header component
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if this response is being processed asynchronously and data has been
+ * written to this response's body, or if seizePower() has been called on
+ * this
+ */
+ void setHeader(in string name, in string value, in boolean merge);
+
+ /**
+ * This is used for testing our header handling, so header will be sent out
+ * without transformation. There can be multiple headers.
+ */
+ void setHeaderNoCheck(in string name, in string value);
+
+ /**
+ * A stream to which data appearing in the body of this response (or in the
+ * totality of the response if seizePower() is called) should be written.
+ * After this response has been designated as being processed asynchronously,
+ * or after seizePower() has been called on this, subsequent writes will no
+ * longer be buffered and will be written to the underlying transport without
+ * delaying until the entire response is constructed. Write-through may or
+ * may not be synchronous in the implementation, and in any case particular
+ * behavior may not be observable to the HTTP client as intermediate buffers
+ * both in the server socket and in the client may delay written data; be
+ * prepared for delays at any time.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if accessed after this response is fully constructed
+ */
+ readonly attribute nsIOutputStream bodyOutputStream;
+
+ /**
+ * Writes a string to the response's output stream. This method is merely a
+ * convenient shorthand for writing the same data to bodyOutputStream
+ * directly.
+ *
+ * @note
+ * This method is only guaranteed to work with ASCII data.
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if called after this response has been fully constructed
+ */
+ void write(in string data);
+
+ /**
+ * Signals that this response is being constructed asynchronously. Requests
+ * are typically completely constructed during nsIHttpRequestHandler.handle;
+ * however, responses which require significant resources (time, memory,
+ * processing) to construct can be created and sent incrementally by calling
+ * this method during the call to nsIHttpRequestHandler.handle. This method
+ * only has this effect when called during nsIHttpRequestHandler.handle;
+ * behavior is undefined if it is called at a later time. It may be called
+ * multiple times with no ill effect, so long as each call occurs before
+ * finish() is called.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * if not initially called within a nsIHttpRequestHandler.handle call or if
+ * called after this response has been finished
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if seizePower() has been called on this
+ */
+ void processAsync();
+
+ /**
+ * Seizes complete control of this response (and its connection) from the
+ * server, allowing raw and unfettered access to data being sent in the HTTP
+ * response. Once this method has been called the only property which may be
+ * accessed without an exception being thrown is bodyOutputStream, and the
+ * only methods which may be accessed without an exception being thrown are
+ * write(), finish(), and seizePower() (which may be called multiple times
+ * without ill effect so long as all calls are otherwise allowed).
+ *
+ * After a successful call, all data subsequently written to the body of this
+ * response is written directly to the corresponding connection. (Previously-
+ * written data is silently discarded.) No status line or headers are sent
+ * before doing so; if the response handler wishes to write such data, it must
+ * do so manually. Data generation completes only when finish() is called; it
+ * is not enough to simply call close() on bodyOutputStream.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if processAsync() has been called on this
+ * @throws NS_ERROR_UNEXPECTED
+ * if finish() has been called on this
+ */
+ void seizePower();
+
+ /**
+ * Signals that construction of this response is complete and that it may be
+ * sent over the network to the client, or if seizePower() has been called
+ * signals that all data has been written and that the underlying connection
+ * may be closed. This method may only be called after processAsync() or
+ * seizePower() has been called. This method is idempotent.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * if processAsync() or seizePower() has not already been properly called
+ */
+ void finish();
+};
diff --git a/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^
new file mode 100644
index 0000000000..b005a65fd2
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^
@@ -0,0 +1 @@
+If this has goofy headers on it, it's a success.
diff --git a/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^
new file mode 100644
index 0000000000..66e1522317
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^
@@ -0,0 +1,3 @@
+HTTP 500 This Isn't A Server Error
+Foo-RFC: 3092
+Shaving-Cream-Atom: Illudium Phosdex
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_both.html b/netwerk/test/httpserver/test/data/cern_meta/test_both.html
new file mode 100644
index 0000000000..db18ea5d7a
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html
@@ -0,0 +1,2 @@
+This page is a text file served with status 501. (That's really a lie, tho,
+because this is definitely Implemented.)
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^
new file mode 100644
index 0000000000..bb3c16a2e2
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^
@@ -0,0 +1,2 @@
+HTTP 501 Unimplemented
+Content-Type: text/plain
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt
new file mode 100644
index 0000000000..7235fa32a5
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>This is really HTML, not text</title>
+</head>
+<body>
+<p>This file is really HTML; the test_ctype_override.txt^headers^ file sets a
+ new header that overwrites the default text/plain header.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^
new file mode 100644
index 0000000000..156209f9c8
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html
new file mode 100644
index 0000000000..fd243c640e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>This is a 404 page</title>
+</head>
+<body>
+<p>This page has a 404 HTTP status associated with it, via
+ <code>test_status_override.html^headers^</code>.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^
new file mode 100644
index 0000000000..f438a05746
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^
@@ -0,0 +1 @@
+HTTP 404 Can't Find This
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt
new file mode 100644
index 0000000000..4718ec282f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt
@@ -0,0 +1 @@
+This page has an HTTP status override without a description (it defaults to "").
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^
new file mode 100644
index 0000000000..32da7632f9
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^
@@ -0,0 +1 @@
+HTTP 732
diff --git a/netwerk/test/httpserver/test/data/name-scheme/bar.html^^ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^
new file mode 100644
index 0000000000..bed1f34c9f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <title>Welcome to bar.html^</title>
+</head>
+<body>
+<p>This file is named with two trailing carets, so the last is stripped
+ away, producing bar.html^ as the final name.</p>
+</body>
+</html>
+
diff --git a/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^
new file mode 100644
index 0000000000..04fbaa08fe
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^
@@ -0,0 +1,2 @@
+HTTP 200 OK
+Content-Type: text/html
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^
new file mode 100644
index 0000000000..dccee48e34
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^
@@ -0,0 +1 @@
+This file shouldn't be shown in directory listings.
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^
new file mode 100644
index 0000000000..a8ee35a3b6
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^
@@ -0,0 +1 @@
+This file should show up in directory listings as SHOULD_SEE_THIS.txt^.
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt b/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt
new file mode 100644
index 0000000000..2ceca8ca9e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt
@@ -0,0 +1,2 @@
+File in a directory named with a trailing caret (in the virtual FS; on disk it
+actually ends with two carets).
diff --git a/netwerk/test/httpserver/test/data/name-scheme/foo.html^ b/netwerk/test/httpserver/test/data/name-scheme/foo.html^
new file mode 100644
index 0000000000..a3efe8b5c3
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/foo.html^
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>ERROR</title>
+</head>
+<body>
+<p>This file should never be served by the web server because its name ends
+ with a caret not followed by another caret.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt b/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt
new file mode 100644
index 0000000000..ab71eabaf0
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt
@@ -0,0 +1 @@
+This should be seen.
diff --git a/netwerk/test/httpserver/test/data/ranges/empty.txt b/netwerk/test/httpserver/test/data/ranges/empty.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/empty.txt
diff --git a/netwerk/test/httpserver/test/data/ranges/headers.txt b/netwerk/test/httpserver/test/data/ranges/headers.txt
new file mode 100644
index 0000000000..6cf83528c8
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/headers.txt
@@ -0,0 +1 @@
+Hello Kitty
diff --git a/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^ b/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^
new file mode 100644
index 0000000000..d0a633f042
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^
@@ -0,0 +1 @@
+X-SJS-Header: customized
diff --git a/netwerk/test/httpserver/test/data/ranges/range.txt b/netwerk/test/httpserver/test/data/ranges/range.txt
new file mode 100644
index 0000000000..ab71eabaf0
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/range.txt
@@ -0,0 +1 @@
+This should be seen.
diff --git a/netwerk/test/httpserver/test/data/sjs/cgi.sjs b/netwerk/test/httpserver/test/data/sjs/cgi.sjs
new file mode 100644
index 0000000000..a6b987a8b7
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/cgi.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ if (request.queryString == "throw") {
+ throw new Error("monkey wrench!");
+ }
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("PASS");
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^ b/netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^
new file mode 100644
index 0000000000..a83ff774ab
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^
@@ -0,0 +1,2 @@
+HTTP 500 Error
+This-Header: SHOULD NOT APPEAR IN CGI.JSC RESPONSES!
diff --git a/netwerk/test/httpserver/test/data/sjs/object-state.sjs b/netwerk/test/httpserver/test/data/sjs/object-state.sjs
new file mode 100644
index 0000000000..8f027dfedf
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/object-state.sjs
@@ -0,0 +1,74 @@
+function parseQueryString(str) {
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++) {
+ var match = regex.exec(paramArray[i]);
+ if (!match) {
+ throw new Error("Bad parameter in queryString! '" + paramArray[i] + "'");
+ }
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+/*
+ * We're relying somewhat dubiously on all data being sent as soon as it's
+ * available at numerous levels (in Necko in the server-side part of the
+ * connection, in the OS's outgoing socket buffer, in the OS's incoming socket
+ * buffer, and in Necko in the client-side part of the connection), but to the
+ * best of my knowledge there's no way to force data flow at all those levels,
+ * so this is the best we can do.
+ */
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ /*
+ * NB: A Content-Type header is *necessary* to avoid content-sniffing, which
+ * will delay onStartRequest past the the point where the entire head of
+ * the response has been received.
+ */
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var params = parseQueryString(request.queryString);
+
+ switch (params.state) {
+ case "initial":
+ response.processAsync();
+ response.write("do");
+ var state = {
+ QueryInterface: ChromeUtils.generateQI([]),
+ end() {
+ response.write("ne");
+ response.finish();
+ },
+ };
+ state.wrappedJSObject = state;
+ setObjectState("object-state-test", state);
+ getObjectState("object-state-test", function (obj) {
+ if (obj !== state) {
+ response.write("FAIL bad state save");
+ response.finish();
+ }
+ });
+ break;
+
+ case "intermediate":
+ response.write("intermediate");
+ break;
+
+ case "trigger":
+ response.write("trigger");
+ getObjectState("object-state-test", function (obj) {
+ obj.wrappedJSObject.end();
+ setObjectState("object-state-test", null);
+ });
+ break;
+
+ default:
+ response.setStatusLine(request.httpVersion, 500, "Unexpected State");
+ response.write("Bad state: " + params.state);
+ break;
+ }
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/qi.sjs b/netwerk/test/httpserver/test/data/sjs/qi.sjs
new file mode 100644
index 0000000000..ee0fc74a0f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/qi.sjs
@@ -0,0 +1,45 @@
+function handleRequest(request, response) {
+ var exstr, qid;
+
+ response.setStatusLine(request.httpVersion, 500, "FAIL");
+
+ var passed = false;
+ try {
+ qid = request.QueryInterface(Ci.nsIHttpRequest);
+ passed = qid === request;
+ } catch (e) {
+ // eslint-disable-next-line no-control-regex
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(
+ request.httpVersion,
+ 500,
+ "request doesn't QI: " + exstr
+ );
+ return;
+ }
+ if (!passed) {
+ response.setStatusLine(request.httpVersion, 500, "request QI'd wrongly?");
+ return;
+ }
+
+ passed = false;
+ try {
+ qid = response.QueryInterface(Ci.nsIHttpResponse);
+ passed = qid === response;
+ } catch (e) {
+ // eslint-disable-next-line no-control-regex
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(
+ request.httpVersion,
+ 500,
+ "response doesn't QI: " + exstr
+ );
+ return;
+ }
+ if (!passed) {
+ response.setStatusLine(request.httpVersion, 500, "response QI'd wrongly?");
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "SJS QI Tests Passed");
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/range-checker.sjs b/netwerk/test/httpserver/test/data/sjs/range-checker.sjs
new file mode 100644
index 0000000000..4bc447f739
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/range-checker.sjs
@@ -0,0 +1 @@
+function handleRequest(request, response) {}
diff --git a/netwerk/test/httpserver/test/data/sjs/sjs b/netwerk/test/httpserver/test/data/sjs/sjs
new file mode 100644
index 0000000000..374ca41674
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response)
+{
+ response.write("FAIL");
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/state1.sjs b/netwerk/test/httpserver/test/data/sjs/state1.sjs
new file mode 100644
index 0000000000..1a2540eca1
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/state1.sjs
@@ -0,0 +1,38 @@
+function parseQueryString(str) {
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++) {
+ var match = regex.exec(paramArray[i]);
+ if (!match) {
+ throw new Error("Bad parameter in queryString! '" + paramArray[i] + "'");
+ }
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var params = parseQueryString(request.queryString);
+
+ var oldShared = getSharedState("shared-value");
+ response.setHeader("X-Old-Shared-Value", oldShared, false);
+
+ var newShared = params.newShared;
+ if (newShared !== undefined) {
+ setSharedState("shared-value", newShared);
+ response.setHeader("X-New-Shared-Value", newShared, false);
+ }
+
+ var oldPrivate = getState("private-value");
+ response.setHeader("X-Old-Private-Value", oldPrivate, false);
+
+ var newPrivate = params.newPrivate;
+ if (newPrivate !== undefined) {
+ setState("private-value", newPrivate);
+ response.setHeader("X-New-Private-Value", newPrivate, false);
+ }
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/state2.sjs b/netwerk/test/httpserver/test/data/sjs/state2.sjs
new file mode 100644
index 0000000000..1a2540eca1
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/state2.sjs
@@ -0,0 +1,38 @@
+function parseQueryString(str) {
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++) {
+ var match = regex.exec(paramArray[i]);
+ if (!match) {
+ throw new Error("Bad parameter in queryString! '" + paramArray[i] + "'");
+ }
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var params = parseQueryString(request.queryString);
+
+ var oldShared = getSharedState("shared-value");
+ response.setHeader("X-Old-Shared-Value", oldShared, false);
+
+ var newShared = params.newShared;
+ if (newShared !== undefined) {
+ setSharedState("shared-value", newShared);
+ response.setHeader("X-New-Shared-Value", newShared, false);
+ }
+
+ var oldPrivate = getState("private-value");
+ response.setHeader("X-Old-Private-Value", oldPrivate, false);
+
+ var newPrivate = params.newPrivate;
+ if (newPrivate !== undefined) {
+ setState("private-value", newPrivate);
+ response.setHeader("X-New-Private-Value", newPrivate, false);
+ }
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/thrower.sjs b/netwerk/test/httpserver/test/data/sjs/thrower.sjs
new file mode 100644
index 0000000000..b34de70e30
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/thrower.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ if (request.queryString == "throw") {
+ undefined[5];
+ }
+ response.setHeader("X-Test-Status", "PASS", false);
+}
diff --git a/netwerk/test/httpserver/test/head_utils.js b/netwerk/test/httpserver/test/head_utils.js
new file mode 100644
index 0000000000..3f2fad4940
--- /dev/null
+++ b/netwerk/test/httpserver/test/head_utils.js
@@ -0,0 +1,605 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {
+ dumpn,
+ LineData,
+ nsHttpHeaders,
+ HttpServer,
+ WriteThroughCopier,
+ overrideBinaryStreamsForTests,
+} = ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs");
+
+// if these tests fail, we'll want the debug output
+var linDEBUG = true;
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+const CC = Components.Constructor;
+const FileInputStream = CC(
+ "@mozilla.org/network/file-input-stream;1",
+ "nsIFileInputStream",
+ "init"
+);
+var BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+var BinaryOutputStream = CC(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+const ScriptableInputStream = CC(
+ "@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init"
+);
+
+/**
+ * Constructs a new nsHttpServer instance. This function is intended to
+ * encapsulate construction of a server so that at some point in the future it
+ * is possible to run these tests (with at most slight modifications) against
+ * the server when used as an XPCOM component (not as an inline script).
+ */
+function createServer() {
+ return new HttpServer();
+}
+
+/**
+ * Creates a new HTTP channel.
+ *
+ * @param url
+ * the URL of the channel to create
+ */
+function makeChannel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+/**
+ * Make a binary input stream wrapper for the given stream.
+ *
+ * @param stream
+ * the nsIInputStream to wrap
+ */
+function makeBIS(stream) {
+ return new BinaryInputStream(stream);
+}
+
+/**
+ * Returns the contents of the file as a string.
+ *
+ * @param file : nsIFile
+ * the file whose contents are to be read
+ * @returns string
+ * the contents of the file
+ */
+function fileContents(file) {
+ const PR_RDONLY = 0x01;
+ var fis = new FileInputStream(
+ file,
+ PR_RDONLY,
+ 0o444,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF
+ );
+ var sis = new ScriptableInputStream(fis);
+ var contents = sis.read(file.fileSize);
+ sis.close();
+ return contents;
+}
+
+/**
+ * Iterates over the lines, delimited by CRLF, in data, returning each line
+ * without the trailing line separator.
+ *
+ * @param data : string
+ * a string consisting of lines of data separated by CRLFs
+ * @returns Iterator
+ * an Iterator which returns each line from data in turn; note that this
+ * includes a final empty line if data ended with a CRLF
+ */
+function* LineIterator(data) {
+ var index = 0;
+ do {
+ index = data.indexOf("\r\n");
+ if (index >= 0) {
+ yield data.substring(0, index);
+ } else {
+ yield data;
+ }
+
+ data = data.substring(index + 2);
+ } while (index >= 0);
+}
+
+/**
+ * Throws if iter does not contain exactly the CRLF-separated lines in the
+ * array expectedLines.
+ *
+ * @param iter : Iterator
+ * an Iterator which returns lines of text
+ * @param expectedLines : [string]
+ * an array of the expected lines of text
+ * @throws an error message if iter doesn't agree with expectedLines
+ */
+function expectLines(iter, expectedLines) {
+ var index = 0;
+ for (var line of iter) {
+ if (expectedLines.length == index) {
+ throw new Error(
+ `Error: got more than ${expectedLines.length} expected lines!`
+ );
+ }
+
+ var expected = expectedLines[index++];
+ if (expected !== line) {
+ throw new Error(`Error on line ${index}!
+ actual: '${line}',
+ expect: '${expected}'`);
+ }
+ }
+
+ if (expectedLines.length !== index) {
+ throw new Error(
+ `Expected more lines! Got ${index}, expected ${expectedLines.length}`
+ );
+ }
+}
+
+/**
+ * Spew a bunch of HTTP metadata from request into the body of response.
+ *
+ * @param request : nsIHttpRequest
+ * the request whose metadata should be output
+ * @param response : nsIHttpResponse
+ * the response to which the metadata is written
+ */
+function writeDetails(request, response) {
+ response.write("Method: " + request.method + "\r\n");
+ response.write("Path: " + request.path + "\r\n");
+ response.write("Query: " + request.queryString + "\r\n");
+ response.write("Version: " + request.httpVersion + "\r\n");
+ response.write("Scheme: " + request.scheme + "\r\n");
+ response.write("Host: " + request.host + "\r\n");
+ response.write("Port: " + request.port);
+}
+
+/**
+ * Advances iter past all non-blank lines and a single blank line, after which
+ * point the body of the response will be returned next from the iterator.
+ *
+ * @param iter : Iterator
+ * an iterator over the CRLF-delimited lines in an HTTP response, currently
+ * just after the Request-Line
+ */
+function skipHeaders(iter) {
+ var line = iter.next().value;
+ while (line !== "") {
+ line = iter.next().value;
+ }
+}
+
+/**
+ * Checks that the exception e (which may be an XPConnect-created exception
+ * object or a raw nsresult number) is the given nsresult.
+ *
+ * @param e : Exception or nsresult
+ * the actual exception
+ * @param code : nsresult
+ * the expected exception
+ */
+function isException(e, code) {
+ if (e !== code && e.result !== code) {
+ do_throw("unexpected error: " + e);
+ }
+}
+
+/**
+ * Calls the given function at least the specified number of milliseconds later.
+ * The callback will not undershoot the given time, but it might overshoot --
+ * don't expect precision!
+ *
+ * @param milliseconds : uint
+ * the number of milliseconds to delay
+ * @param callback : function() : void
+ * the function to call
+ */
+function callLater(msecs, callback) {
+ do_timeout(msecs, callback);
+}
+
+/** *****************************************************
+ * SIMPLE SUPPORT FOR LOADING/TESTING A SERIES OF URLS *
+ *******************************************************/
+
+/**
+ * Create a completion callback which will stop the given server and end the
+ * test, assuming nothing else remains to be done at that point.
+ */
+function testComplete(srv) {
+ return function complete() {
+ do_test_pending();
+ srv.stop(function quit() {
+ do_test_finished();
+ });
+ };
+}
+
+/**
+ * Represents a path to load from the tested HTTP server, along with actions to
+ * take before, during, and after loading the associated page.
+ *
+ * @param path
+ * the URL to load from the server
+ * @param initChannel
+ * a function which takes as a single parameter a channel created for path and
+ * initializes its state, or null if no additional initialization is needed
+ * @param onStartRequest
+ * called during onStartRequest for the load of the URL, with the same
+ * parameters; the request parameter has been QI'd to nsIHttpChannel and
+ * nsIHttpChannelInternal for convenience; may be null if nothing needs to be
+ * done
+ * @param onStopRequest
+ * called during onStopRequest for the channel, with the same parameters plus
+ * a trailing parameter containing an array of the bytes of data downloaded in
+ * the body of the channel response; the request parameter has been QI'd to
+ * nsIHttpChannel and nsIHttpChannelInternal for convenience; may be null if
+ * nothing needs to be done
+ */
+function Test(path, initChannel, onStartRequest, onStopRequest) {
+ function nil() {}
+
+ this.path = path;
+ this.initChannel = initChannel || nil;
+ this.onStartRequest = onStartRequest || nil;
+ this.onStopRequest = onStopRequest || nil;
+}
+
+/**
+ * Runs all the tests in testArray.
+ *
+ * @param testArray
+ * a non-empty array of Tests to run, in order
+ * @param done
+ * function to call when all tests have run (e.g. to shut down the server)
+ */
+function runHttpTests(testArray, done) {
+ /** Kicks off running the next test in the array. */
+ function performNextTest() {
+ if (++testIndex == testArray.length) {
+ try {
+ done();
+ } catch (e) {
+ do_report_unexpected_exception(e, "running test-completion callback");
+ }
+ return;
+ }
+
+ do_test_pending();
+
+ var test = testArray[testIndex];
+ var ch = makeChannel(test.path);
+ try {
+ test.initChannel(ch);
+ } catch (e) {
+ try {
+ do_report_unexpected_exception(
+ e,
+ "testArray[" + testIndex + "].initChannel(ch)"
+ );
+ } catch (x) {
+ /* swallow and let tests continue */
+ }
+ }
+
+ listener._channel = ch;
+ ch.asyncOpen(listener);
+ }
+
+ /** Index of the test being run. */
+ var testIndex = -1;
+
+ /** Stream listener for the channels. */
+ var listener = {
+ /** Current channel being observed by this. */
+ _channel: null,
+ /** Array of bytes of data in body of response. */
+ _data: [],
+
+ onStartRequest(request) {
+ Assert.ok(request === this._channel);
+ var ch = request
+ .QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ this._data.length = 0;
+ try {
+ try {
+ testArray[testIndex].onStartRequest(ch);
+ } catch (e) {
+ do_report_unexpected_exception(
+ e,
+ "testArray[" + testIndex + "].onStartRequest"
+ );
+ }
+ } catch (e) {
+ do_note_exception(
+ e,
+ "!!! swallowing onStartRequest exception so onStopRequest is " +
+ "called..."
+ );
+ }
+ },
+ onDataAvailable(request, inputStream, offset, count) {
+ var quantum = 262144; // just above half the argument-count limit
+ var bis = makeBIS(inputStream);
+ for (var start = 0; start < count; start += quantum) {
+ var newData = bis.readByteArray(Math.min(quantum, count - start));
+ Array.prototype.push.apply(this._data, newData);
+ }
+ },
+ onStopRequest(request, status) {
+ this._channel = null;
+
+ var ch = request
+ .QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ // NB: The onStopRequest callback must run before performNextTest here,
+ // because the latter runs the next test's initChannel callback, and
+ // we want one test to be sequentially processed before the next
+ // one.
+ try {
+ testArray[testIndex].onStopRequest(ch, status, this._data);
+ } catch (e) {
+ do_report_unexpected_exception(
+ e,
+ "testArray[" + testIndex + "].onStartRequest"
+ );
+ } finally {
+ try {
+ performNextTest();
+ } finally {
+ do_test_finished();
+ }
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+ };
+
+ performNextTest();
+}
+
+/** **************************************
+ * RAW REQUEST FORMAT TESTING FUNCTIONS *
+ ****************************************/
+
+/**
+ * Sends a raw string of bytes to the given host and port and checks that the
+ * response is acceptable.
+ *
+ * @param host : string
+ * the host to which a connection should be made
+ * @param port : PRUint16
+ * the port to use for the connection
+ * @param data : string or [string...]
+ * either:
+ * - the raw data to send, as a string of characters with codes in the
+ * range 0-255, or
+ * - an array of such strings whose concatenation forms the raw data
+ * @param responseCheck : function(string) : void
+ * a function which is provided with the data sent by the remote host which
+ * conducts whatever tests it wants on that data; useful for tweaking the test
+ * environment between tests
+ */
+function RawTest(host, port, data, responseCheck) {
+ if (0 > port || 65535 < port || port % 1 !== 0) {
+ throw new Error("bad port");
+ }
+ if (!(data instanceof Array)) {
+ data = [data];
+ }
+ if (data.length <= 0) {
+ throw new Error("bad data length");
+ }
+
+ if (
+ !data.every(function (v) {
+ // eslint-disable-next-line no-control-regex
+ return /^[\x00-\xff]*$/.test(v);
+ })
+ ) {
+ throw new Error("bad data contained non-byte-valued character");
+ }
+
+ this.host = host;
+ this.port = port;
+ this.data = data;
+ this.responseCheck = responseCheck;
+}
+
+/**
+ * Runs all the tests in testArray, an array of RawTests.
+ *
+ * @param testArray : [RawTest]
+ * an array of RawTests to run, in order
+ * @param done
+ * function to call when all tests have run (e.g. to shut down the server)
+ * @param beforeTestCallback
+ * function to call before each test is run. Gets passed testIndex when called
+ */
+function runRawTests(testArray, done, beforeTestCallback) {
+ do_test_pending();
+
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+ );
+
+ var currentThread =
+ Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+
+ /** Kicks off running the next test in the array. */
+ function performNextTest() {
+ if (++testIndex == testArray.length) {
+ do_test_finished();
+ try {
+ done();
+ } catch (e) {
+ do_report_unexpected_exception(e, "running test-completion callback");
+ }
+ return;
+ }
+
+ if (beforeTestCallback) {
+ try {
+ beforeTestCallback(testIndex);
+ } catch (e) {
+ /* We don't care if this call fails */
+ }
+ }
+
+ var rawTest = testArray[testIndex];
+
+ var transport = sts.createTransport(
+ [],
+ rawTest.host,
+ rawTest.port,
+ null,
+ null
+ );
+
+ var inStream = transport.openInputStream(0, 0, 0);
+ var outStream = transport.openOutputStream(0, 0, 0);
+
+ // reset
+ dataIndex = 0;
+ received = "";
+
+ waitForMoreInput(inStream);
+ waitToWriteOutput(outStream);
+ }
+
+ function waitForMoreInput(stream) {
+ reader.stream = stream;
+ stream = stream.QueryInterface(Ci.nsIAsyncInputStream);
+ stream.asyncWait(reader, 0, 0, currentThread);
+ }
+
+ function waitToWriteOutput(stream) {
+ // Do the QueryInterface here, not earlier, because there is no
+ // guarantee that 'stream' passed in here been QIed to nsIAsyncOutputStream
+ // since the last GC.
+ stream = stream.QueryInterface(Ci.nsIAsyncOutputStream);
+ stream.asyncWait(
+ writer,
+ 0,
+ testArray[testIndex].data[dataIndex].length,
+ currentThread
+ );
+ }
+
+ /** Index of the test being run. */
+ var testIndex = -1;
+
+ /**
+ * Index of remaining data strings to be written to the socket in current
+ * test.
+ */
+ var dataIndex = 0;
+
+ /** Data received so far from the server. */
+ var received = "";
+
+ /** Reads data from the socket. */
+ var reader = {
+ onInputStreamReady(stream) {
+ Assert.ok(stream === this.stream);
+ try {
+ var bis = new BinaryInputStream(stream);
+
+ var av = 0;
+ try {
+ av = bis.available();
+ } catch (e) {
+ /* default to 0 */
+ do_note_exception(e);
+ }
+
+ if (av > 0) {
+ var quantum = 262144;
+ for (var start = 0; start < av; start += quantum) {
+ var bytes = bis.readByteArray(Math.min(quantum, av - start));
+ received += String.fromCharCode.apply(null, bytes);
+ }
+ waitForMoreInput(stream);
+ return;
+ }
+ } catch (e) {
+ do_report_unexpected_exception(e);
+ }
+
+ var rawTest = testArray[testIndex];
+ try {
+ rawTest.responseCheck(received);
+ } catch (e) {
+ do_report_unexpected_exception(e);
+ } finally {
+ try {
+ stream.close();
+ performNextTest();
+ } catch (e) {
+ do_report_unexpected_exception(e);
+ }
+ }
+ },
+ };
+
+ /** Writes data to the socket. */
+ var writer = {
+ onOutputStreamReady(stream) {
+ var str = testArray[testIndex].data[dataIndex];
+
+ var written = 0;
+ try {
+ written = stream.write(str, str.length);
+ if (written == str.length) {
+ dataIndex++;
+ } else {
+ testArray[testIndex].data[dataIndex] = str.substring(written);
+ }
+ } catch (e) {
+ do_note_exception(e);
+ /* stream could have been closed, just ignore */
+ }
+
+ try {
+ // Keep writing data while we can write and
+ // until there's no more data to read
+ if (written > 0 && dataIndex < testArray[testIndex].data.length) {
+ waitToWriteOutput(stream);
+ } else {
+ stream.close();
+ }
+ } catch (e) {
+ do_report_unexpected_exception(e);
+ }
+ },
+ };
+
+ performNextTest();
+}
diff --git a/netwerk/test/httpserver/test/test_async_response_sending.js b/netwerk/test/httpserver/test/test_async_response_sending.js
new file mode 100644
index 0000000000..bc025308ae
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_async_response_sending.js
@@ -0,0 +1,1661 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Ensures that data a request handler writes out in response is sent only as
+ * quickly as the client can receive it, without racing ahead and being forced
+ * to block while writing that data.
+ *
+ * NB: These tests are extremely tied to the current implementation, in terms of
+ * when and how stream-ready notifications occur, the amount of data which will
+ * be read or written at each notification, and so on. If the implementation
+ * changes in any way with respect to stream copying, this test will probably
+ * have to change a little at the edges as well.
+ */
+
+function run_test() {
+ do_test_pending();
+ tests.push(function testsComplete(_) {
+ dumpn(
+ // eslint-disable-next-line no-useless-concat
+ "******************\n" + "* TESTS COMPLETE *\n" + "******************"
+ );
+ do_test_finished();
+ });
+
+ runNextTest();
+}
+
+function runNextTest() {
+ testIndex++;
+ dumpn("*** runNextTest(), testIndex: " + testIndex);
+
+ try {
+ var test = tests[testIndex];
+ test(runNextTest);
+ } catch (e) {
+ var msg = "exception running test " + testIndex + ": " + e;
+ if (e && "stack" in e) {
+ msg += "\nstack follows:\n" + e.stack;
+ }
+ do_throw(msg);
+ }
+}
+
+/** ***********
+ * TEST DATA *
+ *************/
+
+const NOTHING = [];
+
+const FIRST_SEGMENT = [1, 2, 3, 4];
+const SECOND_SEGMENT = [5, 6, 7, 8];
+const THIRD_SEGMENT = [9, 10, 11, 12];
+
+const SEGMENT = FIRST_SEGMENT;
+const TWO_SEGMENTS = [1, 2, 3, 4, 5, 6, 7, 8];
+const THREE_SEGMENTS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+
+const SEGMENT_AND_HALF = [1, 2, 3, 4, 5, 6];
+
+const QUARTER_SEGMENT = [1];
+const HALF_SEGMENT = [1, 2];
+const SECOND_HALF_SEGMENT = [3, 4];
+const EXTRA_HALF_SEGMENT = [5, 6];
+const MIDDLE_HALF_SEGMENT = [2, 3];
+const LAST_QUARTER_SEGMENT = [4];
+const HALF_THIRD_SEGMENT = [9, 10];
+const LATTER_HALF_THIRD_SEGMENT = [11, 12];
+
+const TWO_HALF_SEGMENTS = [1, 2, 1, 2];
+
+/** *******
+ * TESTS *
+ *********/
+
+var tests = [
+ sourceClosedWithoutWrite,
+ writeOneSegmentThenClose,
+ simpleWriteThenRead,
+ writeLittleBeforeReading,
+ writeMultipleSegmentsThenRead,
+ writeLotsBeforeReading,
+ writeLotsBeforeReading2,
+ writeThenReadPartial,
+ manyPartialWrites,
+ partialRead,
+ partialWrite,
+ sinkClosedImmediately,
+ sinkClosedWithReadableData,
+ sinkClosedAfterWrite,
+ sourceAndSinkClosed,
+ sinkAndSourceClosed,
+ sourceAndSinkClosedWithPendingData,
+ sinkAndSourceClosedWithPendingData,
+];
+var testIndex = -1;
+
+function sourceClosedWithoutWrite(next) {
+ var t = new CopyTest("sourceClosedWithoutWrite", next);
+
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [NOTHING]);
+}
+
+function writeOneSegmentThenClose(next) {
+ var t = new CopyTest("writeLittleBeforeReading", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT]);
+}
+
+function simpleWriteThenRead(next) {
+ var t = new CopyTest("simpleWriteThenRead", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [SEGMENT]);
+}
+
+function writeLittleBeforeReading(next) {
+ var t = new CopyTest("writeLittleBeforeReading", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT, SEGMENT]);
+}
+
+function writeMultipleSegmentsThenRead(next) {
+ var t = new CopyTest("writeMultipleSegmentsThenRead", next);
+
+ t.addToSource(TWO_SEGMENTS);
+ t.makeSourceReadable(TWO_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(TWO_SEGMENTS.length, [
+ FIRST_SEGMENT,
+ SECOND_SEGMENT,
+ ]);
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [TWO_SEGMENTS]);
+}
+
+function writeLotsBeforeReading(next) {
+ var t = new CopyTest("writeLotsBeforeReading", next);
+
+ t.addToSource(TWO_SEGMENTS);
+ t.makeSourceReadable(TWO_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(FIRST_SEGMENT.length, [FIRST_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SECOND_SEGMENT.length, [SECOND_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(2 * SEGMENT.length, [SEGMENT, SEGMENT]);
+ t.expect(Cr.NS_OK, [TWO_SEGMENTS, SEGMENT, SEGMENT]);
+}
+
+function writeLotsBeforeReading2(next) {
+ var t = new CopyTest("writeLotsBeforeReading", next);
+
+ t.addToSource(THREE_SEGMENTS);
+ t.makeSourceReadable(THREE_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(FIRST_SEGMENT.length, [FIRST_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SECOND_SEGMENT.length, [SECOND_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(THIRD_SEGMENT.length, [THIRD_SEGMENT]);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(2 * SEGMENT.length, [SEGMENT, SEGMENT]);
+ t.expect(Cr.NS_OK, [THREE_SEGMENTS, SEGMENT, SEGMENT]);
+}
+
+function writeThenReadPartial(next) {
+ var t = new CopyTest("writeThenReadPartial", next);
+
+ t.addToSource(SEGMENT_AND_HALF);
+ t.makeSourceReadable(SEGMENT_AND_HALF.length);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(EXTRA_HALF_SEGMENT.length, [EXTRA_HALF_SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT_AND_HALF]);
+}
+
+function manyPartialWrites(next) {
+ var t = new CopyTest("manyPartialWrites", next);
+
+ t.addToSource(HALF_SEGMENT);
+ t.makeSourceReadable(HALF_SEGMENT.length);
+
+ t.addToSource(HALF_SEGMENT);
+ t.makeSourceReadable(HALF_SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(2 * HALF_SEGMENT.length, [TWO_HALF_SEGMENTS]);
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [TWO_HALF_SEGMENTS]);
+}
+
+function partialRead(next) {
+ var t = new CopyTest("partialRead", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.addToSource(HALF_SEGMENT);
+ t.makeSourceReadable(HALF_SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.closeSourceAndWaitFor(Cr.NS_OK, HALF_SEGMENT.length, [HALF_SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT, HALF_SEGMENT]);
+}
+
+function partialWrite(next) {
+ var t = new CopyTest("partialWrite", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableByIncrementsAndWaitFor(SEGMENT.length, [
+ QUARTER_SEGMENT,
+ MIDDLE_HALF_SEGMENT,
+ LAST_QUARTER_SEGMENT,
+ ]);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableByIncrementsAndWaitFor(SEGMENT.length, [
+ HALF_SEGMENT,
+ SECOND_HALF_SEGMENT,
+ ]);
+
+ t.addToSource(THREE_SEGMENTS);
+ t.makeSourceReadable(THREE_SEGMENTS.length);
+ t.makeSinkWritableByIncrementsAndWaitFor(THREE_SEGMENTS.length, [
+ HALF_SEGMENT,
+ SECOND_HALF_SEGMENT,
+ SECOND_SEGMENT,
+ HALF_THIRD_SEGMENT,
+ LATTER_HALF_THIRD_SEGMENT,
+ ]);
+
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [SEGMENT, SEGMENT, THREE_SEGMENTS]);
+}
+
+function sinkClosedImmediately(next) {
+ var t = new CopyTest("sinkClosedImmediately", next);
+
+ t.closeSink(Cr.NS_OK);
+ t.expect(Cr.NS_ERROR_UNEXPECTED, [NOTHING]);
+}
+
+function sinkClosedWithReadableData(next) {
+ var t = new CopyTest("sinkClosedWithReadableData", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSink(Cr.NS_OK);
+ t.expect(Cr.NS_ERROR_UNEXPECTED, [NOTHING]);
+}
+
+function sinkClosedAfterWrite(next) {
+ var t = new CopyTest("sinkClosedAfterWrite", next);
+
+ t.addToSource(TWO_SEGMENTS);
+ t.makeSourceReadable(TWO_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(FIRST_SEGMENT.length, [FIRST_SEGMENT]);
+ t.closeSink(Cr.NS_OK);
+ t.expect(Cr.NS_ERROR_UNEXPECTED, [FIRST_SEGMENT]);
+}
+
+function sourceAndSinkClosed(next) {
+ var t = new CopyTest("sourceAndSinkClosed", next);
+
+ t.closeSourceThenSink(Cr.NS_OK, Cr.NS_OK);
+ t.expect(Cr.NS_OK, []);
+}
+
+function sinkAndSourceClosed(next) {
+ var t = new CopyTest("sinkAndSourceClosed", next);
+
+ t.closeSinkThenSource(Cr.NS_OK, Cr.NS_OK);
+
+ // sink notify received first, hence error
+ t.expect(Cr.NS_ERROR_UNEXPECTED, []);
+}
+
+function sourceAndSinkClosedWithPendingData(next) {
+ var t = new CopyTest("sourceAndSinkClosedWithPendingData", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+
+ t.closeSourceThenSink(Cr.NS_OK, Cr.NS_OK);
+
+ // not all data from source copied, so error
+ t.expect(Cr.NS_ERROR_UNEXPECTED, []);
+}
+
+function sinkAndSourceClosedWithPendingData(next) {
+ var t = new CopyTest("sinkAndSourceClosedWithPendingData", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+
+ t.closeSinkThenSource(Cr.NS_OK, Cr.NS_OK);
+
+ // not all data from source copied, plus sink notify received first, so error
+ t.expect(Cr.NS_ERROR_UNEXPECTED, []);
+}
+
+/** ***********
+ * UTILITIES *
+ *************/
+
+/** Returns the sum of the elements in arr. */
+function sum(arr) {
+ var s = 0;
+ for (var i = 0, sz = arr.length; i < sz; i++) {
+ s += arr[i];
+ }
+ return s;
+}
+
+/**
+ * Returns a constructor for an input or output stream callback that will wrap
+ * the one provided to it as an argument.
+ *
+ * @param wrapperCallback : (nsIInputStreamCallback | nsIOutputStreamCallback) : void
+ * the original callback object (not a function!) being wrapped
+ * @param name : string
+ * either "onInputStreamReady" if we're wrapping an input stream callback or
+ * "onOutputStreamReady" if we're wrapping an output stream callback
+ * @returns function(nsIInputStreamCallback | nsIOutputStreamCallback) : (nsIInputStreamCallback | nsIOutputStreamCallback)
+ * a constructor function which constructs a callback object (not function!)
+ * which, when called, first calls the original callback provided to it and
+ * then calls wrapperCallback
+ */
+function createStreamReadyInterceptor(wrapperCallback, name) {
+ return function StreamReadyInterceptor(callback) {
+ this.wrappedCallback = callback;
+ this[name] = function streamReadyInterceptor(stream) {
+ dumpn("*** StreamReadyInterceptor." + name);
+
+ try {
+ dumpn("*** calling original " + name + "...");
+ callback[name](stream);
+ } catch (e) {
+ dumpn("!!! error running inner callback: " + e);
+ throw e;
+ } finally {
+ dumpn("*** calling wrapper " + name + "...");
+ wrapperCallback[name](stream);
+ }
+ };
+ };
+}
+
+/**
+ * Print out a banner with the given message, uppercased, for debugging
+ * purposes.
+ */
+function note(m) {
+ m = m.toUpperCase();
+ var asterisks = Array(m.length + 1 + 4).join("*");
+ dumpn(asterisks + "\n* " + m + " *\n" + asterisks);
+}
+
+/** *********
+ * MOCKERY *
+ ***********/
+
+/*
+ * Blatantly violate abstractions in the name of testability. THIS IS NOT
+ * PUBLIC API! If you use any of these I will knowingly break your code by
+ * changing the names of variables and properties.
+ */
+// These are used in head.js.
+BinaryInputStream = function BIS(stream) {
+ return stream;
+};
+BinaryOutputStream = function BOS(stream) {
+ return stream;
+};
+Response.SEGMENT_SIZE = SEGMENT.length;
+// This overrides in httpd.js.
+overrideBinaryStreamsForTests(
+ BinaryInputStream,
+ BinaryOutputStream,
+ SEGMENT.length
+);
+
+/**
+ * Roughly mocks an nsIPipe, presenting non-blocking input and output streams
+ * that appear to also be binary streams and whose readability and writability
+ * amounts are configurable. Only the methods used in this test have been
+ * implemented -- these aren't exact mocks (can't be, actually, because input
+ * streams have unscriptable methods).
+ *
+ * @param name : string
+ * a name for this pipe, used in debugging output
+ */
+function CustomPipe(name) {
+ var self = this;
+
+ /** Data read from input that's buffered until it can be written to output. */
+ this._data = [];
+
+ /**
+ * The status of this pipe, which is to say the error result the ends of this
+ * pipe will return when attempts are made to use them. This value is always
+ * an error result when copying has finished, because success codes are
+ * converted to NS_BASE_STREAM_CLOSED.
+ */
+ this._status = Cr.NS_OK;
+
+ /** The input end of this pipe. */
+ var input = (this.inputStream = {
+ /** A name for this stream, used in debugging output. */
+ name: name + " input",
+
+ /**
+ * The number of bytes of data available to be read from this pipe, or
+ * Infinity if any amount of data in this pipe is made readable as soon as
+ * it is written to the pipe output.
+ */
+ _readable: 0,
+
+ /**
+ * Data regarding a pending stream-ready callback on this, or null if no
+ * callback is currently waiting to be called.
+ */
+ _waiter: null,
+
+ /**
+ * The event currently dispatched to make a stream-ready callback, if any
+ * such callback is currently ready to be made and not already in
+ * progress, or null when no callback is waiting to happen.
+ */
+ _event: null,
+
+ /**
+ * A stream-ready constructor to wrap an existing callback to intercept
+ * stream-ready notifications, or null if notifications shouldn't be
+ * wrapped at all.
+ */
+ _streamReadyInterceptCreator: null,
+
+ /**
+ * Registers a stream-ready wrapper creator function so that a
+ * stream-ready callback made in the future can be wrapped.
+ */
+ interceptStreamReadyCallbacks(streamReadyInterceptCreator) {
+ dumpn("*** [" + this.name + "].interceptStreamReadyCallbacks");
+
+ Assert.ok(
+ this._streamReadyInterceptCreator === null,
+ "intercepting twice"
+ );
+ this._streamReadyInterceptCreator = streamReadyInterceptCreator;
+ if (this._waiter) {
+ this._waiter.callback = new streamReadyInterceptCreator(
+ this._waiter.callback
+ );
+ }
+ },
+
+ /**
+ * Removes a previously-registered stream-ready wrapper creator function,
+ * also clearing any current wrapping.
+ */
+ removeStreamReadyInterceptor() {
+ dumpn("*** [" + this.name + "].removeStreamReadyInterceptor()");
+
+ Assert.ok(
+ this._streamReadyInterceptCreator !== null,
+ "removing interceptor when none present?"
+ );
+ this._streamReadyInterceptCreator = null;
+ if (this._waiter) {
+ this._waiter.callback = this._waiter.callback.wrappedCallback;
+ }
+ },
+
+ //
+ // see nsIAsyncInputStream.asyncWait
+ //
+ asyncWait: function asyncWait(callback, flags, requestedCount, target) {
+ dumpn("*** [" + this.name + "].asyncWait");
+
+ Assert.ok(callback && typeof callback !== "function");
+
+ var closureOnly =
+ (flags & Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY) !== 0;
+
+ Assert.ok(
+ this._waiter === null || (this._waiter.closureOnly && !closureOnly),
+ "asyncWait already called with a non-closure-only " +
+ "callback? unexpected!"
+ );
+
+ this._waiter = {
+ callback: this._streamReadyInterceptCreator
+ ? new this._streamReadyInterceptCreator(callback)
+ : callback,
+ closureOnly,
+ requestedCount,
+ eventTarget: target,
+ };
+
+ if (
+ !Components.isSuccessCode(self._status) ||
+ (!closureOnly &&
+ this._readable >= requestedCount &&
+ self._data.length >= requestedCount)
+ ) {
+ this._notify();
+ }
+ },
+
+ //
+ // see nsIAsyncInputStream.closeWithStatus
+ //
+ closeWithStatus: function closeWithStatus(status) {
+ // eslint-disable-next-line no-useless-concat
+ dumpn("*** [" + this.name + "].closeWithStatus" + "(" + status + ")");
+
+ if (!Components.isSuccessCode(self._status)) {
+ dumpn(
+ "*** ignoring second closure of [input " +
+ this.name +
+ "] " +
+ "(status " +
+ self._status +
+ ")"
+ );
+ return;
+ }
+
+ if (Components.isSuccessCode(status)) {
+ status = Cr.NS_BASE_STREAM_CLOSED;
+ }
+
+ self._status = status;
+
+ if (this._waiter) {
+ this._notify();
+ }
+ if (output._waiter) {
+ output._notify();
+ }
+ },
+
+ //
+ // see nsIBinaryInputStream.readByteArray
+ //
+ readByteArray: function readByteArray(count) {
+ dumpn("*** [" + this.name + "].readByteArray(" + count + ")");
+
+ if (self._data.length === 0) {
+ throw Components.isSuccessCode(self._status)
+ ? Cr.NS_BASE_STREAM_WOULD_BLOCK
+ : self._status;
+ }
+
+ Assert.ok(
+ this._readable <= self._data.length || this._readable === Infinity,
+ "consistency check"
+ );
+
+ if (this._readable < count || self._data.length < count) {
+ throw Components.Exception("", Cr.NS_BASE_STREAM_WOULD_BLOCK);
+ }
+ this._readable -= count;
+ return self._data.splice(0, count);
+ },
+
+ /**
+ * Makes the given number of additional bytes of data previously written
+ * to the pipe's output stream available for reading, triggering future
+ * notifications when required.
+ *
+ * @param count : uint
+ * the number of bytes of additional data to make available; must not be
+ * greater than the number of bytes already buffered but not made
+ * available by previous makeReadable calls
+ */
+ makeReadable: function makeReadable(count) {
+ dumpn("*** [" + this.name + "].makeReadable(" + count + ")");
+
+ Assert.ok(Components.isSuccessCode(self._status), "errant call");
+ Assert.ok(
+ this._readable + count <= self._data.length ||
+ this._readable === Infinity,
+ "increasing readable beyond written amount"
+ );
+
+ this._readable += count;
+
+ dumpn("readable: " + this._readable + ", data: " + self._data);
+
+ var waiter = this._waiter;
+ if (waiter !== null) {
+ if (waiter.requestedCount <= this._readable && !waiter.closureOnly) {
+ this._notify();
+ }
+ }
+ },
+
+ /**
+ * Disables the readability limit on this stream, meaning that as soon as
+ * *any* amount of data is written to output it becomes available from
+ * this stream and a stream-ready event is dispatched (if any stream-ready
+ * callback is currently set).
+ */
+ disableReadabilityLimit: function disableReadabilityLimit() {
+ dumpn("*** [" + this.name + "].disableReadabilityLimit()");
+
+ this._readable = Infinity;
+ },
+
+ //
+ // see nsIInputStream.available
+ //
+ available: function available() {
+ dumpn("*** [" + this.name + "].available()");
+
+ if (self._data.length === 0 && !Components.isSuccessCode(self._status)) {
+ throw self._status;
+ }
+
+ return Math.min(this._readable, self._data.length);
+ },
+
+ /**
+ * Dispatches a pending stream-ready event ahead of schedule, rather than
+ * waiting for it to be dispatched in response to normal writes. This is
+ * useful when writing to the output has completed, and we need to have
+ * read all data written to this stream. If the output isn't closed and
+ * the reading of data from this races ahead of the last write to output,
+ * we need a notification to know when everything that's been written has
+ * been read. This ordinarily might be supplied by closing output, but
+ * in some cases it's not desirable to close output, so this supplies an
+ * alternative method to get notified when the last write has occurred.
+ */
+ maybeNotifyFinally: function maybeNotifyFinally() {
+ dumpn("*** [" + this.name + "].maybeNotifyFinally()");
+
+ Assert.ok(this._waiter !== null, "must be waiting now");
+
+ if (self._data.length) {
+ dumpn(
+ "*** data still pending, normal notifications will signal " +
+ "completion"
+ );
+ return;
+ }
+
+ // No data waiting to be written, so notify. We could just close the
+ // stream, but that's less faithful to the server's behavior (it doesn't
+ // close the stream, and we're pretending to impersonate the server as
+ // much as we can here), so instead we're going to notify when no data
+ // can be read. The CopyTest has already been flagged as complete, so
+ // the stream listener will detect that this is a wrap-it-up notify and
+ // invoke the next test.
+ this._notify();
+ },
+
+ /**
+ * Dispatches an event to call a previously-registered stream-ready
+ * callback.
+ */
+ _notify: function _notify() {
+ dumpn("*** [" + this.name + "]._notify()");
+
+ var waiter = this._waiter;
+ Assert.ok(waiter !== null, "no waiter?");
+
+ if (this._event === null) {
+ var event = (this._event = {
+ run: function run() {
+ input._waiter = null;
+ input._event = null;
+ try {
+ Assert.ok(
+ !Components.isSuccessCode(self._status) ||
+ input._readable >= waiter.requestedCount
+ );
+ waiter.callback.onInputStreamReady(input);
+ } catch (e) {
+ do_throw("error calling onInputStreamReady: " + e);
+ }
+ },
+ });
+ waiter.eventTarget.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+ });
+
+ /** The output end of this pipe. */
+ var output = (this.outputStream = {
+ /** A name for this stream, used in debugging output. */
+ name: name + " output",
+
+ /**
+ * The number of bytes of data which may be written to this pipe without
+ * blocking.
+ */
+ _writable: 0,
+
+ /**
+ * The increments in which pending data should be written, rather than
+ * simply defaulting to the amount requested (which, given that
+ * input.asyncWait precisely respects the requestedCount argument, will
+ * ordinarily always be writable in that amount), as an array whose
+ * elements from start to finish are the number of bytes to write each
+ * time write() or writeByteArray() is subsequently called. The sum of
+ * the values in this array, if this array is not empty, is always equal
+ * to this._writable.
+ */
+ _writableAmounts: [],
+
+ /**
+ * Data regarding a pending stream-ready callback on this, or null if no
+ * callback is currently waiting to be called.
+ */
+ _waiter: null,
+
+ /**
+ * The event currently dispatched to make a stream-ready callback, if any
+ * such callback is currently ready to be made and not already in
+ * progress, or null when no callback is waiting to happen.
+ */
+ _event: null,
+
+ /**
+ * A stream-ready constructor to wrap an existing callback to intercept
+ * stream-ready notifications, or null if notifications shouldn't be
+ * wrapped at all.
+ */
+ _streamReadyInterceptCreator: null,
+
+ /**
+ * Registers a stream-ready wrapper creator function so that a
+ * stream-ready callback made in the future can be wrapped.
+ */
+ interceptStreamReadyCallbacks(streamReadyInterceptCreator) {
+ dumpn("*** [" + this.name + "].interceptStreamReadyCallbacks");
+
+ Assert.ok(
+ this._streamReadyInterceptCreator !== null,
+ "intercepting onOutputStreamReady twice"
+ );
+ this._streamReadyInterceptCreator = streamReadyInterceptCreator;
+ if (this._waiter) {
+ this._waiter.callback = new streamReadyInterceptCreator(
+ this._waiter.callback
+ );
+ }
+ },
+
+ /**
+ * Removes a previously-registered stream-ready wrapper creator function,
+ * also clearing any current wrapping.
+ */
+ removeStreamReadyInterceptor() {
+ dumpn("*** [" + this.name + "].removeStreamReadyInterceptor()");
+
+ Assert.ok(
+ this._streamReadyInterceptCreator !== null,
+ "removing interceptor when none present?"
+ );
+ this._streamReadyInterceptCreator = null;
+ if (this._waiter) {
+ this._waiter.callback = this._waiter.callback.wrappedCallback;
+ }
+ },
+
+ //
+ // see nsIAsyncOutputStream.asyncWait
+ //
+ asyncWait: function asyncWait(callback, flags, requestedCount, target) {
+ dumpn("*** [" + this.name + "].asyncWait");
+
+ Assert.ok(callback && typeof callback !== "function");
+
+ var closureOnly =
+ (flags & Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY) !== 0;
+
+ Assert.ok(
+ this._waiter === null || (this._waiter.closureOnly && !closureOnly),
+ "asyncWait already called with a non-closure-only " +
+ "callback? unexpected!"
+ );
+
+ this._waiter = {
+ callback: this._streamReadyInterceptCreator
+ ? new this._streamReadyInterceptCreator(callback)
+ : callback,
+ closureOnly,
+ requestedCount,
+ eventTarget: target,
+ toString: function toString() {
+ return (
+ "waiter(" +
+ (closureOnly ? "closure only, " : "") +
+ "requestedCount: " +
+ requestedCount +
+ ", target: " +
+ target +
+ ")"
+ );
+ },
+ };
+
+ if (
+ (!closureOnly && this._writable >= requestedCount) ||
+ !Components.isSuccessCode(this.status)
+ ) {
+ this._notify();
+ }
+ },
+
+ //
+ // see nsIAsyncOutputStream.closeWithStatus
+ //
+ closeWithStatus: function closeWithStatus(status) {
+ dumpn("*** [" + this.name + "].closeWithStatus(" + status + ")");
+
+ if (!Components.isSuccessCode(self._status)) {
+ dumpn(
+ "*** ignoring redundant closure of [input " +
+ this.name +
+ "] " +
+ "because it's already closed (status " +
+ self._status +
+ ")"
+ );
+ return;
+ }
+
+ if (Components.isSuccessCode(status)) {
+ status = Cr.NS_BASE_STREAM_CLOSED;
+ }
+
+ self._status = status;
+
+ if (input._waiter) {
+ input._notify();
+ }
+ if (this._waiter) {
+ this._notify();
+ }
+ },
+
+ //
+ // see nsIBinaryOutputStream.writeByteArray
+ //
+ writeByteArray: function writeByteArray(bytes) {
+ dumpn(`*** [${this.name}].writeByteArray([${bytes}])`);
+
+ if (!Components.isSuccessCode(self._status)) {
+ throw self._status;
+ }
+
+ Assert.equal(
+ this._writableAmounts.length,
+ 0,
+ "writeByteArray can't support specified-length writes"
+ );
+
+ if (this._writable < bytes.length) {
+ throw Components.Exception("", Cr.NS_BASE_STREAM_WOULD_BLOCK);
+ }
+
+ self._data.push.apply(self._data, bytes);
+ this._writable -= bytes.length;
+
+ if (
+ input._readable === Infinity &&
+ input._waiter &&
+ !input._waiter.closureOnly
+ ) {
+ input._notify();
+ }
+ },
+
+ //
+ // see nsIOutputStream.write
+ //
+ write: function write(str, length) {
+ dumpn("*** [" + this.name + "].write");
+
+ Assert.equal(str.length, length, "sanity");
+ if (!Components.isSuccessCode(self._status)) {
+ throw self._status;
+ }
+ if (this._writable === 0) {
+ throw Components.Exception("", Cr.NS_BASE_STREAM_WOULD_BLOCK);
+ }
+
+ var actualWritten;
+ if (this._writableAmounts.length === 0) {
+ actualWritten = Math.min(this._writable, length);
+ } else {
+ Assert.ok(
+ this._writable >= this._writableAmounts[0],
+ "writable amounts value greater than writable data?"
+ );
+ Assert.equal(
+ this._writable,
+ sum(this._writableAmounts),
+ "total writable amount not equal to sum of writable increments"
+ );
+ actualWritten = this._writableAmounts.shift();
+ }
+
+ var bytes = str
+ .substring(0, actualWritten)
+ .split("")
+ .map(function (v) {
+ return v.charCodeAt(0);
+ });
+
+ self._data.push.apply(self._data, bytes);
+ this._writable -= actualWritten;
+
+ if (
+ input._readable === Infinity &&
+ input._waiter &&
+ !input._waiter.closureOnly
+ ) {
+ input._notify();
+ }
+
+ return actualWritten;
+ },
+
+ /**
+ * Increase the amount of data that can be written without blocking by the
+ * given number of bytes, triggering future notifications when required.
+ *
+ * @param count : uint
+ * the number of bytes of additional data to make writable
+ */
+ makeWritable: function makeWritable(count) {
+ dumpn("*** [" + this.name + "].makeWritable(" + count + ")");
+
+ Assert.ok(Components.isSuccessCode(self._status));
+
+ this._writable += count;
+
+ var waiter = this._waiter;
+ if (
+ waiter &&
+ !waiter.closureOnly &&
+ waiter.requestedCount <= this._writable
+ ) {
+ this._notify();
+ }
+ },
+
+ /**
+ * Increase the amount of data that can be written without blocking, but
+ * do so by specifying a number of bytes that will be written each time
+ * a write occurs, even as asyncWait notifications are initially triggered
+ * as usual. Thus, rather than writes eagerly writing everything possible
+ * at each step, attempts to write out data by segment devolve into a
+ * partial segment write, then another, and so on until the amount of data
+ * specified as permitted to be written, has been written.
+ *
+ * Note that the writeByteArray method is incompatible with the previous
+ * calling of this method, in that, until all increments provided to this
+ * method have been consumed, writeByteArray cannot be called. Once all
+ * increments have been consumed, writeByteArray may again be called.
+ *
+ * @param increments : [uint]
+ * an array whose elements are positive numbers of bytes to permit to be
+ * written each time write() is subsequently called on this, ignoring
+ * the total amount of writable space specified by the sum of all
+ * increments
+ */
+ makeWritableByIncrements: function makeWritableByIncrements(increments) {
+ dumpn(
+ "*** [" +
+ this.name +
+ "].makeWritableByIncrements" +
+ "([" +
+ increments.join(", ") +
+ "])"
+ );
+
+ Assert.greater(increments.length, 0, "bad increments");
+ Assert.ok(
+ increments.every(function (v) {
+ return v > 0;
+ }),
+ "zero increment?"
+ );
+
+ Assert.ok(Components.isSuccessCode(self._status));
+
+ this._writable += sum(increments);
+ this._writableAmounts = increments;
+
+ var waiter = this._waiter;
+ if (
+ waiter &&
+ !waiter.closureOnly &&
+ waiter.requestedCount <= this._writable
+ ) {
+ this._notify();
+ }
+ },
+
+ /**
+ * Dispatches an event to call a previously-registered stream-ready
+ * callback.
+ */
+ _notify: function _notify() {
+ dumpn("*** [" + this.name + "]._notify()");
+
+ var waiter = this._waiter;
+ Assert.ok(waiter !== null, "no waiter?");
+
+ if (this._event === null) {
+ var event = (this._event = {
+ run: function run() {
+ output._waiter = null;
+ output._event = null;
+
+ try {
+ waiter.callback.onOutputStreamReady(output);
+ } catch (e) {
+ do_throw("error calling onOutputStreamReady: " + e);
+ }
+ },
+ });
+ waiter.eventTarget.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+ });
+}
+
+/**
+ * Represents a sequence of interactions to perform with a copier, in a given
+ * order and at the desired time intervals.
+ *
+ * @param name : string
+ * test name, used in debugging output
+ */
+function CopyTest(name, next) {
+ /** Name used in debugging output. */
+ this.name = name;
+
+ /** A function called when the test completes. */
+ this._done = next;
+
+ var sourcePipe = new CustomPipe(name + "-source");
+
+ /** The source of data for the copier to copy. */
+ this._source = sourcePipe.inputStream;
+
+ /**
+ * The sink to which to write data which will appear in the copier's source.
+ */
+ this._copyableDataStream = sourcePipe.outputStream;
+
+ var sinkPipe = new CustomPipe(name + "-sink");
+
+ /** The sink to which the copier copies data. */
+ this._sink = sinkPipe.outputStream;
+
+ /** Input stream from which to read data the copier's written to its sink. */
+ this._copiedDataStream = sinkPipe.inputStream;
+
+ this._copiedDataStream.disableReadabilityLimit();
+
+ /**
+ * True if there's a callback waiting to read data written by the copier to
+ * its output, from the input end of the pipe representing the copier's sink.
+ */
+ this._waitingForData = false;
+
+ /**
+ * An array of the bytes of data expected to be written to output by the
+ * copier when this test runs.
+ */
+ this._expectedData = undefined;
+
+ /** Array of bytes of data received so far. */
+ this._receivedData = [];
+
+ /** The expected final status returned by the copier. */
+ this._expectedStatus = -1;
+
+ /** The actual final status returned by the copier. */
+ this._actualStatus = -1;
+
+ /** The most recent sequence of bytes written to output by the copier. */
+ this._lastQuantum = [];
+
+ /**
+ * True iff we've received the last quantum of data written to the sink by the
+ * copier.
+ */
+ this._allDataWritten = false;
+
+ /**
+ * True iff the copier has notified its associated stream listener of
+ * completion.
+ */
+ this._copyingFinished = false;
+
+ /** Index of the next task to execute while driving the copier. */
+ this._currentTask = 0;
+
+ /** Array containing all tasks to run. */
+ this._tasks = [];
+
+ /** The copier used by this test. */
+ this._copier = new WriteThroughCopier(this._source, this._sink, this, null);
+
+ // Start watching for data written by the copier to the sink.
+ this._waitForWrittenData();
+}
+CopyTest.prototype = {
+ /**
+ * Adds the given array of bytes to data in the copier's source.
+ *
+ * @param bytes : [uint]
+ * array of bytes of data to add to the source for the copier
+ */
+ addToSource: function addToSource(bytes) {
+ var self = this;
+ this._addToTasks(function addToSourceTask() {
+ note("addToSourceTask");
+
+ try {
+ self._copyableDataStream.makeWritable(bytes.length);
+ self._copyableDataStream.writeByteArray(bytes);
+ } finally {
+ self._stageNextTask();
+ }
+ });
+ },
+
+ /**
+ * Makes bytes of data previously added to the source available to be read by
+ * the copier.
+ *
+ * @param count : uint
+ * number of bytes to make available for reading
+ */
+ makeSourceReadable: function makeSourceReadable(count) {
+ var self = this;
+ this._addToTasks(function makeSourceReadableTask() {
+ note("makeSourceReadableTask");
+
+ self._source.makeReadable(count);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Increases available space in the sink by the given amount, waits for the
+ * given series of arrays of bytes to be written to sink by the copier, and
+ * causes execution to asynchronously continue to the next task when the last
+ * of those arrays of bytes is received.
+ *
+ * @param bytes : uint
+ * number of bytes of space to make available in the sink
+ * @param dataQuantums : [[uint]]
+ * array of byte arrays to expect to be written in sequence to the sink
+ */
+ makeSinkWritableAndWaitFor: function makeSinkWritableAndWaitFor(
+ bytes,
+ dataQuantums
+ ) {
+ var self = this;
+
+ Assert.equal(
+ bytes,
+ dataQuantums.reduce(function (partial, current) {
+ return partial + current.length;
+ }, 0),
+ "bytes/quantums mismatch"
+ );
+
+ function increaseSinkSpaceTask() {
+ /* Now do the actual work to trigger the interceptor. */
+ self._sink.makeWritable(bytes);
+ }
+
+ this._waitForHelper(
+ "increaseSinkSpaceTask",
+ dataQuantums,
+ increaseSinkSpaceTask
+ );
+ },
+
+ /**
+ * Increases available space in the sink by the given amount, waits for the
+ * given series of arrays of bytes to be written to sink by the copier, and
+ * causes execution to asynchronously continue to the next task when the last
+ * of those arrays of bytes is received.
+ *
+ * @param bytes : uint
+ * number of bytes of space to make available in the sink
+ * @param dataQuantums : [[uint]]
+ * array of byte arrays to expect to be written in sequence to the sink
+ */
+ makeSinkWritableByIncrementsAndWaitFor:
+ function makeSinkWritableByIncrementsAndWaitFor(bytes, dataQuantums) {
+ var self = this;
+
+ var desiredAmounts = dataQuantums.map(function (v) {
+ return v.length;
+ });
+ Assert.equal(bytes, sum(desiredAmounts), "bytes/quantums mismatch");
+
+ function increaseSinkSpaceByIncrementsTask() {
+ /* Now do the actual work to trigger the interceptor incrementally. */
+ self._sink.makeWritableByIncrements(desiredAmounts);
+ }
+
+ this._waitForHelper(
+ "increaseSinkSpaceByIncrementsTask",
+ dataQuantums,
+ increaseSinkSpaceByIncrementsTask
+ );
+ },
+
+ /**
+ * Close the copier's source stream, then asynchronously continue to the next
+ * task.
+ *
+ * @param status : nsresult
+ * the status to provide when closing the copier's source stream
+ */
+ closeSource: function closeSource(status) {
+ var self = this;
+
+ this._addToTasks(function closeSourceTask() {
+ note("closeSourceTask");
+
+ self._source.closeWithStatus(status);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Close the copier's source stream, then wait for the given number of bytes
+ * and for the given series of arrays of bytes to be written to the sink, then
+ * asynchronously continue to the next task.
+ *
+ * @param status : nsresult
+ * the status to provide when closing the copier's source stream
+ * @param bytes : uint
+ * number of bytes of space to make available in the sink
+ * @param dataQuantums : [[uint]]
+ * array of byte arrays to expect to be written in sequence to the sink
+ */
+ closeSourceAndWaitFor: function closeSourceAndWaitFor(
+ status,
+ bytes,
+ dataQuantums
+ ) {
+ var self = this;
+
+ Assert.equal(
+ bytes,
+ sum(
+ dataQuantums.map(function (v) {
+ return v.length;
+ })
+ ),
+ "bytes/quantums mismatch"
+ );
+
+ function closeSourceAndWaitForTask() {
+ self._sink.makeWritable(bytes);
+ self._copyableDataStream.closeWithStatus(status);
+ }
+
+ this._waitForHelper(
+ "closeSourceAndWaitForTask",
+ dataQuantums,
+ closeSourceAndWaitForTask
+ );
+ },
+
+ /**
+ * Closes the copier's sink stream, providing the given status, then
+ * asynchronously continue to the next task.
+ *
+ * @param status : nsresult
+ * the status to provide when closing the copier's sink stream
+ */
+ closeSink: function closeSink(status) {
+ var self = this;
+ this._addToTasks(function closeSinkTask() {
+ note("closeSinkTask");
+
+ self._sink.closeWithStatus(status);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Closes the copier's source stream, then immediately closes the copier's
+ * sink stream, then asynchronously continues to the next task.
+ *
+ * @param sourceStatus : nsresult
+ * the status to provide when closing the copier's source stream
+ * @param sinkStatus : nsresult
+ * the status to provide when closing the copier's sink stream
+ */
+ closeSourceThenSink: function closeSourceThenSink(sourceStatus, sinkStatus) {
+ var self = this;
+ this._addToTasks(function closeSourceThenSinkTask() {
+ note("closeSourceThenSinkTask");
+
+ self._source.closeWithStatus(sourceStatus);
+ self._sink.closeWithStatus(sinkStatus);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Closes the copier's sink stream, then immediately closes the copier's
+ * source stream, then asynchronously continues to the next task.
+ *
+ * @param sinkStatus : nsresult
+ * the status to provide when closing the copier's sink stream
+ * @param sourceStatus : nsresult
+ * the status to provide when closing the copier's source stream
+ */
+ closeSinkThenSource: function closeSinkThenSource(sinkStatus, sourceStatus) {
+ var self = this;
+ this._addToTasks(function closeSinkThenSourceTask() {
+ note("closeSinkThenSource");
+
+ self._sink.closeWithStatus(sinkStatus);
+ self._source.closeWithStatus(sourceStatus);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Indicates that the given status is expected to be returned when the stream
+ * listener for the copy indicates completion, that the expected data copied
+ * by the copier to sink are the concatenation of the arrays of bytes in
+ * receivedData, and kicks off the tasks in this test.
+ *
+ * @param expectedStatus : nsresult
+ * the status expected to be returned by the copier at completion
+ * @param receivedData : [[uint]]
+ * an array containing arrays of bytes whose concatenation constitutes the
+ * expected copied data
+ */
+ expect: function expect(expectedStatus, receivedData) {
+ this._expectedStatus = expectedStatus;
+ this._expectedData = [];
+ for (var i = 0, sz = receivedData.length; i < sz; i++) {
+ this._expectedData.push.apply(this._expectedData, receivedData[i]);
+ }
+
+ this._stageNextTask();
+ },
+
+ /**
+ * Sets up a stream interceptor that will verify that each piece of data
+ * written to the sink by the copier corresponds to the currently expected
+ * pieces of data, calls the trigger, then waits for those pieces of data to
+ * be received. Once all have been received, the interceptor is removed and
+ * the next task is asynchronously executed.
+ *
+ * @param name : string
+ * name of the task created by this, used in debugging output
+ * @param dataQuantums : [[uint]]
+ * array of expected arrays of bytes to be written to the sink by the copier
+ * @param trigger : function() : void
+ * function to call after setting up the interceptor to wait for
+ * notifications (which will be generated as a result of this function's
+ * actions)
+ */
+ _waitForHelper: function _waitForHelper(name, dataQuantums, trigger) {
+ var self = this;
+ this._addToTasks(function waitForHelperTask() {
+ note(name);
+
+ var quantumIndex = 0;
+
+ /*
+ * Intercept all data-available notifications so we can continue when all
+ * the ones we expect have been received.
+ */
+ var streamReadyCallback = {
+ onInputStreamReady: function wrapperOnInputStreamReady(input) {
+ dumpn(
+ "*** streamReadyCallback.onInputStreamReady" +
+ "(" +
+ input.name +
+ ")"
+ );
+
+ Assert.equal(this, streamReadyCallback, "sanity");
+
+ try {
+ if (quantumIndex < dataQuantums.length) {
+ var quantum = dataQuantums[quantumIndex++];
+ var sz = quantum.length;
+ Assert.equal(
+ self._lastQuantum.length,
+ sz,
+ "different quantum lengths"
+ );
+ for (var i = 0; i < sz; i++) {
+ Assert.equal(
+ self._lastQuantum[i],
+ quantum[i],
+ "bad data at " + i
+ );
+ }
+
+ dumpn(
+ "*** waiting to check remaining " +
+ (dataQuantums.length - quantumIndex) +
+ " quantums..."
+ );
+ }
+ } finally {
+ if (quantumIndex === dataQuantums.length) {
+ dumpn("*** data checks completed! next task...");
+ self._copiedDataStream.removeStreamReadyInterceptor();
+ self._stageNextTask();
+ }
+ }
+ },
+ };
+
+ var interceptor = createStreamReadyInterceptor(
+ streamReadyCallback,
+ "onInputStreamReady"
+ );
+ self._copiedDataStream.interceptStreamReadyCallbacks(interceptor);
+
+ /* Do the deed. */
+ trigger();
+ });
+ },
+
+ /**
+ * Initiates asynchronous waiting for data written to the copier's sink to be
+ * available for reading from the input end of the sink's pipe. The callback
+ * stores the received data for comparison in the interceptor used in the
+ * callback added by _waitForHelper and signals test completion when it
+ * receives a zero-data-available notification (if the copier has notified
+ * that it is finished; otherwise allows execution to continue until that has
+ * occurred).
+ */
+ _waitForWrittenData: function _waitForWrittenData() {
+ dumpn("*** _waitForWrittenData (" + this.name + ")");
+
+ var self = this;
+ var outputWrittenWatcher = {
+ onInputStreamReady: function onInputStreamReady(input) {
+ dumpn(
+ // eslint-disable-next-line no-useless-concat
+ "*** outputWrittenWatcher.onInputStreamReady" + "(" + input.name + ")"
+ );
+
+ if (self._allDataWritten) {
+ do_throw(
+ "ruh-roh! why are we getting notified of more data " +
+ "after we should have received all of it?"
+ );
+ }
+
+ self._waitingForData = false;
+
+ try {
+ var avail = input.available();
+ } catch (e) {
+ dumpn("*** available() threw! error: " + e);
+ if (self._completed) {
+ dumpn(
+ "*** NB: this isn't a problem, because we've copied " +
+ "completely now, and this notify may have been expedited " +
+ "by maybeNotifyFinally such that we're being called when " +
+ "we can *guarantee* nothing is available any more"
+ );
+ }
+ avail = 0;
+ }
+
+ if (avail > 0) {
+ var data = input.readByteArray(avail);
+ Assert.equal(
+ data.length,
+ avail,
+ "readByteArray returned wrong number of bytes?"
+ );
+ self._lastQuantum = data;
+ self._receivedData.push.apply(self._receivedData, data);
+ }
+
+ if (avail === 0) {
+ dumpn("*** all data received!");
+
+ self._allDataWritten = true;
+
+ if (self._copyingFinished) {
+ dumpn("*** copying already finished, continuing to next test");
+ self._testComplete();
+ } else {
+ dumpn("*** copying not finished, waiting for that to happen");
+ }
+
+ return;
+ }
+
+ self._waitForWrittenData();
+ },
+ };
+
+ this._copiedDataStream.asyncWait(
+ outputWrittenWatcher,
+ 0,
+ 1,
+ Services.tm.currentThread
+ );
+ this._waitingForData = true;
+ },
+
+ /**
+ * Indicates this test is complete, does the final data-received and copy
+ * status comparisons, and calls the test-completion function provided when
+ * this test was first created.
+ */
+ _testComplete: function _testComplete() {
+ dumpn("*** CopyTest(" + this.name + ") complete! On to the next test...");
+
+ try {
+ Assert.ok(this._allDataWritten, "expect all data written now!");
+ Assert.ok(this._copyingFinished, "expect copying finished now!");
+
+ Assert.equal(
+ this._actualStatus,
+ this._expectedStatus,
+ "wrong final status"
+ );
+
+ var expected = this._expectedData,
+ received = this._receivedData;
+ dumpn("received: [" + received + "], expected: [" + expected + "]");
+ Assert.equal(received.length, expected.length, "wrong data");
+ for (var i = 0, sz = expected.length; i < sz; i++) {
+ Assert.equal(received[i], expected[i], "bad data at " + i);
+ }
+ } catch (e) {
+ dumpn("!!! ERROR PERFORMING FINAL " + this.name + " CHECKS! " + e);
+ throw e;
+ } finally {
+ dumpn(
+ "*** CopyTest(" +
+ this.name +
+ ") complete! " +
+ "Invoking test-completion callback..."
+ );
+ this._done();
+ }
+ },
+
+ /** Dispatches an event at this thread which will run the next task. */
+ _stageNextTask: function _stageNextTask() {
+ dumpn("*** CopyTest(" + this.name + ")._stageNextTask()");
+
+ if (this._currentTask === this._tasks.length) {
+ dumpn("*** CopyTest(" + this.name + ") tasks complete!");
+ return;
+ }
+
+ var task = this._tasks[this._currentTask++];
+ var event = {
+ run: function run() {
+ try {
+ task();
+ } catch (e) {
+ do_throw("exception thrown running task: " + e);
+ }
+ },
+ };
+ Services.tm.dispatchToMainThread(event);
+ },
+
+ /**
+ * Adds the given function as a task to be run at a later time.
+ *
+ * @param task : function() : void
+ * the function to call as a task
+ */
+ _addToTasks: function _addToTasks(task) {
+ this._tasks.push(task);
+ },
+
+ //
+ // see nsIRequestObserver.onStartRequest
+ //
+ onStartRequest: function onStartRequest(self) {
+ dumpn("*** CopyTest.onStartRequest (" + self.name + ")");
+
+ Assert.equal(this._receivedData.length, 0);
+ Assert.equal(this._lastQuantum.length, 0);
+ },
+
+ //
+ // see nsIRequestObserver.onStopRequest
+ //
+ onStopRequest: function onStopRequest(self, status) {
+ dumpn("*** CopyTest.onStopRequest (" + self.name + ", " + status + ")");
+
+ this._actualStatus = status;
+
+ this._copyingFinished = true;
+
+ if (this._allDataWritten) {
+ dumpn("*** all data written, continuing with remaining tests...");
+ this._testComplete();
+ } else {
+ /*
+ * Everything's copied as far as the copier is concerned. However, there
+ * may be a backup transferring from the output end of the copy sink to
+ * the input end where we can actually verify that the expected data was
+ * written as expected, because that transfer occurs asynchronously. If
+ * we do final data-received checks now, we'll miss still-pending data.
+ * Therefore, to wrap up this copy test we still need to asynchronously
+ * wait on the input end of the sink until we hit end-of-stream or some
+ * error condition. Then we know we're done and can continue with the
+ * next test.
+ */
+ dumpn("*** not all data copied, waiting for that to happen...");
+
+ if (!this._waitingForData) {
+ this._waitForWrittenData();
+ }
+
+ this._copiedDataStream.maybeNotifyFinally();
+ }
+ },
+};
diff --git a/netwerk/test/httpserver/test/test_basic_functionality.js b/netwerk/test/httpserver/test/test_basic_functionality.js
new file mode 100644
index 0000000000..b8864abd17
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_basic_functionality.js
@@ -0,0 +1,182 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Basic functionality test, from the client programmer's POV.
+ */
+
+ChromeUtils.defineLazyGetter(this, "port", function () {
+ return srv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ "http://localhost:" + port + "/objHandler",
+ null,
+ start_objHandler,
+ null
+ ),
+ new Test(
+ "http://localhost:" + port + "/functionHandler",
+ null,
+ start_functionHandler,
+ null
+ ),
+ new Test(
+ "http://localhost:" + port + "/nonexistent-path",
+ null,
+ start_non_existent_path,
+ null
+ ),
+ new Test(
+ "http://localhost:" + port + "/lotsOfHeaders",
+ null,
+ start_lots_of_headers,
+ null
+ ),
+ ];
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+
+ // base path
+ // XXX should actually test this works with a file by comparing streams!
+ var path = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ srv.registerDirectory("/", path);
+
+ // register a few test paths
+ srv.registerPathHandler("/objHandler", objHandler);
+ srv.registerPathHandler("/functionHandler", functionHandler);
+ srv.registerPathHandler("/lotsOfHeaders", lotsOfHeadersHandler);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+const HEADER_COUNT = 1000;
+
+// TEST DATA
+
+// common properties *always* appended by server
+// or invariants for every URL in paths
+function commonCheck(ch) {
+ Assert.ok(ch.contentLength > -1);
+ Assert.equal(ch.getResponseHeader("connection"), "close");
+ Assert.ok(!ch.isNoStoreResponse());
+ Assert.ok(!ch.isPrivateResponse());
+}
+
+function start_objHandler(ch) {
+ commonCheck(ch);
+
+ Assert.equal(ch.responseStatus, 200);
+ Assert.ok(ch.requestSucceeded);
+ Assert.equal(ch.getResponseHeader("content-type"), "text/plain");
+ Assert.equal(ch.responseStatusText, "OK");
+
+ var reqMin = {},
+ reqMaj = {},
+ respMin = {},
+ respMaj = {};
+ ch.getRequestVersion(reqMaj, reqMin);
+ ch.getResponseVersion(respMaj, respMin);
+ Assert.ok(reqMaj.value == respMaj.value && reqMin.value == respMin.value);
+}
+
+function start_functionHandler(ch) {
+ commonCheck(ch);
+
+ Assert.equal(ch.responseStatus, 404);
+ Assert.ok(!ch.requestSucceeded);
+ Assert.equal(ch.getResponseHeader("foopy"), "quux-baz");
+ Assert.equal(ch.responseStatusText, "Page Not Found");
+
+ var reqMin = {},
+ reqMaj = {},
+ respMin = {},
+ respMaj = {};
+ ch.getRequestVersion(reqMaj, reqMin);
+ ch.getResponseVersion(respMaj, respMin);
+ Assert.ok(reqMaj.value == 1 && reqMin.value == 1);
+ Assert.ok(respMaj.value == 1 && respMin.value == 1);
+}
+
+function start_non_existent_path(ch) {
+ commonCheck(ch);
+
+ Assert.equal(ch.responseStatus, 404);
+ Assert.ok(!ch.requestSucceeded);
+}
+
+function start_lots_of_headers(ch) {
+ commonCheck(ch);
+
+ Assert.equal(ch.responseStatus, 200);
+ Assert.ok(ch.requestSucceeded);
+
+ for (var i = 0; i < HEADER_COUNT; i++) {
+ Assert.equal(ch.getResponseHeader("X-Header-" + i), "value " + i);
+ }
+}
+
+// PATH HANDLERS
+
+// /objHandler
+var objHandler = {
+ handle(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = "Request (slightly reformatted):\n\n";
+ body += metadata.method + " " + metadata.path;
+
+ Assert.equal(metadata.port, port);
+
+ if (metadata.queryString) {
+ body += "?" + metadata.queryString;
+ }
+
+ body += " HTTP/" + metadata.httpVersion + "\n";
+
+ var headEnum = metadata.headers;
+ while (headEnum.hasMoreElements()) {
+ var fieldName = headEnum
+ .getNext()
+ .QueryInterface(Ci.nsISupportsString).data;
+ body += fieldName + ": " + metadata.getHeader(fieldName) + "\n";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIHttpRequestHandler"]),
+};
+
+// /functionHandler
+function functionHandler(metadata, response) {
+ response.setStatusLine("1.1", 404, "Page Not Found");
+ response.setHeader("foopy", "quux-baz", false);
+
+ Assert.equal(metadata.port, port);
+ Assert.equal(metadata.host, "localhost");
+ Assert.equal(metadata.path.charAt(0), "/");
+
+ var body = "this is text\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /lotsOfHeaders
+function lotsOfHeadersHandler(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+
+ for (var i = 0; i < HEADER_COUNT; i++) {
+ response.setHeader("X-Header-" + i, "value " + i, false);
+ }
+}
diff --git a/netwerk/test/httpserver/test/test_body_length.js b/netwerk/test/httpserver/test/test_body_length.js
new file mode 100644
index 0000000000..85f35d7441
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_body_length.js
@@ -0,0 +1,68 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests that the Content-Length header in incoming requests is interpreted as
+ * a decimal number, even if it has the form (including leading zero) of an
+ * octal number.
+ */
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+ srv.registerPathHandler("/content-length", contentLength);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+const REQUEST_DATA = "12345678901234567";
+
+function contentLength(request, response) {
+ Assert.equal(request.method, "POST");
+ Assert.equal(request.getHeader("Content-Length"), "017");
+
+ var body = new ScriptableInputStream(request.bodyInputStream);
+
+ var avail;
+ var data = "";
+ while ((avail = body.available()) > 0) {
+ data += body.read(avail);
+ }
+
+ Assert.equal(data, REQUEST_DATA);
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/content-length",
+ init_content_length
+ ),
+ ];
+});
+
+function init_content_length(ch) {
+ var content = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ content.data = REQUEST_DATA;
+
+ ch.QueryInterface(Ci.nsIUploadChannel).setUploadStream(
+ content,
+ "text/plain",
+ REQUEST_DATA.length
+ );
+
+ // Override the values implicitly set by setUploadStream above.
+ ch.requestMethod = "POST";
+ ch.setRequestHeader("Content-Length", "017", false); // 17 bytes, not 15
+}
diff --git a/netwerk/test/httpserver/test/test_byte_range.js b/netwerk/test/httpserver/test/test_byte_range.js
new file mode 100644
index 0000000000..ef92824734
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_byte_range.js
@@ -0,0 +1,272 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// checks if a byte range request and non-byte range request retrieve the
+// correct data.
+
+var srv;
+ChromeUtils.defineLazyGetter(this, "PREFIX", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange,
+ start_byterange,
+ stop_byterange
+ ),
+ new Test(PREFIX + "/range.txt", init_byterange2, start_byterange2),
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange3,
+ start_byterange3,
+ stop_byterange3
+ ),
+ new Test(PREFIX + "/range.txt", init_byterange4, start_byterange4),
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange5,
+ start_byterange5,
+ stop_byterange5
+ ),
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange6,
+ start_byterange6,
+ stop_byterange6
+ ),
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange7,
+ start_byterange7,
+ stop_byterange7
+ ),
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange8,
+ start_byterange8,
+ stop_byterange8
+ ),
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange9,
+ start_byterange9,
+ stop_byterange9
+ ),
+ new Test(PREFIX + "/range.txt", init_byterange10, start_byterange10),
+ new Test(
+ PREFIX + "/range.txt",
+ init_byterange11,
+ start_byterange11,
+ stop_byterange11
+ ),
+ new Test(PREFIX + "/empty.txt", null, start_byterange12, stop_byterange12),
+ new Test(
+ PREFIX + "/headers.txt",
+ init_byterange13,
+ start_byterange13,
+ null
+ ),
+ new Test(PREFIX + "/range.txt", null, start_normal, stop_normal),
+ ];
+});
+
+function run_test() {
+ srv = createServer();
+ var dir = do_get_file("data/ranges/");
+ srv.registerDirectory("/", dir);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+function start_normal(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.getResponseHeader("Content-Length"), "21");
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function stop_normal(ch, status, data) {
+ Assert.equal(data.length, 21);
+ Assert.equal(data[0], 0x54);
+ Assert.equal(data[20], 0x0a);
+}
+
+function init_byterange(ch) {
+ ch.setRequestHeader("Range", "bytes=10-", false);
+}
+
+function start_byterange(ch) {
+ Assert.equal(ch.responseStatus, 206);
+ Assert.equal(ch.getResponseHeader("Content-Length"), "11");
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/plain");
+ Assert.equal(ch.getResponseHeader("Content-Range"), "bytes 10-20/21");
+}
+
+function stop_byterange(ch, status, data) {
+ Assert.equal(data.length, 11);
+ Assert.equal(data[0], 0x64);
+ Assert.equal(data[10], 0x0a);
+}
+
+function init_byterange2(ch) {
+ ch.setRequestHeader("Range", "bytes=21-", false);
+}
+
+function start_byterange2(ch) {
+ Assert.equal(ch.responseStatus, 416);
+}
+
+function init_byterange3(ch) {
+ ch.setRequestHeader("Range", "bytes=10-15", false);
+}
+
+function start_byterange3(ch) {
+ Assert.equal(ch.responseStatus, 206);
+ Assert.equal(ch.getResponseHeader("Content-Length"), "6");
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/plain");
+ Assert.equal(ch.getResponseHeader("Content-Range"), "bytes 10-15/21");
+}
+
+function stop_byterange3(ch, status, data) {
+ Assert.equal(data.length, 6);
+ Assert.equal(data[0], 0x64);
+ Assert.equal(data[1], 0x20);
+ Assert.equal(data[2], 0x62);
+ Assert.equal(data[3], 0x65);
+ Assert.equal(data[4], 0x20);
+ Assert.equal(data[5], 0x73);
+}
+
+function init_byterange4(ch) {
+ ch.setRequestHeader("Range", "xbytes=21-", false);
+}
+
+function start_byterange4(ch) {
+ Assert.equal(ch.responseStatus, 400);
+}
+
+function init_byterange5(ch) {
+ ch.setRequestHeader("Range", "bytes=-5", false);
+}
+
+function start_byterange5(ch) {
+ Assert.equal(ch.responseStatus, 206);
+}
+
+function stop_byterange5(ch, status, data) {
+ Assert.equal(data.length, 5);
+ Assert.equal(data[0], 0x65);
+ Assert.equal(data[1], 0x65);
+ Assert.equal(data[2], 0x6e);
+ Assert.equal(data[3], 0x2e);
+ Assert.equal(data[4], 0x0a);
+}
+
+function init_byterange6(ch) {
+ ch.setRequestHeader("Range", "bytes=15-12", false);
+}
+
+function start_byterange6(ch) {
+ Assert.equal(ch.responseStatus, 200);
+}
+
+function stop_byterange6(ch, status, data) {
+ Assert.equal(data.length, 21);
+ Assert.equal(data[0], 0x54);
+ Assert.equal(data[20], 0x0a);
+}
+
+function init_byterange7(ch) {
+ ch.setRequestHeader("Range", "bytes=0-5", false);
+}
+
+function start_byterange7(ch) {
+ Assert.equal(ch.responseStatus, 206);
+ Assert.equal(ch.getResponseHeader("Content-Length"), "6");
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/plain");
+ Assert.equal(ch.getResponseHeader("Content-Range"), "bytes 0-5/21");
+}
+
+function stop_byterange7(ch, status, data) {
+ Assert.equal(data.length, 6);
+ Assert.equal(data[0], 0x54);
+ Assert.equal(data[1], 0x68);
+ Assert.equal(data[2], 0x69);
+ Assert.equal(data[3], 0x73);
+ Assert.equal(data[4], 0x20);
+ Assert.equal(data[5], 0x73);
+}
+
+function init_byterange8(ch) {
+ ch.setRequestHeader("Range", "bytes=20-21", false);
+}
+
+function start_byterange8(ch) {
+ Assert.equal(ch.responseStatus, 206);
+ Assert.equal(ch.getResponseHeader("Content-Range"), "bytes 20-20/21");
+}
+
+function stop_byterange8(ch, status, data) {
+ Assert.equal(data.length, 1);
+ Assert.equal(data[0], 0x0a);
+}
+
+function init_byterange9(ch) {
+ ch.setRequestHeader("Range", "bytes=020-021", false);
+}
+
+function start_byterange9(ch) {
+ Assert.equal(ch.responseStatus, 206);
+}
+
+function stop_byterange9(ch, status, data) {
+ Assert.equal(data.length, 1);
+ Assert.equal(data[0], 0x0a);
+}
+
+function init_byterange10(ch) {
+ ch.setRequestHeader("Range", "bytes=-", false);
+}
+
+function start_byterange10(ch) {
+ Assert.equal(ch.responseStatus, 400);
+}
+
+function init_byterange11(ch) {
+ ch.setRequestHeader("Range", "bytes=-500", false);
+}
+
+function start_byterange11(ch) {
+ Assert.equal(ch.responseStatus, 206);
+}
+
+function stop_byterange11(ch, status, data) {
+ Assert.equal(data.length, 21);
+ Assert.equal(data[0], 0x54);
+ Assert.equal(data[20], 0x0a);
+}
+
+function start_byterange12(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.getResponseHeader("Content-Length"), "0");
+}
+
+function stop_byterange12(ch, status, data) {
+ Assert.equal(data.length, 0);
+}
+
+function init_byterange13(ch) {
+ ch.setRequestHeader("Range", "bytes=9999999-", false);
+}
+
+function start_byterange13(ch) {
+ Assert.equal(ch.responseStatus, 416);
+ Assert.equal(ch.getResponseHeader("X-SJS-Header"), "customized");
+}
diff --git a/netwerk/test/httpserver/test/test_cern_meta.js b/netwerk/test/httpserver/test/test_cern_meta.js
new file mode 100644
index 0000000000..3497a88ea7
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_cern_meta.js
@@ -0,0 +1,79 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// exercises support for mod_cern_meta-style header/status line modification
+var srv;
+
+ChromeUtils.defineLazyGetter(this, "PREFIX", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(PREFIX + "/test_both.html", null, start_testBoth, null),
+ new Test(
+ PREFIX + "/test_ctype_override.txt",
+ null,
+ start_test_ctype_override_txt,
+ null
+ ),
+ new Test(
+ PREFIX + "/test_status_override.html",
+ null,
+ start_test_status_override_html,
+ null
+ ),
+ new Test(
+ PREFIX + "/test_status_override_nodesc.txt",
+ null,
+ start_test_status_override_nodesc_txt,
+ null
+ ),
+ new Test(PREFIX + "/caret_test.txt^", null, start_caret_test_txt_, null),
+ ];
+});
+
+function run_test() {
+ srv = createServer();
+
+ var cernDir = do_get_file("data/cern_meta/");
+ srv.registerDirectory("/", cernDir);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// TEST DATA
+
+function start_testBoth(ch) {
+ Assert.equal(ch.responseStatus, 501);
+ Assert.equal(ch.responseStatusText, "Unimplemented");
+
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function start_test_ctype_override_txt(ch) {
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/html");
+}
+
+function start_test_status_override_html(ch) {
+ Assert.equal(ch.responseStatus, 404);
+ Assert.equal(ch.responseStatusText, "Can't Find This");
+}
+
+function start_test_status_override_nodesc_txt(ch) {
+ Assert.equal(ch.responseStatus, 732);
+ Assert.equal(ch.responseStatusText, "");
+}
+
+function start_caret_test_txt_(ch) {
+ Assert.equal(ch.responseStatus, 500);
+ Assert.equal(ch.responseStatusText, "This Isn't A Server Error");
+
+ Assert.equal(ch.getResponseHeader("Foo-RFC"), "3092");
+ Assert.equal(ch.getResponseHeader("Shaving-Cream-Atom"), "Illudium Phosdex");
+}
diff --git a/netwerk/test/httpserver/test/test_default_index_handler.js b/netwerk/test/httpserver/test/test_default_index_handler.js
new file mode 100644
index 0000000000..efa02cb6e3
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_default_index_handler.js
@@ -0,0 +1,248 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// checks for correct output with the default index handler, mostly to do
+// escaping checks -- highly dependent on the default index handler output
+// format
+
+var srv, dir, gDirEntries;
+
+ChromeUtils.defineLazyGetter(this, "BASE_URL", function () {
+ return "http://localhost:" + srv.identity.primaryPort + "/";
+});
+
+function run_test() {
+ createTestDirectory();
+
+ srv = createServer();
+ srv.registerDirectory("/", dir);
+
+ var nameDir = do_get_file("data/name-scheme/");
+ srv.registerDirectory("/bar/", nameDir);
+
+ srv.start(-1);
+
+ function done() {
+ do_test_pending();
+ destroyTestDirectory();
+ srv.stop(function () {
+ do_test_finished();
+ });
+ }
+
+ runHttpTests(tests, done);
+}
+
+function createTestDirectory() {
+ dir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ dir.append("index_handler_test_" + Math.random());
+ dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+
+ // populate with test directories, files, etc.
+ // Files must be in expected order of display on the index page!
+
+ var files = [];
+
+ makeFile("aa_directory", true, dir, files);
+ makeFile("Ba_directory", true, dir, files);
+ makeFile("bb_directory", true, dir, files);
+ makeFile("foo", true, dir, files);
+ makeFile("a_file", false, dir, files);
+ makeFile("B_file", false, dir, files);
+ makeFile("za'z", false, dir, files);
+ makeFile("zb&z", false, dir, files);
+ makeFile("zc<q", false, dir, files);
+ makeFile('zd"q', false, dir, files);
+ makeFile("ze%g", false, dir, files);
+ makeFile("zf%200h", false, dir, files);
+ makeFile("zg>m", false, dir, files);
+
+ gDirEntries = [files];
+
+ var subdir = dir.clone();
+ subdir.append("foo");
+
+ files = [];
+
+ makeFile("aa_dir", true, subdir, files);
+ makeFile("b_dir", true, subdir, files);
+ makeFile("AA_file.txt", false, subdir, files);
+ makeFile("test.txt", false, subdir, files);
+
+ gDirEntries.push(files);
+}
+
+function destroyTestDirectory() {
+ dir.remove(true);
+}
+
+/** ***********
+ * UTILITIES *
+ *************/
+
+/** Verifies data in bytes for the trailing-caret path above. */
+function hiddenDataCheck(bytes, uri, path) {
+ var data = String.fromCharCode.apply(null, bytes);
+
+ var parser = new DOMParser();
+
+ // Note: the index format isn't XML -- it's actually HTML -- but we require
+ // the index format also be valid XML, albeit XML without namespaces,
+ // XML declarations, etc. Doing this simplifies output checking.
+ try {
+ var doc = parser.parseFromString(data, "application/xml");
+ } catch (e) {
+ do_throw("document failed to parse as XML");
+ }
+
+ var body = doc.documentElement.getElementsByTagName("body");
+ Assert.equal(body.length, 1);
+ body = body[0];
+
+ // header
+ var header = body.getElementsByTagName("h1");
+ Assert.equal(header.length, 1);
+
+ Assert.equal(header[0].textContent, path);
+
+ // files
+ var lst = body.getElementsByTagName("ol");
+ Assert.equal(lst.length, 1);
+ var items = lst[0].getElementsByTagName("li");
+
+ var top = Services.io.newURI(uri);
+
+ // N.B. No ERROR_IF_SEE_THIS.txt^ file!
+ var dirEntries = [
+ { name: "file.txt", isDirectory: false },
+ { name: "SHOULD_SEE_THIS.txt^", isDirectory: false },
+ ];
+
+ for (var i = 0; i < items.length; i++) {
+ var link = items[i].childNodes[0];
+ var f = dirEntries[i];
+
+ var sep = f.isDirectory ? "/" : "";
+
+ Assert.equal(link.textContent, f.name + sep);
+
+ uri = Services.io.newURI(link.getAttribute("href"), null, top);
+ Assert.equal(decodeURIComponent(uri.pathQueryRef), path + f.name + sep);
+ }
+}
+
+/**
+ * Verifies data in bytes (an array of bytes) represents an index page for the
+ * given URI and path, which should be a page listing the given directory
+ * entries, in order.
+ *
+ * @param bytes
+ * array of bytes representing the index page's contents
+ * @param uri
+ * string which is the URI of the index page
+ * @param path
+ * the path portion of uri
+ * @param dirEntries
+ * sorted (in the manner the directory entries should be sorted) array of
+ * objects, each of which has a name property (whose value is the file's name,
+ * without / if it's a directory) and an isDirectory property (with expected
+ * value)
+ */
+function dataCheck(bytes, uri, path, dirEntries) {
+ var data = String.fromCharCode.apply(null, bytes);
+
+ var parser = new DOMParser();
+
+ // Note: the index format isn't XML -- it's actually HTML -- but we require
+ // the index format also be valid XML, albeit XML without namespaces,
+ // XML declarations, etc. Doing this simplifies output checking.
+ try {
+ var doc = parser.parseFromString(data, "application/xml");
+ } catch (e) {
+ do_throw("document failed to parse as XML");
+ }
+
+ var body = doc.documentElement.getElementsByTagName("body");
+ Assert.equal(body.length, 1);
+ body = body[0];
+
+ // header
+ var header = body.getElementsByTagName("h1");
+ Assert.equal(header.length, 1);
+
+ Assert.equal(header[0].textContent, path);
+
+ // files
+ var lst = body.getElementsByTagName("ol");
+ Assert.equal(lst.length, 1);
+ var items = lst[0].getElementsByTagName("li");
+ var top = Services.io.newURI(uri);
+
+ for (var i = 0; i < items.length; i++) {
+ var link = items[i].childNodes[0];
+ var f = dirEntries[i];
+
+ var sep = f.isDirectory ? "/" : "";
+
+ Assert.equal(link.textContent, f.name + sep);
+
+ uri = Services.io.newURI(link.getAttribute("href"), null, top);
+ Assert.equal(decodeURIComponent(uri.pathQueryRef), path + f.name + sep);
+ }
+}
+
+/**
+ * Create a file/directory with the given name underneath parentDir, and
+ * append an object with name/isDirectory properties to lst corresponding
+ * to it if the file/directory could be created.
+ */
+function makeFile(name, isDirectory, parentDir, lst) {
+ var type = Ci.nsIFile[isDirectory ? "DIRECTORY_TYPE" : "NORMAL_FILE_TYPE"];
+ var file = parentDir.clone();
+
+ try {
+ file.append(name);
+ file.create(type, 0o755);
+ lst.push({ name, isDirectory });
+ } catch (e) {
+ /* OS probably doesn't like file name, skip */
+ }
+}
+
+/** *******
+ * TESTS *
+ *********/
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(BASE_URL, null, start, stopRootDirectory),
+ new Test(BASE_URL + "foo/", null, start, stopFooDirectory),
+ new Test(
+ BASE_URL + "bar/folder^/",
+ null,
+ start,
+ stopTrailingCaretDirectory
+ ),
+ ];
+});
+
+// check top-level directory listing
+function start(ch) {
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/html;charset=utf-8");
+}
+function stopRootDirectory(ch, status, data) {
+ dataCheck(data, BASE_URL, "/", gDirEntries[0]);
+}
+
+// check non-top-level, too
+function stopFooDirectory(ch, status, data) {
+ dataCheck(data, BASE_URL + "foo/", "/foo/", gDirEntries[1]);
+}
+
+// trailing-caret leaf with hidden files
+function stopTrailingCaretDirectory(ch, status, data) {
+ hiddenDataCheck(data, BASE_URL + "bar/folder^/", "/bar/folder^/");
+}
diff --git a/netwerk/test/httpserver/test/test_empty_body.js b/netwerk/test/httpserver/test/test_empty_body.js
new file mode 100644
index 0000000000..fa9b7cbfdc
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_empty_body.js
@@ -0,0 +1,59 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// in its original incarnation, the server didn't like empty response-bodies;
+// see the comment in _end for details
+
+var srv;
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/empty-body-unwritten",
+ null,
+ ensureEmpty,
+ null
+ ),
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/empty-body-written",
+ null,
+ ensureEmpty,
+ null
+ ),
+ ];
+});
+
+function run_test() {
+ srv = createServer();
+
+ // register a few test paths
+ srv.registerPathHandler("/empty-body-unwritten", emptyBodyUnwritten);
+ srv.registerPathHandler("/empty-body-written", emptyBodyWritten);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// TEST DATA
+
+function ensureEmpty(ch) {
+ Assert.ok(ch.contentLength == 0);
+}
+
+// PATH HANDLERS
+
+// /empty-body-unwritten
+function emptyBodyUnwritten(metadata, response) {
+ response.setStatusLine("1.1", 200, "OK");
+}
+
+// /empty-body-written
+function emptyBodyWritten(metadata, response) {
+ response.setStatusLine("1.1", 200, "OK");
+ var body = "";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/httpserver/test/test_errorhandler_exception.js b/netwerk/test/httpserver/test/test_errorhandler_exception.js
new file mode 100644
index 0000000000..e9cdb0bf4f
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_errorhandler_exception.js
@@ -0,0 +1,95 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Request handlers may throw exceptions, and those exception should be caught
+// by the server and converted into the proper error codes.
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/throws/exception",
+ null,
+ start_throws_exception,
+ succeeded
+ ),
+ new Test(
+ "http://localhost:" +
+ srv.identity.primaryPort +
+ "/this/file/does/not/exist/and/404s",
+ null,
+ start_nonexistent_404_fails_so_400,
+ succeeded
+ ),
+ new Test(
+ "http://localhost:" +
+ srv.identity.primaryPort +
+ "/attempts/404/fails/so/400/fails/so/500s",
+ register400Handler,
+ start_multiple_exceptions_500,
+ succeeded
+ ),
+ ];
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+
+ srv.registerErrorHandler(404, throwsException);
+ srv.registerPathHandler("/throws/exception", throwsException);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// TEST DATA
+
+function checkStatusLine(
+ channel,
+ httpMaxVer,
+ httpMinVer,
+ httpCode,
+ statusText
+) {
+ Assert.equal(channel.responseStatus, httpCode);
+ Assert.equal(channel.responseStatusText, statusText);
+
+ var respMaj = {},
+ respMin = {};
+ channel.getResponseVersion(respMaj, respMin);
+ Assert.equal(respMaj.value, httpMaxVer);
+ Assert.equal(respMin.value, httpMinVer);
+}
+
+function start_throws_exception(ch) {
+ checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
+}
+
+function start_nonexistent_404_fails_so_400(ch) {
+ checkStatusLine(ch, 1, 1, 400, "Bad Request");
+}
+
+function start_multiple_exceptions_500(ch) {
+ checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
+}
+
+function succeeded(ch, status, data) {
+ Assert.ok(Components.isSuccessCode(status));
+}
+
+function register400Handler(ch) {
+ srv.registerErrorHandler(400, throwsException);
+}
+
+// PATH HANDLERS
+
+// /throws/exception (and also a 404 and 400 error handler)
+function throwsException(metadata, response) {
+ throw new Error("this shouldn't cause an exit...");
+ do_throw("Not reached!"); // eslint-disable-line no-unreachable
+}
diff --git a/netwerk/test/httpserver/test/test_header_array.js b/netwerk/test/httpserver/test/test_header_array.js
new file mode 100644
index 0000000000..1579c5ceeb
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_header_array.js
@@ -0,0 +1,66 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// test that special headers are sent as an array of headers with the same name
+
+var srv;
+
+function run_test() {
+ srv;
+
+ srv = createServer();
+ srv.registerPathHandler("/path-handler", pathHandler);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+/** **********
+ * HANDLERS *
+ ************/
+
+function pathHandler(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ response.setHeader("Proxy-Authenticate", "First line 1", true);
+ response.setHeader("Proxy-Authenticate", "Second line 1", true);
+ response.setHeader("Proxy-Authenticate", "Third line 1", true);
+
+ response.setHeader("WWW-Authenticate", "Not merged line 1", false);
+ response.setHeader("WWW-Authenticate", "Not merged line 2", true);
+
+ response.setHeader("WWW-Authenticate", "First line 2", false);
+ response.setHeader("WWW-Authenticate", "Second line 2", true);
+ response.setHeader("WWW-Authenticate", "Third line 2", true);
+
+ response.setHeader("X-Single-Header-Merge", "Single 1", true);
+ response.setHeader("X-Single-Header-Merge", "Single 2", true);
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/path-handler",
+ null,
+ check
+ ),
+ ];
+});
+
+function check(ch) {
+ var headerValue;
+
+ headerValue = ch.getResponseHeader("Proxy-Authenticate");
+ Assert.equal(headerValue, "First line 1\nSecond line 1\nThird line 1");
+ headerValue = ch.getResponseHeader("WWW-Authenticate");
+ Assert.equal(headerValue, "First line 2\nSecond line 2\nThird line 2");
+ headerValue = ch.getResponseHeader("X-Single-Header-Merge");
+ Assert.equal(headerValue, "Single 1,Single 2");
+}
diff --git a/netwerk/test/httpserver/test/test_headers.js b/netwerk/test/httpserver/test/test_headers.js
new file mode 100644
index 0000000000..8e920c6f2f
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_headers.js
@@ -0,0 +1,169 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// tests for header storage in httpd.js; nsHttpHeaders is an *internal* data
+// structure and is not to be used directly outside of httpd.js itself except
+// for testing purposes
+
+/**
+ * Ensures that a fieldname-fieldvalue combination is a valid header.
+ *
+ * @param fieldName
+ * the name of the header
+ * @param fieldValue
+ * the value of the header
+ * @param headers
+ * an nsHttpHeaders object to use to check validity
+ */
+function assertValidHeader(fieldName, fieldValue, headers) {
+ try {
+ headers.setHeader(fieldName, fieldValue, false);
+ } catch (e) {
+ do_throw("Unexpected exception thrown: " + e);
+ }
+}
+
+/**
+ * Ensures that a fieldname-fieldvalue combination is not a valid header.
+ *
+ * @param fieldName
+ * the name of the header
+ * @param fieldValue
+ * the value of the header
+ * @param headers
+ * an nsHttpHeaders object to use to check validity
+ */
+function assertInvalidHeader(fieldName, fieldValue, headers) {
+ try {
+ headers.setHeader(fieldName, fieldValue, false);
+ throw new Error(
+ `Setting (${fieldName}, ${fieldValue}) as header succeeded!`
+ );
+ } catch (e) {
+ if (e.result !== Cr.NS_ERROR_INVALID_ARG) {
+ do_throw("Unexpected exception thrown: " + e);
+ }
+ }
+}
+
+function run_test() {
+ testHeaderValidity();
+ testGetHeader();
+ testHeaderEnumerator();
+ testHasHeader();
+}
+
+function testHeaderValidity() {
+ var headers = new nsHttpHeaders();
+
+ assertInvalidHeader("f o", "bar", headers);
+ assertInvalidHeader("f\0n", "bar", headers);
+ assertInvalidHeader("foo:", "bar", headers);
+ assertInvalidHeader("f\\o", "bar", headers);
+ assertInvalidHeader("@xml", "bar", headers);
+ assertInvalidHeader("fiz(", "bar", headers);
+ assertInvalidHeader("HTTP/1.1", "bar", headers);
+ assertInvalidHeader('b"b', "bar", headers);
+ assertInvalidHeader("ascsd\t", "bar", headers);
+ assertInvalidHeader("{fds", "bar", headers);
+ assertInvalidHeader("baz?", "bar", headers);
+ assertInvalidHeader("a\\b\\c", "bar", headers);
+ assertInvalidHeader("\0x7F", "bar", headers);
+ assertInvalidHeader("\0x1F", "bar", headers);
+ assertInvalidHeader("f\n", "bar", headers);
+ assertInvalidHeader("foo", "b\nar", headers);
+ assertInvalidHeader("foo", "b\rar", headers);
+ assertInvalidHeader("foo", "b\0", headers);
+
+ // request splitting, fwiw -- we're actually immune to this type of attack so
+ // long as we don't implement persistent connections
+ assertInvalidHeader("f\r\nGET /badness HTTP/1.1\r\nFoo", "bar", headers);
+
+ assertValidHeader("f'", "baz", headers);
+ assertValidHeader("f`", "baz", headers);
+ assertValidHeader("f.", "baz", headers);
+ assertValidHeader("f---", "baz", headers);
+ assertValidHeader("---", "baz", headers);
+ assertValidHeader("~~~", "baz", headers);
+ assertValidHeader("~~~", "b\r\n bar", headers);
+ assertValidHeader("~~~", "b\r\n\tbar", headers);
+}
+
+function testGetHeader() {
+ var headers = new nsHttpHeaders();
+
+ headers.setHeader("Content-Type", "text/html", false);
+ var c = headers.getHeader("content-type");
+ Assert.equal(c, "text/html");
+
+ headers.setHeader("test", "FOO", false);
+ c = headers.getHeader("test");
+ Assert.equal(c, "FOO");
+
+ try {
+ headers.getHeader(":");
+ throw new Error("Failed to throw for invalid header");
+ } catch (e) {
+ if (e.result !== Cr.NS_ERROR_INVALID_ARG) {
+ do_throw("headers.getHeader(':') must throw invalid arg");
+ }
+ }
+
+ try {
+ headers.getHeader("valid");
+ throw new Error("header doesn't exist");
+ } catch (e) {
+ if (e.result !== Cr.NS_ERROR_NOT_AVAILABLE) {
+ do_throw("shouldn't be a header named 'valid' in headers!");
+ }
+ }
+}
+
+function testHeaderEnumerator() {
+ var headers = new nsHttpHeaders();
+
+ var heads = {
+ foo: "17",
+ baz: "two six niner",
+ decaf: "class Program { int .7; int main(){ .7 = 5; return 7 - .7; } }",
+ };
+
+ for (var i in heads) {
+ headers.setHeader(i, heads[i], false);
+ }
+
+ var en = headers.enumerator;
+ while (en.hasMoreElements()) {
+ var it = en.getNext().QueryInterface(Ci.nsISupportsString).data;
+ Assert.ok(it.toLowerCase() in heads);
+ delete heads[it.toLowerCase()];
+ }
+
+ if (Object.keys(heads).length) {
+ do_throw("still have properties in heads!?!?");
+ }
+}
+
+function testHasHeader() {
+ var headers = new nsHttpHeaders();
+
+ headers.setHeader("foo", "bar", false);
+ Assert.ok(headers.hasHeader("foo"));
+ Assert.ok(headers.hasHeader("fOo"));
+ Assert.ok(!headers.hasHeader("not-there"));
+
+ headers.setHeader("f`'~", "bar", false);
+ Assert.ok(headers.hasHeader("F`'~"));
+
+ try {
+ headers.hasHeader(":");
+ throw new Error("failed to throw");
+ } catch (e) {
+ if (e.result !== Cr.NS_ERROR_INVALID_ARG) {
+ do_throw(".hasHeader for an invalid name should throw");
+ }
+ }
+}
diff --git a/netwerk/test/httpserver/test/test_host.js b/netwerk/test/httpserver/test/test_host.js
new file mode 100644
index 0000000000..2f5fadde92
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_host.js
@@ -0,0 +1,608 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that the scheme, host, and port of the server are correctly recorded
+ * and used in HTTP requests and responses.
+ */
+
+"use strict";
+
+const PORT = 4444;
+const FAKE_PORT_ONE = 8888;
+const FAKE_PORT_TWO = 8889;
+
+let srv, id;
+
+add_task(async function run_test1() {
+ dump("*** run_test1");
+
+ srv = createServer();
+
+ srv.registerPathHandler("/http/1.0-request", http10Request);
+ srv.registerPathHandler("/http/1.1-good-host", http11goodHost);
+ srv.registerPathHandler(
+ "/http/1.1-good-host-wacky-port",
+ http11goodHostWackyPort
+ );
+ srv.registerPathHandler("/http/1.1-ip-host", http11ipHost);
+
+ srv.start(FAKE_PORT_ONE);
+
+ id = srv.identity;
+
+ // The default location is http://localhost:PORT, where PORT is whatever you
+ // provided when you started the server. http://127.0.0.1:PORT is also part
+ // of the default set of locations.
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "localhost");
+ Assert.equal(id.primaryPort, FAKE_PORT_ONE);
+ Assert.ok(id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // This should be a nop.
+ id.add("http", "localhost", FAKE_PORT_ONE);
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "localhost");
+ Assert.equal(id.primaryPort, FAKE_PORT_ONE);
+ Assert.ok(id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // Change the primary location and make sure all the getters work correctly.
+ id.setPrimary("http", "127.0.0.1", FAKE_PORT_ONE);
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "127.0.0.1");
+ Assert.equal(id.primaryPort, FAKE_PORT_ONE);
+ Assert.ok(id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // Okay, now remove the primary location -- we fall back to the original
+ // location.
+ id.remove("http", "127.0.0.1", FAKE_PORT_ONE);
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "localhost");
+ Assert.equal(id.primaryPort, FAKE_PORT_ONE);
+ Assert.ok(id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // You can't remove every location -- try this and the original default
+ // location will be silently readded.
+ id.remove("http", "localhost", FAKE_PORT_ONE);
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "localhost");
+ Assert.equal(id.primaryPort, FAKE_PORT_ONE);
+ Assert.ok(id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // Okay, now that we've exercised that behavior, shut down the server and
+ // restart it on the correct port, to exercise port-changing behaviors at
+ // server start and stop.
+
+ await new Promise(resolve => srv.stop(resolve));
+});
+
+add_task(async function run_test_2() {
+ dump("*** run_test_2");
+
+ // Our primary location is gone because it was dependent on the port on which
+ // the server was running.
+ checkPrimariesThrow(id);
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+
+ srv.start(FAKE_PORT_TWO);
+
+ // We should have picked up http://localhost:8889 as our primary location now
+ // that we've restarted.
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "localhost");
+ Assert.equal(id.primaryPort, FAKE_PORT_TWO);
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ Assert.ok(id.has("http", "localhost", FAKE_PORT_TWO));
+ Assert.ok(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+
+ // Now we're going to see what happens when we shut down with a primary
+ // location that wasn't a default. That location should persist, and the
+ // default we remove should still not be present.
+ id.setPrimary("http", "example.com", FAKE_PORT_TWO);
+ Assert.ok(id.has("http", "example.com", FAKE_PORT_TWO));
+ Assert.ok(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ Assert.ok(id.has("http", "localhost", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+
+ id.remove("http", "localhost", FAKE_PORT_TWO);
+ Assert.ok(id.has("http", "example.com", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_TWO));
+ Assert.ok(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ id.remove("http", "127.0.0.1", FAKE_PORT_TWO);
+ Assert.ok(id.has("http", "example.com", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ await new Promise(resolve => srv.stop(resolve));
+});
+
+add_task(async function run_test_3() {
+ dump("*** run_test_3");
+
+ // Only the default added location disappears; any others stay around,
+ // possibly as the primary location. We may have removed the default primary
+ // location, but the one we set manually should persist here.
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "example.com");
+ Assert.equal(id.primaryPort, FAKE_PORT_TWO);
+ Assert.ok(id.has("http", "example.com", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ srv.start(PORT);
+
+ // Starting always adds HTTP entries for 127.0.0.1:port and localhost:port.
+ Assert.ok(id.has("http", "example.com", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ Assert.ok(id.has("http", "localhost", PORT));
+ Assert.ok(id.has("http", "127.0.0.1", PORT));
+
+ // Remove the primary location we'd left set from last time.
+ id.remove("http", "example.com", FAKE_PORT_TWO);
+
+ // Default-port behavior testing requires the server responds to requests
+ // claiming to be on one such port.
+ id.add("http", "localhost", 80);
+
+ // Make sure we don't have anything lying around from running on either the
+ // first or the second port -- all we should have is our generated default,
+ // plus the additional port to test "portless" hostport variants.
+ Assert.ok(id.has("http", "localhost", 80));
+ Assert.equal(id.primaryScheme, "http");
+ Assert.equal(id.primaryHost, "localhost");
+ Assert.equal(id.primaryPort, PORT);
+ Assert.ok(id.has("http", "localhost", PORT));
+ Assert.ok(id.has("http", "127.0.0.1", PORT));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ Assert.ok(!id.has("http", "example.com", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "localhost", FAKE_PORT_TWO));
+ Assert.ok(!id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+
+ // Okay, finally done with identity testing. Our primary location is the one
+ // we want it to be, so we're off!
+ await new Promise(resolve =>
+ runRawTests(tests, resolve, idx => dump(`running test no ${idx}`))
+ );
+
+ // Finally shut down the server.
+ await new Promise(resolve => srv.stop(resolve));
+});
+
+/** *******************
+ * UTILITY FUNCTIONS *
+ *********************/
+
+/**
+ * Verifies that all .primary* getters on a server identity correctly throw
+ * NS_ERROR_NOT_INITIALIZED.
+ *
+ * @param aId : nsIHttpServerIdentity
+ * the server identity to test
+ */
+function checkPrimariesThrow(aId) {
+ let threw = false;
+ try {
+ aId.primaryScheme;
+ } catch (e) {
+ threw = e.result === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ Assert.ok(threw);
+
+ threw = false;
+ try {
+ aId.primaryHost;
+ } catch (e) {
+ threw = e.result === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ Assert.ok(threw);
+
+ threw = false;
+ try {
+ aId.primaryPort;
+ } catch (e) {
+ threw = e.result === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ Assert.ok(threw);
+}
+
+/**
+ * Utility function to check for a 400 response.
+ */
+function check400(aData) {
+ let iter = LineIterator(aData);
+
+ // Status-Line
+ let { value: firstLine } = iter.next();
+ Assert.equal(firstLine.substring(0, HTTP_400_LEADER_LENGTH), HTTP_400_LEADER);
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+const HTTP_400_LEADER = "HTTP/1.1 400 ";
+const HTTP_400_LEADER_LENGTH = HTTP_400_LEADER.length;
+
+var test, data;
+var tests = [];
+
+// HTTP/1.0 request, to ensure we see our default scheme/host/port
+
+function http10Request(request, response) {
+ writeDetails(request, response);
+ response.setStatusLine("1.0", 200, "TEST PASSED");
+}
+data = "GET /http/1.0-request HTTP/1.0\r\n\r\n";
+function check10(aData) {
+ let iter = LineIterator(aData);
+
+ // Status-Line
+ Assert.equal(iter.next().value, "HTTP/1.0 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ let body = [
+ "Method: GET",
+ "Path: /http/1.0-request",
+ "Query: ",
+ "Version: 1.0",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check10);
+tests.push(test);
+
+// HTTP/1.1 request, no Host header, expect a 400 response
+
+// eslint-disable-next-line no-useless-concat
+data = "GET /http/1.1-request HTTP/1.1\r\n" + "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, wrong host, expect a 400 response
+
+data =
+ // eslint-disable-next-line no-useless-concat
+ "GET /http/1.1-request HTTP/1.1\r\n" + "Host: not-localhost\r\n" + "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, wrong host/right port, expect a 400 response
+
+data =
+ "GET /http/1.1-request HTTP/1.1\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, Host header has host but no port, expect a 400 response
+
+// eslint-disable-next-line no-useless-concat
+data = "GET /http/1.1-request HTTP/1.1\r\n" + "Host: 127.0.0.1\r\n" + "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, Request-URI has wrong port, expect a 400 response
+
+data =
+ "GET http://127.0.0.1/http/1.1-request HTTP/1.1\r\n" +
+ "Host: 127.0.0.1\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, Request-URI has wrong port, expect a 400 response
+
+data =
+ "GET http://localhost:31337/http/1.1-request HTTP/1.1\r\n" +
+ "Host: localhost:31337\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, Request-URI has wrong scheme, expect a 400 response
+
+data =
+ "GET https://localhost:4444/http/1.1-request HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, correct Host header, expect handler's response
+
+function http11goodHost(request, response) {
+ writeDetails(request, response);
+ response.setStatusLine("1.1", 200, "TEST PASSED");
+}
+data =
+ // eslint-disable-next-line no-useless-concat
+ "GET /http/1.1-good-host HTTP/1.1\r\n" + "Host: localhost:4444\r\n" + "\r\n";
+function check11goodHost(aData) {
+ let iter = LineIterator(aData);
+
+ // Status-Line
+ Assert.equal(iter.next().value, "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ let body = [
+ "Method: GET",
+ "Path: /http/1.1-good-host",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check11goodHost);
+tests.push(test);
+
+// HTTP/1.1 request, Host header is secondary identity
+
+function http11ipHost(request, response) {
+ writeDetails(request, response);
+ response.setStatusLine("1.1", 200, "TEST PASSED");
+}
+data =
+ // eslint-disable-next-line no-useless-concat
+ "GET /http/1.1-ip-host HTTP/1.1\r\n" + "Host: 127.0.0.1:4444\r\n" + "\r\n";
+function check11ipHost(aData) {
+ let iter = LineIterator(aData);
+
+ // Status-Line
+ Assert.equal(iter.next().value, "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ let body = [
+ "Method: GET",
+ "Path: /http/1.1-ip-host",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: 127.0.0.1",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check11ipHost);
+tests.push(test);
+
+// HTTP/1.1 request, absolute path, accurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data =
+ "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost);
+tests.push(test);
+
+// HTTP/1.1 request, absolute path, inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data =
+ "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: localhost:1234\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost);
+tests.push(test);
+
+// HTTP/1.1 request, absolute path, different inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data =
+ "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost);
+tests.push(test);
+
+// HTTP/1.1 request, absolute path, yet another inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data =
+ "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: yippity-skippity\r\n" +
+ "\r\n";
+function checkInaccurate(aData) {
+ check11goodHost(aData);
+
+ // dynamism setup
+ srv.identity.setPrimary("http", "127.0.0.1", 4444);
+}
+test = new RawTest("localhost", PORT, data, checkInaccurate);
+tests.push(test);
+
+// HTTP/1.0 request, absolute path, different inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data =
+ "GET /http/1.0-request HTTP/1.0\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+function check10ip(aData) {
+ let iter = LineIterator(aData);
+
+ // Status-Line
+ Assert.equal(iter.next().value, "HTTP/1.0 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ let body = [
+ "Method: GET",
+ "Path: /http/1.0-request",
+ "Query: ",
+ "Version: 1.0",
+ "Scheme: http",
+ "Host: 127.0.0.1",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check10ip);
+tests.push(test);
+
+// HTTP/1.1 request, Host header with implied port
+
+function http11goodHostWackyPort(request, response) {
+ writeDetails(request, response);
+ response.setStatusLine("1.1", 200, "TEST PASSED");
+}
+data =
+ "GET /http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+function check11goodHostWackyPort(aData) {
+ let iter = LineIterator(aData);
+
+ // Status-Line
+ Assert.equal(iter.next().value, "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ let body = [
+ "Method: GET",
+ "Path: /http/1.1-good-host-wacky-port",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: 80",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort);
+tests.push(test);
+
+// HTTP/1.1 request, Host header with wacky implied port
+
+data =
+ "GET /http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost:\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort);
+tests.push(test);
+
+// HTTP/1.1 request, absolute URI with implied port
+
+data =
+ "GET http://localhost/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort);
+tests.push(test);
+
+// HTTP/1.1 request, absolute URI with wacky implied port
+
+data =
+ "GET http://localhost:/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort);
+tests.push(test);
+
+// HTTP/1.1 request, absolute URI with explicit implied port, ignored Host
+
+data =
+ "GET http://localhost:80/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: who-cares\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort);
+tests.push(test);
+
+// HTTP/1.1 request, a malformed Request-URI
+
+data =
+ "GET is-this-the-real-life-is-this-just-fantasy HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, a malformed Host header
+
+// eslint-disable-next-line no-useless-concat
+data = "GET /http/1.1-request HTTP/1.1\r\n" + "Host: la la la\r\n" + "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, a malformed Host header but absolute URI, 5.2 sez fine
+
+data =
+ "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: la la la\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost);
+tests.push(test);
+
+// HTTP/1.0 request, absolute URI, but those aren't valid in HTTP/1.0
+
+data =
+ "GET http://localhost:4444/http/1.1-request HTTP/1.0\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, absolute URI with unrecognized host
+
+data =
+ "GET http://not-localhost:4444/http/1.1-request HTTP/1.1\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
+
+// HTTP/1.1 request, absolute URI with unrecognized host (but not in Host)
+
+data =
+ "GET http://not-localhost:4444/http/1.1-request HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400);
+tests.push(test);
diff --git a/netwerk/test/httpserver/test/test_host_identity.js b/netwerk/test/httpserver/test/test_host_identity.js
new file mode 100644
index 0000000000..1a1662d8cf
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_host_identity.js
@@ -0,0 +1,115 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that the server accepts requests to custom host names.
+ * This is commonly used in tests that map custom host names to the server via
+ * a proxy e.g. by XPCShellContentUtils.createHttpServer.
+ */
+
+var srv = createServer();
+srv.start(-1);
+registerCleanupFunction(() => new Promise(resolve => srv.stop(resolve)));
+const PORT = srv.identity.primaryPort;
+srv.registerPathHandler("/dump-request", dumpRequestLines);
+
+function dumpRequestLines(request, response) {
+ writeDetails(request, response);
+ response.setStatusLine(request.httpVersion, 200, "TEST PASSED");
+}
+
+function makeRawRequest(requestLinePath, hostHeader) {
+ return `GET ${requestLinePath} HTTP/1.1\r\nHost: ${hostHeader}\r\n\r\n`;
+}
+
+function verifyResponseHostPort(data, query, expectedHost, expectedPort) {
+ var iter = LineIterator(data);
+
+ // Status-Line
+ Assert.equal(iter.next().value, "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body = [
+ "Method: GET",
+ "Path: /dump-request",
+ "Query: " + query,
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: " + expectedHost,
+ "Port: " + expectedPort,
+ ];
+
+ expectLines(iter, body);
+}
+
+function runIdentityTest(host, port) {
+ srv.identity.add("http", host, port);
+
+ function checkAbsoluteRequestURI(data) {
+ verifyResponseHostPort(data, "absolute", host, port);
+ }
+ function checkHostHeader(data) {
+ verifyResponseHostPort(data, "relative", host, port);
+ }
+
+ let tests = [];
+ let test, data;
+ let hostport = `${host}:${port}`;
+ data = makeRawRequest(`http://${hostport}/dump-request?absolute`, hostport);
+ test = new RawTest("localhost", PORT, data, checkAbsoluteRequestURI);
+ tests.push(test);
+
+ data = makeRawRequest("/dump-request?relative", hostport);
+ test = new RawTest("localhost", PORT, data, checkHostHeader);
+ tests.push(test);
+ return new Promise(resolve => {
+ runRawTests(tests, resolve);
+ });
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+add_task(async function test_basic_example_com() {
+ await runIdentityTest("example.com", 1234);
+ await runIdentityTest("example.com", 5432);
+});
+
+add_task(async function test_fully_qualified_domain_name_aka_fqdn() {
+ await runIdentityTest("fully-qualified-domain-name.", 1234);
+});
+
+add_task(async function test_ipv4() {
+ await runIdentityTest("1.2.3.4", 1234);
+});
+
+add_task(async function test_ipv6() {
+ Assert.throws(
+ () => srv.identity.add("http", "[notipv6]", 1234),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "should reject invalid host, clearly not bracketed IPv6"
+ );
+ Assert.throws(
+ () => srv.identity.add("http", "[::127.0.0.1]", 1234),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "should reject non-canonical IPv6"
+ );
+ await runIdentityTest("[::123]", 1234);
+ await runIdentityTest("[1:2:3:a:b:c:d:abcd]", 1234);
+});
+
+add_task(async function test_internationalized_domain_name() {
+ Assert.throws(
+ () => srv.identity.add("http", "δοκιμή", 1234),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "should reject IDN not in punycode"
+ );
+
+ await runIdentityTest("xn--jxalpdlp", 1234);
+});
diff --git a/netwerk/test/httpserver/test/test_linedata.js b/netwerk/test/httpserver/test/test_linedata.js
new file mode 100644
index 0000000000..83fed3e8c0
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_linedata.js
@@ -0,0 +1,22 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+// test that the LineData internal data structure works correctly
+
+const CR = 0x0d;
+const LF = 0x0a;
+
+function run_test() {
+ var data = new LineData();
+ data.appendBytes(["a".charCodeAt(0), CR]);
+
+ var out = { value: "" };
+ Assert.ok(!data.readLine(out));
+
+ data.appendBytes([LF]);
+ Assert.ok(data.readLine(out));
+ Assert.equal(out.value, "a");
+}
diff --git a/netwerk/test/httpserver/test/test_load_module.js b/netwerk/test/httpserver/test/test_load_module.js
new file mode 100644
index 0000000000..38c2ca4170
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_load_module.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Ensure httpd.js can be imported as a module and that a server starts.
+ */
+function run_test() {
+ const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+ );
+
+ let server = new HttpServer();
+ server.start(-1);
+
+ do_test_pending();
+
+ server.stop(do_test_finished);
+}
diff --git a/netwerk/test/httpserver/test/test_name_scheme.js b/netwerk/test/httpserver/test/test_name_scheme.js
new file mode 100644
index 0000000000..4f9fefe159
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_name_scheme.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// requests for files ending with a caret (^) are handled specially to enable
+// htaccess-like functionality without the need to explicitly disable display
+// of such files
+
+var srv;
+
+ChromeUtils.defineLazyGetter(this, "PREFIX", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(PREFIX + "/bar.html^", null, start_bar_html_, null),
+ new Test(PREFIX + "/foo.html^", null, start_foo_html_, null),
+ new Test(PREFIX + "/normal-file.txt", null, start_normal_file_txt, null),
+ new Test(PREFIX + "/folder^/file.txt", null, start_folder__file_txt, null),
+
+ new Test(PREFIX + "/foo/bar.html^", null, start_bar_html_, null),
+ new Test(PREFIX + "/foo/foo.html^", null, start_foo_html_, null),
+ new Test(
+ PREFIX + "/foo/normal-file.txt",
+ null,
+ start_normal_file_txt,
+ null
+ ),
+ new Test(
+ PREFIX + "/foo/folder^/file.txt",
+ null,
+ start_folder__file_txt,
+ null
+ ),
+
+ new Test(PREFIX + "/end-caret^/bar.html^", null, start_bar_html_, null),
+ new Test(PREFIX + "/end-caret^/foo.html^", null, start_foo_html_, null),
+ new Test(
+ PREFIX + "/end-caret^/normal-file.txt",
+ null,
+ start_normal_file_txt,
+ null
+ ),
+ new Test(
+ PREFIX + "/end-caret^/folder^/file.txt",
+ null,
+ start_folder__file_txt,
+ null
+ ),
+ ];
+});
+
+function run_test() {
+ srv = createServer();
+
+ // make sure underscores work in directories "mounted" in directories with
+ // folders starting with _
+ var nameDir = do_get_file("data/name-scheme/");
+ srv.registerDirectory("/", nameDir);
+ srv.registerDirectory("/foo/", nameDir);
+ srv.registerDirectory("/end-caret^/", nameDir);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// TEST DATA
+
+function start_bar_html_(ch) {
+ Assert.equal(ch.responseStatus, 200);
+
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/html");
+}
+
+function start_foo_html_(ch) {
+ Assert.equal(ch.responseStatus, 404);
+}
+
+function start_normal_file_txt(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function start_folder__file_txt(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.getResponseHeader("Content-Type"), "text/plain");
+}
diff --git a/netwerk/test/httpserver/test/test_processasync.js b/netwerk/test/httpserver/test/test_processasync.js
new file mode 100644
index 0000000000..321c9b086f
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_processasync.js
@@ -0,0 +1,272 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests for correct behavior of asynchronous responses.
+ */
+
+ChromeUtils.defineLazyGetter(this, "PREPATH", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+ for (var path in handlers) {
+ srv.registerPathHandler(path, handlers[path]);
+ }
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(PREPATH + "/handleSync", null, start_handleSync, null),
+ new Test(
+ PREPATH + "/handleAsync1",
+ null,
+ start_handleAsync1,
+ stop_handleAsync1
+ ),
+ new Test(
+ PREPATH + "/handleAsync2",
+ init_handleAsync2,
+ start_handleAsync2,
+ stop_handleAsync2
+ ),
+ new Test(
+ PREPATH + "/handleAsyncOrdering",
+ null,
+ null,
+ stop_handleAsyncOrdering
+ ),
+ ];
+});
+
+var handlers = {};
+
+function handleSync(request, response) {
+ response.setStatusLine(request.httpVersion, 500, "handleSync fail");
+
+ try {
+ response.finish();
+ do_throw("finish called on sync response");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_UNEXPECTED);
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "handleSync pass");
+}
+handlers["/handleSync"] = handleSync;
+
+function start_handleSync(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.responseStatusText, "handleSync pass");
+}
+
+function handleAsync1(request, response) {
+ response.setStatusLine(request.httpVersion, 500, "Old status line!");
+ response.setHeader("X-Foo", "old value", false);
+
+ response.processAsync();
+
+ response.setStatusLine(request.httpVersion, 200, "New status line!");
+ response.setHeader("X-Foo", "new value", false);
+
+ response.finish();
+
+ try {
+ response.setStatusLine(request.httpVersion, 500, "Too late!");
+ do_throw("late setStatusLine didn't throw");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try {
+ response.setHeader("X-Foo", "late value", false);
+ do_throw("late setHeader didn't throw");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try {
+ response.bodyOutputStream;
+ do_throw("late bodyOutputStream get didn't throw");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try {
+ response.write("fugly");
+ do_throw("late write() didn't throw");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+}
+handlers["/handleAsync1"] = handleAsync1;
+
+function start_handleAsync1(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.responseStatusText, "New status line!");
+ Assert.equal(ch.getResponseHeader("X-Foo"), "new value");
+}
+
+function stop_handleAsync1(ch, status, data) {
+ Assert.equal(data.length, 0);
+}
+
+const startToHeaderDelay = 500;
+const startToFinishedDelay = 750;
+
+function handleAsync2(request, response) {
+ response.processAsync();
+
+ response.setStatusLine(request.httpVersion, 200, "Status line");
+ response.setHeader("X-Custom-Header", "value", false);
+
+ callLater(startToHeaderDelay, function () {
+ var preBody = "BO";
+ response.bodyOutputStream.write(preBody, preBody.length);
+
+ try {
+ response.setStatusLine(request.httpVersion, 500, "after body write");
+ do_throw("setStatusLine succeeded");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try {
+ response.setHeader("X-Custom-Header", "new 1", false);
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ callLater(startToFinishedDelay - startToHeaderDelay, function () {
+ var postBody = "DY";
+ response.bodyOutputStream.write(postBody, postBody.length);
+
+ response.finish();
+ response.finish(); // idempotency
+
+ try {
+ response.setStatusLine(request.httpVersion, 500, "after finish");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try {
+ response.setHeader("X-Custom-Header", "new 2", false);
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try {
+ response.write("EVIL");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ });
+ });
+}
+handlers["/handleAsync2"] = handleAsync2;
+
+var startTime_handleAsync2;
+
+function init_handleAsync2(ch) {
+ var now = (startTime_handleAsync2 = Date.now());
+ dumpn("*** init_HandleAsync2: start time " + now);
+}
+
+function start_handleAsync2(ch) {
+ var now = Date.now();
+ dumpn(
+ "*** start_handleAsync2: onStartRequest time " +
+ now +
+ ", " +
+ (now - startTime_handleAsync2) +
+ "ms after start time"
+ );
+ Assert.ok(now >= startTime_handleAsync2 + startToHeaderDelay);
+
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.responseStatusText, "Status line");
+ Assert.equal(ch.getResponseHeader("X-Custom-Header"), "value");
+}
+
+function stop_handleAsync2(ch, status, data) {
+ var now = Date.now();
+ dumpn(
+ "*** stop_handleAsync2: onStopRequest time " +
+ now +
+ ", " +
+ (now - startTime_handleAsync2) +
+ "ms after header time"
+ );
+ Assert.ok(now >= startTime_handleAsync2 + startToFinishedDelay);
+
+ Assert.equal(String.fromCharCode.apply(null, data), "BODY");
+}
+
+/*
+ * Tests that accessing output stream *before* calling processAsync() works
+ * correctly, sending written data immediately as it is written, not buffering
+ * until finish() is called -- which for this much data would mean we would all
+ * but certainly deadlock, since we're trying to read/write all this data in one
+ * process on a single thread.
+ */
+function handleAsyncOrdering(request, response) {
+ var out = new BinaryOutputStream(response.bodyOutputStream);
+
+ var data = [];
+ for (var i = 0; i < 65536; i++) {
+ data[i] = 0;
+ }
+ var count = 20;
+
+ var writeData = {
+ run() {
+ if (count-- === 0) {
+ response.finish();
+ return;
+ }
+
+ try {
+ out.writeByteArray(data);
+ step();
+ } catch (e) {
+ try {
+ do_throw("error writing data: " + e);
+ } finally {
+ response.finish();
+ }
+ }
+ },
+ };
+ function step() {
+ // Use gThreadManager here because it's expedient, *not* because it's
+ // intended for public use! If you do this in client code, expect me to
+ // knowingly break your code by changing the variable name. :-P
+ Services.tm.dispatchToMainThread(writeData);
+ }
+ step();
+ response.processAsync();
+}
+handlers["/handleAsyncOrdering"] = handleAsyncOrdering;
+
+function stop_handleAsyncOrdering(ch, status, data) {
+ Assert.equal(data.length, 20 * 65536);
+ data.forEach(function (v, index) {
+ if (v !== 0) {
+ do_throw("value " + v + " at index " + index + " should be zero");
+ }
+ });
+}
diff --git a/netwerk/test/httpserver/test/test_qi.js b/netwerk/test/httpserver/test/test_qi.js
new file mode 100644
index 0000000000..e88e53119e
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_qi.js
@@ -0,0 +1,107 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* eslint-disable no-control-regex */
+
+/*
+ * Verify the presence of explicit QueryInterface methods on XPCOM objects
+ * exposed by httpd.js, rather than allowing QueryInterface to be implicitly
+ * created by XPConnect.
+ */
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/test",
+ null,
+ start_test,
+ null
+ ),
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/sjs/qi.sjs",
+ null,
+ start_sjs_qi,
+ null
+ ),
+ ];
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+
+ try {
+ srv.identity.QueryInterface(Ci.nsIHttpServerIdentity);
+ } catch (e) {
+ var exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ do_throw("server identity didn't QI: " + exstr);
+ return;
+ }
+
+ srv.registerPathHandler("/test", testHandler);
+ srv.registerDirectory("/", do_get_file("data/"));
+ srv.registerContentType("sjs", "sjs");
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// TEST DATA
+
+function start_test(ch) {
+ Assert.equal(ch.responseStatusText, "QI Tests Passed");
+ Assert.equal(ch.responseStatus, 200);
+}
+
+function start_sjs_qi(ch) {
+ Assert.equal(ch.responseStatusText, "SJS QI Tests Passed");
+ Assert.equal(ch.responseStatus, 200);
+}
+
+function testHandler(request, response) {
+ var exstr;
+ var qid;
+
+ response.setStatusLine(request.httpVersion, 500, "FAIL");
+
+ var passed = false;
+ try {
+ qid = request.QueryInterface(Ci.nsIHttpRequest);
+ passed = qid === request;
+ } catch (e) {
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(
+ request.httpVersion,
+ 500,
+ "request doesn't QI: " + exstr
+ );
+ return;
+ }
+ if (!passed) {
+ response.setStatusLine(request.httpVersion, 500, "request QI'd wrongly?");
+ return;
+ }
+
+ passed = false;
+ try {
+ qid = response.QueryInterface(Ci.nsIHttpResponse);
+ passed = qid === response;
+ } catch (e) {
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(
+ request.httpVersion,
+ 500,
+ "response doesn't QI: " + exstr
+ );
+ return;
+ }
+ if (!passed) {
+ response.setStatusLine(request.httpVersion, 500, "response QI'd wrongly?");
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "QI Tests Passed");
+}
diff --git a/netwerk/test/httpserver/test/test_registerdirectory.js b/netwerk/test/httpserver/test/test_registerdirectory.js
new file mode 100644
index 0000000000..84bb032efc
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerdirectory.js
@@ -0,0 +1,278 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// tests the registerDirectory API
+
+ChromeUtils.defineLazyGetter(this, "BASE", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+function nocache(ch) {
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // important!
+}
+
+function notFound(ch) {
+ Assert.equal(ch.responseStatus, 404);
+ Assert.ok(!ch.requestSucceeded);
+}
+
+function checkOverride(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.responseStatusText, "OK");
+ Assert.ok(ch.requestSucceeded);
+ Assert.equal(ch.getResponseHeader("Override-Succeeded"), "yes");
+}
+
+function check200(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.responseStatusText, "OK");
+}
+
+function checkFile(ch, status, data) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.ok(ch.requestSucceeded);
+
+ var actualFile = serverBasePath.clone();
+ actualFile.append("test_registerdirectory.js");
+ Assert.equal(
+ ch.getResponseHeader("Content-Length"),
+ actualFile.fileSize.toString()
+ );
+ Assert.equal(
+ data.map(v => String.fromCharCode(v)).join(""),
+ fileContents(actualFile)
+ );
+}
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ /** *********************
+ * without a base path *
+ ***********************/
+ new Test(BASE + "/test_registerdirectory.js", nocache, notFound, null),
+
+ /** ******************
+ * with a base path *
+ ********************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ serverBasePath = testsDirectory.clone();
+ srv.registerDirectory("/", serverBasePath);
+ },
+ null,
+ checkFile
+ ),
+
+ /** ***************************
+ * without a base path again *
+ *****************************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ serverBasePath = null;
+ srv.registerDirectory("/", serverBasePath);
+ },
+ notFound,
+ null
+ ),
+
+ /** *************************
+ * registered path handler *
+ ***************************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ srv.registerPathHandler(
+ "/test_registerdirectory.js",
+ override_test_registerdirectory
+ );
+ },
+ checkOverride,
+ null
+ ),
+
+ /** **********************
+ * removed path handler *
+ ************************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function init_registerDirectory6(ch) {
+ nocache(ch);
+ srv.registerPathHandler("/test_registerdirectory.js", null);
+ },
+ notFound,
+ null
+ ),
+
+ /** ******************
+ * with a base path *
+ ********************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+
+ // set the base path again
+ serverBasePath = testsDirectory.clone();
+ srv.registerDirectory("/", serverBasePath);
+ },
+ null,
+ checkFile
+ ),
+
+ /** ***********************
+ * ...and a path handler *
+ *************************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ srv.registerPathHandler(
+ "/test_registerdirectory.js",
+ override_test_registerdirectory
+ );
+ },
+ checkOverride,
+ null
+ ),
+
+ /** **********************
+ * removed base handler *
+ ************************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ serverBasePath = null;
+ srv.registerDirectory("/", serverBasePath);
+ },
+ checkOverride,
+ null
+ ),
+
+ /** **********************
+ * removed path handler *
+ ************************/
+ new Test(
+ BASE + "/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ srv.registerPathHandler("/test_registerdirectory.js", null);
+ },
+ notFound,
+ null
+ ),
+
+ /** ***********************
+ * mapping set up, works *
+ *************************/
+ new Test(
+ BASE + "/foo/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ serverBasePath = testsDirectory.clone();
+ srv.registerDirectory("/foo/", serverBasePath);
+ },
+ check200,
+ null
+ ),
+
+ /** *******************
+ * no mapping, fails *
+ *********************/
+ new Test(
+ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ nocache,
+ notFound,
+ null
+ ),
+
+ /** ****************
+ * mapping, works *
+ ******************/
+ new Test(
+ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ srv.registerDirectory(
+ "/foo/test_registerdirectory.js/",
+ serverBasePath
+ );
+ },
+ null,
+ checkFile
+ ),
+
+ /** **********************************
+ * two mappings set up, still works *
+ ************************************/
+ new Test(BASE + "/foo/test_registerdirectory.js", nocache, null, checkFile),
+
+ /** ************************
+ * remove topmost mapping *
+ **************************/
+ new Test(
+ BASE + "/foo/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ srv.registerDirectory("/foo/", null);
+ },
+ notFound,
+ null
+ ),
+
+ /** ************************************
+ * lower mapping still present, works *
+ **************************************/
+ new Test(
+ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ nocache,
+ null,
+ checkFile
+ ),
+
+ /** *****************
+ * mapping removed *
+ *******************/
+ new Test(
+ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ function (ch) {
+ nocache(ch);
+ srv.registerDirectory("/foo/test_registerdirectory.js/", null);
+ },
+ notFound,
+ null
+ ),
+ ];
+});
+
+var srv;
+var serverBasePath;
+var testsDirectory;
+
+function run_test() {
+ testsDirectory = do_get_cwd();
+
+ srv = createServer();
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// PATH HANDLERS
+
+// override of /test_registerdirectory.js
+function override_test_registerdirectory(metadata, response) {
+ response.setStatusLine("1.1", 200, "OK");
+ response.setHeader("Override-Succeeded", "yes", false);
+
+ var body = "success!";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/httpserver/test/test_registerfile.js b/netwerk/test/httpserver/test/test_registerfile.js
new file mode 100644
index 0000000000..9b93d62b07
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerfile.js
@@ -0,0 +1,44 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// tests the registerFile API
+
+ChromeUtils.defineLazyGetter(this, "BASE", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var file = do_get_file("test_registerfile.js");
+
+function onStart(ch) {
+ Assert.equal(ch.responseStatus, 200);
+}
+
+function onStop(ch, status, data) {
+ // not sufficient for equality, but not likely to be wrong!
+ Assert.equal(data.length, file.fileSize);
+}
+
+ChromeUtils.defineLazyGetter(this, "test", function () {
+ return new Test(BASE + "/foo", null, onStart, onStop);
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+
+ try {
+ srv.registerFile("/foo", do_get_profile());
+ throw new Error("registerFile succeeded!");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ srv.registerFile("/foo", file);
+ srv.start(-1);
+
+ runHttpTests([test], testComplete(srv));
+}
diff --git a/netwerk/test/httpserver/test/test_registerprefix.js b/netwerk/test/httpserver/test/test_registerprefix.js
new file mode 100644
index 0000000000..5edb75225c
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerprefix.js
@@ -0,0 +1,130 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// tests the registerPrefixHandler API
+
+ChromeUtils.defineLazyGetter(this, "BASE", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+function nocache(ch) {
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // important!
+}
+
+function notFound(ch) {
+ Assert.equal(ch.responseStatus, 404);
+ Assert.ok(!ch.requestSucceeded);
+}
+
+function makeCheckOverride(magic) {
+ return function checkOverride(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(ch.responseStatusText, "OK");
+ Assert.ok(ch.requestSucceeded);
+ Assert.equal(ch.getResponseHeader("Override-Succeeded"), magic);
+ };
+}
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ BASE + "/prefix/dummy",
+ prefixHandler,
+ null,
+ makeCheckOverride("prefix")
+ ),
+ new Test(
+ BASE + "/prefix/dummy",
+ pathHandler,
+ null,
+ makeCheckOverride("path")
+ ),
+ new Test(
+ BASE + "/prefix/subpath/dummy",
+ longerPrefixHandler,
+ null,
+ makeCheckOverride("subpath")
+ ),
+ new Test(BASE + "/prefix/dummy", removeHandlers, null, notFound),
+ new Test(
+ BASE + "/prefix/subpath/dummy",
+ newPrefixHandler,
+ null,
+ makeCheckOverride("subpath")
+ ),
+ ];
+});
+
+/** *************************
+ * registered prefix handler *
+ ***************************/
+
+function prefixHandler(channel) {
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/", makeOverride("prefix"));
+}
+
+/** ******************************
+ * registered path handler on top *
+ ********************************/
+
+function pathHandler(channel) {
+ nocache(channel);
+ srv.registerPathHandler("/prefix/dummy", makeOverride("path"));
+}
+
+/** ********************************
+ * registered longer prefix handler *
+ **********************************/
+
+function longerPrefixHandler(channel) {
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/subpath/", makeOverride("subpath"));
+}
+
+/** **********************
+ * removed prefix handler *
+ ************************/
+
+function removeHandlers(channel) {
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/", null);
+ srv.registerPathHandler("/prefix/dummy", null);
+}
+
+/** ***************************
+ * re-register shorter handler *
+ *****************************/
+
+function newPrefixHandler(channel) {
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/", makeOverride("prefix"));
+}
+
+var srv;
+
+function run_test() {
+ // Ensure the profile exists.
+ do_get_profile();
+
+ srv = createServer();
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// PATH HANDLERS
+
+// generate an override
+function makeOverride(magic) {
+ return function override(metadata, response) {
+ response.setStatusLine("1.1", 200, "OK");
+ response.setHeader("Override-Succeeded", magic, false);
+
+ var body = "success!";
+ response.bodyOutputStream.write(body, body.length);
+ };
+}
diff --git a/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js b/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js
new file mode 100644
index 0000000000..b1d7a7d071
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js
@@ -0,0 +1,137 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that even when an incoming request's data for the Request-Line doesn't
+ * all fit in a single onInputStreamReady notification, the request is handled
+ * properly.
+ */
+
+var srv = createServer();
+srv.start(-1);
+const PORT = srv.identity.primaryPort;
+
+function run_test() {
+ srv.registerPathHandler(
+ "/lots-of-leading-blank-lines",
+ lotsOfLeadingBlankLines
+ );
+ srv.registerPathHandler("/very-long-request-line", veryLongRequestLine);
+
+ runRawTests(tests, testComplete(srv));
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+var test, gData, str;
+var tests = [];
+
+function veryLongRequestLine(request, response) {
+ writeDetails(request, response);
+ response.setStatusLine(request.httpVersion, 200, "TEST PASSED");
+}
+
+var reallyLong = "0123456789ABCDEF0123456789ABCDEF"; // 32
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 128
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 512
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 2048
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 8192
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 32768
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 131072
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 524288
+if (reallyLong.length !== 524288) {
+ throw new TypeError("generated length not as long as expected");
+}
+str =
+ "GET /very-long-request-line?" +
+ reallyLong +
+ " HTTP/1.1\r\n" +
+ "Host: localhost:" +
+ PORT +
+ "\r\n" +
+ "\r\n";
+gData = [];
+for (let i = 0; i < str.length; i += 16384) {
+ gData.push(str.substr(i, 16384));
+}
+
+function checkVeryLongRequestLine(data) {
+ var iter = LineIterator(data);
+
+ print("data length: " + data.length);
+ print("iter object: " + iter);
+
+ // Status-Line
+ Assert.equal(iter.next().value, "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body = [
+ "Method: GET",
+ "Path: /very-long-request-line",
+ "Query: " + reallyLong,
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: " + PORT,
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, gData, checkVeryLongRequestLine);
+tests.push(test);
+
+function lotsOfLeadingBlankLines(request, response) {
+ writeDetails(request, response);
+ response.setStatusLine(request.httpVersion, 200, "TEST PASSED");
+}
+
+var blankLines = "\r\n";
+for (let i = 0; i < 14; i++) {
+ blankLines += blankLines;
+}
+str =
+ blankLines +
+ "GET /lots-of-leading-blank-lines HTTP/1.1\r\n" +
+ "Host: localhost:" +
+ PORT +
+ "\r\n" +
+ "\r\n";
+gData = [];
+for (let i = 0; i < str.length; i += 100) {
+ gData.push(str.substr(i, 100));
+}
+
+function checkLotsOfLeadingBlankLines(data) {
+ var iter = LineIterator(data);
+
+ // Status-Line
+ print("data length: " + data.length);
+ print("iter object: " + iter);
+
+ Assert.equal(iter.next().value, "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body = [
+ "Method: GET",
+ "Path: /lots-of-leading-blank-lines",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: " + PORT,
+ ];
+
+ expectLines(iter, body);
+}
+
+test = new RawTest("localhost", PORT, gData, checkLotsOfLeadingBlankLines);
+tests.push(test);
diff --git a/netwerk/test/httpserver/test/test_response_write.js b/netwerk/test/httpserver/test/test_response_write.js
new file mode 100644
index 0000000000..7f5837419e
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_response_write.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// make sure response.write works for strings, and coerces other args to strings
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/writeString",
+ null,
+ check_1234,
+ succeeded
+ ),
+ new Test(
+ "http://localhost:" + srv.identity.primaryPort + "/writeInt",
+ null,
+ check_1234,
+ succeeded
+ ),
+ ];
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+
+ srv.registerPathHandler("/writeString", writeString);
+ srv.registerPathHandler("/writeInt", writeInt);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// TEST DATA
+
+function succeeded(ch, status, data) {
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.equal(data.map(v => String.fromCharCode(v)).join(""), "1234");
+}
+
+function check_1234(ch) {
+ Assert.equal(ch.getResponseHeader("Content-Length"), "4");
+}
+
+// PATH HANDLERS
+
+function writeString(metadata, response) {
+ response.write("1234");
+}
+
+function writeInt(metadata, response) {
+ response.write(1234);
+}
diff --git a/netwerk/test/httpserver/test/test_seizepower.js b/netwerk/test/httpserver/test/test_seizepower.js
new file mode 100644
index 0000000000..a74054ea37
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_seizepower.js
@@ -0,0 +1,180 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests that the seizePower API works correctly.
+ */
+
+ChromeUtils.defineLazyGetter(this, "PORT", function () {
+ return srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test() {
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ srv = createServer();
+
+ srv.registerPathHandler("/raw-data", handleRawData);
+ srv.registerPathHandler("/called-too-late", handleTooLate);
+ srv.registerPathHandler("/exceptions", handleExceptions);
+ srv.registerPathHandler("/async-seizure", handleAsyncSeizure);
+ srv.registerPathHandler("/seize-after-async", handleSeizeAfterAsync);
+
+ srv.start(-1);
+
+ runRawTests(tests, function () {
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ testComplete(srv)();
+ });
+}
+
+function checkException(fun, err, msg) {
+ try {
+ fun();
+ } catch (e) {
+ if (e !== err && e.result !== err) {
+ do_throw(msg);
+ }
+ return;
+ }
+ do_throw(msg);
+}
+
+/** ***************
+ * PATH HANDLERS *
+ *****************/
+
+function handleRawData(request, response) {
+ response.seizePower();
+ response.write("Raw data!");
+ response.finish();
+}
+
+function handleTooLate(request, response) {
+ response.write("DO NOT WANT");
+ var output = response.bodyOutputStream;
+
+ response.seizePower();
+
+ if (response.bodyOutputStream !== output) {
+ response.write("bodyOutputStream changed!");
+ } else {
+ response.write("too-late passed");
+ }
+ response.finish();
+}
+
+function handleExceptions(request, response) {
+ response.seizePower();
+ checkException(
+ function () {
+ response.setStatusLine("1.0", 500, "ISE");
+ },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "setStatusLine should throw not-available after seizePower"
+ );
+ checkException(
+ function () {
+ response.setHeader("X-Fail", "FAIL", false);
+ },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "setHeader should throw not-available after seizePower"
+ );
+ checkException(
+ function () {
+ response.processAsync();
+ },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "processAsync should throw not-available after seizePower"
+ );
+ var out = response.bodyOutputStream;
+ var data = "exceptions test passed";
+ out.write(data, data.length);
+ response.seizePower(); // idempotency test of seizePower
+ response.finish();
+ response.finish(); // idempotency test of finish after seizePower
+ checkException(
+ function () {
+ response.seizePower();
+ },
+ Cr.NS_ERROR_UNEXPECTED,
+ "seizePower should throw unexpected after finish"
+ );
+}
+
+function handleAsyncSeizure(request, response) {
+ response.seizePower();
+ callLater(1, function () {
+ response.write("async seizure passed");
+ response.bodyOutputStream.close();
+ callLater(1, function () {
+ response.finish();
+ });
+ });
+}
+
+function handleSeizeAfterAsync(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "async seizure pass");
+ response.processAsync();
+ checkException(
+ function () {
+ response.seizePower();
+ },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "seizePower should throw not-available after processAsync"
+ );
+ callLater(1, function () {
+ response.finish();
+ });
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new RawTest("localhost", PORT, data0, checkRawData),
+ new RawTest("localhost", PORT, data1, checkTooLate),
+ new RawTest("localhost", PORT, data2, checkExceptions),
+ new RawTest("localhost", PORT, data3, checkAsyncSeizure),
+ new RawTest("localhost", PORT, data4, checkSeizeAfterAsync),
+ ];
+});
+
+// eslint-disable-next-line no-useless-concat
+var data0 = "GET /raw-data HTTP/1.0\r\n" + "\r\n";
+function checkRawData(data) {
+ Assert.equal(data, "Raw data!");
+}
+
+// eslint-disable-next-line no-useless-concat
+var data1 = "GET /called-too-late HTTP/1.0\r\n" + "\r\n";
+function checkTooLate(data) {
+ Assert.equal(LineIterator(data).next().value, "too-late passed");
+}
+
+// eslint-disable-next-line no-useless-concat
+var data2 = "GET /exceptions HTTP/1.0\r\n" + "\r\n";
+function checkExceptions(data) {
+ Assert.equal("exceptions test passed", data);
+}
+
+// eslint-disable-next-line no-useless-concat
+var data3 = "GET /async-seizure HTTP/1.0\r\n" + "\r\n";
+function checkAsyncSeizure(data) {
+ Assert.equal(data, "async seizure passed");
+}
+
+// eslint-disable-next-line no-useless-concat
+var data4 = "GET /seize-after-async HTTP/1.0\r\n" + "\r\n";
+function checkSeizeAfterAsync(data) {
+ Assert.equal(
+ LineIterator(data).next().value,
+ "HTTP/1.0 200 async seizure pass"
+ );
+}
diff --git a/netwerk/test/httpserver/test/test_setindexhandler.js b/netwerk/test/httpserver/test/test_setindexhandler.js
new file mode 100644
index 0000000000..e65f565673
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_setindexhandler.js
@@ -0,0 +1,60 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Make sure setIndexHandler works as expected
+
+var srv, serverBasePath;
+
+function run_test() {
+ srv = createServer();
+ serverBasePath = do_get_profile();
+ srv.registerDirectory("/", serverBasePath);
+ srv.setIndexHandler(myIndexHandler);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + srv.identity.primaryPort + "/";
+});
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(URL, init, startCustomIndexHandler, stopCustomIndexHandler),
+ new Test(URL, init, startDefaultIndexHandler, stopDefaultIndexHandler),
+ ];
+});
+
+function init(ch) {
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // important!
+}
+function startCustomIndexHandler(ch) {
+ Assert.equal(ch.getResponseHeader("Content-Length"), "10");
+ srv.setIndexHandler(null);
+}
+function stopCustomIndexHandler(ch, status, data) {
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.equal(String.fromCharCode.apply(null, data), "directory!");
+}
+
+function startDefaultIndexHandler(ch) {
+ Assert.equal(ch.responseStatus, 200);
+}
+function stopDefaultIndexHandler(ch, status, data) {
+ Assert.ok(Components.isSuccessCode(status));
+}
+
+// PATH HANDLERS
+
+function myIndexHandler(metadata, response) {
+ var dir = metadata.getProperty("directory");
+ Assert.ok(dir != null);
+ Assert.ok(dir instanceof Ci.nsIFile);
+ Assert.ok(dir.equals(serverBasePath));
+
+ response.write("directory!");
+}
diff --git a/netwerk/test/httpserver/test/test_setstatusline.js b/netwerk/test/httpserver/test/test_setstatusline.js
new file mode 100644
index 0000000000..f9cc189a68
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_setstatusline.js
@@ -0,0 +1,142 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// exercise nsIHttpResponse.setStatusLine, ensure its atomicity, and ensure the
+// specified behavior occurs if it's not called
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+
+ srv.registerPathHandler("/no/setstatusline", noSetstatusline);
+ srv.registerPathHandler("/http1_0", http1_0);
+ srv.registerPathHandler("/http1_1", http1_1);
+ srv.registerPathHandler("/invalidVersion", invalidVersion);
+ srv.registerPathHandler("/invalidStatus", invalidStatus);
+ srv.registerPathHandler("/invalidDescription", invalidDescription);
+ srv.registerPathHandler("/crazyCode", crazyCode);
+ srv.registerPathHandler("/nullVersion", nullVersion);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+/** ***********
+ * UTILITIES *
+ *************/
+
+function checkStatusLine(
+ channel,
+ httpMaxVer,
+ httpMinVer,
+ httpCode,
+ statusText
+) {
+ Assert.equal(channel.responseStatus, httpCode);
+ Assert.equal(channel.responseStatusText, statusText);
+
+ var respMaj = {},
+ respMin = {};
+ channel.getResponseVersion(respMaj, respMin);
+ Assert.equal(respMaj.value, httpMaxVer);
+ Assert.equal(respMin.value, httpMinVer);
+}
+
+/** *******
+ * TESTS *
+ *********/
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(URL + "/no/setstatusline", null, startNoSetStatusLine, stop),
+ new Test(URL + "/http1_0", null, startHttp1_0, stop),
+ new Test(URL + "/http1_1", null, startHttp1_1, stop),
+ new Test(URL + "/invalidVersion", null, startPassedTrue, stop),
+ new Test(URL + "/invalidStatus", null, startPassedTrue, stop),
+ new Test(URL + "/invalidDescription", null, startPassedTrue, stop),
+ new Test(URL + "/crazyCode", null, startCrazy, stop),
+ new Test(URL + "/nullVersion", null, startNullVersion, stop),
+ ];
+});
+
+// /no/setstatusline
+function noSetstatusline(metadata, response) {}
+function startNoSetStatusLine(ch) {
+ checkStatusLine(ch, 1, 1, 200, "OK");
+}
+function stop(ch, status, data) {
+ Assert.ok(Components.isSuccessCode(status));
+}
+
+// /http1_0
+function http1_0(metadata, response) {
+ response.setStatusLine("1.0", 200, "OK");
+}
+function startHttp1_0(ch) {
+ checkStatusLine(ch, 1, 0, 200, "OK");
+}
+
+// /http1_1
+function http1_1(metadata, response) {
+ response.setStatusLine("1.1", 200, "OK");
+}
+function startHttp1_1(ch) {
+ checkStatusLine(ch, 1, 1, 200, "OK");
+}
+
+// /invalidVersion
+function invalidVersion(metadata, response) {
+ try {
+ response.setStatusLine(" 1.0", 200, "FAILED");
+ } catch (e) {
+ response.setHeader("Passed", "true", false);
+ }
+}
+function startPassedTrue(ch) {
+ checkStatusLine(ch, 1, 1, 200, "OK");
+ Assert.equal(ch.getResponseHeader("Passed"), "true");
+}
+
+// /invalidStatus
+function invalidStatus(metadata, response) {
+ try {
+ response.setStatusLine("1.0", 1000, "FAILED");
+ } catch (e) {
+ response.setHeader("Passed", "true", false);
+ }
+}
+
+// /invalidDescription
+function invalidDescription(metadata, response) {
+ try {
+ response.setStatusLine("1.0", 200, "FAILED\x01");
+ } catch (e) {
+ response.setHeader("Passed", "true", false);
+ }
+}
+
+// /crazyCode
+function crazyCode(metadata, response) {
+ response.setStatusLine("1.1", 617, "Crazy");
+}
+function startCrazy(ch) {
+ checkStatusLine(ch, 1, 1, 617, "Crazy");
+}
+
+// /nullVersion
+function nullVersion(metadata, response) {
+ response.setStatusLine(null, 255, "NULL");
+}
+function startNullVersion(ch) {
+ // currently, this server implementation defaults to 1.1
+ checkStatusLine(ch, 1, 1, 255, "NULL");
+}
diff --git a/netwerk/test/httpserver/test/test_sjs.js b/netwerk/test/httpserver/test/test_sjs.js
new file mode 100644
index 0000000000..51d92ec4e0
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs.js
@@ -0,0 +1,243 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// tests support for server JS-generated pages
+
+var srv = createServer();
+
+var sjs = do_get_file("data/sjs/cgi.sjs");
+// NB: The server has no state at this point -- all state is set up and torn
+// down in the tests, because we run the same tests twice with only a
+// different query string on the requests, followed by the oddball
+// test that doesn't care about throwing or not.
+srv.start(-1);
+const PORT = srv.identity.primaryPort;
+
+const BASE = "http://localhost:" + PORT;
+var test;
+var tests = [];
+
+/** *******************
+ * UTILITY FUNCTIONS *
+ *********************/
+
+function bytesToString(bytes) {
+ return bytes
+ .map(function (v) {
+ return String.fromCharCode(v);
+ })
+ .join("");
+}
+
+function skipCache(ch) {
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+}
+
+/** ******************
+ * DEFINE THE TESTS *
+ ********************/
+
+/**
+ * Adds the set of tests defined in here, differentiating between tests with a
+ * SJS which throws an exception and creates a server error and tests with a
+ * normal, successful SJS.
+ */
+function setupTests(throwing) {
+ const TEST_URL = BASE + "/cgi.sjs" + (throwing ? "?throw" : "");
+
+ // registerFile with SJS => raw text
+
+ function setupFile(ch) {
+ srv.registerFile("/cgi.sjs", sjs);
+ skipCache(ch);
+ }
+
+ function verifyRawText(channel, status, bytes) {
+ dumpn(channel.originalURI.spec);
+ Assert.equal(bytesToString(bytes), fileContents(sjs));
+ }
+
+ test = new Test(TEST_URL, setupFile, null, verifyRawText);
+ tests.push(test);
+
+ // add mapping, => interpreted
+
+ function addTypeMapping(ch) {
+ srv.registerContentType("sjs", "sjs");
+ skipCache(ch);
+ }
+
+ function checkType(ch) {
+ if (throwing) {
+ Assert.ok(!ch.requestSucceeded);
+ Assert.equal(ch.responseStatus, 500);
+ } else {
+ Assert.equal(ch.contentType, "text/plain");
+ }
+ }
+
+ function checkContents(ch, status, data) {
+ if (!throwing) {
+ Assert.equal("PASS", bytesToString(data));
+ }
+ }
+
+ test = new Test(TEST_URL, addTypeMapping, checkType, checkContents);
+ tests.push(test);
+
+ // remove file/type mapping, map containing directory => raw text
+
+ function setupDirectoryAndRemoveType(ch) {
+ dumpn("removing type mapping");
+ srv.registerContentType("sjs", null);
+ srv.registerFile("/cgi.sjs", null);
+ srv.registerDirectory("/", sjs.parent);
+ skipCache(ch);
+ }
+
+ test = new Test(TEST_URL, setupDirectoryAndRemoveType, null, verifyRawText);
+ tests.push(test);
+
+ // add mapping, => interpreted
+
+ function contentAndCleanup(ch, status, data) {
+ checkContents(ch, status, data);
+
+ // clean up state we've set up
+ srv.registerDirectory("/", null);
+ srv.registerContentType("sjs", null);
+ }
+
+ test = new Test(TEST_URL, addTypeMapping, checkType, contentAndCleanup);
+ tests.push(test);
+
+ // NB: No remaining state in the server right now! If we have any here,
+ // either the second run of tests (without ?throw) or the tests added
+ // after the two sets will almost certainly fail.
+}
+
+/** ***************
+ * ADD THE TESTS *
+ *****************/
+
+setupTests(true);
+setupTests(false);
+
+// Test that when extension-mappings are used, the entire filename cannot be
+// treated as an extension -- there must be at least one dot for a filename to
+// match an extension.
+
+function init(ch) {
+ // clean up state we've set up
+ srv.registerDirectory("/", sjs.parent);
+ srv.registerContentType("sjs", "sjs");
+ skipCache(ch);
+}
+
+function checkNotSJS(ch, status, data) {
+ Assert.notEqual("FAIL", bytesToString(data));
+}
+
+test = new Test(BASE + "/sjs", init, null, checkNotSJS);
+tests.push(test);
+
+// Test that Range requests are passed through to the SJS file without
+// bounds checking.
+
+function rangeInit(expectedRangeHeader) {
+ return function setupRangeRequest(ch) {
+ ch.setRequestHeader("Range", expectedRangeHeader, false);
+ };
+}
+
+function checkRangeResult(ch) {
+ try {
+ var val = ch.getResponseHeader("Content-Range");
+ } catch (e) {
+ /* IDL doesn't specify a particular exception to require */
+ }
+ if (val !== undefined) {
+ do_throw(
+ "should not have gotten a Content-Range header, but got one " +
+ "with this value: " +
+ val
+ );
+ }
+ Assert.equal(200, ch.responseStatus);
+ Assert.equal("OK", ch.responseStatusText);
+}
+
+test = new Test(
+ BASE + "/range-checker.sjs",
+ rangeInit("not-a-bytes-equals-specifier"),
+ checkRangeResult,
+ null
+);
+tests.push(test);
+test = new Test(
+ BASE + "/range-checker.sjs",
+ rangeInit("bytes=-"),
+ checkRangeResult,
+ null
+);
+tests.push(test);
+test = new Test(
+ BASE + "/range-checker.sjs",
+ rangeInit("bytes=1000000-"),
+ checkRangeResult,
+ null
+);
+tests.push(test);
+test = new Test(
+ BASE + "/range-checker.sjs",
+ rangeInit("bytes=1-4"),
+ checkRangeResult,
+ null
+);
+tests.push(test);
+test = new Test(
+ BASE + "/range-checker.sjs",
+ rangeInit("bytes=-4"),
+ checkRangeResult,
+ null
+);
+tests.push(test);
+
+// One last test: for file mappings, the content-type is determined by the
+// extension of the file on the server, not by the extension of the requested
+// path.
+
+function setupFileMapping(ch) {
+ srv.registerFile("/script.html", sjs);
+}
+
+function onStart(ch) {
+ Assert.equal(ch.contentType, "text/plain");
+}
+
+function onStop(ch, status, data) {
+ Assert.equal("PASS", bytesToString(data));
+}
+
+test = new Test(BASE + "/script.html", setupFileMapping, onStart, onStop);
+tests.push(test);
+
+/** ***************
+ * RUN THE TESTS *
+ *****************/
+
+function run_test() {
+ // Test for a content-type which isn't a field-value
+ try {
+ srv.registerContentType("foo", "bar\nbaz");
+ throw new Error(
+ "this server throws on content-types which aren't field-values"
+ );
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_INVALID_ARG);
+ }
+ runHttpTests(tests, testComplete(srv));
+}
diff --git a/netwerk/test/httpserver/test/test_sjs_object_state.js b/netwerk/test/httpserver/test/test_sjs_object_state.js
new file mode 100644
index 0000000000..30c1e5c064
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_object_state.js
@@ -0,0 +1,305 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests that the object-state-preservation mechanism works correctly.
+ */
+
+ChromeUtils.defineLazyGetter(this, "PATH", function () {
+ return "http://localhost:" + srv.identity.primaryPort + "/object-state.sjs";
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+ var sjsDir = do_get_file("data/sjs/");
+ srv.registerDirectory("/", sjsDir);
+ srv.registerContentType("sjs", "sjs");
+ srv.start(-1);
+
+ do_test_pending();
+
+ new HTTPTestLoader(PATH + "?state=initial", initialStart, initialStop);
+}
+
+/** ******************
+ * OBSERVER METHODS *
+ ********************/
+
+/*
+ * In practice the current implementation will guarantee an exact ordering of
+ * all start and stop callbacks. However, in the interests of robustness, this
+ * test will pass given any valid ordering of callbacks. Any ordering of calls
+ * which obeys the partial ordering shown by this directed acyclic graph will be
+ * handled correctly:
+ *
+ * initialStart
+ * |
+ * V
+ * intermediateStart
+ * |
+ * V
+ * intermediateStop
+ * | \
+ * | V
+ * | initialStop
+ * V
+ * triggerStart
+ * |
+ * V
+ * triggerStop
+ *
+ */
+
+var initialStarted = false;
+function initialStart(ch) {
+ dumpn("*** initialStart");
+
+ if (initialStarted) {
+ do_throw("initialStart: initialStarted is true?!?!");
+ }
+
+ initialStarted = true;
+
+ new HTTPTestLoader(
+ PATH + "?state=intermediate",
+ intermediateStart,
+ intermediateStop
+ );
+}
+
+var initialStopped = false;
+function initialStop(ch, status, data) {
+ dumpn("*** initialStop");
+
+ Assert.equal(
+ data
+ .map(function (v) {
+ return String.fromCharCode(v);
+ })
+ .join(""),
+ "done"
+ );
+
+ Assert.equal(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted) {
+ do_throw("initialStop: initialStarted is false?!?!");
+ }
+ if (initialStopped) {
+ do_throw("initialStop: initialStopped is true?!?!");
+ }
+ if (!intermediateStarted) {
+ do_throw("initialStop: intermediateStarted is false?!?!");
+ }
+ if (!intermediateStopped) {
+ do_throw("initialStop: intermediateStopped is false?!?!");
+ }
+
+ initialStopped = true;
+
+ checkForFinish();
+}
+
+var intermediateStarted = false;
+function intermediateStart(ch) {
+ dumpn("*** intermediateStart");
+
+ Assert.notEqual(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted) {
+ do_throw("intermediateStart: initialStarted is false?!?!");
+ }
+ if (intermediateStarted) {
+ do_throw("intermediateStart: intermediateStarted is true?!?!");
+ }
+
+ intermediateStarted = true;
+}
+
+var intermediateStopped = false;
+function intermediateStop(ch, status, data) {
+ dumpn("*** intermediateStop");
+
+ Assert.equal(
+ data
+ .map(function (v) {
+ return String.fromCharCode(v);
+ })
+ .join(""),
+ "intermediate"
+ );
+
+ Assert.notEqual(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted) {
+ do_throw("intermediateStop: initialStarted is false?!?!");
+ }
+ if (!intermediateStarted) {
+ do_throw("intermediateStop: intermediateStarted is false?!?!");
+ }
+ if (intermediateStopped) {
+ do_throw("intermediateStop: intermediateStopped is true?!?!");
+ }
+
+ intermediateStopped = true;
+
+ new HTTPTestLoader(PATH + "?state=trigger", triggerStart, triggerStop);
+}
+
+var triggerStarted = false;
+function triggerStart(ch) {
+ dumpn("*** triggerStart");
+
+ if (!initialStarted) {
+ do_throw("triggerStart: initialStarted is false?!?!");
+ }
+ if (!intermediateStarted) {
+ do_throw("triggerStart: intermediateStarted is false?!?!");
+ }
+ if (!intermediateStopped) {
+ do_throw("triggerStart: intermediateStopped is false?!?!");
+ }
+ if (triggerStarted) {
+ do_throw("triggerStart: triggerStarted is true?!?!");
+ }
+
+ triggerStarted = true;
+}
+
+var triggerStopped = false;
+function triggerStop(ch, status, data) {
+ dumpn("*** triggerStop");
+
+ Assert.equal(
+ data
+ .map(function (v) {
+ return String.fromCharCode(v);
+ })
+ .join(""),
+ "trigger"
+ );
+
+ if (!initialStarted) {
+ do_throw("triggerStop: initialStarted is false?!?!");
+ }
+ if (!intermediateStarted) {
+ do_throw("triggerStop: intermediateStarted is false?!?!");
+ }
+ if (!intermediateStopped) {
+ do_throw("triggerStop: intermediateStopped is false?!?!");
+ }
+ if (!triggerStarted) {
+ do_throw("triggerStop: triggerStarted is false?!?!");
+ }
+ if (triggerStopped) {
+ do_throw("triggerStop: triggerStopped is false?!?!");
+ }
+
+ triggerStopped = true;
+
+ checkForFinish();
+}
+
+var finished = false;
+function checkForFinish() {
+ if (finished) {
+ try {
+ do_throw("uh-oh, how are we being finished twice?!?!");
+ } finally {
+ // eslint-disable-next-line no-undef
+ quit(1);
+ }
+ }
+
+ if (triggerStopped && initialStopped) {
+ finished = true;
+ try {
+ Assert.equal(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted) {
+ do_throw("checkForFinish: initialStarted is false?!?!");
+ }
+ if (!intermediateStarted) {
+ do_throw("checkForFinish: intermediateStarted is false?!?!");
+ }
+ if (!intermediateStopped) {
+ do_throw("checkForFinish: intermediateStopped is false?!?!");
+ }
+ if (!triggerStarted) {
+ do_throw("checkForFinish: triggerStarted is false?!?!");
+ }
+ } finally {
+ srv.stop(do_test_finished);
+ }
+ }
+}
+
+/** *******************************
+ * UTILITY OBSERVABLE URL LOADER *
+ *********************************/
+
+/** Stream listener for the channels. */
+function HTTPTestLoader(path, start, stop) {
+ /** Path to load. */
+ this._path = path;
+
+ /** Array of bytes of data in body of response. */
+ this._data = [];
+
+ /** onStartRequest callback. */
+ this._start = start;
+
+ /** onStopRequest callback. */
+ this._stop = stop;
+
+ var channel = makeChannel(path);
+ channel.asyncOpen(this);
+}
+HTTPTestLoader.prototype = {
+ onStartRequest(request) {
+ dumpn("*** HTTPTestLoader.onStartRequest for " + this._path);
+
+ var ch = request
+ .QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ try {
+ try {
+ this._start(ch);
+ } catch (e) {
+ do_throw(this._path + ": error in onStartRequest: " + e);
+ }
+ } catch (e) {
+ dumpn(
+ "!!! swallowing onStartRequest exception so onStopRequest is " +
+ "called..."
+ );
+ }
+ },
+ onDataAvailable(request, inputStream, offset, count) {
+ dumpn("*** HTTPTestLoader.onDataAvailable for " + this._path);
+
+ Array.prototype.push.apply(
+ this._data,
+ makeBIS(inputStream).readByteArray(count)
+ );
+ },
+ onStopRequest(request, status) {
+ dumpn("*** HTTPTestLoader.onStopRequest for " + this._path);
+
+ var ch = request
+ .QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ this._stop(ch, status, this._data);
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+};
diff --git a/netwerk/test/httpserver/test/test_sjs_state.js b/netwerk/test/httpserver/test/test_sjs_state.js
new file mode 100644
index 0000000000..4d1fc37930
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_state.js
@@ -0,0 +1,203 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// exercises the server's state-preservation API
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+ var sjsDir = do_get_file("data/sjs/");
+ srv.registerDirectory("/", sjsDir);
+ srv.registerContentType("sjs", "sjs");
+ srv.registerPathHandler("/path-handler", pathHandler);
+ srv.start(-1);
+
+ function done() {
+ Assert.equal(srv.getSharedState("shared-value"), "done!");
+ Assert.equal(
+ srv.getState("/path-handler", "private-value"),
+ "pathHandlerPrivate2"
+ );
+ Assert.equal(srv.getState("/state1.sjs", "private-value"), "");
+ Assert.equal(srv.getState("/state2.sjs", "private-value"), "newPrivate5");
+ do_test_pending();
+ srv.stop(function () {
+ do_test_finished();
+ });
+ }
+
+ runHttpTests(tests, done);
+}
+
+/** **********
+ * HANDLERS *
+ ************/
+
+var firstTime = true;
+
+function pathHandler(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ response.setHeader(
+ "X-Old-Shared-Value",
+ srv.getSharedState("shared-value"),
+ false
+ );
+ response.setHeader(
+ "X-Old-Private-Value",
+ srv.getState("/path-handler", "private-value"),
+ false
+ );
+
+ var privateValue, sharedValue;
+ if (firstTime) {
+ firstTime = false;
+ privateValue = "pathHandlerPrivate";
+ sharedValue = "pathHandlerShared";
+ } else {
+ privateValue = "pathHandlerPrivate2";
+ sharedValue = "";
+ }
+
+ srv.setState("/path-handler", "private-value", privateValue);
+ srv.setSharedState("shared-value", sharedValue);
+
+ response.setHeader("X-New-Private-Value", privateValue, false);
+ response.setHeader("X-New-Shared-Value", sharedValue, false);
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ return [
+ new Test(
+ // eslint-disable-next-line no-useless-concat
+ URL + "/state1.sjs?" + "newShared=newShared&newPrivate=newPrivate",
+ null,
+ start_initial,
+ null
+ ),
+ new Test(
+ // eslint-disable-next-line no-useless-concat
+ URL + "/state1.sjs?" + "newShared=newShared2&newPrivate=newPrivate2",
+ null,
+ start_overwrite,
+ null
+ ),
+ new Test(
+ // eslint-disable-next-line no-useless-concat
+ URL + "/state1.sjs?" + "newShared=&newPrivate=newPrivate3",
+ null,
+ start_remove,
+ null
+ ),
+ new Test(URL + "/path-handler", null, start_handler, null),
+ new Test(URL + "/path-handler", null, start_handler_again, null),
+ new Test(
+ // eslint-disable-next-line no-useless-concat
+ URL + "/state2.sjs?" + "newShared=newShared4&newPrivate=newPrivate4",
+ null,
+ start_other_initial,
+ null
+ ),
+ new Test(
+ // eslint-disable-next-line no-useless-concat
+ URL + "/state2.sjs?" + "newShared=",
+ null,
+ start_other_remove_ignore,
+ null
+ ),
+ new Test(
+ // eslint-disable-next-line no-useless-concat
+ URL + "/state2.sjs?" + "newShared=newShared5&newPrivate=newPrivate5",
+ null,
+ start_other_set_new,
+ null
+ ),
+ new Test(
+ // eslint-disable-next-line no-useless-concat
+ URL + "/state1.sjs?" + "newShared=done!&newPrivate=",
+ null,
+ start_set_remove_original,
+ null
+ ),
+ ];
+});
+
+/* Hack around bug 474845 for now. */
+function getHeaderFunction(ch) {
+ function getHeader(name) {
+ try {
+ return ch.getResponseHeader(name);
+ } catch (e) {
+ if (e.result !== Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw e;
+ }
+ }
+ return "";
+ }
+ return getHeader;
+}
+
+function expectValues(ch, oldShared, newShared, oldPrivate, newPrivate) {
+ var getHeader = getHeaderFunction(ch);
+
+ Assert.equal(ch.responseStatus, 200);
+ Assert.equal(getHeader("X-Old-Shared-Value"), oldShared);
+ Assert.equal(getHeader("X-New-Shared-Value"), newShared);
+ Assert.equal(getHeader("X-Old-Private-Value"), oldPrivate);
+ Assert.equal(getHeader("X-New-Private-Value"), newPrivate);
+}
+
+function start_initial(ch) {
+ dumpn("XXX start_initial");
+ expectValues(ch, "", "newShared", "", "newPrivate");
+}
+
+function start_overwrite(ch) {
+ expectValues(ch, "newShared", "newShared2", "newPrivate", "newPrivate2");
+}
+
+function start_remove(ch) {
+ expectValues(ch, "newShared2", "", "newPrivate2", "newPrivate3");
+}
+
+function start_handler(ch) {
+ expectValues(ch, "", "pathHandlerShared", "", "pathHandlerPrivate");
+}
+
+function start_handler_again(ch) {
+ expectValues(
+ ch,
+ "pathHandlerShared",
+ "",
+ "pathHandlerPrivate",
+ "pathHandlerPrivate2"
+ );
+}
+
+function start_other_initial(ch) {
+ expectValues(ch, "", "newShared4", "", "newPrivate4");
+}
+
+function start_other_remove_ignore(ch) {
+ expectValues(ch, "newShared4", "", "newPrivate4", "");
+}
+
+function start_other_set_new(ch) {
+ expectValues(ch, "", "newShared5", "newPrivate4", "newPrivate5");
+}
+
+function start_set_remove_original(ch) {
+ expectValues(ch, "newShared5", "done!", "newPrivate3", "");
+}
diff --git a/netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js b/netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js
new file mode 100644
index 0000000000..8610dcb7fb
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js
@@ -0,0 +1,73 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests that running an SJS a whole lot of times doesn't have any ill effects
+ * (like exceeding open-file limits due to not closing the SJS file each time,
+ * then preventing any file from being opened).
+ */
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test() {
+ srv = createServer();
+ var sjsDir = do_get_file("data/sjs/");
+ srv.registerDirectory("/", sjsDir);
+ srv.registerContentType("sjs", "sjs");
+ srv.start(-1);
+
+ function done() {
+ do_test_pending();
+ srv.stop(function () {
+ do_test_finished();
+ });
+ Assert.equal(gStartCount, TEST_RUNS);
+ Assert.ok(lastPassed);
+ }
+
+ runHttpTests(tests, done);
+}
+
+/** *************
+ * BEGIN TESTS *
+ ***************/
+
+var gStartCount = 0;
+var lastPassed = false;
+
+// This hits the open-file limit for me on OS X; your mileage may vary.
+const TEST_RUNS = 250;
+
+ChromeUtils.defineLazyGetter(this, "tests", function () {
+ var _tests = new Array(TEST_RUNS + 1);
+ var _test = new Test(URL + "/thrower.sjs?throw", null, start_thrower);
+ for (var i = 0; i < TEST_RUNS; i++) {
+ _tests[i] = _test;
+ }
+ // ...and don't forget to stop!
+ _tests[TEST_RUNS] = new Test(URL + "/thrower.sjs", null, start_last);
+ return _tests;
+});
+
+function start_thrower(ch) {
+ Assert.equal(ch.responseStatus, 500);
+ Assert.ok(!ch.requestSucceeded);
+
+ gStartCount++;
+}
+
+function start_last(ch) {
+ Assert.equal(ch.responseStatus, 200);
+ Assert.ok(ch.requestSucceeded);
+
+ Assert.equal(ch.getResponseHeader("X-Test-Status"), "PASS");
+
+ lastPassed = true;
+}
diff --git a/netwerk/test/httpserver/test/test_start_stop.js b/netwerk/test/httpserver/test/test_start_stop.js
new file mode 100644
index 0000000000..9dcd7c673a
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_start_stop.js
@@ -0,0 +1,166 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests for correct behavior of the server start() and stop() methods.
+ */
+
+ChromeUtils.defineLazyGetter(this, "PORT", function () {
+ return srv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "PREPATH", function () {
+ return "http://localhost:" + PORT;
+});
+
+var srv, srv2;
+
+function run_test() {
+ if (mozinfo.os == "win") {
+ dumpn(
+ "*** not running test_start_stop.js on Windows for now, because " +
+ "Windows is dumb"
+ );
+ return;
+ }
+
+ dumpn("*** run_test");
+
+ srv = createServer();
+ srv.start(-1);
+
+ try {
+ srv.start(PORT);
+ do_throw("starting a started server");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_ALREADY_INITIALIZED);
+ }
+
+ do_test_pending();
+ srv.stop(function () {
+ try {
+ do_test_pending();
+ run_test_2();
+ } finally {
+ do_test_finished();
+ }
+ });
+}
+
+function run_test_2() {
+ dumpn("*** run_test_2");
+
+ do_test_finished();
+
+ srv.start(PORT);
+ srv2 = createServer();
+
+ try {
+ srv2.start(PORT);
+ do_throw("two servers on one port?");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ do_test_pending();
+ try {
+ srv.stop({
+ onStopped() {
+ try {
+ do_test_pending();
+ run_test_3();
+ } finally {
+ do_test_finished();
+ }
+ },
+ });
+ } catch (e) {
+ do_throw("error stopping with an object: " + e);
+ }
+}
+
+function run_test_3() {
+ dumpn("*** run_test_3");
+
+ do_test_finished();
+
+ srv.start(PORT);
+
+ do_test_pending();
+ try {
+ srv.stop().then(function () {
+ try {
+ do_test_pending();
+ run_test_4();
+ } finally {
+ do_test_finished();
+ }
+ });
+ } catch (e) {
+ do_throw("error stopping with an object: " + e);
+ }
+}
+
+function run_test_4() {
+ dumpn("*** run_test_4");
+
+ do_test_finished();
+
+ srv.registerPathHandler("/handle", handle);
+ srv.start(PORT);
+
+ // Don't rely on the exact (but implementation-constant) sequence of events
+ // as it currently exists by making either run_test_5 or serverStopped handle
+ // the final shutdown.
+ do_test_pending();
+
+ runHttpTests([new Test(PREPATH + "/handle")], run_test_5);
+}
+
+var testsComplete = false;
+
+function run_test_5() {
+ dumpn("*** run_test_5");
+
+ testsComplete = true;
+ if (stopped) {
+ do_test_finished();
+ }
+}
+
+const INTERVAL = 500;
+
+function handle(request, response) {
+ response.processAsync();
+
+ dumpn("*** stopping server...");
+ srv.stop(serverStopped);
+
+ callLater(INTERVAL, function () {
+ Assert.ok(!stopped);
+
+ callLater(INTERVAL, function () {
+ Assert.ok(!stopped);
+ response.finish();
+
+ try {
+ response.processAsync();
+ do_throw("late processAsync didn't throw?");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_UNEXPECTED);
+ }
+ });
+ });
+}
+
+var stopped = false;
+function serverStopped() {
+ dumpn("*** server really, fully shut down now");
+ stopped = true;
+ if (testsComplete) {
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/httpserver/test/test_start_stop_ipv6.js b/netwerk/test/httpserver/test/test_start_stop_ipv6.js
new file mode 100644
index 0000000000..060605d0e3
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_start_stop_ipv6.js
@@ -0,0 +1,166 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests for correct behavior of the server start_ipv6() and stop() methods.
+ */
+
+ChromeUtils.defineLazyGetter(this, "PORT", function () {
+ return srv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "PREPATH", function () {
+ return "http://localhost:" + PORT;
+});
+
+var srv, srv2;
+
+function run_test() {
+ if (mozinfo.os == "win") {
+ dumpn(
+ "*** not running test_start_stop.js on Windows for now, because " +
+ "Windows is dumb"
+ );
+ return;
+ }
+
+ dumpn("*** run_test");
+
+ srv = createServer();
+ srv.start_ipv6(-1);
+
+ try {
+ srv.start_ipv6(PORT);
+ do_throw("starting a started server");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_ALREADY_INITIALIZED);
+ }
+
+ do_test_pending();
+ srv.stop(function () {
+ try {
+ do_test_pending();
+ run_test_2();
+ } finally {
+ do_test_finished();
+ }
+ });
+}
+
+function run_test_2() {
+ dumpn("*** run_test_2");
+
+ do_test_finished();
+
+ srv.start_ipv6(PORT);
+ srv2 = createServer();
+
+ try {
+ srv2.start_ipv6(PORT);
+ do_throw("two servers on one port?");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ do_test_pending();
+ try {
+ srv.stop({
+ onStopped() {
+ try {
+ do_test_pending();
+ run_test_3();
+ } finally {
+ do_test_finished();
+ }
+ },
+ });
+ } catch (e) {
+ do_throw("error stopping with an object: " + e);
+ }
+}
+
+function run_test_3() {
+ dumpn("*** run_test_3");
+
+ do_test_finished();
+
+ srv.start_ipv6(PORT);
+
+ do_test_pending();
+ try {
+ srv.stop().then(function () {
+ try {
+ do_test_pending();
+ run_test_4();
+ } finally {
+ do_test_finished();
+ }
+ });
+ } catch (e) {
+ do_throw("error stopping with an object: " + e);
+ }
+}
+
+function run_test_4() {
+ dumpn("*** run_test_4");
+
+ do_test_finished();
+
+ srv.registerPathHandler("/handle", handle);
+ srv.start_ipv6(PORT);
+
+ // Don't rely on the exact (but implementation-constant) sequence of events
+ // as it currently exists by making either run_test_5 or serverStopped handle
+ // the final shutdown.
+ do_test_pending();
+
+ runHttpTests([new Test(PREPATH + "/handle")], run_test_5);
+}
+
+var testsComplete = false;
+
+function run_test_5() {
+ dumpn("*** run_test_5");
+
+ testsComplete = true;
+ if (stopped) {
+ do_test_finished();
+ }
+}
+
+const INTERVAL = 500;
+
+function handle(request, response) {
+ response.processAsync();
+
+ dumpn("*** stopping server...");
+ srv.stop(serverStopped);
+
+ callLater(INTERVAL, function () {
+ Assert.ok(!stopped);
+
+ callLater(INTERVAL, function () {
+ Assert.ok(!stopped);
+ response.finish();
+
+ try {
+ response.processAsync();
+ do_throw("late processAsync didn't throw?");
+ } catch (e) {
+ isException(e, Cr.NS_ERROR_UNEXPECTED);
+ }
+ });
+ });
+}
+
+var stopped = false;
+function serverStopped() {
+ dumpn("*** server really, fully shut down now");
+ stopped = true;
+ if (testsComplete) {
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/httpserver/test/xpcshell.toml b/netwerk/test/httpserver/test/xpcshell.toml
new file mode 100644
index 0000000000..0a687e0363
--- /dev/null
+++ b/netwerk/test/httpserver/test/xpcshell.toml
@@ -0,0 +1,68 @@
+[DEFAULT]
+head = "head_utils.js"
+support-files = ["data/**"]
+
+["test_async_response_sending.js"]
+
+["test_basic_functionality.js"]
+
+["test_body_length.js"]
+
+["test_byte_range.js"]
+
+["test_cern_meta.js"]
+
+["test_default_index_handler.js"]
+
+["test_empty_body.js"]
+
+["test_errorhandler_exception.js"]
+
+["test_header_array.js"]
+
+["test_headers.js"]
+
+["test_host.js"]
+skip-if = ["os == 'mac'"]
+run-sequentially = "Reusing same server on different specific ports."
+
+["test_host_identity.js"]
+
+["test_linedata.js"]
+
+["test_load_module.js"]
+
+["test_name_scheme.js"]
+
+["test_processasync.js"]
+
+["test_qi.js"]
+
+["test_registerdirectory.js"]
+
+["test_registerfile.js"]
+
+["test_registerprefix.js"]
+
+["test_request_line_split_in_two_packets.js"]
+
+["test_response_write.js"]
+
+["test_seizepower.js"]
+skip-if = ["os == 'mac' && socketprocess_networking"]
+
+["test_setindexhandler.js"]
+
+["test_setstatusline.js"]
+
+["test_sjs.js"]
+
+["test_sjs_object_state.js"]
+
+["test_sjs_state.js"]
+
+["test_sjs_throwing_exceptions.js"]
+
+["test_start_stop.js"]
+
+["test_start_stop_ipv6.js"]
diff --git a/netwerk/test/marionette/manifest.toml b/netwerk/test/marionette/manifest.toml
new file mode 100644
index 0000000000..9c6355f499
--- /dev/null
+++ b/netwerk/test/marionette/manifest.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["test_purge_http_cache_at_shutdown.py"]
diff --git a/netwerk/test/marionette/test_purge_http_cache_at_shutdown.py b/netwerk/test/marionette/test_purge_http_cache_at_shutdown.py
new file mode 100644
index 0000000000..ee27e29e8d
--- /dev/null
+++ b/netwerk/test/marionette/test_purge_http_cache_at_shutdown.py
@@ -0,0 +1,125 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+from pathlib import Path
+
+from marionette_driver import Wait
+from marionette_harness import MarionetteTestCase
+
+
+class PurgeHTTPCacheAtShutdownTestCase(MarionetteTestCase):
+ def setUp(self):
+ super().setUp()
+ self.marionette.enforce_gecko_prefs(
+ {
+ "privacy.sanitize.sanitizeOnShutdown": True,
+ "privacy.clearOnShutdown.cache": True,
+ "network.cache.shutdown_purge_in_background_task": True,
+ }
+ )
+
+ self.profile_path = Path(self.marionette.profile_path)
+ self.cache_path = self.profile_path.joinpath("cache2")
+
+ def tearDown(self):
+ self.marionette.cleanup()
+ super().tearDown()
+
+ def cacheDirExists(self):
+ return self.cache_path.exists()
+
+ def renamedDirExists(self):
+ return any(
+ child.name.endswith(".purge.bg_rm") for child in self.profile_path.iterdir()
+ )
+
+ def initLockDir(self):
+ self.lock_dir = None
+ with self.marionette.using_context("chrome"):
+ path = self.marionette.execute_script(
+ """
+ return Services.dirsvc.get("UpdRootD", Ci.nsIFile).parent.parent.path;
+ """
+ )
+ self.lock_dir = Path(path)
+
+ def assertNoLocks(self):
+ locks = [
+ x
+ for x in self.lock_dir.iterdir()
+ if x.is_file() and "-cachePurge" in x.name
+ ]
+ self.assertEqual(locks, [], "All locks should have been removed")
+
+ # Tests are run in lexicographical order, so test_a* is run first to
+ # cleanup locks that may be there from previous runs.
+ def test_asetup(self):
+ self.initLockDir()
+
+ self.marionette.quit()
+
+ Wait(self.marionette, timeout=60).until(
+ lambda _: not self.cacheDirExists() and not self.renamedDirExists(),
+ message="Cache directory must be removed after orderly shutdown",
+ )
+
+ # delete locks from previous runs
+ locks = [
+ x
+ for x in self.lock_dir.iterdir()
+ if x.is_file() and "-cachePurge" in x.name
+ ]
+ for lock in locks:
+ os.remove(lock)
+ # all locks should have been removed successfully.
+ self.assertNoLocks()
+
+ def test_ensure_cache_purge_after_in_app_quit(self):
+ self.assertTrue(self.cacheDirExists(), "Cache directory must exist")
+ self.initLockDir()
+
+ self.marionette.quit()
+
+ Wait(self.marionette, timeout=60).until(
+ lambda _: not self.cacheDirExists() and not self.renamedDirExists(),
+ message="Cache directory must be removed after orderly shutdown",
+ )
+
+ self.assertNoLocks()
+
+ def test_longstanding_cache_purge_after_in_app_quit(self):
+ self.assertTrue(self.cacheDirExists(), "Cache directory must exist")
+ self.initLockDir()
+
+ self.marionette.set_pref(
+ "toolkit.background_tasks.remove_directory.testing.sleep_ms", 5000
+ )
+
+ self.marionette.quit()
+
+ Wait(self.marionette, timeout=60).until(
+ lambda _: not self.cacheDirExists() and not self.renamedDirExists(),
+ message="Cache directory must be removed after orderly shutdown",
+ )
+
+ self.assertNoLocks()
+
+ def test_ensure_cache_purge_after_forced_restart(self):
+ """
+ Doing forced restart here to prevent the shutdown phase purging and only allow startup
+ phase one, via `CacheFileIOManager::OnDelayedStartupFinished`.
+ """
+ self.profile_path.joinpath("foo.purge.bg_rm").mkdir()
+ self.initLockDir()
+
+ self.marionette.restart(in_app=False)
+
+ Wait(self.marionette, timeout=60).until(
+ lambda _: not self.renamedDirExists(),
+ message="Directories with .purge.bg_rm postfix must be removed at startup after"
+ "disorderly shutdown",
+ )
+
+ self.assertNoLocks()
diff --git a/netwerk/test/mochitests/beltzner.jpg b/netwerk/test/mochitests/beltzner.jpg
new file mode 100644
index 0000000000..75849bc40d
--- /dev/null
+++ b/netwerk/test/mochitests/beltzner.jpg
Binary files differ
diff --git a/netwerk/test/mochitests/beltzner.jpg^headers^ b/netwerk/test/mochitests/beltzner.jpg^headers^
new file mode 100644
index 0000000000..cb51f27018
--- /dev/null
+++ b/netwerk/test/mochitests/beltzner.jpg^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+Set-Cookie: mike=beltzer
+
diff --git a/netwerk/test/mochitests/empty.html b/netwerk/test/mochitests/empty.html
new file mode 100644
index 0000000000..e60f5abdf4
--- /dev/null
+++ b/netwerk/test/mochitests/empty.html
@@ -0,0 +1,16 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ This page does nothing. If the loading page managed to load this, the test
+ probably succeeded.
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_1331680.js b/netwerk/test/mochitests/file_1331680.js
new file mode 100644
index 0000000000..868d6991ae
--- /dev/null
+++ b/netwerk/test/mochitests/file_1331680.js
@@ -0,0 +1,24 @@
+/* eslint-env mozilla/chrome-script */
+
+"use strict";
+
+var observer = {
+ observe(subject, topic) {
+ if (topic == "cookie-changed") {
+ let notification = subject.QueryInterface(Ci.nsICookieNotification);
+ let cookie = notification.cookie.QueryInterface(Ci.nsICookie);
+ sendAsyncMessage("cookieName", cookie.name + "=" + cookie.value);
+ sendAsyncMessage("cookieOperation", notification.action);
+ }
+ },
+};
+
+addMessageListener("createObserver", function (e) {
+ Services.obs.addObserver(observer, "cookie-changed");
+ sendAsyncMessage("createObserver:return");
+});
+
+addMessageListener("removeObserver", function (e) {
+ Services.obs.removeObserver(observer, "cookie-changed");
+ sendAsyncMessage("removeObserver:return");
+});
diff --git a/netwerk/test/mochitests/file_1502055.sjs b/netwerk/test/mochitests/file_1502055.sjs
new file mode 100644
index 0000000000..71a4ddfa66
--- /dev/null
+++ b/netwerk/test/mochitests/file_1502055.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+ var count = parseInt(getState("count"));
+ if (!count) {
+ count = 0;
+ }
+
+ if (count == 0) {
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<html><body>Hello world!</body></html>");
+ setState("count", "1");
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, "304", "Not Modified");
+ response.setHeader("Clear-Site-Data", '"storage"');
+}
diff --git a/netwerk/test/mochitests/file_1503201.sjs b/netwerk/test/mochitests/file_1503201.sjs
new file mode 100644
index 0000000000..ac5c304103
--- /dev/null
+++ b/netwerk/test/mochitests/file_1503201.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Bearer realm="foo"');
+}
diff --git a/netwerk/test/mochitests/file_chromecommon.js b/netwerk/test/mochitests/file_chromecommon.js
new file mode 100644
index 0000000000..986a9d7ef1
--- /dev/null
+++ b/netwerk/test/mochitests/file_chromecommon.js
@@ -0,0 +1,15 @@
+/* eslint-env mozilla/chrome-script */
+
+"use strict";
+
+// eslint-disable-next-line mozilla/use-services
+let cs = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+
+addMessageListener("getCookieCountAndClear", () => {
+ let count = cs.cookies.length;
+ cs.removeAll();
+
+ sendAsyncMessage("getCookieCountAndClear:return", { count });
+});
+
+cs.removeAll();
diff --git a/netwerk/test/mochitests/file_documentcookie_maxage_chromescript.js b/netwerk/test/mochitests/file_documentcookie_maxage_chromescript.js
new file mode 100644
index 0000000000..dc1e8eb05f
--- /dev/null
+++ b/netwerk/test/mochitests/file_documentcookie_maxage_chromescript.js
@@ -0,0 +1,45 @@
+/* eslint-env mozilla/chrome-script */
+
+"use strict";
+
+function getCookieService() {
+ // eslint-disable-next-line mozilla/use-services
+ return Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+}
+
+function getCookies(cs) {
+ let cookies = [];
+ for (let cookie of cs.cookies) {
+ cookies.push({
+ host: cookie.host,
+ path: cookie.path,
+ name: cookie.name,
+ value: cookie.value,
+ expires: cookie.expires,
+ });
+ }
+ return cookies;
+}
+
+function removeAllCookies(cs) {
+ cs.removeAll();
+}
+
+addMessageListener("init", _ => {
+ let cs = getCookieService();
+ removeAllCookies(cs);
+ sendAsyncMessage("init:return");
+});
+
+addMessageListener("getCookies", _ => {
+ let cs = getCookieService();
+ let cookies = getCookies(cs);
+ removeAllCookies(cs);
+ sendAsyncMessage("getCookies:return", { cookies });
+});
+
+addMessageListener("shutdown", _ => {
+ let cs = getCookieService();
+ removeAllCookies(cs);
+ sendAsyncMessage("shutdown:return");
+});
diff --git a/netwerk/test/mochitests/file_domain_hierarchy_inner.html b/netwerk/test/mochitests/file_domain_hierarchy_inner.html
new file mode 100644
index 0000000000..713432196f
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_hierarchy_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="https://example.com/tests/netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_domain_hierarchy_inner.html^headers^ b/netwerk/test/mochitests/file_domain_hierarchy_inner.html^headers^
new file mode 100644
index 0000000000..a56be562a4
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_hierarchy_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta=tag
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html
new file mode 100644
index 0000000000..dfceac2c6c
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can2=has2";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="https://example.org/tests/netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html^headers^ b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html^headers^
new file mode 100644
index 0000000000..1ab9133473
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta2=tag2
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html
new file mode 100644
index 0000000000..d306efb1c0
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can3=has3";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.parent.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html^headers^ b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html^headers^
new file mode 100644
index 0000000000..add3336ec9
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_hierarchy_inner_inner_inner.html^headers^
@@ -0,0 +1 @@
+Set-Cookie: meta3=tag3
diff --git a/netwerk/test/mochitests/file_domain_inner.html b/netwerk/test/mochitests/file_domain_inner.html
new file mode 100644
index 0000000000..30a0821980
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="https://example.org/tests/netwerk/test/mochitests/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_domain_inner.html^headers^ b/netwerk/test/mochitests/file_domain_inner.html^headers^
new file mode 100644
index 0000000000..a56be562a4
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta=tag
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_domain_inner_inner.html b/netwerk/test/mochitests/file_domain_inner_inner.html
new file mode 100644
index 0000000000..5850e3fa0f
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_inner_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can2=has2";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_domain_inner_inner.html^headers^ b/netwerk/test/mochitests/file_domain_inner_inner.html^headers^
new file mode 100644
index 0000000000..1ab9133473
--- /dev/null
+++ b/netwerk/test/mochitests/file_domain_inner_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta2=tag2
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_iframe_allow_same_origin.html b/netwerk/test/mochitests/file_iframe_allow_same_origin.html
new file mode 100644
index 0000000000..c34187bb0a
--- /dev/null
+++ b/netwerk/test/mochitests/file_iframe_allow_same_origin.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<script>
+document.cookie = "if2_1=if2_val1";
+document.cookie = "if2_2=if2_val2";
+window.parent.postMessage(document.cookie, "*");
+</script>
+</html>
diff --git a/netwerk/test/mochitests/file_iframe_allow_scripts.html b/netwerk/test/mochitests/file_iframe_allow_scripts.html
new file mode 100644
index 0000000000..01504841a1
--- /dev/null
+++ b/netwerk/test/mochitests/file_iframe_allow_scripts.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<script>
+document.cookie = "if1=if1_val";
+</script>
+</html>
diff --git a/netwerk/test/mochitests/file_image_inner.html b/netwerk/test/mochitests/file_image_inner.html
new file mode 100644
index 0000000000..6daf7224da
--- /dev/null
+++ b/netwerk/test/mochitests/file_image_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+</head>
+<body>
+<iframe name="frame1" src="https://example.org/tests/netwerk/test/mochitests/file_image_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_image_inner.html^headers^ b/netwerk/test/mochitests/file_image_inner.html^headers^
new file mode 100644
index 0000000000..a56be562a4
--- /dev/null
+++ b/netwerk/test/mochitests/file_image_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta=tag
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_image_inner_inner.html b/netwerk/test/mochitests/file_image_inner_inner.html
new file mode 100644
index 0000000000..1e605f1a25
--- /dev/null
+++ b/netwerk/test/mochitests/file_image_inner_inner.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <link rel="stylesheet" type="text/css" media="all" href="https://example.org/tests/netwerk/test/mochitests/test1.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="https://example.com/tests/netwerk/test/mochitests/test2.css" />
+ <script type="text/javascript">
+ function runTest() {
+ document.cookie = "can2=has2";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.opener.postMessage("message", "http://mochi.test:8888");
+ }
+ </script>
+</head>
+<body>
+<img src="https://example.org/tests/netwerk/test/mochitests/image1.png" onload="runTest()" />
+<img src="https://example.com/tests/netwerk/test/mochitests/image2.png" onload="runTest()" />
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_image_inner_inner.html^headers^ b/netwerk/test/mochitests/file_image_inner_inner.html^headers^
new file mode 100644
index 0000000000..1ab9133473
--- /dev/null
+++ b/netwerk/test/mochitests/file_image_inner_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta2=tag2
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_lnk.lnk b/netwerk/test/mochitests/file_lnk.lnk
new file mode 100644
index 0000000000..abce7587d2
--- /dev/null
+++ b/netwerk/test/mochitests/file_lnk.lnk
Binary files differ
diff --git a/netwerk/test/mochitests/file_loadflags_inner.html b/netwerk/test/mochitests/file_loadflags_inner.html
new file mode 100644
index 0000000000..5fc7e8d913
--- /dev/null
+++ b/netwerk/test/mochitests/file_loadflags_inner.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ function runTest() {
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("f_lf_i msg data img", "http://mochi.test:8888");
+ }
+ </script>
+</head>
+<body onload="window.opener.postMessage('f_lf_i msg data page', 'http://mochi.test:8888');">
+<img src="http://example.org/tests/netwerk/test/mochitests/beltzner.jpg" onload="runTest()" />
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_loadflags_inner.html^headers^ b/netwerk/test/mochitests/file_loadflags_inner.html^headers^
new file mode 100644
index 0000000000..a56be562a4
--- /dev/null
+++ b/netwerk/test/mochitests/file_loadflags_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta=tag
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs b/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs
new file mode 100644
index 0000000000..0c89f7d538
--- /dev/null
+++ b/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs
@@ -0,0 +1,109 @@
+/*
+ * Redirect handler specifically for the needs of:
+ * Bug 1194052 - Append Principal to RedirectChain within LoadInfo before the channel is succesfully openend
+ */
+
+function createIframeContent(aQuery) {
+ var content = `
+ <!DOCTYPE HTML>
+ <html>
+ <head><meta charset="utf-8">
+ <title>Bug 1194052 - LoadInfo redirect chain subtest</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?${aQuery}");
+ myXHR.onload = function() {
+ var loadinfo = SpecialPowers.wrap(myXHR).channel.loadInfo;
+ var redirectChain = loadinfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = loadinfo.redirectChainIncludingInternalRedirects;
+ var resultOBJ = { redirectChain : [], redirectChainIncludingInternalRedirects : [] };
+ for (var i = 0; i < redirectChain.length; i++) {
+ resultOBJ.redirectChain.push(redirectChain[i].principal.spec);
+ }
+ for (var i = 0; i < redirectChainIncludingInternalRedirects.length; i++) {
+ resultOBJ.redirectChainIncludingInternalRedirects.push(redirectChainIncludingInternalRedirects[i].principal.spec);
+ }
+ var loadinfoJSON = JSON.stringify(resultOBJ);
+ window.parent.postMessage({ loadinfo: loadinfoJSON }, "*");
+ }
+ myXHR.onerror = function() {
+ var resultOBJ = { redirectChain : [], redirectChainIncludingInternalRedirects : [] };
+ var loadinfoJSON = JSON.stringify(resultOBJ);
+ window.parent.postMessage({ loadinfo: loadinfoJSON }, "*");
+ }
+ myXHR.send();
+ </script>
+ </body>
+ </html>`;
+
+ return content;
+}
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ var queryString = request.queryString;
+
+ if (
+ queryString == "iframe-redir-https-2" ||
+ queryString == "iframe-redir-err-2"
+ ) {
+ var query = queryString.replace("iframe-", "");
+ // send upgrade-insecure-requests CSP header
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader(
+ "Content-Security-Policy",
+ "upgrade-insecure-requests",
+ false
+ );
+ response.write(createIframeContent(query));
+ return;
+ }
+
+ // at the end of the redirectchain we return some text
+ // for sanity checking
+ if (queryString == "redir-0" || queryString == "redir-https-0") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("checking redirectchain");
+ return;
+ }
+
+ // special case redir-err-1 and return an error to trigger the fallback
+ if (queryString == "redir-err-1") {
+ response.setStatusLine("1.1", 404, "Bad request");
+ return;
+ }
+
+ // must be a redirect
+ var newLocation = "";
+ switch (queryString) {
+ case "redir-err-2":
+ newLocation =
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-1";
+ break;
+
+ case "redir-https-2":
+ newLocation =
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1";
+ break;
+
+ case "redir-https-1":
+ newLocation =
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-0";
+ break;
+
+ case "redir-2":
+ newLocation =
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-1";
+ break;
+
+ case "redir-1":
+ newLocation =
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-0";
+ break;
+ }
+
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+}
diff --git a/netwerk/test/mochitests/file_localhost_inner.html b/netwerk/test/mochitests/file_localhost_inner.html
new file mode 100644
index 0000000000..d291370a47
--- /dev/null
+++ b/netwerk/test/mochitests/file_localhost_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://mochi.test:8888/tests/netwerk/test/mochitests/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_localhost_inner.html^headers^ b/netwerk/test/mochitests/file_localhost_inner.html^headers^
new file mode 100644
index 0000000000..a56be562a4
--- /dev/null
+++ b/netwerk/test/mochitests/file_localhost_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta=tag
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_loopback_inner.html b/netwerk/test/mochitests/file_loopback_inner.html
new file mode 100644
index 0000000000..91aa1d89c5
--- /dev/null
+++ b/netwerk/test/mochitests/file_loopback_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://127.0.0.1:8888/tests/netwerk/test/mochitests/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_loopback_inner.html^headers^ b/netwerk/test/mochitests/file_loopback_inner.html^headers^
new file mode 100644
index 0000000000..a56be562a4
--- /dev/null
+++ b/netwerk/test/mochitests/file_loopback_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta=tag
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_subdomain_inner.html b/netwerk/test/mochitests/file_subdomain_inner.html
new file mode 100644
index 0000000000..a48f36a7de
--- /dev/null
+++ b/netwerk/test/mochitests/file_subdomain_inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="https://test2.example.org/tests/netwerk/test/mochitests/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_subdomain_inner.html^headers^ b/netwerk/test/mochitests/file_subdomain_inner.html^headers^
new file mode 100644
index 0000000000..a56be562a4
--- /dev/null
+++ b/netwerk/test/mochitests/file_subdomain_inner.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: meta=tag
+Cache-Control: no-cache, no-store, must-revalidate
+Pragma: no-cache
+Expires: 0
diff --git a/netwerk/test/mochitests/file_testcommon.js b/netwerk/test/mochitests/file_testcommon.js
new file mode 100644
index 0000000000..4a9b0504e9
--- /dev/null
+++ b/netwerk/test/mochitests/file_testcommon.js
@@ -0,0 +1,84 @@
+"use strict";
+
+const SCRIPT_URL = SimpleTest.getTestFileURL("file_chromecommon.js");
+
+var gExpectedCookies;
+var gExpectedLoads;
+
+var gPopup;
+
+var gScript;
+
+var gLoads = 0;
+
+function setupTest(uri, cookies, loads) {
+ SimpleTest.waitForExplicitFinish();
+
+ var prefSet = new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["network.cookie.cookieBehavior", 1],
+ // cookieBehavior 1 allows cookies from chrome script if we enable
+ // exceptions.
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ ],
+ },
+ resolve
+ );
+ });
+
+ gScript = SpecialPowers.loadChromeScript(SCRIPT_URL);
+ gExpectedCookies = cookies;
+ gExpectedLoads = loads;
+
+ // Listen for MessageEvents.
+ window.addEventListener("message", messageReceiver);
+
+ prefSet.then(() => {
+ // load a window which contains an iframe; each will attempt to set
+ // cookies from their respective domains.
+ gPopup = window.open(uri, "hai", "width=100,height=100");
+ });
+}
+
+function finishTest() {
+ gScript.destroy();
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+}
+
+/** Receives MessageEvents to this window. */
+// Count and check loads.
+function messageReceiver(evt) {
+ is(evt.data, "message", "message data received from popup");
+ if (evt.data != "message") {
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver);
+
+ finishTest();
+ return;
+ }
+
+ // only run the test when all our children are done loading & setting cookies
+ if (++gLoads == gExpectedLoads) {
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver);
+
+ runTest();
+ }
+}
+
+// runTest() is run by messageReceiver().
+// Count and check cookies.
+function runTest() {
+ // set a cookie from a domain of "localhost"
+ document.cookie = "oh=hai";
+
+ gScript.addMessageListener("getCookieCountAndClear:return", ({ count }) => {
+ is(count, gExpectedCookies, "total number of cookies");
+ finishTest();
+ });
+ gScript.sendAsyncMessage("getCookieCountAndClear");
+}
diff --git a/netwerk/test/mochitests/file_testloadflags.js b/netwerk/test/mochitests/file_testloadflags.js
new file mode 100644
index 0000000000..54374b8b8d
--- /dev/null
+++ b/netwerk/test/mochitests/file_testloadflags.js
@@ -0,0 +1,130 @@
+"use strict";
+
+const SCRIPT_URL = SimpleTest.getTestFileURL(
+ "file_testloadflags_chromescript.js"
+);
+
+let gScript;
+var gExpectedCookies;
+var gExpectedHeaders;
+var gExpectedLoads;
+
+var gObs;
+var gPopup;
+
+var gHeaders = 0;
+var gLoads = 0;
+
+// setupTest() is run from 'onload='.
+function setupTest(iframeUri, domain, cookies, loads, headers) {
+ info(
+ "setupTest uri: " +
+ iframeUri +
+ " domain: " +
+ domain +
+ " cookies: " +
+ cookies +
+ " loads: " +
+ loads +
+ " headers: " +
+ headers
+ );
+
+ SimpleTest.waitForExplicitFinish();
+
+ var prefSet = new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["network.cookie.cookieBehavior", 1],
+ ["network.cookie.sameSite.schemeful", false],
+ ],
+ },
+ resolve
+ );
+ });
+
+ gExpectedCookies = cookies;
+ gExpectedLoads = loads;
+ gExpectedHeaders = headers;
+
+ gScript = SpecialPowers.loadChromeScript(SCRIPT_URL);
+ gScript.addMessageListener("info", ({ str }) => info(str));
+ gScript.addMessageListener("ok", ({ c, m }) => ok(c, m));
+ gScript.addMessageListener("observer:gotCookie", ({ cookie, uri }) => {
+ isnot(
+ cookie.indexOf("oh=hai"),
+ -1,
+ "cookie 'oh=hai' is in header for " + uri
+ );
+ ++gHeaders;
+ });
+
+ var scriptReady = new Promise(resolve => {
+ gScript.addMessageListener("init:return", resolve);
+ gScript.sendAsyncMessage("init", { domain });
+ });
+
+ // Listen for MessageEvents.
+ window.addEventListener("message", messageReceiver);
+
+ Promise.all([prefSet, scriptReady]).then(() => {
+ // load a window which contains an iframe; each will attempt to set
+ // cookies from their respective domains.
+ gPopup = window.open(iframeUri, "hai", "width=100,height=100");
+ });
+}
+
+function finishTest() {
+ gScript.addMessageListener("shutdown:return", () => {
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+ gScript.sendAsyncMessage("shutdown");
+}
+
+/** Receives MessageEvents to this window. */
+// Count and check loads.
+function messageReceiver(evt) {
+ ok(
+ evt.data == "f_lf_i msg data img" || evt.data == "f_lf_i msg data page",
+ "message data received from popup"
+ );
+ if (evt.data == "f_lf_i msg data img") {
+ info("message data received from popup for image");
+ }
+ if (evt.data == "f_lf_i msg data page") {
+ info("message data received from popup for page");
+ }
+ if (evt.data != "f_lf_i msg data img" && evt.data != "f_lf_i msg data page") {
+ info("got this message but don't know what it is " + evt.data);
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver);
+
+ finishTest();
+ return;
+ }
+
+ // only run the test when all our children are done loading & setting cookies
+ if (++gLoads == gExpectedLoads) {
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver);
+
+ runTest();
+ }
+}
+
+// runTest() is run by messageReceiver().
+// Check headers, and count and check cookies.
+function runTest() {
+ // set a cookie from a domain of "localhost"
+ document.cookie = "o=noes";
+
+ is(gHeaders, gExpectedHeaders, "number of observed request headers");
+ gScript.addMessageListener("getCookieCount:return", ({ count }) => {
+ is(count, gExpectedCookies, "total number of cookies");
+ finishTest();
+ });
+
+ gScript.sendAsyncMessage("getCookieCount");
+}
diff --git a/netwerk/test/mochitests/file_testloadflags_chromescript.js b/netwerk/test/mochitests/file_testloadflags_chromescript.js
new file mode 100644
index 0000000000..a74920d2c2
--- /dev/null
+++ b/netwerk/test/mochitests/file_testloadflags_chromescript.js
@@ -0,0 +1,144 @@
+/* eslint-env mozilla/chrome-script */
+/* eslint-disable mozilla/use-services */
+
+"use strict";
+
+var gObs;
+
+function info(s) {
+ sendAsyncMessage("info", { str: String(s) });
+}
+
+function ok(c, m) {
+ sendAsyncMessage("ok", { c, m });
+}
+
+function is(a, b, m) {
+ ok(Object.is(a, b), m + " (" + a + " === " + b + ")");
+}
+
+// Count headers.
+function obs() {
+ info("adding observer");
+
+ this.os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ this.os.addObserver(this, "http-on-modify-request");
+}
+
+obs.prototype = {
+ observe(theSubject, theTopic, theData) {
+ info("theSubject " + theSubject);
+ info("theTopic " + theTopic);
+ info("theData " + theData);
+
+ var channel = theSubject.QueryInterface(Ci.nsIHttpChannel);
+ info("channel " + channel);
+ try {
+ info("channel.URI " + channel.URI);
+ info("channel.URI.spec " + channel.URI.spec);
+ channel.visitRequestHeaders({
+ visitHeader(aHeader, aValue) {
+ info(aHeader + ": " + aValue);
+ },
+ });
+ } catch (err) {
+ ok(false, "catch error " + err);
+ }
+
+ // Ignore notifications we don't care about (like favicons)
+ if (
+ !channel.URI.spec.includes(
+ "http://example.org/tests/netwerk/test/mochitests/"
+ )
+ ) {
+ info("ignoring this one");
+ return;
+ }
+
+ sendAsyncMessage("observer:gotCookie", {
+ cookie: channel.getRequestHeader("Cookie"),
+ uri: channel.URI.spec,
+ });
+ },
+
+ remove() {
+ info("removing observer");
+
+ this.os.removeObserver(this, "http-on-modify-request");
+ this.os = null;
+ },
+};
+
+function getCookieCount(cs) {
+ let count = 0;
+ for (let cookie of cs.cookies) {
+ info("cookie: " + cookie);
+ info(
+ "cookie host " +
+ cookie.host +
+ " path " +
+ cookie.path +
+ " name " +
+ cookie.name +
+ " value " +
+ cookie.value +
+ " isSecure " +
+ cookie.isSecure +
+ " expires " +
+ cookie.expires
+ );
+ ++count;
+ }
+
+ return count;
+}
+
+addMessageListener("init", ({ domain }) => {
+ let cs = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+
+ info("we are going to remove these cookies");
+
+ let count = getCookieCount(cs);
+ info(count + " cookies");
+
+ cs.removeAll();
+ cs.add(
+ domain,
+ "/",
+ "oh",
+ "hai",
+ false,
+ false,
+ true,
+ Math.pow(2, 62),
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ is(
+ cs.countCookiesFromHost(domain),
+ 1,
+ "number of cookies for domain " + domain
+ );
+
+ gObs = new obs();
+ sendAsyncMessage("init:return");
+});
+
+addMessageListener("getCookieCount", () => {
+ let cs = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ let count = getCookieCount(cs);
+
+ cs.removeAll();
+ sendAsyncMessage("getCookieCount:return", { count });
+});
+
+addMessageListener("shutdown", () => {
+ gObs.remove();
+
+ let cs = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ cs.removeAll();
+ sendAsyncMessage("shutdown:return");
+});
diff --git a/netwerk/test/mochitests/iframe_1502055.html b/netwerk/test/mochitests/iframe_1502055.html
new file mode 100644
index 0000000000..7f3e915978
--- /dev/null
+++ b/netwerk/test/mochitests/iframe_1502055.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script type="application/javascript">
+
+function info(msg) {
+ parent.postMessage({type: "info", msg }, "*");
+}
+
+let registration;
+
+info("Registering a ServiceWorker");
+navigator.serviceWorker.register('sw_1502055.js', {scope: "foo/"})
+.then(reg => {
+ registration = reg;
+ info("Fetching a resource");
+ return fetch("file_1502055.sjs")
+}).then(r => r.text())
+.then(() => {
+ info("Fetching a resource, again");
+ return fetch("file_1502055.sjs")
+}).then(r => r.text()).then(() => {
+ info("Unregistering the ServiceWorker");
+ return registration.unregister();
+})
+.then(() => {
+ parent.postMessage({ type: "finish" }, "*");
+});
+
+</script>
+
+</body>
+</html>
diff --git a/netwerk/test/mochitests/image1.png b/netwerk/test/mochitests/image1.png
new file mode 100644
index 0000000000..272d67c0ce
--- /dev/null
+++ b/netwerk/test/mochitests/image1.png
Binary files differ
diff --git a/netwerk/test/mochitests/image1.png^headers^ b/netwerk/test/mochitests/image1.png^headers^
new file mode 100644
index 0000000000..2390289e01
--- /dev/null
+++ b/netwerk/test/mochitests/image1.png^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+Set-Cookie: foo=bar
+
diff --git a/netwerk/test/mochitests/image2.png b/netwerk/test/mochitests/image2.png
new file mode 100644
index 0000000000..272d67c0ce
--- /dev/null
+++ b/netwerk/test/mochitests/image2.png
Binary files differ
diff --git a/netwerk/test/mochitests/image2.png^headers^ b/netwerk/test/mochitests/image2.png^headers^
new file mode 100644
index 0000000000..6c0eea5ab6
--- /dev/null
+++ b/netwerk/test/mochitests/image2.png^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+Set-Cookie: foo2=bar2
+
diff --git a/netwerk/test/mochitests/method.sjs b/netwerk/test/mochitests/method.sjs
new file mode 100644
index 0000000000..f29b20aa00
--- /dev/null
+++ b/netwerk/test/mochitests/method.sjs
@@ -0,0 +1,9 @@
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ // echo method
+ response.write(request.method);
+}
diff --git a/netwerk/test/mochitests/mochitest.toml b/netwerk/test/mochitests/mochitest.toml
new file mode 100644
index 0000000000..8d5cb8ff92
--- /dev/null
+++ b/netwerk/test/mochitests/mochitest.toml
@@ -0,0 +1,217 @@
+[DEFAULT]
+support-files = [
+ "method.sjs",
+ "partial_content.sjs",
+ "rel_preconnect.sjs",
+ "set_cookie_xhr.sjs",
+ "reset_cookie_xhr.sjs",
+ "web_packaged_app.sjs",
+ "file_documentcookie_maxage_chromescript.js",
+ "file_loadinfo_redirectchain.sjs",
+ "file_1331680.js",
+ "file_1503201.sjs",
+ "file_iframe_allow_scripts.html",
+ "file_iframe_allow_same_origin.html",
+ "redirect_idn.html^headers^",
+ "redirect_idn.html",
+ "empty.html",
+ "redirect.sjs",
+ "redirect_to.sjs",
+ "origin_header.sjs",
+ "origin_header_form_post.html",
+ "origin_header_form_post_xorigin.html",
+ "subResources.sjs",
+ "beltzner.jpg",
+ "beltzner.jpg^headers^",
+ "file_chromecommon.js",
+ "file_domain_hierarchy_inner.html",
+ "file_domain_hierarchy_inner.html^headers^",
+ "file_domain_hierarchy_inner_inner.html",
+ "file_domain_hierarchy_inner_inner.html^headers^",
+ "file_domain_hierarchy_inner_inner_inner.html",
+ "file_domain_hierarchy_inner_inner_inner.html^headers^",
+ "file_domain_inner.html",
+ "file_domain_inner.html^headers^",
+ "file_domain_inner_inner.html",
+ "file_domain_inner_inner.html^headers^",
+ "file_image_inner.html",
+ "file_image_inner.html^headers^",
+ "file_image_inner_inner.html",
+ "file_image_inner_inner.html^headers^",
+ "file_lnk.lnk",
+ "file_loadflags_inner.html",
+ "file_loadflags_inner.html^headers^",
+ "file_localhost_inner.html",
+ "file_localhost_inner.html^headers^",
+ "file_loopback_inner.html",
+ "file_loopback_inner.html^headers^",
+ "file_subdomain_inner.html",
+ "file_subdomain_inner.html^headers^",
+ "file_testcommon.js",
+ "file_testloadflags.js",
+ "file_testloadflags_chromescript.js",
+ "image1.png",
+ "image1.png^headers^",
+ "image2.png",
+ "image2.png^headers^",
+ "test1.css",
+ "test1.css^headers^",
+ "test2.css",
+ "test2.css^headers^",
+]
+prefs = ["javascript.options.large_arraybuffers=true"]
+
+["test_1331680.html"]
+
+["test_1331680_iframe.html"]
+
+["test_1331680_xhr.html"]
+skip-if = ["verify"]
+
+["test_1396395.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_1421324.html"]
+
+["test_1425031.html"]
+
+["test_1502055.html"]
+support-files = [
+ "sw_1502055.js",
+ "file_1502055.sjs",
+ "iframe_1502055.html",
+]
+
+["test_1503201.html"]
+
+["test_accept_header.html"]
+support-files = ["test_accept_header.sjs"]
+
+["test_arraybufferinputstream.html"]
+
+["test_arraybufferinputstream_large.html"]
+# Large ArrayBuffers not supported on 32-bit. TSan shadow memory causes OOMs.
+skip-if = [
+ "win11_2009 && bits == 32",
+ "tsan",
+ "asan",
+]
+
+["test_different_domain_in_hierarchy.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_differentdomain.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_documentcookies_maxage.html"]
+# Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+skip-if = ["xorigin"]
+
+["test_fetch_lnk.html"]
+
+["test_idn_redirect.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_image.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_loadflags.html"]
+# Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+skip-if = [
+ "xorigin",
+ "http3",
+ "http2",
+]
+
+["test_loadinfo_redirectchain.html"]
+fail-if = ["xorigin"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_origin_header.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_partially_cached_content.html"]
+
+["test_redirect_ref.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_rel_preconnect.html"]
+
+["test_same_base_domain.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_same_base_domain_2.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_same_base_domain_3.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_same_base_domain_4.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_same_base_domain_5.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_same_base_domain_6.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_samedomain.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_uri_scheme.html"]
+skip-if = ["verify && debug && os == 'mac'"]
+
+["test_url_perf.html"]
+# test to check the performance of URL parsing.
+# No need to run in CI.
+skip-if = ["true"]
+
+["test_viewsource_unlinkable.html"]
+
+["test_xhr_method_case.html"]
+skip-if = ["http2"]
diff --git a/netwerk/test/mochitests/origin_header.sjs b/netwerk/test/mochitests/origin_header.sjs
new file mode 100644
index 0000000000..72cc4b2ae6
--- /dev/null
+++ b/netwerk/test/mochitests/origin_header.sjs
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var origin = request.hasHeader("Origin") ? request.getHeader("Origin") : "";
+ response.write("Origin: " + origin);
+}
diff --git a/netwerk/test/mochitests/origin_header_form_post.html b/netwerk/test/mochitests/origin_header_form_post.html
new file mode 100644
index 0000000000..01c2df5ef2
--- /dev/null
+++ b/netwerk/test/mochitests/origin_header_form_post.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script>
+ function submitForm() {
+ document.getElementById("form").submit();
+ }
+ </script>
+</head>
+<body onload="submitForm()">
+ <form action="http://Mochi.test:8888/tests/netwerk/test/mochitests/origin_header.sjs"
+ method="POST"
+ id="form">
+ <input type="submit" value="Submit POST">
+ </form>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/origin_header_form_post_xorigin.html b/netwerk/test/mochitests/origin_header_form_post_xorigin.html
new file mode 100644
index 0000000000..52e173747b
--- /dev/null
+++ b/netwerk/test/mochitests/origin_header_form_post_xorigin.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script>
+ function submitForm() {
+ document.getElementById("form").submit();
+ }
+ </script>
+</head>
+<body onload="submitForm()">
+ <form action="http://test1.mochi.test:8888/tests/netwerk/test/mochitests/origin_header.sjs"
+ method="POST"
+ id="form">
+ <input type="submit" value="Submit POST">
+ </form>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/partial_content.sjs b/netwerk/test/mochitests/partial_content.sjs
new file mode 100644
index 0000000000..67bfff377a
--- /dev/null
+++ b/netwerk/test/mochitests/partial_content.sjs
@@ -0,0 +1,193 @@
+/* -*- 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/. */
+
+/* Debug and Error wrapper functions for dump().
+ */
+function ERR(response, responseCode, responseCodeStr, msg) {
+ // Reset state var.
+ setState("expectedRequestType", "");
+ // Dump to console log and send to client in response.
+ dump("SERVER ERROR: " + msg + "\n");
+ response.write("HTTP/1.1 " + responseCode + " " + responseCodeStr + "\r\n");
+ response.write("Content-Type: text/html; charset=UTF-8\r\n");
+ response.write("Content-Length: " + msg.length + "\r\n");
+ response.write("\r\n");
+ response.write(msg);
+}
+
+function DBG(msg) {
+ // Dump to console only.
+ dump("SERVER DEBUG: " + msg + "\n");
+}
+
+/* Delivers content in parts to test partially cached content: requires two
+ * requests for the same resource.
+ *
+ * First call will respond with partial content, but a 200 header and
+ * Content-Length equal to the full content length. No Range or If-Range
+ * headers are allowed in the request.
+ *
+ * Second call will require Range and If-Range in the request headers, and
+ * will respond with the range requested.
+ */
+function handleRequest(request, response) {
+ DBG("Trying to seize power");
+ response.seizePower();
+
+ DBG("About to check state vars");
+ // Get state var to determine if this is the first or second request.
+ var expectedRequestType;
+ var lastModified;
+ if (getState("expectedRequestType") === "") {
+ DBG("First call: Should be requesting full content.");
+ expectedRequestType = "fullRequest";
+ // Set state var for second request.
+ setState("expectedRequestType", "partialRequest");
+ // Create lastModified variable for responses.
+ lastModified = new Date().toUTCString();
+ setState("lastModified", lastModified);
+ } else if (getState("expectedRequestType") === "partialRequest") {
+ DBG("Second call: Should be requesting undelivered content.");
+ expectedRequestType = "partialRequest";
+ // Reset state var for first request.
+ setState("expectedRequestType", "");
+ // Get last modified date and reset state var.
+ lastModified = getState("lastModified");
+ } else {
+ ERR(
+ response,
+ 500,
+ "Internal Server Error",
+ 'Invalid expectedRequestType "' +
+ expectedRequestType +
+ '"in ' +
+ "server state db."
+ );
+ return;
+ }
+
+ // Look for Range and If-Range
+ var range = request.hasHeader("Range") ? request.getHeader("Range") : "";
+ var ifRange = request.hasHeader("If-Range")
+ ? request.getHeader("If-Range")
+ : "";
+
+ if (expectedRequestType === "fullRequest") {
+ // Should not have Range or If-Range in first request.
+ if (range && range.length) {
+ ERR(
+ response,
+ 400,
+ "Bad Request",
+ 'Should not receive "Range: ' + range + '" for first, full request.'
+ );
+ return;
+ }
+ if (ifRange && ifRange.length) {
+ ERR(
+ response,
+ 400,
+ "Bad Request",
+ 'Should not receive "Range: ' + range + '" for first, full request.'
+ );
+ return;
+ }
+ } else if (expectedRequestType === "partialRequest") {
+ // Range AND If-Range should both be present in second request.
+ if (!range) {
+ ERR(
+ response,
+ 400,
+ "Bad Request",
+ 'Should receive "Range: " for second, partial request.'
+ );
+ return;
+ }
+ if (!ifRange) {
+ ERR(
+ response,
+ 400,
+ "Bad Request",
+ 'Should receive "If-Range: " for second, partial request.'
+ );
+ return;
+ }
+ } else {
+ // Somewhat redundant, but a check for errors in this test code.
+ ERR(
+ response,
+ 500,
+ "Internal Server Error",
+ 'expectedRequestType not set correctly: "' + expectedRequestType + '"'
+ );
+ return;
+ }
+
+ // Prepare content in two parts for responses.
+ var partialContent =
+ '<html><head></head><body><p id="firstResponse">First response</p>';
+ var remainderContent =
+ '<p id="secondResponse">Second response</p></body></html>';
+ var totalLength = partialContent.length + remainderContent.length;
+
+ DBG("totalLength: " + totalLength);
+
+ // Prepare common headers for the two responses.
+ let date = new Date();
+ DBG("Date: " + date.toUTCString() + ", Last-Modified: " + lastModified);
+ var commonHeaders =
+ "Date: " +
+ date.toUTCString() +
+ "\r\n" +
+ "Last-Modified: " +
+ lastModified +
+ "\r\n" +
+ "Content-Type: text/html; charset=UTF-8\r\n" +
+ "ETag: abcd0123\r\n" +
+ "Accept-Ranges: bytes\r\n";
+
+ // Prepare specific headers and content for first and second responses.
+ if (expectedRequestType === "fullRequest") {
+ DBG("First response: Sending partial content with a full header");
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write(commonHeaders);
+ // Set Content-Length to full length of resource.
+ response.write("Content-Length: " + totalLength + "\r\n");
+ response.write("\r\n");
+ response.write(partialContent);
+ } else if (expectedRequestType === "partialRequest") {
+ DBG("Second response: Sending remaining content with a range header");
+ response.write("HTTP/1.1 206 Partial Content\r\n");
+ response.write(commonHeaders);
+ // Set Content-Length to length of bytes transmitted.
+ response.write("Content-Length: " + remainderContent.length + "\r\n");
+ response.write(
+ "Content-Range: bytes " +
+ partialContent.length +
+ "-" +
+ (totalLength - 1) +
+ "/" +
+ totalLength +
+ "\r\n"
+ );
+ response.write("\r\n");
+ response.write(remainderContent);
+ } else {
+ // Somewhat redundant, but a check for errors in this test code.
+ ERR(
+ response,
+ 500,
+ "Internal Server Error",
+ "Something very bad happened here: expectedRequestType is invalid " +
+ 'towards the end of handleRequest! - "' +
+ expectedRequestType +
+ '"'
+ );
+ return;
+ }
+
+ response.finish();
+}
diff --git a/netwerk/test/mochitests/redirect.sjs b/netwerk/test/mochitests/redirect.sjs
new file mode 100644
index 0000000000..73efea59cf
--- /dev/null
+++ b/netwerk/test/mochitests/redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", "empty.html#");
+}
diff --git a/netwerk/test/mochitests/redirect_idn.html b/netwerk/test/mochitests/redirect_idn.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/mochitests/redirect_idn.html
diff --git a/netwerk/test/mochitests/redirect_idn.html^headers^ b/netwerk/test/mochitests/redirect_idn.html^headers^
new file mode 100644
index 0000000000..753f65db87
--- /dev/null
+++ b/netwerk/test/mochitests/redirect_idn.html^headers^
@@ -0,0 +1,3 @@
+HTTP 301 Moved Permanently
+Location: http://exämple.test/tests/netwerk/test/mochitests/empty.html
+X-Comment: Bug 1142083 - This is a redirect to http://exämple.test
diff --git a/netwerk/test/mochitests/redirect_to.sjs b/netwerk/test/mochitests/redirect_to.sjs
new file mode 100644
index 0000000000..f090c70849
--- /dev/null
+++ b/netwerk/test/mochitests/redirect_to.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 308, "Permanent Redirect");
+ response.setHeader("Location", request.queryString);
+}
diff --git a/netwerk/test/mochitests/rel_preconnect.sjs b/netwerk/test/mochitests/rel_preconnect.sjs
new file mode 100644
index 0000000000..5423c877f0
--- /dev/null
+++ b/netwerk/test/mochitests/rel_preconnect.sjs
@@ -0,0 +1,17 @@
+// Generate response header "Link: <HREF>; rel=preconnect"
+// HREF is provided by the request header X-Link
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader(
+ "Link",
+ "<" +
+ request.queryString +
+ ">; rel=preconnect" +
+ ", " +
+ "<" +
+ request.queryString +
+ ">; rel=preconnect; crossOrigin=anonymous"
+ );
+ response.write("check that header");
+}
diff --git a/netwerk/test/mochitests/reset_cookie_xhr.sjs b/netwerk/test/mochitests/reset_cookie_xhr.sjs
new file mode 100644
index 0000000000..3620958d29
--- /dev/null
+++ b/netwerk/test/mochitests/reset_cookie_xhr.sjs
@@ -0,0 +1,15 @@
+function handleRequest(request, response) {
+ var queryString = request.queryString;
+ switch (queryString) {
+ case "set_cookie":
+ response.setHeader("Set-Cookie", "testXHR1=xhr_val1; path=/", false);
+ break;
+ case "modify_cookie":
+ response.setHeader(
+ "Set-Cookie",
+ "testXHR1=xhr_val2; path=/; HttpOnly",
+ false
+ );
+ break;
+ }
+}
diff --git a/netwerk/test/mochitests/set_cookie_xhr.sjs b/netwerk/test/mochitests/set_cookie_xhr.sjs
new file mode 100644
index 0000000000..19855bfec9
--- /dev/null
+++ b/netwerk/test/mochitests/set_cookie_xhr.sjs
@@ -0,0 +1,15 @@
+function handleRequest(request, response) {
+ var queryString = request.queryString;
+ switch (queryString) {
+ case "xhr1":
+ response.setHeader("Set-Cookie", "xhr1=xhr_val1; path=/", false);
+ break;
+ case "xhr2":
+ response.setHeader(
+ "Set-Cookie",
+ "xhr2=xhr_val2; path=/; HttpOnly",
+ false
+ );
+ break;
+ }
+}
diff --git a/netwerk/test/mochitests/signed_web_packaged_app.sjs b/netwerk/test/mochitests/signed_web_packaged_app.sjs
new file mode 100644
index 0000000000..dc386ad6fe
--- /dev/null
+++ b/netwerk/test/mochitests/signed_web_packaged_app.sjs
@@ -0,0 +1,73 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "application/package", false);
+ response.write(signedPackage);
+}
+
+// The package content
+// getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
+var signedPackage = `manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgEEMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MTExOTAzMDEwNVoXDTM1MTExOTAzMDEwNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPback9X7RRxKTc3/5o2vm9Ro6XNiSM9NPsN3djjCIVz50bY0rJkP98zsqpFjnLwqHeJigxyYoqFexRhRLgKrG10CxNl4rxP6CEPENjvj5FfbX/HUZpT/DelNR18F498yD95vSHcSrCc3JrjV3bKA+wgt11E4a0Ba95S1RuwtehZw1+Y4hO8nHpbSGfjD0BpluFY2nDoYAm+aWSrsmLuJsKLO8Xn2I1brZFJUynR3q1ujuDE9EJk1niDLfOeVgXM4AavJS5C0ZBHhAhR2W+K9NN97jpkpmHFqecTwDXB7rEhsyB3185cI7anaaQfHHfH5+4SD+cMDNtYIOSgLO06ZwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAlnVyLz5dPhS0ZhZD6qJOUzSo6nFwMxNX1m0oS37mevtuh0b0o1gmEuMw3mVxiAVkC2vPsoxBL2wLlAkcEdBPxGEqhBmtiBY3F3DgvEkf+/sOY1rnr6O1qLZuBAnPzA1Vnco8Jwf0DYF0PxaRd8yT5XSl5qGpM2DItEldZwuKKaL94UEgIeC2c+Uv/IOyrv+EyftX96vcmRwr8ghPFLQ+36H5nuAKEpDD170EvfWl1zs0dUPiqSI6l+hy5V14gl63Woi34L727+FKx8oatbyZtdvbeeOmenfTLifLomnZdx+3WMLkp3TLlHa5xDLwifvZtBP2d3c6zHp7gdrGU1u2WTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQQwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTEyNTAzMDQzMFowIwYJKoZIhvcNAQkEMRYEFD4ut4oKoYdcGzyfQE6ROeazv+uNMA0GCSqGSIb3DQEBAQUABIIBAFG99dKBSOzQmYVn6lHKWERVDtYXbDTIVF957ID8YH9B5unlX/PdludTNbP5dzn8GWQV08tNRgoXQ5sgxjifHunrpaR1WiR6XqvwOCBeA5NB688jxGNxth6zg6fCGFaynsYMX3FlglfIW+AYwyQUclbv+C4UORJpBjvuknOnK+UDBLVSoP9ivL6KhylYna3oFcs0SMsumc/jf/oQW51LzFHpn61TRUqdDgvGhwcjgphMhKj23KwkjwRspU2oIWNRAuhZgqDD5BJlNniCr9X5Hx1dW6tIVISO91CLAryYkGZKRJYekXctCpIvldUkIDeh2tAw5owr0jtsVd6ovFF3bV4=\r
+--NKWXJUAFXB\r
+Content-Location: manifest.webapp\r
+Content-Type: application/x-web-app-manifest+json\r
+\r
+{
+ "moz-package-origin": "http://mochi.test:8888",
+ "name": "My App",
+ "moz-resources": [
+ {
+ "src": "page2.html",
+ "integrity": "JREF3JbXGvZ+I1KHtoz3f46ZkeIPrvXtG4VyFQrJ7II="
+ },
+ {
+ "src": "index.html",
+ "integrity": "Jkvco7U8WOY9s0YREsPouX+DWK7FWlgZwA0iYYSrb7Q="
+ },
+ {
+ "src": "scripts/script.js",
+ "integrity": "6TqtNArQKrrsXEQWu3D9ZD8xvDRIkhyV6zVdTcmsT5Q="
+ },
+ {
+ "src": "scripts/library.js",
+ "integrity": "TN2ByXZiaBiBCvS4MeZ02UyNi44vED+KjdjLInUl4o8="
+ }
+ ],
+ "moz-permissions": [
+ {
+ "systemXHR": {
+ "description": "Needed to download stuff"
+ }
+ }
+ ],
+ "package-identifier": "09bc9714-7ab6-4320-9d20-fde4c237522c",
+ "description": "A great app!"
+}\r
+--NKWXJUAFXB\r
+Content-Location: page2.html\r
+Content-Type: text/html\r
+\r
+<html>
+ page2.html
+</html>
+\r
+--NKWXJUAFXB\r
+Content-Location: index.html\r
+Content-Type: text/html\r
+\r
+<html>
+ Last updated: 2015/10/28
+ <iframe id="innerFrame" src="page2.html"></iframe>
+</html>
+\r
+--NKWXJUAFXB\r
+Content-Location: scripts/script.js\r
+Content-Type: text/javascript\r
+\r
+// script.js
+\r
+--NKWXJUAFXB\r
+Content-Location: scripts/library.js\r
+Content-Type: text/javascript\r
+\r
+// library.js
+\r
+--NKWXJUAFXB--`;
diff --git a/netwerk/test/mochitests/subResources.sjs b/netwerk/test/mochitests/subResources.sjs
new file mode 100644
index 0000000000..ec2cfaa750
--- /dev/null
+++ b/netwerk/test/mochitests/subResources.sjs
@@ -0,0 +1,78 @@
+const kTwoDays = 2 * 24 * 60 * 60;
+const kInTwoDays = new Date().getTime() + kTwoDays * 1000;
+
+function getDateInTwoDays() {
+ let date2 = new Date(kInTwoDays);
+ let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ let months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ let day = date2.getUTCDate();
+ if (day < 10) {
+ day = "0" + day;
+ }
+ let month = months[date2.getUTCMonth()];
+ let year = date2.getUTCFullYear();
+ let hour = date2.getUTCHours();
+ if (hour < 10) {
+ hour = "0" + hour;
+ }
+ let minute = date2.getUTCMinutes();
+ if (minute < 10) {
+ minute = "0" + minute;
+ }
+ let second = date2.getUTCSeconds();
+ if (second < 10) {
+ second = "0" + second;
+ }
+ return (
+ days[date2.getUTCDay()] +
+ ", " +
+ day +
+ "-" +
+ month +
+ "-" +
+ year +
+ " " +
+ hour +
+ ":" +
+ minute +
+ ":" +
+ second +
+ " GMT"
+ );
+}
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ let suffix = " path=/; domain:.mochi.test";
+
+ if (aRequest.queryString.includes("3")) {
+ aResponse.setHeader(
+ "Set-Cookie",
+ "test3=value3; expires=Fri, 02-Jan-2037 00:00:01 GMT;" + suffix
+ );
+ } else if (aRequest.queryString.includes("4")) {
+ let date2 = getDateInTwoDays();
+
+ aResponse.setHeader(
+ "Set-Cookie",
+ "test4=value4; expires=" + date2 + ";" + suffix
+ );
+ }
+
+ aResponse.setHeader("Content-Type", "text/javascript", false);
+ aResponse.write("42;");
+}
diff --git a/netwerk/test/mochitests/sw_1502055.js b/netwerk/test/mochitests/sw_1502055.js
new file mode 100644
index 0000000000..40a8c178f1
--- /dev/null
+++ b/netwerk/test/mochitests/sw_1502055.js
@@ -0,0 +1 @@
+/* empty */
diff --git a/netwerk/test/mochitests/test1.css b/netwerk/test/mochitests/test1.css
new file mode 100644
index 0000000000..139597f9cb
--- /dev/null
+++ b/netwerk/test/mochitests/test1.css
@@ -0,0 +1,2 @@
+
+
diff --git a/netwerk/test/mochitests/test1.css^headers^ b/netwerk/test/mochitests/test1.css^headers^
new file mode 100644
index 0000000000..729babb5a4
--- /dev/null
+++ b/netwerk/test/mochitests/test1.css^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-cache
+Set-Cookie: css=bar
+
diff --git a/netwerk/test/mochitests/test2.css b/netwerk/test/mochitests/test2.css
new file mode 100644
index 0000000000..139597f9cb
--- /dev/null
+++ b/netwerk/test/mochitests/test2.css
@@ -0,0 +1,2 @@
+
+
diff --git a/netwerk/test/mochitests/test2.css^headers^ b/netwerk/test/mochitests/test2.css^headers^
new file mode 100644
index 0000000000..b12d32c72b
--- /dev/null
+++ b/netwerk/test/mochitests/test2.css^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-cache
+Set-Cookie: css2=bar2
+
diff --git a/netwerk/test/mochitests/test_1331680.html b/netwerk/test/mochitests/test_1331680.html
new file mode 100644
index 0000000000..30d74de0af
--- /dev/null
+++ b/netwerk/test/mochitests/test_1331680.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1331680
+-->
+<head>
+ <title>Cookies set in content processes update immediately.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1331680">Mozilla Bug 1331680</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('file_1331680.js'));
+
+// Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ["network.cookie.sameSite.laxByDefault", false],
+ ]
+}, () => {
+ gScript.addMessageListener("cookieName", confirmCookieName);
+ gScript.addMessageListener("createObserver:return", testSetCookie);
+ gScript.addMessageListener("removeObserver:return", finishTest);
+ gScript.sendAsyncMessage('createObserver');
+});
+
+var testsNum = 0;
+
+function confirmRemoveAllCookies() {
+ is(document.cookie, "", "Removed all cookies.");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+}
+
+// Confirm the notify which represents the cookie is updating.
+function confirmCookieName(name) {
+ testsNum++;
+ switch(testsNum) {
+ case 1:
+ case 3:
+ is(name, "cookie0=test1", "An update for the cookie named " + name + " was observed.");
+ break;
+ case 2:
+ is(name, "cookie2=test3", "An update for the cookie named " + name + " was observed.");
+ break;
+ case 4:
+ is(name, "cookie2=test3", "An update for the cookie named " + name + " was observed.");
+ gScript.sendAsyncMessage('removeObserver');
+ break;
+ }
+}
+
+function finishTest() {
+ is(document.cookie, "", "Removed all cookies from cookie-changed");
+ SimpleTest.finish();
+}
+
+/* Test document.cookie
+ * 1. Set a cookie and confirm the cookies which are processed from observer.
+ * 2. Set a cookie and get cookie.
+ */
+const COOKIE_NAMES = ["cookie0", "cookie1", "cookie2"];
+function testSetCookie() {
+ document.cookie = COOKIE_NAMES[0] + "=test1";
+ document.cookie = COOKIE_NAMES[1] + "=test2; HttpOnly";
+ document.cookie = COOKIE_NAMES[2] + "=test3";
+ var confirmCookieString = COOKIE_NAMES[0] + "=test1; " + COOKIE_NAMES[2] + "=test3";
+ is(document.cookie, confirmCookieString, "Confirm the cookies string which be get is current.");
+ for (var i = 0; i < COOKIE_NAMES.length; i++) {
+ document.cookie = COOKIE_NAMES[i] + "=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
+ }
+ is(document.cookie, "", "Removed all cookies.");
+}
+
+</script>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_1331680_iframe.html b/netwerk/test/mochitests/test_1331680_iframe.html
new file mode 100644
index 0000000000..85842332b1
--- /dev/null
+++ b/netwerk/test/mochitests/test_1331680_iframe.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=643051
+-->
+<head>
+ <title>Cookies set from iframe in content process</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1331680">Mozilla Bug 1331680</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+const IFRAME_COOKIE_NAMES = ["if1", "if2_1", "if2_2"];
+const ID = ["if_1", "if_2", "if_3"];
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('file_1331680.js'));
+
+/* Test iframe
+ * 1. Create three iframes, and one of the iframe will create two cookies.
+ * 2. Confirm the cookies can be proessed from observer.
+ * 3. Confirm document.cookie can get cookies "if2_1" and "if2_2".
+ * 4. Confirm the iframe whose source is "about:blank" can get parent's cookies.
+ */
+function createIframe(id, src, sandbox_flags) {
+ return new Promise(resolve => {
+ var ifr = document.createElement("iframe");
+ ifr.id = id;
+ ifr.src = src;
+ ifr.sandbox = sandbox_flags;
+ ifr.addEventListener("load", resolve);
+ document.body.appendChild(ifr);
+ });
+};
+
+function confirmCookies(id) {
+ is(document.cookie, "if2_1=if2_val1; if2_2=if2_val2", "Confirm the cookies can get after iframe was deleted");
+ var new_ifr = document.getElementById(id);
+ is(new_ifr.contentDocument.cookie, document.cookie, "Confirm the inner document.cookie = parent document.cookie");
+ document.cookie = IFRAME_COOKIE_NAMES[1] + "=; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+ document.cookie = IFRAME_COOKIE_NAMES[2] + "=; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+ is(document.cookie, "", "Removed all cookies");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+}
+
+addEventListener("message", function(event) {
+ is(event.data, document.cookie, "Confirm the iframe 2 can communicate with iframe");
+});
+
+SpecialPowers.pushPrefEnv({
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ set: [["network.cookie.sameSite.laxByDefault", false]],
+}).then(_ => createIframe(ID[0], "file_iframe_allow_scripts.html", "allow-scripts"))
+ .then(_ => createIframe(ID[1], "file_iframe_allow_same_origin.html", "allow-scripts allow-same-origin"))
+ .then(_ => createIframe(ID[2], "about:blank", "allow-scripts allow-same-origin"))
+ .then(_ => confirmCookies(ID[2]));
+
+</script>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_1331680_xhr.html b/netwerk/test/mochitests/test_1331680_xhr.html
new file mode 100644
index 0000000000..0649d33ff4
--- /dev/null
+++ b/netwerk/test/mochitests/test_1331680_xhr.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Cookie changes from XHR requests are observed in content processes.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const XHR_COOKIE_NAMES = ["xhr1", "xhr2"];
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('file_1331680.js'));
+gScript.addMessageListener("cookieName", confirmCookieName);
+gScript.addMessageListener("removeObserver:return", finishTest);
+gScript.sendAsyncMessage('createObserver');
+
+// Confirm the notify which represents the cookie is updating.
+var testsNum = 0;
+function confirmCookieName(name) {
+ testsNum++;
+ switch(testsNum) {
+ case 1:
+ is(name, "xhr1=xhr_val1", "The cookie which names " + name + " is update to db");
+ break;
+ case 2:
+ is(document.cookie, "xhr1=xhr_val1", "Confirm the cookie string");
+ for (var i = 0; i < XHR_COOKIE_NAMES.length; i++) {
+ document.cookie = XHR_COOKIE_NAMES[i] + "=; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+ }
+ break;
+ case 3:
+ is(document.cookie, "", "Confirm the cookie string");
+ gScript.sendAsyncMessage('removeObserver');
+ break;
+ }
+}
+
+function finishTest() {
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+}
+
+function createXHR(url) {
+ return new Promise(function (resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true); // async request
+ xhr.onload = function () {
+ if (this.status >= 200 && this.status < 300) {
+ resolve(xhr.response);
+ } else {
+ reject({
+ status: this.status,
+ statusText: xhr.statusText
+ });
+ }
+ };
+ xhr.onerror = function () {
+ reject({
+ status: this.status,
+ statusText: xhr.statusText
+ });
+ };
+ xhr.send();
+ });
+}
+
+/* Test XHR
+ * 1. Create two XHR.
+ * 2. One of the XHR create a cookie names "xhr1", and other one create a http-only cookie names "xhr2".
+ * 3. Child process only set xhr1 to cookies hash table.
+ * 4. Child process only can get the xhr1 cookie from cookies hash table.
+ */
+SpecialPowers.pushPrefEnv({
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ set: [["network.cookie.sameSite.laxByDefault", false]],
+}).then(_ => createXHR('set_cookie_xhr.sjs?xhr1'))
+ .then(_ => createXHR('set_cookie_xhr.sjs?xhr2'));
+
+</script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_1396395.html b/netwerk/test/mochitests/test_1396395.html
new file mode 100644
index 0000000000..dcfb952b3a
--- /dev/null
+++ b/netwerk/test/mochitests/test_1396395.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+ <iframe id="f" src="about:blank"></iframe>
+ <script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var script = SpecialPowers.loadChromeScript(() => {
+ /* eslint-env mozilla/chrome-script */
+ Services.obs.addObserver(function onExamResp(subject, topic, data) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ if (!channel.URI.spec.startsWith("http://example.org")) {
+ return;
+ }
+ Services.obs.removeObserver(onExamResp, 'http-on-examine-response');
+ channel.suspend();
+ Promise.resolve().then(() => {
+ channel.resume();
+ });
+ }, 'http-on-examine-response');
+
+ sendAsyncMessage('start-test');
+});
+
+script.addMessageListener('start-test', () => {
+ const iframe = document.getElementById('f');
+
+ iframe.contentWindow.onunload = function () {
+ info('initiate sync XHR during the page loading');
+ let xhr = new XMLHttpRequest();
+ xhr.open('GET', window.location, false);
+ xhr.send(null);
+ ok(true, 'complete without crash');
+ script.destroy();
+ SimpleTest.finish();
+ }
+
+ iframe.src = 'http://example.org';
+});
+ </script>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_1421324.html b/netwerk/test/mochitests/test_1421324.html
new file mode 100644
index 0000000000..627a339e0a
--- /dev/null
+++ b/netwerk/test/mochitests/test_1421324.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Cookie changes from XHR requests are observed in content processes.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('file_1331680.js'));
+gScript.addMessageListener("cookieName", confirmCookieName);
+gScript.addMessageListener("removeObserver:return", finishTest);
+gScript.sendAsyncMessage('createObserver');
+
+// Confirm the notify which represents the cookie is updating.
+var testsNum = 0;
+function confirmCookieName(name) {
+ testsNum++;
+ switch(testsNum) {
+ case 1:
+ is(name, "testXHR1=xhr_val1", "The cookie which names " + name + " is update to db");
+ break;
+ case 2:
+ document.cookie = "testXHR2=xhr_val2; path=/";
+ break;
+ case 3:
+ is(document.cookie, "testXHR2=xhr_val2", "Confirm the cookie string");
+ document.cookie = "testXHR1=; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+ document.cookie = "testXHR2=; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+ gScript.sendAsyncMessage('removeObserver');
+ break;
+ }
+}
+
+function finishTest() {
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+}
+
+function createXHR(url) {
+ return new Promise(function (resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true); // async request
+ xhr.onload = function () {
+ if (this.status >= 200 && this.status < 300) {
+ resolve(xhr.response);
+ } else {
+ reject({
+ status: this.status,
+ statusText: xhr.statusText
+ });
+ }
+ };
+ xhr.onerror = function () {
+ reject({
+ status: this.status,
+ statusText: xhr.statusText
+ });
+ };
+ xhr.send();
+ });
+}
+
+
+/* Test XHR
+ * 1. Create two XHR.
+ * 2. One of the XHR create a cookie names "set_cookie", and other one create a http-only cookie names "modify_cookie".
+ * 3. Child process only set testXHR1 to cookies hash table.
+ * 4. Child process only can get the testXHR1 cookie from cookies hash table.
+ */
+SpecialPowers.pushPrefEnv({
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ set: [["network.cookie.sameSite.laxByDefault", false]],
+}).then(_ => createXHR('reset_cookie_xhr.sjs?set_cookie'))
+ .then(_ => createXHR('reset_cookie_xhr.sjs?modify_cookie'));
+
+</script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_1425031.html b/netwerk/test/mochitests/test_1425031.html
new file mode 100644
index 0000000000..54865501c8
--- /dev/null
+++ b/netwerk/test/mochitests/test_1425031.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1425031
+-->
+<head>
+ <title>Cookies set in content processes update immediately.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1425031">Mozilla Bug 1425031</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<script type="application/javascript">
+
+// Verify that cookie operations initiated by content processes do not cause
+// asynchronous updates for those operations to be processed later.
+
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('file_1331680.js'));
+var testsNum = 0;
+var cookieString = "cookie0=test";
+let {COOKIE_ADDED, COOKIE_DELETED} = SpecialPowers.Ci.nsICookieNotification;
+
+// Confirm the notify which represents the cookie is updating.
+function confirmCookieOperation(op) {
+ testsNum++;
+ switch(testsNum) {
+ case 1:
+ is(op, COOKIE_ADDED, "Confirm the cookie operation is added.");
+ is(document.cookie, cookieString, "Confirm the cookie string is unaffected by the addition");
+ break;
+ case 2:
+ is(op, COOKIE_DELETED, "Confirm the cookie operation is deleted.");
+ is(document.cookie, cookieString, "Confirm the cookie string is unaffected by the deletion");
+ break;
+ case 3:
+ is(op, COOKIE_ADDED, "Confirm the cookie operation is added.");
+ is(document.cookie, cookieString, "Confirm the cookie string is unaffected by the second addition.");
+ document.cookie = "cookie0=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
+ gScript.sendAsyncMessage('removeObserver');
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+ break;
+ }
+}
+
+function testSetCookie() {
+ document.cookie = cookieString;
+ is(document.cookie, cookieString, "Confirm cookie string.");
+ document.cookie = "cookie0=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
+ is(document.cookie, "", "Removed all cookies.");
+ document.cookie = cookieString;
+ is(document.cookie, cookieString, "Confirm cookie string.");
+}
+
+// Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.sameSite.laxByDefault", false]],
+}, () => {
+ gScript.addMessageListener("cookieOperation", confirmCookieOperation);
+ gScript.addMessageListener("createObserver:return", testSetCookie);
+ gScript.sendAsyncMessage('createObserver');
+});
+
+</script>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_1502055.html b/netwerk/test/mochitests/test_1502055.html
new file mode 100644
index 0000000000..472ee204e6
--- /dev/null
+++ b/netwerk/test/mochitests/test_1502055.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Clear-Site-Data + 304 header.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+ (async () => {
+ // Grant example.org first-party storage-access to allow service workers to
+ // run in the third-party context when dFPI is enabled. This won't be
+ // necessary anymore once we enable service worker partitioning in beta and
+ // release. See Bug 1730885.
+ await SpecialPowers.pushPermissions([
+ {
+ type: "3rdPartyStorage^https://example.org",
+ allow: true,
+ context: document.location.origin,
+ },
+ ]);
+ await SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]
+ })
+ let ifr = document.createElement('iframe');
+ ifr.src = "https://example.org/tests/netwerk/test/mochitests/iframe_1502055.html";
+ document.body.appendChild(ifr);
+ addEventListener("message", e => {
+ if (e.data.type == "finish") {
+ ok(true, "Test passed");
+ SimpleTest.finish();
+ return;
+ }
+
+ if (e.data.type == "info") {
+ info(e.data.msg);
+ }
+ });
+ })();
+</script>
+
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_1503201.html b/netwerk/test/mochitests/test_1503201.html
new file mode 100644
index 0000000000..2e724f33ce
--- /dev/null
+++ b/netwerk/test/mochitests/test_1503201.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1503201
+-->
+<head>
+ <title>A WWW-Authenticate response header with an invalid realm doesn't crash the browser</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1503201">Mozilla Bug 1503201</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+fetch("file_1503201.sjs")
+ .then(() => ok(true, "no crash"))
+ .then(() => SimpleTest.finish());
+
+</script>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_accept_header.html b/netwerk/test/mochitests/test_accept_header.html
new file mode 100644
index 0000000000..0acae2a825
--- /dev/null
+++ b/netwerk/test/mochitests/test_accept_header.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Accept header</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+SimpleTest.requestCompleteLog();
+
+// All the requests are sent to test_accept_header.sjs which will return
+// different content based on the queryString. When the queryString is 'get',
+// test_accept_header.sjs returns a JSON object with the latest request and its
+// accept header value.
+
+function test_last_request_and_continue(query, expected) {
+ fetch("test_accept_header.sjs?get").then(r => r.json()).then(json => {
+ is(json.type, query, "Expected: " + query);
+ is(json.accept, expected, "Accept header: " + expected);
+ next();
+ });
+}
+
+function test_iframe() {
+ let ifr = document.createElement("iframe");
+ ifr.src = "test_accept_header.sjs?iframe";
+ ifr.onload = () => {
+ test_last_request_and_continue("iframe", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8");
+ };
+ document.body.appendChild(ifr);
+}
+
+function test_image() {
+ let i = new Image();
+ i.src = "test_accept_header.sjs?image";
+ i.onload = function() {
+ // Fetch spec says we should have: "image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"
+ test_last_request_and_continue("image", "image/avif,image/webp,*/*");
+ }
+}
+
+function test_style() {
+ let head = document.getElementsByTagName("head")[0];
+ let link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.type = "text/css";
+ link.href = "test_accept_header.sjs?style";
+ link.onload = () => {
+ test_last_request_and_continue("style", "text/css,*/*;q=0.1");
+ };
+ head.appendChild(link);
+}
+
+function test_worker() {
+ let w = new Worker("test_accept_header.sjs?worker");
+ w.onmessage = function() {
+ test_last_request_and_continue("worker", "*/*");
+ }
+}
+
+let tests = [
+ test_iframe,
+ test_image,
+ test_style,
+ test_worker,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ let test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({ "set": [
+ [ "dom.enable_performance_observer", true ]
+]}, next);
+
+</script>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_accept_header.sjs b/netwerk/test/mochitests/test_accept_header.sjs
new file mode 100644
index 0000000000..6e73acd293
--- /dev/null
+++ b/netwerk/test/mochitests/test_accept_header.sjs
@@ -0,0 +1,62 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ dump(`test_accept_header ${request.path}?${request.queryString}\n`);
+
+ if (request.queryString == "worker") {
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write("postMessage(42)");
+
+ setState(
+ "data",
+ JSON.stringify({ type: "worker", accept: request.getHeader("Accept") })
+ );
+ return;
+ }
+
+ if (request.queryString == "image") {
+ // A 1x1 PNG image.
+ // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+ const IMAGE = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+ );
+
+ response.setHeader("Content-Type", "image/png", false);
+ response.write(IMAGE);
+
+ setState(
+ "data",
+ JSON.stringify({ type: "image", accept: request.getHeader("Accept") })
+ );
+ return;
+ }
+
+ if (request.queryString == "style") {
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("");
+
+ setState(
+ "data",
+ JSON.stringify({ type: "style", accept: request.getHeader("Accept") })
+ );
+ return;
+ }
+
+ if (request.queryString == "iframe") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<h1>Hello world!</h1>");
+
+ setState(
+ "data",
+ JSON.stringify({ type: "iframe", accept: request.getHeader("Accept") })
+ );
+ return;
+ }
+
+ if (request.queryString == "get") {
+ response.setHeader("Content-Type", "application/json", false);
+ response.write(getState("data"));
+
+ setState("data", "");
+ }
+}
diff --git a/netwerk/test/mochitests/test_arraybufferinputstream.html b/netwerk/test/mochitests/test_arraybufferinputstream.html
new file mode 100644
index 0000000000..5e3c5faa3e
--- /dev/null
+++ b/netwerk/test/mochitests/test_arraybufferinputstream.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>ArrayBuffer stream test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+function detachArrayBuffer(ab)
+{
+ var w = new Worker("data:application/javascript,");
+ w.postMessage(ab, [ab]);
+}
+
+function test()
+{
+ var ab = new ArrayBuffer(4000);
+ var ta = new Uint8Array(ab);
+ ta[0] = 'a'.charCodeAt(0);
+ ta[1] = 'b'.charCodeAt(0);
+
+ const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci;
+ var abis = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
+ .createInstance(Ci.nsIArrayBufferInputStream);
+
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(abis);
+
+ is(sis.read(1), "", "should read no data from an uninitialized ABIS");
+
+ abis.setData(ab, 0, 256 * 1024);
+
+ is(sis.read(1), "a", "should read 'a' after init");
+
+ detachArrayBuffer(ab);
+
+ SpecialPowers.forceGC();
+ SpecialPowers.forceGC();
+
+ try
+ {
+ is(sis.read(1), "b", "should read 'b' after detaching buffer");
+ }
+ catch (e)
+ {
+ ok(false, "reading from stream should have worked");
+ }
+
+ // A regression test for bug 1265076. Previously, overflowing
+ // the internal buffer from readSegments would cause incorrect
+ // copying. The constant mirrors the value in
+ // ArrayBufferInputStream::readSegments.
+ var size = 8192;
+ ab = new ArrayBuffer(2 * size);
+ ta = new Uint8Array(ab);
+
+ var i;
+ for (i = 0; i < size; ++i) {
+ ta[i] = 'x'.charCodeAt(0);
+ }
+ for (i = 0; i < size; ++i) {
+ ta[size + i] = 'y'.charCodeAt(0);
+ }
+
+ abis = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
+ .createInstance(Ci.nsIArrayBufferInputStream);
+ abis.setData(ab, 0, 2 * size);
+
+ sis = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(abis);
+
+ var result = sis.read(2 * size);
+ is(result, "x".repeat(size) + "y".repeat(size), "correctly read the data");
+}
+
+test();
+</script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_arraybufferinputstream_large.html b/netwerk/test/mochitests/test_arraybufferinputstream_large.html
new file mode 100644
index 0000000000..33922d6e37
--- /dev/null
+++ b/netwerk/test/mochitests/test_arraybufferinputstream_large.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ArrayBuffer stream with large ArrayBuffer test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+add_task(async function testLargeArrayBuffer() {
+ let ab = new ArrayBuffer(4.5 * 1024 * 1024 * 1024); // 4.5 GB.
+ let ta = new Uint8Array(ab);
+
+ const { Cc, Ci } = SpecialPowers;
+ let abis = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
+ .createInstance(Ci.nsIArrayBufferInputStream);
+
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(abis);
+
+ // The stream currently doesn't support more than UINT32_MAX bytes.
+ let ex;
+ try {
+ abis.setData(ab, 0, ab.byteLength);
+ } catch (e) {
+ ex = e;
+ }
+ is(ex.message.includes("NS_ERROR_ILLEGAL_VALUE"), true, "Expecting exception");
+
+ // Reading a small slice of the large ArrayBuffer is fine, even near the end.
+ ta[ta.length - 10] = "a".charCodeAt(0);
+ ta[ta.length - 9] = "b".charCodeAt(0);
+ ta[ta.length - 8] = "c".charCodeAt(0);
+ abis.setData(ab, ab.byteLength - 10, 2);
+ is(sis.read(1), "a", "should read 'a' after init");
+ is(sis.read(1), "b", "should read 'b' after 'a'");
+ is(sis.read(1), "", "Should be done reading data");
+});
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_different_domain_in_hierarchy.html b/netwerk/test/mochitests/test_different_domain_in_hierarchy.html
new file mode 100644
index 0000000000..0ec6d35d4d
--- /dev/null
+++ b/netwerk/test/mochitests/test_different_domain_in_hierarchy.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test cookie requests from within a window hierarchy of different base domains</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('https://example.org/tests/netwerk/test/mochitests/file_domain_hierarchy_inner.html', 4, 3)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_differentdomain.html b/netwerk/test/mochitests/test_differentdomain.html
new file mode 100644
index 0000000000..75cc903758
--- /dev/null
+++ b/netwerk/test/mochitests/test_differentdomain.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('https://example.com/tests/netwerk/test/mochitests/file_domain_inner.html', 3, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_documentcookies_maxage.html b/netwerk/test/mochitests/test_documentcookies_maxage.html
new file mode 100644
index 0000000000..eb130b2fed
--- /dev/null
+++ b/netwerk/test/mochitests/test_documentcookies_maxage.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for document.cookie max-age pref</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+const kTwoDays = 2 * 24 * 60 * 60;
+const kSevenDays = 7 * 24 * 60 * 60;
+const kInTwoDays = (new Date().getTime() + kTwoDays * 1000);
+const kInSevenDays = (new Date().getTime() + kSevenDays * 1000);
+const kScriptURL = SimpleTest.getTestFileURL("file_documentcookie_maxage_chromescript.js");
+
+let gScript;
+
+function getDateInTwoDays()
+{
+ let date2 = new Date(kInTwoDays);
+ let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
+ "Nov", "Dec"];
+ let day = date2.getUTCDate();
+ if (day < 10) {
+ day = "0" + day;
+ }
+ let month = months[date2.getUTCMonth()];
+ let year = date2.getUTCFullYear();
+ let hour = date2.getUTCHours();
+ if (hour < 10) {
+ hour = "0" + hour;
+ }
+ let minute = date2.getUTCMinutes();
+ if (minute < 10) {
+ minute = "0" + minute;
+ }
+ let second = date2.getUTCSeconds();
+ if (second < 10) {
+ second = "0" + second;
+ }
+ return days[date2.getUTCDay()] + ", " + day + "-" + month + "-" +
+ year + " " + hour + ":" + minute + ":" + second + " GMT";
+}
+
+function dotest()
+{
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.documentCookies.maxage", kSevenDays]],
+ }).then(_ => {
+ gScript = SpecialPowers.loadChromeScript(kScriptURL);
+
+ return new Promise(resolve => {
+ gScript.addMessageListener("init:return", resolve);
+ gScript.sendAsyncMessage("init");
+ });
+ }).then(_ => {
+ let date2 = getDateInTwoDays();
+
+ document.cookie = "test1=value1; expires=Fri, 02-Jan-2037 00:00:01 GMT;";
+ document.cookie = "test2=value2; expires=" + date2 + ";";
+
+ return fetch("subResources.sjs?3");
+ }).then(_ => {
+ return fetch("subResources.sjs?4");
+ }).then(_ => {
+ return new Promise(resolve => {
+ gScript.addMessageListener("getCookies:return", resolve);
+ gScript.sendAsyncMessage("getCookies");
+ });
+ }).then(_ => {
+ for (let cookie of _.cookies) {
+ switch (cookie.name) {
+ case "test1": {
+ is(cookie.value, "value1", "The correct value expected");
+ let d = new Date(cookie.expires * 1000);
+ let [day, month, year] = [d.getUTCDate(), d.getUTCMonth(), d.getUTCFullYear()];
+ let d2 = new Date(kInSevenDays);
+ let [day2, month2, year2] = [d2.getUTCDate(), d2.getUTCMonth(), d2.getUTCFullYear()];
+ is(day, day2, "Days match");
+ is(month, month2, "Months match");
+ is(year, year2, "Years match");
+ }
+ break;
+
+ case "test2": {
+ is(cookie.value, "value2", "The correct value expected");
+ let d = new Date(cookie.expires * 1000);
+ let [day, month, year] = [d.getUTCDate(), d.getUTCMonth(), d.getUTCFullYear()];
+ let d2 = new Date(kInTwoDays);
+ let [day2, month2, year2] = [d2.getUTCDate(), d2.getUTCMonth(), d2.getUTCFullYear()];
+ is(day, day2, "Days match");
+ is(month, month2, "Months match");
+ is(year, year2, "Years match");
+ }
+ break;
+
+ case "test3": {
+ is(cookie.value, "value3", "The correct value expected");
+ let d = new Date(cookie.expires * 1000);
+ let [day, month, year] = [d.getUTCDate(), d.getUTCMonth(), d.getUTCFullYear()];
+ let d2 = new Date("Fri, 02 Jan 2037 00:00:01 GMT");
+ let [day2, month2, year2] = [d2.getUTCDate(), d2.getUTCMonth(), d2.getUTCFullYear()];
+ is(day, day2, "Days match");
+ is(month, month2, "Months match");
+ is(year, year2, "Years match");
+ }
+ break;
+
+ case "test4": {
+ is(cookie.value, "value4", "The correct value expected");
+ let d = new Date(cookie.expires * 1000);
+ let [day, month, year] = [d.getUTCDate(), d.getUTCMonth(), d.getUTCFullYear()];
+ let d2 = new Date(kInTwoDays);
+ let [day2, month2, year2] = [d2.getUTCDate(), d2.getUTCMonth(), d2.getUTCFullYear()];
+ is(day, day2, "Days match");
+ is(month, month2, "Months match");
+ is(year, year2, "Years match");
+ }
+ break;
+
+ default:
+ ok(false, "Unexpected cookie found!");
+ break;
+ }
+ }
+
+ return new Promise(resolve => {
+ gScript.addMessageListener("shutdown:return", resolve);
+ gScript.sendAsyncMessage("shutdown");
+ });
+ }).then(finish);
+}
+
+function finish()
+{
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="dotest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_fetch_lnk.html b/netwerk/test/mochitests/test_fetch_lnk.html
new file mode 100644
index 0000000000..e1154a5951
--- /dev/null
+++ b/netwerk/test/mochitests/test_fetch_lnk.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Downloading .lnk through HTTP should always download the file without parsing it</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ SimpleTest.waitForExplicitFinish();
+ // Download .lnk which points to a system executable
+ fetch("file_lnk.lnk").then(async res => {
+ ok(res.ok, "Download success");
+ ok(res.url.endsWith("file_lnk.lnk"), "file name should be of the lnk file");
+ is(res.headers.get("Content-Length"), "1531", "The size should be of the lnk file");
+ SimpleTest.finish();
+ }, () => {
+ ok(false, "Unreachable code");
+ })
+</script>
diff --git a/netwerk/test/mochitests/test_idn_redirect.html b/netwerk/test/mochitests/test_idn_redirect.html
new file mode 100644
index 0000000000..cc920665b3
--- /dev/null
+++ b/netwerk/test/mochitests/test_idn_redirect.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Bug 1142083 - IDN Unicode domain redirect is broken
+ This test loads redirectme.html which is redirected simple_test.html, on a different IDN domain.
+ A message is posted to that page, with responds with another.
+ Upon receiving that message, we consider that the IDN redirect has functioned properly, since the intended page was loaded.
+-->
+<head>
+ <title>Test for URI Manipulation</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var iframe = document.createElement("iframe");
+iframe.src = "about:blank";
+iframe.addEventListener("load", finishTest);
+document.body.appendChild(iframe);
+iframe.src = "http://mochi.test:8888/tests/netwerk/test/mochitests/redirect_idn.html";
+
+function finishTest(e) {
+ ok(true);
+ SimpleTest.finish();
+}
+
+</script>
+
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_image.html b/netwerk/test/mochitests/test_image.html
new file mode 100644
index 0000000000..db75cdbca5
--- /dev/null
+++ b/netwerk/test/mochitests/test_image.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('https://example.org/tests/netwerk/test/mochitests/file_image_inner.html', 7, 3)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js"></script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_loadflags.html b/netwerk/test/mochitests/test_loadflags.html
new file mode 100644
index 0000000000..fba93102ea
--- /dev/null
+++ b/netwerk/test/mochitests/test_loadflags.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<!--
+ *5 cookies: 1+1 from file_testloadflags.js, 2 from file_loadflags_inner.html + 1 from beltzner.jpg.
+ *1 load: file_loadflags_inner.html.
+ *2 headers: 1 for file_loadflags_inner.html + 1 for beltzner.jpg.
+ -->
+<body onload="setupTest('http://example.org/tests/netwerk/test/mochitests/file_loadflags_inner.html', 'example.org', 5, 2, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testloadflags.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_loadinfo_redirectchain.html b/netwerk/test/mochitests/test_loadinfo_redirectchain.html
new file mode 100644
index 0000000000..a657ce678c
--- /dev/null
+++ b/netwerk/test/mochitests/test_loadinfo_redirectchain.html
@@ -0,0 +1,269 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1194052 - Append Principal to RedirectChain within LoadInfo before the channel is succesfully openend</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * We perform the following tests on the redirectchain of the loadinfo:
+ * (1) checkLoadInfoWithoutRedirects:
+ * checks the length of the redirectchain and tries to pop an element
+ * which should result in an exception and not a crash.
+ * (2) checkLoadInfoWithTwoRedirects:
+ * perform two redirects and confirm that both redirect chains
+ * contain the redirected URIs.
+ * (3) checkLoadInfoWithInternalRedirects:
+ * perform two redirects including CSPs upgrade-insecure-requests
+ * so that the redirectchain which includes internal redirects differs.
+ * (4) checkLoadInfoWithInternalRedirectsAndFallback
+ * perform two redirects including CSPs upgrade-insecure-requests
+ * including a 404 repsonse and hence a fallback.
+ * (5) checkHTTPURITruncation
+ * perform a redirect to a URI with an HTTP scheme to check that unwanted
+ * URI components are removed before being added to the redirectchain.
+ * (6) checkHTTPSURITruncation
+ * perform a redirect to a URI with an HTTPS scheme to check that unwanted
+ * URI components are removed before being added to the redirectchain.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+// ************** HELPERS ***************
+
+function compareChains(aLoadInfo, aExpectedRedirectChain, aExpectedRedirectChainIncludingInternalRedirects) {
+ var redirectChain = aLoadInfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = aLoadInfo.redirectChainIncludingInternalRedirects;
+
+ is(redirectChain.length,
+ aExpectedRedirectChain.length,
+ "confirming length of redirectChain is " + aExpectedRedirectChain.length);
+
+ is(redirectChainIncludingInternalRedirects.length,
+ aExpectedRedirectChainIncludingInternalRedirects.length,
+ "confirming length of redirectChainIncludingInternalRedirects is " +
+ aExpectedRedirectChainIncludingInternalRedirects.length);
+}
+
+function compareTruncatedChains(redirectChain, aExpectedRedirectChain) {
+ is(redirectChain.length,
+ aExpectedRedirectChain.length,
+ "confirming length of redirectChain is " + aExpectedRedirectChain.length);
+
+ for (var i = 0; i < redirectChain.length; i++) {
+ is(redirectChain[i],
+ aExpectedRedirectChain[i],
+ "redirect chain should match expected chain");
+ }
+}
+
+
+// *************** TEST 1 ***************
+
+function checkLoadInfoWithoutRedirects() {
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-0");
+
+ myXHR.onload = function() {
+ var loadinfo = SpecialPowers.wrap(myXHR).channel.loadInfo;
+ var redirectChain = loadinfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = loadinfo.redirectChainIncludingInternalRedirects;
+
+ is(redirectChain.length, 0, "no redirect, length should be 0");
+ is(redirectChainIncludingInternalRedirects.length, 0, "no redirect, length should be 0");
+ is(myXHR.responseText, "checking redirectchain", "sanity check to make sure redirects succeeded");
+
+ // try to pop an element from redirectChain
+ try {
+ loadinfo.popRedirectedPrincipal(false);
+ ok(false, "should not be possible to pop from redirectChain");
+ }
+ catch(e) {
+ ok(true, "popping element from empty redirectChain should throw");
+ }
+
+ // try to pop an element from redirectChainIncludingInternalRedirects
+ try {
+ loadinfo.popRedirectedPrincipal(true);
+ ok(false, "should not be possible to pop from redirectChainIncludingInternalRedirects");
+ }
+ catch(e) {
+ ok(true, "popping element from empty redirectChainIncludingInternalRedirects should throw");
+ }
+ // move on to the next test
+ checkLoadInfoWithTwoRedirects();
+ }
+ myXHR.onerror = function() {
+ ok(false, "xhr problem within checkLoadInfoWithoutRedirect()");
+ }
+ myXHR.send();
+}
+
+// *************** TEST 2 ***************
+
+function checkLoadInfoWithTwoRedirects() {
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-2");
+
+ const EXPECTED_REDIRECT_CHAIN = [
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs",
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs"
+ ];
+
+ const EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS = EXPECTED_REDIRECT_CHAIN;
+
+ // Referrer header will not change when redirect
+ const EXPECTED_REFERRER =
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/test_loadinfo_redirectchain.html";
+ const isAndroid = !!navigator.userAgent.includes("Android");
+ const EXPECTED_REMOTE_IP = isAndroid ? "10.0.2.2" : "127.0.0.1";
+
+ myXHR.onload = function() {
+ is(myXHR.responseText, "checking redirectchain", "sanity check to make sure redirects succeeded");
+
+ var loadinfo = SpecialPowers.wrap(myXHR).channel.loadInfo;
+
+ compareChains(loadinfo, EXPECTED_REDIRECT_CHAIN, EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS);
+
+ for (var i = 0; i < loadinfo.redirectChain.length; i++) {
+ is(loadinfo.redirectChain[i].referrerURI.spec, EXPECTED_REFERRER, "referrer should match");
+ is(loadinfo.redirectChain[i].remoteAddress, EXPECTED_REMOTE_IP, "remote address should match");
+ }
+
+ // move on to the next test
+ checkLoadInfoWithInternalRedirects();
+ }
+ myXHR.onerror = function() {
+ ok(false, "xhr problem within checkLoadInfoWithTwoRedirects()");
+ }
+ myXHR.send();
+}
+
+// *************** TEST 3 ***************
+
+function confirmCheckLoadInfoWithInternalRedirects(event) {
+ const EXPECTED_REDIRECT_CHAIN = [
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-2",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1"
+ ];
+
+ const EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS = [
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-2",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-2",
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1",
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-0",
+ ];
+
+ var loadinfo = JSON.parse(event.data.loadinfo);
+ compareChains(loadinfo, EXPECTED_REDIRECT_CHAIN, EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS);
+
+ // remove the postMessage listener and move on to the next test
+ window.removeEventListener("message", confirmCheckLoadInfoWithInternalRedirects);
+ checkLoadInfoWithInternalRedirectsAndFallback();
+}
+
+function checkLoadInfoWithInternalRedirects() {
+ // load the XHR request into an iframe so we can apply a CSP to the iframe
+ // a postMessage returns the result back to the main page.
+ window.addEventListener("message", confirmCheckLoadInfoWithInternalRedirects);
+ document.getElementById("testframe").src =
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?iframe-redir-https-2";
+}
+
+// *************** TEST 4 ***************
+
+function confirmCheckLoadInfoWithInternalRedirectsAndFallback(event) {
+ var EXPECTED_REDIRECT_CHAIN = [
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-2",
+ ];
+
+ var EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS = [
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-2",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-2",
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-1",
+ ];
+
+ var loadinfo = JSON.parse(event.data.loadinfo);
+ compareChains(loadinfo, EXPECTED_REDIRECT_CHAIN, EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS);
+
+ // remove the postMessage listener and finish test
+ window.removeEventListener("message", confirmCheckLoadInfoWithInternalRedirectsAndFallback);
+ checkHTTPURITruncation();
+}
+
+function checkLoadInfoWithInternalRedirectsAndFallback() {
+ // load the XHR request into an iframe so we can apply a CSP to the iframe
+ // a postMessage returns the result back to the main page.
+ window.addEventListener("message", confirmCheckLoadInfoWithInternalRedirectsAndFallback);
+ document.getElementById("testframe").src =
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?iframe-redir-err-2";
+}
+
+// *************** TEST 5 ***************
+
+function checkHTTPURITruncation() {
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://root:toor@mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-1#baz");
+
+ const EXPECTED_REDIRECT_CHAIN = [
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs", // redir-1
+ ];
+
+ var loadinfo = SpecialPowers.wrap(myXHR).channel.loadInfo;
+
+ myXHR.onload = function() {
+ var redirectChain = [];
+
+ for (var i = 0; i < loadinfo.redirectChain.length; i++) {
+ redirectChain[i] = loadinfo.redirectChain[i].principal.asciiSpec;
+ }
+
+ compareTruncatedChains(redirectChain, EXPECTED_REDIRECT_CHAIN);
+
+ // move on to the next test
+ checkHTTPSURITruncation();
+ }
+ myXHR.onerror = function(e) {
+ ok(false, "xhr problem within checkHTTPURITruncation()" + e);
+ }
+ myXHR.send();
+}
+
+// *************** TEST 6 ***************
+
+function confirmCheckHTTPSURITruncation(event) {
+ const EXPECTED_REDIRECT_CHAIN = [
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs", // redir-https-2
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs", // redir-https-1
+ ];
+
+ var loadinfo = JSON.parse(event.data.loadinfo);
+ compareTruncatedChains(loadinfo.redirectChain, EXPECTED_REDIRECT_CHAIN);
+
+ // remove the postMessage listener and move on to the next test
+ window.removeEventListener("message", confirmCheckHTTPSURITruncation);
+ SimpleTest.finish();
+}
+
+function checkHTTPSURITruncation() {
+ // load the XHR request into an iframe so we can apply a CSP to the iframe
+ // a postMessage returns the result back to the main page.
+ window.addEventListener("message", confirmCheckHTTPSURITruncation);
+ document.getElementById("testframe").src =
+ "https://root:toor@example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?iframe-redir-https-2#baz";
+}
+
+// *************** START TESTS ***************
+
+checkLoadInfoWithoutRedirects();
+
+</script>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_origin_header.html b/netwerk/test/mochitests/test_origin_header.html
new file mode 100644
index 0000000000..f90887ddf0
--- /dev/null
+++ b/netwerk/test/mochitests/test_origin_header.html
@@ -0,0 +1,398 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <title> Bug 446344 - Test Origin Header</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=446344">Mozilla Bug 446344</a></p>
+
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+const EMPTY_ORIGIN = "Origin: ";
+
+let testsToRun = [
+ {
+ name: "sendOriginHeader=0 (never)",
+ prefs: [
+ ["network.http.sendOriginHeader", 0],
+ ],
+ results: {
+ framePost: EMPTY_ORIGIN,
+ framePostXOrigin: EMPTY_ORIGIN,
+ frameGet: EMPTY_ORIGIN,
+ framePostNonSandboxed: EMPTY_ORIGIN,
+ framePostNonSandboxedXOrigin: EMPTY_ORIGIN,
+ framePostSandboxed: EMPTY_ORIGIN,
+ framePostSrcDoc: EMPTY_ORIGIN,
+ framePostSrcDocXOrigin: EMPTY_ORIGIN,
+ framePostDataURI: EMPTY_ORIGIN,
+ framePostSameOriginToXOrigin: EMPTY_ORIGIN,
+ framePostXOriginToSameOrigin: EMPTY_ORIGIN,
+ framePostXOriginToXOrigin: EMPTY_ORIGIN,
+ },
+ },
+ {
+ name: "sendOriginHeader=1 (same-origin)",
+ prefs: [
+ ["network.http.sendOriginHeader", 1],
+ ],
+ results: {
+ framePost: "Origin: http://mochi.test:8888",
+ framePostXOrigin: "Origin: null",
+ frameGet: EMPTY_ORIGIN,
+ framePostNonSandboxed: "Origin: http://mochi.test:8888",
+ framePostNonSandboxedXOrigin: "Origin: null",
+ framePostSandboxed: "Origin: null",
+ framePostSrcDoc: "Origin: http://mochi.test:8888",
+ framePostSrcDocXOrigin: "Origin: null",
+ framePostDataURI: "Origin: null",
+ framePostSameOriginToXOrigin: "Origin: null",
+ framePostXOriginToSameOrigin: "Origin: null",
+ framePostXOriginToXOrigin: "Origin: null",
+ },
+ },
+ {
+ name: "sendOriginHeader=2 (always)",
+ prefs: [
+ ["network.http.sendOriginHeader", 2],
+ ],
+ results: {
+ framePost: "Origin: http://mochi.test:8888",
+ framePostXOrigin: "Origin: http://mochi.test:8888",
+ frameGet: EMPTY_ORIGIN,
+ framePostNonSandboxed: "Origin: http://mochi.test:8888",
+ framePostNonSandboxedXOrigin: "Origin: http://mochi.test:8888",
+ framePostSandboxed: "Origin: null",
+ framePostSrcDoc: "Origin: http://mochi.test:8888",
+ framePostSrcDocXOrigin: "Origin: http://mochi.test:8888",
+ framePostDataURI: "Origin: null",
+ framePostSameOriginToXOrigin: "Origin: http://mochi.test:8888",
+ framePostXOriginToSameOrigin: "Origin: null",
+ framePostXOriginToXOrigin: "Origin: http://mochi.test:8888",
+ },
+ },
+ {
+ name: "sendRefererHeader=0 (never)",
+ prefs: [
+ ["network.http.sendRefererHeader", 0],
+ ],
+ results: {
+ framePost: "Origin: http://mochi.test:8888",
+ framePostXOrigin: "Origin: http://mochi.test:8888",
+ frameGet: EMPTY_ORIGIN,
+ framePostNonSandboxed: "Origin: http://mochi.test:8888",
+ framePostNonSandboxedXOrigin: "Origin: http://mochi.test:8888",
+ framePostSandboxed: "Origin: null",
+ framePostSrcDoc: "Origin: http://mochi.test:8888",
+ framePostSrcDocXOrigin: "Origin: http://mochi.test:8888",
+ framePostDataURI: "Origin: null",
+ framePostSameOriginToXOrigin: "Origin: http://mochi.test:8888",
+ framePostXOriginToSameOrigin: "Origin: null",
+ framePostXOriginToXOrigin: "Origin: http://mochi.test:8888",
+ },
+ },
+ {
+ name: "userControlPolicy=0 (no-referrer)",
+ prefs: [
+ ["network.http.sendRefererHeader", 2],
+ ["network.http.referer.defaultPolicy", 0],
+ ],
+ results: {
+ framePost: "Origin: null",
+ framePostXOrigin: "Origin: null",
+ frameGet: EMPTY_ORIGIN,
+ framePostNonSandboxed: "Origin: null",
+ framePostNonSandboxedXOrigin: "Origin: null",
+ framePostSandboxed: "Origin: null",
+ framePostSrcDoc: "Origin: null",
+ framePostSrcDocXOrigin: "Origin: null",
+ framePostDataURI: "Origin: null",
+ framePostSameOriginToXOrigin: "Origin: null",
+ framePostXOriginToSameOrigin: "Origin: null",
+ framePostXOriginToXOrigin: "Origin: null",
+ },
+ },
+];
+
+let checksToRun = [
+ {
+ name: "POST",
+ frameID: "framePost",
+ formID: "formPost",
+ },
+ {
+ name: "cross-origin POST",
+ frameID: "framePostXOrigin",
+ formID: "formPostXOrigin",
+ },
+ {
+ name: "GET",
+ frameID: "frameGet",
+ formID: "formGet",
+ },
+ {
+ name: "POST inside iframe",
+ frameID: "framePostNonSandboxed",
+ frameSrc: "HTTP://mochi.test:8888/tests/netwerk/test/mochitests/origin_header_form_post.html",
+ },
+ {
+ name: "cross-origin POST inside iframe",
+ frameID: "framePostNonSandboxedXOrigin",
+ frameSrc: "Http://mochi.test:8888/tests/netwerk/test/mochitests/origin_header_form_post_xorigin.html",
+ },
+ {
+ name: "POST inside sandboxed iframe",
+ frameID: "framePostSandboxed",
+ frameSrc: "http://mochi.test:8888/tests/netwerk/test/mochitests/origin_header_form_post.html",
+ },
+ {
+ name: "POST inside a srcdoc iframe",
+ frameID: "framePostSrcDoc",
+ srcdoc: "origin_header_form_post.html",
+ },
+ {
+ name: "cross-origin POST inside a srcdoc iframe",
+ frameID: "framePostSrcDocXOrigin",
+ srcdoc: "origin_header_form_post_xorigin.html",
+ },
+ {
+ name: "POST inside a data: iframe",
+ frameID: "framePostDataURI",
+ dataURI: "origin_header_form_post.html",
+ },
+ {
+ name: "same-origin POST redirected to cross-origin",
+ frameID: "framePostSameOriginToXOrigin",
+ formID: "formPostSameOriginToXOrigin",
+ },
+ {
+ name: "cross-origin POST redirected to same-origin",
+ frameID: "framePostXOriginToSameOrigin",
+ formID: "formPostXOriginToSameOrigin",
+ },
+ {
+ name: "cross-origin POST redirected to cross-origin",
+ frameID: "framePostXOriginToXOrigin",
+ formID: "formPostXOriginToXOrigin",
+ },
+];
+
+function frameLoaded(test, check)
+{
+ let frame = window.document.getElementById(check.frameID);
+ frame.onload = null;
+ let result = SpecialPowers.wrap(frame).contentDocument.documentElement.textContent;
+ is(result, test.results[check.frameID], check.name + " with " + test.name);
+}
+
+function submitForm(test, check)
+{
+ return new Promise((resolve, reject) => {
+ document.getElementById(check.frameID).onload = () => {
+ frameLoaded(test, check);
+ resolve();
+ };
+ document.getElementById(check.formID).submit();
+ });
+}
+
+function loadIframe(test, check)
+{
+ return new Promise((resolve, reject) => {
+ let frame = SpecialPowers.wrap(window.document.getElementById(check.frameID));
+ frame.onload = function () {
+ // Ignore the first load and wait for the submitted form instead.
+ let location = frame.contentWindow.location + "";
+ if (location.endsWith("origin_header.sjs")) {
+ frameLoaded(test, check);
+ resolve();
+ }
+ }
+ frame.src = check.frameSrc;
+ });
+}
+
+function loadSrcDocFrame(test, check)
+{
+ return new Promise((resolve, reject) => {
+ let frame = SpecialPowers.wrap(window.document.getElementById(check.frameID));
+ frame.onload = function () {
+ // Ignore the first load and wait for the submitted form instead.
+ let location = frame.contentWindow.location + "";
+ if (location.endsWith("origin_header.sjs")) {
+ frameLoaded(test, check);
+ resolve();
+ }
+ }
+ fetch(check.srcdoc).then((response) => {
+ response.text().then((body) => {
+ frame.srcdoc = body;
+ });;
+ });
+ });
+ }
+
+function loadDataURIFrame(test, check)
+{
+ return new Promise((resolve, reject) => {
+ let frame = SpecialPowers.wrap(window.document.getElementById(check.frameID));
+ frame.onload = function () {
+ // Ignore the first load and wait for the submitted form instead.
+ let location = frame.contentWindow.location + "";
+ if (location.endsWith("origin_header.sjs")) {
+ frameLoaded(test, check);
+ resolve();
+ }
+ }
+ fetch(check.dataURI).then((response) => {
+ response.text().then((body) => {
+ frame.src = "data:text/html," + encodeURIComponent(body);
+ });;
+ });
+ });
+}
+
+async function resetFrames()
+{
+ let checkPromises = [];
+ for (let check of checksToRun) {
+ checkPromises.push(new Promise((resolve, reject) => {
+ let frame = document.getElementById(check.frameID);
+ frame.onload = () => resolve();
+ if (check.srcdoc) {
+ frame.srcdoc = "";
+ } else {
+ frame.src = "about:blank";
+ }
+ }));
+ }
+ await Promise.all(checkPromises);
+}
+
+async function runTests()
+{
+ for (let test of testsToRun) {
+ await resetFrames();
+ await SpecialPowers.pushPrefEnv({"set": test.prefs});
+
+ let checkPromises = [];
+ for (let check of checksToRun) {
+ if (check.formID) {
+ checkPromises.push(submitForm(test, check));
+ } else if (check.frameSrc) {
+ checkPromises.push(loadIframe(test, check));
+ } else if (check.srcdoc) {
+ checkPromises.push(loadSrcDocFrame(test, check));
+ } else if (check.dataURI) {
+ checkPromises.push(loadDataURIFrame(test, check));
+ } else {
+ ok(false, "Unsupported check");
+ break;
+ }
+ }
+ await Promise.all(checkPromises);
+ };
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5); // work around Android timeouts
+addLoadEvent(runTests);
+
+</script>
+</pre>
+<table>
+<tr>
+ <td>
+ <iframe src="about:blank" name="framePost" id="framePost"></iframe>
+ <form action="origin_header.sjs"
+ method="POST"
+ id="formPost"
+ target="framePost">
+ <input type="submit" value="Submit POST">
+ </form>
+ </td>
+ <td>
+ <iframe src="about:blank" name="framePostXOrigin" id="framePostXOrigin"></iframe>
+ <form action="http://test1.mochi.test:8888/tests/netwerk/test/mochitests/origin_header.sjs"
+ method="POST"
+ id="formPostXOrigin"
+ target="framePostXOrigin">
+ <input type="submit" value="Submit XOrigin POST">
+ </form>
+ </td>
+ <td>
+ <iframe src="about:blank" name="frameGet" id="frameGet"></iframe>
+ <form action="origin_header.sjs"
+ method="GET"
+ id="formGet"
+ target="frameGet">
+ <input type="submit" value="Submit GET">
+ </form>
+ </td>
+ <td>
+ <iframe src="about:blank" name="framePostSameOriginToXOrigin" id="framePostSameOriginToXOrigin"></iframe>
+ <form action="redirect_to.sjs?http://test1.mochi.test:8888/tests/netwerk/test/mochitests/origin_header.sjs"
+ method="POST"
+ id="formPostSameOriginToXOrigin"
+ target="framePostSameOriginToXOrigin">
+ <input type="Submit" value="Submit SameOrigin POST redirected to XOrigin">
+ </form>
+ </td>
+ <td>
+ <iframe src="about:blank" name="framePostXOriginToSameOrigin" id="framePostXOriginToSameOrigin"></iframe>
+ <form action="http://test1.mochi.test:8888/tests/netwerk/test/mochitests/redirect_to.sjs?http://mochi.test:8888/tests/netwerk/test/mochitests/origin_header.sjs"
+ method="POST"
+ id="formPostXOriginToSameOrigin"
+ target="framePostXOriginToSameOrigin">
+ <input type="Submit" value="Submit XOrigin POST redirected to SameOrigin">
+ </form>
+ </td>
+ <td>
+ <iframe src="about:blank" name="framePostXOriginToXOrigin" id="framePostXOriginToXOrigin"></iframe>
+ <form action="http://test1.mochi.test:8888/tests/netwerk/test/mochitests/redirect_to.sjs?/tests/netwerk/test/mochitests/origin_header.sjs"
+ method="POST"
+ id="formPostXOriginToXOrigin"
+ target="framePostXOriginToXOrigin">
+ <input type="Submit" value="Submit XOrigin POST redirected to XOrigin">
+ </form>
+ </td>
+</tr>
+<tr>
+ <td>
+ <iframe src="about:blank" id="framePostNonSandboxed"></iframe>
+ <div>Non-sandboxed iframe</div>
+ </td>
+ <td>
+ <iframe src="about:blank" id="framePostNonSandboxedXOrigin"></iframe>
+ <div>Non-sandboxed cross-origin iframe</div>
+ </td>
+ <td>
+ <iframe src="about:blank" id="framePostSandboxed" sandbox="allow-forms allow-scripts"></iframe>
+ <div>Sandboxed iframe</div>
+ </td>
+</tr>
+<tr>
+ <td>
+ <iframe id="framePostSrcDoc" src="about:blank"></iframe>
+ <div>Srcdoc iframe</div>
+ </td>
+ <td>
+ <iframe id="framePostSrcDocXOrigin" src="about:blank"></iframe>
+ <div>Srcdoc cross-origin iframe</div>
+ </td>
+ <td>
+ <iframe id="framePostDataURI" src="about:blank"></iframe>
+ <div>data: URI iframe</div>
+ </td>
+</tr>
+</table>
+
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_partially_cached_content.html b/netwerk/test/mochitests/test_partially_cached_content.html
new file mode 100644
index 0000000000..8b6df555f8
--- /dev/null
+++ b/netwerk/test/mochitests/test_partially_cached_content.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=497003
+
+ This test verifies that partially cached content is read from the cache first
+ and then from the network. It is written in the mochitest framework to take
+ thread retargeting into consideration of nsIStreamListener callbacks (inc.
+ nsIRequestObserver). E.g. HTML5 Stream Parser requesting retargeting of
+ nsIStreamListener callbacks to the parser thread.
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 497003: support sending OnDataAvailable() to other threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=497003">Mozilla Bug 497003: support sending OnDataAvailable() to other threads</a></p>
+ <p><iframe id="contentFrame" src="partial_content.sjs"></iframe></p>
+
+<pre id="test">
+<script>
+
+
+
+/* Check that the iframe has initial content only after the first load.
+ */
+function expectInitialContent(e) {
+ info("expectInitialContent",
+ "First response received: should have partial content");
+ var frameElement = document.getElementById('contentFrame');
+ var frameWindow = frameElement.contentWindow;
+
+ // Expect "First response" in received HTML.
+ var firstResponse = frameWindow.document.getElementById('firstResponse');
+ ok(firstResponse, "First response should exist");
+ if (firstResponse) {
+ is(firstResponse.innerHTML, "First response",
+ "First response should be correct");
+ }
+
+ // Expect NOT to get any second response element.
+ var secondResponse = frameWindow.document.getElementById('secondResponse');
+ ok(!secondResponse, "Should not get text for second response in first.");
+
+ // Set up listener for second load.
+ removeEventListener("load", expectInitialContent, false);
+ frameElement.addEventListener("load", expectFullContent);
+
+ var reload = ()=>frameElement.src = "partial_content.sjs";
+
+ // Before reload, disable rcwn to avoid racing and a non-range request.
+ SpecialPowers.pushPrefEnv({set: [["network.http.rcwn.enabled", false]]},
+ reload);
+}
+
+/* Check that the iframe has all the content after the second load.
+ */
+function expectFullContent(e)
+{
+ info("expectFullContent",
+ "Second response received: should complete content from first load");
+ var frameWindow = document.getElementById('contentFrame').contentWindow;
+
+ // Expect "First response" to still be there
+ var firstResponse = frameWindow.document.getElementById('firstResponse');
+ ok(firstResponse, "First response should exist");
+ if (firstResponse) {
+ is(firstResponse.innerHTML, "First response",
+ "First response should be correct");
+ }
+
+ // Expect "Second response" to be there also.
+ var secondResponse = frameWindow.document.getElementById('secondResponse');
+ ok(secondResponse, "Second response should exist");
+ if (secondResponse) {
+ is(secondResponse.innerHTML, "Second response",
+ "Second response should be correct");
+ }
+
+ SimpleTest.finish();
+}
+
+// Set listener for first load to expect partial content.
+// Note: Set listener on the global object/window since 'load' should not fire
+// for partially loaded content in an iframe.
+addEventListener("load", expectInitialContent, false);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_redirect_ref.html b/netwerk/test/mochitests/test_redirect_ref.html
new file mode 100644
index 0000000000..0b234695d4
--- /dev/null
+++ b/netwerk/test/mochitests/test_redirect_ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title> Bug 1234575 - Test redirect ref</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var iframe = document.createElement("iframe");
+iframe.src = "about:blank";
+iframe.addEventListener("load", finishTest);
+document.body.appendChild(iframe);
+iframe.src = "redirect.sjs#start";
+
+function finishTest(e) {
+ is(iframe.contentWindow.location.href, "http://mochi.test:8888/tests/netwerk/test/mochitests/empty.html#");
+ SimpleTest.finish();
+}
+
+</script>
+
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_rel_preconnect.html b/netwerk/test/mochitests/test_rel_preconnect.html
new file mode 100644
index 0000000000..c7e0bada07
--- /dev/null
+++ b/netwerk/test/mochitests/test_rel_preconnect.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for link rel=preconnect</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci, Cr = SpecialPowers.Cr;
+
+var remainder = 4;
+var observer;
+
+async function doTest()
+{
+ await SpecialPowers.setBoolPref("network.http.debug-observations", true);
+
+ observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
+ remainder--;
+ ok(true, "observed remainder = " + remainder);
+ if (!remainder) {
+ SpecialPowers.removeObserver(observer, "speculative-connect-request");
+ SpecialPowers.setBoolPref("network.http.debug-observations", false);
+ SimpleTest.finish();
+ }
+ });
+ SpecialPowers.addObserver(observer, "speculative-connect-request");
+
+ // test the link rel=preconnect element in the head for both normal
+ // and crossOrigin=anonymous
+ var link = document.createElement("link");
+ link.rel = "preconnect";
+ link.href = "//localhost:8888";
+ document.head.appendChild(link);
+ link = document.createElement("link");
+ link.rel = "preconnect";
+ link.href = "//localhost:8888";
+ link.crossOrigin = "anonymous";
+ document.head.appendChild(link);
+
+ // test the http link response header - the test contains both a
+ // normal and anonymous preconnect link header
+ var iframe = document.createElement('iframe');
+ iframe.src = 'rel_preconnect.sjs?//localhost:8888';
+
+ document.body.appendChild(iframe);
+}
+
+</script>
+</head>
+<body onload="doTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_same_base_domain.html b/netwerk/test/mochitests/test_same_base_domain.html
new file mode 100644
index 0000000000..bcf069e8be
--- /dev/null
+++ b/netwerk/test/mochitests/test_same_base_domain.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('https://test1.example.org/tests/netwerk/test/mochitests/file_domain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_same_base_domain_2.html b/netwerk/test/mochitests/test_same_base_domain_2.html
new file mode 100644
index 0000000000..5647831c29
--- /dev/null
+++ b/netwerk/test/mochitests/test_same_base_domain_2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('https://test1.example.org/tests/netwerk/test/mochitests/file_subdomain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_same_base_domain_3.html b/netwerk/test/mochitests/test_same_base_domain_3.html
new file mode 100644
index 0000000000..62a4cfba95
--- /dev/null
+++ b/netwerk/test/mochitests/test_same_base_domain_3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('https://example.org/tests/netwerk/test/mochitests/file_subdomain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_same_base_domain_4.html b/netwerk/test/mochitests/test_same_base_domain_4.html
new file mode 100644
index 0000000000..87fbb1c720
--- /dev/null
+++ b/netwerk/test/mochitests/test_same_base_domain_4.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://mochi.test:8888/tests/netwerk/test/mochitests/file_localhost_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_same_base_domain_5.html b/netwerk/test/mochitests/test_same_base_domain_5.html
new file mode 100644
index 0000000000..7e4d2e3b1f
--- /dev/null
+++ b/netwerk/test/mochitests/test_same_base_domain_5.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('https://sub.sectest2.example.org/tests/netwerk/test/mochitests/file_subdomain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_same_base_domain_6.html b/netwerk/test/mochitests/test_same_base_domain_6.html
new file mode 100644
index 0000000000..195c38657b
--- /dev/null
+++ b/netwerk/test/mochitests/test_same_base_domain_6.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runThisTest()">
+<p id="display"></p>
+<pre id="test">
+ <script>
+ function runThisTest() {
+ // By default, proxies don't apply to 127.0.0.1.
+ // We need them to for this test (at least on android), though:
+ SpecialPowers.pushPrefEnv({set: [
+ ["network.proxy.allow_hijacking_localhost", true]
+ ]}).then(function() {
+ setupTest('http://127.0.0.1:8888/tests/netwerk/test/mochitests/file_loopback_inner.html', 5, 2);
+ });
+ }
+ </script>
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_samedomain.html b/netwerk/test/mochitests/test_samedomain.html
new file mode 100644
index 0000000000..82aace8bab
--- /dev/null
+++ b/netwerk/test/mochitests/test_samedomain.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://example.org/tests/netwerk/test/mochitests/file_domain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_uri_scheme.html b/netwerk/test/mochitests/test_uri_scheme.html
new file mode 100644
index 0000000000..b0c247f336
--- /dev/null
+++ b/netwerk/test/mochitests/test_uri_scheme.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for URI Manipulation</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+function dotest1()
+{
+ SimpleTest.waitForExplicitFinish();
+ var o = new URL("http://localhost/");
+ try { o.href = "foopy:bar:baz"; } catch(e) { }
+ o.protocol = "http:";
+ o.hostname;
+ try { o.href = "http://localhost/"; } catch(e) { }
+ ok(o.protocol, "http:");
+ dotest2();
+}
+
+function dotest2()
+{
+ var o = new URL("http://www.mozilla.org/");
+ try {
+ o.href ="aaaaaaaaaaa:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ } catch(e) { }
+ o.hash = "#";
+ o.pathname = "/";
+ o.protocol = "http:";
+ try { o.href = "http://localhost/"; } catch(e) { }
+ ok(o.protocol, "http:");
+ dotest3();
+}
+
+function dotest3()
+{
+ is(new URL("resource://123/").href, "resource://123/");
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="dotest1();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_url_perf.html b/netwerk/test/mochitests/test_url_perf.html
new file mode 100644
index 0000000000..c403398f06
--- /dev/null
+++ b/netwerk/test/mochitests/test_url_perf.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Networking performance test: url parsing</title>
+</head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+
+ // this test is just used locally to get an idea of how long url parsing takes
+ "use strict";
+
+ let runs = 20000;
+ let timeToCreateDataUrl = 0;
+ let timeToCreateDataSlashUrl = 0;
+ let timeToCreateJsUrl = 0;
+ let timeToCreateJsSlashUrl = 0;
+
+ // data scheme
+ add_task(async () => {
+ let before = new Date().getTime();
+ for (let i = 0; i < runs; i++) {
+ new URL("data:,test" + i);
+ }
+ let after = new Date().getTime();
+ timeToCreateDataUrl = after - before;
+ dump(
+ "Time to create data url (milliseconds): " + timeToCreateDataUrl + "\n"
+ );
+ });
+
+ // data://
+ add_task(async () => {
+ let before = new Date().getTime();
+ for (let i = 0; i < runs; i++) {
+ new URL("data://,test" + i);
+ }
+ let after = new Date().getTime();
+ timeToCreateDataSlashUrl = after - before;
+ dump(
+ "Time to create data // url (milliseconds): " + timeToCreateDataSlashUrl + "\n"
+ );
+ });
+
+ // javascript scheme
+ add_task(async () => {
+ let beforeJs = new Date().getTime();
+ for (let i = 0; i < runs; i++) {
+ new URL("javascript:,test" + i);
+ }
+ let afterJs = new Date().getTime();
+ timeToCreateJsUrl = afterJs - beforeJs;
+ dump(
+ "Time to create JS url (milliseconds): " + timeToCreateJsUrl + "\n"
+ );
+ });
+
+ // javascript://
+ add_task(async () => {
+ let beforeJs = new Date().getTime();
+ for (let i = 0; i < runs; i++) {
+ new URL("javascript://,test" + i);
+ }
+ let afterJs = new Date().getTime();
+ timeToCreateJsSlashUrl = afterJs - beforeJs;
+ dump(
+ "Time to create JS // url (milliseconds): " + timeToCreateJsSlashUrl + "\n"
+ );
+ });
+
+ ok("finished");
+
+</script>
+<body>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_viewsource_unlinkable.html b/netwerk/test/mochitests/test_viewsource_unlinkable.html
new file mode 100644
index 0000000000..f4c4064183
--- /dev/null
+++ b/netwerk/test/mochitests/test_viewsource_unlinkable.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for view-source linkability</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+function runTest() {
+ SimpleTest.doesThrow(function() {
+ window.open('view-source:' + location.href, "_blank");
+ }, "Trying to access view-source URL from unprivileged code should throw.");
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_xhr_method_case.html b/netwerk/test/mochitests/test_xhr_method_case.html
new file mode 100644
index 0000000000..ddb830328c
--- /dev/null
+++ b/netwerk/test/mochitests/test_xhr_method_case.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+XHR uppercases certain method names, but not others
+-->
+<head>
+ <title>Test for XHR Method casing</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+const testMethods = [
+// these methods should be normalized
+ ["get", "GET"],
+ ["GET", "GET"],
+ ["GeT", "GET"],
+ ["geT", "GET"],
+ ["GEt", "GET"],
+ ["post", "POST"],
+ ["POST", "POST"],
+ ["delete", "DELETE"],
+ ["DELETE", "DELETE"],
+ ["options", "OPTIONS"],
+ ["OPTIONS", "OPTIONS"],
+ ["put", "PUT"],
+ ["PUT", "PUT"],
+// HEAD is not tested because we use the resposne body as part of the test
+// ["head", "HEAD"],
+// ["HEAD", "HEAD"],
+
+// other custom methods should not be normalized
+ ["Foo", "Foo"],
+ ["bAR", "bAR"],
+ ["foobar", "foobar"],
+ ["FOOBAR", "FOOBAR"]
+]
+
+function doIter(index)
+{
+ var xhr = new XMLHttpRequest();
+ xhr.open(testMethods[index][0], 'method.sjs', false); // sync request
+ xhr.send();
+ is(xhr.status, 200, 'transaction failed');
+ is(xhr.response, testMethods[index][1], 'unexpected method');
+}
+
+function dotest()
+{
+ SimpleTest.waitForExplicitFinish();
+ for (var i = 0; i < testMethods.length; i++) {
+ doIter(i);
+ }
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+<body onload="dotest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/web_packaged_app.sjs b/netwerk/test/mochitests/web_packaged_app.sjs
new file mode 100644
index 0000000000..772b8a0835
--- /dev/null
+++ b/netwerk/test/mochitests/web_packaged_app.sjs
@@ -0,0 +1,47 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "application/package", false);
+ response.write(octetStreamData.getData());
+}
+
+// The package content
+// getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
+var octetStreamData = {
+ content: [
+ {
+ headers: ["Content-Location: /index.html", "Content-Type: text/html"],
+ data: "<html>\r\n <head>\r\n <script> alert('OK: hello'); alert('DONE'); </script>\r\n</head>\r\n Web Packaged App Index\r\n</html>\r\n",
+ type: "text/html",
+ },
+ {
+ headers: [
+ "Content-Location: /scripts/app.js",
+ "Content-Type: text/javascript",
+ ],
+ data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n",
+ type: "text/javascript",
+ },
+ {
+ headers: [
+ "Content-Location: /scripts/helpers/math.js",
+ "Content-Type: text/javascript",
+ ],
+ data: "export function sum(nums) { ... }\r\n...\r\n",
+ type: "text/javascript",
+ },
+ ],
+ token: "gc0pJq0M:08jU534c0p",
+ getData() {
+ var str = "";
+ for (var i in this.content) {
+ str += "--" + this.token + "\r\n";
+ for (var j in this.content[i].headers) {
+ str += this.content[i].headers[j] + "\r\n";
+ }
+ str += "\r\n";
+ str += this.content[i].data + "\r\n";
+ }
+
+ str += "--" + this.token + "--";
+ return str;
+ },
+};
diff --git a/netwerk/test/moz.build b/netwerk/test/moz.build
new file mode 100644
index 0000000000..864d1f5b87
--- /dev/null
+++ b/netwerk/test/moz.build
@@ -0,0 +1,33 @@
+# -*- 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/.
+
+TEST_DIRS += ["httpserver", "gtest", "http3server"]
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser/browser.toml",
+ "useragent/browser_nonsnap.toml",
+ "useragent/browser_snap.toml",
+]
+MOCHITEST_MANIFESTS += ["mochitests/mochitest.toml"]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "unit/node_execute/xpcshell.toml",
+ "unit/xpcshell.toml",
+ "unit_ipc/xpcshell.toml",
+]
+
+TESTING_JS_MODULES += [
+ "browser/cookie_filtering_helper.sys.mjs",
+ "browser/early_hint_preload_test_helper.sys.mjs",
+ "unit/test_http3_prio_helpers.js",
+]
+
+PERFTESTS_MANIFESTS += ["perf/perftest.toml", "unit/perftest.toml"]
+
+MARIONETTE_MANIFESTS += ["marionette/manifest.toml"]
+
+if CONFIG["FUZZING_INTERFACES"]:
+ TEST_DIRS += ["fuzz"]
diff --git a/netwerk/test/perf/.eslintrc.js b/netwerk/test/perf/.eslintrc.js
new file mode 100644
index 0000000000..f040032509
--- /dev/null
+++ b/netwerk/test/perf/.eslintrc.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.exports = {
+ env: {
+ browser: true,
+ node: true,
+ },
+};
diff --git a/netwerk/test/perf/hooks_throttling.py b/netwerk/test/perf/hooks_throttling.py
new file mode 100644
index 0000000000..5f46b3f0d3
--- /dev/null
+++ b/netwerk/test/perf/hooks_throttling.py
@@ -0,0 +1,202 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+"""
+Drives the throttling feature when the test calls our
+controlled server.
+"""
+import http.client
+import json
+import os
+import sys
+import time
+from urllib.parse import urlparse
+
+from mozperftest.test.browsertime import add_option
+from mozperftest.utils import get_tc_secret
+
+ENDPOINTS = {
+ "linux": "h3.dev.mozaws.net",
+ "darwin": "h3.mac.dev.mozaws.net",
+ "win32": "h3.win.dev.mozaws.net",
+}
+CTRL_SERVER = ENDPOINTS[sys.platform]
+TASK_CLUSTER = "TASK_ID" in os.environ.keys()
+_SECRET = {
+ "throttler_host": f"https://{CTRL_SERVER}/_throttler",
+ "throttler_key": os.environ.get("WEBNETEM_KEY", ""),
+}
+if TASK_CLUSTER:
+ _SECRET.update(get_tc_secret())
+
+if _SECRET["throttler_key"] == "":
+ if TASK_CLUSTER:
+ raise Exception("throttler_key not found in secret")
+ raise Exception("WEBNETEM_KEY not set")
+
+_TIMEOUT = 30
+WAIT_TIME = 60 * 10
+IDLE_TIME = 10
+BREATHE_TIME = 20
+
+
+class Throttler:
+ def __init__(self, env, host, key):
+ self.env = env
+ self.host = host
+ self.key = key
+ self.verbose = env.get_arg("verbose", False)
+ self.logger = self.verbose and self.env.info or self.env.debug
+
+ def log(self, msg):
+ self.logger("[throttler] " + msg)
+
+ def _request(self, action, data=None):
+ kw = {}
+ headers = {b"X-WEBNETEM-KEY": self.key}
+ verb = data is None and "GET" or "POST"
+ if data is not None:
+ data = json.dumps(data)
+ headers[b"Content-type"] = b"application/json"
+
+ parsed = urlparse(self.host)
+ server = parsed.netloc
+ path = parsed.path
+ if action != "status":
+ path += "/" + action
+
+ self.log(f"Calling {verb} {path}")
+ conn = http.client.HTTPSConnection(server, timeout=_TIMEOUT)
+ conn.request(verb, path, body=data, headers=headers, **kw)
+ resp = conn.getresponse()
+ res = resp.read()
+ if resp.status >= 400:
+ raise Exception(res)
+ res = json.loads(res)
+ return res
+
+ def start(self, data=None):
+ self.log("Starting")
+ now = time.time()
+ acquired = False
+
+ while time.time() - now < WAIT_TIME:
+ status = self._request("status")
+ if status.get("test_running"):
+ # a test is running
+ self.log("A test is already controlling the server")
+ self.log(f"Waiting {IDLE_TIME} seconds")
+ else:
+ try:
+ self._request("start_test")
+ acquired = True
+ break
+ except Exception:
+ # we got beat in the race
+ self.log("Someone else beat us")
+ time.sleep(IDLE_TIME)
+
+ if not acquired:
+ raise Exception("Could not acquire the test server")
+
+ if data is not None:
+ self._request("shape", data)
+
+ def stop(self):
+ self.log("Stopping")
+ try:
+ self._request("reset")
+ finally:
+ self._request("stop_test")
+
+
+def get_throttler(env):
+ host = _SECRET["throttler_host"]
+ key = _SECRET["throttler_key"].encode()
+ return Throttler(env, host, key)
+
+
+_PROTOCOL = "h2", "h3"
+_PAGE = "gallery", "news", "shopping", "photoblog"
+
+# set the network condition here.
+# each item has a name and some netem options:
+#
+# loss_ratio: specify percentage of packets that will be lost
+# loss_corr: specify a correlation factor for the random packet loss
+# dup_ratio: specify percentage of packets that will be duplicated
+# delay: specify an overall delay for each packet
+# jitter: specify amount of jitter in milliseconds
+# delay_jitter_corr: specify a correlation factor for the random jitter
+# reorder_ratio: specify percentage of packets that will be reordered
+# reorder_corr: specify a correlation factor for the random reordering
+#
+_THROTTLING = (
+ {"name": "full"}, # no throttling.
+ {"name": "one", "delay": "20"},
+ {"name": "two", "delay": "50"},
+ {"name": "three", "delay": "100"},
+ {"name": "four", "delay": "200"},
+ {"name": "five", "delay": "300"},
+)
+
+
+def get_test():
+ """Iterate on test conditions.
+
+ For each cycle, we return a combination of: protocol, page, throttling
+ settings. Each combination has a name, and that name will be used along with
+ the protocol as a prefix for each metrics.
+ """
+ for proto in _PROTOCOL:
+ for page in _PAGE:
+ url = f"https://{CTRL_SERVER}/{page}.html"
+ for throttler_settings in _THROTTLING:
+ yield proto, page, url, throttler_settings
+
+
+combo = get_test()
+
+
+def before_cycle(metadata, env, cycle, script):
+ global combo
+ if "throttlable" not in script["tags"]:
+ return
+ throttler = get_throttler(env)
+ try:
+ proto, page, url, throttler_settings = next(combo)
+ except StopIteration:
+ combo = get_test()
+ proto, page, url, throttler_settings = next(combo)
+
+ # setting the url for the browsertime script
+ add_option(env, "browsertime.url", url, overwrite=True)
+
+ # enabling http if needed
+ if proto == "h3":
+ add_option(env, "firefox.preference", "network.http.http3.enable:true")
+
+ # prefix used to differenciate metrics
+ name = throttler_settings["name"]
+ script["name"] = f"{name}_{proto}_{page}"
+
+ # throttling the controlled server if needed
+ if throttler_settings != {"name": "full"}:
+ env.info("Calling the controlled server")
+ throttler.start(throttler_settings)
+ else:
+ env.info("No throttling for this call")
+ throttler.start()
+
+
+def after_cycle(metadata, env, cycle, script):
+ if "throttlable" not in script["tags"]:
+ return
+ throttler = get_throttler(env)
+ try:
+ throttler.stop()
+ except Exception:
+ pass
+
+ # give a chance for a competitive job to take over
+ time.sleep(BREATHE_TIME)
diff --git a/netwerk/test/perf/perftest.toml b/netwerk/test/perf/perftest.toml
new file mode 100644
index 0000000000..f15f441465
--- /dev/null
+++ b/netwerk/test/perf/perftest.toml
@@ -0,0 +1,17 @@
+[DEFAULT]
+
+["perftest_http3_cloudflareblog.js"]
+
+["perftest_http3_controlled.js"]
+
+["perftest_http3_facebook_scroll.js"]
+
+["perftest_http3_google_image.js"]
+
+["perftest_http3_google_search.js"]
+
+["perftest_http3_lucasquicfetch.js"]
+
+["perftest_http3_youtube_watch.js"]
+
+["perftest_http3_youtube_watch_scroll.js"]
diff --git a/netwerk/test/perf/perftest_http3_cloudflareblog.js b/netwerk/test/perf/perftest_http3_cloudflareblog.js
new file mode 100644
index 0000000000..4bbea56bbd
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_cloudflareblog.js
@@ -0,0 +1,56 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function test(context, commands) {
+ let rootUrl = "https://blog.cloudflare.com/";
+ let waitTime = 1000;
+
+ if (
+ (typeof context.options.browsertime !== "undefined") &
+ (typeof context.options.browsertime.waitTime !== "undefined")
+ ) {
+ waitTime = context.options.browsertime.waitTime;
+ }
+
+ // Make firefox learn of HTTP/3 server
+ // XXX: Need to build an HTTP/3-specific conditioned profile
+ // to handle these pre-navigations.
+ await commands.navigate(rootUrl);
+
+ let cycles = 1;
+ for (let cycle = 0; cycle < cycles; cycle++) {
+ // Measure initial pageload
+ await commands.measure.start("pageload");
+ await commands.navigate(rootUrl);
+ await commands.measure.stop();
+ commands.measure.result[0].browserScripts.pageinfo.url =
+ "Cloudflare Blog - Main";
+
+ // Wait for X seconds
+ await commands.wait.byTime(waitTime);
+
+ // Measure navigation pageload
+ await commands.measure.start("pageload");
+ await commands.click.byJsAndWait(`
+ document.querySelectorAll("article")[0].querySelector("a")
+ `);
+ await commands.measure.stop();
+ commands.measure.result[1].browserScripts.pageinfo.url =
+ "Cloudflare Blog - Article";
+ }
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ name: "cloudflare",
+ component: "netwerk",
+ description: "User-journey live site test for cloudflare blog.",
+};
diff --git a/netwerk/test/perf/perftest_http3_controlled.js b/netwerk/test/perf/perftest_http3_controlled.js
new file mode 100644
index 0000000000..243c314b5b
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_controlled.js
@@ -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/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function test(context, commands) {
+ let url = context.options.browsertime.url;
+
+ // Make firefox learn of HTTP/3 server
+ // XXX: Need to build an HTTP/3-specific conditioned profile
+ // to handle these pre-navigations.
+ await commands.navigate(url);
+
+ // Measure initial pageload
+ await commands.measure.start("pageload");
+ await commands.navigate(url);
+ await commands.measure.stop();
+ commands.measure.result[0].browserScripts.pageinfo.url = url;
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ name: "controlled",
+ description: "User-journey live site test for controlled server",
+ tags: ["throttlable"],
+};
diff --git a/netwerk/test/perf/perftest_http3_facebook_scroll.js b/netwerk/test/perf/perftest_http3_facebook_scroll.js
new file mode 100644
index 0000000000..7e736a0f5f
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_facebook_scroll.js
@@ -0,0 +1,165 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function captureNetworkRequest(commands) {
+ var capture_network_request = [];
+ var capture_resource = await commands.js.run(`
+ return performance.getEntriesByType("resource");
+ `);
+ for (var i = 0; i < capture_resource.length; i++) {
+ capture_network_request.push(capture_resource[i].name);
+ }
+ return capture_network_request;
+}
+
+async function waitForScrollRequestsEnd(
+ prevCount,
+ maxStableCount,
+ timeout,
+ commands,
+ context
+) {
+ let starttime = await commands.js.run(`return performance.now();`);
+ let endtime = await commands.js.run(`return performance.now();`);
+ let changing = true;
+ let newCount = -1;
+ let stableCount = 0;
+
+ while (
+ ((await commands.js.run(`return performance.now();`)) - starttime <
+ timeout) &
+ changing
+ ) {
+ // Wait a bit before making another round
+ await commands.wait.byTime(100);
+ newCount = (await captureNetworkRequest(commands)).length;
+ context.log.debug(`${newCount}, ${prevCount}, ${stableCount}`);
+
+ // Check if we are approaching stability
+ if (newCount == prevCount) {
+ // Gather the end time now
+ if (stableCount == 0) {
+ endtime = await commands.js.run(`return performance.now();`);
+ }
+ stableCount++;
+ } else {
+ prevCount = newCount;
+ stableCount = 0;
+ }
+
+ if (stableCount >= maxStableCount) {
+ // Stability achieved
+ changing = false;
+ }
+ }
+
+ return {
+ start: starttime,
+ end: endtime,
+ numResources: newCount,
+ };
+}
+
+async function test(context, commands) {
+ let rootUrl = "https://www.facebook.com/lambofgod/";
+ let waitTime = 1000;
+ let numScrolls = 5;
+
+ const average = arr => arr.reduce((p, c) => p + c, 0) / arr.length;
+
+ if (typeof context.options.browsertime !== "undefined") {
+ if (typeof context.options.browsertime.waitTime !== "undefined") {
+ waitTime = context.options.browsertime.waitTime;
+ }
+ if (typeof context.options.browsertime.numScrolls !== "undefined") {
+ numScrolls = context.options.browsertime.numScrolls;
+ }
+ }
+
+ // Make firefox learn of HTTP/3 server
+ await commands.navigate(rootUrl);
+
+ let cycles = 1;
+ for (let cycle = 0; cycle < cycles; cycle++) {
+ // Measure the pageload
+ await commands.measure.start("pageload");
+ await commands.navigate(rootUrl);
+ await commands.measure.stop();
+
+ // Initial scroll to make the new user popup show
+ await commands.js.runAndWait(
+ `window.scrollTo({ top: 1000, behavior: 'smooth' })`
+ );
+ await commands.wait.byTime(1000);
+ await commands.click.byLinkTextAndWait("Not Now");
+
+ let vals = [];
+ let badIterations = 0;
+ for (let iteration = 0; iteration < numScrolls; iteration++) {
+ // Clear old resources
+ await commands.js.run(`performance.clearResourceTimings();`);
+
+ // Get current resource count
+ let currCount = (await captureNetworkRequest(commands)).length;
+
+ // Scroll to a ridiculously high value for "infinite" down-scrolling
+ commands.js.runAndWait(`
+ window.scrollTo({ top: 100000000 })
+ `);
+
+ /*
+ The maxStableCount of 22 was chosen as a trade-off between fast iterations
+ and minimizing the number of bad iterations.
+ */
+ let newInfo = await waitForScrollRequestsEnd(
+ currCount,
+ 22,
+ 120000,
+ commands,
+ context
+ );
+
+ // Gather metrics
+ let ndiff = newInfo.numResources - currCount;
+ let tdiff = (newInfo.end - newInfo.start) / 1000;
+
+ // Check if we had a bad iteration
+ if (ndiff == 0) {
+ context.log.info("Bad iteration, redoing...");
+ iteration--;
+ badIterations++;
+ if (badIterations == 5) {
+ throw new Error("Too many bad scroll iterations occurred");
+ }
+ continue;
+ }
+
+ vals.push(ndiff / tdiff);
+
+ // Wait X seconds before scrolling again
+ await commands.wait.byTime(waitTime);
+ }
+
+ if (!vals.length) {
+ throw new Error("No requestsPerSecond values were obtained");
+ }
+
+ commands.measure.result[0].browserScripts.pageinfo.requestsPerSecond =
+ average(vals);
+ }
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ component: "netwerk",
+ name: "facebook-scroll",
+ description: "Measures the number of requests per second after a scroll.",
+};
diff --git a/netwerk/test/perf/perftest_http3_google_image.js b/netwerk/test/perf/perftest_http3_google_image.js
new file mode 100644
index 0000000000..8d0f788245
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_google_image.js
@@ -0,0 +1,190 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function getNumImagesLoaded(elementSelector, commands) {
+ return commands.js.run(`
+ let sum = 0;
+ document.querySelectorAll(${elementSelector}).forEach(e => {
+ sum += e.complete & e.naturalHeight != 0;
+ });
+ return sum;
+ `);
+}
+
+async function waitForImgLoadEnd(
+ prevCount,
+ maxStableCount,
+ iterationDelay,
+ timeout,
+ commands,
+ context,
+ counter,
+ elementSelector
+) {
+ let starttime = await commands.js.run(`return performance.now();`);
+ let endtime = await commands.js.run(`return performance.now();`);
+ let changing = true;
+ let newCount = -1;
+ let stableCount = 0;
+
+ while (
+ ((await commands.js.run(`return performance.now();`)) - starttime <
+ timeout) &
+ changing
+ ) {
+ // Wait a bit before making another round
+ await commands.wait.byTime(iterationDelay);
+ newCount = await counter(elementSelector, commands);
+ context.log.debug(`${newCount}, ${prevCount}, ${stableCount}`);
+
+ // Check if we are approaching stability
+ if (newCount == prevCount) {
+ // Gather the end time now
+ if (stableCount == 0) {
+ endtime = await commands.js.run(`return performance.now();`);
+ }
+ stableCount++;
+ } else {
+ prevCount = newCount;
+ stableCount = 0;
+ }
+
+ if (stableCount >= maxStableCount) {
+ // Stability achieved
+ changing = false;
+ }
+ }
+
+ return {
+ start: starttime,
+ end: endtime,
+ numResources: newCount,
+ };
+}
+
+async function test(context, commands) {
+ let rootUrl = "https://www.google.com/search?q=kittens&tbm=isch";
+ let waitTime = 1000;
+ let numScrolls = 10;
+
+ const average = arr => arr.reduce((p, c) => p + c, 0) / arr.length;
+
+ if (typeof context.options.browsertime !== "undefined") {
+ if (typeof context.options.browsertime.waitTime !== "undefined") {
+ waitTime = context.options.browsertime.waitTime;
+ }
+ if (typeof context.options.browsertime.numScrolls !== "undefined") {
+ numScrolls = context.options.browsertime.numScrolls;
+ }
+ }
+
+ // Make firefox learn of HTTP/3 server
+ await commands.navigate(rootUrl);
+
+ let cycles = 1;
+ for (let cycle = 0; cycle < cycles; cycle++) {
+ // Measure the pageload
+ await commands.measure.start("pageload");
+ await commands.navigate(rootUrl);
+ await commands.measure.stop();
+
+ async function getHeight() {
+ return commands.js.run(`return document.body.scrollHeight;`);
+ }
+
+ let vals = [];
+ let badIterations = 0;
+ let prevHeight = 0;
+ for (let iteration = 0; iteration < numScrolls; iteration++) {
+ // Get current image count
+ let currCount = await getNumImagesLoaded(`".isv-r img"`, commands);
+ prevHeight = await getHeight();
+
+ // Scroll to a ridiculously high value for "infinite" down-scrolling
+ commands.js.runAndWait(`
+ window.scrollTo({ top: 100000000 })
+ `);
+
+ /*
+ The maxStableCount of 22 was chosen as a trade-off between fast iterations
+ and minimizing the number of bad iterations.
+ */
+ let results = await waitForImgLoadEnd(
+ currCount,
+ 22,
+ 100,
+ 120000,
+ commands,
+ context,
+ getNumImagesLoaded,
+ `".isv-r img"`
+ );
+
+ // Gather metrics
+ let ndiff = results.numResources - currCount;
+ let tdiff = (results.end - results.start) / 1000;
+
+ // Check if we had a bad iteration
+ if (ndiff == 0) {
+ // Check if the end of the search results was reached
+ if (prevHeight == (await getHeight())) {
+ context.log.info("Reached end of page.");
+ break;
+ }
+ context.log.info("Bad iteration, redoing...");
+ iteration--;
+ badIterations++;
+ if (badIterations == 5) {
+ throw new Error("Too many bad scroll iterations occurred");
+ }
+ continue;
+ }
+
+ context.log.info(`${ndiff}, ${tdiff}`);
+ vals.push(ndiff / tdiff);
+
+ // Wait X seconds before scrolling again
+ await commands.wait.byTime(waitTime);
+ }
+
+ if (!vals.length) {
+ throw new Error("No requestsPerSecond values were obtained");
+ }
+
+ commands.measure.result[0].browserScripts.pageinfo.imagesPerSecond =
+ average(vals);
+
+ // Test clicking and and opening an image
+ await commands.wait.byTime(waitTime);
+ commands.click.byJs(`
+ const links = document.querySelectorAll(".islib"); links[links.length-1]
+ `);
+ let results = await waitForImgLoadEnd(
+ 0,
+ 22,
+ 50,
+ 120000,
+ commands,
+ context,
+ getNumImagesLoaded,
+ `"#islsp img"`
+ );
+ commands.measure.result[0].browserScripts.pageinfo.imageLoadTime =
+ results.end - results.start;
+ }
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ component: "netwerk",
+ name: "g-image",
+ description: "Measures the number of images per second after a scroll.",
+};
diff --git a/netwerk/test/perf/perftest_http3_google_search.js b/netwerk/test/perf/perftest_http3_google_search.js
new file mode 100644
index 0000000000..8183e3a152
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_google_search.js
@@ -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/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function test(context, commands) {
+ let rootUrl = "https://www.google.com/";
+ let waitTime = 1000;
+ const driver = context.selenium.driver;
+ const webdriver = context.selenium.webdriver;
+
+ if (
+ (typeof context.options.browsertime !== "undefined") &
+ (typeof context.options.browsertime.waitTime !== "undefined")
+ ) {
+ waitTime = context.options.browsertime.waitTime;
+ }
+
+ // Make firefox learn of HTTP/3 server
+ await commands.navigate(rootUrl);
+
+ let cycles = 1;
+ for (let cycle = 0; cycle < cycles; cycle++) {
+ await commands.navigate(rootUrl);
+ await commands.wait.byTime(1000);
+
+ // Set up the search
+ context.log.info("Setting up search");
+ const searchfield = driver.findElement(webdriver.By.name("q"));
+ searchfield.sendKeys("Python\n");
+ await commands.wait.byTime(5000);
+
+ // Measure the search time
+ context.log.info("Start search");
+ await commands.measure.start("pageload");
+ await commands.click.byJs(`document.querySelector("input[name='btnK']")`);
+ await commands.wait.byTime(5000);
+ await commands.measure.stop();
+ context.log.info("Done");
+
+ commands.measure.result[0].browserScripts.pageinfo.url =
+ "Google Search (Python)";
+
+ // Wait for X seconds
+ context.log.info(`Waiting for ${waitTime} milliseconds`);
+ await commands.wait.byTime(waitTime);
+
+ // Go to the next search page and measure
+ context.log.info("Going to second page of search results");
+ await commands.measure.start("pageload");
+ await commands.click.byIdAndWait("pnnext");
+
+ // XXX: Increase wait time when we add latencies
+ await commands.wait.byTime(3000);
+ await commands.measure.stop();
+
+ commands.measure.result[1].browserScripts.pageinfo.url =
+ "Google Search (Python) - Next Page";
+ }
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ component: "netwerk",
+ name: "g-search",
+ description: "User-journey live site test for google search",
+};
diff --git a/netwerk/test/perf/perftest_http3_lucasquicfetch.js b/netwerk/test/perf/perftest_http3_lucasquicfetch.js
new file mode 100644
index 0000000000..49a5b4c824
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_lucasquicfetch.js
@@ -0,0 +1,134 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function getNumLoaded(commands) {
+ return commands.js.run(`
+ let sum = 0;
+ document.querySelectorAll("#imgContainer img").forEach(e => {
+ sum += e.complete & e.naturalHeight != 0;
+ });
+ return sum;
+ `);
+}
+
+async function waitForImgLoadEnd(
+ prevCount,
+ maxStableCount,
+ timeout,
+ commands,
+ context
+) {
+ let starttime = await commands.js.run(`return performance.now();`);
+ let endtime = await commands.js.run(`return performance.now();`);
+ let changing = true;
+ let newCount = -1;
+ let stableCount = 0;
+
+ while (
+ ((await commands.js.run(`return performance.now();`)) - starttime <
+ timeout) &
+ changing
+ ) {
+ // Wait a bit before making another round
+ await commands.wait.byTime(100);
+ newCount = await getNumLoaded(commands);
+ context.log.debug(`${newCount}, ${prevCount}, ${stableCount}`);
+
+ // Check if we are approaching stability
+ if (newCount == prevCount) {
+ // Gather the end time now
+ if (stableCount == 0) {
+ endtime = await commands.js.run(`return performance.now();`);
+ }
+ stableCount++;
+ } else {
+ prevCount = newCount;
+ stableCount = 0;
+ }
+
+ if (stableCount >= maxStableCount) {
+ // Stability achieved
+ changing = false;
+ }
+ }
+
+ return {
+ start: starttime,
+ end: endtime,
+ numResources: newCount,
+ };
+}
+
+async function test(context, commands) {
+ let rootUrl = "https://lucaspardue.com/quictilesfetch.html";
+ let cycles = 5;
+
+ if (
+ (typeof context.options.browsertime !== "undefined") &
+ (typeof context.options.browsertime.cycles !== "undefined")
+ ) {
+ cycles = context.options.browsertime.cycles;
+ }
+
+ // Make firefox learn of HTTP/3 server
+ // XXX: Need to build an HTTP/3-specific conditioned profile
+ // to handle these pre-navigations.
+ await commands.navigate(rootUrl);
+
+ let combos = [
+ [100, 1],
+ [100, 100],
+ [300, 300],
+ ];
+ for (let cycle = 0; cycle < cycles; cycle++) {
+ for (let combo = 0; combo < combos.length; combo++) {
+ await commands.measure.start("pageload");
+ await commands.navigate(rootUrl);
+ await commands.measure.stop();
+ let last = commands.measure.result.length - 1;
+ commands.measure.result[
+ last
+ ].browserScripts.pageinfo.url = `LucasQUIC (r=${combos[combo][0]}, p=${combos[combo][1]})`;
+
+ // Set the input fields
+ await commands.js.runAndWait(`
+ document.querySelector("#maxReq").setAttribute(
+ "value",
+ ${combos[combo][0]}
+ )
+ `);
+ await commands.js.runAndWait(`
+ document.querySelector("#reqGroup").setAttribute(
+ "value",
+ ${combos[combo][1]}
+ )
+ `);
+
+ // Start the test and wait for the images to finish loading
+ commands.click.byJs(`document.querySelector("button")`);
+ let results = await waitForImgLoadEnd(0, 40, 120000, commands, context);
+
+ commands.measure.result[last].browserScripts.pageinfo.resourceLoadTime =
+ results.end - results.start;
+ commands.measure.result[last].browserScripts.pageinfo.imagesLoaded =
+ results.numResources;
+ commands.measure.result[last].browserScripts.pageinfo.imagesMissed =
+ combos[combo][0] - results.numResources;
+ }
+ }
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ name: "lq-fetch",
+ component: "netwerk",
+ description: "Measures the amount of time it takes to load a set of images.",
+};
diff --git a/netwerk/test/perf/perftest_http3_youtube_watch.js b/netwerk/test/perf/perftest_http3_youtube_watch.js
new file mode 100644
index 0000000000..1fd525941d
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_youtube_watch.js
@@ -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/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function test(context, commands) {
+ let rootUrl = "https://www.youtube.com/watch?v=COU5T-Wafa4";
+ let waitTime = 20000;
+
+ if (
+ (typeof context.options.browsertime !== "undefined") &
+ (typeof context.options.browsertime.waitTime !== "undefined")
+ ) {
+ waitTime = context.options.browsertime.waitTime;
+ }
+
+ // Make firefox learn of HTTP/3 server
+ await commands.navigate(rootUrl);
+
+ let cycles = 1;
+ for (let cycle = 0; cycle < cycles; cycle++) {
+ await commands.measure.start("pageload");
+ await commands.navigate(rootUrl);
+
+ // Make sure the video is running
+ if (
+ await commands.js.run(`return document.querySelector("video").paused;`)
+ ) {
+ throw new Error("Video should be running but it's paused");
+ }
+
+ // Disable youtube autoplay
+ await commands.click.byIdAndWait("toggleButton");
+
+ // Start playback quality measurements
+ const start = await commands.js.run(`return performance.now();`);
+ while (
+ !(await commands.js.run(`
+ return document.querySelector("video").ended;
+ `)) &
+ !(await commands.js.run(`
+ return document.querySelector("video").paused;
+ `)) &
+ ((await commands.js.run(`return performance.now();`)) - start < waitTime)
+ ) {
+ await commands.wait.byTime(5000);
+ context.log.info("playing...");
+ }
+
+ // Video done, now gather metrics
+ const playbackQuality = await commands.js.run(
+ `return document.querySelector("video").getVideoPlaybackQuality();`
+ );
+ await commands.measure.stop();
+
+ commands.measure.result[0].browserScripts.pageinfo.droppedFrames =
+ playbackQuality.droppedVideoFrames;
+ commands.measure.result[0].browserScripts.pageinfo.decodedFrames =
+ playbackQuality.totalVideoFrames;
+ }
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ component: "netwerk",
+ name: "youtube-noscroll",
+ description: "Measures quality of the video being played.",
+};
diff --git a/netwerk/test/perf/perftest_http3_youtube_watch_scroll.js b/netwerk/test/perf/perftest_http3_youtube_watch_scroll.js
new file mode 100644
index 0000000000..8f30fcc5e6
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_youtube_watch_scroll.js
@@ -0,0 +1,86 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+/* eslint-env node */
+
+/*
+Ensure the `--firefox.preference=network.http.http3.enable:true` is
+set for this test.
+*/
+
+async function test(context, commands) {
+ let rootUrl = "https://www.youtube.com/watch?v=COU5T-Wafa4";
+ let waitTime = 20000;
+
+ if (
+ (typeof context.options.browsertime !== "undefined") &
+ (typeof context.options.browsertime.waitTime !== "undefined")
+ ) {
+ waitTime = context.options.browsertime.waitTime;
+ }
+
+ // Make firefox learn of HTTP/3 server
+ await commands.navigate(rootUrl);
+
+ let cycles = 1;
+ for (let cycle = 0; cycle < cycles; cycle++) {
+ await commands.measure.start("pageload");
+ await commands.navigate(rootUrl);
+
+ // Make sure the video is running
+ // XXX: Should we start the video ourself?
+ if (
+ await commands.js.run(`return document.querySelector("video").paused;`)
+ ) {
+ throw new Error("Video should be running but it's paused");
+ }
+
+ // Disable youtube autoplay
+ await commands.click.byIdAndWait("toggleButton");
+
+ // Start playback quality measurements
+ const start = await commands.js.run(`return performance.now();`);
+ let counter = 1;
+ let direction = 0;
+ while (
+ !(await commands.js.run(`
+ return document.querySelector("video").ended;
+ `)) &
+ !(await commands.js.run(`
+ return document.querySelector("video").paused;
+ `)) &
+ ((await commands.js.run(`return performance.now();`)) - start < waitTime)
+ ) {
+ // Reset the scroll after going down 10 times
+ direction = counter * 1000;
+ if (direction > 10000) {
+ counter = -1;
+ }
+ counter++;
+
+ await commands.js.runAndWait(
+ `window.scrollTo({ top: ${direction}, behavior: 'smooth' })`
+ );
+ context.log.info("playing while scrolling...");
+ }
+
+ // Video done, now gather metrics
+ const playbackQuality = await commands.js.run(
+ `return document.querySelector("video").getVideoPlaybackQuality();`
+ );
+ await commands.measure.stop();
+
+ commands.measure.result[0].browserScripts.pageinfo.droppedFrames =
+ playbackQuality.droppedVideoFrames;
+ commands.measure.result[0].browserScripts.pageinfo.decodedFrames =
+ playbackQuality.totalVideoFrames;
+ }
+}
+
+module.exports = {
+ test,
+ owner: "Network Team",
+ component: "netwerk",
+ name: "youtube-scroll",
+ description: "Measures quality of the video being played.",
+};
diff --git a/netwerk/test/reftest/658949-1-ref.html b/netwerk/test/reftest/658949-1-ref.html
new file mode 100644
index 0000000000..6e6d7e25f6
--- /dev/null
+++ b/netwerk/test/reftest/658949-1-ref.html
@@ -0,0 +1 @@
+<iframe src="data:text/html,ABC"></iframe>
diff --git a/netwerk/test/reftest/658949-1.html b/netwerk/test/reftest/658949-1.html
new file mode 100644
index 0000000000..f61c03a525
--- /dev/null
+++ b/netwerk/test/reftest/658949-1.html
@@ -0,0 +1 @@
+<iframe src="data:text/html,ABC#myRef"></iframe>
diff --git a/netwerk/test/reftest/bug565432-1-ref.html b/netwerk/test/reftest/bug565432-1-ref.html
new file mode 100644
index 0000000000..fade48a48d
--- /dev/null
+++ b/netwerk/test/reftest/bug565432-1-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Test newlines in href</title>
+<ul>
+<li><a href="about:blank">Link</a>
+<li><a href="data:,test">Link</a>
+<li><a href="file:///tmp/test">Link</a>
+<li><a href="ftp://test.invalid/">Link</a>
+<li><a href="gopher://test.invalid/">Link</a>
+<li><a href="http://test.invalid/">Link</a>
+<li><a href="ftp://test.invalid/%0a">Not Link</a>
+</ul>
diff --git a/netwerk/test/reftest/bug565432-1.html b/netwerk/test/reftest/bug565432-1.html
new file mode 100644
index 0000000000..26a7270d06
--- /dev/null
+++ b/netwerk/test/reftest/bug565432-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Test newlines in href</title>
+<ul>
+<li><a href="
+about:blank">Link</a>
+<li><a href="
+data:,test">Link</a>
+<li><a href="
+file:///tmp/test">Link</a>
+<li><a href="
+ftp://test.invalid/">Link</a>
+<li><a href="
+gopher://test.invalid/">Link</a>
+<li><a href="
+http://test.invalid/">Link</a>
+<li><a href="ftp://test.invalid/%0a">Not Link</a>
+</ul>
diff --git a/netwerk/test/reftest/reftest.list b/netwerk/test/reftest/reftest.list
new file mode 100644
index 0000000000..98b5d4fb9a
--- /dev/null
+++ b/netwerk/test/reftest/reftest.list
@@ -0,0 +1,2 @@
+== bug565432-1.html bug565432-1-ref.html
+== 658949-1.html 658949-1-ref.html
diff --git a/netwerk/test/unit/client-cert.p12 b/netwerk/test/unit/client-cert.p12
new file mode 100644
index 0000000000..0f0fd43eba
--- /dev/null
+++ b/netwerk/test/unit/client-cert.p12
Binary files differ
diff --git a/netwerk/test/unit/client-cert.p12.pkcs12spec b/netwerk/test/unit/client-cert.p12.pkcs12spec
new file mode 100644
index 0000000000..548c1a6aa6
--- /dev/null
+++ b/netwerk/test/unit/client-cert.p12.pkcs12spec
@@ -0,0 +1,3 @@
+issuer:Test CA
+subject:Test End-entity
+extension:subjectAlternativeName:example.com
diff --git a/netwerk/test/unit/data/cookies_v10.sqlite b/netwerk/test/unit/data/cookies_v10.sqlite
new file mode 100644
index 0000000000..2301731f8e
--- /dev/null
+++ b/netwerk/test/unit/data/cookies_v10.sqlite
Binary files differ
diff --git a/netwerk/test/unit/data/image.png b/netwerk/test/unit/data/image.png
new file mode 100644
index 0000000000..e0c5d3d6a1
--- /dev/null
+++ b/netwerk/test/unit/data/image.png
Binary files differ
diff --git a/netwerk/test/unit/data/signed_win.exe b/netwerk/test/unit/data/signed_win.exe
new file mode 100644
index 0000000000..de3bb40e84
--- /dev/null
+++ b/netwerk/test/unit/data/signed_win.exe
Binary files differ
diff --git a/netwerk/test/unit/data/system_root.lnk b/netwerk/test/unit/data/system_root.lnk
new file mode 100644
index 0000000000..e5885ce9a5
--- /dev/null
+++ b/netwerk/test/unit/data/system_root.lnk
Binary files differ
diff --git a/netwerk/test/unit/data/test_psl.txt b/netwerk/test/unit/data/test_psl.txt
new file mode 100644
index 0000000000..fa6e0d4cec
--- /dev/null
+++ b/netwerk/test/unit/data/test_psl.txt
@@ -0,0 +1,98 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// null input.
+checkPublicSuffix(null, null);
+// Mixed case.
+checkPublicSuffix('COM', null);
+checkPublicSuffix('example.COM', 'example.com');
+checkPublicSuffix('WwW.example.COM', 'example.com');
+// Leading dot.
+checkPublicSuffix('.com', null);
+checkPublicSuffix('.example', null);
+checkPublicSuffix('.example.com', null);
+checkPublicSuffix('.example.example', null);
+// Unlisted TLD.
+checkPublicSuffix('example', null);
+checkPublicSuffix('example.example', 'example.example');
+checkPublicSuffix('b.example.example', 'example.example');
+checkPublicSuffix('a.b.example.example', 'example.example');
+// Listed, but non-Internet, TLD.
+//checkPublicSuffix('local', null);
+//checkPublicSuffix('example.local', null);
+//checkPublicSuffix('b.example.local', null);
+//checkPublicSuffix('a.b.example.local', null);
+// TLD with only 1 rule.
+checkPublicSuffix('biz', null);
+checkPublicSuffix('domain.biz', 'domain.biz');
+checkPublicSuffix('b.domain.biz', 'domain.biz');
+checkPublicSuffix('a.b.domain.biz', 'domain.biz');
+// TLD with some 2-level rules.
+checkPublicSuffix('com', null);
+checkPublicSuffix('example.com', 'example.com');
+checkPublicSuffix('b.example.com', 'example.com');
+checkPublicSuffix('a.b.example.com', 'example.com');
+checkPublicSuffix('uk.com', null);
+checkPublicSuffix('example.uk.com', 'example.uk.com');
+checkPublicSuffix('b.example.uk.com', 'example.uk.com');
+checkPublicSuffix('a.b.example.uk.com', 'example.uk.com');
+checkPublicSuffix('test.ac', 'test.ac');
+// TLD with only 1 (wildcard) rule.
+checkPublicSuffix('bd', null);
+checkPublicSuffix('c.bd', null);
+checkPublicSuffix('b.c.bd', 'b.c.bd');
+checkPublicSuffix('a.b.c.bd', 'b.c.bd');
+// More complex TLD.
+checkPublicSuffix('jp', null);
+checkPublicSuffix('test.jp', 'test.jp');
+checkPublicSuffix('www.test.jp', 'test.jp');
+checkPublicSuffix('ac.jp', null);
+checkPublicSuffix('test.ac.jp', 'test.ac.jp');
+checkPublicSuffix('www.test.ac.jp', 'test.ac.jp');
+checkPublicSuffix('kyoto.jp', null);
+checkPublicSuffix('test.kyoto.jp', 'test.kyoto.jp');
+checkPublicSuffix('ide.kyoto.jp', null);
+checkPublicSuffix('b.ide.kyoto.jp', 'b.ide.kyoto.jp');
+checkPublicSuffix('a.b.ide.kyoto.jp', 'b.ide.kyoto.jp');
+checkPublicSuffix('c.kobe.jp', null);
+checkPublicSuffix('b.c.kobe.jp', 'b.c.kobe.jp');
+checkPublicSuffix('a.b.c.kobe.jp', 'b.c.kobe.jp');
+checkPublicSuffix('city.kobe.jp', 'city.kobe.jp');
+checkPublicSuffix('www.city.kobe.jp', 'city.kobe.jp');
+// TLD with a wildcard rule and exceptions.
+checkPublicSuffix('ck', null);
+checkPublicSuffix('test.ck', null);
+checkPublicSuffix('b.test.ck', 'b.test.ck');
+checkPublicSuffix('a.b.test.ck', 'b.test.ck');
+checkPublicSuffix('www.ck', 'www.ck');
+checkPublicSuffix('www.www.ck', 'www.ck');
+// US K12.
+checkPublicSuffix('us', null);
+checkPublicSuffix('test.us', 'test.us');
+checkPublicSuffix('www.test.us', 'test.us');
+checkPublicSuffix('ak.us', null);
+checkPublicSuffix('test.ak.us', 'test.ak.us');
+checkPublicSuffix('www.test.ak.us', 'test.ak.us');
+checkPublicSuffix('k12.ak.us', null);
+checkPublicSuffix('test.k12.ak.us', 'test.k12.ak.us');
+checkPublicSuffix('www.test.k12.ak.us', 'test.k12.ak.us');
+// IDN labels.
+checkPublicSuffix('食狮.com.cn', '食狮.com.cn');
+checkPublicSuffix('食狮.公司.cn', '食狮.公司.cn');
+checkPublicSuffix('www.食狮.公司.cn', '食狮.公司.cn');
+checkPublicSuffix('shishi.公司.cn', 'shishi.公司.cn');
+checkPublicSuffix('公司.cn', null);
+checkPublicSuffix('食狮.中国', '食狮.中国');
+checkPublicSuffix('www.食狮.中国', '食狮.中国');
+checkPublicSuffix('shishi.中国', 'shishi.中国');
+checkPublicSuffix('中国', null);
+// Same as above, but punycoded.
+checkPublicSuffix('xn--85x722f.com.cn', 'xn--85x722f.com.cn');
+checkPublicSuffix('xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn');
+checkPublicSuffix('www.xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn');
+checkPublicSuffix('shishi.xn--55qx5d.cn', 'shishi.xn--55qx5d.cn');
+checkPublicSuffix('xn--55qx5d.cn', null);
+checkPublicSuffix('xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s');
+checkPublicSuffix('www.xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s');
+checkPublicSuffix('shishi.xn--fiqs8s', 'shishi.xn--fiqs8s');
+checkPublicSuffix('xn--fiqs8s', null);
diff --git a/netwerk/test/unit/data/test_readline1.txt b/netwerk/test/unit/data/test_readline1.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline1.txt
diff --git a/netwerk/test/unit/data/test_readline2.txt b/netwerk/test/unit/data/test_readline2.txt
new file mode 100644
index 0000000000..67c3297611
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline2.txt
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/netwerk/test/unit/data/test_readline3.txt b/netwerk/test/unit/data/test_readline3.txt
new file mode 100644
index 0000000000..decdc51878
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline3.txt
@@ -0,0 +1,3 @@
+
+
+
diff --git a/netwerk/test/unit/data/test_readline4.txt b/netwerk/test/unit/data/test_readline4.txt
new file mode 100644
index 0000000000..ca25c36540
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline4.txt
@@ -0,0 +1,3 @@
+1
+ 23 456
+78901
diff --git a/netwerk/test/unit/data/test_readline5.txt b/netwerk/test/unit/data/test_readline5.txt
new file mode 100644
index 0000000000..8463b7858e
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline5.txt
@@ -0,0 +1 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE \ No newline at end of file
diff --git a/netwerk/test/unit/data/test_readline6.txt b/netwerk/test/unit/data/test_readline6.txt
new file mode 100644
index 0000000000..872c40afc4
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline6.txt
@@ -0,0 +1 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE
diff --git a/netwerk/test/unit/data/test_readline7.txt b/netwerk/test/unit/data/test_readline7.txt
new file mode 100644
index 0000000000..59ee122ce1
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline7.txt
@@ -0,0 +1,2 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE
+ \ No newline at end of file
diff --git a/netwerk/test/unit/data/test_readline8.txt b/netwerk/test/unit/data/test_readline8.txt
new file mode 100644
index 0000000000..ff6fc09a4a
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline8.txt
@@ -0,0 +1 @@
+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE \ No newline at end of file
diff --git a/netwerk/test/unit/head_cache.js b/netwerk/test/unit/head_cache.js
new file mode 100644
index 0000000000..7ec0e11f97
--- /dev/null
+++ b/netwerk/test/unit/head_cache.js
@@ -0,0 +1,137 @@
+"use strict";
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+function evict_cache_entries(where) {
+ var clearDisk = !where || where == "disk" || where == "all";
+ var clearMem = !where || where == "memory" || where == "all";
+
+ var storage;
+
+ if (clearMem) {
+ storage = Services.cache2.memoryCacheStorage(
+ Services.loadContextInfo.default
+ );
+ storage.asyncEvictStorage(null);
+ }
+
+ if (clearDisk) {
+ storage = Services.cache2.diskCacheStorage(
+ Services.loadContextInfo.default
+ );
+ storage.asyncEvictStorage(null);
+ }
+}
+
+function createURI(urispec) {
+ return Services.io.newURI(urispec);
+}
+
+function getCacheStorage(where, lci) {
+ if (!lci) {
+ lci = Services.loadContextInfo.default;
+ }
+ switch (where) {
+ case "disk":
+ return Services.cache2.diskCacheStorage(lci);
+ case "memory":
+ return Services.cache2.memoryCacheStorage(lci);
+ case "pin":
+ return Services.cache2.pinningCacheStorage(lci);
+ }
+ return null;
+}
+
+function asyncOpenCacheEntry(key, where, flags, lci, callback) {
+ key = createURI(key);
+
+ function CacheListener() {}
+ CacheListener.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
+
+ onCacheEntryCheck(entry) {
+ if (typeof callback === "object") {
+ return callback.onCacheEntryCheck(entry);
+ }
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable(entry, isnew, status) {
+ if (typeof callback === "object") {
+ // Root us at the callback
+ callback.__cache_listener_root = this;
+ callback.onCacheEntryAvailable(entry, isnew, status);
+ } else {
+ callback(status, entry);
+ }
+ },
+
+ run() {
+ var storage = getCacheStorage(where, lci);
+ storage.asyncOpenURI(key, "", flags, this);
+ },
+ };
+
+ new CacheListener().run();
+}
+
+function syncWithCacheIOThread(callback, force) {
+ if (force) {
+ asyncOpenCacheEntry(
+ "http://nonexistententry/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ function (status, entry) {
+ Assert.equal(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ callback();
+ }
+ );
+ } else {
+ callback();
+ }
+}
+
+function get_device_entry_count(where, lci, continuation) {
+ var storage = getCacheStorage(where, lci);
+ if (!storage) {
+ continuation(-1, 0);
+ return;
+ }
+
+ var visitor = {
+ onCacheStorageInfo(entryCount, consumption) {
+ executeSoon(function () {
+ continuation(entryCount, consumption);
+ });
+ },
+ };
+
+ // get the device entry count
+ storage.asyncVisitStorage(visitor, false);
+}
+
+function asyncCheckCacheEntryPresence(key, where, shouldExist, continuation) {
+ asyncOpenCacheEntry(
+ key,
+ where,
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ function (status, entry) {
+ if (shouldExist) {
+ dump("TEST-INFO | checking cache key " + key + " exists @ " + where);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.ok(!!entry);
+ } else {
+ dump(
+ "TEST-INFO | checking cache key " + key + " doesn't exist @ " + where
+ );
+ Assert.equal(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ Assert.equal(null, entry);
+ }
+ continuation();
+ }
+ );
+}
diff --git a/netwerk/test/unit/head_cache2.js b/netwerk/test/unit/head_cache2.js
new file mode 100644
index 0000000000..f7c865872a
--- /dev/null
+++ b/netwerk/test/unit/head_cache2.js
@@ -0,0 +1,429 @@
+/* import-globals-from head_cache.js */
+/* import-globals-from head_channels.js */
+
+"use strict";
+
+var callbacks = [];
+
+// Expect an existing entry
+const NORMAL = 0;
+// Expect a new entry
+const NEW = 1 << 0;
+// Return early from onCacheEntryCheck and set the callback to state it expects onCacheEntryCheck to happen
+const NOTVALID = 1 << 1;
+// Throw from onCacheEntryAvailable
+const THROWAVAIL = 1 << 2;
+// Open entry for reading-only
+const READONLY = 1 << 3;
+// Expect the entry to not be found
+const NOTFOUND = 1 << 4;
+// Return ENTRY_NEEDS_REVALIDATION from onCacheEntryCheck
+const REVAL = 1 << 5;
+// Return ENTRY_PARTIAL from onCacheEntryCheck, in combo with NEW or RECREATE bypasses check for emptiness of the entry
+const PARTIAL = 1 << 6;
+// Expect the entry is doomed, i.e. the output stream should not be possible to open
+const DOOMED = 1 << 7;
+// Don't trigger the go-on callback until the entry is written
+const WAITFORWRITE = 1 << 8;
+// Don't write data (i.e. don't open output stream)
+const METAONLY = 1 << 9;
+// Do recreation of an existing cache entry
+const RECREATE = 1 << 10;
+// Do not give me the entry
+const NOTWANTED = 1 << 11;
+// Tell the cache to wait for the entry to be completely written first
+const COMPLETE = 1 << 12;
+// Don't write meta/data and don't set valid in the callback, consumer will do it manually
+const DONTFILL = 1 << 13;
+// Used in combination with METAONLY, don't call setValid() on the entry after metadata has been set
+const DONTSETVALID = 1 << 14;
+// Notify before checking the data, useful for proper callback ordering checks
+const NOTIFYBEFOREREAD = 1 << 15;
+// It's allowed to not get an existing entry (result of opening is undetermined)
+const MAYBE_NEW = 1 << 16;
+
+var log_c2 = true;
+function LOG_C2(o, m) {
+ if (!log_c2) {
+ return;
+ }
+ if (!m) {
+ dump("TEST-INFO | CACHE2: " + o + "\n");
+ } else {
+ dump(
+ "TEST-INFO | CACHE2: callback #" +
+ o.order +
+ "(" +
+ (o.workingData ? o.workingData.substr(0, 10) : "---") +
+ ") " +
+ m +
+ "\n"
+ );
+ }
+}
+
+function pumpReadStream(inputStream, goon) {
+ if (inputStream.isNonBlocking()) {
+ // non-blocking stream, must read via pump
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
+ Ci.nsIInputStreamPump
+ );
+ pump.init(inputStream, 0, 0, true);
+ let data = "";
+ pump.asyncRead({
+ onStartRequest(aRequest) {},
+ onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ wrapper.init(aInputStream);
+ var str = wrapper.read(wrapper.available());
+ LOG_C2("reading data '" + str.substring(0, 5) + "'");
+ data += str;
+ },
+ onStopRequest(aRequest, aStatusCode) {
+ LOG_C2("done reading data: " + aStatusCode);
+ Assert.equal(aStatusCode, Cr.NS_OK);
+ goon(data);
+ },
+ });
+ } else {
+ // blocking stream
+ let data = read_stream(inputStream, inputStream.available());
+ goon(data);
+ }
+}
+
+OpenCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
+ onCacheEntryCheck(entry) {
+ LOG_C2(this, "onCacheEntryCheck");
+ Assert.ok(!this.onCheckPassed);
+ this.onCheckPassed = true;
+
+ if (this.behavior & NOTVALID) {
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ }
+
+ if (this.behavior & NOTWANTED) {
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NOT_WANTED");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_NOT_WANTED;
+ }
+
+ Assert.equal(entry.getMetaDataElement("meto"), this.workingMetadata);
+
+ // check for sane flag combination
+ Assert.notEqual(this.behavior & (REVAL | PARTIAL), REVAL | PARTIAL);
+
+ if (this.behavior & (REVAL | PARTIAL)) {
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NEEDS_REVALIDATION");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_NEEDS_REVALIDATION;
+ }
+
+ if (this.behavior & COMPLETE) {
+ LOG_C2(
+ this,
+ "onCacheEntryCheck DONE, return RECHECK_AFTER_WRITE_FINISHED"
+ );
+ // Specific to the new backend because of concurrent read/write:
+ // when a consumer returns RECHECK_AFTER_WRITE_FINISHED from onCacheEntryCheck
+ // the cache calls this callback again after the entry write has finished.
+ // This gives the consumer a chance to recheck completeness of the entry
+ // again.
+ // Thus, we reset state as onCheck would have never been called.
+ this.onCheckPassed = false;
+ // Don't return RECHECK_AFTER_WRITE_FINISHED on second call of onCacheEntryCheck.
+ this.behavior &= ~COMPLETE;
+ return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED;
+ }
+
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable(entry, isnew, status) {
+ if (this.behavior & MAYBE_NEW && isnew) {
+ this.behavior |= NEW;
+ }
+
+ LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
+ Assert.ok(!this.onAvailPassed);
+ this.onAvailPassed = true;
+
+ Assert.equal(isnew, !!(this.behavior & NEW));
+
+ if (this.behavior & (NOTFOUND | NOTWANTED)) {
+ Assert.equal(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ Assert.ok(!entry);
+ if (this.behavior & THROWAVAIL) {
+ this.throwAndNotify(entry);
+ }
+ this.goon(entry);
+ } else if (this.behavior & (NEW | RECREATE)) {
+ Assert.ok(!!entry);
+
+ if (this.behavior & RECREATE) {
+ entry = entry.recreate();
+ Assert.ok(!!entry);
+ }
+
+ if (this.behavior & THROWAVAIL) {
+ this.throwAndNotify(entry);
+ }
+
+ if (!(this.behavior & WAITFORWRITE)) {
+ this.goon(entry);
+ }
+
+ if (!(this.behavior & PARTIAL)) {
+ try {
+ entry.getMetaDataElement("meto");
+ Assert.ok(false);
+ } catch (ex) {}
+ }
+
+ if (this.behavior & DONTFILL) {
+ Assert.equal(false, this.behavior & WAITFORWRITE);
+ return;
+ }
+
+ let self = this;
+ executeSoon(function () {
+ // emulate network latency
+ entry.setMetaDataElement("meto", self.workingMetadata);
+ entry.metaDataReady();
+ if (self.behavior & METAONLY) {
+ // Since forcing GC/CC doesn't trigger OnWriterClosed, we have to set the entry valid manually :(
+ if (!(self.behavior & DONTSETVALID)) {
+ entry.setValid();
+ }
+
+ entry.close();
+ if (self.behavior & WAITFORWRITE) {
+ self.goon(entry);
+ }
+
+ return;
+ }
+ executeSoon(function () {
+ // emulate more network latency
+ if (self.behavior & DOOMED) {
+ LOG_C2(self, "checking doom state");
+ try {
+ let os = entry.openOutputStream(0, -1);
+ // Unfortunately, in the undetermined state we cannot even check whether the entry
+ // is actually doomed or not.
+ os.close();
+ Assert.ok(!!(self.behavior & MAYBE_NEW));
+ } catch (ex) {
+ Assert.ok(true);
+ }
+ if (self.behavior & WAITFORWRITE) {
+ self.goon(entry);
+ }
+ return;
+ }
+
+ var offset = self.behavior & PARTIAL ? entry.dataSize : 0;
+ LOG_C2(self, "openOutputStream @ " + offset);
+ let os = entry.openOutputStream(offset, -1);
+ LOG_C2(self, "writing data");
+ var wrt = os.write(self.workingData, self.workingData.length);
+ Assert.equal(wrt, self.workingData.length);
+ os.close();
+ if (self.behavior & WAITFORWRITE) {
+ self.goon(entry);
+ }
+
+ entry.close();
+ });
+ });
+ } else {
+ // NORMAL
+ Assert.ok(!!entry);
+ Assert.equal(entry.getMetaDataElement("meto"), this.workingMetadata);
+ if (this.behavior & THROWAVAIL) {
+ this.throwAndNotify(entry);
+ }
+ if (this.behavior & NOTIFYBEFOREREAD) {
+ this.goon(entry, true);
+ }
+
+ let self = this;
+ pumpReadStream(entry.openInputStream(0), function (data) {
+ Assert.equal(data, self.workingData);
+ self.onDataCheckPassed = true;
+ LOG_C2(self, "entry read done");
+ self.goon(entry);
+ entry.close();
+ });
+ }
+ },
+ selfCheck() {
+ LOG_C2(this, "selfCheck");
+
+ Assert.ok(this.onCheckPassed || this.behavior & MAYBE_NEW);
+ Assert.ok(this.onAvailPassed);
+ Assert.ok(this.onDataCheckPassed || this.behavior & MAYBE_NEW);
+ },
+ throwAndNotify(entry) {
+ LOG_C2(this, "Throwing");
+ var self = this;
+ executeSoon(function () {
+ LOG_C2(self, "Notifying");
+ self.goon(entry);
+ });
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ },
+};
+
+function OpenCallback(behavior, workingMetadata, workingData, goon) {
+ this.behavior = behavior;
+ this.workingMetadata = workingMetadata;
+ this.workingData = workingData;
+ this.goon = goon;
+ this.onCheckPassed =
+ (!!(behavior & (NEW | RECREATE)) || !workingMetadata) &&
+ !(behavior & NOTVALID);
+ this.onAvailPassed = false;
+ this.onDataCheckPassed =
+ !!(behavior & (NEW | RECREATE | NOTWANTED)) || !workingMetadata;
+ callbacks.push(this);
+ this.order = callbacks.length;
+}
+
+VisitCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ onCacheStorageInfo(num, consumption) {
+ LOG_C2(this, "onCacheStorageInfo: num=" + num + ", size=" + consumption);
+ Assert.equal(this.num, num);
+ Assert.equal(this.consumption, consumption);
+ if (!this.entries) {
+ this.notify();
+ }
+ },
+ onCacheEntryInfo(
+ aURI,
+ aIdEnhance,
+ aDataSize,
+ aAltDataSize,
+ aFetchCount,
+ aLastModifiedTime,
+ aExpirationTime,
+ aPinned,
+ aInfo
+ ) {
+ var key = (aIdEnhance ? aIdEnhance + ":" : "") + aURI.asciiSpec;
+ LOG_C2(this, "onCacheEntryInfo: key=" + key);
+
+ function findCacheIndex(element) {
+ if (typeof element === "string") {
+ return element === key;
+ } else if (typeof element === "object") {
+ return (
+ element.uri === key &&
+ element.lci.isAnonymous === aInfo.isAnonymous &&
+ ChromeUtils.isOriginAttributesEqual(
+ element.lci.originAttributes,
+ aInfo.originAttributes
+ )
+ );
+ }
+
+ return false;
+ }
+
+ Assert.ok(!!this.entries);
+
+ var index = this.entries.findIndex(findCacheIndex);
+ Assert.ok(index > -1);
+
+ this.entries.splice(index, 1);
+ },
+ onCacheEntryVisitCompleted() {
+ LOG_C2(this, "onCacheEntryVisitCompleted");
+ if (this.entries) {
+ Assert.equal(this.entries.length, 0);
+ }
+ this.notify();
+ },
+ notify() {
+ Assert.ok(!!this.goon);
+ var goon = this.goon;
+ this.goon = null;
+ executeSoon(goon);
+ },
+ selfCheck() {
+ Assert.ok(!this.entries || !this.entries.length);
+ },
+};
+
+function VisitCallback(num, consumption, entries, goon) {
+ this.num = num;
+ this.consumption = consumption;
+ this.entries = entries;
+ this.goon = goon;
+ callbacks.push(this);
+ this.order = callbacks.length;
+}
+
+EvictionCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryDoomCallback"]),
+ onCacheEntryDoomed(result) {
+ Assert.equal(this.expectedSuccess, result == Cr.NS_OK);
+ this.goon();
+ },
+ selfCheck() {},
+};
+
+function EvictionCallback(success, goon) {
+ this.expectedSuccess = success;
+ this.goon = goon;
+ callbacks.push(this);
+ this.order = callbacks.length;
+}
+
+MultipleCallbacks.prototype = {
+ fired() {
+ if (--this.pending == 0) {
+ var self = this;
+ if (this.delayed) {
+ executeSoon(function () {
+ self.goon();
+ });
+ } else {
+ this.goon();
+ }
+ }
+ },
+ add() {
+ ++this.pending;
+ },
+};
+
+function MultipleCallbacks(number, goon, delayed) {
+ this.pending = number;
+ this.goon = goon;
+ this.delayed = delayed;
+}
+
+function wait_for_cache_index(continue_func) {
+ // This callback will not fire before the index is in the ready state. nsICacheStorage.exists() will
+ // no longer throw after this point.
+ Services.cache2.asyncGetDiskConsumption({
+ onNetworkCacheDiskConsumption() {
+ continue_func();
+ },
+ // eslint-disable-next-line mozilla/use-chromeutils-generateqi
+ QueryInterface() {
+ return this;
+ },
+ });
+}
+
+function finish_cache2_test() {
+ callbacks.forEach(function (callback, index) {
+ callback.selfCheck();
+ });
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/head_channels.js b/netwerk/test/unit/head_channels.js
new file mode 100644
index 0000000000..140135339a
--- /dev/null
+++ b/netwerk/test/unit/head_channels.js
@@ -0,0 +1,527 @@
+/**
+ * Read count bytes from stream and return as a String object
+ */
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+
+function read_stream(stream, count) {
+ /* assume stream has non-ASCII data */
+ var wrapper = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ wrapper.setInputStream(stream);
+ /* JS methods can be called with a maximum of 65535 arguments, and input
+ streams don't have to return all the data they make .available() when
+ asked to .read() that number of bytes. */
+ var data = [];
+ while (count > 0) {
+ var bytes = wrapper.readByteArray(Math.min(65535, count));
+ data.push(String.fromCharCode.apply(null, bytes));
+ count -= bytes.length;
+ if (!bytes.length) {
+ do_throw("Nothing read from input stream!");
+ }
+ }
+ return data.join("");
+}
+
+const CL_EXPECT_FAILURE = 0x1;
+const CL_EXPECT_GZIP = 0x2;
+const CL_EXPECT_3S_DELAY = 0x4;
+const CL_SUSPEND = 0x8;
+const CL_ALLOW_UNKNOWN_CL = 0x10;
+const CL_EXPECT_LATE_FAILURE = 0x20;
+const CL_FROM_CACHE = 0x40; // Response must be from the cache
+const CL_NOT_FROM_CACHE = 0x80; // Response must NOT be from the cache
+const CL_IGNORE_CL = 0x100; // don't bother to verify the content-length
+const CL_IGNORE_DELAYS = 0x200; // don't throw if channel returns after a long delay
+
+const SUSPEND_DELAY = 3000;
+
+/**
+ * A stream listener that calls a callback function with a specified
+ * context and the received data when the channel is loaded.
+ *
+ * Signature of the closure:
+ * void closure(in nsIRequest request, in ACString data, in JSObject context);
+ *
+ * This listener makes sure that various parts of the channel API are
+ * implemented correctly and that the channel's status is a success code
+ * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags
+ * to allow a failure code)
+ *
+ * Note that it also requires a valid content length on the channel and
+ * is thus not fully generic.
+ */
+function ChannelListener(closure, ctx, flags) {
+ this._closure = closure;
+ this._closurectx = ctx;
+ this._flags = flags;
+ this._isFromCache = false;
+ this._cacheEntryId = undefined;
+}
+ChannelListener.prototype = {
+ _closure: null,
+ _closurectx: null,
+ _buffer: "",
+ _got_onstartrequest: false,
+ _got_onstoprequest: false,
+ _contentLen: -1,
+ _lastEvent: 0,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ try {
+ if (this._got_onstartrequest) {
+ do_throw("Got second onStartRequest event!");
+ }
+ this._got_onstartrequest = true;
+ this._lastEvent = Date.now();
+
+ try {
+ this._isFromCache = request
+ .QueryInterface(Ci.nsICacheInfoChannel)
+ .isFromCache();
+ } catch (e) {}
+
+ var thrown = false;
+ try {
+ this._cacheEntryId = request
+ .QueryInterface(Ci.nsICacheInfoChannel)
+ .getCacheEntryId();
+ } catch (e) {
+ thrown = true;
+ }
+ if (this._isFromCache && thrown) {
+ do_throw("Should get a CacheEntryId");
+ } else if (!this._isFromCache && !thrown) {
+ do_throw("Shouldn't get a CacheEntryId");
+ }
+
+ request.QueryInterface(Ci.nsIChannel);
+ try {
+ this._contentLen = request.contentLength;
+ } catch (ex) {
+ if (!(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))) {
+ do_throw("Could not get contentLength");
+ }
+ }
+ if (!request.isPending()) {
+ do_throw("request reports itself as not pending from onStartRequest!");
+ }
+ if (
+ this._contentLen == -1 &&
+ !(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))
+ ) {
+ do_throw("Content length is unknown in onStartRequest!");
+ }
+
+ if (this._flags & CL_FROM_CACHE) {
+ request.QueryInterface(Ci.nsICachingChannel);
+ if (!request.isFromCache()) {
+ do_throw("Response is not from the cache (CL_FROM_CACHE)");
+ }
+ }
+ if (this._flags & CL_NOT_FROM_CACHE) {
+ request.QueryInterface(Ci.nsICachingChannel);
+ if (request.isFromCache()) {
+ do_throw("Response is from the cache (CL_NOT_FROM_CACHE)");
+ }
+ }
+
+ if (this._flags & CL_SUSPEND) {
+ request.suspend();
+ do_timeout(SUSPEND_DELAY, function () {
+ request.resume();
+ });
+ }
+ } catch (ex) {
+ do_throw("Error in onStartRequest: " + ex);
+ }
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ try {
+ let current = Date.now();
+
+ if (!this._got_onstartrequest) {
+ do_throw("onDataAvailable without onStartRequest event!");
+ }
+ if (this._got_onstoprequest) {
+ do_throw("onDataAvailable after onStopRequest event!");
+ }
+ if (!request.isPending()) {
+ do_throw("request reports itself as not pending from onDataAvailable!");
+ }
+ if (this._flags & CL_EXPECT_FAILURE) {
+ do_throw("Got data despite expecting a failure");
+ }
+
+ if (
+ !(this._flags & CL_IGNORE_DELAYS) &&
+ current - this._lastEvent >= SUSPEND_DELAY &&
+ !(this._flags & CL_EXPECT_3S_DELAY)
+ ) {
+ do_throw("Data received after significant unexpected delay");
+ } else if (
+ current - this._lastEvent < SUSPEND_DELAY &&
+ this._flags & CL_EXPECT_3S_DELAY
+ ) {
+ do_throw("Data received sooner than expected");
+ } else if (
+ current - this._lastEvent >= SUSPEND_DELAY &&
+ this._flags & CL_EXPECT_3S_DELAY
+ ) {
+ this._flags &= ~CL_EXPECT_3S_DELAY;
+ } // No more delays expected
+
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ this._lastEvent = current;
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest(request, status) {
+ try {
+ var success = Components.isSuccessCode(status);
+ if (!this._got_onstartrequest) {
+ do_throw("onStopRequest without onStartRequest event!");
+ }
+ if (this._got_onstoprequest) {
+ do_throw("Got second onStopRequest event!");
+ }
+ this._got_onstoprequest = true;
+ if (
+ this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE) &&
+ success
+ ) {
+ do_throw(
+ "Should have failed to load URL (status is " +
+ status.toString(16) +
+ ")"
+ );
+ } else if (
+ !(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) &&
+ !success
+ ) {
+ do_throw("Failed to load URL: " + status.toString(16));
+ }
+ if (status != request.status) {
+ do_throw("request.status does not match status arg to onStopRequest!");
+ }
+ if (request.isPending()) {
+ do_throw("request reports itself as pending from onStopRequest!");
+ }
+ if (
+ !(
+ this._flags &
+ (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE | CL_IGNORE_CL)
+ ) &&
+ !(this._flags & CL_EXPECT_GZIP) &&
+ this._contentLen != -1
+ ) {
+ Assert.equal(this._buffer.length, this._contentLen);
+ }
+ } catch (ex) {
+ do_throw("Error in onStopRequest: " + ex);
+ }
+ try {
+ this._closure(
+ request,
+ this._buffer,
+ this._closurectx,
+ this._isFromCache,
+ this._cacheEntryId
+ );
+ this._closurectx = null;
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+var ES_ABORT_REDIRECT = 0x01;
+
+function ChannelEventSink(flags) {
+ this._flags = flags;
+}
+
+ChannelEventSink.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+ if (this._flags & ES_ABORT_REDIRECT) {
+ throw Components.Exception("", Cr.NS_BINDING_ABORTED);
+ }
+
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ },
+};
+
+/**
+ * A helper class to construct origin attributes.
+ */
+function OriginAttributes(inIsolatedMozBrowser, privateId) {
+ this.inIsolatedMozBrowser = inIsolatedMozBrowser;
+ this.privateBrowsingId = privateId;
+}
+OriginAttributes.prototype = {
+ inIsolatedMozBrowser: false,
+ privateBrowsingId: 0,
+};
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let pem = readFile(certFile)
+ .replace(/-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----/, "")
+ .replace(/[\r\n]/g, "");
+ certdb.addCertFromBase64(pem, trustString);
+}
+
+// Helper code to test nsISerializable
+function serialize_to_escaped_string(obj) {
+ let objectOutStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIObjectOutputStream
+ );
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ objectOutStream.setOutputStream(pipe.outputStream);
+ objectOutStream.writeCompoundObject(obj, Ci.nsISupports, true);
+ objectOutStream.close();
+
+ let objectInStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIObjectInputStream
+ );
+ objectInStream.setInputStream(pipe.inputStream);
+ let data = [];
+ // This reads all the data from the stream until an error occurs.
+ while (true) {
+ try {
+ let bytes = objectInStream.readByteArray(1);
+ data.push(String.fromCharCode.apply(null, bytes));
+ } catch (e) {
+ break;
+ }
+ }
+ return escape(data.join(""));
+}
+
+function deserialize_from_escaped_string(str) {
+ let payload = unescape(str);
+ let data = [];
+ let i = 0;
+ while (i < payload.length) {
+ data.push(payload.charCodeAt(i++));
+ }
+
+ let objectOutStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIObjectOutputStream
+ );
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ objectOutStream.setOutputStream(pipe.outputStream);
+ objectOutStream.writeByteArray(data);
+ objectOutStream.close();
+
+ let objectInStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIObjectInputStream
+ );
+ objectInStream.setInputStream(pipe.inputStream);
+ return objectInStream.readObject(true);
+}
+
+async function asyncStartTLSTestServer(
+ serverBinName,
+ certsPath,
+ addDefaultRoot = true
+) {
+ const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+ );
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ // The trusted CA that is typically used for "good" certificates.
+ if (addDefaultRoot) {
+ addCertFromFile(certdb, `${certsPath}/test-ca.pem`, "CTu,u,u");
+ }
+
+ const CALLBACK_PORT = 8444;
+
+ let greBinDir = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+ Services.env.set("DYLD_LIBRARY_PATH", greBinDir.path);
+ // TODO(bug 1107794): Android libraries are in /data/local/xpcb, but "GreBinD"
+ // does not return this path on Android, so hard code it here.
+ Services.env.set("LD_LIBRARY_PATH", greBinDir.path + ":/data/local/xpcb");
+ Services.env.set("MOZ_TLS_SERVER_DEBUG_LEVEL", "3");
+ Services.env.set("MOZ_TLS_SERVER_CALLBACK_PORT", CALLBACK_PORT);
+
+ let httpServer = new HttpServer();
+ let serverReady = new Promise(resolve => {
+ httpServer.registerPathHandler(
+ "/",
+ function handleServerCallback(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain");
+ let responseBody = "OK!";
+ aResponse.bodyOutputStream.write(responseBody, responseBody.length);
+ executeSoon(function () {
+ httpServer.stop(resolve);
+ });
+ }
+ );
+ httpServer.start(CALLBACK_PORT);
+ });
+
+ let serverBin = _getBinaryUtil(serverBinName);
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(serverBin);
+ let certDir = do_get_file(certsPath, false);
+ Assert.ok(certDir.exists(), `certificate folder (${certsPath}) should exist`);
+ // Using "sql:" causes the SQL DB to be used so we can run tests on Android.
+ process.run(false, ["sql:" + certDir.path, Services.appinfo.processID], 2);
+
+ registerCleanupFunction(function () {
+ process.kill();
+ });
+
+ await serverReady;
+}
+
+function _getBinaryUtil(binaryUtilName) {
+ let utilBin = Services.dirsvc.get("GreD", Ci.nsIFile);
+ // On macOS, GreD is .../Contents/Resources, and most binary utilities
+ // are located there, but certutil is in GreBinD (or .../Contents/MacOS),
+ // so we have to change the path accordingly.
+ if (binaryUtilName === "certutil") {
+ utilBin = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+ }
+ utilBin.append(binaryUtilName + mozinfo.bin_suffix);
+ // If we're testing locally, the above works. If not, the server executable
+ // is in another location.
+ if (!utilBin.exists()) {
+ utilBin = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ while (utilBin.path.includes("xpcshell")) {
+ utilBin = utilBin.parent;
+ }
+ utilBin.append("bin");
+ utilBin.append(binaryUtilName + mozinfo.bin_suffix);
+ }
+ // But maybe we're on Android, where binaries are in /data/local/xpcb.
+ if (!utilBin.exists()) {
+ utilBin.initWithPath("/data/local/xpcb/");
+ utilBin.append(binaryUtilName);
+ }
+ Assert.ok(utilBin.exists(), `Binary util ${binaryUtilName} should exist`);
+ return utilBin;
+}
+
+function promiseAsyncOpen(chan) {
+ return new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((req, buf, ctx, isCache, cacheId) => {
+ resolve({ req, buf, ctx, isCache, cacheId });
+ })
+ );
+ });
+}
+
+function hexStringToBytes(hex) {
+ let bytes = [];
+ for (let hexByteStr of hex.split(/(..)/)) {
+ if (hexByteStr.length) {
+ bytes.push(parseInt(hexByteStr, 16));
+ }
+ }
+ return bytes;
+}
+
+function stringToBytes(str) {
+ return Array.from(str, chr => chr.charCodeAt(0));
+}
+
+function BinaryHttpResponse(status, headerNames, headerValues, content) {
+ this.status = status;
+ this.headerNames = headerNames;
+ this.headerValues = headerValues;
+ this.content = content;
+}
+
+BinaryHttpResponse.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIBinaryHttpResponse"]),
+};
+
+function bytesToString(bytes) {
+ return String.fromCharCode.apply(null, bytes);
+}
+
+function check_http_info(request, expected_httpVersion, expected_proxy) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+
+ Assert.equal(expected_httpVersion, httpVersion);
+ if (expected_proxy) {
+ Assert.equal(httpProxyConnectResponseCode, 200);
+ } else {
+ Assert.equal(httpProxyConnectResponseCode, -1);
+ }
+}
+
+function makeHTTPChannel(url, with_proxy) {
+ function createPrincipal(uri) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ return ssm.createContentPrincipal(Services.io.newURI(uri), {});
+ } catch (e) {
+ return null;
+ }
+ }
+
+ if (with_proxy) {
+ return Services.io
+ .newChannelFromURIWithProxyFlags(
+ Services.io.newURI(url),
+ null,
+ Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL,
+ null,
+ createPrincipal(url),
+ createPrincipal(url),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ )
+ .QueryInterface(Ci.nsIHttpChannel);
+ }
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
diff --git a/netwerk/test/unit/head_cookies.js b/netwerk/test/unit/head_cookies.js
new file mode 100644
index 0000000000..037068689f
--- /dev/null
+++ b/netwerk/test/unit/head_cookies.js
@@ -0,0 +1,1062 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* import-globals-from head_cache.js */
+
+"use strict";
+
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+// Don't pick up default permissions from profile.
+Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
+
+CookieXPCShellUtils.init(this);
+
+function do_check_throws(f, result, stack) {
+ if (!stack) {
+ stack = Components.stack.caller;
+ }
+
+ try {
+ f();
+ } catch (exc) {
+ if (exc.result == result) {
+ return;
+ }
+ do_throw("expected result " + result + ", caught " + exc, stack);
+ }
+ do_throw("expected result " + result + ", none thrown", stack);
+}
+
+// Helper to step a generator function and catch a StopIteration exception.
+function do_run_generator(generator) {
+ try {
+ generator.next();
+ } catch (e) {
+ do_throw("caught exception " + e, Components.stack.caller);
+ }
+}
+
+// Helper to finish a generator function test.
+function do_finish_generator_test(generator) {
+ executeSoon(function () {
+ generator.return();
+ do_test_finished();
+ });
+}
+
+function _observer(generator, topic) {
+ Services.obs.addObserver(this, topic);
+
+ this.generator = generator;
+ this.topic = topic;
+}
+
+_observer.prototype = {
+ observe(subject, topic, data) {
+ Assert.equal(this.topic, topic);
+
+ Services.obs.removeObserver(this, this.topic);
+
+ // Continue executing the generator function.
+ if (this.generator) {
+ do_run_generator(this.generator);
+ }
+
+ this.generator = null;
+ this.topic = null;
+ },
+};
+
+// Close the cookie database. If a generator is supplied, it will be invoked
+// once the close is complete.
+function do_close_profile(generator) {
+ // Register an observer for db close.
+ new _observer(generator, "cookie-db-closed");
+
+ // Close the db.
+ let service = Services.cookies.QueryInterface(Ci.nsIObserver);
+ service.observe(null, "profile-before-change", null);
+}
+
+function _promise_observer(topic) {
+ Services.obs.addObserver(this, topic);
+
+ this.topic = topic;
+ return new Promise(resolve => (this.resolve = resolve));
+}
+
+_promise_observer.prototype = {
+ observe(subject, topic, data) {
+ Assert.equal(this.topic, topic);
+
+ Services.obs.removeObserver(this, this.topic);
+ if (this.resolve) {
+ this.resolve();
+ }
+
+ this.resolve = null;
+ this.topic = null;
+ },
+};
+
+// Close the cookie database. And resolve a promise.
+function promise_close_profile() {
+ // Register an observer for db close.
+ let promise = new _promise_observer("cookie-db-closed");
+
+ // Close the db.
+ let service = Services.cookies.QueryInterface(Ci.nsIObserver);
+ service.observe(null, "profile-before-change", null);
+
+ return promise;
+}
+
+// Load the cookie database.
+function promise_load_profile() {
+ // Register an observer for read completion.
+ let promise = new _promise_observer("cookie-db-read");
+
+ // Load the profile.
+ let service = Services.cookies.QueryInterface(Ci.nsIObserver);
+ service.observe(null, "profile-do-change", "");
+
+ return promise;
+}
+
+// Load the cookie database. If a generator is supplied, it will be invoked
+// once the load is complete.
+function do_load_profile(generator) {
+ // Register an observer for read completion.
+ new _observer(generator, "cookie-db-read");
+
+ // Load the profile.
+ let service = Services.cookies.QueryInterface(Ci.nsIObserver);
+ service.observe(null, "profile-do-change", "");
+}
+
+// Set a single session cookie using http and test the cookie count
+// against 'expected'
+function do_set_single_http_cookie(uri, channel, expected) {
+ Services.cookies.setCookieStringFromHttp(uri, "foo=bar", channel);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri.host), expected);
+}
+
+// Set two cookies; via document.channel and via http request.
+async function do_set_cookies(uri, channel, session, expected) {
+ let suffix = session ? "" : "; max-age=1000";
+
+ // via document.cookie
+ const thirdPartyUrl = "http://third.com/";
+ const contentPage = await CookieXPCShellUtils.loadContentPage(thirdPartyUrl);
+ await contentPage.spawn(
+ [
+ {
+ cookie: "can=has" + suffix,
+ url: uri.spec,
+ },
+ ],
+ async function (obj) {
+ await new this.content.Promise(resolve => {
+ let doc = this.content.document;
+ let ifr = doc.createElement("iframe");
+ ifr.src = obj.url;
+ doc.body.appendChild(ifr);
+ ifr.addEventListener("load", async () => {
+ await this.SpecialPowers.spawn(ifr, [obj.cookie], cookie => {
+ this.content.document.cookie = cookie;
+ });
+ resolve();
+ });
+ });
+ }
+ );
+ await contentPage.close();
+
+ Assert.equal(Services.cookies.countCookiesFromHost(uri.host), expected[0]);
+
+ // via http request
+ Services.cookies.setCookieStringFromHttp(uri, "hot=dog" + suffix, channel);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri.host), expected[1]);
+}
+
+function do_count_cookies() {
+ return Services.cookies.cookies.length;
+}
+
+// Helper object to store cookie data.
+function Cookie(
+ name,
+ value,
+ host,
+ path,
+ expiry,
+ lastAccessed,
+ creationTime,
+ isSession,
+ isSecure,
+ isHttpOnly,
+ inBrowserElement = false,
+ originAttributes = {},
+ sameSite = Ci.nsICookie.SAMESITE_NONE,
+ rawSameSite = Ci.nsICookie.SAMESITE_NONE,
+ schemeMap = Ci.nsICookie.SCHEME_UNSET,
+ isPartitioned = false
+) {
+ this.name = name;
+ this.value = value;
+ this.host = host;
+ this.path = path;
+ this.expiry = expiry;
+ this.lastAccessed = lastAccessed;
+ this.creationTime = creationTime;
+ this.isSession = isSession;
+ this.isSecure = isSecure;
+ this.isHttpOnly = isHttpOnly;
+ this.inBrowserElement = inBrowserElement;
+ this.originAttributes = originAttributes;
+ this.sameSite = sameSite;
+ this.rawSameSite = rawSameSite;
+ this.schemeMap = schemeMap;
+ this.isPartitioned = isPartitioned;
+
+ let strippedHost = host.charAt(0) == "." ? host.slice(1) : host;
+
+ try {
+ this.baseDomain = Services.eTLD.getBaseDomainFromHost(strippedHost);
+ } catch (e) {
+ if (
+ e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
+ e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
+ ) {
+ this.baseDomain = strippedHost;
+ }
+ }
+}
+
+// Object representing a database connection and associated statements. The
+// implementation varies depending on schema version.
+function CookieDatabaseConnection(file, schema) {
+ // Manually generate a cookies.sqlite file with appropriate rows, columns,
+ // and schema version. If it already exists, just set up our statements.
+ let exists = file.exists();
+
+ this.db = Services.storage.openDatabase(file);
+ this.schema = schema;
+ if (!exists) {
+ this.db.schemaVersion = schema;
+ }
+
+ switch (schema) {
+ case 1: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER)"
+ );
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ id, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :id, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :isSecure, \
+ :isHttpOnly)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies WHERE id = :id"
+ );
+
+ break;
+ }
+
+ case 2: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER)"
+ );
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT OR REPLACE INTO moz_cookies ( \
+ id, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :id, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :isSecure, \
+ :isHttpOnly)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies WHERE id = :id"
+ );
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id"
+ );
+
+ break;
+ }
+
+ case 3: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ baseDomain TEXT, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER)"
+ );
+
+ this.db.executeSimpleSQL(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"
+ );
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ id, \
+ baseDomain, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :id, \
+ :baseDomain, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :isSecure, \
+ :isHttpOnly)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies WHERE id = :id"
+ );
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id"
+ );
+
+ break;
+ }
+
+ case 4: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ baseDomain TEXT, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ creationTime INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER \
+ CONSTRAINT moz_uniqueid UNIQUE (name, host, path))"
+ );
+
+ this.db.executeSimpleSQL(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"
+ );
+
+ this.db.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ baseDomain, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ creationTime, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :baseDomain, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :creationTime, \
+ :isSecure, \
+ :isHttpOnly)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies \
+ WHERE name = :name AND host = :host AND path = :path"
+ );
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+ WHERE name = :name AND host = :host AND path = :path"
+ );
+
+ break;
+ }
+
+ case 10: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ baseDomain TEXT, \
+ originAttributes TEXT NOT NULL DEFAULT '', \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ creationTime INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER, \
+ inBrowserElement INTEGER DEFAULT 0, \
+ sameSite INTEGER DEFAULT 0, \
+ rawSameSite INTEGER DEFAULT 0, \
+ CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes))"
+ );
+
+ this.db.executeSimpleSQL(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"
+ );
+
+ this.db.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ this.db.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ name, \
+ value, \
+ host, \
+ baseDomain, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ creationTime, \
+ isSecure, \
+ isHttpOnly, \
+ inBrowserElement, \
+ originAttributes, \
+ sameSite, \
+ rawSameSite \
+ ) VALUES ( \
+ :name, \
+ :value, \
+ :host, \
+ :baseDomain, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :creationTime, \
+ :isSecure, \
+ :isHttpOnly, \
+ :inBrowserElement, \
+ :originAttributes, \
+ :sameSite, \
+ :rawSameSite)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ break;
+ }
+
+ case 11: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ originAttributes TEXT NOT NULL DEFAULT '', \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ creationTime INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER, \
+ inBrowserElement INTEGER DEFAULT 0, \
+ sameSite INTEGER DEFAULT 0, \
+ rawSameSite INTEGER DEFAULT 0, \
+ CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes))"
+ );
+
+ this.db.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ this.db.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ creationTime, \
+ isSecure, \
+ isHttpOnly, \
+ inBrowserElement, \
+ originAttributes, \
+ sameSite, \
+ rawSameSite \
+ ) VALUES ( \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :creationTime, \
+ :isSecure, \
+ :isHttpOnly, \
+ :inBrowserElement, \
+ :originAttributes, \
+ :sameSite, \
+ :rawSameSite)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ break;
+ }
+
+ case 12: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ originAttributes TEXT NOT NULL DEFAULT '', \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ creationTime INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER, \
+ inBrowserElement INTEGER DEFAULT 0, \
+ sameSite INTEGER DEFAULT 0, \
+ rawSameSite INTEGER DEFAULT 0, \
+ schemeMap INTEGER DEFAULT 0, \
+ CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes))"
+ );
+
+ this.db.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ this.db.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ creationTime, \
+ isSecure, \
+ isHttpOnly, \
+ inBrowserElement, \
+ originAttributes, \
+ sameSite, \
+ rawSameSite, \
+ schemeMap \
+ ) VALUES ( \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :creationTime, \
+ :isSecure, \
+ :isHttpOnly, \
+ :inBrowserElement, \
+ :originAttributes, \
+ :sameSite, \
+ :rawSameSite, \
+ :schemeMap)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ break;
+ }
+
+ case 13: {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ originAttributes TEXT NOT NULL DEFAULT '', \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ creationTime INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER, \
+ inBrowserElement INTEGER DEFAULT 0, \
+ sameSite INTEGER DEFAULT 0, \
+ rawSameSite INTEGER DEFAULT 0, \
+ schemeMap INTEGER DEFAULT 0, \
+ isPartitionedAttributeSet INTEGER DEFAULT 0, \
+ CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes))"
+ );
+
+ this.db.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ this.db.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ creationTime, \
+ isSecure, \
+ isHttpOnly, \
+ inBrowserElement, \
+ originAttributes, \
+ sameSite, \
+ rawSameSite, \
+ schemeMap, \
+ isPartitionedAttributeSet \
+ ) VALUES ( \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :creationTime, \
+ :isSecure, \
+ :isHttpOnly, \
+ :inBrowserElement, \
+ :originAttributes, \
+ :sameSite, \
+ :rawSameSite, \
+ :schemeMap, \
+ :isPartitionedAttributeSet)"
+ );
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+ WHERE name = :name AND host = :host AND path = :path AND \
+ originAttributes = :originAttributes"
+ );
+
+ break;
+ }
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+}
+
+CookieDatabaseConnection.prototype = {
+ insertCookie(cookie) {
+ if (!(cookie instanceof Cookie)) {
+ do_throw("not a cookie");
+ }
+
+ switch (this.schema) {
+ case 1:
+ this.stmtInsert.bindByName("id", cookie.creationTime);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ case 2:
+ this.stmtInsert.bindByName("id", cookie.creationTime);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ case 3:
+ this.stmtInsert.bindByName("id", cookie.creationTime);
+ this.stmtInsert.bindByName("baseDomain", cookie.baseDomain);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ case 4:
+ this.stmtInsert.bindByName("baseDomain", cookie.baseDomain);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ case 10:
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("baseDomain", cookie.baseDomain);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ this.stmtInsert.bindByName("inBrowserElement", cookie.inBrowserElement);
+ this.stmtInsert.bindByName(
+ "originAttributes",
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ this.stmtInsert.bindByName("sameSite", cookie.sameSite);
+ this.stmtInsert.bindByName("rawSameSite", cookie.rawSameSite);
+ break;
+
+ case 11:
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ this.stmtInsert.bindByName("inBrowserElement", cookie.inBrowserElement);
+ this.stmtInsert.bindByName(
+ "originAttributes",
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ this.stmtInsert.bindByName("sameSite", cookie.sameSite);
+ this.stmtInsert.bindByName("rawSameSite", cookie.rawSameSite);
+ break;
+
+ case 12:
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ this.stmtInsert.bindByName("inBrowserElement", cookie.inBrowserElement);
+ this.stmtInsert.bindByName(
+ "originAttributes",
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ this.stmtInsert.bindByName("sameSite", cookie.sameSite);
+ this.stmtInsert.bindByName("rawSameSite", cookie.rawSameSite);
+ this.stmtInsert.bindByName("schemeMap", cookie.schemeMap);
+ break;
+
+ case 13:
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ this.stmtInsert.bindByName("inBrowserElement", cookie.inBrowserElement);
+ this.stmtInsert.bindByName(
+ "originAttributes",
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ this.stmtInsert.bindByName("sameSite", cookie.sameSite);
+ this.stmtInsert.bindByName("rawSameSite", cookie.rawSameSite);
+ this.stmtInsert.bindByName("schemeMap", cookie.schemeMap);
+ this.stmtInsert.bindByName(
+ "isPartitionedAttributeSet",
+ cookie.isPartitioned
+ );
+ break;
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+
+ do_execute_stmt(this.stmtInsert);
+ },
+
+ deleteCookie(cookie) {
+ if (!(cookie instanceof Cookie)) {
+ do_throw("not a cookie");
+ }
+
+ switch (this.db.schemaVersion) {
+ case 1:
+ case 2:
+ case 3:
+ this.stmtDelete.bindByName("id", cookie.creationTime);
+ break;
+
+ case 4:
+ this.stmtDelete.bindByName("name", cookie.name);
+ this.stmtDelete.bindByName("host", cookie.host);
+ this.stmtDelete.bindByName("path", cookie.path);
+ break;
+
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ this.stmtDelete.bindByName("name", cookie.name);
+ this.stmtDelete.bindByName("host", cookie.host);
+ this.stmtDelete.bindByName("path", cookie.path);
+ this.stmtDelete.bindByName(
+ "originAttributes",
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ break;
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+
+ do_execute_stmt(this.stmtDelete);
+ },
+
+ updateCookie(cookie) {
+ if (!(cookie instanceof Cookie)) {
+ do_throw("not a cookie");
+ }
+
+ switch (this.db.schemaVersion) {
+ case 1:
+ do_throw("can't update a schema 1 cookie!");
+ break;
+ case 2:
+ case 3:
+ this.stmtUpdate.bindByName("id", cookie.creationTime);
+ this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
+ break;
+
+ case 4:
+ this.stmtDelete.bindByName("name", cookie.name);
+ this.stmtDelete.bindByName("host", cookie.host);
+ this.stmtDelete.bindByName("path", cookie.path);
+ this.stmtUpdate.bindByName("name", cookie.name);
+ this.stmtUpdate.bindByName("host", cookie.host);
+ this.stmtUpdate.bindByName("path", cookie.path);
+ this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
+ break;
+
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ this.stmtDelete.bindByName("name", cookie.name);
+ this.stmtDelete.bindByName("host", cookie.host);
+ this.stmtDelete.bindByName("path", cookie.path);
+ this.stmtDelete.bindByName(
+ "originAttributes",
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ this.stmtUpdate.bindByName("name", cookie.name);
+ this.stmtUpdate.bindByName("host", cookie.host);
+ this.stmtUpdate.bindByName("path", cookie.path);
+ this.stmtUpdate.bindByName(
+ "originAttributes",
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
+ break;
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+
+ do_execute_stmt(this.stmtUpdate);
+ },
+
+ close() {
+ this.stmtInsert.finalize();
+ this.stmtDelete.finalize();
+ if (this.stmtUpdate) {
+ this.stmtUpdate.finalize();
+ }
+ this.db.close();
+
+ this.stmtInsert = null;
+ this.stmtDelete = null;
+ this.stmtUpdate = null;
+ this.db = null;
+ },
+};
+
+function do_get_cookie_file(profile) {
+ let file = profile.clone();
+ file.append("cookies.sqlite");
+ return file;
+}
+
+// Count the cookies from 'host' in a database. If 'host' is null, count all
+// cookies.
+function do_count_cookies_in_db(connection, host) {
+ let select = null;
+ if (host) {
+ select = connection.createStatement(
+ "SELECT COUNT(1) FROM moz_cookies WHERE host = :host"
+ );
+ select.bindByName("host", host);
+ } else {
+ select = connection.createStatement("SELECT COUNT(1) FROM moz_cookies");
+ }
+
+ select.executeStep();
+ let result = select.getInt32(0);
+ select.reset();
+ select.finalize();
+ return result;
+}
+
+// Execute 'stmt', ensuring that we reset it if it throws.
+function do_execute_stmt(stmt) {
+ try {
+ stmt.executeStep();
+ stmt.reset();
+ } catch (e) {
+ stmt.reset();
+ throw e;
+ }
+}
diff --git a/netwerk/test/unit/head_http3.js b/netwerk/test/unit/head_http3.js
new file mode 100644
index 0000000000..56bc04db14
--- /dev/null
+++ b/netwerk/test/unit/head_http3.js
@@ -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/. */
+
+/* import-globals-from head_channels.js */
+/* import-globals-from head_cookies.js */
+
+async function http3_setup_tests(http3version) {
+ let h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ let h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ `foo.example.com;${http3version}=:${h3Port}`
+ );
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ // `../unit/` so that unit_ipc tests can use as well
+ addCertFromFile(certdb, "../unit/http2-ca.pem", "CTu,u,u");
+
+ await setup_altsvc("https://foo.example.com/", h3Route, http3version);
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+let CheckHttp3Listener = function () {};
+
+CheckHttp3Listener.prototype = {
+ expectedRoute: "",
+ http3version: "",
+
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ if (routed == this.expectedRoute) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, this.http3version);
+ this.finish(true);
+ } else {
+ dump("try again to get alt svc mapping\n");
+ this.finish(false);
+ }
+ },
+};
+
+async function setup_altsvc(uri, expectedRoute, http3version) {
+ let result = false;
+ do {
+ let chan = makeChan(uri);
+ let listener = new CheckHttp3Listener();
+ listener.expectedRoute = expectedRoute;
+ listener.http3version = http3version;
+ result = await altsvcSetupPromise(chan, listener);
+ dump("results=" + result);
+ } while (result === false);
+}
+
+function altsvcSetupPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+function http3_clear_prefs() {
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.disableIPv6");
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ Services.prefs.clearUserPref("network.http.http3.support_version1");
+ dump("cleanup done\n");
+}
diff --git a/netwerk/test/unit/head_servers.js b/netwerk/test/unit/head_servers.js
new file mode 100644
index 0000000000..d2d449b482
--- /dev/null
+++ b/netwerk/test/unit/head_servers.js
@@ -0,0 +1,925 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+
+const { NodeServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+/* globals require, __dirname, global, Buffer, process */
+
+class BaseNodeHTTPServerCode {
+ static globalHandler(req, resp) {
+ let path = new URL(req.url, "http://example.com").pathname;
+ let handler = global.path_handlers[path];
+ if (handler) {
+ return handler(req, resp);
+ }
+
+ // Didn't find a handler for this path.
+ let response = `<h1> 404 Path not found: ${path}</h1>`;
+ resp.setHeader("Content-Type", "text/html");
+ resp.setHeader("Content-Length", response.length);
+ resp.writeHead(404);
+ resp.end(response);
+ return undefined;
+ }
+}
+
+class ADB {
+ static async stopForwarding(port) {
+ return this.forwardPort(port, true);
+ }
+
+ static async forwardPort(port, remove = false) {
+ if (!process.env.MOZ_ANDROID_DATA_DIR) {
+ // Not android, or we don't know how to do the forwarding
+ return true;
+ }
+ // When creating a server on Android we must make sure that the port
+ // is forwarded from the host machine to the emulator.
+ let adb_path = "adb";
+ if (process.env.MOZ_FETCHES_DIR) {
+ adb_path = `${process.env.MOZ_FETCHES_DIR}/android-sdk-linux/platform-tools/adb`;
+ }
+
+ let command = `${adb_path} reverse tcp:${port} tcp:${port}`;
+ if (remove) {
+ command = `${adb_path} reverse --remove tcp:${port}`;
+ return true;
+ }
+
+ try {
+ await new Promise((resolve, reject) => {
+ const { exec } = require("child_process");
+ exec(command, (error, stdout, stderr) => {
+ if (error) {
+ console.log(`error: ${error.message}`);
+ reject(error);
+ } else if (stderr) {
+ console.log(`stderr: ${stderr}`);
+ reject(stderr);
+ } else {
+ // console.log(`stdout: ${stdout}`);
+ resolve();
+ }
+ });
+ });
+ } catch (error) {
+ console.log(`Command failed: ${error}`);
+ return false;
+ }
+
+ return true;
+ }
+
+ static async listenAndForwardPort(server, port) {
+ let retryCount = 0;
+ const maxRetries = 10;
+
+ while (retryCount < maxRetries) {
+ await server.listen(port);
+ let serverPort = server.address().port;
+ let res = await ADB.forwardPort(serverPort);
+
+ if (res) {
+ return serverPort;
+ }
+
+ retryCount++;
+ console.log(
+ `Port forwarding failed. Retrying (${retryCount}/${maxRetries})...`
+ );
+ server.close();
+ // eslint-disable-next-line no-undef
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ return -1;
+ }
+}
+
+class BaseNodeServer {
+ protocol() {
+ return this._protocol;
+ }
+ version() {
+ return this._version;
+ }
+ origin() {
+ return `${this.protocol()}://localhost:${this.port()}`;
+ }
+ port() {
+ return this._port;
+ }
+ domain() {
+ return `localhost`;
+ }
+
+ /// Stops the server
+ async stop() {
+ if (this.processId) {
+ await this.execute(`ADB.stopForwarding(${this.port()})`);
+ await NodeServer.kill(this.processId);
+ this.processId = undefined;
+ }
+ }
+
+ /// Executes a command in the context of the node server
+ async execute(command) {
+ return NodeServer.execute(this.processId, command);
+ }
+
+ /// @path : string - the path on the server that we're handling. ex: /path
+ /// @handler : function(req, resp, url) - function that processes request and
+ /// emits a response.
+ async registerPathHandler(path, handler) {
+ return this.execute(
+ `global.path_handlers["${path}"] = ${handler.toString()}`
+ );
+ }
+}
+
+// HTTP
+
+class NodeHTTPServerCode extends BaseNodeHTTPServerCode {
+ static async startServer(port) {
+ const http = require("http");
+ global.server = http.createServer(BaseNodeHTTPServerCode.globalHandler);
+
+ let serverPort = await ADB.listenAndForwardPort(global.server, port);
+ return serverPort;
+ }
+}
+
+class NodeHTTPServer extends BaseNodeServer {
+ _protocol = "http";
+ _version = "http/1.1";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseNodeHTTPServerCode);
+ await this.execute(NodeHTTPServerCode);
+ await this.execute(ADB);
+ this._port = await this.execute(`NodeHTTPServerCode.startServer(${port})`);
+ await this.execute(`global.path_handlers = {};`);
+ }
+}
+
+// HTTPS
+
+class NodeHTTPSServerCode extends BaseNodeHTTPServerCode {
+ static async startServer(port) {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+ };
+ const https = require("https");
+ global.server = https.createServer(
+ options,
+ BaseNodeHTTPServerCode.globalHandler
+ );
+
+ let serverPort = await ADB.listenAndForwardPort(global.server, port);
+ return serverPort;
+ }
+}
+
+class NodeHTTPSServer extends BaseNodeServer {
+ _protocol = "https";
+ _version = "http/1.1";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseNodeHTTPServerCode);
+ await this.execute(NodeHTTPSServerCode);
+ await this.execute(ADB);
+ this._port = await this.execute(`NodeHTTPSServerCode.startServer(${port})`);
+ await this.execute(`global.path_handlers = {};`);
+ }
+}
+
+// HTTP2
+
+class NodeHTTP2ServerCode extends BaseNodeHTTPServerCode {
+ static async startServer(port) {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+ };
+ const http2 = require("http2");
+ global.server = http2.createSecureServer(
+ options,
+ BaseNodeHTTPServerCode.globalHandler
+ );
+
+ global.sessionCount = 0;
+ global.server.on("session", () => {
+ global.sessionCount++;
+ });
+
+ let serverPort = await ADB.listenAndForwardPort(global.server, port);
+ return serverPort;
+ }
+
+ static sessionCount() {
+ return global.sessionCount;
+ }
+}
+
+class NodeHTTP2Server extends BaseNodeServer {
+ _protocol = "https";
+ _version = "h2";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseNodeHTTPServerCode);
+ await this.execute(NodeHTTP2ServerCode);
+ await this.execute(ADB);
+ this._port = await this.execute(`NodeHTTP2ServerCode.startServer(${port})`);
+ await this.execute(`global.path_handlers = {};`);
+ }
+
+ async sessionCount() {
+ let count = this.execute(`NodeHTTP2ServerCode.sessionCount()`);
+ return count;
+ }
+}
+
+// Base HTTP proxy
+
+class BaseProxyCode {
+ static proxyHandler(req, res) {
+ if (req.url.startsWith("/")) {
+ res.writeHead(405);
+ res.end();
+ return;
+ }
+
+ let url = new URL(req.url);
+ const http = require("http");
+ let preq = http
+ .request(
+ {
+ method: req.method,
+ path: url.pathname,
+ port: url.port,
+ host: url.hostname,
+ protocol: url.protocol,
+ },
+ proxyresp => {
+ res.writeHead(
+ proxyresp.statusCode,
+ proxyresp.statusMessage,
+ proxyresp.headers
+ );
+ proxyresp.on("data", chunk => {
+ if (!res.writableEnded) {
+ res.write(chunk);
+ }
+ });
+ proxyresp.on("end", () => {
+ res.end();
+ });
+ }
+ )
+ .on("error", e => {
+ console.log(`sock err: ${e}`);
+ });
+ if (req.method != "POST") {
+ preq.end();
+ } else {
+ req.on("data", chunk => {
+ if (!preq.writableEnded) {
+ preq.write(chunk);
+ }
+ });
+ req.on("end", () => preq.end());
+ }
+ }
+
+ static onConnect(req, clientSocket, head) {
+ if (global.connect_handler) {
+ global.connect_handler(req, clientSocket, head);
+ return;
+ }
+ const net = require("net");
+ // Connect to an origin server
+ const { port, hostname } = new URL(`https://${req.url}`);
+ const serverSocket = net
+ .connect(port || 443, hostname, () => {
+ clientSocket.write(
+ "HTTP/1.1 200 Connection Established\r\n" +
+ "Proxy-agent: Node.js-Proxy\r\n" +
+ "\r\n"
+ );
+ serverSocket.write(head);
+ serverSocket.pipe(clientSocket);
+ clientSocket.pipe(serverSocket);
+ })
+ .on("error", e => {
+ // The socket will error out when we kill the connection
+ // just ignore it.
+ });
+ clientSocket.on("error", e => {
+ // Sometimes we got ECONNRESET error on windows platform.
+ // Ignore it for now.
+ });
+ }
+}
+
+class BaseHTTPProxy extends BaseNodeServer {
+ registerFilter() {
+ const pps =
+ Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+ this.filter = new NodeProxyFilter(
+ this.protocol(),
+ "localhost",
+ this.port(),
+ 0
+ );
+ pps.registerFilter(this.filter, 10);
+ registerCleanupFunction(() => {
+ this.unregisterFilter();
+ });
+ }
+
+ unregisterFilter() {
+ const pps =
+ Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+ if (this.filter) {
+ pps.unregisterFilter(this.filter);
+ this.filter = undefined;
+ }
+ }
+
+ /// Stops the server
+ async stop() {
+ this.unregisterFilter();
+ await super.stop();
+ }
+
+ async registerConnectHandler(handler) {
+ return this.execute(`global.connect_handler = ${handler.toString()}`);
+ }
+}
+
+// HTTP1 Proxy
+
+class NodeProxyFilter {
+ constructor(type, host, port, flags) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
+ }
+ applyFilter(uri, pi, cb) {
+ const pps =
+ Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ "",
+ "",
+ this._flags,
+ 1000,
+ null
+ )
+ );
+ }
+}
+
+class HTTPProxyCode {
+ static async startServer(port) {
+ const http = require("http");
+ global.proxy = http.createServer(BaseProxyCode.proxyHandler);
+ global.proxy.on("connect", BaseProxyCode.onConnect);
+
+ let proxyPort = await ADB.listenAndForwardPort(global.proxy, port);
+ return proxyPort;
+ }
+}
+
+class NodeHTTPProxyServer extends BaseHTTPProxy {
+ _protocol = "http";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseProxyCode);
+ await this.execute(HTTPProxyCode);
+ await this.execute(ADB);
+ await this.execute(`global.connect_handler = null;`);
+ this._port = await this.execute(`HTTPProxyCode.startServer(${port})`);
+
+ this.registerFilter();
+ }
+}
+
+// HTTPS proxy
+
+class HTTPSProxyCode {
+ static async startServer(port) {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/proxy-cert.key"),
+ cert: fs.readFileSync(__dirname + "/proxy-cert.pem"),
+ };
+ const https = require("https");
+ global.proxy = https.createServer(options, BaseProxyCode.proxyHandler);
+ global.proxy.on("connect", BaseProxyCode.onConnect);
+
+ let proxyPort = await ADB.listenAndForwardPort(global.proxy, port);
+ return proxyPort;
+ }
+}
+
+class NodeHTTPSProxyServer extends BaseHTTPProxy {
+ _protocol = "https";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseProxyCode);
+ await this.execute(HTTPSProxyCode);
+ await this.execute(ADB);
+ await this.execute(`global.connect_handler = null;`);
+ this._port = await this.execute(`HTTPSProxyCode.startServer(${port})`);
+
+ this.registerFilter();
+ }
+}
+
+// HTTP2 proxy
+
+class HTTP2ProxyCode {
+ static async startServer(port, auth) {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/proxy-cert.key"),
+ cert: fs.readFileSync(__dirname + "/proxy-cert.pem"),
+ };
+ const http2 = require("http2");
+ global.proxy = http2.createSecureServer(options);
+ global.socketCounts = {};
+ this.setupProxy(auth);
+
+ let proxyPort = await ADB.listenAndForwardPort(global.proxy, port);
+ return proxyPort;
+ }
+
+ static setupProxy(auth) {
+ if (!global.proxy) {
+ throw new Error("proxy is null");
+ }
+
+ global.proxy.on("stream", (stream, headers) => {
+ if (headers[":scheme"] === "http") {
+ const http = require("http");
+ let url = new URL(
+ `${headers[":scheme"]}://${headers[":authority"]}${headers[":path"]}`
+ );
+ let req = http
+ .request(
+ {
+ method: headers[":method"],
+ path: headers[":path"],
+ port: url.port,
+ host: url.hostname,
+ protocol: url.protocol,
+ },
+ proxyresp => {
+ let proxyheaders = Object.assign({}, proxyresp.headers);
+ // Filter out some prohibited headers.
+ ["connection", "transfer-encoding", "keep-alive"].forEach(
+ prop => {
+ delete proxyheaders[prop];
+ }
+ );
+ try {
+ stream.respond(
+ Object.assign(
+ { ":status": proxyresp.statusCode },
+ proxyheaders
+ )
+ );
+ } catch (e) {
+ // The channel may have been closed already.
+ if (e.message != "The stream has been destroyed") {
+ throw e;
+ }
+ }
+ proxyresp.on("data", chunk => {
+ if (stream.writable) {
+ stream.write(chunk);
+ }
+ });
+ proxyresp.on("end", () => {
+ stream.end();
+ });
+ }
+ )
+ .on("error", e => {
+ console.log(`sock err: ${e}`);
+ });
+
+ if (headers[":method"] != "POST") {
+ req.end();
+ } else {
+ stream.on("data", chunk => {
+ if (!req.writableEnded) {
+ req.write(chunk);
+ }
+ });
+ stream.on("end", () => req.end());
+ }
+ return;
+ }
+ if (headers[":method"] !== "CONNECT") {
+ // Only accept CONNECT requests
+ stream.respond({ ":status": 405 });
+ stream.end();
+ return;
+ }
+
+ const authorization_token = headers["proxy-authorization"];
+ if (auth && !authorization_token) {
+ stream.respond({
+ ":status": 407,
+ "proxy-authenticate": "Basic realm='foo'",
+ });
+ stream.end();
+ return;
+ }
+
+ const target = headers[":authority"];
+ const { port } = new URL(`https://${target}`);
+ const net = require("net");
+ const socket = net.connect(port, "127.0.0.1", () => {
+ try {
+ global.socketCounts[socket.remotePort] =
+ (global.socketCounts[socket.remotePort] || 0) + 1;
+ stream.respond({ ":status": 200 });
+ socket.pipe(stream);
+ stream.pipe(socket);
+ } catch (exception) {
+ console.log(exception);
+ stream.close();
+ }
+ });
+ const http2 = require("http2");
+ socket.on("error", error => {
+ const status = error.errno == "ENOTFOUND" ? 404 : 502;
+ try {
+ // If we already sent headers when the socket connected
+ // then sending the status again would throw.
+ if (!stream.sentHeaders) {
+ stream.respond({ ":status": status });
+ }
+ stream.end();
+ } catch (exception) {
+ stream.close(http2.constants.NGHTTP2_CONNECT_ERROR);
+ }
+ });
+ stream.on("close", () => {
+ socket.end();
+ });
+ socket.on("close", () => {
+ stream.close();
+ });
+ stream.on("end", () => {
+ socket.end();
+ });
+ stream.on("aborted", () => {
+ socket.end();
+ });
+ stream.on("error", error => {
+ console.log("RESPONSE STREAM ERROR", error);
+ });
+ });
+ }
+
+ static socketCount(port) {
+ return global.socketCounts[port];
+ }
+}
+
+class NodeHTTP2ProxyServer extends BaseHTTPProxy {
+ _protocol = "https";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0, auth) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseProxyCode);
+ await this.execute(HTTP2ProxyCode);
+ await this.execute(ADB);
+ await this.execute(`global.connect_handler = null;`);
+ this._port = await this.execute(
+ `HTTP2ProxyCode.startServer(${port}, ${auth})`
+ );
+
+ this.registerFilter();
+ }
+
+ async socketCount(port) {
+ let count = this.execute(`HTTP2ProxyCode.socketCount(${port})`);
+ return count;
+ }
+}
+
+// websocket server
+
+class NodeWebSocketServerCode extends BaseNodeHTTPServerCode {
+ static messageHandler(data, ws) {
+ if (global.wsInputHandler) {
+ global.wsInputHandler(data, ws);
+ return;
+ }
+
+ ws.send("test");
+ }
+
+ static async startServer(port) {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+ };
+ const https = require("https");
+ global.server = https.createServer(
+ options,
+ BaseNodeHTTPServerCode.globalHandler
+ );
+
+ let node_ws_root = `${__dirname}/../node-ws`;
+ const WebSocket = require(`${node_ws_root}/lib/websocket`);
+ WebSocket.Server = require(`${node_ws_root}/lib/websocket-server`);
+ global.webSocketServer = new WebSocket.Server({ server: global.server });
+ global.webSocketServer.on("connection", function connection(ws) {
+ ws.on("message", data =>
+ NodeWebSocketServerCode.messageHandler(data, ws)
+ );
+ });
+
+ let serverPort = await ADB.listenAndForwardPort(global.server, port);
+ return serverPort;
+ }
+}
+
+class NodeWebSocketServer extends BaseNodeServer {
+ _protocol = "wss";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseNodeHTTPServerCode);
+ await this.execute(NodeWebSocketServerCode);
+ await this.execute(ADB);
+ this._port = await this.execute(
+ `NodeWebSocketServerCode.startServer(${port})`
+ );
+ await this.execute(`global.path_handlers = {};`);
+ await this.execute(`global.wsInputHandler = null;`);
+ }
+
+ async registerMessageHandler(handler) {
+ return this.execute(`global.wsInputHandler = ${handler.toString()}`);
+ }
+}
+
+// websocket http2 server
+// This code is inspired by
+// https://github.com/szmarczak/http2-wrapper/blob/master/examples/ws/server.js
+class NodeWebSocketHttp2ServerCode extends BaseNodeHTTPServerCode {
+ static async startServer(port) {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+ settings: {
+ enableConnectProtocol: true,
+ },
+ };
+ const http2 = require("http2");
+ global.h2Server = http2.createSecureServer(options);
+
+ let node_ws_root = `${__dirname}/../node-ws`;
+ const WebSocket = require(`${node_ws_root}/lib/websocket`);
+
+ global.h2Server.on("stream", (stream, headers) => {
+ if (headers[":method"] === "CONNECT") {
+ stream.respond();
+
+ const ws = new WebSocket(null);
+ stream.setNoDelay = () => {};
+ ws.setSocket(stream, Buffer.from(""), 100 * 1024 * 1024);
+
+ ws.on("message", data => {
+ if (global.wsInputHandler) {
+ global.wsInputHandler(data, ws);
+ return;
+ }
+
+ ws.send("test");
+ });
+ } else {
+ stream.respond();
+ stream.end("ok");
+ }
+ });
+
+ let serverPort = await ADB.listenAndForwardPort(global.h2Server, port);
+ return serverPort;
+ }
+}
+
+class NodeWebSocketHttp2Server extends BaseNodeServer {
+ _protocol = "h2ws";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseNodeHTTPServerCode);
+ await this.execute(NodeWebSocketHttp2ServerCode);
+ await this.execute(ADB);
+ this._port = await this.execute(
+ `NodeWebSocketHttp2ServerCode.startServer(${port})`
+ );
+ await this.execute(`global.path_handlers = {};`);
+ await this.execute(`global.wsInputHandler = null;`);
+ }
+
+ async registerMessageHandler(handler) {
+ return this.execute(`global.wsInputHandler = ${handler.toString()}`);
+ }
+}
+
+// Helper functions
+
+async function with_node_servers(arrayOfClasses, asyncClosure) {
+ for (let s of arrayOfClasses) {
+ let server = new s();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ await asyncClosure(server);
+ await server.stop();
+ }
+}
+
+// nsITLSServerSocket needs a certificate with a corresponding private key
+// available. xpcshell tests can import the test file "client-cert.p12" using
+// the password "password", resulting in a certificate with the common name
+// "Test End-entity" being available with a corresponding private key.
+function getTestServerCertificate() {
+ const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ const certFile = do_get_file("client-cert.p12");
+ certDB.importPKCS12File(certFile, "password");
+ for (const cert of certDB.getCerts()) {
+ if (cert.commonName == "Test End-entity") {
+ return cert;
+ }
+ }
+ return null;
+}
+
+class WebSocketConnection {
+ constructor() {
+ this._openPromise = new Promise(resolve => {
+ this._openCallback = resolve;
+ });
+
+ this._stopPromise = new Promise(resolve => {
+ this._stopCallback = resolve;
+ });
+
+ this._msgPromise = new Promise(resolve => {
+ this._msgCallback = resolve;
+ });
+
+ this._proxyAvailablePromise = new Promise(resolve => {
+ this._proxyAvailCallback = resolve;
+ });
+
+ this._messages = [];
+ this._ws = null;
+ }
+
+ get QueryInterface() {
+ return ChromeUtils.generateQI([
+ "nsIWebSocketListener",
+ "nsIProtocolProxyCallback",
+ ]);
+ }
+
+ onAcknowledge(aContext, aSize) {}
+ onBinaryMessageAvailable(aContext, aMsg) {
+ this._messages.push(aMsg);
+ this._msgCallback();
+ }
+ onMessageAvailable(aContext, aMsg) {}
+ onServerClose(aContext, aCode, aReason) {}
+ onWebSocketListenerStart(aContext) {}
+ onStart(aContext) {
+ this._openCallback();
+ }
+ onStop(aContext, aStatusCode) {
+ this._stopCallback({ status: aStatusCode });
+ this._ws = null;
+ }
+ onProxyAvailable(req, chan, proxyInfo, status) {
+ if (proxyInfo) {
+ this._proxyAvailCallback({ type: proxyInfo.type });
+ } else {
+ this._proxyAvailCallback({});
+ }
+ }
+
+ static makeWebSocketChan() {
+ let chan = Cc["@mozilla.org/network/protocol;1?name=wss"].createInstance(
+ Ci.nsIWebSocketChannel
+ );
+ chan.initLoadInfo(
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET
+ );
+ return chan;
+ }
+ // Returns a promise that resolves when the websocket channel is opened.
+ open(url) {
+ this._ws = WebSocketConnection.makeWebSocketChan();
+ let uri = Services.io.newURI(url);
+ this._ws.asyncOpen(uri, url, {}, 0, this, null);
+ return this._openPromise;
+ }
+ // Closes the inner websocket. code and reason arguments are optional.
+ close(code, reason) {
+ this._ws.close(code || Ci.nsIWebSocketChannel.CLOSE_NORMAL, reason || "");
+ }
+ // Sends a message to the server.
+ send(msg) {
+ this._ws.sendMsg(msg);
+ }
+ // Returns a promise that resolves when the channel's onStop is called.
+ // Promise resolves with an `{status}` object, where status is the
+ // result passed to onStop.
+ finished() {
+ return this._stopPromise;
+ }
+ getProxyInfo() {
+ return this._proxyAvailablePromise;
+ }
+
+ // Returned promise resolves with an array of received messages
+ // If messages have been received in the the past before calling
+ // receiveMessages, the promise will immediately resolve. Otherwise
+ // it will resolve when the first message is received.
+ async receiveMessages() {
+ await this._msgPromise;
+ this._msgPromise = new Promise(resolve => {
+ this._msgCallback = resolve;
+ });
+ let messages = this._messages;
+ this._messages = [];
+ return messages;
+ }
+}
diff --git a/netwerk/test/unit/head_telemetry.js b/netwerk/test/unit/head_telemetry.js
new file mode 100644
index 0000000000..c3b1ec66aa
--- /dev/null
+++ b/netwerk/test/unit/head_telemetry.js
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+var HandshakeTelemetryHelpers = {
+ HISTOGRAMS: ["SSL_HANDSHAKE_RESULT", "SSL_TIME_UNTIL_READY"],
+ FLAVORS: ["", "_FIRST_TRY", "_CONSERVATIVE", "_ECH", "_ECH_GREASE"],
+
+ /**
+ * Prints the Histogram to console.
+ *
+ * @param {*} name The identifier of the Histogram.
+ */
+ dumpHistogram(name) {
+ let values = Services.telemetry.getHistogramById(name).snapshot().values;
+ dump(`${name}: ${JSON.stringify(values)}\n`);
+ },
+
+ /**
+ * Counts the number of entries in the histogram, ignoring the bucket value.
+ * e.g. {0: 1, 1: 2, 3: 3} has 6 entries.
+ *
+ * @param {Object} histObject The histogram to count the entries of.
+ * @returns The count of the number of entries in the histogram.
+ */
+ countHistogramEntries(histObject) {
+ Assert.ok(
+ !mozinfo.socketprocess_networking,
+ "Histograms don't populate on network process"
+ );
+ let count = 0;
+ let m = histObject.snapshot().values;
+ for (let k in m) {
+ count += m[k];
+ }
+ return count;
+ },
+
+ /**
+ * Assert that the histogram index is the right value. It expects that
+ * other indexes are all zero.
+ *
+ * @param {Object} histogram The histogram to check.
+ * @param {Number} index The index to check against the expected value.
+ * @param {Number} expected The expected value of the index.
+ */
+ assertHistogramMap(histogram, expectedEntries) {
+ Assert.ok(
+ !mozinfo.socketprocess_networking,
+ "Histograms don't populate on network process"
+ );
+ let snapshot = JSON.parse(JSON.stringify(histogram));
+ for (let [Tk, Tv] of expectedEntries.entries()) {
+ let found = false;
+ for (let [i, val] of Object.entries(snapshot.values)) {
+ if (i == Tk) {
+ found = true;
+ Assert.equal(val, Tv, `expected counts should match at index ${i}`);
+ snapshot.values[i] = 0; // Reset the value
+ }
+ }
+ Assert.ok(found, `Should have found an entry at index ${Tk}`);
+ }
+ for (let k in snapshot.values) {
+ Assert.equal(
+ snapshot.values[k],
+ 0,
+ `Should NOT have found an entry at index ${k} of value ${snapshot.values[k]}`
+ );
+ }
+ },
+
+ /**
+ * Generates the pairwise concatonation of histograms and flavors.
+ *
+ * @param {Array} histogramList A subset of HISTOGRAMS.
+ * @param {Array} flavorList A subset of FLAVORS.
+ * @returns {Array} Valid TLS Histogram identifiers
+ */
+ getHistogramNames(histogramList, flavorList) {
+ let output = [];
+ for (let h of histogramList) {
+ Assert.ok(this.HISTOGRAMS.includes(h), "Histogram name valid");
+ for (let f of flavorList) {
+ Assert.ok(this.FLAVORS.includes(f), "Histogram flavor valid");
+ output.push(h.concat(f));
+ }
+ }
+ return output;
+ },
+
+ /**
+ * getHistogramNames but mapped to Histogram objects.
+ */
+ getHistograms(histogramList, flavorList) {
+ return this.getHistogramNames(histogramList, flavorList).map(x =>
+ Services.telemetry.getHistogramById(x)
+ );
+ },
+
+ /**
+ * Clears TLS Handshake Histograms.
+ */
+ resetHistograms() {
+ let allHistograms = this.getHistograms(this.HISTOGRAMS, this.FLAVORS);
+ for (let h of allHistograms) {
+ h.clear();
+ }
+ },
+
+ /**
+ * Checks that all TLS Handshake Histograms of a particular flavor have
+ * exactly resultCount entries for the resultCode and no other entries.
+ *
+ * @param {Array} flavors An array of strings corresponding to which types
+ * of histograms should have entries. See
+ * HandshakeTelemetryHelpers.FLAVORS.
+ * @param {number} resultCode The expected result code, see sslerr.h. 0 is success, all others are errors.
+ * @param {number} resultCount The number of handshake results expected.
+ */
+ checkEntry(flavors, resultCode, resultCount) {
+ Assert.ok(
+ !mozinfo.socketprocess_networking,
+ "Histograms don't populate on network process"
+ );
+ // SSL_HANDSHAKE_RESULT_{FLAVOR}
+ for (let h of this.getHistograms(["SSL_HANDSHAKE_RESULT"], flavors)) {
+ TelemetryTestUtils.assertHistogram(h, resultCode, resultCount);
+ }
+
+ // SSL_TIME_UNTIL_READY_{FLAVOR} should only contain values if we expected success.
+ if (resultCode === 0) {
+ for (let h of this.getHistograms(["SSL_TIME_UNTIL_READY"], flavors)) {
+ Assert.ok(
+ this.countHistogramEntries(h) === resultCount,
+ "Timing entry count correct"
+ );
+ }
+ } else {
+ for (let h of this.getHistograms(["SSL_TIME_UNTIL_READY"], flavors)) {
+ Assert.ok(
+ this.countHistogramEntries(h) === 0,
+ "No timing entries expected"
+ );
+ }
+ }
+ },
+
+ checkSuccess(flavors, resultCount = 1) {
+ this.checkEntry(flavors, 0, resultCount);
+ },
+
+ checkEmpty(flavors) {
+ for (let h of this.getHistogramNames(this.HISTOGRAMS, flavors)) {
+ let hObj = Services.telemetry.getHistogramById(h);
+ Assert.ok(
+ this.countHistogramEntries(hObj) === 0,
+ `No entries expected in ${h.name}. Contents: ${JSON.stringify(
+ hObj.snapshot()
+ )}`
+ );
+ }
+ },
+};
diff --git a/netwerk/test/unit/head_trr.js b/netwerk/test/unit/head_trr.js
new file mode 100644
index 0000000000..8262c735de
--- /dev/null
+++ b/netwerk/test/unit/head_trr.js
@@ -0,0 +1,550 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+/* globals require, __dirname, global, Buffer, process */
+
+/// Sets the TRR related prefs and adds the certificate we use for the HTTP2
+/// server.
+function trr_test_setup() {
+ dump("start!\n");
+
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+ // the TRR server is on 127.0.0.1
+ if (AppConstants.platform == "android") {
+ Services.prefs.setCharPref("network.trr.bootstrapAddr", "10.0.2.2");
+ } else {
+ Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
+ }
+
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ Services.prefs.setBoolPref("network.trr.wait-for-portal", false);
+ // don't confirm that TRR is working, just go!
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ // some tests rely on the cache not being cleared on pref change.
+ // we specifically test that this works
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ // Turn off strict fallback mode and TRR retry for most tests,
+ // it is tested specifically.
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+ Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", false);
+
+ // Turn off temp blocklist feature in tests. When enabled we may issue a
+ // lookup to resolve a parent name when blocklisting, which may bleed into
+ // and interfere with subsequent tasks.
+ Services.prefs.setBoolPref("network.trr.temp_blocklist", false);
+
+ // We intentionally don't set the TRR mode. Each test should set it
+ // after setup in the first test.
+
+ return h2Port;
+}
+
+/// Clears the prefs that we're likely to set while testing TRR code
+function trr_clear_prefs() {
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.trr.uri");
+ Services.prefs.clearUserPref("network.trr.credentials");
+ Services.prefs.clearUserPref("network.trr.wait-for-portal");
+ Services.prefs.clearUserPref("network.trr.allow-rfc1918");
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.confirmationNS");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+ Services.prefs.clearUserPref("network.trr.temp_blocklist_duration_sec");
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+ Services.prefs.clearUserPref("network.trr.early-AAAA");
+ Services.prefs.clearUserPref("network.trr.excluded-domains");
+ Services.prefs.clearUserPref("network.trr.builtin-excluded-domains");
+ Services.prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ Services.prefs.clearUserPref("network.trr.fetch_off_main_thread");
+ Services.prefs.clearUserPref("captivedetect.canonicalURL");
+
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+ Services.prefs.clearUserPref(
+ "network.trr.send_empty_accept-encoding_headers"
+ );
+ Services.prefs.clearUserPref("network.trr.strict_native_fallback");
+ Services.prefs.clearUserPref("network.trr.temp_blocklist");
+}
+
+/// This class sends a DNS query and can be awaited as a promise to get the
+/// response.
+class TRRDNSListener {
+ constructor(...args) {
+ if (args.length < 2) {
+ Assert.ok(false, "TRRDNSListener requires at least two arguments");
+ }
+ this.name = args[0];
+ if (typeof args[1] == "object") {
+ this.options = args[1];
+ } else {
+ this.options = {
+ expectedAnswer: args[1],
+ expectedSuccess: args[2] ?? true,
+ delay: args[3],
+ trrServer: args[4] ?? "",
+ expectEarlyFail: args[5] ?? "",
+ flags: args[6] ?? 0,
+ type: args[7] ?? Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ port: args[8] ?? -1,
+ };
+ }
+ this.expectedAnswer = this.options.expectedAnswer ?? undefined;
+ this.expectedSuccess = this.options.expectedSuccess ?? true;
+ this.delay = this.options.delay;
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ this.type = this.options.type ?? Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT;
+ let trrServer = this.options.trrServer || "";
+ let port = this.options.port || -1;
+
+ // This may be called in a child process that doesn't have Services available.
+ // eslint-disable-next-line mozilla/use-services
+ const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+ );
+ const currentThread = threadManager.currentThread;
+
+ this.additionalInfo =
+ trrServer == "" && port == -1
+ ? null
+ : Services.dns.newAdditionalInfo(trrServer, port);
+ try {
+ this.request = Services.dns.asyncResolve(
+ this.name,
+ this.type,
+ this.options.flags || 0,
+ this.additionalInfo,
+ this,
+ currentThread,
+ this.options.originAttributes || {} // defaultOriginAttributes
+ );
+ Assert.ok(!this.options.expectEarlyFail, "asyncResolve ok");
+ } catch (e) {
+ Assert.ok(this.options.expectEarlyFail, "asyncResolve fail");
+ this.resolve({ error: e });
+ }
+ }
+
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.ok(
+ inRequest == this.request,
+ "Checking that this is the correct callback"
+ );
+
+ // If we don't expect success here, just resolve and the caller will
+ // decide what to do with the results.
+ if (!this.expectedSuccess) {
+ this.resolve({ inRequest, inRecord, inStatus });
+ return;
+ }
+
+ Assert.equal(inStatus, Cr.NS_OK, "Checking status");
+
+ if (this.type != Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT) {
+ this.resolve({ inRequest, inRecord, inStatus });
+ return;
+ }
+
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let answer = inRecord.getNextAddrAsString();
+ Assert.equal(
+ answer,
+ this.expectedAnswer,
+ `Checking result for ${this.name}`
+ );
+ inRecord.rewind(); // In case the caller also checks the addresses
+
+ if (this.delay !== undefined) {
+ Assert.greaterOrEqual(
+ inRecord.trrFetchDurationNetworkOnly,
+ this.delay,
+ `the response should take at least ${this.delay}`
+ );
+
+ Assert.greaterOrEqual(
+ inRecord.trrFetchDuration,
+ this.delay,
+ `the response should take at least ${this.delay}`
+ );
+
+ if (this.delay == 0) {
+ // The response timing should be really 0
+ Assert.equal(
+ inRecord.trrFetchDurationNetworkOnly,
+ 0,
+ `the response time should be 0`
+ );
+
+ Assert.equal(
+ inRecord.trrFetchDuration,
+ this.delay,
+ `the response time should be 0`
+ );
+ }
+ }
+
+ this.resolve({ inRequest, inRecord, inStatus });
+ }
+
+ QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) || aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+
+ // Implement then so we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+
+ cancel(aStatus = Cr.NS_ERROR_ABORT) {
+ Services.dns.cancelAsyncResolve(
+ this.name,
+ this.type,
+ this.options.flags || 0,
+ this.resolverInfo,
+ this,
+ aStatus,
+ {}
+ );
+ }
+}
+
+// This is for reteriiving the raw bytes from a DNS answer.
+function answerHandler(req, resp) {
+ let searchParams = new URL(req.url, "http://example.com").searchParams;
+ console.log("req.searchParams:" + searchParams);
+ if (!searchParams.get("host")) {
+ resp.writeHead(400);
+ resp.end("Missing search parameter");
+ return;
+ }
+
+ function processRequest(req1, resp1) {
+ let domain = searchParams.get("host");
+ let type = searchParams.get("type");
+ let response = global.dns_query_answers[`${domain}/${type}`] || {};
+ let buf = global.dnsPacket.encode({
+ type: "response",
+ id: 0,
+ flags: 0,
+ questions: [],
+ answers: response.answers || [],
+ additionals: response.additionals || [],
+ });
+ let writeResponse = (resp2, buf2, context) => {
+ try {
+ let data = buf2.toString("hex");
+ resp2.setHeader("Content-Length", data.length);
+ resp2.writeHead(200, { "Content-Type": "plain/text" });
+ resp2.write(data);
+ resp2.end("");
+ } catch (e) {}
+ };
+
+ writeResponse(resp1, buf, response);
+ }
+
+ processRequest(req, resp);
+}
+
+/// This is the default handler for /dns-query
+/// It implements basic functionality for parsing the DoH packet, then
+/// queries global.dns_query_answers for available answers for the DNS query.
+function trrQueryHandler(req, resp, url) {
+ let requestBody = Buffer.from("");
+ let method = req.headers[global.http2.constants.HTTP2_HEADER_METHOD];
+ let contentLength = req.headers["content-length"];
+
+ if (method == "POST") {
+ req.on("data", chunk => {
+ requestBody = Buffer.concat([requestBody, chunk]);
+ if (requestBody.length == contentLength) {
+ processRequest(req, resp, requestBody);
+ }
+ });
+ } else if (method == "GET") {
+ if (!url.query.dns) {
+ resp.writeHead(400);
+ resp.end("Missing dns parameter");
+ return;
+ }
+
+ requestBody = Buffer.from(url.query.dns, "base64");
+ processRequest(req, resp, requestBody);
+ } else {
+ // unexpected method.
+ resp.writeHead(405);
+ resp.end("Unexpected method");
+ }
+
+ function processRequest(req1, resp1, payload) {
+ let dnsQuery = global.dnsPacket.decode(payload);
+ let domain = dnsQuery.questions[0].name;
+ let type = dnsQuery.questions[0].type;
+ let response = global.dns_query_answers[`${domain}/${type}`] || {};
+
+ if (!global.dns_query_counts[domain]) {
+ global.dns_query_counts[domain] = {};
+ }
+ global.dns_query_counts[domain][type] =
+ global.dns_query_counts[domain][type] + 1 || 1;
+
+ let flags = global.dnsPacket.RECURSION_DESIRED;
+ if (!response.answers && !response.flags) {
+ flags |= 2; // SERVFAIL
+ }
+ flags |= response.flags || 0;
+ let buf = global.dnsPacket.encode({
+ type: "response",
+ id: dnsQuery.id,
+ flags,
+ questions: dnsQuery.questions,
+ answers: response.answers || [],
+ additionals: response.additionals || [],
+ });
+
+ let writeResponse = (resp2, buf2, context) => {
+ try {
+ if (context.error) {
+ // If the error is a valid HTTP response number just write it out.
+ if (context.error < 600) {
+ resp2.writeHead(context.error);
+ resp2.end("Intentional error");
+ return;
+ }
+
+ // Bigger error means force close the session
+ req1.stream.session.close();
+ return;
+ }
+ resp2.setHeader("Content-Length", buf2.length);
+ resp2.writeHead(200, { "Content-Type": "application/dns-message" });
+ resp2.write(buf2);
+ resp2.end("");
+ } catch (e) {}
+ };
+
+ if (response.delay) {
+ // This function is handled within the httpserver where setTimeout is
+ // available.
+ // eslint-disable-next-line no-undef
+ setTimeout(
+ arg => {
+ writeResponse(arg[0], arg[1], arg[2]);
+ },
+ response.delay,
+ [resp1, buf, response]
+ );
+ return;
+ }
+
+ writeResponse(resp1, buf, response);
+ }
+}
+
+function getRequestCount(domain, type) {
+ if (!global.dns_query_counts[domain]) {
+ return 0;
+ }
+ return global.dns_query_counts[domain][type] || 0;
+}
+
+// A convenient wrapper around NodeServer
+class TRRServer extends NodeHTTP2Server {
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ await super.start(port);
+ await this.execute(`( () => {
+ // key: string "name/type"
+ // value: array [answer1, answer2]
+ global.dns_query_answers = {};
+
+ // key: domain
+ // value: a map containing {key: type, value: number of requests}
+ global.dns_query_counts = {};
+
+ global.dnsPacket = require(\`\${__dirname}/../dns-packet\`);
+ global.ip = require(\`\${__dirname}/../node_ip\`);
+ global.http2 = require("http2");
+ })()`);
+ await this.registerPathHandler("/dns-query", trrQueryHandler);
+ await this.registerPathHandler("/dnsAnswer", answerHandler);
+ await this.execute(getRequestCount);
+ }
+
+ /// @name : string - name we're providing answers for. eg: foo.example.com
+ /// @type : string - the DNS query type. eg: "A", "AAAA", "CNAME", etc
+ /// @response : a map containing the response
+ /// answers: array of answers (hashmap) that dnsPacket can parse
+ /// eg: [{
+ /// name: "bar.example.com",
+ /// ttl: 55,
+ /// type: "A",
+ /// flush: false,
+ /// data: "1.2.3.4",
+ /// }]
+ /// additionals - array of answers (hashmap) to be added to the additional section
+ /// delay: int - if not 0 the response will be sent with after `delay` ms.
+ /// flags: int - flags to be set on the answer
+ /// error: int - HTTP status. If truthy then the response will send this status
+ async registerDoHAnswers(name, type, response = {}) {
+ let text = `global.dns_query_answers["${name}/${type}"] = ${JSON.stringify(
+ response
+ )}`;
+ return this.execute(text);
+ }
+
+ async requestCount(domain, type) {
+ return this.execute(`getRequestCount("${domain}", "${type}")`);
+ }
+}
+
+// Implements a basic HTTP2 proxy server
+class TRRProxyCode {
+ static async startServer(endServerPort) {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+ };
+
+ const http2 = require("http2");
+ global.proxy = http2.createSecureServer(options);
+ this.setupProxy();
+ global.endServerPort = endServerPort;
+
+ await global.proxy.listen(0);
+
+ let serverPort = global.proxy.address().port;
+ return serverPort;
+ }
+
+ static closeProxy() {
+ global.proxy.closeSockets();
+ return new Promise(resolve => {
+ global.proxy.close(resolve);
+ });
+ }
+
+ static proxyRequestCount() {
+ return global.proxy_stream_count;
+ }
+
+ static setupProxy() {
+ if (!global.proxy) {
+ throw new Error("proxy is null");
+ }
+
+ global.proxy_stream_count = 0;
+
+ // We need to track active connections so we can forcefully close keep-alive
+ // connections when shutting down the proxy.
+ global.proxy.socketIndex = 0;
+ global.proxy.socketMap = {};
+ global.proxy.on("connection", function (socket) {
+ let index = global.proxy.socketIndex++;
+ global.proxy.socketMap[index] = socket;
+ socket.on("close", function () {
+ delete global.proxy.socketMap[index];
+ });
+ });
+ global.proxy.closeSockets = function () {
+ for (let i in global.proxy.socketMap) {
+ global.proxy.socketMap[i].destroy();
+ }
+ };
+
+ global.proxy.on("stream", (stream, headers) => {
+ if (headers[":method"] !== "CONNECT") {
+ // Only accept CONNECT requests
+ stream.respond({ ":status": 405 });
+ stream.end();
+ return;
+ }
+ global.proxy_stream_count++;
+ const net = require("net");
+ const socket = net.connect(global.endServerPort, "127.0.0.1", () => {
+ try {
+ stream.respond({ ":status": 200 });
+ socket.pipe(stream);
+ stream.pipe(socket);
+ } catch (exception) {
+ console.log(exception);
+ stream.close();
+ }
+ });
+ socket.on("error", error => {
+ throw new Error(
+ `Unxpected error when conneting the HTTP/2 server from the HTTP/2 proxy during CONNECT handling: '${error}'`
+ );
+ });
+ });
+ }
+}
+
+class TRRProxy {
+ // Starts the proxy
+ async start(port) {
+ info("TRRProxy start!");
+ this.processId = await NodeServer.fork();
+ info("processid=" + this.processId);
+ await this.execute(TRRProxyCode);
+ this.port = await this.execute(`TRRProxyCode.startServer(${port})`);
+ Assert.notEqual(this.port, null);
+ }
+
+ // Executes a command in the context of the node server
+ async execute(command) {
+ return NodeServer.execute(this.processId, command);
+ }
+
+ // Stops the server
+ async stop() {
+ if (this.processId) {
+ await NodeServer.execute(this.processId, `TRRProxyCode.closeProxy()`);
+ await NodeServer.kill(this.processId);
+ }
+ }
+
+ async request_count() {
+ let data = await NodeServer.execute(
+ this.processId,
+ `TRRProxyCode.proxyRequestCount()`
+ );
+ return parseInt(data);
+ }
+}
diff --git a/netwerk/test/unit/head_websocket.js b/netwerk/test/unit/head_websocket.js
new file mode 100644
index 0000000000..84c5987f38
--- /dev/null
+++ b/netwerk/test/unit/head_websocket.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function WebSocketListener(closure, ws, sentMsg) {
+ this._closure = closure;
+ this._ws = ws;
+ this._sentMsg = sentMsg;
+}
+
+WebSocketListener.prototype = {
+ _closure: null,
+ _ws: null,
+ _sentMsg: null,
+ _received: null,
+ QueryInterface: ChromeUtils.generateQI(["nsIWebSocketListener"]),
+
+ onAcknowledge(aContext, aSize) {},
+ onBinaryMessageAvailable(aContext, aMsg) {
+ info("WsListener::onBinaryMessageAvailable");
+ this._received = aMsg;
+ this._ws.close(0, null);
+ },
+ onMessageAvailable(aContext, aMsg) {},
+ onServerClose(aContext, aCode, aReason) {},
+ onWebSocketListenerStart(aContext) {},
+ onStart(aContext) {
+ this._ws.sendMsg(this._sentMsg);
+ },
+ onStop(aContext, aStatusCode) {
+ try {
+ this._closure(aStatusCode, this._received);
+ this._ws = null;
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+function makeWebSocketChan() {
+ let chan = Cc["@mozilla.org/network/protocol;1?name=wss"].createInstance(
+ Ci.nsIWebSocketChannel
+ );
+ chan.initLoadInfo(
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET
+ );
+ return chan;
+}
+
+function openWebSocketChannelPromise(chan, url, msg) {
+ let uri = Services.io.newURI(url);
+ return new Promise(resolve => {
+ function finish(status, result) {
+ resolve([status, result]);
+ }
+ chan.asyncOpen(
+ uri,
+ url,
+ {},
+ 0,
+ new WebSocketListener(finish, chan, msg),
+ null
+ );
+ });
+}
diff --git a/netwerk/test/unit/head_webtransport.js b/netwerk/test/unit/head_webtransport.js
new file mode 100644
index 0000000000..99432e950d
--- /dev/null
+++ b/netwerk/test/unit/head_webtransport.js
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cookies.js */
+
+let WebTransportListener = function () {};
+
+WebTransportListener.prototype = {
+ onSessionReady(sessionId) {
+ info("SessionId " + sessionId);
+ this.ready();
+ },
+ onSessionClosed(errorCode, reason) {
+ info("Error: " + errorCode + " reason: " + reason);
+ if (this.closed) {
+ this.closed();
+ }
+ },
+ onIncomingBidirectionalStreamAvailable(stream) {
+ info("got incoming bidirectional stream");
+ this.streamAvailable(stream);
+ },
+ onIncomingUnidirectionalStreamAvailable(stream) {
+ info("got incoming unidirectional stream");
+ this.streamAvailable(stream);
+ },
+ onDatagramReceived(data) {
+ info("got datagram");
+ if (this.onDatagram) {
+ this.onDatagram(data);
+ }
+ },
+ onMaxDatagramSize(size) {
+ info("max datagram size: " + size);
+ if (this.onMaxDatagramSize) {
+ this.onMaxDatagramSize(size);
+ }
+ },
+ onOutgoingDatagramOutCome(id, outcome) {
+ if (this.onDatagramOutcome) {
+ this.onDatagramOutcome({ id, outcome });
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["WebTransportSessionEventListener"]),
+};
+
+function WebTransportStreamCallback() {}
+
+WebTransportStreamCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIWebTransportStreamCallback"]),
+
+ onBidirectionalStreamReady(aStream) {
+ Assert.ok(aStream != null);
+ this.finish(aStream);
+ },
+ onUnidirectionalStreamReady(aStream) {
+ Assert.ok(aStream != null);
+ this.finish(aStream);
+ },
+ onError(aError) {
+ this.finish(aError);
+ },
+};
+
+function StreamStatsCallback() {}
+
+StreamStatsCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebTransportStreamStatsCallback",
+ ]),
+
+ onSendStatsAvailable(aStats) {
+ Assert.ok(aStats != null);
+ this.finish(aStats);
+ },
+ onReceiveStatsAvailable(aStats) {
+ Assert.ok(aStats != null);
+ this.finish(aStats);
+ },
+};
+
+function inputStreamReader() {}
+
+inputStreamReader.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInputStreamCallback"]),
+
+ onInputStreamReady(input) {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ this.finish(data);
+ },
+};
+
+function streamCreatePromise(transport, bidi) {
+ return new Promise(resolve => {
+ let listener = new WebTransportStreamCallback().QueryInterface(
+ Ci.nsIWebTransportStreamCallback
+ );
+ listener.finish = resolve;
+
+ if (bidi) {
+ transport.createOutgoingBidirectionalStream(listener);
+ } else {
+ transport.createOutgoingUnidirectionalStream(listener);
+ }
+ });
+}
+
+function sendStreamStatsPromise(stream) {
+ return new Promise(resolve => {
+ let listener = new StreamStatsCallback().QueryInterface(
+ Ci.nsIWebTransportStreamStatsCallback
+ );
+ listener.finish = resolve;
+
+ stream.QueryInterface(Ci.nsIWebTransportSendStream);
+ stream.getSendStreamStats(listener);
+ });
+}
+
+function receiveStreamStatsPromise(stream) {
+ return new Promise(resolve => {
+ let listener = new StreamStatsCallback().QueryInterface(
+ Ci.nsIWebTransportStreamStatsCallback
+ );
+ listener.finish = resolve;
+
+ stream.QueryInterface(Ci.nsIWebTransportReceiveStream);
+ stream.getReceiveStreamStats(listener);
+ });
+}
diff --git a/netwerk/test/unit/http2-ca.pem b/netwerk/test/unit/http2-ca.pem
new file mode 100644
index 0000000000..ef5a801720
--- /dev/null
+++ b/netwerk/test/unit/http2-ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1DCCAbygAwIBAgIURZvN7yVqFNwThGHASoy1OlOGvOMwDQYJKoZIhvcNAQEL
+BQAwGTEXMBUGA1UEAwwOIEhUVFAyIFRlc3QgQ0EwIhgPMjAxNzAxMDEwMDAwMDBa
+GA8yMDI3MDEwMTAwMDAwMFowGTEXMBUGA1UEAwwOIEhUVFAyIFRlc3QgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT
+2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzV
+JJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8N
+jf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCA
+BiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVh
+He4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMB
+AAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADyDiQnKjsvR
+NrOk0aqgJ8XgK/IgJXFLbAVivjBLwnJGEkwxrFtC14mpTrPuXw9AybhroMjinq4Y
+cNYTFuTE34k0fZEU8d60J/Tpfd1i0EB8+oUPuqOn+N29/LeHPAnkDJdOZye3w0U+
+StAI79WqUYQaKIG7qLnt60dQwBte12uvbuPaB3mREIfDXOKcjLBdZHL1waWjtzUX
+z2E91VIdpvJGfEfXC3fIe1uO9Jh/E9NVWci84+njkNsl+OyBfOJ8T+pV3SHfWedp
+Zbjwh6UTukIuc3mW0rS/qZOa2w3HQaO53BMbluo0w1+cscOepsATld2HHvSiHB+0
+K8SWFRHdBOU=
+-----END CERTIFICATE-----
diff --git a/netwerk/test/unit/http2-ca.pem.certspec b/netwerk/test/unit/http2-ca.pem.certspec
new file mode 100644
index 0000000000..46f62e3fbc
--- /dev/null
+++ b/netwerk/test/unit/http2-ca.pem.certspec
@@ -0,0 +1,4 @@
+issuer: HTTP2 Test CA
+subject: HTTP2 Test CA
+validity:20170101-20270101
+extension:basicConstraints:cA,
diff --git a/netwerk/test/unit/http2_test_common.js b/netwerk/test/unit/http2_test_common.js
new file mode 100644
index 0000000000..341aa191da
--- /dev/null
+++ b/netwerk/test/unit/http2_test_common.js
@@ -0,0 +1,1504 @@
+// test HTTP/2
+
+"use strict";
+
+/* import-globals-from head_channels.js */
+
+// Generate a small and a large post with known pre-calculated md5 sums
+function generateContent(size) {
+ var content = "";
+ for (var i = 0; i < size; i++) {
+ content += "0";
+ }
+ return content;
+}
+
+var posts = [];
+posts.push(generateContent(10));
+posts.push(generateContent(250000));
+posts.push(generateContent(128000));
+
+// pre-calculated md5sums (in hex) of the above posts
+var md5s = [
+ "f1b708bba17f1ce948dc979f4d7092bc",
+ "2ef8d3b6c8f329318eb1a119b12622b6",
+];
+
+var bigListenerData = generateContent(128 * 1024);
+var bigListenerMD5 = "8f607cfdd2c87d6a7eedb657dafbd836";
+
+function checkIsHttp2(request) {
+ try {
+ if (request.getResponseHeader("X-Firefox-Spdy") == "h2") {
+ if (request.getResponseHeader("X-Connection-Http2") == "yes") {
+ return true;
+ }
+ return false; // Weird case, but the server disagrees with us
+ }
+ } catch (e) {
+ // Nothing to do here
+ }
+ return false;
+}
+
+var Http2CheckListener = function () {};
+
+Http2CheckListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ isHttp2Connection: false,
+ shouldBeHttp2: true,
+ accum: 0,
+ expected: -1,
+ shouldSucceed: true,
+
+ onStartRequest: function testOnStartRequest(request) {
+ this.onStartRequestFired = true;
+ if (this.shouldSucceed && !Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ } else if (
+ !this.shouldSucceed &&
+ Components.isSuccessCode(request.status)
+ ) {
+ do_throw("Channel succeeded unexpectedly!");
+ }
+
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.requestSucceeded, this.shouldSucceed);
+ if (this.shouldSucceed) {
+ Assert.equal(request.responseStatus, 200);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.accum += cnt;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(this.onStartRequestFired);
+ if (this.expected != -1) {
+ Assert.equal(this.accum, this.expected);
+ }
+
+ if (this.shouldSucceed) {
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+ } else {
+ Assert.ok(!Components.isSuccessCode(status));
+ }
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ },
+};
+
+/*
+ * Support for testing valid multiplexing of streams
+ */
+
+var multiplexContent = generateContent(30 * 1024);
+
+/* Listener class to control the testing of multiplexing */
+var Http2MultiplexListener = function () {};
+
+Http2MultiplexListener.prototype = new Http2CheckListener();
+
+Http2MultiplexListener.prototype.streamID = 0;
+Http2MultiplexListener.prototype.buffer = "";
+
+Http2MultiplexListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID"));
+ var data = read_stream(stream, cnt);
+ this.buffer = this.buffer.concat(data);
+};
+
+Http2MultiplexListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+ Assert.ok(this.buffer == multiplexContent);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ // This is what does most of the hard work for us
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ var streamID = this.streamID;
+ this.finish({ httpProxyConnectResponseCode, streamID });
+};
+
+// Does the appropriate checks for header gatewaying
+var Http2HeaderListener = function (name, callback) {
+ this.name = name;
+ this.callback = callback;
+};
+
+Http2HeaderListener.prototype = new Http2CheckListener();
+Http2HeaderListener.prototype.value = "";
+
+Http2HeaderListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ var hvalue = request.getResponseHeader(this.name);
+ Assert.notEqual(hvalue, "");
+ this.callback(hvalue);
+ read_stream(stream, cnt);
+};
+
+var Http2PushListener = function (shouldBePushed) {
+ this.shouldBePushed = shouldBePushed;
+};
+
+Http2PushListener.prototype = new Http2CheckListener();
+
+Http2PushListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/push.js` ||
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/push2.js` ||
+ request.originalURI.spec == `https://localhost:${this.serverPort}/push5.js`
+ ) {
+ Assert.equal(
+ request.getResponseHeader("pushed"),
+ this.shouldBePushed ? "yes" : "no"
+ );
+ }
+ read_stream(stream, cnt);
+};
+
+const pushHdrTxt =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+const pullHdrTxt = pushHdrTxt.split("").reverse().join("");
+
+function checkContinuedHeaders(getHeader, headerPrefix, headerText) {
+ for (var i = 0; i < 265; i++) {
+ Assert.equal(getHeader(headerPrefix + 1), headerText);
+ }
+}
+
+var Http2ContinuedHeaderListener = function () {};
+
+Http2ContinuedHeaderListener.prototype = new Http2CheckListener();
+
+Http2ContinuedHeaderListener.prototype.onStopsLeft = 2;
+
+Http2ContinuedHeaderListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIHttpPushListener",
+ "nsIStreamListener",
+]);
+
+Http2ContinuedHeaderListener.prototype.getInterface = function (aIID) {
+ return this.QueryInterface(aIID);
+};
+
+Http2ContinuedHeaderListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/continuedheaders`
+ ) {
+ // This is the original request, so the only one where we'll have continued response headers
+ checkContinuedHeaders(
+ request.getResponseHeader,
+ "X-Pull-Test-Header-",
+ pullHdrTxt
+ );
+ }
+ read_stream(stream, cnt);
+};
+
+Http2ContinuedHeaderListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+
+ --this.onStopsLeft;
+ if (this.onStopsLeft === 0) {
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+};
+
+Http2ContinuedHeaderListener.prototype.onPush = function (
+ associatedChannel,
+ pushChannel
+) {
+ Assert.equal(
+ associatedChannel.originalURI.spec,
+ "https://localhost:" + this.serverPort + "/continuedheaders"
+ );
+ Assert.equal(pushChannel.getRequestHeader("x-pushed-request"), "true");
+ checkContinuedHeaders(
+ pushChannel.getRequestHeader,
+ "X-Push-Test-Header-",
+ pushHdrTxt
+ );
+
+ pushChannel.asyncOpen(this);
+};
+
+// Does the appropriate checks for a large GET response
+var Http2BigListener = function () {};
+
+Http2BigListener.prototype = new Http2CheckListener();
+Http2BigListener.prototype.buffer = "";
+
+Http2BigListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.buffer = this.buffer.concat(read_stream(stream, cnt));
+ // We know the server should send us the same data as our big post will be,
+ // so the md5 should be the same
+ Assert.equal(bigListenerMD5, request.getResponseHeader("X-Expected-MD5"));
+};
+
+Http2BigListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+
+ // Don't want to flood output, so don't use do_check_eq
+ Assert.ok(this.buffer == bigListenerData);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+var Http2HugeSuspendedListener = function () {};
+
+Http2HugeSuspendedListener.prototype = new Http2CheckListener();
+Http2HugeSuspendedListener.prototype.count = 0;
+
+Http2HugeSuspendedListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.count += cnt;
+ read_stream(stream, cnt);
+};
+
+Http2HugeSuspendedListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+ Assert.equal(this.count, 1024 * 1024 * 1); // 1mb of data expected
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+// Does the appropriate checks for POSTs
+var Http2PostListener = function (expected_md5) {
+ this.expected_md5 = expected_md5;
+};
+
+Http2PostListener.prototype = new Http2CheckListener();
+Http2PostListener.prototype.expected_md5 = "";
+
+Http2PostListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ Assert.equal(
+ this.expected_md5,
+ request.getResponseHeader("X-Calculated-MD5")
+ );
+};
+
+var ResumeStalledChannelListener = function () {};
+
+ResumeStalledChannelListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ isHttp2Connection: false,
+ shouldBeHttp2: true,
+ resumable: null,
+
+ onStartRequest: function testOnStartRequest(request) {
+ this.onStartRequestFired = true;
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.responseStatus, 200);
+ Assert.equal(request.requestSucceeded, true);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+ this.resumable.resume();
+ },
+};
+
+// test a large download that creates stream flow control and
+// confirm we can do another independent stream while the download
+// stream is stuck
+async function test_http2_blocking_download(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/bigdownload`);
+ var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.initialRwin = 500000; // make the stream.suspend push back in h2
+ var p = new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.expected = 3 * 1024 * 1024;
+ chan.asyncOpen(listener);
+ chan.suspend();
+ });
+ // wait 5 seconds so that stream flow control kicks in and then see if we
+ // can do a basic transaction (i.e. session not blocked). afterwards resume
+ // channel
+ do_timeout(5000, function () {
+ var simpleChannel = makeHTTPChannel(`https://localhost:${serverPort}/`);
+ var sl = new ResumeStalledChannelListener();
+ sl.resumable = chan;
+ simpleChannel.asyncOpen(sl);
+ });
+ return p;
+}
+
+// Make sure we make a HTTP2 connection and both us and the server mark it as such
+async function test_http2_basic(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/`);
+ var p = new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+ return p;
+}
+
+async function test_http2_basic_unblocked_dep(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/basic_unblocked_dep`
+ );
+ var cos = chan.QueryInterface(Ci.nsIClassOfService);
+ cos.addClassFlags(Ci.nsIClassOfService.Unblocked);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// make sure we don't use h2 when disallowed
+async function test_http2_nospdy(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.allowSpdy = false;
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+// Support for making sure XHR works over SPDY
+function checkXhr(xhr, finish) {
+ if (xhr.readyState != 4) {
+ return;
+ }
+
+ Assert.equal(xhr.status, 200);
+ Assert.equal(checkIsHttp2(xhr), true);
+ finish();
+}
+
+// Fires off an XHR request over h2
+async function test_http2_xhr(serverPort) {
+ return new Promise(resolve => {
+ var req = new XMLHttpRequest();
+ req.open("GET", `https://localhost:${serverPort}/`, true);
+ req.addEventListener("readystatechange", function (evt) {
+ checkXhr(req, resolve);
+ });
+ req.send(null);
+ });
+}
+
+var Http2ConcurrentListener = function () {};
+
+Http2ConcurrentListener.prototype = new Http2CheckListener();
+Http2ConcurrentListener.prototype.count = 0;
+Http2ConcurrentListener.prototype.target = 0;
+Http2ConcurrentListener.prototype.reset = 0;
+Http2ConcurrentListener.prototype.recvdHdr = 0;
+
+Http2ConcurrentListener.prototype.onStopRequest = function (request, status) {
+ this.count++;
+ Assert.ok(this.isHttp2Connection);
+ if (this.recvdHdr > 0) {
+ Assert.equal(request.getResponseHeader("X-Recvd"), this.recvdHdr);
+ }
+
+ if (this.count == this.target) {
+ if (this.reset > 0) {
+ Services.prefs.setIntPref(
+ "network.http.http2.default-concurrent",
+ this.reset
+ );
+ }
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+};
+
+async function test_http2_concurrent(concurrent_channels, serverPort) {
+ var p = new Promise(resolve => {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.finish = resolve;
+ concurrent_listener.target = 201;
+ concurrent_listener.reset = Services.prefs.getIntPref(
+ "network.http.http2.default-concurrent"
+ );
+ Services.prefs.setIntPref("network.http.http2.default-concurrent", 100);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeHTTPChannel(
+ `https://localhost:${serverPort}/750ms`
+ );
+ concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ concurrent_channels[i].asyncOpen(concurrent_listener);
+ }
+ });
+ return p;
+}
+
+async function test_http2_concurrent_post(concurrent_channels, serverPort) {
+ return new Promise(resolve => {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.finish = resolve;
+ concurrent_listener.target = 8;
+ concurrent_listener.recvdHdr = posts[2].length;
+ concurrent_listener.reset = Services.prefs.getIntPref(
+ "network.http.http2.default-concurrent"
+ );
+ Services.prefs.setIntPref("network.http.http2.default-concurrent", 3);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeHTTPChannel(
+ `https://localhost:${serverPort}/750msPost`
+ );
+ concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = posts[2];
+ var uchan = concurrent_channels[i].QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+ concurrent_channels[i].requestMethod = "POST";
+ concurrent_channels[i].asyncOpen(concurrent_listener);
+ }
+ });
+}
+
+// Test to make sure we get multiplexing right
+async function test_http2_multiplex(serverPort) {
+ let chan1 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex1`);
+ let chan2 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex2`);
+ let listener1 = new Http2MultiplexListener();
+ let listener2 = new Http2MultiplexListener();
+
+ let promises = [];
+ let p1 = new Promise(resolve => {
+ listener1.finish = resolve;
+ });
+ promises.push(p1);
+ let p2 = new Promise(resolve => {
+ listener2.finish = resolve;
+ });
+ promises.push(p2);
+
+ chan1.asyncOpen(listener1);
+ chan2.asyncOpen(listener2);
+ return Promise.all(promises);
+}
+
+// Test to make sure we gateway non-standard headers properly
+async function test_http2_header(serverPort) {
+ let chan = makeHTTPChannel(`https://localhost:${serverPort}/header`);
+ let hvalue = "Headers are fun";
+ chan.setRequestHeader("X-Test-Header", hvalue, false);
+ return new Promise(resolve => {
+ let listener = new Http2HeaderListener("X-Received-Test-Header", function (
+ received_hvalue
+ ) {
+ Assert.equal(received_hvalue, hvalue);
+ });
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// Test to make sure headers with invalid characters in the name are rejected
+async function test_http2_invalid_response_header(serverPort, invalid_kind) {
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldSucceed = false;
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/invalid_response_header/${invalid_kind}`
+ );
+ chan.asyncOpen(listener);
+ });
+}
+
+// Test to make sure cookies are split into separate fields before compression
+async function test_http2_cookie_crumbling(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/cookie_crumbling`
+ );
+ var cookiesSent = ["a=b", "c=d01234567890123456789", "e=f"].sort();
+ chan.setRequestHeader("Cookie", cookiesSent.join("; "), false);
+ return new Promise(resolve => {
+ var listener = new Http2HeaderListener("X-Received-Header-Pairs", function (
+ pairsReceived
+ ) {
+ var cookiesReceived = JSON.parse(pairsReceived)
+ .filter(function (pair) {
+ return pair[0] == "cookie";
+ })
+ .map(function (pair) {
+ return pair[1];
+ })
+ .sort();
+ Assert.equal(cookiesReceived.length, cookiesSent.length);
+ cookiesReceived.forEach(function (cookieReceived, index) {
+ Assert.equal(cookiesSent[index], cookieReceived);
+ });
+ });
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push2(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push3(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push2`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push4(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push2.js`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push5(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push5`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push6(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push5.js`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+// this is a basic test where the server sends a simple document with 2 header
+// blocks. bug 1027364
+async function test_http2_doubleheader(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/doubleheader`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// Make sure we handle GETs that cover more than 2 frames properly
+async function test_http2_big(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/big`);
+ return new Promise(resolve => {
+ var listener = new Http2BigListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_huge_suspended(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/huge`);
+ return new Promise(resolve => {
+ var listener = new Http2HugeSuspendedListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ chan.suspend();
+ do_timeout(500, chan.resume);
+ });
+}
+
+// Support for doing a POST
+function do_post(content, chan, listener, method) {
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = content;
+
+ var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+
+ chan.requestMethod = method;
+
+ chan.asyncOpen(listener);
+}
+
+// Make sure we can do a simple POST
+async function test_http2_post(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+ var p = new Promise(resolve => {
+ var listener = new Http2PostListener(md5s[0]);
+ listener.finish = resolve;
+ do_post(posts[0], chan, listener, "POST");
+ });
+ return p;
+}
+
+async function test_http2_empty_post(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+ var p = new Promise(resolve => {
+ var listener = new Http2PostListener("0");
+ listener.finish = resolve;
+ do_post("", chan, listener, "POST");
+ });
+ return p;
+}
+
+// Make sure we can do a simple PATCH
+async function test_http2_patch(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/patch`);
+ return new Promise(resolve => {
+ var listener = new Http2PostListener(md5s[0]);
+ listener.finish = resolve;
+ do_post(posts[0], chan, listener, "PATCH");
+ });
+}
+
+// Make sure we can do a POST that covers more than 2 frames
+async function test_http2_post_big(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+ return new Promise(resolve => {
+ var listener = new Http2PostListener(md5s[1]);
+ listener.finish = resolve;
+ do_post(posts[1], chan, listener, "POST");
+ });
+}
+
+// When a http proxy is used alt-svc is disable. Therefore if withProxy is true,
+// try numberOfTries times to connect and make sure that alt-svc is not use and we never
+// connect to the HTTP/2 server.
+var altsvcClientListener = function (
+ finish,
+ httpserv,
+ httpserv2,
+ withProxy,
+ numberOfTries
+) {
+ this.finish = finish;
+ this.httpserv = httpserv;
+ this.httpserv2 = httpserv2;
+ this.withProxy = withProxy;
+ this.numberOfTries = numberOfTries;
+};
+
+altsvcClientListener.prototype = {
+ onStartRequest: function test_onStartR(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ var isHttp2Connection = checkIsHttp2(
+ request.QueryInterface(Ci.nsIHttpChannel)
+ );
+ if (!isHttp2Connection) {
+ dump("/altsvc1 not over h2 yet - retry\n");
+ if (this.withProxy && this.numberOfTries == 0) {
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ return;
+ }
+ let chan = makeHTTPChannel(
+ `http://foo.example.com:${this.httpserv}/altsvc1`,
+ this.withProxy
+ ).QueryInterface(Ci.nsIHttpChannel);
+ // we use this header to tell the server to issue a altsvc frame for the
+ // speficied origin we will use in the next part of the test
+ chan.setRequestHeader(
+ "x-redirect-origin",
+ `http://foo.example.com:${this.httpserv2}`,
+ false
+ );
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(
+ new altsvcClientListener(
+ this.finish,
+ this.httpserv,
+ this.httpserv2,
+ this.withProxy,
+ this.numberOfTries - 1
+ )
+ );
+ } else {
+ Assert.ok(isHttp2Connection);
+ let chan = makeHTTPChannel(
+ `http://foo.example.com:${this.httpserv2}/altsvc2`
+ ).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(
+ new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2)
+ );
+ }
+ },
+};
+
+var altsvcClientListener2 = function (finish, httpserv, httpserv2) {
+ this.finish = finish;
+ this.httpserv = httpserv;
+ this.httpserv2 = httpserv2;
+};
+
+altsvcClientListener2.prototype = {
+ onStartRequest: function test_onStartR(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ var isHttp2Connection = checkIsHttp2(
+ request.QueryInterface(Ci.nsIHttpChannel)
+ );
+ if (!isHttp2Connection) {
+ dump("/altsvc2 not over h2 yet - retry\n");
+ var chan = makeHTTPChannel(
+ `http://foo.example.com:${this.httpserv2}/altsvc2`
+ ).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(
+ new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2)
+ );
+ } else {
+ Assert.ok(isHttp2Connection);
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+ },
+};
+
+async function test_http2_altsvc(httpserv, httpserv2, withProxy) {
+ var chan = makeHTTPChannel(
+ `http://foo.example.com:${httpserv}/altsvc1`,
+ withProxy
+ ).QueryInterface(Ci.nsIHttpChannel);
+ return new Promise(resolve => {
+ var numberOfTries = 0;
+ if (withProxy) {
+ numberOfTries = 20;
+ }
+ chan.asyncOpen(
+ new altsvcClientListener(
+ resolve,
+ httpserv,
+ httpserv2,
+ withProxy,
+ numberOfTries
+ )
+ );
+ });
+}
+
+var Http2PushApiListener = function (finish, serverPort) {
+ this.finish = finish;
+ this.serverPort = serverPort;
+};
+
+Http2PushApiListener.prototype = {
+ checksPending: 9, // 4 onDataAvailable and 5 onStop
+
+ getInterface(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIHttpPushListener",
+ "nsIStreamListener",
+ ]),
+
+ // nsIHttpPushListener
+ onPush: function onPush(associatedChannel, pushChannel) {
+ Assert.equal(
+ associatedChannel.originalURI.spec,
+ "https://localhost:" + this.serverPort + "/pushapi1"
+ );
+ Assert.equal(pushChannel.getRequestHeader("x-pushed-request"), "true");
+
+ pushChannel.asyncOpen(this);
+ if (
+ pushChannel.originalURI.spec ==
+ "https://localhost:" + this.serverPort + "/pushapi1/2"
+ ) {
+ pushChannel.cancel(Cr.NS_ERROR_ABORT);
+ } else if (
+ pushChannel.originalURI.spec ==
+ "https://localhost:" + this.serverPort + "/pushapi1/3"
+ ) {
+ Assert.ok(pushChannel.getRequestHeader("Accept-Encoding").includes("br"));
+ }
+ },
+
+ // normal Channel listeners
+ onStartRequest: function pushAPIOnStart(request) {},
+
+ onDataAvailable: function pushAPIOnDataAvailable(
+ request,
+ stream,
+ offset,
+ cnt
+ ) {
+ Assert.notEqual(
+ request.originalURI.spec,
+ `https://localhost:${this.serverPort}/pushapi1/2`
+ );
+
+ var data = read_stream(stream, cnt);
+
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1`
+ ) {
+ Assert.equal(data[0], "0");
+ --this.checksPending;
+ } else if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1/1`
+ ) {
+ Assert.equal(data[0], "1");
+ --this.checksPending; // twice
+ } else if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1/3`
+ ) {
+ Assert.equal(data[0], "3");
+ --this.checksPending;
+ } else {
+ Assert.equal(true, false);
+ }
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1/2`
+ ) {
+ Assert.equal(request.status, Cr.NS_ERROR_ABORT);
+ } else {
+ Assert.equal(request.status, Cr.NS_OK);
+ }
+
+ --this.checksPending; // 5 times - one for each push plus the pull
+ if (!this.checksPending) {
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+ },
+};
+
+// pushAPI testcase 1 expects
+// 1 to pull /pushapi1 with 0
+// 2 to see /pushapi1/1 with 1
+// 3 to see /pushapi1/1 with 1 (again)
+// 4 to see /pushapi1/2 that it will cancel
+// 5 to see /pushapi1/3 with 3 with brotli
+
+async function test_http2_pushapi_1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/pushapi1`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushApiListener(resolve, serverPort);
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener);
+ });
+}
+
+var WrongSuiteListener = function () {};
+
+WrongSuiteListener.prototype = new Http2CheckListener();
+WrongSuiteListener.prototype.shouldBeHttp2 = false;
+WrongSuiteListener.prototype.onStopRequest = function (request, status) {
+ Services.prefs.setBoolPref(
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
+ true
+ );
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Http2CheckListener.prototype.onStopRequest.call(this, request, status);
+};
+
+// test that we use h1 without the mandatory cipher suite available when
+// offering at most tls1.2
+async function test_http2_wrongsuite_tls12(serverPort) {
+ Services.prefs.setBoolPref(
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
+ false
+ );
+ Services.prefs.setIntPref("security.tls.version.max", 3);
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`);
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return new Promise(resolve => {
+ var listener = new WrongSuiteListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// test that we use h2 when offering tls1.3 or higher regardless of if the
+// mandatory cipher suite is available
+async function test_http2_wrongsuite_tls13(serverPort) {
+ Services.prefs.setBoolPref(
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
+ false
+ );
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`);
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return new Promise(resolve => {
+ var listener = new WrongSuiteListener();
+ listener.finish = resolve;
+ listener.shouldBeHttp2 = true;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_h11required_stream(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/h11required_stream`
+ );
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+function H11RequiredSessionListener() {}
+
+H11RequiredSessionListener.prototype = new Http2CheckListener();
+
+H11RequiredSessionListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ var streamReused = request.getResponseHeader("X-H11Required-Stream-Ok");
+ Assert.equal(streamReused, "yes");
+
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+async function test_http2_h11required_session(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/h11required_session`
+ );
+ return new Promise(resolve => {
+ var listener = new H11RequiredSessionListener();
+ listener.finish = resolve;
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_retry_rst(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/rstonce`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_continuations(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/continuedheaders`
+ );
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2ContinuedHeaderListener();
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener);
+ });
+}
+
+function Http2IllegalHpackValidationListener() {}
+
+Http2IllegalHpackValidationListener.prototype = new Http2CheckListener();
+Http2IllegalHpackValidationListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackValidationListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ var wentAway = request.getResponseHeader("X-Did-Goaway") === "yes";
+ Assert.equal(wentAway, this.shouldGoAway);
+
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+function Http2IllegalHpackListener() {}
+Http2IllegalHpackListener.prototype = new Http2CheckListener();
+Http2IllegalHpackListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackListener.prototype.onStopRequest = function (request, status) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${this.serverPort}/illegalhpack_validate`
+ );
+ var listener = new Http2IllegalHpackValidationListener();
+ listener.finish = this.finish;
+ listener.shouldGoAway = this.shouldGoAway;
+ chan.asyncOpen(listener);
+};
+
+async function test_http2_illegalhpacksoft(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/illegalhpacksoft`
+ );
+ return new Promise(resolve => {
+ var listener = new Http2IllegalHpackListener();
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ listener.shouldGoAway = false;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_illegalhpackhard(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/illegalhpackhard`
+ );
+ return new Promise(resolve => {
+ var listener = new Http2IllegalHpackListener();
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ listener.shouldGoAway = true;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_folded_header(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/foldedheader`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_empty_data(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/emptydata`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_firstparty1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "foo.com" };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_firstparty2(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "bar.com" };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(false);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_firstparty3(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "foo.com" };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_userContext1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 1 };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_userContext2(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 2 };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(false);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_userContext3(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 1 };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_status_phrase(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/statusphrase`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+var PulledDiskCacheListener = function () {};
+PulledDiskCacheListener.prototype = new Http2CheckListener();
+PulledDiskCacheListener.prototype.EXPECTED_DATA = "this was pulled via h2";
+PulledDiskCacheListener.prototype.readData = "";
+PulledDiskCacheListener.prototype.onDataAvailable =
+ function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.accum += cnt;
+ this.readData += read_stream(stream, cnt);
+ };
+PulledDiskCacheListener.prototype.onStopRequest = function testOnStopRequest(
+ request,
+ status
+) {
+ Assert.equal(this.EXPECTED_DATA, this.readData);
+ Http2CheckListener.prorotype.onStopRequest.call(this, request, status);
+};
+
+const DISK_CACHE_DATA = "this is from disk cache";
+
+var FromDiskCacheListener = function (finish, loadGroup, serverPort) {
+ this.finish = finish;
+ this.loadGroup = loadGroup;
+ this.serverPort = serverPort;
+};
+FromDiskCacheListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ readData: "",
+
+ onStartRequest: function testOnStartRequest(request) {
+ this.onStartRequestFired = true;
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.ok(request.requestSucceeded);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.readData += read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.equal(this.readData, DISK_CACHE_DATA);
+
+ evict_cache_entries("disk");
+ syncWithCacheIOThread(() => {
+ // Now that we know the entry is out of the disk cache, check to make sure
+ // we don't have this hiding in the push cache somewhere - if we do, it
+ // didn't get cancelled, and we have a bug.
+ var chan = makeHTTPChannel(
+ `https://localhost:${this.serverPort}/diskcache`
+ );
+ var listener = new PulledDiskCacheListener();
+ listener.finish = this.finish;
+ chan.loadGroup = this.loadGroup;
+ chan.asyncOpen(listener);
+ });
+ },
+};
+
+var Http2DiskCachePushListener = function () {};
+Http2DiskCachePushListener.prototype = new Http2CheckListener();
+
+Http2DiskCachePushListener.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ // Now we need to open a channel to ensure we get data from the disk cache
+ // for the pushed item, instead of from the push cache.
+ var chan = makeHTTPChannel(`https://localhost:${this.serverPort}/diskcache`);
+ var listener = new FromDiskCacheListener(
+ this.finish,
+ this.loadGroup,
+ this.serverPort
+ );
+ chan.loadGroup = this.loadGroup;
+ chan.asyncOpen(listener);
+};
+
+function continue_test_http2_disk_cache_push(
+ status,
+ entry,
+ finish,
+ loadGroup,
+ serverPort
+) {
+ // TODO - store stuff in cache entry, then open an h2 channel that will push
+ // this, once that completes, open a channel for the cache entry we made and
+ // ensure it came from disk cache, not the push cache.
+ var outputStream = entry.openOutputStream(0, -1);
+ outputStream.write(DISK_CACHE_DATA, DISK_CACHE_DATA.length);
+
+ // Now we open our URL that will push data for the URL above
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/pushindisk`);
+ var listener = new Http2DiskCachePushListener();
+ listener.finish = finish;
+ listener.loadGroup = loadGroup;
+ listener.serverPort = serverPort;
+ chan.loadGroup = loadGroup;
+ chan.asyncOpen(listener);
+}
+
+async function test_http2_disk_cache_push(loadGroup, serverPort) {
+ return new Promise(resolve => {
+ asyncOpenCacheEntry(
+ `https://localhost:${serverPort}/diskcache`,
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (status, entry) {
+ continue_test_http2_disk_cache_push(
+ status,
+ entry,
+ resolve,
+ loadGroup,
+ serverPort
+ );
+ },
+ false
+ );
+ });
+}
+
+var Http2DoublepushListener = function () {};
+Http2DoublepushListener.prototype = new Http2CheckListener();
+Http2DoublepushListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ var chan = makeHTTPChannel(
+ `https://localhost:${this.serverPort}/doublypushed`
+ );
+ var listener = new Http2DoublypushedListener();
+ listener.finish = this.finish;
+ chan.loadGroup = this.loadGroup;
+ chan.asyncOpen(listener);
+};
+
+var Http2DoublypushedListener = function () {};
+Http2DoublypushedListener.prototype = new Http2CheckListener();
+Http2DoublypushedListener.prototype.readData = "";
+Http2DoublypushedListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.accum += cnt;
+ this.readData += read_stream(stream, cnt);
+};
+Http2DoublypushedListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.equal(this.readData, "pushed");
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ let httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+function test_http2_doublepush(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/doublepush`);
+ return new Promise(resolve => {
+ var listener = new Http2DoublepushListener();
+ listener.finish = resolve;
+ listener.loadGroup = loadGroup;
+ listener.serverPort = serverPort;
+ chan.loadGroup = loadGroup;
+ chan.asyncOpen(listener);
+ });
+}
diff --git a/netwerk/test/unit/node_execute/test_node_execute_loop.js b/netwerk/test/unit/node_execute/test_node_execute_loop.js
new file mode 100644
index 0000000000..10400b8b54
--- /dev/null
+++ b/netwerk/test/unit/node_execute/test_node_execute_loop.js
@@ -0,0 +1,22 @@
+// This test checks that the interaction between NodeServer.execute defined in
+// httpd.js and the node server that we're interacting with defined in
+// moz-http2.js is working properly.
+// This test spawns a node server that loops on while true and makes sure
+// the the process group is killed by runxpcshelltests.py at exit.
+// See bug 1855174
+
+"use strict";
+
+const { NodeServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+add_task(async function killOnEnd() {
+ let id = await NodeServer.fork();
+ await NodeServer.execute(id, `console.log("hello");`);
+ await NodeServer.execute(id, `console.error("hello");`);
+ // Make the forked subprocess hang forever.
+ NodeServer.execute(id, "while (true) {}").catch(e => {});
+ await new Promise(resolve => do_timeout(10, resolve));
+ // Should get killed at the end of the test by the harness.
+});
diff --git a/netwerk/test/unit/node_execute/xpcshell.toml b/netwerk/test/unit/node_execute/xpcshell.toml
new file mode 100644
index 0000000000..7d881a3bda
--- /dev/null
+++ b/netwerk/test/unit/node_execute/xpcshell.toml
@@ -0,0 +1,5 @@
+[DEFAULT]
+
+["test_node_execute_loop.js"]
+run-sequentially = "node server exceptions dont replay well"
+skip-if = ["verify"] # running it once hangs forever so don't run it in a loop.
diff --git a/netwerk/test/unit/perftest.toml b/netwerk/test/unit/perftest.toml
new file mode 100644
index 0000000000..709f6ce7b4
--- /dev/null
+++ b/netwerk/test/unit/perftest.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["test_http3_perf.js"]
diff --git a/netwerk/test/unit/proxy-ca.pem b/netwerk/test/unit/proxy-ca.pem
new file mode 100644
index 0000000000..5325d8cbd2
--- /dev/null
+++ b/netwerk/test/unit/proxy-ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1DCCAbygAwIBAgIUW4p+/QPIt/MX8PWl1HdqbTSfjakwDQYJKoZIhvcNAQEL
+BQAwGTEXMBUGA1UEAwwOIFByb3h5IFRlc3QgQ0EwIhgPMjAyMjAxMDEwMDAwMDBa
+GA8yMDMyMDEwMTAwMDAwMFowGTEXMBUGA1UEAwwOIFByb3h5IFRlc3QgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT
+2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzV
+JJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8N
+jf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCA
+BiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVh
+He4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMB
+AAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIeZDX+A8ZhI
+NU+wg2vTMKr5hyd0cOVUgOOWUGmATrgAMuo9Asn29SNcI61nGTrUpDBuDCy8OTQf
+J7Gva5eLmS/c+INpRtLzcCKvkd06bexSKk8naUTuFwtwtS0WQwSV20yUf9mR+UcO
+60U9F2r7dHfgFqXlNhQ1AngXkfMOlrCWw50CyMj7y9fOeJ22Q0JDXzK2UU64tWhi
+e22fSfFCdjRcIPiqdG+BHNQe2M6DYPMgrrEnRqq/3mJsf886FxR1AhqkhYS/tkLZ
+TGSrETajIK62v3qY9LNB8iWv2e0lj0pZlPsTmUWysIXc3fAF6RIu3T8Ypxm5i6sw
+OfmaaMxPV20=
+-----END CERTIFICATE-----
diff --git a/netwerk/test/unit/proxy-ca.pem.certspec b/netwerk/test/unit/proxy-ca.pem.certspec
new file mode 100644
index 0000000000..4e0279143d
--- /dev/null
+++ b/netwerk/test/unit/proxy-ca.pem.certspec
@@ -0,0 +1,4 @@
+issuer: Proxy Test CA
+subject: Proxy Test CA
+validity:20220101-20320101
+extension:basicConstraints:cA,
diff --git a/netwerk/test/unit/socks_client_subprocess.js b/netwerk/test/unit/socks_client_subprocess.js
new file mode 100644
index 0000000000..7bd5dc120e
--- /dev/null
+++ b/netwerk/test/unit/socks_client_subprocess.js
@@ -0,0 +1,94 @@
+/* global arguments */
+
+"use strict";
+
+var CC = Components.Constructor;
+
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+const ProtocolProxyService = CC(
+ "@mozilla.org/network/protocol-proxy-service;1",
+ "nsIProtocolProxyService"
+);
+var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+);
+
+function waitForStream(stream, streamType) {
+ return new Promise((resolve, reject) => {
+ stream = stream.QueryInterface(streamType);
+ if (!stream) {
+ reject("stream didn't implement given stream type");
+ }
+ let currentThread =
+ Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+ stream.asyncWait(resolve, 0, 0, currentThread);
+ });
+}
+
+async function launchConnection(
+ socks_vers,
+ socks_port,
+ dest_host,
+ dest_port,
+ dns
+) {
+ let pi_flags = 0;
+ if (dns == "remote") {
+ pi_flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+
+ let pps = new ProtocolProxyService();
+ let pi = pps.newProxyInfo(
+ socks_vers,
+ "localhost",
+ socks_port,
+ "",
+ "",
+ pi_flags,
+ -1,
+ null
+ );
+ let trans = sts.createTransport([], dest_host, dest_port, pi, null);
+ let input = trans.openInputStream(0, 0, 0);
+ let output = trans.openOutputStream(0, 0, 0);
+ input = await waitForStream(input, Ci.nsIAsyncInputStream);
+ let bin = new BinaryInputStream(input);
+ let data = bin.readBytes(5);
+ let response;
+ if (data == "PING!") {
+ print("client: got ping, sending pong.");
+ response = "PONG!";
+ } else {
+ print("client: wrong data from server:", data);
+ response = "Error: wrong data received.";
+ }
+ output = await waitForStream(output, Ci.nsIAsyncOutputStream);
+ output.write(response, response.length);
+ output.close();
+ input.close();
+}
+
+async function run(args) {
+ for (let arg of args) {
+ print("client: running test", arg);
+ let test = arg.split("|");
+ await launchConnection(
+ test[0],
+ parseInt(test[1]),
+ test[2],
+ parseInt(test[3]),
+ test[4]
+ );
+ }
+}
+
+var satisfied = false;
+run(arguments).then(() => (satisfied = true));
+var mainThread = Cc["@mozilla.org/thread-manager;1"].getService().mainThread;
+while (!satisfied) {
+ mainThread.processNextEvent(true);
+}
diff --git a/netwerk/test/unit/test_1073747.js b/netwerk/test/unit/test_1073747.js
new file mode 100644
index 0000000000..dd8c597113
--- /dev/null
+++ b/netwerk/test/unit/test_1073747.js
@@ -0,0 +1,42 @@
+// Test based on submitted one from Peter B Shalimoff
+
+"use strict";
+
+var test = function (s, funcName) {
+ function Arg() {}
+ Arg.prototype.toString = function () {
+ info("Testing " + funcName + " with null args");
+ return this.value;
+ };
+ // create a generic arg lits of null, -1, and 10 nulls
+ var args = [s, -1];
+ for (var i = 0; i < 10; ++i) {
+ args.push(new Arg());
+ }
+ var up = Cc["@mozilla.org/network/url-parser;1?auth=maybe"].getService(
+ Ci.nsIURLParser
+ );
+ try {
+ up[funcName].apply(up, args);
+ return args;
+ } catch (x) {
+ Assert.ok(true); // make sure it throws an exception instead of crashing
+ return x;
+ }
+};
+var s = null;
+var funcs = [
+ "parseAuthority",
+ "parseFileName",
+ "parseFilePath",
+ "parsePath",
+ "parseServerInfo",
+ "parseURL",
+ "parseUserInfo",
+];
+
+function run_test() {
+ funcs.forEach(function (f) {
+ test(s, f);
+ });
+}
diff --git a/netwerk/test/unit/test_304_headers.js b/netwerk/test/unit/test_304_headers.js
new file mode 100644
index 0000000000..b3583663bf
--- /dev/null
+++ b/netwerk/test/unit/test_304_headers.js
@@ -0,0 +1,91 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return `http://localhost:${httpServer.identity.primaryPort}/test`;
+});
+
+let httpServer = null;
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function contentHandler(metadata, response) {
+ response.seizePower();
+ let etag = "";
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {}
+
+ if (etag == "test-etag1") {
+ response.write("HTTP/1.1 304 Not Modified\r\n");
+
+ response.write("Link: <ref>; param1=value1\r\n");
+ response.write("Link: <ref2>; param2=value2\r\n");
+ response.write("Link: <ref3>; param1=value1\r\n");
+ response.write("\r\n");
+ response.finish();
+ return;
+ }
+
+ response.write("HTTP/1.1 200 OK\r\n");
+
+ response.write("ETag: test-etag1\r\n");
+ response.write("Link: <ref>; param1=value1\r\n");
+ response.write("Link: <ref2>; param2=value2\r\n");
+ response.write("Link: <ref3>; param1=value1\r\n");
+ response.write("\r\n");
+ response.finish();
+}
+
+add_task(async function test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/test", contentHandler);
+ httpServer.start(-1);
+ registerCleanupFunction(async () => {
+ await httpServer.stop();
+ });
+
+ let chan = make_channel(Services.io.newURI(URL));
+ chan.requestMethod = "HEAD";
+ await new Promise(resolve => {
+ chan.asyncOpen({
+ onStopRequest(req, status) {
+ equal(status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannel).getResponseHeader("Link"),
+ "<ref>; param1=value1, <ref2>; param2=value2, <ref3>; param1=value1"
+ );
+ resolve();
+ },
+ onStartRequest(req) {},
+ onDataAvailable() {},
+ });
+ });
+
+ chan = make_channel(Services.io.newURI(URL));
+ chan.requestMethod = "HEAD";
+ await new Promise(resolve => {
+ chan.asyncOpen({
+ onStopRequest(req, status) {
+ equal(status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannel).getResponseHeader("Link"),
+ "<ref>; param1=value1, <ref2>; param2=value2, <ref3>; param1=value1"
+ );
+ resolve();
+ },
+ onStartRequest(req) {},
+ onDataAvailable() {},
+ });
+ });
+});
diff --git a/netwerk/test/unit/test_304_responses.js b/netwerk/test/unit/test_304_responses.js
new file mode 100644
index 0000000000..a468eec191
--- /dev/null
+++ b/netwerk/test/unit/test_304_responses.js
@@ -0,0 +1,92 @@
+"use strict";
+// https://bugzilla.mozilla.org/show_bug.cgi?id=761228
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+const testFileName = "test_customConditionalRequest_304";
+const basePath = "/" + testFileName + "/";
+
+ChromeUtils.defineLazyGetter(this, "baseURI", function () {
+ return URL + basePath;
+});
+
+const unexpected304 = "unexpected304";
+const existingCached304 = "existingCached304";
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function alwaysReturn304Handler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ response.setHeader("Returned-From-Handler", "1");
+}
+
+function run_test() {
+ evict_cache_entries();
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(
+ basePath + unexpected304,
+ alwaysReturn304Handler
+ );
+ httpServer.registerPathHandler(
+ basePath + existingCached304,
+ alwaysReturn304Handler
+ );
+ httpServer.start(-1);
+ run_next_test();
+}
+
+function consume304(request, buffer) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(request.responseStatus, 304);
+ Assert.equal(request.getResponseHeader("Returned-From-Handler"), "1");
+ run_next_test();
+}
+
+// Test that we return a 304 response to the caller when we are not expecting
+// a 304 response (i.e. when the server shouldn't have sent us one).
+add_test(function test_unexpected_304() {
+ var chan = make_channel(baseURI + unexpected304);
+ chan.asyncOpen(new ChannelListener(consume304, null));
+});
+
+// Test that we can cope with a 304 response that was (erroneously) stored in
+// the cache.
+add_test(function test_304_stored_in_cache() {
+ asyncOpenCacheEntry(
+ baseURI + existingCached304,
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (entryStatus, cacheEntry) {
+ cacheEntry.setMetaDataElement("request-method", "GET");
+ cacheEntry.setMetaDataElement(
+ "response-head",
+ // eslint-disable-next-line no-useless-concat
+ "HTTP/1.1 304 Not Modified\r\n" + "\r\n"
+ );
+ cacheEntry.metaDataReady();
+ cacheEntry.close();
+
+ var chan = make_channel(baseURI + existingCached304);
+
+ // make it a custom conditional request
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.setRequestHeader("If-None-Match", '"foo"', false);
+
+ chan.asyncOpen(new ChannelListener(consume304, null));
+ }
+ );
+});
diff --git a/netwerk/test/unit/test_307_redirect.js b/netwerk/test/unit/test_307_redirect.js
new file mode 100644
index 0000000000..983884c134
--- /dev/null
+++ b/netwerk/test/unit/test_307_redirect.js
@@ -0,0 +1,95 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "uri", function () {
+ return URL + "/redirect";
+});
+
+ChromeUtils.defineLazyGetter(this, "noRedirectURI", function () {
+ return URL + "/content";
+});
+
+var httpserver = null;
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const requestBody = "request body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
+ response.setHeader("Location", noRedirectURI, false);
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.writeFrom(
+ metadata.bodyInputStream,
+ metadata.bodyInputStream.available()
+ );
+}
+
+function noRedirectStreamObserver(request, buffer) {
+ Assert.equal(buffer, requestBody);
+ var chan = make_channel(uri);
+ var uploadStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ uploadStream.setData(requestBody, requestBody.length);
+ chan
+ .QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(uploadStream, "text/plain", -1);
+ chan.asyncOpen(new ChannelListener(noHeaderStreamObserver, null));
+}
+
+function noHeaderStreamObserver(request, buffer) {
+ Assert.equal(buffer, requestBody);
+ var chan = make_channel(uri);
+ var uploadStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ var streamBody =
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: " +
+ requestBody.length +
+ "\r\n\r\n" +
+ requestBody;
+ uploadStream.setData(streamBody, streamBody.length);
+ chan
+ .QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(uploadStream, "", -1);
+ chan.asyncOpen(new ChannelListener(headerStreamObserver, null));
+}
+
+function headerStreamObserver(request, buffer) {
+ Assert.equal(buffer, requestBody);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/redirect", redirectHandler);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ Services.prefs.setBoolPref("network.http.prompt-temp-redirect", false);
+
+ var chan = make_channel(noRedirectURI);
+ var uploadStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ uploadStream.setData(requestBody, requestBody.length);
+ chan
+ .QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(uploadStream, "text/plain", -1);
+ chan.asyncOpen(new ChannelListener(noRedirectStreamObserver, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_421.js b/netwerk/test/unit/test_421.js
new file mode 100644
index 0000000000..680973384e
--- /dev/null
+++ b/netwerk/test/unit/test_421.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/421";
+var httpbody = "0123456789";
+var channel;
+
+function run_test() {
+ setup_test();
+ do_test_pending();
+}
+
+function setup_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ channel = setupChannel(testpath);
+
+ channel.asyncOpen(new ChannelListener(checkRequestResponse, channel));
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+var iters = 0;
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+
+ if (!iters) {
+ response.setStatusLine("1.1", 421, "Not Authoritative " + iters);
+ } else {
+ response.setStatusLine("1.1", 200, "OK");
+ }
+ ++iters;
+
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function checkRequestResponse(request, data, context) {
+ Assert.equal(channel.responseStatus, 200);
+ Assert.equal(channel.responseStatusText, "OK");
+ Assert.ok(channel.requestSucceeded);
+
+ Assert.equal(channel.contentType, "text/plain");
+ Assert.equal(channel.contentLength, httpbody.length);
+ Assert.equal(data, httpbody);
+
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_MIME_params.js b/netwerk/test/unit/test_MIME_params.js
new file mode 100644
index 0000000000..bebd5cb39b
--- /dev/null
+++ b/netwerk/test/unit/test_MIME_params.js
@@ -0,0 +1,798 @@
+/**
+ * Tests for parsing header fields using the syntax used in
+ * Content-Disposition and Content-Type
+ *
+ * See also https://bugzilla.mozilla.org/show_bug.cgi?id=609667
+ */
+
+"use strict";
+
+var BS = "\\";
+var DQUOTE = '"';
+
+// Test array:
+// - element 0: "Content-Disposition" header to test
+// under MIME (email):
+// - element 1: correct value returned for disposition-type (empty param name)
+// - element 2: correct value for filename returned
+// under HTTP:
+// (currently supports continuations; expected results without continuations
+// are commented out for now)
+// - element 3: correct value returned for disposition-type (empty param name)
+// - element 4: correct value for filename returned
+//
+// 3 and 4 may be left out if they are identical
+
+var tests = [
+ // No filename parameter: return nothing
+ ["attachment;", "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // basic
+ ["attachment; filename=basic", "attachment", "basic"],
+
+ // extended
+ ["attachment; filename*=UTF-8''extended", "attachment", "extended"],
+
+ // prefer extended to basic (bug 588781)
+ [
+ "attachment; filename=basic; filename*=UTF-8''extended",
+ "attachment",
+ "extended",
+ ],
+
+ // prefer extended to basic (bug 588781)
+ [
+ "attachment; filename*=UTF-8''extended; filename=basic",
+ "attachment",
+ "extended",
+ ],
+
+ // use first basic value (invalid; error recovery)
+ ["attachment; filename=first; filename=wrong", "attachment", "first"],
+
+ // old school bad HTTP servers: missing 'attachment' or 'inline'
+ // (invalid; error recovery)
+ ["filename=old", "filename=old", "old"],
+
+ ["attachment; filename*=UTF-8''extended", "attachment", "extended"],
+
+ // continuations not part of RFC 5987 (bug 610054)
+ [
+ "attachment; filename*0=foo; filename*1=bar",
+ "attachment",
+ "foobar",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */
+ ],
+
+ // Return first continuation (invalid; error recovery)
+ [
+ "attachment; filename*0=first; filename*0=wrong; filename=basic",
+ "attachment",
+ "first",
+ /* "attachment", "basic" */
+ ],
+
+ // Only use correctly ordered continuations (invalid; error recovery)
+ [
+ "attachment; filename*0=first; filename*1=second; filename*0=wrong",
+ "attachment",
+ "firstsecond",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */
+ ],
+
+ // prefer continuation to basic (unless RFC 5987)
+ [
+ "attachment; filename=basic; filename*0=foo; filename*1=bar",
+ "attachment",
+ "foobar",
+ /* "attachment", "basic" */
+ ],
+
+ // Prefer extended to basic and/or (broken or not) continuation
+ // (invalid; error recovery)
+ [
+ "attachment; filename=basic; filename*0=first; filename*0=wrong; filename*=UTF-8''extended",
+ "attachment",
+ "extended",
+ ],
+
+ // RFC 2231 not clear on correct outcome: we prefer non-continued extended
+ // (invalid; error recovery)
+ [
+ "attachment; filename=basic; filename*=UTF-8''extended; filename*0=foo; filename*1=bar",
+ "attachment",
+ "extended",
+ ],
+
+ // Gaps should result in returning only value until gap hit
+ // (invalid; error recovery)
+ [
+ "attachment; filename*0=foo; filename*2=bar",
+ "attachment",
+ "foo",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */
+ ],
+
+ // Don't allow leading 0's (*01) (invalid; error recovery)
+ [
+ "attachment; filename*0=foo; filename*01=bar",
+ "attachment",
+ "foo",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */
+ ],
+
+ // continuations should prevail over non-extended (unless RFC 5987)
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''multi;\r\n" +
+ " filename*1=line;\r\n" +
+ " filename*2*=%20extended",
+ "attachment",
+ "multiline extended",
+ /* "attachment", "basic" */
+ ],
+
+ // Gaps should result in returning only value until gap hit
+ // (invalid; error recovery)
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''multi;\r\n" +
+ " filename*1=line;\r\n" +
+ " filename*3*=%20extended",
+ "attachment",
+ "multiline",
+ /* "attachment", "basic" */
+ ],
+
+ // First series, only please, and don't slurp up higher elements (*2 in this
+ // case) from later series into earlier one (invalid; error recovery)
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''multi;\r\n" +
+ " filename*1=line;\r\n" +
+ " filename*0*=UTF-8''wrong;\r\n" +
+ " filename*1=bad;\r\n" +
+ " filename*2=evil",
+ "attachment",
+ "multiline",
+ /* "attachment", "basic" */
+ ],
+
+ // RFC 2231 not clear on correct outcome: we prefer non-continued extended
+ // (invalid; error recovery)
+ [
+ "attachment; filename=basic; filename*0=UTF-8''multi\r\n;" +
+ " filename*=UTF-8''extended;\r\n" +
+ " filename*1=line;\r\n" +
+ " filename*2*=%20extended",
+ "attachment",
+ "extended",
+ ],
+
+ // sneaky: if unescaped, make sure we leave UTF-8'' in value
+ [
+ "attachment; filename*0=UTF-8''unescaped;\r\n" +
+ " filename*1*=%20so%20includes%20UTF-8''%20in%20value",
+ "attachment",
+ "UTF-8''unescaped so includes UTF-8'' in value",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */
+ ],
+
+ // sneaky: if unescaped, make sure we leave UTF-8'' in value
+ [
+ "attachment; filename=basic; filename*0=UTF-8''unescaped;\r\n" +
+ " filename*1*=%20so%20includes%20UTF-8''%20in%20value",
+ "attachment",
+ "UTF-8''unescaped so includes UTF-8'' in value",
+ /* "attachment", "basic" */
+ ],
+
+ // Prefer basic over invalid continuation
+ // (invalid; error recovery)
+ [
+ "attachment; filename=basic; filename*1=multi;\r\n" +
+ " filename*2=line;\r\n" +
+ " filename*3*=%20extended",
+ "attachment",
+ "basic",
+ ],
+
+ // support digits over 10
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n" +
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n" +
+ " filename*11=b; filename*12=c;filename*13=d;filename*14=e;filename*15=f\r\n",
+ "attachment",
+ "0123456789abcdef",
+ /* "attachment", "basic" */
+ ],
+
+ // support digits over 10 (detect gaps)
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n" +
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n" +
+ " filename*11=b; filename*12=c;filename*14=e\r\n",
+ "attachment",
+ "0123456789abc",
+ /* "attachment", "basic" */
+ ],
+
+ // return nothing: invalid
+ // (invalid; error recovery)
+ [
+ "attachment; filename*1=multi;\r\n" +
+ " filename*2=line;\r\n" +
+ " filename*3*=%20extended",
+ "attachment",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+
+ // Bug 272541: Empty disposition type treated as "attachment"
+
+ // sanity check
+ [
+ "attachment; filename=foo.html",
+ "attachment",
+ "foo.html",
+ "attachment",
+ "foo.html",
+ ],
+
+ // the actual bug
+ [
+ "; filename=foo.html",
+ Cr.NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY,
+ "foo.html",
+ Cr.NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY,
+ "foo.html",
+ ],
+
+ // regression check, but see bug 671204
+ [
+ "filename=foo.html",
+ "filename=foo.html",
+ "foo.html",
+ "filename=foo.html",
+ "foo.html",
+ ],
+
+ // Bug 384571: RFC 2231 parameters not decoded when appearing in reversed order
+
+ // check ordering
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n" +
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n" +
+ " filename*11=b; filename*12=c;filename*13=d;filename*15=f;filename*14=e;\r\n",
+ "attachment",
+ "0123456789abcdef",
+ /* "attachment", "basic" */
+ ],
+
+ // check non-digits in sequence numbers
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
+ " filename*1a=1\r\n",
+ "attachment",
+ "0",
+ /* "attachment", "basic" */
+ ],
+
+ // check duplicate sequence numbers
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
+ " filename*0=bad; filename*1=1;\r\n",
+ "attachment",
+ "0",
+ /* "attachment", "basic" */
+ ],
+
+ // check overflow
+ [
+ "attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
+ " filename*11111111111111111111111111111111111111111111111111111111111=1",
+ "attachment",
+ "0",
+ /* "attachment", "basic" */
+ ],
+
+ // check underflow
+ [
+ // eslint-disable-next-line no-useless-concat
+ "attachment; filename=basic; filename*0*=UTF-8''0;\r\n" + " filename*-1=1",
+ "attachment",
+ "0",
+ /* "attachment", "basic" */
+ ],
+
+ // check mixed token/quoted-string
+ [
+ 'attachment; filename=basic; filename*0="0";\r\n' +
+ " filename*1=1;\r\n" +
+ " filename*2*=%32",
+ "attachment",
+ "012",
+ /* "attachment", "basic" */
+ ],
+
+ // check empty sequence number
+ [
+ "attachment; filename=basic; filename**=UTF-8''0\r\n",
+ "attachment",
+ "basic",
+ "attachment",
+ "basic",
+ ],
+
+ // Bug 419157: ensure that a MIME parameter with no charset information
+ // fallbacks to Latin-1
+
+ [
+ "attachment;filename=IT839\x04\xB5(m8)2.pdf;",
+ "attachment",
+ "IT839\u0004\u00b5(m8)2.pdf",
+ ],
+
+ // Bug 588389: unescaping backslashes in quoted string parameters
+
+ // '\"', should be parsed as '"'
+ [
+ "attachment; filename=" + DQUOTE + (BS + DQUOTE) + DQUOTE,
+ "attachment",
+ DQUOTE,
+ ],
+
+ // 'a\"b', should be parsed as 'a"b'
+ [
+ "attachment; filename=" + DQUOTE + "a" + (BS + DQUOTE) + "b" + DQUOTE,
+ "attachment",
+ "a" + DQUOTE + "b",
+ ],
+
+ // '\x', should be parsed as 'x'
+ ["attachment; filename=" + DQUOTE + (BS + "x") + DQUOTE, "attachment", "x"],
+
+ // test empty param (quoted-string)
+ ["attachment; filename=" + DQUOTE + DQUOTE, "attachment", ""],
+
+ // test empty param
+ ["attachment; filename=", "attachment", ""],
+
+ // Bug 601933: RFC 2047 does not apply to parameters (at least in HTTP)
+ [
+ "attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=",
+ "attachment",
+ "foo-\u00e4.html",
+ /* "attachment", "=?ISO-8859-1?Q?foo-=E4.html?=" */
+ ],
+
+ [
+ 'attachment; filename="=?ISO-8859-1?Q?foo-=E4.html?="',
+ "attachment",
+ "foo-\u00e4.html",
+ /* "attachment", "=?ISO-8859-1?Q?foo-=E4.html?=" */
+ ],
+
+ // format sent by GMail as of 2012-07-23 (5987 overrides 2047)
+ [
+ "attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"; filename*=UTF-8''5987",
+ "attachment",
+ "5987",
+ ],
+
+ // Bug 651185: double quotes around 2231/5987 encoded param
+ // Change reverted to backwards compat issues with various web services,
+ // such as OWA (Bug 703015), plus similar problems in Thunderbird. If this
+ // is tried again in the future, email probably needs to be special-cased.
+
+ // sanity check
+ ["attachment; filename*=utf-8''%41", "attachment", "A"],
+
+ // the actual bug
+ [
+ "attachment; filename*=" + DQUOTE + "utf-8''%41" + DQUOTE,
+ "attachment",
+ "A",
+ ],
+ // previously with the fix for 651185:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // Bug 670333: Content-Disposition parser does not require presence of "="
+ // in params
+
+ // sanity check
+ ["attachment; filename*=UTF-8''foo-%41.html", "attachment", "foo-A.html"],
+
+ // the actual bug
+ [
+ "attachment; filename *=UTF-8''foo-%41.html",
+ "attachment",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+
+ // the actual bug, without 2231/5987 encoding
+ ["attachment; filename X", "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // sanity check with WS on both sides
+ ["attachment; filename = foo-A.html", "attachment", "foo-A.html"],
+
+ // Bug 685192: in RFC2231/5987 encoding, a missing charset field should be
+ // treated as error
+
+ // the actual bug
+ ["attachment; filename*=''foo", "attachment", "foo"],
+ // previously with the fix for 692574:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // sanity check
+ ["attachment; filename*=a''foo", "attachment", "foo"],
+
+ // Bug 692574: RFC2231/5987 decoding should not tolerate missing single
+ // quotes
+
+ // one missing
+ ["attachment; filename*=UTF-8'foo-%41.html", "attachment", "foo-A.html"],
+ // previously with the fix for 692574:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // both missing
+ ["attachment; filename*=foo-%41.html", "attachment", "foo-A.html"],
+ // previously with the fix for 692574:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // make sure fallback works
+ [
+ "attachment; filename*=UTF-8'foo-%41.html; filename=bar.html",
+ "attachment",
+ "foo-A.html",
+ ],
+ // previously with the fix for 692574:
+ // "attachment", "bar.html"],
+
+ // Bug 693806: RFC2231/5987 encoding: charset information should be treated
+ // as authoritative
+
+ // UTF-8 labeled ISO-8859-1
+ ["attachment; filename*=ISO-8859-1''%c3%a4", "attachment", "\u00c3\u00a4"],
+
+ // UTF-8 labeled ISO-8859-1, but with octets not allowed in ISO-8859-1
+ // accepts x82, understands it as Win1252, maps it to Unicode \u20a1
+ [
+ "attachment; filename*=ISO-8859-1''%e2%82%ac",
+ "attachment",
+ "\u00e2\u201a\u00ac",
+ ],
+
+ // defective UTF-8
+ ["attachment; filename*=UTF-8''A%e4B", "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // defective UTF-8, with fallback
+ [
+ "attachment; filename*=UTF-8''A%e4B; filename=fallback",
+ "attachment",
+ "fallback",
+ ],
+
+ // defective UTF-8 (continuations), with fallback
+ [
+ "attachment; filename*0*=UTF-8''A%e4B; filename=fallback",
+ "attachment",
+ "fallback",
+ ],
+
+ // check that charsets aren't mixed up
+ [
+ "attachment; filename*0*=ISO-8859-15''euro-sign%3d%a4; filename*=ISO-8859-1''currency-sign%3d%a4",
+ "attachment",
+ "currency-sign=\u00a4",
+ ],
+
+ // same as above, except reversed
+ [
+ "attachment; filename*=ISO-8859-1''currency-sign%3d%a4; filename*0*=ISO-8859-15''euro-sign%3d%a4",
+ "attachment",
+ "currency-sign=\u00a4",
+ ],
+
+ // Bug 704989: add workaround for broken Outlook Web App (OWA)
+ // attachment handling
+
+ ['attachment; filename*="a%20b"', "attachment", "a b"],
+
+ // Bug 717121: crash nsMIMEHeaderParamImpl::DoParameterInternal
+
+ ['attachment; filename="', "attachment", ""],
+
+ // We used to read past string if last param w/o = and ;
+ // Note: was only detected on windows PGO builds
+ ["attachment; filename=foo; trouble", "attachment", "foo"],
+
+ // Same, followed by space, hits another case
+ ["attachment; filename=foo; trouble ", "attachment", "foo"],
+
+ ["attachment", "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // Bug 730574: quoted-string in RFC2231-continuations not handled
+
+ [
+ 'attachment; filename=basic; filename*0="foo"; filename*1="\\b\\a\\r.html"',
+ "attachment",
+ "foobar.html",
+ /* "attachment", "basic" */
+ ],
+
+ // unmatched escape char
+ [
+ 'attachment; filename=basic; filename*0="foo"; filename*1="\\b\\a\\',
+ "attachment",
+ "fooba\\",
+ /* "attachment", "basic" */
+ ],
+
+ // Bug 732369: Content-Disposition parser does not require presence of ";" between params
+ // optimally, this would not even return the disposition type "attachment"
+
+ [
+ "attachment; extension=bla filename=foo",
+ "attachment",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+
+ // Bug 1440677 - spaces inside filenames ought to be quoted, but too many
+ // servers do the wrong thing and most browsers accept this, so we were
+ // forced to do the same for compat.
+ ["attachment; filename=foo extension=bla", "attachment", "foo extension=bla"],
+
+ ["attachment filename=foo", "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // Bug 777687: handling of broken %escapes
+
+ ["attachment; filename*=UTF-8''f%oo; filename=bar", "attachment", "bar"],
+
+ ["attachment; filename*=UTF-8''foo%; filename=bar", "attachment", "bar"],
+
+ // Bug 783502 - xpcshell test netwerk/test/unit/test_MIME_params.js fails on AddressSanitizer
+ ['attachment; filename="\\b\\a\\', "attachment", "ba\\"],
+
+ // Bug 1412213 - do continue to parse, behind an empty parameter
+ ["attachment; ; filename=foo", "attachment", "foo"],
+
+ // Bug 1412213 - do continue to parse, behind a parameter w/o =
+ ["attachment; badparameter; filename=foo", "attachment", "foo"],
+
+ // Bug 1440677 - spaces inside filenames ought to be quoted, but too many
+ // servers do the wrong thing and most browsers accept this, so we were
+ // forced to do the same for compat.
+ ["attachment; filename=foo bar.html", "attachment", "foo bar.html"],
+ // Note: we keep the tab character, but later validation will replace with a space,
+ // as file systems do not like tab characters.
+ ["attachment; filename=foo\tbar.html", "attachment", "foo\tbar.html"],
+ // Newlines get stripped completely (in practice, http header parsing may
+ // munge these into spaces before they get to us, but we should check we deal
+ // with them either way):
+ ["attachment; filename=foo\nbar.html", "attachment", "foobar.html"],
+ ["attachment; filename=foo\r\nbar.html", "attachment", "foobar.html"],
+ ["attachment; filename=foo\rbar.html", "attachment", "foobar.html"],
+
+ // Trailing rubbish shouldn't matter:
+ ["attachment; filename=foo bar; garbage", "attachment", "foo bar"],
+ ["attachment; filename=foo bar; extension=blah", "attachment", "foo bar"],
+
+ // Check that whitespace processing can't crash.
+ ["attachment; filename = ", "attachment", ""],
+
+ // Bug 1784348
+ [
+ "attachment; filename=foo.exe\0.pdf",
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ "attachment; filename=\0\0foo\0",
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ ["attachment; filename=foo\0\0\0", "attachment", "foo"],
+ ["attachment; filename=\0\0\0", "attachment", ""],
+];
+
+var rfc5987paramtests = [
+ [
+ // basic test
+ "UTF-8'language'value",
+ "value",
+ "language",
+ Cr.NS_OK,
+ ],
+ [
+ // percent decoding
+ "UTF-8''1%202",
+ "1 2",
+ "",
+ Cr.NS_OK,
+ ],
+ [
+ // UTF-8
+ "UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
+ "\u00a3 and \u20ac rates",
+ "",
+ Cr.NS_OK,
+ ],
+ [
+ // missing charset
+ "''abc",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // ISO-8859-1: unsupported
+ "ISO-8859-1''%A3%20rates",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // unknown charset
+ "foo''abc",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // missing component
+ "abc",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // missing component
+ "'abc",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // illegal chars
+ "UTF-8''a b",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // broken % escapes
+ "UTF-8''a%zz",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // broken % escapes
+ "UTF-8''a%b",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // broken % escapes
+ "UTF-8''a%",
+ "",
+ "",
+ Cr.NS_ERROR_INVALID_ARG,
+ ],
+ [
+ // broken UTF-8
+ "UTF-8''%A3%20rates",
+ "",
+ "",
+ 0x8050000e /* NS_ERROR_UDEC_ILLEGALINPUT */,
+ ],
+];
+
+function do_tests(whichRFC) {
+ var mhp = Cc["@mozilla.org/network/mime-hdrparam;1"].getService(
+ Ci.nsIMIMEHeaderParam
+ );
+
+ var unused = { value: null };
+
+ for (var i = 0; i < tests.length; ++i) {
+ dump("Testing #" + i + ": " + tests[i] + "\n");
+
+ // check disposition type
+ var expectedDt =
+ tests[i].length == 3 || whichRFC == 0 ? tests[i][1] : tests[i][3];
+
+ try {
+ let result;
+
+ if (whichRFC == 0) {
+ result = mhp.getParameter(tests[i][0], "", "UTF-8", true, unused);
+ } else {
+ result = mhp.getParameterHTTP(tests[i][0], "", "UTF-8", true, unused);
+ }
+
+ Assert.equal(result, expectedDt);
+ } catch (e) {
+ // Tests can also succeed by expecting to fail with given error code
+ if (e.result) {
+ // Allow following tests to run by catching exception from do_check_eq()
+ try {
+ Assert.equal(e.result, expectedDt);
+ } catch (e1) {}
+ }
+ continue;
+ }
+
+ // check filename parameter
+ var expectedFn =
+ tests[i].length == 3 || whichRFC == 0 ? tests[i][2] : tests[i][4];
+
+ try {
+ let result;
+
+ if (whichRFC == 0) {
+ result = mhp.getParameter(
+ tests[i][0],
+ "filename",
+ "UTF-8",
+ true,
+ unused
+ );
+ } else {
+ result = mhp.getParameterHTTP(
+ tests[i][0],
+ "filename",
+ "UTF-8",
+ true,
+ unused
+ );
+ }
+
+ Assert.equal(result, expectedFn);
+ } catch (e) {
+ // Tests can also succeed by expecting to fail with given error code
+ if (e.result) {
+ // Allow following tests to run by catching exception from do_check_eq()
+ try {
+ Assert.equal(e.result, expectedFn);
+ } catch (e1) {}
+ }
+ continue;
+ }
+ }
+}
+
+function test_decode5987Param() {
+ var mhp = Cc["@mozilla.org/network/mime-hdrparam;1"].getService(
+ Ci.nsIMIMEHeaderParam
+ );
+
+ for (var i = 0; i < rfc5987paramtests.length; ++i) {
+ dump("Testing #" + i + ": " + rfc5987paramtests[i] + "\n");
+
+ var lang = {};
+ try {
+ var decoded = mhp.decodeRFC5987Param(rfc5987paramtests[i][0], lang);
+ if (rfc5987paramtests[i][3] == Cr.NS_OK) {
+ Assert.equal(rfc5987paramtests[i][1], decoded);
+ Assert.equal(rfc5987paramtests[i][2], lang.value);
+ } else {
+ Assert.equal(rfc5987paramtests[i][3], "instead got: " + decoded);
+ }
+ } catch (e) {
+ Assert.equal(rfc5987paramtests[i][3], e.result);
+ }
+ }
+}
+
+function run_test() {
+ // Test RFC 2231 (complete header field values)
+ do_tests(0);
+
+ // Test RFC 5987 (complete header field values)
+ do_tests(1);
+
+ // tests for RFC5987 parameter parsing
+ test_decode5987Param();
+}
diff --git a/netwerk/test/unit/test_NetUtil.js b/netwerk/test/unit/test_NetUtil.js
new file mode 100644
index 0000000000..624e35f30e
--- /dev/null
+++ b/netwerk/test/unit/test_NetUtil.js
@@ -0,0 +1,804 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file tests the methods on NetUtil.jsm.
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// We need the profile directory so the test harness will clean up our test
+// files.
+do_get_profile();
+
+const OUTPUT_STREAM_CONTRACT_ID = "@mozilla.org/network/file-output-stream;1";
+const SAFE_OUTPUT_STREAM_CONTRACT_ID =
+ "@mozilla.org/network/safe-file-output-stream;1";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helper Methods
+
+/**
+ * Reads the contents of a file and returns it as a string.
+ *
+ * @param aFile
+ * The file to return from.
+ * @return the contents of the file in the form of a string.
+ */
+function getFileContents(aFile) {
+ "use strict";
+
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(aFile, -1, 0, 0);
+
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+ Ci.nsIConverterInputStream
+ );
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let string = {};
+ cstream.readString(-1, string);
+ cstream.close();
+ return string.value;
+}
+
+/**
+ * Tests asynchronously writing a file using NetUtil.asyncCopy.
+ *
+ * @param aContractId
+ * The contract ID to use for the output stream
+ * @param aDeferOpen
+ * Whether to use DEFER_OPEN in the output stream.
+ */
+function async_write_file(aContractId, aDeferOpen) {
+ do_test_pending();
+
+ // First, we need an output file to write to.
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-async-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ // Then, we need an output stream to our output file.
+ let ostream = Cc[aContractId].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(
+ file,
+ -1,
+ -1,
+ aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0
+ );
+
+ // Finally, we need an input stream to take data from.
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ NetUtil.asyncCopy(istream, ostream, function (aResult) {
+ // Make sure the copy was successful!
+ Assert.ok(Components.isSuccessCode(aResult));
+
+ // Check the file contents.
+ Assert.equal(TEST_DATA, getFileContents(file));
+
+ // Finish the test.
+ do_test_finished();
+ run_next_test();
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+// Test NetUtil.asyncCopy for all possible buffering scenarios
+function test_async_copy() {
+ // Create a data sample
+ function make_sample(text) {
+ let data = [];
+ for (let i = 0; i <= 100; ++i) {
+ data.push(text);
+ }
+ return data.join();
+ }
+
+ // Create an input buffer holding some data
+ function make_input(isBuffered, data) {
+ if (isBuffered) {
+ // String input streams are buffered
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ istream.setData(data, data.length);
+ return istream;
+ }
+
+ // File input streams are not buffered, so let's create a file
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-asyncFetch-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let ostream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, 0);
+ ostream.write(data, data.length);
+ ostream.close();
+
+ let istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ istream.init(file, -1, 0, 0);
+
+ return istream;
+ }
+
+ // Create an output buffer holding some data
+ function make_output(isBuffered) {
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-asyncFetch-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let ostream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, 0);
+
+ if (!isBuffered) {
+ return { file, sink: ostream };
+ }
+
+ let bstream = Cc[
+ "@mozilla.org/network/buffered-output-stream;1"
+ ].createInstance(Ci.nsIBufferedOutputStream);
+ bstream.init(ostream, 256);
+ return { file, sink: bstream };
+ }
+ (async function () {
+ do_test_pending();
+ for (let bufferedInput of [true, false]) {
+ for (let bufferedOutput of [true, false]) {
+ let text =
+ "test_async_copy with " +
+ (bufferedInput ? "buffered input" : "unbuffered input") +
+ ", " +
+ (bufferedOutput ? "buffered output" : "unbuffered output");
+ info(text);
+ let TEST_DATA = "[" + make_sample(text) + "]";
+ let source = make_input(bufferedInput, TEST_DATA);
+ let { file, sink } = make_output(bufferedOutput);
+ let result = await new Promise(resolve => {
+ NetUtil.asyncCopy(source, sink, resolve);
+ });
+
+ // Make sure the copy was successful!
+ if (!Components.isSuccessCode(result)) {
+ do_throw(new Components.Exception("asyncCopy error", result));
+ }
+
+ // Check the file contents.
+ Assert.equal(TEST_DATA, getFileContents(file));
+ }
+ }
+
+ do_test_finished();
+ run_next_test();
+ })();
+}
+
+function test_async_write_file() {
+ async_write_file(OUTPUT_STREAM_CONTRACT_ID);
+}
+
+function test_async_write_file_deferred() {
+ async_write_file(OUTPUT_STREAM_CONTRACT_ID, true);
+}
+
+function test_async_write_file_safe() {
+ async_write_file(SAFE_OUTPUT_STREAM_CONTRACT_ID);
+}
+
+function test_async_write_file_safe_deferred() {
+ async_write_file(SAFE_OUTPUT_STREAM_CONTRACT_ID, true);
+}
+
+function test_newURI_no_spec_throws() {
+ try {
+ NetUtil.newURI();
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_newURI() {
+ // Check that we get the same URI back from the IO service and the utility
+ // method.
+ const TEST_URI = "http://mozilla.org";
+ let iosURI = Services.io.newURI(TEST_URI);
+ let NetUtilURI = NetUtil.newURI(TEST_URI);
+ Assert.ok(iosURI.equals(NetUtilURI));
+
+ run_next_test();
+}
+
+function test_newURI_takes_nsIFile() {
+ // Create a test file that we can pass into NetUtil.newURI
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-test-file.tmp");
+
+ // Check that we get the same URI back from the IO service and the utility
+ // method.
+ let iosURI = Services.io.newFileURI(file);
+ let NetUtilURI = NetUtil.newURI(file);
+ Assert.ok(iosURI.equals(NetUtilURI));
+
+ run_next_test();
+}
+
+function test_asyncFetch_no_channel() {
+ try {
+ NetUtil.asyncFetch(null, function () {});
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_asyncFetch_no_callback() {
+ try {
+ NetUtil.asyncFetch({});
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_asyncFetch_with_nsIChannel() {
+ const TEST_DATA = "this is a test string";
+
+ // Start the http server, and register our handler.
+ let server = new HttpServer();
+ server.registerPathHandler("/test", function (aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain", false);
+ aResponse.write(TEST_DATA);
+ });
+ server.start(-1);
+
+ // Create our channel.
+ let channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/test",
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Open our channel asynchronously.
+ NetUtil.asyncFetch(channel, function (aInputStream, aResult) {
+ // Check that we had success.
+ Assert.ok(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ Assert.equal(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ Assert.equal(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ });
+}
+
+function test_asyncFetch_with_nsIURI() {
+ const TEST_DATA = "this is a test string";
+
+ // Start the http server, and register our handler.
+ let server = new HttpServer();
+ server.registerPathHandler("/test", function (aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain", false);
+ aResponse.write(TEST_DATA);
+ });
+ server.start(-1);
+
+ // Create our URI.
+ let uri = NetUtil.newURI(
+ "http://localhost:" + server.identity.primaryPort + "/test"
+ );
+
+ // Open our URI asynchronously.
+ NetUtil.asyncFetch(
+ {
+ uri,
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aResult) {
+ // Check that we had success.
+ Assert.ok(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ Assert.equal(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ Assert.equal(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+}
+
+function test_asyncFetch_with_string() {
+ const TEST_DATA = "this is a test string";
+
+ // Start the http server, and register our handler.
+ let server = new HttpServer();
+ server.registerPathHandler("/test", function (aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain", false);
+ aResponse.write(TEST_DATA);
+ });
+ server.start(-1);
+
+ // Open our location asynchronously.
+ NetUtil.asyncFetch(
+ {
+ uri: "http://localhost:" + server.identity.primaryPort + "/test",
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aResult) {
+ // Check that we had success.
+ Assert.ok(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ Assert.equal(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ Assert.equal(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+}
+
+function test_asyncFetch_with_nsIFile() {
+ const TEST_DATA = "this is a test string";
+
+ // First we need a file to read from.
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-asyncFetch-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ // Write the test data to the file.
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ ostream.init(file, -1, -1, 0);
+ ostream.write(TEST_DATA, TEST_DATA.length);
+
+ // Sanity check to make sure the data was written.
+ Assert.equal(TEST_DATA, getFileContents(file));
+
+ // Open our file asynchronously.
+ // Note that this causes main-tread I/O and should be avoided in production.
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(file),
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aResult) {
+ // Check that we had success.
+ Assert.ok(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ Assert.equal(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ Assert.equal(TEST_DATA, result);
+
+ run_next_test();
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+}
+
+function test_asyncFetch_with_nsIInputString() {
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ // Read the input stream asynchronously.
+ NetUtil.asyncFetch(
+ istream,
+ function (aInputStream, aResult) {
+ // Check that we had success.
+ Assert.ok(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ Assert.equal(aInputStream.available(), TEST_DATA.length);
+ Assert.equal(
+ NetUtil.readInputStreamToString(aInputStream, TEST_DATA.length),
+ TEST_DATA
+ );
+
+ run_next_test();
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+}
+
+function test_asyncFetch_does_not_block() {
+ // Create our channel that has no data.
+ let channel = NetUtil.newChannel({
+ uri: "data:text/plain,",
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Open our channel asynchronously.
+ NetUtil.asyncFetch(channel, function (aInputStream, aResult) {
+ // Check that we had success.
+ Assert.ok(Components.isSuccessCode(aResult));
+
+ // Check that reading a byte throws that the stream was closed (as opposed
+ // saying it would block).
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ is.init(aInputStream);
+ try {
+ is.read(1);
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_BASE_STREAM_CLOSED);
+ }
+
+ run_next_test();
+ });
+}
+
+function test_newChannel_no_specifier() {
+ try {
+ NetUtil.newChannel();
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_newChannel_with_string() {
+ const TEST_SPEC = "http://mozilla.org";
+
+ // Check that we get the same URI back from channel the IO service creates and
+ // the channel the utility method creates.
+ let iosChannel = Services.io.newChannel(
+ TEST_SPEC,
+ null,
+ null,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ let NetUtilChannel = NetUtil.newChannel({
+ uri: TEST_SPEC,
+ loadUsingSystemPrincipal: true,
+ });
+ Assert.ok(iosChannel.URI.equals(NetUtilChannel.URI));
+
+ run_next_test();
+}
+
+function test_newChannel_with_nsIURI() {
+ const TEST_SPEC = "http://mozilla.org";
+
+ // Check that we get the same URI back from channel the IO service creates and
+ // the channel the utility method creates.
+ let uri = NetUtil.newURI(TEST_SPEC);
+ let iosChannel = Services.io.newChannelFromURI(
+ uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ let NetUtilChannel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+ Assert.ok(iosChannel.URI.equals(NetUtilChannel.URI));
+
+ run_next_test();
+}
+
+function test_newChannel_with_options() {
+ let uri = "data:text/plain,";
+
+ let iosChannel = Services.io.newChannelFromURI(
+ NetUtil.newURI(uri),
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+
+ function checkEqualToIOSChannel(channel) {
+ Assert.ok(iosChannel.URI.equals(channel.URI));
+ }
+
+ checkEqualToIOSChannel(
+ NetUtil.newChannel({
+ uri,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ })
+ );
+
+ checkEqualToIOSChannel(
+ NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ })
+ );
+
+ run_next_test();
+}
+
+function test_newChannel_with_wrong_options() {
+ let uri = "data:text/plain,";
+ let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+ Assert.throws(() => {
+ NetUtil.newChannel({ uri, loadUsingSystemPrincipal: true }, null, null);
+ }, /requires a single object argument/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({ loadUsingSystemPrincipal: true });
+ }, /requires the 'uri' property/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({ uri, loadingNode: true });
+ }, /requires the 'securityFlags'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({ uri, securityFlags: 0 });
+ }, /requires at least one of the 'loadingNode'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({
+ uri,
+ loadingPrincipal: systemPrincipal,
+ securityFlags: 0,
+ });
+ }, /requires the 'contentPolicyType'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: systemPrincipal,
+ });
+ }, /to be 'true' or 'undefined'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({
+ uri,
+ loadingPrincipal: systemPrincipal,
+ loadUsingSystemPrincipal: true,
+ });
+ }, /does not accept 'loadUsingSystemPrincipal'/);
+
+ run_next_test();
+}
+
+function test_readInputStreamToString() {
+ const TEST_DATA = "this is a test string\0 with an embedded null";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsISupportsCString
+ );
+ istream.data = TEST_DATA;
+
+ Assert.equal(
+ NetUtil.readInputStreamToString(istream, TEST_DATA.length),
+ TEST_DATA
+ );
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_no_input_stream() {
+ try {
+ NetUtil.readInputStreamToString("hi", 2);
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_no_bytes_arg() {
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ try {
+ NetUtil.readInputStreamToString(istream);
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_blocking_stream() {
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, 0, null);
+
+ try {
+ NetUtil.readInputStreamToString(pipe.inputStream, 10);
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_BASE_STREAM_WOULD_BLOCK);
+ }
+ run_next_test();
+}
+
+function test_readInputStreamToString_too_many_bytes() {
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ try {
+ NetUtil.readInputStreamToString(istream, TEST_DATA.length + 10);
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_FAILURE);
+ }
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_with_charset() {
+ const TEST_DATA = "\uff10\uff11\uff12\uff13";
+ const TEST_DATA_UTF8 = "\xef\xbc\x90\xef\xbc\x91\xef\xbc\x92\xef\xbc\x93";
+ const TEST_DATA_SJIS = "\x82\x4f\x82\x50\x82\x51\x82\x52";
+
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+
+ istream.setData(TEST_DATA_UTF8, TEST_DATA_UTF8.length);
+ Assert.equal(
+ NetUtil.readInputStreamToString(istream, TEST_DATA_UTF8.length, {
+ charset: "UTF-8",
+ }),
+ TEST_DATA
+ );
+
+ istream.setData(TEST_DATA_SJIS, TEST_DATA_SJIS.length);
+ Assert.equal(
+ NetUtil.readInputStreamToString(istream, TEST_DATA_SJIS.length, {
+ charset: "Shift_JIS",
+ }),
+ TEST_DATA
+ );
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_invalid_sequence() {
+ const TEST_DATA = "\ufffd\ufffd\ufffd\ufffd";
+ const TEST_DATA_UTF8 = "\xaa\xaa\xaa\xaa";
+
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+
+ istream.setData(TEST_DATA_UTF8, TEST_DATA_UTF8.length);
+ try {
+ NetUtil.readInputStreamToString(istream, TEST_DATA_UTF8.length, {
+ charset: "UTF-8",
+ });
+ do_throw("should throw!");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_ILLEGAL_INPUT);
+ }
+
+ istream.setData(TEST_DATA_UTF8, TEST_DATA_UTF8.length);
+ Assert.equal(
+ NetUtil.readInputStreamToString(istream, TEST_DATA_UTF8.length, {
+ charset: "UTF-8",
+ replacement: Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER,
+ }),
+ TEST_DATA
+ );
+
+ run_next_test();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Runner
+
+[
+ test_async_copy,
+ test_async_write_file,
+ test_async_write_file_deferred,
+ test_async_write_file_safe,
+ test_async_write_file_safe_deferred,
+ test_newURI_no_spec_throws,
+ test_newURI,
+ test_newURI_takes_nsIFile,
+ test_asyncFetch_no_channel,
+ test_asyncFetch_no_callback,
+ test_asyncFetch_with_nsIChannel,
+ test_asyncFetch_with_nsIURI,
+ test_asyncFetch_with_string,
+ test_asyncFetch_with_nsIFile,
+ test_asyncFetch_with_nsIInputString,
+ test_asyncFetch_does_not_block,
+ test_newChannel_no_specifier,
+ test_newChannel_with_string,
+ test_newChannel_with_nsIURI,
+ test_newChannel_with_options,
+ test_newChannel_with_wrong_options,
+ test_readInputStreamToString,
+ test_readInputStreamToString_no_input_stream,
+ test_readInputStreamToString_no_bytes_arg,
+ test_readInputStreamToString_blocking_stream,
+ test_readInputStreamToString_too_many_bytes,
+ test_readInputStreamToString_with_charset,
+ test_readInputStreamToString_invalid_sequence,
+].forEach(f => add_test(f));
diff --git a/netwerk/test/unit/test_SuperfluousAuth.js b/netwerk/test/unit/test_SuperfluousAuth.js
new file mode 100644
index 0000000000..2766b00c4c
--- /dev/null
+++ b/netwerk/test/unit/test_SuperfluousAuth.js
@@ -0,0 +1,101 @@
+/*
+
+Create two http requests with the same URL in which has a user name. We allow
+first http request to be loaded and saved in the cache, so the second request
+will be served from the cache. However, we disallow loading by returning 1
+in the prompt service. In the end, the second request will be failed.
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://foo@localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+const gMockPromptService = {
+ firstTimeCalled: false,
+ confirmExBC() {
+ if (!this.firstTimeCalled) {
+ this.firstTimeCalled = true;
+ return 0;
+ }
+
+ return 1;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+};
+
+var gMockPromptServiceCID = MockRegistrar.register(
+ "@mozilla.org/prompter;1",
+ gMockPromptService
+);
+
+registerCleanupFunction(() => {
+ MockRegistrar.unregister(gMockPromptServiceCID);
+});
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+const responseBody = "body";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var chan1 = makeChan(URL + "/content");
+ chan1.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ var chan2 = makeChan(URL + "/content");
+ chan2.asyncOpen(
+ new ChannelListener(secondTimeThrough, null, CL_EXPECT_FAILURE)
+ );
+ });
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ Assert.ok(gMockPromptService.firstTimeCalled, "Prompt service invoked");
+}
+
+function secondTimeThrough(request, buffer) {
+ Assert.equal(request.status, Cr.NS_ERROR_SUPERFLUOS_AUTH);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_URIs.js b/netwerk/test/unit/test_URIs.js
new file mode 100644
index 0000000000..548ecd5535
--- /dev/null
+++ b/netwerk/test/unit/test_URIs.js
@@ -0,0 +1,996 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+"use strict";
+
+// Run by: cd objdir; make -C netwerk/test/ xpcshell-tests
+// or: cd objdir; make SOLO_FILE="test_URIs.js" -C netwerk/test/ check-one
+
+// See also test_URIs2.js.
+
+// Relevant RFCs: 1738, 1808, 2396, 3986 (newer than the code)
+// http://greenbytes.de/tech/webdav/rfc3986.html#rfc.section.5.4
+// http://greenbytes.de/tech/tc/uris/
+
+Services.prefs.setBoolPref("network.url.useDefaultURI", true);
+
+// TEST DATA
+// ---------
+var gTests = [
+ {
+ spec: "about:blank",
+ scheme: "about",
+ prePath: "about:",
+ pathQueryRef: "blank",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: true,
+ immutable: true,
+ },
+ {
+ spec: "about:foobar",
+ scheme: "about",
+ prePath: "about:",
+ pathQueryRef: "foobar",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ immutable: true,
+ },
+ {
+ spec: "chrome://foobar/somedir/somefile.xml",
+ scheme: "chrome",
+ prePath: "chrome://foobar",
+ pathQueryRef: "/somedir/somefile.xml",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ immutable: true,
+ },
+ {
+ spec: "data:text/html;charset=utf-8,<html></html>",
+ scheme: "data",
+ prePath: "data:",
+ pathQueryRef: "text/html;charset=utf-8,<html></html>",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "data:text/html;charset=utf-8,<html>\r\n\t</html>",
+ scheme: "data",
+ prePath: "data:",
+ pathQueryRef: "text/html;charset=utf-8,<html></html>",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "data:text/plain,hello%20world",
+ scheme: "data",
+ prePath: "data:",
+ pathQueryRef: "text/plain,hello%20world",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "data:text/plain,hello world",
+ scheme: "data",
+ prePath: "data:",
+ pathQueryRef: "text/plain,hello world",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "file:///dir/afile",
+ scheme: "data",
+ prePath: "data:",
+ pathQueryRef: "text/plain,2",
+ ref: "",
+ relativeURI: "data:te\nxt/plain,2",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "file://",
+ scheme: "file",
+ prePath: "file://",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "file:///",
+ scheme: "file",
+ prePath: "file://",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "file:///myFile.html",
+ scheme: "file",
+ prePath: "file://",
+ pathQueryRef: "/myFile.html",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "file:///dir/afile",
+ scheme: "file",
+ prePath: "file://",
+ pathQueryRef: "/dir/data/text/plain,2",
+ ref: "",
+ relativeURI: "data/text/plain,2",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "file:///dir/dir2/",
+ scheme: "file",
+ prePath: "file://",
+ pathQueryRef: "/dir/dir2/data/text/plain,2",
+ ref: "",
+ relativeURI: "data/text/plain,2",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "ftp://ftp.mozilla.org/pub/mozilla.org/README",
+ scheme: "ftp",
+ prePath: "ftp://ftp.mozilla.org",
+ pathQueryRef: "/pub/mozilla.org/README",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "ftp://foo:bar@ftp.mozilla.org:100/pub/mozilla.org/README",
+ scheme: "ftp",
+ prePath: "ftp://foo:bar@ftp.mozilla.org:100",
+ port: 100,
+ username: "foo",
+ password: "bar",
+ pathQueryRef: "/pub/mozilla.org/README",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "ftp://foo:@ftp.mozilla.org:100/pub/mozilla.org/README",
+ scheme: "ftp",
+ prePath: "ftp://foo@ftp.mozilla.org:100",
+ port: 100,
+ username: "foo",
+ password: "",
+ pathQueryRef: "/pub/mozilla.org/README",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ //Bug 706249
+ {
+ spec: "gopher://mozilla.org/",
+ scheme: "gopher",
+ prePath: "gopher://mozilla.org",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://www.example.com/",
+ scheme: "http",
+ prePath: "http://www.example.com",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://www.exa\nmple.com/",
+ scheme: "http",
+ prePath: "http://www.example.com",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://10.32.4.239/",
+ scheme: "http",
+ prePath: "http://10.32.4.239",
+ host: "10.32.4.239",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://[::192.9.5.5]/ipng",
+ scheme: "http",
+ prePath: "http://[::c009:505]",
+ host: "::c009:505",
+ pathQueryRef: "/ipng",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:8888/index.html",
+ scheme: "http",
+ prePath: "http://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:8888",
+ host: "fedc:ba98:7654:3210:fedc:ba98:7654:3210",
+ port: 8888,
+ pathQueryRef: "/index.html",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://bar:foo@www.mozilla.org:8080/pub/mozilla.org/README.html",
+ scheme: "http",
+ prePath: "http://bar:foo@www.mozilla.org:8080",
+ port: 8080,
+ username: "bar",
+ password: "foo",
+ host: "www.mozilla.org",
+ pathQueryRef: "/pub/mozilla.org/README.html",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "jar:resource://!/",
+ scheme: "jar",
+ prePath: "jar:",
+ pathQueryRef: "resource:///!/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: true,
+ },
+ {
+ spec: "jar:resource://gre/chrome.toolkit.jar!/",
+ scheme: "jar",
+ prePath: "jar:",
+ pathQueryRef: "resource://gre/chrome.toolkit.jar!/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: true,
+ },
+ {
+ spec: "mailto:webmaster@mozilla.com",
+ scheme: "mailto",
+ prePath: "mailto:",
+ pathQueryRef: "webmaster@mozilla.com",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "javascript:new Date()",
+ scheme: "javascript",
+ prePath: "javascript:",
+ pathQueryRef: "new Date()",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "blob:123456",
+ scheme: "blob",
+ prePath: "blob:",
+ pathQueryRef: "123456",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ immutable: true,
+ },
+ {
+ spec: "place:sort=8&maxResults=10",
+ scheme: "place",
+ prePath: "place:",
+ pathQueryRef: "sort=8&maxResults=10",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "resource://gre/",
+ scheme: "resource",
+ prePath: "resource://gre",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "resource://gre/components/",
+ scheme: "resource",
+ prePath: "resource://gre",
+ pathQueryRef: "/components/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+
+ // Adding more? Consider adding to test_URIs2.js instead, so that neither
+ // test runs for *too* long, risking timeouts on slow platforms.
+];
+
+var gHashSuffixes = ["#", "#myRef", "#myRef?a=b", "#myRef#", "#myRef#x:yz"];
+
+// TEST HELPER FUNCTIONS
+// ---------------------
+function do_info(text, stack) {
+ if (!stack) {
+ stack = Components.stack.caller;
+ }
+
+ dump(
+ "\n" +
+ "TEST-INFO | " +
+ stack.filename +
+ " | [" +
+ stack.name +
+ " : " +
+ stack.lineNumber +
+ "] " +
+ text +
+ "\n"
+ );
+}
+
+// Checks that the URIs satisfy equals(), in both possible orderings.
+// Also checks URI.equalsExceptRef(), because equal URIs should also be equal
+// when we ignore the ref.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of ok.
+function do_check_uri_eq(aURI1, aURI2, aCheckTrueFunc = ok) {
+ do_info("(uri equals check: '" + aURI1.spec + "' == '" + aURI2.spec + "')");
+ aCheckTrueFunc(aURI1.equals(aURI2));
+ do_info("(uri equals check: '" + aURI2.spec + "' == '" + aURI1.spec + "')");
+ aCheckTrueFunc(aURI2.equals(aURI1));
+
+ // (Only take the extra step of testing 'equalsExceptRef' when we expect the
+ // URIs to really be equal. In 'todo' cases, the URIs may or may not be
+ // equal when refs are ignored - there's no way of knowing in general.)
+ if (aCheckTrueFunc == ok) {
+ do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc);
+ }
+}
+
+// Checks that the URIs satisfy equalsExceptRef(), in both possible orderings.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of ok.
+function do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc = ok) {
+ do_info(
+ "(uri equalsExceptRef check: '" + aURI1.spec + "' == '" + aURI2.spec + "')"
+ );
+ aCheckTrueFunc(aURI1.equalsExceptRef(aURI2));
+ do_info(
+ "(uri equalsExceptRef check: '" + aURI2.spec + "' == '" + aURI1.spec + "')"
+ );
+ aCheckTrueFunc(aURI2.equalsExceptRef(aURI1));
+}
+
+// Checks that the given property on aURI matches the corresponding property
+// in the test bundle (or matches some function of that corresponding property,
+// if aTestFunctor is passed in).
+function do_check_property(aTest, aURI, aPropertyName, aTestFunctor) {
+ if (aTest[aPropertyName]) {
+ var expectedVal = aTestFunctor
+ ? aTestFunctor(aTest[aPropertyName])
+ : aTest[aPropertyName];
+
+ do_info(
+ "testing " +
+ aPropertyName +
+ " of " +
+ (aTestFunctor ? "modified '" : "'") +
+ aTest.spec +
+ "' is '" +
+ expectedVal +
+ "'"
+ );
+ Assert.equal(aURI[aPropertyName], expectedVal);
+ }
+}
+
+// Test that a given URI parses correctly into its various components.
+function do_test_uri_basic(aTest) {
+ var URI;
+
+ do_info(
+ "Basic tests for " +
+ aTest.spec +
+ " relative URI: " +
+ (aTest.relativeURI === undefined ? "(none)" : aTest.relativeURI)
+ );
+
+ try {
+ URI = NetUtil.newURI(aTest.spec);
+ } catch (e) {
+ do_info("Caught error on parse of" + aTest.spec + " Error: " + e.result);
+ if (aTest.fail) {
+ Assert.equal(e.result, aTest.result);
+ return;
+ }
+ do_throw(e.result);
+ }
+
+ if (aTest.relativeURI) {
+ var relURI;
+
+ try {
+ relURI = Services.io.newURI(aTest.relativeURI, null, URI);
+ } catch (e) {
+ do_info(
+ "Caught error on Relative parse of " +
+ aTest.spec +
+ " + " +
+ aTest.relativeURI +
+ " Error: " +
+ e.result
+ );
+ if (aTest.relativeFail) {
+ Assert.equal(e.result, aTest.relativeFail);
+ return;
+ }
+ do_throw(e.result);
+ }
+ do_info(
+ "relURI.pathQueryRef = " +
+ relURI.pathQueryRef +
+ ", was " +
+ URI.pathQueryRef
+ );
+ URI = relURI;
+ do_info("URI.pathQueryRef now = " + URI.pathQueryRef);
+ }
+
+ // Sanity-check
+ do_info("testing " + aTest.spec + " equals a clone of itself");
+ do_check_uri_eq(URI, URI.mutate().finalize());
+ do_check_uri_eqExceptRef(URI, URI.mutate().setRef("").finalize());
+ do_info("testing " + aTest.spec + " instanceof nsIURL");
+ Assert.equal(URI instanceof Ci.nsIURL, aTest.nsIURL);
+ do_info("testing " + aTest.spec + " instanceof nsINestedURI");
+ Assert.equal(URI instanceof Ci.nsINestedURI, aTest.nsINestedURI);
+
+ do_info(
+ "testing that " +
+ aTest.spec +
+ " throws or returns false " +
+ "from equals(null)"
+ );
+ // XXXdholbert At some point it'd probably be worth making this behavior
+ // (throwing vs. returning false) consistent across URI implementations.
+ var threw = false;
+ var isEqualToNull;
+ try {
+ isEqualToNull = URI.equals(null);
+ } catch (e) {
+ threw = true;
+ }
+ Assert.ok(threw || !isEqualToNull);
+
+ // Check the various components
+ do_check_property(aTest, URI, "scheme");
+ do_check_property(aTest, URI, "prePath");
+ do_check_property(aTest, URI, "pathQueryRef");
+ do_check_property(aTest, URI, "query");
+ do_check_property(aTest, URI, "ref");
+ do_check_property(aTest, URI, "port");
+ do_check_property(aTest, URI, "username");
+ do_check_property(aTest, URI, "password");
+ do_check_property(aTest, URI, "host");
+ do_check_property(aTest, URI, "specIgnoringRef");
+
+ do_info("testing hasRef");
+ Assert.equal(URI.hasRef, !!aTest.ref, "URI.hasRef is correct");
+ do_info("testing hasUserPass");
+ Assert.equal(
+ URI.hasUserPass,
+ !!aTest.username || !!aTest.password,
+ "URI.hasUserPass is correct"
+ );
+}
+
+// Test that a given URI parses correctly when we add a given ref to the end
+function do_test_uri_with_hash_suffix(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ Assert.equal(aSuffix[0], "#");
+
+ var origURI = NetUtil.newURI(aTest.spec);
+ var testURI;
+
+ if (aTest.relativeURI) {
+ try {
+ origURI = Services.io.newURI(aTest.relativeURI, null, origURI);
+ } catch (e) {
+ do_info(
+ "Caught error on Relative parse of " +
+ aTest.spec +
+ " + " +
+ aTest.relativeURI +
+ " Error: " +
+ e.result
+ );
+ return;
+ }
+ try {
+ testURI = Services.io.newURI(aSuffix, null, origURI);
+ } catch (e) {
+ do_info(
+ "Caught error adding suffix to " +
+ aTest.spec +
+ " + " +
+ aTest.relativeURI +
+ ", suffix " +
+ aSuffix +
+ " Error: " +
+ e.result
+ );
+ return;
+ }
+ } else {
+ testURI = NetUtil.newURI(aTest.spec + aSuffix);
+ }
+
+ do_info(
+ "testing " +
+ aTest.spec +
+ " with '" +
+ aSuffix +
+ "' appended " +
+ "equals a clone of itself"
+ );
+ do_check_uri_eq(testURI, testURI.mutate().finalize());
+
+ do_info(
+ "testing " +
+ aTest.spec +
+ " doesn't equal self with '" +
+ aSuffix +
+ "' appended"
+ );
+
+ Assert.ok(!origURI.equals(testURI));
+
+ do_info(
+ "testing " +
+ aTest.spec +
+ " is equalExceptRef to self with '" +
+ aSuffix +
+ "' appended"
+ );
+ do_check_uri_eqExceptRef(origURI, testURI);
+
+ Assert.equal(testURI.hasRef, true);
+
+ if (!origURI.ref) {
+ // These tests fail if origURI has a ref
+ do_info(
+ "testing setRef('') on " +
+ testURI.spec +
+ " is equal to no-ref version but not equal to ref version"
+ );
+ var cloneNoRef = testURI.mutate().setRef("").finalize(); // we used to clone here.
+ do_info("cloneNoRef: " + cloneNoRef.spec + " hasRef: " + cloneNoRef.hasRef);
+ do_info("testURI: " + testURI.spec + " hasRef: " + testURI.hasRef);
+ do_check_uri_eq(cloneNoRef, origURI);
+ Assert.ok(!cloneNoRef.equals(testURI));
+
+ do_info(
+ "testing cloneWithNewRef on " +
+ testURI.spec +
+ " with an empty ref is equal to no-ref version but not equal to ref version"
+ );
+ var cloneNewRef = testURI.mutate().setRef("").finalize();
+ do_check_uri_eq(cloneNewRef, origURI);
+ do_check_uri_eq(cloneNewRef, cloneNoRef);
+ Assert.ok(!cloneNewRef.equals(testURI));
+
+ do_info(
+ "testing cloneWithNewRef on " +
+ origURI.spec +
+ " with the same new ref is equal to ref version and not equal to no-ref version"
+ );
+ cloneNewRef = origURI.mutate().setRef(aSuffix).finalize();
+ do_check_uri_eq(cloneNewRef, testURI);
+ Assert.ok(cloneNewRef.equals(testURI));
+ }
+
+ do_check_property(aTest, testURI, "scheme");
+ do_check_property(aTest, testURI, "prePath");
+ if (!origURI.ref) {
+ // These don't work if it's a ref already because '+' doesn't give the right result
+ do_check_property(aTest, testURI, "pathQueryRef", function (aStr) {
+ return aStr + aSuffix;
+ });
+ do_check_property(aTest, testURI, "ref", function (aStr) {
+ return aSuffix.substr(1);
+ });
+ }
+}
+
+// Tests various ways of setting & clearing a ref on a URI.
+function do_test_mutate_ref(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ Assert.equal(aSuffix[0], "#");
+
+ var refURIWithSuffix = NetUtil.newURI(aTest.spec + aSuffix);
+ var refURIWithoutSuffix = NetUtil.newURI(aTest.spec);
+
+ var testURI = NetUtil.newURI(aTest.spec);
+
+ // First: Try setting .ref to our suffix
+ do_info(
+ "testing that setting .ref on " +
+ aTest.spec +
+ " to '" +
+ aSuffix +
+ "' does what we expect"
+ );
+ testURI = testURI.mutate().setRef(aSuffix).finalize();
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+
+ // Now try setting .ref but leave off the initial hash (expect same result)
+ var suffixLackingHash = aSuffix.substr(1);
+ if (suffixLackingHash) {
+ // (skip this our suffix was *just* a #)
+ do_info(
+ "testing that setting .ref on " +
+ aTest.spec +
+ " to '" +
+ suffixLackingHash +
+ "' does what we expect"
+ );
+ testURI = testURI.mutate().setRef(suffixLackingHash).finalize();
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+ }
+
+ // Now, clear .ref (should get us back the original spec)
+ do_info(
+ "testing that clearing .ref on " + testURI.spec + " does what we expect"
+ );
+ testURI = testURI.mutate().setRef("").finalize();
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ if (!aTest.relativeURI) {
+ // TODO: These tests don't work as-is for relative URIs.
+
+ // Now try setting .spec directly (including suffix) and then clearing .ref
+ var specWithSuffix = aTest.spec + aSuffix;
+ do_info(
+ "testing that setting spec to " +
+ specWithSuffix +
+ " and then clearing ref does what we expect"
+ );
+
+ testURI = testURI.mutate().setSpec(specWithSuffix).setRef("").finalize();
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // XXX nsIJARURI throws an exception in SetPath(), so skip it for next part.
+ if (!(testURI instanceof Ci.nsIJARURI)) {
+ // Now try setting .pathQueryRef directly (including suffix) and then clearing .ref
+ // (same as above, but with now with .pathQueryRef instead of .spec)
+ testURI = NetUtil.newURI(aTest.spec);
+
+ var pathWithSuffix = aTest.pathQueryRef + aSuffix;
+ do_info(
+ "testing that setting path to " +
+ pathWithSuffix +
+ " and then clearing ref does what we expect"
+ );
+ testURI = testURI
+ .mutate()
+ .setPathQueryRef(pathWithSuffix)
+ .setRef("")
+ .finalize();
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // Also: make sure that clearing .pathQueryRef also clears .ref
+ testURI = testURI.mutate().setPathQueryRef(pathWithSuffix).finalize();
+ do_info(
+ "testing that clearing path from " +
+ pathWithSuffix +
+ " also clears .ref"
+ );
+ testURI = testURI.mutate().setPathQueryRef("").finalize();
+ Assert.equal(testURI.ref, "");
+ }
+ }
+}
+
+// Check that changing nested/about URIs works correctly.
+add_task(function check_nested_mutations() {
+ // nsNestedAboutURI
+ let uri1 = Services.io.newURI("about:blank#");
+ let uri2 = Services.io.newURI("about:blank");
+ let uri3 = uri1.mutate().setRef("").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setRef("#").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ uri1 = Services.io.newURI("about:blank?something");
+ uri2 = Services.io.newURI("about:blank");
+ uri3 = uri1.mutate().setQuery("").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setQuery("something").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ uri1 = Services.io.newURI("about:blank?query#ref");
+ uri2 = Services.io.newURI("about:blank");
+ uri3 = uri1.mutate().setPathQueryRef("blank").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setPathQueryRef("blank?query#ref").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ // nsSimpleNestedURI
+ uri1 = Services.io.newURI("view-source:http://example.com/path#");
+ uri2 = Services.io.newURI("view-source:http://example.com/path");
+ uri3 = uri1.mutate().setRef("").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setRef("#").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ uri1 = Services.io.newURI("view-source:http://example.com/path?something");
+ uri2 = Services.io.newURI("view-source:http://example.com/path");
+ uri3 = uri1.mutate().setQuery("").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setQuery("something").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ uri1 = Services.io.newURI("view-source:http://example.com/path?query#ref");
+ uri2 = Services.io.newURI("view-source:http://example.com/path");
+ uri3 = uri1.mutate().setPathQueryRef("path").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setPathQueryRef("path?query#ref").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ uri1 = Services.io.newURI("view-source:about:blank#");
+ uri2 = Services.io.newURI("view-source:about:blank");
+ uri3 = uri1.mutate().setRef("").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setRef("#").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ uri1 = Services.io.newURI("view-source:about:blank?something");
+ uri2 = Services.io.newURI("view-source:about:blank");
+ uri3 = uri1.mutate().setQuery("").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setQuery("something").finalize();
+ do_check_uri_eq(uri3, uri1);
+
+ uri1 = Services.io.newURI("view-source:about:blank?query#ref");
+ uri2 = Services.io.newURI("view-source:about:blank");
+ uri3 = uri1.mutate().setPathQueryRef("blank").finalize();
+ do_check_uri_eq(uri3, uri2);
+ uri3 = uri2.mutate().setPathQueryRef("blank?query#ref").finalize();
+ do_check_uri_eq(uri3, uri1);
+});
+
+add_task(function check_space_escaping() {
+ let uri = Services.io.newURI("data:text/plain,hello%20world#space hash");
+ Assert.equal(uri.spec, "data:text/plain,hello%20world#space%20hash");
+ uri = Services.io.newURI("data:text/plain,hello%20world#space%20hash");
+ Assert.equal(uri.spec, "data:text/plain,hello%20world#space%20hash");
+ uri = Services.io.newURI("data:text/plain,hello world#space%20hash");
+ Assert.equal(uri.spec, "data:text/plain,hello world#space%20hash");
+ uri = Services.io.newURI("data:text/plain,hello world#space hash");
+ Assert.equal(uri.spec, "data:text/plain,hello world#space%20hash");
+ uri = Services.io.newURI("http://example.com/test path#test path");
+ uri = Services.io.newURI("http://example.com/test%20path#test%20path");
+});
+
+add_task(function check_schemeIsNull() {
+ let uri = Services.io.newURI("data:text/plain,aaa");
+ Assert.ok(!uri.schemeIs(null));
+ uri = Services.io.newURI("http://example.com");
+ Assert.ok(!uri.schemeIs(null));
+ uri = Services.io.newURI("dummyscheme://example.com");
+ Assert.ok(!uri.schemeIs(null));
+ uri = Services.io.newURI("jar:resource://gre/chrome.toolkit.jar!/");
+ Assert.ok(!uri.schemeIs(null));
+ uri = Services.io.newURI("moz-icon://.unknown?size=32");
+ Assert.ok(!uri.schemeIs(null));
+});
+
+// Check that characters in the query of moz-extension aren't improperly unescaped (Bug 1547882)
+add_task(function check_mozextension_query() {
+ let uri = Services.io.newURI(
+ "moz-extension://a7d1572e-3beb-4d93-a920-c408fa09e8ea/_source/holding.html"
+ );
+ uri = uri
+ .mutate()
+ .setQuery("u=https%3A%2F%2Fnews.ycombinator.com%2F")
+ .finalize();
+ Assert.equal(uri.query, "u=https%3A%2F%2Fnews.ycombinator.com%2F");
+ uri = Services.io.newURI(
+ "moz-extension://a7d1572e-3beb-4d93-a920-c408fa09e8ea/_source/holding.html?u=https%3A%2F%2Fnews.ycombinator.com%2F"
+ );
+ Assert.equal(
+ uri.spec,
+ "moz-extension://a7d1572e-3beb-4d93-a920-c408fa09e8ea/_source/holding.html?u=https%3A%2F%2Fnews.ycombinator.com%2F"
+ );
+ Assert.equal(uri.query, "u=https%3A%2F%2Fnews.ycombinator.com%2F");
+});
+
+add_task(function check_resolve() {
+ let base = Services.io.newURI("http://example.com");
+ let uri = Services.io.newURI("tel::+371 27028456", "utf-8", base);
+ Assert.equal(uri.spec, "tel::+371 27028456");
+});
+
+add_task(function test_extra_protocols() {
+ // dweb://
+ let url = Services.io.newURI("dweb://example.com/test");
+ Assert.equal(url.host, "example.com");
+
+ // dat://
+ url = Services.io.newURI(
+ "dat://41f8a987cfeba80a037e51cc8357d513b62514de36f2f9b3d3eeec7a8fb3b5a5/"
+ );
+ Assert.equal(
+ url.host,
+ "41f8a987cfeba80a037e51cc8357d513b62514de36f2f9b3d3eeec7a8fb3b5a5"
+ );
+ url = Services.io.newURI("dat://example.com/test");
+ Assert.equal(url.host, "example.com");
+
+ // ipfs://
+ url = Services.io.newURI(
+ "ipfs://bafybeiccfclkdtucu6y4yc5cpr6y3yuinr67svmii46v5cfcrkp47ihehy/frontend/license.txt"
+ );
+ Assert.equal(url.scheme, "ipfs");
+ Assert.equal(
+ url.host,
+ "bafybeiccfclkdtucu6y4yc5cpr6y3yuinr67svmii46v5cfcrkp47ihehy"
+ );
+ Assert.equal(url.filePath, "/frontend/license.txt");
+
+ // ipns://
+ url = Services.io.newURI("ipns://peerdium.gozala.io/index.html");
+ Assert.equal(url.scheme, "ipns");
+ Assert.equal(url.host, "peerdium.gozala.io");
+ Assert.equal(url.filePath, "/index.html");
+
+ // ssb://
+ url = Services.io.newURI("ssb://scuttlebutt.nz/index.html");
+ Assert.equal(url.scheme, "ssb");
+ Assert.equal(url.host, "scuttlebutt.nz");
+ Assert.equal(url.filePath, "/index.html");
+
+ // wtp://
+ url = Services.io.newURI(
+ "wtp://951ead31d09e4049fc1f21f137e233dd0589fcbd/blog/vim-tips/"
+ );
+ Assert.equal(url.scheme, "wtp");
+ Assert.equal(url.host, "951ead31d09e4049fc1f21f137e233dd0589fcbd");
+ Assert.equal(url.filePath, "/blog/vim-tips/");
+});
+
+// TEST MAIN FUNCTION
+// ------------------
+add_task(function mainTest() {
+ // UTF-8 check - From bug 622981
+ // ASCII
+ let base = Services.io.newURI("http://example.org/xenia?");
+ let resolved = Services.io.newURI("?x", null, base);
+ let expected = Services.io.newURI("http://example.org/xenia?x");
+ do_info(
+ "Bug 662981: ACSII - comparing " + resolved.spec + " and " + expected.spec
+ );
+ Assert.ok(resolved.equals(expected));
+
+ // UTF-8 character "è"
+ // Bug 622981 was triggered by an empty query string
+ base = Services.io.newURI("http://example.org/xènia?");
+ resolved = Services.io.newURI("?x", null, base);
+ expected = Services.io.newURI("http://example.org/xènia?x");
+ do_info(
+ "Bug 662981: UTF8 - comparing " + resolved.spec + " and " + expected.spec
+ );
+ Assert.ok(resolved.equals(expected));
+
+ gTests.forEach(function (aTest) {
+ // Check basic URI functionality
+ do_test_uri_basic(aTest);
+
+ if (!aTest.fail) {
+ // Try adding various #-prefixed strings to the ends of the URIs
+ gHashSuffixes.forEach(function (aSuffix) {
+ do_test_uri_with_hash_suffix(aTest, aSuffix);
+ if (!aTest.immutable) {
+ do_test_mutate_ref(aTest, aSuffix);
+ }
+ });
+
+ // For URIs that we couldn't mutate above due to them being immutable:
+ // Now we check that they're actually immutable.
+ if (aTest.immutable) {
+ Assert.ok(aTest.immutable);
+ }
+ }
+ });
+});
+
+function check_round_trip_serialization(spec) {
+ dump(`checking ${spec}\n`);
+ let uri = Services.io.newURI(spec);
+ let str = serialize_to_escaped_string(uri);
+ let other = deserialize_from_escaped_string(str).QueryInterface(Ci.nsIURI);
+ equal(other.spec, uri.spec);
+}
+
+add_task(function test_iconURI_serialization() {
+ // URIs taken from test_moz_icon_uri.js
+
+ let tests = [
+ "moz-icon://foo.html?contentType=bar&size=button&state=normal",
+ "moz-icon://foo.html?size=3",
+ "moz-icon://stock/foo",
+ "moz-icon:file://foo.txt",
+ "moz-icon://file://foo.txt",
+ ];
+
+ tests.forEach(str => check_round_trip_serialization(str));
+});
+
+add_task(function test_jarURI_serialization() {
+ check_round_trip_serialization("jar:http://example.com/bar.jar!/");
+});
+
+add_task(async function round_trip_invalid_ace_label() {
+ // This is well-formed punycode, but an invalid ACE label due to hyphens in
+ // positions 3 & 4 and trailing hyphen. (Punycode-decode yields "xn--d淾-")
+ let uri = Services.io.newURI("http://xn--xn--d--fg4n/");
+ Assert.equal(uri.spec, "http://xn--xn--d--fg4n/");
+
+ // Entirely invalid punycode will throw a MALFORMED error.
+ Assert.throws(() => {
+ uri = Services.io.newURI("http://a.b.c.XN--pokxncvks");
+ }, /NS_ERROR_MALFORMED_URI/);
+});
+
+add_task(async function test_bug1875119() {
+ let uri1 = Services.io.newURI("file:///path");
+ let uri2 = Services.io.newURI("resource://test/bla");
+ // type of uri2 is still SubstitutingURL which overrides the implementation of EnsureFile,
+ // but it's scheme is now file.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1876483 to disallow this
+ uri2 = uri2.mutate().setSpec("file:///path2").finalize();
+ Assert.throws(
+ () => uri1.equals(uri2),
+ /(NS_NOINTERFACE)|(NS_ERROR_FILE_UNRECOGNIZED_PATH)/,
+ "uri2 is in an invalid state and should throw"
+ );
+});
+
+add_task(async function test_bug1843717() {
+ // Make sure file path normalization on windows
+ // doesn't affect the hash of the URL.
+ let base = Services.io.newURI("file:///abc\\def/");
+ let uri = Services.io.newURI("foo\\bar#x\\y", null, base);
+ Assert.equal(uri.spec, "file:///abc/def/foo/bar#x\\y");
+ uri = Services.io.newURI("foo\\bar#xy", null, base);
+ Assert.equal(uri.spec, "file:///abc/def/foo/bar#xy");
+ uri = Services.io.newURI("foo\\bar#", null, base);
+ Assert.equal(uri.spec, "file:///abc/def/foo/bar#");
+});
diff --git a/netwerk/test/unit/test_URIs2.js b/netwerk/test/unit/test_URIs2.js
new file mode 100644
index 0000000000..3ca9706543
--- /dev/null
+++ b/netwerk/test/unit/test_URIs2.js
@@ -0,0 +1,881 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+"use strict";
+
+// Run by: cd objdir; make -C netwerk/test/ xpcshell-tests
+// or: cd objdir; make SOLO_FILE="test_URIs2.js" -C netwerk/test/ check-one
+
+// This is a clone of test_URIs.js, with a different set of test data in gTests.
+// The original test data in test_URIs.js was split between test_URIs and test_URIs2.js
+// because test_URIs.js was running for too long on slow platforms, causing
+// intermittent timeouts.
+
+// Relevant RFCs: 1738, 1808, 2396, 3986 (newer than the code)
+// http://greenbytes.de/tech/webdav/rfc3986.html#rfc.section.5.4
+// http://greenbytes.de/tech/tc/uris/
+
+// TEST DATA
+// ---------
+var gTests = [
+ {
+ spec: "view-source:about:blank",
+ scheme: "view-source",
+ prePath: "view-source:",
+ pathQueryRef: "about:blank",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: true,
+ immutable: true,
+ },
+ {
+ spec: "view-source:http://www.mozilla.org/",
+ scheme: "view-source",
+ prePath: "view-source:",
+ pathQueryRef: "http://www.mozilla.org/",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: true,
+ immutable: true,
+ },
+ {
+ spec: "x-external:",
+ scheme: "x-external",
+ prePath: "x-external:",
+ pathQueryRef: "",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "x-external:abc",
+ scheme: "x-external",
+ prePath: "x-external:",
+ pathQueryRef: "abc",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://www2.example.com/",
+ relativeURI: "a/b/c/d",
+ scheme: "http",
+ prePath: "http://www2.example.com",
+ pathQueryRef: "/a/b/c/d",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ // relative URL testcases from http://greenbytes.de/tech/webdav/rfc3986.html#rfc.section.5.4
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g:h",
+ scheme: "g",
+ prePath: "g:",
+ pathQueryRef: "h",
+ ref: "",
+ nsIURL: false,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "./g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "/g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "?y",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/d;p?y",
+ ref: "", // fix
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g?y",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g?y",
+ ref: "", // fix
+ specIgnoringRef: "http://a/b/c/g?y",
+ hasRef: false,
+ hasQuery: true,
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "#s",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/d;p?q#s",
+ ref: "s", // fix
+ specIgnoringRef: "http://a/b/c/d;p?q",
+ hasRef: true,
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g#s",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g#s",
+ ref: "s",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g?y#s",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g?y#s",
+ ref: "s",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ /*
+ Bug xxxxxx - we return a path of b/c/;x
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ";x",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/d;x",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ */
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g;x",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x?y#s",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g;x?y#s",
+ ref: "s",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ /*
+ Can't easily specify a relative URI of "" to the test code
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/d",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ */
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: ".",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "./",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "..",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "../",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "../g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "../..",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+
+ // abnormal examples
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../../g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../../../g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+
+ // coalesce
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "/./g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "/../g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g.",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g.",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: ".g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/.g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g..",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g..",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "..g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/..g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: ".",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "./../g",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/g",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "./g/.",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g/",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/./h",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g/h",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/../h",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/h",
+ ref: "", // fix
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x=1/./y",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/g;x=1/y",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x=1/../y",
+ scheme: "http",
+ prePath: "http://a",
+ pathQueryRef: "/b/c/y",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ // protocol-relative http://tools.ietf.org/html/rfc3986#section-4.2
+ {
+ spec: "http://www2.example.com/",
+ relativeURI: "//www3.example2.com/bar",
+ scheme: "http",
+ prePath: "http://www3.example2.com",
+ pathQueryRef: "/bar",
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+ {
+ spec: "https://www2.example.com/",
+ relativeURI: "//www3.example2.com/bar",
+ scheme: "https",
+ prePath: "https://www3.example2.com",
+ pathQueryRef: "/bar",
+ hasQuery: false,
+ ref: "",
+ nsIURL: true,
+ nsINestedURI: false,
+ },
+];
+
+var gHashSuffixes = ["#", "#myRef", "#myRef?a=b", "#myRef#", "#myRef#x:yz"];
+
+// TEST HELPER FUNCTIONS
+// ---------------------
+function do_info(text, stack) {
+ if (!stack) {
+ stack = Components.stack.caller;
+ }
+
+ dump(
+ "\n" +
+ "TEST-INFO | " +
+ stack.filename +
+ " | [" +
+ stack.name +
+ " : " +
+ stack.lineNumber +
+ "] " +
+ text +
+ "\n"
+ );
+}
+
+// Checks that the URIs satisfy equals(), in both possible orderings.
+// Also checks URI.equalsExceptRef(), because equal URIs should also be equal
+// when we ignore the ref.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of ok.
+function do_check_uri_eq(aURI1, aURI2, aCheckTrueFunc = ok) {
+ do_info("(uri equals check: '" + aURI1.spec + "' == '" + aURI2.spec + "')");
+ aCheckTrueFunc(aURI1.equals(aURI2));
+ do_info("(uri equals check: '" + aURI2.spec + "' == '" + aURI1.spec + "')");
+ aCheckTrueFunc(aURI2.equals(aURI1));
+
+ // (Only take the extra step of testing 'equalsExceptRef' when we expect the
+ // URIs to really be equal. In 'todo' cases, the URIs may or may not be
+ // equal when refs are ignored - there's no way of knowing in general.)
+ if (aCheckTrueFunc == ok) {
+ do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc);
+ }
+}
+
+// Checks that the URIs satisfy equalsExceptRef(), in both possible orderings.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of ok.
+function do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc = ok) {
+ do_info(
+ "(uri equalsExceptRef check: '" + aURI1.spec + "' == '" + aURI2.spec + "')"
+ );
+ aCheckTrueFunc(aURI1.equalsExceptRef(aURI2));
+ do_info(
+ "(uri equalsExceptRef check: '" + aURI2.spec + "' == '" + aURI1.spec + "')"
+ );
+ aCheckTrueFunc(aURI2.equalsExceptRef(aURI1));
+}
+
+// Checks that the given property on aURI matches the corresponding property
+// in the test bundle (or matches some function of that corresponding property,
+// if aTestFunctor is passed in).
+function do_check_property(aTest, aURI, aPropertyName, aTestFunctor) {
+ if (aTest[aPropertyName]) {
+ var expectedVal = aTestFunctor
+ ? aTestFunctor(aTest[aPropertyName])
+ : aTest[aPropertyName];
+
+ do_info(
+ "testing " +
+ aPropertyName +
+ " of " +
+ (aTestFunctor ? "modified '" : "'") +
+ aTest.spec +
+ "' is '" +
+ expectedVal +
+ "'"
+ );
+ Assert.equal(aURI[aPropertyName], expectedVal);
+ }
+}
+
+// Test that a given URI parses correctly into its various components.
+function do_test_uri_basic(aTest) {
+ var URI;
+
+ do_info(
+ "Basic tests for " + aTest.spec + " relative URI: " + aTest.relativeURI
+ );
+
+ try {
+ URI = NetUtil.newURI(aTest.spec);
+ } catch (e) {
+ do_info("Caught error on parse of" + aTest.spec + " Error: " + e.result);
+ if (aTest.fail) {
+ Assert.equal(e.result, aTest.result);
+ return;
+ }
+ do_throw(e.result);
+ }
+
+ if (aTest.relativeURI) {
+ var relURI;
+
+ try {
+ relURI = Services.io.newURI(aTest.relativeURI, null, URI);
+ } catch (e) {
+ do_info(
+ "Caught error on Relative parse of " +
+ aTest.spec +
+ " + " +
+ aTest.relativeURI +
+ " Error: " +
+ e.result
+ );
+ if (aTest.relativeFail) {
+ Assert.equal(e.result, aTest.relativeFail);
+ return;
+ }
+ do_throw(e.result);
+ }
+ do_info(
+ "relURI.pathQueryRef = " +
+ relURI.pathQueryRef +
+ ", was " +
+ URI.pathQueryRef
+ );
+ URI = relURI;
+ do_info("URI.pathQueryRef now = " + URI.pathQueryRef);
+ }
+
+ // Sanity-check
+ do_info("testing " + aTest.spec + " equals a clone of itself");
+ do_check_uri_eq(URI, URI.mutate().finalize());
+ do_check_uri_eqExceptRef(URI, URI.mutate().setRef("").finalize());
+ do_info("testing " + aTest.spec + " instanceof nsIURL");
+ Assert.equal(URI instanceof Ci.nsIURL, aTest.nsIURL);
+ do_info("testing " + aTest.spec + " instanceof nsINestedURI");
+ Assert.equal(URI instanceof Ci.nsINestedURI, aTest.nsINestedURI);
+
+ do_info(
+ "testing that " +
+ aTest.spec +
+ " throws or returns false " +
+ "from equals(null)"
+ );
+ // XXXdholbert At some point it'd probably be worth making this behavior
+ // (throwing vs. returning false) consistent across URI implementations.
+ var threw = false;
+ var isEqualToNull;
+ try {
+ isEqualToNull = URI.equals(null);
+ } catch (e) {
+ threw = true;
+ }
+ Assert.ok(threw || !isEqualToNull);
+
+ // Check the various components
+ do_check_property(aTest, URI, "scheme");
+ do_check_property(aTest, URI, "prePath");
+ do_check_property(aTest, URI, "pathQueryRef");
+ do_check_property(aTest, URI, "query");
+ do_check_property(aTest, URI, "ref");
+ do_check_property(aTest, URI, "port");
+ do_check_property(aTest, URI, "username");
+ do_check_property(aTest, URI, "password");
+ do_check_property(aTest, URI, "host");
+ do_check_property(aTest, URI, "specIgnoringRef");
+ if ("hasRef" in aTest) {
+ do_info("testing hasref: " + aTest.hasRef + " vs " + URI.hasRef);
+ Assert.equal(aTest.hasRef, URI.hasRef);
+ }
+ if ("hasQuery" in aTest) {
+ do_info("testing hasQuery: " + aTest.hasQuery + " vs " + URI.hasQuery);
+ Assert.equal(aTest.hasQuery, URI.hasQuery);
+ }
+}
+
+// Test that a given URI parses correctly when we add a given ref to the end
+function do_test_uri_with_hash_suffix(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ Assert.equal(aSuffix[0], "#");
+
+ var origURI = NetUtil.newURI(aTest.spec);
+ var testURI;
+
+ if (aTest.relativeURI) {
+ try {
+ origURI = Services.io.newURI(aTest.relativeURI, null, origURI);
+ } catch (e) {
+ do_info(
+ "Caught error on Relative parse of " +
+ aTest.spec +
+ " + " +
+ aTest.relativeURI +
+ " Error: " +
+ e.result
+ );
+ return;
+ }
+ try {
+ testURI = Services.io.newURI(aSuffix, null, origURI);
+ } catch (e) {
+ do_info(
+ "Caught error adding suffix to " +
+ aTest.spec +
+ " + " +
+ aTest.relativeURI +
+ ", suffix " +
+ aSuffix +
+ " Error: " +
+ e.result
+ );
+ return;
+ }
+ } else {
+ testURI = NetUtil.newURI(aTest.spec + aSuffix);
+ }
+
+ do_info(
+ "testing " +
+ aTest.spec +
+ " with '" +
+ aSuffix +
+ "' appended " +
+ "equals a clone of itself"
+ );
+ do_check_uri_eq(testURI, testURI.mutate().finalize());
+
+ do_info(
+ "testing " +
+ aTest.spec +
+ " doesn't equal self with '" +
+ aSuffix +
+ "' appended"
+ );
+
+ Assert.ok(!origURI.equals(testURI));
+
+ do_info(
+ "testing " +
+ aTest.spec +
+ " is equalExceptRef to self with '" +
+ aSuffix +
+ "' appended"
+ );
+ do_check_uri_eqExceptRef(origURI, testURI);
+
+ Assert.equal(testURI.hasRef, true);
+
+ if (!origURI.ref) {
+ // These tests fail if origURI has a ref
+ do_info(
+ "testing cloneIgnoringRef on " +
+ testURI.spec +
+ " is equal to no-ref version but not equal to ref version"
+ );
+ var cloneNoRef = testURI.mutate().setRef("").finalize();
+ do_check_uri_eq(cloneNoRef, origURI);
+ Assert.ok(!cloneNoRef.equals(testURI));
+ }
+
+ do_check_property(aTest, testURI, "scheme");
+ do_check_property(aTest, testURI, "prePath");
+ if (!origURI.ref) {
+ // These don't work if it's a ref already because '+' doesn't give the right result
+ do_check_property(aTest, testURI, "pathQueryRef", function (aStr) {
+ return aStr + aSuffix;
+ });
+ do_check_property(aTest, testURI, "ref", function (aStr) {
+ return aSuffix.substr(1);
+ });
+ }
+}
+
+// Tests various ways of setting & clearing a ref on a URI.
+function do_test_mutate_ref(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ Assert.equal(aSuffix[0], "#");
+
+ var refURIWithSuffix = NetUtil.newURI(aTest.spec + aSuffix);
+ var refURIWithoutSuffix = NetUtil.newURI(aTest.spec);
+
+ var testURI = NetUtil.newURI(aTest.spec);
+
+ // First: Try setting .ref to our suffix
+ do_info(
+ "testing that setting .ref on " +
+ aTest.spec +
+ " to '" +
+ aSuffix +
+ "' does what we expect"
+ );
+ testURI = testURI.mutate().setRef(aSuffix).finalize();
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+
+ // Now try setting .ref but leave off the initial hash (expect same result)
+ var suffixLackingHash = aSuffix.substr(1);
+ if (suffixLackingHash) {
+ // (skip this our suffix was *just* a #)
+ do_info(
+ "testing that setting .ref on " +
+ aTest.spec +
+ " to '" +
+ suffixLackingHash +
+ "' does what we expect"
+ );
+ testURI = testURI.mutate().setRef(suffixLackingHash).finalize();
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+ }
+
+ // Now, clear .ref (should get us back the original spec)
+ do_info(
+ "testing that clearing .ref on " + testURI.spec + " does what we expect"
+ );
+ testURI = testURI.mutate().setRef("").finalize();
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ if (!aTest.relativeURI) {
+ // TODO: These tests don't work as-is for relative URIs.
+
+ // Now try setting .spec directly (including suffix) and then clearing .ref
+ var specWithSuffix = aTest.spec + aSuffix;
+ do_info(
+ "testing that setting spec to " +
+ specWithSuffix +
+ " and then clearing ref does what we expect"
+ );
+ testURI = testURI.mutate().setSpec(specWithSuffix).setRef("").finalize();
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // XXX nsIJARURI throws an exception in SetPath(), so skip it for next part.
+ if (!(testURI instanceof Ci.nsIJARURI)) {
+ // Now try setting .pathQueryRef directly (including suffix) and then clearing .ref
+ // (same as above, but with now with .pathQueryRef instead of .spec)
+ testURI = NetUtil.newURI(aTest.spec);
+
+ var pathWithSuffix = aTest.pathQueryRef + aSuffix;
+ do_info(
+ "testing that setting path to " +
+ pathWithSuffix +
+ " and then clearing ref does what we expect"
+ );
+ testURI = testURI
+ .mutate()
+ .setPathQueryRef(pathWithSuffix)
+ .setRef("")
+ .finalize();
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // Also: make sure that clearing .pathQueryRef also clears .ref
+ testURI = testURI.mutate().setPathQueryRef(pathWithSuffix).finalize();
+ do_info(
+ "testing that clearing path from " +
+ pathWithSuffix +
+ " also clears .ref"
+ );
+ testURI = testURI.mutate().setPathQueryRef("").finalize();
+ Assert.equal(testURI.ref, "");
+ }
+ }
+}
+
+// TEST MAIN FUNCTION
+// ------------------
+function run_test() {
+ // UTF-8 check - From bug 622981
+ // ASCII
+ let base = Services.io.newURI("http://example.org/xenia?");
+ let resolved = Services.io.newURI("?x", null, base);
+ let expected = Services.io.newURI("http://example.org/xenia?x");
+ do_info(
+ "Bug 662981: ACSII - comparing " + resolved.spec + " and " + expected.spec
+ );
+ Assert.ok(resolved.equals(expected));
+
+ // UTF-8 character "è"
+ // Bug 622981 was triggered by an empty query string
+ base = Services.io.newURI("http://example.org/xènia?");
+ resolved = Services.io.newURI("?x", null, base);
+ expected = Services.io.newURI("http://example.org/xènia?x");
+ do_info(
+ "Bug 662981: UTF8 - comparing " + resolved.spec + " and " + expected.spec
+ );
+ Assert.ok(resolved.equals(expected));
+
+ gTests.forEach(function (aTest) {
+ // Check basic URI functionality
+ do_test_uri_basic(aTest);
+
+ if (!aTest.fail) {
+ // Try adding various #-prefixed strings to the ends of the URIs
+ gHashSuffixes.forEach(function (aSuffix) {
+ do_test_uri_with_hash_suffix(aTest, aSuffix);
+ if (!aTest.immutable) {
+ do_test_mutate_ref(aTest, aSuffix);
+ }
+ });
+
+ // For URIs that we couldn't mutate above due to them being immutable:
+ // Now we check that they're actually immutable.
+ if (aTest.immutable) {
+ Assert.ok(aTest.immutable);
+ }
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_XHR_redirects.js b/netwerk/test/unit/test_XHR_redirects.js
new file mode 100644
index 0000000000..e3bee4c76f
--- /dev/null
+++ b/netwerk/test/unit/test_XHR_redirects.js
@@ -0,0 +1,275 @@
+// This file tests whether XmlHttpRequests correctly handle redirects,
+// including rewriting POSTs to GETs (on 301/302/303), as well as
+// prompting for redirects of other unsafe methods (such as PUTs, DELETEs,
+// etc--see HttpBaseChannel::IsSafeMethod). Since no prompting is possible
+// in xpcshell, we get an error for prompts, and the request fails.
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+);
+
+var sSame;
+var sOther;
+var sRedirectPromptPref;
+
+const BUGID = "676059";
+const OTHERBUGID = "696849";
+
+ChromeUtils.defineLazyGetter(this, "pSame", function () {
+ return sSame.identity.primaryPort;
+});
+ChromeUtils.defineLazyGetter(this, "pOther", function () {
+ return sOther.identity.primaryPort;
+});
+
+function createXHR(async, method, path) {
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, "http://localhost:" + pSame + path, async);
+ return xhr;
+}
+
+function checkResults(xhr, method, status, unsafe) {
+ if (unsafe) {
+ if (sRedirectPromptPref) {
+ // The method is null if we prompt for unsafe redirects
+ method = null;
+ } else {
+ // The status code is 200 when we don't prompt for unsafe redirects
+ status = 200;
+ }
+ }
+
+ if (xhr.readyState != 4) {
+ return false;
+ }
+ Assert.equal(xhr.status, status);
+
+ if (status == 200) {
+ // if followed then check for echoed method name
+ Assert.equal(xhr.getResponseHeader("X-Received-Method"), method);
+ }
+
+ return true;
+}
+
+function run_test() {
+ // start servers
+ sSame = new HttpServer();
+
+ // same-origin redirects
+ sSame.registerPathHandler(
+ "/bug" + BUGID + "-redirect301",
+ bug676059redirect301
+ );
+ sSame.registerPathHandler(
+ "/bug" + BUGID + "-redirect302",
+ bug676059redirect302
+ );
+ sSame.registerPathHandler(
+ "/bug" + BUGID + "-redirect303",
+ bug676059redirect303
+ );
+ sSame.registerPathHandler(
+ "/bug" + BUGID + "-redirect307",
+ bug676059redirect307
+ );
+ sSame.registerPathHandler(
+ "/bug" + BUGID + "-redirect308",
+ bug676059redirect308
+ );
+
+ // cross-origin redirects
+ sSame.registerPathHandler(
+ "/bug" + OTHERBUGID + "-redirect301",
+ bug696849redirect301
+ );
+ sSame.registerPathHandler(
+ "/bug" + OTHERBUGID + "-redirect302",
+ bug696849redirect302
+ );
+ sSame.registerPathHandler(
+ "/bug" + OTHERBUGID + "-redirect303",
+ bug696849redirect303
+ );
+ sSame.registerPathHandler(
+ "/bug" + OTHERBUGID + "-redirect307",
+ bug696849redirect307
+ );
+ sSame.registerPathHandler(
+ "/bug" + OTHERBUGID + "-redirect308",
+ bug696849redirect308
+ );
+
+ // same-origin target
+ sSame.registerPathHandler("/bug" + BUGID + "-target", echoMethod);
+ sSame.start(-1);
+
+ // cross-origin target
+ sOther = new HttpServer();
+ sOther.registerPathHandler("/bug" + OTHERBUGID + "-target", echoMethod);
+ sOther.start(-1);
+
+ // format: redirectType, methodToSend, redirectedMethod, finalStatus
+ // redirectType sets the URI the initial request goes to
+ // methodToSend is the HTTP method to send
+ // redirectedMethod is the method to use for the redirect, if any
+ // finalStatus is 200 when the redirect takes place, redirectType otherwise
+
+ // Note that unsafe methods should not follow the redirect automatically
+ // Of the methods below, DELETE, POST and PUT are unsafe
+
+ sRedirectPromptPref = Preferences.get("network.http.prompt-temp-redirect");
+ // Following Bug 677754 we don't prompt for unsafe redirects
+
+ // same-origin variant
+ var tests = [
+ // 301: rewrite just POST
+ [301, "DELETE", "DELETE", 301, true],
+ [301, "GET", "GET", 200, false],
+ [301, "HEAD", "HEAD", 200, false],
+ [301, "POST", "GET", 200, false],
+ [301, "PUT", "PUT", 301, true],
+ [301, "PROPFIND", "PROPFIND", 200, false],
+ // 302: see 301
+ [302, "DELETE", "DELETE", 302, true],
+ [302, "GET", "GET", 200, false],
+ [302, "HEAD", "HEAD", 200, false],
+ [302, "POST", "GET", 200, false],
+ [302, "PUT", "PUT", 302, true],
+ [302, "PROPFIND", "PROPFIND", 200, false],
+ // 303: rewrite to GET except HEAD
+ [303, "DELETE", "GET", 200, false],
+ [303, "GET", "GET", 200, false],
+ [303, "HEAD", "HEAD", 200, false],
+ [303, "POST", "GET", 200, false],
+ [303, "PUT", "GET", 200, false],
+ [303, "PROPFIND", "GET", 200, false],
+ // 307: never rewrite
+ [307, "DELETE", "DELETE", 307, true],
+ [307, "GET", "GET", 200, false],
+ [307, "HEAD", "HEAD", 200, false],
+ [307, "POST", "POST", 307, true],
+ [307, "PUT", "PUT", 307, true],
+ [307, "PROPFIND", "PROPFIND", 200, false],
+ // 308: never rewrite
+ [308, "DELETE", "DELETE", 308, true],
+ [308, "GET", "GET", 200, false],
+ [308, "HEAD", "HEAD", 200, false],
+ [308, "POST", "POST", 308, true],
+ [308, "PUT", "PUT", 308, true],
+ [308, "PROPFIND", "PROPFIND", 200, false],
+ ];
+
+ // cross-origin variant
+ var othertests = tests; // for now these have identical results
+
+ var xhr;
+
+ for (let i = 0; i < tests.length; ++i) {
+ dump("Testing " + tests[i] + "\n");
+ xhr = createXHR(
+ false,
+ tests[i][1],
+ "/bug" + BUGID + "-redirect" + tests[i][0]
+ );
+ xhr.send(null);
+ checkResults(xhr, tests[i][2], tests[i][3], tests[i][4]);
+ }
+
+ for (let i = 0; i < othertests.length; ++i) {
+ dump("Testing " + othertests[i] + " (cross-origin)\n");
+ xhr = createXHR(
+ false,
+ othertests[i][1],
+ "/bug" + OTHERBUGID + "-redirect" + othertests[i][0]
+ );
+ xhr.send(null);
+ checkResults(xhr, othertests[i][2], tests[i][3], tests[i][4]);
+ }
+
+ sSame.stop(do_test_finished);
+ sOther.stop(do_test_finished);
+}
+
+function redirect(metadata, response, status, port, bugid) {
+ // set a proper reason string to avoid confusion when looking at the
+ // HTTP messages
+ var reason;
+ if (status == 301) {
+ reason = "Moved Permanently";
+ } else if (status == 302) {
+ reason = "Found";
+ } else if (status == 303) {
+ reason = "See Other";
+ } else if (status == 307) {
+ reason = "Temporary Redirect";
+ } else if (status == 308) {
+ reason = "Permanent Redirect";
+ }
+
+ response.setStatusLine(metadata.httpVersion, status, reason);
+ response.setHeader(
+ "Location",
+ "http://localhost:" + port + "/bug" + bugid + "-target"
+ );
+}
+
+// PATH HANDLER FOR /bug676059-redirect301
+function bug676059redirect301(metadata, response) {
+ redirect(metadata, response, 301, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect301
+function bug696849redirect301(metadata, response) {
+ redirect(metadata, response, 301, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect302
+function bug676059redirect302(metadata, response) {
+ redirect(metadata, response, 302, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect302
+function bug696849redirect302(metadata, response) {
+ redirect(metadata, response, 302, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect303
+function bug676059redirect303(metadata, response) {
+ redirect(metadata, response, 303, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect303
+function bug696849redirect303(metadata, response) {
+ redirect(metadata, response, 303, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect307
+function bug676059redirect307(metadata, response) {
+ redirect(metadata, response, 307, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect308
+function bug676059redirect308(metadata, response) {
+ redirect(metadata, response, 308, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect307
+function bug696849redirect307(metadata, response) {
+ redirect(metadata, response, 307, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect308
+function bug696849redirect308(metadata, response) {
+ redirect(metadata, response, 308, pOther, OTHERBUGID);
+}
+
+// Echo the request method in "X-Received-Method" header field
+function echoMethod(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("X-Received-Method", metadata.method);
+}
diff --git a/netwerk/test/unit/test_about_networking.js b/netwerk/test/unit/test_about_networking.js
new file mode 100644
index 0000000000..2cbae65058
--- /dev/null
+++ b/netwerk/test/unit/test_about_networking.js
@@ -0,0 +1,120 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService(
+ Ci.nsIDashboard
+);
+
+const gServerSocket = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+);
+const gHttpServer = new HttpServer();
+
+add_test(function test_http() {
+ gDashboard.requestHttpConnections(function (data) {
+ let found = false;
+ for (let i = 0; i < data.connections.length; i++) {
+ if (data.connections[i].host == "localhost") {
+ found = true;
+ break;
+ }
+ }
+ Assert.equal(found, true);
+
+ run_next_test();
+ });
+});
+
+add_test(function test_dns() {
+ gDashboard.requestDNSInfo(function (data) {
+ let found = false;
+ for (let i = 0; i < data.entries.length; i++) {
+ if (data.entries[i].hostname == "localhost") {
+ found = true;
+ break;
+ }
+ }
+ Assert.equal(found, true);
+
+ do_test_pending();
+ gHttpServer.stop(do_test_finished);
+
+ run_next_test();
+ });
+});
+
+add_test(function test_sockets() {
+ // TODO: enable this test in bug 1581892.
+ if (mozinfo.socketprocess_networking) {
+ info("skip test_sockets");
+ run_next_test();
+ return;
+ }
+
+ let sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+ );
+ let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ let transport = sts.createTransport(
+ [],
+ "127.0.0.1",
+ gServerSocket.port,
+ null,
+ null
+ );
+ let listener = {
+ onTransportStatus(aTransport, aStatus, aProgress, aProgressMax) {
+ if (aStatus == Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ gDashboard.requestSockets(function (data) {
+ gServerSocket.close();
+ let found = false;
+ for (let i = 0; i < data.sockets.length; i++) {
+ if (data.sockets[i].host == "127.0.0.1") {
+ found = true;
+ break;
+ }
+ }
+ Assert.equal(found, true);
+
+ run_next_test();
+ });
+ }
+ },
+ };
+ transport.setEventSink(listener, threadManager.currentThread);
+
+ transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);
+});
+
+function run_test() {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // We always resolve localhost as it's hardcoded without the following pref:
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ gHttpServer.start(-1);
+
+ let uri = Services.io.newURI(
+ "http://localhost:" + gHttpServer.identity.primaryPort
+ );
+ let channel = NetUtil.newChannel({ uri, loadUsingSystemPrincipal: true });
+
+ channel.open();
+
+ gServerSocket.init(-1, true, -1);
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_about_protocol.js b/netwerk/test/unit/test_about_protocol.js
new file mode 100644
index 0000000000..7fc6d63c1e
--- /dev/null
+++ b/netwerk/test/unit/test_about_protocol.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var unsafeAboutModule = {
+ QueryInterface: ChromeUtils.generateQI(["nsIAboutModule"]),
+ newChannel(aURI, aLoadInfo) {
+ var uri = Services.io.newURI("about:blank");
+ let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
+ chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
+ return chan;
+ },
+ getURIFlags(aURI) {
+ return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT;
+ },
+};
+
+var factory = {
+ createInstance(aIID) {
+ return unsafeAboutModule.QueryInterface(aIID);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
+};
+
+function run_test() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ let classID = Services.uuid.generateUUID();
+ registrar.registerFactory(
+ classID,
+ "",
+ "@mozilla.org/network/protocol/about;1?what=unsafe",
+ factory
+ );
+
+ let aboutUnsafeChan = NetUtil.newChannel({
+ uri: "about:unsafe",
+ loadUsingSystemPrincipal: true,
+ });
+
+ Assert.equal(
+ null,
+ aboutUnsafeChan.owner,
+ "URI_SAFE_FOR_UNTRUSTED_CONTENT channel has no owner"
+ );
+
+ registrar.unregisterFactory(classID, factory);
+}
diff --git a/netwerk/test/unit/test_aboutblank.js b/netwerk/test/unit/test_aboutblank.js
new file mode 100644
index 0000000000..2d5c92f095
--- /dev/null
+++ b/netwerk/test/unit/test_aboutblank.js
@@ -0,0 +1,32 @@
+"use strict";
+
+function run_test() {
+ var base = NetUtil.newURI("http://www.example.com");
+ var about1 = NetUtil.newURI("about:blank");
+ var about2 = NetUtil.newURI("about:blank", null, base);
+
+ var chan1 = NetUtil.newChannel({
+ uri: about1,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIPropertyBag2);
+
+ var chan2 = NetUtil.newChannel({
+ uri: about2,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIPropertyBag2);
+
+ var haveProp = false;
+ var propVal = null;
+ try {
+ propVal = chan1.getPropertyAsInterface("baseURI", Ci.nsIURI);
+ haveProp = true;
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw e;
+ }
+ // Property shouldn't be there.
+ }
+ Assert.equal(propVal, null);
+ Assert.equal(haveProp, false);
+ Assert.equal(chan2.getPropertyAsInterface("baseURI", Ci.nsIURI), base);
+}
diff --git a/netwerk/test/unit/test_addr_in_use_error.js b/netwerk/test/unit/test_addr_in_use_error.js
new file mode 100644
index 0000000000..d25860e896
--- /dev/null
+++ b/netwerk/test/unit/test_addr_in_use_error.js
@@ -0,0 +1,36 @@
+// Opening a second listening socket on the same address as an extant
+// socket should elicit NS_ERROR_SOCKET_ADDRESS_IN_USE on non-Windows
+// machines.
+
+"use strict";
+
+var CC = Components.Constructor;
+
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+
+function testAddrInUse() {
+ // Windows lets us have as many sockets listening on the same address as
+ // we like, evidently.
+ if (mozinfo.os == "win") {
+ return;
+ }
+
+ // Create listening socket:
+ // any port (-1), loopback only (true), default backlog (-1)
+ let listener = ServerSocket(-1, true, -1);
+ Assert.ok(listener instanceof Ci.nsIServerSocket);
+
+ // Try to create another listening socket on the same port, whatever that was.
+ do_check_throws_nsIException(
+ () => ServerSocket(listener.port, true, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE"
+ );
+}
+
+function run_test() {
+ testAddrInUse();
+}
diff --git a/netwerk/test/unit/test_alt-data_closeWithStatus.js b/netwerk/test/unit/test_alt-data_closeWithStatus.js
new file mode 100644
index 0000000000..df81419cc2
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_closeWithStatus.js
@@ -0,0 +1,184 @@
+/**
+ * Test for the "alternative data stream" - closing the stream with an error.
+ *
+ * - we load a URL with preference for an alt data (check what we get is the raw data,
+ * since there was nothing previously cached)
+ * - we store something in alt data (using the asyncWait method)
+ * - then we abort the operation calling closeWithStatus()
+ * - we flush the HTTP cache
+ * - we reload the same URL using a new channel, again prefering the alt data be loaded
+ * - again we receive the data from the server.
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+// needs to be rooted
+var cacheFlushObserver = (cacheFlushObserver = {
+ observe() {
+ cacheFlushObserver = null;
+ readServerContentAgain();
+ },
+});
+
+var currentThread = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+const responseContent = "response body";
+const responseContent2 = "response body 2";
+const altContent = "!@#$%^&*()";
+const altContentType = "text/binary";
+
+var shouldPassRevalidation = true;
+
+var cache_storage = null;
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("ETag", "test-etag1");
+
+ let etag;
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+
+ if (etag == "test-etag1" && shouldPassRevalidation) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ } else {
+ var content = shouldPassRevalidation ? responseContent : responseContent2;
+ response.bodyOutputStream.write(content, content.length);
+ }
+}
+
+function check_has_alt_data_in_index(aHasAltData, callback) {
+ if (inChildProcess()) {
+ callback();
+ return;
+ }
+
+ syncWithCacheIOThread(() => {
+ var hasAltData = {};
+ cache_storage.getCacheIndexEntryAttrs(createURI(URL), "", hasAltData, {});
+ Assert.equal(hasAltData.value, aHasAltData);
+ callback();
+ }, true);
+}
+
+function run_test() {
+ do_get_profile();
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+ do_test_pending();
+
+ if (!inChildProcess()) {
+ cache_storage = getCacheStorage("disk");
+ wait_for_cache_index(asyncOpen);
+ } else {
+ asyncOpen();
+ }
+}
+
+function asyncOpen() {
+ var chan = make_channel(URL);
+
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+
+ chan.asyncOpen(new ChannelListener(readServerContent, null));
+}
+
+function readServerContent(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(buffer, responseContent);
+ Assert.equal(cc.alternativeDataType, "");
+ check_has_alt_data_in_index(false, () => {
+ if (!inChildProcess()) {
+ currentThread = Services.tm.currentThread;
+ }
+
+ executeSoon(() => {
+ var os = cc.openAlternativeOutputStream(
+ altContentType,
+ altContent.length
+ );
+
+ var aos = os.QueryInterface(Ci.nsIAsyncOutputStream);
+ aos.asyncWait(
+ _ => {
+ os.write(altContent, altContent.length);
+ aos.closeWithStatus(Cr.NS_ERROR_FAILURE);
+ executeSoon(flushAndReadServerContentAgain);
+ },
+ 0,
+ 0,
+ currentThread
+ );
+ });
+ });
+}
+
+function flushAndReadServerContentAgain() {
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ gc();
+ if (!inChildProcess()) {
+ Services.cache2
+ .QueryInterface(Ci.nsICacheTesting)
+ .flush(cacheFlushObserver);
+ } else {
+ do_send_remote_message("flush");
+ do_await_remote_message("flushed").then(() => {
+ readServerContentAgain();
+ });
+ }
+}
+
+function readServerContentAgain() {
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ "dummy1",
+ "text/javascript",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ cc.preferAlternativeDataType(
+ altContentType,
+ "text/plain",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ cc.preferAlternativeDataType("dummy2", "", Ci.nsICacheInfoChannel.ASYNC);
+
+ chan.asyncOpen(new ChannelListener(readServerContentAgainCB, null));
+}
+
+function readServerContentAgainCB(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(buffer, responseContent);
+ Assert.equal(cc.alternativeDataType, "");
+ check_has_alt_data_in_index(false, () => httpServer.stop(do_test_finished));
+}
diff --git a/netwerk/test/unit/test_alt-data_cross_process.js b/netwerk/test/unit/test_alt-data_cross_process.js
new file mode 100644
index 0000000000..e46120f312
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_cross_process.js
@@ -0,0 +1,153 @@
+/**
+ * Test for the "alternative data stream" stored withing a cache entry.
+ *
+ * - we load a URL with preference for an alt data (check what we get is the raw data,
+ * since there was nothing previously cached)
+ * - we store the alt data along the channel (to the cache entry)
+ * - we flush the HTTP cache
+ * - we reload the same URL using a new channel, again prefering the alt data be loaded
+ * - this time the alt data must arive
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+const responseContent = "response body";
+const responseContent2 = "response body 2";
+const altContent = "!@#$%^&*()";
+const altContentType = "text/binary";
+
+var servedNotModified = false;
+var shouldPassRevalidation = true;
+
+var cache_storage = null;
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("ETag", "test-etag1");
+
+ let etag;
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+
+ if (etag == "test-etag1" && shouldPassRevalidation) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ servedNotModified = true;
+ } else {
+ var content = shouldPassRevalidation ? responseContent : responseContent2;
+ response.bodyOutputStream.write(content, content.length);
+ }
+}
+
+function check_has_alt_data_in_index(aHasAltData, callback) {
+ if (inChildProcess()) {
+ callback();
+ return;
+ }
+
+ syncWithCacheIOThread(() => {
+ var hasAltData = {};
+ cache_storage.getCacheIndexEntryAttrs(createURI(URL), "", hasAltData, {});
+ Assert.equal(hasAltData.value, aHasAltData);
+ callback();
+ }, true);
+}
+
+// This file is loaded as part of test_alt-data_cross_process_wrap.js.
+// eslint-disable-next-line no-unused-vars
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+ do_test_pending();
+
+ asyncOpen();
+}
+
+function asyncOpen() {
+ var chan = make_channel(URL);
+
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+
+ chan.asyncOpen(new ChannelListener(readServerContent, null));
+}
+
+function readServerContent(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(buffer, responseContent);
+ Assert.equal(cc.alternativeDataType, "");
+ check_has_alt_data_in_index(false, () => {
+ executeSoon(() => {
+ var os = cc.openAlternativeOutputStream(
+ altContentType,
+ altContent.length
+ );
+ os.write(altContent, altContent.length);
+ os.close();
+
+ executeSoon(flushAndOpenAltChannel);
+ });
+ });
+}
+
+function flushAndOpenAltChannel() {
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ gc();
+ do_send_remote_message("flush");
+ do_await_remote_message("flushed").then(() => {
+ openAltChannel();
+ });
+}
+
+function openAltChannel() {
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+
+ chan.asyncOpen(new ChannelListener(readAltContent, null));
+}
+
+function readAltContent(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(servedNotModified, true);
+ Assert.equal(cc.alternativeDataType, altContentType);
+ Assert.equal(buffer, altContent);
+
+ // FINISH
+ do_send_remote_message("done");
+ do_await_remote_message("finish").then(() => {
+ httpServer.stop(do_test_finished);
+ });
+}
diff --git a/netwerk/test/unit/test_alt-data_overwrite.js b/netwerk/test/unit/test_alt-data_overwrite.js
new file mode 100644
index 0000000000..3a3c1964d2
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_overwrite.js
@@ -0,0 +1,209 @@
+/**
+ * Test for overwriting the alternative data in a cache entry.
+ *
+ * - run_test loads a new channel
+ * - readServerContent checks the content, and saves alt-data
+ * - cacheFlushObserver creates a new channel with "text/binary" alt-data type
+ * - readAltContent checks that it gets back alt-data and creates a channel with the dummy/null alt-data type
+ * - readServerContent2 checks that it gets regular content, from the cache and tries to overwrite the alt-data with the same representation
+ * - cacheFlushObserver2 creates a new channel with "text/binary" alt-data type
+ * - readAltContent2 checks that it gets back alt-data, and tries to overwrite with a kind of alt-data
+ * - cacheFlushObserver3 creates a new channel with "text/binary2" alt-data type
+ * - readAltContent3 checks that it gets back the newly saved alt-data
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+let httpServer = null;
+
+function make_and_open_channel(url, altContentType, callback) {
+ let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ if (altContentType) {
+ let cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ }
+ chan.asyncOpen(new ChannelListener(callback, null));
+}
+
+const responseContent = "response body";
+const altContent = "!@#$%^&*()";
+const altContentType = "text/binary";
+const altContent2 = "abc";
+const altContentType2 = "text/binary2";
+
+let servedNotModified = false;
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("ETag", "test-etag1");
+ let etag = "";
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+
+ if (etag == "test-etag1") {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ servedNotModified = true;
+ } else {
+ servedNotModified = false;
+ response.bodyOutputStream.write(responseContent, responseContent.length);
+ }
+}
+
+function run_test() {
+ do_get_profile();
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ do_test_pending();
+ make_and_open_channel(URL, altContentType, readServerContent);
+}
+
+function readServerContent(request, buffer) {
+ let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(buffer, responseContent);
+ Assert.equal(cc.alternativeDataType, "");
+
+ executeSoon(() => {
+ let os = cc.openAlternativeOutputStream(altContentType, altContent.length);
+ os.write(altContent, altContent.length);
+ os.close();
+
+ executeSoon(flushAndOpenAltChannel);
+ });
+}
+
+function flushAndOpenAltChannel() {
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ Cu.forceShrinkingGC();
+ Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver);
+}
+
+// needs to be rooted
+let cacheFlushObserver = {
+ observe() {
+ if (!cacheFlushObserver) {
+ info("ignoring cacheFlushObserver\n");
+ return;
+ }
+ cacheFlushObserver = null;
+ Cu.forceShrinkingGC();
+ make_and_open_channel(URL, altContentType, readAltContent);
+ },
+};
+
+function readAltContent(request, buffer, closure, fromCache) {
+ Cu.forceShrinkingGC();
+ let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(fromCache || servedNotModified, true);
+ Assert.equal(cc.alternativeDataType, altContentType);
+ Assert.equal(buffer, altContent);
+
+ make_and_open_channel(URL, "dummy/null", readServerContent2);
+}
+
+function readServerContent2(request, buffer, closure, fromCache) {
+ Cu.forceShrinkingGC();
+ let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(fromCache || servedNotModified, true);
+ Assert.equal(buffer, responseContent);
+ Assert.equal(cc.alternativeDataType, "");
+
+ executeSoon(() => {
+ let os = cc.openAlternativeOutputStream(altContentType, altContent.length);
+ os.write(altContent, altContent.length);
+ os.close();
+
+ executeSoon(flushAndOpenAltChannel2);
+ });
+}
+
+function flushAndOpenAltChannel2() {
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ Cu.forceShrinkingGC();
+ Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver2);
+}
+
+// needs to be rooted
+let cacheFlushObserver2 = {
+ observe() {
+ if (!cacheFlushObserver2) {
+ info("ignoring cacheFlushObserver2\n");
+ return;
+ }
+ cacheFlushObserver2 = null;
+ Cu.forceShrinkingGC();
+ make_and_open_channel(URL, altContentType, readAltContent2);
+ },
+};
+
+function readAltContent2(request, buffer, closure, fromCache) {
+ Cu.forceShrinkingGC();
+ let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(servedNotModified || fromCache, true);
+ Assert.equal(cc.alternativeDataType, altContentType);
+ Assert.equal(buffer, altContent);
+
+ executeSoon(() => {
+ Cu.forceShrinkingGC();
+ info("writing other content\n");
+ let os = cc.openAlternativeOutputStream(
+ altContentType2,
+ altContent2.length
+ );
+ os.write(altContent2, altContent2.length);
+ os.close();
+
+ executeSoon(flushAndOpenAltChannel3);
+ });
+}
+
+function flushAndOpenAltChannel3() {
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ Cu.forceShrinkingGC();
+ Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver3);
+}
+
+// needs to be rooted
+let cacheFlushObserver3 = {
+ observe() {
+ if (!cacheFlushObserver3) {
+ info("ignoring cacheFlushObserver3\n");
+ return;
+ }
+
+ cacheFlushObserver3 = null;
+ Cu.forceShrinkingGC();
+ make_and_open_channel(URL, altContentType2, readAltContent3);
+ },
+};
+
+function readAltContent3(request, buffer, closure, fromCache) {
+ let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(servedNotModified || fromCache, true);
+ Assert.equal(cc.alternativeDataType, altContentType2);
+ Assert.equal(buffer, altContent2);
+
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_alt-data_simple.js b/netwerk/test/unit/test_alt-data_simple.js
new file mode 100644
index 0000000000..d648884e7a
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -0,0 +1,212 @@
+/**
+ * Test for the "alternative data stream" stored withing a cache entry.
+ *
+ * - we load a URL with preference for an alt data (check what we get is the raw data,
+ * since there was nothing previously cached)
+ * - we store the alt data along the channel (to the cache entry)
+ * - we flush the HTTP cache
+ * - we reload the same URL using a new channel, again prefering the alt data be loaded
+ * - this time the alt data must arive
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+const responseContent = "response body";
+const responseContent2 = "response body 2";
+const altContent = "!@#$%^&*()";
+const altContentType = "text/binary";
+
+var servedNotModified = false;
+var shouldPassRevalidation = true;
+
+var cache_storage = null;
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("ETag", "test-etag1");
+
+ let etag;
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+
+ if (etag == "test-etag1" && shouldPassRevalidation) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ servedNotModified = true;
+ } else {
+ var content = shouldPassRevalidation ? responseContent : responseContent2;
+ response.bodyOutputStream.write(content, content.length);
+ }
+}
+
+function check_has_alt_data_in_index(aHasAltData, callback) {
+ if (inChildProcess()) {
+ callback();
+ return;
+ }
+
+ syncWithCacheIOThread(() => {
+ var hasAltData = {};
+ cache_storage.getCacheIndexEntryAttrs(createURI(URL), "", hasAltData, {});
+ Assert.equal(hasAltData.value, aHasAltData);
+ callback();
+ }, true);
+}
+
+function run_test() {
+ do_get_profile();
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+ do_test_pending();
+
+ if (!inChildProcess()) {
+ cache_storage = getCacheStorage("disk");
+ wait_for_cache_index(asyncOpen);
+ } else {
+ asyncOpen();
+ }
+}
+
+function asyncOpen() {
+ var chan = make_channel(URL);
+
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+
+ chan.asyncOpen(new ChannelListener(readServerContent, null));
+}
+
+function readServerContent(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(buffer, responseContent);
+ Assert.equal(cc.alternativeDataType, "");
+ check_has_alt_data_in_index(false, () => {
+ executeSoon(() => {
+ var os = cc.openAlternativeOutputStream(
+ altContentType,
+ altContent.length
+ );
+ os.write(altContent, altContent.length);
+ os.close();
+
+ executeSoon(flushAndOpenAltChannel);
+ });
+ });
+}
+
+// needs to be rooted
+var cacheFlushObserver = (cacheFlushObserver = {
+ observe() {
+ cacheFlushObserver = null;
+ openAltChannel();
+ },
+});
+
+function flushAndOpenAltChannel() {
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ gc();
+ if (!inChildProcess()) {
+ Services.cache2
+ .QueryInterface(Ci.nsICacheTesting)
+ .flush(cacheFlushObserver);
+ } else {
+ do_send_remote_message("flush");
+ do_await_remote_message("flushed").then(() => {
+ openAltChannel();
+ });
+ }
+}
+
+function openAltChannel() {
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ "dummy1",
+ "text/javascript",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ cc.preferAlternativeDataType(
+ altContentType,
+ "text/plain",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ cc.preferAlternativeDataType("dummy2", "", Ci.nsICacheInfoChannel.ASYNC);
+
+ chan.asyncOpen(new ChannelListener(readAltContent, null));
+}
+
+function readAltContent(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(servedNotModified, true);
+ Assert.equal(cc.alternativeDataType, altContentType);
+ Assert.equal(buffer, altContent);
+ check_has_alt_data_in_index(true, () => {
+ cc.getOriginalInputStream({
+ onInputStreamReady(aInputStream) {
+ executeSoon(() => readOriginalInputStream(aInputStream));
+ },
+ });
+ });
+}
+
+function readOriginalInputStream(aInputStream) {
+ // We expect the async stream length to match the expected content.
+ // If the test times out, it's probably because of this.
+ try {
+ let originalData = read_stream(aInputStream, responseContent.length);
+ Assert.equal(originalData, responseContent);
+ requestAgain();
+ } catch (e) {
+ equal(e.result, Cr.NS_BASE_STREAM_WOULD_BLOCK);
+ executeSoon(() => readOriginalInputStream(aInputStream));
+ }
+}
+
+function requestAgain() {
+ shouldPassRevalidation = false;
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ chan.asyncOpen(new ChannelListener(readEmptyAltContent, null));
+}
+
+function readEmptyAltContent(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ // the cache is overwrite and the alt-data is reset
+ Assert.equal(cc.alternativeDataType, "");
+ Assert.equal(buffer, responseContent2);
+ check_has_alt_data_in_index(false, () => httpServer.stop(do_test_finished));
+}
diff --git a/netwerk/test/unit/test_alt-data_stream.js b/netwerk/test/unit/test_alt-data_stream.js
new file mode 100644
index 0000000000..9b6bfd050a
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_stream.js
@@ -0,0 +1,163 @@
+/**
+ * Test for the "alternative data stream" stored withing a cache entry.
+ *
+ * - we load a URL with preference for an alt data (check what we get is the raw data,
+ * since there was nothing previously cached)
+ * - we write a big chunk of alt-data to the output stream
+ * - we load the URL again, expecting to get alt-data
+ * - we check that the alt-data is streamed. We should get the first chunk, then
+ * the rest of the alt-data is written, and we check that it is received in
+ * the proper order.
+ *
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseContent = "response body";
+// We need a large content in order to make sure that the IPDL stream is cut
+// into several different chunks.
+// We fill each chunk with a different character for easy debugging.
+const altContent =
+ "a".repeat(128 * 1024) +
+ "b".repeat(128 * 1024) +
+ "c".repeat(128 * 1024) +
+ "d".repeat(128 * 1024) +
+ "e".repeat(128 * 1024) +
+ "f".repeat(128 * 1024) +
+ "g".repeat(128 * 1024) +
+ "h".repeat(128 * 1024) +
+ "i".repeat(13); // Just so the chunk size doesn't match exactly.
+
+const firstChunkSize = Math.floor(altContent.length / 4);
+const altContentType = "text/binary";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "max-age=86400");
+
+ response.bodyOutputStream.write(responseContent, responseContent.length);
+}
+
+function run_test() {
+ do_get_profile();
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(URL);
+
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+
+ chan.asyncOpen(new ChannelListener(readServerContent, null));
+ do_test_pending();
+}
+
+// Output stream used to write alt-data to the cache entry.
+var os;
+
+function readServerContent(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(buffer, responseContent);
+ Assert.equal(cc.alternativeDataType, "");
+
+ executeSoon(() => {
+ os = cc.openAlternativeOutputStream(altContentType, altContent.length);
+ // Write a quarter of the alt data content
+ os.write(altContent, firstChunkSize);
+
+ executeSoon(openAltChannel);
+ });
+}
+
+function openAltChannel() {
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+
+ chan.asyncOpen(altDataListener);
+}
+
+var altDataListener = {
+ buffer: "",
+ onStartRequest(request) {},
+ onDataAvailable(request, stream, offset, count) {
+ let string = NetUtil.readInputStreamToString(stream, count);
+ this.buffer += string;
+
+ // XXX: this condition might be a bit volatile. If this test times out,
+ // it probably means that for some reason, the listener didn't get all the
+ // data in the first chunk.
+ if (this.buffer.length == firstChunkSize) {
+ // write the rest of the content
+ os.write(
+ altContent.substring(firstChunkSize, altContent.length),
+ altContent.length - firstChunkSize
+ );
+ os.close();
+ }
+ },
+ onStopRequest(request, status) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+ Assert.equal(cc.alternativeDataType, altContentType);
+ Assert.equal(this.buffer.length, altContent.length);
+ Assert.equal(this.buffer, altContent);
+ openAltChannelWithOriginalContent();
+ },
+};
+
+function openAltChannelWithOriginalContent() {
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.SERIALIZE
+ );
+
+ chan.asyncOpen(originalListener);
+}
+
+var originalListener = {
+ buffer: "",
+ onStartRequest(request) {},
+ onDataAvailable(request, stream, offset, count) {
+ let string = NetUtil.readInputStreamToString(stream, count);
+ this.buffer += string;
+ },
+ onStopRequest(request, status) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+ Assert.equal(cc.alternativeDataType, altContentType);
+ Assert.equal(this.buffer.length, responseContent.length);
+ Assert.equal(this.buffer, responseContent);
+ testAltDataStream(cc);
+ },
+};
+
+function testAltDataStream(cc) {
+ Assert.ok(!!cc.alternativeDataInputStream);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_alt-data_too_big.js b/netwerk/test/unit/test_alt-data_too_big.js
new file mode 100644
index 0000000000..fc9ca337fa
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_too_big.js
@@ -0,0 +1,113 @@
+/**
+ * Test for handling too big alternative data
+ *
+ * - first we try to open an output stream for too big alt-data which must fail
+ * and leave original data intact
+ *
+ * - then we open the output stream without passing predicted data size which
+ * succeeds but writing must fail later at the size limit and the original
+ * data must be kept
+ */
+
+"use strict";
+
+var data = "data ";
+var altData = "alt-data";
+
+function run_test() {
+ do_get_profile();
+
+ // Expand both data to 1MB
+ for (let i = 0; i < 17; i++) {
+ data += data;
+ altData += altData;
+ }
+
+ // Set the limit so that the data fits but alt-data doesn't.
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1800);
+
+ write_data();
+
+ do_test_pending();
+}
+
+function write_data() {
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+
+ var os = entry.openOutputStream(0, -1);
+ var written = os.write(data, data.length);
+ Assert.equal(written, data.length);
+ os.close();
+
+ open_big_altdata_output(entry);
+ }
+ );
+}
+
+function open_big_altdata_output(entry) {
+ try {
+ entry.openAlternativeOutputStream("text/binary", altData.length);
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_FILE_TOO_BIG);
+ }
+ entry.close();
+
+ check_entry(write_big_altdata);
+}
+
+function write_big_altdata() {
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+
+ var os = entry.openAlternativeOutputStream("text/binary", -1);
+ try {
+ os.write(altData, altData.length);
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_FILE_TOO_BIG);
+ }
+ os.close();
+ entry.close();
+
+ check_entry(do_test_finished);
+ }
+ );
+}
+
+function check_entry(cb) {
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+
+ var is = null;
+ try {
+ is = entry.openAlternativeInputStream("text/binary");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ is = entry.openInputStream(0);
+ pumpReadStream(is, function (read) {
+ Assert.equal(read.length, data.length);
+ is.close();
+ entry.close();
+
+ executeSoon(cb);
+ });
+ }
+ );
+}
diff --git a/netwerk/test/unit/test_altsvc.js b/netwerk/test/unit/test_altsvc.js
new file mode 100644
index 0000000000..ea9ac3ade0
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc.js
@@ -0,0 +1,597 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var h2Port;
+var prefs;
+var http2pref;
+var altsvcpref1;
+var altsvcpref2;
+
+// https://foo.example.com:(h2Port)
+// https://bar.example.com:(h2Port) <- invalid for bar, but ok for foo
+var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort)
+var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort)
+
+var otherServer; // server socket listening for other connection.
+
+var h2FooRoute; // foo.example.com:H2PORT
+var h2BarRoute; // bar.example.com:H2PORT
+var h2Route; // :H2PORT
+var httpFooOrigin; // http://foo.exmaple.com:PORT/
+var httpsFooOrigin; // https://foo.exmaple.com:PORT/
+var httpBarOrigin; // http://bar.example.com:PORT/
+var httpsBarOrigin; // https://bar.example.com:PORT/
+
+function run_test() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Services.prefs;
+
+ http2pref = prefs.getBoolPref("network.http.http2.enabled");
+ altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
+ altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
+
+ prefs.setBoolPref("network.http.http2.enabled", true);
+ prefs.setBoolPref("network.http.altsvc.enabled", true);
+ prefs.setBoolPref("network.http.altsvc.oe", true);
+ prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, bar.example.com"
+ );
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. The same cert is used
+ // for both h2FooRoute and h2BarRoute though it is only valid for
+ // the foo.example.com domain name.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ h1Foo = new HttpServer();
+ h1Foo.registerPathHandler("/altsvc-test", h1Server);
+ h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
+ h1Foo.start(-1);
+ h1Foo.identity.setPrimary(
+ "http",
+ "foo.example.com",
+ h1Foo.identity.primaryPort
+ );
+
+ h1Bar = new HttpServer();
+ h1Bar.registerPathHandler("/altsvc-test", h1Server);
+ h1Bar.start(-1);
+ h1Bar.identity.setPrimary(
+ "http",
+ "bar.example.com",
+ h1Bar.identity.primaryPort
+ );
+
+ h2FooRoute = "foo.example.com:" + h2Port;
+ h2BarRoute = "bar.example.com:" + h2Port;
+ h2Route = ":" + h2Port;
+
+ httpFooOrigin = "http://foo.example.com:" + h1Foo.identity.primaryPort + "/";
+ httpsFooOrigin = "https://" + h2FooRoute + "/";
+ httpBarOrigin = "http://bar.example.com:" + h1Bar.identity.primaryPort + "/";
+ httpsBarOrigin = "https://" + h2BarRoute + "/";
+ dump(
+ "http foo - " +
+ httpFooOrigin +
+ "\n" +
+ "https foo - " +
+ httpsFooOrigin +
+ "\n" +
+ "http bar - " +
+ httpBarOrigin +
+ "\n" +
+ "https bar - " +
+ httpsBarOrigin +
+ "\n"
+ );
+
+ doTest1();
+}
+
+function h1Server(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ try {
+ var hval = "h2=" + metadata.getHeader("x-altsvc");
+ response.setHeader("Alt-Svc", hval, false);
+ } catch (e) {}
+
+ var body = "Q: What did 0 say to 8? A: Nice Belt!\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ var body = '["http://foo.example.com:' + h1Foo.identity.primaryPort + '"]';
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function resetPrefs() {
+ prefs.setBoolPref("network.http.http2.enabled", http2pref);
+ prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
+ prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.security.ports.banned");
+}
+
+function makeChan(origin) {
+ return NetUtil.newChannel({
+ uri: origin + "altsvc-test",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var origin;
+var xaltsvc;
+var loadWithoutClearingMappings = false;
+var disallowH3 = false;
+var disallowH2 = false;
+var nextTest;
+var expectPass = true;
+var waitFor = 0;
+var originAttributes = {};
+
+var Listener = function () {};
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ if (expectPass) {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw(
+ "Channel should have a success code! (" + request.status + ")"
+ );
+ }
+ Assert.equal(request.responseStatus, 200);
+ } else {
+ Assert.equal(Components.isSuccessCode(request.status), false);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ var routed = "";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+ Assert.equal(Components.isSuccessCode(status), expectPass);
+
+ if (waitFor != 0) {
+ Assert.equal(routed, "");
+ do_test_pending();
+ loadWithoutClearingMappings = true;
+ do_timeout(waitFor, doTest);
+ waitFor = 0;
+ xaltsvc = "NA";
+ } else if (xaltsvc == "NA") {
+ Assert.equal(routed, "");
+ nextTest();
+ } else if (routed == xaltsvc) {
+ Assert.equal(routed, xaltsvc); // always true, but a useful log
+ nextTest();
+ } else {
+ dump("poll later for alt svc mapping\n");
+ do_test_pending();
+ loadWithoutClearingMappings = true;
+ do_timeout(500, doTest);
+ }
+
+ do_test_finished();
+ },
+};
+
+function testsDone() {
+ dump("testDone\n");
+ resetPrefs();
+ do_test_pending();
+ otherServer.close();
+ do_test_pending();
+ h1Foo.stop(do_test_finished);
+ do_test_pending();
+ h1Bar.stop(do_test_finished);
+}
+
+function doTest() {
+ dump("execute doTest " + origin + "\n");
+ var chan = makeChan(origin);
+ var listener = new Listener();
+ if (xaltsvc != "NA") {
+ chan.setRequestHeader("x-altsvc", xaltsvc, false);
+ }
+ if (loadWithoutClearingMappings) {
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ } else {
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ }
+ if (disallowH3) {
+ let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.allowHttp3 = false;
+ disallowH3 = false;
+ }
+ if (disallowH2) {
+ let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.allowSpdy = false;
+ disallowH2 = false;
+ }
+ loadWithoutClearingMappings = false;
+ chan.loadInfo.originAttributes = originAttributes;
+ chan.asyncOpen(listener);
+}
+
+// xaltsvc is overloaded to do two things..
+// 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header
+// 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route)
+//
+// When xaltsvc is set to h2Route (i.e. :port with the implied hostname) it doesn't match the alt-used,
+// which is always explicit, so it needs to be changed after the channel is created but before the
+// listener is invoked
+
+// http://foo served from h2=:port
+function doTest1() {
+ dump("doTest1()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2Route;
+ nextTest = doTest2;
+ do_test_pending();
+ doTest();
+ xaltsvc = h2FooRoute;
+}
+
+// http://foo served from h2=foo:port
+function doTest2() {
+ dump("doTest2()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2FooRoute;
+ nextTest = doTest3;
+ do_test_pending();
+ doTest();
+}
+
+// http://foo served from h2=bar:port
+// requires cert for foo
+function doTest3() {
+ dump("doTest3()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2BarRoute;
+ nextTest = doTest4;
+ do_test_pending();
+ doTest();
+}
+
+// https://bar should fail because host bar has cert for foo
+function doTest4() {
+ dump("doTest4()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = "";
+ expectPass = false;
+ nextTest = doTest5;
+ do_test_pending();
+ doTest();
+}
+
+// https://foo no alt-svc (just check cert setup)
+function doTest5() {
+ dump("doTest5()\n");
+ origin = httpsFooOrigin;
+ xaltsvc = "NA";
+ expectPass = true;
+ nextTest = doTest6;
+ do_test_pending();
+ doTest();
+}
+
+// https://foo via bar (bar has cert for foo)
+function doTest6() {
+ dump("doTest6()\n");
+ origin = httpsFooOrigin;
+ xaltsvc = h2BarRoute;
+ nextTest = doTest7;
+ do_test_pending();
+ doTest();
+}
+
+// check again https://bar should fail because host bar has cert for foo
+function doTest7() {
+ dump("doTest7()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = "";
+ expectPass = false;
+ nextTest = doTest8;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar via h2 on bar
+// should not use TLS/h2 because h2BarRoute is not auth'd for bar
+// however the test ought to PASS (i.e. get a 200) because fallback
+// to plaintext happens.. thus the timeout
+function doTest8() {
+ dump("doTest8()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h2BarRoute;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest9;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar served from h2=:port, which is like the bar route in 8
+function doTest9() {
+ dump("doTest9()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h2Route;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest10;
+ do_test_pending();
+ doTest();
+ xaltsvc = h2BarRoute;
+}
+
+// check again https://bar should fail because host bar has cert for foo
+function doTest10() {
+ dump("doTest10()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = "";
+ expectPass = false;
+ nextTest = doTest11;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar served from h2=foo, should fail because host foo only has
+// cert for foo. Fail in this case means alt-svc is not used, but content
+// is served
+function doTest11() {
+ dump("doTest11()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h2FooRoute;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest12;
+ do_test_pending();
+ doTest();
+}
+
+// Test 12-15:
+// Insert a cache of http://foo served from h2=:port with origin attributes.
+function doTest12() {
+ dump("doTest12()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2Route;
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ nextTest = doTest13;
+ do_test_pending();
+ doTest();
+ xaltsvc = h2FooRoute;
+}
+
+// Make sure we get a cache miss with a different userContextId.
+function doTest13() {
+ dump("doTest13()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ originAttributes = {
+ userContextId: 2,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest14;
+ do_test_pending();
+ doTest();
+}
+
+// Make sure we get a cache miss with a different firstPartyDomain.
+function doTest14() {
+ dump("doTest14()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "b.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest15;
+ do_test_pending();
+ doTest();
+}
+//
+// Make sure we get a cache hit with the same origin attributes.
+function doTest15() {
+ dump("doTest15()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest16;
+ do_test_pending();
+ doTest();
+ // This ensures a cache hit.
+ xaltsvc = h2FooRoute;
+}
+
+// Make sure we do not use H2 if it is disabled on a channel.
+function doTest16() {
+ dump("doTest16()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ disallowH2 = true;
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest17;
+ do_test_pending();
+ doTest();
+}
+
+// Make sure we use H2 if only Http3 is disabled on a channel.
+function doTest17() {
+ dump("doTest17()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2Route;
+ disallowH3 = true;
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest18;
+ do_test_pending();
+ doTest();
+ // This should ensures a cache hit.
+ xaltsvc = h2FooRoute;
+}
+
+// Check we don't connect to blocked ports
+function doTest18() {
+ dump("doTest18()\n");
+ origin = httpFooOrigin;
+ nextTest = testsDone;
+ otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ otherServer.init(-1, true, -1);
+ xaltsvc = "localhost:" + otherServer.port;
+ Services.prefs.setCharPref(
+ "network.security.ports.banned",
+ "" + otherServer.port
+ );
+ dump("Blocked port: " + otherServer.port);
+ waitFor = 500;
+ otherServer.asyncListen({
+ onSocketAccepted() {
+ Assert.ok(false, "Got connection to socket when we didn't expect it!");
+ },
+ onStopListening() {
+ // We get closed when the entire file is done, which guarantees we get the socket accept
+ // if we do connect to the alt-svc header
+ do_test_finished();
+ },
+ });
+ nextTest = doTest19;
+ do_test_pending();
+ doTest();
+}
+
+// Check we don't connect to blocked ports
+function doTest19() {
+ dump("doTest19()\n");
+ origin = httpFooOrigin;
+ nextTest = testsDone;
+ otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ const BAD_PORT_U32 = 6667 + 65536;
+ otherServer.init(BAD_PORT_U32, true, -1);
+ Assert.ok(otherServer.port == 6667, "Trying to listen on port 6667");
+ xaltsvc = "localhost:" + BAD_PORT_U32;
+ dump("Blocked port: " + otherServer.port);
+ waitFor = 500;
+ otherServer.asyncListen({
+ onSocketAccepted() {
+ Assert.ok(false, "Got connection to socket when we didn't expect it!");
+ },
+ onStopListening() {
+ // We get closed when the entire file is done, which guarantees we get the socket accept
+ // if we do connect to the alt-svc header
+ do_test_finished();
+ },
+ });
+ nextTest = doTest20;
+ do_test_pending();
+ doTest();
+}
+function doTest20() {
+ dump("doTest20()\n");
+ origin = httpFooOrigin;
+ nextTest = testsDone;
+ otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ const BAD_PORT_U64 = 6666 + 429496729;
+ otherServer.init(6666, true, -1);
+ Assert.ok(otherServer.port == 6666, "Trying to listen on port 6666");
+ xaltsvc = "localhost:" + BAD_PORT_U64;
+ dump("Blocked port: " + otherServer.port);
+ waitFor = 500;
+ otherServer.asyncListen({
+ onSocketAccepted() {
+ Assert.ok(false, "Got connection to socket when we didn't expect it!");
+ },
+ onStopListening() {
+ // We get closed when the entire file is done, which guarantees we get the socket accept
+ // if we do connect to the alt-svc header
+ do_test_finished();
+ },
+ });
+ nextTest = doTest21;
+ do_test_pending();
+ doTest();
+}
+// Port 65535 should be OK
+function doTest21() {
+ dump("doTest21()\n");
+ origin = httpFooOrigin;
+ nextTest = testsDone;
+ otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ const GOOD_PORT = 65535;
+ otherServer.init(65535, true, -1);
+ Assert.ok(otherServer.port == 65535, "Trying to listen on port 65535");
+ xaltsvc = "localhost:" + GOOD_PORT;
+ dump("Allowed port: " + otherServer.port);
+ waitFor = 500;
+ otherServer.asyncListen({
+ onSocketAccepted() {
+ Assert.ok(true, "Got connection to socket when we didn't expect it!");
+ },
+ onStopListening() {
+ // We get closed when the entire file is done, which guarantees we get the socket accept
+ // if we do connect to the alt-svc header
+ do_test_finished();
+ },
+ });
+ do_test_pending();
+ doTest();
+}
diff --git a/netwerk/test/unit/test_altsvc_http3.js b/netwerk/test/unit/test_altsvc_http3.js
new file mode 100644
index 0000000000..c80afdf116
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc_http3.js
@@ -0,0 +1,494 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var h3Port;
+
+// https://foo.example.com:(h3Port)
+// https://bar.example.com:(h3Port) <- invalid for bar, but ok for foo
+var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort)
+var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort)
+
+var otherServer; // server socket listening for other connection.
+
+var h3FooRoute; // foo.example.com:H3PORT
+var h3BarRoute; // bar.example.com:H3PORT
+var h3Route; // :H3PORT
+var httpFooOrigin; // http://foo.exmaple.com:PORT/
+var httpsFooOrigin; // https://foo.exmaple.com:PORT/
+var httpBarOrigin; // http://bar.example.com:PORT/
+var httpsBarOrigin; // https://bar.example.com:PORT/
+
+function run_test() {
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ // Set to allow the cert presented by our H3 server
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setBoolPref("network.http.altsvc.enabled", true);
+ Services.prefs.setBoolPref("network.http.altsvc.oe", true);
+ Services.prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, bar.example.com"
+ );
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. The same cert is used
+ // for both h3FooRoute and h3BarRoute though it is only valid for
+ // the foo.example.com domain name.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ h1Foo = new HttpServer();
+ h1Foo.registerPathHandler("/altsvc-test", h1Server);
+ h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
+ h1Foo.start(-1);
+ h1Foo.identity.setPrimary(
+ "http",
+ "foo.example.com",
+ h1Foo.identity.primaryPort
+ );
+
+ h1Bar = new HttpServer();
+ h1Bar.registerPathHandler("/altsvc-test", h1Server);
+ h1Bar.start(-1);
+ h1Bar.identity.setPrimary(
+ "http",
+ "bar.example.com",
+ h1Bar.identity.primaryPort
+ );
+
+ h3FooRoute = "foo.example.com:" + h3Port;
+ h3BarRoute = "bar.example.com:" + h3Port;
+ h3Route = ":" + h3Port;
+
+ httpFooOrigin = "http://foo.example.com:" + h1Foo.identity.primaryPort + "/";
+ httpsFooOrigin = "https://" + h3FooRoute + "/";
+ httpBarOrigin = "http://bar.example.com:" + h1Bar.identity.primaryPort + "/";
+ httpsBarOrigin = "https://" + h3BarRoute + "/";
+ dump(
+ "http foo - " +
+ httpFooOrigin +
+ "\n" +
+ "https foo - " +
+ httpsFooOrigin +
+ "\n" +
+ "http bar - " +
+ httpBarOrigin +
+ "\n" +
+ "https bar - " +
+ httpsBarOrigin +
+ "\n"
+ );
+
+ doTest1();
+}
+
+function h1Server(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ try {
+ var hval = "h3-29=" + metadata.getHeader("x-altsvc");
+ response.setHeader("Alt-Svc", hval, false);
+ } catch (e) {}
+
+ var body = "Q: What did 0 say to 8? A: Nice Belt!\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ var body = '["http://foo.example.com:' + h1Foo.identity.primaryPort + '"]';
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function resetPrefs() {
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.http.altsvc.enabled");
+ Services.prefs.clearUserPref("network.http.altsvc.oe");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.security.ports.banned");
+}
+
+function makeChan(origin) {
+ return NetUtil.newChannel({
+ uri: origin + "altsvc-test",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var origin;
+var xaltsvc;
+var loadWithoutClearingMappings = false;
+var disallowH3 = false;
+var disallowH2 = false;
+var testKeepAliveNotSet = false;
+var nextTest;
+var expectPass = true;
+var waitFor = 0;
+var originAttributes = {};
+
+var Listener = function () {};
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ if (expectPass) {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw(
+ "Channel should have a success code! (" + request.status + ")"
+ );
+ }
+ Assert.equal(request.responseStatus, 200);
+ } else {
+ Assert.equal(Components.isSuccessCode(request.status), false);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ var routed = "";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+ Assert.equal(Components.isSuccessCode(status), expectPass);
+
+ if (waitFor != 0) {
+ Assert.equal(routed, "");
+ do_test_pending();
+ loadWithoutClearingMappings = true;
+ do_timeout(waitFor, doTest);
+ waitFor = 0;
+ xaltsvc = "NA";
+ } else if (xaltsvc == "NA") {
+ Assert.equal(routed, "");
+ nextTest();
+ } else if (routed == xaltsvc) {
+ Assert.equal(routed, xaltsvc); // always true, but a useful log
+ nextTest();
+ } else {
+ dump("poll later for alt svc mapping\n");
+ do_test_pending();
+ loadWithoutClearingMappings = true;
+ do_timeout(500, doTest);
+ }
+
+ do_test_finished();
+ },
+};
+
+function testsDone() {
+ dump("testDone\n");
+ resetPrefs();
+ do_test_pending();
+ otherServer.close();
+ do_test_pending();
+ h1Foo.stop(do_test_finished);
+ do_test_pending();
+ h1Bar.stop(do_test_finished);
+}
+
+function doTest() {
+ dump("execute doTest " + origin + "\n");
+ var chan = makeChan(origin);
+ var listener = new Listener();
+ if (xaltsvc != "NA") {
+ chan.setRequestHeader("x-altsvc", xaltsvc, false);
+ }
+ if (testKeepAliveNotSet) {
+ chan.setRequestHeader("Connection", "close", false);
+ testKeepAliveNotSet = false;
+ }
+ if (loadWithoutClearingMappings) {
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ } else {
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ }
+ if (disallowH3) {
+ let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.allowHttp3 = false;
+ disallowH3 = false;
+ }
+ if (disallowH2) {
+ let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.allowSpdy = false;
+ disallowH2 = false;
+ }
+ loadWithoutClearingMappings = false;
+ chan.loadInfo.originAttributes = originAttributes;
+ chan.asyncOpen(listener);
+}
+
+// xaltsvc is overloaded to do two things..
+// 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header
+// 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route)
+//
+// When xaltsvc is set to h3Route (i.e. :port with the implied hostname) it doesn't match the alt-used,
+// which is always explicit, so it needs to be changed after the channel is created but before the
+// listener is invoked
+
+// http://foo served from h3-29=:port
+function doTest1() {
+ dump("doTest1()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h3Route;
+ nextTest = doTest2;
+ do_test_pending();
+ doTest();
+ xaltsvc = h3FooRoute;
+}
+
+// http://foo served from h3-29=foo:port
+function doTest2() {
+ dump("doTest2()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h3FooRoute;
+ nextTest = doTest3;
+ do_test_pending();
+ doTest();
+}
+
+// http://foo served from h3-29=bar:port
+// requires cert for foo
+function doTest3() {
+ dump("doTest3()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h3BarRoute;
+ nextTest = doTest4;
+ do_test_pending();
+ doTest();
+}
+
+// https://bar should fail because host bar has cert for foo
+function doTest4() {
+ dump("doTest4()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = "";
+ expectPass = false;
+ nextTest = doTest5;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar via h3 on bar
+// should not use TLS/h3 because h3BarRoute is not auth'd for bar
+// however the test ought to PASS (i.e. get a 200) because fallback
+// to plaintext happens.. thus the timeout
+function doTest5() {
+ dump("doTest5()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h3BarRoute;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest6;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar served from h3-29=:port, which is like the bar route in 8
+function doTest6() {
+ dump("doTest6()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h3Route;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest7;
+ do_test_pending();
+ doTest();
+ xaltsvc = h3BarRoute;
+}
+
+// check again https://bar should fail because host bar has cert for foo
+function doTest7() {
+ dump("doTest7()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = "";
+ expectPass = false;
+ nextTest = doTest8;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar served from h3-29=foo, should fail because host foo only has
+// cert for foo. Fail in this case means alt-svc is not used, but content
+// is served
+function doTest8() {
+ dump("doTest8()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h3FooRoute;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest9;
+ do_test_pending();
+ doTest();
+}
+
+// Test 9-12:
+// Insert a cache of http://foo served from h3-29=:port with origin attributes.
+function doTest9() {
+ dump("doTest9()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h3Route;
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ nextTest = doTest10;
+ do_test_pending();
+ doTest();
+ xaltsvc = h3FooRoute;
+}
+
+// Make sure we get a cache miss with a different userContextId.
+function doTest10() {
+ dump("doTest10()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ originAttributes = {
+ userContextId: 2,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest11;
+ do_test_pending();
+ doTest();
+}
+
+// Make sure we get a cache miss with a different firstPartyDomain.
+function doTest11() {
+ dump("doTest11()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "b.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest12;
+ do_test_pending();
+ doTest();
+}
+//
+// Make sure we get a cache hit with the same origin attributes.
+function doTest12() {
+ dump("doTest12()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest13;
+ do_test_pending();
+ doTest();
+ // This ensures a cache hit.
+ xaltsvc = h3FooRoute;
+}
+
+// Make sure we do not use H3 if it is disabled on a channel.
+function doTest13() {
+ dump("doTest13()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ disallowH3 = true;
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest14;
+ do_test_pending();
+ doTest();
+}
+
+// Make sure we use H3 if only Http2 is disabled on a channel.
+function doTest14() {
+ dump("doTest14()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ disallowH2 = true;
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest15;
+ do_test_pending();
+ doTest();
+ // This should ensures a cache hit.
+ xaltsvc = h3FooRoute;
+}
+
+// Make sure we do not use H3 if NS_HTTP_ALLOW_KEEPALIVE is not set.
+function doTest15() {
+ dump("doTest15()\n");
+ origin = httpFooOrigin;
+ xaltsvc = "NA";
+ testKeepAliveNotSet = true;
+ originAttributes = {
+ userContextId: 1,
+ firstPartyDomain: "a.com",
+ };
+ loadWithoutClearingMappings = true;
+ nextTest = doTest16;
+ do_test_pending();
+ doTest();
+}
+
+// Check we don't connect to blocked ports
+function doTest16() {
+ dump("doTest16()\n");
+ origin = httpFooOrigin;
+ nextTest = testsDone;
+ otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ otherServer.init(-1, true, -1);
+ xaltsvc = "localhost:" + otherServer.port;
+ Services.prefs.setCharPref(
+ "network.security.ports.banned",
+ "" + otherServer.port
+ );
+ dump("Blocked port: " + otherServer.port);
+ waitFor = 500;
+ otherServer.asyncListen({
+ onSocketAccepted() {
+ Assert.ok(false, "Got connection to socket when we didn't expect it!");
+ },
+ onStopListening() {
+ // We get closed when the entire file is done, which guarantees we get the socket accept
+ // if we do connect to the alt-svc header
+ do_test_finished();
+ },
+ });
+ do_test_pending();
+ doTest();
+}
diff --git a/netwerk/test/unit/test_altsvc_pref.js b/netwerk/test/unit/test_altsvc_pref.js
new file mode 100644
index 0000000000..3e6d3289f4
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc_pref.js
@@ -0,0 +1,136 @@
+"use strict";
+
+let h3Port;
+let h3Route;
+let h3AltSvc;
+let prefs;
+let httpsOrigin;
+
+let tests = [
+ // The altSvc storage may not be up imediately, therefore run test_no_altsvc_pref
+ // for a couple times to wait for the storage.
+ test_no_altsvc_pref,
+ test_no_altsvc_pref,
+ test_no_altsvc_pref,
+ test_altsvc_pref,
+ testsDone,
+];
+
+let current_test = 0;
+
+function run_next_test() {
+ if (current_test < tests.length) {
+ dump("starting test number " + current_test + "\n");
+ tests[current_test]();
+ current_test++;
+ }
+}
+
+function run_test() {
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Services.prefs;
+
+ prefs.setBoolPref("network.http.http3.enable", true);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ prefs.setBoolPref("network.dns.disableIPv6", true);
+
+ // The certificate for the http3server server is for foo.example.com and
+ // is signed by http2-ca.pem so add that cert to the trust list as a
+ // signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ httpsOrigin = "https://foo.example.com/";
+
+ run_next_test();
+}
+
+let Http3CheckListener = function () {};
+
+Http3CheckListener.prototype = {
+ expectedRoute: "",
+ expectedStatus: Cr.NS_OK,
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.status, this.expectedStatus);
+ if (Components.isSuccessCode(this.expectedStatus)) {
+ Assert.equal(request.responseStatus, 200);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, this.expectedStatus);
+ if (Components.isSuccessCode(this.expectedStatus)) {
+ Assert.equal(request.responseStatus, 200);
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ Assert.equal(routed, this.expectedRoute);
+
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3");
+ }
+
+ do_test_finished();
+ },
+};
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function test_no_altsvc_pref() {
+ dump("test_no_altsvc_pref");
+ do_test_pending();
+
+ let chan = makeChan(httpsOrigin + "http3-test");
+ let listener = new Http3CheckListener();
+ listener.expectedStatus = Cr.NS_ERROR_CONNECTION_REFUSED;
+ chan.asyncOpen(listener);
+}
+
+function test_altsvc_pref() {
+ dump("test_altsvc_pref");
+ do_test_pending();
+
+ prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "foo.example.com;h3-29=" + h3AltSvc
+ );
+
+ let chan = makeChan(httpsOrigin + "http3-test");
+ let listener = new Http3CheckListener();
+ listener.expectedRoute = h3Route;
+ chan.asyncOpen(listener);
+}
+
+function testsDone() {
+ prefs.clearUserPref("network.http.http3.enable");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.dns.disableIPv6");
+ prefs.clearUserPref("network.http.http3.alt-svc-mapping-for-testing");
+ dump("testDone\n");
+}
diff --git a/netwerk/test/unit/test_anonymous-coalescing.js b/netwerk/test/unit/test_anonymous-coalescing.js
new file mode 100644
index 0000000000..c46dbfe52b
--- /dev/null
+++ b/netwerk/test/unit/test_anonymous-coalescing.js
@@ -0,0 +1,179 @@
+/*
+- test to check we use only a single connection for both onymous and anonymous requests over an existing h2 session
+- request from a domain w/o LOAD_ANONYMOUS flag
+- request again from the same domain, but different URI, with LOAD_ANONYMOUS flag, check the client is using the same conn
+- close all and do it in the opposite way (do an anonymous req first)
+*/
+
+"use strict";
+
+var h2Port;
+var prefs;
+var http2pref;
+var extpref;
+
+function run_test() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Services.prefs;
+
+ http2pref = prefs.getBoolPref("network.http.http2.enabled");
+ extpref = prefs.getBoolPref("network.http.originextension");
+
+ prefs.setBoolPref("network.http.http2.enabled", true);
+ prefs.setBoolPref("network.http.originextension", true);
+ prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, alt1.example.com"
+ );
+
+ // The moz-http2 cert is for {foo, alt1, alt2}.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ doTest1();
+}
+
+function resetPrefs() {
+ prefs.setBoolPref("network.http.http2.enabled", http2pref);
+ prefs.setBoolPref("network.http.originextension", extpref);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function makeChan(origin) {
+ return NetUtil.newChannel({
+ uri: origin,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var nextTest;
+var origin;
+var nextPortExpectedToBeSame = false;
+var currentPort = 0;
+var forceReload = false;
+var anonymous = false;
+
+var Listener = function () {};
+Listener.prototype.clientPort = 0;
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+ Assert.equal(request.responseStatus, 200);
+ this.clientPort = parseInt(request.getResponseHeader("x-client-port"));
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(Components.isSuccessCode(status));
+ if (nextPortExpectedToBeSame) {
+ Assert.equal(currentPort, this.clientPort);
+ } else {
+ Assert.notEqual(currentPort, this.clientPort);
+ }
+ currentPort = this.clientPort;
+ nextTest();
+ do_test_finished();
+ },
+};
+
+function testsDone() {
+ dump("testsDone\n");
+ resetPrefs();
+}
+
+function doTest() {
+ dump("execute doTest " + origin + "\n");
+
+ var loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ if (anonymous) {
+ loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
+ }
+ anonymous = false;
+ if (forceReload) {
+ loadFlags |= Ci.nsIRequest.LOAD_FRESH_CONNECTION;
+ }
+ forceReload = false;
+
+ var chan = makeChan(origin);
+ chan.loadFlags = loadFlags;
+
+ var listener = new Listener();
+ chan.asyncOpen(listener);
+}
+
+function doTest1() {
+ dump("doTest1()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-1";
+ nextTest = doTest2;
+ nextPortExpectedToBeSame = false;
+ do_test_pending();
+ doTest();
+}
+
+function doTest2() {
+ // Run the same test as above to make sure connection is marked experienced.
+ dump("doTest2()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-1";
+ nextTest = doTest3;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest3() {
+ // connection expected to be reused for an anonymous request
+ dump("doTest3()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-2";
+ nextTest = doTest4;
+ nextPortExpectedToBeSame = true;
+ anonymous = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest4() {
+ dump("doTest4()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-3";
+ nextTest = doTest5;
+ nextPortExpectedToBeSame = false;
+ forceReload = true;
+ anonymous = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest5() {
+ // Run the same test as above just without forceReload to make sure connection
+ // is marked experienced.
+ dump("doTest5()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-3";
+ nextTest = doTest6;
+ nextPortExpectedToBeSame = true;
+ anonymous = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest6() {
+ dump("doTest6()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-4";
+ nextTest = testsDone;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
diff --git a/netwerk/test/unit/test_auth_dialog_permission.js b/netwerk/test/unit/test_auth_dialog_permission.js
new file mode 100644
index 0000000000..a45b651588
--- /dev/null
+++ b/netwerk/test/unit/test_auth_dialog_permission.js
@@ -0,0 +1,280 @@
+// This file tests authentication prompt depending on pref
+// network.auth.subresource-http-auth-allow:
+// 0 - don't allow sub-resources to open HTTP authentication credentials
+// dialogs
+// 1 - allow sub-resources to open HTTP authentication credentials dialogs,
+// but don't allow it for cross-origin sub-resources
+// 2 - allow the cross-origin authentication as well.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var prefs = Services.prefs;
+
+// Since this test creates a TYPE_DOCUMENT channel via javascript, it will
+// end up using the wrong LoadInfo constructor. Setting this pref will disable
+// the ContentPolicyType assertion in the constructor.
+prefs.setBoolPref("network.loadinfo.skip_type_assertion", true);
+
+function authHandler(metadata, response) {
+ // btoa("guest:guest"), but that function is not available here
+ var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ var body;
+ if (
+ metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ response.setHeader("Content-Type", "text/javascript", false);
+
+ body = "success";
+ } else {
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ response.setHeader("Content-Type", "text/javascript", false);
+
+ body = "failed";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var httpserv = new HttpServer();
+httpserv.registerPathHandler("/auth", authHandler);
+httpserv.start(-1);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+function AuthPrompt(promptExpected) {
+ this.promptExpected = promptExpected;
+}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
+
+ prompt(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword(title, text, realm, savePW, user, pw) {
+ Assert.ok(this.promptExpected, "Not expected the authentication prompt.");
+
+ user.value = this.user;
+ pw.value = this.pass;
+ return true;
+ },
+
+ promptPassword(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ },
+};
+
+function Requestor(promptExpected) {
+ this.promptExpected = promptExpected;
+}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt)) {
+ this.prompter = new AuthPrompt(this.promptExpected);
+ return this.prompter;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompter: null,
+};
+
+function make_uri(url) {
+ return Services.io.newURI(url);
+}
+
+function makeChan(loadingUrl, url, contentPolicy) {
+ var uri = make_uri(loadingUrl);
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: contentPolicy,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function Test(
+ subresource_http_auth_allow_pref,
+ loadingUri,
+ uri,
+ contentPolicy,
+ expectedCode
+) {
+ this._subresource_http_auth_allow_pref = subresource_http_auth_allow_pref;
+ this._loadingUri = loadingUri;
+ this._uri = uri;
+ this._contentPolicy = contentPolicy;
+ this._expectedCode = expectedCode;
+}
+
+Test.prototype = {
+ _subresource_http_auth_allow_pref: 1,
+ _loadingUri: null,
+ _uri: null,
+ _contentPolicy: Ci.nsIContentPolicy.TYPE_OTHER,
+ _expectedCode: 200,
+
+ onStartRequest(request) {
+ try {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code!");
+ }
+
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ do_throw("Expecting an HTTP channel");
+ }
+
+ Assert.equal(request.responseStatus, this._expectedCode);
+ // The request should be succeeded iff we expect 200
+ Assert.equal(request.requestSucceeded, this._expectedCode == 200);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_ERROR_ABORT);
+
+ // Clear the auth cache.
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+
+ do_timeout(0, run_next_test);
+ },
+
+ run() {
+ dump(
+ "Run test: " +
+ this._subresource_http_auth_allow_pref +
+ this._loadingUri +
+ this._uri +
+ this._contentPolicy +
+ this._expectedCode +
+ " \n"
+ );
+
+ prefs.setIntPref(
+ "network.auth.subresource-http-auth-allow",
+ this._subresource_http_auth_allow_pref
+ );
+ let chan = makeChan(this._loadingUri, this._uri, this._contentPolicy);
+ chan.notificationCallbacks = new Requestor(this._expectedCode == 200);
+ chan.asyncOpen(this);
+ },
+};
+
+var tests = [
+ // For the next 3 tests the preference is set to 2 - allow the cross-origin
+ // authentication as well.
+
+ // A cross-origin request.
+ new Test(
+ 2,
+ "http://example.com",
+ URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER,
+ 200
+ ),
+ // A non cross-origin sub-resource request.
+ new Test(2, URL + "/", URL + "/auth", Ci.nsIContentPolicy.TYPE_OTHER, 200),
+ // A top level document.
+ new Test(
+ 2,
+ URL + "/auth",
+ URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ 200
+ ),
+
+ // For the next 3 tests the preference is set to 1 - allow sub-resources to
+ // open HTTP authentication credentials dialogs, but don't allow it for
+ // cross-origin sub-resources
+
+ // A cross-origin request.
+ new Test(
+ 1,
+ "http://example.com",
+ URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER,
+ 401
+ ),
+ // A non cross-origin sub-resource request.
+ new Test(1, URL + "/", URL + "/auth", Ci.nsIContentPolicy.TYPE_OTHER, 200),
+ // A top level document.
+ new Test(
+ 1,
+ URL + "/auth",
+ URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ 200
+ ),
+
+ // For the next 3 tests the preference is set to 0 - don't allow sub-resources
+ // to open HTTP authentication credentials dialogs.
+
+ // A cross-origin request.
+ new Test(
+ 0,
+ "http://example.com",
+ URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER,
+ 401
+ ),
+ // A sub-resource request.
+ new Test(0, URL + "/", URL + "/auth", Ci.nsIContentPolicy.TYPE_OTHER, 401),
+ // A top level request.
+ new Test(
+ 0,
+ URL + "/auth",
+ URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ 200
+ ),
+];
+
+function run_next_test() {
+ var nextTest = tests.shift();
+ if (!nextTest) {
+ httpserv.stop(do_test_finished);
+ return;
+ }
+
+ nextTest.run();
+}
+
+function run_test() {
+ do_test_pending();
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_auth_jar.js b/netwerk/test/unit/test_auth_jar.js
new file mode 100644
index 0000000000..a6f1ea257c
--- /dev/null
+++ b/netwerk/test/unit/test_auth_jar.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function createURI(s) {
+ return Services.io.newURI(s);
+}
+
+function run_test() {
+ // Set up a profile.
+ do_get_profile();
+
+ var secMan = Services.scriptSecurityManager;
+ const kURI1 = "http://example.com";
+ var app = secMan.createContentPrincipal(createURI(kURI1), {});
+ var appbrowser = secMan.createContentPrincipal(createURI(kURI1), {
+ inIsolatedMozBrowser: true,
+ });
+
+ var am = Cc["@mozilla.org/network/http-auth-manager;1"].getService(
+ Ci.nsIHttpAuthManager
+ );
+ am.setAuthIdentity(
+ "http",
+ "a.example.com",
+ -1,
+ "basic",
+ "realm",
+ "",
+ "example.com",
+ "user",
+ "pass",
+ false,
+ app
+ );
+ am.setAuthIdentity(
+ "http",
+ "a.example.com",
+ -1,
+ "basic",
+ "realm",
+ "",
+ "example.com",
+ "user3",
+ "pass3",
+ false,
+ appbrowser
+ );
+
+ Services.clearData.deleteDataFromOriginAttributesPattern({
+ inIsolatedMozBrowser: true,
+ });
+
+ var domain = { value: "" },
+ user = { value: "" },
+ pass = { value: "" };
+ try {
+ am.getAuthIdentity(
+ "http",
+ "a.example.com",
+ -1,
+ "basic",
+ "realm",
+ "",
+ domain,
+ user,
+ pass,
+ false,
+ appbrowser
+ );
+ Assert.equal(false, true); // no identity should be present
+ } catch (x) {
+ Assert.equal(domain.value, "");
+ Assert.equal(user.value, "");
+ Assert.equal(pass.value, "");
+ }
+
+ am.getAuthIdentity(
+ "http",
+ "a.example.com",
+ -1,
+ "basic",
+ "realm",
+ "",
+ domain,
+ user,
+ pass,
+ false,
+ app
+ );
+ Assert.equal(domain.value, "example.com");
+ Assert.equal(user.value, "user");
+ Assert.equal(pass.value, "pass");
+}
diff --git a/netwerk/test/unit/test_auth_multiple.js b/netwerk/test/unit/test_auth_multiple.js
new file mode 100644
index 0000000000..3cf039ea7d
--- /dev/null
+++ b/netwerk/test/unit/test_auth_multiple.js
@@ -0,0 +1,464 @@
+// This file tests authentication prompt callbacks
+// TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// Turn off the authentication dialog blocking for this test.
+var prefs = Services.prefs;
+prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+function URL(domain, path = "") {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ return `http://${domain}:${httpserv.identity.primaryPort}/${path}`;
+}
+
+ChromeUtils.defineLazyGetter(this, "PORT", function () {
+ return httpserv.identity.primaryPort;
+});
+
+const FLAG_RETURN_FALSE = 1 << 0;
+const FLAG_WRONG_PASSWORD = 1 << 1;
+const FLAG_BOGUS_USER = 1 << 2;
+// const FLAG_PREVIOUS_FAILED = 1 << 3;
+const CROSS_ORIGIN = 1 << 4;
+// const FLAG_NO_REALM = 1 << 5;
+const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
+
+function AuthPrompt1(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt1.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
+
+ prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword: function ap1_promptUP(
+ title,
+ text,
+ realm,
+ savePW,
+ user,
+ pw
+ ) {
+ if (!(this.flags & CROSS_ORIGIN)) {
+ if (!text.includes(this.expectedRealm)) {
+ do_throw("Text must indicate the realm");
+ }
+ } else if (text.includes(this.expectedRealm)) {
+ do_throw("There should not be realm for cross origin");
+ }
+ if (!text.includes("localhost")) {
+ do_throw("Text must indicate the hostname");
+ }
+ if (!text.includes(String(PORT))) {
+ do_throw("Text must indicate the port");
+ }
+ if (text.includes("-1")) {
+ do_throw("Text must contain negative numbers");
+ }
+
+ if (this.flags & FLAG_RETURN_FALSE) {
+ return false;
+ }
+
+ if (this.flags & FLAG_BOGUS_USER) {
+ this.user = "foo\nbar";
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ this.user = "é";
+ }
+
+ user.value = this.user;
+ if (this.flags & FLAG_WRONG_PASSWORD) {
+ pw.value = this.pass + ".wrong";
+ // Now clear the flag to avoid an infinite loop
+ this.flags &= ~FLAG_WRONG_PASSWORD;
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ pw.value = "é";
+ } else {
+ pw.value = this.pass;
+ }
+ return true;
+ },
+
+ promptPassword: function ap1_promptPW(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ },
+};
+
+function AuthPrompt2(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt2.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap2_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor(flags, versions) {
+ this.flags = flags;
+ this.versions = versions;
+}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt1) {
+ this.prompt1 = new AuthPrompt1(this.flags);
+ }
+ return this.prompt1;
+ }
+ if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2) {
+ this.prompt2 = new AuthPrompt2(this.flags);
+ }
+ return this.prompt2;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt1: null,
+ prompt2: null,
+};
+
+function RealmTestRequestor() {}
+
+RealmTestRequestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIAuthPrompt2",
+ ]),
+
+ getInterface: function realmtest_interface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ return this;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
+ Assert.equal(authInfo.realm, '"foo_bar');
+
+ return false;
+ },
+
+ asyncPromptAuth: function realmtest_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function makeChan(url) {
+ let loadingUrl = Services.io
+ .newURI(url)
+ .mutate()
+ .setPathQueryRef("")
+ .finalize();
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ loadingUrl,
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+function ntlm_auth(metadata, response) {
+ let challenge = metadata.getHeader("Authorization");
+ if (!challenge.startsWith("NTLM ")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ return;
+ }
+
+ let decoded = atob(challenge.substring(5));
+ info(decoded);
+
+ if (!decoded.startsWith("NTLMSSP\0")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ return;
+ }
+
+ let isNegotiate = decoded.substring(8).startsWith("\x01\x00\x00\x00");
+ let isAuthenticate = decoded.substring(8).startsWith("\x03\x00\x00\x00");
+
+ if (isNegotiate) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader(
+ "WWW-Authenticate",
+ "NTLM TlRMTVNTUAACAAAAAAAAAAAoAAABggAAASNFZ4mrze8AAAAAAAAAAAAAAAAAAAAA",
+ false
+ );
+ return;
+ }
+
+ if (isAuthenticate) {
+ let body = "OK";
+ response.bodyOutputStream.write(body, body.length);
+ return;
+ }
+
+ // Something else went wrong.
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+}
+
+function basic_auth(metadata, response) {
+ let challenge = metadata.getHeader("Authorization");
+ if (!challenge.startsWith("Basic ")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ return;
+ }
+
+ if (challenge == "Basic Z3Vlc3Q6Z3Vlc3Q=") {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ let body = "success";
+ response.bodyOutputStream.write(body, body.length);
+ return;
+ }
+
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+}
+
+//
+// Digest functions
+//
+function bytesFromString(str) {
+ const encoder = new TextEncoder();
+ return encoder.encode(str);
+}
+
+// return the two-digit hexadecimal code for a byte
+function toHexString(charCode) {
+ return ("0" + charCode.toString(16)).slice(-2);
+}
+
+function H(str) {
+ var data = bytesFromString(str);
+ var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+ ch.init(Ci.nsICryptoHash.MD5);
+ ch.update(data, data.length);
+ var hash = ch.finish(false);
+ return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+}
+
+const nonce = "6f93719059cf8d568005727f3250e798";
+const opaque = "1234opaque1234";
+const digestChallenge = `Digest realm="secret", domain="/", qop=auth,algorithm=MD5, nonce="${nonce}" opaque="${opaque}"`;
+//
+// Digest handler
+//
+// /auth/digest
+function authDigest(metadata, response) {
+ var cnonceRE = /cnonce="(\w+)"/;
+ var responseRE = /response="(\w+)"/;
+ var usernameRE = /username="(\w+)"/;
+ var body = "";
+ // check creds if we have them
+ if (metadata.hasHeader("Authorization")) {
+ var auth = metadata.getHeader("Authorization");
+ var cnonce = auth.match(cnonceRE)[1];
+ var clientDigest = auth.match(responseRE)[1];
+ var username = auth.match(usernameRE)[1];
+ var nc = "00000001";
+
+ if (username != "guest") {
+ response.setStatusLine(metadata.httpVersion, 400, "bad request");
+ body = "should never get here";
+ } else {
+ // see RFC2617 for the description of this calculation
+ var A1 = "guest:secret:guest";
+ var A2 = "GET:/path";
+ var noncebits = [nonce, nc, cnonce, "auth", H(A2)].join(":");
+ var digest = H([H(A1), noncebits].join(":"));
+
+ if (clientDigest == digest) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ body = "digest";
+ } else {
+ info(clientDigest);
+ info(digest);
+ handle_unauthorized(metadata, response);
+ return;
+ }
+ }
+ } else {
+ // no header, send one
+ handle_unauthorized(metadata, response);
+ return;
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+let challenges = ["NTLM", `Basic realm="secret"`, digestChallenge];
+
+function handle_unauthorized(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+
+ for (let ch of challenges) {
+ response.setHeader("WWW-Authenticate", ch, true);
+ }
+}
+
+// /path
+function auth_handler(metadata, response) {
+ if (!metadata.hasHeader("Authorization")) {
+ handle_unauthorized(metadata, response);
+ return;
+ }
+
+ let challenge = metadata.getHeader("Authorization");
+ if (challenge.startsWith("NTLM ")) {
+ ntlm_auth(metadata, response);
+ return;
+ }
+
+ if (challenge.startsWith("Basic ")) {
+ basic_auth(metadata, response);
+ return;
+ }
+
+ if (challenge.startsWith("Digest ")) {
+ authDigest(metadata, response);
+ return;
+ }
+
+ handle_unauthorized(metadata, response);
+}
+
+let httpserv;
+add_setup(() => {
+ Services.prefs.setBoolPref("network.auth.force-generic-ntlm", true);
+ Services.prefs.setBoolPref("network.auth.force-generic-ntlm-v1", true);
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+ Services.prefs.setBoolPref("network.http.sanitize-headers-in-logs", false);
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/path", auth_handler);
+ httpserv.start(-1);
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.auth.force-generic-ntlm");
+ Services.prefs.clearUserPref("network.auth.force-generic-ntlm-v1");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+ Services.prefs.clearUserPref("network.http.sanitize-headers-in-logs");
+
+ await httpserv.stop();
+ });
+});
+
+add_task(async function test_ntlm_first() {
+ Services.prefs.setBoolPref(
+ "network.auth.choose_most_secure_challenge",
+ false
+ );
+ challenges = ["NTLM", `Basic realm="secret"`, digestChallenge];
+ httpserv.identity.add("http", "ntlm.com", httpserv.identity.primaryPort);
+ let chan = makeChan(URL("ntlm.com", "/path"));
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ let [req, buf] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((request, buffer) => resolve([request, buffer]), null)
+ );
+ });
+ Assert.equal(buf, "OK");
+ Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+});
+
+add_task(async function test_basic_first() {
+ Services.prefs.setBoolPref(
+ "network.auth.choose_most_secure_challenge",
+ false
+ );
+ challenges = [`Basic realm="secret"`, "NTLM", digestChallenge];
+ httpserv.identity.add("http", "basic.com", httpserv.identity.primaryPort);
+ let chan = makeChan(URL("basic.com", "/path"));
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ let [req, buf] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((request, buffer) => resolve([request, buffer]), null)
+ );
+ });
+ Assert.equal(buf, "success");
+ Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+});
+
+add_task(async function test_digest_first() {
+ Services.prefs.setBoolPref(
+ "network.auth.choose_most_secure_challenge",
+ false
+ );
+ challenges = [digestChallenge, `Basic realm="secret"`, "NTLM"];
+ httpserv.identity.add("http", "digest.com", httpserv.identity.primaryPort);
+ let chan = makeChan(URL("digest.com", "/path"));
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ let [req, buf] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((request, buffer) => resolve([request, buffer]), null)
+ );
+ });
+ Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ Assert.equal(buf, "digest");
+});
+
+add_task(async function test_choose_most_secure() {
+ // When the pref is true, we rank the challenges by how secure they are.
+ // In this case, NTLM should be the most secure.
+ Services.prefs.setBoolPref("network.auth.choose_most_secure_challenge", true);
+ challenges = [digestChallenge, `Basic realm="secret"`, "NTLM"];
+ httpserv.identity.add(
+ "http",
+ "ntlmstrong.com",
+ httpserv.identity.primaryPort
+ );
+ let chan = makeChan(URL("ntlmstrong.com", "/path"));
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ let [req, buf] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((request, buffer) => resolve([request, buffer]), null)
+ );
+ });
+ Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ Assert.equal(buf, "OK");
+});
diff --git a/netwerk/test/unit/test_auth_proxy.js b/netwerk/test/unit/test_auth_proxy.js
new file mode 100644
index 0000000000..d49e230922
--- /dev/null
+++ b/netwerk/test/unit/test_auth_proxy.js
@@ -0,0 +1,463 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This tests the automatic login to the proxy with password,
+ * if the password is stored and the browser is restarted.
+ *
+ * <copied from="test_authentication.js"/>
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const FLAG_RETURN_FALSE = 1 << 0;
+const FLAG_WRONG_PASSWORD = 1 << 1;
+const FLAG_PREVIOUS_FAILED = 1 << 2;
+
+function AuthPrompt2(proxyFlags, hostFlags) {
+ this.proxyCred.flags = proxyFlags;
+ this.hostCred.flags = hostFlags;
+}
+AuthPrompt2.prototype = {
+ proxyCred: {
+ user: "proxy",
+ pass: "guest",
+ realmExpected: "intern",
+ flags: 0,
+ },
+ hostCred: { user: "host", pass: "guest", realmExpected: "extern", flags: 0 },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap2_promptAuth(channel, encryptionLevel, authInfo) {
+ try {
+ // never HOST and PROXY set at the same time in prompt
+ Assert.equal(
+ (authInfo.flags & Ci.nsIAuthInformation.AUTH_HOST) != 0,
+ (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) == 0
+ );
+
+ var isProxy = (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) != 0;
+ var cred = isProxy ? this.proxyCred : this.hostCred;
+
+ dump(
+ "with flags: " +
+ ((cred.flags & FLAG_WRONG_PASSWORD) != 0 ? "wrong password" : "") +
+ " " +
+ ((cred.flags & FLAG_PREVIOUS_FAILED) != 0 ? "previous failed" : "") +
+ " " +
+ ((cred.flags & FLAG_RETURN_FALSE) != 0 ? "return false" : "") +
+ "\n"
+ );
+
+ // PROXY properly set by necko (checked using realm)
+ Assert.equal(cred.realmExpected, authInfo.realm);
+
+ // PREVIOUS_FAILED properly set by necko
+ Assert.equal(
+ (cred.flags & FLAG_PREVIOUS_FAILED) != 0,
+ (authInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) != 0
+ );
+
+ if (cred.flags & FLAG_RETURN_FALSE) {
+ cred.flags |= FLAG_PREVIOUS_FAILED;
+ cred.flags &= ~FLAG_RETURN_FALSE;
+ return false;
+ }
+
+ authInfo.username = cred.user;
+ if (cred.flags & FLAG_WRONG_PASSWORD) {
+ authInfo.password = cred.pass + ".wrong";
+ cred.flags |= FLAG_PREVIOUS_FAILED;
+ // Now clear the flag to avoid an infinite loop
+ cred.flags &= ~FLAG_WRONG_PASSWORD;
+ } else {
+ authInfo.password = cred.pass;
+ cred.flags &= ~FLAG_PREVIOUS_FAILED;
+ }
+ } catch (e) {
+ do_throw(e);
+ }
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(
+ channel,
+ callback,
+ context,
+ encryptionLevel,
+ authInfo
+ ) {
+ var me = this;
+ var allOverAndDead = false;
+ executeSoon(function () {
+ try {
+ if (allOverAndDead) {
+ throw new Error("already canceled");
+ }
+ var ret = me.promptAuth(channel, encryptionLevel, authInfo);
+ if (!ret) {
+ callback.onAuthCancelled(context, true);
+ } else {
+ callback.onAuthAvailable(context, authInfo);
+ }
+ allOverAndDead = true;
+ } catch (e) {
+ do_throw(e);
+ }
+ });
+ return new Cancelable(function () {
+ if (allOverAndDead) {
+ throw new Error("can't cancel, already ran");
+ }
+ callback.onAuthAvailable(context, authInfo);
+ allOverAndDead = true;
+ });
+ },
+};
+
+function Cancelable(onCancelFunc) {
+ this.onCancelFunc = onCancelFunc;
+}
+Cancelable.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
+ cancel: function cancel() {
+ try {
+ this.onCancelFunc();
+ } catch (e) {
+ do_throw(e);
+ }
+ },
+};
+
+function Requestor(proxyFlags, hostFlags) {
+ this.proxyFlags = proxyFlags;
+ this.hostFlags = hostFlags;
+}
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt)) {
+ dump("authprompt1 not implemented\n");
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ try {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2) {
+ this.prompt2 = new AuthPrompt2(this.proxyFlags, this.hostFlags);
+ }
+ return this.prompt2;
+ } catch (e) {
+ do_throw(e);
+ }
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt2: null,
+};
+
+var listener = {
+ expectedCode: -1, // uninitialized
+
+ onStartRequest: function test_onStartR(request) {
+ try {
+ // Proxy auth cancellation return failures to avoid spoofing
+ if (
+ !Components.isSuccessCode(request.status) &&
+ this.expectedCode != 407
+ ) {
+ do_throw("Channel should have a success code!");
+ }
+
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ do_throw("Expecting an HTTP channel");
+ }
+
+ Assert.equal(this.expectedCode, request.responseStatus);
+ // If we expect 200, the request should have succeeded
+ Assert.equal(this.expectedCode == 200, request.requestSucceeded);
+
+ var cookie = "";
+ try {
+ cookie = request.getRequestHeader("Cookie");
+ } catch (e) {}
+ Assert.equal(cookie, "");
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ Assert.equal(status, Cr.NS_ERROR_ABORT);
+
+ if (current_test < tests.length - 1) {
+ // First, need to clear the auth cache
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+
+ do_test_finished();
+ },
+};
+
+function makeChan(url) {
+ if (!url) {
+ url = "http://somesite/";
+ }
+
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var current_test = 0;
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", proxyAuthHandler);
+ httpserv.identity.add("http", "somesite", 80);
+ httpserv.start(-1);
+
+ Services.prefs.setCharPref("network.proxy.http", "localhost");
+ Services.prefs.setIntPref(
+ "network.proxy.http_port",
+ httpserv.identity.primaryPort
+ );
+ Services.prefs.setCharPref("network.proxy.no_proxies_on", "");
+ Services.prefs.setIntPref("network.proxy.type", 1);
+
+ // Turn off the authentication dialog blocking for this test.
+ Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+ Services.prefs.setBoolPref(
+ "network.auth.non-web-content-triggered-resources-http-auth-allow",
+ true
+ );
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.proxy.http");
+ Services.prefs.clearUserPref("network.proxy.http_port");
+ Services.prefs.clearUserPref("network.proxy.no_proxies_on");
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.auth.subresource-http-auth-allow");
+ Services.prefs.clearUserPref(
+ "network.auth.non-web-content-triggered-resources-http-auth-allow"
+ );
+ });
+
+ tests[current_test]();
+}
+
+function test_proxy_returnfalse() {
+ dump("\ntest: proxy returnfalse\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0);
+ listener.expectedCode = 407; // Proxy Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_proxy_wrongpw() {
+ dump("\ntest: proxy wrongpw\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 0);
+ listener.expectedCode = 200; // Eventually OK
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function test_all_ok() {
+ dump("\ntest: all ok\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, 0);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function test_proxy_407_cookie() {
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0);
+ chan.setRequestHeader("X-Set-407-Cookie", "1", false);
+ listener.expectedCode = 407; // Proxy Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_proxy_200_cookie() {
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, 0);
+ chan.setRequestHeader("X-Set-407-Cookie", "1", false);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function test_host_returnfalse() {
+ dump("\ntest: host returnfalse\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, FLAG_RETURN_FALSE);
+ listener.expectedCode = 401; // Host Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_host_wrongpw() {
+ dump("\ntest: host wrongpw\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, FLAG_WRONG_PASSWORD);
+ listener.expectedCode = 200; // Eventually OK
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function test_proxy_wrongpw_host_wrongpw() {
+ dump("\ntest: proxy wrongpw, host wrongpw\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(
+ FLAG_WRONG_PASSWORD,
+ FLAG_WRONG_PASSWORD
+ );
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function test_proxy_wrongpw_host_returnfalse() {
+ dump("\ntest: proxy wrongpw, host return false\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(
+ FLAG_WRONG_PASSWORD,
+ FLAG_RETURN_FALSE
+ );
+ listener.expectedCode = 401; // Host Unauthorized
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+var tests = [
+ test_proxy_returnfalse,
+ test_proxy_wrongpw,
+ test_all_ok,
+ test_proxy_407_cookie,
+ test_proxy_200_cookie,
+ test_host_returnfalse,
+ test_host_wrongpw,
+ test_proxy_wrongpw_host_wrongpw,
+ test_proxy_wrongpw_host_returnfalse,
+];
+
+// PATH HANDLERS
+
+// Proxy
+function proxyAuthHandler(metadata, response) {
+ try {
+ var realm = "intern";
+ // btoa("proxy:guest"), but that function is not available here
+ var expectedHeader = "Basic cHJveHk6Z3Vlc3Q=";
+
+ var body;
+ if (
+ metadata.hasHeader("Proxy-Authorization") &&
+ metadata.getHeader("Proxy-Authorization") == expectedHeader
+ ) {
+ dump("proxy password ok\n");
+ response.setHeader(
+ "Proxy-Authenticate",
+ 'Basic realm="' + realm + '"',
+ false
+ );
+
+ hostAuthHandler(metadata, response);
+ } else {
+ dump("proxy password required\n");
+ response.setStatusLine(
+ metadata.httpVersion,
+ 407,
+ "Unauthorized by HTTP proxy"
+ );
+ response.setHeader(
+ "Proxy-Authenticate",
+ 'Basic realm="' + realm + '"',
+ false
+ );
+ if (metadata.hasHeader("X-Set-407-Cookie")) {
+ response.setHeader("Set-Cookie", "chewy", false);
+ }
+ body = "failed";
+ response.bodyOutputStream.write(body, body.length);
+ }
+ } catch (e) {
+ do_throw(e);
+ }
+}
+
+// Host /auth
+function hostAuthHandler(metadata, response) {
+ try {
+ var realm = "extern";
+ // btoa("host:guest"), but that function is not available here
+ var expectedHeader = "Basic aG9zdDpndWVzdA==";
+
+ var body;
+ if (
+ metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader
+ ) {
+ dump("host password ok\n");
+ response.setStatusLine(
+ metadata.httpVersion,
+ 200,
+ "OK, authorized for host"
+ );
+ response.setHeader(
+ "WWW-Authenticate",
+ 'Basic realm="' + realm + '"',
+ false
+ );
+ body = "success";
+ } else {
+ dump("host password required\n");
+ response.setStatusLine(
+ metadata.httpVersion,
+ 401,
+ "Unauthorized by HTTP server host"
+ );
+ response.setHeader(
+ "WWW-Authenticate",
+ 'Basic realm="' + realm + '"',
+ false
+ );
+ body = "failed";
+ }
+ response.bodyOutputStream.write(body, body.length);
+ } catch (e) {
+ do_throw(e);
+ }
+}
diff --git a/netwerk/test/unit/test_authentication.js b/netwerk/test/unit/test_authentication.js
new file mode 100644
index 0000000000..823e2cb36b
--- /dev/null
+++ b/netwerk/test/unit/test_authentication.js
@@ -0,0 +1,1400 @@
+// This file tests authentication prompt callbacks
+// TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// Turn off the authentication dialog blocking for this test.
+Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "PORT", function () {
+ return httpserv.identity.primaryPort;
+});
+
+const FLAG_RETURN_FALSE = 1 << 0;
+const FLAG_WRONG_PASSWORD = 1 << 1;
+const FLAG_BOGUS_USER = 1 << 2;
+const FLAG_PREVIOUS_FAILED = 1 << 3;
+const CROSS_ORIGIN = 1 << 4;
+const FLAG_NO_REALM = 1 << 5;
+const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
+
+const nsIAuthPrompt2 = Ci.nsIAuthPrompt2;
+const nsIAuthInformation = Ci.nsIAuthInformation;
+
+function AuthPrompt1(flags) {
+ this.flags = flags;
+}
+
+var initialChannelId = -1;
+
+AuthPrompt1.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
+
+ prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword: function ap1_promptUP(
+ title,
+ text,
+ realm,
+ savePW,
+ user,
+ pw
+ ) {
+ if (this.flags & FLAG_NO_REALM) {
+ // Note that the realm here isn't actually the realm. it's a pw mgr key.
+ Assert.equal(URL + " (" + this.expectedRealm + ")", realm);
+ }
+ if (!(this.flags & CROSS_ORIGIN)) {
+ if (!text.includes(this.expectedRealm)) {
+ do_throw("Text must indicate the realm");
+ }
+ } else if (text.includes(this.expectedRealm)) {
+ do_throw("There should not be realm for cross origin");
+ }
+ if (!text.includes("localhost")) {
+ do_throw("Text must indicate the hostname");
+ }
+ if (!text.includes(String(PORT))) {
+ do_throw("Text must indicate the port");
+ }
+ if (text.includes("-1")) {
+ do_throw("Text must contain negative numbers");
+ }
+
+ if (this.flags & FLAG_RETURN_FALSE) {
+ return false;
+ }
+
+ if (this.flags & FLAG_BOGUS_USER) {
+ this.user = "foo\nbar";
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ this.user = "é";
+ }
+
+ user.value = this.user;
+ if (this.flags & FLAG_WRONG_PASSWORD) {
+ pw.value = this.pass + ".wrong";
+ // Now clear the flag to avoid an infinite loop
+ this.flags &= ~FLAG_WRONG_PASSWORD;
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ pw.value = "é";
+ } else {
+ pw.value = this.pass;
+ }
+ return true;
+ },
+
+ promptPassword: function ap1_promptPW(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ },
+};
+
+function AuthPrompt2(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt2.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap2_promptAuth(channel, level, authInfo) {
+ var isNTLM = channel.URI.pathQueryRef.includes("ntlm");
+ var isDigest = channel.URI.pathQueryRef.includes("digest");
+
+ if (isNTLM || this.flags & FLAG_NO_REALM) {
+ this.expectedRealm = ""; // NTLM knows no realms
+ }
+
+ Assert.equal(this.expectedRealm, authInfo.realm);
+
+ var expectedLevel =
+ isNTLM || isDigest
+ ? nsIAuthPrompt2.LEVEL_PW_ENCRYPTED
+ : nsIAuthPrompt2.LEVEL_NONE;
+ Assert.equal(expectedLevel, level);
+
+ var expectedFlags = nsIAuthInformation.AUTH_HOST;
+
+ if (this.flags & FLAG_PREVIOUS_FAILED) {
+ expectedFlags |= nsIAuthInformation.PREVIOUS_FAILED;
+ }
+
+ if (this.flags & CROSS_ORIGIN) {
+ expectedFlags |= nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;
+ }
+
+ if (isNTLM) {
+ expectedFlags |= nsIAuthInformation.NEED_DOMAIN;
+ }
+
+ const kAllKnownFlags = 127; // Don't fail test for newly added flags
+ Assert.equal(expectedFlags, authInfo.flags & kAllKnownFlags);
+
+ // eslint-disable-next-line no-nested-ternary
+ var expectedScheme = isNTLM ? "ntlm" : isDigest ? "digest" : "basic";
+ Assert.equal(expectedScheme, authInfo.authenticationScheme);
+
+ // No passwords in the URL -> nothing should be prefilled
+ Assert.equal(authInfo.username, "");
+ Assert.equal(authInfo.password, "");
+ Assert.equal(authInfo.domain, "");
+
+ if (this.flags & FLAG_RETURN_FALSE) {
+ this.flags |= FLAG_PREVIOUS_FAILED;
+ return false;
+ }
+
+ if (this.flags & FLAG_BOGUS_USER) {
+ this.user = "foo\nbar";
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ this.user = "é";
+ }
+
+ authInfo.username = this.user;
+ if (this.flags & FLAG_WRONG_PASSWORD) {
+ authInfo.password = this.pass + ".wrong";
+ this.flags |= FLAG_PREVIOUS_FAILED;
+ // Now clear the flag to avoid an infinite loop
+ this.flags &= ~FLAG_WRONG_PASSWORD;
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ authInfo.password = "é";
+ } else {
+ authInfo.password = this.pass;
+ this.flags &= ~FLAG_PREVIOUS_FAILED;
+ }
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
+ let self = this;
+ executeSoon(function () {
+ let ret = self.promptAuth(chan, lvl, info);
+ if (ret) {
+ cb.onAuthAvailable(ctx, info);
+ } else {
+ cb.onAuthCancelled(ctx, true);
+ }
+ });
+ },
+};
+
+function Requestor(flags, versions) {
+ this.flags = flags;
+ this.versions = versions;
+}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt1) {
+ this.prompt1 = new AuthPrompt1(this.flags);
+ }
+ return this.prompt1;
+ }
+ if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2) {
+ this.prompt2 = new AuthPrompt2(this.flags);
+ }
+ return this.prompt2;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt1: null,
+ prompt2: null,
+};
+
+function RealmTestRequestor() {
+ this.promptRealm = "";
+}
+
+RealmTestRequestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIAuthPrompt2",
+ ]),
+
+ getInterface: function realmtest_interface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ return this;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
+ this.promptRealm = authInfo.realm;
+
+ return false;
+ },
+
+ asyncPromptAuth: function realmtest_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+var listener = {
+ expectedCode: -1, // Uninitialized
+ nextTest: undefined,
+ expectRequestFail: false,
+ onStartRequest: function test_onStartR(request) {
+ try {
+ if (
+ !this.expectRequestFail &&
+ !Components.isSuccessCode(request.status)
+ ) {
+ do_throw("Channel should have a success code!");
+ }
+
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ do_throw("Expecting an HTTP channel");
+ }
+
+ if (
+ Services.prefs.getBoolPref("network.auth.use_redirect_for_retries") &&
+ // we should skip redirect check if we do not expect to succeed
+ this.expectedCode == 200
+ ) {
+ // ensure channel ids are initialized
+ Assert.notEqual(initialChannelId, -1);
+
+ // for each request we must use a unique channel ID.
+ // See Bug 1820807
+ var chan = request.QueryInterface(Ci.nsIIdentChannel);
+ Assert.notEqual(initialChannelId, chan.channelId);
+ }
+
+ Assert.equal(request.responseStatus, this.expectedCode);
+ // The request should be succeeded if we expect 200
+ Assert.equal(request.requestSucceeded, this.expectedCode == 200);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ Assert.equal(status, Cr.NS_ERROR_ABORT);
+ initialChannelId = -1;
+ this.nextTest();
+ },
+};
+
+let ChannelEventSink1 = {
+ _classDescription: "WebRequest channel event sink",
+ _classID: Components.ID("115062f8-92f1-11e5-8b7f-08001110f7ec"),
+ _contractID: "@mozilla.org/webrequest/channel-event-sink;1",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]),
+
+ init() {
+ Components.manager
+ .QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(
+ this._classID,
+ this._classDescription,
+ this._contractID,
+ this
+ );
+ },
+
+ register() {
+ Services.catMan.addCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ this._contractID,
+ false,
+ true
+ );
+ },
+
+ unregister() {
+ Services.catMan.deleteCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ false
+ );
+ },
+
+ // nsIChannelEventSink implementation
+ asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
+ // Abort the redirection
+ redirectCallback.onRedirectVerifyCallback(Cr.NS_ERROR_ABORT);
+ },
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+};
+
+function makeChan(
+ url,
+ loadingUrl,
+ securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER
+) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags,
+ contentPolicyType,
+ });
+}
+
+var ChannelCreationObserver = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "http-on-opening-request") {
+ initialChannelId = aSubject.QueryInterface(Ci.nsIIdentChannel).channelId;
+ }
+ },
+};
+
+var httpserv = null;
+
+function setup() {
+ httpserv = new HttpServer();
+
+ httpserv.registerPathHandler("/auth", authHandler);
+ httpserv.registerPathHandler(
+ "/auth/stored/wrong/credentials/",
+ authHandlerWrongStoredCredentials
+ );
+ httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
+ httpserv.registerPathHandler("/auth/realm", authRealm);
+ httpserv.registerPathHandler("/auth/non_ascii", authNonascii);
+ httpserv.registerPathHandler("/auth/digest_md5", authDigestMD5);
+ httpserv.registerPathHandler("/auth/digest_md5sess", authDigestMD5sess);
+ httpserv.registerPathHandler("/auth/digest_sha256", authDigestSHA256);
+ httpserv.registerPathHandler("/auth/digest_sha256sess", authDigestSHA256sess);
+ httpserv.registerPathHandler("/auth/digest_sha256_md5", authDigestSHA256_MD5);
+ httpserv.registerPathHandler("/auth/digest_md5_sha256", authDigestMD5_SHA256);
+ httpserv.registerPathHandler(
+ "/auth/digest_md5_sha256_oneline",
+ authDigestMD5_SHA256_oneline
+ );
+ httpserv.registerPathHandler("/auth/short_digest", authShortDigest);
+ httpserv.registerPathHandler("/largeRealm", largeRealm);
+ httpserv.registerPathHandler("/largeDomain", largeDomain);
+
+ httpserv.registerPathHandler("/corp-coep", corpAndCoep);
+
+ httpserv.start(-1);
+
+ registerCleanupFunction(async () => {
+ await httpserv.stop();
+ });
+ Services.obs.addObserver(ChannelCreationObserver, "http-on-opening-request");
+}
+setup();
+
+async function openAndListen(chan) {
+ await new Promise(resolve => {
+ listener.nextTest = resolve;
+ chan.asyncOpen(listener);
+ });
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+}
+
+async function test_noauth() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+}
+
+async function test_returnfalse1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 1);
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+}
+
+async function test_wrongpw1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 1);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_prompt1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 1);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_prompt1CrossOrigin() {
+ var chan = makeChan(URL + "/auth", "http://example.org");
+
+ chan.notificationCallbacks = new Requestor(16, 1);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_prompt2CrossOrigin() {
+ var chan = makeChan(URL + "/auth", "http://example.org");
+
+ chan.notificationCallbacks = new Requestor(16, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_returnfalse2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+}
+
+async function test_wrongpw2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+var requestNum = 0;
+var expectedRequestNum = 0;
+async function test_wrong_stored_passwd() {
+ // tests that we don't retry auth requests for incorrect custom credentials passed during channel creation
+ requestNum = 0;
+ expectedRequestNum = 1;
+ var chan = makeChan(URL + "/auth/stored/wrong/credentials/", URL);
+ chan.nsIHttpChannel.setRequestHeader("Authorization", "wrong_cred", false);
+ chan.notificationCallbacks = new Requestor(0, 1);
+ listener.expectedCode = 401; // Unauthorized
+
+ await openAndListen(chan);
+}
+
+async function test_prompt2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_ntlm() {
+ var chan = makeChan(URL + "/auth/ntlm/simple", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+}
+
+async function test_basicrealm() {
+ var chan = makeChan(URL + "/auth/realm", URL);
+
+ let requestor = new RealmTestRequestor();
+ chan.notificationCallbacks = requestor;
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+ Assert.equal(requestor.promptRealm, '"foo_bar');
+}
+
+async function test_nonascii() {
+ var chan = makeChan(URL + "/auth/non_ascii", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_NON_ASCII_USER_PASSWORD, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_digest_noauth() {
+ var chan = makeChan(URL + "/auth/digest_md5", URL);
+
+ // chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+}
+
+async function test_digest_md5() {
+ var chan = makeChan(URL + "/auth/digest_md5", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ async function test_digest_md5_redirect_veto() {
+ ChannelEventSink1.init();
+ ChannelEventSink1.register();
+ var chan = makeChan(URL + "/auth/digest_md5", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 1);
+ listener.expectedCode = 401; // Unauthorized
+ listener.expectRequestFail = true;
+ await openAndListen(chan);
+ ChannelEventSink1.unregister();
+ listener.expectRequestFail = false;
+ }
+);
+
+async function test_digest_md5sess() {
+ var chan = makeChan(URL + "/auth/digest_md5sess", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_digest_sha256() {
+ var chan = makeChan(URL + "/auth/digest_sha256", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_digest_sha256sess() {
+ var chan = makeChan(URL + "/auth/digest_sha256sess", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_digest_sha256_md5() {
+ var chan = makeChan(URL + "/auth/digest_sha256_md5", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_digest_md5_sha256() {
+ var chan = makeChan(URL + "/auth/digest_md5_sha256", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_digest_md5_sha256_oneline() {
+ var chan = makeChan(URL + "/auth/digest_md5_sha256_oneline", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ await openAndListen(chan);
+}
+
+async function test_digest_bogus_user() {
+ var chan = makeChan(URL + "/auth/digest_md5", URL);
+ chan.notificationCallbacks = new Requestor(FLAG_BOGUS_USER, 2);
+ listener.expectedCode = 401; // unauthorized
+ await openAndListen(chan);
+}
+
+// Test header "WWW-Authenticate: Digest" - bug 1338876.
+async function test_short_digest() {
+ var chan = makeChan(URL + "/auth/short_digest", URL);
+ chan.notificationCallbacks = new Requestor(FLAG_NO_REALM, 2);
+ listener.expectedCode = 401; // OK
+ await openAndListen(chan);
+}
+
+// Test that COOP/COEP are processed even though asyncPromptAuth is cancelled.
+async function test_corp_coep() {
+ var chan = makeChan(
+ URL + "/corp-coep",
+ URL,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ Ci.nsIContentPolicy.TYPE_DOCUMENT
+ );
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // OK
+ await openAndListen(chan);
+
+ Assert.equal(
+ chan.getResponseHeader("cross-origin-embedder-policy"),
+ "require-corp"
+ );
+ Assert.equal(
+ chan.getResponseHeader("cross-origin-opener-policy"),
+ "same-origin"
+ );
+}
+
+// XXX(valentin): this makes tests fail if it's not run last. Why?
+async function test_nonascii_xhr() {
+ await new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", URL + "/auth/non_ascii", true, "é", "é");
+ xhr.onreadystatechange = function (event) {
+ if (xhr.readyState == 4) {
+ Assert.equal(xhr.status, 200);
+ resolve();
+ xhr.onreadystatechange = null;
+ }
+ };
+ xhr.send(null);
+ });
+}
+
+let auth_tests = [
+ test_noauth,
+ test_returnfalse1,
+ test_wrongpw1,
+ test_wrong_stored_passwd,
+ test_prompt1,
+ test_prompt1CrossOrigin,
+ test_prompt2CrossOrigin,
+ test_returnfalse2,
+ test_wrongpw2,
+ test_prompt2,
+ test_ntlm,
+ test_basicrealm,
+ test_nonascii,
+ test_digest_noauth,
+ test_digest_md5,
+ test_digest_md5sess,
+ test_digest_sha256,
+ test_digest_sha256sess,
+ test_digest_sha256_md5,
+ test_digest_md5_sha256,
+ test_digest_md5_sha256_oneline,
+ test_digest_bogus_user,
+ test_short_digest,
+ test_corp_coep,
+ test_nonascii_xhr,
+];
+
+for (let auth_test of auth_tests) {
+ add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ auth_test
+ );
+}
+
+for (let auth_test of auth_tests) {
+ add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ auth_test
+ );
+}
+
+// PATH HANDLERS
+
+// /auth
+function authHandler(metadata, response) {
+ // btoa("guest:guest"), but that function is not available here
+ var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ var body;
+ if (
+ metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "success";
+ } else {
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "failed";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function authHandlerWrongStoredCredentials(metadata, response) {
+ var body;
+ if (++requestNum > expectedRequestNum) {
+ response.setStatusLine(metadata.httpVersion, 500, "");
+ } else {
+ response.setStatusLine(
+ metadata.httpVersion,
+ 401,
+ "Unauthorized" + requestNum
+ );
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ }
+
+ body = "failed";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /auth/ntlm/simple
+function authNtlmSimple(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader(
+ "WWW-Authenticate",
+ "NTLM" /* + ' realm="secret"' */,
+ false
+ );
+
+ var body =
+ "NOTE: This just sends an NTLM challenge, it never\n" +
+ "accepts the authentication. It also closes\n" +
+ "the connection after sending the challenge\n";
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /auth/realm
+function authRealm(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="\\"f\\oo_bar"', false);
+ var body = "success";
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /auth/nonAscii
+function authNonascii(metadata, response) {
+ // btoa("é:é"), but that function is not available here
+ var expectedHeader = "Basic w6k6w6k=";
+
+ var body;
+ if (
+ metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ // Use correct XML syntax since this function is also used for testing XHR.
+ body = "<?xml version='1.0' ?><root>success</root>";
+ } else {
+ // didn't know é:é, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "<?xml version='1.0' ?><root>failed</root>";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function corpAndCoep(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("cross-origin-embedder-policy", "require-corp");
+ response.setHeader("cross-origin-opener-policy", "same-origin");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+}
+
+//
+// Digest functions
+//
+function bytesFromString(str) {
+ return new TextEncoder().encode(str);
+}
+
+// return the two-digit hexadecimal code for a byte
+function toHexString(charCode) {
+ return ("0" + charCode.toString(16)).slice(-2);
+}
+
+function HMD5(str) {
+ var data = bytesFromString(str);
+ var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+ ch.init(Ci.nsICryptoHash.MD5);
+ ch.update(data, data.length);
+ var hash = ch.finish(false);
+ return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+}
+
+function HSHA256(str) {
+ var data = bytesFromString(str);
+ var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+ ch.init(Ci.nsICryptoHash.SHA256);
+ ch.update(data, data.length);
+ var hash = ch.finish(false);
+ return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+}
+
+//
+// Digest handler
+//
+// /auth/digest
+function authDigestMD5_helper(metadata, response, test_name) {
+ var nonce = "6f93719059cf8d568005727f3250e798";
+ var opaque = "1234opaque1234";
+ var body;
+ var send_401 = 0;
+ // check creds if we have them
+ if (metadata.hasHeader("Authorization")) {
+ var cnonceRE = /cnonce="(\w+)"/;
+ var responseRE = /response="(\w+)"/;
+ var usernameRE = /username="(\w+)"/;
+ var algorithmRE = /algorithm=([\w-]+)/;
+ var auth = metadata.getHeader("Authorization");
+ var cnonce = auth.match(cnonceRE)[1];
+ var clientDigest = auth.match(responseRE)[1];
+ var username = auth.match(usernameRE)[1];
+ var algorithm = auth.match(algorithmRE)[1];
+ var nc = "00000001";
+
+ if (username != "guest") {
+ response.setStatusLine(metadata.httpVersion, 400, "bad request");
+ body = "should never get here";
+ } else if (
+ algorithm != null &&
+ algorithm != "MD5" &&
+ algorithm != "MD5-sess"
+ ) {
+ response.setStatusLine(metadata.httpVersion, 400, "bad request");
+ body = "Algorithm must be same as provided in WWW-Authenticate header";
+ } else {
+ // see RFC2617 for the description of this calculation
+ var A1 = "guest:secret:guest";
+ if (algorithm == "MD5-sess") {
+ A1 = [HMD5(A1), nonce, cnonce].join(":");
+ }
+ var A2 = "GET:/auth/" + test_name;
+ var noncebits = [nonce, nc, cnonce, "auth", HMD5(A2)].join(":");
+ var digest = HMD5([HMD5(A1), noncebits].join(":"));
+
+ if (clientDigest == digest) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ body = "success";
+ } else {
+ send_401 = 1;
+ body = "auth failed";
+ }
+ }
+ } else {
+ // no header, send one
+ send_401 = 1;
+ body = "failed, no header";
+ }
+
+ if (send_401) {
+ var authenticate_md5 =
+ 'Digest realm="secret", domain="/", qop=auth,' +
+ 'algorithm=MD5, nonce="' +
+ nonce +
+ '" opaque="' +
+ opaque +
+ '"';
+ var authenticate_md5sess =
+ 'Digest realm="secret", domain="/", qop=auth,' +
+ 'algorithm=MD5, nonce="' +
+ nonce +
+ '" opaque="' +
+ opaque +
+ '"';
+ if (test_name == "digest_md5") {
+ response.setHeader("WWW-Authenticate", authenticate_md5, false);
+ } else if (test_name == "digest_md5sess") {
+ response.setHeader("WWW-Authenticate", authenticate_md5sess, false);
+ }
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function authDigestMD5(metadata, response) {
+ authDigestMD5_helper(metadata, response, "digest_md5");
+}
+
+function authDigestMD5sess(metadata, response) {
+ authDigestMD5_helper(metadata, response, "digest_md5sess");
+}
+
+function authDigestSHA256_helper(metadata, response, test_name) {
+ var nonce = "6f93719059cf8d568005727f3250e798";
+ var opaque = "1234opaque1234";
+ var body;
+ var send_401 = 0;
+ // check creds if we have them
+ if (metadata.hasHeader("Authorization")) {
+ var cnonceRE = /cnonce="(\w+)"/;
+ var responseRE = /response="(\w+)"/;
+ var usernameRE = /username="(\w+)"/;
+ var algorithmRE = /algorithm=([\w-]+)/;
+ var auth = metadata.getHeader("Authorization");
+ var cnonce = auth.match(cnonceRE)[1];
+ var clientDigest = auth.match(responseRE)[1];
+ var username = auth.match(usernameRE)[1];
+ var algorithm = auth.match(algorithmRE)[1];
+ var nc = "00000001";
+
+ if (username != "guest") {
+ response.setStatusLine(metadata.httpVersion, 400, "bad request");
+ body = "should never get here";
+ } else if (algorithm != "SHA-256" && algorithm != "SHA-256-sess") {
+ response.setStatusLine(metadata.httpVersion, 400, "bad request");
+ body = "Algorithm must be same as provided in WWW-Authenticate header";
+ } else {
+ // see RFC7616 for the description of this calculation
+ var A1 = "guest:secret:guest";
+ if (algorithm == "SHA-256-sess") {
+ A1 = [HSHA256(A1), nonce, cnonce].join(":");
+ }
+ var A2 = "GET:/auth/" + test_name;
+ var noncebits = [nonce, nc, cnonce, "auth", HSHA256(A2)].join(":");
+ var digest = HSHA256([HSHA256(A1), noncebits].join(":"));
+
+ if (clientDigest == digest) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ body = "success";
+ } else {
+ send_401 = 1;
+ body = "auth failed";
+ }
+ }
+ } else {
+ // no header, send one
+ send_401 = 1;
+ body = "failed, no header";
+ }
+
+ if (send_401) {
+ var authenticate_sha256 =
+ 'Digest realm="secret", domain="/", qop=auth, ' +
+ 'algorithm=SHA-256, nonce="' +
+ nonce +
+ '", opaque="' +
+ opaque +
+ '"';
+ var authenticate_sha256sess =
+ 'Digest realm="secret", domain="/", qop=auth, ' +
+ 'algorithm=SHA-256-sess, nonce="' +
+ nonce +
+ '", opaque="' +
+ opaque +
+ '"';
+ var authenticate_md5 =
+ 'Digest realm="secret", domain="/", qop=auth, ' +
+ 'algorithm=MD5, nonce="' +
+ nonce +
+ '", opaque="' +
+ opaque +
+ '"';
+ if (test_name == "digest_sha256") {
+ response.setHeader("WWW-Authenticate", authenticate_sha256, false);
+ } else if (test_name == "digest_sha256sess") {
+ response.setHeader("WWW-Authenticate", authenticate_sha256sess, false);
+ } else if (test_name == "digest_md5_sha256") {
+ response.setHeader("WWW-Authenticate", authenticate_md5, false);
+ response.setHeader("WWW-Authenticate", authenticate_sha256, true);
+ } else if (test_name == "digest_md5_sha256_oneline") {
+ response.setHeader(
+ "WWW-Authenticate",
+ authenticate_md5 + " " + authenticate_sha256,
+ false
+ );
+ } else if (test_name == "digest_sha256_md5") {
+ response.setHeader("WWW-Authenticate", authenticate_sha256, false);
+ response.setHeader("WWW-Authenticate", authenticate_md5, true);
+ }
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function authDigestSHA256(metadata, response) {
+ authDigestSHA256_helper(metadata, response, "digest_sha256");
+}
+
+function authDigestSHA256sess(metadata, response) {
+ authDigestSHA256_helper(metadata, response, "digest_sha256sess");
+}
+
+function authDigestSHA256_MD5(metadata, response) {
+ authDigestSHA256_helper(metadata, response, "digest_sha256_md5");
+}
+
+function authDigestMD5_SHA256(metadata, response) {
+ authDigestSHA256_helper(metadata, response, "digest_md5_sha256");
+}
+
+function authDigestMD5_SHA256_oneline(metadata, response) {
+ authDigestSHA256_helper(metadata, response, "digest_md5_sha256_oneline");
+}
+
+function authShortDigest(metadata, response) {
+ // no header, send one
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "Digest", false);
+}
+
+let buildLargePayload = (function () {
+ let size = 33 * 1024;
+ let ret = "";
+ return function () {
+ // Return cached value.
+ if (ret.length) {
+ return ret;
+ }
+ for (let i = 0; i < size; i++) {
+ ret += "a";
+ }
+ return ret;
+ };
+})();
+
+function largeRealm(metadata, response) {
+ // test > 32KB realm tokens
+ var body;
+
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader(
+ "WWW-Authenticate",
+ 'Digest realm="' + buildLargePayload() + '", domain="foo"'
+ );
+
+ body = "need to authenticate";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function largeDomain(metadata, response) {
+ // test > 32KB domain tokens
+ var body;
+
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader(
+ "WWW-Authenticate",
+ 'Digest realm="foo", domain="' + buildLargePayload() + '"'
+ );
+
+ body = "need to authenticate";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+add_task(async function test_large_realm() {
+ var chan = makeChan(URL + "/largeRealm", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+});
+
+add_task(async function test_large_domain() {
+ var chan = makeChan(URL + "/largeDomain", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ await openAndListen(chan);
+});
+
+async function add_parse_realm_testcase(testcase) {
+ httpserv.registerPathHandler("/parse_realm", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", testcase.input, false);
+
+ let body = "failed";
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ let chan = makeChan(URL + "/parse_realm", URL);
+ let requestor = new RealmTestRequestor();
+ chan.notificationCallbacks = requestor;
+
+ listener.expectedCode = 401;
+ await openAndListen(chan);
+ Assert.equal(requestor.promptRealm, testcase.realm);
+}
+
+add_task(async function simplebasic() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="foo"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasiclf() {
+ await add_parse_realm_testcase({
+ input: `Basic\r\n realm="foo"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasicucase() {
+ await add_parse_realm_testcase({
+ input: `BASIC REALM="foo"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasictok() {
+ await add_parse_realm_testcase({
+ input: `Basic realm=foo`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasictokbs() {
+ await add_parse_realm_testcase({
+ input: `Basic realm=\\f\\o\\o`,
+ scheme: `Basic`,
+ realm: `\\foo`,
+ });
+});
+
+add_task(async function simplebasicsq() {
+ await add_parse_realm_testcase({
+ input: `Basic realm='foo'`,
+ scheme: `Basic`,
+ realm: `'foo'`,
+ });
+});
+
+add_task(async function simplebasicpct() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="foo%20bar"`,
+ scheme: `Basic`,
+ realm: `foo%20bar`,
+ });
+});
+
+add_task(async function simplebasiccomma() {
+ await add_parse_realm_testcase({
+ input: `Basic , realm="foo"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasiccomma2() {
+ await add_parse_realm_testcase({
+ input: `Basic, realm="foo"`,
+ scheme: `Basic`,
+ realm: ``,
+ });
+});
+
+add_task(async function simplebasicnorealm() {
+ await add_parse_realm_testcase({
+ input: `Basic`,
+ scheme: `Basic`,
+ realm: ``,
+ });
+});
+
+add_task(async function simplebasic2realms() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="foo", realm="bar"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasicwsrealm() {
+ await add_parse_realm_testcase({
+ input: `Basic realm = "foo"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasicrealmsqc() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="\\f\\o\\o"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasicrealmsqc2() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="\\"foo\\""`,
+ scheme: `Basic`,
+ realm: `"foo"`,
+ });
+});
+
+add_task(async function simplebasicnewparam1() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="foo", bar="xyz",, a=b,,,c=d`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasicnewparam2() {
+ await add_parse_realm_testcase({
+ input: `Basic bar="xyz", realm="foo"`,
+ scheme: `Basic`,
+ realm: `foo`,
+ });
+});
+
+add_task(async function simplebasicrealmiso88591() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="foo-ä"`,
+ scheme: `Basic`,
+ realm: `foo-ä`,
+ });
+});
+
+add_task(async function simplebasicrealmutf8() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="foo-ä"`,
+ scheme: `Basic`,
+ realm: `foo-ä`,
+ });
+});
+
+add_task(async function simplebasicrealmrfc2047() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="=?ISO-8859-1?Q?foo-=E4?="`,
+ scheme: `Basic`,
+ realm: `=?ISO-8859-1?Q?foo-=E4?=`,
+ });
+});
+
+add_task(async function multibasicunknown() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="basic", Newauth realm="newauth"`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
+
+add_task(async function multibasicunknownnoparam() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="basic", Newauth`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
+
+add_task(async function multibasicunknown2() {
+ await add_parse_realm_testcase({
+ input: `Newauth realm="newauth", Basic realm="basic"`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
+
+add_task(async function multibasicunknown2np() {
+ await add_parse_realm_testcase({
+ input: `Newauth, Basic realm="basic"`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
+
+add_task(async function multibasicunknown2mf() {
+ httpserv.registerPathHandler("/parse_realm", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", `Newauth realm="newauth"`, false);
+ response.setHeader("WWW-Authenticate", `Basic realm="basic"`, false);
+
+ let body = "failed";
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ let chan = makeChan(URL + "/parse_realm", URL);
+ let requestor = new RealmTestRequestor();
+ chan.notificationCallbacks = requestor;
+
+ listener.expectedCode = 401;
+ await openAndListen(chan);
+ Assert.equal(requestor.promptRealm, "basic");
+});
+
+add_task(async function multibasicempty() {
+ await add_parse_realm_testcase({
+ input: `,Basic realm="basic"`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
+
+add_task(async function multibasicqs() {
+ await add_parse_realm_testcase({
+ input: `Newauth realm="apps", type=1, title="Login to \"apps\"", Basic realm="simple"`,
+ scheme: `Basic`,
+ realm: `simple`,
+ });
+});
+
+add_task(async function multidisgscheme() {
+ await add_parse_realm_testcase({
+ input: `Newauth realm="Newauth Realm", basic=foo, Basic realm="Basic Realm"`,
+ scheme: `Basic`,
+ realm: `Basic Realm`,
+ });
+});
+
+add_task(async function unknown() {
+ await add_parse_realm_testcase({
+ input: `Newauth param="value"`,
+ scheme: `Basic`,
+ realm: ``,
+ });
+});
+
+add_task(async function parametersnotrequired() {
+ await add_parse_realm_testcase({ input: `A, B`, scheme: `Basic`, realm: `` });
+});
+
+add_task(async function disguisedrealm() {
+ await add_parse_realm_testcase({
+ input: `Basic foo="realm=nottherealm", realm="basic"`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
+
+add_task(async function disguisedrealm2() {
+ await add_parse_realm_testcase({
+ input: `Basic nottherealm="nottherealm", realm="basic"`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
+
+add_task(async function missingquote() {
+ await add_parse_realm_testcase({
+ input: `Basic realm="basic`,
+ scheme: `Basic`,
+ realm: `basic`,
+ });
+});
diff --git a/netwerk/test/unit/test_authpromptwrapper.js b/netwerk/test/unit/test_authpromptwrapper.js
new file mode 100644
index 0000000000..69680354ab
--- /dev/null
+++ b/netwerk/test/unit/test_authpromptwrapper.js
@@ -0,0 +1,207 @@
+// NOTE: This tests code outside of Necko. The test still lives here because
+// the contract is part of Necko.
+
+// TODO:
+// - HTTPS
+// - Proxies
+
+"use strict";
+
+const nsIAuthInformation = Ci.nsIAuthInformation;
+const nsIAuthPromptAdapterFactory = Ci.nsIAuthPromptAdapterFactory;
+
+function run_test() {
+ const contractID = "@mozilla.org/network/authprompt-adapter-factory;1";
+ if (!(contractID in Cc)) {
+ print("No adapter factory found, skipping testing");
+ return;
+ }
+ var adapter = Cc[contractID].getService();
+ Assert.equal(adapter instanceof nsIAuthPromptAdapterFactory, true);
+
+ // NOTE: xpconnect lets us get away with passing an empty object here
+ // For this part of the test, we only care that this function returns
+ // success
+ Assert.notEqual(adapter.createAdapter({}), null);
+
+ const host = "www.mozilla.org";
+
+ var info = {
+ username: "",
+ password: "",
+ domain: "",
+
+ flags: nsIAuthInformation.AUTH_HOST,
+ authenticationScheme: "basic",
+ realm: "secretrealm",
+ };
+
+ const CALLED_PROMPT = 1 << 0;
+ const CALLED_PROMPTUP = 1 << 1;
+ const CALLED_PROMPTP = 1 << 2;
+ function Prompt1() {}
+ Prompt1.prototype = {
+ called: 0,
+ rv: true,
+
+ user: "foo\\bar",
+ pw: "bar",
+
+ scheme: "http",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
+
+ prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+ this.called |= CALLED_PROMPT;
+ this.doChecks(text, realm);
+ return this.rv;
+ },
+
+ promptUsernameAndPassword: function ap1_promptUP(
+ title,
+ text,
+ realm,
+ savePW,
+ user,
+ pw
+ ) {
+ this.called |= CALLED_PROMPTUP;
+ this.doChecks(text, realm);
+ user.value = this.user;
+ pw.value = this.pw;
+ return this.rv;
+ },
+
+ promptPassword: function ap1_promptPW(title, text, realm, save, pwd) {
+ this.called |= CALLED_PROMPTP;
+ this.doChecks(text, realm);
+ pwd.value = this.pw;
+ return this.rv;
+ },
+
+ doChecks: function ap1_check(text, realm) {
+ Assert.equal(this.scheme + "://" + host + " (" + info.realm + ")", realm);
+
+ Assert.notEqual(text.indexOf(host), -1);
+ if (info.flags & nsIAuthInformation.ONLY_PASSWORD) {
+ // Should have the username in the text
+ Assert.notEqual(text.indexOf(info.username), -1);
+ } else {
+ // Make sure that we show the realm if we have one and that we don't
+ // show "" otherwise
+ if (info.realm != "") {
+ Assert.notEqual(text.indexOf(info.realm), -1);
+ } else {
+ Assert.equal(text.indexOf('""'), -1);
+ }
+ // No explicit port in the URL; message should not contain -1
+ // for those cases
+ Assert.equal(text.indexOf("-1"), -1);
+ }
+ },
+ };
+
+ // Also have to make up a channel
+ var uri = NetUtil.newURI("http://" + host);
+ var chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+
+ function do_tests(expectedRV) {
+ var prompt1;
+ var wrapper;
+
+ // 1: The simple case
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ wrapper = adapter.createAdapter(prompt1);
+
+ var rv = wrapper.promptAuth(chan, 0, info);
+ Assert.equal(rv, prompt1.rv);
+ Assert.equal(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ Assert.equal(info.domain, "");
+ Assert.equal(info.username, prompt1.user);
+ Assert.equal(info.password, prompt1.pw);
+ }
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 2: Only ask for a PW
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ info.flags |= nsIAuthInformation.ONLY_PASSWORD;
+
+ // Initialize the username so that the prompt can show it
+ info.username = prompt1.user;
+
+ wrapper = adapter.createAdapter(prompt1);
+ rv = wrapper.promptAuth(chan, 0, info);
+ Assert.equal(rv, prompt1.rv);
+ Assert.equal(prompt1.called, CALLED_PROMPTP);
+
+ if (rv) {
+ Assert.equal(info.domain, "");
+ Assert.equal(info.username, prompt1.user); // we initialized this
+ Assert.equal(info.password, prompt1.pw);
+ }
+
+ info.flags &= ~nsIAuthInformation.ONLY_PASSWORD;
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 3: user, pw and domain
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ info.flags |= nsIAuthInformation.NEED_DOMAIN;
+
+ wrapper = adapter.createAdapter(prompt1);
+ rv = wrapper.promptAuth(chan, 0, info);
+ Assert.equal(rv, prompt1.rv);
+ Assert.equal(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ Assert.equal(info.domain, "foo");
+ Assert.equal(info.username, "bar");
+ Assert.equal(info.password, prompt1.pw);
+ }
+
+ info.flags &= ~nsIAuthInformation.NEED_DOMAIN;
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 4: username that doesn't contain a domain
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ info.flags |= nsIAuthInformation.NEED_DOMAIN;
+
+ prompt1.user = "foo";
+
+ wrapper = adapter.createAdapter(prompt1);
+ rv = wrapper.promptAuth(chan, 0, info);
+ Assert.equal(rv, prompt1.rv);
+ Assert.equal(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ Assert.equal(info.domain, "");
+ Assert.equal(info.username, prompt1.user);
+ Assert.equal(info.password, prompt1.pw);
+ }
+
+ info.flags &= ~nsIAuthInformation.NEED_DOMAIN;
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+ }
+ do_tests(true);
+ do_tests(false);
+}
diff --git a/netwerk/test/unit/test_backgroundfilesaver.js b/netwerk/test/unit/test_backgroundfilesaver.js
new file mode 100644
index 0000000000..eeceab9bf8
--- /dev/null
+++ b/netwerk/test/unit/test_backgroundfilesaver.js
@@ -0,0 +1,761 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests components that implement nsIBackgroundFileSaver.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
+});
+
+const BackgroundFileSaverOutputStream = Components.Constructor(
+ "@mozilla.org/network/background-file-saver;1?mode=outputstream",
+ "nsIBackgroundFileSaver"
+);
+
+const BackgroundFileSaverStreamListener = Components.Constructor(
+ "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
+ "nsIBackgroundFileSaver"
+);
+
+const StringInputStream = Components.Constructor(
+ "@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream",
+ "setData"
+);
+
+const REQUEST_SUSPEND_AT = 1024 * 1024 * 4;
+const TEST_DATA_SHORT = "This test string is written to the file.";
+const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
+const TEST_FILE_NAME_2 = "test-backgroundfilesaver-2.txt";
+const TEST_FILE_NAME_3 = "test-backgroundfilesaver-3.txt";
+
+// A map of test data length to the expected SHA-256 hashes
+const EXPECTED_HASHES = {
+ // No data
+ 0: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ // TEST_DATA_SHORT
+ 40: "f37176b690e8744ee990a206c086cba54d1502aa2456c3b0c84ef6345d72a192",
+ // TEST_DATA_SHORT + TEST_DATA_SHORT
+ 80: "780c0e91f50bb7ec922cc11e16859e6d5df283c0d9470f61772e3d79f41eeb58",
+ // TEST_DATA_LONG
+ 4718592: "372cb9e5ce7b76d3e2a5042e78aa72dcf973e659a262c61b7ff51df74b36767b",
+ // TEST_DATA_LONG + TEST_DATA_LONG
+ 9437184: "693e4f8c6855a6fed4f5f9370d12cc53105672f3ff69783581e7d925984c41d3",
+};
+
+// Generate a long string of data in a moderately fast way.
+const TEST_256_CHARS = new Array(257).join("-");
+const DESIRED_LENGTH = REQUEST_SUSPEND_AT * 1.125;
+const TEST_DATA_LONG = new Array(1 + DESIRED_LENGTH / 256).join(TEST_256_CHARS);
+Assert.equal(TEST_DATA_LONG.length, DESIRED_LENGTH);
+
+/**
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
+ */
+function getTempFile(leafName) {
+ return FileTestUtils.getTempFile(leafName);
+}
+
+/**
+ * Helper function for converting a binary blob to its hex equivalent.
+ *
+ * @param str
+ * String possibly containing non-printable chars.
+ * @return A hex-encoded string.
+ */
+function toHex(str) {
+ var hex = "";
+ for (var i = 0; i < str.length; i++) {
+ hex += ("0" + str.charCodeAt(i).toString(16)).slice(-2);
+ }
+ return hex;
+}
+
+/**
+ * Ensures that the given file contents are equal to the given string.
+ *
+ * @param aFile
+ * nsIFile whose contents should be verified.
+ * @param aExpectedContents
+ * String containing the octets that are expected in the file.
+ *
+ * @return {Promise}
+ * @resolves When the operation completes.
+ * @rejects Never.
+ */
+function promiseVerifyContents(aFile, aExpectedContents) {
+ return new Promise(resolve => {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(aFile),
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aStatus) {
+ Assert.ok(Components.isSuccessCode(aStatus));
+ let contents = NetUtil.readInputStreamToString(
+ aInputStream,
+ aInputStream.available()
+ );
+ if (contents.length <= TEST_DATA_SHORT.length * 2) {
+ Assert.equal(contents, aExpectedContents);
+ } else {
+ // Do not print the entire content string to the test log.
+ Assert.equal(contents.length, aExpectedContents.length);
+ Assert.ok(contents == aExpectedContents);
+ }
+ resolve();
+ }
+ );
+ });
+}
+
+/**
+ * Waits for the given saver object to complete.
+ *
+ * @param aSaver
+ * The saver, with the output stream or a stream listener implementation.
+ * @param aOnTargetChangeFn
+ * Optional callback invoked with the target file name when it changes.
+ *
+ * @return {Promise}
+ * @resolves When onSaveComplete is called with a success code.
+ * @rejects With an exception, if onSaveComplete is called with a failure code.
+ */
+function promiseSaverComplete(aSaver, aOnTargetChangeFn) {
+ return new Promise((resolve, reject) => {
+ aSaver.observer = {
+ onTargetChange: function BFSO_onSaveComplete(saver, aTarget) {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(saver, aStatus) {
+ if (Components.isSuccessCode(aStatus)) {
+ resolve();
+ } else {
+ reject(new Components.Exception("Saver failed.", aStatus));
+ }
+ },
+ };
+ });
+}
+
+/**
+ * Feeds a string to a BackgroundFileSaverOutputStream.
+ *
+ * @param aSourceString
+ * The source data to copy.
+ * @param aSaverOutputStream
+ * The BackgroundFileSaverOutputStream to feed.
+ * @param aCloseWhenDone
+ * If true, the output stream will be closed when the copy finishes.
+ *
+ * @return {Promise}
+ * @resolves When the copy completes with a success code.
+ * @rejects With an exception, if the copy fails.
+ */
+function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) {
+ return new Promise((resolve, reject) => {
+ let inputStream = new StringInputStream(
+ aSourceString,
+ aSourceString.length
+ );
+ let copier = Cc[
+ "@mozilla.org/network/async-stream-copier;1"
+ ].createInstance(Ci.nsIAsyncStreamCopier);
+ copier.init(
+ inputStream,
+ aSaverOutputStream,
+ null,
+ false,
+ true,
+ 0x8000,
+ true,
+ aCloseWhenDone
+ );
+ copier.asyncCopy(
+ {
+ onStartRequest() {},
+ onStopRequest(aRequest, aStatusCode) {
+ if (Components.isSuccessCode(aStatusCode)) {
+ resolve();
+ } else {
+ reject(new Components.Exception(aStatusCode));
+ }
+ },
+ },
+ null
+ );
+ });
+}
+
+/**
+ * Feeds a string to a BackgroundFileSaverStreamListener.
+ *
+ * @param aSourceString
+ * The source data to copy.
+ * @param aSaverStreamListener
+ * The BackgroundFileSaverStreamListener to feed.
+ * @param aCloseWhenDone
+ * If true, the output stream will be closed when the copy finishes.
+ *
+ * @return {Promise}
+ * @resolves When the operation completes with a success code.
+ * @rejects With an exception, if the operation fails.
+ */
+function promisePumpToSaver(
+ aSourceString,
+ aSaverStreamListener,
+ aCloseWhenDone
+) {
+ return new Promise((resolve, reject) => {
+ aSaverStreamListener.QueryInterface(Ci.nsIStreamListener);
+ let inputStream = new StringInputStream(
+ aSourceString,
+ aSourceString.length
+ );
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
+ Ci.nsIInputStreamPump
+ );
+ pump.init(inputStream, 0, 0, true);
+ pump.asyncRead({
+ onStartRequest: function PPTS_onStartRequest(aRequest) {
+ aSaverStreamListener.onStartRequest(aRequest);
+ },
+ onStopRequest: function PPTS_onStopRequest(aRequest, aStatusCode) {
+ aSaverStreamListener.onStopRequest(aRequest, aStatusCode);
+ if (Components.isSuccessCode(aStatusCode)) {
+ resolve();
+ } else {
+ reject(new Components.Exception(aStatusCode));
+ }
+ },
+ onDataAvailable: function PPTS_onDataAvailable(
+ aRequest,
+ aInputStream,
+ aOffset,
+ aCount
+ ) {
+ aSaverStreamListener.onDataAvailable(
+ aRequest,
+ aInputStream,
+ aOffset,
+ aCount
+ );
+ },
+ });
+ });
+}
+
+var gStillRunning = true;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+add_task(function test_setup() {
+ // Wait 10 minutes, that is half of the external xpcshell timeout.
+ do_timeout(10 * 60 * 1000, function () {
+ if (gStillRunning) {
+ do_throw("Test timed out.");
+ }
+ });
+});
+
+add_task(async function test_normal() {
+ // This test demonstrates the most basic use case.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Create the object implementing the output stream.
+ let saver = new BackgroundFileSaverOutputStream();
+
+ // Set up callbacks for completion and target file name change.
+ let receivedOnTargetChange = false;
+ function onTargetChange(aTarget) {
+ Assert.ok(destFile.equals(aTarget));
+ receivedOnTargetChange = true;
+ }
+ let completionPromise = promiseSaverComplete(saver, onTargetChange);
+
+ // Set the target file.
+ saver.setTarget(destFile, false);
+
+ // Write some data and close the output stream.
+ await promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ // Indicate that we are ready to finish, and wait for a successful callback.
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Only after we receive the completion notification, we can also be sure that
+ // we've received the target file name change notification before it.
+ Assert.ok(receivedOnTargetChange);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(async function test_combinations() {
+ let initialFile = getTempFile(TEST_FILE_NAME_1);
+ let renamedFile = getTempFile(TEST_FILE_NAME_2);
+
+ // Keep track of the current file.
+ let currentFile = null;
+ function onTargetChange(aTarget) {
+ currentFile = null;
+ info("Target file changed to: " + aTarget.leafName);
+ currentFile = aTarget;
+ }
+
+ // Tests various combinations of events and behaviors for both the stream
+ // listener and the output stream implementations.
+ for (let testFlags = 0; testFlags < 32; testFlags++) {
+ let keepPartialOnFailure = !!(testFlags & 1);
+ let renameAtSomePoint = !!(testFlags & 2);
+ let cancelAtSomePoint = !!(testFlags & 4);
+ let useStreamListener = !!(testFlags & 8);
+ let useLongData = !!(testFlags & 16);
+
+ let startTime = Date.now();
+ info(
+ "Starting keepPartialOnFailure = " +
+ keepPartialOnFailure +
+ ", renameAtSomePoint = " +
+ renameAtSomePoint +
+ ", cancelAtSomePoint = " +
+ cancelAtSomePoint +
+ ", useStreamListener = " +
+ useStreamListener +
+ ", useLongData = " +
+ useLongData
+ );
+
+ // Create the object and register the observers.
+ currentFile = null;
+ let saver = useStreamListener
+ ? new BackgroundFileSaverStreamListener()
+ : new BackgroundFileSaverOutputStream();
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver, onTargetChange);
+
+ // Start feeding the first chunk of data to the saver. In case we are using
+ // the stream listener, we only write one chunk.
+ let testData = useLongData ? TEST_DATA_LONG : TEST_DATA_SHORT;
+ let feedPromise = useStreamListener
+ ? promisePumpToSaver(testData + testData, saver)
+ : promiseCopyToSaver(testData, saver, false);
+
+ // Set a target output file.
+ saver.setTarget(initialFile, keepPartialOnFailure);
+
+ // Wait for the first chunk of data to be copied.
+ await feedPromise;
+
+ if (renameAtSomePoint) {
+ saver.setTarget(renamedFile, keepPartialOnFailure);
+ }
+
+ if (cancelAtSomePoint) {
+ saver.finish(Cr.NS_ERROR_FAILURE);
+ }
+
+ // Feed the second chunk of data to the saver.
+ if (!useStreamListener) {
+ await promiseCopyToSaver(testData, saver, true);
+ }
+
+ // Wait for completion, and ensure we succeeded or failed as expected.
+ if (!cancelAtSomePoint) {
+ saver.finish(Cr.NS_OK);
+ }
+ try {
+ await completionPromise;
+ if (cancelAtSomePoint) {
+ do_throw("Failure expected.");
+ }
+ } catch (ex) {
+ if (!cancelAtSomePoint || ex.result != Cr.NS_ERROR_FAILURE) {
+ throw ex;
+ }
+ }
+
+ if (!cancelAtSomePoint) {
+ // In this case, the file must exist.
+ Assert.ok(currentFile.exists());
+ let expectedContents = testData + testData;
+ await promiseVerifyContents(currentFile, expectedContents);
+ Assert.equal(
+ EXPECTED_HASHES[expectedContents.length],
+ toHex(saver.sha256Hash)
+ );
+ currentFile.remove(false);
+
+ // If the target was really renamed, the old file should not exist.
+ if (renamedFile.equals(currentFile)) {
+ Assert.ok(!initialFile.exists());
+ }
+ } else if (!keepPartialOnFailure) {
+ // In this case, the file must not exist.
+ Assert.ok(!initialFile.exists());
+ Assert.ok(!renamedFile.exists());
+ } else {
+ // In this case, the file may or may not exist, because canceling can
+ // interrupt the asynchronous operation at any point, even before the file
+ // has been created for the first time.
+ if (initialFile.exists()) {
+ initialFile.remove(false);
+ }
+ if (renamedFile.exists()) {
+ renamedFile.remove(false);
+ }
+ }
+
+ info("Test case completed in " + (Date.now() - startTime) + " ms.");
+ }
+});
+
+add_task(async function test_setTarget_after_close_stream() {
+ // This test checks the case where we close the output stream before we call
+ // the setTarget method. All the data should be buffered and written anyway.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver);
+
+ // Copy some data to the output stream of the file saver. This data must
+ // be shorter than the internal component's pipe buffer for the test to
+ // succeed, because otherwise the test would block waiting for the write to
+ // complete.
+ await promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ // Set the target file and wait for the output to finish.
+ saver.setTarget(destFile, false);
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results.
+ await promiseVerifyContents(destFile, TEST_DATA_SHORT);
+ Assert.equal(
+ EXPECTED_HASHES[TEST_DATA_SHORT.length],
+ toHex(saver.sha256Hash)
+ );
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(async function test_setTarget_fast() {
+ // This test checks a fast rename of the target file.
+ let destFile1 = getTempFile(TEST_FILE_NAME_1);
+ let destFile2 = getTempFile(TEST_FILE_NAME_2);
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ // Set the initial name after the stream is closed, then rename immediately.
+ await promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+ saver.setTarget(destFile1, false);
+ saver.setTarget(destFile2, false);
+
+ // Wait for all the operations to complete.
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results and clean up.
+ Assert.ok(!destFile1.exists());
+ await promiseVerifyContents(destFile2, TEST_DATA_SHORT);
+ destFile2.remove(false);
+});
+
+add_task(async function test_setTarget_multiple() {
+ // This test checks multiple renames of the target file.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ // Rename both before and after the stream is closed.
+ saver.setTarget(getTempFile(TEST_FILE_NAME_2), false);
+ saver.setTarget(getTempFile(TEST_FILE_NAME_3), false);
+ await promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+ saver.setTarget(getTempFile(TEST_FILE_NAME_2), false);
+ saver.setTarget(destFile, false);
+
+ // Wait for all the operations to complete.
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results and clean up.
+ Assert.ok(!getTempFile(TEST_FILE_NAME_2).exists());
+ Assert.ok(!getTempFile(TEST_FILE_NAME_3).exists());
+ await promiseVerifyContents(destFile, TEST_DATA_SHORT);
+ destFile.remove(false);
+});
+
+add_task(async function test_enableAppend() {
+ // This test checks append mode with hashing disabled.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableAppend();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ await promiseCopyToSaver(TEST_DATA_LONG, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results.
+ let expectedContents =
+ i == 0 ? TEST_DATA_LONG : TEST_DATA_LONG + TEST_DATA_LONG;
+ await promiseVerifyContents(destFile, expectedContents);
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(async function test_enableAppend_setTarget_fast() {
+ // This test checks a fast rename of the target file in append mode.
+ let destFile1 = getTempFile(TEST_FILE_NAME_1);
+ let destFile2 = getTempFile(TEST_FILE_NAME_2);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableAppend();
+ let completionPromise = promiseSaverComplete(saver);
+
+ await promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ // The first time, we start appending to the first file and rename to the
+ // second file. The second time, we start appending to the second file,
+ // that was created the first time, and rename back to the first file.
+ let firstFile = i == 0 ? destFile1 : destFile2;
+ let secondFile = i == 0 ? destFile2 : destFile1;
+ saver.setTarget(firstFile, false);
+ saver.setTarget(secondFile, false);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results.
+ Assert.ok(!firstFile.exists());
+ let expectedContents =
+ i == 0 ? TEST_DATA_SHORT : TEST_DATA_SHORT + TEST_DATA_SHORT;
+ await promiseVerifyContents(secondFile, expectedContents);
+ }
+
+ // Clean up.
+ destFile1.remove(false);
+});
+
+add_task(async function test_enableAppend_hash() {
+ // This test checks append mode, also verifying that the computed hash
+ // includes the contents of the existing data.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableAppend();
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ await promiseCopyToSaver(TEST_DATA_LONG, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results.
+ let expectedContents =
+ i == 0 ? TEST_DATA_LONG : TEST_DATA_LONG + TEST_DATA_LONG;
+ await promiseVerifyContents(destFile, expectedContents);
+ Assert.equal(
+ EXPECTED_HASHES[expectedContents.length],
+ toHex(saver.sha256Hash)
+ );
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(async function test_finish_only() {
+ // This test checks creating the object and doing nothing.
+ let saver = new BackgroundFileSaverOutputStream();
+ function onTargetChange(aTarget) {
+ do_throw("Should not receive the onTargetChange notification.");
+ }
+ let completionPromise = promiseSaverComplete(saver, onTargetChange);
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+});
+
+add_task(async function test_empty() {
+ // This test checks we still create an empty file when no data is fed.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ await promiseCopyToSaver("", saver, true);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results.
+ Assert.ok(destFile.exists());
+ Assert.equal(destFile.fileSize, 0);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(async function test_empty_hash() {
+ // This test checks the hash of an empty file, both in normal and append mode.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test normal mode first, then append mode.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ if (i == 1) {
+ saver.enableAppend();
+ }
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ await promiseCopyToSaver("", saver, true);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // Verify results.
+ Assert.equal(destFile.fileSize, 0);
+ Assert.equal(EXPECTED_HASHES[0], toHex(saver.sha256Hash));
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(async function test_invalid_hash() {
+ let saver = new BackgroundFileSaverStreamListener();
+ let completionPromise = promiseSaverComplete(saver);
+ // We shouldn't be able to get the hash if hashing hasn't been enabled
+ try {
+ saver.sha256Hash;
+ do_throw("Shouldn't be able to get hash if hashing not enabled");
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw ex;
+ }
+ }
+ // Enable hashing, but don't feed any data to saver
+ saver.enableSha256();
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+ saver.setTarget(destFile, false);
+ // We don't wait on promiseSaverComplete, so the hash getter can run before
+ // or after onSaveComplete is called. However, the expected behavior is the
+ // same in both cases since the hash is only valid when the save completes
+ // successfully.
+ saver.finish(Cr.NS_ERROR_FAILURE);
+ try {
+ saver.sha256Hash;
+ do_throw("Shouldn't be able to get hash if save did not succeed");
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw ex;
+ }
+ }
+ // Wait for completion so that the worker thread finishes dealing with the
+ // target file. We expect it to fail.
+ try {
+ await completionPromise;
+ do_throw("completionPromise should throw");
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_FAILURE) {
+ throw ex;
+ }
+ }
+});
+
+add_task(async function test_signature() {
+ // Check that we get a signature if the saver is finished.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ try {
+ saver.signatureInfo;
+ do_throw("Can't get signature if saver is not complete");
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw ex;
+ }
+ }
+
+ saver.enableSignatureInfo();
+ saver.setTarget(destFile, false);
+ await promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+ await promiseVerifyContents(destFile, TEST_DATA_SHORT);
+
+ // signatureInfo is an empty nsIArray
+ Assert.equal(0, saver.signatureInfo.length);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(async function test_signature_not_enabled() {
+ // Check that we get a signature if the saver is finished on Windows.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+ saver.setTarget(destFile, false);
+ await promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+ try {
+ saver.signatureInfo;
+ do_throw("Can't get signature if not enabled");
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw ex;
+ }
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_teardown() {
+ gStillRunning = false;
+});
diff --git a/netwerk/test/unit/test_be_conservative.js b/netwerk/test/unit/test_be_conservative.js
new file mode 100644
index 0000000000..af8cf23976
--- /dev/null
+++ b/netwerk/test/unit/test_be_conservative.js
@@ -0,0 +1,256 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+
+// Allow telemetry probes which may otherwise be disabled for some
+// applications (e.g. Thunderbird).
+Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+);
+
+// Tests that nsIHttpChannelInternal.beConservative correctly limits the use of
+// advanced TLS features that may cause compatibility issues. Does so by
+// starting a TLS server that requires the advanced features and then ensuring
+// that a client that is set to be conservative will fail when connecting.
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+class InputStreamCallback {
+ constructor(output) {
+ this.output = output;
+ this.stopped = false;
+ }
+
+ onInputStreamReady(stream) {
+ info("input stream ready");
+ if (this.stopped) {
+ info("input stream callback stopped - bailing");
+ return;
+ }
+ let available = 0;
+ try {
+ available = stream.available();
+ } catch (e) {
+ // onInputStreamReady may fire when the stream has been closed.
+ equal(
+ e.result,
+ Cr.NS_BASE_STREAM_CLOSED,
+ "error should be NS_BASE_STREAM_CLOSED"
+ );
+ }
+ if (available > 0) {
+ let request = NetUtil.readInputStreamToString(stream, available, {
+ charset: "utf8",
+ });
+ ok(
+ request.startsWith("GET / HTTP/1.1\r\n"),
+ "Should get a simple GET / HTTP/1.1 request"
+ );
+ let response =
+ "HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 2\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\nOK";
+ let written = this.output.write(response, response.length);
+ equal(
+ written,
+ response.length,
+ "should have been able to write entire response"
+ );
+ }
+ this.output.close();
+ info("done with input stream ready");
+ }
+
+ stop() {
+ this.stopped = true;
+ this.output.close();
+ }
+}
+
+class TLSServerSecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ this.callbacks = [];
+ this.stopped = false;
+ }
+
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+ info(`TLS version used: ${status.tlsVersionUsed}`);
+
+ if (this.stopped) {
+ info("handshake done callback stopped - bailing");
+ return;
+ }
+
+ let callback = new InputStreamCallback(this.output);
+ this.callbacks.push(callback);
+ this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
+ }
+
+ stop() {
+ this.stopped = true;
+ this.input.close();
+ this.output.close();
+ this.callbacks.forEach(callback => {
+ callback.stop();
+ });
+ }
+}
+
+class ServerSocketListener {
+ constructor() {
+ this.securityObservers = [];
+ }
+
+ onSocketAccepted(socket, transport) {
+ info("accepted TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ let securityObserver = new TLSServerSecurityObserver(input, output);
+ this.securityObservers.push(securityObserver);
+ connectionInfo.setSecurityObserver(securityObserver);
+ }
+
+ // For some reason we get input stream callback events after we've stopped
+ // listening, so this ensures we just drop those events.
+ onStopListening() {
+ info("onStopListening");
+ this.securityObservers.forEach(observer => {
+ observer.stop();
+ });
+ }
+}
+
+function startServer(cert, minServerVersion, maxServerVersion) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+ tlsServer.setVersionRange(minServerVersion, maxServerVersion);
+ tlsServer.setSessionTickets(false);
+ tlsServer.asyncListen(new ServerSocketListener());
+ return tlsServer;
+}
+
+const hostname = "example.com";
+
+function storeCertOverride(port, cert) {
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true);
+}
+
+function startClient(port, beConservative, expectSuccess) {
+ HandshakeTelemetryHelpers.resetHistograms();
+ let flavors = ["", "_FIRST_TRY"];
+ let nonflavors = [];
+ if (beConservative) {
+ flavors.push("_CONSERVATIVE");
+ nonflavors.push("_ECH");
+ nonflavors.push("_ECH_GREASE");
+ } else {
+ nonflavors.push("_CONSERVATIVE");
+ }
+
+ let req = new XMLHttpRequest();
+ req.open("GET", `https://${hostname}:${port}`);
+ let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.beConservative = beConservative;
+ return new Promise((resolve, reject) => {
+ req.onload = () => {
+ ok(
+ expectSuccess,
+ `should ${expectSuccess ? "" : "not "}have gotten load event`
+ );
+ equal(req.responseText, "OK", "response text should be 'OK'");
+
+ // Only check telemetry if network process is disabled.
+ if (!mozinfo.socketprocess_networking) {
+ HandshakeTelemetryHelpers.checkSuccess(flavors);
+ HandshakeTelemetryHelpers.checkEmpty(nonflavors);
+ }
+
+ resolve();
+ };
+ req.onerror = () => {
+ ok(
+ !expectSuccess,
+ `should ${!expectSuccess ? "" : "not "}have gotten an error`
+ );
+
+ // Only check telemetry if network process is disabled.
+ if (!mozinfo.socketprocess_networking) {
+ // 98 is SSL_ERROR_PROTOCOL_VERSION_ALERT (see sslerr.h)
+ HandshakeTelemetryHelpers.checkEntry(flavors, 98, 1);
+ HandshakeTelemetryHelpers.checkEmpty(nonflavors);
+ }
+
+ resolve();
+ };
+
+ req.send();
+ });
+}
+
+add_task(async function () {
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ Services.prefs.setCharPref("network.dns.localDomains", hostname);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+ let cert = getTestServerCertificate();
+
+ // First run a server that accepts TLS 1.2 and 1.3. A conservative client
+ // should succeed in connecting.
+ let server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(
+ server.port,
+ true /*be conservative*/,
+ true /*should succeed*/
+ );
+ server.close();
+
+ // Now run a server that only accepts TLS 1.3. A conservative client will not
+ // succeed in this case.
+ server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(
+ server.port,
+ true /*be conservative*/,
+ false /*should fail*/
+ );
+
+ // However, a non-conservative client should succeed.
+ await startClient(
+ server.port,
+ false /*don't be conservative*/,
+ true /*should succeed*/
+ );
+ server.close();
+});
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+});
diff --git a/netwerk/test/unit/test_be_conservative_error_handling.js b/netwerk/test/unit/test_be_conservative_error_handling.js
new file mode 100644
index 0000000000..eae3042592
--- /dev/null
+++ b/netwerk/test/unit/test_be_conservative_error_handling.js
@@ -0,0 +1,216 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+
+// Tests that nsIHttpChannelInternal.beConservative correctly limits the use of
+// advanced TLS features that may cause compatibility issues. Does so by
+// starting a TLS server that requires the advanced features and then ensuring
+// that a client that is set to be conservative will fail when connecting.
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+class InputStreamCallback {
+ constructor(output) {
+ this.output = output;
+ this.stopped = false;
+ }
+
+ onInputStreamReady(stream) {
+ info("input stream ready");
+ if (this.stopped) {
+ info("input stream callback stopped - bailing");
+ return;
+ }
+ let available = 0;
+ try {
+ available = stream.available();
+ } catch (e) {
+ // onInputStreamReady may fire when the stream has been closed.
+ equal(
+ e.result,
+ Cr.NS_BASE_STREAM_CLOSED,
+ "error should be NS_BASE_STREAM_CLOSED"
+ );
+ }
+ if (available > 0) {
+ let request = NetUtil.readInputStreamToString(stream, available, {
+ charset: "utf8",
+ });
+ ok(
+ request.startsWith("GET / HTTP/1.1\r\n"),
+ "Should get a simple GET / HTTP/1.1 request"
+ );
+ let response =
+ "HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 2\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\nOK";
+ let written = this.output.write(response, response.length);
+ equal(
+ written,
+ response.length,
+ "should have been able to write entire response"
+ );
+ }
+ this.output.close();
+ info("done with input stream ready");
+ }
+
+ stop() {
+ this.stopped = true;
+ this.output.close();
+ }
+}
+
+class TLSServerSecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ this.callbacks = [];
+ this.stopped = false;
+ }
+
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+ info(`TLS version used: ${status.tlsVersionUsed}`);
+
+ if (this.stopped) {
+ info("handshake done callback stopped - bailing");
+ return;
+ }
+
+ let callback = new InputStreamCallback(this.output);
+ this.callbacks.push(callback);
+ this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
+ }
+
+ stop() {
+ this.stopped = true;
+ this.input.close();
+ this.output.close();
+ this.callbacks.forEach(callback => {
+ callback.stop();
+ });
+ }
+}
+
+class ServerSocketListener {
+ constructor() {
+ this.securityObservers = [];
+ }
+
+ onSocketAccepted(socket, transport) {
+ info("accepted TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ let securityObserver = new TLSServerSecurityObserver(input, output);
+ this.securityObservers.push(securityObserver);
+ connectionInfo.setSecurityObserver(securityObserver);
+ }
+
+ // For some reason we get input stream callback events after we've stopped
+ // listening, so this ensures we just drop those events.
+ onStopListening() {
+ info("onStopListening");
+ this.securityObservers.forEach(observer => {
+ observer.stop();
+ });
+ }
+}
+
+function startServer(cert, minServerVersion, maxServerVersion) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+ tlsServer.setVersionRange(minServerVersion, maxServerVersion);
+ tlsServer.setSessionTickets(false);
+ tlsServer.asyncListen(new ServerSocketListener());
+ return tlsServer;
+}
+
+const hostname = "example.com";
+
+function storeCertOverride(port, cert) {
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true);
+}
+
+function startClient(port, beConservative, expectSuccess) {
+ let req = new XMLHttpRequest();
+ req.open("GET", `https://${hostname}:${port}`);
+ let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.beConservative = beConservative;
+ return new Promise((resolve, reject) => {
+ req.onload = () => {
+ ok(
+ expectSuccess,
+ `should ${expectSuccess ? "" : "not "}have gotten load event`
+ );
+ equal(req.responseText, "OK", "response text should be 'OK'");
+ resolve();
+ };
+ req.onerror = () => {
+ ok(
+ !expectSuccess,
+ `should ${!expectSuccess ? "" : "not "}have gotten an error`
+ );
+ resolve();
+ };
+
+ req.send();
+ });
+}
+
+add_task(async function () {
+ // Restrict to only TLS 1.3.
+ Services.prefs.setIntPref("security.tls.version.min", 4);
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ Services.prefs.setCharPref("network.dns.localDomains", hostname);
+ let cert = getTestServerCertificate();
+
+ // Run a server that accepts TLS 1.2 and 1.3. The connection should succeed.
+ let server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(
+ server.port,
+ true /*be conservative*/,
+ true /*should succeed*/
+ );
+ server.close();
+
+ // Now run a server that only accepts TLS 1.3. A conservative client will not
+ // succeed in this case.
+ server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(
+ server.port,
+ true /*be conservative*/,
+ false /*should fail*/
+ );
+ server.close();
+});
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("security.tls.version.min");
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
diff --git a/netwerk/test/unit/test_bhttp.js b/netwerk/test/unit/test_bhttp.js
new file mode 100644
index 0000000000..61c8066a79
--- /dev/null
+++ b/netwerk/test/unit/test_bhttp.js
@@ -0,0 +1,220 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Unit tests for the binary http bindings.
+// Tests basic encoding and decoding of requests and responses.
+
+function BinaryHttpRequest(
+ method,
+ scheme,
+ authority,
+ path,
+ headerNames,
+ headerValues,
+ content
+) {
+ this.method = method;
+ this.scheme = scheme;
+ this.authority = authority;
+ this.path = path;
+ this.headerNames = headerNames;
+ this.headerValues = headerValues;
+ this.content = content;
+}
+
+BinaryHttpRequest.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIBinaryHttpRequest"]),
+};
+
+function test_encode_request() {
+ let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
+ Ci.nsIBinaryHttp
+ );
+ let request = new BinaryHttpRequest(
+ "GET",
+ "https",
+ "",
+ "/hello.txt",
+ ["user-agent", "host", "accept-language"],
+ [
+ "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3",
+ "www.example.com",
+ "en, mi",
+ ],
+ []
+ );
+ let encoded = bhttp.encodeRequest(request);
+ // This example is from RFC 9292.
+ let expected = hexStringToBytes(
+ "0003474554056874747073000a2f6865" +
+ "6c6c6f2e747874406c0a757365722d61" +
+ "67656e74346375726c2f372e31362e33" +
+ "206c69626375726c2f372e31362e3320" +
+ "4f70656e53534c2f302e392e376c207a" +
+ "6c69622f312e322e3304686f73740f77" +
+ "77772e6578616d706c652e636f6d0f61" +
+ "63636570742d6c616e67756167650665" +
+ "6e2c206d690000"
+ );
+ deepEqual(encoded, expected);
+
+ let mismatchedHeaders = new BinaryHttpRequest(
+ "GET",
+ "https",
+ "",
+ "",
+ ["whoops-only-one-header-name"],
+ ["some-header-value", "some-other-header-value"],
+ []
+ );
+ // The implementation uses "NS_ERROR_INVALID_ARG", because that's an
+ // appropriate description for the error. However, that is an alias to
+ // "NS_ERROR_ILLEGAL_VALUE", which is what the actual exception uses, so
+ // that's what is tested for here.
+ Assert.throws(
+ () => bhttp.encodeRequest(mismatchedHeaders),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+}
+
+function test_decode_request() {
+ let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
+ Ci.nsIBinaryHttp
+ );
+
+ // From RFC 9292.
+ let encoded = hexStringToBytes(
+ "0003474554056874747073000a2f6865" +
+ "6c6c6f2e747874406c0a757365722d61" +
+ "67656e74346375726c2f372e31362e33" +
+ "206c69626375726c2f372e31362e3320" +
+ "4f70656e53534c2f302e392e376c207a" +
+ "6c69622f312e322e3304686f73740f77" +
+ "77772e6578616d706c652e636f6d0f61" +
+ "63636570742d6c616e67756167650665" +
+ "6e2c206d690000"
+ );
+ let request = bhttp.decodeRequest(encoded);
+ equal(request.method, "GET");
+ equal(request.scheme, "https");
+ equal(request.authority, "");
+ equal(request.path, "/hello.txt");
+ let expectedHeaderNames = ["user-agent", "host", "accept-language"];
+ deepEqual(request.headerNames, expectedHeaderNames);
+ let expectedHeaderValues = [
+ "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3",
+ "www.example.com",
+ "en, mi",
+ ];
+ deepEqual(request.headerValues, expectedHeaderValues);
+ deepEqual(request.content, []);
+
+ let garbage = hexStringToBytes("115f00ab64c0fa783fe4cb723eaa87fa78900a0b00");
+ Assert.throws(() => bhttp.decodeRequest(garbage), /NS_ERROR_UNEXPECTED/);
+}
+
+function test_decode_response() {
+ let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
+ Ci.nsIBinaryHttp
+ );
+ // From RFC 9292.
+ let encoded = hexStringToBytes(
+ "0340660772756e6e696e670a22736c65" +
+ "657020313522004067046c696e6b233c" +
+ "2f7374796c652e6373733e3b2072656c" +
+ "3d7072656c6f61643b2061733d737479" +
+ "6c65046c696e6b243c2f736372697074" +
+ "2e6a733e3b2072656c3d7072656c6f61" +
+ "643b2061733d7363726970740040c804" +
+ "646174651d4d6f6e2c203237204a756c" +
+ "20323030392031323a32383a35332047" +
+ "4d540673657276657206417061636865" +
+ "0d6c6173742d6d6f6469666965641d57" +
+ "65642c203232204a756c203230303920" +
+ "31393a31353a353620474d5404657461" +
+ "671422333461613338372d642d313536" +
+ "3865623030220d6163636570742d7261" +
+ "6e6765730562797465730e636f6e7465" +
+ "6e742d6c656e67746802353104766172" +
+ "790f4163636570742d456e636f64696e" +
+ "670c636f6e74656e742d747970650a74" +
+ "6578742f706c61696e003348656c6c6f" +
+ "20576f726c6421204d7920636f6e7465" +
+ "6e7420696e636c756465732061207472" +
+ "61696c696e672043524c462e0d0a0000"
+ );
+ let response = bhttp.decodeResponse(encoded);
+ equal(response.status, 200);
+ deepEqual(
+ response.content,
+ stringToBytes("Hello World! My content includes a trailing CRLF.\r\n")
+ );
+ let expectedHeaderNames = [
+ "date",
+ "server",
+ "last-modified",
+ "etag",
+ "accept-ranges",
+ "content-length",
+ "vary",
+ "content-type",
+ ];
+ deepEqual(response.headerNames, expectedHeaderNames);
+ let expectedHeaderValues = [
+ "Mon, 27 Jul 2009 12:28:53 GMT",
+ "Apache",
+ "Wed, 22 Jul 2009 19:15:56 GMT",
+ '"34aa387-d-1568eb00"',
+ "bytes",
+ "51",
+ "Accept-Encoding",
+ "text/plain",
+ ];
+ deepEqual(response.headerValues, expectedHeaderValues);
+
+ let garbage = hexStringToBytes(
+ "0367890084cb0ab03115fa0b4c2ea0fa783f7a87fa00"
+ );
+ Assert.throws(() => bhttp.decodeResponse(garbage), /NS_ERROR_UNEXPECTED/);
+}
+
+function test_encode_response() {
+ let response = new BinaryHttpResponse(
+ 418,
+ ["content-type"],
+ ["text/plain"],
+ stringToBytes("I'm a teapot")
+ );
+ let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
+ Ci.nsIBinaryHttp
+ );
+ let encoded = bhttp.encodeResponse(response);
+ let expected = hexStringToBytes(
+ "0141a2180c636f6e74656e742d747970650a746578742f706c61696e0c49276d206120746561706f7400"
+ );
+ deepEqual(encoded, expected);
+
+ let mismatchedHeaders = new BinaryHttpResponse(
+ 500,
+ ["some-header", "some-other-header"],
+ ["whoops-only-one-header-value"],
+ []
+ );
+ // The implementation uses "NS_ERROR_INVALID_ARG", because that's an
+ // appropriate description for the error. However, that is an alias to
+ // "NS_ERROR_ILLEGAL_VALUE", which is what the actual exception uses, so
+ // that's what is tested for here.
+ Assert.throws(
+ () => bhttp.encodeResponse(mismatchedHeaders),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+}
+
+function run_test() {
+ test_encode_request();
+ test_decode_request();
+ test_encode_response();
+ test_decode_response();
+}
diff --git a/netwerk/test/unit/test_blob_channelname.js b/netwerk/test/unit/test_blob_channelname.js
new file mode 100644
index 0000000000..c1a09272da
--- /dev/null
+++ b/netwerk/test/unit/test_blob_channelname.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function channelname() {
+ var file = new File(
+ [new Blob(["test"], { type: "text/plain" })],
+ "test-name"
+ );
+ var url = URL.createObjectURL(file);
+ var channel = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+
+ let inputStream = channel.open();
+ ok(inputStream, "Should be able to open channel");
+ ok(
+ inputStream.QueryInterface(Ci.nsIAsyncInputStream),
+ "Stream should support async operations"
+ );
+
+ await new Promise(resolve => {
+ inputStream.asyncWait(
+ () => {
+ let available = inputStream.available();
+ ok(available, "There should be data to read");
+ Assert.equal(
+ channel.contentDispositionFilename,
+ "test-name",
+ "filename matches"
+ );
+ resolve();
+ },
+ 0,
+ 0,
+ Services.tm.mainThread
+ );
+ });
+
+ inputStream.close();
+ channel.cancel(Cr.NS_ERROR_FAILURE);
+});
diff --git a/netwerk/test/unit/test_brotli_decoding.js b/netwerk/test/unit/test_brotli_decoding.js
new file mode 100644
index 0000000000..55e2869401
--- /dev/null
+++ b/netwerk/test/unit/test_brotli_decoding.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+let endChunk2ReceivedInTime = false;
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+let channelListener = function (closure) {
+ this._closure = closure;
+ this._start = Date.now();
+};
+
+channelListener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ let data = read_stream(stream, cnt);
+ let current = Date.now();
+ let elapsed = current - this._start;
+ dump("data:" + data.slice(-10) + "\n");
+ dump("elapsed=" + elapsed + "\n");
+ if (elapsed < 2500 && data[data.length - 1] == "E") {
+ endChunk2ReceivedInTime = true;
+ }
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ this._closure();
+ },
+};
+
+add_task(async function test_http2() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let server = new NodeHTTP2Server();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ let chan = makeChan(`https://localhost:${server.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 404);
+ await server.registerPathHandler("/test", (req1, resp) => {
+ resp.writeHead(200, {
+ "content-type": "text/html; charset=utf-8",
+ "content-encoding": "br",
+ });
+ resp.write(
+ Buffer.from([
+ 0x8b, 0x2a, 0x80, 0x3c, 0x64, 0x69, 0x76, 0x3e, 0x73, 0x74, 0x61, 0x72,
+ 0x74, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x3c, 0x64, 0x69, 0x76, 0x20,
+ 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d,
+ 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x3a, 0x20, 0x65, 0x6c,
+ 0x6c, 0x69, 0x70, 0x73, 0x69, 0x73, 0x3b, 0x6f, 0x76, 0x65, 0x72, 0x66,
+ 0x6c, 0x6f, 0x77, 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b,
+ 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x72,
+ 0x74, 0x6c, 0x3b, 0x22, 0x3e,
+ ])
+ );
+
+ // This function is handled within the httpserver where setTimeout is
+ // available.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout, no-undef
+ setTimeout(function () {
+ resp.write(
+ Buffer.from([
+ 0xfa, 0xff, 0x0b, 0x00, 0x80, 0xaa, 0xaa, 0xaa, 0xea, 0x3f, 0x72,
+ 0x59, 0xd6, 0x05, 0x73, 0x5b, 0xb6, 0x75, 0xea, 0xe6, 0xfd, 0xa8,
+ 0x54, 0xc7, 0x62, 0xd8, 0x18, 0x86, 0x61, 0x18, 0x86, 0x63, 0xa9,
+ 0x86, 0x61, 0x18, 0x86, 0xe1, 0x63, 0x8e, 0x63, 0xa9, 0x86, 0x61,
+ 0x18, 0x86, 0x61, 0xd8, 0xe0, 0xbc, 0x85, 0x48, 0x1f, 0xa0, 0x05,
+ 0xda, 0x6f, 0xef, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xff, 0xc8, 0x65, 0x59, 0x17, 0xcc, 0x6d, 0xd9, 0xd6,
+ 0xa9, 0x9b, 0xf7, 0xff, 0x0d, 0xd5, 0xb1, 0x18, 0xe6, 0x63, 0x18,
+ 0x86, 0x61, 0x18, 0x8e, 0xa5, 0x1a, 0x86, 0x61, 0x18, 0x86, 0x61,
+ 0x8e, 0xed, 0x57, 0x0d, 0xc3, 0x30, 0x0c, 0xc3, 0xb0, 0xc1, 0x79,
+ 0x0b, 0x91, 0x3e, 0x40, 0x6c, 0x1c, 0x00, 0x90, 0xd6, 0x3b, 0x00,
+ 0x50, 0x96, 0x31, 0x53, 0xe6, 0x2c, 0x59, 0xb3, 0x65, 0xcf, 0x91,
+ 0x33, 0x57, 0x31, 0x03,
+ ])
+ );
+ }, 100);
+
+ // This function is handled within the httpserver where setTimeout is
+ // available.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout, no-undef
+ setTimeout(function () {
+ resp.end(
+ Buffer.from([
+ 0x98, 0x00, 0x08, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x3c, 0x64,
+ 0x69, 0x76, 0x3e, 0x65, 0x6e, 0x64, 0x3c, 0x2f, 0x64, 0x69, 0x76,
+ 0x3e, 0x03,
+ ])
+ );
+ }, 2500);
+ });
+ chan = makeChan(`https://localhost:${server.port()}/test`);
+ await new Promise(resolve => {
+ chan.asyncOpen(new channelListener(() => resolve()));
+ });
+
+ equal(
+ endChunk2ReceivedInTime,
+ true,
+ "End of chunk 2 not received before chunk 3 was sent"
+ );
+});
diff --git a/netwerk/test/unit/test_brotli_http.js b/netwerk/test/unit/test_brotli_http.js
new file mode 100644
index 0000000000..1207c95012
--- /dev/null
+++ b/netwerk/test/unit/test_brotli_http.js
@@ -0,0 +1,122 @@
+// This test exists mostly as documentation that
+// Firefox can load brotli files over HTTP if we set the proper pref.
+
+"use strict";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "br", false);
+ response.write("\x0b\x02\x80hello\x03");
+}
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+add_task(async function check_brotli() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ async function test() {
+ let chan = NetUtil.newChannel({ uri: URL, loadUsingSystemPrincipal: true });
+ let [, buff] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff1) => {
+ resolve([req, buff1]);
+ },
+ null,
+ CL_IGNORE_CL
+ )
+ );
+ });
+ return buff;
+ }
+
+ Services.prefs.setBoolPref(
+ "network.http.encoding.trustworthy_is_https",
+ true
+ );
+ equal(
+ await test(),
+ "hello",
+ "Should decode brotli when trustworthy_is_https=true"
+ );
+ Services.prefs.setBoolPref(
+ "network.http.encoding.trustworthy_is_https",
+ false
+ );
+ equal(
+ await test(),
+ "\x0b\x02\x80hello\x03",
+ "Should not decode brotli when trustworthy_is_https=false"
+ );
+ Services.prefs.setCharPref(
+ "network.http.accept-encoding",
+ "gzip, deflate, br"
+ );
+ equal(
+ await test(),
+ "hello",
+ "Should decode brotli if we set the HTTP accept encoding to include brotli"
+ );
+ Services.prefs.clearUserPref("network.http.accept-encoding");
+ Services.prefs.clearUserPref("network.http.encoding.trustworthy_is_https");
+ await httpServer.stop();
+});
+
+// Make sure we still decode brotli on HTTPS
+// Node server doesn't work on Android yet.
+add_task(
+ { skip_if: () => AppConstants.platform == "android" },
+ async function check_https() {
+ Services.prefs.setBoolPref(
+ "network.http.encoding.trustworthy_is_https",
+ true
+ );
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let server = new NodeHTTPSServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ await server.registerPathHandler("/brotli", (req, resp) => {
+ resp.setHeader("Content-Type", "text/plain");
+ resp.setHeader("Content-Encoding", "br");
+ let output = "\x0b\x02\x80hello\x03";
+ resp.writeHead(200);
+ resp.end(output, "binary");
+ });
+ equal(
+ Services.prefs.getCharPref("network.http.accept-encoding.secure"),
+ "gzip, deflate, br"
+ );
+ let { req, buff } = await new Promise(resolve => {
+ let chan = NetUtil.newChannel({
+ uri: `${server.origin()}/brotli`,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(
+ new ChannelListener(
+ (req1, buff1) => resolve({ req: req1, buff: buff1 }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(buff, "hello");
+ }
+);
diff --git a/netwerk/test/unit/test_brotli_unknown_content_type.js b/netwerk/test/unit/test_brotli_unknown_content_type.js
new file mode 100644
index 0000000000..7df0d4f847
--- /dev/null
+++ b/netwerk/test/unit/test_brotli_unknown_content_type.js
@@ -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/. */
+
+// This test file makes sure that we can decode brotli files when the
+// Content-Type header is missing (Bug 1715401)
+
+"use strict";
+
+function emptyBrotli(metadata, response) {
+ response.setHeader("Content-Encoding", "br", false);
+ response.write("\x01\x03\x06\x03");
+}
+
+function largeEmptyBrotli(metadata, response) {
+ response.setHeader("Content-Encoding", "br", false);
+ response.write("\x01\x03" + "\x06".repeat(600) + "\x03");
+}
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL_EMPTY_BROTLI", function () {
+ return (
+ "http://localhost:" + httpServer.identity.primaryPort + "/empty-brotli"
+ );
+});
+
+ChromeUtils.defineLazyGetter(this, "URL_LARGE_EMPTY_BROTLI", function () {
+ return (
+ "http://localhost:" +
+ httpServer.identity.primaryPort +
+ "/large-empty-brotli"
+ );
+});
+
+var httpServer = null;
+
+add_task(async function check_brotli() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/empty-brotli", emptyBrotli);
+ httpServer.registerPathHandler("/large-empty-brotli", largeEmptyBrotli);
+ httpServer.start(-1);
+
+ async function test(url) {
+ let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ let [, response] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => {
+ resolve([req, buff]);
+ },
+ null,
+ CL_IGNORE_CL
+ )
+ );
+ });
+ return response;
+ }
+ equal(
+ await test(URL_EMPTY_BROTLI),
+ "",
+ "Should decode brotli even when brotli output is empty"
+ );
+ equal(
+ await test(URL_LARGE_EMPTY_BROTLI),
+ "",
+ "Should decode brotli even when the nsUnknownDecoder can't get any decoded output"
+ );
+ Services.prefs.clearUserPref("network.http.accept-encoding");
+ Services.prefs.clearUserPref("network.http.encoding.trustworthy_is_https");
+ await httpServer.stop();
+});
diff --git a/netwerk/test/unit/test_bug1064258.js b/netwerk/test/unit/test_bug1064258.js
new file mode 100644
index 0000000000..9be44254b5
--- /dev/null
+++ b/netwerk/test/unit/test_bug1064258.js
@@ -0,0 +1,144 @@
+/**
+ * Check how nsICachingChannel.cacheOnlyMetadata works.
+ * - all channels involved in this test are set cacheOnlyMetadata = true
+ * - do a previously uncached request for a long living content
+ * - check we have downloaded the content from the server (channel provides it)
+ * - check the entry has metadata, but zero-length content
+ * - load the same URL again, now cached
+ * - check the channel is giving no content (no call to OnDataAvailable) but succeeds
+ * - repeat again, but for a different URL that is not cached (immediately expires)
+ * - only difference is that we get a newer version of the content from the server during the second request
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody1 = "response body 1";
+const responseBody2a = "response body 2a";
+const responseBody2b = "response body 2b";
+
+function contentHandler1(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-control", "max-age=999999");
+ response.bodyOutputStream.write(responseBody1, responseBody1.length);
+}
+
+var content2passCount = 0;
+
+function contentHandler2(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-control", "no-cache");
+ switch (content2passCount++) {
+ case 0:
+ response.setHeader("ETag", "testetag");
+ response.bodyOutputStream.write(responseBody2a, responseBody2a.length);
+ break;
+ case 1:
+ Assert.ok(metadata.hasHeader("If-None-Match"));
+ Assert.equal(metadata.getHeader("If-None-Match"), "testetag");
+ response.bodyOutputStream.write(responseBody2b, responseBody2b.length);
+ break;
+ default:
+ throw new Error("Unexpected request in the test");
+ }
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content1", contentHandler1);
+ httpServer.registerPathHandler("/content2", contentHandler2);
+ httpServer.start(-1);
+
+ run_test_content1a();
+ do_test_pending();
+}
+
+function run_test_content1a() {
+ var chan = make_channel(URL + "/content1");
+ let caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen(new ChannelListener(contentListener1a, null));
+}
+
+function contentListener1a(request, buffer) {
+ Assert.equal(buffer, responseBody1);
+
+ asyncOpenCacheEntry(URL + "/content1", "disk", 0, null, cacheCheck1);
+}
+
+function cacheCheck1(status, entry) {
+ Assert.equal(status, 0);
+ Assert.equal(entry.dataSize, 0);
+ try {
+ Assert.notEqual(entry.getMetaDataElement("response-head"), null);
+ } catch (ex) {
+ do_throw("Missing response head");
+ }
+
+ var chan = make_channel(URL + "/content1");
+ let caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen(new ChannelListener(contentListener1b, null, CL_IGNORE_CL));
+}
+
+function contentListener1b(request, buffer) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(request.requestMethod, "GET");
+ Assert.equal(request.responseStatus, 200);
+ Assert.equal(request.getResponseHeader("Cache-control"), "max-age=999999");
+
+ Assert.equal(buffer, "");
+ run_test_content2a();
+}
+
+// Now same set of steps but this time for an immediately expiring content.
+
+function run_test_content2a() {
+ var chan = make_channel(URL + "/content2");
+ let caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen(new ChannelListener(contentListener2a, null));
+}
+
+function contentListener2a(request, buffer) {
+ Assert.equal(buffer, responseBody2a);
+
+ asyncOpenCacheEntry(URL + "/content2", "disk", 0, null, cacheCheck2);
+}
+
+function cacheCheck2(status, entry) {
+ Assert.equal(status, 0);
+ Assert.equal(entry.dataSize, 0);
+ try {
+ Assert.notEqual(entry.getMetaDataElement("response-head"), null);
+ Assert.ok(
+ entry.getMetaDataElement("response-head").match("etag: testetag")
+ );
+ } catch (ex) {
+ do_throw("Missing response head");
+ }
+
+ var chan = make_channel(URL + "/content2");
+ let caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen(new ChannelListener(contentListener2b, null));
+}
+
+function contentListener2b(request, buffer) {
+ Assert.equal(buffer, responseBody2b);
+
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_bug1177909.js b/netwerk/test/unit/test_bug1177909.js
new file mode 100644
index 0000000000..0fc94f8a2c
--- /dev/null
+++ b/netwerk/test/unit/test_bug1177909.js
@@ -0,0 +1,251 @@
+"use strict";
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gProxyService",
+ "@mozilla.org/network/protocol-proxy-service;1",
+ "nsIProtocolProxyService"
+);
+
+ChromeUtils.defineLazyGetter(this, "systemSettings", function () {
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsISystemProxySettings"]),
+
+ mainThreadOnly: true,
+ PACURI: null,
+
+ getProxyForURI(aSpec, aScheme, aHost, aPort) {
+ if (aPort != -1) {
+ return "SOCKS5 http://localhost:9050";
+ }
+ if (aScheme == "http") {
+ return "PROXY http://localhost:8080";
+ }
+ if (aScheme == "https") {
+ return "HTTPS https://localhost:8080";
+ }
+ return "DIRECT";
+ },
+ };
+});
+
+let gMockProxy = MockRegistrar.register(
+ "@mozilla.org/system-proxy-settings;1",
+ systemSettings
+);
+
+registerCleanupFunction(() => {
+ MockRegistrar.unregister(gMockProxy);
+});
+
+function makeChannel(uri) {
+ return NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+async function TestProxyType(chan, flags) {
+ Services.prefs.setIntPref(
+ "network.proxy.type",
+ Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM
+ );
+
+ return new Promise((resolve, reject) => {
+ gProxyService.asyncResolve(chan, flags, {
+ onProxyAvailable(req, uri, pi, status) {
+ resolve(pi);
+ },
+ });
+ });
+}
+
+async function TestProxyTypeByURI(uri) {
+ return TestProxyType(makeChannel(uri), 0);
+}
+
+add_task(async function testHttpProxy() {
+ let pi = await TestProxyTypeByURI("http://www.mozilla.org/");
+ equal(pi.host, "localhost", "Expected proxy host to be localhost");
+ equal(pi.port, 8080, "Expected proxy port to be 8080");
+ equal(pi.type, "http", "Expected proxy type to be http");
+});
+
+add_task(async function testHttpsProxy() {
+ let pi = await TestProxyTypeByURI("https://www.mozilla.org/");
+ equal(pi.host, "localhost", "Expected proxy host to be localhost");
+ equal(pi.port, 8080, "Expected proxy port to be 8080");
+ equal(pi.type, "https", "Expected proxy type to be https");
+});
+
+add_task(async function testSocksProxy() {
+ let pi = await TestProxyTypeByURI("http://www.mozilla.org:1234/");
+ equal(pi.host, "localhost", "Expected proxy host to be localhost");
+ equal(pi.port, 9050, "Expected proxy port to be 8080");
+ equal(pi.type, "socks", "Expected proxy type to be http");
+});
+
+add_task(async function testDirectProxy() {
+ // Do what |WebSocketChannel::AsyncOpen| do, but do not prefer https proxy.
+ let proxyURI = Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("wss://ws.mozilla.org/")
+ .finalize();
+ let uri = proxyURI.mutate().setScheme("https").finalize();
+
+ let chan = Services.io.newChannelFromURIWithProxyFlags(
+ uri,
+ proxyURI,
+ 0,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+
+ let pi = await TestProxyType(chan, 0);
+ equal(pi, null, "Expected proxy host to be null");
+});
+
+add_task(async function testWebSocketProxy() {
+ // Do what |WebSocketChannel::AsyncOpen| do
+ let proxyURI = Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("wss://ws.mozilla.org/")
+ .finalize();
+ let uri = proxyURI.mutate().setScheme("https").finalize();
+
+ let proxyFlags =
+ Ci.nsIProtocolProxyService.RESOLVE_PREFER_SOCKS_PROXY |
+ Ci.nsIProtocolProxyService.RESOLVE_PREFER_HTTPS_PROXY |
+ Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL;
+
+ let chan = Services.io.newChannelFromURIWithProxyFlags(
+ uri,
+ proxyURI,
+ proxyFlags,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+
+ let pi = await TestProxyType(chan, proxyFlags);
+ equal(pi.host, "localhost", "Expected proxy host to be localhost");
+ equal(pi.port, 8080, "Expected proxy port to be 8080");
+ equal(pi.type, "https", "Expected proxy type to be https");
+});
+
+add_task(async function testPreferHttpsProxy() {
+ let uri = Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://mozilla.org/")
+ .finalize();
+ let proxyFlags = Ci.nsIProtocolProxyService.RESOLVE_PREFER_HTTPS_PROXY;
+
+ let chan = Services.io.newChannelFromURIWithProxyFlags(
+ uri,
+ null,
+ proxyFlags,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+
+ let pi = await TestProxyType(chan, proxyFlags);
+ equal(pi.host, "localhost", "Expected proxy host to be localhost");
+ equal(pi.port, 8080, "Expected proxy port to be 8080");
+ equal(pi.type, "https", "Expected proxy type to be https");
+});
+
+add_task(async function testProxyHttpsToHttpIsBlocked() {
+ // Ensure that regressions of bug 1702417 will be detected by the next test
+ const turnUri = Services.io.newURI("http://turn.example.com/");
+ const proxyFlags =
+ Ci.nsIProtocolProxyService.RESOLVE_PREFER_HTTPS_PROXY |
+ Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL;
+
+ const fakeContentPrincipal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://example.com"
+ );
+
+ const chan = Services.io.newChannelFromURIWithProxyFlags(
+ turnUri,
+ null,
+ proxyFlags,
+ null,
+ fakeContentPrincipal,
+ fakeContentPrincipal,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+
+ const pi = await TestProxyType(chan, proxyFlags);
+ equal(pi.host, "localhost", "Expected proxy host to be localhost");
+ equal(pi.port, 8080, "Expected proxy port to be 8080");
+ equal(pi.type, "https", "Expected proxy type to be https");
+
+ const csm = Cc["@mozilla.org/contentsecuritymanager;1"].getService(
+ Ci.nsIContentSecurityManager
+ );
+
+ try {
+ csm.performSecurityCheck(chan, null);
+ Assert.ok(
+ false,
+ "performSecurityCheck should fail (due to mixed content blocking)"
+ );
+ } catch (e) {
+ Assert.equal(
+ e.result,
+ Cr.NS_ERROR_CONTENT_BLOCKED,
+ "performSecurityCheck should throw NS_ERROR_CONTENT_BLOCKED"
+ );
+ }
+});
+
+add_task(async function testProxyHttpsToTurnTcpWorks() {
+ // Test for bug 1702417
+ const turnUri = Services.io.newURI("http://turn.example.com/");
+ const proxyFlags =
+ Ci.nsIProtocolProxyService.RESOLVE_PREFER_HTTPS_PROXY |
+ Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL;
+
+ const fakeContentPrincipal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://example.com"
+ );
+
+ const chan = Services.io.newChannelFromURIWithProxyFlags(
+ turnUri,
+ null,
+ proxyFlags,
+ null,
+ fakeContentPrincipal,
+ fakeContentPrincipal,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ // This is what allows this to avoid mixed content blocking
+ Ci.nsIContentPolicy.TYPE_PROXIED_WEBRTC_MEDIA
+ );
+
+ const pi = await TestProxyType(chan, proxyFlags);
+ equal(pi.host, "localhost", "Expected proxy host to be localhost");
+ equal(pi.port, 8080, "Expected proxy port to be 8080");
+ equal(pi.type, "https", "Expected proxy type to be https");
+
+ const csm = Cc["@mozilla.org/contentsecuritymanager;1"].getService(
+ Ci.nsIContentSecurityManager
+ );
+
+ csm.performSecurityCheck(chan, null);
+ Assert.ok(true, "performSecurityCheck should succeed");
+});
diff --git a/netwerk/test/unit/test_bug1195415.js b/netwerk/test/unit/test_bug1195415.js
new file mode 100644
index 0000000000..eb312d27be
--- /dev/null
+++ b/netwerk/test/unit/test_bug1195415.js
@@ -0,0 +1,116 @@
+// Test for bug 1195415
+
+"use strict";
+
+function run_test() {
+ var ios = Services.io;
+ var ssm = Services.scriptSecurityManager;
+
+ // NON-UNICODE
+ var uri = ios.newURI("http://foo.com/file.txt");
+ Assert.equal(uri.asciiHostPort, "foo.com");
+ uri = uri.mutate().setPort(90).finalize();
+ var prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "foo.com:90");
+ Assert.equal(prin.origin, "http://foo.com:90");
+
+ uri = ios.newURI("http://foo.com:10/file.txt");
+ Assert.equal(uri.asciiHostPort, "foo.com:10");
+ uri = uri.mutate().setPort(500).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "foo.com:500");
+ Assert.equal(prin.origin, "http://foo.com:500");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "foo.com:5000");
+ uri = uri.mutate().setPort(20).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "foo.com:20");
+ Assert.equal(prin.origin, "http://foo.com:20");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "foo.com:5000");
+ uri = uri.mutate().setPort(-1).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "foo.com");
+ Assert.equal(prin.origin, "http://foo.com");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "foo.com:5000");
+ uri = uri.mutate().setPort(80).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "foo.com");
+ Assert.equal(prin.origin, "http://foo.com");
+
+ // UNICODE
+ uri = ios.newURI("http://jos\u00e9.example.net.ch/file.txt");
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ uri = uri.mutate().setPort(90).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch:90");
+ Assert.equal(prin.origin, "http://xn--jos-dma.example.net.ch:90");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:10/file.txt");
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch:10");
+ uri = uri.mutate().setPort(500).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch:500");
+ Assert.equal(prin.origin, "http://xn--jos-dma.example.net.ch:500");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri = uri.mutate().setPort(20).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch:20");
+ Assert.equal(prin.origin, "http://xn--jos-dma.example.net.ch:20");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri = uri.mutate().setPort(-1).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ Assert.equal(prin.origin, "http://xn--jos-dma.example.net.ch");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri = uri.mutate().setPort(80).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ Assert.equal(prin.origin, "http://xn--jos-dma.example.net.ch");
+
+ // ipv6
+ uri = ios.newURI("http://[123:45::678]/file.txt");
+ Assert.equal(uri.asciiHostPort, "[123:45::678]");
+ uri = uri.mutate().setPort(90).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "[123:45::678]:90");
+ Assert.equal(prin.origin, "http://[123:45::678]:90");
+
+ uri = ios.newURI("http://[123:45::678]:10/file.txt");
+ Assert.equal(uri.asciiHostPort, "[123:45::678]:10");
+ uri = uri.mutate().setPort(500).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "[123:45::678]:500");
+ Assert.equal(prin.origin, "http://[123:45::678]:500");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "[123:45::678]:5000");
+ uri = uri.mutate().setPort(20).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "[123:45::678]:20");
+ Assert.equal(prin.origin, "http://[123:45::678]:20");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "[123:45::678]:5000");
+ uri = uri.mutate().setPort(-1).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "[123:45::678]");
+ Assert.equal(prin.origin, "http://[123:45::678]");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt");
+ Assert.equal(uri.asciiHostPort, "[123:45::678]:5000");
+ uri = uri.mutate().setPort(80).finalize();
+ prin = ssm.createContentPrincipal(uri, {});
+ Assert.equal(uri.asciiHostPort, "[123:45::678]");
+ Assert.equal(prin.origin, "http://[123:45::678]");
+}
diff --git a/netwerk/test/unit/test_bug1218029.js b/netwerk/test/unit/test_bug1218029.js
new file mode 100644
index 0000000000..48165807bf
--- /dev/null
+++ b/netwerk/test/unit/test_bug1218029.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var tests = [
+ { data: "", chunks: [], status: Cr.NS_OK, consume: [], dataChunks: [""] },
+ {
+ data: "TWO-PARTS",
+ chunks: [4, 5],
+ status: Cr.NS_OK,
+ consume: [4, 5],
+ dataChunks: ["TWO-", "PARTS", ""],
+ },
+ {
+ data: "TWO-PARTS",
+ chunks: [4, 5],
+ status: Cr.NS_OK,
+ consume: [0, 0],
+ dataChunks: ["TWO-", "TWO-PARTS", "TWO-PARTS"],
+ },
+ {
+ data: "3-PARTS",
+ chunks: [1, 1, 5],
+ status: Cr.NS_OK,
+ consume: [0, 2, 5],
+ dataChunks: ["3", "3-", "PARTS", ""],
+ },
+ {
+ data: "ALL-AT-ONCE",
+ chunks: [11],
+ status: Cr.NS_OK,
+ consume: [0],
+ dataChunks: ["ALL-AT-ONCE", "ALL-AT-ONCE"],
+ },
+ {
+ data: "ALL-AT-ONCE",
+ chunks: [11],
+ status: Cr.NS_OK,
+ consume: [11],
+ dataChunks: ["ALL-AT-ONCE", ""],
+ },
+ {
+ data: "ERROR",
+ chunks: [1],
+ status: Cr.NS_ERROR_OUT_OF_MEMORY,
+ consume: [0],
+ dataChunks: ["E", "E"],
+ },
+];
+
+/**
+ * @typedef TestData
+ * @property {string} data - data for the test.
+ * @property {Array} chunks - lengths of the chunks that are incrementally sent
+ * to the loader.
+ * @property {number} status - final status sent on onStopRequest.
+ * @property {Array} consume - lengths of consumed data that is reported at
+ * the onIncrementalData callback.
+ * @property {Array} dataChunks - data chunks that are reported at the
+ * onIncrementalData and onStreamComplete callbacks.
+ */
+
+function execute_test(test) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = test.data;
+
+ let channel = {
+ contentLength: -1,
+ QueryInterface: ChromeUtils.generateQI(["nsIChannel"]),
+ };
+
+ let chunkIndex = 0;
+
+ let observer = {
+ onStreamComplete(loader, context, status, length, data) {
+ equal(chunkIndex, test.dataChunks.length - 1);
+ var expectedChunk = test.dataChunks[chunkIndex];
+ equal(length, expectedChunk.length);
+ equal(String.fromCharCode.apply(null, data), expectedChunk);
+
+ equal(status, test.status);
+ },
+ onIncrementalData(loader, context, length, data, consumed) {
+ ok(chunkIndex < test.dataChunks.length - 1);
+ var expectedChunk = test.dataChunks[chunkIndex];
+ equal(length, expectedChunk.length);
+ equal(String.fromCharCode.apply(null, data), expectedChunk);
+
+ consumed.value = test.consume[chunkIndex];
+ chunkIndex++;
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIIncrementalStreamLoaderObserver",
+ ]),
+ };
+
+ let listener = Cc[
+ "@mozilla.org/network/incremental-stream-loader;1"
+ ].createInstance(Ci.nsIIncrementalStreamLoader);
+ listener.init(observer);
+
+ listener.onStartRequest(channel);
+ var offset = 0;
+ test.chunks.forEach(function (chunkLength) {
+ listener.onDataAvailable(channel, stream, offset, chunkLength);
+ offset += chunkLength;
+ });
+ listener.onStopRequest(channel, test.status);
+}
+
+function run_test() {
+ tests.forEach(execute_test);
+}
diff --git a/netwerk/test/unit/test_bug1279246.js b/netwerk/test/unit/test_bug1279246.js
new file mode 100644
index 0000000000..be73dbee14
--- /dev/null
+++ b/netwerk/test/unit/test_bug1279246.js
@@ -0,0 +1,100 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var pass = 0;
+var responseBody = [0x0b, 0x02, 0x80, 0x74, 0x65, 0x73, 0x74, 0x0a, 0x03];
+var responseLen = 5;
+var testUrl = "/test/brotli";
+
+function setupChannel() {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + testUrl,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function Listener() {}
+
+Listener.prototype = {
+ _buffer: null,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ this._buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, cnt) {
+ if (pass == 0) {
+ this._buffer = this._buffer.concat(read_stream(stream, cnt));
+ } else {
+ request.QueryInterface(Ci.nsICachingChannel);
+ if (!request.isFromCache()) {
+ do_throw("Response is not from the cache");
+ }
+
+ request.cancel(Cr.NS_ERROR_ABORT);
+ }
+ },
+
+ onStopRequest(request, status) {
+ if (pass == 0) {
+ Assert.equal(this._buffer.length, responseLen);
+ pass++;
+
+ var channel = setupChannel();
+ channel.loadFlags = Ci.nsIRequest.VALIDATE_NEVER;
+ channel.asyncOpen(new Listener());
+ } else {
+ httpserver.stop(do_test_finished);
+ prefs.setCharPref("network.http.accept-encoding", cePref);
+ }
+ },
+};
+
+var prefs;
+var cePref;
+function run_test() {
+ do_get_profile();
+
+ prefs = Services.prefs;
+ cePref = prefs.getCharPref("network.http.accept-encoding");
+ prefs.setCharPref("network.http.accept-encoding", "gzip, deflate, br");
+
+ // Disable rcwn to make cache behavior deterministic.
+ prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpserver.registerPathHandler(testUrl, handler);
+ httpserver.start(-1);
+
+ var channel = setupChannel();
+ channel.asyncOpen(new Listener());
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ Assert.equal(pass, 0); // the second response must be server from the cache
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "br", false);
+ response.setHeader("Content-Length", "" + responseBody.length, false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIBinaryOutputStream
+ );
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(responseBody);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_bug1312774_http1.js b/netwerk/test/unit/test_bug1312774_http1.js
new file mode 100644
index 0000000000..6f100295ae
--- /dev/null
+++ b/netwerk/test/unit/test_bug1312774_http1.js
@@ -0,0 +1,149 @@
+// test bug 1312774.
+// Create 6 (=network.http.max-persistent-connections-per-server)
+// common Http requests and 2 urgent-start Http requests to a single
+// host and path, in parallel.
+// Let all the requests unanswered by the server handler. (process them
+// async and don't finish)
+// The first 6 pending common requests will fill the limit for per-server
+// parallelism.
+// But the two urgent requests must reach the server despite those 6 common
+// pending requests.
+// The server handler doesn't let the test finish until all 8 expected requests
+// arrive.
+// Note: if the urgent request handling is broken (the urgent-marked requests
+// get blocked by queuing) this test will time out
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+var server = new HttpServer();
+server.start(-1);
+var baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+var maxConnections = 0;
+var urgentRequests = 0;
+var debug = false;
+
+function log(msg) {
+ if (!debug) {
+ return;
+ }
+
+ if (msg) {
+ dump("TEST INFO | " + msg + "\n");
+ }
+}
+
+function make_channel(url) {
+ var request = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+ request.QueryInterface(Ci.nsIHttpChannel);
+ return request;
+}
+
+function serverStopListener() {
+ server.stop();
+}
+
+function commonHttpRequest(id) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ var listner = new HttpResponseListener(id);
+ chan.setRequestHeader("X-ID", id, false);
+ chan.setRequestHeader("Cache-control", "no-store", false);
+ chan.asyncOpen(listner);
+ log("Create common http request id=" + id);
+}
+
+function urgentStartHttpRequest(id) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ var listner = new HttpResponseListener(id);
+ var cos = chan.QueryInterface(Ci.nsIClassOfService);
+ cos.addClassFlags(Ci.nsIClassOfService.UrgentStart);
+ chan.setRequestHeader("X-ID", id, false);
+ chan.setRequestHeader("Cache-control", "no-store", false);
+ chan.asyncOpen(listner);
+ log("Create urgent-start http request id=" + id);
+}
+
+function setup_httpRequests() {
+ log("setup_httpRequests");
+ for (var i = 0; i < maxConnections; i++) {
+ commonHttpRequest(i);
+ do_test_pending();
+ }
+}
+
+function setup_urgentStartRequests() {
+ for (var i = 0; i < urgentRequests; i++) {
+ urgentStartHttpRequest(1000 + i);
+ do_test_pending();
+ }
+}
+
+function HttpResponseListener(id) {
+ this.id = id;
+}
+
+HttpResponseListener.prototype = {
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, off, cnt) {},
+
+ onStopRequest(request, status) {
+ log("STOP id=" + this.id);
+ do_test_finished();
+ },
+};
+
+var responseQueue = [];
+function setup_http_server() {
+ log("setup_http_server");
+ maxConnections = Services.prefs.getIntPref(
+ "network.http.max-persistent-connections-per-server"
+ );
+ urgentRequests = 2;
+ var allCommonHttpRequestReceived = false;
+ // Start server; will be stopped at test cleanup time.
+ server.registerPathHandler("/", function (metadata, response) {
+ var id = metadata.getHeader("X-ID");
+ log("Server recived the response id=" + id);
+ response.processAsync();
+ responseQueue.push(response);
+
+ if (
+ responseQueue.length == maxConnections &&
+ !allCommonHttpRequestReceived
+ ) {
+ allCommonHttpRequestReceived = true;
+ setup_urgentStartRequests();
+ }
+ // Wait for all expected requests to come but don't process then.
+ // Collect them in a queue for later processing. We don't want to
+ // respond to the client until all the expected requests are made
+ // to the server.
+ if (responseQueue.length == maxConnections + urgentRequests) {
+ processResponse();
+ }
+ });
+
+ registerCleanupFunction(function () {
+ server.stop(serverStopListener);
+ });
+}
+
+function processResponse() {
+ while (responseQueue.length) {
+ var resposne = responseQueue.pop();
+ resposne.finish();
+ }
+}
+
+function run_test() {
+ setup_http_server();
+ setup_httpRequests();
+}
diff --git a/netwerk/test/unit/test_bug1312782_http1.js b/netwerk/test/unit/test_bug1312782_http1.js
new file mode 100644
index 0000000000..cf820a4f56
--- /dev/null
+++ b/netwerk/test/unit/test_bug1312782_http1.js
@@ -0,0 +1,197 @@
+// test bug 1312782.
+//
+// Summary:
+// Assume we have 6 http requests in queue, 4 are from the focused window and
+// the other 2 are from the non-focused window. We want to test that the server
+// should receive 4 requests from the focused window first and then receive the
+// rest 2 requests.
+//
+// Test step:
+// 1. Create 6 dummy http requests. Server would not process responses until get
+// all 6 requests.
+// 2. Once server receive 6 dummy requests, create 4 http requests with the focused
+// window id and 2 requests with non-focused window id. Note that the requets's
+// id is a serial number starting from the focused window id.
+// 3. Server starts to process the 6 dummy http requests, so the client can start to
+// process the pending queue. Server will queue those http requests again and wait
+// until get all 6 requests.
+// 4. When the server receive all 6 requests, starts to check that the request ids of
+// the first 4 requests in the queue should be all less than focused window id
+// plus 4. Also, the request ids of the rest requests should be less than non-focused
+// window id + 2.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var server = new HttpServer();
+server.start(-1);
+var baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+var maxConnections = 0;
+var debug = false;
+const FOCUSED_WINDOW_ID = 123;
+var NON_FOCUSED_WINDOW_ID;
+var FOCUSED_WINDOW_REQUEST_COUNT;
+var NON_FOCUSED_WINDOW_REQUEST_COUNT;
+
+function log(msg) {
+ if (!debug) {
+ return;
+ }
+
+ if (msg) {
+ dump("TEST INFO | " + msg + "\n");
+ }
+}
+
+function make_channel(url) {
+ var request = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+ request.QueryInterface(Ci.nsIHttpChannel);
+ return request;
+}
+
+function serverStopListener() {
+ server.stop();
+}
+
+function createHttpRequest(browserId, requestId) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ chan.browserId = browserId;
+ var listner = new HttpResponseListener(requestId);
+ chan.setRequestHeader("X-ID", requestId, false);
+ chan.setRequestHeader("Cache-control", "no-store", false);
+ chan.asyncOpen(listner);
+ log("Create http request id=" + requestId);
+}
+
+function setup_dummyHttpRequests() {
+ log("setup_dummyHttpRequests");
+ for (var i = 0; i < maxConnections; i++) {
+ createHttpRequest(0, i);
+ do_test_pending();
+ }
+}
+
+function setup_focusedWindowHttpRequests() {
+ log("setup_focusedWindowHttpRequests");
+ for (var i = 0; i < FOCUSED_WINDOW_REQUEST_COUNT; i++) {
+ createHttpRequest(FOCUSED_WINDOW_ID, FOCUSED_WINDOW_ID + i);
+ do_test_pending();
+ }
+}
+
+function setup_nonFocusedWindowHttpRequests() {
+ log("setup_nonFocusedWindowHttpRequests");
+ for (var i = 0; i < NON_FOCUSED_WINDOW_REQUEST_COUNT; i++) {
+ createHttpRequest(NON_FOCUSED_WINDOW_ID, NON_FOCUSED_WINDOW_ID + i);
+ do_test_pending();
+ }
+}
+
+function HttpResponseListener(id) {
+ this.id = id;
+}
+
+HttpResponseListener.prototype = {
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, off, cnt) {},
+
+ onStopRequest(request, status) {
+ log("STOP id=" + this.id);
+ do_test_finished();
+ },
+};
+
+function check_response_id(responses, maxWindowId) {
+ for (var i = 0; i < responses.length; i++) {
+ var id = responses[i].getHeader("X-ID");
+ log("response id=" + id + " maxWindowId=" + maxWindowId);
+ Assert.ok(id < maxWindowId);
+ }
+}
+
+var responseQueue = [];
+function setup_http_server() {
+ log("setup_http_server");
+ maxConnections = Services.prefs.getIntPref(
+ "network.http.max-persistent-connections-per-server"
+ );
+ FOCUSED_WINDOW_REQUEST_COUNT = Math.floor(maxConnections * 0.8);
+ NON_FOCUSED_WINDOW_REQUEST_COUNT =
+ maxConnections - FOCUSED_WINDOW_REQUEST_COUNT;
+ NON_FOCUSED_WINDOW_ID = FOCUSED_WINDOW_ID + FOCUSED_WINDOW_REQUEST_COUNT;
+
+ var allDummyHttpRequestReceived = false;
+ // Start server; will be stopped at test cleanup time.
+ server.registerPathHandler("/", function (metadata, response) {
+ var id = metadata.getHeader("X-ID");
+ log("Server recived the response id=" + id);
+
+ response.processAsync();
+ response.setHeader("X-ID", id);
+ responseQueue.push(response);
+
+ if (
+ responseQueue.length == maxConnections &&
+ !allDummyHttpRequestReceived
+ ) {
+ log("received all dummy http requets");
+ allDummyHttpRequestReceived = true;
+ setup_nonFocusedWindowHttpRequests();
+ setup_focusedWindowHttpRequests();
+ processResponses();
+ } else if (responseQueue.length == maxConnections) {
+ var focusedWindowResponses = responseQueue.slice(
+ 0,
+ FOCUSED_WINDOW_REQUEST_COUNT
+ );
+ var nonFocusedWindowResponses = responseQueue.slice(
+ FOCUSED_WINDOW_REQUEST_COUNT,
+ responseQueue.length
+ );
+ check_response_id(
+ focusedWindowResponses,
+ FOCUSED_WINDOW_ID + FOCUSED_WINDOW_REQUEST_COUNT
+ );
+ check_response_id(
+ nonFocusedWindowResponses,
+ NON_FOCUSED_WINDOW_ID + NON_FOCUSED_WINDOW_REQUEST_COUNT
+ );
+ processResponses();
+ }
+ });
+
+ registerCleanupFunction(function () {
+ server.stop(serverStopListener);
+ });
+}
+
+function processResponses() {
+ while (responseQueue.length) {
+ var resposne = responseQueue.pop();
+ resposne.finish();
+ }
+}
+
+function run_test() {
+ // Make sure "network.http.active_tab_priority" is true, so we can expect to
+ // receive http requests with focused window id before others.
+ Services.prefs.setBoolPref("network.http.active_tab_priority", true);
+
+ setup_http_server();
+ setup_dummyHttpRequests();
+
+ var windowIdWrapper = Cc["@mozilla.org/supports-PRUint64;1"].createInstance(
+ Ci.nsISupportsPRUint64
+ );
+ windowIdWrapper.data = FOCUSED_WINDOW_ID;
+ var obsvc = Services.obs;
+ obsvc.notifyObservers(windowIdWrapper, "net:current-browser-id");
+}
diff --git a/netwerk/test/unit/test_bug1355539_http1.js b/netwerk/test/unit/test_bug1355539_http1.js
new file mode 100644
index 0000000000..17b2930388
--- /dev/null
+++ b/netwerk/test/unit/test_bug1355539_http1.js
@@ -0,0 +1,206 @@
+// test bug 1355539.
+//
+// Summary:
+// Transactions in one pending queue are splited into two groups:
+// [(Blocking Group)|(Non Blocking Group)]
+// In each group, the transactions are ordered by its priority.
+// This test will check if the transaction's order in pending queue is correct.
+//
+// Test step:
+// 1. Create 6 dummy http requests. Server would not process responses until get
+// all 6 requests.
+// 2. Once server receive 6 dummy requests, create another 6 http requests with the
+// defined priority and class flag in |transactionQueue|.
+// 3. Server starts to process the 6 dummy http requests, so the client can start to
+// process the pending queue. Server will queue those http requests and put them in
+// |responseQueue|.
+// 4. When the server receive all 6 requests, check if the order in |responseQueue| is
+// equal to |transactionQueue| by comparing the value of X-ID.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var server = new HttpServer();
+server.start(-1);
+var baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+var maxConnections = 0;
+var debug = false;
+var dummyResponseQueue = [];
+var responseQueue = [];
+
+function log(msg) {
+ if (!debug) {
+ return;
+ }
+
+ if (msg) {
+ dump("TEST INFO | " + msg + "\n");
+ }
+}
+
+function make_channel(url) {
+ var request = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+ request.QueryInterface(Ci.nsIHttpChannel);
+ return request;
+}
+
+function serverStopListener() {
+ server.stop();
+}
+
+function createHttpRequest(requestId, priority, isBlocking, callback) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ var listner = new HttpResponseListener(requestId, callback);
+ chan.setRequestHeader("X-ID", requestId, false);
+ chan.setRequestHeader("Cache-control", "no-store", false);
+ chan.QueryInterface(Ci.nsISupportsPriority).priority = priority;
+ if (isBlocking) {
+ var cos = chan.QueryInterface(Ci.nsIClassOfService);
+ cos.addClassFlags(Ci.nsIClassOfService.Leader);
+ }
+ chan.asyncOpen(listner);
+ log("Create http request id=" + requestId);
+}
+
+function setup_dummyHttpRequests(callback) {
+ log("setup_dummyHttpRequests");
+ for (var i = 0; i < maxConnections; i++) {
+ createHttpRequest(i, i, false, callback);
+ do_test_pending();
+ }
+}
+
+var transactionQueue = [
+ {
+ requestId: 101,
+ priority: Ci.nsISupportsPriority.PRIORITY_HIGH,
+ isBlocking: true,
+ },
+ {
+ requestId: 102,
+ priority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ isBlocking: true,
+ },
+ {
+ requestId: 103,
+ priority: Ci.nsISupportsPriority.PRIORITY_LOW,
+ isBlocking: true,
+ },
+ {
+ requestId: 104,
+ priority: Ci.nsISupportsPriority.PRIORITY_HIGH,
+ isBlocking: false,
+ },
+ {
+ requestId: 105,
+ priority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ isBlocking: false,
+ },
+ {
+ requestId: 106,
+ priority: Ci.nsISupportsPriority.PRIORITY_LOW,
+ isBlocking: false,
+ },
+];
+
+function setup_HttpRequests() {
+ log("setup_HttpRequests");
+ // Create channels in reverse order
+ for (var i = transactionQueue.length - 1; i > -1; ) {
+ var e = transactionQueue[i];
+ createHttpRequest(e.requestId, e.priority, e.isBlocking);
+ do_test_pending();
+ --i;
+ }
+}
+
+function check_response_id(responses) {
+ for (var i = 0; i < responses.length; i++) {
+ var id = responses[i].getHeader("X-ID");
+ Assert.equal(id, transactionQueue[i].requestId);
+ }
+}
+
+function HttpResponseListener(id, onStopCallback) {
+ this.id = id;
+ this.stopCallback = onStopCallback;
+}
+
+HttpResponseListener.prototype = {
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, off, cnt) {},
+
+ onStopRequest(request, status) {
+ log("STOP id=" + this.id);
+ do_test_finished();
+ if (this.stopCallback) {
+ this.stopCallback();
+ }
+ },
+};
+
+function setup_http_server() {
+ log("setup_http_server");
+ maxConnections = Services.prefs.getIntPref(
+ "network.http.max-persistent-connections-per-server"
+ );
+
+ var allDummyHttpRequestReceived = false;
+ // Start server; will be stopped at test cleanup time.
+ server.registerPathHandler("/", function (metadata, response) {
+ var id = metadata.getHeader("X-ID");
+ log("Server recived the response id=" + id);
+
+ response.processAsync();
+ response.setHeader("X-ID", id);
+
+ if (!allDummyHttpRequestReceived) {
+ dummyResponseQueue.push(response);
+ } else {
+ responseQueue.push(response);
+ }
+
+ if (dummyResponseQueue.length == maxConnections) {
+ log("received all dummy http requets");
+ allDummyHttpRequestReceived = true;
+ setup_HttpRequests();
+ processDummyResponse();
+ } else if (responseQueue.length == maxConnections) {
+ log("received all http requets");
+ check_response_id(responseQueue);
+ processResponses();
+ }
+ });
+
+ registerCleanupFunction(function () {
+ server.stop(serverStopListener);
+ });
+}
+
+function processDummyResponse() {
+ if (!dummyResponseQueue.length) {
+ return;
+ }
+ var resposne = dummyResponseQueue.pop();
+ resposne.finish();
+}
+
+function processResponses() {
+ while (responseQueue.length) {
+ var resposne = responseQueue.pop();
+ resposne.finish();
+ }
+}
+
+function run_test() {
+ setup_http_server();
+ setup_dummyHttpRequests(processDummyResponse);
+}
diff --git a/netwerk/test/unit/test_bug1378385_http1.js b/netwerk/test/unit/test_bug1378385_http1.js
new file mode 100644
index 0000000000..0e21db3478
--- /dev/null
+++ b/netwerk/test/unit/test_bug1378385_http1.js
@@ -0,0 +1,198 @@
+// test bug 1378385.
+//
+// Summary:
+// Assume we have 6 http requests in queue, 3 are from the focused window with
+// normal priority and the other 3 are from the non-focused window with the
+// highest priority.
+// We want to test that when "network.http.active_tab_priority" is false,
+// the server should receive 3 requests with the highest priority first
+// and then receive the rest 3 requests.
+//
+// Test step:
+// 1. Create 6 dummy http requests. Server would not process responses until told
+// all 6 requests.
+// 2. Once server receive 6 dummy requests, create 3 http requests with the focused
+// window id and normal priority and 3 requests with non-focused window id and
+// the highrst priority.
+// Note that the requets's id is set to its window id.
+// 3. Server starts to process the 6 dummy http requests, so the client can start to
+// process the pending queue. Server will queue those http requests again and wait
+// until get all 6 requests.
+// 4. When the server receive all 6 requests, we want to check if 3 requests with higher
+// priority are sent before others.
+// First, we check that if the request id of the first 3 requests in the queue is
+// equal to non focused window id.
+// Second, we check if the request id of the rest requests is equal to focused
+// window id.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var server = new HttpServer();
+server.start(-1);
+var baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+var maxConnections = 0;
+var debug = false;
+const FOCUSED_WINDOW_ID = 123;
+var NON_FOCUSED_WINDOW_ID;
+var FOCUSED_WINDOW_REQUEST_COUNT;
+var NON_FOCUSED_WINDOW_REQUEST_COUNT;
+
+function log(msg) {
+ if (!debug) {
+ return;
+ }
+
+ if (msg) {
+ dump("TEST INFO | " + msg + "\n");
+ }
+}
+
+function make_channel(url) {
+ var request = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+ request.QueryInterface(Ci.nsIHttpChannel);
+ return request;
+}
+
+function serverStopListener() {
+ server.stop();
+}
+
+function createHttpRequest(browserId, requestId, priority) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ chan.browserId = browserId;
+ chan.QueryInterface(Ci.nsISupportsPriority).priority = priority;
+ var listner = new HttpResponseListener(requestId);
+ chan.setRequestHeader("X-ID", requestId, false);
+ chan.setRequestHeader("Cache-control", "no-store", false);
+ chan.asyncOpen(listner);
+ log("Create http request id=" + requestId);
+}
+
+function setup_dummyHttpRequests() {
+ log("setup_dummyHttpRequests");
+ for (var i = 0; i < maxConnections; i++) {
+ createHttpRequest(0, i, Ci.nsISupportsPriority.PRIORITY_NORMAL);
+ do_test_pending();
+ }
+}
+
+function setup_focusedWindowHttpRequests() {
+ log("setup_focusedWindowHttpRequests");
+ for (var i = 0; i < FOCUSED_WINDOW_REQUEST_COUNT; i++) {
+ createHttpRequest(
+ FOCUSED_WINDOW_ID,
+ FOCUSED_WINDOW_ID,
+ Ci.nsISupportsPriority.PRIORITY_NORMAL
+ );
+ do_test_pending();
+ }
+}
+
+function setup_nonFocusedWindowHttpRequests() {
+ log("setup_nonFocusedWindowHttpRequests");
+ for (var i = 0; i < NON_FOCUSED_WINDOW_REQUEST_COUNT; i++) {
+ createHttpRequest(
+ NON_FOCUSED_WINDOW_ID,
+ NON_FOCUSED_WINDOW_ID,
+ Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ );
+ do_test_pending();
+ }
+}
+
+function HttpResponseListener(id) {
+ this.id = id;
+}
+
+HttpResponseListener.prototype = {
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, off, cnt) {},
+
+ onStopRequest(request, status) {
+ log("STOP id=" + this.id);
+ do_test_finished();
+ },
+};
+
+function check_response_id(responses, browserId) {
+ for (var i = 0; i < responses.length; i++) {
+ var id = responses[i].getHeader("X-ID");
+ log("response id=" + id + " browserId=" + browserId);
+ Assert.equal(id, browserId);
+ }
+}
+
+var responseQueue = [];
+function setup_http_server() {
+ log("setup_http_server");
+ maxConnections = Services.prefs.getIntPref(
+ "network.http.max-persistent-connections-per-server"
+ );
+ FOCUSED_WINDOW_REQUEST_COUNT = Math.floor(maxConnections * 0.5);
+ NON_FOCUSED_WINDOW_REQUEST_COUNT =
+ maxConnections - FOCUSED_WINDOW_REQUEST_COUNT;
+ NON_FOCUSED_WINDOW_ID = FOCUSED_WINDOW_ID + FOCUSED_WINDOW_REQUEST_COUNT;
+
+ var allDummyHttpRequestReceived = false;
+ // Start server; will be stopped at test cleanup time.
+ server.registerPathHandler("/", function (metadata, response) {
+ var id = metadata.getHeader("X-ID");
+ log("Server recived the response id=" + id);
+
+ response.processAsync();
+ response.setHeader("X-ID", id);
+ responseQueue.push(response);
+
+ if (
+ responseQueue.length == maxConnections &&
+ !allDummyHttpRequestReceived
+ ) {
+ log("received all dummy http requets");
+ allDummyHttpRequestReceived = true;
+ setup_nonFocusedWindowHttpRequests();
+ setup_focusedWindowHttpRequests();
+ processResponses();
+ } else if (responseQueue.length == maxConnections) {
+ var nonFocusedWindowResponses = responseQueue.slice(
+ 0,
+ NON_FOCUSED_WINDOW_REQUEST_COUNT
+ );
+ var focusedWindowResponses = responseQueue.slice(
+ NON_FOCUSED_WINDOW_REQUEST_COUNT,
+ responseQueue.length
+ );
+ check_response_id(nonFocusedWindowResponses, NON_FOCUSED_WINDOW_ID);
+ check_response_id(focusedWindowResponses, FOCUSED_WINDOW_ID);
+ processResponses();
+ }
+ });
+
+ registerCleanupFunction(function () {
+ server.stop(serverStopListener);
+ });
+}
+
+function processResponses() {
+ while (responseQueue.length) {
+ var resposne = responseQueue.pop();
+ resposne.finish();
+ }
+}
+
+function run_test() {
+ // Set "network.http.active_tab_priority" to false, so we can expect to
+ // receive http requests with higher priority first.
+ Services.prefs.setBoolPref("network.http.active_tab_priority", false);
+
+ setup_http_server();
+ setup_dummyHttpRequests();
+}
diff --git a/netwerk/test/unit/test_bug1411316_http1.js b/netwerk/test/unit/test_bug1411316_http1.js
new file mode 100644
index 0000000000..d1df42afdc
--- /dev/null
+++ b/netwerk/test/unit/test_bug1411316_http1.js
@@ -0,0 +1,116 @@
+// Test bug 1411316.
+//
+// Summary:
+// The purpose of this test is to test whether the HttpConnectionMgr really
+// cancel and close all connecitons when get "net:cancel-all-connections".
+//
+// Test step:
+// 1. Create 6 http requests. Server would not process responses and just put
+// all requests in its queue.
+// 2. Once server receive all 6 requests, call notifyObservers with the
+// topic "net:cancel-all-connections".
+// 3. We expect that all 6 active connections should be closed with the status
+// NS_ERROR_ABORT.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var server = new HttpServer();
+server.start(-1);
+var baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+var maxConnections = 0;
+var debug = false;
+var requestId = 0;
+
+function log(msg) {
+ if (!debug) {
+ return;
+ }
+
+ if (msg) {
+ dump("TEST INFO | " + msg + "\n");
+ }
+}
+
+function make_channel(url) {
+ var request = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+ request.QueryInterface(Ci.nsIHttpChannel);
+ return request;
+}
+
+function serverStopListener() {
+ server.stop();
+}
+
+function createHttpRequest(status) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ var listner = new HttpResponseListener(++requestId, status);
+ chan.setRequestHeader("X-ID", requestId, false);
+ chan.setRequestHeader("Cache-control", "no-store", false);
+ chan.asyncOpen(listner);
+ log("Create http request id=" + requestId);
+}
+
+function setupHttpRequests(status) {
+ log("setupHttpRequests");
+ for (var i = 0; i < maxConnections; i++) {
+ createHttpRequest(status);
+ do_test_pending();
+ }
+}
+
+function HttpResponseListener(id, onStopRequestStatus) {
+ this.id = id;
+ this.onStopRequestStatus = onStopRequestStatus;
+}
+
+HttpResponseListener.prototype = {
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, off, cnt) {},
+
+ onStopRequest(request, status) {
+ log("STOP id=" + this.id + " status=" + status);
+ Assert.ok(this.onStopRequestStatus == status);
+ do_test_finished();
+ },
+};
+
+var responseQueue = [];
+function setup_http_server() {
+ log("setup_http_server");
+ maxConnections = Services.prefs.getIntPref(
+ "network.http.max-persistent-connections-per-server"
+ );
+
+ // Start server; will be stopped at test cleanup time.
+ server.registerPathHandler("/", function (metadata, response) {
+ var id = metadata.getHeader("X-ID");
+ log("Server recived the response id=" + id);
+
+ response.processAsync();
+ response.setHeader("X-ID", id);
+ responseQueue.push(response);
+
+ if (responseQueue.length == maxConnections) {
+ log("received all http requets");
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ }
+ });
+
+ registerCleanupFunction(function () {
+ server.stop(serverStopListener);
+ });
+}
+
+function run_test() {
+ setup_http_server();
+ setupHttpRequests(Cr.NS_ERROR_ABORT);
+}
diff --git a/netwerk/test/unit/test_bug1527293.js b/netwerk/test/unit/test_bug1527293.js
new file mode 100644
index 0000000000..bed2bddec6
--- /dev/null
+++ b/netwerk/test/unit/test_bug1527293.js
@@ -0,0 +1,94 @@
+// Test bug 1527293
+//
+// Summary:
+// The purpose of this test is to check that a cache entry is doomed and not
+// reused when we don't write the content due to max entry size limit.
+//
+// Test step:
+// 1. Create http request for an entry whose size is bigger than we allow to
+// cache. The response must contain Content-Range header so the content size
+// is known in advance, but it must not contain Content-Length header because
+// the bug isn't reproducible with it.
+// 2. After receiving and checking the content do the same request again.
+// 3. Check that the request isn't conditional, i.e. the entry from previous
+// load was doomed.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+
+ Assert.throws(
+ () => {
+ metadata.getHeader("If-None-Match");
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "conditional request not expected"
+ );
+
+ response.setHeader("Accept-Ranges", "bytes");
+ let len = responseBody.length;
+ response.setHeader("Content-Range", "0-" + (len - 1) + "/" + len);
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ // Static check
+ Assert.ok(responseBody.length > 1024);
+
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(URL + "/content");
+ chan.asyncOpen(new ChannelListener(firstTimeThrough, null));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+
+ var chan = make_channel(URL + "/content");
+ chan.asyncOpen(new ChannelListener(secondTimeThrough, null));
+}
+
+function secondTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_bug1683176.js b/netwerk/test/unit/test_bug1683176.js
new file mode 100644
index 0000000000..9b0fddf7bd
--- /dev/null
+++ b/netwerk/test/unit/test_bug1683176.js
@@ -0,0 +1,91 @@
+// Test bug 1683176
+//
+// Summary:
+// Test the case when a channel is cancelled when "negotiate" authentication
+// is processing.
+//
+
+"use strict";
+
+let prefs;
+let httpserv;
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+function makeChan(url, loadingUrl) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+function authHandler(metadata, response) {
+ var body = "blablabla";
+
+ response.seizePower();
+ response.write("HTTP/1.1 401 Unauthorized\r\n");
+ response.write("WWW-Authenticate: Negotiate\r\n");
+ response.write("WWW-Authenticate: Basic realm=test\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function setup() {
+ prefs = Services.prefs;
+
+ prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+ prefs.setStringPref("network.negotiate-auth.trusted-uris", "localhost");
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/auth", authHandler);
+ httpserv.start(-1);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ prefs.clearUserPref("network.auth.subresource-http-auth-allow");
+ prefs.clearUserPref("network.negotiate-auth.trusted-uris");
+ await httpserv.stop();
+});
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ let topic = "http-on-transaction-suspended-authentication";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == topic) {
+ Services.obs.removeObserver(observer, topic);
+ let channel = aSubject.QueryInterface(Ci.nsIChannel);
+ channel.cancel(Cr.NS_BINDING_ABORTED);
+ resolve();
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+
+ chan.asyncOpen(new ChannelListener(finish, null, CL_EXPECT_FAILURE));
+ function finish() {
+ resolve();
+ }
+ });
+}
+
+add_task(async function testCancelAuthentication() {
+ let chan = makeChan(URL + "/auth", URL);
+ await channelOpenPromise(chan);
+ Assert.equal(chan.status, Cr.NS_BINDING_ABORTED);
+});
diff --git a/netwerk/test/unit/test_bug1725766.js b/netwerk/test/unit/test_bug1725766.js
new file mode 100644
index 0000000000..7b50a7cbd4
--- /dev/null
+++ b/netwerk/test/unit/test_bug1725766.js
@@ -0,0 +1,85 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+let hserv = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+);
+let handlerInfo;
+const testScheme = "x-moz-test";
+
+function setup() {
+ var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ handler.name = testScheme;
+ handler.uriTemplate = "http://test.mozilla.org/%s";
+
+ var extps = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ handlerInfo = extps.getProtocolHandlerInfo(testScheme);
+ handlerInfo.possibleApplicationHandlers.appendElement(handler);
+
+ hserv.store(handlerInfo);
+ Assert.ok(extps.externalProtocolHandlerExists(testScheme));
+}
+
+setup();
+registerCleanupFunction(() => {
+ hserv.remove(handlerInfo);
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+add_task(async function viewsourceExternalProtocol() {
+ Assert.throws(
+ () => makeChan(`view-source:${testScheme}:foo.example.com`),
+ /NS_ERROR_MALFORMED_URI/
+ );
+});
+
+add_task(async function viewsourceExternalProtocolRedirect() {
+ let httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", `${testScheme}:foo@bar.com`, false);
+
+ var body = "Moved\n";
+ response.bodyOutputStream.write(body, body.length);
+ });
+ httpserv.start(-1);
+
+ let chan = makeChan(
+ `view-source:http://127.0.0.1:${httpserv.identity.primaryPort}/`
+ );
+ let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
+ Assert.equal(req.status, Cr.NS_ERROR_MALFORMED_URI);
+ await httpserv.stop();
+});
diff --git a/netwerk/test/unit/test_bug203271.js b/netwerk/test/unit/test_bug203271.js
new file mode 100644
index 0000000000..da43a1b4aa
--- /dev/null
+++ b/netwerk/test/unit/test_bug203271.js
@@ -0,0 +1,249 @@
+//
+// Tests if a response with an Expires-header in the past
+// and Cache-Control: max-age in the future works as
+// specified in RFC 2616 section 14.9.3 by letting max-age
+// take precedence
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ // original problem described in bug#203271
+ {
+ url: "/precedence",
+ server: "0",
+ expected: "0",
+ responseheader: [
+ "Expires: " + getDateString(-1),
+ "Cache-Control: max-age=3600",
+ ],
+ },
+
+ {
+ url: "/precedence?0",
+ server: "0",
+ expected: "0",
+ responseheader: [
+ "Cache-Control: max-age=3600",
+ "Expires: " + getDateString(-1),
+ ],
+ },
+
+ // max-age=1s, expires=1 year from now
+ {
+ url: "/precedence?1",
+ server: "0",
+ expected: "0",
+ responseheader: [
+ "Expires: " + getDateString(1),
+ "Cache-Control: max-age=1",
+ ],
+ },
+
+ // expires=now
+ {
+ url: "/precedence?2",
+ server: "0",
+ expected: "0",
+ responseheader: ["Expires: " + getDateString(0)],
+ },
+
+ // max-age=1s
+ {
+ url: "/precedence?3",
+ server: "0",
+ expected: "0",
+ responseheader: ["Cache-Control: max-age=1"],
+ },
+
+ // The test below is the example from
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=203271#c27
+ //
+ // max-age=2592000s (1 month), expires=1 year from now, date=1 year ago
+ {
+ url: "/precedence?4",
+ server: "0",
+ expected: "0",
+ responseheader: [
+ "Cache-Control: private, max-age=2592000",
+ "Expires: " + getDateString(+1),
+ ],
+ explicitDate: getDateString(-1),
+ },
+
+ // The two tests below are also examples of clocks really out of synch
+ // max-age=1s, date=1 year from now
+ {
+ url: "/precedence?5",
+ server: "0",
+ expected: "0",
+ responseheader: ["Cache-Control: max-age=1"],
+ explicitDate: getDateString(1),
+ },
+
+ // max-age=60s, date=1 year from now
+ {
+ url: "/precedence?6",
+ server: "0",
+ expected: "0",
+ responseheader: ["Cache-Control: max-age=60"],
+ explicitDate: getDateString(1),
+ },
+
+ // this is just to get a pause of 3s to allow cache-entries to expire
+ { url: "/precedence?999", server: "0", expected: "0", delay: "3000" },
+
+ // Below are the cases which actually matters
+ { url: "/precedence", server: "1", expected: "0" }, // should be cached
+
+ { url: "/precedence?0", server: "1", expected: "0" }, // should be cached
+
+ { url: "/precedence?1", server: "1", expected: "1" }, // should have expired
+
+ { url: "/precedence?2", server: "1", expected: "1" }, // should have expired
+
+ { url: "/precedence?3", server: "1", expected: "1" }, // should have expired
+
+ { url: "/precedence?4", server: "1", expected: "1" }, // should have expired
+
+ { url: "/precedence?5", server: "1", expected: "1" }, // should have expired
+
+ { url: "/precedence?6", server: "1", expected: "0" }, // should be cached
+];
+
+function logit(i, data, ctx) {
+ dump(
+ "requested [" +
+ tests[i].server +
+ "] " +
+ "got [" +
+ data +
+ "] " +
+ "expected [" +
+ tests[i].expected +
+ "]"
+ );
+
+ if (tests[i].responseheader) {
+ dump("\t[" + tests[i].responseheader + "]");
+ }
+ dump("\n");
+ // Dump all response-headers
+ dump("\n===================================\n");
+ ctx.visitResponseHeaders({
+ visitHeader(key, val) {
+ dump("\t" + key + ":" + val + "\n");
+ },
+ });
+ dump("===================================\n");
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.requestMethod = "GET"; // default value, just being paranoid...
+ httpChan.setRequestHeader("x-request", value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server);
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, channel));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data, ctx);
+ Assert.equal(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ var delay = tests[index++].delay;
+ if (delay) {
+ do_timeout(delay, triggerNextTest);
+ } else {
+ triggerNextTest();
+ }
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/precedence", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ triggerNextTest();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = metadata.getHeader("x-request");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var date = tests[index].explicitDate;
+ if (date == undefined) {
+ response.setHeader("Date", getDateString(0), false);
+ } else {
+ response.setHeader("Date", date, false);
+ }
+
+ var header = tests[index].responseheader;
+ if (header == undefined) {
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ } else {
+ for (var i = 0; i < header.length; i++) {
+ var splitHdr = header[i].split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ var d = new Date();
+ return (
+ days[d.getUTCDay()] +
+ ", " +
+ d.getUTCDate() +
+ " " +
+ months[d.getUTCMonth()] +
+ " " +
+ (d.getUTCFullYear() + yearDelta) +
+ " " +
+ d.getUTCHours() +
+ ":" +
+ d.getUTCMinutes() +
+ ":" +
+ d.getUTCSeconds() +
+ " UTC"
+ );
+}
diff --git a/netwerk/test/unit/test_bug248970_cache.js b/netwerk/test/unit/test_bug248970_cache.js
new file mode 100644
index 0000000000..d52d3b88d9
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cache.js
@@ -0,0 +1,144 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// names for cache devices
+const kDiskDevice = "disk";
+const kMemoryDevice = "memory";
+
+const kCacheA = "http://cache/A";
+const kCacheA2 = "http://cache/A2";
+const kCacheB = "http://cache/B";
+const kTestContent = "test content";
+
+const entries = [
+ // key content device should exist after leaving PB
+ [kCacheA, kTestContent, kMemoryDevice, true],
+ [kCacheA2, kTestContent, kDiskDevice, false],
+ [kCacheB, kTestContent, kDiskDevice, true],
+];
+
+var store_idx;
+var store_cb = null;
+
+function store_entries(cb) {
+ if (cb) {
+ store_cb = cb;
+ store_idx = 0;
+ }
+
+ if (store_idx == entries.length) {
+ executeSoon(store_cb);
+ return;
+ }
+
+ asyncOpenCacheEntry(
+ entries[store_idx][0],
+ entries[store_idx][2],
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ Services.loadContextInfo.custom(false, {
+ privateBrowsingId: entries[store_idx][3] ? 0 : 1,
+ }),
+ store_data
+ );
+}
+
+var store_data = function (status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0, entries[store_idx][1].length);
+
+ var written = os.write(entries[store_idx][1], entries[store_idx][1].length);
+ if (written != entries[store_idx][1].length) {
+ do_throw(
+ "os.write has not written all data!\n" +
+ " Expected: " +
+ entries[store_idx][1].length +
+ "\n" +
+ " Actual: " +
+ written +
+ "\n"
+ );
+ }
+ os.close();
+ entry.close();
+ store_idx++;
+ executeSoon(store_entries);
+};
+
+var check_idx;
+var check_cb = null;
+var check_pb_exited;
+function check_entries(cb, pbExited) {
+ if (cb) {
+ check_cb = cb;
+ check_idx = 0;
+ check_pb_exited = pbExited;
+ }
+
+ if (check_idx == entries.length) {
+ executeSoon(check_cb);
+ return;
+ }
+
+ asyncOpenCacheEntry(
+ entries[check_idx][0],
+ entries[check_idx][2],
+ Ci.nsICacheStorage.OPEN_READONLY,
+ Services.loadContextInfo.custom(false, {
+ privateBrowsingId: entries[check_idx][3] ? 0 : 1,
+ }),
+ check_data
+ );
+}
+
+var check_data = function (status, entry) {
+ var cont = function () {
+ check_idx++;
+ executeSoon(check_entries);
+ };
+
+ if (!check_pb_exited || entries[check_idx][3]) {
+ Assert.equal(status, Cr.NS_OK);
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function (read) {
+ entry.close();
+ Assert.equal(read, entries[check_idx][1]);
+ cont();
+ });
+ } else {
+ Assert.equal(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ cont();
+ }
+};
+
+function run_test() {
+ // Simulate a profile dir for xpcshell
+ do_get_profile();
+
+ // Start off with an empty cache
+ evict_cache_entries();
+
+ // Store cache-A, cache-A2, cache-B and cache-C
+ store_entries(run_test2);
+
+ do_test_pending();
+}
+
+function run_test2() {
+ // Check if cache-A, cache-A2, cache-B and cache-C are available
+ check_entries(run_test3, false);
+}
+
+function run_test3() {
+ // Simulate all private browsing instances being closed
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+
+ // Make sure the memory device is not empty
+ get_device_entry_count(kMemoryDevice, null, function (count) {
+ Assert.equal(count, 1);
+ // Check if cache-A is gone, and cache-B and cache-C are still available
+ check_entries(do_test_finished, true);
+ });
+}
diff --git a/netwerk/test/unit/test_bug248970_cookie.js b/netwerk/test/unit/test_bug248970_cookie.js
new file mode 100644
index 0000000000..0dfe34e8e0
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cookie.js
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver;
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+function makeChan(path) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/" + path,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function setup_chan(path, isPrivate, callback) {
+ var chan = makeChan(path);
+ chan.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(isPrivate);
+ chan.asyncOpen(new ChannelListener(callback));
+}
+
+function set_cookie(value, callback) {
+ return setup_chan("set?cookie=" + value, false, callback);
+}
+
+function set_private_cookie(value, callback) {
+ return setup_chan("set?cookie=" + value, true, callback);
+}
+
+function check_cookie_presence(value, isPrivate, expected, callback) {
+ setup_chan(
+ "present?cookie=" + value.replace("=", "|"),
+ isPrivate,
+ function (req) {
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.responseStatus, expected ? 200 : 404);
+ callback(req);
+ }
+ );
+}
+
+function presentHandler(metadata, response) {
+ var present = false;
+ var match = /cookie=([^&]*)/.exec(metadata.queryString);
+ if (match) {
+ try {
+ present = metadata
+ .getHeader("Cookie")
+ .includes(match[1].replace("|", "="));
+ } catch (x) {}
+ }
+ response.setStatusLine("1.0", present ? 200 : 404, "");
+}
+
+function setHandler(metadata, response) {
+ response.setStatusLine("1.0", 200, "Cookie set");
+ var match = /cookie=([^&]*)/.exec(metadata.queryString);
+ if (match) {
+ response.setHeader("Set-Cookie", match[1]);
+ }
+}
+
+function run_test() {
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/set", setHandler);
+ httpserver.registerPathHandler("/present", presentHandler);
+ httpserver.start(-1);
+
+ do_test_pending();
+
+ function check_cookie(req) {
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.responseStatus, 200);
+ try {
+ Assert.ok(
+ req.getResponseHeader("Set-Cookie") != "",
+ "expected a Set-Cookie header"
+ );
+ } catch (x) {
+ do_throw("missing Set-Cookie header");
+ }
+
+ runNextTest();
+ }
+
+ let tests = [];
+
+ function runNextTest() {
+ executeSoon(tests.shift());
+ }
+
+ tests.push(function () {
+ set_cookie("C1=V1", check_cookie);
+ });
+ tests.push(function () {
+ set_private_cookie("C2=V2", check_cookie);
+ });
+ tests.push(function () {
+ // Check that the first cookie is present in a non-private request
+ check_cookie_presence("C1=V1", false, true, runNextTest);
+ });
+ tests.push(function () {
+ // Check that the second cookie is present in a private request
+ check_cookie_presence("C2=V2", true, true, runNextTest);
+ });
+ tests.push(function () {
+ // Check that the first cookie is not present in a private request
+ check_cookie_presence("C1=V1", true, false, runNextTest);
+ });
+ tests.push(function () {
+ // Check that the second cookie is not present in a non-private request
+ check_cookie_presence("C2=V2", false, false, runNextTest);
+ });
+
+ // The following test only works in a non-e10s situation at the moment,
+ // since the notification needs to run in the parent process but there is
+ // no existing mechanism to make that happen.
+ if (!inChildProcess()) {
+ tests.push(function () {
+ // Simulate all private browsing instances being closed
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+ // Check that all private cookies are now unavailable in new private requests
+ check_cookie_presence("C2=V2", true, false, runNextTest);
+ });
+ }
+
+ tests.push(function () {
+ httpserver.stop(do_test_finished);
+ });
+
+ runNextTest();
+}
diff --git a/netwerk/test/unit/test_bug261425.js b/netwerk/test/unit/test_bug261425.js
new file mode 100644
index 0000000000..f58ad62e93
--- /dev/null
+++ b/netwerk/test/unit/test_bug261425.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function run_test() {
+ var newURI = Services.io.newURI("http://foo.com");
+
+ var success = false;
+ try {
+ newURI = newURI.mutate().setSpec("http: //foo.com").finalize();
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success) {
+ do_throw(
+ "We didn't throw NS_ERROR_MALFORMED_URI when a space was passed in the hostname!"
+ );
+ }
+
+ success = false;
+ try {
+ newURI.mutate().setHost(" foo.com").finalize();
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success) {
+ do_throw(
+ "We didn't throw NS_ERROR_MALFORMED_URI when a space was passed in the hostname!"
+ );
+ }
+}
diff --git a/netwerk/test/unit/test_bug263127.js b/netwerk/test/unit/test_bug263127.js
new file mode 100644
index 0000000000..75dc650980
--- /dev/null
+++ b/netwerk/test/unit/test_bug263127.js
@@ -0,0 +1,58 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var server;
+const BUGID = "263127";
+
+var listener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIDownloadObserver"]),
+
+ onDownloadComplete(downloader, request, status, file) {
+ do_test_pending();
+ server.stop(do_test_finished);
+
+ if (!file) {
+ do_throw("Download failed");
+ }
+
+ try {
+ file.remove(false);
+ } catch (e) {
+ do_throw(e);
+ }
+
+ Assert.ok(!file.exists());
+
+ do_test_finished();
+ },
+};
+
+function run_test() {
+ // start server
+ server = new HttpServer();
+ server.start(-1);
+
+ // Initialize downloader
+ var channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/",
+ loadUsingSystemPrincipal: true,
+ });
+ var targetFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ targetFile.append("bug" + BUGID + ".test");
+ if (targetFile.exists()) {
+ targetFile.remove(false);
+ }
+
+ var downloader = Cc["@mozilla.org/network/downloader;1"].createInstance(
+ Ci.nsIDownloader
+ );
+ downloader.init(listener, targetFile);
+
+ // Start download
+ channel.asyncOpen(downloader);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug282432.js b/netwerk/test/unit/test_bug282432.js
new file mode 100644
index 0000000000..5e6e7a19b6
--- /dev/null
+++ b/netwerk/test/unit/test_bug282432.js
@@ -0,0 +1,37 @@
+"use strict";
+
+function run_test() {
+ do_test_pending();
+
+ function StreamListener() {}
+
+ StreamListener.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(aRequest) {},
+
+ onStopRequest(aRequest, aStatusCode) {
+ // Make sure we can catch the error NS_ERROR_FILE_NOT_FOUND here.
+ Assert.equal(aStatusCode, Cr.NS_ERROR_FILE_NOT_FOUND);
+ do_test_finished();
+ },
+
+ onDataAvailable(aRequest, aStream, aOffset, aCount) {
+ do_throw("The channel must not call onDataAvailable().");
+ },
+ };
+
+ let listener = new StreamListener();
+
+ // This file does not exist.
+ let file = do_get_file("_NOT_EXIST_.txt", true);
+ Assert.ok(!file.exists());
+ let channel = NetUtil.newChannel({
+ uri: Services.io.newFileURI(file),
+ loadUsingSystemPrincipal: true,
+ });
+ channel.asyncOpen(listener);
+}
diff --git a/netwerk/test/unit/test_bug321706.js b/netwerk/test/unit/test_bug321706.js
new file mode 100644
index 0000000000..a267d6586d
--- /dev/null
+++ b/netwerk/test/unit/test_bug321706.js
@@ -0,0 +1,10 @@
+"use strict";
+
+const url = "http://foo.com/folder/file?/.";
+
+function run_test() {
+ var newURI = Services.io.newURI(url);
+ Assert.equal(newURI.spec, url);
+ Assert.equal(newURI.pathQueryRef, "/folder/file?/.");
+ Assert.equal(newURI.resolve("./file?/."), url);
+}
diff --git a/netwerk/test/unit/test_bug331825.js b/netwerk/test/unit/test_bug331825.js
new file mode 100644
index 0000000000..5a389c74a4
--- /dev/null
+++ b/netwerk/test/unit/test_bug331825.js
@@ -0,0 +1,43 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var server;
+const BUGID = "331825";
+
+function TestListener() {}
+TestListener.prototype.onStartRequest = function (request) {};
+TestListener.prototype.onStopRequest = function (request, status) {
+ var channel = request.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(channel.responseStatus, 304);
+
+ server.stop(do_test_finished);
+};
+
+function run_test() {
+ // start server
+ server = new HttpServer();
+
+ server.registerPathHandler("/bug" + BUGID, bug331825);
+
+ server.start(-1);
+
+ // make request
+ var channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/bug" + BUGID,
+ loadUsingSystemPrincipal: true,
+ });
+
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ channel.setRequestHeader("If-None-Match", "foobar", false);
+ channel.asyncOpen(new TestListener());
+
+ do_test_pending();
+}
+
+// PATH HANDLER FOR /bug331825
+function bug331825(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+}
diff --git a/netwerk/test/unit/test_bug336501.js b/netwerk/test/unit/test_bug336501.js
new file mode 100644
index 0000000000..3e15fd42bb
--- /dev/null
+++ b/netwerk/test/unit/test_bug336501.js
@@ -0,0 +1,26 @@
+"use strict";
+
+function run_test() {
+ var f = do_get_file("test_bug336501.js");
+
+ var fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fis.init(f, -1, -1, 0);
+
+ var bis = Cc["@mozilla.org/network/buffered-input-stream;1"].createInstance(
+ Ci.nsIBufferedInputStream
+ );
+ bis.init(fis, 32);
+
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sis.init(bis);
+
+ sis.read(45);
+ sis.close();
+
+ var data = sis.read(45);
+ Assert.equal(data.length, 0);
+}
diff --git a/netwerk/test/unit/test_bug337744.js b/netwerk/test/unit/test_bug337744.js
new file mode 100644
index 0000000000..69a99b8765
--- /dev/null
+++ b/netwerk/test/unit/test_bug337744.js
@@ -0,0 +1,126 @@
+/* verify that certain invalid URIs are not parsed by the resource
+ protocol handler */
+
+"use strict";
+
+const specs = [
+ "resource://res-test//",
+ "resource://res-test/?foo=http:",
+ "resource://res-test/?foo=" + encodeURIComponent("http://example.com/"),
+ "resource://res-test/?foo=" + encodeURIComponent("x\\y"),
+ "resource://res-test/..%2F",
+ "resource://res-test/..%2f",
+ "resource://res-test/..%2F..",
+ "resource://res-test/..%2f..",
+ "resource://res-test/../../",
+ "resource://res-test/http://www.mozilla.org/",
+ "resource://res-test/file:///",
+];
+
+const error_specs = [
+ "resource://res-test/..\\",
+ "resource://res-test/..\\..\\",
+ "resource://res-test/..%5C",
+ "resource://res-test/..%5c",
+];
+
+// Create some fake principal that has not enough
+// privileges to access any resource: uri.
+var uri = NetUtil.newURI("http://www.example.com");
+var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
+
+function get_channel(spec) {
+ var channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(spec),
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ Assert.throws(
+ () => {
+ channel.asyncOpen(null);
+ },
+ /NS_ERROR_DOM_BAD_URI/,
+ `asyncOpen() of uri: ${spec} should throw`
+ );
+ Assert.throws(
+ () => {
+ channel.open();
+ },
+ /NS_ERROR_DOM_BAD_URI/,
+ `Open() of uri: ${spec} should throw`
+ );
+
+ return channel;
+}
+
+function check_safe_resolution(spec, rootURI) {
+ info(`Testing URL "${spec}"`);
+
+ let channel = get_channel(spec);
+
+ ok(
+ channel.name.startsWith(rootURI),
+ `URL resolved safely to ${channel.name}`
+ );
+ let startOfQuery = channel.name.indexOf("?");
+ if (startOfQuery == -1) {
+ ok(!/%2f/i.test(channel.name), `URL contains no escaped / characters`);
+ } else {
+ // Escaped slashes are allowed in the query or hash part of the URL
+ ok(
+ !channel.name.replace(/\?.*/, "").includes("%2f"),
+ `URL contains no escaped slashes before the query ${channel.name}`
+ );
+ }
+}
+
+function check_resolution_error(spec) {
+ Assert.throws(
+ () => {
+ get_channel(spec);
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "Expected a malformed URI error"
+ );
+}
+
+function run_test() {
+ // resource:/// and resource://gre/ are resolved specially, so we need
+ // to create a temporary resource package to test the standard logic
+ // with.
+
+ let resProto = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(
+ Ci.nsIResProtocolHandler
+ );
+ let rootFile = Services.dirsvc.get("GreD", Ci.nsIFile);
+ let rootURI = Services.io.newFileURI(rootFile);
+
+ rootFile.append("directory-that-does-not-exist");
+ let inexistentURI = Services.io.newFileURI(rootFile);
+
+ resProto.setSubstitution("res-test", rootURI);
+ resProto.setSubstitution("res-inexistent", inexistentURI);
+ registerCleanupFunction(() => {
+ resProto.setSubstitution("res-test", null);
+ resProto.setSubstitution("res-inexistent", null);
+ });
+
+ let baseRoot = resProto.resolveURI(Services.io.newURI("resource:///"));
+ let greRoot = resProto.resolveURI(Services.io.newURI("resource://gre/"));
+
+ for (let spec of specs) {
+ check_safe_resolution(spec, rootURI.spec);
+ check_safe_resolution(
+ spec.replace("res-test", "res-inexistent"),
+ inexistentURI.spec
+ );
+ check_safe_resolution(spec.replace("res-test", ""), baseRoot);
+ check_safe_resolution(spec.replace("res-test", "gre"), greRoot);
+ }
+
+ for (let spec of error_specs) {
+ check_resolution_error(spec);
+ }
+}
diff --git a/netwerk/test/unit/test_bug368702.js b/netwerk/test/unit/test_bug368702.js
new file mode 100644
index 0000000000..c77f40a71b
--- /dev/null
+++ b/netwerk/test/unit/test_bug368702.js
@@ -0,0 +1,147 @@
+"use strict";
+
+function run_test() {
+ var tld = Services.eTLD;
+ Assert.equal(tld.getPublicSuffixFromHost("localhost"), "localhost");
+ Assert.equal(tld.getPublicSuffixFromHost("localhost."), "localhost.");
+ Assert.equal(tld.getPublicSuffixFromHost("domain.com"), "com");
+ Assert.equal(tld.getPublicSuffixFromHost("domain.com."), "com.");
+ Assert.equal(tld.getPublicSuffixFromHost("domain.co.uk"), "co.uk");
+ Assert.equal(tld.getPublicSuffixFromHost("domain.co.uk."), "co.uk.");
+ Assert.equal(tld.getPublicSuffixFromHost("co.uk"), "co.uk");
+ Assert.equal(tld.getBaseDomainFromHost("domain.co.uk"), "domain.co.uk");
+ Assert.equal(tld.getBaseDomainFromHost("domain.co.uk."), "domain.co.uk.");
+
+ try {
+ tld.getPublicSuffixFromHost("");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ tld.getBaseDomainFromHost("domain.co.uk", 1);
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ tld.getBaseDomainFromHost("co.uk");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ tld.getBaseDomainFromHost("");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ tld.getPublicSuffixFromHost("1.2.3.4");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ tld.getPublicSuffixFromHost("2010:836B:4179::836B:4179");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ tld.getPublicSuffixFromHost("3232235878");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ tld.getPublicSuffixFromHost("::ffff:192.9.5.5");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ tld.getPublicSuffixFromHost("::1");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ // Check IP addresses with trailing dot as well, Necko sometimes accepts
+ // those (depending on operating system, see bug 380543)
+ try {
+ tld.getPublicSuffixFromHost("127.0.0.1.");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ tld.getPublicSuffixFromHost("::ffff:127.0.0.1.");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ // check normalization: output should be consistent with
+ // nsIURI::GetAsciiHost(), i.e. lowercased and ASCII/ACE encoded
+ var uri = Services.io.newURI("http://b\u00FCcher.co.uk");
+ Assert.equal(tld.getBaseDomain(uri), "xn--bcher-kva.co.uk");
+ Assert.equal(
+ tld.getBaseDomainFromHost("b\u00FCcher.co.uk"),
+ "xn--bcher-kva.co.uk"
+ );
+ Assert.equal(tld.getPublicSuffix(uri), "co.uk");
+ Assert.equal(tld.getPublicSuffixFromHost("b\u00FCcher.co.uk"), "co.uk");
+
+ // check that malformed hosts are rejected as invalid args
+ try {
+ tld.getBaseDomainFromHost("domain.co.uk..");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ tld.getBaseDomainFromHost("domain.co..uk");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ tld.getBaseDomainFromHost(".domain.co.uk");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ tld.getBaseDomainFromHost(".domain.co.uk");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ tld.getBaseDomainFromHost(".");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ tld.getBaseDomainFromHost("..");
+ do_throw("this should fail");
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+}
diff --git a/netwerk/test/unit/test_bug369787.js b/netwerk/test/unit/test_bug369787.js
new file mode 100644
index 0000000000..36cf2472e1
--- /dev/null
+++ b/netwerk/test/unit/test_bug369787.js
@@ -0,0 +1,73 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const BUGID = "369787";
+var server = null;
+var channel = null;
+
+function change_content_type() {
+ var origType = channel.contentType;
+ const newType = "x-foo/x-bar";
+ channel.contentType = newType;
+ Assert.equal(channel.contentType, newType);
+ channel.contentType = origType;
+ Assert.equal(channel.contentType, origType);
+}
+
+function TestListener() {}
+TestListener.prototype.onStartRequest = function (request) {
+ try {
+ // request might be different from channel
+ channel = request.QueryInterface(Ci.nsIChannel);
+
+ change_content_type();
+ } catch (ex) {
+ print(ex);
+ throw ex;
+ }
+};
+TestListener.prototype.onStopRequest = function (request, status) {
+ try {
+ change_content_type();
+ } catch (ex) {
+ print(ex);
+ // don't re-throw ex to avoid hanging the test
+ }
+
+ do_timeout(0, after_channel_closed);
+};
+
+function after_channel_closed() {
+ try {
+ change_content_type();
+ } finally {
+ server.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ // start server
+ server = new HttpServer();
+
+ server.registerPathHandler("/bug" + BUGID, bug369787);
+
+ server.start(-1);
+
+ // make request
+ channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/bug" + BUGID,
+ loadUsingSystemPrincipal: true,
+ });
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ channel.asyncOpen(new TestListener());
+
+ do_test_pending();
+}
+
+// PATH HANDLER FOR /bug369787
+function bug369787(metadata, response) {
+ /* do nothing */
+}
diff --git a/netwerk/test/unit/test_bug371473.js b/netwerk/test/unit/test_bug371473.js
new file mode 100644
index 0000000000..999a6c00cf
--- /dev/null
+++ b/netwerk/test/unit/test_bug371473.js
@@ -0,0 +1,36 @@
+"use strict";
+
+function test_not_too_long() {
+ var spec = "jar:http://example.com/bar.jar!/";
+ try {
+ Services.io.newURI(spec);
+ } catch (e) {
+ do_throw("newURI threw even though it wasn't passed a large nested URI?");
+ }
+}
+
+function test_too_long() {
+ var i;
+ var prefix = "jar:";
+ for (i = 0; i < 16; i++) {
+ prefix = prefix + prefix;
+ }
+ var suffix = "!/";
+ for (i = 0; i < 16; i++) {
+ suffix = suffix + suffix;
+ }
+
+ var spec = prefix + "http://example.com/bar.jar" + suffix;
+ try {
+ // The following will produce a recursive call that if
+ // unchecked would lead to a stack overflow. If we
+ // do not crash here and thus an exception is caught
+ // we have passed the test.
+ Services.io.newURI(spec);
+ } catch (e) {}
+}
+
+function run_test() {
+ test_not_too_long();
+ test_too_long();
+}
diff --git a/netwerk/test/unit/test_bug376844.js b/netwerk/test/unit/test_bug376844.js
new file mode 100644
index 0000000000..5184a30e54
--- /dev/null
+++ b/netwerk/test/unit/test_bug376844.js
@@ -0,0 +1,19 @@
+"use strict";
+
+const testURLs = [
+ ["http://example.com/<", "http://example.com/%3C"],
+ ["http://example.com/>", "http://example.com/%3E"],
+ ["http://example.com/'", "http://example.com/'"],
+ ['http://example.com/"', "http://example.com/%22"],
+ ["http://example.com/?<", "http://example.com/?%3C"],
+ ["http://example.com/?>", "http://example.com/?%3E"],
+ ["http://example.com/?'", "http://example.com/?%27"],
+ ['http://example.com/?"', "http://example.com/?%22"],
+];
+
+function run_test() {
+ for (var i = 0; i < testURLs.length; i++) {
+ var uri = Services.io.newURI(testURLs[i][0]);
+ Assert.equal(uri.spec, testURLs[i][1]);
+ }
+}
diff --git a/netwerk/test/unit/test_bug376865.js b/netwerk/test/unit/test_bug376865.js
new file mode 100644
index 0000000000..260dcbf7e6
--- /dev/null
+++ b/netwerk/test/unit/test_bug376865.js
@@ -0,0 +1,23 @@
+"use strict";
+
+function run_test() {
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsISupportsCString
+ );
+ stream.data = "foo bar baz";
+
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
+ Ci.nsIInputStreamPump
+ );
+ pump.init(stream, 0, 0, false);
+
+ // When we pass a null listener argument too asyncRead we expect it to throw
+ // instead of crashing.
+ try {
+ pump.asyncRead(null);
+ } catch (e) {
+ return;
+ }
+
+ do_throw("asyncRead didn't throw when passed a null listener argument.");
+}
diff --git a/netwerk/test/unit/test_bug379034.js b/netwerk/test/unit/test_bug379034.js
new file mode 100644
index 0000000000..eb28daf51e
--- /dev/null
+++ b/netwerk/test/unit/test_bug379034.js
@@ -0,0 +1,19 @@
+"use strict";
+
+function run_test() {
+ const ios = Services.io;
+
+ var base = ios.newURI("http://localhost/bug379034/index.html");
+
+ var uri = ios.newURI("http:a.html", null, base);
+ Assert.equal(uri.spec, "http://localhost/bug379034/a.html");
+
+ uri = ios.newURI("HtTp:b.html", null, base);
+ Assert.equal(uri.spec, "http://localhost/bug379034/b.html");
+
+ uri = ios.newURI("https:c.html", null, base);
+ Assert.equal(uri.spec, "https://c.html/");
+
+ uri = ios.newURI("./https:d.html", null, base);
+ Assert.equal(uri.spec, "http://localhost/bug379034/https:d.html");
+}
diff --git a/netwerk/test/unit/test_bug380994.js b/netwerk/test/unit/test_bug380994.js
new file mode 100644
index 0000000000..c46d9b29ed
--- /dev/null
+++ b/netwerk/test/unit/test_bug380994.js
@@ -0,0 +1,24 @@
+/* check resource: protocol for traversal problems */
+
+"use strict";
+
+const specs = [
+ "resource:///chrome/../plugins",
+ "resource:///chrome%2f../plugins",
+ "resource:///chrome/..%2fplugins",
+ "resource:///chrome%2f%2e%2e%2fplugins",
+ "resource:///../../../..",
+ "resource:///..%2f..%2f..%2f..",
+ "resource:///%2e%2e",
+];
+
+function run_test() {
+ for (var spec of specs) {
+ var uri = Services.io.newURI(spec);
+ if (uri.spec.includes("..")) {
+ do_throw(
+ "resource: traversal remains: '" + spec + "' ==> '" + uri.spec + "'"
+ );
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_bug388281.js b/netwerk/test/unit/test_bug388281.js
new file mode 100644
index 0000000000..5ded2ad2b5
--- /dev/null
+++ b/netwerk/test/unit/test_bug388281.js
@@ -0,0 +1,25 @@
+"use strict";
+
+function run_test() {
+ const ios = Services.io;
+
+ var uri = ios.newURI("http://foo.com/file.txt");
+ uri = uri.mutate().setPort(90).finalize();
+ Assert.equal(uri.hostPort, "foo.com:90");
+
+ uri = ios.newURI("http://foo.com:10/file.txt");
+ uri = uri.mutate().setPort(500).finalize();
+ Assert.equal(uri.hostPort, "foo.com:500");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt");
+ uri = uri.mutate().setPort(20).finalize();
+ Assert.equal(uri.hostPort, "foo.com:20");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt");
+ uri = uri.mutate().setPort(-1).finalize();
+ Assert.equal(uri.hostPort, "foo.com");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt");
+ uri = uri.mutate().setPort(80).finalize();
+ Assert.equal(uri.hostPort, "foo.com");
+}
diff --git a/netwerk/test/unit/test_bug396389.js b/netwerk/test/unit/test_bug396389.js
new file mode 100644
index 0000000000..cb71f549a6
--- /dev/null
+++ b/netwerk/test/unit/test_bug396389.js
@@ -0,0 +1,63 @@
+"use strict";
+
+function round_trip(uri) {
+ var objectOutStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIObjectOutputStream
+ );
+ var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ objectOutStream.setOutputStream(pipe.outputStream);
+ objectOutStream.writeCompoundObject(uri, Ci.nsISupports, true);
+ objectOutStream.close();
+
+ var objectInStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIObjectInputStream
+ );
+ objectInStream.setInputStream(pipe.inputStream);
+ return objectInStream.readObject(true).QueryInterface(Ci.nsIURI);
+}
+
+var prefData = [
+ {
+ name: "network.IDN_show_punycode",
+ newVal: false,
+ },
+];
+
+function run_test() {
+ var uri1 = Services.io.newURI("file:///");
+ Assert.ok(uri1 instanceof Ci.nsIFileURL);
+
+ var uri2 = uri1.mutate().finalize();
+ Assert.ok(uri2 instanceof Ci.nsIFileURL);
+ Assert.ok(uri1.equals(uri2));
+
+ var uri3 = round_trip(uri1);
+ Assert.ok(uri3 instanceof Ci.nsIFileURL);
+ Assert.ok(uri1.equals(uri3));
+
+ // Make sure our prefs are set such that this test actually means something
+ var prefs = Services.prefs;
+ for (let pref of prefData) {
+ prefs.setBoolPref(pref.name, pref.newVal);
+ }
+
+ try {
+ // URI stolen from
+ // http://lists.w3.org/Archives/Public/public-iri/2004Mar/0012.html
+ var uri4 = Services.io.newURI("http://xn--jos-dma.example.net.ch/");
+ Assert.equal(uri4.asciiHost, "xn--jos-dma.example.net.ch");
+ Assert.equal(uri4.displayHost, "jos\u00e9.example.net.ch");
+
+ var uri5 = round_trip(uri4);
+ Assert.ok(uri4.equals(uri5));
+ Assert.equal(uri4.displayHost, uri5.displayHost);
+ Assert.equal(uri4.asciiHost, uri5.asciiHost);
+ } finally {
+ for (let pref of prefData) {
+ if (prefs.prefHasUserValue(pref.name)) {
+ prefs.clearUserPref(pref.name);
+ }
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_bug401564.js b/netwerk/test/unit/test_bug401564.js
new file mode 100644
index 0000000000..7cc52f3965
--- /dev/null
+++ b/netwerk/test/unit/test_bug401564.js
@@ -0,0 +1,42 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+const noRedirectURI = "/content";
+const acceptType = "application/json";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Moved Temporarily");
+ response.setHeader("Location", noRedirectURI, false);
+}
+
+function contentHandler(metadata, response) {
+ Assert.equal(metadata.getHeader("Accept"), acceptType);
+ httpserver.stop(do_test_finished);
+}
+
+function dummyHandler(request, buffer) {}
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/redirect", redirectHandler);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ Services.prefs.setBoolPref("network.http.prompt-temp-redirect", false);
+
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/redirect",
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.setRequestHeader("Accept", acceptType, false);
+
+ chan.asyncOpen(new ChannelListener(dummyHandler, null));
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug411952.js b/netwerk/test/unit/test_bug411952.js
new file mode 100644
index 0000000000..4584957188
--- /dev/null
+++ b/netwerk/test/unit/test_bug411952.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function run_test() {
+ try {
+ var cm = Services.cookies;
+ Assert.notEqual(cm, null, "Retrieving the cookie manager failed");
+
+ const time = new Date("Jan 1, 2030").getTime() / 1000;
+ cm.add(
+ "example.com",
+ "/",
+ "C",
+ "V",
+ false,
+ true,
+ false,
+ time,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ const now = Math.floor(new Date().getTime() / 1000);
+
+ var found = false;
+ for (let cookie of cm.cookies) {
+ if (
+ cookie.host == "example.com" &&
+ cookie.path == "/" &&
+ cookie.name == "C"
+ ) {
+ Assert.ok(
+ "creationTime" in cookie,
+ "creationTime attribute is not accessible on the cookie"
+ );
+ var creationTime = Math.floor(cookie.creationTime / 1000000);
+ // allow the times to slip by one second at most,
+ // which should be fine under normal circumstances.
+ Assert.ok(
+ Math.abs(creationTime - now) <= 1,
+ "Cookie's creationTime is set incorrectly"
+ );
+ found = true;
+ break;
+ }
+ }
+
+ Assert.ok(found, "Didn't find the cookie we were after");
+ } catch (e) {
+ do_throw("Unexpected exception: " + e.toString());
+ }
+
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_bug412457.js b/netwerk/test/unit/test_bug412457.js
new file mode 100644
index 0000000000..30fd2ed3fc
--- /dev/null
+++ b/netwerk/test/unit/test_bug412457.js
@@ -0,0 +1,79 @@
+"use strict";
+
+function run_test() {
+ // check if hostname is unescaped before applying IDNA
+ var newURI = Services.io.newURI("http://\u5341%2ecom/");
+ Assert.equal(newURI.asciiHost, "xn--kkr.com");
+
+ // escaped UTF8
+ newURI = newURI.mutate().setSpec("http://%e5%8d%81.com").finalize();
+ Assert.equal(newURI.asciiHost, "xn--kkr.com");
+
+ // There should be only allowed characters in hostname after
+ // unescaping and attempting to apply IDNA. "\x80" is illegal in
+ // UTF-8, so IDNA fails, and 0x80 is illegal in DNS too.
+ Assert.throws(
+ () => {
+ newURI = newURI.mutate().setSpec("http://%80.com").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "illegal UTF character"
+ );
+
+ // test parsing URL with all possible host terminators
+ newURI = newURI.mutate().setSpec("http://example.com?foo").finalize();
+ Assert.equal(newURI.asciiHost, "example.com");
+
+ newURI = newURI.mutate().setSpec("http://example.com#foo").finalize();
+ Assert.equal(newURI.asciiHost, "example.com");
+
+ newURI = newURI.mutate().setSpec("http://example.com:80").finalize();
+ Assert.equal(newURI.asciiHost, "example.com");
+
+ newURI = newURI.mutate().setSpec("http://example.com/foo").finalize();
+ Assert.equal(newURI.asciiHost, "example.com");
+
+ // Characters that are invalid in the host
+ Assert.throws(
+ () => {
+ newURI = newURI.mutate().setSpec("http://example.com%3ffoo").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad escaped character"
+ );
+ Assert.throws(
+ () => {
+ newURI = newURI.mutate().setSpec("http://example.com%23foo").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad escaped character"
+ );
+ Assert.throws(
+ () => {
+ newURI = newURI.mutate().setSpec("http://example.com%3bfoo").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad escaped character"
+ );
+ Assert.throws(
+ () => {
+ newURI = newURI.mutate().setSpec("http://example.com%3a80").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad escaped character"
+ );
+ Assert.throws(
+ () => {
+ newURI = newURI.mutate().setSpec("http://example.com%2ffoo").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad escaped character"
+ );
+ Assert.throws(
+ () => {
+ newURI = newURI.mutate().setSpec("http://example.com%00").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad escaped character"
+ );
+}
diff --git a/netwerk/test/unit/test_bug412945.js b/netwerk/test/unit/test_bug412945.js
new file mode 100644
index 0000000000..78c48bbf22
--- /dev/null
+++ b/netwerk/test/unit/test_bug412945.js
@@ -0,0 +1,44 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserv;
+
+function TestListener() {}
+
+TestListener.prototype.onStartRequest = function (request) {};
+
+TestListener.prototype.onStopRequest = function (request, status) {
+ httpserv.stop(do_test_finished);
+};
+
+function run_test() {
+ httpserv = new HttpServer();
+
+ httpserv.registerPathHandler("/bug412945", bug412945);
+
+ httpserv.start(-1);
+
+ // make request
+ var channel = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserv.identity.primaryPort + "/bug412945",
+ loadUsingSystemPrincipal: true,
+ });
+
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ channel.requestMethod = "POST";
+ channel.asyncOpen(new TestListener(), null);
+
+ do_test_pending();
+}
+
+function bug412945(metadata, response) {
+ if (
+ !metadata.hasHeader("Content-Length") ||
+ metadata.getHeader("Content-Length") != "0"
+ ) {
+ do_throw("Content-Length header not found!");
+ }
+}
diff --git a/netwerk/test/unit/test_bug414122.js b/netwerk/test/unit/test_bug414122.js
new file mode 100644
index 0000000000..66c2bdf4bd
--- /dev/null
+++ b/netwerk/test/unit/test_bug414122.js
@@ -0,0 +1,59 @@
+"use strict";
+
+const PR_RDONLY = 0x1;
+
+var idn = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
+
+function run_test() {
+ var fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fis.init(
+ do_get_file("effective_tld_names.dat"),
+ PR_RDONLY,
+ 0o444,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF
+ );
+
+ var lis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+ Ci.nsIConverterInputStream
+ );
+ lis.init(fis, "UTF-8", 1024, 0);
+ lis.QueryInterface(Ci.nsIUnicharLineInputStream);
+
+ var out = { value: "" };
+ do {
+ var more = lis.readLine(out);
+ var line = out.value;
+
+ line = line.replace(/^\s+/, "");
+ var firstTwo = line.substring(0, 2); // a misnomer, but whatever
+ if (firstTwo == "" || firstTwo == "//") {
+ continue;
+ }
+
+ var space = line.search(/[ \t]/);
+ line = line.substring(0, space == -1 ? line.length : space);
+
+ if ("*." == firstTwo) {
+ let rest = line.substring(2);
+ checkPublicSuffix(
+ "foo.SUPER-SPECIAL-AWESOME-PREFIX." + rest,
+ "SUPER-SPECIAL-AWESOME-PREFIX." + rest
+ );
+ } else if ("!" == line.charAt(0)) {
+ checkPublicSuffix(
+ line.substring(1),
+ line.substring(line.indexOf(".") + 1)
+ );
+ } else {
+ checkPublicSuffix("SUPER-SPECIAL-AWESOME-PREFIX." + line, line);
+ }
+ } while (more);
+}
+
+function checkPublicSuffix(host, expectedSuffix) {
+ expectedSuffix = idn.convertUTF8toACE(expectedSuffix).toLowerCase();
+ var actualSuffix = Services.eTLD.getPublicSuffixFromHost(host);
+ Assert.equal(actualSuffix, expectedSuffix);
+}
diff --git a/netwerk/test/unit/test_bug427957.js b/netwerk/test/unit/test_bug427957.js
new file mode 100644
index 0000000000..33a2444c9d
--- /dev/null
+++ b/netwerk/test/unit/test_bug427957.js
@@ -0,0 +1,100 @@
+/**
+ * Test for Bidi restrictions on IDNs from RFC 3454
+ */
+
+"use strict";
+
+var idnService;
+
+function expected_pass(inputIDN) {
+ var isASCII = {};
+ var displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ Assert.equal(displayIDN, inputIDN);
+}
+
+function expected_fail(inputIDN) {
+ var isASCII = {};
+ var displayIDN = "";
+
+ try {
+ displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ } catch (e) {}
+
+ Assert.notEqual(displayIDN, inputIDN);
+}
+
+function run_test() {
+ idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+ /*
+ * In any profile that specifies bidirectional character handling, all
+ * three of the following requirements MUST be met:
+ *
+ * 1) The characters in section 5.8 MUST be prohibited.
+ */
+
+ // 0340; COMBINING GRAVE TONE MARK
+ expected_fail("foo\u0340bar.com");
+ // 0341; COMBINING ACUTE TONE MARK
+ expected_fail("foo\u0341bar.com");
+ // 200E; LEFT-TO-RIGHT MARK
+ expected_fail("foo\u200ebar.com");
+ // 200F; RIGHT-TO-LEFT MARK
+ // Note: this is an RTL IDN so that it doesn't fail test 2) below
+ expected_fail(
+ "\u200f\u0645\u062B\u0627\u0644.\u0622\u0632\u0645\u0627\u06CC\u0634\u06CC"
+ );
+ // 202A; LEFT-TO-RIGHT EMBEDDING
+ expected_fail("foo\u202abar.com");
+ // 202B; RIGHT-TO-LEFT EMBEDDING
+ expected_fail("foo\u202bbar.com");
+ // 202C; POP DIRECTIONAL FORMATTING
+ expected_fail("foo\u202cbar.com");
+ // 202D; LEFT-TO-RIGHT OVERRIDE
+ expected_fail("foo\u202dbar.com");
+ // 202E; RIGHT-TO-LEFT OVERRIDE
+ expected_fail("foo\u202ebar.com");
+ // 206A; INHIBIT SYMMETRIC SWAPPING
+ expected_fail("foo\u206abar.com");
+ // 206B; ACTIVATE SYMMETRIC SWAPPING
+ expected_fail("foo\u206bbar.com");
+ // 206C; INHIBIT ARABIC FORM SHAPING
+ expected_fail("foo\u206cbar.com");
+ // 206D; ACTIVATE ARABIC FORM SHAPING
+ expected_fail("foo\u206dbar.com");
+ // 206E; NATIONAL DIGIT SHAPES
+ expected_fail("foo\u206ebar.com");
+ // 206F; NOMINAL DIGIT SHAPES
+ expected_fail("foo\u206fbar.com");
+
+ /*
+ * 2) If a string contains any RandALCat character, the string MUST NOT
+ * contain any LCat character.
+ */
+
+ // www.מיץpetel.com is invalid
+ expected_fail("www.\u05DE\u05D9\u05E5petel.com");
+ // But www.מיץפטל.com is fine because the ltr and rtl characters are in
+ // different labels
+ expected_pass("www.\u05DE\u05D9\u05E5\u05E4\u05D8\u05DC.com");
+
+ /*
+ * 3) If a string contains any RandALCat character, a RandALCat
+ * character MUST be the first character of the string, and a
+ * RandALCat character MUST be the last character of the string.
+ */
+
+ // www.1מיץ.com is invalid
+ expected_fail("www.1\u05DE\u05D9\u05E5.com");
+ // www.!מיץ.com is invalid
+ expected_fail("www.!\u05DE\u05D9\u05E5.com");
+ // www.מיץ!.com is invalid
+ expected_fail("www.\u05DE\u05D9\u05E5!.com");
+
+ // XXX TODO: add a test for an RTL label ending with a digit. This was
+ // invalid in IDNA2003 but became valid in IDNA2008
+
+ // But www.מיץ1פטל.com is fine
+ expected_pass("www.\u05DE\u05D9\u05E51\u05E4\u05D8\u05DC.com");
+}
diff --git a/netwerk/test/unit/test_bug429347.js b/netwerk/test/unit/test_bug429347.js
new file mode 100644
index 0000000000..ad6c508eb6
--- /dev/null
+++ b/netwerk/test/unit/test_bug429347.js
@@ -0,0 +1,39 @@
+"use strict";
+
+function run_test() {
+ var ios = Services.io;
+
+ var uri1 = ios.newURI("http://example.com#bar");
+ var uri2 = ios.newURI("http://example.com/#bar");
+ Assert.ok(uri1.equals(uri2));
+
+ uri1 = uri1.mutate().setSpec("http://example.com?bar").finalize();
+ uri2 = uri2.mutate().setSpec("http://example.com/?bar").finalize();
+ Assert.ok(uri1.equals(uri2));
+
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=665706
+ // ";" is not parsed as special anymore and thus ends up
+ // in the authority component (see RFC 3986)
+ uri1 = uri1.mutate().setSpec("http://example.com;bar").finalize();
+ uri2 = uri2.mutate().setSpec("http://example.com/;bar").finalize();
+ Assert.ok(!uri1.equals(uri2));
+
+ uri1 = uri1.mutate().setSpec("http://example.com#").finalize();
+ uri2 = uri2.mutate().setSpec("http://example.com/#").finalize();
+ Assert.ok(uri1.equals(uri2));
+
+ uri1 = uri1.mutate().setSpec("http://example.com?").finalize();
+ uri2 = uri2.mutate().setSpec("http://example.com/?").finalize();
+ Assert.ok(uri1.equals(uri2));
+
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=665706
+ // ";" is not parsed as special anymore and thus ends up
+ // in the authority component (see RFC 3986)
+ uri1 = uri1.mutate().setSpec("http://example.com;").finalize();
+ uri2 = uri2.mutate().setSpec("http://example.com/;").finalize();
+ Assert.ok(!uri1.equals(uri2));
+
+ uri1 = uri1.mutate().setSpec("http://example.com").finalize();
+ uri2 = uri2.mutate().setSpec("http://example.com/").finalize();
+ Assert.ok(uri1.equals(uri2));
+}
diff --git a/netwerk/test/unit/test_bug455311.js b/netwerk/test/unit/test_bug455311.js
new file mode 100644
index 0000000000..36e000a174
--- /dev/null
+++ b/netwerk/test/unit/test_bug455311.js
@@ -0,0 +1,128 @@
+"use strict";
+
+function getUrlLinkFile() {
+ if (mozinfo.os == "win") {
+ return do_get_file("test_link.url");
+ }
+ if (mozinfo.os == "linux") {
+ return do_get_file("test_link.desktop");
+ }
+ do_throw("Unexpected platform");
+ return null;
+}
+
+const ios = Services.io;
+
+function NotificationCallbacks(origURI, newURI) {
+ this._origURI = origURI;
+ this._newURI = newURI;
+}
+NotificationCallbacks.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIChannelEventSink",
+ ]),
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+ asyncOnChannelRedirect(oldChan, newChan, flags, callback) {
+ Assert.equal(oldChan.URI.spec, this._origURI.spec);
+ Assert.equal(oldChan.URI, this._origURI);
+ Assert.equal(oldChan.originalURI.spec, this._origURI.spec);
+ Assert.equal(oldChan.originalURI, this._origURI);
+ Assert.equal(newChan.originalURI.spec, this._newURI.spec);
+ Assert.equal(newChan.originalURI, newChan.URI);
+ Assert.equal(newChan.URI.spec, this._newURI.spec);
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+};
+
+function RequestObserver(origURI, newURI, nextTest) {
+ this._origURI = origURI;
+ this._newURI = newURI;
+ this._nextTest = nextTest;
+}
+RequestObserver.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIRequestObserver",
+ "nsIStreamListener",
+ ]),
+ onStartRequest(req) {
+ var chan = req.QueryInterface(Ci.nsIChannel);
+ Assert.equal(chan.URI.spec, this._origURI.spec);
+ Assert.equal(chan.URI, this._origURI);
+ Assert.equal(chan.originalURI.spec, this._origURI.spec);
+ Assert.equal(chan.originalURI, this._origURI);
+ },
+ onDataAvailable(req, stream, offset, count) {
+ do_throw("Unexpected call to onDataAvailable");
+ },
+ onStopRequest(req, status) {
+ var chan = req.QueryInterface(Ci.nsIChannel);
+ try {
+ Assert.equal(chan.URI.spec, this._origURI.spec);
+ Assert.equal(chan.URI, this._origURI);
+ Assert.equal(chan.originalURI.spec, this._origURI.spec);
+ Assert.equal(chan.originalURI, this._origURI);
+ Assert.equal(status, Cr.NS_ERROR_ABORT);
+ Assert.ok(!chan.isPending());
+ } catch (e) {}
+ this._nextTest();
+ },
+};
+
+function test_cancel(linkURI, newURI) {
+ var chan = NetUtil.newChannel({
+ uri: linkURI,
+ loadUsingSystemPrincipal: true,
+ });
+ Assert.equal(chan.URI, linkURI);
+ Assert.equal(chan.originalURI, linkURI);
+ chan.asyncOpen(new RequestObserver(linkURI, newURI, do_test_finished));
+ Assert.ok(chan.isPending());
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ Assert.ok(chan.isPending());
+}
+
+function test_channel(linkURI, newURI) {
+ const chan = NetUtil.newChannel({
+ uri: linkURI,
+ loadUsingSystemPrincipal: true,
+ });
+ Assert.equal(chan.URI, linkURI);
+ Assert.equal(chan.originalURI, linkURI);
+ chan.notificationCallbacks = new NotificationCallbacks(linkURI, newURI);
+ chan.asyncOpen(
+ new RequestObserver(linkURI, newURI, () => test_cancel(linkURI, newURI))
+ );
+ Assert.ok(chan.isPending());
+}
+
+function run_test() {
+ if (mozinfo.os != "win" && mozinfo.os != "linux") {
+ return;
+ }
+
+ let link = getUrlLinkFile();
+ let linkURI;
+ if (link.isSymlink()) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(link.target);
+ linkURI = ios.newFileURI(file);
+ } else {
+ linkURI = ios.newFileURI(link);
+ }
+
+ do_test_pending();
+ test_channel(linkURI, ios.newURI("http://www.mozilla.org/"));
+
+ if (mozinfo.os != "win") {
+ return;
+ }
+
+ link = do_get_file("test_link.lnk");
+ test_channel(
+ ios.newFileURI(link),
+ ios.newURI("file:///Z:/moz-nonexistent/index.html")
+ );
+}
diff --git a/netwerk/test/unit/test_bug464591.js b/netwerk/test/unit/test_bug464591.js
new file mode 100644
index 0000000000..bc2f481a0e
--- /dev/null
+++ b/netwerk/test/unit/test_bug464591.js
@@ -0,0 +1,94 @@
+// 1.percent-encoded IDN that contains blacklisted character should be converted
+// to punycode, not UTF-8 string
+// 2.only hostname-valid percent encoded ASCII characters should be decoded
+// 3.IDN convertion must not bypassed by %00
+
+"use strict";
+
+let reference = [
+ [
+ "www.example.com%e2%88%95www.mozill%d0%b0.com%e2%81%84www.mozilla.org",
+ "www.example.xn--comwww-re3c.xn--mozill-8nf.xn--comwww-rq0c.mozilla.org",
+ ],
+];
+
+let badURIs = [
+ ["www.mozill%61%2f.org"], // a slash is not valid in the hostname
+ ["www.e%00xample.com%e2%88%95www.mozill%d0%b0.com%e2%81%84www.mozill%61.org"],
+];
+
+let prefData = [
+ {
+ name: "network.enableIDN",
+ newVal: true,
+ },
+ {
+ name: "network.IDN_show_punycode",
+ newVal: false,
+ },
+];
+
+let prefIdnBlackList = {
+ name: "network.IDN.extra_blocked_chars",
+ minimumList: "\u2215\u0430\u2044",
+};
+
+function stringToURL(str) {
+ return Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIStandardURLMutator)
+ .init(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 80, str, "UTF-8", null)
+ .finalize()
+ .QueryInterface(Ci.nsIURL);
+}
+
+function run_test() {
+ // Make sure our prefs are set such that this test actually means something
+ let prefs = Services.prefs;
+ for (let pref of prefData) {
+ prefs.setBoolPref(pref.name, pref.newVal);
+ }
+
+ prefIdnBlackList.set = false;
+ try {
+ prefIdnBlackList.oldVal = prefs.getComplexValue(
+ prefIdnBlackList.name,
+ Ci.nsIPrefLocalizedString
+ ).data;
+ prefs.getComplexValue(
+ prefIdnBlackList.name,
+ Ci.nsIPrefLocalizedString
+ ).data = prefIdnBlackList.minimumList;
+ prefIdnBlackList.set = true;
+ } catch (e) {}
+
+ registerCleanupFunction(function () {
+ for (let pref of prefData) {
+ prefs.clearUserPref(pref.name);
+ }
+ if (prefIdnBlackList.set) {
+ prefs.getComplexValue(
+ prefIdnBlackList.name,
+ Ci.nsIPrefLocalizedString
+ ).data = prefIdnBlackList.oldVal;
+ }
+ });
+
+ for (let i = 0; i < reference.length; ++i) {
+ try {
+ let result = stringToURL("http://" + reference[i][0]).host;
+ equal(result, reference[i][1]);
+ } catch (e) {
+ ok(false, "Error testing " + reference[i][0]);
+ }
+ }
+
+ for (let i = 0; i < badURIs.length; ++i) {
+ Assert.throws(
+ () => {
+ stringToURL("http://" + badURIs[i][0]).host;
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad escaped character"
+ );
+ }
+}
diff --git a/netwerk/test/unit/test_bug468426.js b/netwerk/test/unit/test_bug468426.js
new file mode 100644
index 0000000000..def7041ad8
--- /dev/null
+++ b/netwerk/test/unit/test_bug468426.js
@@ -0,0 +1,131 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ // Initial request. Cached variant will have no cookie
+ { url: "/bug468426", server: "0", expected: "0", cookie: null },
+
+ // Cache now contains a variant with no value for cookie. If we don't
+ // set cookie we expect to receive the cached variant
+ { url: "/bug468426", server: "1", expected: "0", cookie: null },
+
+ // Cache still contains a variant with no value for cookie. If we
+ // set a value for cookie we expect a fresh value
+ { url: "/bug468426", server: "2", expected: "2", cookie: "c=2" },
+
+ // Cache now contains a variant with cookie "c=2". If the request
+ // also set cookie "c=2", we expect to receive the cached variant.
+ { url: "/bug468426", server: "3", expected: "2", cookie: "c=2" },
+
+ // Cache still contains a variant with cookie "c=2". When setting
+ // cookie "c=4" in the request we expect a fresh value
+ { url: "/bug468426", server: "4", expected: "4", cookie: "c=4" },
+
+ // Cache now contains a variant with cookie "c=4". When setting
+ // cookie "c=4" in the request we expect the cached variant
+ { url: "/bug468426", server: "5", expected: "4", cookie: "c=4" },
+
+ // Cache still contains a variant with cookie "c=4". When setting
+ // no cookie in the request we expect a fresh value
+ { url: "/bug468426", server: "6", expected: "6", cookie: null },
+];
+
+function setupChannel(suffix, value, cookie) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ if (cookie != null) {
+ httpChan.setRequestHeader("Cookie", cookie, false);
+ }
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(
+ tests[index].url,
+ tests[index].server,
+ tests[index].cookie
+ );
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ Assert.equal(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ index++;
+ // This call happens in onStopRequest from the channel. Opening a new
+ // channel to the same url here is no good idea! Post it instead...
+ do_timeout(1, triggerNextTest);
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/bug468426", handler);
+ httpserver.start(-1);
+
+ // Clear cache and trigger the first test
+ evict_cache_entries();
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = "unset";
+ try {
+ body = metadata.getHeader("x-request");
+ } catch (e) {}
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ response.setHeader("Vary", "Cookie", false);
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ var d = new Date();
+ return (
+ days[d.getUTCDay()] +
+ ", " +
+ d.getUTCDate() +
+ " " +
+ months[d.getUTCMonth()] +
+ " " +
+ (d.getUTCFullYear() + yearDelta) +
+ " " +
+ d.getUTCHours() +
+ ":" +
+ d.getUTCMinutes() +
+ ":" +
+ d.getUTCSeconds() +
+ " UTC"
+ );
+}
diff --git a/netwerk/test/unit/test_bug468594.js b/netwerk/test/unit/test_bug468594.js
new file mode 100644
index 0000000000..abab980f48
--- /dev/null
+++ b/netwerk/test/unit/test_bug468594.js
@@ -0,0 +1,178 @@
+//
+// This script emulates the test called "Freshness"
+// by Mark Nottingham, located at
+//
+// http://mnot.net/javascript/xmlhttprequest/cache.html
+//
+// The issue with Mr. Nottinghams page is that the server
+// always seems to send an Expires-header in the response,
+// breaking the finer details of the test. This script has
+// full control of response-headers, however, and can perform
+// the intended testing plus some extra stuff.
+//
+// Please see RFC 2616 section 13.2.1 6th paragraph for the
+// definition of "explicit expiration time" being used here.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ { url: "/freshness", server: "0", expected: "0" },
+ { url: "/freshness", server: "1", expected: "0" }, // cached
+
+ // RFC 2616 section 13.9 2nd paragraph says not to heuristically cache
+ // querystring, but we allow it to maintain web compat
+ { url: "/freshness?a", server: "2", expected: "2" },
+ { url: "/freshness?a", server: "3", expected: "2" },
+
+ // explicit expiration dates in the future should be cached
+ {
+ url: "/freshness?b",
+ server: "4",
+ expected: "4",
+ responseheader: "Expires: " + getDateString(1),
+ },
+ { url: "/freshness?b", server: "5", expected: "4" }, // cached due to Expires
+
+ {
+ url: "/freshness?c",
+ server: "6",
+ expected: "6",
+ responseheader: "Cache-Control: max-age=3600",
+ },
+ { url: "/freshness?c", server: "7", expected: "6" }, // cached due to max-age
+
+ // explicit expiration dates in the past should NOT be cached
+ {
+ url: "/freshness?d",
+ server: "8",
+ expected: "8",
+ responseheader: "Expires: " + getDateString(-1),
+ },
+ { url: "/freshness?d", server: "9", expected: "9" },
+
+ {
+ url: "/freshness?e",
+ server: "10",
+ expected: "10",
+ responseheader: "Cache-Control: max-age=0",
+ },
+ { url: "/freshness?e", server: "11", expected: "11" },
+
+ { url: "/freshness", server: "99", expected: "0" }, // cached
+];
+
+function logit(i, data) {
+ dump(
+ tests[i].url +
+ "\t requested [" +
+ tests[i].server +
+ "]" +
+ " got [" +
+ data +
+ "] expected [" +
+ tests[i].expected +
+ "]"
+ );
+ if (tests[i].responseheader) {
+ dump("\t[" + tests[i].responseheader + "]");
+ }
+ dump("\n");
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server);
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data);
+ Assert.equal(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ index++;
+ triggerNextTest();
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/freshness", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = metadata.getHeader("x-request");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Date", getDateString(0), false);
+
+ var header = tests[index].responseheader;
+ if (header == null) {
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ } else {
+ var splitHdr = header.split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ var d = new Date();
+ return (
+ days[d.getUTCDay()] +
+ ", " +
+ d.getUTCDate() +
+ " " +
+ months[d.getUTCMonth()] +
+ " " +
+ (d.getUTCFullYear() + yearDelta) +
+ " " +
+ d.getUTCHours() +
+ ":" +
+ d.getUTCMinutes() +
+ ":" +
+ d.getUTCSeconds() +
+ " UTC"
+ );
+}
diff --git a/netwerk/test/unit/test_bug470716.js b/netwerk/test/unit/test_bug470716.js
new file mode 100644
index 0000000000..009e7eee11
--- /dev/null
+++ b/netwerk/test/unit/test_bug470716.js
@@ -0,0 +1,171 @@
+"use strict";
+
+var CC = Components.Constructor;
+
+const StreamCopier = CC(
+ "@mozilla.org/network/async-stream-copier;1",
+ "nsIAsyncStreamCopier",
+ "init"
+);
+
+const ScriptableInputStream = CC(
+ "@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init"
+);
+
+const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
+
+var pipe1;
+var pipe2;
+var copier;
+var test_result;
+var test_content;
+var test_source_closed;
+var test_sink_closed;
+var test_nr;
+
+var copyObserver = {
+ onStartRequest(request) {},
+
+ onStopRequest(request, statusCode) {
+ // check status code
+ Assert.equal(statusCode, test_result);
+
+ // check number of copied bytes
+ Assert.equal(pipe2.inputStream.available(), test_content.length);
+
+ // check content
+ var scinp = new ScriptableInputStream(pipe2.inputStream);
+ var content = scinp.read(scinp.available());
+ Assert.equal(content, test_content);
+
+ // check closed sink
+ try {
+ pipe2.outputStream.write("closedSinkTest", 14);
+ Assert.ok(!test_sink_closed);
+ } catch (ex) {
+ Assert.ok(test_sink_closed);
+ }
+
+ // check closed source
+ try {
+ pipe1.outputStream.write("closedSourceTest", 16);
+ Assert.ok(!test_source_closed);
+ } catch (ex) {
+ Assert.ok(test_source_closed);
+ }
+
+ do_timeout(0, do_test);
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIRequestObserver"]),
+};
+
+function startCopier(closeSource, closeSink) {
+ pipe1 = new Pipe(
+ true /* nonBlockingInput */,
+ true /* nonBlockingOutput */,
+ 0 /* segmentSize */,
+ 0xffffffff /* segmentCount */,
+ null /* segmentAllocator */
+ );
+
+ pipe2 = new Pipe(
+ true /* nonBlockingInput */,
+ true /* nonBlockingOutput */,
+ 0 /* segmentSize */,
+ 0xffffffff /* segmentCount */,
+ null /* segmentAllocator */
+ );
+
+ copier = new StreamCopier(
+ pipe1.inputStream /* aSource */,
+ pipe2.outputStream /* aSink */,
+ null /* aTarget */,
+ true /* aSourceBuffered */,
+ true /* aSinkBuffered */,
+ 8192 /* aChunkSize */,
+ closeSource /* aCloseSource */,
+ closeSink /* aCloseSink */
+ );
+
+ copier.asyncCopy(copyObserver, null);
+}
+
+function do_test() {
+ test_nr++;
+ test_content = "test" + test_nr;
+
+ switch (test_nr) {
+ case 1:
+ case 2: // close sink
+ case 3: // close source
+ case 4: // close both
+ // test canceling transfer
+ // use some undefined error code to check if it is successfully passed
+ // to the request observer
+ test_result = 0x87654321;
+
+ test_source_closed = (test_nr - 1) >> 1 != 0;
+ test_sink_closed = (test_nr - 1) % 2 != 0;
+
+ startCopier(test_source_closed, test_sink_closed);
+ pipe1.outputStream.write(test_content, test_content.length);
+ pipe1.outputStream.flush();
+ do_timeout(20, function () {
+ copier.cancel(test_result);
+ pipe1.outputStream.write("a", 1);
+ });
+ break;
+ case 5:
+ case 6: // close sink
+ case 7: // close source
+ case 8: // close both
+ // test copying with EOF on source
+ test_result = 0;
+
+ test_source_closed = (test_nr - 5) >> 1 != 0;
+ test_sink_closed = (test_nr - 5) % 2 != 0;
+
+ startCopier(test_source_closed, test_sink_closed);
+ pipe1.outputStream.write(test_content, test_content.length);
+ // we will close the source
+ test_source_closed = true;
+ pipe1.outputStream.close();
+ break;
+ case 9:
+ case 10: // close sink
+ case 11: // close source
+ case 12: // close both
+ // test copying with error on sink
+ // use some undefined error code to check if it is successfully passed
+ // to the request observer
+ test_result = 0x87654321;
+
+ test_source_closed = (test_nr - 9) >> 1 != 0;
+ test_sink_closed = (test_nr - 9) % 2 != 0;
+
+ startCopier(test_source_closed, test_sink_closed);
+ pipe1.outputStream.write(test_content, test_content.length);
+ pipe1.outputStream.flush();
+ // we will close the sink
+ test_sink_closed = true;
+ do_timeout(20, function () {
+ pipe2.outputStream
+ .QueryInterface(Ci.nsIAsyncOutputStream)
+ .closeWithStatus(test_result);
+ pipe1.outputStream.write("a", 1);
+ });
+ break;
+ case 13:
+ do_test_finished();
+ break;
+ }
+}
+
+function run_test() {
+ test_nr = 0;
+ do_timeout(0, do_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug477578.js b/netwerk/test/unit/test_bug477578.js
new file mode 100644
index 0000000000..c21b1281da
--- /dev/null
+++ b/netwerk/test/unit/test_bug477578.js
@@ -0,0 +1,51 @@
+// test that methods are not normalized
+
+"use strict";
+
+const testMethods = [
+ ["GET"],
+ ["get"],
+ ["Get"],
+ ["gET"],
+ ["gEt"],
+ ["post"],
+ ["POST"],
+ ["head"],
+ ["HEAD"],
+ ["put"],
+ ["PUT"],
+ ["delete"],
+ ["DELETE"],
+ ["connect"],
+ ["CONNECT"],
+ ["options"],
+ ["trace"],
+ ["track"],
+ ["copy"],
+ ["index"],
+ ["lock"],
+ ["m-post"],
+ ["mkcol"],
+ ["move"],
+ ["propfind"],
+ ["proppatch"],
+ ["unlock"],
+ ["link"],
+ ["LINK"],
+ ["foo"],
+ ["foO"],
+ ["fOo"],
+ ["Foo"],
+];
+
+function run_test() {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost/",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ for (var i = 0; i < testMethods.length; i++) {
+ chan.requestMethod = testMethods[i];
+ Assert.equal(chan.requestMethod, testMethods[i]);
+ }
+}
diff --git a/netwerk/test/unit/test_bug479413.js b/netwerk/test/unit/test_bug479413.js
new file mode 100644
index 0000000000..c28f3da412
--- /dev/null
+++ b/netwerk/test/unit/test_bug479413.js
@@ -0,0 +1,48 @@
+/**
+ * Test for unassigned code points in IDNs (RFC 3454 section 7)
+ */
+
+"use strict";
+
+var idnService;
+
+function expected_pass(inputIDN) {
+ var isASCII = {};
+ var displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ Assert.equal(displayIDN, inputIDN);
+}
+
+function expected_fail(inputIDN) {
+ var isASCII = {};
+ var displayIDN = "";
+
+ try {
+ displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ } catch (e) {}
+
+ Assert.notEqual(displayIDN, inputIDN);
+}
+
+function run_test() {
+ idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+
+ // assigned code point
+ expected_pass("foo\u0101bar.com");
+
+ // assigned code point in punycode. Should *fail* because the URL will be
+ // converted to Unicode for display
+ expected_fail("xn--foobar-5za.com");
+
+ // unassigned code point
+ expected_fail("foo\u3040bar.com");
+
+ // unassigned code point in punycode. Should *pass* because the URL will not
+ // be converted to Unicode
+ expected_pass("xn--foobar-533e.com");
+
+ // code point assigned since Unicode 3.0
+ // XXX This test will unexpectedly pass when we update to IDNAbis
+ expected_fail("foo\u0370bar.com");
+}
diff --git a/netwerk/test/unit/test_bug479485.js b/netwerk/test/unit/test_bug479485.js
new file mode 100644
index 0000000000..30e2e0428d
--- /dev/null
+++ b/netwerk/test/unit/test_bug479485.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function run_test() {
+ var ios = Services.io;
+
+ var test_port = function (port, exception_expected) {
+ dump((port || "no port provided") + "\n");
+ var exception_threw = false;
+ try {
+ var newURI = ios.newURI("http://foo.com" + port);
+ } catch (e) {
+ exception_threw = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (exception_threw != exception_expected) {
+ do_throw(
+ "We did" +
+ (exception_expected ? "n't" : "") +
+ " throw NS_ERROR_MALFORMED_URI when creating a new URI with " +
+ port +
+ " as a port"
+ );
+ }
+ Assert.equal(exception_threw, exception_expected);
+
+ exception_threw = false;
+ newURI = ios.newURI("http://foo.com");
+ try {
+ newURI
+ .mutate()
+ .setSpec("http://foo.com" + port)
+ .finalize();
+ } catch (e) {
+ exception_threw = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (exception_threw != exception_expected) {
+ do_throw(
+ "We did" +
+ (exception_expected ? "n't" : "") +
+ " throw NS_ERROR_MALFORMED_URI when setting a spec of a URI with " +
+ port +
+ " as a port"
+ );
+ }
+ Assert.equal(exception_threw, exception_expected);
+ };
+
+ test_port(":invalid", true);
+ test_port(":-2", true);
+ test_port(":-1", true);
+ test_port(":0", false);
+ test_port(
+ ":185891548721348172817857824356013651809236172635716571865023757816234081723451516780356",
+ true
+ );
+
+ // Following 3 tests are all failing, we do not throw, although we parse the whole string and use only 5870 as a portnumber
+ test_port(":5870:80", true);
+ test_port(":5870-80", true);
+ test_port(":5870+80", true);
+
+ // Just a regression check
+ test_port(":5870", false);
+ test_port(":80", false);
+ test_port("", false);
+}
diff --git a/netwerk/test/unit/test_bug482601.js b/netwerk/test/unit/test_bug482601.js
new file mode 100644
index 0000000000..ae4e848dbf
--- /dev/null
+++ b/netwerk/test/unit/test_bug482601.js
@@ -0,0 +1,264 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserv = null;
+var test_nr = 0;
+var observers_called = "";
+var handlers_called = "";
+var buffer = "";
+
+var observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (observers_called.length) {
+ observers_called += ",";
+ }
+
+ observers_called += topic;
+ },
+};
+
+var listener = {
+ onStartRequest(request) {
+ buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ buffer = buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ Assert.equal(buffer, "0123456789");
+ Assert.equal(observers_called, results[test_nr]);
+ test_nr++;
+ do_timeout(0, do_test);
+ },
+};
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/bug482601/nocache", bug482601_nocache);
+ httpserv.registerPathHandler("/bug482601/partial", bug482601_partial);
+ httpserv.registerPathHandler("/bug482601/cached", bug482601_cached);
+ httpserv.registerPathHandler(
+ "/bug482601/only_from_cache",
+ bug482601_only_from_cache
+ );
+ httpserv.start(-1);
+
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.addObserver(observer, "http-on-examine-response");
+ obs.addObserver(observer, "http-on-examine-merged-response");
+ obs.addObserver(observer, "http-on-examine-cached-response");
+
+ do_timeout(0, do_test);
+ do_test_pending();
+}
+
+function do_test() {
+ if (test_nr < tests.length) {
+ tests[test_nr]();
+ } else {
+ Assert.equal(handlers_called, "nocache,partial,cached");
+ httpserv.stop(do_test_finished);
+ }
+}
+
+var tests = [test_nocache, test_partial, test_cached, test_only_from_cache];
+
+var results = [
+ "http-on-examine-response",
+ "http-on-examine-response,http-on-examine-merged-response",
+ "http-on-examine-response,http-on-examine-merged-response",
+ "http-on-examine-cached-response",
+];
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function storeCache(aCacheEntry, aResponseHeads, aContent) {
+ aCacheEntry.setMetaDataElement("request-method", "GET");
+ aCacheEntry.setMetaDataElement("response-head", aResponseHeads);
+ aCacheEntry.setMetaDataElement("charset", "ISO-8859-1");
+
+ var oStream = aCacheEntry.openOutputStream(0, aContent.length);
+ var written = oStream.write(aContent, aContent.length);
+ if (written != aContent.length) {
+ do_throw(
+ "oStream.write has not written all data!\n" +
+ " Expected: " +
+ written +
+ "\n" +
+ " Actual: " +
+ aContent.length +
+ "\n"
+ );
+ }
+ oStream.close();
+ aCacheEntry.close();
+}
+
+function test_nocache() {
+ observers_called = "";
+
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/bug482601/nocache"
+ );
+ chan.asyncOpen(listener);
+}
+
+function test_partial() {
+ asyncOpenCacheEntry(
+ "http://localhost:" + httpserv.identity.primaryPort + "/bug482601/partial",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ test_partial2
+ );
+}
+
+function test_partial2(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ storeCache(
+ entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123"
+ );
+
+ observers_called = "";
+
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/bug482601/partial"
+ );
+ chan.asyncOpen(listener);
+}
+
+function test_cached() {
+ asyncOpenCacheEntry(
+ "http://localhost:" + httpserv.identity.primaryPort + "/bug482601/cached",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ test_cached2
+ );
+}
+
+function test_cached2(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ storeCache(
+ entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123456789"
+ );
+
+ observers_called = "";
+
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/bug482601/cached"
+ );
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen(listener);
+}
+
+function test_only_from_cache() {
+ asyncOpenCacheEntry(
+ "http://localhost:" +
+ httpserv.identity.primaryPort +
+ "/bug482601/only_from_cache",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ test_only_from_cache2
+ );
+}
+
+function test_only_from_cache2(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ storeCache(
+ entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123456789"
+ );
+
+ observers_called = "";
+
+ var chan = makeChan(
+ "http://localhost:" +
+ httpserv.identity.primaryPort +
+ "/bug482601/only_from_cache"
+ );
+ chan.loadFlags = Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE;
+ chan.asyncOpen(listener);
+}
+
+// PATHS
+
+// /bug482601/nocache
+function bug482601_nocache(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ var body = "0123456789";
+ response.bodyOutputStream.write(body, body.length);
+ handlers_called += "nocache";
+}
+
+// /bug482601/partial
+function bug482601_partial(metadata, response) {
+ Assert.ok(metadata.hasHeader("If-Range"));
+ Assert.equal(metadata.getHeader("If-Range"), "Thu, 1 Jan 2009 00:00:00 GMT");
+ Assert.ok(metadata.hasHeader("Range"));
+ Assert.equal(metadata.getHeader("Range"), "bytes=4-");
+
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "bytes 4-9/10", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", "Thu, 1 Jan 2009 00:00:00 GMT");
+
+ var body = "456789";
+ response.bodyOutputStream.write(body, body.length);
+ handlers_called += ",partial";
+}
+
+// /bug482601/cached
+function bug482601_cached(metadata, response) {
+ Assert.ok(metadata.hasHeader("If-Modified-Since"));
+ Assert.equal(
+ metadata.getHeader("If-Modified-Since"),
+ "Thu, 1 Jan 2009 00:00:00 GMT"
+ );
+
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ handlers_called += ",cached";
+}
+
+// /bug482601/only_from_cache
+function bug482601_only_from_cache(metadata, response) {
+ do_throw("This should not be reached");
+}
diff --git a/netwerk/test/unit/test_bug482934.js b/netwerk/test/unit/test_bug482934.js
new file mode 100644
index 0000000000..ee2579b1bb
--- /dev/null
+++ b/netwerk/test/unit/test_bug482934.js
@@ -0,0 +1,190 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var response_code;
+var response_body;
+
+var request_time;
+var response_time;
+
+var cache_storage;
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+
+var base_url = "http://localhost:" + httpserver.identity.primaryPort;
+var resource = "/resource";
+var resource_url = base_url + resource;
+
+// Test flags
+var hit_server = false;
+
+function make_channel(aUrl) {
+ // Reset test global status
+ hit_server = false;
+
+ var req = NetUtil.newChannel({ uri: aUrl, loadUsingSystemPrincipal: true });
+ req.QueryInterface(Ci.nsIHttpChannel);
+ req.setRequestHeader("If-Modified-Since", request_time, false);
+ return req;
+}
+
+function make_uri(aUrl) {
+ return Services.io.newURI(aUrl);
+}
+
+function resource_handler(aMetadata, aResponse) {
+ hit_server = true;
+ Assert.ok(aMetadata.hasHeader("If-Modified-Since"));
+ Assert.equal(aMetadata.getHeader("If-Modified-Since"), request_time);
+
+ if (response_code == "200") {
+ aResponse.setStatusLine(aMetadata.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain", false);
+ aResponse.setHeader("Last-Modified", response_time, false);
+
+ aResponse.bodyOutputStream.write(response_body, response_body.length);
+ } else if (response_code == "304") {
+ aResponse.setStatusLine(aMetadata.httpVersion, 304, "Not Modified");
+ aResponse.setHeader("Returned-From-Handler", "1");
+ }
+}
+
+function check_cached_data(aCachedData, aCallback) {
+ asyncOpenCacheEntry(
+ resource_url,
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ function (aStatus, aEntry) {
+ Assert.equal(aStatus, Cr.NS_OK);
+ pumpReadStream(aEntry.openInputStream(0), function (aData) {
+ Assert.equal(aData, aCachedData);
+ aCallback();
+ });
+ }
+ );
+}
+
+function run_test() {
+ do_get_profile();
+ evict_cache_entries();
+
+ do_test_pending();
+
+ cache_storage = getCacheStorage("disk");
+ httpserver.registerPathHandler(resource, resource_handler);
+
+ wait_for_cache_index(run_next_test);
+}
+
+// 1. send custom conditional request when we don't have an entry
+// server returns 304 -> client receives 304
+add_test(() => {
+ response_code = "304";
+ response_body = "";
+ request_time = "Thu, 1 Jan 2009 00:00:00 GMT";
+ response_time = "Thu, 1 Jan 2009 00:00:00 GMT";
+
+ var ch = make_channel(resource_url);
+ ch.asyncOpen(
+ new ChannelListener(function (aRequest, aData) {
+ syncWithCacheIOThread(() => {
+ Assert.ok(hit_server);
+ Assert.equal(
+ aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus,
+ 304
+ );
+ Assert.ok(!cache_storage.exists(make_uri(resource_url), ""));
+ Assert.equal(aRequest.getResponseHeader("Returned-From-Handler"), "1");
+
+ run_next_test();
+ }, true);
+ }, null)
+ );
+});
+
+// 2. send custom conditional request when we don't have an entry
+// server returns 200 -> result is cached
+add_test(() => {
+ response_code = "200";
+ response_body = "content_body";
+ request_time = "Thu, 1 Jan 2009 00:00:00 GMT";
+ response_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+
+ var ch = make_channel(resource_url);
+ ch.asyncOpen(
+ new ChannelListener(function (aRequest, aData) {
+ syncWithCacheIOThread(() => {
+ Assert.ok(hit_server);
+ Assert.equal(
+ aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus,
+ 200
+ );
+ Assert.ok(cache_storage.exists(make_uri(resource_url), ""));
+
+ check_cached_data(response_body, run_next_test);
+ }, true);
+ }, null)
+ );
+});
+
+// 3. send custom conditional request when we have an entry
+// server returns 304 -> client receives 304 and cached entry is unchanged
+add_test(() => {
+ response_code = "304";
+ var cached_body = response_body;
+ response_body = "";
+ request_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+ response_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+
+ var ch = make_channel(resource_url);
+ ch.asyncOpen(
+ new ChannelListener(function (aRequest, aData) {
+ syncWithCacheIOThread(() => {
+ Assert.ok(hit_server);
+ Assert.equal(
+ aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus,
+ 304
+ );
+ Assert.ok(cache_storage.exists(make_uri(resource_url), ""));
+ Assert.equal(aRequest.getResponseHeader("Returned-From-Handler"), "1");
+ Assert.equal(aData, "");
+
+ // Check the cache data is not changed
+ check_cached_data(cached_body, run_next_test);
+ }, true);
+ }, null)
+ );
+});
+
+// 4. send custom conditional request when we have an entry
+// server returns 200 -> result is cached
+add_test(() => {
+ response_code = "200";
+ response_body = "updated_content_body";
+ request_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+ response_time = "Sat, 3 Jan 2009 00:00:00 GMT";
+ var ch = make_channel(resource_url);
+ ch.asyncOpen(
+ new ChannelListener(function (aRequest, aData) {
+ syncWithCacheIOThread(() => {
+ Assert.ok(hit_server);
+ Assert.equal(
+ aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus,
+ 200
+ );
+ Assert.ok(cache_storage.exists(make_uri(resource_url), ""));
+
+ // Check the cache data is updated
+ check_cached_data(response_body, () => {
+ run_next_test();
+ httpserver.stop(do_test_finished);
+ });
+ }, true);
+ }, null)
+ );
+});
diff --git a/netwerk/test/unit/test_bug490095.js b/netwerk/test/unit/test_bug490095.js
new file mode 100644
index 0000000000..4e0a37e450
--- /dev/null
+++ b/netwerk/test/unit/test_bug490095.js
@@ -0,0 +1,160 @@
+//
+// Verify that the VALIDATE_NEVER and LOAD_FROM_CACHE flags override
+// heuristic query freshness as defined in RFC 2616 section 13.9
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ { url: "/freshness?a", server: "0", expected: "0" },
+ { url: "/freshness?a", server: "1", expected: "1" },
+
+ // Setting the VALIDATE_NEVER flag should grab entry from cache
+ {
+ url: "/freshness?a",
+ server: "2",
+ expected: "1",
+ flags: Ci.nsIRequest.VALIDATE_NEVER,
+ },
+
+ // Finally, check that request is validated with no flags set
+ { url: "/freshness?a", server: "99", expected: "99" },
+
+ { url: "/freshness?b", server: "0", expected: "0" },
+ { url: "/freshness?b", server: "1", expected: "1" },
+
+ // Setting the LOAD_FROM_CACHE flag also grab the entry from cache
+ {
+ url: "/freshness?b",
+ server: "2",
+ expected: "1",
+ flags: Ci.nsIRequest.LOAD_FROM_CACHE,
+ },
+
+ // Finally, check that request is validated with no flags set
+ { url: "/freshness?b", server: "99", expected: "99" },
+];
+
+function logit(i, data) {
+ dump(
+ tests[i].url +
+ "\t requested [" +
+ tests[i].server +
+ "]" +
+ " got [" +
+ data +
+ "] expected [" +
+ tests[i].expected +
+ "]"
+ );
+ if (tests[i].responseheader) {
+ dump("\t[" + tests[i].responseheader + "]");
+ }
+ dump("\n");
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var test = tests[index];
+ var channel = setupChannel(test.url, test.server);
+ if (test.flags) {
+ channel.loadFlags = test.flags;
+ }
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data);
+ Assert.equal(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ index++;
+ // this call happens in onStopRequest from the channel, and opening a
+ // new channel to the same url here is no good idea... post it instead
+ do_timeout(1, triggerNextTest);
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/freshness", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = metadata.getHeader("x-request");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Date", getDateString(0), false);
+ response.setHeader("Cache-Control", "max-age=0", false);
+
+ var header = tests[index].responseheader;
+ if (header == null) {
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ } else {
+ var splitHdr = header.split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ var d = new Date();
+ return (
+ days[d.getUTCDay()] +
+ ", " +
+ d.getUTCDate() +
+ " " +
+ months[d.getUTCMonth()] +
+ " " +
+ (d.getUTCFullYear() + yearDelta) +
+ " " +
+ d.getUTCHours() +
+ ":" +
+ d.getUTCMinutes() +
+ ":" +
+ d.getUTCSeconds() +
+ " UTC"
+ );
+}
diff --git a/netwerk/test/unit/test_bug504014.js b/netwerk/test/unit/test_bug504014.js
new file mode 100644
index 0000000000..f7e2ed2452
--- /dev/null
+++ b/netwerk/test/unit/test_bug504014.js
@@ -0,0 +1,72 @@
+"use strict";
+
+var valid_URIs = [
+ "http://[::]/",
+ "http://[::1]/",
+ "http://[1::]/",
+ "http://[::]/",
+ "http://[::1]/",
+ "http://[1::]/",
+ "http://[1:2:3:4:5:6:7::]/",
+ "http://[::1:2:3:4:5:6:7]/",
+ "http://[1:2:a:B:c:D:e:F]/",
+ "http://[1::8]/",
+ "http://[1:2::8]/",
+ "http://[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]/",
+ "http://[::192.168.1.1]/",
+ "http://[1::0.0.0.0]/",
+ "http://[1:2::255.255.255.255]/",
+ "http://[1:2:3::255.255.255.255]/",
+ "http://[1:2:3:4::255.255.255.255]/",
+ "http://[1:2:3:4:5::255.255.255.255]/",
+ "http://[1:2:3:4:5:6:255.255.255.255]/",
+];
+
+var invalid_URIs = [
+ "http://[1]/",
+ "http://[192.168.1.1]/",
+ "http://[:::]/",
+ "http://[:::1]/",
+ "http://[1:::]/",
+ "http://[::1::]/",
+ "http://[1:2:3:4:5:6:7:]/",
+ "http://[:2:3:4:5:6:7:8]/",
+ "http://[1:2:3:4:5:6:7:8:]/",
+ "http://[:1:2:3:4:5:6:7:8]/",
+ "http://[1:2:3:4:5:6:7:8::]/",
+ "http://[::1:2:3:4:5:6:7:8]/",
+ "http://[1:2:3:4:5:6:7]/",
+ "http://[1:2:3:4:5:6:7:8:9]/",
+ "http://[00001:2:3:4:5:6:7:8]/",
+ "http://[0001:2:3:4:5:6:7:89abc]/",
+ "http://[A:b:C:d:E:f:G:h]/",
+ "http://[::192.168.1]/",
+ "http://[::192.168.1.]/",
+ "http://[::.168.1.1]/",
+ "http://[::192..1.1]/",
+ "http://[::0192.168.1.1]/",
+ "http://[::256.255.255.255]/",
+ "http://[::1x.255.255.255]/",
+ "http://[::192.4294967464.1.1]/",
+ "http://[1:2:3:4:5:6::255.255.255.255]/",
+ "http://[1:2:3:4:5:6:7:255.255.255.255]/",
+];
+
+function run_test() {
+ for (let i = 0; i < valid_URIs.length; i++) {
+ try {
+ Services.io.newURI(valid_URIs[i]);
+ } catch (e) {
+ do_throw("cannot create URI:" + valid_URIs[i]);
+ }
+ }
+
+ for (let i = 0; i < invalid_URIs.length; i++) {
+ try {
+ Services.io.newURI(invalid_URIs[i]);
+ do_throw("should throw: " + invalid_URIs[i]);
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_MALFORMED_URI);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_bug510359.js b/netwerk/test/unit/test_bug510359.js
new file mode 100644
index 0000000000..a273cc4710
--- /dev/null
+++ b/netwerk/test/unit/test_bug510359.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ { url: "/bug510359", server: "0", expected: "0" },
+ { url: "/bug510359", server: "1", expected: "1" },
+];
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ httpChan.setRequestHeader("Cookie", "c=" + value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server);
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ Assert.equal(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ index++;
+ triggerNextTest();
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/bug510359", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ try {
+ metadata.getHeader("If-Modified-Since");
+ response.setStatusLine(metadata.httpVersion, 500, "Failed");
+ var msg = "Client should not set If-Modified-Since header";
+ response.bodyOutputStream.write(msg, msg.length);
+ } catch (ex) {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ response.setHeader("Vary", "Cookie", false);
+ var body = metadata.getHeader("x-request");
+ response.bodyOutputStream.write(body, body.length);
+ }
+}
+
+function getDateString(yearDelta) {
+ var months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ var d = new Date();
+ return (
+ days[d.getUTCDay()] +
+ ", " +
+ d.getUTCDate() +
+ " " +
+ months[d.getUTCMonth()] +
+ " " +
+ (d.getUTCFullYear() + yearDelta) +
+ " " +
+ d.getUTCHours() +
+ ":" +
+ d.getUTCMinutes() +
+ ":" +
+ d.getUTCSeconds() +
+ " UTC"
+ );
+}
diff --git a/netwerk/test/unit/test_bug526789.js b/netwerk/test/unit/test_bug526789.js
new file mode 100644
index 0000000000..ec80249a3c
--- /dev/null
+++ b/netwerk/test/unit/test_bug526789.js
@@ -0,0 +1,289 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ var cm = Services.cookies;
+ var expiry = (Date.now() + 1000) * 1000;
+
+ cm.removeAll();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // test that variants of 'baz.com' get normalized appropriately, but that
+ // malformed hosts are rejected
+ cm.add(
+ "baz.com",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Assert.equal(cm.countCookiesFromHost("baz.com"), 1);
+ Assert.equal(cm.countCookiesFromHost("BAZ.com"), 1);
+ Assert.equal(cm.countCookiesFromHost(".baz.com"), 1);
+ Assert.equal(cm.countCookiesFromHost("baz.com."), 0);
+ Assert.equal(cm.countCookiesFromHost(".baz.com."), 0);
+ do_check_throws(function () {
+ cm.countCookiesFromHost("baz.com..");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.countCookiesFromHost("baz..com");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.countCookiesFromHost("..baz.com");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ cm.remove("BAZ.com.", "foo", "/", {});
+ Assert.equal(cm.countCookiesFromHost("baz.com"), 1);
+ cm.remove("baz.com", "foo", "/", {});
+ Assert.equal(cm.countCookiesFromHost("baz.com"), 0);
+
+ // Test that 'baz.com' and 'baz.com.' are treated differently
+ cm.add(
+ "baz.com.",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Assert.equal(cm.countCookiesFromHost("baz.com"), 0);
+ Assert.equal(cm.countCookiesFromHost("BAZ.com"), 0);
+ Assert.equal(cm.countCookiesFromHost(".baz.com"), 0);
+ Assert.equal(cm.countCookiesFromHost("baz.com."), 1);
+ Assert.equal(cm.countCookiesFromHost(".baz.com."), 1);
+ cm.remove("baz.com", "foo", "/", {});
+ Assert.equal(cm.countCookiesFromHost("baz.com."), 1);
+ cm.remove("baz.com.", "foo", "/", {});
+ Assert.equal(cm.countCookiesFromHost("baz.com."), 0);
+
+ // test that domain cookies are illegal for IP addresses, aliases such as
+ // 'localhost', and eTLD's such as 'co.uk'
+ cm.add(
+ "192.168.0.1",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Assert.equal(cm.countCookiesFromHost("192.168.0.1"), 1);
+ Assert.equal(cm.countCookiesFromHost("192.168.0.1."), 0);
+ do_check_throws(function () {
+ cm.countCookiesFromHost(".192.168.0.1");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.countCookiesFromHost(".192.168.0.1.");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.add(
+ "localhost",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Assert.equal(cm.countCookiesFromHost("localhost"), 1);
+ Assert.equal(cm.countCookiesFromHost("localhost."), 0);
+ do_check_throws(function () {
+ cm.countCookiesFromHost(".localhost");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.countCookiesFromHost(".localhost.");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.add(
+ "co.uk",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Assert.equal(cm.countCookiesFromHost("co.uk"), 1);
+ Assert.equal(cm.countCookiesFromHost("co.uk."), 0);
+ do_check_throws(function () {
+ cm.countCookiesFromHost(".co.uk");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.countCookiesFromHost(".co.uk.");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.removeAll();
+
+ CookieXPCShellUtils.createServer({
+ hosts: ["baz.com", "192.168.0.1", "localhost", "co.uk", "foo.com"],
+ });
+
+ var uri = NetUtil.newURI("http://baz.com/");
+ Services.scriptSecurityManager.createContentPrincipal(uri, {});
+
+ Assert.equal(uri.asciiHost, "baz.com");
+
+ await CookieXPCShellUtils.setCookieToDocument(uri.spec, "foo=bar");
+ const docCookies = await CookieXPCShellUtils.getCookieStringFromDocument(
+ uri.spec
+ );
+ Assert.equal(docCookies, "foo=bar");
+
+ Assert.equal(cm.countCookiesFromHost(""), 0);
+ do_check_throws(function () {
+ cm.countCookiesFromHost(".");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.countCookiesFromHost("..");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ var cookies = cm.getCookiesFromHost("", {});
+ Assert.ok(!cookies.length);
+ do_check_throws(function () {
+ cm.getCookiesFromHost(".", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.getCookiesFromHost("..", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cookies = cm.getCookiesFromHost("baz.com", {});
+ Assert.equal(cookies.length, 1);
+ Assert.equal(cookies[0].name, "foo");
+ cookies = cm.getCookiesFromHost("", {});
+ Assert.ok(!cookies.length);
+ do_check_throws(function () {
+ cm.getCookiesFromHost(".", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function () {
+ cm.getCookiesFromHost("..", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.removeAll();
+
+ // test that an empty host to add() or remove() works,
+ // but a host of '.' doesn't
+ cm.add(
+ "",
+ "/",
+ "foo2",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Assert.equal(getCookieCount(), 1);
+ do_check_throws(function () {
+ cm.add(
+ ".",
+ "/",
+ "foo3",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ Assert.equal(getCookieCount(), 1);
+
+ cm.remove("", "foo2", "/", {});
+ Assert.equal(getCookieCount(), 0);
+ do_check_throws(function () {
+ cm.remove(".", "foo3", "/", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ // test that the 'domain' attribute accepts a leading dot for IP addresses,
+ // aliases such as 'localhost', and eTLD's such as 'co.uk'; but that the
+ // resulting cookie is for the exact host only.
+ await testDomainCookie("http://192.168.0.1/", "192.168.0.1");
+ await testDomainCookie("http://localhost/", "localhost");
+ await testDomainCookie("http://co.uk/", "co.uk");
+
+ // Test that trailing dots are treated differently for purposes of the
+ // 'domain' attribute when using setCookieStringFromDocument.
+ await testTrailingDotCookie("http://localhost/", "localhost");
+ await testTrailingDotCookie("http://foo.com/", "foo.com");
+
+ cm.removeAll();
+});
+
+function getCookieCount() {
+ var cm = Services.cookies;
+ return cm.cookies.length;
+}
+
+async function testDomainCookie(uriString, domain) {
+ var cm = Services.cookies;
+
+ cm.removeAll();
+
+ await CookieXPCShellUtils.setCookieToDocument(
+ uriString,
+ "foo=bar; domain=" + domain
+ );
+
+ var cookies = cm.getCookiesFromHost(domain, {});
+ Assert.ok(cookies.length);
+ Assert.equal(cookies[0].host, domain);
+ cm.removeAll();
+
+ await CookieXPCShellUtils.setCookieToDocument(
+ uriString,
+ "foo=bar; domain=." + domain
+ );
+
+ cookies = cm.getCookiesFromHost(domain, {});
+ Assert.ok(cookies.length);
+ Assert.equal(cookies[0].host, domain);
+ cm.removeAll();
+}
+
+async function testTrailingDotCookie(uriString, domain) {
+ var cm = Services.cookies;
+
+ cm.removeAll();
+
+ await CookieXPCShellUtils.setCookieToDocument(
+ uriString,
+ "foo=bar; domain=" + domain + "/"
+ );
+
+ Assert.equal(cm.countCookiesFromHost(domain), 0);
+ Assert.equal(cm.countCookiesFromHost(domain + "."), 0);
+ cm.removeAll();
+ Services.prefs.clearUserPref("dom.security.https_first");
+}
diff --git a/netwerk/test/unit/test_bug528292.js b/netwerk/test/unit/test_bug528292.js
new file mode 100644
index 0000000000..84449aff71
--- /dev/null
+++ b/netwerk/test/unit/test_bug528292.js
@@ -0,0 +1,87 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const sentCookieVal = "foo=bar";
+const responseBody = "response body";
+
+ChromeUtils.defineLazyGetter(this, "baseURL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+const preRedirectPath = "/528292/pre-redirect";
+
+ChromeUtils.defineLazyGetter(this, "preRedirectURL", function () {
+ return baseURL + preRedirectPath;
+});
+
+const postRedirectPath = "/528292/post-redirect";
+
+ChromeUtils.defineLazyGetter(this, "postRedirectURL", function () {
+ return baseURL + postRedirectPath;
+});
+
+var httpServer = null;
+var receivedCookieVal = null;
+
+function preRedirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", postRedirectURL, false);
+}
+
+function postRedirectHandler(metadata, response) {
+ receivedCookieVal = metadata.getHeader("Cookie");
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+add_task(async () => {
+ // Start the HTTP server.
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(preRedirectPath, preRedirectHandler);
+ httpServer.registerPathHandler(postRedirectPath, postRedirectHandler);
+ httpServer.start(-1);
+
+ if (!inChildProcess()) {
+ // Disable third-party cookies in general.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ // Set up a channel with forceAllowThirdPartyCookie set to true. We'll use
+ // the channel both to set a cookie and then to load the pre-redirect URI.
+ var chan = NetUtil.newChannel({
+ uri: preRedirectURL,
+ loadUsingSystemPrincipal: true,
+ })
+ .QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+ chan.forceAllowThirdPartyCookie = true;
+
+ // Set a cookie on one of the URIs. It doesn't matter which one, since
+ // they're both from the same host, which is enough for the cookie service
+ // to send the cookie with both requests.
+ var postRedirectURI = Services.io.newURI(postRedirectURL);
+
+ await CookieXPCShellUtils.setCookieToDocument(
+ postRedirectURI.spec,
+ sentCookieVal
+ );
+
+ // Load the pre-redirect URI.
+ await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null));
+ });
+
+ Assert.equal(receivedCookieVal, sentCookieVal);
+ httpServer.stop(do_test_finished);
+});
diff --git a/netwerk/test/unit/test_bug536324_64bit_content_length.js b/netwerk/test/unit/test_bug536324_64bit_content_length.js
new file mode 100644
index 0000000000..7ed8ceca6f
--- /dev/null
+++ b/netwerk/test/unit/test_bug536324_64bit_content_length.js
@@ -0,0 +1,66 @@
+/* Test to ensure our 64-bit content length implementation works, at least for
+ a simple HTTP case */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// This C-L is significantly larger than (U)INT32_MAX, to make sure we do
+// 64-bit properly.
+const CONTENT_LENGTH = "1152921504606846975";
+
+var httpServer = null;
+
+var listener = {
+ onStartRequest(req) {},
+
+ onDataAvailable(req, stream, off, count) {
+ Assert.equal(req.getResponseHeader("Content-Length"), CONTENT_LENGTH);
+
+ // We're done here, cancel the channel
+ req.cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ onStopRequest(req, stat) {
+ httpServer.stop(do_test_finished);
+ },
+};
+
+function hugeContentLength(metadata, response) {
+ var text = "abcdefghijklmnopqrstuvwxyz";
+ var bytes_written = 0;
+
+ response.seizePower();
+
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Length: " + CONTENT_LENGTH + "\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+
+ // Write enough data to ensure onDataAvailable gets called
+ while (bytes_written < 4096) {
+ response.write(text);
+ bytes_written += text.length;
+ }
+
+ response.finish();
+}
+
+function test_hugeContentLength() {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpServer.identity.primaryPort + "/",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(listener);
+}
+
+add_test(test_hugeContentLength);
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/", hugeContentLength);
+ httpServer.start(-1);
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_bug540566.js b/netwerk/test/unit/test_bug540566.js
new file mode 100644
index 0000000000..24260421ad
--- /dev/null
+++ b/netwerk/test/unit/test_bug540566.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+function continue_test(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ // TODO - mayhemer: remove this tests completely
+ // entry.deviceID;
+ // if the above line does not crash, the test was successful
+ do_test_finished();
+}
+
+function run_test() {
+ asyncOpenCacheEntry(
+ "http://some.key/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ continue_test
+ );
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug553970.js b/netwerk/test/unit/test_bug553970.js
new file mode 100644
index 0000000000..adb45a2ee9
--- /dev/null
+++ b/netwerk/test/unit/test_bug553970.js
@@ -0,0 +1,50 @@
+"use strict";
+
+function makeURL(spec) {
+ return Services.io.newURI(spec).QueryInterface(Ci.nsIURL);
+}
+
+// Checks that nsIURL::GetRelativeSpec does what it claims to do.
+function run_test() {
+ // Elements of tests have the form [this.spec, aURIToCompare.spec, expectedResult].
+ let tests = [
+ [
+ "http://mozilla.org/",
+ "http://www.mozilla.org/",
+ "http://www.mozilla.org/",
+ ],
+ [
+ "http://mozilla.org/",
+ "http://www.mozilla.org",
+ "http://www.mozilla.org/",
+ ],
+ ["http://foo.com/bar/", "http://foo.com:80/bar/", ""],
+ ["http://foo.com/", "http://foo.com/a.htm#b", "a.htm#b"],
+ ["http://foo.com/a/b/", "http://foo.com/c", "../../c"],
+ ["http://foo.com/a?b/c/", "http://foo.com/c", "c"],
+ ["http://foo.com/a#b/c/", "http://foo.com/c", "c"],
+ ["http://foo.com/a;p?b/c/", "http://foo.com/c", "c"],
+ ["http://foo.com/a/b?c/d/", "http://foo.com/c", "../c"],
+ ["http://foo.com/a/b#c/d/", "http://foo.com/c", "../c"],
+ ["http://foo.com/a/b;p?c/d/", "http://foo.com/c", "../c"],
+ ["http://foo.com/a/b/c?d/e/", "http://foo.com/f", "../../f"],
+ ["http://foo.com/a/b/c#d/e/", "http://foo.com/f", "../../f"],
+ ["http://foo.com/a/b/c;p?d/e/", "http://foo.com/f", "../../f"],
+ ["http://foo.com/a?b/c/", "http://foo.com/c/d", "c/d"],
+ ["http://foo.com/a#b/c/", "http://foo.com/c/d", "c/d"],
+ ["http://foo.com/a;p?b/c/", "http://foo.com/c/d", "c/d"],
+ ["http://foo.com/a/b?c/d/", "http://foo.com/c/d", "../c/d"],
+ ["http://foo.com/a/b#c/d/", "http://foo.com/c/d", "../c/d"],
+ ["http://foo.com/a/b;p?c/d/", "http://foo.com/c/d", "../c/d"],
+ ["http://foo.com/a/b/c?d/e/", "http://foo.com/f/g/", "../../f/g/"],
+ ["http://foo.com/a/b/c#d/e/", "http://foo.com/f/g/", "../../f/g/"],
+ ["http://foo.com/a/b/c;p?d/e/", "http://foo.com/f/g/", "../../f/g/"],
+ ];
+
+ for (var i = 0; i < tests.length; i++) {
+ let url1 = makeURL(tests[i][0]);
+ let url2 = makeURL(tests[i][1]);
+ let expected = tests[i][2];
+ Assert.equal(expected, url1.getRelativeSpec(url2));
+ }
+}
diff --git a/netwerk/test/unit/test_bug561042.js b/netwerk/test/unit/test_bug561042.js
new file mode 100644
index 0000000000..6bdf4d59a6
--- /dev/null
+++ b/netwerk/test/unit/test_bug561042.js
@@ -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/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const SERVER_PORT = 8080;
+const baseURL = "http://localhost:" + SERVER_PORT + "/";
+
+var cookie = "";
+for (let i = 0; i < 10000; i++) {
+ cookie += " big cookie";
+}
+
+var listener = {
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream) {},
+
+ onStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ server.stop(do_test_finished);
+ },
+};
+
+var server = new HttpServer();
+function run_test() {
+ server.start(SERVER_PORT);
+ server.registerPathHandler("/", function (metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Set-Cookie", "BigCookie=" + cookie, false);
+ response.write("Hello world");
+ });
+ var chan = NetUtil.newChannel({
+ uri: baseURL,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug561276.js b/netwerk/test/unit/test_bug561276.js
new file mode 100644
index 0000000000..9176b7c702
--- /dev/null
+++ b/netwerk/test/unit/test_bug561276.js
@@ -0,0 +1,66 @@
+//
+// Verify that we hit the net if we discover a cycle of redirects
+// coming from cache.
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var iteration = 0;
+
+function setupChannel(suffix) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ return httpChan;
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ Assert.equal("Ok", data);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/redirect1", redirectHandler1);
+ httpserver.registerPathHandler("/redirect2", redirectHandler2);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ // load first time
+ var channel = setupChannel("/redirect1");
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null));
+
+ do_test_pending();
+}
+
+function redirectHandler1(metadata, response) {
+ // first time we return a cacheable 302 pointing to next redirect
+ if (iteration < 1) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Location", "/redirect2", false);
+
+ // next time called we return 200
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+ }
+ iteration += 1;
+}
+
+function redirectHandler2(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Location", "/redirect1", false);
+}
diff --git a/netwerk/test/unit/test_bug580508.js b/netwerk/test/unit/test_bug580508.js
new file mode 100644
index 0000000000..a17f59b334
--- /dev/null
+++ b/netwerk/test/unit/test_bug580508.js
@@ -0,0 +1,31 @@
+"use strict";
+
+var ioService = Services.io;
+var resProt = ioService
+ .getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+function run_test() {
+ // Define a resource:// alias that points to another resource:// URI.
+ let greModulesURI = ioService.newURI("resource://gre/modules/");
+ resProt.setSubstitution("my-gre-modules", greModulesURI);
+
+ // When we ask for the alias, we should not get the resource://
+ // URI that we registered it for but the original file URI.
+ let greFileSpec = ioService.newURI(
+ "modules/",
+ null,
+ resProt.getSubstitution("gre")
+ ).spec;
+ let aliasURI = resProt.getSubstitution("my-gre-modules");
+ Assert.equal(aliasURI.spec, greFileSpec);
+
+ // Resolving URIs using the original resource path and the alias
+ // should yield the same result.
+ let greNetUtilURI = ioService.newURI("resource://gre/modules/NetUtil.jsm");
+ let myNetUtilURI = ioService.newURI("resource://my-gre-modules/NetUtil.jsm");
+ Assert.equal(
+ resProt.resolveURI(greNetUtilURI),
+ resProt.resolveURI(myNetUtilURI)
+ );
+}
diff --git a/netwerk/test/unit/test_bug586908.js b/netwerk/test/unit/test_bug586908.js
new file mode 100644
index 0000000000..1d3ab20347
--- /dev/null
+++ b/netwerk/test/unit/test_bug586908.js
@@ -0,0 +1,103 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+var httpserv = null;
+
+ChromeUtils.defineLazyGetter(this, "systemSettings", function () {
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsISystemProxySettings"]),
+
+ mainThreadOnly: true,
+ PACURI: "http://localhost:" + httpserv.identity.primaryPort + "/redirect",
+ getProxyForURI(aURI) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ };
+});
+
+function checkValue(request, data, ctx) {
+ Assert.ok(called);
+ Assert.equal("ok", data);
+ httpserv.stop(do_test_finished);
+}
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/redirect", redirect);
+ httpserv.registerPathHandler("/pac", pac);
+ httpserv.registerPathHandler("/target", target);
+ httpserv.start(-1);
+
+ MockRegistrar.register(
+ "@mozilla.org/system-proxy-settings;1",
+ systemSettings
+ );
+
+ // Ensure we're using system-properties
+ Services.prefs.setIntPref(
+ "network.proxy.type",
+ Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM
+ );
+
+ // clear cache
+ evict_cache_entries();
+
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/target"
+ );
+ chan.asyncOpen(new ChannelListener(checkValue, null));
+
+ do_test_pending();
+}
+
+var called = false,
+ failed = false;
+function redirect(metadata, response) {
+ // If called second time, just return the PAC but set failed-flag
+ if (called) {
+ failed = true;
+ pac(metadata, response);
+ return;
+ }
+
+ called = true;
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", "/pac", false);
+ var body = "Moved\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function pac(metadata, response) {
+ var PAC = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader(
+ "Content-Type",
+ "application/x-ns-proxy-autoconfig",
+ false
+ );
+ response.bodyOutputStream.write(PAC, PAC.length);
+}
+
+function target(metadata, response) {
+ var retval = "ok";
+ if (failed) {
+ retval = "failed";
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(retval, retval.length);
+}
diff --git a/netwerk/test/unit/test_bug596443.js b/netwerk/test/unit/test_bug596443.js
new file mode 100644
index 0000000000..55f9922e07
--- /dev/null
+++ b/netwerk/test/unit/test_bug596443.js
@@ -0,0 +1,117 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+var httpserver = new HttpServer();
+
+var expectedOnStopRequests = 3;
+
+function setupChannel(suffix, xRequest, flags) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ if (flags) {
+ chan.loadFlags |= flags;
+ }
+
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.setRequestHeader("x-request", xRequest, false);
+
+ return httpChan;
+}
+
+function Listener(response) {
+ this._response = response;
+}
+Listener.prototype = {
+ _response: null,
+ _buffer: null,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ this._buffer = "";
+ },
+ onDataAvailable(request, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+ onStopRequest(request, status) {
+ Assert.equal(this._buffer, this._response);
+ if (--expectedOnStopRequests == 0) {
+ do_timeout(10, function () {
+ httpserver.stop(do_test_finished);
+ });
+ }
+ },
+};
+
+function run_test() {
+ httpserver.registerPathHandler("/bug596443", handler);
+ httpserver.start(-1);
+
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ // make sure we have a profile so we can use the disk-cache
+ do_get_profile();
+
+ // clear cache
+ evict_cache_entries();
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var ch0 = setupChannel(
+ "/bug596443",
+ "Response0",
+ Ci.nsIRequest.LOAD_BYPASS_CACHE
+ );
+ ch0.asyncOpen(new Listener("Response0"));
+
+ var ch1 = setupChannel(
+ "/bug596443",
+ "Response1",
+ Ci.nsIRequest.LOAD_BYPASS_CACHE
+ );
+ ch1.asyncOpen(new Listener("Response1"));
+
+ var ch2 = setupChannel("/bug596443", "Should not be used");
+ ch2.asyncOpen(new Listener("Response1")); // Note param: we expect this to come from cache
+ });
+
+ do_test_pending();
+}
+
+function triggerHandlers() {
+ do_timeout(100, handlers[1]);
+ do_timeout(100, handlers[0]);
+}
+
+var handlers = [];
+function handler(metadata, response) {
+ var func = function (body) {
+ return function () {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Length", "" + body.length, false);
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ };
+ };
+
+ response.processAsync();
+ var request = metadata.getHeader("x-request");
+ handlers.push(func(request));
+
+ if (handlers.length > 1) {
+ triggerHandlers();
+ }
+}
diff --git a/netwerk/test/unit/test_bug618835.js b/netwerk/test/unit/test_bug618835.js
new file mode 100644
index 0000000000..4f4ea8b776
--- /dev/null
+++ b/netwerk/test/unit/test_bug618835.js
@@ -0,0 +1,124 @@
+//
+// If a response to a non-safe HTTP request-method contains the Location- or
+// Content-Location header, we must make sure to invalidate any cached entry
+// representing the URIs pointed to by either header. RFC 2616 section 13.10
+//
+// This test uses 3 URIs: "/post" is the target of a POST-request and always
+// redirects (301) to "/redirect". The URIs "/redirect" and "/cl" both counts
+// the number of loads from the server (handler). The response from "/post"
+// always contains the headers "Location: /redirect" and "Content-Location:
+// /cl", whose cached entries are to be invalidated. The tests verifies that
+// "/redirect" and "/cl" are loaded from server the expected number of times.
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserv;
+
+function setupChannel(path) {
+ return NetUtil.newChannel({
+ uri: path,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+// Verify that Content-Location-URI has been loaded once, load post_target
+function InitialListener() {}
+InitialListener.prototype = {
+ onStartRequest(request) {},
+ onStopRequest(request, status) {
+ Assert.equal(1, numberOfCLHandlerCalls);
+ executeSoon(function () {
+ var channel = setupChannel(
+ "http://localhost:" + httpserv.identity.primaryPort + "/post"
+ );
+ channel.requestMethod = "POST";
+ channel.asyncOpen(new RedirectingListener());
+ });
+ },
+};
+
+// Verify that Location-URI has been loaded once, reload post_target
+function RedirectingListener() {}
+RedirectingListener.prototype = {
+ onStartRequest(request) {},
+ onStopRequest(request, status) {
+ Assert.equal(1, numberOfHandlerCalls);
+ executeSoon(function () {
+ var channel = setupChannel(
+ "http://localhost:" + httpserv.identity.primaryPort + "/post"
+ );
+ channel.requestMethod = "POST";
+ channel.asyncOpen(new VerifyingListener());
+ });
+ },
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// reload Content-Location-URI
+function VerifyingListener() {}
+VerifyingListener.prototype = {
+ onStartRequest(request) {},
+ onStopRequest(request, status) {
+ Assert.equal(2, numberOfHandlerCalls);
+ var channel = setupChannel(
+ "http://localhost:" + httpserv.identity.primaryPort + "/cl"
+ );
+ channel.asyncOpen(new FinalListener());
+ },
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// stop test
+function FinalListener() {}
+FinalListener.prototype = {
+ onStartRequest(request) {},
+ onStopRequest(request, status) {
+ Assert.equal(2, numberOfCLHandlerCalls);
+ httpserv.stop(do_test_finished);
+ },
+};
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/cl", content_location);
+ httpserv.registerPathHandler("/post", post_target);
+ httpserv.registerPathHandler("/redirect", redirect_target);
+ httpserv.start(-1);
+
+ // Clear cache
+ evict_cache_entries();
+
+ // Load Content-Location URI into cache and start the chain of loads
+ var channel = setupChannel(
+ "http://localhost:" + httpserv.identity.primaryPort + "/cl"
+ );
+ channel.asyncOpen(new InitialListener());
+
+ do_test_pending();
+}
+
+var numberOfCLHandlerCalls = 0;
+function content_location(metadata, response) {
+ numberOfCLHandlerCalls++;
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Cache-Control", "max-age=360000", false);
+}
+
+function post_target(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", "/redirect", false);
+ response.setHeader("Content-Location", "/cl", false);
+ response.setHeader("Cache-Control", "max-age=360000", false);
+}
+
+var numberOfHandlerCalls = 0;
+function redirect_target(metadata, response) {
+ numberOfHandlerCalls++;
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Cache-Control", "max-age=360000", false);
+}
diff --git a/netwerk/test/unit/test_bug633743.js b/netwerk/test/unit/test_bug633743.js
new file mode 100644
index 0000000000..6e49751c78
--- /dev/null
+++ b/netwerk/test/unit/test_bug633743.js
@@ -0,0 +1,195 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const VALUE_HDR_NAME = "X-HTTP-VALUE-HEADER";
+const VARY_HDR_NAME = "X-HTTP-VARY-HEADER";
+const CACHECTRL_HDR_NAME = "X-CACHE-CONTROL-HEADER";
+
+var httpserver = null;
+
+function make_channel(flags, vary, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/bug633743",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan.QueryInterface(Ci.nsIHttpChannel);
+}
+
+function Test(flags, varyHdr, sendValue, expectValue, cacheHdr) {
+ this._flags = flags;
+ this._varyHdr = varyHdr;
+ this._sendVal = sendValue;
+ this._expectVal = expectValue;
+ this._cacheHdr = cacheHdr;
+}
+
+Test.prototype = {
+ _buffer: "",
+ _flags: null,
+ _varyHdr: null,
+ _sendVal: null,
+ _expectVal: null,
+ _cacheHdr: null,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(this._buffer, this._expectVal);
+ do_timeout(0, run_next_test);
+ },
+
+ run() {
+ var channel = make_channel();
+ channel.loadFlags = this._flags;
+ channel.setRequestHeader(VALUE_HDR_NAME, this._sendVal, false);
+ channel.setRequestHeader(VARY_HDR_NAME, this._varyHdr, false);
+ if (this._cacheHdr) {
+ channel.setRequestHeader(CACHECTRL_HDR_NAME, this._cacheHdr, false);
+ }
+
+ channel.asyncOpen(this);
+ },
+};
+
+var gTests = [
+ // Test LOAD_FROM_CACHE: Load cache-entry
+ new Test(
+ Ci.nsIRequest.LOAD_NORMAL,
+ "entity-initial", // hdr-value used to vary
+ "request1", // echoed by handler
+ "request1" // value expected to receive in channel
+ ),
+ // Verify that it was cached
+ new Test(
+ Ci.nsIRequest.LOAD_NORMAL,
+ "entity-initial", // hdr-value used to vary
+ "fresh value with LOAD_NORMAL", // echoed by handler
+ "request1" // value expected to receive in channel
+ ),
+ // Load same entity with LOAD_FROM_CACHE-flag
+ new Test(
+ Ci.nsIRequest.LOAD_FROM_CACHE,
+ "entity-initial", // hdr-value used to vary
+ "fresh value with LOAD_FROM_CACHE", // echoed by handler
+ "request1" // value expected to receive in channel
+ ),
+ // Load different entity with LOAD_FROM_CACHE-flag
+ new Test(
+ Ci.nsIRequest.LOAD_FROM_CACHE,
+ "entity-l-f-c", // hdr-value used to vary
+ "request2", // echoed by handler
+ "request2" // value expected to receive in channel
+ ),
+ // Verify that new value was cached
+ new Test(
+ Ci.nsIRequest.LOAD_NORMAL,
+ "entity-l-f-c", // hdr-value used to vary
+ "fresh value with LOAD_NORMAL", // echoed by handler
+ "request2" // value expected to receive in channel
+ ),
+
+ // Test VALIDATE_NEVER: Note previous cache-entry
+ new Test(
+ Ci.nsIRequest.VALIDATE_NEVER,
+ "entity-v-n", // hdr-value used to vary
+ "request3", // echoed by handler
+ "request3" // value expected to receive in channel
+ ),
+ // Verify that cache-entry was replaced
+ new Test(
+ Ci.nsIRequest.LOAD_NORMAL,
+ "entity-v-n", // hdr-value used to vary
+ "fresh value with LOAD_NORMAL", // echoed by handler
+ "request3" // value expected to receive in channel
+ ),
+
+ // Test combination VALIDATE_NEVER && no-store: Load new cache-entry
+ new Test(
+ Ci.nsIRequest.LOAD_NORMAL,
+ "entity-2", // hdr-value used to vary
+ "request4", // echoed by handler
+ "request4", // value expected to receive in channel
+ "no-store" // set no-store on response
+ ),
+ // Ensure we validate without IMS header in this case (verified in handler)
+ new Test(
+ Ci.nsIRequest.VALIDATE_NEVER,
+ "entity-2-v-n", // hdr-value used to vary
+ "request5", // echoed by handler
+ "request5" // value expected to receive in channel
+ ),
+
+ // Test VALIDATE-ALWAYS: Load new entity
+ new Test(
+ Ci.nsIRequest.LOAD_NORMAL,
+ "entity-3", // hdr-value used to vary
+ "request6", // echoed by handler
+ "request6", // value expected to receive in channel
+ "no-cache" // set no-cache on response
+ ),
+ // Ensure we don't send IMS header also in this case (verified in handler)
+ new Test(
+ Ci.nsIRequest.VALIDATE_ALWAYS,
+ "entity-3-v-a", // hdr-value used to vary
+ "request7", // echoed by handler
+ "request7" // value expected to receive in channel
+ ),
+];
+
+function run_next_test() {
+ if (!gTests.length) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+
+ var test = gTests.shift();
+ test.run();
+}
+
+function handler(metadata, response) {
+ // None of the tests above should send an IMS
+ Assert.ok(!metadata.hasHeader("If-Modified-Since"));
+
+ // Pick up requested value to echo
+ var hdr = "default value";
+ try {
+ hdr = metadata.getHeader(VALUE_HDR_NAME);
+ } catch (ex) {}
+
+ // Pick up requested cache-control header-value
+ var cctrlVal = "max-age=10000";
+ try {
+ cctrlVal = metadata.getHeader(CACHECTRL_HDR_NAME);
+ } catch (ex) {}
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", cctrlVal, false);
+ response.setHeader("Vary", VARY_HDR_NAME, false);
+ response.setHeader("Last-Modified", "Tue, 15 Nov 1994 12:45:26 GMT", false);
+ response.bodyOutputStream.write(hdr, hdr.length);
+}
+
+function run_test() {
+ // clear the cache
+ evict_cache_entries();
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/bug633743", handler);
+ httpserver.start(-1);
+
+ run_next_test();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug650522.js b/netwerk/test/unit/test_bug650522.js
new file mode 100644
index 0000000000..2ffcb5438a
--- /dev/null
+++ b/netwerk/test/unit/test_bug650522.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ Services.prefs.setBoolPref("network.cookie.sameSite.schemeful", false);
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ var expiry = (Date.now() + 1000) * 1000;
+
+ // Test our handling of host names with a single character at the beginning
+ // followed by a dot.
+ Services.cookies.add(
+ "e.com",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTP
+ );
+ Assert.equal(Services.cookies.countCookiesFromHost("e.com"), 1);
+
+ CookieXPCShellUtils.createServer({ hosts: ["e.com"] });
+ const cookies = await CookieXPCShellUtils.getCookieStringFromDocument(
+ "http://e.com/"
+ );
+ Assert.equal(cookies, "foo=bar");
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/test/unit/test_bug650995.js b/netwerk/test/unit/test_bug650995.js
new file mode 100644
index 0000000000..d9ccab6540
--- /dev/null
+++ b/netwerk/test/unit/test_bug650995.js
@@ -0,0 +1,187 @@
+//
+// Test that "max_entry_size" prefs for disk- and memory-cache prevents
+// caching resources with size out of bounds
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+do_get_profile();
+
+const prefService = Services.prefs;
+
+const httpserver = new HttpServer();
+
+// Repeats the given data until the total size is larger than 1K
+function repeatToLargerThan1K(data) {
+ while (data.length <= 1024) {
+ data += data;
+ }
+ return data;
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.setRequestHeader("x-request", value, false);
+
+ return httpChan;
+}
+
+var tests = [
+ new InitializeCacheDevices(true, false), // enable and create mem-device
+ new TestCacheEntrySize(
+ function () {
+ prefService.setIntPref("browser.cache.memory.max_entry_size", 1);
+ },
+ "012345",
+ "9876543210",
+ "012345"
+ ), // expect cached value
+ new TestCacheEntrySize(
+ function () {
+ prefService.setIntPref("browser.cache.memory.max_entry_size", 1);
+ },
+ "0123456789a",
+ "9876543210",
+ "9876543210"
+ ), // expect fresh value
+ new TestCacheEntrySize(
+ function () {
+ prefService.setIntPref("browser.cache.memory.max_entry_size", -1);
+ },
+ "0123456789a",
+ "9876543210",
+ "0123456789a"
+ ), // expect cached value
+
+ new InitializeCacheDevices(false, true), // enable and create disk-device
+ new TestCacheEntrySize(
+ function () {
+ prefService.setIntPref("browser.cache.disk.max_entry_size", 1);
+ },
+ "012345",
+ "9876543210",
+ "012345"
+ ), // expect cached value
+ new TestCacheEntrySize(
+ function () {
+ prefService.setIntPref("browser.cache.disk.max_entry_size", 1);
+ },
+ "0123456789a",
+ "9876543210",
+ "9876543210"
+ ), // expect fresh value
+ new TestCacheEntrySize(
+ function () {
+ prefService.setIntPref("browser.cache.disk.max_entry_size", -1);
+ },
+ "0123456789a",
+ "9876543210",
+ "0123456789a"
+ ), // expect cached value
+];
+
+function nextTest() {
+ // We really want each test to be self-contained. Make sure cache is
+ // cleared and also let all operations finish before starting a new test
+ syncWithCacheIOThread(function () {
+ Services.cache2.clear();
+ syncWithCacheIOThread(runNextTest);
+ });
+}
+
+function runNextTest() {
+ var aTest = tests.shift();
+ if (!aTest) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+ executeSoon(function () {
+ aTest.start();
+ });
+}
+
+// Just make sure devices are created
+function InitializeCacheDevices(memDevice, diskDevice) {
+ this.start = function () {
+ prefService.setBoolPref("browser.cache.memory.enable", memDevice);
+ if (memDevice) {
+ let cap = prefService.getIntPref("browser.cache.memory.capacity", 0);
+ if (cap == 0) {
+ prefService.setIntPref("browser.cache.memory.capacity", 1024);
+ }
+ }
+ prefService.setBoolPref("browser.cache.disk.enable", diskDevice);
+ if (diskDevice) {
+ let cap = prefService.getIntPref("browser.cache.disk.capacity", 0);
+ if (cap == 0) {
+ prefService.setIntPref("browser.cache.disk.capacity", 1024);
+ }
+ }
+ var channel = setupChannel("/bug650995", "Initial value");
+ channel.asyncOpen(new ChannelListener(nextTest, null));
+ };
+}
+
+function TestCacheEntrySize(
+ setSizeFunc,
+ firstRequest,
+ secondRequest,
+ secondExpectedReply
+) {
+ // Initially, this test used 10 bytes as the limit for caching entries.
+ // Since we now use 1K granularity we have to extend lengths to be larger
+ // than 1K if it is larger than 10
+ if (firstRequest.length > 10) {
+ firstRequest = repeatToLargerThan1K(firstRequest);
+ }
+ if (secondExpectedReply.length > 10) {
+ secondExpectedReply = repeatToLargerThan1K(secondExpectedReply);
+ }
+
+ this.start = function () {
+ setSizeFunc();
+ var channel = setupChannel("/bug650995", firstRequest);
+ channel.asyncOpen(new ChannelListener(this.initialLoad, this));
+ };
+ this.initialLoad = function (request, data, ctx) {
+ Assert.equal(firstRequest, data);
+ var channel = setupChannel("/bug650995", secondRequest);
+ executeSoon(function () {
+ channel.asyncOpen(new ChannelListener(ctx.testAndTriggerNext, ctx));
+ });
+ };
+ this.testAndTriggerNext = function (request, data, ctx) {
+ Assert.equal(secondExpectedReply, data);
+ executeSoon(nextTest);
+ };
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/bug650995", handler);
+ httpserver.start(-1);
+
+ prefService.setBoolPref("network.http.rcwn.enabled", false);
+
+ nextTest();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = "BOOM!";
+ try {
+ body = metadata.getHeader("x-request");
+ } catch (e) {}
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_bug652761.js b/netwerk/test/unit/test_bug652761.js
new file mode 100644
index 0000000000..030f18a1fd
--- /dev/null
+++ b/netwerk/test/unit/test_bug652761.js
@@ -0,0 +1,19 @@
+// This is just a crashtest for a url that is rejected at parse time (port 80,000)
+
+"use strict";
+
+function run_test() {
+ // Bug 1301621 makes invalid ports throw
+ Assert.throws(
+ () => {
+ NetUtil.newChannel({
+ uri: "http://localhost:80000/",
+ loadUsingSystemPrincipal: true,
+ });
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "invalid port"
+ );
+
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_bug654926.js b/netwerk/test/unit/test_bug654926.js
new file mode 100644
index 0000000000..0f29a3bcf4
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926.js
@@ -0,0 +1,91 @@
+"use strict";
+
+function gen_1MiB() {
+ var i;
+ var data = "x";
+ for (i = 0; i < 20; i++) {
+ data += data;
+ }
+ return data;
+}
+
+function write_and_check(str, data, len) {
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw(
+ "str.write has not written all data!\n" +
+ " Expected: " +
+ len +
+ "\n" +
+ " Actual: " +
+ written +
+ "\n"
+ );
+ }
+}
+
+function write_datafile(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var data = gen_1MiB();
+ var os = entry.openOutputStream(0, data.length);
+
+ // write 2MiB
+ var i;
+ for (i = 0; i < 2; i++) {
+ write_and_check(os, data, data.length);
+ }
+
+ os.close();
+ entry.close();
+
+ // now change max_entry_size so that the existing entry is too big
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1024);
+
+ // append to entry
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ append_datafile
+ );
+}
+
+function append_datafile(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize, -1);
+ var data = gen_1MiB();
+
+ // append 1MiB
+ try {
+ write_and_check(os, data, data.length);
+ do_throw();
+ } catch (ex) {}
+
+ // closing the ostream should fail in this case
+ try {
+ os.close();
+ do_throw();
+ } catch (ex) {}
+
+ entry.close();
+
+ do_test_finished();
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ write_datafile
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug654926_doom_and_read.js b/netwerk/test/unit/test_bug654926_doom_and_read.js
new file mode 100644
index 0000000000..1ed618e04a
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926_doom_and_read.js
@@ -0,0 +1,82 @@
+"use strict";
+
+function gen_1MiB() {
+ var i;
+ var data = "x";
+ for (i = 0; i < 20; i++) {
+ data += data;
+ }
+ return data;
+}
+
+function write_and_check(str, data, len) {
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw(
+ "str.write has not written all data!\n" +
+ " Expected: " +
+ len +
+ "\n" +
+ " Actual: " +
+ written +
+ "\n"
+ );
+ }
+}
+
+function write_datafile(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var data = gen_1MiB();
+ var os = entry.openOutputStream(0, data.length);
+
+ write_and_check(os, data, data.length);
+
+ os.close();
+ entry.close();
+
+ // open, doom, append, read
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ test_read_after_doom
+ );
+}
+
+function test_read_after_doom(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var data = gen_1MiB();
+ var os = entry.openOutputStream(entry.dataSize, data.length);
+
+ entry.asyncDoom(null);
+ write_and_check(os, data, data.length);
+
+ os.close();
+
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function (read) {
+ Assert.equal(read.length, 2 * 1024 * 1024);
+ is.close();
+
+ entry.close();
+ do_test_finished();
+ });
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ write_datafile
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug654926_test_seek.js b/netwerk/test/unit/test_bug654926_test_seek.js
new file mode 100644
index 0000000000..148e9f9043
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926_test_seek.js
@@ -0,0 +1,76 @@
+"use strict";
+
+function gen_1MiB() {
+ var i;
+ var data = "x";
+ for (i = 0; i < 20; i++) {
+ data += data;
+ }
+ return data;
+}
+
+function write_and_check(str, data, len) {
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw(
+ "str.write has not written all data!\n" +
+ " Expected: " +
+ len +
+ "\n" +
+ " Actual: " +
+ written +
+ "\n"
+ );
+ }
+}
+
+function write_datafile(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var data = gen_1MiB();
+ var os = entry.openOutputStream(0, data.length);
+
+ write_and_check(os, data, data.length);
+
+ os.close();
+ entry.close();
+
+ // try to open the entry for appending
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ open_for_readwrite
+ );
+}
+
+function open_for_readwrite(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize, -1);
+
+ // Opening the entry for appending data calls nsDiskCacheStreamIO::Seek()
+ // which initializes mFD. If no data is written then mBufDirty is false and
+ // mFD won't be closed in nsDiskCacheStreamIO::Flush().
+
+ os.close();
+ entry.close();
+
+ do_test_finished();
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ write_datafile
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug659569.js b/netwerk/test/unit/test_bug659569.js
new file mode 100644
index 0000000000..1bdc61b618
--- /dev/null
+++ b/netwerk/test/unit/test_bug659569.js
@@ -0,0 +1,60 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+
+function setupChannel(suffix) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ Assert.equal("Ok", data);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ // We don't want to have CookieJarSettings blocking this test.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ httpserver.registerPathHandler("/redirect1", redirectHandler1);
+ httpserver.registerPathHandler("/redirect2", redirectHandler2);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ // load first time
+ var channel = setupChannel("/redirect1");
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null));
+ do_test_pending();
+}
+
+function redirectHandler1(metadata, response) {
+ if (!metadata.hasHeader("Cookie")) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Location", "/redirect2?query", false);
+ response.setHeader("Set-Cookie", "MyCookie=1", false);
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+ }
+}
+
+function redirectHandler2(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", "/redirect1", false);
+}
diff --git a/netwerk/test/unit/test_bug660066.js b/netwerk/test/unit/test_bug660066.js
new file mode 100644
index 0000000000..2e7c060135
--- /dev/null
+++ b/netwerk/test/unit/test_bug660066.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+"use strict";
+
+const SIMPLEURI_SPEC = "data:text/plain,hello world";
+const BLOBURI_SPEC = "blob:123456";
+
+function do_info(text, stack) {
+ if (!stack) {
+ stack = Components.stack.caller;
+ }
+
+ dump(
+ "\n" +
+ "TEST-INFO | " +
+ stack.filename +
+ " | [" +
+ stack.name +
+ " : " +
+ stack.lineNumber +
+ "] " +
+ text +
+ "\n"
+ );
+}
+
+function do_check_uri_neq(uri1, uri2) {
+ do_info("Checking equality in forward direction...");
+ Assert.ok(!uri1.equals(uri2));
+ Assert.ok(!uri1.equalsExceptRef(uri2));
+
+ do_info("Checking equality in reverse direction...");
+ Assert.ok(!uri2.equals(uri1));
+ Assert.ok(!uri2.equalsExceptRef(uri1));
+}
+
+function run_test() {
+ var simpleURI = NetUtil.newURI(SIMPLEURI_SPEC);
+ var fileDataURI = NetUtil.newURI(BLOBURI_SPEC);
+
+ do_info("Checking that " + SIMPLEURI_SPEC + " != " + BLOBURI_SPEC);
+ do_check_uri_neq(simpleURI, fileDataURI);
+
+ do_info("Changing the nsSimpleURI spec to match the nsFileDataURI");
+ simpleURI = simpleURI.mutate().setSpec(BLOBURI_SPEC).finalize();
+
+ do_info("Verifying that .spec matches");
+ Assert.equal(simpleURI.spec, fileDataURI.spec);
+
+ do_info(
+ "Checking that nsSimpleURI != nsFileDataURI despite their .spec matching"
+ );
+ do_check_uri_neq(simpleURI, fileDataURI);
+}
diff --git a/netwerk/test/unit/test_bug667087.js b/netwerk/test/unit/test_bug667087.js
new file mode 100644
index 0000000000..79589799e7
--- /dev/null
+++ b/netwerk/test/unit/test_bug667087.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+ var expiry = (Date.now() + 1000) * 1000;
+
+ // Test our handling of host names with a single character consisting only
+ // of a single character
+ Services.cookies.add(
+ "a",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTP
+ );
+ Assert.equal(Services.cookies.countCookiesFromHost("a"), 1);
+
+ CookieXPCShellUtils.createServer({ hosts: ["a"] });
+ const cookies = await CookieXPCShellUtils.getCookieStringFromDocument(
+ "http://a/"
+ );
+ Assert.equal(cookies, "foo=bar");
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/test/unit/test_bug667818.js b/netwerk/test/unit/test_bug667818.js
new file mode 100644
index 0000000000..9c6e495da0
--- /dev/null
+++ b/netwerk/test/unit/test_bug667818.js
@@ -0,0 +1,49 @@
+"use strict";
+
+function makeURI(str) {
+ return Services.io.newURI(str);
+}
+
+add_task(async () => {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ var uri = makeURI("http://example.com/");
+ var channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ Services.scriptSecurityManager.createContentPrincipal(uri, {});
+
+ CookieXPCShellUtils.createServer({ hosts: ["example.com"] });
+
+ // Try an expiration time before the epoch
+
+ await CookieXPCShellUtils.setCookieToDocument(
+ uri.spec,
+ "test=test; path=/; domain=example.com; expires=Sun, 31-Dec-1899 16:00:00 GMT;"
+ );
+ Assert.equal(
+ await CookieXPCShellUtils.getCookieStringFromDocument(uri.spec),
+ ""
+ );
+
+ // Now sanity check
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "test2=test2; path=/; domain=example.com;",
+ channel
+ );
+
+ Assert.equal(
+ await CookieXPCShellUtils.getCookieStringFromDocument(uri.spec),
+ "test2=test2"
+ );
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/test/unit/test_bug667907.js b/netwerk/test/unit/test_bug667907.js
new file mode 100644
index 0000000000..5c6b13d538
--- /dev/null
+++ b/netwerk/test/unit/test_bug667907.js
@@ -0,0 +1,88 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+var simplePath = "/simple";
+var normalPath = "/normal";
+var httpbody = "<html></html>";
+
+ChromeUtils.defineLazyGetter(this, "uri1", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + simplePath;
+});
+
+ChromeUtils.defineLazyGetter(this, "uri2", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + normalPath;
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var listener_proto = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ Assert.equal(
+ request.QueryInterface(Ci.nsIChannel).contentType,
+ this.contentType
+ );
+ request.cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ do_throw("Unexpected onDataAvailable");
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_BINDING_ABORTED);
+ this.termination_func();
+ },
+};
+
+function listener(contentType, termination_func) {
+ this.contentType = contentType;
+ this.termination_func = termination_func;
+}
+listener.prototype = listener_proto;
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(simplePath, simpleHandler);
+ httpserver.registerPathHandler(normalPath, normalHandler);
+ httpserver.start(-1);
+
+ var channel = make_channel(uri1);
+ channel.asyncOpen(
+ new listener("text/plain", function () {
+ run_test2();
+ })
+ );
+
+ do_test_pending();
+}
+
+function run_test2() {
+ var channel = make_channel(uri2);
+ channel.asyncOpen(
+ new listener("text/html", function () {
+ httpserver.stop(do_test_finished);
+ })
+ );
+}
+
+function simpleHandler(metadata, response) {
+ response.seizePower();
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ response.finish();
+}
+
+function normalHandler(metadata, response) {
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_bug669001.js b/netwerk/test/unit/test_bug669001.js
new file mode 100644
index 0000000000..ebd5998cf1
--- /dev/null
+++ b/netwerk/test/unit/test_bug669001.js
@@ -0,0 +1,178 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpServer = null;
+var path = "/bug699001";
+
+ChromeUtils.defineLazyGetter(this, "URI", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + path;
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var fetched;
+
+// The test loads a resource that expires in one year, has an etag and varies only by User-Agent
+// First we load it, then check we load it only from the cache w/o even checking with the server
+// Then we modify our User-Agent and try it again
+// We have to get a new content (even though with the same etag) and again on next load only from
+// cache w/o accessing the server
+// Goal is to check we've updated User-Agent request header in cache after we've got 304 response
+// from the server
+
+var tests = [
+ {
+ prepare() {},
+ test(response) {
+ Assert.ok(fetched);
+ },
+ },
+ {
+ prepare() {},
+ test(response) {
+ Assert.ok(!fetched);
+ },
+ },
+ {
+ prepare() {
+ setUA("A different User Agent");
+ },
+ test(response) {
+ Assert.ok(fetched);
+ },
+ },
+ {
+ prepare() {},
+ test(response) {
+ Assert.ok(!fetched);
+ },
+ },
+ {
+ prepare() {
+ setUA("And another User Agent");
+ },
+ test(response) {
+ Assert.ok(fetched);
+ },
+ },
+ {
+ prepare() {},
+ test(response) {
+ Assert.ok(!fetched);
+ },
+ },
+];
+
+function handler(metadata, response) {
+ if (metadata.hasHeader("If-None-Match")) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not modified");
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain");
+
+ var body = "body";
+ response.bodyOutputStream.write(body, body.length);
+ }
+
+ fetched = true;
+
+ response.setHeader("Expires", getDateString(+1));
+ response.setHeader("Cache-Control", "private");
+ response.setHeader("Vary", "User-Agent");
+ response.setHeader("ETag", "1234");
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(path, handler);
+ httpServer.start(-1);
+
+ do_test_pending();
+
+ nextTest();
+}
+
+function nextTest() {
+ fetched = false;
+ tests[0].prepare();
+
+ dump("Testing with User-Agent: " + getUA() + "\n");
+ var chan = make_channel(URI);
+
+ // Give the old channel a chance to close the cache entry first.
+ // XXX This is actually a race condition that might be considered a bug...
+ executeSoon(function () {
+ chan.asyncOpen(new ChannelListener(checkAndShiftTest, null));
+ });
+}
+
+function checkAndShiftTest(request, response) {
+ tests[0].test(response);
+
+ tests.shift();
+ if (!tests.length) {
+ httpServer.stop(tearDown);
+ return;
+ }
+
+ nextTest();
+}
+
+function tearDown() {
+ setUA("");
+ do_test_finished();
+}
+
+// Helpers
+
+function getUA() {
+ var httphandler = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
+ Ci.nsIHttpProtocolHandler
+ );
+ return httphandler.userAgent;
+}
+
+function setUA(value) {
+ Services.prefs.setCharPref("general.useragent.override", value);
+}
+
+function getDateString(yearDelta) {
+ var months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ var d = new Date();
+ return (
+ days[d.getUTCDay()] +
+ ", " +
+ d.getUTCDate() +
+ " " +
+ months[d.getUTCMonth()] +
+ " " +
+ (d.getUTCFullYear() + yearDelta) +
+ " " +
+ d.getUTCHours() +
+ ":" +
+ d.getUTCMinutes() +
+ ":" +
+ d.getUTCSeconds() +
+ " UTC"
+ );
+}
diff --git a/netwerk/test/unit/test_bug770243.js b/netwerk/test/unit/test_bug770243.js
new file mode 100644
index 0000000000..60aa6a07bb
--- /dev/null
+++ b/netwerk/test/unit/test_bug770243.js
@@ -0,0 +1,246 @@
+/* this test does the following:
+ Always requests the same resource, while for each request getting:
+ 1. 200 + ETag: "one"
+ 2. 401 followed by 200 + ETag: "two"
+ 3. 401 followed by 304
+ 4. 407 followed by 200 + ETag: "three"
+ 5. 407 followed by 304
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserv;
+
+function addCreds(scheme, host) {
+ var authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].getService(
+ Ci.nsIHttpAuthManager
+ );
+ authMgr.setAuthIdentity(
+ scheme,
+ host,
+ httpserv.identity.primaryPort,
+ "basic",
+ "secret",
+ "/",
+ "",
+ "user",
+ "pass"
+ );
+}
+
+function clearCreds() {
+ var authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].getService(
+ Ci.nsIHttpAuthManager
+ );
+ authMgr.clearAll();
+}
+
+function makeChan() {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserv.identity.primaryPort + "/",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+// Array of handlers that are called one by one in response to expected requests
+
+var handlers = [
+ // Test 1
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Authorization"), false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("ETag", '"one"', false);
+ response.setHeader("Cache-control", "no-cache", false);
+ response.setHeader("Content-type", "text/plain", false);
+ var body = "Response body 1";
+ response.bodyOutputStream.write(body, body.length);
+ },
+
+ // Test 2
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Authorization"), false);
+ Assert.equal(metadata.getHeader("If-None-Match"), '"one"');
+ response.setStatusLine(metadata.httpVersion, 401, "Authenticate");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Authorization"), true);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("ETag", '"two"', false);
+ response.setHeader("Cache-control", "no-cache", false);
+ response.setHeader("Content-type", "text/plain", false);
+ var body = "Response body 2";
+ response.bodyOutputStream.write(body, body.length);
+ clearCreds();
+ },
+
+ // Test 3
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Authorization"), false);
+ Assert.equal(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 401, "Authenticate");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Authorization"), true);
+ Assert.equal(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 304, "OK");
+ response.setHeader("ETag", '"two"', false);
+ clearCreds();
+ },
+
+ // Test 4
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Authorization"), false);
+ Assert.equal(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 407, "Proxy Authenticate");
+ response.setHeader("Proxy-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Proxy-Authorization"), true);
+ Assert.equal(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("ETag", '"three"', false);
+ response.setHeader("Cache-control", "no-cache", false);
+ response.setHeader("Content-type", "text/plain", false);
+ var body = "Response body 3";
+ response.bodyOutputStream.write(body, body.length);
+ clearCreds();
+ },
+
+ // Test 5
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Proxy-Authorization"), false);
+ Assert.equal(metadata.getHeader("If-None-Match"), '"three"');
+ response.setStatusLine(metadata.httpVersion, 407, "Proxy Authenticate");
+ response.setHeader("Proxy-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function (metadata, response) {
+ Assert.equal(metadata.hasHeader("Proxy-Authorization"), true);
+ Assert.equal(metadata.getHeader("If-None-Match"), '"three"');
+ response.setStatusLine(metadata.httpVersion, 304, "OK");
+ response.setHeader("ETag", '"three"', false);
+ response.setHeader("Cache-control", "no-cache", false);
+ clearCreds();
+ },
+];
+
+function handler(metadata, response) {
+ handlers.shift()(metadata, response);
+}
+
+// Array of tests to run, self-driven
+
+function sync_and_run_next_test() {
+ syncWithCacheIOThread(function () {
+ tests.shift()();
+ });
+}
+
+var tests = [
+ // Test 1: 200 (cacheable)
+ function () {
+ var ch = makeChan();
+ ch.asyncOpen(
+ new ChannelListener(
+ function (req, body) {
+ Assert.equal(body, "Response body 1");
+ sync_and_run_next_test();
+ },
+ null,
+ CL_NOT_FROM_CACHE
+ )
+ );
+ },
+
+ // Test 2: 401 and 200 + new content
+ function () {
+ var ch = makeChan();
+ ch.asyncOpen(
+ new ChannelListener(
+ function (req, body) {
+ Assert.equal(body, "Response body 2");
+ sync_and_run_next_test();
+ },
+ null,
+ CL_NOT_FROM_CACHE
+ )
+ );
+ },
+
+ // Test 3: 401 and 304
+ function () {
+ var ch = makeChan();
+ ch.asyncOpen(
+ new ChannelListener(
+ function (req, body) {
+ Assert.equal(body, "Response body 2");
+ sync_and_run_next_test();
+ },
+ null,
+ CL_FROM_CACHE
+ )
+ );
+ },
+
+ // Test 4: 407 and 200 + new content
+ function () {
+ var ch = makeChan();
+ ch.asyncOpen(
+ new ChannelListener(
+ function (req, body) {
+ Assert.equal(body, "Response body 3");
+ sync_and_run_next_test();
+ },
+ null,
+ CL_NOT_FROM_CACHE
+ )
+ );
+ },
+
+ // Test 5: 407 and 304
+ function () {
+ var ch = makeChan();
+ ch.asyncOpen(
+ new ChannelListener(
+ function (req, body) {
+ Assert.equal(body, "Response body 3");
+ sync_and_run_next_test();
+ },
+ null,
+ CL_FROM_CACHE
+ )
+ );
+ },
+
+ // End of test run
+ function () {
+ httpserv.stop(do_test_finished);
+ },
+];
+
+function run_test() {
+ do_get_profile();
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", handler);
+ httpserv.start(-1);
+
+ const prefs = Services.prefs;
+ prefs.setCharPref("network.proxy.http", "localhost");
+ prefs.setIntPref("network.proxy.http_port", httpserv.identity.primaryPort);
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ prefs.setIntPref("network.proxy.type", 1);
+ prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ tests.shift()();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug812167.js b/netwerk/test/unit/test_bug812167.js
new file mode 100644
index 0000000000..8f4d1310a0
--- /dev/null
+++ b/netwerk/test/unit/test_bug812167.js
@@ -0,0 +1,143 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+/*
+- get 302 with Cache-control: no-store
+- check cache entry for the 302 response is cached only in memory device
+- get 302 with Expires: -1
+- check cache entry for the 302 response is not cached at all
+*/
+
+var httpserver = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath1 = "/redirect-no-store/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI1", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + randomPath1;
+});
+
+var randomPath2 = "/redirect-expires-past/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI2", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + randomPath2;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+var redirectHandler_NoStore_calls = 0;
+function redirectHandler_NoStore(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader(
+ "Location",
+ "http://localhost:" + httpserver.identity.primaryPort + "/content",
+ false
+ );
+ response.setHeader("Cache-control", "no-store");
+ ++redirectHandler_NoStore_calls;
+}
+
+var redirectHandler_ExpiresInPast_calls = 0;
+function redirectHandler_ExpiresInPast(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader(
+ "Location",
+ "http://localhost:" + httpserver.identity.primaryPort + "/content",
+ false
+ );
+ response.setHeader("Expires", "-1");
+ ++redirectHandler_ExpiresInPast_calls;
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function check_response(
+ path,
+ request,
+ buffer,
+ expectedExpiration,
+ continuation
+) {
+ Assert.equal(buffer, responseBody);
+
+ // Entry is always there, old cache wrapping code does session->SetDoomEntriesIfExpired(false),
+ // just check it's not persisted or is expired (dep on the test).
+ asyncOpenCacheEntry(
+ path,
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ function (status, entry) {
+ Assert.equal(status, 0);
+
+ // Expired entry is on disk, no-store entry is in memory
+ Assert.equal(entry.persistent, expectedExpiration);
+
+ // Do the request again and check the server handler is called appropriately
+ var chan = make_channel(path);
+ chan.asyncOpen(
+ new ChannelListener(function (request1, buffer1) {
+ Assert.equal(buffer1, responseBody);
+
+ if (expectedExpiration) {
+ // Handler had to be called second time
+ Assert.equal(redirectHandler_ExpiresInPast_calls, 2);
+ } else {
+ // Handler had to be called second time (no-store forces validate),
+ // and we are just in memory
+ Assert.equal(redirectHandler_NoStore_calls, 2);
+ Assert.ok(!entry.persistent);
+ }
+
+ continuation();
+ }, null)
+ );
+ }
+ );
+}
+
+function run_test_no_store() {
+ var chan = make_channel(randomURI1);
+ chan.asyncOpen(
+ new ChannelListener(function (request, buffer) {
+ // Cache-control: no-store response should only be found in the memory cache.
+ check_response(randomURI1, request, buffer, false, run_test_expires_past);
+ }, null)
+ );
+}
+
+function run_test_expires_past() {
+ var chan = make_channel(randomURI2);
+ chan.asyncOpen(
+ new ChannelListener(function (request, buffer) {
+ // Expires: -1 response should not be found in any cache.
+ check_response(randomURI2, request, buffer, true, finish_test);
+ }, null)
+ );
+}
+
+function finish_test() {
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ do_get_profile();
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(randomPath1, redirectHandler_NoStore);
+ httpserver.registerPathHandler(randomPath2, redirectHandler_ExpiresInPast);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ run_test_no_store();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug826063.js b/netwerk/test/unit/test_bug826063.js
new file mode 100644
index 0000000000..3e17df6461
--- /dev/null
+++ b/netwerk/test/unit/test_bug826063.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that nsIPrivateBrowsingChannel.isChannelPrivate yields the correct
+ * result for various combinations of .setPrivate() and nsILoadContexts
+ */
+
+"use strict";
+
+var URIs = ["http://example.org", "https://example.org"];
+
+function* getChannels() {
+ for (let u of URIs) {
+ yield NetUtil.newChannel({
+ uri: u,
+ loadUsingSystemPrincipal: true,
+ });
+ }
+}
+
+function checkPrivate(channel, shouldBePrivate) {
+ Assert.equal(
+ channel.QueryInterface(Ci.nsIPrivateBrowsingChannel).isChannelPrivate,
+ shouldBePrivate
+ );
+}
+
+/**
+ * Default configuration
+ * Default is non-private
+ */
+add_test(function test_plain() {
+ for (let c of getChannels()) {
+ checkPrivate(c, false);
+ }
+ run_next_test();
+});
+
+/**
+ * Explicitly setPrivate(true), no load context
+ */
+add_test(function test_setPrivate_private() {
+ for (let c of getChannels()) {
+ c.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(true);
+ checkPrivate(c, true);
+ }
+ run_next_test();
+});
+
+/**
+ * Explicitly setPrivate(false), no load context
+ */
+add_test(function test_setPrivate_regular() {
+ for (let c of getChannels()) {
+ c.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(false);
+ checkPrivate(c, false);
+ }
+ run_next_test();
+});
+
+/**
+ * Load context mandates private mode
+ */
+add_test(function test_LoadContextPrivate() {
+ let ctx = Cu.createPrivateLoadContext();
+ for (let c of getChannels()) {
+ c.notificationCallbacks = ctx;
+ checkPrivate(c, true);
+ }
+ run_next_test();
+});
+
+/**
+ * Load context mandates regular mode
+ */
+add_test(function test_LoadContextRegular() {
+ let ctx = Cu.createLoadContext();
+ for (let c of getChannels()) {
+ c.notificationCallbacks = ctx;
+ checkPrivate(c, false);
+ }
+ run_next_test();
+});
+
+// Do not test simultanous uses of .setPrivate and load context.
+// There is little merit in doing so, and combining both will assert in
+// Debug builds anyway.
diff --git a/netwerk/test/unit/test_bug856978.js b/netwerk/test/unit/test_bug856978.js
new file mode 100644
index 0000000000..607b392473
--- /dev/null
+++ b/netwerk/test/unit/test_bug856978.js
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 test makes sure that the authorization header can get deleted e.g. by
+// extensions if they are observing "http-on-modify-request". In a first step
+// the auth cache is filled with credentials which then get added to the
+// following request. On "http-on-modify-request" it is tested whether the
+// authorization header got added at all and if so it gets removed. This test
+// passes iff both succeeds.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var notification = "http-on-modify-request";
+
+var httpServer = null;
+
+var authCredentials = "guest:guest";
+var authPath = "/authTest";
+var authCredsURL = "http://" + authCredentials + "@localhost:8888" + authPath;
+var authURL = "http://localhost:8888" + authPath;
+
+function authHandler(metadata, response) {
+ if (metadata.hasHeader("Test")) {
+ // Lets see if the auth header got deleted.
+ var noAuthHeader = false;
+ if (!metadata.hasHeader("Authorization")) {
+ noAuthHeader = true;
+ }
+ Assert.ok(noAuthHeader);
+ }
+ // Not our test request yet.
+ else if (!metadata.hasHeader("Authorization")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ }
+}
+
+function RequestObserver() {
+ this.register();
+}
+
+RequestObserver.prototype = {
+ register() {
+ info("Registering " + notification);
+ Services.obs.addObserver(this, notification, true);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ observe(subject, topic, data) {
+ if (topic == notification) {
+ if (!(subject instanceof Ci.nsIHttpChannel)) {
+ do_throw(notification + " observed a non-HTTP channel.");
+ }
+ try {
+ subject.getRequestHeader("Authorization");
+ } catch (e) {
+ // Throw if there is no header to delete. We should get one iff caching
+ // the auth credentials is working and the header gets added _before_
+ // "http-on-modify-request" gets called.
+ httpServer.stop(do_test_finished);
+ do_throw("No authorization header found, aborting!");
+ }
+ // We are still here. Let's remove the authorization header now.
+ subject.setRequestHeader("Authorization", null, false);
+ }
+ },
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ if (current_test < tests.length - 1) {
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpServer.stop(do_test_finished);
+ }
+ do_test_finished();
+ },
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var tests = [startAuthHeaderTest, removeAuthHeaderTest];
+
+var current_test = 0;
+
+// Must create a RequestObserver for the test to pass, we keep it in memory
+// to avoid garbage collection.
+// eslint-disable-next-line no-unused-vars
+var requestObserver = null;
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(authPath, authHandler);
+ httpServer.start(8888);
+
+ tests[0]();
+}
+
+function startAuthHeaderTest() {
+ var chan = makeChan(authCredsURL);
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function removeAuthHeaderTest() {
+ // After caching the auth credentials in the first test, lets try to remove
+ // the authorization header now...
+ requestObserver = new RequestObserver();
+ var chan = makeChan(authURL);
+ // Indicating that the request is coming from the second test.
+ chan.setRequestHeader("Test", "1", false);
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug894586.js b/netwerk/test/unit/test_bug894586.js
new file mode 100644
index 0000000000..bc25731d36
--- /dev/null
+++ b/netwerk/test/unit/test_bug894586.js
@@ -0,0 +1,135 @@
+/*
+ * Tests for bug 894586: nsSyncLoadService::PushSyncStreamToListener
+ * should not fail for channels of unknown size
+ */
+
+"use strict";
+
+var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"].getService(
+ Ci.nsIContentSecurityManager
+);
+
+function ProtocolHandler() {
+ this.uri = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec(this.scheme + ":dummy")
+ .finalize();
+}
+
+ProtocolHandler.prototype = {
+ /** nsIProtocolHandler */
+ get scheme() {
+ return "x-bug894586";
+ },
+ newChannel(aURI, aLoadInfo) {
+ this.loadInfo = aLoadInfo;
+ return this;
+ },
+ allowPort(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsIChannel */
+ get originalURI() {
+ return this.uri;
+ },
+ get URI() {
+ return this.uri;
+ },
+ owner: null,
+ notificationCallbacks: null,
+ get securityInfo() {
+ return null;
+ },
+ get contentType() {
+ return "text/css";
+ },
+ set contentType(val) {},
+ contentCharset: "UTF-8",
+ get contentLength() {
+ return -1;
+ },
+ set contentLength(val) {
+ throw Components.Exception(
+ "Setting content length",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ },
+ open() {
+ // throws an error if security checks fail
+ contentSecManager.performSecurityCheck(this, null);
+
+ var file = do_get_file("test_bug894586.js", false);
+ Assert.ok(file.exists());
+ var url = Services.io.newFileURI(file);
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).open();
+ },
+ asyncOpen(aListener, aContext) {
+ throw Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ contentDisposition: Ci.nsIChannel.DISPOSITION_INLINE,
+ get contentDispositionFilename() {
+ throw Components.Exception("No file name", Cr.NS_ERROR_NOT_AVAILABLE);
+ },
+ get contentDispositionHeader() {
+ throw Components.Exception("No header", Cr.NS_ERROR_NOT_AVAILABLE);
+ },
+
+ /** nsIRequest */
+ get name() {
+ return this.uri.spec;
+ },
+ isPending: () => false,
+ get status() {
+ return Cr.NS_OK;
+ },
+ cancel(status) {},
+ loadGroup: null,
+ loadFlags:
+ Ci.nsIRequest.LOAD_NORMAL |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+
+ /** nsISupports */
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIProtocolHandler",
+ "nsIRequest",
+ "nsIChannel",
+ ]),
+};
+
+/**
+ * Attempt a sync load; we use the stylesheet service to do this for us,
+ * based on the knowledge that it forces a sync load under the hood.
+ */
+function run_test() {
+ var handler = new ProtocolHandler();
+
+ Services.io.registerProtocolHandler(
+ handler.scheme,
+ handler,
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
+ Ci.nsIProtocolHandler.URI_NON_PERSISTABLE |
+ Ci.nsIProtocolHandler.URI_SYNC_LOAD_IS_OK,
+ -1
+ );
+ try {
+ var ss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
+ Ci.nsIStyleSheetService
+ );
+ ss.loadAndRegisterSheet(handler.uri, Ci.nsIStyleSheetService.AGENT_SHEET);
+ Assert.ok(
+ ss.sheetRegistered(handler.uri, Ci.nsIStyleSheetService.AGENT_SHEET)
+ );
+ } finally {
+ Services.io.unregisterProtocolHandler(handler.scheme);
+ }
+}
+
+// vim: set et ts=2 :
diff --git a/netwerk/test/unit/test_bug935499.js b/netwerk/test/unit/test_bug935499.js
new file mode 100644
index 0000000000..2da8168d2d
--- /dev/null
+++ b/netwerk/test/unit/test_bug935499.js
@@ -0,0 +1,10 @@
+"use strict";
+
+function run_test() {
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+
+ var isASCII = {};
+ Assert.equal(idnService.convertToDisplayIDN("xn--", isASCII), "xn--");
+}
diff --git a/netwerk/test/unit/test_cache-control_request.js b/netwerk/test/unit/test_cache-control_request.js
new file mode 100644
index 0000000000..f0873cba24
--- /dev/null
+++ b/netwerk/test/unit/test_cache-control_request.js
@@ -0,0 +1,448 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+var cache = null;
+
+var base_url = "http://localhost:" + httpserver.identity.primaryPort;
+var resource_age_100 = "/resource_age_100";
+var resource_age_100_url = base_url + resource_age_100;
+var resource_stale_100 = "/resource_stale_100";
+var resource_stale_100_url = base_url + resource_stale_100;
+var resource_fresh_100 = "/resource_fresh_100";
+var resource_fresh_100_url = base_url + resource_fresh_100;
+
+// Test flags
+var hit_server = false;
+
+function make_channel(url, cache_control) {
+ // Reset test global status
+ hit_server = false;
+
+ var req = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ req.QueryInterface(Ci.nsIHttpChannel);
+ if (cache_control) {
+ req.setRequestHeader("Cache-control", cache_control, false);
+ }
+
+ return req;
+}
+
+function make_uri(url) {
+ return Services.io.newURI(url);
+}
+
+function resource_age_100_handler(metadata, response) {
+ hit_server = true;
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Age", "100", false);
+ response.setHeader("Last-Modified", date_string_from_now(-100), false);
+ response.setHeader("Expires", date_string_from_now(+9999), false);
+
+ const body = "data1";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function resource_stale_100_handler(metadata, response) {
+ hit_server = true;
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Date", date_string_from_now(-200), false);
+ response.setHeader("Last-Modified", date_string_from_now(-200), false);
+ response.setHeader("Cache-Control", "max-age=100", false);
+ response.setHeader("Expires", date_string_from_now(-100), false);
+
+ const body = "data2";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function resource_fresh_100_handler(metadata, response) {
+ hit_server = true;
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", date_string_from_now(0), false);
+ response.setHeader("Cache-Control", "max-age=100", false);
+ response.setHeader("Expires", date_string_from_now(+100), false);
+
+ const body = "data3";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function run_test() {
+ do_get_profile();
+
+ do_test_pending();
+
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpserver.registerPathHandler(resource_age_100, resource_age_100_handler);
+ httpserver.registerPathHandler(
+ resource_stale_100,
+ resource_stale_100_handler
+ );
+ httpserver.registerPathHandler(
+ resource_fresh_100,
+ resource_fresh_100_handler
+ );
+ cache = getCacheStorage("disk");
+
+ wait_for_cache_index(run_next_test);
+}
+
+// Here starts the list of tests
+
+// ============================================================================
+// Cache-Control: no-store
+
+add_test(() => {
+ // Must not create a cache entry
+ var ch = make_channel(resource_age_100_url, "no-store");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(!cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // Prepare state only, cache the entry
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // Check the prepared cache entry is used when no special directives are added
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // Try again, while we already keep a cache entry,
+ // the channel must not use it, entry should stay in the cache
+ var ch = make_channel(resource_age_100_url, "no-store");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+// ============================================================================
+// Cache-Control: no-cache
+
+add_test(() => {
+ // Check the prepared cache entry is used when no special directives are added
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // The existing entry should be revalidated (we expect a server hit)
+ var ch = make_channel(resource_age_100_url, "no-cache");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+// ============================================================================
+// Cache-Control: max-age
+
+add_test(() => {
+ // Check the prepared cache entry is used when no special directives are added
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // The existing entry's age is greater than the maximum requested,
+ // should hit server
+ var ch = make_channel(resource_age_100_url, "max-age=10");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // The existing entry's age is greater than the maximum requested,
+ // but the max-stale directive says to use it when it's fresh enough
+ var ch = make_channel(resource_age_100_url, "max-age=10, max-stale=99999");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // The existing entry's age is lesser than the maximum requested,
+ // should go from cache
+ var ch = make_channel(resource_age_100_url, "max-age=1000");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+// ============================================================================
+// Cache-Control: max-stale
+
+add_test(() => {
+ // Preprate the entry first
+ var ch = make_channel(resource_stale_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ // Must shift the expiration time set on the entry to |now| be in the past
+ do_timeout(1500, run_next_test);
+ }, null)
+ );
+});
+
+add_test(() => {
+ // Check it's not reused (as it's stale) when no special directives
+ // are provided
+ var ch = make_channel(resource_stale_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ do_timeout(1500, run_next_test);
+ }, null)
+ );
+});
+
+add_test(() => {
+ // Accept cached responses of any stale time
+ var ch = make_channel(resource_stale_100_url, "max-stale");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ do_timeout(1500, run_next_test);
+ }, null)
+ );
+});
+
+add_test(() => {
+ // The entry is stale only by 100 seconds, accept it
+ var ch = make_channel(resource_stale_100_url, "max-stale=1000");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ do_timeout(1500, run_next_test);
+ }, null)
+ );
+});
+
+add_test(() => {
+ // The entry is stale by 100 seconds but we only accept a 10 seconds stale
+ // entry, go from server
+ var ch = make_channel(resource_stale_100_url, "max-stale=10");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+// ============================================================================
+// Cache-Control: min-fresh
+
+add_test(() => {
+ // Preprate the entry first
+ var ch = make_channel(resource_fresh_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // Check it's reused when no special directives are provided
+ var ch = make_channel(resource_fresh_100_url);
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // Entry fresh enough to be served from the cache
+ var ch = make_channel(resource_fresh_100_url, "min-fresh=10");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(!hit_server);
+ Assert.ok(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ // The entry is not fresh enough
+ var ch = make_channel(resource_fresh_100_url, "min-fresh=1000");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+// ============================================================================
+// Parser test, if the Cache-Control header would not parse correctly, the entry
+// doesn't load from the server.
+
+add_test(() => {
+ var ch = make_channel(
+ resource_fresh_100_url,
+ 'unknown1,unknown2 = "a,b", min-fresh = 1000 '
+ );
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+add_test(() => {
+ var ch = make_channel(resource_fresh_100_url, "no-cache = , min-fresh = 10");
+ ch.asyncOpen(
+ new ChannelListener(function (request, data) {
+ Assert.ok(hit_server);
+ Assert.ok(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null)
+ );
+});
+
+// ============================================================================
+// Done
+
+add_test(() => {
+ run_next_test();
+ httpserver.stop(do_test_finished);
+});
+
+// ============================================================================
+// Helpers
+
+function date_string_from_now(delta_secs) {
+ var months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ var d = new Date();
+ d.setTime(d.getTime() + delta_secs * 1000);
+ return (
+ days[d.getUTCDay()] +
+ ", " +
+ d.getUTCDate() +
+ " " +
+ months[d.getUTCMonth()] +
+ " " +
+ d.getUTCFullYear() +
+ " " +
+ d.getUTCHours() +
+ ":" +
+ d.getUTCMinutes() +
+ ":" +
+ d.getUTCSeconds() +
+ " UTC"
+ );
+}
diff --git a/netwerk/test/unit/test_cache-entry-id.js b/netwerk/test/unit/test_cache-entry-id.js
new file mode 100644
index 0000000000..f0011de47f
--- /dev/null
+++ b/netwerk/test/unit/test_cache-entry-id.js
@@ -0,0 +1,220 @@
+/**
+ * Test for the "CacheEntryId" under several cases.
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+const responseContent = "response body";
+const responseContent2 = "response body 2";
+const altContent = "!@#$%^&*()";
+const altContentType = "text/binary";
+
+function isParentProcess() {
+ let appInfo = Cc["@mozilla.org/xre/app-info;1"];
+ return (
+ !appInfo ||
+ Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+var handlers = [
+ (m, r) => {
+ r.bodyOutputStream.write(responseContent, responseContent.length);
+ },
+ (m, r) => {
+ r.setStatusLine(m.httpVersion, 304, "Not Modified");
+ },
+ (m, r) => {
+ r.setStatusLine(m.httpVersion, 304, "Not Modified");
+ },
+ (m, r) => {
+ r.setStatusLine(m.httpVersion, 304, "Not Modified");
+ },
+ (m, r) => {
+ r.setStatusLine(m.httpVersion, 304, "Not Modified");
+ },
+ (m, r) => {
+ r.bodyOutputStream.write(responseContent2, responseContent2.length);
+ },
+ (m, r) => {
+ r.setStatusLine(m.httpVersion, 304, "Not Modified");
+ },
+];
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+
+ var handler = handlers.shift();
+ if (handler) {
+ handler(metadata, response);
+ return;
+ }
+
+ Assert.ok(false, "Should not reach here.");
+}
+
+function fetch(preferredDataType = null) {
+ return new Promise(resolve => {
+ var chan = NetUtil.newChannel({ uri: URL, loadUsingSystemPrincipal: true });
+
+ if (preferredDataType) {
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ altContentType,
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ }
+
+ chan.asyncOpen(
+ new ChannelListener((request, buffer, ctx, isFromCache, cacheEntryId) => {
+ resolve({ request, buffer, isFromCache, cacheEntryId });
+ }, null)
+ );
+ });
+}
+
+function check(
+ response,
+ content,
+ preferredDataType,
+ isFromCache,
+ cacheEntryIdChecker
+) {
+ var cc = response.request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ Assert.equal(response.buffer, content);
+ Assert.equal(cc.alternativeDataType, preferredDataType);
+ Assert.equal(response.isFromCache, isFromCache);
+ Assert.ok(!cacheEntryIdChecker || cacheEntryIdChecker(response.cacheEntryId));
+
+ return response;
+}
+
+function writeAltData(request) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+ var os = cc.openAlternativeOutputStream(altContentType, altContent.length);
+ os.write(altContent, altContent.length);
+ os.close();
+ gc(); // We need to do a GC pass to ensure the cache entry has been freed.
+
+ return new Promise(resolve => {
+ if (isParentProcess()) {
+ Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(resolve);
+ } else {
+ do_send_remote_message("flush");
+ do_await_remote_message("flushed").then(resolve);
+ }
+ });
+}
+
+function run_test() {
+ do_get_profile();
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+ do_test_pending();
+
+ var targetCacheEntryId = null;
+
+ return (
+ Promise.resolve()
+ // Setup testing environment: Placing alternative data into HTTP cache.
+ .then(_ => fetch(altContentType))
+ .then(r =>
+ check(
+ r,
+ responseContent,
+ "",
+ false,
+ cacheEntryId => cacheEntryId === undefined
+ )
+ )
+ .then(r => writeAltData(r.request))
+
+ // Start testing.
+ .then(_ => fetch(altContentType))
+ .then(r =>
+ check(
+ r,
+ altContent,
+ altContentType,
+ true,
+ cacheEntryId => cacheEntryId !== undefined
+ )
+ )
+ .then(r => (targetCacheEntryId = r.cacheEntryId))
+
+ .then(_ => fetch())
+ .then(r =>
+ check(
+ r,
+ responseContent,
+ "",
+ true,
+ cacheEntryId => cacheEntryId === targetCacheEntryId
+ )
+ )
+
+ .then(_ => fetch(altContentType))
+ .then(r =>
+ check(
+ r,
+ altContent,
+ altContentType,
+ true,
+ cacheEntryId => cacheEntryId === targetCacheEntryId
+ )
+ )
+
+ .then(_ => fetch())
+ .then(r =>
+ check(
+ r,
+ responseContent,
+ "",
+ true,
+ cacheEntryId => cacheEntryId === targetCacheEntryId
+ )
+ )
+
+ .then(_ => fetch()) // The response is changed here.
+ .then(r =>
+ check(
+ r,
+ responseContent2,
+ "",
+ false,
+ cacheEntryId => cacheEntryId === undefined
+ )
+ )
+
+ .then(_ => fetch())
+ .then(r =>
+ check(
+ r,
+ responseContent2,
+ "",
+ true,
+ cacheEntryId =>
+ cacheEntryId !== undefined && cacheEntryId !== targetCacheEntryId
+ )
+ )
+
+ // Tear down.
+ .catch(e => Assert.ok(false, "Unexpected exception: " + e))
+ .then(_ => Assert.equal(handlers.length, 0))
+ .then(_ => httpServer.stop(do_test_finished))
+ );
+}
diff --git a/netwerk/test/unit/test_cache2-00-service-get.js b/netwerk/test/unit/test_cache2-00-service-get.js
new file mode 100644
index 0000000000..0d348a81de
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-00-service-get.js
@@ -0,0 +1,15 @@
+"use strict";
+
+function run_test() {
+ // Just check the contract ID alias works well.
+ try {
+ var serviceA = Services.cache2;
+ Assert.ok(serviceA);
+ var serviceB = Services.cache2;
+ Assert.ok(serviceB);
+
+ Assert.equal(serviceA, serviceB);
+ } catch (ex) {
+ do_throw("Cannot instantiate cache storage service: " + ex);
+ }
+}
diff --git a/netwerk/test/unit/test_cache2-01-basic.js b/netwerk/test/unit/test_cache2-01-basic.js
new file mode 100644
index 0000000000..6b02bd0d91
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01-basic.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW, "a2m", "a2d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01a-basic-readonly.js b/netwerk/test/unit/test_cache2-01a-basic-readonly.js
new file mode 100644
index 0000000000..2988651c61
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01a-basic-readonly.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://ro/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://ro/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry(
+ "http://ro/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW, "a2m", "a2d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://ro/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01b-basic-datasize.js b/netwerk/test/unit/test_cache2-01b-basic-datasize.js
new file mode 100644
index 0000000000..56b3b8d5f2
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01b-basic-datasize.js
@@ -0,0 +1,51 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | WAITFORWRITE, "a1m", "a1d", function (entry1) {
+ // Open for read and check
+ Assert.equal(entry1.dataSize, 3);
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function (entry2) {
+ // Open for rewrite (truncate), write different meta and data
+ Assert.equal(entry2.dataSize, 3);
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW | WAITFORWRITE, "a2m", "a2d", function (
+ entry3
+ ) {
+ // Open for read and check
+ Assert.equal(entry3.dataSize, 3);
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function (entry4) {
+ Assert.equal(entry4.dataSize, 3);
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js b/netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js
new file mode 100644
index 0000000000..33adb6206c
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://mt/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | METAONLY, "a1m", "a1d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://mt/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "", function () {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry(
+ "http://mt/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW, "a2m", "a2d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://mt/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01d-basic-not-wanted.js b/netwerk/test/unit/test_cache2-01d-basic-not-wanted.js
new file mode 100644
index 0000000000..9d2b3f8458
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01d-basic-not-wanted.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ // Open but don't want the entry
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NOTWANTED, "a1m", "a1d", function () {
+ // Open for read again and check the entry is OK
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js b/netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js
new file mode 100644
index 0000000000..e77e7afdfc
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js
@@ -0,0 +1,39 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, delay the actual write
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | DONTFILL, "a1m", "a1d", function () {
+ var bypassed = false;
+
+ // Open and bypass
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_BYPASS_IF_BUSY,
+ null,
+ new OpenCallback(NOTFOUND, "", "", function () {
+ Assert.ok(!bypassed);
+ bypassed = true;
+ })
+ );
+
+ // do_execute_soon for two reasons:
+ // 1. we want finish_cache2_test call for sure after do_test_pending, but all the callbacks here
+ // may invoke synchronously
+ // 2. precaution when the OPEN_BYPASS_IF_BUSY invocation become a post one day
+ executeSoon(function () {
+ Assert.ok(bypassed);
+ finish_cache2_test();
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01f-basic-openTruncate.js b/netwerk/test/unit/test_cache2-01f-basic-openTruncate.js
new file mode 100644
index 0000000000..9d514f7cc7
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01f-basic-openTruncate.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var storage = getCacheStorage("disk");
+ var entry = storage.openTruncate(createURI("http://new1/"), "");
+ Assert.ok(!!entry);
+
+ // Fill the entry, and when done, check it's content
+ new OpenCallback(NEW, "meta", "data", function () {
+ asyncOpenCacheEntry(
+ "http://new1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "meta", "data", function () {
+ finish_cache2_test();
+ })
+ );
+ }).onCacheEntryAvailable(entry, true, 0);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-02-open-non-existing.js b/netwerk/test/unit/test_cache2-02-open-non-existing.js
new file mode 100644
index 0000000000..2ee0efa687
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-02-open-non-existing.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open non-existing for read, should fail
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NOTFOUND, null, null, function () {
+ // Open the same non-existing for read again, should fail second time
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NOTFOUND, null, null, function () {
+ // Try it again normally, should go
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function () {
+ // ...and check
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-02b-open-non-existing-and-doom.js b/netwerk/test/unit/test_cache2-02b-open-non-existing-and-doom.js
new file mode 100644
index 0000000000..d54aeeb9eb
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-02b-open-non-existing-and-doom.js
@@ -0,0 +1,180 @@
+"use strict";
+
+add_task(async function test() {
+ do_get_profile();
+ do_test_pending();
+
+ await new Promise(resolve => {
+ // Open non-existing for read, should fail
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NOTFOUND, null, null, function (entry) {
+ resolve(entry);
+ })
+ );
+ });
+
+ await new Promise(resolve => {
+ // Open the same non-existing for read again, should fail second time
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NOTFOUND, null, null, function (entry) {
+ resolve(entry);
+ })
+ );
+ });
+
+ await new Promise(resolve => {
+ // Try it again normally, should go
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function (entry) {
+ resolve(entry);
+ })
+ );
+ });
+
+ await new Promise(resolve => {
+ // ...and check
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function (entry) {
+ resolve(entry);
+ })
+ );
+ });
+
+ Services.prefs.setBoolPref("network.cache.bug1708673", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.cache.bug1708673");
+ });
+
+ let asyncDoomVisitor = new Promise(resolve => {
+ let doomTasks = [];
+ let visitor = {
+ onCacheStorageInfo() {},
+ async onCacheEntryInfo(
+ aURI,
+ aIdEnhance,
+ aDataSize,
+ aAltDataSize,
+ aFetchCount,
+ aLastModifiedTime,
+ aExpirationTime,
+ aPinned,
+ aInfo
+ ) {
+ doomTasks.push(
+ new Promise(resolve1 => {
+ Services.cache2
+ .diskCacheStorage(aInfo, false)
+ .asyncDoomURI(aURI, aIdEnhance, {
+ onCacheEntryDoomed() {
+ info("doomed");
+ resolve1();
+ },
+ });
+ })
+ );
+ },
+ onCacheEntryVisitCompleted() {
+ Promise.allSettled(doomTasks).then(resolve);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ Services.cache2.asyncVisitAllStorages(visitor, true);
+ });
+
+ let asyncOpenVisitor = new Promise(resolve => {
+ let openTasks = [];
+ let visitor = {
+ onCacheStorageInfo() {},
+ async onCacheEntryInfo(
+ aURI,
+ aIdEnhance,
+ aDataSize,
+ aAltDataSize,
+ aFetchCount,
+ aLastModifiedTime,
+ aExpirationTime,
+ aPinned,
+ aInfo
+ ) {
+ info(`found ${aURI.spec}`);
+ openTasks.push(
+ new Promise(r2 => {
+ Services.cache2
+ .diskCacheStorage(aInfo, false)
+ .asyncOpenURI(
+ aURI,
+ "",
+ Ci.nsICacheStorage.OPEN_READONLY |
+ Ci.nsICacheStorage.OPEN_SECRETLY,
+ {
+ onCacheEntryCheck() {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable(entry, isnew, status) {
+ info("opened");
+ r2();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsICacheEntryOpenCallback",
+ ]),
+ }
+ );
+ })
+ );
+ },
+ onCacheEntryVisitCompleted() {
+ Promise.all(openTasks).then(resolve);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ Services.cache2.asyncVisitAllStorages(visitor, true);
+ });
+
+ await Promise.all([asyncDoomVisitor, asyncOpenVisitor]);
+
+ info("finished visiting");
+
+ await new Promise(resolve => {
+ let entryCount = 0;
+ let visitor = {
+ onCacheStorageInfo() {},
+ async onCacheEntryInfo(
+ aURI,
+ aIdEnhance,
+ aDataSize,
+ aAltDataSize,
+ aFetchCount,
+ aLastModifiedTime,
+ aExpirationTime,
+ aPinned,
+ aInfo
+ ) {
+ entryCount++;
+ },
+ onCacheEntryVisitCompleted() {
+ Assert.equal(entryCount, 0);
+ resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ Services.cache2.asyncVisitAllStorages(visitor, true);
+ });
+
+ finish_cache2_test();
+});
diff --git a/netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js b/netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js
new file mode 100644
index 0000000000..2b1e230b1a
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js
@@ -0,0 +1,36 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open but let OCEA throw
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | THROWAVAIL, null, null, function () {
+ // Try it again, should go
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "c1m", "c1d", function () {
+ // ...and check
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(false, "c1m", "c1d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js b/netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js
new file mode 100644
index 0000000000..aea4cfdbf3
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open but let OCEA throw
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | THROWAVAIL, null, null, function () {
+ // Open but let OCEA throw ones again
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | THROWAVAIL, null, null, function () {
+ // Try it again, should go
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "d1m", "d1d", function () {
+ // ...and check
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "d1m", "d1d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-05-visit.js b/netwerk/test/unit/test_cache2-05-visit.js
new file mode 100644
index 0000000000..b4ed3ea26e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-05-visit.js
@@ -0,0 +1,113 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var storage = getCacheStorage("disk");
+ var mc = new MultipleCallbacks(4, function () {
+ // Method asyncVisitStorage() gets the data from index on Cache I/O thread
+ // with INDEX priority, so it is ensured that index contains information
+ // about all pending writes. However, OpenCallback emulates network latency
+ // by postponing the writes using do_execute_soon. We must do the same here
+ // to make sure that all writes are posted to Cache I/O thread before we
+ // visit the storage.
+ executeSoon(function () {
+ syncWithCacheIOThread(function () {
+ var expectedConsumption = 4096;
+
+ storage.asyncVisitStorage(
+ // Test should store 4 entries
+ new VisitCallback(
+ 4,
+ expectedConsumption,
+ ["http://a/", "http://b/", "http://c/", "http://d/"],
+ function () {
+ storage.asyncVisitStorage(
+ // Still 4 entries expected, now don't walk them
+ new VisitCallback(4, expectedConsumption, null, function () {
+ finish_cache2_test();
+ }),
+ false
+ );
+ }
+ ),
+ true
+ );
+ });
+ });
+ });
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function () {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "c1m", "c1d", function () {
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "c1m", "c1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "d1m", "d1d", function () {
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "d1m", "d1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-06-pb-mode.js b/netwerk/test/unit/test_cache2-06-pb-mode.js
new file mode 100644
index 0000000000..ddece34276
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-06-pb-mode.js
@@ -0,0 +1,50 @@
+"use strict";
+
+function exitPB() {
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+}
+
+function run_test() {
+ do_get_profile();
+
+ // Store PB entry
+ asyncOpenCacheEntry(
+ "http://p1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.private,
+ new OpenCallback(NEW, "p1m", "p1d", function () {
+ asyncOpenCacheEntry(
+ "http://p1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.private,
+ new OpenCallback(NORMAL, "p1m", "p1d", function () {
+ // Check it's there
+ syncWithCacheIOThread(function () {
+ var storage = getCacheStorage(
+ "disk",
+ Services.loadContextInfo.private
+ );
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 12, ["http://p1/"], function () {
+ // Simulate PB exit
+ exitPB();
+ // Check the entry is gone
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function () {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ });
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-07-visit-memory.js b/netwerk/test/unit/test_cache2-07-visit-memory.js
new file mode 100644
index 0000000000..11f0f6a2e7
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-07-visit-memory.js
@@ -0,0 +1,123 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Add entry to the memory storage
+ var mc = new MultipleCallbacks(5, function () {
+ // Check it's there by visiting the storage
+ syncWithCacheIOThread(function () {
+ var storage = getCacheStorage("memory");
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 12, ["http://mem1/"], function () {
+ storage = getCacheStorage("disk");
+ storage.asyncVisitStorage(
+ // Previous tests should store 4 disk entries
+ new VisitCallback(
+ 4,
+ 4096,
+ ["http://a/", "http://b/", "http://c/", "http://d/"],
+ function () {
+ finish_cache2_test();
+ }
+ ),
+ true
+ );
+ }),
+ true
+ );
+ });
+ });
+
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "m1m", "m1d", function () {
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "m1m", "m1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-07a-open-memory.js b/netwerk/test/unit/test_cache2-07a-open-memory.js
new file mode 100644
index 0000000000..3392ee33fc
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-07a-open-memory.js
@@ -0,0 +1,81 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // First check how behaves the memory storage.
+
+ asyncOpenCacheEntry(
+ "http://mem-first/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "mem1-meta", "mem1-data", function (entryM1) {
+ Assert.ok(!entryM1.persistent);
+ asyncOpenCacheEntry(
+ "http://mem-first/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "mem1-meta", "mem1-data", function (entryM2) {
+ Assert.ok(!entryM1.persistent);
+ Assert.ok(!entryM2.persistent);
+
+ // Now check the disk storage behavior.
+
+ asyncOpenCacheEntry(
+ "http://disk-first/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ // Must wait for write, since opening the entry as memory-only before the disk one
+ // is written would cause NS_ERROR_NOT_AVAILABLE from openOutputStream when writing
+ // this disk entry since it's doomed during opening of the memory-only entry for the same URL.
+ new OpenCallback(
+ NEW | WAITFORWRITE,
+ "disk1-meta",
+ "disk1-data",
+ function (entryD1) {
+ Assert.ok(entryD1.persistent);
+ // Now open the same URL as a memory-only entry, the disk entry must be doomed.
+ asyncOpenCacheEntry(
+ "http://disk-first/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ // This must be recreated
+ new OpenCallback(NEW, "mem2-meta", "mem2-data", function (
+ entryD2
+ ) {
+ Assert.ok(entryD1.persistent);
+ Assert.ok(!entryD2.persistent);
+ // Check we get it back, even when opening via the disk storage
+ asyncOpenCacheEntry(
+ "http://disk-first/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(
+ NORMAL,
+ "mem2-meta",
+ "mem2-data",
+ function (entryD3) {
+ Assert.ok(entryD1.persistent);
+ Assert.ok(!entryD2.persistent);
+ Assert.ok(!entryD3.persistent);
+ finish_cache2_test();
+ }
+ )
+ );
+ })
+ );
+ }
+ )
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js b/netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js
new file mode 100644
index 0000000000..395c41dd7c
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js
@@ -0,0 +1,25 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function (entry) {
+ var storage = getCacheStorage("memory");
+ // Have to fail
+ storage.asyncDoomURI(
+ createURI("http://a/"),
+ "",
+ new EvictionCallback(false, function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js b/netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js
new file mode 100644
index 0000000000..5b9caa9cfb
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js
@@ -0,0 +1,32 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ var storage = getCacheStorage("disk");
+ storage.asyncDoomURI(
+ createURI("http://a/"),
+ "",
+ new EvictionCallback(true, function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-10-evict-direct.js b/netwerk/test/unit/test_cache2-10-evict-direct.js
new file mode 100644
index 0000000000..dc278e17d4
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-10-evict-direct.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function () {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function (entry) {
+ entry.asyncDoom(
+ new EvictionCallback(true, function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js b/netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js
new file mode 100644
index 0000000000..83728af20d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | DOOMED, "b1m", "b1d", function (entry) {
+ entry.asyncDoom(
+ new EvictionCallback(true, function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-11-evict-memory.js b/netwerk/test/unit/test_cache2-11-evict-memory.js
new file mode 100644
index 0000000000..b73bf2f5cc
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-11-evict-memory.js
@@ -0,0 +1,89 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var memoryStorage = getCacheStorage("memory");
+ var mc = new MultipleCallbacks(3, function () {
+ memoryStorage.asyncEvictStorage(
+ new EvictionCallback(true, function () {
+ memoryStorage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function () {
+ var diskStorage = getCacheStorage("disk");
+
+ var expectedConsumption = 2048;
+
+ diskStorage.asyncVisitStorage(
+ new VisitCallback(
+ 2,
+ expectedConsumption,
+ ["http://a/", "http://b/"],
+ function () {
+ finish_cache2_test();
+ }
+ ),
+ true
+ );
+ }),
+ true
+ );
+ })
+ );
+ });
+
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "m2m", "m2d", function () {
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "m2m", "m2d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-12-evict-disk.js b/netwerk/test/unit/test_cache2-12-evict-disk.js
new file mode 100644
index 0000000000..6b0d31a27b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-12-evict-disk.js
@@ -0,0 +1,81 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var mc = new MultipleCallbacks(3, function () {
+ var diskStorage = getCacheStorage("disk");
+ diskStorage.asyncEvictStorage(
+ new EvictionCallback(true, function () {
+ diskStorage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function () {
+ var memoryStorage = getCacheStorage("memory");
+ memoryStorage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function () {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ })
+ );
+ });
+
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "m2m", "m2d", function () {
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "m2m", "m2d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function () {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-13-evict-non-existing.js b/netwerk/test/unit/test_cache2-13-evict-non-existing.js
new file mode 100644
index 0000000000..a2d40fd153
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-13-evict-non-existing.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var storage = getCacheStorage("disk");
+ storage.asyncDoomURI(
+ createURI("http://non-existing/"),
+ "",
+ new EvictionCallback(false, function () {
+ finish_cache2_test();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-14-concurent-readers.js b/netwerk/test/unit/test_cache2-14-concurent-readers.js
new file mode 100644
index 0000000000..d2e80582dc
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-14-concurent-readers.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "x1m", "x1d", function (entry) {
+ // nothing to do here, we expect concurent callbacks to get
+ // all notified, then the test finishes
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "x1m", "x1d", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "x1m", "x1d", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "x1m", "x1d", function (entry) {
+ mc.fired();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js b/netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js
new file mode 100644
index 0000000000..244dca9a3b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js
@@ -0,0 +1,76 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "x1m", "x1d", function (entry) {
+ // nothing to do here, we expect concurent callbacks to get
+ // all notified, then the test finishes
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ var order = 0;
+
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(
+ NORMAL | COMPLETE | NOTIFYBEFOREREAD,
+ "x1m",
+ "x1d",
+ function (entry, beforeReading) {
+ if (beforeReading) {
+ ++order;
+ Assert.equal(order, 3);
+ } else {
+ mc.fired();
+ }
+ }
+ )
+ );
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL | NOTIFYBEFOREREAD, "x1m", "x1d", function (
+ entry,
+ beforeReading
+ ) {
+ if (beforeReading) {
+ ++order;
+ Assert.equal(order, 1);
+ } else {
+ mc.fired();
+ }
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://x/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL | NOTIFYBEFOREREAD, "x1m", "x1d", function (
+ entry,
+ beforeReading
+ ) {
+ if (beforeReading) {
+ ++order;
+ Assert.equal(order, 2);
+ } else {
+ mc.fired();
+ }
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-15-conditional-304.js b/netwerk/test/unit/test_cache2-15-conditional-304.js
new file mode 100644
index 0000000000..7b672e69a2
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-15-conditional-304.js
@@ -0,0 +1,60 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://304/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "31m", "31d", function () {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry(
+ "http://304/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(REVAL, "31m", "31d", function (entry) {
+ // emulate 304 from the server
+ executeSoon(function () {
+ entry.setValid(); // this will trigger OpenCallbacks bellow
+ });
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry(
+ "http://304/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "31m", "31d", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://304/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "31m", "31d", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://304/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "31m", "31d", function (entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-16-conditional-200.js b/netwerk/test/unit/test_cache2-16-conditional-200.js
new file mode 100644
index 0000000000..beaa3f0dae
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-16-conditional-200.js
@@ -0,0 +1,76 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "21m", "21d", function () {
+ asyncOpenCacheEntry(
+ "http://200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "21m", "21d", function () {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry(
+ "http://200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(REVAL, "21m", "21d", function (entry) {
+ // emulate 200 from server (new content)
+ executeSoon(function () {
+ var entry2 = entry.recreate();
+
+ // now fill the new entry, use OpenCallback directly for it
+ new OpenCallback(
+ NEW,
+ "22m",
+ "22d",
+ function () {}
+ ).onCacheEntryAvailable(entry2, true, Cr.NS_OK);
+ });
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry(
+ "http://200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "22m", "22d", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "22m", "22d", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "22m", "22d", function (entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-17-evict-all.js b/netwerk/test/unit/test_cache2-17-evict-all.js
new file mode 100644
index 0000000000..83829c631e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-17-evict-all.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ Services.cache2.clear();
+
+ var storage = getCacheStorage("disk");
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function () {
+ finish_cache2_test();
+ }),
+ true
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-18-not-valid.js b/netwerk/test/unit/test_cache2-18-not-valid.js
new file mode 100644
index 0000000000..8cbe37be4a
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-18-not-valid.js
@@ -0,0 +1,38 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write but expect it to fail, since other callback will recreate (and doom)
+ // the first entry before it opens output stream (note: in case of problems the DOOMED flag
+ // can be removed, it is not the test failure when opening the output stream on recreated entry.
+ asyncOpenCacheEntry(
+ "http://nv/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | DOOMED, "v1m", "v1d", function () {
+ // Open for rewrite (don't validate), write different meta and data
+ asyncOpenCacheEntry(
+ "http://nv/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NOTVALID | RECREATE, "v2m", "v2d", function () {
+ // And check...
+ asyncOpenCacheEntry(
+ "http://nv/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "v2m", "v2d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-19-range-206.js b/netwerk/test/unit/test_cache2-19-range-206.js
new file mode 100644
index 0000000000..f3f9491932
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-19-range-206.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://r206/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "206m", "206part1-", function () {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry(
+ "http://r206/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(PARTIAL, "206m", "206part1-", function (entry) {
+ // emulate 206 from the server, i.e. resume transaction and write content to the output stream
+ new OpenCallback(
+ NEW | WAITFORWRITE | PARTIAL,
+ "206m",
+ "-part2",
+ function (entry1) {
+ entry1.setValid();
+ }
+ ).onCacheEntryAvailable(entry, true, Cr.NS_OK);
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry(
+ "http://r206/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "206m", "206part1--part2", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://r206/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "206m", "206part1--part2", function (entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://r206/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "206m", "206part1--part2", function (entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-20-range-200.js b/netwerk/test/unit/test_cache2-20-range-200.js
new file mode 100644
index 0000000000..31b8223c35
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-20-range-200.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://r200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "200m1", "200part1a-", function () {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry(
+ "http://r200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(PARTIAL, "200m1", "200part1a-", function (entry) {
+ // emulate 200 from the server, i.e. recreate the entry, resume transaction and
+ // write new content to the output stream
+ new OpenCallback(
+ NEW | WAITFORWRITE | RECREATE,
+ "200m2",
+ "200part1b--part2b",
+ function (entry1) {
+ entry1.setValid();
+ }
+ ).onCacheEntryAvailable(entry, true, Cr.NS_OK);
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry(
+ "http://r200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "200m2", "200part1b--part2b", function (
+ entry
+ ) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://r200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "200m2", "200part1b--part2b", function (
+ entry
+ ) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry(
+ "http://r200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "200m2", "200part1b--part2b", function (
+ entry
+ ) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-21-anon-storage.js b/netwerk/test/unit/test_cache2-21-anon-storage.js
new file mode 100644
index 0000000000..1db0047475
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-21-anon-storage.js
@@ -0,0 +1,52 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Create and check an entry anon disk storage
+ asyncOpenCacheEntry(
+ "http://anon1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.anonymous,
+ new OpenCallback(NEW, "an1", "an1", function () {
+ asyncOpenCacheEntry(
+ "http://anon1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.anonymous,
+ new OpenCallback(NORMAL, "an1", "an1", function () {
+ // Create and check an entry non-anon disk storage
+ asyncOpenCacheEntry(
+ "http://anon1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NEW, "na1", "na1", function () {
+ asyncOpenCacheEntry(
+ "http://anon1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NORMAL, "na1", "na1", function () {
+ // check the anon entry is still there and intact
+ asyncOpenCacheEntry(
+ "http://anon1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.anonymous,
+ new OpenCallback(NORMAL, "an1", "an1", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-22-anon-visit.js b/netwerk/test/unit/test_cache2-22-anon-visit.js
new file mode 100644
index 0000000000..1768f142bd
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-22-anon-visit.js
@@ -0,0 +1,43 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var mc = new MultipleCallbacks(2, function () {
+ var storage = getCacheStorage("disk", Services.loadContextInfo.default);
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 1024, ["http://an2/"], function () {
+ storage = getCacheStorage("disk", Services.loadContextInfo.anonymous);
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 1024, ["http://an2/"], function () {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ });
+
+ asyncOpenCacheEntry(
+ "http://an2/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "an2", "an2", function (entry) {
+ mc.fired();
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://an2/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.anonymous,
+ new OpenCallback(NEW | WAITFORWRITE, "an2", "an2", function (entry) {
+ mc.fired();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-23-read-over-chunk.js b/netwerk/test/unit/test_cache2-23-read-over-chunk.js
new file mode 100644
index 0000000000..89bdb2d963
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-23-read-over-chunk.js
@@ -0,0 +1,34 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ const kChunkSize = 256 * 1024;
+
+ var payload = "";
+ for (var i = 0; i < kChunkSize + 10; ++i) {
+ if (i < kChunkSize - 5) {
+ payload += "0";
+ } else {
+ payload += String.fromCharCode(i + 65);
+ }
+ }
+
+ asyncOpenCacheEntry(
+ "http://read/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ Services.loadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "", payload, function (entry) {
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function (read) {
+ Assert.equal(read.length, kChunkSize + 10);
+ is.close();
+ Assert.ok(read == payload); // not using do_check_eq since logger will fail for the 1/4MB string
+ finish_cache2_test();
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-24-exists.js b/netwerk/test/unit/test_cache2-24-exists.js
new file mode 100644
index 0000000000..7f7c50e9f0
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-24-exists.js
@@ -0,0 +1,43 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var mc = new MultipleCallbacks(2, function () {
+ var mem = getCacheStorage("memory");
+ var disk = getCacheStorage("disk");
+
+ Assert.ok(disk.exists(createURI("http://m1/"), ""));
+ Assert.ok(mem.exists(createURI("http://m1/"), ""));
+ Assert.ok(!mem.exists(createURI("http://m2/"), ""));
+ Assert.ok(disk.exists(createURI("http://d1/"), ""));
+ do_check_throws_nsIException(
+ () => disk.exists(createURI("http://d2/"), ""),
+ "NS_ERROR_NOT_AVAILABLE"
+ );
+
+ finish_cache2_test();
+ });
+
+ asyncOpenCacheEntry(
+ "http://d1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "meta", "data", function (entry) {
+ mc.fired();
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://m1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "meta", "data", function (entry) {
+ mc.fired();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js b/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
new file mode 100644
index 0000000000..8c5a383ba6
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function gen_200k() {
+ var i;
+ var data = "0123456789ABCDEFGHIJLKMNO";
+ for (i = 0; i < 13; i++) {
+ data += data;
+ }
+ return data;
+}
+
+// Keep the output stream of the first entry in a global variable, so the
+// CacheFile and its buffer isn't released before we write the data to the
+// second entry.
+var oStr;
+
+function run_test() {
+ do_get_profile();
+
+ // set max chunks memory so that only one full chunk fits within the limit
+ Services.prefs.setIntPref("browser.cache.disk.max_chunks_memory_usage", 300);
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var data = gen_200k();
+ oStr = entry.openOutputStream(0, data.length);
+ Assert.equal(data.length, oStr.write(data, data.length));
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (status1, entry1) {
+ Assert.equal(status1, Cr.NS_OK);
+ var oStr2 = entry1.openOutputStream(0, data.length);
+ do_check_throws_nsIException(
+ () => oStr2.write(data, data.length),
+ "NS_ERROR_OUT_OF_MEMORY"
+ );
+ finish_cache2_test();
+ }
+ );
+ }
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-26-no-outputstream-open.js b/netwerk/test/unit/test_cache2-26-no-outputstream-open.js
new file mode 100644
index 0000000000..bdb116436c
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-26-no-outputstream-open.js
@@ -0,0 +1,36 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, but never write and never mark valid
+ asyncOpenCacheEntry(
+ "http://no-data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(
+ NEW | METAONLY | DONTSETVALID | WAITFORWRITE,
+ "meta",
+ "",
+ function () {
+ // Open again, we must get the callback and zero-length data
+ executeSoon(() => {
+ Cu.forceGC(); // invokes OnHandleClosed on the entry
+
+ asyncOpenCacheEntry(
+ "http://no-data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "meta", "", function () {
+ finish_cache2_test();
+ })
+ );
+ });
+ }
+ )
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-27-force-valid-for.js b/netwerk/test/unit/test_cache2-27-force-valid-for.js
new file mode 100644
index 0000000000..78ac7eb847
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-27-force-valid-for.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var mc = new MultipleCallbacks(2, function () {
+ finish_cache2_test();
+ });
+
+ asyncOpenCacheEntry(
+ "http://m1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NEW, "meta", "data", function (entry) {
+ // Check the default
+ equal(entry.isForcedValid, false);
+
+ // Forced valid and confirm
+ entry.forceValidFor(2);
+ do_timeout(1000, function () {
+ equal(entry.isForcedValid, true);
+ mc.fired();
+ });
+
+ // Confirm the timeout occurs
+ do_timeout(3000, function () {
+ equal(entry.isForcedValid, false);
+ mc.fired();
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-28-last-access-attrs.js b/netwerk/test/unit/test_cache2-28-last-access-attrs.js
new file mode 100644
index 0000000000..7450e1998f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-28-last-access-attrs.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+ function NowSeconds() {
+ return parseInt(new Date().getTime() / 1000);
+ }
+ function do_check_time(t, min, max) {
+ Assert.ok(t >= min);
+ Assert.ok(t <= max);
+ }
+
+ var timeStart = NowSeconds();
+
+ asyncOpenCacheEntry(
+ "http://t/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "m", "d", function (entry) {
+ var firstOpen = NowSeconds();
+ Assert.equal(entry.fetchCount, 1);
+ do_check_time(entry.lastFetched, timeStart, firstOpen);
+ do_check_time(entry.lastModified, timeStart, firstOpen);
+
+ do_timeout(2000, () => {
+ asyncOpenCacheEntry(
+ "http://t/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "m", "d", function (entry1) {
+ var secondOpen = NowSeconds();
+ Assert.equal(entry1.fetchCount, 2);
+ do_check_time(entry1.lastFetched, firstOpen, secondOpen);
+ do_check_time(entry1.lastModified, timeStart, firstOpen);
+
+ finish_cache2_test();
+ })
+ );
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js b/netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js
new file mode 100644
index 0000000000..d30e13af22
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js
@@ -0,0 +1,42 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+ function NowSeconds() {
+ return parseInt(new Date().getTime() / 1000);
+ }
+ function do_check_time(a, b) {
+ Assert.ok(Math.abs(a - b) < 0.5);
+ }
+
+ asyncOpenCacheEntry(
+ "http://t/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "m", "d", function (entry) {
+ var now1 = NowSeconds();
+ Assert.equal(entry.fetchCount, 1);
+ do_check_time(entry.lastFetched, now1);
+ do_check_time(entry.lastModified, now1);
+
+ do_timeout(2000, () => {
+ asyncOpenCacheEntry(
+ "http://t/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_SECRETLY,
+ null,
+ new OpenCallback(NORMAL, "m", "d", function (entry1) {
+ Assert.equal(entry1.fetchCount, 1);
+ do_check_time(entry1.lastFetched, now1);
+ do_check_time(entry1.lastModified, now1);
+
+ finish_cache2_test();
+ })
+ );
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js b/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
new file mode 100644
index 0000000000..b97c4b113c
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
@@ -0,0 +1,76 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits
+This test is using a resumable response.
+- with a profile, set max-entry-size to 0
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry, it's doomed
+- second channel now must engage interrupted concurrent write algorithm and read the content again from the network
+- both channels must deliver full content w/o errors
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ if (metadata.hasHeader("If-Range")) {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "0-12/13");
+ }
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 0);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen(new ChannelListener(secondTimeThrough, null));
+ });
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js b/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
new file mode 100644
index 0000000000..e221be7661
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
@@ -0,0 +1,80 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This test is using a non-resumable response.
+- with a profile, set max-entry-size to 0
+- first channel makes a request for a non-resumable (chunked) response
+- second channel makes a request for the same resource, concurrent read is bypassed (non-resumable response)
+- first channel writes first bytes to the cache output stream, but that fails because of the max-entry-size limit and entry is doomed
+- cache entry output stream is closed
+- second channel gets the entry, opening the input stream must fail
+- second channel must read the content again from the network
+- both channels must deliver full content w/o errors
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "c\r\ndata reached\r\n3\r\nhej\r\n0\r\n\r\n";
+const responseBodyDecoded = "data reachedhej";
+
+function contentHandler(metadata, response) {
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(responseBody);
+ response.finish();
+}
+
+function run_test() {
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 0);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen(
+ new ChannelListener(firstTimeThrough, null, CL_ALLOW_UNKNOWN_CL)
+ );
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen(
+ new ChannelListener(secondTimeThrough, null, CL_ALLOW_UNKNOWN_CL)
+ );
+ });
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBodyDecoded);
+}
+
+function secondTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBodyDecoded);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js b/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js
new file mode 100644
index 0000000000..706ad894ca
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js
@@ -0,0 +1,95 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29a test, this test checks that cocurrency is resumed when the first channel is interrupted
+in the middle of reading and the second channel already consumed some content from the cache entry.
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- both channels must deliver full content w/o errors
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ if (metadata.hasHeader("If-Range")) {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+
+ let len = responseBody.length;
+ response.setHeader("Content-Range", "0-" + (len - 1) + "/" + len);
+ }
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ // Static check
+ Assert.ok(responseBody.length > 1024);
+
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen(new ChannelListener(secondTimeThrough, null));
+ });
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js b/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js
new file mode 100644
index 0000000000..d9886a6fbf
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js
@@ -0,0 +1,95 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29c test, this test checks that a corrupted 206 response is correctly handled (no crashes or asserion failures)
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- the response to the range request is broken (bad Content-Range header)
+- the first must deliver full content w/o errors
+- the second channel must correctly fail
+
+*/
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ if (metadata.hasHeader("If-Range")) {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ // Deliberately broken response header to trigger corrupted content error on the second channel
+ response.setHeader("Content-Range", "0-1/2");
+ }
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ // Static check
+ Assert.ok(responseBody.length > 1024);
+
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen(
+ new ChannelListener(secondTimeThrough, null, CL_EXPECT_FAILURE)
+ );
+ });
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer) {
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js b/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js
new file mode 100644
index 0000000000..7d517d518d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js
@@ -0,0 +1,90 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29c test, this test checks that a corrupted 206 response is correctly handled (no crashes or asserion failures)
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- the response to the range request is plain 200
+- the first must deliver full content w/o errors
+- the second channel must correctly fail
+
+*/
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ // Static check
+ Assert.ok(responseBody.length > 1024);
+
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen(
+ new ChannelListener(secondTimeThrough, null, CL_EXPECT_FAILURE)
+ );
+ });
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer) {
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-30a-entry-pinning.js b/netwerk/test/unit/test_cache2-30a-entry-pinning.js
new file mode 100644
index 0000000000..b2541cbf86
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30a-entry-pinning.js
@@ -0,0 +1,39 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry(
+ "http://a/",
+ "pin",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ Services.loadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "a1m", "a1d", function () {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ // Now clear the whole cache
+ Services.cache2.clear();
+
+ // The pinned entry should be intact
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js b/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
new file mode 100644
index 0000000000..21f7f02e45
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var lci = Services.loadContextInfo.default;
+
+ // Open a pinned entry for write, write
+ asyncOpenCacheEntry(
+ "http://a/",
+ "pin",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ lci,
+ new OpenCallback(NEW | WAITFORWRITE, "a1m", "a1d", function () {
+ // Now clear the disk storage, that should leave the pinned entry in the cache
+ var diskStorage = getCacheStorage("disk", lci);
+ diskStorage.asyncEvictStorage(null);
+
+ // Open for read and check, it should still be there
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ // Now clear the pinning storage, entry should be gone
+ var pinningStorage = getCacheStorage("pin", lci);
+ pinningStorage.asyncEvictStorage(null);
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(NEW, "", "", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
new file mode 100644
index 0000000000..73dfee0f6b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
@@ -0,0 +1,185 @@
+/*
+
+This is a complex test checking the internal "deferred doom" functionality in both CacheEntry and CacheFileHandle.
+
+- We create a batch of 10 non-pinned and 10 pinned entries, write something to them.
+- Then we purge them from memory, so they have to reload from disk.
+- After that the IO thread is suspended not to process events on the READ (3) level. This forces opening operation and eviction
+ sync operations happen before we know actual pinning status of already cached entries.
+- We async-open the same batch of the 10+10 entries again, all should open as existing with the expected, previously stored
+ content
+- After all these entries are made to open, we clear the cache. This does some synchronous operations on the entries
+ being open and also on the handles being in an already open state (but before the entry metadata has started to be read.)
+ Expected is to leave the pinned entries only.
+- Now, we resume the IO thread, so it start reading. One could say this is a hack, but this can very well happen in reality
+ on slow disk or when a large number of entries is about to be open at once. Suspending the IO thread is just doing this
+ simulation is a fully deterministic way and actually very easily and elegantly.
+- After the resume we want to open all those 10+10 entries once again (no purgin involved this time.). It is expected
+ to open all the pinning entries intact and loose all the non-pinned entries (get them as new and empty again.)
+
+*/
+
+"use strict";
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) {
+ if (true) {
+ dump(">>>>>>>>>>>>> " + msg + "\n");
+ }
+}
+
+function run_test() {
+ do_get_profile();
+
+ var lci = Services.loadContextInfo.default;
+ var testingInterface = Services.cache2.QueryInterface(Ci.nsICacheTesting);
+ Assert.ok(testingInterface);
+
+ var mc = new MultipleCallbacks(
+ 1,
+ function () {
+ // (2)
+
+ mc = new MultipleCallbacks(1, finish_cache2_test);
+ // Release all references to cache entries so that they can be purged
+ // Calling gc() four times is needed to force it to actually release
+ // entries that are obviously unreferenced. Yeah, I know, this is wacky...
+ gc();
+ gc();
+ executeSoon(() => {
+ gc();
+ gc();
+ log_("purging");
+
+ // Invokes cacheservice:purge-memory-pools when done.
+ Services.cache2.purgeFromMemory(
+ Ci.nsICacheStorageService.PURGE_EVERYTHING
+ ); // goes to (3)
+ });
+ },
+ true
+ );
+
+ // (1), here we start
+
+ var i;
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ log_("first set of opens");
+
+ // Callbacks 1-20
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://pinned" + i + "/",
+ "pin",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ lci,
+ new OpenCallback(NEW | WAITFORWRITE, "m" + i, "p" + i, function (entry) {
+ mc.fired();
+ })
+ );
+
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://common" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ lci,
+ new OpenCallback(NEW | WAITFORWRITE, "m" + i, "d" + i, function (entry) {
+ mc.fired();
+ })
+ );
+ }
+
+ mc.fired(); // Goes to (2)
+
+ Services.obs.addObserver(
+ {
+ observe(subject, topic, data) {
+ // (3)
+
+ log_("after purge, second set of opens");
+ // Prevent the I/O thread from reading the data. We first want to schedule clear of the cache.
+ // This deterministically emulates a slow hard drive.
+ testingInterface.suspendCacheIOThread(3);
+
+ // All entries should load
+ // Callbacks 21-40
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://pinned" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(NORMAL, "m" + i, "p" + i, function (entry) {
+ mc.fired();
+ })
+ );
+
+ // Unfortunately we cannot ensure that entries existing in the cache will be delivered to the consumer
+ // when soon after are evicted by some cache API call. It's better to not ensure getting an entry
+ // than allowing to get an entry that was just evicted from the cache. Entries may be delievered
+ // as new, but are already doomed. Output stream cannot be openned, or the file handle is already
+ // writing to a doomed file.
+ //
+ // The API now just ensures that entries removed by any of the cache eviction APIs are never more
+ // available to consumers.
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://common" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(MAYBE_NEW | DOOMED, "m" + i, "d" + i, function (
+ entry
+ ) {
+ mc.fired();
+ })
+ );
+ }
+
+ log_("clearing");
+ // Now clear everything except pinned, all entries are in state of reading
+ Services.cache2.clear();
+ log_("cleared");
+
+ // Resume reading the cache data, only now the pinning status on entries will be discovered,
+ // the deferred dooming code will trigger.
+ testingInterface.resumeCacheIOThread();
+
+ log_("third set of opens");
+ // Now open again. Pinned entries should be there, disk entries should be the renewed entries.
+ // Callbacks 41-60
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://pinned" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(NORMAL, "m" + i, "p" + i, function (entry) {
+ mc.fired();
+ })
+ );
+
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://common" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(NEW, "m2" + i, "d2" + i, function (entry) {
+ mc.fired();
+ })
+ );
+ }
+
+ mc.fired(); // Finishes this test
+ },
+ },
+ "cacheservice:purge-memory-pools"
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
new file mode 100644
index 0000000000..fd4622f3f4
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
@@ -0,0 +1,148 @@
+/*
+
+This test exercises the CacheFileContextEvictor::WasEvicted API and code using it.
+
+- We store 10+10 (pinned and non-pinned) entries to the cache, wait for them being written.
+- Then we purge the memory pools.
+- Now the IO thread is suspended on the EVICT (7) level to prevent actual deletion of the files.
+- Index is disabled.
+- We do clear() of the cache, this creates the "ce_*" file and posts to the EVICT level
+ the eviction loop mechanics.
+- We open again those 10+10 entries previously stored.
+- IO is resumed
+- We expect to get all the pinned and
+ loose all the non-pinned (common) entries.
+
+*/
+
+"use strict";
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) {
+ if (true) {
+ dump(">>>>>>>>>>>>> " + msg + "\n");
+ }
+}
+
+function run_test() {
+ do_get_profile();
+
+ var lci = Services.loadContextInfo.default;
+ var testingInterface = Services.cache2.QueryInterface(Ci.nsICacheTesting);
+ Assert.ok(testingInterface);
+
+ var mc = new MultipleCallbacks(
+ 1,
+ function () {
+ // (2)
+
+ mc = new MultipleCallbacks(1, finish_cache2_test);
+ // Release all references to cache entries so that they can be purged
+ // Calling gc() four times is needed to force it to actually release
+ // entries that are obviously unreferenced. Yeah, I know, this is wacky...
+ gc();
+ gc();
+ executeSoon(() => {
+ gc();
+ gc();
+ log_("purging");
+
+ // Invokes cacheservice:purge-memory-pools when done.
+ Services.cache2.purgeFromMemory(
+ Ci.nsICacheStorageService.PURGE_EVERYTHING
+ ); // goes to (3)
+ });
+ },
+ true
+ );
+
+ // (1), here we start
+
+ log_("first set of opens");
+ var i;
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ // Callbacks 1-20
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://pinned" + i + "/",
+ "pin",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ lci,
+ new OpenCallback(NEW | WAITFORWRITE, "m" + i, "p" + i, function (entry) {
+ mc.fired();
+ })
+ );
+
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://common" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ lci,
+ new OpenCallback(NEW | WAITFORWRITE, "m" + i, "d" + i, function (entry) {
+ mc.fired();
+ })
+ );
+ }
+
+ mc.fired(); // Goes to (2)
+
+ Services.obs.addObserver(
+ {
+ observe(subject, topic, data) {
+ // (3)
+
+ log_("after purge");
+ // Prevent the I/O thread from evicting physically the data. We first want to re-open the entries.
+ // This deterministically emulates a slow hard drive.
+ testingInterface.suspendCacheIOThread(7);
+
+ log_("clearing");
+ // Now clear everything except pinned. Stores the "ce_*" file and schedules background eviction.
+ Services.cache2.clear();
+ log_("cleared");
+
+ log_("second set of opens");
+ // Now open again. Pinned entries should be there, disk entries should be the renewed entries.
+ // Callbacks 21-40
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://pinned" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(NORMAL, "m" + i, "p" + i, function (entry) {
+ mc.fired();
+ })
+ );
+
+ mc.add();
+ asyncOpenCacheEntry(
+ "http://common" + i + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lci,
+ new OpenCallback(NEW, "m2" + i, "d2" + i, function (entry) {
+ mc.fired();
+ })
+ );
+ }
+
+ // Resume IO, this will just pop-off the CacheFileContextEvictor::EvictEntries() because of
+ // an early check on CacheIOThread::YieldAndRerun() in that method.
+ // CacheFileIOManager::OpenFileInternal should now run and CacheFileContextEvictor::WasEvicted
+ // should be checked on.
+ log_("resuming");
+ testingInterface.resumeCacheIOThread();
+ log_("resumed");
+
+ mc.fired(); // Finishes this test
+ },
+ },
+ "cacheservice:purge-memory-pools"
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-31-visit-all.js b/netwerk/test/unit/test_cache2-31-visit-all.js
new file mode 100644
index 0000000000..3fed10881f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-31-visit-all.js
@@ -0,0 +1,88 @@
+"use strict";
+
+function run_test() {
+ getCacheStorage("disk");
+ var lcis = [
+ Services.loadContextInfo.default,
+ Services.loadContextInfo.custom(false, { userContextId: 1 }),
+ Services.loadContextInfo.custom(false, { userContextId: 2 }),
+ Services.loadContextInfo.custom(false, { userContextId: 3 }),
+ ];
+
+ do_get_profile();
+
+ var mc = new MultipleCallbacks(
+ 8,
+ function () {
+ executeSoon(function () {
+ var expectedConsumption = 8192;
+ var entries = [
+ { uri: "http://a/", lci: lcis[0] }, // default
+ { uri: "http://b/", lci: lcis[0] }, // default
+ { uri: "http://a/", lci: lcis[1] }, // user Context 1
+ { uri: "http://b/", lci: lcis[1] }, // user Context 1
+ { uri: "http://a/", lci: lcis[2] }, // user Context 2
+ { uri: "http://b/", lci: lcis[2] }, // user Context 2
+ { uri: "http://a/", lci: lcis[3] }, // user Context 3
+ { uri: "http://b/", lci: lcis[3] },
+ ]; // user Context 3
+
+ Services.cache2.asyncVisitAllStorages(
+ // Test should store 8 entries across 4 originAttributes
+ new VisitCallback(8, expectedConsumption, entries, function () {
+ Services.cache2.asyncVisitAllStorages(
+ // Still 8 entries expected, now don't walk them
+ new VisitCallback(8, expectedConsumption, null, function () {
+ finish_cache2_test();
+ }),
+ false
+ );
+ }),
+ true
+ );
+ });
+ },
+ true
+ );
+
+ // Add two cache entries for each originAttributes.
+ for (var i = 0; i < lcis.length; i++) {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lcis[i],
+ new OpenCallback(NEW, "a1m", "a1d", function () {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lcis[i],
+ new OpenCallback(NORMAL, "a1m", "a1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lcis[i],
+ new OpenCallback(NEW, "b1m", "b1d", function () {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lcis[i],
+ new OpenCallback(NORMAL, "b1m", "b1d", function () {
+ mc.fired();
+ })
+ );
+ })
+ );
+ }
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-32-clear-origin.js b/netwerk/test/unit/test_cache2-32-clear-origin.js
new file mode 100644
index 0000000000..2234ccd13d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-32-clear-origin.js
@@ -0,0 +1,69 @@
+"use strict";
+
+const URL = "http://example.net";
+const URL2 = "http://foo.bar";
+
+function run_test() {
+ do_get_profile();
+
+ asyncOpenCacheEntry(
+ URL + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "e1m", "e1d", function () {
+ asyncOpenCacheEntry(
+ URL + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "e1m", "e1d", function () {
+ asyncOpenCacheEntry(
+ URL2 + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "f1m", "f1d", function () {
+ asyncOpenCacheEntry(
+ URL2 + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "f1m", "f1d", function () {
+ var url = Services.io.newURI(URL);
+ var principal =
+ Services.scriptSecurityManager.createContentPrincipal(
+ url,
+ {}
+ );
+
+ Services.cache2.clearOrigin(principal);
+
+ asyncOpenCacheEntry(
+ URL + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "e1m", "e1d", function () {
+ asyncOpenCacheEntry(
+ URL2 + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "f1m", "f1d", function () {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache_204_response.js b/netwerk/test/unit/test_cache_204_response.js
new file mode 100644
index 0000000000..3038b7ed71
--- /dev/null
+++ b/netwerk/test/unit/test_cache_204_response.js
@@ -0,0 +1,62 @@
+/* -*- 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/. */
+
+/*
+Test if 204 response is cached.
+1. Make first http request and return a 204 response.
+2. Check if the first response is not cached.
+3. Make second http request and check if the response is cached.
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-control", "max-age=9999", false);
+ response.setStatusLine(metadata.httpVersion, 204, "No Content");
+}
+
+function make_channel(url) {
+ let channel = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return channel;
+}
+
+async function get_response(channel, fromCache) {
+ return new Promise(resolve => {
+ channel.asyncOpen(
+ new ChannelListener((request, buffer, ctx, isFromCache) => {
+ ok(fromCache == isFromCache, `got response from cache = ${fromCache}`);
+ resolve();
+ })
+ );
+ });
+}
+
+async function stop_server(httpserver) {
+ return new Promise(resolve => {
+ httpserver.stop(resolve);
+ });
+}
+
+add_task(async function () {
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/testdir", test_handler);
+ httpserver.start(-1);
+ const PORT = httpserver.identity.primaryPort;
+ const URI = `http://localhost:${PORT}/testdir`;
+
+ await get_response(make_channel(URI, "GET"), false);
+ await get_response(make_channel(URI, "GET"), true);
+
+ await stop_server(httpserver);
+});
diff --git a/netwerk/test/unit/test_cache_jar.js b/netwerk/test/unit/test_cache_jar.js
new file mode 100644
index 0000000000..bedde668b4
--- /dev/null
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -0,0 +1,105 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort + "/cached";
+});
+
+var httpserv = null;
+var handlers_called = 0;
+
+function cached_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ var body = "0123456789";
+ response.bodyOutputStream.write(body, body.length);
+ handlers_called++;
+}
+
+function makeChan(url, inIsolatedMozBrowser, userContextId) {
+ var chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadInfo.originAttributes = { inIsolatedMozBrowser, userContextId };
+ return chan;
+}
+
+// [inIsolatedMozBrowser, userContextId, expected_handlers_called]
+var firstTests = [
+ [false, 0, 1],
+ [true, 0, 1],
+ [false, 1, 1],
+ [true, 1, 1],
+];
+var secondTests = [
+ [false, 0, 0],
+ [true, 0, 0],
+ [false, 1, 1],
+ [true, 1, 0],
+];
+
+async function run_all_tests() {
+ for (let test of firstTests) {
+ handlers_called = 0;
+ await test_channel(...test);
+ }
+
+ // We can't easily cause webapp data to be cleared from the child process, so skip
+ // the rest of these tests.
+ let procType = Services.appinfo.processType;
+ if (procType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ return;
+ }
+
+ Services.clearData.deleteDataFromOriginAttributesPattern({
+ userContextId: 1,
+ });
+
+ for (let test of secondTests) {
+ handlers_called = 0;
+ await test_channel(...test);
+ }
+}
+
+function run_test() {
+ do_get_profile();
+
+ do_test_pending();
+
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/cached", cached_handler);
+ httpserv.start(-1);
+ run_all_tests().then(() => {
+ do_test_finished();
+ });
+}
+
+function test_channel(inIsolatedMozBrowser, userContextId, expected) {
+ return new Promise(resolve => {
+ var chan = makeChan(URL, inIsolatedMozBrowser, userContextId);
+ chan.asyncOpen(
+ new ChannelListener(doneFirstLoad.bind(null, resolve), expected)
+ );
+ });
+}
+
+function doneFirstLoad(resolve, req, buffer, expected) {
+ // Load it again, make sure it hits the cache
+ var oa = req.loadInfo.originAttributes;
+ var chan = makeChan(URL, oa.isInIsolatedMozBrowserElement, oa.userContextId);
+ chan.asyncOpen(
+ new ChannelListener(doneSecondLoad.bind(null, resolve), expected)
+ );
+}
+
+function doneSecondLoad(resolve, req, buffer, expected) {
+ Assert.equal(handlers_called, expected);
+ resolve();
+}
diff --git a/netwerk/test/unit/test_cacheflags.js b/netwerk/test/unit/test_cacheflags.js
new file mode 100644
index 0000000000..614f420a5e
--- /dev/null
+++ b/netwerk/test/unit/test_cacheflags.js
@@ -0,0 +1,437 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+
+// Need to randomize, because apparently no one clears our cache
+var suffix = Math.random();
+var httpBase = "http://localhost:" + httpserver.identity.primaryPort;
+var shortexpPath = "/shortexp" + suffix;
+var longexpPath = "/longexp/" + suffix;
+var longexp2Path = "/longexp/2/" + suffix;
+var nocachePath = "/nocache" + suffix;
+var nostorePath = "/nostore" + suffix;
+var test410Path = "/test410" + suffix;
+var test404Path = "/test404" + suffix;
+
+var PrivateBrowsingLoadContext = Cu.createPrivateLoadContext();
+
+function make_channel(url, flags, usePrivateBrowsing) {
+ var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+
+ var uri = Services.io.newURI(url);
+ var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+ privateBrowsingId: usePrivateBrowsing ? 1 : 0,
+ });
+
+ var req = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ req.loadFlags = flags;
+ if (usePrivateBrowsing) {
+ req.notificationCallbacks = PrivateBrowsingLoadContext;
+ }
+ return req;
+}
+
+function Test(
+ path,
+ flags,
+ expectSuccess,
+ readFromCache,
+ hitServer,
+ usePrivateBrowsing /* defaults to false */
+) {
+ this.path = path;
+ this.flags = flags;
+ this.expectSuccess = expectSuccess;
+ this.readFromCache = readFromCache;
+ this.hitServer = hitServer;
+ this.usePrivateBrowsing = usePrivateBrowsing;
+}
+
+Test.prototype = {
+ flags: 0,
+ expectSuccess: true,
+ readFromCache: false,
+ hitServer: true,
+ usePrivateBrowsing: false,
+ _buffer: "",
+ _isFromCache: false,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ var cachingChannel = request.QueryInterface(Ci.nsICacheInfoChannel);
+ this._isFromCache = request.isPending() && cachingChannel.isFromCache();
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(Components.isSuccessCode(status), this.expectSuccess);
+ Assert.equal(this._isFromCache, this.readFromCache);
+ Assert.equal(gHitServer, this.hitServer);
+
+ do_timeout(0, run_next_test);
+ },
+
+ run() {
+ dump(
+ "Running:" +
+ "\n " +
+ this.path +
+ "\n " +
+ this.flags +
+ "\n " +
+ this.expectSuccess +
+ "\n " +
+ this.readFromCache +
+ "\n " +
+ this.hitServer +
+ "\n"
+ );
+ gHitServer = false;
+ var channel = make_channel(this.path, this.flags, this.usePrivateBrowsing);
+ channel.asyncOpen(this);
+ },
+};
+
+var gHitServer = false;
+
+var gTests = [
+ new Test(
+ httpBase + shortexpPath,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true, // hit server
+ true
+ ), // USE PRIVATE BROWSING, so not cached for later requests
+ new Test(
+ httpBase + shortexpPath,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + shortexpPath,
+ 0,
+ true, // expect success
+ true, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + shortexpPath,
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + shortexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ false, // expect success
+ false, // read from cache
+ false
+ ), // hit server
+ new Test(
+ httpBase + shortexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+ new Test(
+ httpBase + shortexpPath,
+ Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+
+ new Test(
+ httpBase + longexpPath,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + longexpPath,
+ 0,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+ new Test(
+ httpBase + longexpPath,
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + longexpPath,
+ Ci.nsIRequest.VALIDATE_ALWAYS,
+ true, // expect success
+ true, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + longexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+ new Test(
+ httpBase + longexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+ new Test(
+ httpBase + longexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_ALWAYS,
+ false, // expect success
+ false, // read from cache
+ false
+ ), // hit server
+ new Test(
+ httpBase + longexpPath,
+ Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+
+ new Test(
+ httpBase + longexp2Path,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + longexp2Path,
+ 0,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+
+ new Test(
+ httpBase + nocachePath,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + nocachePath,
+ 0,
+ true, // expect success
+ true, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + nocachePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ false, // expect success
+ false, // read from cache
+ false
+ ), // hit server
+
+ // CACHE2: mayhemer - entry is doomed... I think the logic is wrong, we should not doom them
+ // as they are not valid, but take them as they need to reval
+ /*
+ new Test(httpBase + nocachePath, Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+ */
+
+ // LOAD_ONLY_FROM_CACHE would normally fail (because no-cache forces
+ // a validation), but VALIDATE_NEVER should override that.
+ new Test(
+ httpBase + nocachePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+
+ // ... however, no-cache over ssl should act like no-store and force
+ // a validation (and therefore failure) even if VALIDATE_NEVER is
+ // set.
+ /* XXX bug 466524: We can't currently start an ssl server in xpcshell tests,
+ so this test is currently disabled.
+ new Test(httpsBase + nocachePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsIRequest.VALIDATE_NEVER,
+ false, // expect success
+ false, // read from cache
+ false) // hit server
+ */
+
+ new Test(
+ httpBase + nostorePath,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + nostorePath,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + nostorePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ false, // expect success
+ false, // read from cache
+ false
+ ), // hit server
+ new Test(
+ httpBase + nostorePath,
+ Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+ // no-store should force the validation (and therefore failure, with
+ // LOAD_ONLY_FROM_CACHE) even if VALIDATE_NEVER is set.
+ new Test(
+ httpBase + nostorePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
+ false, // expect success
+ false, // read from cache
+ false
+ ), // hit server
+
+ new Test(
+ httpBase + test410Path,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + test410Path,
+ 0,
+ true, // expect success
+ true, // read from cache
+ false
+ ), // hit server
+
+ new Test(
+ httpBase + test404Path,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+ new Test(
+ httpBase + test404Path,
+ 0,
+ true, // expect success
+ false, // read from cache
+ true
+ ), // hit server
+];
+
+function run_next_test() {
+ if (!gTests.length) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+
+ var test = gTests.shift();
+ test.run();
+}
+
+function handler(httpStatus, metadata, response) {
+ gHitServer = true;
+ let etag;
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+ if (etag == "testtag") {
+ // Allow using the cached data
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ } else {
+ response.setStatusLine(metadata.httpVersion, httpStatus, "Useless Phrase");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "testtag", false);
+ const body = "data";
+ response.bodyOutputStream.write(body, body.length);
+ }
+}
+
+function nocache_handler(metadata, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ handler(200, metadata, response);
+}
+
+function nostore_handler(metadata, response) {
+ response.setHeader("Cache-Control", "no-store", false);
+ handler(200, metadata, response);
+}
+
+function test410_handler(metadata, response) {
+ handler(410, metadata, response);
+}
+
+function test404_handler(metadata, response) {
+ handler(404, metadata, response);
+}
+
+function shortexp_handler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age=0", false);
+ handler(200, metadata, response);
+}
+
+function longexp_handler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ handler(200, metadata, response);
+}
+
+// test spaces around max-age value token
+function longexp2_handler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age = 10000", false);
+ handler(200, metadata, response);
+}
+
+function run_test() {
+ httpserver.registerPathHandler(shortexpPath, shortexp_handler);
+ httpserver.registerPathHandler(longexpPath, longexp_handler);
+ httpserver.registerPathHandler(longexp2Path, longexp2_handler);
+ httpserver.registerPathHandler(nocachePath, nocache_handler);
+ httpserver.registerPathHandler(nostorePath, nostore_handler);
+ httpserver.registerPathHandler(test410Path, test410_handler);
+ httpserver.registerPathHandler(test404Path, test404_handler);
+
+ run_next_test();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_captive_portal_service.js b/netwerk/test/unit/test_captive_portal_service.js
new file mode 100644
index 0000000000..d5c951d16c
--- /dev/null
+++ b/netwerk/test/unit/test_captive_portal_service.js
@@ -0,0 +1,325 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let httpserver = null;
+ChromeUtils.defineLazyGetter(this, "cpURI", function () {
+ return (
+ "http://localhost:" + httpserver.identity.primaryPort + "/captive.html"
+ );
+});
+
+const SUCCESS_STRING =
+ '<meta http-equiv="refresh" content="0;url=https://support.mozilla.org/kb/captive-portal"/>';
+let cpResponse = SUCCESS_STRING;
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(cpResponse, cpResponse.length);
+}
+
+const PREF_CAPTIVE_ENABLED = "network.captive-portal-service.enabled";
+const PREF_CAPTIVE_TESTMODE = "network.captive-portal-service.testMode";
+const PREF_CAPTIVE_ENDPOINT = "captivedetect.canonicalURL";
+const PREF_CAPTIVE_MINTIME = "network.captive-portal-service.minInterval";
+const PREF_CAPTIVE_MAXTIME = "network.captive-portal-service.maxInterval";
+const PREF_DNS_NATIVE_IS_LOCALHOST = "network.dns.native-is-localhost";
+
+const cps = Cc["@mozilla.org/network/captive-portal-service;1"].getService(
+ Ci.nsICaptivePortalService
+);
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref(PREF_CAPTIVE_ENABLED);
+ Services.prefs.clearUserPref(PREF_CAPTIVE_TESTMODE);
+ Services.prefs.clearUserPref(PREF_CAPTIVE_ENDPOINT);
+ Services.prefs.clearUserPref(PREF_CAPTIVE_MINTIME);
+ Services.prefs.clearUserPref(PREF_CAPTIVE_MAXTIME);
+ Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST);
+
+ await new Promise(resolve => {
+ httpserver.stop(resolve);
+ });
+});
+
+function observerPromise(topic) {
+ return new Promise(resolve => {
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == topic) {
+ Services.obs.removeObserver(observer, topic);
+ resolve(aData);
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ });
+}
+
+add_task(function setup() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/captive.html", contentHandler);
+ httpserver.start(-1);
+
+ Services.prefs.setCharPref(PREF_CAPTIVE_ENDPOINT, cpURI);
+ Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 50);
+ Services.prefs.setIntPref(PREF_CAPTIVE_MAXTIME, 100);
+ Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE, true);
+ Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST, true);
+});
+
+add_task(async function test_simple() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ let notification = observerPromise("network:captive-portal-connectivity");
+ // The service is started by nsIOService when the pref becomes true.
+ // We might want to add a method to do this in the future.
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+
+ let observerPayload = await notification;
+ equal(observerPayload, "clear");
+ equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
+
+ cpResponse = "other";
+ notification = observerPromise("captive-portal-login");
+ cps.recheckCaptivePortal();
+ await notification;
+ equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
+
+ cpResponse = SUCCESS_STRING;
+ notification = observerPromise("captive-portal-login-success");
+ cps.recheckCaptivePortal();
+ await notification;
+ equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL);
+});
+
+// This test redirects to another URL which returns the same content.
+// It should still be interpreted as a captive portal.
+add_task(async function test_redirect_success() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ httpserver.registerPathHandler("/succ.txt", (metadata, response) => {
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(cpResponse, cpResponse.length);
+ });
+ httpserver.registerPathHandler("/captive.html", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
+ response.setHeader(
+ "Location",
+ `http://localhost:${httpserver.identity.primaryPort}/succ.txt`
+ );
+ });
+
+ let notification = observerPromise("captive-portal-login").then(
+ () => "login"
+ );
+ let succNotif = observerPromise("network:captive-portal-connectivity").then(
+ () => "connectivity"
+ );
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+
+ let winner = await Promise.race([notification, succNotif]);
+ equal(winner, "login", "This should have been a login, not a success");
+ equal(
+ cps.state,
+ Ci.nsICaptivePortalService.LOCKED_PORTAL,
+ "Should be locked after redirect to same text"
+ );
+});
+
+// This redirects to another URI with a different content.
+// We check that it triggers a captive portal login
+add_task(async function test_redirect_bad() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ httpserver.registerPathHandler("/bad.txt", (metadata, response) => {
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write("bad", "bad".length);
+ });
+
+ httpserver.registerPathHandler("/captive.html", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
+ response.setHeader(
+ "Location",
+ `http://localhost:${httpserver.identity.primaryPort}/bad.txt`
+ );
+ });
+
+ let notification = observerPromise("captive-portal-login");
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+
+ await notification;
+ equal(
+ cps.state,
+ Ci.nsICaptivePortalService.LOCKED_PORTAL,
+ "Should be locked after redirect to bad text"
+ );
+});
+
+// This redirects to the same URI.
+// We check that it triggers a captive portal login
+add_task(async function test_redirect_loop() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ // This is actually a redirect loop
+ httpserver.registerPathHandler("/captive.html", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
+ response.setHeader("Location", cpURI);
+ });
+
+ let notification = observerPromise("captive-portal-login");
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+
+ await notification;
+ equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
+});
+
+// This redirects to a https URI.
+// We check that it triggers a captive portal login
+add_task(async function test_redirect_https() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Any kind of redirection should trigger the captive portal login.
+ httpserver.registerPathHandler("/captive.html", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
+ response.setHeader("Location", `https://foo.example.com:${h2Port}/exit`);
+ });
+
+ let notification = observerPromise("captive-portal-login");
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+
+ await notification;
+ equal(
+ cps.state,
+ Ci.nsICaptivePortalService.LOCKED_PORTAL,
+ "Should be locked after redirect to https"
+ );
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+});
+
+// This test uses a 511 status code to request a captive portal login
+// We check that it triggers a captive portal login
+// See RFC 6585 for details
+add_task(async function test_511_error() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ httpserver.registerPathHandler("/captive.html", (metadata, response) => {
+ response.setStatusLine(
+ metadata.httpVersion,
+ 511,
+ "Network Authentication Required"
+ );
+ cpResponse = '<meta http-equiv="refresh" content="0;url=/login">';
+ contentHandler(metadata, response);
+ });
+
+ let notification = observerPromise("captive-portal-login");
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+
+ await notification;
+ equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
+});
+
+// Any other 5xx HTTP error, is assumed to be an issue with the
+// canonical web server, and should not trigger a captive portal login
+add_task(async function test_generic_5xx_error() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ let requests = 0;
+ httpserver.registerPathHandler("/captive.html", (metadata, response) => {
+ if (requests++ === 0) {
+ // on first attempt, send 503 error
+ response.setStatusLine(
+ metadata.httpVersion,
+ 503,
+ "Internal Server Error"
+ );
+ cpResponse = "<h1>Internal Server Error</h1>";
+ } else {
+ // on retry, send canonical reply
+ cpResponse = SUCCESS_STRING;
+ }
+ contentHandler(metadata, response);
+ });
+
+ let notification = observerPromise("network:captive-portal-connectivity");
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+
+ await notification;
+ equal(requests, 2);
+ equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
+});
+
+add_task(async function test_changed_notification() {
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
+ equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
+
+ httpserver.registerPathHandler("/captive.html", contentHandler);
+ cpResponse = SUCCESS_STRING;
+
+ let changedNotificationCount = 0;
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ changedNotificationCount += 1;
+ },
+ };
+ Services.obs.addObserver(
+ observer,
+ "network:captive-portal-connectivity-changed"
+ );
+
+ let notification = observerPromise(
+ "network:captive-portal-connectivity-changed"
+ );
+ Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
+ await notification;
+ equal(changedNotificationCount, 1);
+ equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
+
+ notification = observerPromise("network:captive-portal-connectivity");
+ cps.recheckCaptivePortal();
+ await notification;
+ equal(changedNotificationCount, 1);
+ equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
+
+ notification = observerPromise("captive-portal-login");
+ cpResponse = "you are captive";
+ cps.recheckCaptivePortal();
+ await notification;
+ equal(changedNotificationCount, 1);
+ equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
+
+ notification = observerPromise("captive-portal-login-success");
+ cpResponse = SUCCESS_STRING;
+ cps.recheckCaptivePortal();
+ await notification;
+ equal(changedNotificationCount, 2);
+ equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL);
+
+ notification = observerPromise("captive-portal-login");
+ cpResponse = "you are captive";
+ cps.recheckCaptivePortal();
+ await notification;
+ equal(changedNotificationCount, 2);
+ equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
+
+ Services.obs.removeObserver(
+ observer,
+ "network:captive-portal-connectivity-changed"
+ );
+});
diff --git a/netwerk/test/unit/test_cert_info.js b/netwerk/test/unit/test_cert_info.js
new file mode 100644
index 0000000000..abf5a1d634
--- /dev/null
+++ b/netwerk/test/unit/test_cert_info.js
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+async function test_cert_failure(server_creator, https_proxy) {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let server = new server_creator();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("done");
+ });
+ let url;
+ if (server_creator == NodeHTTPServer) {
+ url = `http://localhost:${server.port()}/test`;
+ } else {
+ url = `https://localhost:${server.port()}/test`;
+ }
+ let chan = makeChan(url);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ let secinfo = req.securityInfo;
+ if (server_creator == NodeHTTPServer) {
+ if (!https_proxy) {
+ Assert.equal(secinfo, null);
+ } else {
+ // In the case we are connecting to an insecure HTTP server
+ // through a secure proxy, nsHttpChannel will have the security
+ // info from the proxy.
+ // We will discuss this behavir in bug 1785777.
+ secinfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ Assert.equal(secinfo.serverCert.commonName, " Proxy Test Cert");
+ }
+ } else {
+ secinfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ Assert.equal(secinfo.serverCert.commonName, " HTTP2 Test Cert");
+ }
+}
+
+add_task(async function test_http() {
+ await test_cert_failure(NodeHTTPServer, false);
+});
+
+add_task(async function test_https() {
+ await test_cert_failure(NodeHTTPSServer, false);
+});
+
+add_task(async function test_http2() {
+ await test_cert_failure(NodeHTTP2Server, false);
+});
+
+add_task(async function test_http_proxy_http_server() {
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+ await test_cert_failure(NodeHTTPServer, false);
+});
+
+add_task(async function test_http_proxy_https_server() {
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+ await test_cert_failure(NodeHTTPSServer, false);
+});
+
+add_task(async function test_http_proxy_http2_server() {
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+ await test_cert_failure(NodeHTTP2Server, false);
+});
+
+add_task(async function test_https_proxy_http_server() {
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+ await test_cert_failure(NodeHTTPServer, true);
+});
+
+add_task(async function test_https_proxy_https_server() {
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+ await test_cert_failure(NodeHTTPSServer, true);
+});
+
+add_task(async function test_https_proxy_http2_server() {
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+ await test_cert_failure(NodeHTTP2Server, true);
+});
+
+add_task(async function test_http2_proxy_http_server() {
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+
+ await test_cert_failure(NodeHTTPServer, true);
+});
+
+add_task(async function test_http2_proxy_https_server() {
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+
+ await test_cert_failure(NodeHTTPSServer, true);
+});
+
+add_task(async function test_http2_proxy_http2_server() {
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+
+ await test_cert_failure(NodeHTTP2Server, true);
+});
diff --git a/netwerk/test/unit/test_cert_verification_failure.js b/netwerk/test/unit/test_cert_verification_failure.js
new file mode 100644
index 0000000000..11eb575dc1
--- /dev/null
+++ b/netwerk/test/unit/test_cert_verification_failure.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+async function test_cert_failure(server_or_proxy, server_cert) {
+ let server = new server_or_proxy();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ let chan = makeChan(`https://localhost:${server.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ });
+ equal(req.status, 0x805a1ff3); // SEC_ERROR_UNKNOWN_ISSUER
+ let secinfo = req.securityInfo;
+ secinfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ if (server_cert) {
+ Assert.equal(secinfo.serverCert.commonName, " HTTP2 Test Cert");
+ } else {
+ Assert.equal(secinfo.serverCert.commonName, " Proxy Test Cert");
+ }
+}
+
+add_task(async function test_https() {
+ await test_cert_failure(NodeHTTPSServer, true);
+});
+
+add_task(async function test_http2() {
+ await test_cert_failure(NodeHTTP2Server, true);
+});
+
+add_task(async function test_https_proxy() {
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+ await test_cert_failure(NodeHTTPSServer, false);
+});
+
+add_task(async function test_http2_proxy() {
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(() => {
+ proxy.stop();
+ });
+
+ await test_cert_failure(NodeHTTPSServer, false);
+});
diff --git a/netwerk/test/unit/test_channel_close.js b/netwerk/test/unit/test_channel_close.js
new file mode 100644
index 0000000000..f8f78fd211
--- /dev/null
+++ b/netwerk/test/unit/test_channel_close.js
@@ -0,0 +1,70 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+var live_channels = [];
+
+function run_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var local_channel;
+
+ // Opened channel that has no remaining references on shutdown
+ local_channel = setupChannel(testpath);
+ local_channel.asyncOpen(new ChannelListener(checkRequest, local_channel));
+
+ // Opened channel that has no remaining references after being opened
+ setupChannel(testpath).asyncOpen(new ChannelListener(function () {}, null));
+
+ // Unopened channel that has remaining references on shutdown
+ live_channels.push(setupChannel(testpath));
+
+ // Opened channel that has remaining references on shutdown
+ live_channels.push(setupChannel(testpath));
+ live_channels[1].asyncOpen(
+ new ChannelListener(checkRequestFinish, live_channels[1])
+ );
+ });
+
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function checkRequest(request, data, context) {
+ Assert.equal(data, httpbody);
+}
+
+function checkRequestFinish(request, data, context) {
+ checkRequest(request, data, context);
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_channel_long_domain.js b/netwerk/test/unit/test_channel_long_domain.js
new file mode 100644
index 0000000000..86dbbed3e3
--- /dev/null
+++ b/netwerk/test/unit/test_channel_long_domain.js
@@ -0,0 +1,14 @@
+// Tests that domains longer than 253 characters fail to load when pref is true
+
+add_task(async function test_long_domain_fails() {
+ let domain = "http://" + "a".repeat(254);
+
+ let req = await new Promise(resolve => {
+ let chan = NetUtil.newChannel({
+ uri: domain,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ });
+ Assert.equal(req.status, Cr.NS_ERROR_UNKNOWN_HOST, "Request should fail");
+});
diff --git a/netwerk/test/unit/test_channel_priority.js b/netwerk/test/unit/test_channel_priority.js
new file mode 100644
index 0000000000..b230042890
--- /dev/null
+++ b/netwerk/test/unit/test_channel_priority.js
@@ -0,0 +1,98 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let httpserver;
+let port;
+
+function startHttpServer() {
+ httpserver = new HttpServer();
+
+ httpserver.registerPathHandler("/resource", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.bodyOutputStream.write("data", 4);
+ });
+
+ httpserver.registerPathHandler("/redirect", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 302, "Redirect");
+ response.setHeader("Location", "/resource", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ });
+
+ httpserver.start(-1);
+ port = httpserver.identity.primaryPort;
+}
+
+function stopHttpServer() {
+ httpserver.stop(() => {});
+}
+
+function makeRequest(uri) {
+ let requestChannel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+ requestChannel.QueryInterface(Ci.nsISupportsPriority);
+ requestChannel.priority = Ci.nsISupportsPriority.PRIORITY_HIGHEST;
+ requestChannel.asyncOpen(new ChannelListener(checkResponse, requestChannel));
+}
+
+function checkResponse(request, buffer, requestChannel) {
+ requestChannel.QueryInterface(Ci.nsISupportsPriority);
+ Assert.equal(
+ requestChannel.priority,
+ Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ );
+
+ // the response channel can be different (if it was redirected)
+ let responseChannel = request.QueryInterface(Ci.nsISupportsPriority);
+ Assert.equal(
+ responseChannel.priority,
+ Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ );
+
+ run_next_test();
+}
+
+add_test(function test_regular_request() {
+ makeRequest(`http://localhost:${port}/resource`);
+});
+
+add_test(function test_redirect() {
+ makeRequest(`http://localhost:${port}/redirect`);
+});
+
+function run_test() {
+ // jshint ignore:line
+ if (!runningInParent) {
+ // add a task to report test finished to parent process at the end of test queue,
+ // since do_register_cleanup is not available in child xpcshell test script.
+ add_test(function () {
+ do_send_remote_message("finished");
+ run_next_test();
+ });
+
+ // waiting for parent process to assign server port via configPort()
+ return;
+ }
+
+ startHttpServer();
+ registerCleanupFunction(stopHttpServer);
+ run_next_test();
+}
+
+// This is used by unit_ipc/test_channel_priority_wrap.js for e10s XPCShell test
+/* exported configPort */
+function configPort(serverPort) {
+ // jshint ignore:line
+ port = serverPort;
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_chunked_responses.js b/netwerk/test/unit/test_chunked_responses.js
new file mode 100644
index 0000000000..c8dee14efe
--- /dev/null
+++ b/netwerk/test/unit/test_chunked_responses.js
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test Chunked-Encoded response parsing.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var test_flags = [];
+var testPathBase = "/chunked_hdrs";
+
+function run_test() {
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num) {
+ var testPath = testPathBase + num;
+ // eslint-disable-next-line no-eval
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ var flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen(
+ // eslint-disable-next-line no-eval
+ new ChannelListener(eval("completeTest" + num), channel, flags)
+ );
+}
+
+function setupChannel(url) {
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests() {
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: FAIL because of overflowed chunked size. The parser uses long so
+// the test case uses >64bit to fail on all platforms.
+test_flags[1] = CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler1(metadata, response) {
+ var body = "12345678123456789\r\ndata never reached";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest1(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_UNEXPECTED);
+
+ run_test_number(2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: FAIL because of non-hex in chunked length
+
+test_flags[2] = CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler2(metadata, response) {
+ var body = "junkintheway 123\r\ndata never reached";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest2(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_UNEXPECTED);
+ run_test_number(3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: OK in spite of non-hex digits after size in the length field
+
+test_flags[3] = CL_ALLOW_UNKNOWN_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler3(metadata, response) {
+ var body = "c junkafter\r\ndata reached\r\n0\r\n\r\n";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest3(request, data, ctx) {
+ Assert.equal(request.status, 0);
+ run_test_number(4);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: Verify a fully compliant chunked response.
+
+test_flags[4] = CL_ALLOW_UNKNOWN_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler4(metadata, response) {
+ var body = "c\r\ndata reached\r\n3\r\nhej\r\n0\r\n\r\n";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest4(request, data, ctx) {
+ Assert.equal(request.status, 0);
+ run_test_number(5);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 5: A chunk size larger than 32 bit but smaller than 64bit also fails
+// This is probabaly subject to get improved at some point.
+
+test_flags[5] = CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler5(metadata, response) {
+ var body = "123456781\r\ndata never reached";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest5(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_UNEXPECTED);
+ endTests();
+ // run_test_number(6);
+}
diff --git a/netwerk/test/unit/test_client_auth_with_proxy.js b/netwerk/test/unit/test_client_auth_with_proxy.js
new file mode 100644
index 0000000000..5a205f4db1
--- /dev/null
+++ b/netwerk/test/unit/test_client_auth_with_proxy.js
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+class SecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ }
+
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+
+ let output = this.output;
+ this.input.asyncWait(
+ {
+ onInputStreamReady(readyInput) {
+ let request = NetUtil.readInputStreamToString(
+ readyInput,
+ readyInput.available()
+ );
+ ok(
+ request.startsWith("GET /") && request.includes("HTTP/1.1"),
+ "expecting an HTTP/1.1 GET request"
+ );
+ let response =
+ "HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\n" +
+ "Connection:Close\r\nContent-Length:2\r\n\r\nOK";
+ output.write(response, response.length);
+ },
+ },
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ }
+}
+
+function startServer(cert) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+
+ let securityObservers = [];
+
+ let listener = {
+ onSocketAccepted(socket, transport) {
+ info("Accepted TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ connectionInfo.setSecurityObserver(new SecurityObserver(input, output));
+ },
+
+ onStopListening() {
+ info("onStopListening");
+ for (let securityObserver of securityObservers) {
+ securityObserver.input.close();
+ securityObserver.output.close();
+ }
+ },
+ };
+
+ tlsServer.setSessionTickets(false);
+ tlsServer.setRequestClientCertificate(Ci.nsITLSServerSocket.REQUEST_ALWAYS);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer;
+}
+
+// Replace the UI dialog that prompts the user to pick a client certificate.
+const clientAuthDialogService = {
+ chooseCertificate(hostname, certArray, loadContext, callback) {
+ callback.certificateChosen(certArray[0], false);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
+};
+
+let server;
+add_setup(async function setup() {
+ do_get_profile();
+
+ let clientAuthDialogServiceCID = MockRegistrar.register(
+ "@mozilla.org/security/ClientAuthDialogService;1",
+ clientAuthDialogService
+ );
+
+ let cert = getTestServerCertificate();
+ ok(!!cert, "Got self-signed cert");
+ server = startServer(cert);
+
+ certOverrideService.rememberValidityOverride(
+ "localhost",
+ server.port,
+ {},
+ cert,
+ true
+ );
+
+ registerCleanupFunction(async function () {
+ MockRegistrar.unregister(clientAuthDialogServiceCID);
+ certOverrideService.clearValidityOverride("localhost", server.port, {});
+ server.close();
+ });
+});
+
+add_task(async function test_client_auth_with_proxy() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+
+ for (let p of proxies) {
+ info(`Test with proxy:${p.name}`);
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ let chan = makeChan(`https://localhost:${server.port}`);
+ let [req, buff] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(buff, "OK");
+ req.QueryInterface(Ci.nsIProxiedChannel);
+ ok(!!req.proxyInfo);
+ notEqual(req.proxyInfo.type, "direct");
+ await proxy.stop();
+ }
+});
diff --git a/netwerk/test/unit/test_coaleasing_h2_and_h3_connection.js b/netwerk/test/unit/test_coaleasing_h2_and_h3_connection.js
new file mode 100644
index 0000000000..3c998ffe29
--- /dev/null
+++ b/netwerk/test/unit/test_coaleasing_h2_and_h3_connection.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let h2Port;
+let h3Port;
+
+add_setup(async function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ Services.prefs.setBoolPref("network.http.altsvc.oe", true);
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.http.altsvc.oe");
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+add_task(async function testNotCoaleasingH2Connection() {
+ const host = "foo.example.com";
+ Services.prefs.setCharPref("network.dns.localDomains", host);
+
+ let server = new NodeHTTPSServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.execute(`global.h3Port = "${h3Port}";`);
+ await server.registerPathHandler("/altsvc", (req, resp) => {
+ const body = "done";
+ resp.setHeader("Content-Length", body.length);
+ resp.setHeader("Alt-Svc", `h3-29=:${global.h3Port}`);
+ resp.writeHead(200);
+ resp.write(body);
+ resp.end("");
+ });
+
+ let chan = makeChan(`https://${host}:${server.port()}/altsvc`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "http/1.1");
+
+ // Some delay to make sure the H3 speculative connection is created.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // To clear the altsvc cache.
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+
+ // Add another alt-svc header to route to moz-http2.js.
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ `${host};h2=:${h2Port}`
+ );
+
+ let start = new Date().getTime();
+ chan = makeChan(`https://${host}:${server.port()}/server-timing`);
+ chan.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
+ [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+
+ // The time this request takes should be way more less than the
+ // neqo idle timeout (30s).
+ let duration = (new Date().getTime() - start) / 1000;
+ Assert.less(duration, 10);
+});
diff --git a/netwerk/test/unit/test_compareURIs.js b/netwerk/test/unit/test_compareURIs.js
new file mode 100644
index 0000000000..e460de1c29
--- /dev/null
+++ b/netwerk/test/unit/test_compareURIs.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function do_info(text, stack) {
+ if (!stack) {
+ stack = Components.stack.caller;
+ }
+
+ dump(
+ "TEST-INFO | " +
+ stack.filename +
+ " | [" +
+ stack.name +
+ " : " +
+ stack.lineNumber +
+ "] " +
+ text +
+ "\n"
+ );
+}
+function run_test() {
+ var tests = [
+ ["http://mozilla.org/", "http://mozilla.org/somewhere/there", true],
+ ["http://mozilla.org/", "http://www.mozilla.org/", false],
+ ["http://mozilla.org/", "http://mozilla.org:80", true],
+ ["http://mozilla.org/", "http://mozilla.org:90", false],
+ ["http://mozilla.org", "https://mozilla.org", false],
+ ["http://mozilla.org", "https://mozilla.org:80", false],
+ ["http://mozilla.org:443", "https://mozilla.org", false],
+ ["https://mozilla.org:443", "https://mozilla.org", true],
+ ["https://mozilla.org:443", "https://mozilla.org/somewhere/", true],
+ ["about:", "about:", false],
+ ["data:text/plain,text", "data:text/plain,text", false],
+ ["about:blank", "about:blank", false],
+ ["about:", "http://mozilla.org/", false],
+ ["about:", "about:config", false],
+ ["about:text/plain,text", "data:text/plain,text", false],
+ ["jar:http://mozilla.org/!/", "http://mozilla.org/", true],
+ ["view-source:http://mozilla.org/", "http://mozilla.org/", true],
+ ];
+
+ tests.forEach(function (aTest) {
+ do_info("Comparing " + aTest[0] + " to " + aTest[1]);
+
+ var uri1 = NetUtil.newURI(aTest[0]);
+ var uri2 = NetUtil.newURI(aTest[1]);
+
+ var equal;
+ try {
+ Services.scriptSecurityManager.checkSameOriginURI(
+ uri1,
+ uri2,
+ false,
+ false
+ );
+ equal = true;
+ } catch (e) {
+ equal = false;
+ }
+ Assert.equal(equal, aTest[2]);
+ });
+}
diff --git a/netwerk/test/unit/test_compressappend.js b/netwerk/test/unit/test_compressappend.js
new file mode 100644
index 0000000000..05f19be4b5
--- /dev/null
+++ b/netwerk/test/unit/test_compressappend.js
@@ -0,0 +1,99 @@
+//
+// Test that data can be appended to a cache entry even when the data is
+// compressed by the cache compression feature - bug 648429.
+//
+
+"use strict";
+
+function write_and_check(str, data, len) {
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw(
+ "str.write has not written all data!\n" +
+ " Expected: " +
+ len +
+ "\n" +
+ " Actual: " +
+ written +
+ "\n"
+ );
+ }
+}
+
+function TestAppend(compress, callback) {
+ this._compress = compress;
+ this._callback = callback;
+ this.run();
+}
+
+TestAppend.prototype = {
+ _compress: false,
+ _callback: null,
+
+ run() {
+ evict_cache_entries();
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ this.writeData.bind(this)
+ );
+ },
+
+ writeData(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ if (this._compress) {
+ entry.setMetaDataElement("uncompressed-len", "0");
+ }
+ var os = entry.openOutputStream(0, 5);
+ write_and_check(os, "12345", 5);
+ os.close();
+ entry.close();
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ this.appendData.bind(this)
+ );
+ },
+
+ appendData(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.storageDataSize, 5);
+ write_and_check(os, "abcde", 5);
+ os.close();
+ entry.close();
+
+ asyncOpenCacheEntry(
+ "http://data/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ this.checkData.bind(this)
+ );
+ },
+
+ checkData(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var self = this;
+ pumpReadStream(entry.openInputStream(0), function (str) {
+ Assert.equal(str.length, 10);
+ Assert.equal(str, "12345abcde");
+ entry.close();
+
+ executeSoon(self._callback);
+ });
+ },
+};
+
+function run_test() {
+ do_get_profile();
+ new TestAppend(false, run_test2);
+ do_test_pending();
+}
+
+function run_test2() {
+ new TestAppend(true, do_test_finished);
+}
diff --git a/netwerk/test/unit/test_connection_based_auth.js b/netwerk/test/unit/test_connection_based_auth.js
new file mode 100644
index 0000000000..3a21ffcb77
--- /dev/null
+++ b/netwerk/test/unit/test_connection_based_auth.js
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+add_task(async function test_connection_based_auth() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ await proxy.registerConnectHandler((req, clientSocket, head) => {
+ if (!req.headers["proxy-authorization"]) {
+ clientSocket.write(
+ "HTTP/1.1 407 Unauthorized\r\n" +
+ "Proxy-agent: Node.js-Proxy\r\n" +
+ "Connection: keep-alive\r\n" +
+ "Proxy-Authenticate: mock_auth\r\n" +
+ "Content-Length: 0\r\n" +
+ "\r\n"
+ );
+
+ clientSocket.on("data", data => {
+ let array = data.toString().split("\r\n");
+ let proxyAuthorization = "";
+ for (let line of array) {
+ let pair = line.split(":").map(element => element.trim());
+ if (pair[0] === "Proxy-Authorization") {
+ proxyAuthorization = pair[1];
+ }
+ }
+
+ if (proxyAuthorization === "moz_test_credentials") {
+ // We don't return 200 OK here, because we don't have a server
+ // to connect to.
+ clientSocket.write(
+ "HTTP/1.1 404 Not Found\r\nProxy-agent: Node.js-Proxy\r\n\r\n"
+ );
+ } else {
+ clientSocket.write(
+ "HTTP/1.1 502 Error\r\nProxy-agent: Node.js-Proxy\r\n\r\n"
+ );
+ }
+ clientSocket.destroy();
+ });
+ return;
+ }
+
+ // We should not reach here.
+ clientSocket.write(
+ "HTTP/1.1 502 Error\r\nProxy-agent: Node.js-Proxy\r\n\r\n"
+ );
+ clientSocket.destroy();
+ });
+
+ let chan = makeChan(`https://example.ntlm.com/test`);
+ let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
+ Assert.equal(req.status, Cr.NS_ERROR_UNKNOWN_HOST);
+ req.QueryInterface(Ci.nsIProxiedChannel);
+ Assert.equal(req.httpProxyConnectResponseCode, 404);
+
+ await proxy.stop();
+});
diff --git a/netwerk/test/unit/test_content_encoding_gzip.js b/netwerk/test/unit/test_content_encoding_gzip.js
new file mode 100644
index 0000000000..48479034b8
--- /dev/null
+++ b/netwerk/test/unit/test_content_encoding_gzip.js
@@ -0,0 +1,163 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ {
+ url: "/test/cegzip1",
+ flags: CL_EXPECT_GZIP,
+ ce: "gzip",
+ body: [
+ 0x1f, 0x8b, 0x08, 0x08, 0x5a, 0xa0, 0x31, 0x4f, 0x00, 0x03, 0x74, 0x78,
+ 0x74, 0x00, 0x2b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x92, 0xd4, 0xe2,
+ 0x12, 0x43, 0x2e, 0x00, 0xb9, 0x23, 0xd7, 0x3b, 0x0e, 0x00, 0x00, 0x00,
+ ],
+ datalen: 14, // the data length of the uncompressed document
+ },
+
+ {
+ url: "/test/cegzip2",
+ flags: CL_EXPECT_GZIP,
+ ce: "gzip, gzip",
+ body: [
+ 0x1f, 0x8b, 0x08, 0x00, 0x72, 0xa1, 0x31, 0x4f, 0x00, 0x03, 0x93, 0xef,
+ 0xe6, 0xe0, 0x88, 0x5a, 0x60, 0xe8, 0xcf, 0xc0, 0x5c, 0x52, 0x51, 0xc2,
+ 0xa0, 0x7d, 0xf2, 0x84, 0x4e, 0x18, 0xc3, 0xa2, 0x49, 0x57, 0x1e, 0x09,
+ 0x39, 0xeb, 0x31, 0xec, 0x54, 0xbe, 0x6e, 0xcd, 0xc7, 0xc0, 0xc0, 0x00,
+ 0x00, 0x6e, 0x90, 0x7a, 0x85, 0x24, 0x00, 0x00, 0x00,
+ ],
+ datalen: 14, // the data length of the uncompressed document
+ },
+
+ {
+ url: "/test/cebrotli1",
+ flags: CL_EXPECT_GZIP,
+ ce: "br",
+ body: [0x0b, 0x02, 0x80, 0x74, 0x65, 0x73, 0x74, 0x0a, 0x03],
+
+ datalen: 5, // the data length of the uncompressed document
+ },
+
+ // this is a truncated brotli document, producing no output bytes
+ {
+ url: "/test/cebrotli2",
+ flags: CL_EXPECT_GZIP,
+ ce: "br",
+ body: [0x0b, 0x0a, 0x09],
+ datalen: 0,
+ },
+
+ // this is brotli but should come through as identity due to prefs
+ {
+ url: "/test/cebrotli3",
+ flags: 0,
+ ce: "br",
+ body: [0x0b, 0x02, 0x80, 0x74, 0x65, 0x73, 0x74, 0x0a, 0x03],
+
+ datalen: 9,
+ },
+
+ // this is not a brotli document
+ {
+ url: "/test/cebrotli4",
+ flags: CL_EXPECT_GZIP | CL_EXPECT_FAILURE,
+ ce: "br",
+ body: [
+ 0x01, 0xe6, 0x00, 0x76, 0x42, 0x10, 0x01, 0x1c, 0x24, 0x24, 0x3c, 0xd7,
+ 0xd7, 0xd7, 0x01, 0x1c,
+ ],
+ datalen: 16,
+ },
+
+ // this is a brotli document
+ {
+ url: "/test/cebrotli5",
+ flags: CL_EXPECT_GZIP,
+ ce: "br",
+ body: [0x0b, 0x00, 0x80, 0x4e, 0x03],
+ datalen: 1,
+ },
+
+ // this a truncated brotli document (missing the end marker)
+ // producing one output byte
+ {
+ url: "/test/cebrotli6",
+ flags: CL_EXPECT_GZIP,
+ ce: "br",
+ body: [0x0b, 0x00, 0x80, 0x4e],
+ datalen: 1,
+ },
+];
+
+function setupChannel(url) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + url,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function startIter() {
+ if (tests[index].url === "/test/cebrotli3") {
+ // this test wants to make sure we don't do brotli when not in a-e
+ prefs.setCharPref("network.http.accept-encoding", "gzip, deflate");
+ } else {
+ prefs.setCharPref("network.http.accept-encoding", "gzip, deflate, br");
+ }
+ var channel = setupChannel(tests[index].url);
+ channel.asyncOpen(
+ new ChannelListener(completeIter, channel, tests[index].flags)
+ );
+}
+
+function completeIter(request, data, ctx) {
+ if (!(tests[index].flags & CL_EXPECT_FAILURE)) {
+ Assert.equal(data.length, tests[index].datalen, "test " + index);
+ }
+ if (++index < tests.length) {
+ startIter();
+ } else {
+ httpserver.stop(do_test_finished);
+ prefs.setCharPref("network.http.accept-encoding", cePref);
+ }
+}
+
+var prefs;
+var cePref;
+function run_test() {
+ prefs = Services.prefs;
+ cePref = prefs.getCharPref("network.http.accept-encoding");
+ prefs.setBoolPref("network.http.encoding.trustworthy_is_https", false);
+
+ httpserver.registerPathHandler("/test/cegzip1", handler);
+ httpserver.registerPathHandler("/test/cegzip2", handler);
+ httpserver.registerPathHandler("/test/cebrotli1", handler);
+ httpserver.registerPathHandler("/test/cebrotli2", handler);
+ httpserver.registerPathHandler("/test/cebrotli3", handler);
+ httpserver.registerPathHandler("/test/cebrotli4", handler);
+ httpserver.registerPathHandler("/test/cebrotli5", handler);
+ httpserver.registerPathHandler("/test/cebrotli6", handler);
+ httpserver.start(-1);
+
+ startIter();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", tests[index].ce, false);
+ response.setHeader("Content-Length", "" + tests[index].body.length, false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIBinaryOutputStream
+ );
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(tests[index].body);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_content_length_underrun.js b/netwerk/test/unit/test_content_length_underrun.js
new file mode 100644
index 0000000000..f5f8e40a8a
--- /dev/null
+++ b/netwerk/test/unit/test_content_length_underrun.js
@@ -0,0 +1,295 @@
+/*
+ * Test Content-Length underrun behavior
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var test_flags = [];
+var testPathBase = "/cl_hdrs";
+
+var prefs;
+var enforcePrefStrict;
+var enforcePrefSoft;
+var enforcePrefStrictChunked;
+
+Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+});
+
+function run_test() {
+ prefs = Services.prefs;
+ enforcePrefStrict = prefs.getBoolPref("network.http.enforce-framing.http1");
+ enforcePrefSoft = prefs.getBoolPref("network.http.enforce-framing.soft");
+ enforcePrefStrictChunked = prefs.getBoolPref(
+ "network.http.enforce-framing.strict_chunked_encoding"
+ );
+
+ prefs.setBoolPref("network.http.enforce-framing.http1", true);
+
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num) {
+ let testPath = testPathBase + num;
+ // eslint-disable-next-line no-eval
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ let flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen(
+ // eslint-disable-next-line no-eval
+ new ChannelListener(eval("completeTest" + num), channel, flags)
+ );
+}
+
+function run_gzip_test(num) {
+ let testPath = testPathBase + num;
+ // eslint-disable-next-line no-eval
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+
+ function StreamListener() {}
+
+ StreamListener.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(aRequest) {},
+
+ onStopRequest(aRequest, aStatusCode) {
+ // Make sure we catch the error NS_ERROR_NET_PARTIAL_TRANSFER here.
+ Assert.equal(aStatusCode, Cr.NS_ERROR_NET_PARTIAL_TRANSFER);
+ // do_test_finished();
+ endTests();
+ },
+
+ onDataAvailable(request, stream, offset, count) {},
+ };
+
+ let listener = new StreamListener();
+
+ channel.asyncOpen(listener);
+}
+
+function setupChannel(url) {
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests() {
+ // restore the prefs to pre-test values
+ prefs.setBoolPref("network.http.enforce-framing.http1", enforcePrefStrict);
+ prefs.setBoolPref("network.http.enforce-framing.soft", enforcePrefSoft);
+ prefs.setBoolPref(
+ "network.http.enforce-framing.strict_chunked_encoding",
+ enforcePrefStrictChunked
+ );
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: FAIL because of Content-Length underrun with HTTP 1.1
+test_flags[1] = CL_EXPECT_LATE_FAILURE;
+
+// eslint-disable-next-line no-unused-vars
+function handler1(metadata, response) {
+ var body = "blablabla";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 556677\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest1(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_NET_PARTIAL_TRANSFER);
+
+ run_test_number(11);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 11: PASS because of Content-Length underrun with HTTP 1.1 but non 2xx
+test_flags[11] = CL_IGNORE_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler11(metadata, response) {
+ var body = "blablabla";
+
+ response.seizePower();
+ response.write("HTTP/1.1 404 NotOK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 556677\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest11(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ run_test_number(2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: Succeed because Content-Length underrun is with HTTP 1.0
+
+test_flags[2] = CL_IGNORE_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler2(metadata, response) {
+ var body = "short content";
+
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 12345678\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest2(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+
+ // test 3 requires the enforce-framing prefs to be false
+ prefs.setBoolPref("network.http.enforce-framing.http1", false);
+ prefs.setBoolPref("network.http.enforce-framing.soft", false);
+ prefs.setBoolPref(
+ "network.http.enforce-framing.strict_chunked_encoding",
+ false
+ );
+ run_test_number(3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: SUCCEED with bad Content-Length because pref allows it
+test_flags[3] = CL_IGNORE_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler3(metadata, response) {
+ var body = "blablabla";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 556677\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest3(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ prefs.setBoolPref("network.http.enforce-framing.soft", true);
+ run_test_number(4);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: Succeed because a cut off deflate stream can't be detected
+test_flags[4] = CL_IGNORE_CL;
+
+// eslint-disable-next-line no-unused-vars
+function handler4(metadata, response) {
+ // this is the beginning of a deflate compressed response body
+
+ var body =
+ "\xcd\x57\xcd\x6e\x1b\x37\x10\xbe\x07\xc8\x3b\x0c\x36\x68\x72\xd1" +
+ "\xbf\x92\x22\xb1\x57\x0a\x64\x4b\x6a\x0c\x28\xb6\x61\xa9\x41\x73" +
+ "\x2a\xb8\xbb\x94\x44\x98\xfb\x03\x92\x92\xec\x06\x7d\x97\x1e\xeb" +
+ "\xbe\x86\x5e\xac\xc3\x25\x97\xa2\x64\xb9\x75\x0b\x14\xe8\x69\x87" +
+ "\x33\x9c\x1f\x7e\x33\x9c\xe1\x86\x9f\x66\x9f\x27\xfd\x97\x2f\x20" +
+ "\xfc\x34\x1a\x0c\x35\x01\xa1\x62\x8a\xd3\xfe\xf5\xcd\xd5\xe5\xd5" +
+ "\x6c\x54\x83\x49\xbe\x60\x31\xa3\x1c\x12\x0a\x0b\x2a\x15\xcb\x33" +
+ "\x4d\xae\x19\x05\x19\xe7\x9c\x30\x41\x1b\x61\xd3\x28\x95\xfa\x29" +
+ "\x55\x04\x32\x92\xd2\x5e\x90\x50\x19\x0b\x56\x68\x9d\x00\xe2\x3c" +
+ "\x53\x34\x53\xbd\xc0\x99\x56\xf9\x4a\x51\xe0\x64\xcf\x18\x24\x24" +
+ "\x93\xb0\xca\x40\xd2\x15\x07\x6e\xbd\x37\x60\x82\x3b\x8f\x86\x22" +
+ "\x21\xcb\x15\x95\x35\x20\x91\xa4\x59\xac\xa9\x62\x95\x31\xed\x14" +
+ "\xc9\x98\x2c\x19\x15\x3a\x62\x45\xef\x70\x1b\x50\x05\xa4\x28\xc4" +
+ "\xf6\x21\x66\xa4\xdc\x83\x32\x09\x85\xc8\xe7\x54\xa2\x4b\x81\x74" +
+ "\xbe\x12\xc0\x91\xb9\x7d\x50\x24\xe2\x0c\xd9\x29\x06\x2e\xdd\x79";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 553677\r\n");
+ response.write("Content-Encoding: deflate\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest4(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+
+ prefs.setBoolPref("network.http.enforce-framing.http1", true);
+ run_gzip_test(99);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 99: FAIL because a cut off gzip stream CAN be detected
+
+// Note that test 99 here is run completely different than the other tests in
+// this file so if you add more tests here, consider adding them before this.
+
+// eslint-disable-next-line no-unused-vars
+function handler99(metadata, response) {
+ // this is the beginning of a gzip compressed response body
+
+ var body =
+ "\x1f\x8b\x08\x00\x80\xb9\x25\x53\x00\x03\xd4\xd9\x79\xb8\x8e\xe5" +
+ "\xba\x00\xf0\x65\x19\x33\x24\x15\x29\xf3\x50\x52\xc6\xac\x85\x10" +
+ "\x8b\x12\x22\x45\xe6\xb6\x21\x9a\x96\x84\x4c\x69\x32\xec\x84\x92" +
+ "\xcc\x99\x6a\xd9\x32\xa5\xd0\x40\xd9\xc6\x14\x15\x95\x28\x62\x9b" +
+ "\x09\xc9\x70\x4a\x25\x53\xec\x8e\x9c\xe5\x1c\x9d\xeb\xfe\x9d\x73" +
+ "\x9d\x3f\xf6\x1f\xe7\xbd\xae\xcf\xf3\xbd\xbf\xef\x7e\x9f\xeb\x79" +
+ "\xef\xf7\x99\xde\xe5\xee\x6e\xdd\x3b\x75\xeb\xd1\xb5\x6c\xb3\xd4" +
+ "\x47\x1f\x48\xf8\x17\x1d\x15\xce\x1d\x55\x92\x93\xcf\x97\xe7\x8e" +
+ "\x8b\xca\xe4\xca\x55\x92\x2a\x54\x4e\x4e\x4e\x4a\xa8\x78\x53\xa5" +
+ "\x8a\x15\x2b\x55\x4a\xfa\xe3\x7b\x85\x8a\x37\x55\x48\xae\x92\x50" +
+ "\xb4\xc2\xbf\xaa\x41\x17\x1f\xbd\x7b\xf6\xba\xaf\x47\xd1\xa2\x09" +
+ "\x3d\xba\x75\xeb\xf5\x3f\xc5\xfd\x6f\xbf\xff\x3f\x3d\xfa\xd7\x6d" +
+ "\x74\x7b\x62\x86\x0c\xff\x79\x9e\x98\x50\x33\xe1\x8f\xb3\x01\xef" +
+ "\xb6\x38\x7f\x9e\x92\xee\xf9\xa7\xee\xcb\x74\x21\x26\x25\xa1\x6a" +
+ "\x42\xf6\x73\xff\x96\x4c\x28\x91\x90\xe5\xdc\x79\xa6\x8b\xe2\x52" +
+ "\xd2\xbf\x5d\x28\x2b\x24\x26\xfc\xa9\xcc\x96\x1e\x97\x31\xfd\xba" +
+ "\xee\xe9\xde\x3d\x31\xe5\x4f\x65\xc1\xf4\xb8\x0b\x65\x86\x8b\xca";
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 553677\r\n");
+ response.write("Content-Encoding: gzip\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_content_sniffer.js b/netwerk/test/unit/test_content_sniffer.js
new file mode 100644
index 0000000000..26fb55cece
--- /dev/null
+++ b/netwerk/test/unit/test_content_sniffer.js
@@ -0,0 +1,157 @@
+// This file tests nsIContentSniffer, introduced in bug 324985
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const unknownType = "application/x-unknown-content-type";
+const sniffedType = "application/x-sniffed";
+
+const snifferCID = Components.ID("{4c93d2db-8a56-48d7-b261-9cf2a8d998eb}");
+const snifferContract = "@mozilla.org/network/unittest/contentsniffer;1";
+const categoryName = "net-content-sniffers";
+
+var sniffing_enabled = true;
+
+var isNosniff = false;
+
+/**
+ * This object is both a factory and an nsIContentSniffer implementation (so, it
+ * is de-facto a service)
+ */
+var sniffer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIContentSniffer"]),
+ createInstance: function sniffer_ci(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ getMIMETypeFromContent(request, data, length) {
+ return sniffedType;
+ },
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request) {
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ if (chan.contentType == unknownType) {
+ do_throw("Type should not be unknown!");
+ }
+ if (isNosniff) {
+ if (chan.contentType == sniffedType) {
+ do_throw("Sniffer called for X-Content-Type-Options:nosniff");
+ }
+ } else if (
+ sniffing_enabled &&
+ this._iteration > 2 &&
+ chan.contentType != sniffedType
+ ) {
+ do_throw(
+ "Expecting <" +
+ sniffedType +
+ "> but got <" +
+ chan.contentType +
+ "> for " +
+ chan.URI.spec
+ );
+ } else if (!sniffing_enabled && chan.contentType == sniffedType) {
+ do_throw(
+ "Sniffing not enabled but sniffer called for " + chan.URI.spec
+ );
+ }
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable: function test_ODA() {
+ throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ run_test_iteration(this._iteration);
+ do_test_finished();
+ },
+
+ _iteration: 1,
+};
+
+function makeChan(url) {
+ var chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ if (sniffing_enabled) {
+ chan.loadFlags |= Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+ }
+
+ return chan;
+}
+
+var httpserv = null;
+var urls = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/nosniff", nosniffHandler);
+ httpserv.start(-1);
+
+ urls = [
+ // NOTE: First URL here runs without our content sniffer
+ "data:" + unknownType + ", Some text",
+ "data:" + unknownType + ", Text", // Make sure sniffing works even if we
+ // used the unknown content sniffer too
+ "data:text/plain, Some more text",
+ "http://localhost:" + httpserv.identity.primaryPort,
+ "http://localhost:" + httpserv.identity.primaryPort + "/nosniff",
+ ];
+
+ Components.manager.nsIComponentRegistrar.registerFactory(
+ snifferCID,
+ "Unit test content sniffer",
+ snifferContract,
+ sniffer
+ );
+
+ run_test_iteration(1);
+}
+
+function nosniffHandler(request, response) {
+ response.setHeader("X-Content-Type-Options", "nosniff");
+}
+
+function run_test_iteration(index) {
+ if (index > urls.length) {
+ if (sniffing_enabled) {
+ sniffing_enabled = false;
+ index = listener._iteration = 1;
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ return; // we're done
+ }
+ }
+
+ if (sniffing_enabled && index == 2) {
+ // Register our sniffer only here
+ // This also makes sure that dynamic registration is working
+ var catMan = Services.catMan;
+ catMan.nsICategoryManager.addCategoryEntry(
+ categoryName,
+ "unit test",
+ snifferContract,
+ false,
+ true
+ );
+ } else if (sniffing_enabled && index == 5) {
+ isNosniff = true;
+ }
+
+ var chan = makeChan(urls[index - 1]);
+
+ listener._iteration++;
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cookie_blacklist.js b/netwerk/test/unit/test_cookie_blacklist.js
new file mode 100644
index 0000000000..d6d25927e9
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_blacklist.js
@@ -0,0 +1,43 @@
+"use strict";
+
+const GOOD_COOKIE = "GoodCookie=OMNOMNOM";
+const SPACEY_COOKIE = "Spacey Cookie=Major Tom";
+
+add_task(async () => {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ var cookieURI = Services.io.newURI(
+ "http://mozilla.org/test_cookie_blacklist.js"
+ );
+ const channel = NetUtil.newChannel({
+ uri: cookieURI,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ Services.cookies.setCookieStringFromHttp(
+ cookieURI,
+ "BadCookie1=\x01",
+ channel
+ );
+ Services.cookies.setCookieStringFromHttp(cookieURI, "BadCookie2=\v", channel);
+ Services.cookies.setCookieStringFromHttp(
+ cookieURI,
+ "Bad\x07Name=illegal",
+ channel
+ );
+ Services.cookies.setCookieStringFromHttp(cookieURI, GOOD_COOKIE, channel);
+ Services.cookies.setCookieStringFromHttp(cookieURI, SPACEY_COOKIE, channel);
+
+ CookieXPCShellUtils.createServer({ hosts: ["mozilla.org"] });
+
+ const storedCookie = await CookieXPCShellUtils.getCookieStringFromDocument(
+ cookieURI.spec
+ );
+ Assert.equal(storedCookie, GOOD_COOKIE + "; " + SPACEY_COOKIE);
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/test/unit/test_cookie_header.js b/netwerk/test/unit/test_cookie_header.js
new file mode 100644
index 0000000000..a5326ef25c
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_header.js
@@ -0,0 +1,112 @@
+// This file tests bug 250375
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort + "/";
+});
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function check_request_header(chan, name, value) {
+ var chanValue;
+ try {
+ chanValue = chan.getRequestHeader(name);
+ } catch (e) {
+ do_throw(
+ "Expected to find header '" +
+ name +
+ "' but didn't find it, got exception: " +
+ e
+ );
+ }
+ dump("Value for header '" + name + "' is '" + chanValue + "'\n");
+ Assert.equal(chanValue, value);
+}
+
+var cookieVal = "C1=V1";
+
+var listener = {
+ onStartRequest: function test_onStartR(request) {
+ try {
+ var chan = request.QueryInterface(Ci.nsIHttpChannel);
+ check_request_header(chan, "Cookie", cookieVal);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable: function test_ODA() {
+ throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+ },
+
+ onStopRequest: async function test_onStopR(request, status) {
+ if (this._iteration == 1) {
+ await run_test_continued();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+ do_test_finished();
+ },
+
+ _iteration: 1,
+};
+
+function makeChan() {
+ return NetUtil.newChannel({
+ uri: URL,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var httpserv = null;
+
+function run_test() {
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ httpserv = new HttpServer();
+ httpserv.start(-1);
+
+ var chan = makeChan();
+
+ chan.setRequestHeader("Cookie", cookieVal, false);
+
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+async function run_test_continued() {
+ var chan = makeChan();
+
+ var cookie2 = "C2=V2";
+
+ await CookieXPCShellUtils.setCookieToDocument(chan.URI.spec, cookie2);
+
+ chan.setRequestHeader("Cookie", cookieVal, false);
+
+ // We expect that the setRequestHeader overrides the
+ // automatically-added one, so insert cookie2 in front
+ cookieVal = cookie2 + "; " + cookieVal;
+
+ listener._iteration++;
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cookie_ipv6.js b/netwerk/test/unit/test_cookie_ipv6.js
new file mode 100644
index 0000000000..a91b8a6848
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_ipv6.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that channels with different LoadInfo
+ * are stored in separate namespaces ("cookie jars")
+ */
+
+"use strict";
+
+let ip = "[::1]";
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return `http://${ip}:${httpserver.identity.primaryPort}/`;
+});
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let httpserver = new HttpServer();
+
+function cookieSetHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader(
+ "Set-Cookie",
+ `Set-Cookie: T1=T2; path=/; SameSite=Lax; domain=${ip}; httponly`,
+ false
+ );
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("Content-Length", "2");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+add_task(async function test_cookie_ipv6() {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ httpserver.registerPathHandler("/", cookieSetHandler);
+ httpserver._start(-1, ip);
+
+ var chan = NetUtil.newChannel({
+ uri: URL,
+ loadUsingSystemPrincipal: true,
+ });
+ await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve));
+ });
+ equal(Services.cookies.cookies.length, 1);
+});
diff --git a/netwerk/test/unit/test_cookie_partitioned_attribute.js b/netwerk/test/unit/test_cookie_partitioned_attribute.js
new file mode 100644
index 0000000000..f1f3744831
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_partitioned_attribute.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function count_IsPartitioned(bool) {
+ var cookieCountIsPartitioned = 0;
+ Services.cookies.cookies.forEach(cookie => {
+ if (cookie.isPartitioned === bool) {
+ cookieCountIsPartitioned += 1;
+ }
+ });
+ return cookieCountIsPartitioned;
+}
+
+add_task(async function test_IsPartitioned() {
+ let profile = do_get_profile();
+ let dbFile = do_get_cookie_file(profile);
+ Assert.ok(!dbFile.exists());
+
+ let schema13db = new CookieDatabaseConnection(dbFile, 13);
+ let now = Date.now() * 1000; // date in microseconds
+
+ // add some non-partitioned cookies for key
+ let nUnpartitioned = 5;
+ let hostNonPartitioned = "cookie-host-non-partitioned.com";
+ for (let i = 0; i < nUnpartitioned; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ hostNonPartitioned,
+ "/", // path
+ now, // expiry
+ now, // last accessed
+ now, // creation time
+ false, // session
+ false, // secure
+ false, // http-only
+ false, // inBrowserElement
+ {} // OA
+ );
+ schema13db.insertCookie(cookie);
+ }
+
+ // add some partitioned cookies
+ let nPartitioned = 5;
+ let hostPartitioned = "host-partitioned.com";
+ for (let i = 0; i < nPartitioned; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ hostPartitioned,
+ "/", // path
+ now, // expiry
+ now, // last accessed
+ now, // creation time
+ false, // session
+ false, // secure
+ false, // http-only
+ false, // inBrowserElement
+ { partitionKey: "(https,example.com)" }
+ );
+ schema13db.insertCookie(cookie);
+ }
+
+ Assert.equal(do_count_cookies_in_db(schema13db.db), 10);
+
+ // Startup the cookie service and check the cookie counts by OA
+ let cookieCountNonPart =
+ Services.cookies.countCookiesFromHost(hostNonPartitioned); // includes expired cookies
+ Assert.equal(cookieCountNonPart, nUnpartitioned);
+ let cookieCountPart = Services.cookies.getCookiesFromHost(hostPartitioned, {
+ partitionKey: "(https,example.com)",
+ }).length; // includes expired cookies
+ Assert.equal(cookieCountPart, nPartitioned);
+
+ // Startup the cookie service and check the cookie counts by isPartitioned (IsPartitioned())
+ Assert.equal(count_IsPartitioned(false), nUnpartitioned);
+ Assert.equal(count_IsPartitioned(true), nPartitioned);
+
+ schema13db.close();
+});
diff --git a/netwerk/test/unit/test_cookiejars.js b/netwerk/test/unit/test_cookiejars.js
new file mode 100644
index 0000000000..1d78893f8c
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars.js
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that channels with different LoadInfo
+ * are stored in separate namespaces ("cookie jars")
+ */
+
+"use strict";
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+
+var cookieSetPath = "/setcookie";
+var cookieCheckPath = "/checkcookie";
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+// Test array:
+// - element 0: name for cookie, used both to set and later to check
+// - element 1: loadInfo (determines cookie namespace)
+//
+// TODO: bug 722850: make private browsing work per-app, and add tests. For now
+// all values are 'false' for PB.
+
+var tests = [
+ {
+ cookieName: "LCC_App0_BrowF_PrivF",
+ originAttributes: new OriginAttributes(0, false, 0),
+ },
+ {
+ cookieName: "LCC_App0_BrowT_PrivF",
+ originAttributes: new OriginAttributes(0, true, 0),
+ },
+ {
+ cookieName: "LCC_App1_BrowF_PrivF",
+ originAttributes: new OriginAttributes(1, false, 0),
+ },
+ {
+ cookieName: "LCC_App1_BrowT_PrivF",
+ originAttributes: new OriginAttributes(1, true, 0),
+ },
+];
+
+// test number: index into 'tests' array
+var i = 0;
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.loadInfo.originAttributes = tests[i].originAttributes;
+ chan.QueryInterface(Ci.nsIHttpChannel);
+
+ let loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
+ Ci.nsILoadGroup
+ );
+
+ if (chan.loadInfo.originAttributes.privateBrowsingId == 0) {
+ loadGroup.notificationCallbacks = Cu.createLoadContext();
+ chan.loadGroup = loadGroup;
+
+ chan.notificationCallbacks = Cu.createLoadContext();
+ } else {
+ loadGroup.notificationCallbacks = Cu.createPrivateLoadContext();
+ chan.loadGroup = loadGroup;
+
+ chan.notificationCallbacks = Cu.createPrivateLoadContext();
+ }
+
+ return chan;
+}
+
+function setCookie() {
+ var channel = setupChannel(cookieSetPath);
+ channel.setRequestHeader("foo-set-cookie", tests[i].cookieName, false);
+ channel.asyncOpen(new ChannelListener(setNextCookie, null));
+}
+
+function setNextCookie(request, data, context) {
+ if (++i == tests.length) {
+ // all cookies set: switch to checking them
+ i = 0;
+ checkCookie();
+ } else {
+ info("setNextCookie:i=" + i);
+ setCookie();
+ }
+}
+
+// Open channel that should send one and only one correct Cookie: header to
+// server, corresponding to it's namespace
+function checkCookie() {
+ var channel = setupChannel(cookieCheckPath);
+ channel.asyncOpen(new ChannelListener(completeCheckCookie, null));
+}
+
+function completeCheckCookie(request, data, context) {
+ // Look for all cookies in what the server saw: fail if we see any besides the
+ // one expected cookie for each namespace;
+ var expectedCookie = tests[i].cookieName;
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var cookiesSeen = request.getResponseHeader("foo-saw-cookies");
+
+ var j;
+ for (j = 0; j < tests.length; j++) {
+ var cookieToCheck = tests[j].cookieName;
+ let found = cookiesSeen.includes(cookieToCheck);
+ if (found && expectedCookie != cookieToCheck) {
+ do_throw(
+ "test index " +
+ i +
+ ": found unexpected cookie '" +
+ cookieToCheck +
+ "': in '" +
+ cookiesSeen +
+ "'"
+ );
+ } else if (!found && expectedCookie == cookieToCheck) {
+ do_throw(
+ "test index " +
+ i +
+ ": missing expected cookie '" +
+ expectedCookie +
+ "': in '" +
+ cookiesSeen +
+ "'"
+ );
+ }
+ }
+ // If we get here we're good.
+ info("Saw only correct cookie '" + expectedCookie + "'");
+ Assert.ok(true);
+
+ if (++i == tests.length) {
+ // end of tests
+ httpserver.stop(do_test_finished);
+ } else {
+ checkCookie();
+ }
+}
+
+function run_test() {
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ httpserver.registerPathHandler(cookieSetPath, cookieSetHandler);
+ httpserver.registerPathHandler(cookieCheckPath, cookieCheckHandler);
+ httpserver.start(-1);
+
+ setCookie();
+ do_test_pending();
+}
+
+function cookieSetHandler(metadata, response) {
+ var cookieName = metadata.getHeader("foo-set-cookie");
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Set-Cookie", cookieName + "=1; Path=/", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function cookieCheckHandler(metadata, response) {
+ var cookies = metadata.getHeader("Cookie");
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("foo-saw-cookies", cookies, false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
diff --git a/netwerk/test/unit/test_cookiejars_safebrowsing.js b/netwerk/test/unit/test_cookiejars_safebrowsing.js
new file mode 100644
index 0000000000..855bacef42
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars_safebrowsing.js
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Description of the test:
+ * We show that we can separate the safebrowsing cookie by creating a custom
+ * OriginAttributes using a unique safebrowsing first-party domain. Setting this
+ * custom OriginAttributes on the loadInfo of the channel allows us to query the
+ * first-party domain and therefore separate the safebrowsing cookie in its own
+ * cookie-jar. For testing safebrowsing update we do >> NOT << emulate a response
+ * in the body, rather we only set the cookies in the header of the response
+ * and confirm that cookies are separated in their own cookie-jar.
+ *
+ * 1) We init safebrowsing and simulate an update (cookies are set for localhost)
+ *
+ * 2) We open a channel that should send regular cookies, but not the
+ * safebrowsing cookie.
+ *
+ * 3) We open a channel with a custom callback, simulating a safebrowsing cookie
+ * that should send this simulated safebrowsing cookie as well as the
+ * real safebrowsing cookies. (Confirming that the safebrowsing cookies
+ * actually get stored in the correct jar).
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var setCookiePath = "/setcookie";
+var checkCookiePath = "/checkcookie";
+var safebrowsingUpdatePath = "/safebrowsingUpdate";
+var safebrowsingGethashPath = "/safebrowsingGethash";
+var httpserver;
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function cookieSetHandler(metadata, response) {
+ var cookieName = metadata.getHeader("set-cookie");
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("set-Cookie", cookieName + "=1; Path=/", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function cookieCheckHandler(metadata, response) {
+ var cookies = metadata.getHeader("Cookie");
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("saw-cookies", cookies, false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function safebrowsingUpdateHandler(metadata, response) {
+ var cookieName = "sb-update-cookie";
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("set-Cookie", cookieName + "=1; Path=/", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function safebrowsingGethashHandler(metadata, response) {
+ var cookieName = "sb-gethash-cookie";
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("set-Cookie", cookieName + "=1; Path=/", false);
+ response.setHeader("Content-Type", "text/plain");
+
+ let msg = "test-phish-simplea:1:32\n" + "a".repeat(32);
+ response.bodyOutputStream.write(msg, msg.length);
+}
+
+function setupChannel(path, originAttributes) {
+ var channel = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ });
+ channel.loadInfo.originAttributes = originAttributes;
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ return channel;
+}
+
+function run_test() {
+ // Set up a profile
+ do_get_profile();
+
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(setCookiePath, cookieSetHandler);
+ httpserver.registerPathHandler(checkCookiePath, cookieCheckHandler);
+ httpserver.registerPathHandler(
+ safebrowsingUpdatePath,
+ safebrowsingUpdateHandler
+ );
+ httpserver.registerPathHandler(
+ safebrowsingGethashPath,
+ safebrowsingGethashHandler
+ );
+
+ httpserver.start(-1);
+ run_next_test();
+}
+
+// this test does not emulate a response in the body,
+// rather we only set the cookies in the header of response.
+add_test(function test_safebrowsing_update() {
+ var streamUpdater = Cc[
+ "@mozilla.org/url-classifier/streamupdater;1"
+ ].getService(Ci.nsIUrlClassifierStreamUpdater);
+
+ function onSuccess() {
+ run_next_test();
+ }
+ function onUpdateError() {
+ do_throw("ERROR: received onUpdateError!");
+ }
+ function onDownloadError() {
+ do_throw("ERROR: received onDownloadError!");
+ }
+
+ streamUpdater.downloadUpdates(
+ "test-phish-simple,test-malware-simple",
+ "",
+ true,
+ URL + safebrowsingUpdatePath,
+ onSuccess,
+ onUpdateError,
+ onDownloadError
+ );
+});
+
+add_test(function test_safebrowsing_gethash() {
+ var hashCompleter = Cc[
+ "@mozilla.org/url-classifier/hashcompleter;1"
+ ].getService(Ci.nsIUrlClassifierHashCompleter);
+
+ hashCompleter.complete(
+ "aaaa",
+ URL + safebrowsingGethashPath,
+ "test-phish-simple",
+ {
+ completionV2(hash, table, chunkId) {},
+
+ completionFinished(status) {
+ Assert.equal(status, Cr.NS_OK);
+ run_next_test();
+ },
+ }
+ );
+});
+
+add_test(function test_non_safebrowsing_cookie() {
+ var cookieName = "regCookie_id0";
+ var originAttributes = new OriginAttributes(0, false, 0);
+
+ function setNonSafeBrowsingCookie() {
+ var channel = setupChannel(setCookiePath, originAttributes);
+ channel.setRequestHeader("set-cookie", cookieName, false);
+ channel.asyncOpen(new ChannelListener(checkNonSafeBrowsingCookie, null));
+ }
+
+ function checkNonSafeBrowsingCookie() {
+ var channel = setupChannel(checkCookiePath, originAttributes);
+ channel.asyncOpen(
+ new ChannelListener(completeCheckNonSafeBrowsingCookie, null)
+ );
+ }
+
+ function completeCheckNonSafeBrowsingCookie(request, data, context) {
+ // Confirm that only the >> ONE << cookie is sent over the channel.
+ var expectedCookie = cookieName + "=1";
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var cookiesSeen = request.getResponseHeader("saw-cookies");
+ Assert.equal(cookiesSeen, expectedCookie);
+ run_next_test();
+ }
+
+ setNonSafeBrowsingCookie();
+});
+
+add_test(function test_safebrowsing_cookie() {
+ var cookieName = "sbCookie_id4294967294";
+ var originAttributes = new OriginAttributes(0, false, 0);
+ originAttributes.firstPartyDomain =
+ "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla";
+
+ function setSafeBrowsingCookie() {
+ var channel = setupChannel(setCookiePath, originAttributes);
+ channel.setRequestHeader("set-cookie", cookieName, false);
+ channel.asyncOpen(new ChannelListener(checkSafeBrowsingCookie, null));
+ }
+
+ function checkSafeBrowsingCookie() {
+ var channel = setupChannel(checkCookiePath, originAttributes);
+ channel.asyncOpen(
+ new ChannelListener(completeCheckSafeBrowsingCookie, null)
+ );
+ }
+
+ function completeCheckSafeBrowsingCookie(request, data, context) {
+ // Confirm that all >> THREE << cookies are sent back over the channel:
+ // a) the safebrowsing cookie set when updating
+ // b) the safebrowsing cookie set when sending gethash
+ // c) the regular cookie with custom loadcontext defined in this test.
+ var expectedCookies = "sb-update-cookie=1; ";
+ expectedCookies += "sb-gethash-cookie=1; ";
+ expectedCookies += cookieName + "=1";
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var cookiesSeen = request.getResponseHeader("saw-cookies");
+
+ Assert.equal(cookiesSeen, expectedCookies);
+ httpserver.stop(do_test_finished);
+ }
+
+ setSafeBrowsingCookie();
+});
diff --git a/netwerk/test/unit/test_cookies_async_failure.js b/netwerk/test/unit/test_cookies_async_failure.js
new file mode 100644
index 0000000000..c61da23f99
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_async_failure.js
@@ -0,0 +1,514 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the various ways opening a cookie database can fail in an asynchronous
+// (i.e. after synchronous initialization) manner, and that the database is
+// renamed and recreated under each circumstance. These circumstances are, in no
+// particular order:
+//
+// 1) A write operation failing after the database has been read in.
+// 2) Asynchronous read failure due to a corrupt database.
+// 3) Synchronous read failure due to a corrupt database, when reading:
+// a) a single base domain;
+// b) the entire database.
+// 4) Asynchronous read failure, followed by another failure during INSERT but
+// before the database closes for rebuilding. (The additional error should be
+// ignored.)
+// 5) Asynchronous read failure, followed by an INSERT failure during rebuild.
+// This should result in an abort of the database rebuild; the partially-
+// built database should be moved to 'cookies.sqlite.bak-rebuild'.
+
+"use strict";
+
+let profile;
+let cookie;
+
+add_task(async () => {
+ // Set up a profile.
+ profile = do_get_profile();
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // Bug 1617611 - Fix all the tests broken by "cookies SameSite=Lax by default"
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+
+ // The server.
+ const hosts = ["foo.com", "hither.com", "haithur.com", "bar.com"];
+ for (let i = 0; i < 3000; ++i) {
+ hosts.push(i + ".com");
+ }
+ CookieXPCShellUtils.createServer({ hosts });
+
+ // Get the cookie file and the backup file.
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file().exists());
+
+ // Create a cookie object for testing.
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+ cookie = new Cookie(
+ "oh",
+ "hai",
+ "bar.com",
+ "/",
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ await run_test_1();
+ await run_test_2();
+ await run_test_3();
+ await run_test_4();
+ await run_test_5();
+ Services.prefs.clearUserPref("dom.security.https_first");
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+});
+
+function do_get_backup_file() {
+ let file = profile.clone();
+ file.append("cookies.sqlite.bak");
+ return file;
+}
+
+function do_get_rebuild_backup_file() {
+ let file = profile.clone();
+ file.append("cookies.sqlite.bak-rebuild");
+ return file;
+}
+
+function do_corrupt_db(file) {
+ // Sanity check: the database size should be larger than 320k, since we've
+ // written about 460k of data. If it's not, let's make it obvious now.
+ let size = file.fileSize;
+ Assert.ok(size > 320e3);
+
+ // Corrupt the database by writing bad data to the end of the file. We
+ // assume that the important metadata -- table structure etc -- is stored
+ // elsewhere, and that doing this will not cause synchronous failure when
+ // initializing the database connection. This is totally empirical --
+ // overwriting between 1k and 100k of live data seems to work. (Note that the
+ // database file will be larger than the actual content requires, since the
+ // cookie service uses a large growth increment. So we calculate the offset
+ // based on the expected size of the content, not just the file size.)
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ ostream.init(file, 2, -1, 0);
+ let sstream = ostream.QueryInterface(Ci.nsISeekableStream);
+ let n = size - 320e3 + 20e3;
+ sstream.seek(Ci.nsISeekableStream.NS_SEEK_SET, size - n);
+ for (let i = 0; i < n; ++i) {
+ ostream.write("a", 1);
+ }
+ ostream.flush();
+ ostream.close();
+
+ Assert.equal(file.clone().fileSize, size);
+ return size;
+}
+
+async function run_test_1() {
+ // Load the profile and populate it.
+ await CookieXPCShellUtils.setCookieToDocument(
+ "http://foo.com/",
+ "oh=hai; max-age=1000"
+ );
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Open a database connection now, before we load the profile and begin
+ // asynchronous write operations.
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 12);
+ Assert.equal(do_count_cookies_in_db(db.db), 1);
+
+ // Load the profile, and wait for async read completion...
+ await promise_load_profile();
+
+ // Insert a row.
+ db.insertCookie(cookie);
+ db.close();
+
+ // Attempt to insert a cookie with the same (name, host, path) triplet.
+ Services.cookies.add(
+ cookie.host,
+ cookie.path,
+ cookie.name,
+ "hallo",
+ cookie.isSecure,
+ cookie.isHttpOnly,
+ cookie.isSession,
+ cookie.expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+
+ // Check that the cookie service accepted the new cookie.
+ Assert.equal(Services.cookies.countCookiesFromHost(cookie.host), 1);
+
+ let isRebuildingDone = false;
+ let rebuildingObserve = function (subject, topic, data) {
+ isRebuildingDone = true;
+ Services.obs.removeObserver(rebuildingObserve, "cookie-db-rebuilding");
+ };
+ Services.obs.addObserver(rebuildingObserve, "cookie-db-rebuilding");
+
+ // Crash test: we're going to rebuild the cookie database. Close all the db
+ // connections in the main thread and initialize a new database file in the
+ // cookie thread. Trigger some access of cookies to ensure we won't crash in
+ // the chaos status.
+ for (let i = 0; i < 10; ++i) {
+ Assert.equal(Services.cookies.countCookiesFromHost(cookie.host), 1);
+ await new Promise(resolve => executeSoon(resolve));
+ }
+
+ // Wait for the cookie service to rename the old database and rebuild if not yet.
+ if (!isRebuildingDone) {
+ Services.obs.removeObserver(rebuildingObserve, "cookie-db-rebuilding");
+ await new _promise_observer("cookie-db-rebuilding");
+ }
+
+ await new Promise(resolve => executeSoon(resolve));
+
+ // At this point, the cookies should still be in memory.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost(cookie.host), 1);
+ Assert.equal(do_count_cookies(), 2);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Check that the original database was renamed, and that it contains the
+ // original cookie.
+ Assert.ok(do_get_backup_file().exists());
+ let backupdb = Services.storage.openDatabase(do_get_backup_file());
+ Assert.equal(do_count_cookies_in_db(backupdb, "foo.com"), 1);
+ backupdb.close();
+
+ // Load the profile, and check that it contains the new cookie.
+ do_load_profile();
+
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 1);
+ let cookies = Services.cookies.getCookiesFromHost(cookie.host, {});
+ Assert.equal(cookies.length, 1);
+ let dbcookie = cookies[0];
+ Assert.equal(dbcookie.value, "hallo");
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file().remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file().exists());
+}
+
+async function run_test_2() {
+ // Load the profile and populate it.
+ do_load_profile();
+
+ Services.cookies.runInTransaction(_ => {
+ let uri = NetUtil.newURI("http://foo.com/");
+ const channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ for (let i = 0; i < 3000; ++i) {
+ uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh=hai; max-age=1000",
+ channel
+ );
+ }
+ });
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ Assert.ok(!do_get_backup_file().exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
+ Assert.equal(do_count_cookies(), 0);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Check that the original database was renamed.
+ Assert.ok(do_get_backup_file().exists());
+ Assert.equal(do_get_backup_file().fileSize, size);
+ let db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ db.close();
+
+ do_load_profile();
+ Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
+ Assert.equal(do_count_cookies(), 0);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file().remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file().exists());
+}
+
+async function run_test_3() {
+ // Set the maximum cookies per base domain limit to a large value, so that
+ // corrupting the database is easier.
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 3000);
+
+ // Load the profile and populate it.
+ do_load_profile();
+ Services.cookies.runInTransaction(_ => {
+ let uri = NetUtil.newURI("http://hither.com/");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ for (let i = 0; i < 10; ++i) {
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh" + i + "=hai; max-age=1000",
+ channel
+ );
+ }
+ uri = NetUtil.newURI("http://haithur.com/");
+ channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ for (let i = 10; i < 3000; ++i) {
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh" + i + "=hai; max-age=1000",
+ channel
+ );
+ }
+ });
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ Assert.ok(!do_get_backup_file().exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookies.countCookiesFromHost("hither.com"), 0);
+ Assert.equal(Services.cookies.countCookiesFromHost("haithur.com"), 0);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ let db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ Assert.equal(do_count_cookies_in_db(db, "hither.com"), 0);
+ Assert.equal(do_count_cookies_in_db(db), 0);
+ db.close();
+
+ // Check that the original database was renamed.
+ Assert.ok(do_get_backup_file().exists());
+ Assert.equal(do_get_backup_file().fileSize, size);
+
+ // Rename it back, and try loading the entire database synchronously.
+ do_get_backup_file().moveTo(null, "cookies.sqlite");
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ Assert.ok(!do_get_backup_file().exists());
+
+ // Synchronously read in everything.
+ Assert.equal(do_count_cookies(), 0);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ Assert.equal(do_count_cookies_in_db(db), 0);
+ db.close();
+
+ // Check that the original database was renamed.
+ Assert.ok(do_get_backup_file().exists());
+ Assert.equal(do_get_backup_file().fileSize, size);
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file().remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file().exists());
+}
+
+async function run_test_4() {
+ // Load the profile and populate it.
+ do_load_profile();
+ Services.cookies.runInTransaction(_ => {
+ let uri = NetUtil.newURI("http://foo.com/");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ for (let i = 0; i < 3000; ++i) {
+ uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh=hai; max-age=1000",
+ channel
+ );
+ }
+ });
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ Assert.ok(!do_get_backup_file().exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
+
+ // Queue up an INSERT for the same base domain. This should also go into
+ // memory and be written out during database rebuild.
+ await CookieXPCShellUtils.setCookieToDocument(
+ "http://0.com/",
+ "oh2=hai; max-age=1000"
+ );
+
+ // At this point, the cookies should still be in memory.
+ Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 1);
+ Assert.equal(do_count_cookies(), 1);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Check that the original database was renamed.
+ Assert.ok(do_get_backup_file().exists());
+ Assert.equal(do_get_backup_file().fileSize, size);
+
+ // Load the profile, and check that it contains the new cookie.
+ do_load_profile();
+ Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 1);
+ Assert.equal(do_count_cookies(), 1);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file().remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file().exists());
+}
+
+async function run_test_5() {
+ // Load the profile and populate it.
+ do_load_profile();
+ Services.cookies.runInTransaction(_ => {
+ let uri = NetUtil.newURI("http://bar.com/");
+ const channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh=hai; path=/; max-age=1000",
+ channel
+ );
+ for (let i = 0; i < 3000; ++i) {
+ uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh=hai; max-age=1000",
+ channel
+ );
+ }
+ });
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ Assert.ok(!do_get_backup_file().exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 0);
+ Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
+ Assert.equal(do_count_cookies(), 0);
+ Assert.ok(do_get_backup_file().exists());
+ Assert.equal(do_get_backup_file().fileSize, size);
+ Assert.ok(!do_get_rebuild_backup_file().exists());
+
+ // Open a database connection, and write a row that will trigger a constraint
+ // violation.
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 12);
+ db.insertCookie(cookie);
+ Assert.equal(do_count_cookies_in_db(db.db, "bar.com"), 1);
+ Assert.equal(do_count_cookies_in_db(db.db), 1);
+ db.close();
+
+ // Check that the original backup and the database itself are gone.
+ Assert.ok(do_get_backup_file().exists());
+ Assert.equal(do_get_backup_file().fileSize, size);
+
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 0);
+ Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
+ Assert.equal(do_count_cookies(), 0);
+
+ // Close the profile. We do not need to wait for completion, because the
+ // database has already been closed. Ensure the cookie file is unlocked.
+ await promise_close_profile();
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file().remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file().exists());
+}
diff --git a/netwerk/test/unit/test_cookies_partition_counting.js b/netwerk/test/unit/test_cookies_partition_counting.js
new file mode 100644
index 0000000000..26bd56e1f6
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_partition_counting.js
@@ -0,0 +1,183 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // FOG needs to be initialized in order for data to flow.
+ Services.fog.initializeFOG();
+});
+
+add_task(async function test_purge_counting() {
+ let profile = do_get_profile();
+ let dbFile = do_get_cookie_file(profile);
+ Assert.ok(!dbFile.exists());
+
+ let schema12db = new CookieDatabaseConnection(dbFile, 12);
+ let now = Date.now() * 1000; // date in microseconds
+
+ // add some non-partitioned cookies for key
+ let cookieNum1 = 1;
+ let hostNonPartitioned = "cookie-host-non-partitioned.com";
+ for (let i = 0; i < cookieNum1; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ hostNonPartitioned,
+ "/", // path
+ now, // expiry
+ now, // needed to get the cookie by the db init
+ now, // creation time
+ false, // session
+ false, // secure
+ false, // http-only
+ false, // inBrowserElement
+ {} // OA
+ );
+ schema12db.insertCookie(cookie);
+ }
+
+ // add some non-partitioned cookies different key
+ let cookieNum2 = 10;
+ for (let i = 0; i < cookieNum2; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ hostNonPartitioned,
+ "/", // path
+ now, // expiry
+ now, // needed to get the cookie by the db init
+ now, // creation time
+ false, // session
+ false, // secure
+ false, // http-only
+ false, // inBrowserElement
+ { userContextId: 8 } // OA
+ );
+ schema12db.insertCookie(cookie);
+ }
+
+ // add some partitioned cookies
+ let hostPartitioned = "host-partitioned.com";
+ let cookieNum3 = 3;
+ for (let i = 0; i < cookieNum3; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ hostPartitioned,
+ "/", // path
+ now, // expiry
+ now, // needed to get the cookie by the db init
+ now, // creation time
+ false, // session
+ false, // secure
+ false, // http-only
+ false, // inBrowserElement
+ { partitionKey: "(https,example.com)" }
+ );
+ schema12db.insertCookie(cookie);
+ }
+
+ // add some partitioned with different OA
+ let cookieNum4 = 35;
+ for (let i = 0; i < cookieNum4; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ hostPartitioned,
+ "/", // path
+ now, // expiry
+ now, // needed to get the cookie by the db init
+ now, // creation time
+ false, // session
+ false, // secure
+ false, // http-only
+ false, // inBrowserElement
+ { partitionKey: "(https,example.com)", userContextId: 7 }
+ );
+ schema12db.insertCookie(cookie);
+ }
+
+ let allCookieCount = cookieNum1 + cookieNum2 + cookieNum3 + cookieNum4;
+ Assert.equal(do_count_cookies_in_db(schema12db.db), allCookieCount);
+
+ // startup the cookie service and check the cookie counts
+ let cookieCountNonPart =
+ Services.cookies.countCookiesFromHost(hostNonPartitioned); // includes expired cookies
+ Assert.equal(cookieCountNonPart, cookieNum1);
+ let cookieCountNonPartOA = Services.cookies.getCookiesFromHost(
+ hostNonPartitioned,
+ { userContextId: 8 }
+ ).length; // includes expired cookies
+ Assert.equal(cookieCountNonPartOA, cookieNum2);
+ let cookieCountPart = Services.cookies.getCookiesFromHost(hostPartitioned, {
+ partitionKey: "(https,example.com)",
+ }).length; // includes expired cookies
+ Assert.equal(cookieCountPart, cookieNum3);
+ let cookieCountPartOA = Services.cookies.getCookiesFromHost(hostPartitioned, {
+ partitionKey: "(https,example.com)",
+ userContextId: 7,
+ }).length; // includes expired cookies
+ Assert.equal(cookieCountPartOA, cookieNum4);
+
+ // trigger the collection
+ Services.obs.notifyObservers(null, "idle-daily");
+
+ // check telem fired for all cookie count
+ let cct = await Glean.networking.cookieCountTotal.testGetValue();
+ Assert.equal(cct.sum, allCookieCount, "All cookies telem");
+
+ // check telem for all un/partitioned counts
+ let ccp = await Glean.networking.cookieCountPartitioned.testGetValue();
+ Assert.equal(
+ ccp.sum,
+ cookieNum3 + cookieNum4,
+ "All partitioned cookies telem"
+ );
+
+ let ccu = await Glean.networking.cookieCountUnpartitioned.testGetValue();
+ Assert.equal(
+ ccu.sum,
+ cookieNum1 + cookieNum2,
+ "All unpartitioned cookies telem"
+ );
+
+ // check telem for part by key (host+OA)
+ // Note: With the decided histogram layout we see the buckets
+ // (used for indexing the histogram's buckets)
+ let histPartByKey =
+ await Glean.networking.cookieCountPartByKey.testGetValue();
+ Assert.equal(histPartByKey.values[2], 1, "Partitioned bucket 2 has 1 value");
+ Assert.equal(
+ histPartByKey.values[31],
+ 1,
+ "Partitioned bucket 31 has 1 value"
+ );
+ Assert.equal(
+ histPartByKey.sum,
+ cookieNum3 + cookieNum4,
+ "Partitioned bucket sums correctly"
+ );
+
+ // check telem for unpart by key (host+OA)
+ let histUnpartByKey =
+ await Glean.networking.cookieCountUnpartByKey.testGetValue();
+ Assert.equal(
+ histUnpartByKey.values[1],
+ 1,
+ "Unpartitioned bucket 1 has 1 value"
+ );
+ Assert.equal(
+ histUnpartByKey.values[8],
+ 1,
+ "Unpartitioned bucket 8 has 1 value"
+ );
+ Assert.equal(
+ histUnpartByKey.sum,
+ cookieNum1 + cookieNum2,
+ "Unpartitioned bucket sums correctly"
+ );
+
+ schema12db.close();
+});
diff --git a/netwerk/test/unit/test_cookies_privatebrowsing.js b/netwerk/test/unit/test_cookies_privatebrowsing.js
new file mode 100644
index 0000000000..9d3528440a
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_privatebrowsing.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test private browsing mode.
+
+"use strict";
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function getCookieStringFromPrivateDocument(uriSpec) {
+ return CookieXPCShellUtils.getCookieStringFromDocument(uriSpec, {
+ privateBrowsing: true,
+ });
+}
+
+add_task(async () => {
+ // Set up a profile.
+ do_get_profile();
+
+ // We don't want to have CookieJarSettings blocking this test.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // Test with cookies enabled.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // Test with https-first-mode disabled in PBM
+ Services.prefs.setBoolPref("dom.security.https_first_pbm", false);
+
+ CookieXPCShellUtils.createServer({ hosts: ["foo.com", "bar.com"] });
+
+ // We need to keep a private-browsing window active, otherwise the
+ // 'last-pb-context-exited' notification will be dispatched.
+ const privateBrowsingHolder = await CookieXPCShellUtils.loadContentPage(
+ "http://bar.com/",
+ { privateBrowsing: true }
+ );
+
+ // Create URIs pointing to foo.com and bar.com.
+ let uri1 = NetUtil.newURI("http://foo.com/foo.html");
+ let uri2 = NetUtil.newURI("http://bar.com/bar.html");
+
+ // Set a cookie for host 1.
+ Services.cookies.setCookieStringFromHttp(
+ uri1,
+ "oh=hai; max-age=1000",
+ make_channel(uri1.spec)
+ );
+ Assert.equal(Services.cookies.countCookiesFromHost(uri1.host), 1);
+
+ // Enter private browsing mode, set a cookie for host 2, and check the counts.
+ var chan1 = make_channel(uri1.spec);
+ chan1.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ chan1.setPrivate(true);
+
+ var chan2 = make_channel(uri2.spec);
+ chan2.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ chan2.setPrivate(true);
+
+ Services.cookies.setCookieStringFromHttp(uri2, "oh=hai; max-age=1000", chan2);
+ Assert.equal(await getCookieStringFromPrivateDocument(uri1.spec), "");
+ Assert.equal(await getCookieStringFromPrivateDocument(uri2.spec), "oh=hai");
+
+ // Remove cookies and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+ Assert.equal(await getCookieStringFromPrivateDocument(uri1.spec), "");
+ Assert.equal(await getCookieStringFromPrivateDocument(uri2.spec), "");
+
+ Services.cookies.setCookieStringFromHttp(uri2, "oh=hai; max-age=1000", chan2);
+ Assert.equal(await getCookieStringFromPrivateDocument(uri2.spec), "oh=hai");
+
+ // Leave private browsing mode and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+ Assert.equal(Services.cookies.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // Fake a profile change.
+ await promise_close_profile();
+ do_load_profile();
+
+ // Check that the right cookie persisted.
+ Assert.equal(Services.cookies.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // Enter private browsing mode, set a cookie for host 2, and check the counts.
+ Assert.equal(await getCookieStringFromPrivateDocument(uri1.spec), "");
+ Assert.equal(await getCookieStringFromPrivateDocument(uri2.spec), "");
+ Services.cookies.setCookieStringFromHttp(uri2, "oh=hai; max-age=1000", chan2);
+ Assert.equal(await getCookieStringFromPrivateDocument(uri2.spec), "oh=hai");
+
+ // Fake a profile change.
+ await promise_close_profile();
+ do_load_profile();
+
+ // We're still in private browsing mode, but should have a new session.
+ // Check counts.
+ Assert.equal(await getCookieStringFromPrivateDocument(uri1.spec), "");
+ Assert.equal(await getCookieStringFromPrivateDocument(uri2.spec), "");
+
+ // Leave private browsing mode and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+ Assert.equal(Services.cookies.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // Enter private browsing mode.
+
+ // Fake a profile change, but wait for async read completion.
+ await promise_close_profile();
+ await promise_load_profile();
+
+ // We're still in private browsing mode, but should have a new session.
+ // Check counts.
+ Assert.equal(await getCookieStringFromPrivateDocument(uri1.spec), "");
+ Assert.equal(await getCookieStringFromPrivateDocument(uri2.spec), "");
+
+ // Leave private browsing mode and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+ Assert.equal(Services.cookies.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // Let's release the last PB window.
+ privateBrowsingHolder.close();
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/test/unit/test_cookies_profile_close.js b/netwerk/test/unit/test_cookies_profile_close.js
new file mode 100644
index 0000000000..6ea9ab23f3
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_profile_close.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the cookie APIs behave sanely after 'profile-before-change'.
+
+"use strict";
+
+add_task(async () => {
+ // Set up a profile.
+ do_get_profile();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // Start the cookieservice.
+ Services.cookies;
+
+ CookieXPCShellUtils.createServer({ hosts: ["foo.com"] });
+
+ // Set a cookie.
+ let uri = NetUtil.newURI("http://foo.com");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ Services.scriptSecurityManager.createContentPrincipal(uri, {});
+
+ await CookieXPCShellUtils.setCookieToDocument(
+ uri.spec,
+ "oh=hai; max-age=1000"
+ );
+
+ let cookies = Services.cookies.cookies;
+ Assert.ok(cookies.length == 1);
+ let cookie = cookies[0];
+
+ // Fire 'profile-before-change'.
+ do_close_profile();
+
+ let promise = new _promise_observer("cookie-db-closed");
+
+ // Check that the APIs behave appropriately.
+ Assert.equal(
+ await CookieXPCShellUtils.getCookieStringFromDocument("http://foo.com/"),
+ ""
+ );
+
+ Assert.equal(Services.cookies.getCookieStringFromHttp(uri, channel), "");
+
+ await CookieXPCShellUtils.setCookieToDocument(uri.spec, "oh2=hai");
+
+ Services.cookies.setCookieStringFromHttp(uri, "oh3=hai", channel);
+ Assert.equal(
+ await CookieXPCShellUtils.getCookieStringFromDocument("http://foo.com/"),
+ ""
+ );
+
+ do_check_throws(function () {
+ Services.cookies.removeAll();
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function () {
+ Services.cookies.cookies;
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function () {
+ Services.cookies.add(
+ "foo.com",
+ "",
+ "oh4",
+ "hai",
+ false,
+ false,
+ false,
+ 0,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function () {
+ Services.cookies.remove("foo.com", "", "oh4", {});
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function () {
+ Services.cookies.cookieExists(cookie.host, cookie.path, cookie.name, {});
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function () {
+ Services.cookies.countCookiesFromHost("foo.com");
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function () {
+ Services.cookies.getCookiesFromHost("foo.com", {});
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ // Wait for the database to finish closing.
+ await promise;
+
+ // Load the profile and check that the API is available.
+ do_load_profile();
+ Assert.ok(
+ Services.cookies.cookieExists(cookie.host, cookie.path, cookie.name, {})
+ );
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/netwerk/test/unit/test_cookies_purge_counting.js b/netwerk/test/unit/test_cookies_purge_counting.js
new file mode 100644
index 0000000000..d92fabfbba
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_purge_counting.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // FOG needs to be initialized in order for data to flow.
+ Services.fog.initializeFOG();
+});
+
+add_task(async function test_purge_counting() {
+ let profile = do_get_profile();
+ let dbFile = do_get_cookie_file(profile);
+ Assert.ok(!dbFile.exists());
+
+ let schema12db = new CookieDatabaseConnection(dbFile, 12);
+
+ let now = Date.now() * 1000; // date in microseconds
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+
+ let manyHosts = 50;
+ let manyCookies = 140;
+ let totalCookies = manyHosts * manyCookies;
+
+ // add many expired cookies for each host
+ for (let hostNum = 0; hostNum < manyHosts; hostNum++) {
+ let host = "cookie-host" + hostNum + ".com";
+ for (let i = 0; i < manyCookies; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ host,
+ "/", // path
+ pastExpiry,
+ pastExpiry, // needed to get the cookie by the db init
+ now,
+ false,
+ false,
+ false
+ );
+ schema12db.insertCookie(cookie);
+ }
+ }
+
+ let validCookies = Services.cookies.cookies.length; // includes expired cookies
+ Assert.equal(validCookies, totalCookies);
+
+ // add a valid cookie - this triggers the purge
+ Services.cookies.add(
+ "cookie-host0.com", // any host
+ "/", // path
+ "cookie-name-x",
+ "cookie-value-x",
+ false, // secure
+ true, // http-only
+ true, // isSession
+ futureExpiry,
+ {}, // OA
+ Ci.nsICookie.SAMESITE_NONE, // SameSite
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+
+ // check that we purge all the expired cookies and not the unexpired
+ validCookies = Services.cookies.cookies.length;
+ Assert.equal(validCookies, 1);
+
+ // check that the telemetry fired
+ let cpm = await Glean.networking.cookiePurgeMax.testGetValue();
+ Assert.equal(cpm.sum, totalCookies, "Purge the expected number of cookies");
+
+ schema12db.close();
+});
diff --git a/netwerk/test/unit/test_cookies_purge_counting_per_host.js b/netwerk/test/unit/test_cookies_purge_counting_per_host.js
new file mode 100644
index 0000000000..83a10c75ac
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_purge_counting_per_host.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // FOG needs to be initialized in order for data to flow.
+ Services.fog.initializeFOG();
+});
+
+add_task(async function test_purge_counting_per_host() {
+ let profile = do_get_profile();
+ let dbFile = do_get_cookie_file(profile);
+ Assert.ok(!dbFile.exists());
+
+ let schema12db = new CookieDatabaseConnection(dbFile, 12);
+
+ let now = Date.now() * 1000; // date in microseconds
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+
+ let host = "cookie-host1.com";
+
+ let manyCookies = 180;
+ let cookieMax = 150;
+ let cookiesPurged = manyCookies - cookieMax;
+
+ // add many expired cookies for a single host
+ for (let i = 0; i < manyCookies; i++) {
+ let cookie = new Cookie(
+ "cookie-name" + i,
+ "cookie-value" + i,
+ host,
+ "/", // path
+ pastExpiry,
+ now, // last accessed
+ now, // creation time
+ false,
+ false,
+ false
+ );
+ schema12db.insertCookie(cookie);
+ }
+
+ // check that the cookies were added to the db
+ Assert.equal(do_count_cookies_in_db(schema12db.db), manyCookies);
+ Assert.equal(
+ do_count_cookies_in_db(schema12db.db, "cookie-host1.com"),
+ manyCookies
+ );
+
+ // startup the cookie service and check the cookie count
+ let validCookies = Services.cookies.countCookiesFromHost(host); // includes expired cookies
+ Assert.equal(validCookies, manyCookies);
+
+ // add a cookie - this will trigger the purge
+ Services.cookies.add(
+ host,
+ "/", // path
+ "cookie-name-x",
+ "cookie-value-x",
+ false, // secure
+ true, // http-only
+ true, // isSession
+ futureExpiry,
+ {}, // OA
+ Ci.nsICookie.SAMESITE_NONE, // SameSite
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+
+ // check that we purge down to the cookieMax (plus the cookie added)
+ validCookies = Services.cookies.countCookiesFromHost(host);
+ Assert.equal(validCookies, cookieMax + 1);
+
+ // check that the telemetry fired
+ let cpem = await Glean.networking.cookiePurgeEntryMax.testGetValue();
+ Assert.equal(cpem.sum, cookiesPurged, "Purge the expected number of cookies");
+
+ schema12db.close();
+});
diff --git a/netwerk/test/unit/test_cookies_read.js b/netwerk/test/unit/test_cookies_read.js
new file mode 100644
index 0000000000..e7e9e84b3e
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_read.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test cookie database asynchronous read operation.
+
+"use strict";
+
+var CMAX = 1000; // # of cookies to create
+
+add_task(async () => {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // Bug 1617611 - Fix all the tests broken by "cookies SameSite=Lax by default"
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+
+ // Start the cookieservice, to force creation of a database.
+ // Get the sessionCookies to join the initialization in cookie thread
+ Services.cookies.sessionCookies;
+
+ // Open a database connection now, after synchronous initialization has
+ // completed. We may not be able to open one later once asynchronous writing
+ // begins.
+ Assert.ok(do_get_cookie_file(profile).exists());
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 12);
+
+ let uri = NetUtil.newURI("http://foo.com/");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ for (let i = 0; i < CMAX; ++i) {
+ uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh=hai; max-age=1000",
+ channel
+ );
+ }
+
+ Assert.equal(do_count_cookies(), CMAX);
+
+ // Wait until all CMAX cookies have been written out to the database.
+ while (do_count_cookies_in_db(db.db) < CMAX) {
+ await new Promise(resolve => executeSoon(resolve));
+ }
+
+ // Check the WAL file size. We set it to 16 pages of 32k, which means it
+ // should be around 500k.
+ let file = db.db.databaseFile;
+ Assert.ok(file.exists());
+ Assert.ok(file.fileSize < 1e6);
+ db.close();
+
+ // fake a profile change
+ await promise_close_profile();
+ do_load_profile();
+
+ // test a few random cookies
+ Assert.equal(Services.cookies.countCookiesFromHost("999.com"), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost("abc.com"), 0);
+ Assert.equal(Services.cookies.countCookiesFromHost("100.com"), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost("400.com"), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost("xyz.com"), 0);
+
+ // force synchronous load of everything
+ Assert.equal(do_count_cookies(), CMAX);
+
+ // check that everything's precisely correct
+ for (let i = 0; i < CMAX; ++i) {
+ let host = i.toString() + ".com";
+ Assert.equal(Services.cookies.countCookiesFromHost(host), 1);
+ }
+
+ // reload again, to make sure the additions were written correctly
+ await promise_close_profile();
+ do_load_profile();
+
+ // remove some of the cookies, in both reverse and forward order
+ for (let i = 100; i-- > 0; ) {
+ let host = i.toString() + ".com";
+ Services.cookies.remove(host, "oh", "/", {});
+ }
+ for (let i = CMAX - 100; i < CMAX; ++i) {
+ let host = i.toString() + ".com";
+ Services.cookies.remove(host, "oh", "/", {});
+ }
+
+ // check the count
+ Assert.equal(do_count_cookies(), CMAX - 200);
+
+ // reload again, to make sure the removals were written correctly
+ await promise_close_profile();
+ do_load_profile();
+
+ // check the count
+ Assert.equal(do_count_cookies(), CMAX - 200);
+
+ // reload again, but wait for async read completion
+ await promise_close_profile();
+ await promise_load_profile();
+
+ // check that everything's precisely correct
+ Assert.equal(do_count_cookies(), CMAX - 200);
+ for (let i = 100; i < CMAX - 100; ++i) {
+ let host = i.toString() + ".com";
+ Assert.equal(Services.cookies.countCookiesFromHost(host), 1);
+ }
+
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+});
diff --git a/netwerk/test/unit/test_cookies_sync_failure.js b/netwerk/test/unit/test_cookies_sync_failure.js
new file mode 100644
index 0000000000..6c420d1cb0
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_sync_failure.js
@@ -0,0 +1,344 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the various ways opening a cookie database can fail in a synchronous
+// (i.e. immediate) manner, and that the database is renamed and recreated
+// under each circumstance. These circumstances are, in no particular order:
+//
+// 1) A corrupt database, such that opening the connection fails.
+// 2) The 'moz_cookies' table doesn't exist.
+// 3) Not all of the expected columns exist, and statement creation fails when:
+// a) The schema version is larger than the current version.
+// b) The schema version is less than or equal to the current version.
+// 4) Migration fails. This will have different modes depending on the initial
+// version:
+// a) Schema 1: the 'lastAccessed' column already exists.
+// b) Schema 2: the 'baseDomain' column already exists; or 'baseDomain'
+// cannot be computed for a particular host.
+// c) Schema 3: the 'creationTime' column already exists; or the
+// 'moz_uniqueid' index already exists.
+
+"use strict";
+
+let profile;
+let cookieFile;
+let backupFile;
+let sub_generator;
+let now;
+let futureExpiry;
+let cookie;
+
+var COOKIE_DATABASE_SCHEMA_CURRENT = 13;
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function finish_test() {
+ executeSoon(function () {
+ test_generator.return();
+ do_test_finished();
+ });
+}
+
+function* do_run_test() {
+ // Set up a profile.
+ profile = do_get_profile();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // Get the cookie file and the backup file.
+ cookieFile = profile.clone();
+ cookieFile.append("cookies.sqlite");
+ backupFile = profile.clone();
+ backupFile.append("cookies.sqlite.bak");
+ Assert.ok(!cookieFile.exists());
+ Assert.ok(!backupFile.exists());
+
+ // Create a cookie object for testing.
+ now = Date.now() * 1000;
+ futureExpiry = Math.round(now / 1e6 + 1000);
+ cookie = new Cookie(
+ "oh",
+ "hai",
+ "bar.com",
+ "/",
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ sub_generator = run_test_1(test_generator);
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_2(test_generator);
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_3(test_generator, 99);
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_3(test_generator, COOKIE_DATABASE_SCHEMA_CURRENT);
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_3(test_generator, 4);
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_3(test_generator, 3);
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_4_exists(
+ test_generator,
+ 1,
+ "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"
+ );
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_4_exists(
+ test_generator,
+ 2,
+ "ALTER TABLE moz_cookies ADD baseDomain TEXT"
+ );
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_4_baseDomain(test_generator);
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_4_exists(
+ test_generator,
+ 3,
+ "ALTER TABLE moz_cookies ADD creationTime INTEGER"
+ );
+ sub_generator.next();
+ yield;
+
+ sub_generator = run_test_4_exists(
+ test_generator,
+ 3,
+ "CREATE UNIQUE INDEX moz_uniqueid ON moz_cookies (name, host, path)"
+ );
+ sub_generator.next();
+ yield;
+
+ finish_test();
+}
+
+const garbage = "hello thar!";
+
+function create_garbage_file(file) {
+ // Create an empty database file.
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, -1);
+ Assert.ok(file.exists());
+ Assert.equal(file.fileSize, 0);
+
+ // Write some garbage to it.
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ ostream.init(file, -1, -1, 0);
+ ostream.write(garbage, garbage.length);
+ ostream.flush();
+ ostream.close();
+
+ file = file.clone(); // Windows maintains a stat cache. It's lame.
+ Assert.equal(file.fileSize, garbage.length);
+}
+
+function check_garbage_file(file) {
+ Assert.ok(file.exists());
+ Assert.equal(file.fileSize, garbage.length);
+ file.remove(false);
+ Assert.ok(!file.exists());
+}
+
+function* run_test_1(generator) {
+ // Create a garbage database file.
+ create_garbage_file(cookieFile);
+
+ let uri = NetUtil.newURI("http://foo.com/");
+ const channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ // Load the profile and populate it.
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh=hai; max-age=1000",
+ channel
+ );
+
+ // Fake a profile change.
+ do_close_profile(sub_generator);
+ yield;
+ do_load_profile();
+
+ // Check that the new database contains the cookie, and the old file was
+ // renamed.
+ Assert.equal(do_count_cookies(), 1);
+ check_garbage_file(backupFile);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ cookieFile.remove(false);
+ Assert.ok(!cookieFile.exists());
+ do_run_generator(generator);
+}
+
+function* run_test_2(generator) {
+ // Load the profile and populate it.
+ do_load_profile();
+ let uri = NetUtil.newURI("http://foo.com/");
+ const channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ Services.cookies.setCookieStringFromHttp(
+ uri,
+ "oh=hai; max-age=1000",
+ channel
+ );
+
+ // Fake a profile change.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Drop the table.
+ let db = Services.storage.openDatabase(cookieFile);
+ db.executeSimpleSQL("DROP TABLE moz_cookies");
+ db.close();
+
+ // Load the profile and check that the table is recreated in-place.
+ do_load_profile();
+ Assert.equal(do_count_cookies(), 0);
+ Assert.ok(!backupFile.exists());
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ cookieFile.remove(false);
+ Assert.ok(!cookieFile.exists());
+ do_run_generator(generator);
+}
+
+function* run_test_3(generator, schema) {
+ // Manually create a schema 2 database, populate it, and set the schema
+ // version to the desired number.
+ let schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ schema2db.insertCookie(cookie);
+ schema2db.db.schemaVersion = schema;
+ schema2db.close();
+
+ // Load the profile and check that the column existence test fails.
+ do_load_profile();
+ Assert.equal(do_count_cookies(), 0);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the schema version has been reset.
+ let db = Services.storage.openDatabase(cookieFile);
+ Assert.equal(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
+ db.close();
+
+ // Clean up.
+ cookieFile.remove(false);
+ Assert.ok(!cookieFile.exists());
+ do_run_generator(generator);
+}
+
+function* run_test_4_exists(generator, schema, stmt) {
+ // Manually create a database, populate it, and add the desired column.
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), schema);
+ db.insertCookie(cookie);
+ db.db.executeSimpleSQL(stmt);
+ db.close();
+
+ // Load the profile and check that migration fails.
+ do_load_profile();
+ Assert.equal(do_count_cookies(), 0);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the schema version has been reset and the backup file exists.
+ db = Services.storage.openDatabase(cookieFile);
+ Assert.equal(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
+ db.close();
+ Assert.ok(backupFile.exists());
+
+ // Clean up.
+ cookieFile.remove(false);
+ backupFile.remove(false);
+ Assert.ok(!cookieFile.exists());
+ Assert.ok(!backupFile.exists());
+ do_run_generator(generator);
+}
+
+function* run_test_4_baseDomain(generator) {
+ // Manually create a database and populate it with a bad host.
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ let badCookie = new Cookie(
+ "oh",
+ "hai",
+ ".",
+ "/",
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+ db.insertCookie(badCookie);
+ db.close();
+
+ // Load the profile and check that migration fails.
+ do_load_profile();
+ Assert.equal(do_count_cookies(), 0);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the schema version has been reset and the backup file exists.
+ db = Services.storage.openDatabase(cookieFile);
+ Assert.equal(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
+ db.close();
+ Assert.ok(backupFile.exists());
+
+ // Clean up.
+ cookieFile.remove(false);
+ backupFile.remove(false);
+ Assert.ok(!cookieFile.exists());
+ Assert.ok(!backupFile.exists());
+ do_run_generator(generator);
+}
diff --git a/netwerk/test/unit/test_cookies_thirdparty.js b/netwerk/test/unit/test_cookies_thirdparty.js
new file mode 100644
index 0000000000..54d03bd709
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_thirdparty.js
@@ -0,0 +1,164 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test third party cookie blocking, for the cases:
+// 1) with null channel
+// 2) with channel, but with no docshell parent
+
+"use strict";
+
+add_task(async () => {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // Bug 1617611 - Fix all the tests broken by "cookies SameSite=Lax by default"
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+
+ CookieXPCShellUtils.createServer({
+ hosts: ["foo.com", "bar.com", "third.com"],
+ });
+
+ function createChannel(uri, principal = null) {
+ const channel = NetUtil.newChannel({
+ uri,
+ loadingPrincipal:
+ principal ||
+ Services.scriptSecurityManager.createContentPrincipal(uri, {}),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ return channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ }
+
+ // Create URIs and channels pointing to foo.com and bar.com.
+ // We will use these to put foo.com into first and third party contexts.
+ let spec1 = "http://foo.com/foo.html";
+ let spec2 = "http://bar.com/bar.html";
+ let uri1 = NetUtil.newURI(spec1);
+ let uri2 = NetUtil.newURI(spec2);
+
+ // test with cookies enabled
+ {
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_ACCEPT
+ );
+
+ let channel1 = createChannel(uri1);
+ let channel2 = createChannel(uri2);
+
+ await do_set_cookies(uri1, channel1, true, [1, 2]);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel2, true, [1, 2]);
+ Services.cookies.removeAll();
+ }
+
+ // test with third party cookies blocked
+ {
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN
+ );
+
+ let channel1 = createChannel(uri1);
+ let channel2 = createChannel(uri2);
+
+ await do_set_cookies(uri1, channel1, true, [0, 1]);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel2, true, [0, 0]);
+ Services.cookies.removeAll();
+ }
+
+ // test with third party cookies blocked using system principal
+ {
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN
+ );
+
+ let channel1 = createChannel(
+ uri1,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ let channel2 = createChannel(
+ uri2,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await do_set_cookies(uri1, channel1, true, [0, 1]);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel2, true, [0, 0]);
+ Services.cookies.removeAll();
+ }
+
+ // Force the channel URI to be used when determining the originating URI of
+ // the channel.
+ // test with third party cookies blocked
+
+ // test with cookies enabled
+ {
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_ACCEPT
+ );
+
+ let channel1 = createChannel(uri1);
+ channel1.forceAllowThirdPartyCookie = true;
+
+ let channel2 = createChannel(uri2);
+ channel2.forceAllowThirdPartyCookie = true;
+
+ await do_set_cookies(uri1, channel1, true, [1, 2]);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel2, true, [1, 2]);
+ Services.cookies.removeAll();
+ }
+
+ // test with third party cookies blocked
+ {
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN
+ );
+
+ let channel1 = createChannel(uri1);
+ channel1.forceAllowThirdPartyCookie = true;
+
+ let channel2 = createChannel(uri2);
+ channel2.forceAllowThirdPartyCookie = true;
+
+ await do_set_cookies(uri1, channel1, true, [0, 1]);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel2, true, [0, 0]);
+ Services.cookies.removeAll();
+ }
+
+ // test with third party cookies limited
+ {
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN
+ );
+
+ let channel1 = createChannel(uri1);
+ channel1.forceAllowThirdPartyCookie = true;
+
+ let channel2 = createChannel(uri2);
+ channel2.forceAllowThirdPartyCookie = true;
+
+ await do_set_cookies(uri1, channel1, true, [0, 1]);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel2, true, [0, 0]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri1, channel1, 1);
+ await do_set_cookies(uri1, channel2, true, [1, 2]);
+ Services.cookies.removeAll();
+ }
+ Services.prefs.clearUserPref("dom.security.https_first");
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+});
diff --git a/netwerk/test/unit/test_cookies_thirdparty_session.js b/netwerk/test/unit/test_cookies_thirdparty_session.js
new file mode 100644
index 0000000000..eefd5d87f9
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_thirdparty_session.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test third party persistence across sessions, for the cases:
+// 1) network.cookie.thirdparty.sessionOnly = false
+// 2) network.cookie.thirdparty.sessionOnly = true
+
+"use strict";
+
+add_task(async () => {
+ // Set up a profile.
+ do_get_profile();
+
+ // We don't want to have CookieJarSettings blocking this test.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ // Bug 1617611 - Fix all the tests broken by "cookies SameSite=Lax by default"
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+
+ CookieXPCShellUtils.createServer({
+ hosts: ["foo.com", "bar.com", "third.com"],
+ });
+
+ // Create URIs and channels pointing to foo.com and bar.com.
+ // We will use these to put foo.com into first and third party contexts.
+ var spec1 = "http://foo.com/foo.html";
+ var spec2 = "http://bar.com/bar.html";
+ var uri1 = NetUtil.newURI(spec1);
+ var uri2 = NetUtil.newURI(spec2);
+ var channel1 = NetUtil.newChannel({
+ uri: uri1,
+ loadUsingSystemPrincipal: true,
+ });
+ var channel2 = NetUtil.newChannel({
+ uri: uri2,
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Force the channel URI to be used when determining the originating URI of
+ // the channel.
+ var httpchannel1 = channel1.QueryInterface(Ci.nsIHttpChannelInternal);
+ var httpchannel2 = channel2.QueryInterface(Ci.nsIHttpChannelInternal);
+ httpchannel1.forceAllowThirdPartyCookie = true;
+ httpchannel2.forceAllowThirdPartyCookie = true;
+
+ // test with cookies enabled, and third party cookies persistent.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref("network.cookie.thirdparty.sessionOnly", false);
+ await do_set_cookies(uri1, channel2, false, [1, 2]);
+ await do_set_cookies(uri2, channel1, true, [1, 2]);
+
+ // fake a profile change
+ await promise_close_profile();
+
+ do_load_profile();
+ Assert.equal(Services.cookies.countCookiesFromHost(uri1.host), 2);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // test with third party cookies for session only.
+ Services.prefs.setBoolPref("network.cookie.thirdparty.sessionOnly", true);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel2, false, [1, 2]);
+ await do_set_cookies(uri2, channel1, true, [1, 2]);
+
+ // fake a profile change
+ await promise_close_profile();
+
+ do_load_profile();
+ Assert.equal(Services.cookies.countCookiesFromHost(uri1.host), 0);
+ Assert.equal(Services.cookies.countCookiesFromHost(uri2.host), 0);
+ Services.prefs.clearUserPref("dom.security.https_first");
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+});
diff --git a/netwerk/test/unit/test_cookies_upgrade_10.js b/netwerk/test/unit/test_cookies_upgrade_10.js
new file mode 100644
index 0000000000..8d9e595a28
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_upgrade_10.js
@@ -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/. */
+
+"use strict";
+
+function getDBVersion(dbfile) {
+ let dbConnection = Services.storage.openDatabase(dbfile);
+ let version = dbConnection.schemaVersion;
+ dbConnection.close();
+
+ return version;
+}
+
+function indexExists(dbfile, indexname) {
+ let dbConnection = Services.storage.openDatabase(dbfile);
+ let result = dbConnection.indexExists(indexname);
+ dbConnection.close();
+
+ return result;
+}
+
+add_task(async function () {
+ try {
+ let testfile = do_get_file("data/cookies_v10.sqlite");
+ let profileDir = do_get_profile();
+
+ // Cleanup from any previous tests or failures.
+ let destFile = profileDir.clone();
+ destFile.append("cookies.sqlite");
+ if (destFile.exists()) {
+ destFile.remove(false);
+ }
+
+ testfile.copyTo(profileDir, "cookies.sqlite");
+ Assert.equal(10, getDBVersion(destFile));
+
+ Assert.ok(destFile.exists());
+
+ // Check that the index exists
+ Assert.ok(indexExists(destFile, "moz_basedomain"));
+
+ // Do something that will cause the cookie service access and upgrade the
+ // database.
+ Services.cookies.cookies;
+
+ // Pretend that we're about to shut down, to tell the cookie manager
+ // to clean up its connection with its database.
+ Services.startup.advanceShutdownPhase(
+ Services.startup.SHUTDOWN_PHASE_APPSHUTDOWN
+ );
+
+ // check for upgraded schema.
+ Assert.equal(13, getDBVersion(destFile));
+
+ // Check that the index was deleted
+ Assert.ok(!indexExists(destFile, "moz_basedomain"));
+ } catch (e) {
+ throw new Error(`FAILED: ${e}`);
+ }
+});
diff --git a/netwerk/test/unit/test_data_protocol.js b/netwerk/test/unit/test_data_protocol.js
new file mode 100644
index 0000000000..484ad469de
--- /dev/null
+++ b/netwerk/test/unit/test_data_protocol.js
@@ -0,0 +1,90 @@
+/* run some tests on the data: protocol handler */
+
+// The behaviour wrt spaces is:
+// - Textual content keeps all spaces
+// - Other content strips unescaped spaces
+// - Base64 content strips escaped and unescaped spaces
+
+"use strict";
+
+var urls = [
+ ["data:,", "text/plain", ""],
+ ["data:,foo", "text/plain", "foo"],
+ [
+ "data:application/octet-stream,foo bar",
+ "application/octet-stream",
+ "foo bar",
+ ],
+ [
+ "data:application/octet-stream,foo%20bar",
+ "application/octet-stream",
+ "foo bar",
+ ],
+ ["data:application/xhtml+xml,foo bar", "application/xhtml+xml", "foo bar"],
+ ["data:application/xhtml+xml,foo%20bar", "application/xhtml+xml", "foo bar"],
+ ["data:text/plain,foo%00 bar", "text/plain", "foo\x00 bar"],
+ ["data:text/plain;x=y,foo%00 bar", "text/plain", "foo\x00 bar"],
+ ["data:;x=y,foo%00 bar", "text/plain", "foo\x00 bar"],
+ ["data:text/plain;base64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ ["DATA:TEXT/PLAIN;BASE64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ ["DaTa:;BaSe64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ ["data:;x=y;base64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ // Bug 774240
+ [
+ "data:application/octet-stream;base64=y,foobar",
+ "application/octet-stream",
+ "foobar",
+ ],
+ ["data:text/plain;base64;x=y,dGVzdA==", "text/plain", "dGVzdA=="],
+ ["data:text/plain;x=y;base64,dGVzdA==", "text/plain", "test"],
+ ["data:text/plain;x=y;base64,", "text/plain", ""],
+ ["data: ;charset=x ; base64,WA", "text/plain", "X", "x"],
+ ["data:base64,WA", "text/plain", "WA", "US-ASCII"],
+];
+
+function run_test() {
+ dump("*** run_test\n");
+
+ function on_read_complete(request, data, idx) {
+ dump("*** run_test.on_read_complete\n");
+
+ if (request.nsIChannel.contentType != urls[idx][1]) {
+ do_throw(
+ "Type mismatch! Is <" +
+ chan.contentType +
+ ">, should be <" +
+ urls[idx][1] +
+ ">"
+ );
+ }
+
+ if (urls[idx][3] && request.nsIChannel.contentCharset !== urls[idx][3]) {
+ do_throw(
+ `Charset mismatch! Test <${urls[idx][0]}> - Is <${request.nsIChannel.contentCharset}>, should be <${urls[idx][3]}>`
+ );
+ }
+
+ /* read completed successfully. now compare the data. */
+ if (data != urls[idx][2]) {
+ do_throw(
+ "Stream contents do not match with direct read! Is <" +
+ data +
+ ">, should be <" +
+ urls[idx][2] +
+ ">"
+ );
+ }
+ do_test_finished();
+ }
+
+ for (var i = 0; i < urls.length; ++i) {
+ dump("*** opening channel " + i + "\n");
+ do_test_pending();
+ var chan = NetUtil.newChannel({
+ uri: urls[i][0],
+ loadUsingSystemPrincipal: true,
+ });
+ chan.contentType = "foo/bar"; // should be ignored
+ chan.asyncOpen(new ChannelListener(on_read_complete, i));
+ }
+}
diff --git a/netwerk/test/unit/test_defaultURI.js b/netwerk/test/unit/test_defaultURI.js
new file mode 100644
index 0000000000..36f6432db4
--- /dev/null
+++ b/netwerk/test/unit/test_defaultURI.js
@@ -0,0 +1,186 @@
+"use strict";
+
+function stringToDefaultURI(str) {
+ return Cc["@mozilla.org/network/default-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec(str)
+ .finalize();
+}
+
+add_task(function test_getters() {
+ let uri = stringToDefaultURI(
+ "proto://user:password@hostname:123/path/to/file?query#hash"
+ );
+ equal(uri.spec, "proto://user:password@hostname:123/path/to/file?query#hash");
+ equal(uri.prePath, "proto://user:password@hostname:123");
+ equal(uri.scheme, "proto");
+ equal(uri.userPass, "user:password");
+ equal(uri.username, "user");
+ equal(uri.password, "password");
+ equal(uri.hasUserPass, true);
+ equal(uri.hostPort, "hostname:123");
+ equal(uri.host, "hostname");
+ equal(uri.port, 123);
+ equal(uri.pathQueryRef, "/path/to/file?query#hash");
+ equal(uri.asciiSpec, uri.spec);
+ equal(uri.asciiHostPort, uri.hostPort);
+ equal(uri.asciiHost, uri.host);
+ equal(uri.ref, "hash");
+ equal(
+ uri.specIgnoringRef,
+ "proto://user:password@hostname:123/path/to/file?query"
+ );
+ equal(uri.hasRef, true);
+ equal(uri.filePath, "/path/to/file");
+ equal(uri.query, "query");
+ equal(uri.displayHost, uri.host);
+ equal(uri.displayHostPort, uri.hostPort);
+ equal(uri.displaySpec, uri.spec);
+ equal(uri.displayPrePath, uri.prePath);
+});
+
+add_task(function test_methods() {
+ let uri = stringToDefaultURI(
+ "proto://user:password@hostname:123/path/to/file?query#hash"
+ );
+ let uri_same = stringToDefaultURI(
+ "proto://user:password@hostname:123/path/to/file?query#hash"
+ );
+ let uri_different = stringToDefaultURI(
+ "proto://user:password@hostname:123/path/to/file?query"
+ );
+ let uri_very_different = stringToDefaultURI(
+ "proto://user:password@hostname:123/path/to/file?query1#hash"
+ );
+ ok(uri.equals(uri_same));
+ ok(!uri.equals(uri_different));
+ ok(uri.schemeIs("proto"));
+ ok(!uri.schemeIs("proto2"));
+ ok(!uri.schemeIs("proto "));
+ ok(!uri.schemeIs("proto\n"));
+ equal(uri.resolve("/hello"), "proto://user:password@hostname:123/hello");
+ equal(
+ uri.resolve("hello"),
+ "proto://user:password@hostname:123/path/to/hello"
+ );
+ equal(uri.resolve("proto2:otherhost"), "proto2:otherhost");
+ ok(uri.equalsExceptRef(uri_same));
+ ok(uri.equalsExceptRef(uri_different));
+ ok(!uri.equalsExceptRef(uri_very_different));
+});
+
+add_task(function test_mutator() {
+ let uri = stringToDefaultURI(
+ "proto://user:pass@host:123/path/to/file?query#hash"
+ );
+
+ let check = (callSetters, verify) => {
+ let m = uri.mutate();
+ callSetters(m);
+ verify(m.finalize());
+ };
+
+ check(
+ m => m.setSpec("test:bla"),
+ u => equal(u.spec, "test:bla")
+ );
+ check(
+ m => m.setSpec("test:bla"),
+ u => equal(u.spec, "test:bla")
+ );
+ check(
+ m => m.setScheme("some"),
+ u => equal(u.spec, "some://user:pass@host:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setUserPass("u"),
+ u => equal(u.spec, "proto://u@host:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setUserPass("u:p"),
+ u => equal(u.spec, "proto://u:p@host:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setUserPass(":p"),
+ u => equal(u.spec, "proto://:p@host:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setUserPass(""),
+ u => equal(u.spec, "proto://host:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setUsername("u"),
+ u => equal(u.spec, "proto://u:pass@host:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setPassword("p"),
+ u => equal(u.spec, "proto://user:p@host:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setHostPort("h"),
+ u => equal(u.spec, "proto://user:pass@h:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setHostPort("h:456"),
+ u => equal(u.spec, "proto://user:pass@h:456/path/to/file?query#hash")
+ );
+ check(
+ m => m.setHost("bla"),
+ u => equal(u.spec, "proto://user:pass@bla:123/path/to/file?query#hash")
+ );
+ check(
+ m => m.setPort(987),
+ u => equal(u.spec, "proto://user:pass@host:987/path/to/file?query#hash")
+ );
+ check(
+ m => m.setPathQueryRef("/p?q#r"),
+ u => equal(u.spec, "proto://user:pass@host:123/p?q#r")
+ );
+ check(
+ m => m.setRef("r"),
+ u => equal(u.spec, "proto://user:pass@host:123/path/to/file?query#r")
+ );
+ check(
+ m => m.setFilePath("/my/path"),
+ u => equal(u.spec, "proto://user:pass@host:123/my/path?query#hash")
+ );
+ check(
+ m => m.setQuery("q"),
+ u => equal(u.spec, "proto://user:pass@host:123/path/to/file?q#hash")
+ );
+});
+
+add_task(function test_ipv6() {
+ let uri = stringToDefaultURI("non-special://[2001::1]/");
+ equal(uri.hostPort, "[2001::1]");
+ // Hopefully this will change after bug 1603199.
+ equal(uri.host, "2001::1");
+});
+
+add_task(function test_serialization() {
+ let uri = stringToDefaultURI("http://example.org/path");
+ let str = serialize_to_escaped_string(uri);
+ let other = deserialize_from_escaped_string(str).QueryInterface(Ci.nsIURI);
+ equal(other.spec, uri.spec);
+});
+
+// This test assumes the serialization never changes, which might not be true.
+// It's OK to change the test if we ever make changes to the serialization
+// code and this starts failing.
+add_task(function test_deserialize_from_string() {
+ let payload =
+ "%04DZ%A0%FD%27L%99%BDAk%E61%8A%E9%2C%00%00%00%00%00%00%00" +
+ "%00%C0%00%00%00%00%00%00F%00%00%00%13scheme%3Astuff/to/say";
+ equal(
+ deserialize_from_escaped_string(payload).QueryInterface(Ci.nsIURI).spec,
+ stringToDefaultURI("scheme:stuff/to/say").spec
+ );
+
+ let payload2 =
+ "%04DZ%A0%FD%27L%99%BDAk%E61%8A%E9%2C%00%00%00%00%00%00%00" +
+ "%00%C0%00%00%00%00%00%00F%00%00%00%17http%3A//example.org/path";
+ equal(
+ deserialize_from_escaped_string(payload2).QueryInterface(Ci.nsIURI).spec,
+ stringToDefaultURI("http://example.org/path").spec
+ );
+});
diff --git a/netwerk/test/unit/test_dns_by_type_resolve.js b/netwerk/test/unit/test_dns_by_type_resolve.js
new file mode 100644
index 0000000000..26b087f301
--- /dev/null
+++ b/netwerk/test/unit/test_dns_by_type_resolve.js
@@ -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/. */
+
+"use strict";
+
+let h2Port;
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+add_setup(async function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ trr_test_setup();
+ registerCleanupFunction(() => {
+ trr_clear_prefs();
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+let test_answer = "bXkgdm9pY2UgaXMgbXkgcGFzc3dvcmQ=";
+let test_answer_addr = "127.0.0.1";
+
+add_task(async function testTXTResolve() {
+ // use the h2 server as DOH provider
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/doh"
+ );
+
+ let { inRecord } = await new TRRDNSListener("_esni.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_TXT,
+ });
+
+ let answer = inRecord
+ .QueryInterface(Ci.nsIDNSTXTRecord)
+ .getRecordsAsOneString();
+ Assert.equal(answer, test_answer, "got correct answer");
+});
+
+// verify TXT record pushed on a A record request
+add_task(async function testTXTRecordPushPart1() {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/txt-dns-push"
+ );
+ let { inRecord } = await new TRRDNSListener("_esni_push.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ expectedAnswer: "127.0.0.1",
+ });
+
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let answer = inRecord.getNextAddrAsString();
+ Assert.equal(answer, test_answer_addr, "got correct answer");
+});
+
+// verify the TXT pushed record
+add_task(async function testTXTRecordPushPart2() {
+ // At this point the second host name should've been pushed and we can resolve it using
+ // cache only. Set back the URI to a path that fails.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/404"
+ );
+ let { inRecord } = await new TRRDNSListener("_esni_push.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_TXT,
+ });
+
+ let answer = inRecord
+ .QueryInterface(Ci.nsIDNSTXTRecord)
+ .getRecordsAsOneString();
+ Assert.equal(answer, test_answer, "got correct answer");
+});
diff --git a/netwerk/test/unit/test_dns_cancel.js b/netwerk/test/unit/test_dns_cancel.js
new file mode 100644
index 0000000000..c20e63ae4c
--- /dev/null
+++ b/netwerk/test/unit/test_dns_cancel.js
@@ -0,0 +1,119 @@
+"use strict";
+
+var hostname1 = "";
+var hostname2 = "";
+var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+for (var i = 0; i < 20; i++) {
+ hostname1 += possible.charAt(Math.floor(Math.random() * possible.length));
+ hostname2 += possible.charAt(Math.floor(Math.random() * possible.length));
+}
+
+var requestList1Canceled2;
+var requestList1NotCanceled;
+
+var requestList2Canceled;
+var requestList2NotCanceled;
+
+var listener1 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ // One request should be resolved and two request should be canceled.
+ if (inRequest == requestList1NotCanceled) {
+ // This request should not be canceled.
+ Assert.notEqual(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+};
+
+var listener2 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ // One request should be resolved and the other canceled.
+ if (inRequest == requestList2NotCanceled) {
+ // The request should not be canceled.
+ Assert.notEqual(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+};
+
+const defaultOriginAttributes = {};
+
+function run_test() {
+ var mainThread = Services.tm.currentThread;
+
+ var flags = Ci.nsIDNSService.RESOLVE_BYPASS_CACHE;
+
+ // This one will be canceled with cancelAsyncResolve.
+ Services.dns.asyncResolve(
+ hostname2,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener1,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Services.dns.cancelAsyncResolve(
+ hostname2,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener1,
+ Cr.NS_ERROR_ABORT,
+ defaultOriginAttributes
+ );
+
+ // This one will not be canceled.
+ requestList1NotCanceled = Services.dns.asyncResolve(
+ hostname1,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener1,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+ requestList1Canceled2 = Services.dns.asyncResolve(
+ hostname1,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener1,
+ mainThread,
+ defaultOriginAttributes
+ );
+ requestList1Canceled2.cancel(Cr.NS_ERROR_ABORT);
+
+ // This one will not be canceled.
+ requestList2NotCanceled = Services.dns.asyncResolve(
+ hostname1,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener2,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+ requestList2Canceled = Services.dns.asyncResolve(
+ hostname2,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener2,
+ mainThread,
+ defaultOriginAttributes
+ );
+ requestList2Canceled.cancel(Cr.NS_ERROR_ABORT);
+
+ do_test_pending();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_dns_disable_ipv4.js b/netwerk/test/unit/test_dns_disable_ipv4.js
new file mode 100644
index 0000000000..286964341e
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv4.js
@@ -0,0 +1,68 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV4 flag doesn't
+// return any IPv4 addresses.
+//
+
+"use strict";
+
+const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+const defaultOriginAttributes = {};
+
+add_task(async function test_none() {
+ let [, inRecord] = await new Promise(resolve => {
+ let listener = {
+ onLookupComplete(inRequest, inRecord1, inStatus) {
+ resolve([inRequest, inRecord1, inStatus]);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+ };
+
+ Services.dns.asyncResolve(
+ "example.org",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ null, // resolverInfo
+ listener,
+ null,
+ defaultOriginAttributes
+ );
+ });
+
+ if (inRecord && inRecord.QueryInterface(Ci.nsIDNSAddrRecord)) {
+ while (inRecord.hasMore()) {
+ let nextIP = inRecord.getNextAddrAsString();
+ ok(nextIP.includes(":"), `${nextIP} should be IPv6`);
+ }
+ }
+});
+
+add_task(async function test_some() {
+ Services.dns.clearCache(true);
+ gOverride.addIPOverride("example.com", "1.1.1.1");
+ gOverride.addIPOverride("example.org", "::1:2:3");
+ let [, inRecord] = await new Promise(resolve => {
+ let listener = {
+ onLookupComplete(inRequest, inRecord1, inStatus) {
+ resolve([inRequest, inRecord1, inStatus]);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+ };
+
+ Services.dns.asyncResolve(
+ "example.org",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ null, // resolverInfo
+ listener,
+ null,
+ defaultOriginAttributes
+ );
+ });
+
+ ok(inRecord.QueryInterface(Ci.nsIDNSAddrRecord));
+ equal(inRecord.getNextAddrAsString(), "::1:2:3");
+ equal(inRecord.hasMore(), false);
+});
diff --git a/netwerk/test/unit/test_dns_disable_ipv6.js b/netwerk/test/unit/test_dns_disable_ipv6.js
new file mode 100644
index 0000000000..d05c56091f
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv6.js
@@ -0,0 +1,51 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV6 flag doesn't
+// return any IPv6 addresses.
+//
+
+"use strict";
+
+var listener = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ if (inStatus != Cr.NS_OK) {
+ Assert.equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+ do_test_finished();
+ return;
+ }
+
+ while (true) {
+ try {
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ // If there is an answer it should be an IPv4 address
+ dump(answer);
+ Assert.ok(!answer.includes(":"));
+ Assert.ok(answer.includes("."));
+ } catch (e) {
+ break;
+ }
+ }
+ do_test_finished();
+ },
+};
+
+const defaultOriginAttributes = {};
+
+function run_test() {
+ do_test_pending();
+ try {
+ Services.dns.asyncResolve(
+ "example.com",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ null, // resolverInfo
+ listener,
+ null,
+ defaultOriginAttributes
+ );
+ } catch (e) {
+ dump(e);
+ Assert.ok(false);
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/unit/test_dns_disabled.js b/netwerk/test/unit/test_dns_disabled.js
new file mode 100644
index 0000000000..cfffd5530f
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disabled.js
@@ -0,0 +1,88 @@
+"use strict";
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+const mainThread = Services.tm.currentThread;
+
+function makeListenerBlock(next) {
+ return {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.ok(!Components.isSuccessCode(inStatus));
+ next();
+ },
+ };
+}
+
+function makeListenerDontBlock(next, expectedAnswer) {
+ return {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.equal(inStatus, Cr.NS_OK);
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ if (expectedAnswer) {
+ Assert.equal(answer, expectedAnswer);
+ } else {
+ Assert.ok(answer == "127.0.0.1" || answer == "::1");
+ }
+ next();
+ },
+ };
+}
+
+function do_test({ dnsDisabled, mustBlock, testDomain, expectedAnswer }) {
+ return new Promise(resolve => {
+ Services.prefs.setBoolPref("network.dns.disabled", dnsDisabled);
+ try {
+ Services.dns.asyncResolve(
+ testDomain,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ mustBlock
+ ? makeListenerBlock(resolve)
+ : makeListenerDontBlock(resolve, expectedAnswer),
+ mainThread,
+ {} // Default origin attributes
+ );
+ } catch (e) {
+ Assert.ok(mustBlock === true);
+ resolve();
+ }
+ });
+}
+
+function setup() {
+ override.addIPOverride("foo.bar", "127.0.0.1");
+ registerCleanupFunction(function () {
+ override.clearOverrides();
+ Services.prefs.clearUserPref("network.dns.disabled");
+ });
+}
+setup();
+
+// IP literals should be resolved even if dns is disabled
+add_task(async function testIPLiteral() {
+ return do_test({
+ dnsDisabled: true,
+ mustBlock: false,
+ testDomain: "0x01010101",
+ expectedAnswer: "1.1.1.1",
+ });
+});
+
+add_task(async function testBlocked() {
+ return do_test({
+ dnsDisabled: true,
+ mustBlock: true,
+ testDomain: "foo.bar",
+ });
+});
+
+add_task(async function testNotBlocked() {
+ return do_test({
+ dnsDisabled: false,
+ mustBlock: false,
+ testDomain: "foo.bar",
+ });
+});
diff --git a/netwerk/test/unit/test_dns_localredirect.js b/netwerk/test/unit/test_dns_localredirect.js
new file mode 100644
index 0000000000..3ca432f477
--- /dev/null
+++ b/netwerk/test/unit/test_dns_localredirect.js
@@ -0,0 +1,59 @@
+"use strict";
+
+var prefs = Services.prefs;
+
+var nextTest;
+
+var listener = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ Assert.ok(answer == "127.0.0.1" || answer == "::1");
+
+ nextTest();
+ do_test_finished();
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+};
+
+const defaultOriginAttributes = {};
+
+function run_test() {
+ prefs.setCharPref("network.dns.localDomains", "local.vingtetun.org");
+
+ var mainThread = Services.tm.currentThread;
+ nextTest = do_test_2;
+ Services.dns.asyncResolve(
+ "local.vingtetun.org",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ do_test_pending();
+}
+
+function do_test_2() {
+ var mainThread = Services.tm.currentThread;
+ nextTest = testsDone;
+ prefs.setCharPref("network.dns.forceResolve", "localhost");
+ Services.dns.asyncResolve(
+ "www.example.com",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ do_test_pending();
+}
+
+function testsDone() {
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.dns.forceResolve");
+}
diff --git a/netwerk/test/unit/test_dns_offline.js b/netwerk/test/unit/test_dns_offline.js
new file mode 100644
index 0000000000..db9c436292
--- /dev/null
+++ b/netwerk/test/unit/test_dns_offline.js
@@ -0,0 +1,105 @@
+"use strict";
+
+var ioService = Services.io;
+var prefs = Services.prefs;
+var mainThread = Services.tm.currentThread;
+
+var listener1 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.equal(inStatus, Cr.NS_ERROR_OFFLINE);
+ test2();
+ do_test_finished();
+ },
+};
+
+var listener2 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.equal(inStatus, Cr.NS_OK);
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ Assert.ok(answer == "127.0.0.1" || answer == "::1");
+ test3();
+ do_test_finished();
+ },
+};
+
+var listener3 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.equal(inStatus, Cr.NS_OK);
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ Assert.ok(answer == "127.0.0.1" || answer == "::1");
+ cleanup();
+ do_test_finished();
+ },
+};
+
+const defaultOriginAttributes = {};
+
+function run_test() {
+ do_test_pending();
+ prefs.setBoolPref("network.dns.offline-localhost", false);
+ // We always resolve localhost as it's hardcoded without the following pref:
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ ioService.offline = true;
+ try {
+ Services.dns.asyncResolve(
+ "localhost",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener1,
+ mainThread,
+ defaultOriginAttributes
+ );
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_OFFLINE);
+ test2();
+ do_test_finished();
+ }
+}
+
+function test2() {
+ do_test_pending();
+ prefs.setBoolPref("network.dns.offline-localhost", true);
+ ioService.offline = false;
+ ioService.offline = true;
+ // we need to let the main thread run and apply the changes
+ do_timeout(0, test2Continued);
+}
+
+function test2Continued() {
+ Services.dns.asyncResolve(
+ "localhost",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener2,
+ mainThread,
+ defaultOriginAttributes
+ );
+}
+
+function test3() {
+ do_test_pending();
+ ioService.offline = false;
+ // we need to let the main thread run and apply the changes
+ do_timeout(0, test3Continued);
+}
+
+function test3Continued() {
+ Services.dns.asyncResolve(
+ "localhost",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener3,
+ mainThread,
+ defaultOriginAttributes
+ );
+}
+
+function cleanup() {
+ prefs.clearUserPref("network.dns.offline-localhost");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+}
diff --git a/netwerk/test/unit/test_dns_onion.js b/netwerk/test/unit/test_dns_onion.js
new file mode 100644
index 0000000000..928753f71f
--- /dev/null
+++ b/netwerk/test/unit/test_dns_onion.js
@@ -0,0 +1,76 @@
+"use strict";
+
+var mainThread = Services.tm.currentThread;
+
+var onionPref;
+var localdomainPref;
+var prefs = Services.prefs;
+
+// check that we don't lookup .onion
+var listenerBlock = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.ok(!Components.isSuccessCode(inStatus));
+ do_test_dontBlock();
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+};
+
+// check that we do lookup .onion (via pref)
+var listenerDontBlock = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ Assert.ok(answer == "127.0.0.1" || answer == "::1");
+ all_done();
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+};
+
+const defaultOriginAttributes = {};
+
+function do_test_dontBlock() {
+ prefs.setBoolPref("network.dns.blockDotOnion", false);
+ Services.dns.asyncResolve(
+ "private.onion",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listenerDontBlock,
+ mainThread,
+ defaultOriginAttributes
+ );
+}
+
+function do_test_block() {
+ prefs.setBoolPref("network.dns.blockDotOnion", true);
+ try {
+ Services.dns.asyncResolve(
+ "private.onion",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listenerBlock,
+ mainThread,
+ defaultOriginAttributes
+ );
+ } catch (e) {
+ // it is ok for this negative test to fail fast
+ Assert.ok(true);
+ do_test_dontBlock();
+ }
+}
+
+function all_done() {
+ // reset locally modified prefs
+ prefs.setCharPref("network.dns.localDomains", localdomainPref);
+ prefs.setBoolPref("network.dns.blockDotOnion", onionPref);
+ do_test_finished();
+}
+
+function run_test() {
+ onionPref = prefs.getBoolPref("network.dns.blockDotOnion");
+ localdomainPref = prefs.getCharPref("network.dns.localDomains");
+ prefs.setCharPref("network.dns.localDomains", "private.onion");
+ do_test_block();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_dns_originAttributes.js b/netwerk/test/unit/test_dns_originAttributes.js
new file mode 100644
index 0000000000..39e2a4f0f1
--- /dev/null
+++ b/netwerk/test/unit/test_dns_originAttributes.js
@@ -0,0 +1,93 @@
+"use strict";
+
+var prefs = Services.prefs;
+var mainThread = Services.tm.currentThread;
+
+var listener1 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.equal(inStatus, Cr.NS_OK);
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ Assert.ok(answer == "127.0.0.1" || answer == "::1");
+ test2();
+ do_test_finished();
+ },
+};
+
+var listener2 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.equal(inStatus, Cr.NS_OK);
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ var answer = inRecord.getNextAddrAsString();
+ Assert.ok(answer == "127.0.0.1" || answer == "::1");
+ test3();
+ do_test_finished();
+ },
+};
+
+var listener3 = {
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ Assert.equal(inStatus, Cr.NS_ERROR_OFFLINE);
+ cleanup();
+ do_test_finished();
+ },
+};
+
+const firstOriginAttributes = { userContextId: 1 };
+const secondOriginAttributes = { userContextId: 2 };
+
+// First, we resolve the address normally for first originAttributes.
+function run_test() {
+ do_test_pending();
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ Services.dns.asyncResolve(
+ "localhost",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener1,
+ mainThread,
+ firstOriginAttributes
+ );
+}
+
+// Second, we resolve the same address offline to see whether its DNS cache works
+// correctly.
+function test2() {
+ do_test_pending();
+ Services.dns.asyncResolve(
+ "localhost",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_OFFLINE,
+ null, // resolverInfo
+ listener2,
+ mainThread,
+ firstOriginAttributes
+ );
+}
+
+// Third, we resolve the same address offline again with different originAttributes.
+// This resolving should fail since the DNS cache of the given address is not exist
+// for this originAttributes.
+function test3() {
+ do_test_pending();
+ try {
+ Services.dns.asyncResolve(
+ "localhost",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_OFFLINE,
+ null, // resolverInfo
+ listener3,
+ mainThread,
+ secondOriginAttributes
+ );
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_ERROR_OFFLINE);
+ cleanup();
+ do_test_finished();
+ }
+}
+
+function cleanup() {
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+}
diff --git a/netwerk/test/unit/test_dns_override.js b/netwerk/test/unit/test_dns_override.js
new file mode 100644
index 0000000000..f092dd531c
--- /dev/null
+++ b/netwerk/test/unit/test_dns_override.js
@@ -0,0 +1,515 @@
+"use strict";
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+const defaultOriginAttributes = {};
+const mainThread = Services.tm.currentThread;
+
+class Listener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+
+ async firstAddress() {
+ let all = await this.addresses();
+ if (all.length) {
+ return all[0];
+ }
+
+ return undefined;
+ }
+
+ async addresses() {
+ let [, inRecord] = await this.promise;
+ let addresses = [];
+ if (!inRecord) {
+ return addresses; // returns []
+ }
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ while (inRecord.hasMore()) {
+ addresses.push(inRecord.getNextAddrAsString());
+ }
+ return addresses;
+ }
+
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+Listener.prototype.QueryInterface = ChromeUtils.generateQI(["nsIDNSListener"]);
+
+const DOMAIN = "example.org";
+const OTHER = "example.com";
+
+add_setup(async function setup() {
+ trr_test_setup();
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ });
+});
+
+add_task(async function test_bad_IPs() {
+ Assert.throws(
+ () => override.addIPOverride(DOMAIN, DOMAIN),
+ /NS_ERROR_UNEXPECTED/,
+ "Should throw if input is not an IP address"
+ );
+ Assert.throws(
+ () => override.addIPOverride(DOMAIN, ""),
+ /NS_ERROR_UNEXPECTED/,
+ "Should throw if input is not an IP address"
+ );
+ Assert.throws(
+ () => override.addIPOverride(DOMAIN, " "),
+ /NS_ERROR_UNEXPECTED/,
+ "Should throw if input is not an IP address"
+ );
+ Assert.throws(
+ () => override.addIPOverride(DOMAIN, "1-2-3-4"),
+ /NS_ERROR_UNEXPECTED/,
+ "Should throw if input is not an IP address"
+ );
+});
+
+add_task(async function test_ipv4() {
+ let listener = new Listener();
+ override.addIPOverride(DOMAIN, "1.2.3.4");
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.equal(await listener.firstAddress(), "1.2.3.4");
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_ipv6() {
+ let listener = new Listener();
+ override.addIPOverride(DOMAIN, "fe80::6a99:9b2b:6ccc:6e1b");
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.equal(await listener.firstAddress(), "fe80::6a99:9b2b:6ccc:6e1b");
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_clearOverrides() {
+ let listener = new Listener();
+ override.addIPOverride(DOMAIN, "1.2.3.4");
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.equal(await listener.firstAddress(), "1.2.3.4");
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+
+ listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.notEqual(await listener.firstAddress(), "1.2.3.4");
+
+ await new Promise(resolve => do_timeout(1000, resolve));
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_clearHostOverride() {
+ override.addIPOverride(DOMAIN, "2.2.2.2");
+ override.addIPOverride(OTHER, "2.2.2.2");
+ override.clearHostOverride(DOMAIN);
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ Assert.notEqual(await listener.firstAddress(), "2.2.2.2");
+
+ listener = new Listener();
+ Services.dns.asyncResolve(
+ OTHER,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.equal(await listener.firstAddress(), "2.2.2.2");
+
+ // Note: this test will use the actual system resolver. On windows we do a
+ // second async call to the system libraries to get the TTL values, which
+ // keeps the record alive after the onLookupComplete()
+ // We need to wait for a bit, until the second call is finished before we
+ // can clear the cache to make sure we evict everything.
+ // If the next task ever starts failing, with an IP that is not in this
+ // file, then likely the timeout is too small.
+ await new Promise(resolve => do_timeout(1000, resolve));
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_multiple_IPs() {
+ override.addIPOverride(DOMAIN, "2.2.2.2");
+ override.addIPOverride(DOMAIN, "1.1.1.1");
+ override.addIPOverride(DOMAIN, "::1");
+ override.addIPOverride(DOMAIN, "fe80::6a99:9b2b:6ccc:6e1b");
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.deepEqual(await listener.addresses(), [
+ "2.2.2.2",
+ "1.1.1.1",
+ "::1",
+ "fe80::6a99:9b2b:6ccc:6e1b",
+ ]);
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_address_family_flags() {
+ override.addIPOverride(DOMAIN, "2.2.2.2");
+ override.addIPOverride(DOMAIN, "1.1.1.1");
+ override.addIPOverride(DOMAIN, "::1");
+ override.addIPOverride(DOMAIN, "fe80::6a99:9b2b:6ccc:6e1b");
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.deepEqual(await listener.addresses(), [
+ "::1",
+ "fe80::6a99:9b2b:6ccc:6e1b",
+ ]);
+
+ listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.deepEqual(await listener.addresses(), ["2.2.2.2", "1.1.1.1"]);
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_cname_flag() {
+ override.addIPOverride(DOMAIN, "2.2.2.2");
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ let [, inRecord] = await listener;
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ Assert.throws(
+ () => inRecord.canonicalName,
+ /NS_ERROR_NOT_AVAILABLE/,
+ "No canonical name flag"
+ );
+ Assert.equal(inRecord.getNextAddrAsString(), "2.2.2.2");
+
+ listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ [, inRecord] = await listener;
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ Assert.equal(inRecord.canonicalName, DOMAIN, "No canonical name specified");
+ Assert.equal(inRecord.getNextAddrAsString(), "2.2.2.2");
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+
+ override.addIPOverride(DOMAIN, "2.2.2.2");
+ override.setCnameOverride(DOMAIN, OTHER);
+ listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ [, inRecord] = await listener;
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ Assert.equal(inRecord.canonicalName, OTHER, "Must have correct CNAME");
+ Assert.equal(inRecord.getNextAddrAsString(), "2.2.2.2");
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_nxdomain() {
+ override.addIPOverride(DOMAIN, "N/A");
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [, , inStatus] = await listener;
+ equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+function hexToUint8Array(hex) {
+ return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
+}
+
+add_task(
+ {
+ skip_if: () => mozinfo.os == "win" || mozinfo.os == "android",
+ },
+ async function test_https_record_override() {
+ let trrServer = new TRRServer();
+ await trrServer.start();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+
+ await trrServer.registerDoHAnswers("service.com", "HTTPS", {
+ answers: [
+ {
+ name: "service.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: ".",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "no-default-alpn" },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: "1.2.3.4" },
+ { key: "echconfig", value: "123..." },
+ { key: "ipv6hint", value: "::1" },
+ { key: "odoh", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "service.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.com",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] },
+ { key: "echconfig", value: "abc..." },
+ { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] },
+ { key: "odoh", value: "def..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ let chan = makeChan(
+ `https://foo.example.com:${trrServer.port()}/dnsAnswer?host=service.com&type=HTTPS`
+ );
+ let [, buf] = await channelOpenPromise(chan);
+ let rawBuffer = hexToUint8Array(buf);
+
+ override.addHTTPSRecordOverride("service.com", rawBuffer, rawBuffer.length);
+
+ Services.prefs.setBoolPref("network.dns.native_https_query", true);
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.native_https_query");
+ });
+
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ "service.com",
+ Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [, inRecord, inStatus] = await listener;
+ equal(inStatus, Cr.NS_OK);
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ equal(answer[0].priority, 1);
+ equal(answer[0].name, "service.com");
+ Assert.deepEqual(
+ answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
+ ["h2", "h3"],
+ "got correct answer"
+ );
+ Assert.ok(
+ answer[0].values[1].QueryInterface(Ci.nsISVCParamNoDefaultAlpn),
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[2].QueryInterface(Ci.nsISVCParamPort).port,
+ 8888,
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[3].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
+ .address,
+ "1.2.3.4",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[4].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
+ "123...",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[5].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
+ .address,
+ "::1",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[6].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
+ "456...",
+ "got correct answer"
+ );
+
+ Assert.equal(answer[1].priority, 2);
+ Assert.equal(answer[1].name, "test.com");
+ Assert.equal(answer[1].values.length, 5);
+ Assert.deepEqual(
+ answer[1].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
+ ["h2"],
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
+ .address,
+ "1.2.3.4",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[1]
+ .address,
+ "5.6.7.8",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[2].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
+ "abc...",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
+ .address,
+ "::1",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[1]
+ .address,
+ "fe80::794f:6d2c:3d5e:7836",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[4].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
+ "def...",
+ "got correct answer"
+ );
+ }
+);
diff --git a/netwerk/test/unit/test_dns_override_for_localhost.js b/netwerk/test/unit/test_dns_override_for_localhost.js
new file mode 100644
index 0000000000..ecd708ee53
--- /dev/null
+++ b/netwerk/test/unit/test_dns_override_for_localhost.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+const defaultOriginAttributes = {};
+const mainThread = Services.tm.currentThread;
+
+class Listener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+
+ async addresses() {
+ let [, inRecord] = await this.promise;
+ let addresses = [];
+ if (!inRecord) {
+ return addresses; // returns []
+ }
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ while (inRecord.hasMore()) {
+ addresses.push(inRecord.getNextAddrAsString());
+ }
+ return addresses;
+ }
+
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+Listener.prototype.QueryInterface = ChromeUtils.generateQI(["nsIDNSListener"]);
+
+const DOMAINS = [
+ "localhost",
+ "localhost.",
+ "vhost.localhost",
+ "vhost.localhost.",
+];
+DOMAINS.forEach(domain => {
+ add_task(async function test_() {
+ let listener1 = new Listener();
+ const overrides = ["1.2.3.4", "5.6.7.8"];
+ overrides.forEach(ip_address => {
+ override.addIPOverride(domain, ip_address);
+ });
+
+ // Verify that loopback host names are not overridden.
+ Services.dns.asyncResolve(
+ domain,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener1,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.deepEqual(
+ await listener1.addresses(),
+ ["127.0.0.1", "::1"],
+ `${domain} is not overridden`
+ );
+
+ // Verify that if localhost hijacking is enabled, the overrides
+ // registered above are taken into account.
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ let listener2 = new Listener();
+ Services.dns.asyncResolve(
+ domain,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener2,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.deepEqual(
+ await listener2.addresses(),
+ overrides,
+ `${domain} is overridden`
+ );
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+
+ Services.dns.clearCache(false);
+ override.clearOverrides();
+ });
+});
diff --git a/netwerk/test/unit/test_dns_proxy_bypass.js b/netwerk/test/unit/test_dns_proxy_bypass.js
new file mode 100644
index 0000000000..d53efc3dd3
--- /dev/null
+++ b/netwerk/test/unit/test_dns_proxy_bypass.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var prefs = Services.prefs;
+
+function setup() {
+ prefs.setBoolPref("network.dns.notifyResolution", true);
+ prefs.setCharPref("network.proxy.socks", "127.0.0.1");
+ prefs.setIntPref("network.proxy.socks_port", 9000);
+ prefs.setIntPref("network.proxy.type", 1);
+ prefs.setBoolPref("network.proxy.socks_remote_dns", true);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ prefs.clearUserPref("network.proxy.socks");
+ prefs.clearUserPref("network.proxy.socks_port");
+ prefs.clearUserPref("network.proxy.type");
+ prefs.clearUserPref("network.proxy.socks_remote_dns");
+ prefs.clearUserPref("network.dns.notifyResolution");
+});
+
+var url = "ws://dnsleak.example.com";
+
+var dnsRequestObserver = {
+ register() {
+ this.obs = Services.obs;
+ this.obs.addObserver(this, "dns-resolution-request");
+ },
+
+ unregister() {
+ if (this.obs) {
+ this.obs.removeObserver(this, "dns-resolution-request");
+ }
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "dns-resolution-request") {
+ Assert.ok(!data.includes("dnsleak.example.com"), `no dnsleak: ${data}`);
+ }
+ },
+};
+
+function WSListener(closure) {
+ this._closure = closure;
+}
+WSListener.prototype = {
+ onAcknowledge(aContext, aSize) {},
+ onBinaryMessageAvailable(aContext, aMsg) {},
+ onMessageAvailable(aContext, aMsg) {},
+ onServerClose(aContext, aCode, aReason) {},
+ onStart(aContext) {},
+ onStop(aContext, aStatusCode) {
+ dnsRequestObserver.unregister();
+ this._closure();
+ },
+};
+
+add_task(async function test_dns_websocket_channel() {
+ dnsRequestObserver.register();
+
+ var chan = Cc["@mozilla.org/network/protocol;1?name=ws"].createInstance(
+ Ci.nsIWebSocketChannel
+ );
+
+ var uri = Services.io.newURI(url);
+ chan.initLoadInfo(
+ null, // aLoadingNode
+ Services.scriptSecurityManager.createContentPrincipal(uri, {}),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET
+ );
+
+ await new Promise(resolve =>
+ chan.asyncOpen(uri, url, {}, 0, new WSListener(resolve), null)
+ );
+});
+
+add_task(async function test_dns_resolve_proxy() {
+ dnsRequestObserver.register();
+
+ let { error } = await new TRRDNSListener("dnsleak.example.com", {
+ expectEarlyFail: true,
+ });
+ Assert.equal(
+ error.result,
+ Cr.NS_ERROR_UNKNOWN_PROXY_HOST,
+ "error is NS_ERROR_UNKNOWN_PROXY_HOST"
+ );
+ dnsRequestObserver.unregister();
+});
diff --git a/netwerk/test/unit/test_dns_retry.js b/netwerk/test/unit/test_dns_retry.js
new file mode 100644
index 0000000000..24688417fa
--- /dev/null
+++ b/netwerk/test/unit/test_dns_retry.js
@@ -0,0 +1,319 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+trr_test_setup();
+let httpServerIPv4 = new HttpServer();
+let httpServerIPv6 = new HttpServer();
+let trrServer;
+let testpath = "/simple";
+let httpbody = "0123456789";
+let CC_IPV4 = "example_cc_ipv4.com";
+let CC_IPV6 = "example_cc_ipv6.com";
+Services.prefs.clearUserPref("network.dns.native-is-localhost");
+
+ChromeUtils.defineLazyGetter(this, "URL_CC_IPV4", function () {
+ return `http://${CC_IPV4}:${httpServerIPv4.identity.primaryPort}${testpath}`;
+});
+ChromeUtils.defineLazyGetter(this, "URL_CC_IPV6", function () {
+ return `http://${CC_IPV6}:${httpServerIPv6.identity.primaryPort}${testpath}`;
+});
+ChromeUtils.defineLazyGetter(this, "URL6a", function () {
+ return `http://example6a.com:${httpServerIPv6.identity.primaryPort}${testpath}`;
+});
+ChromeUtils.defineLazyGetter(this, "URL6b", function () {
+ return `http://example6b.com:${httpServerIPv6.identity.primaryPort}${testpath}`;
+});
+
+const ncs = Cc[
+ "@mozilla.org/network/network-connectivity-service;1"
+].getService(Ci.nsINetworkConnectivityService);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.captive-portal-service.testMode");
+ Services.prefs.clearUserPref("network.connectivity-service.IPv6.url");
+ Services.prefs.clearUserPref("network.connectivity-service.IPv4.url");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+
+ trr_clear_prefs();
+ await httpServerIPv4.stop();
+ await httpServerIPv6.stop();
+ await trrServer.stop();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ chan.setTRRMode(Ci.nsIRequest.TRR_DEFAULT_MODE);
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+add_task(async function test_setup() {
+ httpServerIPv4.registerPathHandler(testpath, serverHandler);
+ httpServerIPv4.start(-1);
+ httpServerIPv6.registerPathHandler(testpath, serverHandler);
+ httpServerIPv6.start_ipv6(-1);
+ Services.prefs.setCharPref(
+ "network.dns.localDomains",
+ `foo.example.com, ${CC_IPV4}, ${CC_IPV6}`
+ );
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await registerDoHAnswers(true, true);
+});
+
+async function registerDoHAnswers(ipv4, ipv6) {
+ let hosts = ["example6a.com", "example6b.com"];
+ for (const host of hosts) {
+ let ipv4answers = [];
+ if (ipv4) {
+ ipv4answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ];
+ }
+ await trrServer.registerDoHAnswers(host, "A", {
+ answers: ipv4answers,
+ });
+
+ let ipv6answers = [];
+ if (ipv6) {
+ ipv6answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::1",
+ },
+ ];
+ }
+
+ await trrServer.registerDoHAnswers(host, "AAAA", {
+ answers: ipv6answers,
+ });
+ }
+
+ Services.dns.clearCache(true);
+}
+
+let StatusCounter = function () {
+ this._statusCount = {};
+};
+StatusCounter.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIProgressEventSink",
+ ]),
+
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ onProgress(request, progress, progressMax) {},
+ onStatus(request, status, statusArg) {
+ this._statusCount[status] = 1 + (this._statusCount[status] || 0);
+ },
+};
+
+let HttpListener = function (finish, succeeded) {
+ this.finish = finish;
+ this.succeeded = succeeded;
+};
+
+HttpListener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ equal(this.succeeded, status == Cr.NS_OK);
+ this.finish();
+ },
+};
+
+function promiseObserverNotification(aTopic, matchFunc) {
+ return new Promise((resolve, reject) => {
+ Services.obs.addObserver(function observe(subject, topic, data) {
+ let matches = typeof matchFunc != "function" || matchFunc(subject, data);
+ if (!matches) {
+ return;
+ }
+ Services.obs.removeObserver(observe, topic);
+ resolve({ subject, data });
+ }, aTopic);
+ });
+}
+
+async function make_request(uri, check_events, succeeded) {
+ let chan = makeChan(uri);
+ let statusCounter = new StatusCounter();
+ chan.notificationCallbacks = statusCounter;
+ await new Promise(resolve =>
+ chan.asyncOpen(new HttpListener(resolve, succeeded))
+ );
+
+ if (check_events) {
+ equal(
+ statusCounter._statusCount[0x4b000b] || 0,
+ 1,
+ "Expecting only one instance of NS_NET_STATUS_RESOLVED_HOST"
+ );
+ equal(
+ statusCounter._statusCount[0x4b0007] || 0,
+ 1,
+ "Expecting only one instance of NS_NET_STATUS_CONNECTING_TO"
+ );
+ }
+}
+
+async function setup_connectivity(ipv6, ipv4) {
+ Services.prefs.setBoolPref("network.captive-portal-service.testMode", true);
+
+ if (ipv6) {
+ Services.prefs.setCharPref(
+ "network.connectivity-service.IPv6.url",
+ URL_CC_IPV6 + testpath
+ );
+ } else {
+ Services.prefs.setCharPref(
+ "network.connectivity-service.IPv6.url",
+ "http://donotexist.example.com"
+ );
+ }
+
+ if (ipv4) {
+ Services.prefs.setCharPref(
+ "network.connectivity-service.IPv4.url",
+ URL_CC_IPV4 + testpath
+ );
+ } else {
+ Services.prefs.setCharPref(
+ "network.connectivity-service.IPv4.url",
+ "http://donotexist.example.com"
+ );
+ }
+
+ let topic = "network:connectivity-service:ip-checks-complete";
+ if (mozinfo.socketprocess_networking) {
+ topic += "-from-socket-process";
+ }
+ let observerNotification = promiseObserverNotification(topic);
+ ncs.recheckIPConnectivity();
+ await observerNotification;
+
+ if (!ipv6) {
+ equal(
+ ncs.IPv6,
+ Ci.nsINetworkConnectivityService.NOT_AVAILABLE,
+ "Check IPv6 support"
+ );
+ } else {
+ equal(ncs.IPv6, Ci.nsINetworkConnectivityService.OK, "Check IPv6 support");
+ }
+
+ if (!ipv4) {
+ equal(
+ ncs.IPv4,
+ Ci.nsINetworkConnectivityService.NOT_AVAILABLE,
+ "Check IPv4 support"
+ );
+ } else {
+ equal(ncs.IPv4, Ci.nsINetworkConnectivityService.OK, "Check IPv4 support");
+ }
+}
+
+// This test that we retry to connect using IPv4 when IPv6 connecivity is not
+// present, but a ConnectionEntry have IPv6 prefered set.
+// Speculative connections are disabled.
+add_task(async function test_prefer_address_version_fail_trr3_1() {
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+ await registerDoHAnswers(true, true);
+
+ // Make a request to setup the address version preference to a ConnectionEntry.
+ await make_request(URL6a, true, true);
+
+ // connect again using the address version preference from the ConnectionEntry.
+ await make_request(URL6a, true, true);
+
+ // Make IPv6 connectivity check fail
+ await setup_connectivity(false, true);
+
+ Services.dns.clearCache(true);
+
+ // This will succeed as we query both DNS records
+ await make_request(URL6a, true, true);
+
+ // Now make the DNS server only return IPv4 records
+ await registerDoHAnswers(true, false);
+ // This will fail, because the server is not lisenting to IPv4 address as well,
+ // We should still get NS_NET_STATUS_RESOLVED_HOST and
+ // NS_NET_STATUS_CONNECTING_TO notification.
+ await make_request(URL6a, true, false);
+
+ // Make IPv6 connectivity check succeed again
+ await setup_connectivity(true, true);
+});
+
+// This test that we retry to connect using IPv4 when IPv6 connecivity is not
+// present, but a ConnectionEntry have IPv6 prefered set.
+// Speculative connections are enabled.
+add_task(async function test_prefer_address_version_fail_trr3_2() {
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ await registerDoHAnswers(true, true);
+
+ // Make a request to setup the address version preference to a ConnectionEntry.
+ await make_request(URL6b, false, true);
+
+ // connect again using the address version preference from the ConnectionEntry.
+ await make_request(URL6b, false, true);
+
+ // Make IPv6 connectivity check fail
+ await setup_connectivity(false, true);
+
+ Services.dns.clearCache(true);
+
+ // This will succeed as we query both DNS records
+ await make_request(URL6b, false, true);
+
+ // Now make the DNS server only return IPv4 records
+ await registerDoHAnswers(true, false);
+ // This will fail, because the server is not lisenting to IPv4 address as well,
+ // We should still get NS_NET_STATUS_RESOLVED_HOST and
+ // NS_NET_STATUS_CONNECTING_TO notification.
+ await make_request(URL6b, true, false);
+});
diff --git a/netwerk/test/unit/test_dns_service.js b/netwerk/test/unit/test_dns_service.js
new file mode 100644
index 0000000000..da404c1e7d
--- /dev/null
+++ b/netwerk/test/unit/test_dns_service.js
@@ -0,0 +1,123 @@
+"use strict";
+
+const defaultOriginAttributes = {};
+const mainThread = Services.tm.currentThread;
+
+const overrideService = Cc[
+ "@mozilla.org/network/native-dns-override;1"
+].getService(Ci.nsINativeDNSResolverOverride);
+
+class Listener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+Listener.prototype.QueryInterface = ChromeUtils.generateQI(["nsIDNSListener"]);
+
+const DOMAIN_IDN = "bücher.org";
+const ACE_IDN = "xn--bcher-kva.org";
+
+const ADDR1 = "127.0.0.1";
+const ADDR2 = "::1";
+
+add_task(async function test_dns_localhost() {
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ "localhost",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ let [, inRecord] = await listener;
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let answer = inRecord.getNextAddrAsString();
+ Assert.ok(answer == ADDR1 || answer == ADDR2);
+});
+
+add_task(async function test_idn_cname() {
+ let listener = new Listener();
+ Services.dns.asyncResolve(
+ DOMAIN_IDN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ let [, inRecord] = await listener;
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ Assert.equal(inRecord.canonicalName, ACE_IDN, "IDN is returned as punycode");
+});
+
+add_task(
+ {
+ skip_if: () =>
+ Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT,
+ },
+ async function test_long_domain() {
+ let listener = new Listener();
+ let domain = "a".repeat(253);
+ overrideService.addIPOverride(domain, "1.2.3.4");
+ Services.dns.asyncResolve(
+ domain,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ let [, , inStatus] = await listener;
+ Assert.equal(inStatus, Cr.NS_OK);
+
+ listener = new Listener();
+ domain = "a".repeat(254);
+ overrideService.addIPOverride(domain, "1.2.3.4");
+
+ if (mozinfo.socketprocess_networking) {
+ // When using the socket process, the call fails asynchronously.
+ Services.dns.asyncResolve(
+ domain,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ let [, , inStatus1] = await listener;
+ Assert.equal(inStatus1, Cr.NS_ERROR_UNKNOWN_HOST);
+ } else {
+ Assert.throws(
+ () => {
+ Services.dns.asyncResolve(
+ domain,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ },
+ /NS_ERROR_UNKNOWN_HOST/,
+ "Should throw for large domains"
+ );
+ }
+ }
+);
diff --git a/netwerk/test/unit/test_domain_eviction.js b/netwerk/test/unit/test_domain_eviction.js
new file mode 100644
index 0000000000..58520c2daa
--- /dev/null
+++ b/netwerk/test/unit/test_domain_eviction.js
@@ -0,0 +1,182 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that domain eviction occurs when the cookies per base domain limit is
+// reached, and that expired cookies are evicted before live cookies.
+
+"use strict";
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function continue_test() {
+ do_run_generator(test_generator);
+}
+
+function* do_run_test() {
+ // Set quotaPerHost to maxPerHost - 1, so there is only one cookie
+ // will be evicted everytime.
+ Services.prefs.setIntPref("network.cookie.quotaPerHost", 49);
+ // Set the base domain limit to 50 so we have a known value.
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 50);
+
+ let futureExpiry = Math.floor(Date.now() / 1000 + 1000);
+
+ // test eviction under the 50 cookies per base domain limit. this means
+ // that cookies for foo.com and bar.foo.com should count toward this limit,
+ // while cookies for baz.com should not. there are several tests we perform
+ // to make sure the base domain logic is working correctly.
+
+ // 1) simplest case: set 100 cookies for "foo.bar" and make sure 50 survive.
+ setCookies("foo.bar", 100, futureExpiry);
+ Assert.equal(countCookies("foo.bar", "foo.bar"), 50);
+
+ // 2) set cookies for different subdomains of "foo.baz", and an unrelated
+ // domain, and make sure all 50 within the "foo.baz" base domain are counted.
+ setCookies("foo.baz", 10, futureExpiry);
+ setCookies(".foo.baz", 10, futureExpiry);
+ setCookies("bar.foo.baz", 10, futureExpiry);
+ setCookies("baz.bar.foo.baz", 10, futureExpiry);
+ setCookies("unrelated.domain", 50, futureExpiry);
+ Assert.equal(countCookies("foo.baz", "baz.bar.foo.baz"), 40);
+ setCookies("foo.baz", 20, futureExpiry);
+ Assert.equal(countCookies("foo.baz", "baz.bar.foo.baz"), 50);
+
+ // 3) ensure cookies are evicted by order of lastAccessed time, if the
+ // limit on cookies per base domain is reached.
+ setCookies("horse.radish", 10, futureExpiry);
+
+ // Wait a while, to make sure the first batch of cookies is older than
+ // the second (timer resolution varies on different platforms).
+ do_timeout(100, continue_test);
+ yield;
+
+ setCookies("tasty.horse.radish", 50, futureExpiry);
+ Assert.equal(countCookies("horse.radish", "horse.radish"), 50);
+
+ for (let cookie of Services.cookies.cookies) {
+ if (cookie.host == "horse.radish") {
+ do_throw("cookies not evicted by lastAccessed order");
+ }
+ }
+
+ // Test that expired cookies for a domain are evicted before live ones.
+ let shortExpiry = Math.floor(Date.now() / 1000 + 2);
+ setCookies("captchart.com", 49, futureExpiry);
+ Services.cookies.add(
+ "captchart.com",
+ "",
+ "test100",
+ "eviction",
+ false,
+ false,
+ false,
+ shortExpiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ do_timeout(2100, continue_test);
+ yield;
+
+ Assert.equal(countCookies("captchart.com", "captchart.com"), 50);
+ Services.cookies.add(
+ "captchart.com",
+ "",
+ "test200",
+ "eviction",
+ false,
+ false,
+ false,
+ futureExpiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Assert.equal(countCookies("captchart.com", "captchart.com"), 50);
+
+ for (let cookie of Services.cookies.getCookiesFromHost("captchart.com", {})) {
+ Assert.ok(cookie.expiry == futureExpiry);
+ }
+
+ do_finish_generator_test(test_generator);
+}
+
+// set 'aNumber' cookies with host 'aHost', with distinct names.
+function setCookies(aHost, aNumber, aExpiry) {
+ for (let i = 0; i < aNumber; ++i) {
+ Services.cookies.add(
+ aHost,
+ "",
+ "test" + i,
+ "eviction",
+ false,
+ false,
+ false,
+ aExpiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ }
+}
+
+// count how many cookies are within domain 'aBaseDomain', using three
+// independent interface methods on nsICookieManager:
+// 1) 'cookies', an array of all cookies;
+// 2) 'countCookiesFromHost', which returns the number of cookies within the
+// base domain of 'aHost',
+// 3) 'getCookiesFromHost', which returns an array of 2).
+function countCookies(aBaseDomain, aHost) {
+ // count how many cookies are within domain 'aBaseDomain' using the cookies
+ // array.
+ let cookies = [];
+ for (let cookie of Services.cookies.cookies) {
+ if (
+ cookie.host.length >= aBaseDomain.length &&
+ cookie.host.slice(cookie.host.length - aBaseDomain.length) == aBaseDomain
+ ) {
+ cookies.push(cookie);
+ }
+ }
+
+ // confirm the count using countCookiesFromHost and getCookiesFromHost.
+ let result = cookies.length;
+ Assert.equal(
+ Services.cookies.countCookiesFromHost(aBaseDomain),
+ cookies.length
+ );
+ Assert.equal(Services.cookies.countCookiesFromHost(aHost), cookies.length);
+
+ for (let cookie of Services.cookies.getCookiesFromHost(aHost, {})) {
+ if (
+ cookie.host.length >= aBaseDomain.length &&
+ cookie.host.slice(cookie.host.length - aBaseDomain.length) == aBaseDomain
+ ) {
+ let found = false;
+ for (let i = 0; i < cookies.length; ++i) {
+ if (cookies[i].host == cookie.host && cookies[i].name == cookie.name) {
+ found = true;
+ cookies.splice(i, 1);
+ break;
+ }
+ }
+
+ if (!found) {
+ do_throw("cookie " + cookie.name + " not found in master cookies");
+ }
+ } else {
+ do_throw(
+ "cookie host " + cookie.host + " not within domain " + aBaseDomain
+ );
+ }
+ }
+
+ Assert.equal(cookies.length, 0);
+
+ return result;
+}
diff --git a/netwerk/test/unit/test_dooh.js b/netwerk/test/unit/test_dooh.js
new file mode 100644
index 0000000000..cab4b3bb02
--- /dev/null
+++ b/netwerk/test/unit/test_dooh.js
@@ -0,0 +1,354 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from trr_common.js */
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let httpServer;
+let ohttpServer;
+let ohttpEncodedConfig = "not a valid config";
+
+// Decapsulate the request, send it to the actual TRR, receive the response,
+// encapsulate it, and send it back through `response`.
+async function forwardToTRR(request, response) {
+ let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ inputStream.init(request.bodyInputStream);
+ let requestBody = inputStream.readBytes(inputStream.available());
+ let ohttpResponse = ohttpServer.decapsulate(stringToBytes(requestBody));
+ let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
+ Ci.nsIBinaryHttp
+ );
+ let decodedRequest = bhttp.decodeRequest(ohttpResponse.request);
+ let headers = {};
+ for (
+ let i = 0;
+ i < decodedRequest.headerNames.length && decodedRequest.headerValues.length;
+ i++
+ ) {
+ headers[decodedRequest.headerNames[i]] = decodedRequest.headerValues[i];
+ }
+ let uri = `${decodedRequest.scheme}://${decodedRequest.authority}${decodedRequest.path}`;
+ let body = new Uint8Array(decodedRequest.content.length);
+ for (let i = 0; i < decodedRequest.content.length; i++) {
+ body[i] = decodedRequest.content[i];
+ }
+ try {
+ // Timeout after 10 seconds.
+ let fetchInProgress = true;
+ let controller = new AbortController();
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ if (fetchInProgress) {
+ controller.abort();
+ }
+ }, 10000);
+ let trrResponse = await fetch(uri, {
+ method: decodedRequest.method,
+ headers,
+ body: decodedRequest.method == "POST" ? body : undefined,
+ credentials: "omit",
+ signal: controller.signal,
+ });
+ fetchInProgress = false;
+ let data = new Uint8Array(await trrResponse.arrayBuffer());
+ let trrResponseContent = [];
+ for (let i = 0; i < data.length; i++) {
+ trrResponseContent.push(data[i]);
+ }
+ let trrResponseHeaderNames = [];
+ let trrResponseHeaderValues = [];
+ for (let header of trrResponse.headers) {
+ trrResponseHeaderNames.push(header[0]);
+ trrResponseHeaderValues.push(header[1]);
+ }
+ let binaryResponse = new BinaryHttpResponse(
+ trrResponse.status,
+ trrResponseHeaderNames,
+ trrResponseHeaderValues,
+ trrResponseContent
+ );
+ let responseBytes = bhttp.encodeResponse(binaryResponse);
+ let encResponse = ohttpResponse.encapsulate(responseBytes);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "message/ohttp-res", false);
+ response.write(bytesToString(encResponse));
+ } catch (e) {
+ // Some tests involve the responder either timing out or closing the
+ // connection unexpectedly.
+ }
+}
+
+add_setup(async function setup() {
+ h2Port = trr_test_setup();
+ runningOHTTPTests = true;
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+
+ let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService(
+ Ci.nsIObliviousHttp
+ );
+ ohttpServer = ohttp.server();
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/relay", function (request, response) {
+ response.processAsync();
+ forwardToTRR(request, response).then(() => {
+ response.finish();
+ });
+ });
+ httpServer.registerPathHandler("/config", function (request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/ohttp-keys", false);
+ response.write(ohttpEncodedConfig);
+ });
+ httpServer.start(-1);
+
+ Services.prefs.setBoolPref("network.trr.use_ohttp", true);
+ // On windows the TTL fetch will race with clearing the cache
+ // to refresh the cache entry.
+ Services.prefs.setBoolPref("network.dns.get-ttl", false);
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.trr.use_ohttp");
+ Services.prefs.clearUserPref("network.trr.ohttp.config_uri");
+ Services.prefs.clearUserPref("network.trr.ohttp.relay_uri");
+ Services.prefs.clearUserPref("network.trr.ohttp.uri");
+ Services.prefs.clearUserPref("network.dns.get-ttl");
+ await new Promise((resolve, reject) => {
+ httpServer.stop(resolve);
+ });
+ });
+});
+
+// Test that if DNS-over-OHTTP isn't configured, the implementation falls back
+// to platform resolution.
+add_task(async function test_ohttp_not_configured() {
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await new TRRDNSListener("example.com", "127.0.0.1");
+});
+
+add_task(async function set_ohttp_invalid_prefs() {
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.relay_uri",
+ "http://nonexistent.test"
+ );
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ "http://nonexistent.test"
+ );
+
+ Cc["@mozilla.org/network/oblivious-http-service;1"].getService(
+ Ci.nsIObliviousHttpService
+ );
+ await configPromise;
+});
+
+// Test that if DNS-over-OHTTP has an invalid configuration, the implementation
+// falls back to platform resolution.
+add_task(async function test_ohttp_invalid_prefs_fallback() {
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await new TRRDNSListener("example.com", "127.0.0.1");
+});
+
+add_task(async function set_ohttp_prefs_500_error() {
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.relay_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/relay`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/500error`
+ );
+ await configPromise;
+});
+
+// Test that if DNS-over-OHTTP has an invalid configuration, the implementation
+// falls back to platform resolution.
+add_task(async function test_ohttp_500_error_fallback() {
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await new TRRDNSListener("example.com", "127.0.0.1");
+});
+
+add_task(async function retryConfigOnConnectivityChange() {
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ // First we make sure the config is properly loaded
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ let ohttpService = Cc[
+ "@mozilla.org/network/oblivious-http-service;1"
+ ].getService(Ci.nsIObliviousHttpService);
+ ohttpService.clearTRRConfig();
+ ohttpEncodedConfig = bytesToString(ohttpServer.encodedConfig);
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.relay_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/relay`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/config`
+ );
+ let [, status] = await configPromise;
+ equal(status, "success");
+ info("retryConfigOnConnectivityChange setup complete");
+
+ ohttpService.clearTRRConfig();
+
+ let port = httpServer.identity.primaryPort;
+ // Stop the server so getting the config fails.
+ await httpServer.stop();
+
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity-changed"
+ );
+ [, status] = await configPromise;
+ equal(status, "failed");
+
+ // Should fallback to native DNS since the config is empty
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("example.com", "127.0.0.1");
+
+ // Start the server back again.
+ httpServer.start(port);
+ Assert.equal(
+ port,
+ httpServer.identity.primaryPort,
+ "server should get the same port"
+ );
+
+ // Still the config hasn't been reloaded.
+ await new TRRDNSListener("example2.com", "127.0.0.1");
+
+ // Signal a connectivity change so we reload the config
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity-changed"
+ );
+ [, status] = await configPromise;
+ equal(status, "success");
+
+ await new TRRDNSListener("example3.com", "2.2.2.2");
+
+ // Now check that we also reload a missing config if a TRR confirmation fails.
+ ohttpService.clearTRRConfig();
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:trr-confirmation",
+ "CONFIRM_FAILED"
+ );
+ [, status] = await configPromise;
+ equal(status, "success");
+ await new TRRDNSListener("example4.com", "2.2.2.2");
+
+ // set the config to an invalid value and check that as long as the URL
+ // doesn't change, we dont reload it again on connectivity notifications.
+ ohttpEncodedConfig = "not a valid config";
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity-changed"
+ );
+
+ await new TRRDNSListener("example5.com", "2.2.2.2");
+
+ // The change should not cause any config reload because we already have a config.
+ [, status] = await configPromise;
+ equal(status, "no-changes");
+
+ await new TRRDNSListener("example6.com", "2.2.2.2");
+ // Clear the config_uri pref so it gets set to the proper value in the next test.
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref("network.trr.ohttp.config_uri", ``);
+ await configPromise;
+});
+
+add_task(async function set_ohttp_prefs_valid() {
+ let ohttpService = Cc[
+ "@mozilla.org/network/oblivious-http-service;1"
+ ].getService(Ci.nsIObliviousHttpService);
+ ohttpService.clearTRRConfig();
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ ohttpEncodedConfig = bytesToString(ohttpServer.encodedConfig);
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/config`
+ );
+ await configPromise;
+});
+
+add_task(test_A_record);
+
+add_task(test_AAAA_records);
+
+add_task(test_RFC1918);
+
+add_task(test_GET_ECS);
+
+add_task(test_timeout_mode3);
+
+add_task(test_strict_native_fallback);
+
+add_task(test_no_answers_fallback);
+
+add_task(test_404_fallback);
+
+add_task(test_mode_1_and_4);
+
+add_task(test_CNAME);
+
+add_task(test_name_mismatch);
+
+add_task(test_mode_2);
+
+add_task(test_excluded_domains);
+
+add_task(test_captiveportal_canonicalURL);
+
+add_task(test_parentalcontrols);
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref
+add_task(test_builtin_excluded_domains);
+
+add_task(test_excluded_domains_mode3);
+
+add_task(test25e);
+
+add_task(test_parentalcontrols_mode3);
+
+add_task(test_builtin_excluded_domains_mode3);
+
+add_task(count_cookies);
+
+// This test doesn't work with having a JS httpd server as a relay.
+// add_task(test_connection_closed);
+
+add_task(test_fetch_time);
+
+add_task(test_fqdn);
+
+add_task(test_ipv6_trr_fallback);
+
+add_task(test_ipv4_trr_fallback);
+
+add_task(test_no_retry_without_doh);
diff --git a/netwerk/test/unit/test_doomentry.js b/netwerk/test/unit/test_doomentry.js
new file mode 100644
index 0000000000..07e17a1080
--- /dev/null
+++ b/netwerk/test/unit/test_doomentry.js
@@ -0,0 +1,108 @@
+/**
+ * Test for nsICacheStorage.asyncDoomURI().
+ * It tests dooming
+ * - an existent inactive entry
+ * - a non-existent inactive entry
+ * - an existent active entry
+ */
+
+"use strict";
+
+function doom(url, callback) {
+ Services.cache2
+ .diskCacheStorage(Services.loadContextInfo.default)
+ .asyncDoomURI(createURI(url), "", {
+ onCacheEntryDoomed(result) {
+ callback(result);
+ },
+ });
+}
+
+function write_and_check(str, data, len) {
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw(
+ "str.write has not written all data!\n" +
+ " Expected: " +
+ len +
+ "\n" +
+ " Actual: " +
+ written +
+ "\n"
+ );
+ }
+}
+
+function write_entry() {
+ asyncOpenCacheEntry(
+ "http://testentry/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ function (status, entry) {
+ write_entry_cont(entry, entry.openOutputStream(0, -1));
+ }
+ );
+}
+
+function write_entry_cont(entry, ostream) {
+ var data = "testdata";
+ write_and_check(ostream, data, data.length);
+ ostream.close();
+ entry.close();
+ doom("http://testentry/", check_doom1);
+}
+
+function check_doom1(status) {
+ Assert.equal(status, Cr.NS_OK);
+ doom("http://nonexistententry/", check_doom2);
+}
+
+function check_doom2(status) {
+ Assert.equal(status, Cr.NS_ERROR_NOT_AVAILABLE);
+ asyncOpenCacheEntry(
+ "http://testentry/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ function (stat, entry) {
+ write_entry2(entry, entry.openOutputStream(0, -1));
+ }
+ );
+}
+
+var gEntry;
+var gOstream;
+function write_entry2(entry, ostream) {
+ // write some data and doom the entry while it is active
+ var data = "testdata";
+ write_and_check(ostream, data, data.length);
+ gEntry = entry;
+ gOstream = ostream;
+ doom("http://testentry/", check_doom3);
+}
+
+function check_doom3(status) {
+ Assert.equal(status, Cr.NS_OK);
+ // entry was doomed but writing should still succeed
+ var data = "testdata";
+ write_and_check(gOstream, data, data.length);
+ gOstream.close();
+ gEntry.close();
+ // dooming the same entry again should fail
+ doom("http://testentry/", check_doom4);
+}
+
+function check_doom4(status) {
+ Assert.equal(status, Cr.NS_ERROR_NOT_AVAILABLE);
+ do_test_finished();
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+ write_entry();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_duplicate_headers.js b/netwerk/test/unit/test_duplicate_headers.js
new file mode 100644
index 0000000000..78d2355d1e
--- /dev/null
+++ b/netwerk/test/unit/test_duplicate_headers.js
@@ -0,0 +1,563 @@
+/*
+ * Tests bugs 597706, 655389: prevent duplicate headers with differing values
+ * for some headers like Content-Length, Location, etc.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+"use strict";
+
+// The tests in this file use number indexes to run, which can't be detected
+// via ESLint.
+/* eslint-disable no-unused-vars */
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var test_flags = [];
+var testPathBase = "/dupe_hdrs";
+
+function run_test() {
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num) {
+ let testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, globalThis["handler" + num]);
+
+ var channel = setupChannel(testPath);
+ let flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen(
+ new ChannelListener(globalThis["completeTest" + num], channel, flags)
+ );
+}
+
+function setupChannel(url) {
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests() {
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: FAIL because of conflicting Content-Length headers
+test_flags[1] = CL_EXPECT_FAILURE;
+
+function handler1(metadata, response) {
+ var body = "012345678901234567890123456789";
+ // Comrades! We must seize power from the petty-bourgeois running dogs of
+ // httpd.js in order to reply with multiple instances of the same header!
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length: 20\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest1(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: OK to have duplicate same Content-Length headers
+
+function handler2(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest2(request, data, ctx) {
+ Assert.equal(request.status, 0);
+ run_test_number(3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: FAIL: 2nd Content-length is blank
+test_flags[3] = CL_EXPECT_FAILURE;
+
+function handler3(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length:\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest3(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(4);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: ensure that blank C-len header doesn't allow attacker to reset Clen,
+// then insert CRLF attack
+test_flags[4] = CL_EXPECT_FAILURE;
+
+function handler4(metadata, response) {
+ var body = "012345678901234567890123456789";
+
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+
+ // Bad Mr Hacker! Bad!
+ var evilBody = "We are the Evil bytes, Evil bytes, Evil bytes!";
+ response.write("Content-Length:\r\n");
+ response.write("Content-Length: %s\r\n\r\n%s" % (evilBody.length, evilBody));
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest4(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(5);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 5: ensure that we take 1st instance of duplicate, nonmerged headers that
+// are permitted : (ex: Referrer)
+
+function handler5(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Referer: naive.org\r\n");
+ response.write("Referer: evil.net\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest5(request, data, ctx) {
+ try {
+ let referer = request.getResponseHeader("Referer");
+ Assert.equal(referer, "naive.org");
+ } catch (ex) {
+ do_throw("Referer header should be present");
+ }
+
+ run_test_number(6);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 5: FAIL if multiple, different Location: headers present
+// - needed to prevent CRLF injection attacks
+test_flags[6] = CL_EXPECT_FAILURE;
+
+function handler6(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Location: " + URL + "/content\r\n");
+ response.write("Location: http://www.microsoft.com/\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest6(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ // run_test_number(7); // Test 7 leaking under e10s: unrelated bug?
+ run_test_number(8);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 7: OK to have multiple Location: headers with same value
+
+function handler7(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 5
+ response.write("Location: " + URL + testPathBase + "5\r\n");
+ response.write("Location: " + URL + testPathBase + "5\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest7(request, data, ctx) {
+ // for some reason need this here
+ request.QueryInterface(Ci.nsIHttpChannel);
+
+ try {
+ let referer = request.getResponseHeader("Referer");
+ Assert.equal(referer, "naive.org");
+ } catch (ex) {
+ do_throw("Referer header should be present");
+ }
+
+ run_test_number(8);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FAIL if 2nd Location: headers blank
+test_flags[8] = CL_EXPECT_FAILURE;
+
+function handler8(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 4
+ response.write("Location: " + URL + testPathBase + "4\r\n");
+ response.write("Location:\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest8(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(9);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 9: ensure that blank Location header doesn't allow attacker to reset,
+// then insert an evil one
+test_flags[9] = CL_EXPECT_FAILURE;
+
+function handler9(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 2
+ response.write("Location: " + URL + testPathBase + "2\r\n");
+ response.write("Location:\r\n");
+ // redirect to previous test handler that completes OK: test 4
+ response.write("Location: " + URL + testPathBase + "4\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest9(request, data, ctx) {
+ // All redirection should fail:
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(10);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 10: FAIL: if conflicting values for Content-Dispo
+test_flags[10] = CL_EXPECT_FAILURE;
+
+function handler10(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Disposition: attachment; filename=foo\r\n");
+ response.write("Content-Disposition: attachment; filename=bar\r\n");
+ response.write("Content-Disposition: attachment; filename=baz\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest10(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(11);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 11: OK to have duplicate same Content-Disposition headers
+
+function handler11(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Disposition: attachment; filename=foo\r\n");
+ response.write("Content-Disposition: attachment; filename=foo\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest11(request, data, ctx) {
+ Assert.equal(request.status, 0);
+
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ Assert.equal(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ Assert.equal(chan.contentDispositionFilename, "foo");
+ Assert.equal(chan.contentDispositionHeader, "attachment; filename=foo");
+ } catch (ex) {
+ do_throw("error parsing Content-Disposition: " + ex);
+ }
+
+ run_test_number(12);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Bug 716801 OK for Location: header to be blank
+
+function handler12(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Location:\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest12(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(30, data.length);
+
+ run_test_number(13);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Negative content length is ok
+test_flags[13] = CL_ALLOW_UNKNOWN_CL;
+
+function handler13(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: -1\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest13(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(30, data.length);
+
+ run_test_number(14);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// leading negative content length is not ok if paired with positive one
+
+test_flags[14] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+function handler14(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: -1\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest14(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(15);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// trailing negative content length is not ok if paired with positive one
+
+test_flags[15] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+function handler15(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length: -1\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest15(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(16);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// empty content length is ok
+test_flags[16] = CL_ALLOW_UNKNOWN_CL;
+let reran16 = false;
+
+function handler16(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: \r\n");
+ response.write("Cache-Control: max-age=600\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest16(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(30, data.length);
+
+ if (!reran16) {
+ reran16 = true;
+ run_test_number(16);
+ } else {
+ run_test_number(17);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// empty content length paired with non empty is not ok
+test_flags[17] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+function handler17(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: \r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest17(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(18);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// alpha content-length is just like -1
+test_flags[18] = CL_ALLOW_UNKNOWN_CL;
+
+function handler18(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: seventeen\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest18(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(30, data.length);
+
+ run_test_number(19);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// semi-colons are ok too in the content-length
+test_flags[19] = CL_ALLOW_UNKNOWN_CL;
+
+function handler19(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30;\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest19(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(30, data.length);
+
+ run_test_number(20);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FAIL if 1st Location: header is blank, followed by non-blank
+test_flags[20] = CL_EXPECT_FAILURE;
+
+function handler20(metadata, response) {
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 4
+ response.write("Location:\r\n");
+ response.write("Location: " + URL + testPathBase + "4\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest20(request, data, ctx) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ endTests();
+}
diff --git a/netwerk/test/unit/test_early_hint_listener.js b/netwerk/test/unit/test_early_hint_listener.js
new file mode 100644
index 0000000000..73b95067fe
--- /dev/null
+++ b/netwerk/test/unit/test_early_hint_listener.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var earlyhintspath = "/earlyhints";
+var multipleearlyhintspath = "/multipleearlyhintspath";
+var otherearlyhintspath = "/otherearlyhintspath";
+var noearlyhintspath = "/noearlyhints";
+var httpbody = "0123456789";
+var hint1 = "</style.css>; rel=preload; as=style";
+var hint2 = "</img.png>; rel=preload; as=image";
+
+function earlyHintsResponse(metadata, response) {
+ response.setInformationalResponseStatusLine(
+ metadata.httpVersion,
+ 103,
+ "EarlyHints"
+ );
+ response.setInformationalResponseHeader("Link", hint1, false);
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function multipleEarlyHintsResponse(metadata, response) {
+ response.setInformationalResponseStatusLine(
+ metadata.httpVersion,
+ 103,
+ "EarlyHints"
+ );
+ response.setInformationalResponseHeader("Link", hint1, false);
+ response.setInformationalHeaderNoCheck("Link", hint2);
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function otherHeadersEarlyHintsResponse(metadata, response) {
+ response.setInformationalResponseStatusLine(
+ metadata.httpVersion,
+ 103,
+ "EarlyHints"
+ );
+ response.setInformationalResponseHeader("Link", hint1, false);
+ response.setInformationalResponseHeader("Something", "something", false);
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function noEarlyHintsResponse(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+let EarlyHintsListener = function () {};
+
+EarlyHintsListener.prototype = {
+ _expected_hints: "",
+ earlyHintsReceived: false,
+
+ earlyHint: function testEarlyHint(header) {
+ Assert.equal(header, this._expected_hints);
+ this.earlyHintsReceived = true;
+ },
+};
+
+function chanPromise(uri, listener) {
+ return new Promise(resolve => {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ NetUtil.newURI(uri),
+ {}
+ );
+ var chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ chan
+ .QueryInterface(Ci.nsIHttpChannelInternal)
+ .setEarlyHintObserver(listener);
+ chan.asyncOpen(new ChannelListener(resolve));
+ });
+}
+
+add_task(async function setup() {
+ httpserver.registerPathHandler(earlyhintspath, earlyHintsResponse);
+ httpserver.registerPathHandler(
+ multipleearlyhintspath,
+ multipleEarlyHintsResponse
+ );
+ httpserver.registerPathHandler(
+ otherearlyhintspath,
+ otherHeadersEarlyHintsResponse
+ );
+ httpserver.registerPathHandler(noearlyhintspath, noEarlyHintsResponse);
+ httpserver.start(-1);
+});
+
+add_task(async function early_hints() {
+ let earlyHints = new EarlyHintsListener();
+ earlyHints._expected_hints = hint1;
+
+ await chanPromise(
+ "http://localhost:" + httpserver.identity.primaryPort + earlyhintspath,
+ earlyHints
+ );
+ Assert.ok(earlyHints.earlyHintsReceived);
+});
+
+add_task(async function no_early_hints() {
+ let earlyHints = new EarlyHintsListener("");
+
+ await chanPromise(
+ "http://localhost:" + httpserver.identity.primaryPort + noearlyhintspath,
+ earlyHints
+ );
+ Assert.ok(!earlyHints.earlyHintsReceived);
+});
+
+add_task(async function early_hints_multiple() {
+ let earlyHints = new EarlyHintsListener();
+ earlyHints._expected_hints = hint1 + ", " + hint2;
+
+ await chanPromise(
+ "http://localhost:" +
+ httpserver.identity.primaryPort +
+ multipleearlyhintspath,
+ earlyHints
+ );
+ Assert.ok(earlyHints.earlyHintsReceived);
+});
+
+add_task(async function early_hints_other() {
+ let earlyHints = new EarlyHintsListener();
+ earlyHints._expected_hints = hint1;
+
+ await chanPromise(
+ "http://localhost:" + httpserver.identity.primaryPort + otherearlyhintspath,
+ earlyHints
+ );
+ Assert.ok(earlyHints.earlyHintsReceived);
+});
+
+add_task(async function early_hints_only_secure_context() {
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ let earlyHints2 = new EarlyHintsListener();
+ earlyHints2._expected_hints = "";
+
+ await chanPromise(
+ "http://localhost:" + httpserver.identity.primaryPort + earlyhintspath,
+ earlyHints2
+ );
+ Assert.ok(!earlyHints2.earlyHintsReceived);
+});
+
+add_task(async function clean_up() {
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ await new Promise(resolve => {
+ httpserver.stop(resolve);
+ });
+});
diff --git a/netwerk/test/unit/test_early_hint_listener_http2.js b/netwerk/test/unit/test_early_hint_listener_http2.js
new file mode 100644
index 0000000000..3ea32f0bcc
--- /dev/null
+++ b/netwerk/test/unit/test_early_hint_listener_http2.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// The server will always respond with a 103 EarlyHint followed by a
+// 200 response.
+// 103 response contains:
+// 1) a non-link header
+// 2) a link header if a request has a "link-to-set" header. If the
+// request header is not set, the response will not have Link headers.
+// A "link-to-set" header may contain multiple link headers
+// separated with a comma.
+
+var earlyhintspath = "/103_response";
+var hint1 = "</style.css>; rel=preload; as=style";
+var hint2 = "</img.png>; rel=preload; as=image";
+
+let EarlyHintsListener = function () {};
+
+EarlyHintsListener.prototype = {
+ _expected_hints: "",
+ earlyHintsReceived: false,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIEarlyHintObserver"]),
+
+ earlyHint: function testEarlyHint(header) {
+ Assert.equal(header, this._expected_hints);
+ this.earlyHintsReceived = true;
+ },
+};
+
+function chanPromise(uri, listener, headers) {
+ return new Promise(resolve => {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ NetUtil.newURI(uri),
+ {}
+ );
+ var chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ chan
+ .QueryInterface(Ci.nsIHttpChannel)
+ .setRequestHeader("link-to-set", headers, false);
+ chan
+ .QueryInterface(Ci.nsIHttpChannelInternal)
+ .setEarlyHintObserver(listener);
+ chan.asyncOpen(new ChannelListener(resolve));
+ });
+}
+
+let http2Port;
+
+add_task(async function setup() {
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ http2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(http2Port, null);
+ Assert.notEqual(http2Port, "");
+
+ do_get_profile();
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
+
+add_task(async function early_hints() {
+ let earlyHints = new EarlyHintsListener();
+ earlyHints._expected_hints = hint1;
+
+ await chanPromise(
+ `https://foo.example.com:${http2Port}${earlyhintspath}`,
+ earlyHints,
+ hint1
+ );
+ Assert.ok(earlyHints.earlyHintsReceived);
+});
+
+// Test when there is no Link header in a 103 response.
+// 103 response will contain non-link headers.
+add_task(async function no_early_hints() {
+ let earlyHints = new EarlyHintsListener("");
+
+ await chanPromise(
+ `https://foo.example.com:${http2Port}${earlyhintspath}`,
+ earlyHints,
+ ""
+ );
+ Assert.ok(!earlyHints.earlyHintsReceived);
+});
+
+add_task(async function early_hints_multiple() {
+ let earlyHints = new EarlyHintsListener();
+ earlyHints._expected_hints = hint1 + ", " + hint2;
+
+ await chanPromise(
+ `https://foo.example.com:${http2Port}${earlyhintspath}`,
+ earlyHints,
+ hint1 + ", " + hint2
+ );
+ Assert.ok(earlyHints.earlyHintsReceived);
+});
diff --git a/netwerk/test/unit/test_ech_grease.js b/netwerk/test/unit/test_ech_grease.js
new file mode 100644
index 0000000000..bbe9c027b5
--- /dev/null
+++ b/netwerk/test/unit/test_ech_grease.js
@@ -0,0 +1,270 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+
+// Allow telemetry probes which may otherwise be disabled for some
+// applications (e.g. Thunderbird).
+Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+);
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+class InputStreamCallback {
+ constructor(output) {
+ this.output = output;
+ this.stopped = false;
+ }
+
+ onInputStreamReady(stream) {
+ info("input stream ready");
+ if (this.stopped) {
+ info("input stream callback stopped - bailing");
+ return;
+ }
+ let available = 0;
+ try {
+ available = stream.available();
+ } catch (e) {
+ // onInputStreamReady may fire when the stream has been closed.
+ equal(
+ e.result,
+ Cr.NS_BASE_STREAM_CLOSED,
+ "error should be NS_BASE_STREAM_CLOSED"
+ );
+ }
+ if (available > 0) {
+ let request = NetUtil.readInputStreamToString(stream, available, {
+ charset: "utf8",
+ });
+ ok(
+ request.startsWith("GET / HTTP/1.1\r\n"),
+ "Should get a simple GET / HTTP/1.1 request"
+ );
+ let response =
+ "HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 2\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\nOK";
+ let written = this.output.write(response, response.length);
+ equal(
+ written,
+ response.length,
+ "should have been able to write entire response"
+ );
+ }
+ this.output.close();
+ info("done with input stream ready");
+ }
+
+ stop() {
+ this.stopped = true;
+ this.output.close();
+ }
+}
+
+class TLSServerSecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ this.callbacks = [];
+ this.stopped = false;
+ }
+
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+ info(`TLS version used: ${status.tlsVersionUsed}`);
+
+ if (this.stopped) {
+ info("handshake done callback stopped - bailing");
+ return;
+ }
+
+ let callback = new InputStreamCallback(this.output);
+ this.callbacks.push(callback);
+ this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
+ }
+
+ stop() {
+ this.stopped = true;
+ this.input.close();
+ this.output.close();
+ this.callbacks.forEach(callback => {
+ callback.stop();
+ });
+ }
+}
+
+class ServerSocketListener {
+ constructor() {
+ this.securityObservers = [];
+ }
+
+ onSocketAccepted(socket, transport) {
+ info("accepted TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ let securityObserver = new TLSServerSecurityObserver(input, output);
+ this.securityObservers.push(securityObserver);
+ connectionInfo.setSecurityObserver(securityObserver);
+ }
+
+ // For some reason we get input stream callback events after we've stopped
+ // listening, so this ensures we just drop those events.
+ onStopListening() {
+ info("onStopListening");
+ this.securityObservers.forEach(observer => {
+ observer.stop();
+ });
+ }
+}
+
+function startServer(
+ minServerVersion = Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ maxServerVersion = Ci.nsITLSClientStatus.TLS_VERSION_1_3
+) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = getTestServerCertificate();
+ tlsServer.setVersionRange(minServerVersion, maxServerVersion);
+ tlsServer.setSessionTickets(false);
+ tlsServer.asyncListen(new ServerSocketListener());
+ storeCertOverride(tlsServer.port, tlsServer.serverCert);
+ return tlsServer;
+}
+
+const hostname = "example.com";
+
+function storeCertOverride(port, cert) {
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true);
+}
+
+function startClient(port, useGREASE, beConservative) {
+ HandshakeTelemetryHelpers.resetHistograms();
+ let flavors = ["", "_FIRST_TRY"];
+ let nonflavors = ["_ECH"];
+
+ if (useGREASE) {
+ Services.prefs.setIntPref("security.tls.ech.grease_probability", 100);
+ } else {
+ Services.prefs.setIntPref("security.tls.ech.grease_probability", 0);
+ }
+
+ let req = new XMLHttpRequest();
+ req.open("GET", `https://${hostname}:${port}`);
+
+ if (beConservative) {
+ // We don't have a way to set DONT_TRY_ECH at the moment.
+ let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.beConservative = beConservative;
+ flavors.push("_CONSERVATIVE");
+ } else {
+ nonflavors.push("_CONSERVATIVE");
+ }
+
+ //GREASE is only used if enabled and not in conservative mode.
+ if (useGREASE && !beConservative) {
+ flavors.push("_ECH_GREASE");
+ } else {
+ nonflavors.push("_ECH_GREASE");
+ }
+
+ return new Promise((resolve, reject) => {
+ req.onload = () => {
+ equal(req.responseText, "OK", "response text should be 'OK'");
+
+ // Only check telemetry if network process is disabled.
+ if (!mozinfo.socketprocess_networking) {
+ HandshakeTelemetryHelpers.checkSuccess(flavors);
+ HandshakeTelemetryHelpers.checkEmpty(nonflavors);
+ }
+
+ resolve();
+ };
+ req.onerror = () => {
+ ok(false, `should not have gotten an error`);
+ resolve();
+ };
+
+ req.send();
+ });
+}
+
+function setup() {
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ Services.prefs.setCharPref("network.dns.localDomains", hostname);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+}
+setup();
+
+add_task(async function GreaseYConservativeN() {
+ // First run a server that accepts TLS 1.2 and 1.3. A conservative client
+ // should succeed in connecting.
+ let server = startServer();
+
+ await startClient(
+ server.port,
+ true /*be conservative*/,
+ false /*should succeed*/
+ );
+ server.close();
+});
+
+add_task(async function GreaseNConservativeY() {
+ // First run a server that accepts TLS 1.2 and 1.3. A conservative client
+ // should succeed in connecting.
+ let server = startServer();
+
+ await startClient(
+ server.port,
+ false /*be conservative*/,
+ true /*should succeed*/
+ );
+ server.close();
+});
+
+add_task(async function GreaseYConservativeY() {
+ // First run a server that accepts TLS 1.2 and 1.3. A conservative client
+ // should succeed in connecting.
+ let server = startServer();
+
+ await startClient(
+ server.port,
+ true /*be conservative*/,
+ true /*should succeed*/
+ );
+ server.close();
+});
+
+add_task(async function GreaseNConservativeN() {
+ // First run a server that accepts TLS 1.2 and 1.3. A conservative client
+ // should succeed in connecting.
+ let server = startServer();
+
+ await startClient(
+ server.port,
+ false /*be conservative*/,
+ false /*should succeed*/
+ );
+ server.close();
+});
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("security.tls.ech.grease_probability");
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+});
diff --git a/netwerk/test/unit/test_event_sink.js b/netwerk/test/unit/test_event_sink.js
new file mode 100644
index 0000000000..e49080c88c
--- /dev/null
+++ b/netwerk/test/unit/test_event_sink.js
@@ -0,0 +1,183 @@
+// This file tests channel event sinks (bug 315598 et al)
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+const sinkCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}");
+const sinkContract = "@mozilla.org/network/unittest/channeleventsink;1";
+
+const categoryName = "net-channel-event-sinks";
+
+/**
+ * This object is both a factory and an nsIChannelEventSink implementation (so, it
+ * is de-facto a service). It's also an interface requestor that gives out
+ * itself when asked for nsIChannelEventSink.
+ */
+var eventsink = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIChannelEventSink"]),
+ createInstance: function eventsink_ci(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ asyncOnChannelRedirect: function eventsink_onredir(
+ oldChan,
+ newChan,
+ flags,
+ callback
+ ) {
+ // veto
+ this.called = true;
+ throw Components.Exception("", Cr.NS_BINDING_ABORTED);
+ },
+
+ getInterface: function eventsink_gi(iid) {
+ if (iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ called: false,
+};
+
+var listener = {
+ expectSinkCall: true,
+
+ onStartRequest: function test_onStartR(request) {
+ try {
+ // Commenting out this check pending resolution of bug 255119
+ //if (Components.isSuccessCode(request.status))
+ // do_throw("Channel should have a failure code!");
+
+ // The current URI must be the original URI, as all redirects have been
+ // cancelled
+ if (
+ !(request instanceof Ci.nsIChannel) ||
+ !request.URI.equals(request.originalURI)
+ ) {
+ do_throw(
+ "Wrong URI: Is <" +
+ request.URI.spec +
+ ">, should be <" +
+ request.originalURI.spec +
+ ">"
+ );
+ }
+
+ if (request instanceof Ci.nsIHttpChannel) {
+ // As we expect a blocked redirect, verify that we have a 3xx status
+ Assert.equal(Math.floor(request.responseStatus / 100), 3);
+ Assert.equal(request.requestSucceeded, false);
+ }
+
+ Assert.equal(eventsink.called, this.expectSinkCall);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ if (this._iteration <= 2) {
+ run_test_continued();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+ do_test_finished();
+ },
+
+ _iteration: 1,
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/redirect", redirect);
+ httpserv.registerPathHandler("/redirectfile", redirectfile);
+ httpserv.start(-1);
+
+ Components.manager.nsIComponentRegistrar.registerFactory(
+ sinkCID,
+ "Unit test Event sink",
+ sinkContract,
+ eventsink
+ );
+
+ // Step 1: Set the callbacks on the listener itself
+ var chan = makeChan(URL + "/redirect");
+ chan.notificationCallbacks = eventsink;
+
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function run_test_continued() {
+ eventsink.called = false;
+
+ var chan;
+ if (listener._iteration == 1) {
+ // Step 2: Category entry
+ Services.catMan.addCategoryEntry(
+ categoryName,
+ "unit test",
+ sinkContract,
+ false,
+ true
+ );
+ chan = makeChan(URL + "/redirect");
+ } else {
+ // Step 3: Global contract id
+ Services.catMan.deleteCategoryEntry(categoryName, "unit test", false);
+ listener.expectSinkCall = false;
+ chan = makeChan(URL + "/redirectfile");
+ }
+
+ listener._iteration++;
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+// PATHS
+
+// /redirect
+function redirect(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+ response.setHeader(
+ "Location",
+ "http://localhost:" + metadata.port + "/",
+ false
+ );
+
+ var body = "Moved\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /redirectfile
+function redirectfile(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Location", "file:///etc/", false);
+
+ var body = "Attempted to move to a file URI, but failed.\n";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_eviction.js b/netwerk/test/unit/test_eviction.js
new file mode 100644
index 0000000000..eac7ece5be
--- /dev/null
+++ b/netwerk/test/unit/test_eviction.js
@@ -0,0 +1,252 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function continue_test() {
+ do_run_generator(test_generator);
+}
+
+function repeat_test() {
+ // The test is probably going to fail because setting a batch of cookies took
+ // a significant fraction of 'gPurgeAge'. Compensate by rerunning the
+ // test with a larger purge age.
+ Assert.ok(gPurgeAge < 64);
+ gPurgeAge *= 2;
+ gShortExpiry *= 2;
+
+ executeSoon(function () {
+ test_generator.return();
+ test_generator = do_run_test();
+ do_run_generator(test_generator);
+ });
+}
+
+// Purge threshold, in seconds.
+var gPurgeAge = 1;
+
+// Short expiry age, in seconds.
+var gShortExpiry = 2;
+
+// Required delay to ensure a purge occurs, in milliseconds. This must be at
+// least gPurgeAge + 10%, and includes a little fuzz to account for timer
+// resolution and possible differences between PR_Now() and Date.now().
+function get_purge_delay() {
+ return gPurgeAge * 1100 + 100;
+}
+
+// Required delay to ensure a cookie set with an expiry time 'gShortExpiry' into
+// the future will have expired.
+function get_expiry_delay() {
+ return gShortExpiry * 1000 + 100;
+}
+
+function* do_run_test() {
+ // Set up a profile.
+ do_get_profile();
+
+ // twiddle prefs to convenient values for this test
+ Services.prefs.setIntPref("network.cookie.purgeAge", gPurgeAge);
+ Services.prefs.setIntPref("network.cookie.maxNumber", 100);
+
+ let expiry = Date.now() / 1000 + 1000;
+
+ // eviction is performed based on two limits: when the total number of cookies
+ // exceeds maxNumber + 10% (110), and when cookies are older than purgeAge
+ // (1 second). purging is done when both conditions are satisfied, and only
+ // those cookies are purged.
+
+ // we test the following cases of eviction:
+ // 1) excess and age are satisfied, but only some of the excess are old enough
+ // to be purged.
+ Services.cookies.removeAll();
+ if (!set_cookies(0, 5, expiry)) {
+ repeat_test();
+ return;
+ }
+ // Sleep a while, to make sure the first batch of cookies is older than
+ // the second (timer resolution varies on different platforms).
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(5, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ // Fake a profile change, to ensure eviction affects the database correctly.
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ Assert.ok(check_remaining_cookies(111, 5, 106));
+
+ // 2) excess and age are satisfied, and all of the excess are old enough
+ // to be purged.
+ Services.cookies.removeAll();
+ if (!set_cookies(0, 10, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(10, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ Assert.ok(check_remaining_cookies(111, 10, 101));
+
+ // 3) excess and age are satisfied, and more than the excess are old enough
+ // to be purged.
+ Services.cookies.removeAll();
+ if (!set_cookies(0, 50, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(50, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ Assert.ok(check_remaining_cookies(111, 50, 101));
+
+ // 4) excess but not age are satisfied.
+ Services.cookies.removeAll();
+ if (!set_cookies(0, 120, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ Assert.ok(check_remaining_cookies(120, 0, 120));
+
+ // 5) age but not excess are satisfied.
+ Services.cookies.removeAll();
+ if (!set_cookies(0, 20, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(20, 110, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ Assert.ok(check_remaining_cookies(110, 20, 110));
+
+ // 6) Excess and age are satisfied, but the cookie limit can be satisfied by
+ // purging expired cookies.
+ Services.cookies.removeAll();
+ let shortExpiry = Math.floor(Date.now() / 1000) + gShortExpiry;
+ if (!set_cookies(0, 20, shortExpiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_expiry_delay(), continue_test);
+ yield;
+ if (!set_cookies(20, 110, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(110, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ Assert.ok(check_remaining_cookies(111, 20, 91));
+
+ do_finish_generator_test(test_generator);
+}
+
+// Set 'end - begin' total cookies, with consecutively increasing hosts numbered
+// 'begin' to 'end'.
+function set_cookies(begin, end, expiry) {
+ Assert.ok(begin != end);
+
+ let beginTime;
+ for (let i = begin; i < end; ++i) {
+ let host = "eviction." + i + ".tests";
+ Services.cookies.add(
+ host,
+ "",
+ "test",
+ "eviction",
+ false,
+ false,
+ false,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+
+ if (i == begin) {
+ beginTime = get_creationTime(i);
+ }
+ }
+
+ let endTime = get_creationTime(end - 1);
+ Assert.ok(begin == end - 1 || endTime > beginTime);
+ if (endTime - beginTime > gPurgeAge * 1000000) {
+ // Setting cookies took an amount of time very close to the purge threshold.
+ // Retry the test with a larger threshold.
+ return false;
+ }
+
+ return true;
+}
+
+function get_creationTime(i) {
+ let host = "eviction." + i + ".tests";
+ let cookies = Services.cookies.getCookiesFromHost(host, {});
+ Assert.ok(cookies.length);
+ let cookie = cookies[0];
+ return cookie.creationTime;
+}
+
+// Test that 'aNumberToExpect' cookies remain after purging is complete, and
+// that the cookies that remain consist of the set expected given the number of
+// of older and newer cookies -- eviction should occur by order of lastAccessed
+// time, if both the limit on total cookies (maxNumber + 10%) and the purge age
+// + 10% are exceeded.
+function check_remaining_cookies(aNumberTotal, aNumberOld, aNumberToExpect) {
+ let i = 0;
+ for (let cookie of Services.cookies.cookies) {
+ ++i;
+
+ if (aNumberTotal != aNumberToExpect) {
+ // make sure the cookie is one of the batch we expect was purged.
+ var hostNumber = Number(cookie.rawHost.split(".")[1]);
+ if (hostNumber < aNumberOld - aNumberToExpect) {
+ break;
+ }
+ }
+ }
+
+ return i == aNumberToExpect;
+}
diff --git a/netwerk/test/unit/test_extract_charset_from_content_type.js b/netwerk/test/unit/test_extract_charset_from_content_type.js
new file mode 100644
index 0000000000..a90e646048
--- /dev/null
+++ b/netwerk/test/unit/test_extract_charset_from_content_type.js
@@ -0,0 +1,238 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var charset = {};
+var charsetStart = {};
+var charsetEnd = {};
+var hadCharset;
+
+function check(aHadCharset, aCharset, aCharsetStart, aCharsetEnd) {
+ Assert.equal(aHadCharset, hadCharset);
+ Assert.equal(aCharset, charset.value);
+ Assert.equal(aCharsetStart, charsetStart.value);
+ Assert.equal(aCharsetEnd, charsetEnd.value);
+}
+
+function run_test() {
+ var netutil = Services.io;
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 9, 9);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "TEXT/HTML",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 9, 9);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html, text/html",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 9, 9);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html, text/plain",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 21, 21);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html, ",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 9, 9);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html, */*",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 9, 9);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html, foo",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 9, 9);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html; charset=ISO-8859-1",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 9, 29);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html ; charset=ISO-8859-1",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 11, 34);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html ; charset=ISO-8859-1 ",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 11, 36);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html ; charset=ISO-8859-1 ; ",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 11, 35);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/html; charset="ISO-8859-1"',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 9, 31);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html; charset='ISO-8859-1'",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "'ISO-8859-1'", 9, 31);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/html; charset="ISO-8859-1", text/html',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 9, 31);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/html; charset="ISO-8859-1", text/html; charset=UTF8',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "UTF8", 42, 56);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html; charset=ISO-8859-1, TEXT/HTML",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 9, 29);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/html; charset=ISO-8859-1, TEXT/plain",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 41, 41);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain, TEXT/HTML; charset="ISO-8859-1", text/html, TEXT/HTML',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 21, 43);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 43, 65);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(true, "ISO-8859-1", 41, 63);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/plain; param= , text/html",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 30, 30);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain; param=", text/html"',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 10, 10);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain; param=", \\" , text/html"',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 10, 10);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain; param=", \\" , text/html , "',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 10, 10);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain param=", \\" , text/html , "',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 38, 38);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ "text/plain charset=UTF8",
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 23, 23);
+
+ hadCharset = netutil.extractCharsetFromContentType(
+ 'text/plain, TEXT/HTML; param="charset=UTF8"; ; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset,
+ charsetStart,
+ charsetEnd
+ );
+ check(false, "", 21, 21);
+}
diff --git a/netwerk/test/unit/test_file_protocol.js b/netwerk/test/unit/test_file_protocol.js
new file mode 100644
index 0000000000..707bddef24
--- /dev/null
+++ b/netwerk/test/unit/test_file_protocol.js
@@ -0,0 +1,277 @@
+/* run some tests on the file:// protocol handler */
+
+"use strict";
+
+const PR_RDONLY = 0x1; // see prio.h
+
+const special_type = "application/x-our-special-type";
+
+[
+ test_read_file,
+ test_read_dir_1,
+ test_read_dir_2,
+ test_upload_file,
+ test_load_shelllink,
+ do_test_finished,
+].forEach(f => add_test(f));
+
+function new_file_input_stream(file, buffered) {
+ var stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ stream.init(file, PR_RDONLY, 0, 0);
+ if (!buffered) {
+ return stream;
+ }
+
+ var buffer = Cc[
+ "@mozilla.org/network/buffered-input-stream;1"
+ ].createInstance(Ci.nsIBufferedInputStream);
+ buffer.init(stream, 4096);
+ return buffer;
+}
+
+function new_file_channel(file) {
+ let uri = Services.io.newFileURI(file);
+ return NetUtil.newChannel({
+ uri,
+ loadingPrincipal: Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ ),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+}
+
+/*
+ * stream listener
+ * this listener has some additional file-specific tests, so we can't just use
+ * ChannelListener here.
+ */
+function FileStreamListener(closure) {
+ this._closure = closure;
+}
+FileStreamListener.prototype = {
+ _closure: null,
+ _buffer: "",
+ _got_onstartrequest: false,
+ _got_onstoprequest: false,
+ _contentLen: -1,
+
+ _isDir(request) {
+ request.QueryInterface(Ci.nsIFileChannel);
+ return request.file.isDirectory();
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ if (this._got_onstartrequest) {
+ do_throw("Got second onStartRequest event!");
+ }
+ this._got_onstartrequest = true;
+
+ if (!this._isDir(request)) {
+ request.QueryInterface(Ci.nsIChannel);
+ this._contentLen = request.contentLength;
+ if (this._contentLen == -1) {
+ do_throw("Content length is unknown in onStartRequest!");
+ }
+ }
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ if (!this._got_onstartrequest) {
+ do_throw("onDataAvailable without onStartRequest event!");
+ }
+ if (this._got_onstoprequest) {
+ do_throw("onDataAvailable after onStopRequest event!");
+ }
+ if (!request.isPending()) {
+ do_throw("request reports itself as not pending from onStartRequest!");
+ }
+
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest(request, status) {
+ if (!this._got_onstartrequest) {
+ do_throw("onStopRequest without onStartRequest event!");
+ }
+ if (this._got_onstoprequest) {
+ do_throw("Got second onStopRequest event!");
+ }
+ this._got_onstoprequest = true;
+ if (!Components.isSuccessCode(status)) {
+ do_throw("Failed to load file: " + status.toString(16));
+ }
+ if (status != request.status) {
+ do_throw("request.status does not match status arg to onStopRequest!");
+ }
+ if (request.isPending()) {
+ do_throw("request reports itself as pending from onStopRequest!");
+ }
+ if (this._contentLen != -1 && this._buffer.length != this._contentLen) {
+ do_throw("did not read nsIChannel.contentLength number of bytes!");
+ }
+
+ this._closure(this._buffer);
+ },
+};
+
+function test_read_file() {
+ dump("*** test_read_file\n");
+
+ var file = do_get_file("../unit/data/test_readline6.txt");
+ var chan = new_file_channel(file);
+
+ function on_read_complete(data) {
+ dump("*** test_read_file.on_read_complete\n");
+
+ // bug 326693
+ if (chan.contentType != special_type) {
+ do_throw(
+ "Type mismatch! Is <" +
+ chan.contentType +
+ ">, should be <" +
+ special_type +
+ ">"
+ );
+ }
+
+ /* read completed successfully. now read data directly from file,
+ and compare the result. */
+ var stream = new_file_input_stream(file, false);
+ var result = read_stream(stream, stream.available());
+ if (result != data) {
+ do_throw("Stream contents do not match with direct read!");
+ }
+ run_next_test();
+ }
+
+ chan.contentType = special_type;
+ chan.asyncOpen(new FileStreamListener(on_read_complete));
+}
+
+function do_test_read_dir(set_type, expected_type) {
+ dump("*** test_read_dir(" + set_type + ", " + expected_type + ")\n");
+
+ var file = do_get_tempdir();
+ var chan = new_file_channel(file);
+
+ function on_read_complete(data) {
+ dump(
+ "*** test_read_dir.on_read_complete(" +
+ set_type +
+ ", " +
+ expected_type +
+ ")\n"
+ );
+
+ // bug 326693
+ if (chan.contentType != expected_type) {
+ do_throw(
+ "Type mismatch! Is <" +
+ chan.contentType +
+ ">, should be <" +
+ expected_type +
+ ">"
+ );
+ }
+
+ run_next_test();
+ }
+
+ if (set_type) {
+ chan.contentType = expected_type;
+ }
+ chan.asyncOpen(new FileStreamListener(on_read_complete));
+}
+
+function test_read_dir_1() {
+ return do_test_read_dir(false, "application/http-index-format");
+}
+
+function test_read_dir_2() {
+ return do_test_read_dir(true, special_type);
+}
+
+function test_upload_file() {
+ dump("*** test_upload_file\n");
+
+ var file = do_get_file("../unit/data/test_readline6.txt"); // file to upload
+ var dest = do_get_tempdir(); // file upload destination
+ dest.append("junk.dat");
+ dest.createUnique(dest.NORMAL_FILE_TYPE, 0o600);
+
+ var uploadstream = new_file_input_stream(file, true);
+
+ var chan = new_file_channel(dest);
+ chan.QueryInterface(Ci.nsIUploadChannel);
+ chan.setUploadStream(uploadstream, "", file.fileSize);
+
+ function on_upload_complete(data) {
+ dump("*** test_upload_file.on_upload_complete\n");
+
+ // bug 326693
+ if (chan.contentType != special_type) {
+ do_throw(
+ "Type mismatch! Is <" +
+ chan.contentType +
+ ">, should be <" +
+ special_type +
+ ">"
+ );
+ }
+
+ /* upload of file completed successfully. */
+ if (data.length) {
+ do_throw("Upload resulted in data!");
+ }
+
+ var oldstream = new_file_input_stream(file, false);
+ var newstream = new_file_input_stream(dest, false);
+ var olddata = read_stream(oldstream, oldstream.available());
+ var newdata = read_stream(newstream, newstream.available());
+ if (olddata != newdata) {
+ do_throw("Stream contents do not match after file copy!");
+ }
+ oldstream.close();
+ newstream.close();
+
+ /* cleanup... also ensures that the destination file is not in
+ use when OnStopRequest is called. */
+ try {
+ dest.remove(false);
+ } catch (e) {
+ dump(e + "\n");
+ do_throw("Unable to remove uploaded file!\n");
+ }
+
+ run_next_test();
+ }
+
+ chan.contentType = special_type;
+ chan.asyncOpen(new FileStreamListener(on_upload_complete));
+}
+
+function test_load_shelllink() {
+ // lnk files should not resolve to their targets
+ dump("*** test_load_shelllink\n");
+ let file = do_get_file("data/system_root.lnk", false);
+ var chan = new_file_channel(file);
+
+ // The original URI path should be the same as the URI path
+ Assert.equal(chan.URI.pathQueryRef, chan.originalURI.pathQueryRef);
+
+ // The original URI path should be the same as the lnk file path
+ Assert.equal(
+ chan.originalURI.pathQueryRef,
+ Services.io.newFileURI(file).pathQueryRef
+ );
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_filestreams.js b/netwerk/test/unit/test_filestreams.js
new file mode 100644
index 0000000000..b1edf3d27b
--- /dev/null
+++ b/netwerk/test/unit/test_filestreams.js
@@ -0,0 +1,300 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// We need the profile directory so the test harness will clean up our test
+// files.
+do_get_profile();
+
+const OUTPUT_STREAM_CONTRACT_ID = "@mozilla.org/network/file-output-stream;1";
+const SAFE_OUTPUT_STREAM_CONTRACT_ID =
+ "@mozilla.org/network/safe-file-output-stream;1";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helper Methods
+
+/**
+ * Generates a leafName for a file that does not exist, but does *not*
+ * create the file. Similar to createUnique except for the fact that createUnique
+ * does create the file.
+ *
+ * @param aFile
+ * The file to modify in order for it to have a unique leafname.
+ */
+function ensure_unique(aFile) {
+ ensure_unique.fileIndex = ensure_unique.fileIndex || 0;
+
+ var leafName = aFile.leafName;
+ while (aFile.clone().exists()) {
+ aFile.leafName = leafName + "_" + ensure_unique.fileIndex++;
+ }
+}
+
+/**
+ * Tests for files being accessed at the right time. Streams that use
+ * DEFER_OPEN should only open or create the file when an operation is
+ * done, and not during Init().
+ *
+ * Note that for writing, we check for actual writing in test_NetUtil (async)
+ * and in sync_operations in this file (sync), whereas in this function we
+ * just check that the file is *not* created during init.
+ *
+ * @param aContractId
+ * The contract ID to use for the output stream
+ * @param aDeferOpen
+ * Whether to check with DEFER_OPEN or not
+ * @param aTrickDeferredOpen
+ * Whether we try to 'trick' deferred opens by changing the file object before
+ * the actual open. The stream should have a clone, so changes to the file
+ * object after Init and before Open should not affect it.
+ */
+function check_access(aContractId, aDeferOpen, aTrickDeferredOpen) {
+ const LEAF_NAME = "filestreams-test-file.tmp";
+ const TRICKY_LEAF_NAME = "BetYouDidNotExpectThat.tmp";
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(LEAF_NAME);
+
+ // Writing
+
+ ensure_unique(file);
+ let ostream = Cc[aContractId].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(
+ file,
+ -1,
+ -1,
+ aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0
+ );
+ Assert.equal(aDeferOpen, !file.clone().exists()); // If defer, should not exist and vice versa
+ if (aDeferOpen) {
+ // File should appear when we do write to it.
+ if (aTrickDeferredOpen) {
+ // See |@param aDeferOpen| in the JavaDoc comment for this function
+ file.leafName = TRICKY_LEAF_NAME;
+ }
+ ostream.write("data", 4);
+ if (aTrickDeferredOpen) {
+ file.leafName = LEAF_NAME;
+ }
+ // We did a write, so the file should now exist
+ Assert.ok(file.clone().exists());
+ }
+ ostream.close();
+
+ // Reading
+
+ ensure_unique(file);
+ let istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ var initOk, getOk;
+ try {
+ istream.init(
+ file,
+ -1,
+ 0,
+ aDeferOpen ? Ci.nsIFileInputStream.DEFER_OPEN : 0
+ );
+ initOk = true;
+ } catch (e) {
+ initOk = false;
+ }
+ try {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, 0, 0);
+ getOk = true;
+ } catch (e) {
+ getOk = false;
+ }
+
+ // If the open is deferred, then Init should succeed even though the file we
+ // intend to read does not exist, and then trying to read from it should
+ // fail. The other case is where the open is not deferred, and there we should
+ // get an error when we Init (and also when we try to read).
+ Assert.ok(
+ (aDeferOpen && initOk && !getOk) || (!aDeferOpen && !initOk && !getOk)
+ );
+ istream.close();
+}
+
+/**
+ * We test async operations in test_NetUtil.js, and here test for simple sync
+ * operations on input streams.
+ *
+ * @param aDeferOpen
+ * Whether to use DEFER_OPEN in the streams.
+ */
+function sync_operations(aDeferOpen) {
+ const TEST_DATA = "this is a test string";
+ const LEAF_NAME = "filestreams-test-file.tmp";
+
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(LEAF_NAME);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let ostream = Cc[OUTPUT_STREAM_CONTRACT_ID].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ ostream.init(
+ file,
+ -1,
+ -1,
+ aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0
+ );
+
+ ostream.write(TEST_DATA, TEST_DATA.length);
+ ostream.close();
+
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, 0, aDeferOpen ? Ci.nsIFileInputStream.DEFER_OPEN : 0);
+
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+ Ci.nsIConverterInputStream
+ );
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let string = {};
+ cstream.readString(-1, string);
+ cstream.close();
+ fstream.close();
+
+ Assert.equal(string.value, TEST_DATA);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+function test_access() {
+ check_access(OUTPUT_STREAM_CONTRACT_ID, false, false);
+}
+
+function test_access_trick() {
+ check_access(OUTPUT_STREAM_CONTRACT_ID, false, true);
+}
+
+function test_access_defer() {
+ check_access(OUTPUT_STREAM_CONTRACT_ID, true, false);
+}
+
+function test_access_defer_trick() {
+ check_access(OUTPUT_STREAM_CONTRACT_ID, true, true);
+}
+
+function test_access_safe() {
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, false, false);
+}
+
+function test_access_safe_trick() {
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, false, true);
+}
+
+function test_access_safe_defer() {
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, true, false);
+}
+
+function test_access_safe_defer_trick() {
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, true, true);
+}
+
+function test_sync_operations() {
+ sync_operations();
+}
+
+function test_sync_operations_deferred() {
+ sync_operations(true);
+}
+
+function do_test_zero_size_buffered(disableBuffering) {
+ const LEAF_NAME = "filestreams-test-file.tmp";
+ const BUFFERSIZE = 4096;
+
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(LEAF_NAME);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(
+ file,
+ -1,
+ 0,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF | Ci.nsIFileInputStream.REOPEN_ON_REWIND
+ );
+
+ var buffered = Cc[
+ "@mozilla.org/network/buffered-input-stream;1"
+ ].createInstance(Ci.nsIBufferedInputStream);
+ buffered.init(fstream, BUFFERSIZE);
+
+ if (disableBuffering) {
+ buffered.QueryInterface(Ci.nsIStreamBufferAccess).disableBuffering();
+ }
+
+ // Scriptable input streams clamp read sizes to the return value of
+ // available(), so don't quite do what we want here.
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+ Ci.nsIConverterInputStream
+ );
+ cstream.init(buffered, "UTF-8", 0, 0);
+
+ Assert.equal(buffered.available(), 0);
+
+ // Now try reading from this stream
+ let string = {};
+ Assert.equal(cstream.readString(BUFFERSIZE, string), 0);
+ Assert.equal(string.value, "");
+
+ // Now check that available() throws
+ var exceptionThrown = false;
+ try {
+ Assert.equal(buffered.available(), 0);
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ Assert.ok(exceptionThrown);
+
+ // OK, now seek back to start
+ buffered.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+
+ // Now check that available() does not throw
+ exceptionThrown = false;
+ try {
+ Assert.equal(buffered.available(), 0);
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ Assert.ok(!exceptionThrown);
+}
+
+function test_zero_size_buffered() {
+ do_test_zero_size_buffered(false);
+ do_test_zero_size_buffered(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Runner
+
+var tests = [
+ test_access,
+ test_access_trick,
+ test_access_defer,
+ test_access_defer_trick,
+ test_access_safe,
+ test_access_safe_trick,
+ test_access_safe_defer,
+ test_access_safe_defer_trick,
+ test_sync_operations,
+ test_sync_operations_deferred,
+ test_zero_size_buffered,
+];
+
+function run_test() {
+ tests.forEach(function (test) {
+ test();
+ });
+}
diff --git a/netwerk/test/unit/test_freshconnection.js b/netwerk/test/unit/test_freshconnection.js
new file mode 100644
index 0000000000..5d0f5bc5b7
--- /dev/null
+++ b/netwerk/test/unit/test_freshconnection.js
@@ -0,0 +1,30 @@
+// This is essentially a debug mode crashtest to make sure everything
+// involved in a reload runs on the right thread. It relies on the
+// assertions in necko.
+
+"use strict";
+
+var listener = {
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ do_test_finished();
+ },
+};
+
+function run_test() {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:4444",
+ loadUsingSystemPrincipal: true,
+ });
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_getHost.js b/netwerk/test/unit/test_getHost.js
new file mode 100644
index 0000000000..c27fc0c028
--- /dev/null
+++ b/netwerk/test/unit/test_getHost.js
@@ -0,0 +1,65 @@
+// Test getLocalHost/getLocalPort and getRemoteHost/getRemotePort.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+var gotOnStartRequest = false;
+
+function CheckGetHostListener() {}
+
+CheckGetHostListener.prototype = {
+ onStartRequest(request) {
+ dump("*** listener onStartRequest\n");
+
+ gotOnStartRequest = true;
+
+ request.QueryInterface(Ci.nsIHttpChannelInternal);
+ try {
+ Assert.equal(request.localAddress, "127.0.0.1");
+ Assert.equal(request.localPort > 0, true);
+ Assert.notEqual(request.localPort, PORT);
+ Assert.equal(request.remoteAddress, "127.0.0.1");
+ Assert.equal(request.remotePort, PORT);
+ } catch (e) {
+ Assert.ok(0, "Get local/remote host/port throws an error!");
+ }
+ },
+
+ onStopRequest(request, statusCode) {
+ dump("*** listener onStopRequest\n");
+
+ Assert.equal(gotOnStartRequest, true);
+ httpserver.stop(do_test_finished);
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIRequestObserver"]),
+};
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ var responseBody = "blah blah";
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/testdir", test_handler);
+
+ var channel = make_channel("http://localhost:" + PORT + "/testdir");
+ channel.asyncOpen(new CheckGetHostListener());
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_gio_protocol.js b/netwerk/test/unit/test_gio_protocol.js
new file mode 100644
index 0000000000..37ce37abab
--- /dev/null
+++ b/netwerk/test/unit/test_gio_protocol.js
@@ -0,0 +1,201 @@
+/* run some tests on the gvfs/gio protocol handler */
+
+"use strict";
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+const PR_RDONLY = 0x1; // see prio.h
+
+[
+ do_test_read_data_dir,
+ do_test_read_recent,
+ test_read_file,
+ do_test_finished,
+].forEach(f => add_test(f));
+
+function setup() {
+ // Allowing some protocols to get a channel
+ if (!inChildProcess()) {
+ Services.prefs.setCharPref(
+ "network.gio.supported-protocols",
+ "localtest:,recent:"
+ );
+ } else {
+ do_send_remote_message("gio-allow-test-protocols");
+ do_await_remote_message("gio-allow-test-protocols-done");
+ }
+}
+
+setup();
+
+registerCleanupFunction(() => {
+ // Resetting the protocols to None
+ if (!inChildProcess()) {
+ Services.prefs.clearUserPref("network.gio.supported-protocols");
+ }
+});
+
+function new_file_input_stream(file, buffered) {
+ var stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ stream.init(file, PR_RDONLY, 0, 0);
+ if (!buffered) {
+ return stream;
+ }
+
+ var buffer = Cc[
+ "@mozilla.org/network/buffered-input-stream;1"
+ ].createInstance(Ci.nsIBufferedInputStream);
+ buffer.init(stream, 4096);
+ return buffer;
+}
+
+function new_file_channel(file) {
+ var chan = NetUtil.newChannel({
+ uri: file,
+ loadUsingSystemPrincipal: true,
+ });
+
+ return chan;
+}
+
+/*
+ * stream listener
+ * this listener has some additional file-specific tests, so we can't just use
+ * ChannelListener here.
+ */
+function FileStreamListener(closure) {
+ this._closure = closure;
+}
+FileStreamListener.prototype = {
+ _closure: null,
+ _buffer: "",
+ _got_onstartrequest: false,
+ _got_onstoprequest: false,
+ _contentLen: -1,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ if (this._got_onstartrequest) {
+ do_throw("Got second onStartRequest event!");
+ }
+ this._got_onstartrequest = true;
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ if (!this._got_onstartrequest) {
+ do_throw("onDataAvailable without onStartRequest event!");
+ }
+ if (this._got_onstoprequest) {
+ do_throw("onDataAvailable after onStopRequest event!");
+ }
+ if (!request.isPending()) {
+ do_throw("request reports itself as not pending from onStartRequest!");
+ }
+
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest(request, status) {
+ if (!this._got_onstartrequest) {
+ do_throw("onStopRequest without onStartRequest event!");
+ }
+ if (this._got_onstoprequest) {
+ do_throw("Got second onStopRequest event!");
+ }
+ this._got_onstoprequest = true;
+ if (!Components.isSuccessCode(status)) {
+ do_throw("Failed to load file: " + status.toString(16));
+ }
+ if (status != request.status) {
+ do_throw("request.status does not match status arg to onStopRequest!");
+ }
+ if (request.isPending()) {
+ do_throw("request reports itself as pending from onStopRequest!");
+ }
+ if (this._contentLen != -1 && this._buffer.length != this._contentLen) {
+ do_throw("did not read nsIChannel.contentLength number of bytes!");
+ }
+
+ this._closure(this._buffer);
+ },
+};
+
+function test_read_file() {
+ dump("*** test_read_file\n");
+ // Going via parent path, because this is opended from test/unit/ and test/unit_ipc/
+ var file = do_get_file("../unit/data/test_readline4.txt");
+ var chan = new_file_channel("localtest://" + file.path);
+
+ function on_read_complete(data) {
+ dump("*** test_read_file.on_read_complete()\n");
+ /* read completed successfully. now read data directly from file,
+ and compare the result. */
+ var stream = new_file_input_stream(file, false);
+ var result = read_stream(stream, stream.available());
+ if (result != data) {
+ do_throw("Stream contents do not match with direct read!");
+ }
+ run_next_test();
+ }
+
+ chan.asyncOpen(new FileStreamListener(on_read_complete));
+}
+
+function do_test_read_data_dir() {
+ dump('*** test_read_data_dir("../data/")\n');
+
+ var dir = do_get_file("../unit/data/");
+ var chan = new_file_channel("localtest://" + dir.path);
+
+ function on_read_complete(data) {
+ dump("*** test_read_data_dir.on_read_complete()\n");
+
+ // The data-directory should be listed, containing a header-line and the files therein
+ if (
+ !(
+ data.includes("200: filename content-length last-modified file-type") &&
+ data.includes("201: test_readline1.txt") &&
+ data.includes("201: test_readline2.txt")
+ )
+ ) {
+ do_throw(
+ "test_read_data_dir() - Bad data! Does not contain needles! Is <" +
+ data +
+ ">"
+ );
+ }
+ run_next_test();
+ }
+ chan.asyncOpen(new FileStreamListener(on_read_complete));
+}
+
+function do_test_read_recent() {
+ dump('*** test_read_recent("recent://")\n');
+
+ var chan = new_file_channel("recent:///");
+
+ function on_read_complete(data) {
+ dump("*** test_read_recent.on_read_complete()\n");
+
+ // The data-directory should be listed, containing a header-line and the files therein
+ if (
+ !data.includes("200: filename content-length last-modified file-type")
+ ) {
+ do_throw(
+ "do_test_read_recent() - Bad data! Does not contain header! Is <" +
+ data +
+ ">"
+ );
+ }
+ run_next_test();
+ }
+ chan.asyncOpen(new FileStreamListener(on_read_complete));
+}
diff --git a/netwerk/test/unit/test_gre_resources.js b/netwerk/test/unit/test_gre_resources.js
new file mode 100644
index 0000000000..4ea8d04b95
--- /dev/null
+++ b/netwerk/test/unit/test_gre_resources.js
@@ -0,0 +1,30 @@
+// test that things that are expected to be in gre-resources are still there
+
+"use strict";
+
+function wrapInputStream(input) {
+ var nsIScriptableInputStream = Ci.nsIScriptableInputStream;
+ var factory = Cc["@mozilla.org/scriptableinputstream;1"];
+ var wrapper = factory.createInstance(nsIScriptableInputStream);
+ wrapper.init(input);
+ return wrapper;
+}
+
+function check_file(file) {
+ var channel = NetUtil.newChannel({
+ uri: "resource://gre-resources/" + file,
+ loadUsingSystemPrincipal: true,
+ });
+ try {
+ let instr = wrapInputStream(channel.open());
+ Assert.ok(!!instr.read(1024).length);
+ } catch (e) {
+ do_throw("Failed to read " + file + " from gre-resources:" + e);
+ }
+}
+
+function run_test() {
+ for (let file of ["ua.css"]) {
+ check_file(file);
+ }
+}
diff --git a/netwerk/test/unit/test_h2proxy_connection_limit.js b/netwerk/test/unit/test_h2proxy_connection_limit.js
new file mode 100644
index 0000000000..f326b48b40
--- /dev/null
+++ b/netwerk/test/unit/test_h2proxy_connection_limit.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Summary:
+// Test whether the connection limit is honored when http2 proxy is used.
+//
+// Test step:
+// 1. Create 30 http requests.
+// 2. Check if the count of all sockets created by proxy is less than 6.
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+add_task(async function test_connection_limit() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ const maxConnections = 6;
+ Services.prefs.setIntPref(
+ "network.http.max-persistent-connections-per-server",
+ maxConnections
+ );
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref(
+ "network.http.max-persistent-connections-per-server"
+ );
+ });
+
+ await with_node_servers([NodeHTTP2Server], async server => {
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("All good");
+ });
+
+ let promises = [];
+ for (let i = 0; i < 30; ++i) {
+ let chan = makeChan(`${server.origin()}/test`);
+ promises.push(
+ new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL)
+ );
+ })
+ );
+ }
+ await Promise.all(promises);
+ let count = await proxy.socketCount(server.port());
+ Assert.lessOrEqual(
+ count,
+ maxConnections,
+ "socket count should be less than maxConnections"
+ );
+ });
+});
diff --git a/netwerk/test/unit/test_head.js b/netwerk/test/unit/test_head.js
new file mode 100644
index 0000000000..8a6b2a5cd7
--- /dev/null
+++ b/netwerk/test/unit/test_head.js
@@ -0,0 +1,171 @@
+//
+// HTTP headers test
+//
+
+// Note: sets Cc and Ci variables
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+var channel;
+
+var dbg = 0;
+if (dbg) {
+ print("============== START ==========");
+}
+
+function run_test() {
+ setup_test();
+ do_test_pending();
+}
+
+function setup_test() {
+ if (dbg) {
+ print("============== setup_test: in");
+ }
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ channel = setupChannel(testpath);
+
+ channel.setRequestHeader("ReplaceMe", "initial value", true);
+ var setOK = channel.getRequestHeader("ReplaceMe");
+ Assert.equal(setOK, "initial value");
+ channel.setRequestHeader("ReplaceMe", "replaced", false);
+ setOK = channel.getRequestHeader("ReplaceMe");
+ Assert.equal(setOK, "replaced");
+
+ channel.setRequestHeader("MergeMe", "foo1", true);
+ channel.setRequestHeader("MergeMe", "foo2", true);
+ channel.setRequestHeader("MergeMe", "foo3", true);
+ setOK = channel.getRequestHeader("MergeMe");
+ Assert.equal(setOK, "foo1, foo2, foo3");
+
+ channel.setEmptyRequestHeader("Empty");
+ setOK = channel.getRequestHeader("Empty");
+ Assert.equal(setOK, "");
+
+ channel.setRequestHeader("ReplaceWithEmpty", "initial value", true);
+ setOK = channel.getRequestHeader("ReplaceWithEmpty");
+ Assert.equal(setOK, "initial value");
+ channel.setEmptyRequestHeader("ReplaceWithEmpty");
+ setOK = channel.getRequestHeader("ReplaceWithEmpty");
+ Assert.equal(setOK, "");
+
+ channel.setEmptyRequestHeader("MergeWithEmpty");
+ setOK = channel.getRequestHeader("MergeWithEmpty");
+ Assert.equal(setOK, "");
+ channel.setRequestHeader("MergeWithEmpty", "foo", true);
+ setOK = channel.getRequestHeader("MergeWithEmpty");
+ Assert.equal(setOK, "foo");
+
+ var uri = NetUtil.newURI("http://foo1.invalid:80");
+ channel.referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+ setOK = channel.getRequestHeader("Referer");
+ Assert.equal(setOK, "http://foo1.invalid/");
+
+ uri = NetUtil.newURI("http://foo2.invalid:90/bar");
+ channel.referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+ setOK = channel.getRequestHeader("Referer");
+ // No triggering URI inloadInfo, assume load is cross-origin.
+ Assert.equal(setOK, "http://foo2.invalid:90/");
+
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen(new ChannelListener(checkRequestResponse, channel));
+
+ if (dbg) {
+ print("============== setup_test: out");
+ }
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ if (dbg) {
+ print("============== serverHandler: in");
+ }
+
+ var setOK = metadata.getHeader("ReplaceMe");
+ Assert.equal(setOK, "replaced");
+ setOK = metadata.getHeader("MergeMe");
+ Assert.equal(setOK, "foo1, foo2, foo3");
+ setOK = metadata.getHeader("Empty");
+ Assert.equal(setOK, "");
+ setOK = metadata.getHeader("ReplaceWithEmpty");
+ Assert.equal(setOK, "");
+ setOK = metadata.getHeader("MergeWithEmpty");
+ Assert.equal(setOK, "foo");
+ setOK = metadata.getHeader("Referer");
+ Assert.equal(setOK, "http://foo2.invalid:90/");
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setStatusLine("1.1", 200, "OK");
+
+ // note: httpd.js' "Response" class uses ',' (no space) for merge.
+ response.setHeader("httpdMerge", "bar1", false);
+ response.setHeader("httpdMerge", "bar2", true);
+ response.setHeader("httpdMerge", "bar3", true);
+ // Some special headers like Proxy-Authenticate merge with \n
+ response.setHeader("Proxy-Authenticate", "line 1", true);
+ response.setHeader("Proxy-Authenticate", "line 2", true);
+ response.setHeader("Proxy-Authenticate", "line 3", true);
+
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+
+ if (dbg) {
+ print("============== serverHandler: out");
+ }
+}
+
+function checkRequestResponse(request, data, context) {
+ if (dbg) {
+ print("============== checkRequestResponse: in");
+ }
+
+ Assert.equal(channel.responseStatus, 200);
+ Assert.equal(channel.responseStatusText, "OK");
+ Assert.ok(channel.requestSucceeded);
+
+ var response = channel.getResponseHeader("httpdMerge");
+ Assert.equal(response, "bar1,bar2,bar3");
+ channel.setResponseHeader("httpdMerge", "bar", true);
+ Assert.equal(channel.getResponseHeader("httpdMerge"), "bar1,bar2,bar3, bar");
+
+ response = channel.getResponseHeader("Proxy-Authenticate");
+ Assert.equal(response, "line 1\nline 2\nline 3");
+
+ channel.contentCharset = "UTF-8";
+ Assert.equal(channel.contentCharset, "UTF-8");
+ Assert.equal(channel.contentType, "text/plain");
+ Assert.equal(channel.contentLength, httpbody.length);
+ Assert.equal(data, httpbody);
+
+ httpserver.stop(do_test_finished);
+ if (dbg) {
+ print("============== checkRequestResponse: out");
+ }
+}
diff --git a/netwerk/test/unit/test_head_request_no_response_body.js b/netwerk/test/unit/test_head_request_no_response_body.js
new file mode 100644
index 0000000000..852a03040f
--- /dev/null
+++ b/netwerk/test/unit/test_head_request_no_response_body.js
@@ -0,0 +1,78 @@
+/* -*- 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/. */
+
+/*
+
+Test that a response to HEAD method should not have a body.
+1. Create a GET request and write the response into cache.
+2. Create the second GET request with the same URI and see if the response is
+ from cache.
+3. Create a HEAD request and test if we got a response with an empty body.
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const responseContent = "response body";
+
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-control", "max-age=9999", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ if (metadata.method != "HEAD") {
+ response.bodyOutputStream.write(responseContent, responseContent.length);
+ }
+}
+
+function make_channel(url, method) {
+ let channel = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ channel.requestMethod = method;
+ return channel;
+}
+
+async function get_response(channel, fromCache) {
+ return new Promise(resolve => {
+ channel.asyncOpen(
+ new ChannelListener((request, buffer, ctx, isFromCache) => {
+ ok(fromCache == isFromCache, `got response from cache = ${fromCache}`);
+ resolve(buffer);
+ })
+ );
+ });
+}
+
+async function stop_server(httpserver) {
+ return new Promise(resolve => {
+ httpserver.stop(resolve);
+ });
+}
+
+add_task(async function () {
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/testdir", test_handler);
+ httpserver.start(-1);
+ const PORT = httpserver.identity.primaryPort;
+ const URI = `http://localhost:${PORT}/testdir`;
+
+ let response;
+
+ response = await get_response(make_channel(URI, "GET"), false);
+ ok(response === responseContent, "got response body");
+
+ response = await get_response(make_channel(URI, "GET"), true);
+ ok(response === responseContent, "got response body from cache");
+
+ response = await get_response(make_channel(URI, "HEAD"), false);
+ ok(response === "", "should have empty body");
+
+ await stop_server(httpserver);
+});
diff --git a/netwerk/test/unit/test_header_Accept-Language.js b/netwerk/test/unit/test_header_Accept-Language.js
new file mode 100644
index 0000000000..b00e02d13b
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language.js
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// HTTP Accept-Language header test
+//
+
+"use strict";
+
+var testpath = "/bug672448";
+
+function run_test() {
+ let intlPrefs = Services.prefs.getBranch("intl.");
+
+ // Save old value of preference for later.
+ let oldPref = intlPrefs.getCharPref("accept_languages");
+
+ // Test different numbers of languages, to test different fractions.
+ let acceptLangTests = [
+ "qaa", // 1
+ "qaa,qab", // 2
+ "qaa,qab,qac,qad", // 4
+ "qaa,qab,qac,qad,qae,qaf,qag,qah", // 8
+ "qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj", // 10
+ "qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj,qak", // 11
+ "qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj,qak,qal,qam,qan,qao,qap,qaq,qar,qas,qat,qau", // 21
+ oldPref, // Restore old value of preference (and test it).
+ ];
+
+ let acceptLangTestsNum = acceptLangTests.length;
+
+ for (let i = 0; i < acceptLangTestsNum; i++) {
+ // Set preference to test value.
+ intlPrefs.setCharPref("accept_languages", acceptLangTests[i]);
+
+ // Test value.
+ test_accepted_languages();
+ }
+}
+
+function test_accepted_languages() {
+ let channel = setupChannel(testpath);
+
+ let AcceptLanguage = channel.getRequestHeader("Accept-Language");
+
+ let acceptedLanguages = AcceptLanguage.split(",");
+
+ let acceptedLanguagesLength = acceptedLanguages.length;
+
+ for (let i = 0; i < acceptedLanguagesLength; i++) {
+ let qualityValue;
+
+ try {
+ // The q-value must conform to the definition in HTTP/1.1 Section 3.9.
+ [, , qualityValue] = acceptedLanguages[i]
+ .trim()
+ .match(/^([a-z0-9_-]*?)(?:;q=(1(?:\.0{0,3})?|0(?:\.[0-9]{0,3})))?$/i);
+ } catch (e) {
+ do_throw("Invalid language tag or quality value: " + e);
+ }
+
+ if (i == 0) {
+ // The first language shouldn't have a quality value.
+ Assert.equal(qualityValue, undefined);
+ } else {
+ let decimalPlaces;
+
+ // When the number of languages is small, we keep the quality value to only one decimal place.
+ // Otherwise, it can be up to two decimal places.
+ if (acceptedLanguagesLength < 10) {
+ Assert.ok(qualityValue.length == 3);
+
+ decimalPlaces = 1;
+ } else {
+ Assert.ok(qualityValue.length >= 3);
+ Assert.ok(qualityValue.length <= 4);
+
+ decimalPlaces = 2;
+ }
+
+ // All the other languages should have an evenly-spaced quality value.
+ Assert.equal(
+ parseFloat(qualityValue).toFixed(decimalPlaces),
+ (1.0 - (1 / acceptedLanguagesLength) * i).toFixed(decimalPlaces)
+ );
+ }
+ }
+}
+
+function setupChannel(path) {
+ let chan = NetUtil.newChannel({
+ uri: "http://localhost:4444" + path,
+ loadUsingSystemPrincipal: true,
+ });
+
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
diff --git a/netwerk/test/unit/test_header_Accept-Language_case.js b/netwerk/test/unit/test_header_Accept-Language_case.js
new file mode 100644
index 0000000000..69d936d74a
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language_case.js
@@ -0,0 +1,50 @@
+"use strict";
+
+var testpath = "/bug1054739";
+
+function run_test() {
+ let intlPrefs = Services.prefs.getBranch("intl.");
+
+ let oldAcceptLangPref = intlPrefs.getCharPref("accept_languages");
+
+ let testData = [
+ ["en", "en"],
+ ["ast", "ast"],
+ ["fr-ca", "fr-CA"],
+ ["zh-yue", "zh-yue"],
+ ["az-latn", "az-Latn"],
+ ["sl-nedis", "sl-nedis"],
+ ["zh-hant-hk", "zh-Hant-HK"],
+ ["ZH-HANT-HK", "zh-Hant-HK"],
+ ["en-us-x-priv", "en-US-x-priv"],
+ ["en-us-x-twain", "en-US-x-twain"],
+ ["de, en-US, en", "de,en-US;q=0.7,en;q=0.3"],
+ ["de,en-us,en", "de,en-US;q=0.7,en;q=0.3"],
+ ["en-US, en", "en-US,en;q=0.5"],
+ ["EN-US;q=0.2, EN", "en-US,en;q=0.5"],
+ ["en ;q=0.8, de ", "en,de;q=0.5"],
+ [",en,", "en"],
+ ];
+
+ for (let i = 0; i < testData.length; i++) {
+ let acceptLangPref = testData[i][0];
+ let expectedHeader = testData[i][1];
+
+ intlPrefs.setCharPref("accept_languages", acceptLangPref);
+ let acceptLangHeader =
+ setupChannel(testpath).getRequestHeader("Accept-Language");
+ equal(acceptLangHeader, expectedHeader);
+ }
+
+ intlPrefs.setCharPref("accept_languages", oldAcceptLangPref);
+}
+
+function setupChannel(path) {
+ let uri = NetUtil.newURI("http://localhost:4444" + path);
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
diff --git a/netwerk/test/unit/test_header_Server_Timing.js b/netwerk/test/unit/test_header_Server_Timing.js
new file mode 100644
index 0000000000..0e65cf3ccf
--- /dev/null
+++ b/netwerk/test/unit/test_header_Server_Timing.js
@@ -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/. */
+
+//
+// HTTP Server-Timing header test
+//
+
+"use strict";
+
+function make_and_open_channel(url, callback) {
+ let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ chan.asyncOpen(new ChannelListener(callback, null, CL_ALLOW_UNKNOWN_CL));
+}
+
+var responseServerTiming = [
+ { metric: "metric", duration: "123.4", description: "description" },
+ { metric: "metric2", duration: "456.78", description: "description1" },
+];
+var trailerServerTiming = [
+ { metric: "metric3", duration: "789.11", description: "description2" },
+ { metric: "metric4", duration: "1112.13", description: "description3" },
+];
+
+function run_test() {
+ do_test_pending();
+
+ // Set up to allow the cert presented by the server
+ do_get_profile();
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ });
+
+ var serverPort = Services.env.get("MOZHTTP2_PORT");
+ make_and_open_channel(
+ "https://foo.example.com:" + serverPort + "/server-timing",
+ readServerContent
+ );
+}
+
+function checkServerTimingContent(headers) {
+ var expectedResult = responseServerTiming.concat(trailerServerTiming);
+ Assert.equal(headers.length, expectedResult.length);
+
+ for (var i = 0; i < expectedResult.length; i++) {
+ let header = headers.queryElementAt(i, Ci.nsIServerTiming);
+ Assert.equal(header.name, expectedResult[i].metric);
+ Assert.equal(header.description, expectedResult[i].description);
+ Assert.equal(header.duration, parseFloat(expectedResult[i].duration));
+ }
+}
+
+function readServerContent(request, buffer) {
+ let channel = request.QueryInterface(Ci.nsITimedChannel);
+ let headers = channel.serverTiming.QueryInterface(Ci.nsIArray);
+ checkServerTimingContent(headers);
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_headers.js b/netwerk/test/unit/test_headers.js
new file mode 100644
index 0000000000..e1439c6c43
--- /dev/null
+++ b/netwerk/test/unit/test_headers.js
@@ -0,0 +1,184 @@
+//
+// cleaner HTTP header test infrastructure
+//
+// tests bugs: 589292, [add more here: see hg log for definitive list]
+//
+// TO ADD NEW TESTS:
+// 1) Increment up 'lastTest' to new number (say, "99")
+// 2) Add new test 'handler99' and 'completeTest99' functions.
+// 3) If your test should fail the necko channel, set
+// test_flags[99] = CL_EXPECT_FAILURE.
+//
+// TO DEBUG JUST ONE TEST: temporarily change firstTest and lastTest to equal
+// the test # you're interested in.
+//
+// For tests that need duplicate copies of headers to be sent, see
+// test_duplicate_headers.js
+
+"use strict";
+
+var firstTest = 1; // set to test of interest when debugging
+var lastTest = 4; // set to test of interest when debugging
+////////////////////////////////////////////////////////////////////////////////
+
+// Note: sets Cc and Ci variables
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var nextTest = firstTest;
+var test_flags = [];
+var testPathBase = "/test_headers";
+
+function run_test() {
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(nextTest);
+}
+
+function runNextTest() {
+ if (nextTest == lastTest) {
+ endTests();
+ return;
+ }
+ nextTest++;
+ // Make sure test functions exist
+ if (globalThis["handler" + nextTest] == undefined) {
+ do_throw("handler" + nextTest + " undefined!");
+ }
+ if (globalThis["completeTest" + nextTest] == undefined) {
+ do_throw("completeTest" + nextTest + " undefined!");
+ }
+
+ run_test_number(nextTest);
+}
+
+function run_test_number(num) {
+ let testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, globalThis["handler" + num]);
+
+ var channel = setupChannel(testPath);
+ let flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen(
+ new ChannelListener(globalThis["completeTest" + num], channel, flags)
+ );
+}
+
+function setupChannel(url) {
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests() {
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: test Content-Disposition channel attributes
+// eslint-disable-next-line no-unused-vars
+function handler1(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Disposition", "attachment; filename=foo");
+ response.setHeader("Content-Type", "text/plain", false);
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest1(request, data, ctx) {
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ Assert.equal(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ Assert.equal(chan.contentDispositionFilename, "foo");
+ Assert.equal(chan.contentDispositionHeader, "attachment; filename=foo");
+ } catch (ex) {
+ do_throw("error parsing Content-Disposition: " + ex);
+ }
+ runNextTest();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: no filename
+// eslint-disable-next-line no-unused-vars
+function handler2(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Disposition", "attachment");
+ var body = "foo";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest2(request, data, ctx) {
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ Assert.equal(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ Assert.equal(chan.contentDispositionHeader, "attachment");
+ chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ info("correctly ate exception");
+ }
+ runNextTest();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: filename missing
+// eslint-disable-next-line no-unused-vars
+function handler3(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Disposition", "attachment; filename=");
+ var body = "foo";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest3(request, data, ctx) {
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ Assert.equal(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ Assert.equal(chan.contentDispositionHeader, "attachment; filename=");
+ chan.contentDispositionFilename; // should barf
+
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ info("correctly ate exception");
+ }
+ runNextTest();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: inline
+// eslint-disable-next-line no-unused-vars
+function handler4(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Disposition", "inline");
+ var body = "foo";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// eslint-disable-next-line no-unused-vars
+function completeTest4(request, data, ctx) {
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ Assert.equal(chan.contentDisposition, chan.DISPOSITION_INLINE);
+ Assert.equal(chan.contentDispositionHeader, "inline");
+
+ chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ info("correctly ate exception");
+ }
+ runNextTest();
+}
diff --git a/netwerk/test/unit/test_hostnameIsLocalIPAddress.js b/netwerk/test/unit/test_hostnameIsLocalIPAddress.js
new file mode 100644
index 0000000000..64d246c633
--- /dev/null
+++ b/netwerk/test/unit/test_hostnameIsLocalIPAddress.js
@@ -0,0 +1,37 @@
+"use strict";
+
+function run_test() {
+ let testURIs = [
+ ["http://example.com", false],
+ ["about:robots", false],
+ // 10/8 prefix (RFC 1918)
+ ["http://9.255.255.255", false],
+ ["http://10.0.0.0", true],
+ ["http://10.0.23.31", true],
+ ["http://10.255.255.255", true],
+ ["http://11.0.0.0", false],
+ // 169.254/16 prefix (Link Local)
+ ["http://169.253.255.255", false],
+ ["http://169.254.0.0", true],
+ ["http://169.254.42.91", true],
+ ["http://169.254.255.255", true],
+ ["http://169.255.0.0", false],
+ // 172.16/12 prefix (RFC 1918)
+ ["http://172.15.255.255", false],
+ ["http://172.16.0.0", true],
+ ["http://172.25.110.0", true],
+ ["http://172.31.255.255", true],
+ ["http://172.32.0.0", false],
+ // 192.168/16 prefix (RFC 1918)
+ ["http://192.167.255.255", false],
+ ["http://192.168.0.0", true],
+ ["http://192.168.127.10", true],
+ ["http://192.168.255.255", true],
+ ["http://192.169.0.0", false],
+ ];
+
+ for (let [uri, isLocal] of testURIs) {
+ let nsuri = Services.io.newURI(uri);
+ equal(isLocal, Services.io.hostnameIsLocalIPAddress(nsuri));
+ }
+}
diff --git a/netwerk/test/unit/test_hostnameIsSharedIPAddress.js b/netwerk/test/unit/test_hostnameIsSharedIPAddress.js
new file mode 100644
index 0000000000..c3eaeabc0e
--- /dev/null
+++ b/netwerk/test/unit/test_hostnameIsSharedIPAddress.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function run_test() {
+ let testURIs = [
+ // 100.64/10 prefix (RFC 6598)
+ ["http://100.63.255.254", false],
+ ["http://100.64.0.0", true],
+ ["http://100.91.63.42", true],
+ ["http://100.127.255.254", true],
+ ["http://100.128.0.0", false],
+ ];
+
+ for (let [uri, isShared] of testURIs) {
+ let nsuri = Services.io.newURI(uri);
+ equal(isShared, Services.io.hostnameIsSharedIPAddress(nsuri));
+ }
+}
diff --git a/netwerk/test/unit/test_hpke_config_manager.js b/netwerk/test/unit/test_hpke_config_manager.js
new file mode 100644
index 0000000000..66d06c3464
--- /dev/null
+++ b/netwerk/test/unit/test_hpke_config_manager.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let { HPKEConfigManager } = ChromeUtils.importESModule(
+ "resource://gre/modules/HPKEConfigManager.sys.mjs"
+);
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let gHttpServer;
+let gValidRequestCount = 0;
+let gFickleIsWorking = true;
+
+add_setup(async function () {
+ gHttpServer = new HttpServer();
+ let invalidHandler = (req, res) => {
+ res.setStatusLine(req.httpVersion, 500, "Oh no, it broke");
+ res.write("Uh oh, it broke.");
+ };
+ let validHandler = (req, res) => {
+ res.setHeader("Content-Type", "application/ohttp-keys");
+ res.write("1234");
+ gValidRequestCount++;
+ };
+
+ gHttpServer.registerPathHandler("/.wellknown/invalid", invalidHandler);
+ gHttpServer.registerPathHandler("/.wellknown/valid", validHandler);
+
+ gHttpServer.registerPathHandler("/.wellknown/fickle", (req, res) => {
+ if (gFickleIsWorking) {
+ return validHandler(req, res);
+ }
+ return invalidHandler(req, res);
+ });
+
+ gHttpServer.start(-1);
+});
+
+function getLocalURL(path) {
+ return `http://localhost:${gHttpServer.identity.primaryPort}/.wellknown/${path}`;
+}
+
+add_task(async function test_broken_url_returns_null() {
+ Assert.equal(await HPKEConfigManager.get(getLocalURL("invalid")), null);
+});
+
+add_task(async function test_working_url_returns_data() {
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("valid")),
+ new TextEncoder().encode("1234")
+ );
+});
+
+add_task(async function test_we_only_request_once() {
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("valid")),
+ new TextEncoder().encode("1234")
+ );
+ let oldRequestCount = gValidRequestCount;
+
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("valid")),
+ new TextEncoder().encode("1234")
+ );
+ Assert.equal(
+ oldRequestCount,
+ gValidRequestCount,
+ "Shouldn't have made another request."
+ );
+});
+
+add_task(async function test_maxAge_forces_refresh() {
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("valid")),
+ new TextEncoder().encode("1234")
+ );
+ let oldRequestCount = gValidRequestCount;
+
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("valid"), { maxAge: 0 }),
+ new TextEncoder().encode("1234")
+ );
+ Assert.equal(
+ oldRequestCount + 1,
+ gValidRequestCount,
+ "Should have made another request due to maxAge."
+ );
+});
+
+add_task(async function test_maxAge_handling_of_invalid_requests() {
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("fickle")),
+ new TextEncoder().encode("1234")
+ );
+
+ gFickleIsWorking = false;
+
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("fickle"), { maxAge: 0 }),
+ null
+ );
+
+ Assert.deepEqual(
+ await HPKEConfigManager.get(getLocalURL("fickle")),
+ new TextEncoder().encode("1234"),
+ "Should still have the cached config if no max age is passed."
+ );
+});
diff --git a/netwerk/test/unit/test_http1-proxy.js b/netwerk/test/unit/test_http1-proxy.js
new file mode 100644
index 0000000000..7daa37e368
--- /dev/null
+++ b/netwerk/test/unit/test_http1-proxy.js
@@ -0,0 +1,231 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test checks following expectations when using HTTP/1 proxy:
+ *
+ * - check we are seeing expected nsresult error codes on channels
+ * (nsIChannel.status) corresponding to different proxy status code
+ * responses (502, 504, 407, ...)
+ * - check we don't try to ask for credentials or otherwise authenticate to
+ * the proxy when 407 is returned and there is no Proxy-Authenticate
+ * response header sent
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+let server_port;
+let http_server;
+
+class ProxyFilter {
+ constructor(type, host, port, flags) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
+ }
+ applyFilter(uri, pi, cb) {
+ if (uri.spec.match(/(\/proxy-session-counter)/)) {
+ cb.onProxyFilterResult(pi);
+ return;
+ }
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ "",
+ "",
+ this._flags,
+ 1000,
+ null
+ )
+ );
+ }
+}
+
+class UnxpectedAuthPrompt2 {
+ constructor(signal) {
+ this.signal = signal;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
+ }
+ asyncPromptAuth() {
+ this.signal.triggered = true;
+ throw Components.Exception("", Cr.ERROR_UNEXPECTED);
+ }
+}
+
+class AuthRequestor {
+ constructor(prompt) {
+ this.prompt = prompt;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]);
+ }
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ return this.prompt();
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ // Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+}
+
+function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) {
+ return new Promise(resolve => {
+ channel.asyncOpen(
+ new ChannelListener(
+ (request, data) => {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ const status = request.status;
+ const http_code = status ? undefined : request.responseStatus;
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ const proxy_connect_response_code =
+ request.httpProxyConnectResponseCode;
+ resolve({ status, http_code, data, proxy_connect_response_code });
+ },
+ null,
+ flags
+ )
+ );
+ });
+}
+
+function connect_handler(request, response) {
+ Assert.equal(request.method, "CONNECT");
+
+ switch (request.host) {
+ case "404.example.com":
+ response.setStatusLine(request.httpVersion, 404, "Not found");
+ break;
+ case "407.example.com":
+ response.setStatusLine(request.httpVersion, 407, "Authenticate");
+ // And deliberately no Proxy-Authenticate header
+ break;
+ case "429.example.com":
+ response.setStatusLine(request.httpVersion, 429, "Too Many Requests");
+ break;
+ case "502.example.com":
+ response.setStatusLine(request.httpVersion, 502, "Bad Gateway");
+ break;
+ case "504.example.com":
+ response.setStatusLine(request.httpVersion, 504, "Gateway timeout");
+ break;
+ default:
+ response.setStatusLine(request.httpVersion, 500, "I am dumb");
+ }
+}
+
+add_task(async function setup() {
+ http_server = new HttpServer();
+ http_server.identity.add("https", "404.example.com", 443);
+ http_server.identity.add("https", "407.example.com", 443);
+ http_server.identity.add("https", "429.example.com", 443);
+ http_server.identity.add("https", "502.example.com", 443);
+ http_server.identity.add("https", "504.example.com", 443);
+ http_server.registerPathHandler("CONNECT", connect_handler);
+ http_server.start(-1);
+ server_port = http_server.identity.primaryPort;
+
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ pps.registerFilter(new ProxyFilter("http", "localhost", server_port, 0), 10);
+});
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+});
+
+/**
+ * Test series beginning.
+ */
+
+// The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
+// code from the channel and not try to ask for any credentials.
+add_task(async function proxy_auth_failure() {
+ const chan = make_channel(`https://407.example.com/`);
+ const auth_prompt = { triggered: false };
+ chan.notificationCallbacks = new AuthRequestor(
+ () => new UnxpectedAuthPrompt2(auth_prompt)
+ );
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ chan,
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
+ Assert.equal(proxy_connect_response_code, 407);
+ Assert.equal(http_code, undefined);
+ Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
+});
+
+// 502 Bad gateway code returned by the proxy.
+add_task(async function proxy_bad_gateway_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(proxy_connect_response_code, 502);
+ Assert.equal(http_code, undefined);
+});
+
+// 504 Gateway timeout code returned by the proxy.
+add_task(async function proxy_gateway_timeout_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://504.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
+ Assert.equal(proxy_connect_response_code, 504);
+ Assert.equal(http_code, undefined);
+});
+
+// 404 Not Found means the proxy could not resolve the host.
+add_task(async function proxy_host_not_found_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://404.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
+ Assert.equal(proxy_connect_response_code, 404);
+ Assert.equal(http_code, undefined);
+});
+
+// 429 Too Many Requests means we sent too many requests.
+add_task(async function proxy_too_many_requests_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://429.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
+ Assert.equal(proxy_connect_response_code, 429);
+ Assert.equal(http_code, undefined);
+});
+
+add_task(async function shutdown() {
+ await new Promise(resolve => {
+ http_server.stop(resolve);
+ });
+});
diff --git a/netwerk/test/unit/test_http2-proxy-failing.js b/netwerk/test/unit/test_http2-proxy-failing.js
new file mode 100644
index 0000000000..8530f55373
--- /dev/null
+++ b/netwerk/test/unit/test_http2-proxy-failing.js
@@ -0,0 +1,174 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Test stream failure on the session to the proxy:
+ * - Test the case the error closes the affected stream only
+ * - Test the case the error closes the whole session and cancels existing
+ * streams.
+ */
+
+/* eslint-env node */
+
+"use strict";
+
+const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+let filter;
+
+class ProxyFilter {
+ constructor(type, host, port, flags) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
+ }
+ applyFilter(uri, pi, cb) {
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ null,
+ null,
+ this._flags,
+ 1000,
+ null
+ )
+ );
+ }
+}
+
+function createPrincipal(url) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ return ssm.createContentPrincipal(Services.io.newURI(url), {});
+ } catch (e) {
+ return null;
+ }
+}
+
+function make_channel(url) {
+ return Services.io.newChannelFromURIWithProxyFlags(
+ Services.io.newURI(url),
+ null,
+ 16,
+ null,
+ createPrincipal(url),
+ createPrincipal(url),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+}
+
+function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL, delay = 0) {
+ return new Promise(resolve => {
+ var listener = new ChannelListener(
+ (request, data) => {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ const status = request.status;
+ const http_code = status ? undefined : request.responseStatus;
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ const proxy_connect_response_code =
+ request.httpProxyConnectResponseCode;
+ resolve({ status, http_code, data, proxy_connect_response_code });
+ },
+ null,
+ flags
+ );
+ if (delay > 0) {
+ do_timeout(delay, function () {
+ channel.asyncOpen(listener);
+ });
+ } else {
+ channel.asyncOpen(listener);
+ }
+ });
+}
+
+add_task(async function setup() {
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let proxy_port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(proxy_port, null);
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ filter = new ProxyFilter("https", "localhost", proxy_port, 16);
+ pps.registerFilter(filter, 10);
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+
+ pps.unregisterFilter(filter);
+});
+
+add_task(
+ async function proxy_server_stream_soft_failure_multiple_streams_not_affected() {
+ let should_succeed = get_response(make_channel(`http://750.example.com`));
+ const failed = await get_response(
+ make_channel(`http://illegalhpacksoft.example.com`),
+ CL_EXPECT_FAILURE,
+ 20
+ );
+
+ const succeeded = await should_succeed;
+
+ Assert.equal(failed.status, Cr.NS_ERROR_ILLEGAL_VALUE);
+ Assert.equal(failed.proxy_connect_response_code, 0);
+ Assert.equal(failed.http_code, undefined);
+ Assert.equal(succeeded.status, Cr.NS_OK);
+ Assert.equal(succeeded.proxy_connect_response_code, 200);
+ Assert.equal(succeeded.http_code, 200);
+ }
+);
+
+add_task(
+ async function proxy_server_stream_hard_failure_multiple_streams_affected() {
+ let should_failed = get_response(
+ make_channel(`http://750.example.com`),
+ CL_EXPECT_FAILURE
+ );
+ const failed1 = await get_response(
+ make_channel(`http://illegalhpackhard.example.com`),
+ CL_EXPECT_FAILURE
+ );
+
+ const failed2 = await should_failed;
+
+ Assert.equal(failed1.status, 0x804b0053);
+ Assert.equal(failed1.proxy_connect_response_code, 0);
+ Assert.equal(failed1.http_code, undefined);
+ Assert.equal(failed2.status, 0x804b0053);
+ Assert.equal(failed2.proxy_connect_response_code, 0);
+ Assert.equal(failed2.http_code, undefined);
+ }
+);
+
+add_task(async function test_http2_h11required_stream() {
+ let should_failed = await get_response(
+ make_channel(`http://h11required.com`),
+ CL_EXPECT_FAILURE
+ );
+
+ // See HTTP/1.1 connect handler in moz-http2.js. The handler returns
+ // "404 Not Found", so the expected error code is NS_ERROR_UNKNOWN_HOST.
+ Assert.equal(should_failed.status, Cr.NS_ERROR_UNKNOWN_HOST);
+ Assert.equal(should_failed.proxy_connect_response_code, 404);
+ Assert.equal(should_failed.http_code, undefined);
+});
diff --git a/netwerk/test/unit/test_http2-proxy.js b/netwerk/test/unit/test_http2-proxy.js
new file mode 100644
index 0000000000..d8aa0019fe
--- /dev/null
+++ b/netwerk/test/unit/test_http2-proxy.js
@@ -0,0 +1,862 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test checks following expectations when using HTTP/2 proxy:
+ *
+ * - when we request https access, we don't create different sessions for
+ * different origins, only new tunnels inside a single session
+ * - when the isolation key (`proxy_isolation`) is changed, new single session
+ * is created for new requests to same origins as before
+ * - error code returned from the tunnel (a proxy error - not end-server
+ * error!) doesn't kill the existing session
+ * - check we are seeing expected nsresult error codes on channels
+ * (nsIChannel.status) corresponding to different proxy status code
+ * responses (502, 504, 407, ...)
+ * - check we don't try to ask for credentials or otherwise authenticate to
+ * the proxy when 407 is returned and there is no Proxy-Authenticate
+ * response header sent
+ * - a stream reset for a connect stream to the proxy does not cause session to
+ * be closed and the request through the proxy will failed.
+ * - a "soft" stream error on a connection to the origin server will close the
+ * stream, but it will not close niether the HTTP/2 session to the proxy nor
+ * to the origin server.
+ * - a "hard" stream error on a connection to the origin server will close the
+ * HTTP/2 session to the origin server, but it will not close the HTTP/2
+ * session to the proxy.
+ */
+
+/* eslint-env node */
+/* global serverPort */
+
+"use strict";
+
+const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+let proxy_port;
+let filter;
+let proxy;
+
+// See moz-http2
+const proxy_auth = "authorization-token";
+let proxy_isolation;
+
+class ProxyFilter {
+ constructor(type, host, port, flags) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
+ }
+ applyFilter(uri, pi, cb) {
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ proxy_auth,
+ proxy_isolation,
+ this._flags,
+ 1000,
+ null
+ )
+ );
+ }
+}
+
+class UnxpectedAuthPrompt2 {
+ constructor(signal) {
+ this.signal = signal;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
+ }
+ asyncPromptAuth() {
+ this.signal.triggered = true;
+ throw Components.Exception("", Cr.ERROR_UNEXPECTED);
+ }
+}
+
+class SimpleAuthPrompt2 {
+ constructor(signal) {
+ this.signal = signal;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
+ }
+ asyncPromptAuth(channel, callback, context, encryptionLevel, authInfo) {
+ this.signal.triggered = true;
+ executeSoon(function () {
+ authInfo.username = "user";
+ authInfo.password = "pass";
+ callback.onAuthAvailable(context, authInfo);
+ });
+ }
+}
+
+class AuthRequestor {
+ constructor(prompt) {
+ this.prompt = prompt;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]);
+ }
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ return this.prompt();
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+}
+
+function createPrincipal(url) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ return ssm.createContentPrincipal(Services.io.newURI(url), {});
+ } catch (e) {
+ return null;
+ }
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: createPrincipal(url),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ // Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+}
+
+function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL, delay = 0) {
+ return new Promise(resolve => {
+ var listener = new ChannelListener(
+ (request, data) => {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ const status = request.status;
+ const http_code = status ? undefined : request.responseStatus;
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ const proxy_connect_response_code =
+ request.httpProxyConnectResponseCode;
+ resolve({ status, http_code, data, proxy_connect_response_code });
+ },
+ null,
+ flags
+ );
+ if (delay > 0) {
+ do_timeout(delay, function () {
+ channel.asyncOpen(listener);
+ });
+ } else {
+ channel.asyncOpen(listener);
+ }
+ });
+}
+
+let initial_session_count = 0;
+
+class http2ProxyCode {
+ static listen(server, envport) {
+ if (!server) {
+ return Promise.resolve(0);
+ }
+
+ let portSelection = 0;
+ if (envport !== undefined) {
+ try {
+ portSelection = parseInt(envport, 10);
+ } catch (e) {
+ portSelection = -1;
+ }
+ }
+ return new Promise(resolve => {
+ server.listen(portSelection, "0.0.0.0", 2000, () => {
+ resolve(server.address().port);
+ });
+ });
+ }
+
+ static startNewProxy() {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+ };
+ const http2 = require("http2");
+ global.proxy = http2.createSecureServer(options);
+ this.setupProxy();
+ return http2ProxyCode.listen(proxy).then(port => {
+ return { port, success: true };
+ });
+ }
+
+ static closeProxy() {
+ proxy.closeSockets();
+ return new Promise(resolve => {
+ proxy.close(resolve);
+ });
+ }
+
+ static proxySessionCount() {
+ if (!proxy) {
+ return 0;
+ }
+ return proxy.proxy_session_count;
+ }
+
+ static proxySessionToOriginServersCount() {
+ if (!proxy) {
+ return 0;
+ }
+ return proxy.sessionToOriginServersCount;
+ }
+
+ static setupProxy() {
+ if (!proxy) {
+ throw new Error("proxy is null");
+ }
+ proxy.proxy_session_count = 0;
+ proxy.sessionToOriginServersCount = 0;
+ proxy.on("session", () => {
+ ++proxy.proxy_session_count;
+ });
+
+ // We need to track active connections so we can forcefully close keep-alive
+ // connections when shutting down the proxy.
+ proxy.socketIndex = 0;
+ proxy.socketMap = {};
+ proxy.on("connection", function (socket) {
+ let index = proxy.socketIndex++;
+ proxy.socketMap[index] = socket;
+ socket.on("close", function () {
+ delete proxy.socketMap[index];
+ });
+ });
+ proxy.closeSockets = function () {
+ for (let i in proxy.socketMap) {
+ proxy.socketMap[i].destroy();
+ }
+ };
+
+ proxy.on("stream", (stream, headers) => {
+ if (headers[":method"] !== "CONNECT") {
+ // Only accept CONNECT requests
+ stream.respond({ ":status": 405 });
+ stream.end();
+ return;
+ }
+
+ const target = headers[":authority"];
+
+ const authorization_token = headers["proxy-authorization"];
+ if (target == "407.example.com:443") {
+ stream.respond({ ":status": 407 });
+ // Deliberately send no Proxy-Authenticate header
+ stream.end();
+ return;
+ }
+ if (target == "407.basic.example.com:443") {
+ // we want to return a different response than 407 to not re-request
+ // credentials (and thus loop) but also not 200 to not let the channel
+ // attempt to waste time connecting a non-existing https server - hence
+ // 418 I'm a teapot :)
+ if ("Basic dXNlcjpwYXNz" == authorization_token) {
+ stream.respond({ ":status": 418 });
+ stream.end();
+ return;
+ }
+ stream.respond({
+ ":status": 407,
+ "proxy-authenticate": "Basic realm='foo'",
+ });
+ stream.end();
+ return;
+ }
+ if (target == "404.example.com:443") {
+ // 404 Not Found, a response code that a proxy should return when the host can't be found
+ stream.respond({ ":status": 404 });
+ stream.end();
+ return;
+ }
+ if (target == "429.example.com:443") {
+ // 429 Too Many Requests, a response code that a proxy should return when receiving too many requests
+ stream.respond({ ":status": 429 });
+ stream.end();
+ return;
+ }
+ if (target == "502.example.com:443") {
+ // 502 Bad Gateway, a response code mostly resembling immediate connection error
+ stream.respond({ ":status": 502 });
+ stream.end();
+ return;
+ }
+ if (target == "504.example.com:443") {
+ // 504 Gateway Timeout, did not receive a timely response from an upstream server
+ stream.respond({ ":status": 504 });
+ stream.end();
+ return;
+ }
+ if (target == "reset.example.com:443") {
+ // always reset the stream.
+ stream.close(0x0);
+ return;
+ }
+
+ ++proxy.sessionToOriginServersCount;
+ const net = require("net");
+ const socket = net.connect(serverPort, "127.0.0.1", () => {
+ try {
+ stream.respond({ ":status": 200 });
+ socket.pipe(stream);
+ stream.pipe(socket);
+ } catch (exception) {
+ console.log(exception);
+ stream.close();
+ }
+ });
+ socket.on("error", error => {
+ throw new Error(
+ `Unexpected error when conneting the HTTP/2 server from the HTTP/2 proxy during CONNECT handling: '${error}'`
+ );
+ });
+ });
+ }
+}
+
+async function proxy_session_counter() {
+ let data = await NodeServer.execute(
+ processId,
+ `http2ProxyCode.proxySessionCount()`
+ );
+ return parseInt(data) - initial_session_count;
+}
+async function proxy_session_to_origin_server_counter() {
+ let data = await NodeServer.execute(
+ processId,
+ `http2ProxyCode.proxySessionToOriginServersCount()`
+ );
+ return parseInt(data) - initial_session_count;
+}
+let processId;
+add_task(async function setup() {
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let server_port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(server_port, null);
+ processId = await NodeServer.fork();
+ await NodeServer.execute(processId, `serverPort = ${server_port}`);
+ await NodeServer.execute(processId, http2ProxyCode);
+ let newProxy = await NodeServer.execute(
+ processId,
+ `http2ProxyCode.startNewProxy()`
+ );
+ proxy_port = newProxy.port;
+ Assert.notEqual(proxy_port, null);
+
+ Services.prefs.setStringPref(
+ "services.settings.server",
+ `data:,#remote-settings-dummy/v1`
+ );
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+
+ // Even with network state isolation active, we don't end up using the
+ // partitioned principal.
+ Services.prefs.setBoolPref("privacy.partition.network_state", true);
+
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ filter = new ProxyFilter("https", "localhost", proxy_port, 0);
+ pps.registerFilter(filter, 10);
+
+ initial_session_count = await proxy_session_counter();
+ info(`Initial proxy session count = ${initial_session_count}`);
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("services.settings.server");
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+
+ pps.unregisterFilter(filter);
+
+ await NodeServer.execute(processId, `http2ProxyCode.closeProxy()`);
+ await NodeServer.kill(processId);
+});
+
+/**
+ * Test series beginning.
+ */
+
+// Check we reach the h2 end server and keep only one session with the proxy for two different origin.
+// Here we use the first isolation token.
+add_task(async function proxy_success_one_session() {
+ proxy_isolation = "TOKEN1";
+
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.ok(alt1.data.match("You Win!"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "Created just one session with the proxy"
+ );
+});
+
+// The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
+// code from the channel and not try to ask for any credentials.
+add_task(async function proxy_auth_failure() {
+ const chan = make_channel(`https://407.example.com/`);
+ const auth_prompt = { triggered: false };
+ chan.notificationCallbacks = new AuthRequestor(
+ () => new UnxpectedAuthPrompt2(auth_prompt)
+ );
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ chan,
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
+ Assert.equal(proxy_connect_response_code, 407);
+ Assert.equal(http_code, undefined);
+ Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 407"
+ );
+});
+
+// The proxy responses with 407 with Proxy-Authenticate header presence. Make
+// sure that we prompt the auth prompt to ask for credentials.
+add_task(async function proxy_auth_basic() {
+ const chan = make_channel(`https://407.basic.example.com/`);
+ const auth_prompt = { triggered: false };
+ chan.notificationCallbacks = new AuthRequestor(
+ () => new SimpleAuthPrompt2(auth_prompt)
+ );
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ chan,
+ CL_EXPECT_FAILURE
+ );
+
+ // 418 indicates we pass the basic authentication.
+ Assert.equal(status, Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);
+ Assert.equal(proxy_connect_response_code, 418);
+ Assert.equal(http_code, undefined);
+ Assert.equal(auth_prompt.triggered, true, "Auth prompt should trigger");
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 407"
+ );
+});
+
+// 502 Bad gateway code returned by the proxy, still one session only, proper different code
+// from the channel.
+add_task(async function proxy_bad_gateway_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(proxy_connect_response_code, 502);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 502 after 407"
+ );
+});
+
+// Second 502 Bad gateway code returned by the proxy, still one session only with the proxy.
+add_task(async function proxy_bad_gateway_failure_two() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(proxy_connect_response_code, 502);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by second 502"
+ );
+});
+
+// 504 Gateway timeout code returned by the proxy, still one session only, proper different code
+// from the channel.
+add_task(async function proxy_gateway_timeout_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://504.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
+ Assert.equal(proxy_connect_response_code, 504);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 504 after 502"
+ );
+});
+
+// 404 Not Found means the proxy could not resolve the host. As for other error responses
+// we still expect this not to close the existing session.
+add_task(async function proxy_host_not_found_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://404.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
+ Assert.equal(proxy_connect_response_code, 404);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 404 after 504"
+ );
+});
+
+add_task(async function proxy_too_many_requests_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://429.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
+ Assert.equal(proxy_connect_response_code, 429);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 429 after 504"
+ );
+});
+
+add_task(async function proxy_stream_reset_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://reset.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_NET_INTERRUPT);
+ Assert.equal(proxy_connect_response_code, 0);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 429 after 504"
+ );
+});
+
+// The soft errors are not closing the session.
+add_task(async function origin_server_stream_soft_failure() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://foo.example.com/illegalhpacksoft`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_ILLEGAL_VALUE);
+ Assert.equal(proxy_connect_response_code, 200);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No session to the proxy closed by soft stream errors"
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No session to the origin server closed by soft stream errors"
+ );
+});
+
+// The soft errors are not closing the session.
+add_task(
+ async function origin_server_stream_soft_failure_multiple_streams_not_affected() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+
+ let should_succeed = get_response(
+ make_channel(`https://foo.example.com/750ms`)
+ );
+
+ const failed = await get_response(
+ make_channel(`https://foo.example.com/illegalhpacksoft`),
+ CL_EXPECT_FAILURE,
+ 20
+ );
+
+ const succeeded = await should_succeed;
+
+ Assert.equal(failed.status, Cr.NS_ERROR_ILLEGAL_VALUE);
+ Assert.equal(failed.proxy_connect_response_code, 200);
+ Assert.equal(failed.http_code, undefined);
+ Assert.equal(succeeded.status, Cr.NS_OK);
+ Assert.equal(succeeded.proxy_connect_response_code, 200);
+ Assert.equal(succeeded.http_code, 200);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No session to the proxy closed by soft stream errors"
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No session to the origin server closed by soft stream errors"
+ );
+ }
+);
+
+// Make sure that the above error codes don't kill the session to the proxy.
+add_task(async function proxy_success_still_one_session() {
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.http_code, 200);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session to the proxy created after stream error codes"
+ );
+});
+
+// Have a new isolation key, this means we are expected to create a new, and again one only,
+// session with the proxy to reach the end server.
+add_task(async function proxy_success_isolated_session() {
+ Assert.notEqual(proxy_isolation, "TOKEN2");
+ proxy_isolation = "TOKEN2";
+
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+ const lh = await get_response(
+ make_channel(`https://localhost/random-request-3`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.ok(alt1.data.match("You Win!"));
+ Assert.equal(lh.status, Cr.NS_OK);
+ Assert.equal(lh.proxy_connect_response_code, 200);
+ Assert.equal(lh.http_code, 200);
+ Assert.ok(lh.data.match("random-request-3"));
+ Assert.ok(lh.data.match("You Win!"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "Just one new session seen after changing the isolation key"
+ );
+});
+
+// Check that error codes are still handled the same way with new isolation, just in case.
+add_task(async function proxy_bad_gateway_failure_isolated() {
+ const failure1 = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+ const failure2 = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(failure1.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(failure1.proxy_connect_response_code, 502);
+ Assert.equal(failure1.http_code, undefined);
+ Assert.equal(failure2.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(failure2.proxy_connect_response_code, 502);
+ Assert.equal(failure2.http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session created by 502"
+ );
+});
+
+add_task(async function proxy_success_check_number_of_session() {
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+ const lh = await get_response(
+ make_channel(`https://localhost/random-request-3`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.ok(alt1.data.match("You Win!"));
+ Assert.equal(lh.status, Cr.NS_OK);
+ Assert.equal(lh.proxy_connect_response_code, 200);
+ Assert.equal(lh.http_code, 200);
+ Assert.ok(lh.data.match("random-request-3"));
+ Assert.ok(lh.data.match("You Win!"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "The number of sessions has not changed"
+ );
+});
+
+// The hard errors are closing the session.
+add_task(async function origin_server_stream_hard_failure() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://foo.example.com/illegalhpackhard`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, 0x804b0053);
+ Assert.equal(proxy_connect_response_code, 200);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy."
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No new session to the origin server yet."
+ );
+
+ // Check the a new session ill be opened.
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy is created after a hard stream failure on the session to the origin server."
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server + 1,
+ "A new session to the origin server after a hard stream error"
+ );
+});
+
+// The hard errors are closing the session.
+add_task(
+ async function origin_server_stream_hard_failure_multiple_streams_affected() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+ let should_fail = get_response(
+ make_channel(`https://foo.example.com/750msNoData`),
+ CL_EXPECT_FAILURE
+ );
+ const failed1 = await get_response(
+ make_channel(`https://foo.example.com/illegalhpackhard`),
+ CL_EXPECT_FAILURE,
+ 10
+ );
+
+ const failed2 = await should_fail;
+
+ Assert.equal(failed1.status, 0x804b0053);
+ Assert.equal(failed1.proxy_connect_response_code, 200);
+ Assert.equal(failed1.http_code, undefined);
+ Assert.equal(failed2.status, 0x804b0053);
+ Assert.equal(failed2.proxy_connect_response_code, 200);
+ Assert.equal(failed2.http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy"
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No session to the origin server yet."
+ );
+ // Check the a new session ill be opened.
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy is created after a hard stream failure on the session to the origin server."
+ );
+
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server + 1,
+ "A new session to the origin server after a hard stream error"
+ );
+ }
+);
diff --git a/netwerk/test/unit/test_http2.js b/netwerk/test/unit/test_http2.js
new file mode 100644
index 0000000000..0b80e99dcc
--- /dev/null
+++ b/netwerk/test/unit/test_http2.js
@@ -0,0 +1,481 @@
+/* import-globals-from http2_test_common.js */
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var concurrent_channels = [];
+var httpserv = null;
+var httpserv2 = null;
+
+var loadGroup;
+var serverPort;
+
+function altsvcHttp1Server(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Alt-Svc", 'h2=":' + serverPort + '"', false);
+ var body = "this is where a cool kid would write something neat.\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+
+ var body = '["http://foo.example.com:' + httpserv.identity.primaryPort + '"]';
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function altsvcHttp1Server2(metadata, response) {
+ // this server should never be used thanks to an alt svc frame from the
+ // h2 server.. but in case of some async lag in setting the alt svc route
+ // up we have it.
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ var body = "hanging.\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK2(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+
+ var body =
+ '["http://foo.example.com:' + httpserv2.identity.primaryPort + '"]';
+ response.bodyOutputStream.write(body, body.length);
+}
+
+add_setup(async function setup() {
+ serverPort = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(serverPort, null);
+ dump("using port " + serverPort + "\n");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. Some older tests in
+ // this suite use localhost with a TOFU exception, but new ones should use
+ // foo.example.com
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+ Services.prefs.setBoolPref("network.http.http2.allow-push", true);
+ Services.prefs.setBoolPref("network.http.altsvc.enabled", true);
+ Services.prefs.setBoolPref("network.http.altsvc.oe", true);
+ Services.prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, bar.example.com"
+ );
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
+ Ci.nsILoadGroup
+ );
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/altsvc1", altsvcHttp1Server);
+ httpserv.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
+ httpserv.start(-1);
+ httpserv.identity.setPrimary(
+ "http",
+ "foo.example.com",
+ httpserv.identity.primaryPort
+ );
+
+ httpserv2 = new HttpServer();
+ httpserv2.registerPathHandler("/altsvc2", altsvcHttp1Server2);
+ httpserv2.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK2);
+ httpserv2.start(-1);
+ httpserv2.identity.setPrimary(
+ "http",
+ "foo.example.com",
+ httpserv2.identity.primaryPort
+ );
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ Services.prefs.clearUserPref("network.http.http2.allow-push");
+ Services.prefs.clearUserPref("network.http.altsvc.enabled");
+ Services.prefs.clearUserPref("network.http.altsvc.oe");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref(
+ "network.cookieJarSettings.unblocked_for_testing"
+ );
+ await httpserv.stop();
+ await httpserv2.stop();
+});
+
+// hack - the header test resets the multiplex object on the server,
+// so make sure header is always run before the multiplex test.
+//
+// make sure post_big runs first to test race condition in restarting
+// a stalled stream when a SETTINGS frame arrives
+add_task(async function do_test_http2_post_big() {
+ const { httpProxyConnectResponseCode } = await test_http2_post_big(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_basic() {
+ const { httpProxyConnectResponseCode } = await test_http2_basic(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_concurrent() {
+ const { httpProxyConnectResponseCode } = await test_http2_concurrent(
+ concurrent_channels,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_concurrent_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_concurrent_post(
+ concurrent_channels,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_basic_unblocked_dep() {
+ const { httpProxyConnectResponseCode } = await test_http2_basic_unblocked_dep(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_nospdy() {
+ const { httpProxyConnectResponseCode } = await test_http2_nospdy(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push1() {
+ const { httpProxyConnectResponseCode } = await test_http2_push1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push2() {
+ const { httpProxyConnectResponseCode } = await test_http2_push2(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push3() {
+ const { httpProxyConnectResponseCode } = await test_http2_push3(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push4() {
+ const { httpProxyConnectResponseCode } = await test_http2_push4(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push5() {
+ const { httpProxyConnectResponseCode } = await test_http2_push5(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push6() {
+ const { httpProxyConnectResponseCode } = await test_http2_push6(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_altsvc() {
+ const { httpProxyConnectResponseCode } = await test_http2_altsvc(
+ httpserv.identity.primaryPort,
+ httpserv2.identity.primaryPort,
+ false
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_doubleheader() {
+ const { httpProxyConnectResponseCode } = await test_http2_doubleheader(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_xhr() {
+ await test_http2_xhr(serverPort);
+});
+
+add_task(async function do_test_http2_header() {
+ const { httpProxyConnectResponseCode } = await test_http2_header(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_invalid_response_header_name_spaces() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(serverPort, "name_spaces");
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(
+ async function do_test_http2_invalid_response_header_value_line_feed() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(serverPort, "value_line_feed");
+ Assert.equal(httpProxyConnectResponseCode, -1);
+ }
+);
+
+add_task(
+ async function do_test_http2_invalid_response_header_value_carriage_return() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(
+ serverPort,
+ "value_carriage_return"
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+ }
+);
+
+add_task(async function do_test_http2_invalid_response_header_value_null() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(serverPort, "value_null");
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_cookie_crumbling() {
+ const { httpProxyConnectResponseCode } = await test_http2_cookie_crumbling(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_multiplex() {
+ var values = await test_http2_multiplex(serverPort);
+ Assert.equal(values[0].httpProxyConnectResponseCode, -1);
+ Assert.equal(values[1].httpProxyConnectResponseCode, -1);
+ Assert.notEqual(values[0].streamID, values[1].streamID);
+});
+
+add_task(async function do_test_http2_big() {
+ const { httpProxyConnectResponseCode } = await test_http2_big(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_huge_suspended() {
+ const { httpProxyConnectResponseCode } = await test_http2_huge_suspended(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_post(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_empty_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_empty_post(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_patch() {
+ const { httpProxyConnectResponseCode } = await test_http2_patch(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_pushapi_1() {
+ const { httpProxyConnectResponseCode } = await test_http2_pushapi_1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_continuations() {
+ const { httpProxyConnectResponseCode } = await test_http2_continuations(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_blocking_download() {
+ const { httpProxyConnectResponseCode } = await test_http2_blocking_download(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_illegalhpacksoft() {
+ const { httpProxyConnectResponseCode } = await test_http2_illegalhpacksoft(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_illegalhpackhard() {
+ const { httpProxyConnectResponseCode } = await test_http2_illegalhpackhard(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_folded_header() {
+ const { httpProxyConnectResponseCode } = await test_http2_folded_header(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_empty_data() {
+ const { httpProxyConnectResponseCode } = await test_http2_empty_data(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_status_phrase() {
+ const { httpProxyConnectResponseCode } = await test_http2_status_phrase(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_doublepush() {
+ const { httpProxyConnectResponseCode } = await test_http2_doublepush(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_disk_cache_push() {
+ const { httpProxyConnectResponseCode } = await test_http2_disk_cache_push(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_h11required_stream() {
+ // Add new tests above here - best to add new tests before h1
+ // streams get too involved
+ // These next two must always come in this order
+ const { httpProxyConnectResponseCode } = await test_http2_h11required_stream(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_h11required_session() {
+ const { httpProxyConnectResponseCode } = await test_http2_h11required_session(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_retry_rst() {
+ const { httpProxyConnectResponseCode } = await test_http2_retry_rst(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_wrongsuite_tls12() {
+ const { httpProxyConnectResponseCode } = await test_http2_wrongsuite_tls12(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_wrongsuite_tls13() {
+ const { httpProxyConnectResponseCode } = await test_http2_wrongsuite_tls13(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push_firstparty1() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_firstparty1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push_firstparty2() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_firstparty2(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push_firstparty3() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_firstparty3(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push_userContext1() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_userContext1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push_userContext2() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_userContext2(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
+
+add_task(async function do_test_http2_push_userContext3() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_userContext3(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, -1);
+});
diff --git a/netwerk/test/unit/test_http2_with_proxy.js b/netwerk/test/unit/test_http2_with_proxy.js
new file mode 100644
index 0000000000..858a0da570
--- /dev/null
+++ b/netwerk/test/unit/test_http2_with_proxy.js
@@ -0,0 +1,425 @@
+// test HTTP/2 with a HTTP/2 prooxy
+
+"use strict";
+
+/* import-globals-from http2_test_common.js */
+/* import-globals-from head_servers.js */
+
+var concurrent_channels = [];
+
+var loadGroup;
+var serverPort;
+var proxy;
+
+add_setup(async function setup() {
+ serverPort = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(serverPort, null);
+ dump("using port " + serverPort + "\n");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. Some older tests in
+ // this suite use localhost with a TOFU exception, but new ones should use
+ // foo.example.com
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+ Services.prefs.setBoolPref("network.http.http2.allow-push", true);
+ Services.prefs.setBoolPref("network.http.altsvc.enabled", true);
+ Services.prefs.setBoolPref("network.http.altsvc.oe", true);
+ Services.prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, bar.example.com"
+ );
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
+ Ci.nsILoadGroup
+ );
+
+ Services.prefs.setStringPref(
+ "services.settings.server",
+ `data:,#remote-settings-dummy/v1`
+ );
+
+ proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ Services.prefs.clearUserPref("network.http.http2.allow-push");
+ Services.prefs.clearUserPref("network.http.altsvc.enabled");
+ Services.prefs.clearUserPref("network.http.altsvc.oe");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref(
+ "network.cookieJarSettings.unblocked_for_testing"
+ );
+
+ await proxy.stop();
+});
+
+// hack - the header test resets the multiplex object on the server,
+// so make sure header is always run before the multiplex test.
+//
+// make sure post_big runs first to test race condition in restarting
+// a stalled stream when a SETTINGS frame arrives
+add_task(async function do_test_http2_post_big() {
+ const { httpProxyConnectResponseCode } = await test_http2_post_big(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_basic() {
+ const { httpProxyConnectResponseCode } = await test_http2_basic(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_concurrent() {
+ const { httpProxyConnectResponseCode } = await test_http2_concurrent(
+ concurrent_channels,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_concurrent_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_concurrent_post(
+ concurrent_channels,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_basic_unblocked_dep() {
+ const { httpProxyConnectResponseCode } = await test_http2_basic_unblocked_dep(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_nospdy() {
+ const { httpProxyConnectResponseCode } = await test_http2_nospdy(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push1() {
+ const { httpProxyConnectResponseCode } = await test_http2_push1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push2() {
+ const { httpProxyConnectResponseCode } = await test_http2_push2(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push3() {
+ const { httpProxyConnectResponseCode } = await test_http2_push3(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push4() {
+ const { httpProxyConnectResponseCode } = await test_http2_push4(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push5() {
+ const { httpProxyConnectResponseCode } = await test_http2_push5(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push6() {
+ const { httpProxyConnectResponseCode } = await test_http2_push6(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_doubleheader() {
+ const { httpProxyConnectResponseCode } = await test_http2_doubleheader(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_xhr() {
+ await test_http2_xhr(serverPort);
+});
+
+add_task(async function do_test_http2_header() {
+ const { httpProxyConnectResponseCode } = await test_http2_header(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_invalid_response_header_name_spaces() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(serverPort, "name_spaces");
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(
+ async function do_test_http2_invalid_response_header_value_line_feed() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(serverPort, "value_line_feed");
+ Assert.equal(httpProxyConnectResponseCode, 200);
+ }
+);
+
+add_task(
+ async function do_test_http2_invalid_response_header_value_carriage_return() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(
+ serverPort,
+ "value_carriage_return"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+ }
+);
+
+add_task(async function do_test_http2_invalid_response_header_value_null() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(serverPort, "value_null");
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_cookie_crumbling() {
+ const { httpProxyConnectResponseCode } = await test_http2_cookie_crumbling(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_multiplex() {
+ var values = await test_http2_multiplex(serverPort);
+ Assert.equal(values[0].httpProxyConnectResponseCode, 200);
+ Assert.equal(values[1].httpProxyConnectResponseCode, 200);
+ Assert.notEqual(values[0].streamID, values[1].streamID);
+});
+
+add_task(async function do_test_http2_big() {
+ const { httpProxyConnectResponseCode } = await test_http2_big(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_huge_suspended() {
+ const { httpProxyConnectResponseCode } = await test_http2_huge_suspended(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_post(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_empty_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_empty_post(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_patch() {
+ const { httpProxyConnectResponseCode } = await test_http2_patch(serverPort);
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_pushapi_1() {
+ const { httpProxyConnectResponseCode } = await test_http2_pushapi_1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 0);
+});
+
+add_task(async function do_test_http2_continuations() {
+ const { httpProxyConnectResponseCode } = await test_http2_continuations(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 0);
+});
+
+add_task(async function do_test_http2_blocking_download() {
+ const { httpProxyConnectResponseCode } = await test_http2_blocking_download(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_illegalhpacksoft() {
+ const { httpProxyConnectResponseCode } = await test_http2_illegalhpacksoft(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_illegalhpackhard() {
+ const { httpProxyConnectResponseCode } = await test_http2_illegalhpackhard(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_folded_header() {
+ const { httpProxyConnectResponseCode } = await test_http2_folded_header(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_empty_data() {
+ const { httpProxyConnectResponseCode } = await test_http2_empty_data(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_status_phrase() {
+ const { httpProxyConnectResponseCode } = await test_http2_status_phrase(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_doublepush() {
+ const { httpProxyConnectResponseCode } = await test_http2_doublepush(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_disk_cache_push() {
+ const { httpProxyConnectResponseCode } = await test_http2_disk_cache_push(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_h11required_stream() {
+ // Add new tests above here - best to add new tests before h1
+ // streams get too involved
+ // These next two must always come in this order
+ const { httpProxyConnectResponseCode } = await test_http2_h11required_stream(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_h11required_session() {
+ const { httpProxyConnectResponseCode } = await test_http2_h11required_session(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_retry_rst() {
+ const { httpProxyConnectResponseCode } = await test_http2_retry_rst(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_wrongsuite_tls12() {
+ // For this test we need to start HTTPS 1.1 proxy because HTTP/2 proxy cannot be used.
+ proxy.unregisterFilter();
+ let proxyHttp1 = new NodeHTTPSProxyServer();
+ await proxyHttp1.start();
+ proxyHttp1.registerFilter();
+ registerCleanupFunction(() => {
+ proxyHttp1.stop();
+ });
+ const { httpProxyConnectResponseCode } = await test_http2_wrongsuite_tls12(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+ proxyHttp1.unregisterFilter();
+ proxy.registerFilter();
+});
+
+add_task(async function do_test_http2_wrongsuite_tls13() {
+ const { httpProxyConnectResponseCode } = await test_http2_wrongsuite_tls13(
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push_firstparty1() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_firstparty1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push_firstparty2() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_firstparty2(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push_firstparty3() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_firstparty3(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push_userContext1() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_userContext1(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push_userContext2() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_userContext2(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_push_userContext3() {
+ const { httpProxyConnectResponseCode } = await test_http2_push_userContext3(
+ loadGroup,
+ serverPort
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
diff --git a/netwerk/test/unit/test_http3.js b/netwerk/test/unit/test_http3.js
new file mode 100644
index 0000000000..7f3f5b118d
--- /dev/null
+++ b/netwerk/test/unit/test_http3.js
@@ -0,0 +1,571 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// Generate a post with known pre-calculated md5 sum.
+function generateContent(size) {
+ let content = "";
+ for (let i = 0; i < size; i++) {
+ content += "0";
+ }
+ return content;
+}
+
+let post = generateContent(10);
+
+// Max concurent stream number in neqo is 100.
+// Openning 120 streams will test queuing of streams.
+let number_of_parallel_requests = 120;
+let h1Server = null;
+let h3Route;
+let httpsOrigin;
+let httpOrigin;
+let h3AltSvc;
+
+let prefs;
+
+let tests = [
+ // This test must be the first because it setsup alt-svc connection, that
+ // other tests use.
+ test_https_alt_svc,
+ test_multiple_requests,
+ test_request_cancelled_by_server,
+ test_stream_cancelled_by_necko,
+ test_multiple_request_one_is_cancelled,
+ test_multiple_request_one_is_cancelled_by_necko,
+ test_post,
+ test_patch,
+ test_http_alt_svc,
+ test_slow_receiver,
+ // This test should be at the end, because it will close http3
+ // connection and the transaction will switch to already existing http2
+ // connection.
+ // TODO: Bug 1582667 should try to fix issue with connection being closed.
+ test_version_fallback,
+ testsDone,
+];
+
+let current_test = 0;
+
+function run_next_test() {
+ if (current_test < tests.length) {
+ dump("starting test number " + current_test + "\n");
+ tests[current_test]();
+ current_test++;
+ }
+}
+
+function run_test() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Services.prefs;
+
+ prefs.setBoolPref("network.http.http3.enable", true);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ // We always resolve elements of localDomains as it's hardcoded without the
+ // following pref:
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ prefs.setBoolPref("network.http.altsvc.oe", true);
+
+ // The certificate for the http3server server is for foo.example.com and
+ // is signed by http2-ca.pem so add that cert to the trust list as a
+ // signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ httpsOrigin = "https://foo.example.com:" + h2Port + "/";
+
+ h1Server = new HttpServer();
+ h1Server.registerPathHandler("/http3-test", h1Response);
+ h1Server.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
+ h1Server.registerPathHandler("/VersionFallback", h1Response);
+ h1Server.start(-1);
+ h1Server.identity.setPrimary(
+ "http",
+ "foo.example.com",
+ h1Server.identity.primaryPort
+ );
+ httpOrigin = "http://foo.example.com:" + h1Server.identity.primaryPort + "/";
+
+ run_next_test();
+}
+
+function h1Response(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ try {
+ let hval = "h3-29=" + metadata.getHeader("x-altsvc");
+ response.setHeader("Alt-Svc", hval, false);
+ } catch (e) {}
+
+ let body = "Q: What did 0 say to 8? A: Nice Belt!\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ let body = '["http://foo.example.com:' + h1Server.identity.primaryPort + '"]';
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+let Http3CheckListener = function () {};
+
+Http3CheckListener.prototype = {
+ onDataAvailableFired: false,
+ expectedStatus: Cr.NS_OK,
+ expectedRoute: "",
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ Assert.equal(request.status, this.expectedStatus);
+ if (Components.isSuccessCode(this.expectedStatus)) {
+ Assert.equal(request.responseStatus, 200);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, this.expectedStatus);
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ Assert.equal(routed, this.expectedRoute);
+
+ if (Components.isSuccessCode(this.expectedStatus)) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ Assert.equal(this.onDataAvailableFired, true);
+ Assert.equal(request.getResponseHeader("X-Firefox-Http3"), "h3-29");
+ }
+ run_next_test();
+ do_test_finished();
+ },
+};
+
+let WaitForHttp3Listener = function () {};
+
+WaitForHttp3Listener.prototype = new Http3CheckListener();
+
+WaitForHttp3Listener.prototype.uri = "";
+WaitForHttp3Listener.prototype.h3AltSvc = "";
+
+WaitForHttp3Listener.prototype.onStopRequest = function testOnStopRequest(
+ request,
+ status
+) {
+ Assert.equal(status, this.expectedStatus);
+
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+
+ if (routed == this.expectedRoute) {
+ Assert.equal(routed, this.expectedRoute); // always true, but a useful log
+ Assert.equal(httpVersion, "h3-29");
+ run_next_test();
+ } else {
+ dump("poll later for alt svc mapping\n");
+ if (httpVersion == "h2") {
+ request.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.ok(request.supportsHTTP3);
+ }
+ do_test_pending();
+ do_timeout(500, () => {
+ doTest(this.uri, this.expectedRoute, this.h3AltSvc);
+ });
+ }
+
+ do_test_finished();
+};
+
+function doTest(uri, expectedRoute, altSvc) {
+ let chan = makeChan(uri);
+ let listener = new WaitForHttp3Listener();
+ listener.uri = uri;
+ listener.expectedRoute = expectedRoute;
+ listener.h3AltSvc = altSvc;
+ chan.setRequestHeader("x-altsvc", altSvc, false);
+ chan.asyncOpen(listener);
+}
+
+// Test Alt-Svc for http3.
+// H2 server returns alt-svc=h3-29=:h3port
+function test_https_alt_svc() {
+ dump("test_https_alt_svc()\n");
+ do_test_pending();
+ doTest(httpsOrigin + "http3-test", h3Route, h3AltSvc);
+}
+
+// Listener for a number of parallel requests. if with_error is set, one of
+// the channels will be cancelled (by the server or in onStartRequest).
+let MultipleListener = function () {};
+
+MultipleListener.prototype = {
+ number_of_parallel_requests: 0,
+ with_error: Cr.NS_OK,
+ count_of_done_requests: 0,
+ error_found_onstart: false,
+ error_found_onstop: false,
+ need_cancel_found: false,
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ let need_cancel = "";
+ try {
+ need_cancel = request.getRequestHeader("CancelMe");
+ } catch (e) {}
+ if (need_cancel != "") {
+ this.need_cancel_found = true;
+ request.cancel(Cr.NS_ERROR_ABORT);
+ } else if (Components.isSuccessCode(request.status)) {
+ Assert.equal(request.responseStatus, 200);
+ } else if (this.error_found_onstart) {
+ do_throw("We should have only one request failing.");
+ } else {
+ Assert.equal(request.status, this.with_error);
+ this.error_found_onstart = true;
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let routed = "";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ Assert.equal(routed, this.expectedRoute);
+
+ if (Components.isSuccessCode(request.status)) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ }
+
+ if (!Components.isSuccessCode(request.status)) {
+ if (this.error_found_onstop) {
+ do_throw("We should have only one request failing.");
+ } else {
+ Assert.equal(request.status, this.with_error);
+ this.error_found_onstop = true;
+ }
+ }
+ this.count_of_done_requests++;
+ if (this.count_of_done_requests == this.number_of_parallel_requests) {
+ if (Components.isSuccessCode(this.with_error)) {
+ Assert.equal(this.error_found_onstart, false);
+ Assert.equal(this.error_found_onstop, false);
+ } else {
+ Assert.ok(this.error_found_onstart || this.need_cancel_found);
+ Assert.equal(this.error_found_onstop, true);
+ }
+ run_next_test();
+ }
+ do_test_finished();
+ },
+};
+
+// Multiple requests
+function test_multiple_requests() {
+ dump("test_multiple_requests()\n");
+
+ let listener = new MultipleListener();
+ listener.number_of_parallel_requests = number_of_parallel_requests;
+ listener.expectedRoute = h3Route;
+
+ for (let i = 0; i < number_of_parallel_requests; i++) {
+ let chan = makeChan(httpsOrigin + "20000");
+ chan.asyncOpen(listener);
+ do_test_pending();
+ }
+}
+
+// A request cancelled by a server.
+function test_request_cancelled_by_server() {
+ dump("test_request_cancelled_by_server()\n");
+
+ let listener = new Http3CheckListener();
+ listener.expectedStatus = Cr.NS_ERROR_NET_INTERRUPT;
+ listener.expectedRoute = h3Route;
+ let chan = makeChan(httpsOrigin + "RequestCancelled");
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+let CancelRequestListener = function () {};
+
+CancelRequestListener.prototype = new Http3CheckListener();
+
+CancelRequestListener.prototype.expectedStatus = Cr.NS_ERROR_ABORT;
+
+CancelRequestListener.prototype.onStartRequest = function testOnStartRequest(
+ request
+) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ Assert.equal(Components.isSuccessCode(request.status), true);
+ request.cancel(Cr.NS_ERROR_ABORT);
+};
+
+// Cancel stream after OnStartRequest.
+function test_stream_cancelled_by_necko() {
+ dump("test_stream_cancelled_by_necko()\n");
+
+ let listener = new CancelRequestListener();
+ listener.expectedRoute = h3Route;
+ let chan = makeChan(httpsOrigin + "20000");
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+// Multiple requests, one gets cancelled by the server, the other should finish normally.
+function test_multiple_request_one_is_cancelled() {
+ dump("test_multiple_request_one_is_cancelled()\n");
+
+ let listener = new MultipleListener();
+ listener.number_of_parallel_requests = number_of_parallel_requests;
+ listener.with_error = Cr.NS_ERROR_NET_INTERRUPT;
+ listener.expectedRoute = h3Route;
+
+ for (let i = 0; i < number_of_parallel_requests; i++) {
+ let uri = httpsOrigin + "20000";
+ if (i == 4) {
+ // Add a request that will be cancelled by the server.
+ uri = httpsOrigin + "RequestCancelled";
+ }
+ let chan = makeChan(uri);
+ chan.asyncOpen(listener);
+ do_test_pending();
+ }
+}
+
+// Multiple requests, one gets cancelled by us, the other should finish normally.
+function test_multiple_request_one_is_cancelled_by_necko() {
+ dump("test_multiple_request_one_is_cancelled_by_necko()\n");
+
+ let listener = new MultipleListener();
+ listener.number_of_parallel_requests = number_of_parallel_requests;
+ listener.with_error = Cr.NS_ERROR_ABORT;
+ listener.expectedRoute = h3Route;
+ for (let i = 0; i < number_of_parallel_requests; i++) {
+ let chan = makeChan(httpsOrigin + "20000");
+ if (i == 4) {
+ // MultipleListener will cancel request with this header.
+ chan.setRequestHeader("CancelMe", "true", false);
+ }
+ chan.asyncOpen(listener);
+ do_test_pending();
+ }
+}
+
+let PostListener = function () {};
+
+PostListener.prototype = new Http3CheckListener();
+
+PostListener.prototype.onDataAvailable = function (request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ read_stream(stream, cnt);
+};
+
+// Support for doing a POST
+function do_post(content, chan, listener, method) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = content;
+
+ let uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+
+ chan.requestMethod = method;
+
+ chan.asyncOpen(listener);
+}
+
+// Test a simple POST
+function test_post() {
+ dump("test_post()");
+ let chan = makeChan(httpsOrigin + "post");
+ let listener = new PostListener();
+ listener.expectedRoute = h3Route;
+ do_post(post, chan, listener, "POST");
+ do_test_pending();
+}
+
+// Test a simple PATCH
+function test_patch() {
+ dump("test_patch()");
+ let chan = makeChan(httpsOrigin + "patch");
+ let listener = new PostListener();
+ listener.expectedRoute = h3Route;
+ do_post(post, chan, listener, "PATCH");
+ do_test_pending();
+}
+
+// Test alt-svc for http (without s)
+function test_http_alt_svc() {
+ dump("test_http_alt_svc()\n");
+
+ do_test_pending();
+ doTest(httpOrigin + "http3-test", h3Route, h3AltSvc);
+}
+
+let SlowReceiverListener = function () {};
+
+SlowReceiverListener.prototype = new Http3CheckListener();
+SlowReceiverListener.prototype.count = 0;
+
+SlowReceiverListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.count += cnt;
+ read_stream(stream, cnt);
+};
+
+SlowReceiverListener.prototype.onStopRequest = function (request, status) {
+ Assert.equal(status, this.expectedStatus);
+ Assert.equal(this.count, 10000000);
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ Assert.equal(routed, this.expectedRoute);
+
+ if (Components.isSuccessCode(this.expectedStatus)) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ Assert.equal(this.onDataAvailableFired, true);
+ }
+ run_next_test();
+ do_test_finished();
+};
+
+function test_slow_receiver() {
+ dump("test_slow_receiver()\n");
+ let chan = makeChan(httpsOrigin + "10000000");
+ let listener = new SlowReceiverListener();
+ listener.expectedRoute = h3Route;
+ chan.asyncOpen(listener);
+ do_test_pending();
+ chan.suspend();
+ do_timeout(1000, chan.resume);
+}
+
+let CheckFallbackListener = function () {};
+
+CheckFallbackListener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ Assert.equal(routed, "0");
+
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "http/1.1");
+ run_next_test();
+ do_test_finished();
+ },
+};
+
+// Server cancels request with VersionFallback.
+function test_version_fallback() {
+ dump("test_version_fallback()\n");
+
+ let chan = makeChan(httpsOrigin + "VersionFallback");
+ let listener = new CheckFallbackListener();
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function testsDone() {
+ prefs.clearUserPref("network.http.http3.enable");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ prefs.clearUserPref("network.http.altsvc.oe");
+ dump("testDone\n");
+ do_test_pending();
+ h1Server.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_http3_0rtt.js b/netwerk/test/unit/test_http3_0rtt.js
new file mode 100644
index 0000000000..1002263ed5
--- /dev/null
+++ b/netwerk/test/unit/test_http3_0rtt.js
@@ -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/. */
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3-29");
+});
+
+let Http3Listener = function () {};
+
+Http3Listener.prototype = {
+ resumed: false,
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(request.responseStatus, 200);
+
+ let secinfo = request.securityInfo;
+ Assert.equal(secinfo.resumed, this.resumed);
+ Assert.ok(secinfo.serverCert != null);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+
+ this.finish();
+ },
+};
+
+function chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+async function test_first_conn_no_resumed() {
+ let listener = new Http3Listener();
+ listener.resumed = false;
+ let chan = makeChan("https://foo.example.com/30");
+ await chanPromise(chan, listener);
+}
+
+async function test_0RTT(enable_0rtt, resumed) {
+ info(`enable_0rtt=${enable_0rtt} resumed=${resumed}`);
+ Services.prefs.setBoolPref("network.http.http3.enable_0rtt", enable_0rtt);
+
+ // Make sure the h3 connection created by the previous test is cleared.
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // This connecion should be resumed.
+ let listener = new Http3Listener();
+ listener.resumed = resumed;
+ let chan = makeChan("https://foo.example.com/30");
+ await chanPromise(chan, listener);
+}
+
+add_task(async function test_0RTT_setups() {
+ await test_first_conn_no_resumed();
+
+ // http3.0RTT enabled
+ await test_0RTT(true, true);
+
+ // http3.0RTT disabled
+ await test_0RTT(false, false);
+});
diff --git a/netwerk/test/unit/test_http3_421.js b/netwerk/test/unit/test_http3_421.js
new file mode 100644
index 0000000000..de04babe06
--- /dev/null
+++ b/netwerk/test/unit/test_http3_421.js
@@ -0,0 +1,172 @@
+"use strict";
+
+let h3Route;
+let httpsOrigin;
+let h3AltSvc;
+let prefs;
+
+let tests = [test_https_alt_svc, test_response_421, testsDone];
+let current_test = 0;
+
+function run_next_test() {
+ if (current_test < tests.length) {
+ dump("starting test number " + current_test + "\n");
+ tests[current_test]();
+ current_test++;
+ }
+}
+
+function run_test() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Services.prefs;
+
+ prefs.setBoolPref("network.http.http3.enable", true);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ // We always resolve elements of localDomains as it's hardcoded without the
+ // following pref:
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ // The certificate for the http3server server is for foo.example.com and
+ // is signed by http2-ca.pem so add that cert to the trust list as a
+ // signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ httpsOrigin = "https://foo.example.com:" + h2Port + "/";
+
+ run_next_test();
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+let Http3Listener = function () {};
+
+Http3Listener.prototype = {
+ onDataAvailableFired: false,
+ buffer: "",
+ routed: "",
+ httpVersion: "",
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.buffer = this.buffer.concat(read_stream(stream, cnt));
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ Assert.equal(this.onDataAvailableFired, true);
+ this.routed = "NA";
+ try {
+ this.routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + this.routed + "\n");
+
+ this.httpVersion = "";
+ try {
+ this.httpVersion = request.protocolVersion;
+ } catch (e) {}
+ dump("httpVersion is " + this.httpVersion + "\n");
+ },
+};
+
+let WaitForHttp3Listener = function () {};
+
+WaitForHttp3Listener.prototype = new Http3Listener();
+
+WaitForHttp3Listener.prototype.uri = "";
+
+WaitForHttp3Listener.prototype.onStopRequest = function testOnStopRequest(
+ request,
+ status
+) {
+ Http3Listener.prototype.onStopRequest.call(this, request, status);
+
+ if (this.routed == h3Route) {
+ Assert.equal(this.httpVersion, "h3-29");
+ run_next_test();
+ } else {
+ dump("poll later for alt svc mapping\n");
+ do_test_pending();
+ do_timeout(500, () => {
+ doTest(this.uri);
+ });
+ }
+
+ do_test_finished();
+};
+
+function doTest(uri) {
+ let chan = makeChan(uri);
+ let listener = new WaitForHttp3Listener();
+ listener.uri = uri;
+ chan.setRequestHeader("x-altsvc", h3AltSvc, false);
+ chan.asyncOpen(listener);
+}
+
+// Test Alt-Svc for http3.
+// H2 server returns alt-svc=h3-29=:h3port
+function test_https_alt_svc() {
+ dump("test_https_alt_svc()\n");
+
+ do_test_pending();
+ doTest(httpsOrigin + "http3-test");
+}
+
+let Resp421Listener = function () {};
+
+Resp421Listener.prototype = new Http3Listener();
+
+Resp421Listener.prototype.onStopRequest = function testOnStopRequest(
+ request,
+ status
+) {
+ Http3Listener.prototype.onStopRequest.call(this, request, status);
+
+ Assert.equal(this.routed, "0");
+ Assert.equal(this.httpVersion, "h2");
+ Assert.ok(this.buffer.match("You Win! [(]by requesting/Response421[)]"));
+
+ run_next_test();
+ do_test_finished();
+};
+
+function test_response_421() {
+ dump("test_response_421()\n");
+
+ let listener = new Resp421Listener();
+ let chan = makeChan(httpsOrigin + "Response421");
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function testsDone() {
+ prefs.clearUserPref("network.http.http3.enable");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ dump("testDone\n");
+ do_test_pending();
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_http3_alt_svc.js b/netwerk/test/unit/test_http3_alt_svc.js
new file mode 100644
index 0000000000..201101eb19
--- /dev/null
+++ b/netwerk/test/unit/test_http3_alt_svc.js
@@ -0,0 +1,136 @@
+"use strict";
+
+let httpsOrigin;
+let h3AltSvc;
+let h3Route;
+let prefs;
+
+let tests = [test_https_alt_svc, testsDone];
+
+let current_test = 0;
+
+function run_next_test() {
+ if (current_test < tests.length) {
+ dump("starting test number " + current_test + "\n");
+ tests[current_test]();
+ current_test++;
+ }
+}
+
+function run_test() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Services.prefs;
+
+ prefs.setBoolPref("network.http.http3.enable", true);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ // We always resolve elements of localDomains as it's hardcoded without the
+ // following pref:
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ // The certificate for the http3server server is for foo.example.com and
+ // is signed by http2-ca.pem so add that cert to the trust list as a
+ // signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ httpsOrigin = "https://foo.example.com:" + h2Port + "/";
+
+ run_next_test();
+}
+
+function createPrincipal(url) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ return ssm.createContentPrincipal(Services.io.newURI(url), {});
+ } catch (e) {
+ return null;
+ }
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: createPrincipal(uri),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+let WaitForHttp3Listener = function () {};
+
+WaitForHttp3Listener.prototype = {
+ onDataAvailableFired: false,
+ expectedRoute: "",
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ if (routed == this.expectedRoute) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ run_next_test();
+ } else {
+ dump("poll later for alt svc mapping\n");
+ do_test_pending();
+ do_timeout(500, () => {
+ doTest(this.uri, this.expectedRoute, this.h3AltSvc);
+ });
+ }
+
+ do_test_finished();
+ },
+};
+
+function doTest(uri, expectedRoute, altSvc) {
+ let chan = makeChan(uri);
+ let listener = new WaitForHttp3Listener();
+ listener.uri = uri;
+ listener.expectedRoute = expectedRoute;
+ listener.h3AltSvc = altSvc;
+ chan.setRequestHeader("x-altsvc", altSvc, false);
+ chan.asyncOpen(listener);
+}
+
+// Test Alt-Svc for http3.
+// H2 server returns alt-svc=h2=foo2.example.com:8000,h3-29=:h3port,h3-30=foo2.example.com:8443
+function test_https_alt_svc() {
+ dump("test_https_alt_svc()\n");
+ do_test_pending();
+ doTest(httpsOrigin + "http3-test2", h3Route, h3AltSvc);
+}
+
+function testsDone() {
+ prefs.clearUserPref("network.http.http3.enable");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ dump("testDone\n");
+}
diff --git a/netwerk/test/unit/test_http3_coalescing.js b/netwerk/test/unit/test_http3_coalescing.js
new file mode 100644
index 0000000000..b6c19ef626
--- /dev/null
+++ b/netwerk/test/unit/test_http3_coalescing.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let h2Port;
+let h3Port;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed"
+ );
+ Services.prefs.clearUserPref("network.dns.httpssvc.reset_exclustion_list");
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout"
+ );
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ }
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+async function H3CoalescingTest(host1, host2) {
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ `${host1};h3-29=:${h3Port}`
+ );
+ Services.prefs.setCharPref("network.dns.localDomains", host1);
+
+ let chan = makeChan(`https://${host1}`);
+ let [req] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.protocolVersion, "h3-29");
+ let hash = req.getResponseHeader("x-http3-conn-hash");
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ `${host2};h3-29=:${h3Port}`
+ );
+ Services.prefs.setCharPref("network.dns.localDomains", host2);
+
+ chan = makeChan(`https://${host2}`);
+ [req] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.protocolVersion, "h3-29");
+ // The port used by the second connection should be the same as the first one.
+ Assert.equal(req.getResponseHeader("x-http3-conn-hash"), hash);
+}
+
+add_task(async function testH3CoalescingWithSpeculativeConnection() {
+ await http3_setup_tests("h3-29");
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ await H3CoalescingTest("foo.h3_coalescing.org", "bar.h3_coalescing.org");
+});
+
+add_task(async function testH3CoalescingWithoutSpeculativeConnection() {
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ await H3CoalescingTest("baz.h3_coalescing.org", "qux.h3_coalescing.org");
+});
diff --git a/netwerk/test/unit/test_http3_direct_proxy.js b/netwerk/test/unit/test_http3_direct_proxy.js
new file mode 100644
index 0000000000..74d2b11fc1
--- /dev/null
+++ b/netwerk/test/unit/test_http3_direct_proxy.js
@@ -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/. */
+
+// Test if a HTTP3 connection can be established when a proxy info says
+// to use direct connection
+
+"use strict";
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.autoconfig_url");
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3-29");
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+add_task(async function testHttp3WithDirectProxy() {
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' return "DIRECT; PROXY foopy:8080;"' +
+ "}";
+
+ // Configure PAC
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ Services.prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ let chan = makeChan(`https://foo.example.com`);
+ let [req] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.protocolVersion, "h3-29");
+});
diff --git a/netwerk/test/unit/test_http3_dns_retry.js b/netwerk/test/unit/test_http3_dns_retry.js
new file mode 100644
index 0000000000..facbd4e90b
--- /dev/null
+++ b/netwerk/test/unit/test_http3_dns_retry.js
@@ -0,0 +1,357 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let h2Port;
+let h3Port;
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+add_setup(async function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ trr_test_setup();
+
+ if (mozinfo.socketprocess_networking) {
+ Cc["@mozilla.org/network/protocol;1?name=http"].getService(
+ Ci.nsIHttpProtocolHandler
+ );
+ Services.dns; // Needed to trigger socket process.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR first
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ Services.prefs.setBoolPref(
+ "network.http.http3.block_loopback_ipv6_addr",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.http.http3.retry_different_ip_family",
+ true
+ );
+ Services.prefs.setBoolPref("network.dns.get-ttl", false);
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ registerCleanupFunction(async () => {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ trr_clear_prefs();
+ Services.prefs.clearUserPref(
+ "network.http.http3.retry_different_ip_family"
+ );
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.http.http3.block_loopback_ipv6_addr");
+ Services.prefs.clearUserPref("network.dns.get-ttl");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+ });
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ // eslint-disable-next-line no-async-promise-executor
+ return new Promise(async resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+async function registerDoHAnswers(host, ipv4Answers, ipv6Answers, httpsRecord) {
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers(host, "HTTPS", {
+ answers: httpsRecord,
+ });
+
+ await trrServer.registerDoHAnswers(host, "AAAA", {
+ answers: ipv6Answers,
+ });
+
+ await trrServer.registerDoHAnswers(host, "A", {
+ answers: ipv4Answers,
+ });
+
+ Services.dns.clearCache(true);
+}
+
+// Test if we retry IPv4 address for Http/3 properly.
+add_task(async function test_retry_with_ipv4() {
+ let host = "test.http3_retry.com";
+ let ipv4answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ];
+ // The UDP socket will return connection refused error because we set
+ // "network.http.http3.block_loopback_ipv6_addr" to true.
+ let ipv6answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::1",
+ },
+ ];
+ let httpsRecord = [
+ {
+ name: host,
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: host,
+ values: [
+ { key: "alpn", value: "h3" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ];
+
+ await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
+
+ let chan = makeChan(`https://${host}`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3");
+
+ await trrServer.stop();
+});
+
+add_task(async function test_retry_with_ipv4_disabled() {
+ let host = "test.http3_retry_ipv4_blocked.com";
+ let ipv4answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ];
+ // The UDP socket will return connection refused error because we set
+ // "network.http.http3.block_loopback_ipv6_addr" to true.
+ let ipv6answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::1",
+ },
+ ];
+ let httpsRecord = [
+ {
+ name: host,
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: host,
+ values: [
+ { key: "alpn", value: "h3" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ];
+
+ await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
+
+ let chan = makeChan(`https://${host}`);
+ chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ chan.setIPv4Disabled();
+
+ await channelOpenPromise(chan, CL_EXPECT_FAILURE);
+ await trrServer.stop();
+});
+
+// See bug 1837252. There is no way to observe the outcome of this test, because
+// the crash in bug 1837252 is only triggered by speculative connection.
+// The outcome of this test is no crash.
+add_task(async function test_retry_with_ipv4_failed() {
+ let host = "test.http3_retry_failed.com";
+ // Return a wrong answer intentionally.
+ let ipv4answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ];
+ // The UDP socket will return connection refused error because we set
+ // "network.http.http3.block_loopback_ipv6_addr" to true.
+ let ipv6answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::1",
+ },
+ ];
+ let httpsRecord = [
+ {
+ name: host,
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: host,
+ values: [
+ { key: "alpn", value: "h3" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ];
+
+ await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
+
+ // This speculative connection is used to trigger the mechanism to retry
+ // Http/3 connection with a IPv4 address.
+ // We want to make the connection entry's IP preference known,
+ // so DnsAndConnectSocket::mRetryWithDifferentIPFamily will be set to true
+ // before the second speculative connection.
+ let uri = Services.io.newURI(`https://test.http3_retry_failed.com`);
+ Services.io.speculativeConnect(
+ uri,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ false
+ );
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 3000));
+
+ // When this speculative connection is created, the connection entry is
+ // already set to prefer IPv4. Since we provided an invalid A response,
+ // DnsAndConnectSocket::OnLookupComplete is called with an error.
+ // Since DnsAndConnectSocket::mRetryWithDifferentIPFamily is true, we do
+ // retry DNS lookup. During retry, we should not create UDP connection.
+ Services.io.speculativeConnect(
+ uri,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ false
+ );
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 3000));
+ await trrServer.stop();
+});
+
+add_task(async function test_retry_with_0rtt() {
+ let host = "test.http3_retry_0rtt.com";
+ let ipv4answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ];
+ // The UDP socket will return connection refused error because we set
+ // "network.http.http3.block_loopback_ipv6_addr" to true.
+ let ipv6answers = [
+ {
+ name: host,
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::1",
+ },
+ ];
+ let httpsRecord = [
+ {
+ name: host,
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: host,
+ values: [
+ { key: "alpn", value: "h3" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ];
+
+ await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
+
+ let chan = makeChan(`https://${host}`);
+ chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ chan.setIPv6Disabled();
+
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3");
+
+ // Make sure the h3 connection created by the previous test is cleared.
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ chan = makeChan(`https://${host}`);
+ chan.QueryInterface(Ci.nsIHttpChannelInternal);
+
+ [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3");
+
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_http3_early_hint_listener.js b/netwerk/test/unit/test_http3_early_hint_listener.js
new file mode 100644
index 0000000000..5100ea3151
--- /dev/null
+++ b/netwerk/test/unit/test_http3_early_hint_listener.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// The server will always respond with a 103 EarlyHint followed by a
+// 200 response.
+// 103 response contains:
+// 1) a non-link header
+// 2) a link header if a request has a "link-to-set" header. If the
+// request header is not set, the response will not have Link headers.
+// A "link-to-set" header may contain multiple link headers
+// separated with a comma.
+
+const earlyhintspath = "/103_response";
+const hint1 = "</style.css>; rel=preload; as=style";
+const hint2 = "</img.png>; rel=preload; as=image";
+
+let EarlyHintsListener = function () {};
+
+EarlyHintsListener.prototype = {
+ _expected_hints: [],
+ earlyHintsReceived: 0,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIEarlyHintObserver"]),
+
+ earlyHint(header) {
+ Assert.ok(this._expected_hints.includes(header));
+ this.earlyHintsReceived += 1;
+ },
+};
+
+function chanPromise(uri, listener, headers) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ NetUtil.newURI(uri),
+ {}
+ );
+ var chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ chan
+ .QueryInterface(Ci.nsIHttpChannel)
+ .setRequestHeader("link-to-set", headers, false);
+ chan.QueryInterface(Ci.nsIHttpChannelInternal).setEarlyHintObserver(listener);
+
+ return promiseAsyncOpen(chan);
+}
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3-29");
+});
+
+add_task(async function early_hints() {
+ let earlyHints = new EarlyHintsListener();
+ earlyHints._expected_hints = [hint1];
+
+ await chanPromise(
+ `https://foo.example.com${earlyhintspath}`,
+ earlyHints,
+ hint1
+ );
+ Assert.equal(earlyHints.earlyHintsReceived, 1);
+});
+
+// Test when there is no Link header in a 103 response.
+// 103 response will contain non-link headers.
+add_task(async function no_early_hints() {
+ let earlyHints = new EarlyHintsListener();
+
+ await chanPromise(`https://foo.example.com${earlyhintspath}`, earlyHints, "");
+ Assert.equal(earlyHints.earlyHintsReceived, 0);
+});
+
+add_task(async function early_hints_multiple() {
+ let earlyHints = new EarlyHintsListener();
+ earlyHints._expected_hints = [hint1, hint2];
+
+ await chanPromise(
+ `https://foo.example.com${earlyhintspath}`,
+ earlyHints,
+ hint1 + ", " + hint2
+ );
+ Assert.equal(earlyHints.earlyHintsReceived, 2);
+});
diff --git a/netwerk/test/unit/test_http3_error_before_connect.js b/netwerk/test/unit/test_http3_error_before_connect.js
new file mode 100644
index 0000000000..72f8d61182
--- /dev/null
+++ b/netwerk/test/unit/test_http3_error_before_connect.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+let httpsUri;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.disableIPv6");
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ Services.prefs.clearUserPref("network.http.http3.backup_timer_delay");
+ dump("cleanup done\n");
+});
+
+function makeChan() {
+ let chan = NetUtil.newChannel({
+ uri: httpsUri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+add_task(async function test_setup() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ let h3Port = Services.env.get("MOZHTTP3_PORT_NO_RESPONSE");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ // Set AltSvc to point to not existing HTTP3 server on port 443
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "foo.example.com;h3-29=:" + h3Port
+ );
+ Services.prefs.setIntPref("network.http.http3.backup_timer_delay", 0);
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ httpsUri = "https://foo.example.com:" + h2Port + "/";
+});
+
+add_task(async function test_fatal_stream_error() {
+ let result = 1;
+ // We need to loop here because we need to wait for AltSvc storage to
+ // to be started.
+ // We also do not have a way to verify that HTTP3 has been tried, because
+ // the fallback is automatic, so try a couple of times.
+ do {
+ // We need to close HTTP2 connections, otherwise our connection pooling
+ // will dispatch the request over already existing HTTP2 connection.
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+ result++;
+ } while (result < 5);
+});
+
+let CheckOnlyHttp2Listener = function () {};
+
+CheckOnlyHttp2Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h2");
+
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+ Assert.ok(routed === "0" || routed === "NA");
+ this.finish();
+ },
+};
+
+add_task(async function test_no_http3_after_error() {
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+});
+
+// also after all connections are closed.
+add_task(async function test_no_http3_after_error2() {
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+});
diff --git a/netwerk/test/unit/test_http3_fast_fallback.js b/netwerk/test/unit/test_http3_fast_fallback.js
new file mode 100644
index 0000000000..ab2b6b7044
--- /dev/null
+++ b/netwerk/test/unit/test_http3_fast_fallback.js
@@ -0,0 +1,908 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let h2Port;
+let h3Port;
+let trrServer;
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+add_setup(async function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT_NO_RESPONSE");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ trr_test_setup();
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR first
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
+ Services.prefs.clearUserPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed"
+ );
+ Services.prefs.clearUserPref("network.dns.httpssvc.reset_exclustion_list");
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout"
+ );
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ Services.prefs.clearUserPref("network.http.http3.backup_timer_delay");
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref(
+ "network.http.http3.parallel_fallback_conn_limit"
+ );
+ if (trrServer) {
+ await trrServer.stop();
+ }
+ });
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags, delay) {
+ // eslint-disable-next-line no-async-promise-executor
+ return new Promise(async resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ if (delay) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, delay));
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+let CheckOnlyHttp2Listener = function () {};
+
+CheckOnlyHttp2Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h2");
+
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+ Assert.ok(routed === "0" || routed === "NA");
+ this.finish();
+ },
+};
+
+async function fast_fallback_test() {
+ let result = 1;
+ // We need to loop here because we need to wait for AltSvc storage to
+ // to be started.
+ // We also do not have a way to verify that HTTP3 has been tried, because
+ // the fallback is automatic, so try a couple of times.
+ do {
+ // We need to close HTTP2 connections, otherwise our connection pooling
+ // will dispatch the request over already existing HTTP2 connection.
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ let chan = makeChan(`https://foo.example.com:${h2Port}/`);
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+ result++;
+ } while (result < 3);
+}
+
+// Test the case when speculative connection is enabled. In this case, when the
+// backup connection is ready, the http transaction is still in pending
+// queue because the h3 connection is never ready to accept transactions.
+add_task(async function test_fast_fallback_with_speculative_connection() {
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ // Set AltSvc to point to not existing HTTP3 server on port 443
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "foo.example.com;h3-29=:" + h3Port
+ );
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+
+ await fast_fallback_test();
+});
+
+// Test the case when speculative connection is disabled. In this case, when the
+// back connection is ready, the http transaction is already activated,
+// but the socket is not ready to write.
+add_task(async function test_fast_fallback_without_speculative_connection() {
+ // Make sure the h3 connection created by the previous test is cleared.
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ // Clear the h3 excluded list, otherwise the Alt-Svc mapping will not be used.
+ Services.obs.notifyObservers(null, "network:reset-http3-excluded-list");
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+
+ await fast_fallback_test();
+
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+});
+
+// Test when echConfig is disabled and we have https rr for http3. We use a
+// longer timeout in this test, so when fast fallback timer is triggered, the
+// http transaction is already activated.
+add_task(async function testFastfallback() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 1000
+ );
+
+ await trrServer.registerDoHAnswers("test.fastfallback.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fastfallback1.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.fastfallback2.com",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fastfallback1.com", "A", {
+ answers: [
+ {
+ name: "test.fastfallback1.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fastfallback2.com", "A", {
+ answers: [
+ {
+ name: "test.fastfallback2.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let chan = makeChan(`https://test.fastfallback.com/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ await trrServer.stop();
+});
+
+// Like the previous test, but with a shorter timeout, so when fast fallback
+// timer is triggered, the http transaction is still in pending queue.
+add_task(async function testFastfallback1() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 10
+ );
+
+ await trrServer.registerDoHAnswers("test.fastfallback.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.fastfallback.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fastfallback1.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fastfallback.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.fastfallback2.org",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fastfallback1.org", "A", {
+ answers: [
+ {
+ name: "test.fastfallback1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fastfallback2.org", "A", {
+ answers: [
+ {
+ name: "test.fastfallback2.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let chan = makeChan(`https://test.fastfallback.org/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ await trrServer.stop();
+});
+
+// Test when echConfig is enabled, we can sucessfully fallback to the last
+// record.
+add_task(async function testFastfallbackWithEchConfig() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 50
+ );
+
+ await trrServer.registerDoHAnswers("test.ech.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.ech1.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.ech2.org",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.ech3.org",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.ech1.org", "A", {
+ answers: [
+ {
+ name: "test.ech1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.ech3.org", "A", {
+ answers: [
+ {
+ name: "test.ech3.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let chan = makeChan(`https://test.ech.org/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ await trrServer.stop();
+});
+
+// Test when echConfig is enabled, the connection should fail when not all
+// records have echConfig.
+add_task(async function testFastfallbackWithpartialEchConfig() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 50
+ );
+
+ await trrServer.registerDoHAnswers("test.partial_ech.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.partial_ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.partial_ech1.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.partial_ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.partial_ech2.org",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.partial_ech1.org", "A", {
+ answers: [
+ {
+ name: "test.partial_ech1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.partial_ech2.org", "A", {
+ answers: [
+ {
+ name: "test.partial_ech2.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let chan = makeChan(`https://test.partial_ech.org/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.stop();
+});
+
+add_task(async function testFastfallbackWithoutEchConfig() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 50
+ );
+
+ await trrServer.registerDoHAnswers("test.no_ech_h2.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.no_ech_h2.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.no_ech_h3.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.no_ech_h3.org", "A", {
+ answers: [
+ {
+ name: "test.no_ech_h3.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.no_ech_h2.org", "A", {
+ answers: [
+ {
+ name: "test.no_ech_h2.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let chan = makeChan(`https://test.no_ech_h2.org:${h2Port}/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ await trrServer.stop();
+});
+
+add_task(async function testH3FallbackWithMultipleTransactions() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ // Disable fast fallback.
+ Services.prefs.setIntPref(
+ "network.http.http3.parallel_fallback_conn_limit",
+ 0
+ );
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+
+ await trrServer.registerDoHAnswers("test.multiple_trans.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.multiple_trans.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.multiple_trans.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.multiple_trans.org", "A", {
+ answers: [
+ {
+ name: "test.multiple_trans.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let promises = [];
+ for (let i = 0; i < 2; ++i) {
+ let chan = makeChan(
+ `https://test.multiple_trans.org:${h2Port}/server-timing`
+ );
+ promises.push(channelOpenPromise(chan));
+ }
+
+ let res = await Promise.all(promises);
+ res.forEach(function (e) {
+ let [req] = e;
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+ });
+
+ await trrServer.stop();
+});
+
+add_task(async function testTwoFastFallbackTimers() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ Services.prefs.clearUserPref(
+ "network.http.http3.parallel_fallback_conn_limit"
+ );
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "foo.fallback.org;h3-29=:" + h3Port
+ );
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 10
+ );
+ Services.prefs.setIntPref("network.http.http3.backup_timer_delay", 100);
+
+ await trrServer.registerDoHAnswers("foo.fallback.org", "HTTPS", {
+ answers: [
+ {
+ name: "foo.fallback.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "foo.fallback.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("foo.fallback.org", "A", {
+ answers: [
+ {
+ name: "foo.fallback.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ // Test the case that http3 backup timer is triggered after
+ // fast fallback timer or HTTPS RR.
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 10
+ );
+ Services.prefs.setIntPref("network.http.http3.backup_timer_delay", 100);
+
+ async function createChannelAndStartTest() {
+ let chan = makeChan(`https://foo.fallback.org:${h2Port}/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+ }
+
+ await createChannelAndStartTest();
+
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ Services.obs.notifyObservers(null, "network:reset-http3-excluded-list");
+ Services.dns.clearCache(true);
+
+ // Do the same test again, but with a different configuration.
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 100
+ );
+ Services.prefs.setIntPref("network.http.http3.backup_timer_delay", 10);
+
+ await createChannelAndStartTest();
+
+ await trrServer.stop();
+});
+
+add_task(async function testH3FastFallbackWithMultipleTransactions() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ Services.prefs.clearUserPref(
+ "network.http.http3.parallel_fallback_conn_limit"
+ );
+
+ Services.prefs.setIntPref("network.http.http3.backup_timer_delay", 500);
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "test.multiple_fallback_trans.org;h3-29=:" + h3Port
+ );
+
+ await trrServer.registerDoHAnswers("test.multiple_fallback_trans.org", "A", {
+ answers: [
+ {
+ name: "test.multiple_fallback_trans.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let promises = [];
+ for (let i = 0; i < 3; ++i) {
+ let chan = makeChan(
+ `https://test.multiple_fallback_trans.org:${h2Port}/server-timing`
+ );
+ if (i == 0) {
+ promises.push(channelOpenPromise(chan));
+ } else {
+ promises.push(channelOpenPromise(chan, null, 500));
+ }
+ }
+
+ let res = await Promise.all(promises);
+ res.forEach(function (e) {
+ let [req] = e;
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+ });
+
+ await trrServer.stop();
+});
+
+add_task(async function testFastfallbackToTheSameRecord() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 1000
+ );
+
+ await trrServer.registerDoHAnswers("test.ech.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.ech1.org",
+ values: [
+ { key: "alpn", value: ["h3-29", "h2"] },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.ech1.org", "A", {
+ answers: [
+ {
+ name: "test.ech1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let chan = makeChan(`https://test.ech.org/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_http3_fatal_stream_error.js b/netwerk/test/unit/test_http3_fatal_stream_error.js
new file mode 100644
index 0000000000..4c0b41089a
--- /dev/null
+++ b/netwerk/test/unit/test_http3_fatal_stream_error.js
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+let httpsUri;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.disableIPv6");
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ Services.prefs.clearUserPref("network.http.http3.backup_timer_delay");
+ dump("cleanup done\n");
+});
+
+let Http3FailedListener = function () {};
+
+Http3FailedListener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.amount += cnt;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ if (Components.isSuccessCode(status)) {
+ // This is still HTTP2 connection
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h2");
+ this.finish(false);
+ } else {
+ Assert.equal(status, Cr.NS_ERROR_NET_PARTIAL_TRANSFER);
+ this.finish(true);
+ }
+ },
+};
+
+function makeChan() {
+ let chan = NetUtil.newChannel({
+ uri: httpsUri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function altsvcSetupPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+add_task(async function test_fatal_error() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+
+ let h3Port = Services.env.get("MOZHTTP3_PORT_FAILED");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "foo.example.com;h3-29=:" + h3Port
+ );
+ Services.prefs.setIntPref("network.http.http3.backup_timer_delay", 0);
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ httpsUri = "https://foo.example.com:" + h2Port + "/";
+});
+
+add_task(async function test_fatal_stream_error() {
+ let result = false;
+ // We need to loop here because we need to wait for AltSvc storage to
+ // to be started.
+ do {
+ // We need to close HTTP2 connections, otherwise our connection pooling
+ // will dispatch the request over already existing HTTP2 connection.
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ let chan = makeChan();
+ let listener = new Http3FailedListener();
+ result = await altsvcSetupPromise(chan, listener);
+ } while (result === false);
+});
+
+let CheckOnlyHttp2Listener = function () {};
+
+CheckOnlyHttp2Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h2");
+ this.finish(false);
+ },
+};
+
+add_task(async function test_no_http3_after_error() {
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+});
+
+// also after all connections are closed.
+add_task(async function test_no_http3_after_error2() {
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+});
diff --git a/netwerk/test/unit/test_http3_large_post.js b/netwerk/test/unit/test_http3_large_post.js
new file mode 100644
index 0000000000..2ed8e51bb4
--- /dev/null
+++ b/netwerk/test/unit/test_http3_large_post.js
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3-29");
+});
+
+let Http3Listener = function (amount) {
+ this.amount = amount;
+};
+
+Http3Listener.prototype = {
+ expectedStatus: Cr.NS_OK,
+ amount: 0,
+ onProgressMaxNotificationCount: 0,
+ onProgressNotificationCount: 0,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIProgressEventSink"]),
+
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIProgressEventSink)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ onProgress(request, progress, progressMax) {
+ // we will get notifications for the request and the response.
+ if (progress === progressMax) {
+ this.onProgressMaxNotificationCount += 1;
+ }
+ // For a large upload there should be a multiple notifications.
+ this.onProgressNotificationCount += 1;
+ },
+
+ onStatus(request, status, statusArg) {},
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.equal(request.status, this.expectedStatus);
+ if (Components.isSuccessCode(this.expectedStatus)) {
+ Assert.equal(request.responseStatus, 200);
+ }
+ Assert.equal(
+ this.amount,
+ request.getResponseHeader("x-data-received-length")
+ );
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ // We should get 2 correctOnProgress, i.e. one for request and one for the response.
+ Assert.equal(this.onProgressMaxNotificationCount, 2);
+ if (this.amount > 500000) {
+ // 10 is an arbitrary number, but we cannot calculate exact number
+ // because it depends on timing.
+ Assert.ok(this.onProgressNotificationCount > 10);
+ }
+ this.finish();
+ },
+};
+
+function chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+function makeChan(uri, amount) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = generateContent(amount);
+ let uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+ chan.requestMethod = "POST";
+ return chan;
+}
+
+// Generate a post.
+function generateContent(size) {
+ let content = "";
+ for (let i = 0; i < size; i++) {
+ content += "0";
+ }
+ return content;
+}
+
+// Send a large post that can fit into a neqo stream buffer at once.
+// But the amount of data is larger than the necko's default stream
+// buffer side, therefore ReadSegments will be called multiple times.
+add_task(async function test_large_post() {
+ let amount = 1 << 16;
+ let listener = new Http3Listener(amount);
+ let chan = makeChan("https://foo.example.com/post", amount);
+ chan.notificationCallbacks = listener;
+ await chanPromise(chan, listener);
+});
+
+// Send a large post that cannot fit into a neqo stream buffer at once.
+// StreamWritable events will trigger sending more data when the buffer
+// space is freed
+add_task(async function test_large_post2() {
+ let amount = 1 << 23;
+ let listener = new Http3Listener(amount);
+ let chan = makeChan("https://foo.example.com/post", amount);
+ chan.notificationCallbacks = listener;
+ await chanPromise(chan, listener);
+});
+
+// Send a post in the same way viaduct does in bug 1749957.
+add_task(async function test_bug1749957_bug1750056() {
+ let amount = 200; // Tests the bug as long as it's non-zero.
+ let uri = "https://foo.example.com/post";
+ let listener = new Http3Listener(amount);
+
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ // https://searchfox.org/mozilla-central/rev/1920b17ac5988fcfec4e45e2a94478ebfbfc6f88/toolkit/components/viaduct/ViaductRequest.cpp#120-152
+ {
+ chan.requestMethod = "POST";
+ chan.setRequestHeader("content-length", "" + amount, /* aMerge = */ false);
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = generateContent(amount);
+ let uchan = chan.QueryInterface(Ci.nsIUploadChannel2);
+ uchan.explicitSetUploadStream(
+ stream,
+ /* aContentType = */ "",
+ /* aContentLength = */ -1,
+ "POST",
+ /* aStreamHasHeaders = */ false
+ );
+ }
+
+ chan.notificationCallbacks = listener;
+ await chanPromise(chan, listener);
+});
diff --git a/netwerk/test/unit/test_http3_large_post_telemetry.js b/netwerk/test/unit/test_http3_large_post_telemetry.js
new file mode 100644
index 0000000000..33ad4b7d21
--- /dev/null
+++ b/netwerk/test/unit/test_http3_large_post_telemetry.js
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+let indexes_10_100 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 27, 30,
+ 33, 37, 41, 46, 51, 57, 63, 70, 78, 87, 97, 108, 120, 133, 148, 165, 184, 205,
+ 228, 254, 282, 314, 349, 388, 431, 479, 533, 593, 659, 733, 815, 906, 1008,
+ 1121, 1247, 1387, 1542, 1715, 1907, 2121, 2359, 2623, 2917, 3244, 3607, 4011,
+ 4460, 4960, 5516, 6134, 6821, 7585, 8435, 9380, 10431, 11600, 12900, 14345,
+ 15952, 17739, 19727, 21937, 24395, 27129, 30169, 33549, 37308, 41488, 46137,
+ 51307, 57056, 63449, 70559, 78465, 87257, 97035, 107908, 120000,
+];
+
+let indexes_gt_100 = [
+ 0, 30000, 30643, 31300, 31971, 32657, 33357, 34072, 34803, 35549, 36311,
+ 37090, 37885, 38697, 39527, 40375, 41241, 42125, 43028, 43951, 44894, 45857,
+ 46840, 47845, 48871, 49919, 50990, 52084, 53201, 54342, 55507, 56697, 57913,
+ 59155, 60424, 61720, 63044, 64396, 65777, 67188, 68629, 70101, 71604, 73140,
+ 74709, 76311, 77948, 79620, 81327, 83071, 84853, 86673, 88532, 90431, 92370,
+ 94351, 96374, 98441, 100552, 102708, 104911, 107161, 109459, 111806, 114204,
+ 116653, 119155, 121710, 124320, 126986, 129709, 132491, 135332, 138234,
+ 141199, 144227, 147320, 150479, 153706, 157002, 160369, 163808, 167321,
+ 170909, 174574, 178318, 182142, 186048, 190038, 194114, 198277, 202529,
+ 206872, 211309, 215841, 220470, 225198, 230028, 234961, 240000,
+];
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+ Services.prefs.clearUserPref(
+ "toolkit.telemetry.testing.overrideProductsCheck"
+ );
+});
+
+add_task(async function setup() {
+ // Enable the collection (during test) for all products so even products
+ // that don't collect the data will be able to run the test without failure.
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ await http3_setup_tests("h3-29");
+});
+
+let Http3Listener = function () {};
+
+Http3Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ this.finish();
+ },
+};
+
+function chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+function makeChan(uri, amount) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = generateContent(amount);
+ let uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+ chan.requestMethod = "POST";
+ return chan;
+}
+
+// Generate a post.
+function generateContent(size) {
+ let content = "";
+ for (let i = 0; i < size; i++) {
+ content += "0";
+ }
+ return content;
+}
+
+async function test_large_post(amount, hist_name, key, indexes) {
+ let hist = TelemetryTestUtils.getAndClearKeyedHistogram(hist_name);
+
+ let listener = new Http3Listener();
+ listener.amount = amount;
+ let chan = makeChan("https://foo.example.com/post", amount);
+ let tchan = chan.QueryInterface(Ci.nsITimedChannel);
+ tchan.timingEnabled = true;
+ await chanPromise(chan, listener);
+
+ let time = (tchan.responseStartTime - tchan.requestStartTime) / 1000;
+ let i = 0;
+ while (i < indexes.length && time > indexes[i + 1]) {
+ i += 1;
+ }
+ TelemetryTestUtils.assertKeyedHistogramValue(hist, key, indexes[i], 1);
+}
+
+add_task(async function test_11M() {
+ await test_large_post(
+ 11 * (1 << 20),
+ "HTTP3_UPLOAD_TIME_10M_100M",
+ "uses_http3_10_50",
+ indexes_10_100
+ );
+});
+
+add_task(async function test_51M() {
+ await test_large_post(
+ 51 * (1 << 20),
+ "HTTP3_UPLOAD_TIME_10M_100M",
+ "uses_http3_50_100",
+ indexes_10_100
+ );
+});
+
+add_task(async function test_101M() {
+ await test_large_post(
+ 101 * (1 << 20),
+ "HTTP3_UPLOAD_TIME_GT_100M",
+ "uses_http3",
+ indexes_gt_100
+ );
+});
diff --git a/netwerk/test/unit/test_http3_perf.js b/netwerk/test/unit/test_http3_perf.js
new file mode 100644
index 0000000000..0a813cdf19
--- /dev/null
+++ b/netwerk/test/unit/test_http3_perf.js
@@ -0,0 +1,262 @@
+"use strict";
+
+// This test is run as part of the perf tests which require the metadata.
+/* exported perfMetadata */
+var perfMetadata = {
+ owner: "Network Team",
+ name: "http3 raw",
+ description:
+ "XPCShell tests that verifies the lib integration against a local server",
+ options: {
+ default: {
+ perfherder: true,
+ perfherder_metrics: [
+ {
+ name: "speed",
+ unit: "bps",
+ },
+ ],
+ xpcshell_cycles: 13,
+ verbose: true,
+ try_platform: ["linux", "mac"],
+ },
+ },
+ tags: ["network", "http3", "quic"],
+};
+
+var performance = performance || {};
+performance.now = (function () {
+ return (
+ performance.now ||
+ performance.mozNow ||
+ performance.msNow ||
+ performance.oNow ||
+ performance.webkitNow ||
+ Date.now
+ );
+})();
+
+let h3Route;
+let httpsOrigin;
+let h3AltSvc;
+
+let prefs;
+
+let tests = [
+ // This test must be the first because it setsup alt-svc connection, that
+ // other tests use.
+ test_https_alt_svc,
+ test_download,
+ testsDone,
+];
+
+let current_test = 0;
+
+function run_next_test() {
+ if (current_test < tests.length) {
+ dump("starting test number " + current_test + "\n");
+ tests[current_test]();
+ current_test++;
+ }
+}
+
+// eslint-disable-next-line no-unused-vars
+function run_test() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Services.prefs;
+
+ prefs.setBoolPref("network.http.http3.enable", true);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ // We always resolve elements of localDomains as it's hardcoded without the
+ // following pref:
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ // The certificate for the http3server server is for foo.example.com and
+ // is signed by http2-ca.pem so add that cert to the trust list as a
+ // signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ httpsOrigin = "https://foo.example.com:" + h2Port + "/";
+
+ run_next_test();
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+let Http3CheckListener = function () {};
+
+Http3CheckListener.prototype = {
+ onDataAvailableFired: false,
+ expectedRoute: "",
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ dump("status is " + status + "\n");
+ // Assert.equal(status, Cr.NS_OK);
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ Assert.equal(routed, this.expectedRoute);
+
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ Assert.equal(this.onDataAvailableFired, true);
+ },
+};
+
+let WaitForHttp3Listener = function () {};
+
+WaitForHttp3Listener.prototype = new Http3CheckListener();
+
+WaitForHttp3Listener.prototype.uri = "";
+WaitForHttp3Listener.prototype.h3AltSvc = "";
+
+WaitForHttp3Listener.prototype.onStopRequest = function testOnStopRequest(
+ request,
+ status
+) {
+ Assert.equal(status, Cr.NS_OK);
+ let routed = "NA";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+
+ if (routed == this.expectedRoute) {
+ Assert.equal(routed, this.expectedRoute); // always true, but a useful log
+
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ run_next_test();
+ } else {
+ dump("poll later for alt svc mapping\n");
+ do_test_pending();
+ do_timeout(500, () => {
+ doTest(this.uri, this.expectedRoute, this.h3AltSvc);
+ });
+ }
+
+ do_test_finished();
+};
+
+function doTest(uri, expectedRoute, altSvc) {
+ let chan = makeChan(uri);
+ let listener = new WaitForHttp3Listener();
+ listener.uri = uri;
+ listener.expectedRoute = expectedRoute;
+ listener.h3AltSvc = altSvc;
+ chan.setRequestHeader("x-altsvc", altSvc, false);
+ chan.asyncOpen(listener);
+}
+
+// Test Alt-Svc for http3.
+// H2 server returns alt-svc=h3-29=:h3port
+function test_https_alt_svc() {
+ dump("test_https_alt_svc()\n");
+
+ do_test_pending();
+ doTest(httpsOrigin + "http3-test", h3Route, h3AltSvc);
+}
+
+let PerfHttp3Listener = function () {};
+
+PerfHttp3Listener.prototype = new Http3CheckListener();
+PerfHttp3Listener.prototype.amount = 0;
+PerfHttp3Listener.prototype.bytesRead = 0;
+PerfHttp3Listener.prototype.startTime = 0;
+
+PerfHttp3Listener.prototype.onStartRequest = function testOnStartRequest(
+ request
+) {
+ this.startTime = performance.now();
+ Http3CheckListener.prototype.onStartRequest.call(this, request);
+};
+
+PerfHttp3Listener.prototype.onDataAvailable = function testOnStopRequest(
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.bytesRead += cnt;
+ Http3CheckListener.prototype.onDataAvailable.call(
+ this,
+ request,
+ stream,
+ off,
+ cnt
+ );
+};
+
+PerfHttp3Listener.prototype.onStopRequest = function testOnStopRequest(
+ request,
+ status
+) {
+ let stopTime = performance.now();
+ Http3CheckListener.prototype.onStopRequest.call(this, request, status);
+ if (this.bytesRead != this.amount) {
+ dump("Wrong number of bytes...");
+ } else {
+ let speed = (this.bytesRead * 1000) / (stopTime - this.startTime);
+ info("perfMetrics", { speed });
+ }
+ run_next_test();
+ do_test_finished();
+};
+
+function test_download() {
+ dump("test_download()\n");
+
+ let listener = new PerfHttp3Listener();
+ listener.expectedRoute = h3Route;
+ listener.amount = 1024 * 1024;
+ let chan = makeChan(httpsOrigin + listener.amount.toString());
+ chan.asyncOpen(listener);
+ do_test_pending();
+}
+
+function testsDone() {
+ prefs.clearUserPref("network.http.http3.enable");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ dump("testDone\n");
+ do_test_pending();
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_http3_prio_disabled.js b/netwerk/test/unit/test_http3_prio_disabled.js
new file mode 100644
index 0000000000..b73ca98709
--- /dev/null
+++ b/netwerk/test/unit/test_http3_prio_disabled.js
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 test file can be run directly as a part of parent/main process
+// or indirectly from the wrapper test file as a part of child/content process
+
+// need to get access to helper functions/structures
+// load ensures
+// * testing environment is available (ie Assert.ok())
+/*global inChildProcess, test_flag_priority */
+load("../unit/test_http3_prio_helpers.js");
+
+// direct call to this test file should cleanup after itself
+// otherwise the wrapper will handle
+if (!inChildProcess()) {
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.priority");
+ http3_clear_prefs();
+ });
+}
+
+// setup once, before tests
+add_task(async function setup() {
+ // wrapper handles when testing as content process for pref change
+ if (!inChildProcess()) {
+ await http3_setup_tests("h3-29");
+ }
+});
+
+// tests various flags when priority has been disabled on variable incremental
+// this function should only be called the preferences priority disabled
+async function test_http3_prio_disabled(incremental) {
+ await test_flag_priority("disabled (none)", null, null, null, null); // default-test
+ await test_flag_priority(
+ "disabled (urgent_start)",
+ Ci.nsIClassOfService.UrgentStart,
+ null,
+ incremental,
+ null
+ );
+ await test_flag_priority(
+ "disabled (leader)",
+ Ci.nsIClassOfService.Leader,
+ null,
+ incremental,
+ null
+ );
+ await test_flag_priority(
+ "disabled (unblocked)",
+ Ci.nsIClassOfService.Unblocked,
+ null,
+ incremental,
+ null
+ );
+ await test_flag_priority(
+ "disabled (follower)",
+ Ci.nsIClassOfService.Follower,
+ null,
+ incremental,
+ null
+ );
+ await test_flag_priority(
+ "disabled (speculative)",
+ Ci.nsIClassOfService.Speculative,
+ null,
+ incremental,
+ null
+ );
+ await test_flag_priority(
+ "disabled (background)",
+ Ci.nsIClassOfService.Background,
+ null,
+ incremental,
+ null
+ );
+ await test_flag_priority(
+ "disabled (background)",
+ Ci.nsIClassOfService.Tail,
+ null,
+ incremental,
+ null
+ );
+}
+
+// run tests after setup
+
+// test that various urgency flags and incremental=true don't propogate to header
+// when priority setting is disabled
+add_task(async function test_http3_prio_disabled_inc_true() {
+ // wrapper handles when testing as content process for pref change
+ if (!inChildProcess()) {
+ Services.prefs.setBoolPref("network.http.http3.priority", false);
+ }
+ await test_http3_prio_disabled(true);
+});
+
+// test that various urgency flags and incremental=false don't propogate to header
+// when priority setting is disabled
+add_task(async function test_http3_prio_disabled_inc_false() {
+ // wrapper handles when testing as content process for pref change
+ if (!inChildProcess()) {
+ Services.prefs.setBoolPref("network.http.http3.priority", false);
+ }
+ await test_http3_prio_disabled(false);
+});
diff --git a/netwerk/test/unit/test_http3_prio_enabled.js b/netwerk/test/unit/test_http3_prio_enabled.js
new file mode 100644
index 0000000000..6dd30c590a
--- /dev/null
+++ b/netwerk/test/unit/test_http3_prio_enabled.js
@@ -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/. */
+
+// this test file can be run directly as a part of parent/main process
+// or indirectly from the wrapper test file as a part of child/content process
+
+// need to get access to helper functions/structures
+// load ensures
+// * testing environment is available (ie Assert.ok())
+/*global inChildProcess, test_flag_priority */
+load("../unit/test_http3_prio_helpers.js");
+
+// direct call to this test file should cleanup after itself
+// otherwise the wrapper will handle
+if (!inChildProcess()) {
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.priority");
+ http3_clear_prefs();
+ });
+}
+
+// setup once, before tests
+add_task(async function setup() {
+ // wrapper handles when testing as content process for pref change
+ if (!inChildProcess()) {
+ await http3_setup_tests("h3-29");
+ }
+});
+
+// tests various flags when priority has been enabled on variable incremental
+// this function should only be called the preferences priority disabled
+async function test_http3_prio_enabled(incremental) {
+ await test_flag_priority("enabled (none)", null, "u=4", null, false); // default-test
+ await test_flag_priority(
+ "enabled (urgent_start)",
+ Ci.nsIClassOfService.UrgentStart,
+ "u=1",
+ incremental,
+ incremental
+ );
+ await test_flag_priority(
+ "enabled (leader)",
+ Ci.nsIClassOfService.Leader,
+ "u=2",
+ incremental,
+ incremental
+ );
+
+ // if priority-urgency and incremental are both default values
+ // then we shouldn't expect to see the priority header at all
+ // hence when:
+ // incremental=true -> we expect incremental
+ // incremental=false -> we expect null
+ await test_flag_priority(
+ "enabled (unblocked)",
+ Ci.nsIClassOfService.Unblocked,
+ null,
+ incremental,
+ incremental ? incremental : null
+ );
+
+ await test_flag_priority(
+ "enabled (follower)",
+ Ci.nsIClassOfService.Follower,
+ "u=4",
+ incremental,
+ incremental
+ );
+ await test_flag_priority(
+ "enabled (speculative)",
+ Ci.nsIClassOfService.Speculative,
+ "u=6",
+ incremental,
+ incremental
+ );
+ await test_flag_priority(
+ "enabled (background)",
+ Ci.nsIClassOfService.Background,
+ "u=6",
+ incremental,
+ incremental
+ );
+ await test_flag_priority(
+ "enabled (background)",
+ Ci.nsIClassOfService.Tail,
+ "u=6",
+ incremental,
+ incremental
+ );
+}
+
+// with priority enabled: test urgency flags with both incremental enabled and disabled
+add_task(async function test_http3_prio_enabled_incremental_true() {
+ // wrapper handles when testing as content process for pref change
+ if (!inChildProcess()) {
+ Services.prefs.setBoolPref("network.http.http3.priority", true);
+ }
+ await test_http3_prio_enabled(true);
+});
+
+add_task(async function test_http3_prio_enabled_incremental_false() {
+ // wrapper handles when testing as content process for pref change
+ if (!inChildProcess()) {
+ Services.prefs.setBoolPref("network.http.http3.priority", true);
+ }
+ await test_http3_prio_enabled(false);
+});
diff --git a/netwerk/test/unit/test_http3_prio_helpers.js b/netwerk/test/unit/test_http3_prio_helpers.js
new file mode 100644
index 0000000000..c1f6d06bcb
--- /dev/null
+++ b/netwerk/test/unit/test_http3_prio_helpers.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// uses head_http3.js, which uses http2-ca.pem
+"use strict";
+
+/* exported inChildProcess, test_flag_priority */
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+let Http3Listener = function (
+ closure,
+ expected_priority,
+ expected_incremental,
+ context
+) {
+ this._closure = closure;
+ this._expected_priority = expected_priority;
+ this._expected_incremental = expected_incremental;
+ this._context = context;
+};
+
+// string -> [string, bool]
+// "u=3,i" -> ["u=3", true]
+function parse_priority_response_header(priority) {
+ const priority_array = priority.split(",");
+
+ // parse for urgency string
+ const urgency = priority_array.find(element => element.includes("u="));
+
+ // parse for incremental bool
+ const incremental = !!priority_array.find(element => element == "i");
+
+ return [urgency ? urgency : null, incremental];
+}
+
+Http3Listener.prototype = {
+ resumed: false,
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(request.responseStatus, 200);
+
+ let secinfo = request.securityInfo;
+ Assert.equal(secinfo.resumed, this.resumed);
+ Assert.ok(secinfo.serverCert != null);
+
+ // check priority urgency and incremental from response header
+ let priority_urgency = null;
+ let incremental = null;
+ try {
+ const prh = request.getResponseHeader("priority-mirror");
+ [priority_urgency, incremental] = parse_priority_response_header(prh);
+ } catch (e) {
+ console.log("Failed to get priority-mirror from response header");
+ }
+ Assert.equal(priority_urgency, this._expected_priority, this._context);
+ Assert.equal(incremental, this._expected_incremental, this._context);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+
+ try {
+ this._closure();
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+function make_channel(url) {
+ var request = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+ request.QueryInterface(Ci.nsIHttpChannel);
+ return request;
+}
+
+async function test_flag_priority(
+ context,
+ flag,
+ expected_priority,
+ incremental,
+ expected_incremental
+) {
+ var chan = make_channel("https://foo.example.com/priority_mirror");
+ var cos = chan.QueryInterface(Ci.nsIClassOfService);
+
+ // configure the channel with flags
+ if (flag != null) {
+ cos.addClassFlags(flag);
+ }
+
+ // configure the channel with incremental
+ if (incremental != null) {
+ cos.incremental = incremental;
+ }
+
+ await new Promise(resolve =>
+ chan.asyncOpen(
+ new Http3Listener(
+ resolve,
+ expected_priority,
+ expected_incremental,
+ context
+ )
+ )
+ );
+}
diff --git a/netwerk/test/unit/test_http3_server.js b/netwerk/test/unit/test_http3_server.js
new file mode 100644
index 0000000000..3daf9cfa3c
--- /dev/null
+++ b/netwerk/test/unit/test_http3_server.js
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let h2Port;
+let h3Port;
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+add_setup(async function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT_PROXY");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ trr_test_setup();
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.disablePrefetch");
+ await trrServer.stop();
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns;
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ resolve([req, buffer]);
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+// Use NodeHTTPServer to create an HTTP server and test if the Http/3 server
+// can act as a reverse proxy.
+add_task(async function testHttp3ServerAsReverseProxy() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.h3_example.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.h3_example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.h3_example.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.h3_example.com", "A", {
+ answers: [
+ {
+ name: "test.h3_example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.h3_example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let server = new NodeHTTPServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ if (req.method === "GET") {
+ resp.writeHead(200);
+ resp.end("got GET request");
+ } else if (req.method === "POST") {
+ let received = "";
+ req.on("data", function receivePostData(chunk) {
+ received += chunk.toString();
+ });
+ req.on("end", function finishPost() {
+ resp.writeHead(200);
+ resp.end(received);
+ });
+ }
+ });
+
+ // Tell the Http/3 server which port to forward requests.
+ let chan = makeChan(`https://test.h3_example.com/port?${server.port()}`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+
+ // Test GET method.
+ chan = makeChan(`https://test.h3_example.com/test`);
+ let [req, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ Assert.equal(req.protocolVersion, "h3-29");
+ Assert.equal(buf, "got GET request");
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = "b".repeat(500);
+
+ // Test POST method.
+ chan = makeChan(`https://test.h3_example.com/test`);
+ chan
+ .QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(stream, "text/plain", stream.available());
+ chan.requestMethod = "POST";
+
+ [req, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ Assert.equal(req.protocolVersion, "h3-29");
+ Assert.equal(buf, stream.data);
+
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_http3_server_not_existing.js b/netwerk/test/unit/test_http3_server_not_existing.js
new file mode 100644
index 0000000000..b2b5518974
--- /dev/null
+++ b/netwerk/test/unit/test_http3_server_not_existing.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+let httpsUri;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.disableIPv6");
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ Services.prefs.clearUserPref("network.http.http3.backup_timer_delay");
+ dump("cleanup done\n");
+});
+
+function makeChan() {
+ let chan = NetUtil.newChannel({
+ uri: httpsUri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function altsvcSetupPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+add_task(async function test_fatal_error() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ // Set AltSvc to point to not existing HTTP3 server on port 443
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "foo.example.com;h3-29=:443"
+ );
+ Services.prefs.setIntPref("network.http.http3.backup_timer_delay", 0);
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ httpsUri = "https://foo.example.com:" + h2Port + "/";
+});
+
+add_task(async function test_fatal_stream_error() {
+ let result = 1;
+ // We need to loop here because we need to wait for AltSvc storage to
+ // to be started.
+ // We also do not have a way to verify that HTTP3 has been tried, because
+ // the fallback is automatic, so try a couple of times.
+ do {
+ // We need to close HTTP2 connections, otherwise our connection pooling
+ // will dispatch the request over already existing HTTP2 connection.
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+ result++;
+ } while (result < 5);
+});
+
+let CheckOnlyHttp2Listener = function () {};
+
+CheckOnlyHttp2Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h2");
+ this.finish();
+ },
+};
+
+add_task(async function test_no_http3_after_error() {
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+});
+
+// also after all connections are closed.
+add_task(async function test_no_http3_after_error2() {
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+ let chan = makeChan();
+ let listener = new CheckOnlyHttp2Listener();
+ await altsvcSetupPromise(chan, listener);
+});
diff --git a/netwerk/test/unit/test_http3_trans_close.js b/netwerk/test/unit/test_http3_trans_close.js
new file mode 100644
index 0000000000..0bbb183aab
--- /dev/null
+++ b/netwerk/test/unit/test_http3_trans_close.js
@@ -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/. */
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3-29");
+});
+
+let Http3Listener = function () {};
+
+Http3Listener.prototype = {
+ expectedAmount: 0,
+ expectedStatus: Cr.NS_OK,
+ amount: 0,
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.equal(request.status, this.expectedStatus);
+ if (Components.isSuccessCode(this.expectedStatus)) {
+ Assert.equal(request.responseStatus, 200);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.amount += cnt;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3-29");
+ Assert.equal(this.amount, this.expectedAmount);
+
+ this.finish();
+ },
+};
+
+function chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+add_task(async function test_response_without_body() {
+ let chan = makeChan("https://foo.example.com/no_body");
+ let listener = new Http3Listener();
+ listener.expectedAmount = 0;
+ await chanPromise(chan, listener);
+});
+
+add_task(async function test_response_without_content_length() {
+ let chan = makeChan("https://foo.example.com/no_content_length");
+ let listener = new Http3Listener();
+ listener.expectedAmount = 4000;
+ await chanPromise(chan, listener);
+});
+
+add_task(async function test_content_length_smaller_than_data_len() {
+ let chan = makeChan("https://foo.example.com/content_length_smaller");
+ let listener = new Http3Listener();
+ // content-lentgth is 4000, but data length is 8000.
+ // We should return an error here - bug 1670086.
+ listener.expectedAmount = 4000;
+ await chanPromise(chan, listener);
+});
diff --git a/netwerk/test/unit/test_http3_version1.js b/netwerk/test/unit/test_http3_version1.js
new file mode 100644
index 0000000000..65f4eef906
--- /dev/null
+++ b/netwerk/test/unit/test_http3_version1.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+});
+
+let httpsUri;
+
+add_task(async function pre_setup() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ httpsUri = "https://foo.example.com:" + h2Port + "/";
+ Services.prefs.setBoolPref("network.http.http3.support_version1", true);
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3");
+});
+
+function chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish() {
+ resolve();
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+function makeH2Chan() {
+ let chan = NetUtil.newChannel({
+ uri: httpsUri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+let Http3Listener = function () {};
+
+Http3Listener.prototype = {
+ version1enabled: "",
+
+ onStartRequest: function testOnStartRequest(request) {},
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ if (this.version1enabled) {
+ Assert.equal(httpVersion, "h3");
+ } else {
+ Assert.equal(httpVersion, "h2");
+ }
+
+ this.finish();
+ },
+};
+
+add_task(async function test_version1_enabled_1() {
+ Services.prefs.setBoolPref("network.http.http3.support_version1", true);
+ let listener = new Http3Listener();
+ listener.version1enabled = true;
+ let chan = makeH2Chan("https://foo.example.com/");
+ await chanPromise(chan, listener);
+});
+
+add_task(async function test_version1_disabled() {
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ Services.prefs.setBoolPref("network.http.http3.support_version1", false);
+ let listener = new Http3Listener();
+ listener.version1enabled = false;
+ let chan = makeH2Chan("https://foo.example.com/");
+ await chanPromise(chan, listener);
+});
+
+add_task(async function test_version1_enabled_2() {
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ Services.prefs.setBoolPref("network.http.http3.support_version1", true);
+ let listener = new Http3Listener();
+ listener.version1enabled = true;
+ let chan = makeH2Chan("https://foo.example.com/");
+ await chanPromise(chan, listener);
+});
diff --git a/netwerk/test/unit/test_httpResponseTimeout.js b/netwerk/test/unit/test_httpResponseTimeout.js
new file mode 100644
index 0000000000..c689abc2b5
--- /dev/null
+++ b/netwerk/test/unit/test_httpResponseTimeout.js
@@ -0,0 +1,162 @@
+/* -*- Mode: javascript; 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/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var baseURL;
+const kResponseTimeoutPref = "network.http.response.timeout";
+const kResponseTimeout = 1;
+const kShortLivedKeepalivePref =
+ "network.http.tcp_keepalive.short_lived_connections";
+const kLongLivedKeepalivePref =
+ "network.http.tcp_keepalive.long_lived_connections";
+
+const prefService = Services.prefs;
+
+var server = new HttpServer();
+
+function TimeoutListener(expectResponse) {
+ this.expectResponse = expectResponse;
+}
+
+TimeoutListener.prototype = {
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream) {},
+
+ onStopRequest(request, status) {
+ if (this.expectResponse) {
+ Assert.equal(status, Cr.NS_OK);
+ } else {
+ Assert.equal(status, Cr.NS_ERROR_NET_TIMEOUT);
+ }
+
+ run_next_test();
+ },
+};
+
+function serverStopListener() {
+ do_test_finished();
+}
+
+function testTimeout(timeoutEnabled, expectResponse) {
+ // Set timeout pref.
+ if (timeoutEnabled) {
+ prefService.setIntPref(kResponseTimeoutPref, kResponseTimeout);
+ } else {
+ prefService.setIntPref(kResponseTimeoutPref, 0);
+ }
+
+ var chan = NetUtil.newChannel({
+ uri: baseURL,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ var listener = new TimeoutListener(expectResponse);
+ chan.asyncOpen(listener);
+}
+
+function testTimeoutEnabled() {
+ // Set a timeout value; expect a timeout and no response.
+ testTimeout(true, false);
+}
+
+function testTimeoutDisabled() {
+ // Set a timeout value of 0; expect a response.
+ testTimeout(false, true);
+}
+
+function testTimeoutDisabledByShortLivedKeepalives() {
+ // Enable TCP Keepalives for short lived HTTP connections.
+ prefService.setBoolPref(kShortLivedKeepalivePref, true);
+ prefService.setBoolPref(kLongLivedKeepalivePref, false);
+
+ // Try to set a timeout value, but expect a response without timeout.
+ testTimeout(true, true);
+}
+
+function testTimeoutDisabledByLongLivedKeepalives() {
+ // Enable TCP Keepalives for long lived HTTP connections.
+ prefService.setBoolPref(kShortLivedKeepalivePref, false);
+ prefService.setBoolPref(kLongLivedKeepalivePref, true);
+
+ // Try to set a timeout value, but expect a response without timeout.
+ testTimeout(true, true);
+}
+
+function testTimeoutDisabledByBothKeepalives() {
+ // Enable TCP Keepalives for short and long lived HTTP connections.
+ prefService.setBoolPref(kShortLivedKeepalivePref, true);
+ prefService.setBoolPref(kLongLivedKeepalivePref, true);
+
+ // Try to set a timeout value, but expect a response without timeout.
+ testTimeout(true, true);
+}
+
+function setup_tests() {
+ // Start tests with timeout enabled, i.e. disable TCP keepalives for HTTP.
+ // Reset pref in cleanup.
+ if (prefService.getBoolPref(kShortLivedKeepalivePref)) {
+ prefService.setBoolPref(kShortLivedKeepalivePref, false);
+ registerCleanupFunction(function () {
+ prefService.setBoolPref(kShortLivedKeepalivePref, true);
+ });
+ }
+ if (prefService.getBoolPref(kLongLivedKeepalivePref)) {
+ prefService.setBoolPref(kLongLivedKeepalivePref, false);
+ registerCleanupFunction(function () {
+ prefService.setBoolPref(kLongLivedKeepalivePref, true);
+ });
+ }
+
+ var tests = [
+ // Enable with a timeout value >0;
+ testTimeoutEnabled,
+ // Disable with a timeout value of 0;
+ testTimeoutDisabled,
+ // Disable by enabling TCP keepalive for short-lived HTTP connections.
+ testTimeoutDisabledByShortLivedKeepalives,
+ // Disable by enabling TCP keepalive for long-lived HTTP connections.
+ testTimeoutDisabledByLongLivedKeepalives,
+ // Disable by enabling TCP keepalive for both HTTP connection types.
+ testTimeoutDisabledByBothKeepalives,
+ ];
+
+ for (var i = 0; i < tests.length; i++) {
+ add_test(tests[i]);
+ }
+}
+
+function setup_http_server() {
+ // Start server; will be stopped at test cleanup time.
+ server.start(-1);
+ baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+ info("Using baseURL: " + baseURL);
+ server.registerPathHandler("/", function (metadata, response) {
+ // Wait until the timeout should have passed, then respond.
+ response.processAsync();
+
+ do_timeout((kResponseTimeout + 1) * 1000 /* ms */, function () {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.write("Hello world");
+ response.finish();
+ });
+ });
+ registerCleanupFunction(function () {
+ server.stop(serverStopListener);
+ });
+}
+
+function run_test() {
+ setup_http_server();
+
+ setup_tests();
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_http_408_retry.js b/netwerk/test/unit/test_http_408_retry.js
new file mode 100644
index 0000000000..16392ab4bf
--- /dev/null
+++ b/netwerk/test/unit/test_http_408_retry.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+async function loadURL(uri, flags) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+
+ return new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((req, buff) => resolve({ req, buff }), null, flags)
+ );
+ });
+}
+
+add_task(async function test() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ async function check408retry(server) {
+ info(`Testing ${server.constructor.name}`);
+ await server.execute(`global.server_name = "${server.constructor.name}";`);
+ if (
+ server.constructor.name == "NodeHTTPServer" ||
+ server.constructor.name == "NodeHTTPSServer"
+ ) {
+ await server.registerPathHandler("/test", (req, resp) => {
+ let oldSock = global.socket;
+ global.socket = resp.socket;
+ if (global.socket == oldSock) {
+ // This function is handled within the httpserver where setTimeout is
+ // available.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout, no-undef
+ setTimeout(
+ arg => {
+ arg.writeHead(408);
+ arg.end("stuff");
+ },
+ 1100,
+ resp
+ );
+ return;
+ }
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+ } else {
+ await server.registerPathHandler("/test", (req, resp) => {
+ global.socket = resp.socket;
+ if (!global.sent408) {
+ global.sent408 = true;
+ resp.writeHead(408);
+ resp.end("stuff");
+ return;
+ }
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+ }
+
+ async function load() {
+ let { req, buff } = await loadURL(
+ `${server.origin()}/test`,
+ CL_ALLOW_UNKNOWN_CL
+ );
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(buff, server.constructor.name);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannel).protocolVersion,
+ server.constructor.name == "NodeHTTP2Server" ? "h2" : "http/1.1"
+ );
+ }
+
+ info("first load");
+ await load();
+ info("second load");
+ await load();
+ }
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ check408retry
+ );
+});
diff --git a/netwerk/test/unit/test_http_headers.js b/netwerk/test/unit/test_http_headers.js
new file mode 100644
index 0000000000..b43cebea1f
--- /dev/null
+++ b/netwerk/test/unit/test_http_headers.js
@@ -0,0 +1,75 @@
+"use strict";
+
+function check_request_header(chan, name, value) {
+ var chanValue;
+ try {
+ chanValue = chan.getRequestHeader(name);
+ } catch (e) {
+ do_throw("Expected to find header '" + name + "' but didn't find it");
+ }
+ Assert.equal(chanValue, value);
+}
+
+function run_test() {
+ var chan = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ check_request_header(chan, "host", "www.mozilla.org");
+ check_request_header(chan, "Host", "www.mozilla.org");
+
+ chan.setRequestHeader("foopy", "bar", false);
+ check_request_header(chan, "foopy", "bar");
+
+ chan.setRequestHeader("foopy", "baz", true);
+ check_request_header(chan, "foopy", "bar, baz");
+
+ for (let i = 0; i < 100; ++i) {
+ chan.setRequestHeader("foopy" + i, i, false);
+ }
+
+ for (let i = 0; i < 100; ++i) {
+ check_request_header(chan, "foopy" + i, i);
+ }
+
+ var x = false;
+ try {
+ chan.setRequestHeader("foo:py", "baz", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x) {
+ do_throw("header with colon not rejected");
+ }
+
+ x = false;
+ try {
+ chan.setRequestHeader("foopy", "b\naz", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x) {
+ do_throw("header value with newline not rejected");
+ }
+
+ x = false;
+ try {
+ chan.setRequestHeader("foopy\u0080", "baz", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x) {
+ do_throw("header name with non-ASCII not rejected");
+ }
+
+ x = false;
+ try {
+ chan.setRequestHeader("foopy", "b\u0000az", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x) {
+ do_throw("header value with null-byte not rejected");
+ }
+}
diff --git a/netwerk/test/unit/test_http_server_timing.js b/netwerk/test/unit/test_http_server_timing.js
new file mode 100644
index 0000000000..c9d46797f3
--- /dev/null
+++ b/netwerk/test/unit/test_http_server_timing.js
@@ -0,0 +1,97 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-env node */
+
+"use strict";
+
+const responseServerTiming = [
+ { metric: "metric", duration: "123.4", description: "description" },
+ { metric: "metric2", duration: "456.78", description: "description1" },
+];
+const trailerServerTiming = [
+ { metric: "metric3", duration: "789.11", description: "description2" },
+ { metric: "metric4", duration: "1112.13", description: "description3" },
+];
+
+let port;
+
+let server;
+add_task(async function setup() {
+ server = new NodeHTTPServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ server.registerPathHandler("/", (req, res) => {
+ res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Content-Length", "12");
+ res.setHeader("Transfer-Encoding", "chunked");
+ res.setHeader("Trailer", "Server-Timing");
+ res.setHeader(
+ "Server-Timing",
+ "metric; dur=123.4; desc=description, metric2; dur=456.78; desc=description1"
+ );
+ res.write("data reached");
+ res.addTrailers({
+ "Server-Timing":
+ "metric3; dur=789.11; desc=description2, metric4; dur=1112.13; desc=description3",
+ });
+ res.end();
+ });
+ port = server.port();
+});
+
+// Test that secure origins can use server-timing, even with plain http
+add_task(async function test_localhost_origin() {
+ let chan = NetUtil.newChannel({
+ uri: `http://localhost:${port}/`,
+ loadUsingSystemPrincipal: true,
+ });
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((request, buffer) => {
+ let channel = request.QueryInterface(Ci.nsITimedChannel);
+ let headers = channel.serverTiming.QueryInterface(Ci.nsIArray);
+ ok(headers.length);
+
+ let expectedResult = responseServerTiming.concat(trailerServerTiming);
+ Assert.equal(headers.length, expectedResult.length);
+
+ for (let i = 0; i < expectedResult.length; i++) {
+ let header = headers.queryElementAt(i, Ci.nsIServerTiming);
+ Assert.equal(header.name, expectedResult[i].metric);
+ Assert.equal(header.description, expectedResult[i].description);
+ Assert.equal(header.duration, parseFloat(expectedResult[i].duration));
+ }
+ resolve();
+ }, null)
+ );
+ });
+});
+
+// Test that insecure origins can't use server timing.
+add_task(async function test_http_non_localhost() {
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+ });
+
+ let chan = NetUtil.newChannel({
+ uri: `http://example.org:${port}/`,
+ loadUsingSystemPrincipal: true,
+ });
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((request, buffer) => {
+ let channel = request.QueryInterface(Ci.nsITimedChannel);
+ let headers = channel.serverTiming.QueryInterface(Ci.nsIArray);
+ Assert.equal(headers.length, 0);
+ resolve();
+ }, null)
+ );
+ });
+});
diff --git a/netwerk/test/unit/test_http_sfv.js b/netwerk/test/unit/test_http_sfv.js
new file mode 100644
index 0000000000..4b7ec9893c
--- /dev/null
+++ b/netwerk/test/unit/test_http_sfv.js
@@ -0,0 +1,597 @@
+"use strict";
+
+const gService = Cc["@mozilla.org/http-sfv-service;1"].getService(
+ Ci.nsISFVService
+);
+
+add_task(async function test_sfv_bare_item() {
+ // tests bare item
+ let item_int = gService.newInteger(19);
+ Assert.equal(item_int.value, 19, "check bare item value");
+
+ let item_bool = gService.newBool(true);
+ Assert.equal(item_bool.value, true, "check bare item value");
+ item_bool.value = false;
+ Assert.equal(item_bool.value, false, "check bare item value");
+
+ let item_float = gService.newDecimal(145.45);
+ Assert.equal(item_float.value, 145.45);
+
+ let item_str = gService.newString("some_string");
+ Assert.equal(item_str.value, "some_string", "check bare item value");
+
+ let item_byte_seq = gService.newByteSequence("aGVsbG8=");
+ Assert.equal(item_byte_seq.value, "aGVsbG8=", "check bare item value");
+
+ let item_token = gService.newToken("*a");
+ Assert.equal(item_token.value, "*a", "check bare item value");
+});
+
+add_task(async function test_sfv_params() {
+ // test params
+ let params = gService.newParameters();
+ let bool_param = gService.newBool(false);
+ let int_param = gService.newInteger(15);
+ let decimal_param = gService.newDecimal(15.45);
+
+ params.set("bool_param", bool_param);
+ params.set("int_param", int_param);
+ params.set("decimal_param", decimal_param);
+
+ Assert.throws(
+ () => {
+ params.get("some_param");
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "must throw exception as key does not exist in parameters"
+ );
+ Assert.equal(
+ params.get("bool_param").QueryInterface(Ci.nsISFVBool).value,
+ false,
+ "get parameter by key and check its value"
+ );
+ Assert.equal(
+ params.get("int_param").QueryInterface(Ci.nsISFVInteger).value,
+ 15,
+ "get parameter by key and check its value"
+ );
+ Assert.equal(
+ params.get("decimal_param").QueryInterface(Ci.nsISFVDecimal).value,
+ 15.45,
+ "get parameter by key and check its value"
+ );
+ Assert.deepEqual(
+ params.keys(),
+ ["bool_param", "int_param", "decimal_param"],
+ "check that parameters contain all the expected keys"
+ );
+
+ params.delete("int_param");
+ Assert.deepEqual(
+ params.keys(),
+ ["bool_param", "decimal_param"],
+ "check that parameter has been deleted"
+ );
+
+ Assert.throws(
+ () => {
+ params.delete("some_param");
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "must throw exception upon attempt to delete by non-existing key"
+ );
+});
+
+add_task(async function test_sfv_inner_list() {
+ // create primitives for inner list
+ let item1_params = gService.newParameters();
+ item1_params.set("param_1", gService.newToken("*smth"));
+ let item1 = gService.newItem(gService.newDecimal(172.145865), item1_params);
+
+ let item2_params = gService.newParameters();
+ item2_params.set("param_1", gService.newBool(true));
+ item2_params.set("param_2", gService.newInteger(145454));
+ let item2 = gService.newItem(
+ gService.newByteSequence("weather"),
+ item2_params
+ );
+
+ // create inner list
+ let inner_list_params = gService.newParameters();
+ inner_list_params.set("inner_param", gService.newByteSequence("tests"));
+ let inner_list = gService.newInnerList([item1, item2], inner_list_params);
+
+ // check inner list members & params
+ let inner_list_members = inner_list.QueryInterface(Ci.nsISFVInnerList).items;
+ let inner_list_parameters = inner_list
+ .QueryInterface(Ci.nsISFVInnerList)
+ .params.QueryInterface(Ci.nsISFVParams);
+ Assert.equal(inner_list_members.length, 2, "check inner list length");
+
+ let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem);
+ Assert.equal(
+ inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value,
+ 172.145865,
+ "check inner list member value"
+ );
+
+ let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem);
+ Assert.equal(
+ inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value,
+ "weather",
+ "check inner list member value"
+ );
+
+ Assert.equal(
+ inner_list_parameters.get("inner_param").QueryInterface(Ci.nsISFVByteSeq)
+ .value,
+ "tests",
+ "get inner list parameter by key and check its value"
+ );
+});
+
+add_task(async function test_sfv_item() {
+ // create parameters for item
+ let params = gService.newParameters();
+ let param1 = gService.newBool(false);
+ let param2 = gService.newString("str_value");
+ let param3 = gService.newBool(true);
+ params.set("param_1", param1);
+ params.set("param_2", param2);
+ params.set("param_3", param3);
+
+ // create item field
+ let item = gService.newItem(gService.newToken("*abc"), params);
+
+ Assert.equal(
+ item.value.QueryInterface(Ci.nsISFVToken).value,
+ "*abc",
+ "check items's value"
+ );
+ Assert.equal(
+ item.params.get("param_1").QueryInterface(Ci.nsISFVBool).value,
+ false,
+ "get item parameter by key and check its value"
+ );
+ Assert.equal(
+ item.params.get("param_2").QueryInterface(Ci.nsISFVString).value,
+ "str_value",
+ "get item parameter by key and check its value"
+ );
+ Assert.equal(
+ item.params.get("param_3").QueryInterface(Ci.nsISFVBool).value,
+ true,
+ "get item parameter by key and check its value"
+ );
+
+ // check item field serialization
+ let serialized = item.serialize();
+ Assert.equal(
+ serialized,
+ `*abc;param_1=?0;param_2="str_value";param_3`,
+ "serialized output must match expected one"
+ );
+});
+
+add_task(async function test_sfv_list() {
+ // create primitives for List
+ let item1_params = gService.newParameters();
+ item1_params.set("param_1", gService.newToken("*smth"));
+ let item1 = gService.newItem(gService.newDecimal(145454.14568), item1_params);
+
+ let item2_params = gService.newParameters();
+ item2_params.set("param_1", gService.newBool(true));
+ let item2 = gService.newItem(
+ gService.newByteSequence("weather"),
+ item2_params
+ );
+
+ let inner_list = gService.newInnerList(
+ [item1, item2],
+ gService.newParameters()
+ );
+
+ // create list field
+ let list = gService.newList([item1, inner_list]);
+
+ // check list's members
+ let list_members = list.members;
+ Assert.equal(list_members.length, 2, "check list length");
+
+ // check list's member of item type
+ let member1 = list_members[0].QueryInterface(Ci.nsISFVItem);
+ Assert.equal(
+ member1.value.QueryInterface(Ci.nsISFVDecimal).value,
+ 145454.14568,
+ "check list member's value"
+ );
+ let member1_parameters = member1.params;
+ Assert.equal(
+ member1_parameters.get("param_1").QueryInterface(Ci.nsISFVToken).value,
+ "*smth",
+ "get list member's parameter by key and check its value"
+ );
+
+ // check list's member of inner list type
+ let inner_list_members = list_members[1].QueryInterface(
+ Ci.nsISFVInnerList
+ ).items;
+ Assert.equal(inner_list_members.length, 2, "check inner list length");
+
+ let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem);
+ Assert.equal(
+ inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value,
+ 145454.14568,
+ "check inner list member's value"
+ );
+
+ let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem);
+ Assert.equal(
+ inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value,
+ "weather",
+ "check inner list member's value"
+ );
+
+ // check inner list member's params
+ list_members[1]
+ .QueryInterface(Ci.nsISFVInnerList)
+ .params.QueryInterface(Ci.nsISFVParams);
+
+ // check serialization of list field
+ let expected_serialized =
+ "145454.146;param_1=*smth, (145454.146;param_1=*smth :d2VhdGhlcg==:;param_1)";
+ let actual_serialized = list.serialize();
+ Assert.equal(
+ actual_serialized,
+ expected_serialized,
+ "serialized output must match expected one"
+ );
+});
+
+add_task(async function test_sfv_dictionary() {
+ // create primitives for dictionary field
+
+ // dict member1
+ let params1 = gService.newParameters();
+ params1.set("mp_1", gService.newBool(true));
+ params1.set("mp_2", gService.newDecimal(68.758602));
+ let member1 = gService.newItem(gService.newString("member_1"), params1);
+
+ // dict member2
+ let params2 = gService.newParameters();
+ let inner_item1 = gService.newItem(
+ gService.newString("inner_item_1"),
+ gService.newParameters()
+ );
+ let inner_item2 = gService.newItem(
+ gService.newToken("tok"),
+ gService.newParameters()
+ );
+ let member2 = gService.newInnerList([inner_item1, inner_item2], params2);
+
+ // dict member3
+ let params_3 = gService.newParameters();
+ params_3.set("mp_1", gService.newInteger(6586));
+ let member3 = gService.newItem(gService.newString("member_3"), params_3);
+
+ // create dictionary field
+ let dict = gService.newDictionary();
+ dict.set("key_1", member1);
+ dict.set("key_2", member2);
+ dict.set("key_3", member3);
+
+ // check dictionary keys
+ let expected = ["key_1", "key_2", "key_3"];
+ Assert.deepEqual(
+ expected,
+ dict.keys(),
+ "check dictionary contains all the expected keys"
+ );
+
+ // check dictionary members
+ Assert.throws(
+ () => {
+ dict.get("key_4");
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "must throw exception as key does not exist in dictionary"
+ );
+
+ // let dict_member1 = dict.get("key_1").QueryInterface(Ci.nsISFVItem);
+ let dict_member2 = dict.get("key_2").QueryInterface(Ci.nsISFVInnerList);
+ let dict_member3 = dict.get("key_3").QueryInterface(Ci.nsISFVItem);
+
+ // Assert.equal(
+ // dict_member1.value.QueryInterface(Ci.nsISFVString).value,
+ // "member_1",
+ // "check dictionary member's value"
+ // );
+ // Assert.equal(
+ // dict_member1.params.get("mp_1").QueryInterface(Ci.nsISFVBool).value,
+ // true,
+ // "get dictionary member's parameter by key and check its value"
+ // );
+ // Assert.equal(
+ // dict_member1.params.get("mp_2").QueryInterface(Ci.nsISFVDecimal).value,
+ // "68.758602",
+ // "get dictionary member's parameter by key and check its value"
+ // );
+
+ let dict_member2_items = dict_member2.QueryInterface(
+ Ci.nsISFVInnerList
+ ).items;
+ let dict_member2_params = dict_member2
+ .QueryInterface(Ci.nsISFVInnerList)
+ .params.QueryInterface(Ci.nsISFVParams);
+ Assert.equal(
+ dict_member2_items[0]
+ .QueryInterface(Ci.nsISFVItem)
+ .value.QueryInterface(Ci.nsISFVString).value,
+ "inner_item_1",
+ "get dictionary member of inner list type, and check inner list member's value"
+ );
+ Assert.equal(
+ dict_member2_items[1]
+ .QueryInterface(Ci.nsISFVItem)
+ .value.QueryInterface(Ci.nsISFVToken).value,
+ "tok",
+ "get dictionary member of inner list type, and check inner list member's value"
+ );
+ Assert.throws(
+ () => {
+ dict_member2_params.get("some_param");
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "must throw exception as dict member's parameters are empty"
+ );
+
+ Assert.equal(
+ dict_member3.value.QueryInterface(Ci.nsISFVString).value,
+ "member_3",
+ "check dictionary member's value"
+ );
+ Assert.equal(
+ dict_member3.params.get("mp_1").QueryInterface(Ci.nsISFVInteger).value,
+ 6586,
+ "get dictionary member's parameter by key and check its value"
+ );
+
+ // check serialization of Dictionary field
+ let expected_serialized = `key_1="member_1";mp_1;mp_2=68.759, key_2=("inner_item_1" tok), key_3="member_3";mp_1=6586`;
+ let actual_serialized = dict.serialize();
+ Assert.equal(
+ actual_serialized,
+ expected_serialized,
+ "serialized output must match expected one"
+ );
+
+ // check deleting dict member
+ dict.delete("key_2");
+ Assert.deepEqual(
+ dict.keys(),
+ ["key_1", "key_3"],
+ "check that dictionary member has been deleted"
+ );
+
+ Assert.throws(
+ () => {
+ dict.delete("some_key");
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "must throw exception upon attempt to delete by non-existing key"
+ );
+});
+
+add_task(async function test_sfv_item_parsing() {
+ Assert.ok(gService.parseItem(`"str"`), "input must be parsed into Item");
+ Assert.ok(gService.parseItem("12.35;a "), "input must be parsed into Item");
+ Assert.ok(gService.parseItem("12.35; a "), "input must be parsed into Item");
+ Assert.ok(gService.parseItem("12.35 "), "input must be parsed into Item");
+
+ Assert.throws(
+ () => {
+ gService.parseItem("12.35;\ta ");
+ },
+ /NS_ERROR_FAILURE/,
+ "item parsing must fail: invalid parameters delimiter"
+ );
+
+ Assert.throws(
+ () => {
+ gService.parseItem("125666.3565648855455");
+ },
+ /NS_ERROR_FAILURE/,
+ "item parsing must fail: decimal too long"
+ );
+});
+
+add_task(async function test_sfv_list_parsing() {
+ Assert.ok(
+ gService.parseList(
+ "(?1;param_1=*smth :d2VhdGhlcg==:;param_1;param_2=145454);inner_param=:d2VpcmR0ZXN0cw==:"
+ ),
+ "input must be parsed into List"
+ );
+ Assert.ok("a, (b c)", "input must be parsed into List");
+
+ Assert.throws(() => {
+ gService.parseList("?tok", "list parsing must fail");
+ }, /NS_ERROR_FAILURE/);
+
+ Assert.throws(() => {
+ gService.parseList(
+ "a, (b, c)",
+ "list parsing must fail: invalid delimiter within inner list"
+ );
+ }, /NS_ERROR_FAILURE/);
+
+ Assert.throws(
+ () => {
+ gService.parseList("a, b c");
+ },
+ /NS_ERROR_FAILURE/,
+ "list parsing must fail: invalid delimiter"
+ );
+});
+
+add_task(async function test_sfv_dict_parsing() {
+ Assert.ok(
+ gService.parseDictionary(`abc=123;a=1;b=2, def=456, ghi=789;q=9;r="+w"`),
+ "input must be parsed into Dictionary"
+ );
+ Assert.ok(
+ gService.parseDictionary("a=1\t,\t\t\t c=*"),
+ "input must be parsed into Dictionary"
+ );
+ Assert.ok(
+ gService.parseDictionary("a=1\t,\tc=* \t\t"),
+ "input must be parsed into Dictionary"
+ );
+
+ Assert.throws(
+ () => {
+ gService.parseDictionary("a=1\t,\tc=*,");
+ },
+ /NS_ERROR_FAILURE/,
+ "dictionary parsing must fail: trailing comma"
+ );
+
+ Assert.throws(
+ () => {
+ gService.parseDictionary("a=1 c=*");
+ },
+ /NS_ERROR_FAILURE/,
+ "dictionary parsing must fail: invalid delimiter"
+ );
+
+ Assert.throws(
+ () => {
+ gService.parseDictionary("INVALID_key=1, c=*");
+ },
+ /NS_ERROR_FAILURE/,
+ "dictionary parsing must fail: invalid key format, can't be in uppercase"
+ );
+});
+
+add_task(async function test_sfv_list_parse_serialize() {
+ let list_field = gService.parseList("1 , 42, (42 43)");
+ Assert.equal(
+ list_field.serialize(),
+ "1, 42, (42 43)",
+ "serialized output must match expected one"
+ );
+
+ // create new inner list with parameters
+ function params() {
+ let inner_list_params = gService.newParameters();
+ inner_list_params.set("key1", gService.newString("value1"));
+ inner_list_params.set("key2", gService.newBool(true));
+ inner_list_params.set("key3", gService.newBool(false));
+ return inner_list_params;
+ }
+
+ function changeMembers() {
+ // set one of list members to inner list and check it's serialized as expected
+ let members = list_field.members;
+ members[1] = gService.newInnerList(
+ [
+ gService.newItem(
+ gService.newDecimal(-1865.75653),
+ gService.newParameters()
+ ),
+ gService.newItem(gService.newToken("token"), gService.newParameters()),
+ gService.newItem(
+ gService.newString(`no"yes`),
+ gService.newParameters()
+ ),
+ ],
+ params()
+ );
+ return members;
+ }
+
+ list_field.members = changeMembers();
+
+ Assert.equal(
+ list_field.serialize(),
+ `1, (-1865.757 token "no\\"yes");key1="value1";key2;key3=?0, (42 43)`,
+ "update list member and check list is serialized as expected"
+ );
+});
+
+add_task(async function test_sfv_dict_parse_serialize() {
+ let dict_field = gService.parseDictionary(
+ "a=1, b; foo=*, \tc=3, \t \tabc=123;a=1;b=2\t"
+ );
+ Assert.equal(
+ dict_field.serialize(),
+ "a=1, b;foo=*, c=3, abc=123;a=1;b=2",
+ "serialized output must match expected one"
+ );
+
+ // set new value for existing dict's key
+ dict_field.set(
+ "a",
+ gService.newItem(gService.newInteger(165), gService.newParameters())
+ );
+
+ // add new member to dict
+ dict_field.set(
+ "key",
+ gService.newItem(gService.newDecimal(45.0), gService.newParameters())
+ );
+
+ // check dict is serialized properly after the above changes
+ Assert.equal(
+ dict_field.serialize(),
+ "a=165, b;foo=*, c=3, abc=123;a=1;b=2, key=45.0",
+ "update dictionary members and dictionary list is serialized as expected"
+ );
+});
+
+add_task(async function test_sfv_list_parse_more() {
+ // check parsing of multiline header of List type
+ let list_field = gService.parseList("(12 abc), 12.456\t\t ");
+ list_field.parseMore("11, 15, tok");
+ Assert.equal(
+ list_field.serialize(),
+ "(12 abc), 12.456, 11, 15, tok",
+ "multi-line field value parsed and serialized successfully"
+ );
+
+ // should fail parsing one more line
+ Assert.throws(
+ () => {
+ list_field.parseMore("(tk\t1)");
+ },
+ /NS_ERROR_FAILURE/,
+ "line parsing must fail: invalid delimiter in inner list"
+ );
+ Assert.equal(
+ list_field.serialize(),
+ "(12 abc), 12.456, 11, 15, tok",
+ "parsed value must not change if parsing one more line of header fails"
+ );
+});
+
+add_task(async function test_sfv_dict_parse_more() {
+ // check parsing of multiline header of Dictionary type
+ let dict_field = gService.parseDictionary("");
+ dict_field.parseMore("key2=?0, key3=?1, key4=itm");
+ dict_field.parseMore("key1, key5=11, key4=45");
+
+ Assert.equal(
+ dict_field.serialize(),
+ "key2=?0, key3, key4=45, key1, key5=11",
+ "multi-line field value parsed and serialized successfully"
+ );
+
+ // should fail parsing one more line
+ Assert.throws(
+ () => {
+ dict_field.parseMore("c=12, _k=13");
+ },
+ /NS_ERROR_FAILURE/,
+ "line parsing must fail: invalid key format"
+ );
+});
diff --git a/netwerk/test/unit/test_httpauth.js b/netwerk/test/unit/test_httpauth.js
new file mode 100644
index 0000000000..9c9de82618
--- /dev/null
+++ b/netwerk/test/unit/test_httpauth.js
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 test makes sure the HTTP authenticated sessions are correctly cleared
+// when entering and leaving the private browsing mode.
+
+"use strict";
+
+function run_test() {
+ var am = Cc["@mozilla.org/network/http-auth-manager;1"].getService(
+ Ci.nsIHttpAuthManager
+ );
+
+ const kHost1 = "pbtest3.example.com";
+ const kHost2 = "pbtest4.example.com";
+ const kPort = 80;
+ const kHTTP = "http";
+ const kBasic = "basic";
+ const kRealm = "realm";
+ const kDomain = "example.com";
+ const kUser = "user";
+ const kUser2 = "user2";
+ const kPassword = "pass";
+ const kPassword2 = "pass2";
+ const kEmpty = "";
+
+ const PRIVATE = true;
+ const NOT_PRIVATE = false;
+
+ try {
+ var domain = { value: kEmpty },
+ user = { value: kEmpty },
+ pass = { value: kEmpty };
+ // simulate a login via HTTP auth outside of the private mode
+ am.setAuthIdentity(
+ kHTTP,
+ kHost1,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ kDomain,
+ kUser,
+ kPassword
+ );
+ // make sure the recently added auth entry is available outside the private browsing mode
+ am.getAuthIdentity(
+ kHTTP,
+ kHost1,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ domain,
+ user,
+ pass,
+ NOT_PRIVATE
+ );
+ Assert.equal(domain.value, kDomain);
+ Assert.equal(user.value, kUser);
+ Assert.equal(pass.value, kPassword);
+
+ // make sure the added auth entry is no longer accessible in private
+ domain = { value: kEmpty };
+ user = { value: kEmpty };
+ pass = { value: kEmpty };
+ try {
+ // should throw
+ am.getAuthIdentity(
+ kHTTP,
+ kHost1,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ domain,
+ user,
+ pass,
+ PRIVATE
+ );
+ do_throw(
+ "Auth entry should not be retrievable after entering the private browsing mode"
+ );
+ } catch (e) {
+ Assert.equal(domain.value, kEmpty);
+ Assert.equal(user.value, kEmpty);
+ Assert.equal(pass.value, kEmpty);
+ }
+
+ // simulate a login via HTTP auth inside of the private mode
+ am.setAuthIdentity(
+ kHTTP,
+ kHost2,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ kDomain,
+ kUser2,
+ kPassword2,
+ PRIVATE
+ );
+ // make sure the recently added auth entry is available inside the private browsing mode
+ domain = { value: kEmpty };
+ user = { value: kEmpty };
+ pass = { value: kEmpty };
+ am.getAuthIdentity(
+ kHTTP,
+ kHost2,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ domain,
+ user,
+ pass,
+ PRIVATE
+ );
+ Assert.equal(domain.value, kDomain);
+ Assert.equal(user.value, kUser2);
+ Assert.equal(pass.value, kPassword2);
+
+ try {
+ // make sure the recently added auth entry is not available outside the private browsing mode
+ domain = { value: kEmpty };
+ user = { value: kEmpty };
+ pass = { value: kEmpty };
+ am.getAuthIdentity(
+ kHTTP,
+ kHost2,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ domain,
+ user,
+ pass,
+ NOT_PRIVATE
+ );
+ do_throw(
+ "Auth entry should not be retrievable outside of private browsing mode"
+ );
+ } catch (x) {
+ Assert.equal(domain.value, kEmpty);
+ Assert.equal(user.value, kEmpty);
+ Assert.equal(pass.value, kEmpty);
+ }
+
+ // simulate leaving private browsing mode
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+
+ // make sure the added auth entry is no longer accessible in any privacy state
+ domain = { value: kEmpty };
+ user = { value: kEmpty };
+ pass = { value: kEmpty };
+ try {
+ // should throw (not available in public mode)
+ am.getAuthIdentity(
+ kHTTP,
+ kHost2,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ domain,
+ user,
+ pass,
+ NOT_PRIVATE
+ );
+ do_throw(
+ "Auth entry should not be retrievable after exiting the private browsing mode"
+ );
+ } catch (e) {
+ Assert.equal(domain.value, kEmpty);
+ Assert.equal(user.value, kEmpty);
+ Assert.equal(pass.value, kEmpty);
+ }
+ try {
+ // should throw (no longer available in private mode)
+ am.getAuthIdentity(
+ kHTTP,
+ kHost2,
+ kPort,
+ kBasic,
+ kRealm,
+ kEmpty,
+ domain,
+ user,
+ pass,
+ PRIVATE
+ );
+ do_throw(
+ "Auth entry should not be retrievable in private mode after exiting the private browsing mode"
+ );
+ } catch (x) {
+ Assert.equal(domain.value, kEmpty);
+ Assert.equal(user.value, kEmpty);
+ Assert.equal(pass.value, kEmpty);
+ }
+ } catch (e) {
+ do_throw("Unexpected exception while testing HTTP auth manager: " + e);
+ }
+}
diff --git a/netwerk/test/unit/test_httpcancel.js b/netwerk/test/unit/test_httpcancel.js
new file mode 100644
index 0000000000..04958ffd2b
--- /dev/null
+++ b/netwerk/test/unit/test_httpcancel.js
@@ -0,0 +1,261 @@
+// This file ensures that canceling a channel early does not
+// send the request to the server (bug 350790)
+//
+// I've also shoehorned in a test that ENSURE_CALLED_BEFORE_CONNECT works as
+// expected: see comments that start with ENSURE_CALLED_BEFORE_CONNECT:
+//
+// This test also checks that cancelling a channel before asyncOpen, after
+// onStopRequest, or during onDataAvailable works as expected.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const reason = "testing";
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+var ios = Services.io;
+var ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+var observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ subject = subject.QueryInterface(Ci.nsIRequest);
+ subject.cancelWithReason(Cr.NS_BINDING_ABORTED, reason);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: setting values should still work
+ try {
+ subject.QueryInterface(Ci.nsIHttpChannel);
+ let currentReferrer = subject.getRequestHeader("Referer");
+ Assert.equal(currentReferrer, "http://site1.com/");
+ var uri = ios.newURI("http://site2.com");
+ subject.referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ uri
+ );
+ } catch (ex) {
+ do_throw("Exception: " + ex);
+ }
+ },
+};
+
+let cancelDuringOnStartListener = {
+ onStartRequest: function test_onStartR(request) {
+ Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
+ // We didn't sync the reason to child process.
+ if (!inChildProcess()) {
+ Assert.equal(request.canceledReason, reason);
+ }
+
+ // ENSURE_CALLED_BEFORE_CONNECT: setting referrer should now fail
+ try {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ let currentReferrer = request.getRequestHeader("Referer");
+ Assert.equal(currentReferrer, "http://site2.com/");
+ var uri = ios.newURI("http://site3.com/");
+
+ // Need to set NECKO_ERRORS_ARE_FATAL=0 else we'll abort process
+ Services.env.set("NECKO_ERRORS_ARE_FATAL", "0");
+ // we expect setting referrer to fail
+ try {
+ request.referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ uri
+ );
+ do_throw("Error should have been thrown before getting here");
+ } catch (ex) {}
+ } catch (ex) {
+ do_throw("Exception: " + ex);
+ }
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ this.resolved();
+ },
+};
+
+var cancelDuringOnDataListener = {
+ data: "",
+ channel: null,
+ receivedSomeData: null,
+ onStartRequest: function test_onStartR(request, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, stream, offset, count) {
+ let string = NetUtil.readInputStreamToString(stream, count);
+ Assert.ok(!string.includes("b"));
+ this.data += string;
+ this.channel.cancel(Cr.NS_BINDING_ABORTED);
+ if (this.receivedSomeData) {
+ this.receivedSomeData();
+ }
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ Assert.ok(this.data.includes("a"), `data: ${this.data}`);
+ Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
+ this.resolved();
+ },
+};
+
+function makeChan(url) {
+ var chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: set original value
+ var uri = ios.newURI("http://site1.com");
+ chan.referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+ return chan;
+}
+
+var httpserv = null;
+
+add_task(async function setup() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/failtest", failtest);
+ httpserv.registerPathHandler("/cancel_middle", cancel_middle);
+ httpserv.registerPathHandler("/normal_response", normal_response);
+ httpserv.start(-1);
+
+ registerCleanupFunction(async () => {
+ await new Promise(resolve => httpserv.stop(resolve));
+ });
+});
+
+add_task(async function test_cancel_during_onModifyRequest() {
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
+ );
+
+ if (!inChildProcess()) {
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ } else {
+ do_send_remote_message("register-observer");
+ await do_await_remote_message("register-observer-done");
+ }
+
+ await new Promise(resolve => {
+ cancelDuringOnStartListener.resolved = resolve;
+ chan.asyncOpen(cancelDuringOnStartListener);
+ });
+
+ if (!inChildProcess()) {
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ } else {
+ do_send_remote_message("unregister-observer");
+ await do_await_remote_message("unregister-observer-done");
+ }
+});
+
+add_task(async function test_cancel_before_asyncOpen() {
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
+ );
+
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+
+ Assert.throws(
+ () => {
+ chan.asyncOpen(cancelDuringOnStartListener);
+ },
+ /NS_BINDING_ABORTED/,
+ "cannot open if already cancelled"
+ );
+});
+
+add_task(async function test_cancel_during_onData() {
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/cancel_middle"
+ );
+
+ await new Promise(resolve => {
+ cancelDuringOnDataListener.resolved = resolve;
+ cancelDuringOnDataListener.channel = chan;
+ chan.asyncOpen(cancelDuringOnDataListener);
+ });
+});
+
+var cancelAfterOnStopListener = {
+ data: "",
+ channel: null,
+ onStartRequest: function test_onStartR(request, ctx) {
+ Assert.equal(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, stream, offset, count) {
+ let string = NetUtil.readInputStreamToString(stream, count);
+ this.data += string;
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ info("onStopRequest");
+ Assert.equal(request.status, Cr.NS_OK);
+ this.resolved();
+ },
+};
+
+add_task(async function test_cancel_after_onStop() {
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/normal_response"
+ );
+
+ await new Promise(resolve => {
+ cancelAfterOnStopListener.resolved = resolve;
+ cancelAfterOnStopListener.channel = chan;
+ chan.asyncOpen(cancelAfterOnStopListener);
+ });
+ Assert.equal(chan.status, Cr.NS_OK);
+
+ // For now it's unclear if cancelling after onStop should throw,
+ // silently fail, or overwrite the channel's status as we currently do.
+ // See discussion in bug 1553083
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ Assert.equal(chan.status, Cr.NS_BINDING_ABORTED);
+});
+
+// PATHS
+
+// /failtest
+function failtest(metadata, response) {
+ do_throw("This should not be reached");
+}
+
+function cancel_middle(metadata, response) {
+ response.processAsync();
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ let str1 = "a".repeat(128 * 1024);
+ response.write(str1, str1.length);
+ response.bodyOutputStream.flush();
+
+ let p = new Promise(resolve => {
+ cancelDuringOnDataListener.receivedSomeData = resolve;
+ });
+ p.then(() => {
+ let str2 = "b".repeat(128 * 1024);
+ response.write(str2, str2.length);
+ response.finish();
+ });
+}
+
+function normal_response(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ let str1 = "Is this normal?";
+ response.write(str1, str1.length);
+}
diff --git a/netwerk/test/unit/test_https_rr_ech_prefs.js b/netwerk/test/unit/test_https_rr_ech_prefs.js
new file mode 100644
index 0000000000..f70c672dde
--- /dev/null
+++ b/netwerk/test/unit/test_https_rr_ech_prefs.js
@@ -0,0 +1,535 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let trrServer;
+
+function setup() {
+ trr_test_setup();
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+function checkResult(inRecord, noHttp2, noHttp3, result) {
+ if (!result) {
+ Assert.throws(
+ () => {
+ inRecord
+ .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
+ .GetServiceModeRecord(noHttp2, noHttp3);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should get an error"
+ );
+ return;
+ }
+
+ let record = inRecord
+ .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
+ .GetServiceModeRecord(noHttp2, noHttp3);
+ Assert.equal(record.priority, result.expectedPriority);
+ Assert.equal(record.name, result.expectedName);
+ Assert.equal(record.selectedAlpn, result.expectedAlpn);
+}
+
+// Test configuration:
+// There are two records: one has a echConfig and the other doesn't.
+// We want to test if the record with echConfig is preferred.
+add_task(async function testEchConfigEnabled() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+
+ await trrServer.registerDoHAnswers("test.bar.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.bar_1.com",
+ values: [{ key: "alpn", value: ["h3-29"] }],
+ },
+ },
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.bar_2.com",
+ values: [
+ { key: "alpn", value: ["h2"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.bar.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.bar_1.com",
+ expectedAlpn: "h3-29",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 2,
+ expectedName: "test.bar_2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.bar_1.com",
+ expectedAlpn: "h3-29",
+ });
+ checkResult(inRecord, true, true);
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.dns.clearCache(true);
+
+ ({ inRecord } = await new TRRDNSListener("test.bar.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 2,
+ expectedName: "test.bar_2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 2,
+ expectedName: "test.bar_2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.bar_1.com",
+ expectedAlpn: "h3-29",
+ });
+ checkResult(inRecord, true, true);
+
+ await trrServer.stop();
+ trrServer = null;
+});
+
+// Test configuration:
+// There are two records: both have echConfigs, and only one supports h3.
+// This test is about testing which record should we get when
+// network.dns.http3_echconfig.enabled is true and false.
+// When network.dns.http3_echconfig.enabled is false, we should try to
+// connect with h2 and echConfig.
+add_task(async function testTwoRecordsHaveEchConfig() {
+ Services.dns.clearCache(true);
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.foo_h3.com",
+ values: [
+ { key: "alpn", value: ["h3"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.foo_h2.com",
+ values: [
+ { key: "alpn", value: ["h2"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true);
+
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+ Services.dns.clearCache(true);
+ ({ inRecord } = await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true);
+
+ await trrServer.stop();
+ trrServer = null;
+});
+
+// Test configuration:
+// There are two records: both have echConfigs, and one supports h3 and h2.
+// When network.dns.http3_echconfig.enabled is false, we should use the record
+// that supports h3 and h2 (the alpn is h2).
+add_task(async function testTwoRecordsHaveEchConfig1() {
+ Services.dns.clearCache(true);
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.foo_h3.com",
+ values: [
+ { key: "alpn", value: ["h3", "h2"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.foo_h2.com",
+ values: [
+ { key: "alpn", value: ["h2", "http/1.1"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "http/1.1",
+ });
+ checkResult(inRecord, true, true, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "http/1.1",
+ });
+
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+ Services.dns.clearCache(true);
+ ({ inRecord } = await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "http/1.1",
+ });
+
+ await trrServer.stop();
+ trrServer = null;
+});
+
+// Test configuration:
+// There are two records: only one support h3 and only one has echConfig.
+// This test is about never usng the record without echConfig.
+add_task(async function testOneRecordsHasEchConfig() {
+ Services.dns.clearCache(true);
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.foo_h3.com",
+ values: [
+ { key: "alpn", value: ["h3"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.foo_h2.com",
+ values: [{ key: "alpn", value: ["h2"] }],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true);
+
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+ Services.dns.clearCache(true);
+ ({ inRecord } = await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 2,
+ expectedName: "test.foo_h2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true);
+
+ await trrServer.stop();
+ trrServer = null;
+});
+
+// Test the case that "network.http.http3.enable" and
+// "network.http.http2.enabled" are true/false.
+add_task(async function testHttp3AndHttp2Pref() {
+ Services.dns.clearCache(true);
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setBoolPref("network.http.http3.enable", false);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.foo_h3.com",
+ values: [
+ { key: "alpn", value: ["h3", "h2"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.foo_h2.com",
+ values: [
+ { key: "alpn", value: ["h2"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false);
+ checkResult(inRecord, true, true);
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", false);
+ checkResult(inRecord, false, false);
+
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, false, true);
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.foo_h3.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true);
+
+ await trrServer.stop();
+ trrServer = null;
+});
diff --git a/netwerk/test/unit/test_https_rr_sorted_alpn.js b/netwerk/test/unit/test_https_rr_sorted_alpn.js
new file mode 100644
index 0000000000..95284de0c3
--- /dev/null
+++ b/netwerk/test/unit/test_https_rr_sorted_alpn.js
@@ -0,0 +1,226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let trrServer;
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+add_setup(async function setup() {
+ trr_test_setup();
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.http.http3.support_version1");
+ Services.prefs.clearUserPref("security.tls.version.max");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+});
+
+function checkResult(inRecord, noHttp2, noHttp3, result) {
+ if (!result) {
+ Assert.throws(
+ () => {
+ inRecord
+ .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
+ .GetServiceModeRecord(noHttp2, noHttp3);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should get an error"
+ );
+ return;
+ }
+
+ let record = inRecord
+ .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
+ .GetServiceModeRecord(noHttp2, noHttp3);
+ Assert.equal(record.priority, result.expectedPriority);
+ Assert.equal(record.name, result.expectedName);
+ Assert.equal(record.selectedAlpn, result.expectedAlpn);
+}
+
+add_task(async function testSortedAlpnH3() {
+ Services.dns.clearCache(true);
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.support_version1", true);
+ await trrServer.registerDoHAnswers("test.alpn.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.alpn.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.alpn.com",
+ values: [{ key: "alpn", value: ["h2", "http/1.1", "h3-30", "h3"] }],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.alpn.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "http/1.1",
+ });
+
+ Services.prefs.setBoolPref("network.http.http3.support_version1", false);
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h3-30",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h3-30",
+ });
+ checkResult(inRecord, true, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "http/1.1",
+ });
+ Services.prefs.setBoolPref("network.http.http3.support_version1", true);
+
+ // Disable TLS1.3
+ Services.prefs.setIntPref("security.tls.version.max", 3);
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "http/1.1",
+ });
+ checkResult(inRecord, true, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "http/1.1",
+ });
+
+ // Enable TLS1.3
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "h3",
+ });
+ checkResult(inRecord, true, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn.com",
+ expectedAlpn: "http/1.1",
+ });
+});
+
+add_task(async function testSortedAlpnH2() {
+ Services.dns.clearCache(true);
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ await trrServer.registerDoHAnswers("test.alpn_2.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.alpn_2.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.alpn_2.com",
+ values: [{ key: "alpn", value: ["http/1.1", "h2"] }],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.alpn_2.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ checkResult(inRecord, false, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn_2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, false, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn_2.com",
+ expectedAlpn: "h2",
+ });
+ checkResult(inRecord, true, false, {
+ expectedPriority: 1,
+ expectedName: "test.alpn_2.com",
+ expectedAlpn: "http/1.1",
+ });
+ checkResult(inRecord, true, true, {
+ expectedPriority: 1,
+ expectedName: "test.alpn_2.com",
+ expectedAlpn: "http/1.1",
+ });
+
+ await trrServer.stop();
+ trrServer = null;
+});
diff --git a/netwerk/test/unit/test_httpssvc_ech_with_alpn.js b/netwerk/test/unit/test_httpssvc_ech_with_alpn.js
new file mode 100644
index 0000000000..fbd9768daf
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_ech_with_alpn.js
@@ -0,0 +1,246 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+add_setup(async function setup() {
+ // Allow telemetry probes which may otherwise be disabled for some
+ // applications (e.g. Thunderbird).
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ trr_test_setup();
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+
+ // Set the server to always select http/1.1
+ Services.env.set("MOZ_TLS_ECH_ALPN_FLAG", 1);
+
+ await asyncStartTLSTestServer(
+ "EncryptedClientHelloServer",
+ "../../../security/manager/ssl/tests/unit/test_encrypted_client_hello"
+ );
+});
+
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.trr.uri");
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
+ Services.prefs.clearUserPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed"
+ );
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
+ Services.env.set("MOZ_TLS_ECH_ALPN_FLAG", "");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ resolve([req, buffer]);
+ }
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+function ActivityObserver() {}
+
+ActivityObserver.prototype = {
+ activites: [],
+ observeConnectionActivity(
+ aHost,
+ aPort,
+ aSSL,
+ aHasECH,
+ aIsHttp3,
+ aActivityType,
+ aActivitySubtype,
+ aTimestamp,
+ aExtraStringData
+ ) {
+ dump(
+ "*** Connection Activity 0x" +
+ aActivityType.toString(16) +
+ " 0x" +
+ aActivitySubtype.toString(16) +
+ " " +
+ aExtraStringData +
+ "\n"
+ );
+ this.activites.push({ host: aHost, subType: aActivitySubtype });
+ },
+};
+
+function checkHttpActivities(activites) {
+ let foundDNSAndSocket = false;
+ let foundSettingECH = false;
+ let foundConnectionCreated = false;
+ for (let activity of activites) {
+ switch (activity.subType) {
+ case Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED:
+ case Ci.nsIHttpActivityObserver
+ .ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED:
+ foundDNSAndSocket = true;
+ break;
+ case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_ECH_SET:
+ foundSettingECH = true;
+ break;
+ case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_CONNECTION_CREATED:
+ foundConnectionCreated = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ Assert.equal(foundDNSAndSocket, true, "Should have one DnsAndSock created");
+ Assert.equal(foundSettingECH, true, "Should have echConfig");
+ Assert.equal(
+ foundConnectionCreated,
+ true,
+ "Should have one connection created"
+ );
+}
+
+async function testWrapper(alpnAdvertisement) {
+ const ECH_CONFIG_FIXED =
+ "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAEAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ let observerService = Cc[
+ "@mozilla.org/network/http-activity-distributor;1"
+ ].getService(Ci.nsIHttpActivityDistributor);
+ let observer = new ActivityObserver();
+ observerService.addObserver(observer);
+ observerService.observeConnection = true;
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ // Only the last record is valid to use.
+ await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", {
+ answers: [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "ech-private.example.com",
+ values: [
+ { key: "alpn", value: alpnAdvertisement },
+ { key: "port", value: 8443 },
+ {
+ key: "echconfig",
+ value: ECH_CONFIG_FIXED,
+ needBase64Decode: true,
+ },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("ech-private.example.com", "A", {
+ answers: [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("ech-private.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ HandshakeTelemetryHelpers.resetHistograms();
+ let chan = makeChan(`https://ech-private.example.com`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ let securityInfo = chan.securityInfo;
+ Assert.ok(securityInfo.isAcceptedEch, "This host should have accepted ECH");
+
+ // Only check telemetry if network process is disabled.
+ if (!mozinfo.socketprocess_networking) {
+ HandshakeTelemetryHelpers.checkSuccess(["", "_ECH", "_FIRST_TRY"]);
+ HandshakeTelemetryHelpers.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
+ }
+
+ await trrServer.stop();
+ observerService.removeObserver(observer);
+ observerService.observeConnection = false;
+
+ let filtered = observer.activites.filter(
+ activity => activity.host === "ech-private.example.com"
+ );
+ checkHttpActivities(filtered);
+}
+
+add_task(async function h1Advertised() {
+ await testWrapper(["http/1.1"]);
+});
+
+add_task(async function h2Advertised() {
+ await testWrapper(["h2"]);
+});
+
+add_task(async function h3Advertised() {
+ await testWrapper(["h3"]);
+});
+
+add_task(async function h1h2Advertised() {
+ await testWrapper(["http/1.1", "h2"]);
+});
+
+add_task(async function h2h3Advertised() {
+ await testWrapper(["h3", "h2"]);
+});
+
+add_task(async function unknownAdvertised() {
+ await testWrapper(["foo"]);
+});
diff --git a/netwerk/test/unit/test_httpssvc_https_upgrade.js b/netwerk/test/unit/test_httpssvc_https_upgrade.js
new file mode 100644
index 0000000000..837006767c
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_https_upgrade.js
@@ -0,0 +1,352 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+add_setup(async function setup() {
+ trr_test_setup();
+
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ Services.prefs.setBoolPref(
+ "network.dns.use_https_rr_for_speculative_connection",
+ true
+ );
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref(
+ "network.dns.use_https_rr_for_speculative_connection"
+ );
+ Services.prefs.clearUserPref("network.dns.notifyResolution");
+ Services.prefs.clearUserPref("network.dns.disablePrefetch");
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+// When observer is specified, the channel will be suspended when receiving
+// "http-on-modify-request".
+function channelOpenPromise(chan, flags, observer) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ resolve([req, buffer]);
+ }
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ if (observer) {
+ let topic = "http-on-modify-request";
+ Services.obs.addObserver(observer, topic);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+class EventSinkListener {
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+ asyncOnChannelRedirect(oldChan, newChan, flags, callback) {
+ Assert.equal(oldChan.URI.hostPort, newChan.URI.hostPort);
+ Assert.equal(oldChan.URI.scheme, "http");
+ Assert.equal(newChan.URI.scheme, "https");
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+}
+
+EventSinkListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIChannelEventSink",
+]);
+
+// Test if the request is upgraded to https with a HTTPSSVC record.
+add_task(async function testUseHTTPSSVCAsHSTS() {
+ Services.dns.clearCache(true);
+ // Do DNS resolution before creating the channel, so the HTTPSSVC record will
+ // be resolved from the cache.
+ await new TRRDNSListener("test.httpssvc.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ // Since the HTTPS RR should be served from cache, the DNS record is available
+ // before nsHttpChannel::MaybeUseHTTPSRRForUpgrade() is called.
+ let chan = makeChan(`http://test.httpssvc.com:80/server-timing`);
+ let listener = new EventSinkListener();
+ chan.notificationCallbacks = listener;
+
+ let [req] = await channelOpenPromise(chan);
+
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ chan = makeChan(`http://test.httpssvc.com:80/server-timing`);
+ listener = new EventSinkListener();
+ chan.notificationCallbacks = listener;
+
+ [req] = await channelOpenPromise(chan);
+
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+});
+
+// Test the case that we got an invalid DNS response. In this case,
+// nsHttpChannel::OnHTTPSRRAvailable is called after
+// nsHttpChannel::MaybeUseHTTPSRRForUpgrade.
+add_task(async function testInvalidDNSResult() {
+ Services.dns.clearCache(true);
+
+ let httpserv = new HttpServer();
+ let content = "ok";
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start(-1);
+ httpserv.identity.setPrimary(
+ "http",
+ "foo.notexisted.com",
+ httpserv.identity.primaryPort
+ );
+
+ let chan = makeChan(
+ `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
+ );
+ let [, response] = await channelOpenPromise(chan);
+ Assert.equal(response, content);
+ await new Promise(resolve => httpserv.stop(resolve));
+});
+
+// The same test as above, but nsHttpChannel::MaybeUseHTTPSRRForUpgrade is
+// called after nsHttpChannel::OnHTTPSRRAvailable.
+add_task(async function testInvalidDNSResult1() {
+ Services.dns.clearCache(true);
+
+ let httpserv = new HttpServer();
+ let content = "ok";
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start(-1);
+ httpserv.identity.setPrimary(
+ "http",
+ "foo.notexisted.com",
+ httpserv.identity.primaryPort
+ );
+
+ let chan = makeChan(
+ `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
+ );
+
+ let topic = "http-on-modify-request";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == topic) {
+ Services.obs.removeObserver(observer, topic);
+ let channel = aSubject.QueryInterface(Ci.nsIChannel);
+ channel.suspend();
+
+ new TRRDNSListener("foo.notexisted.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ }).then(() => channel.resume());
+ }
+ },
+ };
+
+ let [, response] = await channelOpenPromise(chan, 0, observer);
+ Assert.equal(response, content);
+ await new Promise(resolve => httpserv.stop(resolve));
+});
+
+add_task(async function testLiteralIP() {
+ let httpserv = new HttpServer();
+ let content = "ok";
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start(-1);
+
+ let chan = makeChan(`http://127.0.0.1:${httpserv.identity.primaryPort}/`);
+ let [, response] = await channelOpenPromise(chan);
+ Assert.equal(response, content);
+ await new Promise(resolve => httpserv.stop(resolve));
+});
+
+// Test the case that an HTTPS RR is available and the server returns a 307
+// for redirecting back to http.
+add_task(async function testEndlessUpgradeDowngrade() {
+ Services.dns.clearCache(true);
+
+ let httpserv = new HttpServer();
+ let content = "okok";
+ httpserv.start(-1);
+ let port = httpserv.identity.primaryPort;
+ httpserv.registerPathHandler(
+ `/redirect_to_http`,
+ function handler(metadata, response) {
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ }
+ );
+ httpserv.identity.setPrimary("http", "test.httpsrr.redirect.com", port);
+
+ let chan = makeChan(
+ `http://test.httpsrr.redirect.com:${port}/redirect_to_http?port=${port}`
+ );
+
+ let [, response] = await channelOpenPromise(chan);
+ Assert.equal(response, content);
+ await new Promise(resolve => httpserv.stop(resolve));
+});
+
+add_task(async function testHttpRequestBlocked() {
+ Services.dns.clearCache(true);
+
+ let dnsRequestObserver = {
+ register() {
+ this.obs = Services.obs;
+ this.obs.addObserver(this, "dns-resolution-request");
+ },
+ unregister() {
+ if (this.obs) {
+ this.obs.removeObserver(this, "dns-resolution-request");
+ }
+ },
+ observe(subject, topic, data) {
+ if (topic == "dns-resolution-request") {
+ Assert.ok(false, "unreachable");
+ }
+ },
+ };
+
+ dnsRequestObserver.register();
+ Services.prefs.setBoolPref("network.dns.notifyResolution", true);
+ Services.prefs.setBoolPref("network.dns.disablePrefetch", true);
+
+ let httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ Assert.ok(false, "unreachable");
+ });
+ httpserv.start(-1);
+ httpserv.identity.setPrimary(
+ "http",
+ "foo.blocked.com",
+ httpserv.identity.primaryPort
+ );
+
+ let chan = makeChan(
+ `http://foo.blocked.com:${httpserv.identity.primaryPort}/`
+ );
+
+ let topic = "http-on-modify-request";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == topic) {
+ Services.obs.removeObserver(observer, topic);
+ let channel = aSubject.QueryInterface(Ci.nsIChannel);
+ channel.cancel(Cr.NS_BINDING_ABORTED);
+ }
+ },
+ };
+
+ let [request] = await channelOpenPromise(chan, CL_EXPECT_FAILURE, observer);
+ request.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
+ dnsRequestObserver.unregister();
+ await new Promise(resolve => httpserv.stop(resolve));
+});
+
+function createPrincipal(url) {
+ return Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(url),
+ {}
+ );
+}
+
+// Test if the Origin header stays the same after an internal HTTPS upgrade
+// caused by HTTPS RR.
+add_task(async function testHTTPSRRUpgradeWithOriginHeader() {
+ Services.dns.clearCache(true);
+
+ const url = "http://test.httpssvc.com:80/origin_header";
+ const originURL = "http://example.com";
+ let chan = Services.io
+ .newChannelFromURIWithProxyFlags(
+ Services.io.newURI(url),
+ null,
+ Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL,
+ null,
+ createPrincipal(originURL),
+ createPrincipal(url),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_DOCUMENT
+ )
+ .QueryInterface(Ci.nsIHttpChannel);
+ chan.referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ NetUtil.newURI(url)
+ );
+ chan.setRequestHeader("Origin", originURL, false);
+
+ let [req, buf] = await channelOpenPromise(chan);
+
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+ Assert.equal(buf, originURL);
+});
diff --git a/netwerk/test/unit/test_httpssvc_iphint.js b/netwerk/test/unit/test_httpssvc_iphint.js
new file mode 100644
index 0000000000..13d9a7e648
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_iphint.js
@@ -0,0 +1,350 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let h2Port;
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+add_setup(async function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ trr_test_setup();
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.disablePrefetch");
+ await trrServer.stop();
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+});
+
+// Test if IP hint addresses can be accessed as regular A/AAAA records.
+add_task(async function testStoreIPHint() {
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.IPHint.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.IPHint.com",
+ ttl: 999,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.IPHint.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] },
+ { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] },
+ ],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.IPHint.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).ttl, 999);
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "test.IPHint.com");
+ Assert.equal(answer[0].values.length, 4);
+ Assert.deepEqual(
+ answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
+ ["h2", "h3"],
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[1].QueryInterface(Ci.nsISVCParamPort).port,
+ 8888,
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[2].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
+ .address,
+ "1.2.3.4",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[2].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[1]
+ .address,
+ "5.6.7.8",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
+ .address,
+ "::1",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[1]
+ .address,
+ "fe80::794f:6d2c:3d5e:7836",
+ "got correct answer"
+ );
+
+ async function verifyAnswer(domain, flags, expectedAddresses) {
+ // eslint-disable-next-line no-shadow
+ let { inRecord } = await new TRRDNSListener(domain, {
+ flags,
+ expectedSuccess: false,
+ });
+ Assert.ok(inRecord);
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let addresses = [];
+ while (inRecord.hasMore()) {
+ addresses.push(inRecord.getNextAddrAsString());
+ }
+ Assert.deepEqual(addresses, expectedAddresses);
+ Assert.equal(inRecord.ttl, 999);
+ }
+
+ await verifyAnswer("test.IPHint.com", Ci.nsIDNSService.RESOLVE_IP_HINT, [
+ "1.2.3.4",
+ "5.6.7.8",
+ "::1",
+ "fe80::794f:6d2c:3d5e:7836",
+ ]);
+
+ await verifyAnswer(
+ "test.IPHint.com",
+ Ci.nsIDNSService.RESOLVE_IP_HINT | Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ ["::1", "fe80::794f:6d2c:3d5e:7836"]
+ );
+
+ await verifyAnswer(
+ "test.IPHint.com",
+ Ci.nsIDNSService.RESOLVE_IP_HINT | Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ ["1.2.3.4", "5.6.7.8"]
+ );
+
+ info("checking that IPv6 hints are ignored when disableIPv6 is true");
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ await trrServer.registerDoHAnswers("testv6.IPHint.com", "HTTPS", {
+ answers: [
+ {
+ name: "testv6.IPHint.com",
+ ttl: 999,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "testv6.IPHint.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] },
+ { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] },
+ ],
+ },
+ },
+ ],
+ });
+
+ ({ inRecord } = await new TRRDNSListener("testv6.IPHint.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+ Services.prefs.setBoolPref("network.dns.disableIPv6", false);
+
+ await verifyAnswer("testv6.IPHint.com", Ci.nsIDNSService.RESOLVE_IP_HINT, [
+ "1.2.3.4",
+ "5.6.7.8",
+ ]);
+
+ await verifyAnswer(
+ "testv6.IPHint.com",
+ Ci.nsIDNSService.RESOLVE_IP_HINT | Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ ["1.2.3.4", "5.6.7.8"]
+ );
+
+ await trrServer.stop();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+// Test if we can connect to the server with the IP hint address.
+add_task(async function testConnectionWithIPHint() {
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://127.0.0.1:" + h2Port + "/httpssvc_use_iphint"
+ );
+
+ // Resolving test.iphint.com should be failed.
+ let { inStatus } = await new TRRDNSListener("test.iphint.com", {
+ expectedSuccess: false,
+ });
+ Assert.equal(
+ inStatus,
+ Cr.NS_ERROR_UNKNOWN_HOST,
+ "status is NS_ERROR_UNKNOWN_HOST"
+ );
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ // The connection should be succeeded since the IP hint is 127.0.0.1.
+ let chan = makeChan(`http://test.iphint.com:8080/server-timing`);
+ // Note that the partitionKey stored in DNS cache would be
+ // "%28https%2Ciphint.com%29". The http request to test.iphint.com will be
+ // upgraded to https and the ip hint address will be used by the https
+ // request in the end.
+ let [req] = await channelOpenPromise(chan);
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+
+ await trrServer.stop();
+});
+
+// Test the case that we failed to use IP Hint address because DNS cache
+// is bypassed.
+add_task(async function testIPHintWithFreshDNS() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ // To make sure NS_HTTP_REFRESH_DNS not be cleared.
+ Services.prefs.setBoolPref("network.dns.disablePrefetch", true);
+
+ await trrServer.registerDoHAnswers("test.iphint.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.iphint.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "svc.iphint.net",
+ values: [],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("svc.iphint.net", "HTTPS", {
+ answers: [
+ {
+ name: "svc.iphint.net",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "svc.iphint.net",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ { key: "ipv4hint", value: "127.0.0.1" },
+ ],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.iphint.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "svc.iphint.net");
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ let chan = makeChan(`https://test.iphint.org/server-timing`);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ let [req] = await channelOpenPromise(
+ chan,
+ CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL
+ );
+ // Failed because there no A record for "svc.iphint.net".
+ Assert.equal(req.status, Cr.NS_ERROR_UNKNOWN_HOST);
+
+ await trrServer.registerDoHAnswers("svc.iphint.net", "A", {
+ answers: [
+ {
+ name: "svc.iphint.net",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ chan = makeChan(`https://test.iphint.org/server-timing`);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_httpssvc_priority.js b/netwerk/test/unit/test_httpssvc_priority.js
new file mode 100644
index 0000000000..401e23f85c
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_priority.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+});
+
+add_task(async function testPriorityAndECHConfig() {
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.priority.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.priority.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.p1.com",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ {
+ name: "test.priority.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 4,
+ name: "test.p4.com",
+ values: [{ key: "echconfig", value: "456..." }],
+ },
+ },
+ {
+ name: "test.priority.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.p3.com",
+ values: [{ key: "ipv4hint", value: "1.2.3.4" }],
+ },
+ },
+ {
+ name: "test.priority.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.p2.com",
+ values: [{ key: "echconfig", value: "123..." }],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.priority.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer.length, 4);
+
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "test.p1.com");
+
+ Assert.equal(answer[1].priority, 2);
+ Assert.equal(answer[1].name, "test.p2.com");
+
+ Assert.equal(answer[2].priority, 3);
+ Assert.equal(answer[2].name, "test.p3.com");
+
+ Assert.equal(answer[3].priority, 4);
+ Assert.equal(answer[3].name, "test.p4.com");
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.dns.clearCache(true);
+ ({ inRecord } = await new TRRDNSListener("test.priority.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+
+ answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer.length, 4);
+
+ Assert.equal(answer[0].priority, 2);
+ Assert.equal(answer[0].name, "test.p2.com");
+ Assert.equal(
+ answer[0].values[0].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
+ "123...",
+ "got correct answer"
+ );
+
+ Assert.equal(answer[1].priority, 4);
+ Assert.equal(answer[1].name, "test.p4.com");
+ Assert.equal(
+ answer[1].values[0].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
+ "456...",
+ "got correct answer"
+ );
+
+ Assert.equal(answer[2].priority, 1);
+ Assert.equal(answer[2].name, "test.p1.com");
+
+ Assert.equal(answer[3].priority, 3);
+ Assert.equal(answer[3].name, "test.p3.com");
+});
diff --git a/netwerk/test/unit/test_httpssvc_retry_with_ech.js b/netwerk/test/unit/test_httpssvc_retry_with_ech.js
new file mode 100644
index 0000000000..c261e29c70
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_retry_with_ech.js
@@ -0,0 +1,511 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let trrServer;
+let h3Port;
+let h3EchConfig;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+function checkSecurityInfo(chan, expectPrivateDNS, expectAcceptedECH) {
+ let securityInfo = chan.securityInfo;
+ Assert.equal(
+ securityInfo.isAcceptedEch,
+ expectAcceptedECH,
+ "ECH Status == Expected Status"
+ );
+ Assert.equal(
+ securityInfo.usedPrivateDNS,
+ expectPrivateDNS,
+ "Private DNS Status == Expected Status"
+ );
+}
+
+add_setup(async function setup() {
+ // Allow telemetry probes which may otherwise be disabled for some
+ // applications (e.g. Thunderbird).
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ trr_test_setup();
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+
+ await asyncStartTLSTestServer(
+ "EncryptedClientHelloServer",
+ "../../../security/manager/ssl/tests/unit/test_encrypted_client_hello"
+ );
+
+ h3Port = Services.env.get("MOZHTTP3_PORT_ECH");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ h3EchConfig = Services.env.get("MOZHTTP3_ECH");
+ Assert.notEqual(h3EchConfig, null);
+ Assert.notEqual(h3EchConfig, "");
+});
+
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.trr.uri");
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
+ Services.prefs.clearUserPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed"
+ );
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
+ Services.prefs.clearUserPref("security.tls.ech.grease_http3");
+ Services.prefs.clearUserPref("security.tls.ech.grease_probability");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ resolve([req, buffer]);
+ }
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+function ActivityObserver() {}
+
+ActivityObserver.prototype = {
+ activites: [],
+ observeConnectionActivity(
+ aHost,
+ aPort,
+ aSSL,
+ aHasECH,
+ aIsHttp3,
+ aActivityType,
+ aActivitySubtype,
+ aTimestamp,
+ aExtraStringData
+ ) {
+ dump(
+ "*** Connection Activity 0x" +
+ aActivityType.toString(16) +
+ " 0x" +
+ aActivitySubtype.toString(16) +
+ " " +
+ aExtraStringData +
+ "\n"
+ );
+ this.activites.push({ host: aHost, subType: aActivitySubtype });
+ },
+};
+
+function checkHttpActivities(activites, expectECH) {
+ let foundDNSAndSocket = false;
+ let foundSettingECH = false;
+ let foundConnectionCreated = false;
+ for (let activity of activites) {
+ switch (activity.subType) {
+ case Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED:
+ case Ci.nsIHttpActivityObserver
+ .ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED:
+ foundDNSAndSocket = true;
+ break;
+ case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_ECH_SET:
+ foundSettingECH = true;
+ break;
+ case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_CONNECTION_CREATED:
+ foundConnectionCreated = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ Assert.equal(foundDNSAndSocket, true, "Should have one DnsAndSock created");
+ Assert.equal(foundSettingECH, expectECH, "Should have echConfig");
+ Assert.equal(
+ foundConnectionCreated,
+ true,
+ "Should have one connection created"
+ );
+}
+
+add_task(async function testConnectWithECH() {
+ const ECH_CONFIG_FIXED =
+ "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAEAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ let observerService = Cc[
+ "@mozilla.org/network/http-activity-distributor;1"
+ ].getService(Ci.nsIHttpActivityDistributor);
+ let observer = new ActivityObserver();
+ observerService.addObserver(observer);
+ observerService.observeConnection = true;
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ // Only the last record is valid to use.
+ await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", {
+ answers: [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "ech-private.example.com",
+ values: [
+ { key: "alpn", value: "http/1.1" },
+ { key: "port", value: 8443 },
+ {
+ key: "echconfig",
+ value: ECH_CONFIG_FIXED,
+ needBase64Decode: true,
+ },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("ech-private.example.com", "A", {
+ answers: [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("ech-private.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ HandshakeTelemetryHelpers.resetHistograms();
+ let chan = makeChan(`https://ech-private.example.com`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ checkSecurityInfo(chan, true, true);
+ // Only check telemetry if network process is disabled.
+ if (!mozinfo.socketprocess_networking) {
+ HandshakeTelemetryHelpers.checkSuccess(["", "_ECH", "_FIRST_TRY"]);
+ HandshakeTelemetryHelpers.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
+ }
+
+ await trrServer.stop();
+ observerService.removeObserver(observer);
+ observerService.observeConnection = false;
+
+ let filtered = observer.activites.filter(
+ activity => activity.host === "ech-private.example.com"
+ );
+ checkHttpActivities(filtered, true);
+});
+
+add_task(async function testEchRetry() {
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ Services.dns.clearCache(true);
+
+ const ECH_CONFIG_TRUSTED_RETRY =
+ "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAMAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ // Only the last record is valid to use.
+ await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", {
+ answers: [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "ech-private.example.com",
+ values: [
+ { key: "alpn", value: "http/1.1" },
+ { key: "port", value: 8443 },
+ {
+ key: "echconfig",
+ value: ECH_CONFIG_TRUSTED_RETRY,
+ needBase64Decode: true,
+ },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("ech-private.example.com", "A", {
+ answers: [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("ech-private.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+
+ HandshakeTelemetryHelpers.resetHistograms();
+ let chan = makeChan(`https://ech-private.example.com`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ checkSecurityInfo(chan, true, true);
+ // Only check telemetry if network process is disabled.
+ if (!mozinfo.socketprocess_networking) {
+ for (let hName of ["SSL_HANDSHAKE_RESULT", "SSL_HANDSHAKE_RESULT_ECH"]) {
+ let h = Services.telemetry.getHistogramById(hName);
+ HandshakeTelemetryHelpers.assertHistogramMap(
+ h.snapshot(),
+ new Map([
+ ["0", 1],
+ ["188", 1],
+ ])
+ );
+ }
+ HandshakeTelemetryHelpers.checkEntry(["_FIRST_TRY"], 188, 1);
+ HandshakeTelemetryHelpers.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
+ }
+
+ await trrServer.stop();
+});
+
+async function H3ECHTest(
+ echConfig,
+ expectedHistKey,
+ expectedHistEntries,
+ advertiseECH
+) {
+ Services.dns.clearCache(true);
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ resetEchTelemetry();
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.dns.port_prefixed_qname_https_rr", true);
+
+ let observerService = Cc[
+ "@mozilla.org/network/http-activity-distributor;1"
+ ].getService(Ci.nsIHttpActivityDistributor);
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ let observer = new ActivityObserver();
+ observerService.addObserver(observer);
+ observerService.observeConnection = true;
+ // Clear activities for past connections
+ observer.activites = [];
+
+ let portPrefixedName = `_${h3Port}._https.public.example.com`;
+ let vals = [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ];
+ if (advertiseECH) {
+ vals.push({
+ key: "echconfig",
+ value: echConfig,
+ needBase64Decode: true,
+ });
+ }
+ // Only the last record is valid to use.
+
+ await trrServer.registerDoHAnswers(portPrefixedName, "HTTPS", {
+ answers: [
+ {
+ name: portPrefixedName,
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: ".",
+ values: vals,
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("public.example.com", "A", {
+ answers: [
+ {
+ name: "public.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("public.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ port: h3Port,
+ });
+
+ let chan = makeChan(`https://public.example.com:${h3Port}`);
+ let [req] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.protocolVersion, "h3-29");
+ checkSecurityInfo(chan, true, advertiseECH);
+
+ await trrServer.stop();
+
+ observerService.removeObserver(observer);
+ observerService.observeConnection = false;
+
+ let filtered = observer.activites.filter(
+ activity => activity.host === "public.example.com"
+ );
+ checkHttpActivities(filtered, advertiseECH);
+ await checkEchTelemetry(expectedHistKey, expectedHistEntries);
+}
+
+function resetEchTelemetry() {
+ Services.telemetry.getKeyedHistogramById("HTTP3_ECH_OUTCOME").clear();
+}
+
+async function checkEchTelemetry(histKey, histEntries) {
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ let values = Services.telemetry
+ .getKeyedHistogramById("HTTP3_ECH_OUTCOME")
+ .snapshot()[histKey];
+ if (!mozinfo.socketprocess_networking) {
+ HandshakeTelemetryHelpers.assertHistogramMap(values, histEntries);
+ }
+}
+
+add_task(async function testH3WithNoEch() {
+ Services.prefs.setBoolPref("security.tls.ech.grease_http3", false);
+ Services.prefs.setIntPref("security.tls.ech.grease_probability", 0);
+ await H3ECHTest(
+ h3EchConfig,
+ "NONE",
+ new Map([
+ ["0", 1],
+ ["1", 0],
+ ]),
+ false
+ );
+});
+
+add_task(async function testH3WithECH() {
+ await H3ECHTest(
+ h3EchConfig,
+ "REAL",
+ new Map([
+ ["0", 1],
+ ["1", 0],
+ ]),
+ true
+ );
+});
+
+add_task(async function testH3WithGreaseEch() {
+ Services.prefs.setBoolPref("security.tls.ech.grease_http3", true);
+ Services.prefs.setIntPref("security.tls.ech.grease_probability", 100);
+ await H3ECHTest(
+ h3EchConfig,
+ "GREASE",
+ new Map([
+ ["0", 1],
+ ["1", 0],
+ ]),
+ false
+ );
+});
+
+add_task(async function testH3WithECHRetry() {
+ Services.dns.clearCache(true);
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ function base64ToArray(base64) {
+ var binary_string = atob(base64);
+ var len = binary_string.length;
+ var bytes = new Uint8Array(len);
+ for (var i = 0; i < len; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+ return bytes;
+ }
+
+ let decodedConfig = base64ToArray(h3EchConfig);
+ decodedConfig[6] ^= 0x94;
+ let encoded = btoa(String.fromCharCode.apply(null, decodedConfig));
+ await H3ECHTest(
+ encoded,
+ "REAL",
+ new Map([
+ ["0", 1],
+ ["1", 1],
+ ]),
+ true
+ );
+});
diff --git a/netwerk/test/unit/test_httpssvc_retry_without_ech.js b/netwerk/test/unit/test_httpssvc_retry_without_ech.js
new file mode 100644
index 0000000000..4503c00504
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_retry_without_ech.js
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+add_setup(async function setup() {
+ trr_test_setup();
+
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+
+ // An arbitrary, non-ECH server.
+ await asyncStartTLSTestServer(
+ "DelegatedCredentialsServer",
+ "../../../security/manager/ssl/tests/unit/test_delegated_credentials"
+ );
+
+ let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
+ await nssComponent.asyncClearSSLExternalAndInternalSessionCache();
+});
+
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed"
+ );
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ resolve([req, buffer]);
+ }
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+add_task(async function testRetryWithoutECH() {
+ const ECH_CONFIG_FIXED =
+ "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAEAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed",
+ true
+ );
+
+ // Only the last record is valid to use.
+ await trrServer.registerDoHAnswers(
+ "delegated-disabled.example.com",
+ "HTTPS",
+ {
+ answers: [
+ {
+ name: "delegated-disabled.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "delegated-disabled.example.com",
+ values: [
+ {
+ key: "echconfig",
+ value: ECH_CONFIG_FIXED,
+ needBase64Decode: true,
+ },
+ ],
+ },
+ },
+ ],
+ }
+ );
+
+ await trrServer.registerDoHAnswers("delegated-disabled.example.com", "A", {
+ answers: [
+ {
+ name: "delegated-disabled.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("delegated-disabled.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://delegated-disabled.example.com:8443`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ let securityInfo = chan.securityInfo;
+
+ Assert.ok(
+ !securityInfo.isAcceptedEch,
+ "This host should not have accepted ECH"
+ );
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_httpsuspend.js b/netwerk/test/unit/test_httpsuspend.js
new file mode 100644
index 0000000000..581caf906e
--- /dev/null
+++ b/netwerk/test/unit/test_httpsuspend.js
@@ -0,0 +1,86 @@
+// This file ensures that suspending a channel directly after opening it
+// suspends future notifications correctly.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+const MIN_TIME_DIFFERENCE = 3000;
+const RESUME_DELAY = 5000;
+
+var listener = {
+ _lastEvent: 0,
+ _gotData: false,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ this._lastEvent = Date.now();
+ request.QueryInterface(Ci.nsIRequest);
+
+ // Insert a delay between this and the next callback to ensure message buffering
+ // works correctly
+ request.suspend();
+ request.suspend();
+ do_timeout(RESUME_DELAY, function () {
+ request.resume();
+ });
+ do_timeout(RESUME_DELAY + 1000, function () {
+ request.resume();
+ });
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ Assert.ok(Date.now() - this._lastEvent >= MIN_TIME_DIFFERENCE);
+ read_stream(stream, count);
+
+ // Ensure that suspending and resuming inside a callback works correctly
+ request.suspend();
+ request.suspend();
+ request.resume();
+ request.resume();
+
+ this._gotData = true;
+ },
+
+ onStopRequest(request, status) {
+ Assert.ok(this._gotData);
+ httpserv.stop(do_test_finished);
+ },
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/woo", data);
+ httpserv.start(-1);
+
+ var chan = makeChan(URL + "/woo");
+ chan.QueryInterface(Ci.nsIRequest);
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function data(metadata, response) {
+ let httpbody = "0123456789";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
diff --git a/netwerk/test/unit/test_idn_blacklist.js b/netwerk/test/unit/test_idn_blacklist.js
new file mode 100644
index 0000000000..565407ac3b
--- /dev/null
+++ b/netwerk/test/unit/test_idn_blacklist.js
@@ -0,0 +1,168 @@
+// Test that URLs containing characters in the IDN blacklist are
+// always displayed as punycode
+
+"use strict";
+
+const testcases = [
+ // Original Punycode or
+ // normalized form
+ //
+ ["\u00BC", "xn--14-c6t"],
+ ["\u00BD", "xn--12-c6t"],
+ ["\u00BE", "xn--34-c6t"],
+ ["\u01C3", "xn--ija"],
+ ["\u02D0", "xn--6qa"],
+ ["\u0337", "xn--4ta"],
+ ["\u0338", "xn--5ta"],
+ ["\u0589", "xn--3bb"],
+ ["\u05C3", "xn--rdb"],
+ ["\u05F4", "xn--5eb"],
+ ["\u0609", "xn--rfb"],
+ ["\u060A", "xn--sfb"],
+ ["\u066A", "xn--jib"],
+ ["\u06D4", "xn--klb"],
+ ["\u0701", "xn--umb"],
+ ["\u0702", "xn--vmb"],
+ ["\u0703", "xn--wmb"],
+ ["\u0704", "xn--xmb"],
+ ["\u115F", "xn--osd"],
+ ["\u1160", "xn--psd"],
+ ["\u1735", "xn--d0e"],
+ ["\u2027", "xn--svg"],
+ ["\u2028", "xn--tvg"],
+ ["\u2029", "xn--uvg"],
+ ["\u2039", "xn--bwg"],
+ ["\u203A", "xn--cwg"],
+ ["\u2041", "xn--jwg"],
+ ["\u2044", "xn--mwg"],
+ ["\u2052", "xn--0wg"],
+ ["\u2153", "xn--13-c6t"],
+ ["\u2154", "xn--23-c6t"],
+ ["\u2155", "xn--15-c6t"],
+ ["\u2156", "xn--25-c6t"],
+ ["\u2157", "xn--35-c6t"],
+ ["\u2158", "xn--45-c6t"],
+ ["\u2159", "xn--16-c6t"],
+ ["\u215A", "xn--56-c6t"],
+ ["\u215B", "xn--18-c6t"],
+ ["\u215C", "xn--38-c6t"],
+ ["\u215D", "xn--58-c6t"],
+ ["\u215E", "xn--78-c6t"],
+ ["\u215F", "xn--1-zjn"],
+ ["\u2215", "xn--w9g"],
+ ["\u2236", "xn--ubh"],
+ ["\u23AE", "xn--lmh"],
+ ["\u2571", "xn--hzh"],
+ ["\u29F6", "xn--jxi"],
+ ["\u29F8", "xn--lxi"],
+ ["\u2AFB", "xn--z4i"],
+ ["\u2AFD", "xn--14i"],
+ ["\u2FF0", "xn--85j"],
+ ["\u2FF1", "xn--95j"],
+ ["\u2FF2", "xn--b6j"],
+ ["\u2FF3", "xn--c6j"],
+ ["\u2FF4", "xn--d6j"],
+ ["\u2FF5", "xn--e6j"],
+ ["\u2FF6", "xn--f6j"],
+ ["\u2FF7", "xn--g6j"],
+ ["\u2FF8", "xn--h6j"],
+ ["\u2FF9", "xn--i6j"],
+ ["\u2FFA", "xn--j6j"],
+ ["\u2FFB", "xn--k6j"],
+ ["\u3014", "xn--96j"],
+ ["\u3015", "xn--b7j"],
+ ["\u3033", "xn--57j"],
+ ["\u3164", "xn--psd"],
+ ["\u321D", "xn--()-357j35d"],
+ ["\u321E", "xn--()-357jf36c"],
+ ["\u33AE", "xn--rads-id9a"],
+ ["\u33AF", "xn--rads2-4d6b"],
+ ["\u33C6", "xn--ckg-tc2a"],
+ ["\u33DF", "xn--am-6bv"],
+ ["\uA789", "xn--058a"],
+ ["\uFE3F", "xn--x6j"],
+ ["\uFE5D", "xn--96j"],
+ ["\uFE5E", "xn--b7j"],
+ ["\uFFA0", "xn--psd"],
+ ["\uFFF9", "xn--vn7c"],
+ ["\uFFFA", "xn--wn7c"],
+ ["\uFFFB", "xn--xn7c"],
+ ["\uFFFC", "xn--yn7c"],
+ ["\uFFFD", "xn--zn7c"],
+
+ // Characters from the IDN blacklist that normalize to ASCII
+ // If we start using STD3ASCIIRules these will be blocked (bug 316444)
+ ["\u00A0", " "],
+ ["\u2000", " "],
+ ["\u2001", " "],
+ ["\u2002", " "],
+ ["\u2003", " "],
+ ["\u2004", " "],
+ ["\u2005", " "],
+ ["\u2006", " "],
+ ["\u2007", " "],
+ ["\u2008", " "],
+ ["\u2009", " "],
+ ["\u200A", " "],
+ ["\u2024", "."],
+ ["\u202F", " "],
+ ["\u205F", " "],
+ ["\u3000", " "],
+ ["\u3002", "."],
+ ["\uFE14", ";"],
+ ["\uFE15", "!"],
+ ["\uFF0E", "."],
+ ["\uFF0F", "/"],
+ ["\uFF61", "."],
+
+ // Characters from the IDN blacklist that are stripped by Nameprep
+ ["\u200B", ""],
+ ["\uFEFF", ""],
+];
+
+function run_test() {
+ var pbi = Services.prefs;
+ var oldProfile = pbi.getCharPref(
+ "network.IDN.restriction_profile",
+ "moderate"
+ );
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+
+ pbi.setCharPref("network.IDN.restriction_profile", "moderate");
+
+ for (var j = 0; j < testcases.length; ++j) {
+ var test = testcases[j];
+ var URL = test[0] + ".com";
+ var punycodeURL = test[1] + ".com";
+ var isASCII = {};
+
+ var result;
+ try {
+ result = idnService.convertToDisplayIDN(URL, isASCII);
+ } catch (e) {
+ result = ".com";
+ }
+ // If the punycode URL is equivalent to \ufffd.com (i.e. the
+ // blacklisted character has been replaced by a unicode
+ // REPLACEMENT CHARACTER, skip the test
+ if (result != "xn--zn7c.com") {
+ if (punycodeURL.substr(0, 4) == "xn--") {
+ // test convertToDisplayIDN with a Unicode URL and with a
+ // Punycode URL if we have one
+ equal(escape(result), escape(punycodeURL));
+
+ result = idnService.convertToDisplayIDN(punycodeURL, isASCII);
+ equal(escape(result), escape(punycodeURL));
+ } else {
+ // The "punycode" URL isn't punycode. This happens in testcases
+ // where the Unicode URL has become normalized to an ASCII URL,
+ // so, even though expectedUnicode is true, the expected result
+ // is equal to punycodeURL
+ equal(escape(result), escape(punycodeURL));
+ }
+ }
+ }
+ pbi.setCharPref("network.IDN.restriction_profile", oldProfile);
+}
diff --git a/netwerk/test/unit/test_idn_spoof.js b/netwerk/test/unit/test_idn_spoof.js
new file mode 100644
index 0000000000..8512d3272e
--- /dev/null
+++ b/netwerk/test/unit/test_idn_spoof.js
@@ -0,0 +1,1052 @@
+// Copyright 2015 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// https://source.chromium.org/chromium/chromium/src/+/main:LICENSE
+
+// Tests nsIIDNService
+// Imported from https://source.chromium.org/chromium/chromium/src/+/main:components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc;drc=e544837967287f956ba69af3b228b202e8e7cf1a
+
+"use strict";
+
+const idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+);
+
+const kSafe = 1;
+const kUnsafe = 2;
+const kInvalid = 3;
+
+// prettier-ignore
+let testCases = [
+ // No IDN
+ ["www.google.com", "www.google.com", kSafe],
+ ["www.google.com.", "www.google.com.", kSafe],
+ [".", ".", kSafe],
+ ["", "", kSafe],
+ // Invalid IDN
+ ["xn--example-.com", "xn--example-.com", kInvalid],
+ // IDN
+ // Hanzi (Traditional Chinese)
+ ["xn--1lq90ic7f1rc.cn", "\u5317\u4eac\u5927\u5b78.cn", kSafe],
+ // Hanzi ('video' in Simplified Chinese)
+ ["xn--cy2a840a.com", "\u89c6\u9891.com", kSafe],
+ // Hanzi + '123'
+ ["www.xn--123-p18d.com", "www.\u4e00123.com", kSafe],
+ // Hanzi + Latin : U+56FD is simplified
+ ["www.xn--hello-9n1hm04c.com", "www.hello\u4e2d\u56fd.com", kSafe],
+ // Kanji + Kana (Japanese)
+ ["xn--l8jvb1ey91xtjb.jp", "\u671d\u65e5\u3042\u3055\u3072.jp", kSafe],
+ // Katakana including U+30FC
+ ["xn--tckm4i2e.jp", "\u30b3\u30de\u30fc\u30b9.jp", kSafe],
+ ["xn--3ck7a7g.jp", "\u30ce\u30f3\u30bd.jp", kSafe],
+ // Katakana + Latin (Japanese)
+ ["xn--e-efusa1mzf.jp", "e\u30b3\u30de\u30fc\u30b9.jp", kSafe],
+ ["xn--3bkxe.jp", "\u30c8\u309a.jp", kSafe],
+ // Hangul (Korean)
+ ["www.xn--or3b17p6jjc.kr", "www.\uc804\uc790\uc815\ubd80.kr", kSafe],
+ // b<u-umlaut>cher (German)
+ ["xn--bcher-kva.de", "b\u00fccher.de", kSafe],
+ // a with diaeresis
+ ["www.xn--frgbolaget-q5a.se", "www.f\u00e4rgbolaget.se", kSafe],
+ // c-cedilla (French)
+ ["www.xn--alliancefranaise-npb.fr", "www.alliancefran\u00e7aise.fr", kSafe],
+ // caf'e with acute accent (French)
+ ["xn--caf-dma.fr", "caf\u00e9.fr", kSafe],
+ // c-cedillla and a with tilde (Portuguese)
+ ["xn--poema-9qae5a.com.br", "p\u00e3oema\u00e7\u00e3.com.br", kSafe],
+ // s with caron
+ ["xn--achy-f6a.com", "\u0161achy.com", kSafe],
+ ["xn--kxae4bafwg.gr", "\u03bf\u03c5\u03c4\u03bf\u03c0\u03af\u03b1.gr", kSafe],
+ // Eutopia + 123 (Greek)
+ ["xn---123-pldm0haj2bk.gr", "\u03bf\u03c5\u03c4\u03bf\u03c0\u03af\u03b1-123.gr", kSafe],
+ // Cyrillic (Russian)
+ ["xn--n1aeec9b.r", "\u0442\u043e\u0440\u0442\u044b.r", kSafe],
+ // Cyrillic + 123 (Russian)
+ ["xn---123-45dmmc5f.r", "\u0442\u043e\u0440\u0442\u044b-123.r", kSafe],
+ // 'president' in Russian. Is a wholescript confusable, but allowed.
+ ["xn--d1abbgf6aiiy.xn--p1ai", "\u043f\u0440\u0435\u0437\u0438\u0434\u0435\u043d\u0442.\u0440\u0444", kSafe],
+ // Arabic
+ ["xn--mgba1fmg.eg", "\u0627\u0641\u0644\u0627\u0645.eg", kSafe],
+ // Hebrew
+ ["xn--4dbib.he", "\u05d5\u05d0\u05d4.he", kSafe],
+ // Hebrew + Common
+ ["xn---123-ptf2c5c6bt.il", "\u05e2\u05d1\u05e8\u05d9\u05ea-123.il", kSafe],
+ // Thai
+ ["xn--12c2cc4ag3b4ccu.th", "\u0e2a\u0e32\u0e22\u0e01\u0e32\u0e23\u0e1a\u0e34\u0e19.th", kSafe],
+ // Thai + Common
+ ["xn---123-9goxcp8c9db2r.th", "\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22-123.th", kSafe],
+ // Devangari (Hindi)
+ ["www.xn--l1b6a9e1b7c.in", "www.\u0905\u0915\u094b\u0932\u093e.in", kSafe],
+ // Devanagari + Common
+ ["xn---123-kbjl2j0bl2k.in", "\u0939\u093f\u0928\u094d\u0926\u0940-123.in", kSafe],
+
+ // Block mixed numeric + numeric lookalike (12.com, using U+0577).
+ ["xn--1-xcc.com", "1\u0577.com", kUnsafe, "DISABLED"],
+
+ // Block mixed numeric lookalike + numeric (੨0.com, uses U+0A68).
+ ["xn--0-6ee.com", "\u0a680.com", kUnsafe],
+ // Block fully numeric lookalikes (৪੨.com using U+09EA and U+0A68).
+ ["xn--47b6w.com", "\u09ea\u0a68.com", kUnsafe],
+ // Block single script digit lookalikes (using three U+0A68 characters).
+ ["xn--qccaa.com", "\u0a68\u0a68\u0a68.com", kUnsafe, "DISABLED"],
+
+ // URL test with mostly numbers and one confusable character
+ // Georgian 'd' 4000.com
+ ["xn--4000-pfr.com", "\u10eb4000.com", kUnsafe, "DISABLED"],
+
+ // What used to be 5 Aspirational scripts in the earlier versions of UAX 31.
+ // UAX 31 does not define aspirational scripts any more.
+ // See http://www.unicode.org/reports/tr31/#Aspirational_Use_Scripts .
+ // Unified Canadian Syllabary
+ ["xn--dfe0tte.ca", "\u1456\u14c2\u14ef.ca", kUnsafe],
+ // Tifinagh
+ ["xn--4ljxa2bb4a6bxb.ma", "\u2d5c\u2d49\u2d3c\u2d49\u2d4f\u2d30\u2d56.ma", kUnsafe],
+ // Tifinagh with a disallowed character(U+2D6F)
+ ["xn--hmjzaby5d5f.ma", "\u2d5c\u2d49\u2d3c\u2d6f\u2d49\u2d4f.ma", kInvalid],
+
+ // Yi
+ ["xn--4o7a6e1x64c.cn", "\ua188\ua320\ua071\ua0b7.cn", kUnsafe],
+ // Mongolian - 'ordu' (place, camp)
+ ["xn--56ec8bp.cn", "\u1823\u1837\u1833\u1824.cn", kUnsafe],
+ // Mongolian with a disallowed character
+ ["xn--95e5de3ds.cn", "\u1823\u1837\u1804\u1833\u1824.cn", kUnsafe],
+ // Miao/Pollad
+ ["xn--2u0fpf0a.cn", "\U00016f04\U00016f62\U00016f59.cn", kUnsafe],
+
+ // Script mixing tests
+ // The following script combinations are allowed.
+ // HIGHLY_RESTRICTIVE with Latin limited to ASCII-Latin.
+ // ASCII-Latin + Japn (Kana + Han)
+ // ASCII-Latin + Kore (Hangul + Han)
+ // ASCII-Latin + Han + Bopomofo
+ // "payp<alpha>l.com"
+ ["xn--paypl-g9d.com", "payp\u03b1l.com", kUnsafe],
+ // google.gr with Greek omicron and epsilon
+ ["xn--ggl-6xc1ca.gr", "g\u03bf\u03bfgl\u03b5.gr", kUnsafe],
+ // google.ru with Cyrillic o
+ ["xn--ggl-tdd6ba.r", "g\u043e\u043egl\u0435.r", kUnsafe],
+ // h<e with acute>llo<China in Han>.cn
+ ["xn--hllo-bpa7979ih5m.cn", "h\u00e9llo\u4e2d\u56fd.cn", kUnsafe, "DISABLED"],
+ // <Greek rho><Cyrillic a><Cyrillic u>.ru
+ ["xn--2xa6t2b.r", "\u03c1\u0430\u0443.r", kUnsafe],
+ // Georgian + Latin
+ ["xn--abcef-vuu.test", "abc\u10ebef.test", kUnsafe],
+ // Hangul + Latin
+ ["xn--han-eb9ll88m.kr", "\ud55c\uae00han.kr", kSafe],
+ // Hangul + Latin + Han with IDN ccTLD
+ ["xn--han-or0kq92gkm3c.xn--3e0b707e", "\ud55c\uae00han\u97d3.\ud55c\uad6d", kSafe],
+ // non-ASCII Latin + Hangul
+ ["xn--caf-dma9024xvpg.kr", "caf\u00e9\uce74\ud398.kr", kUnsafe, "DISABLED"],
+ // Hangul + Hiragana
+ ["xn--y9j3b9855e.kr", "\ud55c\u3072\u3089.kr", kUnsafe],
+ // <Hiragana>.<Hangul> is allowed because script mixing check is per label.
+ ["xn--y9j3b.xn--3e0b707e", "\u3072\u3089.\ud55c\uad6d", kSafe],
+ // Traditional Han + Latin
+ ["xn--hanzi-u57ii69i.tw", "\u6f22\u5b57hanzi.tw", kSafe],
+ // Simplified Han + Latin
+ ["xn--hanzi-u57i952h.cn", "\u6c49\u5b57hanzi.cn", kSafe],
+ // Simplified Han + Traditonal Han
+ ["xn--hanzi-if9kt8n.cn", "\u6c49\u6f22hanzi.cn", kSafe],
+ // Han + Hiragana + Katakana + Latin
+ ["xn--kanji-ii4dpizfq59yuykqr4b.jp", "\u632f\u308a\u4eee\u540d\u30ab\u30bfkanji.jp", kSafe],
+ // Han + Bopomofo
+ ["xn--5ekcde0577e87tc.tw", "\u6ce8\u97f3\u3105\u3106\u3107\u3108.tw", kSafe],
+ // Han + Latin + Bopomofo
+ ["xn--bopo-ty4cghi8509kk7xd.tw", "\u6ce8\u97f3bopo\u3105\u3106\u3107\u3108.tw", kSafe],
+ // Latin + Bopomofo
+ ["xn--bopomofo-hj5gkalm.tw", "bopomofo\u3105\u3106\u3107\u3108.tw", kSafe],
+ // Bopomofo + Katakana
+ ["xn--lcka3d1bztghi.tw", "\u3105\u3106\u3107\u3108\u30ab\u30bf\u30ab\u30ca.tw", kUnsafe],
+ // Bopomofo + Hangul
+ ["xn--5ekcde4543qbec.tw", "\u3105\u3106\u3107\u3108\uc8fc\uc74c.tw", kUnsafe],
+ // Devanagari + Latin
+ ["xn--ab-3ofh8fqbj6h.in", "ab\u0939\u093f\u0928\u094d\u0926\u0940.in", kUnsafe],
+ // Thai + Latin
+ ["xn--ab-jsi9al4bxdb6n.th", "ab\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22.th", kUnsafe],
+ // Armenian + Latin
+ ["xn--bs-red.com", "b\u057ds.com", kUnsafe],
+ // Tibetan + Latin
+ ["xn--foo-vkm.com", "foo\u0f37.com", kUnsafe],
+ // Oriya + Latin
+ ["xn--fo-h3g.com", "fo\u0b66.com", kUnsafe],
+ // Gujarati + Latin
+ ["xn--fo-isg.com", "fo\u0ae6.com", kUnsafe],
+ // <vitamin in Katakana>b1.com
+ ["xn--b1-xi4a7cvc9f.com", "\u30d3\u30bf\u30df\u30f3b1.com", kSafe],
+ // Devanagari + Han
+ ["xn--t2bes3ds6749n.com", "\u0930\u094b\u0932\u0947\u76e7\u0938.com", kUnsafe],
+ // Devanagari + Bengali
+ ["xn--11b0x.in", "\u0915\u0995.in", kUnsafe],
+ // Canadian Syllabary + Latin
+ ["xn--ab-lym.com", "ab\u14bf.com", kUnsafe],
+ ["xn--ab1-p6q.com", "ab1\u14bf.com", kUnsafe],
+ ["xn--1ab-m6qd.com", "\u14bf1ab\u14bf.com", kUnsafe],
+ ["xn--ab-jymc.com", "\u14bfab\u14bf.com", kUnsafe],
+ // Tifinagh + Latin
+ ["xn--liy-bq1b.com", "li\u2d4fy.com", kUnsafe],
+ ["xn--rol-cq1b.com", "rol\u2d4f.com", kUnsafe],
+ ["xn--ily-8p1b.com", "\u2d4fily.com", kUnsafe],
+ ["xn--1ly-8p1b.com", "\u2d4f1ly.com", kUnsafe],
+
+ // Invisibility check
+ // Thai tone mark malek(U+0E48) repeated
+ ["xn--03c0b3ca.th", "\u0e23\u0e35\u0e48\u0e48.th", kUnsafe],
+ // Accute accent repeated
+ ["xn--a-xbba.com", "a\u0301\u0301.com", kInvalid],
+ // 'a' with acuted accent + another acute accent
+ ["xn--1ca20i.com", "\u00e1\u0301.com", kUnsafe, "DISABLED"],
+ // Combining mark at the beginning
+ ["xn--abc-fdc.jp", "\u0300abc.jp", kInvalid],
+
+ // The following three are detected by |dangerous_pattern| regex, but
+ // can be regarded as an extension of blocking repeated diacritic marks.
+ // i followed by U+0307 (combining dot above)
+ ["xn--pixel-8fd.com", "pi\u0307xel.com", kUnsafe],
+ // U+0131 (dotless i) followed by U+0307
+ ["xn--pxel-lza43z.com", "p\u0131\u0307xel.com", kUnsafe],
+ // j followed by U+0307 (combining dot above)
+ ["xn--jack-qwc.com", "j\u0307ack.com", kUnsafe],
+ // l followed by U+0307
+ ["xn--lace-qwc.com", "l\u0307ace.com", kUnsafe],
+
+ // Do not allow a combining mark after dotless i/j.
+ ["xn--pxel-lza29y.com", "p\u0131\u0300xel.com", kUnsafe],
+ ["xn--ack-gpb42h.com", "\u0237\u0301ack.com", kUnsafe],
+
+ // Mixed script confusable
+ // google with Armenian Small Letter Oh(U+0585)
+ ["xn--gogle-lkg.com", "g\u0585ogle.com", kUnsafe],
+ ["xn--range-kkg.com", "\u0585range.com", kUnsafe],
+ ["xn--cucko-pkg.com", "cucko\u0585.com", kUnsafe],
+ // Latin 'o' in Armenian.
+ ["xn--o-ybcg0cu0cq.com", "o\u0580\u0574\u0578\u0582\u0566\u0568.com", kUnsafe],
+ // Hiragana HE(U+3078) mixed with Katakana
+ ["xn--49jxi3as0d0fpc.com", "\u30e2\u30d2\u30fc\u30c8\u3078\u30d6\u30f3.com", kUnsafe, "DISABLED"],
+
+ // U+30FC should be preceded by a Hiragana/Katakana.
+ // Katakana + U+30FC + Han
+ ["xn--lck0ip02qw5ya.jp", "\u30ab\u30fc\u91ce\u7403.jp", kSafe],
+ // Hiragana + U+30FC + Han
+ ["xn--u8j5tr47nw5ya.jp", "\u304b\u30fc\u91ce\u7403.jp", kSafe],
+ // U+30FC + Han
+ ["xn--weka801xo02a.com", "\u30fc\u52d5\u753b\u30fc.com", kUnsafe],
+ // Han + U+30FC + Han
+ ["xn--wekz60nb2ay85atj0b.jp", "\u65e5\u672c\u30fc\u91ce\u7403.jp", kUnsafe],
+ // U+30FC at the beginning
+ ["xn--wek060nb2a.jp", "\u30fc\u65e5\u672c.jp", kUnsafe],
+ // Latin + U+30FC + Latin
+ ["xn--abcdef-r64e.jp", "abc\u30fcdef.jp", kUnsafe],
+
+ // U+30FB (・) is not allowed next to Latin, but allowed otherwise.
+ // U+30FB + Han
+ ["xn--vekt920a.jp", "\u30fb\u91ce.jp", kSafe],
+ // Han + U+30FB + Han
+ ["xn--vek160nb2ay85atj0b.jp", "\u65e5\u672c\u30fb\u91ce\u7403.jp", kSafe],
+ // Latin + U+30FB + Latin
+ ["xn--abcdef-k64e.jp", "abc\u30fbdef.jp", kUnsafe, "DISABLED"],
+ // U+30FB + Latin
+ ["xn--abc-os4b.jp", "\u30fbabc.jp", kUnsafe, "DISABLED"],
+
+ // U+30FD (ヽ) is allowed only after Katakana.
+ // Katakana + U+30FD
+ ["xn--lck2i.jp", "\u30ab\u30fd.jp", kSafe],
+ // Hiragana + U+30FD
+ ["xn--u8j7t.jp", "\u304b\u30fd.jp", kUnsafe, "DISABLED"],
+ // Han + U+30FD
+ ["xn--xek368f.jp", "\u4e00\u30fd.jp", kUnsafe, "DISABLED"],
+ ["xn--a-mju.jp", "a\u30fd.jp", kUnsafe, "DISABLED"],
+ ["xn--a1-bo4a.jp", "a1\u30fd.jp", kUnsafe, "DISABLED"],
+
+ // U+30FE (ヾ) is allowed only after Katakana.
+ // Katakana + U+30FE
+ ["xn--lck4i.jp", "\u30ab\u30fe.jp", kSafe],
+ // Hiragana + U+30FE
+ ["xn--u8j9t.jp", "\u304b\u30fe.jp", kUnsafe, "DISABLED"],
+ // Han + U+30FE
+ ["xn--yek168f.jp", "\u4e00\u30fe.jp", kUnsafe, "DISABLED"],
+ ["xn--a-oju.jp", "a\u30fe.jp", kUnsafe, "DISABLED"],
+ ["xn--a1-eo4a.jp", "a1\u30fe.jp", kUnsafe, "DISABLED"],
+
+ // Cyrillic labels made of Latin-look-alike Cyrillic letters.
+ // 1) ѕсоре.com with ѕсоре in Cyrillic.
+ ["xn--e1argc3h.com", "\u0455\u0441\u043e\u0440\u0435.com", kUnsafe, "DISABLED"],
+ // 2) ѕсоре123.com with ѕсоре in Cyrillic.
+ ["xn--123-qdd8bmf3n.com", "\u0455\u0441\u043e\u0440\u0435123.com", kUnsafe, "DISABLED"],
+ // 3) ѕсоре-рау.com with ѕсоре and рау in Cyrillic.
+ ["xn----8sbn9akccw8m.com", "\u0455\u0441\u043e\u0440\u0435-\u0440\u0430\u0443.com", kUnsafe, "DISABLED"],
+ // 4) ѕсоре1рау.com with scope and pay in Cyrillic and a non-letter between
+ // them.
+ ["xn--1-8sbn9akccw8m.com", "\u0455\u0441\u043e\u0440\u0435\u0031\u0440\u0430\u0443.com", kUnsafe, "DISABLED"],
+
+ // The same as above three, but in IDN TLD (рф).
+ // 1) ѕсоре.рф with ѕсоре in Cyrillic.
+ ["xn--e1argc3h.xn--p1ai", "\u0455\u0441\u043e\u0440\u0435.\u0440\u0444", kSafe],
+ // 2) ѕсоре123.рф with ѕсоре in Cyrillic.
+ ["xn--123-qdd8bmf3n.xn--p1ai", "\u0455\u0441\u043e\u0440\u0435123.\u0440\u0444", kSafe],
+ // 3) ѕсоре-рау.рф with ѕсоре and рау in Cyrillic.
+ ["xn----8sbn9akccw8m.xn--p1ai", "\u0455\u0441\u043e\u0440\u0435-\u0440\u0430\u0443.\u0440\u0444", kSafe],
+ // 4) ѕсоре1рау.com with scope and pay in Cyrillic and a non-letter between
+ // them.
+ ["xn--1-8sbn9akccw8m.xn--p1ai", "\u0455\u0441\u043e\u0440\u0435\u0031\u0440\u0430\u0443.\u0440\u0444", kSafe],
+
+ // Same as above three, but in .ru TLD.
+ // 1) ѕсоре.ru with ѕсоре in Cyrillic.
+ ["xn--e1argc3h.r", "\u0455\u0441\u043e\u0440\u0435.r", kSafe],
+ // 2) ѕсоре123.ru with ѕсоре in Cyrillic.
+ ["xn--123-qdd8bmf3n.r", "\u0455\u0441\u043e\u0440\u0435123.r", kSafe],
+ // 3) ѕсоре-рау.ru with ѕсоре and рау in Cyrillic.
+ ["xn----8sbn9akccw8m.r", "\u0455\u0441\u043e\u0440\u0435-\u0440\u0430\u0443.r", kSafe],
+ // 4) ѕсоре1рау.com with scope and pay in Cyrillic and a non-letter between
+ // them.
+ ["xn--1-8sbn9akccw8m.r", "\u0455\u0441\u043e\u0440\u0435\u0031\u0440\u0430\u0443.r", kSafe],
+
+ // ѕсоре-рау.한국 with ѕсоре and рау in Cyrillic. The label will remain
+ // punycode while the TLD will be decoded.
+ ["xn----8sbn9akccw8m.xn--3e0b707e", "xn----8sbn9akccw8m.\ud55c\uad6d", kSafe, "DISABLED"],
+
+ // музей (museum in Russian) has characters without a Latin-look-alike.
+ ["xn--e1adhj9a.com", "\u043c\u0443\u0437\u0435\u0439.com", kSafe],
+
+ // ѕсоԗе.com is Cyrillic with Latin lookalikes.
+ ["xn--e1ari3f61c.com", "\u0455\u0441\u043e\u0517\u0435.com", kUnsafe, "DISABLED"],
+
+ // ыоԍ.com is Cyrillic with Latin lookalikes.
+ ["xn--n1az74c.com", "\u044b\u043e\u050d.com", kUnsafe],
+
+ // сю.com is Cyrillic with Latin lookalikes.
+ ["xn--q1a0a.com", "\u0441\u044e.com", kUnsafe, "DISABLED"],
+
+ // Regression test for lowercase letters in whole script confusable
+ // lookalike character lists.
+ ["xn--80a8a6a.com", "\u0430\u044c\u0441.com", kUnsafe, "DISABLED"],
+
+ // googlе.한국 where е is Cyrillic. This tests the generic case when one
+ // label is not allowed but other labels in the domain name are still
+ // decoded. Here, googlе is left in punycode but the TLD is decoded.
+ ["xn--googl-3we.xn--3e0b707e", "xn--googl-3we.\ud55c\uad6d", kSafe],
+
+ // Combining Diacritic marks after a script other than Latin-Greek-Cyrillic
+ ["xn--rsa2568fvxya.com", "\ud55c\u0307\uae00.com", kUnsafe, "DISABLED"], // 한́글.com
+ ["xn--rsa0336bjom.com", "\u6f22\u0307\u5b57.com", kUnsafe, "DISABLED"], // 漢̇字.com
+ // नागरी́.com
+ ["xn--lsa922apb7a6do.com", "\u0928\u093e\u0917\u0930\u0940\u0301.com", kUnsafe, "DISABLED"],
+
+ // Similarity checks against the list of top domains. "digklmo68.com" and
+ // 'digklmo68.co.uk" are listed for unittest in the top domain list.
+ // đigklmo68.com:
+ ["xn--igklmo68-kcb.com", "\u0111igklmo68.com", kUnsafe, "DISABLED"],
+ // www.đigklmo68.com:
+ ["www.xn--igklmo68-kcb.com", "www.\u0111igklmo68.com", kUnsafe, "DISABLED"],
+ // foo.bar.đigklmo68.com:
+ ["foo.bar.xn--igklmo68-kcb.com", "foo.bar.\u0111igklmo68.com", kUnsafe, "DISABLED"],
+ // đigklmo68.co.uk:
+ ["xn--igklmo68-kcb.co.uk", "\u0111igklmo68.co.uk", kUnsafe, "DISABLED"],
+ // mail.đigklmo68.co.uk:
+ ["mail.xn--igklmo68-kcb.co.uk", "mail.\u0111igklmo68.co.uk", kUnsafe, "DISABLED"],
+ // di̇gklmo68.com:
+ ["xn--digklmo68-6jf.com", "di\u0307gklmo68.com", kUnsafe],
+ // dig̱klmo68.com:
+ ["xn--digklmo68-7vf.com", "dig\u0331klmo68.com", kUnsafe, "DISABLED"],
+ // digĸlmo68.com:
+ ["xn--diglmo68-omb.com", "dig\u0138lmo68.com", kUnsafe],
+ // digkłmo68.com:
+ ["xn--digkmo68-9ob.com", "digk\u0142mo68.com", kUnsafe, "DISABLED"],
+ // digklṃo68.com:
+ ["xn--digklo68-l89c.com", "digkl\u1e43o68.com", kUnsafe, "DISABLED"],
+ // digklmø68.com:
+ ["xn--digklm68-b5a.com", "digklm\u00f868.com", kUnsafe, "DISABLED"],
+ // digklmoб8.com:
+ ["xn--digklmo8-h7g.com", "digklmo\u04318.com", kUnsafe],
+ // digklmo6৪.com:
+ ["xn--digklmo6-7yr.com", "digklmo6\u09ea.com", kUnsafe],
+
+ // 'islkpx123.com' is in the test domain list.
+ // 'іѕӏкрх123' can look like 'islkpx123' in some fonts.
+ ["xn--123-bed4a4a6hh40i.com", "\u0456\u0455\u04cf\u043a\u0440\u0445123.com", kUnsafe, "DISABLED"],
+
+ // 'o2.com', '28.com', '39.com', '43.com', '89.com', 'oo.com' and 'qq.com'
+ // are all explicitly added to the test domain list to aid testing of
+ // Latin-lookalikes that are numerics in other character sets and similar
+ // edge cases.
+ //
+ // Bengali:
+ ["xn--07be.com", "\u09e6\u09e8.com", kUnsafe, "DISABLED"],
+ ["xn--27be.com", "\u09e8\u09ea.com", kUnsafe, "DISABLED"],
+ ["xn--77ba.com", "\u09ed\u09ed.com", kUnsafe, "DISABLED"],
+ // Gurmukhi:
+ ["xn--qcce.com", "\u0a68\u0a6a.com", kUnsafe, "DISABLED"],
+ ["xn--occe.com", "\u0a66\u0a68.com", kUnsafe, "DISABLED"],
+ ["xn--rccd.com", "\u0a6b\u0a69.com", kUnsafe, "DISABLED"],
+ ["xn--pcca.com", "\u0a67\u0a67.com", kUnsafe, "DISABLED"],
+ // Telugu:
+ ["xn--drcb.com", "\u0c69\u0c68.com", kUnsafe, "DISABLED"],
+ // Devanagari:
+ ["xn--d4be.com", "\u0966\u0968.com", kUnsafe, "DISABLED"],
+ // Kannada:
+ ["xn--yucg.com", "\u0ce6\u0ce9.com", kUnsafe, "DISABLED"],
+ ["xn--yuco.com", "\u0ce6\u0ced.com", kUnsafe, "DISABLED"],
+ // Oriya:
+ ["xn--1jcf.com", "\u0b6b\u0b68.com", kUnsafe, "DISABLED"],
+ ["xn--zjca.com", "\u0b66\u0b66.com", kUnsafe, "DISABLED"],
+ // Gujarati:
+ ["xn--cgce.com", "\u0ae6\u0ae8.com", kUnsafe, "DISABLED"],
+ ["xn--fgci.com", "\u0ae9\u0aed.com", kUnsafe, "DISABLED"],
+ ["xn--dgca.com", "\u0ae7\u0ae7.com", kUnsafe, "DISABLED"],
+
+ // wmhtb.com
+ ["xn--l1acpvx.com", "\u0448\u043c\u043d\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // щмнть.com
+ ["xn--l1acpzs.com", "\u0449\u043c\u043d\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // шмнтв.com
+ ["xn--b1atdu1a.com", "\u0448\u043c\u043d\u0442\u0432.com", kUnsafe, "DISABLED"],
+ // шмԋтв.com
+ ["xn--b1atsw09g.com", "\u0448\u043c\u050b\u0442\u0432.com", kUnsafe],
+ // шмԧтв.com
+ ["xn--b1atsw03i.com", "\u0448\u043c\u0527\u0442\u0432.com", kUnsafe, "DISABLED"],
+ // шмԋԏв.com
+ ["xn--b1at9a12dua.com", "\u0448\u043c\u050b\u050f\u0432.com", kUnsafe],
+ // ഠട345.com
+ ["xn--345-jtke.com", "\u0d20\u0d1f345.com", kUnsafe, "DISABLED"],
+
+ // Test additional confusable LGC characters (most of them without
+ // decomposition into base + diacritc mark). The corresponding ASCII
+ // domain names are in the test top domain list.
+ // ϼκαωχ.com
+ ["xn--mxar4bh6w.com", "\u03fc\u03ba\u03b1\u03c9\u03c7.com", kUnsafe, "DISABLED"],
+ // þħĸŧƅ.com
+ ["xn--vda6f3b2kpf.com", "\u00fe\u0127\u0138\u0167\u0185.com", kUnsafe],
+ // þhktb.com
+ ["xn--hktb-9ra.com", "\u00fehktb.com", kUnsafe, "DISABLED"],
+ // pħktb.com
+ ["xn--pktb-5xa.com", "p\u0127ktb.com", kUnsafe, "DISABLED"],
+ // phĸtb.com
+ ["xn--phtb-m0a.com", "ph\u0138tb.com", kUnsafe],
+ // phkŧb.com
+ ["xn--phkb-d7a.com", "phk\u0167b.com", kUnsafe, "DISABLED"],
+ // phktƅ.com
+ ["xn--phkt-ocb.com", "phkt\u0185.com", kUnsafe],
+ // ҏнкть.com
+ ["xn--j1afq4bxw.com", "\u048f\u043d\u043a\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏћкть.com
+ ["xn--j1aq4a7cvo.com", "\u048f\u045b\u043a\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏңкть.com
+ ["xn--j1aq4azund.com", "\u048f\u04a3\u043a\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏҥкть.com
+ ["xn--j1aq4azuxd.com", "\u048f\u04a5\u043a\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏӈкть.com
+ ["xn--j1aq4azuyj.com", "\u048f\u04c8\u043a\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏԧкть.com
+ ["xn--j1aq4azu9z.com", "\u048f\u0527\u043a\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏԩкть.com
+ ["xn--j1aq4azuq0a.com", "\u048f\u0529\u043a\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнқть.com
+ ["xn--m1ak4azu6b.com", "\u048f\u043d\u049b\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнҝть.com
+ ["xn--m1ak4azunc.com", "\u048f\u043d\u049d\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнҟть.com
+ ["xn--m1ak4azuxc.com", "\u048f\u043d\u049f\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнҡть.com
+ ["xn--m1ak4azu7c.com", "\u048f\u043d\u04a1\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнӄть.com
+ ["xn--m1ak4azu8i.com", "\u048f\u043d\u04c4\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнԟть.com
+ ["xn--m1ak4azuzy.com", "\u048f\u043d\u051f\u0442\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнԟҭь.com
+ ["xn--m1a4a4nnery.com", "\u048f\u043d\u051f\u04ad\u044c.com", kUnsafe, "DISABLED"],
+ // ҏнԟҭҍ.com
+ ["xn--m1a4ne5jry.com", "\u048f\u043d\u051f\u04ad\u048d.com", kUnsafe, "DISABLED"],
+ // ҏнԟҭв.com
+ ["xn--b1av9v8dry.com", "\u048f\u043d\u051f\u04ad\u0432.com", kUnsafe, "DISABLED"],
+ // ҏӊԟҭв.com
+ ["xn--b1a9p8c1e8r.com", "\u048f\u04ca\u051f\u04ad\u0432.com", kUnsafe, "DISABLED"],
+ // wmŋr.com
+ ["xn--wmr-jxa.com", "wm\u014br.com", kUnsafe, "DISABLED"],
+ // шмпґ.com
+ ["xn--l1agz80a.com", "\u0448\u043c\u043f\u0491.com", kUnsafe, "DISABLED"],
+ // щмпґ.com
+ ["xn--l1ag2a0y.com", "\u0449\u043c\u043f\u0491.com", kUnsafe, "DISABLED"],
+ // щӎпґ.com
+ ["xn--o1at1tsi.com", "\u0449\u04ce\u043f\u0491.com", kUnsafe, "DISABLED"],
+ // ґғ.com
+ ["xn--03ae.com", "\u0491\u0493.com", kUnsafe, "DISABLED"],
+ // ґӻ.com
+ ["xn--03a6s.com", "\u0491\u04fb.com", kUnsafe, "DISABLED"],
+ // ҫұҳҽ.com
+ ["xn--r4amg4b.com", "\u04ab\u04b1\u04b3\u04bd.com", kUnsafe, "DISABLED"],
+ // ҫұӽҽ.com
+ ["xn--r4am0b8r.com", "\u04ab\u04b1\u04fd\u04bd.com", kUnsafe, "DISABLED"],
+ // ҫұӿҽ.com
+ ["xn--r4am0b3s.com", "\u04ab\u04b1\u04ff\u04bd.com", kUnsafe, "DISABLED"],
+ // ҫұӿҿ.com
+ ["xn--r4am6b4p.com", "\u04ab\u04b1\u04ff\u04bf.com", kUnsafe, "DISABLED"],
+ // ҫұӿє.com
+ ["xn--91a7osa62a.com", "\u04ab\u04b1\u04ff\u0454.com", kUnsafe, "DISABLED"],
+ // ӏԃԍ.com
+ ["xn--s5a8h4a.com", "\u04cf\u0503\u050d.com", kUnsafe],
+
+ // U+04CF(ӏ) is mapped to multiple characters, lowercase L(l) and
+ // lowercase I(i). Lowercase L is also regarded as similar to digit 1.
+ // The test domain list has {ig, ld, 1gd}.com for Cyrillic.
+ // ӏԍ.com
+ ["xn--s5a8j.com", "\u04cf\u050d.com", kUnsafe],
+ // ӏԃ.com
+ ["xn--s5a8h.com", "\u04cf\u0503.com", kUnsafe],
+ // ӏԍԃ.com
+ ["xn--s5a8h3a.com", "\u04cf\u050d\u0503.com", kUnsafe],
+
+ // 1շ34567890.com
+ ["xn--134567890-gnk.com", "1\u057734567890.com", kUnsafe, "DISABLED"],
+ // ꓲ2345б7890.com
+ ["xn--23457890-e7g93622b.com", "\ua4f22345\u04317890.com", kUnsafe],
+ // 1ᒿ345б7890.com
+ ["xn--13457890-e7g0943b.com", "1\u14bf345\u04317890.com", kUnsafe],
+ // 12з4567890.com
+ ["xn--124567890-10h.com", "12\u04374567890.com", kUnsafe, "DISABLED"],
+ // 12ҙ4567890.com
+ ["xn--124567890-1ti.com", "12\u04994567890.com", kUnsafe, "DISABLED"],
+ // 12ӡ4567890.com
+ ["xn--124567890-mfj.com", "12\u04e14567890.com", kUnsafe, "DISABLED"],
+ // 12उ4567890.com
+ ["xn--124567890-m3r.com", "12\u09094567890.com", kUnsafe, "DISABLED"],
+ // 12ও4567890.com
+ ["xn--124567890-17s.com", "12\u09934567890.com", kUnsafe, "DISABLED"],
+ // 12ਤ4567890.com
+ ["xn--124567890-hfu.com", "12\u0a244567890.com", kUnsafe, "DISABLED"],
+ // 12ဒ4567890.com
+ ["xn--124567890-6s6a.com", "12\u10124567890.com", kUnsafe, "DISABLED"],
+ // 12ვ4567890.com
+ ["xn--124567890-we8a.com", "12\u10D54567890.com", kUnsafe, "DISABLED"],
+ // 12პ4567890.com
+ ["xn--124567890-hh8a.com", "12\u10DE4567890.com", kUnsafe, "DISABLED"],
+ // 123ㄐ567890.com
+ ["xn--123567890-dr5h.com", "123ㄐ567890.com", kUnsafe, "DISABLED"],
+ // 123Ꮞ567890.com
+ ["xn--123567890-dm4b.com", "123\u13ce567890.com", kUnsafe],
+ // 12345б7890.com
+ ["xn--123457890-fzh.com", "12345\u04317890.com", kUnsafe, "DISABLED"],
+ // 12345ճ7890.com
+ ["xn--123457890-fmk.com", "12345ճ7890.com", kUnsafe, "DISABLED"],
+ // 1234567ȣ90.com
+ ["xn--123456790-6od.com", "1234567\u022390.com", kUnsafe],
+ // 12345678୨0.com
+ ["xn--123456780-71w.com", "12345678\u0b680.com", kUnsafe],
+ // 123456789ଠ.com
+ ["xn--123456789-ohw.com", "123456789\u0b20.com", kUnsafe, "DISABLED"],
+ // 123456789ꓳ.com
+ ["xn--123456789-tx75a.com", "123456789\ua4f3.com", kUnsafe],
+
+ // aeœ.com
+ ["xn--ae-fsa.com", "ae\u0153.com", kUnsafe, "DISABLED"],
+ // æce.com
+ ["xn--ce-0ia.com", "\u00e6ce.com", kUnsafe, "DISABLED"],
+ // æœ.com
+ ["xn--6ca2t.com", "\u00e6\u0153.com", kUnsafe, "DISABLED"],
+ // ӕԥ.com
+ ["xn--y5a4n.com", "\u04d5\u0525.com", kUnsafe, "DISABLED"],
+
+ // ငၔဌ၂ဝ.com (entirely made of Myanmar characters)
+ ["xn--ridq5c9hnd.com", "\u1004\u1054\u100c\u1042\u101d.com", kUnsafe, "DISABLED"],
+
+ // ฟรฟร.com (made of two Thai characters. similar to wsws.com in
+ // some fonts)
+ ["xn--w3calb.com", "\u0e1f\u0e23\u0e1f\u0e23.com", kUnsafe, "DISABLED"],
+ // พรบ.com
+ ["xn--r3chp.com", "\u0e1e\u0e23\u0e1a.com", kUnsafe, "DISABLED"],
+ // ฟรบ.com
+ ["xn--r3cjm.com", "\u0e1f\u0e23\u0e1a.com", kUnsafe, "DISABLED"],
+
+ // Lao characters that look like w, s, o, and u.
+ // ພຣບ.com
+ ["xn--f7chp.com", "\u0e9e\u0ea3\u0e9a.com", kUnsafe, "DISABLED"],
+ // ຟຣບ.com
+ ["xn--f7cjm.com", "\u0e9f\u0ea3\u0e9a.com", kUnsafe, "DISABLED"],
+ // ຟຮບ.com
+ ["xn--f7cj9b.com", "\u0e9f\u0eae\u0e9a.com", kUnsafe, "DISABLED"],
+ // ຟຮ໐ບ.com
+ ["xn--f7cj9b5h.com", "\u0e9f\u0eae\u0ed0\u0e9a.com", kUnsafe, "DISABLED"],
+
+ // Lao character that looks like n.
+ // ก11.com
+ ["xn--11-lqi.com", "\u0e0111.com", kUnsafe, "DISABLED"],
+
+ // At one point the skeleton of 'w' was 'vv', ensure that
+ // that it's treated as 'w'.
+ ["xn--wder-qqa.com", "w\u00f3der.com", kUnsafe, "DISABLED"],
+
+ // Mixed digits: the first two will also fail mixed script test
+ // Latin + ASCII digit + Deva digit
+ ["xn--asc1deva-j0q.co.in", "asc1deva\u0967.co.in", kUnsafe],
+ // Latin + Deva digit + Beng digit
+ ["xn--devabeng-f0qu3f.co.in", "deva\u0967beng\u09e7.co.in", kUnsafe],
+ // ASCII digit + Deva digit
+ ["xn--79-v5f.co.in", "7\u09ea9.co.in", kUnsafe],
+ // Deva digit + Beng digit
+ ["xn--e4b0x.co.in", "\u0967\u09e7.co.in", kUnsafe],
+ // U+4E00 (CJK Ideograph One) is not a digit, but it's not allowed next to
+ // non-Kana scripts including numbers.
+ ["xn--d12-s18d.cn", "d12\u4e00.cn", kUnsafe, "DISABLED"],
+ // One that's really long that will force a buffer realloc
+ ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", kSafe],
+
+ // Not allowed; characters outside [:Identifier_Status=Allowed:]
+ // Limited Use Scripts: UTS 31 Table 7.
+ // Vai
+ ["xn--sn8a.com", "\ua50b.com", kUnsafe],
+ // 'CARD' look-alike in Cherokee
+ ["xn--58db0a9q.com", "\u13df\u13aa\u13a1\u13a0.com", kUnsafe],
+ // Scripts excluded from Identifiers: UTS 31 Table 4
+ // Coptic
+ ["xn--5ya.com", "\u03e7.com", kUnsafe],
+ // Old Italic
+ ["xn--097cc.com", "\U00010300\U00010301.com", kUnsafe],
+
+ // U+115F (Hangul Filler)
+ ["xn--osd3820f24c.kr", "\uac00\ub098\u115f.kr", kInvalid],
+ ["www.xn--google-ho0coa.com", "www.\u2039google\u203a.com", kUnsafe],
+ // Latin small capital w: hardᴡare.com
+ ["xn--hardare-l41c.com", "hard\u1d21are.com", kUnsafe],
+ // Minus Sign(U+2212)
+ ["xn--t9g238xc2a.jp", "\u65e5\u2212\u672c.jp", kUnsafe],
+ // Latin Small Letter Script G: ɡɡ.com
+ ["xn--0naa.com", "\u0261\u0261.com", kUnsafe],
+ // Hangul Jamo(U+11xx)
+ ["xn--0pdc3b.com", "\u1102\u1103\u1110.com", kUnsafe],
+ // degree sign: 36°c.com
+ ["xn--36c-tfa.com", "36\u00b0c.com", kUnsafe],
+ // Pound sign
+ ["xn--5free-fga.com", "5free\u00a3.com", kUnsafe],
+ // Hebrew points (U+05B0, U+05B6)
+ ["xn--7cbl2kc2a.com", "\u05e1\u05b6\u05e7\u05b0\u05e1.com", kUnsafe],
+ // Danda(U+0964)
+ ["xn--81bp1b6ch8s.com", "\u0924\u093f\u091c\u0964\u0930\u0940.com", kUnsafe],
+ // Small letter script G(U+0261)
+ ["xn--oogle-qmc.com", "\u0261oogle.com", kUnsafe],
+ // Small Katakana Extension(U+31F1)
+ ["xn--wlk.com", "\u31f1.com", kUnsafe],
+ // Heart symbol: ♥
+ ["xn--ab-u0x.com", "ab\u2665.com", kUnsafe],
+ // Emoji
+ ["xn--vi8hiv.xyz", "\U0001f355\U0001f4a9.xyz", kUnsafe],
+ // Registered trade mark
+ ["xn--egistered-fna.com", "\u00aeegistered.com", kUnsafe],
+ // Latin Letter Retroflex Click
+ ["xn--registered-25c.com", "registered\u01c3.com", kUnsafe],
+ // ASCII '!' not allowed in IDN
+ ["xn--!-257eu42c.kr", "\uc548\ub155!.kr", kUnsafe],
+ // 'GOOGLE' in IPA extension: ɢᴏᴏɢʟᴇ
+ ["xn--1naa7pn51hcbaa.com", "\u0262\u1d0f\u1d0f\u0262\u029f\u1d07.com", kUnsafe],
+ // Padlock icon spoof.
+ ["xn--google-hj64e.com", "\U0001f512google.com", kUnsafe],
+
+ // Custom block list
+ // Combining Long Solidus Overlay
+ ["google.xn--comabc-k8d", "google.com\u0338abc", kUnsafe],
+ // Hyphenation Point instead of Katakana Middle dot
+ ["xn--svgy16dha.jp", "\u30a1\u2027\u30a3.jp", kUnsafe],
+ // Gershayim with other Hebrew characters is allowed.
+ ["xn--5db6bh9b.il", "\u05e9\u05d1\u05f4\u05e6.il", kSafe, "DISABLED"],
+ // Hebrew Gershayim with Latin is invalid according to Python's idna
+ // package.
+ ["xn--ab-yod.com", "a\u05f4b.com", kInvalid],
+ // Hebrew Gershayim with Arabic is disallowed.
+ ["xn--5eb7h.eg", "\u0628\u05f4.eg", kUnsafe],
+// #if BUILDFLAG(IS_APPLE)
+ // These characters are blocked due to a font issue on Mac.
+ // Tibetan transliteration characters.
+ ["xn--com-lum.test.pl", "com\u0f8c.test.pl", kUnsafe],
+ // Arabic letter KASHMIRI YEH
+ ["xn--fgb.com", "\u0620.com", kUnsafe, "DISABLED"],
+// #endif
+
+ // Hyphens (http://unicode.org/cldr/utility/confusables.jsp?a=-)
+ // Hyphen-Minus (the only hyphen allowed)
+ // abc-def
+ ["abc-def.com", "abc-def.com", kSafe],
+ // Modifier Letter Minus Sign
+ ["xn--abcdef-5od.com", "abc\u02d7def.com", kUnsafe],
+ // Hyphen
+ ["xn--abcdef-dg0c.com", "abc\u2010def.com", kUnsafe],
+ // Non-Breaking Hyphen
+ // This is actually an invalid IDNA domain (U+2011 normalizes to U+2010),
+ // but it is included to ensure that we do not inadvertently allow this
+ // character to be displayed as Unicode.
+ ["xn--abcdef-kg0c.com", "abc\u2011def.com", kInvalid],
+ // Figure Dash.
+ // Python's idna package refuses to decode the minus signs and dashes. ICU
+ // decodes them but treats them as unsafe in spoof checks, so these test
+ // cases are marked as unsafe instead of invalid.
+ ["xn--abcdef-rg0c.com", "abc\u2012def.com", kUnsafe],
+ // En Dash
+ ["xn--abcdef-yg0c.com", "abc\u2013def.com", kUnsafe],
+ // Hyphen Bullet
+ ["xn--abcdef-kq0c.com", "abc\u2043def.com", kUnsafe],
+ // Minus Sign
+ ["xn--abcdef-5d3c.com", "abc\u2212def.com", kUnsafe],
+ // Heavy Minus Sign
+ ["xn--abcdef-kg1d.com", "abc\u2796def.com", kUnsafe],
+ // Em Dash
+ // Small Em Dash (U+FE58) is normalized to Em Dash.
+ ["xn--abcdef-5g0c.com", "abc\u2014def.com", kUnsafe],
+ // Coptic Small Letter Dialect-P Ni. Looks like dash.
+ // Coptic Capital Letter Dialect-P Ni is normalized to small letter.
+ ["xn--abcdef-yy8d.com", "abc\u2cbbdef.com", kUnsafe],
+
+ // Block NV8 (Not valid in IDN 2008) characters.
+ // U+058A (֊)
+ ["xn--ab-vfd.com", "a\u058ab.com", kUnsafe],
+ ["xn--y9ac3j.com", "\u0561\u058a\u0562.com", kUnsafe],
+ // U+2019 (’)
+ ["xn--ab-n2t.com", "a\u2019b.com", kUnsafe],
+ // U+2027 (‧)
+ ["xn--ab-u3t.com", "a\u2027b.com", kUnsafe],
+ // U+30A0 (゠)
+ ["xn--ab-bg4a.com", "a\u30a0b.com", kUnsafe],
+ ["xn--9bk3828aea.com", "\uac00\u30a0\uac01.com", kUnsafe],
+ ["xn--9bk279fba.com", "\u4e00\u30a0\u4e00.com", kUnsafe],
+ ["xn--n8jl2x.com", "\u304a\u30a0\u3044.com", kUnsafe],
+ ["xn--fbke7f.com", "\u3082\u30a0\u3084.com", kUnsafe],
+
+ // Block single/double-quote-like characters.
+ // U+02BB (ʻ)
+ ["xn--ab-8nb.com", "a\u02bbb.com", kUnsafe, "DISABLED"],
+ // U+02BC (ʼ)
+ ["xn--ab-cob.com", "a\u02bcb.com", kUnsafe, "DISABLED"],
+ // U+144A: Not allowed to mix with scripts other than Canadian Syllabics.
+ ["xn--ab-jom.com", "a\u144ab.com", kUnsafe],
+ ["xn--xcec9s.com", "\u1401\u144a\u1402.com", kUnsafe],
+
+ // Custom dangerous patterns
+ // Two Katakana-Hiragana combining mark in a row
+ ["google.xn--com-oh4ba.evil.jp", "google.com\u309a\u309a.evil.jp", kUnsafe],
+ // Katakana Letter No not enclosed by {Han,Hiragana,Katakana}.
+ ["google.xn--comevil-v04f.jp", "google.com\u30ceevil.jp", kUnsafe, "DISABLED"],
+ // TODO(jshin): Review the danger of allowing the following two.
+ // Hiragana 'No' by itself is allowed.
+ ["xn--ldk.jp", "\u30ce.jp", kSafe],
+ // Hebrew Gershayim used by itself is allowed.
+ ["xn--5eb.il", "\u05f4.il", kSafe, "DISABLED"],
+
+ // Block RTL nonspacing marks (NSM) after unrelated scripts.
+ ["xn--foog-ycg.com", "foog\u0650.com", kUnsafe], // Latin + Arabic N]M
+ ["xn--foog-jdg.com", "foog\u0654.com", kUnsafe], // Latin + Arabic N]M
+ ["xn--foog-jhg.com", "foog\u0670.com", kUnsafe], // Latin + Arbic N]M
+ ["xn--foog-opf.com", "foog\u05b4.com", kUnsafe], // Latin + Hebrew N]M
+ ["xn--shb5495f.com", "\uac00\u0650.com", kUnsafe], // Hang + Arabic N]M
+
+ // 4 Deviation characters between IDNA 2003 and IDNA 2008
+ // When entered in Unicode, the first two are mapped to 'ss' and Greek sigma
+ // and the latter two are mapped away. However, the punycode form should
+ // remain in punycode.
+ // U+00DF(sharp-s)
+ ["xn--fu-hia.de", "fu\u00df.de", kUnsafe, "DISABLED"],
+ // U+03C2(final-sigma)
+ ["xn--mxac2c.gr", "\u03b1\u03b2\u03c2.gr", kUnsafe, "DISABLED"],
+ // U+200C(ZWNJ)
+ ["xn--h2by8byc123p.in", "\u0924\u094d\u200c\u0930\u093f.in", kUnsafe],
+ // U+200C(ZWJ)
+ ["xn--11b6iy14e.in", "\u0915\u094d\u200d.in", kUnsafe],
+
+ // Math Monospace Small A. When entered in Unicode, it's canonicalized to
+ // 'a'. The punycode form should remain in punycode.
+ ["xn--bc-9x80a.xyz", "\U0001d68abc.xyz", kInvalid],
+ // Math Sans Bold Capital Alpha
+ ["xn--bc-rg90a.xyz", "\U0001d756bc.xyz", kInvalid],
+ // U+3000 is canonicalized to a space(U+0020), but the punycode form
+ // should remain in punycode.
+ ["xn--p6j412gn7f.cn", "\u4e2d\u56fd\u3000", kInvalid],
+ // U+3002 is canonicalized to ASCII fullstop(U+002E), but the punycode form
+ // should remain in punycode.
+ ["xn--r6j012gn7f.cn", "\u4e2d\u56fd\u3002", kInvalid],
+ // Invalid punycode
+ // Has a codepoint beyond U+10FFFF.
+ ["xn--krank-kg706554a", "", kInvalid],
+ // '?' in punycode.
+ ["xn--hello?world.com", "", kInvalid],
+
+ // Not allowed in UTS46/IDNA 2008
+ // Georgian Capital Letter(U+10BD)
+ ["xn--1nd.com", "\u10bd.com", kInvalid],
+ // 3rd and 4th characters are '-'.
+ ["xn-----8kci4dhsd", "\u0440\u0443--\u0430\u0432\u0442\u043e", kInvalid],
+ // Leading combining mark
+ ["xn--72b.com", "\u093e.com", kInvalid],
+ // BiDi check per IDNA 2008/UTS 46
+ // Cannot starts with AN(Arabic-Indic Number)
+ ["xn--8hbae.eg", "\u0662\u0660\u0660.eg", kInvalid],
+ // Cannot start with a RTL character and ends with a LTR
+ ["xn--x-ymcov.eg", "\u062c\u0627\u0631x.eg", kInvalid],
+ // Can start with a RTL character and ends with EN(European Number)
+ ["xn--2-ymcov.eg", "\u062c\u0627\u06312.eg", kSafe],
+ // Can start with a RTL and end with AN
+ ["xn--mgbjq0r.eg", "\u062c\u0627\u0631\u0662.eg", kSafe],
+
+ // Extremely rare Latin letters
+ // Latin Ext B - Pinyin: ǔnion.com
+ ["xn--nion-unb.com", "\u01d4nion.com", kUnsafe, "DISABLED"],
+ // Latin Ext C: ⱴase.com
+ ["xn--ase-7z0b.com", "\u2c74ase.com", kUnsafe],
+ // Latin Ext D: ꝴode.com
+ ["xn--ode-ut3l.com", "\ua774ode.com", kUnsafe],
+ // Latin Ext Additional: ḷily.com
+ ["xn--ily-n3y.com", "\u1e37ily.com", kUnsafe, "DISABLED"],
+ // Latin Ext E: ꬺove.com
+ ["xn--ove-8y6l.com", "\uab3aove.com", kUnsafe],
+ // Greek Ext: ᾳβγ.com
+ ["xn--nxac616s.com", "\u1fb3\u03b2\u03b3.com", kInvalid],
+ // Cyrillic Ext A (label cannot begin with an illegal combining character).
+ ["xn--lrj.com", "\u2def.com", kInvalid],
+ // Cyrillic Ext B: ꙡ.com
+ ["xn--kx8a.com", "\ua661.com", kUnsafe],
+ // Cyrillic Ext C: ᲂ.com (Narrow o)
+ ["xn--43f.com", "\u1c82.com", kInvalid],
+
+ // The skeleton of Extended Arabic-Indic Digit Zero (۰) is a dot. Check that
+ // this is handled correctly (crbug/877045).
+ ["xn--dmb", "\u06f0", kSafe],
+
+ // Test that top domains whose skeletons are the same as the domain name are
+ // handled properly. In this case, tést.net should match test.net top
+ // domain and not be converted to unicode.
+ ["xn--tst-bma.net", "t\u00e9st.net", kUnsafe, "DISABLED"],
+ // Variations of the above, for testing crbug.com/925199.
+ // some.tést.net should match test.net.
+ ["some.xn--tst-bma.net", "some.t\u00e9st.net", kUnsafe, "DISABLED"],
+ // The following should not match test.net, so should be converted to
+ // unicode.
+ // ést.net (a suffix of tést.net).
+ ["xn--st-9ia.net", "\u00e9st.net", kSafe],
+ // some.ést.net
+ ["some.xn--st-9ia.net", "some.\u00e9st.net", kSafe],
+ // atést.net (tést.net is a suffix of atést.net)
+ ["xn--atst-cpa.net", "at\u00e9st.net", kSafe],
+ // some.atést.net
+ ["some.xn--atst-cpa.net", "some.at\u00e9st.net", kSafe],
+
+ // Modifier-letter-voicing should be blocked (wwwˬtest.com).
+ ["xn--wwwtest-2be.com", "www\u02ectest.com", kUnsafe, "DISABLED"],
+
+ // oĸ.com: Not a top domain, should be blocked because of Kra.
+ ["xn--o-tka.com", "o\u0138.com", kUnsafe],
+
+ // U+4E00 and U+3127 should be blocked when next to non-CJK.
+ ["xn--ipaddress-w75n.com", "ip\u4e00address.com", kUnsafe, "DISABLED"],
+ ["xn--ipaddress-wx5h.com", "ip\u3127address.com", kUnsafe, "DISABLED"],
+ // U+4E00 and U+3127 at the beginning and end of a string.
+ ["xn--google-gg5e.com", "google\u3127.com", kUnsafe, "DISABLED"],
+ ["xn--google-9f5e.com", "\u3127google.com", kUnsafe, "DISABLED"],
+ ["xn--google-gn7i.com", "google\u4e00.com", kUnsafe, "DISABLED"],
+ ["xn--google-9m7i.com", "\u4e00google.com", kUnsafe, "DISABLED"],
+ // These are allowed because U+4E00 and U+3127 are not immediately next to
+ // non-CJK.
+ ["xn--gamer-fg1hz05u.com", "\u4e00\u751fgamer.com", kSafe],
+ ["xn--gamer-kg1hy05u.com", "gamer\u751f\u4e00.com", kSafe],
+ ["xn--gamer-f94d4426b.com", "\u3127\u751fgamer.com", kSafe],
+ ["xn--gamer-k94d3426b.com", "gamer\u751f\u3127.com", kSafe],
+ ["xn--4gqz91g.com", "\u4e00\u732b.com", kSafe],
+ ["xn--4fkv10r.com", "\u3127\u732b.com", kSafe],
+ // U+4E00 with another ideograph.
+ ["xn--4gqc.com", "\u4e00\u4e01.com", kSafe],
+
+ // CJK ideographs looking like slashes should be blocked when next to
+ // non-CJK.
+ ["example.xn--comtest-k63k", "example.com\u4e36test", kUnsafe, "DISABLED"],
+ ["example.xn--comtest-u83k", "example.com\u4e40test", kUnsafe, "DISABLED"],
+ ["example.xn--comtest-283k", "example.com\u4e41test", kUnsafe, "DISABLED"],
+ ["example.xn--comtest-m83k", "example.com\u4e3ftest", kUnsafe, "DISABLED"],
+ // This is allowed because the ideographs are not immediately next to
+ // non-CJK.
+ ["xn--oiqsace.com", "\u4e36\u4e40\u4e41\u4e3f.com", kSafe],
+
+ // Kana voiced sound marks are not allowed.
+ ["xn--google-1m4e.com", "google\u3099.com", kUnsafe],
+ ["xn--google-8m4e.com", "google\u309A.com", kUnsafe],
+
+ // Small letter theta looks like a zero.
+ ["xn--123456789-yzg.com", "123456789\u03b8.com", kUnsafe, "DISABLED"],
+
+ ["xn--est-118d.net", "\u4e03est.net", kUnsafe, "DISABLED"],
+ ["xn--est-918d.net", "\u4e05est.net", kUnsafe, "DISABLED"],
+ ["xn--est-e28d.net", "\u4e06est.net", kUnsafe, "DISABLED"],
+ ["xn--est-t18d.net", "\u4e01est.net", kUnsafe, "DISABLED"],
+ ["xn--3-cq6a.com", "\u4e293.com", kUnsafe, "DISABLED"],
+ ["xn--cxe-n68d.com", "c\u4e2bxe.com", kUnsafe, "DISABLED"],
+ ["xn--cye-b98d.com", "cy\u4e42e.com", kUnsafe, "DISABLED"],
+
+ // U+05D7 can look like Latin n in many fonts.
+ ["xn--ceba.com", "\u05d7\u05d7.com", kUnsafe, "DISABLED"],
+
+ // U+00FE (þ) and U+00F0 (ð) are only allowed under the .is TLD.
+ ["xn--acdef-wva.com", "a\u00fecdef.com", kUnsafe, "DISABLED"],
+ ["xn--mnpqr-jta.com", "mn\u00f0pqr.com", kUnsafe, "DISABLED"],
+ ["xn--acdef-wva.is", "a\u00fecdef.is", kSafe],
+ ["xn--mnpqr-jta.is", "mn\u00f0pqr.is", kSafe],
+
+ // U+0259 (ə) is only allowed under the .az TLD.
+ ["xn--xample-vyc.com", "\u0259xample.com", kUnsafe, "DISABLED"],
+ ["xn--xample-vyc.az", "\u0259xample.az", kSafe],
+
+ // U+00B7 is only allowed on Catalan domains between two l's.
+ ["xn--googlecom-5pa.com", "google\u00b7com.com", kUnsafe, "DISABLED"],
+ ["xn--ll-0ea.com", "l\u00b7l.com", kUnsafe, "DISABLED"],
+ ["xn--ll-0ea.cat", "l\u00b7l.cat", kSafe],
+ ["xn--al-0ea.cat", "a\u00b7l.cat", kUnsafe, "DISABLED"],
+ ["xn--la-0ea.cat", "l\u00b7a.cat", kUnsafe, "DISABLED"],
+ ["xn--l-fda.cat", "\u00b7l.cat", kUnsafe, "DISABLED"],
+ ["xn--l-gda.cat", "l\u00b7.cat", kUnsafe, "DISABLED"],
+
+ ["xn--googlecom-gk6n.com", "google\u4e28com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-0y6n.com", "google\u4e5bcom.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-v85n.com", "google\u4e03com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-g95n.com", "google\u4e05com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-go6n.com", "google\u4e36com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-b76o.com", "google\u5341com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-ql3h.com", "google\u3007com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-0r5h.com", "google\u3112com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-bu5h.com", "google\u311acom.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-qv5h.com", "google\u311fcom.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-0x5h.com", "google\u3127com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-by5h.com", "google\u3128com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-ly5h.com", "google\u3129com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-5o5h.com", "google\u3108com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-075n.com", "google\u4e00com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-046h.com", "google\u31bacom.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-026h.com", "google\u31b3com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-lg9q.com", "google\u5de5com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-g040a.com", "google\u8ba0com.com", kUnsafe, "DISABLED"],
+ ["xn--googlecom-b85n.com", "google\u4e01com.com", kUnsafe, "DISABLED"],
+
+ // Whole-script-confusables. Cyrillic is sufficiently handled in cases above
+ // so it's not included here.
+ // Armenian:
+ ["xn--mbbkpm.com", "\u0578\u057d\u0582\u0585.com", kUnsafe, "DISABLED"],
+ ["xn--mbbkpm.am", "\u0578\u057d\u0582\u0585.am", kSafe],
+ ["xn--mbbkpm.xn--y9a3aq", "\u0578\u057d\u0582\u0585.\u0570\u0561\u0575", kSafe],
+ // Ethiopic:
+ ["xn--6xd66aa62c.com", "\u1220\u12d0\u12d0\u1350.com", kUnsafe, "DISABLED"],
+ ["xn--6xd66aa62c.et", "\u1220\u12d0\u12d0\u1350.et", kSafe],
+ ["xn--6xd66aa62c.xn--m0d3gwjla96a", "\u1220\u12d0\u12d0\u1350.\u12a2\u1275\u12ee\u1335\u12eb", kSafe],
+ // Greek:
+ ["xn--mxapd.com", "\u03b9\u03ba\u03b1.com", kUnsafe, "DISABLED"],
+ ["xn--mxapd.gr", "\u03b9\u03ba\u03b1.gr", kSafe],
+ ["xn--mxapd.xn--qxam", "\u03b9\u03ba\u03b1.\u03b5\u03bb", kSafe],
+ // Georgian:
+ ["xn--gpd3ag.com", "\u10fd\u10ff\u10ee.com", kUnsafe, "DISABLED"],
+ ["xn--gpd3ag.ge", "\u10fd\u10ff\u10ee.ge", kSafe],
+ ["xn--gpd3ag.xn--node", "\u10fd\u10ff\u10ee.\u10d2\u10d4", kSafe],
+ // Hebrew:
+ ["xn--7dbh4a.com", "\u05d7\u05e1\u05d3.com", kUnsafe, "DISABLED"],
+ ["xn--7dbh4a.il", "\u05d7\u05e1\u05d3.il", kSafe],
+ ["xn--9dbq2a.xn--7dbh4a", "\u05e7\u05d5\u05dd.\u05d7\u05e1\u05d3", kSafe],
+ // Myanmar:
+ ["xn--oidbbf41a.com", "\u1004\u1040\u1002\u1001\u1002.com", kUnsafe, "DISABLED"],
+ ["xn--oidbbf41a.mm", "\u1004\u1040\u1002\u1001\u1002.mm", kSafe],
+ ["xn--oidbbf41a.xn--7idjb0f4ck", "\u1004\u1040\u1002\u1001\u1002.\u1019\u103c\u1014\u103a\u1019\u102c", kSafe],
+ // Myanmar Shan digits:
+ ["xn--rmdcmef.com", "\u1090\u1091\u1095\u1096\u1097.com", kUnsafe, "DISABLED"],
+ ["xn--rmdcmef.mm", "\u1090\u1091\u1095\u1096\u1097.mm", kSafe],
+ ["xn--rmdcmef.xn--7idjb0f4ck", "\u1090\u1091\u1095\u1096\u1097.\u1019\u103c\u1014\u103a\u1019\u102c", kSafe],
+// Thai:
+// #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ ["xn--o3cedqz2c.com", "\u0e17\u0e19\u0e1a\u0e1e\u0e23\u0e2b.com", kUnsafe, "DISABLED"],
+ ["xn--o3cedqz2c.th", "\u0e17\u0e19\u0e1a\u0e1e\u0e23\u0e2b.th", kSafe],
+ ["xn--o3cedqz2c.xn--o3cw4h", "\u0e17\u0e19\u0e1a\u0e1e\u0e23\u0e2b.\u0e44\u0e17\u0e22", kSafe],
+// #else
+ ["xn--r3ch7hsc.com", "\u0e1e\u0e1a\u0e40\u0e50.com", kUnsafe, "DISABLED"],
+ ["xn--r3ch7hsc.th", "\u0e1e\u0e1a\u0e40\u0e50.th", kSafe],
+ ["xn--r3ch7hsc.xn--o3cw4h", "\u0e1e\u0e1a\u0e40\u0e50.\u0e44\u0e17\u0e22", kSafe],
+// #endif
+
+ // Indic scripts:
+ // Bengali:
+ ["xn--07baub.com", "\u09e6\u09ed\u09e6\u09ed.com", kUnsafe, "DISABLED"],
+ // Devanagari:
+ ["xn--62ba6j.com", "\u093d\u0966\u093d.com", kUnsafe, "DISABLED"],
+ // Gujarati:
+ ["xn--becd.com", "\u0aa1\u0a9f.com", kUnsafe, "DISABLED"],
+ // Gurmukhi:
+ ["xn--occacb.com", "\u0a66\u0a67\u0a66\u0a67.com", kUnsafe, "DISABLED"],
+ // Kannada:
+ ["xn--stca6jf.com", "\u0cbd\u0ce6\u0cbd\u0ce7.com", kUnsafe, "DISABLED"],
+ // Malayalam:
+ ["xn--lwccv.com", "\u0d1f\u0d20\u0d27.com", kUnsafe, "DISABLED"],
+ // Oriya:
+ ["xn--zhca6ub.com", "\u0b6e\u0b20\u0b6e\u0b20.com", kUnsafe, "DISABLED"],
+ // Tamil:
+ ["xn--mlca6ab.com", "\u0b9f\u0baa\u0b9f\u0baa.com", kUnsafe, "DISABLED"],
+ // Telugu:
+ ["xn--brcaabbb.com", "\u0c67\u0c66\u0c67\u0c66\u0c67\u0c66.com", kUnsafe, "DISABLED"],
+
+ // IDN domain matching an IDN top-domain (f\u00f3\u00f3.com)
+ ["xn--fo-5ja.com", "f\u00f3o.com", kUnsafe, "DISABLED"],
+
+ // crbug.com/769547: Subdomains of top domains should be allowed.
+ ["xn--xample-9ua.test.net", "\u00e9xample.test.net", kSafe],
+ // Skeleton of the eTLD+1 matches a top domain, but the eTLD+1 itself is
+ // not a top domain. Should not be decoded to unicode.
+ ["xn--xample-9ua.test.xn--nt-bja", "\u00e9xample.test.n\u00e9t", kUnsafe, "DISABLED"],
+
+ // Digit lookalike check of 16კ.com with character “კ” (U+10D9)
+ // Test case for https://crbug.com/1156531
+ ["xn--16-1ik.com", "16\u10d9.com", kUnsafe, "DISABLED"],
+
+ // Skeleton generator check of officeკ65.com with character “კ” (U+10D9)
+ // Test case for https://crbug.com/1156531
+ ["xn--office65-l04a.com", "office\u10d965.com", kUnsafe],
+
+ // Digit lookalike check of 16ੜ.com with character “ੜ” (U+0A5C)
+ // Test case for https://crbug.com/1156531 (missed skeleton map)
+ ["xn--16-ogg.com", "16\u0a5c.com", kUnsafe, "DISABLED"],
+
+ // Skeleton generator check of officeੜ65.com with character “ੜ” (U+0A5C)
+ // Test case for https://crbug.com/1156531 (missed skeleton map)
+ ["xn--office65-hts.com", "office\u0a5c65.com", kUnsafe],
+
+ // New test cases go ↑↑ above.
+
+ // /!\ WARNING: You MUST use tools/security/idn_test_case_generator.py to
+ // generate new test cases, as specified by the comment at the top of this
+ // test list. Why must you use that python script?
+ // 1. It is easy to get things wrong. There were several hand-crafted
+ // incorrect test cases committed that was later fixed.
+ // 2. This test _also_ is a test of Chromium's IDN encoder/decoder, so using
+ // Chromium's IDN encoder/decoder to generate test files loses an
+ // advantage of having Python's IDN encode/decode the tests.
+];
+
+function checkEquals(a, b, message, expectedFail) {
+ if (!expectedFail) {
+ Assert.equal(a, b, message);
+ } else {
+ Assert.notEqual(a, b, `EXPECTED-FAIL: ${message}`);
+ }
+}
+
+add_task(async function test_chrome_spoofs() {
+ for (let test of testCases) {
+ let isAscii = {};
+ let result = idnService.convertToDisplayIDN(test[0], isAscii);
+ let expectedFail = test.length == 4 && test[3] == "DISABLED";
+ if (test[2] == kSafe) {
+ checkEquals(
+ result,
+ test[1],
+ `kSafe label ${test[0]} should convert to ${test[1]}`,
+ expectedFail
+ );
+ } else if (test[2] == kUnsafe) {
+ checkEquals(
+ result,
+ test[0],
+ `kUnsafe label ${test[0]} should not convert to ${test[1]}`,
+ expectedFail
+ );
+ } else if (test[2] == kInvalid) {
+ checkEquals(
+ result,
+ test[0],
+ `kInvalid label ${test[0]} should stay the same`,
+ expectedFail
+ );
+ }
+ }
+});
diff --git a/netwerk/test/unit/test_idn_urls.js b/netwerk/test/unit/test_idn_urls.js
new file mode 100644
index 0000000000..0059b133c0
--- /dev/null
+++ b/netwerk/test/unit/test_idn_urls.js
@@ -0,0 +1,436 @@
+// Test algorithm for unicode display of IDNA URL (bug 722299)
+
+"use strict";
+
+const testcases = [
+ // Original Punycode or Expected UTF-8 by profile
+ // URL normalized form ASCII-Only, High, Moderate
+ //
+ // Latin script
+ ["cuillère", "xn--cuillre-6xa", false, true, true],
+
+ // repeated non-spacing marks
+ ["gruz̀̀ere", "xn--gruzere-ogea", false, false, false],
+
+ // non-XID character
+ ["I♥NY", "xn--iny-zx5a", false, false, false],
+
+ /*
+ Behaviour of this test changed in IDNA2008, replacing the non-XID
+ character with U+FFFD replacement character - when all platforms use
+ IDNA2008 it can be uncommented and the punycode URL changed to
+ "xn--mgbl3eb85703a"
+
+ // new non-XID character in Unicode 6.3
+ ["حلا\u061cل", "xn--bgbvr6gc", false, false, false],
+*/
+
+ // U+30FB KATAKANA MIDDLE DOT is excluded from non-XID characters (bug 857490)
+ ["乾燥肌・石けん", "xn--08j4gylj12hz80b0uhfup", false, true, true],
+
+ // Cyrillic alone
+ ["толсто́й", "xn--lsa83dealbred", false, true, true],
+
+ // Mixed script Cyrillic/Latin
+ [
+ "толсто́й-in-Russian",
+ "xn---in-russian-1jg071b0a8bb4cpd",
+ false,
+ false,
+ false,
+ ],
+
+ // Mixed script Latin/Cyrillic
+ ["war-and-миръ", "xn--war-and--b9g3b7b3h", false, false, false],
+
+ // Cherokee (Restricted script)
+ ["ᏣᎳᎩ", "xn--f9dt7l", false, false, false],
+
+ // Yi (former Aspirational script, now Restricted per Unicode 10.0 update to UAX 31)
+ ["ꆈꌠꁱꂷ", "xn--4o7a6e1x64c", false, false, false],
+
+ // Greek alone
+ ["πλάτων", "xn--hxa3ahjw4a", false, true, true],
+
+ // Mixed script Greek/Latin
+ [
+ "πλάτωνicrelationship",
+ "xn--icrelationship-96j4t9a3cwe2e",
+ false,
+ false,
+ false,
+ ],
+
+ // Mixed script Latin/Greek
+ ["spaceὈδύσσεια", "xn--space-h9dui0b0ga2j1562b", false, false, false],
+
+ // Devanagari alone
+ ["मराठी", "xn--d2b1ag0dl", false, true, true],
+
+ // Devanagari with Armenian
+ ["मराठीՀայաստան", "xn--y9aaa1d0ai1cq964f8dwa2o1a", false, false, false],
+
+ // Devanagari with common
+ ["मराठी123", "xn--123-mhh3em2hra", false, true, true],
+
+ // Common with Devanagari
+ ["123मराठी", "xn--123-phh3em2hra", false, true, true],
+
+ // Latin with Han
+ ["chairman毛", "xn--chairman-k65r", false, true, true],
+
+ // Han with Latin
+ ["山葵sauce", "xn--sauce-6j9ii40v", false, true, true],
+
+ // Latin with Han, Hiragana and Katakana
+ ["van語ではドイ", "xn--van-ub4bpb6w0in486d", false, true, true],
+
+ // Latin with Han, Katakana and Hiragana
+ ["van語ドイでは", "xn--van-ub4bpb4w0ip486d", false, true, true],
+
+ // Latin with Hiragana, Han and Katakana
+ ["vanでは語ドイ", "xn--van-ub4bpb6w0ip486d", false, true, true],
+
+ // Latin with Hiragana, Katakana and Han
+ ["vanではドイ語", "xn--van-ub4bpb6w0ir486d", false, true, true],
+
+ // Latin with Katakana, Han and Hiragana
+ ["vanドイ語では", "xn--van-ub4bpb4w0ir486d", false, true, true],
+
+ // Latin with Katakana, Hiragana and Han
+ ["vanドイでは語", "xn--van-ub4bpb4w0it486d", false, true, true],
+
+ // Han with Latin, Hiragana and Katakana
+ ["語vanではドイ", "xn--van-ub4bpb6w0ik486d", false, true, true],
+
+ // Han with Latin, Katakana and Hiragana
+ ["語vanドイでは", "xn--van-ub4bpb4w0im486d", false, true, true],
+
+ // Han with Hiragana, Latin and Katakana
+ ["語ではvanドイ", "xn--van-rb4bpb9w0ik486d", false, true, true],
+
+ // Han with Hiragana, Katakana and Latin
+ ["語ではドイvan", "xn--van-rb4bpb6w0in486d", false, true, true],
+
+ // Han with Katakana, Latin and Hiragana
+ ["語ドイvanでは", "xn--van-ub4bpb1w0ip486d", false, true, true],
+
+ // Han with Katakana, Hiragana and Latin
+ ["語ドイではvan", "xn--van-rb4bpb4w0ip486d", false, true, true],
+
+ // Hiragana with Latin, Han and Katakana
+ ["イツvan語ではド", "xn--van-ub4bpb1wvhsbx330n", false, true, true],
+
+ // Hiragana with Latin, Katakana and Han
+ ["ではvanドイ語", "xn--van-rb4bpb9w0ir486d", false, true, true],
+
+ // Hiragana with Han, Latin and Katakana
+ ["では語vanドイ", "xn--van-rb4bpb9w0im486d", false, true, true],
+
+ // Hiragana with Han, Katakana and Latin
+ ["では語ドイvan", "xn--van-rb4bpb6w0ip486d", false, true, true],
+
+ // Hiragana with Katakana, Latin and Han
+ ["ではドイvan語", "xn--van-rb4bpb6w0iu486d", false, true, true],
+
+ // Hiragana with Katakana, Han and Latin
+ ["ではドイ語van", "xn--van-rb4bpb6w0ir486d", false, true, true],
+
+ // Katakana with Latin, Han and Hiragana
+ ["ドイvan語では", "xn--van-ub4bpb1w0iu486d", false, true, true],
+
+ // Katakana with Latin, Hiragana and Han
+ ["ドイvanでは語", "xn--van-ub4bpb1w0iw486d", false, true, true],
+
+ // Katakana with Han, Latin and Hiragana
+ ["ドイ語vanでは", "xn--van-ub4bpb1w0ir486d", false, true, true],
+
+ // Katakana with Han, Hiragana and Latin
+ ["ドイ語ではvan", "xn--van-rb4bpb4w0ir486d", false, true, true],
+
+ // Katakana with Hiragana, Latin and Han
+ ["ドイではvan語", "xn--van-rb4bpb4w0iw486d", false, true, true],
+
+ // Katakana with Hiragana, Han and Latin
+ ["ドイでは語van", "xn--van-rb4bpb4w0it486d", false, true, true],
+
+ // Han with common
+ ["中国123", "xn--123-u68dy61b", false, true, true],
+
+ // common with Han
+ ["123中国", "xn--123-x68dy61b", false, true, true],
+
+ // Characters that normalize to permitted characters
+ // (also tests Plane 1 supplementary characters)
+ ["super𝟖", "super8", true, true, true],
+
+ // Han from Plane 2
+ ["𠀀𠀁𠀂", "xn--j50icd", false, true, true],
+
+ // Han from Plane 2 with js (UTF-16) escapes
+ ["\uD840\uDC00\uD840\uDC01\uD840\uDC02", "xn--j50icd", false, true, true],
+
+ // Same with a lone high surrogate at the end
+ ["\uD840\uDC00\uD840\uDC01\uD840", "xn--zn7c0336bda", false, false, false],
+
+ // Latin text and Bengali digits
+ ["super৪", "xn--super-k2l", false, false, true],
+
+ // Bengali digits and Latin text
+ ["৫ab", "xn--ab-x5f", false, false, true],
+
+ // Bengali text and Latin digits
+ ["অঙ্কুর8", "xn--8-70d2cp0j6dtd", false, true, true],
+
+ // Latin digits and Bengali text
+ ["5াব", "xn--5-h3d7c", false, true, true],
+
+ // Mixed numbering systems
+ ["٢٠۰٠", "xn--8hbae38c", false, false, false],
+
+ // Traditional Chinese
+ ["萬城", "xn--uis754h", false, true, true],
+
+ // Simplified Chinese
+ ["万城", "xn--chq31v", false, true, true],
+
+ // Simplified-only and Traditional-only Chinese in the same label
+ ["万萬城", "xn--chq31vsl1b", false, true, true],
+
+ // Traditional-only and Simplified-only Chinese in the same label
+ ["萬万城", "xn--chq31vrl1b", false, true, true],
+
+ // Han and Latin and Bopomofo
+ [
+ "注音符号bopomofoㄅㄆㄇㄈ",
+ "xn--bopomofo-hj5gkalm1637i876cuw0brk5f",
+ false,
+ true,
+ true,
+ ],
+
+ // Han, bopomofo, Latin
+ [
+ "注音符号ㄅㄆㄇㄈbopomofo",
+ "xn--bopomofo-8i5gkalm9637i876cuw0brk5f",
+ false,
+ true,
+ true,
+ ],
+
+ // Latin, Han, Bopomofo
+ [
+ "bopomofo注音符号ㄅㄆㄇㄈ",
+ "xn--bopomofo-hj5gkalm9637i876cuw0brk5f",
+ false,
+ true,
+ true,
+ ],
+
+ // Latin, Bopomofo, Han
+ [
+ "bopomofoㄅㄆㄇㄈ注音符号",
+ "xn--bopomofo-hj5gkalm3737i876cuw0brk5f",
+ false,
+ true,
+ true,
+ ],
+
+ // Bopomofo, Han, Latin
+ [
+ "ㄅㄆㄇㄈ注音符号bopomofo",
+ "xn--bopomofo-8i5gkalm3737i876cuw0brk5f",
+ false,
+ true,
+ true,
+ ],
+
+ // Bopomofo, Latin, Han
+ [
+ "ㄅㄆㄇㄈbopomofo注音符号",
+ "xn--bopomofo-8i5gkalm1837i876cuw0brk5f",
+ false,
+ true,
+ true,
+ ],
+
+ // Han, bopomofo and katakana
+ [
+ "注音符号ㄅㄆㄇㄈボポモフォ",
+ "xn--jckteuaez1shij0450gylvccz9asi4e",
+ false,
+ false,
+ false,
+ ],
+
+ // Han, katakana, bopomofo
+ [
+ "注音符号ボポモフォㄅㄆㄇㄈ",
+ "xn--jckteuaez6shij5350gylvccz9asi4e",
+ false,
+ false,
+ false,
+ ],
+
+ // bopomofo, han, katakana
+ [
+ "ㄅㄆㄇㄈ注音符号ボポモフォ",
+ "xn--jckteuaez1shij4450gylvccz9asi4e",
+ false,
+ false,
+ false,
+ ],
+
+ // bopomofo, katakana, han
+ [
+ "ㄅㄆㄇㄈボポモフォ注音符号",
+ "xn--jckteuaez1shij9450gylvccz9asi4e",
+ false,
+ false,
+ false,
+ ],
+
+ // katakana, Han, bopomofo
+ [
+ "ボポモフォ注音符号ㄅㄆㄇㄈ",
+ "xn--jckteuaez6shij0450gylvccz9asi4e",
+ false,
+ false,
+ false,
+ ],
+
+ // katakana, bopomofo, Han
+ [
+ "ボポモフォㄅㄆㄇㄈ注音符号",
+ "xn--jckteuaez6shij4450gylvccz9asi4e",
+ false,
+ false,
+ false,
+ ],
+
+ // Han, Hangul and Latin
+ ["韓한글hangul", "xn--hangul-2m5ti09k79ze", false, true, true],
+
+ // Han, Latin and Hangul
+ ["韓hangul한글", "xn--hangul-2m5to09k79ze", false, true, true],
+
+ // Hangul, Han and Latin
+ ["한글韓hangul", "xn--hangul-2m5th09k79ze", false, true, true],
+
+ // Hangul, Latin and Han
+ ["한글hangul韓", "xn--hangul-8m5t898k79ze", false, true, true],
+
+ // Latin, Han and Hangul
+ ["hangul韓한글", "xn--hangul-8m5ti09k79ze", false, true, true],
+
+ // Latin, Hangul and Han
+ ["hangul한글韓", "xn--hangul-8m5th09k79ze", false, true, true],
+
+ // Hangul and katakana
+ ["한글ハングル", "xn--qck1c2d4a9266lkmzb", false, false, false],
+
+ // Katakana and Hangul
+ ["ハングル한글", "xn--qck1c2d4a2366lkmzb", false, false, false],
+
+ // Thai (also tests that node with over 63 UTF-8 octets doesn't fail)
+ [
+ "เครื่องทําน้ําทําน้ําแข็ง",
+ "xn--22cdjb2fanb9fyepcbbb9dwh4a3igze4fdcd",
+ false,
+ true,
+ true,
+ ],
+
+ // Effect of adding valid or invalid subdomains (bug 1399540)
+ ["䕮䕵䕶䕱.ascii", "xn--google.ascii", false, true, true],
+ ["ascii.䕮䕵䕶䕱", "ascii.xn--google", false, true, true],
+ ["中国123.䕮䕵䕶䕱", "xn--123-u68dy61b.xn--google", false, true, true],
+ ["䕮䕵䕶䕱.中国123", "xn--google.xn--123-u68dy61b", false, true, true],
+ [
+ "xn--accountlogin.䕮䕵䕶䕱",
+ "xn--accountlogin.xn--google",
+ false,
+ true,
+ true,
+ ],
+ [
+ "䕮䕵䕶䕱.xn--accountlogin",
+ "xn--google.xn--accountlogin",
+ false,
+ true,
+ true,
+ ],
+
+ // Arabic diacritic not allowed in Latin text (bug 1370497)
+ ["goo\u0650gle", "xn--google-yri", false, false, false],
+ // ...but Arabic diacritics are allowed on Arabic text
+ ["العَرَبِي", "xn--mgbc0a5a6cxbzabt", false, true, true],
+
+ // Hebrew diacritic also not allowed in Latin text (bug 1404349)
+ ["goo\u05b4gle", "xn--google-rvh", false, false, false],
+
+ // Accents above dotless-i are not allowed
+ ["na\u0131\u0308ve", "xn--nave-mza04z", false, false, false],
+ ["d\u0131\u0302ner", "xn--dner-lza40z", false, false, false],
+ // but the corresponding accented-i (based on dotted i) is OK
+ ["na\u00efve.com", "xn--nave-6pa.com", false, true, true],
+ ["d\u00eener.com", "xn--dner-0pa.com", false, true, true],
+];
+
+const profiles = ["ASCII", "high", "moderate"];
+
+function run_test() {
+ var pbi = Services.prefs;
+ var oldProfile = pbi.getCharPref(
+ "network.IDN.restriction_profile",
+ "moderate"
+ );
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+
+ for (var i = 0; i < profiles.length; ++i) {
+ pbi.setCharPref("network.IDN.restriction_profile", profiles[i]);
+
+ dump("testing " + profiles[i] + " profile");
+
+ for (var j = 0; j < testcases.length; ++j) {
+ var test = testcases[j];
+ var URL = test[0] + ".com";
+ var punycodeURL = test[1] + ".com";
+ var expectedUnicode = test[2 + i];
+ var isASCII = {};
+
+ var result;
+ try {
+ result = idnService.convertToDisplayIDN(URL, isASCII);
+ } catch (e) {
+ result = ".com";
+ }
+ if (
+ punycodeURL.substr(0, 4) == "xn--" ||
+ punycodeURL.indexOf(".xn--") > 0
+ ) {
+ // test convertToDisplayIDN with a Unicode URL and with a
+ // Punycode URL if we have one
+ Assert.equal(
+ escape(result),
+ expectedUnicode ? escape(URL) : escape(punycodeURL)
+ );
+
+ result = idnService.convertToDisplayIDN(punycodeURL, isASCII);
+ Assert.equal(
+ escape(result),
+ expectedUnicode ? escape(URL) : escape(punycodeURL)
+ );
+ } else {
+ // The "punycode" URL isn't punycode. This happens in testcases
+ // where the Unicode URL has become normalized to an ASCII URL,
+ // so, even though expectedUnicode is true, the expected result
+ // is equal to punycodeURL
+ Assert.equal(escape(result), escape(punycodeURL));
+ }
+ }
+ }
+ pbi.setCharPref("network.IDN.restriction_profile", oldProfile);
+}
diff --git a/netwerk/test/unit/test_idna2008.js b/netwerk/test/unit/test_idna2008.js
new file mode 100644
index 0000000000..0e0290ce79
--- /dev/null
+++ b/netwerk/test/unit/test_idna2008.js
@@ -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/. */
+
+"use strict";
+
+const kTransitionalProcessing = false;
+
+// Four characters map differently under non-transitional processing:
+const labels = [
+ // U+00DF LATIN SMALL LETTER SHARP S to "ss"
+ "stra\u00dfe",
+ // U+03C2 GREEK SMALL LETTER FINAL SIGMA to U+03C3 GREEK SMALL LETTER SIGMA
+ "\u03b5\u03bb\u03bb\u03ac\u03c2",
+ // U+200C ZERO WIDTH NON-JOINER in Indic script
+ "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc",
+ // U+200D ZERO WIDTH JOINER in Arabic script
+ "\u0dc1\u0dca\u200d\u0dbb\u0dd3",
+
+ // But CONTEXTJ rules prohibit ZWJ and ZWNJ in non-Arabic or Indic scripts
+ // U+200C ZERO WIDTH NON-JOINER in Latin script
+ "m\u200cn",
+ // U+200D ZERO WIDTH JOINER in Latin script
+ "p\u200dq",
+];
+
+const transitionalExpected = [
+ "strasse",
+ "xn--hxarsa5b",
+ "xn--mgba3gch31f",
+ "xn--10cl1a0b",
+ "",
+ "",
+];
+
+const nonTransitionalExpected = [
+ "xn--strae-oqa",
+ "xn--hxarsa0b",
+ "xn--mgba3gch31f060k",
+ "xn--10cl1a0b660p",
+ "",
+ "",
+];
+
+// Test options for converting IDN URLs under IDNA2008
+function run_test() {
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+
+ for (var i = 0; i < labels.length; ++i) {
+ var result;
+ try {
+ result = idnService.convertUTF8toACE(labels[i]);
+ } catch (e) {
+ result = "";
+ }
+
+ if (kTransitionalProcessing) {
+ equal(result, transitionalExpected[i]);
+ } else {
+ equal(result, nonTransitionalExpected[i]);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_idnservice.js b/netwerk/test/unit/test_idnservice.js
new file mode 100644
index 0000000000..0c52f300e3
--- /dev/null
+++ b/netwerk/test/unit/test_idnservice.js
@@ -0,0 +1,39 @@
+// Tests nsIIDNService
+
+"use strict";
+
+const idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+);
+
+add_task(async function test_simple() {
+ let reference = [
+ // The 3rd element indicates whether the second element
+ // is ACE-encoded
+ ["asciihost", "asciihost", false],
+ ["b\u00FCcher", "xn--bcher-kva", true],
+ ];
+
+ for (var i = 0; i < reference.length; ++i) {
+ dump("Testing " + reference[i] + "\n");
+ // We test the following:
+ // - Converting UTF-8 to ACE and back gives us the expected answer
+ // - Converting the ASCII string UTF-8 -> ACE leaves the string unchanged
+ // - isACE returns true when we expect it to (third array elem true)
+ Assert.equal(idnService.convertUTF8toACE(reference[i][0]), reference[i][1]);
+ Assert.equal(idnService.convertUTF8toACE(reference[i][1]), reference[i][1]);
+ Assert.equal(idnService.convertACEtoUTF8(reference[i][1]), reference[i][0]);
+ Assert.equal(idnService.isACE(reference[i][1]), reference[i][2]);
+ }
+});
+
+add_task(async function test_extra_blocked() {
+ let isAscii = {};
+ equal(idnService.convertToDisplayIDN("xn--gou-2lb.ro", isAscii), "goșu.ro");
+ Services.prefs.setStringPref("network.IDN.extra_blocked_chars", "ș");
+ equal(
+ idnService.convertToDisplayIDN("xn--gou-2lb.ro", isAscii),
+ "xn--gou-2lb.ro"
+ );
+ Services.prefs.clearUserPref("network.IDN.extra_blocked_chars");
+});
diff --git a/netwerk/test/unit/test_immutable.js b/netwerk/test/unit/test_immutable.js
new file mode 100644
index 0000000000..f2b07c7114
--- /dev/null
+++ b/netwerk/test/unit/test_immutable.js
@@ -0,0 +1,206 @@
+"use strict";
+
+var prefs;
+var http2pref;
+var origin;
+var rcwnpref;
+
+function run_test() {
+ var h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Services.prefs;
+
+ http2pref = prefs.getBoolPref("network.http.http2.enabled");
+ rcwnpref = prefs.getBoolPref("network.http.rcwn.enabled");
+
+ prefs.setBoolPref("network.http.http2.enabled", true);
+ prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, bar.example.com"
+ );
+ // Disable rcwn to make cache behavior deterministic.
+ prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ origin = "https://foo.example.com:" + h2Port;
+ dump("origin - " + origin + "\n");
+ doTest1();
+}
+
+function resetPrefs() {
+ prefs.setBoolPref("network.http.http2.enabled", http2pref);
+ prefs.setBoolPref("network.http.rcwn.enabled", rcwnpref);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function makeChan(path) {
+ return NetUtil.newChannel({
+ uri: origin + path,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var nextTest;
+var expectPass = true;
+var expectConditional = false;
+
+var Listener = function () {};
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ if (expectPass) {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw(
+ "Channel should have a success code! (" + request.status + ")"
+ );
+ }
+ Assert.equal(request.responseStatus, 200);
+ } else {
+ Assert.equal(Components.isSuccessCode(request.status), false);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ if (expectConditional) {
+ Assert.equal(request.getResponseHeader("x-conditional"), "true");
+ } else {
+ try {
+ Assert.notEqual(request.getResponseHeader("x-conditional"), "true");
+ } catch (e) {
+ Assert.ok(true);
+ }
+ }
+ nextTest();
+ do_test_finished();
+ },
+};
+
+function testsDone() {
+ dump("testDone\n");
+ resetPrefs();
+}
+
+function doTest1() {
+ dump("execute doTest1 - resource without immutable. initial request\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan("/immutable-test-without-attribute");
+ var listener = new Listener();
+ nextTest = doTest2;
+ chan.asyncOpen(listener);
+}
+
+function doTest2() {
+ dump("execute doTest2 - resource without immutable. reload\n");
+ do_test_pending();
+ expectConditional = true;
+ var chan = makeChan("/immutable-test-without-attribute");
+ var listener = new Listener();
+ nextTest = doTest3;
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen(listener);
+}
+
+function doTest3() {
+ dump("execute doTest3 - resource without immutable. shift reload\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan("/immutable-test-without-attribute");
+ var listener = new Listener();
+ nextTest = doTest4;
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(listener);
+}
+
+function doTest4() {
+ dump("execute doTest4 - resource with immutable. initial request\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan("/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = doTest5;
+ chan.asyncOpen(listener);
+}
+
+function doTest5() {
+ dump("execute doTest5 - resource with immutable. reload\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan("/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = doTest6;
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen(listener);
+}
+
+function doTest6() {
+ dump("execute doTest6 - resource with immutable. shift reload\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan("/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = doTest7;
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(listener);
+}
+
+function doTest7() {
+ dump("execute doTest7 - expired resource with immutable. initial request\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan("/immutable-test-expired-with-Expires-header");
+ var listener = new Listener();
+ nextTest = doTest8;
+ chan.asyncOpen(listener);
+}
+
+function doTest8() {
+ dump("execute doTest8 - expired resource with immutable. reload\n");
+ do_test_pending();
+ expectConditional = true;
+ var chan = makeChan("/immutable-test-expired-with-Expires-header");
+ var listener = new Listener();
+ nextTest = doTest9;
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen(listener);
+}
+
+function doTest9() {
+ dump(
+ "execute doTest9 - expired resource with immutable cache extension and Last modified header. initial request\n"
+ );
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan("/immutable-test-expired-with-last-modified-header");
+ var listener = new Listener();
+ nextTest = doTest10;
+ chan.asyncOpen(listener);
+}
+
+function doTest10() {
+ dump(
+ "execute doTest10 - expired resource with immutable cache extension and Last modified heder. reload\n"
+ );
+ do_test_pending();
+ expectConditional = true;
+ var chan = makeChan("/immutable-test-expired-with-last-modified-header");
+ var listener = new Listener();
+ nextTest = testsDone;
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen(listener);
+}
diff --git a/netwerk/test/unit/test_inhibit_caching.js b/netwerk/test/unit/test_inhibit_caching.js
new file mode 100644
index 0000000000..330e0f632f
--- /dev/null
+++ b/netwerk/test/unit/test_inhibit_caching.js
@@ -0,0 +1,94 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var first = true;
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ var body = "first";
+ if (!first) {
+ body = "second";
+ }
+ first = false;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+ChromeUtils.defineLazyGetter(this, "uri", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = null;
+
+function run_test() {
+ // setup test
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/test", contentHandler);
+ httpserver.start(-1);
+
+ add_test(test_first_response);
+ add_test(test_inhibit_caching);
+
+ run_next_test();
+}
+
+// Makes a regular request
+function test_first_response() {
+ var chan = NetUtil.newChannel({
+ uri: uri + "/test",
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(new ChannelListener(check_first_response, null));
+}
+
+// Checks that we got the appropriate response
+function check_first_response(request, buffer) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(request.responseStatus, 200);
+ Assert.equal(buffer, "first");
+ // Open the cache entry to check its contents
+ asyncOpenCacheEntry(
+ uri + "/test",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ cache_entry_callback
+ );
+}
+
+// Checks that the cache entry has the correct contents
+function cache_entry_callback(status, entry) {
+ equal(status, Cr.NS_OK);
+ var inputStream = entry.openInputStream(0);
+ pumpReadStream(inputStream, function (read) {
+ inputStream.close();
+ equal(read, "first");
+ run_next_test();
+ });
+}
+
+// Makes a request with the INHIBIT_CACHING load flag
+function test_inhibit_caching() {
+ var chan = NetUtil.newChannel({
+ uri: uri + "/test",
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIRequest).loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ chan.asyncOpen(new ChannelListener(check_second_response, null));
+}
+
+// Checks that we got a different response from the first request
+function check_second_response(request, buffer) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(request.responseStatus, 200);
+ Assert.equal(buffer, "second");
+ // Checks that the cache entry still contains the content from the first request
+ asyncOpenCacheEntry(
+ uri + "/test",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ cache_entry_callback
+ );
+}
diff --git a/netwerk/test/unit/test_ioservice.js b/netwerk/test/unit/test_ioservice.js
new file mode 100644
index 0000000000..d218d77a7a
--- /dev/null
+++ b/netwerk/test/unit/test_ioservice.js
@@ -0,0 +1,19 @@
+"use strict";
+
+add_task(function test_extractScheme() {
+ equal(Services.io.extractScheme("HtTp://example.com"), "http");
+ Assert.throws(
+ () => {
+ Services.io.extractScheme("://example.com");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "missing scheme"
+ );
+ Assert.throws(
+ () => {
+ Services.io.extractScheme("ht%tp://example.com");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad scheme"
+ );
+});
diff --git a/netwerk/test/unit/test_large_port.js b/netwerk/test/unit/test_large_port.js
new file mode 100644
index 0000000000..a0dd0a19cf
--- /dev/null
+++ b/netwerk/test/unit/test_large_port.js
@@ -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/. */
+
+// Ensure that non-16-bit URIs are rejected
+
+"use strict";
+
+function run_test() {
+ let mutator = Cc[
+ "@mozilla.org/network/standard-url-mutator;1"
+ ].createInstance(Ci.nsIURIMutator);
+ Assert.ok(mutator, "Mutator constructor works");
+
+ let url = Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIStandardURLMutator)
+ .init(
+ Ci.nsIStandardURL.URLTYPE_AUTHORITY,
+ 65535,
+ "http://localhost",
+ "UTF-8",
+ null
+ )
+ .finalize();
+
+ // Bug 1301621 makes invalid ports throw
+ Assert.throws(
+ () => {
+ url = Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIStandardURLMutator)
+ .init(
+ Ci.nsIStandardURL.URLTYPE_AUTHORITY,
+ 65536,
+ "http://localhost",
+ "UTF-8",
+ null
+ )
+ .finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "invalid port during creation"
+ );
+
+ Assert.throws(
+ () => {
+ url = url
+ .mutate()
+ .QueryInterface(Ci.nsIStandardURLMutator)
+ .setDefaultPort(65536)
+ .finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "invalid port in setDefaultPort"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setPort(65536).finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "invalid port in port setter"
+ );
+
+ Assert.equal(url.port, -1);
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_link.desktop b/netwerk/test/unit/test_link.desktop
new file mode 100644
index 0000000000..b1798202e3
--- /dev/null
+++ b/netwerk/test/unit/test_link.desktop
@@ -0,0 +1,3 @@
+[Desktop Entry]
+Type=Link
+URL=http://www.mozilla.org/
diff --git a/netwerk/test/unit/test_link.lnk b/netwerk/test/unit/test_link.lnk
new file mode 100644
index 0000000000..125d859f43
--- /dev/null
+++ b/netwerk/test/unit/test_link.lnk
Binary files differ
diff --git a/netwerk/test/unit/test_link.url b/netwerk/test/unit/test_link.url
new file mode 100644
index 0000000000..05f8275544
--- /dev/null
+++ b/netwerk/test/unit/test_link.url
@@ -0,0 +1,5 @@
+[InternetShortcut]
+URL=http://www.mozilla.org/
+IDList=
+[{000214A0-0000-0000-C000-000000000046}]
+Prop3=19,2
diff --git a/netwerk/test/unit/test_loadgroup_cancel.js b/netwerk/test/unit/test_loadgroup_cancel.js
new file mode 100644
index 0000000000..9d5779437f
--- /dev/null
+++ b/netwerk/test/unit/test_loadgroup_cancel.js
@@ -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/. */
+
+"use strict";
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function request_handler(metadata, response) {
+ response.processAsync();
+ do_timeout(500, () => {
+ const body = "some body once told me...";
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Length", "" + body.length, false);
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ });
+}
+
+// This test checks that when canceling a loadgroup by the time the loadgroup's
+// groupObserver is sent OnStopRequest for a request, that request has been
+// canceled.
+add_task(async function test_cancelledInOnStop() {
+ let http_server = new HttpServer();
+ http_server.registerPathHandler("/test1", request_handler);
+ http_server.registerPathHandler("/test2", request_handler);
+ http_server.registerPathHandler("/test3", request_handler);
+ http_server.start(-1);
+ const port = http_server.identity.primaryPort;
+
+ let loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
+ Ci.nsILoadGroup
+ );
+
+ let loadListener = {
+ onStartRequest: aRequest => {
+ info("onStartRequest");
+ },
+ onStopRequest: (aRequest, aStatusCode) => {
+ equal(
+ aStatusCode,
+ Cr.NS_ERROR_ABORT,
+ "aStatusCode must be the cancellation code"
+ );
+ equal(
+ aRequest.status,
+ Cr.NS_ERROR_ABORT,
+ "aRequest.status must be the cancellation code"
+ );
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIRequestObserver",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ loadGroup.groupObserver = loadListener;
+
+ let chan1 = makeChan(`http://localhost:${port}/test1`);
+ chan1.loadGroup = loadGroup;
+ let chan2 = makeChan(`http://localhost:${port}/test2`);
+ chan2.loadGroup = loadGroup;
+ let chan3 = makeChan(`http://localhost:${port}/test3`);
+ chan3.loadGroup = loadGroup;
+
+ await new Promise(resolve => do_timeout(500, resolve));
+
+ let promises = [
+ new Promise(resolve => {
+ chan1.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ }),
+ new Promise(resolve => {
+ chan2.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ }),
+ new Promise(resolve => {
+ chan3.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ }),
+ ];
+
+ loadGroup.cancel(Cr.NS_ERROR_ABORT);
+
+ await Promise.all(promises);
+
+ await new Promise(resolve => {
+ http_server.stop(resolve);
+ });
+});
diff --git a/netwerk/test/unit/test_localhost_offline.js b/netwerk/test/unit/test_localhost_offline.js
new file mode 100644
index 0000000000..a1f157944b
--- /dev/null
+++ b/netwerk/test/unit/test_localhost_offline.js
@@ -0,0 +1,77 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+var httpServer = null;
+const body = "Hello";
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+function makeURL(host) {
+ return `http://${host}:${httpServer.identity.primaryPort}/`;
+}
+
+add_task(async function test_localhost_offline() {
+ Services.io.offline = true;
+ Services.prefs.setBoolPref("network.disable-localhost-when-offline", false);
+ let chan = makeChan(makeURL("127.0.0.1"));
+ let [, resp] = await channelOpenPromise(chan);
+ Assert.equal(resp, body, "Should get correct response");
+
+ chan = makeChan(makeURL("localhost"));
+ [, resp] = await channelOpenPromise(chan);
+ Assert.equal(resp, body, "Should get response");
+
+ Services.prefs.setBoolPref("network.disable-localhost-when-offline", true);
+
+ chan = makeChan(makeURL("127.0.0.1"));
+ let [req] = await channelOpenPromise(
+ chan,
+ CL_ALLOW_UNKNOWN_CL | CL_EXPECT_FAILURE
+ );
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.status, Cr.NS_ERROR_OFFLINE);
+
+ chan = makeChan(makeURL("localhost"));
+ [req] = await channelOpenPromise(
+ chan,
+ CL_ALLOW_UNKNOWN_CL | CL_EXPECT_FAILURE
+ );
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.status, Cr.NS_ERROR_OFFLINE);
+
+ Services.prefs.clearUserPref("network.disable-localhost-when-offline");
+ Services.io.offline = false;
+});
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/", (request, response) => {
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Length: " + body.length + "\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+ });
+ httpServer.start(-1);
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_localstreams.js b/netwerk/test/unit/test_localstreams.js
new file mode 100644
index 0000000000..3e9c26b111
--- /dev/null
+++ b/netwerk/test/unit/test_localstreams.js
@@ -0,0 +1,89 @@
+// Tests bug 304414
+
+"use strict";
+
+const PR_RDONLY = 0x1; // see prio.h
+
+// Does some sanity checks on the stream and returns the number of bytes read
+// when the checks passed.
+function test_stream(stream) {
+ // This test only handles blocking streams; that's desired for file streams
+ // anyway.
+ Assert.equal(stream.isNonBlocking(), false);
+
+ // Check that the stream is not buffered
+ Assert.equal(
+ Cc["@mozilla.org/io-util;1"]
+ .getService(Ci.nsIIOUtil)
+ .inputStreamIsBuffered(stream),
+ false
+ );
+
+ // Wrap it in a binary stream (to avoid wrong results that
+ // scriptablestream would produce with binary content)
+ var binstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ binstream.setInputStream(stream);
+
+ var numread = 0;
+ for (;;) {
+ Assert.equal(stream.available(), binstream.available());
+ var avail = stream.available();
+ Assert.notEqual(avail, -1);
+
+ // PR_UINT32_MAX and PR_INT32_MAX; the files we're testing with aren't that
+ // large.
+ Assert.notEqual(avail, Math.pow(2, 32) - 1);
+ Assert.notEqual(avail, Math.pow(2, 31) - 1);
+
+ if (!avail) {
+ // For blocking streams, available() only returns 0 on EOF
+ // Make sure that there is really no data left
+ var could_read = false;
+ try {
+ binstream.readByteArray(1);
+ could_read = true;
+ } catch (e) {
+ // We expect the exception, so do nothing here
+ }
+ if (could_read) {
+ do_throw("Data readable when available indicated EOF!");
+ }
+ return numread;
+ }
+
+ dump("Trying to read " + avail + " bytes\n");
+ // Note: Verification that this does return as much bytes as we asked for is
+ // done in the binarystream implementation
+ binstream.readByteArray(avail);
+
+ numread += avail;
+ }
+}
+
+function stream_for_file(file) {
+ var str = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ str.init(file, PR_RDONLY, 0, 0);
+ return str;
+}
+
+function stream_from_channel(file) {
+ var uri = Services.io.newFileURI(file);
+ return NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).open();
+}
+
+function run_test() {
+ // Get a file and a directory in order to do some testing
+ var file = do_get_file("../unit/data/test_readline6.txt");
+ var len = file.fileSize;
+ Assert.equal(test_stream(stream_for_file(file)), len);
+ Assert.equal(test_stream(stream_from_channel(file)), len);
+ var dir = file.parent;
+ test_stream(stream_from_channel(dir)); // Can't do size checking
+}
diff --git a/netwerk/test/unit/test_mismatch_last-modified.js b/netwerk/test/unit/test_mismatch_last-modified.js
new file mode 100644
index 0000000000..010d5d1178
--- /dev/null
+++ b/netwerk/test/unit/test_mismatch_last-modified.js
@@ -0,0 +1,154 @@
+"use strict";
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+var httpserver = new HttpServer();
+
+// Test the handling of a cache revalidation with mismatching last-modified
+// headers. If we get such a revalidation the cache entry should be purged.
+// see bug 717350
+
+// In this test the wrong data is from 11-16-1994 with a value of 'A',
+// and the right data is from 11-15-1994 with a value of 'B'.
+
+// the same URL is requested 3 times. the first time the wrong data comes
+// back, the second time that wrong data is revalidated with a 304 but
+// a L-M header of the right data (this triggers a cache purge), and
+// the third time the right data is returned.
+
+var listener_3 = {
+ // this listener is used to process the the request made after
+ // the cache invalidation. it expects to see the 'right data'
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA(request, inputStream, offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+
+ Assert.equal(data[0], "B".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ httpserver.stop(do_test_finished);
+ },
+};
+
+ChromeUtils.defineLazyGetter(this, "listener_2", function () {
+ return {
+ // this listener is used to process the revalidation of the
+ // corrupted cache entry. its revalidation prompts it to be cleaned
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA(request, inputStream, offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+
+ // This is 'A' from a cache revalidation, but that reval will clean the cache
+ // because of mismatched last-modified response headers
+
+ Assert.equal(data[0], "A".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/test1",
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(listener_3);
+ },
+ };
+});
+
+ChromeUtils.defineLazyGetter(this, "listener_1", function () {
+ return {
+ // this listener processes the initial request from a empty cache.
+ // the server responds with the wrong data ('A')
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA(request, inputStream, offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+ Assert.equal(data[0], "A".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/test1",
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(listener_2);
+ },
+ };
+});
+
+function run_test() {
+ do_get_profile();
+
+ evict_cache_entries();
+
+ httpserver.registerPathHandler("/test1", handler);
+ httpserver.start(-1);
+
+ var port = httpserver.identity.primaryPort;
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + port + "/test1",
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(listener_1);
+
+ do_test_pending();
+}
+
+var iter = 0;
+function handler(metadata, response) {
+ iter++;
+ if (metadata.hasHeader("If-Modified-Since")) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ response.setHeader("Last-Modified", "Tue, 15 Nov 1994 12:45:26 GMT", false);
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Cache-Control", "max-age=0", false);
+ if (iter == 1) {
+ // simulated wrong response
+ response.setHeader(
+ "Last-Modified",
+ "Wed, 16 Nov 1994 00:00:00 GMT",
+ false
+ );
+ response.bodyOutputStream.write("A", 1);
+ }
+ if (iter == 3) {
+ // 'correct' response
+ response.setHeader(
+ "Last-Modified",
+ "Tue, 15 Nov 1994 12:45:26 GMT",
+ false
+ );
+ response.bodyOutputStream.write("B", 1);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_mozTXTToHTMLConv.js b/netwerk/test/unit/test_mozTXTToHTMLConv.js
new file mode 100644
index 0000000000..1e93a440ad
--- /dev/null
+++ b/netwerk/test/unit/test_mozTXTToHTMLConv.js
@@ -0,0 +1,394 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that mozITXTToHTMLConv works properly.
+ */
+
+"use strict";
+
+function run_test() {
+ let converter = Cc["@mozilla.org/txttohtmlconv;1"].getService(
+ Ci.mozITXTToHTMLConv
+ );
+
+ const scanTXTtests = [
+ // -- RFC1738
+ {
+ input: "RFC1738: <URL:http://mozilla.org> then",
+ url: "http://mozilla.org",
+ },
+ {
+ input: "RFC1738: <URL:mailto:john.doe+test@mozilla.org> then",
+ url: "mailto:john.doe+test@mozilla.org",
+ },
+ // -- RFC2396E
+ {
+ input: "RFC2396E: <http://mozilla.org/> then",
+ url: "http://mozilla.org/",
+ },
+ {
+ input: "RFC2396E: <john.doe+test@mozilla.org> then",
+ url: "mailto:john.doe+test@mozilla.org",
+ },
+ // -- abbreviated
+ {
+ input: "see www.mozilla.org maybe",
+ url: "http://www.mozilla.org",
+ },
+ {
+ input: "mail john.doe+test@mozilla.org maybe",
+ url: "mailto:john.doe+test@mozilla.org",
+ },
+ // -- delimiters
+ {
+ input: "see http://www.mozilla.org/maybe today", // Spaces
+ url: "http://www.mozilla.org/maybe",
+ },
+ {
+ input: 'see "http://www.mozilla.org/maybe today"', // Double quotes
+ url: "http://www.mozilla.org/maybetoday", // spaces ignored
+ },
+ {
+ input: "see <http://www.mozilla.org/maybe today>", // Angle brackets
+ url: "http://www.mozilla.org/maybetoday", // spaces ignored
+ },
+ // -- freetext
+ {
+ input: "I mean http://www.mozilla.org/.",
+ url: "http://www.mozilla.org/",
+ },
+ {
+ input: "you mean http://mozilla.org:80, right?",
+ url: "http://mozilla.org:80",
+ },
+ {
+ input: "go to http://mozilla.org; then go home",
+ url: "http://mozilla.org",
+ },
+ {
+ input: "http://mozilla.org! yay!",
+ url: "http://mozilla.org",
+ },
+ {
+ input: "er, http://mozilla.com?",
+ url: "http://mozilla.com",
+ },
+ {
+ input: "http://example.org- where things happen",
+ url: "http://example.org",
+ },
+ {
+ input: "see http://mozilla.org: front page",
+ url: "http://mozilla.org",
+ },
+ {
+ input: "'http://mozilla.org/': that's the url",
+ url: "http://mozilla.org/",
+ },
+ {
+ input: "some special http://mozilla.org/?x=.,;!-:x",
+ url: "http://mozilla.org/?x=.,;!-:x",
+ },
+ {
+ // escape & when producing html
+ input: "'http://example.org/?test=true&success=true': ok",
+ url: "http://example.org/?test=true&amp;success=true",
+ },
+ {
+ input: "bracket: http://localhost/[1] etc.",
+ url: "http://localhost/",
+ },
+ {
+ input: "bracket: john.doe+test@mozilla.org[1] etc.",
+ url: "mailto:john.doe+test@mozilla.org",
+ },
+ {
+ input: "parenthesis: (http://localhost/) etc.",
+ url: "http://localhost/",
+ },
+ {
+ input: "parenthesis: (john.doe+test@mozilla.org) etc.",
+ url: "mailto:john.doe+test@mozilla.org",
+ },
+ {
+ input: "(thunderbird)http://mozilla.org/thunderbird",
+ url: "http://mozilla.org/thunderbird",
+ },
+ {
+ input: "(mail)john.doe+test@mozilla.org",
+ url: "mailto:john.doe+test@mozilla.org",
+ },
+ {
+ input: "()http://mozilla.org",
+ url: "http://mozilla.org",
+ },
+ {
+ input:
+ "parenthesis included: http://kb.mozillazine.org/Performance_(Thunderbird) etc.",
+ url: "http://kb.mozillazine.org/Performance_(Thunderbird)",
+ },
+ {
+ input: "parenthesis slash bracket: (http://localhost/)[1] etc.",
+ url: "http://localhost/",
+ },
+ {
+ input: "parenthesis bracket: (http://example.org[1]) etc.",
+ url: "http://example.org",
+ },
+ {
+ input: "ipv6 1: https://[1080::8:800:200C:417A]/foo?bar=x test",
+ url: "https://[1080::8:800:200C:417A]/foo?bar=x",
+ },
+ {
+ input: "ipv6 2: http://[::ffff:127.0.0.1]/#yay test",
+ url: "http://[::ffff:127.0.0.1]/#yay",
+ },
+ {
+ input: "ipv6 parenthesis port: (http://[2001:db8::1]:80/) test",
+ url: "http://[2001:db8::1]:80/",
+ },
+ {
+ input:
+ "test http://www.map.com/map.php?t=Nova_Scotia&markers=//Not_a_survey||description=plm2 test",
+ url: "http://www.map.com/map.php?t=Nova_Scotia&amp;markers=//Not_a_survey||description=plm2",
+ },
+ {
+ input: "bug#1509493 (john@mozilla.org)john@mozilla.org test",
+ url: "mailto:john@mozilla.org",
+ text: "john@mozilla.org",
+ },
+ {
+ input: "bug#1509493 {john@mozilla.org}john@mozilla.org test",
+ url: "mailto:john@mozilla.org",
+ text: "john@mozilla.org",
+ },
+ ];
+
+ const scanTXTglyph = [
+ // Some "glyph" testing (not exhaustive, the system supports 16 different
+ // smiley types).
+ {
+ input: "this is superscript: x^2",
+ results: ["<sup", "2", "</sup>"],
+ },
+ {
+ input: "this is plus-minus: +/-",
+ results: ["&plusmn;"],
+ },
+ {
+ input: "this is a smiley :)",
+ results: ["🙂"],
+ },
+ {
+ input: "this is a smiley :-)",
+ results: ["🙂"],
+ },
+ {
+ input: "this is a smiley :-(",
+ results: ["🙁"],
+ },
+ ];
+
+ const scanTXTstrings = [
+ "underline", // ASCII
+ "äöüßáéíóúî", // Latin-1
+ "a\u0301c\u0327c\u030Ce\u0309n\u0303t\u0326e\u0308d\u0323",
+ // áçčẻñțëḍ Latin
+ "\u016B\u00F1\u0257\u0119\u0211\u0142\u00ED\u00F1\u0119",
+ // Pseudo-ese ūñɗęȑłíñę
+ "\u01DDu\u0131\u0283\u0279\u01DDpun", // Upside down ǝuıʃɹǝpun
+ "\u03C5\u03C0\u03BF\u03B3\u03C1\u03AC\u03BC\u03BC\u03B9\u03C3\u03B7",
+ // Greek υπογράμμιση
+ "\u0441\u0438\u043B\u044C\u043D\u0443\u044E", // Russian сильную
+ "\u0C2C\u0C32\u0C2E\u0C46\u0C56\u0C28", // Telugu బలమైన
+ "\u508D\u7DDA\u3059\u308B", // Japanese 傍線する
+ "\uD841\uDF0E\uD841\uDF31\uD841\uDF79\uD843\uDC53\uD843\uDC78",
+ // Chinese (supplementary plane)
+ "\uD801\uDC14\uD801\uDC2F\uD801\uDC45\uD801\uDC28\uD801\uDC49\uD801\uDC2F\uD801\uDC3B",
+ // Deseret 𐐔𐐯𐑅𐐨𐑉𐐯𐐻
+ ];
+
+ const scanTXTstructs = [
+ {
+ delimiter: "/",
+ tag: "i",
+ class: "moz-txt-slash",
+ },
+ {
+ delimiter: "*",
+ tag: "b",
+ class: "moz-txt-star",
+ },
+ {
+ delimiter: "_",
+ tag: "span",
+ class: "moz-txt-underscore",
+ },
+ {
+ delimiter: "|",
+ tag: "code",
+ class: "moz-txt-verticalline",
+ },
+ ];
+
+ const scanHTMLtests = [
+ {
+ input: "http://foo.example.com",
+ shouldChange: true,
+ },
+ {
+ input: " <a href='http://a.example.com/'>foo</a>",
+ shouldChange: false,
+ },
+ {
+ input: "<abbr>see http://abbr.example.com</abbr>",
+ shouldChange: true,
+ },
+ {
+ input: "<!-- see http://comment.example.com/ -->",
+ shouldChange: false,
+ },
+ {
+ input: "<!-- greater > -->",
+ shouldChange: false,
+ },
+ {
+ input: "<!-- lesser < -->",
+ shouldChange: false,
+ },
+ {
+ input:
+ "<style id='ex'>background-image: url(http://example.com/ex.png);</style>",
+ shouldChange: false,
+ },
+ {
+ input: "<style>body > p, body > div { color:blue }</style>",
+ shouldChange: false,
+ },
+ {
+ input: "<script>window.location='http://script.example.com/';</script>",
+ shouldChange: false,
+ },
+ {
+ input: "<head><title>http://head.example.com/</title></head>",
+ shouldChange: false,
+ },
+ {
+ input: "<header>see http://header.example.com</header>",
+ shouldChange: true,
+ },
+ {
+ input: "<iframe src='http://iframe.example.com/' />",
+ shouldChange: false,
+ },
+ {
+ input: "broken end <script",
+ shouldChange: false,
+ },
+ ];
+
+ function hrefLink(url) {
+ return ' href="' + url + '"';
+ }
+
+ function linkText(plaintext) {
+ return ">" + plaintext + "</a>";
+ }
+
+ for (let i = 0; i < scanTXTtests.length; i++) {
+ let t = scanTXTtests[i];
+ let output = converter.scanTXT(t.input, Ci.mozITXTToHTMLConv.kURLs);
+ let link = hrefLink(t.url);
+ let text;
+ if (t.text) {
+ text = linkText(t.text);
+ }
+ if (!output.includes(link)) {
+ do_throw(
+ "Unexpected conversion by scanTXT: input=" +
+ t.input +
+ ", output=" +
+ output +
+ ", link=" +
+ link
+ );
+ }
+ if (text && !output.includes(text)) {
+ do_throw(
+ "Unexpected conversion by scanTXT: input=" +
+ t.input +
+ ", output=" +
+ output +
+ ", text=" +
+ text
+ );
+ }
+ }
+
+ for (let i = 0; i < scanTXTglyph.length; i++) {
+ let t = scanTXTglyph[i];
+ let output = converter.scanTXT(
+ t.input,
+ Ci.mozITXTToHTMLConv.kGlyphSubstitution
+ );
+ for (let j = 0; j < t.results.length; j++) {
+ if (!output.includes(t.results[j])) {
+ do_throw(
+ "Unexpected conversion by scanTXT: input=" +
+ t.input +
+ ", output=" +
+ output +
+ ", expected=" +
+ t.results[j]
+ );
+ }
+ }
+ }
+
+ for (let i = 0; i < scanTXTstrings.length; ++i) {
+ for (let j = 0; j < scanTXTstructs.length; ++j) {
+ let input =
+ scanTXTstructs[j].delimiter +
+ scanTXTstrings[i] +
+ scanTXTstructs[j].delimiter;
+ let expected =
+ "<" +
+ scanTXTstructs[j].tag +
+ ' class="' +
+ scanTXTstructs[j].class +
+ '">' +
+ '<span class="moz-txt-tag">' +
+ scanTXTstructs[j].delimiter +
+ "</span>" +
+ scanTXTstrings[i] +
+ '<span class="moz-txt-tag">' +
+ scanTXTstructs[j].delimiter +
+ "</span>" +
+ "</" +
+ scanTXTstructs[j].tag +
+ ">";
+ let actual = converter.scanTXT(input, Ci.mozITXTToHTMLConv.kStructPhrase);
+ Assert.equal(encodeURIComponent(actual), encodeURIComponent(expected));
+ }
+ }
+
+ for (let i = 0; i < scanHTMLtests.length; i++) {
+ let t = scanHTMLtests[i];
+ let output = converter.scanHTML(t.input, Ci.mozITXTToHTMLConv.kURLs);
+ let changed = t.input != output;
+ if (changed != t.shouldChange) {
+ do_throw(
+ "Unexpected change by scanHTML: changed=" +
+ changed +
+ ", shouldChange=" +
+ t.shouldChange +
+ ", \ninput=" +
+ t.input +
+ ", \noutput=" +
+ output
+ );
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_multipart_byteranges.js b/netwerk/test/unit/test_multipart_byteranges.js
new file mode 100644
index 0000000000..475aced456
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_byteranges.js
@@ -0,0 +1,143 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+
+ChromeUtils.defineLazyGetter(this, "uri", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var multipartBody =
+ "--boundary\r\n" +
+ "Content-type: text/plain\r\n" +
+ "Content-range: bytes 0-2/10\r\n" +
+ "\r\n" +
+ "aaa\r\n" +
+ "--boundary\r\n" +
+ "Content-type: text/plain\r\n" +
+ "Content-range: bytes 3-7/10\r\n" +
+ "\r\n" +
+ "bbbbb" +
+ "\r\n" +
+ "--boundary\r\n" +
+ "Content-type: text/plain\r\n" +
+ "Content-range: bytes 8-9/10\r\n" +
+ "\r\n" +
+ "cc" +
+ "\r\n" +
+ "--boundary--";
+
+function contentHandler(metadata, response) {
+ response.setHeader(
+ "Content-Type",
+ 'multipart/byteranges; boundary="boundary"'
+ );
+ response.bodyOutputStream.write(multipartBody, multipartBody.length);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData = [
+ {
+ data: "aaa",
+ type: "text/plain",
+ isByteRangeRequest: true,
+ startRange: 0,
+ endRange: 2,
+ },
+ {
+ data: "bbbbb",
+ type: "text/plain",
+ isByteRangeRequest: true,
+ startRange: 3,
+ endRange: 7,
+ },
+ {
+ data: "cc",
+ type: "text/plain",
+ isByteRangeRequest: true,
+ startRange: 8,
+ endRange: 9,
+ },
+];
+
+function responseHandler(request, buffer) {
+ Assert.equal(buffer, testData[testNum].data);
+ Assert.equal(
+ request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type
+ );
+ Assert.equal(
+ request.QueryInterface(Ci.nsIByteRangeRequest).isByteRangeRequest,
+ testData[testNum].isByteRangeRequest
+ );
+ Assert.equal(
+ request.QueryInterface(Ci.nsIByteRangeRequest).startRange,
+ testData[testNum].startRange
+ );
+ Assert.equal(
+ request.QueryInterface(Ci.nsIByteRangeRequest).endRange,
+ testData[testNum].endRange
+ );
+ if (++testNum == numTests) {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+var multipartListener = {
+ _buffer: "",
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ this._buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest(request, status) {
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var conv = streamConv.asyncConvertData(
+ "multipart/byteranges",
+ "*/*",
+ multipartListener,
+ null
+ );
+
+ var chan = make_channel(uri);
+ chan.asyncOpen(conv, null);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js b/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js
new file mode 100644
index 0000000000..c1865f5668
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js
@@ -0,0 +1,115 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+
+ChromeUtils.defineLazyGetter(this, "uri", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var multipartBody =
+ "--boundary\r\n\r\nSome text\r\n--boundary\r\nContent-Type: text/x-test\r\n\r\n<?xml version='1.1'?>\r\n<root/>\r\n--boundary\r\n\r\n<?xml version='1.0'?><root/>\r\n--boundary--";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", 'multipart/mixed; boundary="boundary"');
+ response.processAsync();
+
+ var body = multipartBody;
+ function byteByByte() {
+ if (!body.length) {
+ response.finish();
+ return;
+ }
+
+ var onebyte = body[0];
+ response.bodyOutputStream.write(onebyte, 1);
+ body = body.substring(1);
+ do_timeout(1, byteByByte);
+ }
+
+ do_timeout(1, byteByByte);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData = [
+ { data: "Some text", type: "text/plain" },
+ { data: "<?xml version='1.1'?>\r\n<root/>", type: "text/x-test" },
+ { data: "<?xml version='1.0'?><root/>", type: "text/xml" },
+];
+
+function responseHandler(request, buffer) {
+ Assert.equal(buffer, testData[testNum].data);
+ Assert.equal(
+ request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type
+ );
+ if (++testNum == numTests) {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+var multipartListener = {
+ _buffer: "",
+ _index: 0,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ this._buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest(request, status) {
+ this._index++;
+ // Second part should be last part
+ Assert.equal(
+ request.QueryInterface(Ci.nsIMultiPartChannel).isLastPart,
+ this._index == testData.length
+ );
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var conv = streamConv.asyncConvertData(
+ "multipart/mixed",
+ "*/*",
+ multipartListener,
+ null
+ );
+
+ var chan = make_channel(uri);
+ chan.asyncOpen(conv);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_multipart_streamconv.js b/netwerk/test/unit/test_multipart_streamconv.js
new file mode 100644
index 0000000000..c1503e4d8c
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv.js
@@ -0,0 +1,100 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+
+ChromeUtils.defineLazyGetter(this, "uri", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var multipartBody =
+ "--boundary\r\nSet-Cookie: foo=bar\r\n\r\nSome text\r\n--boundary\r\nContent-Type: text/x-test\r\n\r\n<?xml version='1.1'?>\r\n<root/>\r\n--boundary\r\n\r\n<?xml version='1.0'?><root/>\r\n--boundary--";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", 'multipart/mixed; boundary="boundary"');
+ response.bodyOutputStream.write(multipartBody, multipartBody.length);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData = [
+ { data: "Some text", type: "text/plain" },
+ { data: "<?xml version='1.1'?>\r\n<root/>", type: "text/x-test" },
+ { data: "<?xml version='1.0'?><root/>", type: "text/xml" },
+];
+
+function responseHandler(request, buffer) {
+ Assert.equal(buffer, testData[testNum].data);
+ Assert.equal(
+ request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type
+ );
+ if (++testNum == numTests) {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+var multipartListener = {
+ _buffer: "",
+ _index: 0,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ this._buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest(request, status) {
+ this._index++;
+ // Second part should be last part
+ Assert.equal(
+ request.QueryInterface(Ci.nsIMultiPartChannel).isLastPart,
+ this._index == testData.length
+ );
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var conv = streamConv.asyncConvertData(
+ "multipart/mixed",
+ "*/*",
+ multipartListener,
+ null
+ );
+
+ var chan = make_channel(uri);
+ chan.asyncOpen(conv);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_multipart_streamconv_empty.js b/netwerk/test/unit/test_multipart_streamconv_empty.js
new file mode 100644
index 0000000000..3d3f9dc859
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv_empty.js
@@ -0,0 +1,68 @@
+"use strict";
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+add_task(async function test_empty() {
+ let uri = "http://localhost";
+ let httpChan = make_channel(uri);
+
+ let channel = Cc["@mozilla.org/network/input-stream-channel;1"]
+ .createInstance(Ci.nsIInputStreamChannel)
+ .QueryInterface(Ci.nsIChannel);
+
+ channel.setURI(httpChan.URI);
+ channel.loadInfo = httpChan.loadInfo;
+
+ let inputStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ inputStream.setUTF8Data(""); // Pass an empty string
+
+ channel.contentStream = inputStream;
+
+ let [status, buffer] = await new Promise(resolve => {
+ let streamConv = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ let multipartListener = {
+ _buffer: "",
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {},
+ onDataAvailable(request, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest(request, status1) {
+ resolve([status1, this._buffer]);
+ },
+ };
+ let conv = streamConv.asyncConvertData(
+ "multipart/mixed",
+ "*/*",
+ multipartListener,
+ null
+ );
+
+ let chan = make_channel(uri);
+ chan.asyncOpen(conv);
+ });
+
+ Assert.notEqual(
+ status,
+ Cr.NS_OK,
+ "Should be an error code because content has no boundary"
+ );
+ Assert.equal(buffer, "", "Should have received no content");
+});
diff --git a/netwerk/test/unit/test_multipart_streamconv_missing_boundary_lead_dashes.js b/netwerk/test/unit/test_multipart_streamconv_missing_boundary_lead_dashes.js
new file mode 100644
index 0000000000..3ed2bd39c6
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv_missing_boundary_lead_dashes.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+
+ChromeUtils.defineLazyGetter(this, "uri", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var multipartBody =
+ "\r\nboundary\r\n\r\nSome text\r\nboundary\r\n\r\n<?xml version='1.0'?><root/>\r\nboundary--";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", 'multipart/mixed; boundary="boundary"');
+ response.bodyOutputStream.write(multipartBody, multipartBody.length);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData = [
+ { data: "Some text", type: "text/plain" },
+ { data: "<?xml version='1.0'?><root/>", type: "text/xml" },
+];
+
+function responseHandler(request, buffer) {
+ Assert.equal(buffer, testData[testNum].data);
+ Assert.equal(
+ request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type
+ );
+ if (++testNum == numTests) {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+var multipartListener = {
+ _buffer: "",
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ this._buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest(request, status) {
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var conv = streamConv.asyncConvertData(
+ "multipart/mixed",
+ "*/*",
+ multipartListener,
+ null
+ );
+
+ var chan = make_channel(uri);
+ chan.asyncOpen(conv);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js b/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js
new file mode 100644
index 0000000000..1df4311b90
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+
+ChromeUtils.defineLazyGetter(this, "uri", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var multipartBody =
+ "Preamble\r\n--boundary\r\n\r\nSome text\r\n--boundary\r\n\r\n<?xml version='1.0'?><root/>\r\n--boundary--";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", 'multipart/mixed; boundary="boundary"');
+ response.bodyOutputStream.write(multipartBody, multipartBody.length);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData = [
+ { data: "Some text", type: "text/plain" },
+ { data: "<?xml version='1.0'?><root/>", type: "text/xml" },
+];
+
+function responseHandler(request, buffer) {
+ Assert.equal(buffer, testData[testNum].data);
+ Assert.equal(
+ request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type
+ );
+ if (++testNum == numTests) {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+var multipartListener = {
+ _buffer: "",
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ this._buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest(request, status) {
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ },
+};
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var conv = streamConv.asyncConvertData(
+ "multipart/mixed",
+ "*/*",
+ multipartListener,
+ null
+ );
+
+ var chan = make_channel(uri);
+ chan.asyncOpen(conv);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_nestedabout_serialize.js b/netwerk/test/unit/test_nestedabout_serialize.js
new file mode 100644
index 0000000000..a8554150d2
--- /dev/null
+++ b/netwerk/test/unit/test_nestedabout_serialize.js
@@ -0,0 +1,39 @@
+"use strict";
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+const BinaryOutputStream = Components.Constructor(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+const Pipe = Components.Constructor("@mozilla.org/pipe;1", "nsIPipe", "init");
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].createInstance(
+ Ci.nsIIOService
+ );
+
+ var baseURI = ios.newURI("http://example.com/", "UTF-8");
+
+ // This depends on the redirector for about:license having the
+ // nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT flag.
+ var aboutLicense = ios.newURI("about:license", "UTF-8", baseURI);
+
+ var pipe = new Pipe(false, false, 0, 0, null);
+ var output = new BinaryOutputStream(pipe.outputStream);
+ var input = new BinaryInputStream(pipe.inputStream);
+ output.QueryInterface(Ci.nsIObjectOutputStream);
+ input.QueryInterface(Ci.nsIObjectInputStream);
+
+ output.writeCompoundObject(aboutLicense, Ci.nsIURI, true);
+ var copy = input.readObject(true);
+ copy.QueryInterface(Ci.nsIURI);
+
+ Assert.equal(copy.asciiSpec, aboutLicense.asciiSpec);
+ Assert.ok(copy.equals(aboutLicense));
+}
diff --git a/netwerk/test/unit/test_net_addr.js b/netwerk/test/unit/test_net_addr.js
new file mode 100644
index 0000000000..331fa9bc74
--- /dev/null
+++ b/netwerk/test/unit/test_net_addr.js
@@ -0,0 +1,216 @@
+"use strict";
+
+var CC = Components.Constructor;
+
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+
+/**
+ * TestServer: A single instance of this is created as |serv|. When created,
+ * it starts listening on the loopback address on port |serv.port|. Tests will
+ * connect to it after setting |serv.acceptCallback|, which is invoked after it
+ * accepts a connection.
+ *
+ * Within |serv.acceptCallback|, various properties of |serv| can be used to
+ * run checks. After the callback, the connection is closed, but the server
+ * remains listening until |serv.stop|
+ *
+ * Note: TestServer can only handle a single connection at a time. Tests
+ * should use run_next_test at the end of |serv.acceptCallback| to start the
+ * following test which creates a connection.
+ */
+function TestServer() {
+ this.reset();
+
+ // start server.
+ // any port (-1), loopback only (true), default backlog (-1)
+ this.listener = ServerSocket(-1, true, -1);
+ this.port = this.listener.port;
+ info("server: listening on " + this.port);
+ this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+ onSocketAccepted(socket, trans) {
+ info("server: got client connection");
+
+ // one connection at a time.
+ if (this.input !== null) {
+ try {
+ socket.close();
+ } catch (ignore) {}
+ do_throw("Test written to handle one connection at a time.");
+ }
+
+ try {
+ this.input = trans.openInputStream(0, 0, 0);
+ this.output = trans.openOutputStream(0, 0, 0);
+ this.selfAddr = trans.getScriptableSelfAddr();
+ this.peerAddr = trans.getScriptablePeerAddr();
+
+ this.acceptCallback();
+ } catch (e) {
+ /* In a native callback such as onSocketAccepted, exceptions might not
+ * get output correctly or logged to test output. Send them through
+ * do_throw, which fails the test immediately. */
+ do_report_unexpected_exception(e, "in TestServer.onSocketAccepted");
+ }
+
+ this.reset();
+ },
+
+ onStopListening(socket) {},
+
+ /**
+ * Called to close a connection and clean up properties.
+ */
+ reset() {
+ if (this.input) {
+ try {
+ this.input.close();
+ } catch (ignore) {}
+ }
+ if (this.output) {
+ try {
+ this.output.close();
+ } catch (ignore) {}
+ }
+
+ this.input = null;
+ this.output = null;
+ this.acceptCallback = null;
+ this.selfAddr = null;
+ this.peerAddr = null;
+ },
+
+ /**
+ * Cleanup for TestServer and this test case.
+ */
+ stop() {
+ this.reset();
+ try {
+ this.listener.close();
+ } catch (ignore) {}
+ },
+};
+
+/**
+ * Helper function.
+ * Compares two nsINetAddr objects and ensures they are logically equivalent.
+ */
+function checkAddrEqual(lhs, rhs) {
+ Assert.equal(lhs.family, rhs.family);
+
+ if (lhs.family === Ci.nsINetAddr.FAMILY_INET) {
+ Assert.equal(lhs.address, rhs.address);
+ Assert.equal(lhs.port, rhs.port);
+ }
+
+ /* TODO: fully support ipv6 and local */
+}
+
+/**
+ * An instance of SocketTransportService, used to create connections.
+ */
+var sts;
+
+/**
+ * Single instance of TestServer
+ */
+var serv;
+
+/**
+ * A place for individual tests to place Objects of importance for access
+ * throughout asynchronous testing. Particularly important for any output or
+ * input streams opened, as cleanup of those objects (by the garbage collector)
+ * causes the stream to close and may have other side effects.
+ */
+var testDataStore = null;
+
+/**
+ * IPv4 test.
+ */
+function testIpv4() {
+ testDataStore = {
+ transport: null,
+ ouput: null,
+ };
+
+ serv.acceptCallback = function () {
+ // disable the timeoutCallback
+ serv.timeoutCallback = function () {};
+
+ var selfAddr = testDataStore.transport.getScriptableSelfAddr();
+ var peerAddr = testDataStore.transport.getScriptablePeerAddr();
+
+ // check peerAddr against expected values
+ Assert.equal(peerAddr.family, Ci.nsINetAddr.FAMILY_INET);
+ Assert.equal(peerAddr.port, testDataStore.transport.port);
+ Assert.equal(peerAddr.port, serv.port);
+ Assert.equal(peerAddr.address, "127.0.0.1");
+
+ // check selfAddr against expected values
+ Assert.equal(selfAddr.family, Ci.nsINetAddr.FAMILY_INET);
+ Assert.equal(selfAddr.address, "127.0.0.1");
+
+ // check that selfAddr = server.peerAddr and vice versa.
+ checkAddrEqual(selfAddr, serv.peerAddr);
+ checkAddrEqual(peerAddr, serv.selfAddr);
+
+ testDataStore = null;
+ executeSoon(run_next_test);
+ };
+
+ // Useful timeout for debugging test hangs
+ /*serv.timeoutCallback = function(tname) {
+ if (tname === 'testIpv4')
+ do_throw('testIpv4 never completed a connection to TestServ');
+ };
+ do_timeout(connectTimeout, function(){ serv.timeoutCallback('testIpv4'); });*/
+
+ testDataStore.transport = sts.createTransport(
+ [],
+ "127.0.0.1",
+ serv.port,
+ null,
+ null
+ );
+ /*
+ * Need to hold |output| so that the output stream doesn't close itself and
+ * the associated connection.
+ */
+ testDataStore.output = testDataStore.transport.openOutputStream(
+ Ci.nsITransport.OPEN_BLOCKING,
+ 0,
+ 0
+ );
+
+ /* NEXT:
+ * openOutputStream -> onSocketAccepted -> acceptedCallback -> run_next_test
+ * OR (if the above timeout is uncommented)
+ * <connectTimeout lapses> -> timeoutCallback -> do_throw
+ */
+}
+
+/**
+ * Running the tests.
+ */
+function run_test() {
+ sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+ );
+ serv = new TestServer();
+
+ registerCleanupFunction(function () {
+ serv.stop();
+ });
+
+ add_test(testIpv4);
+ /* TODO: testIpv6 */
+ /* TODO: testLocal */
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_network_connectivity_service.js b/netwerk/test/unit/test_network_connectivity_service.js
new file mode 100644
index 0000000000..6774e79e34
--- /dev/null
+++ b/netwerk/test/unit/test_network_connectivity_service.js
@@ -0,0 +1,215 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+/**
+ * Waits for an observer notification to fire.
+ *
+ * @param {String} topicName The notification topic.
+ * @returns {Promise} A promise that fulfills when the notification is fired.
+ */
+function promiseObserverNotification(topicName, matchFunc) {
+ return new Promise((resolve, reject) => {
+ Services.obs.addObserver(function observe(subject, topic, data) {
+ let matches = typeof matchFunc != "function" || matchFunc(subject, data);
+ if (!matches) {
+ return;
+ }
+ Services.obs.removeObserver(observe, topic);
+ resolve({ subject, data });
+ }, topicName);
+ });
+}
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.connectivity-service.DNSv4.domain");
+ Services.prefs.clearUserPref("network.connectivity-service.DNSv6.domain");
+ Services.prefs.clearUserPref("network.captive-portal-service.testMode");
+ Services.prefs.clearUserPref("network.connectivity-service.IPv4.url");
+ Services.prefs.clearUserPref("network.connectivity-service.IPv6.url");
+});
+
+let httpserver = null;
+let httpserverv6 = null;
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/content";
+});
+
+ChromeUtils.defineLazyGetter(this, "URLv6", function () {
+ return "http://[::1]:" + httpserverv6.identity.primaryPort + "/content";
+});
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+
+ const responseBody = "anybody";
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+const kDNSv6Domain =
+ mozinfo.os == "linux" || mozinfo.os == "android"
+ ? "ip6-localhost"
+ : "localhost";
+
+add_task(async function testDNS() {
+ let ncs = Cc[
+ "@mozilla.org/network/network-connectivity-service;1"
+ ].getService(Ci.nsINetworkConnectivityService);
+
+ // Set the endpoints, trigger a DNS recheck, and wait for it to complete.
+ Services.prefs.setCharPref(
+ "network.connectivity-service.DNSv4.domain",
+ "example.org"
+ );
+ Services.prefs.setCharPref(
+ "network.connectivity-service.DNSv6.domain",
+ kDNSv6Domain
+ );
+ ncs.recheckDNS();
+ await promiseObserverNotification(
+ "network:connectivity-service:dns-checks-complete"
+ );
+
+ equal(
+ ncs.DNSv4,
+ Ci.nsINetworkConnectivityService.OK,
+ "Check DNSv4 support (expect OK)"
+ );
+ equal(
+ ncs.DNSv6,
+ Ci.nsINetworkConnectivityService.OK,
+ "Check DNSv6 support (expect OK)"
+ );
+
+ // Set the endpoints to non-exitant domains, trigger a DNS recheck, and wait for it to complete.
+ Services.prefs.setCharPref(
+ "network.connectivity-service.DNSv4.domain",
+ "does-not-exist.example"
+ );
+ Services.prefs.setCharPref(
+ "network.connectivity-service.DNSv6.domain",
+ "does-not-exist.example"
+ );
+ let observerNotification = promiseObserverNotification(
+ "network:connectivity-service:dns-checks-complete"
+ );
+ ncs.recheckDNS();
+ await observerNotification;
+
+ equal(
+ ncs.DNSv4,
+ Ci.nsINetworkConnectivityService.NOT_AVAILABLE,
+ "Check DNSv4 support (expect N/A)"
+ );
+ equal(
+ ncs.DNSv6,
+ Ci.nsINetworkConnectivityService.NOT_AVAILABLE,
+ "Check DNSv6 support (expect N/A)"
+ );
+
+ // Set the endpoints back to the proper domains, and simulate a captive portal
+ // event.
+ Services.prefs.setCharPref(
+ "network.connectivity-service.DNSv4.domain",
+ "example.org"
+ );
+ Services.prefs.setCharPref(
+ "network.connectivity-service.DNSv6.domain",
+ kDNSv6Domain
+ );
+ observerNotification = promiseObserverNotification(
+ "network:connectivity-service:dns-checks-complete"
+ );
+ Services.obs.notifyObservers(null, "network:captive-portal-connectivity");
+ // This will cause the state to go to UNKNOWN for a bit, until the check is completed.
+ equal(
+ ncs.DNSv4,
+ Ci.nsINetworkConnectivityService.UNKNOWN,
+ "Check DNSv4 support (expect UNKNOWN)"
+ );
+ equal(
+ ncs.DNSv6,
+ Ci.nsINetworkConnectivityService.UNKNOWN,
+ "Check DNSv6 support (expect UNKNOWN)"
+ );
+
+ await observerNotification;
+
+ equal(
+ ncs.DNSv4,
+ Ci.nsINetworkConnectivityService.OK,
+ "Check DNSv4 support (expect OK)"
+ );
+ equal(
+ ncs.DNSv6,
+ Ci.nsINetworkConnectivityService.OK,
+ "Check DNSv6 support (expect OK)"
+ );
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ httpserverv6 = new HttpServer();
+ httpserverv6.registerPathHandler("/contentt", contentHandler);
+ httpserverv6._start(-1, "[::1]");
+
+ // Before setting the pref, this status is unknown in automation
+ equal(
+ ncs.IPv4,
+ Ci.nsINetworkConnectivityService.UNKNOWN,
+ "Check IPv4 support (expect UNKNOWN)"
+ );
+ equal(
+ ncs.IPv6,
+ Ci.nsINetworkConnectivityService.UNKNOWN,
+ "Check IPv6 support (expect UNKNOWN)"
+ );
+
+ Services.prefs.setBoolPref("network.captive-portal-service.testMode", true);
+ Services.prefs.setCharPref("network.connectivity-service.IPv4.url", URL);
+ Services.prefs.setCharPref("network.connectivity-service.IPv6.url", URLv6);
+ observerNotification = promiseObserverNotification(
+ "network:connectivity-service:ip-checks-complete"
+ );
+ ncs.recheckIPConnectivity();
+ await observerNotification;
+
+ equal(
+ ncs.IPv4,
+ Ci.nsINetworkConnectivityService.OK,
+ "Check IPv4 support (expect OK)"
+ );
+ equal(
+ ncs.IPv6,
+ Ci.nsINetworkConnectivityService.OK,
+ "Check IPv6 support (expect OK)"
+ );
+
+ // check that the CPS status is NOT_AVAILABLE when the endpoint is down.
+ await new Promise(resolve => httpserver.stop(resolve));
+ await new Promise(resolve => httpserverv6.stop(resolve));
+ observerNotification = promiseObserverNotification(
+ "network:connectivity-service:ip-checks-complete"
+ );
+ Services.obs.notifyObservers(null, "network:captive-portal-connectivity");
+ await observerNotification;
+
+ equal(
+ ncs.IPv4,
+ Ci.nsINetworkConnectivityService.NOT_AVAILABLE,
+ "Check IPv4 support (expect NOT_AVAILABLE)"
+ );
+ equal(
+ ncs.IPv6,
+ Ci.nsINetworkConnectivityService.NOT_AVAILABLE,
+ "Check IPv6 support (expect NOT_AVAILABLE)"
+ );
+});
diff --git a/netwerk/test/unit/test_networking_over_socket_process.js b/netwerk/test/unit/test_networking_over_socket_process.js
new file mode 100644
index 0000000000..79e17140e1
--- /dev/null
+++ b/netwerk/test/unit/test_networking_over_socket_process.js
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+let h2Port;
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+function setup() {
+ Services.prefs.setIntPref("network.max_socket_process_failed_count", 2);
+ trr_test_setup();
+
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ Assert.ok(mozinfo.socketprocess_networking);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.max_socket_process_failed_count");
+ trr_clear_prefs();
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+add_task(async function setupTRRServer() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ // Only the last record is valid to use.
+ await trrServer.registerDoHAnswers("test.example.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.example.com",
+ values: [
+ { key: "alpn", value: ["h2"] },
+ { key: "port", value: h2Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.example.com", "A", {
+ answers: [
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+});
+
+async function doTestSimpleRequest(fromSocketProcess) {
+ let { inRecord } = await new TRRDNSListener("test.example.com", "127.0.0.1");
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ Assert.equal(inRecord.resolvedInSocketProcess(), fromSocketProcess);
+
+ let chan = makeChan(`https://test.example.com/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.isLoadedBySocketProcess, fromSocketProcess);
+}
+
+// Test if the data is loaded from socket process.
+add_task(async function testSimpleRequest() {
+ await doTestSimpleRequest(true);
+});
+
+function killSocketProcess(pid) {
+ const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+ );
+ ProcessTools.kill(pid);
+}
+
+// Test if socket process is restarted.
+add_task(async function testSimpleRequestAfterCrash() {
+ let socketProcessId = Services.io.socketProcessId;
+ info(`socket process pid is ${socketProcessId}`);
+ Assert.ok(socketProcessId != 0);
+
+ killSocketProcess(socketProcessId);
+
+ info("wait socket process restart...");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+
+ await doTestSimpleRequest(true);
+});
+
+// Test if data is loaded from parent process.
+add_task(async function testTooManyCrashes() {
+ let socketProcessId = Services.io.socketProcessId;
+ info(`socket process pid is ${socketProcessId}`);
+ Assert.ok(socketProcessId != 0);
+
+ let socketProcessCrashed = false;
+ Services.obs.addObserver(function observe(subject, topic, data) {
+ Services.obs.removeObserver(observe, topic);
+ socketProcessCrashed = true;
+ }, "network:socket-process-crashed");
+
+ killSocketProcess(socketProcessId);
+ await TestUtils.waitForCondition(() => socketProcessCrashed);
+ await doTestSimpleRequest(false);
+});
diff --git a/netwerk/test/unit/test_no_cookies_after_last_pb_exit.js b/netwerk/test/unit/test_no_cookies_after_last_pb_exit.js
new file mode 100644
index 0000000000..0f798ab0da
--- /dev/null
+++ b/netwerk/test/unit/test_no_cookies_after_last_pb_exit.js
@@ -0,0 +1,135 @@
+"use strict";
+
+do_get_profile();
+
+// This test checks that active private-browsing HTTP channels, do not save
+// cookies after the termination of the private-browsing session.
+
+// This test consists in following steps:
+// - starts a http server
+// - no cookies at this point
+// - does a beacon request in private-browsing mode
+// - after the completion of the request, a cookie should be set (cookie cleanup)
+// - does a beacon request in private-browsing mode and dispatch a
+// last-pb-context-exit notification
+// - after the completion of the request, no cookies should be set
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let server;
+
+function setupServer() {
+ info("Starting the server...");
+
+ function beaconHandler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 204, "No Content");
+ response.setHeader("Set-Cookie", "a=b; path=/beacon; sameSite=lax", false);
+ response.bodyOutputStream.write("", 0);
+ }
+
+ server = new HttpServer();
+ server.registerPathHandler("/beacon", beaconHandler);
+ server.start(-1);
+ next();
+}
+
+function shutdownServer() {
+ info("Terminating the server...");
+ server.stop(next);
+}
+
+function sendRequest(notification) {
+ info("Sending a request...");
+
+ var privateLoadContext = Cu.createPrivateLoadContext();
+
+ var path =
+ "http://localhost:" +
+ server.identity.primaryPort +
+ "/beacon?" +
+ Math.random();
+
+ var uri = NetUtil.newURI(path);
+ var securityFlags =
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL |
+ Ci.nsILoadInfo.SEC_COOKIES_INCLUDE;
+ var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+ privateBrowsingId: 1,
+ });
+
+ var chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_BEACON,
+ });
+
+ chan.notificationCallbacks = Cu.createPrivateLoadContext();
+
+ let loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
+ Ci.nsILoadGroup
+ );
+
+ loadGroup.notificationCallbacks = Cu.createPrivateLoadContext();
+ chan.loadGroup = loadGroup;
+
+ chan.notificationCallbacks = privateLoadContext;
+ var channelListener = new ChannelListener(next, null, CL_ALLOW_UNKNOWN_CL);
+
+ if (notification) {
+ info("Sending notification...");
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+ }
+
+ chan.asyncOpen(channelListener);
+}
+
+function checkCookies(hasCookie) {
+ let cm = Services.cookies;
+ Assert.equal(
+ cm.cookieExists("localhost", "/beacon", "a", { privateBrowsingId: 1 }),
+ hasCookie
+ );
+ cm.removeAll();
+ next();
+}
+
+const steps = [
+ setupServer,
+
+ // no cookie at startup
+ () => checkCookies(false),
+
+ // no last-pb-context-exit notification
+ () => sendRequest(false),
+ () => checkCookies(true),
+
+ // last-pb-context-exit notification
+ () => sendRequest(true),
+ () => checkCookies(false),
+
+ shutdownServer,
+];
+
+function next() {
+ if (!steps.length) {
+ do_test_finished();
+ return;
+ }
+
+ steps.shift()();
+}
+
+function run_test() {
+ // We don't want to have CookieJarSettings blocking this test.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ do_test_pending();
+ next();
+}
diff --git a/netwerk/test/unit/test_node_execute.js b/netwerk/test/unit/test_node_execute.js
new file mode 100644
index 0000000000..3640514a8e
--- /dev/null
+++ b/netwerk/test/unit/test_node_execute.js
@@ -0,0 +1,87 @@
+// This test checks that the interaction between NodeServer.execute defined in
+// httpd.js and the node server that we're interacting with defined in
+// moz-http2.js is working properly.
+/* global my_defined_var */
+
+"use strict";
+
+add_task(async function test_execute() {
+ function f() {
+ return "bla";
+ }
+ let id = await NodeServer.fork();
+ equal(await NodeServer.execute(id, `"hello"`), "hello");
+ equal(await NodeServer.execute(id, `(() => "hello")()`), "hello");
+ equal(await NodeServer.execute(id, `my_defined_var = 1;`), 1);
+ equal(await NodeServer.execute(id, `(() => my_defined_var)()`), 1);
+ equal(await NodeServer.execute(id, `my_defined_var`), 1);
+
+ await NodeServer.execute(id, `not_defined_var`)
+ .then(() => {
+ ok(false, "should have thrown");
+ })
+ .catch(e => {
+ equal(e.message, "ReferenceError: not_defined_var is not defined");
+ ok(
+ e.stack.includes("moz-http2-child.js"),
+ `stack should be coming from moz-http2-child.js - ${e.stack}`
+ );
+ });
+ await NodeServer.execute("definitely_wrong_id", `"hello"`)
+ .then(() => {
+ ok(false, "should have thrown");
+ })
+ .catch(e => {
+ equal(e.message, "Error: could not find id");
+ ok(
+ e.stack.includes("moz-http2.js"),
+ `stack should be coming from moz-http2.js - ${e.stack}`
+ );
+ });
+
+ // Defines f in the context of the node server.
+ // The implementation of NodeServer.execute prepends `functionName =` to the
+ // body of the function we pass so it gets attached to the global context
+ // in the server.
+ equal(await NodeServer.execute(id, f), undefined);
+ equal(await NodeServer.execute(id, `f()`), "bla");
+
+ class myClass {
+ static doStuff() {
+ return my_defined_var;
+ }
+ }
+
+ equal(await NodeServer.execute(id, myClass), undefined);
+ equal(await NodeServer.execute(id, `myClass.doStuff()`), 1);
+
+ equal(await NodeServer.kill(id), undefined);
+ await NodeServer.execute(id, `f()`)
+ .then(() => ok(false, "should throw"))
+ .catch(e => equal(e.message, "Error: could not find id"));
+ id = await NodeServer.fork();
+ // Check that a child process dying during a call throws an error.
+ await NodeServer.execute(id, `process.exit()`)
+ .then(() => ok(false, "should throw"))
+ .catch(e =>
+ equal(e.message, "child process exit closing code: 0 signal: null")
+ );
+
+ id = await NodeServer.fork();
+ equal(
+ await NodeServer.execute(
+ id,
+ `setTimeout(function() { sendBackResponse(undefined) }, 0); 2`
+ ),
+ 2
+ );
+ await new Promise(resolve => do_timeout(10, resolve));
+ await NodeServer.kill(id)
+ .then(() => ok(false, "should throw"))
+ .catch(e =>
+ equal(
+ e.message,
+ `forked process without handler sent: {"error":"","errorStack":""}\n`
+ )
+ );
+});
diff --git a/netwerk/test/unit/test_nojsredir.js b/netwerk/test/unit/test_nojsredir.js
new file mode 100644
index 0000000000..6b6831a222
--- /dev/null
+++ b/netwerk/test/unit/test_nojsredir.js
@@ -0,0 +1,67 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ { url: "/test/test", datalen: 16 },
+
+ // Test that the http channel fails and the response body is suppressed
+ // bug 255119
+ {
+ url: "/test/test",
+ responseheader: ["Location: javascript:alert()"],
+ flags: CL_EXPECT_FAILURE,
+ datalen: 0,
+ },
+];
+
+function setupChannel(url) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + url,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function startIter() {
+ var channel = setupChannel(tests[index].url);
+ channel.asyncOpen(
+ new ChannelListener(completeIter, channel, tests[index].flags)
+ );
+}
+
+function completeIter(request, data, ctx) {
+ Assert.ok(data.length == tests[index].datalen);
+ if (++index < tests.length) {
+ startIter();
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/test/test", handler);
+ httpserver.start(-1);
+
+ startIter();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = "thequickbrownfox";
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var header = tests[index].responseheader;
+ if (header != undefined) {
+ for (var i = 0; i < header.length; i++) {
+ var splitHdr = header[i].split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+ }
+
+ response.setStatusLine(metadata.httpVersion, 302, "Redirected");
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_non_ipv4_hostname_ending_in_number_cookie_db.js b/netwerk/test/unit/test_non_ipv4_hostname_ending_in_number_cookie_db.js
new file mode 100644
index 0000000000..2d4859857c
--- /dev/null
+++ b/netwerk/test/unit/test_non_ipv4_hostname_ending_in_number_cookie_db.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Note that E2E test doesn't seem possible via setting a cookie
+// from xpcshell (main process) since:
+// 1. setCookieStringFromHttp requires a valid URL
+// 2. setCookieStringFromDocument requires access to the document
+// 3. Services.cookies.add() is just a backdoor that will bypass
+// Similarly, even with a browser test, in order to call
+// content.document.cookie we would need to SpecialPowers.spawn
+// into a BrowserTestUtils tab which requires a valid URL
+
+// not possible to make a valid url with non-ipv4 hostname ending in a number
+add_task(async function test_url_failure() {
+ let validUrl = Services.io.newURI("https://example.com/");
+ Assert.equal(validUrl.host, "example.com");
+
+ // ipv4 ending in number is fine
+ let validUrl2 = Services.io.newURI("https://1.2.3.4");
+ Assert.equal(validUrl2.host, "1.2.3.4");
+
+ // non-ipv4 ending in number is not
+ try {
+ Assert.throws(
+ () => {
+ Services.io.newURI("https://example.0");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "non-ipv3 ending in number throws"
+ );
+ } catch {}
+});
+
+add_task(async function test_migrate_invalid_cookie() {
+ let profile = do_get_profile();
+ let dbFile = do_get_cookie_file(profile);
+ Assert.ok(!dbFile.exists());
+
+ let schema12db = new CookieDatabaseConnection(dbFile, 12);
+
+ let now = Date.now() * 1000; // date in microseconds
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+
+ let cookie1 = new Cookie(
+ "cookie-name1",
+ "cookie-value1",
+ "cookie-host1.com",
+ "/", // path
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ // non-ipv4 urls that have a hostname ending in a number are now invalid
+ let badcookie = new Cookie(
+ "cookie-name",
+ "cookie-value",
+ "cookie-host.0",
+ "/", // path
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ let cookie2 = new Cookie(
+ "cookie-name2",
+ "cookie-value2",
+ "cookie-host2.com",
+ "/", // path
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ schema12db.insertCookie(cookie1);
+ schema12db.insertCookie(badcookie);
+ schema12db.insertCookie(cookie2);
+
+ // check that 3 cookies were added to the db
+ Assert.equal(do_count_cookies_in_db(schema12db.db), 3);
+ Assert.equal(do_count_cookies_in_db(schema12db.db, "cookie-host1.com"), 1);
+ Assert.equal(do_count_cookies_in_db(schema12db.db, "cookie-host2.com"), 1);
+ Assert.equal(do_count_cookies_in_db(schema12db.db, "cookie-host.0"), 1);
+
+ // start the cookie service, pull the data into memory
+ // check that cookie1 and cookie2 were brought into memory
+ // and that badcookie was not
+ let cookie1Exists = false;
+ let cookie2Exists = false;
+ let badcookieExists = false;
+ for (let cookie of Services.cookies.cookies) {
+ if (cookie.host == "cookie-host1.com") {
+ cookie1Exists = true;
+ }
+ if (cookie.host == "cookie-host2.com") {
+ cookie2Exists = true;
+ }
+ if (cookie.host == "cookie-host.0") {
+ badcookieExists = true;
+ }
+ }
+ Assert.ok(cookie1Exists, "Cookie 1 was inadvertently removed");
+ Assert.ok(cookie2Exists, "Cookie 2 was inadvertently removed");
+ Assert.ok(!badcookieExists, "Bad cookie was not filtered by migration");
+ // Schema was upgraded by cookie service
+ Assert.equal(schema12db.db.schemaVersion, 13);
+
+ // reload to make sure removal was written correctly
+ await promise_close_profile();
+ do_load_profile();
+
+ // check that the db was also updated
+ Assert.equal(do_count_cookies_in_db(schema12db.db), 2);
+ Assert.equal(do_count_cookies_in_db(schema12db.db, "cookie-host1.com"), 1);
+ Assert.equal(do_count_cookies_in_db(schema12db.db, "cookie-host2.com"), 1);
+ Assert.equal(do_count_cookies_in_db(schema12db.db, "cookie-host.0"), 0);
+
+ schema12db.close();
+});
diff --git a/netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js b/netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js
new file mode 100644
index 0000000000..577b2c6da8
--- /dev/null
+++ b/netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js
@@ -0,0 +1,193 @@
+"use strict";
+
+var CC = Components.Constructor;
+
+var Pipe = CC("@mozilla.org/pipe;1", Ci.nsIPipe, "init");
+var BufferedOutputStream = CC(
+ "@mozilla.org/network/buffered-output-stream;1",
+ Ci.nsIBufferedOutputStream,
+ "init"
+);
+var ScriptableInputStream = CC(
+ "@mozilla.org/scriptableinputstream;1",
+ Ci.nsIScriptableInputStream,
+ "init"
+);
+
+// Verify that pipes behave as we expect. Subsequent tests assume
+// pipes behave as demonstrated here.
+add_test(function checkWouldBlockPipe() {
+ // Create a pipe with a one-byte buffer
+ var pipe = new Pipe(true, true, 1, 1);
+
+ // Writing two bytes should transfer only one byte, and
+ // return a partial count, not would-block.
+ Assert.equal(pipe.outputStream.write("xy", 2), 1);
+ Assert.equal(pipe.inputStream.available(), 1);
+
+ do_check_throws_nsIException(
+ () => pipe.outputStream.write("y", 1),
+ "NS_BASE_STREAM_WOULD_BLOCK"
+ );
+
+ // Check that nothing was written to the pipe.
+ Assert.equal(pipe.inputStream.available(), 1);
+ run_next_test();
+});
+
+// A writeFrom to a buffered stream should return
+// NS_BASE_STREAM_WOULD_BLOCK if no data was written.
+add_test(function writeFromBlocksImmediately() {
+ // Create a full pipe for our output stream. This will 'would-block' when
+ // written to.
+ var outPipe = new Pipe(true, true, 1, 1);
+ Assert.equal(outPipe.outputStream.write("x", 1), 1);
+
+ // Create a buffered stream, and fill its buffer, so the next write will
+ // try to flush.
+ var buffered = new BufferedOutputStream(outPipe.outputStream, 10);
+ Assert.equal(buffered.write("0123456789", 10), 10);
+
+ // Create a pipe with some data to be our input stream for the writeFrom
+ // call.
+ var inPipe = new Pipe(true, true, 1, 1);
+ Assert.equal(inPipe.outputStream.write("y", 1), 1);
+
+ Assert.equal(inPipe.inputStream.available(), 1);
+ do_check_throws_nsIException(
+ () => buffered.writeFrom(inPipe.inputStream, 1),
+ "NS_BASE_STREAM_WOULD_BLOCK"
+ );
+
+ // No data should have been consumed from the pipe.
+ Assert.equal(inPipe.inputStream.available(), 1);
+
+ run_next_test();
+});
+
+// A writeFrom to a buffered stream should return a partial count if any
+// data is written, when the last Flush call can only flush a portion of
+// the data.
+add_test(function writeFromReturnsPartialCountOnPartialFlush() {
+ // Create a pipe for our output stream. This will accept five bytes, and
+ // then 'would-block'.
+ var outPipe = new Pipe(true, true, 5, 1);
+
+ // Create a reference to the pipe's readable end that can be used
+ // from JavaScript.
+ var outPipeReadable = new ScriptableInputStream(outPipe.inputStream);
+
+ // Create a buffered stream whose buffer is too large to be flushed
+ // entirely to the output pipe.
+ var buffered = new BufferedOutputStream(outPipe.outputStream, 7);
+
+ // Create a pipe to be our input stream for the writeFrom call.
+ var inPipe = new Pipe(true, true, 15, 1);
+
+ // Write some data to our input pipe, for the rest of the test to consume.
+ Assert.equal(inPipe.outputStream.write("0123456789abcde", 15), 15);
+ Assert.equal(inPipe.inputStream.available(), 15);
+
+ // Write from the input pipe to the buffered stream. The buffered stream
+ // will fill its seven-byte buffer; and then the flush will only succeed
+ // in writing five bytes to the output pipe. The writeFrom call should
+ // return the number of bytes it consumed from inputStream.
+ Assert.equal(buffered.writeFrom(inPipe.inputStream, 11), 7);
+ Assert.equal(outPipe.inputStream.available(), 5);
+ Assert.equal(inPipe.inputStream.available(), 8);
+
+ // The partially-successful Flush should have created five bytes of
+ // available space in the buffered stream's buffer, so we should be able
+ // to write five bytes to it without blocking.
+ Assert.equal(buffered.writeFrom(inPipe.inputStream, 5), 5);
+ Assert.equal(outPipe.inputStream.available(), 5);
+ Assert.equal(inPipe.inputStream.available(), 3);
+
+ // Attempting to write any more data should would-block.
+ do_check_throws_nsIException(
+ () => buffered.writeFrom(inPipe.inputStream, 1),
+ "NS_BASE_STREAM_WOULD_BLOCK"
+ );
+
+ // No data should have been consumed from the pipe.
+ Assert.equal(inPipe.inputStream.available(), 3);
+
+ // Push the rest of the data through, checking that it all came through.
+ Assert.equal(outPipeReadable.available(), 5);
+ Assert.equal(outPipeReadable.read(5), "01234");
+ // Flush returns NS_ERROR_FAILURE if it can't transfer the full amount.
+ do_check_throws_nsIException(() => buffered.flush(), "NS_ERROR_FAILURE");
+ Assert.equal(outPipeReadable.available(), 5);
+ Assert.equal(outPipeReadable.read(5), "56789");
+ buffered.flush();
+ Assert.equal(outPipeReadable.available(), 2);
+ Assert.equal(outPipeReadable.read(2), "ab");
+ Assert.equal(buffered.writeFrom(inPipe.inputStream, 3), 3);
+ buffered.flush();
+ Assert.equal(outPipeReadable.available(), 3);
+ Assert.equal(outPipeReadable.read(3), "cde");
+
+ run_next_test();
+});
+
+// A writeFrom to a buffered stream should return a partial count if any
+// data is written, when the last Flush call blocks.
+add_test(function writeFromReturnsPartialCountOnBlock() {
+ // Create a pipe for our output stream. This will accept five bytes, and
+ // then 'would-block'.
+ var outPipe = new Pipe(true, true, 5, 1);
+
+ // Create a reference to the pipe's readable end that can be used
+ // from JavaScript.
+ var outPipeReadable = new ScriptableInputStream(outPipe.inputStream);
+
+ // Create a buffered stream whose buffer is too large to be flushed
+ // entirely to the output pipe.
+ var buffered = new BufferedOutputStream(outPipe.outputStream, 7);
+
+ // Create a pipe to be our input stream for the writeFrom call.
+ var inPipe = new Pipe(true, true, 15, 1);
+
+ // Write some data to our input pipe, for the rest of the test to consume.
+ Assert.equal(inPipe.outputStream.write("0123456789abcde", 15), 15);
+ Assert.equal(inPipe.inputStream.available(), 15);
+
+ // Write enough from the input pipe to the buffered stream to fill the
+ // output pipe's buffer, and then flush it. Nothing should block or fail,
+ // but the output pipe should now be full.
+ Assert.equal(buffered.writeFrom(inPipe.inputStream, 5), 5);
+ buffered.flush();
+ Assert.equal(outPipe.inputStream.available(), 5);
+ Assert.equal(inPipe.inputStream.available(), 10);
+
+ // Now try to write more from the input pipe than the buffered stream's
+ // buffer can hold. It will attempt to flush, but the output pipe will
+ // would-block without accepting any data. writeFrom should return the
+ // correct partial count.
+ Assert.equal(buffered.writeFrom(inPipe.inputStream, 10), 7);
+ Assert.equal(outPipe.inputStream.available(), 5);
+ Assert.equal(inPipe.inputStream.available(), 3);
+
+ // Attempting to write any more data should would-block.
+ do_check_throws_nsIException(
+ () => buffered.writeFrom(inPipe.inputStream, 3),
+ "NS_BASE_STREAM_WOULD_BLOCK"
+ );
+
+ // No data should have been consumed from the pipe.
+ Assert.equal(inPipe.inputStream.available(), 3);
+
+ // Push the rest of the data through, checking that it all came through.
+ Assert.equal(outPipeReadable.available(), 5);
+ Assert.equal(outPipeReadable.read(5), "01234");
+ // Flush returns NS_ERROR_FAILURE if it can't transfer the full amount.
+ do_check_throws_nsIException(() => buffered.flush(), "NS_ERROR_FAILURE");
+ Assert.equal(outPipeReadable.available(), 5);
+ Assert.equal(outPipeReadable.read(5), "56789");
+ Assert.equal(buffered.writeFrom(inPipe.inputStream, 3), 3);
+ buffered.flush();
+ Assert.equal(outPipeReadable.available(), 5);
+ Assert.equal(outPipeReadable.read(5), "abcde");
+
+ run_next_test();
+});
diff --git a/netwerk/test/unit/test_ntlm_authentication.js b/netwerk/test/unit/test_ntlm_authentication.js
new file mode 100644
index 0000000000..f17f7ebf7d
--- /dev/null
+++ b/netwerk/test/unit/test_ntlm_authentication.js
@@ -0,0 +1,268 @@
+// This file tests authentication prompt callbacks
+// TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// Turn off the authentication dialog blocking for this test.
+var prefs = Services.prefs;
+prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "PORT", function () {
+ return httpserv.identity.primaryPort;
+});
+
+const FLAG_RETURN_FALSE = 1 << 0;
+const FLAG_WRONG_PASSWORD = 1 << 1;
+const FLAG_BOGUS_USER = 1 << 2;
+// const FLAG_PREVIOUS_FAILED = 1 << 3;
+const CROSS_ORIGIN = 1 << 4;
+const FLAG_NO_REALM = 1 << 5;
+const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
+
+function AuthPrompt1(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt1.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
+
+ prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword: function ap1_promptUP(
+ title,
+ text,
+ realm,
+ savePW,
+ user,
+ pw
+ ) {
+ if (this.flags & FLAG_NO_REALM) {
+ // Note that the realm here isn't actually the realm. it's a pw mgr key.
+ Assert.equal(URL + " (" + this.expectedRealm + ")", realm);
+ }
+ if (!(this.flags & CROSS_ORIGIN)) {
+ if (!text.includes(this.expectedRealm)) {
+ do_throw("Text must indicate the realm");
+ }
+ } else if (text.includes(this.expectedRealm)) {
+ do_throw("There should not be realm for cross origin");
+ }
+ if (!text.includes("localhost")) {
+ do_throw("Text must indicate the hostname");
+ }
+ if (!text.includes(String(PORT))) {
+ do_throw("Text must indicate the port");
+ }
+ if (text.includes("-1")) {
+ do_throw("Text must contain negative numbers");
+ }
+
+ if (this.flags & FLAG_RETURN_FALSE) {
+ return false;
+ }
+
+ if (this.flags & FLAG_BOGUS_USER) {
+ this.user = "foo\nbar";
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ this.user = "é";
+ }
+
+ user.value = this.user;
+ if (this.flags & FLAG_WRONG_PASSWORD) {
+ pw.value = this.pass + ".wrong";
+ // Now clear the flag to avoid an infinite loop
+ this.flags &= ~FLAG_WRONG_PASSWORD;
+ } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+ pw.value = "é";
+ } else {
+ pw.value = this.pass;
+ }
+ return true;
+ },
+
+ promptPassword: function ap1_promptPW(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ },
+};
+
+function AuthPrompt2(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt2.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap2_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor(flags, versions) {
+ this.flags = flags;
+ this.versions = versions;
+}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt1) {
+ this.prompt1 = new AuthPrompt1(this.flags);
+ }
+ return this.prompt1;
+ }
+ if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2) {
+ this.prompt2 = new AuthPrompt2(this.flags);
+ }
+ return this.prompt2;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt1: null,
+ prompt2: null,
+};
+
+function RealmTestRequestor() {}
+
+RealmTestRequestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIAuthPrompt2",
+ ]),
+
+ getInterface: function realmtest_interface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ return this;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
+ Assert.equal(authInfo.realm, '"foo_bar');
+
+ return false;
+ },
+
+ asyncPromptAuth: function realmtest_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function makeChan(url, loadingUrl) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+// /auth/ntlm/simple
+function authNtlmSimple(metadata, response) {
+ if (!metadata.hasHeader("Authorization")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "NTLM", false);
+ return;
+ }
+
+ let challenge = metadata.getHeader("Authorization");
+ if (!challenge.startsWith("NTLM ")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ return;
+ }
+
+ let decoded = atob(challenge.substring(5));
+ info(decoded);
+
+ if (!decoded.startsWith("NTLMSSP\0")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ return;
+ }
+
+ let isNegotiate = decoded.substring(8).startsWith("\x01\x00\x00\x00");
+ let isAuthenticate = decoded.substring(8).startsWith("\x03\x00\x00\x00");
+
+ if (isNegotiate) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader(
+ "WWW-Authenticate",
+ "NTLM TlRMTVNTUAACAAAAAAAAAAAoAAABggAAASNFZ4mrze8AAAAAAAAAAAAAAAAAAAAA",
+ false
+ );
+ return;
+ }
+
+ if (isAuthenticate) {
+ let body = "OK";
+ response.bodyOutputStream.write(body, body.length);
+ return;
+ }
+
+ // Something else went wrong.
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+}
+
+let httpserv;
+add_task(async function test_ntlm() {
+ Services.prefs.setBoolPref("network.auth.force-generic-ntlm", true);
+ Services.prefs.setBoolPref("network.auth.force-generic-ntlm-v1", true);
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
+ httpserv.start(-1);
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.auth.force-generic-ntlm");
+ Services.prefs.clearUserPref("network.auth.force-generic-ntlm-v1");
+
+ await httpserv.stop();
+ });
+
+ var chan = makeChan(URL + "/auth/ntlm/simple", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ let [req, buf] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((req1, buf1) => resolve([req1, buf1]), null)
+ );
+ });
+ Assert.ok(buf);
+ Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+});
diff --git a/netwerk/test/unit/test_ntlm_proxy_and_web_auth.js b/netwerk/test/unit/test_ntlm_proxy_and_web_auth.js
new file mode 100644
index 0000000000..3435839275
--- /dev/null
+++ b/netwerk/test/unit/test_ntlm_proxy_and_web_auth.js
@@ -0,0 +1,362 @@
+// Unit tests for a NTLM authenticated proxy, proxying for a NTLM authenticated
+// web server.
+//
+// Currently the tests do not determine whether the Authentication dialogs have
+// been displayed.
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+function AuthPrompt() {}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+
+ return true;
+ },
+
+ asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor() {}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt) {
+ this.prompt = new AuthPrompt();
+ }
+ return this.prompt;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt: null,
+};
+
+function makeChan(url, loadingUrl) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+function TestListener(resolve) {
+ this.resolve = resolve;
+}
+TestListener.prototype.onStartRequest = function (request, context) {
+ // Need to do the instanceof to allow request.responseStatus
+ // to be read.
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ dump("Expecting an HTTP channel");
+ }
+
+ Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code");
+};
+TestListener.prototype.onStopRequest = function (request, context, status) {
+ Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
+
+ this.resolve();
+};
+TestListener.prototype.onDataAvaiable = function (
+ request,
+ context,
+ stream,
+ offset,
+ count
+) {
+ read_stream(stream, count);
+};
+
+// NTLM Messages, for the received type 1 and 3 messages only check that they
+// are of the expected type.
+const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA";
+const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA";
+const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA";
+const NTLM_PREFIX_LEN = 21;
+
+const NTLM_CHALLENGE =
+ NTLM_TYPE2_PREFIX +
+ "DAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAAR" +
+ "ABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUg" +
+ "BWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwB" +
+ "lAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
+
+const PROXY_CHALLENGE =
+ NTLM_TYPE2_PREFIX +
+ "DgAOADgAAAAFgooCqLNOPe2aZOAAAAAAAAAAAFAAUABGAAAA" +
+ "BgEAAAAAAA9HAFcATAAtAE0ATwBaAAIADgBHAFcATAAtAE0A" +
+ "TwBaAAEADgBHAFcATAAtAE0ATwBaAAQAAgAAAAMAEgBsAG8A" +
+ "YwBhAGwAaABvAHMAdAAHAAgAOKEwGEZL0gEAAAAA";
+
+// Proxy and Web server responses for the happy path scenario.
+// i.e. successful proxy auth and successful web server auth
+//
+function authHandler(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request to the Proxy resppond with a 407 to start auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ // Proxy - Expecting a type 3 Authenticate message from the client
+ // Will respond with a 401 to start web server auth sequence
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "NTLM", false);
+ break;
+ case 3:
+ // Web Server - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
+ break;
+ case 4:
+ // Web Server - Expecting a type 3 Authenticate message from the client
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ break;
+ default:
+ // We should be authenticated and further requests are permitted
+ authorization = metadata.getHeader("Authorization");
+ authorization = metadata.getHeader("Proxy-Authorization");
+ Assert.isnull(authorization);
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ }
+ requestsMade++;
+}
+
+// Proxy responses simulating an invalid proxy password
+// Note: that the connection should not be reused after the
+// proxy auth fails.
+//
+function authHandlerInvalidProxyPassword(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request respond with a 407 to initiate auth sequence
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ // Proxy - Expecting a type 3 Authenticate message from the client
+ // Respond with a 407 to indicate invalid credentials
+ //
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ default:
+ // Strictly speaking the connection should not be reused at this point
+ // and reaching here should be an error, but have commented out for now
+ //dump( "ERROR: NTLM Proxy Authentication, connection should not be reused");
+ //Assert.fail();
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ }
+ requestsMade++;
+}
+
+// Proxy and web server responses simulating a successful Proxy auth
+// and a failed web server auth
+// Note: the connection should not be reused once the password failure is
+// detected
+function authHandlerInvalidWebPassword(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request return a 407 to start Proxy auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", NTLM_CHALLENGE, false);
+ break;
+ case 2:
+ // Proxy - Expecting a type 3 Authenticate message from the client
+ // Responds with a 401 to start web server auth
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "NTLM", false);
+ break;
+ case 3:
+ // Web Server - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
+ break;
+ case 4:
+ // Web Server - Expecting a type 3 Authenticate message from the client
+ // Respond with a 401 to restart the auth sequence.
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ break;
+ default:
+ // We should not get called past step 4
+ Assert.ok(
+ false,
+ "ERROR: NTLM Auth failed connection should not be reused"
+ );
+ }
+ requestsMade++;
+}
+
+// Tests to run test_bad_proxy_pass and test_bad_web_pass are split into two stages
+// so that once we determine how detect password dialog displays we can check
+// that the are displayed correctly, i.e. proxy password should not be prompted
+// for when retrying the web server password
+
+var httpserver = null;
+function setup() {
+ httpserver = new HttpServer();
+ httpserver.start(-1);
+
+ Services.prefs.setCharPref("network.proxy.http", "localhost");
+ Services.prefs.setIntPref(
+ "network.proxy.http_port",
+ httpserver.identity.primaryPort
+ );
+ Services.prefs.setCharPref("network.proxy.no_proxies_on", "");
+ Services.prefs.setIntPref("network.proxy.type", 1);
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.proxy.http");
+ Services.prefs.clearUserPref("network.proxy.http_port");
+ Services.prefs.clearUserPref("network.proxy.no_proxies_on");
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+
+ await httpserver.stop();
+ });
+}
+setup();
+
+var expectedRequests = 0; // Number of HTTP requests that are expected
+var requestsMade = 0; // The number of requests that were made
+var expectedResponse = 0; // The response code
+// Note that any test failures in the HTTP handler
+// will manifest as a 500 response code
+
+// Common test setup
+// Parameters:
+// path - path component of the URL
+// handler - http handler function for the httpserver
+// requests - expected number oh http requests
+// response - expected http response code
+// clearCache - clear the authentication cache before running the test
+function setupTest(path, handler, requests, response, clearCache) {
+ requestsMade = 0;
+ expectedRequests = requests;
+ expectedResponse = response;
+
+ // clear the auth cache if requested
+ if (clearCache) {
+ dump("Clearing auth cache");
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+ }
+
+ return new Promise(resolve => {
+ var chan = makeChan(URL + path, URL);
+ httpserver.registerPathHandler(path, handler);
+ chan.notificationCallbacks = new Requestor();
+ chan.asyncOpen(new TestListener(resolve));
+ });
+}
+
+// Happy code path
+// Succesful proxy and web server auth.
+add_task(async function test_happy_path() {
+ dump("RUNNING TEST: test_happy_path");
+ await setupTest("/auth", authHandler, 5, 200, 1);
+});
+
+// Failed proxy authentication
+add_task(async function test_bad_proxy_pass_stage01() {
+ dump("RUNNING TEST: test_bad_proxy_pass_stage01");
+ await setupTest("/auth", authHandlerInvalidProxyPassword, 4, 407, 1);
+});
+// Successful logon after failed proxy auth
+add_task(async function test_bad_proxy_pass_stage02() {
+ dump("RUNNING TEST: test_bad_proxy_pass_stage02");
+ await setupTest("/auth", authHandler, 5, 200, 0);
+});
+
+// successful proxy logon, unsuccessful web server sign on
+add_task(async function test_bad_web_pass_stage01() {
+ dump("RUNNING TEST: test_bad_web_pass_stage01");
+ await setupTest("/auth", authHandlerInvalidWebPassword, 5, 401, 1);
+});
+// successful logon after failed web server auth.
+add_task(async function test_bad_web_pass_stage02() {
+ dump("RUNNING TEST: test_bad_web_pass_stage02");
+ await setupTest("/auth", authHandler, 5, 200, 0);
+});
diff --git a/netwerk/test/unit/test_ntlm_proxy_auth.js b/netwerk/test/unit/test_ntlm_proxy_auth.js
new file mode 100644
index 0000000000..b6cb27890b
--- /dev/null
+++ b/netwerk/test/unit/test_ntlm_proxy_auth.js
@@ -0,0 +1,408 @@
+// Unit tests for a NTLM authenticated proxy
+//
+// Currently the tests do not determine whether the Authentication dialogs have
+// been displayed.
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+function AuthPrompt() {}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+
+ return true;
+ },
+
+ asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor() {}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt) {
+ this.prompt = new AuthPrompt();
+ }
+ return this.prompt;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt: null,
+};
+
+function makeChan(url, loadingUrl) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+function TestListener(resolve) {
+ this.resolve = resolve;
+}
+TestListener.prototype.onStartRequest = function (request, context) {
+ // Need to do the instanceof to allow request.responseStatus
+ // to be read.
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ dump("Expecting an HTTP channel");
+ }
+
+ Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code");
+};
+TestListener.prototype.onStopRequest = function (request, context, status) {
+ Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
+ Assert.equal(
+ exptTypeOneCount,
+ ntlmTypeOneCount,
+ "Number of type one messages received"
+ );
+ Assert.equal(
+ exptTypeTwoCount,
+ ntlmTypeTwoCount,
+ "Number of type two messages received"
+ );
+
+ this.resolve();
+};
+TestListener.prototype.onDataAvaiable = function (
+ request,
+ context,
+ stream,
+ offset,
+ count
+) {
+ read_stream(stream, count);
+};
+
+// NTLM Messages, for the received type 1 and 3 messages only check that they
+// are of the expected type.
+const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA";
+const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA";
+const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA";
+const NTLM_PREFIX_LEN = 21;
+
+const PROXY_CHALLENGE =
+ NTLM_TYPE2_PREFIX +
+ "DgAOADgAAAAFgooCqLNOPe2aZOAAAAAAAAAAAFAAUABGAAAA" +
+ "BgEAAAAAAA9HAFcATAAtAE0ATwBaAAIADgBHAFcATAAtAE0A" +
+ "TwBaAAEADgBHAFcATAAtAE0ATwBaAAQAAgAAAAMAEgBsAG8A" +
+ "YwBhAGwAaABvAHMAdAAHAAgAOKEwGEZL0gEAAAAA";
+
+// Proxy responses for the happy path scenario.
+// i.e. successful proxy auth
+//
+function successfulAuth(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request to the Proxy resppond with a 407 to start auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ // Proxy - Expecting a type 3 Authenticate message from the client
+ // Will respond with a 401 to start web server auth sequence
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ break;
+ default:
+ // We should be authenticated and further requests are permitted
+ authorization = metadata.getHeader("Proxy-Authorization");
+ Assert.isnull(authorization);
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ }
+ requestsMade++;
+}
+
+// Proxy responses simulating an invalid proxy password
+// Note: that the connection should not be reused after the
+// proxy auth fails.
+//
+function failedAuth(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request respond with a 407 to initiate auth sequence
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ // Proxy - Expecting a type 3 Authenticate message from the client
+ // Respond with a 407 to indicate invalid credentials
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ default:
+ // Strictly speaking the connection should not be reused at this point
+ // commenting out for now.
+ dump("ERROR: NTLM Proxy Authentication, connection should not be reused");
+ // assert.fail();
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ }
+ requestsMade++;
+}
+//
+// Simulate a connection reset once the connection has been authenticated
+// Detects bug 486508
+//
+function connectionReset(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request to the Proxy resppond with a 407 to start auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ ntlmTypeOneCount++;
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ ntlmTypeTwoCount++;
+ try {
+ response.seizePower();
+ response.finish();
+ } catch (e) {
+ Assert.ok(false, "unexpected exception" + e);
+ }
+ break;
+ default:
+ // Should not get any further requests on this channel
+ dump("ERROR: NTLM Proxy Authentication, connection should not be reused");
+ Assert.ok(false);
+ }
+ requestsMade++;
+}
+
+//
+// Reset the connection after a negotiate message has been received
+//
+function connectionReset02(metadata, response) {
+ var connectionNumber = httpserver.connectionNumber;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request to the Proxy respond with a 407 to start auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ Assert.equal(connectionNumber, httpserver.connectionNumber);
+ break;
+ case 1:
+ // eslint-disable-next-line no-fallthrough
+ default:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ Assert.equal(connectionNumber, httpserver.connectionNumber);
+ var authorization = metadata.getHeader("Proxy-Authorization");
+ var authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ ntlmTypeOneCount++;
+ try {
+ response.seizePower();
+ response.finish();
+ } catch (e) {
+ Assert.ok(false, "unexpected exception" + e);
+ }
+ }
+ requestsMade++;
+}
+
+var httpserver = null;
+function setup() {
+ httpserver = new HttpServer();
+ httpserver.start(-1);
+
+ Services.prefs.setCharPref("network.proxy.http", "localhost");
+ Services.prefs.setIntPref(
+ "network.proxy.http_port",
+ httpserver.identity.primaryPort
+ );
+ Services.prefs.setCharPref("network.proxy.no_proxies_on", "");
+ Services.prefs.setIntPref("network.proxy.type", 1);
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.proxy.http");
+ Services.prefs.clearUserPref("network.proxy.http_port");
+ Services.prefs.clearUserPref("network.proxy.no_proxies_on");
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+
+ await httpserver.stop();
+ });
+}
+setup();
+
+var expectedRequests = 0; // Number of HTTP requests that are expected
+var requestsMade = 0; // The number of requests that were made
+var expectedResponse = 0; // The response code
+// Note that any test failures in the HTTP handler
+// will manifest as a 500 response code
+
+// Common test setup
+// Parameters:
+// path - path component of the URL
+// handler - http handler function for the httpserver
+// requests - expected number oh http requests
+// response - expected http response code
+// clearCache - clear the authentication cache before running the test
+function setupTest(path, handler, requests, response, clearCache) {
+ requestsMade = 0;
+ expectedRequests = requests;
+ expectedResponse = response;
+
+ // clear the auth cache if requested
+ if (clearCache) {
+ dump("Clearing auth cache");
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+ }
+
+ return new Promise(resolve => {
+ var chan = makeChan(URL + path, URL);
+ httpserver.registerPathHandler(path, handler);
+ chan.notificationCallbacks = new Requestor();
+ chan.asyncOpen(new TestListener(resolve));
+ });
+}
+
+let ntlmTypeOneCount = 0; // The number of NTLM type one messages received
+let exptTypeOneCount = 0; // The number of NTLM type one messages that should be received
+let ntlmTypeTwoCount = 0; // The number of NTLM type two messages received
+let exptTypeTwoCount = 0; // The number of NTLM type two messages that should received
+
+// Happy code path
+// Successful proxy auth.
+async function test_happy_path() {
+ dump("RUNNING TEST: test_happy_path");
+ await setupTest("/auth", successfulAuth, 3, 200, 1);
+}
+
+// Failed proxy authentication
+async function test_failed_auth() {
+ dump("RUNNING TEST:failed auth ");
+ await setupTest("/auth", failedAuth, 4, 407, 1);
+}
+
+// Test connection reset, after successful auth
+async function test_connection_reset() {
+ dump("RUNNING TEST:connection reset ");
+ ntlmTypeOneCount = 0;
+ ntlmTypeTwoCount = 0;
+ exptTypeOneCount = 1;
+ exptTypeTwoCount = 1;
+ await setupTest("/auth", connectionReset, 3, 500, 1);
+}
+
+// Test connection reset after sending a negotiate.
+// Client should retry request using the same connection
+async function test_connection_reset02() {
+ dump("RUNNING TEST:connection reset ");
+ ntlmTypeOneCount = 0;
+ ntlmTypeTwoCount = 0;
+ let maxRetryAttempt = 5;
+ exptTypeOneCount = maxRetryAttempt;
+ exptTypeTwoCount = 0;
+
+ Services.prefs.setIntPref(
+ "network.http.request.max-attempts",
+ maxRetryAttempt
+ );
+
+ await setupTest("/auth", connectionReset02, maxRetryAttempt + 1, 500, 1);
+}
+
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_happy_path
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_failed_auth
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_connection_reset
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_connection_reset02
+);
+
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_happy_path
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_failed_auth
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_connection_reset
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_connection_reset02
+);
diff --git a/netwerk/test/unit/test_ntlm_web_auth.js b/netwerk/test/unit/test_ntlm_web_auth.js
new file mode 100644
index 0000000000..1325477bc6
--- /dev/null
+++ b/netwerk/test/unit/test_ntlm_web_auth.js
@@ -0,0 +1,251 @@
+// Unit tests for a NTLM authenticated web server.
+//
+// Currently the tests do not determine whether the Authentication dialogs have
+// been displayed.
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+function AuthPrompt() {}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+
+ return true;
+ },
+
+ asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor() {}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt) {
+ this.prompt = new AuthPrompt();
+ }
+ return this.prompt;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt: null,
+};
+
+function makeChan(url, loadingUrl) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+function TestListener() {}
+TestListener.prototype.onStartRequest = function (request, context) {
+ // Need to do the instanceof to allow request.responseStatus
+ // to be read.
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ dump("Expecting an HTTP channel");
+ }
+
+ Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code");
+};
+TestListener.prototype.onStopRequest = function (request, context, status) {
+ Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
+
+ if (current_test < tests.length - 1) {
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpserver.stop(do_test_finished);
+ }
+
+ do_test_finished();
+};
+TestListener.prototype.onDataAvaiable = function (
+ request,
+ context,
+ stream,
+ offset,
+ count
+) {
+ read_stream(stream, count);
+};
+
+// NTLM Messages, for the received type 1 and 3 messages only check that they
+// are of the expected type.
+const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA";
+const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA";
+const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA";
+const NTLM_PREFIX_LEN = 21;
+
+const NTLM_CHALLENGE =
+ NTLM_TYPE2_PREFIX +
+ "DAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAAR" +
+ "ABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUg" +
+ "BWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwB" +
+ "lAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
+
+// Web server responses for the happy path scenario.
+// i.e. successful web server auth
+//
+function successfulAuth(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Web Server - Initial request
+ // Will respond with a 401 to start web server auth sequence
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Web Server - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
+ break;
+ case 2:
+ // Web Server - Expecting a type 3 Authenticate message from the client
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ break;
+ default:
+ // We should be authenticated and further requests are permitted
+ authorization = metadata.getHeader("Authorization");
+ Assert.isnull(authorization);
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ }
+ requestsMade++;
+}
+
+// web server responses simulating an unsuccessful web server auth
+function failedAuth(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Web Server - First request return a 401 to start auth sequence
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Web Server - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
+ break;
+ case 2:
+ // Web Server - Expecting a type 3 Authenticate message from the client
+ // Respond with a 401 to restart the auth sequence.
+ authorization = metadata.getHeader("Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ break;
+ default:
+ // We should not get called past step 2
+ // Strictly speaking the connection should not be used again
+ // commented out for testing
+ // dump( "ERROR: NTLM Auth failed connection should not be reused");
+ //Assert.fail();
+ response.setHeader("WWW-Authenticate", "NTLM", false);
+ }
+ requestsMade++;
+}
+
+var tests = [test_happy_path, test_failed_auth];
+var current_test = 0;
+
+var httpserver = null;
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.start(-1);
+
+ tests[0]();
+}
+
+var expectedRequests = 0; // Number of HTTP requests that are expected
+var requestsMade = 0; // The number of requests that were made
+var expectedResponse = 0; // The response code
+// Note that any test failures in the HTTP handler
+// will manifest as a 500 response code
+
+// Common test setup
+// Parameters:
+// path - path component of the URL
+// handler - http handler function for the httpserver
+// requests - expected number oh http requests
+// response - expected http response code
+// clearCache - clear the authentication cache before running the test
+function setupTest(path, handler, requests, response, clearCache) {
+ requestsMade = 0;
+ expectedRequests = requests;
+ expectedResponse = response;
+
+ // clear the auth cache if requested
+ if (clearCache) {
+ dump("Clearing auth cache");
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+ }
+
+ var chan = makeChan(URL + path, URL);
+ httpserver.registerPathHandler(path, handler);
+ chan.notificationCallbacks = new Requestor();
+ chan.asyncOpen(new TestListener());
+
+ return chan;
+}
+
+// Happy code path
+// Succesful web server auth.
+function test_happy_path() {
+ dump("RUNNING TEST: test_happy_path");
+ setupTest("/auth", successfulAuth, 3, 200, 1);
+
+ do_test_pending();
+}
+
+// Unsuccessful web server sign on
+function test_failed_auth() {
+ dump("RUNNING TEST: test_failed_auth");
+ setupTest("/auth", failedAuth, 3, 401, 1);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_oblivious_http.js b/netwerk/test/unit/test_oblivious_http.js
new file mode 100644
index 0000000000..027426038d
--- /dev/null
+++ b/netwerk/test/unit/test_oblivious_http.js
@@ -0,0 +1,206 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+class ObliviousHttpTestRequest {
+ constructor(method, uri, headers, content) {
+ this.method = method;
+ this.uri = uri;
+ this.headers = headers;
+ this.content = content;
+ }
+}
+
+class ObliviousHttpTestResponse {
+ constructor(status, headers, content) {
+ this.status = status;
+ this.headers = headers;
+ this.content = content;
+ }
+}
+
+class ObliviousHttpTestCase {
+ constructor(request, response) {
+ this.request = request;
+ this.response = response;
+ }
+}
+
+add_task(async function test_oblivious_http() {
+ let testcases = [
+ new ObliviousHttpTestCase(
+ new ObliviousHttpTestRequest(
+ "GET",
+ NetUtil.newURI("https://example.com"),
+ { "X-Some-Header": "header value" },
+ ""
+ ),
+ new ObliviousHttpTestResponse(200, {}, "Hello, World!")
+ ),
+ new ObliviousHttpTestCase(
+ new ObliviousHttpTestRequest(
+ "POST",
+ NetUtil.newURI("http://example.test"),
+ { "X-Some-Header": "header value", "X-Some-Other-Header": "25" },
+ "Posting some content..."
+ ),
+ new ObliviousHttpTestResponse(
+ 418,
+ { "X-Teapot": "teapot" },
+ "I'm a teapot"
+ )
+ ),
+ new ObliviousHttpTestCase(
+ new ObliviousHttpTestRequest(
+ "GET",
+ NetUtil.newURI("http://example.test/404"),
+ { "X-Some-Header": "header value", "X-Some-Other-Header": "25" },
+ ""
+ ),
+ undefined // 404 relay
+ ),
+ ];
+
+ for (let testcase of testcases) {
+ await run_one_testcase(testcase);
+ }
+});
+
+async function run_one_testcase(testcase) {
+ let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService(
+ Ci.nsIObliviousHttp
+ );
+ let ohttpServer = ohttp.server();
+
+ let httpServer = new HttpServer();
+ httpServer.registerPathHandler("/", function (request, response) {
+ let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ inputStream.init(request.bodyInputStream);
+ let requestBody = inputStream.readBytes(inputStream.available());
+ let ohttpResponse = ohttpServer.decapsulate(stringToBytes(requestBody));
+ let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
+ Ci.nsIBinaryHttp
+ );
+ let decodedRequest = bhttp.decodeRequest(ohttpResponse.request);
+ equal(decodedRequest.method, testcase.request.method);
+ equal(decodedRequest.scheme, testcase.request.uri.scheme);
+ equal(decodedRequest.authority, testcase.request.uri.hostPort);
+ equal(decodedRequest.path, testcase.request.uri.pathQueryRef);
+ for (
+ let i = 0;
+ i < decodedRequest.headerNames.length &&
+ i < decodedRequest.headerValues.length;
+ i++
+ ) {
+ equal(
+ decodedRequest.headerValues[i],
+ testcase.request.headers[decodedRequest.headerNames[i]]
+ );
+ }
+ equal(bytesToString(decodedRequest.content), testcase.request.content);
+
+ let responseHeaderNames = ["content-type"];
+ let responseHeaderValues = ["text/plain"];
+ for (let headerName of Object.keys(testcase.response.headers)) {
+ responseHeaderNames.push(headerName);
+ responseHeaderValues.push(testcase.response.headers[headerName]);
+ }
+ let binaryResponse = new BinaryHttpResponse(
+ testcase.response.status,
+ responseHeaderNames,
+ responseHeaderValues,
+ stringToBytes(testcase.response.content)
+ );
+ let responseBytes = bhttp.encodeResponse(binaryResponse);
+ let encResponse = ohttpResponse.encapsulate(responseBytes);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "message/ohttp-res", false);
+ response.write(bytesToString(encResponse));
+ });
+ httpServer.start(-1);
+
+ let ohttpService = Cc[
+ "@mozilla.org/network/oblivious-http-service;1"
+ ].getService(Ci.nsIObliviousHttpService);
+ let relayURI = NetUtil.newURI(
+ `http://localhost:${httpServer.identity.primaryPort}/`
+ );
+ if (!testcase.response) {
+ relayURI = NetUtil.newURI(
+ `http://localhost:${httpServer.identity.primaryPort}/404`
+ );
+ }
+ let obliviousHttpChannel = ohttpService
+ .newChannel(relayURI, testcase.request.uri, ohttpServer.encodedConfig)
+ .QueryInterface(Ci.nsIHttpChannel);
+ for (let headerName of Object.keys(testcase.request.headers)) {
+ obliviousHttpChannel.setRequestHeader(
+ headerName,
+ testcase.request.headers[headerName],
+ false
+ );
+ }
+ if (testcase.request.method == "POST") {
+ let uploadChannel = obliviousHttpChannel.QueryInterface(
+ Ci.nsIUploadChannel2
+ );
+ ok(uploadChannel);
+ let bodyStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ bodyStream.setData(
+ testcase.request.content,
+ testcase.request.content.length
+ );
+ uploadChannel.explicitSetUploadStream(
+ bodyStream,
+ null,
+ -1,
+ testcase.request.method,
+ false
+ );
+ }
+ let response = await new Promise((resolve, reject) => {
+ NetUtil.asyncFetch(obliviousHttpChannel, function (inputStream, result) {
+ let scriptableInputStream = Cc[
+ "@mozilla.org/scriptableinputstream;1"
+ ].createInstance(Ci.nsIScriptableInputStream);
+ scriptableInputStream.init(inputStream);
+ try {
+ // If decoding failed just return undefined.
+ inputStream.available();
+ } catch (e) {
+ resolve(undefined);
+ return;
+ }
+ let responseBody = scriptableInputStream.readBytes(
+ inputStream.available()
+ );
+ resolve(responseBody);
+ });
+ });
+ if (testcase.response) {
+ equal(response, testcase.response.content);
+ for (let headerName of Object.keys(testcase.response.headers)) {
+ equal(
+ obliviousHttpChannel.getResponseHeader(headerName),
+ testcase.response.headers[headerName]
+ );
+ }
+ } else {
+ let relayChannel = obliviousHttpChannel.QueryInterface(
+ Ci.nsIObliviousHttpChannel
+ ).relayChannel;
+ equal(relayChannel.responseStatus, 404);
+ }
+ await new Promise((resolve, reject) => {
+ httpServer.stop(resolve);
+ });
+}
diff --git a/netwerk/test/unit/test_obs-fold.js b/netwerk/test/unit/test_obs-fold.js
new file mode 100644
index 0000000000..fbdc2a4c9d
--- /dev/null
+++ b/netwerk/test/unit/test_obs-fold.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+let body = "abcd";
+function request_handler1(metadata, response) {
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("X-header-first: FIRSTVALUE\r\n");
+ response.write("X-header-second: 1; second\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+// This handler is for obs-fold
+// The line that contains X-header-second starts with a space. As a consequence
+// it gets folded into the previous line.
+function request_handler2(metadata, response) {
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("X-header-first: FIRSTVALUE\r\n");
+ // Note the space at the begining of the line
+ response.write(" X-header-second: 1; second\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+add_task(async function test() {
+ let http_server = new HttpServer();
+ http_server.registerPathHandler("/test1", request_handler1);
+ http_server.registerPathHandler("/test2", request_handler2);
+ http_server.start(-1);
+ const port = http_server.identity.primaryPort;
+
+ let chan1 = makeChan(`http://localhost:${port}/test1`);
+ await new Promise(resolve => {
+ chan1.asyncOpen(new ChannelListener(resolve));
+ });
+ equal(chan1.getResponseHeader("X-header-first"), "FIRSTVALUE");
+ equal(chan1.getResponseHeader("X-header-second"), "1; second");
+
+ let chan2 = makeChan(`http://localhost:${port}/test2`);
+ await new Promise(resolve => {
+ chan2.asyncOpen(new ChannelListener(resolve));
+ });
+ equal(
+ chan2.getResponseHeader("X-header-first"),
+ "FIRSTVALUE X-header-second: 1; second"
+ );
+ Assert.throws(
+ () => chan2.getResponseHeader("X-header-second"),
+ /NS_ERROR_NOT_AVAILABLE/
+ );
+
+ await new Promise(resolve => http_server.stop(resolve));
+});
diff --git a/netwerk/test/unit/test_offline_status.js b/netwerk/test/unit/test_offline_status.js
new file mode 100644
index 0000000000..22fde0bd20
--- /dev/null
+++ b/netwerk/test/unit/test_offline_status.js
@@ -0,0 +1,15 @@
+"use strict";
+
+function run_test() {
+ try {
+ var linkService = Cc[
+ "@mozilla.org/network/network-link-service;1"
+ ].getService(Ci.nsINetworkLinkService);
+
+ // The offline status should depends on the link status
+ Assert.notEqual(Services.io.offline, linkService.isLinkUp);
+ } catch (e) {
+ // The network link service might not be available
+ Assert.equal(Services.io.offline, false);
+ }
+}
diff --git a/netwerk/test/unit/test_ohttp.js b/netwerk/test/unit/test_ohttp.js
new file mode 100644
index 0000000000..b7bd2f1d06
--- /dev/null
+++ b/netwerk/test/unit/test_ohttp.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test_known_config() {
+ let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService(
+ Ci.nsIObliviousHttp
+ );
+ let encodedConfig = hexStringToBytes(
+ "0100209403aafe76dfd4568481e04e44b42d744287eae4070b50e48baa7a91a4e80d5600080001000100010003"
+ );
+ let request = hexStringToBytes(
+ "00034745540568747470730b6578616d706c652e636f6d012f"
+ );
+ let ohttpRequest = ohttp.encapsulateRequest(encodedConfig, request);
+ ok(ohttpRequest);
+}
+
+function test_with_server() {
+ let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService(
+ Ci.nsIObliviousHttp
+ );
+ let server = ohttp.server();
+ ok(server.encodedConfig);
+ let request = hexStringToBytes(
+ "00034745540568747470730b6578616d706c652e636f6d012f"
+ );
+ let ohttpRequest = ohttp.encapsulateRequest(server.encodedConfig, request);
+ let ohttpResponse = server.decapsulate(ohttpRequest.encRequest);
+ ok(ohttpResponse);
+ deepEqual(ohttpResponse.request, request);
+ let response = hexStringToBytes("0140c8");
+ let encResponse = ohttpResponse.encapsulate(response);
+ deepEqual(ohttpRequest.response.decapsulate(encResponse), response);
+}
+
+function run_test() {
+ test_known_config();
+ test_with_server();
+}
diff --git a/netwerk/test/unit/test_orb_empty_header.js b/netwerk/test/unit/test_orb_empty_header.js
new file mode 100644
index 0000000000..24866b7073
--- /dev/null
+++ b/netwerk/test/unit/test_orb_empty_header.js
@@ -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/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+function makeChan(uri) {
+ var principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.com"
+ );
+ let chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+async function setup() {
+ if (!inChildProcess()) {
+ Services.prefs.setBoolPref("browser.opaqueResponseBlocking", true);
+ }
+ let server = new NodeHTTPServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ await server.registerPathHandler("/dosniff", (req, resp) => {
+ resp.writeHead(500, {
+ "Content-Type": "application/json",
+ "Set-Cookie": "mycookie",
+ });
+ resp.write("good");
+ resp.end("done");
+ });
+ await server.registerPathHandler("/nosniff", (req, resp) => {
+ resp.writeHead(500, {
+ "Content-Type": "application/msword",
+ "Set-Cookie": "mycookie",
+ });
+ resp.write("good");
+ resp.end("done");
+ });
+
+ return server;
+}
+async function test_empty_header(server, doSniff) {
+ let chan;
+ if (doSniff) {
+ chan = makeChan(`${server.origin()}/dosniff`);
+ } else {
+ chan = makeChan(`${server.origin()}/nosniff`);
+ }
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ });
+ equal(req.status, Cr.NS_ERROR_FAILURE);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 500);
+
+ req.visitResponseHeaders({
+ visitHeader: function visit(_aName, _aValue) {
+ ok(false);
+ },
+ });
+}
+
+add_task(async function () {
+ let server = await setup();
+ await test_empty_header(server, true);
+ await test_empty_header(server, false);
+});
diff --git a/netwerk/test/unit/test_origin.js b/netwerk/test/unit/test_origin.js
new file mode 100644
index 0000000000..9ef86e8885
--- /dev/null
+++ b/netwerk/test/unit/test_origin.js
@@ -0,0 +1,323 @@
+"use strict";
+
+var h2Port;
+var prefs;
+var http2pref;
+var extpref;
+var loadGroup;
+
+function run_test() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Services.prefs;
+
+ http2pref = prefs.getBoolPref("network.http.http2.enabled");
+ extpref = prefs.getBoolPref("network.http.originextension");
+
+ prefs.setBoolPref("network.http.http2.enabled", true);
+ prefs.setBoolPref("network.http.originextension", true);
+ prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, alt1.example.com"
+ );
+
+ // The moz-http2 cert is for {foo, alt1, alt2}.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ doTest1();
+}
+
+function resetPrefs() {
+ prefs.setBoolPref("network.http.http2.enabled", http2pref);
+ prefs.setBoolPref("network.http.originextension", extpref);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function makeChan(origin) {
+ return NetUtil.newChannel({
+ uri: origin,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+let origin;
+var nextTest;
+var nextPortExpectedToBeSame = false;
+var currentPort = 0;
+var forceReload = false;
+var forceFailListener = false;
+
+var Listener = function () {};
+Listener.prototype.clientPort = 0;
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+ Assert.equal(request.responseStatus, 200);
+ this.clientPort = parseInt(request.getResponseHeader("x-client-port"));
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(Components.isSuccessCode(status));
+ if (nextPortExpectedToBeSame) {
+ Assert.equal(currentPort, this.clientPort);
+ } else {
+ Assert.notEqual(currentPort, this.clientPort);
+ }
+ currentPort = this.clientPort;
+ nextTest();
+ do_test_finished();
+ },
+};
+
+var FailListener = function () {};
+FailListener.prototype = {
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.ok(!Components.isSuccessCode(request.status));
+ },
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(!Components.isSuccessCode(request.status));
+ nextTest();
+ do_test_finished();
+ },
+};
+
+function testsDone() {
+ dump("testsDone\n");
+ resetPrefs();
+}
+
+function doTest() {
+ dump("execute doTest " + origin + "\n");
+ var chan = makeChan(origin);
+ var listener;
+ if (!forceFailListener) {
+ listener = new Listener();
+ } else {
+ listener = new FailListener();
+ }
+ forceFailListener = false;
+
+ if (!forceReload) {
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ } else {
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ }
+ forceReload = false;
+ chan.asyncOpen(listener);
+}
+
+function doTest1() {
+ dump("doTest1()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-1";
+ nextTest = doTest2;
+ nextPortExpectedToBeSame = false;
+ do_test_pending();
+ doTest();
+}
+
+function doTest2() {
+ // plain connection reuse
+ dump("doTest2()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-2";
+ nextTest = doTest3;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest3() {
+ // 7540 style coalescing
+ dump("doTest3()\n");
+ origin = "https://alt1.example.com:" + h2Port + "/origin-3";
+ nextTest = doTest4;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest4() {
+ // forces an empty origin frame to be omitted
+ dump("doTest4()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-4";
+ nextTest = doTest5;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest5() {
+ // 7540 style coalescing should not work due to empty origin set
+ dump("doTest5()\n");
+ origin = "https://alt1.example.com:" + h2Port + "/origin-5";
+ nextTest = doTest6;
+ nextPortExpectedToBeSame = false;
+ do_test_pending();
+ doTest();
+}
+
+function doTest6() {
+ // get a fresh connection with alt1 and alt2 in origin set
+ // note that there is no dns for alt2
+ dump("doTest6()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-6";
+ nextTest = doTest7;
+ nextPortExpectedToBeSame = false;
+ forceReload = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest7() {
+ // check conn reuse to ensure sni is implicit in origin set
+ dump("doTest7()\n");
+ origin = "https://foo.example.com:" + h2Port + "/origin-7";
+ nextTest = doTest8;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest8() {
+ // alt1 is in origin set (and is 7540 eligible)
+ dump("doTest8()\n");
+ origin = "https://alt1.example.com:" + h2Port + "/origin-8";
+ nextTest = doTest9;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest9() {
+ // alt2 is in origin set but does not have dns
+ dump("doTest9()\n");
+ origin = "https://alt2.example.com:" + h2Port + "/origin-9";
+ nextTest = doTest10;
+ nextPortExpectedToBeSame = true;
+ do_test_pending();
+ doTest();
+}
+
+function doTest10() {
+ // bar is in origin set but does not have dns like alt2
+ // but the cert is not valid for bar. so expect a failure
+ dump("doTest10()\n");
+ origin = "https://bar.example.com:" + h2Port + "/origin-10";
+ nextTest = doTest11;
+ nextPortExpectedToBeSame = false;
+ forceFailListener = true;
+ do_test_pending();
+ doTest();
+}
+
+var Http2PushApiListener = function () {};
+
+Http2PushApiListener.prototype = {
+ fooOK: false,
+ alt1OK: false,
+
+ getInterface(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIHttpPushListener",
+ "nsIStreamListener",
+ ]),
+
+ // nsIHttpPushListener
+ onPush: function onPush(associatedChannel, pushChannel) {
+ dump(
+ "push api onpush " +
+ pushChannel.originalURI.spec +
+ " associated to " +
+ associatedChannel.originalURI.spec +
+ "\n"
+ );
+
+ Assert.equal(
+ associatedChannel.originalURI.spec,
+ "https://foo.example.com:" + h2Port + "/origin-11-a"
+ );
+ Assert.equal(pushChannel.getRequestHeader("x-pushed-request"), "true");
+
+ if (
+ pushChannel.originalURI.spec ===
+ "https://foo.example.com:" + h2Port + "/origin-11-b"
+ ) {
+ this.fooOK = true;
+ } else if (
+ pushChannel.originalURI.spec ===
+ "https://alt1.example.com:" + h2Port + "/origin-11-e"
+ ) {
+ this.alt1OK = true;
+ } else {
+ // any push of bar or madeup should not end up in onPush()
+ Assert.equal(true, false);
+ }
+ pushChannel.cancel(Cr.NS_ERROR_ABORT);
+ },
+
+ // normal Channel listeners
+ onStartRequest: function pushAPIOnStart(request) {
+ dump("push api onstart " + request.originalURI.spec + "\n");
+ },
+
+ onDataAvailable: function pushAPIOnDataAvailable(
+ request,
+ stream,
+ offset,
+ cnt
+ ) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ dump("push api onstop " + request.originalURI.spec + "\n");
+ Assert.ok(this.fooOK);
+ Assert.ok(this.alt1OK);
+ nextTest();
+ do_test_finished();
+ },
+};
+
+function doTest11() {
+ // we are connected with an SNI of foo from test6
+ // but the origin set is alt1, alt2, bar - foo is implied
+ // and bar is not actually covered by the cert
+ //
+ // the server will push foo (b-OK), bar (c-NOT OK), madeup (d-NOT OK), alt1 (e-OK),
+
+ dump("doTest11()\n");
+ do_test_pending();
+ loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
+ Ci.nsILoadGroup
+ );
+ var chan = makeChan("https://foo.example.com:" + h2Port + "/origin-11-a");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushApiListener();
+ nextTest = testsDone;
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener);
+}
diff --git a/netwerk/test/unit/test_original_sent_received_head.js b/netwerk/test/unit/test_original_sent_received_head.js
new file mode 100644
index 0000000000..744528b832
--- /dev/null
+++ b/netwerk/test/unit/test_original_sent_received_head.js
@@ -0,0 +1,249 @@
+//
+// HTTP headers test
+// Response headers can be changed after they have been received, e.g. empty
+// headers are deleted, some duplicate header are merged (if no error is
+// thrown), etc.
+//
+// The "original header" is introduced to hold the header array in the order
+// and the form as they have been received from the network.
+// Here, the "original headers" are tested.
+//
+// Original headers will be stored in the cache as well. This test checks
+// that too.
+
+// Note: sets Cc and Ci variables
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+var dbg = 1;
+
+function run_test() {
+ if (dbg) {
+ print("============== START ==========");
+ }
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ run_next_test();
+}
+
+add_test(function test_headerChange() {
+ if (dbg) {
+ print("============== test_headerChange setup: in");
+ }
+
+ var channel1 = setupChannel(testpath);
+ channel1.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+
+ // ChannelListener defined in head_channels.js
+ channel1.asyncOpen(new ChannelListener(checkResponse, null));
+
+ if (dbg) {
+ print("============== test_headerChange setup: out");
+ }
+});
+
+add_test(function test_fromCache() {
+ if (dbg) {
+ print("============== test_fromCache setup: in");
+ }
+
+ var channel2 = setupChannel(testpath);
+ channel2.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE;
+
+ // ChannelListener defined in head_channels.js
+ channel2.asyncOpen(new ChannelListener(checkResponse, null));
+
+ if (dbg) {
+ print("============== test_fromCache setup: out");
+ }
+});
+
+add_test(function finish() {
+ if (dbg) {
+ print("============== STOP ==========");
+ }
+ httpserver.stop(do_test_finished);
+});
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ if (dbg) {
+ print("============== serverHandler: in");
+ }
+
+ let etag;
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+ if (etag == "testtag") {
+ if (dbg) {
+ print("============== 304 answerr: in");
+ }
+ response.setStatusLine("1.1", 304, "Not Modified");
+ } else {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setStatusLine("1.1", 200, "OK");
+
+ // Set a empty header. A empty link header will not appear in header list,
+ // but in the "original headers", it will be still exactly as received.
+ response.setHeaderNoCheck("Link", "", true);
+ response.setHeaderNoCheck("Link", "value1");
+ response.setHeaderNoCheck("Link", "value2");
+ response.setHeaderNoCheck("Location", "loc");
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setHeader("ETag", "testtag", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ }
+ if (dbg) {
+ print("============== serverHandler: out");
+ }
+}
+
+function checkResponse(request, data, context) {
+ if (dbg) {
+ print("============== checkResponse: in");
+ }
+
+ request.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(request.responseStatus, 200);
+ Assert.equal(request.responseStatusText, "OK");
+ Assert.ok(request.requestSucceeded);
+
+ // Response header have only one link header.
+ var linkHeaderFound = 0;
+ var locationHeaderFound = 0;
+ request.visitResponseHeaders({
+ visitHeader: function visit(aName, aValue) {
+ if (aName == "link") {
+ linkHeaderFound++;
+ Assert.equal(aValue, "value1, value2");
+ }
+ if (aName == "location") {
+ locationHeaderFound++;
+ Assert.equal(aValue, "loc");
+ }
+ },
+ });
+ Assert.equal(linkHeaderFound, 1);
+ Assert.equal(locationHeaderFound, 1);
+
+ // The "original header" still contains 3 link headers.
+ var linkOrgHeaderFound = 0;
+ var locationOrgHeaderFound = 0;
+ request.visitOriginalResponseHeaders({
+ visitHeader: function visitOrg(aName, aValue) {
+ if (aName == "link") {
+ if (linkOrgHeaderFound == 0) {
+ Assert.equal(aValue, "");
+ } else if (linkOrgHeaderFound == 1) {
+ Assert.equal(aValue, "value1");
+ } else {
+ Assert.equal(aValue, "value2");
+ }
+ linkOrgHeaderFound++;
+ }
+ if (aName == "location") {
+ locationOrgHeaderFound++;
+ Assert.equal(aValue, "loc");
+ }
+ },
+ });
+ Assert.equal(linkOrgHeaderFound, 3);
+ Assert.equal(locationOrgHeaderFound, 1);
+
+ if (dbg) {
+ print("============== Remove headers");
+ }
+ // Remove header.
+ request.setResponseHeader("Link", "", false);
+ request.setResponseHeader("Location", "", false);
+
+ var linkHeaderFound2 = false;
+ var locationHeaderFound2 = 0;
+ request.visitResponseHeaders({
+ visitHeader: function visit(aName, aValue) {
+ if (aName == "Link") {
+ linkHeaderFound2 = true;
+ }
+ if (aName == "Location") {
+ locationHeaderFound2 = true;
+ }
+ },
+ });
+ Assert.ok(!linkHeaderFound2, "There should be no link header");
+ Assert.ok(!locationHeaderFound2, "There should be no location headers.");
+
+ // The "original header" still contains the empty header.
+ var linkOrgHeaderFound2 = 0;
+ var locationOrgHeaderFound2 = 0;
+ request.visitOriginalResponseHeaders({
+ visitHeader: function visitOrg(aName, aValue) {
+ if (aName == "link") {
+ if (linkOrgHeaderFound2 == 0) {
+ Assert.equal(aValue, "");
+ } else if (linkOrgHeaderFound2 == 1) {
+ Assert.equal(aValue, "value1");
+ } else {
+ Assert.equal(aValue, "value2");
+ }
+ linkOrgHeaderFound2++;
+ }
+ if (aName == "location") {
+ locationOrgHeaderFound2++;
+ Assert.equal(aValue, "loc");
+ }
+ },
+ });
+ Assert.ok(linkOrgHeaderFound2 == 3, "Original link header still here.");
+ Assert.ok(
+ locationOrgHeaderFound2 == 1,
+ "Original location header still here."
+ );
+
+ if (dbg) {
+ print("============== Test GetResponseHeader");
+ }
+ var linkOrgHeaderFound3 = 0;
+ request.getOriginalResponseHeader("link", {
+ visitHeader: function visitOrg(aName, aValue) {
+ if (linkOrgHeaderFound3 == 0) {
+ Assert.equal(aValue, "");
+ } else if (linkOrgHeaderFound3 == 1) {
+ Assert.equal(aValue, "value1");
+ } else {
+ Assert.equal(aValue, "value2");
+ }
+ linkOrgHeaderFound3++;
+ },
+ });
+ Assert.ok(linkOrgHeaderFound2 == 3, "Original link header still here.");
+
+ if (dbg) {
+ print("============== checkResponse: out");
+ }
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_pac_reload_after_network_change.js b/netwerk/test/unit/test_pac_reload_after_network_change.js
new file mode 100644
index 0000000000..1b236473ab
--- /dev/null
+++ b/netwerk/test/unit/test_pac_reload_after_network_change.js
@@ -0,0 +1,75 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gProxyService",
+ "@mozilla.org/network/protocol-proxy-service;1",
+ "nsIProtocolProxyService"
+);
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let pacServer;
+const proxyPort = 4433;
+
+add_setup(async function () {
+ pacServer = new HttpServer();
+ pacServer.registerPathHandler(
+ "/proxy.pac",
+ function handler(metadata, response) {
+ let content = `function FindProxyForURL(url, host) { return "HTTPS localhost:${proxyPort}"; }`;
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ }
+ );
+ pacServer.start(-1);
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.autoconfig_url");
+ Services.prefs.clearUserPref("network.proxy.reload_pac_delay");
+});
+
+async function getProxyInfo() {
+ return new Promise((resolve, reject) => {
+ let uri = Services.io.newURI("http://www.mozilla.org/");
+ gProxyService.asyncResolve(uri, 0, {
+ onProxyAvailable(_req, _uri, pi, _status) {
+ resolve(pi);
+ },
+ });
+ });
+}
+
+// Test if we can successfully get PAC when the PAC server is available.
+add_task(async function testPAC() {
+ // Configure PAC
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ Services.prefs.setCharPref(
+ "network.proxy.autoconfig_url",
+ `http://localhost:${pacServer.identity.primaryPort}/proxy.pac`
+ );
+
+ let pi = await getProxyInfo();
+ Assert.equal(pi.port, proxyPort, "Expected proxy port to be the same");
+ Assert.equal(pi.type, "https", "Expected proxy type to be https");
+});
+
+// When PAC server is down, we should not use proxy at all.
+add_task(async function testWhenPACServerDown() {
+ Services.prefs.setIntPref("network.proxy.reload_pac_delay", 0);
+ await new Promise(resolve => pacServer.stop(resolve));
+
+ Services.obs.notifyObservers(null, "network:link-status-changed", "changed");
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 3000));
+
+ let pi = await getProxyInfo();
+ Assert.equal(pi, null, "should have no proxy");
+});
diff --git a/netwerk/test/unit/test_parse_content_type.js b/netwerk/test/unit/test_parse_content_type.js
new file mode 100644
index 0000000000..20b76d558f
--- /dev/null
+++ b/netwerk/test/unit/test_parse_content_type.js
@@ -0,0 +1,365 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var charset = {};
+var hadCharset = {};
+var type;
+
+function reset() {
+ delete charset.value;
+ delete hadCharset.value;
+ type = undefined;
+}
+
+function check(aType, aCharset, aHadCharset) {
+ Assert.equal(type, aType);
+ Assert.equal(aCharset, charset.value);
+ Assert.equal(aHadCharset, hadCharset.value);
+ reset();
+}
+
+add_task(function test_parseResponseContentType() {
+ var netutil = Services.io;
+
+ type = netutil.parseRequestContentType("text/html", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseResponseContentType("text/html", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType("TEXT/HTML", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseResponseContentType("TEXT/HTML", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType(
+ "text/html, text/html",
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/html, text/html",
+ charset,
+ hadCharset
+ );
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType(
+ "text/html, text/plain",
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/html, text/plain",
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType("text/html, ", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/html, ", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType("text/html, */*", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/html, */*",
+ charset,
+ hadCharset
+ );
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType("text/html, foo", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/html, foo",
+ charset,
+ hadCharset
+ );
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType(
+ "text/html; charset=ISO-8859-1",
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseResponseContentType(
+ "text/html; charset=ISO-8859-1",
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType(
+ 'text/html; charset="ISO-8859-1"',
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseResponseContentType(
+ 'text/html; charset="ISO-8859-1"',
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType(
+ "text/html; charset='ISO-8859-1'",
+ charset,
+ hadCharset
+ );
+ check("text/html", "'ISO-8859-1'", true);
+
+ type = netutil.parseResponseContentType(
+ "text/html; charset='ISO-8859-1'",
+ charset,
+ hadCharset
+ );
+ check("text/html", "'ISO-8859-1'", true);
+
+ type = netutil.parseRequestContentType(
+ 'text/html; charset="ISO-8859-1", text/html',
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/html; charset="ISO-8859-1", text/html',
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType(
+ 'text/html; charset="ISO-8859-1", text/html; charset=UTF8',
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/html; charset="ISO-8859-1", text/html; charset=UTF8',
+ charset,
+ hadCharset
+ );
+ check("text/html", "UTF8", true);
+
+ type = netutil.parseRequestContentType(
+ "text/html; charset=ISO-8859-1, TEXT/HTML",
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/html; charset=ISO-8859-1, TEXT/HTML",
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType(
+ "text/html; charset=ISO-8859-1, TEXT/plain",
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/html; charset=ISO-8859-1, TEXT/plain",
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", true);
+
+ type = netutil.parseRequestContentType(
+ "text/plain, TEXT/HTML; charset=ISO-8859-1, text/html, TEXT/HTML",
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/plain, TEXT/HTML; charset=ISO-8859-1, text/html, TEXT/HTML",
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML',
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML',
+ charset,
+ hadCharset
+ );
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML',
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseRequestContentType(
+ "text/plain; param= , text/html",
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/plain; param= , text/html",
+ charset,
+ hadCharset
+ );
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain; param=", text/html"',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/plain; param=", text/html"',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain; param=", \\" , text/html"',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/plain; param=", \\" , text/html"',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain; param=", \\" , text/html , "',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/plain; param=", \\" , text/html , "',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain param=", \\" , text/html , "',
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/plain param=", \\" , text/html , "',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType(
+ "text/plain charset=UTF8",
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ "text/plain charset=UTF8",
+ charset,
+ hadCharset
+ );
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType(
+ 'text/plain, TEXT/HTML; param="charset=UTF8"; ; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset,
+ hadCharset
+ );
+ check("", "", false);
+
+ type = netutil.parseResponseContentType(
+ 'text/plain, TEXT/HTML; param="charset=UTF8"; ; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset,
+ hadCharset
+ );
+ check("text/html", "", false);
+
+ // Bug 562915 - correctness: "\x" is "x"
+ type = netutil.parseResponseContentType(
+ 'text/plain; charset="UTF\\-8"',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "UTF-8", true);
+
+ // Bug 700589
+
+ // check that single quote doesn't confuse parsing of subsequent parameters
+ type = netutil.parseResponseContentType(
+ 'text/plain; x=\'; charset="UTF-8"',
+ charset,
+ hadCharset
+ );
+ check("text/plain", "UTF-8", true);
+
+ // check that single quotes do not get removed from extracted charset
+ type = netutil.parseResponseContentType(
+ "text/plain; charset='UTF-8'",
+ charset,
+ hadCharset
+ );
+ check("text/plain", "'UTF-8'", true);
+});
diff --git a/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js b/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js
new file mode 100644
index 0000000000..0dd5482708
--- /dev/null
+++ b/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js
@@ -0,0 +1,106 @@
+/*
+
+ This is only a crash test. We load a partial content, cache it. Then we change the limit
+ for single cache entry size (shrink it) so that the next request for the rest of the content
+ will hit that limit and doom/remove the entry. We change the size manually, but in reality
+ it's being changed by cache smart size.
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+// Have 2kb response (8 * 2 ^ 8)
+var responseBody = "response";
+for (var i = 0; i < 8; ++i) {
+ responseBody += responseBody;
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "range");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+
+ if (!metadata.hasHeader("If-Range")) {
+ response.setHeader("Content-Length", responseBody.length + "");
+ response.processAsync();
+ let slice = responseBody.slice(0, 100);
+ response.bodyOutputStream.write(slice, slice.length);
+ response.finish();
+ } else {
+ let slice = responseBody.slice(100);
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader(
+ "Content-Range",
+ (responseBody.length - slice.length).toString() +
+ "-" +
+ (responseBody.length - 1).toString() +
+ "/" +
+ responseBody.length.toString()
+ );
+
+ response.setHeader("Content-Length", slice.length + "");
+ response.bodyOutputStream.write(slice, slice.length);
+ }
+}
+
+let enforceSoftPref;
+let enforceStrictChunkedPref;
+
+function run_test() {
+ enforceSoftPref = Services.prefs.getBoolPref(
+ "network.http.enforce-framing.soft"
+ );
+ Services.prefs.setBoolPref("network.http.enforce-framing.soft", false);
+
+ enforceStrictChunkedPref = Services.prefs.getBoolPref(
+ "network.http.enforce-framing.strict_chunked_encoding"
+ );
+ Services.prefs.setBoolPref(
+ "network.http.enforce-framing.strict_chunked_encoding",
+ false
+ );
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(URL + "/content");
+ chan.asyncOpen(new ChannelListener(firstTimeThrough, null, CL_IGNORE_CL));
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ // Change single cache entry limit to 1 kb. This emulates smart size change.
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ var chan = make_channel(URL + "/content");
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ Services.prefs.setBoolPref(
+ "network.http.enforce-framing.soft",
+ enforceSoftPref
+ );
+ Services.prefs.setBoolPref(
+ "network.http.enforce-framing.strict_chunked_encoding",
+ enforceStrictChunkedPref
+ );
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_permmgr.js b/netwerk/test/unit/test_permmgr.js
new file mode 100644
index 0000000000..5d10429d58
--- /dev/null
+++ b/netwerk/test/unit/test_permmgr.js
@@ -0,0 +1,125 @@
+// tests nsIPermissionManager
+
+"use strict";
+
+var hosts = [
+ // format: [host, type, permission]
+ ["http://mozilla.org", "cookie", 1],
+ ["http://mozilla.org", "image", 2],
+ ["http://mozilla.org", "popup", 3],
+ ["http://mozilla.com", "cookie", 1],
+ ["http://www.mozilla.com", "cookie", 2],
+ ["http://dev.mozilla.com", "cookie", 3],
+];
+
+var results = [
+ // format: [host, type, testPermission result, testExactPermission result]
+ // test defaults
+ ["http://localhost", "cookie", 0, 0],
+ ["http://spreadfirefox.com", "cookie", 0, 0],
+ // test different types
+ ["http://mozilla.org", "cookie", 1, 1],
+ ["http://mozilla.org", "image", 2, 2],
+ ["http://mozilla.org", "popup", 3, 3],
+ // test subdomains
+ ["http://www.mozilla.org", "cookie", 1, 0],
+ ["http://www.dev.mozilla.org", "cookie", 1, 0],
+ // test different permissions on subdomains
+ ["http://mozilla.com", "cookie", 1, 1],
+ ["http://www.mozilla.com", "cookie", 2, 2],
+ ["http://dev.mozilla.com", "cookie", 3, 3],
+ ["http://www.dev.mozilla.com", "cookie", 3, 0],
+];
+
+function run_test() {
+ Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
+ var pm = Services.perms;
+
+ var ioService = Services.io;
+
+ var secMan = Services.scriptSecurityManager;
+
+ // nsIPermissionManager implementation is an extension; don't fail if it's not there
+ if (!pm) {
+ return;
+ }
+
+ // put a few hosts in
+ for (let i = 0; i < hosts.length; ++i) {
+ let uri = ioService.newURI(hosts[i][0]);
+ let principal = secMan.createContentPrincipal(uri, {});
+
+ pm.addFromPrincipal(principal, hosts[i][1], hosts[i][2]);
+ }
+
+ // test the result
+ for (let i = 0; i < results.length; ++i) {
+ let uri = ioService.newURI(results[i][0]);
+ let principal = secMan.createContentPrincipal(uri, {});
+
+ Assert.equal(
+ pm.testPermissionFromPrincipal(principal, results[i][1]),
+ results[i][2]
+ );
+ Assert.equal(
+ pm.testExactPermissionFromPrincipal(principal, results[i][1]),
+ results[i][3]
+ );
+ }
+
+ // test the all property ...
+ var perms = pm.all;
+ Assert.equal(perms.length, hosts.length);
+
+ // ... remove all the hosts ...
+ for (let j = 0; j < perms.length; ++j) {
+ pm.removePermission(perms[j]);
+ }
+
+ // ... ensure each and every element is equal ...
+ for (let i = 0; i < hosts.length; ++i) {
+ for (let j = 0; j < perms.length; ++j) {
+ if (
+ perms[j].matchesURI(ioService.newURI(hosts[i][0]), true) &&
+ hosts[i][1] == perms[j].type &&
+ hosts[i][2] == perms[j].capability
+ ) {
+ perms.splice(j, 1);
+ break;
+ }
+ }
+ }
+ Assert.equal(perms.length, 0);
+
+ // ... and check the permmgr's empty
+ Assert.equal(pm.all.length, 0);
+
+ // test UTF8 normalization behavior: expect ASCII/ACE host encodings
+ var utf8 = "b\u00FCcher.dolske.org"; // "bücher.dolske.org"
+ var aceref = "xn--bcher-kva.dolske.org";
+ var principal = secMan.createContentPrincipal(
+ ioService.newURI("http://" + utf8),
+ {}
+ );
+ pm.addFromPrincipal(principal, "utf8", 1);
+ Assert.notEqual(Services.perms.all.length, 0);
+ var ace = Services.perms.all[0];
+ Assert.equal(ace.principal.asciiHost, aceref);
+ Assert.equal(Services.perms.all.length > 1, false);
+
+ // test removeAll()
+ pm.removeAll();
+ Assert.equal(Services.perms.all.length, 0);
+
+ principal = secMan.createContentPrincipalFromOrigin(
+ "https://www.example.com"
+ );
+ pm.addFromPrincipal(principal, "offline-app", pm.ALLOW_ACTION);
+ // Remove existing entry.
+ let perm = pm.getPermissionObject(principal, "offline-app", true);
+ pm.removePermission(perm);
+ // Try to remove already deleted entry.
+ perm = pm.getPermissionObject(principal, "offline-app", true);
+ pm.removePermission(perm);
+ Assert.equal(Services.perms.all.length, 0);
+}
diff --git a/netwerk/test/unit/test_ping_aboutnetworking.js b/netwerk/test/unit/test_ping_aboutnetworking.js
new file mode 100644
index 0000000000..fbaaeaa405
--- /dev/null
+++ b/netwerk/test/unit/test_ping_aboutnetworking.js
@@ -0,0 +1,103 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+"use strict";
+
+const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService(
+ Ci.nsIDashboard
+);
+
+function connectionFailed(status) {
+ let status_ok = [
+ "NS_NET_STATUS_RESOLVING_HOST",
+ "NS_NET_STATUS_RESOLVED_HOST",
+ "NS_NET_STATUS_CONNECTING_TO",
+ "NS_NET_STATUS_CONNECTED_TO",
+ ];
+ for (let i = 0; i < status_ok.length; i++) {
+ if (status == status_ok[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function test_sockets(serverSocket) {
+ // TODO: enable this test in bug 1581892.
+ if (mozinfo.socketprocess_networking) {
+ info("skip test_sockets");
+ do_test_finished();
+ return;
+ }
+
+ do_test_pending();
+ gDashboard.requestSockets(function (data) {
+ let index = -1;
+ info("requestSockets: " + JSON.stringify(data.sockets));
+ for (let i = 0; i < data.sockets.length; i++) {
+ if (data.sockets[i].host == "127.0.0.1") {
+ index = i;
+ break;
+ }
+ }
+ Assert.notEqual(index, -1);
+ Assert.equal(data.sockets[index].port, serverSocket.port);
+ Assert.equal(data.sockets[index].type, "TCP");
+
+ do_test_finished();
+ });
+}
+
+function run_test() {
+ var ps = Services.prefs;
+ // disable network changed events to avoid the the risk of having the dns
+ // cache getting flushed behind our back
+ ps.setBoolPref("network.notify.changed", false);
+ // Localhost is hardcoded to loopback and isn't cached, disable that with this pref
+ ps.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ registerCleanupFunction(function () {
+ ps.clearUserPref("network.notify.changed");
+ ps.clearUserPref("network.proxy.allow_hijacking_localhost");
+ });
+
+ let serverSocket = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ serverSocket.init(-1, true, -1);
+
+ do_test_pending();
+ gDashboard.requestConnection(
+ "localhost",
+ serverSocket.port,
+ "tcp",
+ 15,
+ function (connInfo) {
+ if (connInfo.status == "NS_NET_STATUS_CONNECTED_TO") {
+ do_test_pending();
+ gDashboard.requestDNSInfo(function (data) {
+ let found = false;
+ info("requestDNSInfo: " + JSON.stringify(data.entries));
+ for (let i = 0; i < data.entries.length; i++) {
+ if (data.entries[i].hostname == "localhost") {
+ found = true;
+ break;
+ }
+ }
+ Assert.equal(found, true);
+
+ do_test_finished();
+ test_sockets(serverSocket);
+ });
+
+ do_test_finished();
+ }
+ if (connectionFailed(connInfo.status)) {
+ do_throw(connInfo.status);
+ }
+ }
+ );
+}
diff --git a/netwerk/test/unit/test_plaintext_sniff.js b/netwerk/test/unit/test_plaintext_sniff.js
new file mode 100644
index 0000000000..896e458165
--- /dev/null
+++ b/netwerk/test/unit/test_plaintext_sniff.js
@@ -0,0 +1,211 @@
+// Test the plaintext-or-binary sniffer
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// List of Content-Type headers to test. For each header we have an array.
+// The first element in the array is the Content-Type header string. The
+// second element in the array is a boolean indicating whether we allow
+// sniffing for that type.
+var contentTypeHeaderList = [
+ ["text/plain", true],
+ ["text/plain; charset=ISO-8859-1", true],
+ ["text/plain; charset=iso-8859-1", true],
+ ["text/plain; charset=UTF-8", true],
+ ["text/plain; charset=unknown", false],
+ ["text/plain; param", false],
+ ["text/plain; charset=ISO-8859-1; param", false],
+ ["text/plain; charset=iso-8859-1; param", false],
+ ["text/plain; charset=UTF-8; param", false],
+ ["text/plain; charset=utf-8", false],
+ ["text/plain; charset=utf8", false],
+ ["text/plain; charset=UTF8", false],
+ ["text/plain; charset=iSo-8859-1", false],
+];
+
+// List of response bodies to test. For each response we have an array. The
+// first element in the array is the body string. The second element in the
+// array is a boolean indicating whether that string should sniff as binary.
+var bodyList = [["Plaintext", false]];
+
+// List of possible BOMs
+var BOMList = [
+ "\xFE\xFF", // UTF-16BE
+ "\xFF\xFE", // UTF-16LE
+ "\xEF\xBB\xBF", // UTF-8
+ "\x00\x00\xFE\xFF", // UCS-4BE
+ "\x00\x00\xFF\xFE", // UCS-4LE
+];
+
+// Build up bodyList. The things we treat as binary are ASCII codes 0-8,
+// 14-26, 28-31. That is, the control char range, except for tab, newline,
+// vertical tab, form feed, carriage return, and ESC (this last being used by
+// Shift_JIS, apparently).
+function isBinaryChar(ch) {
+ return (
+ (0 <= ch && ch <= 8) || (14 <= ch && ch <= 26) || (28 <= ch && ch <= 31)
+ );
+}
+
+// Test chars on their own
+var i;
+for (i = 0; i <= 127; ++i) {
+ bodyList.push([String.fromCharCode(i), isBinaryChar(i)]);
+}
+
+// Test that having a BOM prevents plaintext sniffing
+var j;
+for (i = 0; i <= 127; ++i) {
+ for (j = 0; j < BOMList.length; ++j) {
+ bodyList.push([BOMList[j] + String.fromCharCode(i, i), false]);
+ }
+}
+
+// Test that having a BOM requires at least 4 chars to kick in
+for (i = 0; i <= 127; ++i) {
+ for (j = 0; j < BOMList.length; ++j) {
+ bodyList.push([
+ BOMList[j] + String.fromCharCode(i),
+ BOMList[j].length == 2 && isBinaryChar(i),
+ ]);
+ }
+}
+
+function makeChan(headerIdx, bodyIdx) {
+ var chan = NetUtil.newChannel({
+ uri:
+ "http://localhost:" +
+ httpserv.identity.primaryPort +
+ "/" +
+ headerIdx +
+ "/" +
+ bodyIdx,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ chan.loadFlags |= Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ return chan;
+}
+
+function makeListener(headerIdx, bodyIdx) {
+ var listener = {
+ onStartRequest: function test_onStartR(request) {
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+
+ Assert.equal(chan.status, Cr.NS_OK);
+
+ var type = chan.contentType;
+
+ var expectedType =
+ contentTypeHeaderList[headerIdx][1] && bodyList[bodyIdx][1]
+ ? "application/x-vnd.mozilla.guess-from-ext"
+ : "text/plain";
+ if (expectedType != type) {
+ do_throw(
+ "Unexpected sniffed type '" +
+ type +
+ "'. " +
+ "Should be '" +
+ expectedType +
+ "'. " +
+ "Header is ['" +
+ contentTypeHeaderList[headerIdx][0] +
+ "', " +
+ contentTypeHeaderList[headerIdx][1] +
+ "]. " +
+ "Body is ['" +
+ bodyList[bodyIdx][0].toSource() +
+ "', " +
+ bodyList[bodyIdx][1] +
+ "]."
+ );
+ }
+ Assert.equal(expectedType, type);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ // Advance to next test
+ ++headerIdx;
+ if (headerIdx == contentTypeHeaderList.length) {
+ headerIdx = 0;
+ ++bodyIdx;
+ }
+
+ if (bodyIdx == bodyList.length) {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ } else {
+ doTest(headerIdx, bodyIdx);
+ }
+
+ do_test_finished();
+ },
+ };
+
+ return listener;
+}
+
+function doTest(headerIdx, bodyIdx) {
+ var chan = makeChan(headerIdx, bodyIdx);
+
+ var listener = makeListener(headerIdx, bodyIdx);
+
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function createResponse(headerIdx, bodyIdx, metadata, response) {
+ response.setHeader(
+ "Content-Type",
+ contentTypeHeaderList[headerIdx][0],
+ false
+ );
+ response.bodyOutputStream.write(
+ bodyList[bodyIdx][0],
+ bodyList[bodyIdx][0].length
+ );
+}
+
+function makeHandler(headerIdx, bodyIdx) {
+ var f = function handlerClosure(metadata, response) {
+ return createResponse(headerIdx, bodyIdx, metadata, response);
+ };
+ return f;
+}
+
+var httpserv;
+function run_test() {
+ // disable on Windows for now, because it seems to leak sockets and die.
+ // Silly operating system!
+ // This is a really nasty way to detect Windows. I wish we could do better.
+ if (mozinfo.os == "win") {
+ //failing eslint no-empty test
+ }
+
+ httpserv = new HttpServer();
+
+ for (i = 0; i < contentTypeHeaderList.length; ++i) {
+ for (j = 0; j < bodyList.length; ++j) {
+ httpserv.registerPathHandler("/" + i + "/" + j, makeHandler(i, j));
+ }
+ }
+
+ httpserv.start(-1);
+
+ doTest(0, 0);
+}
diff --git a/netwerk/test/unit/test_port_remapping.js b/netwerk/test/unit/test_port_remapping.js
new file mode 100644
index 0000000000..274a1117ec
--- /dev/null
+++ b/netwerk/test/unit/test_port_remapping.js
@@ -0,0 +1,50 @@
+// This test is checking the `network.socket.forcePort` preference has an effect.
+// We remap an ilusional port `8765` to go to the port the server actually binds to.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const REMAPPED_PORT = 8765;
+
+add_task(async function check_protocols() {
+ function contentHandler(metadata, response) {
+ let responseBody = "The server should never return this!";
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+ }
+
+ const httpserv = new HttpServer();
+ httpserv.registerPathHandler("/content", contentHandler);
+ httpserv.start(-1);
+
+ do_get_profile();
+ Services.prefs.setCharPref(
+ "network.socket.forcePort",
+ `${REMAPPED_PORT}=${httpserv.identity.primaryPort}`
+ );
+
+ function get_response() {
+ return new Promise(resolve => {
+ const URL = `http://localhost:${REMAPPED_PORT}/content`;
+ const channel = make_channel(URL);
+ channel.asyncOpen(
+ new ChannelListener((request, data) => {
+ resolve(data);
+ })
+ );
+ });
+ }
+
+ // We expect "Bad request" from the test server because the server doesn't
+ // have identity for the remapped port. We don't want to add it too, because
+ // that would not prove we actualy remap the port number.
+ Assert.equal(await get_response(), "Bad request\n");
+ await new Promise(resolve => httpserv.stop(resolve));
+});
diff --git a/netwerk/test/unit/test_post.js b/netwerk/test/unit/test_post.js
new file mode 100644
index 0000000000..f57a149920
--- /dev/null
+++ b/netwerk/test/unit/test_post.js
@@ -0,0 +1,141 @@
+//
+// POST test
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+
+var testfile = do_get_file("../unit/data/test_readline6.txt");
+
+const BOUNDARY = "AaB03x";
+var teststring1 =
+ "--" +
+ BOUNDARY +
+ "\r\n" +
+ 'Content-Disposition: form-data; name="body"\r\n\r\n' +
+ "0123456789\r\n" +
+ "--" +
+ BOUNDARY +
+ "\r\n" +
+ 'Content-Disposition: form-data; name="files"; filename="' +
+ testfile.leafName +
+ '"\r\n' +
+ "Content-Type: application/octet-stream\r\n" +
+ "Content-Length: " +
+ testfile.fileSize +
+ "\r\n\r\n";
+var teststring2 = "--" + BOUNDARY + "--\r\n";
+
+const BUFFERSIZE = 4096;
+var correctOnProgress = false;
+
+var listenerCallback = {
+ QueryInterface: ChromeUtils.generateQI(["nsIProgressEventSink"]),
+
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIProgressEventSink)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ onProgress(request, progress, progressMax) {
+ // this works because the response is 0 bytes and does not trigger onprogress
+ if (progress === progressMax) {
+ correctOnProgress = true;
+ }
+ },
+
+ onStatus(request, status, statusArg) {},
+};
+
+function run_test() {
+ var sstream1 = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ sstream1.data = teststring1;
+
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(testfile, -1, -1, 0);
+
+ var buffered = Cc[
+ "@mozilla.org/network/buffered-input-stream;1"
+ ].createInstance(Ci.nsIBufferedInputStream);
+ buffered.init(fstream, BUFFERSIZE);
+
+ var sstream2 = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ sstream2.data = teststring2;
+
+ var multi = Cc["@mozilla.org/io/multiplex-input-stream;1"].createInstance(
+ Ci.nsIMultiplexInputStream
+ );
+ multi.appendStream(sstream1);
+ multi.appendStream(buffered);
+ multi.appendStream(sstream2);
+
+ var mime = Cc["@mozilla.org/network/mime-input-stream;1"].createInstance(
+ Ci.nsIMIMEInputStream
+ );
+ mime.addHeader("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
+ mime.setData(multi);
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ var channel = setupChannel(testpath);
+
+ channel
+ .QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(mime, "", mime.available());
+ channel.requestMethod = "POST";
+ channel.notificationCallbacks = listenerCallback;
+ channel.asyncOpen(new ChannelListener(checkRequest, channel));
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ return NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function serverHandler(metadata, response) {
+ Assert.equal(metadata.method, "POST");
+
+ var data = read_stream(
+ metadata.bodyInputStream,
+ metadata.bodyInputStream.available()
+ );
+
+ var testfile_stream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ testfile_stream.init(testfile, -1, -1, 0);
+
+ Assert.equal(
+ teststring1 +
+ read_stream(testfile_stream, testfile_stream.available()) +
+ teststring2,
+ data
+ );
+}
+
+function checkRequest(request, data, context) {
+ Assert.ok(correctOnProgress);
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_predictor.js b/netwerk/test/unit/test_predictor.js
new file mode 100644
index 0000000000..dce86ce5e0
--- /dev/null
+++ b/netwerk/test/unit/test_predictor.js
@@ -0,0 +1,852 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+var running_single_process = false;
+
+var predictor = null;
+
+function is_child_process() {
+ return Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+}
+
+function extract_origin(uri) {
+ var o = uri.scheme + "://" + uri.asciiHost;
+ if (uri.port !== -1) {
+ o = o + ":" + uri.port;
+ }
+ return o;
+}
+
+var origin_attributes = {};
+
+var ValidityChecker = function (verifier, httpStatus) {
+ this.verifier = verifier;
+ this.httpStatus = httpStatus;
+};
+
+ValidityChecker.prototype = {
+ verifier: null,
+ httpStatus: 0,
+
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
+
+ onCacheEntryCheck(entry) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable(entry, isnew, status) {
+ // Check if forced valid
+ Assert.equal(entry.isForcedValid, this.httpStatus === 200);
+ this.verifier.maybe_run_next_test();
+ },
+};
+
+var Verifier = function _verifier(
+ testing,
+ expected_prefetches,
+ expected_preconnects,
+ expected_preresolves
+) {
+ this.verifying = testing;
+ this.expected_prefetches = expected_prefetches;
+ this.expected_preconnects = expected_preconnects;
+ this.expected_preresolves = expected_preresolves;
+};
+
+Verifier.prototype = {
+ complete: false,
+ verifying: null,
+ expected_prefetches: null,
+ expected_preconnects: null,
+ expected_preresolves: null,
+
+ getInterface: function verifier_getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkPredictorVerifier"]),
+
+ maybe_run_next_test: function verifier_maybe_run_next_test() {
+ if (
+ this.expected_prefetches.length === 0 &&
+ this.expected_preconnects.length === 0 &&
+ this.expected_preresolves.length === 0 &&
+ !this.complete
+ ) {
+ this.complete = true;
+ Assert.ok(true, "Well this is unexpected...");
+ // This kicks off the ability to run the next test
+ reset_predictor();
+ }
+ },
+
+ onPredictPrefetch: function verifier_onPredictPrefetch(uri, status) {
+ var index = this.expected_prefetches.indexOf(uri.asciiSpec);
+ if (index == -1 && !this.complete) {
+ Assert.ok(false, "Got prefetch for unexpected uri " + uri.asciiSpec);
+ } else {
+ this.expected_prefetches.splice(index, 1);
+ }
+
+ dump("checking validity of entry for " + uri.spec + "\n");
+ var checker = new ValidityChecker(this, status);
+ asyncOpenCacheEntry(
+ uri.spec,
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ checker
+ );
+ },
+
+ onPredictPreconnect: function verifier_onPredictPreconnect(uri) {
+ var origin = extract_origin(uri);
+ var index = this.expected_preconnects.indexOf(origin);
+ if (index == -1 && !this.complete) {
+ Assert.ok(false, "Got preconnect for unexpected uri " + origin);
+ } else {
+ this.expected_preconnects.splice(index, 1);
+ }
+ this.maybe_run_next_test();
+ },
+
+ onPredictDNS: function verifier_onPredictDNS(uri) {
+ var origin = extract_origin(uri);
+ var index = this.expected_preresolves.indexOf(origin);
+ if (index == -1 && !this.complete) {
+ Assert.ok(false, "Got preresolve for unexpected uri " + origin);
+ } else {
+ this.expected_preresolves.splice(index, 1);
+ }
+ this.maybe_run_next_test();
+ },
+};
+
+function reset_predictor() {
+ if (running_single_process || is_child_process()) {
+ predictor.reset();
+ } else {
+ sendCommand("predictor.reset();");
+ }
+}
+
+function newURI(s) {
+ return Services.io.newURI(s);
+}
+
+var prepListener = {
+ numEntriesToOpen: 0,
+ numEntriesOpened: 0,
+ continueCallback: null,
+
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
+
+ init(entriesToOpen, cb) {
+ this.numEntriesOpened = 0;
+ this.numEntriesToOpen = entriesToOpen;
+ this.continueCallback = cb;
+ },
+
+ onCacheEntryCheck(entry) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable(entry, isNew, result) {
+ Assert.equal(result, Cr.NS_OK);
+ entry.setMetaDataElement("predictor_test", "1");
+ entry.metaDataReady();
+ this.numEntriesOpened++;
+ if (this.numEntriesToOpen == this.numEntriesOpened) {
+ this.continueCallback();
+ }
+ },
+};
+
+function open_and_continue(uris, continueCallback) {
+ var ds = Services.cache2.diskCacheStorage(Services.loadContextInfo.default);
+
+ prepListener.init(uris.length, continueCallback);
+ for (var i = 0; i < uris.length; ++i) {
+ ds.asyncOpenURI(
+ uris[i],
+ "",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ prepListener
+ );
+ }
+}
+
+function test_link_hover() {
+ if (!running_single_process && !is_child_process()) {
+ // This one we can just proxy to the child and be done with, no extra setup
+ // is necessary.
+ sendCommand("test_link_hover();");
+ return;
+ }
+
+ var uri = newURI("http://localhost:4444/foo/bar");
+ var referrer = newURI("http://localhost:4444/foo");
+ var preconns = ["http://localhost:4444"];
+
+ var verifier = new Verifier("hover", [], preconns, []);
+ predictor.predict(
+ uri,
+ referrer,
+ predictor.PREDICT_LINK,
+ origin_attributes,
+ verifier
+ );
+}
+
+const pageload_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_pageload() {
+ var subresources = [
+ "http://localhost:4444/style.css",
+ "http://localhost:4443/jquery.js",
+ "http://localhost:4444/image.png",
+ ];
+
+ // This is necessary to learn the origin stuff
+ predictor.learn(
+ pageload_toplevel,
+ null,
+ predictor.LEARN_LOAD_TOPLEVEL,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ // allow the learn() to run on the main thread
+ var preconns = [];
+
+ var sruri = newURI(subresources[0]);
+ predictor.learn(
+ sruri,
+ pageload_toplevel,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ preconns.push(extract_origin(sruri));
+
+ sruri = newURI(subresources[1]);
+ predictor.learn(
+ sruri,
+ pageload_toplevel,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ preconns.push(extract_origin(sruri));
+
+ sruri = newURI(subresources[2]);
+ predictor.learn(
+ sruri,
+ pageload_toplevel,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ preconns.push(extract_origin(sruri));
+
+ var verifier = new Verifier("pageload", [], preconns, []);
+ predictor.predict(
+ pageload_toplevel,
+ null,
+ predictor.PREDICT_LOAD,
+ origin_attributes,
+ verifier
+ );
+ });
+ });
+ });
+ });
+}
+
+function test_pageload() {
+ open_and_continue([pageload_toplevel], function () {
+ if (running_single_process) {
+ continue_test_pageload();
+ } else {
+ sendCommand("continue_test_pageload();");
+ }
+ });
+}
+
+const redirect_inituri = newURI("http://localhost:4443/redirect");
+const redirect_targeturi = newURI("http://localhost:4444/index.html");
+
+function continue_test_redirect() {
+ var subresources = [
+ "http://localhost:4444/style.css",
+ "http://localhost:4443/jquery.js",
+ "http://localhost:4444/image.png",
+ ];
+
+ predictor.learn(
+ redirect_inituri,
+ null,
+ predictor.LEARN_LOAD_TOPLEVEL,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ predictor.learn(
+ redirect_targeturi,
+ null,
+ predictor.LEARN_LOAD_TOPLEVEL,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ predictor.learn(
+ redirect_targeturi,
+ redirect_inituri,
+ predictor.LEARN_LOAD_REDIRECT,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ var preconns = [];
+ preconns.push(extract_origin(redirect_targeturi));
+
+ var sruri = newURI(subresources[0]);
+ predictor.learn(
+ sruri,
+ redirect_targeturi,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ preconns.push(extract_origin(sruri));
+
+ sruri = newURI(subresources[1]);
+ predictor.learn(
+ sruri[1],
+ redirect_targeturi,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ preconns.push(extract_origin(sruri));
+
+ sruri = newURI(subresources[2]);
+ predictor.learn(
+ sruri[2],
+ redirect_targeturi,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ preconns.push(extract_origin(sruri));
+
+ var verifier = new Verifier("redirect", [], preconns, []);
+ predictor.predict(
+ redirect_inituri,
+ null,
+ predictor.PREDICT_LOAD,
+ origin_attributes,
+ verifier
+ );
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
+// Test is currently disabled.
+// eslint-disable-next-line no-unused-vars
+function test_redirect() {
+ open_and_continue([redirect_inituri, redirect_targeturi], function () {
+ if (running_single_process) {
+ continue_test_redirect();
+ } else {
+ sendCommand("continue_test_redirect();");
+ }
+ });
+}
+
+// Test is currently disabled.
+// eslint-disable-next-line no-unused-vars
+function test_startup() {
+ if (!running_single_process && !is_child_process()) {
+ // This one we can just proxy to the child and be done with, no extra setup
+ // is necessary.
+ sendCommand("test_startup();");
+ return;
+ }
+
+ var uris = ["http://localhost:4444/startup", "http://localhost:4443/startup"];
+ var preconns = [];
+ var uri = newURI(uris[0]);
+ predictor.learn(uri, null, predictor.LEARN_STARTUP, origin_attributes);
+ do_timeout(0, () => {
+ preconns.push(extract_origin(uri));
+
+ uri = newURI(uris[1]);
+ predictor.learn(uri, null, predictor.LEARN_STARTUP, origin_attributes);
+ do_timeout(0, () => {
+ preconns.push(extract_origin(uri));
+
+ var verifier = new Verifier("startup", [], preconns, []);
+ predictor.predict(
+ null,
+ null,
+ predictor.PREDICT_STARTUP,
+ origin_attributes,
+ verifier
+ );
+ });
+ });
+}
+
+const dns_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_dns() {
+ var subresource = "http://localhost:4443/jquery.js";
+
+ predictor.learn(
+ dns_toplevel,
+ null,
+ predictor.LEARN_LOAD_TOPLEVEL,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ var sruri = newURI(subresource);
+ predictor.learn(
+ sruri,
+ dns_toplevel,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ var preresolves = [extract_origin(sruri)];
+ var verifier = new Verifier("dns", [], [], preresolves);
+ predictor.predict(
+ dns_toplevel,
+ null,
+ predictor.PREDICT_LOAD,
+ origin_attributes,
+ verifier
+ );
+ });
+ });
+}
+
+function test_dns() {
+ open_and_continue([dns_toplevel], function () {
+ // Ensure that this will do preresolves
+ Services.prefs.setIntPref(
+ "network.predictor.preconnect-min-confidence",
+ 101
+ );
+ if (running_single_process) {
+ continue_test_dns();
+ } else {
+ sendCommand("continue_test_dns();");
+ }
+ });
+}
+
+const origin_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_origin() {
+ var subresources = [
+ "http://localhost:4444/style.css",
+ "http://localhost:4443/jquery.js",
+ "http://localhost:4444/image.png",
+ ];
+ predictor.learn(
+ origin_toplevel,
+ null,
+ predictor.LEARN_LOAD_TOPLEVEL,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ var preconns = [];
+
+ var sruri = newURI(subresources[0]);
+ predictor.learn(
+ sruri,
+ origin_toplevel,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ var origin = extract_origin(sruri);
+ if (!preconns.includes(origin)) {
+ preconns.push(origin);
+ }
+
+ sruri = newURI(subresources[1]);
+ predictor.learn(
+ sruri,
+ origin_toplevel,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ var origin1 = extract_origin(sruri);
+ if (!preconns.includes(origin1)) {
+ preconns.push(origin1);
+ }
+
+ sruri = newURI(subresources[2]);
+ predictor.learn(
+ sruri,
+ origin_toplevel,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ do_timeout(0, () => {
+ var origin2 = extract_origin(sruri);
+ if (!preconns.includes(origin2)) {
+ preconns.push(origin2);
+ }
+
+ var loaduri = newURI("http://localhost:4444/anotherpage.html");
+ var verifier = new Verifier("origin", [], preconns, []);
+ predictor.predict(
+ loaduri,
+ null,
+ predictor.PREDICT_LOAD,
+ origin_attributes,
+ verifier
+ );
+ });
+ });
+ });
+ });
+}
+
+function test_origin() {
+ open_and_continue([origin_toplevel], function () {
+ if (running_single_process) {
+ continue_test_origin();
+ } else {
+ sendCommand("continue_test_origin();");
+ }
+ });
+}
+
+var httpserv = null;
+var prefetch_tluri;
+var prefetch_sruri;
+
+function prefetchHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ var body = "Success (meow meow meow).";
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var prefetchListener = {
+ onStartRequest(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable(request, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest(request, status) {
+ run_next_test();
+ },
+};
+
+function test_prefetch_setup() {
+ // Disable preconnects and preresolves
+ Services.prefs.setIntPref("network.predictor.preconnect-min-confidence", 101);
+ Services.prefs.setIntPref("network.predictor.preresolve-min-confidence", 101);
+
+ Services.prefs.setBoolPref("network.predictor.enable-prefetch", true);
+
+ // Makes it so we only have to call test_prefetch_prime twice to make prefetch
+ // do its thing.
+ Services.prefs.setIntPref("network.predictor.prefetch-rolling-load-count", 2);
+
+ // This test does not run in e10s-mode, so we'll just go ahead and skip it.
+ // We've left the e10s test code in below, just in case someone wants to try
+ // to make it work at some point in the future.
+ if (!running_single_process) {
+ dump("skipping test_prefetch_setup due to e10s\n");
+ run_next_test();
+ return;
+ }
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/cat.jpg", prefetchHandler);
+ httpserv.start(-1);
+
+ var tluri =
+ "http://127.0.0.1:" + httpserv.identity.primaryPort + "/index.html";
+ var sruri = "http://127.0.0.1:" + httpserv.identity.primaryPort + "/cat.jpg";
+ prefetch_tluri = newURI(tluri);
+ prefetch_sruri = newURI(sruri);
+ if (!running_single_process && !is_child_process()) {
+ // Give the child process access to these values
+ sendCommand('prefetch_tluri = newURI("' + tluri + '");');
+ sendCommand('prefetch_sruri = newURI("' + sruri + '");');
+ }
+
+ run_next_test();
+}
+
+// Used to "prime the pump" for prefetch - it makes sure all our learns go
+// through as expected so that prefetching will happen.
+function test_prefetch_prime() {
+ // This test does not run in e10s-mode, so we'll just go ahead and skip it.
+ // We've left the e10s test code in below, just in case someone wants to try
+ // to make it work at some point in the future.
+ if (!running_single_process) {
+ dump("skipping test_prefetch_prime due to e10s\n");
+ run_next_test();
+ return;
+ }
+
+ open_and_continue([prefetch_tluri], function () {
+ if (running_single_process) {
+ predictor.learn(
+ prefetch_tluri,
+ null,
+ predictor.LEARN_LOAD_TOPLEVEL,
+ origin_attributes
+ );
+ predictor.learn(
+ prefetch_sruri,
+ prefetch_tluri,
+ predictor.LEARN_LOAD_SUBRESOURCE,
+ origin_attributes
+ );
+ } else {
+ sendCommand(
+ "predictor.learn(prefetch_tluri, null, predictor.LEARN_LOAD_TOPLEVEL, origin_attributes);"
+ );
+ sendCommand(
+ "predictor.learn(prefetch_sruri, prefetch_tluri, predictor.LEARN_LOAD_SUBRESOURCE, origin_attributes);"
+ );
+ }
+
+ // This runs in the parent or only process
+ var channel = NetUtil.newChannel({
+ uri: prefetch_sruri.asciiSpec,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ channel.requestMethod = "GET";
+ channel.referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ prefetch_tluri
+ );
+ channel.asyncOpen(prefetchListener);
+ });
+}
+
+function test_prefetch() {
+ // This test does not run in e10s-mode, so we'll just go ahead and skip it.
+ // We've left the e10s test code in below, just in case someone wants to try
+ // to make it work at some point in the future.
+ if (!running_single_process) {
+ dump("skipping test_prefetch due to e10s\n");
+ run_next_test();
+ return;
+ }
+
+ // Setup for this has all been taken care of by test_prefetch_prime, so we can
+ // continue on without pausing here.
+ if (running_single_process) {
+ continue_test_prefetch();
+ } else {
+ sendCommand("continue_test_prefetch();");
+ }
+}
+
+function continue_test_prefetch() {
+ var prefetches = [prefetch_sruri.asciiSpec];
+ var verifier = new Verifier("prefetch", prefetches, [], []);
+ predictor.predict(
+ prefetch_tluri,
+ null,
+ predictor.PREDICT_LOAD,
+ origin_attributes,
+ verifier
+ );
+}
+
+function test_visitor_doom() {
+ // See bug 1708673
+ Services.prefs.setBoolPref("network.cache.bug1708673", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.cache.bug1708673");
+ });
+
+ let p1 = new Promise(resolve => {
+ let doomTasks = [];
+ let visitor = {
+ onCacheStorageInfo() {},
+ async onCacheEntryInfo(
+ aURI,
+ aIdEnhance,
+ aDataSize,
+ aAltDataSize,
+ aFetchCount,
+ aLastModifiedTime,
+ aExpirationTime,
+ aPinned,
+ aInfo
+ ) {
+ let storages = [
+ Services.cache2.memoryCacheStorage(aInfo),
+ Services.cache2.diskCacheStorage(aInfo, false),
+ ];
+ console.debug("asyncDoomURI", aURI.spec);
+ let doomTask = Promise.all(
+ storages.map(storage => {
+ return new Promise(resolve1 => {
+ storage.asyncDoomURI(aURI, aIdEnhance, {
+ onCacheEntryDoomed: resolve1,
+ });
+ });
+ })
+ );
+ doomTasks.push(doomTask);
+ },
+ onCacheEntryVisitCompleted() {
+ Promise.allSettled(doomTasks).then(resolve);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ Services.cache2.asyncVisitAllStorages(visitor, true);
+ });
+
+ let p2 = new Promise(resolve => {
+ reset_predictor();
+ resolve();
+ });
+
+ do_test_pending();
+ Promise.allSettled([p1, p2]).then(() => {
+ return new Promise(resolve => {
+ let entryCount = 0;
+ let visitor = {
+ onCacheStorageInfo() {},
+ async onCacheEntryInfo(
+ aURI,
+ aIdEnhance,
+ aDataSize,
+ aAltDataSize,
+ aFetchCount,
+ aLastModifiedTime,
+ aExpirationTime,
+ aPinned,
+ aInfo
+ ) {
+ entryCount++;
+ },
+ onCacheEntryVisitCompleted() {
+ Assert.equal(entryCount, 0);
+ resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ Services.cache2.asyncVisitAllStorages(visitor, true);
+ }).then(run_next_test);
+ });
+}
+
+function cleanup() {
+ observer.cleaningUp = true;
+ if (running_single_process) {
+ // The http server is required (and started) by the prefetch test, which
+ // only runs in single-process mode, so don't try to shut it down if we're
+ // in e10s mode.
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+ reset_predictor();
+}
+
+var tests = [
+ // This must ALWAYS come first, to ensure a clean slate
+ reset_predictor,
+ test_link_hover,
+ test_pageload,
+ // TODO: These are disabled until the features are re-written
+ //test_redirect,
+ //test_startup,
+ // END DISABLED TESTS
+ test_origin,
+ test_dns,
+ test_prefetch_setup,
+ test_prefetch_prime,
+ test_prefetch_prime,
+ test_prefetch,
+ test_visitor_doom,
+ // This must ALWAYS come last, to ensure we clean up after ourselves
+ cleanup,
+];
+
+var observer = {
+ cleaningUp: false,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (topic != "predictor-reset-complete") {
+ return;
+ }
+
+ if (this.cleaningUp) {
+ unregisterObserver();
+ }
+
+ run_next_test();
+ },
+};
+
+function registerObserver() {
+ Services.obs.addObserver(observer, "predictor-reset-complete");
+}
+
+function unregisterObserver() {
+ Services.obs.removeObserver(observer, "predictor-reset-complete");
+}
+
+function run_test_real() {
+ tests.forEach(f => add_test(f));
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.predictor.enabled", true);
+ Services.prefs.setBoolPref("network.predictor.doing-tests", true);
+
+ predictor = Cc["@mozilla.org/network/predictor;1"].getService(
+ Ci.nsINetworkPredictor
+ );
+
+ registerObserver();
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.predictor.preconnect-min-confidence");
+ Services.prefs.clearUserPref("network.predictor.enabled");
+ Services.prefs.clearUserPref("network.predictor.preresolve-min-confidence");
+ Services.prefs.clearUserPref("network.predictor.enable-prefetch");
+ Services.prefs.clearUserPref(
+ "network.predictor.prefetch-rolling-load-count"
+ );
+ Services.prefs.clearUserPref("network.predictor.doing-tests");
+ });
+
+ run_next_test();
+}
+
+function run_test() {
+ // This indirection is necessary to make e10s tests work as expected
+ running_single_process = true;
+ run_test_real();
+}
diff --git a/netwerk/test/unit/test_private_cookie_changed.js b/netwerk/test/unit/test_private_cookie_changed.js
new file mode 100644
index 0000000000..89e9f5e75a
--- /dev/null
+++ b/netwerk/test/unit/test_private_cookie_changed.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+function makeChan(uri, isPrivate) {
+ var chan = NetUtil.newChannel({
+ uri: uri.spec,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ chan.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(isPrivate);
+ return chan;
+}
+
+function run_test() {
+ // We don't want to have CookieJarSettings blocking this test.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ let publicNotifications = 0;
+ let privateNotifications = 0;
+ Services.obs.addObserver(function () {
+ publicNotifications++;
+ }, "cookie-changed");
+ Services.obs.addObserver(function () {
+ privateNotifications++;
+ }, "private-cookie-changed");
+
+ let uri = NetUtil.newURI("http://foo.com/");
+ let publicChan = makeChan(uri, false);
+ let svc = Services.cookies.QueryInterface(Ci.nsICookieService);
+ svc.setCookieStringFromHttp(uri, "oh=hai", publicChan);
+ let privateChan = makeChan(uri, true);
+ svc.setCookieStringFromHttp(uri, "oh=hai", privateChan);
+ Assert.equal(publicNotifications, 1);
+ Assert.equal(privateNotifications, 1);
+}
diff --git a/netwerk/test/unit/test_private_necko_channel.js b/netwerk/test/unit/test_private_necko_channel.js
new file mode 100644
index 0000000000..dc6a73e145
--- /dev/null
+++ b/netwerk/test/unit/test_private_necko_channel.js
@@ -0,0 +1,60 @@
+//
+// Private channel test
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+function run_test() {
+ // Simulate a profile dir for xpcshell
+ do_get_profile();
+
+ // Start off with an empty cache
+ evict_cache_entries();
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ var channel = setupChannel(testpath);
+ channel.loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance();
+
+ channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ channel.setPrivate(true);
+
+ channel.asyncOpen(new ChannelListener(checkRequest, channel));
+
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + path,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function checkRequest(request, data, context) {
+ get_device_entry_count("disk", null, function (count) {
+ Assert.equal(count, 0);
+ get_device_entry_count(
+ "disk",
+ Services.loadContextInfo.private,
+ function (count1) {
+ Assert.equal(count1, 1);
+ httpserver.stop(do_test_finished);
+ }
+ );
+ });
+}
diff --git a/netwerk/test/unit/test_progress.js b/netwerk/test/unit/test_progress.js
new file mode 100644
index 0000000000..8363f0e337
--- /dev/null
+++ b/netwerk/test/unit/test_progress.js
@@ -0,0 +1,145 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+var last = 0,
+ max = 0;
+
+const STATUS_RECEIVING_FROM = 0x4b0006;
+const LOOPS = 50000;
+
+const TYPE_ONSTATUS = 1;
+const TYPE_ONPROGRESS = 2;
+const TYPE_ONSTARTREQUEST = 3;
+const TYPE_ONDATAAVAILABLE = 4;
+const TYPE_ONSTOPREQUEST = 5;
+
+var ProgressCallback = function () {};
+
+ProgressCallback.prototype = {
+ _listener: null,
+ _got_onstartrequest: false,
+ _got_onstatus_after_onstartrequest: false,
+ _last_callback_handled: null,
+ statusArg: "",
+ finish: null,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIProgressEventSink",
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ getInterface(iid) {
+ if (
+ iid.equals(Ci.nsIProgressEventSink) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver)
+ ) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ onStartRequest(request) {
+ Assert.equal(this._last_callback_handled, TYPE_ONSTATUS);
+ this._got_onstartrequest = true;
+ this._last_callback_handled = TYPE_ONSTARTREQUEST;
+
+ this._listener = new ChannelListener(checkRequest, request);
+ this._listener.onStartRequest(request);
+ },
+
+ onDataAvailable(request, data, offset, count) {
+ Assert.equal(this._last_callback_handled, TYPE_ONPROGRESS);
+ this._last_callback_handled = TYPE_ONDATAAVAILABLE;
+
+ this._listener.onDataAvailable(request, data, offset, count);
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ Assert.ok(this._got_onstatus_after_onstartrequest);
+ this._last_callback_handled = TYPE_ONSTOPREQUEST;
+
+ this._listener.onStopRequest(request, status);
+ delete this._listener;
+ this.finish();
+ },
+
+ onProgress(request, progress, progressMax) {
+ Assert.equal(this._last_callback_handled, TYPE_ONSTATUS);
+ this._last_callback_handled = TYPE_ONPROGRESS;
+
+ Assert.equal(this.mStatus, STATUS_RECEIVING_FROM);
+ last = progress;
+ max = progressMax;
+ },
+
+ onStatus(request, status, statusArg) {
+ if (!this._got_onstartrequest) {
+ // Ensure that all messages before onStartRequest are onStatus
+ if (this._last_callback_handled) {
+ Assert.equal(this._last_callback_handled, TYPE_ONSTATUS);
+ }
+ } else if (this._last_callback_handled == TYPE_ONSTARTREQUEST) {
+ this._got_onstatus_after_onstartrequest = true;
+ } else {
+ Assert.equal(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ }
+ this._last_callback_handled = TYPE_ONSTATUS;
+
+ Assert.equal(statusArg, this.statusArg);
+ this.mStatus = status;
+ },
+
+ mStatus: 0,
+};
+
+registerCleanupFunction(async () => {
+ await httpserver.stop();
+});
+
+function chanPromise(uri, statusArg) {
+ return new Promise(resolve => {
+ var chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ let listener = new ProgressCallback();
+ listener.statusArg = statusArg;
+ chan.notificationCallbacks = listener;
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+add_task(async function test_http1_1() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ await chanPromise(URL + testpath, "localhost");
+});
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ for (let i = 0; i < LOOPS; i++) {
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ }
+}
+
+function checkRequest(request, data, context) {
+ Assert.equal(last, httpbody.length * LOOPS);
+ Assert.equal(max, httpbody.length * LOOPS);
+}
diff --git a/netwerk/test/unit/test_progress_no_proxy_and_proxy.js b/netwerk/test/unit/test_progress_no_proxy_and_proxy.js
new file mode 100644
index 0000000000..cb132360c8
--- /dev/null
+++ b/netwerk/test/unit/test_progress_no_proxy_and_proxy.js
@@ -0,0 +1,205 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// This test can be merged with test_progress.js once HTTP/3 tests are
+// enabled on all plaforms.
+
+var last = 0;
+var max = 0;
+var using_proxy = false;
+
+const RESPONSE_LENGTH = 3000000;
+const STATUS_RECEIVING_FROM = 0x4b0006;
+
+const TYPE_ONSTATUS = 1;
+const TYPE_ONPROGRESS = 2;
+const TYPE_ONSTARTREQUEST = 3;
+const TYPE_ONDATAAVAILABLE = 4;
+const TYPE_ONSTOPREQUEST = 5;
+
+var ProgressCallback = function () {};
+
+ProgressCallback.prototype = {
+ _listener: null,
+ _got_onstartrequest: false,
+ _got_onstatus_after_onstartrequest: false,
+ _last_callback_handled: null,
+ statusArg: "",
+ finish: null,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIProgressEventSink",
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ getInterface(iid) {
+ if (
+ iid.equals(Ci.nsIProgressEventSink) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver)
+ ) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ onStartRequest(request) {
+ Assert.equal(this._last_callback_handled, TYPE_ONSTATUS);
+ this._got_onstartrequest = true;
+ this._last_callback_handled = TYPE_ONSTARTREQUEST;
+
+ this._listener = new ChannelListener(checkRequest, request);
+ this._listener.onStartRequest(request);
+ },
+
+ onDataAvailable(request, data, offset, count) {
+ Assert.equal(this._last_callback_handled, TYPE_ONPROGRESS);
+ this._last_callback_handled = TYPE_ONDATAAVAILABLE;
+
+ this._listener.onDataAvailable(request, data, offset, count);
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ Assert.ok(this._got_onstatus_after_onstartrequest);
+ this._last_callback_handled = TYPE_ONSTOPREQUEST;
+
+ this._listener.onStopRequest(request, status);
+ delete this._listener;
+
+ check_http_info(request, this.expected_httpVersion, using_proxy);
+
+ this.finish();
+ },
+
+ onProgress(request, progress, progressMax) {
+ Assert.equal(this._last_callback_handled, TYPE_ONSTATUS);
+ this._last_callback_handled = TYPE_ONPROGRESS;
+
+ Assert.equal(this.mStatus, STATUS_RECEIVING_FROM);
+ last = progress;
+ max = progressMax;
+ },
+
+ onStatus(request, status, statusArg) {
+ if (!this._got_onstartrequest) {
+ // Ensure that all messages before onStartRequest are onStatus
+ if (this._last_callback_handled) {
+ Assert.equal(this._last_callback_handled, TYPE_ONSTATUS);
+ }
+ } else if (this._last_callback_handled == TYPE_ONSTARTREQUEST) {
+ this._got_onstatus_after_onstartrequest = true;
+ } else {
+ Assert.equal(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ }
+ this._last_callback_handled = TYPE_ONSTATUS;
+
+ Assert.equal(statusArg, this.statusArg);
+ this.mStatus = status;
+ },
+
+ mStatus: 0,
+};
+
+function chanPromise(uri, statusArg, version) {
+ return new Promise(resolve => {
+ var chan = makeHTTPChannel(uri, using_proxy);
+ chan.requestMethod = "GET";
+ let listener = new ProgressCallback();
+ listener.statusArg = statusArg;
+ chan.notificationCallbacks = listener;
+ listener.expected_httpVersion = version;
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+function checkRequest(request, data, context) {
+ Assert.equal(last, RESPONSE_LENGTH);
+ Assert.equal(max, RESPONSE_LENGTH);
+}
+
+async function check_progress(server) {
+ info(`Testing ${server.constructor.name}`);
+ await server.registerPathHandler("/test", (req, resp) => {
+ // Generate a post.
+ function generateContent(size) {
+ return "0".repeat(size);
+ }
+
+ resp.writeHead(200, {
+ "content-type": "application/json",
+ "content-length": "3000000",
+ });
+ resp.end(generateContent(3000000));
+ });
+ await chanPromise(
+ `${server.origin()}/test`,
+ `${server.domain()}`,
+ server.version()
+ );
+}
+
+add_task(async function setup() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+});
+
+add_task(async function test_http_1_and_2() {
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ check_progress
+ );
+});
+
+add_task(async function test_http_proxy() {
+ using_proxy = true;
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ check_progress
+ );
+ await proxy.stop();
+ using_proxy = false;
+});
+
+add_task(async function test_https_proxy() {
+ using_proxy = true;
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ check_progress
+ );
+ await proxy.stop();
+ using_proxy = false;
+});
+
+add_task(async function test_http2_proxy() {
+ using_proxy = true;
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ check_progress
+ );
+ await proxy.stop();
+ using_proxy = false;
+});
+
+add_task(async function test_http3() {
+ await http3_setup_tests("h3-29");
+ await chanPromise(
+ "https://foo.example.com/" + RESPONSE_LENGTH,
+ "foo.example.com",
+ "h3-29"
+ );
+ http3_clear_prefs();
+});
diff --git a/netwerk/test/unit/test_protocolproxyservice-async-filters.js b/netwerk/test/unit/test_protocolproxyservice-async-filters.js
new file mode 100644
index 0000000000..fcf43d63ef
--- /dev/null
+++ b/netwerk/test/unit/test_protocolproxyservice-async-filters.js
@@ -0,0 +1,435 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This testcase exercises the Protocol Proxy Service's async filter functionality
+// run_filter_*() are entry points for each individual test.
+
+"use strict";
+
+var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+/**
+ * Test nsIProtocolHandler that allows proxying, but doesn't allow HTTP
+ * proxying.
+ */
+function TestProtocolHandler() {}
+TestProtocolHandler.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler"]),
+ scheme: "moz-test",
+ defaultPort: -1,
+ protocolFlags:
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.ALLOWS_PROXY |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
+ newChannel(uri, aLoadInfo) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ allowPort(port, scheme) {
+ return true;
+ },
+};
+
+function TestProtocolHandlerFactory() {}
+TestProtocolHandlerFactory.prototype = {
+ createInstance(iid) {
+ return new TestProtocolHandler().QueryInterface(iid);
+ },
+};
+
+function register_test_protocol_handler() {
+ var reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ reg.registerFactory(
+ Components.ID("{4ea7dd3a-8cae-499c-9f18-e1de773ca25b}"),
+ "TestProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=moz-test",
+ new TestProtocolHandlerFactory()
+ );
+}
+
+function check_proxy(pi, type, host, port, flags, timeout, hasNext) {
+ Assert.notEqual(pi, null);
+ Assert.equal(pi.type, type);
+ Assert.equal(pi.host, host);
+ Assert.equal(pi.port, port);
+ if (flags != -1) {
+ Assert.equal(pi.flags, flags);
+ }
+ if (timeout != -1) {
+ Assert.equal(pi.failoverTimeout, timeout);
+ }
+ if (hasNext) {
+ Assert.notEqual(pi.failoverProxy, null);
+ } else {
+ Assert.equal(pi.failoverProxy, null);
+ }
+}
+
+const SYNC = 0;
+const THROW = 1;
+const ASYNC = 2;
+
+function TestFilter(type, host, port, flags, timeout, result) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this._timeout = timeout;
+ this._result = result;
+}
+TestFilter.prototype = {
+ _type: "",
+ _host: "",
+ _port: -1,
+ _flags: 0,
+ _timeout: 0,
+ _async: false,
+ _throwing: false,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyFilter"]),
+
+ applyFilter(uri, pi, cb) {
+ if (this._result == THROW) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ var pi_tail = pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ "",
+ "",
+ this._flags,
+ this._timeout,
+ null
+ );
+ if (pi) {
+ pi.failoverProxy = pi_tail;
+ } else {
+ pi = pi_tail;
+ }
+
+ if (this._result == ASYNC) {
+ executeSoon(() => {
+ cb.onProxyFilterResult(pi);
+ });
+ } else {
+ cb.onProxyFilterResult(pi);
+ }
+ },
+};
+
+function resolveCallback() {}
+resolveCallback.prototype = {
+ nextFunction: null,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyCallback"]),
+
+ onProxyAvailable(req, channel, pi, status) {
+ this.nextFunction(pi);
+ },
+};
+
+// ==============================================================
+
+var filter1;
+var filter2;
+var filter3;
+
+function run_filter_test1() {
+ filter1 = new TestFilter("http", "foo", 8080, 0, 10, ASYNC);
+ filter2 = new TestFilter("http", "bar", 8090, 0, 10, ASYNC);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter2);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_2(pi) {
+ check_proxy(pi, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_3;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_3(pi) {
+ Assert.equal(pi, null);
+ run_filter2_sync_async();
+}
+
+function run_filter2_sync_async() {
+ filter1 = new TestFilter("http", "foo", 8080, 0, 10, SYNC);
+ filter2 = new TestFilter("http", "bar", 8090, 0, 10, ASYNC);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test2_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test2_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+
+ run_filter3_async_sync();
+}
+
+function run_filter3_async_sync() {
+ filter1 = new TestFilter("http", "foo", 8080, 0, 10, ASYNC);
+ filter2 = new TestFilter("http", "bar", 8090, 0, 10, SYNC);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test3_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test3_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+
+ run_filter4_throwing_sync_sync();
+}
+
+function run_filter4_throwing_sync_sync() {
+ filter1 = new TestFilter("", "", 0, 0, 0, THROW);
+ filter2 = new TestFilter("http", "foo", 8080, 0, 10, SYNC);
+ filter3 = new TestFilter("http", "bar", 8090, 0, 10, SYNC);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+ pps.registerFilter(filter3, 5);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test4_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla2.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test4_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+ pps.unregisterFilter(filter3);
+
+ run_filter5_sync_sync_throwing();
+}
+
+function run_filter5_sync_sync_throwing() {
+ filter1 = new TestFilter("http", "foo", 8080, 0, 10, SYNC);
+ filter2 = new TestFilter("http", "bar", 8090, 0, 10, SYNC);
+ filter3 = new TestFilter("", "", 0, 0, 0, THROW);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+ pps.registerFilter(filter3, 5);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test5_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test5_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+ pps.unregisterFilter(filter3);
+
+ run_filter5_2_throwing_async_async();
+}
+
+function run_filter5_2_throwing_async_async() {
+ filter1 = new TestFilter("", "", 0, 0, 0, THROW);
+ filter2 = new TestFilter("http", "foo", 8080, 0, 10, ASYNC);
+ filter3 = new TestFilter("http", "bar", 8090, 0, 10, ASYNC);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+ pps.registerFilter(filter3, 5);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test5_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test5_2(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+ pps.unregisterFilter(filter3);
+
+ run_filter6_async_async_throwing();
+}
+
+function run_filter6_async_async_throwing() {
+ filter1 = new TestFilter("http", "foo", 8080, 0, 10, ASYNC);
+ filter2 = new TestFilter("http", "bar", 8090, 0, 10, ASYNC);
+ filter3 = new TestFilter("", "", 0, 0, 0, THROW);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+ pps.registerFilter(filter3, 5);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test6_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test6_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+ pps.unregisterFilter(filter3);
+
+ run_filter7_async_throwing_async();
+}
+
+function run_filter7_async_throwing_async() {
+ filter1 = new TestFilter("http", "foo", 8080, 0, 10, ASYNC);
+ filter2 = new TestFilter("", "", 0, 0, 0, THROW);
+ filter3 = new TestFilter("http", "bar", 8090, 0, 10, ASYNC);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+ pps.registerFilter(filter3, 5);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test7_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test7_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+ pps.unregisterFilter(filter3);
+
+ run_filter8_sync_throwing_sync();
+}
+
+function run_filter8_sync_throwing_sync() {
+ filter1 = new TestFilter("http", "foo", 8080, 0, 10, SYNC);
+ filter2 = new TestFilter("", "", 0, 0, 0, THROW);
+ filter3 = new TestFilter("http", "bar", 8090, 0, 10, SYNC);
+ pps.registerFilter(filter1, 20);
+ pps.registerFilter(filter2, 10);
+ pps.registerFilter(filter3, 5);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test8_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test8_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter1);
+ pps.unregisterFilter(filter2);
+ pps.unregisterFilter(filter3);
+
+ run_filter9_throwing();
+}
+
+function run_filter9_throwing() {
+ filter1 = new TestFilter("", "", 0, 0, 0, THROW);
+ pps.registerFilter(filter1, 20);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test9_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test9_1(pi) {
+ Assert.equal(pi, null);
+ do_test_finished();
+}
+
+// =========================================
+
+function run_test() {
+ register_test_protocol_handler();
+
+ // start of asynchronous test chain
+ run_filter_test1();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_protocolproxyservice.js b/netwerk/test/unit/test_protocolproxyservice.js
new file mode 100644
index 0000000000..64472651f6
--- /dev/null
+++ b/netwerk/test/unit/test_protocolproxyservice.js
@@ -0,0 +1,1071 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This testcase exercises the Protocol Proxy Service
+
+// These are the major sub tests:
+// run_filter_test();
+// run_filter_test2()
+// run_filter_test3()
+// run_pref_test();
+// run_pac_test();
+// run_pac_cancel_test();
+// run_proxy_host_filters_test();
+// run_myipaddress_test();
+// run_failed_script_test();
+// run_isresolvable_test();
+
+"use strict";
+
+var ios = Services.io;
+var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+var prefs = Services.prefs;
+var again = true;
+
+/**
+ * Test nsIProtocolHandler that allows proxying, but doesn't allow HTTP
+ * proxying.
+ */
+function TestProtocolHandler() {}
+TestProtocolHandler.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler"]),
+ scheme: "moz-test",
+ defaultPort: -1,
+ protocolFlags:
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.ALLOWS_PROXY |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
+ newChannel(uri, aLoadInfo) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ allowPort(port, scheme) {
+ return true;
+ },
+};
+
+function TestProtocolHandlerFactory() {}
+TestProtocolHandlerFactory.prototype = {
+ createInstance(iid) {
+ return new TestProtocolHandler().QueryInterface(iid);
+ },
+};
+
+function register_test_protocol_handler() {
+ var reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ reg.registerFactory(
+ Components.ID("{4ea7dd3a-8cae-499c-9f18-e1de773ca25b}"),
+ "TestProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=moz-test",
+ new TestProtocolHandlerFactory()
+ );
+}
+
+function check_proxy(pi, type, host, port, flags, timeout, hasNext) {
+ Assert.notEqual(pi, null);
+ Assert.equal(pi.type, type);
+ Assert.equal(pi.host, host);
+ Assert.equal(pi.port, port);
+ if (flags != -1) {
+ Assert.equal(pi.flags, flags);
+ }
+ if (timeout != -1) {
+ Assert.equal(pi.failoverTimeout, timeout);
+ }
+ if (hasNext) {
+ Assert.notEqual(pi.failoverProxy, null);
+ } else {
+ Assert.equal(pi.failoverProxy, null);
+ }
+}
+
+function TestFilter(type, host, port, flags, timeout) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this._timeout = timeout;
+}
+TestFilter.prototype = {
+ _type: "",
+ _host: "",
+ _port: -1,
+ _flags: 0,
+ _timeout: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyFilter"]),
+ applyFilter(uri, pi, cb) {
+ var pi_tail = pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ "",
+ "",
+ this._flags,
+ this._timeout,
+ null
+ );
+ if (pi) {
+ pi.failoverProxy = pi_tail;
+ } else {
+ pi = pi_tail;
+ }
+ cb.onProxyFilterResult(pi);
+ },
+};
+
+function BasicFilter() {}
+BasicFilter.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyFilter"]),
+ applyFilter(uri, pi, cb) {
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ "http",
+ "localhost",
+ 8080,
+ "",
+ "",
+ 0,
+ 10,
+ pps.newProxyInfo("direct", "", -1, "", "", 0, 0, null)
+ )
+ );
+ },
+};
+
+function BasicChannelFilter() {}
+BasicChannelFilter.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyChannelFilter"]),
+ applyFilter(channel, pi, cb) {
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ "http",
+ channel.URI.host,
+ 7777,
+ "",
+ "",
+ 0,
+ 10,
+ pps.newProxyInfo("direct", "", -1, "", "", 0, 0, null)
+ )
+ );
+ },
+};
+
+function resolveCallback() {}
+resolveCallback.prototype = {
+ nextFunction: null,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyCallback"]),
+
+ onProxyAvailable(req, channel, pi, status) {
+ this.nextFunction(pi);
+ },
+};
+
+function run_filter_test() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Verify initial state
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_1;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+var filter01;
+var filter02;
+
+function filter_test0_1(pi) {
+ Assert.equal(pi, null);
+
+ // Push a filter and verify the results
+
+ filter01 = new BasicFilter();
+ filter02 = new BasicFilter();
+ pps.registerFilter(filter01, 10);
+ pps.registerFilter(filter02, 20);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test0_2(pi) {
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ pps.unregisterFilter(filter02);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_3;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test0_3(pi) {
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ // Remove filter and verify that we return to the initial state
+
+ pps.unregisterFilter(filter01);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_4;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+var filter03;
+
+function filter_test0_4(pi) {
+ Assert.equal(pi, null);
+ filter03 = new BasicChannelFilter();
+ pps.registerChannelFilter(filter03, 10);
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_5;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test0_5(pi) {
+ pps.unregisterChannelFilter(filter03);
+ check_proxy(pi, "http", "www.mozilla.org", 7777, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+ run_filter_test_uri();
+}
+
+function run_filter_test_uri() {
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_1;
+ var uri = ios.newURI("http://www.mozilla.org/");
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_1(pi) {
+ Assert.equal(pi, null);
+
+ // Push a filter and verify the results
+
+ filter01 = new BasicFilter();
+ filter02 = new BasicFilter();
+ pps.registerFilter(filter01, 10);
+ pps.registerFilter(filter02, 20);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_2;
+ var uri = ios.newURI("http://www.mozilla.org/");
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_2(pi) {
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ pps.unregisterFilter(filter02);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_3;
+ var uri = ios.newURI("http://www.mozilla.org/");
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_3(pi) {
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ // Remove filter and verify that we return to the initial state
+
+ pps.unregisterFilter(filter01);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_4;
+ var uri = ios.newURI("http://www.mozilla.org/");
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_4(pi) {
+ Assert.equal(pi, null);
+ run_filter_test2();
+}
+
+var filter11;
+var filter12;
+
+function run_filter_test2() {
+ // Push a filter and verify the results
+
+ filter11 = new TestFilter("http", "foo", 8080, 0, 10);
+ filter12 = new TestFilter("http", "bar", 8090, 0, 10);
+ pps.registerFilter(filter11, 20);
+ pps.registerFilter(filter12, 10);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter12);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_2(pi) {
+ check_proxy(pi, "http", "foo", 8080, 0, 10, false);
+
+ // Remove filter and verify that we return to the initial state
+
+ pps.unregisterFilter(filter11);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_3;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_3(pi) {
+ Assert.equal(pi, null);
+ run_filter_test3();
+}
+
+var filter_3_1;
+
+function run_filter_test3() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ // Push a filter and verify the results asynchronously
+
+ filter_3_1 = new TestFilter("http", "foo", 8080, 0, 10);
+ pps.registerFilter(filter_3_1, 20);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test3_1;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test3_1(pi) {
+ check_proxy(pi, "http", "foo", 8080, 0, 10, false);
+ pps.unregisterFilter(filter_3_1);
+ run_pref_test();
+}
+
+function run_pref_test() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ // Verify 'direct' setting
+
+ prefs.setIntPref("network.proxy.type", 0);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_1;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_1(pi) {
+ Assert.equal(pi, null);
+
+ // Verify 'manual' setting
+ prefs.setIntPref("network.proxy.type", 1);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_2(pi) {
+ // nothing yet configured
+ Assert.equal(pi, null);
+
+ // try HTTP configuration
+ prefs.setCharPref("network.proxy.http", "foopy");
+ prefs.setIntPref("network.proxy.http_port", 8080);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_3;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_3(pi) {
+ check_proxy(pi, "http", "foopy", 8080, 0, -1, false);
+
+ prefs.setCharPref("network.proxy.http", "");
+ prefs.setIntPref("network.proxy.http_port", 0);
+
+ // try SOCKS configuration
+ prefs.setCharPref("network.proxy.socks", "barbar");
+ prefs.setIntPref("network.proxy.socks_port", 1203);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_4;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_4(pi) {
+ check_proxy(pi, "socks", "barbar", 1203, 0, -1, false);
+ run_pac_test();
+}
+
+function TestResolveCallback(type, nexttest) {
+ this.type = type;
+ this.nexttest = nexttest;
+}
+TestResolveCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyCallback"]),
+
+ onProxyAvailable: function TestResolveCallback_onProxyAvailable(
+ req,
+ channel,
+ pi,
+ status
+ ) {
+ dump("*** channelURI=" + channel.URI.spec + ", status=" + status + "\n");
+
+ if (this.type == null) {
+ Assert.equal(pi, null);
+ } else {
+ Assert.notEqual(req, null);
+ Assert.notEqual(channel, null);
+ Assert.equal(status, 0);
+ Assert.notEqual(pi, null);
+ check_proxy(pi, this.type, "foopy", 8080, 0, -1, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, -1, -1, false);
+ }
+
+ this.nexttest();
+ },
+};
+
+var originalTLSProxy;
+
+function run_pac_test() {
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' return "PROXY foopy:8080; DIRECT";' +
+ "}";
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ // Configure PAC
+
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ pps.asyncResolve(channel, 0, new TestResolveCallback("http", run_pac2_test));
+}
+
+function run_pac2_test() {
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' return "HTTPS foopy:8080; DIRECT";' +
+ "}";
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ // Configure PAC
+ originalTLSProxy = prefs.getBoolPref("network.proxy.proxy_over_tls");
+
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ prefs.setBoolPref("network.proxy.proxy_over_tls", true);
+
+ pps.asyncResolve(channel, 0, new TestResolveCallback("https", run_pac3_test));
+}
+
+function run_pac3_test() {
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' return "HTTPS foopy:8080; DIRECT";' +
+ "}";
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ // Configure PAC
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ prefs.setBoolPref("network.proxy.proxy_over_tls", false);
+
+ pps.asyncResolve(channel, 0, new TestResolveCallback(null, run_pac4_test));
+}
+
+function run_pac4_test() {
+ // Bug 1251332
+ let wRange = [
+ ["SUN", "MON", "SAT", "MON"], // for Sun
+ ["SUN", "TUE", "SAT", "TUE"], // for Mon
+ ["MON", "WED", "SAT", "WED"], // for Tue
+ ["TUE", "THU", "SAT", "THU"], // for Wed
+ ["WED", "FRI", "WED", "SUN"], // for Thu
+ ["THU", "SAT", "THU", "SUN"], // for Fri
+ ["FRI", "SAT", "FRI", "SUN"], // for Sat
+ ];
+ let today = new Date().getDay();
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' if (weekdayRange("' +
+ wRange[today][0] +
+ '", "' +
+ wRange[today][1] +
+ '") &&' +
+ ' weekdayRange("' +
+ wRange[today][2] +
+ '", "' +
+ wRange[today][3] +
+ '")) {' +
+ ' return "PROXY foopy:8080; DIRECT";' +
+ " }" +
+ "}";
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ // Configure PAC
+
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ pps.asyncResolve(
+ channel,
+ 0,
+ new TestResolveCallback("http", run_utf8_pac_test)
+ );
+}
+
+function run_utf8_pac_test() {
+ var pac =
+ "data:text/plain;charset=UTF-8," +
+ "function FindProxyForURL(url, host) {" +
+ " /*" +
+ " U+00A9 COPYRIGHT SIGN: %C2%A9," +
+ " U+0B87 TAMIL LETTER I: %E0%AE%87," +
+ " U+10398 UGARITIC LETTER THANNA: %F0%90%8E%98 " +
+ " */" +
+ ' var multiBytes = "%C2%A9 %E0%AE%87 %F0%90%8E%98"; ' +
+ " /* 6 UTF-16 units above if PAC script run as UTF-8; 11 units if run as Latin-1 */ " +
+ " return multiBytes.length === 6 " +
+ ' ? "PROXY foopy:8080; DIRECT" ' +
+ ' : "PROXY epicfail-utf8:12345; DIRECT";' +
+ "}";
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Configure PAC
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ pps.asyncResolve(
+ channel,
+ 0,
+ new TestResolveCallback("http", run_latin1_pac_test)
+ );
+}
+
+function run_latin1_pac_test() {
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ " /* A too-long encoding of U+0000, so not valid UTF-8 */ " +
+ ' var multiBytes = "%C0%80"; ' +
+ " /* 2 UTF-16 units because interpreted as Latin-1 */ " +
+ " return multiBytes.length === 2 " +
+ ' ? "PROXY foopy:8080; DIRECT" ' +
+ ' : "PROXY epicfail-latin1:12345; DIRECT";' +
+ "}";
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Configure PAC
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ pps.asyncResolve(
+ channel,
+ 0,
+ new TestResolveCallback("http", finish_pac_test)
+ );
+}
+
+function finish_pac_test() {
+ prefs.setBoolPref("network.proxy.proxy_over_tls", originalTLSProxy);
+ run_pac_cancel_test();
+}
+
+function TestResolveCancelationCallback() {}
+TestResolveCancelationCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyCallback"]),
+
+ onProxyAvailable: function TestResolveCancelationCallback_onProxyAvailable(
+ req,
+ channel,
+ pi,
+ status
+ ) {
+ dump("*** channelURI=" + channel.URI.spec + ", status=" + status + "\n");
+
+ Assert.notEqual(req, null);
+ Assert.notEqual(channel, null);
+ Assert.equal(status, Cr.NS_ERROR_ABORT);
+ Assert.equal(pi, null);
+
+ prefs.setCharPref("network.proxy.autoconfig_url", "");
+ prefs.setIntPref("network.proxy.type", 0);
+
+ run_proxy_host_filters_test();
+ },
+};
+
+function run_pac_cancel_test() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ // Configure PAC
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' return "PROXY foopy:8080; DIRECT";' +
+ "}";
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var req = pps.asyncResolve(channel, 0, new TestResolveCancelationCallback());
+ req.cancel(Cr.NS_ERROR_ABORT);
+}
+
+var hostList;
+var hostIDX;
+var bShouldBeFiltered;
+var hostNextFX;
+
+function check_host_filters(hl, shouldBe, nextFX) {
+ hostList = hl;
+ hostIDX = 0;
+ bShouldBeFiltered = shouldBe;
+ hostNextFX = nextFX;
+
+ if (hostList.length > hostIDX) {
+ check_host_filter(hostIDX);
+ }
+}
+
+function check_host_filters_cb() {
+ hostIDX++;
+ if (hostList.length > hostIDX) {
+ check_host_filter(hostIDX);
+ } else {
+ hostNextFX();
+ }
+}
+
+function check_host_filter(i) {
+ dump(
+ "*** uri=" + hostList[i] + " bShouldBeFiltered=" + bShouldBeFiltered + "\n"
+ );
+ var channel = NetUtil.newChannel({
+ uri: hostList[i],
+ loadUsingSystemPrincipal: true,
+ });
+ var cb = new resolveCallback();
+ cb.nextFunction = host_filter_cb;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function host_filter_cb(proxy) {
+ if (bShouldBeFiltered) {
+ Assert.equal(proxy, null);
+ } else {
+ Assert.notEqual(proxy, null);
+ // Just to be sure, let's check that the proxy is correct
+ // - this should match the proxy setup in the calling function
+ check_proxy(proxy, "http", "foopy", 8080, 0, -1, false);
+ }
+ check_host_filters_cb();
+}
+
+// Verify that hists in the host filter list are not proxied
+// refers to "network.proxy.no_proxies_on"
+
+var uriStrUseProxyList;
+var hostFilterList;
+var uriStrFilterList;
+
+function run_proxy_host_filters_test() {
+ // Get prefs object from DOM
+ // Setup a basic HTTP proxy configuration
+ // - pps.resolve() needs this to return proxy info for non-filtered hosts
+ prefs.setIntPref("network.proxy.type", 1);
+ prefs.setCharPref("network.proxy.http", "foopy");
+ prefs.setIntPref("network.proxy.http_port", 8080);
+
+ // Setup host filter list string for "no_proxies_on"
+ hostFilterList =
+ "www.mozilla.org, www.google.com, www.apple.com, " +
+ ".domain, .domain2.org";
+ prefs.setCharPref("network.proxy.no_proxies_on", hostFilterList);
+ Assert.equal(
+ prefs.getCharPref("network.proxy.no_proxies_on"),
+ hostFilterList
+ );
+
+ // Check the hosts that should be filtered out
+ uriStrFilterList = [
+ "http://www.mozilla.org/",
+ "http://www.google.com/",
+ "http://www.apple.com/",
+ "http://somehost.domain/",
+ "http://someotherhost.domain/",
+ "http://somehost.domain2.org/",
+ "http://somehost.subdomain.domain2.org/",
+ ];
+ check_host_filters(uriStrFilterList, true, host_filters_1);
+}
+
+function host_filters_1() {
+ // Check the hosts that should be proxied
+ uriStrUseProxyList = [
+ "http://www.mozilla.com/",
+ "http://mail.google.com/",
+ "http://somehost.domain.co.uk/",
+ "http://somelocalhost/",
+ ];
+ check_host_filters(uriStrUseProxyList, false, host_filters_2);
+}
+
+function host_filters_2() {
+ // Set no_proxies_on to include local hosts
+ prefs.setCharPref(
+ "network.proxy.no_proxies_on",
+ hostFilterList + ", <local>"
+ );
+ Assert.equal(
+ prefs.getCharPref("network.proxy.no_proxies_on"),
+ hostFilterList + ", <local>"
+ );
+ // Amend lists - move local domain to filtered list
+ uriStrFilterList.push(uriStrUseProxyList.pop());
+ check_host_filters(uriStrFilterList, true, host_filters_3);
+}
+
+function host_filters_3() {
+ check_host_filters(uriStrUseProxyList, false, host_filters_4);
+}
+
+function host_filters_4() {
+ // Cleanup
+ prefs.setCharPref("network.proxy.no_proxies_on", "");
+ Assert.equal(prefs.getCharPref("network.proxy.no_proxies_on"), "");
+
+ run_myipaddress_test();
+}
+
+function run_myipaddress_test() {
+ // This test makes sure myIpAddress() comes up with some valid
+ // IP address other than localhost. The DUT must be configured with
+ // an Internet route for this to work - though no Internet traffic
+ // should be created.
+
+ var pac =
+ "data:text/plain," +
+ "var pacUseMultihomedDNS = true;\n" +
+ "function FindProxyForURL(url, host) {" +
+ ' return "PROXY " + myIpAddress() + ":1234";' +
+ "}";
+
+ // no traffic to this IP is ever sent, it is just a public IP that
+ // does not require DNS to determine a route.
+ var channel = NetUtil.newChannel({
+ uri: "http://192.0.43.10/",
+ loadUsingSystemPrincipal: true,
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = myipaddress_callback;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function myipaddress_callback(pi) {
+ Assert.notEqual(pi, null);
+ Assert.equal(pi.type, "http");
+ Assert.equal(pi.port, 1234);
+
+ // make sure we didn't return localhost
+ Assert.notEqual(pi.host, null);
+ Assert.notEqual(pi.host, "127.0.0.1");
+ Assert.notEqual(pi.host, "::1");
+
+ run_myipaddress_test_2();
+}
+
+function run_myipaddress_test_2() {
+ // test that myIPAddress() can be used outside of the scope of
+ // FindProxyForURL(). bug 829646.
+
+ var pac =
+ "data:text/plain," +
+ "var pacUseMultihomedDNS = true;\n" +
+ "var myaddr = myIpAddress(); " +
+ "function FindProxyForURL(url, host) {" +
+ ' return "PROXY " + myaddr + ":5678";' +
+ "}";
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = myipaddress2_callback;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function myipaddress2_callback(pi) {
+ Assert.notEqual(pi, null);
+ Assert.equal(pi.type, "http");
+ Assert.equal(pi.port, 5678);
+
+ // make sure we didn't return localhost
+ Assert.notEqual(pi.host, null);
+ Assert.notEqual(pi.host, "127.0.0.1");
+ Assert.notEqual(pi.host, "::1");
+
+ run_failed_script_test();
+}
+
+function run_failed_script_test() {
+ // test to make sure we go direct with invalid PAC
+ // eslint-disable-next-line no-useless-concat
+ var pac = "data:text/plain," + "\nfor(;\n";
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = failed_script_callback;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+var directFilter;
+const TEST_URI = "http://127.0.0.1:7247/";
+
+function failed_script_callback(pi) {
+ // we should go direct
+ Assert.equal(pi, null);
+
+ // test that we honor filters when configured to go direct
+ prefs.setIntPref("network.proxy.type", 0);
+ directFilter = new TestFilter("http", "127.0.0.1", 7246, 0, 0);
+ pps.registerFilter(directFilter, 10);
+
+ // test that on-modify-request contains the proxy info too
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.addObserver(directFilterListener, "http-on-modify-request");
+
+ var ssm = Services.scriptSecurityManager;
+ let uri = TEST_URI;
+ var chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: ssm.createContentPrincipal(Services.io.newURI(uri), {}),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ chan.asyncOpen(directFilterListener);
+}
+
+var directFilterListener = {
+ onModifyRequestCalled: false,
+
+ onStartRequest: function test_onStart(request) {},
+ onDataAvailable: function test_OnData() {},
+
+ onStopRequest: function test_onStop(request, status) {
+ // check on the PI from the channel itself
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ check_proxy(request.proxyInfo, "http", "127.0.0.1", 7246, 0, 0, false);
+ pps.unregisterFilter(directFilter);
+
+ // check on the PI from on-modify-request
+ Assert.ok(this.onModifyRequestCalled);
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.removeObserver(this, "http-on-modify-request");
+
+ run_isresolvable_test();
+ },
+
+ observe(subject, topic, data) {
+ if (
+ topic === "http-on-modify-request" &&
+ subject instanceof Ci.nsIHttpChannel &&
+ subject instanceof Ci.nsIProxiedChannel
+ ) {
+ info("check proxy in observe uri=" + subject.URI.spec);
+ if (subject.URI.spec != TEST_URI) {
+ return;
+ }
+ check_proxy(subject.proxyInfo, "http", "127.0.0.1", 7246, 0, 0, false);
+ this.onModifyRequestCalled = true;
+ }
+ },
+};
+
+function run_isresolvable_test() {
+ // test a non resolvable host in the pac file
+
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' if (isResolvable("nonexistant.lan.onion"))' +
+ ' return "DIRECT";' +
+ ' return "PROXY 127.0.0.1:1234";' +
+ "}";
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = isresolvable_callback;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function isresolvable_callback(pi) {
+ Assert.notEqual(pi, null);
+ Assert.equal(pi.type, "http");
+ Assert.equal(pi.port, 1234);
+ Assert.equal(pi.host, "127.0.0.1");
+
+ run_localhost_pac();
+}
+
+function run_localhost_pac() {
+ // test localhost in the pac file
+
+ var pac =
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {" +
+ ' return "PROXY totallycrazy:1234";' +
+ "}";
+
+ // Use default filter list string for "no_proxies_on" ("localhost, 127.0.0.1")
+ prefs.clearUserPref("network.proxy.no_proxies_on");
+ var channel = NetUtil.newChannel({
+ uri: "http://localhost/",
+ loadUsingSystemPrincipal: true,
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = localhost_callback;
+ pps.asyncResolve(channel, 0, cb);
+}
+
+function localhost_callback(pi) {
+ Assert.equal(pi, null); // no proxy!
+
+ prefs.setIntPref("network.proxy.type", 0);
+
+ if (mozinfo.socketprocess_networking && again) {
+ info("run test again");
+ again = false;
+ cleanUp();
+ prefs.setBoolPref("network.proxy.parse_pac_on_socket_process", true);
+ run_filter_test();
+ } else {
+ cleanUp();
+ do_test_finished();
+ }
+}
+
+function cleanUp() {
+ prefs.clearUserPref("network.proxy.type");
+ prefs.clearUserPref("network.proxy.http");
+ prefs.clearUserPref("network.proxy.http_port");
+ prefs.clearUserPref("network.proxy.socks");
+ prefs.clearUserPref("network.proxy.socks_port");
+ prefs.clearUserPref("network.proxy.autoconfig_url");
+ prefs.clearUserPref("network.proxy.proxy_over_tls");
+ prefs.clearUserPref("network.proxy.no_proxies_on");
+ prefs.clearUserPref("network.proxy.parse_pac_on_socket_process");
+}
+
+function run_test() {
+ register_test_protocol_handler();
+
+ prefs.setBoolPref("network.proxy.parse_pac_on_socket_process", false);
+ // start of asynchronous test chain
+ run_filter_test();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-failover_canceled.js b/netwerk/test/unit/test_proxy-failover_canceled.js
new file mode 100644
index 0000000000..e594c497cc
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_canceled.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ // we want to cancel the failover proxy engage, so, do not allow
+ // redirects from now.
+
+ var nc = new ChannelEventSink();
+ nc._flags = ES_ABORT_REDIRECT;
+
+ var prefs = Services.prefs.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref("no_proxies_on", "nothing");
+ prefs.setBoolPref("allow_hijacking_localhost", true);
+ prefs.setCharPref(
+ "autoconfig_url",
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY a_non_existent_domain_x7x6c572v:80; PROXY localhost:" +
+ httpServer.identity.primaryPort +
+ "';}"
+ );
+
+ var chan = make_channel(
+ "http://localhost:" + httpServer.identity.primaryPort + "/content"
+ );
+ chan.notificationCallbacks = nc;
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-failover_passing.js b/netwerk/test/unit/test_proxy-failover_passing.js
new file mode 100644
index 0000000000..a6fab56690
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_passing.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var prefs = Services.prefs.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref(
+ "autoconfig_url",
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY a_non_existent_domain_x7x6c572v:80; PROXY localhost:" +
+ httpServer.identity.primaryPort +
+ "';}"
+ );
+
+ var chan = make_channel(
+ "http://localhost:" + httpServer.identity.primaryPort + "/content"
+ );
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-replace_canceled.js b/netwerk/test/unit/test_proxy-replace_canceled.js
new file mode 100644
index 0000000000..b39d5e97f6
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_canceled.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var prefs = Services.prefs.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref(
+ "autoconfig_url",
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY localhost:" +
+ httpServer.identity.primaryPort +
+ "';}"
+ );
+
+ // this test assumed that a AsyncOnChannelRedirect query is made for
+ // each proxy failover or on the inital proxy only when PAC mode is used.
+ // Neither of those are documented anywhere that I can find and the latter
+ // hasn't been a useful property because it is PAC dependent and the type
+ // is generally unknown and OS driven. 769764 changed that to remove the
+ // internal redirect used to setup the initial proxy/channel as that isn't
+ // a redirect in any sense.
+
+ var chan = make_channel(
+ "http://localhost:" + httpServer.identity.primaryPort + "/content"
+ );
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-replace_passing.js b/netwerk/test/unit/test_proxy-replace_passing.js
new file mode 100644
index 0000000000..9c843c001e
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_passing.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var prefs = Services.prefs.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref(
+ "autoconfig_url",
+ "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY localhost:" +
+ httpServer.identity.primaryPort +
+ "';}"
+ );
+
+ var chan = make_channel(
+ "http://localhost:" + httpServer.identity.primaryPort + "/content"
+ );
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-slow-upload.js b/netwerk/test/unit/test_proxy-slow-upload.js
new file mode 100644
index 0000000000..1a406890e1
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-slow-upload.js
@@ -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/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+const SIZE = 4096;
+const CONTENT = "x".repeat(SIZE);
+
+add_task(async function test_slow_upload() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+ await server.registerPathHandler("/test", (req, resp) => {
+ let content = "";
+ req.on("data", data => {
+ global.data_count = (global.data_count || 0) + 1;
+ content += data;
+ });
+ req.on("end", () => {
+ resp.writeHead(200);
+ resp.end(content);
+ });
+ });
+
+ let sstream = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ sstream.data = CONTENT;
+
+ let mime = Cc[
+ "@mozilla.org/network/mime-input-stream;1"
+ ].createInstance(Ci.nsIMIMEInputStream);
+ mime.addHeader("Content-Type", "multipart/form-data; boundary=zzzzz");
+ mime.setData(sstream);
+
+ let tq = Cc["@mozilla.org/network/throttlequeue;1"].createInstance(
+ Ci.nsIInputChannelThrottleQueue
+ );
+ // Make sure the request takes more than one read.
+ tq.init(100 + SIZE / 2, 100 + SIZE / 2);
+
+ let chan = NetUtil.newChannel({
+ uri: `${server.origin()}/test`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ let tic = chan.QueryInterface(Ci.nsIThrottledInputChannel);
+ tic.throttleQueue = tq;
+
+ chan
+ .QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(mime, "", mime.available());
+ chan.requestMethod = "POST";
+
+ let { req, buff } = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req1, buff1) => resolve({ req: req1, buff: buff1 }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ ok(buff == CONTENT, "Content must match");
+ ok(!!req.QueryInterface(Ci.nsIProxiedChannel).proxyInfo);
+ greater(
+ await server.execute(`global.data_count`),
+ 1,
+ "Content should have been streamed to the server in several chunks"
+ );
+ }
+ );
+ await proxy.stop();
+ }
+});
diff --git a/netwerk/test/unit/test_proxy_cancel.js b/netwerk/test/unit/test_proxy_cancel.js
new file mode 100644
index 0000000000..d891f3147c
--- /dev/null
+++ b/netwerk/test/unit/test_proxy_cancel.js
@@ -0,0 +1,397 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals setTimeout */
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+add_task(async function test_cancel_after_asyncOpen() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ let chan = makeChan(`${server.origin()}/test`);
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+ }
+ );
+ await proxy.stop();
+ }
+});
+
+// const NS_NET_STATUS_CONNECTING_TO = 0x4b0007;
+// const NS_NET_STATUS_CONNECTED_TO = 0x4b0004;
+// const NS_NET_STATUS_SENDING_TO = 0x4b0005;
+const NS_NET_STATUS_WAITING_FOR = 0x4b000a; // 2152398858
+const NS_NET_STATUS_RECEIVING_FROM = 0x4b0006;
+// const NS_NET_STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c; // 2152398860
+// const NS_NET_STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d; // 2152398861
+
+add_task(async function test_cancel_after_connect_http2proxy() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ // Set up a proxy for each server to make sure proxy state is clean
+ // for each test.
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+ await proxy.execute(`
+ global.session_counter = 0;
+ global.proxy.on("session", () => {
+ global.session_counter++;
+ });
+ `);
+
+ info(`Testing ${proxy.constructor.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ global.reqCount = (global.reqCount || 0) + 1;
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ let chan = makeChan(`${server.origin()}/test`);
+ chan.notificationCallbacks = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIProgressEventSink",
+ ]),
+
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ onProgress(request, progress, progressMax) {},
+ onStatus(request, status, statusArg) {
+ info(`status = ${status}`);
+ // XXX(valentin): Is this the best status to be cancelling?
+ if (status == NS_NET_STATUS_WAITING_FOR) {
+ info("cancelling connected channel");
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ }
+ },
+ };
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+
+ // Since we're cancelling just after connect, we'd expect that no
+ // requests are actually registered. But because we're cancelling on the
+ // main thread, and the request is being performed on the socket thread,
+ // it might actually reach the server, especially in chaos test mode.
+ // Assert.equal(
+ // await server.execute(`global.reqCount || 0`),
+ // 0,
+ // `No requests should have been made at this point`
+ // );
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+
+ chan = makeChan(`${server.origin()}/test`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ // eslint-disable-next-line no-shadow
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+
+ // Check that there's still only one session.
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+ await proxy.stop();
+ }
+ );
+});
+
+add_task(async function test_cancel_after_sending_request() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ await proxy.execute(`
+ global.session_counter = 0;
+ global.proxy.on("session", () => {
+ global.session_counter++;
+ });
+ `);
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ // Here we simmulate a slow response to give the test time to
+ // cancel the channel before receiving the response.
+ global.request_count = (global.request_count || 0) + 1;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ }, 2000);
+ });
+ await server.registerPathHandler("/instant", (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ // It seems proxy.on("session") only gets emitted after a full request.
+ // So we first load a simple request, then the long lasting request
+ // that we then cancel before it has the chance to complete.
+ let chan = makeChan(`${server.origin()}/instant`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL)
+ );
+ });
+
+ chan = makeChan(`${server.origin()}/test`);
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+ // XXX(valentin) This might be a little racy
+ await TestUtils.waitForCondition(async () => {
+ return (await server.execute("global.request_count")) > 0;
+ });
+
+ chan.cancel(Cr.NS_ERROR_ABORT);
+
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+
+ async function checkSessionCounter() {
+ if (p.name == "NodeHTTP2ProxyServer") {
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+ }
+ }
+
+ await checkSessionCounter();
+
+ chan = makeChan(`${server.origin()}/instant`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ // eslint-disable-next-line no-shadow
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+ await checkSessionCounter();
+
+ await proxy.stop();
+ }
+ }
+ );
+});
+
+add_task(async function test_cancel_during_response() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ await proxy.execute(`
+ global.session_counter = 0;
+ global.proxy.on("session", () => {
+ global.session_counter++;
+ });
+ `);
+
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.write("a".repeat(1000));
+ // Here we send the response back in two chunks.
+ // The channel should be cancelled after the first one.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ resp.write("a".repeat(1000));
+ resp.end(global.server_name);
+ }, 2000);
+ });
+ await server.registerPathHandler("/instant", (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ let chan = makeChan(`${server.origin()}/test`);
+
+ chan.notificationCallbacks = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIProgressEventSink",
+ ]),
+
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ onProgress(request, progress, progressMax) {
+ info(`progress: ${progress}/${progressMax}`);
+ // Check that we never get more than 1000 bytes.
+ Assert.equal(progress, 1000);
+ },
+ onStatus(request, status, statusArg) {
+ if (status == NS_NET_STATUS_RECEIVING_FROM) {
+ info("cancelling when receiving request");
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ }
+ },
+ };
+
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+
+ async function checkSessionCounter() {
+ if (p.name == "NodeHTTP2ProxyServer") {
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+ }
+ }
+
+ await checkSessionCounter();
+
+ chan = makeChan(`${server.origin()}/instant`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ // eslint-disable-next-line no-shadow
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+
+ await checkSessionCounter();
+
+ await proxy.stop();
+ }
+ }
+ );
+});
diff --git a/netwerk/test/unit/test_proxy_pac.js b/netwerk/test/unit/test_proxy_pac.js
new file mode 100644
index 0000000000..343ad771fb
--- /dev/null
+++ b/netwerk/test/unit/test_proxy_pac.js
@@ -0,0 +1,126 @@
+// These are globlas defined for proxy servers, in ProxyAutoConfig.cpp. See
+// PACGlobalFunctions
+/* globals dnsResolve, alert */
+
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+class ConsoleListener {
+ messages = [];
+ observe(message) {
+ // Ignore unexpected messages.
+ if (!(message instanceof Ci.nsIConsoleMessage)) {
+ return;
+ }
+ if (!message.message.includes("PAC")) {
+ return;
+ }
+
+ this.messages.push(message.message);
+ }
+
+ register() {
+ Services.console.registerListener(this);
+ }
+
+ unregister() {
+ Services.console.unregisterListener(this);
+ }
+
+ clear() {
+ this.messages = [];
+ }
+}
+
+async function configurePac(fn) {
+ let pacURI = `data:application/x-ns-proxy-autoconfig;charset=utf-8,${encodeURIComponent(
+ fn.toString()
+ )}`;
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ Services.prefs.setStringPref("network.proxy.autoconfig_url", pacURI);
+
+ // Do a request so the PAC gets loaded
+ let chan = NetUtil.newChannel({
+ uri: `http://localhost:1234/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
+ );
+
+ await TestUtils.waitForCondition(
+ () =>
+ !!consoleListener.messages.filter(
+ e => e.includes("PAC file installed from"),
+ 0
+ ).length,
+ "Wait for PAC file to be installed."
+ );
+ consoleListener.clear();
+}
+
+let consoleListener = null;
+function setup() {
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ Services.prefs.setStringPref("network.proxy.autoconfig_url", "");
+ consoleListener = new ConsoleListener();
+ consoleListener.register();
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.autoconfig_url");
+ if (consoleListener) {
+ consoleListener.unregister();
+ consoleListener = null;
+ }
+ });
+}
+setup();
+
+// This method checks that calling dnsResult(null) does not result in
+// resolving the DNS name "null"
+add_task(async function test_bug1724345() {
+ consoleListener.clear();
+ // isInNet is defined by ascii_pac_utils.js which is included for proxies.
+ /* globals isInNet */
+ let pac = function FindProxyForURL(url, host) {
+ alert(`PAC resolving: ${host}`);
+ let destIP = dnsResolve(host);
+ alert(`PAC result: ${destIP}`);
+ alert(
+ `PAC isInNet: ${host} ${destIP} ${isInNet(
+ destIP,
+ "127.0.0.0",
+ "255.0.0.0"
+ )}`
+ );
+ return "DIRECT";
+ };
+
+ await configurePac(pac);
+
+ override.clearOverrides();
+ override.addIPOverride("example.org", "N/A");
+ override.addIPOverride("null", "127.0.0.1");
+ Services.dns.clearCache(true);
+
+ let chan = NetUtil.newChannel({
+ uri: `http://example.org:1234/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
+ );
+ ok(
+ !!consoleListener.messages.filter(e =>
+ e.includes("PAC isInNet: example.org null false")
+ ).length,
+ `should have proper result ${consoleListener.messages}`
+ );
+});
diff --git a/netwerk/test/unit/test_proxyconnect.js b/netwerk/test/unit/test_proxyconnect.js
new file mode 100644
index 0000000000..7015b3bffb
--- /dev/null
+++ b/netwerk/test/unit/test_proxyconnect.js
@@ -0,0 +1,360 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+// test_connectonly tests happy path of proxy connect
+// 1. CONNECT to localhost:socketserver_port
+// 2. Write 200 Connection established
+// 3. Write data to the tunnel (and read server-side)
+// 4. Read data from the tunnel (and write server-side)
+// 5. done
+// test_connectonly_noproxy tests an http channel with only connect set but
+// no proxy configured.
+// 1. OnTransportAvailable callback NOT called (checked in step 2)
+// 2. StopRequest callback called
+// 3. done
+// test_connectonly_nonhttp tests an http channel with only connect set with a
+// non-http proxy.
+// 1. OnTransportAvailable callback NOT called (checked in step 2)
+// 2. StopRequest callback called
+// 3. done
+
+// -1 then initialized with an actual port from the serversocket
+"use strict";
+
+var socketserver_port = -1;
+
+const CC = Components.Constructor;
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+const BinaryOutputStream = CC(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+const STATE_NONE = 0;
+const STATE_READ_CONNECT_REQUEST = 1;
+const STATE_WRITE_CONNECTION_ESTABLISHED = 2;
+const STATE_CHECK_WRITE = 3; // write to the tunnel
+const STATE_CHECK_WRITE_READ = 4; // wrote to the tunnel, check connection data
+const STATE_CHECK_READ = 5; // read from the tunnel
+const STATE_CHECK_READ_WROTE = 6; // wrote to connection, check tunnel data
+const STATE_COMPLETED = 100;
+
+const CONNECT_RESPONSE_STRING = "HTTP/1.1 200 Connection established\r\n\r\n";
+const CHECK_WRITE_STRING = "hello";
+const CHECK_READ_STRING = "world";
+const ALPN = "webrtc";
+
+var connectRequest = "";
+var checkWriteData = "";
+var checkReadData = "";
+
+var threadManager;
+var socket;
+var streamIn;
+var streamOut;
+var accepted = false;
+var acceptedSocket;
+var state = STATE_NONE;
+var transportAvailable = false;
+var proxiedChannel;
+var listener = {
+ expectedCode: -1, // uninitialized
+
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ if (state === STATE_COMPLETED) {
+ Assert.equal(transportAvailable, false, "transport available not called");
+ Assert.equal(status, 0x80004005, "error code matches");
+ Assert.equal(proxiedChannel.httpProxyConnectResponseCode, 200);
+ nextTest();
+ return;
+ }
+
+ Assert.equal(accepted, true, "socket accepted");
+ accepted = false;
+ },
+};
+
+var upgradeListener = {
+ onTransportAvailable: (transport, socketIn, socketOut) => {
+ if (!transport || !socketIn || !socketOut) {
+ do_throw("on transport available failed");
+ }
+
+ if (state !== STATE_CHECK_WRITE) {
+ do_throw("bad state");
+ }
+
+ transportAvailable = true;
+
+ socketIn.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ socketOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIHttpUpgradeListener"]),
+};
+
+var connectHandler = {
+ onInputStreamReady: input => {
+ try {
+ const bis = new BinaryInputStream(input);
+ var data = bis.readByteArray(input.available());
+
+ dataAvailable(data);
+
+ if (state !== STATE_COMPLETED) {
+ input.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ }
+ } catch (e) {
+ do_throw(e);
+ }
+ },
+ onOutputStreamReady: output => {
+ writeData(output);
+ },
+ QueryInterface: iid => {
+ if (
+ iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback)
+ ) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+};
+
+function dataAvailable(data) {
+ switch (state) {
+ case STATE_READ_CONNECT_REQUEST:
+ connectRequest += String.fromCharCode.apply(String, data);
+ const headerEnding = connectRequest.indexOf("\r\n\r\n");
+ const alpnHeaderIndex = connectRequest.indexOf(`ALPN: ${ALPN}`);
+
+ if (headerEnding != -1) {
+ const requestLine = `CONNECT localhost:${socketserver_port} HTTP/1.1`;
+ Assert.equal(connectRequest.indexOf(requestLine), 0, "connect request");
+ Assert.equal(headerEnding, connectRequest.length - 4, "req head only");
+ Assert.notEqual(alpnHeaderIndex, -1, "alpn header found");
+
+ state = STATE_WRITE_CONNECTION_ESTABLISHED;
+ streamOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ }
+
+ break;
+ case STATE_CHECK_WRITE_READ:
+ checkWriteData += String.fromCharCode.apply(String, data);
+
+ if (checkWriteData.length >= CHECK_WRITE_STRING.length) {
+ Assert.equal(checkWriteData, CHECK_WRITE_STRING, "correct write data");
+
+ state = STATE_CHECK_READ;
+ streamOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ }
+
+ break;
+ case STATE_CHECK_READ_WROTE:
+ checkReadData += String.fromCharCode.apply(String, data);
+
+ if (checkReadData.length >= CHECK_READ_STRING.length) {
+ Assert.equal(checkReadData, CHECK_READ_STRING, "correct read data");
+
+ state = STATE_COMPLETED;
+
+ streamIn.asyncWait(null, 0, 0, null);
+ acceptedSocket.close(0);
+
+ nextTest();
+ }
+
+ break;
+ default:
+ do_throw("bad state: " + state);
+ }
+}
+
+function writeData(output) {
+ let bos = new BinaryOutputStream(output);
+
+ switch (state) {
+ case STATE_WRITE_CONNECTION_ESTABLISHED:
+ bos.write(CONNECT_RESPONSE_STRING, CONNECT_RESPONSE_STRING.length);
+ state = STATE_CHECK_WRITE;
+ break;
+ case STATE_CHECK_READ:
+ bos.write(CHECK_READ_STRING, CHECK_READ_STRING.length);
+ state = STATE_CHECK_READ_WROTE;
+ break;
+ case STATE_CHECK_WRITE:
+ bos.write(CHECK_WRITE_STRING, CHECK_WRITE_STRING.length);
+ state = STATE_CHECK_WRITE_READ;
+ break;
+ default:
+ do_throw("bad state: " + state);
+ }
+}
+
+function makeChan(url) {
+ if (!url) {
+ url = "https://localhost:" + socketserver_port + "/";
+ }
+
+ var flags =
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL |
+ Ci.nsILoadInfo.SEC_DONT_FOLLOW_REDIRECTS |
+ Ci.nsILoadInfo.SEC_COOKIES_OMIT;
+
+ var chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ securityFlags: flags,
+ });
+ chan = chan.QueryInterface(Ci.nsIHttpChannel);
+
+ var internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.HTTPUpgrade(ALPN, upgradeListener);
+ internal.setConnectOnly();
+
+ return chan;
+}
+
+function socketAccepted(socket1, transport) {
+ accepted = true;
+
+ // copied from httpd.js
+ const SEGMENT_SIZE = 8192;
+ const SEGMENT_COUNT = 1024;
+
+ switch (state) {
+ case STATE_NONE:
+ state = STATE_READ_CONNECT_REQUEST;
+ break;
+ default:
+ return;
+ }
+
+ acceptedSocket = transport;
+
+ try {
+ streamIn = transport
+ .openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ streamOut = transport
+ .openOutputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncOutputStream);
+
+ streamIn.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ } catch (e) {
+ transport.close(Cr.NS_BINDING_ABORTED);
+ do_throw(e);
+ }
+}
+
+function stopListening() {
+ if (tests && tests.length !== 0 && do_throw) {
+ do_throw("should never stop");
+ }
+}
+
+function createProxy() {
+ try {
+ threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ socket = new ServerSocket(-1, true, 1);
+ socketserver_port = socket.port;
+
+ socket.asyncListen({
+ onSocketAccepted: socketAccepted,
+ onStopListening: stopListening,
+ });
+ } catch (e) {
+ do_throw(e);
+ }
+}
+
+function test_connectonly() {
+ Services.prefs.setCharPref("network.proxy.ssl", "localhost");
+ Services.prefs.setIntPref("network.proxy.ssl_port", socketserver_port);
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ Services.prefs.setIntPref("network.proxy.type", 1);
+
+ var chan = makeChan();
+ proxiedChannel = chan.QueryInterface(Ci.nsIProxiedChannel);
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_connectonly_noproxy() {
+ clearPrefs();
+ var chan = makeChan();
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_connectonly_nonhttp() {
+ clearPrefs();
+
+ Services.prefs.setCharPref("network.proxy.socks", "localhost");
+ Services.prefs.setIntPref("network.proxy.socks_port", socketserver_port);
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ Services.prefs.setIntPref("network.proxy.type", 1);
+
+ var chan = makeChan();
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function nextTest() {
+ transportAvailable = false;
+
+ if (!tests.length) {
+ do_test_finished();
+ return;
+ }
+
+ tests.shift()();
+ do_test_finished();
+}
+
+var tests = [
+ test_connectonly,
+ test_connectonly_noproxy,
+ test_connectonly_nonhttp,
+];
+
+function clearPrefs() {
+ Services.prefs.clearUserPref("network.proxy.ssl");
+ Services.prefs.clearUserPref("network.proxy.ssl_port");
+ Services.prefs.clearUserPref("network.proxy.socks");
+ Services.prefs.clearUserPref("network.proxy.socks_port");
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ Services.prefs.clearUserPref("network.proxy.type");
+}
+
+function run_test() {
+ createProxy();
+
+ registerCleanupFunction(clearPrefs);
+
+ nextTest();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_psl.js b/netwerk/test/unit/test_psl.js
new file mode 100644
index 0000000000..d9c0c2965d
--- /dev/null
+++ b/netwerk/test/unit/test_psl.js
@@ -0,0 +1,39 @@
+"use strict";
+
+var idna = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+);
+
+function run_test() {
+ var file = do_get_file("data/test_psl.txt");
+ var uri = Services.io.newFileURI(file);
+ var srvScope = {};
+ Services.scriptloader.loadSubScript(uri.spec, srvScope);
+}
+
+// Exported to the loaded script
+/* exported checkPublicSuffix */
+function checkPublicSuffix(host, expectedSuffix) {
+ var actualSuffix = null;
+ try {
+ actualSuffix = Services.eTLD.getBaseDomainFromHost(host);
+ } catch (e) {
+ if (
+ e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS &&
+ e.result != Cr.NS_ERROR_ILLEGAL_VALUE
+ ) {
+ throw e;
+ }
+ }
+ // The EffectiveTLDService always gives back punycoded labels.
+ // The test suite wants to get back what it put in.
+ if (
+ actualSuffix !== null &&
+ expectedSuffix !== null &&
+ /(^|\.)xn--/.test(actualSuffix) &&
+ !/(^|\.)xn--/.test(expectedSuffix)
+ ) {
+ actualSuffix = idna.convertACEtoUTF8(actualSuffix);
+ }
+ Assert.equal(actualSuffix, expectedSuffix);
+}
diff --git a/netwerk/test/unit/test_race_cache_with_network.js b/netwerk/test/unit/test_race_cache_with_network.js
new file mode 100644
index 0000000000..3e34c6af46
--- /dev/null
+++ b/netwerk/test/unit/test_race_cache_with_network.js
@@ -0,0 +1,273 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+let gResponseBody = "blahblah";
+let g200Counter = 0;
+let g304Counter = 0;
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("ETag", "test-etag1");
+
+ let etag;
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+
+ if (etag == "test-etag1") {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ g304Counter++;
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
+ g200Counter++;
+ }
+}
+
+function cached_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "max-age=3600");
+ response.setHeader("ETag", "test-etag1");
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
+
+ g200Counter++;
+}
+
+let gResponseCounter = 0;
+let gIsFromCache = 0;
+function checkContent(request, buffer, context, isFromCache) {
+ Assert.equal(buffer, gResponseBody);
+ info(
+ "isRacing: " +
+ request.QueryInterface(Ci.nsICacheInfoChannel).isRacing() +
+ "\n"
+ );
+ gResponseCounter++;
+ if (isFromCache) {
+ gIsFromCache++;
+ }
+ executeSoon(() => {
+ testGenerator.next();
+ });
+}
+
+function run_test() {
+ do_get_profile();
+ // In this test, we manually use |TriggerNetwork| to prove we could send
+ // net and cache reqeust simultaneously. Therefore we should disable
+ // racing in the HttpChannel first.
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+ httpserver.registerPathHandler("/rcwn", test_handler);
+ httpserver.registerPathHandler("/rcwn_cached", cached_handler);
+ testGenerator.next();
+ do_test_pending();
+}
+
+let testGenerator = testSteps();
+function* testSteps() {
+ /*
+ * In this test, we have a relatively low timeout of 200ms and an assertion that
+ * the timer works properly by checking that the time was greater than 200ms.
+ * With a timer precision of 100ms (for example) we will clamp downwards to 200
+ * and cause the assertion to fail. To resolve this, we hardcode a precision of
+ * 20ms.
+ */
+ Services.prefs.setBoolPref("privacy.reduceTimerPrecision", true);
+ Services.prefs.setIntPref(
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ 20000
+ );
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.reduceTimerPrecision");
+ Services.prefs.clearUserPref(
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
+ );
+ });
+
+ // Initial request. Stores the response in the cache.
+ let channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 1);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 0, "check number of 304 responses");
+
+ // Checks that response is returned from the cache, after a 304 response.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 2);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 1, "check number of 304 responses");
+
+ // Checks that delaying the response from the cache works.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(200);
+ let startTime = Date.now();
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ greaterOrEqual(
+ Date.now() - startTime,
+ 200,
+ "Check that timer works properly"
+ );
+ equal(gResponseCounter, 3);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 2, "check number of 304 responses");
+
+ // Checks that we can trigger the cache open immediately, even if the cache delay is set very high.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ do_timeout(50, function () {
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_triggerDelayedOpenCacheEntry();
+ });
+ yield undefined;
+ equal(gResponseCounter, 4);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 3, "check number of 304 responses");
+
+ // Sets a high delay for the cache fetch, and triggers the network activity.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ // Trigger network after 50 ms.
+ yield undefined;
+ equal(gResponseCounter, 5);
+ equal(g200Counter, 2, "check number of 200 responses");
+ equal(g304Counter, 3, "check number of 304 responses");
+
+ // Sets a high delay for the cache fetch, and triggers the network activity.
+ // While the network response is produced, we trigger the cache fetch.
+ // Because the network response was the first, a non-conditional request is sent.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 6);
+ equal(g200Counter, 3, "check number of 200 responses");
+ equal(g304Counter, 3, "check number of 304 responses");
+
+ // Triggers cache open before triggering network.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(5000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_triggerDelayedOpenCacheEntry();
+ yield undefined;
+ equal(gResponseCounter, 7);
+ equal(
+ g200Counter,
+ 3,
+ `check number of 200 responses | 200: ${g200Counter}, 304: ${g304Counter}`
+ );
+ equal(
+ g304Counter,
+ 4,
+ `check number of 304 responses | 200: ${g200Counter}, 304: ${g304Counter}`
+ );
+
+ // Load the cached handler so we don't need to revalidate
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 8);
+ equal(g200Counter, 4, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Make sure response is loaded from the cache, not the network
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 9);
+ equal(g200Counter, 4, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Cache times out, so we trigger the network
+ gIsFromCache = 0;
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ // trigger network after 50 ms
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 10);
+ equal(gIsFromCache, 0, "should be from the network");
+ equal(g200Counter, 5, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Cache callback comes back right after network is triggered.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(55);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 11);
+ info("IsFromCache: " + gIsFromCache + "\n");
+ info("Number of 200 responses: " + g200Counter + "\n");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Set an increasingly high timeout to trigger opening the cache entry
+ // This way we ensure that some of the entries we will get from the network,
+ // and some we will get from the cache.
+ gIsFromCache = 0;
+ for (var i = 0; i < 50; i++) {
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(i * 100);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(10);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ // This may be racy. The delay was chosen because the distribution of net-cache
+ // results was around 25-25 on my machine.
+ yield undefined;
+ }
+
+ greater(gIsFromCache, 0, "Some of the responses should be from the cache");
+ less(gIsFromCache, 50, "Some of the responses should be from the net");
+
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_range_requests.js b/netwerk/test/unit/test_range_requests.js
new file mode 100644
index 0000000000..cb7a1e7a72
--- /dev/null
+++ b/netwerk/test/unit/test_range_requests.js
@@ -0,0 +1,443 @@
+//
+// This test makes sure range-requests are sent and treated the way we want
+// See bug #612135 for a thorough discussion on the subject
+//
+// Necko does a range-request for a partial cache-entry iff
+//
+// 1) size of the cached entry < value of the cached Content-Length header
+// (not tested here - see bug #612135 comments 108-110)
+// 2) the size of the cached entry is > 0 (see bug #628607)
+// 3) the cached entry does not have a "no-store" Cache-Control header
+// 4) the cached entry does not have a Content-Encoding (see bug #613159)
+// 5) the request does not have a conditional-request header set by client
+// 6) nsHttpResponseHead::IsResumable() is true for the cached entry
+// 7) a basic positive test that makes sure byte ranges work
+// 8) ensure NS_ERROR_CORRUPTED_CONTENT is thrown when total entity size
+// of 206 does not match content-length of 200
+//
+// The test has one handler for each case and run_tests() fires one request
+// for each. None of the handlers should see a Range-header.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+
+const clearTextBody = "This is a slightly longer test\n";
+const encodedBody = [
+ 0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78,
+ 0x74, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8,
+ 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85, 0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c,
+ 0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85, 0x92, 0xd4, 0xe2, 0x12,
+ 0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00,
+];
+
+const partial_data_length = 4;
+var port = null; // set in run_test
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+// StreamListener which cancels its request on first data available
+function Canceler(continueFn) {
+ this.continueFn = continueFn;
+}
+Canceler.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, offset, count) {
+ // Read stream so we don't assert for not reading from the stream
+ // if cancelling the channel is slow.
+ read_stream(stream, count);
+
+ request.QueryInterface(Ci.nsIChannel).cancel(Cr.NS_BINDING_ABORTED);
+ },
+ onStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_BINDING_ABORTED);
+ this.continueFn(request, null);
+ },
+};
+// Simple StreamListener which performs no validations
+function MyListener(continueFn) {
+ this.continueFn = continueFn;
+ this._buffer = null;
+}
+MyListener.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+ onStartRequest(request) {
+ this._buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+ onStopRequest(request, status) {
+ this.continueFn(request, this._buffer);
+ },
+};
+
+var case_8_range_request = false;
+function FailedChannelListener(continueFn) {
+ this.continueFn = continueFn;
+}
+FailedChannelListener.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, offset, count) {
+ read_stream(stream, count);
+ },
+
+ onStopRequest(request, status) {
+ if (case_8_range_request) {
+ Assert.equal(status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+ }
+ this.continueFn(request, null);
+ },
+};
+
+function received_cleartext(request, data) {
+ Assert.equal(clearTextBody, data);
+ testFinished();
+}
+
+function setStdHeaders(response, length) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age: 360000");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + length);
+}
+
+function handler_2(metadata, response) {
+ setStdHeaders(response, clearTextBody.length);
+ Assert.ok(!metadata.hasHeader("Range"));
+ response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
+}
+function received_partial_2(request, data) {
+ Assert.equal(data, undefined);
+ var chan = make_channel("http://localhost:" + port + "/test_2");
+ chan.asyncOpen(new ChannelListener(received_cleartext, null));
+}
+
+var case_3_request_no = 0;
+function handler_3(metadata, response) {
+ var body = clearTextBody;
+ setStdHeaders(response, body.length);
+ response.setHeader("Cache-Control", "no-store", false);
+ switch (case_3_request_no) {
+ case 0:
+ Assert.ok(!metadata.hasHeader("Range"));
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ Assert.ok(!metadata.hasHeader("Range"));
+ response.bodyOutputStream.write(body, body.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_3_request_no++;
+}
+function received_partial_3(request, data) {
+ Assert.equal(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_3");
+ chan.asyncOpen(new ChannelListener(received_cleartext, null));
+}
+
+var case_4_request_no = 0;
+function handler_4(metadata, response) {
+ switch (case_4_request_no) {
+ case 0:
+ Assert.ok(!metadata.hasHeader("Range"));
+ var body = encodedBody;
+ setStdHeaders(response, body.length);
+ response.setHeader("Content-Encoding", "gzip", false);
+ body = body.slice(0, partial_data_length);
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIBinaryOutputStream
+ );
+ bos.setOutputStream(response.bodyOutputStream);
+ response.processAsync();
+ bos.writeByteArray(body);
+ response.finish();
+ break;
+ case 1:
+ Assert.ok(!metadata.hasHeader("Range"));
+ setStdHeaders(response, clearTextBody.length);
+ response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_4_request_no++;
+}
+function received_partial_4(request, data) {
+ // checking length does not work with encoded data
+ // do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_4");
+ chan.asyncOpen(new MyListener(received_cleartext));
+}
+
+var case_5_request_no = 0;
+function handler_5(metadata, response) {
+ var body = clearTextBody;
+ setStdHeaders(response, body.length);
+ switch (case_5_request_no) {
+ case 0:
+ Assert.ok(!metadata.hasHeader("Range"));
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ Assert.ok(!metadata.hasHeader("Range"));
+ response.bodyOutputStream.write(body, body.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_5_request_no++;
+}
+function received_partial_5(request, data) {
+ Assert.equal(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_5");
+ chan.setRequestHeader("If-Match", "Some eTag", false);
+ chan.asyncOpen(new ChannelListener(received_cleartext, null));
+}
+
+var case_6_request_no = 0;
+function handler_6(metadata, response) {
+ switch (case_6_request_no) {
+ case 0:
+ Assert.ok(!metadata.hasHeader("Range"));
+ var body = clearTextBody;
+ setStdHeaders(response, body.length);
+ response.setHeader("Accept-Ranges", "", false);
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ Assert.ok(!metadata.hasHeader("Range"));
+ setStdHeaders(response, clearTextBody.length);
+ response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_6_request_no++;
+}
+function received_partial_6(request, data) {
+ // would like to verify that the response does not have Accept-Ranges
+ Assert.equal(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_6");
+ chan.asyncOpen(new ChannelListener(received_cleartext, null));
+}
+
+const simpleBody = "0123456789";
+
+function received_simple(request, data) {
+ Assert.equal(simpleBody, data);
+ testFinished();
+}
+
+var case_7_request_no = 0;
+function handler_7(metadata, response) {
+ switch (case_7_request_no) {
+ case 0:
+ Assert.ok(!metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test7Etag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
+ response.finish();
+ break;
+ case 1:
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test7Etag");
+ if (metadata.hasHeader("Range")) {
+ Assert.ok(metadata.hasHeader("If-Range"));
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "4-9/10");
+ response.setHeader("Content-Length", "6");
+ response.bodyOutputStream.write(simpleBody.slice(4), 6);
+ } else {
+ response.setHeader("Content-Length", "10");
+ response.bodyOutputStream.write(simpleBody, 10);
+ }
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_7_request_no++;
+}
+function received_partial_7(request, data) {
+ // make sure we get the first 4 bytes
+ Assert.equal(4, data.length);
+ // do it again to get the rest
+ var chan = make_channel("http://localhost:" + port + "/test_7");
+ chan.asyncOpen(new ChannelListener(received_simple, null));
+}
+
+var case_8_request_no = 0;
+function handler_8(metadata, response) {
+ switch (case_8_request_no) {
+ case 0:
+ Assert.ok(!metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test8Etag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
+ response.finish();
+ break;
+ case 1:
+ if (metadata.hasHeader("Range")) {
+ Assert.ok(metadata.hasHeader("If-Range"));
+ case_8_range_request = true;
+ }
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test8Etag");
+ response.setHeader("Content-Range", "4-8/9"); // intentionally broken
+ response.setHeader("Content-Length", "5");
+ response.bodyOutputStream.write(simpleBody.slice(4), 5);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_8_request_no++;
+}
+function received_partial_8(request, data) {
+ // make sure we get the first 4 bytes
+ Assert.equal(4, data.length);
+ // do it again to get the rest
+ var chan = make_channel("http://localhost:" + port + "/test_8");
+ chan.asyncOpen(
+ new FailedChannelListener(testFinished, null, CL_EXPECT_LATE_FAILURE)
+ );
+}
+
+var case_9_request_no = 0;
+function handler_9(metadata, response) {
+ switch (case_9_request_no) {
+ case 0:
+ Assert.ok(!metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "W/test9WeakEtag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
+ response.finish(); // truncated response
+ break;
+ case 1:
+ Assert.ok(!metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "W/test9WeakEtag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody, 10);
+ response.finish(); // full response
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_9_request_no++;
+}
+function received_partial_9(request, data) {
+ Assert.equal(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_9");
+ chan.asyncOpen(new ChannelListener(received_simple, null));
+}
+
+// Simple mechanism to keep track of tests and stop the server
+var numTestsFinished = 0;
+function testFinished() {
+ if (++numTestsFinished == 7) {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/test_2", handler_2);
+ httpserver.registerPathHandler("/test_3", handler_3);
+ httpserver.registerPathHandler("/test_4", handler_4);
+ httpserver.registerPathHandler("/test_5", handler_5);
+ httpserver.registerPathHandler("/test_6", handler_6);
+ httpserver.registerPathHandler("/test_7", handler_7);
+ httpserver.registerPathHandler("/test_8", handler_8);
+ httpserver.registerPathHandler("/test_9", handler_9);
+ httpserver.start(-1);
+
+ port = httpserver.identity.primaryPort;
+
+ // wipe out cached content
+ evict_cache_entries();
+
+ // Case 2: zero-length partial entry must not trigger range-request
+ let chan = make_channel("http://localhost:" + port + "/test_2");
+ chan.asyncOpen(new Canceler(received_partial_2));
+
+ // Case 3: no-store response must not trigger range-request
+ chan = make_channel("http://localhost:" + port + "/test_3");
+ chan.asyncOpen(new MyListener(received_partial_3));
+
+ // Case 4: response with content-encoding must not trigger range-request
+ chan = make_channel("http://localhost:" + port + "/test_4");
+ chan.asyncOpen(new MyListener(received_partial_4));
+
+ // Case 5: conditional request-header set by client
+ chan = make_channel("http://localhost:" + port + "/test_5");
+ chan.asyncOpen(new MyListener(received_partial_5));
+
+ // Case 6: response is not resumable (drop the Accept-Ranges header)
+ chan = make_channel("http://localhost:" + port + "/test_6");
+ chan.asyncOpen(new MyListener(received_partial_6));
+
+ // Case 7: a basic positive test
+ chan = make_channel("http://localhost:" + port + "/test_7");
+ chan.asyncOpen(new MyListener(received_partial_7));
+
+ // Case 8: check that mismatched 206 and 200 sizes throw error
+ chan = make_channel("http://localhost:" + port + "/test_8");
+ chan.asyncOpen(new MyListener(received_partial_8));
+
+ // Case 9: check that weak etag is not used for a range request
+ chan = make_channel("http://localhost:" + port + "/test_9");
+ chan.asyncOpen(new MyListener(received_partial_9));
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_rcwn_always_cache_new_content.js b/netwerk/test/unit/test_rcwn_always_cache_new_content.js
new file mode 100644
index 0000000000..160ab631ca
--- /dev/null
+++ b/netwerk/test/unit/test_rcwn_always_cache_new_content.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+let gFirstResponseBody = "first version";
+let gSecondResponseBody = "second version";
+let gRequestCounter = 0;
+
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "max-age=3600");
+ response.setHeader("ETag", "test-etag1");
+
+ switch (gRequestCounter) {
+ case 0:
+ response.bodyOutputStream.write(
+ gFirstResponseBody,
+ gFirstResponseBody.length
+ );
+ break;
+ case 1:
+ response.bodyOutputStream.write(
+ gSecondResponseBody,
+ gSecondResponseBody.length
+ );
+ break;
+ default:
+ do_throw("Unexpected request");
+ }
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+}
+
+function checkContent(request, buffer, context, isFromCache) {
+ let isRacing = request.QueryInterface(Ci.nsICacheInfoChannel).isRacing();
+ switch (gRequestCounter) {
+ case 0:
+ Assert.equal(buffer, gFirstResponseBody);
+ Assert.ok(!isFromCache);
+ Assert.ok(!isRacing);
+ break;
+ case 1:
+ Assert.equal(buffer, gSecondResponseBody);
+ Assert.ok(!isFromCache);
+ Assert.ok(isRacing);
+ break;
+ case 2:
+ Assert.equal(buffer, gSecondResponseBody);
+ Assert.ok(isFromCache);
+ Assert.ok(!isRacing);
+ break;
+ default:
+ do_throw("Unexpected request");
+ }
+
+ gRequestCounter++;
+ executeSoon(() => {
+ testGenerator.next();
+ });
+}
+
+function run_test() {
+ do_get_profile();
+ // In this test, we race the requests manually using |TriggerNetwork|,
+ // therefore we should disable racing in the HttpChannel first.
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+ httpserver.registerPathHandler("/rcwn", test_handler);
+ testGenerator.next();
+ do_test_pending();
+}
+
+let testGenerator = testSteps();
+function* testSteps() {
+ // Store first version of the content in the cache.
+ let channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gRequestCounter, 1);
+
+ // Simulate the network victory by setting high delay for the cache fetch and
+ // triggering the network.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ // Trigger network after 50 ms.
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gRequestCounter, 2);
+
+ // Simulate navigation back by specifying VALIDATE_NEVER flag.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel.loadFlags = Ci.nsIRequest.VALIDATE_NEVER;
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gRequestCounter, 3);
+
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_rcwn_interrupted.js b/netwerk/test/unit/test_rcwn_interrupted.js
new file mode 100644
index 0000000000..5f3d059999
--- /dev/null
+++ b/netwerk/test/unit/test_rcwn_interrupted.js
@@ -0,0 +1,108 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29a test, this test checks that cocurrency is resumed when the first channel is interrupted
+in the middle of reading and the second channel already consumed some content from the cache entry.
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- both channels must deliver full content w/o errors
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response) {
+ response.processAsync();
+ do_timeout(500, () => {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ if (metadata.hasHeader("If-Range")) {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+
+ let len = responseBody.length;
+ response.setHeader("Content-Range", "0-" + (len - 1) + "/" + len);
+ }
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+
+ response.finish();
+ });
+}
+
+function run_test() {
+ // Static check
+ Assert.ok(responseBody.length > 1024);
+
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ httpProtocolHandler.EnsureHSTSDataReady().then(function () {
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen(
+ new ChannelListener(firstTimeThrough, null, CL_IGNORE_DELAYS)
+ );
+ var chan2 = make_channel(URL + "/content");
+ chan2
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(200);
+ chan2.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ chan2.asyncOpen(
+ new ChannelListener(secondTimeThrough, null, CL_IGNORE_DELAYS)
+ );
+ });
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_readline.js b/netwerk/test/unit/test_readline.js
new file mode 100644
index 0000000000..ac0c915406
--- /dev/null
+++ b/netwerk/test/unit/test_readline.js
@@ -0,0 +1,104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const PR_RDONLY = 0x1;
+
+function new_file_input_stream(file) {
+ var stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ stream.init(file, PR_RDONLY, 0, 0);
+ return stream;
+}
+
+function new_line_input_stream(filename) {
+ return new_file_input_stream(do_get_file(filename)).QueryInterface(
+ Ci.nsILineInputStream
+ );
+}
+
+var test_array = [
+ { file: "data/test_readline1.txt", lines: [] },
+ { file: "data/test_readline2.txt", lines: [""] },
+ { file: "data/test_readline3.txt", lines: ["", "", "", "", ""] },
+ { file: "data/test_readline4.txt", lines: ["1", "23", "456", "", "78901"] },
+ {
+ file: "data/test_readline5.txt",
+ lines: [
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE",
+ ],
+ },
+ {
+ file: "data/test_readline6.txt",
+ lines: [
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE",
+ ],
+ },
+ {
+ file: "data/test_readline7.txt",
+ lines: [
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE",
+ "",
+ ],
+ },
+ {
+ file: "data/test_readline8.txt",
+ lines: [
+ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE",
+ ],
+ },
+];
+
+function err(file, lineNo, msg) {
+ do_throw('"' + file + '" line ' + lineNo + ", " + msg);
+}
+
+function run_test() {
+ for (var test of test_array) {
+ var lineStream = new_line_input_stream(test.file);
+ var lineNo = 0;
+ var more = false;
+ var line = {};
+ more = lineStream.readLine(line);
+ for (var check of test.lines) {
+ ++lineNo;
+ if (lineNo == test.lines.length) {
+ if (more) {
+ err(
+ test.file,
+ lineNo,
+ "There should be no more data after the last line"
+ );
+ }
+ } else if (!more) {
+ err(test.file, lineNo, "There should be more data after this line");
+ }
+ if (line.value != check) {
+ err(
+ test.file,
+ lineNo,
+ "Wrong value, got '" + line.value + "' expected '" + check + "'"
+ );
+ }
+ dump(
+ 'ok "' +
+ test.file +
+ '" line ' +
+ lineNo +
+ " (length " +
+ line.value.length +
+ "): '" +
+ line.value +
+ "'\n"
+ );
+ more = lineStream.readLine(line);
+ }
+ if (more) {
+ err(test.file, lineNo, "'more' should be false after reading all lines");
+ }
+ dump('ok "' + test.file + '" succeeded\n');
+ lineStream.close();
+ }
+}
diff --git a/netwerk/test/unit/test_redirect-caching_canceled.js b/netwerk/test/unit/test_redirect-caching_canceled.js
new file mode 100644
index 0000000000..0ecf2f74b7
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_canceled.js
@@ -0,0 +1,64 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+ response.setHeader("Cache-control", "max-age=1000", false);
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(new ChannelListener(secondTimeThrough, null));
+}
+
+function secondTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect-caching_failure.js b/netwerk/test/unit/test_redirect-caching_failure.js
new file mode 100644
index 0000000000..2f8105dd94
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_failure.js
@@ -0,0 +1,81 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+/*
+ * The test is checking async redirect code path that is loading a cached
+ * redirect. But creation of the target channel fails before we even try
+ * to do async open on it. We force the creation error by forbidding
+ * the port number the URI contains. It must be done only after we have
+ * attempted to do the redirect (open the target URL) otherwise it's not
+ * cached.
+ */
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var serverRequestCount = 0;
+
+function redirectHandler(metadata, response) {
+ ++serverRequestCount;
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://non-existent.tld:65400", false);
+ response.setHeader("Cache-control", "max-age=1000", false);
+}
+
+function firstTimeThrough(request) {
+ Assert.equal(request.status, Cr.NS_ERROR_UNKNOWN_HOST);
+ Assert.equal(serverRequestCount, 1);
+
+ const nextHop = () => {
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ };
+
+ if (inChildProcess()) {
+ do_send_remote_message("disable-ports");
+ do_await_remote_message("disable-ports-done").then(nextHop);
+ } else {
+ Services.prefs.setCharPref("network.security.ports.banned", "65400");
+ nextHop();
+ }
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(request.status, Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED);
+ Assert.equal(serverRequestCount, 1);
+ Assert.equal(buffer, "");
+
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(
+ new ChannelListener(firstTimeThrough, null, CL_EXPECT_FAILURE)
+ );
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect-caching_passing.js b/netwerk/test/unit/test_redirect-caching_passing.js
new file mode 100644
index 0000000000..0e6616f493
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_passing.js
@@ -0,0 +1,56 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(randomPath, redirectHandler);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_baduri.js b/netwerk/test/unit/test_redirect_baduri.js
new file mode 100644
index 0000000000..f83c9a264f
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_baduri.js
@@ -0,0 +1,44 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+/*
+ * Test whether we fail bad URIs in HTTP redirect as CORRUPTED_CONTENT.
+ */
+
+var httpServer = null;
+
+var BadRedirectPath = "/BadRedirect";
+ChromeUtils.defineLazyGetter(this, "BadRedirectURI", function () {
+ return (
+ "http://localhost:" + httpServer.identity.primaryPort + BadRedirectPath
+ );
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function BadRedirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ // '>' in URI will fail to parse: we should not render response
+ response.setHeader("Location", "http://localhost:4444>BadRedirect", false);
+}
+
+function checkFailed(request, buffer) {
+ Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
+
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(BadRedirectPath, BadRedirectHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(BadRedirectURI);
+ chan.asyncOpen(new ChannelListener(checkFailed, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_canceled.js b/netwerk/test/unit/test_redirect_canceled.js
new file mode 100644
index 0000000000..9eb9d8b33a
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_canceled.js
@@ -0,0 +1,50 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_different-protocol.js b/netwerk/test/unit/test_redirect_different-protocol.js
new file mode 100644
index 0000000000..78b305c84e
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_different-protocol.js
@@ -0,0 +1,49 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const redirectTargetBody = "response body";
+const response301Body = "redirect body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.bodyOutputStream.write(response301Body, response301Body.length);
+ response.setHeader(
+ "Location",
+ "data:text/plain," + redirectTargetBody,
+ false
+ );
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, redirectTargetBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(new ChannelListener(finish_test, null, 0));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_failure.js b/netwerk/test/unit/test_redirect_failure.js
new file mode 100644
index 0000000000..28b4668580
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_failure.js
@@ -0,0 +1,59 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+/*
+ * The test is checking async redirect code path that is loading a
+ * redirect. But creation of the target channel fails before we even try
+ * to do async open on it. We force the creation error by forbidding
+ * the port number the URI contains.
+ */
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://non-existent.tld:65400", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(request.status, Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED);
+
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ if (!inChildProcess()) {
+ Services.prefs.setCharPref("network.security.ports.banned", "65400");
+ }
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_from_script.js b/netwerk/test/unit/test_redirect_from_script.js
new file mode 100644
index 0000000000..0c42d66b64
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script.js
@@ -0,0 +1,247 @@
+/*
+ * Test whether the rewrite-requests-from-script API implemented here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=765934 is functioning
+ * correctly
+ *
+ * The test has the following components:
+ *
+ * testViaXHR() checks that internal redirects occur correctly for requests
+ * made with XMLHttpRequest objects.
+ *
+ * testViaAsyncOpen() checks that internal redirects occur correctly when made
+ * with nsIHTTPChannel.asyncOpen().
+ *
+ * Both of the above functions tests four requests:
+ *
+ * Test 1: a simple case that redirects within a server;
+ * Test 2: a second that redirects to a second webserver;
+ * Test 3: internal script redirects in response to a server-side 302 redirect;
+ * Test 4: one internal script redirects in response to another's redirect.
+ *
+ * The successful redirects are confirmed by the presence of a custom response
+ * header.
+ *
+ */
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// the topic we observe to use the API. http-on-opening-request might also
+// work for some purposes.
+let redirectHook = "http-on-modify-request";
+
+var httpServer = null,
+ httpServer2 = null;
+
+ChromeUtils.defineLazyGetter(this, "port1", function () {
+ return httpServer.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "port2", function () {
+ return httpServer2.identity.primaryPort;
+});
+
+// Test Part 1: a cross-path redirect on a single HTTP server
+// http://localhost:port1/bait -> http://localhost:port1/switch
+var baitPath = "/bait";
+ChromeUtils.defineLazyGetter(this, "baitURI", function () {
+ return "http://localhost:" + port1 + baitPath;
+});
+var baitText = "you got the worm";
+
+var redirectedPath = "/switch";
+ChromeUtils.defineLazyGetter(this, "redirectedURI", function () {
+ return "http://localhost:" + port1 + redirectedPath;
+});
+var redirectedText = "worms are not tasty";
+
+// Test Part 2: Now, a redirect to a different server
+// http://localhost:port1/bait2 -> http://localhost:port2/switch
+var bait2Path = "/bait2";
+ChromeUtils.defineLazyGetter(this, "bait2URI", function () {
+ return "http://localhost:" + port1 + bait2Path;
+});
+
+ChromeUtils.defineLazyGetter(this, "redirected2URI", function () {
+ return "http://localhost:" + port2 + redirectedPath;
+});
+
+// Test Part 3, begin with a serverside redirect that itself turns into an instance
+// of Test Part 1
+var bait3Path = "/bait3";
+ChromeUtils.defineLazyGetter(this, "bait3URI", function () {
+ return "http://localhost:" + port1 + bait3Path;
+});
+
+// Test Part 4, begin with this client-side redirect and which then redirects
+// to an instance of Test Part 1
+var bait4Path = "/bait4";
+ChromeUtils.defineLazyGetter(this, "bait4URI", function () {
+ return "http://localhost:" + port1 + bait4Path;
+});
+
+var testHeaderName = "X-Redirected-By-Script";
+var testHeaderVal = "Success";
+var testHeaderVal2 = "Success on server 2";
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function baitHandler(metadata, response) {
+ // Content-Type required: https://bugzilla.mozilla.org/show_bug.cgi?id=748117
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(baitText, baitText.length);
+}
+
+function redirectedHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal);
+}
+
+function redirected2Handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal2);
+}
+
+function bait3Handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", baitURI);
+}
+
+function Redirector() {
+ this.register();
+}
+
+Redirector.prototype = {
+ // This class observes an event and uses that to
+ // trigger a redirectTo(uri) redirect using the new API
+ register() {
+ Services.obs.addObserver(this, redirectHook, true);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ observe(subject, topic, data) {
+ if (topic == redirectHook) {
+ if (!(subject instanceof Ci.nsIHttpChannel)) {
+ do_throw(redirectHook + " observed a non-HTTP channel");
+ }
+ var channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ var target = null;
+ if (channel.URI.spec == baitURI) {
+ target = redirectedURI;
+ }
+ if (channel.URI.spec == bait2URI) {
+ target = redirected2URI;
+ }
+ if (channel.URI.spec == bait4URI) {
+ target = baitURI;
+ }
+ // if we have a target, redirect there
+ if (target) {
+ var tURI = Services.io.newURI(target);
+ try {
+ channel.redirectTo(tURI);
+ } catch (e) {
+ do_throw("Exception in redirectTo " + e + "\n");
+ }
+ }
+ }
+ },
+};
+
+function makeAsyncTest(uri, headerValue, nextTask) {
+ // Make a test to check a redirect that is created with channel.asyncOpen()
+
+ // Produce a callback function which checks for the presence of headerValue,
+ // and then continues to the next async test task
+ var verifier = function (req, buffer) {
+ if (!(req instanceof Ci.nsIHttpChannel)) {
+ do_throw(req + " is not an nsIHttpChannel, catastrophe imminent!");
+ }
+
+ var httpChannel = req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(httpChannel.getResponseHeader(testHeaderName), headerValue);
+ Assert.equal(buffer, redirectedText);
+ nextTask();
+ };
+
+ // Produce a function to run an asyncOpen test using the above verifier
+ var test = function () {
+ var chan = make_channel(uri);
+ chan.asyncOpen(new ChannelListener(verifier));
+ };
+ return test;
+}
+
+// will be defined in run_test because of the lazy getters,
+// since the server's port is defined dynamically
+var testViaAsyncOpen4 = null;
+var testViaAsyncOpen3 = null;
+var testViaAsyncOpen2 = null;
+var testViaAsyncOpen = null;
+
+function testViaXHR() {
+ runXHRTest(baitURI, testHeaderVal);
+ runXHRTest(bait2URI, testHeaderVal2);
+ runXHRTest(bait3URI, testHeaderVal);
+ runXHRTest(bait4URI, testHeaderVal);
+}
+
+function runXHRTest(uri, headerValue) {
+ // Check that making an XHR request for uri winds up redirecting to a result with the
+ // appropriate headerValue
+ var req = new XMLHttpRequest();
+ req.open("GET", uri, false);
+ req.send();
+ Assert.equal(req.getResponseHeader(testHeaderName), headerValue);
+ Assert.equal(req.response, redirectedText);
+}
+
+function done() {
+ httpServer.stop(function () {
+ httpServer2.stop(do_test_finished);
+ });
+}
+
+// Needed for side-effects
+new Redirector();
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(baitPath, baitHandler);
+ httpServer.registerPathHandler(bait2Path, baitHandler);
+ httpServer.registerPathHandler(bait3Path, bait3Handler);
+ httpServer.registerPathHandler(bait4Path, baitHandler);
+ httpServer.registerPathHandler(redirectedPath, redirectedHandler);
+ httpServer.start(-1);
+ httpServer2 = new HttpServer();
+ httpServer2.registerPathHandler(redirectedPath, redirected2Handler);
+ httpServer2.start(-1);
+
+ // The tests depend on each other, and therefore need to be defined in the
+ // reverse of the order they are called in. It is therefore best to read this
+ // stanza backwards!
+ testViaAsyncOpen4 = makeAsyncTest(bait4URI, testHeaderVal, done);
+ testViaAsyncOpen3 = makeAsyncTest(bait3URI, testHeaderVal, testViaAsyncOpen4);
+ testViaAsyncOpen2 = makeAsyncTest(
+ bait2URI,
+ testHeaderVal2,
+ testViaAsyncOpen3
+ );
+ testViaAsyncOpen = makeAsyncTest(baitURI, testHeaderVal, testViaAsyncOpen2);
+
+ testViaXHR();
+ testViaAsyncOpen(); // will call done() asynchronously for cleanup
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_from_script_after-open_passing.js b/netwerk/test/unit/test_redirect_from_script_after-open_passing.js
new file mode 100644
index 0000000000..315f888f64
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script_after-open_passing.js
@@ -0,0 +1,247 @@
+/*
+ * Test whether the rewrite-requests-from-script API implemented here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=765934 is functioning
+ * correctly
+ *
+ * The test has the following components:
+ *
+ * testViaXHR() checks that internal redirects occur correctly for requests
+ * made with XMLHttpRequest objects.
+ *
+ * testViaAsyncOpen() checks that internal redirects occur correctly when made
+ * with nsIHTTPChannel.asyncOpen().
+ *
+ * Both of the above functions tests four requests:
+ *
+ * Test 1: a simple case that redirects within a server;
+ * Test 2: a second that redirects to a second webserver;
+ * Test 3: internal script redirects in response to a server-side 302 redirect;
+ * Test 4: one internal script redirects in response to another's redirect.
+ *
+ * The successful redirects are confirmed by the presence of a custom response
+ * header.
+ *
+ */
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// the topic we observe to use the API. http-on-opening-request might also
+// work for some purposes.
+let redirectHook = "http-on-examine-response";
+
+var httpServer = null,
+ httpServer2 = null;
+
+ChromeUtils.defineLazyGetter(this, "port1", function () {
+ return httpServer.identity.primaryPort;
+});
+
+ChromeUtils.defineLazyGetter(this, "port2", function () {
+ return httpServer2.identity.primaryPort;
+});
+
+// Test Part 1: a cross-path redirect on a single HTTP server
+// http://localhost:port1/bait -> http://localhost:port1/switch
+var baitPath = "/bait";
+ChromeUtils.defineLazyGetter(this, "baitURI", function () {
+ return "http://localhost:" + port1 + baitPath;
+});
+var baitText = "you got the worm";
+
+var redirectedPath = "/switch";
+ChromeUtils.defineLazyGetter(this, "redirectedURI", function () {
+ return "http://localhost:" + port1 + redirectedPath;
+});
+var redirectedText = "worms are not tasty";
+
+// Test Part 2: Now, a redirect to a different server
+// http://localhost:port1/bait2 -> http://localhost:port2/switch
+var bait2Path = "/bait2";
+ChromeUtils.defineLazyGetter(this, "bait2URI", function () {
+ return "http://localhost:" + port1 + bait2Path;
+});
+
+ChromeUtils.defineLazyGetter(this, "redirected2URI", function () {
+ return "http://localhost:" + port2 + redirectedPath;
+});
+
+// Test Part 3, begin with a serverside redirect that itself turns into an instance
+// of Test Part 1
+var bait3Path = "/bait3";
+ChromeUtils.defineLazyGetter(this, "bait3URI", function () {
+ return "http://localhost:" + port1 + bait3Path;
+});
+
+// Test Part 4, begin with this client-side redirect and which then redirects
+// to an instance of Test Part 1
+var bait4Path = "/bait4";
+ChromeUtils.defineLazyGetter(this, "bait4URI", function () {
+ return "http://localhost:" + port1 + bait4Path;
+});
+
+var testHeaderName = "X-Redirected-By-Script";
+var testHeaderVal = "Success";
+var testHeaderVal2 = "Success on server 2";
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function baitHandler(metadata, response) {
+ // Content-Type required: https://bugzilla.mozilla.org/show_bug.cgi?id=748117
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(baitText, baitText.length);
+}
+
+function redirectedHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal);
+}
+
+function redirected2Handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal2);
+}
+
+function bait3Handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", baitURI);
+}
+
+function Redirector() {
+ this.register();
+}
+
+Redirector.prototype = {
+ // This class observes an event and uses that to
+ // trigger a redirectTo(uri) redirect using the new API
+ register() {
+ Services.obs.addObserver(this, redirectHook, true);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ observe(subject, topic, data) {
+ if (topic == redirectHook) {
+ if (!(subject instanceof Ci.nsIHttpChannel)) {
+ do_throw(redirectHook + " observed a non-HTTP channel");
+ }
+ var channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ var target = null;
+ if (channel.URI.spec == baitURI) {
+ target = redirectedURI;
+ }
+ if (channel.URI.spec == bait2URI) {
+ target = redirected2URI;
+ }
+ if (channel.URI.spec == bait4URI) {
+ target = baitURI;
+ }
+ // if we have a target, redirect there
+ if (target) {
+ var tURI = Services.io.newURI(target);
+ try {
+ channel.redirectTo(tURI);
+ } catch (e) {
+ do_throw("Exception in redirectTo " + e + "\n");
+ }
+ }
+ }
+ },
+};
+
+function makeAsyncTest(uri, headerValue, nextTask) {
+ // Make a test to check a redirect that is created with channel.asyncOpen()
+
+ // Produce a callback function which checks for the presence of headerValue,
+ // and then continues to the next async test task
+ var verifier = function (req, buffer) {
+ if (!(req instanceof Ci.nsIHttpChannel)) {
+ do_throw(req + " is not an nsIHttpChannel, catastrophe imminent!");
+ }
+
+ var httpChannel = req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(httpChannel.getResponseHeader(testHeaderName), headerValue);
+ Assert.equal(buffer, redirectedText);
+ nextTask();
+ };
+
+ // Produce a function to run an asyncOpen test using the above verifier
+ var test = function () {
+ var chan = make_channel(uri);
+ chan.asyncOpen(new ChannelListener(verifier));
+ };
+ return test;
+}
+
+// will be defined in run_test because of the lazy getters,
+// since the server's port is defined dynamically
+var testViaAsyncOpen4 = null;
+var testViaAsyncOpen3 = null;
+var testViaAsyncOpen2 = null;
+var testViaAsyncOpen = null;
+
+function testViaXHR() {
+ runXHRTest(baitURI, testHeaderVal);
+ runXHRTest(bait2URI, testHeaderVal2);
+ runXHRTest(bait3URI, testHeaderVal);
+ runXHRTest(bait4URI, testHeaderVal);
+}
+
+function runXHRTest(uri, headerValue) {
+ // Check that making an XHR request for uri winds up redirecting to a result with the
+ // appropriate headerValue
+ var req = new XMLHttpRequest();
+ req.open("GET", uri, false);
+ req.send();
+ Assert.equal(req.getResponseHeader(testHeaderName), headerValue);
+ Assert.equal(req.response, redirectedText);
+}
+
+function done() {
+ httpServer.stop(function () {
+ httpServer2.stop(do_test_finished);
+ });
+}
+
+// Needed for side-effects
+new Redirector();
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(baitPath, baitHandler);
+ httpServer.registerPathHandler(bait2Path, baitHandler);
+ httpServer.registerPathHandler(bait3Path, bait3Handler);
+ httpServer.registerPathHandler(bait4Path, baitHandler);
+ httpServer.registerPathHandler(redirectedPath, redirectedHandler);
+ httpServer.start(-1);
+ httpServer2 = new HttpServer();
+ httpServer2.registerPathHandler(redirectedPath, redirected2Handler);
+ httpServer2.start(-1);
+
+ // The tests depend on each other, and therefore need to be defined in the
+ // reverse of the order they are called in. It is therefore best to read this
+ // stanza backwards!
+ testViaAsyncOpen4 = makeAsyncTest(bait4URI, testHeaderVal, done);
+ testViaAsyncOpen3 = makeAsyncTest(bait3URI, testHeaderVal, testViaAsyncOpen4);
+ testViaAsyncOpen2 = makeAsyncTest(
+ bait2URI,
+ testHeaderVal2,
+ testViaAsyncOpen3
+ );
+ testViaAsyncOpen = makeAsyncTest(baitURI, testHeaderVal, testViaAsyncOpen2);
+
+ testViaXHR();
+ testViaAsyncOpen(); // will call done() asynchronously for cleanup
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_history.js b/netwerk/test/unit/test_redirect_history.js
new file mode 100644
index 0000000000..124f976881
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_history.js
@@ -0,0 +1,75 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+var redirects = [];
+const numRedirects = 10;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function contentHandler(request, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ let chan = request.QueryInterface(Ci.nsIChannel);
+ let redirectChain = chan.loadInfo.redirectChain;
+
+ Assert.equal(numRedirects - 1, redirectChain.length);
+ for (let i = 0; i < numRedirects - 1; ++i) {
+ let principal = redirectChain[i].principal;
+ Assert.equal(URL + redirects[i], principal.spec);
+ Assert.equal(redirectChain[i].referrerURI.spec, "http://test.com/");
+ Assert.equal(redirectChain[i].remoteAddress, "127.0.0.1");
+ }
+ httpServer.stop(do_test_finished);
+}
+
+function redirectHandler(index, request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved");
+ let path = redirects[index + 1];
+ response.setHeader("Location", URL + path, false);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ for (let i = 0; i < numRedirects; ++i) {
+ var randomPath = "/redirect/" + Math.random();
+ redirects.push(randomPath);
+ if (i < numRedirects - 1) {
+ httpServer.registerPathHandler(randomPath, redirectHandler.bind(this, i));
+ } else {
+ // The last one doesn't redirect
+ httpServer.registerPathHandler(
+ redirects[numRedirects - 1],
+ contentHandler
+ );
+ }
+ }
+ httpServer.start(-1);
+
+ var chan = make_channel(URL + redirects[0]);
+ var uri = NetUtil.newURI("http://test.com");
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ httpChan.referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_loop.js b/netwerk/test/unit/test_redirect_loop.js
new file mode 100644
index 0000000000..f25bee18c5
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_loop.js
@@ -0,0 +1,88 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+/*
+ * This xpcshell test checks whether we detect infinite HTTP redirect loops.
+ * We check loops with "Location:" set to 1) full URI, 2) relative URI, and 3)
+ * empty Location header (which resolves to a relative link to the original
+ * URI when the original URI ends in a slash).
+ */
+
+var httpServer = new HttpServer();
+httpServer.start(-1);
+const PORT = httpServer.identity.primaryPort;
+
+var fullLoopPath = "/fullLoop";
+var fullLoopURI = "http://localhost:" + PORT + fullLoopPath;
+
+var relativeLoopPath = "/relativeLoop";
+var relativeLoopURI = "http://localhost:" + PORT + relativeLoopPath;
+
+// must use directory-style URI, so empty Location redirects back to self
+var emptyLoopPath = "/empty/";
+var emptyLoopURI = "http://localhost:" + PORT + emptyLoopPath;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function fullLoopHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader(
+ "Location",
+ "http://localhost:" + PORT + "/fullLoop",
+ false
+ );
+}
+
+function relativeLoopHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "relativeLoop", false);
+}
+
+function emptyLoopHandler(metadata, response) {
+ // Comrades! We must seize power from the petty-bourgeois running dogs of
+ // httpd.js in order to reply with a blank Location header!
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Location: \r\n");
+ response.write("Content-Length: 4\r\n");
+ response.write("\r\n");
+ response.write("oops");
+ response.finish();
+}
+
+function testFullLoop(request, buffer) {
+ Assert.equal(request.status, Cr.NS_ERROR_REDIRECT_LOOP);
+
+ var chan = make_channel(relativeLoopURI);
+ chan.asyncOpen(
+ new ChannelListener(testRelativeLoop, null, CL_EXPECT_FAILURE)
+ );
+}
+
+function testRelativeLoop(request, buffer) {
+ Assert.equal(request.status, Cr.NS_ERROR_REDIRECT_LOOP);
+
+ var chan = make_channel(emptyLoopURI);
+ chan.asyncOpen(new ChannelListener(testEmptyLoop, null, CL_EXPECT_FAILURE));
+}
+
+function testEmptyLoop(request, buffer) {
+ Assert.equal(request.status, Cr.NS_ERROR_REDIRECT_LOOP);
+
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer.registerPathHandler(fullLoopPath, fullLoopHandler);
+ httpServer.registerPathHandler(relativeLoopPath, relativeLoopHandler);
+ httpServer.registerPathHandler(emptyLoopPath, emptyLoopHandler);
+
+ var chan = make_channel(fullLoopURI);
+ chan.asyncOpen(new ChannelListener(testFullLoop, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_passing.js b/netwerk/test/unit/test_redirect_passing.js
new file mode 100644
index 0000000000..b712e4d819
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_passing.js
@@ -0,0 +1,55 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen(new ChannelListener(firstTimeThrough, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_protocol_telemetry.js b/netwerk/test/unit/test_redirect_protocol_telemetry.js
new file mode 100644
index 0000000000..526ba440ec
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_protocol_telemetry.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+add_task(async function check_protocols() {
+ // Enable the collection (during test) for all products so even products
+ // that don't collect the data will be able to run the test without failure.
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ let httpserv = new HttpServer();
+ httpserv.registerPathHandler("/redirect", redirectHandler);
+ httpserv.registerPathHandler("/content", contentHandler);
+ httpserv.start(-1);
+
+ var responseProtocol;
+
+ function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ let location =
+ responseProtocol == "data"
+ ? "data:text/plain,test"
+ : `${responseProtocol}://localhost:${httpserv.identity.primaryPort}/content`;
+ response.setHeader("Location", location, false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ }
+
+ function contentHandler(metadata, response) {
+ let responseBody = "Content body";
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+ }
+
+ function make_test(protocol) {
+ do_get_profile();
+ let redirect_hist = TelemetryTestUtils.getAndClearKeyedHistogram(
+ "NETWORK_HTTP_REDIRECT_TO_SCHEME"
+ );
+ return new Promise(resolve => {
+ const URL = `http://localhost:${httpserv.identity.primaryPort}/redirect`;
+ responseProtocol = protocol;
+ let channel = make_channel(URL);
+ let p = new Promise(resolve1 =>
+ channel.asyncOpen(new ChannelListener(resolve1))
+ );
+ p.then((request, buffer) => {
+ TelemetryTestUtils.assertKeyedHistogramSum(redirect_hist, protocol, 1);
+ resolve();
+ });
+ });
+ }
+
+ await make_test("http");
+ await make_test("data");
+
+ await new Promise(resolve => httpserv.stop(resolve));
+});
diff --git a/netwerk/test/unit/test_redirect_veto.js b/netwerk/test/unit/test_redirect_veto.js
new file mode 100644
index 0000000000..bcc9ddb625
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_veto.js
@@ -0,0 +1,102 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+let ChannelEventSink2 = {
+ _classDescription: "WebRequest channel event sink",
+ _classID: Components.ID("115062f8-92f1-11e5-8b7f-08001110f7ec"),
+ _contractID: "@mozilla.org/webrequest/channel-event-sink;1",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]),
+
+ init() {
+ Components.manager
+ .QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(
+ this._classID,
+ this._classDescription,
+ this._contractID,
+ this
+ );
+ },
+
+ register() {
+ Services.catMan.addCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ this._contractID,
+ false,
+ true
+ );
+ },
+
+ unregister() {
+ Services.catMan.deleteCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ false
+ );
+ },
+
+ // nsIChannelEventSink implementation
+ asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
+ // Abort the redirection
+ redirectCallback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+};
+
+add_task(async function test_redirect_veto() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ ChannelEventSink2.init();
+ ChannelEventSink2.register();
+
+ let chan = make_channel(randomURI);
+ let [req, buff] = await new Promise(resolve =>
+ chan.asyncOpen(
+ new ChannelListener((aReq, aBuff) => resolve([aReq, aBuff], null))
+ )
+ );
+ Assert.equal(buff, "");
+ Assert.equal(req.status, Cr.NS_OK);
+ await httpServer.stop();
+ ChannelEventSink2.unregister();
+});
diff --git a/netwerk/test/unit/test_reentrancy.js b/netwerk/test/unit/test_reentrancy.js
new file mode 100644
index 0000000000..3bd7f54f79
--- /dev/null
+++ b/netwerk/test/unit/test_reentrancy.js
@@ -0,0 +1,109 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "<?xml version='1.0' ?><root>0123456789</root>";
+
+function syncXHR() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", URL + testpath, false);
+ xhr.send(null);
+}
+
+const MAX_TESTS = 2;
+
+var listener = {
+ _done_onStart: false,
+ _done_onData: false,
+ _test: 0,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {
+ switch (this._test) {
+ case 0:
+ request.suspend();
+ syncXHR();
+ request.resume();
+ break;
+ case 1:
+ request.suspend();
+ syncXHR();
+ executeSoon(function () {
+ request.resume();
+ });
+ break;
+ case 2:
+ executeSoon(function () {
+ request.suspend();
+ });
+ executeSoon(function () {
+ request.resume();
+ });
+ syncXHR();
+ break;
+ }
+
+ this._done_onStart = true;
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ Assert.ok(this._done_onStart);
+ read_stream(stream, count);
+ this._done_onData = true;
+ },
+
+ onStopRequest(request, status) {
+ Assert.ok(this._done_onData);
+ this._reset();
+ if (this._test <= MAX_TESTS) {
+ next_test();
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+ },
+
+ _reset() {
+ this._done_onStart = false;
+ this._done_onData = false;
+ this._test++;
+ },
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function next_test() {
+ var chan = makeChan(URL + testpath);
+ chan.QueryInterface(Ci.nsIRequest);
+ chan.asyncOpen(listener);
+}
+
+function run_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ next_test();
+
+ do_test_pending();
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/xml", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
diff --git a/netwerk/test/unit/test_referrer.js b/netwerk/test/unit/test_referrer.js
new file mode 100644
index 0000000000..0c42849a43
--- /dev/null
+++ b/netwerk/test/unit/test_referrer.js
@@ -0,0 +1,248 @@
+"use strict";
+
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+function getTestReferrer(server_uri, referer_uri, isPrivate = false) {
+ var uri = NetUtil.newURI(server_uri);
+ let referrer = NetUtil.newURI(referer_uri);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ referrer,
+ { privateBrowsingId: isPrivate ? 1 : 0 }
+ );
+ var chan = NetUtil.newChannel({
+ uri,
+ loadingPrincipal: principal,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ });
+
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ referrer
+ );
+ var header = null;
+ try {
+ header = chan.getRequestHeader("Referer");
+ } catch (NS_ERROR_NOT_AVAILABLE) {}
+ return header;
+}
+
+function run_test() {
+ var prefs = Services.prefs;
+
+ var server_uri = "http://bar.examplesite.com/path2";
+ var server_uri_2 = "http://bar.example.com/anotherpath";
+ var referer_uri = "http://foo.example.com/path";
+ var referer_uri_2 = "http://bar.examplesite.com/path3?q=blah";
+ var referer_uri_2_anchor = "http://bar.examplesite.com/path3?q=blah#anchor";
+ var referer_uri_idn = "http://sub1.\xe4lt.example/path";
+
+ // for https tests
+ var server_uri_https = "https://bar.example.com/anotherpath";
+ var referer_uri_https = "https://bar.example.com/path3?q=blah";
+ var referer_uri_2_https = "https://bar.examplesite.com/path3?q=blah";
+
+ // tests for sendRefererHeader
+ prefs.setIntPref("network.http.sendRefererHeader", 0);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri));
+ prefs.setIntPref("network.http.sendRefererHeader", 2);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri),
+ "http://foo.example.com/"
+ );
+
+ // test that https ref is not sent to http
+ Assert.equal(null, getTestReferrer(server_uri_2, referer_uri_https));
+
+ // tests for referer.defaultPolicy
+ prefs.setIntPref("network.http.referer.defaultPolicy", 0);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri));
+ prefs.setIntPref("network.http.referer.defaultPolicy", 1);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri));
+ Assert.equal(getTestReferrer(server_uri, referer_uri_2), referer_uri_2);
+ prefs.setIntPref("network.http.referer.defaultPolicy", 2);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri_https));
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_https),
+ referer_uri_https
+ );
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_2_https),
+ "https://bar.examplesite.com/"
+ );
+ Assert.equal(getTestReferrer(server_uri, referer_uri_2), referer_uri_2);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri),
+ "http://foo.example.com/"
+ );
+ prefs.setIntPref("network.http.referer.defaultPolicy", 3);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), referer_uri);
+ Assert.equal(null, getTestReferrer(server_uri_2, referer_uri_https));
+
+ // tests for referer.defaultPolicy.pbmode
+ prefs.setIntPref("network.http.referer.defaultPolicy.pbmode", 0);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri, true));
+ prefs.setIntPref("network.http.referer.defaultPolicy.pbmode", 1);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri, true));
+ Assert.equal(getTestReferrer(server_uri, referer_uri_2, true), referer_uri_2);
+ prefs.setIntPref("network.http.referer.defaultPolicy.pbmode", 2);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri_https, true));
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_https, true),
+ referer_uri_https
+ );
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_2_https, true),
+ "https://bar.examplesite.com/"
+ );
+ Assert.equal(getTestReferrer(server_uri, referer_uri_2, true), referer_uri_2);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri, true),
+ "http://foo.example.com/"
+ );
+ prefs.setIntPref("network.http.referer.defaultPolicy.pbmode", 3);
+ Assert.equal(getTestReferrer(server_uri, referer_uri, true), referer_uri);
+ Assert.equal(null, getTestReferrer(server_uri_2, referer_uri_https, true));
+
+ // tests for referer.spoofSource
+ prefs.setBoolPref("network.http.referer.spoofSource", true);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), server_uri);
+ prefs.setBoolPref("network.http.referer.spoofSource", false);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // tests for referer.XOriginPolicy
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 2);
+ Assert.equal(null, getTestReferrer(server_uri_2, referer_uri));
+ Assert.equal(getTestReferrer(server_uri, referer_uri_2), referer_uri_2);
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 1);
+ Assert.equal(getTestReferrer(server_uri_2, referer_uri), referer_uri);
+ Assert.equal(null, getTestReferrer(server_uri, referer_uri));
+ // https test
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_https),
+ referer_uri_https
+ );
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 0);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // tests for referer.trimmingPolicy
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2),
+ "http://bar.examplesite.com/path3"
+ );
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_idn),
+ "http://sub1.xn--lt-uia.example/path"
+ );
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 2);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2),
+ "http://bar.examplesite.com/"
+ );
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_idn),
+ "http://sub1.xn--lt-uia.example/"
+ );
+ // https test
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_https),
+ "https://bar.example.com/"
+ );
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
+ // test that anchor is lopped off in ordinary case
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2_anchor),
+ referer_uri_2
+ );
+
+ // tests for referer.XOriginTrimmingPolicy
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 1);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri),
+ "http://foo.example.com/path"
+ );
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_idn),
+ "http://sub1.xn--lt-uia.example/path"
+ );
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2),
+ "http://bar.examplesite.com/path3?q=blah"
+ );
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2),
+ "http://bar.examplesite.com/path3"
+ );
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 2);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri),
+ "http://foo.example.com/"
+ );
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_idn),
+ "http://sub1.xn--lt-uia.example/"
+ );
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2),
+ "http://bar.examplesite.com/path3"
+ );
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2),
+ "http://bar.examplesite.com/path3?q=blah"
+ );
+ // https tests
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_https),
+ "https://bar.example.com/path3?q=blah"
+ );
+ Assert.equal(
+ getTestReferrer(server_uri_https, referer_uri_2_https),
+ "https://bar.examplesite.com/"
+ );
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 0);
+ // test that anchor is lopped off in ordinary case
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri_2_anchor),
+ referer_uri_2
+ );
+
+ // test referrer length limitation
+ // referer_uri's length is 27 and origin's length is 23
+ prefs.setIntPref("network.http.referer.referrerLengthLimit", 27);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), referer_uri);
+ prefs.setIntPref("network.http.referer.referrerLengthLimit", 26);
+ Assert.equal(
+ getTestReferrer(server_uri, referer_uri),
+ "http://foo.example.com/"
+ );
+ prefs.setIntPref("network.http.referer.referrerLengthLimit", 22);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), null);
+ prefs.setIntPref("network.http.referer.referrerLengthLimit", 0);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), referer_uri);
+ prefs.setIntPref("network.http.referer.referrerLengthLimit", 4096);
+ Assert.equal(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // combination test: send spoofed path-only when hosts match
+ var combo_referer_uri = "http://blah.foo.com/path?q=hot";
+ var dest_uri = "http://blah.foo.com:9999/spoofedpath?q=bad";
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ prefs.setBoolPref("network.http.referer.spoofSource", true);
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 2);
+ Assert.equal(
+ getTestReferrer(dest_uri, combo_referer_uri),
+ "http://blah.foo.com:9999/spoofedpath"
+ );
+ Assert.equal(
+ null,
+ getTestReferrer(dest_uri, "http://gah.foo.com/anotherpath")
+ );
+}
diff --git a/netwerk/test/unit/test_referrer_cross_origin.js b/netwerk/test/unit/test_referrer_cross_origin.js
new file mode 100644
index 0000000000..ada64fcced
--- /dev/null
+++ b/netwerk/test/unit/test_referrer_cross_origin.js
@@ -0,0 +1,332 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+function test_policy(test) {
+ info("Running test: " + test.toSource());
+
+ let prefs = Services.prefs;
+
+ if (test.trimmingPolicy !== undefined) {
+ prefs.setIntPref(
+ "network.http.referer.trimmingPolicy",
+ test.trimmingPolicy
+ );
+ } else {
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
+ }
+
+ if (test.XOriginTrimmingPolicy !== undefined) {
+ prefs.setIntPref(
+ "network.http.referer.XOriginTrimmingPolicy",
+ test.XOriginTrimmingPolicy
+ );
+ } else {
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 0);
+ }
+
+ if (test.disallowRelaxingDefault) {
+ prefs.setBoolPref(
+ "network.http.referer.disallowCrossSiteRelaxingDefault",
+ test.disallowRelaxingDefault
+ );
+ } else {
+ prefs.setBoolPref(
+ "network.http.referer.disallowCrossSiteRelaxingDefault",
+ false
+ );
+ }
+
+ let referrer = NetUtil.newURI(test.referrer);
+ let triggeringPrincipal =
+ Services.scriptSecurityManager.createContentPrincipal(referrer, {});
+ let chan = NetUtil.newChannel({
+ uri: test.url,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ triggeringPrincipal,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ });
+
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.referrerInfo = new ReferrerInfo(test.policy, true, referrer);
+
+ if (test.expectedReferrerSpec === undefined) {
+ try {
+ chan.getRequestHeader("Referer");
+ do_throw("Should not find a Referer header!");
+ } catch (e) {}
+ } else {
+ let header = chan.getRequestHeader("Referer");
+ Assert.equal(header, test.expectedReferrerSpec);
+ }
+}
+
+const nsIReferrerInfo = Ci.nsIReferrerInfo;
+var gTests = [
+ // Test same origin policy w/o cross origin
+ {
+ policy: nsIReferrerInfo.SAME_ORIGIN,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo?a",
+ },
+ {
+ policy: nsIReferrerInfo.SAME_ORIGIN,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.SAME_ORIGIN,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo",
+ },
+ {
+ policy: nsIReferrerInfo.SAME_ORIGIN,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.SAME_ORIGIN,
+ trimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/",
+ },
+ {
+ policy: nsIReferrerInfo.SAME_ORIGIN,
+ trimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+
+ // Test origin when xorigin policy w/o cross origin
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo?a",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo?a",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo?a",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+
+ // Test strict origin when xorigin policy w/o cross origin
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo?a",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ url: "http://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 1,
+ url: "http://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ trimmingPolicy: 2,
+ url: "http://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo?a",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 1,
+ url: "http://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo?a",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: "https://foo.example/",
+ },
+ {
+ policy: nsIReferrerInfo.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
+ XOriginTrimmingPolicy: 2,
+ url: "http://test.example/foo?a",
+ referrer: "https://foo.example/foo?a",
+ expectedReferrerSpec: undefined,
+ },
+
+ // Test mix and choose max of XOriginTrimmingPolicy and trimmingPolicy
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ XOriginTrimmingPolicy: 2,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://test1.example/foo?a",
+ expectedReferrerSpec: "https://test1.example/",
+ },
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ XOriginTrimmingPolicy: 2,
+ trimmingPolicy: 1,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/foo",
+ },
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ XOriginTrimmingPolicy: 1,
+ trimmingPolicy: 2,
+ url: "https://test.example/foo?a",
+ referrer: "https://test.example/foo?a",
+ expectedReferrerSpec: "https://test.example/",
+ },
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ XOriginTrimmingPolicy: 1,
+ trimmingPolicy: 0,
+ url: "https://test.example/foo?a",
+ referrer: "https://test1.example/foo?a",
+ expectedReferrerSpec: "https://test1.example/foo",
+ },
+];
+
+function run_test() {
+ gTests.forEach(test => test_policy(test));
+ Services.prefs.clearUserPref("network.http.referer.trimmingPolicy");
+ Services.prefs.clearUserPref("network.http.referer.XOriginTrimmingPolicy");
+ Services.prefs.clearUserPref(
+ "network.http.referer.disallowCrossSiteRelaxingDefault"
+ );
+}
diff --git a/netwerk/test/unit/test_referrer_policy.js b/netwerk/test/unit/test_referrer_policy.js
new file mode 100644
index 0000000000..18b1cb3a16
--- /dev/null
+++ b/netwerk/test/unit/test_referrer_policy.js
@@ -0,0 +1,154 @@
+"use strict";
+
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+function test_policy(test) {
+ info("Running test: " + test.toSource());
+
+ var prefs = Services.prefs;
+ if (test.defaultReferrerPolicyPref !== undefined) {
+ prefs.setIntPref(
+ "network.http.referer.defaultPolicy",
+ test.defaultReferrerPolicyPref
+ );
+ } else {
+ prefs.setIntPref("network.http.referer.defaultPolicy", 3);
+ }
+
+ if (test.disallowRelaxingDefault) {
+ prefs.setBoolPref(
+ "network.http.referer.disallowCrossSiteRelaxingDefault",
+ test.disallowRelaxingDefault
+ );
+ } else {
+ prefs.setBoolPref(
+ "network.http.referer.disallowCrossSiteRelaxingDefault",
+ false
+ );
+ }
+
+ var uri = NetUtil.newURI(test.url);
+ var chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+
+ var referrer = NetUtil.newURI(test.referrer);
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.referrerInfo = new ReferrerInfo(test.policy, true, referrer);
+
+ if (test.expectedReferrerSpec === undefined) {
+ try {
+ chan.getRequestHeader("Referer");
+ do_throw("Should not find a Referer header!");
+ } catch (e) {}
+ } else {
+ var header = chan.getRequestHeader("Referer");
+ Assert.equal(header, test.expectedReferrerSpec);
+ }
+}
+
+const nsIReferrerInfo = Ci.nsIReferrerInfo;
+// Assuming cross origin because we have no triggering principal available
+var gTests = [
+ {
+ policy: nsIReferrerInfo.EMPTY,
+ defaultReferrerPolicyPref: 0,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.EMPTY,
+ defaultReferrerPolicyPref: 1,
+ url: "http://test.example/foo",
+ referrer: "http://test1.example/referrer",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.EMPTY,
+ defaultReferrerPolicyPref: 2,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/",
+ },
+ {
+ policy: nsIReferrerInfo.EMPTY,
+ defaultReferrerPolicyPref: 2,
+ url: "https://test.example/foo",
+ referrer: "https://test1.example/referrer",
+ expectedReferrerSpec: "https://test1.example/",
+ },
+ {
+ policy: nsIReferrerInfo.EMPTY,
+ defaultReferrerPolicyPref: 3,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer",
+ },
+ {
+ policy: nsIReferrerInfo.EMPTY,
+ defaultReferrerPolicyPref: 3,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer",
+ },
+ {
+ policy: nsIReferrerInfo.EMPTY,
+ defaultReferrerPolicyPref: 3,
+ url: "http://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.NO_REFERRER,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: undefined,
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/",
+ },
+ {
+ policy: nsIReferrerInfo.ORIGIN,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/",
+ },
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer",
+ },
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer",
+ },
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ url: "http://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer",
+ },
+ {
+ policy: nsIReferrerInfo.UNSAFE_URL,
+ url: "http://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer",
+ },
+];
+
+function run_test() {
+ gTests.forEach(test => test_policy(test));
+ Services.prefs.clearUserPref("network.http.referer.disallowRelaxingDefault");
+}
diff --git a/netwerk/test/unit/test_reopen.js b/netwerk/test/unit/test_reopen.js
new file mode 100644
index 0000000000..6bae0e71cf
--- /dev/null
+++ b/netwerk/test/unit/test_reopen.js
@@ -0,0 +1,136 @@
+// This testcase verifies that channels can't be reopened
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=372486
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+const NS_ERROR_IN_PROGRESS = 0x804b000f;
+const NS_ERROR_ALREADY_OPENED = 0x804b0049;
+
+var chan = null;
+var httpserv = null;
+
+[test_data_channel, test_http_channel, test_file_channel, end].forEach(f =>
+ add_test(f)
+);
+
+// Utility functions
+
+function makeChan(url) {
+ return (chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIChannel));
+}
+
+function new_file_channel(file) {
+ return NetUtil.newChannel({
+ uri: Services.io.newFileURI(file),
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function check_throws(closure, error) {
+ var thrown = false;
+ try {
+ closure();
+ } catch (e) {
+ if (error instanceof Array) {
+ Assert.notEqual(error.indexOf(e.result), -1);
+ } else {
+ Assert.equal(e.result, error);
+ }
+ thrown = true;
+ }
+ Assert.ok(thrown);
+}
+
+function check_open_throws(error) {
+ check_throws(function () {
+ chan.open(listener);
+ }, error);
+}
+
+function check_async_open_throws(error) {
+ check_throws(function () {
+ chan.asyncOpen(listener);
+ }, error);
+}
+
+var listener = {
+ onStartRequest: function test_onStartR(request) {
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+ },
+
+ onDataAvailable: function test_ODA(request, inputStream, offset, count) {
+ new BinaryInputStream(inputStream).readByteArray(count); // required by API
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ // Once onStopRequest is reached, the channel is marked as having been
+ // opened
+ check_async_open_throws(NS_ERROR_ALREADY_OPENED);
+ do_timeout(0, after_channel_closed);
+ },
+};
+
+function after_channel_closed() {
+ check_async_open_throws(NS_ERROR_ALREADY_OPENED);
+
+ run_next_test();
+}
+
+function test_channel(createChanClosure) {
+ // First, synchronous reopening test
+ chan = createChanClosure();
+ chan.open();
+ check_open_throws(NS_ERROR_IN_PROGRESS);
+ check_async_open_throws([NS_ERROR_IN_PROGRESS, NS_ERROR_ALREADY_OPENED]);
+
+ // Then, asynchronous one
+ chan = createChanClosure();
+ chan.asyncOpen(listener);
+ check_open_throws(NS_ERROR_IN_PROGRESS);
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+}
+
+function test_data_channel() {
+ test_channel(function () {
+ return makeChan("data:text/plain,foo");
+ });
+}
+
+function test_http_channel() {
+ test_channel(function () {
+ return makeChan("http://localhost:" + httpserv.identity.primaryPort + "/");
+ });
+}
+
+function test_file_channel() {
+ var file = do_get_file("data/test_readline1.txt");
+ test_channel(function () {
+ return new_file_channel(file);
+ });
+}
+
+function end() {
+ httpserv.stop(do_test_finished);
+}
+
+function run_test() {
+ // start server
+ httpserv = new HttpServer();
+ httpserv.start(-1);
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_reply_without_content_type.js b/netwerk/test/unit/test_reply_without_content_type.js
new file mode 100644
index 0000000000..f32ad81d84
--- /dev/null
+++ b/netwerk/test/unit/test_reply_without_content_type.js
@@ -0,0 +1,151 @@
+//
+// tests HTTP replies that lack content-type (where we try to sniff content-type).
+//
+
+// Note: sets Cc and Ci variables
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var testpath = "/simple_plainText";
+var httpbody = "<html><body>omg hai</body></html>";
+var testpathGZip = "/simple_gzip";
+//this is compressed httpbody;
+var httpbodyGZip = [
+ "0x1f",
+ "0x8b",
+ "0x8",
+ "0x0",
+ "0x0",
+ "0x0",
+ "0x0",
+ "0x0",
+ "0x0",
+ "0x3",
+ "0xb3",
+ "0xc9",
+ "0x28",
+ "0xc9",
+ "0xcd",
+ "0xb1",
+ "0xb3",
+ "0x49",
+ "0xca",
+ "0x4f",
+ "0xa9",
+ "0xb4",
+ "0xcb",
+ "0xcf",
+ "0x4d",
+ "0x57",
+ "0xc8",
+ "0x48",
+ "0xcc",
+ "0xb4",
+ "0xd1",
+ "0x7",
+ "0xf3",
+ "0x6c",
+ "0xf4",
+ "0xc1",
+ "0x52",
+ "0x0",
+ "0x4",
+ "0x99",
+ "0x79",
+ "0x2b",
+ "0x21",
+ "0x0",
+ "0x0",
+ "0x0",
+];
+
+var dbg = 0;
+if (dbg) {
+ print("============== START ==========");
+}
+
+add_test(function test_plainText() {
+ if (dbg) {
+ print("============== test_plainText: in");
+ }
+ httpserver.registerPathHandler(testpath, serverHandler_plainText);
+ httpserver.start(-1);
+ var channel = setupChannel(testpath);
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen(new ChannelListener(checkRequest, channel));
+ do_test_pending();
+ if (dbg) {
+ print("============== test_plainText: out");
+ }
+});
+
+add_test(function test_GZip() {
+ if (dbg) {
+ print("============== test_GZip: in");
+ }
+ httpserver.registerPathHandler(testpathGZip, serverHandler_GZip);
+ httpserver.start(-1);
+ var channel = setupChannel(testpathGZip);
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen(new ChannelListener(checkRequest, channel, CL_EXPECT_GZIP));
+ do_test_pending();
+ if (dbg) {
+ print("============== test_GZip: out");
+ }
+});
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + path,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler_plainText(metadata, response) {
+ if (dbg) {
+ print("============== serverHandler plainText: in");
+ }
+ // no content type set
+ // response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ if (dbg) {
+ print("============== serverHandler plainText: out");
+ }
+}
+
+function serverHandler_GZip(metadata, response) {
+ if (dbg) {
+ print("============== serverHandler GZip: in");
+ }
+ // no content type set
+ // response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIBinaryOutputStream
+ );
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(httpbodyGZip);
+ if (dbg) {
+ print("============== serverHandler GZip: out");
+ }
+}
+
+function checkRequest(request, data, context) {
+ if (dbg) {
+ print("============== checkRequest: in");
+ }
+ Assert.equal(data, httpbody);
+ Assert.equal(request.QueryInterface(Ci.nsIChannel).contentType, "text/html");
+ httpserver.stop(do_test_finished);
+ run_next_test();
+ if (dbg) {
+ print("============== checkRequest: out");
+ }
+}
diff --git a/netwerk/test/unit/test_resumable_channel.js b/netwerk/test/unit/test_resumable_channel.js
new file mode 100644
index 0000000000..0407aa4983
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_channel.js
@@ -0,0 +1,426 @@
+/* Tests various aspects of nsIResumableChannel in combination with HTTP */
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = null;
+
+const NS_ERROR_ENTITY_CHANGED = 0x804b0020;
+const NS_ERROR_NOT_RESUMABLE = 0x804b0019;
+
+const rangeBody = "Body of the range request handler.\r\n";
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function AuthPrompt2() {}
+
+AuthPrompt2.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap2_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor() {}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2) {
+ this.prompt2 = new AuthPrompt2();
+ }
+ return this.prompt2;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt2: null,
+};
+
+function run_test() {
+ dump("*** run_test\n");
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/auth", authHandler);
+ httpserver.registerPathHandler("/range", rangeHandler);
+ httpserver.registerPathHandler("/acceptranges", acceptRangesHandler);
+ httpserver.registerPathHandler("/redir", redirHandler);
+
+ var entityID;
+
+ function get_entity_id(request, data, ctx) {
+ dump("*** get_entity_id()\n");
+ Assert.ok(
+ request instanceof Ci.nsIResumableChannel,
+ "must be a resumable channel"
+ );
+ entityID = request.entityID;
+ dump("*** entity id = " + entityID + "\n");
+
+ // Try a non-resumable URL (responds with 200)
+ var chan = make_channel(URL);
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen(new ChannelListener(try_resume, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_resume(request, data, ctx) {
+ dump("*** try_resume()\n");
+ Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a successful resume
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen(new ChannelListener(try_resume_zero, null));
+ }
+
+ function try_resume_zero(request, data, ctx) {
+ dump("*** try_resume_zero()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(data, rangeBody.substring(1));
+
+ // Try a server which doesn't support range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "none", false);
+ chan.asyncOpen(new ChannelListener(try_no_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_no_range(request, data, ctx) {
+ dump("*** try_no_range()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which supports "bytes" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytes", false);
+ chan.asyncOpen(new ChannelListener(try_bytes_range, null));
+ }
+
+ function try_bytes_range(request, data, ctx) {
+ dump("*** try_bytes_range()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(data, rangeBody);
+
+ // Try a server which supports "foo" and "bar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foo, bar", false);
+ chan.asyncOpen(
+ new ChannelListener(try_foo_bar_range, null, CL_EXPECT_FAILURE)
+ );
+ }
+
+ function try_foo_bar_range(request, data, ctx) {
+ dump("*** try_foo_bar_range()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which supports "foobar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foobar", false);
+ chan.asyncOpen(
+ new ChannelListener(try_foobar_range, null, CL_EXPECT_FAILURE)
+ );
+ }
+
+ function try_foobar_range(request, data, ctx) {
+ dump("*** try_foobar_range()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which supports "bytes" and "foobar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader(
+ "X-Range-Type",
+ "bytes, foobar",
+ false
+ );
+ chan.asyncOpen(new ChannelListener(try_bytes_foobar_range, null));
+ }
+
+ function try_bytes_foobar_range(request, data, ctx) {
+ dump("*** try_bytes_foobar_range()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(data, rangeBody);
+
+ // Try a server which supports "bytesfoo" and "bar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader(
+ "X-Range-Type",
+ "bytesfoo, bar",
+ false
+ );
+ chan.asyncOpen(
+ new ChannelListener(try_bytesfoo_bar_range, null, CL_EXPECT_FAILURE)
+ );
+ }
+
+ function try_bytesfoo_bar_range(request, data, ctx) {
+ dump("*** try_bytesfoo_bar_range()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which doesn't send Accept-Ranges header at all
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen(new ChannelListener(try_no_accept_ranges, null));
+ }
+
+ function try_no_accept_ranges(request, data, ctx) {
+ dump("*** try_no_accept_ranges()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(data, rangeBody);
+
+ // Try a successful suspend/resume from 0
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen(
+ new ChannelListener(
+ try_suspend_resume,
+ null,
+ CL_SUSPEND | CL_EXPECT_3S_DELAY
+ )
+ );
+ }
+
+ function try_suspend_resume(request, data, ctx) {
+ dump("*** try_suspend_resume()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(data, rangeBody);
+
+ // Try a successful resume from 0
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen(new ChannelListener(success, null));
+ }
+
+ function success(request, data, ctx) {
+ dump("*** success()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(data, rangeBody);
+
+ // Authentication (no password; working resume)
+ // (should not give us any data)
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
+ chan.asyncOpen(
+ new ChannelListener(test_auth_nopw, null, CL_EXPECT_FAILURE)
+ );
+ }
+
+ function test_auth_nopw(request, data, ctx) {
+ dump("*** test_auth_nopw()\n");
+ Assert.ok(!request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(request.status, NS_ERROR_ENTITY_CHANGED);
+
+ // Authentication + not working resume
+ var chan = make_channel(
+ "http://guest:guest@localhost:" +
+ httpserver.identity.primaryPort +
+ "/auth"
+ );
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.notificationCallbacks = new Requestor();
+ chan.asyncOpen(new ChannelListener(test_auth, null, CL_EXPECT_FAILURE));
+ }
+ function test_auth(request, data, ctx) {
+ dump("*** test_auth()\n");
+ Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
+ Assert.ok(request.nsIHttpChannel.responseStatus < 300);
+
+ // Authentication + working resume
+ var chan = make_channel(
+ "http://guest:guest@localhost:" +
+ httpserver.identity.primaryPort +
+ "/range"
+ );
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.notificationCallbacks = new Requestor();
+ chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
+ chan.asyncOpen(new ChannelListener(test_auth_resume, null));
+ }
+
+ function test_auth_resume(request, data, ctx) {
+ dump("*** test_auth_resume()\n");
+ Assert.equal(data, rangeBody.substring(1));
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+
+ // 404 page (same content length as real content)
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Want-404", "true", false);
+ chan.asyncOpen(new ChannelListener(test_404, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_404(request, data, ctx) {
+ dump("*** test_404()\n");
+ Assert.equal(request.status, NS_ERROR_ENTITY_CHANGED);
+ Assert.equal(request.nsIHttpChannel.responseStatus, 404);
+
+ // 416 Requested Range Not Satisfiable
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1000, entityID);
+ chan.asyncOpen(new ChannelListener(test_416, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_416(request, data, ctx) {
+ dump("*** test_416()\n");
+ Assert.equal(request.status, NS_ERROR_ENTITY_CHANGED);
+ Assert.equal(request.nsIHttpChannel.responseStatus, 416);
+
+ // Redirect + successful resume
+ var chan = make_channel(URL + "/redir");
+ chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/range", false);
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen(new ChannelListener(test_redir_resume, null));
+ }
+
+ function test_redir_resume(request, data, ctx) {
+ dump("*** test_redir_resume()\n");
+ Assert.ok(request.nsIHttpChannel.requestSucceeded);
+ Assert.equal(data, rangeBody.substring(1));
+ Assert.equal(request.nsIHttpChannel.responseStatus, 206);
+
+ // Redirect + failed resume
+ var chan = make_channel(URL + "/redir");
+ chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/", false);
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen(
+ new ChannelListener(test_redir_noresume, null, CL_EXPECT_FAILURE)
+ );
+ }
+
+ function test_redir_noresume(request, data, ctx) {
+ dump("*** test_redir_noresume()\n");
+ Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ httpserver.stop(do_test_finished);
+ }
+
+ httpserver.start(-1);
+ var chan = make_channel(URL + "/range");
+ chan.asyncOpen(new ChannelListener(get_entity_id, null));
+ do_test_pending();
+}
+
+// HANDLERS
+
+function handleAuth(metadata, response) {
+ // btoa("guest:guest"), but that function is not available here
+ var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ if (
+ metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ return true;
+ }
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ return false;
+}
+
+// /auth
+function authHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ var body = handleAuth(metadata, response) ? "success" : "failure";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /range
+function rangeHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+
+ if (metadata.hasHeader("X-Need-Auth")) {
+ if (!handleAuth(metadata, response)) {
+ body = "auth failed";
+ response.bodyOutputStream.write(body, body.length);
+ return;
+ }
+ }
+
+ if (metadata.hasHeader("X-Want-404")) {
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ body = rangeBody;
+ response.bodyOutputStream.write(body, body.length);
+ return;
+ }
+
+ var body = rangeBody;
+
+ if (metadata.hasHeader("Range")) {
+ // Syntax: bytes=[from]-[to] (we don't support multiple ranges)
+ var matches = metadata
+ .getHeader("Range")
+ .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+ var from = matches[1] === undefined ? 0 : matches[1];
+ var to = matches[2] === undefined ? rangeBody.length - 1 : matches[2];
+ if (from >= rangeBody.length) {
+ response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
+ response.setHeader("Content-Range", "*/" + rangeBody.length, false);
+ return;
+ }
+ body = body.substring(from, to + 1);
+ // always respond to successful range requests with 206
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader(
+ "Content-Range",
+ from + "-" + to + "/" + rangeBody.length,
+ false
+ );
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /acceptranges
+function acceptRangesHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ if (metadata.hasHeader("X-Range-Type")) {
+ response.setHeader(
+ "Accept-Ranges",
+ metadata.getHeader("X-Range-Type"),
+ false
+ );
+ }
+ response.bodyOutputStream.write(rangeBody, rangeBody.length);
+}
+
+// /redir
+function redirHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Location", metadata.getHeader("X-Redir-To"), false);
+ var body = "redirect\r\n";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_resumable_truncate.js b/netwerk/test/unit/test_resumable_truncate.js
new file mode 100644
index 0000000000..cdcab591c2
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_truncate.js
@@ -0,0 +1,95 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function cachedHandler(metadata, response) {
+ var body = responseBody;
+ if (metadata.hasHeader("Range")) {
+ var matches = metadata
+ .getHeader("Range")
+ .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+ var from = matches[1] === undefined ? 0 : matches[1];
+ var to = matches[2] === undefined ? responseBody.length - 1 : matches[2];
+ if (from >= responseBody.length) {
+ response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
+ response.setHeader("Content-Range", "*/" + responseBody.length, false);
+ return;
+ }
+ body = responseBody.slice(from, to + 1);
+ // always respond to successful range requests with 206
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader(
+ "Content-Range",
+ from + "-" + to + "/" + responseBody.length,
+ false
+ );
+ }
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Accept-Ranges", "bytes");
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function Canceler(continueFn) {
+ this.continueFn = continueFn;
+}
+
+Canceler.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "nsIRequestObserver",
+ ]),
+
+ onStartRequest(request) {},
+
+ onDataAvailable(request, stream, offset, count) {
+ request.QueryInterface(Ci.nsIChannel).cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_BINDING_ABORTED);
+ this.continueFn();
+ },
+};
+
+function finish_test() {
+ httpserver.stop(do_test_finished);
+}
+
+function start_cache_read() {
+ var chan = make_channel(
+ "http://localhost:" + httpserver.identity.primaryPort + "/cached/test.gz"
+ );
+ chan.asyncOpen(new ChannelListener(finish_test, null));
+}
+
+function start_canceler() {
+ var chan = make_channel(
+ "http://localhost:" + httpserver.identity.primaryPort + "/cached/test.gz"
+ );
+ chan.asyncOpen(new Canceler(start_cache_read));
+}
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/cached/test.gz", cachedHandler);
+ httpserver.start(-1);
+
+ var chan = make_channel(
+ "http://localhost:" + httpserver.identity.primaryPort + "/cached/test.gz"
+ );
+ chan.asyncOpen(new ChannelListener(start_canceler, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_retry_0rtt.js b/netwerk/test/unit/test_retry_0rtt.js
new file mode 100644
index 0000000000..e1770ab8a1
--- /dev/null
+++ b/netwerk/test/unit/test_retry_0rtt.js
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+var httpServer = null;
+
+let handlerCallbacks = {};
+
+function listenHandler(metadata, response) {
+ info(metadata.path);
+ handlerCallbacks[metadata.path] = (handlerCallbacks[metadata.path] || 0) + 1;
+}
+
+function handlerCount(path) {
+ return handlerCallbacks[path] || 0;
+}
+
+ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs");
+
+// Bug 1805371: Tests that require FaultyServer can't currently be built
+// with system NSS.
+add_setup(
+ {
+ skip_if: () => AppConstants.MOZ_SYSTEM_NSS,
+ },
+ async () => {
+ httpServer = new HttpServer();
+ httpServer.registerPrefixHandler("/callback/", listenHandler);
+ httpServer.start(-1);
+
+ registerCleanupFunction(async () => {
+ await httpServer.stop();
+ });
+
+ Services.env.set(
+ "FAULTY_SERVER_CALLBACK_PORT",
+ httpServer.identity.primaryPort
+ );
+ Services.env.set("MOZ_TLS_SERVER_0RTT", "1");
+ await asyncStartTLSTestServer(
+ "FaultyServer",
+ "../../../security/manager/ssl/tests/unit/test_faulty_server"
+ );
+ let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
+ await nssComponent.asyncClearSSLExternalAndInternalSessionCache();
+ }
+);
+
+async function sleep(time) {
+ return new Promise(resolve => {
+ do_timeout(time * 1000, resolve);
+ });
+}
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((req, buffer) => resolve([req, buffer]), null, flags)
+ );
+ });
+}
+
+add_task(
+ {
+ skip_if: () => AppConstants.MOZ_SYSTEM_NSS,
+ },
+ async function testRetry0Rtt() {
+ var retryDomains = [
+ "0rtt-alert-bad-mac.example.com",
+ "0rtt-alert-protocol-version.example.com",
+ //"0rtt-alert-unexpected.example.com", // TODO(bug 1753204): uncomment this
+ ];
+
+ Services.prefs.setCharPref("network.dns.localDomains", retryDomains);
+
+ Services.prefs.setBoolPref("network.ssl_tokens_cache_enabled", true);
+
+ for (var i = 0; i < retryDomains.length; i++) {
+ {
+ let countOfEarlyData = handlerCount("/callback/1");
+ let chan = makeChan(`https://${retryDomains[i]}:8443`);
+ let [, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ ok(buf);
+ equal(
+ handlerCount("/callback/1"),
+ countOfEarlyData,
+ "no early data sent"
+ );
+ }
+
+ // The server has an anti-replay mechanism that prohibits it from
+ // accepting 0-RTT connections immediately at startup.
+ await sleep(1);
+
+ {
+ let countOfEarlyData = handlerCount("/callback/1");
+ let chan = makeChan(`https://${retryDomains[i]}:8443`);
+ let [, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ ok(buf);
+ equal(
+ handlerCount("/callback/1"),
+ countOfEarlyData + 1,
+ "got early data"
+ );
+ }
+ }
+ }
+);
diff --git a/netwerk/test/unit/test_safeoutputstream.js b/netwerk/test/unit/test_safeoutputstream.js
new file mode 100644
index 0000000000..4925394ce4
--- /dev/null
+++ b/netwerk/test/unit/test_safeoutputstream.js
@@ -0,0 +1,70 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+function write_atomic(file, str) {
+ var stream = Cc[
+ "@mozilla.org/network/atomic-file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, -1, -1, 0);
+ do {
+ var written = stream.write(str, str.length);
+ if (written == str.length) {
+ break;
+ }
+ str = str.substring(written);
+ } while (1);
+ stream.QueryInterface(Ci.nsISafeOutputStream).finish();
+ stream.close();
+}
+
+function write(file, str) {
+ var stream = Cc[
+ "@mozilla.org/network/safe-file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, -1, -1, 0);
+ do {
+ var written = stream.write(str, str.length);
+ if (written == str.length) {
+ break;
+ }
+ str = str.substring(written);
+ } while (1);
+ stream.QueryInterface(Ci.nsISafeOutputStream).finish();
+ stream.close();
+}
+
+function checkFile(file, str) {
+ var stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ stream.init(file, -1, -1, 0);
+
+ var scriptStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ scriptStream.init(stream);
+
+ Assert.equal(scriptStream.read(scriptStream.available()), str);
+ scriptStream.close();
+}
+
+function run_test() {
+ var filename = "\u0913";
+ var file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append(filename);
+
+ write(file, "First write");
+ checkFile(file, "First write");
+
+ write(file, "Second write");
+ checkFile(file, "Second write");
+
+ write_atomic(file, "First write: Atomic");
+ checkFile(file, "First write: Atomic");
+
+ write_atomic(file, "Second write: Atomic");
+ checkFile(file, "Second write: Atomic");
+}
diff --git a/netwerk/test/unit/test_safeoutputstream_append.js b/netwerk/test/unit/test_safeoutputstream_append.js
new file mode 100644
index 0000000000..9716001bd2
--- /dev/null
+++ b/netwerk/test/unit/test_safeoutputstream_append.js
@@ -0,0 +1,45 @@
+/* atomic-file-output-stream and safe-file-output-stream should throw and
+ * exception if PR_APPEND is explicity specified without PR_TRUNCATE. */
+"use strict";
+
+const PR_WRONLY = 0x02;
+const PR_CREATE_FILE = 0x08;
+const PR_APPEND = 0x10;
+const PR_TRUNCATE = 0x20;
+
+function check_flag(file, contractID, flags, throws) {
+ let stream = Cc[contractID].createInstance(Ci.nsIFileOutputStream);
+
+ if (throws) {
+ /* NS_ERROR_INVALID_ARG is reported as NS_ERROR_ILLEGAL_VALUE, since they
+ * are same value. */
+ Assert.throws(
+ () => stream.init(file, flags, 0o644, 0),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ } else {
+ stream.init(file, flags, 0o644, 0);
+ stream.close();
+ }
+}
+
+function run_test() {
+ let filename = "test.txt";
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append(filename);
+
+ let tests = [
+ [PR_WRONLY | PR_CREATE_FILE | PR_APPEND | PR_TRUNCATE, false],
+ [PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, false],
+ [PR_WRONLY | PR_CREATE_FILE | PR_APPEND, true],
+ [-1, false],
+ ];
+ for (let contractID of [
+ "@mozilla.org/network/atomic-file-output-stream;1",
+ "@mozilla.org/network/safe-file-output-stream;1",
+ ]) {
+ for (let [flags, throws] of tests) {
+ check_flag(file, contractID, flags, throws);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_schema_10_migration.js b/netwerk/test/unit/test_schema_10_migration.js
new file mode 100644
index 0000000000..af50c967fd
--- /dev/null
+++ b/netwerk/test/unit/test_schema_10_migration.js
@@ -0,0 +1,181 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test cookie database migration from version 10 (prerelease Gecko 2.0) to the
+// current version, presently 12.
+"use strict";
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ executeSoon(function () {
+ test_generator.return();
+ do_test_finished();
+ });
+}
+
+function* do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Start the cookieservice, to force creation of a database.
+ // Get the sessionCookies to join the initialization in cookie thread
+ Services.cookies.sessionCookies;
+
+ // Close the profile.
+ do_close_profile(test_generator);
+ yield;
+
+ // Remove the cookie file in order to create another database file.
+ do_get_cookie_file(profile).remove(false);
+
+ // Create a schema 10 database.
+ let schema10db = new CookieDatabaseConnection(
+ do_get_cookie_file(profile),
+ 10
+ );
+
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+
+ // Populate it, with:
+ // 1) Unexpired, unique cookies.
+ for (let i = 0; i < 20; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "foo.com",
+ "/",
+ futureExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema10db.insertCookie(cookie);
+ }
+
+ // 2) Expired, unique cookies.
+ for (let i = 20; i < 40; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "bar.com",
+ "/",
+ pastExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema10db.insertCookie(cookie);
+ }
+
+ // 3) Many copies of the same cookie, some of which have expired and
+ // some of which have not.
+ for (let i = 40; i < 45; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry + i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema10db.insertCookie(cookie);
+ } catch (e) {}
+ }
+ for (let i = 45; i < 50; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry - i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema10db.insertCookie(cookie);
+ } catch (e) {}
+ }
+ for (let i = 50; i < 55; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry - i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema10db.insertCookie(cookie);
+ } catch (e) {}
+ }
+ for (let i = 55; i < 60; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry + i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema10db.insertCookie(cookie);
+ } catch (e) {}
+ }
+
+ // Close it.
+ schema10db.close();
+ schema10db = null;
+
+ // Load the database, forcing migration to the current schema version. Then
+ // test the expected set of cookies:
+ do_load_profile();
+
+ // 1) All unexpired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ Assert.equal(Services.cookies.countCookiesFromHost("baz.com"), 1);
+ let cookies = Services.cookies.getCookiesFromHost("baz.com", {});
+ let cookie = cookies[0];
+ Assert.equal(cookie.expiry, futureExpiry + 40);
+
+ finish_test();
+}
diff --git a/netwerk/test/unit/test_schema_12_migration.js b/netwerk/test/unit/test_schema_12_migration.js
new file mode 100644
index 0000000000..ded67a1214
--- /dev/null
+++ b/netwerk/test/unit/test_schema_12_migration.js
@@ -0,0 +1,181 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test cookie database migration from version 12 to the current version,
+// presently 13, which added the "partitioned" cookie attribute.
+"use strict";
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ executeSoon(function () {
+ test_generator.return();
+ do_test_finished();
+ });
+}
+
+function* do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Start the cookieservice, to force creation of a database.
+ // Get the sessionCookies to join the initialization in cookie thread
+ Services.cookies.sessionCookies;
+
+ // Close the profile.
+ do_close_profile(test_generator);
+ yield;
+
+ // Remove the cookie file in order to create another database file.
+ do_get_cookie_file(profile).remove(false);
+
+ // Create a schema 12 database.
+ let schema12db = new CookieDatabaseConnection(
+ do_get_cookie_file(profile),
+ 12
+ );
+
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+
+ // Populate it, with:
+ // 1) Unexpired, unique cookies.
+ for (let i = 0; i < 20; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "foo.com",
+ "/",
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ schema12db.insertCookie(cookie);
+ }
+
+ // 2) Expired, unique cookies.
+ for (let i = 20; i < 40; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "bar.com",
+ "/",
+ pastExpiry,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ schema12db.insertCookie(cookie);
+ }
+
+ // 3) Many copies of the same cookie, some of which have expired and
+ // some of which have not.
+ for (let i = 40; i < 45; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry + i,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema12db.insertCookie(cookie);
+ } catch (e) {}
+ }
+ for (let i = 45; i < 50; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry - i,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema12db.insertCookie(cookie);
+ } catch (e) {}
+ }
+ for (let i = 50; i < 55; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry - i,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema12db.insertCookie(cookie);
+ } catch (e) {}
+ }
+ for (let i = 55; i < 60; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry + i,
+ now,
+ now,
+ false,
+ false,
+ false
+ );
+
+ try {
+ schema12db.insertCookie(cookie);
+ } catch (e) {}
+ }
+
+ // Close it.
+ schema12db.close();
+ schema12db = null;
+
+ // Load the database, forcing migration to the current schema version. Then
+ // test the expected set of cookies:
+ do_load_profile();
+
+ // 1) All unexpired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ Assert.equal(Services.cookies.countCookiesFromHost("baz.com"), 1);
+ let cookies = Services.cookies.getCookiesFromHost("baz.com", {});
+ let cookie = cookies[0];
+ Assert.equal(cookie.expiry, futureExpiry + 40);
+
+ finish_test();
+}
diff --git a/netwerk/test/unit/test_schema_13_db.js b/netwerk/test/unit/test_schema_13_db.js
new file mode 100644
index 0000000000..b79d89199b
--- /dev/null
+++ b/netwerk/test/unit/test_schema_13_db.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test cookie database schema 13
+"use strict";
+
+add_task(async function test_schema_13_db() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Start the cookieservice, to force creation of a database.
+ Services.cookies.sessionCookies;
+
+ // Assert schema 13 cookie db was created
+ Assert.ok(do_get_cookie_file(profile).exists());
+ let dbConnection = Services.storage.openDatabase(do_get_cookie_file(profile));
+ let version = dbConnection.schemaVersion;
+ dbConnection.close();
+ Assert.equal(version, 13);
+
+ // Close the profile.
+ await promise_close_profile();
+
+ // Open CookieDatabaseConnection to manipulate DB without using services.
+ let schema13db = new CookieDatabaseConnection(
+ do_get_cookie_file(profile),
+ 13
+ );
+
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+
+ let N = 20;
+ // Populate db with N unexpired, unique, partially partitioned cookies.
+ for (let i = 0; i < N; ++i) {
+ let cookie = new Cookie(
+ "test" + i,
+ "Some data",
+ "foo.com",
+ "/",
+ futureExpiry,
+ now,
+ now,
+ false,
+ false,
+ false,
+ false,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_UNSET,
+ !!(i % 2) // isPartitioned
+ );
+ schema13db.insertCookie(cookie);
+ }
+ schema13db.close();
+ schema13db = null;
+
+ // Reload profile.
+ await promise_load_profile();
+
+ // Assert inserted cookies are in the db and correctly handled by services.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), N);
+
+ // Open connection to manipulated db
+ dbConnection = Services.storage.openDatabase(do_get_cookie_file(profile));
+ // Check that schema is still correct after profile reload / db opening
+ Assert.equal(dbConnection.schemaVersion, 13);
+
+ // Count cookies with isPartitionedAttributeSet set to 1 (true)
+ let stmt = dbConnection.createStatement(
+ "SELECT COUNT(1) FROM moz_cookies WHERE isPartitionedAttributeSet = 1"
+ );
+ let success = stmt.executeStep();
+ Assert.ok(success);
+
+ // Assert the correct number of partitioned cookies was inserted / read
+ let isPartitionedAttributeSetCount = stmt.getInt32(0);
+ stmt.finalize();
+ Assert.equal(isPartitionedAttributeSetCount, N / 2);
+
+ // Cleanup
+ Services.cookies.removeAll();
+ stmt.finalize();
+ dbConnection.close();
+ do_close_profile();
+});
diff --git a/netwerk/test/unit/test_schema_2_migration.js b/netwerk/test/unit/test_schema_2_migration.js
new file mode 100644
index 0000000000..03588c0130
--- /dev/null
+++ b/netwerk/test/unit/test_schema_2_migration.js
@@ -0,0 +1,303 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test cookie database migration from version 2 (Gecko 1.9.3) to the current
+// version, presently 4 (Gecko 2.0).
+"use strict";
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ executeSoon(function () {
+ test_generator.return();
+ do_test_finished();
+ });
+}
+
+function* do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Start the cookieservice, to force creation of a database.
+ // Get the sessionCookies to join the initialization in cookie thread
+ Services.cookies.sessionCookies;
+
+ // Close the profile.
+ do_close_profile(test_generator);
+ yield;
+
+ // Remove the cookie file in order to create another database file.
+ do_get_cookie_file(profile).remove(false);
+
+ // Create a schema 2 database.
+ let schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+
+ // Populate it, with:
+ // 1) Unexpired, unique cookies.
+ for (let i = 0; i < 20; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "foo.com",
+ "/",
+ futureExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // 2) Expired, unique cookies.
+ for (let i = 20; i < 40; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "bar.com",
+ "/",
+ pastExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // 3) Many copies of the same cookie, some of which have expired and
+ // some of which have not.
+ for (let i = 40; i < 45; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry + i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 45; i < 50; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry - i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 50; i < 55; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry - i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 55; i < 60; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry + i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // Close it.
+ schema2db.close();
+ schema2db = null;
+
+ // Load the database, forcing migration to the current schema version. Then
+ // test the expected set of cookies:
+ do_load_profile();
+
+ // 1) All unexpired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ Assert.equal(Services.cookies.countCookiesFromHost("baz.com"), 1);
+ let cookies = Services.cookies.getCookiesFromHost("baz.com", {});
+ let cookie = cookies[0];
+ Assert.equal(cookie.expiry, futureExpiry + 44);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database so we can execute some more schema 2 statements on it.
+ schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+
+ // Populate it with more cookies.
+ for (let i = 60; i < 80; ++i) {
+ schema2db.insertCookie(
+ new Cookie(
+ "oh" + i,
+ "hai",
+ "foo.com",
+ "/",
+ futureExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ )
+ );
+ }
+ for (let i = 80; i < 100; ++i) {
+ schema2db.insertCookie(
+ new Cookie(
+ "oh" + i,
+ "hai",
+ "cat.com",
+ "/",
+ futureExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ )
+ );
+ }
+
+ // Attempt to add a cookie with the same (name, host, path) values as another
+ // cookie. This should succeed since we have a REPLACE clause for conflict on
+ // the unique index.
+ cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry,
+ now,
+ now + 100,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+
+ // Check that there is, indeed, a singular cookie for baz.com.
+ Assert.equal(do_count_cookies_in_db(schema2db.db, "baz.com"), 1);
+
+ // Close it.
+ schema2db.close();
+ schema2db = null;
+
+ // Back up the database, so we can test both asynchronous and synchronous
+ // loading separately.
+ let file = do_get_cookie_file(profile);
+ let copy = profile.clone();
+ copy.append("cookies.sqlite.copy");
+ file.copyTo(null, copy.leafName);
+
+ // Load the database asynchronously, forcing a purge of the newly-added
+ // cookies. (Their baseDomain column will be NULL.)
+ do_load_profile(test_generator);
+ yield;
+
+ // Test the expected set of cookies.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 40);
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 20);
+ Assert.equal(Services.cookies.countCookiesFromHost("baz.com"), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost("cat.com"), 20);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Copy the database back.
+ file.remove(false);
+ copy.copyTo(null, file.leafName);
+
+ // Load the database host-at-a-time.
+ do_load_profile();
+
+ // Test the expected set of cookies.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 40);
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 20);
+ Assert.equal(Services.cookies.countCookiesFromHost("baz.com"), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost("cat.com"), 20);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database and prove that they were deleted.
+ schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ Assert.equal(do_count_cookies_in_db(schema2db.db), 81);
+ Assert.equal(do_count_cookies_in_db(schema2db.db, "foo.com"), 40);
+ Assert.equal(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
+ schema2db.close();
+
+ // Copy the database back.
+ file.remove(false);
+ copy.copyTo(null, file.leafName);
+
+ // Load the database synchronously, in its entirety.
+ do_load_profile();
+ Assert.equal(do_count_cookies(), 81);
+
+ // Test the expected set of cookies.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 40);
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 20);
+ Assert.equal(Services.cookies.countCookiesFromHost("baz.com"), 1);
+ Assert.equal(Services.cookies.countCookiesFromHost("cat.com"), 20);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database and prove that they were deleted.
+ schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ Assert.equal(do_count_cookies_in_db(schema2db.db), 81);
+ Assert.equal(do_count_cookies_in_db(schema2db.db, "foo.com"), 40);
+ Assert.equal(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
+ schema2db.close();
+
+ finish_test();
+}
diff --git a/netwerk/test/unit/test_schema_3_migration.js b/netwerk/test/unit/test_schema_3_migration.js
new file mode 100644
index 0000000000..7b5c639950
--- /dev/null
+++ b/netwerk/test/unit/test_schema_3_migration.js
@@ -0,0 +1,170 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test cookie database migration from version 3 (prerelease Gecko 2.0) to the
+// current version, presently 4 (Gecko 2.0).
+"use strict";
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ executeSoon(function () {
+ test_generator.return();
+ do_test_finished();
+ });
+}
+
+function* do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Start the cookieservice, to force creation of a database.
+ // Get the sessionCookies to join the initialization in cookie thread
+ Services.cookies.sessionCookies;
+
+ // Close the profile.
+ do_close_profile(test_generator);
+ yield;
+
+ // Remove the cookie file in order to create another database file.
+ do_get_cookie_file(profile).remove(false);
+
+ // Create a schema 3 database.
+ let schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
+
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+
+ // Populate it, with:
+ // 1) Unexpired, unique cookies.
+ for (let i = 0; i < 20; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "foo.com",
+ "/",
+ futureExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema3db.insertCookie(cookie);
+ }
+
+ // 2) Expired, unique cookies.
+ for (let i = 20; i < 40; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "bar.com",
+ "/",
+ pastExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema3db.insertCookie(cookie);
+ }
+
+ // 3) Many copies of the same cookie, some of which have expired and
+ // some of which have not.
+ for (let i = 40; i < 45; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry + i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema3db.insertCookie(cookie);
+ }
+ for (let i = 45; i < 50; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry - i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema3db.insertCookie(cookie);
+ }
+ for (let i = 50; i < 55; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ futureExpiry - i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema3db.insertCookie(cookie);
+ }
+ for (let i = 55; i < 60; ++i) {
+ let cookie = new Cookie(
+ "oh",
+ "hai",
+ "baz.com",
+ "/",
+ pastExpiry + i,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema3db.insertCookie(cookie);
+ }
+
+ // Close it.
+ schema3db.close();
+ schema3db = null;
+
+ // Load the database, forcing migration to the current schema version. Then
+ // test the expected set of cookies:
+ do_load_profile();
+
+ // 1) All unexpired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ Assert.equal(Services.cookies.countCookiesFromHost("baz.com"), 1);
+ let cookies = Services.cookies.getCookiesFromHost("baz.com", {});
+ let cookie = cookies[0];
+ Assert.equal(cookie.expiry, futureExpiry + 44);
+
+ finish_test();
+}
diff --git a/netwerk/test/unit/test_separate_connections.js b/netwerk/test/unit/test_separate_connections.js
new file mode 100644
index 0000000000..4f5c82ec97
--- /dev/null
+++ b/netwerk/test/unit/test_separate_connections.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+// This unit test ensures each container has its own connection pool.
+// We verify this behavior by opening channels with different userContextId,
+// and their connection info's hash keys should be different.
+
+// In the first round of this test, we record the hash key in each container.
+// In the second round, we check if each container's hash key is consistent
+// and different from other container's hash key.
+
+let httpserv = null;
+let gSecondRoundStarted = false;
+
+function handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ let body = "0123456789";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function makeChan(url, userContextId) {
+ let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ chan.loadInfo.originAttributes = { userContextId };
+ return chan;
+}
+
+let previousHashKeys = [];
+
+function Listener(userContextId) {
+ this.userContextId = userContextId;
+}
+
+let gTestsRun = 0;
+Listener.prototype = {
+ onStartRequest(request) {
+ request
+ .QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ Assert.equal(
+ request.loadInfo.originAttributes.userContextId,
+ this.userContextId
+ );
+
+ let hashKey = request.connectionInfoHashKey;
+ if (gSecondRoundStarted) {
+ // Compare the hash keys with the previous set ones.
+ // Hash keys should match if and only if their userContextId are the same.
+ for (let userContextId = 0; userContextId < 3; userContextId++) {
+ if (userContextId == this.userContextId) {
+ Assert.equal(hashKey, previousHashKeys[userContextId]);
+ } else {
+ Assert.notEqual(hashKey, previousHashKeys[userContextId]);
+ }
+ }
+ } else {
+ // Set the hash keys in the first round.
+ previousHashKeys[this.userContextId] = hashKey;
+ }
+ },
+ onDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+ onStopRequest() {
+ gTestsRun++;
+ if (gTestsRun == 3) {
+ gTestsRun = 0;
+ if (gSecondRoundStarted) {
+ // The second round finishes.
+ httpserv.stop(do_test_finished);
+ } else {
+ // The first round finishes. Do the second round.
+ gSecondRoundStarted = true;
+ doTest();
+ }
+ }
+ },
+};
+
+function doTest() {
+ for (let userContextId = 0; userContextId < 3; userContextId++) {
+ let chan = makeChan(URL, userContextId);
+ let listener = new Listener(userContextId);
+ chan.asyncOpen(listener);
+ }
+}
+
+function run_test() {
+ do_test_pending();
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", handler);
+ httpserv.start(-1);
+
+ doTest();
+}
diff --git a/netwerk/test/unit/test_servers.js b/netwerk/test/unit/test_servers.js
new file mode 100644
index 0000000000..1aee369f5c
--- /dev/null
+++ b/netwerk/test/unit/test_servers.js
@@ -0,0 +1,324 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+function registerSimplePathHandler(server, path) {
+ return server.registerPathHandler(path, (req, resp) => {
+ resp.writeHead(200);
+ resp.end("done");
+ });
+}
+
+function regiisterServerNamePathHandler(server, path) {
+ return server.registerPathHandler(path, (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+}
+
+add_task(async function test_dual_stack() {
+ let httpserv = new HttpServer();
+ let content = "ok";
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start_dualStack(-1);
+
+ let chan = makeChan(`http://127.0.0.1:${httpserv.identity.primaryPort}/`);
+ let [, response] = await channelOpenPromise(chan);
+ Assert.equal(response, content);
+
+ chan = makeChan(`http://[::1]:${httpserv.identity.primaryPort}/`);
+ [, response] = await channelOpenPromise(chan);
+ Assert.equal(response, content);
+ await new Promise(resolve => httpserv.stop(resolve));
+});
+
+add_task(async function test_http() {
+ let server = new NodeHTTPServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ let chan = makeChan(`http://localhost:${server.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 404);
+ await registerSimplePathHandler(server, "/test");
+ chan = makeChan(`http://localhost:${server.port()}/test`);
+ req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).protocolVersion, "http/1.1");
+ equal(req.QueryInterface(Ci.nsIHttpChannelInternal).isProxyUsed, false);
+
+ await server.stop();
+});
+
+add_task(async function test_https() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let server = new NodeHTTPSServer();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ let chan = makeChan(`https://localhost:${server.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 404);
+ await registerSimplePathHandler(server, "/test");
+ chan = makeChan(`https://localhost:${server.port()}/test`);
+ req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).protocolVersion, "http/1.1");
+
+ await server.stop();
+});
+
+add_task(async function test_http2() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let server = new NodeHTTP2Server();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+ let chan = makeChan(`https://localhost:${server.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 404);
+ await registerSimplePathHandler(server, "/test");
+ chan = makeChan(`https://localhost:${server.port()}/test`);
+ req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).protocolVersion, "h2");
+
+ await server.stop();
+});
+
+add_task(async function test_http1_proxy() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ let chan = makeChan(`http://localhost:${proxy.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 405);
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+ await regiisterServerNamePathHandler(server, "/test");
+ let [req1, buff] = await channelOpenPromise(
+ makeChan(`${server.origin()}/test`),
+ CL_ALLOW_UNKNOWN_CL
+ );
+ equal(req1.status, Cr.NS_OK);
+ equal(req1.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(buff, server.constructor.name);
+ //Bug 1792187: Check if proxy is set to true when a proxy is used.
+ equal(req1.QueryInterface(Ci.nsIHttpChannelInternal).isProxyUsed, true);
+ equal(
+ req1.QueryInterface(Ci.nsIHttpChannel).protocolVersion,
+ server.constructor.name == "NodeHTTP2Server" ? "h2" : "http/1.1"
+ );
+ }
+ );
+
+ await proxy.stop();
+});
+
+add_task(async function test_https_proxy() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ let chan = makeChan(`https://localhost:${proxy.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 405);
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+ await regiisterServerNamePathHandler(server, "/test");
+
+ let [req1, buff] = await channelOpenPromise(
+ makeChan(`${server.origin()}/test`),
+ CL_ALLOW_UNKNOWN_CL
+ );
+ equal(req1.status, Cr.NS_OK);
+ equal(req1.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(buff, server.constructor.name);
+ }
+ );
+
+ await proxy.stop();
+});
+
+add_task(async function test_http2_proxy() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ let chan = makeChan(`https://localhost:${proxy.port()}/test`);
+ let req = await new Promise(resolve => {
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 405);
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+ await regiisterServerNamePathHandler(server, "/test");
+ let [req1, buff] = await channelOpenPromise(
+ makeChan(`${server.origin()}/test`),
+ CL_ALLOW_UNKNOWN_CL
+ );
+ equal(req1.status, Cr.NS_OK);
+ equal(req1.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(buff, server.constructor.name);
+ }
+ );
+
+ await proxy.stop();
+});
+
+add_task(async function test_proxy_with_redirects() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+ await server.registerPathHandler("/redirect", (req, resp) => {
+ resp.writeHead(302, {
+ Location: "/test",
+ });
+ resp.end(global.server_name);
+ });
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ let chan = makeChan(`${server.origin()}/redirect`);
+ let [req, buff] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ equal(req.status, Cr.NS_OK);
+ equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+ equal(buff, server.constructor.name);
+ req.QueryInterface(Ci.nsIProxiedChannel);
+ ok(!!req.proxyInfo);
+ notEqual(req.proxyInfo.type, "direct");
+ }
+ );
+ await proxy.stop();
+ }
+});
diff --git a/netwerk/test/unit/test_signature_extraction.js b/netwerk/test/unit/test_signature_extraction.js
new file mode 100644
index 0000000000..3d9db2bdbf
--- /dev/null
+++ b/netwerk/test/unit/test_signature_extraction.js
@@ -0,0 +1,203 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests signature extraction using Windows Authenticode APIs of
+ * downloaded files.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
+});
+
+const BackgroundFileSaverOutputStream = Components.Constructor(
+ "@mozilla.org/network/background-file-saver;1?mode=outputstream",
+ "nsIBackgroundFileSaver"
+);
+
+const StringInputStream = Components.Constructor(
+ "@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream",
+ "setData"
+);
+
+const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
+
+/**
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
+ */
+function getTempFile(leafName) {
+ return FileTestUtils.getTempFile(leafName);
+}
+
+/**
+ * Waits for the given saver object to complete.
+ *
+ * @param aSaver
+ * The saver, with the output stream or a stream listener implementation.
+ * @param aOnTargetChangeFn
+ * Optional callback invoked with the target file name when it changes.
+ *
+ * @return {Promise}
+ * @resolves When onSaveComplete is called with a success code.
+ * @rejects With an exception, if onSaveComplete is called with a failure code.
+ */
+function promiseSaverComplete(aSaver, aOnTargetChangeFn) {
+ return new Promise((resolve, reject) => {
+ aSaver.observer = {
+ onTargetChange: function BFSO_onSaveComplete(saver, aTarget) {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(saver, aStatus) {
+ if (Components.isSuccessCode(aStatus)) {
+ resolve();
+ } else {
+ reject(new Components.Exception("Saver failed.", aStatus));
+ }
+ },
+ };
+ });
+}
+
+/**
+ * Feeds a string to a BackgroundFileSaverOutputStream.
+ *
+ * @param aSourceString
+ * The source data to copy.
+ * @param aSaverOutputStream
+ * The BackgroundFileSaverOutputStream to feed.
+ * @param aCloseWhenDone
+ * If true, the output stream will be closed when the copy finishes.
+ *
+ * @return {Promise}
+ * @resolves When the copy completes with a success code.
+ * @rejects With an exception, if the copy fails.
+ */
+function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) {
+ return new Promise((resolve, reject) => {
+ let inputStream = new StringInputStream(
+ aSourceString,
+ aSourceString.length
+ );
+ let copier = Cc[
+ "@mozilla.org/network/async-stream-copier;1"
+ ].createInstance(Ci.nsIAsyncStreamCopier);
+ copier.init(
+ inputStream,
+ aSaverOutputStream,
+ null,
+ false,
+ true,
+ 0x8000,
+ true,
+ aCloseWhenDone
+ );
+ copier.asyncCopy(
+ {
+ onStartRequest() {},
+ onStopRequest(aRequest, aContext, aStatusCode) {
+ if (Components.isSuccessCode(aStatusCode)) {
+ resolve();
+ } else {
+ reject(new Components.Exception(aStatusCode));
+ }
+ },
+ },
+ null
+ );
+ });
+}
+
+var gStillRunning = true;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+add_task(function test_setup() {
+ // Wait 10 minutes, that is half of the external xpcshell timeout.
+ do_timeout(10 * 60 * 1000, function () {
+ if (gStillRunning) {
+ do_throw("Test timed out.");
+ }
+ });
+});
+
+function readFileToString(aFilename) {
+ let f = do_get_file(aFilename);
+ let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ stream.init(f, -1, 0, 0);
+ let buf = NetUtil.readInputStreamToString(stream, stream.available());
+ return buf;
+}
+
+add_task(async function test_signature() {
+ // Check that we get a signature if the saver is finished on Windows.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let data = readFileToString("data/signed_win.exe");
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ try {
+ saver.signatureInfo;
+ do_throw("Can't get signature before saver is complete.");
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw ex;
+ }
+ }
+
+ saver.enableSignatureInfo();
+ saver.setTarget(destFile, false);
+ await promiseCopyToSaver(data, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ await completionPromise;
+
+ // There's only one Array of certs(raw bytes) in the signature array.
+ Assert.equal(1, saver.signatureInfo.length);
+ let certLists = saver.signatureInfo;
+ Assert.ok(certLists.length === 1);
+
+ // Check that it has 3 certs(raw bytes).
+ let certs = certLists[0];
+ Assert.ok(certs.length === 3);
+
+ const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ let signer = certDB.constructX509(certs[0]);
+ let issuer = certDB.constructX509(certs[1]);
+ let root = certDB.constructX509(certs[2]);
+
+ let organization = "Microsoft Corporation";
+ Assert.equal("Microsoft Corporation", signer.commonName);
+ Assert.equal(organization, signer.organization);
+ Assert.equal("Copyright (c) 2002 Microsoft Corp.", signer.organizationalUnit);
+
+ Assert.equal("Microsoft Code Signing PCA", issuer.commonName);
+ Assert.equal(organization, issuer.organization);
+ Assert.equal("Copyright (c) 2000 Microsoft Corp.", issuer.organizationalUnit);
+
+ Assert.equal("Microsoft Root Authority", root.commonName);
+ Assert.ok(!root.organization);
+ Assert.equal("Copyright (c) 1997 Microsoft Corp.", root.organizationalUnit);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_teardown() {
+ gStillRunning = false;
+});
diff --git a/netwerk/test/unit/test_simple.js b/netwerk/test/unit/test_simple.js
new file mode 100644
index 0000000000..52c068f517
--- /dev/null
+++ b/netwerk/test/unit/test_simple.js
@@ -0,0 +1,70 @@
+//
+// Simple HTTP test: fetches page
+//
+
+// Note: sets Cc and Ci variables
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+var dbg = 0;
+if (dbg) {
+ print("============== START ==========");
+}
+
+function run_test() {
+ setup_test();
+ do_test_pending();
+}
+
+function setup_test() {
+ if (dbg) {
+ print("============== setup_test: in");
+ }
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ var channel = setupChannel(testpath);
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen(new ChannelListener(checkRequest, channel));
+ if (dbg) {
+ print("============== setup_test: out");
+ }
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + path,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ if (dbg) {
+ print("============== serverHandler: in");
+ }
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ if (dbg) {
+ print("============== serverHandler: out");
+ }
+}
+
+function checkRequest(request, data, context) {
+ if (dbg) {
+ print("============== checkRequest: in");
+ }
+ Assert.equal(data, httpbody);
+ httpserver.stop(do_test_finished);
+ if (dbg) {
+ print("============== checkRequest: out");
+ }
+}
diff --git a/netwerk/test/unit/test_sockettransportsvc_available.js b/netwerk/test/unit/test_sockettransportsvc_available.js
new file mode 100644
index 0000000000..664b6a853d
--- /dev/null
+++ b/netwerk/test/unit/test_sockettransportsvc_available.js
@@ -0,0 +1,11 @@
+"use strict";
+
+function run_test() {
+ try {
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+ );
+ } catch (e) {}
+
+ Assert.ok(!!sts);
+}
diff --git a/netwerk/test/unit/test_socks.js b/netwerk/test/unit/test_socks.js
new file mode 100644
index 0000000000..6ce9f4895e
--- /dev/null
+++ b/netwerk/test/unit/test_socks.js
@@ -0,0 +1,520 @@
+"use strict";
+
+var CC = Components.Constructor;
+
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+const DirectoryService = CC(
+ "@mozilla.org/file/directory_service;1",
+ "nsIProperties"
+);
+const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init");
+
+const currentThread =
+ Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+
+var socks_test_server = null;
+var socks_listen_port = -1;
+
+function getAvailableBytes(input) {
+ var len = 0;
+
+ try {
+ len = input.available();
+ } catch (e) {}
+
+ return len;
+}
+
+function runScriptSubprocess(script, args) {
+ var ds = new DirectoryService();
+ var bin = ds.get("XREExeF", Ci.nsIFile);
+ if (!bin.exists()) {
+ do_throw("Can't find xpcshell binary");
+ }
+
+ var file = do_get_file(script);
+ var proc = new Process(bin);
+ var procArgs = [file.path].concat(args);
+
+ proc.run(false, procArgs, procArgs.length);
+
+ return proc;
+}
+
+function buf2ip(buf) {
+ if (buf.length == 16) {
+ var ip =
+ ((buf[0] << 4) | buf[1]).toString(16) +
+ ":" +
+ ((buf[2] << 4) | buf[3]).toString(16) +
+ ":" +
+ ((buf[4] << 4) | buf[5]).toString(16) +
+ ":" +
+ ((buf[6] << 4) | buf[7]).toString(16) +
+ ":" +
+ ((buf[8] << 4) | buf[9]).toString(16) +
+ ":" +
+ ((buf[10] << 4) | buf[11]).toString(16) +
+ ":" +
+ ((buf[12] << 4) | buf[13]).toString(16) +
+ ":" +
+ ((buf[14] << 4) | buf[15]).toString(16);
+ for (var i = 8; i >= 2; i--) {
+ var re = new RegExp("(^|:)(0(:|$)){" + i + "}");
+ var shortip = ip.replace(re, "::");
+ if (shortip != ip) {
+ return shortip;
+ }
+ }
+ return ip;
+ }
+ return buf.join(".");
+}
+
+function buf2int(buf) {
+ var n = 0;
+
+ for (var i in buf) {
+ n |= buf[i] << ((buf.length - i - 1) * 8);
+ }
+
+ return n;
+}
+
+function buf2str(buf) {
+ return String.fromCharCode.apply(null, buf);
+}
+
+const STATE_WAIT_GREETING = 1;
+const STATE_WAIT_SOCKS4_REQUEST = 2;
+const STATE_WAIT_SOCKS4_USERNAME = 3;
+const STATE_WAIT_SOCKS4_HOSTNAME = 4;
+const STATE_WAIT_SOCKS5_GREETING = 5;
+const STATE_WAIT_SOCKS5_REQUEST = 6;
+const STATE_WAIT_PONG = 7;
+const STATE_GOT_PONG = 8;
+
+function SocksClient(server, client_in, client_out) {
+ this.server = server;
+ this.type = "";
+ this.username = "";
+ this.dest_name = "";
+ this.dest_addr = [];
+ this.dest_port = [];
+
+ this.client_in = client_in;
+ this.client_out = client_out;
+ this.inbuf = [];
+ this.outbuf = String();
+ this.state = STATE_WAIT_GREETING;
+ this.waitRead(this.client_in);
+}
+SocksClient.prototype = {
+ onInputStreamReady(input) {
+ var len = getAvailableBytes(input);
+
+ if (len == 0) {
+ print("server: client closed!");
+ Assert.equal(this.state, STATE_GOT_PONG);
+ this.close();
+ this.server.testCompleted(this);
+ return;
+ }
+
+ var bin = new BinaryInputStream(input);
+ var data = bin.readByteArray(len);
+ this.inbuf = this.inbuf.concat(data);
+
+ switch (this.state) {
+ case STATE_WAIT_GREETING:
+ this.checkSocksGreeting();
+ break;
+ case STATE_WAIT_SOCKS4_REQUEST:
+ this.checkSocks4Request();
+ break;
+ case STATE_WAIT_SOCKS4_USERNAME:
+ this.checkSocks4Username();
+ break;
+ case STATE_WAIT_SOCKS4_HOSTNAME:
+ this.checkSocks4Hostname();
+ break;
+ case STATE_WAIT_SOCKS5_GREETING:
+ this.checkSocks5Greeting();
+ break;
+ case STATE_WAIT_SOCKS5_REQUEST:
+ this.checkSocks5Request();
+ break;
+ case STATE_WAIT_PONG:
+ this.checkPong();
+ break;
+ default:
+ do_throw("server: read in invalid state!");
+ }
+
+ this.waitRead(input);
+ },
+
+ onOutputStreamReady(output) {
+ var len = output.write(this.outbuf, this.outbuf.length);
+ if (len != this.outbuf.length) {
+ this.outbuf = this.outbuf.substring(len);
+ this.waitWrite(output);
+ } else {
+ this.outbuf = String();
+ }
+ },
+
+ waitRead(input) {
+ input.asyncWait(this, 0, 0, currentThread);
+ },
+
+ waitWrite(output) {
+ output.asyncWait(this, 0, 0, currentThread);
+ },
+
+ write(buf) {
+ this.outbuf += buf;
+ this.waitWrite(this.client_out);
+ },
+
+ checkSocksGreeting() {
+ if (!this.inbuf.length) {
+ return;
+ }
+
+ if (this.inbuf[0] == 4) {
+ print("server: got socks 4");
+ this.type = "socks4";
+ this.state = STATE_WAIT_SOCKS4_REQUEST;
+ this.checkSocks4Request();
+ } else if (this.inbuf[0] == 5) {
+ print("server: got socks 5");
+ this.type = "socks";
+ this.state = STATE_WAIT_SOCKS5_GREETING;
+ this.checkSocks5Greeting();
+ } else {
+ do_throw("Unknown socks protocol!");
+ }
+ },
+
+ checkSocks4Request() {
+ if (this.inbuf.length < 8) {
+ return;
+ }
+
+ Assert.equal(this.inbuf[1], 0x01);
+
+ this.dest_port = this.inbuf.slice(2, 4);
+ this.dest_addr = this.inbuf.slice(4, 8);
+
+ this.inbuf = this.inbuf.slice(8);
+ this.state = STATE_WAIT_SOCKS4_USERNAME;
+ this.checkSocks4Username();
+ },
+
+ readString() {
+ var i = this.inbuf.indexOf(0);
+ var str = null;
+
+ if (i >= 0) {
+ var buf = this.inbuf.slice(0, i);
+ str = buf2str(buf);
+ this.inbuf = this.inbuf.slice(i + 1);
+ }
+
+ return str;
+ },
+
+ checkSocks4Username() {
+ var str = this.readString();
+
+ if (str == null) {
+ return;
+ }
+
+ this.username = str;
+ if (
+ this.dest_addr[0] == 0 &&
+ this.dest_addr[1] == 0 &&
+ this.dest_addr[2] == 0 &&
+ this.dest_addr[3] != 0
+ ) {
+ this.state = STATE_WAIT_SOCKS4_HOSTNAME;
+ this.checkSocks4Hostname();
+ } else {
+ this.sendSocks4Response();
+ }
+ },
+
+ checkSocks4Hostname() {
+ var str = this.readString();
+
+ if (str == null) {
+ return;
+ }
+
+ this.dest_name = str;
+ this.sendSocks4Response();
+ },
+
+ sendSocks4Response() {
+ this.outbuf = "\x00\x5a\x00\x00\x00\x00\x00\x00";
+ this.sendPing();
+ },
+
+ checkSocks5Greeting() {
+ if (this.inbuf.length < 2) {
+ return;
+ }
+ var nmethods = this.inbuf[1];
+ if (this.inbuf.length < 2 + nmethods) {
+ return;
+ }
+
+ Assert.ok(nmethods >= 1);
+ var methods = this.inbuf.slice(2, 2 + nmethods);
+ Assert.ok(0 in methods);
+
+ this.inbuf = [];
+ this.state = STATE_WAIT_SOCKS5_REQUEST;
+ this.write("\x05\x00");
+ },
+
+ checkSocks5Request() {
+ if (this.inbuf.length < 4) {
+ return;
+ }
+
+ Assert.equal(this.inbuf[0], 0x05);
+ Assert.equal(this.inbuf[1], 0x01);
+ Assert.equal(this.inbuf[2], 0x00);
+
+ var atype = this.inbuf[3];
+ var len;
+ var name = false;
+
+ switch (atype) {
+ case 0x01:
+ len = 4;
+ break;
+ case 0x03:
+ len = this.inbuf[4];
+ name = true;
+ break;
+ case 0x04:
+ len = 16;
+ break;
+ default:
+ do_throw("Unknown address type " + atype);
+ }
+
+ if (name) {
+ if (this.inbuf.length < 4 + len + 1 + 2) {
+ return;
+ }
+
+ let buf = this.inbuf.slice(5, 5 + len);
+ this.dest_name = buf2str(buf);
+ len += 1;
+ } else {
+ if (this.inbuf.length < 4 + len + 2) {
+ return;
+ }
+
+ this.dest_addr = this.inbuf.slice(4, 4 + len);
+ }
+
+ len += 4;
+ this.dest_port = this.inbuf.slice(len, len + 2);
+ this.inbuf = this.inbuf.slice(len + 2);
+ this.sendSocks5Response();
+ },
+
+ sendSocks5Response() {
+ if (this.dest_addr.length == 16) {
+ // send a successful response with the address, [::1]:80
+ this.outbuf +=
+ "\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80";
+ } else {
+ // send a successful response with the address, 127.0.0.1:80
+ this.outbuf += "\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80";
+ }
+ this.sendPing();
+ },
+
+ sendPing() {
+ print("server: sending ping");
+ this.state = STATE_WAIT_PONG;
+ this.outbuf += "PING!";
+ this.inbuf = [];
+ this.waitWrite(this.client_out);
+ },
+
+ checkPong() {
+ var pong = buf2str(this.inbuf);
+ Assert.equal(pong, "PONG!");
+ this.state = STATE_GOT_PONG;
+ },
+
+ close() {
+ this.client_in.close();
+ this.client_out.close();
+ },
+};
+
+function SocksTestServer() {
+ this.listener = ServerSocket(-1, true, -1);
+ socks_listen_port = this.listener.port;
+ print("server: listening on", socks_listen_port);
+ this.listener.asyncListen(this);
+ this.test_cases = [];
+ this.client_connections = [];
+ this.client_subprocess = null;
+ // port is used as the ID for test cases
+ this.test_port_id = 8000;
+ this.tests_completed = 0;
+}
+SocksTestServer.prototype = {
+ addTestCase(test) {
+ test.finished = false;
+ test.port = this.test_port_id++;
+ this.test_cases.push(test);
+ },
+
+ pickTest(id) {
+ for (var i in this.test_cases) {
+ var test = this.test_cases[i];
+ if (test.port == id) {
+ this.tests_completed++;
+ return test;
+ }
+ }
+ do_throw("No test case with id " + id);
+ return null;
+ },
+
+ testCompleted(client) {
+ var port_id = buf2int(client.dest_port);
+ var test = this.pickTest(port_id);
+
+ print("server: test finished", test.port);
+ Assert.ok(test != null);
+ Assert.equal(test.expectedType || test.type, client.type);
+ Assert.equal(test.port, port_id);
+
+ if (test.remote_dns) {
+ Assert.equal(test.host, client.dest_name);
+ } else {
+ Assert.equal(test.host, buf2ip(client.dest_addr));
+ }
+
+ if (this.test_cases.length == this.tests_completed) {
+ print("server: all tests completed");
+ this.close();
+ do_test_finished();
+ }
+ },
+
+ runClientSubprocess() {
+ var argv = [];
+
+ // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local>
+ for (var test of this.test_cases) {
+ var arg =
+ test.type +
+ "|" +
+ String(socks_listen_port) +
+ "|" +
+ test.host +
+ "|" +
+ test.port +
+ "|";
+ if (test.remote_dns) {
+ arg += "remote";
+ } else {
+ arg += "local";
+ }
+ print("server: using test case", arg);
+ argv.push(arg);
+ }
+
+ this.client_subprocess = runScriptSubprocess(
+ "socks_client_subprocess.js",
+ argv
+ );
+ },
+
+ onSocketAccepted(socket, trans) {
+ print("server: got client connection");
+ var input = trans.openInputStream(0, 0, 0);
+ var output = trans.openOutputStream(0, 0, 0);
+ var client = new SocksClient(this, input, output);
+ this.client_connections.push(client);
+ },
+
+ onStopListening(socket) {},
+
+ close() {
+ if (this.client_subprocess) {
+ try {
+ this.client_subprocess.kill();
+ } catch (x) {
+ do_note_exception(x, "Killing subprocess failed");
+ }
+ this.client_subprocess = null;
+ }
+ this.client_connections = [];
+ if (this.listener) {
+ this.listener.close();
+ this.listener = null;
+ }
+ },
+};
+
+function run_test() {
+ socks_test_server = new SocksTestServer();
+
+ socks_test_server.addTestCase({
+ type: "socks4",
+ host: "127.0.0.1",
+ remote_dns: false,
+ });
+ socks_test_server.addTestCase({
+ type: "socks4",
+ host: "12345.xxx",
+ remote_dns: true,
+ });
+ socks_test_server.addTestCase({
+ type: "socks4",
+ expectedType: "socks",
+ host: "::1",
+ remote_dns: false,
+ });
+ socks_test_server.addTestCase({
+ type: "socks",
+ host: "127.0.0.1",
+ remote_dns: false,
+ });
+ socks_test_server.addTestCase({
+ type: "socks",
+ host: "abcdefg.xxx",
+ remote_dns: true,
+ });
+ socks_test_server.addTestCase({
+ type: "socks",
+ host: "::1",
+ remote_dns: false,
+ });
+ socks_test_server.runClientSubprocess();
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_speculative_connect.js b/netwerk/test/unit/test_speculative_connect.js
new file mode 100644
index 0000000000..f99ace0d71
--- /dev/null
+++ b/netwerk/test/unit/test_speculative_connect.js
@@ -0,0 +1,382 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* vim: set ts=4 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var CC = Components.Constructor;
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+var serv;
+var ios;
+
+/** Example local IP addresses (literal IP address hostname).
+ *
+ * Note: for IPv6 Unique Local and Link Local, a wider range of addresses is
+ * set aside than those most commonly used. Technically, link local addresses
+ * include those beginning with fe80:: through febf::, although in practise
+ * only fe80:: is used. Necko code blocks speculative connections for the wider
+ * range; hence, this test considers that range too.
+ */
+var localIPv4Literals = [
+ // IPv4 RFC1918 \
+ "10.0.0.1",
+ "10.10.10.10",
+ "10.255.255.255", // 10/8
+ "172.16.0.1",
+ "172.23.172.12",
+ "172.31.255.255", // 172.16/20
+ "192.168.0.1",
+ "192.168.192.168",
+ "192.168.255.255", // 192.168/16
+ // IPv4 Link Local
+ "169.254.0.1",
+ "169.254.192.154",
+ "169.254.255.255", // 169.254/16
+];
+var localIPv6Literals = [
+ // IPv6 Unique Local fc00::/7
+ "fc00::1",
+ "fdfe:dcba:9876:abcd:ef01:2345:6789:abcd",
+ "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ // IPv6 Link Local fe80::/10
+ "fe80::1",
+ "fe80::abcd:ef01:2345:6789",
+ "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+];
+var localIPLiterals = localIPv4Literals.concat(localIPv6Literals);
+
+/** Test function list and descriptions.
+ */
+var testList = [
+ test_localhost_http_speculative_connect,
+ test_localhost_https_speculative_connect,
+ test_hostnames_resolving_to_local_addresses,
+ test_proxies_with_local_addresses,
+];
+
+var testDescription = [
+ "Expect pass with localhost, http",
+ "Expect pass with localhost, https",
+ "Expect failure with resolved local IPs",
+ "Expect failure for proxies with local IPs",
+];
+
+var testIdx = 0;
+var hostIdx = 0;
+
+/** TestServer
+ *
+ * Implements nsIServerSocket for test_speculative_connect.
+ */
+function TestServer() {
+ this.listener = ServerSocket(-1, true, -1);
+ this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIServerSocket"]),
+ onSocketAccepted(socket, trans) {
+ try {
+ this.listener.close();
+ } catch (e) {}
+ Assert.ok(true);
+ next_test();
+ },
+
+ onStopListening(socket) {},
+};
+
+/** TestFailedStreamCallback
+ *
+ * Implements nsI[Input|Output]StreamCallback for socket layer tests.
+ * Expect failure in all cases
+ */
+function TestFailedStreamCallback(transport, hostname, next) {
+ this.transport = transport;
+ this.hostname = hostname;
+ this.next = next;
+ this.dummyContent = "G";
+ this.closed = false;
+}
+
+TestFailedStreamCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInputStreamCallback",
+ "nsIOutputStreamCallback",
+ ]),
+ processException(e) {
+ if (this.closed) {
+ return;
+ }
+ do_check_instanceof(e, Ci.nsIException);
+ // A refusal to connect speculatively should throw an error.
+ Assert.equal(e.result, Cr.NS_ERROR_CONNECTION_REFUSED);
+ this.closed = true;
+ this.transport.close(Cr.NS_BINDING_ABORTED);
+ this.next();
+ },
+ onOutputStreamReady(outstream) {
+ info("outputstream handler.");
+ Assert.notEqual(typeof outstream, undefined);
+ try {
+ outstream.write(this.dummyContent, this.dummyContent.length);
+ } catch (e) {
+ this.processException(e);
+ return;
+ }
+ info("no exception on write. Wait for read.");
+ },
+ onInputStreamReady(instream) {
+ info("inputstream handler.");
+ Assert.notEqual(typeof instream, undefined);
+ try {
+ instream.available();
+ } catch (e) {
+ this.processException(e);
+ return;
+ }
+ do_throw("Speculative Connect should have failed for " + this.hostname);
+ this.transport.close(Cr.NS_BINDING_ABORTED);
+ this.next();
+ },
+};
+
+/** test_localhost_http_speculative_connect
+ *
+ * Tests a basic positive case using nsIOService.SpeculativeConnect:
+ * connecting to localhost via http.
+ */
+function test_localhost_http_speculative_connect() {
+ serv = new TestServer();
+ var ssm = Services.scriptSecurityManager;
+ var URI = ios.newURI(
+ "http://localhost:" + serv.listener.port + "/just/a/test"
+ );
+ var principal = ssm.createContentPrincipal(URI, {});
+
+ ios
+ .QueryInterface(Ci.nsISpeculativeConnect)
+ .speculativeConnect(URI, principal, null, false);
+}
+
+/** test_localhost_https_speculative_connect
+ *
+ * Tests a basic positive case using nsIOService.SpeculativeConnect:
+ * connecting to localhost via https.
+ */
+function test_localhost_https_speculative_connect() {
+ serv = new TestServer();
+ var ssm = Services.scriptSecurityManager;
+ var URI = ios.newURI(
+ "https://localhost:" + serv.listener.port + "/just/a/test"
+ );
+ var principal = ssm.createContentPrincipal(URI, {});
+
+ ios
+ .QueryInterface(Ci.nsISpeculativeConnect)
+ .speculativeConnect(URI, principal, null, false);
+}
+
+/* Speculative connections should not be allowed for hosts with local IP
+ * addresses (Bug 853423). That list includes:
+ * -- IPv4 RFC1918 and Link Local Addresses.
+ * -- IPv6 Unique and Link Local Addresses.
+ *
+ * Two tests are required:
+ * 1. Verify IP Literals passed to the SpeculativeConnect API.
+ * 2. Verify hostnames that need to be resolved at the socket layer.
+ */
+
+/** test_hostnames_resolving_to_addresses
+ *
+ * Common test function for resolved hostnames. Takes a list of hosts, a
+ * boolean to determine if the test is expected to succeed or fail, and a
+ * function to call the next test case.
+ */
+function test_hostnames_resolving_to_addresses(host, next) {
+ info(host);
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+ );
+ Assert.notEqual(typeof sts, undefined);
+ var transport = sts.createTransport([], host, 80, null, null);
+ Assert.notEqual(typeof transport, undefined);
+
+ transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
+ Assert.equal(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
+
+ var outStream = transport.openOutputStream(
+ Ci.nsITransport.OPEN_UNBUFFERED,
+ 0,
+ 0
+ );
+ var inStream = transport.openInputStream(0, 0, 0);
+ Assert.notEqual(typeof outStream, undefined);
+ Assert.notEqual(typeof inStream, undefined);
+
+ var callback = new TestFailedStreamCallback(transport, host, next);
+ Assert.notEqual(typeof callback, undefined);
+
+ // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
+ // adds callback to ns*StreamReadyEvent on main thread, and doesn't
+ // addref off the main thread.
+ var gThreadManager = Services.tm;
+ var mainThread = gThreadManager.currentThread;
+
+ try {
+ outStream
+ .QueryInterface(Ci.nsIAsyncOutputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ inStream
+ .QueryInterface(Ci.nsIAsyncInputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ } catch (e) {
+ do_throw("asyncWait should not fail!");
+ }
+}
+
+/**
+ * test_hostnames_resolving_to_local_addresses
+ *
+ * Creates an nsISocketTransport and simulates a speculative connect request
+ * for a hostname that resolves to a local IP address.
+ * Runs asynchronously; on test success (i.e. failure to connect), the callback
+ * will call this function again until all hostnames in the test list are done.
+ *
+ * Note: This test also uses an IP literal for the hostname. This should be ok,
+ * as the socket layer will ask for the hostname to be resolved anyway, and DNS
+ * code should return a numerical version of the address internally.
+ */
+function test_hostnames_resolving_to_local_addresses() {
+ if (hostIdx >= localIPLiterals.length) {
+ // No more local IP addresses; move on.
+ next_test();
+ return;
+ }
+ var host = localIPLiterals[hostIdx++];
+ // Test another local IP address when the current one is done.
+ var next = test_hostnames_resolving_to_local_addresses;
+ test_hostnames_resolving_to_addresses(host, next);
+}
+
+/** test_speculative_connect_with_host_list
+ *
+ * Common test function for resolved proxy hosts. Takes a list of hosts, a
+ * boolean to determine if the test is expected to succeed or fail, and a
+ * function to call the next test case.
+ */
+function test_proxies(proxyHost, next) {
+ info("Proxy: " + proxyHost);
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+ );
+ Assert.notEqual(typeof sts, undefined);
+ var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+ Assert.notEqual(typeof pps, undefined);
+
+ var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, "", "", 0, 1, null);
+ Assert.notEqual(typeof proxyInfo, undefined);
+
+ var transport = sts.createTransport([], "dummyHost", 80, proxyInfo, null);
+ Assert.notEqual(typeof transport, undefined);
+
+ transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
+
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
+ Assert.equal(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
+
+ var outStream = transport.openOutputStream(
+ Ci.nsITransport.OPEN_UNBUFFERED,
+ 0,
+ 0
+ );
+ var inStream = transport.openInputStream(0, 0, 0);
+ Assert.notEqual(typeof outStream, undefined);
+ Assert.notEqual(typeof inStream, undefined);
+
+ var callback = new TestFailedStreamCallback(transport, proxyHost, next);
+ Assert.notEqual(typeof callback, undefined);
+
+ // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
+ // adds callback to ns*StreamReadyEvent on main thread, and doesn't
+ // addref off the main thread.
+ var gThreadManager = Services.tm;
+ var mainThread = gThreadManager.currentThread;
+
+ try {
+ outStream
+ .QueryInterface(Ci.nsIAsyncOutputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ inStream
+ .QueryInterface(Ci.nsIAsyncInputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ } catch (e) {
+ do_throw("asyncWait should not fail!");
+ }
+}
+
+/**
+ * test_proxies_with_local_addresses
+ *
+ * Creates an nsISocketTransport and simulates a speculative connect request
+ * for a proxy that resolves to a local IP address.
+ * Runs asynchronously; on test success (i.e. failure to connect), the callback
+ * will call this function again until all proxies in the test list are done.
+ *
+ * Note: This test also uses an IP literal for the proxy. This should be ok,
+ * as the socket layer will ask for the proxy to be resolved anyway, and DNS
+ * code should return a numerical version of the address internally.
+ */
+function test_proxies_with_local_addresses() {
+ if (hostIdx >= localIPLiterals.length) {
+ // No more local IP addresses; move on.
+ next_test();
+ return;
+ }
+ var host = localIPLiterals[hostIdx++];
+ // Test another local IP address when the current one is done.
+ var next = test_proxies_with_local_addresses;
+ test_proxies(host, next);
+}
+
+/** next_test
+ *
+ * Calls the next test in testList. Each test is responsible for calling this
+ * function when its test cases are complete.
+ */
+function next_test() {
+ if (testIdx >= testList.length) {
+ // No more tests; we're done.
+ do_test_finished();
+ return;
+ }
+ info("SpeculativeConnect: " + testDescription[testIdx]);
+ hostIdx = 0;
+ // Start next test in list.
+ testList[testIdx++]();
+}
+
+/** run_test
+ *
+ * Main entry function for test execution.
+ */
+function run_test() {
+ ios = Services.io;
+
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ });
+
+ do_test_pending();
+ next_test();
+}
diff --git a/netwerk/test/unit/test_stale-while-revalidate_loop.js b/netwerk/test/unit/test_stale-while-revalidate_loop.js
new file mode 100644
index 0000000000..dc28815119
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_loop.js
@@ -0,0 +1,43 @@
+/*
+
+Tests the Cache-control: stale-while-revalidate response directive.
+
+Loads a HTTPS resource with the stale-while-revalidate and tries to load it
+twice.
+
+*/
+
+"use strict";
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+async function get_response(channel, fromCache) {
+ return new Promise(resolve => {
+ channel.asyncOpen(
+ new ChannelListener((request, buffer, ctx, isFromCache) => {
+ resolve(buffer);
+ })
+ );
+ });
+}
+
+add_task(async function () {
+ do_get_profile();
+ const PORT = Services.env.get("MOZHTTP2_PORT");
+ const URI = `https://localhost:${PORT}/stale-while-revalidate-loop-test`;
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let response = await get_response(make_channel(URI), false);
+ ok(response == "1", "got response ver 1");
+ response = await get_response(make_channel(URI), false);
+ ok(response == "1", "got response ver 1");
+});
diff --git a/netwerk/test/unit/test_stale-while-revalidate_max-age-0.js b/netwerk/test/unit/test_stale-while-revalidate_max-age-0.js
new file mode 100644
index 0000000000..01bb6639bc
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_max-age-0.js
@@ -0,0 +1,113 @@
+/*
+
+Tests the Cache-control: stale-while-revalidate response directive.
+
+Purpose is to check we perform the background revalidation when max-age=0 but
+the window is set and we hit it.
+
+* Make request #1.
+ - response is from the server and version=1
+ - max-age=0, stale-while-revalidate=9999
+* Switch version of the data on the server and prolong the max-age to not let req #3
+ do a bck reval at the end of the test (prevent leaks/shutdown races.)
+* Make request #2 in 2 seconds (entry should be expired by that time, but fall into
+ the reval window.)
+ - response is from the cache, version=1
+ - a new background request should be made for the data
+* Wait for "http-on-background-revalidation" notifying finish of the background reval.
+* Make request #3.
+ - response is from the cache, version=2
+* Done.
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let max_age;
+let version;
+let generate_response = ver => `response version=${ver}`;
+
+function test_handler(metadata, response) {
+ const originalBody = generate_response(version);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader(
+ "Cache-control",
+ `max-age=${max_age}, stale-while-revalidate=9999`,
+ false
+ );
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+async function get_response(channel, fromCache) {
+ return new Promise(resolve => {
+ channel.asyncOpen(
+ new ChannelListener((request, buffer, ctx, isFromCache) => {
+ ok(fromCache == isFromCache, `got response from cache = ${fromCache}`);
+ resolve(buffer);
+ })
+ );
+ });
+}
+
+async function sleep(time) {
+ return new Promise(resolve => {
+ do_timeout(time * 1000, resolve);
+ });
+}
+
+async function stop_server(httpserver) {
+ return new Promise(resolve => {
+ httpserver.stop(resolve);
+ });
+}
+
+async function background_reval_promise() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(resolve, "http-on-background-revalidation");
+ });
+}
+
+add_task(async function () {
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/testdir", test_handler);
+ httpserver.start(-1);
+ const PORT = httpserver.identity.primaryPort;
+ const URI = `http://localhost:${PORT}/testdir`;
+
+ let response;
+
+ version = 1;
+ max_age = 0;
+ response = await get_response(make_channel(URI), false);
+ ok(response == generate_response(1), "got response ver 1");
+
+ await sleep(2);
+
+ // must specifically wait for the internal channel to finish the reval to make
+ // the test race-free.
+ let reval_done = background_reval_promise();
+
+ version = 2;
+ max_age = 100;
+ response = await get_response(make_channel(URI), true);
+ ok(response == generate_response(1), "got response ver 1");
+
+ await reval_done;
+
+ response = await get_response(make_channel(URI), true);
+ ok(response == generate_response(2), "got response ver 2");
+
+ await stop_server(httpserver);
+});
diff --git a/netwerk/test/unit/test_stale-while-revalidate_negative.js b/netwerk/test/unit/test_stale-while-revalidate_negative.js
new file mode 100644
index 0000000000..c9ef87e2dd
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_negative.js
@@ -0,0 +1,92 @@
+/*
+
+Tests the Cache-control: stale-while-revalidate response directive.
+
+Purpose is to check we DON'T perform the background revalidation when we make the
+request past the reval window.
+
+* Make request #1.
+ - response is from the server and version=1
+ - max-age=1, stale-while-revalidate=1
+* Switch version of the data on the server.
+* Make request #2 in 3 seconds (entry should be expired by that time and no longer
+ fall into the reval window.)
+ - response is from the server, version=2
+* Done.
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let max_age;
+let version;
+let generate_response = ver => `response version=${ver}`;
+
+function test_handler(metadata, response) {
+ const originalBody = generate_response(version);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader(
+ "Cache-control",
+ `max-age=${max_age}, stale-while-revalidate=1`,
+ false
+ );
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+async function get_response(channel, fromCache) {
+ return new Promise(resolve => {
+ channel.asyncOpen(
+ new ChannelListener((request, buffer, ctx, isFromCache) => {
+ ok(fromCache == isFromCache, `got response from cache = ${fromCache}`);
+ resolve(buffer);
+ })
+ );
+ });
+}
+
+async function sleep(time) {
+ return new Promise(resolve => {
+ do_timeout(time * 1000, resolve);
+ });
+}
+
+async function stop_server(httpserver) {
+ return new Promise(resolve => {
+ httpserver.stop(resolve);
+ });
+}
+
+add_task(async function () {
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/testdir", test_handler);
+ httpserver.start(-1);
+ const PORT = httpserver.identity.primaryPort;
+ const URI = `http://localhost:${PORT}/testdir`;
+
+ let response;
+
+ version = 1;
+ max_age = 1;
+ response = await get_response(make_channel(URI), false);
+ ok(response == generate_response(1), "got response ver 1");
+
+ await sleep(max_age + 1 /* stale window */ + 1 /* to expire the window */);
+
+ version = 2;
+ response = await get_response(make_channel(URI), false);
+ ok(response == generate_response(2), "got response ver 2");
+
+ await stop_server(httpserver);
+});
diff --git a/netwerk/test/unit/test_stale-while-revalidate_positive.js b/netwerk/test/unit/test_stale-while-revalidate_positive.js
new file mode 100644
index 0000000000..51ce6bdd83
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_positive.js
@@ -0,0 +1,113 @@
+/*
+
+Tests the Cache-control: stale-while-revalidate response directive.
+
+Purpose is to check we perform the background revalidation when the window is set
+and we hit it.
+
+* Make request #1.
+ - response is from the server and version=1
+ - max-age=1, stale-while-revalidate=9999
+* Switch version of the data on the server and prolong the max-age to not let req #3
+ do a bck reval at the end of the test (prevent leaks/shutdown races.)
+* Make request #2 in 2 seconds (entry should be expired by that time, but fall into
+ the reval window.)
+ - response is from the cache, version=1
+ - a new background request should be made for the data
+* Wait for "http-on-background-revalidation" notifying finish of the background reval.
+* Make request #3.
+ - response is from the cache, version=2
+* Done.
+
+*/
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let max_age;
+let version;
+let generate_response = ver => `response version=${ver}`;
+
+function test_handler(metadata, response) {
+ const originalBody = generate_response(version);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader(
+ "Cache-control",
+ `max-age=${max_age}, stale-while-revalidate=9999`,
+ false
+ );
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+async function get_response(channel, fromCache) {
+ return new Promise(resolve => {
+ channel.asyncOpen(
+ new ChannelListener((request, buffer, ctx, isFromCache) => {
+ ok(fromCache == isFromCache, `got response from cache = ${fromCache}`);
+ resolve(buffer);
+ })
+ );
+ });
+}
+
+async function sleep(time) {
+ return new Promise(resolve => {
+ do_timeout(time * 1000, resolve);
+ });
+}
+
+async function stop_server(httpserver) {
+ return new Promise(resolve => {
+ httpserver.stop(resolve);
+ });
+}
+
+async function background_reval_promise() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(resolve, "http-on-background-revalidation");
+ });
+}
+
+add_task(async function () {
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/testdir", test_handler);
+ httpserver.start(-1);
+ const PORT = httpserver.identity.primaryPort;
+ const URI = `http://localhost:${PORT}/testdir`;
+
+ let response;
+
+ version = 1;
+ max_age = 1;
+ response = await get_response(make_channel(URI), false);
+ ok(response == generate_response(1), "got response ver 1");
+
+ await sleep(max_age + 1);
+
+ // must specifically wait for the internal channel to finish the reval to make
+ // the test race-free.
+ let reval_done = background_reval_promise();
+
+ version = 2;
+ max_age = 100;
+ response = await get_response(make_channel(URI), true);
+ ok(response == generate_response(1), "got response ver 1");
+
+ await reval_done;
+
+ response = await get_response(make_channel(URI), true);
+ ok(response == generate_response(2), "got response ver 2");
+
+ await stop_server(httpserver);
+});
diff --git a/netwerk/test/unit/test_standardurl.js b/netwerk/test/unit/test_standardurl.js
new file mode 100644
index 0000000000..cf3f736929
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl.js
@@ -0,0 +1,1053 @@
+"use strict";
+
+const gPrefs = Services.prefs;
+
+function symmetricEquality(expect, a, b) {
+ /* Use if/else instead of |do_check_eq(expect, a.spec == b.spec)| so
+ that we get the specs output on the console if the check fails.
+ */
+ if (expect) {
+ /* Check all the sub-pieces too, since that can help with
+ debugging cases when equals() returns something unexpected */
+ /* We don't check port in the loop, because it can be defaulted in
+ some cases. */
+ [
+ "spec",
+ "prePath",
+ "scheme",
+ "userPass",
+ "username",
+ "password",
+ "hostPort",
+ "host",
+ "pathQueryRef",
+ "filePath",
+ "query",
+ "ref",
+ "directory",
+ "fileName",
+ "fileBaseName",
+ "fileExtension",
+ ].map(function (prop) {
+ dump("Testing '" + prop + "'\n");
+ Assert.equal(a[prop], b[prop]);
+ });
+ } else {
+ Assert.notEqual(a.spec, b.spec);
+ }
+ Assert.equal(expect, a.equals(b));
+ Assert.equal(expect, b.equals(a));
+}
+
+function stringToURL(str) {
+ return Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIStandardURLMutator)
+ .init(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 80, str, "UTF-8", null)
+ .finalize()
+ .QueryInterface(Ci.nsIURL);
+}
+
+function pairToURLs(pair) {
+ Assert.equal(pair.length, 2);
+ return pair.map(stringToURL);
+}
+
+add_test(function test_setEmptyPath() {
+ var pairs = [
+ ["http://example.com", "http://example.com/tests/dom/tests"],
+ ["http://example.com:80", "http://example.com/tests/dom/tests"],
+ ["http://example.com:80/", "http://example.com/tests/dom/test"],
+ ["http://example.com/", "http://example.com/tests/dom/tests"],
+ ["http://example.com/a", "http://example.com/tests/dom/tests"],
+ ["http://example.com:80/a", "http://example.com/tests/dom/tests"],
+ ].map(pairToURLs);
+
+ for (var [provided, target] of pairs) {
+ symmetricEquality(false, target, provided);
+
+ provided = provided.mutate().setPathQueryRef("").finalize();
+ target = target.mutate().setPathQueryRef("").finalize();
+
+ Assert.equal(provided.spec, target.spec);
+ symmetricEquality(true, target, provided);
+ }
+ run_next_test();
+});
+
+add_test(function test_setQuery() {
+ var pairs = [
+ ["http://example.com", "http://example.com/?foo"],
+ ["http://example.com/bar", "http://example.com/bar?foo"],
+ ["http://example.com#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/?longerthanfoo#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/?longerthanfoo", "http://example.com/?foo"],
+ /* And one that's nonempty but shorter than "foo" */
+ ["http://example.com/?f#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/?f", "http://example.com/?foo"],
+ ].map(pairToURLs);
+
+ for (var [provided, target] of pairs) {
+ symmetricEquality(false, provided, target);
+
+ provided = provided
+ .mutate()
+ .setQuery("foo")
+ .finalize()
+ .QueryInterface(Ci.nsIURL);
+
+ Assert.equal(provided.spec, target.spec);
+ symmetricEquality(true, provided, target);
+ }
+
+ [provided, target] = [
+ "http://example.com/#",
+ "http://example.com/?foo#bar",
+ ].map(stringToURL);
+ symmetricEquality(false, provided, target);
+ provided = provided
+ .mutate()
+ .setQuery("foo")
+ .finalize()
+ .QueryInterface(Ci.nsIURL);
+ symmetricEquality(false, provided, target);
+
+ var newProvided = Services.io
+ .newURI("#bar", null, provided)
+ .QueryInterface(Ci.nsIURL);
+
+ Assert.equal(newProvided.spec, target.spec);
+ symmetricEquality(true, newProvided, target);
+ run_next_test();
+});
+
+add_test(function test_setRef() {
+ var tests = [
+ ["http://example.com", "", "http://example.com/"],
+ ["http://example.com:80", "", "http://example.com:80/"],
+ ["http://example.com:80/", "", "http://example.com:80/"],
+ ["http://example.com/", "", "http://example.com/"],
+ ["http://example.com/a", "", "http://example.com/a"],
+ ["http://example.com:80/a", "", "http://example.com:80/a"],
+
+ ["http://example.com", "x", "http://example.com/#x"],
+ ["http://example.com:80", "x", "http://example.com:80/#x"],
+ ["http://example.com:80/", "x", "http://example.com:80/#x"],
+ ["http://example.com/", "x", "http://example.com/#x"],
+ ["http://example.com/a", "x", "http://example.com/a#x"],
+ ["http://example.com:80/a", "x", "http://example.com:80/a#x"],
+
+ ["http://example.com", "xx", "http://example.com/#xx"],
+ ["http://example.com:80", "xx", "http://example.com:80/#xx"],
+ ["http://example.com:80/", "xx", "http://example.com:80/#xx"],
+ ["http://example.com/", "xx", "http://example.com/#xx"],
+ ["http://example.com/a", "xx", "http://example.com/a#xx"],
+ ["http://example.com:80/a", "xx", "http://example.com:80/a#xx"],
+
+ [
+ "http://example.com",
+ "xxxxxxxxxxxxxx",
+ "http://example.com/#xxxxxxxxxxxxxx",
+ ],
+ [
+ "http://example.com:80",
+ "xxxxxxxxxxxxxx",
+ "http://example.com:80/#xxxxxxxxxxxxxx",
+ ],
+ [
+ "http://example.com:80/",
+ "xxxxxxxxxxxxxx",
+ "http://example.com:80/#xxxxxxxxxxxxxx",
+ ],
+ [
+ "http://example.com/",
+ "xxxxxxxxxxxxxx",
+ "http://example.com/#xxxxxxxxxxxxxx",
+ ],
+ [
+ "http://example.com/a",
+ "xxxxxxxxxxxxxx",
+ "http://example.com/a#xxxxxxxxxxxxxx",
+ ],
+ [
+ "http://example.com:80/a",
+ "xxxxxxxxxxxxxx",
+ "http://example.com:80/a#xxxxxxxxxxxxxx",
+ ],
+ ];
+
+ for (var [before, ref, result] of tests) {
+ /* Test1: starting with empty ref */
+ var a = stringToURL(before);
+ a = a.mutate().setRef(ref).finalize().QueryInterface(Ci.nsIURL);
+ var b = stringToURL(result);
+
+ Assert.equal(a.spec, b.spec);
+ Assert.equal(ref, b.ref);
+ symmetricEquality(true, a, b);
+
+ /* Test2: starting with non-empty */
+ a = a.mutate().setRef("yyyy").finalize().QueryInterface(Ci.nsIURL);
+ var c = stringToURL(before);
+ c = c.mutate().setRef("yyyy").finalize().QueryInterface(Ci.nsIURL);
+ symmetricEquality(true, a, c);
+
+ /* Test3: reset the ref */
+ a = a.mutate().setRef("").finalize().QueryInterface(Ci.nsIURL);
+ symmetricEquality(true, a, stringToURL(before));
+
+ /* Test4: verify again after reset */
+ a = a.mutate().setRef(ref).finalize().QueryInterface(Ci.nsIURL);
+ symmetricEquality(true, a, b);
+ }
+ run_next_test();
+});
+
+// Bug 960014 - Make nsStandardURL::SetHost less magical around IPv6
+add_test(function test_ipv6() {
+ var url = stringToURL("http://example.com");
+ url = url.mutate().setHost("[2001::1]").finalize();
+ Assert.equal(url.host, "2001::1");
+
+ url = stringToURL("http://example.com");
+ url = url.mutate().setHostPort("[2001::1]:30").finalize();
+ Assert.equal(url.host, "2001::1");
+ Assert.equal(url.port, 30);
+ Assert.equal(url.hostPort, "[2001::1]:30");
+
+ url = stringToURL("http://example.com");
+ url = url.mutate().setHostPort("2001:1").finalize();
+ Assert.equal(url.host, "0.0.7.209");
+ Assert.equal(url.port, 1);
+ Assert.equal(url.hostPort, "0.0.7.209:1");
+ run_next_test();
+});
+
+add_test(function test_ipv6_fail() {
+ var url = stringToURL("http://example.com");
+
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("2001::1").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "missing brackets"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("[2001::1]:20").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "url.host with port"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("[2001::1").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "missing last bracket"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("2001::1]").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "missing first bracket"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("2001[::1]").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad bracket position"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("[]").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "empty IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("[hello]").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHost("[192.168.1.1]").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("2001::1").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "missing brackets"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("[2001::1]30").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "missing : after IP"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("[2001:1]").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("[2001:1]10").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("[2001:1]10:20").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("[2001:1]:10:20").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("[2001:1").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("2001]:1").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("2001:1]").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad IPv6 address"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setHostPort("").finalize();
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "Empty hostPort should fail"
+ );
+
+ // These checks used to fail, but now don't (see bug 1433958 comment 57)
+ url = url.mutate().setHostPort("[2001::1]:").finalize();
+ Assert.equal(url.spec, "http://[2001::1]/");
+ url = url.mutate().setHostPort("[2002::1]:bad").finalize();
+ Assert.equal(url.spec, "http://[2002::1]/");
+
+ run_next_test();
+});
+
+add_test(function test_clearedSpec() {
+ var url = stringToURL("http://example.com/path");
+ Assert.throws(
+ () => {
+ url = url.mutate().setSpec("http: example").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "set bad spec"
+ );
+ Assert.throws(
+ () => {
+ url = url.mutate().setSpec("").finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "set empty spec"
+ );
+ Assert.equal(url.spec, "http://example.com/path");
+ url = url
+ .mutate()
+ .setHost("allizom.org")
+ .finalize()
+ .QueryInterface(Ci.nsIURL);
+
+ var ref = stringToURL("http://allizom.org/path");
+ symmetricEquality(true, url, ref);
+ run_next_test();
+});
+
+add_test(function test_escapeBrackets() {
+ // Query
+ var url = stringToURL("http://example.com/?a[x]=1");
+ Assert.equal(url.spec, "http://example.com/?a[x]=1");
+
+ url = stringToURL("http://example.com/?a%5Bx%5D=1");
+ Assert.equal(url.spec, "http://example.com/?a%5Bx%5D=1");
+
+ url = stringToURL("http://[2001::1]/?a[x]=1");
+ Assert.equal(url.spec, "http://[2001::1]/?a[x]=1");
+
+ url = stringToURL("http://[2001::1]/?a%5Bx%5D=1");
+ Assert.equal(url.spec, "http://[2001::1]/?a%5Bx%5D=1");
+
+ // Path
+ url = stringToURL("http://example.com/brackets[x]/test");
+ Assert.equal(url.spec, "http://example.com/brackets[x]/test");
+
+ url = stringToURL("http://example.com/a%5Bx%5D/test");
+ Assert.equal(url.spec, "http://example.com/a%5Bx%5D/test");
+ run_next_test();
+});
+
+add_test(function test_escapeQuote() {
+ var url = stringToURL("http://example.com/#'");
+ Assert.equal(url.spec, "http://example.com/#'");
+ Assert.equal(url.ref, "'");
+ url = url.mutate().setRef("test'test").finalize();
+ Assert.equal(url.spec, "http://example.com/#test'test");
+ Assert.equal(url.ref, "test'test");
+ run_next_test();
+});
+
+add_test(function test_apostropheEncoding() {
+ // For now, single quote is escaped everywhere _except_ the path.
+ // This policy is controlled by the bitmask in nsEscape.cpp::EscapeChars[]
+ var url = stringToURL("http://example.com/dir'/file'.ext'");
+ Assert.equal(url.spec, "http://example.com/dir'/file'.ext'");
+ run_next_test();
+});
+
+add_test(function test_accentEncoding() {
+ var url = stringToURL("http://example.com/?hello=`");
+ Assert.equal(url.spec, "http://example.com/?hello=`");
+ Assert.equal(url.query, "hello=`");
+
+ url = stringToURL("http://example.com/?hello=%2C");
+ Assert.equal(url.spec, "http://example.com/?hello=%2C");
+ Assert.equal(url.query, "hello=%2C");
+ run_next_test();
+});
+
+add_test(
+ { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
+ function test_percentDecoding() {
+ var url = stringToURL("http://%70%61%73%74%65%62%69%6E.com");
+ Assert.equal(url.spec, "http://pastebin.com/");
+
+ // Disallowed hostname characters are rejected even when percent encoded
+ Assert.throws(
+ () => {
+ url = stringToURL("http://example.com%0a%23.google.com/");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "invalid characters are not allowed"
+ );
+ run_next_test();
+ }
+);
+
+add_test(function test_hugeStringThrows() {
+ let prefs = Services.prefs;
+ let maxLen = prefs.getIntPref("network.standard-url.max-length");
+ let url = stringToURL("http://test:test@example.com");
+
+ let hugeString = new Array(maxLen + 1).fill("a").join("");
+ let setters = [
+ { method: "setSpec", qi: Ci.nsIURIMutator },
+ { method: "setUsername", qi: Ci.nsIURIMutator },
+ { method: "setPassword", qi: Ci.nsIURIMutator },
+ { method: "setFilePath", qi: Ci.nsIURIMutator },
+ { method: "setHostPort", qi: Ci.nsIURIMutator },
+ { method: "setHost", qi: Ci.nsIURIMutator },
+ { method: "setUserPass", qi: Ci.nsIURIMutator },
+ { method: "setPathQueryRef", qi: Ci.nsIURIMutator },
+ { method: "setQuery", qi: Ci.nsIURIMutator },
+ { method: "setRef", qi: Ci.nsIURIMutator },
+ { method: "setScheme", qi: Ci.nsIURIMutator },
+ { method: "setFileName", qi: Ci.nsIURLMutator },
+ { method: "setFileExtension", qi: Ci.nsIURLMutator },
+ { method: "setFileBaseName", qi: Ci.nsIURLMutator },
+ ];
+
+ for (let prop of setters) {
+ Assert.throws(
+ () =>
+ (url = url
+ .mutate()
+ .QueryInterface(prop.qi)
+ [prop.method](hugeString)
+ .finalize()),
+ /NS_ERROR_MALFORMED_URI/,
+ `Passing a huge string to "${prop.method}" should throw`
+ );
+ }
+
+ run_next_test();
+});
+
+add_test(function test_filterWhitespace() {
+ let url = stringToURL(
+ " \r\n\th\nt\rt\tp://ex\r\n\tample.com/path\r\n\t/\r\n\tto the/fil\r\n\te.e\r\n\txt?que\r\n\try#ha\r\n\tsh \r\n\t "
+ );
+ Assert.equal(
+ url.spec,
+ "http://example.com/path/to%20the/file.ext?query#hash"
+ );
+
+ // These setters should filter \r\n\t.
+ url = stringToURL("http://test.com/path?query#hash");
+ url = url.mutate().setFilePath("pa\r\n\tth").finalize();
+ Assert.equal(url.spec, "http://test.com/path?query#hash");
+ url = url.mutate().setQuery("que\r\n\try").finalize();
+ Assert.equal(url.spec, "http://test.com/path?query#hash");
+ url = url.mutate().setRef("ha\r\n\tsh").finalize();
+ Assert.equal(url.spec, "http://test.com/path?query#hash");
+ url = url
+ .mutate()
+ .QueryInterface(Ci.nsIURLMutator)
+ .setFileName("fi\r\n\tle.name")
+ .finalize();
+ Assert.equal(url.spec, "http://test.com/fi%0D%0A%09le.name?query#hash");
+
+ run_next_test();
+});
+
+add_test(function test_backslashReplacement() {
+ var url = stringToURL(
+ "http:\\\\test.com\\path/to\\file?query\\backslash#hash\\"
+ );
+ Assert.equal(
+ url.spec,
+ "http://test.com/path/to/file?query\\backslash#hash\\"
+ );
+
+ url = stringToURL("http:\\\\test.com\\example.org/path\\to/file");
+ Assert.equal(url.spec, "http://test.com/example.org/path/to/file");
+ Assert.equal(url.host, "test.com");
+ Assert.equal(url.pathQueryRef, "/example.org/path/to/file");
+
+ run_next_test();
+});
+
+add_test(function test_authority_host() {
+ Assert.throws(
+ () => {
+ stringToURL("http:");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "TYPE_AUTHORITY should have host"
+ );
+ Assert.throws(
+ () => {
+ stringToURL("http:///");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "TYPE_AUTHORITY should have host"
+ );
+
+ run_next_test();
+});
+
+add_test(function test_trim_C0_and_space() {
+ var url = stringToURL(
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f http://example.com/ \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f "
+ );
+ Assert.equal(url.spec, "http://example.com/");
+ url = url
+ .mutate()
+ .setSpec(
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f http://test.com/ \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f "
+ )
+ .finalize();
+ Assert.equal(url.spec, "http://test.com/");
+ Assert.throws(
+ () => {
+ url = url
+ .mutate()
+ .setSpec(
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19 "
+ )
+ .finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "set empty spec"
+ );
+ run_next_test();
+});
+
+// This tests that C0-and-space characters in the path, query and ref are
+// percent encoded.
+add_test(function test_encode_C0_and_space() {
+ function toHex(d) {
+ var hex = d.toString(16);
+ if (hex.length == 1) {
+ hex = "0" + hex;
+ }
+ return hex.toUpperCase();
+ }
+
+ for (var i = 0x0; i <= 0x20; i++) {
+ // These characters get filtered - they are not encoded.
+ if (
+ String.fromCharCode(i) == "\r" ||
+ String.fromCharCode(i) == "\n" ||
+ String.fromCharCode(i) == "\t"
+ ) {
+ continue;
+ }
+ let url = stringToURL(
+ "http://example.com/pa" +
+ String.fromCharCode(i) +
+ "th?qu" +
+ String.fromCharCode(i) +
+ "ery#ha" +
+ String.fromCharCode(i) +
+ "sh"
+ );
+ Assert.equal(
+ url.spec,
+ "http://example.com/pa%" +
+ toHex(i) +
+ "th?qu%" +
+ toHex(i) +
+ "ery#ha%" +
+ toHex(i) +
+ "sh"
+ );
+ }
+
+ // Additionally, we need to check the setters.
+ let url = stringToURL("http://example.com/path?query#hash");
+ url = url.mutate().setFilePath("pa\0th").finalize();
+ Assert.equal(url.spec, "http://example.com/pa%00th?query#hash");
+ url = url.mutate().setQuery("qu\0ery").finalize();
+ Assert.equal(url.spec, "http://example.com/pa%00th?qu%00ery#hash");
+ url = url.mutate().setRef("ha\0sh").finalize();
+ Assert.equal(url.spec, "http://example.com/pa%00th?qu%00ery#ha%00sh");
+ url = url
+ .mutate()
+ .QueryInterface(Ci.nsIURLMutator)
+ .setFileName("fi\0le.name")
+ .finalize();
+ Assert.equal(url.spec, "http://example.com/fi%00le.name?qu%00ery#ha%00sh");
+
+ run_next_test();
+});
+
+add_test(function test_ipv4Normalize() {
+ var localIPv4s = [
+ "http://127.0.0.1",
+ "http://127.0.1",
+ "http://127.1",
+ "http://2130706433",
+ "http://0177.00.00.01",
+ "http://0177.00.01",
+ "http://0177.01",
+ "http://00000000000000000000000000177.0000000.0000000.0001",
+ "http://000000177.0000001",
+ "http://017700000001",
+ "http://0x7f.0x00.0x00.0x01",
+ "http://0x7f.0x01",
+ "http://0x7f000001",
+ "http://0x007f.0x0000.0x0000.0x0001",
+ "http://000177.0.00000.0x0001",
+ "http://127.0.0.1.",
+ ].map(stringToURL);
+
+ let url;
+ for (url of localIPv4s) {
+ Assert.equal(url.spec, "http://127.0.0.1/");
+ }
+
+ // These should treated as a domain instead of an IPv4.
+ var nonIPv4s = [
+ "http://2+3/",
+ "http://0.0.0.-1/",
+ "http://1.2.3.4../",
+ "resource://123/",
+ "resource://4294967296/",
+ ];
+ var spec;
+ for (spec of nonIPv4s) {
+ url = stringToURL(spec);
+ Assert.equal(url.spec, spec);
+ }
+
+ url = stringToURL("resource://path/to/resource/");
+ url = url.mutate().setHost("123").finalize();
+ Assert.equal(url.host, "123");
+
+ run_next_test();
+});
+
+add_test(function test_invalidHostChars() {
+ var url = stringToURL("http://example.org/");
+ for (let i = 0; i <= 0x20; i++) {
+ // These characters get filtered.
+ if (
+ String.fromCharCode(i) == "\r" ||
+ String.fromCharCode(i) == "\n" ||
+ String.fromCharCode(i) == "\t"
+ ) {
+ continue;
+ }
+ Assert.throws(
+ () => {
+ url = url
+ .mutate()
+ .setHost("a" + String.fromCharCode(i) + "b")
+ .finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "Trying to set hostname containing char code: " + i
+ );
+ }
+ for (let c of '@[]*<>|:"') {
+ Assert.throws(
+ () => {
+ url = url
+ .mutate()
+ .setHost("a" + c)
+ .finalize();
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "Trying to set hostname containing char: " + c
+ );
+ }
+
+ // It also can't contain /, \, #, ?, but we treat these characters as
+ // hostname separators, so there is no way to set them and fail.
+ run_next_test();
+});
+
+add_test(function test_normalize_ipv6() {
+ var url = stringToURL("http://example.com");
+ url = url.mutate().setHost("[::192.9.5.5]").finalize();
+ Assert.equal(url.spec, "http://[::c009:505]/");
+
+ run_next_test();
+});
+
+add_test(function test_emptyPassword() {
+ var url = stringToURL("http://a:@example.com");
+ Assert.equal(url.spec, "http://a@example.com/");
+ url = url.mutate().setPassword("pp").finalize();
+ Assert.equal(url.spec, "http://a:pp@example.com/");
+ url = url.mutate().setPassword("").finalize();
+ Assert.equal(url.spec, "http://a@example.com/");
+ url = url.mutate().setUserPass("xxx:").finalize();
+ Assert.equal(url.spec, "http://xxx@example.com/");
+ url = url.mutate().setPassword("zzzz").finalize();
+ Assert.equal(url.spec, "http://xxx:zzzz@example.com/");
+ url = url.mutate().setUserPass("xxxxx:yyyyyy").finalize();
+ Assert.equal(url.spec, "http://xxxxx:yyyyyy@example.com/");
+ url = url.mutate().setUserPass("z:").finalize();
+ Assert.equal(url.spec, "http://z@example.com/");
+ url = url.mutate().setPassword("ppppppppppp").finalize();
+ Assert.equal(url.spec, "http://z:ppppppppppp@example.com/");
+
+ url = stringToURL("http://example.com");
+ url = url.mutate().setPassword("").finalize(); // Still empty. Should work.
+ Assert.equal(url.spec, "http://example.com/");
+
+ run_next_test();
+});
+
+add_test(function test_emptyUser() {
+ let url = stringToURL("http://:a@example.com/path/to/something?query#hash");
+ Assert.equal(url.spec, "http://:a@example.com/path/to/something?query#hash");
+ url = stringToURL("http://:@example.com/path/to/something?query#hash");
+ Assert.equal(url.spec, "http://example.com/path/to/something?query#hash");
+
+ const kurl = stringToURL(
+ "http://user:pass@example.com:8888/path/to/something?query#hash"
+ );
+ url = kurl.mutate().setUsername("").finalize();
+ Assert.equal(
+ url.spec,
+ "http://:pass@example.com:8888/path/to/something?query#hash"
+ );
+ Assert.equal(url.host, "example.com");
+ Assert.equal(url.hostPort, "example.com:8888");
+ Assert.equal(url.filePath, "/path/to/something");
+ Assert.equal(url.query, "query");
+ Assert.equal(url.ref, "hash");
+ url = kurl.mutate().setUserPass(":pass1").finalize();
+ Assert.equal(
+ url.spec,
+ "http://:pass1@example.com:8888/path/to/something?query#hash"
+ );
+ Assert.equal(url.host, "example.com");
+ Assert.equal(url.hostPort, "example.com:8888");
+ Assert.equal(url.filePath, "/path/to/something");
+ Assert.equal(url.query, "query");
+ Assert.equal(url.ref, "hash");
+ url = url.mutate().setUsername("user2").finalize();
+ Assert.equal(
+ url.spec,
+ "http://user2:pass1@example.com:8888/path/to/something?query#hash"
+ );
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setUserPass(":pass234").finalize();
+ Assert.equal(
+ url.spec,
+ "http://:pass234@example.com:8888/path/to/something?query#hash"
+ );
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setUserPass("").finalize();
+ Assert.equal(
+ url.spec,
+ "http://example.com:8888/path/to/something?query#hash"
+ );
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setPassword("pa").finalize();
+ Assert.equal(
+ url.spec,
+ "http://:pa@example.com:8888/path/to/something?query#hash"
+ );
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setUserPass("user:pass").finalize();
+ symmetricEquality(true, url.QueryInterface(Ci.nsIURL), kurl);
+
+ url = stringToURL("http://example.com:8888/path/to/something?query#hash");
+ url = url.mutate().setPassword("pass").finalize();
+ Assert.equal(
+ url.spec,
+ "http://:pass@example.com:8888/path/to/something?query#hash"
+ );
+ url = url.mutate().setUsername("").finalize();
+ Assert.equal(
+ url.spec,
+ "http://:pass@example.com:8888/path/to/something?query#hash"
+ );
+
+ url = stringToURL("http://example.com:8888");
+ url = url.mutate().setUsername("user").finalize();
+ url = url.mutate().setUsername("").finalize();
+ Assert.equal(url.spec, "http://example.com:8888/");
+
+ url = stringToURL("http://:pass@example.com");
+ Assert.equal(url.spec, "http://:pass@example.com/");
+ url = url.mutate().setPassword("").finalize();
+ Assert.equal(url.spec, "http://example.com/");
+ url = url.mutate().setUserPass("user:pass").finalize();
+ Assert.equal(url.spec, "http://user:pass@example.com/");
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setUserPass("u:p").finalize();
+ Assert.equal(url.spec, "http://u:p@example.com/");
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setUserPass("u1:p23").finalize();
+ Assert.equal(url.spec, "http://u1:p23@example.com/");
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setUsername("u").finalize();
+ Assert.equal(url.spec, "http://u:p23@example.com/");
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setPassword("p").finalize();
+ Assert.equal(url.spec, "http://u:p@example.com/");
+ Assert.equal(url.host, "example.com");
+
+ url = url.mutate().setUserPass("u2:p2").finalize();
+ Assert.equal(url.spec, "http://u2:p2@example.com/");
+ Assert.equal(url.host, "example.com");
+ url = url.mutate().setUserPass("u23:p23").finalize();
+ Assert.equal(url.spec, "http://u23:p23@example.com/");
+ Assert.equal(url.host, "example.com");
+
+ run_next_test();
+});
+
+registerCleanupFunction(function () {
+ gPrefs.clearUserPref("network.standard-url.punycode-host");
+});
+
+add_test(function test_idna_host() {
+ // See bug 945240 - this test makes sure that URLs return a punycode hostname
+ let url = stringToURL(
+ "http://user:password@ält.example.org:8080/path?query#etc"
+ );
+ equal(url.host, "xn--lt-uia.example.org");
+ equal(url.hostPort, "xn--lt-uia.example.org:8080");
+ equal(url.prePath, "http://user:password@xn--lt-uia.example.org:8080");
+ equal(
+ url.spec,
+ "http://user:password@xn--lt-uia.example.org:8080/path?query#etc"
+ );
+ equal(
+ url.specIgnoringRef,
+ "http://user:password@xn--lt-uia.example.org:8080/path?query"
+ );
+ equal(
+ url
+ .QueryInterface(Ci.nsISensitiveInfoHiddenURI)
+ .getSensitiveInfoHiddenSpec(),
+ "http://user:****@xn--lt-uia.example.org:8080/path?query#etc"
+ );
+
+ equal(url.displayHost, "ält.example.org");
+ equal(url.displayHostPort, "ält.example.org:8080");
+ equal(
+ url.displaySpec,
+ "http://user:password@ält.example.org:8080/path?query#etc"
+ );
+
+ equal(url.asciiHost, "xn--lt-uia.example.org");
+ equal(url.asciiHostPort, "xn--lt-uia.example.org:8080");
+ equal(
+ url.asciiSpec,
+ "http://user:password@xn--lt-uia.example.org:8080/path?query#etc"
+ );
+
+ url = url.mutate().setRef("").finalize(); // SetRef calls InvalidateCache()
+ equal(
+ url.spec,
+ "http://user:password@xn--lt-uia.example.org:8080/path?query"
+ );
+ equal(
+ url.displaySpec,
+ "http://user:password@ält.example.org:8080/path?query"
+ );
+ equal(
+ url.asciiSpec,
+ "http://user:password@xn--lt-uia.example.org:8080/path?query"
+ );
+
+ url = stringToURL("http://user:password@www.ält.com:8080/path?query#etc");
+ url = url.mutate().setRef("").finalize();
+ equal(url.spec, "http://user:password@www.xn--lt-uia.com:8080/path?query");
+
+ run_next_test();
+});
+
+add_test(
+ { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
+ function test_bug1517025() {
+ Assert.throws(
+ () => {
+ stringToURL("https://b%9a/");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad URI"
+ );
+
+ Assert.throws(
+ () => {
+ stringToURL("https://b%9ª/");
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad URI"
+ );
+
+ let base = stringToURL(
+ "https://bug1517025.bmoattachments.org/attachment.cgi?id=9033787"
+ );
+ Assert.throws(
+ () => {
+ Services.io.newURI("/\\b%9ª", "windows-1252", base);
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "bad URI"
+ );
+
+ run_next_test();
+ }
+);
+
+add_task(async function test_emptyHostWithURLType() {
+ let makeURL = (str, type) => {
+ return Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIStandardURLMutator)
+ .init(type, 80, str, "UTF-8", null)
+ .finalize()
+ .QueryInterface(Ci.nsIURL);
+ };
+
+ let url = makeURL("http://foo.com/bar/", Ci.nsIStandardURL.URLTYPE_AUTHORITY);
+ Assert.throws(
+ () => url.mutate().setHost("").finalize().spec,
+ /NS_ERROR_UNEXPECTED/,
+ "Empty host is not allowed for URLTYPE_AUTHORITY"
+ );
+
+ url = makeURL("http://foo.com/bar/", Ci.nsIStandardURL.URLTYPE_STANDARD);
+ Assert.throws(
+ () => url.mutate().setHost("").finalize().spec,
+ /NS_ERROR_UNEXPECTED/,
+ "Empty host is not allowed for URLTYPE_STANDARD"
+ );
+
+ url = makeURL("http://foo.com/bar/", Ci.nsIStandardURL.URLTYPE_NO_AUTHORITY);
+ equal(
+ url.spec,
+ "http:///bar/",
+ "Host is removed when parsing URLTYPE_NO_AUTHORITY"
+ );
+ equal(
+ url.mutate().setHost("").finalize().spec,
+ "http:///bar/",
+ "Setting an empty host does nothing for URLTYPE_NO_AUTHORITY"
+ );
+ Assert.throws(
+ () => url.mutate().setHost("something").finalize().spec,
+ /NS_ERROR_UNEXPECTED/,
+ "Setting a non-empty host is not allowed for URLTYPE_NO_AUTHORITY"
+ );
+ equal(
+ url.mutate().setHost("#j").finalize().spec,
+ "http:///bar/",
+ "Setting a pseudo-empty host does nothing for URLTYPE_NO_AUTHORITY"
+ );
+
+ url = makeURL(
+ "http://example.org:123/foo?bar#baz",
+ Ci.nsIStandardURL.URLTYPE_AUTHORITY
+ );
+ Assert.throws(
+ () => url.mutate().setHost("#j").finalize().spec,
+ /NS_ERROR_UNEXPECTED/,
+ "A pseudo-empty host is not allowed for URLTYPE_AUTHORITY"
+ );
+});
+
+add_task(async function test_fuzz() {
+ let makeURL = str => {
+ return (
+ Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIStandardURLMutator)
+ .QueryInterface(Ci.nsIURIMutator)
+ // .init(type, 80, str, "UTF-8", null)
+ .setSpec(str)
+ .finalize()
+ .QueryInterface(Ci.nsIURL)
+ );
+ };
+
+ Assert.throws(() => {
+ let url = makeURL("/");
+ url.mutate().setHost("(").finalize();
+ }, /NS_ERROR_MALFORMED_URI/);
+});
+
+add_task(async function test_bug1648493() {
+ let url = stringToURL("https://example.com/");
+ url = url.mutate().setScheme("file").finalize();
+ url = url.mutate().setScheme("resource").finalize();
+ url = url.mutate().setPassword("ê").finalize();
+ url = url.mutate().setUsername("ç").finalize();
+ url = url.mutate().setScheme("t").finalize();
+ equal(url.spec, "t://%C3%83%C2%A7:%C3%83%C2%AA@example.com/");
+ equal(url.username, "%C3%83%C2%A7");
+});
diff --git a/netwerk/test/unit/test_standardurl_default_port.js b/netwerk/test/unit/test_standardurl_default_port.js
new file mode 100644
index 0000000000..a62edcf0e7
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_default_port.js
@@ -0,0 +1,58 @@
+/* -*- Mode: javascript; indent-tabs-mode: nil; js-indent-level: 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/. */
+
+/* This test exercises the nsIStandardURL "setDefaultPort" API. */
+
+"use strict";
+
+function run_test() {
+ function stringToURL(str) {
+ return Cc["@mozilla.org/network/standard-url-mutator;1"]
+ .createInstance(Ci.nsIStandardURLMutator)
+ .init(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 80, str, "UTF-8", null)
+ .finalize()
+ .QueryInterface(Ci.nsIStandardURL);
+ }
+
+ // Create a nsStandardURL:
+ var origUrlStr = "http://foo.com/";
+ var stdUrl = stringToURL(origUrlStr);
+ Assert.equal(-1, stdUrl.port);
+
+ // Changing default port shouldn't adjust the value returned by "port",
+ // or the string representation.
+ let def100Url = stdUrl
+ .mutate()
+ .QueryInterface(Ci.nsIStandardURLMutator)
+ .setDefaultPort(100)
+ .finalize();
+ Assert.equal(-1, def100Url.port);
+ Assert.equal(def100Url.spec, origUrlStr);
+
+ // Changing port directly should update .port and .spec, though:
+ let port200Url = stdUrl.mutate().setPort("200").finalize();
+ Assert.equal(200, port200Url.port);
+ Assert.equal(port200Url.spec, "http://foo.com:200/");
+
+ // ...but then if we change default port to match the custom port,
+ // the custom port should reset to -1 and disappear from .spec:
+ let def200Url = port200Url
+ .mutate()
+ .QueryInterface(Ci.nsIStandardURLMutator)
+ .setDefaultPort(200)
+ .finalize();
+ Assert.equal(-1, def200Url.port);
+ Assert.equal(def200Url.spec, origUrlStr);
+
+ // And further changes to default port should not make custom port reappear.
+ let def300Url = def200Url
+ .mutate()
+ .QueryInterface(Ci.nsIStandardURLMutator)
+ .setDefaultPort(300)
+ .finalize();
+ Assert.equal(-1, def300Url.port);
+ Assert.equal(def300Url.spec, origUrlStr);
+}
diff --git a/netwerk/test/unit/test_standardurl_port.js b/netwerk/test/unit/test_standardurl_port.js
new file mode 100644
index 0000000000..76fdad6405
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_port.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function run_test() {
+ function makeURI(aURLSpec, aCharset) {
+ return Services.io.newURI(aURLSpec, aCharset);
+ }
+
+ var httpURI = makeURI("http://foo.com");
+ Assert.equal(-1, httpURI.port);
+
+ // Setting to default shouldn't cause a change
+ httpURI = httpURI.mutate().setPort(80).finalize();
+ Assert.equal(-1, httpURI.port);
+
+ // Setting to default after setting to non-default shouldn't cause a change (bug 403480)
+ httpURI = httpURI.mutate().setPort(123).finalize();
+ Assert.equal(123, httpURI.port);
+ httpURI = httpURI.mutate().setPort(80).finalize();
+ Assert.equal(-1, httpURI.port);
+ Assert.ok(!/80/.test(httpURI.spec));
+
+ // URL parsers shouldn't set ports to default value (bug 407538)
+ httpURI = httpURI.mutate().setSpec("http://foo.com:81").finalize();
+ Assert.equal(81, httpURI.port);
+ httpURI = httpURI.mutate().setSpec("http://foo.com:80").finalize();
+ Assert.equal(-1, httpURI.port);
+ Assert.ok(!/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com");
+ Assert.equal(-1, httpURI.port);
+ Assert.ok(!/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com:80");
+ Assert.equal(-1, httpURI.port);
+ Assert.ok(!/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com:80");
+ Assert.equal(-1, httpURI.port);
+ Assert.ok(!/80/.test(httpURI.spec));
+
+ httpURI = makeURI("https://foo.com");
+ Assert.equal(-1, httpURI.port);
+ Assert.ok(!/443/.test(httpURI.spec));
+
+ httpURI = makeURI("https://foo.com:443");
+ Assert.equal(-1, httpURI.port);
+ Assert.ok(!/443/.test(httpURI.spec));
+
+ // XXX URL parsers shouldn't set ports to default value, even when changing scheme?
+ // not really possible given current nsIURI impls
+ //httpURI.spec = "https://foo.com:443";
+ //do_check_eq(-1, httpURI.port);
+}
diff --git a/netwerk/test/unit/test_streamcopier.js b/netwerk/test/unit/test_streamcopier.js
new file mode 100644
index 0000000000..f550923546
--- /dev/null
+++ b/netwerk/test/unit/test_streamcopier.js
@@ -0,0 +1,63 @@
+"use strict";
+
+var testStr = "This is a test. ";
+for (var i = 0; i < 10; ++i) {
+ testStr += testStr;
+}
+
+function run_test() {
+ // Set up our stream to copy
+ var inStr = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ inStr.setData(testStr, testStr.length);
+
+ // Set up our destination stream. Make sure to use segments a good
+ // bit smaller than our data length.
+ Assert.ok(testStr.length > 1024 * 10);
+ var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 1024, 0xffffffff, null);
+
+ var streamCopier = Cc[
+ "@mozilla.org/network/async-stream-copier;1"
+ ].createInstance(Ci.nsIAsyncStreamCopier);
+ streamCopier.init(
+ inStr,
+ pipe.outputStream,
+ null,
+ true,
+ true,
+ 1024,
+ true,
+ true
+ );
+
+ var ctx = {};
+ ctx.wrappedJSObject = ctx;
+
+ var observer = {
+ onStartRequest(aRequest) {},
+ onStopRequest(aRequest, aStatusCode) {
+ Assert.equal(aStatusCode, 0);
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sis.init(pipe.inputStream);
+ var result = "";
+ var temp;
+ try {
+ // Need this because read() can throw at EOF
+ while ((temp = sis.read(1024))) {
+ result += temp;
+ }
+ } catch (e) {
+ Assert.equal(e.result, Cr.NS_BASE_STREAM_CLOSED);
+ }
+ Assert.equal(result, testStr);
+ do_test_finished();
+ },
+ };
+
+ streamCopier.asyncCopy(observer, ctx);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_substituting_protocol_handler.js b/netwerk/test/unit/test_substituting_protocol_handler.js
new file mode 100644
index 0000000000..00e5af0c21
--- /dev/null
+++ b/netwerk/test/unit/test_substituting_protocol_handler.js
@@ -0,0 +1,64 @@
+"use strict";
+
+add_task(async function test_case_insensitive_substitutions() {
+ let resProto = Services.io
+ .getProtocolHandler("resource")
+ .QueryInterface(Ci.nsISubstitutingProtocolHandler);
+
+ let uri = Services.io.newFileURI(do_get_file("data"));
+
+ resProto.setSubstitution("FooBar", uri);
+ resProto.setSubstitutionWithFlags("BarBaz", uri, 0);
+
+ equal(
+ resProto.resolveURI(Services.io.newURI("resource://foobar/")),
+ uri.spec,
+ "Got correct resolved URI for setSubstitution"
+ );
+
+ equal(
+ resProto.resolveURI(Services.io.newURI("resource://foobar/")),
+ uri.spec,
+ "Got correct resolved URI for setSubstitutionWithFlags"
+ );
+
+ ok(
+ resProto.hasSubstitution("foobar"),
+ "hasSubstitution works with all-lower-case root"
+ );
+ ok(
+ resProto.hasSubstitution("FooBar"),
+ "hasSubstitution works with mixed-case root"
+ );
+
+ equal(
+ resProto.getSubstitution("foobar").spec,
+ uri.spec,
+ "getSubstitution works with all-lower-case root"
+ );
+ equal(
+ resProto.getSubstitution("FooBar").spec,
+ uri.spec,
+ "getSubstitution works with mixed-case root"
+ );
+
+ resProto.setSubstitution("foobar", null);
+ resProto.setSubstitution("barbaz", null);
+
+ Assert.throws(
+ () => resProto.resolveURI(Services.io.newURI("resource://foobar/")),
+ e => e.result == Cr.NS_ERROR_NOT_AVAILABLE,
+ "Correctly unregistered case-insensitive substitution in setSubstitution"
+ );
+ Assert.throws(
+ () => resProto.resolveURI(Services.io.newURI("resource://barbaz/")),
+ e => e.result == Cr.NS_ERROR_NOT_AVAILABLE,
+ "Correctly unregistered case-insensitive substitution in setSubstitutionWithFlags"
+ );
+
+ Assert.throws(
+ () => resProto.getSubstitution("foobar"),
+ e => e.result == Cr.NS_ERROR_NOT_AVAILABLE,
+ "foobar substitution has been removed"
+ );
+});
diff --git a/netwerk/test/unit/test_suspend_channel_before_connect.js b/netwerk/test/unit/test_suspend_channel_before_connect.js
new file mode 100644
index 0000000000..79a0c43236
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_before_connect.js
@@ -0,0 +1,93 @@
+"use strict";
+
+var CC = Components.Constructor;
+
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+
+var obs = Services.obs;
+
+// A server that waits for a connect. If a channel is suspended it should not
+// try to connect to the server until it is is resumed or not try at all if it
+// is cancelled as in this test.
+function TestServer() {
+ this.listener = ServerSocket(-1, true, -1);
+ this.port = this.listener.port;
+ this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+ onSocketAccepted(socket, trans) {
+ Assert.ok(false, "Socket should not have tried to connect!");
+ },
+
+ onStopListening(socket) {},
+
+ stop() {
+ try {
+ this.listener.close();
+ } catch (ignore) {}
+ },
+};
+
+var requestListenerObserver = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (
+ topic === "http-on-modify-request" &&
+ subject instanceof Ci.nsIHttpChannel
+ ) {
+ var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+ chan.suspend();
+ obs.removeObserver(this, "http-on-modify-request");
+
+ // Timers are bad, but we need to wait to see that we are not trying to
+ // connect to the server. There are no other event since nothing should
+ // happen until we resume the channel.
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(
+ () => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ chan.resume();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ }
+ },
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ executeSoon(run_next_test);
+ },
+};
+
+// Add observer and start a channel. Observer is going to suspend the channel on
+// "http-on-modify-request" even. If a channel is suspended so early it should
+// not try to connect at all until it is resumed. In this case we are going to
+// wait for some time and cancel the channel before resuming it.
+add_test(function testNoConnectChannelCanceledEarly() {
+ let serv = new TestServer();
+
+ obs.addObserver(requestListenerObserver, "http-on-modify-request");
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + serv.port,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(listener);
+
+ registerCleanupFunction(function () {
+ serv.stop();
+ });
+});
diff --git a/netwerk/test/unit/test_suspend_channel_on_authRetry.js b/netwerk/test/unit/test_suspend_channel_on_authRetry.js
new file mode 100644
index 0000000000..ad0b728b68
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_authRetry.js
@@ -0,0 +1,264 @@
+// This file tests async handling of a channel suspended in DoAuthRetry
+// notifying http-on-modify-request and http-on-before-connect observers.
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+var obs = Services.obs;
+
+var requestObserver = null;
+
+function AuthPrompt() {}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
+
+ prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword: function promptUP(
+ title,
+ text,
+ realm,
+ savePW,
+ user,
+ pw
+ ) {
+ user.value = this.user;
+ pw.value = this.pass;
+
+ obs.addObserver(requestObserver, "http-on-before-connect");
+ obs.addObserver(requestObserver, "http-on-modify-request");
+ return true;
+ },
+
+ promptPassword: function promptPW(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ },
+};
+
+function requestListenerObserver(
+ suspendOnBeforeConnect,
+ suspendOnModifyRequest
+) {
+ this.suspendOnModifyRequest = suspendOnModifyRequest;
+ this.suspendOnBeforeConnect = suspendOnBeforeConnect;
+}
+
+requestListenerObserver.prototype = {
+ suspendOnModifyRequest: false,
+ suspendOnBeforeConnect: false,
+ gotOnBeforeConnect: false,
+ resumeOnBeforeConnect: false,
+ gotOnModifyRequest: false,
+ resumeOnModifyRequest: false,
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (
+ topic === "http-on-before-connect" &&
+ subject instanceof Ci.nsIHttpChannel
+ ) {
+ if (this.suspendOnBeforeConnect) {
+ let chan = subject.QueryInterface(Ci.nsIHttpChannel);
+ executeSoon(() => {
+ this.resumeOnBeforeConnect = true;
+ chan.resume();
+ });
+ this.gotOnBeforeConnect = true;
+ chan.suspend();
+ }
+ } else if (
+ topic === "http-on-modify-request" &&
+ subject instanceof Ci.nsIHttpChannel
+ ) {
+ if (this.suspendOnModifyRequest) {
+ let chan = subject.QueryInterface(Ci.nsIHttpChannel);
+ executeSoon(() => {
+ this.resumeOnModifyRequest = true;
+ chan.resume();
+ });
+ this.gotOnModifyRequest = true;
+ chan.suspend();
+ }
+ }
+ },
+};
+
+function Requestor() {}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt) {
+ this.prompt = new AuthPrompt();
+ }
+ return this.prompt;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt: null,
+};
+
+var listener = {
+ expectedCode: -1, // Uninitialized
+
+ onStartRequest: function test_onStartR(request) {
+ try {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code!");
+ }
+
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ do_throw("Expecting an HTTP channel");
+ }
+
+ Assert.equal(request.responseStatus, this.expectedCode);
+ // The request should be succeeded iff we expect 200
+ Assert.equal(request.requestSucceeded, this.expectedCode == 200);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ Assert.equal(status, Cr.NS_ERROR_ABORT);
+ if (requestObserver.suspendOnBeforeConnect) {
+ Assert.ok(
+ requestObserver.gotOnBeforeConnect &&
+ requestObserver.resumeOnBeforeConnect
+ );
+ }
+ if (requestObserver.suspendOnModifyRequest) {
+ Assert.ok(
+ requestObserver.gotOnModifyRequest &&
+ requestObserver.resumeOnModifyRequest
+ );
+ }
+ obs.removeObserver(requestObserver, "http-on-before-connect");
+ obs.removeObserver(requestObserver, "http-on-modify-request");
+ moveToNextTest();
+ },
+};
+
+function makeChan(url, loadingUrl) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+var tests = [
+ test_suspend_on_before_connect,
+ test_suspend_on_modify_request,
+ test_suspend_all,
+];
+
+var current_test = 0;
+
+var httpserv = null;
+
+function moveToNextTest() {
+ if (current_test < tests.length - 1) {
+ // First, gotta clear the auth cache
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+
+ do_test_finished();
+}
+
+function run_test() {
+ httpserv = new HttpServer();
+
+ httpserv.registerPathHandler("/auth", authHandler);
+
+ httpserv.start(-1);
+
+ tests[0]();
+}
+
+function test_suspend_on_auth(suspendOnBeforeConnect, suspendOnModifyRequest) {
+ var chan = makeChan(URL + "/auth", URL);
+ requestObserver = new requestListenerObserver(
+ suspendOnBeforeConnect,
+ suspendOnModifyRequest
+ );
+ chan.notificationCallbacks = new Requestor();
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_suspend_on_before_connect() {
+ test_suspend_on_auth(true, false);
+}
+
+function test_suspend_on_modify_request() {
+ test_suspend_on_auth(false, true);
+}
+
+function test_suspend_all() {
+ test_suspend_on_auth(true, true);
+}
+
+// PATH HANDLERS
+
+// /auth
+function authHandler(metadata, response) {
+ // btoa("guest:guest"), but that function is not available here
+ var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ var body;
+ if (
+ metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "success";
+ } else {
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "failed";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_suspend_channel_on_examine.js b/netwerk/test/unit/test_suspend_channel_on_examine.js
new file mode 100644
index 0000000000..7667026a56
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_examine.js
@@ -0,0 +1,76 @@
+// This file tests async handling of a channel suspended in http-on-modify-request.
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var obs = Services.obs;
+
+var baseUrl;
+
+function responseHandler(metadata, response) {
+ var text = "testing";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Set-Cookie", "chewy", false);
+ response.bodyOutputStream.write(text, text.length);
+}
+
+function onExamineListener(callback) {
+ obs.addObserver(
+ {
+ observe(subject, topic, data) {
+ obs.removeObserver(this, "http-on-examine-response");
+ callback(subject.QueryInterface(Ci.nsIHttpChannel));
+ },
+ },
+ "http-on-examine-response"
+ );
+}
+
+function startChannelRequest(uri, flags, callback) {
+ var chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(new ChannelListener(callback, null, flags));
+}
+
+// We first make a request that we'll cancel asynchronously. The response will
+// still contain the set-cookie header. Then verify the cookie was not actually
+// retained.
+add_test(function testAsyncCancel() {
+ onExamineListener(chan => {
+ // Suspend the channel then yield to make this async.
+ chan.suspend();
+ Promise.resolve().then(() => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE, (request, data, context) => {
+ Assert.ok(!data, "no response");
+
+ Assert.equal(
+ Services.cookies.countCookiesFromHost("localhost"),
+ 0,
+ "no cookies set"
+ );
+
+ executeSoon(run_next_test);
+ });
+});
+
+function run_test() {
+ var httpServer = new HttpServer();
+ httpServer.registerPathHandler("/", responseHandler);
+ httpServer.start(-1);
+
+ baseUrl = `http://localhost:${httpServer.identity.primaryPort}`;
+
+ run_next_test();
+
+ registerCleanupFunction(function () {
+ httpServer.stop(() => {});
+ });
+}
diff --git a/netwerk/test/unit/test_suspend_channel_on_examine_merged_response.js b/netwerk/test/unit/test_suspend_channel_on_examine_merged_response.js
new file mode 100644
index 0000000000..7496796500
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_examine_merged_response.js
@@ -0,0 +1,208 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests async handling of a channel suspended in
+// notifying http-on-examine-merged-response observers.
+// Note that this test is developed based on test_bug482601.js.
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserv = null;
+var test_nr = 0;
+var buffer = "";
+var observerCalled = false;
+var channelResumed = false;
+
+var observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (
+ topic === "http-on-examine-merged-response" &&
+ subject instanceof Ci.nsIHttpChannel
+ ) {
+ var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+ executeSoon(() => {
+ Assert.equal(channelResumed, false);
+ channelResumed = true;
+ chan.resume();
+ });
+ Assert.equal(observerCalled, false);
+ observerCalled = true;
+ chan.suspend();
+ }
+ },
+};
+
+var listener = {
+ onStartRequest(request) {
+ buffer = "";
+ },
+
+ onDataAvailable(request, stream, offset, count) {
+ buffer = buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest(request, status) {
+ Assert.equal(status, Cr.NS_OK);
+ Assert.equal(buffer, "0123456789");
+ Assert.equal(channelResumed, true);
+ Assert.equal(observerCalled, true);
+ test_nr++;
+ do_timeout(0, do_test);
+ },
+};
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/path/partial", path_partial);
+ httpserv.registerPathHandler("/path/cached", path_cached);
+ httpserv.start(-1);
+
+ Services.obs.addObserver(observer, "http-on-examine-merged-response");
+
+ do_timeout(0, do_test);
+ do_test_pending();
+}
+
+function do_test() {
+ if (test_nr < tests.length) {
+ tests[test_nr]();
+ } else {
+ Services.obs.removeObserver(observer, "http-on-examine-merged-response");
+ httpserv.stop(do_test_finished);
+ }
+}
+
+var tests = [test_partial, test_cached];
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function storeCache(aCacheEntry, aResponseHeads, aContent) {
+ aCacheEntry.setMetaDataElement("request-method", "GET");
+ aCacheEntry.setMetaDataElement("response-head", aResponseHeads);
+ aCacheEntry.setMetaDataElement("charset", "ISO-8859-1");
+
+ var oStream = aCacheEntry.openOutputStream(0, aContent.length);
+ var written = oStream.write(aContent, aContent.length);
+ if (written != aContent.length) {
+ do_throw(
+ "oStream.write has not written all data!\n" +
+ " Expected: " +
+ written +
+ "\n" +
+ " Actual: " +
+ aContent.length +
+ "\n"
+ );
+ }
+ oStream.close();
+ aCacheEntry.close();
+}
+
+function test_partial() {
+ observerCalled = false;
+ channelResumed = false;
+ asyncOpenCacheEntry(
+ "http://localhost:" + httpserv.identity.primaryPort + "/path/partial",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ test_partial2
+ );
+}
+
+function test_partial2(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ storeCache(
+ entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123"
+ );
+
+ observerCalled = false;
+
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/path/partial"
+ );
+ chan.asyncOpen(listener);
+}
+
+function test_cached() {
+ observerCalled = false;
+ channelResumed = false;
+ asyncOpenCacheEntry(
+ "http://localhost:" + httpserv.identity.primaryPort + "/path/cached",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ test_cached2
+ );
+}
+
+function test_cached2(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ storeCache(
+ entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123456789"
+ );
+
+ observerCalled = false;
+
+ var chan = makeChan(
+ "http://localhost:" + httpserv.identity.primaryPort + "/path/cached"
+ );
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen(listener);
+}
+
+// PATHS
+
+// /path/partial
+function path_partial(metadata, response) {
+ Assert.ok(metadata.hasHeader("If-Range"));
+ Assert.equal(metadata.getHeader("If-Range"), "Thu, 1 Jan 2009 00:00:00 GMT");
+ Assert.ok(metadata.hasHeader("Range"));
+ Assert.equal(metadata.getHeader("Range"), "bytes=4-");
+
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "bytes 4-9/10", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", "Thu, 1 Jan 2009 00:00:00 GMT");
+
+ var body = "456789";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /path/cached
+function path_cached(metadata, response) {
+ Assert.ok(metadata.hasHeader("If-Modified-Since"));
+ Assert.equal(
+ metadata.getHeader("If-Modified-Since"),
+ "Thu, 1 Jan 2009 00:00:00 GMT"
+ );
+
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+}
diff --git a/netwerk/test/unit/test_suspend_channel_on_modified.js b/netwerk/test/unit/test_suspend_channel_on_modified.js
new file mode 100644
index 0000000000..397ffae881
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_modified.js
@@ -0,0 +1,177 @@
+// This file tests async handling of a channel suspended in http-on-modify-request.
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var obs = Services.obs;
+
+var ios = Services.io;
+
+// baseUrl is always the initial connection attempt and is handled by
+// failResponseHandler since every test expects that request will either be
+// redirected or cancelled.
+var baseUrl;
+
+function failResponseHandler(metadata, response) {
+ var text = "failure response";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(text, text.length);
+ Assert.ok(false, "Received request when we shouldn't.");
+}
+
+function successResponseHandler(metadata, response) {
+ var text = "success response";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(text, text.length);
+ Assert.ok(true, "Received expected request.");
+}
+
+function onModifyListener(callback) {
+ obs.addObserver(
+ {
+ observe(subject, topic, data) {
+ obs.removeObserver(this, "http-on-modify-request");
+ callback(subject.QueryInterface(Ci.nsIHttpChannel));
+ },
+ },
+ "http-on-modify-request"
+ );
+}
+
+function startChannelRequest(uri, flags, expectedResponse = null) {
+ var chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.asyncOpen(
+ new ChannelListener(
+ (request, data, context) => {
+ if (expectedResponse) {
+ Assert.equal(data, expectedResponse);
+ } else {
+ Assert.ok(!data, "no response");
+ }
+ executeSoon(run_next_test);
+ },
+ null,
+ flags
+ )
+ );
+}
+
+add_test(function testSimpleRedirect() {
+ onModifyListener(chan => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/success`));
+ });
+ startChannelRequest(baseUrl, undefined, "success response");
+});
+
+add_test(function testSimpleCancel() {
+ onModifyListener(chan => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+add_test(function testSimpleCancelRedirect() {
+ onModifyListener(chan => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/fail`));
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+// Test a request that will get redirected asynchronously. baseUrl should
+// not be requested, we should receive the request for the redirectedUrl.
+add_test(function testAsyncRedirect() {
+ onModifyListener(chan => {
+ // Suspend the channel then yield to make this async.
+ chan.suspend();
+ Promise.resolve().then(() => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/success`));
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, undefined, "success response");
+});
+
+add_test(function testSyncRedirect() {
+ onModifyListener(chan => {
+ chan.suspend();
+ chan.redirectTo(ios.newURI(`${baseUrl}/success`));
+ Promise.resolve().then(() => {
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, undefined, "success response");
+});
+
+add_test(function testAsyncCancel() {
+ onModifyListener(chan => {
+ // Suspend the channel then yield to make this async.
+ chan.suspend();
+ Promise.resolve().then(() => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+add_test(function testSyncCancel() {
+ onModifyListener(chan => {
+ chan.suspend();
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ Promise.resolve().then(() => {
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+// Test request that will get redirected and cancelled asynchronously,
+// ensure no connection is made.
+add_test(function testAsyncCancelRedirect() {
+ onModifyListener(chan => {
+ // Suspend the channel then yield to make this async.
+ chan.suspend();
+ Promise.resolve().then(() => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ chan.redirectTo(ios.newURI(`${baseUrl}/fail`));
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+// Test a request that will get cancelled synchronously, ensure async redirect
+// is not made.
+add_test(function testSyncCancelRedirect() {
+ onModifyListener(chan => {
+ chan.suspend();
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ Promise.resolve().then(() => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/fail`));
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+function run_test() {
+ var httpServer = new HttpServer();
+ httpServer.registerPathHandler("/", failResponseHandler);
+ httpServer.registerPathHandler("/fail", failResponseHandler);
+ httpServer.registerPathHandler("/success", successResponseHandler);
+ httpServer.start(-1);
+
+ baseUrl = `http://localhost:${httpServer.identity.primaryPort}`;
+
+ run_next_test();
+
+ registerCleanupFunction(function () {
+ httpServer.stop(() => {});
+ });
+}
diff --git a/netwerk/test/unit/test_synthesized_response.js b/netwerk/test/unit/test_synthesized_response.js
new file mode 100644
index 0000000000..a598e649c4
--- /dev/null
+++ b/netwerk/test/unit/test_synthesized_response.js
@@ -0,0 +1,288 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function isParentProcess() {
+ let appInfo = Cc["@mozilla.org/xre/app-info;1"];
+ return (
+ !appInfo ||
+ Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+if (isParentProcess()) {
+ // ensure the cache service is prepped when running the test
+ // We only do this in the main process, as the cache storage service leaks
+ // when instantiated in the content process.
+ Services.cache2;
+}
+
+var gotOnProgress;
+var gotOnStatus;
+
+function make_channel(url, body, cb) {
+ gotOnProgress = false;
+ gotOnStatus = false;
+ var chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.notificationCallbacks = {
+ numChecks: 0,
+ QueryInterface: ChromeUtils.generateQI([
+ "nsINetworkInterceptController",
+ "nsIInterfaceRequestor",
+ "nsIProgressEventSink",
+ ]),
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+ onProgress(request, progress, progressMax) {
+ gotOnProgress = true;
+ },
+ onStatus(request, status, statusArg) {
+ gotOnStatus = true;
+ },
+ shouldPrepareForIntercept() {
+ Assert.equal(this.numChecks, 0);
+ this.numChecks++;
+ return true;
+ },
+ channelIntercepted(channel) {
+ channel.QueryInterface(Ci.nsIInterceptedChannel);
+ if (body) {
+ var synthesized = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ synthesized.data = body;
+
+ channel.startSynthesizedResponse(synthesized, null, null, "", false);
+ channel.finishSynthesizedResponse();
+ }
+ if (cb) {
+ cb(channel);
+ }
+ return {
+ dispatch() {},
+ };
+ },
+ };
+ return chan;
+}
+
+const REMOTE_BODY = "http handler body";
+const NON_REMOTE_BODY = "synthesized body";
+const NON_REMOTE_BODY_2 = "synthesized body #2";
+
+function bodyHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.write(REMOTE_BODY);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/body", bodyHandler);
+ httpServer.start(-1);
+
+ run_next_test();
+}
+
+function handle_synthesized_response(request, buffer) {
+ Assert.equal(buffer, NON_REMOTE_BODY);
+ Assert.ok(gotOnStatus);
+ Assert.ok(gotOnProgress);
+ run_next_test();
+}
+
+function handle_synthesized_response_2(request, buffer) {
+ Assert.equal(buffer, NON_REMOTE_BODY_2);
+ Assert.ok(gotOnStatus);
+ Assert.ok(gotOnProgress);
+ run_next_test();
+}
+
+function handle_remote_response(request, buffer) {
+ Assert.equal(buffer, REMOTE_BODY);
+ Assert.ok(gotOnStatus);
+ Assert.ok(gotOnProgress);
+ run_next_test();
+}
+
+// hit the network instead of synthesizing
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (channel) {
+ channel.resetInterception(false);
+ });
+ chan.asyncOpen(new ChannelListener(handle_remote_response, null));
+});
+
+// synthesize a response
+add_test(function () {
+ var chan = make_channel(URL + "/body", NON_REMOTE_BODY);
+ chan.asyncOpen(
+ new ChannelListener(handle_synthesized_response, null, CL_ALLOW_UNKNOWN_CL)
+ );
+});
+
+// hit the network instead of synthesizing, to test that no previous synthesized
+// cache entry is used.
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (channel) {
+ channel.resetInterception(false);
+ });
+ chan.asyncOpen(new ChannelListener(handle_remote_response, null));
+});
+
+// synthesize a different response to ensure no previous response is cached
+add_test(function () {
+ var chan = make_channel(URL + "/body", NON_REMOTE_BODY_2);
+ chan.asyncOpen(
+ new ChannelListener(
+ handle_synthesized_response_2,
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+});
+
+// ensure that the channel waits for a decision and synthesizes headers correctly
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (channel) {
+ do_timeout(100, function () {
+ var synthesized = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+ channel.synthesizeHeader("Content-Length", NON_REMOTE_BODY.length);
+ channel.startSynthesizedResponse(synthesized, null, null, "", false);
+ channel.finishSynthesizedResponse();
+ });
+ });
+ chan.asyncOpen(new ChannelListener(handle_synthesized_response, null));
+});
+
+// ensure that the channel waits for a decision
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (channel) {
+ do_timeout(100, function () {
+ channel.resetInterception(false);
+ });
+ });
+ chan.asyncOpen(new ChannelListener(handle_remote_response, null));
+});
+
+// ensure that the intercepted channel supports suspend/resume
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (intercepted) {
+ var synthesized = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+
+ // set the content-type to ensure that the stream converter doesn't hold up notifications
+ // and cause the test to fail
+ intercepted.synthesizeHeader("Content-Type", "text/plain");
+ intercepted.startSynthesizedResponse(synthesized, null, null, "", false);
+ intercepted.finishSynthesizedResponse();
+ });
+ chan.asyncOpen(
+ new ChannelListener(
+ handle_synthesized_response,
+ null,
+ CL_ALLOW_UNKNOWN_CL | CL_SUSPEND | CL_EXPECT_3S_DELAY
+ )
+ );
+});
+
+// ensure that the intercepted channel can be cancelled
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (intercepted) {
+ intercepted.cancelInterception(Cr.NS_BINDING_ABORTED);
+ });
+ chan.asyncOpen(new ChannelListener(run_next_test, null, CL_EXPECT_FAILURE));
+});
+
+// ensure that the channel can't be cancelled via nsIInterceptedChannel after making a decision
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (channel) {
+ channel.resetInterception(false);
+ do_timeout(0, function () {
+ var gotexception = false;
+ try {
+ channel.cancelInterception();
+ } catch (x) {
+ gotexception = true;
+ }
+ Assert.ok(gotexception);
+ });
+ });
+ chan.asyncOpen(new ChannelListener(handle_remote_response, null));
+});
+
+// ensure that the intercepted channel can be canceled during the response
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (intercepted) {
+ var synthesized = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+
+ let channel = intercepted.channel;
+ intercepted.startSynthesizedResponse(synthesized, null, null, "", false);
+ intercepted.finishSynthesizedResponse();
+ channel.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ chan.asyncOpen(
+ new ChannelListener(
+ run_next_test,
+ null,
+ CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL
+ )
+ );
+});
+
+// ensure that the intercepted channel can be canceled before the response
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (intercepted) {
+ var synthesized = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+
+ intercepted.channel.cancel(Cr.NS_BINDING_ABORTED);
+
+ // This should not throw, but result in the channel firing callbacks
+ // with an error status.
+ intercepted.startSynthesizedResponse(synthesized, null, null, "", false);
+ intercepted.finishSynthesizedResponse();
+ });
+ chan.asyncOpen(
+ new ChannelListener(
+ run_next_test,
+ null,
+ CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL
+ )
+ );
+});
+
+// Ensure that nsIInterceptedChannel.channelIntercepted() can return an error.
+// In this case we should automatically ResetInterception() and complete the
+// network request.
+add_test(function () {
+ var chan = make_channel(URL + "/body", null, function (channel) {
+ throw new Error("boom");
+ });
+ chan.asyncOpen(new ChannelListener(handle_remote_response, null));
+});
+
+add_test(function () {
+ httpServer.stop(run_next_test);
+});
diff --git a/netwerk/test/unit/test_throttlechannel.js b/netwerk/test/unit/test_throttlechannel.js
new file mode 100644
index 0000000000..5a0cfd1151
--- /dev/null
+++ b/netwerk/test/unit/test_throttlechannel.js
@@ -0,0 +1,48 @@
+// Test nsIThrottledInputChannel interface.
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function test_handler(metadata, response) {
+ const originalBody = "the response";
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function run_test() {
+ let httpserver = new HttpServer();
+ httpserver.start(-1);
+ const PORT = httpserver.identity.primaryPort;
+
+ httpserver.registerPathHandler("/testdir", test_handler);
+
+ let channel = make_channel("http://localhost:" + PORT + "/testdir");
+
+ let tq = Cc["@mozilla.org/network/throttlequeue;1"].createInstance(
+ Ci.nsIInputChannelThrottleQueue
+ );
+ tq.init(1000, 1000);
+
+ let tic = channel.QueryInterface(Ci.nsIThrottledInputChannel);
+ tic.throttleQueue = tq;
+
+ channel.asyncOpen(
+ new ChannelListener(() => {
+ ok(tq.bytesProcessed() > 0, "throttled queue processed some bytes");
+
+ httpserver.stop(do_test_finished);
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_throttlequeue.js b/netwerk/test/unit/test_throttlequeue.js
new file mode 100644
index 0000000000..4fa1eaa0e2
--- /dev/null
+++ b/netwerk/test/unit/test_throttlequeue.js
@@ -0,0 +1,25 @@
+// Test ThrottleQueue initialization.
+"use strict";
+
+function init(tq, mean, max) {
+ let threw = false;
+ try {
+ tq.init(mean, max);
+ } catch (e) {
+ threw = true;
+ }
+ return !threw;
+}
+
+function run_test() {
+ let tq = Cc["@mozilla.org/network/throttlequeue;1"].createInstance(
+ Ci.nsIInputChannelThrottleQueue
+ );
+
+ ok(!init(tq, 0, 50), "mean bytes cannot be 0");
+ ok(!init(tq, 50, 0), "max bytes cannot be 0");
+ ok(!init(tq, 0, 0), "mean and max bytes cannot be 0");
+ ok(!init(tq, 70, 20), "max cannot be less than mean");
+
+ ok(init(tq, 2, 2), "valid initialization");
+}
diff --git a/netwerk/test/unit/test_throttling.js b/netwerk/test/unit/test_throttling.js
new file mode 100644
index 0000000000..7627684dce
--- /dev/null
+++ b/netwerk/test/unit/test_throttling.js
@@ -0,0 +1,66 @@
+// Test nsIThrottledInputChannel interface.
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+function test_handler(metadata, response) {
+ const originalBody = "the response";
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function run_test() {
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/testdir", test_handler);
+ httpserver.start(-1);
+
+ const PORT = httpserver.identity.primaryPort;
+ const size = 4096;
+
+ let sstream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ sstream.data = "x".repeat(size);
+
+ let mime = Cc["@mozilla.org/network/mime-input-stream;1"].createInstance(
+ Ci.nsIMIMEInputStream
+ );
+ mime.addHeader("Content-Type", "multipart/form-data; boundary=zzzzz");
+ mime.setData(sstream);
+
+ let tq = Cc["@mozilla.org/network/throttlequeue;1"].createInstance(
+ Ci.nsIInputChannelThrottleQueue
+ );
+ // Make sure the request takes more than one read.
+ tq.init(100 + size / 2, 100 + size / 2);
+
+ let channel = make_channel("http://localhost:" + PORT + "/testdir");
+ channel
+ .QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(mime, "", mime.available());
+ channel.requestMethod = "POST";
+
+ let tic = channel.QueryInterface(Ci.nsIThrottledInputChannel);
+ tic.throttleQueue = tq;
+
+ let startTime = Date.now();
+ channel.asyncOpen(
+ new ChannelListener(() => {
+ ok(Date.now() - startTime > 1000, "request took more than one second");
+
+ httpserver.stop(do_test_finished);
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_tldservice_nextsubdomain.js b/netwerk/test/unit/test_tldservice_nextsubdomain.js
new file mode 100644
index 0000000000..5a078aa809
--- /dev/null
+++ b/netwerk/test/unit/test_tldservice_nextsubdomain.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function run_test() {
+ var tests = [
+ { data: "bar.foo.co.uk", result: "foo.co.uk" },
+ { data: "foo.bar.foo.co.uk", result: "bar.foo.co.uk" },
+ { data: "foo.co.uk", throw: true },
+ { data: "co.uk", throw: true },
+ { data: ".co.uk", throw: true },
+ { data: "com", throw: true },
+ { data: "tûlîp.foo.fr", result: "foo.fr" },
+ { data: "tûlîp.fôû.fr", result: "xn--f-xgav.fr" },
+ { data: "file://foo/bar", throw: true },
+ ];
+
+ tests.forEach(function (test) {
+ try {
+ var r = Services.eTLD.getNextSubDomain(test.data);
+ Assert.equal(r, test.result);
+ } catch (e) {
+ Assert.ok(test.throw);
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_tls13_disabled.js b/netwerk/test/unit/test_tls13_disabled.js
new file mode 100644
index 0000000000..3bcb6333aa
--- /dev/null
+++ b/netwerk/test/unit/test_tls13_disabled.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("security.tls.version.max");
+ http3_clear_prefs();
+});
+
+let httpsUri;
+
+add_task(async function setup() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ httpsUri = "https://foo.example.com:" + h2Port + "/";
+
+ await http3_setup_tests("h3");
+});
+
+let Listener = function () {};
+
+Listener.prototype = {
+ resumed: false,
+
+ onStartRequest: function testOnStartRequest(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ if (this.expect_http3) {
+ Assert.equal(httpVersion, "h3");
+ } else {
+ Assert.notEqual(httpVersion, "h3");
+ }
+
+ this.finish();
+ },
+};
+
+function chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+async function test_http3_used(expect_http3) {
+ let listener = new Listener();
+ listener.expect_http3 = expect_http3;
+ let chan = makeChan(httpsUri);
+ await chanPromise(chan, listener);
+}
+
+add_task(async function test_tls13_pref() {
+ await test_http3_used(true);
+ // Try one more time.
+ await test_http3_used(true);
+
+ // Disable TLS1.3
+ Services.prefs.setIntPref("security.tls.version.max", 3);
+ await test_http3_used(false);
+ // Try one more time.
+ await test_http3_used(false);
+
+ // Enable TLS1.3
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ await test_http3_used(true);
+ // Try one more time.
+ await test_http3_used(true);
+});
diff --git a/netwerk/test/unit/test_tls_flags.js b/netwerk/test/unit/test_tls_flags.js
new file mode 100644
index 0000000000..876cd0ccea
--- /dev/null
+++ b/netwerk/test/unit/test_tls_flags.js
@@ -0,0 +1,248 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+
+// a fork of test_be_conservative
+
+// Tests that nsIHttpChannelInternal.tlsFlags can be used to set the
+// client max version level. Flags can also be used to set the
+// level of intolerance rollback and to test out an experimental 1.3
+// hello, though they are not tested here.
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+class InputStreamCallback {
+ constructor(output) {
+ this.output = output;
+ this.stopped = false;
+ }
+
+ onInputStreamReady(stream) {
+ info("input stream ready");
+ if (this.stopped) {
+ info("input stream callback stopped - bailing");
+ return;
+ }
+ let available = 0;
+ try {
+ available = stream.available();
+ } catch (e) {
+ // onInputStreamReady may fire when the stream has been closed.
+ equal(
+ e.result,
+ Cr.NS_BASE_STREAM_CLOSED,
+ "error should be NS_BASE_STREAM_CLOSED"
+ );
+ }
+ if (available > 0) {
+ let request = NetUtil.readInputStreamToString(stream, available, {
+ charset: "utf8",
+ });
+ ok(
+ request.startsWith("GET / HTTP/1.1\r\n"),
+ "Should get a simple GET / HTTP/1.1 request"
+ );
+ let response =
+ "HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 2\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\nOK";
+ let written = this.output.write(response, response.length);
+ equal(
+ written,
+ response.length,
+ "should have been able to write entire response"
+ );
+ }
+ this.output.close();
+ info("done with input stream ready");
+ }
+
+ stop() {
+ this.stopped = true;
+ this.output.close();
+ }
+}
+
+class TLSServerSecurityObserver {
+ constructor(input, output, expectedVersion) {
+ this.input = input;
+ this.output = output;
+ this.expectedVersion = expectedVersion;
+ this.callbacks = [];
+ this.stopped = false;
+ }
+
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+ info(`TLS version used: ${status.tlsVersionUsed}`);
+ info(this.expectedVersion);
+ equal(
+ status.tlsVersionUsed,
+ this.expectedVersion,
+ "expected version check"
+ );
+ if (this.stopped) {
+ info("handshake done callback stopped - bailing");
+ return;
+ }
+
+ let callback = new InputStreamCallback(this.output);
+ this.callbacks.push(callback);
+ this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
+ }
+
+ stop() {
+ this.stopped = true;
+ this.input.close();
+ this.output.close();
+ this.callbacks.forEach(callback => {
+ callback.stop();
+ });
+ }
+}
+
+function startServer(
+ cert,
+ minServerVersion,
+ maxServerVersion,
+ expectedVersion
+) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+ tlsServer.setVersionRange(minServerVersion, maxServerVersion);
+ tlsServer.setSessionTickets(false);
+
+ let listener = {
+ securityObservers: [],
+
+ onSocketAccepted(socket, transport) {
+ info("accepted TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ let securityObserver = new TLSServerSecurityObserver(
+ input,
+ output,
+ expectedVersion
+ );
+ this.securityObservers.push(securityObserver);
+ connectionInfo.setSecurityObserver(securityObserver);
+ },
+
+ // For some reason we get input stream callback events after we've stopped
+ // listening, so this ensures we just drop those events.
+ onStopListening() {
+ info("onStopListening");
+ this.securityObservers.forEach(observer => {
+ observer.stop();
+ });
+ },
+ };
+ tlsServer.asyncListen(listener);
+ return tlsServer;
+}
+
+const hostname = "example.com";
+
+function storeCertOverride(port, cert) {
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true);
+}
+
+function startClient(port, tlsFlags, expectSuccess) {
+ let req = new XMLHttpRequest();
+ req.open("GET", `https://${hostname}:${port}`);
+ let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.tlsFlags = tlsFlags;
+ return new Promise((resolve, reject) => {
+ req.onload = () => {
+ ok(
+ expectSuccess,
+ `should ${expectSuccess ? "" : "not "}have gotten load event`
+ );
+ equal(req.responseText, "OK", "response text should be 'OK'");
+ resolve();
+ };
+ req.onerror = () => {
+ ok(
+ !expectSuccess,
+ `should ${!expectSuccess ? "" : "not "}have gotten an error`
+ );
+ resolve();
+ };
+
+ req.send();
+ });
+}
+
+add_task(async function () {
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ Services.prefs.setCharPref("network.dns.localDomains", hostname);
+ let cert = getTestServerCertificate();
+
+ // server that accepts 1.1->1.3 and a client max 1.3. expect 1.3
+ info("TEST 1");
+ let server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_1,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(server.port, 4, true /*should succeed*/);
+ server.close();
+
+ // server that accepts 1.1->1.3 and a client max 1.1. expect 1.1
+ info("TEST 2");
+ server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_1,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_1
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(server.port, 2, true);
+ server.close();
+
+ // server that accepts 1.2->1.2 and a client max 1.3. expect 1.2
+ info("TEST 3");
+ server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_2
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(server.port, 4, true);
+ server.close();
+
+ // server that accepts 1.2->1.2 and a client max 1.1. expect fail
+ info("TEST 4");
+ server = startServer(
+ cert,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ 0
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(server.port, 2, false);
+
+ server.close();
+});
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
diff --git a/netwerk/test/unit/test_tls_flags_separate_connections.js b/netwerk/test/unit/test_tls_flags_separate_connections.js
new file mode 100644
index 0000000000..a67c15d8a6
--- /dev/null
+++ b/netwerk/test/unit/test_tls_flags_separate_connections.js
@@ -0,0 +1,117 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+// This unit test ensures connections with different tlsFlags have their own
+// connection pool. We verify this behavior by opening channels with different
+// tlsFlags, and their connection info's hash keys should be different.
+
+// In the first round of this test, we record the hash key for each connection.
+// In the second round, we check if each connection's hash key is consistent
+// and different from other connection's hash key.
+
+let httpserv = null;
+let gSecondRoundStarted = false;
+
+let randomFlagValues = [
+ 0x00000000,
+
+ 0xffffffff,
+
+ 0x12345678, 0x12345678,
+
+ 0x11111111, 0x22222222,
+
+ 0xaaaaaaaa, 0x77777777,
+
+ 0xbbbbbbbb, 0xcccccccc,
+];
+
+function handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ let body = "0123456789";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function makeChan(url, tlsFlags) {
+ let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ chan.tlsFlags = tlsFlags;
+
+ return chan;
+}
+
+let previousHashKeys = {};
+
+function Listener(tlsFlags) {
+ this.tlsFlags = tlsFlags;
+}
+
+let gTestsRun = 0;
+Listener.prototype = {
+ onStartRequest(request) {
+ request
+ .QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ Assert.equal(request.tlsFlags, this.tlsFlags);
+
+ let hashKey = request.connectionInfoHashKey;
+ if (gSecondRoundStarted) {
+ // Compare the hash keys with the previous set ones.
+ // Hash keys should match if and only if their tlsFlags are the same.
+ for (let tlsFlags of randomFlagValues) {
+ if (tlsFlags == this.tlsFlags) {
+ Assert.equal(hashKey, previousHashKeys[tlsFlags]);
+ } else {
+ Assert.notEqual(hashKey, previousHashKeys[tlsFlags]);
+ }
+ }
+ } else {
+ // Set the hash keys in the first round.
+ previousHashKeys[this.tlsFlags] = hashKey;
+ }
+ },
+ onDataAvailable(request, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+ onStopRequest() {
+ gTestsRun++;
+ if (gTestsRun == randomFlagValues.length) {
+ gTestsRun = 0;
+ if (gSecondRoundStarted) {
+ // The second round finishes.
+ httpserv.stop(do_test_finished);
+ } else {
+ // The first round finishes. Do the second round.
+ gSecondRoundStarted = true;
+ doTest();
+ }
+ }
+ },
+};
+
+function doTest() {
+ for (let tlsFlags of randomFlagValues) {
+ let chan = makeChan(URL, tlsFlags);
+ let listener = new Listener(tlsFlags);
+ chan.asyncOpen(listener);
+ }
+}
+
+function run_test() {
+ do_test_pending();
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", handler);
+ httpserv.start(-1);
+
+ doTest();
+}
diff --git a/netwerk/test/unit/test_tls_server.js b/netwerk/test/unit/test_tls_server.js
new file mode 100644
index 0000000000..5183f21fd7
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server.js
@@ -0,0 +1,314 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Need profile dir to store the key / cert
+do_get_profile();
+// Ensure PSM is initialized
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const socketTransportService = Cc[
+ "@mozilla.org/network/socket-transport-service;1"
+].getService(Ci.nsISocketTransportService);
+
+const prefs = Services.prefs;
+
+function areCertsEqual(certA, certB) {
+ let derA = certA.getRawDER();
+ let derB = certB.getRawDER();
+ if (derA.length != derB.length) {
+ return false;
+ }
+ for (let i = 0; i < derA.length; i++) {
+ if (derA[i] != derB[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function startServer(
+ cert,
+ expectingPeerCert,
+ clientCertificateConfig,
+ expectedVersion,
+ expectedVersionStr
+) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+
+ let input, output;
+
+ let listener = {
+ onSocketAccepted(socket, transport) {
+ info("Accept TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ connectionInfo.setSecurityObserver(listener);
+ input = transport.openInputStream(0, 0, 0);
+ output = transport.openOutputStream(0, 0, 0);
+ },
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+ if (expectingPeerCert) {
+ ok(!!status.peerCert, "Has peer cert");
+ ok(
+ areCertsEqual(status.peerCert, cert),
+ "Peer cert matches expected cert"
+ );
+ } else {
+ ok(!status.peerCert, "No peer cert (as expected)");
+ }
+
+ equal(
+ status.tlsVersionUsed,
+ expectedVersion,
+ "Using " + expectedVersionStr
+ );
+ let expectedCipher;
+ if (expectedVersion >= 772) {
+ expectedCipher = "TLS_AES_128_GCM_SHA256";
+ } else {
+ expectedCipher = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ }
+ equal(status.cipherName, expectedCipher, "Using expected cipher");
+ equal(status.keyLength, 128, "Using 128-bit key");
+ equal(status.macLength, 128, "Using 128-bit MAC");
+
+ input.asyncWait(
+ {
+ onInputStreamReady(input1) {
+ NetUtil.asyncCopy(input1, output);
+ },
+ },
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ },
+ onStopListening() {
+ info("onStopListening");
+ input.close();
+ output.close();
+ },
+ };
+
+ tlsServer.setSessionTickets(false);
+ tlsServer.setRequestClientCertificate(clientCertificateConfig);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer;
+}
+
+function storeCertOverride(port, cert) {
+ certOverrideService.rememberValidityOverride(
+ "127.0.0.1",
+ port,
+ {},
+ cert,
+ true
+ );
+}
+
+function startClient(port, sendClientCert, expectingAlert, tlsVersion) {
+ gClientAuthDialogService.selectCertificate = sendClientCert;
+ let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
+ let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
+ let SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT = SSL_ERROR_BASE + 181;
+ let transport = socketTransportService.createTransport(
+ ["ssl"],
+ "127.0.0.1",
+ port,
+ null,
+ null
+ );
+ let input;
+ let output;
+
+ let inputDeferred = Promise.withResolvers();
+ let outputDeferred = Promise.withResolvers();
+
+ let handler = {
+ onTransportStatus(transport1, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady(input1) {
+ try {
+ let data = NetUtil.readInputStreamToString(input1, input1.available());
+ equal(data, "HELLO", "Echoed data received");
+ input1.close();
+ output.close();
+ ok(!expectingAlert, "No cert alert expected");
+ inputDeferred.resolve();
+ } catch (e) {
+ let errorCode = -1 * (e.result & 0xffff);
+ if (expectingAlert) {
+ if (
+ tlsVersion == Ci.nsITLSClientStatus.TLS_VERSION_1_2 &&
+ errorCode == SSL_ERROR_BAD_CERT_ALERT
+ ) {
+ info("Got bad cert alert as expected for tls 1.2");
+ input1.close();
+ output.close();
+ inputDeferred.resolve();
+ return;
+ }
+ if (
+ tlsVersion == Ci.nsITLSClientStatus.TLS_VERSION_1_3 &&
+ errorCode == SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT
+ ) {
+ info("Got cert required alert as expected for tls 1.3");
+ input1.close();
+ output.close();
+ inputDeferred.resolve();
+ return;
+ }
+ }
+ inputDeferred.reject(e);
+ }
+ },
+
+ onOutputStreamReady(output1) {
+ try {
+ output1.write("HELLO", 5);
+ info("Output to server written");
+ outputDeferred.resolve();
+ input = transport.openInputStream(0, 0, 0);
+ input.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ } catch (e) {
+ let errorCode = -1 * (e.result & 0xffff);
+ if (errorCode == SSL_ERROR_BAD_CERT_ALERT) {
+ info("Server doesn't like client cert");
+ }
+ outputDeferred.reject(e);
+ }
+ },
+ };
+
+ transport.setEventSink(handler, Services.tm.currentThread);
+ output = transport.openOutputStream(0, 0, 0);
+
+ return Promise.all([inputDeferred.promise, outputDeferred.promise]);
+}
+
+// Replace the UI dialog that prompts the user to pick a client certificate.
+const gClientAuthDialogService = {
+ _selectCertificate: false,
+
+ set selectCertificate(value) {
+ this._selectCertificate = value;
+ },
+
+ chooseCertificate(hostname, certArray, loadContext, callback) {
+ if (this._selectCertificate) {
+ callback.certificateChosen(certArray[0], false);
+ } else {
+ callback.certificateChose(null, false);
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogService]),
+};
+
+const ClientAuthDialogServiceContractID =
+ "@mozilla.org/security/ClientAuthDialogService;1";
+MockRegistrar.register(
+ ClientAuthDialogServiceContractID,
+ gClientAuthDialogService
+);
+
+const tests = [
+ {
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS,
+ sendClientCert: true,
+ expectingAlert: false,
+ },
+ {
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS,
+ sendClientCert: false,
+ expectingAlert: true,
+ },
+ {
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
+ sendClientCert: true,
+ expectingAlert: false,
+ },
+ {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
+ sendClientCert: false,
+ expectingAlert: false,
+ },
+ {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
+ sendClientCert: true,
+ expectingAlert: false,
+ },
+ {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
+ sendClientCert: false,
+ expectingAlert: false,
+ },
+];
+
+const versions = [
+ {
+ prefValue: 3,
+ version: Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ versionStr: "TLS 1.2",
+ },
+ {
+ prefValue: 4,
+ version: Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+ versionStr: "TLS 1.3",
+ },
+];
+
+add_task(async function () {
+ let cert = getTestServerCertificate();
+ ok(!!cert, "Got self-signed cert");
+ for (let v of versions) {
+ prefs.setIntPref("security.tls.version.max", v.prefValue);
+ for (let t of tests) {
+ let server = startServer(
+ cert,
+ t.expectingPeerCert,
+ t.clientCertificateConfig,
+ v.version,
+ v.versionStr
+ );
+ storeCertOverride(server.port, cert);
+ await startClient(
+ server.port,
+ t.sendClientCert,
+ t.expectingAlert,
+ v.version
+ );
+ server.close();
+ }
+ }
+});
+
+registerCleanupFunction(function () {
+ prefs.clearUserPref("security.tls.version.max");
+});
diff --git a/netwerk/test/unit/test_tls_server_multiple_clients.js b/netwerk/test/unit/test_tls_server_multiple_clients.js
new file mode 100644
index 0000000000..fdafd9744d
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server_multiple_clients.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Need profile dir to store the key / cert
+do_get_profile();
+// Ensure PSM is initialized
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const socketTransportService = Cc[
+ "@mozilla.org/network/socket-transport-service;1"
+].getService(Ci.nsISocketTransportService);
+
+function startServer(cert) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+
+ let input, output;
+
+ let listener = {
+ onSocketAccepted(socket, transport) {
+ info("Accept TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ connectionInfo.setSecurityObserver(listener);
+ input = transport.openInputStream(0, 0, 0);
+ output = transport.openOutputStream(0, 0, 0);
+ },
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+
+ input.asyncWait(
+ {
+ onInputStreamReady(input1) {
+ NetUtil.asyncCopy(input1, output);
+ },
+ },
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ },
+ onStopListening() {},
+ };
+
+ tlsServer.setSessionTickets(false);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer.port;
+}
+
+function storeCertOverride(port, cert) {
+ certOverrideService.rememberValidityOverride(
+ "127.0.0.1",
+ port,
+ {},
+ cert,
+ true
+ );
+}
+
+function startClient(port) {
+ let transport = socketTransportService.createTransport(
+ ["ssl"],
+ "127.0.0.1",
+ port,
+ null,
+ null
+ );
+ let input;
+ let output;
+
+ let inputDeferred = Promise.withResolvers();
+ let outputDeferred = Promise.withResolvers();
+
+ let handler = {
+ onTransportStatus(transport1, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady(input1) {
+ try {
+ let data = NetUtil.readInputStreamToString(input1, input1.available());
+ equal(data, "HELLO", "Echoed data received");
+ input1.close();
+ output.close();
+ inputDeferred.resolve();
+ } catch (e) {
+ inputDeferred.reject(e);
+ }
+ },
+
+ onOutputStreamReady(output1) {
+ try {
+ output1.write("HELLO", 5);
+ info("Output to server written");
+ outputDeferred.resolve();
+ input = transport.openInputStream(0, 0, 0);
+ input.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ } catch (e) {
+ outputDeferred.reject(e);
+ }
+ },
+ };
+
+ transport.setEventSink(handler, Services.tm.currentThread);
+ output = transport.openOutputStream(0, 0, 0);
+
+ return Promise.all([inputDeferred.promise, outputDeferred.promise]);
+}
+
+add_task(async function () {
+ let cert = getTestServerCertificate();
+ ok(!!cert, "Got self-signed cert");
+ let port = startServer(cert);
+ storeCertOverride(port, cert);
+ await startClient(port);
+ await startClient(port);
+});
diff --git a/netwerk/test/unit/test_traceable_channel.js b/netwerk/test/unit/test_traceable_channel.js
new file mode 100644
index 0000000000..3390682bcf
--- /dev/null
+++ b/netwerk/test/unit/test_traceable_channel.js
@@ -0,0 +1,145 @@
+"use strict";
+
+// Test nsITraceableChannel interface.
+// Replace original listener with TracingListener that modifies body of HTTP
+// response. Make sure that body received by original channel's listener
+// is correctly modified.
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+var pipe = null;
+var streamSink = null;
+
+var originalBody = "original http response body";
+var gotOnStartRequest = false;
+
+function TracingListener() {}
+
+TracingListener.prototype = {
+ onStartRequest(request) {
+ dump("*** tracing listener onStartRequest\n");
+
+ gotOnStartRequest = true;
+
+ request.QueryInterface(Ci.nsIHttpChannelInternal);
+
+ // local/remote addresses broken in e10s: disable for now
+ Assert.equal(request.localAddress, "127.0.0.1");
+ Assert.equal(request.localPort > 0, true);
+ Assert.notEqual(request.localPort, PORT);
+ Assert.equal(request.remoteAddress, "127.0.0.1");
+ Assert.equal(request.remotePort, PORT);
+
+ // Make sure listener can't be replaced after OnStartRequest was called.
+ request.QueryInterface(Ci.nsITraceableChannel);
+ try {
+ var newListener = new TracingListener();
+ newListener.listener = request.setNewListener(newListener);
+ } catch (e) {
+ dump("TracingListener.onStartRequest swallowing exception: " + e + "\n");
+ return; // OK
+ }
+ do_throw("replaced channel's listener during onStartRequest.");
+ },
+
+ onStopRequest(request, statusCode) {
+ dump("*** tracing listener onStopRequest\n");
+
+ Assert.equal(gotOnStartRequest, true);
+
+ try {
+ var sin = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+
+ streamSink.close();
+ var input = pipe.inputStream;
+ sin.init(input);
+ Assert.equal(sin.available(), originalBody.length);
+
+ var result = sin.read(originalBody.length);
+ Assert.equal(result, originalBody);
+
+ input.close();
+ } catch (e) {
+ dump("TracingListener.onStopRequest swallowing exception: " + e + "\n");
+ } finally {
+ httpserver.stop(do_test_finished);
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIRequestObserver"]),
+
+ listener: null,
+};
+
+function HttpResponseExaminer() {}
+
+HttpResponseExaminer.prototype = {
+ register() {
+ Services.obs.addObserver(this, "http-on-examine-response", true);
+ dump("Did HttpResponseExaminer.register\n");
+ },
+
+ // Replace channel's listener.
+ observe(subject, topic, data) {
+ dump("In HttpResponseExaminer.observe\n");
+ try {
+ subject.QueryInterface(Ci.nsITraceableChannel);
+
+ var tee = Cc["@mozilla.org/network/stream-listener-tee;1"].createInstance(
+ Ci.nsIStreamListenerTee
+ );
+ var newListener = new TracingListener();
+ pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ streamSink = pipe.outputStream;
+
+ var originalListener = subject.setNewListener(tee);
+ tee.init(originalListener, streamSink, newListener);
+ } catch (e) {
+ do_throw("can't replace listener " + e);
+ }
+ dump("Did HttpResponseExaminer.observe\n");
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+// Check if received body is correctly modified.
+function channel_finished(request, input, ctx) {
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ var observer = new HttpResponseExaminer();
+ observer.register();
+
+ httpserver.registerPathHandler("/testdir", test_handler);
+
+ var channel = make_channel("http://localhost:" + PORT + "/testdir");
+ channel.asyncOpen(new ChannelListener(channel_finished));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_trackingProtection_annotateChannels.js b/netwerk/test/unit/test_trackingProtection_annotateChannels.js
new file mode 100644
index 0000000000..a54005b8e2
--- /dev/null
+++ b/netwerk/test/unit/test_trackingProtection_annotateChannels.js
@@ -0,0 +1,391 @@
+"use strict";
+
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+// This test supports both e10s and non-e10s mode. In non-e10s mode, this test
+// drives itself by creating a profile directory, setting up the URL classifier
+// test tables and adjusting the prefs which are necessary to do the testing.
+// In e10s mode however, some of these operations such as creating a profile
+// directory, setting up the URL classifier test tables and setting prefs
+// aren't supported in the content process, so we split the test into two
+// parts, the part testing the normal priority case by setting both prefs to
+// false (test_trackingProtection_annotateChannels_wrap1.js), and the part
+// testing the lowest priority case by setting both prefs to true
+// (test_trackingProtection_annotateChannels_wrap2.js). These wrapper scripts
+// are also in charge of creating the profile directory and setting up the URL
+// classifier test tables.
+//
+// Below where we need to take different actions based on the process type we're
+// in, we use runtime.processType to take the correct actions.
+
+const runtime = Services.appinfo;
+if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
+ do_get_profile();
+}
+
+const defaultTopWindowURI = NetUtil.newURI("http://www.example.com/");
+
+function listener(tracking, priority, throttleable, nextTest) {
+ this._tracking = tracking;
+ this._priority = priority;
+ this._throttleable = throttleable;
+ this._nextTest = nextTest;
+}
+listener.prototype = {
+ onStartRequest(request) {
+ Assert.equal(
+ request
+ .QueryInterface(Ci.nsIClassifiedChannel)
+ .isThirdPartyTrackingResource(),
+ this._tracking,
+ "tracking flag"
+ );
+ Assert.equal(
+ request.QueryInterface(Ci.nsISupportsPriority).priority,
+ this._priority,
+ "channel priority"
+ );
+ if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT && this._tracking) {
+ Assert.equal(
+ !!(
+ request.QueryInterface(Ci.nsIClassOfService).classFlags &
+ Ci.nsIClassOfService.Throttleable
+ ),
+ this._throttleable,
+ "throttleable flag"
+ );
+ }
+ request.cancel(Cr.NS_ERROR_ABORT);
+ this._nextTest();
+ },
+ onDataAvailable: (request, stream, offset, count) => {},
+ onStopRequest: (request, status) => {},
+};
+
+var httpServer;
+var normalOrigin, trackingOrigin;
+var testPriorityMap;
+var currentTest;
+// When this test is running in e10s mode, the parent process is in charge of
+// setting the prefs for us, so here we merely read our prefs, and if they have
+// been set we skip the normal priority test and only test the lowest priority
+// case, and if it they have not been set we skip the lowest priority test and
+// only test the normal priority case.
+// In non-e10s mode, both of these will remain false and we adjust the prefs
+// ourselves and test both of the cases in one go.
+var skipNormalPriority = false,
+ skipLowestPriority = false;
+
+function setup_test() {
+ httpServer = new HttpServer();
+ httpServer.start(-1);
+ httpServer.identity.setPrimary(
+ "http",
+ "tracking.example.org",
+ httpServer.identity.primaryPort
+ );
+ httpServer.identity.add(
+ "http",
+ "example.org",
+ httpServer.identity.primaryPort
+ );
+ normalOrigin = "http://localhost:" + httpServer.identity.primaryPort;
+ trackingOrigin =
+ "http://tracking.example.org:" + httpServer.identity.primaryPort;
+
+ if (runtime.processType == runtime.PROCESS_TYPE_CONTENT) {
+ if (
+ Services.prefs.getBoolPref(
+ "privacy.trackingprotection.annotate_channels"
+ ) &&
+ Services.prefs.getBoolPref(
+ "privacy.trackingprotection.lower_network_priority"
+ )
+ ) {
+ skipNormalPriority = true;
+ } else {
+ skipLowestPriority = true;
+ }
+ }
+
+ runTests();
+}
+
+function doPriorityTest() {
+ if (!testPriorityMap.length) {
+ runTests();
+ return;
+ }
+
+ currentTest = testPriorityMap.shift();
+
+ // Let's be explicit about what we're testing!
+ Assert.ok(
+ "loadingPrincipal" in currentTest,
+ "check for incomplete test case"
+ );
+ Assert.ok("topWindowURI" in currentTest, "check for incomplete test case");
+
+ var channel = makeChannel(
+ currentTest.path,
+ currentTest.loadingPrincipal,
+ currentTest.topWindowURI
+ );
+ channel.asyncOpen(
+ new listener(
+ currentTest.expectedTracking,
+ currentTest.expectedPriority,
+ currentTest.expectedThrottleable,
+ doPriorityTest
+ )
+ );
+}
+
+function makeChannel(path, loadingPrincipal, topWindowURI) {
+ var chan;
+
+ if (loadingPrincipal) {
+ chan = NetUtil.newChannel({
+ uri: path,
+ loadingPrincipal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+ } else {
+ chan = NetUtil.newChannel({
+ uri: path,
+ loadUsingSystemPrincipal: true,
+ });
+ }
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ if (topWindowURI) {
+ chan
+ .QueryInterface(Ci.nsIHttpChannelInternal)
+ .setTopWindowURIIfUnknown(topWindowURI);
+ }
+ return chan;
+}
+
+var tests = [
+ // Create the HTTP server.
+ setup_test,
+
+ // Add the test table into tracking protection table.
+ function addTestTrackers() {
+ if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
+ UrlClassifierTestUtils.addTestTrackers().then(() => {
+ runTests();
+ });
+ } else {
+ runTests();
+ }
+ },
+
+ // Annotations OFF, normal loading principal, topWinURI of example.com
+ // => trackers should not be de-prioritized
+ function setupAnnotationsOff() {
+ if (skipNormalPriority) {
+ runTests();
+ return;
+ }
+ if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.annotate_channels",
+ false
+ );
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.lower_network_priority",
+ false
+ );
+ }
+ var principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ normalOrigin
+ );
+ testPriorityMap = [
+ {
+ path: normalOrigin + "/innocent.css",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ {
+ path: normalOrigin + "/innocent.js",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ {
+ path: trackingOrigin + "/evil.css",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ {
+ path: trackingOrigin + "/evil.js",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ ];
+ // We add the doPriorityTest test here so that it only gets injected in the
+ // test list if we're not skipping over this test.
+ tests.unshift(doPriorityTest);
+ runTests();
+ },
+
+ // Annotations ON, normal loading principal, topWinURI of example.com
+ // => trackers should be de-prioritized
+ function setupAnnotationsOn() {
+ if (skipLowestPriority) {
+ runTests();
+ return;
+ }
+ if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.annotate_channels",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.lower_network_priority",
+ true
+ );
+ }
+ var principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ normalOrigin
+ );
+ testPriorityMap = [
+ {
+ path: normalOrigin + "/innocent.css",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ {
+ path: normalOrigin + "/innocent.js",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ {
+ path: trackingOrigin + "/evil.css",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: true,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_LOWEST,
+ expectedThrottleable: true,
+ },
+ {
+ path: trackingOrigin + "/evil.js",
+ loadingPrincipal: principal,
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: true,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_LOWEST,
+ expectedThrottleable: true,
+ },
+ ];
+ // We add the doPriorityTest test here so that it only gets injected in the
+ // test list if we're not skipping over this test.
+ tests.unshift(doPriorityTest);
+ runTests();
+ },
+
+ // Annotations ON, system loading principal, topWinURI of example.com
+ // => trackers should not be de-prioritized
+ function setupAnnotationsOnSystemPrincipal() {
+ if (skipLowestPriority) {
+ runTests();
+ return;
+ }
+ if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.annotate_channels",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.lower_network_priority",
+ true
+ );
+ }
+ testPriorityMap = [
+ {
+ path: normalOrigin + "/innocent.css",
+ loadingPrincipal: null, // system principal
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ {
+ path: normalOrigin + "/innocent.js",
+ loadingPrincipal: null, // system principal
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false, // ignored since tracking==false
+ },
+ {
+ path: trackingOrigin + "/evil.css",
+ loadingPrincipal: null, // system principal
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: false,
+ },
+ {
+ path: trackingOrigin + "/evil.js",
+ loadingPrincipal: null, // system principal
+ topWindowURI: defaultTopWindowURI,
+ expectedTracking: false,
+ expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
+ expectedThrottleable: true,
+ },
+ ];
+ // We add the doPriorityTest test here so that it only gets injected in the
+ // test list if we're not skipping over this test.
+ tests.unshift(doPriorityTest);
+ runTests();
+ },
+
+ function cleanUp() {
+ httpServer.stop(do_test_finished);
+ if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ }
+ runTests();
+ },
+];
+
+function runTests() {
+ if (!tests.length) {
+ do_test_finished();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+function run_test() {
+ runTests();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_trr.js b/netwerk/test/unit/test_trr.js
new file mode 100644
index 0000000000..52b36620b1
--- /dev/null
+++ b/netwerk/test/unit/test_trr.js
@@ -0,0 +1,946 @@
+"use strict";
+
+/* import-globals-from trr_common.js */
+
+const gDefaultPref = Services.prefs.getDefaultBranch("");
+
+SetParentalControlEnabled(false);
+
+function setup() {
+ Services.prefs.setBoolPref("network.dns.get-ttl", false);
+ h2Port = trr_test_setup();
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.get-ttl");
+});
+
+async function waitForConfirmation(expectedResponseIP, confirmationShouldFail) {
+ // Check that the confirmation eventually completes.
+ let count = 100;
+ while (count > 0) {
+ if (count == 50 || count == 10) {
+ // At these two points we do a longer timeout to account for a slow
+ // response on the server side. This is usually a problem on the Android
+ // because of the increased delay between the emulator and host.
+ await new Promise(resolve => do_timeout(100 * (100 / count), resolve));
+ }
+ let { inRecord } = await new TRRDNSListener(
+ `ip${count}.example.org`,
+ undefined,
+ false
+ );
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let responseIP = inRecord.getNextAddrAsString();
+ Assert.ok(true, responseIP);
+ if (responseIP == expectedResponseIP) {
+ break;
+ }
+ count--;
+ }
+
+ if (confirmationShouldFail) {
+ Assert.equal(count, 0, "Confirmation did not finish after 100 iterations");
+ return;
+ }
+
+ Assert.greater(count, 0, "Finished confirmation before 100 iterations");
+}
+
+function setModeAndURI(mode, path) {
+ Services.prefs.setIntPref("network.trr.mode", mode);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://${TRR_Domain}:${h2Port}/${path}`
+ );
+}
+
+function makeChan(url, mode, bypassCache) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ chan.setTRRMode(mode);
+ return chan;
+}
+
+add_task(async function test_server_up() {
+ // This test checks that moz-http2.js running in node is working.
+ // This should always be the first test in this file (except for setup)
+ // otherwise we may encounter random failures when the http2 server is down.
+
+ await NodeServer.execute("bad_id", `"hello"`)
+ .then(() => ok(false, "expecting to throw"))
+ .catch(e => equal(e.message, "Error: could not find id"));
+});
+
+add_task(async function test_trr_flags() {
+ Services.prefs.setBoolPref("network.trr.fallback-on-zero-response", true);
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10000);
+ Services.prefs.setIntPref(
+ "network.trr.request_timeout_mode_trronly_ms",
+ 10000
+ );
+
+ let httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ let content = "ok";
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start(-1);
+
+ const URL = `http://example.com:${httpserv.identity.primaryPort}/`;
+
+ for (let mode of [0, 1, 2, 3, 4, 5]) {
+ setModeAndURI(mode, "doh?responseIP=127.0.0.1");
+ for (let flag of [
+ Ci.nsIRequest.TRR_DEFAULT_MODE,
+ Ci.nsIRequest.TRR_DISABLED_MODE,
+ Ci.nsIRequest.TRR_FIRST_MODE,
+ Ci.nsIRequest.TRR_ONLY_MODE,
+ ]) {
+ Services.dns.clearCache(true);
+ let chan = makeChan(URL, flag);
+ let expectTRR =
+ ([2, 3].includes(mode) && flag != Ci.nsIRequest.TRR_DISABLED_MODE) ||
+ (mode == 0 &&
+ [Ci.nsIRequest.TRR_FIRST_MODE, Ci.nsIRequest.TRR_ONLY_MODE].includes(
+ flag
+ ));
+
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve))
+ );
+
+ equal(chan.getTRRMode(), flag);
+ equal(
+ expectTRR,
+ chan.QueryInterface(Ci.nsIHttpChannelInternal).isResolvedByTRR
+ );
+ }
+ }
+
+ await new Promise(resolve => httpserv.stop(resolve));
+ Services.prefs.clearUserPref("network.trr.fallback-on-zero-response");
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+});
+
+add_task(test_A_record);
+
+add_task(async function test_push() {
+ info("Verify DOH push");
+ Services.dns.clearCache(true);
+ info("Asking server to push us a record");
+ setModeAndURI(3, "doh?responseIP=5.5.5.5&push=true");
+
+ await new TRRDNSListener("first.example.com", "5.5.5.5");
+
+ // At this point the second host name should've been pushed and we can resolve it using
+ // cache only. Set back the URI to a path that fails.
+ // Don't clear the cache, otherwise we lose the pushed record.
+ setModeAndURI(3, "404");
+
+ await new TRRDNSListener("push.example.org", "2018::2018");
+});
+
+add_task(test_AAAA_records);
+
+add_task(test_RFC1918);
+
+add_task(test_GET_ECS);
+
+add_task(test_timeout_mode3);
+
+add_task(test_trr_retry);
+
+add_task(test_strict_native_fallback);
+
+add_task(test_no_answers_fallback);
+
+add_task(test_404_fallback);
+
+add_task(test_mode_1_and_4);
+
+add_task(test_CNAME);
+
+add_task(test_name_mismatch);
+
+add_task(test_mode_2);
+
+add_task(test_excluded_domains);
+
+add_task(test_captiveportal_canonicalURL);
+
+add_task(test_parentalcontrols);
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref
+add_task(test_builtin_excluded_domains);
+
+add_task(test_excluded_domains_mode3);
+
+add_task(test25e);
+
+add_task(test_parentalcontrols_mode3);
+
+add_task(test_builtin_excluded_domains_mode3);
+
+add_task(count_cookies);
+
+add_task(test_connection_closed);
+
+add_task(async function test_clearCacheOnURIChange() {
+ info("Check that the TRR cache should be cleared by a pref change.");
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", true);
+ setModeAndURI(2, "doh?responseIP=7.7.7.7");
+
+ await new TRRDNSListener("bar.example.com", "7.7.7.7");
+
+ // The TRR cache should be cleared by this pref change.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=8.8.8.8`
+ );
+
+ await new TRRDNSListener("bar.example.com", "8.8.8.8");
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+});
+
+add_task(async function test_dnsSuffix() {
+ info("Checking that domains matching dns suffix list use Do53");
+ async function checkDnsSuffixInMode(mode) {
+ Services.dns.clearCache(true);
+ setModeAndURI(mode, "doh?responseIP=1.2.3.4&push=true");
+ await new TRRDNSListener("example.org", "1.2.3.4");
+ await new TRRDNSListener("push.example.org", "2018::2018");
+ await new TRRDNSListener("test.com", "1.2.3.4");
+
+ let networkLinkService = {
+ dnsSuffixList: ["example.org"],
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:dns-suffix-list-updated"
+ );
+ await new TRRDNSListener("test.com", "1.2.3.4");
+ if (Services.prefs.getBoolPref("network.trr.split_horizon_mitigations")) {
+ await new TRRDNSListener("example.org", "127.0.0.1");
+ // Also test that we don't use the pushed entry.
+ await new TRRDNSListener("push.example.org", "127.0.0.1");
+ } else {
+ await new TRRDNSListener("example.org", "1.2.3.4");
+ await new TRRDNSListener("push.example.org", "2018::2018");
+ }
+
+ // Attempt to clean up, just in case
+ networkLinkService.dnsSuffixList = [];
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:dns-suffix-list-updated"
+ );
+ }
+
+ Services.prefs.setBoolPref("network.trr.split_horizon_mitigations", true);
+ await checkDnsSuffixInMode(2);
+ Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
+ await checkDnsSuffixInMode(3);
+ Services.prefs.setBoolPref("network.trr.split_horizon_mitigations", false);
+ // Test again with mitigations off
+ await checkDnsSuffixInMode(2);
+ await checkDnsSuffixInMode(3);
+ Services.prefs.clearUserPref("network.trr.split_horizon_mitigations");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+});
+
+add_task(async function test_async_resolve_with_trr_server() {
+ info("Checking asyncResolveWithTrrServer");
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 0); // TRR-disabled
+
+ await new TRRDNSListener(
+ "bar_with_trr1.example.com",
+ "2.2.2.2",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ // Test request without trr server, it should return a native dns response.
+ await new TRRDNSListener("bar_with_trr1.example.com", "127.0.0.1");
+
+ // Mode 2
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener(
+ "bar_with_trr2.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ // Test request without trr server, it should return a response from trr server defined in the pref.
+ await new TRRDNSListener("bar_with_trr2.example.com", "2.2.2.2");
+
+ // Mode 3
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener(
+ "bar_with_trr3.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ // Test request without trr server, it should return a response from trr server defined in the pref.
+ await new TRRDNSListener("bar_with_trr3.example.com", "2.2.2.2");
+
+ // Mode 5
+ Services.dns.clearCache(true);
+ setModeAndURI(5, "doh?responseIP=2.2.2.2");
+
+ // When dns is resolved in socket process, we can't set |expectEarlyFail| to true.
+ let inSocketProcess = mozinfo.socketprocess_networking;
+ await new TRRDNSListener(
+ "bar_with_trr3.example.com",
+ undefined,
+ false,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`,
+ !inSocketProcess
+ );
+
+ // Call normal AsyncOpen, it will return result from the native resolver.
+ await new TRRDNSListener("bar_with_trr3.example.com", "127.0.0.1");
+
+ // Check that cache is ignored when server is different
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener("bar_with_trr4.example.com", "2.2.2.2", true);
+
+ // The record will be fetch again.
+ await new TRRDNSListener(
+ "bar_with_trr4.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ // The record will be fetch again.
+ await new TRRDNSListener(
+ "bar_with_trr5.example.com",
+ "4.4.4.4",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=4.4.4.4`
+ );
+
+ // Check no fallback and no blocklisting upon failure
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ let { inStatus } = await new TRRDNSListener(
+ "bar_with_trr6.example.com",
+ undefined,
+ false,
+ undefined,
+ `https://foo.example.com:${h2Port}/404`
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ await new TRRDNSListener("bar_with_trr6.example.com", "2.2.2.2", true);
+
+ // Check that DoH push doesn't work
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener(
+ "bar_with_trr7.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3&push=true`
+ );
+
+ // AsyncResoleWithTrrServer rejects server pushes and the entry for push.example.org
+ // shouldn't be neither in the default cache not in AsyncResoleWithTrrServer cache.
+ setModeAndURI(2, "404");
+
+ await new TRRDNSListener(
+ "push.example.org",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3&push=true`
+ );
+
+ await new TRRDNSListener("push.example.org", "127.0.0.1");
+
+ // Check confirmation is ignored
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=1::ffff");
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ // AsyncResoleWithTrrServer will succeed
+ await new TRRDNSListener(
+ "bar_with_trr8.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ // Bad port
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ ({ inStatus } = await new TRRDNSListener(
+ "only_once.example.com",
+ undefined,
+ false,
+ undefined,
+ `https://target.example.com:666/404`
+ ));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ // // MOZ_LOG=sync,timestamp,nsHostResolver:5 We should not keep resolving only_once.example.com
+ // // TODO: find a way of automating this
+ // await new Promise(resolve => {});
+});
+
+add_task(test_fetch_time);
+
+add_task(async function test_content_encoding_gzip() {
+ info("Checking gzip content encoding");
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref(
+ "network.trr.send_empty_accept-encoding_headers",
+ false
+ );
+ setModeAndURI(3, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+ Services.prefs.clearUserPref(
+ "network.trr.send_empty_accept-encoding_headers"
+ );
+});
+
+add_task(async function test_redirect() {
+ info("Check handling of redirect");
+
+ // GET
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?redirect=4.4.4.4{&dns}");
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+
+ await new TRRDNSListener("ecs.example.com", "4.4.4.4");
+
+ // POST
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.useGET", false);
+ setModeAndURI(3, "doh?redirect=4.4.4.4");
+
+ await new TRRDNSListener("bar.example.com", "4.4.4.4");
+
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+});
+
+// confirmationNS set without confirmed NS yet
+// checks that we properly fall back to DNS is confirmation is not ready yet,
+// and wait-for-confirmation pref is true
+add_task(async function test_confirmation() {
+ info("Checking that we fall back correctly when confirmation is pending");
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.wait-for-confirmation", true);
+ setModeAndURI(2, "doh?responseIP=7.7.7.7&slowConfirm=true");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await new TRRDNSListener("example.org", "127.0.0.1");
+ await new Promise(resolve => do_timeout(1000, resolve));
+ await waitForConfirmation("7.7.7.7");
+
+ // Reset between each test to force re-confirm
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ info("Check that confirmation is skipped in mode 3");
+ // This is just a smoke test to make sure lookups succeed immediately
+ // in mode 3 without waiting for confirmation.
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=1::ffff&slowConfirm=true");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await new TRRDNSListener("skipConfirmationForMode3.example.com", "1::ffff");
+
+ // Reset between each test to force re-confirm
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.wait-for-confirmation", false);
+ setModeAndURI(2, "doh?responseIP=7.7.7.7&slowConfirm=true");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ // DoH available immediately
+ await new TRRDNSListener("example.org", "7.7.7.7");
+
+ // Reset between each test to force re-confirm
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ // Fallback when confirmation fails
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.wait-for-confirmation", true);
+ setModeAndURI(2, "404");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await waitForConfirmation("7.7.7.7", true);
+
+ await new TRRDNSListener("example.org", "127.0.0.1");
+
+ // Reset
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ Services.prefs.clearUserPref("network.trr.wait-for-confirmation");
+});
+
+add_task(test_fqdn);
+
+add_task(async function test_detected_uri() {
+ info("Test setDetectedTrrURI");
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.clearUserPref("network.trr.uri");
+ let defaultURI = gDefaultPref.getCharPref("network.trr.default_provider_uri");
+ gDefaultPref.setCharPref(
+ "network.trr.default_provider_uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.4.5.6`
+ );
+ await new TRRDNSListener("domainA.example.org.", "3.4.5.6");
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new TRRDNSListener("domainB.example.org.", "1.2.3.4");
+ gDefaultPref.setCharPref("network.trr.default_provider_uri", defaultURI);
+
+ // With a user-set doh uri this time.
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=4.5.6.7");
+ await new TRRDNSListener("domainA.example.org.", "4.5.6.7");
+
+ // This should be a no-op, since we have a user-set URI
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new TRRDNSListener("domainB.example.org.", "4.5.6.7");
+
+ // Test network link status change
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.clearUserPref("network.trr.uri");
+ gDefaultPref.setCharPref(
+ "network.trr.default_provider_uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.4.5.6`
+ );
+ await new TRRDNSListener("domainA.example.org.", "3.4.5.6");
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new TRRDNSListener("domainB.example.org.", "1.2.3.4");
+
+ let networkLinkService = {
+ platformDNSIndications: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:link-status-changed",
+ "changed"
+ );
+
+ await new TRRDNSListener("domainC.example.org.", "3.4.5.6");
+
+ gDefaultPref.setCharPref("network.trr.default_provider_uri", defaultURI);
+});
+
+add_task(async function test_pref_changes() {
+ info("Testing pref change handling");
+ Services.prefs.clearUserPref("network.trr.uri");
+ let defaultURI = gDefaultPref.getCharPref("network.trr.default_provider_uri");
+
+ async function doThenCheckURI(closure, expectedURI, expectChange = true) {
+ let uriChanged;
+ if (expectChange) {
+ uriChanged = topicObserved("network:trr-uri-changed");
+ }
+ closure();
+ if (expectChange) {
+ await uriChanged;
+ }
+ equal(Services.dns.currentTrrURI, expectedURI);
+ }
+
+ // setting the default value of the pref should be reflected in the URI
+ await doThenCheckURI(() => {
+ gDefaultPref.setCharPref(
+ "network.trr.default_provider_uri",
+ `https://foo.example.com:${h2Port}/doh?default`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?default`);
+
+ // the user set value should be reflected in the URI
+ await doThenCheckURI(() => {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?user`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?user`);
+
+ // A user set pref is selected, so it should be chosen instead of the rollout
+ await doThenCheckURI(
+ () => {
+ Services.prefs.setCharPref(
+ "doh-rollout.uri",
+ `https://foo.example.com:${h2Port}/doh?rollout`
+ );
+ },
+ `https://foo.example.com:${h2Port}/doh?user`,
+ false
+ );
+
+ // There is no user set pref, so we go to the rollout pref
+ await doThenCheckURI(() => {
+ Services.prefs.clearUserPref("network.trr.uri");
+ }, `https://foo.example.com:${h2Port}/doh?rollout`);
+
+ // When the URI is set by the rollout addon, detection is allowed
+ await doThenCheckURI(() => {
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?detected`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?detected`);
+
+ // Should switch back to the default provided by the rollout addon
+ await doThenCheckURI(() => {
+ let networkLinkService = {
+ platformDNSIndications: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:link-status-changed",
+ "changed"
+ );
+ }, `https://foo.example.com:${h2Port}/doh?rollout`);
+
+ // Again the user set pref should be chosen
+ await doThenCheckURI(() => {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?user`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?user`);
+
+ // Detection should not work with a user set pref
+ await doThenCheckURI(
+ () => {
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?detected`
+ );
+ },
+ `https://foo.example.com:${h2Port}/doh?user`,
+ false
+ );
+
+ // Should stay the same on network changes
+ await doThenCheckURI(
+ () => {
+ let networkLinkService = {
+ platformDNSIndications: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:link-status-changed",
+ "changed"
+ );
+ },
+ `https://foo.example.com:${h2Port}/doh?user`,
+ false
+ );
+
+ // Restore the pref
+ gDefaultPref.setCharPref("network.trr.default_provider_uri", defaultURI);
+});
+
+add_task(async function test_dohrollout_mode() {
+ info("Testing doh-rollout.mode");
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ equal(Services.dns.currentTrrMode, 0);
+
+ async function doThenCheckMode(trrMode, rolloutMode, expectedMode, message) {
+ let modeChanged;
+ if (Services.dns.currentTrrMode != expectedMode) {
+ modeChanged = topicObserved("network:trr-mode-changed");
+ }
+
+ if (trrMode != undefined) {
+ Services.prefs.setIntPref("network.trr.mode", trrMode);
+ }
+
+ if (rolloutMode != undefined) {
+ Services.prefs.setIntPref("doh-rollout.mode", rolloutMode);
+ }
+
+ if (modeChanged) {
+ await modeChanged;
+ }
+ equal(Services.dns.currentTrrMode, expectedMode, message);
+ }
+
+ await doThenCheckMode(2, undefined, 2);
+ await doThenCheckMode(3, undefined, 3);
+ await doThenCheckMode(5, undefined, 5);
+ await doThenCheckMode(2, undefined, 2);
+ await doThenCheckMode(0, undefined, 0);
+ await doThenCheckMode(1, undefined, 5);
+ await doThenCheckMode(6, undefined, 5);
+
+ await doThenCheckMode(2, 0, 2);
+ await doThenCheckMode(2, 1, 2);
+ await doThenCheckMode(2, 2, 2);
+ await doThenCheckMode(2, 3, 2);
+ await doThenCheckMode(2, 5, 2);
+ await doThenCheckMode(3, 2, 3);
+ await doThenCheckMode(5, 2, 5);
+
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ await doThenCheckMode(undefined, 2, 2);
+ await doThenCheckMode(undefined, 3, 3);
+
+ // All modes that are not 0,2,3 are treated as 5
+ await doThenCheckMode(undefined, 5, 5);
+ await doThenCheckMode(undefined, 4, 5);
+ await doThenCheckMode(undefined, 6, 5);
+
+ await doThenCheckMode(undefined, 2, 2);
+ await doThenCheckMode(3, undefined, 3);
+
+ Services.prefs.clearUserPref("network.trr.mode");
+ equal(Services.dns.currentTrrMode, 2);
+ Services.prefs.clearUserPref("doh-rollout.mode");
+ equal(Services.dns.currentTrrMode, 0);
+});
+
+add_task(test_ipv6_trr_fallback);
+
+add_task(test_ipv4_trr_fallback);
+
+add_task(test_no_retry_without_doh);
+
+// This test checks that normally when the TRR mode goes from ON -> OFF
+// we purge the DNS cache (including TRR), so the entries aren't used on
+// networks where they shouldn't. For example - turning on a VPN.
+add_task(async function test_purge_trr_cache_on_mode_change() {
+ info("Checking that we purge cache when TRR is turned off");
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", true);
+
+ Services.prefs.setIntPref("network.trr.mode", 0);
+ Services.prefs.setIntPref("doh-rollout.mode", 2);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ await new TRRDNSListener("cached.example.com", "3.3.3.3");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ await new TRRDNSListener("cached.example.com", "127.0.0.1");
+
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+ Services.prefs.clearUserPref("doh-rollout.mode");
+});
+
+add_task(async function test_old_bootstrap_pref() {
+ Services.dns.clearCache(true);
+ // Note this is a remote address. Setting this pref should have no effect,
+ // as this is the old name for the bootstrap pref.
+ // If this were to be used, the test would crash when accessing a non-local
+ // IP address.
+ Services.prefs.setCharPref("network.trr.bootstrapAddress", "1.1.1.1");
+ setModeAndURI(Ci.nsIDNSService.MODE_TRRONLY, `doh?responseIP=4.4.4.4`);
+ await new TRRDNSListener("testytest.com", "4.4.4.4");
+});
+
+add_task(async function test_padding() {
+ setModeAndURI(Ci.nsIDNSService.MODE_TRRONLY, `doh`);
+ async function CheckPadding(
+ pad_length,
+ request,
+ none,
+ ecs,
+ padding,
+ ecsPadding
+ ) {
+ Services.prefs.setIntPref("network.trr.padding.length", pad_length);
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", false);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", false);
+ await new TRRDNSListener(request, none);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", false);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+ await new TRRDNSListener(request, ecs);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", false);
+ await new TRRDNSListener(request, padding);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+ await new TRRDNSListener(request, ecsPadding);
+ }
+
+ // short domain name
+ await CheckPadding(
+ 16,
+ "a.pd",
+ "2.2.0.22",
+ "2.2.0.41",
+ "1.1.0.48",
+ "1.1.0.48"
+ );
+ await CheckPadding(256, "a.pd", "2.2.0.22", "2.2.0.41", "1.1.1.0", "1.1.1.0");
+
+ // medium domain name
+ await CheckPadding(
+ 16,
+ "has-padding.pd",
+ "2.2.0.32",
+ "2.2.0.51",
+ "1.1.0.48",
+ "1.1.0.64"
+ );
+ await CheckPadding(
+ 128,
+ "has-padding.pd",
+ "2.2.0.32",
+ "2.2.0.51",
+ "1.1.0.128",
+ "1.1.0.128"
+ );
+ await CheckPadding(
+ 80,
+ "has-padding.pd",
+ "2.2.0.32",
+ "2.2.0.51",
+ "1.1.0.80",
+ "1.1.0.80"
+ );
+
+ // long domain name
+ await CheckPadding(
+ 16,
+ "abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.pd",
+ "2.2.0.131",
+ "2.2.0.150",
+ "1.1.0.160",
+ "1.1.0.160"
+ );
+ await CheckPadding(
+ 128,
+ "abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.pd",
+ "2.2.0.131",
+ "2.2.0.150",
+ "1.1.1.0",
+ "1.1.1.0"
+ );
+ await CheckPadding(
+ 80,
+ "abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.pd",
+ "2.2.0.131",
+ "2.2.0.150",
+ "1.1.0.160",
+ "1.1.0.160"
+ );
+});
+
+add_task(test_connection_reuse_and_cycling);
+
+// Can't test for socket process since telemetry is captured in different process.
+add_task(
+ { skip_if: () => mozinfo.socketprocess_networking },
+ async function test_trr_pb_telemetry() {
+ setModeAndURI(Ci.nsIDNSService.MODE_TRRONLY, `doh`);
+ Services.dns.clearCache(true);
+ Services.fog.initializeFOG();
+ Services.fog.testResetFOG();
+ await new TRRDNSListener("testytest.com", { expectedAnswer: "5.5.5.5" });
+
+ Assert.equal(
+ await Glean.networking.trrRequestCount.regular.testGetValue(),
+ 2
+ ); // One for IPv4 and one for IPv6.
+ Assert.equal(
+ await Glean.networking.trrRequestCount.private.testGetValue(),
+ null
+ );
+
+ await new TRRDNSListener("testytest.com", {
+ expectedAnswer: "5.5.5.5",
+ originAttributes: { privateBrowsingId: 1 },
+ });
+
+ Assert.equal(
+ await Glean.networking.trrRequestCount.regular.testGetValue(),
+ 2
+ );
+ Assert.equal(
+ await Glean.networking.trrRequestCount.private.testGetValue(),
+ 2
+ );
+ }
+);
diff --git a/netwerk/test/unit/test_trr_additional_section.js b/netwerk/test/unit/test_trr_additional_section.js
new file mode 100644
index 0000000000..bd8b300b96
--- /dev/null
+++ b/netwerk/test/unit/test_trr_additional_section.js
@@ -0,0 +1,380 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish));
+ });
+}
+
+let trrServer = new TRRServer();
+registerCleanupFunction(async () => {
+ await trrServer.stop();
+});
+add_task(async function setup_server() {
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+ let chan = makeChan(`https://localhost:${trrServer.port()}/test?bla=some`);
+ let [, resp] = await channelOpenPromise(chan);
+ equal(resp, "<h1> 404 Path not found: /test</h1>");
+});
+
+add_task(async function test_parse_additional_section() {
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("something.foo", "A", {
+ answers: [
+ {
+ name: "something.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ additionals: [
+ {
+ name: "else.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("something.foo", { expectedAnswer: "1.2.3.4" });
+ await new TRRDNSListener("else.foo", { expectedAnswer: "2.3.4.5" });
+
+ await trrServer.registerDoHAnswers("a.foo", "A", {
+ answers: [
+ {
+ name: "a.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ additionals: [
+ {
+ name: "b.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("b.foo", "A", {
+ answers: [
+ {
+ name: "b.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "3.4.5.6",
+ },
+ ],
+ });
+
+ let req1 = new TRRDNSListener("a.foo", { expectedAnswer: "1.2.3.4" });
+
+ // A request for b.foo will be in progress by the time we parse the additional
+ // record. To keep things simple we don't end up saving the record, instead
+ // we wait for the in-progress request to complete.
+ // This check is also racy - if the response for a.foo completes before we make
+ // this request, we'll put the other IP in the cache. But that is very unlikely.
+ let req2 = new TRRDNSListener("b.foo", { expectedAnswer: "3.4.5.6" });
+
+ await Promise.all([req1, req2]);
+
+ // IPv6 additional
+ await trrServer.registerDoHAnswers("xyz.foo", "A", {
+ answers: [
+ {
+ name: "xyz.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ additionals: [
+ {
+ name: "abc.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::1:2:3:4",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("xyz.foo", { expectedAnswer: "1.2.3.4" });
+ await new TRRDNSListener("abc.foo", { expectedAnswer: "::1:2:3:4" });
+
+ // IPv6 additional
+ await trrServer.registerDoHAnswers("ipv6.foo", "AAAA", {
+ answers: [
+ {
+ name: "ipv6.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "2001::a:b:c:d",
+ },
+ ],
+ additionals: [
+ {
+ name: "def.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::a:b:c:d",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("ipv6.foo", { expectedAnswer: "2001::a:b:c:d" });
+ await new TRRDNSListener("def.foo", { expectedAnswer: "::a:b:c:d" });
+
+ // IPv6 additional
+ await trrServer.registerDoHAnswers("ipv6b.foo", "AAAA", {
+ answers: [
+ {
+ name: "ipv6b.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "2001::a:b:c:d",
+ },
+ ],
+ additionals: [
+ {
+ name: "qqqq.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "9.8.7.6",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("ipv6b.foo", { expectedAnswer: "2001::a:b:c:d" });
+ await new TRRDNSListener("qqqq.foo", { expectedAnswer: "9.8.7.6" });
+
+ // Multiple IPs and multiple additional records
+ await trrServer.registerDoHAnswers("multiple.foo", "A", {
+ answers: [
+ {
+ name: "multiple.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "9.9.9.9",
+ },
+ ],
+ additionals: [
+ {
+ // Should be ignored, because it should be in the answer section
+ name: "multiple.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.1.1.1",
+ },
+ {
+ // Is ignored, because it should be in the answer section
+ name: "multiple.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "2001::a:b:c:d",
+ },
+ {
+ name: "yuiop.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "2001::a:b:c:d",
+ },
+ {
+ name: "yuiop.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("multiple.foo", {
+ expectedAnswer: "9.9.9.9",
+ });
+ let IPs = [];
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ inRecord.rewind();
+ while (inRecord.hasMore()) {
+ IPs.push(inRecord.getNextAddrAsString());
+ }
+ equal(IPs.length, 1);
+ equal(IPs[0], "9.9.9.9");
+ IPs = [];
+ ({ inRecord } = await new TRRDNSListener("yuiop.foo", {
+ expectedSuccess: false,
+ }));
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ inRecord.rewind();
+ while (inRecord.hasMore()) {
+ IPs.push(inRecord.getNextAddrAsString());
+ }
+ equal(IPs.length, 2);
+ equal(IPs[0], "2001::a:b:c:d");
+ equal(IPs[1], "1.2.3.4");
+});
+
+add_task(async function test_additional_after_resolve() {
+ await trrServer.registerDoHAnswers("first.foo", "A", {
+ answers: [
+ {
+ name: "first.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "3.4.5.6",
+ },
+ ],
+ });
+ await new TRRDNSListener("first.foo", { expectedAnswer: "3.4.5.6" });
+
+ await trrServer.registerDoHAnswers("second.foo", "A", {
+ answers: [
+ {
+ name: "second.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ additionals: [
+ {
+ name: "first.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("second.foo", { expectedAnswer: "1.2.3.4" });
+ await new TRRDNSListener("first.foo", { expectedAnswer: "2.3.4.5" });
+});
+
+// test for Bug - 1790075
+// Crash was observed when a DNS (using TRR) reply contains an additional
+// record field and this addditional record was previously unsuccessfully
+// resolved
+add_task(async function test_additional_cached_record_override() {
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await new TRRDNSListener("else.foo", { expectedAnswer: "127.0.0.1" });
+
+ await trrServer.registerDoHAnswers("something.foo", "A", {
+ answers: [
+ {
+ name: "something.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ additionals: [
+ {
+ name: "else.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("something.foo", { expectedAnswer: "1.2.3.4" });
+ await new TRRDNSListener("else.foo", { expectedAnswer: "2.3.4.5" });
+});
+
+add_task(async function test_ipv6_disabled() {
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ await trrServer.registerDoHAnswers("ipv6.foo", "A", {
+ answers: [
+ {
+ name: "ipv6.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ additionals: [
+ {
+ name: "sub.ipv6.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::1:2:3:4",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("ipv6.foo", { expectedAnswer: "1.2.3.4" });
+ await new TRRDNSListener("sub.ipv6.foo", { expectedSuccess: false });
+
+ await trrServer.registerDoHAnswers("direct.ipv6.foo", "AAAA", {
+ answers: [
+ {
+ name: "direct.ipv6.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "2001::a:b:c:d",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("direct.ipv6.foo", { expectedSuccess: false });
+
+ Services.prefs.setBoolPref("network.dns.disableIPv6", false);
+});
diff --git a/netwerk/test/unit/test_trr_af_fallback.js b/netwerk/test/unit/test_trr_af_fallback.js
new file mode 100644
index 0000000000..c202e36702
--- /dev/null
+++ b/netwerk/test/unit/test_trr_af_fallback.js
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+let trrServer = null;
+add_task(async function start_trr_server() {
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+ Services.prefs.setBoolPref("network.trr.skip-AAAA-when-not-supported", false);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+add_task(async function unspec_first() {
+ gOverride.clearOverrides();
+ Services.dns.clearCache(true);
+
+ gOverride.addIPOverride("example.org", "1.1.1.1");
+ gOverride.addIPOverride("example.org", "::1");
+
+ await trrServer.registerDoHAnswers("example.org", "A", {
+ answers: [
+ {
+ name: "example.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ // This first request gets cached. IPv6 response gets served from the cache
+ await new TRRDNSListener("example.org", { expectedAnswer: "1.2.3.4" });
+ await new TRRDNSListener("example.org", {
+ flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ expectedAnswer: "1.2.3.4",
+ });
+ let { inStatus } = await new TRRDNSListener("example.org", {
+ flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ expectedSuccess: false,
+ });
+ equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+});
+
+add_task(async function A_then_AAAA_fails() {
+ gOverride.clearOverrides();
+ Services.dns.clearCache(true);
+
+ gOverride.addIPOverride("example.org", "1.1.1.1");
+ gOverride.addIPOverride("example.org", "::1");
+
+ await trrServer.registerDoHAnswers("example.org", "A", {
+ answers: [
+ {
+ name: "example.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ // We do individual IPv4/IPv6 requests - we expect IPv6 not to fallback to Do53 because we have an IPv4 record
+ await new TRRDNSListener("example.org", {
+ flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ expectedAnswer: "1.2.3.4",
+ });
+ let { inStatus } = await new TRRDNSListener("example.org", {
+ flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ expectedSuccess: false,
+ });
+ equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+});
+
+add_task(async function just_AAAA_fails() {
+ gOverride.clearOverrides();
+ Services.dns.clearCache(true);
+
+ gOverride.addIPOverride("example.org", "1.1.1.1");
+ gOverride.addIPOverride("example.org", "::1");
+
+ await trrServer.registerDoHAnswers("example.org", "A", {
+ answers: [
+ {
+ name: "example.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ // We only do an IPv6 req - we expect IPv6 not to fallback to Do53 because we have an IPv4 record
+ let { inStatus } = await new TRRDNSListener("example.org", {
+ flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ expectedSuccess: false,
+ });
+ equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+});
diff --git a/netwerk/test/unit/test_trr_blocklist.js b/netwerk/test/unit/test_trr_blocklist.js
new file mode 100644
index 0000000000..8f9d3ffbc5
--- /dev/null
+++ b/netwerk/test/unit/test_trr_blocklist.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+function setup() {
+ trr_test_setup();
+ Services.prefs.setBoolPref("network.trr.temp_blocklist", true);
+}
+setup();
+
+add_task(async function checkBlocklisting() {
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ info(`port = ${trrServer.port()}\n`);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+
+ await trrServer.registerDoHAnswers("top.test.com", "NS", {});
+
+ override.addIPOverride("sub.top.test.com", "2.2.2.2");
+ override.addIPOverride("sub2.top.test.com", "2.2.2.2");
+ await new TRRDNSListener("sub.top.test.com", {
+ expectedAnswer: "2.2.2.2",
+ });
+ equal(await trrServer.requestCount("sub.top.test.com", "A"), 1);
+
+ // Clear the cache so that we need to consult the blocklist and not simply
+ // return the cached DNS record.
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("sub.top.test.com", {
+ expectedAnswer: "2.2.2.2",
+ });
+ equal(
+ await trrServer.requestCount("sub.top.test.com", "A"),
+ 1,
+ "Request should go directly to native because result is still in blocklist"
+ );
+
+ // XXX(valentin): if this ever starts intermittently failing we need to add
+ // a sleep here. But the check for the parent NS should normally complete
+ // before the second subdomain request.
+ equal(
+ await trrServer.requestCount("top.test.com", "NS"),
+ 1,
+ "Should have checked parent domain"
+ );
+ await new TRRDNSListener("sub2.top.test.com", {
+ expectedAnswer: "2.2.2.2",
+ });
+ equal(await trrServer.requestCount("sub2.top.test.com", "A"), 0);
+
+ // The blocklist should instantly expire.
+ Services.prefs.setIntPref("network.trr.temp_blocklist_duration_sec", 0);
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("sub.top.test.com", {
+ expectedAnswer: "2.2.2.2",
+ });
+ // blocklist expired. Do another check.
+ equal(
+ await trrServer.requestCount("sub.top.test.com", "A"),
+ 2,
+ "We should do another TRR request because the bloclist expired"
+ );
+});
diff --git a/netwerk/test/unit/test_trr_cancel.js b/netwerk/test/unit/test_trr_cancel.js
new file mode 100644
index 0000000000..60b65d7eaf
--- /dev/null
+++ b/netwerk/test/unit/test_trr_cancel.js
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+let trrServer = null;
+add_task(async function start_trr_server() {
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+add_task(async function sanity_check() {
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ // Simple check to see that TRR works.
+ await new TRRDNSListener("example.com", { expectedAnswer: "1.2.3.4" });
+});
+
+// Cancelling the request is not sync when using the socket process, so
+// we skip this test when it's enabled.
+add_task(
+ { skip_if: () => mozinfo.socketprocess_networking },
+ async function cancel_immediately() {
+ await trrServer.registerDoHAnswers("example.org", "A", {
+ answers: [
+ {
+ name: "example.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ],
+ });
+ let r1 = new TRRDNSListener("example.org", { expectedSuccess: false });
+ let r2 = new TRRDNSListener("example.org", { expectedAnswer: "2.3.4.5" });
+ r1.cancel();
+ let { inStatus } = await r1;
+ equal(inStatus, Cr.NS_ERROR_ABORT);
+ await r2;
+ equal(await trrServer.requestCount("example.org", "A"), 1);
+
+ // Now we cancel both of them
+ Services.dns.clearCache(true);
+ r1 = new TRRDNSListener("example.org", { expectedSuccess: false });
+ r2 = new TRRDNSListener("example.org", { expectedSuccess: false });
+ r1.cancel();
+ r2.cancel();
+ ({ inStatus } = await r1);
+ equal(inStatus, Cr.NS_ERROR_ABORT);
+ ({ inStatus } = await r2);
+ equal(inStatus, Cr.NS_ERROR_ABORT);
+ await new Promise(resolve => do_timeout(50, resolve));
+ equal(await trrServer.requestCount("example.org", "A"), 2);
+ }
+);
+
+add_task(async function cancel_delayed() {
+ Services.dns.clearCache(true);
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.1.1.1",
+ },
+ ],
+ delay: 500,
+ });
+ let r1 = new TRRDNSListener("example.com", { expectedSuccess: false });
+ let r2 = new TRRDNSListener("example.com", { expectedAnswer: "1.1.1.1" });
+ await new Promise(resolve => do_timeout(50, resolve));
+ r1.cancel();
+ let { inStatus } = await r1;
+ equal(inStatus, Cr.NS_ERROR_ABORT);
+ await r2;
+});
+
+add_task(async function cancel_after_completed() {
+ Services.dns.clearCache(true);
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.2.2.2",
+ },
+ ],
+ });
+ let r1 = new TRRDNSListener("example.com", { expectedAnswer: "2.2.2.2" });
+ await r1;
+ let r2 = new TRRDNSListener("example.com", { expectedAnswer: "2.2.2.2" });
+ // Check that cancelling r1 after it's complete does not affect r2 in any way.
+ r1.cancel();
+ await r2;
+});
+
+add_task(async function clearCacheWhileResolving() {
+ Services.dns.clearCache(true);
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "3.3.3.3",
+ },
+ ],
+ delay: 500,
+ });
+ // Check that calling clearCache does not leave the request hanging.
+ let r1 = new TRRDNSListener("example.com", { expectedAnswer: "3.3.3.3" });
+ let r2 = new TRRDNSListener("example.com", { expectedAnswer: "3.3.3.3" });
+ Services.dns.clearCache(true);
+ await r1;
+ await r2;
+
+ // Also check the same for HTTPS records
+ await trrServer.registerDoHAnswers("httpsvc.com", "HTTPS", {
+ answers: [
+ {
+ name: "httpsvc.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.p1.com",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ],
+ delay: 500,
+ });
+ let r3 = new TRRDNSListener("httpsvc.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+ let r4 = new TRRDNSListener("httpsvc.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+ Services.dns.clearCache(true);
+ await r3;
+ await r4;
+ equal(await trrServer.requestCount("httpsvc.com", "HTTPS"), 1);
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("httpsvc.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+ equal(await trrServer.requestCount("httpsvc.com", "HTTPS"), 2);
+});
diff --git a/netwerk/test/unit/test_trr_case_sensitivity.js b/netwerk/test/unit/test_trr_case_sensitivity.js
new file mode 100644
index 0000000000..94bd79c6c4
--- /dev/null
+++ b/netwerk/test/unit/test_trr_case_sensitivity.js
@@ -0,0 +1,153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish));
+ });
+}
+
+add_task(async function test_trr_casing() {
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+ let chan = makeChan(`https://localhost:${trrServer.port()}/test?bla=some`);
+ let [, resp] = await channelOpenPromise(chan);
+ equal(resp, "<h1> 404 Path not found: /test</h1>");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ // This CNAME response goes to B.example.com (uppercased)
+ // It should be lowercased by the code
+ await trrServer.registerDoHAnswers("a.example.com", "A", {
+ answers: [
+ {
+ name: "a.example.com",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "B.example.com",
+ },
+ ],
+ });
+ // Like in bug 1635566, the response for B.example.com will be lowercased
+ // by the server too -> b.example.com
+ // Requesting this resource would case the browser to reject the resource
+ await trrServer.registerDoHAnswers("B.example.com", "A", {
+ answers: [
+ {
+ name: "b.example.com",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "c.example.com",
+ },
+ ],
+ });
+
+ // The browser should request this one
+ await trrServer.registerDoHAnswers("b.example.com", "A", {
+ answers: [
+ {
+ name: "b.example.com",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "c.example.com",
+ },
+ ],
+ });
+ // Finally, it gets an IP
+ await trrServer.registerDoHAnswers("c.example.com", "A", {
+ answers: [
+ {
+ name: "c.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ await new TRRDNSListener("a.example.com", { expectedAnswer: "1.2.3.4" });
+
+ await trrServer.registerDoHAnswers("a.test.com", "A", {
+ answers: [
+ {
+ name: "a.test.com",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "B.test.com",
+ },
+ ],
+ });
+ // We try this again, this time we explicitly make sure this resource
+ // is never used
+ await trrServer.registerDoHAnswers("B.test.com", "A", {
+ answers: [
+ {
+ name: "B.test.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "9.9.9.9",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("b.test.com", "A", {
+ answers: [
+ {
+ name: "b.test.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "8.8.8.8",
+ },
+ ],
+ });
+ await new TRRDNSListener("a.test.com", { expectedAnswer: "8.8.8.8" });
+
+ await trrServer.registerDoHAnswers("CAPITAL.COM", "A", {
+ answers: [
+ {
+ name: "capital.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.2.2.2",
+ },
+ ],
+ });
+ await new TRRDNSListener("CAPITAL.COM", { expectedAnswer: "2.2.2.2" });
+ await new TRRDNSListener("CAPITAL.COM.", { expectedAnswer: "2.2.2.2" });
+
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_trr_cname_chain.js b/netwerk/test/unit/test_trr_cname_chain.js
new file mode 100644
index 0000000000..a3da592b7b
--- /dev/null
+++ b/netwerk/test/unit/test_trr_cname_chain.js
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let trrServer;
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish));
+ });
+}
+
+add_setup(async function setup() {
+ trr_test_setup();
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ });
+
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+ let chan = makeChan(`https://localhost:${trrServer.port()}/test?bla=some`);
+ let [, resp] = await channelOpenPromise(chan);
+ equal(resp, "<h1> 404 Path not found: /test</h1>");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+});
+
+add_task(async function test_follow_cnames_same_response() {
+ await trrServer.registerDoHAnswers("something.foo", "A", {
+ answers: [
+ {
+ name: "something.foo",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "other.foo",
+ },
+ {
+ name: "other.foo",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "bla.foo",
+ },
+ {
+ name: "bla.foo",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "xyz.foo",
+ },
+ {
+ name: "xyz.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ let { inRecord } = await new TRRDNSListener("something.foo", {
+ expectedAnswer: "1.2.3.4",
+ flags: Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+ });
+ equal(inRecord.QueryInterface(Ci.nsIDNSAddrRecord).canonicalName, "xyz.foo");
+
+ await trrServer.registerDoHAnswers("a.foo", "A", {
+ answers: [
+ {
+ name: "a.foo",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "b.foo",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("b.foo", "A", {
+ answers: [
+ {
+ name: "b.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ],
+ });
+ await new TRRDNSListener("a.foo", { expectedAnswer: "2.3.4.5" });
+});
+
+add_task(async function test_cname_nodata() {
+ // Test that we don't needlessly follow cname chains when the RA flag is set
+ // on the response.
+
+ await trrServer.registerDoHAnswers("first.foo", "A", {
+ flags: 0x80,
+ answers: [
+ {
+ name: "first.foo",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "second.foo",
+ },
+ {
+ name: "second.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("first.foo", "AAAA", {
+ flags: 0x80,
+ answers: [
+ {
+ name: "first.foo",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "second.foo",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("first.foo", { expectedAnswer: "1.2.3.4" });
+ equal(await trrServer.requestCount("first.foo", "A"), 1);
+ equal(await trrServer.requestCount("first.foo", "AAAA"), 1);
+ equal(await trrServer.requestCount("second.foo", "A"), 0);
+ equal(await trrServer.requestCount("second.foo", "AAAA"), 0);
+
+ await trrServer.registerDoHAnswers("first.bar", "A", {
+ answers: [
+ {
+ name: "first.bar",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "second.bar",
+ },
+ {
+ name: "second.bar",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("first.bar", "AAAA", {
+ answers: [
+ {
+ name: "first.bar",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "second.bar",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("first.bar", { expectedAnswer: "1.2.3.4" });
+ equal(await trrServer.requestCount("first.bar", "A"), 1);
+ equal(await trrServer.requestCount("first.bar", "AAAA"), 1);
+ equal(await trrServer.requestCount("second.bar", "A"), 0); // addr included in first response
+ equal(await trrServer.requestCount("second.bar", "AAAA"), 1); // will follow cname because no flag is set
+
+ // Check that it also works for HTTPS records
+
+ await trrServer.registerDoHAnswers("first.bar", "HTTPS", {
+ flags: 0x80,
+ answers: [
+ {
+ name: "second.bar",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "no-default-alpn" },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: "1.2.3.4" },
+ { key: "echconfig", value: "123..." },
+ { key: "ipv6hint", value: "::1" },
+ ],
+ },
+ },
+ {
+ name: "first.bar",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "second.bar",
+ },
+ ],
+ });
+
+ let { inStatus } = await new TRRDNSListener("first.bar", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+ Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should work`);
+ equal(await trrServer.requestCount("first.bar", "HTTPS"), 1);
+ equal(await trrServer.requestCount("second.bar", "HTTPS"), 0);
+});
diff --git a/netwerk/test/unit/test_trr_confirmation.js b/netwerk/test/unit/test_trr_confirmation.js
new file mode 100644
index 0000000000..6635f71377
--- /dev/null
+++ b/netwerk/test/unit/test_trr_confirmation.js
@@ -0,0 +1,401 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+async function waitForConfirmationState(state, msToWait = 0) {
+ await TestUtils.waitForCondition(
+ () => Services.dns.currentTrrConfirmationState == state,
+ `Timed out waiting for ${state}. Currently ${Services.dns.currentTrrConfirmationState}`,
+ 1,
+ msToWait
+ );
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ state,
+ "expected confirmation state"
+ );
+}
+
+const CONFIRM_OFF = 0;
+const CONFIRM_TRYING_OK = 1;
+const CONFIRM_OK = 2;
+const CONFIRM_FAILED = 3;
+const CONFIRM_TRYING_FAILED = 4;
+const CONFIRM_DISABLED = 5;
+
+function setup() {
+ trr_test_setup();
+ Services.prefs.setBoolPref("network.trr.skip-check-for-blocked-host", true);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.trr.skip-check-for-blocked-host");
+});
+
+let trrServer = null;
+add_task(async function start_trr_server() {
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+
+ await trrServer.registerDoHAnswers(`faily.com`, "NS", {
+ answers: [
+ {
+ name: "faily.com",
+ ttl: 55,
+ type: "NS",
+ flush: false,
+ data: "ns.faily.com",
+ },
+ ],
+ });
+
+ for (let i = 0; i < 15; i++) {
+ await trrServer.registerDoHAnswers(`failing-domain${i}.faily.com`, "A", {
+ error: 600,
+ });
+ await trrServer.registerDoHAnswers(`failing-domain${i}.faily.com`, "AAAA", {
+ error: 600,
+ });
+ }
+});
+
+function trigger15Failures() {
+ // We need to clear the cache in case a previous call to this method
+ // put the results in the DNS cache.
+ Services.dns.clearCache(true);
+
+ let dnsRequests = [];
+ // There are actually two TRR requests sent for A and AAAA records, so doing
+ // DNS query 10 times should be enough to trigger confirmation process.
+ for (let i = 0; i < 10; i++) {
+ dnsRequests.push(
+ new TRRDNSListener(`failing-domain${i}.faily.com`, {
+ expectedAnswer: "127.0.0.1",
+ })
+ );
+ }
+
+ return Promise.all(dnsRequests);
+}
+
+async function registerNS(delay) {
+ return trrServer.registerDoHAnswers("confirm.example.com", "NS", {
+ answers: [
+ {
+ name: "confirm.example.com",
+ ttl: 55,
+ type: "NS",
+ flush: false,
+ data: "test.com",
+ },
+ ],
+ delay,
+ });
+}
+
+add_task(async function confirm_off() {
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OFF);
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRROFF);
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OFF);
+});
+
+add_task(async function confirm_disabled() {
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_DISABLED);
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_DISABLED);
+});
+
+add_task(async function confirm_ok() {
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+ await registerNS(0);
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await new TRRDNSListener("example.com", { expectedAnswer: "1.2.3.4" });
+ equal(await trrServer.requestCount("example.com", "A"), 1);
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+
+ await registerNS(500);
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await new Promise(resolve => do_timeout(100, resolve));
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Confirmation should still be pending"
+ );
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+});
+
+add_task(async function confirm_timeout() {
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OFF);
+ await registerNS(7000);
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await waitForConfirmationState(CONFIRM_FAILED, 7500);
+ // After the confirmation fails, a timer will periodically trigger a retry
+ // causing the state to go into CONFIRM_TRYING_FAILED.
+ await waitForConfirmationState(CONFIRM_TRYING_FAILED, 500);
+});
+
+add_task(async function confirm_fail_fast() {
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OFF);
+ await trrServer.registerDoHAnswers("confirm.example.com", "NS", {
+ error: 404,
+ });
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await waitForConfirmationState(CONFIRM_FAILED, 100);
+});
+
+add_task(async function multiple_failures() {
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OFF);
+
+ await registerNS(100);
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+ await registerNS(4000);
+ let failures = trigger15Failures();
+ await waitForConfirmationState(CONFIRM_TRYING_OK, 3000);
+ await failures;
+ // Check that failures during confirmation are ignored.
+ await trigger15Failures();
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await waitForConfirmationState(CONFIRM_OK, 4500);
+});
+
+add_task(async function test_connectivity_change() {
+ await registerNS(100);
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ let confirmationCount = await trrServer.requestCount(
+ "confirm.example.com",
+ "NS"
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+ equal(
+ await trrServer.requestCount("confirm.example.com", "NS"),
+ confirmationCount + 1
+ );
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity",
+ "clear"
+ );
+ // This means a CP check completed successfully. But no CP was previously
+ // detected, so this is mostly a no-op.
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OK);
+
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity",
+ "captive"
+ );
+ // This basically a successful CP login event. Wasn't captive before.
+ // Still treating as a no-op.
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OK);
+
+ // This makes the TRR service set mCaptiveIsPassed=false
+ Services.obs.notifyObservers(
+ null,
+ "captive-portal-login",
+ "{type: 'captive-portal-login', id: 0, url: 'http://localhost/'}"
+ );
+
+ await registerNS(500);
+ let failures = trigger15Failures();
+ // The failure should cause us to go into CONFIRM_TRYING_OK and do an NS req
+ await waitForConfirmationState(CONFIRM_TRYING_OK, 3000);
+ await failures;
+
+ // The notification sets mCaptiveIsPassed=true then triggers an entirely new
+ // confirmation.
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity",
+ "clear"
+ );
+ // The notification should cause us to send a new confirmation request
+ equal(
+ Services.dns.currentTrrConfirmationState,
+ CONFIRM_TRYING_OK,
+ "Should be CONFIRM_TRYING_OK"
+ );
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+ // two extra confirmation events should have been received by the server
+ equal(
+ await trrServer.requestCount("confirm.example.com", "NS"),
+ confirmationCount + 3
+ );
+});
+
+add_task(async function test_network_change() {
+ let confirmationCount = await trrServer.requestCount(
+ "confirm.example.com",
+ "NS"
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OK);
+
+ Services.obs.notifyObservers(null, "network:link-status-changed", "up");
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OK);
+ equal(
+ await trrServer.requestCount("confirm.example.com", "NS"),
+ confirmationCount
+ );
+
+ let failures = trigger15Failures();
+ // The failure should cause us to go into CONFIRM_TRYING_OK and do an NS req
+ await waitForConfirmationState(CONFIRM_TRYING_OK, 3000);
+ await failures;
+ // The network up event should reset the confirmation to TRYING_OK and do
+ // another NS req
+ Services.obs.notifyObservers(null, "network:link-status-changed", "up");
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_TRYING_OK);
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+ // two extra confirmation events should have been received by the server
+ equal(
+ await trrServer.requestCount("confirm.example.com", "NS"),
+ confirmationCount + 2
+ );
+});
+
+add_task(async function test_uri_pref_change() {
+ let confirmationCount = await trrServer.requestCount(
+ "confirm.example.com",
+ "NS"
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_OK);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query?changed`
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_TRYING_OK);
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+ equal(
+ await trrServer.requestCount("confirm.example.com", "NS"),
+ confirmationCount + 1
+ );
+});
+
+add_task(async function test_autodetected_uri() {
+ const defaultPrefBranch = Services.prefs.getDefaultBranch("");
+ let defaultURI = defaultPrefBranch.getCharPref(
+ "network.trr.default_provider_uri"
+ );
+ defaultPrefBranch.setCharPref(
+ "network.trr.default_provider_uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query?changed`
+ );
+ // For setDetectedTrrURI to work we must pretend we are using the default.
+ Services.prefs.clearUserPref("network.trr.uri");
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+ let confirmationCount = await trrServer.requestCount(
+ "confirm.example.com",
+ "NS"
+ );
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${trrServer.port()}/dns-query?changed2`
+ );
+ equal(Services.dns.currentTrrConfirmationState, CONFIRM_TRYING_OK);
+ await waitForConfirmationState(CONFIRM_OK, 1000);
+ equal(
+ await trrServer.requestCount("confirm.example.com", "NS"),
+ confirmationCount + 1
+ );
+
+ // reset the default URI
+ defaultPrefBranch.setCharPref("network.trr.default_provider_uri", defaultURI);
+});
diff --git a/netwerk/test/unit/test_trr_decoding.js b/netwerk/test/unit/test_trr_decoding.js
new file mode 100644
index 0000000000..a2b80ec773
--- /dev/null
+++ b/netwerk/test/unit/test_trr_decoding.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+let trrServer = null;
+add_setup(async function start_trr_server() {
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+});
+
+add_task(async function ignoreUnknownTypes() {
+ Services.dns.clearCache(true);
+ await trrServer.registerDoHAnswers("abc.def.ced.com", "A", {
+ answers: [
+ {
+ name: "abc.def.ced.com",
+ ttl: 55,
+ type: "DNAME",
+ flush: false,
+ data: "def.ced.com.test",
+ },
+ {
+ name: "abc.def.ced.com",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "abc.def.ced.com.test",
+ },
+ {
+ name: "abc.def.ced.com.test",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "3.3.3.3",
+ },
+ ],
+ });
+ await new TRRDNSListener("abc.def.ced.com", { expectedAnswer: "3.3.3.3" });
+});
diff --git a/netwerk/test/unit/test_trr_domain.js b/netwerk/test/unit/test_trr_domain.js
new file mode 100644
index 0000000000..b8b6c460eb
--- /dev/null
+++ b/netwerk/test/unit/test_trr_domain.js
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// This test simmulates intermittent native DNS functionality.
+// We verify that we don't use the negative DNS record for the DoH server.
+// The first resolve of foo.example.com fails, so we expect TRR not to work.
+// Immediately after the native DNS starts working, it should connect to the
+// TRR server and start working.
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+function setup() {
+ trr_test_setup();
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+}
+setup();
+
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ override.clearOverrides();
+});
+
+add_task(async function intermittent_dns_mode3() {
+ override.addIPOverride("foo.example.com", "N/A");
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ info(`port = ${trrServer.port()}\n`);
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ let { inStatus } = await new TRRDNSListener("example.com", {
+ expectedSuccess: false,
+ });
+ equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+ await trrServer.registerDoHAnswers("example.org", "A", {
+ answers: [
+ {
+ name: "example.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ override.addIPOverride("foo.example.com", "127.0.0.1");
+ await new TRRDNSListener("example.org", { expectedAnswer: "1.2.3.4" });
+ await trrServer.stop();
+});
+
+add_task(async function intermittent_dns_mode2() {
+ override.addIPOverride("foo.example.com", "N/A");
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ info(`port = ${trrServer.port()}\n`);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.1.1.1",
+ },
+ ],
+ });
+ override.addIPOverride("example.com", "2.2.2.2");
+ await new TRRDNSListener("example.com", {
+ expectedAnswer: "2.2.2.2",
+ });
+ await trrServer.registerDoHAnswers("example.org", "A", {
+ answers: [
+ {
+ name: "example.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ override.addIPOverride("example.org", "3.3.3.3");
+ override.addIPOverride("foo.example.com", "127.0.0.1");
+ await new TRRDNSListener("example.org", { expectedAnswer: "1.2.3.4" });
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_trr_enterprise_policy.js b/netwerk/test/unit/test_trr_enterprise_policy.js
new file mode 100644
index 0000000000..e96753d554
--- /dev/null
+++ b/netwerk/test/unit/test_trr_enterprise_policy.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+
+updateAppInfo({
+ name: "XPCShell",
+ ID: "xpcshell@tests.mozilla.org",
+ version: "48",
+ platformVersion: "48",
+});
+
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+
+// This initializes the policy engine for xpcshell tests
+let policies = Cc["@mozilla.org/enterprisepolicies;1"].getService(
+ Ci.nsIObserver
+);
+policies.observe(null, "policies-startup", null);
+
+add_task(async function test_enterprise_policy_unlocked() {
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: false,
+ ProviderURL: "https://example.org/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ },
+ },
+ });
+
+ equal(Services.prefs.getIntPref("network.trr.mode"), 5);
+ equal(Services.prefs.prefIsLocked("network.trr.mode"), false);
+ equal(
+ Services.prefs.getStringPref("network.trr.uri"),
+ "https://example.org/provider"
+ );
+ equal(Services.prefs.prefIsLocked("network.trr.uri"), false);
+ equal(
+ Services.prefs.getStringPref("network.trr.excluded-domains"),
+ "example.com,example.org"
+ );
+ equal(Services.prefs.prefIsLocked("network.trr.excluded-domains"), false);
+ equal(Services.dns.currentTrrMode, 5);
+ equal(Services.dns.currentTrrURI, "https://example.org/provider");
+ Services.dns.setDetectedTrrURI("https://autodetect.example.com/provider");
+ equal(Services.dns.currentTrrMode, 5);
+ equal(Services.dns.currentTrrURI, "https://example.org/provider");
+});
+
+add_task(async function test_enterprise_policy_locked() {
+ // Read dns.currentTrrMode to make DNS service initialized earlier.
+ info("Services.dns.currentTrrMode:" + Services.dns.currentTrrMode);
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: true,
+ ProviderURL: "https://example.com/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ Locked: true,
+ },
+ },
+ });
+
+ equal(Services.prefs.getIntPref("network.trr.mode"), 2);
+ equal(Services.prefs.prefIsLocked("network.trr.mode"), true);
+ equal(
+ Services.prefs.getStringPref("network.trr.uri"),
+ "https://example.com/provider"
+ );
+ equal(Services.prefs.prefIsLocked("network.trr.uri"), true);
+ equal(
+ Services.prefs.getStringPref("network.trr.excluded-domains"),
+ "example.com,example.org"
+ );
+ equal(Services.prefs.prefIsLocked("network.trr.excluded-domains"), true);
+ equal(Services.dns.currentTrrMode, 2);
+ equal(Services.dns.currentTrrURI, "https://example.com/provider");
+ Services.dns.setDetectedTrrURI("https://autodetect.example.com/provider");
+ equal(Services.dns.currentTrrURI, "https://example.com/provider");
+});
diff --git a/netwerk/test/unit/test_trr_extended_error.js b/netwerk/test/unit/test_trr_extended_error.js
new file mode 100644
index 0000000000..68606c93f8
--- /dev/null
+++ b/netwerk/test/unit/test_trr_extended_error.js
@@ -0,0 +1,319 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish));
+ });
+}
+
+let trrServer;
+add_task(async function setup() {
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+ let chan = makeChan(`https://localhost:${trrServer.port()}/test?bla=some`);
+ let [, resp] = await channelOpenPromise(chan);
+ equal(resp, "<h1> 404 Path not found: /test</h1>");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+});
+
+add_task(async function test_extended_error_1() {
+ await trrServer.registerDoHAnswers("something.foo", "A", {
+ answers: [
+ {
+ name: "something.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("something.foo", { expectedAnswer: "1.2.3.4" });
+
+ await trrServer.registerDoHAnswers("a.foo", "A", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "DNSSec bogus",
+ },
+ ],
+ },
+ ],
+ flags: 2, // SERVFAIL
+ });
+
+ // Check that we don't fall back to DNS
+ let { inStatus } = await new TRRDNSListener("a.foo", {
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
+
+add_task(async function test_extended_error_filtered() {
+ await trrServer.registerDoHAnswers("b.foo", "A", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ });
+
+ // Check that we don't fall back to DNS
+ let { inStatus } = await new TRRDNSListener("b.foo", {
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
+
+add_task(async function test_extended_error_not_ready() {
+ await trrServer.registerDoHAnswers("c.foo", "A", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 14, // Not ready
+ text: "Not ready",
+ },
+ ],
+ },
+ ],
+ });
+
+ // For this code it's OK to fallback
+ await new TRRDNSListener("c.foo", { expectedAnswer: "127.0.0.1" });
+});
+
+add_task(async function ipv6_answer_and_delayed_ipv4_error() {
+ // AAAA comes back immediately.
+ // A EDNS_ERROR comes back later, with a delay
+ await trrServer.registerDoHAnswers("delay1.com", "AAAA", {
+ answers: [
+ {
+ name: "delay1.com",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::a:b:c:d",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("delay1.com", "A", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ delay: 200,
+ });
+
+ // Check that we don't fall back to DNS
+ await new TRRDNSListener("delay1.com", { expectedAnswer: "::a:b:c:d" });
+});
+
+add_task(async function ipv4_error_and_delayed_ipv6_answer() {
+ // AAAA comes back immediately delay
+ // A EDNS_ERROR comes back immediately
+ await trrServer.registerDoHAnswers("delay2.com", "AAAA", {
+ answers: [
+ {
+ name: "delay2.com",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::a:b:c:d",
+ },
+ ],
+ delay: 200,
+ });
+ await trrServer.registerDoHAnswers("delay2.com", "A", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ });
+
+ // Check that we don't fall back to DNS
+ await new TRRDNSListener("delay2.com", { expectedAnswer: "::a:b:c:d" });
+});
+
+add_task(async function ipv4_answer_and_delayed_ipv6_error() {
+ // A comes back immediately.
+ // AAAA EDNS_ERROR comes back later, with a delay
+ await trrServer.registerDoHAnswers("delay3.com", "A", {
+ answers: [
+ {
+ name: "delay3.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("delay3.com", "AAAA", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ delay: 200,
+ });
+
+ // Check that we don't fall back to DNS
+ await new TRRDNSListener("delay3.com", { expectedAnswer: "1.2.3.4" });
+});
+
+add_task(async function delayed_ipv4_answer_and_ipv6_error() {
+ // A comes back with delay.
+ // AAAA EDNS_ERROR comes immediately
+ await trrServer.registerDoHAnswers("delay4.com", "A", {
+ answers: [
+ {
+ name: "delay4.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ delay: 200,
+ });
+ await trrServer.registerDoHAnswers("delay4.com", "AAAA", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ });
+
+ // Check that we don't fall back to DNS
+ await new TRRDNSListener("delay4.com", { expectedAnswer: "1.2.3.4" });
+});
+
+add_task(async function test_only_ipv4_extended_error() {
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ await trrServer.registerDoHAnswers("only.com", "A", {
+ answers: [],
+ additionals: [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ });
+ let { inStatus } = await new TRRDNSListener("only.com", {
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
diff --git a/netwerk/test/unit/test_trr_https_fallback.js b/netwerk/test/unit/test_trr_https_fallback.js
new file mode 100644
index 0000000000..23c5adedaa
--- /dev/null
+++ b/netwerk/test/unit/test_trr_https_fallback.js
@@ -0,0 +1,1105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+let h2Port;
+let h3Port;
+let h3NoResponsePort;
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+add_setup(async function setup() {
+ trr_test_setup();
+
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ h3NoResponsePort = Services.env.get("MOZHTTP3_PORT_NO_RESPONSE");
+ Assert.notEqual(h3NoResponsePort, null);
+ Assert.notEqual(h3NoResponsePort, "");
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed"
+ );
+ Services.prefs.clearUserPref("network.dns.httpssvc.reset_exclustion_list");
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout"
+ );
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+// Test if we can fallback to the last record sucessfully.
+add_task(async function testFallbackToTheLastRecord() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ // Only the last record is valid to use.
+ await trrServer.registerDoHAnswers("test.fallback.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fallback1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "123..." },
+ ],
+ },
+ },
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 4,
+ name: "foo.example.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.fallback3.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.fallback2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.fallback.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.fallback.com:${h2Port}/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ await trrServer.stop();
+});
+
+add_task(async function testFallbackToTheOrigin() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setBoolPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed",
+ true
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ // All records are not able to use to connect, so we fallback to the origin
+ // one.
+ await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.foo1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "123..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.foo3.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.foo2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.foo.com", "A", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.foo.com:${h2Port}/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ await trrServer.stop();
+});
+
+// Test when all records are failed and network.dns.echconfig.fallback_to_origin
+// is false. In this case, the connection is always failed.
+add_task(async function testAllRecordsFailed() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed",
+ false
+ );
+
+ await trrServer.registerDoHAnswers("test.bar.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.bar1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "123..." },
+ ],
+ },
+ },
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.bar3.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.bar2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.bar.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ // This channel should be failed.
+ let chan = makeChan(`https://test.bar.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.stop();
+});
+
+// Test when all records have no echConfig, we directly fallback to the origin
+// one.
+add_task(async function testFallbackToTheOrigin2() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.example.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.example1.com",
+ values: [{ key: "alpn", value: ["h2", "h3-26"] }],
+ },
+ },
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.example3.com",
+ values: [{ key: "alpn", value: ["h2", "h3-26"] }],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.example.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("test.example.com", "A", {
+ answers: [
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ chan = makeChan(`https://test.example.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan);
+
+ await trrServer.stop();
+});
+
+// Test when some records have echConfig and some not, we directly fallback to
+// the origin one.
+add_task(async function testFallbackToTheOrigin3() {
+ Services.dns.clearCache(true);
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("vulnerable.com", "A", {
+ answers: [
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("vulnerable.com", "HTTPS", {
+ answers: [
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "vulnerable1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "vulnerable2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "vulnerable3.com",
+ values: [{ key: "alpn", value: ["h2", "h3-26"] }],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("vulnerable.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://vulnerable.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan);
+
+ await trrServer.stop();
+});
+
+add_task(async function testResetExclusionList() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref(
+ "network.dns.httpssvc.reset_exclustion_list",
+ false
+ );
+
+ await trrServer.registerDoHAnswers("test.reset.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.reset.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.reset1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.reset.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.reset2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.reset.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ // After this request, test.reset1.com and test.reset2.com should be both in
+ // the exclusion list.
+ let chan = makeChan(`https://test.reset.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ // This request should be also failed, because all records are excluded.
+ chan = makeChan(`https://test.reset.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("test.reset1.com", "A", {
+ answers: [
+ {
+ name: "test.reset1.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ Services.prefs.setBoolPref(
+ "network.dns.httpssvc.reset_exclustion_list",
+ true
+ );
+
+ // After enable network.dns.httpssvc.reset_exclustion_list and register
+ // A record for test.reset1.com, this request should be succeeded.
+ chan = makeChan(`https://test.reset.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan);
+
+ await trrServer.stop();
+});
+
+// Simply test if we can connect to H3 server.
+add_task(async function testH3Connection() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 100
+ );
+
+ await trrServer.registerDoHAnswers("test.h3.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.h3.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("www.h3.com", "A", {
+ answers: [
+ {
+ name: "www.h3.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.h3.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.h3.com`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3-29");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h3Port);
+
+ await trrServer.stop();
+});
+
+add_task(async function testFastfallbackToH2() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ // Use a short timeout to make sure the fast fallback timer will be triggered.
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 1
+ );
+ Services.prefs.setCharPref(
+ "network.dns.localDomains",
+ "test.fastfallback1.com"
+ );
+
+ await trrServer.registerDoHAnswers("test.fastfallback.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fastfallback1.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3NoResponsePort },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.fastfallback2.com",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fastfallback2.com", "A", {
+ answers: [
+ {
+ name: "test.fastfallback2.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.fastfallback.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.fastfallback.com/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ // Use a longer timeout to test the case that the timer is canceled.
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 5000
+ );
+
+ chan = makeChan(`https://test.fastfallback.com/server-timing`);
+ [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ await trrServer.stop();
+});
+
+// Test when we fail to establish H3 connection.
+add_task(async function testFailedH3Connection() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ await trrServer.registerDoHAnswers("test.h3.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.h3.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.h3.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.h3.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.stop();
+});
+
+// Test we don't use the service mode record whose domain is in
+// http3 excluded list.
+add_task(async function testHttp3ExcludedList() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "www.h3_fail.org;h3-29=:" + h3Port
+ );
+
+ // This will fail because there is no address record for www.h3_fail.org.
+ let chan = makeChan(`https://www.h3_fail.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ // Now www.h3_fail.org should be already excluded, so the second record
+ // foo.example.com will be selected.
+ await trrServer.registerDoHAnswers("test.h3_excluded.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.h3_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3_fail.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ {
+ name: "test.h3_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "foo.example.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.h3_excluded.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ chan = makeChan(`https://test.h3_excluded.org`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3-29");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h3Port);
+
+ await trrServer.stop();
+});
+
+add_task(async function testAllRecordsInHttp3ExcludedList() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "www.h3_fail1.org;h3-29=:" + h3Port
+ );
+
+ await trrServer.registerDoHAnswers("www.h3_all_excluded.org", "A", {
+ answers: [
+ {
+ name: "www.h3_all_excluded.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ // Test we can connect to www.h3_all_excluded.org sucessfully.
+ let chan = makeChan(
+ `https://www.h3_all_excluded.org:${h2Port}/server-timing`
+ );
+
+ let [req] = await channelOpenPromise(chan);
+
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ // This will fail because there is no address record for www.h3_fail1.org.
+ chan = makeChan(`https://www.h3_fail1.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "www.h3_fail2.org;h3-29=:" + h3Port
+ );
+
+ // This will fail because there is no address record for www.h3_fail2.org.
+ chan = makeChan(`https://www.h3_fail2.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("www.h3_all_excluded.org", "HTTPS", {
+ answers: [
+ {
+ name: "www.h3_all_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3_fail1.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "www.h3_all_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "www.h3_fail2.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("www.h3_all_excluded.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+
+ // All HTTPS RRs are in http3 excluded list and all records are failed to
+ // connect, so don't fallback to the origin one.
+ chan = makeChan(`https://www.h3_all_excluded.org:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("www.h3_fail1.org", "A", {
+ answers: [
+ {
+ name: "www.h3_fail1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ // The the case that when all records are in http3 excluded list, we still
+ // give the first record one more shot.
+ chan = makeChan(`https://www.h3_all_excluded.org`);
+ [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3-29");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h3Port);
+
+ await trrServer.stop();
+});
+
+WebSocketListener.prototype = {
+ onAcknowledge(aContext, aSize) {},
+ onBinaryMessageAvailable(aContext, aMsg) {},
+ onMessageAvailable(aContext, aMsg) {},
+ onServerClose(aContext, aCode, aReason) {},
+ onStart(aContext) {
+ this.finish();
+ },
+ onStop(aContext, aStatusCode) {},
+};
+
+add_task(async function testUpgradeNotUsingHTTPSRR() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.ws.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.ws.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.ws1.com",
+ values: [{ key: "port", value: ["8888"] }],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.ws.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ await trrServer.registerDoHAnswers("test.ws.com", "A", {
+ answers: [
+ {
+ name: "test.ws.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let wssUri = "wss://test.ws.com:" + h2Port + "/websocket";
+ let chan = Cc["@mozilla.org/network/protocol;1?name=wss"].createInstance(
+ Ci.nsIWebSocketChannel
+ );
+ chan.initLoadInfo(
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_DOCUMENT
+ );
+
+ var uri = Services.io.newURI(wssUri);
+ var wsListener = new WebSocketListener();
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ await new Promise(resolve => {
+ wsListener.finish = resolve;
+ chan.asyncOpen(uri, wssUri, {}, 0, wsListener, null);
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ });
+
+ await trrServer.stop();
+});
+
+// Test if we fallback to h2 with echConfig.
+add_task(async function testFallbackToH2WithEchConfig() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ await trrServer.registerDoHAnswers("test.fallback.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.fallback.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fallback.org",
+ values: [
+ { key: "alpn", value: ["h2", "h3-29"] },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fallback.org", "A", {
+ answers: [
+ {
+ name: "test.fallback.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.fallback.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ await new TRRDNSListener("test.fallback.org", "127.0.0.1");
+
+ let chan = makeChan(`https://test.fallback.org/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_trr_httpssvc.js b/netwerk/test/unit/test_trr_httpssvc.js
new file mode 100644
index 0000000000..6c1db447a8
--- /dev/null
+++ b/netwerk/test/unit/test_trr_httpssvc.js
@@ -0,0 +1,728 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+let h2Port;
+let trrServer;
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+add_setup(async function setup() {
+ if (inChildProcess()) {
+ return;
+ }
+
+ trr_test_setup();
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
+ await trrServer.stop();
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+});
+
+add_task(async function testHTTPSSVC() {
+ // use the h2 server as DOH provider
+ if (!inChildProcess()) {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc"
+ );
+ }
+
+ let { inRecord } = await new TRRDNSListener("test.httpssvc.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "h3pool");
+ Assert.equal(answer[0].values.length, 7);
+ Assert.deepEqual(
+ answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
+ ["h2", "h3"],
+ "got correct answer"
+ );
+ Assert.ok(
+ answer[0].values[1].QueryInterface(Ci.nsISVCParamNoDefaultAlpn),
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[2].QueryInterface(Ci.nsISVCParamPort).port,
+ 8888,
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[3].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
+ .address,
+ "1.2.3.4",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[4].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
+ "123...",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[5].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
+ .address,
+ "::1",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[0].values[6].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
+ "456...",
+ "got correct answer"
+ );
+ Assert.equal(answer[1].priority, 2);
+ Assert.equal(answer[1].name, "test.httpssvc.com");
+ Assert.equal(answer[1].values.length, 5);
+ Assert.deepEqual(
+ answer[1].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
+ ["h2"],
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
+ .address,
+ "1.2.3.4",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[1]
+ .address,
+ "5.6.7.8",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[2].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
+ "abc...",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
+ .address,
+ "::1",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[1]
+ .address,
+ "fe80::794f:6d2c:3d5e:7836",
+ "got correct answer"
+ );
+ Assert.equal(
+ answer[1].values[4].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
+ "def...",
+ "got correct answer"
+ );
+ Assert.equal(answer[2].priority, 3);
+ Assert.equal(answer[2].name, "hello");
+ Assert.equal(answer[2].values.length, 0);
+});
+
+add_task(async function test_aliasform() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+
+ if (inChildProcess()) {
+ do_send_remote_message("mode3-port", trrServer.port());
+ await do_await_remote_message("mode3-port-done");
+ } else {
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ }
+
+ // Make sure that HTTPS AliasForm is only treated as a CNAME for HTTPS requests
+ await trrServer.registerDoHAnswers("test1.com", "A", {
+ answers: [
+ {
+ name: "test1.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "something1.com",
+ values: [],
+ },
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("something1.com", "A", {
+ answers: [
+ {
+ name: "something1.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+
+ {
+ let { inStatus } = await new TRRDNSListener("test1.com", {
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ }
+
+ // Test that HTTPS priority = 0 (AliasForm) behaves like a CNAME
+ await trrServer.registerDoHAnswers("test.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "something.com",
+ values: [],
+ },
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("something.com", "HTTPS", {
+ answers: [
+ {
+ name: "something.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ],
+ });
+
+ {
+ let { inStatus, inRecord } = await new TRRDNSListener("test.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ });
+ Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should succeed`);
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "h3pool");
+ }
+
+ // Test a chain of HTTPSSVC AliasForm and CNAMEs
+ await trrServer.registerDoHAnswers("x.com", "HTTPS", {
+ answers: [
+ {
+ name: "x.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "y.com",
+ values: [],
+ },
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("y.com", "HTTPS", {
+ answers: [
+ {
+ name: "y.com",
+ type: "CNAME",
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: "z.com",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("z.com", "HTTPS", {
+ answers: [
+ {
+ name: "z.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "target.com",
+ values: [],
+ },
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("target.com", "HTTPS", {
+ answers: [
+ {
+ name: "target.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ],
+ });
+
+ let { inStatus, inRecord } = await new TRRDNSListener("x.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ });
+ Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should succeed`);
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "h3pool");
+
+ // We get a ServiceForm instead of a A answer, CNAME or AliasForm
+ await trrServer.registerDoHAnswers("no-ip-host.com", "A", {
+ answers: [
+ {
+ name: "no-ip-host.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "no-default-alpn" },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: "1.2.3.4" },
+ { key: "echconfig", value: "123..." },
+ { key: "ipv6hint", value: "::1" },
+ ],
+ },
+ },
+ ],
+ });
+
+ ({ inStatus } = await new TRRDNSListener("no-ip-host.com", {
+ expectedSuccess: false,
+ }));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ // Test CNAME/AliasForm loop
+ await trrServer.registerDoHAnswers("loop.com", "HTTPS", {
+ answers: [
+ {
+ name: "loop.com",
+ type: "CNAME",
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: "loop2.com",
+ },
+ ],
+ });
+ await trrServer.registerDoHAnswers("loop2.com", "HTTPS", {
+ answers: [
+ {
+ name: "loop2.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "loop.com",
+ values: [],
+ },
+ },
+ ],
+ });
+
+ // Make sure these are the first requests
+ Assert.equal(await trrServer.requestCount("loop.com", "HTTPS"), 0);
+ Assert.equal(await trrServer.requestCount("loop2.com", "HTTPS"), 0);
+
+ ({ inStatus } = await new TRRDNSListener("loop.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ }));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ // Make sure the error was actually triggered by a loop.
+ Assert.greater(await trrServer.requestCount("loop.com", "HTTPS"), 2);
+ Assert.greater(await trrServer.requestCount("loop2.com", "HTTPS"), 2);
+
+ // Alias form for .
+ await trrServer.registerDoHAnswers("empty.com", "A", {
+ answers: [
+ {
+ name: "empty.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "", // This is not allowed
+ values: [],
+ },
+ },
+ ],
+ });
+
+ ({ inStatus } = await new TRRDNSListener("empty.com", {
+ expectedSuccess: false,
+ }));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ // We should ignore ServiceForm if an AliasForm record is also present
+ await trrServer.registerDoHAnswers("multi.com", "HTTPS", {
+ answers: [
+ {
+ name: "multi.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "no-default-alpn" },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: "1.2.3.4" },
+ { key: "echconfig", value: "123..." },
+ { key: "ipv6hint", value: "::1" },
+ ],
+ },
+ },
+ {
+ name: "multi.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "example.com",
+ values: [],
+ },
+ },
+ ],
+ });
+
+ let { inStatus: inStatus2 } = await new TRRDNSListener("multi.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus2),
+ `${inStatus2} should be an error code`
+ );
+
+ // the svcparam keys are in reverse order
+ await trrServer.registerDoHAnswers("order.com", "HTTPS", {
+ answers: [
+ {
+ name: "order.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ { key: "ipv6hint", value: "::1" },
+ { key: "echconfig", value: "123..." },
+ { key: "ipv4hint", value: "1.2.3.4" },
+ { key: "port", value: 8888 },
+ { key: "no-default-alpn" },
+ { key: "alpn", value: ["h2", "h3"] },
+ ],
+ },
+ },
+ ],
+ });
+
+ ({ inStatus: inStatus2 } = await new TRRDNSListener("order.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ }));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus2),
+ `${inStatus2} should be an error code`
+ );
+
+ // duplicate svcparam keys
+ await trrServer.registerDoHAnswers("duplicate.com", "HTTPS", {
+ answers: [
+ {
+ name: "duplicate.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "alpn", value: ["h2", "h3", "h4"] },
+ ],
+ },
+ },
+ ],
+ });
+
+ ({ inStatus: inStatus2 } = await new TRRDNSListener("duplicate.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ }));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus2),
+ `${inStatus2} should be an error code`
+ );
+
+ // mandatory svcparam
+ await trrServer.registerDoHAnswers("mandatory.com", "HTTPS", {
+ answers: [
+ {
+ name: "mandatory.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ { key: "mandatory", value: ["key100"] },
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "key100" },
+ ],
+ },
+ },
+ ],
+ });
+
+ ({ inStatus: inStatus2 } = await new TRRDNSListener("mandatory.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ }));
+ Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
+
+ // mandatory svcparam
+ await trrServer.registerDoHAnswers("mandatory2.com", "HTTPS", {
+ answers: [
+ {
+ name: "mandatory2.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ {
+ key: "mandatory",
+ value: [
+ "alpn",
+ "no-default-alpn",
+ "port",
+ "ipv4hint",
+ "echconfig",
+ "ipv6hint",
+ ],
+ },
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "no-default-alpn" },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: "1.2.3.4" },
+ { key: "echconfig", value: "123..." },
+ { key: "ipv6hint", value: "::1" },
+ ],
+ },
+ },
+ ],
+ });
+
+ ({ inStatus: inStatus2 } = await new TRRDNSListener("mandatory2.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+
+ Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should succeed`);
+
+ // alias-mode with . targetName
+ await trrServer.registerDoHAnswers("no-alias.com", "HTTPS", {
+ answers: [
+ {
+ name: "no-alias.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: ".",
+ values: [],
+ },
+ },
+ ],
+ });
+
+ ({ inStatus: inStatus2 } = await new TRRDNSListener("no-alias.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ }));
+
+ Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
+
+ // service-mode with . targetName
+ await trrServer.registerDoHAnswers("service.com", "HTTPS", {
+ answers: [
+ {
+ name: "service.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: ".",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ],
+ });
+
+ ({ inRecord, inStatus: inStatus2 } = await new TRRDNSListener("service.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+ Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should work`);
+ answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "service.com");
+});
+
+add_task(async function testNegativeResponse() {
+ let { inStatus } = await new TRRDNSListener("negative_test.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ await trrServer.registerDoHAnswers("negative_test.com", "HTTPS", {
+ answers: [
+ {
+ name: "negative_test.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "negative_test.com",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ],
+ });
+
+ // Should still be failed because a negative response is from DNS cache.
+ ({ inStatus } = await new TRRDNSListener("negative_test.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess: false,
+ }));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ if (inChildProcess()) {
+ do_send_remote_message("clearCache");
+ await do_await_remote_message("clearCache-done");
+ } else {
+ Services.dns.clearCache(true);
+ }
+
+ let inRecord;
+ ({ inRecord, inStatus } = await new TRRDNSListener("negative_test.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ }));
+ Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should work`);
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "negative_test.com");
+});
+
+add_task(async function testPortPrefixedName() {
+ if (inChildProcess()) {
+ do_send_remote_message("set-port-prefixed-pref");
+ await do_await_remote_message("set-port-prefixed-pref-done");
+ } else {
+ Services.prefs.setBoolPref(
+ "network.dns.port_prefixed_qname_https_rr",
+ true
+ );
+ }
+
+ await trrServer.registerDoHAnswers(
+ "_4433._https.port_prefix.test.com",
+ "HTTPS",
+ {
+ answers: [
+ {
+ name: "_4433._https.port_prefix.test.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "port_prefix.test1.com",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ],
+ }
+ );
+
+ let { inRecord, inStatus } = await new TRRDNSListener(
+ "port_prefix.test.com",
+ {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ port: 4433,
+ }
+ );
+ Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should work`);
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "port_prefix.test1.com");
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_trr_nat64.js b/netwerk/test/unit/test_trr_nat64.js
new file mode 100644
index 0000000000..aa41f2a611
--- /dev/null
+++ b/netwerk/test/unit/test_trr_nat64.js
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.connectivity-service.nat64-prefix");
+ override.clearOverrides();
+ trr_clear_prefs();
+});
+
+/**
+ * Waits for an observer notification to fire.
+ *
+ * @param {String} topic The notification topic.
+ * @returns {Promise} A promise that fulfills when the notification is fired.
+ */
+function promiseObserverNotification(topic, matchFunc) {
+ return new Promise((resolve, reject) => {
+ Services.obs.addObserver(function observe(subject, topic1, data) {
+ let matches = typeof matchFunc != "function" || matchFunc(subject, data);
+ if (!matches) {
+ return;
+ }
+ Services.obs.removeObserver(observe, topic);
+ resolve({ subject, data });
+ }, topic);
+ });
+}
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish));
+ });
+}
+
+add_task(async function test_add_nat64_prefix_to_trr() {
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+ let chan = makeChan(`https://localhost:${trrServer.port()}/test?bla=some`);
+ let [, resp] = await channelOpenPromise(chan);
+ equal(resp, "<h1> 404 Path not found: /test</h1>");
+ Services.dns.clearCache(true);
+ override.addIPOverride("ipv4only.arpa", "fe80::9b2b:c000:00aa");
+ Services.prefs.setCharPref(
+ "network.connectivity-service.nat64-prefix",
+ "ae80::3b1b:c343:1133"
+ );
+
+ let topic = "network:connectivity-service:dns-checks-complete";
+ if (mozinfo.socketprocess_networking) {
+ topic += "-from-socket-process";
+ }
+ let notification = promiseObserverNotification(topic);
+ Services.obs.notifyObservers(null, "network:captive-portal-connectivity");
+ await notification;
+
+ Services.prefs.setIntPref("network.trr.mode", 2);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("xyz.foo", "A", {
+ answers: [
+ {
+ name: "xyz.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ let { inRecord } = await new TRRDNSListener("xyz.foo", {
+ expectedSuccess: false,
+ });
+
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ Assert.equal(
+ inRecord.getNextAddrAsString(),
+ "1.2.3.4",
+ `Checking that native IPv4 addresses have higher priority.`
+ );
+
+ Assert.equal(
+ inRecord.getNextAddrAsString(),
+ "ae80::3b1b:102:304",
+ `Checking the manually entered NAT64-prefixed address is in the middle.`
+ );
+
+ Assert.equal(
+ inRecord.getNextAddrAsString(),
+ "fe80::9b2b:102:304",
+ `Checking that the NAT64-prefixed address is appended at the back.`
+ );
+
+ await trrServer.stop();
+});
diff --git a/netwerk/test/unit/test_trr_noPrefetch.js b/netwerk/test/unit/test_trr_noPrefetch.js
new file mode 100644
index 0000000000..ca3188a370
--- /dev/null
+++ b/netwerk/test/unit/test_trr_noPrefetch.js
@@ -0,0 +1,174 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let trrServer = null;
+add_setup(async function setup() {
+ if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ return;
+ }
+
+ trr_test_setup();
+ Services.prefs.setBoolPref("network.dns.disablePrefetch", true);
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.disablePrefetch");
+ trr_clear_prefs();
+ });
+
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ // We need to define both A and AAAA responses, otherwise
+ // we might race and pick up the skip reason for the other request.
+ await trrServer.registerDoHAnswers(`myfoo.test`, "A", {
+ answers: [],
+ });
+ await trrServer.registerDoHAnswers(`myfoo.test`, "AAAA", {
+ answers: [],
+ });
+
+ // myfoo2.test will return sever error as it's not defined
+
+ // return nxdomain for this one
+ await trrServer.registerDoHAnswers(`myfoo3.test`, "A", {
+ flags: 0x03,
+ answers: [],
+ });
+ await trrServer.registerDoHAnswers(`myfoo3.test`, "AAAA", {
+ flags: 0x03,
+ answers: [],
+ });
+
+ await trrServer.registerDoHAnswers(`alt1.example.com`, "A", {
+ answers: [
+ {
+ name: "alt1.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+});
+
+add_task(async function test_failure() {
+ let req = await new Promise(resolve => {
+ let chan = NetUtil.newChannel({
+ uri: `http://myfoo.test/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ });
+
+ equal(req.status, Cr.NS_ERROR_UNKNOWN_HOST);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).effectiveTRRMode,
+ Ci.nsIRequest.TRR_ONLY_MODE
+ );
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).trrSkipReason,
+ Ci.nsITRRSkipReason.TRR_NO_ANSWERS
+ );
+
+ req = await new Promise(resolve => {
+ let chan = NetUtil.newChannel({
+ uri: `http://myfoo2.test/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ });
+
+ equal(req.status, Cr.NS_ERROR_UNKNOWN_HOST);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).effectiveTRRMode,
+ Ci.nsIRequest.TRR_ONLY_MODE
+ );
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).trrSkipReason,
+ Ci.nsITRRSkipReason.TRR_RCODE_FAIL
+ );
+
+ req = await new Promise(resolve => {
+ let chan = NetUtil.newChannel({
+ uri: `http://myfoo3.test/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE));
+ });
+
+ equal(req.status, Cr.NS_ERROR_UNKNOWN_HOST);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).effectiveTRRMode,
+ Ci.nsIRequest.TRR_ONLY_MODE
+ );
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).trrSkipReason,
+ Ci.nsITRRSkipReason.TRR_NXDOMAIN
+ );
+});
+
+add_task(async function test_success() {
+ let httpServer = new NodeHTTP2Server();
+ await httpServer.start();
+ await httpServer.registerPathHandler("/", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("done");
+ });
+ registerCleanupFunction(async () => {
+ await httpServer.stop();
+ });
+
+ let req = await new Promise(resolve => {
+ let chan = NetUtil.newChannel({
+ uri: `https://alt1.example.com:${httpServer.port()}/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+
+ equal(req.status, Cr.NS_OK);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).effectiveTRRMode,
+ Ci.nsIRequest.TRR_ONLY_MODE
+ );
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).trrSkipReason,
+ Ci.nsITRRSkipReason.TRR_OK
+ );
+
+ // Another request to check connection reuse
+ req = await new Promise(resolve => {
+ let chan = NetUtil.newChannel({
+ uri: `https://alt1.example.com:${httpServer.port()}/second`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+ });
+
+ equal(req.status, Cr.NS_OK);
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).effectiveTRRMode,
+ Ci.nsIRequest.TRR_ONLY_MODE
+ );
+ equal(
+ req.QueryInterface(Ci.nsIHttpChannelInternal).trrSkipReason,
+ Ci.nsITRRSkipReason.TRR_OK
+ );
+});
diff --git a/netwerk/test/unit/test_trr_proxy.js b/netwerk/test/unit/test_trr_proxy.js
new file mode 100644
index 0000000000..962971d103
--- /dev/null
+++ b/netwerk/test/unit/test_trr_proxy.js
@@ -0,0 +1,156 @@
+// These are globlas defined for proxy servers, in ProxyAutoConfig.cpp. See
+// PACGlobalFunctions
+/* globals dnsResolve, alert */
+
+/* This test checks that using a PAC script still works when TRR is on.
+ Steps:
+ - Set the pac script
+ - Do a request to make sure that the script is loaded
+ - Set the TRR mode
+ - Make a request that would lead to running the PAC script
+ We run these steps for TRR mode 2 and 3, and with fetchOffMainThread = true/false
+*/
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.parse_pac_on_socket_process");
+ trr_clear_prefs();
+});
+
+function FindProxyForURL(url, host) {
+ alert(`PAC resolving: ${host}`);
+ alert(dnsResolve(host));
+ return "DIRECT";
+}
+
+ChromeUtils.defineLazyGetter(this, "systemSettings", function () {
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsISystemProxySettings"]),
+
+ mainThreadOnly: true,
+ PACURI: `data:application/x-ns-proxy-autoconfig;charset=utf-8,${encodeURIComponent(
+ FindProxyForURL.toString()
+ )}`,
+ getProxyForURI(aURI) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ };
+});
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+async function do_test_pac_dnsResolve() {
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ Services.console.reset();
+ // Create a console listener.
+ let consolePromise = new Promise(resolve => {
+ let listener = {
+ observe(message) {
+ // Ignore unexpected messages.
+ if (!(message instanceof Ci.nsIConsoleMessage)) {
+ return;
+ }
+
+ if (message.message.includes("PAC file installed from")) {
+ Services.console.unregisterListener(listener);
+ resolve();
+ }
+ },
+ };
+
+ Services.console.registerListener(listener);
+ });
+
+ MockRegistrar.register(
+ "@mozilla.org/system-proxy-settings;1",
+ systemSettings
+ );
+ Services.prefs.setIntPref(
+ "network.proxy.type",
+ Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM
+ );
+
+ let httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ let content = "ok";
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start(-1);
+
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", false);
+ Services.prefs.setIntPref("network.trr.mode", 0); // Disable TRR until the PAC is loaded
+ override.addIPOverride("example.org", "127.0.0.1");
+ let chan = NetUtil.newChannel({
+ uri: `http://example.org:${httpserv.identity.primaryPort}/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ await consolePromise;
+
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ override.addIPOverride("foo.example.com", "127.0.0.1");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=127.0.0.1`
+ );
+
+ trr_test_setup();
+
+ async function test_with(DOMAIN, trrMode, fetchOffMainThread) {
+ Services.prefs.setIntPref("network.trr.mode", trrMode); // TRR first
+ Services.prefs.setBoolPref(
+ "network.trr.fetch_off_main_thread",
+ fetchOffMainThread
+ );
+ override.addIPOverride(DOMAIN, "127.0.0.1");
+
+ chan = NetUtil.newChannel({
+ uri: `http://${DOMAIN}:${httpserv.identity.primaryPort}/`,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+
+ await override.clearHostOverride(DOMAIN);
+ }
+
+ await test_with("test1.com", 2, true);
+ await test_with("test2.com", 3, true);
+ await test_with("test3.com", 2, false);
+ await test_with("test4.com", 3, false);
+ await httpserv.stop();
+}
+
+add_task(async function test_pac_dnsResolve() {
+ Services.prefs.setBoolPref(
+ "network.proxy.parse_pac_on_socket_process",
+ false
+ );
+
+ await do_test_pac_dnsResolve();
+
+ if (mozinfo.socketprocess_networking) {
+ info("run test again");
+ Services.prefs.clearUserPref("network.proxy.type");
+ trr_clear_prefs();
+ Services.prefs.setBoolPref(
+ "network.proxy.parse_pac_on_socket_process",
+ true
+ );
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ Services.prefs.setIntPref("network.proxy.type", 0);
+ await do_test_pac_dnsResolve();
+ }
+});
diff --git a/netwerk/test/unit/test_trr_proxy_auth.js b/netwerk/test/unit/test_trr_proxy_auth.js
new file mode 100644
index 0000000000..f339d82dea
--- /dev/null
+++ b/netwerk/test/unit/test_trr_proxy_auth.js
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+function setup() {
+ trr_test_setup();
+ Services.prefs.setBoolPref("network.trr.fetch_off_main_thread", true);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function AuthPrompt() {}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+
+ return true;
+ },
+
+ asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor() {}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt) {
+ this.prompt = new AuthPrompt();
+ }
+ return this.prompt;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt: null,
+};
+
+// Test if we successfully retry TRR request on main thread.
+add_task(async function test_trr_proxy_auth() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.proxy.com", "A", {
+ answers: [
+ {
+ name: "test.proxy.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "3.3.3.3",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.proxy.com", "3.3.3.3");
+
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start(0, true);
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ await trrServer.stop();
+ });
+
+ let authTriggered = false;
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "http-on-examine-response") {
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ let channel = aSubject.QueryInterface(Ci.nsIChannel);
+ channel.notificationCallbacks = new Requestor();
+ if (
+ channel.URI.spec.startsWith(
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ )
+ ) {
+ authTriggered = true;
+ }
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "http-on-examine-response");
+
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("test.proxy.com", "3.3.3.3");
+ Assert.ok(authTriggered);
+});
diff --git a/netwerk/test/unit/test_trr_strict_mode.js b/netwerk/test/unit/test_trr_strict_mode.js
new file mode 100644
index 0000000000..c4f8706fd5
--- /dev/null
+++ b/netwerk/test/unit/test_trr_strict_mode.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function setup() {
+ trr_test_setup();
+}
+setup();
+
+add_task(async function checkBlocklisting() {
+ Services.prefs.setBoolPref("network.trr.temp_blocklist", true);
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ info(`port = ${trrServer.port()}\n`);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+
+ // Check that we properly fallback to native DNS for a variety of DNS rcodes
+ for (let i = 0; i <= 5; i++) {
+ info(`testing rcode=${i}`);
+ await trrServer.registerDoHAnswers(`sub${i}.blocklisted.com`, "A", {
+ flags: i,
+ });
+ await trrServer.registerDoHAnswers(`sub${i}.blocklisted.com`, "AAAA", {
+ flags: i,
+ });
+
+ await new TRRDNSListener(`sub${i}.blocklisted.com`, {
+ expectedAnswer: "127.0.0.1",
+ });
+ Services.dns.clearCache(true);
+ await new TRRDNSListener(`sub${i}.blocklisted.com`, {
+ expectedAnswer: "127.0.0.1",
+ });
+ await new TRRDNSListener(`sub.sub${i}.blocklisted.com`, {
+ expectedAnswer: "127.0.0.1",
+ });
+ Services.dns.clearCache(true);
+ }
+});
diff --git a/netwerk/test/unit/test_trr_telemetry.js b/netwerk/test/unit/test_trr_telemetry.js
new file mode 100644
index 0000000000..fff6f07774
--- /dev/null
+++ b/netwerk/test/unit/test_trr_telemetry.js
@@ -0,0 +1,118 @@
+"use strict";
+
+/* import-globals-from trr_common.js */
+
+// Allow telemetry probes which may otherwise be disabled for some
+// applications (e.g. Thunderbird).
+Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+);
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+function setup() {
+ h2Port = trr_test_setup();
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+async function trrLookup(mode, rolloutMode) {
+ let hist = TelemetryTestUtils.getAndClearKeyedHistogram(
+ "TRR_SKIP_REASON_TRR_FIRST2"
+ );
+
+ if (rolloutMode) {
+ info("Testing doh-rollout.mode");
+ setModeAndURI(0, "doh?responseIP=2.2.2.2");
+ Services.prefs.setIntPref("doh-rollout.mode", rolloutMode);
+ } else {
+ setModeAndURI(mode, "doh?responseIP=2.2.2.2");
+ }
+
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("test.example.com", "2.2.2.2");
+ let expectedKey = `(other)_${mode}`;
+ if (mode == 0) {
+ expectedKey = "(other)";
+ }
+
+ let snapshot = hist.snapshot();
+ await TestUtils.waitForCondition(() => {
+ snapshot = hist.snapshot();
+ info("snapshot:" + JSON.stringify(snapshot));
+ return snapshot;
+ });
+ TelemetryTestUtils.assertKeyedHistogramValue(
+ hist,
+ expectedKey,
+ Ci.nsITRRSkipReason.TRR_OK,
+ 1
+ );
+}
+
+add_task(async function test_trr_lookup_mode_2() {
+ await trrLookup(Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+add_task(async function test_trr_lookup_mode_3() {
+ await trrLookup(Ci.nsIDNSService.MODE_TRRONLY);
+});
+
+add_task(async function test_trr_lookup_mode_0() {
+ await trrLookup(
+ Ci.nsIDNSService.MODE_NATIVEONLY,
+ Ci.nsIDNSService.MODE_TRRFIRST
+ );
+});
+
+async function trrByTypeLookup(trrURI, expectedSuccess, expectedSkipReason) {
+ Services.prefs.setIntPref(
+ "doh-rollout.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+
+ let hist = TelemetryTestUtils.getAndClearKeyedHistogram(
+ "TRR_RELEVANT_SKIP_REASON_TRR_FIRST_TYPE_REC"
+ );
+
+ setModeAndURI(Ci.nsIDNSService.MODE_TRRFIRST, trrURI);
+
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("test.httpssvc.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ expectedSuccess,
+ });
+ let expectedKey = `(other)_2`;
+
+ let snapshot = hist.snapshot();
+ await TestUtils.waitForCondition(() => {
+ snapshot = hist.snapshot();
+ info("snapshot:" + JSON.stringify(snapshot));
+ return snapshot;
+ });
+
+ TelemetryTestUtils.assertKeyedHistogramValue(
+ hist,
+ expectedKey,
+ expectedSkipReason,
+ 1
+ );
+}
+
+add_task(async function test_trr_by_type_lookup_success() {
+ await trrByTypeLookup("httpssvc", true, Ci.nsITRRSkipReason.TRR_OK);
+});
+
+add_task(async function test_trr_by_type_lookup_fail() {
+ await trrByTypeLookup(
+ "doh?responseIP=none",
+ false,
+ Ci.nsITRRSkipReason.TRR_NO_ANSWERS
+ );
+});
diff --git a/netwerk/test/unit/test_trr_ttl.js b/netwerk/test/unit/test_trr_ttl.js
new file mode 100644
index 0000000000..36cd0d4a5a
--- /dev/null
+++ b/netwerk/test/unit/test_trr_ttl.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+let trrServer = null;
+add_task(async function setup() {
+ trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+ dump(`port = ${trrServer.port()}\n`);
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+add_task(async function check_ttl_works() {
+ await trrServer.registerDoHAnswers("example.com", "A", {
+ answers: [
+ {
+ name: "example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ let { inRecord } = await new TRRDNSListener("example.com", {
+ expectedAnswer: "1.2.3.4",
+ });
+ equal(inRecord.QueryInterface(Ci.nsIDNSAddrRecord).ttl, 55);
+ await trrServer.registerDoHAnswers("example.org", "A", {
+ answers: [
+ {
+ name: "example.org",
+ ttl: 999,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ });
+ // Simple check to see that TRR works.
+ ({ inRecord } = await new TRRDNSListener("example.org", {
+ expectedAnswer: "1.2.3.4",
+ }));
+ equal(inRecord.QueryInterface(Ci.nsIDNSAddrRecord).ttl, 999);
+});
diff --git a/netwerk/test/unit/test_trr_with_proxy.js b/netwerk/test/unit/test_trr_with_proxy.js
new file mode 100644
index 0000000000..7d238adb98
--- /dev/null
+++ b/netwerk/test/unit/test_trr_with_proxy.js
@@ -0,0 +1,210 @@
+/* This test checks that a TRRServiceChannel can connect to the server with
+ a proxy.
+ Steps:
+ - Setup the proxy (PAC, proxy filter, and system proxy settings)
+ - Test when "network.trr.async_connInfo" is false. In this case, every
+ TRRServicChannel waits for the proxy info to be resolved.
+ - Test when "network.trr.async_connInfo" is true. In this case, every
+ TRRServicChannel uses an already created connection info to connect.
+ - The test test_trr_uri_change() is about checking if trr connection info
+ is updated correctly when trr uri changed.
+*/
+
+"use strict";
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+/* import-globals-from trr_common.js */
+
+let filter;
+let systemProxySettings;
+let trrProxy;
+const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+function setup() {
+ h2Port = trr_test_setup();
+ SetParentalControlEnabled(false);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.autoconfig_url");
+ Services.prefs.clearUserPref("network.trr.async_connInfo");
+ if (trrProxy) {
+ await trrProxy.stop();
+ }
+});
+
+class ProxyFilter {
+ constructor(type, host, port, flags) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
+ }
+ applyFilter(uri, pi, cb) {
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ "",
+ "",
+ this._flags,
+ 1000,
+ null
+ )
+ );
+ }
+}
+
+async function doTest(proxySetup, delay) {
+ info("Verifying a basic A record");
+ Services.dns.clearCache(true);
+ // Close all previous connections.
+ Services.obs.notifyObservers(null, "net:cancel-all-connections");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ setModeAndURI(2, "doh?responseIP=2.2.2.2"); // TRR-first
+
+ trrProxy = new TRRProxy();
+ await trrProxy.start(h2Port);
+ info("port=" + trrProxy.port);
+
+ await proxySetup(trrProxy.port);
+
+ if (delay) {
+ await new Promise(resolve => do_timeout(delay, resolve));
+ }
+
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+
+ // A non-zero request count indicates that TRR requests are being routed
+ // through the proxy.
+ Assert.ok(
+ (await trrProxy.request_count()) >= 1,
+ `Request count should be at least 1`
+ );
+
+ // clean up
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.autoconfig_url");
+ if (filter) {
+ pps.unregisterFilter(filter);
+ filter = null;
+ }
+ if (systemProxySettings) {
+ MockRegistrar.unregister(systemProxySettings);
+ systemProxySettings = null;
+ }
+
+ await trrProxy.stop();
+ trrProxy = null;
+}
+
+add_task(async function test_trr_proxy() {
+ async function setupPACWithDataURL(proxyPort) {
+ var pac = `data:text/plain, function FindProxyForURL(url, host) { return "HTTPS foo.example.com:${proxyPort}";}`;
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ Services.prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ }
+
+ async function setupPACWithHttpURL(proxyPort) {
+ let httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ let content = `function FindProxyForURL(url, host) { return "HTTPS foo.example.com:${proxyPort}";}`;
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start(-1);
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ let pacUri = `http://127.0.0.1:${httpserv.identity.primaryPort}/`;
+ Services.prefs.setCharPref("network.proxy.autoconfig_url", pacUri);
+
+ function consoleMessageObserved() {
+ return new Promise(resolve => {
+ let listener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
+ observe(msg) {
+ if (msg == `PAC file installed from ${pacUri}`) {
+ Services.console.unregisterListener(listener);
+ resolve();
+ }
+ },
+ };
+ Services.console.registerListener(listener);
+ });
+ }
+
+ await consoleMessageObserved();
+ }
+
+ async function setupProxyFilter(proxyPort) {
+ filter = new ProxyFilter("https", "foo.example.com", proxyPort, 0);
+ pps.registerFilter(filter, 10);
+ }
+
+ async function setupSystemProxySettings(proxyPort) {
+ systemProxySettings = {
+ QueryInterface: ChromeUtils.generateQI(["nsISystemProxySettings"]),
+ mainThreadOnly: true,
+ PACURI: null,
+ getProxyForURI: (aSpec, aScheme, aHost, aPort) => {
+ return `HTTPS foo.example.com:${proxyPort}`;
+ },
+ };
+
+ MockRegistrar.register(
+ "@mozilla.org/system-proxy-settings;1",
+ systemProxySettings
+ );
+
+ Services.prefs.setIntPref(
+ "network.proxy.type",
+ Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM
+ );
+
+ // simulate that system proxy setting is changed.
+ pps.notifyProxyConfigChangedInternal();
+ }
+
+ Services.prefs.setBoolPref("network.trr.async_connInfo", false);
+ await doTest(setupPACWithDataURL);
+ await doTest(setupPACWithDataURL, 1000);
+ await doTest(setupPACWithHttpURL);
+ await doTest(setupPACWithHttpURL, 1000);
+ await doTest(setupProxyFilter);
+ await doTest(setupProxyFilter, 1000);
+ await doTest(setupSystemProxySettings);
+ await doTest(setupSystemProxySettings, 1000);
+
+ Services.prefs.setBoolPref("network.trr.async_connInfo", true);
+ await doTest(setupPACWithDataURL);
+ await doTest(setupPACWithDataURL, 1000);
+ await doTest(setupPACWithHttpURL);
+ await doTest(setupPACWithHttpURL, 1000);
+ await doTest(setupProxyFilter);
+ await doTest(setupProxyFilter, 1000);
+ await doTest(setupSystemProxySettings);
+ await doTest(setupSystemProxySettings, 1000);
+});
+
+add_task(async function test_trr_uri_change() {
+ Services.prefs.setIntPref("network.proxy.type", 0);
+ Services.prefs.setBoolPref("network.trr.async_connInfo", true);
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2", "127.0.0.1");
+
+ await new TRRDNSListener("car.example.com", "127.0.0.1");
+
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await new TRRDNSListener("car.example.net", "2.2.2.2");
+});
diff --git a/netwerk/test/unit/test_udp_multicast.js b/netwerk/test/unit/test_udp_multicast.js
new file mode 100644
index 0000000000..202a4a1d99
--- /dev/null
+++ b/netwerk/test/unit/test_udp_multicast.js
@@ -0,0 +1,99 @@
+// Bug 960397: UDP multicast options
+"use strict";
+
+var { Constructor: CC } = Components;
+
+const UDPSocket = CC(
+ "@mozilla.org/network/udp-socket;1",
+ "nsIUDPSocket",
+ "init"
+);
+const ADDRESS_TEST1 = "224.0.0.200";
+const ADDRESS_TEST2 = "224.0.0.201";
+const ADDRESS_TEST3 = "224.0.0.202";
+const ADDRESS_TEST4 = "224.0.0.203";
+
+const TIMEOUT = 2000;
+
+function createSocketAndJoin(addr) {
+ let socket = new UDPSocket(
+ -1,
+ false,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ socket.joinMulticast(addr);
+ return socket;
+}
+
+function sendPing(socket, addr) {
+ let ping = "ping";
+ let rawPing = new TextEncoder().encode(ping);
+
+ return new Promise((resolve, reject) => {
+ socket.asyncListen({
+ onPacketReceived(s, message) {
+ info("Received on port " + socket.port);
+ Assert.equal(message.data, ping);
+ socket.close();
+ resolve(message.data);
+ },
+ onStopListening(sock, status) {},
+ });
+
+ info("Multicast send to port " + socket.port);
+ socket.send(addr, socket.port, rawPing, rawPing.length);
+
+ // Timers are bad, but it seems like the only way to test *not* getting a
+ // packet.
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(
+ () => {
+ socket.close();
+ reject();
+ },
+ TIMEOUT,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ });
+}
+
+add_test(() => {
+ info("Joining multicast group");
+ let socket = createSocketAndJoin(ADDRESS_TEST1);
+ sendPing(socket, ADDRESS_TEST1).then(run_next_test, () =>
+ do_throw("Joined group, but no packet received")
+ );
+});
+
+// Disabled on Windows11 for frequent intermittent failures.
+// See bug 1760123.
+add_test({ skip_if: () => mozinfo.win11_2009 }, () => {
+ info("Disabling multicast loopback");
+ let socket = createSocketAndJoin(ADDRESS_TEST2);
+ socket.multicastLoopback = false;
+ sendPing(socket, ADDRESS_TEST2).then(
+ () => do_throw("Loopback disabled, but still got a packet"),
+ run_next_test
+ );
+});
+
+// This fails locally on windows 11.
+add_test({ skip_if: () => mozinfo.win11_2009 }, () => {
+ info("Changing multicast interface");
+ let socket = createSocketAndJoin(ADDRESS_TEST3);
+ socket.multicastInterface = "127.0.0.1";
+ sendPing(socket, ADDRESS_TEST3).then(
+ () => do_throw("Changed interface, but still got a packet"),
+ run_next_test
+ );
+});
+
+add_test(() => {
+ info("Leaving multicast group");
+ let socket = createSocketAndJoin(ADDRESS_TEST4);
+ socket.leaveMulticast(ADDRESS_TEST4);
+ sendPing(socket, ADDRESS_TEST4).then(
+ () => do_throw("Left group, but still got a packet"),
+ run_next_test
+ );
+});
diff --git a/netwerk/test/unit/test_udpsocket.js b/netwerk/test/unit/test_udpsocket.js
new file mode 100644
index 0000000000..340fe6aa13
--- /dev/null
+++ b/netwerk/test/unit/test_udpsocket.js
@@ -0,0 +1,89 @@
+/* -*- Mode: Javascript; indent-tabs-mode: nil; js-indent-level: 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/. */
+"use strict";
+
+const HELLO_WORLD = "Hello World";
+const EMPTY_MESSAGE = "";
+
+add_test(function test_udp_message_raw_data() {
+ info("test for nsIUDPMessage.rawData");
+
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ socket.init(-1, true, Services.scriptSecurityManager.getSystemPrincipal());
+ info("Port assigned : " + socket.port);
+ socket.asyncListen({
+ QueryInterface: ChromeUtils.generateQI(["nsIUDPSocketListener"]),
+ onPacketReceived(aSocket, aMessage) {
+ let recv_data = String.fromCharCode.apply(null, aMessage.rawData);
+ Assert.equal(recv_data, HELLO_WORLD);
+ Assert.equal(recv_data, aMessage.data);
+ socket.close();
+ run_next_test();
+ },
+ onStopListening(aSocket, aStatus) {},
+ });
+
+ let rawData = new Uint8Array(HELLO_WORLD.length);
+ for (let i = 0; i < HELLO_WORLD.length; i++) {
+ rawData[i] = HELLO_WORLD.charCodeAt(i);
+ }
+ let written = socket.send("127.0.0.1", socket.port, rawData);
+ Assert.equal(written, HELLO_WORLD.length);
+});
+
+add_test(function test_udp_send_stream() {
+ info("test for nsIUDPSocket.sendBinaryStream");
+
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ socket.init(-1, true, Services.scriptSecurityManager.getSystemPrincipal());
+ socket.asyncListen({
+ QueryInterface: ChromeUtils.generateQI(["nsIUDPSocketListener"]),
+ onPacketReceived(aSocket, aMessage) {
+ let recv_data = String.fromCharCode.apply(null, aMessage.rawData);
+ Assert.equal(recv_data, HELLO_WORLD);
+ socket.close();
+ run_next_test();
+ },
+ onStopListening(aSocket, aStatus) {},
+ });
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.setData(HELLO_WORLD, HELLO_WORLD.length);
+ socket.sendBinaryStream("127.0.0.1", socket.port, stream);
+});
+
+add_test(function test_udp_message_zero_length() {
+ info("test for nsIUDPMessage with zero length");
+
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ socket.init(-1, true, Services.scriptSecurityManager.getSystemPrincipal());
+ info("Port assigned : " + socket.port);
+ socket.asyncListen({
+ QueryInterface: ChromeUtils.generateQI(["nsIUDPSocketListener"]),
+ onPacketReceived(aSocket, aMessage) {
+ let recv_data = String.fromCharCode.apply(null, aMessage.rawData);
+ Assert.equal(recv_data, EMPTY_MESSAGE);
+ Assert.equal(recv_data, aMessage.data);
+ socket.close();
+ run_next_test();
+ },
+ onStopListening(aSocket, aStatus) {},
+ });
+
+ let rawData = new Uint8Array(EMPTY_MESSAGE.length);
+ let written = socket.send("127.0.0.1", socket.port, rawData);
+ Assert.equal(written, EMPTY_MESSAGE.length);
+});
diff --git a/netwerk/test/unit/test_udpsocket_offline.js b/netwerk/test/unit/test_udpsocket_offline.js
new file mode 100644
index 0000000000..080f54281d
--- /dev/null
+++ b/netwerk/test/unit/test_udpsocket_offline.js
@@ -0,0 +1,144 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_test(function test_ipv4_any() {
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ Assert.throws(() => {
+ socket.init(-1, false, Services.scriptSecurityManager.getSystemPrincipal());
+ }, /NS_ERROR_OFFLINE/);
+
+ run_next_test();
+});
+
+add_test(function test_ipv6_any() {
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ Assert.throws(() => {
+ socket.init2("::", -1, Services.scriptSecurityManager.getSystemPrincipal());
+ }, /NS_ERROR_OFFLINE/);
+
+ run_next_test();
+});
+
+add_test(function test_ipv4() {
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ Assert.throws(() => {
+ socket.init2(
+ "240.0.0.1",
+ -1,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ }, /NS_ERROR_OFFLINE/);
+
+ run_next_test();
+});
+
+add_test(function test_ipv6() {
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ Assert.throws(() => {
+ socket.init2(
+ "2001:db8::1",
+ -1,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ }, /NS_ERROR_OFFLINE/);
+
+ run_next_test();
+});
+
+add_test(function test_ipv4_loopback() {
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ try {
+ socket.init2(
+ "127.0.0.1",
+ -1,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ true
+ );
+ } catch (e) {
+ Assert.ok(false, "unexpected exception: " + e);
+ }
+
+ // Now with localhost connections disabled in offline mode.
+ Services.prefs.setBoolPref("network.disable-localhost-when-offline", true);
+ socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ Assert.throws(() => {
+ socket.init2(
+ "127.0.0.1",
+ -1,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ true
+ );
+ }, /NS_ERROR_OFFLINE/);
+
+ Services.prefs.setBoolPref("network.disable-localhost-when-offline", false);
+
+ run_next_test();
+});
+
+add_test(function test_ipv6_loopback() {
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ try {
+ socket.init2(
+ "::1",
+ -1,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ true
+ );
+ } catch (e) {
+ Assert.ok(false, "unexpected exception: " + e);
+ }
+
+ // Now with localhost connections disabled in offline mode.
+ Services.prefs.setBoolPref("network.disable-localhost-when-offline", true);
+ socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+
+ Assert.throws(() => {
+ socket.init2(
+ "::1",
+ -1,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ true
+ );
+ }, /NS_ERROR_OFFLINE/);
+
+ Services.prefs.setBoolPref("network.disable-localhost-when-offline", false);
+
+ run_next_test();
+});
+
+function run_test() {
+ // jshint ignore:line
+ Services.io.offline = true;
+ Services.prefs.setBoolPref("network.disable-localhost-when-offline", false);
+ registerCleanupFunction(() => {
+ Services.io.offline = false;
+ Services.prefs.clearUserPref("network.disable-localhost-when-offline");
+ });
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_unescapestring.js b/netwerk/test/unit/test_unescapestring.js
new file mode 100644
index 0000000000..9853d2c0af
--- /dev/null
+++ b/netwerk/test/unit/test_unescapestring.js
@@ -0,0 +1,35 @@
+"use strict";
+
+const ONLY_NONASCII = Ci.nsINetUtil.ESCAPE_URL_ONLY_NONASCII;
+const SKIP_CONTROL = Ci.nsINetUtil.ESCAPE_URL_SKIP_CONTROL;
+
+var tests = [
+ ["foo", "foo", 0],
+ ["foo%20bar", "foo bar", 0],
+ ["foo%2zbar", "foo%2zbar", 0],
+ ["foo%", "foo%", 0],
+ ["%zzfoo", "%zzfoo", 0],
+ ["foo%z", "foo%z", 0],
+ ["foo%00bar", "foo\x00bar", 0],
+ ["foo%ffbar", "foo\xffbar", 0],
+ ["%00%1b%20%61%7f%80%ff", "%00%1b%20%61%7f\x80\xff", ONLY_NONASCII],
+ ["%00%1b%20%61%7f%80%ff", "%00%1b a%7f\x80\xff", SKIP_CONTROL],
+ [
+ "%00%1b%20%61%7f%80%ff",
+ "%00%1b%20%61%7f\x80\xff",
+ ONLY_NONASCII | SKIP_CONTROL,
+ ],
+ // Test that we do not drop the high-bytes of a UTF-16 string.
+ ["\u30a8\u30c9", "\xe3\x82\xa8\xe3\x83\x89", 0],
+];
+
+function run_test() {
+ for (var i = 0; i < tests.length; ++i) {
+ dump("Test " + i + " (" + tests[i][0] + ", " + tests[i][2] + ")\n");
+ Assert.equal(
+ Services.io.unescapeString(tests[i][0], tests[i][2]),
+ tests[i][1]
+ );
+ }
+ dump(tests.length + " tests passed\n");
+}
diff --git a/netwerk/test/unit/test_unix_domain.js b/netwerk/test/unit/test_unix_domain.js
new file mode 100644
index 0000000000..221721dc38
--- /dev/null
+++ b/netwerk/test/unit/test_unix_domain.js
@@ -0,0 +1,699 @@
+// Exercise Unix domain sockets.
+"use strict";
+
+var CC = Components.Constructor;
+
+const UnixServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initWithFilename"
+);
+const UnixAbstractServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initWithAbstractAddress"
+);
+
+const ScriptableInputStream = CC(
+ "@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init"
+);
+
+const socketTransportService = Cc[
+ "@mozilla.org/network/socket-transport-service;1"
+].getService(Ci.nsISocketTransportService);
+
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+const allPermissions = parseInt("777", 8);
+
+function run_test() {
+ // If we're on Windows, simply check for graceful failure.
+ if (mozinfo.os == "win") {
+ test_not_supported();
+ return;
+ }
+
+ // The xpcshell temp directory on Android doesn't seem to let us create
+ // Unix domain sockets. (Perhaps it's a FAT filesystem?)
+ if (mozinfo.os != "android") {
+ add_test(test_echo);
+ add_test(test_name_too_long);
+ add_test(test_no_directory);
+ add_test(test_no_such_socket);
+ add_test(test_address_in_use);
+ add_test(test_file_in_way);
+ add_test(test_create_permission);
+ add_test(test_connect_permission);
+ add_test(test_long_socket_name);
+ add_test(test_keep_when_offline);
+ }
+
+ if (mozinfo.os == "android" || mozinfo.os == "linux") {
+ add_test(test_abstract_address_socket);
+ }
+
+ run_next_test();
+}
+
+// Check that creating a Unix domain socket fails gracefully on Windows.
+function test_not_supported() {
+ let socketName = do_get_tempdir();
+ socketName.append("socket");
+ info("creating socket: " + socketName.path);
+
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"
+ );
+
+ do_check_throws_nsIException(
+ () => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"
+ );
+}
+
+// Actually exchange data with Unix domain sockets.
+function test_echo() {
+ let log = "";
+
+ let socketName = do_get_tempdir();
+ socketName.append("socket");
+
+ // Create a server socket, listening for connections.
+ info("creating socket: " + socketName.path);
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+ server.asyncListen({
+ onSocketAccepted(aServ, aTransport) {
+ info("called test_echo's onSocketAccepted");
+ log += "a";
+
+ Assert.equal(aServ, server);
+
+ let connection = aTransport;
+
+ // Check the server socket's self address.
+ let connectionSelfAddr = connection.getScriptableSelfAddr();
+ Assert.equal(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(connectionSelfAddr.address, socketName.path);
+
+ // The client socket is anonymous, so the server transport should
+ // have an empty peer address.
+ Assert.equal(connection.host, "");
+ Assert.equal(connection.port, 0);
+ let connectionPeerAddr = connection.getScriptablePeerAddr();
+ Assert.equal(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(connectionPeerAddr.address, "");
+
+ let serverAsyncInput = connection
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = connection.openOutputStream(0, 0, 0);
+
+ serverAsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_echo's server's onInputStreamReady");
+ let serverScriptableInput = new ScriptableInputStream(aStream);
+
+ // Receive data from the client, and send back a response.
+ Assert.equal(
+ serverScriptableInput.readBytes(17),
+ "Mervyn Murgatroyd"
+ );
+ info("server has read message from client");
+ serverOutput.write("Ruthven Murgatroyd", 18);
+ info("server has written to client");
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+
+ onStopListening(aServ, aStatus) {
+ info("called test_echo's onStopListening");
+ log += "s";
+
+ Assert.equal(aServ, server);
+ Assert.equal(log, "acs");
+
+ run_next_test();
+ },
+ });
+
+ // Create a client socket, and connect to the server.
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ Assert.equal(client.host, socketName.path);
+ Assert.equal(client.port, 0);
+
+ let clientAsyncInput = client
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let clientInput = new ScriptableInputStream(clientAsyncInput);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+
+ clientOutput.write("Mervyn Murgatroyd", 17);
+ info("client has written to server");
+
+ clientAsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_echo's client's onInputStreamReady");
+ log += "c";
+
+ Assert.equal(aStream, clientAsyncInput);
+
+ // Now that the connection has been established, we can check the
+ // transport's self and peer addresses.
+ let clientSelfAddr = client.getScriptableSelfAddr();
+ Assert.equal(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(clientSelfAddr.address, "");
+
+ Assert.equal(client.host, socketName.path); // re-check, but hey
+ let clientPeerAddr = client.getScriptablePeerAddr();
+ Assert.equal(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(clientPeerAddr.address, socketName.path);
+
+ Assert.equal(clientInput.readBytes(18), "Ruthven Murgatroyd");
+ info("client has read message from server");
+
+ server.close();
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+}
+
+// Create client and server sockets using a path that's too long.
+function test_name_too_long() {
+ let socketName = do_get_tempdir();
+ // The length limits on all the systems NSPR supports are a bit past 100.
+ socketName.append(new Array(1000).join("x"));
+
+ // The length must be checked before we ever make any system calls --- we
+ // have to create the sockaddr first --- so it's unambiguous which error
+ // we should get here.
+
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ // Unlike most other client socket errors, this one gets reported
+ // immediately, as we can't even initialize the sockaddr with the given
+ // name.
+ do_check_throws_nsIException(
+ () => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ run_next_test();
+}
+
+// Try creating a socket in a directory that doesn't exist.
+function test_no_directory() {
+ let socketName = do_get_tempdir();
+ socketName.append("missing");
+ socketName.append("socket");
+
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NOT_FOUND"
+ );
+
+ run_next_test();
+}
+
+// Try connecting to a server socket that isn't there.
+function test_no_such_socket() {
+ let socketName = do_get_tempdir();
+ socketName.append("nonexistent-socket");
+
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ let clientAsyncInput = client
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ clientAsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_no_such_socket's onInputStreamReady");
+
+ Assert.equal(aStream, clientAsyncInput);
+
+ // nsISocketTransport puts off actually creating sockets as long as
+ // possible, so the error in connecting doesn't actually show up until
+ // this point.
+ do_check_throws_nsIException(
+ () => clientAsyncInput.available(),
+ "NS_ERROR_FILE_NOT_FOUND"
+ );
+
+ clientAsyncInput.close();
+ client.close(Cr.NS_OK);
+
+ run_next_test();
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+}
+
+// Creating a socket with a name that another socket is already using is an
+// error.
+function test_address_in_use() {
+ let socketName = do_get_tempdir();
+ socketName.append("socket-in-use");
+
+ // Create one server socket.
+ new UnixServerSocket(socketName, allPermissions, -1);
+
+ // Now try to create another with the same name.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE"
+ );
+
+ run_next_test();
+}
+
+// Creating a socket with a name that is already a file is an error.
+function test_file_in_way() {
+ let socketName = do_get_tempdir();
+ socketName.append("file_in_way");
+
+ // Create a file with the given name.
+ socketName.create(Ci.nsIFile.NORMAL_FILE_TYPE, allPermissions);
+
+ // Try to create a socket with the same name.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE"
+ );
+
+ // Try to create a socket under a name that uses that as a parent directory.
+ socketName.append("socket");
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NOT_DIRECTORY"
+ );
+
+ run_next_test();
+}
+
+// It is not permitted to create a socket in a directory which we are not
+// permitted to execute, or create files in.
+function test_create_permission() {
+ let dirName = do_get_tempdir();
+ dirName.append("unfriendly");
+
+ let socketName = dirName.clone();
+ socketName.append("socket");
+
+ // The test harness has difficulty cleaning things up if we don't make
+ // everything writable before we're done.
+ try {
+ // Create a directory which we are not permitted to search.
+ dirName.create(Ci.nsIFile.DIRECTORY_TYPE, 0);
+
+ // Try to create a socket in that directory. Because Linux returns EACCES
+ // when a 'connect' fails because of a local firewall rule,
+ // nsIServerSocket returns NS_ERROR_CONNECTION_REFUSED in this case.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+
+ // Grant read and execute permission, but not write permission on the directory.
+ dirName.permissions = parseInt("0555", 8);
+
+ // This should also fail; we need write permission.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+ } finally {
+ // Make the directory writable, so the test harness can clean it up.
+ dirName.permissions = allPermissions;
+ }
+
+ // This should succeed, since we now have all the permissions on the
+ // directory we could want.
+ do_check_instanceof(
+ new UnixServerSocket(socketName, allPermissions, -1),
+ Ci.nsIServerSocket
+ );
+
+ run_next_test();
+}
+
+// To connect to a Unix domain socket, we need search permission on the
+// directories containing it, and some kind of permission or other on the
+// socket itself.
+function test_connect_permission() {
+ // This test involves a lot of callbacks, but they're written out so that
+ // the actual control flow proceeds from top to bottom.
+ let log = "";
+
+ // Create a directory which we are permitted to search - at first.
+ let dirName = do_get_tempdir();
+ dirName.append("inhospitable");
+ dirName.create(Ci.nsIFile.DIRECTORY_TYPE, allPermissions);
+
+ let socketName = dirName.clone();
+ socketName.append("socket");
+
+ // Create a server socket in that directory, listening for connections,
+ // and accessible.
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+ server.asyncListen({
+ onSocketAccepted: socketAccepted,
+ onStopListening: stopListening,
+ });
+
+ // Make the directory unsearchable.
+ dirName.permissions = 0;
+
+ let client3;
+
+ let client1 = socketTransportService.createUnixDomainTransport(socketName);
+ let client1AsyncInput = client1
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ client1AsyncInput.asyncWait(
+ function () {
+ info("called test_connect_permission's client1's onInputStreamReady");
+ log += "1";
+
+ // nsISocketTransport puts off actually creating sockets as long as
+ // possible, so the error doesn't actually show up until this point.
+ do_check_throws_nsIException(
+ () => client1AsyncInput.available(),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+
+ client1AsyncInput.close();
+ client1.close(Cr.NS_OK);
+
+ // Make the directory searchable, but make the socket inaccessible.
+ dirName.permissions = allPermissions;
+ socketName.permissions = 0;
+
+ let client2 =
+ socketTransportService.createUnixDomainTransport(socketName);
+ let client2AsyncInput = client2
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ client2AsyncInput.asyncWait(
+ function () {
+ info("called test_connect_permission's client2's onInputStreamReady");
+ log += "2";
+
+ do_check_throws_nsIException(
+ () => client2AsyncInput.available(),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+
+ client2AsyncInput.close();
+ client2.close(Cr.NS_OK);
+
+ // Now make everything accessible, and try one last time.
+ socketName.permissions = allPermissions;
+
+ client3 =
+ socketTransportService.createUnixDomainTransport(socketName);
+
+ let client3Output = client3.openOutputStream(0, 0, 0);
+ client3Output.write("Hanratty", 8);
+
+ let client3AsyncInput = client3
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ client3AsyncInput.asyncWait(
+ client3InputStreamReady,
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+
+ function socketAccepted(aServ, aTransport) {
+ info("called test_connect_permission's onSocketAccepted");
+ log += "a";
+
+ let serverInput = aTransport
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = aTransport.openOutputStream(0, 0, 0);
+
+ serverInput.asyncWait(
+ function (aStream) {
+ info(
+ "called test_connect_permission's socketAccepted's onInputStreamReady"
+ );
+ log += "i";
+
+ // Receive data from the client, and send back a response.
+ let serverScriptableInput = new ScriptableInputStream(serverInput);
+ Assert.equal(serverScriptableInput.readBytes(8), "Hanratty");
+ serverOutput.write("Ferlingatti", 11);
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ }
+
+ function client3InputStreamReady(aStream) {
+ info("called client3's onInputStreamReady");
+ log += "3";
+
+ let client3Input = new ScriptableInputStream(aStream);
+
+ Assert.equal(client3Input.readBytes(11), "Ferlingatti");
+
+ client3.close(Cr.NS_OK);
+ server.close();
+ }
+
+ function stopListening(aServ, aStatus) {
+ info("called test_connect_permission's server's stopListening");
+ log += "s";
+
+ Assert.equal(log, "12ai3s");
+
+ run_next_test();
+ }
+}
+
+// Creating a socket with a long filename doesn't crash.
+function test_long_socket_name() {
+ let socketName = do_get_tempdir();
+ socketName.append(new Array(10000).join("long"));
+
+ // Try to create a server socket with the long name.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ // Try to connect to a socket with the long name.
+ do_check_throws_nsIException(
+ () => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ run_next_test();
+}
+
+// Going offline should not shut down Unix domain sockets.
+function test_keep_when_offline() {
+ let log = "";
+
+ let socketName = do_get_tempdir();
+ socketName.append("keep-when-offline");
+
+ // Create a listening socket.
+ let listener = new UnixServerSocket(socketName, allPermissions, -1);
+ listener.asyncListen({ onSocketAccepted: onAccepted, onStopListening });
+
+ // Connect a client socket to the listening socket.
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+ let clientInput = client.openInputStream(0, 0, 0);
+ clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
+ let clientScriptableInput = new ScriptableInputStream(clientInput);
+
+ let server, serverInput, serverScriptableInput, serverOutput;
+
+ // How many times has the server invited the client to go first?
+ let count = 0;
+
+ // The server accepted connection callback.
+ function onAccepted(aListener, aServer) {
+ info("test_keep_when_offline: onAccepted called");
+ log += "a";
+ Assert.equal(aListener, listener);
+ server = aServer;
+
+ // Prepare to receive messages from the client.
+ serverInput = server.openInputStream(0, 0, 0);
+ serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
+ serverScriptableInput = new ScriptableInputStream(serverInput);
+
+ // Start a conversation with the client.
+ serverOutput = server.openOutputStream(0, 0, 0);
+ serverOutput.write("After you, Alphonse!", 20);
+ count++;
+ }
+
+ // The client has seen its end of the socket close.
+ function clientReady(aStream) {
+ log += "c";
+ info("test_keep_when_offline: clientReady called: " + log);
+ Assert.equal(aStream, clientInput);
+
+ // If the connection has been closed, end the conversation and stop listening.
+ let available;
+ try {
+ available = clientInput.available();
+ } catch (ex) {
+ do_check_instanceof(ex, Ci.nsIException);
+ Assert.equal(ex.result, Cr.NS_BASE_STREAM_CLOSED);
+
+ info("client received end-of-stream; closing client output stream");
+ log += ")";
+
+ client.close(Cr.NS_OK);
+
+ // Now both output streams have been closed, and both input streams
+ // have received the close notification. Stop listening for
+ // connections.
+ listener.close();
+ }
+
+ if (available) {
+ // Check the message from the server.
+ Assert.equal(clientScriptableInput.readBytes(20), "After you, Alphonse!");
+
+ // Write our response to the server.
+ clientOutput.write("No, after you, Gaston!", 22);
+
+ // Ask to be called again, when more input arrives.
+ clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
+ }
+ }
+
+ function serverReady(aStream) {
+ log += "s";
+ info("test_keep_when_offline: serverReady called: " + log);
+ Assert.equal(aStream, serverInput);
+
+ // Check the message from the client.
+ Assert.equal(serverScriptableInput.readBytes(22), "No, after you, Gaston!");
+
+ // This should not shut things down: Unix domain sockets should
+ // remain open in offline mode.
+ if (count == 5) {
+ Services.io.offline = true;
+ log += "o";
+ }
+
+ if (count < 10) {
+ // Insist.
+ serverOutput.write("After you, Alphonse!", 20);
+ count++;
+
+ // As long as the input stream is open, always ask to be called again
+ // when more input arrives.
+ serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
+ } else if (count == 10) {
+ // After sending ten times and receiving ten replies, we're not
+ // going to send any more. Close the server's output stream; the
+ // client's input stream should see this.
+ info("closing server transport");
+ server.close(Cr.NS_OK);
+ log += "(";
+ }
+ }
+
+ // We have stopped listening.
+ function onStopListening(aServ, aStatus) {
+ info("test_keep_when_offline: onStopListening called");
+ log += "L";
+ Assert.equal(log, "acscscscscsocscscscscs(c)L");
+
+ Assert.equal(aServ, listener);
+ Assert.equal(aStatus, Cr.NS_BINDING_ABORTED);
+
+ run_next_test();
+ }
+}
+
+function test_abstract_address_socket() {
+ const socketname = "abstractsocket";
+ let server = new UnixAbstractServerSocket(socketname, -1);
+ server.asyncListen({
+ onSocketAccepted: (aServ, aTransport) => {
+ let serverInput = aTransport
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = aTransport.openOutputStream(0, 0, 0);
+
+ serverInput.asyncWait(
+ aStream => {
+ info(
+ "called test_abstract_address_socket's onSocketAccepted's onInputStreamReady"
+ );
+
+ // Receive data from the client, and send back a response.
+ let serverScriptableInput = new ScriptableInputStream(serverInput);
+ Assert.equal(serverScriptableInput.readBytes(9), "ping ping");
+ serverOutput.write("pong", 4);
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+ onStopListening: (aServ, aTransport) => {},
+ });
+
+ let client =
+ socketTransportService.createUnixDomainAbstractAddressTransport(socketname);
+ Assert.equal(client.host, socketname);
+ Assert.equal(client.port, 0);
+ let clientInput = client
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+
+ clientOutput.write("ping ping", 9);
+
+ clientInput.asyncWait(
+ aStream => {
+ let clientScriptInput = new ScriptableInputStream(clientInput);
+ let available = clientScriptInput.available();
+ if (available) {
+ Assert.equal(clientScriptInput.readBytes(4), "pong");
+
+ client.close(Cr.NS_OK);
+ server.close(Cr.NS_OK);
+
+ run_next_test();
+ }
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+}
diff --git a/netwerk/test/unit/test_uri_mutator.js b/netwerk/test/unit/test_uri_mutator.js
new file mode 100644
index 0000000000..fb9228fc5b
--- /dev/null
+++ b/netwerk/test/unit/test_uri_mutator.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+"use strict";
+
+function standardMutator() {
+ return Cc["@mozilla.org/network/standard-url-mutator;1"].createInstance(
+ Ci.nsIURIMutator
+ );
+}
+
+add_task(async function test_simple_setter_chaining() {
+ let uri = standardMutator()
+ .setSpec("http://example.com/")
+ .setQuery("hello")
+ .setRef("bla")
+ .setScheme("ftp")
+ .finalize();
+ equal(uri.spec, "ftp://example.com/?hello#bla");
+});
+
+add_task(async function test_qi_behaviour() {
+ let uri = standardMutator()
+ .setSpec("http://example.com/")
+ .QueryInterface(Ci.nsIURI);
+ equal(uri.spec, "http://example.com/");
+
+ Assert.throws(
+ () => {
+ uri = standardMutator().QueryInterface(Ci.nsIURI);
+ },
+ /NS_NOINTERFACE/,
+ "mutator doesn't QI if it holds no URI"
+ );
+
+ let mutator = standardMutator().setSpec("http://example.com/path");
+ uri = mutator.QueryInterface(Ci.nsIURI);
+ equal(uri.spec, "http://example.com/path");
+ Assert.throws(
+ () => {
+ uri = mutator.QueryInterface(Ci.nsIURI);
+ },
+ /NS_NOINTERFACE/,
+ "Second QueryInterface should fail"
+ );
+});
diff --git a/netwerk/test/unit/test_use_httpssvc.js b/netwerk/test/unit/test_use_httpssvc.js
new file mode 100644
index 0000000000..31174b3374
--- /dev/null
+++ b/netwerk/test/unit/test_use_httpssvc.js
@@ -0,0 +1,240 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let h2Port;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+add_setup(async function setup() {
+ trr_test_setup();
+
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ registerCleanupFunction(() => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR first
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ chan.asyncOpen(new ChannelListener(finish, null, CL_ALLOW_UNKNOWN_CL));
+ });
+}
+
+// This is for testing when the HTTPSSVC record is not available when
+// the transaction is added in connection manager.
+add_task(async function testUseHTTPSSVCForHttpsUpgrade() {
+ // use the h2 server as DOH provider
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+ Services.dns.clearCache(true);
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ let chan = makeChan(`https://test.httpssvc.com:8080/`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+});
+
+class EventSinkListener {
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+ asyncOnChannelRedirect(oldChan, newChan, flags, callback) {
+ Assert.equal(oldChan.URI.hostPort, newChan.URI.hostPort);
+ Assert.equal(oldChan.URI.scheme, "http");
+ Assert.equal(newChan.URI.scheme, "https");
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+}
+
+EventSinkListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIChannelEventSink",
+]);
+
+// Test if the request is upgraded to https with a HTTPSSVC record.
+add_task(async function testUseHTTPSSVCAsHSTS() {
+ // use the h2 server as DOH provider
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+ Services.dns.clearCache(true);
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ // At this time, the DataStorage is not ready, so MaybeUseHTTPSRRForUpgrade()
+ // is called from the callback of NS_ShouldSecureUpgrade().
+ let chan = makeChan(`http://test.httpssvc.com:80/`);
+ let listener = new EventSinkListener();
+ chan.notificationCallbacks = listener;
+
+ let [req] = await channelOpenPromise(chan);
+
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ // At this time, the DataStorage is ready, so MaybeUseHTTPSRRForUpgrade()
+ // is called from nsHttpChannel::OnBeforeConnect().
+ chan = makeChan(`http://test.httpssvc.com:80/`);
+ listener = new EventSinkListener();
+ chan.notificationCallbacks = listener;
+
+ [req] = await channelOpenPromise(chan);
+
+ req.QueryInterface(Ci.nsIHttpChannel);
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+});
+
+// This is for testing when the HTTPSSVC record is already available before
+// the transaction is added in connection manager.
+add_task(async function testUseHTTPSSVC() {
+ // use the h2 server as DOH provider
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+
+ // Do DNS resolution before creating the channel, so the HTTPSSVC record will
+ // be resolved from the cache.
+ await new TRRDNSListener("test.httpssvc.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ // We need to skip the security check, since our test cert is signed for
+ // foo.example.com, not test.httpssvc.com.
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ let chan = makeChan(`https://test.httpssvc.com:8888`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+});
+
+// Test if we can successfully fallback to the original host and port.
+add_task(async function testFallback() {
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port()}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.fallback.com", "A", {
+ answers: [
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+ // Use a wrong port number 8888, so this connection will be refused.
+ await trrServer.registerDoHAnswers("test.fallback.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "foo.example.com",
+ values: [{ key: "port", value: 8888 }],
+ },
+ },
+ ],
+ });
+
+ let { inRecord } = await new TRRDNSListener("test.fallback.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let record = inRecord
+ .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
+ .GetServiceModeRecord(false, false);
+ Assert.equal(record.priority, 1);
+ Assert.equal(record.name, "foo.example.com");
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ // When the connection with port 8888 failed, the correct h2Port will be used
+ // to connect again.
+ let chan = makeChan(`https://test.fallback.com:${h2Port}`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+});
diff --git a/netwerk/test/unit/test_verify_traffic.js b/netwerk/test/unit/test_verify_traffic.js
new file mode 100644
index 0000000000..be41223642
--- /dev/null
+++ b/netwerk/test/unit/test_verify_traffic.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+async function registerSimplePathHandler(server, path) {
+ return server.registerPathHandler(path, (req, resp) => {
+ resp.writeHead(200);
+ resp.end("done");
+ });
+}
+
+add_task(async function test_verify_traffic_for_http2() {
+ Services.prefs.setBoolPref(
+ "network.http.http2.move_to_pending_list_after_network_change",
+ true
+ );
+
+ // Bug 1878505: It seems when HTTPS RR is enabled, a speculative
+ // connection that waits to receive a HTTPS response will receive it
+ // after the actual connection is established, leading to an extra
+ // connection being established.
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let server = new NodeHTTP2Server();
+ await server.start();
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref(
+ "network.http.http2.move_to_pending_list_after_network_change"
+ );
+ await server.stop();
+ });
+
+ try {
+ await server.registerPathHandler("/longDelay", (req, resp) => {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout, no-undef
+ setTimeout(function () {
+ resp.writeHead(200);
+ resp.end("done");
+ }, 8000);
+ });
+ } catch (e) {}
+
+ await registerSimplePathHandler(server, "/test");
+
+ // Send some requests and check if we have only one h2 session.
+ for (let i = 0; i < 2; i++) {
+ let chan = makeChan(`https://localhost:${server.port()}/test`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ }
+ let sessionCount = await server.sessionCount();
+ Assert.equal(sessionCount, 1);
+
+ let res = await new Promise(resolve => {
+ // Create a request that takes 8s to finish.
+ let chan = makeChan(`https://localhost:${server.port()}/longDelay`);
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
+
+ // Send a network change event to trigger VerifyTraffic(). After this,
+ // the connnection will be put in the pending list.
+ // We'll crate a new connection for the new request.
+ Services.obs.notifyObservers(
+ null,
+ "network:link-status-changed",
+ "changed"
+ );
+
+ // This request will use the new connection.
+ let chan1 = makeChan(`https://localhost:${server.port()}/test`);
+ chan1.asyncOpen(new ChannelListener(() => {}, null, CL_ALLOW_UNKNOWN_CL));
+ });
+
+ // The connection in the pending list should be still working.
+ Assert.equal(res.status, Cr.NS_OK);
+ Assert.equal(res.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+
+ sessionCount = await server.sessionCount();
+ Assert.equal(sessionCount, 2);
+
+ await server.stop();
+});
diff --git a/netwerk/test/unit/test_websocket_500k.js b/netwerk/test/unit/test_websocket_500k.js
new file mode 100644
index 0000000000..94a324cca0
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_500k.js
@@ -0,0 +1,222 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+ChromeUtils.defineESModuleGetters(this, {
+ ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
+});
+
+add_setup(async function () {
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
+
+async function channelOpenPromise(url, msg) {
+ let conn = new WebSocketConnection();
+ await conn.open(url);
+ conn.send(msg);
+ let res = await conn.receiveMessages();
+ conn.close();
+ let { status } = await conn.finished();
+ return [status, res];
+}
+
+async function sendDataAndCheck(url) {
+ let data = "a".repeat(500000);
+ let [status, res] = await channelOpenPromise(url, data);
+ Assert.equal(status, Cr.NS_OK);
+ // Use "ObjectUtils.deepEqual" directly to avoid printing data.
+ Assert.ok(ObjectUtils.deepEqual(res, [data]));
+}
+
+add_task(async function test_h2_websocket_500k() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start();
+ registerCleanupFunction(async () => wss.stop());
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `wss://foo.example.com:${wss.port()}`;
+ await sendDataAndCheck(url);
+});
+
+// h1.1 direct
+add_task(async function test_h1_websocket_direct() {
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+ registerCleanupFunction(async () => wss.stop());
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `wss://localhost:${wss.port()}`;
+ await sendDataAndCheck(url);
+});
+
+// ws h1.1 with insecure h1.1 proxy
+add_task(async function test_h1_ws_with_h1_insecure_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", false);
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `wss://localhost:${wss.port()}`;
+ await sendDataAndCheck(url);
+});
+
+// h1 server with secure h1.1 proxy
+add_task(async function test_h1_ws_with_secure_h1_proxy() {
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ await sendDataAndCheck(url);
+
+ await proxy.stop();
+});
+
+// ws h1.1 with h2 proxy
+add_task(async function test_h1_ws_with_h2_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", false);
+
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ await sendDataAndCheck(url);
+
+ await proxy.stop();
+});
+
+// ws h2 with insecure h1.1 proxy
+add_task(async function test_h2_ws_with_h1_insecure_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ await sendDataAndCheck(url);
+
+ await proxy.stop();
+});
+
+add_task(async function test_h2_ws_with_h1_secure_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ await sendDataAndCheck(url);
+
+ await proxy.stop();
+});
+
+// ws h2 with secure h2 proxy
+add_task(async function test_h2_ws_with_h2_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start(); // start and register proxy "filter"
+
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start(); // init port
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ await sendDataAndCheck(url);
+
+ await proxy.stop();
+});
diff --git a/netwerk/test/unit/test_websocket_fails.js b/netwerk/test/unit/test_websocket_fails.js
new file mode 100644
index 0000000000..9acb1bfcd2
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_fails.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+/* import-globals-from head_websocket.js */
+
+var CC = Components.Constructor;
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+
+let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+add_setup(() => {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+});
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.http.http2.websockets");
+});
+
+// TLS handshake to the end server fails - no proxy
+async function test_tls_fail_on_direct_ws_server_handshake() {
+ // no cert and no proxy
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+
+ let chan = makeWebSocketChan();
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test tls handshake with direct ws server fails";
+ let [status] = await openWebSocketChannelPromise(chan, url, msg);
+
+ // can be two errors, seems to be a race between:
+ // * overwriting the WebSocketChannel status with NS_ERROR_NET_RESET and
+ // * getting the original 805A1FF3 // SEC_ERROR_UNKNOWN_ISSUER
+ if (status == 2152398930) {
+ Assert.equal(status, 0x804b0052); // NS_ERROR_NET_INADEQUATE_SECURITY
+ } else {
+ // occasionally this happens
+ Assert.equal(status, 0x804b0057); // NS_ERROR_WEBSOCKET_CONNECTION_REFUSED
+ }
+}
+
+// TLS handshake to proxy fails
+async function test_tls_fail_on_proxy_handshake() {
+ // we have ws cert, but no proxy cert
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+
+ let chan = makeWebSocketChan();
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test tls failure on proxy handshake";
+ let [status] = await openWebSocketChannelPromise(chan, url, msg);
+
+ // see above test for details on why 2 cases here
+ if (status == 2152398930) {
+ Assert.equal(status, 0x804b0052); // NS_ERROR_NET_INADEQUATE_SECURITY
+ } else {
+ Assert.equal(status, 0x804b0057); // NS_ERROR_WEBSOCKET_CONNECTION_REFUSED
+ }
+
+ await proxy.stop();
+}
+
+// the ws server does not respond (closed port)
+async function test_non_responsive_ws_server_closed_port() {
+ // ws server cert already added in previous test
+
+ // no ws server listening (closed port)
+ let randomPort = 666; // "random" port
+ let chan = makeWebSocketChan();
+ let url = `wss://localhost:${randomPort}`;
+ const msg = "test non-responsive ws server closed port";
+ let [status] = await openWebSocketChannelPromise(chan, url, msg);
+ Assert.equal(status, 0x804b0057); // NS_ERROR_WEBSOCKET_CONNECTION_REFUSED
+}
+
+// no ws response from server (ie. no ws server, use tcp server to open port)
+async function test_non_responsive_ws_server_open_port() {
+ // we are expecting the timeout in this test, so lets shorten to 1s
+ Services.prefs.setIntPref("network.websocket.timeout.open", 1);
+
+ // ws server cert already added in previous test
+
+ // use a tcp server to test open port, not a ws server
+ var server = ServerSocket(-1, true, -1); // port, loopback, default-backlog
+ var port = server.port;
+ info("server: listening on " + server.port);
+ server.asyncListen({});
+
+ // queue cleanup after all tests
+ registerCleanupFunction(() => {
+ server.close();
+ Services.prefs.clearUserPref("network.websocket.timeout.open");
+ });
+
+ // try ws connection
+ let chan = makeWebSocketChan();
+ let url = `wss://localhost:${port}`;
+ const msg = "test non-responsive ws server open port";
+ let [status] = await openWebSocketChannelPromise(chan, url, msg);
+ Assert.equal(status, Cr.NS_ERROR_NET_TIMEOUT_EXTERNAL); // we will timeout
+ Services.prefs.clearUserPref("network.websocket.timeout.open");
+}
+
+// proxy does not respond
+async function test_proxy_doesnt_respond() {
+ Services.prefs.setIntPref("network.websocket.timeout.open", 1);
+ Services.prefs.setBoolPref("network.http.http2.websockets", false);
+ // ws cert added in previous test, add proxy cert
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ info("spinning up proxy");
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ // route traffic through non-existant proxy
+ const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+ let randomPort = proxy.port() + 1;
+ var filter = new NodeProxyFilter(
+ proxy.protocol(),
+ "localhost",
+ randomPort,
+ 0
+ );
+ pps.registerFilter(filter, 10);
+
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ Services.prefs.clearUserPref("network.websocket.timeout.open");
+ });
+
+ // setup the websocket server
+ info("spinning up websocket server");
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+ registerCleanupFunction(() => {
+ wss.stop();
+ });
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ info("creating and connecting websocket");
+ let url = `wss://localhost:${wss.port()}`;
+ let conn = new WebSocketConnection();
+ conn.open(url); // do not await, we don't expect a fully opened channel
+
+ // check proxy info
+ info("checking proxy info");
+ let proxyInfoPromise = conn.getProxyInfo();
+ let proxyInfo = await proxyInfoPromise;
+ Assert.equal(proxyInfo.type, "https"); // let's be sure that failure is not "direct"
+
+ // we fail to connect via proxy, as expected
+ let { status } = await conn.finished();
+ info("stats: " + status);
+ Assert.equal(status, 0x804b0057); // NS_ERROR_WEBSOCKET_CONNECTION_REFUSED
+}
+
+add_task(test_tls_fail_on_direct_ws_server_handshake);
+add_task(test_tls_fail_on_proxy_handshake);
+add_task(test_non_responsive_ws_server_closed_port);
+add_task(test_non_responsive_ws_server_open_port);
+add_task(test_proxy_doesnt_respond);
diff --git a/netwerk/test/unit/test_websocket_fails_2.js b/netwerk/test/unit/test_websocket_fails_2.js
new file mode 100644
index 0000000000..57c9b321ad
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_fails_2.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+/* import-globals-from head_websocket.js */
+
+let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+add_setup(() => {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+});
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.http.http2.websockets");
+});
+
+// TLS handshake to the end server fails with proxy
+async function test_tls_fail_on_ws_server_over_proxy() {
+ // we are expecting a timeout, so lets shorten how long we must wait
+ Services.prefs.setIntPref("network.websocket.timeout.open", 1);
+
+ // no cert to ws server
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ Services.prefs.clearUserPref("network.websocket.timeout.open");
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let chan = makeWebSocketChan();
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test tls fail on ws server over proxy";
+ let [status] = await openWebSocketChannelPromise(chan, url, msg);
+
+ Assert.equal(status, Cr.NS_ERROR_NET_TIMEOUT_EXTERNAL);
+}
+add_task(test_tls_fail_on_ws_server_over_proxy);
diff --git a/netwerk/test/unit/test_websocket_offline.js b/netwerk/test/unit/test_websocket_offline.js
new file mode 100644
index 0000000000..1f13879dbc
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_offline.js
@@ -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/. */
+"use strict";
+
+// checking to make sure we don't hang as per 1038304
+// offline so url isn't impt
+var url = "ws://localhost";
+var chan;
+var offlineStatus;
+
+var listener = {
+ onAcknowledge(aContext, aSize) {},
+ onBinaryMessageAvailable(aContext, aMsg) {},
+ onMessageAvailable(aContext, aMsg) {},
+ onServerClose(aContext, aCode, aReason) {},
+ onStart(aContext) {
+ // onStart is not called when a connection fails
+ Assert.ok(false);
+ },
+ onStop(aContext, aStatusCode) {
+ Assert.notEqual(aStatusCode, Cr.NS_OK);
+ Services.io.offline = offlineStatus;
+ do_test_finished();
+ },
+};
+
+function run_test() {
+ offlineStatus = Services.io.offline;
+ Services.io.offline = true;
+
+ try {
+ chan = Cc["@mozilla.org/network/protocol;1?name=ws"].createInstance(
+ Ci.nsIWebSocketChannel
+ );
+ chan.initLoadInfo(
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET
+ );
+
+ var uri = Services.io.newURI(url);
+ chan.asyncOpen(uri, url, {}, 0, listener, null);
+ do_test_pending();
+ } catch (x) {
+ dump("throwing " + x);
+ do_throw(x);
+ }
+}
diff --git a/netwerk/test/unit/test_websocket_server.js b/netwerk/test/unit/test_websocket_server.js
new file mode 100644
index 0000000000..a33413a02f
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_server.js
@@ -0,0 +1,317 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+add_setup(async function setup() {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+
+ registerCleanupFunction(async () => {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ });
+});
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function httpChannelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+async function channelOpenPromise(url, msg) {
+ let conn = new WebSocketConnection();
+ await conn.open(url);
+ conn.send(msg);
+ let res = await conn.receiveMessages();
+ conn.close();
+ let { status } = await conn.finished();
+ return [status, res];
+}
+
+// h1.1 direct
+async function test_h1_websocket_direct() {
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+ registerCleanupFunction(async () => wss.stop());
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test websocket";
+
+ let conn = new WebSocketConnection();
+ await conn.open(url);
+ conn.send(msg);
+ let mess1 = await conn.receiveMessages();
+ Assert.deepEqual(mess1, [msg]);
+
+ // Now send 3 more, and check that we received all of them
+ conn.send(msg);
+ conn.send(msg);
+ conn.send(msg);
+ let mess2 = [];
+ while (mess2.length < 3) {
+ // receive could return 1, 2 or all 3 replies.
+ mess2 = mess2.concat(await conn.receiveMessages());
+ }
+ Assert.deepEqual(mess2, [msg, msg, msg]);
+
+ conn.close();
+ let { status } = await conn.finished();
+
+ Assert.equal(status, Cr.NS_OK);
+}
+
+// h1 server with secure h1.1 proxy
+async function test_h1_ws_with_secure_h1_proxy() {
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h1.1 websocket with h1.1 secure proxy";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+
+ await proxy.stop();
+}
+
+async function test_h2_websocket_direct() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start();
+ registerCleanupFunction(async () => wss.stop());
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h2 websocket h2 direct";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+}
+
+// ws h1.1 with insecure h1.1 proxy
+async function test_h1_ws_with_h1_insecure_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", false);
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h1 websocket with h1 insecure proxy";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+
+ await proxy.stop();
+}
+
+// ws h1.1 with h2 proxy
+async function test_h1_ws_with_h2_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", false);
+
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketServer();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h1 websocket with h2 proxy";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+
+ await proxy.stop();
+}
+
+// ws h2 with insecure h1.1 proxy
+async function test_h2_ws_with_h1_insecure_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start();
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h2 websocket with h1 insecure proxy";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+
+ await proxy.stop();
+}
+
+// ws h2 with secure h1 proxy
+async function test_h2_ws_with_h1_secure_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+
+ let proxy = new NodeHTTPSProxyServer();
+ await proxy.start();
+
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h2 websocket with h1 secure proxy";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+
+ await proxy.stop();
+}
+
+// ws h2 with secure h2 proxy
+async function test_h2_ws_with_h2_proxy() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start(); // start and register proxy "filter"
+
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start(); // init port
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h2 websocket with h2 proxy";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+
+ await proxy.stop();
+}
+
+async function test_bug_1848013() {
+ Services.prefs.setBoolPref("network.http.http2.websockets", true);
+
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+
+ registerCleanupFunction(async () => {
+ await wss.stop();
+ await proxy.stop();
+ });
+
+ let wss = new NodeWebSocketHttp2Server();
+ await wss.start();
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+
+ // To create a h2 connection before the websocket one.
+ let chan = makeChan(`https://localhost:${wss.port()}/`);
+ await httpChannelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+
+ let url = `wss://localhost:${wss.port()}`;
+ const msg = "test h2 websocket with h1 insecure proxy";
+ let [status, res] = await channelOpenPromise(url, msg);
+ Assert.equal(status, Cr.NS_OK);
+ Assert.deepEqual(res, [msg]);
+
+ await proxy.stop();
+}
+
+add_task(test_h1_websocket_direct);
+add_task(test_h2_websocket_direct);
+add_task(test_h1_ws_with_secure_h1_proxy);
+add_task(test_h1_ws_with_h1_insecure_proxy);
+add_task(test_h1_ws_with_h2_proxy);
+add_task(test_h2_ws_with_h1_insecure_proxy);
+add_task(test_h2_ws_with_h1_secure_proxy);
+add_task(test_h2_ws_with_h2_proxy);
+add_task(test_bug_1848013);
diff --git a/netwerk/test/unit/test_websocket_server_multiclient.js b/netwerk/test/unit/test_websocket_server_multiclient.js
new file mode 100644
index 0000000000..33e730acbf
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_server_multiclient.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
+/* import-globals-from head_servers.js */
+/* import-globals-from head_websocket.js */
+
+// These test should basically match the ones in test_websocket_server.js,
+// but with multiple websocket clients making requests on the same server
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+// setup
+add_setup(async function setup() {
+ // turn off cert checking for these tests
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+});
+
+// append cleanup to cleanup queue
+registerCleanupFunction(async () => {
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ Services.prefs.clearUserPref("network.http.http2.websockets");
+});
+
+async function spinup_and_check(proxy_kind, ws_kind) {
+ let ws_h2 = true;
+ if (ws_kind == NodeWebSocketServer) {
+ info("not h2 ws");
+ ws_h2 = false;
+ }
+ Services.prefs.setBoolPref("network.http.http2.websockets", ws_h2);
+
+ let proxy;
+ if (proxy_kind) {
+ proxy = new proxy_kind();
+ await proxy.start();
+ registerCleanupFunction(async () => proxy.stop());
+ }
+
+ let wss = new ws_kind();
+ await wss.start();
+ registerCleanupFunction(async () => wss.stop());
+
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `wss://localhost:${wss.port()}`;
+
+ let conn1 = new WebSocketConnection();
+ await conn1.open(url);
+
+ let conn2 = new WebSocketConnection();
+ await conn2.open(url);
+
+ conn1.send("msg1");
+ conn2.send("msg2");
+
+ let mess2 = await conn2.receiveMessages();
+ Assert.deepEqual(mess2, ["msg2"]);
+
+ conn1.send("msg1 again");
+ let mess1 = [];
+ while (mess1.length < 2) {
+ // receive could return only the first or both replies.
+ mess1 = mess1.concat(await conn1.receiveMessages());
+ }
+ Assert.deepEqual(mess1, ["msg1", "msg1 again"]);
+
+ conn1.close();
+ conn2.close();
+ Assert.deepEqual({ status: Cr.NS_OK }, await conn1.finished());
+ Assert.deepEqual({ status: Cr.NS_OK }, await conn2.finished());
+ await wss.stop();
+
+ if (proxy_kind) {
+ await proxy.stop();
+ }
+}
+
+// h1.1 direct
+async function test_h1_websocket_direct() {
+ await spinup_and_check(null, NodeWebSocketServer);
+}
+
+// h2 direct
+async function test_h2_websocket_direct() {
+ await spinup_and_check(null, NodeWebSocketHttp2Server);
+}
+
+// ws h1.1 with secure h1.1 proxy
+async function test_h1_ws_with_secure_h1_proxy() {
+ await spinup_and_check(NodeHTTPSProxyServer, NodeWebSocketServer);
+}
+
+// ws h1.1 with insecure h1.1 proxy
+async function test_h1_ws_with_insecure_h1_proxy() {
+ await spinup_and_check(NodeHTTPProxyServer, NodeWebSocketServer);
+}
+
+// ws h1.1 with h2 proxy
+async function test_h1_ws_with_h2_proxy() {
+ await spinup_and_check(NodeHTTP2ProxyServer, NodeWebSocketServer);
+}
+
+// ws h2 with insecure h1.1 proxy
+async function test_h2_ws_with_insecure_h1_proxy() {
+ await spinup_and_check(NodeHTTPProxyServer, NodeWebSocketHttp2Server);
+}
+
+// ws h2 with secure h1 proxy
+async function test_h2_ws_with_secure_h1_proxy() {
+ await spinup_and_check(NodeHTTPSProxyServer, NodeWebSocketHttp2Server);
+}
+
+// ws h2 with secure h2 proxy
+async function test_h2_ws_with_h2_proxy() {
+ await spinup_and_check(NodeHTTP2ProxyServer, NodeWebSocketHttp2Server);
+}
+
+add_task(test_h1_websocket_direct);
+add_task(test_h2_websocket_direct);
+add_task(test_h1_ws_with_secure_h1_proxy);
+add_task(test_h1_ws_with_insecure_h1_proxy);
+add_task(test_h1_ws_with_h2_proxy);
+
+// any multi-client test with h2 websocket and any kind of proxy will fail/hang
+add_task(test_h2_ws_with_insecure_h1_proxy);
+add_task(test_h2_ws_with_secure_h1_proxy);
+add_task(test_h2_ws_with_h2_proxy);
diff --git a/netwerk/test/unit/test_websocket_with_h3_active.js b/netwerk/test/unit/test_websocket_with_h3_active.js
new file mode 100644
index 0000000000..f9ed2b08a0
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_with_h3_active.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+registerCleanupFunction(async () => {
+ http3_clear_prefs();
+});
+
+let wssUri;
+let httpsUri;
+
+add_task(async function pre_setup() {
+ let h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ wssUri = "wss://foo.example.com:" + h2Port + "/websocket";
+ httpsUri = "https://foo.example.com:" + h2Port + "/";
+ Services.prefs.setBoolPref("network.http.http3.support_version1", true);
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3");
+});
+
+WebSocketListener.prototype = {
+ onAcknowledge(aContext, aSize) {},
+ onBinaryMessageAvailable(aContext, aMsg) {},
+ onMessageAvailable(aContext, aMsg) {},
+ onServerClose(aContext, aCode, aReason) {},
+ onStart(aContext) {
+ this.finish();
+ },
+ onStop(aContext, aStatusCode) {},
+};
+
+function makeH2Chan() {
+ let chan = NetUtil.newChannel({
+ uri: httpsUri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+add_task(async function open_wss_when_h3_is_active() {
+ // Make an active connection using HTTP/3
+ let chanHttp1 = makeH2Chan(httpsUri);
+ await new Promise(resolve => {
+ chanHttp1.asyncOpen(
+ new ChannelListener(request => {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3");
+ resolve();
+ })
+ );
+ });
+
+ // Now try to connect ot a WebSocket on the same port -> this should not loop
+ // see bug 1717360.
+ let chan = Cc["@mozilla.org/network/protocol;1?name=wss"].createInstance(
+ Ci.nsIWebSocketChannel
+ );
+ chan.initLoadInfo(
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET
+ );
+
+ var uri = Services.io.newURI(wssUri);
+ var wsListener = new WebSocketListener();
+ await new Promise(resolve => {
+ wsListener.finish = resolve;
+ chan.asyncOpen(uri, wssUri, {}, 0, wsListener, null);
+ });
+
+ // Try to use https protocol, it should sttill use HTTP/3
+ let chanHttp2 = makeH2Chan(httpsUri);
+ await new Promise(resolve => {
+ chanHttp2.asyncOpen(
+ new ChannelListener(request => {
+ let httpVersion = "";
+ try {
+ httpVersion = request.protocolVersion;
+ } catch (e) {}
+ Assert.equal(httpVersion, "h3");
+ resolve();
+ })
+ );
+ });
+});
diff --git a/netwerk/test/unit/test_webtransport_simple.js b/netwerk/test/unit/test_webtransport_simple.js
new file mode 100644
index 0000000000..bd99654bc3
--- /dev/null
+++ b/netwerk/test/unit/test_webtransport_simple.js
@@ -0,0 +1,460 @@
+//
+// Simple WebTransport test
+//
+
+/* import-globals-from head_webtransport.js */
+
+"use strict";
+
+var h3Port;
+var host;
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.webtransport.datagrams.enabled");
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+});
+
+add_task(async function setup() {
+ await http3_setup_tests("h3");
+
+ Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true);
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ host = "foo.example.com:" + h3Port;
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ // eslint-disable-next-line no-async-promise-executor
+ return new Promise(async resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+function bytesFromString(str) {
+ return new TextEncoder().encode(str);
+}
+
+add_task(async function test_wt_datagram() {
+ let webTransport = NetUtil.newWebTransport();
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+
+ let pReady = new Promise(resolve => {
+ listener.ready = resolve;
+ });
+ let pSize = new Promise(resolve => {
+ listener.onMaxDatagramSize = resolve;
+ });
+ let pOutcome = new Promise(resolve => {
+ listener.onDatagramOutcome = resolve;
+ });
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/success`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+
+ await pReady;
+
+ webTransport.getMaxDatagramSize();
+ let size = await pSize;
+ info("max size:" + size);
+
+ let rawData = new Uint8Array(size);
+ rawData.fill(42);
+
+ webTransport.sendDatagram(rawData, 1);
+ let { id, outcome } = await pOutcome;
+ Assert.equal(id, 1);
+ Assert.equal(outcome, Ci.WebTransportSessionEventListener.SENT);
+
+ let chan = makeChan(`https://${host}/get_webtransport_datagram`);
+ let [req, buffer] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3");
+
+ Assert.deepEqual(bytesFromString(buffer), rawData);
+
+ webTransport.getMaxDatagramSize();
+ size = await pSize;
+ info("max size:" + size);
+
+ rawData = new Uint8Array(size + 1);
+ webTransport.sendDatagram(rawData, 2);
+
+ pOutcome = new Promise(resolve => {
+ listener.onDatagramOutcome = resolve;
+ });
+ ({ id, outcome } = await pOutcome);
+ Assert.equal(id, 2);
+ Assert.equal(
+ outcome,
+ Ci.WebTransportSessionEventListener.DROPPED_TOO_MUCH_DATA
+ );
+
+ webTransport.closeSession(0, "");
+});
+
+add_task(async function test_connect_wt() {
+ let webTransport = NetUtil.newWebTransport();
+
+ await new Promise(resolve => {
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+ listener.ready = resolve;
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/success`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+ });
+
+ webTransport.closeSession(0, "");
+});
+
+add_task(async function test_redirect_wt() {
+ let webTransport = NetUtil.newWebTransport();
+
+ await new Promise(resolve => {
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+
+ listener.closed = resolve;
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/redirect`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+ });
+});
+
+add_task(async function test_reject() {
+ let webTransport = NetUtil.newWebTransport();
+
+ await new Promise(resolve => {
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+ listener.closed = resolve;
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/reject`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+ });
+});
+
+async function test_closed(path) {
+ let webTransport = NetUtil.newWebTransport();
+
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+
+ let pReady = new Promise(resolve => {
+ listener.ready = resolve;
+ });
+ let pClose = new Promise(resolve => {
+ listener.closed = resolve;
+ });
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}${path}`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+
+ await pReady;
+ await pClose;
+}
+
+add_task(async function test_closed_0ms() {
+ await test_closed("/closeafter0ms");
+});
+
+add_task(async function test_closed_100ms() {
+ await test_closed("/closeafter100ms");
+});
+
+add_task(async function test_wt_stream_create() {
+ let webTransport = NetUtil.newWebTransport().QueryInterface(
+ Ci.nsIWebTransport
+ );
+
+ await new Promise(resolve => {
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+ listener.ready = resolve;
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/success`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+ });
+
+ await streamCreatePromise(webTransport, true);
+ await streamCreatePromise(webTransport, false);
+
+ webTransport.closeSession(0, "");
+});
+
+add_task(async function test_wt_stream_send_and_stats() {
+ let webTransport = NetUtil.newWebTransport().QueryInterface(
+ Ci.nsIWebTransport
+ );
+
+ await new Promise(resolve => {
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+ listener.ready = resolve;
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/success`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+ });
+
+ let stream = await streamCreatePromise(webTransport, false);
+ let outputStream = stream.outputStream;
+
+ let data = "1234567890ABC";
+ outputStream.write(data, data.length);
+
+ // We need some time to send the packet out.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ let stats = await sendStreamStatsPromise(stream);
+ Assert.equal(stats.bytesSent, data.length);
+
+ webTransport.closeSession(0, "");
+});
+
+add_task(async function test_wt_receive_stream_and_stats() {
+ let webTransport = NetUtil.newWebTransport().QueryInterface(
+ Ci.nsIWebTransport
+ );
+
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+
+ let pReady = new Promise(resolve => {
+ listener.ready = resolve;
+ });
+ let pStreamReady = new Promise(resolve => {
+ listener.streamAvailable = resolve;
+ });
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/create_unidi_stream_and_hello`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+
+ await pReady;
+ let stream = await pStreamReady;
+
+ let data = await new Promise(resolve => {
+ let handler = new inputStreamReader().QueryInterface(
+ Ci.nsIInputStreamCallback
+ );
+ handler.finish = resolve;
+ let inputStream = stream.inputStream;
+ inputStream.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ });
+
+ info("data: " + data);
+ Assert.equal(data, "qwerty");
+
+ let stats = await receiveStreamStatsPromise(stream);
+ Assert.equal(stats.bytesReceived, data.length);
+
+ stream.sendStopSending(0);
+
+ webTransport.closeSession(0, "");
+});
+
+add_task(async function test_wt_outgoing_bidi_stream() {
+ let webTransport = NetUtil.newWebTransport().QueryInterface(
+ Ci.nsIWebTransport
+ );
+
+ await new Promise(resolve => {
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+ listener.ready = resolve;
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/success`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+ });
+
+ let stream = await streamCreatePromise(webTransport, true);
+ let outputStream = stream.outputStream;
+
+ let data = "1234567";
+ outputStream.write(data, data.length);
+
+ let received = await new Promise(resolve => {
+ let handler = new inputStreamReader().QueryInterface(
+ Ci.nsIInputStreamCallback
+ );
+ handler.finish = resolve;
+ let inputStream = stream.inputStream;
+ inputStream.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ });
+
+ info("received: " + received);
+ Assert.equal(received, data);
+
+ let stats = await sendStreamStatsPromise(stream);
+ Assert.equal(stats.bytesSent, data.length);
+
+ stats = await receiveStreamStatsPromise(stream);
+ Assert.equal(stats.bytesReceived, data.length);
+
+ webTransport.closeSession(0, "");
+});
+
+add_task(async function test_wt_incoming_bidi_stream() {
+ let webTransport = NetUtil.newWebTransport().QueryInterface(
+ Ci.nsIWebTransport
+ );
+
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+
+ let pReady = new Promise(resolve => {
+ listener.ready = resolve;
+ });
+ let pStreamReady = new Promise(resolve => {
+ listener.streamAvailable = resolve;
+ });
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/create_bidi_stream`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+
+ await pReady;
+ let stream = await pStreamReady;
+
+ let outputStream = stream.outputStream;
+
+ let data = "12345678";
+ outputStream.write(data, data.length);
+
+ let received = await new Promise(resolve => {
+ let handler = new inputStreamReader().QueryInterface(
+ Ci.nsIInputStreamCallback
+ );
+ handler.finish = resolve;
+ let inputStream = stream.inputStream;
+ inputStream.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ });
+
+ info("received: " + received);
+ Assert.equal(received, data);
+
+ let stats = await sendStreamStatsPromise(stream);
+ Assert.equal(stats.bytesSent, data.length);
+
+ stats = await receiveStreamStatsPromise(stream);
+ Assert.equal(stats.bytesReceived, data.length);
+
+ webTransport.closeSession(0, "");
+});
+
+async function createWebTransportAndConnect() {
+ let webTransport = NetUtil.newWebTransport();
+
+ await new Promise(resolve => {
+ let listener = new WebTransportListener().QueryInterface(
+ Ci.WebTransportSessionEventListener
+ );
+ listener.ready = resolve;
+
+ webTransport.asyncConnect(
+ NetUtil.newURI(`https://${host}/success`),
+ [],
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ listener
+ );
+ });
+
+ return webTransport;
+}
+
+add_task(async function test_multple_webtransport_connnection() {
+ let webTransports = [];
+ for (let i = 0; i < 3; i++) {
+ let transport = await createWebTransportAndConnect();
+ webTransports.push(transport);
+ }
+
+ let first = webTransports[0];
+ await streamCreatePromise(first, true);
+
+ for (let i = 0; i < 3; i++) {
+ webTransports[i].closeSession(0, "");
+ }
+});
diff --git a/netwerk/test/unit/test_xmlhttprequest.js b/netwerk/test/unit/test_xmlhttprequest.js
new file mode 100644
index 0000000000..7363a54cfd
--- /dev/null
+++ b/netwerk/test/unit/test_xmlhttprequest.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "<?xml version='1.0' ?><root>0123456789</root>";
+
+function createXHR(async) {
+ var xhr = new XMLHttpRequest();
+ xhr.open(
+ "GET",
+ "http://localhost:" + httpserver.identity.primaryPort + testpath,
+ async
+ );
+ return xhr;
+}
+
+function checkResults(xhr) {
+ if (xhr.readyState != 4) {
+ return false;
+ }
+
+ Assert.equal(xhr.status, 200);
+ Assert.equal(xhr.responseText, httpbody);
+
+ var root_node = xhr.responseXML.getElementsByTagName("root").item(0);
+ Assert.equal(root_node.firstChild.data, "0123456789");
+ return true;
+}
+
+function run_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ // Test sync XHR sending
+ var sync = createXHR(false);
+ sync.send(null);
+ checkResults(sync);
+
+ // Test async XHR sending
+ let async = createXHR(true);
+ async.addEventListener("readystatechange", function (event) {
+ if (checkResults(async)) {
+ httpserver.stop(do_test_finished);
+ }
+ });
+ async.send(null);
+ do_test_pending();
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/xml", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
diff --git a/netwerk/test/unit/trr_common.js b/netwerk/test/unit/trr_common.js
new file mode 100644
index 0000000000..2ddd556983
--- /dev/null
+++ b/netwerk/test/unit/trr_common.js
@@ -0,0 +1,1235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_trr.js */
+/* import-globals-from head_http3.js */
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const TRR_Domain = "foo.example.com";
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+async function SetParentalControlEnabled(aEnabled) {
+ let parentalControlsService = {
+ parentalControlsEnabled: aEnabled,
+ QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
+ };
+ let cid = MockRegistrar.register(
+ "@mozilla.org/parental-controls-service;1",
+ parentalControlsService
+ );
+ Services.dns.reloadParentalControlEnabled();
+ MockRegistrar.unregister(cid);
+}
+
+let runningOHTTPTests = false;
+let h2Port;
+
+function setModeAndURIForODoH(mode, path) {
+ Services.prefs.setIntPref("network.trr.mode", mode);
+ if (path.substr(0, 4) == "doh?") {
+ path = path.replace("doh?", "odoh?");
+ }
+
+ Services.prefs.setCharPref("network.trr.odoh.target_path", `${path}`);
+}
+
+function setModeAndURIForOHTTP(mode, path, domain) {
+ Services.prefs.setIntPref("network.trr.mode", mode);
+ if (domain) {
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.uri",
+ `https://${domain}:${h2Port}/${path}`
+ );
+ } else {
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.uri",
+ `https://${TRR_Domain}:${h2Port}/${path}`
+ );
+ }
+}
+
+function setModeAndURI(mode, path, domain) {
+ if (runningOHTTPTests) {
+ setModeAndURIForOHTTP(mode, path, domain);
+ } else {
+ Services.prefs.setIntPref("network.trr.mode", mode);
+ if (domain) {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://${domain}:${h2Port}/${path}`
+ );
+ } else {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://${TRR_Domain}:${h2Port}/${path}`
+ );
+ }
+ }
+}
+
+async function test_A_record() {
+ info("Verifying a basic A record");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2"); // TRR-first
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+
+ info("Verifying a basic A record - without bootstrapping");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=3.3.3.3"); // TRR-only
+
+ // Clear bootstrap address and add DoH endpoint hostname to local domains
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+ Services.prefs.setCharPref("network.dns.localDomains", TRR_Domain);
+
+ await new TRRDNSListener("bar.example.com", "3.3.3.3");
+
+ Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+
+ info("Verify that the cached record is used when DoH endpoint is down");
+ // Don't clear the cache. That is what we're checking.
+ setModeAndURI(3, "404");
+
+ await new TRRDNSListener("bar.example.com", "3.3.3.3");
+ info("verify working credentials in DOH request");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=4.4.4.4&auth=true");
+ Services.prefs.setCharPref("network.trr.credentials", "user:password");
+
+ await new TRRDNSListener("bar.example.com", "4.4.4.4");
+
+ info("Verify failing credentials in DOH request");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=4.4.4.4&auth=true");
+ Services.prefs.setCharPref("network.trr.credentials", "evil:person");
+
+ let { inStatus } = await new TRRDNSListener(
+ "wrong.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ Services.prefs.clearUserPref("network.trr.credentials");
+}
+
+async function test_AAAA_records() {
+ info("Verifying AAAA record");
+
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2020:2020::2020&delayIPv4=100");
+
+ await new TRRDNSListener("aaaa.example.com", "2020:2020::2020");
+
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2020:2020::2020&delayIPv6=100");
+
+ await new TRRDNSListener("aaaa.example.com", "2020:2020::2020");
+
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2020:2020::2020");
+
+ await new TRRDNSListener("aaaa.example.com", "2020:2020::2020");
+}
+
+async function test_RFC1918() {
+ info("Verifying that RFC1918 address from the server is rejected by default");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=192.168.0.1");
+
+ let { inStatus } = await new TRRDNSListener(
+ "rfc1918.example.com",
+ undefined,
+ false
+ );
+
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ setModeAndURI(3, "doh?responseIP=::ffff:192.168.0.1");
+ ({ inStatus } = await new TRRDNSListener(
+ "rfc1918-ipv6.example.com",
+ undefined,
+ false
+ ));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ info("Verify RFC1918 address from the server is fine when told so");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=192.168.0.1");
+ Services.prefs.setBoolPref("network.trr.allow-rfc1918", true);
+ await new TRRDNSListener("rfc1918.example.com", "192.168.0.1");
+ setModeAndURI(3, "doh?responseIP=::ffff:192.168.0.1");
+
+ await new TRRDNSListener("rfc1918-ipv6.example.com", "::ffff:192.168.0.1");
+
+ Services.prefs.clearUserPref("network.trr.allow-rfc1918");
+}
+
+async function test_GET_ECS() {
+ info("Verifying resolution via GET with ECS disabled");
+ Services.dns.clearCache(true);
+ // The template part should be discarded
+ setModeAndURI(3, "doh{?dns}");
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+
+ await new TRRDNSListener("ecs.example.com", "5.5.5.5");
+
+ info("Verifying resolution via GET with ECS enabled");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh");
+ Services.prefs.setBoolPref("network.trr.disable-ECS", false);
+
+ await new TRRDNSListener("get.example.com", "5.5.5.5");
+
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+}
+
+async function test_timeout_mode3() {
+ info("Verifying that a short timeout causes failure with a slow server");
+ Services.dns.clearCache(true);
+ // First, mode 3.
+ setModeAndURI(3, "doh?noResponse=true");
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
+
+ let { inStatus } = await new TRRDNSListener(
+ "timeout.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ // Now for mode 2
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?noResponse=true");
+
+ await new TRRDNSListener("timeout.example.com", "127.0.0.1"); // Should fallback
+
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+}
+
+async function test_trr_retry() {
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+
+ info("Test fallback to native");
+ Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", false);
+ setModeAndURI(2, "doh?noResponse=true");
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
+
+ await new TRRDNSListener("timeout.example.com", {
+ expectedAnswer: "127.0.0.1",
+ });
+
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+
+ info("Test Retry Success");
+ Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", true);
+
+ let chan = makeChan(
+ `https://foo.example.com:${h2Port}/reset-doh-request-count`,
+ Ci.nsIRequest.TRR_DISABLED_MODE
+ );
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null))
+ );
+
+ setModeAndURI(2, "doh?responseIP=2.2.2.2&retryOnDecodeFailure=true");
+ await new TRRDNSListener("retry_ok.example.com", "2.2.2.2");
+
+ info("Test Retry Failed");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
+ await new TRRDNSListener("retry_ng.example.com", "127.0.0.1");
+}
+
+async function test_strict_native_fallback() {
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", true);
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+
+ info("First a timeout case");
+ setModeAndURI(2, "doh?noResponse=true");
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
+ Services.prefs.setIntPref(
+ "network.trr.strict_fallback_request_timeout_ms",
+ 10
+ );
+
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback_allow_timeouts",
+ false
+ );
+
+ let { inStatus } = await new TRRDNSListener(
+ "timeout.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("timeout.example.com", undefined, false);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback_allow_timeouts",
+ true
+ );
+ await new TRRDNSListener("timeout.example.com", {
+ expectedAnswer: "127.0.0.1",
+ });
+
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback_allow_timeouts",
+ false
+ );
+
+ info("Now a connection error");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+ Services.prefs.clearUserPref(
+ "network.trr.strict_fallback_request_timeout_ms"
+ );
+ ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ info("Now a decode error");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
+ ({ inStatus } = await new TRRDNSListener(
+ "bar.example.com",
+ undefined,
+ false
+ ));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ if (!mozinfo.socketprocess_networking) {
+ // Confirmation state isn't passed cross-process.
+ info("Now with confirmation failed - should fallback");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
+ Services.prefs.setCharPref("network.trr.confirmationNS", "example.com");
+ await TestUtils.waitForCondition(
+ // 3 => CONFIRM_FAILED, 4 => CONFIRM_TRYING_FAILED
+ () =>
+ Services.dns.currentTrrConfirmationState == 3 ||
+ Services.dns.currentTrrConfirmationState == 4,
+ `Timed out waiting for confirmation failure. Currently ${Services.dns.currentTrrConfirmationState}`,
+ 1,
+ 5000
+ );
+ await new TRRDNSListener("bar.example.com", "127.0.0.1"); // Should fallback
+ }
+
+ info("Now a successful case.");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ if (!mozinfo.socketprocess_networking) {
+ // Only need to reset confirmation state if we messed with it before.
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ await TestUtils.waitForCondition(
+ // 5 => CONFIRM_DISABLED
+ () => Services.dns.currentTrrConfirmationState == 5,
+ `Timed out waiting for confirmation disabled. Currently ${Services.dns.currentTrrConfirmationState}`,
+ 1,
+ 5000
+ );
+ }
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+
+ info("Now without strict fallback mode, timeout case");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?noResponse=true");
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
+ Services.prefs.setIntPref(
+ "network.trr.strict_fallback_request_timeout_ms",
+ 10
+ );
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+
+ await new TRRDNSListener("timeout.example.com", "127.0.0.1"); // Should fallback
+
+ info("Now a connection error");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+ Services.prefs.clearUserPref(
+ "network.trr.strict_fallback_request_timeout_ms"
+ );
+ await new TRRDNSListener("closeme.com", "127.0.0.1"); // Should fallback
+
+ info("Now a decode error");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
+ await new TRRDNSListener("bar.example.com", "127.0.0.1"); // Should fallback
+
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+ Services.prefs.clearUserPref(
+ "network.trr.strict_fallback_request_timeout_ms"
+ );
+}
+
+async function test_no_answers_fallback() {
+ info("Verfiying that we correctly fallback to Do53 when no answers from DoH");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=none"); // TRR-first
+
+ await new TRRDNSListener("confirm.example.com", "127.0.0.1");
+
+ info("Now in strict mode - no fallback");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("confirm.example.com", "127.0.0.1");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+}
+
+async function test_404_fallback() {
+ info("Verfiying that we correctly fallback to Do53 when DoH sends 404");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "404"); // TRR-first
+
+ await new TRRDNSListener("test404.example.com", "127.0.0.1");
+
+ info("Now in strict mode - no fallback");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+ Services.dns.clearCache(true);
+ let { inStatus } = await new TRRDNSListener("test404.example.com", {
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+}
+
+async function test_mode_1_and_4() {
+ info("Verifying modes 1 and 4 are treated as TRR-off");
+ for (let mode of [1, 4]) {
+ Services.dns.clearCache(true);
+ setModeAndURI(mode, "doh?responseIP=2.2.2.2");
+ Assert.equal(
+ Services.dns.currentTrrMode,
+ 5,
+ "Effective TRR mode should be 5"
+ );
+ }
+}
+
+async function test_CNAME() {
+ info("Checking that we follow a CNAME correctly");
+ Services.dns.clearCache(true);
+ // The dns-cname path alternates between sending us a CNAME pointing to
+ // another domain, and an A record. If we follow the cname correctly, doing
+ // a lookup with this path as the DoH URI should resolve to that A record.
+ setModeAndURI(3, "dns-cname");
+
+ await new TRRDNSListener("cname.example.com", "99.88.77.66");
+
+ info("Verifying that we bail out when we're thrown into a CNAME loop");
+ Services.dns.clearCache(true);
+ // First mode 3.
+ setModeAndURI(3, "doh?responseIP=none&cnameloop=true");
+
+ let { inStatus } = await new TRRDNSListener(
+ "test18.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ // Now mode 2.
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=none&cnameloop=true");
+
+ await new TRRDNSListener("test20.example.com", "127.0.0.1"); // Should fallback
+
+ info("Check that we correctly handle CNAME bundled with an A record");
+ Services.dns.clearCache(true);
+ // "dns-cname-a" path causes server to send a CNAME as well as an A record
+ setModeAndURI(3, "dns-cname-a");
+
+ await new TRRDNSListener("cname-a.example.com", "9.8.7.6");
+}
+
+async function test_name_mismatch() {
+ info("Verify that records that don't match the requested name are rejected");
+ Services.dns.clearCache(true);
+ // Setting hostname param tells server to always send record for bar.example.com
+ // regardless of what was requested.
+ setModeAndURI(3, "doh?hostname=mismatch.example.com");
+
+ let { inStatus } = await new TRRDNSListener(
+ "bar.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+}
+
+async function test_mode_2() {
+ info("Checking that TRR result is used in mode 2");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=192.192.192.192");
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
+
+ await new TRRDNSListener("bar.example.com", "192.192.192.192");
+
+ info("Now in strict mode");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("bar.example.com", "192.192.192.192");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+}
+
+async function test_excluded_domains() {
+ info("Checking that Do53 is used for names in excluded-domains list");
+ for (let strictMode of [true, false]) {
+ info("Strict mode: " + strictMode);
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback",
+ strictMode
+ );
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=192.192.192.192");
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "bar.example.com"
+ );
+
+ await new TRRDNSListener("bar.example.com", "127.0.0.1"); // Do53 result
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "example.com");
+
+ await new TRRDNSListener("bar.example.com", "127.0.0.1");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "foo.test.com, bar.example.com"
+ );
+ await new TRRDNSListener("bar.example.com", "127.0.0.1");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "bar.example.com, foo.test.com"
+ );
+
+ await new TRRDNSListener("bar.example.com", "127.0.0.1");
+
+ Services.prefs.clearUserPref("network.trr.excluded-domains");
+ }
+}
+
+function topicObserved(topic) {
+ return new Promise(resolve => {
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == topic) {
+ Services.obs.removeObserver(observer, topic);
+ resolve(aData);
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ });
+}
+
+async function test_captiveportal_canonicalURL() {
+ info("Check that captivedetect.canonicalURL is resolved via native DNS");
+ for (let strictMode of [true, false]) {
+ info("Strict mode: " + strictMode);
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback",
+ strictMode
+ );
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ const cpServer = new HttpServer();
+ cpServer.registerPathHandler(
+ "/cp",
+ function handleRawData(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.bodyOutputStream.write("data", 4);
+ }
+ );
+ cpServer.start(-1);
+ cpServer.identity.setPrimary(
+ "http",
+ "detectportal.firefox.com",
+ cpServer.identity.primaryPort
+ );
+ let cpPromise = topicObserved("captive-portal-login");
+
+ Services.prefs.setCharPref(
+ "captivedetect.canonicalURL",
+ `http://detectportal.firefox.com:${cpServer.identity.primaryPort}/cp`
+ );
+ Services.prefs.setBoolPref("network.captive-portal-service.testMode", true);
+ Services.prefs.setBoolPref("network.captive-portal-service.enabled", true);
+
+ // The captive portal has to have used native DNS, otherwise creating
+ // a socket to a non-local IP would trigger a crash.
+ await cpPromise;
+ // Simply resolving the captive portal domain should still use TRR
+ await new TRRDNSListener("detectportal.firefox.com", "2.2.2.2");
+
+ Services.prefs.clearUserPref("network.captive-portal-service.enabled");
+ Services.prefs.clearUserPref("network.captive-portal-service.testMode");
+ Services.prefs.clearUserPref("captivedetect.canonicalURL");
+
+ await new Promise(resolve => cpServer.stop(resolve));
+ }
+}
+
+async function test_parentalcontrols() {
+ info("Check that DoH isn't used when parental controls are enabled");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await SetParentalControlEnabled(true);
+ await new TRRDNSListener("www.example.com", "127.0.0.1");
+ await SetParentalControlEnabled(false);
+
+ info("Now in strict mode");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await SetParentalControlEnabled(true);
+ await new TRRDNSListener("www.example.com", "127.0.0.1");
+ await SetParentalControlEnabled(false);
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+}
+
+async function test_builtin_excluded_domains() {
+ info("Verifying Do53 is used for domains in builtin-excluded-domians list");
+ for (let strictMode of [true, false]) {
+ info("Strict mode: " + strictMode);
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback",
+ strictMode
+ );
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "bar.example.com"
+ );
+ await new TRRDNSListener("bar.example.com", "127.0.0.1");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "example.com"
+ );
+ await new TRRDNSListener("bar.example.com", "127.0.0.1");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "foo.test.com, bar.example.com"
+ );
+ await new TRRDNSListener("bar.example.com", "127.0.0.1");
+ await new TRRDNSListener("foo.test.com", "127.0.0.1");
+ }
+}
+
+async function test_excluded_domains_mode3() {
+ info("Checking Do53 is used for names in excluded-domains list in mode 3");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=192.192.192.192");
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
+
+ await new TRRDNSListener("excluded", "192.192.192.192", true);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded");
+
+ await new TRRDNSListener("excluded", "127.0.0.1");
+
+ // Test .local
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded,local");
+
+ await new TRRDNSListener("test.local", "127.0.0.1");
+
+ // Test .other
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "excluded,local,other"
+ );
+
+ await new TRRDNSListener("domain.other", "127.0.0.1");
+}
+
+async function test25e() {
+ info("Check captivedetect.canonicalURL is resolved via native DNS in mode 3");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=192.192.192.192");
+
+ const cpServer = new HttpServer();
+ cpServer.registerPathHandler(
+ "/cp",
+ function handleRawData(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.bodyOutputStream.write("data", 4);
+ }
+ );
+ cpServer.start(-1);
+ cpServer.identity.setPrimary(
+ "http",
+ "detectportal.firefox.com",
+ cpServer.identity.primaryPort
+ );
+ let cpPromise = topicObserved("captive-portal-login");
+
+ Services.prefs.setCharPref(
+ "captivedetect.canonicalURL",
+ `http://detectportal.firefox.com:${cpServer.identity.primaryPort}/cp`
+ );
+ Services.prefs.setBoolPref("network.captive-portal-service.testMode", true);
+ Services.prefs.setBoolPref("network.captive-portal-service.enabled", true);
+
+ // The captive portal has to have used native DNS, otherwise creating
+ // a socket to a non-local IP would trigger a crash.
+ await cpPromise;
+ // // Simply resolving the captive portal domain should still use TRR
+ await new TRRDNSListener("detectportal.firefox.com", "192.192.192.192");
+
+ Services.prefs.clearUserPref("network.captive-portal-service.enabled");
+ Services.prefs.clearUserPref("network.captive-portal-service.testMode");
+ Services.prefs.clearUserPref("captivedetect.canonicalURL");
+
+ await new Promise(resolve => cpServer.stop(resolve));
+}
+
+async function test_parentalcontrols_mode3() {
+ info("Check DoH isn't used when parental controls are enabled in mode 3");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=192.192.192.192");
+ await SetParentalControlEnabled(true);
+ await new TRRDNSListener("www.example.com", "127.0.0.1");
+ await SetParentalControlEnabled(false);
+}
+
+async function test_builtin_excluded_domains_mode3() {
+ info("Check Do53 used for domains in builtin-excluded-domians list, mode 3");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=192.192.192.192");
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "excluded"
+ );
+
+ await new TRRDNSListener("excluded", "127.0.0.1");
+
+ // Test .local
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "excluded,local"
+ );
+
+ await new TRRDNSListener("test.local", "127.0.0.1");
+
+ // Test .other
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "excluded,local,other"
+ );
+
+ await new TRRDNSListener("domain.other", "127.0.0.1");
+}
+
+async function count_cookies() {
+ info("Check that none of the requests have set any cookies.");
+ Assert.equal(Services.cookies.countCookiesFromHost("example.com"), 0);
+ Assert.equal(Services.cookies.countCookiesFromHost("foo.example.com."), 0);
+}
+
+async function test_connection_closed() {
+ info("Check we handle it correctly when the connection is closed");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2.2.2.2");
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ // We don't need to wait for 30 seconds for the request to fail
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 500);
+ // bootstrap
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
+
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+
+ // makes the TRR connection shut down.
+ let { inStatus } = await new TRRDNSListener("closeme.com", undefined, false);
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ await new TRRDNSListener("bar2.example.com", "2.2.2.2");
+
+ // No bootstrap this time
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded,local");
+ Services.prefs.setCharPref("network.dns.localDomains", TRR_Domain);
+
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+
+ // makes the TRR connection shut down.
+ ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ await new TRRDNSListener("bar2.example.com", "2.2.2.2");
+
+ // No local domains either
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+
+ // makes the TRR connection shut down.
+ ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ await new TRRDNSListener("bar2.example.com", "2.2.2.2");
+
+ // Now make sure that even in mode 3 without a bootstrap address
+ // we are able to restart the TRR connection if it drops - the TRR service
+ // channel will use regular DNS to resolve the TRR address.
+ Services.dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+
+ // makes the TRR connection shut down.
+ ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("bar2.example.com", "2.2.2.2");
+
+ // This test exists to document what happens when we're in TRR only mode
+ // and we don't set a bootstrap address. We use DNS to resolve the
+ // initial URI, but if the connection fails, we don't fallback to DNS
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=9.9.9.9");
+ Services.prefs.setCharPref("network.dns.localDomains", "closeme.com");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+
+ await new TRRDNSListener("bar.example.com", "9.9.9.9");
+
+ // makes the TRR connection shut down. Should fallback to DNS
+ await new TRRDNSListener("closeme.com", "127.0.0.1");
+ // TRR should be back up again
+ await new TRRDNSListener("bar2.example.com", "9.9.9.9");
+}
+
+async function test_fetch_time() {
+ info("Verifying timing");
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2&delayIPv4=20");
+
+ await new TRRDNSListener("bar_time.example.com", "2.2.2.2", true, 20);
+
+ // gets an error from DoH. It will fall back to regular DNS. The TRR timing should be 0.
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "404&delayIPv4=20");
+
+ await new TRRDNSListener("bar_time1.example.com", "127.0.0.1", true, 0);
+
+ // check an excluded domain. It should fall back to regular DNS. The TRR timing should be 0.
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "bar_time2.example.com"
+ );
+ for (let strictMode of [true, false]) {
+ info("Strict mode: " + strictMode);
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback",
+ strictMode
+ );
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2&delayIPv4=20");
+ await new TRRDNSListener("bar_time2.example.com", "127.0.0.1", true, 0);
+ }
+
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+
+ // verify RFC1918 address from the server is rejected and the TRR timing will be not set because the response will be from the native resolver.
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=192.168.0.1&delayIPv4=20");
+ await new TRRDNSListener("rfc1918_time.example.com", "127.0.0.1", true, 0);
+}
+
+async function test_fqdn() {
+ info("Test that we handle FQDN encoding and decoding properly");
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=9.8.7.6");
+
+ await new TRRDNSListener("fqdn.example.org.", "9.8.7.6");
+
+ // GET
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ await new TRRDNSListener("fqdn_get.example.org.", "9.8.7.6");
+
+ Services.prefs.clearUserPref("network.trr.useGET");
+}
+
+async function test_ipv6_trr_fallback() {
+ info("Testing fallback with ipv6");
+ Services.dns.clearCache(true);
+
+ setModeAndURI(2, "doh?responseIP=4.4.4.4");
+ const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+ );
+ gOverride.addIPOverride("ipv6.host.com", "1:1::2");
+
+ // Should not fallback to Do53 because A request for ipv6.host.com returns
+ // 4.4.4.4
+ let { inStatus } = await new TRRDNSListener("ipv6.host.com", {
+ flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ expectedSuccess: false,
+ });
+ equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+
+ // This time both requests fail, so we do fall back
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=none");
+ await new TRRDNSListener("ipv6.host.com", "1:1::2");
+
+ info("In strict mode, the lookup should fail when both reqs fail.");
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+ setModeAndURI(2, "doh?responseIP=none");
+ await new TRRDNSListener("ipv6.host.com", "1:1::2");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+
+ override.clearOverrides();
+}
+
+async function test_ipv4_trr_fallback() {
+ info("Testing fallback with ipv4");
+ Services.dns.clearCache(true);
+
+ setModeAndURI(2, "doh?responseIP=1:2::3");
+ const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+ );
+ gOverride.addIPOverride("ipv4.host.com", "3.4.5.6");
+
+ // Should not fallback to Do53 because A request for ipv4.host.com returns
+ // 1:2::3
+ let { inStatus } = await new TRRDNSListener("ipv4.host.com", {
+ flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ expectedSuccess: false,
+ });
+ equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+
+ // This time both requests fail, so we do fall back
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=none");
+ await new TRRDNSListener("ipv4.host.com", "3.4.5.6");
+
+ // No fallback with strict mode.
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+ setModeAndURI(2, "doh?responseIP=none");
+ await new TRRDNSListener("ipv4.host.com", "3.4.5.6");
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
+
+ override.clearOverrides();
+}
+
+async function test_no_retry_without_doh() {
+ info("Bug 1648147 - if the TRR returns 0.0.0.0 we should not retry with DNS");
+ Services.prefs.setBoolPref("network.trr.fallback-on-zero-response", false);
+
+ async function test(url, ip) {
+ setModeAndURI(2, `doh?responseIP=${ip}`);
+
+ // Requests to 0.0.0.0 are usually directed to localhost, so let's use a port
+ // we know isn't being used - 666 (Doom)
+ let chan = makeChan(url, Ci.nsIRequest.TRR_DEFAULT_MODE);
+ let statusCounter = {
+ statusCount: {},
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIProgressEventSink",
+ ]),
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+ onProgress(request, progress, progressMax) {},
+ onStatus(request, status, statusArg) {
+ this.statusCount[status] = 1 + (this.statusCount[status] || 0);
+ },
+ };
+ chan.notificationCallbacks = statusCounter;
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
+ );
+ equal(
+ statusCounter.statusCount[0x4b000b],
+ 1,
+ "Expecting only one instance of NS_NET_STATUS_RESOLVED_HOST"
+ );
+ equal(
+ statusCounter.statusCount[0x4b0007],
+ 1,
+ "Expecting only one instance of NS_NET_STATUS_CONNECTING_TO"
+ );
+ }
+
+ for (let strictMode of [true, false]) {
+ info("Strict mode: " + strictMode);
+ Services.prefs.setBoolPref(
+ "network.trr.strict_native_fallback",
+ strictMode
+ );
+ await test(`http://unknown.ipv4.stuff:666/path`, "0.0.0.0");
+ await test(`http://unknown.ipv6.stuff:666/path`, "::");
+ }
+}
+
+async function test_connection_reuse_and_cycling() {
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 500);
+ Services.prefs.setIntPref(
+ "network.trr.strict_fallback_request_timeout_ms",
+ 500
+ );
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 500);
+
+ setModeAndURI(2, `doh?responseIP=9.8.7.6`);
+ Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
+ Services.prefs.setCharPref("network.trr.confirmationNS", "example.com");
+ await TestUtils.waitForCondition(
+ // 2 => CONFIRM_OK
+ () => Services.dns.currentTrrConfirmationState == 2,
+ `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
+ 1,
+ 5000
+ );
+
+ // Setting conncycle=true in the URI. Server will start logging reqs.
+ // We will do a specific sequence of lookups, then fetch the log from
+ // the server and check that it matches what we'd expect.
+ setModeAndURI(2, `doh?responseIP=9.8.7.6&conncycle=true`);
+ await TestUtils.waitForCondition(
+ // 2 => CONFIRM_OK
+ () => Services.dns.currentTrrConfirmationState == 2,
+ `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
+ 1,
+ 5000
+ );
+ // Confirmation upon uri-change will have created one req.
+
+ // Two reqs for each bar1 and bar2 - A + AAAA.
+ await new TRRDNSListener("bar1.example.org.", "9.8.7.6");
+ await new TRRDNSListener("bar2.example.org.", "9.8.7.6");
+ // Total so far: (1) + 2 + 2 = 5
+
+ // Two reqs that fail, one Confirmation req, two retried reqs that succeed.
+ await new TRRDNSListener("newconn.example.org.", "9.8.7.6");
+ await TestUtils.waitForCondition(
+ // 2 => CONFIRM_OK
+ () => Services.dns.currentTrrConfirmationState == 2,
+ `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
+ 1,
+ 5000
+ );
+ // Total so far: (5) + 2 + 1 + 2 = 10
+
+ // Two reqs for each bar3 and bar4 .
+ await new TRRDNSListener("bar3.example.org.", "9.8.7.6");
+ await new TRRDNSListener("bar4.example.org.", "9.8.7.6");
+ // Total so far: (10) + 2 + 2 = 14.
+
+ // Two reqs that fail, one Confirmation req, two retried reqs that succeed.
+ await new TRRDNSListener("newconn2.example.org.", "9.8.7.6");
+ await TestUtils.waitForCondition(
+ // 2 => CONFIRM_OK
+ () => Services.dns.currentTrrConfirmationState == 2,
+ `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
+ 1,
+ 5000
+ );
+ // Total so far: (14) + 2 + 1 + 2 = 19
+
+ // Two reqs for each bar5 and bar6 .
+ await new TRRDNSListener("bar5.example.org.", "9.8.7.6");
+ await new TRRDNSListener("bar6.example.org.", "9.8.7.6");
+ // Total so far: (19) + 2 + 2 = 23
+
+ let chan = makeChan(
+ `https://foo.example.com:${h2Port}/get-doh-req-port-log`,
+ Ci.nsIRequest.TRR_DISABLED_MODE
+ );
+ let dohReqPortLog = await new Promise(resolve =>
+ chan.asyncOpen(
+ new ChannelListener((stuff, buffer) => {
+ resolve(JSON.parse(buffer));
+ })
+ )
+ );
+
+ // Since the actual ports seen will vary at runtime, we use placeholders
+ // instead in our expected output definition. For example, if two entries
+ // both have "port1", it means they both should have the same port in the
+ // server's log.
+ // For reqs that fail and trigger a Confirmation + retry, the retried reqs
+ // might not re-use the new connection created for Confirmation due to a
+ // race, so we have an extra alternate expected port for them. This lets
+ // us test that they use *a* new port even if it's not *the* new port.
+ // Subsequent lookups are not affected, they will use the same conn as
+ // the Confirmation req.
+ let expectedLogTemplate = [
+ ["example.com", "port1"],
+ ["bar1.example.org", "port1"],
+ ["bar1.example.org", "port1"],
+ ["bar2.example.org", "port1"],
+ ["bar2.example.org", "port1"],
+ ["newconn.example.org", "port1"],
+ ["newconn.example.org", "port1"],
+ ["example.com", "port2"],
+ ["newconn.example.org", "port2"],
+ ["newconn.example.org", "port2"],
+ ["bar3.example.org", "port2"],
+ ["bar3.example.org", "port2"],
+ ["bar4.example.org", "port2"],
+ ["bar4.example.org", "port2"],
+ ["newconn2.example.org", "port2"],
+ ["newconn2.example.org", "port2"],
+ ["example.com", "port3"],
+ ["newconn2.example.org", "port3"],
+ ["newconn2.example.org", "port3"],
+ ["bar5.example.org", "port3"],
+ ["bar5.example.org", "port3"],
+ ["bar6.example.org", "port3"],
+ ["bar6.example.org", "port3"],
+ ];
+
+ if (expectedLogTemplate.length != dohReqPortLog.length) {
+ // This shouldn't happen, and if it does, we'll fail the assertion
+ // below. But first dump the whole server-side log to help with
+ // debugging should we see a failure. Most likely cause would be
+ // that another consumer of TRR happened to make a request while
+ // the test was running and polluted the log.
+ info(dohReqPortLog);
+ }
+
+ equal(
+ expectedLogTemplate.length,
+ dohReqPortLog.length,
+ "Correct number of req log entries"
+ );
+
+ let seenPorts = new Set();
+ // This is essentially a symbol table - as we iterate through the log
+ // we will assign the actual seen port numbers to the placeholders.
+ let seenPortsByExpectedPort = new Map();
+
+ for (let i = 0; i < expectedLogTemplate.length; i++) {
+ let expectedName = expectedLogTemplate[i][0];
+ let expectedPort = expectedLogTemplate[i][1];
+ let seenName = dohReqPortLog[i][0];
+ let seenPort = dohReqPortLog[i][1];
+ info(`Checking log entry. Name: ${seenName}, Port: ${seenPort}`);
+ equal(expectedName, seenName, "Name matches for entry " + i);
+ if (!seenPortsByExpectedPort.has(expectedPort)) {
+ ok(!seenPorts.has(seenPort), "Port should not have been previously used");
+ seenPorts.add(seenPort);
+ seenPortsByExpectedPort.set(expectedPort, seenPort);
+ } else {
+ equal(
+ seenPort,
+ seenPortsByExpectedPort.get(expectedPort),
+ "Connection was reused as expected"
+ );
+ }
+ }
+}
diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml
new file mode 100644
index 0000000000..dd5957abdb
--- /dev/null
+++ b/netwerk/test/unit/xpcshell.toml
@@ -0,0 +1,1270 @@
+[DEFAULT]
+head = "head_channels.js head_cache.js head_cache2.js head_cookies.js head_servers.js head_trr.js head_http3.js head_telemetry.js head_websocket.js head_webtransport.js"
+support-files = [
+ "http2-ca.pem",
+ "proxy-ca.pem",
+ "client-cert.p12",
+ "data/cookies_v10.sqlite",
+ "data/image.png",
+ "data/system_root.lnk",
+ "data/test_psl.txt",
+ "data/test_readline1.txt",
+ "data/test_readline2.txt",
+ "data/test_readline3.txt",
+ "data/test_readline4.txt",
+ "data/test_readline5.txt",
+ "data/test_readline6.txt",
+ "data/test_readline7.txt",
+ "data/test_readline8.txt",
+ "data/signed_win.exe",
+ "socks_client_subprocess.js",
+ "test_link.desktop",
+ "test_link.url",
+ "test_link.lnk",
+ "../../dns/effective_tld_names.dat",
+ "test_alt-data_cross_process.js",
+ "trr_common.js",
+ "test_http3_prio_helpers.js",
+ "http2_test_common.js",
+]
+
+# dom.serviceWorkers.enabled is currently set to false in StaticPrefList.yaml
+# and enabled individually by app prefs, so for the xpcshell tests that involve
+# interception, we need to explicitly enable the pref.
+# Consider enabling it in StaticPrefList.yaml
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1816325
+# Several tests rely on redirecting to data: URIs, which was allowed for a long
+# time but now forbidden. So we enable it just for these tests.
+prefs = [
+ "dom.serviceWorkers.enabled=true",
+ "network.allow_redirect_to_data=true",
+]
+
+["test_1073747.js"]
+
+["test_304_headers.js"]
+
+["test_304_responses.js"]
+
+["test_307_redirect.js"]
+
+["test_421.js"]
+
+["test_MIME_params.js"]
+
+["test_NetUtil.js"]
+
+["test_SuperfluousAuth.js"]
+
+["test_URIs.js"]
+# Intermittent time-outs on Android, bug 1285020
+requesttimeoutfactor = 2
+
+["test_URIs2.js"]
+# Intermittent time-outs on Android, bug 1285020
+requesttimeoutfactor = 2
+
+["test_XHR_redirects.js"]
+
+["test_about_networking.js"]
+
+["test_about_protocol.js"]
+
+["test_aboutblank.js"]
+
+["test_addr_in_use_error.js"]
+
+["test_alt-data_closeWithStatus.js"]
+
+["test_alt-data_overwrite.js"]
+
+["test_alt-data_simple.js"]
+skip-if = ["os == 'win'"] # Bug 1760081
+run-sequentially = "very high failure rate in parallel"
+
+["test_alt-data_stream.js"]
+
+["test_alt-data_too_big.js"]
+
+["test_altsvc.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_altsvc_http3.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # Bug 1807931
+]
+run-sequentially = "http3server"
+
+["test_altsvc_pref.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+
+["test_anonymous-coalescing.js"]
+
+["test_auth_dialog_permission.js"]
+
+["test_auth_jar.js"]
+
+["test_auth_multiple.js"]
+
+["test_auth_proxy.js"]
+
+["test_authentication.js"]
+
+["test_authpromptwrapper.js"]
+
+["test_backgroundfilesaver.js"]
+
+["test_be_conservative.js"]
+firefox-appdir = "browser"
+
+["test_be_conservative_error_handling.js"]
+firefox-appdir = "browser"
+
+["test_bhttp.js"]
+
+["test_blob_channelname.js"]
+
+["test_brotli_decoding.js"]
+
+["test_brotli_http.js"]
+
+["test_brotli_unknown_content_type.js"]
+
+["test_bug203271.js"]
+
+["test_bug248970_cache.js"]
+
+["test_bug248970_cookie.js"]
+
+["test_bug261425.js"]
+
+["test_bug263127.js"]
+
+["test_bug282432.js"]
+
+["test_bug321706.js"]
+
+["test_bug331825.js"]
+
+["test_bug336501.js"]
+
+["test_bug337744.js"]
+
+["test_bug368702.js"]
+
+["test_bug369787.js"]
+
+["test_bug371473.js"]
+
+["test_bug376844.js"]
+
+["test_bug376865.js"]
+
+["test_bug379034.js"]
+
+["test_bug380994.js"]
+
+["test_bug388281.js"]
+
+["test_bug396389.js"]
+
+["test_bug401564.js"]
+
+["test_bug411952.js"]
+
+["test_bug412457.js"]
+skip-if = ["appname == 'thunderbird'"]
+
+["test_bug412945.js"]
+
+["test_bug414122.js"]
+
+["test_bug427957.js"]
+
+["test_bug429347.js"]
+
+["test_bug455311.js"]
+
+["test_bug464591.js"]
+skip-if = ["appname == 'thunderbird'"]
+
+["test_bug468426.js"]
+
+["test_bug468594.js"]
+
+["test_bug470716.js"]
+
+["test_bug477578.js"]
+
+["test_bug479413.js"]
+
+["test_bug479485.js"]
+
+["test_bug482601.js"]
+
+["test_bug482934.js"]
+
+["test_bug490095.js"]
+
+["test_bug504014.js"]
+
+["test_bug510359.js"]
+
+["test_bug526789.js"]
+
+["test_bug528292.js"]
+
+["test_bug536324_64bit_content_length.js"]
+
+["test_bug540566.js"]
+
+["test_bug553970.js"]
+
+["test_bug561042.js"]
+
+["test_bug561276.js"]
+
+["test_bug580508.js"]
+
+["test_bug586908.js"]
+
+["test_bug596443.js"]
+
+["test_bug618835.js"]
+
+["test_bug633743.js"]
+
+["test_bug650522.js"]
+
+["test_bug650995.js"]
+
+["test_bug652761.js"]
+
+["test_bug654926.js"]
+
+["test_bug654926_doom_and_read.js"]
+
+["test_bug654926_test_seek.js"]
+
+["test_bug659569.js"]
+
+["test_bug660066.js"]
+
+["test_bug667087.js"]
+
+["test_bug667818.js"]
+
+["test_bug667907.js"]
+
+["test_bug669001.js"]
+
+["test_bug770243.js"]
+
+["test_bug812167.js"]
+
+["test_bug826063.js"]
+
+["test_bug856978.js"]
+
+["test_bug894586.js"]
+# Allocating 4GB might actually succeed on 64 bit machines
+skip-if = ["bits != 32"]
+
+["test_bug935499.js"]
+
+["test_bug1064258.js"]
+
+["test_bug1177909.js"]
+
+["test_bug1195415.js"]
+
+["test_bug1218029.js"]
+
+["test_bug1279246.js"]
+
+["test_bug1312774_http1.js"]
+
+["test_bug1312782_http1.js"]
+skip-if = ["os == 'android'"] # Bug 1700483
+
+["test_bug1355539_http1.js"]
+
+["test_bug1378385_http1.js"]
+
+["test_bug1411316_http1.js"]
+
+["test_bug1527293.js"]
+
+["test_bug1683176.js"]
+skip-if = [
+ "os == 'android'",
+ "!debug",
+ "os == 'win' && socketprocess_networking",
+]
+
+["test_bug1725766.js"]
+skip-if = ["os == 'android'"] # skip because of bug 1589327
+
+["test_cache-control_request.js"]
+
+["test_cache-entry-id.js"]
+
+["test_cache2-00-service-get.js"]
+
+["test_cache2-01-basic.js"]
+
+["test_cache2-01a-basic-readonly.js"]
+
+["test_cache2-01b-basic-datasize.js"]
+
+["test_cache2-01c-basic-hasmeta-only.js"]
+
+["test_cache2-01d-basic-not-wanted.js"]
+
+["test_cache2-01e-basic-bypass-if-busy.js"]
+
+["test_cache2-01f-basic-openTruncate.js"]
+
+["test_cache2-02-open-non-existing.js"]
+
+["test_cache2-02b-open-non-existing-and-doom.js"]
+
+["test_cache2-03-oncacheentryavail-throws.js"]
+
+["test_cache2-04-oncacheentryavail-throws2x.js"]
+
+["test_cache2-05-visit.js"]
+
+["test_cache2-06-pb-mode.js"]
+
+["test_cache2-07-visit-memory.js"]
+
+["test_cache2-07a-open-memory.js"]
+
+["test_cache2-08-evict-disk-by-memory-storage.js"]
+
+["test_cache2-09-evict-disk-by-uri.js"]
+
+["test_cache2-10-evict-direct.js"]
+
+["test_cache2-10b-evict-direct-immediate.js"]
+
+["test_cache2-11-evict-memory.js"]
+
+["test_cache2-12-evict-disk.js"]
+
+["test_cache2-13-evict-non-existing.js"]
+
+["test_cache2-14-concurent-readers.js"]
+
+["test_cache2-14b-concurent-readers-complete.js"]
+
+["test_cache2-15-conditional-304.js"]
+
+["test_cache2-16-conditional-200.js"]
+
+["test_cache2-17-evict-all.js"]
+
+["test_cache2-18-not-valid.js"]
+
+["test_cache2-19-range-206.js"]
+
+["test_cache2-20-range-200.js"]
+
+["test_cache2-21-anon-storage.js"]
+
+["test_cache2-22-anon-visit.js"]
+
+["test_cache2-23-read-over-chunk.js"]
+
+["test_cache2-24-exists.js"]
+
+["test_cache2-25-chunk-memory-limit.js"]
+
+["test_cache2-26-no-outputstream-open.js"]
+
+["test_cache2-27-force-valid-for.js"]
+
+["test_cache2-28-last-access-attrs.js"]
+# This test will be fixed in bug 1067931
+skip-if = ["true"]
+
+["test_cache2-28a-OPEN_SECRETLY.js"]
+# This test will be fixed in bug 1067931
+skip-if = ["true"]
+
+["test_cache2-29a-concurrent_read_resumable_entry_size_zero.js"]
+
+["test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js"]
+
+["test_cache2-29c-concurrent_read_half-interrupted.js"]
+
+["test_cache2-29d-concurrent_read_half-corrupted-206.js"]
+
+["test_cache2-29e-concurrent_read_half-non-206-response.js"]
+
+["test_cache2-30a-entry-pinning.js"]
+
+["test_cache2-30b-pinning-storage-clear.js"]
+
+["test_cache2-30c-pinning-deferred-doom.js"]
+
+["test_cache2-30d-pinning-WasEvicted-API.js"]
+
+["test_cache2-31-visit-all.js"]
+
+["test_cache2-32-clear-origin.js"]
+
+["test_cache_204_response.js"]
+
+["test_cache_jar.js"]
+
+["test_cacheflags.js"]
+
+["test_captive_portal_service.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_cert_info.js"]
+
+["test_cert_verification_failure.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_channel_close.js"]
+skip-if = ["os == 'win' && socketprocess_networking && !debug"]
+
+["test_channel_long_domain.js"]
+
+["test_channel_priority.js"]
+
+["test_chunked_responses.js"]
+prefs = ["security.allow_eval_with_system_principal=true"]
+
+["test_client_auth_with_proxy.js"]
+skip-if = ["os == 'android'"]
+
+["test_coaleasing_h2_and_h3_connection.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix",
+] # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+run-sequentially = "http3server"
+
+["test_compareURIs.js"]
+
+["test_compressappend.js"]
+
+["test_connection_based_auth.js"]
+
+["test_content_encoding_gzip.js"]
+
+["test_content_length_underrun.js"]
+
+["test_content_sniffer.js"]
+
+["test_cookie_blacklist.js"]
+
+["test_cookie_header.js"]
+
+["test_cookie_ipv6.js"]
+
+["test_cookie_partitioned_attribute.js"]
+
+["test_cookiejars.js"]
+
+["test_cookiejars_safebrowsing.js"]
+
+["test_cookies_async_failure.js"]
+skip-if = ["os == 'linux' && bits == 64 && !debug"] #Bug 1553353
+
+["test_cookies_partition_counting.js"]
+
+["test_cookies_privatebrowsing.js"]
+
+["test_cookies_profile_close.js"]
+skip-if = ["os == 'android'"] # Bug 1700483
+
+["test_cookies_purge_counting.js"]
+
+["test_cookies_purge_counting_per_host.js"]
+
+["test_cookies_read.js"]
+
+["test_cookies_sync_failure.js"]
+
+["test_cookies_thirdparty.js"]
+
+["test_cookies_thirdparty_session.js"]
+
+["test_cookies_upgrade_10.js"]
+
+["test_data_protocol.js"]
+
+["test_defaultURI.js"]
+
+["test_dns_by_type_resolve.js"]
+
+["test_dns_cancel.js"]
+skip-if = ["verify"]
+
+["test_dns_disable_ipv4.js"]
+
+["test_dns_disable_ipv6.js"]
+
+["test_dns_disabled.js"]
+
+["test_dns_localredirect.js"]
+
+["test_dns_offline.js"]
+
+["test_dns_onion.js"]
+
+["test_dns_originAttributes.js"]
+
+["test_dns_override.js"]
+
+["test_dns_override_for_localhost.js"]
+
+["test_dns_proxy_bypass.js"]
+
+["test_dns_retry.js"]
+skip-if = [
+ "os == 'mac'", # server on a local ipv6 is not started on mac
+ "socketprocess_networking",
+] # bug 1760106
+run-sequentially = "node server exceptions dont replay well"
+
+["test_dns_service.js"]
+
+["test_domain_eviction.js"]
+
+["test_dooh.js"]
+head = "head_channels.js head_cache.js head_cookies.js head_servers.js head_trr.js head_http3.js trr_common.js"
+run-sequentially = "node server exceptions dont replay well"
+skip-if = ["socketprocess_networking"]
+
+["test_doomentry.js"]
+
+["test_duplicate_headers.js"]
+
+["test_early_hint_listener.js"]
+skip-if = ["os == 'win' && msix"] # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+
+["test_early_hint_listener_http2.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_ech_grease.js"]
+firefox-appdir = "browser"
+skip-if = ["tsan && socketprocess_networking"] # Bug 1808236
+
+["test_event_sink.js"]
+
+["test_eviction.js"]
+
+["test_extract_charset_from_content_type.js"]
+
+["test_file_protocol.js"]
+
+["test_filestreams.js"]
+
+["test_freshconnection.js"]
+
+["test_getHost.js"]
+
+["test_gio_protocol.js"]
+run-if = ["os == 'linux'"]
+
+["test_gre_resources.js"]
+
+["test_h2proxy_connection_limit.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_head.js"]
+
+["test_head_request_no_response_body.js"]
+
+["test_header_Accept-Language.js"]
+
+["test_header_Accept-Language_case.js"]
+
+["test_header_Server_Timing.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_headers.js"]
+
+["test_hostnameIsLocalIPAddress.js"]
+
+["test_hostnameIsSharedIPAddress.js"]
+
+["test_hpke_config_manager.js"]
+skip-if = ["!nightly_build"] # OHTTP Config manager not currently shipped to release.
+
+["test_http1-proxy.js"]
+
+["test_http2-proxy-failing.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http2-proxy.js"]
+run-sequentially = "one http2 node proxy is used for all tests, this test is using global session counter"
+skip-if = ["os == 'android'"]
+
+["test_http2.js"]
+run-sequentially = "node server exceptions dont replay well"
+head = "head_channels.js head_cache.js head_cookies.js head_servers.js head_trr.js head_http3.js http2_test_common.js"
+
+["test_http2_with_proxy.js"]
+run-sequentially = "node server exceptions dont replay well"
+head = "head_channels.js head_cache.js head_cookies.js head_servers.js head_trr.js head_http3.js http2_test_common.js"
+
+["test_http3.js"]
+skip-if = [
+ "os == 'android'", # bug 1622901
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "http3server"
+
+["test_http3_0rtt.js"]
+skip-if = [
+ "os == 'win'",
+ "os == 'android'",
+]
+
+["test_http3_421.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "http3server"
+
+["test_http3_alt_svc.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "http3server"
+
+["test_http3_coalescing.js"]
+skip-if = [
+ "os == 'android'",
+ "socketprocess_networking",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+ "apple_silicon", # https://bugzilla.mozilla.org/show_bug.cgi?id=1866067
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_direct_proxy.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_dns_retry.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix",
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_early_hint_listener.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'linux'", # Bug 1773916
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+]
+run-sequentially = "http3server"
+
+["test_http3_error_before_connect.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_fast_fallback.js"]
+skip-if = [
+ "os == 'win'",
+ "os == 'android'",
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_fatal_stream_error.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_large_post.js"]
+skip-if = [
+ "os == 'win'",
+ "os == 'android'",
+]
+
+["test_http3_large_post_telemetry.js"]
+disabled = "bug 1771744 - telemetry probe expired"
+# skip-if =
+# asan
+# tsan
+# os == 'win'
+# os == 'android'
+# socketprocess_networking
+
+["test_http3_perf.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "http3server"
+
+["test_http3_prio_disabled.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "http3server"
+
+["test_http3_prio_enabled.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "http3server"
+
+["test_http3_server.js"]
+skip-if = [
+ "verify",
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_server_not_existing.js"]
+skip-if = ["os == 'android'"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_http3_trans_close.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+]
+run-sequentially = "http3server"
+
+["test_http3_version1.js"]
+skip-if = [
+ "os == 'win'",
+ "os == 'android'",
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_httpResponseTimeout.js"]
+skip-if = ["os == 'win' && socketprocess_networking"]
+
+["test_http_408_retry.js"]
+
+["test_http_headers.js"]
+
+["test_http_server_timing.js"]
+
+["test_http_sfv.js"]
+
+["test_httpauth.js"]
+
+["test_httpcancel.js"]
+
+["test_https_rr_ech_prefs.js"]
+skip-if = ["os == 'android'"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_https_rr_sorted_alpn.js"]
+skip-if = ["os == 'android'"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_httpssvc_ech_with_alpn.js"]
+skip-if = [
+ "os == 'android'", # bug 1622901
+ "os == 'mac' && !debug",
+ "asan",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808048
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_httpssvc_https_upgrade.js"]
+
+["test_httpssvc_iphint.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_httpssvc_priority.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_httpssvc_retry_with_ech.js"]
+skip-if = [
+ "os == 'android'", # bug 1622901
+ "os == 'mac' && !debug",
+ "asan",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808048
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_httpssvc_retry_without_ech.js"]
+skip-if = ["os == 'win' && msix"] # https://bugzilla.mozilla.org/show_bug.cgi?id=1808048
+run-sequentially = "node server exceptions dont replay well"
+
+["test_httpsuspend.js"]
+
+["test_idn_blacklist.js"]
+
+["test_idn_spoof.js"]
+
+["test_idn_urls.js"]
+
+["test_idna2008.js"]
+
+["test_idnservice.js"]
+
+["test_immutable.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_inhibit_caching.js"]
+
+["test_ioservice.js"]
+
+["test_large_port.js"]
+
+["test_loadgroup_cancel.js"]
+
+["test_localhost_offline.js"]
+
+["test_localstreams.js"]
+
+["test_mismatch_last-modified.js"]
+
+["test_mozTXTToHTMLConv.js"]
+
+["test_multipart_byteranges.js"]
+
+["test_multipart_streamconv-byte-by-byte.js"]
+
+["test_multipart_streamconv.js"]
+
+["test_multipart_streamconv_empty.js"]
+
+["test_multipart_streamconv_missing_boundary_lead_dashes.js"]
+
+["test_multipart_streamconv_missing_lead_boundary.js"]
+
+["test_nestedabout_serialize.js"]
+
+["test_net_addr.js"]
+# Bug 732363: test fails on windows for unknown reasons.
+skip-if = ["os == 'win'"]
+
+["test_network_connectivity_service.js"]
+
+["test_networking_over_socket_process.js"]
+skip-if = [
+ "os == 'android'",
+ "!socketprocess_networking",
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_no_cookies_after_last_pb_exit.js"]
+
+["test_node_execute.js"]
+
+["test_nojsredir.js"]
+
+["test_non_ipv4_hostname_ending_in_number_cookie_db.js"]
+
+["test_nsIBufferedOutputStream_writeFrom_block.js"]
+
+["test_ntlm_authentication.js"]
+
+["test_ntlm_proxy_and_web_auth.js"]
+
+["test_ntlm_proxy_auth.js"]
+
+["test_ntlm_web_auth.js"]
+
+["test_oblivious_http.js"]
+
+["test_obs-fold.js"]
+
+["test_offline_status.js"]
+
+["test_ohttp.js"]
+
+["test_orb_empty_header.js"]
+
+["test_origin.js"]
+
+["test_original_sent_received_head.js"]
+
+["test_pac_reload_after_network_change.js"]
+
+["test_parse_content_type.js"]
+
+["test_partial_response_entry_size_smart_shrink.js"]
+
+["test_permmgr.js"]
+
+["test_ping_aboutnetworking.js"]
+skip-if = ["verify && os == 'mac'"]
+
+["test_plaintext_sniff.js"]
+skip-if = ["true"] # Causes sporatic oranges
+
+["test_port_remapping.js"]
+skip-if = ["os == 'win' && socketprocess_networking"]
+
+["test_post.js"]
+
+["test_predictor.js"]
+
+["test_private_cookie_changed.js"]
+
+["test_private_necko_channel.js"]
+
+["test_progress.js"]
+
+["test_progress_no_proxy_and_proxy.js"]
+skip-if = [
+ "os == 'win'",
+ "os == 'android'",
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_protocolproxyservice-async-filters.js"]
+
+["test_protocolproxyservice.js"]
+skip-if = [
+ "apple_silicon", # bug 1707738
+ "tsan && socketprocess_networking", # Bug 1808235
+]
+
+["test_proxy-failover_canceled.js"]
+
+["test_proxy-failover_passing.js"]
+
+["test_proxy-replace_canceled.js"]
+
+["test_proxy-replace_passing.js"]
+
+["test_proxy-slow-upload.js"]
+
+["test_proxy_cancel.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_proxy_pac.js"]
+
+["test_proxyconnect.js"]
+skip-if = [
+ "tsan",
+ "socketprocess_networking", # Bug 1614708
+]
+
+["test_psl.js"]
+
+["test_race_cache_with_network.js"]
+skip-if = [
+ "os == 'win' && !debug", # Bug 1866777
+]
+
+["test_range_requests.js"]
+
+["test_rcwn_always_cache_new_content.js"]
+
+["test_rcwn_interrupted.js"]
+
+["test_readline.js"]
+
+["test_redirect-caching_canceled.js"]
+
+["test_redirect-caching_failure.js"]
+
+["test_redirect-caching_passing.js"]
+
+["test_redirect_baduri.js"]
+
+["test_redirect_canceled.js"]
+
+["test_redirect_different-protocol.js"]
+
+["test_redirect_failure.js"]
+
+["test_redirect_from_script.js"]
+
+["test_redirect_from_script_after-open_passing.js"]
+
+["test_redirect_history.js"]
+
+["test_redirect_loop.js"]
+
+["test_redirect_passing.js"]
+
+["test_redirect_protocol_telemetry.js"]
+
+["test_redirect_veto.js"]
+
+["test_reentrancy.js"]
+
+["test_referrer.js"]
+
+["test_referrer_cross_origin.js"]
+
+["test_referrer_policy.js"]
+
+["test_reopen.js"]
+
+["test_reply_without_content_type.js"]
+
+["test_resumable_channel.js"]
+
+["test_resumable_truncate.js"]
+
+["test_retry_0rtt.js"]
+skip-if = [
+ "verify && os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808048
+]
+run-sequentially = "tlsserver uses fixed port"
+
+["test_safeoutputstream.js"]
+
+["test_safeoutputstream_append.js"]
+
+["test_schema_13_db.js"]
+
+["test_schema_12_migration.js"]
+
+["test_schema_10_migration.js"]
+
+["test_schema_2_migration.js"]
+
+["test_schema_3_migration.js"]
+
+["test_separate_connections.js"]
+
+["test_servers.js"]
+
+["test_signature_extraction.js"]
+skip-if = ["os != 'win'"]
+
+["test_simple.js"]
+
+["test_sockettransportsvc_available.js"]
+
+["test_socks.js"]
+skip-if = [
+ "os == 'mac' && debug", #Bug 1140656
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+]
+# Bug 675039: test fails consistently on Android
+fail-if = ["os == 'android'"]
+# http2 unit tests require us to have node available to run the spdy and http2 server
+
+["test_speculative_connect.js"]
+
+["test_stale-while-revalidate_loop.js"]
+
+["test_stale-while-revalidate_max-age-0.js"]
+
+["test_stale-while-revalidate_negative.js"]
+
+["test_stale-while-revalidate_positive.js"]
+
+["test_standardurl.js"]
+
+["test_standardurl_default_port.js"]
+
+["test_standardurl_port.js"]
+
+["test_streamcopier.js"]
+
+["test_substituting_protocol_handler.js"]
+
+["test_suspend_channel_before_connect.js"]
+
+["test_suspend_channel_on_authRetry.js"]
+
+["test_suspend_channel_on_examine.js"]
+
+["test_suspend_channel_on_examine_merged_response.js"]
+
+["test_suspend_channel_on_modified.js"]
+
+["test_synthesized_response.js"]
+
+["test_throttlechannel.js"]
+
+["test_throttlequeue.js"]
+
+["test_throttling.js"]
+
+["test_tldservice_nextsubdomain.js"]
+
+["test_tls13_disabled.js"]
+skip-if = [
+ "os == 'android'",
+ "verify && os == 'win'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_tls_flags.js"]
+skip-if = ["os == 'android' && processor == 'x86_64'"]
+
+["test_tls_flags_separate_connections.js"]
+
+["test_tls_server.js"]
+firefox-appdir = "browser"
+
+["test_tls_server_multiple_clients.js"]
+
+["test_traceable_channel.js"]
+
+["test_trackingProtection_annotateChannels.js"]
+
+["test_trr.js"]
+head = "head_channels.js head_cache.js head_cookies.js head_servers.js head_trr.js head_http3.js trr_common.js"
+run-sequentially = "very high failure rate in parallel"
+
+["test_trr_additional_section.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_af_fallback.js"]
+
+["test_trr_blocklist.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_cancel.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_case_sensitivity.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_cname_chain.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_confirmation.js"]
+skip-if = [
+ "socketprocess_networking", # confirmation state isn't passed cross-process
+ "appname == 'thunderbird'", # bug 1760097
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_decoding.js"]
+
+["test_trr_domain.js"]
+
+["test_trr_enterprise_policy.js"]
+firefox-appdir = "browser" # needed for resource:///modules/policies/schema.sys.mjs to be registered
+skip-if = [
+ "os == 'android'",
+ "socketprocess_networking",
+]
+
+["test_trr_extended_error.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_https_fallback.js"]
+skip-if = [
+ "asan",
+ "tsan",
+ "os == 'win'",
+ "os == 'android'",
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_httpssvc.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_nat64.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_trr_noPrefetch.js"]
+
+["test_trr_proxy.js"]
+
+["test_trr_proxy_auth.js"]
+skip-if = [
+ "os == 'android'",
+ "socketprocess_networking",
+]
+
+["test_trr_strict_mode.js"]
+
+["test_trr_telemetry.js"]
+head = "head_channels.js head_cache.js head_cookies.js head_servers.js head_trr.js head_http3.js trr_common.js"
+skip-if = [
+ "os == 'android'",
+ "socketprocess_networking",
+]
+
+["test_trr_ttl.js"]
+
+["test_trr_with_proxy.js"]
+head = "head_channels.js head_cache.js head_cookies.js head_servers.js head_trr.js trr_common.js"
+skip-if = [
+ "os == 'android'",
+ "socketprocess_networking", # Bug 1808233
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_udp_multicast.js"]
+
+["test_udpsocket.js"]
+
+["test_udpsocket_offline.js"]
+
+["test_unescapestring.js"]
+
+["test_unix_domain.js"]
+
+["test_uri_mutator.js"]
+
+["test_use_httpssvc.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_verify_traffic.js"]
+
+["test_websocket_500k.js"]
+skip-if = ["verify"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_websocket_fails.js"]
+run-sequentially = "node server exceptions dont replay well"
+skip-if = ["os == 'android' && verify"] # Bug 1804101
+
+["test_websocket_fails_2.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_websocket_offline.js"]
+
+["test_websocket_server.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_websocket_server_multiclient.js"]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_websocket_with_h3_active.js"]
+skip-if = [
+ "os == 'android'",
+ "verify && os == 'win'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
+]
+run-sequentially = "node server exceptions dont replay well"
+
+["test_webtransport_simple.js"]
+# This test will be fixed in bug 1796556
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807931
+ "verify && os == 'win'",
+ "socketprocess_networking",
+]
+
+["test_xmlhttprequest.js"]
diff --git a/netwerk/test/unit_ipc/child_channel_id.js b/netwerk/test/unit_ipc/child_channel_id.js
new file mode 100644
index 0000000000..1c9f948151
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_channel_id.js
@@ -0,0 +1,47 @@
+/**
+ * Send HTTP requests and notify the parent about their channelId
+ */
+/* global NetUtil, ChannelListener */
+
+let shouldQuit = false;
+
+function run_test() {
+ // keep the event loop busy and the test alive until a "finish" command
+ // is issued by parent
+ do_timeout(100, function keepAlive() {
+ if (!shouldQuit) {
+ do_timeout(100, keepAlive);
+ }
+ });
+}
+
+function makeRequest(uri) {
+ let requestChannel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ });
+ requestChannel.asyncOpen(new ChannelListener(checkResponse, requestChannel));
+ requestChannel.QueryInterface(Ci.nsIHttpChannel);
+ dump(`Child opened request: ${uri}, channelId=${requestChannel.channelId}\n`);
+}
+
+function checkResponse(request, buffer, requestChannel) {
+ // notify the parent process about the original request channel
+ requestChannel.QueryInterface(Ci.nsIHttpChannel);
+ do_send_remote_message(`request:${requestChannel.channelId}`);
+
+ // the response channel can be different (if it was redirected)
+ let responseChannel = request.QueryInterface(Ci.nsIHttpChannel);
+
+ let uri = responseChannel.URI.spec;
+ let origUri = responseChannel.originalURI.spec;
+ let id = responseChannel.channelId;
+ dump(`Child got response to: ${uri} (orig=${origUri}), channelId=${id}\n`);
+
+ // notify the parent process about this channel's ID
+ do_send_remote_message(`response:${id}`);
+}
+
+function finish() {
+ shouldQuit = true;
+}
diff --git a/netwerk/test/unit_ipc/child_cookie_header.js b/netwerk/test/unit_ipc/child_cookie_header.js
new file mode 100644
index 0000000000..4686f509f4
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_cookie_header.js
@@ -0,0 +1,93 @@
+/* global NetUtil, ChannelListener */
+
+"use strict";
+
+function inChildProcess() {
+ return (
+ // eslint-disable-next-line mozilla/use-services
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+let uri = null;
+function makeChan() {
+ return NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function OpenChannelPromise(aChannel, aClosure) {
+ return new Promise(resolve => {
+ function processResponse(request, buffer, context) {
+ aClosure(request.QueryInterface(Ci.nsIHttpChannel), buffer, context);
+ resolve();
+ }
+ aChannel.asyncOpen(new ChannelListener(processResponse, null));
+ });
+}
+
+// This test doesn't do much, except to communicate with the parent, and get
+// URL we need to connect to.
+add_task(async function setup() {
+ ok(inChildProcess(), "Sanity check. This should run in the child process");
+ // Initialize the URL. Parent runs the server
+ do_send_remote_message("start-test");
+ uri = await do_await_remote_message("start-test-done");
+});
+
+// This test performs a request, and checks that no cookie header are visible
+// to the child process
+add_task(async function test1() {
+ let chan = makeChan();
+
+ await OpenChannelPromise(chan, (request, buffer) => {
+ equal(buffer, "response");
+ Assert.throws(
+ () => request.getRequestHeader("Cookie"),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Cookie header should not be visible on request in the child"
+ );
+ Assert.throws(
+ () => request.getResponseHeader("Set-Cookie"),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Cookie header should not be visible on response in the child"
+ );
+ });
+
+ // We also check that a cookie was saved by the Set-Cookie header
+ // in the parent.
+ do_send_remote_message("check-cookie-count");
+ let count = await do_await_remote_message("check-cookie-count-done");
+ equal(count, 1);
+});
+
+// This test communicates with the parent, to locally save a new cookie.
+// Then it performs another request, makes sure no cookie headers are visible,
+// after which it checks that both cookies are visible to the parent.
+add_task(async function test2() {
+ do_send_remote_message("set-cookie");
+ await do_await_remote_message("set-cookie-done");
+
+ let chan = makeChan();
+ await OpenChannelPromise(chan, (request, buffer) => {
+ equal(buffer, "response");
+ Assert.throws(
+ () => request.getRequestHeader("Cookie"),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Cookie header should not be visible on request in the child"
+ );
+ Assert.throws(
+ () => request.getResponseHeader("Set-Cookie"),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Cookie header should not be visible on response in the child"
+ );
+ });
+
+ // We should have two cookies. One set by the Set-Cookie header sent by the
+ // server, and one that was manually set in the parent.
+ do_send_remote_message("second-check-cookie-count");
+ let count = await do_await_remote_message("second-check-cookie-count-done");
+ equal(count, 2);
+});
diff --git a/netwerk/test/unit_ipc/child_dns_by_type_resolve.js b/netwerk/test/unit_ipc/child_dns_by_type_resolve.js
new file mode 100644
index 0000000000..d09d29a73e
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_dns_by_type_resolve.js
@@ -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/. */
+
+"use strict";
+
+/* import-globals-from ../unit/head_trr.js */
+
+let test_answer = "bXkgdm9pY2UgaXMgbXkgcGFzc3dvcmQ=";
+let test_answer_addr = "127.0.0.1";
+
+add_task(async function testTXTResolve() {
+ // use the h2 server as DOH provider
+ let { inRecord, inStatus } = await new TRRDNSListener("_esni.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_TXT,
+ });
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+ let answer = inRecord
+ .QueryInterface(Ci.nsIDNSTXTRecord)
+ .getRecordsAsOneString();
+ Assert.equal(answer, test_answer, "got correct answer");
+});
diff --git a/netwerk/test/unit_ipc/child_is_proxy_used.js b/netwerk/test/unit_ipc/child_is_proxy_used.js
new file mode 100644
index 0000000000..216963ec19
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_is_proxy_used.js
@@ -0,0 +1,24 @@
+"use strict";
+
+/* global NetUtil, ChannelListener, CL_ALLOW_UNKNOWN_CL */
+
+add_task(async function check_proxy() {
+ do_send_remote_message("start-test");
+ let URL = await do_await_remote_message("start-test-done");
+ let chan = NetUtil.newChannel({
+ uri: URL,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+
+ let { req, buff } = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ equal(buff, "content");
+ equal(req.QueryInterface(Ci.nsIHttpChannelInternal).isProxyUsed, true);
+});
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener((req, buff) => resolve({ req, buff }), null, flags)
+ );
+ });
+}
diff --git a/netwerk/test/unit_ipc/child_veto_in_parent.js b/netwerk/test/unit_ipc/child_veto_in_parent.js
new file mode 100644
index 0000000000..8f922a254e
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_veto_in_parent.js
@@ -0,0 +1,53 @@
+/* import-globals-from ../unit/head_trr.js */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+ChromeUtils.defineLazyGetter(this, "randomURI", function () {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+}
+
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+add_task(async function doStuff() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ let chan = make_channel(randomURI);
+ let [req, buff] = await new Promise(resolve =>
+ chan.asyncOpen(
+ new ChannelListener((aReq, aBuff) => resolve([aReq, aBuff]), null)
+ )
+ );
+ Assert.equal(buff, "");
+ Assert.equal(req.status, Cr.NS_OK);
+ await httpServer.stop();
+ await do_send_remote_message("child-test-done");
+});
diff --git a/netwerk/test/unit_ipc/head_channels_clone.js b/netwerk/test/unit_ipc/head_channels_clone.js
new file mode 100644
index 0000000000..be87bc8829
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_channels_clone.js
@@ -0,0 +1,12 @@
+/* import-globals-from ../unit/head_channels.js */
+// Load standard base class for network tests into child process
+//
+
+var { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+load("../unit/head_channels.js");
diff --git a/netwerk/test/unit_ipc/head_http3_clone.js b/netwerk/test/unit_ipc/head_http3_clone.js
new file mode 100644
index 0000000000..51e0c8017e
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_http3_clone.js
@@ -0,0 +1,10 @@
+/* import-globals-from ../unit/head_http3.js */
+
+var { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+load("../unit/head_http3.js");
diff --git a/netwerk/test/unit_ipc/head_trr_clone.js b/netwerk/test/unit_ipc/head_trr_clone.js
new file mode 100644
index 0000000000..9516e83f7f
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_trr_clone.js
@@ -0,0 +1,10 @@
+/* import-globals-from ../unit/head_trr.js */
+
+var { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+load("../unit/head_trr.js");
diff --git a/netwerk/test/unit_ipc/test_XHR_redirects.js b/netwerk/test/unit_ipc/test_XHR_redirects.js
new file mode 100644
index 0000000000..472817a9ec
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_XHR_redirects.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_XHR_redirects.js");
+}
diff --git a/netwerk/test/unit_ipc/test_alt-data_closeWithStatus_wrap.js b/netwerk/test/unit_ipc/test_alt-data_closeWithStatus_wrap.js
new file mode 100644
index 0000000000..a9d45a3f12
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_closeWithStatus_wrap.js
@@ -0,0 +1,17 @@
+// needs to be rooted
+var cacheFlushObserver = {
+ observe() {
+ cacheFlushObserver = null;
+ do_send_remote_message("flushed");
+ },
+};
+
+function run_test() {
+ do_get_profile();
+ do_await_remote_message("flush").then(() => {
+ Services.cache2
+ .QueryInterface(Ci.nsICacheTesting)
+ .flush(cacheFlushObserver);
+ });
+ run_test_in_child("../unit/test_alt-data_closeWithStatus.js");
+}
diff --git a/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js b/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
new file mode 100644
index 0000000000..336b9e3129
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
@@ -0,0 +1,96 @@
+// needs to be rooted
+var cacheFlushObserver = {
+ observe() {
+ cacheFlushObserver = null;
+ do_send_remote_message("flushed");
+ },
+};
+
+// We get this from the child a bit later
+var url = null;
+
+// needs to be rooted
+var cacheFlushObserver2 = {
+ observe() {
+ cacheFlushObserver2 = null;
+ openAltChannel();
+ },
+};
+
+function run_test() {
+ do_get_profile();
+ do_await_remote_message("flush").then(() => {
+ Services.cache2
+ .QueryInterface(Ci.nsICacheTesting)
+ .flush(cacheFlushObserver);
+ });
+
+ do_await_remote_message("done").then(() => {
+ sendCommand("URL;", load_channel);
+ });
+
+ run_test_in_child("../unit/test_alt-data_cross_process.js");
+}
+
+function load_channel(channelUrl) {
+ ok(channelUrl);
+ url = channelUrl; // save this to open the alt data channel later
+ var chan = make_channel(channelUrl);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType("text/binary", "", Ci.nsICacheInfoChannel.ASYNC);
+ chan.asyncOpen(new ChannelListener(readTextData, null));
+}
+
+function make_channel(channelUrl, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: channelUrl,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function readTextData(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+ // Since we are in a different process from what that generated the alt-data,
+ // we should receive the original data, not processed content.
+ Assert.equal(cc.alternativeDataType, "");
+ Assert.equal(buffer, "response body");
+
+ // Now let's generate some alt-data in the parent, and make sure we can get it
+ var altContent = "altContentParentGenerated";
+ executeSoon(() => {
+ var os = cc.openAlternativeOutputStream(
+ "text/parent-binary",
+ altContent.length
+ );
+ os.write(altContent, altContent.length);
+ os.close();
+
+ executeSoon(() => {
+ Services.cache2
+ .QueryInterface(Ci.nsICacheTesting)
+ .flush(cacheFlushObserver2);
+ });
+ });
+}
+
+function openAltChannel() {
+ var chan = make_channel(url);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(
+ "text/parent-binary",
+ "",
+ Ci.nsICacheInfoChannel.ASYNC
+ );
+ chan.asyncOpen(new ChannelListener(readAltData, null));
+}
+
+function readAltData(request, buffer) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ // This was generated in the parent, so it's OK to get it.
+ Assert.equal(buffer, "altContentParentGenerated");
+ Assert.equal(cc.alternativeDataType, "text/parent-binary");
+
+ // FINISH
+ do_send_remote_message("finish");
+}
diff --git a/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js b/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js
new file mode 100644
index 0000000000..ae4f12fde9
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js
@@ -0,0 +1,17 @@
+// needs to be rooted
+var cacheFlushObserver = {
+ observe() {
+ cacheFlushObserver = null;
+ do_send_remote_message("flushed");
+ },
+};
+
+function run_test() {
+ do_get_profile();
+ do_await_remote_message("flush").then(() => {
+ Services.cache2
+ .QueryInterface(Ci.nsICacheTesting)
+ .flush(cacheFlushObserver);
+ });
+ run_test_in_child("../unit/test_alt-data_simple.js");
+}
diff --git a/netwerk/test/unit_ipc/test_alt-data_stream_wrap.js b/netwerk/test/unit_ipc/test_alt-data_stream_wrap.js
new file mode 100644
index 0000000000..1eee2f2434
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_stream_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_alt-data_stream.js");
+}
diff --git a/netwerk/test/unit_ipc/test_cache-entry-id_wrap.js b/netwerk/test/unit_ipc/test_cache-entry-id_wrap.js
new file mode 100644
index 0000000000..0358fc1e8d
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cache-entry-id_wrap.js
@@ -0,0 +1,13 @@
+function run_test() {
+ do_get_profile();
+ run_test_in_child("../unit/test_cache-entry-id.js");
+
+ do_await_remote_message("flush").then(() => {
+ let p = new Promise(resolve => {
+ Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(resolve);
+ });
+ p.then(() => {
+ do_send_remote_message("flushed");
+ });
+ });
+}
diff --git a/netwerk/test/unit_ipc/test_cache_jar_wrap.js b/netwerk/test/unit_ipc/test_cache_jar_wrap.js
new file mode 100644
index 0000000000..fa2bb82a8d
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cache_jar_wrap.js
@@ -0,0 +1,4 @@
+function run_test() {
+ run_test_in_child("../unit/head_cache2.js");
+ run_test_in_child("../unit/test_cache_jar.js");
+}
diff --git a/netwerk/test/unit_ipc/test_cacheflags_wrap.js b/netwerk/test/unit_ipc/test_cacheflags_wrap.js
new file mode 100644
index 0000000000..eda3004532
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cacheflags_wrap.js
@@ -0,0 +1,8 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ do_get_profile();
+ run_test_in_child("../unit/test_cacheflags.js");
+}
diff --git a/netwerk/test/unit_ipc/test_channel_close_wrap.js b/netwerk/test/unit_ipc/test_channel_close_wrap.js
new file mode 100644
index 0000000000..ead9999579
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_close_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_channel_close.js");
+}
diff --git a/netwerk/test/unit_ipc/test_channel_id.js b/netwerk/test/unit_ipc/test_channel_id.js
new file mode 100644
index 0000000000..fc37651249
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_id.js
@@ -0,0 +1,109 @@
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+/*
+ * Test that when doing HTTP requests, the nsIHttpChannel is detected in
+ * both parent and child and shares the same channelId across processes.
+ */
+
+let httpserver;
+let port;
+
+function startHttpServer() {
+ httpserver = new HttpServer();
+
+ httpserver.registerPathHandler("/resource", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.bodyOutputStream.write("data", 4);
+ });
+
+ httpserver.registerPathHandler("/redirect", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 302, "Redirect");
+ response.setHeader("Location", "/resource", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ });
+
+ httpserver.start(-1);
+ port = httpserver.identity.primaryPort;
+}
+
+function stopHttpServer(next) {
+ httpserver.stop(next);
+}
+
+let expectedParentChannels = [];
+let expectedChildMessages = [];
+
+let maybeFinishWaitForParentChannels;
+let parentChannelsDone = new Promise(resolve => {
+ maybeFinishWaitForParentChannels = () => {
+ if (!expectedParentChannels.length) {
+ dump("All expected parent channels were detected\n");
+ resolve();
+ }
+ };
+});
+
+function observer(subject, topic, data) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ let uri = channel.URI.spec;
+ let origUri = channel.originalURI.spec;
+ let id = channel.channelId;
+ dump(`Parent detected channel: ${uri} (orig=${origUri}): channelId=${id}\n`);
+
+ // did we expect a new channel?
+ let expected = expectedParentChannels.shift();
+ Assert.ok(!!expected);
+
+ // Start waiting for the messages about request/response from child
+ for (let event of expected) {
+ let message = `${event}:${id}`;
+ dump(`Expecting message from child: ${message}\n`);
+
+ let messagePromise = do_await_remote_message(message).then(() => {
+ dump(`Expected message from child arrived: ${message}\n`);
+ });
+ expectedChildMessages.push(messagePromise);
+ }
+
+ // If we don't expect any further parent channels, finish the parent wait
+ maybeFinishWaitForParentChannels();
+}
+
+function run_test() {
+ startHttpServer();
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ run_test_in_child("child_channel_id.js", makeRequests);
+}
+
+function makeRequests() {
+ // First, a normal request without any redirect. Expect one channel detected
+ // in parent, used by both request and response.
+ expectedParentChannels.push(["request", "response"]);
+ sendCommand(`makeRequest("http://localhost:${port}/resource");`);
+
+ // Second request will be redirected. Expect two channels, one with the
+ // original request, then the redirected one which gets the final response.
+ expectedParentChannels.push(["request"], ["response"]);
+ sendCommand(`makeRequest("http://localhost:${port}/redirect");`);
+
+ waitForParentChannels();
+}
+
+function waitForParentChannels() {
+ parentChannelsDone.then(waitForChildMessages);
+}
+
+function waitForChildMessages() {
+ dump(`Waiting for ${expectedChildMessages.length} child messages\n`);
+ Promise.all(expectedChildMessages).then(finish);
+}
+
+function finish() {
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ sendCommand("finish();", () => stopHttpServer(do_test_finished));
+}
diff --git a/netwerk/test/unit_ipc/test_channel_priority_wrap.js b/netwerk/test/unit_ipc/test_channel_priority_wrap.js
new file mode 100644
index 0000000000..5bc86137bd
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_priority_wrap.js
@@ -0,0 +1,49 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let httpserver;
+let port;
+
+function startHttpServer() {
+ httpserver = new HttpServer();
+
+ httpserver.registerPathHandler("/resource", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.bodyOutputStream.write("data", 4);
+ });
+
+ httpserver.registerPathHandler("/redirect", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 302, "Redirect");
+ response.setHeader("Location", "/resource", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ });
+
+ httpserver.start(-1);
+ port = httpserver.identity.primaryPort;
+}
+
+function stopHttpServer() {
+ httpserver.stop(() => {});
+}
+
+function run_test() {
+ // jshint ignore:line
+ registerCleanupFunction(stopHttpServer);
+
+ run_test_in_child("../unit/test_channel_priority.js", () => {
+ startHttpServer();
+ sendCommand(`configPort(${port});`);
+ do_await_remote_message("finished").then(() => {
+ do_test_finished();
+ });
+ });
+}
diff --git a/netwerk/test/unit_ipc/test_chunked_responses_wrap.js b/netwerk/test/unit_ipc/test_chunked_responses_wrap.js
new file mode 100644
index 0000000000..72bb40554c
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_chunked_responses_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_chunked_responses.js");
+}
diff --git a/netwerk/test/unit_ipc/test_cookie_header_stripped.js b/netwerk/test/unit_ipc/test_cookie_header_stripped.js
new file mode 100644
index 0000000000..273fa5fbde
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookie_header_stripped.js
@@ -0,0 +1,94 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const TEST_DOMAIN = "www.example.com";
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return (
+ "http://" + TEST_DOMAIN + ":" + httpserv.identity.primaryPort + "/path"
+ );
+});
+
+const responseBody1 = "response";
+function requestHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Set-Cookie", "tom=cool; Max-Age=10", true);
+ response.bodyOutputStream.write(responseBody1, responseBody1.length);
+}
+
+let httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/path", requestHandler);
+ httpserv.start(-1);
+ httpserv.identity.add("http", TEST_DOMAIN, httpserv.identity.primaryPort);
+
+ registerCleanupFunction(() => {
+ Services.cookies.removeCookiesWithOriginAttributes("{}", TEST_DOMAIN);
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.cookie.cookieBehavior");
+ Services.prefs.clearUserPref(
+ "network.cookieJarSettings.unblocked_for_testing"
+ );
+
+ httpserv.stop();
+ httpserv = null;
+ });
+
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setCharPref("network.dns.localDomains", TEST_DOMAIN);
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.cookies.removeCookiesWithOriginAttributes("{}", TEST_DOMAIN);
+
+ // Sends back the URL to the child script
+ do_await_remote_message("start-test").then(() => {
+ do_send_remote_message("start-test-done", URL);
+ });
+
+ // Sends back the cookie count for the domain
+ // Should only be one - from Set-Cookie
+ do_await_remote_message("check-cookie-count").then(() => {
+ do_send_remote_message(
+ "check-cookie-count-done",
+ Services.cookies.countCookiesFromHost(TEST_DOMAIN)
+ );
+ });
+
+ // Sends back the cookie count for the domain
+ // There should be 2 cookies. One from the Set-Cookie header, the other set
+ // manually.
+ do_await_remote_message("second-check-cookie-count").then(() => {
+ do_send_remote_message(
+ "second-check-cookie-count-done",
+ Services.cookies.countCookiesFromHost(TEST_DOMAIN)
+ );
+ });
+
+ // Sets a cookie for the test domain
+ do_await_remote_message("set-cookie").then(() => {
+ const expiry = Date.now() + 24 * 60 * 60;
+ Services.cookies.add(
+ TEST_DOMAIN,
+ "/",
+ "cookieName",
+ "cookieValue",
+ false,
+ false,
+ false,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ do_send_remote_message("set-cookie-done");
+ });
+
+ // Run the actual test logic
+ run_test_in_child("child_cookie_header.js");
+}
diff --git a/netwerk/test/unit_ipc/test_cookiejars_wrap.js b/netwerk/test/unit_ipc/test_cookiejars_wrap.js
new file mode 100644
index 0000000000..bb58bcd2bf
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookiejars_wrap.js
@@ -0,0 +1,13 @@
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.skip_browsing_context_check_in_parent_for_testing",
+ true
+ );
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_cookiejars.js");
+}
diff --git a/netwerk/test/unit_ipc/test_dns_by_type_resolve_wrap.js b/netwerk/test/unit_ipc/test_dns_by_type_resolve_wrap.js
new file mode 100644
index 0000000000..72b8f96880
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_by_type_resolve_wrap.js
@@ -0,0 +1,52 @@
+"use strict";
+
+let h2Port;
+
+function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+ // the TRR server is on 127.0.0.1
+ Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
+
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ // 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow
+ Services.prefs.setBoolPref("network.trr.wait-for-portal", false);
+ // don't confirm that TRR is working, just go!
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ // So we can change the pref without clearing the cache to check a pushed
+ // record with a TRR path that fails.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
+ const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+
+ // XXX(valentin): It would be nice to just call trr_test_setup() here, but
+ // the relative path here makes it awkward. Would be nice to fix someday.
+ addCertFromFile(certdb, "../unit/http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(() => {
+ trr_clear_prefs();
+});
+
+function run_test() {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/doh"
+ );
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR first
+ run_test_in_child("child_dns_by_type_resolve.js");
+}
diff --git a/netwerk/test/unit_ipc/test_dns_cancel_wrap.js b/netwerk/test/unit_ipc/test_dns_cancel_wrap.js
new file mode 100644
index 0000000000..2f38aa4ecb
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_cancel_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_dns_cancel.js");
+}
diff --git a/netwerk/test/unit_ipc/test_dns_service_wrap.js b/netwerk/test/unit_ipc/test_dns_service_wrap.js
new file mode 100644
index 0000000000..fdbecf16d3
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_service_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_dns_service.js");
+}
diff --git a/netwerk/test/unit_ipc/test_duplicate_headers_wrap.js b/netwerk/test/unit_ipc/test_duplicate_headers_wrap.js
new file mode 100644
index 0000000000..6225d593d9
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_duplicate_headers_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_duplicate_headers.js");
+}
diff --git a/netwerk/test/unit_ipc/test_event_sink_wrap.js b/netwerk/test/unit_ipc/test_event_sink_wrap.js
new file mode 100644
index 0000000000..908c971f8a
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_event_sink_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_event_sink.js");
+}
diff --git a/netwerk/test/unit_ipc/test_getHost_wrap.js b/netwerk/test/unit_ipc/test_getHost_wrap.js
new file mode 100644
index 0000000000..f74ab7d152
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_getHost_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_getHost.js");
+}
diff --git a/netwerk/test/unit_ipc/test_gio_protocol_wrap.js b/netwerk/test/unit_ipc/test_gio_protocol_wrap.js
new file mode 100644
index 0000000000..f6aa6b83e8
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_gio_protocol_wrap.js
@@ -0,0 +1,21 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+
+function run_test() {
+ Services.prefs.setCharPref(
+ "network.gio.supported-protocols",
+ "localtest:,recent:"
+ );
+
+ do_await_remote_message("gio-allow-test-protocols").then(port => {
+ do_send_remote_message("gio-allow-test-protocols-done");
+ });
+
+ run_test_in_child("../unit/test_gio_protocol.js");
+}
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.gio.supported-protocols");
+});
diff --git a/netwerk/test/unit_ipc/test_head_wrap.js b/netwerk/test/unit_ipc/test_head_wrap.js
new file mode 100644
index 0000000000..13f0702e55
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_head_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_head.js");
+}
diff --git a/netwerk/test/unit_ipc/test_headers_wrap.js b/netwerk/test/unit_ipc/test_headers_wrap.js
new file mode 100644
index 0000000000..e0bae4080d
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_headers_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_headers.js");
+}
diff --git a/netwerk/test/unit_ipc/test_http3_prio_disabled_wrap.js b/netwerk/test/unit_ipc/test_http3_prio_disabled_wrap.js
new file mode 100644
index 0000000000..38988cd450
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_http3_prio_disabled_wrap.js
@@ -0,0 +1,20 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.priority");
+ http3_clear_prefs();
+});
+
+// setup will be called before the child process tests
+add_task(async function setup() {
+ await http3_setup_tests("h3-29");
+});
+
+async function run_test() {
+ // test priority urgency and incremental with priority disabled
+ Services.prefs.setBoolPref("network.http.http3.priority", false);
+ run_test_in_child("../unit/test_http3_prio_disabled.js");
+ run_next_test(); // only pumps next async task from this file
+}
diff --git a/netwerk/test/unit_ipc/test_http3_prio_enabled_wrap.js b/netwerk/test/unit_ipc/test_http3_prio_enabled_wrap.js
new file mode 100644
index 0000000000..dcff09fcba
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_http3_prio_enabled_wrap.js
@@ -0,0 +1,20 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.priority");
+ http3_clear_prefs();
+});
+
+// setup will be called before the child process tests
+add_task(async function setup() {
+ await http3_setup_tests("h3-29");
+});
+
+async function run_test() {
+ // test priority urgency and incremental with priority enabled
+ Services.prefs.setBoolPref("network.http.http3.priority", true);
+ run_test_in_child("../unit/test_http3_prio_enabled.js");
+ run_next_test(); // only pumps next async task from this file
+}
diff --git a/netwerk/test/unit_ipc/test_httpcancel_wrap.js b/netwerk/test/unit_ipc/test_httpcancel_wrap.js
new file mode 100644
index 0000000000..f56d0e198b
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_httpcancel_wrap.js
@@ -0,0 +1,48 @@
+"use strict";
+
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+let observer = null;
+
+function run_test() {
+ do_await_remote_message("register-observer").then(() => {
+ observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ subject = subject.QueryInterface(Ci.nsIRequest);
+ subject.cancel(Cr.NS_BINDING_ABORTED);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: setting values should still work
+ try {
+ subject.QueryInterface(Ci.nsIHttpChannel);
+ let currentReferrer = subject.getRequestHeader("Referer");
+ Assert.equal(currentReferrer, "http://site1.com/");
+ let uri = Services.io.newURI("http://site2.com");
+ subject.referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ uri
+ );
+ } catch (ex) {
+ do_throw("Exception: " + ex);
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ do_send_remote_message("register-observer-done");
+ });
+
+ do_await_remote_message("unregister-observer").then(() => {
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+
+ do_send_remote_message("unregister-observer-done");
+ });
+
+ run_test_in_child("../unit/test_httpcancel.js");
+}
diff --git a/netwerk/test/unit_ipc/test_httpsuspend_wrap.js b/netwerk/test/unit_ipc/test_httpsuspend_wrap.js
new file mode 100644
index 0000000000..0b013bd957
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_httpsuspend_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_httpsuspend.js");
+}
diff --git a/netwerk/test/unit_ipc/test_is_proxy_used.js b/netwerk/test/unit_ipc/test_is_proxy_used.js
new file mode 100644
index 0000000000..2678ffa1b8
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_is_proxy_used.js
@@ -0,0 +1,32 @@
+"use strict";
+/* global NodeHTTPServer, NodeHTTPProxyServer*/
+
+add_task(async function run_test() {
+ let proxy = new NodeHTTPProxyServer();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ let server = new NodeHTTPServer();
+
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ let content = "content";
+ resp.writeHead(200);
+ resp.end(content);
+ });
+
+ do_await_remote_message("start-test").then(() => {
+ do_send_remote_message(
+ "start-test-done",
+ `http://localhost:${server.port()}/test`
+ );
+ });
+
+ run_test_in_child("child_is_proxy_used.js");
+});
diff --git a/netwerk/test/unit_ipc/test_multipart_streamconv_wrap.js b/netwerk/test/unit_ipc/test_multipart_streamconv_wrap.js
new file mode 100644
index 0000000000..b116f095ba
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_multipart_streamconv_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_multipart_streamconv.js");
+}
diff --git a/netwerk/test/unit_ipc/test_orb_empty_header_wrap.js b/netwerk/test/unit_ipc/test_orb_empty_header_wrap.js
new file mode 100644
index 0000000000..dec6c53fee
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_orb_empty_header_wrap.js
@@ -0,0 +1,8 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+// setup will be called before the child process tests
+function run_test() {
+ Services.prefs.setBoolPref("browser.opaqueResponseBlocking", true);
+ run_test_in_child("../unit/test_orb_empty_header.js");
+}
diff --git a/netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js b/netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js
new file mode 100644
index 0000000000..91a8a00f09
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_original_sent_received_head.js");
+}
diff --git a/netwerk/test/unit_ipc/test_post_wrap.js b/netwerk/test/unit_ipc/test_post_wrap.js
new file mode 100644
index 0000000000..27afae5b40
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_post_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_post.js");
+}
diff --git a/netwerk/test/unit_ipc/test_predictor_wrap.js b/netwerk/test/unit_ipc/test_predictor_wrap.js
new file mode 100644
index 0000000000..24aafe1a09
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_predictor_wrap.js
@@ -0,0 +1,44 @@
+// This is the bit that runs in the parent process when the test begins. Here's
+// what happens:
+//
+// - Load the entire single-process test in the parent
+// - Setup the test harness within the child process
+// - Send a command to the child to have the single-process test loaded there, as well
+// - Send a command to the child to make the predictor available
+// - run_test_real in the parent
+// - run_test_real does a bunch of pref-setting and other fun things on the parent
+// - once all that's done, it calls run_next_test IN THE PARENT
+// - every time run_next_test is called, it is called IN THE PARENT
+// - the test that gets started then does any parent-side setup that's necessary
+// - once the parent-side setup is done, it calls continue_<testname> IN THE CHILD
+// - when the test is done running on the child, the child calls predictor.reset
+// this causes some code to be run on the parent which, when complete, sends an
+// obserer service notification IN THE PARENT, which causes run_next_test to be
+// called again, bumping us up to the top of the loop outlined here
+// - when the final test is done, cleanup happens IN THE PARENT and we're done
+//
+// This is a little confusing, but it's what we have to do in order to have some
+// things that must run on the parent (the setup - opening cache entries, etc)
+// but with most of the test running on the child (calls to the predictor api,
+// verification, etc).
+//
+/* import-globals-from ../unit/test_predictor.js */
+
+function run_test() {
+ var test_path = do_get_file("../unit/test_predictor.js").path.replace(
+ /\\/g,
+ "/"
+ );
+ load(test_path);
+ do_load_child_test_harness();
+ do_test_pending();
+ sendCommand('load("' + test_path + '");', function () {
+ sendCommand(
+ 'predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);',
+ function () {
+ run_test_real();
+ do_test_finished();
+ }
+ );
+ });
+}
diff --git a/netwerk/test/unit_ipc/test_progress_wrap.js b/netwerk/test/unit_ipc/test_progress_wrap.js
new file mode 100644
index 0000000000..c4a658c094
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_progress_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_progress.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js b/netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js
new file mode 100644
index 0000000000..8a12f4090e
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect-caching_canceled.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js b/netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js
new file mode 100644
index 0000000000..f95cc399be
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js
@@ -0,0 +1,11 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ do_await_remote_message("disable-ports").then(_ => {
+ Services.prefs.setCharPref("network.security.ports.banned", "65400");
+ do_send_remote_message("disable-ports-done");
+ });
+ run_test_in_child("../unit/test_redirect-caching_failure.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js b/netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js
new file mode 100644
index 0000000000..6288bc311a
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect-caching_passing.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_canceled_wrap.js b/netwerk/test/unit_ipc/test_redirect_canceled_wrap.js
new file mode 100644
index 0000000000..53fa9a5cfc
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_canceled_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect_canceled.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js b/netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js
new file mode 100644
index 0000000000..c45e7810ab
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_redirect_different-protocol.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_failure_wrap.js b/netwerk/test/unit_ipc/test_redirect_failure_wrap.js
new file mode 100644
index 0000000000..ac935afe78
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_failure_wrap.js
@@ -0,0 +1,8 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ Services.prefs.setCharPref("network.security.ports.banned", "65400");
+ run_test_in_child("../unit/test_redirect_failure.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_from_script_wrap.js b/netwerk/test/unit_ipc/test_redirect_from_script_wrap.js
new file mode 100644
index 0000000000..74f77a9ea1
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_from_script_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_redirect_from_script.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_history_wrap.js b/netwerk/test/unit_ipc/test_redirect_history_wrap.js
new file mode 100644
index 0000000000..38cdfa35ec
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_history_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_redirect_history.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_passing_wrap.js b/netwerk/test/unit_ipc/test_redirect_passing_wrap.js
new file mode 100644
index 0000000000..597ac35fb4
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_passing_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_redirect_passing.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_veto_parent.js b/netwerk/test/unit_ipc/test_redirect_veto_parent.js
new file mode 100644
index 0000000000..c2fa3fa000
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_veto_parent.js
@@ -0,0 +1,64 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+
+let ChannelEventSink2 = {
+ _classDescription: "WebRequest channel event sink",
+ _classID: Components.ID("115062f8-92f1-11e5-8b7f-08001110f7ec"),
+ _contractID: "@mozilla.org/webrequest/channel-event-sink;1",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]),
+
+ init() {
+ Components.manager
+ .QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(
+ this._classID,
+ this._classDescription,
+ this._contractID,
+ this
+ );
+ },
+
+ register() {
+ Services.catMan.addCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ this._contractID,
+ false,
+ true
+ );
+ },
+
+ unregister() {
+ Components.manager
+ .QueryInterface(Ci.nsIComponentRegistrar)
+ .unregisterFactory(this._classID, ChannelEventSink2);
+ Services.catMan.deleteCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ false
+ );
+ },
+
+ // nsIChannelEventSink implementation
+ asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
+ // Abort the redirection
+ redirectCallback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+};
+
+add_task(async function run_test() {
+ ChannelEventSink2.init();
+ ChannelEventSink2.register();
+
+ run_test_in_child("child_veto_in_parent.js");
+ await do_await_remote_message("child-test-done");
+ ChannelEventSink2.unregister();
+});
diff --git a/netwerk/test/unit_ipc/test_redirect_veto_wrap.js b/netwerk/test/unit_ipc/test_redirect_veto_wrap.js
new file mode 100644
index 0000000000..554683d944
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_veto_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect_veto.js");
+}
diff --git a/netwerk/test/unit_ipc/test_reentrancy_wrap.js b/netwerk/test/unit_ipc/test_reentrancy_wrap.js
new file mode 100644
index 0000000000..18f2509159
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_reentrancy_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_reentrancy.js");
+}
diff --git a/netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js b/netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js
new file mode 100644
index 0000000000..f2d90c33d0
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_reply_without_content_type.js");
+}
diff --git a/netwerk/test/unit_ipc/test_resumable_channel_wrap.js b/netwerk/test/unit_ipc/test_resumable_channel_wrap.js
new file mode 100644
index 0000000000..573ab25b64
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_resumable_channel_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_resumable_channel.js");
+}
diff --git a/netwerk/test/unit_ipc/test_simple_wrap.js b/netwerk/test/unit_ipc/test_simple_wrap.js
new file mode 100644
index 0000000000..8c6957e944
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_simple_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_simple.js");
+}
diff --git a/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap1.js b/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap1.js
new file mode 100644
index 0000000000..efe6978f35
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap1.js
@@ -0,0 +1,26 @@
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+function run_test() {
+ do_get_profile();
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.annotate_channels",
+ false
+ );
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.lower_network_priority",
+ false
+ );
+ do_test_pending();
+ UrlClassifierTestUtils.addTestTrackers().then(() => {
+ run_test_in_child(
+ "../unit/test_trackingProtection_annotateChannels.js",
+ () => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ do_test_finished();
+ }
+ );
+ do_test_finished();
+ });
+}
diff --git a/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap2.js b/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap2.js
new file mode 100644
index 0000000000..ab5eb356fd
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap2.js
@@ -0,0 +1,26 @@
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+function run_test() {
+ do_get_profile();
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.annotate_channels",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.lower_network_priority",
+ true
+ );
+ do_test_pending();
+ UrlClassifierTestUtils.addTestTrackers().then(() => {
+ run_test_in_child(
+ "../unit/test_trackingProtection_annotateChannels.js",
+ () => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ do_test_finished();
+ }
+ );
+ do_test_finished();
+ });
+}
diff --git a/netwerk/test/unit_ipc/test_trr_httpssvc_wrap.js b/netwerk/test/unit_ipc/test_trr_httpssvc_wrap.js
new file mode 100644
index 0000000000..11d933a71a
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_trr_httpssvc_wrap.js
@@ -0,0 +1,88 @@
+"use strict";
+
+let h2Port;
+let prefs;
+
+function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Services.prefs;
+
+ prefs.setBoolPref("network.security.esni.enabled", false);
+ prefs.setBoolPref("network.http.http2.enabled", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
+
+ // make all native resolve calls "secretly" resolve localhost instead
+ prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ // 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow
+ prefs.setIntPref("network.trr.mode", 3); // TRR first
+ prefs.setBoolPref("network.trr.wait-for-portal", false);
+ // don't confirm that TRR is working, just go!
+ prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ // So we can change the pref without clearing the cache to check a pushed
+ // record with a TRR path that fails.
+ prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
+ const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "../unit/http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(() => {
+ prefs.clearUserPref("network.security.esni.enabled");
+ prefs.clearUserPref("network.http.http2.enabled");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.dns.native-is-localhost");
+ prefs.clearUserPref("network.trr.mode");
+ prefs.clearUserPref("network.trr.uri");
+ prefs.clearUserPref("network.trr.credentials");
+ prefs.clearUserPref("network.trr.wait-for-portal");
+ prefs.clearUserPref("network.trr.allow-rfc1918");
+ prefs.clearUserPref("network.trr.useGET");
+ prefs.clearUserPref("network.trr.confirmationNS");
+ prefs.clearUserPref("network.trr.bootstrapAddr");
+ prefs.clearUserPref("network.trr.temp_blocklist_duration_sec");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
+});
+
+function run_test() {
+ prefs.setIntPref("network.trr.mode", 3);
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc"
+ );
+
+ do_await_remote_message("mode3-port").then(port => {
+ prefs.setIntPref("network.trr.mode", 3);
+ prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${port}/dns-query`
+ );
+ do_send_remote_message("mode3-port-done");
+ });
+
+ do_await_remote_message("clearCache").then(() => {
+ Services.dns.clearCache(true);
+ do_send_remote_message("clearCache-done");
+ });
+
+ do_await_remote_message("set-port-prefixed-pref").then(() => {
+ prefs.setBoolPref("network.dns.port_prefixed_qname_https_rr", true);
+ do_send_remote_message("set-port-prefixed-pref-done");
+ });
+
+ run_test_in_child("../unit/test_trr_httpssvc.js");
+}
diff --git a/netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js b/netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js
new file mode 100644
index 0000000000..00b4a0b85f
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_xmlhttprequest.js");
+}
diff --git a/netwerk/test/unit_ipc/xpcshell.toml b/netwerk/test/unit_ipc/xpcshell.toml
new file mode 100644
index 0000000000..569bcda751
--- /dev/null
+++ b/netwerk/test/unit_ipc/xpcshell.toml
@@ -0,0 +1,240 @@
+[DEFAULT]
+head = "head_channels_clone.js ../unit/head_servers.js head_trr_clone.js head_http3_clone.js"
+skip-if = ["socketprocess_networking"]
+# Several tests rely on redirecting to data: URIs, which was allowed for a long
+# time but now forbidden. So we enable it just for these tests.
+prefs = ["network.allow_redirect_to_data=true"]
+support-files = [
+ "child_channel_id.js",
+ "!/netwerk/test/unit/test_XHR_redirects.js",
+ "!/netwerk/test/unit/test_bug528292.js",
+ "!/netwerk/test/unit/test_cache-entry-id.js",
+ "!/netwerk/test/unit/test_cache_jar.js",
+ "!/netwerk/test/unit/test_cacheflags.js",
+ "!/netwerk/test/unit/test_channel_close.js",
+ "!/netwerk/test/unit/test_cookiejars.js",
+ "!/netwerk/test/unit/test_dns_cancel.js",
+ "!/netwerk/test/unit/test_dns_service.js",
+ "!/netwerk/test/unit/test_duplicate_headers.js",
+ "!/netwerk/test/unit/test_event_sink.js",
+ "!/netwerk/test/unit/test_getHost.js",
+ "!/netwerk/test/unit/test_gio_protocol.js",
+ "!/netwerk/test/unit/test_head.js",
+ "!/netwerk/test/unit/test_headers.js",
+ "!/netwerk/test/unit/test_httpsuspend.js",
+ "!/netwerk/test/unit/test_post.js",
+ "!/netwerk/test/unit/test_predictor.js",
+ "!/netwerk/test/unit/test_progress.js",
+ "!/netwerk/test/unit/test_redirect_veto.js",
+ "!/netwerk/test/unit/test_redirect-caching_canceled.js",
+ "!/netwerk/test/unit/test_redirect-caching_failure.js",
+ "!/netwerk/test/unit/test_redirect-caching_passing.js",
+ "!/netwerk/test/unit/test_redirect_canceled.js",
+ "!/netwerk/test/unit/test_redirect_different-protocol.js",
+ "!/netwerk/test/unit/test_redirect_failure.js",
+ "!/netwerk/test/unit/test_redirect_from_script.js",
+ "!/netwerk/test/unit/test_redirect_history.js",
+ "!/netwerk/test/unit/test_redirect_passing.js",
+ "!/netwerk/test/unit/test_reentrancy.js",
+ "!/netwerk/test/unit/test_reply_without_content_type.js",
+ "!/netwerk/test/unit/test_resumable_channel.js",
+ "!/netwerk/test/unit/test_simple.js",
+ "!/netwerk/test/unit/test_trackingProtection_annotateChannels.js",
+ "!/netwerk/test/unit/test_xmlhttprequest.js",
+ "!/netwerk/test/unit/head_channels.js",
+ "!/netwerk/test/unit/head_trr.js",
+ "!/netwerk/test/unit/head_cache2.js",
+ "!/netwerk/test/unit/data/image.png",
+ "!/netwerk/test/unit/data/system_root.lnk",
+ "!/netwerk/test/unit/data/test_psl.txt",
+ "!/netwerk/test/unit/data/test_readline1.txt",
+ "!/netwerk/test/unit/data/test_readline2.txt",
+ "!/netwerk/test/unit/data/test_readline3.txt",
+ "!/netwerk/test/unit/data/test_readline4.txt",
+ "!/netwerk/test/unit/data/test_readline5.txt",
+ "!/netwerk/test/unit/data/test_readline6.txt",
+ "!/netwerk/test/unit/data/test_readline7.txt",
+ "!/netwerk/test/unit/data/test_readline8.txt",
+ "!/netwerk/test/unit/data/signed_win.exe",
+ "!/netwerk/test/unit/test_alt-data_simple.js",
+ "!/netwerk/test/unit/test_alt-data_stream.js",
+ "!/netwerk/test/unit/test_alt-data_closeWithStatus.js",
+ "!/netwerk/test/unit/test_channel_priority.js",
+ "!/netwerk/test/unit/test_multipart_streamconv.js",
+ "!/netwerk/test/unit/test_original_sent_received_head.js",
+ "!/netwerk/test/unit/test_alt-data_cross_process.js",
+ "!/netwerk/test/unit/test_httpcancel.js",
+ "!/netwerk/test/unit/test_trr_httpssvc.js",
+ "!/netwerk/test/unit/test_http3_prio_enabled.js",
+ "!/netwerk/test/unit/test_http3_prio_disabled.js",
+ "!/netwerk/test/unit/test_http3_prio_helpers.js",
+ "!/netwerk/test/unit/http2-ca.pem",
+ "!/netwerk/test/unit/test_orb_empty_header.js",
+ "child_is_proxy_used.js",
+ "child_cookie_header.js",
+ "child_dns_by_type_resolve.js",
+ "child_veto_in_parent.js",
+]
+
+["test_XHR_redirects.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_alt-data_closeWithStatus_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_alt-data_cross_process_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_alt-data_simple_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_alt-data_stream_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_cache-entry-id_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_cache_jar_wrap.js"]
+
+["test_cacheflags_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_channel_close_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_channel_id.js"]
+
+["test_channel_priority_wrap.js"]
+
+["test_chunked_responses_wrap.js"]
+prefs = [
+ "network.allow_raw_sockets_in_content_processes=true",
+ "security.allow_eval_with_system_principal=true",
+]
+
+["test_cookie_header_stripped.js"]
+
+["test_cookiejars_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_dns_by_type_resolve_wrap.js"]
+
+["test_dns_cancel_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_dns_service_wrap.js"]
+
+["test_duplicate_headers_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_event_sink_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_getHost_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_gio_protocol_wrap.js"]
+run-if = ["os == 'linux'"]
+
+["test_head_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_headers_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_http3_prio_disabled_wrap.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix",
+] # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
+run-sequentially = "http3server"
+
+["test_http3_prio_enabled_wrap.js"]
+skip-if = [
+ "os == 'android'",
+ "os == 'win' && msix",
+] # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
+run-sequentially = "http3server"
+
+["test_httpcancel_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_httpsuspend_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_is_proxy_used.js"]
+
+["test_multipart_streamconv_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_orb_empty_header_wrap.js"]
+
+["test_original_sent_received_head_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_post_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_predictor_wrap.js"]
+
+["test_progress_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect-caching_canceled_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect-caching_failure_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect-caching_passing_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect_canceled_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect_different-protocol_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect_failure_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+# Do not test the channel.redirectTo() API under e10s until 827269 is resolved
+
+["test_redirect_from_script_wrap.js"]
+skip-if = ["true"]
+
+["test_redirect_history_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect_passing_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_redirect_veto_parent.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+run-sequentially = "doesn't play nice with others."
+
+["test_redirect_veto_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_reentrancy_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_reply_without_content_type_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_resumable_channel_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_simple_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_trackingProtection_annotateChannels_wrap1.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_trackingProtection_annotateChannels_wrap2.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
+
+["test_trr_httpssvc_wrap.js"]
+skip-if = ["os == 'android'"]
+
+["test_xmlhttprequest_wrap.js"]
+prefs = ["network.allow_raw_sockets_in_content_processes=true"]
diff --git a/netwerk/test/useragent/browser_nonsnap.toml b/netwerk/test/useragent/browser_nonsnap.toml
new file mode 100644
index 0000000000..8c4600182c
--- /dev/null
+++ b/netwerk/test/useragent/browser_nonsnap.toml
@@ -0,0 +1,4 @@
+[DEFAULT]
+
+["browser_ua_nonsnap.js"]
+run-if = ["os == 'linux'"]
diff --git a/netwerk/test/useragent/browser_snap.toml b/netwerk/test/useragent/browser_snap.toml
new file mode 100644
index 0000000000..3735ff2843
--- /dev/null
+++ b/netwerk/test/useragent/browser_snap.toml
@@ -0,0 +1,8 @@
+[DEFAULT]
+environment = [
+ "SNAP_INSTANCE_NAME=firefox",
+ "SNAP_NAME=firefox",
+]
+
+["browser_ua_snap_ubuntu.js"]
+run-if = ["os == 'linux' && is_ubuntu"]
diff --git a/netwerk/test/useragent/browser_ua_nonsnap.js b/netwerk/test/useragent/browser_ua_nonsnap.js
new file mode 100644
index 0000000000..ad5093b91a
--- /dev/null
+++ b/netwerk/test/useragent/browser_ua_nonsnap.js
@@ -0,0 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+"use strict";
+
+add_task(function test_ua_nonsnap() {
+ ok(navigator.userAgent.match(/X11; Linux/));
+});
diff --git a/netwerk/test/useragent/browser_ua_snap_ubuntu.js b/netwerk/test/useragent/browser_ua_snap_ubuntu.js
new file mode 100644
index 0000000000..00ed5d88b3
--- /dev/null
+++ b/netwerk/test/useragent/browser_ua_snap_ubuntu.js
@@ -0,0 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+"use strict";
+
+add_task(function test_ua_snap_ubuntu() {
+ ok(navigator.userAgent.match(/X11; Ubuntu; Linux/));
+});
diff --git a/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
new file mode 100644
index 0000000000..4dfb7224e7
--- /dev/null
+++ b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
@@ -0,0 +1,920 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=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 "Classifier.h"
+#include "mozilla/Components.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/net/AsyncUrlChannelClassifier.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/net/UrlClassifierFeatureResult.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUrlClassifierDBService.h"
+#include "nsUrlClassifierUtils.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+// Big picture comment
+// -----------------------------------------------------------------------------
+// nsUrlClassifierDBService::channelClassify() classifies a channel using a set
+// of URL-Classifier features. This method minimizes the number of lookups and
+// URI parsing and this is done using the classes here described.
+//
+// The first class is 'FeatureTask' which is able to retrieve the list of
+// features for this channel using the feature-factory. See
+// UrlClassifierFeatureFactory.
+// For each feature, it creates a FeatureData object, which contains the
+// entitylist and blocklist prefs and tables. The reason why we create
+// FeatureData is because:
+// - features are not thread-safe.
+// - we want to store the state of the classification in the FeatureData
+// object.
+//
+// It can happen that multiple features share the same tables. In order to do
+// the lookup just once, we have TableData class. When multiple features
+// contain the same table, they have references to the same couple TableData +
+// URIData objects.
+//
+// During the classification, the channel's URIs are fragmented. In order to
+// create these fragments just once, we use the URIData class, which is pointed
+// by TableData classes.
+//
+// The creation of these classes happens on the main-thread. The classification
+// happens on the worker thread.
+
+// URIData
+// -----------------------------------------------------------------------------
+
+// In order to avoid multiple URI parsing, we have this class which contains
+// nsIURI and its fragments.
+class URIData {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URIData);
+
+ static nsresult Create(nsIURI* aURI, nsIURI* aInnermostURI,
+ nsIUrlClassifierFeature::URIType aURIType,
+ URIData** aData);
+
+ bool IsEqual(nsIURI* aURI) const;
+
+ const nsTArray<nsCString>& Fragments();
+
+ nsIURI* URI() const;
+
+ private:
+ URIData();
+ ~URIData() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCString mURISpec;
+ nsTArray<nsCString> mFragments;
+ nsIUrlClassifierFeature::URIType mURIType;
+};
+
+/* static */
+nsresult URIData::Create(nsIURI* aURI, nsIURI* aInnermostURI,
+ nsIUrlClassifierFeature::URIType aURIType,
+ URIData** aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aInnermostURI);
+
+ RefPtr<URIData> data = new URIData();
+ data->mURI = aURI;
+ data->mURIType = aURIType;
+
+ nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance();
+ if (NS_WARN_IF(!utilsService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = utilsService->GetKeyForURI(aInnermostURI, data->mURISpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::URIData::Create new URIData created for spec "
+ "%s [this=%p]",
+ data->mURISpec.get(), data.get()));
+
+ data.forget(aData);
+ return NS_OK;
+}
+
+URIData::URIData() { MOZ_ASSERT(NS_IsMainThread()); }
+
+bool URIData::IsEqual(nsIURI* aURI) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURI);
+
+ bool isEqual = false;
+ nsresult rv = mURI->Equals(aURI, &isEqual);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return isEqual;
+}
+
+const nsTArray<nsCString>& URIData::Fragments() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (mFragments.IsEmpty()) {
+ nsresult rv;
+
+ if (mURIType == nsIUrlClassifierFeature::pairwiseEntitylistURI) {
+ rv = LookupCache::GetLookupEntitylistFragments(mURISpec, &mFragments);
+ } else {
+ rv = LookupCache::GetLookupFragments(mURISpec, &mFragments);
+ }
+
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ return mFragments;
+}
+
+nsIURI* URIData::URI() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mURI;
+}
+
+// TableData
+// ----------------------------------------------------------------------------
+
+// In order to avoid multiple lookups on the same table + URI, we have this
+// class.
+class TableData {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TableData);
+
+ enum State {
+ eUnclassified,
+ eNoMatch,
+ eMatch,
+ };
+
+ TableData(URIData* aURIData, const nsACString& aTable);
+
+ nsIURI* URI() const;
+
+ const nsACString& Table() const;
+
+ const LookupResultArray& Result() const;
+
+ State MatchState() const;
+
+ bool IsEqual(URIData* aURIData, const nsACString& aTable) const;
+
+ // Returns true if the table classifies the URI. This method must be called
+ // on hte classifier worker thread.
+ bool DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
+
+ private:
+ ~TableData();
+
+ RefPtr<URIData> mURIData;
+ State mState;
+
+ nsCString mTable;
+ LookupResultArray mResults;
+};
+
+TableData::TableData(URIData* aURIData, const nsACString& aTable)
+ : mURIData(aURIData), mState(eUnclassified), mTable(aTable) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURIData);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::TableData CTOR - new TableData created %s "
+ "[this=%p]",
+ aTable.BeginReading(), this));
+}
+
+TableData::~TableData() = default;
+
+nsIURI* TableData::URI() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mURIData->URI();
+}
+
+const nsACString& TableData::Table() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mTable;
+}
+
+const LookupResultArray& TableData::Result() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mResults;
+}
+
+TableData::State TableData::MatchState() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mState;
+}
+
+bool TableData::IsEqual(URIData* aURIData, const nsACString& aTable) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mURIData == aURIData && mTable == aTable;
+}
+
+bool TableData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aWorkerClassifier);
+
+ if (mState == TableData::eUnclassified) {
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::TableData::DoLookup - starting lookup "
+ "[this=%p]",
+ this));
+
+ const nsTArray<nsCString>& fragments = mURIData->Fragments();
+ nsresult rv = aWorkerClassifier->DoSingleLocalLookupWithURIFragments(
+ fragments, mTable, mResults);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ mState = mResults.IsEmpty() ? TableData::eNoMatch : TableData::eMatch;
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::TableData::DoLookup - lookup completed. "
+ "Matches: %d [this=%p]",
+ (int)mResults.Length(), this));
+ }
+
+ return !mResults.IsEmpty();
+}
+
+// FeatureData
+// ----------------------------------------------------------------------------
+
+class FeatureTask;
+
+// This is class contains all the Feature data.
+class FeatureData {
+ enum State {
+ eUnclassified,
+ eNoMatch,
+ eMatchBlocklist,
+ eMatchEntitylist,
+ };
+
+ public:
+ FeatureData() = default;
+ ~FeatureData();
+
+ nsresult Initialize(FeatureTask* aTask, nsIChannel* aChannel,
+ nsIUrlClassifierFeature* aFeature);
+
+ void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
+
+ // Returns true if the next feature should be processed.
+ bool MaybeCompleteClassification(nsIChannel* aChannel);
+
+ private:
+ nsresult InitializeList(FeatureTask* aTask, nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsTArray<RefPtr<TableData>>& aList);
+
+ State mState{eUnclassified};
+ nsCOMPtr<nsIUrlClassifierFeature> mFeature;
+ nsCOMPtr<nsIChannel> mChannel;
+
+ nsTArray<RefPtr<TableData>> mBlocklistTables;
+ nsTArray<RefPtr<TableData>> mEntitylistTables;
+
+ // blocklist + entitylist.
+ nsCString mHostInPrefTables[2];
+};
+
+FeatureData::~FeatureData() {
+ NS_ReleaseOnMainThread("FeatureData:mFeature", mFeature.forget());
+}
+
+nsresult FeatureData::Initialize(FeatureTask* aTask, nsIChannel* aChannel,
+ nsIUrlClassifierFeature* aFeature) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTask);
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(aFeature);
+
+ if (UC_LOG_ENABLED()) {
+ nsAutoCString name;
+ aFeature->GetName(name);
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::Initialize - Feature %s "
+ "[this=%p, channel=%p]",
+ name.get(), this, aChannel));
+ }
+
+ mFeature = aFeature;
+ mChannel = aChannel;
+
+ nsresult rv = InitializeList(
+ aTask, aChannel, nsIUrlClassifierFeature::blocklist, mBlocklistTables);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = InitializeList(aTask, aChannel, nsIUrlClassifierFeature::entitylist,
+ mEntitylistTables);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void FeatureData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aWorkerClassifier);
+ MOZ_ASSERT(mState == eUnclassified);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::DoLookup - lookup starting "
+ "[this=%p]",
+ this));
+
+ // This is wrong, but it's fast: we don't want to check if the host is in the
+ // blocklist table if we know that it's going to be entitylisted by pref.
+ // So, also if maybe it's not blocklisted, let's consider it 'entitylisted'.
+ if (!mHostInPrefTables[nsIUrlClassifierFeature::entitylist].IsEmpty()) {
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::DoLookup - entitylisted by pref "
+ "[this=%p]",
+ this));
+ mState = eMatchEntitylist;
+ return;
+ }
+
+ // Let's check if this feature blocklists the URI.
+
+ bool isBlocklisted =
+ !mHostInPrefTables[nsIUrlClassifierFeature::blocklist].IsEmpty();
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted by pref: "
+ "%d [this=%p]",
+ isBlocklisted, this));
+
+ if (!isBlocklisted) {
+ // If one of the blocklist table matches the URI, we don't need to continue
+ // with the others: the feature is blocklisted (but maybe also
+ // entitylisted).
+ for (TableData* tableData : mBlocklistTables) {
+ if (tableData->DoLookup(aWorkerClassifier)) {
+ isBlocklisted = true;
+ break;
+ }
+ }
+ }
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted before "
+ "entitylisting: %d [this=%p]",
+ isBlocklisted, this));
+
+ if (!isBlocklisted) {
+ mState = eNoMatch;
+ return;
+ }
+
+ // Now, let's check if we need to entitylist the same URI.
+
+ for (TableData* tableData : mEntitylistTables) {
+ // If one of the entitylist table matches the URI, we don't need to continue
+ // with the others: the feature is entitylisted.
+ if (tableData->DoLookup(aWorkerClassifier)) {
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::DoLookup - entitylisted by "
+ "table [this=%p]",
+ this));
+ mState = eMatchEntitylist;
+ return;
+ }
+ }
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted [this=%p]",
+ this));
+ mState = eMatchBlocklist;
+}
+
+bool FeatureData::MaybeCompleteClassification(nsIChannel* aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoCString name;
+ mFeature->GetName(name);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
+ "completing "
+ "classification [this=%p channel=%p]",
+ this, aChannel));
+
+ switch (mState) {
+ case eNoMatch:
+ UC_LOG(
+ ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
+ "no match for feature %s. Let's "
+ "move on [this=%p channel=%p]",
+ name.get(), this, aChannel));
+ return true;
+
+ case eMatchEntitylist:
+ UC_LOG(
+ ("AsyncChannelClassifier::FeatureData::MayebeCompleteClassification "
+ "- entitylisted by feature %s. Let's "
+ "move on [this=%p channel=%p]",
+ name.get(), this, aChannel));
+ return true;
+
+ case eMatchBlocklist:
+ UC_LOG(
+ ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
+ "blocklisted by feature %s [this=%p channel=%p]",
+ name.get(), this, aChannel));
+ break;
+
+ case eUnclassified:
+ MOZ_CRASH("We should not be here!");
+ break;
+ }
+
+ MOZ_ASSERT(mState == eMatchBlocklist);
+
+ // Maybe we have to ignore this host
+ nsAutoCString exceptionList;
+ nsresult rv = mFeature->GetExceptionHostList(exceptionList);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ UC_LOG_WARN(
+ ("AsyncChannelClassifier::FeatureData::MayebeCompleteClassification - "
+ "error. Let's move on [this=%p channel=%p]",
+ this, aChannel));
+ return true;
+ }
+
+ if (!mBlocklistTables.IsEmpty() &&
+ nsContentUtils::IsURIInList(mBlocklistTables[0]->URI(), exceptionList)) {
+ nsCString spec = mBlocklistTables[0]->URI()->GetSpecOrDefault();
+ spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
+ UC_LOG(
+ ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
+ "uri %s found in "
+ "exceptionlist of feature %s [this=%p channel=%p]",
+ spec.get(), name.get(), this, aChannel));
+ return true;
+ }
+
+ nsTArray<nsCString> list;
+ nsTArray<nsCString> hashes;
+ if (!mHostInPrefTables[nsIUrlClassifierFeature::blocklist].IsEmpty()) {
+ list.AppendElement(mHostInPrefTables[nsIUrlClassifierFeature::blocklist]);
+
+ // Telemetry expects every tracking channel has hash, create it for test
+ // entry
+ Completion complete;
+ complete.FromPlaintext(
+ mHostInPrefTables[nsIUrlClassifierFeature::blocklist]);
+ hashes.AppendElement(complete.ToString());
+ }
+
+ for (TableData* tableData : mBlocklistTables) {
+ if (tableData->MatchState() == TableData::eMatch) {
+ list.AppendElement(tableData->Table());
+
+ for (const auto& r : tableData->Result()) {
+ hashes.AppendElement(r->hash.complete.ToString());
+ }
+ }
+ }
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
+ "process channel [this=%p channel=%p]",
+ this, aChannel));
+
+ bool shouldContinue = false;
+ rv = mFeature->ProcessChannel(aChannel, list, hashes, &shouldContinue);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return shouldContinue;
+}
+
+// CallbackHolder
+// ----------------------------------------------------------------------------
+
+// This class keeps the callback alive and makes sure that we release it on the
+// correct thread.
+class CallbackHolder final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(CallbackHolder);
+
+ explicit CallbackHolder(std::function<void()>&& aCallback)
+ : mCallback(std::move(aCallback)) {}
+
+ void Exec() const { mCallback(); }
+
+ private:
+ ~CallbackHolder() = default;
+
+ std::function<void()> mCallback;
+};
+
+// FeatureTask
+// ----------------------------------------------------------------------------
+
+// A FeatureTask is a class that is able to classify a channel using a set of
+// features. The features are grouped by:
+// - URIs - to avoid extra URI parsing.
+// - Tables - to avoid multiple lookup on the same table.
+class FeatureTask {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FeatureTask);
+
+ static nsresult Create(nsIChannel* aChannel,
+ std::function<void()>&& aCallback,
+ FeatureTask** aTask);
+
+ // Called on the classifier thread.
+ void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
+
+ // Called on the main-thread to process the channel.
+ void CompleteClassification();
+
+ nsresult GetOrCreateURIData(nsIURI* aURI, nsIURI* aInnermostURI,
+ nsIUrlClassifierFeature::URIType aURIType,
+ URIData** aData);
+
+ nsresult GetOrCreateTableData(URIData* aURIData, const nsACString& aTable,
+ TableData** aData);
+
+ private:
+ FeatureTask(nsIChannel* aChannel, std::function<void()>&& aCallback);
+ ~FeatureTask();
+
+ nsCOMPtr<nsIChannel> mChannel;
+ RefPtr<CallbackHolder> mCallbackHolder;
+
+ nsTArray<FeatureData> mFeatures;
+ nsTArray<RefPtr<URIData>> mURIs;
+ nsTArray<RefPtr<TableData>> mTables;
+};
+
+// Features are able to classify particular URIs from a channel. For instance,
+// tracking-annotation feature uses the top-level URI to entitylist the current
+// channel's URI. Because of
+// this, this function aggregates feature per URI and tables.
+/* static */
+nsresult FeatureTask::Create(nsIChannel* aChannel,
+ std::function<void()>&& aCallback,
+ FeatureTask** aTask) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(aTask);
+
+ // We need to obtain the list of nsIUrlClassifierFeature objects able to
+ // classify this channel. If the list is empty, we do an early return.
+ nsTArray<nsCOMPtr<nsIUrlClassifierFeature>> features;
+ UrlClassifierFeatureFactory::GetFeaturesFromChannel(aChannel, features);
+ if (features.IsEmpty()) {
+ UC_LOG(
+ ("AsyncChannelClassifier::FeatureTask::Create - no task is needed for "
+ "channel %p",
+ aChannel));
+ return NS_OK;
+ }
+
+ RefPtr<FeatureTask> task = new FeatureTask(aChannel, std::move(aCallback));
+
+ UC_LOG(
+ ("AsyncChannelClassifier::FeatureTask::Create - FeatureTask %p created "
+ "for channel %p",
+ task.get(), aChannel));
+
+ for (nsIUrlClassifierFeature* feature : features) {
+ FeatureData* featureData = task->mFeatures.AppendElement();
+ nsresult rv = featureData->Initialize(task, aChannel, feature);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ task.forget(aTask);
+ return NS_OK;
+}
+
+FeatureTask::FeatureTask(nsIChannel* aChannel,
+ std::function<void()>&& aCallback)
+ : mChannel(aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mChannel);
+
+ std::function<void()> callback = std::move(aCallback);
+ mCallbackHolder = new CallbackHolder(std::move(callback));
+}
+
+FeatureTask::~FeatureTask() {
+ NS_ReleaseOnMainThread("FeatureTask::mChannel", mChannel.forget());
+ NS_ReleaseOnMainThread("FeatureTask::mCallbackHolder",
+ mCallbackHolder.forget());
+}
+
+nsresult FeatureTask::GetOrCreateURIData(
+ nsIURI* aURI, nsIURI* aInnermostURI,
+ nsIUrlClassifierFeature::URIType aURIType, URIData** aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aInnermostURI);
+ MOZ_ASSERT(aData);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - checking if "
+ "a URIData must be "
+ "created [this=%p]",
+ this));
+
+ for (URIData* data : mURIs) {
+ if (data->IsEqual(aURI)) {
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - reuse "
+ "existing URIData %p [this=%p]",
+ data, this));
+
+ RefPtr<URIData> uriData = data;
+ uriData.forget(aData);
+ return NS_OK;
+ }
+ }
+
+ RefPtr<URIData> data;
+ nsresult rv =
+ URIData::Create(aURI, aInnermostURI, aURIType, getter_AddRefs(data));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mURIs.AppendElement(data);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - create new "
+ "URIData %p [this=%p]",
+ data.get(), this));
+
+ data.forget(aData);
+ return NS_OK;
+}
+
+nsresult FeatureTask::GetOrCreateTableData(URIData* aURIData,
+ const nsACString& aTable,
+ TableData** aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURIData);
+ MOZ_ASSERT(aData);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureTask::GetOrCreateTableData - checking "
+ "if TableData must be "
+ "created [this=%p]",
+ this));
+
+ for (TableData* data : mTables) {
+ if (data->IsEqual(aURIData, aTable)) {
+ UC_LOG_LEAK(
+ ("FeatureTask::GetOrCreateTableData - reuse existing TableData %p "
+ "[this=%p]",
+ data, this));
+
+ RefPtr<TableData> tableData = data;
+ tableData.forget(aData);
+ return NS_OK;
+ }
+ }
+
+ RefPtr<TableData> data = new TableData(aURIData, aTable);
+ mTables.AppendElement(data);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureTask::GetOrCreateTableData - create new "
+ "TableData %p [this=%p]",
+ data.get(), this));
+
+ data.forget(aData);
+ return NS_OK;
+}
+
+void FeatureTask::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aWorkerClassifier);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureTask::DoLookup - starting lookup "
+ "[this=%p]",
+ this));
+
+ for (FeatureData& feature : mFeatures) {
+ feature.DoLookup(aWorkerClassifier);
+ }
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureTask::DoLookup - lookup completed "
+ "[this=%p]",
+ this));
+}
+
+void FeatureTask::CompleteClassification() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (FeatureData& feature : mFeatures) {
+ if (!feature.MaybeCompleteClassification(mChannel)) {
+ break;
+ }
+ }
+
+ UC_LOG(
+ ("AsyncChannelClassifier::FeatureTask::CompleteClassification - complete "
+ "classification for "
+ "channel %p [this=%p]",
+ mChannel.get(), this));
+
+ mCallbackHolder->Exec();
+}
+
+nsresult FeatureData::InitializeList(
+ FeatureTask* aTask, nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsTArray<RefPtr<TableData>>& aList) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTask);
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::InitializeList - initialize list "
+ "%d for channel %p [this=%p]",
+ aListType, aChannel, this));
+
+ nsCOMPtr<nsIURI> uri;
+ nsIUrlClassifierFeature::URIType URIType;
+ nsresult rv = mFeature->GetURIByListType(aChannel, aListType, &URIType,
+ getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ if (UC_LOG_ENABLED()) {
+ nsAutoCString errorName;
+ GetErrorName(rv, errorName);
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::InitializeList - Got an "
+ "unexpected error (rv=%s) [this=%p]",
+ errorName.get(), this));
+ }
+ return rv;
+ }
+
+ if (!uri) {
+ // Return success when the URI is empty to conitnue to do the lookup.
+ UC_LOG_LEAK(
+ ("AsyncChannelClassifier::FeatureData::InitializeList - got an empty "
+ "URL [this=%p]",
+ this));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(uri);
+ if (NS_WARN_IF(!innermostURI)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString host;
+ rv = innermostURI->GetHost(host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool found = false;
+ nsAutoCString tableName;
+ rv = mFeature->HasHostInPreferences(host, aListType, tableName, &found);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (found) {
+ mHostInPrefTables[aListType] = tableName;
+ }
+
+ RefPtr<URIData> uriData;
+ rv = aTask->GetOrCreateURIData(uri, innermostURI, URIType,
+ getter_AddRefs(uriData));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(uriData);
+
+ nsTArray<nsCString> tables;
+ rv = mFeature->GetTables(aListType, tables);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (const nsCString& table : tables) {
+ RefPtr<TableData> data;
+ rv = aTask->GetOrCreateTableData(uriData, table, getter_AddRefs(data));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(data);
+ aList.AppendElement(data);
+ }
+
+ return NS_OK;
+}
+
+} // namespace
+
+/* static */
+nsresult AsyncUrlChannelClassifier::CheckChannel(
+ nsIChannel* aChannel, std::function<void()>&& aCallback) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aChannel);
+
+ if (!aCallback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (UC_LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> chanURI;
+ if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(chanURI)))) {
+ nsCString chanSpec = chanURI->GetSpecOrDefault();
+ chanSpec.Truncate(
+ std::min(chanSpec.Length(), UrlClassifierCommon::sMaxSpecLength));
+
+ nsCOMPtr<nsIURI> topWinURI;
+ Unused << UrlClassifierCommon::GetTopWindowURI(aChannel,
+ getter_AddRefs(topWinURI));
+ nsCString topWinSpec =
+ topWinURI ? topWinURI->GetSpecOrDefault() : "(null)"_ns;
+
+ topWinSpec.Truncate(
+ std::min(topWinSpec.Length(), UrlClassifierCommon::sMaxSpecLength));
+
+ UC_LOG(
+ ("AsyncUrlChannelClassifier::CheckChannel - starting the "
+ "classification on channel %p",
+ aChannel));
+ UC_LOG((" uri is %s [channel=%p]", chanSpec.get(), aChannel));
+ UC_LOG(
+ (" top-level uri is %s [channel=%p]", topWinSpec.get(), aChannel));
+ }
+ }
+
+ RefPtr<FeatureTask> task;
+ nsresult rv =
+ FeatureTask::Create(aChannel, std::move(aCallback), getter_AddRefs(task));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!task) {
+ // No task is needed for this channel, return an error so the caller won't
+ // wait for a callback.
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsUrlClassifierDBServiceWorker> workerClassifier =
+ nsUrlClassifierDBService::GetWorker();
+ if (NS_WARN_IF(!workerClassifier)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "AsyncUrlChannelClassifier::CheckChannel",
+ [task, workerClassifier]() -> void {
+ MOZ_ASSERT(!NS_IsMainThread());
+ task->DoLookup(workerClassifier);
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "AsyncUrlChannelClassifier::CheckChannel - return",
+ [task]() -> void { task->CompleteClassification(); });
+
+ NS_DispatchToMainThread(r);
+ });
+
+ return nsUrlClassifierDBService::BackgroundThread()->Dispatch(
+ r, NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/AsyncUrlChannelClassifier.h b/netwerk/url-classifier/AsyncUrlChannelClassifier.h
new file mode 100644
index 0000000000..52eae5a974
--- /dev/null
+++ b/netwerk/url-classifier/AsyncUrlChannelClassifier.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=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/. */
+
+#ifndef mozilla_net_AsyncUrlChannelClassifier_h
+#define mozilla_net_AsyncUrlChannelClassifier_h
+
+#include "nsISupports.h"
+#include <functional>
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class AsyncUrlChannelClassifier final {
+ public:
+ static nsresult CheckChannel(nsIChannel* aChannel,
+ std::function<void()>&& aCallback);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AsyncUrlChannelClassifier_h
diff --git a/netwerk/url-classifier/ChannelClassifierService.cpp b/netwerk/url-classifier/ChannelClassifierService.cpp
new file mode 100644
index 0000000000..4b1145ba00
--- /dev/null
+++ b/netwerk/url-classifier/ChannelClassifierService.cpp
@@ -0,0 +1,271 @@
+/* -*- 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 "ChannelClassifierService.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+
+#include "UrlClassifierFeatureCryptominingProtection.h"
+#include "UrlClassifierFeatureFingerprintingProtection.h"
+#include "UrlClassifierFeatureSocialTrackingProtection.h"
+#include "UrlClassifierFeatureTrackingProtection.h"
+
+#include "mozilla/StaticPtr.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<ChannelClassifierService> gChannelClassifierService;
+
+NS_IMPL_ISUPPORTS(UrlClassifierBlockedChannel, nsIUrlClassifierBlockedChannel)
+
+UrlClassifierBlockedChannel::UrlClassifierBlockedChannel(nsIChannel* aChannel)
+ : mChannel(aChannel),
+ mDecision(ChannelBlockDecision::Blocked),
+ mReason(TRACKING_PROTECTION) {
+ MOZ_ASSERT(aChannel);
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::GetReason(uint8_t* aReason) {
+ NS_ENSURE_ARG_POINTER(aReason);
+
+ *aReason = mReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::GetUrl(nsAString& aUrl) {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ CopyUTF8toUTF16(uri->GetSpecOrDefault(), aUrl);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::GetTabId(uint64_t* aTabId) {
+ NS_ENSURE_ARG_POINTER(aTabId);
+
+ *aTabId = 0;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ RefPtr<dom::BrowsingContext> browsingContext;
+ nsresult rv =
+ loadInfo->GetTargetBrowsingContext(getter_AddRefs(browsingContext));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !browsingContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get top-level browsing context to ensure window global parent is ready
+ // to use, tabId is the same anyway.
+ dom::CanonicalBrowsingContext* top = browsingContext->Canonical()->Top();
+ dom::WindowGlobalParent* wgp = top->GetCurrentWindowGlobal();
+ if (!wgp) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<dom::BrowserParent> browserParent = wgp->GetBrowserParent();
+ if (!browserParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aTabId = browserParent->GetTabId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::GetChannelId(uint64_t* aChannelId) {
+ NS_ENSURE_ARG_POINTER(aChannelId);
+
+ nsCOMPtr<nsIIdentChannel> channel(do_QueryInterface(mChannel));
+ *aChannelId = channel ? channel->ChannelId() : 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::GetTopLevelUrl(nsAString& aTopLevelUrl) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ RefPtr<dom::BrowsingContext> browsingContext;
+ nsresult rv =
+ loadInfo->GetTargetBrowsingContext(getter_AddRefs(browsingContext));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !browsingContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get top-level browsing context to ensure window global parent is ready
+ // to use, tabId is the same anyway.
+ dom::CanonicalBrowsingContext* top = browsingContext->Canonical()->Top();
+ dom::WindowGlobalParent* wgp = top->GetCurrentWindowGlobal();
+ if (!wgp) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsIURI> uri = wgp->GetDocumentURI();
+ if (!uri) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CopyUTF8toUTF16(uri->GetSpecOrDefault(), aTopLevelUrl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::GetTables(nsACString& aTables) {
+ aTables.Assign(mTables);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::GetIsPrivateBrowsing(bool* aIsPrivateBrowsing) {
+ NS_ENSURE_ARG_POINTER(aIsPrivateBrowsing);
+
+ *aIsPrivateBrowsing = NS_UsePrivateBrowsing(mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::Allow() {
+ UC_LOG(("ChannelClassifierService: allow loading the channel %p",
+ mChannel.get()));
+
+ mDecision = ChannelBlockDecision::Allowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierBlockedChannel::Replace() {
+ UC_LOG(("ChannelClassifierService: replace channel %p", mChannel.get()));
+
+ mDecision = ChannelBlockDecision::Replaced;
+ return NS_OK;
+}
+
+void UrlClassifierBlockedChannel::SetReason(const nsACString& aFeatureName,
+ const nsACString& aTableName) {
+ mTables = aTableName;
+
+ nsCOMPtr<nsIUrlClassifierFeature> feature;
+ feature =
+ UrlClassifierFeatureTrackingProtection::GetIfNameMatches(aFeatureName);
+ if (feature) {
+ mReason = TRACKING_PROTECTION;
+ return;
+ }
+
+ feature = UrlClassifierFeatureSocialTrackingProtection::GetIfNameMatches(
+ aFeatureName);
+ if (feature) {
+ mReason = SOCIAL_TRACKING_PROTECTION;
+ return;
+ }
+
+ feature = UrlClassifierFeatureFingerprintingProtection::GetIfNameMatches(
+ aFeatureName);
+ if (feature) {
+ mReason = FINGERPRINTING_PROTECTION;
+ return;
+ }
+
+ feature = UrlClassifierFeatureCryptominingProtection::GetIfNameMatches(
+ aFeatureName);
+ if (feature) {
+ mReason = CRYPTOMINING_PROTECTION;
+ return;
+ }
+}
+
+NS_IMPL_ISUPPORTS(ChannelClassifierService, nsIChannelClassifierService)
+
+// static
+already_AddRefed<nsIChannelClassifierService>
+ChannelClassifierService::GetSingleton() {
+ if (gChannelClassifierService) {
+ return do_AddRef(gChannelClassifierService);
+ }
+
+ gChannelClassifierService = new ChannelClassifierService();
+ ClearOnShutdown(&gChannelClassifierService);
+ return do_AddRef(gChannelClassifierService);
+}
+
+ChannelClassifierService::ChannelClassifierService() { mListeners.Clear(); }
+
+NS_IMETHODIMP
+ChannelClassifierService::AddListener(nsIObserver* aObserver) {
+ MOZ_ASSERT(aObserver);
+ MOZ_ASSERT(!mListeners.Contains(aObserver));
+
+ mListeners.AppendElement(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChannelClassifierService::RemoveListener(nsIObserver* aObserver) {
+ MOZ_ASSERT(aObserver);
+ MOZ_ASSERT(mListeners.Contains(aObserver));
+
+ mListeners.RemoveElement(aObserver);
+ return NS_OK;
+}
+
+/* static */
+ChannelBlockDecision ChannelClassifierService::OnBeforeBlockChannel(
+ nsIChannel* aChannel, const nsACString& aFeatureName,
+ const nsACString& aTableName) {
+ MOZ_ASSERT(aChannel);
+
+ // Don't bother continuing if no one has ever registered listener
+ if (!gChannelClassifierService || !gChannelClassifierService->HasListener()) {
+ return ChannelBlockDecision::Blocked;
+ }
+
+ ChannelBlockDecision decision;
+ nsresult rv = gChannelClassifierService->OnBeforeBlockChannel(
+ aChannel, aFeatureName, aTableName, decision);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return ChannelBlockDecision::Blocked;
+ }
+
+ return decision;
+}
+
+nsresult ChannelClassifierService::OnBeforeBlockChannel(
+ nsIChannel* aChannel, const nsACString& aFeatureName,
+ const nsACString& aTableName, ChannelBlockDecision& aDecision) {
+ MOZ_ASSERT(aChannel);
+
+ aDecision = ChannelBlockDecision::Blocked;
+
+ RefPtr<UrlClassifierBlockedChannel> channel =
+ new UrlClassifierBlockedChannel(aChannel);
+ channel->SetReason(aFeatureName, aTableName);
+
+ for (const auto& listener : mListeners) {
+ listener->Observe(
+ NS_ISUPPORTS_CAST(nsIUrlClassifierBlockedChannel*, channel),
+ "urlclassifier-before-block-channel", nullptr);
+
+ aDecision = channel->GetDecision();
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/ChannelClassifierService.h b/netwerk/url-classifier/ChannelClassifierService.h
new file mode 100644
index 0000000000..2c6c13b10d
--- /dev/null
+++ b/netwerk/url-classifier/ChannelClassifierService.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=2 sw=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/. */
+
+#ifndef mozilla_net_ChannelClassifierService_h
+#define mozilla_net_ChannelClassifierService_h
+
+#include "nsIChannelClassifierService.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+enum class ChannelBlockDecision {
+ Blocked,
+ Replaced,
+ Allowed,
+};
+
+class UrlClassifierBlockedChannel final
+ : public nsIUrlClassifierBlockedChannel {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLCLASSIFIERBLOCKEDCHANNEL
+
+ explicit UrlClassifierBlockedChannel(nsIChannel* aChannel);
+
+ bool IsUnblocked() const {
+ return mDecision != ChannelBlockDecision::Blocked;
+ }
+
+ ChannelBlockDecision GetDecision() { return mDecision; };
+
+ void SetReason(const nsACString& aFeatureName, const nsACString& aTableName);
+
+ protected:
+ ~UrlClassifierBlockedChannel() = default;
+
+ private:
+ nsCOMPtr<nsIChannel> mChannel;
+ ChannelBlockDecision mDecision;
+ uint8_t mReason;
+ nsCString mTables;
+};
+
+class ChannelClassifierService final : public nsIChannelClassifierService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICHANNELCLASSIFIERSERVICE
+
+ friend class UrlClassifierBlockedChannel;
+
+ static already_AddRefed<nsIChannelClassifierService> GetSingleton();
+
+ static ChannelBlockDecision OnBeforeBlockChannel(
+ nsIChannel* aChannel, const nsACString& aFeatureName,
+ const nsACString& aTableName);
+
+ nsresult OnBeforeBlockChannel(nsIChannel* aChannel,
+ const nsACString& aFeatureName,
+ const nsACString& aTableName,
+ ChannelBlockDecision& aDecision);
+
+ bool HasListener() const { return !mListeners.IsEmpty(); }
+
+ private:
+ ChannelClassifierService();
+ ~ChannelClassifierService() = default;
+
+ nsTArray<nsCOMPtr<nsIObserver>> mListeners;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ChannelClassifierService_h
diff --git a/netwerk/url-classifier/UrlClassifierCommon.cpp b/netwerk/url-classifier/UrlClassifierCommon.cpp
new file mode 100644
index 0000000000..21589cef94
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierCommon.cpp
@@ -0,0 +1,671 @@
+/* -*- 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/UrlClassifierCommon.h"
+
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Components.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_channelclassifier.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDocShell.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIParentChannel.h"
+#include "nsIScriptError.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsReadableUtils.h"
+
+namespace mozilla {
+namespace net {
+
+const nsCString::size_type UrlClassifierCommon::sMaxSpecLength = 128;
+
+// MOZ_LOG=nsChannelClassifier:5
+LazyLogModule UrlClassifierCommon::sLog("nsChannelClassifier");
+LazyLogModule UrlClassifierCommon::sLogLeak("nsChannelClassifierLeak");
+
+/* static */
+bool UrlClassifierCommon::AddonMayLoad(nsIChannel* aChannel, nsIURI* aURI) {
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->LoadInfo();
+ // loadingPrincipal is used here to ensure we are loading into an
+ // addon principal. This allows an addon, with explicit permission, to
+ // call out to API endpoints that may otherwise get blocked.
+ nsIPrincipal* loadingPrincipal = channelLoadInfo->GetLoadingPrincipal();
+ if (!loadingPrincipal) {
+ return false;
+ }
+
+ return BasePrincipal::Cast(loadingPrincipal)->AddonAllowsLoad(aURI, true);
+}
+
+/* static */
+bool UrlClassifierCommon::ShouldEnableProtectionForChannel(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIURI> chanURI;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(chanURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ if (UrlClassifierCommon::AddonMayLoad(aChannel, chanURI)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> topWinURI;
+ nsCOMPtr<nsIHttpChannelInternal> channel = do_QueryInterface(aChannel);
+ if (NS_WARN_IF(!channel)) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ auto policyType = loadInfo->GetExternalContentPolicyType();
+ if (policyType == ExtContentPolicy::TYPE_DOCUMENT) {
+ UC_LOG(
+ ("UrlClassifierCommon::ShouldEnableProtectionForChannel - "
+ "skipping top-level load for channel %p",
+ aChannel));
+ return false;
+ }
+
+ // 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.
+
+ return true;
+}
+
+/* static */
+nsresult UrlClassifierCommon::SetTrackingInfo(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aLists,
+ const nsTArray<nsCString>& aFullHashes) {
+ NS_ENSURE_ARG(!aLists.IsEmpty());
+
+ // Can be called in EITHER the parent or child process.
+ nsresult rv;
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(aChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (classifiedChannel) {
+ classifiedChannel->SetMatchedTrackingInfo(aLists, aFullHashes);
+ }
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(aChannel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request.
+ // Tell the child process channel to do this as well.
+ // TODO: We can remove the code sending the IPC to content to update
+ // tracking info once we move the ContentBlockingLog into the parent.
+ // This would be done in Bug 1599046.
+ nsAutoCString strLists, strHashes;
+ TablesToString(aLists, strLists);
+ TablesToString(aFullHashes, strHashes);
+
+ parentChannel->SetClassifierMatchedTrackingInfo(strLists, strHashes);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult UrlClassifierCommon::SetBlockedContent(nsIChannel* channel,
+ nsresult aErrorCode,
+ const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ NS_ENSURE_ARG(!aList.IsEmpty());
+
+ switch (aErrorCode) {
+ case NS_ERROR_MALWARE_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_MALWARE_URI);
+ break;
+ case NS_ERROR_PHISHING_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_PHISHING_URI);
+ break;
+ case NS_ERROR_UNWANTED_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_UNWANTED_URI);
+ break;
+ case NS_ERROR_TRACKING_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_TRACKING_URI);
+ break;
+ case NS_ERROR_BLOCKED_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_BLOCKED_URI);
+ break;
+ case NS_ERROR_HARMFUL_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_HARMFUL_URI);
+ break;
+ case NS_ERROR_CRYPTOMINING_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_CRYPTOMINING_URI);
+ break;
+ case NS_ERROR_FINGERPRINTING_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_FINGERPRINTING_URI);
+ break;
+ case NS_ERROR_SOCIALTRACKING_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_SOCIALTRACKING_URI);
+ break;
+ case NS_ERROR_EMAILTRACKING_URI:
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_EMAILTRACKING_URI);
+ break;
+ default:
+ MOZ_CRASH(
+ "Missing nsILoadInfo::BLOCKING_REASON* for the classification error");
+ break;
+ }
+
+ // Can be called in EITHER the parent or child process.
+ nsresult rv;
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(channel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (classifiedChannel) {
+ classifiedChannel->SetMatchedInfo(aList, aProvider, aFullHash);
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(channel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request.
+ // Tell the child process channel to do this as well.
+ // TODO: We can remove the code sending the IPC to content to update
+ // matched info once we move the ContentBlockingLog into the parent.
+ // This would be done in Bug 1601063.
+ parentChannel->SetClassifierMatchedInfo(aList, aProvider, aFullHash);
+ }
+
+ unsigned state =
+ UrlClassifierFeatureFactory::GetClassifierBlockingEventCode(aErrorCode);
+ if (!state) {
+ state = nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
+ }
+ ContentBlockingNotifier::OnEvent(channel, state);
+
+ return NS_OK;
+ }
+
+ // TODO: ReportToConsole is called in the child process,
+ // If nsContentUtils::ReportToConsole is not fission compatiable(cannot report
+ // to correct top-level window), we need to do this in the parent process
+ // instead (find the top-level window in the parent and send an IPC to child
+ // processes to report console).
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ components::ThirdPartyUtil::Service();
+ if (NS_WARN_IF(!thirdPartyUtil)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uriBeingLoaded =
+ AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(channel);
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = thirdPartyUtil->GetTopWindowForChannel(channel, uriBeingLoaded,
+ getter_AddRefs(win));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ auto* pwin = nsPIDOMWindowOuter::From(win);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ if (!docShell) {
+ return NS_OK;
+ }
+ RefPtr<dom::Document> doc = docShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_OK);
+
+ // Log a warning to the web console.
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ AutoTArray<nsString, 1> params;
+ CopyUTF8toUTF16(uri->GetSpecOrDefault(), *params.AppendElement());
+ const char* message;
+ nsCString category;
+
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode)) {
+ message = UrlClassifierFeatureFactory::
+ ClassifierBlockingErrorCodeToConsoleMessage(aErrorCode, category);
+ } else {
+ message = "UnsafeUriBlocked";
+ category = "Safe Browsing"_ns;
+ }
+
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, category, doc,
+ nsContentUtils::eNECKO_PROPERTIES, message,
+ params);
+
+ return NS_OK;
+}
+
+/* static */
+nsresult UrlClassifierCommon::GetTopWindowURI(nsIChannel* aChannel,
+ nsIURI** aURI) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ RefPtr<dom::BrowsingContext> browsingContext;
+ nsresult rv =
+ loadInfo->GetTargetBrowsingContext(getter_AddRefs(browsingContext));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !browsingContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dom::CanonicalBrowsingContext* top = browsingContext->Canonical()->Top();
+ dom::WindowGlobalParent* wgp = top->GetCurrentWindowGlobal();
+ if (!wgp) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsIURI> uri = wgp->GetDocumentURI();
+ if (!uri) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+/* static */
+nsresult UrlClassifierCommon::CreatePairwiseEntityListURI(nsIChannel* aChannel,
+ nsIURI** aURI) {
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(aURI);
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!chan) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> topWinURI;
+ rv =
+ UrlClassifierCommon::GetTopWindowURI(aChannel, getter_AddRefs(topWinURI));
+ if (NS_FAILED(rv) || !topWinURI) {
+ // SharedWorker and ServiceWorker don't have an associated window, use
+ // client's URI instead.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ Maybe<dom::ClientInfo> clientInfo = loadInfo->GetClientInfo();
+ if (clientInfo.isSome()) {
+ if ((clientInfo->Type() == dom::ClientType::Sharedworker) ||
+ (clientInfo->Type() == dom::ClientType::Serviceworker)) {
+ UC_LOG(
+ ("UrlClassifierCommon::CreatePairwiseEntityListURI - "
+ "channel %p initiated by worker, get uri from client",
+ aChannel));
+
+ auto clientPrincipalOrErr = clientInfo->GetPrincipal();
+ if (clientPrincipalOrErr.isOk()) {
+ nsCOMPtr<nsIPrincipal> principal = clientPrincipalOrErr.unwrap();
+ if (principal) {
+ auto* basePrin = BasePrincipal::Cast(principal);
+ rv = basePrin->GetURI(getter_AddRefs(topWinURI));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+ }
+ }
+ }
+
+ if (!topWinURI) {
+ UC_LOG(
+ ("UrlClassifierCommon::CreatePairwiseEntityListURI - "
+ "no top-level window associated with channel %p, "
+ "get uri from loading principal",
+ aChannel));
+
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+ if (principal) {
+ auto* basePrin = BasePrincipal::Cast(principal);
+ rv = basePrin->GetURI(getter_AddRefs(topWinURI));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+ }
+ }
+
+ if (!topWinURI) {
+ UC_LOG(
+ ("UrlClassifierCommon::CreatePairwiseEntityListURI - "
+ "fail to get top-level window uri for channel %p",
+ aChannel));
+
+ // Return success because we want to continue to look up even without
+ // whitelist.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> chanPrincipal;
+ rv = securityManager->GetChannelURIPrincipal(aChannel,
+ getter_AddRefs(chanPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Craft a entitylist URL like "toplevel.page/?resource=third.party.domain"
+ nsAutoCString pageHostname, resourceDomain;
+ rv = topWinURI->GetHost(pageHostname);
+ if (NS_FAILED(rv)) {
+ // When the top-level page doesn't support GetHost, for example, about:home,
+ // we don't return an error here; instead, we return success to make sure
+ // that the lookup process calling this API continues to run.
+ if (UC_LOG_ENABLED()) {
+ nsCString topWinSpec =
+ topWinURI ? topWinURI->GetSpecOrDefault() : "(null)"_ns;
+ topWinSpec.Truncate(
+ std::min(topWinSpec.Length(), UrlClassifierCommon::sMaxSpecLength));
+ UC_LOG(
+ ("UrlClassifierCommon::CreatePairwiseEntityListURI - "
+ "cannot get host from the top-level uri %s of channel %p",
+ topWinSpec.get(), aChannel));
+ }
+ return NS_OK;
+ }
+
+ rv = chanPrincipal->GetBaseDomain(resourceDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString entitylistEntry =
+ "http://"_ns + pageHostname + "/?resource="_ns + resourceDomain;
+ UC_LOG(
+ ("UrlClassifierCommon::CreatePairwiseEntityListURI - looking for %s in "
+ "the entitylist on channel %p",
+ entitylistEntry.get(), aChannel));
+
+ nsCOMPtr<nsIURI> entitylistURI;
+ rv = NS_NewURI(getter_AddRefs(entitylistURI), entitylistEntry);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ entitylistURI.forget(aURI);
+ return NS_OK;
+}
+
+namespace {
+
+void LowerPriorityHelper(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ bool isBlockingResource = false;
+
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
+ if (cos) {
+ if (StaticPrefs::network_http_tailing_enabled()) {
+ uint32_t cosFlags = 0;
+ cos->GetClassFlags(&cosFlags);
+ isBlockingResource =
+ cosFlags & (nsIClassOfService::UrgentStart |
+ nsIClassOfService::Leader | nsIClassOfService::Unblocked);
+
+ // Requests not allowed to be tailed are usually those with higher
+ // prioritization. That overweights being a tracker: don't throttle
+ // them when not in background.
+ if (!(cosFlags & nsIClassOfService::TailForbidden)) {
+ cos->AddClassFlags(nsIClassOfService::Throttleable);
+ }
+ } else {
+ // Yes, we even don't want to evaluate the isBlockingResource when tailing
+ // is off see bug 1395525.
+
+ cos->AddClassFlags(nsIClassOfService::Throttleable);
+ }
+ }
+
+ if (!isBlockingResource) {
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aChannel);
+ if (p) {
+ UC_LOG(
+ ("UrlClassifierCommon::LowerPriorityHelper - "
+ "setting PRIORITY_LOWEST for channel %p",
+ aChannel));
+ p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+ }
+}
+
+} // namespace
+
+// static
+void UrlClassifierCommon::SetClassificationFlagsHelper(
+ nsIChannel* aChannel, uint32_t aClassificationFlags, bool aIsThirdParty) {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(aChannel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process
+ // request. We should notify the child process as well.
+ parentChannel->NotifyClassificationFlags(aClassificationFlags,
+ aIsThirdParty);
+ }
+
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(aChannel);
+ if (httpChannel) {
+ httpChannel->AddClassificationFlags(aClassificationFlags, aIsThirdParty);
+ }
+}
+
+// static
+void UrlClassifierCommon::AnnotateChannel(nsIChannel* aChannel,
+ uint32_t aClassificationFlags,
+ uint32_t aLoadingState) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIURI> chanURI;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(chanURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ bool isThirdPartyWithTopLevelWinURI =
+ AntiTrackingUtils::IsThirdPartyChannel(aChannel);
+
+ SetClassificationFlagsHelper(aChannel, aClassificationFlags,
+ isThirdPartyWithTopLevelWinURI);
+
+ // We consider valid tracking flags (based on the current strict vs basic list
+ // prefs) and cryptomining (which is not considered as tracking).
+ bool validClassificationFlags =
+ IsTrackingClassificationFlag(aClassificationFlags,
+ NS_UsePrivateBrowsing(aChannel)) ||
+ IsCryptominingClassificationFlag(aClassificationFlags,
+ NS_UsePrivateBrowsing(aChannel));
+
+ if (validClassificationFlags && isThirdPartyWithTopLevelWinURI) {
+ ContentBlockingNotifier::OnEvent(aChannel, aLoadingState);
+ }
+
+ if (isThirdPartyWithTopLevelWinURI &&
+ StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
+ LowerPriorityHelper(aChannel);
+ }
+}
+
+// static
+bool UrlClassifierCommon::IsAllowListed(nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannelInternal> channel = do_QueryInterface(aChannel);
+ if (NS_WARN_IF(!channel)) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ bool isAllowListed = false;
+ if (StaticPrefs::channelclassifier_allowlist_example()) {
+ UC_LOG(
+ ("UrlClassifierCommon::IsAllowListed - "
+ "check allowlisting test domain on channel %p",
+ aChannel));
+
+ nsCOMPtr<nsIIOService> ios = components::IO::Service();
+ if (NS_WARN_IF(!ios)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = ios->NewURI("http://allowlisted.example.com"_ns, nullptr,
+ nullptr, getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ nsCOMPtr<nsIPrincipal> cbAllowListPrincipal =
+ BasePrincipal::CreateContentPrincipal(uri,
+ loadInfo->GetOriginAttributes());
+
+ rv = ContentBlockingAllowList::Check(
+ cbAllowListPrincipal, NS_UsePrivateBrowsing(aChannel), isAllowListed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ } else {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ MOZ_ALWAYS_SUCCEEDS(
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)));
+ isAllowListed = cookieJarSettings->GetIsOnContentBlockingAllowList();
+ }
+
+ if (isAllowListed) {
+ UC_LOG(("UrlClassifierCommon::IsAllowListed - user override on channel %p",
+ aChannel));
+ }
+
+ return isAllowListed;
+}
+
+// static
+bool UrlClassifierCommon::IsTrackingClassificationFlag(uint32_t aFlag,
+ bool aIsPrivate) {
+ bool isLevel2ListEnabled =
+ aIsPrivate
+ ? StaticPrefs::privacy_annotate_channels_strict_list_pbmode_enabled()
+ : StaticPrefs::privacy_annotate_channels_strict_list_enabled();
+
+ if (isLevel2ListEnabled &&
+ (aFlag & nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_ANY_STRICT_TRACKING)) {
+ return true;
+ }
+
+ if (StaticPrefs::privacy_socialtracking_block_cookies_enabled() &&
+ IsSocialTrackingClassificationFlag(aFlag)) {
+ return true;
+ }
+
+ return (
+ aFlag &
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_ANY_BASIC_TRACKING);
+}
+
+// static
+bool UrlClassifierCommon::IsSocialTrackingClassificationFlag(uint32_t aFlag) {
+ return (aFlag & nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_ANY_SOCIAL_TRACKING) != 0;
+}
+
+// static
+bool UrlClassifierCommon::IsCryptominingClassificationFlag(uint32_t aFlag,
+ bool aIsPrivate) {
+ if (aFlag &
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_CRYPTOMINING) {
+ return true;
+ }
+
+ bool isLevel2ListEnabled =
+ aIsPrivate
+ ? StaticPrefs::privacy_annotate_channels_strict_list_pbmode_enabled()
+ : StaticPrefs::privacy_annotate_channels_strict_list_enabled();
+
+ if (isLevel2ListEnabled &&
+ (aFlag & nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_CRYPTOMINING_CONTENT)) {
+ return true;
+ }
+
+ return false;
+}
+
+void UrlClassifierCommon::TablesToString(const nsTArray<nsCString>& aList,
+ nsACString& aString) {
+ // Truncate and append rather than assigning because that's more efficient if
+ // aString is an nsAutoCString.
+ aString.Truncate();
+ StringJoinAppend(aString, ","_ns, aList);
+}
+
+uint32_t UrlClassifierCommon::TablesToClassificationFlags(
+ const nsTArray<nsCString>& aList,
+ const std::vector<ClassificationData>& aData, uint32_t aDefaultFlag) {
+ uint32_t flags = 0;
+ for (const nsCString& table : aList) {
+ flags |= TableToClassificationFlag(table, aData);
+ }
+
+ if (flags == 0) {
+ flags |= aDefaultFlag;
+ }
+
+ return flags;
+}
+
+uint32_t UrlClassifierCommon::TableToClassificationFlag(
+ const nsACString& aTable, const std::vector<ClassificationData>& aData) {
+ for (const ClassificationData& data : aData) {
+ if (StringBeginsWith(aTable, data.mPrefix)) {
+ return data.mFlag;
+ }
+ }
+
+ return 0;
+}
+
+/* static */
+bool UrlClassifierCommon::IsPassiveContent(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ ExtContentPolicyType contentType = loadInfo->GetExternalContentPolicyType();
+
+ // Return true if aChannel is loading passive display content, as
+ // defined by the mixed content blocker.
+ // https://searchfox.org/mozilla-central/rev/c80fa7258c935223fe319c5345b58eae85d4c6ae/dom/security/nsMixedContentBlocker.cpp#532
+ return contentType == ExtContentPolicy::TYPE_IMAGE ||
+ contentType == ExtContentPolicy::TYPE_MEDIA ||
+ (contentType == ExtContentPolicy::TYPE_OBJECT_SUBREQUEST &&
+ !StaticPrefs::security_mixed_content_block_object_subrequest());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierCommon.h b/netwerk/url-classifier/UrlClassifierCommon.h
new file mode 100644
index 0000000000..c1d92ef981
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierCommon.h
@@ -0,0 +1,103 @@
+/* -*- 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_UrlClassifierCommon_h
+#define mozilla_net_UrlClassifierCommon_h
+
+#include "mozilla/Logging.h"
+#include "nsString.h"
+
+#include <vector>
+
+class nsIChannel;
+class nsIURI;
+
+#define UC_LOG(args) MOZ_LOG(UrlClassifierCommon::sLog, LogLevel::Info, args)
+#define UC_LOG_DEBUG(args) \
+ MOZ_LOG(UrlClassifierCommon::sLog, LogLevel::Debug, args)
+#define UC_LOG_WARN(args) \
+ MOZ_LOG(UrlClassifierCommon::sLog, LogLevel::Warning, args)
+#define UC_LOG_LEAK(args) \
+ MOZ_LOG(UrlClassifierCommon::sLogLeak, LogLevel::Info, args)
+
+#define UC_LOG_ENABLED() \
+ MOZ_LOG_TEST(UrlClassifierCommon::sLog, LogLevel::Info) || \
+ MOZ_LOG_TEST(UrlClassifierCommon::sLogLeak, LogLevel::Info)
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierCommon final {
+ public:
+ static const nsCString::size_type sMaxSpecLength;
+
+ static LazyLogModule sLog;
+ static LazyLogModule sLogLeak;
+
+ static bool AddonMayLoad(nsIChannel* aChannel, nsIURI* aURI);
+
+ static bool ShouldEnableProtectionForChannel(nsIChannel* aChannel);
+
+ static nsresult SetBlockedContent(nsIChannel* channel, nsresult aErrorCode,
+ const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash);
+
+ static nsresult SetTrackingInfo(nsIChannel* channel,
+ const nsTArray<nsCString>& aLists,
+ const nsTArray<nsCString>& aFullHashes);
+
+ // Use this function only when you are looking for a pairwise entitylist uri
+ // with the format: http://toplevel.page/?resource=channel.uri.domain
+ static nsresult CreatePairwiseEntityListURI(nsIChannel* aChannel,
+ nsIURI** aURI);
+
+ static void AnnotateChannel(nsIChannel* aChannel,
+ uint32_t aClassificationFlags,
+ uint32_t aLoadingState);
+
+ static bool IsAllowListed(nsIChannel* aChannel);
+
+ static bool IsTrackingClassificationFlag(uint32_t aFlag, bool aIsPrivate);
+
+ static bool IsSocialTrackingClassificationFlag(uint32_t aFlag);
+
+ static bool IsCryptominingClassificationFlag(uint32_t aFlag, bool aIsPrivate);
+
+ // Join the table names in 1 single string.
+ static void TablesToString(const nsTArray<nsCString>& aList,
+ nsACString& aString);
+
+ struct ClassificationData {
+ nsCString mPrefix;
+ uint32_t mFlag;
+ };
+
+ // Checks if the entries in aList are part of the ClassificationData vector
+ // and it returns the corresponding flags. If none of them is found, the
+ // default flag is returned.
+ static uint32_t TablesToClassificationFlags(
+ const nsTArray<nsCString>& aList,
+ const std::vector<ClassificationData>& aData, uint32_t aDefaultFlag);
+
+ static bool IsPassiveContent(nsIChannel* aChannel);
+
+ static void SetClassificationFlagsHelper(nsIChannel* aChannel,
+ uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+
+ private:
+ static uint32_t TableToClassificationFlag(
+ const nsACString& aTable, const std::vector<ClassificationData>& aData);
+
+ friend class AsyncUrlChannelClassifier;
+ static nsresult GetTopWindowURI(nsIChannel* aChannel, nsIURI** aURI);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierCommon_h
diff --git a/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs b/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs
new file mode 100644
index 0000000000..8c2bc8a3aa
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export function UrlClassifierExceptionListService() {}
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
+});
+
+const COLLECTION_NAME = "url-classifier-skip-urls";
+
+class Feature {
+ constructor(name, prefName) {
+ this.name = name;
+ this.prefName = prefName;
+ this.observers = new Set();
+ this.prefValue = null;
+ this.remoteEntries = null;
+
+ if (prefName) {
+ this.prefValue = Services.prefs.getStringPref(this.prefName, null);
+ Services.prefs.addObserver(prefName, this);
+ }
+ }
+
+ async addAndRunObserver(observer) {
+ this.observers.add(observer);
+ this.notifyObservers(observer);
+ }
+
+ removeObserver(observer) {
+ this.observers.delete(observer);
+ }
+
+ observe(subject, topic, data) {
+ if (topic != "nsPref:changed" || data != this.prefName) {
+ console.error(`Unexpected event ${topic} with ${data}`);
+ return;
+ }
+
+ this.prefValue = Services.prefs.getStringPref(this.prefName, null);
+ this.notifyObservers();
+ }
+
+ onRemoteSettingsUpdate(entries) {
+ this.remoteEntries = [];
+
+ for (let entry of entries) {
+ if (entry.feature == this.name) {
+ this.remoteEntries.push(entry.pattern.toLowerCase());
+ }
+ }
+ }
+
+ notifyObservers(observer = null) {
+ let entries = [];
+ if (this.prefValue) {
+ entries = this.prefValue.split(",");
+ }
+
+ if (this.remoteEntries) {
+ for (let entry of this.remoteEntries) {
+ entries.push(entry);
+ }
+ }
+
+ let entriesAsString = entries.join(",").toLowerCase();
+ if (observer) {
+ observer.onExceptionListUpdate(entriesAsString);
+ } else {
+ for (let obs of this.observers) {
+ obs.onExceptionListUpdate(entriesAsString);
+ }
+ }
+ }
+}
+
+UrlClassifierExceptionListService.prototype = {
+ classID: Components.ID("{b9f4fd03-9d87-4bfd-9958-85a821750ddc}"),
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIUrlClassifierExceptionListService",
+ ]),
+
+ features: {},
+ _initialized: false,
+
+ async lazyInit() {
+ if (this._initialized) {
+ return;
+ }
+
+ let rs = lazy.RemoteSettings(COLLECTION_NAME);
+ rs.on("sync", event => {
+ let {
+ data: { current },
+ } = event;
+ this.entries = current || [];
+ this.onUpdateEntries(current);
+ });
+
+ this._initialized = true;
+
+ // If the remote settings list hasn't been populated yet we have to make sure
+ // to do it before firing the first notification.
+ // This has to be run after _initialized is set because we'll be
+ // blocked while getting entries from RemoteSetting, and we don't want
+ // LazyInit is executed again.
+ try {
+ // The data will be initially available from the local DB (via a
+ // resource:// URI).
+ this.entries = await rs.get();
+ } catch (e) {}
+
+ // RemoteSettings.get() could return null, ensure passing a list to
+ // onUpdateEntries.
+ if (!this.entries) {
+ this.entries = [];
+ }
+
+ this.onUpdateEntries(this.entries);
+ },
+
+ onUpdateEntries(entries) {
+ for (let key of Object.keys(this.features)) {
+ let feature = this.features[key];
+ feature.onRemoteSettingsUpdate(entries);
+ feature.notifyObservers();
+ }
+ },
+
+ registerAndRunExceptionListObserver(feature, prefName, observer) {
+ // We don't await this; the caller is C++ and won't await this function,
+ // and because we prevent re-entering into this method, once it's been
+ // called once any subsequent calls will early-return anyway - so
+ // awaiting that would be meaningless. Instead, `Feature` implementations
+ // make sure not to call into observers until they have data, and we
+ // make sure to let feature instances know whether we have data
+ // immediately.
+ this.lazyInit();
+
+ if (!this.features[feature]) {
+ let featureObj = new Feature(feature, prefName);
+ this.features[feature] = featureObj;
+ // If we've previously initialized, we need to pass the entries
+ // we already have to the new feature.
+ if (this.entries) {
+ featureObj.onRemoteSettingsUpdate(this.entries);
+ }
+ }
+ this.features[feature].addAndRunObserver(observer);
+ },
+
+ unregisterExceptionListObserver(feature, observer) {
+ if (!this.features[feature]) {
+ return;
+ }
+ this.features[feature].removeObserver(observer);
+ },
+
+ clear() {
+ this.features = {};
+ this._initialized = false;
+ this.entries = null;
+ },
+};
diff --git a/netwerk/url-classifier/UrlClassifierFeatureBase.cpp b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp
new file mode 100644
index 0000000000..6f5924ab50
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 "UrlClassifierFeatureBase.h"
+#include "Classifier.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+
+using namespace safebrowsing;
+
+namespace net {
+
+namespace {
+
+void OnPrefsChange(const char* aPrefName, void* aArray) {
+ auto* array = static_cast<nsTArray<nsCString>*>(aArray);
+ MOZ_ASSERT(array);
+
+ nsAutoCString value;
+ Preferences::GetCString(aPrefName, value);
+ Classifier::SplitTables(value, *array);
+}
+
+} // namespace
+
+NS_INTERFACE_MAP_BEGIN(UrlClassifierFeatureBase)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIUrlClassifierFeature)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierFeature)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierExceptionListObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(UrlClassifierFeatureBase)
+NS_IMPL_RELEASE(UrlClassifierFeatureBase)
+
+UrlClassifierFeatureBase::UrlClassifierFeatureBase(
+ const nsACString& aName, const nsACString& aPrefBlocklistTables,
+ const nsACString& aPrefEntitylistTables,
+ const nsACString& aPrefBlocklistHosts,
+ const nsACString& aPrefEntitylistHosts,
+ const nsACString& aPrefBlocklistTableName,
+ const nsACString& aPrefEntitylistTableName,
+ const nsACString& aPrefExceptionHosts)
+ : mName(aName), mPrefExceptionHosts(aPrefExceptionHosts) {
+ static_assert(nsIUrlClassifierFeature::blocklist == 0,
+ "nsIUrlClassifierFeature::blocklist must be 0");
+ static_assert(nsIUrlClassifierFeature::entitylist == 1,
+ "nsIUrlClassifierFeature::entitylist must be 1");
+
+ mPrefTables[nsIUrlClassifierFeature::blocklist] = aPrefBlocklistTables;
+ mPrefTables[nsIUrlClassifierFeature::entitylist] = aPrefEntitylistTables;
+
+ mPrefHosts[nsIUrlClassifierFeature::blocklist] = aPrefBlocklistHosts;
+ mPrefHosts[nsIUrlClassifierFeature::entitylist] = aPrefEntitylistHosts;
+
+ mPrefTableNames[nsIUrlClassifierFeature::blocklist] = aPrefBlocklistTableName;
+ mPrefTableNames[nsIUrlClassifierFeature::entitylist] =
+ aPrefEntitylistTableName;
+}
+
+UrlClassifierFeatureBase::~UrlClassifierFeatureBase() = default;
+
+void UrlClassifierFeatureBase::InitializePreferences() {
+ for (uint32_t i = 0; i < 2; ++i) {
+ if (!mPrefTables[i].IsEmpty()) {
+ Preferences::RegisterCallbackAndCall(OnPrefsChange, mPrefTables[i],
+ &mTables[i]);
+ }
+
+ if (!mPrefHosts[i].IsEmpty()) {
+ Preferences::RegisterCallbackAndCall(OnPrefsChange, mPrefHosts[i],
+ &mHosts[i]);
+ }
+ }
+
+ nsCOMPtr<nsIUrlClassifierExceptionListService> exceptionListService =
+ do_GetService("@mozilla.org/url-classifier/exception-list-service;1");
+ if (NS_WARN_IF(!exceptionListService)) {
+ return;
+ }
+
+ exceptionListService->RegisterAndRunExceptionListObserver(
+ mName, mPrefExceptionHosts, this);
+}
+
+void UrlClassifierFeatureBase::ShutdownPreferences() {
+ for (uint32_t i = 0; i < 2; ++i) {
+ if (!mPrefTables[i].IsEmpty()) {
+ Preferences::UnregisterCallback(OnPrefsChange, mPrefTables[i],
+ &mTables[i]);
+ }
+
+ if (!mPrefHosts[i].IsEmpty()) {
+ Preferences::UnregisterCallback(OnPrefsChange, mPrefHosts[i], &mHosts[i]);
+ }
+ }
+
+ nsCOMPtr<nsIUrlClassifierExceptionListService> exceptionListService =
+ do_GetService("@mozilla.org/url-classifier/exception-list-service;1");
+ if (exceptionListService) {
+ exceptionListService->UnregisterExceptionListObserver(mName, this);
+ }
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureBase::OnExceptionListUpdate(const nsACString& aList) {
+ mExceptionHosts = aList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureBase::GetName(nsACString& aName) {
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureBase::GetTables(nsIUrlClassifierFeature::listType aListType,
+ nsTArray<nsCString>& aTables) {
+ if (aListType != nsIUrlClassifierFeature::blocklist &&
+ aListType != nsIUrlClassifierFeature::entitylist) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aTables = mTables[aListType].Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureBase::HasTable(const nsACString& aTable,
+ nsIUrlClassifierFeature::listType aListType,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (aListType != nsIUrlClassifierFeature::blocklist &&
+ aListType != nsIUrlClassifierFeature::entitylist) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = mTables[aListType].Contains(aTable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureBase::HasHostInPreferences(
+ const nsACString& aHost, nsIUrlClassifierFeature::listType aListType,
+ nsACString& aPrefTableName, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (aListType != nsIUrlClassifierFeature::blocklist &&
+ aListType != nsIUrlClassifierFeature::entitylist) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = mHosts[aListType].Contains(aHost);
+ if (*aResult) {
+ aPrefTableName = mPrefTableNames[aListType];
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureBase::GetExceptionHostList(nsACString& aList) {
+ aList = mExceptionHosts;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureAntiTrackingBase::GetExceptionHostList(nsACString& aList) {
+ if (!StaticPrefs::privacy_antitracking_enableWebcompat()) {
+ aList.Truncate();
+ return NS_OK;
+ }
+
+ return UrlClassifierFeatureBase::GetExceptionHostList(aList);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureBase.h b/netwerk/url-classifier/UrlClassifierFeatureBase.h
new file mode 100644
index 0000000000..79e1f3d087
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureBase.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureBase_h
+#define mozilla_net_UrlClassifierFeatureBase_h
+
+#include "nsIUrlClassifierFeature.h"
+#include "nsIUrlClassifierExceptionListService.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureBase : public nsIUrlClassifierFeature,
+ public nsIUrlClassifierExceptionListObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD
+ GetName(nsACString& aName) override;
+
+ NS_IMETHOD
+ GetTables(nsIUrlClassifierFeature::listType aListType,
+ nsTArray<nsCString>& aResult) override;
+
+ NS_IMETHOD
+ HasTable(const nsACString& aTable,
+ nsIUrlClassifierFeature::listType aListType, bool* aResult) override;
+
+ NS_IMETHOD
+ HasHostInPreferences(const nsACString& aHost,
+ nsIUrlClassifierFeature::listType aListType,
+ nsACString& aPrefTableName, bool* aResult) override;
+
+ NS_IMETHOD
+ GetExceptionHostList(nsACString& aList) override;
+
+ NS_IMETHOD
+ OnExceptionListUpdate(const nsACString& aList) override;
+
+ protected:
+ UrlClassifierFeatureBase(const nsACString& aName,
+ const nsACString& aPrefBlocklistTables,
+ const nsACString& aPrefEntitylistTables,
+ const nsACString& aPrefBlocklistHosts,
+ const nsACString& aPrefEntitylistHosts,
+ const nsACString& aPrefBlocklistTableName,
+ const nsACString& aPrefEntitylistTableName,
+ const nsACString& aPrefExceptionHosts);
+
+ virtual ~UrlClassifierFeatureBase();
+
+ void InitializePreferences();
+ void ShutdownPreferences();
+
+ nsCString mName;
+
+ private:
+ nsCString mPrefExceptionHosts;
+
+ // 2: blocklist and entitylist.
+ nsCString mPrefTables[2];
+ nsTArray<nsCString> mTables[2];
+
+ nsCString mPrefHosts[2];
+ nsCString mPrefTableNames[2];
+ nsTArray<nsCString> mHosts[2];
+
+ nsCString mExceptionHosts;
+};
+
+class UrlClassifierFeatureAntiTrackingBase : public UrlClassifierFeatureBase {
+ using UrlClassifierFeatureBase::UrlClassifierFeatureBase;
+
+ NS_IMETHOD
+ GetExceptionHostList(nsACString& aList) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureBase_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp
new file mode 100644
index 0000000000..dbd46eb298
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp
@@ -0,0 +1,170 @@
+/* -*- 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 "UrlClassifierFeatureCryptominingAnnotation.h"
+
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "nsIClassifiedChannel.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define CRYPTOMINING_ANNOTATION_FEATURE_NAME "cryptomining-annotation"
+
+#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST \
+ "urlclassifier.features.cryptomining.annotate.blacklistTables"
+#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.cryptomining.annotate.blacklistHosts"
+#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST \
+ "urlclassifier.features.cryptomining.annotate.whitelistTables"
+#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.cryptomining.annotate.whitelistHosts"
+#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_EXCEPTION_URLS \
+ "urlclassifier.features.cryptomining.annotate.skipURLs"
+#define TABLE_CRYPTOMINING_ANNOTATION_BLOCKLIST_PREF \
+ "cryptomining-annotate-blacklist-pref"
+#define TABLE_CRYPTOMINING_ANNOTATION_ENTITYLIST_PREF \
+ "cryptomining-annotate-whitelist-pref"
+
+StaticRefPtr<UrlClassifierFeatureCryptominingAnnotation>
+ gFeatureCryptominingAnnotation;
+
+} // namespace
+
+UrlClassifierFeatureCryptominingAnnotation::
+ UrlClassifierFeatureCryptominingAnnotation()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(CRYPTOMINING_ANNOTATION_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST),
+ nsLiteralCString(
+ URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(
+ URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_CRYPTOMINING_ANNOTATION_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_CRYPTOMINING_ANNOTATION_ENTITYLIST_PREF),
+ nsLiteralCString(
+ URLCLASSIFIER_CRYPTOMINING_ANNOTATION_EXCEPTION_URLS)) {}
+/* static */ const char* UrlClassifierFeatureCryptominingAnnotation::Name() {
+ return CRYPTOMINING_ANNOTATION_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureCryptominingAnnotation::MaybeInitialize() {
+ UC_LOG_LEAK(("UrlClassifierFeatureCryptominingAnnotation::MaybeInitialize"));
+
+ if (!gFeatureCryptominingAnnotation) {
+ gFeatureCryptominingAnnotation =
+ new UrlClassifierFeatureCryptominingAnnotation();
+ gFeatureCryptominingAnnotation->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureCryptominingAnnotation::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureCryptominingAnnotation::MaybeShutdown"));
+
+ if (gFeatureCryptominingAnnotation) {
+ gFeatureCryptominingAnnotation->ShutdownPreferences();
+ gFeatureCryptominingAnnotation = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureCryptominingAnnotation>
+UrlClassifierFeatureCryptominingAnnotation::MaybeCreate(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureCryptominingAnnotation::MaybeCreate - channel %p",
+ aChannel));
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureCryptominingAnnotation);
+
+ RefPtr<UrlClassifierFeatureCryptominingAnnotation> self =
+ gFeatureCryptominingAnnotation;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureCryptominingAnnotation::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(CRYPTOMINING_ANNOTATION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureCryptominingAnnotation);
+
+ RefPtr<UrlClassifierFeatureCryptominingAnnotation> self =
+ gFeatureCryptominingAnnotation;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCryptominingAnnotation::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ // This is not a blocking feature.
+ *aShouldContinue = true;
+
+ UC_LOG(
+ ("UrlClassifierFeatureCryptominingAnnotation::ProcessChannel - "
+ "annotating channel %p",
+ aChannel));
+
+ static std::vector<UrlClassifierCommon::ClassificationData>
+ sClassificationData = {
+ {"content-cryptomining-track-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_CRYPTOMINING_CONTENT},
+ };
+
+ uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags(
+ aList, sClassificationData,
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_CRYPTOMINING);
+
+ UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes);
+
+ UrlClassifierCommon::AnnotateChannel(
+ aChannel, flags,
+ nsIWebProgressListener::STATE_LOADED_CRYPTOMINING_CONTENT);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCryptominingAnnotation::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h
new file mode 100644
index 0000000000..c843af871d
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureCryptominingAnnotation_h
+#define mozilla_net_UrlClassifierFeatureCryptominingAnnotation_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureCryptominingAnnotation final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureCryptominingAnnotation>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureCryptominingAnnotation();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureCryptominingAnnotation_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp
new file mode 100644
index 0000000000..72406179bc
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp
@@ -0,0 +1,211 @@
+/* -*- 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 "UrlClassifierFeatureCryptominingProtection.h"
+
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "ChannelClassifierService.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "nsNetUtil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define CRYPTOMINING_FEATURE_NAME "cryptomining-protection"
+
+#define URLCLASSIFIER_CRYPTOMINING_BLOCKLIST \
+ "urlclassifier.features.cryptomining.blacklistTables"
+#define URLCLASSIFIER_CRYPTOMINING_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.cryptomining.blacklistHosts"
+#define URLCLASSIFIER_CRYPTOMINING_ENTITYLIST \
+ "urlclassifier.features.cryptomining.whitelistTables"
+#define URLCLASSIFIER_CRYPTOMINING_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.cryptomining.whitelistHosts"
+#define URLCLASSIFIER_CRYPTOMINING_EXCEPTION_URLS \
+ "urlclassifier.features.cryptomining.skipURLs"
+#define TABLE_CRYPTOMINING_BLOCKLIST_PREF "cryptomining-blacklist-pref"
+#define TABLE_CRYPTOMINING_ENTITYLIST_PREF "cryptomining-whitelist-pref"
+
+StaticRefPtr<UrlClassifierFeatureCryptominingProtection>
+ gFeatureCryptominingProtection;
+
+} // namespace
+
+UrlClassifierFeatureCryptominingProtection::
+ UrlClassifierFeatureCryptominingProtection()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(CRYPTOMINING_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ENTITYLIST),
+ nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_CRYPTOMINING_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_CRYPTOMINING_ENTITYLIST_PREF),
+ nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_EXCEPTION_URLS)) {}
+
+/* static */ const char* UrlClassifierFeatureCryptominingProtection::Name() {
+ return CRYPTOMINING_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureCryptominingProtection::MaybeInitialize() {
+ UC_LOG_LEAK(("UrlClassifierFeatureCryptominingProtection::MaybeInitialize"));
+
+ if (!gFeatureCryptominingProtection) {
+ gFeatureCryptominingProtection =
+ new UrlClassifierFeatureCryptominingProtection();
+ gFeatureCryptominingProtection->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureCryptominingProtection::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureCryptominingProtection::MaybeShutdown"));
+
+ if (gFeatureCryptominingProtection) {
+ gFeatureCryptominingProtection->ShutdownPreferences();
+ gFeatureCryptominingProtection = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureCryptominingProtection>
+UrlClassifierFeatureCryptominingProtection::MaybeCreate(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureCryptominingProtection::MaybeCreate - channel %p",
+ aChannel));
+
+ if (!StaticPrefs::privacy_trackingprotection_cryptomining_enabled()) {
+ return nullptr;
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel);
+ if (!isThirdParty) {
+ UC_LOG(
+ ("UrlClassifierFeatureCryptominingProtection::MaybeCreate - "
+ "skipping first party or top-level load for channel %p",
+ aChannel));
+ return nullptr;
+ }
+
+ if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureCryptominingProtection);
+
+ RefPtr<UrlClassifierFeatureCryptominingProtection> self =
+ gFeatureCryptominingProtection;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureCryptominingProtection::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(CRYPTOMINING_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureCryptominingProtection);
+
+ RefPtr<UrlClassifierFeatureCryptominingProtection> self =
+ gFeatureCryptominingProtection;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCryptominingProtection::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel);
+
+ // This is a blocking feature.
+ *aShouldContinue = isAllowListed;
+
+ if (isAllowListed) {
+ return NS_OK;
+ }
+
+ nsAutoCString list;
+ UrlClassifierCommon::TablesToString(aList, list);
+
+ ChannelBlockDecision decision =
+ ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list);
+ if (decision != ChannelBlockDecision::Blocked) {
+ uint32_t event =
+ decision == ChannelBlockDecision::Replaced
+ ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT
+ : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT;
+
+ // Need to set aBlocked to True if we replace the Cryptominer with a shim,
+ // since the shim is treated as a blocked event
+ // Note: If we need to account for which kind of tracker was replaced,
+ // we need to create a new event type in nsIWebProgressListener
+ if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) {
+ ContentBlockingNotifier::OnEvent(aChannel, event, true);
+ } else {
+ ContentBlockingNotifier::OnEvent(aChannel, event, false);
+ }
+
+ *aShouldContinue = true;
+ return NS_OK;
+ }
+
+ UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_CRYPTOMINING_URI,
+ list, ""_ns, ""_ns);
+
+ UC_LOG(
+ ("UrlClassifierFeatureCryptominingProtection::ProcessChannel - "
+ "cancelling channel %p",
+ aChannel));
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel);
+
+ if (httpChannel) {
+ Unused << httpChannel->CancelByURLClassifier(NS_ERROR_CRYPTOMINING_URI);
+ } else {
+ Unused << aChannel->Cancel(NS_ERROR_CRYPTOMINING_URI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCryptominingProtection::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h
new file mode 100644
index 0000000000..5ff888ab17
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureCryptominingProtection_h
+#define mozilla_net_UrlClassifierFeatureCryptominingProtection_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureCryptominingProtection final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureCryptominingProtection>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureCryptominingProtection();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureCryptominingProtection_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp
new file mode 100644
index 0000000000..ec5ad185ae
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "UrlClassifierFeatureCustomTables.h"
+
+namespace mozilla {
+
+NS_INTERFACE_MAP_BEGIN(UrlClassifierFeatureCustomTables)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIUrlClassifierFeature)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierFeature)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(UrlClassifierFeatureCustomTables)
+NS_IMPL_RELEASE(UrlClassifierFeatureCustomTables)
+
+UrlClassifierFeatureCustomTables::UrlClassifierFeatureCustomTables(
+ const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables,
+ const nsTArray<nsCString>& aEntitylistTables)
+ : mName(aName),
+ mBlocklistTables(aBlocklistTables.Clone()),
+ mEntitylistTables(aEntitylistTables.Clone()) {}
+
+UrlClassifierFeatureCustomTables::~UrlClassifierFeatureCustomTables() = default;
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetName(nsACString& aName) {
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetTables(
+ nsIUrlClassifierFeature::listType aListType, nsTArray<nsCString>& aTables) {
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ aTables = mBlocklistTables.Clone();
+ return NS_OK;
+ }
+
+ if (aListType == nsIUrlClassifierFeature::entitylist) {
+ aTables = mEntitylistTables.Clone();
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::HasTable(
+ const nsACString& aTable, nsIUrlClassifierFeature::listType aListType,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aResult = mBlocklistTables.Contains(aTable);
+ return NS_OK;
+ }
+
+ if (aListType == nsIUrlClassifierFeature::entitylist) {
+ *aResult = mEntitylistTables.Contains(aTable);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::HasHostInPreferences(
+ const nsACString& aHost, nsIUrlClassifierFeature::listType aListType,
+ nsACString& aPrefTableName, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetExceptionHostList(nsACString& aList) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ // This is not a blocking feature.
+ *aShouldContinue = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureCustomTables.h b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.h
new file mode 100644
index 0000000000..0fc12243fe
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_UrlClassifierFeatureCustomTables_h
+#define mozilla_UrlClassifierFeatureCustomTables_h
+
+#include "nsIUrlClassifierFeature.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+class UrlClassifierFeatureCustomTables : public nsIUrlClassifierFeature {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLCLASSIFIERFEATURE
+
+ explicit UrlClassifierFeatureCustomTables(
+ const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables,
+ const nsTArray<nsCString>& aEntitylistTables);
+
+ private:
+ virtual ~UrlClassifierFeatureCustomTables();
+
+ nsCString mName;
+ nsTArray<nsCString> mBlocklistTables;
+ nsTArray<nsCString> mEntitylistTables;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_UrlClassifierFeatureCustomTables_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp
new file mode 100644
index 0000000000..d31e34047a
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp
@@ -0,0 +1,268 @@
+/* -*- 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 "UrlClassifierFeatureEmailTrackingDataCollection.h"
+
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsILoadInfo.h"
+
+namespace mozilla::net {
+
+namespace {
+
+#define EMAILTRACKING_DATACOLLECTION_FEATURE_NAME \
+ "emailtracking-data-collection"
+
+#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST \
+ "urlclassifier.features.emailtracking.datacollection.blocklistTables"
+#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.emailtracking.datacollection.blocklistHosts"
+#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST \
+ "urlclassifier.features.emailtracking.datacollection.allowlistTables"
+#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.emailtracking.datacollection.allowlistHosts"
+#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_EXCEPTION_URLS \
+ "urlclassifier.features.emailtracking.datacollection.skipURLs"
+#define TABLE_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_PREF \
+ "emailtracking-data-collection-blocklist-pref"
+#define TABLE_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_PREF \
+ "emailtracking-data-collection-allowlist-pref"
+
+StaticRefPtr<UrlClassifierFeatureEmailTrackingDataCollection>
+ gFeatureEmailTrackingDataCollection;
+StaticAutoPtr<nsCString> gEmailWebAppDomainsPref;
+static constexpr char kEmailWebAppDomainPrefName[] =
+ "privacy.trackingprotection.emailtracking.webapp.domains";
+
+void EmailWebAppDomainPrefChangeCallback(const char* aPrefName, void*) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!strcmp(aPrefName, kEmailWebAppDomainPrefName));
+ MOZ_ASSERT(gEmailWebAppDomainsPref);
+
+ Preferences::GetCString(kEmailWebAppDomainPrefName, *gEmailWebAppDomainsPref);
+}
+
+} // namespace
+
+UrlClassifierFeatureEmailTrackingDataCollection::
+ UrlClassifierFeatureEmailTrackingDataCollection()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(EMAILTRACKING_DATACOLLECTION_FEATURE_NAME),
+ nsLiteralCString(
+ URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST),
+ nsLiteralCString(
+ URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST),
+ nsLiteralCString(
+ URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(
+ URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_PREF),
+ nsLiteralCString(
+ URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_EXCEPTION_URLS)) {}
+
+/* static */
+const char* UrlClassifierFeatureEmailTrackingDataCollection::Name() {
+ return EMAILTRACKING_DATACOLLECTION_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureEmailTrackingDataCollection::MaybeInitialize() {
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureEmailTrackingDataCollection::MaybeInitialize"));
+
+ if (!gFeatureEmailTrackingDataCollection) {
+ gFeatureEmailTrackingDataCollection =
+ new UrlClassifierFeatureEmailTrackingDataCollection();
+ gFeatureEmailTrackingDataCollection->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureEmailTrackingDataCollection::MaybeShutdown() {
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureEmailTrackingDataCollection::MaybeShutdown"));
+
+ if (gFeatureEmailTrackingDataCollection) {
+ gFeatureEmailTrackingDataCollection->ShutdownPreferences();
+ gFeatureEmailTrackingDataCollection = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureEmailTrackingDataCollection>
+UrlClassifierFeatureEmailTrackingDataCollection::MaybeCreate(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureEmailTrackingDataCollection::MaybeCreate - channel "
+ "%p",
+ aChannel));
+
+ if (!StaticPrefs::
+ privacy_trackingprotection_emailtracking_data_collection_enabled()) {
+ return nullptr;
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel);
+
+ // We don't need to collect data if the email tracker is loaded in first-party
+ // context.
+ if (!isThirdParty) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureEmailTrackingDataCollection);
+
+ RefPtr<UrlClassifierFeatureEmailTrackingDataCollection> self =
+ gFeatureEmailTrackingDataCollection;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureEmailTrackingDataCollection::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(EMAILTRACKING_DATACOLLECTION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureEmailTrackingDataCollection);
+
+ RefPtr<UrlClassifierFeatureEmailTrackingDataCollection> self =
+ gFeatureEmailTrackingDataCollection;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureEmailTrackingDataCollection::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ // This is not a blocking feature.
+ *aShouldContinue = true;
+
+ UC_LOG(
+ ("UrlClassifierFeatureEmailTrackingDataCollection::ProcessChannel - "
+ "collecting data from channel %p",
+ aChannel));
+
+ static std::vector<UrlClassifierCommon::ClassificationData>
+ sClassificationData = {
+ {"base-email-track-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_EMAILTRACKING},
+ {"content-email-track-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_EMAILTRACKING_CONTENT},
+ };
+
+ // Get if the top window is a email webapp.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ RefPtr<dom::BrowsingContext> bc;
+ loadInfo->GetBrowsingContext(getter_AddRefs(bc));
+ if (!bc || bc->IsDiscarded()) {
+ return NS_OK;
+ }
+
+ RefPtr<dom::WindowGlobalParent> topWindowParent =
+ bc->Canonical()->GetTopWindowContext();
+ if (!topWindowParent || topWindowParent->IsDiscarded()) {
+ return NS_OK;
+ }
+
+ // Cache the email webapp domains pref value and register the callback
+ // function to update the cached value when the pref changes.
+ if (!gEmailWebAppDomainsPref) {
+ gEmailWebAppDomainsPref = new nsCString();
+
+ Preferences::RegisterCallbackAndCall(EmailWebAppDomainPrefChangeCallback,
+ kEmailWebAppDomainPrefName);
+ RunOnShutdown([]() {
+ Preferences::UnregisterCallback(EmailWebAppDomainPrefChangeCallback,
+ kEmailWebAppDomainPrefName);
+ gEmailWebAppDomainsPref = nullptr;
+ });
+ }
+
+ bool isTopEmailWebApp = topWindowParent->DocumentPrincipal()->IsURIInList(
+ *gEmailWebAppDomainsPref);
+
+ uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags(
+ aList, sClassificationData,
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_EMAILTRACKING);
+
+ if (flags & nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_EMAILTRACKING_CONTENT) {
+ Telemetry::AccumulateCategorical(
+ isTopEmailWebApp
+ ? Telemetry::LABELS_EMAIL_TRACKER_COUNT::content_email_webapp
+ : Telemetry::LABELS_EMAIL_TRACKER_COUNT::content_normal);
+
+ // Notify the load event to record the content blocking log.
+ //
+ // Note that we need to change the code here if we decided to block content
+ // email trackers in the future.
+ ContentBlockingNotifier::OnEvent(
+ aChannel,
+ nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT);
+ } else {
+ Telemetry::AccumulateCategorical(
+ isTopEmailWebApp
+ ? Telemetry::LABELS_EMAIL_TRACKER_COUNT::base_email_webapp
+ : Telemetry::LABELS_EMAIL_TRACKER_COUNT::base_normal);
+ // Notify to record content blocking log. Note that we don't need to notify
+ // if email tracking is enabled because the email tracking protection
+ // feature will be responsible for notifying the blocking event.
+ //
+ // Note that we need to change the code here if we decided to block content
+ // email trackers in the future.
+ if (!StaticPrefs::privacy_trackingprotection_emailtracking_enabled()) {
+ ContentBlockingNotifier::OnEvent(
+ aChannel,
+ nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureEmailTrackingDataCollection::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.h b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.h
new file mode 100644
index 0000000000..130d56c980
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.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 mozilla_net_UrlClassifierFeatureEmailTrackingDataCollection_h
+#define mozilla_net_UrlClassifierFeatureEmailTrackingDataCollection_h
+
+#include "UrlClassifierFeatureBase.h"
+
+namespace mozilla::net {
+
+class UrlClassifierFeatureEmailTrackingDataCollection final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureEmailTrackingDataCollection>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureEmailTrackingDataCollection();
+
+ static void MaybeInitialize();
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_UrlClassifierFeatureEmailTrackingDataCollection_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp
new file mode 100644
index 0000000000..01a18b5381
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp
@@ -0,0 +1,217 @@
+/* -*- 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 "UrlClassifierFeatureEmailTrackingProtection.h"
+
+#include "ChannelClassifierService.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+
+namespace mozilla::net {
+
+namespace {
+
+#define EMAIL_TRACKING_PROTECTION_FEATURE_NAME "emailtracking-protection"
+
+#define URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST \
+ "urlclassifier.features.emailtracking.blocklistTables"
+#define URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.emailtracking.blocklistHosts"
+#define URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST \
+ "urlclassifier.features.emailtracking.allowlistTables"
+#define URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.emailtracking.allowlistHosts"
+#define URLCLASSIFIER_EMAIL_TRACKING_PROTECTION_EXCEPTION_URLS \
+ "urlclassifier.features.emailtracking.skipURLs"
+#define TABLE_EMAIL_TRACKING_BLOCKLIST_PREF "emailtracking-blocklist-pref"
+#define TABLE_EMAIL_TRACKING_ENTITYLIST_PREF "emailtracking-allowlist-pref"
+
+StaticRefPtr<UrlClassifierFeatureEmailTrackingProtection>
+ gFeatureEmailTrackingProtection;
+
+} // namespace
+
+UrlClassifierFeatureEmailTrackingProtection::
+ UrlClassifierFeatureEmailTrackingProtection()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(EMAIL_TRACKING_PROTECTION_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST),
+ nsLiteralCString(URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(
+ URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_EMAIL_TRACKING_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_EMAIL_TRACKING_ENTITYLIST_PREF),
+ nsLiteralCString(
+ URLCLASSIFIER_EMAIL_TRACKING_PROTECTION_EXCEPTION_URLS)) {}
+
+/* static */
+const char* UrlClassifierFeatureEmailTrackingProtection::Name() {
+ return EMAIL_TRACKING_PROTECTION_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureEmailTrackingProtection::MaybeInitialize() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ UC_LOG_LEAK(("UrlClassifierFeatureEmailTrackingProtection::MaybeInitialize"));
+
+ if (!gFeatureEmailTrackingProtection) {
+ gFeatureEmailTrackingProtection =
+ new UrlClassifierFeatureEmailTrackingProtection();
+ gFeatureEmailTrackingProtection->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureEmailTrackingProtection::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureEmailTrackingProtection::MaybeShutdown"));
+
+ if (gFeatureEmailTrackingProtection) {
+ gFeatureEmailTrackingProtection->ShutdownPreferences();
+ gFeatureEmailTrackingProtection = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureEmailTrackingProtection>
+UrlClassifierFeatureEmailTrackingProtection::MaybeCreate(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureEmailTrackingProtection::MaybeCreate - channel %p",
+ aChannel));
+
+ // Check if the email tracking protection is enabled.
+ if (!StaticPrefs::privacy_trackingprotection_emailtracking_enabled() &&
+ !(NS_UsePrivateBrowsing(aChannel) &&
+ StaticPrefs::
+ privacy_trackingprotection_emailtracking_pbmode_enabled())) {
+ return nullptr;
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel);
+ if (!isThirdParty) {
+ UC_LOG(
+ ("UrlClassifierFeatureEmailTrackingProtection::MaybeCreate - "
+ "skipping first party or top-level load for channel %p",
+ aChannel));
+ return nullptr;
+ }
+
+ if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureEmailTrackingProtection);
+
+ RefPtr<UrlClassifierFeatureEmailTrackingProtection> self =
+ gFeatureEmailTrackingProtection;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureEmailTrackingProtection::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(EMAIL_TRACKING_PROTECTION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureEmailTrackingProtection);
+
+ RefPtr<UrlClassifierFeatureEmailTrackingProtection> self =
+ gFeatureEmailTrackingProtection;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureEmailTrackingProtection::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel);
+
+ // This is a blocking feature.
+ *aShouldContinue = isAllowListed;
+
+ if (isAllowListed) {
+ return NS_OK;
+ }
+
+ nsAutoCString list;
+ UrlClassifierCommon::TablesToString(aList, list);
+
+ ChannelBlockDecision decision =
+ ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list);
+ if (decision != ChannelBlockDecision::Blocked) {
+ uint32_t event =
+ decision == ChannelBlockDecision::Replaced
+ ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT
+ : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT;
+
+ // Need to set aBlocked to True if we replace the Email Tracker with a shim,
+ // since the shim is treated as a blocked event
+ // Note: If we need to account for which kind of tracker was replaced,
+ // we need to create a new event type in nsIWebProgressListener
+ if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) {
+ ContentBlockingNotifier::OnEvent(aChannel, event, true);
+ } else {
+ ContentBlockingNotifier::OnEvent(aChannel, event, false);
+ }
+
+ *aShouldContinue = true;
+ return NS_OK;
+ }
+
+ UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_EMAILTRACKING_URI,
+ list, ""_ns, ""_ns);
+
+ UC_LOG(
+ ("UrlClassifierFeatureEmailTrackingProtection::ProcessChannel - "
+ "cancelling channel %p",
+ aChannel));
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ Unused << httpChannel->CancelByURLClassifier(NS_ERROR_EMAILTRACKING_URI);
+ } else {
+ Unused << aChannel->Cancel(NS_ERROR_EMAILTRACKING_URI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureEmailTrackingProtection::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h
new file mode 100644
index 0000000000..812a60f255
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h
@@ -0,0 +1,47 @@
+/* -*- 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_UrlClassifierFeatureEmailTrackingProtection_h
+#define mozilla_net_UrlClassifierFeatureEmailTrackingProtection_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla::net {
+
+class UrlClassifierFeatureEmailTrackingProtection final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureEmailTrackingProtection>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureEmailTrackingProtection();
+
+ static void MaybeInitialize();
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_UrlClassifierFeatureEmailTrackingProtection_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp b/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
new file mode 100644
index 0000000000..747e938f06
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
@@ -0,0 +1,384 @@
+/* -*- 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/UrlClassifierFeatureFactory.h"
+
+// List of Features
+#include "UrlClassifierFeatureCryptominingAnnotation.h"
+#include "UrlClassifierFeatureCryptominingProtection.h"
+#include "UrlClassifierFeatureEmailTrackingDataCollection.h"
+#include "UrlClassifierFeatureEmailTrackingProtection.h"
+#include "UrlClassifierFeatureFingerprintingAnnotation.h"
+#include "UrlClassifierFeatureFingerprintingProtection.h"
+#include "UrlClassifierFeaturePhishingProtection.h"
+#include "UrlClassifierFeatureSocialTrackingAnnotation.h"
+#include "UrlClassifierFeatureSocialTrackingProtection.h"
+#include "UrlClassifierFeatureTrackingProtection.h"
+#include "UrlClassifierFeatureTrackingAnnotation.h"
+#include "UrlClassifierFeatureCustomTables.h"
+
+#include "nsIWebProgressListener.h"
+#include "nsAppRunner.h"
+
+namespace mozilla {
+namespace net {
+
+/* static */
+void UrlClassifierFeatureFactory::Shutdown() {
+ // We want to expose Features only in the parent process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ UrlClassifierFeatureCryptominingAnnotation::MaybeShutdown();
+ UrlClassifierFeatureCryptominingProtection::MaybeShutdown();
+ UrlClassifierFeatureEmailTrackingDataCollection::MaybeShutdown();
+ UrlClassifierFeatureEmailTrackingProtection::MaybeShutdown();
+ UrlClassifierFeatureFingerprintingAnnotation::MaybeShutdown();
+ UrlClassifierFeatureFingerprintingProtection::MaybeShutdown();
+ UrlClassifierFeaturePhishingProtection::MaybeShutdown();
+ UrlClassifierFeatureSocialTrackingAnnotation::MaybeShutdown();
+ UrlClassifierFeatureSocialTrackingProtection::MaybeShutdown();
+ UrlClassifierFeatureTrackingAnnotation::MaybeShutdown();
+ UrlClassifierFeatureTrackingProtection::MaybeShutdown();
+}
+
+/* static */
+void UrlClassifierFeatureFactory::GetFeaturesFromChannel(
+ nsIChannel* aChannel,
+ nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIUrlClassifierFeature> feature;
+
+ // Note that the order of the features is extremely important! When more than
+ // 1 feature classifies the channel, we call ::ProcessChannel() following this
+ // feature order, and this could produce different results with a different
+ // feature ordering.
+
+ // Email Tracking Data Collection
+ // This needs to be run before other features so that other blocking features
+ // won't stop us to collect data for email trackers. Note that this feature
+ // is not a blocking feature.
+ feature =
+ UrlClassifierFeatureEmailTrackingDataCollection::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // Email Tracking Protection
+ feature = UrlClassifierFeatureEmailTrackingProtection::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // Cryptomining Protection
+ feature = UrlClassifierFeatureCryptominingProtection::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // Fingerprinting Protection
+ feature = UrlClassifierFeatureFingerprintingProtection::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // SocialTracking Protection
+ feature = UrlClassifierFeatureSocialTrackingProtection::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // Tracking Protection
+ feature = UrlClassifierFeatureTrackingProtection::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // Cryptomining Annotation
+ feature = UrlClassifierFeatureCryptominingAnnotation::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // Fingerprinting Annotation
+ feature = UrlClassifierFeatureFingerprintingAnnotation::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // SocialTracking Annotation
+ feature = UrlClassifierFeatureSocialTrackingAnnotation::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+
+ // Tracking Annotation
+ feature = UrlClassifierFeatureTrackingAnnotation::MaybeCreate(aChannel);
+ if (feature) {
+ aFeatures.AppendElement(feature);
+ }
+}
+
+/* static */
+void UrlClassifierFeatureFactory::GetPhishingProtectionFeatures(
+ nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures) {
+ UrlClassifierFeaturePhishingProtection::MaybeCreate(aFeatures);
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFactory::GetFeatureByName(const nsACString& aName) {
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIUrlClassifierFeature> feature;
+
+ // Cryptomining Annotation
+ feature = UrlClassifierFeatureCryptominingAnnotation::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // Cryptomining Protection
+ feature = UrlClassifierFeatureCryptominingProtection::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // Email Tracking Data Collection
+ feature =
+ UrlClassifierFeatureEmailTrackingDataCollection::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // Email Tracking Protection
+ feature =
+ UrlClassifierFeatureEmailTrackingProtection::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // Fingerprinting Annotation
+ feature =
+ UrlClassifierFeatureFingerprintingAnnotation::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // Fingerprinting Protection
+ feature =
+ UrlClassifierFeatureFingerprintingProtection::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // SocialTracking Annotation
+ feature =
+ UrlClassifierFeatureSocialTrackingAnnotation::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // SocialTracking Protection
+ feature =
+ UrlClassifierFeatureSocialTrackingProtection::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // Tracking Protection
+ feature = UrlClassifierFeatureTrackingProtection::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // Tracking Annotation
+ feature = UrlClassifierFeatureTrackingAnnotation::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // PhishingProtection features
+ feature = UrlClassifierFeaturePhishingProtection::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ return nullptr;
+}
+
+/* static */
+void UrlClassifierFeatureFactory::GetFeatureNames(nsTArray<nsCString>& aArray) {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ nsAutoCString name;
+
+ // Cryptomining Annotation
+ name.Assign(UrlClassifierFeatureCryptominingAnnotation::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Cryptomining Protection
+ name.Assign(UrlClassifierFeatureCryptominingProtection::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Email Tracking Data Collection
+ name.Assign(UrlClassifierFeatureEmailTrackingDataCollection::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Email Tracking Protection
+ name.Assign(UrlClassifierFeatureEmailTrackingProtection::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Fingerprinting Annotation
+ name.Assign(UrlClassifierFeatureFingerprintingAnnotation::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Fingerprinting Protection
+ name.Assign(UrlClassifierFeatureFingerprintingProtection::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // SocialTracking Annotation
+ name.Assign(UrlClassifierFeatureSocialTrackingAnnotation::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // SocialTracking Protection
+ name.Assign(UrlClassifierFeatureSocialTrackingProtection::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Tracking Protection
+ name.Assign(UrlClassifierFeatureTrackingProtection::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Tracking Annotation
+ name.Assign(UrlClassifierFeatureTrackingAnnotation::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // PhishingProtection features
+ {
+ nsTArray<nsCString> features;
+ UrlClassifierFeaturePhishingProtection::GetFeatureNames(features);
+ aArray.AppendElements(features);
+ }
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFactory::CreateFeatureWithTables(
+ const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables,
+ const nsTArray<nsCString>& aEntitylistTables) {
+ nsCOMPtr<nsIUrlClassifierFeature> feature =
+ new UrlClassifierFeatureCustomTables(aName, aBlocklistTables,
+ aEntitylistTables);
+ return feature.forget();
+}
+
+namespace {
+
+struct BlockingErrorCode {
+ nsresult mErrorCode;
+ uint32_t mBlockingEventCode;
+ const char* mConsoleMessage;
+ nsCString mConsoleCategory;
+};
+
+static const BlockingErrorCode sBlockingErrorCodes[] = {
+ {NS_ERROR_TRACKING_URI,
+ nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT,
+ "TrackerUriBlocked", "Tracking Protection"_ns},
+ {NS_ERROR_FINGERPRINTING_URI,
+ nsIWebProgressListener::STATE_BLOCKED_FINGERPRINTING_CONTENT,
+ "TrackerUriBlocked", "Tracking Protection"_ns},
+ {NS_ERROR_CRYPTOMINING_URI,
+ nsIWebProgressListener::STATE_BLOCKED_CRYPTOMINING_CONTENT,
+ "TrackerUriBlocked", "Tracking Protection"_ns},
+ {NS_ERROR_SOCIALTRACKING_URI,
+ nsIWebProgressListener::STATE_BLOCKED_SOCIALTRACKING_CONTENT,
+ "TrackerUriBlocked", "Tracking Protection"_ns},
+ {NS_ERROR_EMAILTRACKING_URI,
+ nsIWebProgressListener::STATE_BLOCKED_EMAILTRACKING_CONTENT,
+ "TrackerUriBlocked", "Tracking Protection"_ns},
+};
+
+} // namespace
+
+/* static */
+bool UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ nsresult aError) {
+ // In theory we can iterate through the features, but at the moment, we can
+ // just have a simple check here.
+ for (const auto& blockingErrorCode : sBlockingErrorCodes) {
+ if (aError == blockingErrorCode.mErrorCode) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool UrlClassifierFeatureFactory::IsClassifierBlockingEventCode(
+ uint32_t aEventCode) {
+ for (const auto& blockingErrorCode : sBlockingErrorCodes) {
+ if (aEventCode == blockingErrorCode.mBlockingEventCode) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+uint32_t UrlClassifierFeatureFactory::GetClassifierBlockingEventCode(
+ nsresult aErrorCode) {
+ for (const auto& blockingErrorCode : sBlockingErrorCodes) {
+ if (aErrorCode == blockingErrorCode.mErrorCode) {
+ return blockingErrorCode.mBlockingEventCode;
+ }
+ }
+ return 0;
+}
+
+/* static */ const char*
+UrlClassifierFeatureFactory::ClassifierBlockingErrorCodeToConsoleMessage(
+ nsresult aError, nsACString& aCategory) {
+ for (const auto& blockingErrorCode : sBlockingErrorCodes) {
+ if (aError == blockingErrorCode.mErrorCode) {
+ aCategory = blockingErrorCode.mConsoleCategory;
+ return blockingErrorCode.mConsoleMessage;
+ }
+ }
+
+ return nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureFactory.h b/netwerk/url-classifier/UrlClassifierFeatureFactory.h
new file mode 100644
index 0000000000..f8a7537561
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.h
@@ -0,0 +1,57 @@
+/* -*- 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_UrlClassifierFeatureFactory_h
+#define mozilla_net_UrlClassifierFeatureFactory_h
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsIChannel;
+class nsIUrlClassifierFeature;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureFactory final {
+ public:
+ static void Shutdown();
+
+ static void GetFeaturesFromChannel(
+ nsIChannel* aChannel,
+ nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures);
+
+ static void GetPhishingProtectionFeatures(
+ nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetFeatureByName(
+ const nsACString& aName);
+
+ static void GetFeatureNames(nsTArray<nsCString>& aArray);
+
+ static already_AddRefed<nsIUrlClassifierFeature> CreateFeatureWithTables(
+ const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables,
+ const nsTArray<nsCString>& aEntitylistTables);
+
+ // Returns true if this error is known as one of the blocking error codes.
+ static bool IsClassifierBlockingErrorCode(nsresult aError);
+
+ // Returns true if this event is a known blocking state from
+ // nsIWebProgressListener.
+ static bool IsClassifierBlockingEventCode(uint32_t aEventCode);
+
+ static uint32_t GetClassifierBlockingEventCode(nsresult aErrorCode);
+
+ // This can be called only if IsClassifierBlockingErrorCode(aError) returns
+ // true.
+ static const char* ClassifierBlockingErrorCodeToConsoleMessage(
+ nsresult aError, nsACString& aCategory);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureFactory_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp
new file mode 100644
index 0000000000..e5cd9a65f7
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp
@@ -0,0 +1,178 @@
+/* -*- 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 "UrlClassifierFeatureFingerprintingAnnotation.h"
+
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "nsIClassifiedChannel.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define FINGERPRINTING_ANNOTATION_FEATURE_NAME "fingerprinting-annotation"
+
+#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST \
+ "urlclassifier.features.fingerprinting.annotate.blacklistTables"
+#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.fingerprinting.annotate.blacklistHosts"
+#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST \
+ "urlclassifier.features.fingerprinting.annotate.whitelistTables"
+#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.fingerprinting.annotate.whitelistHosts"
+#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_EXCEPTION_URLS \
+ "urlclassifier.features.fingerprinting.annotate.skipURLs"
+#define TABLE_FINGERPRINTING_ANNOTATION_BLOCKLIST_PREF \
+ "fingerprinting-annotate-blacklist-pref"
+#define TABLE_FINGERPRINTING_ANNOTATION_ENTITYLIST_PREF \
+ "fingerprinting-annotate-whitelist-pref"
+
+StaticRefPtr<UrlClassifierFeatureFingerprintingAnnotation>
+ gFeatureFingerprintingAnnotation;
+
+} // namespace
+
+UrlClassifierFeatureFingerprintingAnnotation::
+ UrlClassifierFeatureFingerprintingAnnotation()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(FINGERPRINTING_ANNOTATION_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST),
+ nsLiteralCString(
+ URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(
+ URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_FINGERPRINTING_ANNOTATION_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_FINGERPRINTING_ANNOTATION_ENTITYLIST_PREF),
+ nsLiteralCString(
+ URLCLASSIFIER_FINGERPRINTING_ANNOTATION_EXCEPTION_URLS)) {}
+
+/* static */ const char* UrlClassifierFeatureFingerprintingAnnotation::Name() {
+ return FINGERPRINTING_ANNOTATION_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureFingerprintingAnnotation::MaybeInitialize() {
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureFingerprintingAnnotation::MaybeInitialize"));
+
+ if (!gFeatureFingerprintingAnnotation) {
+ gFeatureFingerprintingAnnotation =
+ new UrlClassifierFeatureFingerprintingAnnotation();
+ gFeatureFingerprintingAnnotation->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureFingerprintingAnnotation::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureFingerprintingAnnotation::MaybeShutdown"));
+
+ if (gFeatureFingerprintingAnnotation) {
+ gFeatureFingerprintingAnnotation->ShutdownPreferences();
+ gFeatureFingerprintingAnnotation = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureFingerprintingAnnotation>
+UrlClassifierFeatureFingerprintingAnnotation::MaybeCreate(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureFingerprintingAnnotation::MaybeCreate - channel %p",
+ aChannel));
+
+ if (UrlClassifierCommon::IsPassiveContent(aChannel)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureFingerprintingAnnotation);
+
+ RefPtr<UrlClassifierFeatureFingerprintingAnnotation> self =
+ gFeatureFingerprintingAnnotation;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFingerprintingAnnotation::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(FINGERPRINTING_ANNOTATION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureFingerprintingAnnotation);
+
+ RefPtr<UrlClassifierFeatureFingerprintingAnnotation> self =
+ gFeatureFingerprintingAnnotation;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureFingerprintingAnnotation::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ // This is not a blocking feature.
+ *aShouldContinue = true;
+
+ UC_LOG(
+ ("UrlClassifierFeatureFingerprintingAnnotation::ProcessChannel - "
+ "annotating channel %p",
+ aChannel));
+
+ static std::vector<UrlClassifierCommon::ClassificationData>
+ sClassificationData = {
+ {"content-fingerprinting-track-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_FINGERPRINTING_CONTENT},
+ };
+
+ uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags(
+ aList, sClassificationData,
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_FINGERPRINTING);
+
+ UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes);
+
+ UrlClassifierCommon::AnnotateChannel(
+ aChannel, flags,
+ nsIWebProgressListener::STATE_LOADED_FINGERPRINTING_CONTENT);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureFingerprintingAnnotation::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h
new file mode 100644
index 0000000000..ab9dff2f66
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureFingerprintingAnnotation_h
+#define mozilla_net_UrlClassifierFeatureFingerprintingAnnotation_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureFingerprintingAnnotation final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureFingerprintingAnnotation>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureFingerprintingAnnotation();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureFingerprintingAnnotation_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp
new file mode 100644
index 0000000000..16a352e484
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp
@@ -0,0 +1,216 @@
+/* -*- 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 "UrlClassifierFeatureFingerprintingProtection.h"
+
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "ChannelClassifierService.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "nsNetUtil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define FINGERPRINTING_FEATURE_NAME "fingerprinting-protection"
+
+#define URLCLASSIFIER_FINGERPRINTING_BLOCKLIST \
+ "urlclassifier.features.fingerprinting.blacklistTables"
+#define URLCLASSIFIER_FINGERPRINTING_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.fingerprinting.blacklistHosts"
+#define URLCLASSIFIER_FINGERPRINTING_ENTITYLIST \
+ "urlclassifier.features.fingerprinting.whitelistTables"
+#define URLCLASSIFIER_FINGERPRINTING_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.fingerprinting.whitelistHosts"
+#define URLCLASSIFIER_FINGERPRINTING_EXCEPTION_URLS \
+ "urlclassifier.features.fingerprinting.skipURLs"
+#define TABLE_FINGERPRINTING_BLOCKLIST_PREF "fingerprinting-blacklist-pref"
+#define TABLE_FINGERPRINTING_ENTITYLIST_PREF "fingerprinting-whitelist-pref"
+
+StaticRefPtr<UrlClassifierFeatureFingerprintingProtection>
+ gFeatureFingerprintingProtection;
+
+} // namespace
+
+UrlClassifierFeatureFingerprintingProtection::
+ UrlClassifierFeatureFingerprintingProtection()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(FINGERPRINTING_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_ENTITYLIST),
+ nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(
+ URLCLASSIFIER_FINGERPRINTING_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_FINGERPRINTING_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_FINGERPRINTING_ENTITYLIST_PREF),
+ nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_EXCEPTION_URLS)) {}
+
+/* static */ const char* UrlClassifierFeatureFingerprintingProtection::Name() {
+ return FINGERPRINTING_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureFingerprintingProtection::MaybeInitialize() {
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureFingerprintingProtection::MaybeInitialize"));
+
+ if (!gFeatureFingerprintingProtection) {
+ gFeatureFingerprintingProtection =
+ new UrlClassifierFeatureFingerprintingProtection();
+ gFeatureFingerprintingProtection->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureFingerprintingProtection::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureFingerprintingProtection::MaybeShutdown"));
+
+ if (gFeatureFingerprintingProtection) {
+ gFeatureFingerprintingProtection->ShutdownPreferences();
+ gFeatureFingerprintingProtection = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureFingerprintingProtection>
+UrlClassifierFeatureFingerprintingProtection::MaybeCreate(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureFingerprintingProtection::MaybeCreate - channel %p",
+ aChannel));
+
+ if (!StaticPrefs::privacy_trackingprotection_fingerprinting_enabled()) {
+ return nullptr;
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel);
+ if (!isThirdParty) {
+ UC_LOG(
+ ("UrlClassifierFeatureFingerprintingProtection::MaybeCreate - "
+ "skipping first party or top-level load for channel %p",
+ aChannel));
+ return nullptr;
+ }
+
+ if (UrlClassifierCommon::IsPassiveContent(aChannel)) {
+ return nullptr;
+ }
+
+ if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureFingerprintingProtection);
+
+ RefPtr<UrlClassifierFeatureFingerprintingProtection> self =
+ gFeatureFingerprintingProtection;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFingerprintingProtection::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(FINGERPRINTING_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureFingerprintingProtection);
+
+ RefPtr<UrlClassifierFeatureFingerprintingProtection> self =
+ gFeatureFingerprintingProtection;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureFingerprintingProtection::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel);
+
+ // This is a blocking feature.
+ *aShouldContinue = isAllowListed;
+
+ if (isAllowListed) {
+ return NS_OK;
+ }
+
+ nsAutoCString list;
+ UrlClassifierCommon::TablesToString(aList, list);
+
+ ChannelBlockDecision decision =
+ ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list);
+ if (decision != ChannelBlockDecision::Blocked) {
+ uint32_t event =
+ decision == ChannelBlockDecision::Replaced
+ ? nsIWebProgressListener::STATE_REPLACED_FINGERPRINTING_CONTENT
+ : nsIWebProgressListener::STATE_ALLOWED_FINGERPRINTING_CONTENT;
+
+ // Need to set aBlocked to True if we replace the Fingerprinter with a shim,
+ // since the shim is treated as a blocked event
+ if (event ==
+ nsIWebProgressListener::STATE_REPLACED_FINGERPRINTING_CONTENT) {
+ ContentBlockingNotifier::OnEvent(aChannel, event, true);
+ } else {
+ ContentBlockingNotifier::OnEvent(aChannel, event, false);
+ }
+
+ *aShouldContinue = true;
+ return NS_OK;
+ }
+
+ UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_FINGERPRINTING_URI,
+ list, ""_ns, ""_ns);
+
+ UC_LOG(
+ ("UrlClassifierFeatureFingerprintingProtection::ProcessChannel - "
+ "cancelling channel %p",
+ aChannel));
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ Unused << httpChannel->CancelByURLClassifier(NS_ERROR_FINGERPRINTING_URI);
+ } else {
+ Unused << aChannel->Cancel(NS_ERROR_FINGERPRINTING_URI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureFingerprintingProtection::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h
new file mode 100644
index 0000000000..2ef0a4fea7
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureFingerprintingProtection_h
+#define mozilla_net_UrlClassifierFeatureFingerprintingProtection_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureFingerprintingProtection final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureFingerprintingProtection>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureFingerprintingProtection();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureFingerprintingProtection_h
diff --git a/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp
new file mode 100644
index 0000000000..d03f9c790b
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp
@@ -0,0 +1,128 @@
+/* -*- 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 "UrlClassifierFeaturePhishingProtection.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+struct UrlClassifierFeaturePhishingProtection::PhishingProtectionFeature {
+ const char* mName;
+ const char* mBlocklistPrefTables;
+ bool (*mPref)();
+
+ RefPtr<UrlClassifierFeaturePhishingProtection> mFeature;
+};
+
+namespace {
+
+struct UrlClassifierFeaturePhishingProtection::PhishingProtectionFeature
+ sPhishingProtectionFeaturesMap[] = {
+ {"malware", "urlclassifier.malwareTable",
+ StaticPrefs::browser_safebrowsing_malware_enabled},
+ {"phishing", "urlclassifier.phishTable",
+ StaticPrefs::browser_safebrowsing_phishing_enabled},
+ {"blockedURIs", "urlclassifier.blockedTable",
+ StaticPrefs::browser_safebrowsing_blockedURIs_enabled},
+};
+
+} // namespace
+
+UrlClassifierFeaturePhishingProtection::UrlClassifierFeaturePhishingProtection(
+ const UrlClassifierFeaturePhishingProtection::PhishingProtectionFeature&
+ aFeature)
+ : UrlClassifierFeatureBase(
+ nsDependentCString(aFeature.mName),
+ nsDependentCString(aFeature.mBlocklistPrefTables),
+ ""_ns, // aPrefEntitylistPrefTbles,
+ ""_ns, // aPrefBlocklistHosts
+ ""_ns, // aPrefEntitylistHosts
+ ""_ns, // aPrefBlocklistTableName
+ ""_ns, // aPrefEntitylistTableName
+ ""_ns) { // aPrefExceptionHosts
+}
+
+/* static */
+void UrlClassifierFeaturePhishingProtection::GetFeatureNames(
+ nsTArray<nsCString>& aArray) {
+ for (const PhishingProtectionFeature& feature :
+ sPhishingProtectionFeaturesMap) {
+ if (feature.mPref()) {
+ aArray.AppendElement(nsDependentCString(feature.mName));
+ }
+ }
+}
+
+/* static */
+void UrlClassifierFeaturePhishingProtection::MaybeInitialize() {
+ for (PhishingProtectionFeature& feature : sPhishingProtectionFeaturesMap) {
+ if (!feature.mFeature && feature.mPref()) {
+ feature.mFeature = new UrlClassifierFeaturePhishingProtection(feature);
+ feature.mFeature->InitializePreferences();
+ }
+ }
+}
+
+/* static */
+void UrlClassifierFeaturePhishingProtection::MaybeShutdown() {
+ for (PhishingProtectionFeature& feature : sPhishingProtectionFeaturesMap) {
+ if (feature.mFeature) {
+ feature.mFeature->ShutdownPreferences();
+ feature.mFeature = nullptr;
+ }
+ }
+}
+
+/* static */
+void UrlClassifierFeaturePhishingProtection::MaybeCreate(
+ nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures) {
+ MaybeInitialize();
+
+ for (const PhishingProtectionFeature& feature :
+ sPhishingProtectionFeaturesMap) {
+ if (feature.mPref()) {
+ MOZ_ASSERT(feature.mFeature);
+ aFeatures.AppendElement(feature.mFeature);
+ }
+ }
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeaturePhishingProtection::GetIfNameMatches(
+ const nsACString& aName) {
+ MaybeInitialize();
+
+ for (const PhishingProtectionFeature& feature :
+ sPhishingProtectionFeaturesMap) {
+ if (feature.mPref() && aName.Equals(feature.mName)) {
+ MOZ_ASSERT(feature.mFeature);
+ nsCOMPtr<nsIUrlClassifierFeature> self = feature.mFeature.get();
+ return self.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeaturePhishingProtection::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeaturePhishingProtection::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h
new file mode 100644
index 0000000000..d5498cbb72
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_UrlClassifierFeaturePhishingProtection_h
+#define mozilla_UrlClassifierFeaturePhishingProtection_h
+
+#include "UrlClassifierFeatureBase.h"
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeaturePhishingProtection final
+ : public UrlClassifierFeatureBase {
+ public:
+ struct PhishingProtectionFeature;
+
+ static void GetFeatureNames(nsTArray<nsCString>& aArray);
+
+ static void MaybeShutdown();
+
+ static void MaybeCreate(nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD
+ ProcessChannel(nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ explicit UrlClassifierFeaturePhishingProtection(
+ const PhishingProtectionFeature& aFeature);
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_UrlClassifierFeaturePhishingProtection_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureResult.cpp b/netwerk/url-classifier/UrlClassifierFeatureResult.cpp
new file mode 100644
index 0000000000..464c6cefe0
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureResult.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/UrlClassifierFeatureResult.h"
+
+namespace mozilla {
+namespace net {
+
+UrlClassifierFeatureResult::UrlClassifierFeatureResult(
+ nsIURI* aURI, nsIUrlClassifierFeature* aFeature, const nsACString& aList)
+ : mURI(aURI), mFeature(aFeature), mList(aList) {}
+
+UrlClassifierFeatureResult::~UrlClassifierFeatureResult() = default;
+
+NS_IMETHODIMP
+UrlClassifierFeatureResult::GetUri(nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsCOMPtr<nsIURI> uri = mURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureResult::GetFeature(nsIUrlClassifierFeature** aFeature) {
+ NS_ENSURE_ARG_POINTER(aFeature);
+ nsCOMPtr<nsIUrlClassifierFeature> feature = mFeature;
+ feature.forget(aFeature);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureResult::GetList(nsACString& aList) {
+ aList = mList;
+ return NS_OK;
+}
+
+NS_INTERFACE_MAP_BEGIN(UrlClassifierFeatureResult)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIUrlClassifierFeatureResult)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierFeatureResult)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(UrlClassifierFeatureResult)
+NS_IMPL_RELEASE(UrlClassifierFeatureResult)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureResult.h b/netwerk/url-classifier/UrlClassifierFeatureResult.h
new file mode 100644
index 0000000000..fba6c95c9b
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureResult.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 mozilla_net_UrlClassifierFeatureResult_h
+#define mozilla_net_UrlClassifierFeatureResult_h
+
+#include "nsIUrlClassifierFeature.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureResult final : public nsIUrlClassifierFeatureResult {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLCLASSIFIERFEATURERESULT
+
+ UrlClassifierFeatureResult(nsIURI* aURI, nsIUrlClassifierFeature* aFeature,
+ const nsACString& aList);
+
+ nsIURI* URI() const { return mURI; }
+
+ nsIUrlClassifierFeature* Feature() const { return mFeature; }
+
+ // Comma separated list of tables.
+ const nsCString& List() const { return mList; }
+
+ protected:
+ ~UrlClassifierFeatureResult();
+
+ private:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIUrlClassifierFeature> mFeature;
+ const nsCString mList;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureResult_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp
new file mode 100644
index 0000000000..e464c0c815
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp
@@ -0,0 +1,178 @@
+/* -*- 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 "UrlClassifierFeatureSocialTrackingAnnotation.h"
+
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "nsIClassifiedChannel.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define SOCIALTRACKING_ANNOTATION_FEATURE_NAME "socialtracking-annotation"
+
+#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST \
+ "urlclassifier.features.socialtracking.annotate.blacklistTables"
+#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.socialtracking.annotate.blacklistHosts"
+#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST \
+ "urlclassifier.features.socialtracking.annotate.whitelistTables"
+#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.socialtracking.annotate.whitelistHosts"
+#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_EXCEPTION_URLS \
+ "urlclassifier.features.socialtracking.annotate.skipURLs"
+#define TABLE_SOCIALTRACKING_ANNOTATION_BLOCKLIST_PREF \
+ "socialtracking-annotate-blacklist-pref"
+#define TABLE_SOCIALTRACKING_ANNOTATION_ENTITYLIST_PREF \
+ "socialtracking-annotate-whitelist-pref"
+
+StaticRefPtr<UrlClassifierFeatureSocialTrackingAnnotation>
+ gFeatureSocialTrackingAnnotation;
+
+} // namespace
+
+UrlClassifierFeatureSocialTrackingAnnotation::
+ UrlClassifierFeatureSocialTrackingAnnotation()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(SOCIALTRACKING_ANNOTATION_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST),
+ nsLiteralCString(
+ URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(
+ URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_SOCIALTRACKING_ANNOTATION_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_SOCIALTRACKING_ANNOTATION_ENTITYLIST_PREF),
+ nsLiteralCString(
+ URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_EXCEPTION_URLS)) {}
+
+/* static */ const char* UrlClassifierFeatureSocialTrackingAnnotation::Name() {
+ return SOCIALTRACKING_ANNOTATION_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureSocialTrackingAnnotation::MaybeInitialize() {
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureSocialTrackingAnnotation::MaybeInitialize"));
+
+ if (!gFeatureSocialTrackingAnnotation) {
+ gFeatureSocialTrackingAnnotation =
+ new UrlClassifierFeatureSocialTrackingAnnotation();
+ gFeatureSocialTrackingAnnotation->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureSocialTrackingAnnotation::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureSocialTrackingAnnotation::MaybeShutdown"));
+
+ if (gFeatureSocialTrackingAnnotation) {
+ gFeatureSocialTrackingAnnotation->ShutdownPreferences();
+ gFeatureSocialTrackingAnnotation = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureSocialTrackingAnnotation>
+UrlClassifierFeatureSocialTrackingAnnotation::MaybeCreate(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureSocialTrackingAnnotation::MaybeCreate - channel %p",
+ aChannel));
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureSocialTrackingAnnotation);
+
+ RefPtr<UrlClassifierFeatureSocialTrackingAnnotation> self =
+ gFeatureSocialTrackingAnnotation;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureSocialTrackingAnnotation::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(SOCIALTRACKING_ANNOTATION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureSocialTrackingAnnotation);
+
+ RefPtr<UrlClassifierFeatureSocialTrackingAnnotation> self =
+ gFeatureSocialTrackingAnnotation;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureSocialTrackingAnnotation::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ // This is not a blocking feature.
+ *aShouldContinue = true;
+
+ UC_LOG(
+ ("UrlClassifierFeatureSocialTrackingAnnotation::ProcessChannel"
+ "annotating channel %p",
+ aChannel));
+
+ static std::vector<UrlClassifierCommon::ClassificationData>
+ sClassificationData = {
+ {"social-tracking-protection-facebook-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_SOCIALTRACKING_FACEBOOK},
+ {"social-tracking-protection-linkedin-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_SOCIALTRACKING_LINKEDIN},
+ {"social-tracking-protection-twitter-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_SOCIALTRACKING_TWITTER},
+ };
+
+ uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags(
+ aList, sClassificationData,
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_SOCIALTRACKING);
+
+ UrlClassifierCommon::AnnotateChannel(
+ aChannel, flags,
+ nsIWebProgressListener::STATE_LOADED_SOCIALTRACKING_CONTENT);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureSocialTrackingAnnotation::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h
new file mode 100644
index 0000000000..1178bb8b29
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureSocialTrackingAnnotation_h
+#define mozilla_net_UrlClassifierFeatureSocialTrackingAnnotation_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureSocialTrackingAnnotation final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureSocialTrackingAnnotation>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureSocialTrackingAnnotation();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureSocialTrackingAnnotation_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp
new file mode 100644
index 0000000000..a2ca7459db
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp
@@ -0,0 +1,213 @@
+/* -*- 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 "UrlClassifierFeatureSocialTrackingProtection.h"
+
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "ChannelClassifierService.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "nsNetUtil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define SOCIALTRACKING_FEATURE_NAME "socialtracking-protection"
+
+#define URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST \
+ "urlclassifier.features.socialtracking.blacklistTables"
+#define URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.features.socialtracking.blacklistHosts"
+#define URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST \
+ "urlclassifier.features.socialtracking.whitelistTables"
+#define URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.features.socialtracking.whitelistHosts"
+#define URLCLASSIFIER_SOCIALTRACKING_EXCEPTION_URLS \
+ "urlclassifier.features.socialtracking.skipURLs"
+#define TABLE_SOCIALTRACKING_BLOCKLIST_PREF "socialtracking-blocklist-pref"
+#define TABLE_SOCIALTRACKING_ENTITYLIST_PREF "socialtracking-entitylist-pref"
+
+StaticRefPtr<UrlClassifierFeatureSocialTrackingProtection>
+ gFeatureSocialTrackingProtection;
+
+} // namespace
+
+UrlClassifierFeatureSocialTrackingProtection::
+ UrlClassifierFeatureSocialTrackingProtection()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(SOCIALTRACKING_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST),
+ nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(
+ URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_SOCIALTRACKING_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_SOCIALTRACKING_ENTITYLIST_PREF),
+ nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_EXCEPTION_URLS)) {}
+
+/* static */ const char* UrlClassifierFeatureSocialTrackingProtection::Name() {
+ return SOCIALTRACKING_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureSocialTrackingProtection::MaybeInitialize() {
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureSocialTrackingProtection::MaybeInitialize"));
+
+ if (!gFeatureSocialTrackingProtection) {
+ gFeatureSocialTrackingProtection =
+ new UrlClassifierFeatureSocialTrackingProtection();
+ gFeatureSocialTrackingProtection->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureSocialTrackingProtection::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureSocialTrackingProtection::MaybeShutdown"));
+
+ if (gFeatureSocialTrackingProtection) {
+ gFeatureSocialTrackingProtection->ShutdownPreferences();
+ gFeatureSocialTrackingProtection = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureSocialTrackingProtection>
+UrlClassifierFeatureSocialTrackingProtection::MaybeCreate(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureSocialTrackingProtection::MaybeCreate - channel %p",
+ aChannel));
+
+ if (!StaticPrefs::privacy_trackingprotection_socialtracking_enabled()) {
+ return nullptr;
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel);
+ if (!isThirdParty) {
+ UC_LOG(
+ ("UrlClassifierFeatureSocialTrackingProtection::MaybeCreate - "
+ "skipping first party or top-level load for channel %p",
+ aChannel));
+ return nullptr;
+ }
+
+ if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureSocialTrackingProtection);
+
+ RefPtr<UrlClassifierFeatureSocialTrackingProtection> self =
+ gFeatureSocialTrackingProtection;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureSocialTrackingProtection::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(SOCIALTRACKING_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureSocialTrackingProtection);
+
+ RefPtr<UrlClassifierFeatureSocialTrackingProtection> self =
+ gFeatureSocialTrackingProtection;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureSocialTrackingProtection::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel);
+
+ // This is a blocking feature.
+ *aShouldContinue = isAllowListed;
+
+ if (isAllowListed) {
+ return NS_OK;
+ }
+
+ nsAutoCString list;
+ UrlClassifierCommon::TablesToString(aList, list);
+
+ ChannelBlockDecision decision =
+ ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list);
+ if (decision != ChannelBlockDecision::Blocked) {
+ uint32_t event =
+ decision == ChannelBlockDecision::Replaced
+ ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT
+ : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT;
+
+ // Need to set aBlocked to True if we replace the Social Tracker
+ // with a shim, since the shim is treated as a blocked event
+ // Note: If we need to account for which kind of tracker was replaced,
+ // we need to create a new event type in nsIWebProgressListener
+ if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) {
+ ContentBlockingNotifier::OnEvent(aChannel, event, true);
+ } else {
+ ContentBlockingNotifier::OnEvent(aChannel, event, false);
+ }
+
+ *aShouldContinue = true;
+ return NS_OK;
+ }
+
+ UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_SOCIALTRACKING_URI,
+ list, ""_ns, ""_ns);
+
+ UC_LOG(
+ ("UrlClassifierFeatureSocialTrackingProtection::ProcessChannel - "
+ "cancelling channel %p",
+ aChannel));
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel);
+
+ if (httpChannel) {
+ Unused << httpChannel->CancelByURLClassifier(NS_ERROR_SOCIALTRACKING_URI);
+ } else {
+ Unused << aChannel->Cancel(NS_ERROR_SOCIALTRACKING_URI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureSocialTrackingProtection::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h
new file mode 100644
index 0000000000..50ec2d0dfe
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureSocialTrackingProtection_h
+#define mozilla_net_UrlClassifierFeatureSocialTrackingProtection_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureSocialTrackingProtection final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureSocialTrackingProtection>
+ MaybeCreate(nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureSocialTrackingProtection();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureSocialTrackingProtection_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
new file mode 100644
index 0000000000..518bde65ef
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
@@ -0,0 +1,180 @@
+/* -*- 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 "UrlClassifierFeatureTrackingAnnotation.h"
+
+#include "Classifier.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIWebProgressListener.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define TRACKING_ANNOTATION_FEATURE_NAME "tracking-annotation"
+
+#define URLCLASSIFIER_ANNOTATION_BLOCKLIST \
+ "urlclassifier.trackingAnnotationTable"
+#define URLCLASSIFIER_ANNOTATION_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.trackingAnnotationTable.testEntries"
+#define URLCLASSIFIER_ANNOTATION_ENTITYLIST \
+ "urlclassifier.trackingAnnotationWhitelistTable"
+#define URLCLASSIFIER_ANNOTATION_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.trackingAnnotationWhitelistTable.testEntries"
+#define URLCLASSIFIER_TRACKING_ANNOTATION_EXCEPTION_URLS \
+ "urlclassifier.trackingAnnotationSkipURLs"
+#define TABLE_ANNOTATION_BLOCKLIST_PREF "annotation-blacklist-pref"
+#define TABLE_ANNOTATION_ENTITYLIST_PREF "annotation-whitelist-pref"
+
+StaticRefPtr<UrlClassifierFeatureTrackingAnnotation> gFeatureTrackingAnnotation;
+
+} // namespace
+
+UrlClassifierFeatureTrackingAnnotation::UrlClassifierFeatureTrackingAnnotation()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(TRACKING_ANNOTATION_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_ANNOTATION_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_ANNOTATION_ENTITYLIST),
+ nsLiteralCString(URLCLASSIFIER_ANNOTATION_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(URLCLASSIFIER_ANNOTATION_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_ANNOTATION_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_ANNOTATION_ENTITYLIST_PREF),
+ nsLiteralCString(URLCLASSIFIER_TRACKING_ANNOTATION_EXCEPTION_URLS)) {}
+
+/* static */ const char* UrlClassifierFeatureTrackingAnnotation::Name() {
+ return TRACKING_ANNOTATION_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureTrackingAnnotation::MaybeInitialize() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ UC_LOG_LEAK(("UrlClassifierFeatureTrackingAnnotation::MaybeInitialize"));
+
+ if (!gFeatureTrackingAnnotation) {
+ gFeatureTrackingAnnotation = new UrlClassifierFeatureTrackingAnnotation();
+ gFeatureTrackingAnnotation->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureTrackingAnnotation::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureTrackingAnnotation::MaybeShutdown"));
+
+ if (gFeatureTrackingAnnotation) {
+ gFeatureTrackingAnnotation->ShutdownPreferences();
+ gFeatureTrackingAnnotation = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureTrackingAnnotation>
+UrlClassifierFeatureTrackingAnnotation::MaybeCreate(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureTrackingAnnotation::MaybeCreate - channel %p",
+ aChannel));
+
+ if (!StaticPrefs::privacy_trackingprotection_annotate_channels()) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureTrackingAnnotation);
+
+ RefPtr<UrlClassifierFeatureTrackingAnnotation> self =
+ gFeatureTrackingAnnotation;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureTrackingAnnotation::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(TRACKING_ANNOTATION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureTrackingAnnotation);
+
+ RefPtr<UrlClassifierFeatureTrackingAnnotation> self =
+ gFeatureTrackingAnnotation;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureTrackingAnnotation::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ // This is not a blocking feature.
+ *aShouldContinue = true;
+
+ UC_LOG(
+ ("UrlClassifierFeatureTrackingAnnotation::ProcessChannel - "
+ "annotating channel %p",
+ aChannel));
+
+ static std::vector<UrlClassifierCommon::ClassificationData>
+ sClassificationData = {
+ {"ads-track-"_ns,
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING_AD},
+ {"analytics-track-"_ns, nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_TRACKING_ANALYTICS},
+ {"social-track-"_ns, nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_TRACKING_SOCIAL},
+ {"content-track-"_ns, nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_TRACKING_CONTENT},
+ };
+
+ uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags(
+ aList, sClassificationData,
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING);
+
+ UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes);
+
+ uint32_t notification =
+ ((flags & nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_TRACKING_CONTENT) != 0)
+ ? nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT
+ : nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT;
+
+ UrlClassifierCommon::AnnotateChannel(aChannel, flags, notification);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureTrackingAnnotation::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
new file mode 100644
index 0000000000..5be6cf0765
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureTrackingAnnotation_h
+#define mozilla_net_UrlClassifierFeatureTrackingAnnotation_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureTrackingAnnotation final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureTrackingAnnotation> MaybeCreate(
+ nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureTrackingAnnotation();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureTrackingAnnotation_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
new file mode 100644
index 0000000000..8c50b33d4d
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
@@ -0,0 +1,217 @@
+/* -*- 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 "UrlClassifierFeatureTrackingProtection.h"
+
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "ChannelClassifierService.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsILoadContext.h"
+#include "nsNetUtil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsXULAppAPI.h"
+#include "nsIWebProgressListener.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define TRACKING_PROTECTION_FEATURE_NAME "tracking-protection"
+
+#define URLCLASSIFIER_TRACKING_BLOCKLIST "urlclassifier.trackingTable"
+#define URLCLASSIFIER_TRACKING_BLOCKLIST_TEST_ENTRIES \
+ "urlclassifier.trackingTable.testEntries"
+#define URLCLASSIFIER_TRACKING_ENTITYLIST "urlclassifier.trackingWhitelistTable"
+#define URLCLASSIFIER_TRACKING_ENTITYLIST_TEST_ENTRIES \
+ "urlclassifier.trackingWhitelistTable.testEntries"
+#define URLCLASSIFIER_TRACKING_PROTECTION_EXCEPTION_URLS \
+ "urlclassifier.trackingSkipURLs"
+#define TABLE_TRACKING_BLOCKLIST_PREF "tracking-blocklist-pref"
+#define TABLE_TRACKING_ENTITYLIST_PREF "tracking-entitylist-pref"
+
+StaticRefPtr<UrlClassifierFeatureTrackingProtection> gFeatureTrackingProtection;
+
+} // namespace
+
+UrlClassifierFeatureTrackingProtection::UrlClassifierFeatureTrackingProtection()
+ : UrlClassifierFeatureAntiTrackingBase(
+ nsLiteralCString(TRACKING_PROTECTION_FEATURE_NAME),
+ nsLiteralCString(URLCLASSIFIER_TRACKING_BLOCKLIST),
+ nsLiteralCString(URLCLASSIFIER_TRACKING_ENTITYLIST),
+ nsLiteralCString(URLCLASSIFIER_TRACKING_BLOCKLIST_TEST_ENTRIES),
+ nsLiteralCString(URLCLASSIFIER_TRACKING_ENTITYLIST_TEST_ENTRIES),
+ nsLiteralCString(TABLE_TRACKING_BLOCKLIST_PREF),
+ nsLiteralCString(TABLE_TRACKING_ENTITYLIST_PREF),
+ nsLiteralCString(URLCLASSIFIER_TRACKING_PROTECTION_EXCEPTION_URLS)) {}
+
+/* static */ const char* UrlClassifierFeatureTrackingProtection::Name() {
+ return TRACKING_PROTECTION_FEATURE_NAME;
+}
+
+/* static */
+void UrlClassifierFeatureTrackingProtection::MaybeInitialize() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ UC_LOG_LEAK(("UrlClassifierFeatureTrackingProtection::MaybeInitialize"));
+
+ if (!gFeatureTrackingProtection) {
+ gFeatureTrackingProtection = new UrlClassifierFeatureTrackingProtection();
+ gFeatureTrackingProtection->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureTrackingProtection::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureTrackingProtection::MaybeShutdown"));
+
+ if (gFeatureTrackingProtection) {
+ gFeatureTrackingProtection->ShutdownPreferences();
+ gFeatureTrackingProtection = nullptr;
+ }
+}
+
+/* static */
+already_AddRefed<UrlClassifierFeatureTrackingProtection>
+UrlClassifierFeatureTrackingProtection::MaybeCreate(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ UC_LOG_LEAK(
+ ("UrlClassifierFeatureTrackingProtection::MaybeCreate - channel %p",
+ aChannel));
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadContext) {
+ // Some channels don't have a loadcontext, check the global tracking
+ // protection preference.
+ if (!StaticPrefs::privacy_trackingprotection_enabled() &&
+ !(NS_UsePrivateBrowsing(aChannel) &&
+ StaticPrefs::privacy_trackingprotection_pbmode_enabled())) {
+ return nullptr;
+ }
+ } else if (!loadContext->UseTrackingProtection()) {
+ return nullptr;
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel);
+ if (!isThirdParty) {
+ UC_LOG(
+ ("UrlClassifierFeatureTrackingProtection::MaybeCreate - "
+ "skipping first party or top-level load for channel %p",
+ aChannel));
+ return nullptr;
+ }
+
+ if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureTrackingProtection);
+
+ RefPtr<UrlClassifierFeatureTrackingProtection> self =
+ gFeatureTrackingProtection;
+ return self.forget();
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureTrackingProtection::GetIfNameMatches(
+ const nsACString& aName) {
+ if (!aName.EqualsLiteral(TRACKING_PROTECTION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ MaybeInitialize();
+ MOZ_ASSERT(gFeatureTrackingProtection);
+
+ RefPtr<UrlClassifierFeatureTrackingProtection> self =
+ gFeatureTrackingProtection;
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureTrackingProtection::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+ bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel);
+
+ // This is a blocking feature.
+ *aShouldContinue = isAllowListed;
+
+ if (isAllowListed) {
+ return NS_OK;
+ }
+
+ nsAutoCString list;
+ UrlClassifierCommon::TablesToString(aList, list);
+
+ ChannelBlockDecision decision =
+ ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list);
+ if (decision != ChannelBlockDecision::Blocked) {
+ uint32_t event =
+ decision == ChannelBlockDecision::Replaced
+ ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT
+ : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT;
+
+ // Need to set aBlocked to True if we replace the Tracker with a shim,
+ // since the shim is treated as a blocked event
+ // Note: If we need to account for which kind of tracker was replaced,
+ // we need to create a new event type in nsIWebProgressListener
+ if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) {
+ ContentBlockingNotifier::OnEvent(aChannel, event, true);
+ } else {
+ ContentBlockingNotifier::OnEvent(aChannel, event, false);
+ }
+
+ *aShouldContinue = true;
+ return NS_OK;
+ }
+
+ UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_TRACKING_URI, list,
+ ""_ns, ""_ns);
+
+ UC_LOG(
+ ("UrlClassifierFeatureTrackingProtection::ProcessChannel - "
+ "cancelling channel %p",
+ aChannel));
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ Unused << httpChannel->CancelByURLClassifier(NS_ERROR_TRACKING_URI);
+ } else {
+ Unused << aChannel->Cancel(NS_ERROR_TRACKING_URI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureTrackingProtection::GetURIByListType(
+ nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aURIType);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (aListType == nsIUrlClassifierFeature::blocklist) {
+ *aURIType = nsIUrlClassifierFeature::blocklistURI;
+ return aChannel->GetURI(aURI);
+ }
+
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist);
+
+ *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI;
+ return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
new file mode 100644
index 0000000000..23abfe1fd9
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureTrackingProtection_h
+#define mozilla_net_UrlClassifierFeatureTrackingProtection_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureTrackingProtection final
+ : public UrlClassifierFeatureAntiTrackingBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static already_AddRefed<UrlClassifierFeatureTrackingProtection> MaybeCreate(
+ nsIChannel* aChannel);
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+ const nsACString& aName);
+
+ NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
+ const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes,
+ bool* aShouldContinue) override;
+
+ NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
+ nsIUrlClassifierFeature::listType aListType,
+ nsIUrlClassifierFeature::URIType* aURIType,
+ nsIURI** aURI) override;
+
+ private:
+ UrlClassifierFeatureTrackingProtection();
+
+ static void MaybeInitialize();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_UrlClassifierFeatureTrackingProtection_h
diff --git a/netwerk/url-classifier/components.conf b/netwerk/url-classifier/components.conf
new file mode 100644
index 0000000000..3cf63a06f1
--- /dev/null
+++ b/netwerk/url-classifier/components.conf
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{7a6da992-dbce-4943-b463-5a2dd011fa1a}',
+ 'contract_ids': ['@mozilla.org/url-classifier/channel-classifier-service;1'],
+ 'singleton': True,
+ 'type': 'nsIChannelClassifierService',
+ 'constructor': 'mozilla::net::ChannelClassifierService::GetSingleton',
+ 'headers': ['mozilla/net/ChannelClassifierService.h'],
+ },
+ {
+ 'cid': '{b9f4fd03-9d87-4bfd-9958-85a821750ddc}',
+ 'contract_ids': ['@mozilla.org/url-classifier/exception-list-service;1'],
+ 'esModule': 'resource://gre/modules/UrlClassifierExceptionListService.sys.mjs',
+ 'constructor': 'UrlClassifierExceptionListService',
+ },
+]
diff --git a/netwerk/url-classifier/moz.build b/netwerk/url-classifier/moz.build
new file mode 100644
index 0000000000..d63206780e
--- /dev/null
+++ b/netwerk/url-classifier/moz.build
@@ -0,0 +1,68 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Safe Browsing")
+
+XPIDL_SOURCES += [
+ "nsIChannelClassifierService.idl",
+ "nsIURIClassifier.idl",
+ "nsIUrlClassifierExceptionListService.idl",
+ "nsIUrlClassifierFeature.idl",
+]
+
+XPIDL_MODULE = "url-classifier"
+
+EXTRA_JS_MODULES += [
+ "UrlClassifierExceptionListService.sys.mjs",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True
+DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True
+
+UNIFIED_SOURCES += [
+ "AsyncUrlChannelClassifier.cpp",
+ "ChannelClassifierService.cpp",
+ "nsChannelClassifier.cpp",
+ "UrlClassifierCommon.cpp",
+ "UrlClassifierFeatureBase.cpp",
+ "UrlClassifierFeatureCryptominingAnnotation.cpp",
+ "UrlClassifierFeatureCryptominingProtection.cpp",
+ "UrlClassifierFeatureCustomTables.cpp",
+ "UrlClassifierFeatureEmailTrackingDataCollection.cpp",
+ "UrlClassifierFeatureEmailTrackingProtection.cpp",
+ "UrlClassifierFeatureFactory.cpp",
+ "UrlClassifierFeatureFingerprintingAnnotation.cpp",
+ "UrlClassifierFeatureFingerprintingProtection.cpp",
+ "UrlClassifierFeaturePhishingProtection.cpp",
+ "UrlClassifierFeatureResult.cpp",
+ "UrlClassifierFeatureSocialTrackingAnnotation.cpp",
+ "UrlClassifierFeatureSocialTrackingProtection.cpp",
+ "UrlClassifierFeatureTrackingAnnotation.cpp",
+ "UrlClassifierFeatureTrackingProtection.cpp",
+]
+
+EXPORTS.mozilla.net += [
+ "AsyncUrlChannelClassifier.h",
+ "ChannelClassifierService.h",
+ "UrlClassifierCommon.h",
+ "UrlClassifierFeatureFactory.h",
+ "UrlClassifierFeatureResult.h",
+]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+ "/toolkit/components/url-classifier",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/url-classifier/nsChannelClassifier.cpp b/netwerk/url-classifier/nsChannelClassifier.cpp
new file mode 100644
index 0000000000..b9bdb3a050
--- /dev/null
+++ b/netwerk/url-classifier/nsChannelClassifier.cpp
@@ -0,0 +1,483 @@
+/* -*- 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 "nsCharSeparatedTokenizer.h"
+#include "nsICacheEntry.h"
+#include "nsICachingChannel.h"
+#include "nsIChannel.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+#include "nsXULAppAPI.h"
+#include "nsQueryObject.h"
+#include "nsPrintfCString.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+#define URLCLASSIFIER_EXCEPTION_HOSTNAMES "urlclassifier.skipHostnames"
+
+// Put CachedPrefs in anonymous namespace to avoid any collision from outside of
+// this file.
+namespace {
+
+/**
+ * It is not recommended to read from Preference everytime a channel is
+ * connected.
+ * That is not fast and we should cache preference values and reuse them
+ */
+class CachedPrefs final {
+ public:
+ static CachedPrefs* GetInstance();
+
+ void Init();
+
+ nsCString GetExceptionHostnames() const { return mExceptionHostnames; }
+ void SetExceptionHostnames(const nsACString& aHostnames) {
+ mExceptionHostnames = aHostnames;
+ }
+
+ private:
+ friend class StaticAutoPtr<CachedPrefs>;
+ CachedPrefs();
+ ~CachedPrefs();
+
+ static void OnPrefsChange(const char* aPrefName, void*);
+
+ nsCString mExceptionHostnames;
+
+ static StaticAutoPtr<CachedPrefs> sInstance;
+};
+
+StaticAutoPtr<CachedPrefs> CachedPrefs::sInstance;
+
+// static
+void CachedPrefs::OnPrefsChange(const char* aPref, void* aPrefs) {
+ auto* prefs = static_cast<CachedPrefs*>(aPrefs);
+
+ if (!strcmp(aPref, URLCLASSIFIER_EXCEPTION_HOSTNAMES)) {
+ nsCString exceptionHostnames;
+ Preferences::GetCString(URLCLASSIFIER_EXCEPTION_HOSTNAMES,
+ exceptionHostnames);
+ ToLowerCase(exceptionHostnames);
+ prefs->SetExceptionHostnames(exceptionHostnames);
+ }
+}
+
+void CachedPrefs::Init() {
+ Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
+ URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
+}
+
+// static
+CachedPrefs* CachedPrefs::GetInstance() {
+ if (!sInstance) {
+ sInstance = new CachedPrefs();
+ sInstance->Init();
+ ClearOnShutdown(&sInstance);
+ }
+ MOZ_ASSERT(sInstance);
+ return sInstance;
+}
+
+CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs); }
+
+CachedPrefs::~CachedPrefs() {
+ MOZ_COUNT_DTOR(CachedPrefs);
+
+ Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange,
+ URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
+}
+
+} // anonymous namespace
+
+NS_IMPL_ISUPPORTS(nsChannelClassifier, nsIURIClassifierCallback, nsIObserver)
+
+nsChannelClassifier::nsChannelClassifier(nsIChannel* aChannel)
+ : mIsAllowListed(false), mSuspendedChannel(false), mChannel(aChannel) {
+ UC_LOG_LEAK(("nsChannelClassifier::nsChannelClassifier [this=%p]", this));
+ MOZ_ASSERT(mChannel);
+}
+
+nsChannelClassifier::~nsChannelClassifier() {
+ UC_LOG_LEAK(("nsChannelClassifier::~nsChannelClassifier [this=%p]", this));
+}
+
+void nsChannelClassifier::Start() {
+ 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, ""_ns, ""_ns, ""_ns);
+ }
+}
+
+nsresult nsChannelClassifier::StartInternal() {
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Don't bother to run the classifier on a load that has already failed.
+ // (this might happen after a redirect)
+ nsresult status;
+ mChannel->GetStatus(&status);
+ if (NS_FAILED(status)) return status;
+
+ // Don't bother to run the classifier on a cached load that was
+ // previously classified as good.
+ if (HasBeenClassified(mChannel)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't bother checking certain types of URIs.
+ if (uri->SchemeIs("about")) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ 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;
+
+ nsCString exceptionHostnames =
+ CachedPrefs::GetInstance()->GetExceptionHostnames();
+ if (!exceptionHostnames.IsEmpty()) {
+ UC_LOG(
+ ("nsChannelClassifier::StartInternal - entitylisted hostnames = %s "
+ "[this=%p]",
+ exceptionHostnames.get(), this));
+ if (IsHostnameEntitylisted(uri, exceptionHostnames)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED || rv == NS_ERROR_NOT_AVAILABLE) {
+ // no URI classifier, ignore this failure.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = securityManager->GetChannelURIPrincipal(mChannel,
+ getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool expectCallback;
+ if (UC_LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> principalURI;
+ nsCString spec;
+ principal->GetAsciiSpec(spec);
+ spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
+ UC_LOG(
+ ("nsChannelClassifier::StartInternal - classifying principal %s on "
+ "channel %p [this=%p]",
+ spec.get(), mChannel.get(), this));
+ }
+ // The classify is running in parent process, no need to give a valid event
+ // target
+ rv = uriClassifier->Classify(principal, 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.
+ UC_LOG_WARN(
+ ("nsChannelClassifier::StartInternal - couldn't suspend channel "
+ "[this=%p]",
+ this));
+ return rv;
+ }
+
+ mSuspendedChannel = true;
+ UC_LOG(
+ ("nsChannelClassifier::StartInternal - suspended channel %p [this=%p]",
+ mChannel.get(), this));
+ } else {
+ UC_LOG_WARN((
+ "nsChannelClassifier::StartInternal - not expecting callback [this=%p]",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Add an observer for shutdown
+ AddShutdownObserver();
+ return NS_OK;
+}
+
+bool nsChannelClassifier::IsHostnameEntitylisted(
+ nsIURI* aUri, const nsACString& aEntitylisted) {
+ nsAutoCString host;
+ nsresult rv = aUri->GetHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return false;
+ }
+ ToLowerCase(host);
+
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(aEntitylisted, ',').ToRange()) {
+ if (token.Equals(host)) {
+ UC_LOG(
+ ("nsChannelClassifier::StartInternal - skipping %s (entitylisted) "
+ "[this=%p]",
+ host.get(), this));
+ 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 (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) ||
+ mIsAllowListed) {
+ return;
+ }
+
+ if (UC_LOG_ENABLED()) {
+ nsAutoCString errorName;
+ GetErrorName(status, errorName);
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
+ UC_LOG(
+ ("nsChannelClassifier::MarkEntryClassified - result is %s "
+ "for uri %s [this=%p, channel=%p]",
+ errorName.get(), spec.get(), this, mChannel.get()));
+ }
+
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
+ if (!cachingChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return;
+ }
+
+ cacheEntry->SetMetaDataElement("necko:classified",
+ NS_SUCCEEDED(status) ? "1" : nullptr);
+}
+
+bool nsChannelClassifier::HasBeenClassified(nsIChannel* aChannel) {
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
+ if (!cachingChannel) {
+ return false;
+ }
+
+ // Only check the tag if we are loading from the cache without
+ // validation.
+ bool fromCache;
+ if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
+ return false;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return false;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return false;
+ }
+
+ nsCString tag;
+ cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
+ return tag.EqualsLiteral("1");
+}
+
+/* static */
+nsresult nsChannelClassifier::SendThreatHitReport(nsIChannel* aChannel,
+ const nsACString& aProvider,
+ const nsACString& aList,
+ const nsACString& aFullHash) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+
+ nsAutoCString provider(aProvider);
+ nsPrintfCString reportEnablePref(
+ "browser.safebrowsing.provider.%s.dataSharing.enabled", provider.get());
+ if (!Preferences::GetBool(reportEnablePref.get(), false)) {
+ UC_LOG(
+ ("nsChannelClassifier::SendThreatHitReport - data sharing disabled for "
+ "%s",
+ provider.get()));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ components::UrlClassifierDB::Service();
+ if (!uriClassifier) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv =
+ uriClassifier->SendThreatHitReport(aChannel, aProvider, aList, aFullHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode,
+ const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(
+ !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+
+ if (mSuspendedChannel) {
+ MarkEntryClassified(aErrorCode);
+
+ if (NS_FAILED(aErrorCode)) {
+ if (UC_LOG_ENABLED()) {
+ nsAutoCString errorName;
+ GetErrorName(aErrorCode, errorName);
+
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ nsCString spec = uri->GetSpecOrDefault();
+ spec.Truncate(
+ std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
+ UC_LOG(
+ ("nsChannelClassifier::OnClassifyComplete - cancelling channel %p "
+ "for %s "
+ "with error code %s [this=%p]",
+ mChannel.get(), spec.get(), errorName.get(), this));
+ }
+
+ // Channel will be cancelled (page element blocked) due to Safe Browsing.
+ // Do update the security state of the document and fire a security
+ // change event.
+ UrlClassifierCommon::SetBlockedContent(mChannel, aErrorCode, aList,
+ aProvider, aFullHash);
+
+ if (aErrorCode == NS_ERROR_MALWARE_URI ||
+ aErrorCode == NS_ERROR_PHISHING_URI ||
+ aErrorCode == NS_ERROR_UNWANTED_URI ||
+ aErrorCode == NS_ERROR_HARMFUL_URI) {
+ SendThreatHitReport(mChannel, aProvider, aList, aFullHash);
+ }
+
+ mChannel->Cancel(aErrorCode);
+ }
+ UC_LOG(
+ ("nsChannelClassifier::OnClassifyComplete - resuming channel %p "
+ "[this=%p]",
+ mChannel.get(), this));
+ mChannel->Resume();
+ }
+
+ mChannel = nullptr;
+ RemoveShutdownObserver();
+
+ return NS_OK;
+}
+
+void nsChannelClassifier::AddShutdownObserver() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "profile-change-net-teardown", false);
+ }
+}
+
+void nsChannelClassifier::RemoveShutdownObserver() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "profile-change-net-teardown");
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIObserver implementation
+NS_IMETHODIMP
+nsChannelClassifier::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "profile-change-net-teardown")) {
+ // If we aren't getting a callback for any reason, make sure
+ // we resume the channel.
+
+ if (mChannel && mSuspendedChannel) {
+ mSuspendedChannel = false;
+ mChannel->Cancel(NS_ERROR_ABORT);
+ mChannel->Resume();
+ mChannel = nullptr;
+ }
+
+ RemoveShutdownObserver();
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/nsChannelClassifier.h b/netwerk/url-classifier/nsChannelClassifier.h
new file mode 100644
index 0000000000..ddd89e2976
--- /dev/null
+++ b/netwerk/url-classifier/nsChannelClassifier.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 nsChannelClassifier_h__
+#define nsChannelClassifier_h__
+
+#include "nsIObserver.h"
+#include "nsIURIClassifier.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+#include <functional>
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class nsChannelClassifier final : public nsIURIClassifierCallback,
+ public nsIObserver {
+ public:
+ explicit nsChannelClassifier(nsIChannel* aChannel);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURICLASSIFIERCALLBACK
+ NS_DECL_NSIOBSERVER
+
+ // Calls nsIURIClassifier.Classify with the principal of the given channel,
+ // and cancels the channel on a bad verdict.
+ void Start();
+
+ private:
+ // True if the channel is on the allow list.
+ bool mIsAllowListed;
+ // True if the channel has been suspended.
+ bool mSuspendedChannel;
+ nsCOMPtr<nsIChannel> mChannel;
+
+ ~nsChannelClassifier();
+ // Caches good classifications for the channel principal.
+ void MarkEntryClassified(nsresult status);
+ bool HasBeenClassified(nsIChannel* aChannel);
+ // Helper function so that we ensure we call ContinueBeginConnect once
+ // Start is called. Returns NS_OK if and only if we will get a callback
+ // from the classifier service.
+ nsresult StartInternal();
+ // Helper function to check a URI against the hostname entitylist
+ bool IsHostnameEntitylisted(nsIURI* aUri, const nsACString& aEntitylisted);
+
+ void AddShutdownObserver();
+ void RemoveShutdownObserver();
+ static nsresult SendThreatHitReport(nsIChannel* aChannel,
+ const nsACString& aProvider,
+ const nsACString& aList,
+ const nsACString& aFullHash);
+
+ public:
+ // If we are blocking content, update the corresponding flag in the respective
+ // docshell and call nsDocLoader::OnSecurityChange.
+ static nsresult SetBlockedContent(nsIChannel* channel, nsresult aErrorCode,
+ const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/url-classifier/nsIChannelClassifierService.idl b/netwerk/url-classifier/nsIChannelClassifierService.idl
new file mode 100644
index 0000000000..781af289f8
--- /dev/null
+++ b/netwerk/url-classifier/nsIChannelClassifierService.idl
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIContentPolicy.idl"
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIURI;
+interface nsIObserver;
+
+[scriptable, uuid(9b0353a7-ab46-4914-9178-2215ee221e4e)]
+interface nsIUrlClassifierBlockedChannel: nsISupports
+{
+ // blocked reason
+ const unsigned long TRACKING_PROTECTION = 0;
+ const unsigned long SOCIAL_TRACKING_PROTECTION = 1;
+ const unsigned long FINGERPRINTING_PROTECTION = 2;
+ const unsigned long CRYPTOMINING_PROTECTION = 3;
+
+ // Feature that blocks this channel.
+ readonly attribute uint8_t reason;
+
+ // Comma separated list of tables that find a match for the channel's url.
+ readonly attribute ACString tables;
+
+ readonly attribute AString url;
+
+ readonly attribute uint64_t tabId;
+
+ readonly attribute uint64_t channelId;
+
+ readonly attribute boolean isPrivateBrowsing;
+
+ readonly attribute AString topLevelUrl;
+
+ // Unblock the load, but inform the UI that the tracking content will be
+ // replaced with a shim. The unblocked channel is still considered as a
+ // tracking channel. The only difference to allow() is the event sent to the
+ // UI. Calls to replace will only unblock the channel. Callers are responsible
+ // for replacing the tracking content.
+ void replace();
+
+ // Unblock the load and inform the UI that the channel has been allowed to
+ // load. The unblocked channel is still considered as a tracking channel.
+ void allow();
+};
+
+[scriptable, uuid(9411409c-5dac-40b9-ba36-2738a7237a4c)]
+interface nsIChannelClassifierService : nsISupports
+{
+ // when a channel is blocked, the observer should receive
+ // "urlclassifier-before-block-channel" callback an alternative way is to
+ // use a custom callback instead of using nsIObserver
+ void addListener(in nsIObserver aObserver);
+
+ void removeListener(in nsIObserver aObserver);
+};
diff --git a/netwerk/url-classifier/nsIURIClassifier.idl b/netwerk/url-classifier/nsIURIClassifier.idl
new file mode 100644
index 0000000000..788d7b3465
--- /dev/null
+++ b/netwerk/url-classifier/nsIURIClassifier.idl
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 "nsIUrlClassifierFeature.idl"
+
+%{C++
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+%}
+[ref] native StringArrayRef(nsTArray<nsCString>);
+
+interface nsIChannel;
+interface nsISerialEventTarget;
+interface nsIPrincipal;
+interface nsIURI;
+interface nsIUrlClassifierFeatureCallback;
+
+/**
+ * Callback function for nsIURIClassifier lookups.
+ */
+[scriptable, function, uuid(8face46e-0c96-470f-af40-0037dcd797bd)]
+interface nsIURIClassifierCallback : nsISupports
+{
+ /**
+ * Called by the URI classifier service when it is done checking a URI.
+ *
+ * Clients are responsible for associating callback objects with classify()
+ * calls.
+ *
+ * @param aErrorCode
+ * The error code with which the channel should be cancelled, or
+ * NS_OK if the load should continue normally.
+ * @param aList
+ * Name of the list that matched
+ * @param aProvider
+ * Name of provider that matched
+ * @param aFullHash
+ * Full hash of URL that matched
+ */
+ void onClassifyComplete(in nsresult aErrorCode,
+ in ACString aList,
+ in ACString aProvider,
+ in ACString aFullHash);
+};
+
+/**
+ * The URI classifier service checks a URI against lists of phishing
+ * and malware sites.
+ */
+[scriptable, uuid(596620cc-76e3-4133-9d90-360e59a794cf)]
+interface nsIURIClassifier : nsISupports
+{
+ /**
+ * Classify a Principal using its URI.
+ *
+ * @param aPrincipal
+ * The principal that should be checked by the URI classifier.
+ *
+ * @param aCallback
+ * The URI classifier will call this callback when the URI has been
+ * classified.
+ *
+ * @return <code>false</code> if classification is not necessary. The
+ * callback will not be called.
+ * <code>true</code> if classification will be performed. The
+ * callback will be called.
+ */
+ boolean classify(in nsIPrincipal aPrincipal,
+ in nsIURIClassifierCallback aCallback);
+
+ /**
+ * Asynchronously classify a URI with list of features. This does not make
+ * network requests.
+ */
+ void asyncClassifyLocalWithFeatures(in nsIURI aURI,
+ in Array<nsIUrlClassifierFeature> aFeatures,
+ in nsIUrlClassifierFeature_listType aListType,
+ in nsIUrlClassifierFeatureCallback aCallback);
+
+ /**
+ * Returns a feature named aFeatureName.
+ */
+ nsIUrlClassifierFeature getFeatureByName(in ACString aFeatureName);
+
+ /**
+ * Returns all the feature names.
+ */
+ Array<ACString> getFeatureNames();
+
+ /**
+ * Create a new feature with a list of tables. This method is just for
+ * testing! Don't use it elsewhere.
+ */
+ nsIUrlClassifierFeature createFeatureWithTables(in ACString aName,
+ in Array<ACString> aBlocklistTables,
+ in Array<ACString> aEntitylistTables);
+
+ /**
+ * Report to the provider that a Safe Browsing warning was shown.
+ *
+ * @param aChannel
+ * Channel for which the URL matched something on the threat list.
+ * @param aProvider
+ * Provider to notify.
+ * @param aList
+ * List where the full hash was found.
+ * @param aFullHash
+ * Full URL hash that triggered the warning.
+ */
+
+ void sendThreatHitReport(in nsIChannel aChannel, in ACString aProvider,
+ in ACString aList, in ACString aFullHash);
+};
diff --git a/netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl b/netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl
new file mode 100644
index 0000000000..256d0b89ae
--- /dev/null
+++ b/netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Observer for exception list updates.
+ */
+[scriptable, function, uuid(f7c918e5-94bf-4b6e-9758-ef7bdab6af7e)]
+interface nsIUrlClassifierExceptionListObserver : nsISupports
+{
+ /**
+ * Called by nsIUrlClassifierExceptionListService when the exception list
+ * for a designated feature changes and when the observer is first registered.
+ *
+ * @param aList
+ * A comma-separated list of url patterns, intended to be parsed
+ * by nsContentUtils::IsURIInList.
+ */
+ void onExceptionListUpdate(in ACString aList);
+};
+
+/**
+ * A service that monitors updates to the exception list of url-classifier
+ * feature from sources such as a local pref and remote settings updates.
+ */
+[scriptable, uuid(75c3d1a3-e977-4079-9e27-b3b56bdb76ea)]
+interface nsIUrlClassifierExceptionListService : nsISupports
+{
+ /**
+ * Register a new observer to exception list updates. When the observer is
+ * registered it is called immediately once. Afterwards it will be called
+ * whenever the specified pref changes or when remote settings for
+ * url-classifier features updates.
+ *
+ * @param aFeature
+ * The feature for which to observe the exception list.
+ *
+ * @param aPrefName
+ * (Optional) A pref name to monitor. The pref must be of string
+ * type and contain a comma-separated list of URL patterns.
+ *
+ * @param aObserver
+ * An nsIUrlClassifierExceptionListObserver object or function that
+ * will receive updates to the exception list as a comma-separated
+ * string. Will be called immediately with the current exception
+ * list value.
+ */
+ void registerAndRunExceptionListObserver(in ACString aFeature,
+ in ACString aPrefName,
+ in nsIUrlClassifierExceptionListObserver aObserver);
+
+ /**
+ * Unregister an observer.
+ *
+ * @param aFeature
+ * The feature for which to stop observing.
+ *
+ * @param aObserver
+ * The nsIUrlClassifierExceptionListObserver object to unregister.
+ */
+ void unregisterExceptionListObserver(in ACString aFeature,
+ in nsIUrlClassifierExceptionListObserver aObserver);
+
+ /**
+ * Clear all data in the service.
+ * This API is for testing only.
+ */
+ void clear();
+};
diff --git a/netwerk/url-classifier/nsIUrlClassifierFeature.idl b/netwerk/url-classifier/nsIUrlClassifierFeature.idl
new file mode 100644
index 0000000000..a7ee58289d
--- /dev/null
+++ b/netwerk/url-classifier/nsIUrlClassifierFeature.idl
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+%}
+[ref] native StringArrayRef(nsTArray<nsCString>);
+[ref] native ConstStringArrayRef(const nsTArray<nsCString>);
+
+interface nsIChannel;
+interface nsIURI;
+
+/**
+ * A single URLClassifier feature.
+ */
+[builtinclass, scriptable, uuid(a6c9b24e-b4f1-426e-af58-2c976c3943a8)]
+interface nsIUrlClassifierFeature : nsISupports
+{
+ cenum listType: 8 {
+ blocklist = 0,
+ entitylist = 1,
+ };
+
+ cenum URIType: 8 {
+ blocklistURI = 0,
+ entitylistURI = 1,
+ pairwiseEntitylistURI = 2,
+ };
+
+ /**
+ * The feature name
+ */
+ readonly attribute ACString name;
+
+ /**
+ * Returns the tables for one of the possible lists.
+ */
+ [noscript] StringArrayRef getTables(in nsIUrlClassifierFeature_listType aListType);
+
+ /**
+ * Returns true if |aTable| is part of the tables of |aListType| type.
+ */
+ [noscript] boolean hasTable(in ACString aTable,
+ in nsIUrlClassifierFeature_listType aListType);
+
+ /**
+ * Returns true if |aHost| is contained in the preference of |aListType| type.
+ * |aPrefTableName| will be set to the table name to use.
+ */
+ [noscript] boolean hasHostInPreferences(in ACString aHost,
+ in nsIUrlClassifierFeature_listType aListType,
+ out ACString aPrefTableName);
+
+ /**
+ * Returns a comma-separated list of hosts to be ignored.
+ */
+ readonly attribute ACString exceptionHostList;
+
+ /**
+ * When this feature matches the channel, this method is executed to do
+ * 'something' on the channel. For instance, a tracking-annotation feature
+ * would mark the channel as tracker, a tracking-protection feature would
+ * cancel the channel.
+ * Returns if we should process other feature results or not. For instance,
+ * tracking-protection cancel the channel, and after that we should stop
+ * processing other features.
+ */
+ [noscript] boolean processChannel(in nsIChannel aChannel,
+ in ConstStringArrayRef aList,
+ in ConstStringArrayRef aHashes);
+
+ /**
+ * Features can work with different URLs from a channel (channel url, or
+ * top-level, or something else). This method returns what we need to use for
+ * the current list.
+ * If the returned URI is created by CreatePairwiseEntityListURI(), the
+ * URIType is pairwiseEntitylistURI. Otherwise, it depends on the listType.
+ */
+ [noscript] nsIURI getURIByListType(in nsIChannel channel,
+ in nsIUrlClassifierFeature_listType listType,
+ out nsIUrlClassifierFeature_URIType URIType);
+};
+
+/**
+ * The result of the classifier operation is this interface.
+ * See asyncClassifyLocalWithFeatures() in nsIURIClassifier.idl.
+ */
+[builtinclass, scriptable, uuid(ccb88140-5d66-4873-9815-a1b98d6cdc92)]
+interface nsIUrlClassifierFeatureResult : nsISupports
+{
+ readonly attribute nsIURI uri;
+
+ readonly attribute nsIUrlClassifierFeature feature;
+
+ // Comma separate tables or preferences.
+ readonly attribute ACString list;
+};
+
+/**
+ * Callback function for nsIURIClassifier lookups.
+ * See asyncClassifyLocalWithFeatures() in nsIURIClassifier.idl.
+ */
+[scriptable, function, uuid(2ea83c26-dfc9-44ed-9cfc-171d4753d78e)]
+interface nsIUrlClassifierFeatureCallback : nsISupports
+{
+ /**
+ * Called by the URI classifier service when it is done checking a URI.
+ *
+ * Clients are responsible for associating callback objects with classify()
+ * calls.
+ *
+ * @param aResults
+ * List of nsIUrlClassifierFeatureResult objects.
+ */
+ void onClassifyComplete(in Array<nsIUrlClassifierFeatureResult> aResults);
+};
diff --git a/netwerk/wifi/WifiScanner.h b/netwerk/wifi/WifiScanner.h
new file mode 100644
index 0000000000..3b2c6eec1f
--- /dev/null
+++ b/netwerk/wifi/WifiScanner.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScanner {
+ public:
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ virtual nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) = 0;
+
+ virtual ~WifiScanner() = default;
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/dbus/DbusWifiScanner.cpp b/netwerk/wifi/dbus/DbusWifiScanner.cpp
new file mode 100644
index 0000000000..43a6cc49dc
--- /dev/null
+++ b/netwerk/wifi/dbus/DbusWifiScanner.cpp
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <gio/gio.h>
+#include "DbusWifiScanner.h"
+#include "nsWifiAccessPoint.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/GRefPtr.h"
+
+namespace mozilla {
+
+WifiScannerImpl::WifiScannerImpl() { MOZ_COUNT_CTOR(WifiScannerImpl); }
+
+WifiScannerImpl::~WifiScannerImpl() { MOZ_COUNT_DTOR(WifiScannerImpl); }
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ AccessPointArray& aAccessPoints) {
+ RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager",
+ "org.freedesktop.NetworkManager", nullptr, nullptr));
+ if (!proxy) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<GVariant> result =
+ dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Devices"));
+ if (!result ||
+ !g_variant_is_of_type(result, G_VARIANT_TYPE_OBJECT_PATH_ARRAY)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gsize num = g_variant_n_children(result);
+ for (gsize i = 0; i < num; i++) {
+ const char* devicePath =
+ g_variant_get_string(g_variant_get_child_value(result, i), nullptr);
+ if (!devicePath || !AddDevice(devicePath, aAccessPoints)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool WifiScannerImpl::AddDevice(const char* aDevicePath,
+ AccessPointArray& aAccessPoints) {
+ RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.NetworkManager", aDevicePath,
+ "org.freedesktop.NetworkManager.Device", nullptr, nullptr));
+ if (!proxy) {
+ return false;
+ }
+
+ RefPtr<GVariant> deviceType =
+ dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "DeviceType"));
+ if (!deviceType || !g_variant_is_of_type(deviceType, G_VARIANT_TYPE_UINT32)) {
+ return false;
+ }
+
+ // http://projects.gnome.org/NetworkManager/developers/api/07/spec-07.html
+ // Refer to NM_DEVICE_TYPE_WIFI under NM_DEVICE_TYPE.
+ const uint32_t NM_DEVICE_TYPE_WIFI = 2;
+ if (g_variant_get_uint32(deviceType) != NM_DEVICE_TYPE_WIFI) {
+ // Don't probe non-wifi devices
+ return true;
+ }
+
+ proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.NetworkManager", aDevicePath,
+ "org.freedesktop.NetworkManager.Device.Wireless", nullptr, nullptr));
+ if (!proxy) {
+ return false;
+ }
+
+ RefPtr<GVariant> points =
+ dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "AccessPoints"));
+ if (!points ||
+ !g_variant_is_of_type(points, G_VARIANT_TYPE_OBJECT_PATH_ARRAY)) {
+ return false;
+ }
+
+ gsize num = g_variant_n_children(points);
+ for (gsize i = 0; i < num; i++) {
+ const char* ap =
+ g_variant_get_string(g_variant_get_child_value(points, i), nullptr);
+ if (!ap || !AddAPProperties(ap, aAccessPoints)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool WifiScannerImpl::AddAPProperties(const char* aApPath,
+ AccessPointArray& aAccessPoints) {
+ RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.NetworkManager", aApPath,
+ "org.freedesktop.NetworkManager.AccessPoint", nullptr, nullptr));
+ if (!proxy) {
+ return false;
+ }
+
+ RefPtr<nsWifiAccessPoint> ap = new nsWifiAccessPoint();
+
+ RefPtr<GVariant> ssid =
+ dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Ssid"));
+ if (!ssid || !g_variant_is_of_type(ssid, G_VARIANT_TYPE_BYTESTRING)) {
+ return false;
+ }
+ const uint32_t MAX_SSID_LEN = 32;
+ gsize len = 0;
+ const char* ssidString = static_cast<const char*>(
+ g_variant_get_fixed_array(ssid, &len, sizeof(guint8)));
+ if (ssidString && len && len <= MAX_SSID_LEN) {
+ ap->setSSID(ssidString, len);
+ }
+
+ RefPtr<GVariant> hwAddress =
+ dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "HwAddress"));
+ if (!hwAddress || !g_variant_is_of_type(hwAddress, G_VARIANT_TYPE_STRING)) {
+ return false;
+ }
+ GUniquePtr<gchar> address(g_variant_dup_string(hwAddress, nullptr));
+ SetMac(address.get(), ap);
+
+ RefPtr<GVariant> st =
+ dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Strength"));
+ if (!st || !g_variant_is_of_type(st, G_VARIANT_TYPE_BYTE)) {
+ return false;
+ }
+
+ uint8_t strength = g_variant_get_byte(st); // in %
+ int signal_strength = (strength / 2) - 100; // strength to dB
+ ap->setSignal(signal_strength);
+
+ aAccessPoints.AppendElement(ap);
+ return true;
+}
+
+bool WifiScannerImpl::SetMac(char* aHwAddress, nsWifiAccessPoint* aAp) {
+ // hwAddress format is XX:XX:XX:XX:XX:XX. Need to convert to XXXXXX format.
+ const uint32_t MAC_LEN = 6;
+ uint8_t macAddress[MAC_LEN];
+ char* savedPtr = nullptr;
+ char* token = strtok_r(aHwAddress, ":", &savedPtr);
+ for (unsigned char& macAddres : macAddress) {
+ if (!token) {
+ return false;
+ }
+ macAddres = strtoul(token, nullptr, 16);
+ token = strtok_r(nullptr, ":", &savedPtr);
+ }
+ aAp->setMac(macAddress);
+ return true;
+}
+
+} // namespace mozilla
diff --git a/netwerk/wifi/dbus/DbusWifiScanner.h b/netwerk/wifi/dbus/DbusWifiScanner.h
new file mode 100644
index 0000000000..70fccb0106
--- /dev/null
+++ b/netwerk/wifi/dbus/DbusWifiScanner.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSWIFIAPSCANNERDBUS_H_
+#define NSWIFIAPSCANNERDBUS_H_
+
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+class nsWifiAccessPoint;
+
+namespace mozilla {
+
+using AccessPointArray = nsTArray<RefPtr<nsIWifiAccessPoint>>;
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ explicit WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(AccessPointArray& accessPoints);
+
+ private:
+ bool AddDevice(const char* aDevicePath, AccessPointArray& aAccessPoints);
+ bool AddAPProperties(const char* aApPath, AccessPointArray& aAccessPoints);
+ bool SetMac(char* aHwAddress, nsWifiAccessPoint* aAp);
+};
+
+} // namespace mozilla
+
+#endif // NSWIFIAPSCANNERDBUS_H_
diff --git a/netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp b/netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp
new file mode 100644
index 0000000000..c2a27d5ee2
--- /dev/null
+++ b/netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Developed by J.R. Oldroyd <fbsd@opal.com>, December 2012.
+
+// For FreeBSD we use the getifaddrs(3) to obtain the list of interfaces
+// and then check for those with an 802.11 media type and able to return
+// a list of stations. This is similar to ifconfig(8).
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#ifdef __DragonFly__
+# include <netproto/802_11/ieee80211_ioctl.h>
+#else
+# include <net80211/ieee80211_ioctl.h>
+#endif
+
+#include <ifaddrs.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "FreeBsdWifiScanner.h"
+#include "nsWifiAccessPoint.h"
+#include "nsWifiMonitor.h"
+
+using namespace mozilla;
+
+#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
+
+WifiScannerImpl::WifiScannerImpl(){};
+WifiScannerImpl::~WifiScannerImpl(){};
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ // get list of interfaces
+ struct ifaddrs* ifal;
+ if (getifaddrs(&ifal) < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ accessPoints.Clear();
+
+ // loop through the interfaces
+ nsresult rv = NS_ERROR_FAILURE;
+ struct ifaddrs* ifa;
+ for (ifa = ifal; ifa; ifa = ifa->ifa_next) {
+ // limit to one interface per address
+ if (ifa->ifa_addr->sa_family != AF_LINK) {
+ continue;
+ }
+
+ // store interface name in socket structure
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name));
+ ifr.ifr_addr.sa_family = AF_LOCAL;
+
+ // open socket to interface
+ int s = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0);
+ if (s < 0) {
+ continue;
+ }
+
+ // clear interface media structure
+ struct ifmediareq ifmr;
+ memset(&ifmr, 0, sizeof(ifmr));
+ strncpy(ifmr.ifm_name, ifa->ifa_name, sizeof(ifmr.ifm_name));
+
+ // get interface media information
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+ close(s);
+ continue;
+ }
+
+ // check interface is a WiFi interface
+ if (IFM_TYPE(ifmr.ifm_active) != IFM_IEEE80211) {
+ close(s);
+ continue;
+ }
+
+ // perform WiFi scan
+ struct ieee80211req i802r;
+ char iscanbuf[32 * 1024];
+ memset(&i802r, 0, sizeof(i802r));
+ strncpy(i802r.i_name, ifa->ifa_name, sizeof(i802r.i_name));
+ i802r.i_type = IEEE80211_IOC_SCAN_RESULTS;
+ i802r.i_data = iscanbuf;
+ i802r.i_len = sizeof(iscanbuf);
+ if (ioctl(s, SIOCG80211, &i802r) < 0) {
+ close(s);
+ continue;
+ }
+
+ // close socket
+ close(s);
+
+ // loop through WiFi networks and build geoloc-lookup structure
+ char* vsr = (char*)i802r.i_data;
+ unsigned len = i802r.i_len;
+ while (len >= sizeof(struct ieee80211req_scan_result)) {
+ struct ieee80211req_scan_result* isr =
+ (struct ieee80211req_scan_result*)vsr;
+
+ // determine size of this entry
+ char* id;
+ int idlen;
+ if (isr->isr_meshid_len) {
+ id = vsr + isr->isr_ie_off + isr->isr_ssid_len;
+ idlen = isr->isr_meshid_len;
+ } else {
+ id = vsr + isr->isr_ie_off;
+ idlen = isr->isr_ssid_len;
+ }
+
+ // copy network data
+ char ssid[IEEE80211_NWID_LEN + 1];
+ strncpy(ssid, id, idlen);
+ ssid[idlen] = '\0';
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ ap->setSSID(ssid, strlen(ssid));
+ ap->setMac(isr->isr_bssid);
+ ap->setSignal(isr->isr_rssi);
+ accessPoints.AppendElement(ap);
+ rv = NS_OK;
+
+ // log the data
+ LOG(
+ ("FreeBSD access point: "
+ "SSID: %s, MAC: %02x-%02x-%02x-%02x-%02x-%02x, "
+ "Strength: %d, Channel: %dMHz\n",
+ ssid, isr->isr_bssid[0], isr->isr_bssid[1], isr->isr_bssid[2],
+ isr->isr_bssid[3], isr->isr_bssid[4], isr->isr_bssid[5],
+ isr->isr_rssi, isr->isr_freq));
+
+ // increment pointers
+ len -= isr->isr_len;
+ vsr += isr->isr_len;
+ }
+ }
+
+ freeifaddrs(ifal);
+
+ return rv;
+}
diff --git a/netwerk/wifi/freebsd/FreeBsdWifiScanner.h b/netwerk/wifi/freebsd/FreeBsdWifiScanner.h
new file mode 100644
index 0000000000..1bd5bf0eb5
--- /dev/null
+++ b/netwerk/wifi/freebsd/FreeBsdWifiScanner.h
@@ -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/. */
+
+#pragma once
+
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/gtest/TestWifiMonitor.cpp b/netwerk/wifi/gtest/TestWifiMonitor.cpp
new file mode 100644
index 0000000000..f12c5c78ef
--- /dev/null
+++ b/netwerk/wifi/gtest/TestWifiMonitor.cpp
@@ -0,0 +1,404 @@
+/* -*- 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 "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "nsIWifiListener.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+#include "WifiScanner.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+#if defined(XP_WIN) && defined(_M_IX86)
+# include <objbase.h> // STDMETHODCALLTYPE
+#endif
+
+// Tests that wifi scanning happens on the right network change events,
+// and that wifi-scan polling is operable on mobile networks.
+
+using ::testing::AtLeast;
+using ::testing::Cardinality;
+using ::testing::Exactly;
+using ::testing::MockFunction;
+using ::testing::Sequence;
+
+static mozilla::LazyLogModule gLog("TestWifiMonitor");
+#define LOGI(x) MOZ_LOG(gLog, mozilla::LogLevel::Info, x)
+#define LOGD(x) MOZ_LOG(gLog, mozilla::LogLevel::Debug, x)
+
+namespace mozilla {
+
+// Timeout if update not received from wifi scanner thread.
+static const uint32_t kWifiScanTestResultTimeoutMs = 100;
+static const uint32_t kTestWifiScanIntervalMs = 10;
+
+// ID counter, used to make sure each call to GetAccessPointsFromWLAN
+// returns "new" access points.
+static int gCurrentId = 0;
+
+static uint32_t gNumScanResults = 0;
+
+struct LinkTypeMobility {
+ const char* mLinkType;
+ bool mIsMobile;
+};
+
+class MockWifiScanner : public WifiScanner {
+ public:
+ MOCK_METHOD(nsresult, GetAccessPointsFromWLAN,
+ (nsTArray<RefPtr<nsIWifiAccessPoint>> & aAccessPoints),
+ (override));
+};
+
+class MockWifiListener : public nsIWifiListener {
+ virtual ~MockWifiListener() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+#if defined(XP_WIN) && defined(_M_IX86)
+ MOCK_METHOD(nsresult, OnChange,
+ (const nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints),
+ (override, Calltype(STDMETHODCALLTYPE)));
+ MOCK_METHOD(nsresult, OnError, (nsresult error),
+ (override, Calltype(STDMETHODCALLTYPE)));
+#else
+ MOCK_METHOD(nsresult, OnChange,
+ (const nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints),
+ (override));
+ MOCK_METHOD(nsresult, OnError, (nsresult error), (override));
+#endif
+};
+
+NS_IMPL_ISUPPORTS(MockWifiListener, nsIWifiListener)
+
+class TestWifiMonitor : public ::testing::Test {
+ public:
+ TestWifiMonitor() {
+ mObs = mozilla::services::GetObserverService();
+ MOZ_ASSERT(mObs);
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ EXPECT_TRUE(nls);
+ rv = nls->GetLinkType(&mOrigLinkType);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ rv = nls->GetIsLinkUp(&mOrigIsLinkUp);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ rv = nls->GetLinkStatusKnown(&mOrigLinkStatusKnown);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ // Reduce wifi-polling interval. 0 turns polling off.
+ mOldScanInterval = Preferences::GetInt(WIFI_SCAN_INTERVAL_MS_PREF);
+ Preferences::SetInt(WIFI_SCAN_INTERVAL_MS_PREF, kTestWifiScanIntervalMs);
+ }
+
+ ~TestWifiMonitor() {
+ Preferences::SetInt(WIFI_SCAN_INTERVAL_MS_PREF, mOldScanInterval);
+
+ // Restore network link type
+ const char* linkType = nullptr;
+ switch (mOrigLinkType) {
+ case nsINetworkLinkService::LINK_TYPE_UNKNOWN:
+ linkType = NS_NETWORK_LINK_TYPE_UNKNOWN;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_ETHERNET:
+ linkType = NS_NETWORK_LINK_TYPE_ETHERNET;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_USB:
+ linkType = NS_NETWORK_LINK_TYPE_USB;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_WIFI:
+ linkType = NS_NETWORK_LINK_TYPE_WIFI;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_MOBILE:
+ linkType = NS_NETWORK_LINK_TYPE_MOBILE;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_WIMAX:
+ linkType = NS_NETWORK_LINK_TYPE_WIMAX;
+ break;
+ }
+ EXPECT_TRUE(linkType);
+ mObs->NotifyObservers(nullptr, NS_NETWORK_LINK_TYPE_TOPIC,
+ NS_ConvertUTF8toUTF16(linkType).get());
+
+ const char* linkStatus = nullptr;
+ if (mOrigLinkStatusKnown) {
+ if (mOrigIsLinkUp) {
+ linkStatus = NS_NETWORK_LINK_DATA_UP;
+ } else {
+ linkStatus = NS_NETWORK_LINK_DATA_DOWN;
+ }
+ } else {
+ linkStatus = NS_NETWORK_LINK_DATA_UNKNOWN;
+ }
+ EXPECT_TRUE(linkStatus);
+ mObs->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
+ NS_ConvertUTF8toUTF16(linkStatus).get());
+ }
+
+ protected:
+ bool WaitForScanResults() {
+ // Wait for kWifiScanTestResultTimeoutMs to allow async calls to complete.
+ bool timedout = false;
+ RefPtr<CancelableRunnable> timer = NS_NewCancelableRunnableFunction(
+ "WaitForScanResults Timeout", [&] { timedout = true; });
+ NS_DelayedDispatchToCurrentThread(do_AddRef(timer),
+ kWifiScanTestResultTimeoutMs);
+
+ mozilla::SpinEventLoopUntil("TestWifiMonitor::WaitForScanResults"_ns,
+ [&]() { return timedout; });
+
+ timer->Cancel();
+ return true;
+ }
+
+ void CreateObjects() {
+ mWifiMonitor = MakeRefPtr<nsWifiMonitor>(MakeUnique<MockWifiScanner>());
+ EXPECT_TRUE(!mWifiMonitor->IsPolling());
+
+ // Start with ETHERNET network type to avoid always polling at test start.
+ mObs->NotifyObservers(
+ nullptr, NS_NETWORK_LINK_TYPE_TOPIC,
+ NS_ConvertUTF8toUTF16(NS_NETWORK_LINK_TYPE_ETHERNET).get());
+
+ mWifiListener = new MockWifiListener();
+ LOGI(("monitor: %p | scanner: %p | listener: %p", mWifiMonitor.get(),
+ mWifiMonitor->mWifiScanner.get(), mWifiListener.get()));
+ }
+
+ void DestroyObjects() {
+ ::testing::Mock::VerifyAndClearExpectations(
+ mWifiMonitor->mWifiScanner.get());
+ ::testing::Mock::VerifyAndClearExpectations(mWifiListener.get());
+
+ // Manually disconnect observers so that the monitor can be destroyed.
+ // In the browser, this would be done on xpcom-shutdown but that is sent
+ // after the tests run, which is too late to avoid a gtest memory-leak
+ // error.
+ mWifiMonitor->Close();
+
+ mWifiMonitor = nullptr;
+ mWifiListener = nullptr;
+ gCurrentId = 0;
+ }
+
+ void StartWatching(bool aRequestPolling) {
+ LOGD(("StartWatching | aRequestPolling: %s | nScanResults: %u",
+ aRequestPolling ? "true" : "false", gNumScanResults));
+ EXPECT_TRUE(NS_SUCCEEDED(
+ mWifiMonitor->StartWatching(mWifiListener, aRequestPolling)));
+ WaitForScanResults();
+ }
+
+ void NotifyOfNetworkEvent(const char* aTopic, const char16_t* aData) {
+ LOGD(("NotifyOfNetworkEvent: (%s, %s) | nScanResults: %u", aTopic,
+ NS_ConvertUTF16toUTF8(aData).get(), gNumScanResults));
+ EXPECT_TRUE(NS_SUCCEEDED(mObs->NotifyObservers(nullptr, aTopic, aData)));
+ WaitForScanResults();
+ }
+
+ void StopWatching() {
+ LOGD(("StopWatching | nScanResults: %u", gNumScanResults));
+ EXPECT_TRUE(NS_SUCCEEDED(mWifiMonitor->StopWatching(mWifiListener)));
+ WaitForScanResults();
+ }
+
+ struct MockCallSequences {
+ Sequence mGetAccessPointsSeq;
+ Sequence mOnChangeSeq;
+ Sequence mOnErrorSeq;
+ };
+
+ void AddMockObjectChecks(const Cardinality& aScanCardinality,
+ MockCallSequences& aSeqs) {
+ // Only add WillRepeatedly handler if scans is more than 0, to avoid a
+ // VERY LOUD gtest warning.
+ if (aScanCardinality.IsSaturatedByCallCount(0)) {
+ EXPECT_CALL(
+ *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()),
+ GetAccessPointsFromWLAN)
+ .Times(aScanCardinality)
+ .InSequence(aSeqs.mGetAccessPointsSeq);
+
+ EXPECT_CALL(*mWifiListener, OnChange)
+ .Times(aScanCardinality)
+ .InSequence(aSeqs.mOnChangeSeq);
+ } else {
+ EXPECT_CALL(
+ *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()),
+ GetAccessPointsFromWLAN)
+ .Times(aScanCardinality)
+ .InSequence(aSeqs.mGetAccessPointsSeq)
+ .WillRepeatedly(
+ [](nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints) {
+ EXPECT_TRUE(!NS_IsMainThread());
+ EXPECT_TRUE(aAccessPoints.IsEmpty());
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ // Signal will be unique so we won't match the prior access
+ // point list.
+ ap->mSignal = gCurrentId++;
+ aAccessPoints.AppendElement(RefPtr(ap));
+ return NS_OK;
+ });
+
+ EXPECT_CALL(*mWifiListener, OnChange)
+ .Times(aScanCardinality)
+ .InSequence(aSeqs.mOnChangeSeq)
+ .WillRepeatedly(
+ [](const nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints) {
+ EXPECT_TRUE(NS_IsMainThread());
+ EXPECT_EQ(aAccessPoints.Length(), 1u);
+ ++gNumScanResults;
+ return NS_OK;
+ });
+ }
+
+ EXPECT_CALL(*mWifiListener, OnError).Times(0).InSequence(aSeqs.mOnErrorSeq);
+ }
+
+ void AddStartWatchingCheck(bool aShouldPoll, MockCallSequences& aSeqs) {
+ AddMockObjectChecks(aShouldPoll ? AtLeast(1) : Exactly(1), aSeqs);
+ }
+
+ void AddNetworkEventCheck(const Cardinality& aScanCardinality,
+ MockCallSequences& aSeqs) {
+ AddMockObjectChecks(aScanCardinality, aSeqs);
+ }
+
+ void AddStopWatchingCheck(bool aShouldPoll, MockCallSequences& aSeqs) {
+ // When polling, we may get stray scan + OnChange calls asynchronously
+ // before stopping. We may also get scan calls after stopping.
+ // We check that the calls actually stopped in ConfirmStoppedCheck.
+ AddMockObjectChecks(aShouldPoll ? AtLeast(0) : Exactly(0), aSeqs);
+ }
+
+ void AddConfirmStoppedCheck(MockCallSequences& aSeqs) {
+ AddMockObjectChecks(Exactly(0), aSeqs);
+ }
+
+ // A Checkpoint is just a mocked function taking an int. It will serve
+ // as a temporal barrier that requires all expectations before it to be
+ // satisfied and retired (meaning they won't be used in matches anymore).
+ class Checkpoint {
+ public:
+ void Check(uint32_t aId, MockCallSequences& aSeqs) {
+ EXPECT_CALL(mFn, Call(aId))
+ .InSequence(aSeqs.mGetAccessPointsSeq, aSeqs.mOnChangeSeq,
+ aSeqs.mOnErrorSeq);
+ }
+
+ void Reach(uint32_t aId) { mFn.Call(aId); }
+
+ private:
+ MockFunction<void(uint32_t)> mFn;
+ };
+
+ // A single test is StartWatching, NotifyOfNetworkEvent, and StopWatching.
+ void RunSingleTest(bool aRequestPolling, bool aShouldPoll,
+ const Cardinality& aScanCardinality, const char* aTopic,
+ const char16_t* aData) {
+ LOGI(("RunSingleTest: <%s, %s> | requestPolling: %s | shouldPoll: %s",
+ aTopic, NS_ConvertUTF16toUTF8(aData).get(),
+ aRequestPolling ? "true" : "false", aShouldPoll ? "true" : "false"));
+ MOZ_ASSERT(aShouldPoll || !aRequestPolling);
+
+ CreateObjects();
+
+ Checkpoint checkpoint;
+ {
+ // gmock expectations are asynchronous by default. Sequence objects
+ // are used here to require that expectations occur in the specified
+ // (partial) order.
+ MockCallSequences seqs;
+
+ AddStartWatchingCheck(aShouldPoll, seqs);
+ checkpoint.Check(1, seqs);
+
+ AddNetworkEventCheck(aScanCardinality, seqs);
+ checkpoint.Check(2, seqs);
+
+ AddStopWatchingCheck(aShouldPoll, seqs);
+ checkpoint.Check(3, seqs);
+
+ AddConfirmStoppedCheck(seqs);
+ }
+
+ // Now run the test on the mock objects.
+ StartWatching(aRequestPolling);
+ checkpoint.Reach(1);
+ EXPECT_EQ(mWifiMonitor->IsPolling(), aRequestPolling);
+
+ NotifyOfNetworkEvent(aTopic, aData);
+ checkpoint.Reach(2);
+ EXPECT_EQ(mWifiMonitor->IsPolling(), aShouldPoll);
+
+ StopWatching();
+ checkpoint.Reach(3);
+ EXPECT_TRUE(!mWifiMonitor->IsPolling());
+
+ // Wait for extraneous calls as a way to confirm it has stopped.
+ WaitForScanResults();
+
+ DestroyObjects();
+ }
+
+ void CheckMessages(bool aRequestPolling) {
+ // NS_NETWORK_LINK_TOPIC messages should cause a new scan.
+ const char* kLinkTopicDatas[] = {
+ NS_NETWORK_LINK_DATA_UP, NS_NETWORK_LINK_DATA_DOWN,
+ NS_NETWORK_LINK_DATA_CHANGED, NS_NETWORK_LINK_DATA_UNKNOWN};
+
+ for (const auto& data : kLinkTopicDatas) {
+ RunSingleTest(aRequestPolling, aRequestPolling,
+ aRequestPolling ? AtLeast(2) : Exactly(1),
+ NS_NETWORK_LINK_TOPIC, NS_ConvertUTF8toUTF16(data).get());
+ }
+
+ // NS_NETWORK_LINK_TYPE_TOPIC should cause wifi scan polling iff the topic
+ // says we have switched to a mobile network (LINK_TYPE_MOBILE or
+ // LINK_TYPE_WIMAX) or we are polling the wifi-scanner (aShouldPoll).
+ const LinkTypeMobility kLinkTypeTopicDatas[] = {
+ {NS_NETWORK_LINK_TYPE_UNKNOWN, true /* mIsMobile */},
+ {NS_NETWORK_LINK_TYPE_ETHERNET, false},
+ {NS_NETWORK_LINK_TYPE_USB, false},
+ {NS_NETWORK_LINK_TYPE_WIFI, false},
+ {NS_NETWORK_LINK_TYPE_WIMAX, true},
+ {NS_NETWORK_LINK_TYPE_MOBILE, true}};
+
+ for (const auto& data : kLinkTypeTopicDatas) {
+ bool shouldPoll = (aRequestPolling || data.mIsMobile);
+ RunSingleTest(aRequestPolling, shouldPoll,
+ shouldPoll ? AtLeast(2) : Exactly(0),
+ NS_NETWORK_LINK_TYPE_TOPIC,
+ NS_ConvertUTF8toUTF16(data.mLinkType).get());
+ }
+ }
+
+ RefPtr<nsWifiMonitor> mWifiMonitor;
+ nsCOMPtr<nsIObserverService> mObs;
+
+ RefPtr<MockWifiListener> mWifiListener;
+
+ int mOldScanInterval;
+ uint32_t mOrigLinkType = 0;
+ bool mOrigIsLinkUp = false;
+ bool mOrigLinkStatusKnown = false;
+};
+
+TEST_F(TestWifiMonitor, WifiScanNoPolling) { CheckMessages(false); }
+
+TEST_F(TestWifiMonitor, WifiScanPolling) { CheckMessages(true); }
+
+} // namespace mozilla
diff --git a/netwerk/wifi/gtest/moz.build b/netwerk/wifi/gtest/moz.build
new file mode 100644
index 0000000000..40f6f5b24d
--- /dev/null
+++ b/netwerk/wifi/gtest/moz.build
@@ -0,0 +1,13 @@
+# -*- 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/.
+
+LOCAL_INCLUDES += ["/netwerk/wifi"]
+
+UNIFIED_SOURCES += [
+ # "TestWifiMonitor.cpp", # see bug 1833020
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/netwerk/wifi/mac/MacWifiScanner.h b/netwerk/wifi/mac/MacWifiScanner.h
new file mode 100644
index 0000000000..43961da8fa
--- /dev/null
+++ b/netwerk/wifi/mac/MacWifiScanner.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+#include "WifiScanner.h"
+
+class nsWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/mac/MacWifiScanner.mm b/netwerk/wifi/mac/MacWifiScanner.mm
new file mode 100644
index 0000000000..9fdccf6c49
--- /dev/null
+++ b/netwerk/wifi/mac/MacWifiScanner.mm
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+#import <CoreWLAN/CoreWLAN.h>
+
+#include <dlfcn.h>
+#include <unistd.h>
+
+#include <objc/objc.h>
+#include <objc/objc-runtime.h>
+
+#include "nsObjCExceptions.h"
+#include "nsCOMArray.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+#include "MacWifiScanner.h"
+
+namespace mozilla {
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ accessPoints.Clear();
+
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ @try {
+ NSBundle* bundle = [[[NSBundle alloc]
+ initWithPath:@"/System/Library/Frameworks/CoreWLAN.framework"]
+ autorelease];
+ if (!bundle) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Class CWI_class = [bundle classNamed:@"CWInterface"];
+ if (!CWI_class) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ id scanResult = [[CWI_class interface] scanForNetworksWithSSID:nil
+ error:nil];
+ if (!scanResult) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NSArray* scan = [NSMutableArray arrayWithArray:scanResult];
+ NSEnumerator* enumerator = [scan objectEnumerator];
+
+ while (id anObject = [enumerator nextObject]) {
+ auto* ap = new nsWifiAccessPoint();
+ if (!ap) {
+ [pool release];
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // [CWInterface bssidData] is deprecated on OS X 10.7 and up. Which is
+ // is a pain, so we'll use it for as long as it's available.
+ unsigned char macData[6] = {0};
+ if ([anObject respondsToSelector:@selector(bssidData)]) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-method-access"
+ NSData* data = [anObject bssidData];
+#pragma clang diagnostic pop
+ if (data) {
+ memcpy(macData, [data bytes], 6);
+ }
+ } else {
+ // [CWInterface bssid] returns a string formatted "00:00:00:00:00:00".
+ NSString* macString = [anObject bssid];
+ if (macString && ([macString length] == 17)) {
+ for (NSUInteger i = 0; i < 6; ++i) {
+ NSString* part =
+ [macString substringWithRange:NSMakeRange(i * 3, 2)];
+ NSScanner* scanner = [NSScanner scannerWithString:part];
+ unsigned int data = 0;
+ if (![scanner scanHexInt:&data]) {
+ data = 0;
+ }
+ macData[i] = (unsigned char)data;
+ }
+ }
+ }
+
+ int signal = (int)((NSInteger)[anObject rssiValue]);
+ ap->setMac(macData);
+ ap->setSignal(signal);
+ ap->setSSID([[anObject ssid] UTF8String], 32);
+
+ accessPoints.AppendElement(ap);
+ }
+ } @catch (NSException*) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ [pool release];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_NOT_AVAILABLE);
+}
+
+} // namespace mozilla
diff --git a/netwerk/wifi/mac/Wifi.h b/netwerk/wifi/mac/Wifi.h
new file mode 100644
index 0000000000..3a177f9643
--- /dev/null
+++ b/netwerk/wifi/mac/Wifi.h
@@ -0,0 +1,119 @@
+// Copyright 2008, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The contents of this file are taken from Apple80211.h from the iStumbler
+// project (http://www.istumbler.net). This project is released under the BSD
+// license with the following restrictions.
+//
+// Copyright (c) 02006, Alf Watt (alf@istumbler.net). All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// * Neither the name of iStumbler nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This is the reverse engineered header for the Apple80211 private framework.
+// The framework can be found at
+// /System/Library/PrivateFrameworks/Apple80211.framework.
+
+#ifndef GEARS_GEOLOCATION_OSX_WIFI_H__
+#define GEARS_GEOLOCATION_OSX_WIFI_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+
+extern "C" {
+
+typedef SInt32 WIErr;
+
+// A WirelessContext should be created using WirelessAttach
+// before any other Wireless functions are called. WirelessDetach
+// is used to dispose of a WirelessContext.
+typedef struct __WirelessContext* WirelessContextPtr;
+
+// WirelessAttach
+//
+// This should be called before all other wireless functions.
+typedef WIErr (*WirelessAttachFunction)(WirelessContextPtr* outContext,
+ const UInt32);
+
+// WirelessDetach
+//
+// This should be called after all other wireless functions.
+typedef WIErr (*WirelessDetachFunction)(WirelessContextPtr inContext);
+
+typedef UInt16 WINetworkInfoFlags;
+
+struct WirelessNetworkInfo {
+ UInt16 channel; // Channel for the network.
+ SInt16 noise; // Noise for the network. 0 for Adhoc.
+ SInt16 signal; // Signal strength of the network. 0 for Adhoc.
+ UInt8 macAddress[6]; // MAC address of the wireless access point.
+ UInt16 beaconInterval; // Beacon interval in milliseconds
+ WINetworkInfoFlags flags; // Flags for the network
+ UInt16 nameLen;
+ SInt8 name[32];
+};
+
+// WirelessScanSplit
+//
+// WirelessScanSplit scans for available wireless networks. It will allocate 2
+// CFArrays to store a list of managed and adhoc networks. The arrays hold
+// CFData objects which contain WirelessNetworkInfo structures.
+//
+// Note: An adhoc network created on the computer the scan is running on will
+// not be found. WirelessGetInfo can be used to find info about a local adhoc
+// network.
+//
+// If stripDups != 0 only one bases tation for each SSID will be returned.
+typedef WIErr (*WirelessScanSplitFunction)(WirelessContextPtr inContext,
+ CFArrayRef* apList,
+ CFArrayRef* adhocList,
+ const UInt32 stripDups);
+
+} // extern "C"
+
+#endif // GEARS_GEOLOCATION_OSX_WIFI_H__
diff --git a/netwerk/wifi/moz.build b/netwerk/wifi/moz.build
new file mode 100644
index 0000000000..75a9384c09
--- /dev/null
+++ b/netwerk/wifi/moz.build
@@ -0,0 +1,63 @@
+# -*- 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 += [
+ "nsIWifiAccessPoint.idl",
+ "nsIWifiListener.idl",
+ "nsIWifiMonitor.idl",
+]
+
+XPIDL_MODULE = "necko_wifi"
+
+UNIFIED_SOURCES += [
+ "nsWifiAccessPoint.cpp",
+ "nsWifiMonitor.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "Darwin":
+ SOURCES += [
+ "mac/MacWifiScanner.mm",
+ ]
+ LOCAL_INCLUDES += [
+ "mac",
+ ]
+elif CONFIG["OS_ARCH"] in ("DragonFly", "FreeBSD"):
+ UNIFIED_SOURCES += [
+ "freebsd/FreeBsdWifiScanner.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "freebsd",
+ ]
+elif CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "win/WinWifiScanner.cpp",
+ "win/WlanLibrary.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "win",
+ ]
+elif CONFIG["OS_ARCH"] == "SunOS":
+ CXXFLAGS += CONFIG["GLIB_CFLAGS"]
+ UNIFIED_SOURCES += [
+ "solaris/SolarisWifiScanner.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "solaris",
+ ]
+elif CONFIG["NECKO_WIFI_DBUS"]:
+ UNIFIED_SOURCES += [
+ "dbus/DbusWifiScanner.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "dbus",
+ ]
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+TEST_DIRS += ["gtest"]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/wifi/nsIWifiAccessPoint.idl b/netwerk/wifi/nsIWifiAccessPoint.idl
new file mode 100644
index 0000000000..c98b42edc4
--- /dev/null
+++ b/netwerk/wifi/nsIWifiAccessPoint.idl
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(E28E614F-8F86-44FF-BCF5-5F18225834A0)]
+interface nsIWifiAccessPoint : nsISupports
+{
+
+ /*
+ * The mac address of the WiFi node. The format of this string is:
+ * XX-XX-XX-XX-XX-XX
+ */
+
+ readonly attribute ACString mac;
+
+ /*
+ * Public name of a wireless network. The charset of this string is ASCII.
+ * This string will be null if not available.
+ *
+ * Note that this is a conversion of the SSID which makes it "displayable".
+ * for any comparisons, you want to use the Raw SSID.
+ */
+
+ readonly attribute AString ssid;
+
+ /*
+ * Public name of a wireless network. These are the bytes that are read off
+ * of the network, may contain nulls, and generally shouldn't be displayed to
+ * the user.
+ *
+ */
+
+ readonly attribute ACString rawSSID;
+
+ /*
+ * Current signal strength measured in dBm.
+ */
+ readonly attribute long signal;
+};
diff --git a/netwerk/wifi/nsIWifiListener.idl b/netwerk/wifi/nsIWifiListener.idl
new file mode 100644
index 0000000000..49024f405f
--- /dev/null
+++ b/netwerk/wifi/nsIWifiListener.idl
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsIWifiAccessPoint;
+
+[scriptable, uuid(BCD4BEDE-F4A5-4A62-9071-D7A60174E376)]
+interface nsIWifiListener : nsISupports
+{
+ /*
+ * Called when the list of access points changes.
+ *
+ * @param accessPoints An array of nsIWifiAccessPoint representing all
+ * access points in view.
+ */
+
+ void onChange(in Array<nsIWifiAccessPoint> accessPoints);
+
+ /*
+ * Called when there is a problem with listening to wifi
+ *
+ * @param error the error which caused this event. The
+ * error values will be nsresult codes.
+ */
+
+ void onError(in nsresult error);
+};
diff --git a/netwerk/wifi/nsIWifiMonitor.idl b/netwerk/wifi/nsIWifiMonitor.idl
new file mode 100644
index 0000000000..feda68f77e
--- /dev/null
+++ b/netwerk/wifi/nsIWifiMonitor.idl
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIWifiListener;
+
+[scriptable, uuid(F289701E-D9AF-4685-BC2F-E4226FF7C018)]
+interface nsIWifiMonitor : nsISupports
+{
+ /*
+ * startWatching
+ * aListener will be called once, then each time the list of wifi access
+ * points change. The wifi access point list will be updated when our
+ * network changes, or on a regular interval if we are on a mobile network.
+ * If aForcePolling is true then we will always poll as long as this
+ * listener is watching.
+ */
+ void startWatching(in nsIWifiListener aListener, in boolean aForcePolling);
+
+ /*
+ * stopWatching
+ * cancels all notifications to the |aListener|.
+ */
+ void stopWatching(in nsIWifiListener aListener);
+};
diff --git a/netwerk/wifi/nsWifiAccessPoint.cpp b/netwerk/wifi/nsWifiAccessPoint.cpp
new file mode 100644
index 0000000000..9803553994
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.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 "nsWifiAccessPoint.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+
+extern mozilla::LazyLogModule gWifiMonitorLog;
+#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsWifiAccessPoint, nsIWifiAccessPoint)
+
+nsWifiAccessPoint::nsWifiAccessPoint() {
+ // make sure these are null terminated (because we are paranoid)
+ mMac[0] = '\0';
+ mSsid[0] = '\0';
+ mSsidLen = 0;
+ mSignal = -1000;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetMac(nsACString& aMac) {
+ aMac.Assign(mMac);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetSsid(nsAString& aSsid) {
+ // just assign and embedded nulls will truncate resulting
+ // in a displayable string.
+ aSsid.AssignASCII(mSsid);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetRawSSID(nsACString& aRawSsid) {
+ aRawSsid.Assign(mSsid, mSsidLen); // SSIDs are 32 chars long
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetSignal(int32_t* aSignal) {
+ NS_ENSURE_ARG(aSignal);
+ *aSignal = mSignal;
+ return NS_OK;
+}
+
+int nsWifiAccessPoint::Compare(const nsWifiAccessPoint& o) const {
+ int ret = strcmp(mMac, o.mMac);
+ if (ret) {
+ return ret;
+ }
+ if (mSsidLen != o.mSsidLen) {
+ return (mSsidLen < o.mSsidLen) ? -1 : 1;
+ }
+ ret = strncmp(mSsid, o.mSsid, mSsidLen);
+ if (ret) {
+ return ret;
+ }
+ if (mSignal == o.mSignal) {
+ return 0;
+ }
+ return (mSignal < o.mSignal) ? -1 : 1;
+}
+
+bool nsWifiAccessPoint::operator==(const nsWifiAccessPoint& o) const {
+ LOG(("nsWifiAccessPoint comparing %s->%s | %s->%s | %d -> %d\n", mSsid,
+ o.mSsid, mMac, o.mMac, mSignal, o.mSignal));
+ return Compare(o) == 0;
+}
diff --git a/netwerk/wifi/nsWifiAccessPoint.h b/netwerk/wifi/nsWifiAccessPoint.h
new file mode 100644
index 0000000000..3f83b2c0c0
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.h
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsWifiAccessPoint__
+#define __nsWifiAccessPoint__
+
+#include <algorithm>
+#include "nsWifiMonitor.h"
+#include "nsIWifiAccessPoint.h"
+
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "mozilla/ArrayUtils.h" // ArrayLength
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+
+class nsWifiAccessPoint final : public nsIWifiAccessPoint {
+ ~nsWifiAccessPoint() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWIFIACCESSPOINT
+
+ nsWifiAccessPoint();
+
+ char mMac[18]{0};
+ int mSignal;
+ char mSsid[33]{0};
+ int mSsidLen;
+
+ void setSignal(int signal) { mSignal = signal; }
+
+ void setMacRaw(const char* aString) {
+ memcpy(mMac, aString, mozilla::ArrayLength(mMac));
+ }
+
+ void setMac(const unsigned char mac_as_int[6]) {
+ // mac_as_int is big-endian. Write in byte chunks.
+ // Format is XX-XX-XX-XX-XX-XX.
+
+ const unsigned char holder[6] = {0};
+ if (!mac_as_int) {
+ mac_as_int = holder;
+ }
+
+ static const char* kMacFormatString = ("%02x-%02x-%02x-%02x-%02x-%02x");
+
+ SprintfLiteral(mMac, kMacFormatString, mac_as_int[0], mac_as_int[1],
+ mac_as_int[2], mac_as_int[3], mac_as_int[4], mac_as_int[5]);
+
+ mMac[17] = 0;
+ }
+
+ void setSSIDRaw(const char* aSSID, size_t len) {
+ mSsidLen = std::min(len, mozilla::ArrayLength(mSsid));
+ memcpy(mSsid, aSSID, mSsidLen);
+ }
+
+ void setSSID(const char* aSSID, unsigned long len) {
+ if (aSSID && (len < sizeof(mSsid))) {
+ strncpy(mSsid, aSSID, len);
+ mSsid[len] = 0;
+ mSsidLen = len;
+ } else {
+ mSsid[0] = 0;
+ mSsidLen = 0;
+ }
+ }
+
+ // 3-value compare for nsWifiAccessPoint
+ int Compare(const nsWifiAccessPoint& o) const;
+
+ bool operator==(const nsWifiAccessPoint& o) const;
+ bool operator!=(const nsWifiAccessPoint& o) const { return !(*this == o); }
+};
+
+#endif
diff --git a/netwerk/wifi/nsWifiMonitor.cpp b/netwerk/wifi/nsWifiMonitor.cpp
new file mode 100644
index 0000000000..5a7be15d16
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.cpp
@@ -0,0 +1,393 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMCID.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+#include "nsINetworkLinkService.h"
+#include "nsQueryObject.h"
+#include "nsNetCID.h"
+
+#include "nsComponentManagerUtils.h"
+#include "mozilla/DelayedRunnable.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Services.h"
+
+#if defined(XP_WIN)
+# include "WinWifiScanner.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include "MacWifiScanner.h"
+#endif
+
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+# include "FreeBsdWifiScanner.h"
+#endif
+
+#if defined(XP_SOLARIS)
+# include "SolarisWifiScanner.h"
+#endif
+
+#if defined(NECKO_WIFI_DBUS)
+# include "DbusWifiScanner.h"
+#endif
+
+using namespace mozilla;
+
+LazyLogModule gWifiMonitorLog("WifiMonitor");
+#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsWifiMonitor, nsIObserver, nsIWifiMonitor)
+
+// Main thread only.
+static uint64_t sNextPollingIndex = 1;
+
+static uint64_t NextPollingIndex() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++sNextPollingIndex;
+
+ // Any non-zero value is valid and we don't care about overflow beyond
+ // that we never want the index to be zero.
+ if (sNextPollingIndex == 0) {
+ ++sNextPollingIndex;
+ }
+ return sNextPollingIndex;
+}
+
+// Should we poll wifi or just check it when our network changes?
+// We poll when we are on a network where the wifi environment
+// could reasonably be expected to change much -- so, on mobile.
+static bool ShouldPollForNetworkType(const char16_t* aLinkType) {
+ auto linkTypeU8 = NS_ConvertUTF16toUTF8(aLinkType);
+ return linkTypeU8 == NS_NETWORK_LINK_TYPE_WIMAX ||
+ linkTypeU8 == NS_NETWORK_LINK_TYPE_MOBILE ||
+ linkTypeU8 == NS_NETWORK_LINK_TYPE_UNKNOWN;
+}
+
+// Enum value version.
+static bool ShouldPollForNetworkType(uint32_t aLinkType) {
+ return aLinkType == nsINetworkLinkService::LINK_TYPE_WIMAX ||
+ aLinkType == nsINetworkLinkService::LINK_TYPE_MOBILE ||
+ aLinkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+}
+
+nsWifiMonitor::nsWifiMonitor(UniquePtr<mozilla::WifiScanner>&& aScanner)
+ : mWifiScanner(std::move(aScanner)) {
+ LOG(("Creating nsWifiMonitor"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ obsSvc->AddObserver(this, NS_NETWORK_LINK_TYPE_TOPIC, false);
+ obsSvc->AddObserver(this, "xpcom-shutdown", false);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && nls) {
+ uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ rv = nls->GetLinkType(&linkType);
+ if (NS_SUCCEEDED(rv)) {
+ mShouldPollForCurrentNetwork = ShouldPollForNetworkType(linkType);
+ if (ShouldPoll()) {
+ mPollingId = NextPollingIndex();
+ DispatchScanToBackgroundThread(mPollingId);
+ }
+ LOG(("nsWifiMonitor network type: %u | shouldPoll: %s", linkType,
+ mShouldPollForCurrentNetwork ? "true" : "false"));
+ }
+ }
+}
+nsWifiMonitor::~nsWifiMonitor() { LOG(("Destroying nsWifiMonitor")); }
+
+void nsWifiMonitor::Close() {
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TYPE_TOPIC);
+ obsSvc->RemoveObserver(this, "xpcom-shutdown");
+ }
+
+ mPollingId = 0;
+ if (mThread) {
+ mThread->Shutdown();
+ }
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ // Make sure any wifi-polling stops.
+ LOG(("nsWifiMonitor received shutdown"));
+ Close();
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ // Network connectivity has either been gained, lost, or changed (e.g.
+ // by changing Wifi network). Issue an immediate one-time scan.
+ // If we were polling, keep polling.
+ LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
+ " | received: " NS_NETWORK_LINK_TOPIC " with status %s",
+ this, static_cast<uint64_t>(mPollingId),
+ NS_ConvertUTF16toUTF8(data).get()));
+ DispatchScanToBackgroundThread(0);
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TYPE_TOPIC)) {
+ // Network type has changed (e.g. from wifi to mobile). When on some
+ // network types, we poll wifi. This event does not indicate that a
+ // new scan would be beneficial right now, so we only issue one if
+ // we need to begin polling.
+ // Use IDs to make sure only one task is polling at a time.
+ LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
+ " | received: " NS_NETWORK_LINK_TYPE_TOPIC " with status %s",
+ this, static_cast<uint64_t>(mPollingId),
+ NS_ConvertUTF16toUTF8(data).get()));
+
+ bool wasPolling = ShouldPoll();
+ MOZ_ASSERT(wasPolling || mPollingId == 0);
+
+ mShouldPollForCurrentNetwork = ShouldPollForNetworkType(data);
+ if (!wasPolling && ShouldPoll()) {
+ // We weren't polling, so start now.
+ mPollingId = NextPollingIndex();
+ DispatchScanToBackgroundThread(mPollingId);
+ } else if (!ShouldPoll()) {
+ // Stop polling if we were.
+ mPollingId = 0;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiMonitor::StartWatching(nsIWifiListener* aListener,
+ bool aForcePolling) {
+ LOG(("nsWifiMonitor::StartWatching %p | listener %p | mPollingId %" PRIu64
+ " | aForcePolling %s",
+ this, aListener, static_cast<uint64_t>(mPollingId),
+ aForcePolling ? "true" : "false"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mListeners.AppendElement(WifiListenerHolder(aListener, aForcePolling));
+
+ // Run a new scan to update the new listener. If we were polling then
+ // stop that polling and start a new polling interval now.
+ MOZ_ASSERT(mPollingId == 0 || ShouldPoll());
+ if (aForcePolling) {
+ ++mNumPollingListeners;
+ }
+ if (ShouldPoll()) {
+ mPollingId = NextPollingIndex();
+ }
+ return DispatchScanToBackgroundThread(mPollingId);
+}
+
+NS_IMETHODIMP nsWifiMonitor::StopWatching(nsIWifiListener* aListener) {
+ LOG(("nsWifiMonitor::StopWatching %p | listener %p | mPollingId %" PRIu64,
+ this, aListener, static_cast<uint64_t>(mPollingId)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ auto idx = mListeners.IndexOf(
+ WifiListenerHolder(aListener), 0,
+ [](const WifiListenerHolder& elt, const WifiListenerHolder& toRemove) {
+ return toRemove.mListener == elt.mListener ? 0 : 1;
+ });
+
+ if (idx == nsTArray<WifiListenerHolder>::NoIndex) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mListeners[idx].mShouldPoll) {
+ --mNumPollingListeners;
+ }
+
+ mListeners.RemoveElementAt(idx);
+
+ if (!ShouldPoll()) {
+ // Stop polling (if we were).
+ LOG(("nsWifiMonitor::StopWatching clearing polling ID"));
+ mPollingId = 0;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsWifiMonitor::DispatchScanToBackgroundThread(uint64_t aPollingId,
+ uint32_t aWaitMs) {
+ RefPtr<Runnable> runnable = NewRunnableMethod<uint64_t>(
+ "WifiScannerThread", this, &nsWifiMonitor::Scan, aPollingId);
+
+ if (!mThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#ifndef XP_MACOSX
+ nsIThreadManager::ThreadCreationOptions options = {};
+#else
+ // If this ASSERT fails, we've increased our default stack size and
+ // may no longer need to special-case the stack size on macOS.
+ static_assert(kMacOSWifiMonitorStackSize >
+ nsIThreadManager::DEFAULT_STACK_SIZE);
+
+ // Mac needs a stack size larger than the default for CoreWLAN.
+ nsIThreadManager::ThreadCreationOptions options = {
+ .stackSize = kMacOSWifiMonitorStackSize};
+#endif
+
+ nsresult rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread),
+ nullptr, options);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aWaitMs) {
+ return mThread->DelayedDispatch(runnable.forget(), aWaitMs);
+ }
+
+ return mThread->Dispatch(runnable.forget());
+}
+
+bool nsWifiMonitor::IsBackgroundThread() {
+ return NS_GetCurrentThread() == mThread;
+}
+
+void nsWifiMonitor::Scan(uint64_t aPollingId) {
+ MOZ_ASSERT(IsBackgroundThread());
+ LOG(("nsWifiMonitor::Scan aPollingId: %" PRIu64 " | mPollingId: %" PRIu64,
+ aPollingId, static_cast<uint64_t>(mPollingId)));
+
+ // If we are using a stale polling ID then stop. If this request to
+ // Scan is not for polling (aPollingId is 0) then always allow it.
+ if (aPollingId && mPollingId != aPollingId) {
+ LOG(("nsWifiMonitor::Scan stopping polling"));
+ return;
+ }
+
+ LOG(("nsWifiMonitor::Scan starting DoScan with id: %" PRIu64, aPollingId));
+ nsresult rv = DoScan();
+ LOG(("nsWifiMonitor::Scan DoScan complete | rv = %d",
+ static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv)) {
+ rv = NS_DispatchToMainThread(NewRunnableMethod<nsresult>(
+ "PassErrorToWifiListeners", this,
+ &nsWifiMonitor::PassErrorToWifiListeners, rv));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // If we are polling then we re-issue Scan after a delay.
+ // We re-check the polling IDs since mPollingId may have changed.
+ if (aPollingId && aPollingId == mPollingId) {
+ uint32_t periodMs = StaticPrefs::network_wifi_scanning_period();
+ if (periodMs) {
+ LOG(("nsWifiMonitor::Scan requesting future scan with id: %" PRIu64
+ " | periodMs: %u",
+ aPollingId, periodMs));
+ DispatchScanToBackgroundThread(aPollingId, periodMs);
+ } else {
+ // Polling for wifi-scans is disabled.
+ mPollingId = 0;
+ }
+ }
+
+ LOG(("nsWifiMonitor::Scan complete"));
+}
+
+nsresult nsWifiMonitor::DoScan() {
+ MOZ_ASSERT(IsBackgroundThread());
+
+ if (!mWifiScanner) {
+ LOG(("Constructing WifiScanner"));
+ mWifiScanner = MakeUnique<mozilla::WifiScannerImpl>();
+ }
+ MOZ_ASSERT(mWifiScanner);
+
+ LOG(("Scanning Wifi for access points"));
+ nsTArray<RefPtr<nsIWifiAccessPoint>> accessPoints;
+ nsresult rv = mWifiScanner->GetAccessPointsFromWLAN(accessPoints);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LOG(("Sorting wifi access points"));
+ accessPoints.Sort([](const RefPtr<nsIWifiAccessPoint>& ia,
+ const RefPtr<nsIWifiAccessPoint>& ib) {
+ const auto& a = static_cast<const nsWifiAccessPoint&>(*ia);
+ const auto& b = static_cast<const nsWifiAccessPoint&>(*ib);
+ return a.Compare(b);
+ });
+
+ // Sorted compare to see if access point list has changed.
+ LOG(("Checking for new access points"));
+ bool accessPointsChanged =
+ accessPoints.Length() != mLastAccessPoints.Length();
+ if (!accessPointsChanged) {
+ auto itAp = accessPoints.begin();
+ auto itLastAp = mLastAccessPoints.begin();
+ while (itAp != accessPoints.end()) {
+ const auto& a = static_cast<const nsWifiAccessPoint&>(**itAp);
+ const auto& b = static_cast<const nsWifiAccessPoint&>(**itLastAp);
+ if (a != b) {
+ accessPointsChanged = true;
+ break;
+ }
+ ++itAp;
+ ++itLastAp;
+ }
+ }
+
+ mLastAccessPoints = std::move(accessPoints);
+
+ LOG(("Sending Wifi access points to the main thread"));
+ auto* mainThread = GetMainThreadSerialEventTarget();
+ if (!mainThread) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_DispatchToMainThread(
+ NewRunnableMethod<const nsTArray<RefPtr<nsIWifiAccessPoint>>&&, bool>(
+ "CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners,
+ mLastAccessPoints.Clone(), accessPointsChanged));
+}
+
+nsresult nsWifiMonitor::CallWifiListeners(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>&& aAccessPoints,
+ bool aAccessPointsChanged) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("Sending wifi access points to the listeners"));
+ for (auto& listener : mListeners) {
+ if (!listener.mHasSentData || aAccessPointsChanged) {
+ listener.mHasSentData = true;
+ listener.mListener->OnChange(aAccessPoints);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsWifiMonitor::PassErrorToWifiListeners(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("About to send error to the wifi listeners"));
+ for (const auto& listener : mListeners) {
+ listener.mListener->OnError(rv);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiMonitor.h b/netwerk/wifi/nsWifiMonitor.h
new file mode 100644
index 0000000000..5a37cc75be
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.h
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsWifiMonitor__
+#define __nsWifiMonitor__
+
+#include "nsIWifiMonitor.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "nsIThread.h"
+#include "nsIRunnable.h"
+#include "nsCOMArray.h"
+#include "nsIWifiListener.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Logging.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Monitor.h"
+#include "WifiScanner.h"
+
+namespace mozilla {
+class TestWifiMonitor;
+}
+
+extern mozilla::LazyLogModule gWifiMonitorLog;
+
+class nsWifiAccessPoint;
+
+// Period between scans when on mobile network.
+#define WIFI_SCAN_INTERVAL_MS_PREF "network.wifi.scanning_period"
+
+#ifdef XP_MACOSX
+// Use a larger stack size for the wifi monitor thread of macOS, to
+// accommodate Core WLAN making large stack allocations.
+# define kMacOSWifiMonitorStackSize (512 * 1024)
+#endif
+
+struct WifiListenerHolder {
+ RefPtr<nsIWifiListener> mListener;
+ bool mShouldPoll;
+ bool mHasSentData = false;
+
+ explicit WifiListenerHolder(nsIWifiListener* aListener,
+ bool aShouldPoll = false)
+ : mListener(aListener), mShouldPoll(aShouldPoll) {}
+};
+
+class nsWifiMonitor final : public nsIWifiMonitor, public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWIFIMONITOR
+ NS_DECL_NSIOBSERVER
+
+ explicit nsWifiMonitor(
+ mozilla::UniquePtr<mozilla::WifiScanner>&& aScanner = nullptr);
+
+ private:
+ friend class mozilla::TestWifiMonitor;
+
+ ~nsWifiMonitor();
+
+ nsresult DispatchScanToBackgroundThread(uint64_t aPollingId = 0,
+ uint32_t aWaitMs = 0);
+
+ void Scan(uint64_t aPollingId);
+ nsresult DoScan();
+
+ nsresult CallWifiListeners(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>&& aAccessPoints,
+ bool aAccessPointsChanged);
+
+ nsresult PassErrorToWifiListeners(nsresult rv);
+
+ void Close();
+
+ bool IsBackgroundThread();
+
+ bool ShouldPoll() {
+ MOZ_ASSERT(!IsBackgroundThread());
+ return (mShouldPollForCurrentNetwork && !mListeners.IsEmpty()) ||
+ mNumPollingListeners > 0;
+ };
+
+#ifdef ENABLE_TESTS
+ // Test-only function that confirms we "should" be polling. May be wrong
+ // if somehow the polling tasks are not set to run on the background
+ // thread.
+ bool IsPolling() { return mThread && mPollingId; }
+#endif
+
+ // Main thread only.
+ nsCOMPtr<nsIThread> mThread;
+
+ // Main thread only.
+ nsTArray<WifiListenerHolder> mListeners;
+
+ // Background thread only.
+ mozilla::UniquePtr<mozilla::WifiScanner> mWifiScanner;
+
+ // Background thread only. Sorted.
+ nsTArray<RefPtr<nsIWifiAccessPoint>> mLastAccessPoints;
+
+ // Wifi-scanning requests may poll, meaning they will run repeatedly on
+ // a scheduled time period. If this value is 0 then polling is not running,
+ // otherwise, it indicates the "ID" of the polling that is running. if some
+ // other polling (with different ID) is running, it will stop, not iterate.
+ mozilla::Atomic<uint64_t> mPollingId;
+
+ // Number of current listeners that requested that the wifi scan poll
+ // periodically.
+ // Main thread only.
+ uint32_t mNumPollingListeners = 0;
+
+ // True if the current network type is one that requires polling
+ // (i.e. a "mobile" network type).
+ // Main thread only.
+ bool mShouldPollForCurrentNetwork = false;
+};
+
+#endif
diff --git a/netwerk/wifi/solaris/SolarisWifiScanner.cpp b/netwerk/wifi/solaris/SolarisWifiScanner.cpp
new file mode 100644
index 0000000000..6f30a81197
--- /dev/null
+++ b/netwerk/wifi/solaris/SolarisWifiScanner.cpp
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+
+#include "nsCRT.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+#include "SolarisWifiScanner.h"
+
+#include <glib.h>
+
+#define DLADM_STRSIZE 256
+#define DLADM_SECTIONS 3
+
+using namespace mozilla;
+
+struct val_strength_t {
+ const char* strength_name;
+ int signal_value;
+};
+
+static val_strength_t strength_vals[] = {{"very weak", -112},
+ {"weak", -88},
+ {"good", -68},
+ {"very good", -40},
+ {"excellent", -16}};
+
+static nsWifiAccessPoint* do_parse_str(char* bssid_str, char* essid_str,
+ char* strength) {
+ unsigned char mac_as_int[6] = {0};
+ sscanf(bssid_str, "%x:%x:%x:%x:%x:%x", &mac_as_int[0], &mac_as_int[1],
+ &mac_as_int[2], &mac_as_int[3], &mac_as_int[4], &mac_as_int[5]);
+
+ int signal = 0;
+ uint32_t strength_vals_count = sizeof(strength_vals) / sizeof(val_strength_t);
+ for (uint32_t i = 0; i < strength_vals_count; i++) {
+ if (!nsCRT::strncasecmp(strength, strength_vals[i].strength_name,
+ DLADM_STRSIZE)) {
+ signal = strength_vals[i].signal_value;
+ break;
+ }
+ }
+
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ if (ap) {
+ ap->setMac(mac_as_int);
+ ap->setSignal(signal);
+ size_t len = essid_str ? strnlen(essid_str, DLADM_STRSIZE) : 0;
+ ap->setSSID(essid_str, len);
+ }
+ return ap;
+}
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ GError* err = nullptr;
+ char* sout = nullptr;
+ char* serr = nullptr;
+ int exit_status = 0;
+ char* dladm_args[] = {
+ "/usr/bin/pfexec", "/usr/sbin/dladm", "scan-wifi", "-p", "-o",
+ "BSSID,ESSID,STRENGTH"};
+
+ gboolean rv = g_spawn_sync("/", dladm_args, nullptr, (GSpawnFlags)0, nullptr,
+ nullptr, &sout, &serr, &exit_status, &err);
+ if (rv && !exit_status) {
+ char wlan[DLADM_SECTIONS][DLADM_STRSIZE + 1];
+ uint32_t section = 0;
+ uint32_t sout_scan = 0;
+ uint32_t wlan_put = 0;
+ bool escape = false;
+ nsWifiAccessPoint* ap;
+ char sout_char;
+ do {
+ sout_char = sout[sout_scan++];
+ if (escape) {
+ escape = false;
+ if (sout_char != '\0') {
+ wlan[section][wlan_put++] = sout_char;
+ continue;
+ }
+ }
+
+ if (sout_char == '\\') {
+ escape = true;
+ continue;
+ }
+
+ if (sout_char == ':') {
+ wlan[section][wlan_put] = '\0';
+ section++;
+ wlan_put = 0;
+ continue;
+ }
+
+ if ((sout_char == '\0') || (sout_char == '\n')) {
+ wlan[section][wlan_put] = '\0';
+ if (section == DLADM_SECTIONS - 1) {
+ ap = do_parse_str(wlan[0], wlan[1], wlan[2]);
+ if (ap) {
+ accessPoints.AppendElement(ap);
+ }
+ }
+ section = 0;
+ wlan_put = 0;
+ continue;
+ }
+
+ wlan[section][wlan_put++] = sout_char;
+
+ } while ((wlan_put <= DLADM_STRSIZE) && (section < DLADM_SECTIONS) &&
+ (sout_char != '\0'));
+ }
+
+ g_free(sout);
+ g_free(serr);
+}
diff --git a/netwerk/wifi/solaris/SolarisWifiScanner.h b/netwerk/wifi/solaris/SolarisWifiScanner.h
new file mode 100644
index 0000000000..1bd5bf0eb5
--- /dev/null
+++ b/netwerk/wifi/solaris/SolarisWifiScanner.h
@@ -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/. */
+
+#pragma once
+
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/win/WinWifiScanner.cpp b/netwerk/wifi/win/WinWifiScanner.cpp
new file mode 100644
index 0000000000..b24fb4a2f1
--- /dev/null
+++ b/netwerk/wifi/win/WinWifiScanner.cpp
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWifiAccessPoint.h"
+#include "WinWifiScanner.h"
+
+#define DOT11_BSS_TYPE_UNUSED static_cast<DOT11_BSS_TYPE>(0)
+
+namespace mozilla {
+
+class InterfaceScanCallbackData {
+ public:
+ explicit InterfaceScanCallbackData(uint32_t numInterfaces)
+ : mCurrentlyScanningInterfaces(numInterfaces) {
+ mAllInterfacesDoneScanningEvent =
+ ::CreateEventW(nullptr, // null security
+ TRUE, // manual reset event
+ FALSE, // initially nonsignaled
+ nullptr); // not named
+ MOZ_ASSERT(NULL != mAllInterfacesDoneScanningEvent);
+ }
+
+ ~InterfaceScanCallbackData() {
+ ::CloseHandle(mAllInterfacesDoneScanningEvent);
+ }
+
+ void OnInterfaceScanComplete() {
+ uint32_t val = ::InterlockedDecrement(&mCurrentlyScanningInterfaces);
+ if (!val) {
+ ::SetEvent(mAllInterfacesDoneScanningEvent);
+ }
+ }
+
+ void WaitForAllInterfacesToFinishScanning(uint32_t msToWait) {
+ ::WaitForSingleObject(mAllInterfacesDoneScanningEvent, msToWait);
+ }
+
+ private:
+ volatile uint32_t mCurrentlyScanningInterfaces;
+ HANDLE mAllInterfacesDoneScanningEvent;
+};
+
+static void WINAPI OnScanComplete(PWLAN_NOTIFICATION_DATA data, PVOID context) {
+ if (WLAN_NOTIFICATION_SOURCE_ACM != data->NotificationSource) {
+ return;
+ }
+
+ if (wlan_notification_acm_scan_complete != data->NotificationCode &&
+ wlan_notification_acm_scan_fail != data->NotificationCode) {
+ return;
+ }
+
+ InterfaceScanCallbackData* cbData =
+ reinterpret_cast<InterfaceScanCallbackData*>(context);
+ cbData->OnInterfaceScanComplete();
+}
+
+WifiScannerImpl::WifiScannerImpl() {
+ // NOTE: We assume that, if we were unable to load the WLAN library when
+ // we initially tried, we will not be able to load it in the future.
+ // Technically, on Windows XP SP2, a user could install the redistributable
+ // and make our assumption incorrect. We opt to avoid making a bunch of
+ // spurious LoadLibrary calls in the common case rather than load the
+ // WLAN API in the edge case.
+ mWlanLibrary.reset(WinWLANLibrary::Load());
+ if (!mWlanLibrary) {
+ NS_WARNING("Could not initialize Windows Wi-Fi scanner");
+ }
+}
+
+WifiScannerImpl::~WifiScannerImpl() {}
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ accessPoints.Clear();
+
+ // NOTE: We do not try to load the WLAN library if we previously failed
+ // to load it. See the note in WifiScannerImpl constructor
+ if (!mWlanLibrary) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
+ WLAN_INTERFACE_INFO_LIST* interface_list = nullptr;
+ if (ERROR_SUCCESS !=
+ (*mWlanLibrary->GetWlanEnumInterfacesPtr())(mWlanLibrary->GetWLANHandle(),
+ nullptr, &interface_list)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This ensures we call WlanFreeMemory on interface_list
+ ScopedWLANObject scopedInterfaceList(*mWlanLibrary, interface_list);
+
+ if (!interface_list->dwNumberOfItems) {
+ return NS_OK;
+ }
+
+ InterfaceScanCallbackData cbData(interface_list->dwNumberOfItems);
+
+ DWORD wlanNotifySource;
+ if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
+ mWlanLibrary->GetWLANHandle(),
+ WLAN_NOTIFICATION_SOURCE_ACM, TRUE,
+ (WLAN_NOTIFICATION_CALLBACK)OnScanComplete, &cbData,
+ NULL, &wlanNotifySource)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Go through the list of interfaces and call `WlanScan` on each
+ for (unsigned int i = 0; i < interface_list->dwNumberOfItems; ++i) {
+ if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanScanPtr())(
+ mWlanLibrary->GetWLANHandle(),
+ &interface_list->InterfaceInfo[i].InterfaceGuid,
+ NULL, NULL, NULL)) {
+ cbData.OnInterfaceScanComplete();
+ }
+ }
+
+ // From the MSDN documentation:
+ // "Wireless network drivers that meet Windows logo requirements are
+ // required to complete a WlanScan function request in 4 seconds"
+ cbData.WaitForAllInterfacesToFinishScanning(5000);
+
+ // Unregister for the notifications. The documentation mentions that,
+ // if a callback is currently running, this will wait for the callback
+ // to complete.
+ (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
+ mWlanLibrary->GetWLANHandle(), WLAN_NOTIFICATION_SOURCE_NONE, TRUE, NULL,
+ NULL, NULL, &wlanNotifySource);
+
+ // Go through the list of interfaces and get the data for each.
+ for (uint32_t i = 0; i < interface_list->dwNumberOfItems; ++i) {
+ WLAN_BSS_LIST* bss_list;
+ if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanGetNetworkBssListPtr())(
+ mWlanLibrary->GetWLANHandle(),
+ &interface_list->InterfaceInfo[i].InterfaceGuid,
+ nullptr, // Use all SSIDs.
+ DOT11_BSS_TYPE_UNUSED,
+ false, // bSecurityEnabled -
+ // unused
+ nullptr, // reserved
+ &bss_list)) {
+ continue;
+ }
+
+ // This ensures we call WlanFreeMemory on bss_list
+ ScopedWLANObject scopedBssList(*mWlanLibrary, bss_list);
+
+ // Store each discovered access point in our outparam
+ for (int j = 0; j < static_cast<int>(bss_list->dwNumberOfItems); ++j) {
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ if (!ap) {
+ continue;
+ }
+
+ const WLAN_BSS_ENTRY bss_entry = bss_list->wlanBssEntries[j];
+ ap->setMac(bss_entry.dot11Bssid);
+ ap->setSignal(bss_entry.lRssi);
+ ap->setSSID(reinterpret_cast<char const*>(bss_entry.dot11Ssid.ucSSID),
+ bss_entry.dot11Ssid.uSSIDLength);
+
+ accessPoints.AppendElement(ap);
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/netwerk/wifi/win/WinWifiScanner.h b/netwerk/wifi/win/WinWifiScanner.h
new file mode 100644
index 0000000000..ce36c1156d
--- /dev/null
+++ b/netwerk/wifi/win/WinWifiScanner.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/. */
+
+#pragma once
+
+#include "mozilla/UniquePtr.h"
+#include "WlanLibrary.h"
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+
+ private:
+ mozilla::UniquePtr<WinWLANLibrary> mWlanLibrary;
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/win/WlanLibrary.cpp b/netwerk/wifi/win/WlanLibrary.cpp
new file mode 100644
index 0000000000..0fd36be660
--- /dev/null
+++ b/netwerk/wifi/win/WlanLibrary.cpp
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WlanLibrary.h"
+
+// Moz headers (alphabetical)
+
+WinWLANLibrary* WinWLANLibrary::Load() {
+ WinWLANLibrary* ret = new WinWLANLibrary();
+ if (!ret) {
+ return nullptr;
+ }
+
+ if (!ret->Initialize()) {
+ delete ret;
+ return nullptr;
+ }
+
+ return ret;
+}
+
+HANDLE
+WinWLANLibrary::GetWLANHandle() const { return mWlanHandle; }
+
+decltype(::WlanEnumInterfaces)* WinWLANLibrary::GetWlanEnumInterfacesPtr()
+ const {
+ return mWlanEnumInterfacesPtr;
+}
+
+decltype(::WlanGetNetworkBssList)* WinWLANLibrary::GetWlanGetNetworkBssListPtr()
+ const {
+ return mWlanGetNetworkBssListPtr;
+}
+
+decltype(::WlanFreeMemory)* WinWLANLibrary::GetWlanFreeMemoryPtr() const {
+ return mWlanFreeMemoryPtr;
+}
+
+decltype(::WlanCloseHandle)* WinWLANLibrary::GetWlanCloseHandlePtr() const {
+ return mWlanCloseHandlePtr;
+}
+
+decltype(::WlanOpenHandle)* WinWLANLibrary::GetWlanOpenHandlePtr() const {
+ return mWlanOpenHandlePtr;
+}
+
+decltype(::WlanRegisterNotification)*
+WinWLANLibrary::GetWlanRegisterNotificationPtr() const {
+ return mWlanRegisterNotificationPtr;
+}
+
+decltype(::WlanScan)* WinWLANLibrary::GetWlanScanPtr() const {
+ return mWlanScanPtr;
+}
+
+bool WinWLANLibrary::Initialize() {
+ mWlanLibrary = LoadLibraryW(L"Wlanapi.dll");
+ if (!mWlanLibrary) {
+ return false;
+ }
+
+ mWlanOpenHandlePtr = (decltype(::WlanOpenHandle)*)GetProcAddress(
+ mWlanLibrary, "WlanOpenHandle");
+ mWlanEnumInterfacesPtr = (decltype(::WlanEnumInterfaces)*)GetProcAddress(
+ mWlanLibrary, "WlanEnumInterfaces");
+ mWlanRegisterNotificationPtr =
+ (decltype(::WlanRegisterNotification)*)GetProcAddress(
+ mWlanLibrary, "WlanRegisterNotification");
+ mWlanScanPtr =
+ (decltype(::WlanScan)*)GetProcAddress(mWlanLibrary, "WlanScan");
+
+ mWlanFreeMemoryPtr = (decltype(::WlanFreeMemory)*)GetProcAddress(
+ mWlanLibrary, "WlanFreeMemory");
+ mWlanCloseHandlePtr = (decltype(::WlanCloseHandle)*)GetProcAddress(
+ mWlanLibrary, "WlanCloseHandle");
+ mWlanGetNetworkBssListPtr =
+ (decltype(::WlanGetNetworkBssList)*)GetProcAddress(
+ mWlanLibrary, "WlanGetNetworkBssList");
+
+ if (!mWlanOpenHandlePtr || !mWlanEnumInterfacesPtr ||
+ !mWlanRegisterNotificationPtr || !mWlanGetNetworkBssListPtr ||
+ !mWlanScanPtr || !mWlanFreeMemoryPtr || !mWlanCloseHandlePtr) {
+ return false;
+ }
+
+ // Get the handle to the WLAN API.
+ DWORD negotiated_version;
+ // We could be executing on either Windows XP or Windows Vista, so use the
+ // lower version of the client WLAN API. It seems that the negotiated version
+ // is the Vista version irrespective of what we pass!
+ static const int kXpWlanClientVersion = 1;
+ if (ERROR_SUCCESS != (*mWlanOpenHandlePtr)(kXpWlanClientVersion, nullptr,
+ &negotiated_version,
+ &mWlanHandle)) {
+ return false;
+ }
+
+ return true;
+}
+
+WinWLANLibrary::~WinWLANLibrary() {
+ if (mWlanLibrary) {
+ if (mWlanHandle) {
+ (*mWlanCloseHandlePtr)(mWlanLibrary, mWlanHandle);
+ mWlanHandle = nullptr;
+ }
+ ::FreeLibrary(mWlanLibrary);
+ mWlanLibrary = nullptr;
+ }
+}
diff --git a/netwerk/wifi/win/WlanLibrary.h b/netwerk/wifi/win/WlanLibrary.h
new file mode 100644
index 0000000000..8d996f881d
--- /dev/null
+++ b/netwerk/wifi/win/WlanLibrary.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+// Moz headers (alphabetical)
+
+// System headers (alphabetical)
+#include <windows.h> // HINSTANCE, HANDLE
+#include <wlanapi.h> // Wlan* functions
+
+class WinWLANLibrary {
+ public:
+ static WinWLANLibrary* Load();
+ ~WinWLANLibrary();
+
+ HANDLE GetWLANHandle() const;
+ decltype(::WlanEnumInterfaces)* GetWlanEnumInterfacesPtr() const;
+ decltype(::WlanGetNetworkBssList)* GetWlanGetNetworkBssListPtr() const;
+ decltype(::WlanFreeMemory)* GetWlanFreeMemoryPtr() const;
+ decltype(::WlanCloseHandle)* GetWlanCloseHandlePtr() const;
+ decltype(::WlanOpenHandle)* GetWlanOpenHandlePtr() const;
+ decltype(::WlanRegisterNotification)* GetWlanRegisterNotificationPtr() const;
+ decltype(::WlanScan)* GetWlanScanPtr() const;
+
+ private:
+ WinWLANLibrary() = default;
+ bool Initialize();
+
+ HMODULE mWlanLibrary = nullptr;
+ HANDLE mWlanHandle = nullptr;
+ decltype(::WlanEnumInterfaces)* mWlanEnumInterfacesPtr = nullptr;
+ decltype(::WlanGetNetworkBssList)* mWlanGetNetworkBssListPtr = nullptr;
+ decltype(::WlanFreeMemory)* mWlanFreeMemoryPtr = nullptr;
+ decltype(::WlanCloseHandle)* mWlanCloseHandlePtr = nullptr;
+ decltype(::WlanOpenHandle)* mWlanOpenHandlePtr = nullptr;
+ decltype(::WlanRegisterNotification)* mWlanRegisterNotificationPtr = nullptr;
+ decltype(::WlanScan)* mWlanScanPtr = nullptr;
+};
+
+class ScopedWLANObject {
+ public:
+ ScopedWLANObject(const WinWLANLibrary& library, void* object)
+ : mLibrary(library), mObject(object) {}
+
+ ~ScopedWLANObject() { (*(mLibrary.GetWlanFreeMemoryPtr()))(mObject); }
+
+ private:
+ const WinWLANLibrary& mLibrary;
+ void* mObject;
+};